summaryrefslogtreecommitdiffstats
path: root/browser
diff options
context:
space:
mode:
Diffstat (limited to 'browser')
-rw-r--r--browser/.eslintrc.js15
-rw-r--r--browser/LICENSE7
-rw-r--r--browser/Makefile.in16
-rw-r--r--browser/app-rules.mk1
-rw-r--r--browser/app.mozbuild15
-rw-r--r--browser/app/Makefile.in94
-rw-r--r--browser/app/blocklist.xml5085
-rw-r--r--browser/app/firefox.exe.manifest43
-rw-r--r--browser/app/macbuild/Contents/Info.plist.in224
-rw-r--r--browser/app/macbuild/Contents/MacOS-files.in9
-rw-r--r--browser/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in5
-rw-r--r--browser/app/macversion.py44
-rw-r--r--browser/app/module.ver8
-rw-r--r--browser/app/moz.build91
-rw-r--r--browser/app/nsBrowserApp.cpp429
-rw-r--r--browser/app/permissions23
-rw-r--r--browser/app/profile/channel-prefs.js5
-rw-r--r--browser/app/profile/extensions/moz.build7
-rw-r--r--browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in40
-rw-r--r--browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build11
-rw-r--r--browser/app/profile/firefox.js1571
-rw-r--r--browser/app/profile/pagethemes.rdf7
-rw-r--r--browser/app/profile/prefs.js13
-rw-r--r--browser/app/splash.rc21
-rw-r--r--browser/base/.eslintrc.js11
-rw-r--r--browser/base/content/aboutDialog-appUpdater.js428
-rw-r--r--browser/base/content/aboutDialog.css97
-rw-r--r--browser/base/content/aboutDialog.js80
-rw-r--r--browser/base/content/aboutDialog.xul157
-rw-r--r--browser/base/content/aboutNetError.xhtml699
-rw-r--r--browser/base/content/aboutProviderDirectory.xhtml60
-rw-r--r--browser/base/content/aboutRobots-icon.pngbin0 -> 9817 bytes
-rw-r--r--browser/base/content/aboutRobots-widget-left.pngbin0 -> 2224 bytes
-rw-r--r--browser/base/content/aboutRobots.xhtml108
-rw-r--r--browser/base/content/aboutSocialError.xhtml111
-rw-r--r--browser/base/content/aboutTabCrashed.css11
-rw-r--r--browser/base/content/aboutTabCrashed.js309
-rw-r--r--browser/base/content/aboutTabCrashed.xhtml97
-rw-r--r--browser/base/content/aboutaccounts/aboutaccounts.css24
-rw-r--r--browser/base/content/aboutaccounts/aboutaccounts.js543
-rw-r--r--browser/base/content/aboutaccounts/aboutaccounts.xhtml112
-rw-r--r--browser/base/content/aboutaccounts/images/fox.pngbin0 -> 1951 bytes
-rw-r--r--browser/base/content/aboutaccounts/images/graphic_sync_intro.pngbin0 -> 6441 bytes
-rw-r--r--browser/base/content/aboutaccounts/images/graphic_sync_intro@2x.pngbin0 -> 12852 bytes
-rw-r--r--browser/base/content/aboutaccounts/main.css166
-rw-r--r--browser/base/content/aboutaccounts/normalize.css402
-rw-r--r--browser/base/content/abouthealthreport/abouthealth.css15
-rw-r--r--browser/base/content/abouthealthreport/abouthealth.js180
-rw-r--r--browser/base/content/abouthealthreport/abouthealth.xhtml31
-rw-r--r--browser/base/content/abouthome/aboutHome.css454
-rw-r--r--browser/base/content/abouthome/aboutHome.js398
-rw-r--r--browser/base/content/abouthome/aboutHome.xhtml79
-rw-r--r--browser/base/content/abouthome/addons.pngbin0 -> 1444 bytes
-rw-r--r--browser/base/content/abouthome/addons@2x.pngbin0 -> 3783 bytes
-rw-r--r--browser/base/content/abouthome/bookmarks.pngbin0 -> 1276 bytes
-rw-r--r--browser/base/content/abouthome/bookmarks@2x.pngbin0 -> 2946 bytes
-rw-r--r--browser/base/content/abouthome/downloads.pngbin0 -> 898 bytes
-rw-r--r--browser/base/content/abouthome/downloads@2x.pngbin0 -> 2018 bytes
-rw-r--r--browser/base/content/abouthome/history.pngbin0 -> 1654 bytes
-rw-r--r--browser/base/content/abouthome/history@2x.pngbin0 -> 4629 bytes
-rw-r--r--browser/base/content/abouthome/mozilla.pngbin0 -> 2684 bytes
-rw-r--r--browser/base/content/abouthome/mozilla@2x.pngbin0 -> 5647 bytes
-rw-r--r--browser/base/content/abouthome/restore-large.pngbin0 -> 2841 bytes
-rw-r--r--browser/base/content/abouthome/restore-large@2x.pngbin0 -> 7267 bytes
-rw-r--r--browser/base/content/abouthome/restore.pngbin0 -> 1796 bytes
-rw-r--r--browser/base/content/abouthome/restore@2x.pngbin0 -> 4810 bytes
-rw-r--r--browser/base/content/abouthome/settings.pngbin0 -> 1557 bytes
-rw-r--r--browser/base/content/abouthome/settings@2x.pngbin0 -> 3836 bytes
-rw-r--r--browser/base/content/abouthome/snippet1.pngbin0 -> 1470 bytes
-rw-r--r--browser/base/content/abouthome/snippet1@2x.pngbin0 -> 3243 bytes
-rw-r--r--browser/base/content/abouthome/snippet2.pngbin0 -> 3287 bytes
-rw-r--r--browser/base/content/abouthome/snippet2@2x.pngbin0 -> 11027 bytes
-rw-r--r--browser/base/content/abouthome/sync.pngbin0 -> 1879 bytes
-rw-r--r--browser/base/content/abouthome/sync@2x.pngbin0 -> 4615 bytes
-rw-r--r--browser/base/content/baseMenuOverlay.xul118
-rw-r--r--browser/base/content/blockedSite.xhtml196
-rw-r--r--browser/base/content/browser-addons.js747
-rw-r--r--browser/base/content/browser-captivePortal.js257
-rw-r--r--browser/base/content/browser-charsetmenu.inc12
-rw-r--r--browser/base/content/browser-context.inc472
-rw-r--r--browser/base/content/browser-ctrlTab.js587
-rw-r--r--browser/base/content/browser-customization.js100
-rw-r--r--browser/base/content/browser-data-submission-info-bar.js127
-rw-r--r--browser/base/content/browser-devedition.js142
-rw-r--r--browser/base/content/browser-doctype.inc23
-rw-r--r--browser/base/content/browser-feeds.js646
-rw-r--r--browser/base/content/browser-fullScreenAndPointerLock.js673
-rw-r--r--browser/base/content/browser-fullZoom.js526
-rw-r--r--browser/base/content/browser-fxaccounts.js459
-rw-r--r--browser/base/content/browser-gestureSupport.js1244
-rw-r--r--browser/base/content/browser-media.js365
-rw-r--r--browser/base/content/browser-menubar.inc535
-rw-r--r--browser/base/content/browser-places.js2021
-rw-r--r--browser/base/content/browser-plugins.js548
-rw-r--r--browser/base/content/browser-refreshblocker.js84
-rw-r--r--browser/base/content/browser-safebrowsing.js48
-rw-r--r--browser/base/content/browser-sets.inc380
-rw-r--r--browser/base/content/browser-sidebar.js337
-rw-r--r--browser/base/content/browser-social.js503
-rw-r--r--browser/base/content/browser-syncui.js544
-rw-r--r--browser/base/content/browser-tabPreviews.xml37
-rw-r--r--browser/base/content/browser-tabsintitlebar-stub.js17
-rw-r--r--browser/base/content/browser-tabsintitlebar.js307
-rw-r--r--browser/base/content/browser-thumbnails.js142
-rw-r--r--browser/base/content/browser-trackingprotection.js239
-rw-r--r--browser/base/content/browser.css1244
-rwxr-xr-xbrowser/base/content/browser.js8281
-rw-r--r--browser/base/content/browser.xul1134
-rw-r--r--browser/base/content/browserMountPoints.inc12
-rw-r--r--browser/base/content/content.js1503
-rw-r--r--browser/base/content/contentSearchUI.css161
-rw-r--r--browser/base/content/contentSearchUI.js915
-rw-r--r--browser/base/content/defaultthemes/1.footer.jpgbin0 -> 151200 bytes
-rw-r--r--browser/base/content/defaultthemes/1.header.jpgbin0 -> 266398 bytes
-rw-r--r--browser/base/content/defaultthemes/1.icon.jpgbin0 -> 1093 bytes
-rw-r--r--browser/base/content/defaultthemes/1.preview.jpgbin0 -> 7953 bytes
-rw-r--r--browser/base/content/defaultthemes/2.footer.jpgbin0 -> 81134 bytes
-rw-r--r--browser/base/content/defaultthemes/2.header.jpgbin0 -> 173983 bytes
-rw-r--r--browser/base/content/defaultthemes/2.icon.jpgbin0 -> 509 bytes
-rw-r--r--browser/base/content/defaultthemes/2.preview.jpgbin0 -> 2877 bytes
-rw-r--r--browser/base/content/defaultthemes/3.footer.pngbin0 -> 180454 bytes
-rw-r--r--browser/base/content/defaultthemes/3.header.pngbin0 -> 293504 bytes
-rw-r--r--browser/base/content/defaultthemes/3.icon.pngbin0 -> 896 bytes
-rw-r--r--browser/base/content/defaultthemes/3.preview.pngbin0 -> 56585 bytes
-rw-r--r--browser/base/content/defaultthemes/4.footer.pngbin0 -> 384076 bytes
-rw-r--r--browser/base/content/defaultthemes/4.header.pngbin0 -> 769368 bytes
-rw-r--r--browser/base/content/defaultthemes/4.icon.pngbin0 -> 731 bytes
-rw-r--r--browser/base/content/defaultthemes/4.preview.pngbin0 -> 95328 bytes
-rw-r--r--browser/base/content/defaultthemes/5.footer.pngbin0 -> 9760 bytes
-rw-r--r--browser/base/content/defaultthemes/5.header.pngbin0 -> 9760 bytes
-rw-r--r--browser/base/content/defaultthemes/5.icon.jpgbin0 -> 267 bytes
-rw-r--r--browser/base/content/defaultthemes/5.preview.jpgbin0 -> 2837 bytes
-rw-r--r--browser/base/content/defaultthemes/devedition.header.pngbin0 -> 95 bytes
-rw-r--r--browser/base/content/defaultthemes/devedition.icon.pngbin0 -> 2402 bytes
-rw-r--r--browser/base/content/docs/sslerrorreport/dataformat.rst54
-rw-r--r--browser/base/content/docs/sslerrorreport/index.rst15
-rw-r--r--browser/base/content/docs/sslerrorreport/preferences.rst23
-rw-r--r--browser/base/content/downloadManagerOverlay.xul32
-rw-r--r--browser/base/content/gcli_sec_bad.svg7
-rw-r--r--browser/base/content/gcli_sec_good.svg4
-rw-r--r--browser/base/content/gcli_sec_moderate.svg4
-rwxr-xr-xbrowser/base/content/global-scripts.inc38
-rw-r--r--browser/base/content/hiddenWindow.xul20
-rw-r--r--browser/base/content/macBrowserOverlay.xul66
-rw-r--r--browser/base/content/newtab/alternativeDefaultSites.json50
-rw-r--r--browser/base/content/newtab/cells.js126
-rw-r--r--browser/base/content/newtab/customize.js133
-rw-r--r--browser/base/content/newtab/drag.js151
-rw-r--r--browser/base/content/newtab/dragDataHelper.js22
-rw-r--r--browser/base/content/newtab/drop.js150
-rw-r--r--browser/base/content/newtab/dropPreview.js222
-rw-r--r--browser/base/content/newtab/dropTargetShim.js232
-rw-r--r--browser/base/content/newtab/grid.js279
-rw-r--r--browser/base/content/newtab/newTab.css654
-rw-r--r--browser/base/content/newtab/newTab.inadjacent.json3209
-rw-r--r--browser/base/content/newtab/newTab.js71
-rw-r--r--browser/base/content/newtab/newTab.xhtml96
-rw-r--r--browser/base/content/newtab/page.js297
-rw-r--r--browser/base/content/newtab/search.js15
-rw-r--r--browser/base/content/newtab/sites.js440
-rw-r--r--browser/base/content/newtab/transformations.js270
-rw-r--r--browser/base/content/newtab/undo.js116
-rw-r--r--browser/base/content/newtab/updater.js177
-rw-r--r--browser/base/content/nsContextMenu.js1878
-rw-r--r--browser/base/content/overrides/app-license.html6
-rw-r--r--browser/base/content/pageinfo/feeds.js32
-rw-r--r--browser/base/content/pageinfo/feeds.xml40
-rw-r--r--browser/base/content/pageinfo/pageInfo.css26
-rw-r--r--browser/base/content/pageinfo/pageInfo.js1140
-rw-r--r--browser/base/content/pageinfo/pageInfo.xml20
-rw-r--r--browser/base/content/pageinfo/pageInfo.xul438
-rw-r--r--browser/base/content/pageinfo/permissions.js334
-rw-r--r--browser/base/content/pageinfo/security.js388
-rw-r--r--browser/base/content/popup-notifications.inc81
-rw-r--r--browser/base/content/report-phishing-overlay.xul35
-rw-r--r--browser/base/content/safeMode.css8
-rw-r--r--browser/base/content/safeMode.js82
-rw-r--r--browser/base/content/safeMode.xul51
-rw-r--r--browser/base/content/sanitize.js910
-rw-r--r--browser/base/content/sanitize.xul183
-rw-r--r--browser/base/content/sanitizeDialog.css23
-rw-r--r--browser/base/content/sanitizeDialog.js889
-rw-r--r--browser/base/content/social-content.js172
-rw-r--r--browser/base/content/softwareUpdateOverlay.xul18
-rw-r--r--browser/base/content/sync/aboutSyncTabs-bindings.xml46
-rw-r--r--browser/base/content/sync/aboutSyncTabs.css11
-rw-r--r--browser/base/content/sync/aboutSyncTabs.js361
-rw-r--r--browser/base/content/sync/aboutSyncTabs.xul68
-rw-r--r--browser/base/content/sync/addDevice.js157
-rw-r--r--browser/base/content/sync/addDevice.xul129
-rw-r--r--browser/base/content/sync/customize.css28
-rw-r--r--browser/base/content/sync/customize.js25
-rw-r--r--browser/base/content/sync/customize.xul67
-rw-r--r--browser/base/content/sync/genericChange.js233
-rw-r--r--browser/base/content/sync/genericChange.xul123
-rw-r--r--browser/base/content/sync/key.xhtml54
-rw-r--r--browser/base/content/sync/setup.js1060
-rw-r--r--browser/base/content/sync/setup.xul490
-rw-r--r--browser/base/content/sync/utils.js231
-rw-r--r--browser/base/content/tab-content.js947
-rw-r--r--browser/base/content/tab-shape.inc.svg11
-rw-r--r--browser/base/content/tabbrowser.css98
-rw-r--r--browser/base/content/tabbrowser.xml7417
-rw-r--r--browser/base/content/test/alerts/.eslintrc.js7
-rw-r--r--browser/base/content/test/alerts/browser.ini12
-rw-r--r--browser/base/content/test/alerts/browser_notification_close.js71
-rw-r--r--browser/base/content/test/alerts/browser_notification_do_not_disturb.js80
-rw-r--r--browser/base/content/test/alerts/browser_notification_open_settings.js58
-rw-r--r--browser/base/content/test/alerts/browser_notification_permission_migration.js45
-rw-r--r--browser/base/content/test/alerts/browser_notification_remove_permission.js72
-rw-r--r--browser/base/content/test/alerts/browser_notification_replace.js38
-rw-r--r--browser/base/content/test/alerts/browser_notification_tab_switching.js80
-rw-r--r--browser/base/content/test/alerts/file_dom_notifications.html39
-rw-r--r--browser/base/content/test/alerts/head.js71
-rw-r--r--browser/base/content/test/captivePortal/browser.ini9
-rw-r--r--browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js119
-rw-r--r--browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js91
-rw-r--r--browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js82
-rw-r--r--browser/base/content/test/captivePortal/head.js181
-rw-r--r--browser/base/content/test/chrome/.eslintrc.js7
-rw-r--r--browser/base/content/test/chrome/chrome.ini3
-rw-r--r--browser/base/content/test/chrome/test_aboutCrashed.xul86
-rw-r--r--browser/base/content/test/general/.eslintrc.js8
-rw-r--r--browser/base/content/test/general/POSTSearchEngine.xml6
-rw-r--r--browser/base/content/test/general/aboutHome_content_script.js6
-rw-r--r--browser/base/content/test/general/accounts_testRemoteCommands.html83
-rw-r--r--browser/base/content/test/general/alltabslistener.html8
-rw-r--r--browser/base/content/test/general/app_bug575561.html18
-rw-r--r--browser/base/content/test/general/app_subframe_bug575561.html12
-rw-r--r--browser/base/content/test/general/audio.oggbin0 -> 14293 bytes
-rw-r--r--browser/base/content/test/general/benignPage.html12
-rw-r--r--browser/base/content/test/general/browser.ini494
-rw-r--r--browser/base/content/test/general/browser_PageMetaData_pushstate.js29
-rw-r--r--browser/base/content/test/general/browser_aboutAccounts.js499
-rw-r--r--browser/base/content/test/general/browser_aboutCertError.js409
-rw-r--r--browser/base/content/test/general/browser_aboutHealthReport.js139
-rw-r--r--browser/base/content/test/general/browser_aboutHome.js668
-rw-r--r--browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js28
-rw-r--r--browser/base/content/test/general/browser_aboutNetError.js47
-rw-r--r--browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js26
-rw-r--r--browser/base/content/test/general/browser_accesskeys.js82
-rw-r--r--browser/base/content/test/general/browser_addCertException.js50
-rw-r--r--browser/base/content/test/general/browser_addKeywordSearch.js81
-rw-r--r--browser/base/content/test/general/browser_alltabslistener.js206
-rw-r--r--browser/base/content/test/general/browser_audioTabIcon.js504
-rw-r--r--browser/base/content/test/general/browser_backButtonFitts.js42
-rw-r--r--browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js76
-rw-r--r--browser/base/content/test/general/browser_blob-channelname.js11
-rw-r--r--browser/base/content/test/general/browser_blockHPKP.js101
-rw-r--r--browser/base/content/test/general/browser_bookmark_popup.js431
-rw-r--r--browser/base/content/test/general/browser_bookmark_titles.js98
-rw-r--r--browser/base/content/test/general/browser_bug1015721.js54
-rw-r--r--browser/base/content/test/general/browser_bug1045809.js68
-rw-r--r--browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js36
-rw-r--r--browser/base/content/test/general/browser_bug1261299.js73
-rw-r--r--browser/base/content/test/general/browser_bug1297539.js114
-rw-r--r--browser/base/content/test/general/browser_bug1299667.js71
-rw-r--r--browser/base/content/test/general/browser_bug321000.js80
-rw-r--r--browser/base/content/test/general/browser_bug356571.js93
-rw-r--r--browser/base/content/test/general/browser_bug380960.js11
-rw-r--r--browser/base/content/test/general/browser_bug386835.js89
-rw-r--r--browser/base/content/test/general/browser_bug406216.js54
-rw-r--r--browser/base/content/test/general/browser_bug408415.js45
-rw-r--r--browser/base/content/test/general/browser_bug409481.js83
-rw-r--r--browser/base/content/test/general/browser_bug409624.js57
-rw-r--r--browser/base/content/test/general/browser_bug413915.js62
-rw-r--r--browser/base/content/test/general/browser_bug416661.js43
-rw-r--r--browser/base/content/test/general/browser_bug417483.js30
-rw-r--r--browser/base/content/test/general/browser_bug419612.js32
-rw-r--r--browser/base/content/test/general/browser_bug422590.js50
-rw-r--r--browser/base/content/test/general/browser_bug423833.js138
-rw-r--r--browser/base/content/test/general/browser_bug424101.js52
-rw-r--r--browser/base/content/test/general/browser_bug427559.js38
-rw-r--r--browser/base/content/test/general/browser_bug431826.js50
-rw-r--r--browser/base/content/test/general/browser_bug432599.js127
-rw-r--r--browser/base/content/test/general/browser_bug435035.js17
-rw-r--r--browser/base/content/test/general/browser_bug435325.js69
-rw-r--r--browser/base/content/test/general/browser_bug441778.js46
-rw-r--r--browser/base/content/test/general/browser_bug455852.js20
-rw-r--r--browser/base/content/test/general/browser_bug460146.js51
-rw-r--r--browser/base/content/test/general/browser_bug462289.js81
-rw-r--r--browser/base/content/test/general/browser_bug462673.js36
-rw-r--r--browser/base/content/test/general/browser_bug477014.js25
-rw-r--r--browser/base/content/test/general/browser_bug479408.js17
-rw-r--r--browser/base/content/test/general/browser_bug479408_sample.html4
-rw-r--r--browser/base/content/test/general/browser_bug481560.js21
-rw-r--r--browser/base/content/test/general/browser_bug484315.js23
-rw-r--r--browser/base/content/test/general/browser_bug491431.js34
-rw-r--r--browser/base/content/test/general/browser_bug495058.js38
-rw-r--r--browser/base/content/test/general/browser_bug517902.js42
-rw-r--r--browser/base/content/test/general/browser_bug519216.js45
-rw-r--r--browser/base/content/test/general/browser_bug520538.js15
-rw-r--r--browser/base/content/test/general/browser_bug521216.js50
-rw-r--r--browser/base/content/test/general/browser_bug533232.js36
-rw-r--r--browser/base/content/test/general/browser_bug537013.js135
-rw-r--r--browser/base/content/test/general/browser_bug537474.js8
-rw-r--r--browser/base/content/test/general/browser_bug550565.js44
-rw-r--r--browser/base/content/test/general/browser_bug553455.js1200
-rw-r--r--browser/base/content/test/general/browser_bug555224.js40
-rw-r--r--browser/base/content/test/general/browser_bug555767.js54
-rw-r--r--browser/base/content/test/general/browser_bug559991.js42
-rw-r--r--browser/base/content/test/general/browser_bug561636.js370
-rw-r--r--browser/base/content/test/general/browser_bug563588.js30
-rw-r--r--browser/base/content/test/general/browser_bug565575.js14
-rw-r--r--browser/base/content/test/general/browser_bug567306.js50
-rw-r--r--browser/base/content/test/general/browser_bug575561.js97
-rw-r--r--browser/base/content/test/general/browser_bug575830.js33
-rw-r--r--browser/base/content/test/general/browser_bug577121.js29
-rw-r--r--browser/base/content/test/general/browser_bug578534.js23
-rw-r--r--browser/base/content/test/general/browser_bug579872.js28
-rw-r--r--browser/base/content/test/general/browser_bug580638.js60
-rw-r--r--browser/base/content/test/general/browser_bug580956.js26
-rw-r--r--browser/base/content/test/general/browser_bug581242.js21
-rw-r--r--browser/base/content/test/general/browser_bug581253.js86
-rw-r--r--browser/base/content/test/general/browser_bug585558.js153
-rw-r--r--browser/base/content/test/general/browser_bug585785.js35
-rw-r--r--browser/base/content/test/general/browser_bug585830.js25
-rw-r--r--browser/base/content/test/general/browser_bug590206.js163
-rw-r--r--browser/base/content/test/general/browser_bug592338.js163
-rw-r--r--browser/base/content/test/general/browser_bug594131.js21
-rw-r--r--browser/base/content/test/general/browser_bug595507.js36
-rw-r--r--browser/base/content/test/general/browser_bug596687.js25
-rw-r--r--browser/base/content/test/general/browser_bug597218.js38
-rw-r--r--browser/base/content/test/general/browser_bug609700.js20
-rw-r--r--browser/base/content/test/general/browser_bug623893.js37
-rw-r--r--browser/base/content/test/general/browser_bug624734.js29
-rw-r--r--browser/base/content/test/general/browser_bug633691.js28
-rw-r--r--browser/base/content/test/general/browser_bug647886.js40
-rw-r--r--browser/base/content/test/general/browser_bug655584.js23
-rw-r--r--browser/base/content/test/general/browser_bug664672.js19
-rw-r--r--browser/base/content/test/general/browser_bug676619.js124
-rw-r--r--browser/base/content/test/general/browser_bug678392-1.html12
-rw-r--r--browser/base/content/test/general/browser_bug678392-2.html12
-rw-r--r--browser/base/content/test/general/browser_bug678392.js191
-rw-r--r--browser/base/content/test/general/browser_bug710878.js34
-rw-r--r--browser/base/content/test/general/browser_bug719271.js95
-rw-r--r--browser/base/content/test/general/browser_bug724239.js11
-rw-r--r--browser/base/content/test/general/browser_bug734076.js114
-rw-r--r--browser/base/content/test/general/browser_bug735471.js23
-rw-r--r--browser/base/content/test/general/browser_bug749738.js29
-rw-r--r--browser/base/content/test/general/browser_bug763468_perwindowpb.js70
-rw-r--r--browser/base/content/test/general/browser_bug767836_perwindowpb.js90
-rw-r--r--browser/base/content/test/general/browser_bug817947.js55
-rw-r--r--browser/base/content/test/general/browser_bug822367.js187
-rw-r--r--browser/base/content/test/general/browser_bug832435.js23
-rw-r--r--browser/base/content/test/general/browser_bug839103.js120
-rw-r--r--browser/base/content/test/general/browser_bug882977.js29
-rw-r--r--browser/base/content/test/general/browser_bug902156.js174
-rw-r--r--browser/base/content/test/general/browser_bug906190.js240
-rw-r--r--browser/base/content/test/general/browser_bug963945.js23
-rw-r--r--browser/base/content/test/general/browser_bug970746.js121
-rw-r--r--browser/base/content/test/general/browser_bug970746.xhtml20
-rw-r--r--browser/base/content/test/general/browser_clipboard.js174
-rw-r--r--browser/base/content/test/general/browser_clipboard_pastefile.js62
-rw-r--r--browser/base/content/test/general/browser_contentAltClick.js107
-rw-r--r--browser/base/content/test/general/browser_contentAreaClick.js307
-rw-r--r--browser/base/content/test/general/browser_contentSearchUI.js771
-rw-r--r--browser/base/content/test/general/browser_contextmenu.js996
-rw-r--r--browser/base/content/test/general/browser_contextmenu_childprocess.js84
-rw-r--r--browser/base/content/test/general/browser_contextmenu_input.js243
-rw-r--r--browser/base/content/test/general/browser_csp_block_all_mixedcontent.js55
-rw-r--r--browser/base/content/test/general/browser_ctrlTab.js185
-rw-r--r--browser/base/content/test/general/browser_datachoices_notification.js221
-rw-r--r--browser/base/content/test/general/browser_decoderDoctor.js122
-rw-r--r--browser/base/content/test/general/browser_devedition.js129
-rw-r--r--browser/base/content/test/general/browser_discovery.js162
-rw-r--r--browser/base/content/test/general/browser_documentnavigation.js266
-rw-r--r--browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js221
-rw-r--r--browser/base/content/test/general/browser_double_close_tab.js80
-rw-r--r--browser/base/content/test/general/browser_drag.js45
-rw-r--r--browser/base/content/test/general/browser_duplicateIDs.js8
-rw-r--r--browser/base/content/test/general/browser_e10s_about_process.js114
-rw-r--r--browser/base/content/test/general/browser_e10s_chrome_process.js150
-rw-r--r--browser/base/content/test/general/browser_e10s_javascript.js11
-rw-r--r--browser/base/content/test/general/browser_e10s_switchbrowser.js261
-rw-r--r--browser/base/content/test/general/browser_favicon_change.js41
-rw-r--r--browser/base/content/test/general/browser_favicon_change_not_in_document.js34
-rw-r--r--browser/base/content/test/general/browser_feed_discovery.js33
-rw-r--r--browser/base/content/test/general/browser_findbarClose.js35
-rw-r--r--browser/base/content/test/general/browser_focusonkeydown.js26
-rw-r--r--browser/base/content/test/general/browser_fullscreen-window-open.js347
-rw-r--r--browser/base/content/test/general/browser_fxa_migrate.js18
-rw-r--r--browser/base/content/test/general/browser_fxa_oauth.html30
-rw-r--r--browser/base/content/test/general/browser_fxa_oauth.js327
-rw-r--r--browser/base/content/test/general/browser_fxa_oauth_with_keys.html33
-rw-r--r--browser/base/content/test/general/browser_fxa_web_channel.html138
-rw-r--r--browser/base/content/test/general/browser_fxa_web_channel.js210
-rw-r--r--browser/base/content/test/general/browser_fxaccounts.js261
-rw-r--r--browser/base/content/test/general/browser_gZipOfflineChild.js80
-rw-r--r--browser/base/content/test/general/browser_gestureSupport.js670
-rw-r--r--browser/base/content/test/general/browser_getshortcutoruri.js143
-rw-r--r--browser/base/content/test/general/browser_hide_removing.js39
-rw-r--r--browser/base/content/test/general/browser_homeDrop.js90
-rw-r--r--browser/base/content/test/general/browser_identity_UI.js146
-rw-r--r--browser/base/content/test/general/browser_insecureLoginForms.js162
-rw-r--r--browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js39
-rw-r--r--browser/base/content/test/general/browser_keywordBookmarklets.js54
-rw-r--r--browser/base/content/test/general/browser_keywordSearch.js88
-rw-r--r--browser/base/content/test/general/browser_keywordSearch_postData.js94
-rw-r--r--browser/base/content/test/general/browser_lastAccessedTab.js47
-rw-r--r--browser/base/content/test/general/browser_mcb_redirect.js314
-rw-r--r--browser/base/content/test/general/browser_menuButtonBadgeManager.js46
-rw-r--r--browser/base/content/test/general/browser_menuButtonFitts.js32
-rw-r--r--browser/base/content/test/general/browser_middleMouse_noJSPaste.js34
-rw-r--r--browser/base/content/test/general/browser_minimize.js18
-rw-r--r--browser/base/content/test/general/browser_misused_characters_in_strings.js244
-rw-r--r--browser/base/content/test/general/browser_mixedContentFramesOnHttp.js34
-rw-r--r--browser/base/content/test/general/browser_mixedContentFromOnunload.js49
-rw-r--r--browser/base/content/test/general/browser_mixed_content_cert_override.js54
-rw-r--r--browser/base/content/test/general/browser_mixedcontent_securityflags.js70
-rw-r--r--browser/base/content/test/general/browser_modifiedclick_inherit_principal.js30
-rw-r--r--browser/base/content/test/general/browser_newTabDrop.js99
-rw-r--r--browser/base/content/test/general/browser_newWindowDrop.js120
-rw-r--r--browser/base/content/test/general/browser_newwindow_focus.js96
-rw-r--r--browser/base/content/test/general/browser_no_mcb_on_http_site.js106
-rw-r--r--browser/base/content/test/general/browser_offlineQuotaNotification.js95
-rw-r--r--browser/base/content/test/general/browser_overflowScroll.js91
-rw-r--r--browser/base/content/test/general/browser_pageInfo.js38
-rw-r--r--browser/base/content/test/general/browser_page_style_menu.js97
-rw-r--r--browser/base/content/test/general/browser_page_style_menu_update.js67
-rw-r--r--browser/base/content/test/general/browser_pageinfo_svg_image.js38
-rw-r--r--browser/base/content/test/general/browser_parsable_css.js376
-rw-r--r--browser/base/content/test/general/browser_parsable_script.js132
-rw-r--r--browser/base/content/test/general/browser_permissions.js202
-rw-r--r--browser/base/content/test/general/browser_pinnedTabs.js75
-rw-r--r--browser/base/content/test/general/browser_plainTextLinks.js146
-rw-r--r--browser/base/content/test/general/browser_printpreview.js74
-rw-r--r--browser/base/content/test/general/browser_private_browsing_window.js65
-rw-r--r--browser/base/content/test/general/browser_private_no_prompt.js12
-rw-r--r--browser/base/content/test/general/browser_purgehistory_clears_sh.js60
-rw-r--r--browser/base/content/test/general/browser_refreshBlocker.js135
-rw-r--r--browser/base/content/test/general/browser_registerProtocolHandler_notification.html15
-rw-r--r--browser/base/content/test/general/browser_registerProtocolHandler_notification.js43
-rw-r--r--browser/base/content/test/general/browser_relatedTabs.js51
-rw-r--r--browser/base/content/test/general/browser_remoteTroubleshoot.js93
-rw-r--r--browser/base/content/test/general/browser_remoteWebNavigation_postdata.js50
-rw-r--r--browser/base/content/test/general/browser_removeTabsToTheEnd.js24
-rw-r--r--browser/base/content/test/general/browser_restore_isAppTab.js160
-rw-r--r--browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js39
-rw-r--r--browser/base/content/test/general/browser_sanitize-sitepermissions.js52
-rw-r--r--browser/base/content/test/general/browser_sanitize-timespans.js733
-rw-r--r--browser/base/content/test/general/browser_sanitizeDialog.js1027
-rw-r--r--browser/base/content/test/general/browser_save_link-perwindowpb.js199
-rw-r--r--browser/base/content/test/general/browser_save_link_when_window_navigates.js173
-rw-r--r--browser/base/content/test/general/browser_save_private_link_perwindowpb.js116
-rw-r--r--browser/base/content/test/general/browser_save_video.js87
-rw-r--r--browser/base/content/test/general/browser_save_video_frame.js125
-rw-r--r--browser/base/content/test/general/browser_scope.js10
-rw-r--r--browser/base/content/test/general/browser_selectTabAtIndex.js81
-rw-r--r--browser/base/content/test/general/browser_selectpopup.js563
-rw-r--r--browser/base/content/test/general/browser_ssl_error_reports.js174
-rw-r--r--browser/base/content/test/general/browser_star_hsts.js85
-rw-r--r--browser/base/content/test/general/browser_star_hsts.sjs13
-rw-r--r--browser/base/content/test/general/browser_subframe_favicons_not_used.js20
-rw-r--r--browser/base/content/test/general/browser_syncui.js205
-rw-r--r--browser/base/content/test/general/browser_tabDrop.js103
-rw-r--r--browser/base/content/test/general/browser_tabReorder.js49
-rw-r--r--browser/base/content/test/general/browser_tab_close_dependent_window.js24
-rw-r--r--browser/base/content/test/general/browser_tab_detach_restore.js34
-rw-r--r--browser/base/content/test/general/browser_tab_drag_drop_perwindow.js216
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop.js186
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop2.js57
-rw-r--r--browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul169
-rw-r--r--browser/base/content/test/general/browser_tabbar_big_widgets.js29
-rw-r--r--browser/base/content/test/general/browser_tabfocus.js565
-rw-r--r--browser/base/content/test/general/browser_tabkeynavigation.js156
-rw-r--r--browser/base/content/test/general/browser_tabopen_reflows.js157
-rw-r--r--browser/base/content/test/general/browser_tabs_close_beforeunload.js49
-rw-r--r--browser/base/content/test/general/browser_tabs_isActive.js152
-rw-r--r--browser/base/content/test/general/browser_tabs_owner.js44
-rw-r--r--browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js126
-rw-r--r--browser/base/content/test/general/browser_trackingUI_1.js170
-rw-r--r--browser/base/content/test/general/browser_trackingUI_2.js96
-rw-r--r--browser/base/content/test/general/browser_trackingUI_3.js52
-rw-r--r--browser/base/content/test/general/browser_trackingUI_4.js109
-rw-r--r--browser/base/content/test/general/browser_trackingUI_5.js131
-rw-r--r--browser/base/content/test/general/browser_trackingUI_6.js46
-rw-r--r--browser/base/content/test/general/browser_trackingUI_telemetry.js145
-rw-r--r--browser/base/content/test/general/browser_typeAheadFind.js22
-rw-r--r--browser/base/content/test/general/browser_unknownContentType_title.js33
-rw-r--r--browser/base/content/test/general/browser_unloaddialogs.js41
-rw-r--r--browser/base/content/test/general/browser_utilityOverlay.js112
-rw-r--r--browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js55
-rw-r--r--browser/base/content/test/general/browser_visibleFindSelection.js52
-rw-r--r--browser/base/content/test/general/browser_visibleTabs.js97
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js34
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js66
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_contextMenu.js72
-rw-r--r--browser/base/content/test/general/browser_visibleTabs_tabPreview.js41
-rw-r--r--browser/base/content/test/general/browser_web_channel.html189
-rw-r--r--browser/base/content/test/general/browser_web_channel.js436
-rw-r--r--browser/base/content/test/general/browser_web_channel_iframe.html96
-rw-r--r--browser/base/content/test/general/browser_windowactivation.js183
-rw-r--r--browser/base/content/test/general/browser_windowopen_reflows.js117
-rw-r--r--browser/base/content/test/general/browser_zbug569342.js80
-rw-r--r--browser/base/content/test/general/bug1262648_string_with_newlines.dtd3
-rw-r--r--browser/base/content/test/general/bug364677-data.xml5
-rw-r--r--browser/base/content/test/general/bug364677-data.xml^headers^1
-rw-r--r--browser/base/content/test/general/bug395533-data.txt6
-rw-r--r--browser/base/content/test/general/bug592338.html24
-rw-r--r--browser/base/content/test/general/bug792517-2.html5
-rw-r--r--browser/base/content/test/general/bug792517.html5
-rw-r--r--browser/base/content/test/general/bug792517.sjs13
-rw-r--r--browser/base/content/test/general/bug839103.css1
-rw-r--r--browser/base/content/test/general/clipboard_pastefile.html37
-rw-r--r--browser/base/content/test/general/close_beforeunload.html8
-rw-r--r--browser/base/content/test/general/close_beforeunload_opens_second_tab.html3
-rw-r--r--browser/base/content/test/general/contentSearchUI.html21
-rw-r--r--browser/base/content/test/general/contentSearchUI.js209
-rw-r--r--browser/base/content/test/general/content_aboutAccounts.js87
-rw-r--r--browser/base/content/test/general/contextmenu_common.js324
-rw-r--r--browser/base/content/test/general/ctxmenu-image.pngbin0 -> 5401 bytes
-rw-r--r--browser/base/content/test/general/discovery.html8
-rw-r--r--browser/base/content/test/general/download_page.html47
-rw-r--r--browser/base/content/test/general/dummy_page.html9
-rw-r--r--browser/base/content/test/general/feed_discovery.html73
-rw-r--r--browser/base/content/test/general/feed_tab.html17
-rw-r--r--browser/base/content/test/general/file_bug1045809_1.html7
-rw-r--r--browser/base/content/test/general/file_bug1045809_2.html7
-rw-r--r--browser/base/content/test/general/file_bug822367_1.html18
-rw-r--r--browser/base/content/test/general/file_bug822367_1.js1
-rw-r--r--browser/base/content/test/general/file_bug822367_2.html16
-rw-r--r--browser/base/content/test/general/file_bug822367_3.html27
-rw-r--r--browser/base/content/test/general/file_bug822367_4.html18
-rw-r--r--browser/base/content/test/general/file_bug822367_4.js1
-rw-r--r--browser/base/content/test/general/file_bug822367_4B.html18
-rw-r--r--browser/base/content/test/general/file_bug822367_5.html24
-rw-r--r--browser/base/content/test/general/file_bug822367_6.html16
-rw-r--r--browser/base/content/test/general/file_bug902156.js5
-rw-r--r--browser/base/content/test/general/file_bug902156_1.html15
-rw-r--r--browser/base/content/test/general/file_bug902156_2.html17
-rw-r--r--browser/base/content/test/general/file_bug902156_3.html15
-rw-r--r--browser/base/content/test/general/file_bug906190.js5
-rw-r--r--browser/base/content/test/general/file_bug906190.sjs17
-rw-r--r--browser/base/content/test/general/file_bug906190_1.html15
-rw-r--r--browser/base/content/test/general/file_bug906190_2.html15
-rw-r--r--browser/base/content/test/general/file_bug906190_3_4.html14
-rw-r--r--browser/base/content/test/general/file_bug906190_redirected.html15
-rw-r--r--browser/base/content/test/general/file_bug970276_favicon1.icobin0 -> 1406 bytes
-rw-r--r--browser/base/content/test/general/file_bug970276_favicon2.icobin0 -> 1406 bytes
-rw-r--r--browser/base/content/test/general/file_bug970276_popup1.html14
-rw-r--r--browser/base/content/test/general/file_bug970276_popup2.html12
-rw-r--r--browser/base/content/test/general/file_csp_block_all_mixedcontent.html9
-rw-r--r--browser/base/content/test/general/file_csp_block_all_mixedcontent.js3
-rw-r--r--browser/base/content/test/general/file_documentnavigation_frameset.html12
-rw-r--r--browser/base/content/test/general/file_double_close_tab.html15
-rw-r--r--browser/base/content/test/general/file_favicon_change.html13
-rw-r--r--browser/base/content/test/general/file_favicon_change_not_in_document.html21
-rw-r--r--browser/base/content/test/general/file_fullscreen-window-open.html24
-rw-r--r--browser/base/content/test/general/file_generic_favicon.icobin0 -> 1406 bytes
-rw-r--r--browser/base/content/test/general/file_mediaPlayback.html2
-rw-r--r--browser/base/content/test/general/file_mixedContentFramesOnHttp.html14
-rw-r--r--browser/base/content/test/general/file_mixedContentFromOnunload.html18
-rw-r--r--browser/base/content/test/general/file_mixedContentFromOnunload_test1.html14
-rw-r--r--browser/base/content/test/general/file_mixedContentFromOnunload_test2.html15
-rw-r--r--browser/base/content/test/general/file_mixedPassiveContent.html13
-rw-r--r--browser/base/content/test/general/file_trackingUI_6.html16
-rw-r--r--browser/base/content/test/general/file_trackingUI_6.js2
-rw-r--r--browser/base/content/test/general/file_trackingUI_6.js^headers^1
-rw-r--r--browser/base/content/test/general/file_with_favicon.html12
-rw-r--r--browser/base/content/test/general/fxa_profile_handler.sjs34
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.cacheManifest2
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.htmlbin0 -> 303 bytes
-rw-r--r--browser/base/content/test/general/gZipOfflineChild.html^headers^2
-rw-r--r--browser/base/content/test/general/gZipOfflineChild_uncompressed.html21
-rw-r--r--browser/base/content/test/general/head.js1069
-rw-r--r--browser/base/content/test/general/head_plain.js27
-rw-r--r--browser/base/content/test/general/healthreport_pingData.js17
-rw-r--r--browser/base/content/test/general/healthreport_testRemoteCommands.html243
-rw-r--r--browser/base/content/test/general/insecure_opener.html9
-rw-r--r--browser/base/content/test/general/mochitest.ini27
-rw-r--r--browser/base/content/test/general/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/general/navigating_window_with_download.html7
-rw-r--r--browser/base/content/test/general/offlineByDefault.js17
-rw-r--r--browser/base/content/test/general/offlineChild.cacheManifest2
-rw-r--r--browser/base/content/test/general/offlineChild.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/offlineChild.html20
-rw-r--r--browser/base/content/test/general/offlineChild2.cacheManifest2
-rw-r--r--browser/base/content/test/general/offlineChild2.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/offlineChild2.html20
-rw-r--r--browser/base/content/test/general/offlineEvent.cacheManifest2
-rw-r--r--browser/base/content/test/general/offlineEvent.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/general/offlineEvent.html9
-rw-r--r--browser/base/content/test/general/offlineQuotaNotification.cacheManifest7
-rw-r--r--browser/base/content/test/general/offlineQuotaNotification.html9
-rw-r--r--browser/base/content/test/general/page_style_sample.html41
-rw-r--r--browser/base/content/test/general/parsingTestHelpers.jsm131
-rw-r--r--browser/base/content/test/general/permissions.html14
-rw-r--r--browser/base/content/test/general/pinning_headers.sjs23
-rw-r--r--browser/base/content/test/general/print_postdata.sjs22
-rw-r--r--browser/base/content/test/general/refresh_header.sjs24
-rw-r--r--browser/base/content/test/general/refresh_meta.sjs36
-rw-r--r--browser/base/content/test/general/searchSuggestionEngine.sjs9
-rw-r--r--browser/base/content/test/general/searchSuggestionEngine.xml9
-rw-r--r--browser/base/content/test/general/searchSuggestionEngine2.xml9
-rw-r--r--browser/base/content/test/general/ssl_error_reports.sjs91
-rw-r--r--browser/base/content/test/general/subtst_contextmenu.html73
-rw-r--r--browser/base/content/test/general/subtst_contextmenu_input.html29
-rw-r--r--browser/base/content/test/general/subtst_contextmenu_xul.xul9
-rw-r--r--browser/base/content/test/general/svg_image.html11
-rw-r--r--browser/base/content/test/general/test-mixedcontent-securityerrors.html21
-rw-r--r--browser/base/content/test/general/test_bug364677.html32
-rw-r--r--browser/base/content/test/general/test_bug395533.html38
-rw-r--r--browser/base/content/test/general/test_bug435035.html1
-rw-r--r--browser/base/content/test/general/test_bug462673.html18
-rw-r--r--browser/base/content/test/general/test_bug628179.html10
-rw-r--r--browser/base/content/test/general/test_bug839103.html10
-rw-r--r--browser/base/content/test/general/test_bug959531.html9
-rw-r--r--browser/base/content/test/general/test_mcb_double_redirect_image.html23
-rw-r--r--browser/base/content/test/general/test_mcb_redirect.html15
-rw-r--r--browser/base/content/test/general/test_mcb_redirect.js5
-rw-r--r--browser/base/content/test/general/test_mcb_redirect.sjs22
-rw-r--r--browser/base/content/test/general/test_mcb_redirect_image.html23
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font.css10
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font.html47
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font2.css1
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_font2.html48
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_img.css3
-rw-r--r--browser/base/content/test/general/test_no_mcb_on_http_site_img.html47
-rw-r--r--browser/base/content/test/general/test_offlineNotification.html129
-rw-r--r--browser/base/content/test/general/test_offline_gzip.html21
-rw-r--r--browser/base/content/test/general/test_process_flags_chrome.html10
-rw-r--r--browser/base/content/test/general/test_remoteTroubleshoot.html50
-rw-r--r--browser/base/content/test/general/title_test.svg59
-rw-r--r--browser/base/content/test/general/trackingPage.html12
-rw-r--r--browser/base/content/test/general/unknownContentType_file.pif1
-rw-r--r--browser/base/content/test/general/unknownContentType_file.pif^headers^1
-rw-r--r--browser/base/content/test/general/video.oggbin0 -> 285310 bytes
-rw-r--r--browser/base/content/test/general/web_video.html10
-rw-r--r--browser/base/content/test/general/web_video1.ogvbin0 -> 28942 bytes
-rw-r--r--browser/base/content/test/general/web_video1.ogv^headers^3
-rw-r--r--browser/base/content/test/general/zoom_test.html14
-rw-r--r--browser/base/content/test/newtab/.eslintrc.js7
-rw-r--r--browser/base/content/test/newtab/browser.ini55
-rw-r--r--browser/base/content/test/newtab/browser_newtab_1188015.js26
-rw-r--r--browser/base/content/test/newtab/browser_newtab_background_captures.js64
-rw-r--r--browser/base/content/test/newtab/browser_newtab_block.js95
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1145428.js87
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1178586.js83
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1194895.js146
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug1271075.js32
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug721442.js28
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug722273.js73
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug723102.js24
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug723121.js42
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug725996.js35
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug734043.js34
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug735987.js32
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug752841.js56
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug765628.js32
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug876313.js24
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug991111.js35
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug991210.js34
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug998387.js39
-rw-r--r--browser/base/content/test/newtab/browser_newtab_disable.js49
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drag_drop.js95
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js63
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drop_preview.js41
-rw-r--r--browser/base/content/test/newtab/browser_newtab_enhanced.js228
-rw-r--r--browser/base/content/test/newtab/browser_newtab_focus.js48
-rw-r--r--browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js56
-rw-r--r--browser/base/content/test/newtab/browser_newtab_reflow_load.js37
-rw-r--r--browser/base/content/test/newtab/browser_newtab_reportLinkAction.js83
-rw-r--r--browser/base/content/test/newtab/browser_newtab_search.js247
-rw-r--r--browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js53
-rw-r--r--browser/base/content/test/newtab/browser_newtab_undo.js47
-rw-r--r--browser/base/content/test/newtab/browser_newtab_unpin.js56
-rw-r--r--browser/base/content/test/newtab/browser_newtab_update.js48
-rw-r--r--browser/base/content/test/newtab/content-reflows.js26
-rw-r--r--browser/base/content/test/newtab/head.js552
-rw-r--r--browser/base/content/test/newtab/searchEngine1x2xLogo.xml9
-rw-r--r--browser/base/content/test/newtab/searchEngine1xLogo.xml7
-rw-r--r--browser/base/content/test/newtab/searchEngine2xLogo.xml7
-rw-r--r--browser/base/content/test/newtab/searchEngineFavicon.xml6
-rw-r--r--browser/base/content/test/newtab/searchEngineNoLogo.xml5
-rw-r--r--browser/base/content/test/plugins/.eslintrc.js7
-rw-r--r--browser/base/content/test/plugins/blockNoPlugins.xml7
-rw-r--r--browser/base/content/test/plugins/blockPluginHard.xml11
-rw-r--r--browser/base/content/test/plugins/blockPluginInfoURL.xml12
-rw-r--r--browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml11
-rw-r--r--browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml11
-rw-r--r--browser/base/content/test/plugins/blocklist_proxy.js78
-rw-r--r--browser/base/content/test/plugins/browser.ini78
-rw-r--r--browser/base/content/test/plugins/browser_CTP_context_menu.js69
-rw-r--r--browser/base/content/test/plugins/browser_CTP_crashreporting.js233
-rw-r--r--browser/base/content/test/plugins/browser_CTP_data_urls.js255
-rw-r--r--browser/base/content/test/plugins/browser_CTP_drag_drop.js96
-rw-r--r--browser/base/content/test/plugins/browser_CTP_hide_overlay.js88
-rw-r--r--browser/base/content/test/plugins/browser_CTP_iframe.js48
-rw-r--r--browser/base/content/test/plugins/browser_CTP_multi_allow.js99
-rw-r--r--browser/base/content/test/plugins/browser_CTP_nonplugins.js58
-rw-r--r--browser/base/content/test/plugins/browser_CTP_notificationBar.js151
-rw-r--r--browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js120
-rw-r--r--browser/base/content/test/plugins/browser_CTP_remove_navigate.js79
-rw-r--r--browser/base/content/test/plugins/browser_CTP_resize.js130
-rw-r--r--browser/base/content/test/plugins/browser_CTP_zoom.js62
-rw-r--r--browser/base/content/test/plugins/browser_blocking.js349
-rw-r--r--browser/base/content/test/plugins/browser_blocklist_content.js104
-rw-r--r--browser/base/content/test/plugins/browser_bug743421.js119
-rw-r--r--browser/base/content/test/plugins/browser_bug744745.js50
-rw-r--r--browser/base/content/test/plugins/browser_bug787619.js65
-rw-r--r--browser/base/content/test/plugins/browser_bug797677.js43
-rw-r--r--browser/base/content/test/plugins/browser_bug812562.js80
-rw-r--r--browser/base/content/test/plugins/browser_bug818118.js40
-rw-r--r--browser/base/content/test/plugins/browser_bug820497.js71
-rw-r--r--browser/base/content/test/plugins/browser_clearplugindata.html30
-rw-r--r--browser/base/content/test/plugins/browser_clearplugindata.js127
-rw-r--r--browser/base/content/test/plugins/browser_clearplugindata_noage.html30
-rw-r--r--browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js34
-rw-r--r--browser/base/content/test/plugins/browser_pageInfo_plugins.js191
-rw-r--r--browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js207
-rw-r--r--browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js254
-rw-r--r--browser/base/content/test/plugins/browser_plugin_reloading.js85
-rw-r--r--browser/base/content/test/plugins/browser_pluginnotification.js626
-rw-r--r--browser/base/content/test/plugins/browser_plugins_added_dynamically.js137
-rw-r--r--browser/base/content/test/plugins/browser_private_clicktoplay.js216
-rw-r--r--browser/base/content/test/plugins/head.js396
-rw-r--r--browser/base/content/test/plugins/plugin_add_dynamically.html18
-rw-r--r--browser/base/content/test/plugins/plugin_alternate_content.html9
-rw-r--r--browser/base/content/test/plugins/plugin_big.html9
-rw-r--r--browser/base/content/test/plugins/plugin_both.html10
-rw-r--r--browser/base/content/test/plugins/plugin_both2.html10
-rw-r--r--browser/base/content/test/plugins/plugin_bug744745.html12
-rw-r--r--browser/base/content/test/plugins/plugin_bug749455.html8
-rw-r--r--browser/base/content/test/plugins/plugin_bug787619.html9
-rw-r--r--browser/base/content/test/plugins/plugin_bug797677.html5
-rw-r--r--browser/base/content/test/plugins/plugin_bug820497.html17
-rw-r--r--browser/base/content/test/plugins/plugin_clickToPlayAllow.html9
-rw-r--r--browser/base/content/test/plugins/plugin_clickToPlayDeny.html9
-rw-r--r--browser/base/content/test/plugins/plugin_crashCommentAndURL.html27
-rw-r--r--browser/base/content/test/plugins/plugin_data_url.html11
-rw-r--r--browser/base/content/test/plugins/plugin_hidden_to_visible.html11
-rw-r--r--browser/base/content/test/plugins/plugin_iframe.html9
-rw-r--r--browser/base/content/test/plugins/plugin_outsideScrollArea.html25
-rw-r--r--browser/base/content/test/plugins/plugin_overlayed.html27
-rw-r--r--browser/base/content/test/plugins/plugin_positioned.html12
-rw-r--r--browser/base/content/test/plugins/plugin_small.html9
-rw-r--r--browser/base/content/test/plugins/plugin_small_2.html9
-rw-r--r--browser/base/content/test/plugins/plugin_syncRemoved.html15
-rw-r--r--browser/base/content/test/plugins/plugin_test.html9
-rw-r--r--browser/base/content/test/plugins/plugin_test2.html10
-rw-r--r--browser/base/content/test/plugins/plugin_test3.html9
-rw-r--r--browser/base/content/test/plugins/plugin_two_types.html9
-rw-r--r--browser/base/content/test/plugins/plugin_unknown.html9
-rw-r--r--browser/base/content/test/plugins/plugin_zoom.html10
-rw-r--r--browser/base/content/test/popupNotifications/.eslintrc.js7
-rw-r--r--browser/base/content/test/popupNotifications/browser.ini18
-rw-r--r--browser/base/content/test/popupNotifications/browser_displayURI.js28
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification.js203
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_2.js266
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_3.js305
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_4.js294
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js211
-rw-r--r--browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js74
-rw-r--r--browser/base/content/test/popupNotifications/browser_reshow_in_background.js52
-rw-r--r--browser/base/content/test/popupNotifications/head.js303
-rw-r--r--browser/base/content/test/popups/browser.ini4
-rw-r--r--browser/base/content/test/popups/browser_popupUI.js37
-rw-r--r--browser/base/content/test/popups/browser_popup_blocker.js96
-rw-r--r--browser/base/content/test/popups/popup_blocker.html13
-rw-r--r--browser/base/content/test/referrer/.eslintrc.js7
-rw-r--r--browser/base/content/test/referrer/browser.ini24
-rw-r--r--browser/base/content/test/referrer/browser_referrer_middle_click.js20
-rw-r--r--browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js27
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js59
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js31
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js63
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_private.js22
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js21
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_window.js22
-rw-r--r--browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js32
-rw-r--r--browser/base/content/test/referrer/browser_referrer_simple_click.js20
-rw-r--r--browser/base/content/test/referrer/file_referrer_policyserver.sjs37
-rw-r--r--browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs36
-rw-r--r--browser/base/content/test/referrer/file_referrer_testserver.sjs31
-rw-r--r--browser/base/content/test/referrer/head.js265
-rw-r--r--browser/base/content/test/siteIdentity/browser.ini8
-rw-r--r--browser/base/content/test/siteIdentity/browser_identityBlock_focus.js62
-rw-r--r--browser/base/content/test/siteIdentity/browser_identityPopup_focus.js27
-rw-r--r--browser/base/content/test/siteIdentity/head.js6
-rw-r--r--browser/base/content/test/social/.eslintrc.js7
-rw-r--r--browser/base/content/test/social/blocklist.xml6
-rw-r--r--browser/base/content/test/social/browser.ini23
-rw-r--r--browser/base/content/test/social/browser_aboutHome_activation.js229
-rw-r--r--browser/base/content/test/social/browser_addons.js217
-rw-r--r--browser/base/content/test/social/browser_blocklist.js211
-rw-r--r--browser/base/content/test/social/browser_share.js396
-rw-r--r--browser/base/content/test/social/browser_social_activation.js270
-rw-r--r--browser/base/content/test/social/head.js273
-rw-r--r--browser/base/content/test/social/microformats.html18
-rw-r--r--browser/base/content/test/social/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/social/opengraph/og_invalid_url.html11
-rw-r--r--browser/base/content/test/social/opengraph/opengraph.html13
-rw-r--r--browser/base/content/test/social/opengraph/shortlink_linkrel.html10
-rw-r--r--browser/base/content/test/social/opengraph/shorturl_link.html10
-rw-r--r--browser/base/content/test/social/opengraph/shorturl_linkrel.html25
-rw-r--r--browser/base/content/test/social/share.html9
-rw-r--r--browser/base/content/test/social/share_activate.html35
-rw-r--r--browser/base/content/test/social/social_activate.html41
-rw-r--r--browser/base/content/test/social/social_activate_basic.html41
-rw-r--r--browser/base/content/test/social/social_activate_iframe.html11
-rw-r--r--browser/base/content/test/social/social_crash_content_helper.js31
-rw-r--r--browser/base/content/test/social/social_postActivation.html12
-rw-r--r--browser/base/content/test/tabPrompts/.eslintrc.js7
-rw-r--r--browser/base/content/test/tabPrompts/browser.ini4
-rw-r--r--browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js41
-rw-r--r--browser/base/content/test/tabPrompts/browser_multiplePrompts.js72
-rw-r--r--browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js66
-rw-r--r--browser/base/content/test/tabPrompts/openPromptOffTimeout.html10
-rw-r--r--browser/base/content/test/tabcrashed/browser.ini13
-rw-r--r--browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js152
-rw-r--r--browser/base/content/test/tabcrashed/browser_clearEmail.js85
-rw-r--r--browser/base/content/test/tabcrashed/browser_showForm.js40
-rw-r--r--browser/base/content/test/tabcrashed/browser_shown.js203
-rw-r--r--browser/base/content/test/tabcrashed/browser_withoutDump.js36
-rw-r--r--browser/base/content/test/tabcrashed/head.js110
-rw-r--r--browser/base/content/test/tabs/.eslintrc.js7
-rw-r--r--browser/base/content/test/tabs/browser.ini4
-rw-r--r--browser/base/content/test/tabs/browser_tabSpinnerProbe.js93
-rw-r--r--browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js29
-rw-r--r--browser/base/content/test/urlbar/.eslintrc.js7
-rw-r--r--browser/base/content/test/urlbar/authenticate.sjs220
-rw-r--r--browser/base/content/test/urlbar/browser.ini101
-rw-r--r--browser/base/content/test/urlbar/browser_URLBarSetURI.js100
-rw-r--r--browser/base/content/test/urlbar/browser_action_keyword.js119
-rw-r--r--browser/base/content/test/urlbar/browser_action_keyword_override.js40
-rw-r--r--browser/base/content/test/urlbar/browser_action_searchengine.js36
-rw-r--r--browser/base/content/test/urlbar/browser_action_searchengine_alias.js35
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js57
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_autoselect.js92
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_cursor.js17
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js48
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_enter_race.js122
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_no_title.js15
-rw-r--r--browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js102
-rw-r--r--browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js61
-rw-r--r--browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js37
-rw-r--r--browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js124
-rw-r--r--browser/base/content/test/urlbar/browser_bug1070778.js55
-rw-r--r--browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js29
-rw-r--r--browser/base/content/test/urlbar/browser_bug1225194-remotetab.js16
-rw-r--r--browser/base/content/test/urlbar/browser_bug304198.js109
-rw-r--r--browser/base/content/test/urlbar/browser_bug556061.js98
-rw-r--r--browser/base/content/test/urlbar/browser_bug562649.js24
-rw-r--r--browser/base/content/test/urlbar/browser_bug623155.js137
-rw-r--r--browser/base/content/test/urlbar/browser_bug783614.js13
-rw-r--r--browser/base/content/test/urlbar/browser_canonizeURL.js42
-rw-r--r--browser/base/content/test/urlbar/browser_dragdropURL.js15
-rw-r--r--browser/base/content/test/urlbar/browser_locationBarCommand.js218
-rw-r--r--browser/base/content/test/urlbar/browser_locationBarExternalLoad.js65
-rw-r--r--browser/base/content/test/urlbar/browser_moz_action_link.js31
-rw-r--r--browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js49
-rw-r--r--browser/base/content/test/urlbar/browser_search_favicon.js52
-rw-r--r--browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js216
-rw-r--r--browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js84
-rw-r--r--browser/base/content/test/urlbar/browser_urlHighlight.js134
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js104
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js49
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarCopying.js232
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarDecode.js97
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarDelete.js39
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarEnter.js45
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js69
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js17
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js111
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js49
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarOneOffs.js232
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js41
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js57
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarRevert.js37
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js198
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js66
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js254
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js216
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarStop.js30
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarTrimURLs.js98
-rw-r--r--browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js17
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js146
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_blanking.js35
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js41
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js39
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_searchsettings.js30
-rw-r--r--browser/base/content/test/urlbar/browser_urlbar_stop_pending.js138
-rw-r--r--browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js31
-rw-r--r--browser/base/content/test/urlbar/dummy_page.html9
-rw-r--r--browser/base/content/test/urlbar/file_blank_but_not_blank.html2
-rw-r--r--browser/base/content/test/urlbar/file_urlbar_edit_dos.html23
-rw-r--r--browser/base/content/test/urlbar/head.js205
-rw-r--r--browser/base/content/test/urlbar/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/urlbar/print_postdata.sjs22
-rw-r--r--browser/base/content/test/urlbar/redirect_bug623155.sjs16
-rw-r--r--browser/base/content/test/urlbar/searchSuggestionEngine.sjs9
-rw-r--r--browser/base/content/test/urlbar/searchSuggestionEngine.xml9
-rw-r--r--browser/base/content/test/urlbar/slow-page.sjs22
-rw-r--r--browser/base/content/test/urlbar/test_wyciwyg_copying.html13
-rw-r--r--browser/base/content/test/webrtc/.eslintrc.js7
-rw-r--r--browser/base/content/test/webrtc/browser.ini11
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media.js554
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js109
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js266
-rw-r--r--browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js109
-rw-r--r--browser/base/content/test/webrtc/get_user_media.html55
-rw-r--r--browser/base/content/test/webrtc/get_user_media_content_script.js85
-rw-r--r--browser/base/content/test/webrtc/head.js453
-rw-r--r--browser/base/content/urlbarBindings.xml2740
-rw-r--r--browser/base/content/usercontext.svg23
-rw-r--r--browser/base/content/utilityOverlay.js924
-rw-r--r--browser/base/content/viewSourceOverlay.xul26
-rw-r--r--browser/base/content/web-panels.js104
-rw-r--r--browser/base/content/web-panels.xul71
-rw-r--r--browser/base/content/webrtcIndicator.js194
-rw-r--r--browser/base/content/webrtcIndicator.xul35
-rw-r--r--browser/base/content/win6BrowserOverlay.xul12
-rw-r--r--browser/base/jar.mn197
-rw-r--r--browser/base/moz.build50
-rw-r--r--browser/branding/aurora/VisualElements_150.pngbin0 -> 46222 bytes
-rw-r--r--browser/branding/aurora/VisualElements_70.pngbin0 -> 14051 bytes
-rw-r--r--browser/branding/aurora/appname.bmpbin0 -> 198070 bytes
-rw-r--r--browser/branding/aurora/background.pngbin0 -> 127432 bytes
-rw-r--r--browser/branding/aurora/bgintro.bmpbin0 -> 682144 bytes
-rw-r--r--browser/branding/aurora/branding.nsi49
-rw-r--r--browser/branding/aurora/clock.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/aurora/configure.sh7
-rw-r--r--browser/branding/aurora/content/about-background.pngbin0 -> 238888 bytes
-rw-r--r--browser/branding/aurora/content/about-logo.pngbin0 -> 39622 bytes
-rw-r--r--browser/branding/aurora/content/about-logo@2x.pngbin0 -> 108496 bytes
-rw-r--r--browser/branding/aurora/content/about-wordmark.svg61
-rw-r--r--browser/branding/aurora/content/about.pngbin0 -> 83543 bytes
-rw-r--r--browser/branding/aurora/content/aboutDialog.css34
-rw-r--r--browser/branding/aurora/content/icon48.pngbin0 -> 4391 bytes
-rw-r--r--browser/branding/aurora/content/icon64.pngbin0 -> 6764 bytes
-rw-r--r--browser/branding/aurora/content/identity-icons-brand.svg7
-rw-r--r--browser/branding/aurora/content/jar.mn19
-rw-r--r--browser/branding/aurora/content/moz.build7
-rw-r--r--browser/branding/aurora/content/silhouette-40.svg26
-rw-r--r--browser/branding/aurora/default16.pngbin0 -> 790 bytes
-rw-r--r--browser/branding/aurora/default32.pngbin0 -> 2402 bytes
-rw-r--r--browser/branding/aurora/default48.pngbin0 -> 4391 bytes
-rw-r--r--browser/branding/aurora/disk.icnsbin0 -> 784888 bytes
-rw-r--r--browser/branding/aurora/document.icnsbin0 -> 914217 bytes
-rw-r--r--browser/branding/aurora/document.icobin0 -> 91633 bytes
-rw-r--r--browser/branding/aurora/dsstorebin0 -> 12292 bytes
-rw-r--r--browser/branding/aurora/firefox.VisualElementsManifest.xml8
-rw-r--r--browser/branding/aurora/firefox.icnsbin0 -> 880996 bytes
-rw-r--r--browser/branding/aurora/firefox.icobin0 -> 96012 bytes
-rw-r--r--browser/branding/aurora/locales/browserconfig.properties6
-rw-r--r--browser/branding/aurora/locales/en-US/brand.dtd9
-rw-r--r--browser/branding/aurora/locales/en-US/brand.properties10
-rw-r--r--browser/branding/aurora/locales/jar.mn12
-rw-r--r--browser/branding/aurora/locales/moz.build9
-rw-r--r--browser/branding/aurora/moz.build13
-rw-r--r--browser/branding/aurora/mozicon128.pngbin0 -> 20881 bytes
-rw-r--r--browser/branding/aurora/newtab.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/aurora/newwindow.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/aurora/particles.bmpbin0 -> 124216 bytes
-rw-r--r--browser/branding/aurora/pbmode.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/aurora/pencil-rtl.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/aurora/pencil.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/aurora/pref/firefox-branding.js36
-rw-r--r--browser/branding/aurora/wizHeader.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/aurora/wizHeaderRTL.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/aurora/wizWatermark.bmpbin0 -> 154544 bytes
-rw-r--r--browser/branding/branding-common.mozbuild58
-rw-r--r--browser/branding/nightly/VisualElements_150.pngbin0 -> 37693 bytes
-rw-r--r--browser/branding/nightly/VisualElements_70.pngbin0 -> 11763 bytes
-rw-r--r--browser/branding/nightly/appname.bmpbin0 -> 176982 bytes
-rw-r--r--browser/branding/nightly/background.pngbin0 -> 129900 bytes
-rw-r--r--browser/branding/nightly/bgintro.bmpbin0 -> 682144 bytes
-rw-r--r--browser/branding/nightly/branding.nsi45
-rw-r--r--browser/branding/nightly/clock.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/nightly/configure.sh5
-rw-r--r--browser/branding/nightly/content/about-background.pngbin0 -> 162094 bytes
-rw-r--r--browser/branding/nightly/content/about-logo.pngbin0 -> 44476 bytes
-rw-r--r--browser/branding/nightly/content/about-logo@2x.pngbin0 -> 130345 bytes
-rw-r--r--browser/branding/nightly/content/about-wordmark.svg36
-rw-r--r--browser/branding/nightly/content/about.pngbin0 -> 85792 bytes
-rw-r--r--browser/branding/nightly/content/aboutDialog.css29
-rw-r--r--browser/branding/nightly/content/icon48.pngbin0 -> 3937 bytes
-rw-r--r--browser/branding/nightly/content/icon64.pngbin0 -> 5981 bytes
-rw-r--r--browser/branding/nightly/content/identity-icons-brand.svg7
-rw-r--r--browser/branding/nightly/content/jar.mn19
-rw-r--r--browser/branding/nightly/content/moz.build7
-rw-r--r--browser/branding/nightly/content/silhouette-40.svg1360
-rw-r--r--browser/branding/nightly/default16.pngbin0 -> 939 bytes
-rw-r--r--browser/branding/nightly/default32.pngbin0 -> 2590 bytes
-rw-r--r--browser/branding/nightly/default48.pngbin0 -> 3937 bytes
-rw-r--r--browser/branding/nightly/disk.icnsbin0 -> 891873 bytes
-rw-r--r--browser/branding/nightly/document.icnsbin0 -> 809555 bytes
-rw-r--r--browser/branding/nightly/document.icobin0 -> 84749 bytes
-rw-r--r--browser/branding/nightly/dsstorebin0 -> 12292 bytes
-rw-r--r--browser/branding/nightly/firefox.VisualElementsManifest.xml8
-rw-r--r--browser/branding/nightly/firefox.icnsbin0 -> 866586 bytes
-rw-r--r--browser/branding/nightly/firefox.icobin0 -> 86932 bytes
-rw-r--r--browser/branding/nightly/locales/browserconfig.properties6
-rw-r--r--browser/branding/nightly/locales/en-US/brand.dtd9
-rw-r--r--browser/branding/nightly/locales/en-US/brand.properties10
-rw-r--r--browser/branding/nightly/locales/jar.mn12
-rw-r--r--browser/branding/nightly/locales/moz.build9
-rw-r--r--browser/branding/nightly/moz.build13
-rw-r--r--browser/branding/nightly/mozicon128.pngbin0 -> 17364 bytes
-rw-r--r--browser/branding/nightly/newtab.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/nightly/newwindow.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/nightly/particles.bmpbin0 -> 124216 bytes
-rw-r--r--browser/branding/nightly/pbmode.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/nightly/pencil-rtl.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/nightly/pencil.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/nightly/pref/firefox-branding.js34
-rw-r--r--browser/branding/nightly/wizHeader.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/nightly/wizHeaderRTL.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/nightly/wizWatermark.bmpbin0 -> 154544 bytes
-rw-r--r--browser/branding/official/LICENSE10
-rw-r--r--browser/branding/official/VisualElements_150.pngbin0 -> 36640 bytes
-rw-r--r--browser/branding/official/VisualElements_70.pngbin0 -> 12083 bytes
-rw-r--r--browser/branding/official/appname.bmpbin0 -> 12164 bytes
-rw-r--r--browser/branding/official/background.pngbin0 -> 131830 bytes
-rw-r--r--browser/branding/official/bgintro.bmpbin0 -> 682144 bytes
-rw-r--r--browser/branding/official/branding.nsi50
-rw-r--r--browser/branding/official/clock.bmpbin0 -> 8982 bytes
-rw-r--r--browser/branding/official/configure.sh5
-rw-r--r--browser/branding/official/content/about-logo.pngbin0 -> 30788 bytes
-rw-r--r--browser/branding/official/content/about-logo@2x.pngbin0 -> 78972 bytes
-rw-r--r--browser/branding/official/content/about-wordmark.pngbin0 -> 5186 bytes
-rw-r--r--browser/branding/official/content/about.pngbin0 -> 33169 bytes
-rw-r--r--browser/branding/official/content/aboutDialog.css43
-rw-r--r--browser/branding/official/content/icon48.pngbin0 -> 4096 bytes
-rw-r--r--browser/branding/official/content/icon64.pngbin0 -> 6081 bytes
-rw-r--r--browser/branding/official/content/identity-icons-brand.svg7
-rw-r--r--browser/branding/official/content/jar.mn18
-rw-r--r--browser/branding/official/content/moz.build7
-rw-r--r--browser/branding/official/content/silhouette-40.svg26
-rw-r--r--browser/branding/official/default16.pngbin0 -> 829 bytes
-rw-r--r--browser/branding/official/default22.pngbin0 -> 1319 bytes
-rw-r--r--browser/branding/official/default24.pngbin0 -> 1327 bytes
-rw-r--r--browser/branding/official/default256.pngbin0 -> 44148 bytes
-rw-r--r--browser/branding/official/default32.pngbin0 -> 2472 bytes
-rw-r--r--browser/branding/official/default48.pngbin0 -> 4096 bytes
-rw-r--r--browser/branding/official/disk.icnsbin0 -> 459117 bytes
-rw-r--r--browser/branding/official/document.icnsbin0 -> 660004 bytes
-rw-r--r--browser/branding/official/document.icobin0 -> 72937 bytes
-rw-r--r--browser/branding/official/dsstorebin0 -> 12292 bytes
-rw-r--r--browser/branding/official/firefox.VisualElementsManifest.xml8
-rw-r--r--browser/branding/official/firefox.icnsbin0 -> 801511 bytes
-rw-r--r--browser/branding/official/firefox.icobin0 -> 85989 bytes
-rw-r--r--browser/branding/official/locales/browserconfig.properties6
-rw-r--r--browser/branding/official/locales/en-US/brand.dtd9
-rw-r--r--browser/branding/official/locales/en-US/brand.properties16
-rw-r--r--browser/branding/official/locales/jar.mn11
-rw-r--r--browser/branding/official/locales/moz.build7
-rw-r--r--browser/branding/official/moz.build13
-rw-r--r--browser/branding/official/mozicon128.pngbin0 -> 17225 bytes
-rw-r--r--browser/branding/official/newtab.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/official/newwindow.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/official/particles.bmpbin0 -> 8982 bytes
-rw-r--r--browser/branding/official/pbmode.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/official/pencil-rtl.bmpbin0 -> 8982 bytes
-rw-r--r--browser/branding/official/pencil.bmpbin0 -> 8982 bytes
-rw-r--r--browser/branding/official/pref/firefox-branding.js34
-rw-r--r--browser/branding/official/wizHeader.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/official/wizHeaderRTL.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/official/wizWatermark.bmpbin0 -> 154544 bytes
-rw-r--r--browser/branding/unofficial/VisualElements_150.pngbin0 -> 37693 bytes
-rw-r--r--browser/branding/unofficial/VisualElements_70.pngbin0 -> 11763 bytes
-rw-r--r--browser/branding/unofficial/appname.bmpbin0 -> 89356 bytes
-rw-r--r--browser/branding/unofficial/background.pngbin0 -> 116034 bytes
-rw-r--r--browser/branding/unofficial/bgintro.bmpbin0 -> 682144 bytes
-rw-r--r--browser/branding/unofficial/branding.nsi45
-rw-r--r--browser/branding/unofficial/clock.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/unofficial/configure.sh5
-rw-r--r--browser/branding/unofficial/content/about-background.pngbin0 -> 88500 bytes
-rw-r--r--browser/branding/unofficial/content/about-logo.pngbin0 -> 30469 bytes
-rw-r--r--browser/branding/unofficial/content/about-logo@2x.pngbin0 -> 81662 bytes
-rw-r--r--browser/branding/unofficial/content/about-wordmark.svg22
-rw-r--r--browser/branding/unofficial/content/about.pngbin0 -> 54712 bytes
-rw-r--r--browser/branding/unofficial/content/aboutDialog.css25
-rw-r--r--browser/branding/unofficial/content/icon48.pngbin0 -> 3442 bytes
-rw-r--r--browser/branding/unofficial/content/icon64.pngbin0 -> 5096 bytes
-rw-r--r--browser/branding/unofficial/content/identity-icons-brand.svg7
-rw-r--r--browser/branding/unofficial/content/jar.mn19
-rw-r--r--browser/branding/unofficial/content/moz.build7
-rw-r--r--browser/branding/unofficial/content/silhouette-40.svg1360
-rw-r--r--browser/branding/unofficial/default16.pngbin0 -> 901 bytes
-rw-r--r--browser/branding/unofficial/default32.pngbin0 -> 2037 bytes
-rw-r--r--browser/branding/unofficial/default48.pngbin0 -> 3441 bytes
-rw-r--r--browser/branding/unofficial/disk.icnsbin0 -> 710029 bytes
-rw-r--r--browser/branding/unofficial/document.icnsbin0 -> 746810 bytes
-rw-r--r--browser/branding/unofficial/document.icobin0 -> 79414 bytes
-rw-r--r--browser/branding/unofficial/dsstorebin0 -> 12292 bytes
-rw-r--r--browser/branding/unofficial/firefox.VisualElementsManifest.xml8
-rw-r--r--browser/branding/unofficial/firefox.icnsbin0 -> 648208 bytes
-rw-r--r--browser/branding/unofficial/firefox.icobin0 -> 74588 bytes
-rw-r--r--browser/branding/unofficial/locales/browserconfig.properties6
-rw-r--r--browser/branding/unofficial/locales/en-US/brand.dtd9
-rw-r--r--browser/branding/unofficial/locales/en-US/brand.properties10
-rw-r--r--browser/branding/unofficial/locales/jar.mn12
-rw-r--r--browser/branding/unofficial/locales/moz.build9
-rw-r--r--browser/branding/unofficial/moz.build13
-rw-r--r--browser/branding/unofficial/mozicon128.pngbin0 -> 13817 bytes
-rw-r--r--browser/branding/unofficial/newtab.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/unofficial/newwindow.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/unofficial/particles.bmpbin0 -> 124216 bytes
-rw-r--r--browser/branding/unofficial/pbmode.icobin0 -> 6518 bytes
-rw-r--r--browser/branding/unofficial/pencil-rtl.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/unofficial/pencil.bmpbin0 -> 124214 bytes
-rw-r--r--browser/branding/unofficial/pref/firefox-branding.js33
-rw-r--r--browser/branding/unofficial/wizHeader.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/unofficial/wizHeaderRTL.bmpbin0 -> 25820 bytes
-rw-r--r--browser/branding/unofficial/wizWatermark.bmpbin0 -> 154544 bytes
-rw-r--r--browser/build.mk55
-rw-r--r--browser/components/BrowserComponents.manifest44
-rw-r--r--browser/components/about/AboutRedirector.cpp235
-rw-r--r--browser/components/about/AboutRedirector.h32
-rw-r--r--browser/components/about/moz.build19
-rw-r--r--browser/components/build/Makefile.in8
-rw-r--r--browser/components/build/moz.build24
-rw-r--r--browser/components/build/nsBrowserCompsCID.h43
-rw-r--r--browser/components/build/nsModule.cpp131
-rw-r--r--browser/components/contextualidentity/content/usercontext.css91
-rw-r--r--browser/components/contextualidentity/jar.mn6
-rw-r--r--browser/components/contextualidentity/moz.build14
-rw-r--r--browser/components/contextualidentity/test/browser/.eslintrc.js11
-rw-r--r--browser/components/contextualidentity/test/browser/browser.ini30
-rw-r--r--browser/components/contextualidentity/test/browser/browser_aboutURLs.js49
-rw-r--r--browser/components/contextualidentity/test/browser/browser_blobUrl.js78
-rw-r--r--browser/components/contextualidentity/test/browser/browser_broadcastchannel.js80
-rw-r--r--browser/components/contextualidentity/test/browser/browser_count_and_remove.js34
-rw-r--r--browser/components/contextualidentity/test/browser/browser_eme.js186
-rw-r--r--browser/components/contextualidentity/test/browser/browser_favicon.js140
-rw-r--r--browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js219
-rw-r--r--browser/components/contextualidentity/test/browser/browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js86
-rw-r--r--browser/components/contextualidentity/test/browser/browser_forgetAPI_quota_clearStoragesForPrincipal.js147
-rw-r--r--browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js352
-rw-r--r--browser/components/contextualidentity/test/browser/browser_imageCache.js59
-rw-r--r--browser/components/contextualidentity/test/browser/browser_middleClick.js41
-rw-r--r--browser/components/contextualidentity/test/browser/browser_newtabButton.js35
-rw-r--r--browser/components/contextualidentity/test/browser/browser_serviceworkers.js108
-rw-r--r--browser/components/contextualidentity/test/browser/browser_usercontext.js86
-rw-r--r--browser/components/contextualidentity/test/browser/browser_usercontextid_tabdrop.js134
-rw-r--r--browser/components/contextualidentity/test/browser/browser_windowName.js74
-rw-r--r--browser/components/contextualidentity/test/browser/browser_windowOpen.js41
-rw-r--r--browser/components/contextualidentity/test/browser/empty_file.html5
-rw-r--r--browser/components/contextualidentity/test/browser/favicon-normal32.pngbin0 -> 344 bytes
-rw-r--r--browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html23
-rw-r--r--browser/components/contextualidentity/test/browser/file_set_storages.html41
-rw-r--r--browser/components/contextualidentity/test/browser/serviceworker.html12
-rw-r--r--browser/components/contextualidentity/test/browser/worker.js1
-rw-r--r--browser/components/controlcenter/content/panel.inc.xul189
-rw-r--r--browser/components/customizableui/CustomizableUI.jsm4420
-rw-r--r--browser/components/customizableui/CustomizableWidgets.jsm1281
-rw-r--r--browser/components/customizableui/CustomizeMode.jsm2341
-rw-r--r--browser/components/customizableui/DragPositionManager.jsm420
-rw-r--r--browser/components/customizableui/PanelWideWidgetTracker.jsm172
-rw-r--r--browser/components/customizableui/ScrollbarSampler.jsm65
-rw-r--r--browser/components/customizableui/content/customizeMode.inc.xul82
-rw-r--r--browser/components/customizableui/content/jar.mn10
-rw-r--r--browser/components/customizableui/content/moz.build7
-rw-r--r--browser/components/customizableui/content/panelUI.css31
-rw-r--r--browser/components/customizableui/content/panelUI.inc.xul407
-rw-r--r--browser/components/customizableui/content/panelUI.js558
-rw-r--r--browser/components/customizableui/content/panelUI.xml509
-rw-r--r--browser/components/customizableui/content/toolbar.xml618
-rw-r--r--browser/components/customizableui/moz.build26
-rw-r--r--browser/components/customizableui/test/.eslintrc.js7
-rw-r--r--browser/components/customizableui/test/browser.ini154
-rw-r--r--browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js107
-rw-r--r--browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js108
-rw-r--r--browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js71
-rw-r--r--browser/components/customizableui/test/browser_1042100_default_placements_update.js107
-rw-r--r--browser/components/customizableui/test/browser_1058573_showToolbarsDropdown.js25
-rw-r--r--browser/components/customizableui/test/browser_1087303_button_fullscreen.js46
-rw-r--r--browser/components/customizableui/test/browser_1087303_button_preferences.js50
-rw-r--r--browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js24
-rw-r--r--browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js31
-rw-r--r--browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js78
-rw-r--r--browser/components/customizableui/test/browser_873501_handle_specials.js79
-rw-r--r--browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js185
-rw-r--r--browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js61
-rw-r--r--browser/components/customizableui/test/browser_877006_missing_view.js41
-rw-r--r--browser/components/customizableui/test/browser_877178_unregisterArea.js50
-rw-r--r--browser/components/customizableui/test/browser_877447_skip_missing_ids.js25
-rw-r--r--browser/components/customizableui/test/browser_878452_drag_to_panel.js65
-rw-r--r--browser/components/customizableui/test/browser_880164_customization_context_menus.js414
-rw-r--r--browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js497
-rw-r--r--browser/components/customizableui/test/browser_884402_customize_from_overflow.js81
-rw-r--r--browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js45
-rw-r--r--browser/components/customizableui/test/browser_885530_showInPrivateBrowsing.js134
-rw-r--r--browser/components/customizableui/test/browser_886323_buildArea_removable_nodes.js46
-rw-r--r--browser/components/customizableui/test/browser_887438_currentset_shim.js75
-rw-r--r--browser/components/customizableui/test/browser_888817_currentset_updating.js57
-rw-r--r--browser/components/customizableui/test/browser_890140_orphaned_placeholders.js210
-rw-r--r--browser/components/customizableui/test/browser_890262_destroyWidget_after_add_to_panel.js68
-rw-r--r--browser/components/customizableui/test/browser_892955_isWidgetRemovable_for_removed_widgets.js30
-rw-r--r--browser/components/customizableui/test/browser_892956_destroyWidget_defaultPlacements.js24
-rw-r--r--browser/components/customizableui/test/browser_901207_searchbar_in_panel.js113
-rw-r--r--browser/components/customizableui/test/browser_909779_overflow_toolbars_new_window.js31
-rw-r--r--browser/components/customizableui/test/browser_913972_currentset_overflow.js55
-rw-r--r--browser/components/customizableui/test/browser_914138_widget_API_overflowable_toolbar.js131
-rw-r--r--browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js16
-rw-r--r--browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js38
-rw-r--r--browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js24
-rw-r--r--browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js26
-rw-r--r--browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js35
-rw-r--r--browser/components/customizableui/test/browser_934113_menubar_removable.js30
-rw-r--r--browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js89
-rw-r--r--browser/components/customizableui/test/browser_938980_navbar_collapsed.js121
-rw-r--r--browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js25
-rw-r--r--browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js70
-rw-r--r--browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js136
-rw-r--r--browser/components/customizableui/test/browser_940946_removable_from_navbar_customizemode.js22
-rw-r--r--browser/components/customizableui/test/browser_941083_invalidate_wrapper_cache_createWidget.js31
-rw-r--r--browser/components/customizableui/test/browser_942581_unregisterArea_keeps_placements.js106
-rw-r--r--browser/components/customizableui/test/browser_943683_migration_test.js50
-rw-r--r--browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.js17
-rw-r--r--browser/components/customizableui/test/browser_945739_showInPrivateBrowsing_customize_mode.js35
-rw-r--r--browser/components/customizableui/test/browser_947914_button_addons.js33
-rw-r--r--browser/components/customizableui/test/browser_947914_button_copy.js59
-rw-r--r--browser/components/customizableui/test/browser_947914_button_cut.js57
-rw-r--r--browser/components/customizableui/test/browser_947914_button_find.js22
-rw-r--r--browser/components/customizableui/test/browser_947914_button_history.js24
-rw-r--r--browser/components/customizableui/test/browser_947914_button_newPrivateWindow.js48
-rw-r--r--browser/components/customizableui/test/browser_947914_button_newWindow.js47
-rw-r--r--browser/components/customizableui/test/browser_947914_button_paste.js41
-rw-r--r--browser/components/customizableui/test/browser_947914_button_print.js45
-rw-r--r--browser/components/customizableui/test/browser_947914_button_savePage.js20
-rw-r--r--browser/components/customizableui/test/browser_947914_button_zoomIn.js37
-rw-r--r--browser/components/customizableui/test/browser_947914_button_zoomOut.js38
-rw-r--r--browser/components/customizableui/test/browser_947914_button_zoomReset.js40
-rw-r--r--browser/components/customizableui/test/browser_947987_removable_default.js68
-rw-r--r--browser/components/customizableui/test/browser_948985_non_removable_defaultArea.js32
-rw-r--r--browser/components/customizableui/test/browser_952963_areaType_getter_no_area.js52
-rw-r--r--browser/components/customizableui/test/browser_956602_remove_special_widget.js31
-rw-r--r--browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js54
-rw-r--r--browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js67
-rw-r--r--browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js34
-rw-r--r--browser/components/customizableui/test/browser_967000_button_charEncoding.js62
-rw-r--r--browser/components/customizableui/test/browser_967000_button_feeds.js60
-rw-r--r--browser/components/customizableui/test/browser_967000_button_sync.js335
-rw-r--r--browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js65
-rw-r--r--browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js56
-rw-r--r--browser/components/customizableui/test/browser_969427_recreate_destroyed_widget_after_reset.js34
-rw-r--r--browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js26
-rw-r--r--browser/components/customizableui/test/browser_970511_undo_restore_default.js128
-rw-r--r--browser/components/customizableui/test/browser_972267_customizationchange_events.js46
-rwxr-xr-xbrowser/components/customizableui/test/browser_973641_button_addon.js71
-rw-r--r--browser/components/customizableui/test/browser_973932_addonbar_currentset.js30
-rw-r--r--browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js145
-rw-r--r--browser/components/customizableui/test/browser_976792_insertNodeInWindow.js414
-rw-r--r--browser/components/customizableui/test/browser_978084_dragEnd_after_move.js46
-rw-r--r--browser/components/customizableui/test/browser_980155_add_overflow_toolbar.js51
-rw-r--r--browser/components/customizableui/test/browser_981305_separator_insertion.js73
-rw-r--r--browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js93
-rw-r--r--browser/components/customizableui/test/browser_982656_restore_defaults_builtin_widgets.js57
-rw-r--r--browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js267
-rw-r--r--browser/components/customizableui/test/browser_985815_propagate_setToolbarVisibility.js45
-rw-r--r--browser/components/customizableui/test/browser_987177_destroyWidget_xul.js33
-rw-r--r--browser/components/customizableui/test/browser_987177_xul_wrapper_updating.js74
-rwxr-xr-xbrowser/components/customizableui/test/browser_987185_syncButton.js77
-rw-r--r--browser/components/customizableui/test/browser_987492_window_api.js54
-rw-r--r--browser/components/customizableui/test/browser_987640_charEncoding.js60
-rw-r--r--browser/components/customizableui/test/browser_988072_sidebar_events.js392
-rw-r--r--browser/components/customizableui/test/browser_989338_saved_placements_not_resaved.js56
-rw-r--r--browser/components/customizableui/test/browser_989751_subviewbutton_class.js62
-rw-r--r--browser/components/customizableui/test/browser_992747_toggle_noncustomizable_toolbar.js26
-rw-r--r--browser/components/customizableui/test/browser_993322_widget_notoolbar.js36
-rw-r--r--browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js149
-rw-r--r--browser/components/customizableui/test/browser_996364_registerArea_different_properties.js112
-rw-r--r--browser/components/customizableui/test/browser_996635_remove_non_widgets.js43
-rw-r--r--browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js81
-rw-r--r--browser/components/customizableui/test/browser_check_tooltips_in_navbar.js14
-rw-r--r--browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js24
-rw-r--r--browser/components/customizableui/test/browser_panel_toggle.js43
-rw-r--r--browser/components/customizableui/test/browser_switch_to_customize_mode.js34
-rw-r--r--browser/components/customizableui/test/head.js499
-rw-r--r--browser/components/customizableui/test/support/feeds_test_page.html10
-rw-r--r--browser/components/customizableui/test/support/test-feed.xml23
-rw-r--r--browser/components/customizableui/test/support/test_967000_charEncoding_page.html11
-rw-r--r--browser/components/dirprovider/DirectoryProvider.cpp286
-rw-r--r--browser/components/dirprovider/DirectoryProvider.h51
-rw-r--r--browser/components/dirprovider/moz.build19
-rw-r--r--browser/components/dirprovider/tests/unit/.eslintrc.js7
-rw-r--r--browser/components/distribution.js504
-rw-r--r--browser/components/downloads/DownloadsCommon.jsm1553
-rw-r--r--browser/components/downloads/DownloadsTaskbar.jsm177
-rw-r--r--browser/components/downloads/DownloadsViewUI.jsm395
-rw-r--r--browser/components/downloads/content/allDownloadsViewOverlay.js1439
-rw-r--r--browser/components/downloads/content/allDownloadsViewOverlay.xul131
-rw-r--r--browser/components/downloads/content/contentAreaDownloadsView.css11
-rw-r--r--browser/components/downloads/content/contentAreaDownloadsView.js17
-rw-r--r--browser/components/downloads/content/contentAreaDownloadsView.xul46
-rw-r--r--browser/components/downloads/content/download.xml99
-rw-r--r--browser/components/downloads/content/downloads.css267
-rw-r--r--browser/components/downloads/content/downloads.js1732
-rw-r--r--browser/components/downloads/content/downloadsOverlay.xul210
-rw-r--r--browser/components/downloads/content/indicator.js606
-rw-r--r--browser/components/downloads/content/indicatorOverlay.xul36
-rw-r--r--browser/components/downloads/jar.mn16
-rw-r--r--browser/components/downloads/moz.build22
-rw-r--r--browser/components/downloads/test/browser/.eslintrc.js7
-rw-r--r--browser/components/downloads/test/browser/browser.ini15
-rw-r--r--browser/components/downloads/test/browser/browser_basic_functionality.js56
-rw-r--r--browser/components/downloads/test/browser/browser_confirm_unblock_download.js92
-rw-r--r--browser/components/downloads/test/browser/browser_downloads_panel_block.js183
-rw-r--r--browser/components/downloads/test/browser/browser_downloads_panel_footer.js95
-rw-r--r--browser/components/downloads/test/browser/browser_downloads_panel_height.js29
-rw-r--r--browser/components/downloads/test/browser/browser_first_download_panel.js57
-rw-r--r--browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js62
-rw-r--r--browser/components/downloads/test/browser/browser_indicatorDrop.js67
-rw-r--r--browser/components/downloads/test/browser/browser_libraryDrop.js72
-rw-r--r--browser/components/downloads/test/browser/browser_overflow_anchor.js115
-rw-r--r--browser/components/downloads/test/browser/head.js300
-rw-r--r--browser/components/downloads/test/unit/.eslintrc.js7
-rw-r--r--browser/components/downloads/test/unit/head.js18
-rw-r--r--browser/components/downloads/test/unit/test_DownloadsCommon.js37
-rw-r--r--browser/components/downloads/test/unit/xpcshell.ini7
-rw-r--r--browser/components/extensions/.eslintrc.js22
-rw-r--r--browser/components/extensions/ext-bookmarks.js374
-rw-r--r--browser/components/extensions/ext-browserAction.js528
-rw-r--r--browser/components/extensions/ext-c-contextMenus.js158
-rw-r--r--browser/components/extensions/ext-c-omnibox.js32
-rw-r--r--browser/components/extensions/ext-c-tabs.js35
-rw-r--r--browser/components/extensions/ext-commands.js259
-rw-r--r--browser/components/extensions/ext-contextMenus.js537
-rw-r--r--browser/components/extensions/ext-desktop-runtime.js26
-rw-r--r--browser/components/extensions/ext-history.js246
-rw-r--r--browser/components/extensions/ext-omnibox.js104
-rw-r--r--browser/components/extensions/ext-pageAction.js287
-rw-r--r--browser/components/extensions/ext-sessions.js92
-rw-r--r--browser/components/extensions/ext-tabs.js1093
-rw-r--r--browser/components/extensions/ext-utils.js1243
-rw-r--r--browser/components/extensions/ext-windows.js231
-rw-r--r--browser/components/extensions/extension-mac-panel.css3
-rw-r--r--browser/components/extensions/extension-mac.css11
-rw-r--r--browser/components/extensions/extension-win-panel.css7
-rw-r--r--browser/components/extensions/extension.css572
-rw-r--r--browser/components/extensions/extension.svg19
-rw-r--r--browser/components/extensions/extensions-browser.manifest31
-rw-r--r--browser/components/extensions/jar.mn29
-rw-r--r--browser/components/extensions/moz.build17
-rw-r--r--browser/components/extensions/schemas/LICENSE27
-rw-r--r--browser/components/extensions/schemas/bookmarks.json568
-rw-r--r--browser/components/extensions/schemas/browser_action.json430
-rw-r--r--browser/components/extensions/schemas/commands.json148
-rw-r--r--browser/components/extensions/schemas/context_menus.json424
-rw-r--r--browser/components/extensions/schemas/context_menus_internal.json78
-rw-r--r--browser/components/extensions/schemas/history.json316
-rw-r--r--browser/components/extensions/schemas/jar.mn16
-rw-r--r--browser/components/extensions/schemas/moz.build7
-rw-r--r--browser/components/extensions/schemas/omnibox.json248
-rw-r--r--browser/components/extensions/schemas/page_action.json234
-rw-r--r--browser/components/extensions/schemas/sessions.json146
-rw-r--r--browser/components/extensions/schemas/tabs.json1295
-rw-r--r--browser/components/extensions/schemas/windows.json508
-rw-r--r--browser/components/extensions/test/browser/.eslintrc.js36
-rw-r--r--browser/components/extensions/test/browser/browser.ini115
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_context.js398
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_disabled.js68
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js321
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js210
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_popup.js413
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js304
-rw-r--r--browser/components/extensions/test/browser/browser_ext_browserAction_simple.js59
-rw-r--r--browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js113
-rw-r--r--browser/components/extensions/test/browser/browser_ext_commands_execute_page_action.js133
-rw-r--r--browser/components/extensions/test/browser/browser_ext_commands_getAll.js81
-rw-r--r--browser/components/extensions/test/browser/browser_ext_commands_onCommand.js229
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contentscript_connect.js67
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contextMenus.js342
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js96
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js76
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js196
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contextMenus_radioGroups.js100
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contextMenus_uninstall.js84
-rw-r--r--browser/components/extensions/test/browser/browser_ext_contextMenus_urlPatterns.js254
-rw-r--r--browser/components/extensions/test/browser/browser_ext_currentWindow.js149
-rw-r--r--browser/components/extensions/test/browser/browser_ext_getViews.js198
-rw-r--r--browser/components/extensions/test/browser/browser_ext_incognito_popup.js108
-rw-r--r--browser/components/extensions/test/browser/browser_ext_incognito_views.js121
-rw-r--r--browser/components/extensions/test/browser/browser_ext_lastError.js55
-rw-r--r--browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js173
-rw-r--r--browser/components/extensions/test/browser/browser_ext_omnibox.js286
-rw-r--r--browser/components/extensions/test/browser/browser_ext_optionsPage_privileges.js66
-rw-r--r--browser/components/extensions/test/browser/browser_ext_pageAction_context.js178
-rw-r--r--browser/components/extensions/test/browser/browser_ext_pageAction_popup.js238
-rw-r--r--browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js169
-rw-r--r--browser/components/extensions/test/browser/browser_ext_pageAction_simple.js60
-rw-r--r--browser/components/extensions/test/browser/browser_ext_pageAction_title.js226
-rw-r--r--browser/components/extensions/test/browser/browser_ext_popup_api_injection.js101
-rw-r--r--browser/components/extensions/test/browser/browser_ext_popup_background.js133
-rw-r--r--browser/components/extensions/test/browser/browser_ext_popup_corners.js98
-rw-r--r--browser/components/extensions/test/browser/browser_ext_popup_sendMessage.js93
-rw-r--r--browser/components/extensions/test/browser/browser_ext_popup_shutdown.js77
-rw-r--r--browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js276
-rw-r--r--browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js101
-rw-r--r--browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js94
-rw-r--r--browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js97
-rw-r--r--browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js61
-rw-r--r--browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js96
-rw-r--r--browser/components/extensions/test/browser/browser_ext_sessions_restore.js134
-rw-r--r--browser/components/extensions/test/browser/browser_ext_simple.js57
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tab_runtimeConnect.js74
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_audio.js203
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js155
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js156
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_create.js166
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_create_invalid_url.js66
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_detectLanguage.js47
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js146
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_events.js280
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js234
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js217
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js189
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js67
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js107
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js70
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js86
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_move.js103
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_move_window.js98
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_move_window_multiple.js43
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_move_window_pinned.js42
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_onHighlighted.js126
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js198
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_query.js224
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_reload.js54
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js58
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js95
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js227
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_update.js45
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_update_url.js110
-rw-r--r--browser/components/extensions/test/browser/browser_ext_tabs_zoom.js222
-rw-r--r--browser/components/extensions/test/browser/browser_ext_topwindowid.js23
-rw-r--r--browser/components/extensions/test/browser/browser_ext_webNavigation_frameId0.js45
-rw-r--r--browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js168
-rw-r--r--browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js251
-rw-r--r--browser/components/extensions/test/browser/browser_ext_webRequest.js95
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows.js33
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_allowScriptsToClose.js61
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_create.js142
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_create_params.js33
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js140
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_create_url.js84
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_events.js115
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_size.js114
-rw-r--r--browser/components/extensions/test/browser/browser_ext_windows_update.js189
-rw-r--r--browser/components/extensions/test/browser/context.html23
-rw-r--r--browser/components/extensions/test/browser/context_tabs_onUpdated_iframe.html19
-rw-r--r--browser/components/extensions/test/browser/context_tabs_onUpdated_page.html18
-rw-r--r--browser/components/extensions/test/browser/ctxmenu-image.pngbin0 -> 5401 bytes
-rw-r--r--browser/components/extensions/test/browser/file_bypass_cache.sjs11
-rw-r--r--browser/components/extensions/test/browser/file_dummy.html9
-rw-r--r--browser/components/extensions/test/browser/file_iframe_document.html10
-rw-r--r--browser/components/extensions/test/browser/file_iframe_document.sjs41
-rw-r--r--browser/components/extensions/test/browser/file_language_fr_en.html14
-rw-r--r--browser/components/extensions/test/browser/file_language_ja.html10
-rw-r--r--browser/components/extensions/test/browser/file_language_tlh.html12
-rw-r--r--browser/components/extensions/test/browser/file_popup_api_injection_a.html10
-rw-r--r--browser/components/extensions/test/browser/file_popup_api_injection_b.html10
-rw-r--r--browser/components/extensions/test/browser/head.js263
-rw-r--r--browser/components/extensions/test/browser/head_pageAction.js157
-rw-r--r--browser/components/extensions/test/browser/head_sessions.js47
-rw-r--r--browser/components/extensions/test/browser/searchSuggestionEngine.sjs9
-rw-r--r--browser/components/extensions/test/browser/searchSuggestionEngine.xml9
-rw-r--r--browser/components/extensions/test/mochitest/mochitest.ini6
-rw-r--r--browser/components/extensions/test/mochitest/test_ext_all_apis.html75
-rw-r--r--browser/components/extensions/test/xpcshell/.eslintrc.js9
-rw-r--r--browser/components/extensions/test/xpcshell/head.js55
-rw-r--r--browser/components/extensions/test/xpcshell/test_ext_bookmarks.js601
-rw-r--r--browser/components/extensions/test/xpcshell/test_ext_history.js487
-rw-r--r--browser/components/extensions/test/xpcshell/test_ext_manifest_commands.js24
-rw-r--r--browser/components/extensions/test/xpcshell/test_ext_manifest_omnibox.js61
-rw-r--r--browser/components/extensions/test/xpcshell/test_ext_manifest_permissions.js57
-rw-r--r--browser/components/extensions/test/xpcshell/xpcshell.ini11
-rw-r--r--browser/components/feeds/BrowserFeeds.manifest25
-rw-r--r--browser/components/feeds/FeedConverter.js568
-rw-r--r--browser/components/feeds/FeedWriter.js1007
-rw-r--r--browser/components/feeds/WebContentConverter.js1071
-rw-r--r--browser/components/feeds/content/subscribe.js25
-rw-r--r--browser/components/feeds/content/subscribe.xhtml74
-rw-r--r--browser/components/feeds/jar.mn7
-rw-r--r--browser/components/feeds/moz.build41
-rw-r--r--browser/components/feeds/nsFeedSniffer.cpp370
-rw-r--r--browser/components/feeds/nsFeedSniffer.h37
-rw-r--r--browser/components/feeds/nsIFeedResultService.idl70
-rw-r--r--browser/components/feeds/nsIWebContentConverterRegistrar.idl117
-rw-r--r--browser/components/feeds/test/.eslintrc.js7
-rw-r--r--browser/components/feeds/test/bug368464-data.xml18
-rw-r--r--browser/components/feeds/test/bug408328-data.xml63
-rw-r--r--browser/components/feeds/test/bug436801-data.xml44
-rw-r--r--browser/components/feeds/test/bug494328-data.xml24
-rw-r--r--browser/components/feeds/test/bug589543-data.xml23
-rw-r--r--browser/components/feeds/test/chrome/.eslintrc.js7
-rw-r--r--browser/components/feeds/test/chrome/chrome.ini10
-rw-r--r--browser/components/feeds/test/chrome/sample_feed.atom23
-rw-r--r--browser/components/feeds/test/chrome/test_423060.xul56
-rw-r--r--browser/components/feeds/test/chrome/test_bug368464.html32
-rw-r--r--browser/components/feeds/test/chrome/test_bug408328.html37
-rw-r--r--browser/components/feeds/test/chrome/test_maxSniffing.html37
-rw-r--r--browser/components/feeds/test/mochitest.ini14
-rw-r--r--browser/components/feeds/test/test_bug436801.html118
-rw-r--r--browser/components/feeds/test/test_bug494328.html36
-rw-r--r--browser/components/feeds/test/test_bug589543.html32
-rw-r--r--browser/components/feeds/test/test_registerHandler.html85
-rw-r--r--browser/components/feeds/test/unit/.eslintrc.js7
-rw-r--r--browser/components/feeds/test/unit/head_feeds.js5
-rw-r--r--browser/components/feeds/test/unit/test_355473.js43
-rw-r--r--browser/components/feeds/test/unit/test_758990.js42
-rw-r--r--browser/components/feeds/test/unit/xpcshell.ini8
-rw-r--r--browser/components/feeds/test/valid-feed.xml23
-rw-r--r--browser/components/feeds/test/valid-unsniffable-feed.xml32
-rw-r--r--browser/components/migration/.eslintrc.js82
-rw-r--r--browser/components/migration/360seProfileMigrator.js328
-rw-r--r--browser/components/migration/AutoMigrate.jsm670
-rw-r--r--browser/components/migration/BrowserProfileMigrators.manifest33
-rw-r--r--browser/components/migration/ChromeProfileMigrator.js557
-rw-r--r--browser/components/migration/ESEDBReader.jsm590
-rw-r--r--browser/components/migration/EdgeProfileMigrator.js450
-rw-r--r--browser/components/migration/FirefoxProfileMigrator.js255
-rw-r--r--browser/components/migration/IEProfileMigrator.js542
-rw-r--r--browser/components/migration/MSMigrationUtils.jsm889
-rw-r--r--browser/components/migration/MigrationUtils.jsm1117
-rw-r--r--browser/components/migration/ProfileMigrator.js21
-rw-r--r--browser/components/migration/SafariProfileMigrator.js650
-rw-r--r--browser/components/migration/content/aboutWelcomeBack.xhtml82
-rw-r--r--browser/components/migration/content/extra-migration-strings.properties14
-rw-r--r--browser/components/migration/content/migration.js549
-rw-r--r--browser/components/migration/content/migration.xul109
-rw-r--r--browser/components/migration/jar.mn9
-rw-r--r--browser/components/migration/moz.build62
-rw-r--r--browser/components/migration/nsIBrowserProfileMigrator.idl77
-rw-r--r--browser/components/migration/nsIEHistoryEnumerator.cpp120
-rw-r--r--browser/components/migration/nsIEHistoryEnumerator.h37
-rw-r--r--browser/components/migration/nsWindowsMigrationUtils.h36
-rw-r--r--browser/components/migration/tests/browser/.eslintrc.js9
-rw-r--r--browser/components/migration/tests/browser/browser.ini3
-rw-r--r--browser/components/migration/tests/browser/browser_undo_notification.js67
-rw-r--r--browser/components/migration/tests/browser/browser_undo_notification_multiple_dismissal.js122
-rw-r--r--browser/components/migration/tests/browser/browser_undo_notification_wording.js67
-rw-r--r--browser/components/migration/tests/marionette/manifest.ini5
-rw-r--r--browser/components/migration/tests/marionette/test_refresh_firefox.py416
-rw-r--r--browser/components/migration/tests/unit/.eslintrc.js7
-rw-r--r--browser/components/migration/tests/unit/AppData/Local/Google/Chrome/User Data/Default/Login Databin0 -> 22528 bytes
-rw-r--r--browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Default/Cookiesbin0 -> 10240 bytes
-rw-r--r--browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Local State22
-rw-r--r--browser/components/migration/tests/unit/Library/Safari/Bookmarks.plistbin0 -> 1860 bytes
-rw-r--r--browser/components/migration/tests/unit/head_migration.js69
-rw-r--r--browser/components/migration/tests/unit/test_Chrome_cookies.js51
-rw-r--r--browser/components/migration/tests/unit/test_Chrome_passwords.js219
-rw-r--r--browser/components/migration/tests/unit/test_Edge_availability.js20
-rw-r--r--browser/components/migration/tests/unit/test_Edge_db_migration.js471
-rw-r--r--browser/components/migration/tests/unit/test_IE7_passwords.js397
-rw-r--r--browser/components/migration/tests/unit/test_IE_bookmarks.js44
-rw-r--r--browser/components/migration/tests/unit/test_IE_cookies.js111
-rw-r--r--browser/components/migration/tests/unit/test_Safari_bookmarks.js46
-rw-r--r--browser/components/migration/tests/unit/test_automigration.js695
-rw-r--r--browser/components/migration/tests/unit/test_fx_telemetry.js288
-rw-r--r--browser/components/migration/tests/unit/xpcshell.ini26
-rw-r--r--browser/components/moz.build65
-rw-r--r--browser/components/newtab/NewTabComponents.manifest2
-rw-r--r--browser/components/newtab/NewTabMessages.jsm242
-rw-r--r--browser/components/newtab/NewTabPrefsProvider.jsm114
-rw-r--r--browser/components/newtab/NewTabRemoteResources.jsm15
-rw-r--r--browser/components/newtab/NewTabSearchProvider.jsm103
-rw-r--r--browser/components/newtab/NewTabURL.jsm36
-rw-r--r--browser/components/newtab/NewTabWebChannel.jsm299
-rw-r--r--browser/components/newtab/PlacesProvider.jsm211
-rw-r--r--browser/components/newtab/PreviewProvider.jsm49
-rw-r--r--browser/components/newtab/aboutNewTabService.js289
-rw-r--r--browser/components/newtab/moz.build33
-rw-r--r--browser/components/newtab/nsIAboutNewTabService.idl63
-rw-r--r--browser/components/newtab/tests/browser/.eslintrc.js7
-rw-r--r--browser/components/newtab/tests/browser/blue_page.html9
-rw-r--r--browser/components/newtab/tests/browser/browser.ini16
-rw-r--r--browser/components/newtab/tests/browser/browser_PreviewProvider.js90
-rw-r--r--browser/components/newtab/tests/browser/browser_newtab_overrides.js139
-rw-r--r--browser/components/newtab/tests/browser/browser_newtabmessages.js222
-rw-r--r--browser/components/newtab/tests/browser/browser_newtabwebchannel.js251
-rw-r--r--browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js52
-rw-r--r--browser/components/newtab/tests/browser/dummy_page.html10
-rw-r--r--browser/components/newtab/tests/browser/newtabmessages_places.html49
-rw-r--r--browser/components/newtab/tests/browser/newtabmessages_prefs.html32
-rw-r--r--browser/components/newtab/tests/browser/newtabmessages_preview.html37
-rw-r--r--browser/components/newtab/tests/browser/newtabmessages_search.html113
-rw-r--r--browser/components/newtab/tests/browser/newtabwebchannel_basic.html36
-rw-r--r--browser/components/newtab/tests/xpcshell/.eslintrc.js7
-rw-r--r--browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js236
-rw-r--r--browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js50
-rw-r--r--browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js82
-rw-r--r--browser/components/newtab/tests/xpcshell/test_NewTabURL.js52
-rw-r--r--browser/components/newtab/tests/xpcshell/test_PlacesProvider.js358
-rw-r--r--browser/components/newtab/tests/xpcshell/xpcshell.ini11
-rw-r--r--browser/components/nsBrowserContentHandler.js805
-rw-r--r--browser/components/nsBrowserGlue.js2867
-rw-r--r--browser/components/nsIBrowserGlue.idl37
-rw-r--r--browser/components/nsIBrowserHandler.idl20
-rw-r--r--browser/components/originattributes/moz.build16
-rw-r--r--browser/components/originattributes/test/browser/.eslintrc.js7
-rw-r--r--browser/components/originattributes/test/browser/browser.ini64
-rw-r--r--browser/components/originattributes/test/browser/browser_blobURLIsolation.js97
-rw-r--r--browser/components/originattributes/test/browser/browser_broadcastChannel.js47
-rw-r--r--browser/components/originattributes/test/browser/browser_cache.js259
-rw-r--r--browser/components/originattributes/test/browser/browser_clientAuth.js44
-rw-r--r--browser/components/originattributes/test/browser/browser_cookieIsolation.js31
-rw-r--r--browser/components/originattributes/test/browser/browser_favicon_firstParty.js343
-rw-r--r--browser/components/originattributes/test/browser/browser_favicon_userContextId.js257
-rw-r--r--browser/components/originattributes/test/browser/browser_firstPartyIsolation.js174
-rw-r--r--browser/components/originattributes/test/browser/browser_httpauth.js54
-rw-r--r--browser/components/originattributes/test/browser/browser_imageCacheIsolation.js80
-rw-r--r--browser/components/originattributes/test/browser/browser_localStorageIsolation.js24
-rw-r--r--browser/components/originattributes/test/browser/browser_sharedworker.js26
-rw-r--r--browser/components/originattributes/test/browser/dummy.html9
-rw-r--r--browser/components/originattributes/test/browser/file_broadcastChannel.html16
-rw-r--r--browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html15
-rw-r--r--browser/components/originattributes/test/browser/file_cache.html25
-rw-r--r--browser/components/originattributes/test/browser/file_favicon.html11
-rw-r--r--browser/components/originattributes/test/browser/file_favicon.pngbin0 -> 344 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_favicon.png^headers^1
-rw-r--r--browser/components/originattributes/test/browser/file_favicon_cache.html11
-rw-r--r--browser/components/originattributes/test/browser/file_favicon_cache.pngbin0 -> 344 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_favicon_thirdParty.html11
-rw-r--r--browser/components/originattributes/test/browser/file_firstPartyBasic.html8
-rw-r--r--browser/components/originattributes/test/browser/file_sharedworker.html10
-rw-r--r--browser/components/originattributes/test/browser/file_sharedworker.js9
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.audio.oggbin0 -> 2603 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.embed.pngbin0 -> 95 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html18
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.img.pngbin0 -> 95 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.import.js1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.link.css1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.object.pngbin0 -> 95 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.request.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.script.js1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js1
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt13
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogvbin0 -> 16049 bytes
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js9
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html8
-rw-r--r--browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html8
-rw-r--r--browser/components/originattributes/test/browser/head.js365
-rw-r--r--browser/components/originattributes/test/browser/test.html20
-rw-r--r--browser/components/originattributes/test/browser/test.js1
-rw-r--r--browser/components/originattributes/test/browser/test.js^headers^1
-rw-r--r--browser/components/originattributes/test/browser/test2.html12
-rw-r--r--browser/components/originattributes/test/browser/test2.js1
-rw-r--r--browser/components/originattributes/test/browser/test2.js^headers^1
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty.html15
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_cookie.html13
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_html_redirect.html9
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_http_redirect.html9
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^2
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html13
-rw-r--r--browser/components/originattributes/test/browser/test_firstParty_postMessage.html28
-rw-r--r--browser/components/originattributes/test/browser/window.html13
-rw-r--r--browser/components/originattributes/test/browser/worker_blobify.js11
-rw-r--r--browser/components/originattributes/test/browser/worker_deblobify.js31
-rw-r--r--browser/components/originattributes/test/mochitest/file_empty.html2
-rw-r--r--browser/components/originattributes/test/mochitest/mochitest.ini5
-rw-r--r--browser/components/originattributes/test/mochitest/test_permissions_api.html207
-rw-r--r--browser/components/places/PlacesUIUtils.jsm1774
-rw-r--r--browser/components/places/content/bookmarkProperties.js693
-rw-r--r--browser/components/places/content/bookmarkProperties.xul43
-rw-r--r--browser/components/places/content/bookmarksPanel.js24
-rw-r--r--browser/components/places/content/bookmarksPanel.xul54
-rw-r--r--browser/components/places/content/browserPlacesViews.js1996
-rw-r--r--browser/components/places/content/controller.js1742
-rw-r--r--browser/components/places/content/downloadsViewOverlay.xul47
-rw-r--r--browser/components/places/content/editBookmarkOverlay.js1168
-rw-r--r--browser/components/places/content/editBookmarkOverlay.xul188
-rw-r--r--browser/components/places/content/history-panel.js98
-rw-r--r--browser/components/places/content/history-panel.xul95
-rw-r--r--browser/components/places/content/menu.xml633
-rw-r--r--browser/components/places/content/moveBookmarks.js65
-rw-r--r--browser/components/places/content/moveBookmarks.xul53
-rw-r--r--browser/components/places/content/organizer.css7
-rw-r--r--browser/components/places/content/places.css25
-rw-r--r--browser/components/places/content/places.js1405
-rw-r--r--browser/components/places/content/places.xul438
-rw-r--r--browser/components/places/content/placesOverlay.xul233
-rw-r--r--browser/components/places/content/sidebarUtils.js106
-rw-r--r--browser/components/places/content/tree.xml801
-rw-r--r--browser/components/places/content/treeView.js1726
-rw-r--r--browser/components/places/jar.mn34
-rw-r--r--browser/components/places/moz.build18
-rw-r--r--browser/components/places/tests/browser/.eslintrc.js7
-rw-r--r--browser/components/places/tests/browser/bookmark_dummy_1.html9
-rw-r--r--browser/components/places/tests/browser/bookmark_dummy_2.html9
-rw-r--r--browser/components/places/tests/browser/browser.ini58
-rw-r--r--browser/components/places/tests/browser/browser_0_library_left_pane_migration.js90
-rw-r--r--browser/components/places/tests/browser/browser_410196_paste_into_tags.js114
-rw-r--r--browser/components/places/tests/browser/browser_416459_cut.js83
-rw-r--r--browser/components/places/tests/browser/browser_423515.js173
-rw-r--r--browser/components/places/tests/browser/browser_425884.js127
-rw-r--r--browser/components/places/tests/browser/browser_435851_copy_query.js59
-rw-r--r--browser/components/places/tests/browser/browser_475045.js65
-rw-r--r--browser/components/places/tests/browser/browser_555547.js66
-rw-r--r--browser/components/places/tests/browser/browser_bookmarkProperties_addFolderDefaultButton.js53
-rw-r--r--browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js110
-rw-r--r--browser/components/places/tests/browser/browser_bookmarkProperties_addLivemark.js39
-rw-r--r--browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js71
-rw-r--r--browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js42
-rw-r--r--browser/components/places/tests/browser/browser_bookmark_all_tabs.js37
-rw-r--r--browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js61
-rw-r--r--browser/components/places/tests/browser/browser_bookmarksProperties.js450
-rw-r--r--browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js256
-rw-r--r--browser/components/places/tests/browser/browser_forgetthissite_single.js78
-rw-r--r--browser/components/places/tests/browser/browser_history_sidebar_search.js64
-rw-r--r--browser/components/places/tests/browser/browser_library_batch_delete.js114
-rw-r--r--browser/components/places/tests/browser/browser_library_commands.js235
-rw-r--r--browser/components/places/tests/browser/browser_library_downloads.js70
-rw-r--r--browser/components/places/tests/browser/browser_library_infoBox.js197
-rw-r--r--browser/components/places/tests/browser/browser_library_left_pane_fixnames.js94
-rw-r--r--browser/components/places/tests/browser/browser_library_left_pane_select_hierarchy.js41
-rw-r--r--browser/components/places/tests/browser/browser_library_middleclick.js279
-rw-r--r--browser/components/places/tests/browser/browser_library_openFlatContainer.js42
-rw-r--r--browser/components/places/tests/browser/browser_library_open_leak.js23
-rw-r--r--browser/components/places/tests/browser/browser_library_panel_leak.js54
-rw-r--r--browser/components/places/tests/browser/browser_library_search.js182
-rw-r--r--browser/components/places/tests/browser/browser_library_views_liveupdate.js300
-rw-r--r--browser/components/places/tests/browser/browser_markPageAsFollowedLink.js68
-rw-r--r--browser/components/places/tests/browser/browser_sidebarpanels_click.js157
-rw-r--r--browser/components/places/tests/browser/browser_sort_in_library.js249
-rw-r--r--browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js53
-rw-r--r--browser/components/places/tests/browser/browser_views_liveupdate.js475
-rw-r--r--browser/components/places/tests/browser/frameLeft.html8
-rw-r--r--browser/components/places/tests/browser/frameRight.html8
-rw-r--r--browser/components/places/tests/browser/framedPage.html9
-rw-r--r--browser/components/places/tests/browser/head.js460
-rw-r--r--browser/components/places/tests/browser/keyword_form.html17
-rw-r--r--browser/components/places/tests/browser/pageopeningwindow.html9
-rw-r--r--browser/components/places/tests/browser/sidebarpanels_click_test_page.html7
-rw-r--r--browser/components/places/tests/chrome/.eslintrc.js7
-rw-r--r--browser/components/places/tests/chrome/chrome.ini15
-rw-r--r--browser/components/places/tests/chrome/head.js7
-rw-r--r--browser/components/places/tests/chrome/test_0_bug510634.xul99
-rw-r--r--browser/components/places/tests/chrome/test_0_multiple_left_pane.xul85
-rw-r--r--browser/components/places/tests/chrome/test_bug1163447_selectItems_through_shortcut.xul89
-rw-r--r--browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul91
-rw-r--r--browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul83
-rw-r--r--browser/components/places/tests/chrome/test_bug549192.xul120
-rw-r--r--browser/components/places/tests/chrome/test_bug549491.xul78
-rw-r--r--browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul170
-rw-r--r--browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul99
-rw-r--r--browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul204
-rw-r--r--browser/components/places/tests/chrome/test_selectItems_on_nested_tree.xul86
-rw-r--r--browser/components/places/tests/chrome/test_treeview_date.xul159
-rw-r--r--browser/components/places/tests/unit/.eslintrc.js7
-rw-r--r--browser/components/places/tests/unit/bookmarks.glue.html16
-rw-r--r--browser/components/places/tests/unit/bookmarks.glue.json1
-rw-r--r--browser/components/places/tests/unit/corruptDB.sqlitebin0 -> 32772 bytes
-rw-r--r--browser/components/places/tests/unit/distribution.ini27
-rw-r--r--browser/components/places/tests/unit/head_bookmarks.js133
-rw-r--r--browser/components/places/tests/unit/test_421483.js103
-rw-r--r--browser/components/places/tests/unit/test_PUIU_makeTransaction.js361
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js33
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_corrupt.js59
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js52
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js55
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_distribution.js125
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_migrate.js70
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_prefs.js240
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_restore.js62
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js285
-rw-r--r--browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js150
-rw-r--r--browser/components/places/tests/unit/test_clearHistory_shutdown.js181
-rw-r--r--browser/components/places/tests/unit/test_leftpane_corruption_handling.js174
-rw-r--r--browser/components/places/tests/unit/xpcshell.ini25
-rw-r--r--browser/components/preferences/applicationManager.js102
-rw-r--r--browser/components/preferences/applicationManager.xul59
-rw-r--r--browser/components/preferences/blocklists.js209
-rw-r--r--browser/components/preferences/blocklists.xul56
-rw-r--r--browser/components/preferences/colors.xul102
-rw-r--r--browser/components/preferences/connection.js213
-rw-r--r--browser/components/preferences/connection.xul173
-rw-r--r--browser/components/preferences/containers.js176
-rw-r--r--browser/components/preferences/containers.xul52
-rw-r--r--browser/components/preferences/cookies.js948
-rw-r--r--browser/components/preferences/cookies.xul111
-rw-r--r--browser/components/preferences/donottrack.xul43
-rw-r--r--browser/components/preferences/fonts.js105
-rw-r--r--browser/components/preferences/fonts.xul279
-rw-r--r--browser/components/preferences/handlers.css37
-rw-r--r--browser/components/preferences/handlers.xml105
-rw-r--r--browser/components/preferences/in-content/advanced.js770
-rw-r--r--browser/components/preferences/in-content/advanced.xul421
-rw-r--r--browser/components/preferences/in-content/applications.js1900
-rw-r--r--browser/components/preferences/in-content/applications.xul95
-rw-r--r--browser/components/preferences/in-content/containers.js73
-rw-r--r--browser/components/preferences/in-content/containers.xul54
-rw-r--r--browser/components/preferences/in-content/content.js294
-rw-r--r--browser/components/preferences/in-content/content.xul209
-rw-r--r--browser/components/preferences/in-content/jar.mn18
-rw-r--r--browser/components/preferences/in-content/main.js721
-rw-r--r--browser/components/preferences/in-content/main.xul301
-rw-r--r--browser/components/preferences/in-content/moz.build13
-rw-r--r--browser/components/preferences/in-content/preferences.js315
-rw-r--r--browser/components/preferences/in-content/preferences.xul224
-rw-r--r--browser/components/preferences/in-content/privacy.js712
-rw-r--r--browser/components/preferences/in-content/privacy.xul308
-rw-r--r--browser/components/preferences/in-content/search.js604
-rw-r--r--browser/components/preferences/in-content/search.xul86
-rw-r--r--browser/components/preferences/in-content/security.js302
-rw-r--r--browser/components/preferences/in-content/security.xul131
-rw-r--r--browser/components/preferences/in-content/subdialogs.js434
-rw-r--r--browser/components/preferences/in-content/sync.js673
-rw-r--r--browser/components/preferences/in-content/sync.xul359
-rw-r--r--browser/components/preferences/in-content/tests/.eslintrc.js7
-rw-r--r--browser/components/preferences/in-content/tests/browser.ini43
-rw-r--r--browser/components/preferences/in-content/tests/browser_advanced_update.js158
-rw-r--r--browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js76
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js24
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js43
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js92
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul33
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug410900.js46
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug705422.js144
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug731866.js52
-rw-r--r--browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js52
-rw-r--r--browser/components/preferences/in-content/tests/browser_change_app_handler.js98
-rw-r--r--browser/components/preferences/in-content/tests/browser_connection.js99
-rw-r--r--browser/components/preferences/in-content/tests/browser_connection_bug388287.js125
-rw-r--r--browser/components/preferences/in-content/tests/browser_cookies_exceptions.js348
-rw-r--r--browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js103
-rw-r--r--browser/components/preferences/in-content/tests/browser_healthreport.js62
-rw-r--r--browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js20
-rw-r--r--browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js44
-rw-r--r--browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js45
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_1.js18
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_3.js17
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_4.js25
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_5.js17
-rw-r--r--browser/components/preferences/in-content/tests/browser_privacypane_8.js26
-rw-r--r--browser/components/preferences/in-content/tests/browser_proxy_backup.js65
-rw-r--r--browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js37
-rw-r--r--browser/components/preferences/in-content/tests/browser_searchsuggestions.js43
-rw-r--r--browser/components/preferences/in-content/tests/browser_security.js130
-rw-r--r--browser/components/preferences/in-content/tests/browser_subdialogs.js293
-rw-r--r--browser/components/preferences/in-content/tests/browser_telemetry.js52
-rw-r--r--browser/components/preferences/in-content/tests/head.js165
-rw-r--r--browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js330
-rw-r--r--browser/components/preferences/in-content/tests/subdialog.xul27
-rw-r--r--browser/components/preferences/in-content/tests/subdialog2.xul27
-rw-r--r--browser/components/preferences/jar.mn31
-rw-r--r--browser/components/preferences/languages.js312
-rw-r--r--browser/components/preferences/languages.xul101
-rw-r--r--browser/components/preferences/moz.build22
-rw-r--r--browser/components/preferences/permissions.js462
-rw-r--r--browser/components/preferences/permissions.xul83
-rw-r--r--browser/components/preferences/sanitize.js21
-rw-r--r--browser/components/preferences/sanitize.xul101
-rw-r--r--browser/components/preferences/selectBookmark.js83
-rw-r--r--browser/components/preferences/selectBookmark.xul44
-rw-r--r--browser/components/preferences/translation.js255
-rw-r--r--browser/components/preferences/translation.xul88
-rw-r--r--browser/components/privatebrowsing/content/aboutPrivateBrowsing.css10
-rw-r--r--browser/components/privatebrowsing/content/aboutPrivateBrowsing.js98
-rw-r--r--browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml85
-rw-r--r--browser/components/privatebrowsing/jar.mn8
-rw-r--r--browser/components/privatebrowsing/moz.build14
-rw-r--r--browser/components/privatebrowsing/test/browser/.eslintrc.js7
-rw-r--r--browser/components/privatebrowsing/test/browser/browser.ini54
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js282
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js115
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js24
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js23
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js45
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js138
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js53
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js88
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html33
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js60
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js42
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js93
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js95
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js105
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js293
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js54
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html13
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js49
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js25
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js36
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html11
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html10
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html10
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html10
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js61
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js23
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js19
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js133
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html8
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js72
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js95
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js70
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js47
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html13
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js92
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js38
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js82
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js43
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js77
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html9
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js37
-rw-r--r--browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js64
-rw-r--r--browser/components/privatebrowsing/test/browser/empty_file.html1
-rw-r--r--browser/components/privatebrowsing/test/browser/file_favicon.html11
-rw-r--r--browser/components/privatebrowsing/test/browser/file_favicon.pngbin0 -> 344 bytes
-rw-r--r--browser/components/privatebrowsing/test/browser/file_favicon.png^headers^1
-rw-r--r--browser/components/privatebrowsing/test/browser/head.js63
-rw-r--r--browser/components/privatebrowsing/test/browser/popup.html12
-rw-r--r--browser/components/privatebrowsing/test/browser/title.sjs22
-rw-r--r--browser/components/safebrowsing/content/test/.eslintrc.js7
-rw-r--r--browser/components/safebrowsing/content/test/browser.ini8
-rw-r--r--browser/components/safebrowsing/content/test/browser_bug400731.js58
-rw-r--r--browser/components/safebrowsing/content/test/browser_bug415846.js86
-rw-r--r--browser/components/safebrowsing/content/test/browser_whitelisted.js41
-rw-r--r--browser/components/safebrowsing/content/test/head.js55
-rw-r--r--browser/components/search/content/search.xml2090
-rw-r--r--browser/components/search/content/searchReset.js90
-rw-r--r--browser/components/search/content/searchReset.xhtml61
-rw-r--r--browser/components/search/content/searchbarBindings.css18
-rw-r--r--browser/components/search/jar.mn9
-rw-r--r--browser/components/search/moz.build14
-rw-r--r--browser/components/search/test/.eslintrc.js7
-rw-r--r--browser/components/search/test/426329.xml11
-rw-r--r--browser/components/search/test/483086-1.xml10
-rw-r--r--browser/components/search/test/483086-2.xml10
-rw-r--r--browser/components/search/test/browser.ini44
-rw-r--r--browser/components/search/test/browser_426329.js250
-rw-r--r--browser/components/search/test/browser_483086.js49
-rw-r--r--browser/components/search/test/browser_aboutSearchReset.js159
-rw-r--r--browser/components/search/test/browser_abouthome_behavior.js144
-rw-r--r--browser/components/search/test/browser_addEngine.js105
-rw-r--r--browser/components/search/test/browser_amazon.js82
-rw-r--r--browser/components/search/test/browser_amazon_behavior.js166
-rw-r--r--browser/components/search/test/browser_bing.js118
-rw-r--r--browser/components/search/test/browser_bing_behavior.js166
-rw-r--r--browser/components/search/test/browser_contextSearchTabPosition.js62
-rw-r--r--browser/components/search/test/browser_contextmenu.js101
-rw-r--r--browser/components/search/test/browser_google.js100
-rw-r--r--browser/components/search/test/browser_google_behavior.js165
-rw-r--r--browser/components/search/test/browser_google_codes.js161
-rw-r--r--browser/components/search/test/browser_healthreport.js82
-rw-r--r--browser/components/search/test/browser_hiddenOneOffs_cleanup.js99
-rw-r--r--browser/components/search/test/browser_hiddenOneOffs_diacritics.js59
-rw-r--r--browser/components/search/test/browser_oneOffContextMenu.js105
-rw-r--r--browser/components/search/test/browser_oneOffContextMenu_setDefault.js195
-rw-r--r--browser/components/search/test/browser_oneOffHeader.js142
-rw-r--r--browser/components/search/test/browser_private_search_perwindowpb.js76
-rw-r--r--browser/components/search/test/browser_searchbar_keyboard_navigation.js425
-rw-r--r--browser/components/search/test/browser_searchbar_openpopup.js521
-rw-r--r--browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js354
-rw-r--r--browser/components/search/test/browser_webapi.js92
-rw-r--r--browser/components/search/test/browser_yahoo.js132
-rw-r--r--browser/components/search/test/browser_yahoo_behavior.js166
-rw-r--r--browser/components/search/test/head.js156
-rw-r--r--browser/components/search/test/opensearch.html9
-rw-r--r--browser/components/search/test/test.html8
-rw-r--r--browser/components/search/test/testEngine.xml12
-rw-r--r--browser/components/search/test/testEngine_diacritics.xml12
-rw-r--r--browser/components/search/test/testEngine_dupe.xml12
-rw-r--r--browser/components/search/test/testEngine_mozsearch.xml14
-rw-r--r--browser/components/search/test/webapi.html16
-rw-r--r--browser/components/selfsupport/SelfSupportService.js78
-rw-r--r--browser/components/selfsupport/SelfSupportService.manifest2
-rw-r--r--browser/components/selfsupport/moz.build14
-rw-r--r--browser/components/selfsupport/test/.eslintrc.js7
-rw-r--r--browser/components/selfsupport/test/browser.ini3
-rw-r--r--browser/components/selfsupport/test/browser_selfsupportAPI.js88
-rw-r--r--browser/components/sessionstore/ContentRestore.jsm431
-rw-r--r--browser/components/sessionstore/DocShellCapabilities.jsm50
-rw-r--r--browser/components/sessionstore/FrameTree.jsm254
-rw-r--r--browser/components/sessionstore/GlobalState.jsm84
-rw-r--r--browser/components/sessionstore/PageStyle.jsm100
-rw-r--r--browser/components/sessionstore/PrivacyFilter.jsm135
-rw-r--r--browser/components/sessionstore/PrivacyLevel.jsm64
-rw-r--r--browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm214
-rw-r--r--browser/components/sessionstore/RunState.jsm96
-rw-r--r--browser/components/sessionstore/SessionCookies.jsm476
-rw-r--r--browser/components/sessionstore/SessionFile.jsm399
-rw-r--r--browser/components/sessionstore/SessionHistory.jsm428
-rw-r--r--browser/components/sessionstore/SessionMigration.jsm100
-rw-r--r--browser/components/sessionstore/SessionSaver.jsm264
-rw-r--r--browser/components/sessionstore/SessionStorage.jsm173
-rw-r--r--browser/components/sessionstore/SessionStore.jsm4719
-rw-r--r--browser/components/sessionstore/SessionWorker.js381
-rw-r--r--browser/components/sessionstore/SessionWorker.jsm25
-rw-r--r--browser/components/sessionstore/StartupPerformance.jsm234
-rw-r--r--browser/components/sessionstore/TabAttributes.jsm74
-rw-r--r--browser/components/sessionstore/TabState.jsm196
-rw-r--r--browser/components/sessionstore/TabStateCache.jsm163
-rw-r--r--browser/components/sessionstore/TabStateFlusher.jsm184
-rw-r--r--browser/components/sessionstore/content/aboutSessionRestore.js362
-rw-r--r--browser/components/sessionstore/content/aboutSessionRestore.xhtml86
-rw-r--r--browser/components/sessionstore/content/content-sessionStore.js897
-rw-r--r--browser/components/sessionstore/jar.mn8
-rw-r--r--browser/components/sessionstore/moz.build52
-rw-r--r--browser/components/sessionstore/nsISessionStartup.idl66
-rw-r--r--browser/components/sessionstore/nsISessionStore.idl220
-rw-r--r--browser/components/sessionstore/nsSessionStartup.js353
-rw-r--r--browser/components/sessionstore/nsSessionStore.js39
-rw-r--r--browser/components/sessionstore/nsSessionStore.manifest15
-rw-r--r--browser/components/sessionstore/test/.eslintrc.js7
-rw-r--r--browser/components/sessionstore/test/browser.ini242
-rw-r--r--browser/components/sessionstore/test/browser_1234021.js18
-rw-r--r--browser/components/sessionstore/test/browser_1234021_page.html6
-rw-r--r--browser/components/sessionstore/test/browser_248970_b_perwindowpb.js166
-rw-r--r--browser/components/sessionstore/test/browser_248970_b_sample.html37
-rw-r--r--browser/components/sessionstore/test/browser_339445.js32
-rw-r--r--browser/components/sessionstore/test/browser_339445_sample.html18
-rw-r--r--browser/components/sessionstore/test/browser_345898.js44
-rw-r--r--browser/components/sessionstore/test/browser_350525.js102
-rw-r--r--browser/components/sessionstore/test/browser_354894_perwindowpb.js474
-rw-r--r--browser/components/sessionstore/test/browser_367052.js41
-rw-r--r--browser/components/sessionstore/test/browser_393716.js71
-rw-r--r--browser/components/sessionstore/test/browser_394759_basic.js92
-rw-r--r--browser/components/sessionstore/test/browser_394759_behavior.js76
-rw-r--r--browser/components/sessionstore/test/browser_394759_perwindowpb.js55
-rw-r--r--browser/components/sessionstore/test/browser_394759_purge.js130
-rw-r--r--browser/components/sessionstore/test/browser_423132.js59
-rw-r--r--browser/components/sessionstore/test/browser_423132_sample.html14
-rw-r--r--browser/components/sessionstore/test/browser_447951.js65
-rw-r--r--browser/components/sessionstore/test/browser_447951_sample.html5
-rw-r--r--browser/components/sessionstore/test/browser_454908.js47
-rw-r--r--browser/components/sessionstore/test/browser_454908_sample.html8
-rw-r--r--browser/components/sessionstore/test/browser_456342.js49
-rw-r--r--browser/components/sessionstore/test/browser_456342_sample.xhtml36
-rw-r--r--browser/components/sessionstore/test/browser_459906.js62
-rw-r--r--browser/components/sessionstore/test/browser_459906_empty.html3
-rw-r--r--browser/components/sessionstore/test/browser_459906_sample.html41
-rw-r--r--browser/components/sessionstore/test/browser_461634.js85
-rw-r--r--browser/components/sessionstore/test/browser_461743.js39
-rw-r--r--browser/components/sessionstore/test/browser_461743_sample.html56
-rw-r--r--browser/components/sessionstore/test/browser_463205.js40
-rw-r--r--browser/components/sessionstore/test/browser_463205_sample.html7
-rw-r--r--browser/components/sessionstore/test/browser_463206.js53
-rw-r--r--browser/components/sessionstore/test/browser_463206_sample.html11
-rw-r--r--browser/components/sessionstore/test/browser_464199.js85
-rw-r--r--browser/components/sessionstore/test/browser_464620_a.html54
-rw-r--r--browser/components/sessionstore/test/browser_464620_a.js48
-rw-r--r--browser/components/sessionstore/test/browser_464620_b.html58
-rw-r--r--browser/components/sessionstore/test/browser_464620_b.js48
-rw-r--r--browser/components/sessionstore/test/browser_464620_xd.html5
-rw-r--r--browser/components/sessionstore/test/browser_465215.js28
-rw-r--r--browser/components/sessionstore/test/browser_465223.js45
-rw-r--r--browser/components/sessionstore/test/browser_466937.js42
-rw-r--r--browser/components/sessionstore/test/browser_466937_sample.html22
-rw-r--r--browser/components/sessionstore/test/browser_467409-backslashplosion.js74
-rw-r--r--browser/components/sessionstore/test/browser_477657.js60
-rw-r--r--browser/components/sessionstore/test/browser_480893.js47
-rw-r--r--browser/components/sessionstore/test/browser_485482.js37
-rw-r--r--browser/components/sessionstore/test/browser_485482_sample.html12
-rw-r--r--browser/components/sessionstore/test/browser_485563.js26
-rw-r--r--browser/components/sessionstore/test/browser_490040.js65
-rw-r--r--browser/components/sessionstore/test/browser_491168.js42
-rw-r--r--browser/components/sessionstore/test/browser_491577.js120
-rw-r--r--browser/components/sessionstore/test/browser_495495.js46
-rw-r--r--browser/components/sessionstore/test/browser_500328.js120
-rw-r--r--browser/components/sessionstore/test/browser_506482.js73
-rw-r--r--browser/components/sessionstore/test/browser_514751.js38
-rw-r--r--browser/components/sessionstore/test/browser_522375.js21
-rw-r--r--browser/components/sessionstore/test/browser_522545.js269
-rw-r--r--browser/components/sessionstore/test/browser_524745.js42
-rw-r--r--browser/components/sessionstore/test/browser_526613.js72
-rw-r--r--browser/components/sessionstore/test/browser_528776.js21
-rw-r--r--browser/components/sessionstore/test/browser_579868.js30
-rw-r--r--browser/components/sessionstore/test/browser_579879.js20
-rw-r--r--browser/components/sessionstore/test/browser_580512.js81
-rw-r--r--browser/components/sessionstore/test/browser_581937.js19
-rw-r--r--browser/components/sessionstore/test/browser_586068-apptabs.js58
-rw-r--r--browser/components/sessionstore/test/browser_586068-apptabs_ondemand.js53
-rw-r--r--browser/components/sessionstore/test/browser_586068-browser_state_interrupted.js113
-rw-r--r--browser/components/sessionstore/test/browser_586068-cascade.js54
-rw-r--r--browser/components/sessionstore/test/browser_586068-multi_window.js70
-rw-r--r--browser/components/sessionstore/test/browser_586068-reload.js54
-rw-r--r--browser/components/sessionstore/test/browser_586068-select.js69
-rw-r--r--browser/components/sessionstore/test/browser_586068-window_state.js59
-rw-r--r--browser/components/sessionstore/test/browser_586068-window_state_override.js59
-rw-r--r--browser/components/sessionstore/test/browser_586147.js52
-rw-r--r--browser/components/sessionstore/test/browser_588426.js41
-rw-r--r--browser/components/sessionstore/test/browser_589246.js242
-rw-r--r--browser/components/sessionstore/test/browser_590268.js137
-rw-r--r--browser/components/sessionstore/test/browser_590563.js74
-rw-r--r--browser/components/sessionstore/test/browser_595601-restore_hidden.js112
-rw-r--r--browser/components/sessionstore/test/browser_597071.js36
-rw-r--r--browser/components/sessionstore/test/browser_599909.js120
-rw-r--r--browser/components/sessionstore/test/browser_600545.js89
-rw-r--r--browser/components/sessionstore/test/browser_601955.js54
-rw-r--r--browser/components/sessionstore/test/browser_607016.js98
-rw-r--r--browser/components/sessionstore/test/browser_615394-SSWindowState_events.js361
-rw-r--r--browser/components/sessionstore/test/browser_618151.js65
-rw-r--r--browser/components/sessionstore/test/browser_623779.js13
-rw-r--r--browser/components/sessionstore/test/browser_624727.js35
-rw-r--r--browser/components/sessionstore/test/browser_625016.js82
-rw-r--r--browser/components/sessionstore/test/browser_628270.js52
-rw-r--r--browser/components/sessionstore/test/browser_635418.js55
-rw-r--r--browser/components/sessionstore/test/browser_636279.js101
-rw-r--r--browser/components/sessionstore/test/browser_637020.js66
-rw-r--r--browser/components/sessionstore/test/browser_637020_slow.sjs21
-rw-r--r--browser/components/sessionstore/test/browser_644409-scratchpads.js68
-rw-r--r--browser/components/sessionstore/test/browser_645428.js22
-rw-r--r--browser/components/sessionstore/test/browser_659591.js33
-rw-r--r--browser/components/sessionstore/test/browser_662743.js110
-rw-r--r--browser/components/sessionstore/test/browser_662743_sample.html15
-rw-r--r--browser/components/sessionstore/test/browser_662812.js36
-rw-r--r--browser/components/sessionstore/test/browser_665702-state_session.js24
-rw-r--r--browser/components/sessionstore/test/browser_682507.js16
-rw-r--r--browser/components/sessionstore/test/browser_687710.js44
-rw-r--r--browser/components/sessionstore/test/browser_687710_2.js64
-rw-r--r--browser/components/sessionstore/test/browser_694378.js33
-rw-r--r--browser/components/sessionstore/test/browser_701377.js41
-rw-r--r--browser/components/sessionstore/test/browser_705597.js58
-rw-r--r--browser/components/sessionstore/test/browser_707862.js61
-rw-r--r--browser/components/sessionstore/test/browser_739531.js47
-rw-r--r--browser/components/sessionstore/test/browser_739531_sample.html25
-rw-r--r--browser/components/sessionstore/test/browser_739805.js41
-rw-r--r--browser/components/sessionstore/test/browser_819510_perwindowpb.js120
-rw-r--r--browser/components/sessionstore/test/browser_911547.js63
-rw-r--r--browser/components/sessionstore/test/browser_911547_sample.html19
-rw-r--r--browser/components/sessionstore/test/browser_911547_sample.html^headers^1
-rw-r--r--browser/components/sessionstore/test/browser_aboutPrivateBrowsing.js21
-rw-r--r--browser/components/sessionstore/test/browser_aboutSessionRestore.js55
-rw-r--r--browser/components/sessionstore/test/browser_async_duplicate_tab.js78
-rw-r--r--browser/components/sessionstore/test/browser_async_flushes.js113
-rw-r--r--browser/components/sessionstore/test/browser_async_remove_tab.js242
-rw-r--r--browser/components/sessionstore/test/browser_async_window_flushing.js178
-rw-r--r--browser/components/sessionstore/test/browser_attributes.js73
-rw-r--r--browser/components/sessionstore/test/browser_background_tab_crash.js221
-rw-r--r--browser/components/sessionstore/test/browser_backup_recovery.js206
-rw-r--r--browser/components/sessionstore/test/browser_broadcast.js131
-rw-r--r--browser/components/sessionstore/test/browser_capabilities.js76
-rw-r--r--browser/components/sessionstore/test/browser_cleaner.js157
-rw-r--r--browser/components/sessionstore/test/browser_cookies.js173
-rw-r--r--browser/components/sessionstore/test/browser_cookies.sjs21
-rw-r--r--browser/components/sessionstore/test/browser_crashedTabs.js462
-rw-r--r--browser/components/sessionstore/test/browser_dying_cache.js66
-rw-r--r--browser/components/sessionstore/test/browser_dynamic_frames.js87
-rw-r--r--browser/components/sessionstore/test/browser_forget_async_closings.js144
-rw-r--r--browser/components/sessionstore/test/browser_form_restore_events.js63
-rw-r--r--browser/components/sessionstore/test/browser_form_restore_events_sample.html99
-rw-r--r--browser/components/sessionstore/test/browser_formdata.js194
-rw-r--r--browser/components/sessionstore/test/browser_formdata_cc.js79
-rw-r--r--browser/components/sessionstore/test/browser_formdata_format.js113
-rw-r--r--browser/components/sessionstore/test/browser_formdata_format_sample.html7
-rw-r--r--browser/components/sessionstore/test/browser_formdata_sample.html20
-rw-r--r--browser/components/sessionstore/test/browser_formdata_xpath.js151
-rw-r--r--browser/components/sessionstore/test/browser_formdata_xpath_sample.html37
-rw-r--r--browser/components/sessionstore/test/browser_frame_history.js170
-rwxr-xr-xbrowser/components/sessionstore/test/browser_frame_history_a.html5
-rwxr-xr-xbrowser/components/sessionstore/test/browser_frame_history_b.html10
-rwxr-xr-xbrowser/components/sessionstore/test/browser_frame_history_c.html5
-rwxr-xr-xbrowser/components/sessionstore/test/browser_frame_history_c1.html5
-rwxr-xr-xbrowser/components/sessionstore/test/browser_frame_history_c2.html5
-rw-r--r--browser/components/sessionstore/test/browser_frame_history_index.html10
-rw-r--r--browser/components/sessionstore/test/browser_frame_history_index2.html4
-rw-r--r--browser/components/sessionstore/test/browser_frame_history_index_blank.html5
-rw-r--r--browser/components/sessionstore/test/browser_frametree.js131
-rw-r--r--browser/components/sessionstore/test/browser_frametree_sample.html8
-rw-r--r--browser/components/sessionstore/test/browser_frametree_sample_frameset.html11
-rw-r--r--browser/components/sessionstore/test/browser_global_store.js45
-rw-r--r--browser/components/sessionstore/test/browser_history_persist.js93
-rw-r--r--browser/components/sessionstore/test/browser_label_and_icon.js53
-rw-r--r--browser/components/sessionstore/test/browser_merge_closed_tabs.js71
-rw-r--r--browser/components/sessionstore/test/browser_multiple_navigateAndRestore.js36
-rw-r--r--browser/components/sessionstore/test/browser_newtab_userTypedValue.js72
-rw-r--r--browser/components/sessionstore/test/browser_pageStyle.js89
-rw-r--r--browser/components/sessionstore/test/browser_pageStyle_sample.html16
-rw-r--r--browser/components/sessionstore/test/browser_pageStyle_sample_nested.html9
-rw-r--r--browser/components/sessionstore/test/browser_page_title.js45
-rw-r--r--browser/components/sessionstore/test/browser_parentProcessRestoreHash.js95
-rw-r--r--browser/components/sessionstore/test/browser_pending_tabs.js35
-rw-r--r--browser/components/sessionstore/test/browser_privatetabs.js133
-rw-r--r--browser/components/sessionstore/test/browser_purge_shistory.js59
-rw-r--r--browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js342
-rw-r--r--browser/components/sessionstore/test/browser_replace_load.js52
-rw-r--r--browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js171
-rw-r--r--browser/components/sessionstore/test/browser_restore_redirect.js69
-rw-r--r--browser/components/sessionstore/test/browser_revive_crashed_bg_tabs.js56
-rw-r--r--browser/components/sessionstore/test/browser_scrollPositions.js153
-rw-r--r--browser/components/sessionstore/test/browser_scrollPositionsReaderMode.js67
-rw-r--r--browser/components/sessionstore/test/browser_scrollPositions_readerModeArticle.html26
-rw-r--r--browser/components/sessionstore/test/browser_scrollPositions_sample.html8
-rw-r--r--browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html11
-rw-r--r--browser/components/sessionstore/test/browser_send_async_message_oom.js75
-rw-r--r--browser/components/sessionstore/test/browser_sessionHistory.js240
-rw-r--r--browser/components/sessionstore/test/browser_sessionHistory_slow.sjs21
-rw-r--r--browser/components/sessionstore/test/browser_sessionStorage.html27
-rw-r--r--browser/components/sessionstore/test/browser_sessionStorage.js188
-rw-r--r--browser/components/sessionstore/test/browser_sessionStorage_size.js51
-rw-r--r--browser/components/sessionstore/test/browser_sessionStoreContainer.js141
-rw-r--r--browser/components/sessionstore/test/browser_swapDocShells.js35
-rw-r--r--browser/components/sessionstore/test/browser_switch_remoteness.js49
-rw-r--r--browser/components/sessionstore/test/browser_undoCloseById.js118
-rw-r--r--browser/components/sessionstore/test/browser_unrestored_crashedTabs.js69
-rw-r--r--browser/components/sessionstore/test/browser_upgrade_backup.js134
-rw-r--r--browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js26
-rw-r--r--browser/components/sessionstore/test/browser_windowStateContainer.js122
-rw-r--r--browser/components/sessionstore/test/content-forms.js133
-rw-r--r--browser/components/sessionstore/test/content.js222
-rw-r--r--browser/components/sessionstore/test/head.js564
-rw-r--r--browser/components/sessionstore/test/restore_redirect_http.html0
-rw-r--r--browser/components/sessionstore/test/restore_redirect_http.html^headers^2
-rw-r--r--browser/components/sessionstore/test/restore_redirect_js.html10
-rw-r--r--browser/components/sessionstore/test/restore_redirect_target.html8
-rw-r--r--browser/components/sessionstore/test/unit/.eslintrc.js7
-rw-r--r--browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json1
-rw-r--r--browser/components/sessionstore/test/unit/data/sessionstore_invalid.js3
-rw-r--r--browser/components/sessionstore/test/unit/data/sessionstore_valid.js3
-rw-r--r--browser/components/sessionstore/test/unit/head.js32
-rw-r--r--browser/components/sessionstore/test/unit/test_backup_once.js130
-rw-r--r--browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js114
-rw-r--r--browser/components/sessionstore/test/unit/test_shutdown_cleanup.js127
-rw-r--r--browser/components/sessionstore/test/unit/test_startup_invalid_session.js21
-rw-r--r--browser/components/sessionstore/test/unit/test_startup_nosession_async.js22
-rw-r--r--browser/components/sessionstore/test/unit/test_startup_session_async.js27
-rw-r--r--browser/components/sessionstore/test/unit/xpcshell.ini16
-rw-r--r--browser/components/shell/ShellService.jsm114
-rw-r--r--browser/components/shell/content/setDesktopBackground.js214
-rw-r--r--browser/components/shell/content/setDesktopBackground.xul84
-rw-r--r--browser/components/shell/jar.mn7
-rw-r--r--browser/components/shell/moz.build62
-rw-r--r--browser/components/shell/nsGNOMEShellService.cpp638
-rw-r--r--browser/components/shell/nsGNOMEShellService.h36
-rw-r--r--browser/components/shell/nsIGNOMEShellService.idl19
-rw-r--r--browser/components/shell/nsIMacShellService.idl15
-rw-r--r--browser/components/shell/nsIShellService.idl95
-rw-r--r--browser/components/shell/nsIWindowsShellService.idl17
-rw-r--r--browser/components/shell/nsMacShellService.cpp434
-rw-r--r--browser/components/shell/nsMacShellService.h32
-rw-r--r--browser/components/shell/nsSetDefaultBrowser.js30
-rw-r--r--browser/components/shell/nsSetDefaultBrowser.manifest3
-rw-r--r--browser/components/shell/nsShellService.h12
-rw-r--r--browser/components/shell/nsWindowsShellService.cpp1280
-rw-r--r--browser/components/shell/nsWindowsShellService.h37
-rw-r--r--browser/components/shell/test/.eslintrc.js7
-rw-r--r--browser/components/shell/test/browser.ini6
-rw-r--r--browser/components/shell/test/browser_420786.js88
-rw-r--r--browser/components/shell/test/browser_633221.js7
-rw-r--r--browser/components/shell/test/unit/.eslintrc.js7
-rw-r--r--browser/components/shell/test/unit/test_421977.js123
-rw-r--r--browser/components/shell/test/unit/xpcshell.ini7
-rw-r--r--browser/components/syncedtabs/EventEmitter.jsm45
-rw-r--r--browser/components/syncedtabs/SyncedTabsDeckComponent.js169
-rw-r--r--browser/components/syncedtabs/SyncedTabsDeckStore.js60
-rw-r--r--browser/components/syncedtabs/SyncedTabsDeckView.js116
-rw-r--r--browser/components/syncedtabs/SyncedTabsListStore.js235
-rw-r--r--browser/components/syncedtabs/TabListComponent.js142
-rw-r--r--browser/components/syncedtabs/TabListView.js568
-rw-r--r--browser/components/syncedtabs/jar.mn7
-rw-r--r--browser/components/syncedtabs/moz.build24
-rw-r--r--browser/components/syncedtabs/sidebar.js30
-rw-r--r--browser/components/syncedtabs/sidebar.xhtml114
-rw-r--r--browser/components/syncedtabs/test/browser/.eslintrc.js7
-rw-r--r--browser/components/syncedtabs/test/browser/browser.ini4
-rw-r--r--browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js410
-rw-r--r--browser/components/syncedtabs/test/browser/head.js19
-rw-r--r--browser/components/syncedtabs/test/xpcshell/.eslintrc.js7
-rw-r--r--browser/components/syncedtabs/test/xpcshell/head.js29
-rw-r--r--browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js35
-rw-r--r--browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js218
-rw-r--r--browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js64
-rw-r--r--browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js266
-rw-r--r--browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js155
-rw-r--r--browser/components/syncedtabs/test/xpcshell/xpcshell.ini10
-rw-r--r--browser/components/syncedtabs/util.js23
-rw-r--r--browser/components/tests/browser/.eslintrc.js7
-rw-r--r--browser/components/tests/browser/browser.ini4
-rw-r--r--browser/components/tests/browser/browser_bug538331.js426
-rw-r--r--browser/components/tests/browser/browser_contentpermissionprompt.js166
-rw-r--r--browser/components/tests/unit/.eslintrc.js7
-rw-r--r--browser/components/tests/unit/data/engine-de-DE.xml8
-rw-r--r--browser/components/tests/unit/distribution.ini58
-rw-r--r--browser/components/tests/unit/head.js9
-rw-r--r--browser/components/tests/unit/test_browserGlue_migration_loop_cleanup.js32
-rw-r--r--browser/components/tests/unit/test_distribution.js152
-rw-r--r--browser/components/tests/unit/xpcshell.ini10
-rw-r--r--browser/components/translation/BingTranslator.jsm449
-rw-r--r--browser/components/translation/LanguageDetector.jsm143
-rw-r--r--browser/components/translation/Translation.jsm446
-rw-r--r--browser/components/translation/TranslationContentHandler.jsm181
-rw-r--r--browser/components/translation/TranslationDocument.jsm683
-rw-r--r--browser/components/translation/YandexTranslator.jsm343
-rw-r--r--browser/components/translation/cld2/Makefile74
-rw-r--r--browser/components/translation/cld2/cld-worker.js86
-rw-r--r--browser/components/translation/cld2/cld-worker.js.membin0 -> 1097263 bytes
-rw-r--r--browser/components/translation/cld2/cld.idl47
-rw-r--r--browser/components/translation/cld2/cldapp.cc107
-rw-r--r--browser/components/translation/cld2/internal/LICENSE202
-rw-r--r--browser/components/translation/cld2/internal/cld2_dynamic_data.h216
-rw-r--r--browser/components/translation/cld2/internal/cld2_dynamic_data_loader.h52
-rw-r--r--browser/components/translation/cld2/internal/cld2_generated_cjk_compatible.cc298
-rw-r--r--browser/components/translation/cld2/internal/cld2_generated_deltaoctachrome0122.cc4601
-rw-r--r--browser/components/translation/cld2/internal/cld2_generated_distinctoctachrome0122.cc2208
-rw-r--r--browser/components/translation/cld2/internal/cld2_generated_quadchrome0122_16.cc52746
-rw-r--r--browser/components/translation/cld2/internal/cld2tablesummary.h55
-rw-r--r--browser/components/translation/cld2/internal/cld_generated_cjk_delta_bi_4.cc1136
-rw-r--r--browser/components/translation/cld2/internal/cld_generated_cjk_uni_prop_80.cc7133
-rw-r--r--browser/components/translation/cld2/internal/cld_generated_score_quad_octa_0122_2.cc639
-rw-r--r--browser/components/translation/cld2/internal/cldutil.cc620
-rw-r--r--browser/components/translation/cld2/internal/cldutil.h80
-rw-r--r--browser/components/translation/cld2/internal/cldutil_shared.cc437
-rw-r--r--browser/components/translation/cld2/internal/cldutil_shared.h509
-rw-r--r--browser/components/translation/cld2/internal/compact_lang_det.cc322
-rw-r--r--browser/components/translation/cld2/internal/compact_lang_det_hint_code.cc1649
-rw-r--r--browser/components/translation/cld2/internal/compact_lang_det_hint_code.h95
-rw-r--r--browser/components/translation/cld2/internal/compact_lang_det_impl.cc2039
-rw-r--r--browser/components/translation/cld2/internal/compact_lang_det_impl.h183
-rw-r--r--browser/components/translation/cld2/internal/debug.h58
-rw-r--r--browser/components/translation/cld2/internal/debug_empty.cc64
-rw-r--r--browser/components/translation/cld2/internal/fixunicodevalue.cc54
-rw-r--r--browser/components/translation/cld2/internal/fixunicodevalue.h68
-rw-r--r--browser/components/translation/cld2/internal/generated_distinct_bi_0.cc52
-rw-r--r--browser/components/translation/cld2/internal/generated_entities.cc294
-rw-r--r--browser/components/translation/cld2/internal/generated_language.cc4680
-rw-r--r--browser/components/translation/cld2/internal/generated_language.h651
-rw-r--r--browser/components/translation/cld2/internal/generated_ulscript.cc781
-rw-r--r--browser/components/translation/cld2/internal/generated_ulscript.h140
-rw-r--r--browser/components/translation/cld2/internal/getonescriptspan.cc1086
-rw-r--r--browser/components/translation/cld2/internal/getonescriptspan.h110
-rw-r--r--browser/components/translation/cld2/internal/integral_types.h31
-rw-r--r--browser/components/translation/cld2/internal/lang_script.cc560
-rw-r--r--browser/components/translation/cld2/internal/lang_script.h187
-rw-r--r--browser/components/translation/cld2/internal/langspan.h40
-rw-r--r--browser/components/translation/cld2/internal/offsetmap.cc569
-rw-r--r--browser/components/translation/cld2/internal/offsetmap.h175
-rw-r--r--browser/components/translation/cld2/internal/port.h128
-rw-r--r--browser/components/translation/cld2/internal/scoreonescriptspan.cc1334
-rw-r--r--browser/components/translation/cld2/internal/scoreonescriptspan.h297
-rw-r--r--browser/components/translation/cld2/internal/stringpiece.h78
-rw-r--r--browser/components/translation/cld2/internal/tote.cc265
-rw-r--r--browser/components/translation/cld2/internal/tote.h112
-rw-r--r--browser/components/translation/cld2/internal/utf8prop_lettermarkscriptnum.h1629
-rw-r--r--browser/components/translation/cld2/internal/utf8repl_lettermarklower.h756
-rw-r--r--browser/components/translation/cld2/internal/utf8scannot_lettermarkspecial.h1453
-rw-r--r--browser/components/translation/cld2/internal/utf8statetable.cc1369
-rw-r--r--browser/components/translation/cld2/internal/utf8statetable.h283
-rw-r--r--browser/components/translation/cld2/post.js171
-rw-r--r--browser/components/translation/cld2/public/compact_lang_det.h320
-rw-r--r--browser/components/translation/cld2/public/encodings.h169
-rw-r--r--browser/components/translation/jar.mn6
-rw-r--r--browser/components/translation/microsoft-translator-attribution.pngbin0 -> 3422 bytes
-rw-r--r--browser/components/translation/moz.build24
-rw-r--r--browser/components/translation/test/.eslintrc.js7
-rw-r--r--browser/components/translation/test/bing.sjs234
-rw-r--r--browser/components/translation/test/browser.ini13
-rw-r--r--browser/components/translation/test/browser_translation_bing.js133
-rw-r--r--browser/components/translation/test/browser_translation_exceptions.js327
-rw-r--r--browser/components/translation/test/browser_translation_infobar.js216
-rw-r--r--browser/components/translation/test/browser_translation_telemetry.js300
-rw-r--r--browser/components/translation/test/browser_translation_yandex.js130
-rw-r--r--browser/components/translation/test/fixtures/bug1022725-fr.html15
-rw-r--r--browser/components/translation/test/fixtures/result-da39a3ee5e.txt22
-rw-r--r--browser/components/translation/test/fixtures/result-yandex-d448894848.json1
-rw-r--r--browser/components/translation/test/unit/.eslintrc.js7
-rw-r--r--browser/components/translation/test/unit/test_cld2.js463
-rw-r--r--browser/components/translation/test/unit/xpcshell.ini7
-rw-r--r--browser/components/translation/test/yandex.sjs199
-rw-r--r--browser/components/translation/translation-infobar.xml441
-rw-r--r--browser/components/uitour/UITour-lib.js331
-rw-r--r--browser/components/uitour/UITour.jsm2111
-rw-r--r--browser/components/uitour/content-UITour.js103
-rw-r--r--browser/components/uitour/jar.mn6
-rw-r--r--browser/components/uitour/moz.build16
-rw-r--r--browser/components/uitour/test/.eslintrc.js7
-rw-r--r--browser/components/uitour/test/browser.ini49
-rw-r--r--browser/components/uitour/test/browser_UITour.js408
-rw-r--r--browser/components/uitour/test/browser_UITour2.js83
-rw-r--r--browser/components/uitour/test/browser_UITour3.js181
-rw-r--r--browser/components/uitour/test/browser_UITour_annotation_size_attributes.js42
-rw-r--r--browser/components/uitour/test/browser_UITour_availableTargets.js114
-rw-r--r--browser/components/uitour/test/browser_UITour_defaultBrowser.js61
-rw-r--r--browser/components/uitour/test/browser_UITour_detach_tab.js94
-rw-r--r--browser/components/uitour/test/browser_UITour_forceReaderMode.js17
-rw-r--r--browser/components/uitour/test/browser_UITour_heartbeat.js755
-rw-r--r--browser/components/uitour/test/browser_UITour_modalDialog.js104
-rw-r--r--browser/components/uitour/test/browser_UITour_observe.js85
-rw-r--r--browser/components/uitour/test/browser_UITour_panel_close_annotation.js153
-rw-r--r--browser/components/uitour/test/browser_UITour_pocket.js82
-rw-r--r--browser/components/uitour/test/browser_UITour_registerPageID.js108
-rw-r--r--browser/components/uitour/test/browser_UITour_resetProfile.js48
-rw-r--r--browser/components/uitour/test/browser_UITour_showNewTab.js17
-rw-r--r--browser/components/uitour/test/browser_UITour_sync.js105
-rw-r--r--browser/components/uitour/test/browser_UITour_toggleReaderMode.js16
-rw-r--r--browser/components/uitour/test/browser_backgroundTab.js46
-rw-r--r--browser/components/uitour/test/browser_closeTab.js18
-rw-r--r--browser/components/uitour/test/browser_fxa.js68
-rw-r--r--browser/components/uitour/test/browser_no_tabs.js102
-rw-r--r--browser/components/uitour/test/browser_openPreferences.js36
-rw-r--r--browser/components/uitour/test/browser_openSearchPanel.js33
-rw-r--r--browser/components/uitour/test/browser_showMenu_controlCenter.js44
-rw-r--r--browser/components/uitour/test/browser_trackingProtection.js90
-rw-r--r--browser/components/uitour/test/browser_trackingProtection_tour.js77
-rw-r--r--browser/components/uitour/test/head.js449
-rw-r--r--browser/components/uitour/test/image.pngbin0 -> 56060 bytes
-rw-r--r--browser/components/uitour/test/uitour.html42
-rw-r--r--browser/config/mozconfig9
-rw-r--r--browser/config/mozconfigs/common7
-rw-r--r--browser/config/mozconfigs/linux32/artifact10
-rw-r--r--browser/config/mozconfigs/linux32/beta15
-rw-r--r--browser/config/mozconfigs/linux32/common-opt15
-rw-r--r--browser/config/mozconfigs/linux32/debug24
-rw-r--r--browser/config/mozconfigs/linux32/debug-artifact12
-rw-r--r--browser/config/mozconfigs/linux32/debug-asan23
-rw-r--r--browser/config/mozconfigs/linux32/l10n-mozconfig20
-rw-r--r--browser/config/mozconfigs/linux32/nightly15
-rw-r--r--browser/config/mozconfigs/linux32/nightly-asan22
-rw-r--r--browser/config/mozconfigs/linux32/release22
-rw-r--r--browser/config/mozconfigs/linux32/valgrind9
-rw-r--r--browser/config/mozconfigs/linux64/add-on-devel9
-rw-r--r--browser/config/mozconfigs/linux64/artifact10
-rw-r--r--browser/config/mozconfigs/linux64/beta15
-rw-r--r--browser/config/mozconfigs/linux64/code-coverage13
-rw-r--r--browser/config/mozconfigs/linux64/common-opt15
-rw-r--r--browser/config/mozconfigs/linux64/debug22
-rw-r--r--browser/config/mozconfigs/linux64/debug-artifact13
-rw-r--r--browser/config/mozconfigs/linux64/debug-asan23
-rw-r--r--browser/config/mozconfigs/linux64/debug-static-analysis-clang23
-rw-r--r--browser/config/mozconfigs/linux64/hazards35
-rw-r--r--browser/config/mozconfigs/linux64/l10n-mozconfig20
-rw-r--r--browser/config/mozconfigs/linux64/nightly15
-rw-r--r--browser/config/mozconfigs/linux64/nightly-asan19
-rw-r--r--browser/config/mozconfigs/linux64/opt-static-analysis-clang22
-rw-r--r--browser/config/mozconfigs/linux64/opt-tsan9
-rw-r--r--browser/config/mozconfigs/linux64/release22
-rw-r--r--browser/config/mozconfigs/linux64/source4
-rw-r--r--browser/config/mozconfigs/linux64/valgrind9
-rw-r--r--browser/config/mozconfigs/macosx-universal/beta15
-rw-r--r--browser/config/mozconfigs/macosx-universal/common-opt18
-rw-r--r--browser/config/mozconfigs/macosx-universal/l10n-mozconfig22
-rw-r--r--browser/config/mozconfigs/macosx-universal/nightly21
-rw-r--r--browser/config/mozconfigs/macosx-universal/release21
-rw-r--r--browser/config/mozconfigs/macosx64/add-on-devel9
-rw-r--r--browser/config/mozconfigs/macosx64/artifact10
-rw-r--r--browser/config/mozconfigs/macosx64/debug24
-rw-r--r--browser/config/mozconfigs/macosx64/debug-artifact12
-rw-r--r--browser/config/mozconfigs/macosx64/debug-asan20
-rw-r--r--browser/config/mozconfigs/macosx64/debug-static-analysis13
-rw-r--r--browser/config/mozconfigs/macosx64/l10n-mozconfig12
-rw-r--r--browser/config/mozconfigs/macosx64/nightly22
-rw-r--r--browser/config/mozconfigs/macosx64/opt-static-analysis16
-rw-r--r--browser/config/mozconfigs/whitelist100
-rw-r--r--browser/config/mozconfigs/win32/add-on-devel9
-rw-r--r--browser/config/mozconfigs/win32/artifact10
-rw-r--r--browser/config/mozconfigs/win32/beta18
-rw-r--r--browser/config/mozconfigs/win32/common-opt26
-rw-r--r--browser/config/mozconfigs/win32/debug27
-rw-r--r--browser/config/mozconfigs/win32/debug-artifact12
-rw-r--r--browser/config/mozconfigs/win32/debug-static-analysis19
-rw-r--r--browser/config/mozconfigs/win32/l10n-mozconfig19
-rw-r--r--browser/config/mozconfigs/win32/nightly12
-rw-r--r--browser/config/mozconfigs/win32/release24
-rw-r--r--browser/config/mozconfigs/win64/add-on-devel9
-rw-r--r--browser/config/mozconfigs/win64/artifact11
-rw-r--r--browser/config/mozconfigs/win64/beta19
-rw-r--r--browser/config/mozconfigs/win64/common-opt24
-rw-r--r--browser/config/mozconfigs/win64/common-win645
-rw-r--r--browser/config/mozconfigs/win64/debug29
-rw-r--r--browser/config/mozconfigs/win64/debug-artifact13
-rw-r--r--browser/config/mozconfigs/win64/l10n-mozconfig20
-rw-r--r--browser/config/mozconfigs/win64/nightly13
-rw-r--r--browser/config/mozconfigs/win64/release25
-rw-r--r--browser/config/tooltool-manifests/linux32/clang.manifest10
-rw-r--r--browser/config/tooltool-manifests/linux32/releng.manifest41
-rw-r--r--browser/config/tooltool-manifests/linux64/asan.manifest26
-rw-r--r--browser/config/tooltool-manifests/linux64/clang.manifest25
-rw-r--r--browser/config/tooltool-manifests/linux64/clang.manifest.centos618
-rw-r--r--browser/config/tooltool-manifests/linux64/hazard.manifest41
-rw-r--r--browser/config/tooltool-manifests/linux64/msan.manifest26
-rw-r--r--browser/config/tooltool-manifests/linux64/releng.manifest41
-rw-r--r--browser/config/tooltool-manifests/linux64/tsan.manifest26
-rw-r--r--browser/config/tooltool-manifests/macosx64/asan.manifest10
-rw-r--r--browser/config/tooltool-manifests/macosx64/clang.manifest25
-rw-r--r--browser/config/tooltool-manifests/macosx64/cross-releng.manifest65
-rw-r--r--browser/config/tooltool-manifests/macosx64/releng.manifest41
-rw-r--r--browser/config/tooltool-manifests/win32/build-clang-cl.manifest63
-rw-r--r--browser/config/tooltool-manifests/win32/clang.manifest31
-rw-r--r--browser/config/tooltool-manifests/win32/l10n.manifest8
-rw-r--r--browser/config/tooltool-manifests/win32/releng.manifest39
-rw-r--r--browser/config/tooltool-manifests/win64/clang.manifest32
-rw-r--r--browser/config/tooltool-manifests/win64/l10n.manifest8
-rw-r--r--browser/config/tooltool-manifests/win64/releng.manifest40
-rw-r--r--browser/config/version.txt1
-rw-r--r--browser/config/version_display.txt1
-rwxr-xr-xbrowser/confvars.sh64
-rw-r--r--browser/defs.mk1
-rw-r--r--browser/docs/BrowserUsageTelemetry.rst28
-rw-r--r--browser/docs/DirectoryLinksProvider.rst278
-rw-r--r--browser/docs/UITelemetry.rst143
-rw-r--r--browser/docs/index.rst12
-rw-r--r--browser/experiments/.eslintrc.js11
-rw-r--r--browser/experiments/Experiments.jsm2354
-rw-r--r--browser/experiments/Experiments.manifest6
-rw-r--r--browser/experiments/ExperimentsService.js118
-rw-r--r--browser/experiments/Makefile.in16
-rw-r--r--browser/experiments/docs/index.rst13
-rw-r--r--browser/experiments/docs/manifest.rst429
-rw-r--r--browser/experiments/moz.build18
-rw-r--r--browser/experiments/test/addons/experiment-1/install.rdf16
-rw-r--r--browser/experiments/test/addons/experiment-1a/install.rdf16
-rw-r--r--browser/experiments/test/addons/experiment-2/install.rdf16
-rw-r--r--browser/experiments/test/addons/experiment-racybranch/bootstrap.js35
-rw-r--r--browser/experiments/test/addons/experiment-racybranch/install.rdf16
-rw-r--r--browser/experiments/test/xpcshell/.eslintrc.js15
-rw-r--r--browser/experiments/test/xpcshell/experiments_1.manifest19
-rw-r--r--browser/experiments/test/xpcshell/head.js199
-rw-r--r--browser/experiments/test/xpcshell/test_activate.js151
-rw-r--r--browser/experiments/test/xpcshell/test_api.js1647
-rw-r--r--browser/experiments/test/xpcshell/test_cache.js399
-rw-r--r--browser/experiments/test/xpcshell/test_cacherace.js102
-rw-r--r--browser/experiments/test/xpcshell/test_conditions.js325
-rw-r--r--browser/experiments/test/xpcshell/test_disableExperiments.js180
-rw-r--r--browser/experiments/test/xpcshell/test_fetch.js68
-rw-r--r--browser/experiments/test/xpcshell/test_nethang_bug1012924.js47
-rw-r--r--browser/experiments/test/xpcshell/test_previous_provider.js179
-rw-r--r--browser/experiments/test/xpcshell/test_telemetry.js294
-rw-r--r--browser/experiments/test/xpcshell/test_telemetry_disabled.js21
-rw-r--r--browser/experiments/test/xpcshell/test_upgrade.js52
-rw-r--r--browser/experiments/test/xpcshell/xpcshell.ini31
-rw-r--r--browser/extensions/aushelper/bootstrap.js189
-rw-r--r--browser/extensions/aushelper/install.rdf.in32
-rw-r--r--browser/extensions/aushelper/moz.build16
-rw-r--r--browser/extensions/e10srollout/bootstrap.js197
-rw-r--r--browser/extensions/e10srollout/install.rdf.in32
-rw-r--r--browser/extensions/e10srollout/moz.build16
-rw-r--r--browser/extensions/flyweb/bootstrap.js297
-rw-r--r--browser/extensions/flyweb/install.rdf.in32
-rw-r--r--browser/extensions/flyweb/jar.mn10
-rw-r--r--browser/extensions/flyweb/moz.build18
-rw-r--r--browser/extensions/flyweb/skin/flyweb-icon.svg40
-rw-r--r--browser/extensions/flyweb/skin/linux/flyweb.css5
-rw-r--r--browser/extensions/flyweb/skin/linux/icon-16.pngbin0 -> 1251 bytes
-rw-r--r--browser/extensions/flyweb/skin/linux/icon-32-anchored.pngbin0 -> 2711 bytes
-rw-r--r--browser/extensions/flyweb/skin/linux/icon-32.pngbin0 -> 699 bytes
-rw-r--r--browser/extensions/flyweb/skin/linux/icon-64-anchored.pngbin0 -> 6468 bytes
-rw-r--r--browser/extensions/flyweb/skin/linux/icon-64.pngbin0 -> 1311 bytes
-rw-r--r--browser/extensions/flyweb/skin/osx/flyweb.css5
-rw-r--r--browser/extensions/flyweb/skin/osx/icon-16.pngbin0 -> 1646 bytes
-rw-r--r--browser/extensions/flyweb/skin/osx/icon-32-anchored.pngbin0 -> 2711 bytes
-rw-r--r--browser/extensions/flyweb/skin/osx/icon-32.pngbin0 -> 2595 bytes
-rw-r--r--browser/extensions/flyweb/skin/osx/icon-64-anchored.pngbin0 -> 6468 bytes
-rw-r--r--browser/extensions/flyweb/skin/osx/icon-64.pngbin0 -> 3168 bytes
-rw-r--r--browser/extensions/flyweb/skin/shared/flyweb.css54
-rw-r--r--browser/extensions/flyweb/skin/windows/flyweb.css5
-rw-r--r--browser/extensions/flyweb/skin/windows/icon-16.pngbin0 -> 1251 bytes
-rw-r--r--browser/extensions/flyweb/skin/windows/icon-32-anchored.pngbin0 -> 2711 bytes
-rw-r--r--browser/extensions/flyweb/skin/windows/icon-32.pngbin0 -> 699 bytes
-rw-r--r--browser/extensions/flyweb/skin/windows/icon-64-anchored.pngbin0 -> 6468 bytes
-rw-r--r--browser/extensions/flyweb/skin/windows/icon-64.pngbin0 -> 1311 bytes
-rw-r--r--browser/extensions/formautofill/.eslintrc.js474
-rw-r--r--browser/extensions/formautofill/bootstrap.js12
-rw-r--r--browser/extensions/formautofill/content/FormAutofillContent.jsm134
-rw-r--r--browser/extensions/formautofill/content/FormAutofillParent.jsm173
-rw-r--r--browser/extensions/formautofill/content/ProfileStorage.jsm251
-rw-r--r--browser/extensions/formautofill/install.rdf.in32
-rw-r--r--browser/extensions/formautofill/jar.mn7
-rw-r--r--browser/extensions/formautofill/moz.build22
-rw-r--r--browser/extensions/formautofill/test/browser/.eslintrc.js7
-rw-r--r--browser/extensions/formautofill/test/browser/browser.ini3
-rw-r--r--browser/extensions/formautofill/test/browser/browser_check_installed.js14
-rw-r--r--browser/extensions/formautofill/test/unit/.eslintrc5
-rw-r--r--browser/extensions/formautofill/test/unit/head.js81
-rw-r--r--browser/extensions/formautofill/test/unit/test_autofillFormFields.js200
-rw-r--r--browser/extensions/formautofill/test/unit/test_collectFormFields.js122
-rw-r--r--browser/extensions/formautofill/test/unit/test_populateFieldValues.js106
-rw-r--r--browser/extensions/formautofill/test/unit/test_profileStorage.js222
-rw-r--r--browser/extensions/formautofill/test/unit/xpcshell.ini12
-rw-r--r--browser/extensions/moz.build20
-rw-r--r--browser/extensions/pdfjs/LICENSE177
-rw-r--r--browser/extensions/pdfjs/README.mozilla3
-rw-r--r--browser/extensions/pdfjs/chrome.manifest1
-rw-r--r--browser/extensions/pdfjs/content/PdfJs.jsm348
-rw-r--r--browser/extensions/pdfjs/content/PdfJsNetwork.jsm257
-rw-r--r--browser/extensions/pdfjs/content/PdfJsTelemetry.jsm70
-rw-r--r--browser/extensions/pdfjs/content/PdfStreamConverter.jsm1045
-rw-r--r--browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm357
-rw-r--r--browser/extensions/pdfjs/content/PdfjsContentUtils.jsm150
-rw-r--r--browser/extensions/pdfjs/content/build/pdf.js8387
-rw-r--r--browser/extensions/pdfjs/content/build/pdf.worker.js52491
-rw-r--r--browser/extensions/pdfjs/content/network.js614
-rw-r--r--browser/extensions/pdfjs/content/pdfjschildbootstrap.js36
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78-EUC-H.bcmapbin0 -> 2404 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78-EUC-V.bcmapbin0 -> 173 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78-H.bcmapbin0 -> 2379 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-H.bcmapbin0 -> 2398 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-V.bcmapbin0 -> 173 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78-V.bcmapbin0 -> 169 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-H.bcmapbin0 -> 2651 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-V.bcmapbin0 -> 290 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/83pv-RKSJ-H.bcmapbin0 -> 905 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-H.bcmapbin0 -> 721 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-V.bcmapbin0 -> 290 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-H.bcmapbin0 -> 715 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-V.bcmapbin0 -> 291 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-H.bcmapbin0 -> 982 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-V.bcmapbin0 -> 260 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Add-H.bcmapbin0 -> 2419 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-H.bcmapbin0 -> 2413 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-V.bcmapbin0 -> 287 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Add-V.bcmapbin0 -> 282 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-0.bcmapbin0 -> 317 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-1.bcmapbin0 -> 371 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-2.bcmapbin0 -> 376 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-3.bcmapbin0 -> 401 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-4.bcmapbin0 -> 405 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-5.bcmapbin0 -> 406 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-6.bcmapbin0 -> 406 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-UCS2.bcmapbin0 -> 41193 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-0.bcmapbin0 -> 217 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-1.bcmapbin0 -> 250 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-2.bcmapbin0 -> 465 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-3.bcmapbin0 -> 470 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-4.bcmapbin0 -> 601 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-5.bcmapbin0 -> 625 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-UCS2.bcmapbin0 -> 33974 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-0.bcmapbin0 -> 225 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-1.bcmapbin0 -> 226 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-2.bcmapbin0 -> 233 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-3.bcmapbin0 -> 242 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-4.bcmapbin0 -> 337 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-5.bcmapbin0 -> 430 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-6.bcmapbin0 -> 485 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-UCS2.bcmapbin0 -> 40951 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-0.bcmapbin0 -> 241 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-1.bcmapbin0 -> 386 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-2.bcmapbin0 -> 391 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-UCS2.bcmapbin0 -> 23293 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/B5-H.bcmapbin0 -> 1086 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/B5-V.bcmapbin0 -> 142 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/B5pc-H.bcmapbin0 -> 1099 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/B5pc-V.bcmapbin0 -> 144 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-H.bcmapbin0 -> 1780 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-V.bcmapbin0 -> 1920 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/CNS1-H.bcmapbin0 -> 706 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/CNS1-V.bcmapbin0 -> 143 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/CNS2-H.bcmapbin0 -> 504 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/CNS2-V.bcmap3
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-H.bcmapbin0 -> 4426 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-V.bcmapbin0 -> 158 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/ETen-B5-H.bcmapbin0 -> 1125 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/ETen-B5-V.bcmapbin0 -> 158 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-H.bcmap3
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-V.bcmapbin0 -> 172 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/EUC-H.bcmapbin0 -> 578 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/EUC-V.bcmapbin0 -> 170 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Ext-H.bcmapbin0 -> 2536 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-H.bcmapbin0 -> 2542 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-V.bcmapbin0 -> 218 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Ext-V.bcmapbin0 -> 215 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GB-EUC-H.bcmapbin0 -> 549 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GB-EUC-V.bcmapbin0 -> 179 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GB-H.bcmap4
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GB-V.bcmapbin0 -> 175 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-H.bcmapbin0 -> 14692 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-V.bcmapbin0 -> 180 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBK2K-H.bcmapbin0 -> 19662 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBK2K-V.bcmapbin0 -> 219 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-H.bcmapbin0 -> 14686 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-V.bcmapbin0 -> 181 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-H.bcmapbin0 -> 7290 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-V.bcmapbin0 -> 180 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBT-H.bcmapbin0 -> 7269 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBT-V.bcmapbin0 -> 176 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-H.bcmapbin0 -> 7298 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-V.bcmapbin0 -> 182 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-H.bcmapbin0 -> 557 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-V.bcmapbin0 -> 181 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/H.bcmapbin0 -> 553 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-H.bcmapbin0 -> 2654 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-V.bcmapbin0 -> 148 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-H.bcmapbin0 -> 2414 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-V.bcmapbin0 -> 148 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-H.bcmapbin0 -> 2292 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-V.bcmapbin0 -> 149 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-H.bcmapbin0 -> 1772 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-V.bcmapbin0 -> 149 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-H.bcmapbin0 -> 2171 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-V.bcmapbin0 -> 149 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-H.bcmapbin0 -> 4437 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-V.bcmapbin0 -> 159 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Hankaku.bcmapbin0 -> 132 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Hiragana.bcmapbin0 -> 124 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-H.bcmapbin0 -> 1848 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-V.bcmapbin0 -> 164 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSC-H.bcmapbin0 -> 1831 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-H.bcmapbin0 -> 16791 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-V.bcmapbin0 -> 166 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSC-V.bcmapbin0 -> 160 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-H.bcmapbin0 -> 2787 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-H.bcmapbin0 -> 2789 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-V.bcmapbin0 -> 169 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-V.bcmapbin0 -> 166 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-H.bcmapbin0 -> 2024 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-V.bcmapbin0 -> 166 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Katakana.bcmapbin0 -> 100 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/LICENSE36
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/NWP-H.bcmapbin0 -> 2765 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/NWP-V.bcmapbin0 -> 252 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/RKSJ-H.bcmapbin0 -> 534 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/RKSJ-V.bcmapbin0 -> 170 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/Roman.bcmapbin0 -> 96 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-H.bcmapbin0 -> 48280 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-V.bcmapbin0 -> 156 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-H.bcmapbin0 -> 50419 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-V.bcmapbin0 -> 156 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-H.bcmapbin0 -> 52679 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-V.bcmapbin0 -> 160 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-H.bcmapbin0 -> 53629 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-V.bcmapbin0 -> 157 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-H.bcmapbin0 -> 43366 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-V.bcmapbin0 -> 193 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-H.bcmapbin0 -> 44086 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-V.bcmapbin0 -> 178 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-H.bcmapbin0 -> 45738 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-V.bcmapbin0 -> 182 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-H.bcmapbin0 -> 46837 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-V.bcmapbin0 -> 181 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-H.bcmapbin0 -> 25439 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-H.bcmapbin0 -> 119 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-V.bcmapbin0 -> 680 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-V.bcmapbin0 -> 664 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-H.bcmapbin0 -> 39443 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-V.bcmapbin0 -> 643 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-H.bcmapbin0 -> 40539 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-V.bcmapbin0 -> 677 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-H.bcmapbin0 -> 41695 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-V.bcmapbin0 -> 678 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-H.bcmapbin0 -> 39534 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-V.bcmapbin0 -> 647 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-H.bcmapbin0 -> 40630 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-V.bcmapbin0 -> 681 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-H.bcmapbin0 -> 41779 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-V.bcmapbin0 -> 682 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-HW-V.bcmapbin0 -> 705 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-V.bcmapbin0 -> 689 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UTF8-V.bcmapbin0 -> 726 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-H.bcmapbin0 -> 40517 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-V.bcmapbin0 -> 684 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-H.bcmapbin0 -> 40608 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-V.bcmapbin0 -> 688 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-H.bcmapbin0 -> 25783 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-V.bcmapbin0 -> 178 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-H.bcmapbin0 -> 26327 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-V.bcmapbin0 -> 164 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-H.bcmapbin0 -> 26451 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-V.bcmapbin0 -> 168 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-H.bcmapbin0 -> 27790 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-V.bcmapbin0 -> 169 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/V.bcmapbin0 -> 166 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/cmaps/WP-Symbol.bcmapbin0 -> 179 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/debugger.js616
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-check.svg11
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-comment.svg16
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-help.svg26
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-insert.svg10
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-key.svg11
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-newparagraph.svg11
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-noicon.svg7
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-note.svg42
-rw-r--r--browser/extensions/pdfjs/content/web/images/annotation-paragraph.svg16
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl.pngbin0 -> 199 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl@2x.pngbin0 -> 304 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-next.pngbin0 -> 193 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-next@2x.pngbin0 -> 296 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl.pngbin0 -> 193 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl@2x.pngbin0 -> 296 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-previous.pngbin0 -> 199 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/findbarButton-previous@2x.pngbin0 -> 304 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/grab.curbin0 -> 326 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/grabbing.curbin0 -> 326 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/loading-icon.gifbin0 -> 2545 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/loading-small.pngbin0 -> 7402 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/loading-small@2x.pngbin0 -> 16131 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties.pngbin0 -> 403 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties@2x.pngbin0 -> 933 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage.pngbin0 -> 179 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage@2x.pngbin0 -> 266 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool.pngbin0 -> 301 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool@2x.pngbin0 -> 583 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage.pngbin0 -> 175 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage@2x.pngbin0 -> 276 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw.pngbin0 -> 360 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw@2x.pngbin0 -> 731 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw.pngbin0 -> 359 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw@2x.pngbin0 -> 714 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/shadow.pngbin0 -> 290 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/texture.pngbin0 -> 2417 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark.pngbin0 -> 174 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark@2x.pngbin0 -> 260 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-download.pngbin0 -> 259 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-download@2x.pngbin0 -> 425 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows.pngbin0 -> 107 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows@2x.pngbin0 -> 152 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-openFile.pngbin0 -> 295 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-openFile@2x.pngbin0 -> 550 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl.pngbin0 -> 242 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl@2x.pngbin0 -> 398 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown.pngbin0 -> 238 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown@2x.pngbin0 -> 396 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl.pngbin0 -> 245 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl@2x.pngbin0 -> 405 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp.pngbin0 -> 246 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp@2x.pngbin0 -> 403 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode.pngbin0 -> 321 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode@2x.pngbin0 -> 586 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-print.pngbin0 -> 257 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-print@2x.pngbin0 -> 464 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-search.pngbin0 -> 309 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-search@2x.pngbin0 -> 653 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl.pngbin0 -> 246 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl@2x.pngbin0 -> 456 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle.pngbin0 -> 243 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle@2x.pngbin0 -> 458 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl.pngbin0 -> 225 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl@2x.pngbin0 -> 344 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle.pngbin0 -> 225 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle@2x.pngbin0 -> 331 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments.pngbin0 -> 384 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments@2x.pngbin0 -> 859 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl.pngbin0 -> 177 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl@2x.pngbin0 -> 394 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline.pngbin0 -> 178 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline@2x.pngbin0 -> 331 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail.pngbin0 -> 185 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail@2x.pngbin0 -> 219 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn.pngbin0 -> 136 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn@2x.pngbin0 -> 160 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut.pngbin0 -> 88 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut@2x.pngbin0 -> 109 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl.pngbin0 -> 143 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl@2x.pngbin0 -> 167 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/treeitem-collapsed.pngbin0 -> 128 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/treeitem-collapsed@2x.pngbin0 -> 149 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/treeitem-expanded.pngbin0 -> 125 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/images/treeitem-expanded@2x.pngbin0 -> 172 bytes
-rw-r--r--browser/extensions/pdfjs/content/web/l10n.js151
-rw-r--r--browser/extensions/pdfjs/content/web/viewer.css2034
-rw-r--r--browser/extensions/pdfjs/content/web/viewer.html337
-rw-r--r--browser/extensions/pdfjs/content/web/viewer.js8671
-rw-r--r--browser/extensions/pdfjs/jar.mn3
-rw-r--r--browser/extensions/pdfjs/moz.build9
-rw-r--r--browser/extensions/pdfjs/test/.eslintrc.js7
-rw-r--r--browser/extensions/pdfjs/test/browser.ini10
-rw-r--r--browser/extensions/pdfjs/test/browser_pdfjs_main.js67
-rw-r--r--browser/extensions/pdfjs/test/browser_pdfjs_navigation.js283
-rw-r--r--browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js65
-rw-r--r--browser/extensions/pdfjs/test/browser_pdfjs_views.js67
-rw-r--r--browser/extensions/pdfjs/test/browser_pdfjs_zoom.js154
-rw-r--r--browser/extensions/pdfjs/test/file_pdfjs_test.pdfbin0 -> 150611 bytes
-rw-r--r--browser/extensions/pdfjs/test/head.js15
-rw-r--r--browser/extensions/pocket/bootstrap.js511
-rw-r--r--browser/extensions/pocket/content/AboutPocket.jsm93
-rw-r--r--browser/extensions/pocket/content/Pocket.jsm93
-rw-r--r--browser/extensions/pocket/content/main.js737
-rw-r--r--browser/extensions/pocket/content/panels/css/firasans.css6
-rw-r--r--browser/extensions/pocket/content/panels/css/normalize.css424
-rw-r--r--browser/extensions/pocket/content/panels/css/saved.css825
-rw-r--r--browser/extensions/pocket/content/panels/css/signup.css424
-rw-r--r--browser/extensions/pocket/content/panels/fonts/FiraSans-Regular.woffbin0 -> 179188 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocket.svg22
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketerror@1x.pngbin0 -> 1479 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketerror@2x.pngbin0 -> 3131 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketlogo@1x.pngbin0 -> 3539 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketlogo@2x.pngbin0 -> 7378 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketlogosolo@1x.pngbin0 -> 1256 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketlogosolo@2x.pngbin0 -> 2566 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketmenuitem16.pngbin0 -> 264 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketmenuitem16@2x.pngbin0 -> 641 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketsignup_button@1x.pngbin0 -> 7315 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketsignup_button@2x.pngbin0 -> 13480 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketsignup_devices@1x.pngbin0 -> 22496 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketsignup_devices@2x.pngbin0 -> 73925 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketsignup_hero@1x.pngbin0 -> 44964 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/pocketsignup_hero@2x.pngbin0 -> 148877 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/signup_firefoxlogo@1x.pngbin0 -> 635 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/signup_firefoxlogo@2x.pngbin0 -> 1375 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/signup_help@1x.pngbin0 -> 659 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/signup_help@2x.pngbin0 -> 1420 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/signup_or@1x.pngbin0 -> 1843 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/signup_or@2x.pngbin0 -> 2925 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/tag_close@1x.pngbin0 -> 287 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/tag_close@2x.pngbin0 -> 508 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/tag_closeactive@1x.pngbin0 -> 208 bytes
-rw-r--r--browser/extensions/pocket/content/panels/img/tag_closeactive@2x.pngbin0 -> 354 bytes
-rw-r--r--browser/extensions/pocket/content/panels/js/messages.js78
-rw-r--r--browser/extensions/pocket/content/panels/js/saved.js608
-rw-r--r--browser/extensions/pocket/content/panels/js/signup.js193
-rw-r--r--browser/extensions/pocket/content/panels/js/tmpl.js242
-rw-r--r--browser/extensions/pocket/content/panels/js/vendor/handlebars.runtime.js660
-rw-r--r--browser/extensions/pocket/content/panels/js/vendor/jquery-2.1.1.min.js4
-rw-r--r--browser/extensions/pocket/content/panels/js/vendor/jquery.tokeninput.min.js954
-rw-r--r--browser/extensions/pocket/content/panels/license.txt35
-rw-r--r--browser/extensions/pocket/content/panels/saved.html19
-rw-r--r--browser/extensions/pocket/content/panels/signup.html18
-rw-r--r--browser/extensions/pocket/content/panels/tmpl/saved_premiumextras.handlebars2
-rw-r--r--browser/extensions/pocket/content/panels/tmpl/saved_premiumshell.handlebars6
-rw-r--r--browser/extensions/pocket/content/panels/tmpl/saved_shell.handlebars29
-rw-r--r--browser/extensions/pocket/content/panels/tmpl/signup_shell.handlebars32
-rw-r--r--browser/extensions/pocket/content/panels/tmpl/signupstoryboard_shell.handlebars43
-rw-r--r--browser/extensions/pocket/content/pktApi.jsm657
-rw-r--r--browser/extensions/pocket/content/pocket-content-process.js54
-rw-r--r--browser/extensions/pocket/install.rdf.in32
-rw-r--r--browser/extensions/pocket/jar.mn32
-rw-r--r--browser/extensions/pocket/locale/ast/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/az/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/bg/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/bn-BD/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/cs/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/da/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/de/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/dsb/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/en-GB/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/en-US/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/es-AR/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/es-CL/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/es-ES/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/es-MX/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/et/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/fi/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/fr/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/fy-NL/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/gu-IN/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/hr/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/hsb/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/hu/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/it/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/ja/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/jar.mn33
-rw-r--r--browser/extensions/pocket/locale/ka/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/kab/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/lt/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/lv/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/moz.build7
-rw-r--r--browser/extensions/pocket/locale/mr/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/ms/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/nl/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/nn-NO/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/or/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/pl/pocket.properties48
-rw-r--r--browser/extensions/pocket/locale/pt-BR/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/pt-PT/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/rm/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/ro/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/ru/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/sk/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/sl/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/sq/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/sr/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/sv-SE/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/te/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/th/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/tr/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/uk/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/zh-CN/pocket.properties43
-rw-r--r--browser/extensions/pocket/locale/zh-TW/pocket.properties43
-rw-r--r--browser/extensions/pocket/moz.build22
-rw-r--r--browser/extensions/pocket/skin/linux/Toolbar-inverted.pngbin0 -> 743 bytes
-rw-r--r--browser/extensions/pocket/skin/linux/Toolbar-inverted@2x.pngbin0 -> 2299 bytes
-rw-r--r--browser/extensions/pocket/skin/linux/Toolbar.pngbin0 -> 975 bytes
-rw-r--r--browser/extensions/pocket/skin/linux/Toolbar@2x.pngbin0 -> 3223 bytes
-rw-r--r--browser/extensions/pocket/skin/linux/menuPanel.pngbin0 -> 1377 bytes
-rw-r--r--browser/extensions/pocket/skin/linux/menuPanel@2x.pngbin0 -> 2671 bytes
-rw-r--r--browser/extensions/pocket/skin/linux/pocket.css10
-rw-r--r--browser/extensions/pocket/skin/osx/Toolbar-inverted.pngbin0 -> 1438 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/Toolbar-inverted@2x.pngbin0 -> 3323 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/Toolbar-yosemite.pngbin0 -> 958 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/Toolbar-yosemite@2x.pngbin0 -> 2129 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/Toolbar.pngbin0 -> 1571 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/Toolbar@2x.pngbin0 -> 3613 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/menuPanel-yosemite.pngbin0 -> 1570 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/menuPanel-yosemite@2x.pngbin0 -> 3316 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/menuPanel.pngbin0 -> 1976 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/menuPanel@2x.pngbin0 -> 3861 bytes
-rw-r--r--browser/extensions/pocket/skin/osx/pocket.css42
-rw-r--r--browser/extensions/pocket/skin/shared/pocket.css79
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-XP.pngbin0 -> 1141 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-aero.pngbin0 -> 1163 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-aero@2x.pngbin0 -> 2612 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-inverted.pngbin0 -> 825 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-inverted@2x.pngbin0 -> 1767 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-lunaSilver.pngbin0 -> 1155 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-win8.pngbin0 -> 649 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar-win8@2x.pngbin0 -> 1036 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar.pngbin0 -> 635 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/Toolbar@2x.pngbin0 -> 1001 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/menuPanel-aero.pngbin0 -> 2097 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/menuPanel-aero@2x.pngbin0 -> 5063 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/menuPanel.pngbin0 -> 1377 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/menuPanel@2x.pngbin0 -> 2671 bytes
-rw-r--r--browser/extensions/pocket/skin/windows/pocket.css16
-rw-r--r--browser/extensions/pocket/test/.eslintrc.js7
-rw-r--r--browser/extensions/pocket/test/browser.ini6
-rw-r--r--browser/extensions/pocket/test/browser_pocket_ui_check.js61
-rw-r--r--browser/extensions/pocket/test/head.js67
-rw-r--r--browser/extensions/pocket/test/test.html11
-rw-r--r--browser/extensions/webcompat/bootstrap.js9
-rw-r--r--browser/extensions/webcompat/install.rdf.in32
-rw-r--r--browser/extensions/webcompat/moz.build18
-rw-r--r--browser/extensions/webcompat/test/browser/.eslintrc.js7
-rw-r--r--browser/extensions/webcompat/test/browser/browser.ini3
-rw-r--r--browser/extensions/webcompat/test/browser/browser_check_installed.js13
-rw-r--r--browser/fonts/EmojiOneMozilla.ttfbin0 -> 1227260 bytes
-rw-r--r--browser/fonts/README.txt9
-rw-r--r--browser/fonts/moz.build11
-rw-r--r--browser/installer/Makefile.in186
-rw-r--r--browser/installer/allowed-dupes.mn228
-rw-r--r--browser/installer/package-manifest.in830
-rw-r--r--browser/installer/removed-files.in116
-rw-r--r--browser/installer/windows/Makefile.in100
-rw-r--r--browser/installer/windows/app.tag4
-rw-r--r--browser/installer/windows/moz.build11
-rw-r--r--browser/installer/windows/nsis/defines.nsi.in110
-rwxr-xr-xbrowser/installer/windows/nsis/installer.nsi1332
-rw-r--r--browser/installer/windows/nsis/maintenanceservice_installer.nsi335
-rw-r--r--browser/installer/windows/nsis/oneoff_en-US.nsh12
-rwxr-xr-xbrowser/installer/windows/nsis/shared.nsh1410
-rw-r--r--browser/installer/windows/nsis/stub.nsi2093
-rwxr-xr-xbrowser/installer/windows/nsis/uninstaller.nsi627
-rw-r--r--browser/installer/windows/nsis/updater_append.ini12
-rw-r--r--browser/installer/windows/stub.tag4
-rw-r--r--browser/locales/Makefile.in200
-rw-r--r--browser/locales/all-locales97
-rw-r--r--browser/locales/en-US/chrome/browser-region/region.properties41
-rw-r--r--browser/locales/en-US/chrome/browser/aboutAccounts.dtd16
-rw-r--r--browser/locales/en-US/chrome/browser/aboutDialog.dtd108
-rw-r--r--browser/locales/en-US/chrome/browser/aboutHealthReport.dtd6
-rw-r--r--browser/locales/en-US/chrome/browser/aboutHome.dtd40
-rw-r--r--browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd31
-rw-r--r--browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.properties6
-rw-r--r--browser/locales/en-US/chrome/browser/aboutRobots.dtd29
-rw-r--r--browser/locales/en-US/chrome/browser/aboutSearchReset.dtd30
-rw-r--r--browser/locales/en-US/chrome/browser/aboutSessionRestore.dtd51
-rw-r--r--browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd20
-rw-r--r--browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd30
-rw-r--r--browser/locales/en-US/chrome/browser/accounts.properties70
-rw-r--r--browser/locales/en-US/chrome/browser/baseMenuOverlay.dtd54
-rw-r--r--browser/locales/en-US/chrome/browser/browser.dtd884
-rw-r--r--browser/locales/en-US/chrome/browser/browser.properties763
-rw-r--r--browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties115
-rw-r--r--browser/locales/en-US/chrome/browser/downloads/downloads.dtd132
-rw-r--r--browser/locales/en-US/chrome/browser/downloads/downloads.properties108
-rw-r--r--browser/locales/en-US/chrome/browser/engineManager.properties7
-rw-r--r--browser/locales/en-US/chrome/browser/feeds/subscribe.dtd10
-rw-r--r--browser/locales/en-US/chrome/browser/feeds/subscribe.properties52
-rw-r--r--browser/locales/en-US/chrome/browser/lightweightThemes.properties18
-rw-r--r--browser/locales/en-US/chrome/browser/migration/migration.dtd46
-rw-r--r--browser/locales/en-US/chrome/browser/migration/migration.properties80
-rw-r--r--browser/locales/en-US/chrome/browser/newTab.dtd18
-rw-r--r--browser/locales/en-US/chrome/browser/newTab.properties46
-rw-r--r--browser/locales/en-US/chrome/browser/pageInfo.dtd88
-rw-r--r--browser/locales/en-US/chrome/browser/pageInfo.properties56
-rw-r--r--browser/locales/en-US/chrome/browser/places/bookmarkProperties.properties19
-rw-r--r--browser/locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd28
-rw-r--r--browser/locales/en-US/chrome/browser/places/moveBookmarks.dtd9
-rw-r--r--browser/locales/en-US/chrome/browser/places/places.dtd124
-rw-r--r--browser/locales/en-US/chrome/browser/places/places.properties92
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/advanced.dtd119
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/applicationManager.dtd8
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/applicationManager.properties14
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/applications.dtd14
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/blocklists.dtd14
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/colors.dtd30
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/connection.dtd49
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/containers.dtd24
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/containers.properties31
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/content.dtd60
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/cookies.dtd35
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/donottrack.dtd13
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/fonts.dtd107
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/languages.dtd18
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/main.dtd45
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/permissions.dtd28
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/preferences.dtd29
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/preferences.properties193
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/privacy.dtd113
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/search.dtd32
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/security.dtd40
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/selectBookmark.dtd9
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/sync.dtd117
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/tabs.dtd22
-rw-r--r--browser/locales/en-US/chrome/browser/preferences/translation.dtd24
-rw-r--r--browser/locales/en-US/chrome/browser/quitDialog.properties13
-rw-r--r--browser/locales/en-US/chrome/browser/safeMode.dtd17
-rw-r--r--browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd32
-rw-r--r--browser/locales/en-US/chrome/browser/safebrowsing/report-phishing.dtd13
-rw-r--r--browser/locales/en-US/chrome/browser/sanitize.dtd69
-rw-r--r--browser/locales/en-US/chrome/browser/search.properties50
-rw-r--r--browser/locales/en-US/chrome/browser/searchbar.dtd6
-rw-r--r--browser/locales/en-US/chrome/browser/setDesktopBackground.dtd15
-rw-r--r--browser/locales/en-US/chrome/browser/shellservice.properties31
-rw-r--r--browser/locales/en-US/chrome/browser/sitePermissions.properties20
-rw-r--r--browser/locales/en-US/chrome/browser/syncBrand.dtd7
-rw-r--r--browser/locales/en-US/chrome/browser/syncCustomize.dtd27
-rw-r--r--browser/locales/en-US/chrome/browser/syncGenericChange.properties37
-rw-r--r--browser/locales/en-US/chrome/browser/syncKey.dtd18
-rw-r--r--browser/locales/en-US/chrome/browser/syncQuota.dtd8
-rw-r--r--browser/locales/en-US/chrome/browser/syncQuota.properties42
-rw-r--r--browser/locales/en-US/chrome/browser/syncSetup.dtd114
-rw-r--r--browser/locales/en-US/chrome/browser/syncSetup.properties67
-rw-r--r--browser/locales/en-US/chrome/browser/tabbrowser.properties51
-rw-r--r--browser/locales/en-US/chrome/browser/taskbar.properties12
-rw-r--r--browser/locales/en-US/chrome/browser/translation.dtd75
-rw-r--r--browser/locales/en-US/chrome/browser/translation.properties12
-rw-r--r--browser/locales/en-US/chrome/browser/webrtcIndicator.properties61
-rw-r--r--browser/locales/en-US/chrome/overrides/appstrings.properties43
-rw-r--r--browser/locales/en-US/chrome/overrides/netError.dtd217
-rw-r--r--browser/locales/en-US/chrome/overrides/settingsChange.dtd7
-rw-r--r--browser/locales/en-US/crashreporter/crashreporter-override.ini9
-rw-r--r--browser/locales/en-US/defines.inc12
-rw-r--r--browser/locales/en-US/firefox-l10n.js11
-rw-r--r--browser/locales/en-US/installer/custom.properties85
-rw-r--r--browser/locales/en-US/installer/mui.properties61
-rw-r--r--browser/locales/en-US/installer/nsisstrings.properties67
-rw-r--r--browser/locales/en-US/installer/override.properties86
-rw-r--r--browser/locales/en-US/pdfviewer/chrome.properties19
-rw-r--r--browser/locales/en-US/pdfviewer/viewer.properties182
-rw-r--r--browser/locales/en-US/profile/bookmarks.inc72
-rw-r--r--browser/locales/en-US/updater/updater.ini10
-rwxr-xr-xbrowser/locales/filter.py38
-rw-r--r--browser/locales/generic/profile/bookmarks.html.in56
-rw-r--r--browser/locales/jar.mn120
-rw-r--r--browser/locales/l10n.ini22
-rw-r--r--browser/locales/moz.build7
-rw-r--r--browser/locales/search/list.json842
-rw-r--r--browser/locales/searchplugins/allaannonser-sv-SE.xml17
-rw-r--r--browser/locales/searchplugins/allegro-pl.xml17
-rw-r--r--browser/locales/searchplugins/amazon-en-GB.xml18
-rw-r--r--browser/locales/searchplugins/amazon-france.xml18
-rw-r--r--browser/locales/searchplugins/amazon-in.xml17
-rw-r--r--browser/locales/searchplugins/amazon-it.xml18
-rw-r--r--browser/locales/searchplugins/amazon-jp.xml30
-rw-r--r--browser/locales/searchplugins/amazondotcn.xml20
-rw-r--r--browser/locales/searchplugins/amazondotcom-de.xml18
-rw-r--r--browser/locales/searchplugins/amazondotcom.xml18
-rw-r--r--browser/locales/searchplugins/atlas-sk.xml15
-rw-r--r--browser/locales/searchplugins/azerdict.xml18
-rw-r--r--browser/locales/searchplugins/azet-sk.xml15
-rw-r--r--browser/locales/searchplugins/baidu.xml22
-rw-r--r--browser/locales/searchplugins/bbc-alba.xml19
-rw-r--r--browser/locales/searchplugins/bing.xml24
-rw-r--r--browser/locales/searchplugins/bok-NO.xml16
-rw-r--r--browser/locales/searchplugins/bolcom-fy-NL.xml13
-rw-r--r--browser/locales/searchplugins/bolcom-nl.xml13
-rw-r--r--browser/locales/searchplugins/bookplus-fi.xml15
-rw-r--r--browser/locales/searchplugins/buscape.xml15
-rw-r--r--browser/locales/searchplugins/ceneji.xml15
-rw-r--r--browser/locales/searchplugins/chambers-en-GB.xml16
-rw-r--r--browser/locales/searchplugins/cnrtl-tlfi-fr.xml16
-rw-r--r--browser/locales/searchplugins/danawa-kr.xml15
-rw-r--r--browser/locales/searchplugins/daum-kr.xml21
-rw-r--r--browser/locales/searchplugins/ddg.xml23
-rw-r--r--browser/locales/searchplugins/diccionariu-alla.xml14
-rw-r--r--browser/locales/searchplugins/dict-enlv.xml15
-rw-r--r--browser/locales/searchplugins/diec2.xml15
-rw-r--r--browser/locales/searchplugins/diribg.xml17
-rw-r--r--browser/locales/searchplugins/drae.xml13
-rw-r--r--browser/locales/searchplugins/dunaj-sk.xml18
-rw-r--r--browser/locales/searchplugins/eki-ee.xml19
-rw-r--r--browser/locales/searchplugins/elebila.xml16
-rw-r--r--browser/locales/searchplugins/eudict.xml16
-rw-r--r--browser/locales/searchplugins/faclair-beag.xml13
-rw-r--r--browser/locales/searchplugins/findbook-zh-TW.xml18
-rw-r--r--browser/locales/searchplugins/flip.xml19
-rw-r--r--browser/locales/searchplugins/freelang.xml18
-rw-r--r--browser/locales/searchplugins/google-2018.xml18
-rw-r--r--browser/locales/searchplugins/google-nocodes.xml16
-rw-r--r--browser/locales/searchplugins/google.xml18
-rw-r--r--browser/locales/searchplugins/gujaratilexicon.xml18
-rw-r--r--browser/locales/searchplugins/gulesider-NO.xml16
-rw-r--r--browser/locales/searchplugins/heureka-cz.xml18
-rw-r--r--browser/locales/searchplugins/hoepli.xml17
-rw-r--r--browser/locales/searchplugins/images/yandex-en.icobin0 -> 1691 bytes
-rw-r--r--browser/locales/searchplugins/kannadastore.xml14
-rw-r--r--browser/locales/searchplugins/kaz-kk.xml16
-rw-r--r--browser/locales/searchplugins/klask.xml17
-rw-r--r--browser/locales/searchplugins/leit-is.xml15
-rw-r--r--browser/locales/searchplugins/leo_ende_de-rm.xml18
-rw-r--r--browser/locales/searchplugins/leo_ende_de.xml18
-rw-r--r--browser/locales/searchplugins/list-am.xml16
-rw-r--r--browser/locales/searchplugins/longdo.xml20
-rw-r--r--browser/locales/searchplugins/mailru.xml21
-rw-r--r--browser/locales/searchplugins/mapy-cz.xml15
-rw-r--r--browser/locales/searchplugins/marktplaats-fy-NL.xml15
-rw-r--r--browser/locales/searchplugins/marktplaats-nl.xml15
-rw-r--r--browser/locales/searchplugins/mercadolibre-ar.xml15
-rw-r--r--browser/locales/searchplugins/mercadolibre-cl.xml15
-rw-r--r--browser/locales/searchplugins/mercadolibre-mx.xml15
-rw-r--r--browser/locales/searchplugins/mercadolivre.xml15
-rw-r--r--browser/locales/searchplugins/meta-ua.xml20
-rw-r--r--browser/locales/searchplugins/metamarket.xml16
-rw-r--r--browser/locales/searchplugins/morfix-dic.xml38
-rw-r--r--browser/locales/searchplugins/najdi-si.xml16
-rw-r--r--browser/locales/searchplugins/naver-kr.xml22
-rw-r--r--browser/locales/searchplugins/neti-ee.xml15
-rw-r--r--browser/locales/searchplugins/odpiralni.xml15
-rw-r--r--browser/locales/searchplugins/olx.xml17
-rw-r--r--browser/locales/searchplugins/oshiete-goo.xml15
-rw-r--r--browser/locales/searchplugins/osta-ee.xml14
-rw-r--r--browser/locales/searchplugins/ozonru.xml20
-rw-r--r--browser/locales/searchplugins/palasprint.xml15
-rw-r--r--browser/locales/searchplugins/paroledigenova-lij.xml19
-rw-r--r--browser/locales/searchplugins/pledarigrond.xml12
-rw-r--r--browser/locales/searchplugins/pogodak.xml15
-rw-r--r--browser/locales/searchplugins/portalbgdict.xml17
-rw-r--r--browser/locales/searchplugins/priberam.xml27
-rw-r--r--browser/locales/searchplugins/priceru.xml15
-rw-r--r--browser/locales/searchplugins/prisjakt-sv-SE.xml19
-rw-r--r--browser/locales/searchplugins/pwn-pl.xml14
-rw-r--r--browser/locales/searchplugins/qxl-NO.xml16
-rw-r--r--browser/locales/searchplugins/rakuten.xml16
-rw-r--r--browser/locales/searchplugins/rediff.xml15
-rw-r--r--browser/locales/searchplugins/reta-vortaro.xml16
-rw-r--r--browser/locales/searchplugins/salidzinilv.xml19
-rw-r--r--browser/locales/searchplugins/sapo.xml15
-rw-r--r--browser/locales/searchplugins/seznam-cz.xml17
-rw-r--r--browser/locales/searchplugins/slovnik-sk.xml15
-rw-r--r--browser/locales/searchplugins/sslv.xml15
-rw-r--r--browser/locales/searchplugins/sztaki-en-hu.xml21
-rw-r--r--browser/locales/searchplugins/tearma.xml14
-rw-r--r--browser/locales/searchplugins/termau.xml13
-rw-r--r--browser/locales/searchplugins/twitter-ja.xml15
-rw-r--r--browser/locales/searchplugins/twitter.xml15
-rw-r--r--browser/locales/searchplugins/tyda-sv-SE.xml14
-rw-r--r--browser/locales/searchplugins/vatera.xml18
-rw-r--r--browser/locales/searchplugins/webdunia.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-NN.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-NO.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-af.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-an.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ar.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-as.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ast.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-az.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-bg.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-bn.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-br.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-bs.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ca.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-cy.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-cz.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-da.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-de.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-dsb.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-el.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-eo.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-es.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-et.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-eu.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-fa.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-fi.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-fr.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-fy-NL.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ga-IE.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-gd.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-gl.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-gn.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-gu.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-he.xml19
-rwxr-xr-xbrowser/locales/searchplugins/wikipedia-hi.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-hr.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-hsb.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-hu.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-hy.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-id.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-is.xml21
-rw-r--r--browser/locales/searchplugins/wikipedia-it.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ja.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ka.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-kab.xml18
-rw-r--r--browser/locales/searchplugins/wikipedia-kk.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-km.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-kn.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-kr.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-lij.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-lt.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-lv.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-mk.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ml.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-mr.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ms.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-my.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ne.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-nl.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-or.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-pa.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-pl.xml21
-rw-r--r--browser/locales/searchplugins/wikipedia-pt.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-rm.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ru.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-si.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-sk.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-sl.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-sq.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-sr.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-sv-SE.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ta.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-te.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-th.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-tl.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-tr.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-uk.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-ur.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-uz.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-vi.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-wo.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-zh-CN.xml19
-rw-r--r--browser/locales/searchplugins/wikipedia-zh-TW.xml20
-rw-r--r--browser/locales/searchplugins/wikipedia.xml19
-rw-r--r--browser/locales/searchplugins/wikipediaro.xml19
-rw-r--r--browser/locales/searchplugins/wiktionary-te.xml19
-rw-r--r--browser/locales/searchplugins/wolnelektury-pl.xml17
-rw-r--r--browser/locales/searchplugins/yahoo-NO.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-answer-zh-TW.xml16
-rw-r--r--browser/locales/searchplugins/yahoo-ar.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-bid-zh-TW.xml15
-rw-r--r--browser/locales/searchplugins/yahoo-br.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-ch.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-cl.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-de.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-en-CA.xml28
-rw-r--r--browser/locales/searchplugins/yahoo-en-GB.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-es.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-espanol.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-fi.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-france.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-fy-NL.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-id.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-in.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-it.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-jp-auctions.xml17
-rw-r--r--browser/locales/searchplugins/yahoo-jp.xml16
-rw-r--r--browser/locales/searchplugins/yahoo-mx.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-sv-SE.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-tl.xml22
-rw-r--r--browser/locales/searchplugins/yahoo-zh-TW-HK.xml27
-rw-r--r--browser/locales/searchplugins/yahoo-zh-TW.xml27
-rw-r--r--browser/locales/searchplugins/yahoo.xml28
-rw-r--r--browser/locales/searchplugins/yandex-az.xml17
-rw-r--r--browser/locales/searchplugins/yandex-en.xml17
-rw-r--r--browser/locales/searchplugins/yandex-kk.xml22
-rw-r--r--browser/locales/searchplugins/yandex-ru.xml22
-rw-r--r--browser/locales/searchplugins/yandex-tr.xml22
-rw-r--r--browser/locales/searchplugins/yandex-uk.xml16
-rw-r--r--browser/locales/searchplugins/zing-mp3.xml17
-rw-r--r--browser/locales/searchplugins/zoznam-sk.xml15
-rw-r--r--browser/locales/shipped-locales93
-rw-r--r--browser/modules/AboutHome.jsm194
-rw-r--r--browser/modules/AboutNewTab.jsm43
-rw-r--r--browser/modules/AttributionCode.jsm123
-rw-r--r--browser/modules/BrowserUITelemetry.jsm896
-rw-r--r--browser/modules/BrowserUsageTelemetry.jsm468
-rw-r--r--browser/modules/CastingApps.jsm164
-rw-r--r--browser/modules/ContentClick.jsm97
-rw-r--r--browser/modules/ContentCrashHandlers.jsm1035
-rw-r--r--browser/modules/ContentLinkHandler.jsm147
-rw-r--r--browser/modules/ContentObservers.jsm55
-rw-r--r--browser/modules/ContentSearch.jsm566
-rw-r--r--browser/modules/ContentWebRTC.jsm392
-rw-r--r--browser/modules/DirectoryLinksProvider.jsm1255
-rw-r--r--browser/modules/E10SUtils.jsm128
-rw-r--r--browser/modules/Feeds.jsm104
-rw-r--r--browser/modules/FormSubmitObserver.jsm235
-rw-r--r--browser/modules/FormValidationHandler.jsm157
-rw-r--r--browser/modules/HiddenFrame.jsm86
-rw-r--r--browser/modules/LaterRun.jsm172
-rw-r--r--browser/modules/NetworkPrioritizer.jsm194
-rw-r--r--browser/modules/PermissionUI.jsm595
-rw-r--r--browser/modules/PluginContent.jsm1154
-rw-r--r--browser/modules/ProcessHangMonitor.jsm397
-rw-r--r--browser/modules/ReaderParent.jsm186
-rw-r--r--browser/modules/RecentWindow.jsm67
-rw-r--r--browser/modules/RemotePrompt.jsm110
-rw-r--r--browser/modules/Sanitizer.jsm22
-rw-r--r--browser/modules/SelfSupportBackend.jsm331
-rw-r--r--browser/modules/SitePermissions.jsm269
-rw-r--r--browser/modules/Social.jsm272
-rw-r--r--browser/modules/SocialService.jsm1097
-rw-r--r--browser/modules/TransientPrefs.jsm24
-rw-r--r--browser/modules/URLBarZoom.jsm51
-rw-r--r--browser/modules/Windows8WindowFrameColor.jsm53
-rw-r--r--browser/modules/WindowsJumpLists.jsm579
-rw-r--r--browser/modules/WindowsPreviewPerTab.jsm862
-rw-r--r--browser/modules/moz.build56
-rw-r--r--browser/modules/offlineAppCache.jsm20
-rw-r--r--browser/modules/test/.eslintrc.js7
-rw-r--r--browser/modules/test/browser.ini42
-rw-r--r--browser/modules/test/browser_BrowserUITelemetry_buckets.js97
-rw-r--r--browser/modules/test/browser_BrowserUITelemetry_defaults.js37
-rw-r--r--browser/modules/test/browser_BrowserUITelemetry_sidebar.js56
-rw-r--r--browser/modules/test/browser_BrowserUITelemetry_syncedtabs.js114
-rw-r--r--browser/modules/test/browser_ContentSearch.js425
-rw-r--r--browser/modules/test/browser_NetworkPrioritizer.js165
-rw-r--r--browser/modules/test/browser_PermissionUI.js445
-rw-r--r--browser/modules/test/browser_ProcessHangNotifications.js189
-rw-r--r--browser/modules/test/browser_SelfSupportBackend.js214
-rw-r--r--browser/modules/test/browser_UnsubmittedCrashHandler.js680
-rw-r--r--browser/modules/test/browser_UsageTelemetry.js268
-rw-r--r--browser/modules/test/browser_UsageTelemetry_content.js121
-rw-r--r--browser/modules/test/browser_UsageTelemetry_content_aboutHome.js84
-rw-r--r--browser/modules/test/browser_UsageTelemetry_private_and_restore.js90
-rw-r--r--browser/modules/test/browser_UsageTelemetry_searchbar.js195
-rw-r--r--browser/modules/test/browser_UsageTelemetry_urlbar.js220
-rw-r--r--browser/modules/test/browser_taskbar_preview.js100
-rw-r--r--browser/modules/test/browser_urlBar_zoom.js73
-rw-r--r--browser/modules/test/contentSearch.js64
-rw-r--r--browser/modules/test/contentSearchBadImage.xml6
-rw-r--r--browser/modules/test/contentSearchSuggestions.sjs9
-rw-r--r--browser/modules/test/contentSearchSuggestions.xml6
-rw-r--r--browser/modules/test/head.js113
-rw-r--r--browser/modules/test/unit/social/.eslintrc.js7
-rw-r--r--browser/modules/test/unit/social/blocklist.xml6
-rw-r--r--browser/modules/test/unit/social/head.js210
-rw-r--r--browser/modules/test/unit/social/test_SocialService.js166
-rw-r--r--browser/modules/test/unit/social/test_SocialServiceMigration21.js54
-rw-r--r--browser/modules/test/unit/social/test_SocialServiceMigration22.js67
-rw-r--r--browser/modules/test/unit/social/test_SocialServiceMigration29.js61
-rw-r--r--browser/modules/test/unit/social/test_social.js32
-rw-r--r--browser/modules/test/unit/social/test_socialDisabledStartup.js29
-rw-r--r--browser/modules/test/unit/social/xpcshell.ini13
-rw-r--r--browser/modules/test/usageTelemetrySearchSuggestions.sjs9
-rw-r--r--browser/modules/test/usageTelemetrySearchSuggestions.xml6
-rw-r--r--browser/modules/test/xpcshell/.eslintrc.js7
-rw-r--r--browser/modules/test/xpcshell/test_AttributionCode.js110
-rw-r--r--browser/modules/test/xpcshell/test_DirectoryLinksProvider.js1854
-rw-r--r--browser/modules/test/xpcshell/test_LaterRun.js138
-rw-r--r--browser/modules/test/xpcshell/test_SitePermissions.js115
-rw-r--r--browser/modules/test/xpcshell/xpcshell.ini11
-rw-r--r--browser/modules/webrtcUI.jsm963
-rw-r--r--browser/moz.build41
-rw-r--r--browser/moz.configure12
-rw-r--r--browser/themes/LICENSE2
-rw-r--r--browser/themes/linux/Info.pngbin0 -> 767 bytes
-rw-r--r--browser/themes/linux/Privacy-16.pngbin0 -> 822 bytes
-rw-r--r--browser/themes/linux/Security-broken.pngbin0 -> 928 bytes
-rw-r--r--browser/themes/linux/Toolbar-inverted.pngbin0 -> 12548 bytes
-rw-r--r--browser/themes/linux/Toolbar-inverted@2x.pngbin0 -> 29489 bytes
-rw-r--r--browser/themes/linux/Toolbar-small.pngbin0 -> 5955 bytes
-rw-r--r--browser/themes/linux/Toolbar.pngbin0 -> 16494 bytes
-rw-r--r--browser/themes/linux/Toolbar@2x.pngbin0 -> 43720 bytes
-rw-r--r--browser/themes/linux/aboutSessionRestore-window-icon.pngbin0 -> 405 bytes
-rw-r--r--browser/themes/linux/aboutSyncTabs.css101
-rw-r--r--browser/themes/linux/actionicon-tab.pngbin0 -> 236 bytes
-rw-r--r--browser/themes/linux/browser-lightweightTheme.css31
-rw-r--r--browser/themes/linux/browser.css1658
-rw-r--r--browser/themes/linux/click-to-play-warning-stripes.pngbin0 -> 1563 bytes
-rw-r--r--browser/themes/linux/communicator/communicator.css6
-rw-r--r--browser/themes/linux/communicator/jar.mn7
-rw-r--r--browser/themes/linux/communicator/moz.build7
-rw-r--r--browser/themes/linux/controlcenter/panel.css13
-rw-r--r--browser/themes/linux/customizableui/background-noise-toolbar.pngbin0 -> 15601 bytes
-rw-r--r--browser/themes/linux/customizableui/customizeMode-gridTexture.pngbin0 -> 118 bytes
-rw-r--r--browser/themes/linux/customizableui/customizeMode-separatorHorizontal.pngbin0 -> 1426 bytes
-rw-r--r--browser/themes/linux/customizableui/customizeMode-separatorVertical.pngbin0 -> 1649 bytes
-rw-r--r--browser/themes/linux/customizableui/panelUI.css98
-rw-r--r--browser/themes/linux/devedition.css106
-rw-r--r--browser/themes/linux/downloads/allDownloadsViewOverlay.css11
-rw-r--r--browser/themes/linux/downloads/download-glow-menuPanel.pngbin0 -> 898 bytes
-rw-r--r--browser/themes/linux/downloads/download-notification-finish.pngbin0 -> 3887 bytes
-rw-r--r--browser/themes/linux/downloads/download-notification-start.pngbin0 -> 3166 bytes
-rw-r--r--browser/themes/linux/downloads/downloads.css21
-rw-r--r--browser/themes/linux/downloads/indicator.css218
-rw-r--r--browser/themes/linux/feeds/feedIcon.pngbin0 -> 1794 bytes
-rw-r--r--browser/themes/linux/feeds/feedIcon16.pngbin0 -> 799 bytes
-rw-r--r--browser/themes/linux/feeds/subscribe-ui.css7
-rw-r--r--browser/themes/linux/feeds/subscribe.css185
-rw-r--r--browser/themes/linux/jar.mn140
-rw-r--r--browser/themes/linux/linuxShared.inc13
-rw-r--r--browser/themes/linux/menuPanel-customize.pngbin0 -> 883 bytes
-rw-r--r--browser/themes/linux/menuPanel-customize@2x.pngbin0 -> 2193 bytes
-rw-r--r--browser/themes/linux/menuPanel-exit.pngbin0 -> 568 bytes
-rw-r--r--browser/themes/linux/menuPanel-exit@2x.pngbin0 -> 1139 bytes
-rw-r--r--browser/themes/linux/menuPanel-help.pngbin0 -> 1994 bytes
-rw-r--r--browser/themes/linux/menuPanel-help@2x.pngbin0 -> 5300 bytes
-rw-r--r--browser/themes/linux/monitor.pngbin0 -> 6217 bytes
-rw-r--r--browser/themes/linux/monitor_16-10.pngbin0 -> 6787 bytes
-rw-r--r--browser/themes/linux/moz.build11
-rw-r--r--browser/themes/linux/newtab/newTab.css19
-rw-r--r--browser/themes/linux/page-livemarks.pngbin0 -> 830 bytes
-rw-r--r--browser/themes/linux/pageInfo.css267
-rw-r--r--browser/themes/linux/pageInfo.pngbin0 -> 8849 bytes
-rw-r--r--browser/themes/linux/places/autocomplete-star.pngbin0 -> 636 bytes
-rw-r--r--browser/themes/linux/places/bookmarks-menu-arrow.pngbin0 -> 183 bytes
-rw-r--r--browser/themes/linux/places/bookmarks-notification-finish.pngbin0 -> 2875 bytes
-rw-r--r--browser/themes/linux/places/bookmarksMenu.pngbin0 -> 461 bytes
-rw-r--r--browser/themes/linux/places/bookmarksToolbar-menuPanel.pngbin0 -> 700 bytes
-rw-r--r--browser/themes/linux/places/bookmarksToolbar.pngbin0 -> 508 bytes
-rw-r--r--browser/themes/linux/places/calendar.pngbin0 -> 670 bytes
-rw-r--r--browser/themes/linux/places/downloads.pngbin0 -> 599 bytes
-rw-r--r--browser/themes/linux/places/editBookmarkOverlay.css71
-rw-r--r--browser/themes/linux/places/livemark-item.pngbin0 -> 863 bytes
-rw-r--r--browser/themes/linux/places/organizer.css103
-rw-r--r--browser/themes/linux/places/organizer.xml21
-rw-r--r--browser/themes/linux/places/places.css122
-rw-r--r--browser/themes/linux/places/query.pngbin0 -> 678 bytes
-rw-r--r--browser/themes/linux/places/starred48.pngbin0 -> 1877 bytes
-rw-r--r--browser/themes/linux/places/tag.pngbin0 -> 877 bytes
-rw-r--r--browser/themes/linux/places/toolbarDropMarker.pngbin0 -> 583 bytes
-rw-r--r--browser/themes/linux/places/unsortedBookmarks.pngbin0 -> 748 bytes
-rw-r--r--browser/themes/linux/places/unstarred48.pngbin0 -> 2255 bytes
-rw-r--r--browser/themes/linux/preferences/alwaysAsk.pngbin0 -> 575 bytes
-rw-r--r--browser/themes/linux/preferences/applications.css66
-rw-r--r--browser/themes/linux/preferences/in-content/dialog.css19
-rw-r--r--browser/themes/linux/preferences/in-content/preferences.css48
-rw-r--r--browser/themes/linux/preferences/mail.pngbin0 -> 548 bytes
-rw-r--r--browser/themes/linux/preferences/preferences.css106
-rw-r--r--browser/themes/linux/privatebrowsing-mask.pngbin0 -> 1355 bytes
-rw-r--r--browser/themes/linux/reload-stop-go.pngbin0 -> 1945 bytes
-rw-r--r--browser/themes/linux/reload-stop-go@2x.pngbin0 -> 3661 bytes
-rw-r--r--browser/themes/linux/sanitizeDialog.css107
-rw-r--r--browser/themes/linux/searchbar.css336
-rw-r--r--browser/themes/linux/setDesktopBackground.css18
-rw-r--r--browser/themes/linux/slowStartup-16.pngbin0 -> 478 bytes
-rw-r--r--browser/themes/linux/social/services-16.pngbin0 -> 937 bytes
-rw-r--r--browser/themes/linux/social/services-64.pngbin0 -> 5613 bytes
-rw-r--r--browser/themes/linux/social/share-button-active.pngbin0 -> 1341 bytes
-rw-r--r--browser/themes/linux/social/share-button.pngbin0 -> 1346 bytes
-rw-r--r--browser/themes/linux/sync-128.pngbin0 -> 20229 bytes
-rw-r--r--browser/themes/linux/sync-16.pngbin0 -> 1847 bytes
-rw-r--r--browser/themes/linux/sync-32.pngbin0 -> 3384 bytes
-rw-r--r--browser/themes/linux/sync-bg.pngbin0 -> 21309 bytes
-rw-r--r--browser/themes/linux/sync-horizontalbar.pngbin0 -> 721 bytes
-rw-r--r--browser/themes/linux/sync-horizontalbar@2x.pngbin0 -> 1376 bytes
-rw-r--r--browser/themes/linux/sync-notification-24.pngbin0 -> 1565 bytes
-rw-r--r--browser/themes/linux/syncCommon.css49
-rw-r--r--browser/themes/linux/syncProgress-horizontalbar.pngbin0 -> 11892 bytes
-rw-r--r--browser/themes/linux/syncProgress-horizontalbar@2x.pngbin0 -> 9668 bytes
-rw-r--r--browser/themes/linux/syncProgress-menuPanel.pngbin0 -> 25426 bytes
-rw-r--r--browser/themes/linux/syncProgress-menuPanel@2x.pngbin0 -> 55953 bytes
-rw-r--r--browser/themes/linux/syncProgress-toolbar-inverted.pngbin0 -> 14382 bytes
-rw-r--r--browser/themes/linux/syncProgress-toolbar.pngbin0 -> 13293 bytes
-rw-r--r--browser/themes/linux/syncQuota.css26
-rw-r--r--browser/themes/linux/syncSetup.css133
-rw-r--r--browser/themes/linux/syncedtabs/sidebar.css69
-rw-r--r--browser/themes/linux/tabbrowser/alltabs-inverted.pngbin0 -> 221 bytes
-rw-r--r--browser/themes/linux/tabbrowser/alltabs.pngbin0 -> 287 bytes
-rw-r--r--browser/themes/linux/tabbrowser/newtab-inverted.svg13
-rw-r--r--browser/themes/linux/tabbrowser/newtab.svg7
-rw-r--r--browser/themes/linux/tabbrowser/tab-active-middle.pngbin0 -> 92 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-active-middle@2x.pngbin0 -> 120 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-arrow-left-inverted.pngbin0 -> 250 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-arrow-left.pngbin0 -> 368 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-background-end.pngbin0 -> 802 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-background-end@2x.pngbin0 -> 2823 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-background-middle.pngbin0 -> 122 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-background-middle@2x.pngbin0 -> 782 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-background-start.pngbin0 -> 814 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-background-start@2x.pngbin0 -> 2940 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-stroke-end.pngbin0 -> 652 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-stroke-end@2x.pngbin0 -> 1467 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-stroke-start.pngbin0 -> 658 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tab-stroke-start@2x.pngbin0 -> 1477 bytes
-rw-r--r--browser/themes/linux/tabbrowser/tabDragIndicator.pngbin0 -> 450 bytes
-rw-r--r--browser/themes/linux/webRTC-indicator.css116
-rw-r--r--browser/themes/moz.build16
-rw-r--r--browser/themes/osx/Info.pngbin0 -> 614 bytes
-rw-r--r--browser/themes/osx/Privacy-16.pngbin0 -> 852 bytes
-rw-r--r--browser/themes/osx/Toolbar-background-noise.pngbin0 -> 15667 bytes
-rw-r--r--browser/themes/osx/Toolbar-inverted.pngbin0 -> 31859 bytes
-rw-r--r--browser/themes/osx/Toolbar-inverted@2x.pngbin0 -> 77408 bytes
-rw-r--r--browser/themes/osx/Toolbar-yosemite.pngbin0 -> 19487 bytes
-rw-r--r--browser/themes/osx/Toolbar-yosemite@2x.pngbin0 -> 46416 bytes
-rw-r--r--browser/themes/osx/Toolbar.pngbin0 -> 30926 bytes
-rw-r--r--browser/themes/osx/Toolbar@2x.pngbin0 -> 84821 bytes
-rw-r--r--browser/themes/osx/aboutSessionRestore-window-icon.pngbin0 -> 412 bytes
-rw-r--r--browser/themes/osx/aboutSyncTabs.css101
-rw-r--r--browser/themes/osx/actionicon-tab.pngbin0 -> 645 bytes
-rw-r--r--browser/themes/osx/actionicon-tab@2x.pngbin0 -> 1371 bytes
-rw-r--r--browser/themes/osx/browser-lightweightTheme.css38
-rw-r--r--browser/themes/osx/browser.css3406
-rw-r--r--browser/themes/osx/click-to-play-warning-stripes.pngbin0 -> 1563 bytes
-rw-r--r--browser/themes/osx/communicator/communicator.css6
-rw-r--r--browser/themes/osx/communicator/jar.mn7
-rw-r--r--browser/themes/osx/communicator/moz.build7
-rw-r--r--browser/themes/osx/controlcenter/panel.css54
-rw-r--r--browser/themes/osx/customizableui/background-noise-toolbar.pngbin0 -> 15601 bytes
-rw-r--r--browser/themes/osx/customizableui/customize-titleBar-toggle.pngbin0 -> 317 bytes
-rw-r--r--browser/themes/osx/customizableui/customize-titleBar-toggle@2x.pngbin0 -> 538 bytes
-rw-r--r--browser/themes/osx/customizableui/customizeMode-gridTexture.pngbin0 -> 118 bytes
-rw-r--r--browser/themes/osx/customizableui/customizeMode-separatorHorizontal.pngbin0 -> 1426 bytes
-rw-r--r--browser/themes/osx/customizableui/customizeMode-separatorVertical.pngbin0 -> 1649 bytes
-rw-r--r--browser/themes/osx/customizableui/panelUI.css89
-rw-r--r--browser/themes/osx/devedition.css121
-rw-r--r--browser/themes/osx/downloads/allDownloadsViewOverlay.css19
-rw-r--r--browser/themes/osx/downloads/download-glow-menuPanel.pngbin0 -> 2067 bytes
-rw-r--r--browser/themes/osx/downloads/download-glow-menuPanel@2x.pngbin0 -> 3082 bytes
-rw-r--r--browser/themes/osx/downloads/download-notification-finish.pngbin0 -> 4066 bytes
-rw-r--r--browser/themes/osx/downloads/download-notification-finish@2x.pngbin0 -> 9224 bytes
-rw-r--r--browser/themes/osx/downloads/download-notification-start.pngbin0 -> 3166 bytes
-rw-r--r--browser/themes/osx/downloads/download-notification-start@2x.pngbin0 -> 19510 bytes
-rw-r--r--browser/themes/osx/downloads/downloads.css49
-rw-r--r--browser/themes/osx/downloads/indicator.css250
-rw-r--r--browser/themes/osx/feeds/feedIcon.pngbin0 -> 1816 bytes
-rw-r--r--browser/themes/osx/feeds/feedIcon16.pngbin0 -> 852 bytes
-rw-r--r--browser/themes/osx/feeds/subscribe-ui.css8
-rw-r--r--browser/themes/osx/feeds/subscribe.css178
-rw-r--r--browser/themes/osx/jar.mn235
-rw-r--r--browser/themes/osx/keyhole-circle.pngbin0 -> 2210 bytes
-rw-r--r--browser/themes/osx/keyhole-circle@2x.pngbin0 -> 6035 bytes
-rw-r--r--browser/themes/osx/menu-back.pngbin0 -> 231 bytes
-rw-r--r--browser/themes/osx/menu-forward.pngbin0 -> 213 bytes
-rw-r--r--browser/themes/osx/menuPanel-customize-yosemite.pngbin0 -> 219 bytes
-rw-r--r--browser/themes/osx/menuPanel-customize-yosemite@2x.pngbin0 -> 364 bytes
-rw-r--r--browser/themes/osx/menuPanel-customize.pngbin0 -> 874 bytes
-rw-r--r--browser/themes/osx/menuPanel-customize@2x.pngbin0 -> 2197 bytes
-rw-r--r--browser/themes/osx/menuPanel-exit-yosemite.pngbin0 -> 515 bytes
-rw-r--r--browser/themes/osx/menuPanel-exit-yosemite@2x.pngbin0 -> 939 bytes
-rw-r--r--browser/themes/osx/menuPanel-exit.pngbin0 -> 742 bytes
-rw-r--r--browser/themes/osx/menuPanel-exit@2x.pngbin0 -> 1636 bytes
-rw-r--r--browser/themes/osx/menuPanel-help-yosemite.pngbin0 -> 1923 bytes
-rw-r--r--browser/themes/osx/menuPanel-help-yosemite@2x.pngbin0 -> 4506 bytes
-rw-r--r--browser/themes/osx/menuPanel-help.pngbin0 -> 2092 bytes
-rw-r--r--browser/themes/osx/menuPanel-help@2x.pngbin0 -> 5316 bytes
-rw-r--r--browser/themes/osx/monitor.pngbin0 -> 7821 bytes
-rw-r--r--browser/themes/osx/monitor_16-10.pngbin0 -> 8998 bytes
-rw-r--r--browser/themes/osx/moz.build13
-rw-r--r--browser/themes/osx/newtab/newTab.css14
-rw-r--r--browser/themes/osx/page-livemarks.pngbin0 -> 683 bytes
-rw-r--r--browser/themes/osx/page-livemarks@2x.pngbin0 -> 1167 bytes
-rw-r--r--browser/themes/osx/pageInfo.css194
-rw-r--r--browser/themes/osx/panel-expander-closed.pngbin0 -> 155 bytes
-rw-r--r--browser/themes/osx/panel-expander-closed@2x.pngbin0 -> 362 bytes
-rw-r--r--browser/themes/osx/panel-expander-open.pngbin0 -> 155 bytes
-rw-r--r--browser/themes/osx/panel-expander-open@2x.pngbin0 -> 356 bytes
-rw-r--r--browser/themes/osx/panel-plus-sign.pngbin0 -> 212 bytes
-rw-r--r--browser/themes/osx/places/allBookmarks.pngbin0 -> 673 bytes
-rw-r--r--browser/themes/osx/places/autocomplete-star.pngbin0 -> 653 bytes
-rw-r--r--browser/themes/osx/places/autocomplete-star@2x.pngbin0 -> 1200 bytes
-rw-r--r--browser/themes/osx/places/bookmarks-notification-finish.pngbin0 -> 3440 bytes
-rw-r--r--browser/themes/osx/places/bookmarks-notification-finish@2x.pngbin0 -> 8614 bytes
-rw-r--r--browser/themes/osx/places/bookmarksMenu.pngbin0 -> 353 bytes
-rw-r--r--browser/themes/osx/places/bookmarksToolbar-menuPanel.pngbin0 -> 787 bytes
-rw-r--r--browser/themes/osx/places/bookmarksToolbar-menuPanel@2x.pngbin0 -> 1728 bytes
-rw-r--r--browser/themes/osx/places/bookmarksToolbar.pngbin0 -> 524 bytes
-rw-r--r--browser/themes/osx/places/bookmarksToolbar@2x.pngbin0 -> 1179 bytes
-rw-r--r--browser/themes/osx/places/downloads.pngbin0 -> 678 bytes
-rw-r--r--browser/themes/osx/places/editBookmarkOverlay.css94
-rw-r--r--browser/themes/osx/places/folderDropArrow.pngbin0 -> 201 bytes
-rw-r--r--browser/themes/osx/places/folderDropArrow@2x.pngbin0 -> 443 bytes
-rw-r--r--browser/themes/osx/places/history.pngbin0 -> 843 bytes
-rw-r--r--browser/themes/osx/places/history@2x.pngbin0 -> 1872 bytes
-rw-r--r--browser/themes/osx/places/livemark-item.pngbin0 -> 863 bytes
-rw-r--r--browser/themes/osx/places/minus-active.pngbin0 -> 573 bytes
-rw-r--r--browser/themes/osx/places/minus.pngbin0 -> 599 bytes
-rw-r--r--browser/themes/osx/places/organizer.css319
-rw-r--r--browser/themes/osx/places/places.css280
-rw-r--r--browser/themes/osx/places/plus-active.pngbin0 -> 585 bytes
-rw-r--r--browser/themes/osx/places/plus.pngbin0 -> 600 bytes
-rw-r--r--browser/themes/osx/places/query.pngbin0 -> 549 bytes
-rw-r--r--browser/themes/osx/places/query@2x.pngbin0 -> 1055 bytes
-rw-r--r--browser/themes/osx/places/starred48.pngbin0 -> 1877 bytes
-rw-r--r--browser/themes/osx/places/starred48@2x.pngbin0 -> 4918 bytes
-rw-r--r--browser/themes/osx/places/tag.pngbin0 -> 789 bytes
-rw-r--r--browser/themes/osx/places/tag@2x.pngbin0 -> 1593 bytes
-rw-r--r--browser/themes/osx/places/toolbar-lion.pngbin0 -> 1318 bytes
-rw-r--r--browser/themes/osx/places/toolbar.pngbin0 -> 2394 bytes
-rw-r--r--browser/themes/osx/places/toolbarDropMarker.pngbin0 -> 302 bytes
-rw-r--r--browser/themes/osx/places/unfiledBookmarks.pngbin0 -> 586 bytes
-rw-r--r--browser/themes/osx/places/unfiledBookmarks@2x.pngbin0 -> 1289 bytes
-rw-r--r--browser/themes/osx/places/unstarred48.pngbin0 -> 818 bytes
-rw-r--r--browser/themes/osx/preferences/alwaysAsk.pngbin0 -> 530 bytes
-rw-r--r--browser/themes/osx/preferences/application.pngbin0 -> 795 bytes
-rw-r--r--browser/themes/osx/preferences/applications.css73
-rw-r--r--browser/themes/osx/preferences/in-content/dialog.css36
-rw-r--r--browser/themes/osx/preferences/in-content/preferences.css53
-rw-r--r--browser/themes/osx/preferences/preferences.css131
-rw-r--r--browser/themes/osx/preferences/saveFile.pngbin0 -> 570 bytes
-rw-r--r--browser/themes/osx/privatebrowsing-mask-short.pngbin0 -> 1074 bytes
-rw-r--r--browser/themes/osx/privatebrowsing-mask-short@2x.pngbin0 -> 2639 bytes
-rw-r--r--browser/themes/osx/privatebrowsing-mask.pngbin0 -> 918 bytes
-rw-r--r--browser/themes/osx/privatebrowsing-mask@2x.pngbin0 -> 2199 bytes
-rw-r--r--browser/themes/osx/reload-stop-go-yosemite.pngbin0 -> 923 bytes
-rw-r--r--browser/themes/osx/reload-stop-go-yosemite@2x.pngbin0 -> 1619 bytes
-rw-r--r--browser/themes/osx/reload-stop-go.pngbin0 -> 912 bytes
-rw-r--r--browser/themes/osx/reload-stop-go@2x.pngbin0 -> 1850 bytes
-rw-r--r--browser/themes/osx/sanitizeDialog.css91
-rw-r--r--browser/themes/osx/searchbar.css318
-rw-r--r--browser/themes/osx/setDesktopBackground.css18
-rw-r--r--browser/themes/osx/shared.inc13
-rw-r--r--browser/themes/osx/slowStartup-16.pngbin0 -> 499 bytes
-rw-r--r--browser/themes/osx/social/services-16.pngbin0 -> 937 bytes
-rw-r--r--browser/themes/osx/social/services-16@2x.pngbin0 -> 2213 bytes
-rw-r--r--browser/themes/osx/social/services-64.pngbin0 -> 5613 bytes
-rw-r--r--browser/themes/osx/social/services-64@2x.pngbin0 -> 14920 bytes
-rw-r--r--browser/themes/osx/subtle-pattern.pngbin0 -> 14762 bytes
-rw-r--r--browser/themes/osx/sync-128.pngbin0 -> 20229 bytes
-rw-r--r--browser/themes/osx/sync-16.pngbin0 -> 1847 bytes
-rw-r--r--browser/themes/osx/sync-32.pngbin0 -> 3384 bytes
-rw-r--r--browser/themes/osx/sync-bg.pngbin0 -> 21309 bytes
-rw-r--r--browser/themes/osx/sync-horizontalbar-yosemite.pngbin0 -> 311 bytes
-rw-r--r--browser/themes/osx/sync-horizontalbar-yosemite@2x.pngbin0 -> 609 bytes
-rw-r--r--browser/themes/osx/sync-horizontalbar.pngbin0 -> 707 bytes
-rw-r--r--browser/themes/osx/sync-horizontalbar@2x.pngbin0 -> 1145 bytes
-rw-r--r--browser/themes/osx/sync-notification-24.pngbin0 -> 776 bytes
-rw-r--r--browser/themes/osx/syncCommon.css55
-rw-r--r--browser/themes/osx/syncProgress-horizontalbar.pngbin0 -> 11892 bytes
-rw-r--r--browser/themes/osx/syncProgress-horizontalbar@2x.pngbin0 -> 27608 bytes
-rw-r--r--browser/themes/osx/syncProgress-menuPanel.pngbin0 -> 25426 bytes
-rw-r--r--browser/themes/osx/syncProgress-menuPanel@2x.pngbin0 -> 55953 bytes
-rw-r--r--browser/themes/osx/syncProgress-toolbar-inverted.pngbin0 -> 14230 bytes
-rw-r--r--browser/themes/osx/syncProgress-toolbar-inverted@2x.pngbin0 -> 32029 bytes
-rw-r--r--browser/themes/osx/syncProgress-toolbar.pngbin0 -> 13293 bytes
-rw-r--r--browser/themes/osx/syncProgress-toolbar@2x.pngbin0 -> 33662 bytes
-rw-r--r--browser/themes/osx/syncQuota.css26
-rw-r--r--browser/themes/osx/syncSetup.css139
-rw-r--r--browser/themes/osx/syncedtabs/sidebar.css154
-rw-r--r--browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted.pngbin0 -> 421 bytes
-rw-r--r--browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.pngbin0 -> 820 bytes
-rw-r--r--browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion.pngbin0 -> 538 bytes
-rw-r--r--browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion@2x.pngbin0 -> 1350 bytes
-rw-r--r--browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon.pngbin0 -> 639 bytes
-rw-r--r--browser/themes/osx/tabbrowser/newtab-inverted.pngbin0 -> 470 bytes
-rw-r--r--browser/themes/osx/tabbrowser/newtab-inverted@2x.pngbin0 -> 866 bytes
-rw-r--r--browser/themes/osx/tabbrowser/newtab.pngbin0 -> 568 bytes
-rw-r--r--browser/themes/osx/tabbrowser/newtab@2x.pngbin0 -> 1742 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive.pngbin0 -> 78 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive@2x.pngbin0 -> 89 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-active-middle.pngbin0 -> 388 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-active-middle@2x.pngbin0 -> 650 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-left-inverted.pngbin0 -> 918 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-left-inverted@2x.pngbin0 -> 1999 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-left.pngbin0 -> 947 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-left@2x.pngbin0 -> 2768 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-right-inverted.pngbin0 -> 962 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-right-inverted@2x.pngbin0 -> 1944 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-right.pngbin0 -> 996 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-arrow-right@2x.pngbin0 -> 2732 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-background-end.pngbin0 -> 1047 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-background-end@2x.pngbin0 -> 2819 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-background-middle.pngbin0 -> 308 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-background-middle@2x.pngbin0 -> 727 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-background-start.pngbin0 -> 1019 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-background-start@2x.pngbin0 -> 2820 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-selected-end-yosemite-inactive.svg24
-rw-r--r--browser/themes/osx/tabbrowser/tab-selected-start-yosemite-inactive.svg24
-rw-r--r--browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive.pngbin0 -> 339 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive@2x.pngbin0 -> 718 bytes
-rwxr-xr-xbrowser/themes/osx/tabbrowser/tab-stroke-end.pngbin0 -> 1215 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-stroke-end@2x.pngbin0 -> 4991 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive.pngbin0 -> 339 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive@2x.pngbin0 -> 716 bytes
-rwxr-xr-xbrowser/themes/osx/tabbrowser/tab-stroke-start.pngbin0 -> 1219 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tab-stroke-start@2x.pngbin0 -> 4792 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tabDragIndicator.pngbin0 -> 599 bytes
-rw-r--r--browser/themes/osx/tabbrowser/tabDragIndicator@2x.pngbin0 -> 1124 bytes
-rw-r--r--browser/themes/osx/toolbarbutton-dropmarker-lion.pngbin0 -> 150 bytes
-rw-r--r--browser/themes/osx/toolbarbutton-dropmarker-lion@2x.pngbin0 -> 286 bytes
-rw-r--r--browser/themes/osx/toolbarbutton-dropmarker.pngbin0 -> 142 bytes
-rw-r--r--browser/themes/osx/urlbar-history-dropmarker.pngbin0 -> 389 bytes
-rw-r--r--browser/themes/osx/urlbar-history-dropmarker@2x.pngbin0 -> 807 bytes
-rw-r--r--browser/themes/osx/urlbar-popup-blocked.pngbin0 -> 758 bytes
-rw-r--r--browser/themes/osx/urlbar-popup-blocked@2x.pngbin0 -> 769 bytes
-rw-r--r--browser/themes/osx/webRTC-indicator.css35
-rw-r--r--browser/themes/osx/webRTC-sharingDevice-menubar.pngbin0 -> 1383 bytes
-rw-r--r--browser/themes/osx/webRTC-sharingDevice-menubar@2x.pngbin0 -> 1671 bytes
-rw-r--r--browser/themes/osx/webRTC-sharingMicrophone-menubar.pngbin0 -> 3998 bytes
-rw-r--r--browser/themes/osx/webRTC-sharingMicrophone-menubar@2x.pngbin0 -> 4406 bytes
-rw-r--r--browser/themes/osx/webRTC-sharingScreen-menubar.pngbin0 -> 3962 bytes
-rw-r--r--browser/themes/osx/webRTC-sharingScreen-menubar@2x.pngbin0 -> 1770 bytes
-rwxr-xr-xbrowser/themes/preprocess-tab-svgs.py31
-rw-r--r--browser/themes/shared/UITour.inc.css293
-rw-r--r--browser/themes/shared/aboutNetError.css169
-rw-r--r--browser/themes/shared/aboutProviderDirectory.css30
-rw-r--r--browser/themes/shared/aboutSessionRestore.css38
-rw-r--r--browser/themes/shared/aboutSocialError.css40
-rw-r--r--browser/themes/shared/aboutTabCrashed.css101
-rw-r--r--browser/themes/shared/aboutWelcomeBack.css47
-rw-r--r--browser/themes/shared/addons/addon-install-anchor.svg13
-rw-r--r--browser/themes/shared/addons/addon-install-blocked.svg38
-rw-r--r--browser/themes/shared/addons/addon-install-confirm.svg19
-rw-r--r--browser/themes/shared/addons/addon-install-downloading.svg38
-rw-r--r--browser/themes/shared/addons/addon-install-error.svg38
-rw-r--r--browser/themes/shared/addons/addon-install-installed.svg38
-rw-r--r--browser/themes/shared/addons/addon-install-restart.svg46
-rw-r--r--browser/themes/shared/addons/addon-install-warning.svg38
-rw-r--r--browser/themes/shared/autocomplete.inc.css65
-rw-r--r--browser/themes/shared/blockedSite.css65
-rw-r--r--browser/themes/shared/browser.inc13
-rw-r--r--browser/themes/shared/content-contextmenu.svg18
-rw-r--r--browser/themes/shared/contextmenu.inc.css51
-rw-r--r--browser/themes/shared/controlcenter/arrow-subview-back.svg8
-rw-r--r--browser/themes/shared/controlcenter/arrow-subview.svg8
-rw-r--r--browser/themes/shared/controlcenter/conn-not-secure.svg15
-rw-r--r--browser/themes/shared/controlcenter/connection.svg37
-rw-r--r--browser/themes/shared/controlcenter/mcb-disabled.svg34
-rw-r--r--browser/themes/shared/controlcenter/panel.inc.css445
-rw-r--r--browser/themes/shared/controlcenter/permissions.svg20
-rw-r--r--browser/themes/shared/controlcenter/tracking-protection.svg43
-rw-r--r--browser/themes/shared/controlcenter/warning-gray.svg9
-rw-r--r--browser/themes/shared/controlcenter/warning-yellow.svg9
-rw-r--r--browser/themes/shared/ctrlTab.inc.css63
-rw-r--r--browser/themes/shared/customizableui/customize-illustration-rtl.pngbin0 -> 7592 bytes
-rw-r--r--browser/themes/shared/customizableui/customize-illustration-rtl@2x.pngbin0 -> 16521 bytes
-rw-r--r--browser/themes/shared/customizableui/customize-illustration.pngbin0 -> 7609 bytes
-rw-r--r--browser/themes/shared/customizableui/customize-illustration@2x.pngbin0 -> 16465 bytes
-rw-r--r--browser/themes/shared/customizableui/customizeFavicon.icobin0 -> 1084 bytes
-rw-r--r--browser/themes/shared/customizableui/customizeMode.inc.css461
-rw-r--r--browser/themes/shared/customizableui/customizeTip.inc.css77
-rw-r--r--browser/themes/shared/customizableui/info-icon-customizeTip.pngbin0 -> 286 bytes
-rw-r--r--browser/themes/shared/customizableui/info-icon-customizeTip@2x.pngbin0 -> 501 bytes
-rw-r--r--browser/themes/shared/customizableui/menuPanel-customizeFinish.pngbin0 -> 337 bytes
-rw-r--r--browser/themes/shared/customizableui/menuPanel-customizeFinish@2x.pngbin0 -> 625 bytes
-rw-r--r--browser/themes/shared/customizableui/panelUI.inc.css1769
-rw-r--r--browser/themes/shared/customizableui/panelarrow-customizeTip.pngbin0 -> 243 bytes
-rw-r--r--browser/themes/shared/customizableui/panelarrow-customizeTip@2x.pngbin0 -> 260 bytes
-rw-r--r--browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl.pngbin0 -> 190 bytes
-rw-r--r--browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl@2x.pngbin0 -> 465 bytes
-rw-r--r--browser/themes/shared/customizableui/subView-arrow-back-inverted.pngbin0 -> 307 bytes
-rw-r--r--browser/themes/shared/customizableui/subView-arrow-back-inverted@2x.pngbin0 -> 667 bytes
-rw-r--r--browser/themes/shared/customizableui/whimsy.pngbin0 -> 6639 bytes
-rw-r--r--browser/themes/shared/customizableui/whimsy@2x.pngbin0 -> 18370 bytes
-rw-r--r--browser/themes/shared/devedition.inc.css311
-rw-r--r--browser/themes/shared/devedition/urlbar-history-dropmarker.svg22
-rw-r--r--browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css131
-rw-r--r--browser/themes/shared/downloads/contentAreaDownloadsView.css31
-rw-r--r--browser/themes/shared/downloads/download-blocked.svg17
-rw-r--r--browser/themes/shared/downloads/download-summary.svg11
-rw-r--r--browser/themes/shared/downloads/downloads.inc.css372
-rw-r--r--browser/themes/shared/downloads/menubutton-dropmarker.svg8
-rw-r--r--browser/themes/shared/downloads/progressmeter.inc.css70
-rw-r--r--browser/themes/shared/drm-icon.svg38
-rw-r--r--browser/themes/shared/e10s-64@2x.pngbin0 -> 20737 bytes
-rw-r--r--browser/themes/shared/error-pages.css81
-rw-r--r--browser/themes/shared/favicon-search-16.svg10
-rw-r--r--browser/themes/shared/filters.svg9
-rw-r--r--browser/themes/shared/fullscreen/insecure.svg39
-rw-r--r--browser/themes/shared/fullscreen/secure.svg26
-rw-r--r--browser/themes/shared/fullscreen/warning.inc.css51
-rw-r--r--browser/themes/shared/fxa/android.pngbin0 -> 468 bytes
-rw-r--r--browser/themes/shared/fxa/android@2x.pngbin0 -> 719 bytes
-rw-r--r--browser/themes/shared/fxa/default-avatar.svg8
-rw-r--r--browser/themes/shared/fxa/ios.pngbin0 -> 711 bytes
-rw-r--r--browser/themes/shared/fxa/ios@2x.pngbin0 -> 1156 bytes
-rw-r--r--browser/themes/shared/fxa/logo.pngbin0 -> 1728 bytes
-rw-r--r--browser/themes/shared/fxa/logo@2x.pngbin0 -> 3190 bytes
-rw-r--r--browser/themes/shared/fxa/sync-illustration.pngbin0 -> 2420 bytes
-rw-r--r--browser/themes/shared/fxa/sync-illustration.svg16
-rw-r--r--browser/themes/shared/fxa/sync-illustration@2x.pngbin0 -> 6016 bytes
-rw-r--r--browser/themes/shared/heartbeat-icon.svg13
-rw-r--r--browser/themes/shared/heartbeat-star-lit.svg7
-rw-r--r--browser/themes/shared/heartbeat-star-off.svg7
-rw-r--r--browser/themes/shared/icon-colors.inc.svg39
-rw-r--r--browser/themes/shared/icon.pngbin0 -> 2084 bytes
-rw-r--r--browser/themes/shared/identity-block/connection-mixed-active-loaded.svg54
-rw-r--r--browser/themes/shared/identity-block/connection-mixed-passive-loaded.svg52
-rw-r--r--browser/themes/shared/identity-block/connection-secure.svg27
-rw-r--r--browser/themes/shared/identity-block/icons.inc.css62
-rw-r--r--browser/themes/shared/identity-block/identity-block.inc.css163
-rwxr-xr-xbrowser/themes/shared/identity-block/identity-icon.svg39
-rwxr-xr-xbrowser/themes/shared/identity-block/tracking-protection-16.svg59
-rw-r--r--browser/themes/shared/incontent-icons/cert-error.svg42
-rw-r--r--browser/themes/shared/incontent-icons/icon-search-64.svg12
-rw-r--r--browser/themes/shared/incontent-icons/session-restore.svg13
-rw-r--r--browser/themes/shared/incontent-icons/tab-crashed.svg13
-rw-r--r--browser/themes/shared/incontent-icons/welcome-back.svg13
-rw-r--r--browser/themes/shared/incontent-icons/wifi.svg30
-rw-r--r--browser/themes/shared/incontentprefs/containers.css32
-rw-r--r--browser/themes/shared/incontentprefs/dialog.inc.css68
-rw-r--r--browser/themes/shared/incontentprefs/favicon.icobin0 -> 933 bytes
-rw-r--r--browser/themes/shared/incontentprefs/icons.svg63
-rw-r--r--browser/themes/shared/incontentprefs/preferences.inc.css591
-rw-r--r--browser/themes/shared/incontentprefs/search.css49
-rw-r--r--browser/themes/shared/info.svg9
-rw-r--r--browser/themes/shared/jar.inc.mn154
-rw-r--r--browser/themes/shared/menuPanel-small.svg16
-rw-r--r--browser/themes/shared/menuPanel.svg42
-rw-r--r--browser/themes/shared/menupanel.inc.css183
-rw-r--r--browser/themes/shared/newtab/close.pngbin0 -> 931 bytes
-rw-r--r--browser/themes/shared/newtab/controls.svg85
-rw-r--r--browser/themes/shared/newtab/newTab.inc.css344
-rw-r--r--browser/themes/shared/newtab/whimsycorn.pngbin0 -> 3875 bytes
-rw-r--r--browser/themes/shared/notification-icons.inc.css318
-rw-r--r--browser/themes/shared/notification-icons.svg104
-rw-r--r--browser/themes/shared/panel-icons.svg18
-rw-r--r--browser/themes/shared/panic-panel/header-small.pngbin0 -> 1333 bytes
-rw-r--r--browser/themes/shared/panic-panel/header-small@2x.pngbin0 -> 2966 bytes
-rw-r--r--browser/themes/shared/panic-panel/header.pngbin0 -> 1952 bytes
-rw-r--r--browser/themes/shared/panic-panel/header@2x.pngbin0 -> 4369 bytes
-rw-r--r--browser/themes/shared/panic-panel/icons.pngbin0 -> 679 bytes
-rw-r--r--browser/themes/shared/panic-panel/icons@2x.pngbin0 -> 1388 bytes
-rw-r--r--browser/themes/shared/plugin-doorhanger.inc.css65
-rw-r--r--browser/themes/shared/preferences/containers.css53
-rw-r--r--browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css227
-rwxr-xr-xbrowser/themes/shared/privatebrowsing/attention.pngbin0 -> 602 bytes
-rwxr-xr-xbrowser/themes/shared/privatebrowsing/attention@2x.pngbin0 -> 902 bytes
-rwxr-xr-xbrowser/themes/shared/privatebrowsing/check.pngbin0 -> 338 bytes
-rw-r--r--browser/themes/shared/privatebrowsing/check.svg8
-rwxr-xr-xbrowser/themes/shared/privatebrowsing/check@2x.pngbin0 -> 370 bytes
-rw-r--r--browser/themes/shared/privatebrowsing/favicon.svg11
-rw-r--r--browser/themes/shared/privatebrowsing/private-browsing.svg12
-rwxr-xr-xbrowser/themes/shared/privatebrowsing/shield-page.pngbin0 -> 4257 bytes
-rwxr-xr-xbrowser/themes/shared/privatebrowsing/shield-page@2x.pngbin0 -> 9367 bytes
-rw-r--r--browser/themes/shared/privatebrowsing/tracking-protection-off.svg15
-rw-r--r--browser/themes/shared/privatebrowsing/tracking-protection.svg12
-rw-r--r--browser/themes/shared/reader/reader-mode-16.pngbin0 -> 502 bytes
-rw-r--r--browser/themes/shared/reader/reader-mode-16@2x.pngbin0 -> 1063 bytes
-rw-r--r--browser/themes/shared/reader/reader-tour.pngbin0 -> 2672 bytes
-rw-r--r--browser/themes/shared/reader/reader-tour@2x.pngbin0 -> 6426 bytes
-rw-r--r--browser/themes/shared/reader/readerMode.svg29
-rw-r--r--browser/themes/shared/search/badge-add-engine.pngbin0 -> 425 bytes
-rw-r--r--browser/themes/shared/search/badge-add-engine@2x.pngbin0 -> 888 bytes
-rwxr-xr-xbrowser/themes/shared/search/gear.svg7
-rw-r--r--browser/themes/shared/search/history-icon.svg22
-rw-r--r--browser/themes/shared/search/search-arrow-go.svg24
-rw-r--r--browser/themes/shared/search/search-engine-placeholder.pngbin0 -> 252 bytes
-rw-r--r--browser/themes/shared/search/search-engine-placeholder@2x.pngbin0 -> 461 bytes
-rw-r--r--browser/themes/shared/search/search-indicator-badge-add.pngbin0 -> 1000 bytes
-rw-r--r--browser/themes/shared/search/search-indicator-badge-add@2x.pngbin0 -> 2263 bytes
-rw-r--r--browser/themes/shared/search/search-indicator-magnifying-glass.svg7
-rw-r--r--browser/themes/shared/search/search-indicator.pngbin0 -> 344 bytes
-rw-r--r--browser/themes/shared/search/search-indicator@2x.pngbin0 -> 694 bytes
-rw-r--r--browser/themes/shared/searchReset.css22
-rw-r--r--browser/themes/shared/social/gear_clicked.pngbin0 -> 1262 bytes
-rw-r--r--browser/themes/shared/social/gear_default.pngbin0 -> 1271 bytes
-rw-r--r--browser/themes/shared/social/social.inc.css23
-rw-r--r--browser/themes/shared/sync-desktopIcon.svg22
-rw-r--r--browser/themes/shared/sync-mobileIcon.svg22
-rw-r--r--browser/themes/shared/syncedtabs/sidebar.inc.css234
-rw-r--r--browser/themes/shared/tab-selected.svg36
-rw-r--r--browser/themes/shared/tabbrowser/connecting.pngbin0 -> 8540 bytes
-rw-r--r--browser/themes/shared/tabbrowser/connecting@2x.pngbin0 -> 30143 bytes
-rw-r--r--browser/themes/shared/tabbrowser/crashed.svg14
-rw-r--r--browser/themes/shared/tabbrowser/pendingpaint.pngbin0 -> 10133 bytes
-rw-r--r--browser/themes/shared/tabbrowser/tab-audio-small.svg58
-rw-r--r--browser/themes/shared/tabbrowser/tab-audio.svg18
-rw-r--r--browser/themes/shared/tabbrowser/tab-overflow-indicator.pngbin0 -> 578 bytes
-rw-r--r--browser/themes/shared/tabs.inc.css566
-rw-r--r--browser/themes/shared/theme-switcher-icon.pngbin0 -> 2084 bytes
-rw-r--r--browser/themes/shared/theme-switcher-icon@2x.pngbin0 -> 5595 bytes
-rw-r--r--browser/themes/shared/toolbarbutton-dropdown-arrow.pngbin0 -> 91 bytes
-rw-r--r--browser/themes/shared/toolbarbuttons.inc.css347
-rw-r--r--browser/themes/shared/translation/infobar.inc.css95
-rw-r--r--browser/themes/shared/translation/translating-16.pngbin0 -> 21270 bytes
-rw-r--r--browser/themes/shared/translation/translating-16@2x.pngbin0 -> 29889 bytes
-rw-r--r--browser/themes/shared/translation/translation-16.pngbin0 -> 889 bytes
-rw-r--r--browser/themes/shared/translation/translation-16@2x.pngbin0 -> 2076 bytes
-rw-r--r--browser/themes/shared/undoCloseTab.pngbin0 -> 1606 bytes
-rw-r--r--browser/themes/shared/undoCloseTab@2x.pngbin0 -> 2413 bytes
-rw-r--r--browser/themes/shared/update-badge-failed.svg6
-rw-r--r--browser/themes/shared/update-badge.svg6
-rw-r--r--browser/themes/shared/urlbar-star.svg20
-rw-r--r--browser/themes/shared/urlbar-tab.svg21
-rw-r--r--browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css54
-rw-r--r--browser/themes/shared/warning-white.svg6
-rw-r--r--browser/themes/shared/warning.svg7
-rw-r--r--browser/themes/shared/webrtc/camera-white-16.pngbin0 -> 3781 bytes
-rw-r--r--browser/themes/shared/webrtc/camera-white-16@2x.pngbin0 -> 3887 bytes
-rw-r--r--browser/themes/shared/webrtc/microphone-white-16.pngbin0 -> 3794 bytes
-rw-r--r--browser/themes/shared/webrtc/microphone-white-16@2x.pngbin0 -> 3978 bytes
-rw-r--r--browser/themes/shared/webrtc/screen-white-16.pngbin0 -> 3769 bytes
-rw-r--r--browser/themes/shared/webrtc/screen-white-16@2x.pngbin0 -> 3864 bytes
-rw-r--r--browser/themes/tab-svgs.mozbuild22
-rw-r--r--browser/themes/windows/Info-XP.pngbin0 -> 590 bytes
-rw-r--r--browser/themes/windows/Info.pngbin0 -> 577 bytes
-rw-r--r--browser/themes/windows/Privacy-16-XP.pngbin0 -> 799 bytes
-rw-r--r--browser/themes/windows/Privacy-16.pngbin0 -> 789 bytes
-rw-r--r--browser/themes/windows/Toolbar-XP.pngbin0 -> 19638 bytes
-rw-r--r--browser/themes/windows/Toolbar-aero.pngbin0 -> 18276 bytes
-rw-r--r--browser/themes/windows/Toolbar-aero@2x.pngbin0 -> 47581 bytes
-rw-r--r--browser/themes/windows/Toolbar-inverted.pngbin0 -> 12933 bytes
-rw-r--r--browser/themes/windows/Toolbar-inverted@2x.pngbin0 -> 29813 bytes
-rw-r--r--browser/themes/windows/Toolbar-lunaSilver.pngbin0 -> 19034 bytes
-rw-r--r--browser/themes/windows/Toolbar-win8.pngbin0 -> 7239 bytes
-rw-r--r--browser/themes/windows/Toolbar-win8@2x.pngbin0 -> 16482 bytes
-rw-r--r--browser/themes/windows/Toolbar.pngbin0 -> 7308 bytes
-rw-r--r--browser/themes/windows/Toolbar@2x.pngbin0 -> 16636 bytes
-rw-r--r--browser/themes/windows/aboutSessionRestore-window-icon.pngbin0 -> 307 bytes
-rw-r--r--browser/themes/windows/aboutSyncTabs.css101
-rw-r--r--browser/themes/windows/actionicon-tab-XPVista7.pngbin0 -> 421 bytes
-rw-r--r--browser/themes/windows/actionicon-tab.pngbin0 -> 194 bytes
-rw-r--r--browser/themes/windows/actionicon-tab@2x.pngbin0 -> 324 bytes
-rw-r--r--browser/themes/windows/browser-aero.css473
-rw-r--r--browser/themes/windows/browser-lightweightTheme.css39
-rw-r--r--browser/themes/windows/browser.css2711
-rw-r--r--browser/themes/windows/caption-buttons.svg100
-rw-r--r--browser/themes/windows/click-to-play-warning-stripes.pngbin0 -> 1266 bytes
-rw-r--r--browser/themes/windows/communicator/communicator.css6
-rw-r--r--browser/themes/windows/communicator/jar.mn7
-rw-r--r--browser/themes/windows/communicator/moz.build7
-rw-r--r--browser/themes/windows/content-contextmenu.svg46
-rw-r--r--browser/themes/windows/controlcenter/panel.css13
-rw-r--r--browser/themes/windows/customizableui/background-noise-toolbar.pngbin0 -> 15601 bytes
-rw-r--r--browser/themes/windows/customizableui/customize-titleBar-toggle.pngbin0 -> 540 bytes
-rw-r--r--browser/themes/windows/customizableui/customize-titleBar-toggle@2x.pngbin0 -> 1273 bytes
-rw-r--r--browser/themes/windows/customizableui/customizeMode-gridTexture.pngbin0 -> 115 bytes
-rw-r--r--browser/themes/windows/customizableui/customizeMode-separatorHorizontal.pngbin0 -> 1426 bytes
-rw-r--r--browser/themes/windows/customizableui/customizeMode-separatorVertical.pngbin0 -> 1649 bytes
-rw-r--r--browser/themes/windows/customizableui/menu-arrow.svg26
-rw-r--r--browser/themes/windows/customizableui/panelUI.css151
-rw-r--r--browser/themes/windows/devedition.css321
-rw-r--r--browser/themes/windows/downloads/allDownloadsViewOverlay.css49
-rw-r--r--browser/themes/windows/downloads/download-glow-XPVista7.pngbin0 -> 494 bytes
-rw-r--r--browser/themes/windows/downloads/download-glow-menuPanel-XPVista7.pngbin0 -> 893 bytes
-rw-r--r--browser/themes/windows/downloads/download-glow-menuPanel.pngbin0 -> 405 bytes
-rw-r--r--browser/themes/windows/downloads/download-glow.pngbin0 -> 175 bytes
-rw-r--r--browser/themes/windows/downloads/download-notification-finish.pngbin0 -> 3880 bytes
-rw-r--r--browser/themes/windows/downloads/download-notification-start.pngbin0 -> 1478 bytes
-rw-r--r--browser/themes/windows/downloads/downloads.css56
-rw-r--r--browser/themes/windows/downloads/indicator.css225
-rw-r--r--browser/themes/windows/feeds/feedIcon-XP.pngbin0 -> 1770 bytes
-rw-r--r--browser/themes/windows/feeds/feedIcon.pngbin0 -> 1838 bytes
-rw-r--r--browser/themes/windows/feeds/feedIcon16-XP.pngbin0 -> 762 bytes
-rw-r--r--browser/themes/windows/feeds/feedIcon16.pngbin0 -> 780 bytes
-rw-r--r--browser/themes/windows/feeds/subscribe-ui.css7
-rw-r--r--browser/themes/windows/feeds/subscribe.css184
-rw-r--r--browser/themes/windows/jar.mn268
-rw-r--r--browser/themes/windows/keyhole-forward-mask.svg14
-rw-r--r--browser/themes/windows/livemark-folder-XP.pngbin0 -> 667 bytes
-rw-r--r--browser/themes/windows/livemark-folder.pngbin0 -> 619 bytes
-rw-r--r--browser/themes/windows/loop/toolbar-win10.pngbin0 -> 768 bytes
-rw-r--r--browser/themes/windows/loop/toolbar-win10@2x.pngbin0 -> 1599 bytes
-rw-r--r--browser/themes/windows/menu-back-XP.pngbin0 -> 341 bytes
-rw-r--r--browser/themes/windows/menu-back.pngbin0 -> 433 bytes
-rw-r--r--browser/themes/windows/menu-forward-XP.pngbin0 -> 342 bytes
-rw-r--r--browser/themes/windows/menu-forward.pngbin0 -> 434 bytes
-rw-r--r--browser/themes/windows/menuPanel-customize.pngbin0 -> 874 bytes
-rw-r--r--browser/themes/windows/menuPanel-customize@2x.pngbin0 -> 2193 bytes
-rw-r--r--browser/themes/windows/menuPanel-exit.pngbin0 -> 568 bytes
-rw-r--r--browser/themes/windows/menuPanel-exit@2x.pngbin0 -> 1139 bytes
-rw-r--r--browser/themes/windows/menuPanel-help.pngbin0 -> 1967 bytes
-rw-r--r--browser/themes/windows/menuPanel-help@2x.pngbin0 -> 5300 bytes
-rw-r--r--browser/themes/windows/monitor.pngbin0 -> 5476 bytes
-rw-r--r--browser/themes/windows/monitor_16-10.pngbin0 -> 6055 bytes
-rw-r--r--browser/themes/windows/moz.build13
-rw-r--r--browser/themes/windows/newtab/newTab.css20
-rw-r--r--browser/themes/windows/pageInfo-XP.pngbin0 -> 7739 bytes
-rw-r--r--browser/themes/windows/pageInfo.css262
-rw-r--r--browser/themes/windows/pageInfo.pngbin0 -> 8062 bytes
-rw-r--r--browser/themes/windows/places/allBookmarks-XP.pngbin0 -> 504 bytes
-rw-r--r--browser/themes/windows/places/allBookmarks.pngbin0 -> 556 bytes
-rw-r--r--browser/themes/windows/places/autocomplete-star-XPVista7.pngbin0 -> 813 bytes
-rw-r--r--browser/themes/windows/places/autocomplete-star.pngbin0 -> 493 bytes
-rw-r--r--browser/themes/windows/places/autocomplete-star@2x.pngbin0 -> 870 bytes
-rw-r--r--browser/themes/windows/places/bookmarks-notification-finish.pngbin0 -> 3417 bytes
-rw-r--r--browser/themes/windows/places/bookmarksMenu-XP.pngbin0 -> 334 bytes
-rw-r--r--browser/themes/windows/places/bookmarksMenu.pngbin0 -> 307 bytes
-rw-r--r--browser/themes/windows/places/bookmarksToolbar-XP.pngbin0 -> 229 bytes
-rw-r--r--browser/themes/windows/places/bookmarksToolbar-menuPanel-XP.pngbin0 -> 689 bytes
-rw-r--r--browser/themes/windows/places/bookmarksToolbar-menuPanel.pngbin0 -> 843 bytes
-rw-r--r--browser/themes/windows/places/bookmarksToolbar.pngbin0 -> 343 bytes
-rw-r--r--browser/themes/windows/places/calendar-XP.pngbin0 -> 559 bytes
-rw-r--r--browser/themes/windows/places/calendar.pngbin0 -> 567 bytes
-rw-r--r--browser/themes/windows/places/downloads.pngbin0 -> 622 bytes
-rw-r--r--browser/themes/windows/places/editBookmarkOverlay.css80
-rw-r--r--browser/themes/windows/places/history-XP.pngbin0 -> 821 bytes
-rw-r--r--browser/themes/windows/places/history.pngbin0 -> 806 bytes
-rw-r--r--browser/themes/windows/places/libraryToolbar-XP.pngbin0 -> 2037 bytes
-rw-r--r--browser/themes/windows/places/libraryToolbar.pngbin0 -> 1194 bytes
-rw-r--r--browser/themes/windows/places/livemark-item.pngbin0 -> 862 bytes
-rw-r--r--browser/themes/windows/places/organizer.css222
-rw-r--r--browser/themes/windows/places/places.css176
-rw-r--r--browser/themes/windows/places/query-XP.pngbin0 -> 612 bytes
-rw-r--r--browser/themes/windows/places/query.pngbin0 -> 601 bytes
-rw-r--r--browser/themes/windows/places/starred48-XP.pngbin0 -> 1848 bytes
-rw-r--r--browser/themes/windows/places/starred48.pngbin0 -> 1849 bytes
-rw-r--r--browser/themes/windows/places/tag-XP.pngbin0 -> 480 bytes
-rw-r--r--browser/themes/windows/places/tag.pngbin0 -> 639 bytes
-rw-r--r--browser/themes/windows/places/toolbarDropMarker-XP.pngbin0 -> 219 bytes
-rw-r--r--browser/themes/windows/places/toolbarDropMarker.pngbin0 -> 186 bytes
-rw-r--r--browser/themes/windows/places/unsortedBookmarks-XP.pngbin0 -> 712 bytes
-rw-r--r--browser/themes/windows/places/unsortedBookmarks.pngbin0 -> 692 bytes
-rw-r--r--browser/themes/windows/places/unstarred48.pngbin0 -> 477 bytes
-rw-r--r--browser/themes/windows/preferences/alwaysAsk-XP.pngbin0 -> 408 bytes
-rw-r--r--browser/themes/windows/preferences/alwaysAsk.pngbin0 -> 392 bytes
-rw-r--r--browser/themes/windows/preferences/application-XP.pngbin0 -> 388 bytes
-rw-r--r--browser/themes/windows/preferences/application.pngbin0 -> 370 bytes
-rw-r--r--browser/themes/windows/preferences/applications.css64
-rw-r--r--browser/themes/windows/preferences/in-content/dialog.css19
-rw-r--r--browser/themes/windows/preferences/in-content/preferences.css64
-rw-r--r--browser/themes/windows/preferences/preferences.css103
-rw-r--r--browser/themes/windows/preferences/saveFile-XP.pngbin0 -> 740 bytes
-rw-r--r--browser/themes/windows/preferences/saveFile.pngbin0 -> 716 bytes
-rw-r--r--browser/themes/windows/privatebrowsing-mask-tabstrip-XPVista7.pngbin0 -> 949 bytes
-rw-r--r--browser/themes/windows/privatebrowsing-mask-tabstrip.pngbin0 -> 403 bytes
-rw-r--r--browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7-tall.pngbin0 -> 940 bytes
-rw-r--r--browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7.pngbin0 -> 860 bytes
-rw-r--r--browser/themes/windows/privatebrowsing-mask-titlebar.pngbin0 -> 370 bytes
-rw-r--r--browser/themes/windows/reload-stop-go-XPVista7.pngbin0 -> 1944 bytes
-rw-r--r--browser/themes/windows/reload-stop-go-XPVista7@2x.pngbin0 -> 3661 bytes
-rw-r--r--browser/themes/windows/reload-stop-go.pngbin0 -> 966 bytes
-rw-r--r--browser/themes/windows/reload-stop-go@2x.pngbin0 -> 1574 bytes
-rw-r--r--browser/themes/windows/sanitizeDialog.css93
-rw-r--r--browser/themes/windows/searchbar.css328
-rw-r--r--browser/themes/windows/setDesktopBackground.css18
-rw-r--r--browser/themes/windows/slowStartup-16.pngbin0 -> 511 bytes
-rw-r--r--browser/themes/windows/social/services-16.pngbin0 -> 876 bytes
-rw-r--r--browser/themes/windows/social/services-64.pngbin0 -> 5612 bytes
-rw-r--r--browser/themes/windows/sync-128.pngbin0 -> 15515 bytes
-rw-r--r--browser/themes/windows/sync-16.pngbin0 -> 923 bytes
-rw-r--r--browser/themes/windows/sync-32.pngbin0 -> 2496 bytes
-rw-r--r--browser/themes/windows/sync-bg.pngbin0 -> 21309 bytes
-rw-r--r--browser/themes/windows/sync-horizontalbar-XPVista7.pngbin0 -> 719 bytes
-rw-r--r--browser/themes/windows/sync-horizontalbar-XPVista7@2x.pngbin0 -> 1702 bytes
-rw-r--r--browser/themes/windows/sync-horizontalbar.pngbin0 -> 535 bytes
-rw-r--r--browser/themes/windows/sync-horizontalbar@2x.pngbin0 -> 1376 bytes
-rw-r--r--browser/themes/windows/sync-notification-24.pngbin0 -> 1117 bytes
-rw-r--r--browser/themes/windows/syncCommon.css55
-rw-r--r--browser/themes/windows/syncProgress-horizontalbar-XPVista7.pngbin0 -> 11892 bytes
-rw-r--r--browser/themes/windows/syncProgress-horizontalbar-XPVista7@2x.pngbin0 -> 27608 bytes
-rw-r--r--browser/themes/windows/syncProgress-horizontalbar.pngbin0 -> 5469 bytes
-rw-r--r--browser/themes/windows/syncProgress-horizontalbar@2x.pngbin0 -> 9668 bytes
-rw-r--r--browser/themes/windows/syncProgress-menuPanel.pngbin0 -> 25426 bytes
-rw-r--r--browser/themes/windows/syncProgress-menuPanel@2x.pngbin0 -> 55953 bytes
-rw-r--r--browser/themes/windows/syncProgress-toolbar-XPVista7.pngbin0 -> 13293 bytes
-rw-r--r--browser/themes/windows/syncProgress-toolbar-XPVista7@2x.pngbin0 -> 33662 bytes
-rw-r--r--browser/themes/windows/syncProgress-toolbar-inverted.pngbin0 -> 14382 bytes
-rw-r--r--browser/themes/windows/syncProgress-toolbar-inverted@2x.pngbin0 -> 32029 bytes
-rw-r--r--browser/themes/windows/syncProgress-toolbar.pngbin0 -> 5535 bytes
-rw-r--r--browser/themes/windows/syncProgress-toolbar@2x.pngbin0 -> 10387 bytes
-rw-r--r--browser/themes/windows/syncQuota.css26
-rw-r--r--browser/themes/windows/syncSetup.css145
-rw-r--r--browser/themes/windows/syncedtabs/sidebar.css132
-rw-r--r--browser/themes/windows/tabbrowser/newtab-XPVista7.svg16
-rw-r--r--browser/themes/windows/tabbrowser/newtab-inverted-XPVista7.svg13
-rw-r--r--browser/themes/windows/tabbrowser/newtab-inverted.svg13
-rw-r--r--browser/themes/windows/tabbrowser/newtab.svg7
-rw-r--r--browser/themes/windows/tabbrowser/tab-active-middle.pngbin0 -> 92 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-active-middle@2x.pngbin0 -> 120 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-arrow-left-XPVista7.svg13
-rw-r--r--browser/themes/windows/tabbrowser/tab-arrow-left-inverted.svg7
-rw-r--r--browser/themes/windows/tabbrowser/tab-arrow-left.svg6
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-end-preWin10.pngbin0 -> 802 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-end-preWin10@2x.pngbin0 -> 2823 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-end.pngbin0 -> 256 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-end@2x.pngbin0 -> 400 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-middle-preWin10.pngbin0 -> 122 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-middle-preWin10@2x.pngbin0 -> 782 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-middle.pngbin0 -> 75 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-middle@2x.pngbin0 -> 86 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-start-preWin10.pngbin0 -> 814 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-start-preWin10@2x.pngbin0 -> 2940 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-start.pngbin0 -> 257 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-background-start@2x.pngbin0 -> 417 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-stroke-end.pngbin0 -> 652 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-stroke-end@2x.pngbin0 -> 1467 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-stroke-start.pngbin0 -> 658 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tab-stroke-start@2x.pngbin0 -> 1477 bytes
-rw-r--r--browser/themes/windows/tabbrowser/tabDragIndicator.pngbin0 -> 404 bytes
-rw-r--r--browser/themes/windows/toolbarbutton-dropdown-arrow-XPVista7.pngbin0 -> 208 bytes
-rw-r--r--browser/themes/windows/toolbarbutton-dropdown-arrow-inverted.pngbin0 -> 144 bytes
-rw-r--r--browser/themes/windows/urlbar-history-dropmarker-XPVista7.pngbin0 -> 479 bytes
-rw-r--r--browser/themes/windows/urlbar-history-dropmarker-XPVista7@2x.pngbin0 -> 788 bytes
-rw-r--r--browser/themes/windows/urlbar-history-dropmarker.pngbin0 -> 293 bytes
-rw-r--r--browser/themes/windows/urlbar-history-dropmarker@2x.pngbin0 -> 375 bytes
-rw-r--r--browser/themes/windows/urlbar-popup-blocked.pngbin0 -> 744 bytes
-rw-r--r--browser/themes/windows/webRTC-indicator.css116
-rw-r--r--browser/themes/windows/windowsShared.inc13
-rw-r--r--browser/tools/mozscreenshots/.eslintrc.js15
-rw-r--r--browser/tools/mozscreenshots/browser.ini12
-rw-r--r--browser/tools/mozscreenshots/browser_screenshots.js16
-rw-r--r--browser/tools/mozscreenshots/controlCenter/browser.ini6
-rw-r--r--browser/tools/mozscreenshots/controlCenter/browser_controlCenter.js16
-rw-r--r--browser/tools/mozscreenshots/devtools/browser.ini6
-rw-r--r--browser/tools/mozscreenshots/devtools/browser_devtools.js16
-rw-r--r--browser/tools/mozscreenshots/head.js53
-rw-r--r--browser/tools/mozscreenshots/moz.build20
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/Makefile.in12
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/Screenshot.jsm179
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm279
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/bootstrap.js65
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/AppMenu.jsm84
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Buttons.jsm85
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm243
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/CustomizeMode.jsm61
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevEdition.jsm42
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm59
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm92
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm130
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm127
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm152
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/TabsInTitlebar.jsm38
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Toolbars.jsm57
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/configurations/WindowSize.jsm68
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/install.rdf33
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/jar.mn6
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/black_theme.pngbin0 -> 977 bytes
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/borderify.xpibin0 -> 1611 bytes
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed.html11
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_active.html10
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_passive.html10
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/password.html13
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/tracking.html10
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-script.js13
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-style.css28
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots.html36
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/permissionPrompts.html30
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/robot.pngbin0 -> 9817 bytes
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/lib/white_theme.pngbin0 -> 977 bytes
-rw-r--r--browser/tools/mozscreenshots/mozscreenshots/extension/moz.build17
-rw-r--r--browser/tools/mozscreenshots/permissionPrompts/browser.ini6
-rw-r--r--browser/tools/mozscreenshots/permissionPrompts/browser_permissionPrompts.js16
-rw-r--r--browser/tools/mozscreenshots/preferences/browser.ini6
-rw-r--r--browser/tools/mozscreenshots/preferences/browser_preferences.js16
-rw-r--r--browser/tools/mozscreenshots/primaryUI/browser.ini6
-rw-r--r--browser/tools/mozscreenshots/primaryUI/browser_primaryUI.js18
4192 files changed, 565436 insertions, 0 deletions
diff --git a/browser/.eslintrc.js b/browser/.eslintrc.js
new file mode 100644
index 000000000..d3d2f320b
--- /dev/null
+++ b/browser/.eslintrc.js
@@ -0,0 +1,15 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../toolkit/.eslintrc.js"
+ ],
+
+ "rules": {
+ "no-unused-vars": ["error", {
+ "vars": "local",
+ "varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
+ "args": "none",
+ }]
+ }
+};
diff --git a/browser/LICENSE b/browser/LICENSE
new file mode 100644
index 000000000..99d9d6bcd
--- /dev/null
+++ b/browser/LICENSE
@@ -0,0 +1,7 @@
+Please see the file ../toolkit/content/license.html for the copyright
+licensing conditions attached to this codebase, including copies of the
+licenses concerned.
+
+You are not granted rights or licenses to the trademarks of the
+Mozilla Foundation or any party, including without limitation the
+Firefox name or logo.
diff --git a/browser/Makefile.in b/browser/Makefile.in
new file mode 100644
index 000000000..2eb9e708f
--- /dev/null
+++ b/browser/Makefile.in
@@ -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/.
+
+include $(topsrcdir)/config/rules.mk
+
+ifdef MAKENSISU
+
+# For Windows build the uninstaller during the application build since the
+# uninstaller is included with the application for mar file generation.
+libs::
+ $(MAKE) -C installer/windows uninstaller
+ifdef MOZ_MAINTENANCE_SERVICE
+ $(MAKE) -C installer/windows maintenanceservice_installer
+endif
+endif
diff --git a/browser/app-rules.mk b/browser/app-rules.mk
new file mode 100644
index 000000000..2c3165304
--- /dev/null
+++ b/browser/app-rules.mk
@@ -0,0 +1 @@
+PURGECACHES_DIRS = $(DIST)/bin/browser
diff --git a/browser/app.mozbuild b/browser/app.mozbuild
new file mode 100644
index 000000000..454933eb5
--- /dev/null
+++ b/browser/app.mozbuild
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include('/toolkit/toolkit.mozbuild')
+
+if CONFIG['MOZ_EXTENSIONS']:
+ DIRS += ['/extensions']
+
+DIRS += ['/%s' % CONFIG['MOZ_BRANDING_DIRECTORY']]
+
+# Never add dirs after browser because they apparently won't get
+# packaged properly on Mac.
+DIRS += ['/browser']
diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in
new file mode 100644
index 000000000..cbd2ec0fc
--- /dev/null
+++ b/browser/app/Makefile.in
@@ -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/.
+
+dist_dest = $(DIST)/$(MOZ_MACBUNDLE_NAME)
+
+# hardcode en-US for the moment
+AB_CD = en-US
+
+# Build a binary bootstrapping with XRE_main
+
+ifndef MOZ_WINCONSOLE
+ifneq (,$(MOZ_DEBUG)$(MOZ_ASAN))
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+# This switches $(INSTALL) to copy mode, like $(SYSINSTALL), so things that
+# shouldn't get 755 perms need $(IFLAGS1) for either way of calling nsinstall.
+NSDISTMODE = copy
+
+include $(topsrcdir)/config/config.mk
+
+ifeq ($(OS_ARCH),WINNT)
+# Rebuild firefox.exe if the manifest changes - it's included by splash.rc.
+# (this dependency should really be just for firefox.exe, not other targets)
+# Note the manifest file exists in the tree, so we use the explicit filename
+# here.
+EXTRA_DEPS += firefox.exe.manifest
+endif
+
+PROGRAMS_DEST = $(DIST)/bin
+
+include $(topsrcdir)/config/rules.mk
+
+ifneq (,$(filter-out WINNT,$(OS_ARCH)))
+
+ifdef COMPILE_ENVIRONMENT
+libs::
+ cp -p $(MOZ_APP_NAME)$(BIN_SUFFIX) $(DIST)/bin/$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
+endif
+
+GARBAGE += $(addprefix $(FINAL_TARGET)/defaults/pref/, firefox.js)
+
+endif
+
+# channel-prefs.js is handled separate from other prefs due to bug 756325
+libs:: $(srcdir)/profile/channel-prefs.js
+ $(NSINSTALL) -D $(DIST)/bin/defaults/pref
+ $(call py_action,preprocessor,-Fsubstitution $(PREF_PPFLAGS) $(ACDEFINES) $^ -o $(DIST)/bin/defaults/pref/channel-prefs.js)
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+
+MAC_APP_NAME = $(MOZ_APP_DISPLAYNAME)
+
+ifdef MOZ_DEBUG
+MAC_APP_NAME := $(MAC_APP_NAME)Debug
+endif
+
+AB_CD = $(MOZ_UI_LOCALE)
+
+ifeq (zh-TW,$(AB_CD))
+LPROJ_ROOT := $(subst -,_,$(AB_CD))
+else
+LPROJ_ROOT := $(firstword $(subst -, ,$(AB_CD)))
+endif
+LPROJ := Contents/Resources/$(LPROJ_ROOT).lproj
+
+clean clobber repackage::
+ $(RM) -r $(dist_dest)
+
+MAC_BUNDLE_VERSION = $(shell $(PYTHON) $(srcdir)/macversion.py --version=$(MOZ_APP_VERSION) --buildid=$(DEPTH)/buildid.h)
+
+.PHONY: repackage
+tools repackage:: $(DIST)/bin/$(MOZ_APP_NAME)
+ $(MKDIR) -p $(dist_dest)/Contents/MacOS
+ $(MKDIR) -p $(dist_dest)/$(LPROJ)
+ rsync -a --exclude '*.in' $(srcdir)/macbuild/Contents $(dist_dest) --exclude English.lproj
+ rsync -a --exclude '*.in' $(srcdir)/macbuild/Contents/Resources/English.lproj/ $(dist_dest)/$(LPROJ)
+ sed -e 's/%APP_VERSION%/$(MOZ_APP_VERSION)/' -e 's/%MAC_APP_NAME%/$(MAC_APP_NAME)/' -e 's/%MOZ_MACBUNDLE_ID%/$(MOZ_MACBUNDLE_ID)/' -e 's/%MAC_BUNDLE_VERSION%/$(MAC_BUNDLE_VERSION)/' $(srcdir)/macbuild/Contents/Info.plist.in > $(dist_dest)/Contents/Info.plist
+ sed -e 's/%MAC_APP_NAME%/$(MAC_APP_NAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | iconv -f UTF-8 -t UTF-16 > $(dist_dest)/$(LPROJ)/InfoPlist.strings
+ rsync -a --exclude-from='$(srcdir)/macbuild/Contents/MacOS-files.in' $(DIST)/bin/ $(dist_dest)/Contents/Resources
+ rsync -a --include-from='$(srcdir)/macbuild/Contents/MacOS-files.in' --exclude '*' $(DIST)/bin/ $(dist_dest)/Contents/MacOS
+ $(RM) $(dist_dest)/Contents/MacOS/$(MOZ_APP_NAME)
+ rsync -aL $(DIST)/bin/$(MOZ_APP_NAME) $(dist_dest)/Contents/MacOS
+ cp -RL $(DIST)/branding/firefox.icns $(dist_dest)/Contents/Resources/firefox.icns
+ cp -RL $(DIST)/branding/document.icns $(dist_dest)/Contents/Resources/document.icns
+ $(MKDIR) -p $(dist_dest)/Contents/Library/LaunchServices
+ mv -f $(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater $(dist_dest)/Contents/Library/LaunchServices
+ ln -s ../../../../Library/LaunchServices/org.mozilla.updater $(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater
+ printf APPLMOZB > $(dist_dest)/Contents/PkgInfo
+endif
diff --git a/browser/app/blocklist.xml b/browser/app/blocklist.xml
new file mode 100644
index 000000000..2753992a5
--- /dev/null
+++ b/browser/app/blocklist.xml
@@ -0,0 +1,5085 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<blocklist lastupdate="1500496563565" xmlns="http://www.mozilla.org/2006/addons-blocklist">
+ <emItems>
+ <emItem blockID="i988" id="{b12785f5-d8d0-4530-a3ea-5c4263b85bef}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i398" id="{377e5d4d-77e5-476a-8716-7e70a9272da0}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i698" id="{6b2a75c8-6e2e-4267-b955-43e25b54e575}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1231" id="youtube@downloader.yt">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1263" id="axtara__web@axtara.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.1.1" severity="3"/>
+ </emItem>
+ <emItem blockID="i874" id="/^toolbar[0-9]*@findwide\.com$/">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i922" id="{34712C68-7391-4c47-94F3-8F88D49AD632}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="39.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i196" id="info@wxdownloadmanager.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1078" id="/^(jid1-W4CLFIRExukJIFW@jetpack|jid1-W4CLFIRExukJIFW@jetpack_1|jid1-W3CLwrP[a-z]+@jetpack)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i682" id="f6682b47-e12f-400b-9bc0-43b3ccae69d1@39d6f481-b198-4349-9ebe-9a93a86f9267.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1024" id="{458fb825-2370-4973-bf66-9d7142141847}">
+ <prefs>
+ <pref>app.update.auto</pref>
+ <pref>app.update.enabled</pref>
+ <pref>app.update.interval</pref>
+ <pref>app.update.url</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i522" id="/^({976cd962-e0ca-4337-aea7-d93fae63a79c}|{525ba996-1ce4-4677-91c5-9fc4ead2d245}|{91659dab-9117-42d1-a09f-13ec28037717}|{c1211069-1163-4ba8-b8b3-32fc724766be})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i6" id="{3f963a5b-e555-4543-90e2-c3908898db71}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="8.5" severity="1"/>
+ </emItem>
+ <emItem blockID="i692" id="/^(j003-lqgrmgpcekslhg|SupraSavings|j003-dkqonnnthqjnkq|j003-kaggrpmirxjpzh)@jetpack$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i490" id="now.msn.com@services.mozilla.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i42" id="{D19CA586-DD6C-4a0a-96F8-14644F340D60}">
+ <prefs/>
+ <versionRange minVersion="0.1" maxVersion="14.4.0" severity="1"/>
+ </emItem>
+ <emItem blockID="i756" id="{5eeb83d0-96ea-4249-942c-beead6847053}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i884" id="detgdp@gmail.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i543" id="{badea1ae-72ed-4f6a-8c37-4db9a4ac7bc9}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i53" id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
+ <prefs/>
+ <versionRange minVersion="2.0.3" maxVersion="2.0.3" severity="3"/>
+ <versionRange minVersion="4.2" maxVersion="4.2" severity="3"/>
+ </emItem>
+ <emItem blockID="i487" id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1040" id="frhegnejkgner@grhjgewfewf.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1030" id="support@todoist.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="3.9" severity="1"/>
+ </emItem>
+ <emItem blockID="i523" id="/^({7e8a1050-cf67-4575-92df-dcc60e7d952d}|{b3420a9c-a397-4409-b90d-bcf22da1a08a}|{eca6641f-2176-42ba-bdbe-f3e327f8e0af}|{707dca12-3f99-4d94-afea-06dcc0ae0108}|{aea20431-87fc-40be-bc5b-18066fe2819c}|{30ee6676-1ba6-455a-a7e8-298fa863a546})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i732" id="{e935dd68-f90d-46a6-b89e-c4657534b353}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="edad04eb-ea16-42f3-a4a7-20dded33cc37" id="@safesearchscoutee">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i436" id="/(\{7aeae561-714b-45f6-ace3-4a8aed6e227b\})|(\{01e86e69-a2f8-48a0-b068-83869bdba3d0\})|(\{77f5fe49-12e3-4cf5-abb4-d993a0164d9e\})/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i700" id="2bbadf1f-a5af-499f-9642-9942fcdb7c76@f05a14cc-8842-4eee-be17-744677a917ed.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i866" id="faststartff@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i320" id="torntv@torntv.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i524" id="/^({4e988b08-8c51-45c1-8d74-73e0c8724579}|{93ec97bf-fe43-4bca-a735-5c5d6a0a40c4}|{aed63b38-7428-4003-a052-ca6834d8bad3}|{0b5130a9-cc50-4ced-99d5-cda8cc12ae48}|{C4CFC0DE-134F-4466-B2A2-FF7C59A8BFAD})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1266" id="@stopad">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="0.0.4" severity="1"/>
+ </emItem>
+ <emItem blockID="i537" id="rally_toolbar_ff@bulletmedia.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i914" id="{0153E448-190B-4987-BDE1-F256CADA672F}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="39.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i473" id="{81b13b5d-fba1-49fd-9a6b-189483ac548a}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i690" id="{55dce8ba-9dec-4013-937e-adbf9317d990">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i105" id="{95ff02bc-ffc6-45f0-a5c8-619b8226a9de}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i218" id="ffxtlbr@claro.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i626" id="{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="3fd71895-7fc6-4f3f-aa22-1cbb0c5fd922" id="/^({95E84BD3-3604-4AAC-B2CA-D9AC3E55B64B}|{E3605470-291B-44EB-8648-745EE356599A}|{95E5E0AD-65F9-4FFC-A2A2-0008DCF6ED25}|{FF20459C-DA6E-41A7-80BC-8F4FEFD9C575}|{6E727987-C8EA-44DA-8749-310C0FBE3C3E}|{12E8A6C2-B125-479F-AB3C-13B8757C7F04}|{EB6628CF-0675-4DAE-95CE-EFFA23169743})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i716" id="{cc6cc772-f121-49e0-b1f0-c26583cb0c5e}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i501" id="xivars@aol.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i360" id="ytd@mybrowserbar.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i666" id="wecarereminder@bryan">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i504" id="aytac@abc.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i684" id="{9edd0ea8-2819-47c2-8320-b007d5996f8a}">
+ <prefs>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i69" id="{977f3b97-5461-4346-92c8-a14c749b77c9}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i444" id="fplayer@adobe.flash">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i550" id="colmer@yopmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i362" id="addon@defaulttab.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.4.4" severity="1"/>
+ </emItem>
+ <emItem blockID="i140" id="mozillahmpg@mozilla.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i503" id="{9CE11043-9A15-4207-A565-0C94C42D590D}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i16" id="{27182e60-b5f3-411c-b545-b44205977502}">
+ <prefs/>
+ <versionRange minVersion="1.0" maxVersion="1.0" severity="1"/>
+ </emItem>
+ <emItem blockID="i549" id="/^firefox@(albrechto|swiftbrowse|springsmart|storimbo|squirrelweb|betterbrowse|lizardlink|rolimno|browsebeyond|clingclang|weblayers|kasimos|higher-aurum|xaven|bomlabio)\.(com?|net|org|info|biz)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1079" id="/^(@9338379C-DD5C-4A45-9A36-9733DC806FAE|9338379C-DD5C-4A45-9A36-9733DC806FAE|@EBC7B466-8A28-4061-81B5-10ACC05FFE53|@bd6a97c0-4b18-40ed-bce7-3b7d3309e3c4222|@bd6a97c0-4b18-40ed-bce7-3b7d3309e3c4|@b2d6a97c0-4b18-40ed-bce7-3b7d3309e3c4222)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i622" id="/^({ebd898f8-fcf6-4694-bc3b-eabc7271eeb1}|{46008e0d-47ac-4daa-a02a-5eb69044431a}|{213c8ed6-1d78-4d8f-8729-25006aa86a76}|{fa23121f-ee7c-4bd8-8c06-123d087282c5}|{19803860-b306-423c-bbb5-f60a7d82cde5})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i862" id="{CA8C84C6-3918-41b1-BE77-049B2BDD887C}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i882" id="69ffxtbr@PackageTracer_69.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i706" id="thefoxonlybetter@quicksaver">
+ <prefs/>
+ <versionRange minVersion="1.10" maxVersion="*" severity="3"/>
+ <versionRange minVersion="1.6.160" maxVersion="1.6.160" severity="3"/>
+ <versionRange minVersion="0" maxVersion="0.*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1210" id="auto-plugin-checker@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i497" id="{872b5b88-9db5-4310-bdd0-ac189557e5f5}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1522" id="/^(ciscowebexstart1@cisco\.com|ciscowebexstart_test@cisco\.com|ciscowebexstart@cisco\.com|ciscowebexgpc@cisco\.com)$/">
+ <prefs/>
+ <versionRange minVersion="1.0.0" maxVersion="1.0.1" severity="1"/>
+ </emItem>
+ <emItem blockID="i91" id="crossriderapp4926@crossrider.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="0.81.43" severity="1"/>
+ </emItem>
+ <emItem blockID="i376" id="{9e09ac65-43c0-4b9d-970f-11e2e9616c55}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i76" id="crossriderapp3924@crossrider.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i3" id="langpack-vi-VN@firefox.mozilla.org">
+ <prefs/>
+ <versionRange minVersion="2.0" maxVersion="2.0" severity="1"/>
+ </emItem>
+ <emItem blockID="i870" id="M1uwW0@47z8gRpK8sULXXLivB.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i778" id="{f2456568-e603-43db-8838-ffa7c4a685c7}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i318" id="ffxtlbr@incredibar.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1016" id="jid1-uabu5A9hduqzCw@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i754" id="{bb7b7a60-f574-47c2-8a0b-4c56f2da9802}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i694" id="59D317DB041748fdB89B47E6F96058F3@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i12" id="masterfiler@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i71" id="youtube@2youtube.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i447" id="{B18B1E5C-4D81-11E1-9C00-AFEB4824019B}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1261" id="support@lastpass.com">
+ <prefs/>
+ <versionRange minVersion="4.0.0a" maxVersion="4.1.20a" severity="1"/>
+ </emItem>
+ <emItem blockID="i509" id="contato@facefollow.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i394" id="{7D4F1959-3F72-49d5-8E59-F02F8AA6815D}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i810" id="{41339ee8-61ed-489d-b049-01e41fd5d7e0}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i15" id="personas@christopher.beard">
+ <prefs/>
+ <versionRange minVersion="1.6" maxVersion="1.6" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="3.6.*" minVersion="3.6"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i618" id="toolbar@ask.com">
+ <prefs/>
+ <versionRange minVersion="3.15.24" maxVersion="3.15.24.*" severity="1"/>
+ <versionRange minVersion="3.15.13" maxVersion="3.15.13.*" severity="1"/>
+ <versionRange minVersion="3.15.28" maxVersion="3.15.28.*" severity="1"/>
+ <versionRange minVersion="3.15.22" maxVersion="3.15.22.*" severity="1"/>
+ <versionRange minVersion="3.15.8" maxVersion="3.15.8.*" severity="1"/>
+ <versionRange minVersion="3.15.10" maxVersion="3.15.11.*" severity="1"/>
+ <versionRange minVersion="3.15.18" maxVersion="3.15.20.*" severity="1"/>
+ <versionRange minVersion="3.15.5" maxVersion="3.15.5.*" severity="1"/>
+ <versionRange minVersion="3.15.31" maxVersion="3.15.31.*" severity="1"/>
+ <versionRange minVersion="3.15.26" maxVersion="3.15.26.*" severity="1"/>
+ </emItem>
+ <emItem blockID="i529" id="/^(torntv@torntv\.com|trtv3@trtv\.com|torntv2@torntv\.com|e2fd07a6-e282-4f2e-8965-85565fcb6384@b69158e6-3c3b-476c-9d98-ae5838c5b707\.com)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i486" id="xz123@ya456.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i784" id="{41e5ef7a-171d-4ab5-8351-951c65a29908}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i57" id="youtube@youtube3.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i424" id="{C7AE725D-FA5C-4027-BB4C-787EF9F8248A}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.0.0.2" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="23.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i104" id="yasd@youasdr3.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i498" id="hoverst@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i54" id="applebeegifts@mozilla.doslash.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i482" id="brasilescapeeight@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1136" id="/^({1f43c8af-e9e4-4e5a-b77a-f51c7a916324}|{3a3bd700-322e-440a-8a6a-37243d5c7f92}|{6a5b9fc2-733a-4964-a96a-958dd3f3878e}|{7b5d6334-8bc7-4bca-a13e-ff218d5a3f17}|{b87bca5b-2b5d-4ae8-ad53-997aa2e238d4}|{bf8e032b-150f-4656-8f2d-6b5c4a646e0d})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i312" id="extension21804@extension21804.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i552" id="jid0-O6MIff3eO5dIGf5Tcv8RsJDKxrs@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i479" id="mbrsepone@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i744" id="{84a93d51-b7a9-431e-8ff8-d60e5d7f5df1}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i736" id="{c5e48979-bd7f-4cf7-9b73-2482a67a4f37}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i406" id="{bf7380fa-e3b4-4db2-af3e-9d8783a45bfc}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i437" id="{4933189D-C7F7-4C6E-834B-A29F087BFD23}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i222" id="dealcabby@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i100" id="{394DCBA4-1F92-4f8e-8EC9-8D2CB90CB69B}">
+ <prefs/>
+ <versionRange minVersion="2.5.0" maxVersion="2.5.0" severity="1"/>
+ </emItem>
+ <emItem blockID="i590" id="{94cd2cc3-083f-49ba-a218-4cda4b4829fd}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1232" id="nosquint@urandom.ca">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="2.1.9.1-signed.1-signed" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="47"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i748" id="{32da2f20-827d-40aa-a3b4-2fc4a294352e}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i544" id="/^(93abedcf-8e3a-4d02-b761-d1441e437c09@243f129d-aee2-42c2-bcd1-48858e1c22fd\.com|9acfc440-ac2d-417a-a64c-f6f14653b712@09f9a966-9258-4b12-af32-da29bdcc28c5\.com|58ad0086-1cfb-48bb-8ad2-33a8905572bc@5715d2be-69b9-4930-8f7e-64bdeb961cfd\.com)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i650" id="jid1-qj0w91o64N7Eeg@jetpack">
+ <prefs/>
+ <versionRange minVersion="39.5.1" maxVersion="47.0.4" severity="3"/>
+ </emItem>
+ <emItem blockID="i640" id="jid0-l9BxpNUhx1UUgRfKigWzSfrZqAc@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i628" id="ffxtlbr@iminent.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1042" id="gjhrjenrengoe@jfdnkwelfwkm.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1228" id="unblocker30__web@unblocker.yt">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i918" id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}" os="WINNT">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="15.0.5" severity="1"/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="39.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i648" id="firefoxaddon@youtubeenhancer.com">
+ <prefs/>
+ <versionRange minVersion="208.7.0" maxVersion="208.7.0" severity="3"/>
+ <versionRange minVersion="199.7.0" maxVersion="199.7.0" severity="3"/>
+ <versionRange minVersion="199.7.0" maxVersion="208.7.0" severity="3"/>
+ </emItem>
+ <emItem blockID="i762" id="/^({2d7886a0-85bb-4bf2-b684-ba92b4b21d23}|{2fab2e94-d6f9-42de-8839-3510cef6424b}|{c02397f7-75b0-446e-a8fa-6ef70cfbf12b}|{8b337819-d1e8-48d3-8178-168ae8c99c36}|firefox@neurowise.info|firefox@allgenius.info)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i568" id="thunder@xunlei.com" os="Darwin">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="2.0.6" severity="1"/>
+ </emItem>
+ <emItem blockID="i768" id="{f2548724-373f-45fe-be6a-3a85e87b7711}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i772" id="{72b98dbc-939a-4e0e-b5a9-9fdbf75963ef}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i996" id="9598582LLKmjasieijkaslesae@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i586" id="jid1-0xtMKhXFEs4jIg@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i546" id="firefox@browsefox.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i438" id="{02edb56b-9b33-435b-b7df-b2843273a694}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i358" id="lfind@nijadsoft.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i67" id="youtube2@youtube2.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i88" id="anttoolbar@ant.com">
+ <prefs/>
+ <versionRange minVersion="2.4.6.4" maxVersion="2.4.6.4" severity="1"/>
+ </emItem>
+ <emItem blockID="i786" id="{63eb5ed4-e1b3-47ec-a253-f8462f205350}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i115" id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i510" id="{3c9a72a0-b849-40f3-8c84-219109c27554}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1229" id="/^(.*@(unblocker\.yt|sparpilot\.com))|(axtara@axtara\.com)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i530" id="{739df940-c5ee-4bab-9d7e-270894ae687a}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i431" id="chinaescapeone@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i804" id="{ad7ce998-a77b-4062-9ffb-1d0b7cb23183}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i168" id="flashX@adobe.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i535" id="/^ext@WebexpEnhancedV1alpha[0-9]+\.net$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i404" id="{a9bb9fa0-4122-4c75-bd9a-bc27db3f9155}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="510bbd9b-b883-4837-90ab-8e353e27e1be" id="{3B4DE07A-DE43-4DBC-873F-05835FF67DCE}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i730" id="25p@9eAkaLq.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1227" id="{A34CAF42-A3E3-11E5-945F-18C31D5D46B0}">
+ <prefs>
+ <pref>security.csp.enable</pref>
+ <pref>security.fileuri.strict_origin_policy</pref>
+ <pref>security.mixed_content.block_active_content</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i400" id="{dd6b651f-dfb9-4142-b0bd-09912ad22674}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i11" id="yslow@yahoo-inc.com">
+ <prefs/>
+ <versionRange minVersion="2.0.5" maxVersion="2.0.5" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.5.7"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i433" id="{c95a4e8e-816d-4655-8c79-d736da1adb6d}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i584" id="{52b0f3db-f988-4788-b9dc-861d016f4487}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="0.1.9999999" severity="1"/>
+ </emItem>
+ <emItem blockID="i466" id="afext@anchorfree.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i19" id="{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}">
+ <prefs/>
+ <versionRange minVersion="1.1b1" maxVersion="1.1b1" severity="1"/>
+ </emItem>
+ <emItem blockID="i982" id="odtffplugin@ibm.com">
+ <prefs/>
+ <versionRange minVersion="9.0.1.1" maxVersion="9.0.1.100" severity="1"/>
+ </emItem>
+ <emItem blockID="i1036" id="HxLVJK1ioigz9WEWo8QgCs3evE7uW6LEExAniBGG@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i580" id="{51c77233-c0ad-4220-8388-47c11c18b355}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="0.1.9999999" severity="1"/>
+ </emItem>
+ <emItem blockID="i726" id="{d87d56b2-1379-49f4-b081-af2850c79d8e}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1128" id="youtubeunblocker@unblocker.yt">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1038" id="344141-fasf9jas08hasoiesj9ia8ws@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="89a61123-79a2-45d1-aec2-97afca0863eb" id="InternetProtection@360safe.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="5.0.0.1002" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="52.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i471" id="firefox@luckyleap.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i560" id="adsremoval@adsremoval.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i396" id="/@(ft|putlocker|clickmovie|m2k|sharerepo|smarter-?)downloader\.com$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i968" id="{184AA5E6-741D-464a-820E-94B3ABC2F3B4}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i538" id="{354dbb0a-71d5-4e9f-9c02-6c88b9d387ba}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i792" id="{8f894ed3-0bf2-498e-a103-27ef6e88899f}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i742" id="{f894a29a-f065-40c3-bb19-da6057778493}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i658" id="low_quality_flash@pie2k.com">
+ <prefs/>
+ <versionRange minVersion="46.2" maxVersion="47.1" severity="3"/>
+ </emItem>
+ <emItem blockID="i17" id="{3252b9ae-c69a-4eaf-9502-dc9c1f6c009e}">
+ <prefs/>
+ <versionRange minVersion="2.2" maxVersion="2.2" severity="1"/>
+ </emItem>
+ <emItem blockID="i109" id="{392e123b-b691-4a5e-b52f-c4c1027e749c}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i814" id="liiros@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i167" id="{b64982b1-d112-42b5-b1e4-d3867c4533f8}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i172" id="info@bflix.info">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i352" id="vpyekkifgv@vpyekkifgv.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1278" id="/^(ff\-)?dodate(kKKK|XkKKK|k|kk|kkx|kR)@(firefox|flash(1)?)\.pl|dode(ee)?k@firefoxnet\.pl|(addon|1)@upsolutions\.pl$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i256" id="/^[0-9a-f]+@[0-9a-f]+\.info/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i980" id="wHO@W9.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i127" id="plugin@youtubeplayer.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i340" id="chiang@programmer.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i740" id="ascsurfingprotection@iobit.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i554" id="lightningnewtab@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="84dd8a02-c879-4477-8ea7-bf2f225b0940" id="{87010166-e3d0-4db5-a394-0517917201df}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i380" id="{cc8f597b-0765-404e-a575-82aefbd81daf}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i432" id="lugcla21@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i392" id="{EEE6C361-6118-11DC-9C72-001320C79847}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i429" id="{B40794A0-7477-4335-95C5-8CB9BBC5C4A5}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i724" id="{1cdbda58-45f8-4d91-b566-8edce18f8d0a}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i638" id="{7b1bf0b6-a1b9-42b0-b75d-252036438bdc}">
+ <prefs/>
+ <versionRange minVersion="27.8" maxVersion="27.9" severity="3"/>
+ </emItem>
+ <emItem blockID="i533" id="extension@Fast_Free_Converter.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i38" id="{B7082FAA-CB62-4872-9106-E42DD88EDE45}">
+ <prefs/>
+ <versionRange minVersion="3.3.1" maxVersion="*" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="5.0a1"/>
+ </targetApplication>
+ </versionRange>
+ <versionRange minVersion="0.1" maxVersion="3.3.0.*" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.7a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i746" id="{58d2a791-6199-482f-a9aa-9b725ec61362}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
+ <prefs/>
+ <versionRange minVersion="0.1" maxVersion="5.2.0.7164" severity="1"/>
+ </emItem>
+ <emItem blockID="i686" id="{a7f2cb14-0472-42a1-915a-8adca2280a2c}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="04b25e3d-a725-493e-be07-cbd74fb37ea7" id="{95E84BD3-3604-4AAC-B2CA-D9AC3E55B64B}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1279" id="dodatek@flash2.pl">
+ <prefs/>
+ <versionRange minVersion="1.3" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i998" id="meOYKQEbBBjH5Ml91z0p9Aosgus8P55bjTa4KPfl@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i434" id="afurladvisor@anchorfree.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i306" id="{ADFA33FD-16F5-4355-8504-DF4D664CFE10}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i83" id="flash@adobee.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i670" id="/^({ad9a41d2-9a49-4fa6-a79e-71a0785364c8})|(ffxtlbr@mysearchdial\.com)$/">
+ <prefs>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i774" id="x77IjS@xU.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i718" id="G4Ce4@w.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i21" id="support@update-firefox.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i476" id="mbroctone@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i491" id="{515b2424-5911-40bd-8a2c-bdb20286d8f5}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i532" id="249911bc-d1bd-4d66-8c17-df533609e6d8@c76f3de9-939e-4922-b73c-5d7a3139375d.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i65" id="activity@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="28c805a9-e692-4ef8-b3ae-14e085c19ecd" id="{3602008d-8195-4860-965a-d01ac4f9ca96}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1223" id="tmbepff@trendmicro.com">
+ <prefs/>
+ <versionRange minVersion="9.2" maxVersion="9.2.0.1023" severity="1"/>
+ <versionRange minVersion="0" maxVersion="9.1.0.1035" severity="1"/>
+ </emItem>
+ <emItem blockID="i478" id="{7e8a1050-cf67-4575-92df-dcc60e7d952d}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i370" id="happylyrics@hpyproductions.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i924" id="{DAC3F861-B30D-40dd-9166-F4E75327FAC7}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="39.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i7" id="{2224e955-00e9-4613-a844-ce69fccaae91}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i505" id="extacylife@a.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i886" id="searchengine@gmail.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i528" id="008abed2-b43a-46c9-9a5b-a771c87b82da@1ad61d53-2bdc-4484-a26b-b888ecae1906.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1414" id="/^new@kuot\.pro|{13ec6687-0b15-4f01-a5a0-7a891c18e4ee}|rebeccahoppkins(ty(tr)?)?@gmail\.com|{501815af-725e-45be-b0f2-8f36f5617afc}|{9bdb5f1f-b1e1-4a75-be31-bdcaace20a99}|{e9d93e1d-792f-4f95-b738-7adb0e853b7b}|dojadewaskurwa@gmail\.com$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i712" id="{a2bfe612-4cf5-48ea-907c-f3fb25bc9d6b}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i652" id="garg_sms@yahoo.in">
+ <prefs/>
+ <versionRange minVersion="67.9" maxVersion="67.9" severity="3"/>
+ </emItem>
+ <emItem blockID="i47" id="youtube@youtube2.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i86" id="{45147e67-4020-47e2-8f7a-55464fb535aa}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i598" id="{29b136c9-938d-4d3d-8df8-d649d9b74d02}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i108" id="{28bfb930-7620-11e1-b0c4-0800200c9a66}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i226" id="{462be121-2b54-4218-bf00-b9bf8135b23f}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i44" id="sigma@labs.mozilla">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i96" id="youtubeee@youtuber3.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i564" id="/^(firefox@vebergreat\.net|EFGLQA@78ETGYN-0W7FN789T87\.COM)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i500" id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i97" id="support3_en@adobe122.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i439" id="{d2cf9842-af95-48cd-b873-bfbb48cd7f5e}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i576" id="newmoz@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
+ <prefs/>
+ <versionRange minVersion="0.1" maxVersion="*" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="9.0" minVersion="9.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i776" id="g@uzcERQ6ko.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i494" id="/^({e9df9360-97f8-4690-afe6-996c80790da4}|{687578b9-7132-4a7a-80e4-30ee31099e03}|{46a3135d-3683-48cf-b94c-82655cbc0e8a}|{49c795c2-604a-4d18-aeb1-b3eba27e5ea2}|{7473b6bd-4691-4744-a82b-7854eb3d70b6}|{96f454ea-9d38-474f-b504-56193e00c1a5})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i668" id="/^(matchersite(pro(srcs?)?)?\@matchersite(pro(srcs?)?)?\.com)|((pro)?sitematcher(_srcs?|pro|site|sitesrc|-generic)?\@(pro)?sitematcher(_srcs?|pro|site|sitesrc|-generic)?\.com)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i174" id="info@thebflix.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i822" id="{6af08a71-380e-42dd-9312-0111d2bc0630}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i888" id="istart_ffnt@gmail.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i972" id="831778-poidjao88DASfsAnindsd@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i846" id="PDVDZDW52397720@XDDWJXW57740856.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i844" id="e9d197d59f2f45f382b1aa5c14d82@8706aaed9b904554b5cb7984e9.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i216" id="fdm_ffext@freedownloadmanager.org">
+ <prefs/>
+ <versionRange minVersion="1.0" maxVersion="1.3.1" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.0a1"/>
+ </targetApplication>
+ </versionRange>
+ <versionRange minVersion="1.5.7.5" maxVersion="1.5.7.5" severity="1"/>
+ </emItem>
+ <emItem blockID="i515" id="/^({bf9194c2-b86d-4ebc-9b53-1c08b6ff779e}|{61a83e16-7198-49c6-8874-3e4e8faeb4f3}|{f0af464e-5167-45cf-9cf0-66b396d1918c}|{5d9968c3-101c-4944-ba71-72d77393322d}|{01e86e69-a2f8-48a0-b068-83869bdba3d0})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i596" id="{b99c8534-7800-48fa-bd71-519a46cdc7e1}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i461" id="{8E9E3331-D360-4f87-8803-52DE43566502}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i818" id="contentarget@maildrop.cc">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1211" id="flvto@hotger.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i23" id="firefox@bandoo.com">
+ <prefs/>
+ <versionRange minVersion="5.0" maxVersion="5.0" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.7a1pre"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i714" id="{25dd52dc-89a8-469d-9e8f-8d483095d1e8}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i59" id="ghostviewer@youtube2.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i708" id="{849ded12-59e9-4dae-8f86-918b70d213dc}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i448" id="{0134af61-7a0c-4649-aeca-90d776060cb3}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i78" id="socialnetworktools@mozilla.doslash.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i526" id="/^({83a8ce1b-683c-4784-b86d-9eb601b59f38}|{ef1feedd-d8da-4930-96f1-0a1a598375c6}|{79ff1aae-701f-4ca5-aea3-74b3eac6f01b}|{8a184644-a171-4b05-bc9a-28d75ffc9505}|{bc09c55d-0375-4dcc-836e-0e3c8addfbda}|{cef81415-2059-4dd5-9829-1aef3cf27f4f})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i92" id="play5@vide04flash.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i45" id="{22119944-ED35-4ab1-910B-E619EA06A115}">
+ <prefs/>
+ <versionRange minVersion="0.1" maxVersion="7.9.20.6" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="8.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i220" id="pricepeep@getpricepeep.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="2.1.0.19.99" severity="1"/>
+ </emItem>
+ <emItem blockID="i518" id="/^({d6e79525-4524-4707-9b97-1d70df8e7e59}|{ddb4644d-1a37-4e6d-8b6e-8e35e2a8ea6c}|{e55007f4-80c5-418e-ac33-10c4d60db01e}|{e77d8ca6-3a60-4ae9-8461-53b22fa3125b}|{e89a62b7-248e-492f-9715-43bf8c507a2f}|{5ce3e0cb-aa83-45cb-a7da-a2684f05b8f3})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i770" id="{8dc5c42e-9204-2a64-8b97-fa94ff8a241f}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1000" id="jufa098j-LKooapd9jasJ9jliJsd@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i308" id="9518042e-7ad6-4dac-b377-056e28d00c8f@f1cc0a13-4df1-4d66-938f-088db8838882.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i435" id="pluggets@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i696" id="/^({fa95f577-07cb-4470-ac90-e843f5f83c52}|ffxtlbr@speedial\.com)$/">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1212" id="unblocker20@unblocker.yt">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="2.0.0" severity="3"/>
+ </emItem>
+ <emItem blockID="i354" id="{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i84" id="pink@rosaplugin.info">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i364" id="{FE1DEEEA-DB6D-44b8-83F0-34FC0F9D1052}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i547" id="{87934c42-161d-45bc-8cef-ef18abe2a30c}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="3.7.9999999999" severity="1"/>
+ </emItem>
+ <emItem blockID="i356" id="{341f4dac-1966-47ff-aacf-0ce175f1498a}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i536" id="{25D77636-38B1-1260-887C-2D4AFA92D6A4}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i374" id="update@firefox.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i336" id="CortonExt@ext.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i452" id="{77beece6-3997-403a-92fa-0055bfcf88e5}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i858" id="fftoolbar2014@etech.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1523" id="{a0d7ccb3-214d-498b-b4aa-0e8fda9a7bf7}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="20170120" severity="1"/>
+ </emItem>
+ <emItem blockID="i1018" id="grjkntbhr@hgergerherg.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i850" id="P2@D.edu">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i541" id="/^({988919ff-0cd8-4d0c-bc7e-60d55a49eb64}|{494b9726-9084-415c-a499-68c07e187244}|{55b95864-3251-45e9-bb30-1a82589aaff1}|{eef3855c-fc2d-41e6-8d91-d368f51b3055}|{90a1b331-c2b4-4933-9f63-ba7b84d60d58}|{d2cf9842-af95-48cd-b873-bfbb48cd7f5e})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i324" id="/^((34qEOefiyYtRJT@IM5Munavn\.com)|(Mro5Fm1Qgrmq7B@ByrE69VQfZvZdeg\.com)|(KtoY3KGxrCe5ie@yITPUzbBtsHWeCdPmGe\.com)|(9NgIdLK5Dq4ZMwmRo6zk@FNt2GCCLGyUuOD\.com)|(NNux7bWWW@RBWyXdnl6VGls3WAwi\.com)|(E3wI2n@PEHTuuNVu\.com)|(2d3VuWrG6JHBXbQdbr@3BmSnQL\.com))$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i620" id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i70" id="psid-vhvxQHMZBOzUZA@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i790" id="JMLv@njMaHh.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1425" id="/^(pdftoword@addingapps.com|jid0-EYTXLS0GyfQME5irGbnD4HksnbQ@jetpack|jid1-ZjJ7t75BAcbGCX@jetpack)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i674" id="crossriderapp12555@crossrider.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="a84e6eba-4bc1-4416-b481-9b837d39f9f0" id="firefox@mega.co.nz">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="3.16.1" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="56.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i1034" id="a88a77ahjjfjakckmmabsy278djasi@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="9abc7502-bd6f-40d7-b035-abe721345360" id="{368eb817-31b4-4be9-a761-b67598faf9fa}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i562" id="iobitapps@mybrowserbar.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i430" id="1chtw@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i970" id="hha8771ui3-Fo9j9h7aH98jsdfa8sda@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i916" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="39.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i338" id="{1FD91A9C-410C-4090-BBCC-55D3450EF433}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i344" id="lrcsTube@hansanddeta.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="0f8344d0-8211-49a1-81be-c0084b3da9b1" id="fr@fbt.ovh">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i79" id="GifBlock@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i483" id="brasilescapefive@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i469" id="OKitSpace@OKitSpace.es">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i780" id="{b6ef1336-69bb-45b6-8cba-e578fc0e4433}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1058" id="amo-validator-bypass@example.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i48" id="admin@youtubespeedup.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i766" id="/^[a-z0-9]+@foxysecure[a-z0-9]*\.com$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i566" id="{77BEC163-D389-42c1-91A4-C758846296A5}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="9085fdba-8498-46a9-b9fd-4c7343a15c62" id="/^(test2@test\.com)|(test3@test\.com)|(mozilla_cc2\.2@internetdownloadmanager\.com)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="53.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="c142360c-4f93-467e-9717-b638aa085d95" id="/^(\{11112503-5e91-4299-bf4b-f8c07811aa50\})|(\{501815af-725e-45be-b0f2-8f36f5617afc\})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i525" id="/^({65f9f6b7-2dae-46fc-bfaf-f88e4af1beca}|{9ed31f84-c8b3-4926-b950-dff74047ff79}|{0134af61-7a0c-4649-aeca-90d776060cb3}|{02edb56b-9b33-435b-b7df-b2843273a694}|{da51d4f6-3e7e-4ef8-b400-9198e0874606}|{b24577db-155e-4077-bb37-3fdd3c302bb5})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i43" id="supportaccessplugin@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i660" id="youplayer@addons.mozilla.org">
+ <prefs/>
+ <versionRange minVersion="79.9.8" maxVersion="208.0.1" severity="3"/>
+ </emItem>
+ <emItem blockID="i646" id="{e1aaa9f8-4500-47f1-9a0a-b02bd60e4076}">
+ <prefs/>
+ <versionRange minVersion="178.7.0" maxVersion="178.7.0" severity="3"/>
+ </emItem>
+ <emItem blockID="i304" id="{f0e59437-6148-4a98-b0a6-60d557ef57f4}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i467" id="plugin@analytic-s.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i58" id="webmaster@buzzzzvideos.info">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i5" id="support@daemon-tools.cc">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.0.0.5" severity="1"/>
+ </emItem>
+ <emItem blockID="i545" id="superlrcs@svenyor.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i378" id="{a7aae4f0-bc2e-a0dd-fb8d-68ce32c9261f}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i449" id="gystqfr@ylgga.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i163" id="info@allpremiumplay.info">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i994" id="addonhack@mozilla.kewis.ch">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i588" id="quick_start@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i40" id="{28387537-e3f9-4ed7-860c-11e69af4a8a0}">
+ <prefs/>
+ <versionRange minVersion="0.1" maxVersion="4.3.1.00" severity="1"/>
+ </emItem>
+ <emItem blockID="i426" id="addlyrics@addlyrics.net">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i752" id="savingsslider@mybrowserbar.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i77" id="{fa277cfc-1d75-4949-a1f9-4ac8e41b2dfd}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i64" id="royal@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i728" id="l@AdLJ7uz.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i468" id="05dd836e-2cbd-4204-9ff3-2f8a8665967d@a8876730-fb0c-4057-a2fc-f9c09d438e81.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i60" id="youtb3@youtb3.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="e16408c3-4e08-47fd-85a9-3cbbce534e95" id="WebProtection@360safe.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="5.0.0.1009" severity="3"/>
+ </emItem>
+ <emItem blockID="i82" id="{8f42fb8b-b6f6-45de-81c0-d6d39f54f971}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i642" id="{bee6eb20-01e0-ebd1-da83-080329fb9a3a}">
+ <prefs/>
+ <versionRange minVersion="40.10.1" maxVersion="44.10.1" severity="3"/>
+ </emItem>
+ <emItem blockID="i514" id="/^(67314b39-24e6-4f05-99f3-3f88c7cddd17@6c5fa560-13a3-4d42-8e90-53d9930111f9\.com|ffxtlbr@visualbee\.com|{7aeae561-714b-45f6-ace3-4a8aed6e227b}|{7093ee04-f2e4-4637-a667-0f730797b3a0}|{53c4024f-5a2e-4f2a-b33e-e8784d730938})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i531" id="/^(4cb61367-efbf-4aa1-8e3a-7f776c9d5763@cdece6e9-b2ef-40a9-b178-291da9870c59\.com|0efc9c38-1ec7-49ed-8915-53a48b6b7600@e7f17679-2a42-4659-83c5-7ba961fdf75a\.com|6be3335b-ef79-4b0b-a0ba-b87afbc6f4ad@6bbb4d2e-e33e-4fa5-9b37-934f4fb50182\.com)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1423" id="/^(@pluginscribens_firefox|extension@vidscrab.com|firefox@jjj.ee|firefox@shop-reward.de|FxExtPasteNGoHtk@github.lostdj|himanshudotrai@gmail.com|jid0-bigoD0uivzAMmt07zrf3OHqa418@jetpack|jid0-iXbAR01tjT2BsbApyS6XWnjDhy8@jetpack)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i68" id="flashupdate@adobe.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i492" id="{af95cc15-3b9b-45ae-8d9b-98d08eda3111}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="674b6e19-f087-4706-a91d-1e723ed6f79e" id="{1490068c-d8b7-4bd2-9621-a648942b312c}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i488" id="jid1-4P0kohSJxU1qGg@jetpack">
+ <prefs/>
+ <versionRange minVersion="1.2.50" maxVersion="1.2.50" severity="1"/>
+ </emItem>
+ <emItem blockID="i314" id="crossriderapp8812@crossrider.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i262" id="{167d9323-f7cc-48f5-948a-6f012831a69f}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i838" id="{87b5a11e-3b54-42d2-9102-0a7cb1f79ebf}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i678" id="{C4A4F5A0-4B89-4392-AFAC-D58010E349AF}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i570" id="jid1-vW9nopuIAJiRHw@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i14" id="mozilla_cc@internetdownloadmanager.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="6.9.8" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.7a1pre"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i326" id="/^((support2_en@adobe14\.com)|(XN4Xgjw7n4@yUWgc\.com)|(C7yFVpIP@WeolS3acxgS\.com)|(Kbeu4h0z@yNb7QAz7jrYKiiTQ3\.com)|(aWQzX@a6z4gWdPu8FF\.com)|(CBSoqAJLYpCbjTP90@JoV0VMywCjsm75Y0toAd\.com)|(zZ2jWZ1H22Jb5NdELHS@o0jQVWZkY1gx1\.com))$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i39" id="{c2d64ff7-0ab8-4263-89c9-ea3b0f8f050c}">
+ <prefs/>
+ <versionRange minVersion="0.1" maxVersion="4.3.1.00" severity="1"/>
+ </emItem>
+ <emItem blockID="i852" id="6lIy@T.edu">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i856" id="/^({94d62e35-4b43-494c-bf52-ba5935df36ef}|firefox@advanceelite\.com|{bb7b7a60-f574-47c2-8a0b-4c56f2da9802})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i520" id="/^({7316e43a-3ebd-4bb4-95c1-9caf6756c97f}|{0cc09160-108c-4759-bab1-5c12c216e005}|{ef03e721-f564-4333-a331-d4062cee6f2b}|{465fcfbb-47a4-4866-a5d5-d12f9a77da00}|{7557724b-30a9-42a4-98eb-77fcb0fd1be3}|{b7c7d4b0-7a84-4b73-a7ef-48ef59a52c3b})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="f7569261-f575-4719-8202-552b20d013b0" id="{7e907a15-0a4c-4ff4-b64f-5eeb8f841349}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i465" id="trtv3@trtv.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i8" id="{B13721C7-F507-4982-B2E5-502A71474FED}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i662" id="imbaty@taringamp3.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i854" id="/^(7tG@zEb\.net|ru@gfK0J\.edu)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i73" id="a1g0a9g219d@a1.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1126" id="{bbea93c6-64a3-4a5a-854a-9cc61c8d309e}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i516" id="/^({3f3cddf8-f74d-430c-bd19-d2c9147aed3d}|{515b2424-5911-40bd-8a2c-bdb20286d8f5}|{17464f93-137e-4646-a0c6-0dc13faf0113}|{d1b5aad5-d1ae-4b20-88b1-feeaeb4c1ebc}|{aad50c91-b136-49d9-8b30-0e8d3ead63d0})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i51" id="admin@youtubeplayer.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i246" id="support@vide1flash2.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i372" id="5nc3QHFgcb@r06Ws9gvNNVRfH.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i680" id="jid1-bKSXgRwy1UQeRA@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i802" id="{18d5a8fe-5428-485b-968f-b97b05a92b54}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i710" id="{e0352044-1439-48ba-99b6-b05ed1a4d2de}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i506" id="/^ext@bettersurfplus/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i864" id="{0A92F062-6AC6-8180-5881-B6E0C0DC2CC5}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i672" id="/^(saamazon@mybrowserbar\.com)|(saebay@mybrowserbar\.com)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i634" id="jid1-4vUehhSALFNqCw@jetpack">
+ <prefs/>
+ <versionRange minVersion="99.7" maxVersion="99.7" severity="3"/>
+ <versionRange minVersion="100.7" maxVersion="100.7" severity="3"/>
+ </emItem>
+ <emItem blockID="i162" id="{EB7508CA-C7B2-46E0-8C04-3E94A035BD49}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i62" id="jid0-EcdqvFOgWLKHNJPuqAnawlykCGZ@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i103" id="kdrgun@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1119" id="/^(test3@test.org|test2@test.org|test@test.org|support@mozilla.org)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1424" id="/^(jid0-S9kkzfTvEmC985BVmf8ZOzA5nLM@jetpack|jid1-qps14pkDB6UDvA@jetpack|jid1-Tsr09YnAqIWL0Q@jetpack|shole@ats.ext|{38a64ef0-7181-11e3-981f-0800200c9a66}|eochoa@ualberta.ca)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i519" id="703db0db-5fe9-44b6-9f53-c6a91a0ad5bd@7314bc82-969e-4d2a-921b-e5edd0b02cf1.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i484" id="plugin@getwebcake.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i477" id="mbrnovone@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1524" id="ext@alibonus.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.20.9" severity="1"/>
+ </emItem>
+ <emItem blockID="i836" id="hansin@topvest.id">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i495" id="kallow@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i286" id="{58bd07eb-0ee0-4df0-8121-dc9b693373df}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i542" id="/^({bf67a47c-ea97-4caf-a5e3-feeba5331231}|{24a0cfe1-f479-4b19-b627-a96bf1ea3a56})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i676" id="SpecialSavings@SpecialSavings.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i52" id="ff-ext@youtube">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i540" id="/^(ffxtlbr@mixidj\.com|{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}|{67097627-fd8e-4f6b-af4b-ecb65e50112e}|{f6f0f973-a4a3-48cf-9a7a-b7a69c30d71a}|{a3d0e35f-f1da-4ccb-ae77-e9d27777e68d}|{1122b43d-30ee-403f-9bfa-3cc99b0caddd})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i720" id="FXqG@xeeR.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="d33f6d48-a555-49dd-96ff-8d75473403a8" id="mozilla_cc2@internetdownloadmanager.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="6.26.11" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="53.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i493" id="12x3q@3244516.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1265" id="@video_downloader_pro">
+ <prefs/>
+ <versionRange minVersion="1.2.1" maxVersion="1.2.5" severity="1"/>
+ </emItem>
+ <emItem blockID="i348" id="{13c9f1f9-2322-4d5c-81df-6d4bf8476ba4}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="595e0e53-b76b-4188-a160-66f29c636094" id="@68eba425-7a05-4d62-82b1-1d6d5a51716b">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i442" id="pennerdu@faceobooks.ws">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i99" id="pfzPXmnzQRXX6@2iABkVe.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i450" id="{dff137ae-1ffd-11e3-8277-b8ac6f996f26}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i750" id="{46eddf51-a4f6-4476-8d6c-31c5187b2a2f}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i282" id="{33e0daa6-3af3-d8b5-6752-10e949c61516}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.1.999" severity="1"/>
+ </emItem>
+ <emItem blockID="i238" id="/^pink@.*\.info$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="18.0"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i489" id="astrovia@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1245" id="{4ED1F68A-5463-4931-9384-8FFF5ED91D92}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="3.9.9" severity="1"/>
+ </emItem>
+ <emItem blockID="i18" id="msntoolbar@msn.com">
+ <prefs/>
+ <versionRange minVersion=" 0" maxVersion="6.*" severity="1"/>
+ </emItem>
+ <emItem blockID="i455" id="7d51fb17-b199-4d8f-894e-decaff4fc36a@a298838b-7f50-4c7c-9277-df6abbd42a0c.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="5df16afc-c804-43c9-9de5-f1835403e5fb" id="@H99KV4DO-UCCF-9PFO-9ZLK-8RRP4FVOKD9O">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i764" id="prositez@prz.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i474" id="{906000a4-88d9-4d52-b209-7a772970d91f}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i848" id="bcVX5@nQm9l.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i734" id="profsites@pr.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i926" id="{B1FC07E1-E05B-4567-8891-E63FBE545BA8}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="39.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i382" id="{6926c7f7-6006-42d1-b046-eba1b3010315}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i806" id="{d9284e50-81fc-11da-a72b-0800200c9a66}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="7.7.34" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="34.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i782" id="safebrowse@safebrowse.co">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i812" id="{1e4ea5fc-09e5-4f45-a43b-c048304899fc}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i56" id="flash@adobe.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1032" id="KSqOiTeSJEDZtTGuvc18PdPmYodROmYzfpoyiCr2@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="28736359-700e-4b61-9c50-0b533a6bac55" id="xdict@www.iciba.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="2.3.1" severity="1"/>
+ </emItem>
+ <emItem blockID="i539" id="ScorpionSaver@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i472" id="linksicle@linksicle.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="0a47a2f7-f07c-489b-bd39-88122a2dfe6a" id="@DA3566E2-F709-11E5-8E87-A604BC8E7F8B">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="455772a3-8360-4f5a-9a5f-a45b904d0b51" id="{dfa727cb-0246-4c5a-843a-e4a8592cc7b9}">
+ <prefs/>
+ <versionRange minVersion="2.0.0" maxVersion="2.0.0" severity="1"/>
+ </emItem>
+ <emItem blockID="i98" id="youtubeeing@youtuberie.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1213" id="unblocker20__web@unblocker.yt">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i578" id="jid1-XLjasWL55iEE1Q@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1233" id="cloudmask@cloudmask.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="2.0.788" severity="1"/>
+ </emItem>
+ <emItem blockID="i582" id="discoverypro@discoverypro.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i346" id="{a6e67e6f-8615-4fe0-a599-34a73fc3fba5}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i688" id="firefox-extension@mozilla.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i63" id="youtube@youtuber.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i820" id="{aab02ab1-33cf-4dfa-8a9f-f4e60e976d27}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i521" id="/^({66b103a7-d772-4fcd-ace4-16f79a9056e0}|{6926c7f7-6006-42d1-b046-eba1b3010315}|{72cabc40-64b2-46ed-8648-26d831761150}|{73ee2cf2-7b76-4c49-b659-c3d8cf30825d}|{ca6446a5-73d5-4c35-8aa1-c71dc1024a18}|{5373a31d-9410-45e2-b299-4f61428f0be4})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i322" id="jid0-Y6TVIzs0r7r4xkOogmJPNAGFGBw@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1129" id="youtubeunblocker__web@unblocker.yt">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i24" id="{6E19037A-12E3-4295-8915-ED48BC341614}">
+ <prefs/>
+ <versionRange minVersion="0.1" maxVersion="1.3.328.4" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.7a1pre"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i4" id="{4B3803EA-5230-4DC3-A7FC-33638F3D3542}">
+ <prefs/>
+ <versionRange minVersion="1.2" maxVersion="1.2" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i66" id="youtubeer@youtuber.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1214" id="firefoxdav@icloud.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.4.22" severity="1"/>
+ </emItem>
+ <emItem blockID="i808" id="{c96d1ae6-c4cf-4984-b110-f5f561b33b5a}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i517" id="/^({16e193c8-1706-40bf-b6f3-91403a9a22be}|{284fed43-2e13-4afe-8aeb-50827d510e20}|{5e3cc5d8-ed11-4bed-bc47-35b4c4bc1033}|{7429e64a-1fd4-4112-a186-2b5630816b91}|{8c9980d7-0f09-4459-9197-99b3e559660c}|{8f1d9545-0bb9-4583-bb3c-5e1ac1e2920c})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i460" id="{845cab51-d8d2-472f-8bd9-2b44642d97c2}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i136" id="Adobe@flash.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i441" id="{49c53dce-afa0-49a1-a08b-2eb8e8444128}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i527" id="/^({bfec236d-e122-4102-864f-f5f19d897f5e}|{3f842035-47f4-4f10-846b-6199b07f09b8}|{92ed4bbd-83f2-4c70-bb4e-f8d3716143fe})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="d83011de-67a4-479b-a778-916a7232095b" id="{efda3854-2bd9-45a1-9766-49d7ff18931d}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="0.8.17.2" severity="1"/>
+ </emItem>
+ <emItem blockID="i800" id="{424b0d11-e7fe-4a04-b7df-8f2c77f58aaf}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="d6425f24-8c9e-4c0a-89b4-6890fc68d5c9" id="/^\{(9321F452-96D5-11E6-BC3E-3769C7AD2208)|({18ED1ECA-96D3-11E6-A373-BD66C7AD2208})\}$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i920" id="{FCE04E1F-9378-4f39-96F6-5689A9159E45}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="39.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i1050" id="87aukfkausiopoawjsuifhasefgased278djasi@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i224" id="{336D0C35-8A85-403a-B9D2-65C292C39087}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i507" id="4zffxtbr-bs@VideoDownloadConverter_4z.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="5.75.3.25126" severity="1"/>
+ </emItem>
+ <emItem blockID="i22" id="ShopperReports@ShopperReports.com">
+ <prefs/>
+ <versionRange minVersion="3.1.22.0" maxVersion="3.1.22.0" severity="1"/>
+ </emItem>
+ <emItem blockID="22431713-a93b-40f4-8264-0b341b5f6454" id="fi@dictionaries.addons.mozilla.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="2.1.0" severity="3">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="56.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i1230" id="addon@gemaoff">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1137" id="/^({d50bfa5f-291d-48a8-909c-5f1a77b31948}|{d54bc985-6e7b-46cd-ad72-a4a266ad879e}|{d89e5de3-5543-4363-b320-a98cf150f86a}|{f3465017-6f51-4980-84a5-7bee2f961eba}|{fae25f38-ff55-46ea-888f-03b49aaf8812})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i342" id="lbmsrvfvxcblvpane@lpaezhjez.org">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i840" id="{4889ddce-7a83-45e6-afc9-1e4f1149fff4}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i10" id="{8CE11043-9A15-4207-A565-0C94C42D590D}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i788" id="{729c9605-0626-4792-9584-4cbe65b243e6}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i548" id="/^firefox@(jumpflip|webconnect|browsesmart|mybuzzsearch|outobox|greygray|lemurleap|divapton|secretsauce|batbrowse|whilokii|linkswift|qualitink|browsefox|kozaka|diamondata|glindorus|saltarsmart|bizzybolt|websparkle)\.(com?|net|org|info|biz)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i872" id="search-snacks@search-snacks.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i966" id="{5C655500-E712-41e7-9349-CE462F844B19}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.0.1-signed" severity="1"/>
+ </emItem>
+ <emItem blockID="i90" id="videoplugin@player.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1012" id="wxtui502n2xce9j@no14">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i508" id="advance@windowsclient.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1022" id="g99hiaoekjoasiijdkoleabsy278djasi@jetpack">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i165" id="{EEF73632-A085-4fd3-A778-ECD82C8CB297}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i499" id="{babb9931-ad56-444c-b935-38bffe18ad26}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i75" id="firebug@software.joehewitt.com" os="Darwin,Linux">
+ <prefs/>
+ <versionRange minVersion="1.9.0" maxVersion="1.9.0" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="9.*" minVersion="9.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i1264" id="suchpony@suchpony.de">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.6.7" severity="3"/>
+ </emItem>
+ <emItem blockID="c806b01c-3352-4083-afd9-9a8ab6e00b19" id="html5@encoding">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i656" id="hdv@vovcacik.addons.mozilla.org">
+ <prefs/>
+ <versionRange minVersion="102.0" maxVersion="102.0" severity="3"/>
+ </emItem>
+ <emItem blockID="i722" id="{9802047e-5a84-4da3-b103-c55995d147d1}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i228" id="crossriderapp5060@crossrider.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i470" id="extension@FastFreeConverter.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i55" id="youtube@youtube7.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i440" id="{2d069a16-fca1-4e81-81ea-5d5086dcbd0c}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1077" id="helper@vidscrab.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1492" id="googlotim@gmail.com">
+ <prefs/>
+ <versionRange minVersion="1.3.2" maxVersion="1.3.2" severity="1"/>
+ </emItem>
+ <emItem blockID="i93" id="{68b8676b-99a5-46d1-b390-22411d8bcd61}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i664" id="123456789@offeringmedia.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i630" id="webbooster@iminent.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="10d9ce89-b8d4-4b53-b3d7-ecd192681f4e" id="{d03b6b0f-4d44-4666-a6d6-f16ad9483593}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i842" id="{746505DC-0E21-4667-97F8-72EA6BCF5EEF}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i868" id="{6e7f6f9f-8ce6-4611-add2-05f0f7049ee6}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i624" id="/^({b95faac1-a3d7-4d69-8943-ddd5a487d966}|{ecce0073-a837-45a2-95b9-600420505f7e}|{2713b394-286f-4d7c-89ea-4174eeab9f5a}|{da7a20cf-bef4-4342-ad78-0240fdf87055})$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i101" id="{3a12052a-66ef-49db-8c39-e5b0bd5c83fa}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i107" id="{ABDE892B-13A8-4d1b-88E6-365A6E755758}" os="WINNT">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="15.0.5" severity="1"/>
+ </emItem>
+ <emItem blockID="ccebab59-7190-4258-8faa-a0b752dd5301" id="{8ab60777-e899-475d-9a4f-5f2ee02c7ea4}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1493" id="{de71f09a-3342-48c5-95c1-4b0f17567554}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.3.9" severity="3"/>
+ </emItem>
+ <emItem blockID="i453" id="/^brasilescape.*\@facebook\.com$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i816" id="noOpus@outlook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i1262" id="my7thfakeid@gmail.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i402" id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i451" id="{e44a1809-4d10-4ab8-b343-3326b64c7cdd}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i1056" id="{82AF8DCA-6DE9-405D-BD5E-43525BDAD38A}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="7.5.0.9082" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="43.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i350" id="sqlmoz@facebook.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i446" id="{E90FA778-C2B7-41D0-9FA9-3FEC1CA54D66}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="1"/>
+ </emItem>
+ <emItem blockID="i13" id="{E8E88AB0-7182-11DF-904E-6045E0D72085}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="i117" id="{ce7e73df-6a44-4028-8079-5927a588c948}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.0.8" severity="1"/>
+ </emItem>
+ <emItem blockID="i258" id="helperbar@helperbar.com">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="1.0" severity="1"/>
+ </emItem>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p416">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 6 Update 45" minVersion="Java 6 Update 42" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p184">
+ <match exp="Java\(TM\) Plug-in 1\.7\.0(_0?([0-9]|(1[0-1]))?)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p456">
+ <match exp="npvlc\.dll" name="filename"/>
+ <versionRange maxVersion="2.0.5" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p156">
+ <match exp="nppdf32\.dll" name="filename"/>
+ <versionRange maxVersion="9.5.1" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p158">
+ <match exp="nppdf32\.dll" name="filename"/>
+ <versionRange maxVersion="10.1.5.9999" minVersion="10.0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p956">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 8 Update 45" minVersion="Java 8 Update 45" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p32">
+ <match exp="npViewpoint.dll" name="filename"/>
+ <versionRange>
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p180">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 7 Update 10" minVersion="Java 7 Update 0" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p134">
+ <match exp="Java\(TM\) Platform SE 7 U[5-6](\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p592">
+ <match exp="CiscoWebCommunicator\.plugin" name="filename"/>
+ <versionRange maxVersion="3.0.5.99999999999999" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p248">
+ <match exp="Scorch\.plugin" name="filename"/>
+ <versionRange maxVersion="6.2.0b88" minVersion="0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1141">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 7 Update 97" minVersion="Java 7 Update 91" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1053">
+ <match exp="nprpplugin\.dll" name="filename"/>
+ <infoURL>https://real.com/</infoURL>
+ <versionRange maxVersion="17.0.10.7" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p94">
+ <match exp="Flash\ Player\.plugin" name="filename"/>
+ <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+ <versionRange maxVersion="10.2.159.1" minVersion="0" severity="0">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.0.1" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p31">
+ <match exp="NPMySrch.dll" name="filename"/>
+ </pluginItem>
+ <pluginItem blockID="p958">
+ <match exp="Java\(TM\) Platform SE 7 U(79|80)(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p254">
+ <match exp="PDF Browser Plugin\.plugin" name="filename"/>
+ <versionRange maxVersion="2.4.2" minVersion="0" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="18.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p102">
+ <match exp="npmozax\.dll" name="filename"/>
+ <versionRange maxVersion="*" minVersion="0"/>
+ </pluginItem>
+ <pluginItem blockID="33147281-45b2-487e-9fea-f66c6517252d">
+ <match exp="Java\(TM\) Platform SE 8 U(7[6-9]|[8-9]\d|1([0-3]\d|40))(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p80">
+ <match exp="\(TM\)" name="name"/>
+ <match exp="(npjp2\.dll)|(libnpjp2\.so)" name="filename"/>
+ <match exp="[^\d\._]((0(\.\d+(\.\d+([_\.]\d+)?)?)?)|(1\.(([0-5](\.\d+([_\.]\d+)?)?)|(6(\.0([_\.](0?\d|1\d|2\d|30))?)?)|(7(\.0([_\.][0-2])?)?))))([^\d\._]|$)" name="description"/>
+ <versionRange severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p182">
+ <match exp="Java\(TM\) Platform SE 7 U([0-9]|(1[0-1]))(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p332">
+ <match exp="libflashplayer\.so" name="filename"/>
+ <match exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$" name="description"/>
+ <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="19.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p332">
+ <match exp="libflashplayer\.so" name="filename"/>
+ <match exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$" name="description"/>
+ <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.0.*" minVersion="17.0.4"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="3f136e56-4c93-4619-8c0d-d86258c1065d">
+ <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
+ <infoURL>https://get.adobe.com/reader/</infoURL>
+ <versionRange maxVersion="15.006.30244" minVersion="15.006" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="427f5ec6-d1a7-4725-ac29-d5c5e51de537">
+ <match exp="Java\(TM\) Platform SE 7 U(97|98|99|1([0-4][0-9]|50))(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="43b45ad8-a373-42c1-89c6-64e2746885e5">
+ <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
+ <infoURL>https://get.adobe.com/reader/</infoURL>
+ <versionRange maxVersion="15.020.20042" minVersion="15.020" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p366">
+ <match exp="Scorch\.plugin" name="filename"/>
+ <versionRange maxVersion="6.2.0" minVersion="6.2.0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p123">
+ <match exp="JavaPlugin2_NPAPI\.plugin" name="filename"/>
+ <versionRange maxVersion="14.2.0" minVersion="0" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p89" os="Darwin">
+ <match exp="AdobePDFViewerNPAPI\.plugin" name="filename"/>
+ <versionRange maxVersion="10.1.3" minVersion="0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p904">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 8 Update 44" minVersion="Java 8" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1002">
+ <match exp="npUnity3D32\.dll" name="filename"/>
+ <versionRange maxVersion="5.0.3f1" minVersion="5.0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="49b843cc-a8fc-4ede-be0c-a0da56d0214f" os="Linux">
+ <match exp="libflashplayer\.so" name="filename"/>
+ <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+ <versionRange maxVersion="27.0.0.159" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p85">
+ <match exp="JavaPlugin2_NPAPI\.plugin" name="filename"/>
+ <versionRange maxVersion="13.6.0" minVersion="0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1246">
+ <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
+ <infoURL>https://get.adobe.com/reader</infoURL>
+ <versionRange maxVersion="11.0.16 " minVersion="10.1.6" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p408">
+ <match exp="QuickTime Plugin\.plugin" name="filename"/>
+ <versionRange maxVersion="7.6.5" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p214">
+ <match exp="Java\(TM\) Platform SE 7 U7(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p132">
+ <match exp="Java\(TM\) Plug-in 1\.7\.0(_0?([5-6]))?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p420">
+ <match exp="Java\(TM\) Platform SE 7 U(1[6-9]|2[0-4])(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="59c31ade-88d6-4b22-8601-5316f82e3977">
+ <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
+ <infoURL>https://get.adobe.com/reader/</infoURL>
+ <versionRange maxVersion="11.0.18" minVersion="11.0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1004">
+ <match exp="Unity Web Player\.plugin" name="filename"/>
+ <match exp="^($|Unity Web Player version 5.0(\.([0-2]|3f1))?[^0-9.])" name="description"/>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p459">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 7 Update 44" minVersion="Java 7 Update 25" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p296">
+ <match exp="Java\(TM\) Plug-in 1\.7\.0_1[2-5]([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p457">
+ <match exp="Java(\(TM\))? Plug-in ((1\.7\.0_(2[5-9]|3\d|4[0-4]))|(10\.4[0-4](\.[0-9]+)?))([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p1054">
+ <match exp="np32dsw_[0-9]+\.dll" name="filename"/>
+ <infoURL>https://get.adobe.com/shockwave/</infoURL>
+ <versionRange maxVersion="12.2.0.162" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p138">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 7 Update 06" minVersion="Java 7 Update 01" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p212">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 7 Update 07" minVersion="Java 7 Update 07" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p188">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 6 Update 38" minVersion="Java 6 Update 0" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p1142">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 8 Update 76" minVersion="Java 8 Update 64" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p154">
+ <match exp="npctrl\.dll" name="filename"/>
+ <versionRange maxVersion="5.1.20124.9999" minVersion="5.0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1061">
+ <match exp="Java\(TM\) Platform SE 7 U(8[1-9]|90)(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p26">
+ <match exp="^Yahoo Application State Plugin$" name="name"/>
+ <match exp="npYState.dll" name="filename"/>
+ <match exp="^Yahoo Application State Plugin$" name="description"/>
+ <versionRange>
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="3.*" minVersion="3.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p422">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 7 Update 24" minVersion="Java 7 Update 16" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p962">
+ <match exp="Java(\(TM\))? Plug-in 10\.(79|80)(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p902">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 7 Update 78" minVersion="Java 7 Update 45" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p125">
+ <match exp="Java\(TM\) Platform SE ((6( U(\d|([0-2]\d)|3[0-2]))?)|(7(\sU[0-4])?))(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p1059">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 7 Update 90" minVersion="Java 7 Update 81" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p908">
+ <match exp="Java\(TM\) Platform SE 8( U([1-3]?\d|4[0-4]))?(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p252" os="Darwin">
+ <match exp="AdobePDFViewerNPAPI\.plugin" name="filename"/>
+ <versionRange maxVersion="11.0.01" minVersion="11.0.0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="832dc9ff-3314-4df2-abcf-7bd65a645371">
+ <match exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" name="filename"/>
+ <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+ <versionRange maxVersion="27.0.0.159" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p186">
+ <match exp="Java\(TM\) Platform SE 6 U3[1-8](\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p572">
+ <match exp="npdjvu\.dll" name="filename"/>
+ <versionRange maxVersion="6.1.4.27993" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p210">
+ <match exp="Java\(TM\) Plug-in 1\.7\.0(_0?7)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p412">
+ <match exp="Java\(TM\) Plug-in 1\.6\.0_4[2-5]([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p34">
+ <match exp="[Nn][Pp][Jj][Pp][Ii]1[56]0_[0-9]+\.[Dd][Ll][Ll]" name="filename"/>
+ <versionRange>
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="3.6a1pre"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="97647cd8-03c5-416c-b9d3-cd5ef87ab39f">
+ <match exp="np32dsw_1227197\.dll" name="filename"/>
+ <versionRange maxVersion="12.2.7.197" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p960">
+ <match exp="Java\(TM\) Platform SE 8 U45(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p292">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 7 Update 15" minVersion="Java 7 Update 12" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p954">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 7 Update 80" minVersion="Java 7 Update 79" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p190">
+ <match exp="Java\(TM\) Plug-in 1\.6\.0_3[1-8]([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p1062">
+ <match exp="Java\(TM\) Platform SE 8 U(4[6-9]|5\d|6[0-4])(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="ab59635e-2e93-423a-9d57-871dde8ae675">
+ <match exp="Java(\(TM\))? Plug-in 11\.(7[6-9]|[8-9]\d|1([0-3]\d|40))(\.\d+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p129">
+ <match exp="Silverlight\.plugin" name="filename"/>
+ <versionRange maxVersion="5.0.99999" minVersion="0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p294">
+ <match exp="Java\(TM\) Platform SE 7 U1[2-5](\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p330">
+ <match exp="libflashplayer\.so" name="filename"/>
+ <match exp="^Shockwave Flash (([1-9]\.[0-9]+)|(10\.([0-2]|(3 r(([0-9][0-9]?)|1(([0-7][0-9])|8[0-2]))))))( |$)" name="description"/>
+ <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="19.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p330">
+ <match exp="libflashplayer\.so" name="filename"/>
+ <match exp="^Shockwave Flash (([1-9]\.[0-9]+)|(10\.([0-2]|(3 r(([0-9][0-9]?)|1(([0-7][0-9])|8[0-2]))))))( |$)" name="description"/>
+ <infoURL>https://get.adobe.com/flashplayer/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.0.*" minVersion="17.0.4"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p152">
+ <match exp="npctrl\.dll" name="filename"/>
+ <versionRange maxVersion="4.1.10328.0" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1060">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 8 Update 64" minVersion="Java 8 Update 46" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p33">
+ <match exp="[0-6]\.0\.[01]\d{2}\.\d+" name="name"/>
+ <match exp="npdeploytk.dll" name="filename"/>
+ <versionRange severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1144">
+ <match exp="Java\(TM\) Platform SE 8 U(6[4-9]|7[0-6])(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1063">
+ <match exp="Java(\(TM\))? Plug-in 10\.(8[1-9]|90)(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p556">
+ <match exp="npUnity3D32\.dll" name="filename"/>
+ <versionRange maxVersion="4.6.6f1" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1250">
+ <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
+ <infoURL>https://get.adobe.com/reader</infoURL>
+ <versionRange maxVersion="15.016.20045" minVersion="15.016.20045" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1151">
+ <match exp="npqtplugin\.dll" name="filename"/>
+ <infoURL>https://support.apple.com/en-us/HT205771</infoURL>
+ <versionRange maxVersion="*" minVersion="0" severity="0" vulnerabilitystatus="2"/>
+ </pluginItem>
+ <pluginItem blockID="p558">
+ <match exp="Unity Web Player\.plugin" name="filename"/>
+ <match exp="^($|Unity Web Player version ([0-3]|(4\.([0-5]|6(\.([0-5]|6f1)))?[^0-9.])))" name="description"/>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1146">
+ <match exp="Java(\(TM\))? Plug-in 11\.(6[4-9]|7[0-6])(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1143">
+ <match exp="Java\(TM\) Platform SE 7 U(9[1-7])(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p964">
+ <match exp="Java(\(TM\))? Plug-in 11\.45(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p250">
+ <match exp="npFoxitReaderPlugin\.dll" name="filename"/>
+ <versionRange maxVersion="2.2.1.530" minVersion="0" severity="0" vulnerabilitystatus="2"/>
+ </pluginItem>
+ <pluginItem blockID="p1052">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 7 Update 11" minVersion="Java 7 Update 11" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p574">
+ <match exp="NPDjVu\.plugin" name="filename"/>
+ <versionRange maxVersion="6.1.1" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p28">
+ <match exp="NPFFAddOn.dll" name="filename"/>
+ </pluginItem>
+ <pluginItem blockID="p302">
+ <match exp="Java\(TM\) Plug-in 1\.6\.0_(39|40|41)([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p242" os="Darwin">
+ <match exp="Flip4Mac" name="description"/>
+ <versionRange maxVersion="2.4.3.999" minVersion="0" severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="18.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p298">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="Java 6 Update 41" minVersion="Java 6 Update 39" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p458">
+ <match exp="Java\(TM\) Platform SE 7 U(2[5-9]|3\d|4[0-4])(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p119">
+ <match exp="Java\(TM\) Plug-in 1\.(6\.0_(\d|[0-2]\d?|3[0-2])|7\.0(_0?([1-4]))?)([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.*" minVersion="0.1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p428">
+ <match exp="np[dD]eployJava1\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="2"/>
+ </pluginItem>
+ <pluginItem blockID="p1145">
+ <match exp="Java(\(TM\))? Plug-in 10\.(9[1-7])(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1064">
+ <match exp="Java(\(TM\))? Plug-in 11\.(4[6-9]|5\d|6[0-4])(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p300">
+ <match exp="Java\(TM\) Platform SE 6 U(39|40|41)(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="d70fdf87-0441-479c-833f-2213b769eb40">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 7 Update 150" minVersion="Java 7 Update 97" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p240">
+ <match exp="DivXBrowserPlugin\.plugin" name="filename"/>
+ <versionRange maxVersion="1.4" minVersion="0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1055">
+ <match exp="DirectorShockwave\.plugin" name="filename"/>
+ <infoURL>https://get.adobe.com/shockwave/</infoURL>
+ <versionRange maxVersion="12.2.0.162" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p328">
+ <match exp="Silverlight\.plugin" name="filename"/>
+ <versionRange maxVersion="5.1.20124.9999" minVersion="5.1" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="19.0a1"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p328">
+ <match exp="Silverlight\.plugin" name="filename"/>
+ <versionRange maxVersion="5.1.20124.9999" minVersion="5.1" severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="17.0.*" minVersion="17.0.4"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p414">
+ <match exp="Java\(TM\) Platform SE 6 U4[2-5](\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p113">
+ <match exp="npuplaypc\.dll" name="filename"/>
+ <versionRange maxVersion="1.0.0.0" minVersion="0" severity="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1247">
+ <match exp="(nppdf32\.dll)|(AdobePDFViewerNPAPI\.plugin)" name="filename"/>
+ <infoURL>https://get.adobe.com/reader</infoURL>
+ <versionRange maxVersion="15.006.30174" minVersion="15.006.30174" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p418">
+ <match exp="Java\(TM\) Plug-in 1\.7\.0_(1[6-9]|2[0-4])([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+ <versionRange maxVersion="*" minVersion="17.0"/>
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p912">
+ <match exp="Java(\(TM\))? Plug-in 11\.(\d|[1-3]\d|4[0-4])(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p1120">
+ <match exp="(Silverlight\.plugin|npctrl\.dll)" name="filename"/>
+ <infoURL>https://www.microsoft.com/getsilverlight</infoURL>
+ <versionRange maxVersion="5.1.41105.0" minVersion="5.1.20125" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p594">
+ <match exp="npCiscoWebCommunicator\.dll" name="filename"/>
+ <versionRange maxVersion="3.0.5.99999999999999" minVersion="0" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="f24ffd6f-e02b-4cf4-91d9-d54cd793e4bf">
+ <match exp="JavaAppletPlugin\.plugin" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange maxVersion="Java 8 Update 140" minVersion="Java 8 Update" severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p906">
+ <match exp="Java\(TM\) Platform SE 7 U(4[5-9]|(5|6)\d|7[0-8])(\s[^\d\._U]|$)" name="name"/>
+ <match exp="npjp2\.dll" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="p910">
+ <match exp="Java(\(TM\))? Plug-in 10\.(4[5-9]|(5|6)\d|7[0-8])(\.[0-9]+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ <pluginItem blockID="fdc40de3-95ab-41a5-94cf-9b400221a713">
+ <match exp="Java(\(TM\))? Plug-in 10\.(97|98|99|1([0-4]\d|50))(\.\d+)?([^\d\._]|$)" name="name"/>
+ <match exp="libnpjp2\.so" name="filename"/>
+ <infoURL>https://java.com/</infoURL>
+ <versionRange severity="0" vulnerabilitystatus="1"/>
+ </pluginItem>
+ </pluginItems>
+ <gfxItems>
+ <gfxBlacklistEntry blockID="g35">
+ <os>WINNT 6.1</os>
+ <vendor>0x10de</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.17.12.5896</driverVersion>
+ <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x0a6c</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g194">
+ <os>WINNT 6.2</os>
+ <vendor>0x1022</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>9.10.8.0</driverVersion>
+ <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1070">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1872</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1073">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1994</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1216">
+ <os>WINNT 5.2</os>
+ <vendor>0x8086</vendor>
+ <feature>HARDWARE_VIDEO_DECODING</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>10.18.10.3947</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1220">
+ <os>WINNT 6.3</os>
+ <vendor>0x8086</vendor>
+ <feature>HARDWARE_VIDEO_DECODING</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>10.18.10.3947</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g234">
+ <os>Darwin 12</os>
+ <vendor>0x1002</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g204">
+ <os>Darwin 10</os>
+ <vendor>0x8086</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g144">
+ <os>All</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g511">
+ <os>WINNT 5.1</os>
+ <vendor>0x8086</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>6.14.10.5218</driverVersion>
+ <driverVersionComparator>LESS_THAN</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g230">
+ <os>Darwin 10</os>
+ <vendor>0x1002</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1215">
+ <os>WINNT 5.1</os>
+ <vendor>0x8086</vendor>
+ <feature>HARDWARE_VIDEO_DECODING</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>10.18.10.3947</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g192">
+ <os>WINNT 6.2</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>9.10.8.0</driverVersion>
+ <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1221">
+ <os>WINNT 10.0</os>
+ <vendor>0x8086</vendor>
+ <feature>HARDWARE_VIDEO_DECODING</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>10.18.10.3947</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g150">
+ <os>All</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g36">
+ <os>WINNT 6.1</os>
+ <vendor>0x10de</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.17.12.5896</driverVersion>
+ <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x0a6c</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1219">
+ <os>WINNT 6.2</os>
+ <vendor>0x8086</vendor>
+ <feature>HARDWARE_VIDEO_DECODING</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>10.18.10.3947</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g992">
+ <os>WINNT 8.1</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>15.201.1151.0</driverVersion>
+ <driverVersionComparator>LESS_THAN</driverVersionComparator>
+ <devices>
+ <device>0x6920</device>
+ <device>0x6921</device>
+ <device>0x6928</device>
+ <device>0x6929</device>
+ <device>0x692b</device>
+ <device>0x692f</device>
+ <device>0x6930</device>
+ <device>0x6938</device>
+ <device>0x6939</device>
+ <device>0x6900</device>
+ <device>0x6901</device>
+ <device>0x6902</device>
+ <device>0x6903</device>
+ <device>0x6907</device>
+ <device>0x7300</device>
+ <device>0x9870</device>
+ <device>0x9874</device>
+ <device>0x9875</device>
+ <device>0x9876</device>
+ <device>0x9877</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1218">
+ <os>WINNT 6.1</os>
+ <vendor>0x8086</vendor>
+ <feature>HARDWARE_VIDEO_DECODING</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>10.18.10.3947</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1124">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.2086</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g148">
+ <os>All</os>
+ <vendor>0x1022</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g232">
+ <os>Darwin 11</os>
+ <vendor>0x1002</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g206">
+ <os>Darwin 11</os>
+ <vendor>0x8086</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1069">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1855</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g280">
+ <os>WINNT 6.1</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ <devices>
+ <device>0x9802</device>
+ <device>0x9803</device>
+ <device>0x9803</device>
+ <device>0x9804</device>
+ <device>0x9805</device>
+ <device>0x9806</device>
+ <device>0x9807</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1217">
+ <os>WINNT 6.0</os>
+ <vendor>0x8086</vendor>
+ <feature>HARDWARE_VIDEO_DECODING</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>10.18.10.3947</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g202">
+ <os>Darwin 12</os>
+ <vendor>0x10de</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g208">
+ <os>Darwin 12</os>
+ <vendor>0x8086</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g200">
+ <os>Darwin 11</os>
+ <vendor>0x10de</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1068">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1851</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g278">
+ <os>WINNT 6.1</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ <devices>
+ <device>0x9802</device>
+ <device>0x9803</device>
+ <device>0x9803</device>
+ <device>0x9804</device>
+ <device>0x9805</device>
+ <device>0x9806</device>
+ <device>0x9807</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g198">
+ <os>Darwin 10</os>
+ <vendor>0x10de</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1071">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1883</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1072">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1892</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g146">
+ <os>All</os>
+ <vendor>0x1022</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1251">
+ <os>WINNT 5.1</os>
+ <vendor>0x8086</vendor>
+ <feature>WEBGL_ANGLE</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>6.14.10.5218</driverVersion>
+ <driverVersionComparator>LESS_THAN</driverVersionComparator>
+ <versionRange maxVersion="49.9"/>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g974">
+ <os>WINNT 10.0</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>15.201.1151.0</driverVersion>
+ <driverVersionComparator>LESS_THAN</driverVersionComparator>
+ <devices>
+ <device>0x6920</device>
+ <device>0x6921</device>
+ <device>0x6928</device>
+ <device>0x6929</device>
+ <device>0x692b</device>
+ <device>0x692f</device>
+ <device>0x6930</device>
+ <device>0x6938</device>
+ <device>0x6939</device>
+ <device>0x6900</device>
+ <device>0x6901</device>
+ <device>0x6902</device>
+ <device>0x6903</device>
+ <device>0x6907</device>
+ <device>0x7300</device>
+ <device>0x9870</device>
+ <device>0x9874</device>
+ <device>0x9875</device>
+ <device>0x9876</device>
+ <device>0x9877</device>
+ </devices>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g984">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.2413</driverVersion>
+ <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g37">
+ <os>WINNT 5.1</os>
+ <vendor>0x10de</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>7.0.0.0</driverVersion>
+ <driverVersionComparator>GREATER_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ </gfxItems>
+ <certItems>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>L79XLVO2ZmtAu7FAG8Wmzw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENBIEcx">
+ <serialNumber>ESDDtMgFFiaUfKo7HD9qImM7</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>TurPPI6eivtNeGYdM0ZWXQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==">
+ <serialNumber>Hwexgn/ZCJicZPcsIyI8zxQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Bydrxg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>a9/VeyVWrzFD7rM2PEHwQA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABJ/v3ZwA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MDIxCzAJBgNVBAYTAkNOMQ4wDAYDVQQKEwVDTk5JQzETMBEGA1UEAxMKQ05OSUMgUk9PVA==">
+ <serialNumber>STMAjg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==">
+ <serialNumber>AMs=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>AzL4tLuklekJ8lSh6VnRMSrk</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABFqoAZoI=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A+ly3y1rVP59k/MKfcE3DoEq</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9LtnM=</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>45KI4WIxyXfNrdtdj7C6</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB">
+ <serialNumber>BAAAAAABI54PryQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BJDHnthjoDRutxFRJPFnixbU</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CeFU2w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGcMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0x">
+ <serialNumber>APB/jQRgyP8Q</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>KjoVfZ3by6+pL8fssyfM6A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A5oET6WBWx72ColKf0txoWyR</serialNumber>
+ </certItem>
+ <certItem issuerName="MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=">
+ <serialNumber>NpsJHyt3o1U47AAgw3UNXA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>CLc=</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>eLumDUO40KwnecZLJxFM2A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH0xCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>eohOGeS5ZHJeptyBvCu/mQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>C2tQZWb2eoQD2XC3F5JSzg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=">
+ <serialNumber>fbsHfUkagQtznc3rtY1uDg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CqnbFQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A/99bZCzSpexYL5y6dSryDn3</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGKMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEmMCQGA1UECxMdQ29weXJpZ2h0IChjKSAyMDA1IFdJU2VLZXkgU0ExFjAUBgNVBAsTDUludGVybmF0aW9uYWwxKTAnBgNVBAMTIFdJU2VLZXkgQ2VydGlmeUlEIEFkdmFuY2VkIEcxIENB">
+ <serialNumber>WD1AyQAAAAAAJQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByfJhw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
+ <serialNumber>AJBQSPqrEvDE2Hz8xH39Low=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>IyIVazG4RE9AERkb+ekH8w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BydeGg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==">
+ <serialNumber>Bg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEmMCQGA1UEAwwdQ2VydGlub21pcyAtIEF1dG9yaXTDqSBSYWNpbmU=">
+ <serialNumber>GA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABHJRKMpA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABAJmPjfQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==">
+ <serialNumber>Er0moq4zwH8ke2pYafIKdg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEmMCQGA1UEAwwdQ2VydGlub21pcyAtIEF1dG9yaXTDqSBSYWNpbmU=">
+ <serialNumber>HQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByeLBg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==">
+ <serialNumber>Eg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Bydxog==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==">
+ <serialNumber>Pgyeh2mqlVzqI9hFntRbUQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>Gd/pPu+qLnXUdvP9sW73CQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>F5Bg6C237Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>Ai7cBJYqBE0I9NdyoZfRrw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMRUwEwYDVQQLEwxNaWNyb3NvZnQgSVQxHjAcBgNVBAMTFU1pY3Jvc29mdCBJVCBTU0wgU0hBMg==">
+ <serialNumber>WgAFElcDxFjoswSzjAABAAUSVw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>UKM/CNF2OvC4giYnAUG/Ag==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>LzVYePklc3vH3jkk0BZr9g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>BYOGvG32ukb1Yxj2oKoFyw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>P4sUnc++hlU/bXj0zSTlcQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkGA1UEAxMiQ09NT0RPIFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>AKrMYlJmUUin8FOM/0TJrmk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A7uy+rmTav6tDH4dRrsnvXGH</serialNumber>
+ </certItem>
+ <certItem issuerName="MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=">
+ <serialNumber>bqapwACCtKhVagTl7cEP7KFbM0E=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
+ <serialNumber>RUT1Gehd1KKYPfqOlgspoQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=">
+ <serialNumber>KuzHPJLdK5hNgJRo3R47Ag==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==">
+ <serialNumber>WJ2qHzWUqTk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz">
+ <serialNumber>ATE6Xw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>AQAAAAQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZR2VvVHJ1c3QgRFYgU1NMIFNIQTI1NiBDQQ==">
+ <serialNumber>OE4/d+p3YRzzcSl+kmZ8Mw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
+ <serialNumber>OOkLFZaa4CXGyJlLTIEjUQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byc85g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABRE7wRk4=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==">
+ <serialNumber>BFA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByeQ9g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>OYBKgxEHpW/8XGAGAlvJyMA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>bzTw0uq05TUYEGS98bh0Ww==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz">
+ <serialNumber>azAcTWL+ijs=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx">
+ <serialNumber>OUOBG6TE0Lr+uYYGxeVbHg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEfMB0GA1UEAxMWVVROLVVTRVJGaXJzdC1IYXJkd2FyZQ==">
+ <serialNumber>EEpERSryZFMagbsNw/WoWQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==">
+ <serialNumber>A4w=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RDQTEx">
+ <serialNumber>Aw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>TsaDDThhoyhX10SURO3NMg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>CSU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABHkSHlSo=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>QDi5sA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>X3iUdzxCEtOAKpiTLsqjBA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Bydp0g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>SrQ125q7UcLfxVKepx+lRg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABM6d3Z0s=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==">
+ <serialNumber>aBXsv0oU3xqh2xkUPOi8</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
+ <serialNumber>APt5i5rs4dIIQPwZdk9/ISc=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>CjM=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABGMG0Gmw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==">
+ <serialNumber>Xmo3AIW2VHeeJoR0o09RGQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>bf8hEJywo1lAp4UNcLl5Ew==</serialNumber>
+ </certItem>
+ <certItem issuerName="MG0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRswGQYDVQQLExJQcmltYXJ5IENsYXNzIDIgQ0ExJjAkBgNVBAMTHUdsb2JhbFNpZ24gUHJpbWFyeSBDbGFzcyAyIENB">
+ <serialNumber>BAAAAAABHkSl6Co=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEbMBkGA1UEAxMSR2VvVHJ1c3QgRFYgU1NMIENB">
+ <serialNumber>CWhp</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>YwslVqGwc9CHkaZkXNZ4xw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUaGF3dGUsIEluYy4xGzAZBgNVBAMTElRoYXd0ZSBTR0MgQ0EgLSBHMg==">
+ <serialNumber>cDggUYfwJ3A1YcdoeT6s4A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHjAcBgNVBAMTFXRoYXd0ZSBFViBTU0wgQ0EgLSBHMw==">
+ <serialNumber>CrTHPEE6AZSfI3jysin2bA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=">
+ <serialNumber>U3KGm6UTqJ/nsMyteiUa2g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>OIJdAvYxHmLb6YaaMmwmjg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xLTArBgNVBAMMJFN0YWF0IGRlciBOZWRlcmxhbmRlbiBCdXJnZXIgQ0EgLSBHMg==">
+ <serialNumber>ATE3ew==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==">
+ <serialNumber>BAAAAAABL07hTcY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>E5I2y6sIonl4a+TmlXc7fw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>VBSf+IncsTB3RZS4KFCJPQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="ME0xCzAJBgNVBAYTAk5MMRkwFwYDVQQKDBBEaWdpZGVudGl0eSBCLlYuMSMwIQYDVQQDDBpEaWdpZGVudGl0eSBCdXJnZXIgQ0EgLSBHMg==">
+ <serialNumber>DA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGcxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpGcmF1bmhvZmVyMSEwHwYDVQQLExhGcmF1bmhvZmVyIENvcnBvcmF0ZSBQS0kxIDAeBgNVBAMTF0ZyYXVuaG9mZXIgUm9vdCBDQSAyMDA3">
+ <serialNumber>YR0zGQAAAAAAAw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>dSBsq/te0hzZauKHgJ3EWg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHcxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEoMCYGA1UEAxMfU3ltYW50ZWMgQ2xhc3MgMyBFViBTU0wgQ0EgLSBHMg==">
+ <serialNumber>UVKsEezpGWOVQ4W9esstng==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGpMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYDVQQLEy8oYykgMjAwNiB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UEAxMWdGhhd3RlIFByaW1hcnkgUm9vdCBDQQ==">
+ <serialNumber>Ikdj3zYXXGsC/Afm9Tvx+g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MG0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRswGQYDVQQLExJQcmltYXJ5IENsYXNzIDMgQ0ExJjAkBgNVBAMTHUdsb2JhbFNpZ24gUHJpbWFyeSBDbGFzcyAzIENB">
+ <serialNumber>BAAAAAABHkSl6mw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>QDi5sQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABCUVQ9No=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==">
+ <serialNumber>U4P1tUoxl/XkztlVHdtdgw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=">
+ <serialNumber>AIQ8dLGqNIaxxMeg31W16Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>TqfXw+FkhxfVgE9GVMgjWQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>YRJNfMoc12IpmW+Enpv3Pdo=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byd/Tg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=">
+ <serialNumber>AjpW</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAlRXMRIwEAYDVQQKDAlUQUlXQU4tQ0ExEDAOBgNVBAsMB1Jvb3QgQ0ExKjAoBgNVBAMMIVRXQ0EgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>DL8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A8wZnhfuY6VIV1SwGsTGNR7L</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A+RCQYwhofmXM+/hxdyoUzkI</serialNumber>
+ </certItem>
+ <certItem issuerName="MEYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR8wHQYDVQQDExZHZW9UcnVzdCBTSEEyNTYgU1NMIENB">
+ <serialNumber>OUvvVscW0/NltofkmV9qmg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
+ <serialNumber>Cf0103tCm9oulH1QK0weTA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>EDQMI0tR4kSntv1O37N10g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==">
+ <serialNumber>WX89jn8yGZVvoKTD9jDfRQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx">
+ <serialNumber>U+1Y1QpJc0FOR5JdCJ01gQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==">
+ <serialNumber>ATk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESISuBo/wdW2tBztKmHdFCFz</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byd/UA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>NMpMcEnex3eXx4ohk9glcQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A7T0V6o47rgCKl3oUb7jF2Ph</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>F6QlB/yX+A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
+ <serialNumber>CgFBQgAAAUFcf/EVAAAAAg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>LizeWXFWP5pZPI/dLc+PVQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==">
+ <serialNumber>TrKEMhb2PKktH8lHg0AV5A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEkMCIGA1UEAxMbU3ltYW50ZWMgQ2xhc3MgMyBEU0EgU1NMIENB">
+ <serialNumber>AuhvPsYZfVP6UDsuyjeZ4Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABJ/ufRdg=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>FJl6tXgNpSg=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=">
+ <serialNumber>BAAAAAABIBnBjWg=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGcxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEeMBwGA1UEAxMVU3dpc3Njb20gUm9vdCBFViBDQSAy">
+ <serialNumber>AL691kTvkemG9UQNa6McQg8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>OeKv0wi+ATDxfQ6CWir1vA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>F5BhE0zbgQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>VLm3Xe60+1YgPpXCGtXLng==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>Qh/O5w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Bycfmw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
+ <serialNumber>VP3bQF/UdNfxq/UOypU1zQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==">
+ <serialNumber>BAAAAAABQaHhPSY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEZMBcGA1UEAxMQU2VjdXJlIEdsb2JhbCBDQQ==">
+ <serialNumber>QAAnEQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==">
+ <serialNumber>A4g=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A3ZQibPGSZ8nPVbuccaCvUfa</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABJZbEU4I=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BycfpA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BHT6CK6B569m/dd5dEluBOEd</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dD">
+ <serialNumber>Ew1ee9Jq7Q/Dig3ACF4V6Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>ORFgmCj072NjcJnrxOMfQA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>Aj/CJN2QWZAF25GXPXADOA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>WU+jmMhGAumhewqVKrZBmg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx">
+ <serialNumber>HxT1XSjIpzjMprp9Qu1gYQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==">
+ <serialNumber>BAAAAAABL07hUBg=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=">
+ <serialNumber>BAAAAAABL07hRxA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESDYXNBhF+dePFjojs7u2vj1</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>Cfk9qg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BydInw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>d8ToN4Dfs5RqD2yfAp12yQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>F7PAjw2k0dTX5escPnyVOBo=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>DoP7aSdEs/3y+o2Gj9zgWA==</serialNumber>
+ </certItem>
+ <certItem issuerName="ME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIgU2VjdXJlIFNlcnZlciBDQQ==">
+ <serialNumber>Aa8e+91erglSMgsk/mtVaA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH0xCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>fqRDfSf8haCEh2nWE6O+bA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByeBQg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABAPpuVh0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
+ <serialNumber>A/kVDQpE7c9h+WxlWQFzSQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byc68g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA3IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNA==">
+ <serialNumber>RmI44ARDVCUOgXNK9ACAbg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9LtnY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xCzAJBgNVBAYTAlRXMTAwLgYDVQQKDCdHb3Zlcm5tZW50IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=">
+ <serialNumber>APdCebq8ZyZr/T0luxlicNw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGsxCzAJBgNVBAYTAlVTMQ0wCwYDVQQKEwRWSVNBMS8wLQYDVQQLEyZWaXNhIEludGVybmF0aW9uYWwgU2VydmljZSBBc3NvY2lhdGlvbjEcMBoGA1UEAxMTVmlzYSBlQ29tbWVyY2UgUm9vdA==">
+ <serialNumber>B2VhZAPxCDH3s9Mkbu3HfQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==">
+ <serialNumber>OfJBIhFwAdQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>YUlF+VXF2FWFqCo472HfZlw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A7GX+szdK8/7Kf0xUuarfyIN</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==">
+ <serialNumber>BAAAAAABF2Tb8Bc=</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>IA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9LtnE=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>UT6GtTGbEC6SXJteWAKy2g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>P6G7IYSL2RZxtzTh8I6qPA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=">
+ <serialNumber>TasC8Zd8BT8kXEE67cFQmA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGcxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpGcmF1bmhvZmVyMSEwHwYDVQQLExhGcmF1bmhvZmVyIENvcnBvcmF0ZSBQS0kxIDAeBgNVBAMTF0ZyYXVuaG9mZXIgUm9vdCBDQSAyMDA3">
+ <serialNumber>YR3YYQAAAAAABA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9LtnQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByfNeA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>VIFPnH3Io2OmF0J5KK8gzA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==">
+ <serialNumber>D/VlGqmz9Nai1ywCydT/RQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>GTPOETOFf5mIsbuzrojGfw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=">
+ <serialNumber>M64Z5ufZzDRVTHkJR1uXzw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byd/Tw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>WfPUsnnSF04ShWVYEa/KRA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>XbPH0u4MjoIrWzN8QCilfg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>LdbnCbsA9sOgI4mkUpWXPw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==">
+ <serialNumber>AINVG9I4T2jgQgW4N9SNhw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==">
+ <serialNumber>e7wSpVxmgAS5/ioLi2iBIA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byemag==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>AxPlMqxkByCn3XNuYMhYNMcp</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA3IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNA==">
+ <serialNumber>cXXMzbWDHMIdCotb3h64yw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>E/YGRk12iZqZuMfsIiVaeg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Bye2Cg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBSb290IENB">
+ <serialNumber>AJiWmg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==">
+ <serialNumber>BAAAAAABJpQ0AbA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHcxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEoMCYGA1UEAxMfU3ltYW50ZWMgQ2xhc3MgMyBFViBTU0wgQ0EgLSBHMw==">
+ <serialNumber>acI1CFIgmwSFBoU5+ahDgg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABL07hXdQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>CuUEKEJM4xhxlFXraPcSpQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
+ <serialNumber>CgFBQQAAATjkOB1sAAAAAg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>QOu0a5Z9rCkw6Nk7Rg1/AQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByfNbw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>ElBUYv/f+6+gnbAJ23qnAA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3Q=">
+ <serialNumber>a9rf7/BmG9JkKvRuy7J5QA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>QM1zZ4GZ4gfwpQtUYye3Ne0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>XOZMbPKQuJEw8Ib5neDVpQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=">
+ <serialNumber>M0VSOewW3WI=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CcHC/g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>Xbevr3ut3Z9m1GuXC9SonA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABKUXDqxw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A3TWA5Aylxw0x8bVvrmUSNJd</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A3UNTBOHUkbq+k999nJeSJdF</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==">
+ <serialNumber>QZBvapTZFvmYktEPsBYLQQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>fMTRbGCp280pnyE/u53zbA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy">
+ <serialNumber>ANX8SnNRxCmsE/GCl5hw+8A=</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>RFlmmjulj6Ve7PfBi44nnw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=">
+ <serialNumber>XJ8pGvGNM9RIcLUG9YQjLQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>DYifRdP6aQQ8MLbXZY2f5g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CqL7CA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAMTFHRoYXd0ZSBTSEEyNTYgU1NMIENB">
+ <serialNumber>UKKK5ol/rKBZchAAOnZjaA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>KUZMXOUj2sdY2i2Rfgp/5Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABLM/7qjk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>HNo1DR4XCe4mS1iUMsY6Wg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy">
+ <serialNumber>AeNmeF8oVpDp/4GPvA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>UN78HLEKf7W9vQYkzYpJnw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==">
+ <serialNumber>UdNjvA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=">
+ <serialNumber>Os2rnHWYhryvdOXfgan06A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH8xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEwMC4GA1UEAxMnU3ltYW50ZWMgQ2xhc3MgMyBFQ0MgMjU2IGJpdCBFViBDQSAtIEcy">
+ <serialNumber>OhrtngFwotLcm4i+z00SjA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==">
+ <serialNumber>ANU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEcxCzAJBgNVBAYTAkhLMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMQ==">
+ <serialNumber>BGU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>fZ10MyCe51jAjZCsDgqaxA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CcL+EA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABJQcQRNU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>HVRikKXRQ1ouhOpYcOna/A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp">
+ <serialNumber>TA5iEg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUaGF3dGUsIEluYy4xGzAZBgNVBAMTElRoYXd0ZSBTR0MgQ0EgLSBHMg==">
+ <serialNumber>e0bEFhI16xx9U1yvlI56rA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=">
+ <serialNumber>JGKKnm00uOQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>L1fHogsVxmfMBka5q4uzaQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>HA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
+ <serialNumber>AJiU+bpWh2Uc4xFRf8GM9yA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>UV9aaDeNRNtQuXjRYk4Skhg=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=">
+ <serialNumber>RdHgEmEIjdyRFWDRRlk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>JjjcXrfGjTCi1ug/AEeYlg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==">
+ <serialNumber>AQw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==">
+ <serialNumber>JLLEdDl2iHqqyenVWwQ/XA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>FJl6tXgNpSk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>TbPyD9NnsEcxyK6LIsr78g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAAA+X/GIyk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESDItX4ruWiLnrlz0rk4/bmz</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9LtnI=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABJ/ufQg8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>QZCrvQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABMrS7t2g=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>QjiuX0y1agXQQqmDB2yh3w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy">
+ <serialNumber>LTRcDHabRHU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFExCzAJBgNVBAYTAkpQMRMwEQYDVQQKEwpGdWppIFhlcm94MS0wKwYDVQQDEyRGdWppIFhlcm94IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IDI=">
+ <serialNumber>AUa47POQ1dN5</serialNumber>
+ </certItem>
+ <certItem issuerName="MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=">
+ <serialNumber>JLiDzgpL7oFNgJN+jIjt7w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>Mq0P6o03FDk0B2bnJ+mYPGo=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESAyW/JX3+hZIp44EAMlXU2b</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Bydvrw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==">
+ <serialNumber>Aw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>UfM8pWkcmmLGRiGIVydmoA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9Ltms=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>AyYMguSo1my449OZq51C3s3Z</serialNumber>
+ </certItem>
+ <certItem issuerName="MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>Qh/QbQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==">
+ <serialNumber>B8f7CHJUqV3VareLPE+2kA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFIxCzAJBgNVBAYTAk5MMRkwFwYDVQQKDBBEaWdpZGVudGl0eSBCLlYuMSgwJgYDVQQDDB9EaWdpZGVudGl0eSBPcmdhbmlzYXRpZSBDQSAtIEcy">
+ <serialNumber>DA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy">
+ <serialNumber>ZECgRdZEsns=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>AQAAAAM=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>UUFV3S2cUidOOv7ESN65Ng==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Bydr0Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>GdXz4L1b6FKNCMG9Jz2tjA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABMxvC9bk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>EkoaKijVTGVYI5c604iweg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==">
+ <serialNumber>TAA2G+UIK6mqznQKBT77NA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=">
+ <serialNumber>RvCM2iRdkCE82ZOO2dU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>GskXrIFkzLS+4yohQM9EUA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESCyHU+xOECnh9Rf2IvgR8zS</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESByNJZ5TPjg9iZyL6a/h5Zx</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGRMQswCQYDVQQGEwJERTEQMA4GA1UECgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaVjAxOjA4BgNVBAsMMUNvcHlyaWdodCAoQykgU2llbWVucyBBRyAyMDExIEFsbCBSaWdodHMgUmVzZXJ2ZWQxITAfBgNVBAMMGFNpZW1lbnMgSW50ZXJuZXQgQ0EgVjEuMA==">
+ <serialNumber>AaoZYg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>DNHqTQd9QC+JnMy6AWyhkg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGExCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxIDAeBgNVBAsTF0lkZW5UcnVzdCBQdWJsaWMgU2VjdG9yMRwwGgYDVQQDExNJZGVuVHJ1c3QgQUNFUyBDQSAx">
+ <serialNumber>fwAAAQAAAUrz/HmrAAAAAg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=">
+ <serialNumber>BAAAAAABHkSl5AQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByemaQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xCzAJBgNVBAYTAlRXMTAwLgYDVQQKDCdHb3Zlcm5tZW50IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=">
+ <serialNumber>K1ftto7Xcb0YKwQ6uMvOIA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=">
+ <serialNumber>AIZ6Wq/4deFQzwC6NnFpUA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG0MQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xLTArBgNVBAsTJGh0dHA6Ly9jZXJ0cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzEzMDEGA1UEAxMqR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcy">
+ <serialNumber>AOfHzdPzlvw5</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==">
+ <serialNumber>BA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>L7tgs/W85vnhV7I7qJ6N/g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>Qh/SqA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byd/UQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>A9GPKQ8jv9oIxfwiOy7qxQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>GtXUVojhwOTkaQ4bTKblEQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABIg08FMU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byd/Ug==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BOPwjyn5eqfeoxs7Z0y3vqNN</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>VNb2Hjai/t7dmCtOzRXXew==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BKobzjrOxa/6kCR0ImKoqaQW</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>HGD2RtvXMaPDqHIPLdXocw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==">
+ <serialNumber>ARQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>AQAAAAA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>BA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>COwoDFvz7GD8R2K7Lo0rYQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>VN2yeFexyXjPf34fHGmbhg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=">
+ <serialNumber>R4af5A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABLF5/HXY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=">
+ <serialNumber>Hnms0W0OxHSYE2F0XE97sw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABBHYoIFs=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEcxCzAJBgNVBAYTAkhLMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMQ==">
+ <serialNumber>BUE=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UEAxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls">
+ <serialNumber>D/wZ7+m1Mv8SONSEFcs73w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byeaqw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==">
+ <serialNumber>Ermwxw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==">
+ <serialNumber>em/HTY01Cvv6ITgkH+ftlg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENBIEcx">
+ <serialNumber>ESBrHE7sFC7CQ8EM681xA3CY</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=">
+ <serialNumber>Rea7UUYH3jl33BryPIo=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>eR1nUEz8k+nDSBD+bb5uIQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==">
+ <serialNumber>Ermwtg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>LAVIFm0MWZYH+Sv8Vf+IqkM=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>RkNUwM80Jt7beb4ek+iI8w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDIxCzAJBgNVBAYTAkNOMQ4wDAYDVQQKEwVDTk5JQzETMBEGA1UEAxMKQ05OSUMgUk9PVA==">
+ <serialNumber>STMAeg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>Byd5cg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==">
+ <serialNumber>NvEJoRYL2yvAZrAjbDIipQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>QDi5rw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>bAOrKSMsmA0MLJyAJ5BRsUM=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=">
+ <serialNumber>MABJTA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==">
+ <serialNumber>AklaZYwhC9k=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BydCwg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BOc11keA9WJ9R20XQY8hO7yi</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMRUwEwYDVQQLEwxNaWNyb3NvZnQgSVQxHjAcBgNVBAMTFU1pY3Jvc29mdCBJVCBTU0wgU0hBMg==">
+ <serialNumber>WgAFElbyxxPA8BdM4gABAAUSVg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFIxCzAJBgNVBAYTAk5MMRkwFwYDVQQKDBBEaWdpZGVudGl0eSBCLlYuMSgwJgYDVQQDDB9EaWdpZGVudGl0eSBPcmdhbmlzYXRpZSBDQSAtIEcy">
+ <serialNumber>Cw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
+ <serialNumber>ANUANvVYN7xqAISA9rvJPzQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGcxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEeMBwGA1UEAxMVU3dpc3Njb20gUm9vdCBFViBDQSAy">
+ <serialNumber>QFLH3Zrq+I5WQ6TlWzfUxA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>XLhHIg7vP+tWfRqvuKeAxw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTE=">
+ <serialNumber>AOVojQRgyPca</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABJQdAjik=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xGzAZBgNVBAMTEnRoYXd0ZSBTU0wgQ0EgLSBHMg==">
+ <serialNumber>JpUvYJyWjdGmeoH7YcYunw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp">
+ <serialNumber>TA6EVg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByeekA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABHJRKNmk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CeagHQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>RH7WhshwXRK6f0VfOfjXgQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABJQcQQN0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>OnvXX72mvUI2Id/NMzegmg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=">
+ <serialNumber>AjqK</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>DAk9hy8DhHSo+aQetvPB/fY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESD9YhzIEOwiOT7Nwip+E1KI</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=">
+ <serialNumber>BXA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>By7fBTreouRwX/qrpgSUsg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>CSY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>LJ8wKbrQXgT8VExZ6vEfWA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>F5Bg/C8eXg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>J2La+q+JOURNWkX60OP2lQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=">
+ <serialNumber>R/j2qA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=">
+ <serialNumber>AIQ8dLGqNIaxxMeg31W16Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A/7DHCczBnP5qUVh0jF2pvwB</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9Ltmw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290">
+ <serialNumber>DA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CdWFNw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABElatX7I=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>X4C5SJIG0BDeJvpQq4ngCw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESC8DawWRiAyEMd38UXbfgPR</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==">
+ <serialNumber>Ah69dEvrzT4=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==">
+ <serialNumber>BAAAAAABK84ykc0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABHkSHki0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz">
+ <serialNumber>e9JTGBe45yw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>AQAAAAI=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>O2S99lVUxErLSk56GvWRv+E=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>ODTGURr0vY14WkIt15hHrg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==">
+ <serialNumber>OqQ2rV0ISTc308Z/oQgzFw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4">
+ <serialNumber>RXJFI0h6EJY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>Aw1SPC56593ZCZ9vCNHKwQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BycpYA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>FK+rVRFA0o0PnW+X6V60gQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>RMgdRGEBv0KzFCjgGFp0Hg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx">
+ <serialNumber>JD1wxDd8IgmiqX7MyPPg1g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==">
+ <serialNumber>BAAAAAABQaHhNLo=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>AwBGo0Zmp6KRryAguuMvXATI</serialNumber>
+ </certItem>
+ <certItem issuerName="MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=">
+ <serialNumber>AJk3QFH13eHUHHVnsvwS0Vo=</serialNumber>
+ </certItem>
+ <certItem issuerName="MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=">
+ <serialNumber>U3t2Vk8pfxTcaUPpIq0seQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
+ <serialNumber>bx/XHJqcwxDOptxJ2lh5vw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=">
+ <serialNumber>LU4d0t7PAsZNgJGZcb+o/w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>c0ENPRDbRozjU83garZrdA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>LnfcUaXG/pxV2CpXM5+YSg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>EgtJ1f+/tZwlGfg0Uu7XCQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>sPNcCSE9Nkg3jy5IN1xe2Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==">
+ <serialNumber>BAAAAAABCfhiO+s=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=">
+ <serialNumber>Ajp+</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>RbG+tfPUe/vBRfTZF54i8g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGwMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBFbnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=">
+ <serialNumber>cFbYT3bxd1sAAAAAUdNX8A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZR2VvVHJ1c3QgRFYgU1NMIFNIQTI1NiBDQQ==">
+ <serialNumber>ZgwfEqZnBsUNvNuZ77FbQA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>Iqpyf/YoGgvHc8HiDAxAI8o=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOjA4BgNVBAMMMVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBQZXJzb29uIENBIC0gRzM=">
+ <serialNumber>f43O9TualR8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==">
+ <serialNumber>OgxXyntHYBXnPAHDxY0OXg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BOIIipysxAz5xHIMmFRvYchY</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3Q=">
+ <serialNumber>Jq6jgeApiT9O4W2Tx/NTRQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=">
+ <serialNumber>SeEzbpTltqUtqW7UiuJ2</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>Iw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=">
+ <serialNumber>ANygrItIJ2rcKlyS3Lue07U=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==">
+ <serialNumber>ARU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKjAoBgNVBAMTIVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPdmVyaGVpZCBDQQ==">
+ <serialNumber>ATFpsA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>IHj3eiEK3K1Xrpu1uvtBuvE=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
+ <serialNumber>cpqpXVWPk5AXzGw+zNIcBw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESCEUbthDurBjJw0/h/FfuNY</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx">
+ <serialNumber>AI7cApIcPA3cfSpQMf40onQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==">
+ <serialNumber>BAAAAAABQaHhOT4=</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>VUtahOwvvmJFwlvmGDZP5w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9Ltm0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=">
+ <serialNumber>BAAAAAABFUtaxac=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>MWzraR3LLhU9m/qKEhvVLQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>KvQ5AzK6tQy8eBy7NAD/lQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==">
+ <serialNumber>APiyCXmwAUq+95DYa3DmGw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>Ax6Jm7ajV49tqHgf9nYnzRCI</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>AygWP2Fgd2T+iLbmAlKT6g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>dUIqmrcgq/261bRbo7fM1g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>BWuckD4dPHZYW5ThBsl+aQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BydSYg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByfHkw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3Q=">
+ <serialNumber>CMNfzETd7XxesS9FOUj9Mg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==">
+ <serialNumber>VfTSum25nb65YPlpuhJAvg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==">
+ <serialNumber>RqFXxGPuA18=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>UW3oKZKTDsrPy/rfwmGNaQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAlRXMRIwEAYDVQQKDAlUQUlXQU4tQ0ExEDAOBgNVBAsMB1Jvb3QgQ0ExKjAoBgNVBAMMIVRXQ0EgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>QAEy3RIAAAAAAAAMweH5dw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>E77H6yvyFQjO0PcN3x0H+Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A3WVy2V+2VFkWtMvA6HFwnhq</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>B+U=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>KRfQOuBdOSpEmAxSpDZGZg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>Cbssdw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>JV/LVzSKI/wsDgg3UuZHlA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9LtnA=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BOncXh7IZp1SNydhtUdyh2O2</serialNumber>
+ </certItem>
+ <certItem issuerName="MDMxCzAJBgNVBAYTAlBUMQ0wCwYDVQQKDARTQ0VFMRUwEwYDVQQDDAxFQ1JhaXpFc3RhZG8=">
+ <serialNumber>cx0HrIEQg8JHWTP7DzOxSQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A8aDg1/IA4O8gjMPZHVqPI+w</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>Fw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>Gz4uHrL2usrTZrPCHeuF5g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==">
+ <serialNumber>UWMOvf4tj/x5cQN2PXVSww==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=">
+ <serialNumber>NlLRZJFLco/An3cLAGjGgQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>e/fIfg2Dj2tkYIWVu2r82Cc=</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>UMUwXwT1Z4juyQ/CNTf4mw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>d8AtKymQwkOPDBj+hjPzFg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>fa9agGguMHfBorMTXXMd9g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>IIxFSyNM6mWtCgTG0IL3Og==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=">
+ <serialNumber>Ajp/</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>RVWTeb5EKqE7cy7MUD2oJ3M=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGQMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE2MDQGA1UEAxMtQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB">
+ <serialNumber>UoRGnb96CUDTxIqVry6LBg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>F5Bg+EziQQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy">
+ <serialNumber>ATE0vw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTI=">
+ <serialNumber>APB/jQRgyPca</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEZMBcGA1UEAxMQU2VjdXJlIEdsb2JhbCBDQQ==">
+ <serialNumber>TXxtAQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CskruA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>Ig==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHsxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEsMCoGA1UEAxMjU3ltYW50ZWMgQ2xhc3MgMyBFQ0MgMjU2IGJpdCBTU0wgQ0E=">
+ <serialNumber>U3SgRR3J+D6575WuCxuXeQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>Cd/dug==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGRMQswCQYDVQQGEwJERTEQMA4GA1UECgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaVjAxOjA4BgNVBAsMMUNvcHlyaWdodCAoQykgU2llbWVucyBBRyAyMDExIEFsbCBSaWdodHMgUmVzZXJ2ZWQxITAfBgNVBAMMGFNpZW1lbnMgSW50ZXJuZXQgQ0EgVjEuMA==">
+ <serialNumber>WW8OCQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==">
+ <serialNumber>SdegFrLaFTCsoMAW5ED+zA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>EAdmaA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=">
+ <serialNumber>CgFBQQAAATjtdPY5AAAAAg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BLlQHJ611eOZuedFrFgVAfAs</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==">
+ <serialNumber>EQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABGMGjftY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A1V4dX0tTb1rdTZxdWcuZ7YR</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTE=">
+ <serialNumber>AOVojQRgyPcY</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABHkSHjz8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>BUrYjru5px1ym4QUN33TOQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=">
+ <serialNumber>RurwlgVMxeP6Zepun0LGZA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>SurdtfsuPcXXDpY2LkBpYO6BT7o=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==">
+ <serialNumber>ALxyZmb/WL/wAuUiPK5oK/g=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>AxW0+uDsfyCSfhECdsGGpVD8</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BydIoA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=">
+ <serialNumber>dhjnNtYx6cojdAE55TgIBA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A9BRwOwbXRRhCe+kcmglgW3z</serialNumber>
+ </certItem>
+ <certItem issuerName="MEcxCzAJBgNVBAYTAkhLMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMQ==">
+ <serialNumber>BHk=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>EYfoVrySx7V3OUqs4xKvgA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGQMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxOzA5BgNVBAMTMkFyaXN0b3RsZSBVbml2ZXJzaXR5IG9mIFRoZXNzYWxvbmlraSBDZW50cmFsIENBIFI0">
+ <serialNumber>EqthLKdUgwI=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEdMBsGA1UEAxMUQ2VydGlub21pcyAtIFJvb3QgQ0E=">
+ <serialNumber>J8mznxvTvOR5p4Br3a3sm5j5iM0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx">
+ <serialNumber>a9HDb1beqQYmkvFH0qExcg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BydKkg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp">
+ <serialNumber>OGPFrg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xGzAZBgNVBAMTEnRoYXd0ZSBTU0wgQ0EgLSBHMg==">
+ <serialNumber>FNISyWWTGi5Yco6fGh58/A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABA/A35EU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==">
+ <serialNumber>QZCrvA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABEAuMoRs=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A0BOaf9UbJxzqBudSyes/cEM</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==">
+ <serialNumber>BQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>NTgf4iwIfeyJPIomw2dwSXEwtxQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=">
+ <serialNumber>AjqL</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESDu2nhlLPzfx+LYgjlYFP/k</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==">
+ <serialNumber>buROL/l2GuXISv+/JVLkdA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>CqZgEvHAsnzkT//QV9KjXw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExGjAYBgNVBAoTEUludGVsIENvcnBvcmF0aW9uMSUwIwYDVQQDExxJbnRlbCBFeHRlcm5hbCBJc3N1aW5nIENBIDZC">
+ <serialNumber>HwAABsvzDP+DIzUG6QAAAAAGyw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>HZyLf+K70FKc+jomm8DiDw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>Ew==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>IARKrBjlKQLyVGA4X52L7w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=">
+ <serialNumber>BAAAAAABKUXDqA8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>Qh/SnQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BDV89QWZE9MJYlCpFQUv5Y2W</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEfMB0GA1UEAxMWVVROLVVTRVJGaXJzdC1IYXJkd2FyZQ==">
+ <serialNumber>Xrr31RF0DoIzMKXS6XtD+g==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5">
+ <serialNumber>D4dSwi4udjGtMftKLTSFyg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==">
+ <serialNumber>BAAAAAABCFiEp9s=</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>PAdKZPiaac2CvPxbOrsHOw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
+ <serialNumber>GuJ0aGBYhChXAOljooJZ3A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BPVqx4UbKVAbJSFTKwrcFryU</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQKEwVUYWlDQTESMBAGA1UECxMJUG9saWN5IENBMSQwIgYDVQQDExtUYWlDQSBJbmZvcm1hdGlvbiBQb2xpY3kgQ0E=">
+ <serialNumber>UbQGvw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>AQAAAAU=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>Cfk9lw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy">
+ <serialNumber>AIChpbGNqu4XKp9J70syKEs=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=">
+ <serialNumber>cJ+vg4742XhNgJW2ot9eIg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4">
+ <serialNumber>N4XreFRrqFQ=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>Cyr1PA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>Gg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BKrxi2/1iFxHEFzyZvegxq5C</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzQ=">
+ <serialNumber>H08=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==">
+ <serialNumber>BAAAAAABKB/OGqI=</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>Rvm2CEw2IC2Mu/ax0A46QQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByfFnw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>F5BhENPfVw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9Ltm4=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESJJweWBPhoXAaB9c8SHwI4O</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>VOcIuNbTqkpOMUyI108FOg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==">
+ <serialNumber>RnQ3dYovwvB0D5q2YGY=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>CGo/+42e75JBJ2JcOEaMFw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGMxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEmMCQGA1UEAwwdQ2VydGlub21pcyAtIEF1dG9yaXTDqSBSYWNpbmU=">
+ <serialNumber>Eg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>UDE/uwr4z5V8eZI4+1gkAw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>ezdAeCxKH7BFs7vn3byYaw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==">
+ <serialNumber>AZ0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>CcHC1w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BEeJFwO0nu759EPo9tKluw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==">
+ <serialNumber>BAAAAAABK84yjs8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>DjIvBkX+ECVbB/C3i6w2Gg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESCC9oPNcRdPOox+SjWm9dTX</serialNumber>
+ </certItem>
+ <certItem issuerName="MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==">
+ <serialNumber>O2Qh+qhbBRuZA11yDhcLGQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
+ <serialNumber>UU3AP1SMxmyhBFq7MRFZmf0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==">
+ <serialNumber>BAAAAAABHhw1vwc=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABIg08D3U=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A8LV4zckxcwdttbQSk0EPnoA</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGQMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE2MDQGA1UEAxMtQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB">
+ <serialNumber>D9UltDPl4XVfSSqQOvdiwQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==">
+ <serialNumber>Ermw0Q==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>fWK0j/Vi8vNWg3VAGjc02w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESCLRVuhcUZaluIgIVlRJx+O</serialNumber>
+ </certItem>
+ <certItem issuerName="MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz">
+ <serialNumber>ATE5Ig==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>Aw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=">
+ <serialNumber>BAAAAAABL07hSVI=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>Q1r0dRkkG9miuHj/Y52izw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>YNOos6YJoPC77qwSGCpb7w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>DHmmaw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDIxCzAJBgNVBAYTAkNOMQ4wDAYDVQQKEwVDTk5JQzETMBEGA1UEAxMKQ05OSUMgUk9PVA==">
+ <serialNumber>STMAFQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>Nbc68Q8EHza72P/hSWcddw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>Bw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+ <serialNumber>dItWlz2V62Philqj9m6Pbg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDcxJDAiBgNVBAMTG1JDUyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEPMA0GA1UEChMGSFQgc3Js">
+ <serialNumber>AN9bfYOvlR1t</serialNumber>
+ </certItem>
+ <certItem issuerName="MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==">
+ <serialNumber>EA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey">
+ <serialNumber>frj5jTuqBnQ4fljPvVU3KA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>PbXdgANzAyCOCZ5qa/7E6A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByembA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz">
+ <serialNumber>LYTXWk7gMu8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKjAoBgNVBAMTIVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPdmVyaGVpZCBDQQ==">
+ <serialNumber>ATFEdg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==">
+ <serialNumber>RnQ3dg5KdDZs0nyFZk4=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>BwImeaRkSZQLYwFREwKo3R1Jn+8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
+ <serialNumber>PmDn14AwWY28IlJeBXkDvA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>BONHqLIx/ibQE08IQIyoGaXg</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESCis569omrbb20yySF39+aE</serialNumber>
+ </certItem>
+ <certItem issuerName="MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz">
+ <serialNumber>ATE6YA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=">
+ <serialNumber>XhcFm2g619rt8Sro+a4rHA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABL07hW2M=</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESBqoILo90ntDW7OTK43MS2F</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+ <serialNumber>BYyEX2b5+K+myAIR7eXaRQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>A7RCxMe1S9Hb7ENzRxl0mxGP</serialNumber>
+ </certItem>
+ <certItem issuerName="MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=">
+ <serialNumber>BAAAAAABLF5/Gog=</serialNumber>
+ </certItem>
+ <certItem issuerName="MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp">
+ <serialNumber>TA6BjA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu">
+ <serialNumber>BAAAAAABMYnGRuw=</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>a2GKnRbYMZ0oZkRzJE8NIw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290">
+ <serialNumber>Eg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB">
+ <serialNumber>G8sz+bm+vQjTpQNBh5CfMg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=">
+ <serialNumber>EM8bDLBnnoYe4LnWpLIhS4esr3I=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=">
+ <serialNumber>Cfk9oA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+ <serialNumber>Sx51x7V8pYe8rp7PMP/3qg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h">
+ <serialNumber>FQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB">
+ <serialNumber>BAAAAAABHkSl7L4=</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>BydiAg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=">
+ <serialNumber>Cj0=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==">
+ <serialNumber>AyjNQ4dnGD3FD6WL5gYrYru7</serialNumber>
+ </certItem>
+ <certItem issuerName="MG0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRswGQYDVQQLExJQcmltYXJ5IENsYXNzIDEgQ0ExJjAkBgNVBAMTHUdsb2JhbFNpZ24gUHJpbWFyeSBDbGFzcyAxIENB">
+ <serialNumber>BAAAAAABHkSl5ao=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=">
+ <serialNumber>MABJSw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESCVop+Q4/OBgtf4WJkr01Gh</serialNumber>
+ </certItem>
+ <certItem issuerName="MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=">
+ <serialNumber>eViJ2GX26lp5HbF+XNp1kQ==</serialNumber>
+ </certItem>
+ <certItem issuerName="MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=">
+ <serialNumber>ByfDtA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=">
+ <serialNumber>GN2Hrh9Ltm8=</serialNumber>
+ </certItem>
+ <certItem issuerName="MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==">
+ <serialNumber>ANsAyDuSSs7Z83LfMZ+TDw==</serialNumber>
+ </certItem>
+ <certItem issuerName="MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB">
+ <serialNumber>ESByYNtAIfizf2L3NMzCH8zZ</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>QspbHxzWb41SX9TUhF1N1A==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx">
+ <serialNumber>KNhgX8XuJduYciIyatpOQg==</serialNumber>
+ </certItem>
+ <certItem issuerName="MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==">
+ <serialNumber>AImQERVYPoeb</serialNumber>
+ </certItem>
+ <certItem issuerName="MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy">
+ <serialNumber>GpO48aJ8GngtwECqZhm/xA==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=">
+ <serialNumber>CdYL9vSQCEKzBwjO10ud2w==</serialNumber>
+ </certItem>
+ <certItem issuerName="MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=">
+ <serialNumber>a12RvBNhznU=</serialNumber>
+ </certItem>
+ </certItems>
+</blocklist>
diff --git a/browser/app/firefox.exe.manifest b/browser/app/firefox.exe.manifest
new file mode 100644
index 000000000..b23c2a4c0
--- /dev/null
+++ b/browser/app/firefox.exe.manifest
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="Firefox"
+ type="win32"
+/>
+<description>Firefox</description>
+<dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+</dependency>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </ms_asmv3:requestedPrivileges>
+ </ms_asmv3:security>
+</ms_asmv3:trustInfo>
+ <ms_asmv3:application xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ <dpiAware>True/PM</dpiAware>
+ </ms_asmv3:windowsSettings>
+ </ms_asmv3:application>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/browser/app/macbuild/Contents/Info.plist.in b/browser/app/macbuild/Contents/Info.plist.in
new file mode 100644
index 000000000..6212e886d
--- /dev/null
+++ b/browser/app/macbuild/Contents/Info.plist.in
@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>html</string>
+ <string>htm</string>
+ <string>shtml</string>
+ <string>xht</string>
+ <string>xhtml</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>HTML Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>HTML</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>svg</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/svg+xml</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>SVG document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>TEXT</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>NSDocumentClass</key>
+ <string>BrowserDocument</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>text</string>
+ <string>txt</string>
+ <string>js</string>
+ <string>log</string>
+ <string>css</string>
+ <string>xul</string>
+ <string>rdf</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Text Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>TEXT</string>
+ <string>utxt</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>jpeg</string>
+ <string>jpg</string>
+ <string>png</string>
+ <string>gif</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>fileBookmark.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>GIFf</string>
+ <string>JPEG</string>
+ <string>PNGf</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>oga</string>
+ <string>ogg</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>audio/ogg</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>HTML5 Audio (Ogg)</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>ogv</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>video/ogg</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>HTML5 Video (Ogg)</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>webm</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>video/webm</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>HTML5 Video (WebM)</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ </array>
+ <key>CFBundleExecutable</key>
+ <string>firefox</string>
+ <key>CFBundleGetInfoString</key>
+ <string>%MAC_APP_NAME% %APP_VERSION%</string>
+ <key>CFBundleIconFile</key>
+ <string>firefox.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>%MOZ_MACBUNDLE_ID%</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>%MAC_APP_NAME%</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>%APP_VERSION%</string>
+ <key>CFBundleSignature</key>
+ <string>MOZB</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>http URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>http</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>https URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>https</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>ftp URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>ftp</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>file URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>file</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>%MAC_BUNDLE_VERSION%</string>
+ <key>NSAppleScriptEnabled</key>
+ <true/>
+ <key>LSApplicationCategoryType</key>
+ <string>public.app-category.productivity</string>
+ <key>LSFileQuarantineEnabled</key>
+ <true/>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.9.0</string>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>NSPrincipalClass</key>
+ <string>GeckoNSApplication</string>
+ <key>SMPrivilegedExecutables</key>
+ <dict>
+ <key>org.mozilla.updater</key>
+ <string>identifier "org.mozilla.updater" and ((anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]) or (anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = "43AQ936H96"))</string>
+ </dict>
+ <key>NSDisablePersistence</key>
+ <true/>
+</dict>
+</plist>
diff --git a/browser/app/macbuild/Contents/MacOS-files.in b/browser/app/macbuild/Contents/MacOS-files.in
new file mode 100644
index 000000000..849336bc9
--- /dev/null
+++ b/browser/app/macbuild/Contents/MacOS-files.in
@@ -0,0 +1,9 @@
+/*.app/***
+/*.dylib
+/certutil
+/firefox-bin
+/gtest/***
+/pk12util
+/ssltunnel
+/xpcshell
+/XUL
diff --git a/browser/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/browser/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 000000000..74d192cb0
--- /dev/null
+++ b/browser/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,5 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+CFBundleName = "%MAC_APP_NAME%";
diff --git a/browser/app/macversion.py b/browser/app/macversion.py
new file mode 100644
index 000000000..839aac1ff
--- /dev/null
+++ b/browser/app/macversion.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+from optparse import OptionParser
+import sys
+import re
+
+o = OptionParser()
+o.add_option("--buildid", dest="buildid")
+o.add_option("--version", dest="version")
+
+(options, args) = o.parse_args()
+
+if not options.buildid:
+ print >>sys.stderr, "--buildid is required"
+ sys.exit(1)
+
+if not options.version:
+ print >>sys.stderr, "--version is required"
+ sys.exit(1)
+
+# We want to build a version number that matches the format allowed for
+# CFBundleVersion (nnnnn[.nn[.nn]]). We'll incorporate both the version
+# number as well as the date, so that it changes at least daily (for nightly
+# builds), but also so that newly-built older versions (e.g. beta build) aren't
+# considered "newer" than previously-built newer versions (e.g. a trunk nightly)
+
+define, MOZ_BUILDID, buildid = open(options.buildid, 'r').read().split()
+
+# extract only the major version (i.e. "14" from "14.0b1")
+majorVersion = re.match(r'^(\d+)[^\d].*', options.version).group(1)
+# last two digits of the year
+twodigityear = buildid[2:4]
+month = buildid[4:6]
+if month[0] == '0':
+ month = month[1]
+day = buildid[6:8]
+if day[0] == '0':
+ day = day[1]
+
+print '%s.%s.%s' % (majorVersion + twodigityear, month, day)
diff --git a/browser/app/module.ver b/browser/app/module.ver
new file mode 100644
index 000000000..5ef8d2a02
--- /dev/null
+++ b/browser/app/module.ver
@@ -0,0 +1,8 @@
+WIN32_MODULE_COMPANYNAME=Mozilla Corporation
+WIN32_MODULE_COPYRIGHT=Firefox and Mozilla Developers; available under the MPL 2 license.
+WIN32_MODULE_PRODUCTVERSION=@MOZ_APP_WINVERSION@
+WIN32_MODULE_PRODUCTVERSION_STRING=@MOZ_APP_VERSION@
+WIN32_MODULE_TRADEMARKS=Firefox is a Trademark of The Mozilla Foundation.
+WIN32_MODULE_DESCRIPTION=@MOZ_APP_DISPLAYNAME@
+WIN32_MODULE_PRODUCTNAME=@MOZ_APP_DISPLAYNAME@
+WIN32_MODULE_NAME=@MOZ_APP_DISPLAYNAME@
diff --git a/browser/app/moz.build b/browser/app/moz.build
new file mode 100644
index 000000000..520ce4425
--- /dev/null
+++ b/browser/app/moz.build
@@ -0,0 +1,91 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['profile/extensions']
+
+GeckoProgram(CONFIG['MOZ_APP_NAME'])
+
+JS_PREFERENCE_PP_FILES += [
+ 'profile/firefox.js',
+]
+
+SOURCES += [
+ 'nsBrowserApp.cpp',
+]
+
+FINAL_TARGET_FILES += ['blocklist.xml']
+FINAL_TARGET_FILES.defaults.profile += ['profile/prefs.js']
+FINAL_TARGET_FILES.defaults += ['permissions']
+
+DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+
+LOCAL_INCLUDES += [
+ '!/build',
+ '/toolkit/xre',
+ '/xpcom/base',
+ '/xpcom/build',
+]
+
+USE_LIBS += [
+ 'mozglue',
+]
+
+if CONFIG['LIBFUZZER']:
+ USE_LIBS += [ 'fuzzer' ]
+
+if CONFIG['_MSC_VER']:
+ # Always enter a Windows program through wmain, whether or not we're
+ # a console application.
+ WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ RCINCLUDE = 'splash.rc'
+ DEFINES['MOZ_PHOENIX'] = True
+
+for cdm in CONFIG['MOZ_EME_MODULES']:
+ DEFINES['MOZ_%s_EME' % cdm.upper()] = True
+
+if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
+ # For sandbox includes and the include dependencies those have
+ LOCAL_INCLUDES += [
+ '/security/sandbox/chromium',
+ '/security/sandbox/chromium-shim',
+ ]
+
+ USE_LIBS += [
+ 'sandbox_s',
+ ]
+
+ DELAYLOAD_DLLS += [
+ 'winmm.dll',
+ 'user32.dll',
+ ]
+
+# Control the default heap size.
+# This is the heap returned by GetProcessHeap().
+# As we use the CRT heap, the default size is too large and wastes VM.
+#
+# The default heap size is 1MB on Win32.
+# The heap will grow if need be.
+#
+# Set it to 256k. See bug 127069.
+if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
+ LDFLAGS += ['/HEAP:0x40000']
+
+DISABLE_STL_WRAPPING = True
+
+if CONFIG['MOZ_LINKER']:
+ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+
+if CONFIG['HAVE_CLOCK_MONOTONIC']:
+ OS_LIBS += CONFIG['REALTIME_LIBS']
+
+if CONFIG['MOZ_GPSD']:
+ DEFINES['MOZ_GPSD'] = True
+
+for icon in ('firefox', 'document', 'newwindow', 'newtab', 'pbmode'):
+ DEFINES[icon.upper() + '_ICO'] = '"%s/dist/branding/%s.ico"' % (
+ TOPOBJDIR, icon)
diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp
new file mode 100644
index 000000000..981e2f14c
--- /dev/null
+++ b/browser/app/nsBrowserApp.cpp
@@ -0,0 +1,429 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsXULAppAPI.h"
+#include "mozilla/AppData.h"
+#include "application.ini.h"
+#include "nsXPCOMGlue.h"
+#if defined(XP_WIN)
+#include <windows.h>
+#include <stdlib.h>
+#elif defined(XP_UNIX)
+#include <sys/resource.h>
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <time.h>
+
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsStringGlue.h"
+
+#ifdef XP_WIN
+#ifdef MOZ_ASAN
+// ASAN requires firefox.exe to be built with -MD, and it's OK if we don't
+// support Windows XP SP2 in ASAN builds.
+#define XRE_DONT_SUPPORT_XPSP2
+#endif
+#define XRE_WANT_ENVIRON
+#define strcasecmp _stricmp
+#ifdef MOZ_SANDBOX
+#include "mozilla/sandboxing/SandboxInitialization.h"
+#endif
+#endif
+#include "BinaryPath.h"
+
+#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
+
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsDllBlocklist.h"
+
+#if !defined(MOZ_WIDGET_COCOA) && !defined(MOZ_WIDGET_ANDROID) \
+ && !(defined(XP_LINUX) && defined(MOZ_SANDBOX))
+#define MOZ_BROWSER_CAN_BE_CONTENTPROC
+#include "../../ipc/contentproc/plugin-container.cpp"
+#endif
+
+using namespace mozilla;
+
+#ifdef XP_MACOSX
+#define kOSXResourcesFolder "Resources"
+#endif
+#define kDesktopFolder "browser"
+
+static void Output(const char *fmt, ... )
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+#ifndef XP_WIN
+ vfprintf(stderr, fmt, ap);
+#else
+ char msg[2048];
+ vsnprintf_s(msg, _countof(msg), _TRUNCATE, fmt, ap);
+
+ wchar_t wide_msg[2048];
+ MultiByteToWideChar(CP_UTF8,
+ 0,
+ msg,
+ -1,
+ wide_msg,
+ _countof(wide_msg));
+#if MOZ_WINCONSOLE
+ fwprintf_s(stderr, wide_msg);
+#else
+ // Linking user32 at load-time interferes with the DLL blocklist (bug 932100).
+ // This is a rare codepath, so we can load user32 at run-time instead.
+ HMODULE user32 = LoadLibraryW(L"user32.dll");
+ if (user32) {
+ decltype(MessageBoxW)* messageBoxW =
+ (decltype(MessageBoxW)*) GetProcAddress(user32, "MessageBoxW");
+ if (messageBoxW) {
+ messageBoxW(nullptr, wide_msg, L"Firefox", MB_OK
+ | MB_ICONERROR
+ | MB_SETFOREGROUND);
+ }
+ FreeLibrary(user32);
+ }
+#endif
+#endif
+
+ va_end(ap);
+}
+
+/**
+ * Return true if |arg| matches the given argument name.
+ */
+static bool IsArg(const char* arg, const char* s)
+{
+ if (*arg == '-')
+ {
+ if (*++arg == '-')
+ ++arg;
+ return !strcasecmp(arg, s);
+ }
+
+#if defined(XP_WIN)
+ if (*arg == '/')
+ return !strcasecmp(++arg, s);
+#endif
+
+ return false;
+}
+
+XRE_GetFileFromPathType XRE_GetFileFromPath;
+XRE_CreateAppDataType XRE_CreateAppData;
+XRE_FreeAppDataType XRE_FreeAppData;
+XRE_TelemetryAccumulateType XRE_TelemetryAccumulate;
+XRE_StartupTimelineRecordType XRE_StartupTimelineRecord;
+XRE_mainType XRE_main;
+XRE_StopLateWriteChecksType XRE_StopLateWriteChecks;
+XRE_XPCShellMainType XRE_XPCShellMain;
+XRE_GetProcessTypeType XRE_GetProcessType;
+XRE_SetProcessTypeType XRE_SetProcessType;
+XRE_InitChildProcessType XRE_InitChildProcess;
+XRE_EnableSameExecutableForContentProcType XRE_EnableSameExecutableForContentProc;
+#ifdef LIBFUZZER
+XRE_LibFuzzerSetMainType XRE_LibFuzzerSetMain;
+XRE_LibFuzzerGetFuncsType XRE_LibFuzzerGetFuncs;
+#endif
+
+static const nsDynamicFunctionLoad kXULFuncs[] = {
+ { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
+ { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
+ { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
+ { "XRE_TelemetryAccumulate", (NSFuncPtr*) &XRE_TelemetryAccumulate },
+ { "XRE_StartupTimelineRecord", (NSFuncPtr*) &XRE_StartupTimelineRecord },
+ { "XRE_main", (NSFuncPtr*) &XRE_main },
+ { "XRE_StopLateWriteChecks", (NSFuncPtr*) &XRE_StopLateWriteChecks },
+ { "XRE_XPCShellMain", (NSFuncPtr*) &XRE_XPCShellMain },
+ { "XRE_GetProcessType", (NSFuncPtr*) &XRE_GetProcessType },
+ { "XRE_SetProcessType", (NSFuncPtr*) &XRE_SetProcessType },
+ { "XRE_InitChildProcess", (NSFuncPtr*) &XRE_InitChildProcess },
+ { "XRE_EnableSameExecutableForContentProc", (NSFuncPtr*) &XRE_EnableSameExecutableForContentProc },
+#ifdef LIBFUZZER
+ { "XRE_LibFuzzerSetMain", (NSFuncPtr*) &XRE_LibFuzzerSetMain },
+ { "XRE_LibFuzzerGetFuncs", (NSFuncPtr*) &XRE_LibFuzzerGetFuncs },
+#endif
+ { nullptr, nullptr }
+};
+
+#ifdef LIBFUZZER
+int libfuzzer_main(int argc, char **argv);
+
+/* This wrapper is used by the libFuzzer main to call into libxul */
+
+void libFuzzerGetFuncs(const char* moduleName, LibFuzzerInitFunc* initFunc,
+ LibFuzzerTestingFunc* testingFunc) {
+ return XRE_LibFuzzerGetFuncs(moduleName, initFunc, testingFunc);
+}
+#endif
+
+static int do_main(int argc, char* argv[], char* envp[], nsIFile *xreDirectory)
+{
+ nsCOMPtr<nsIFile> appini;
+ nsresult rv;
+ uint32_t mainFlags = 0;
+
+ // Allow firefox.exe to launch XULRunner apps via -app <application.ini>
+ // Note that -app must be the *first* argument.
+ const char *appDataFile = getenv("XUL_APP_FILE");
+ if (appDataFile && *appDataFile) {
+ rv = XRE_GetFileFromPath(appDataFile, getter_AddRefs(appini));
+ if (NS_FAILED(rv)) {
+ Output("Invalid path found: '%s'", appDataFile);
+ return 255;
+ }
+ }
+ else if (argc > 1 && IsArg(argv[1], "app")) {
+ if (argc == 2) {
+ Output("Incorrect number of arguments passed to -app");
+ return 255;
+ }
+
+ rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(appini));
+ if (NS_FAILED(rv)) {
+ Output("application.ini path not recognized: '%s'", argv[2]);
+ return 255;
+ }
+
+ char appEnv[MAXPATHLEN];
+ SprintfLiteral(appEnv, "XUL_APP_FILE=%s", argv[2]);
+ if (putenv(strdup(appEnv))) {
+ Output("Couldn't set %s.\n", appEnv);
+ return 255;
+ }
+ argv[2] = argv[0];
+ argv += 2;
+ argc -= 2;
+ } else if (argc > 1 && IsArg(argv[1], "xpcshell")) {
+ for (int i = 1; i < argc; i++) {
+ argv[i] = argv[i + 1];
+ }
+
+ XREShellData shellData;
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ shellData.sandboxBrokerServices =
+ sandboxing::GetInitializedBrokerServices();
+#endif
+
+ return XRE_XPCShellMain(--argc, argv, envp, &shellData);
+ }
+
+ if (appini) {
+ nsXREAppData *appData;
+ rv = XRE_CreateAppData(appini, &appData);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't read application.ini");
+ return 255;
+ }
+#if defined(HAS_DLL_BLOCKLIST)
+ // The dll blocklist operates in the exe vs. xullib. Pass a flag to
+ // xullib so automated tests can check the result once the browser
+ // is up and running.
+ appData->flags |=
+ DllBlocklist_CheckStatus() ? NS_XRE_DLL_BLOCKLIST_ENABLED : 0;
+#endif
+ // xreDirectory already has a refcount from NS_NewLocalFile
+ appData->xreDirectory = xreDirectory;
+ int result = XRE_main(argc, argv, appData, mainFlags);
+ XRE_FreeAppData(appData);
+ return result;
+ }
+
+ ScopedAppData appData(&sAppData);
+ nsCOMPtr<nsIFile> exeFile;
+ rv = mozilla::BinaryPath::GetFile(argv[0], getter_AddRefs(exeFile));
+ if (NS_FAILED(rv)) {
+ Output("Couldn't find the application directory.\n");
+ return 255;
+ }
+
+ nsCOMPtr<nsIFile> greDir;
+ exeFile->GetParent(getter_AddRefs(greDir));
+#ifdef XP_MACOSX
+ greDir->SetNativeLeafName(NS_LITERAL_CSTRING(kOSXResourcesFolder));
+#endif
+ nsCOMPtr<nsIFile> appSubdir;
+ greDir->Clone(getter_AddRefs(appSubdir));
+ appSubdir->Append(NS_LITERAL_STRING(kDesktopFolder));
+
+ SetStrongPtr(appData.directory, static_cast<nsIFile*>(appSubdir.get()));
+ // xreDirectory already has a refcount from NS_NewLocalFile
+ appData.xreDirectory = xreDirectory;
+
+#if defined(HAS_DLL_BLOCKLIST)
+ appData.flags |=
+ DllBlocklist_CheckStatus() ? NS_XRE_DLL_BLOCKLIST_ENABLED : 0;
+#endif
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ sandbox::BrokerServices* brokerServices =
+ sandboxing::GetInitializedBrokerServices();
+#if defined(MOZ_CONTENT_SANDBOX)
+ if (!brokerServices) {
+ Output("Couldn't initialize the broker services.\n");
+ return 255;
+ }
+#endif
+ appData.sandboxBrokerServices = brokerServices;
+#endif
+
+#ifdef LIBFUZZER
+ if (getenv("LIBFUZZER"))
+ XRE_LibFuzzerSetMain(argc, argv, libfuzzer_main);
+#endif
+
+ return XRE_main(argc, argv, &appData, mainFlags);
+}
+
+static bool
+FileExists(const char *path)
+{
+#ifdef XP_WIN
+ wchar_t wideDir[MAX_PATH];
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, wideDir, MAX_PATH);
+ DWORD fileAttrs = GetFileAttributesW(wideDir);
+ return fileAttrs != INVALID_FILE_ATTRIBUTES;
+#else
+ return access(path, R_OK) == 0;
+#endif
+}
+
+static nsresult
+InitXPCOMGlue(const char *argv0, nsIFile **xreDirectory)
+{
+ char exePath[MAXPATHLEN];
+
+ nsresult rv = mozilla::BinaryPath::Get(argv0, exePath);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't find the application directory.\n");
+ return rv;
+ }
+
+ char *lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
+ if (!lastSlash ||
+ (size_t(lastSlash - exePath) > MAXPATHLEN - sizeof(XPCOM_DLL) - 1))
+ return NS_ERROR_FAILURE;
+
+ strcpy(lastSlash + 1, XPCOM_DLL);
+
+ if (!FileExists(exePath)) {
+ Output("Could not find the Mozilla runtime.\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // We do this because of data in bug 771745
+ XPCOMGlueEnablePreload();
+
+ rv = XPCOMGlueStartup(exePath);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't load XPCOM.\n");
+ return rv;
+ }
+
+ rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't load XRE functions.\n");
+ return rv;
+ }
+
+ // This will set this thread as the main thread.
+ NS_LogInit();
+
+ if (xreDirectory) {
+ // chop XPCOM_DLL off exePath
+ *lastSlash = '\0';
+#ifdef XP_MACOSX
+ lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
+ strcpy(lastSlash + 1, kOSXResourcesFolder);
+#endif
+#ifdef XP_WIN
+ rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(exePath), false,
+ xreDirectory);
+#else
+ rv = NS_NewNativeLocalFile(nsDependentCString(exePath), false,
+ xreDirectory);
+#endif
+ }
+
+ return rv;
+}
+
+int main(int argc, char* argv[], char* envp[])
+{
+ mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+#ifdef HAS_DLL_BLOCKLIST
+ DllBlocklist_Initialize();
+
+#ifdef DEBUG
+ // In order to be effective against AppInit DLLs, the blocklist must be
+ // initialized before user32.dll is loaded into the process (bug 932100).
+ if (GetModuleHandleA("user32.dll")) {
+ fprintf(stderr, "DLL blocklist was unable to intercept AppInit DLLs.\n");
+ }
+#endif
+#endif
+
+#ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
+ // We are launching as a content process, delegate to the appropriate
+ // main
+ if (argc > 1 && IsArg(argv[1], "contentproc")) {
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ // We need to initialize the sandbox TargetServices before InitXPCOMGlue
+ // because we might need the sandbox broker to give access to some files.
+ if (IsSandboxedProcess() && !sandboxing::GetInitializedTargetServices()) {
+ Output("Failed to initialize the sandbox target services.");
+ return 255;
+ }
+#endif
+
+ nsresult rv = InitXPCOMGlue(argv[0], nullptr);
+ if (NS_FAILED(rv)) {
+ return 255;
+ }
+
+ int result = content_process_main(argc, argv);
+
+ // InitXPCOMGlue calls NS_LogInit, so we need to balance it here.
+ NS_LogTerm();
+
+ return result;
+ }
+#endif
+
+
+ nsIFile *xreDirectory;
+
+ nsresult rv = InitXPCOMGlue(argv[0], &xreDirectory);
+ if (NS_FAILED(rv)) {
+ return 255;
+ }
+
+ XRE_StartupTimelineRecord(mozilla::StartupTimeline::START, start);
+
+#ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
+ XRE_EnableSameExecutableForContentProc();
+#endif
+
+ int result = do_main(argc, argv, envp, xreDirectory);
+
+ NS_LogTerm();
+
+#ifdef XP_MACOSX
+ // Allow writes again. While we would like to catch writes from static
+ // destructors to allow early exits to use _exit, we know that there is
+ // at least one such write that we don't control (see bug 826029). For
+ // now we enable writes again and early exits will have to use exit instead
+ // of _exit.
+ XRE_StopLateWriteChecks();
+#endif
+
+ return result;
+}
diff --git a/browser/app/permissions b/browser/app/permissions
new file mode 100644
index 000000000..a2afdded2
--- /dev/null
+++ b/browser/app/permissions
@@ -0,0 +1,23 @@
+# This file has default permissions for the permission manager.
+# The file-format is strict:
+# * matchtype \t type \t permission \t host
+# * "origin" should be used for matchtype, "host" is supported for legacy reasons
+# * type is a string that identifies the type of permission (e.g. "cookie")
+# * permission is an integer between 1 and 15
+# See nsPermissionManager.cpp for more...
+
+# UITour
+origin uitour 1 https://www.mozilla.org
+origin uitour 1 https://self-repair.mozilla.org
+origin uitour 1 https://support.mozilla.org
+origin uitour 1 https://addons.mozilla.org
+origin uitour 1 https://discovery.addons.mozilla.org
+origin uitour 1 about:home
+
+# XPInstall
+origin install 1 https://addons.mozilla.org
+origin install 1 https://testpilot.firefox.com
+
+# Remote troubleshooting
+origin remote-troubleshooting 1 https://input.mozilla.org
+origin remote-troubleshooting 1 https://support.mozilla.org
diff --git a/browser/app/profile/channel-prefs.js b/browser/app/profile/channel-prefs.js
new file mode 100644
index 000000000..633c489f3
--- /dev/null
+++ b/browser/app/profile/channel-prefs.js
@@ -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/. */
+
+pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
diff --git a/browser/app/profile/extensions/moz.build b/browser/app/profile/extensions/moz.build
new file mode 100644
index 000000000..bb02c9165
--- /dev/null
+++ b/browser/app/profile/extensions/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['{972ce4c6-7e08-4474-a285-3208198ce6fd}']
diff --git a/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in b/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in
new file mode 100644
index 000000000..3e803af5b
--- /dev/null
+++ b/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>{972ce4c6-7e08-4474-a285-3208198ce6fd}</em:id>
+ <em:version>@FIREFOX_VERSION@</em:version>
+
+ <!-- Target Application this theme can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@FIREFOX_VERSION@</em:minVersion>
+ <em:maxVersion>@FIREFOX_VERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Default</em:name>
+ <em:description>The default theme.</em:description>
+
+ <!-- Front End Integration Hooks (used by Theme Manager)-->
+ <em:creator>Mozilla</em:creator>
+ <em:contributor>Mozilla Contributors</em:contributor>
+
+ <!-- Allow lightweight themes to apply to this theme -->
+ <em:skinnable>true</em:skinnable>
+
+ <em:internalName>classic/1.0</em:internalName>
+ </Description>
+
+</RDF>
diff --git a/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build b/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build
new file mode 100644
index 000000000..20a4c1cbe
--- /dev/null
+++ b/browser/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_TARGET = 'dist/bin/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}'
+
+FINAL_TARGET_PP_FILES += [
+ 'install.rdf.in',
+]
diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
new file mode 100644
index 000000000..82f8e45a5
--- /dev/null
+++ b/browser/app/profile/firefox.js
@@ -0,0 +1,1571 @@
+# -*- 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/.
+
+// XXX Toolkit-specific preferences should be moved into toolkit.js
+
+#filter substitution
+
+#
+# SYNTAX HINTS:
+#
+# - Dashes are delimiters; use underscores instead.
+# - The first character after a period must be alphabetic.
+# - Computed values (e.g. 50 * 1024) don't work.
+#
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+#define UNIX_BUT_NOT_MAC
+#endif
+#endif
+
+pref("browser.chromeURL","chrome://browser/content/");
+pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindow.xul");
+
+// Enables some extra Extension System Logging (can reduce performance)
+pref("extensions.logging.enabled", false);
+
+// Disables strict compatibility, making addons compatible-by-default.
+pref("extensions.strictCompatibility", false);
+
+// Specifies a minimum maxVersion an addon needs to say it's compatible with
+// for it to be compatible by default.
+pref("extensions.minCompatibleAppVersion", "4.0");
+// Temporary preference to forcibly make themes more safe with Australis even if
+// extensions.checkCompatibility=false has been set.
+pref("extensions.checkCompatibility.temporaryThemeOverride_minAppVersion", "29.0a1");
+
+pref("xpinstall.customConfirmationUI", true);
+
+// Preferences for AMO integration
+pref("extensions.getAddons.cache.enabled", true);
+pref("extensions.getAddons.maxResults", 15);
+pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%");
+pref("extensions.getAddons.getWithPerformance.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/guid:%IDS%?src=firefox&appOS=%OS%&appVersion=%VERSION%&tMain=%TIME_MAIN%&tFirstPaint=%TIME_FIRST_PAINT%&tSessionRestored=%TIME_SESSION_RESTORED%");
+pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/firefox/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
+pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/firefox/api/%API_VERSION%/search/%TERMS%/all/%MAX_RESULTS%/%OS%/%VERSION%/%COMPATIBILITY_MODE%?src=firefox");
+pref("extensions.webservice.discoverURL", "https://discovery.addons.mozilla.org/%LOCALE%/firefox/discovery/pane/%VERSION%/%OS%/%COMPATIBILITY_MODE%");
+pref("extensions.getAddons.recommended.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/list/recommended/all/%MAX_RESULTS%/%OS%/%VERSION%?src=firefox");
+pref("extensions.getAddons.link.url", "https://addons.mozilla.org/%LOCALE%/firefox/");
+
+pref("extensions.update.autoUpdateDefault", true);
+
+pref("extensions.hotfix.id", "firefox-hotfix@mozilla.org");
+pref("extensions.hotfix.cert.checkAttributes", true);
+pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35:22:9E:11:C9:A7:31:04:49:A1:AA");
+pref("extensions.hotfix.certs.2.sha1Fingerprint", "39:E7:2B:7A:5B:CF:37:78:F9:5D:4A:E0:53:2D:2F:3D:68:53:C5:60");
+
+// Check AUS for system add-on updates.
+pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
+
+// Disable add-ons that are not installed by the user in all scopes by default.
+// See the SCOPE constants in AddonManager.jsm for values to use here.
+pref("extensions.autoDisableScopes", 15);
+
+// Add-on content security policies.
+pref("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:;");
+pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
+
+// Require signed add-ons by default
+pref("xpinstall.signatures.required", true);
+pref("xpinstall.signatures.devInfoURL", "https://wiki.mozilla.org/Addons/Extension_Signing");
+
+// Dictionary download preference
+pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
+
+// At startup, should we check to see if the installation
+// date is older than some threshold
+pref("app.update.checkInstallTime", true);
+
+// The number of days a binary is permitted to be old without checking is defined in
+// firefox-branding.js (app.update.checkInstallTime.days)
+
+// The minimum delay in seconds for the timer to fire between the notification
+// of each consumer of the timer manager.
+// minimum=30 seconds, default=120 seconds, and maximum=300 seconds
+pref("app.update.timerMinimumDelay", 120);
+
+// The minimum delay in milliseconds for the first firing after startup of the timer
+// to notify consumers of the timer manager.
+// minimum=10 seconds, default=30 seconds, and maximum=120 seconds
+pref("app.update.timerFirstInterval", 30000);
+
+// App-specific update preferences
+
+// The interval to check for updates (app.update.interval) is defined in
+// firefox-branding.js
+
+// Alternative windowtype for an application update user interface window. When
+// a window with this windowtype is open the application update service won't
+// open the normal application update user interface window.
+pref("app.update.altwindowtype", "Browser:About");
+
+// Enables some extra Application Update Logging (can reduce performance)
+pref("app.update.log", false);
+
+// The number of general background check failures to allow before notifying the
+// user of the failure. User initiated update checks always notify the user of
+// the failure.
+pref("app.update.backgroundMaxErrors", 10);
+
+// Whether or not app updates are enabled
+pref("app.update.enabled", true);
+
+// If set to true, the Update Service will automatically download updates when
+// app updates are enabled per the app.update.enabled preference and if the user
+// can apply updates.
+pref("app.update.auto", true);
+
+// If set to true, the Update Service will present no UI for any event.
+pref("app.update.silent", false);
+
+// If set to true, the hamburger button will show badges for update events.
+#ifndef RELEASE_OR_BETA
+pref("app.update.badge", true);
+#else
+pref("app.update.badge", false);
+#endif
+// app.update.badgeWaitTime is in branding section
+
+// If set to true, the Update Service will apply updates in the background
+// when it finishes downloading them.
+pref("app.update.staging.enabled", true);
+
+// Update service URL:
+pref("app.update.url", "https://aus5.mozilla.org/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
+// app.update.url.manual is in branding section
+// app.update.url.details is in branding section
+
+// app.update.interval is in branding section
+// app.update.promptWaitTime is in branding section
+
+// Show the Update Checking/Ready UI when the user was idle for x seconds
+pref("app.update.idletime", 60);
+
+// Whether or not to attempt using the service for updates.
+#ifdef MOZ_MAINTENANCE_SERVICE
+pref("app.update.service.enabled", true);
+#endif
+
+// Symmetric (can be overridden by individual extensions) update preferences.
+// e.g.
+// extensions.{GUID}.update.enabled
+// extensions.{GUID}.update.url
+// .. etc ..
+//
+pref("extensions.update.enabled", true);
+pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
+pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
+pref("extensions.update.interval", 86400); // Check for updates to Extensions and
+ // Themes every day
+// Non-symmetric (not shared by extensions) extension-specific [update] preferences
+pref("extensions.dss.enabled", false); // Dynamic Skin Switching
+pref("extensions.dss.switchPending", false); // Non-dynamic switch pending after next
+ // restart.
+
+pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name", "chrome://browser/locale/browser.properties");
+pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description", "chrome://browser/locale/browser.properties");
+
+pref("lightweightThemes.update.enabled", true);
+pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
+pref("lightweightThemes.recommendedThemes", "[{\"id\":\"recommended-1\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/a-web-browser-renaissance/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.footer.jpg\",\"textcolor\":\"#000000\",\"accentcolor\":\"#f2d9b1\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.preview.jpg\",\"author\":\"Sean.Martell\",\"version\":\"0\"},{\"id\":\"recommended-2\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/space-fantasy/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.footer.jpg\",\"textcolor\":\"#ffffff\",\"accentcolor\":\"#d9d9d9\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.preview.jpg\",\"author\":\"fx5800p\",\"version\":\"1.0\"},{\"id\":\"recommended-3\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/linen-light/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.footer.png\",\"accentcolor\":\"#ada8a8\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/3.preview.png\",\"author\":\"DVemer\",\"version\":\"1.0\"},{\"id\":\"recommended-4\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/pastel-gradient/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.footer.png\",\"textcolor\":\"#000000\",\"accentcolor\":\"#000000\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.preview.png\",\"author\":\"darrinhenein\",\"version\":\"1.0\"},{\"id\":\"recommended-5\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/carbon-light/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.footer.png\",\"textcolor\":\"#3b3b3b\",\"accentcolor\":\"#2e2e2e\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/5.preview.jpg\",\"author\":\"Jaxivo\",\"version\":\"1.0\"}]");
+
+#if defined(MOZ_ADOBE_EME) || defined(MOZ_WIDEVINE_EME)
+pref("browser.eme.ui.enabled", true);
+#else
+pref("browser.eme.ui.enabled", false);
+#endif
+
+// UI tour experience.
+pref("browser.uitour.enabled", true);
+pref("browser.uitour.loglevel", "Error");
+pref("browser.uitour.requireSecure", true);
+pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/");
+pref("browser.uitour.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tour/");
+// This is used as a regexp match against the page's URL.
+pref("browser.uitour.readerViewTrigger", "^https:\\/\\/www\\.mozilla\\.org\\/[^\\/]+\\/firefox\\/reading\\/start");
+// How long to show a Hearbeat survey (two hours, in seconds)
+pref("browser.uitour.surveyDuration", 7200);
+
+pref("browser.customizemode.tip0.shown", false);
+pref("browser.customizemode.tip0.learnMoreUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/customize");
+
+pref("keyword.enabled", true);
+pref("browser.fixup.domainwhitelist.localhost", true);
+
+pref("general.useragent.locale", "@AB_CD@");
+pref("general.skins.selectedSkin", "classic/1.0");
+
+pref("general.smoothScroll", true);
+#ifdef UNIX_BUT_NOT_MAC
+pref("general.autoScroll", false);
+#else
+pref("general.autoScroll", true);
+#endif
+
+// At startup, check if we're the default browser and prompt user if not.
+pref("browser.shell.checkDefaultBrowser", true);
+pref("browser.shell.shortcutFavicons",true);
+pref("browser.shell.mostRecentDateSetAsDefault", "");
+#ifdef RELEASE_OR_BETA
+pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", false);
+#else
+pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", true);
+#endif
+pref("browser.shell.skipDefaultBrowserCheck", true);
+pref("browser.shell.defaultBrowserCheckCount", 0);
+pref("browser.defaultbrowser.notificationbar", false);
+
+// 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
+// The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
+pref("browser.startup.page", 1);
+pref("browser.startup.homepage", "chrome://branding/locale/browserconfig.properties");
+// Whether we should skip the homepage when opening the first-run page
+pref("browser.startup.firstrunSkipsHomepage", false);
+
+pref("browser.slowStartup.notificationDisabled", false);
+pref("browser.slowStartup.timeThreshold", 40000);
+pref("browser.slowStartup.maxSamples", 5);
+
+// This url, if changed, MUST continue to point to an https url. Pulling arbitrary content to inject into
+// this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream
+// repackager of this code using an alternate snippet url, please keep your users safe
+pref("browser.aboutHomeSnippets.updateUrl", "https://snippets.cdn.mozilla.net/%STARTPAGE_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
+
+pref("browser.enable_automatic_image_resizing", true);
+pref("browser.casting.enabled", false);
+pref("browser.chrome.site_icons", true);
+pref("browser.chrome.favicons", true);
+// browser.warnOnQuit == false will override all other possible prompts when quitting or restarting
+pref("browser.warnOnQuit", true);
+// browser.showQuitWarning specifically controls the quit warning dialog. We
+// might still show the window closing dialog with showQuitWarning == false.
+pref("browser.showQuitWarning", false);
+pref("browser.fullscreen.autohide", true);
+pref("browser.fullscreen.animate", true);
+pref("browser.overlink-delay", 80);
+
+#ifdef UNIX_BUT_NOT_MAC
+pref("browser.urlbar.clickSelectsAll", false);
+#else
+pref("browser.urlbar.clickSelectsAll", true);
+#endif
+#ifdef UNIX_BUT_NOT_MAC
+pref("browser.urlbar.doubleClickSelectsAll", true);
+#else
+pref("browser.urlbar.doubleClickSelectsAll", false);
+#endif
+
+// Control autoFill behavior
+pref("browser.urlbar.autoFill", true);
+pref("browser.urlbar.autoFill.typed", true);
+
+// 0: Match anywhere (e.g., middle of words)
+// 1: Match on word boundaries and then try matching anywhere
+// 2: Match only on word boundaries (e.g., after / or .)
+// 3: Match at the beginning of the url or title
+pref("browser.urlbar.matchBehavior", 1);
+pref("browser.urlbar.filter.javascript", true);
+
+// the maximum number of results to show in autocomplete when doing richResults
+pref("browser.urlbar.maxRichResults", 10);
+// The amount of time (ms) to wait after the user has stopped typing
+// before starting to perform autocomplete. 50 is the default set in
+// autocomplete.xml.
+pref("browser.urlbar.delay", 50);
+
+// The special characters below can be typed into the urlbar to either restrict
+// the search to visited history, bookmarked, tagged pages; or force a match on
+// just the title text or url.
+pref("browser.urlbar.restrict.history", "^");
+pref("browser.urlbar.restrict.bookmark", "*");
+pref("browser.urlbar.restrict.tag", "+");
+pref("browser.urlbar.restrict.openpage", "%");
+pref("browser.urlbar.restrict.typed", "~");
+pref("browser.urlbar.restrict.searches", "$");
+pref("browser.urlbar.match.title", "#");
+pref("browser.urlbar.match.url", "@");
+
+// The default behavior for the urlbar can be configured to use any combination
+// of the match filters with each additional filter adding more results (union).
+pref("browser.urlbar.suggest.history", true);
+pref("browser.urlbar.suggest.bookmark", true);
+pref("browser.urlbar.suggest.openpage", true);
+pref("browser.urlbar.suggest.searches", false);
+pref("browser.urlbar.userMadeSearchSuggestionsChoice", false);
+// 4 here means the suggestion notification will be automatically
+// hidden the 4th day, so it will actually be shown on 3 different days.
+pref("browser.urlbar.daysBeforeHidingSuggestionsPrompt", 4);
+pref("browser.urlbar.lastSuggestionsPromptDate", 20160601);
+
+// Limit the number of characters sent to the current search engine to fetch
+// suggestions.
+pref("browser.urlbar.maxCharsForSearchSuggestions", 20);
+
+// Restrictions to current suggestions can also be applied (intersection).
+// Typed suggestion works only if history is set to true.
+pref("browser.urlbar.suggest.history.onlyTyped", false);
+
+pref("browser.urlbar.formatting.enabled", true);
+pref("browser.urlbar.trimURLs", true);
+
+#if defined(NIGHTLY_BUILD)
+pref("browser.urlbar.oneOffSearches", true);
+#else
+pref("browser.urlbar.oneOffSearches", false);
+#endif
+
+pref("browser.altClickSave", false);
+
+// Enable logging downloads operations to the Console.
+pref("browser.download.loglevel", "Error");
+
+// Number of milliseconds to wait for the http headers (and thus
+// the Content-Disposition filename) before giving up and falling back to
+// picking a filename without that info in hand so that the user sees some
+// feedback from their action.
+pref("browser.download.saveLinkAsFilenameTimeout", 4000);
+
+pref("browser.download.useDownloadDir", true);
+pref("browser.download.folderList", 1);
+pref("browser.download.manager.addToRecentDocs", true);
+pref("browser.download.manager.resumeOnWakeDelay", 10000);
+
+#ifdef RELEASE_OR_BETA
+pref("browser.download.showPanelDropmarker", false);
+#else
+pref("browser.download.showPanelDropmarker", true);
+#endif
+
+// This allows disabling the animated notifications shown by
+// the Downloads Indicator when a download starts or completes.
+pref("browser.download.animateNotifications", true);
+
+// This records whether or not the panel has been shown at least once.
+pref("browser.download.panel.shown", false);
+
+#ifndef XP_MACOSX
+pref("browser.helperApps.deleteTempFileOnExit", true);
+#endif
+
+// search engines URL
+pref("browser.search.searchEnginesURL", "https://addons.mozilla.org/%LOCALE%/firefox/search-engines/");
+
+// pointer to the default engine name
+pref("browser.search.defaultenginename", "chrome://browser-region/locale/region.properties");
+
+// Ordering of Search Engines in the Engine list.
+pref("browser.search.order.1", "chrome://browser-region/locale/region.properties");
+pref("browser.search.order.2", "chrome://browser-region/locale/region.properties");
+pref("browser.search.order.3", "chrome://browser-region/locale/region.properties");
+
+// Market-specific search defaults
+// This is disabled globally, and then enabled for individual locales
+// in firefox-l10n.js (eg. it's enabled for en-US).
+pref("browser.search.geoSpecificDefaults", false);
+pref("browser.search.geoSpecificDefaults.url", "https://search.services.mozilla.com/1/%APP%/%VERSION%/%CHANNEL%/%LOCALE%/%REGION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%");
+
+// US specific default (used as a fallback if the geoSpecificDefaults request fails).
+pref("browser.search.defaultenginename.US", "data:text/plain,browser.search.defaultenginename.US=Google");
+pref("browser.search.order.US.1", "data:text/plain,browser.search.order.US.1=Google");
+pref("browser.search.order.US.2", "data:text/plain,browser.search.order.US.2=Yahoo");
+pref("browser.search.order.US.3", "data:text/plain,browser.search.order.US.3=Bing");
+
+// search bar results always open in a new tab
+pref("browser.search.openintab", false);
+
+// context menu searches open in the foreground
+pref("browser.search.context.loadInBackground", false);
+
+// comma seperated list of of engines to hide in the search panel.
+pref("browser.search.hiddenOneOffs", "");
+
+#ifndef RELEASE_OR_BETA
+pref("browser.search.reset.enabled", true);
+#endif
+
+pref("browser.sessionhistory.max_entries", 50);
+
+// Built-in default permissions.
+pref("permissions.manager.defaultsUrl", "resource://app/defaults/permissions");
+
+// handle links targeting new windows
+// 1=current window/tab, 2=new window, 3=new tab in most recent window
+pref("browser.link.open_newwindow", 3);
+
+// handle external links (i.e. links opened from a different application)
+// default: use browser.link.open_newwindow
+// 1-3: see browser.link.open_newwindow for interpretation
+pref("browser.link.open_newwindow.override.external", -1);
+
+// 0: no restrictions - divert everything
+// 1: don't divert window.open at all
+// 2: don't divert window.open with features
+pref("browser.link.open_newwindow.restriction", 2);
+
+// If true, this pref causes windows opened by window.open to be forced into new
+// tabs (rather than potentially opening separate windows, depending on
+// window.open arguments) when the browser is in fullscreen mode.
+// We set this differently on Mac because the fullscreen implementation there is
+// different.
+#ifdef XP_MACOSX
+pref("browser.link.open_newwindow.disabled_in_fullscreen", true);
+#else
+pref("browser.link.open_newwindow.disabled_in_fullscreen", false);
+#endif
+
+// Tabbed browser
+pref("browser.tabs.closeWindowWithLastTab", true);
+pref("browser.tabs.insertRelatedAfterCurrent", true);
+pref("browser.tabs.warnOnClose", true);
+pref("browser.tabs.warnOnCloseOtherTabs", true);
+pref("browser.tabs.warnOnOpen", true);
+pref("browser.tabs.maxOpenBeforeWarn", 15);
+pref("browser.tabs.loadInBackground", true);
+pref("browser.tabs.opentabfor.middleclick", true);
+pref("browser.tabs.loadDivertedInBackground", false);
+pref("browser.tabs.loadBookmarksInBackground", false);
+pref("browser.tabs.tabClipWidth", 140);
+pref("browser.tabs.animate", true);
+#ifdef UNIX_BUT_NOT_MAC
+pref("browser.tabs.drawInTitlebar", false);
+#else
+pref("browser.tabs.drawInTitlebar", true);
+#endif
+
+// When tabs opened by links in other tabs via a combination of
+// browser.link.open_newwindow being set to 3 and target="_blank" etc are
+// closed:
+// true return to the tab that opened this tab (its owner)
+// false return to the adjacent tab (old default)
+pref("browser.tabs.selectOwnerOnClose", true);
+
+pref("browser.tabs.showAudioPlayingIcon", true);
+// This should match Chromium's audio indicator delay.
+pref("browser.tabs.delayHidingAudioPlayingIconMS", 3000);
+
+pref("browser.tabs.dontfocusfordialogs", true);
+
+pref("browser.ctrlTab.previews", false);
+
+// By default, do not export HTML at shutdown.
+// If true, at shutdown the bookmarks in your menu and toolbar will
+// be exported as HTML to the bookmarks.html file.
+pref("browser.bookmarks.autoExportHTML", false);
+
+// The maximum number of daily bookmark backups to
+// keep in {PROFILEDIR}/bookmarkbackups. Special values:
+// -1: unlimited
+// 0: no backups created (and deletes all existing backups)
+pref("browser.bookmarks.max_backups", 15);
+
+pref("browser.bookmarks.showRecentlyBookmarked", true);
+
+// Scripts & Windows prefs
+pref("dom.disable_open_during_load", true);
+pref("javascript.options.showInConsole", true);
+#ifdef DEBUG
+pref("general.warnOnAboutConfig", false);
+#endif
+
+// This is the pref to control the location bar, change this to true to
+// force this - this makes the origin of popup windows more obvious to avoid
+// spoofing. We would rather not do it by default because it affects UE for web
+// applications, but without it there isn't a really good way to prevent chrome
+// spoofing, see bug 337344
+pref("dom.disable_window_open_feature.location", true);
+// prevent JS from setting status messages
+pref("dom.disable_window_status_change", true);
+// allow JS to move and resize existing windows
+pref("dom.disable_window_move_resize", false);
+// prevent JS from monkeying with window focus, etc
+pref("dom.disable_window_flip", true);
+
+// popups.policy 1=allow,2=reject
+pref("privacy.popups.policy", 1);
+pref("privacy.popups.usecustom", true);
+pref("privacy.popups.showBrowserMessage", true);
+
+pref("privacy.item.cookies", false);
+
+pref("privacy.clearOnShutdown.history", true);
+pref("privacy.clearOnShutdown.formdata", true);
+pref("privacy.clearOnShutdown.downloads", true);
+pref("privacy.clearOnShutdown.cookies", true);
+pref("privacy.clearOnShutdown.cache", true);
+pref("privacy.clearOnShutdown.sessions", true);
+pref("privacy.clearOnShutdown.offlineApps", false);
+pref("privacy.clearOnShutdown.siteSettings", false);
+pref("privacy.clearOnShutdown.openWindows", false);
+
+pref("privacy.cpd.history", true);
+pref("privacy.cpd.formdata", true);
+pref("privacy.cpd.passwords", false);
+pref("privacy.cpd.downloads", true);
+pref("privacy.cpd.cookies", true);
+pref("privacy.cpd.cache", true);
+pref("privacy.cpd.sessions", true);
+pref("privacy.cpd.offlineApps", false);
+pref("privacy.cpd.siteSettings", false);
+pref("privacy.cpd.openWindows", false);
+
+// What default should we use for the time span in the sanitizer:
+// 0 - Clear everything
+// 1 - Last Hour
+// 2 - Last 2 Hours
+// 3 - Last 4 Hours
+// 4 - Today
+// 5 - Last 5 minutes
+// 6 - Last 24 hours
+pref("privacy.sanitize.timeSpan", 1);
+pref("privacy.sanitize.sanitizeOnShutdown", false);
+
+pref("privacy.sanitize.migrateFx3Prefs", false);
+
+pref("privacy.panicButton.enabled", true);
+
+pref("privacy.firstparty.isolate", false);
+
+pref("network.proxy.share_proxy_settings", false); // use the same proxy settings for all protocols
+
+// simple gestures support
+pref("browser.gesture.swipe.left", "Browser:BackOrBackDuplicate");
+pref("browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate");
+pref("browser.gesture.swipe.up", "cmd_scrollTop");
+pref("browser.gesture.swipe.down", "cmd_scrollBottom");
+#ifdef XP_MACOSX
+pref("browser.gesture.pinch.latched", true);
+pref("browser.gesture.pinch.threshold", 150);
+#else
+pref("browser.gesture.pinch.latched", false);
+pref("browser.gesture.pinch.threshold", 25);
+#endif
+#ifdef XP_WIN
+// Enabled for touch input display zoom.
+pref("browser.gesture.pinch.out", "cmd_fullZoomEnlarge");
+pref("browser.gesture.pinch.in", "cmd_fullZoomReduce");
+pref("browser.gesture.pinch.out.shift", "cmd_fullZoomReset");
+pref("browser.gesture.pinch.in.shift", "cmd_fullZoomReset");
+#else
+// Disabled by default due to issues with track pad input.
+pref("browser.gesture.pinch.out", "");
+pref("browser.gesture.pinch.in", "");
+pref("browser.gesture.pinch.out.shift", "");
+pref("browser.gesture.pinch.in.shift", "");
+#endif
+pref("browser.gesture.twist.latched", false);
+pref("browser.gesture.twist.threshold", 0);
+pref("browser.gesture.twist.right", "cmd_gestureRotateRight");
+pref("browser.gesture.twist.left", "cmd_gestureRotateLeft");
+pref("browser.gesture.twist.end", "cmd_gestureRotateEnd");
+pref("browser.gesture.tap", "cmd_fullZoomReset");
+
+pref("browser.snapshots.limit", 0);
+
+// 0: Nothing happens
+// 1: Scrolling contents
+// 2: Go back or go forward, in your history
+// 3: Zoom in or out.
+#ifdef XP_MACOSX
+// On OS X, if the wheel has one axis only, shift+wheel comes through as a
+// horizontal scroll event. Thus, we can't assign anything other than normal
+// scrolling to shift+wheel.
+pref("mousewheel.with_alt.action", 2);
+pref("mousewheel.with_shift.action", 1);
+// On MacOS X, control+wheel is typically handled by system and we don't
+// receive the event. So, command key which is the main modifier key for
+// acceleration is the best modifier for zoom-in/out. However, we should keep
+// the control key setting for backward compatibility.
+pref("mousewheel.with_meta.action", 3); // command key on Mac
+// Disable control-/meta-modified horizontal mousewheel events, since
+// those are used on Mac as part of modified swipe gestures (e.g.
+// Left swipe+Cmd = go back in a new tab).
+pref("mousewheel.with_control.action.override_x", 0);
+pref("mousewheel.with_meta.action.override_x", 0);
+#else
+pref("mousewheel.with_alt.action", 1);
+pref("mousewheel.with_shift.action", 2);
+pref("mousewheel.with_meta.action", 1); // win key on Win, Super/Hyper on Linux
+#endif
+pref("mousewheel.with_control.action",3);
+pref("mousewheel.with_win.action", 1);
+
+pref("browser.xul.error_pages.enabled", true);
+pref("browser.xul.error_pages.expert_bad_cert", false);
+
+// Enable captive portal detection.
+pref("network.captive-portal-service.enabled", true);
+
+// If true, network link events will change the value of navigator.onLine
+pref("network.manage-offline-status", true);
+
+// We want to make sure mail URLs are handled externally...
+pref("network.protocol-handler.external.mailto", true); // for mail
+pref("network.protocol-handler.external.news", true); // for news
+pref("network.protocol-handler.external.snews", true); // for secure news
+pref("network.protocol-handler.external.nntp", true); // also news
+#ifdef XP_WIN
+pref("network.protocol-handler.external.ms-windows-store", true);
+#endif
+
+// ...without warning dialogs
+pref("network.protocol-handler.warn-external.mailto", false);
+pref("network.protocol-handler.warn-external.news", false);
+pref("network.protocol-handler.warn-external.snews", false);
+pref("network.protocol-handler.warn-external.nntp", false);
+#ifdef XP_WIN
+pref("network.protocol-handler.warn-external.ms-windows-store", false);
+#endif
+
+// By default, all protocol handlers are exposed. This means that
+// the browser will respond to openURL commands for all URL types.
+// It will also try to open link clicks inside the browser before
+// failing over to the system handlers.
+pref("network.protocol-handler.expose-all", true);
+pref("network.protocol-handler.expose.mailto", false);
+pref("network.protocol-handler.expose.news", false);
+pref("network.protocol-handler.expose.snews", false);
+pref("network.protocol-handler.expose.nntp", false);
+
+pref("accessibility.typeaheadfind", false);
+pref("accessibility.typeaheadfind.timeout", 5000);
+pref("accessibility.typeaheadfind.linksonly", false);
+pref("accessibility.typeaheadfind.flashBar", 1);
+
+#ifdef NIGHTLY_BUILD
+pref("findbar.highlightAll", true);
+pref("findbar.modalHighlight", true);
+#endif
+
+// Tracks when accessibility is loaded into the previous session.
+pref("accessibility.loadedInLastSession", false);
+
+pref("plugins.click_to_play", true);
+pref("plugins.testmode", false);
+
+pref("plugin.default.state", 1);
+
+// Plugins bundled in XPIs are enabled by default.
+pref("plugin.defaultXpi.state", 2);
+
+// Flash is enabled by default, and Java is click-to-activate by default on
+// all channels.
+pref("plugin.state.flash", 2);
+pref("plugin.state.java", 1);
+
+// On ESR only, we re-enable all plugins instead of only loading Flash.
+pref("plugin.load_flash_only", false);
+
+#ifdef XP_MACOSX
+pref("browser.preferences.animateFadeIn", true);
+#else
+pref("browser.preferences.animateFadeIn", false);
+#endif
+
+#ifdef XP_WIN
+pref("browser.preferences.instantApply", false);
+#else
+pref("browser.preferences.instantApply", true);
+#endif
+
+pref("browser.download.show_plugins_in_list", true);
+pref("browser.download.hide_plugins_without_extensions", true);
+
+// Backspace and Shift+Backspace behavior
+// 0 goes Back/Forward
+// 1 act like PgUp/PgDown
+// 2 and other values, nothing
+#ifdef UNIX_BUT_NOT_MAC
+pref("browser.backspace_action", 2);
+#else
+pref("browser.backspace_action", 0);
+#endif
+
+// this will automatically enable inline spellchecking (if it is available) for
+// editable elements in HTML
+// 0 = spellcheck nothing
+// 1 = check multi-line controls [default]
+// 2 = check multi/single line controls
+pref("layout.spellcheckDefault", 1);
+
+pref("browser.send_pings", false);
+
+/* initial web feed readers list */
+pref("browser.contentHandlers.types.0.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.0.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.0.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.1.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.1.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.1.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.2.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.2.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.2.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.3.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.3.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.3.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.4.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.4.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.4.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.5.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.5.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.5.type", "application/vnd.mozilla.maybe.feed");
+
+pref("browser.feeds.handler", "ask");
+pref("browser.videoFeeds.handler", "ask");
+pref("browser.audioFeeds.handler", "ask");
+
+// At startup, if the handler service notices that the version number in the
+// region.properties file is newer than the version number in the handler
+// service datastore, it will add any new handlers it finds in the prefs (as
+// seeded by this file) to its datastore.
+pref("gecko.handlerService.defaultHandlersVersion", "chrome://browser-region/locale/region.properties");
+
+// The default set of web-based protocol handlers shown in the application
+// selection dialog for webcal: ; I've arbitrarily picked 4 default handlers
+// per protocol, but if some locale wants more than that (or defaults for some
+// protocol not currently listed here), we should go ahead and add those.
+
+// webcal
+pref("gecko.handlerService.schemes.webcal.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// mailto
+pref("gecko.handlerService.schemes.mailto.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// irc
+pref("gecko.handlerService.schemes.irc.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// ircs
+pref("gecko.handlerService.schemes.ircs.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// By default, we don't want protocol/content handlers to be registered from a different host, see bug 402287
+pref("gecko.handlerService.allowRegisterFromDifferentHost", false);
+
+pref("browser.geolocation.warning.infoURL", "https://www.mozilla.org/%LOCALE%/firefox/geolocation/");
+
+pref("browser.EULA.version", 3);
+pref("browser.rights.version", 3);
+pref("browser.rights.3.shown", false);
+
+#ifdef DEBUG
+// Don't show the about:rights notification in debug builds.
+pref("browser.rights.override", true);
+#endif
+
+pref("browser.selfsupport.url", "https://self-repair.mozilla.org/%LOCALE%/repair");
+
+pref("browser.sessionstore.resume_from_crash", true);
+pref("browser.sessionstore.resume_session_once", false);
+
+// minimal interval between two save operations in milliseconds
+pref("browser.sessionstore.interval", 15000);
+// on which sites to save text data, POSTDATA and cookies
+// 0 = everywhere, 1 = unencrypted sites, 2 = nowhere
+pref("browser.sessionstore.privacy_level", 0);
+// how many tabs can be reopened (per window)
+pref("browser.sessionstore.max_tabs_undo", 10);
+// how many windows can be reopened (per session) - on non-OS X platforms this
+// pref may be ignored when dealing with pop-up windows to ensure proper startup
+pref("browser.sessionstore.max_windows_undo", 3);
+// number of crashes that can occur before the about:sessionrestore page is displayed
+// (this pref has no effect if more than 6 hours have passed since the last crash)
+pref("browser.sessionstore.max_resumed_crashes", 1);
+// number of back button session history entries to restore (-1 = all of them)
+pref("browser.sessionstore.max_serialize_back", 10);
+// number of forward button session history entries to restore (-1 = all of them)
+pref("browser.sessionstore.max_serialize_forward", -1);
+// restore_on_demand overrides MAX_CONCURRENT_TAB_RESTORES (sessionstore constant)
+// and restore_hidden_tabs. When true, tabs will not be restored until they are
+// focused (also applies to tabs that aren't visible). When false, the values
+// for MAX_CONCURRENT_TAB_RESTORES and restore_hidden_tabs are respected.
+// Selected tabs are always restored regardless of this pref.
+pref("browser.sessionstore.restore_on_demand", true);
+// Whether to automatically restore hidden tabs (i.e., tabs in other tab groups) or not
+pref("browser.sessionstore.restore_hidden_tabs", false);
+// If restore_on_demand is set, pinned tabs are restored on startup by default.
+// When set to true, this pref overrides that behavior, and pinned tabs will only
+// be restored when they are focused.
+pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
+// The version at which we performed the latest upgrade backup
+pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
+// How many upgrade backups should be kept
+pref("browser.sessionstore.upgradeBackup.maxUpgradeBackups", 3);
+// End-users should not run sessionstore in debug mode
+pref("browser.sessionstore.debug", false);
+// Causes SessionStore to ignore non-final update messages from
+// browser tabs that were not caused by a flush from the parent.
+// This is a testing flag and should not be used by end-users.
+pref("browser.sessionstore.debug.no_auto_updates", false);
+// Forget closed windows/tabs after two weeks
+pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000);
+
+// allow META refresh by default
+pref("accessibility.blockautorefresh", false);
+
+// Whether history is enabled or not.
+pref("places.history.enabled", true);
+
+// the (maximum) number of the recent visits to sample
+// when calculating frecency
+pref("places.frecency.numVisits", 10);
+
+// buckets (in days) for frecency calculation
+pref("places.frecency.firstBucketCutoff", 4);
+pref("places.frecency.secondBucketCutoff", 14);
+pref("places.frecency.thirdBucketCutoff", 31);
+pref("places.frecency.fourthBucketCutoff", 90);
+
+// weights for buckets for frecency calculations
+pref("places.frecency.firstBucketWeight", 100);
+pref("places.frecency.secondBucketWeight", 70);
+pref("places.frecency.thirdBucketWeight", 50);
+pref("places.frecency.fourthBucketWeight", 30);
+pref("places.frecency.defaultBucketWeight", 10);
+
+// bonus (in percent) for visit transition types for frecency calculations
+pref("places.frecency.embedVisitBonus", 0);
+pref("places.frecency.framedLinkVisitBonus", 0);
+pref("places.frecency.linkVisitBonus", 100);
+pref("places.frecency.typedVisitBonus", 2000);
+pref("places.frecency.bookmarkVisitBonus", 75);
+pref("places.frecency.downloadVisitBonus", 0);
+pref("places.frecency.permRedirectVisitBonus", 0);
+pref("places.frecency.tempRedirectVisitBonus", 0);
+pref("places.frecency.reloadVisitBonus", 0);
+pref("places.frecency.defaultVisitBonus", 0);
+
+// bonus (in percent) for place types for frecency calculations
+pref("places.frecency.unvisitedBookmarkBonus", 140);
+pref("places.frecency.unvisitedTypedBonus", 200);
+
+// Controls behavior of the "Add Exception" dialog launched from SSL error pages
+// 0 - don't pre-populate anything
+// 1 - pre-populate site URL, but don't fetch certificate
+// 2 - pre-populate site URL and pre-fetch certificate
+pref("browser.ssl_override_behavior", 2);
+
+// True if the user should be prompted when a web application supports
+// offline apps.
+pref("browser.offline-apps.notify", true);
+
+// if true, use full page zoom instead of text zoom
+pref("browser.zoom.full", true);
+
+// Whether or not to save and restore zoom levels on a per-site basis.
+pref("browser.zoom.siteSpecific", true);
+
+// Whether or not to update background tabs to the current zoom level.
+pref("browser.zoom.updateBackgroundTabs", true);
+
+// The breakpad report server to link to in about:crashes
+pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/");
+
+// URL for "Learn More" for Crash Reporter
+pref("toolkit.crashreporter.infoURL",
+ "https://www.mozilla.org/legal/privacy/firefox.html#crash-reporter");
+
+// base URL for web-based support pages
+pref("app.support.baseURL", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/");
+
+// a11y conflicts with e10s support page
+pref("app.support.e10sAccessibilityUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/accessibility-ppt");
+
+// base url for web-based feedback pages
+#ifdef MOZ_DEV_EDITION
+pref("app.feedback.baseURL", "https://input.mozilla.org/%LOCALE%/feedback/firefoxdev/%VERSION%/");
+#else
+pref("app.feedback.baseURL", "https://input.mozilla.org/%LOCALE%/feedback/%APP%/%VERSION%/");
+#endif
+
+
+// Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
+pref("security.alternate_certificate_error_page", "certerror");
+
+// Whether to start the private browsing mode at application startup
+pref("browser.privatebrowsing.autostart", false);
+
+// Don't try to alter this pref, it'll be reset the next time you use the
+// bookmarking dialog
+pref("browser.bookmarks.editDialog.firstEditField", "namePicker");
+
+pref("dom.ipc.plugins.flash.disable-protected-mode", false);
+
+// Feature-disable the protected-mode auto-flip
+pref("browser.flash-protected-mode-flip.enable", false);
+
+// Whether we've already flipped protected mode automatically
+pref("browser.flash-protected-mode-flip.done", false);
+
+pref("dom.ipc.shims.enabledWarnings", false);
+
+// Start the browser in e10s mode
+pref("browser.tabs.remote.autostart", false);
+pref("browser.tabs.remote.desktopbehavior", true);
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+// When this pref is true the Windows process sandbox will set up dummy
+// interceptions and log to the browser console when calls fail in the sandboxed
+// process and also if they are subsequently allowed by the broker process.
+// This will require a restart.
+pref("security.sandbox.windows.log", false);
+
+// Controls whether and how the Windows NPAPI plugin process is sandboxed.
+// To get a different setting for a particular plugin replace "default", with
+// the plugin's nice file name, see: nsPluginTag::GetNiceFileName.
+// On windows these levels are:
+// 0 - no sandbox
+// 1 - sandbox with USER_NON_ADMIN access token level
+// 2 - a more strict sandbox, which might cause functionality issues. This now
+// includes running at low integrity.
+// 3 - the strongest settings we seem to be able to use without breaking
+// everything, but will probably cause some functionality restrictions
+pref("dom.ipc.plugins.sandbox-level.default", 0);
+#if defined(_AMD64_)
+// The lines in PluginModuleParent.cpp should be changed in line with this.
+pref("dom.ipc.plugins.sandbox-level.flash", 2);
+#else
+pref("dom.ipc.plugins.sandbox-level.flash", 0);
+#endif
+
+#if defined(MOZ_CONTENT_SANDBOX)
+// This controls the strength of the Windows content process sandbox for testing
+// purposes. This will require a restart.
+// On windows these levels are:
+// See - security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+// SetSecurityLevelForContentProcess() for what the different settings mean.
+#if defined(NIGHTLY_BUILD)
+pref("security.sandbox.content.level", 2);
+#else
+pref("security.sandbox.content.level", 1);
+#endif
+
+// This controls the depth of stack trace that is logged when Windows sandbox
+// logging is turned on. This is only currently available for the content
+// process because the only other sandbox (for GMP) has too strict a policy to
+// allow stack tracing. This does not require a restart to take effect.
+pref("security.sandbox.windows.log.stackTraceDepth", 0);
+#endif
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
+// This pref is discussed in bug 1083344, the naming is inspired from its
+// Windows counterpart, but on Mac it's an integer which means:
+// 0 -> "no sandbox"
+// 1 -> "preliminary content sandboxing enabled: write access to
+// home directory is prevented"
+// 2 -> "preliminary content sandboxing enabled with profile protection:
+// write access to home directory is prevented, read and write access
+// to ~/Library and profile directories are prevented (excluding
+// $PROFILE/{extensions,weave})"
+// This setting is read when the content process is started. On Mac the content
+// process is killed when all windows are closed, so a change will take effect
+// when the 1st window is opened.
+#if defined(NIGHTLY_BUILD)
+pref("security.sandbox.content.level", 2);
+#else
+pref("security.sandbox.content.level", 1);
+#endif
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
+// This pref is introduced as part of bug 742434, the naming is inspired from
+// its Windows/Mac counterpart, but on Linux it's an integer which means:
+// 0 -> "no sandbox"
+// 1 -> "content sandbox using seccomp-bpf when available"
+// 2 -> "seccomp-bpf + file broker"
+// Content sandboxing on Linux is currently in the stage of
+// 'just getting it enabled', which includes a very permissive whitelist. We
+// enable seccomp-bpf on nightly to see if everything is running, or if we need
+// to whitelist more system calls.
+//
+// So the purpose of this setting is to allow nightly users to disable the
+// sandbox while we fix their problems. This way, they won't have to wait for
+// another nightly release which disables seccomp-bpf again.
+//
+// This setting may not be required anymore once we decide to permanently
+// enable the content sandbox.
+pref("security.sandbox.content.level", 2);
+#endif
+
+#if defined(XP_MACOSX) || defined(XP_WIN)
+#if defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
+// ID (a UUID when set by gecko) that is used to form the name of a
+// sandbox-writable temporary directory to be used by content processes
+// when a temporary writable file is required in a level 1 sandbox.
+pref("security.sandbox.content.tempDirSuffix", "");
+#endif
+#endif
+
+// This pref governs whether we attempt to work around problems caused by
+// plugins using OS calls to manipulate the cursor while running out-of-
+// process. These workarounds all involve intercepting (hooking) certain
+// OS calls in the plugin process, then arranging to make certain OS calls
+// in the browser process. Eventually plugins will be required to use the
+// NPAPI to manipulate the cursor, and these workarounds will be removed.
+// See bug 621117.
+#ifdef XP_MACOSX
+pref("dom.ipc.plugins.nativeCursorSupport", true);
+#endif
+
+#ifdef XP_WIN
+pref("browser.taskbar.previews.enable", false);
+pref("browser.taskbar.previews.max", 20);
+pref("browser.taskbar.previews.cachetime", 5);
+pref("browser.taskbar.lists.enabled", true);
+pref("browser.taskbar.lists.frequent.enabled", true);
+pref("browser.taskbar.lists.recent.enabled", false);
+pref("browser.taskbar.lists.maxListItemCount", 7);
+pref("browser.taskbar.lists.tasks.enabled", true);
+pref("browser.taskbar.lists.refreshInSeconds", 120);
+#endif
+
+// The sync engines to use.
+pref("services.sync.registerEngines", "Bookmarks,Form,History,Password,Prefs,Tab,Addons,ExtensionStorage");
+// Preferences to be synced by default
+pref("services.sync.prefs.sync.accessibility.blockautorefresh", true);
+pref("services.sync.prefs.sync.accessibility.browsewithcaret", true);
+pref("services.sync.prefs.sync.accessibility.typeaheadfind", true);
+pref("services.sync.prefs.sync.accessibility.typeaheadfind.linksonly", true);
+pref("services.sync.prefs.sync.addons.ignoreUserEnabledChanges", true);
+// The addons prefs related to repository verification are intentionally
+// not synced for security reasons. If a system is compromised, a user
+// could weaken the pref locally, install an add-on from an untrusted
+// source, and this would propagate automatically to other,
+// uncompromised Sync-connected devices.
+pref("services.sync.prefs.sync.browser.ctrlTab.previews", true);
+pref("services.sync.prefs.sync.browser.download.useDownloadDir", true);
+pref("services.sync.prefs.sync.browser.formfill.enable", true);
+pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
+pref("services.sync.prefs.sync.browser.newtabpage.enabled", true);
+pref("services.sync.prefs.sync.browser.newtabpage.enhanced", true);
+pref("services.sync.prefs.sync.browser.newtabpage.pinned", true);
+pref("services.sync.prefs.sync.browser.offline-apps.notify", true);
+pref("services.sync.prefs.sync.browser.safebrowsing.phishing.enabled", true);
+pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true);
+pref("services.sync.prefs.sync.browser.search.update", true);
+pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true);
+pref("services.sync.prefs.sync.browser.startup.homepage", true);
+pref("services.sync.prefs.sync.browser.startup.page", true);
+pref("services.sync.prefs.sync.browser.tabs.loadInBackground", true);
+pref("services.sync.prefs.sync.browser.tabs.warnOnClose", true);
+pref("services.sync.prefs.sync.browser.tabs.warnOnOpen", true);
+pref("services.sync.prefs.sync.browser.urlbar.autocomplete.enabled", true);
+pref("services.sync.prefs.sync.browser.urlbar.maxRichResults", true);
+pref("services.sync.prefs.sync.browser.urlbar.suggest.bookmark", true);
+pref("services.sync.prefs.sync.browser.urlbar.suggest.history", true);
+pref("services.sync.prefs.sync.browser.urlbar.suggest.history.onlyTyped", true);
+pref("services.sync.prefs.sync.browser.urlbar.suggest.openpage", true);
+pref("services.sync.prefs.sync.browser.urlbar.suggest.searches", true);
+pref("services.sync.prefs.sync.dom.disable_open_during_load", true);
+pref("services.sync.prefs.sync.dom.disable_window_flip", true);
+pref("services.sync.prefs.sync.dom.disable_window_move_resize", true);
+pref("services.sync.prefs.sync.dom.event.contextmenu.enabled", true);
+pref("services.sync.prefs.sync.extensions.personas.current", true);
+pref("services.sync.prefs.sync.extensions.update.enabled", true);
+pref("services.sync.prefs.sync.intl.accept_languages", true);
+pref("services.sync.prefs.sync.javascript.enabled", true);
+pref("services.sync.prefs.sync.layout.spellcheckDefault", true);
+pref("services.sync.prefs.sync.lightweightThemes.selectedThemeID", true);
+pref("services.sync.prefs.sync.lightweightThemes.usedThemes", true);
+pref("services.sync.prefs.sync.network.cookie.cookieBehavior", true);
+pref("services.sync.prefs.sync.network.cookie.lifetimePolicy", true);
+pref("services.sync.prefs.sync.network.cookie.lifetime.days", true);
+pref("services.sync.prefs.sync.network.cookie.thirdparty.sessionOnly", true);
+pref("services.sync.prefs.sync.permissions.default.image", true);
+pref("services.sync.prefs.sync.pref.advanced.images.disable_button.view_image", true);
+pref("services.sync.prefs.sync.pref.advanced.javascript.disable_button.advanced", true);
+pref("services.sync.prefs.sync.pref.downloads.disable_button.edit_actions", true);
+pref("services.sync.prefs.sync.pref.privacy.disable_button.cookie_exceptions", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.cache", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.cookies", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.downloads", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.formdata", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.history", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.offlineApps", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.sessions", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.siteSettings", true);
+pref("services.sync.prefs.sync.privacy.donottrackheader.enabled", true);
+pref("services.sync.prefs.sync.privacy.sanitize.sanitizeOnShutdown", true);
+pref("services.sync.prefs.sync.privacy.trackingprotection.enabled", true);
+pref("services.sync.prefs.sync.privacy.trackingprotection.pbmode.enabled", true);
+pref("services.sync.prefs.sync.security.OCSP.enabled", true);
+pref("services.sync.prefs.sync.security.OCSP.require", true);
+pref("services.sync.prefs.sync.security.default_personal_cert", true);
+pref("services.sync.prefs.sync.security.tls.version.min", true);
+pref("services.sync.prefs.sync.security.tls.version.max", true);
+pref("services.sync.prefs.sync.services.sync.syncedTabs.showRemoteIcons", true);
+pref("services.sync.prefs.sync.signon.rememberSignons", true);
+pref("services.sync.prefs.sync.spellchecker.dictionary", true);
+pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
+
+// A preference that controls whether we should show the icon for a remote tab.
+// This pref has no UI but exists because some people may be concerned that
+// fetching these icons to show remote tabs may leak information about that
+// user's tabs and bookmarks. Note this pref is also synced.
+pref("services.sync.syncedTabs.showRemoteIcons", true);
+
+pref("services.sync.sendTabToDevice.enabled", true);
+
+// Developer edition preferences
+#ifdef MOZ_DEV_EDITION
+sticky_pref("lightweightThemes.selectedThemeID", "firefox-devedition@mozilla.org");
+#else
+sticky_pref("lightweightThemes.selectedThemeID", "");
+#endif
+
+// Whether the character encoding menu is under the main Firefox button. This
+// preference is a string so that localizers can alter it.
+pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
+
+// Allow using tab-modal prompts when possible.
+pref("prompts.tab_modal.enabled", true);
+
+// Activates preloading of the new tab url.
+pref("browser.newtab.preload", true);
+
+// Remembers if the about:newtab intro has been shown
+// NOTE: This preference is unused but was not removed in case
+// this information will be valuable in the future.
+pref("browser.newtabpage.introShown", false);
+
+// Toggles the content of 'about:newtab'. Shows the grid when enabled.
+pref("browser.newtabpage.enabled", true);
+
+// Toggles the enhanced content of 'about:newtab'. Shows sponsored tiles.
+sticky_pref("browser.newtabpage.enhanced", true);
+
+// enables Activity Stream inspired layout
+pref("browser.newtabpage.compact", false);
+
+// enables showing basic placeholders for missing thumbnails
+pref("browser.newtabpage.thumbnailPlaceholder", false);
+
+// number of rows of newtab grid
+pref("browser.newtabpage.rows", 3);
+
+// number of columns of newtab grid
+pref("browser.newtabpage.columns", 5);
+
+// directory tiles download URL
+pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
+
+// endpoint to send newtab click and view pings
+pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
+
+// activates the remote-hosted newtab page
+pref("browser.newtabpage.remote", false);
+
+// remote newtab version targeted
+pref("browser.newtabpage.remote.version", "1");
+
+// Toggles endpoints allowed for remote newtab communications
+pref("browser.newtabpage.remote.mode", "production");
+
+// content-signature tests for remote newtab
+pref("browser.newtabpage.remote.content-signing-test", false);
+
+// verification keys for remote-hosted newtab page
+pref("browser.newtabpage.remote.keys", "");
+
+// Enable the DOM fullscreen API.
+pref("full-screen-api.enabled", true);
+
+// Startup Crash Tracking
+// number of startup crashes that can occur before starting into safe mode automatically
+// (this pref has no effect if more than 6 hours have passed since the last crash)
+pref("toolkit.startup.max_resumed_crashes", 3);
+
+// Completely disable pdf.js as an option to preview pdfs within firefox.
+// Note: if this is not disabled it does not necessarily mean pdf.js is the pdf
+// handler just that it is an option.
+pref("pdfjs.disabled", false);
+// Used by pdf.js to know the first time firefox is run with it installed so it
+// can become the default pdf viewer.
+pref("pdfjs.firstRun", true);
+// The values of preferredAction and alwaysAskBeforeHandling before pdf.js
+// became the default.
+pref("pdfjs.previousHandler.preferredAction", 0);
+pref("pdfjs.previousHandler.alwaysAskBeforeHandling", false);
+
+// The maximum amount of decoded image data we'll willingly keep around (we
+// might keep around more than this, but we'll try to get down to this value).
+// (This is intentionally on the high side; see bug 746055.)
+pref("image.mem.max_decoded_image_kb", 256000);
+
+pref("social.sidebar.unload_timeout_ms", 10000);
+
+// Activation from inside of share panel is possible if activationPanelEnabled
+// is true. Pref'd off for release while usage testing is done through beta.
+pref("social.share.activationPanelEnabled", true);
+pref("social.shareDirectory", "https://activations.cdn.mozilla.net/sharePanel.html");
+
+// Block insecure active content on https pages
+pref("security.mixed_content.block_active_content", true);
+
+// Show degraded UI for http pages with password fields.
+pref("security.insecure_password.ui.enabled", true);
+
+// Show in-content login form warning UI for insecure login fields
+pref("security.insecure_field_warning.contextual.enabled", true);
+
+// 1 = allow MITM for certificate pinning checks.
+pref("security.cert_pinning.enforcement_level", 1);
+
+
+// Override the Gecko-default value of false for Firefox.
+pref("plain_text.wrap_long_lines", true);
+
+// If this turns true, Moz*Gesture events are not called stopPropagation()
+// before content.
+pref("dom.debug.propagate_gesture_events_through_content", false);
+
+// The request URL of the GeoLocation backend.
+#ifdef RELEASE_OR_BETA
+pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
+#else
+pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
+#endif
+
+#ifdef XP_MACOSX
+#ifdef RELEASE_OR_BETA
+pref("geo.provider.use_corelocation", false);
+#else
+pref("geo.provider.use_corelocation", true);
+#endif
+#endif
+
+#ifdef XP_WIN
+pref("geo.provider.ms-windows-location", false);
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+#ifdef MOZ_GPSD
+#ifdef RELEASE_OR_BETA
+pref("geo.provider.use_gpsd", false);
+#else
+pref("geo.provider.use_gpsd", true);
+#endif
+#endif
+#endif
+
+// Necko IPC security checks only needed for app isolation for cookies/cache/etc:
+// currently irrelevant for desktop e10s
+pref("network.disable.ipc.security", true);
+
+// CustomizableUI debug logging.
+pref("browser.uiCustomization.debug", false);
+
+// CustomizableUI state of the browser's user interface
+pref("browser.uiCustomization.state", "");
+
+// The remote content URL shown for FxA signup. Must use HTTPS.
+pref("identity.fxaccounts.remote.signup.uri", "https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v3");
+
+// The URL where remote content that forces re-authentication for Firefox Accounts
+// should be fetched. Must use HTTPS.
+pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v3");
+
+// The remote content URL shown for signin in. Must use HTTPS.
+pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v3");
+
+// The remote content URL where FxAccountsWebChannel messages originate.
+pref("identity.fxaccounts.remote.webchannel.uri", "https://accounts.firefox.com/");
+
+// The value of the context query parameter passed in some fxa requests when config
+// discovery is enabled.
+pref("identity.fxaccounts.contextParam", "fx_desktop_v3");
+
+// The URL we take the user to when they opt to "manage" their Firefox Account.
+// Note that this will always need to be in the same TLD as the
+// "identity.fxaccounts.remote.signup.uri" pref.
+pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings?service=sync&context=fx_desktop_v3");
+
+// The remote URL of the FxA Profile Server
+pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
+
+// The remote URL of the FxA OAuth Server
+pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
+
+// Whether we display profile images in the UI or not.
+pref("identity.fxaccounts.profile_image.enabled", true);
+
+// Token server used by the FxA Sync identity.
+pref("identity.sync.tokenserver.uri", "https://token.services.mozilla.com/1.0/sync/1.5");
+
+// URLs for promo links to mobile browsers. Note that consumers are expected to
+// append a value for utm_campaign.
+pref("identity.mobilepromo.android", "https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=");
+pref("identity.mobilepromo.ios", "https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=");
+
+// Migrate any existing Firefox Account data from the default profile to the
+// Developer Edition profile.
+#ifdef MOZ_DEV_EDITION
+pref("identity.fxaccounts.migrateToDevEdition", true);
+#else
+pref("identity.fxaccounts.migrateToDevEdition", false);
+#endif
+
+// On GTK, we now default to showing the menubar only when alt is pressed:
+#ifdef MOZ_WIDGET_GTK
+pref("ui.key.menuAccessKeyFocuses", true);
+#endif
+
+// Encrypted media extensions.
+#ifdef XP_LINUX
+// On Linux EME is visible but disabled by default. This is so that the
+// "Play DRM content" checkbox in the Firefox UI is unchecked by default.
+// DRM requires downloading and installing proprietary binaries, which
+// users on an open source operating systems didn't opt into. The first
+// time a site using EME is encountered, the user will be prompted to
+// enable DRM, whereupon the EME plugin binaries will be downloaded if
+// permission is granted.
+pref("media.eme.enabled", false);
+#else
+pref("media.eme.enabled", true);
+#endif
+pref("media.eme.apiVisible", true);
+
+// Decode using Gecko Media Plugins in <video>, if a system decoder is not
+// availble and the preferred GMP is available.
+pref("media.gmp.decoder.enabled", false);
+
+// If decoding-via-GMP is turned on for <video>, use Adobe's GMP for decoding,
+// if it's available. Note: We won't fallback to another GMP if Adobe's is not
+// installed.
+pref("media.gmp.decoder.aac", 2);
+pref("media.gmp.decoder.h264", 2);
+
+// Whether we should run a test-pattern through EME GMPs before assuming they'll
+// decode H.264.
+pref("media.gmp.trial-create.enabled", true);
+
+// Note: when media.gmp-*.visible is true, provided we're running on a
+// supported platform/OS version, the corresponding CDM appears in the
+// plugins list, Firefox will download the GMP/CDM if enabled, and our
+// UI to re-enable EME prompts the user to re-enable EME if it's disabled
+// and script requests EME. If *.visible is false, we won't show the UI
+// to enable the CDM if its disabled; it's as if the keysystem is completely
+// unsupported.
+
+#ifdef MOZ_ADOBE_EME
+pref("media.gmp-eme-adobe.visible", true);
+// When Adobe EME is enabled in the build system, we don't actually enable
+// the plugin by default, so that it doesn't download and install by default.
+// When Adobe EME is first used, Firefox will prompt the user to enable it,
+// and then download the CDM.
+pref("media.gmp-eme-adobe.enabled", false);
+#endif
+
+#ifdef MOZ_WIDEVINE_EME
+pref("media.gmp-widevinecdm.visible", true);
+pref("media.gmp-widevinecdm.enabled", true);
+#endif
+
+// Play with different values of the decay time and get telemetry,
+// 0 means to randomize (and persist) the experiment value in users' profiles,
+// -1 means no experiment is run and we use the preferred value for frecency (6h)
+pref("browser.cache.frecency_experiment", 0);
+
+pref("browser.translation.detectLanguage", false);
+pref("browser.translation.neverForLanguages", "");
+// Show the translation UI bits, like the info bar, notification icon and preferences.
+pref("browser.translation.ui.show", false);
+// Allows to define the translation engine. Bing is default, Yandex may optionally switched on.
+pref("browser.translation.engine", "bing");
+
+// Telemetry settings.
+// Determines if Telemetry pings can be archived locally.
+pref("toolkit.telemetry.archive.enabled", true);
+
+// Telemetry experiments settings.
+pref("experiments.enabled", true);
+pref("experiments.manifest.fetchIntervalSeconds", 86400);
+pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
+// Whether experiments are supported by the current application profile.
+pref("experiments.supported", true);
+
+// Enable GMP support in the addon manager.
+pref("media.gmp-provider.enabled", true);
+
+#ifdef NIGHTLY_BUILD
+pref("privacy.trackingprotection.ui.enabled", true);
+#else
+pref("privacy.trackingprotection.ui.enabled", false);
+#endif
+pref("privacy.trackingprotection.introCount", 0);
+pref("privacy.trackingprotection.introURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tracking-protection/start/");
+
+// Enable Contextual Identity Containers
+#ifdef NIGHTLY_BUILD
+pref("privacy.userContext.enabled", true);
+pref("privacy.userContext.ui.enabled", true);
+pref("privacy.usercontext.about_newtab_segregation.enabled", true);
+#else
+pref("privacy.userContext.enabled", false);
+pref("privacy.userContext.ui.enabled", false);
+pref("privacy.usercontext.about_newtab_segregation.enabled", false);
+#endif
+
+#ifndef RELEASE_OR_BETA
+// At the moment, autostart.2 is used, while autostart.1 is unused.
+// We leave it here set to false to reset users' defaults and allow
+// us to change everybody to true in the future, when desired.
+pref("browser.tabs.remote.autostart.1", false);
+pref("browser.tabs.remote.autostart.2", true);
+#endif
+
+// For the about:tabcrashed page
+pref("browser.tabs.crashReporting.sendReport", true);
+pref("browser.tabs.crashReporting.includeURL", false);
+pref("browser.tabs.crashReporting.requestEmail", false);
+pref("browser.tabs.crashReporting.emailMe", false);
+pref("browser.tabs.crashReporting.email", "");
+
+// Enable e10s add-on interposition by default.
+pref("extensions.interposition.enabled", true);
+pref("extensions.interposition.prefetching", true);
+
+// Enable blocking of e10s for add-on users on beta/release.
+#ifdef RELEASE_OR_BETA
+pref("extensions.e10sBlocksEnabling", true);
+#endif
+
+// How often to check for CPOW timeouts. CPOWs are only timed out by
+// the hang monitor.
+pref("dom.ipc.cpow.timeout", 500);
+
+// Causes access on unsafe CPOWs from browser code to throw by default.
+pref("dom.ipc.cpows.forbid-unsafe-from-browser", true);
+
+// Don't allow add-ons marked as multiprocessCompatible to use CPOWs.
+pref("dom.ipc.cpows.forbid-cpows-in-compat-addons", true);
+
+// ...except for these add-ons:
+pref("dom.ipc.cpows.allow-cpows-in-compat-addons", "{b9db16a4-6edc-47ec-a1f4-b86292ed211d},firegestures@xuldev.org,{DDC359D1-844A-42a7-9AA1-88A850A938A8},privateTab@infocatcher,mousegesturessuite@lemon_juice.addons.mozilla.org,treestyletab@piro.sakura.ne.jp,cliqz@cliqz.com,{AE93811A-5C9A-4d34-8462-F7B864FC4696},contextsearch2@lwz.addons.mozilla.org,{EF522540-89F5-46b9-B6FE-1829E2B572C6},{677a8f98-fd64-40b0-a883-b8c95d0cbf17},images@wink.su,fx-devtools,toolkit/require,url_advisor@kaspersky.com,{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d},{dc572301-7619-498c-a57d-39143191b318},dta@downthemall.net,{86095750-AD15-46d8-BF32-C0789F7E6A32},screenwise-prod@google.com,{91aa5abe-9de4-4347-b7b5-322c38dd9271},secureLogin@blueimp.net,ich@maltegoetz.de,come.back.block.image.from@cat-in-136.blogspot.com,{7b1bf0b6-a1b9-42b0-b75d-252036438bdc},s3crypto@data,{1e0fd655-5aea-4b4c-a583-f76ef1e3af9c},akahuku.fx.sp@toshiakisp.github.io,{aff87fa2-a58e-4edd-b852-0a20203c1e17},{1018e4d6-728f-4b20-ad56-37578a4de76b},rehostimage@engy.us,lazarus@interclue.com,{b2e69492-2358-071a-7056-24ad0c3defb1},flashstopper@byo.co.il,{e4a8a97b-f2ed-450b-b12d-ee082ba24781},jid1-f3mYMbCpz2AZYl@jetpack,{8c550e28-88c9-4764-bb52-aa489cf2efcd},{37fa1426-b82d-11db-8314-0800200c9a66},{ac2cfa60-bc96-11e0-962b-0800200c9a66},igetter@presenta.net,killspinners@byo.co.il,abhere2@moztw.org,{fc6339b8-9581-4fc7-b824-dffcb091fcb7},wampi@wink.su,backtrack@byalexv.co.uk,Gladiator_X@mail.ru,{73a6fe31-595d-460b-a920-fcc0f8843232},{46551EC9-40F0-4e47-8E18-8E5CF550CFB8},acewebextension_unlisted@acestream.org,@screen_maker,yasearch@yandex.ru,sp@avast.com,s3google@translator,igetterextension@presenta.net,{C1A2A613-35F1-4FCF-B27F-2840527B6556},screenwise-testing@google.com,helper-sig@savefrom.net,browser-loader,ImageSaver@Merci.chao,proxtube@abz.agency,wrc@avast.com,{9AA46F4F-4DC7-4c06-97AF-5035170634FE},jid1-CikLKKPVkw6ipw@jetpack,artur.dubovoy@gmail.com,nlgfeb@nlgfeb.ext,{A065A84F-95B6-433A-A0C8-4C040B77CE8A},fdm_ffext@freedownloadmanager.org");
+
+// Enable e10s hang monitoring (slow script checking and plugin hang
+// detection).
+pref("dom.ipc.processHangMonitor", true);
+
+#ifdef DEBUG
+// Don't report hangs in DEBUG builds. They're too slow and often a
+// debugger is attached.
+pref("dom.ipc.reportProcessHangs", false);
+#else
+pref("dom.ipc.reportProcessHangs", true);
+#endif
+
+pref("browser.reader.detectedFirstArticle", false);
+// Don't limit how many nodes we care about on desktop:
+pref("reader.parse-node-limit", 0);
+
+// On desktop, we want the URLs to be included here for ease of debugging,
+// and because (normally) these errors are not persisted anywhere.
+pref("reader.errors.includeURLs", true);
+
+pref("view_source.tab", true);
+
+pref("dom.serviceWorkers.enabled", false);
+pref("dom.serviceWorkers.openWindow.enabled", false);
+
+// Enable Push API.
+pref("dom.push.enabled", false);
+
+// These are the thumbnail width/height set in about:newtab.
+// If you change this, ENSURE IT IS THE SAME SIZE SET
+// by about:newtab. These values are in CSS pixels.
+pref("toolkit.pageThumbs.minWidth", 280);
+pref("toolkit.pageThumbs.minHeight", 190);
+
+// Enable speech synthesis
+pref("media.webspeech.synth.enabled", true);
+
+pref("browser.esedbreader.loglevel", "Error");
+
+pref("browser.laterrun.enabled", false);
+
+pref("browser.migrate.automigrate.enabled", false);
+// 4 here means the suggestion notification will be automatically
+// hidden the 4th day, so it will actually be shown on 3 different days.
+pref("browser.migrate.automigrate.daysToOfferUndo", 4);
+pref("browser.migrate.automigrate.ui.enabled", true);
+
+pref("browser.migrate.chrome.history.limit", 0);
+pref("browser.migrate.chrome.history.maxAgeInDays", 0);
+
+// Enable browser frames for use on desktop. Only exposed to chrome callers.
+pref("dom.mozBrowserFramesEnabled", true);
+
+pref("extensions.pocket.enabled", true);
+
+pref("signon.schemeUpgrades", true);
+
+// "Simplify Page" feature in Print Preview. This feature is disabled by default
+// in toolkit.
+//
+// This feature is only enabled on Nightly for Linux until bug 1306295 is fixed.
+// For non-Linux, this feature is only enabled up to early Beta.
+#ifdef UNIX_BUT_NOT_MAC
+#if defined(NIGHTLY_BUILD)
+pref("print.use_simplify_page", true);
+#endif
+#else
+#if defined(EARLY_BETA_OR_EARLIER)
+pref("print.use_simplify_page", true);
+#endif
+#endif
+
+// Space separated list of URLS that are allowed to send objects (instead of
+// only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
+pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
+
+// Whether or not the browser should scan for unsubmitted
+// crash reports, and then show a notification for submitting
+// those reports.
+#ifdef EARLY_BETA_OR_EARLIER
+pref("browser.crashReports.unsubmittedCheck.enabled", true);
+#else
+pref("browser.crashReports.unsubmittedCheck.enabled", false);
+#endif
+
+// chancesUntilSuppress is how many times we'll show the unsubmitted
+// crash report notification across different days and shutdown
+// without a user choice before we suppress the notification for
+// some number of days.
+pref("browser.crashReports.unsubmittedCheck.chancesUntilSuppress", 4);
+pref("browser.crashReports.unsubmittedCheck.autoSubmit2", false);
+
+#ifdef NIGHTLY_BUILD
+// Enable the (fairly costly) client/server validation on nightly only. The other prefs
+// controlling validation are located in /services/sync/services-sync.js
+pref("services.sync.validation.enabled", true);
+#endif
diff --git a/browser/app/profile/pagethemes.rdf b/browser/app/profile/pagethemes.rdf
new file mode 100644
index 000000000..3d09b95f5
--- /dev/null
+++ b/browser/app/profile/pagethemes.rdf
@@ -0,0 +1,7 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
+
diff --git a/browser/app/profile/prefs.js b/browser/app/profile/prefs.js
new file mode 100644
index 000000000..8c6f0d639
--- /dev/null
+++ b/browser/app/profile/prefs.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+# Mozilla User Preferences
+
+/* Do not edit this file.
+ *
+ * If you make changes to this file while the browser is running,
+ * the changes will be overwritten when the browser exits.
+ *
+ * To make a manual change to preferences, you can visit the URL about:config
+ */
diff --git a/browser/app/splash.rc b/browser/app/splash.rc
new file mode 100644
index 000000000..c406b7985
--- /dev/null
+++ b/browser/app/splash.rc
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include "nsNativeAppSupportWin.h"
+
+1 24 "firefox.exe.manifest"
+
+IDI_APPICON ICON FIREFOX_ICO
+IDI_DOCUMENT ICON DOCUMENT_ICO
+IDI_APPLICATION ICON FIREFOX_ICO
+IDI_NEWWINDOW ICON NEWWINDOW_ICO
+IDI_NEWTAB ICON NEWTAB_ICO
+IDI_PBMODE ICON PBMODE_ICO
+
+STRINGTABLE DISCARDABLE
+BEGIN
+ IDS_STARTMENU_APPNAME, "@MOZ_APP_DISPLAYNAME@"
+END
diff --git a/browser/base/.eslintrc.js b/browser/base/.eslintrc.js
new file mode 100644
index 000000000..e6cf2032e
--- /dev/null
+++ b/browser/base/.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/browser/base/content/aboutDialog-appUpdater.js b/browser/base/content/aboutDialog-appUpdater.js
new file mode 100644
index 000000000..4b4fc6618
--- /dev/null
+++ b/browser/base/content/aboutDialog-appUpdater.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/. */
+
+// Note: this file is included in aboutDialog.xul if MOZ_UPDATER is defined.
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+
+const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
+const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
+
+var gAppUpdater;
+
+function onUnload(aEvent) {
+ if (gAppUpdater.isChecking)
+ gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK);
+ // Safe to call even when there isn't a download in progress.
+ gAppUpdater.removeDownloadListener();
+ gAppUpdater = null;
+}
+
+
+function appUpdater()
+{
+ XPCOMUtils.defineLazyServiceGetter(this, "aus",
+ "@mozilla.org/updates/update-service;1",
+ "nsIApplicationUpdateService");
+ XPCOMUtils.defineLazyServiceGetter(this, "checker",
+ "@mozilla.org/updates/update-checker;1",
+ "nsIUpdateChecker");
+ XPCOMUtils.defineLazyServiceGetter(this, "um",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager");
+
+ this.updateDeck = document.getElementById("updateDeck");
+
+ // Hide the update deck when the update window is already open and it's not
+ // already applied, to avoid syncing issues between them. Applied updates
+ // don't have any information to sync between the windows as they both just
+ // show the "Restart to continue"-type button.
+ if (Services.wm.getMostRecentWindow("Update:Wizard") &&
+ !this.isApplied) {
+ this.updateDeck.hidden = true;
+ return;
+ }
+
+ this.bundle = Services.strings.
+ createBundle("chrome://browser/locale/browser.properties");
+
+ let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual");
+ let manualLink = document.getElementById("manualLink");
+ manualLink.value = manualURL;
+ manualLink.href = manualURL;
+ document.getElementById("failedLink").href = manualURL;
+
+ if (this.updateDisabledAndLocked) {
+ this.selectPanel("adminDisabled");
+ return;
+ }
+
+ if (this.isPending || this.isApplied) {
+ this.selectPanel("apply");
+ return;
+ }
+
+ if (this.aus.isOtherInstanceHandlingUpdates) {
+ this.selectPanel("otherInstanceHandlingUpdates");
+ return;
+ }
+
+ if (this.isDownloading) {
+ this.startDownload();
+ // selectPanel("downloading") is called from setupDownloadingUI().
+ return;
+ }
+
+ // Honor the "Never check for updates" option by not only disabling background
+ // update checks, but also in the About dialog, by presenting a
+ // "Check for updates" button.
+ // If updates are found, the user is then asked if he wants to "Update to <version>".
+ if (!this.updateEnabled ||
+ Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+ this.selectPanel("checkForUpdates");
+ return;
+ }
+
+ // That leaves the options
+ // "Check for updates, but let me choose whether to install them", and
+ // "Automatically install updates".
+ // In both cases, we check for updates without asking.
+ // In the "let me choose" case, we ask before downloading though, in onCheckComplete.
+ this.checkForUpdates();
+}
+
+appUpdater.prototype =
+{
+ // true when there is an update check in progress.
+ isChecking: false,
+
+ // true when there is an update already staged / ready to be applied.
+ get isPending() {
+ if (this.update) {
+ return this.update.state == "pending" ||
+ this.update.state == "pending-service" ||
+ this.update.state == "pending-elevate";
+ }
+ return this.um.activeUpdate &&
+ (this.um.activeUpdate.state == "pending" ||
+ this.um.activeUpdate.state == "pending-service" ||
+ this.um.activeUpdate.state == "pending-elevate");
+ },
+
+ // true when there is an update already installed in the background.
+ get isApplied() {
+ if (this.update)
+ return this.update.state == "applied" ||
+ this.update.state == "applied-service";
+ return this.um.activeUpdate &&
+ (this.um.activeUpdate.state == "applied" ||
+ this.um.activeUpdate.state == "applied-service");
+ },
+
+ // true when there is an update download in progress.
+ get isDownloading() {
+ if (this.update)
+ return this.update.state == "downloading";
+ return this.um.activeUpdate &&
+ this.um.activeUpdate.state == "downloading";
+ },
+
+ // true when updating is disabled by an administrator.
+ get updateDisabledAndLocked() {
+ return !this.updateEnabled &&
+ Services.prefs.prefIsLocked("app.update.enabled");
+ },
+
+ // true when updating is enabled.
+ get updateEnabled() {
+ try {
+ return Services.prefs.getBoolPref("app.update.enabled");
+ }
+ catch (e) { }
+ return true; // Firefox default is true
+ },
+
+ // true when updating in background is enabled.
+ get backgroundUpdateEnabled() {
+ return this.updateEnabled &&
+ gAppUpdater.aus.canStageUpdates;
+ },
+
+ // true when updating is automatic.
+ get updateAuto() {
+ try {
+ return Services.prefs.getBoolPref("app.update.auto");
+ }
+ catch (e) { }
+ return true; // Firefox default is true
+ },
+
+ /**
+ * Sets the panel of the updateDeck.
+ *
+ * @param aChildID
+ * The id of the deck's child to select, e.g. "apply".
+ */
+ selectPanel: function(aChildID) {
+ let panel = document.getElementById(aChildID);
+
+ let button = panel.querySelector("button");
+ if (button) {
+ if (aChildID == "downloadAndInstall") {
+ let updateVersion = gAppUpdater.update.displayVersion;
+ button.label = this.bundle.formatStringFromName("update.downloadAndInstallButton.label", [updateVersion], 1);
+ button.accessKey = this.bundle.GetStringFromName("update.downloadAndInstallButton.accesskey");
+ }
+ this.updateDeck.selectedPanel = panel;
+ if (!document.commandDispatcher.focusedElement || // don't steal the focus
+ document.commandDispatcher.focusedElement.localName == "button") // except from the other buttons
+ button.focus();
+
+ } else {
+ this.updateDeck.selectedPanel = panel;
+ }
+ },
+
+ /**
+ * Check for updates
+ */
+ checkForUpdates: function() {
+ // Clear prefs that could prevent a user from discovering available updates.
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
+ }
+ this.selectPanel("checkingForUpdates");
+ this.isChecking = true;
+ this.checker.checkForUpdates(this.updateCheckListener, true);
+ // after checking, onCheckComplete() is called
+ },
+
+ /**
+ * Handles oncommand for the "Restart to Update" button
+ * which is presented after the download has been downloaded.
+ */
+ buttonRestartAfterDownload: function() {
+ if (!this.isPending && !this.isApplied) {
+ return;
+ }
+
+ gAppUpdater.selectPanel("restarting");
+
+ // Notify all windows that an application quit has been requested.
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
+ createInstance(Components.interfaces.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data) {
+ gAppUpdater.selectPanel("apply");
+ return;
+ }
+
+ let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
+ getService(Components.interfaces.nsIAppStartup);
+
+ // If already in safe mode restart in safe mode (bug 327119)
+ if (Services.appinfo.inSafeMode) {
+ appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
+ return;
+ }
+
+ appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
+ Components.interfaces.nsIAppStartup.eRestart);
+ },
+
+ /**
+ * Implements nsIUpdateCheckListener. The methods implemented by
+ * nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
+ * to make it clear which are used by each interface.
+ */
+ updateCheckListener: {
+ /**
+ * See nsIUpdateService.idl
+ */
+ onCheckComplete: function(aRequest, aUpdates, aUpdateCount) {
+ gAppUpdater.isChecking = false;
+ gAppUpdater.update = gAppUpdater.aus.
+ selectUpdate(aUpdates, aUpdates.length);
+ if (!gAppUpdater.update) {
+ gAppUpdater.selectPanel("noUpdatesFound");
+ return;
+ }
+
+ if (gAppUpdater.update.unsupported) {
+ if (gAppUpdater.update.detailsURL) {
+ let unsupportedLink = document.getElementById("unsupportedLink");
+ unsupportedLink.href = gAppUpdater.update.detailsURL;
+ }
+ gAppUpdater.selectPanel("unsupportedSystem");
+ return;
+ }
+
+ if (!gAppUpdater.aus.canApplyUpdates) {
+ gAppUpdater.selectPanel("manualUpdate");
+ return;
+ }
+
+ if (gAppUpdater.updateAuto) // automatically download and install
+ gAppUpdater.startDownload();
+ else // ask
+ gAppUpdater.selectPanel("downloadAndInstall");
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ onError: function(aRequest, aUpdate) {
+ // Errors in the update check are treated as no updates found. If the
+ // update check fails repeatedly without a success the user will be
+ // notified with the normal app update user interface so this is safe.
+ gAppUpdater.isChecking = false;
+ gAppUpdater.selectPanel("noUpdatesFound");
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
+ !aIID.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+ },
+
+ /**
+ * Starts the download of an update mar.
+ */
+ startDownload: function() {
+ if (!this.update)
+ this.update = this.um.activeUpdate;
+ this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
+ this.update.setProperty("foregroundDownload", "true");
+
+ this.aus.pauseDownload();
+ let state = this.aus.downloadUpdate(this.update, false);
+ if (state == "failed") {
+ this.selectPanel("downloadFailed");
+ return;
+ }
+
+ this.setupDownloadingUI();
+ },
+
+ /**
+ * Switches to the UI responsible for tracking the download.
+ */
+ setupDownloadingUI: function() {
+ this.downloadStatus = document.getElementById("downloadStatus");
+ this.downloadStatus.value =
+ DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size);
+ this.selectPanel("downloading");
+ this.aus.addDownloadListener(this);
+ },
+
+ removeDownloadListener: function() {
+ if (this.aus) {
+ this.aus.removeDownloadListener(this);
+ }
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStartRequest: function(aRequest, aContext) {
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ switch (aStatusCode) {
+ case Components.results.NS_ERROR_UNEXPECTED:
+ if (this.update.selectedPatch.state == "download-failed" &&
+ (this.update.isCompleteUpdate || this.update.patchCount != 2)) {
+ // Verification error of complete patch, informational text is held in
+ // the update object.
+ this.removeDownloadListener();
+ this.selectPanel("downloadFailed");
+ break;
+ }
+ // Verification failed for a partial patch, complete patch is now
+ // downloading so return early and do NOT remove the download listener!
+ break;
+ case Components.results.NS_BINDING_ABORTED:
+ // Do not remove UI listener since the user may resume downloading again.
+ break;
+ case Components.results.NS_OK:
+ this.removeDownloadListener();
+ if (this.backgroundUpdateEnabled) {
+ this.selectPanel("applying");
+ let self = this;
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // Update the UI when the background updater is finished
+ let status = aData;
+ if (status == "applied" || status == "applied-service" ||
+ status == "pending" || status == "pending-service" ||
+ status == "pending-elevate") {
+ // If the update is successfully applied, or if the updater has
+ // fallen back to non-staged updates, show the "Restart to Update"
+ // button.
+ self.selectPanel("apply");
+ } else if (status == "failed") {
+ // Background update has failed, let's show the UI responsible for
+ // prompting the user to update manually.
+ self.selectPanel("downloadFailed");
+ } else if (status == "downloading") {
+ // We've fallen back to downloading the full update because the
+ // partial update failed to get staged in the background.
+ // Therefore we need to keep our observer.
+ self.setupDownloadingUI();
+ return;
+ }
+ Services.obs.removeObserver(arguments.callee, "update-staged");
+ }, "update-staged", false);
+ } else {
+ this.selectPanel("apply");
+ }
+ break;
+ default:
+ this.removeDownloadListener();
+ this.selectPanel("downloadFailed");
+ break;
+ }
+ },
+
+ /**
+ * See nsIProgressEventSink.idl
+ */
+ onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
+ },
+
+ /**
+ * See nsIProgressEventSink.idl
+ */
+ onProgress: function(aRequest, aContext, aProgress, aProgressMax) {
+ this.downloadStatus.value =
+ DownloadUtils.getTransferTotal(aProgress, aProgressMax);
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIProgressEventSink) &&
+ !aIID.equals(Components.interfaces.nsIRequestObserver) &&
+ !aIID.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
diff --git a/browser/base/content/aboutDialog.css b/browser/base/content/aboutDialog.css
new file mode 100644
index 000000000..65830c8bb
--- /dev/null
+++ b/browser/base/content/aboutDialog.css
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#aboutDialog {
+ width: 620px;
+ /* Set an explicit line-height to avoid discrepancies in 'auto' spacing
+ across screens with different device DPI, which may cause font metrics
+ to round differently. */
+ line-height: 1.5;
+}
+
+#rightBox {
+ background-image: url("chrome://branding/content/about-wordmark.png");
+ background-repeat: no-repeat;
+ /* padding-top creates room for the wordmark */
+ padding-top: 38px;
+ margin-top:20px;
+}
+
+#rightBox:-moz-locale-dir(rtl) {
+ background-position: 100% 0;
+}
+
+#bottomBox {
+ padding: 15px 10px 0;
+}
+
+#version {
+ font-weight: bold;
+ margin-top: 10px;
+ margin-left: 0;
+ -moz-user-select: text;
+ -moz-user-focus: normal;
+ cursor: text;
+}
+
+#version:-moz-locale-dir(rtl) {
+ direction: ltr;
+ text-align: right;
+ margin-left: 5px;
+ margin-right: 0;
+}
+
+#releasenotes {
+ margin-top: 10px;
+}
+
+#distribution,
+#distributionId {
+ display: none;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.text-blurb {
+ margin-bottom: 10px;
+ margin-inline-start: 0;
+ padding-inline-start: 0;
+}
+
+#updateButton,
+#updateDeck > hbox > label {
+ margin-inline-start: 0;
+ padding-inline-start: 0;
+}
+
+.update-throbber {
+ width: 16px;
+ min-height: 16px;
+ margin-inline-end: 3px;
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ .update-throbber {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ }
+}
+
+description > .text-link,
+description > .text-link:focus {
+ margin: 0px;
+ padding: 0px;
+}
+
+.bottom-link,
+.bottom-link:focus {
+ text-align: center;
+ margin: 0 40px;
+}
+
+#currentChannel {
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js
new file mode 100644
index 000000000..569a65adb
--- /dev/null
+++ b/browser/base/content/aboutDialog.js
@@ -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/. */
+
+"use strict";
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+function init(aEvent)
+{
+ if (aEvent.target != document)
+ return;
+
+ try {
+ var distroId = Services.prefs.getCharPref("distribution.id");
+ if (distroId) {
+ var distroVersion = Services.prefs.getCharPref("distribution.version");
+
+ var distroIdField = document.getElementById("distributionId");
+ distroIdField.value = distroId + " - " + distroVersion;
+ distroIdField.style.display = "block";
+
+ try {
+ // This is in its own try catch due to bug 895473 and bug 900925.
+ var distroAbout = Services.prefs.getComplexValue("distribution.about",
+ Components.interfaces.nsISupportsString);
+ var distroField = document.getElementById("distribution");
+ distroField.value = distroAbout;
+ distroField.style.display = "block";
+ }
+ catch (ex) {
+ // Pref is unset
+ Components.utils.reportError(ex);
+ }
+ }
+ }
+ catch (e) {
+ // Pref is unset
+ }
+
+ // Include the build ID and display warning if this is an "a#" (nightly or aurora) build
+ let versionField = document.getElementById("version");
+ let version = Services.appinfo.version;
+ if (/a\d+$/.test(version)) {
+ let buildID = Services.appinfo.appBuildID;
+ let year = buildID.slice(0, 4);
+ let month = buildID.slice(4, 6);
+ let day = buildID.slice(6, 8);
+ versionField.textContent += ` (${year}-${month}-${day})`;
+
+ document.getElementById("experimental").hidden = false;
+ document.getElementById("communityDesc").hidden = true;
+ }
+
+ // Append "(32-bit)" or "(64-bit)" build architecture to the version number:
+ let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+ let archResource = Services.appinfo.is64Bit
+ ? "aboutDialog.architecture.sixtyFourBit"
+ : "aboutDialog.architecture.thirtyTwoBit";
+ let arch = bundle.GetStringFromName(archResource);
+ versionField.textContent += ` (${arch})`;
+
+ if (AppConstants.MOZ_UPDATER) {
+ gAppUpdater = new appUpdater();
+
+ let channelLabel = document.getElementById("currentChannel");
+ let currentChannelText = document.getElementById("currentChannelText");
+ channelLabel.value = UpdateUtils.UpdateChannel;
+ if (/^release($|\-)/.test(channelLabel.value))
+ currentChannelText.hidden = true;
+ }
+
+ if (AppConstants.platform == "macosx") {
+ // it may not be sized at this point, and we need its width to calculate its position
+ window.sizeToContent();
+ window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
+ }
+}
diff --git a/browser/base/content/aboutDialog.xul b/browser/base/content/aboutDialog.xul
new file mode 100644
index 000000000..cbb07a5e1
--- /dev/null
+++ b/browser/base/content/aboutDialog.xul
@@ -0,0 +1,157 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/aboutDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://branding/content/aboutDialog.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
+%aboutDialogDTD;
+]>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="aboutDialog"
+ windowtype="Browser:About"
+ onload="init(event);"
+#ifdef MOZ_UPDATER
+ onunload="onUnload(event);"
+#endif
+#ifdef XP_MACOSX
+ inwindowmenu="false"
+#else
+ title="&aboutDialog.title;"
+#endif
+ role="dialog"
+ aria-describedby="version distribution distributionId communityDesc contributeDesc trademark"
+ >
+
+ <script type="application/javascript" src="chrome://browser/content/aboutDialog.js"/>
+#ifdef MOZ_UPDATER
+ <script type="application/javascript" src="chrome://browser/content/aboutDialog-appUpdater.js"/>
+#endif
+ <vbox id="aboutDialogContainer">
+ <hbox id="clientBox">
+ <vbox id="leftBox" flex="1"/>
+ <vbox id="rightBox" flex="1">
+ <hbox align="baseline">
+#expand <label id="version">__MOZ_APP_VERSION_DISPLAY__</label>
+#ifndef NIGHTLY_BUILD
+#expand <label id="releasenotes" class="text-link" href="https://www.mozilla.org/firefox/__MOZ_APP_VERSION__/releasenotes/">&releaseNotes.link;</label>
+#endif
+ </hbox>
+
+ <label id="distribution" class="text-blurb"/>
+ <label id="distributionId" class="text-blurb"/>
+
+ <vbox id="detailsBox">
+ <vbox id="updateBox">
+#ifdef MOZ_UPDATER
+ <deck id="updateDeck" orient="vertical">
+ <hbox id="checkForUpdates" align="center">
+ <button id="checkForUpdatesButton" align="start"
+ label="&update.checkForUpdatesButton.label;"
+ accesskey="&update.checkForUpdatesButton.accesskey;"
+ oncommand="gAppUpdater.checkForUpdates();"/>
+ <spacer flex="1"/>
+ </hbox>
+ <hbox id="downloadAndInstall" align="center">
+ <button id="downloadAndInstallButton" align="start"
+ oncommand="gAppUpdater.startDownload();"/>
+ <!-- label and accesskey will be filled by JS -->
+ <spacer flex="1"/>
+ </hbox>
+ <hbox id="apply" align="center">
+ <button id="updateButton" align="start"
+ label="&update.updateButton.label2;"
+ accesskey="&update.updateButton.accesskey;"
+ oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
+ <spacer flex="1"/>
+ </hbox>
+ <hbox id="checkingForUpdates" align="center">
+ <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
+ </hbox>
+ <hbox id="downloading" align="center">
+ <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
+ </hbox>
+ <hbox id="applying" align="center">
+ <image class="update-throbber"/><label>&update.applying;</label>
+ </hbox>
+ <hbox id="downloadFailed" align="center">
+ <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
+ </hbox>
+ <hbox id="adminDisabled" align="center">
+ <label>&update.adminDisabled;</label>
+ </hbox>
+ <hbox id="noUpdatesFound" align="center">
+ <label>&update.noUpdatesFound;</label>
+ </hbox>
+ <hbox id="otherInstanceHandlingUpdates" align="center">
+ <label>&update.otherInstanceHandlingUpdates;</label>
+ </hbox>
+ <hbox id="manualUpdate" align="center">
+ <label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
+ </hbox>
+ <hbox id="unsupportedSystem" align="center">
+ <label>&update.unsupported.start;</label><label id="unsupportedLink" class="text-link">&update.unsupported.linkText;</label><label>&update.unsupported.end;</label>
+ </hbox>
+ <hbox id="restarting" align="center">
+ <label>&update.restarting;</label>
+ </hbox>
+ </deck>
+#endif
+ </vbox>
+
+#ifdef MOZ_UPDATER
+ <description class="text-blurb" id="currentChannelText">
+ &channel.description.start;<label id="currentChannel"/>&channel.description.end;
+ </description>
+#endif
+ <vbox id="experimental" hidden="true">
+ <description class="text-blurb" id="warningDesc">
+ &warningDesc.version;
+#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
+ &warningDesc.telemetryDesc;
+#endif
+ </description>
+ <description class="text-blurb" id="communityExperimentalDesc">
+ &community.exp.start;<label class="text-link" href="http://www.mozilla.org/">&community.exp.mozillaLink;</label>&community.exp.middle;<label class="text-link" useoriginprincipal="true" href="about:credits">&community.exp.creditsLink;</label>&community.exp.end;
+ </description>
+ </vbox>
+ <description class="text-blurb" id="communityDesc">
+ &community.start2;<label class="text-link" href="http://www.mozilla.org/">&community.mozillaLink;</label>&community.middle2;<label class="text-link" useoriginprincipal="true" href="about:credits">&community.creditsLink;</label>&community.end3;
+ </description>
+ <description class="text-blurb" id="contributeDesc">
+ &helpus.start;<label class="text-link" href="https://sendto.mozilla.org/page/contribute/Give-Now?source=mozillaorg_default_footer&#38;ref=firefox_about&#38;utm_campaign=firefox_about&#38;tm_source=firefox&#38;tm_medium=referral&#38;utm_content=20140929_FireFoxAbout">&helpus.donateLink;</label>&helpus.middle;<label class="text-link" href="http://www.mozilla.org/contribute/">&helpus.getInvolvedLink;</label>&helpus.end;
+ </description>
+ </vbox>
+ </vbox>
+ </hbox>
+ <vbox id="bottomBox">
+ <hbox pack="center">
+ <label class="text-link bottom-link" useoriginprincipal="true" href="about:license">&bottomLinks.license;</label>
+ <label class="text-link bottom-link" useoriginprincipal="true" href="about:rights">&bottomLinks.rights;</label>
+ <label class="text-link bottom-link" href="https://www.mozilla.org/privacy/">&bottomLinks.privacy;</label>
+ </hbox>
+ <description id="trademark">&trademarkInfo.part1;</description>
+ </vbox>
+ </vbox>
+
+ <keyset>
+ <key keycode="VK_ESCAPE" oncommand="window.close();"/>
+ </keyset>
+
+#ifdef XP_MACOSX
+#include browserMountPoints.inc
+#endif
+</window>
diff --git a/browser/base/content/aboutNetError.xhtml b/browser/base/content/aboutNetError.xhtml
new file mode 100644
index 000000000..f2de106c2
--- /dev/null
+++ b/browser/base/content/aboutNetError.xhtml
@@ -0,0 +1,699 @@
+<?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 % netErrorDTD
+ SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+ <!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>&loadError.label;</title>
+ <link rel="stylesheet" href="chrome://browser/skin/aboutNetError.css" type="text/css" media="all" />
+ <!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
+ toolkit/components/places/src/nsFaviconService.h should be updated. -->
+ <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
+
+ <script type="application/javascript"><![CDATA[
+ // The following parameters are parsed from the error URL:
+ // e - the error code
+ // s - custom CSS class to allow alternate styling/favicons
+ // d - error description
+ // captive - "true" to indicate we're behind a captive portal.
+ // Any other value is ignored.
+
+ // Note that this file uses document.documentURI to get
+ // the URL (with the format from above). This is because
+ // document.location.href gets the current URI off the docshell,
+ // which is the URL displayed in the location bar, i.e.
+ // the URI that the user attempted to load.
+
+ let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
+
+ // Set to true on init if the error code is nssBadCert.
+ let gIsCertError;
+
+ function getErrorCode()
+ {
+ return searchParams.get("e");
+ }
+
+ function getCSSClass()
+ {
+ return searchParams.get("s");
+ }
+
+ function getDescription()
+ {
+ return searchParams.get("d");
+ }
+
+ function isCaptive() {
+ return searchParams.get("captive") == "true";
+ }
+
+ function retryThis(buttonEl)
+ {
+ // Note: The application may wish to handle switching off "offline mode"
+ // before this event handler runs, but using a capturing event handler.
+
+ // Session history has the URL of the page that failed
+ // to load, not the one of the error page. So, just call
+ // reload(), which will also repost POST data correctly.
+ try {
+ location.reload();
+ } catch (e) {
+ // We probably tried to reload a URI that caused an exception to
+ // occur; e.g. a nonexistent file.
+ }
+
+ buttonEl.disabled = true;
+ }
+
+ function doOverride(buttonEl) {
+ var event = new CustomEvent("AboutNetErrorOverride", {bubbles:true});
+ document.dispatchEvent(event);
+ retryThis(buttonEl);
+ }
+
+ function toggleDisplay(node) {
+ const toggle = {
+ "": "block",
+ "none": "block",
+ "block": "none"
+ };
+ return (node.style.display = toggle[node.style.display]);
+ }
+
+ function showCertificateErrorReporting() {
+ // Display error reporting UI
+ document.getElementById("certificateErrorReporting").style.display = "block";
+ }
+
+ function showPrefChangeContainer() {
+ const panel = document.getElementById("prefChangeContainer");
+ panel.style.display = "block";
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+ document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
+ const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
+ document.dispatchEvent(event);
+ });
+ addAutofocus("prefResetButton", "beforeend");
+ }
+
+ function setupAdvancedButton(allowOverride) {
+ // Get the hostname and add it to the panel
+ var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
+ var panel = document.getElementById(panelId);
+ for (var span of panel.querySelectorAll("span.hostname")) {
+ span.textContent = document.location.hostname;
+ }
+ if (!gIsCertError) {
+ panel.replaceChild(document.getElementById("errorLongDesc"),
+ document.getElementById("advancedLongDesc"));
+ }
+
+ // Register click handler for the weakCryptoAdvancedPanel
+ document.getElementById("advancedButton")
+ .addEventListener("click", function togglePanelVisibility() {
+ toggleDisplay(panel);
+ if (gIsCertError) {
+ // Toggling the advanced panel must ensure that the debugging
+ // information panel is hidden as well, since it's opened by the
+ // error code link in the advanced panel.
+ var div = document.getElementById("certificateErrorDebugInformation");
+ div.style.display = "none";
+ }
+
+ if (panel.style.display == "block") {
+ // send event to trigger telemetry ping
+ var event = new CustomEvent("AboutNetErrorUIExpanded", {bubbles:true});
+ document.dispatchEvent(event);
+ }
+ });
+
+ if (allowOverride) {
+ document.getElementById("overrideWeakCryptoPanel").style.display = "flex";
+ var overrideLink = document.getElementById("overrideWeakCrypto");
+ overrideLink.addEventListener("click", () => doOverride(overrideLink), false);
+ }
+ if (!gIsCertError) {
+ return;
+ }
+
+ if (getCSSClass() == "expertBadCert") {
+ toggleDisplay(document.getElementById("badCertAdvancedPanel"));
+ // Toggling the advanced panel must ensure that the debugging
+ // information panel is hidden as well, since it's opened by the
+ // error code link in the advanced panel.
+ var div = document.getElementById("certificateErrorDebugInformation");
+ div.style.display = "none";
+ }
+
+ disallowCertOverridesIfNeeded();
+
+ document.getElementById("badCertTechnicalInfo").textContent = getDescription();
+ }
+
+ function disallowCertOverridesIfNeeded() {
+ var cssClass = getCSSClass();
+ // Disallow overrides if this is a Strict-Transport-Security
+ // host and the cert is bad (STS Spec section 7.3) or if the
+ // certerror is in a frame (bug 633691).
+ if (cssClass == "badStsCert" || window != top) {
+ document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
+ }
+ if (cssClass == "badStsCert") {
+ document.getElementById("badStsCertExplanation").removeAttribute("hidden");
+ }
+ }
+
+ function initPage()
+ {
+ var err = getErrorCode();
+ gIsCertError = (err == "nssBadCert");
+ // Only worry about captive portals if this is a cert error.
+ let showCaptivePortalUI = isCaptive() && gIsCertError;
+ if (showCaptivePortalUI) {
+ err = "captivePortal";
+ }
+
+ // if it's an unknown error or there's no title or description
+ // defined, get the generic message
+ var errTitle = document.getElementById("et_" + err);
+ var errDesc = document.getElementById("ed_" + err);
+ if (!errTitle || !errDesc)
+ {
+ errTitle = document.getElementById("et_generic");
+ errDesc = document.getElementById("ed_generic");
+ }
+
+ document.querySelector(".title-text").innerHTML = errTitle.innerHTML;
+
+ var sd = document.getElementById("errorShortDescText");
+ if (sd) {
+ if (gIsCertError) {
+ sd.innerHTML = errDesc.innerHTML;
+ }
+ else {
+ sd.textContent = getDescription();
+ }
+ }
+ if (showCaptivePortalUI) {
+ initPageCaptivePortal();
+ return;
+ }
+ if (gIsCertError) {
+ initPageCertError();
+ return;
+ }
+ addAutofocus("errorTryAgain");
+
+ document.body.className = "neterror";
+
+ var ld = document.getElementById("errorLongDesc");
+ if (ld)
+ {
+ ld.innerHTML = errDesc.innerHTML;
+ }
+
+ if (err == "sslv3Used") {
+ document.getElementById("learnMoreContainer").style.display = "block";
+ var learnMoreLink = document.getElementById("learnMoreLink");
+ learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-sslv3-error-messages-firefox";
+ document.body.className = "certerror";
+ }
+
+ if (err == "weakCryptoUsed") {
+ document.body.className = "certerror";
+ }
+
+ // remove undisplayed errors to avoid bug 39098
+ var errContainer = document.getElementById("errorContainer");
+ errContainer.parentNode.removeChild(errContainer);
+
+ var className = getCSSClass();
+ if (className && className != "expertBadCert") {
+ // Associate a CSS class with the root of the page, if one was passed in,
+ // to allow custom styling.
+ // Not "expertBadCert" though, don't want to deal with the favicon
+ document.documentElement.className = className;
+
+ // Also, if they specified a CSS class, they must supply their own
+ // favicon. In order to trigger the browser to repaint though, we
+ // need to remove/add the link element.
+ var favicon = document.getElementById("favicon");
+ var faviconParent = favicon.parentNode;
+ faviconParent.removeChild(favicon);
+ favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
+ faviconParent.appendChild(favicon);
+ }
+
+ if (err == "remoteXUL") {
+ // Remove the "Try again" button for remote XUL errors given that
+ // it is useless.
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+ }
+
+ if (err == "cspBlocked") {
+ // Remove the "Try again" button for CSP violations, since it's
+ // almost certainly useless. (Bug 553180)
+ document.getElementById("netErrorButtonContainer").style.display = "none";
+ }
+
+ window.addEventListener("AboutNetErrorOptions", function(evt) {
+ // Pinning errors are of type nssFailure2
+ if (getErrorCode() == "nssFailure2" || getErrorCode() == "weakCryptoUsed") {
+ document.getElementById("learnMoreContainer").style.display = "block";
+ var learnMoreLink = document.getElementById("learnMoreLink");
+ // nssFailure2 also gets us other non-overrideable errors. Choose
+ // a "learn more" link based on description:
+ if (getDescription().includes("mozilla_pkix_error_key_pinning_failure")) {
+ learnMoreLink.href = "https://support.mozilla.org/kb/certificate-pinning-reports";
+ }
+ if (getErrorCode() == "weakCryptoUsed") {
+ learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-weak-crypto-error-messages-firefox";
+ }
+
+ var options = JSON.parse(evt.detail);
+ if (options && options.enabled) {
+ var checkbox = document.getElementById("automaticallyReportInFuture");
+ showCertificateErrorReporting();
+ if (options.automatic) {
+ // set the checkbox
+ checkbox.checked = true;
+ }
+
+ checkbox.addEventListener("change", function(evt) {
+ var event = new CustomEvent("AboutNetErrorSetAutomatic",
+ {bubbles:true, detail:evt.target.checked});
+ document.dispatchEvent(event);
+ }, false);
+ }
+ const hasPrefStyleError = [
+ "interrupted", // This happens with subresources that are above the max tls
+ "SSL_ERROR_PROTOCOL_VERSION_ALERT",
+ "SSL_ERROR_UNSUPPORTED_VERSION",
+ "SSL_ERROR_NO_CYPHER_OVERLAP",
+ "SSL_ERROR_NO_CIPHERS_SUPPORTED"
+ ].some((substring) => getDescription().includes(substring));
+ // If it looks like an error that is user config based
+ if (getErrorCode() == "nssFailure2" && hasPrefStyleError && options && options.changedCertPrefs) {
+ showPrefChangeContainer();
+ }
+ }
+ if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
+ setupAdvancedButton(getErrorCode() == "weakCryptoUsed");
+ }
+ }.bind(this), true, true);
+
+ var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
+ document.dispatchEvent(event);
+
+ if (err == "inadequateSecurityError") {
+ // Remove the "Try again" button for HTTP/2 inadequate security as it
+ // is useless.
+ document.getElementById("errorTryAgain").style.display = "none";
+
+ var container = document.getElementById("errorLongDesc");
+ for (var span of container.querySelectorAll("span.hostname")) {
+ span.textContent = document.location.hostname;
+ }
+ }
+
+ addDomainErrorLinks();
+ }
+
+ function initPageCaptivePortal()
+ {
+ document.body.className = "captiveportal";
+ document.title = document.getElementById("captivePortalPageTitle").textContent;
+
+ document.getElementById("openPortalLoginPageButton")
+ .addEventListener("click", () => {
+ let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles:true});
+ document.dispatchEvent(event);
+ });
+
+ addAutofocus("openPortalLoginPageButton");
+ setupAdvancedButton(true);
+
+ addDomainErrorLinks();
+
+ // When the portal is freed, an event is generated by the frame script
+ // that we can pick up and attempt to reload the original page.
+ window.addEventListener("AboutNetErrorCaptivePortalFreed", () => {
+ document.location.reload();
+ });
+ }
+
+ function initPageCertError() {
+ document.body.className = "certerror";
+ document.title = document.getElementById("certErrorPageTitle").textContent;
+ for (let host of document.querySelectorAll(".hostname")) {
+ host.textContent = document.location.hostname;
+ }
+
+ addAutofocus("returnButton");
+ setupAdvancedButton(true);
+
+ document.getElementById("learnMoreContainer").style.display = "block";
+
+ let checkbox = document.getElementById("automaticallyReportInFuture");
+ checkbox.addEventListener("change", function({target: {checked}}) {
+ document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
+ detail: checked,
+ bubbles: true
+ }));
+ });
+
+ addEventListener("AboutNetErrorOptions", function(event) {
+ var options = JSON.parse(event.detail);
+ if (options && options.enabled) {
+ // Display error reporting UI
+ document.getElementById("certificateErrorReporting").style.display = "block";
+
+ // set the checkbox
+ checkbox.checked = !!options.automatic;
+ }
+ }, true, true);
+
+ let event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
+ document.getElementById("advancedButton").dispatchEvent(event);
+
+ addDomainErrorLinks();
+ }
+
+ /* Only do autofocus if we're the toplevel frame; otherwise we
+ don't want to call attention to ourselves! The key part is
+ that autofocus happens on insertion into the tree, so we
+ can remove the button, add @autofocus, and reinsert the
+ button.
+ */
+ function addAutofocus(buttonId, position = "afterbegin") {
+ if (window.top == window) {
+ var button = document.getElementById(buttonId);
+ var parent = button.parentNode;
+ button.remove();
+ button.setAttribute("autofocus", "true");
+ parent.insertAdjacentElement(position, button);
+ }
+ }
+
+ /* Try to preserve the links contained in the error description, like
+ the error code.
+
+ Also, in the case of SSL error pages about domain mismatch, see if
+ we can hyperlink the user to the correct site. We don't want
+ to do this generically since it allows MitM attacks to redirect
+ users to a site under attacker control, but in certain cases
+ it is safe (and helpful!) to do so. Bug 402210
+ */
+ function addDomainErrorLinks() {
+ // Rather than textContent, we need to treat description as HTML
+ var sdid = gIsCertError ? "badCertTechnicalInfo" : "errorShortDescText";
+ var sd = document.getElementById(sdid);
+ if (sd) {
+ var desc = getDescription();
+
+ // sanitize description text - see bug 441169
+
+ // First, find the index of the <a> tags we care about, being
+ // careful not to use an over-greedy regex.
+ var codeRe = /<a id="errorCode" title="([^"]+)">/;
+ var codeResult = codeRe.exec(desc);
+ var domainRe = /<a id="cert_domain_link" title="([^"]+)">/;
+ var domainResult = domainRe.exec(desc);
+
+ // The order of these links in the description is fixed in
+ // TransportSecurityInfo.cpp:formatOverridableCertErrorMessage.
+ var firstResult = domainResult;
+ if (!domainResult)
+ firstResult = codeResult;
+ if (!firstResult)
+ return;
+ // Remove sd's existing children
+ sd.textContent = "";
+
+ // Everything up to the first link should be text content.
+ sd.appendChild(document.createTextNode(desc.slice(0, firstResult.index)));
+
+ // Now create the actual links.
+ if (domainResult) {
+ createLink(sd, "cert_domain_link", domainResult[1])
+ // Append text for anything between the two links.
+ sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length, codeResult.index)));
+ }
+ createLink(sd, "errorCode", codeResult[1])
+
+ // Finally, append text for anything after the last closing </a>.
+ sd.appendChild(document.createTextNode(desc.slice(desc.lastIndexOf("</a>") + "</a>".length)));
+ }
+
+ if (gIsCertError) {
+ // Initialize the error code link embedded in the error message to
+ // display debug information about the cert error.
+ var errorCode = document.getElementById("errorCode");
+ if (errorCode) {
+ errorCode.href = "javascript:void(0)";
+ errorCode.addEventListener("click", () => {
+ let debugInfo = document.getElementById("certificateErrorDebugInformation");
+ debugInfo.style.display = "block";
+ debugInfo.scrollIntoView({block: "start", behavior: "smooth"});
+ }, false);
+ }
+ }
+
+ // Initialize the cert domain link.
+ var link = document.getElementById("cert_domain_link");
+ if (!link)
+ return;
+
+ var okHost = link.getAttribute("title");
+ var thisHost = document.location.hostname;
+ var proto = document.location.protocol;
+
+ // If okHost is a wildcard domain ("*.example.com") let's
+ // use "www" instead. "*.example.com" isn't going to
+ // get anyone anywhere useful. bug 432491
+ okHost = okHost.replace(/^\*\./, "www.");
+
+ /* case #1:
+ * example.com uses an invalid security certificate.
+ *
+ * The certificate is only valid for www.example.com
+ *
+ * Make sure to include the "." ahead of thisHost so that
+ * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
+ *
+ * We'd normally just use a RegExp here except that we lack a
+ * library function to escape them properly (bug 248062), and
+ * domain names are famous for having '.' characters in them,
+ * which would allow spurious and possibly hostile matches.
+ */
+ if (okHost.endsWith("." + thisHost))
+ link.href = proto + okHost;
+
+ /* case #2:
+ * browser.garage.maemo.org uses an invalid security certificate.
+ *
+ * The certificate is only valid for garage.maemo.org
+ */
+ if (thisHost.endsWith("." + okHost))
+ link.href = proto + okHost;
+
+ // If we set a link, meaning there's something helpful for
+ // the user here, expand the section by default
+ if (link.href && getCSSClass() != "expertBadCert") {
+ var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel"
+ toggleDisplay(document.getElementById(panelId));
+ if (gIsCertError) {
+ // Toggling the advanced panel must ensure that the debugging
+ // information panel is hidden as well, since it's opened by the
+ // error code link in the advanced panel.
+ var div = document.getElementById("certificateErrorDebugInformation");
+ div.style.display = "none";
+ }
+ }
+ }
+
+ function createLink(el, id, text) {
+ var anchorEl = document.createElement("a");
+ anchorEl.setAttribute("id", id);
+ anchorEl.setAttribute("title", text);
+ anchorEl.appendChild(document.createTextNode(text));
+ el.appendChild(anchorEl);
+ }
+ ]]></script>
+ </head>
+
+ <body dir="&locale.dir;">
+ <!-- Contains an alternate page title set on page init for cert errors. -->
+ <div id="certErrorPageTitle" style="display: none;">&certerror.pagetitle1;</div>
+ <div id="captivePortalPageTitle" style="display: none;">&captivePortal.title;</div>
+
+ <!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
+ <div id="errorContainer">
+ <div id="errorTitlesContainer">
+ <h1 id="et_generic">&generic.title;</h1>
+ <h1 id="et_captivePortal">&captivePortal.title;</h1>
+ <h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
+ <h1 id="et_fileNotFound">&fileNotFound.title;</h1>
+ <h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
+ <h1 id="et_malformedURI">&malformedURI.title;</h1>
+ <h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
+ <h1 id="et_connectionFailure">&connectionFailure.title;</h1>
+ <h1 id="et_netTimeout">&netTimeout.title;</h1>
+ <h1 id="et_redirectLoop">&redirectLoop.title;</h1>
+ <h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
+ <h1 id="et_netReset">&netReset.title;</h1>
+ <h1 id="et_notCached">&notCached.title;</h1>
+ <h1 id="et_netOffline">&netOffline.title;</h1>
+ <h1 id="et_netInterrupt">&netInterrupt.title;</h1>
+ <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
+ <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
+ <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
+ <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
+ <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
+ <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
+ <h1 id="et_nssBadCert">&certerror.longpagetitle1;</h1>
+ <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
+ <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
+ <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
+ <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
+ <h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
+ <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
+ </div>
+ <div id="errorDescriptionsContainer">
+ <div id="ed_generic">&generic.longDesc;</div>
+ <div id="ed_captivePortal">&captivePortal.longDesc;</div>
+ <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
+ <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
+ <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
+ <div id="ed_malformedURI">&malformedURI.longDesc;</div>
+ <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
+ <div id="ed_connectionFailure">&connectionFailure.longDesc;</div>
+ <div id="ed_netTimeout">&netTimeout.longDesc;</div>
+ <div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
+ <div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
+ <div id="ed_netReset">&netReset.longDesc;</div>
+ <div id="ed_notCached">&notCached.longDesc;</div>
+ <div id="ed_netOffline">&netOffline.longDesc2;</div>
+ <div id="ed_netInterrupt">&netInterrupt.longDesc;</div>
+ <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
+ <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
+ <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
+ <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
+ <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
+ <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
+ <div id="ed_nssBadCert">&certerror.introPara;</div>
+ <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
+ <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
+ <div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
+ <div id="ed_sslv3Used">&sslv3Used.longDesc2;</div>
+ <div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc2;</div>
+ <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
+ </div>
+ </div>
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer" class="container">
+
+ <!-- Error Title -->
+ <div class="title">
+ <h1 class="title-text"/>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText" />
+ </div>
+ <p id="badStsCertExplanation" hidden="true">&certerror.whatShouldIDo.badStsCertExplanation;</p>
+
+ <div id="wrongSystemTimePanel" style="display: none;">
+ &certerror.wrongSystemTime;
+ </div>
+
+ <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
+ <div id="errorLongDesc" />
+
+ <div id="learnMoreContainer">
+ <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
+ </div>
+
+ <div id="prefChangeContainer" class="button-container">
+ <p>&prefReset.longDesc;</p>
+ <button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
+ </div>
+
+ <div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
+ <button id="returnButton" class="primary" autocomplete="off">&returnToPreviousPage.label;</button>
+ <button id="openPortalLoginPageButton" class="primary" autocomplete="off">&openPortalLoginPage.label;</button>
+ <div class="button-spacer"></div>
+ <button id="advancedButton" autocomplete="off">&advanced.label;</button>
+ </div>
+ </div>
+
+ <div id="netErrorButtonContainer" class="button-container">
+ <button id="errorTryAgain" class="primary" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
+ </div>
+
+ <!-- UI for option to report certificate errors to Mozilla. Removed on
+ init for other error types .-->
+ <div id="certificateErrorReporting">
+ <p class="toggle-container-with-text">
+ <input type="checkbox" id="automaticallyReportInFuture" />
+ <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
+ </p>
+ </div>
+
+ <div id="advancedPanelContainer">
+ <div id="weakCryptoAdvancedPanel" class="advanced-panel">
+ <div id="weakCryptoAdvancedDescription">
+ <p>&weakCryptoAdvanced.longDesc;</p>
+ </div>
+ <div id="advancedLongDesc" />
+ <div id="overrideWeakCryptoPanel">
+ <a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
+ </div>
+ </div>
+
+ <div id="badCertAdvancedPanel" class="advanced-panel">
+ <p id="badCertTechnicalInfo"/>
+ <button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
+ </div>
+ </div>
+
+ </div>
+
+ <div id="certificateErrorDebugInformation">
+ <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
+ <div id="certificateErrorText"/>
+ <button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
+ </div>
+
+ <!--
+ - Note: It is important to run the script this way, instead of using
+ - an onload handler. This is because error pages are loaded as
+ - LOAD_BACKGROUND, which means that onload handlers will not be executed.
+ -->
+ <script type="application/javascript">
+ initPage();
+ </script>
+
+ </body>
+</html>
diff --git a/browser/base/content/aboutProviderDirectory.xhtml b/browser/base/content/aboutProviderDirectory.xhtml
new file mode 100644
index 000000000..596ede4b3
--- /dev/null
+++ b/browser/base/content/aboutProviderDirectory.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+ %brandDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+ %browserDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&social.directory.label;</title>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/skin/aboutProviderDirectory.css"/>
+ </head>
+
+ <body>
+ <div id="activation-link" hidden="true">
+ <div id="message-box">
+ <p>&social.directory.text;</p>
+ </div>
+ <div id="button-box">
+ <button onclick="openDirectory()">&social.directory.button;</button>
+ </div>
+ </div>
+ <div id="activation" hidden="true">
+ <p>&social.directory.introText;</p>
+ <div><iframe id="activation-frame"/></div>
+ <p><a class="link" onclick="openDirectory()">&social.directory.viewmore.text;</a></p>
+ </div>
+ </body>
+
+ <script type="text/javascript;version=1.8"><![CDATA[
+ const Cu = Components.utils;
+
+ Cu.import("resource://gre/modules/Services.jsm");
+
+ function openDirectory() {
+ let url = Services.prefs.getCharPref("social.directories").split(',')[0];
+ window.open(url);
+ window.close();
+ }
+
+ if (Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
+ let url = Services.prefs.getCharPref("social.shareDirectory");
+ document.getElementById("activation-frame").setAttribute("src", url);
+ document.getElementById("activation").removeAttribute("hidden");
+ } else {
+ document.getElementById("activation-link").removeAttribute("hidden");
+ }
+ ]]></script>
+</html>
diff --git a/browser/base/content/aboutRobots-icon.png b/browser/base/content/aboutRobots-icon.png
new file mode 100644
index 000000000..1c4899aaf
--- /dev/null
+++ b/browser/base/content/aboutRobots-icon.png
Binary files differ
diff --git a/browser/base/content/aboutRobots-widget-left.png b/browser/base/content/aboutRobots-widget-left.png
new file mode 100644
index 000000000..3a1e48d5f
--- /dev/null
+++ b/browser/base/content/aboutRobots-widget-left.png
Binary files differ
diff --git a/browser/base/content/aboutRobots.xhtml b/browser/base/content/aboutRobots.xhtml
new file mode 100644
index 000000000..23fe3ba17
--- /dev/null
+++ b/browser/base/content/aboutRobots.xhtml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorDTD
+ SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutrobotsDTD
+ SYSTEM "chrome://browser/locale/aboutRobots.dtd">
+ %aboutrobotsDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&robots.pagetitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+ <link rel="icon" type="image/png" id="favicon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACGFjVEwAAAASAAAAAJNtBPIAAAAaZmNUTAAAAAAAAAAQAAAAEAAAAAAAAAAALuAD6AABhIDeugAAALhJREFUOI2Nk8sNxCAMRDlGohauXFOMpfTiAlxICqAELltHLqlgctg1InzMRhpFAc%2BLGWTnmoeZYamt78zXdZmaQtQMADlnU0OIAlbmJUBEcO4bRKQY2rUXIPmAGnDuG%2FBx3%2FfvOPVaDUg%2BoAPUf1PArIMCSD5glMEsUGaG%2BkyAFWIBaCsKuA%2BHGCNijLgP133XgOEtaPFMy2vUolEGJoCIzBmoRUR9%2B7rxj16DZaW%2FmgtmxnJ8V3oAnApQwNS5zpcAAAAaZmNUTAAAAAEAAAAQAAAAEAAAAAAAAAAAAB4D6AIB52fclgAAACpmZEFUAAAAAjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9WF%2Bu8QAAABpmY1RMAAAAAwAAABAAAAAQAAAAAAAAAAAAHgPoAgEK8Q9%2FAAAAFmZkQVQAAAAEOI1jYBgFo2AUjAIIAAAEEAAB0xIn4wAAABpmY1RMAAAABQAAABAAAAAQAAAAAAAAAAAAHgPoAgHnO30FAAAAQGZkQVQAAAAGOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVfozYcAAAABpmY1RMAAAABwAAABAAAAAQAAAAAAAAAAAAHgPoAgEKra7sAAAAFmZkQVQAAAAIOI1jYBgFo2AUjAIIAAAEEAABM9s3hAAAABpmY1RMAAAACQAAABAAAAAQAAAAAAAAAAAAHgPoAgHn3p%2BwAAAAKmZkQVQAAAAKOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F1BhPl6AAAAGmZjVEwAAAALAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQpITFkAAAAWZmRBVAAAAAw4jWNgGAWjYBSMAggAAAQQAAHaszpmAAAAGmZjVEwAAAANAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeeCPiMAAABAZmRBVAAAAA44jWNgGJ5gpxrDf2LEcIL%2FpzAVYxPDavP%2FUwz%2FpW79%2F%2F%2F%2FFMP%2FnWoQjC5GOxcgu4QYsVEwCmAAAOE0KxUmBL0KAAAAGmZjVEwAAAAPAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQoU7coAAAAWZmRBVAAAABA4jWNgGAWjYBSMAggAAAQQAAEpOBELAAAAGmZjVEwAAAARAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeYVWtoAAAAqZmRBVAAAABI4jWNgGAVYQXNz839ixHBq3qnG8B9ZAzYx2rlgFIwCcgAA8psX%2FWvpAecAAAAaZmNUTAAAABMAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC4OJMwAAABZmZEFUAAAAFDiNY2AYBaNgFIwCCAAABBAAAcBQHOkAAAAaZmNUTAAAABUAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5kn7SQAAAEBmZEFUAAAAFjiNY2AYnmCnGsN%2FYsRwgv%2BnMBVjE8Nq8%2F9TDP%2Blbv3%2F%2F%2F8Uw%2F%2BdahCMLkY7FyC7hBixUTAKYAAA4TQrFc%2BcEoQAAAAaZmNUTAAAABcAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC98ooAAAABZmZEFUAAAAGDiNY2AYBaNgFIwCCAAABBAAASCZDI4AAAAaZmNUTAAAABkAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5qwZ%2FAAAACpmZEFUAAAAGjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9cjJWbAAAABpmY1RMAAAAGwAAABAAAAAQAAAAAAAAAAAAHgPoAgELOsoVAAAAFmZkQVQAAAAcOI1jYBgFo2AUjAIIAAAEEAAByfEBbAAAABpmY1RMAAAAHQAAABAAAAAQAAAAAAAAAAAAHgPoAgHm8LhvAAAAQGZkQVQAAAAeOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVlxR3%2FgAAABpmY1RMAAAAHwAAABAAAAAQAAAAAAAAAAAAHgPoAgELZmuGAAAAFmZkQVQAAAAgOI1jYBgFo2AUjAIIAAAEEAABHP5cFQAAABpmY1RMAAAAIQAAABAAAAAQAAAAAAAAAAAAHgPoAgHlgtAOAAAAKmZkQVQAAAAiOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F0%2FMvDdAAAAAElFTkSuQmCC"/>
+
+ <script type="application/javascript"><![CDATA[
+ var buttonClicked = false;
+ function robotButton()
+ {
+ var button = document.getElementById('errorTryAgain');
+ if (buttonClicked) {
+ button.style.visibility = "hidden";
+ } else {
+ var newLabel = button.getAttribute("label2");
+ button.textContent = newLabel;
+ buttonClicked = true;
+ }
+ }
+ ]]></script>
+
+ <style type="text/css"><![CDATA[
+ #errorPageContainer {
+ background-image: none;
+ }
+
+ #errorPageContainer:before {
+ content: url('chrome://browser/content/aboutRobots-icon.png');
+ position: absolute;
+ }
+
+ body[dir=rtl] #icon,
+ body[dir=rtl] #errorPageContainer:before {
+ transform: scaleX(-1);
+ }
+ ]]></style>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText">&robots.errorTitleText;</h1>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText">&robots.errorShortDescText;</p>
+ </div>
+
+ <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
+ <div id="errorLongDesc">
+ <ul>
+ <li>&robots.errorLongDesc1;</li>
+ <li>&robots.errorLongDesc2;</li>
+ <li>&robots.errorLongDesc3;</li>
+ <li>&robots.errorLongDesc4;</li>
+ </ul>
+ </div>
+
+ <!-- Short Description -->
+ <div id="errorTrailerDesc">
+ <p id="errorTrailerDescText">&robots.errorTrailerDescText;</p>
+ </div>
+
+ </div>
+
+ <!-- Button -->
+ <button id="errorTryAgain"
+ label2="&robots.dontpress;"
+ onclick="robotButton();">&retry.label;</button>
+
+ <img src="chrome://browser/content/aboutRobots-widget-left.png"
+ style="position: absolute; bottom: -12px; left: -10px;"/>
+ <img src="chrome://browser/content/aboutRobots-widget-left.png"
+ style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/>
+ </div>
+
+ </body>
+</html>
diff --git a/browser/base/content/aboutSocialError.xhtml b/browser/base/content/aboutSocialError.xhtml
new file mode 100644
index 000000000..94a4e3dbd
--- /dev/null
+++ b/browser/base/content/aboutSocialError.xhtml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&loadError.label;</title>
+ <link rel="stylesheet" href="chrome://browser/skin/aboutNetError.css" type="text/css" media="all" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/aboutSocialError.css"/>
+ <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
+ </head>
+
+ <body>
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <p id="errorShortDescText" >foo</p>
+ </div>
+
+ <div id="button-box">
+ <button id="btnTryAgain" onclick="tryAgainButton()"/>
+ </div>
+ </div>
+ </body>
+
+ <script type="text/javascript;version=1.8"><![CDATA[
+ const Cu = Components.utils;
+
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource:///modules/Social.jsm");
+
+ let config = {
+ tryAgainCallback: reloadProvider
+ }
+
+ function parseQueryString() {
+ let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
+ let mode = searchParams.get("mode");
+ config.origin = searchParams.get("origin");
+ let encodedURL = searchParams.get("url");
+ let url = decodeURIComponent(encodedURL);
+ // directory does not have origin set, in that case use the url origin for
+ // the error message.
+ if (!config.origin) {
+ let URI = Services.io.newURI(url, null, null);
+ config.origin =
+ Services.scriptSecurityManager.createCodebasePrincipal(URI, {}).origin;
+ }
+
+ switch (mode) {
+ case "compactInfo":
+ document.getElementById("btnTryAgain").style.display = 'none';
+ break;
+ case "tryAgainOnly":
+ //intentional fall-through
+ case "tryAgain":
+ config.tryAgainCallback = loadQueryURL;
+ config.queryURL = url;
+ break;
+ default:
+ break;
+ }
+ }
+
+ function setUpStrings() {
+ let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+
+ let productName = brandBundle.GetStringFromName("brandShortName");
+ let provider = Social._getProviderFromOrigin(config.origin);
+ let providerName = provider ? provider.name : config.origin;
+
+ // Sets up the error message
+ let msg = browserBundle.formatStringFromName("social.error.message", [productName, providerName], 2);
+ document.getElementById("errorShortDescText").textContent = msg;
+
+ // Sets up the buttons' labels and accesskeys
+ let btnTryAgain = document.getElementById("btnTryAgain");
+ btnTryAgain.textContent = browserBundle.GetStringFromName("social.error.tryAgain.label");
+ btnTryAgain.accessKey = browserBundle.GetStringFromName("social.error.tryAgain.accesskey");
+ }
+
+ function tryAgainButton() {
+ config.tryAgainCallback();
+ }
+
+ function loadQueryURL() {
+ window.location.href = config.queryURL;
+ }
+
+ function reloadProvider() {
+ let provider = Social._getProviderFromOrigin(config.origin);
+ provider.reload();
+ }
+
+ parseQueryString();
+ setUpStrings();
+ ]]></script>
+</html>
diff --git a/browser/base/content/aboutTabCrashed.css b/browser/base/content/aboutTabCrashed.css
new file mode 100644
index 000000000..de0eabe8b
--- /dev/null
+++ b/browser/base/content/aboutTabCrashed.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/. */
+
+html:not(.crashDumpSubmitted) #reportSent,
+html:not(.crashDumpAvailable) #reportBox,
+.container[multiple="true"] > .offers > #offerHelpMessageSingle,
+.container[multiple="false"] > .offers > #offerHelpMessageMultiple,
+.container[multiple="false"] > .button-container > #restoreAll {
+ display: none;
+} \ No newline at end of file
diff --git a/browser/base/content/aboutTabCrashed.js b/browser/base/content/aboutTabCrashed.js
new file mode 100644
index 000000000..337add1d2
--- /dev/null
+++ b/browser/base/content/aboutTabCrashed.js
@@ -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/. */
+
+var AboutTabCrashed = {
+ /**
+ * This can be set to true once this page receives a message from the
+ * parent saying whether or not a crash report is available.
+ */
+ hasReport: false,
+
+ /**
+ * The messages that we might receive from the parent.
+ */
+ MESSAGES: [
+ "SetCrashReportAvailable",
+ "CrashReportSent",
+ "UpdateCount",
+ ],
+
+ /**
+ * Items for which we will listen for click events.
+ */
+ CLICK_TARGETS: [
+ "closeTab",
+ "restoreTab",
+ "restoreAll",
+ "sendReport",
+ ],
+
+ /**
+ * Returns information about this crashed tab.
+ *
+ * @return (Object) An object with the following properties:
+ * title (String):
+ * The title of the page that crashed.
+ * URL (String):
+ * The URL of the page that crashed.
+ */
+ get pageData() {
+ delete this.pageData;
+
+ let URL = document.documentURI;
+ let queryString = URL.replace(/^about:tabcrashed?e=tabcrashed/, "");
+
+ let titleMatch = queryString.match(/d=([^&]*)/);
+ let URLMatch = queryString.match(/u=([^&]*)/);
+
+ return this.pageData = {
+ title: titleMatch && titleMatch[1] ? decodeURIComponent(titleMatch[1]) : "",
+ URL: URLMatch && URLMatch[1] ? decodeURIComponent(URLMatch[1]) : "",
+ };
+ },
+
+ init() {
+ this.MESSAGES.forEach((msg) => addMessageListener(msg, this.receiveMessage.bind(this)));
+ addEventListener("DOMContentLoaded", this);
+
+ document.title = this.pageData.title;
+ },
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "UpdateCount": {
+ this.setMultiple(message.data.count > 1);
+ break;
+ }
+ case "SetCrashReportAvailable": {
+ this.onSetCrashReportAvailable(message);
+ break;
+ }
+ case "CrashReportSent": {
+ this.onCrashReportSent();
+ break;
+ }
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "DOMContentLoaded": {
+ this.onDOMContentLoaded();
+ break;
+ }
+ case "click": {
+ this.onClick(event);
+ break;
+ }
+ case "input": {
+ this.onInput(event);
+ break;
+ }
+ }
+ },
+
+ onDOMContentLoaded() {
+ this.CLICK_TARGETS.forEach((targetID) => {
+ let el = document.getElementById(targetID);
+ el.addEventListener("click", this);
+ });
+
+ // For setting "emailMe" checkbox automatically on email value change.
+ document.getElementById("email").addEventListener("input", this);
+
+ // Error pages are loaded as LOAD_BACKGROUND, so they don't get load events.
+ let event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true});
+ document.dispatchEvent(event);
+
+ sendAsyncMessage("Load");
+ },
+
+ onClick(event) {
+ switch (event.target.id) {
+ case "closeTab": {
+ this.sendMessage("closeTab");
+ break;
+ }
+
+ case "restoreTab": {
+ this.sendMessage("restoreTab");
+ break;
+ }
+
+ case "restoreAll": {
+ this.sendMessage("restoreAll");
+ break;
+ }
+
+ case "sendReport": {
+ this.showCrashReportUI(event.target.checked);
+ break;
+ }
+ }
+ },
+
+ onInput(event) {
+ switch (event.target.id) {
+ case "email": {
+ document.getElementById("emailMe").checked = !!event.target.value;
+ break;
+ }
+ }
+ },
+ /**
+ * After this page tells the parent that it has loaded, the parent
+ * will respond with whether or not a crash report is available. This
+ * method handles that message.
+ *
+ * @param message
+ * The message from the parent, which should contain a data
+ * Object property with the following properties:
+ *
+ * hasReport (bool):
+ * Whether or not there is a crash report.
+ *
+ * sendReport (bool):
+ * Whether or not the the user prefers to send the report
+ * by default.
+ *
+ * includeURL (bool):
+ * Whether or not the user prefers to send the URL of
+ * the tab that crashed.
+ *
+ * emailMe (bool):
+ * Whether or not to send the email address of the user
+ * in the report.
+ *
+ * email (String):
+ * The email address of the user (empty if emailMe is false).
+ *
+ * requestAutoSubmit (bool):
+ * Whether or not we should ask the user to automatically
+ * submit backlogged crash reports.
+ *
+ */
+ onSetCrashReportAvailable(message) {
+ let data = message.data;
+
+ if (data.hasReport) {
+ this.hasReport = true;
+ document.documentElement.classList.add("crashDumpAvailable");
+
+ document.getElementById("sendReport").checked = data.sendReport;
+ document.getElementById("includeURL").checked = data.includeURL;
+
+ if (data.requestEmail) {
+ document.getElementById("requestEmail").hidden = false;
+ document.getElementById("emailMe").checked = data.emailMe;
+ if (data.emailMe) {
+ document.getElementById("email").value = data.email;
+ }
+ }
+
+ this.showCrashReportUI(data.sendReport);
+ } else {
+ this.showCrashReportUI(false);
+ }
+
+ if (data.requestAutoSubmit) {
+ document.getElementById("requestAutoSubmit").hidden = false;
+ }
+
+ let event = new CustomEvent("AboutTabCrashedReady", {bubbles:true});
+ document.dispatchEvent(event);
+ },
+
+ /**
+ * Handler for when the parent reports that the crash report associated
+ * with this about:tabcrashed page has been sent.
+ */
+ onCrashReportSent() {
+ document.documentElement.classList.remove("crashDumpAvailable");
+ document.documentElement.classList.add("crashDumpSubmitted");
+ },
+
+ /**
+ * Toggles the display of the crash report form.
+ *
+ * @param shouldShow (bool)
+ * True if the crash report form should be shown
+ */
+ showCrashReportUI(shouldShow) {
+ let options = document.getElementById("options");
+ options.hidden = !shouldShow;
+ },
+
+ /**
+ * Toggles whether or not the page is one of several visible pages
+ * showing the crash reporter. This controls some of the language
+ * on the page, along with what the "primary" button is.
+ *
+ * @param hasMultiple (bool)
+ * True if there are multiple crash report pages being shown.
+ */
+ setMultiple(hasMultiple) {
+ let main = document.getElementById("main");
+ main.setAttribute("multiple", hasMultiple);
+
+ let restoreTab = document.getElementById("restoreTab");
+
+ // The "Restore All" button has the "primary" class by default, so
+ // we only need to modify the "Restore Tab" button.
+ if (hasMultiple) {
+ restoreTab.classList.remove("primary");
+ } else {
+ restoreTab.classList.add("primary");
+ }
+ },
+
+ /**
+ * Sends a message to the parent in response to the user choosing
+ * one of the actions available on the page. This might also send up
+ * crash report information if the user has chosen to submit a crash
+ * report.
+ *
+ * @param messageName (String)
+ * The message to send to the parent
+ */
+ sendMessage(messageName) {
+ let comments = "";
+ let email = "";
+ let URL = "";
+ let sendReport = false;
+ let emailMe = false;
+ let includeURL = false;
+ let autoSubmit = false;
+
+ if (this.hasReport) {
+ sendReport = document.getElementById("sendReport").checked;
+ if (sendReport) {
+ comments = document.getElementById("comments").value.trim();
+
+ includeURL = document.getElementById("includeURL").checked;
+ if (includeURL) {
+ URL = this.pageData.URL.trim();
+ }
+
+ if (!document.getElementById("requestEmail").hidden) {
+ emailMe = document.getElementById("emailMe").checked;
+ if (emailMe) {
+ email = document.getElementById("email").value.trim();
+ }
+ }
+ }
+ }
+
+ let requestAutoSubmit = document.getElementById("requestAutoSubmit");
+ if (requestAutoSubmit.hidden) {
+ // The checkbox is hidden if the user has already opted in to sending
+ // backlogged crash reports.
+ autoSubmit = true;
+ } else {
+ autoSubmit = document.getElementById("autoSubmit").checked;
+ }
+
+ sendAsyncMessage(messageName, {
+ sendReport,
+ comments,
+ email,
+ emailMe,
+ includeURL,
+ URL,
+ autoSubmit,
+ hasReport: this.hasReport,
+ });
+ },
+};
+
+AboutTabCrashed.init();
diff --git a/browser/base/content/aboutTabCrashed.xhtml b/browser/base/content/aboutTabCrashed.xhtml
new file mode 100644
index 000000000..8b18bee9c
--- /dev/null
+++ b/browser/base/content/aboutTabCrashed.xhtml
@@ -0,0 +1,97 @@
+<?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 % tabCrashedDTD
+ SYSTEM "chrome://browser/locale/aboutTabCrashed.dtd">
+ %tabCrashedDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://global/skin/in-content/info-pages.css"/>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/content/aboutTabCrashed.css"/>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/skin/aboutTabCrashed.css"/>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div id="main" class="container" multiple="false">
+
+ <div class="title">
+ <h1 class="title-text">&tabCrashed.header2;</h1>
+ </div>
+
+ <div class="offers">
+ <h2>&tabCrashed.offerHelp;</h2>
+ <p id="offerHelpMessageSingle">&tabCrashed.single.offerHelpMessage;</p>
+ <p id="offerHelpMessageMultiple">&tabCrashed.multiple.offerHelpMessage;</p>
+ </div>
+
+ <div id="reportBox">
+ <h2>&tabCrashed.requestHelp;</h2>
+ <p>&tabCrashed.requestHelpMessage;</p>
+
+ <h2>&tabCrashed.requestReport;</h2>
+
+ <div class="checkbox-with-label">
+ <input type="checkbox" id="sendReport"/>
+ <label for="sendReport">&tabCrashed.sendReport2;</label>
+ </div>
+
+ <ul id="options">
+ <li>
+ <textarea id="comments" placeholder="&tabCrashed.commentPlaceholder2;" rows="4"></textarea>
+ </li>
+
+ <li class="checkbox-with-label">
+ <input type="checkbox" id="includeURL"/>
+ <label for="includeURL">&tabCrashed.includeURL2;</label>
+ </li>
+
+ <li id="requestEmail" hidden="true">
+ <div class="checkbox-with-label">
+ <input type="checkbox" id="emailMe"/>
+ <label for="emailMe">&tabCrashed.emailMe;</label>
+ </div>
+ <input type="text" id="email" placeholder="&tabCrashed.emailPlaceholder;"/>
+ </li>
+ </ul>
+
+ <div id="requestAutoSubmit" hidden="true">
+ <h2>&tabCrashed.requestAutoSubmit2;</h2>
+ <div class="checkbox-with-label">
+ <input type="checkbox" id="autoSubmit"/>
+ <label for="autoSubmit">&tabCrashed.autoSubmit;</label>
+ </div>
+ </div>
+ </div>
+
+ <p id="reportSent">&tabCrashed.reportSent;</p>
+
+ <div class="button-container">
+ <button id="closeTab">
+ &tabCrashed.closeTab;</button>
+ <button id="restoreTab" class="primary">
+ &tabCrashed.restoreTab;</button>
+ <button id="restoreAll" autofocus="true" class="primary">
+ &tabCrashed.restoreAll;</button>
+ </div>
+ </div>
+ </body>
+ <script type="text/javascript;version=1.8" src="chrome://browser/content/aboutTabCrashed.js"/>
+</html>
diff --git a/browser/base/content/aboutaccounts/aboutaccounts.css b/browser/base/content/aboutaccounts/aboutaccounts.css
new file mode 100644
index 000000000..a2c5cb8f0
--- /dev/null
+++ b/browser/base/content/aboutaccounts/aboutaccounts.css
@@ -0,0 +1,24 @@
+html, body {
+ height: 100%;
+}
+
+#remote {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ display: none;
+}
+
+#networkError, #manage, #intro, #stage, #configError {
+ display: none;
+}
+
+#oldsync {
+ background: none;
+ border: 0;
+ color: #0095dd;
+}
+
+#oldsync:focus {
+ outline: 1px dotted #0095dd;
+}
diff --git a/browser/base/content/aboutaccounts/aboutaccounts.js b/browser/base/content/aboutaccounts/aboutaccounts.js
new file mode 100644
index 000000000..a05c1ea75
--- /dev/null
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -0,0 +1,543 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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/Services.jsm");
+Cu.import("resource://gre/modules/FxAccounts.jsm");
+
+var fxAccountsCommon = {};
+Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
+
+// for master-password utilities
+Cu.import("resource://services-sync/util.js");
+
+const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
+const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
+
+const ACTION_URL_PARAM = "action";
+
+const OBSERVER_TOPICS = [
+ fxAccountsCommon.ONVERIFIED_NOTIFICATION,
+ fxAccountsCommon.ONLOGOUT_NOTIFICATION,
+];
+
+function log(msg) {
+ // dump("FXA: " + msg + "\n");
+}
+
+function error(msg) {
+ console.log("Firefox Account Error: " + msg + "\n");
+}
+
+function getPreviousAccountNameHash() {
+ try {
+ return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
+ } catch (_) {
+ return "";
+ }
+}
+
+function setPreviousAccountNameHash(acctName) {
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = sha256(acctName);
+ Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
+}
+
+function needRelinkWarning(acctName) {
+ let prevAcctHash = getPreviousAccountNameHash();
+ return prevAcctHash && prevAcctHash != sha256(acctName);
+}
+
+// Given a string, returns the SHA265 hash in base64
+function sha256(str) {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ // Data is an array of bytes.
+ let data = converter.convertToByteArray(str, {});
+ let hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA256);
+ hasher.update(data, data.length);
+
+ return hasher.finish(true);
+}
+
+function promptForRelink(acctName) {
+ let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+ let continueLabel = sb.GetStringFromName("continue.label");
+ let title = sb.GetStringFromName("relinkVerify.title");
+ let description = sb.formatStringFromName("relinkVerify.description",
+ [acctName], 1);
+ let body = sb.GetStringFromName("relinkVerify.heading") +
+ "\n\n" + description;
+ let ps = Services.prompt;
+ let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
+ (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
+ ps.BUTTON_POS_1_DEFAULT;
+ let pressed = Services.prompt.confirmEx(window, title, body, buttonFlags,
+ continueLabel, null, null, null,
+ {});
+ return pressed == 0; // 0 is the "continue" button
+}
+
+// If the last fxa account used for sync isn't this account, we display
+// a modal dialog checking they really really want to do this...
+// (This is sync-specific, so ideally would be in sync's identity module,
+// but it's a little more seamless to do here, and sync is currently the
+// only fxa consumer, so...
+function shouldAllowRelink(acctName) {
+ return !needRelinkWarning(acctName) || promptForRelink(acctName);
+}
+
+function updateDisplayedEmail(user) {
+ let emailDiv = document.getElementById("email");
+ if (emailDiv && user) {
+ emailDiv.textContent = user.email;
+ }
+}
+
+var wrapper = {
+ iframe: null,
+
+ init: function (url, urlParams) {
+ // If a master-password is enabled, we want to encourage the user to
+ // unlock it. Things still work if not, but the user will probably need
+ // to re-auth next startup (in which case we will get here again and
+ // re-prompt)
+ Utils.ensureMPUnlocked();
+
+ let iframe = document.getElementById("remote");
+ this.iframe = iframe;
+ this.iframe.QueryInterface(Ci.nsIFrameLoaderOwner);
+ let docShell = this.iframe.frameLoader.docShell;
+ docShell.QueryInterface(Ci.nsIWebProgress);
+ docShell.addProgressListener(this.iframeListener, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ iframe.addEventListener("load", this);
+
+ // Ideally we'd just merge urlParams with new URL(url).searchParams, but our
+ // URLSearchParams implementation doesn't support iteration (bug 1085284).
+ let urlParamStr = urlParams.toString();
+ if (urlParamStr) {
+ url += (url.includes("?") ? "&" : "?") + urlParamStr;
+ }
+ this.url = url;
+ // Set the iframe's location with loadURI/LOAD_FLAGS_REPLACE_HISTORY to
+ // avoid having a new history entry being added. REPLACE_HISTORY is used
+ // to replace the current entry, which is `about:blank`.
+ let webNav = iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY, null, null, null);
+ },
+
+ retry: function () {
+ let webNav = this.iframe.frameLoader.docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(this.url, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, null, null, null);
+ },
+
+ iframeListener: {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
+ let failure = false;
+
+ // Captive portals sometimes redirect users
+ if ((aState & Ci.nsIWebProgressListener.STATE_REDIRECTING)) {
+ failure = true;
+ } else if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
+ if (aRequest instanceof Ci.nsIHttpChannel) {
+ try {
+ failure = aRequest.responseStatus != 200;
+ } catch (e) {
+ failure = aStatus != Components.results.NS_OK;
+ }
+ }
+ }
+
+ // Calling cancel() will raise some OnStateChange notifications by itself,
+ // so avoid doing that more than once
+ if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ setErrorPage("networkError");
+ }
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ setErrorPage("networkError");
+ }
+ },
+
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+ },
+
+ handleEvent: function (evt) {
+ switch (evt.type) {
+ case "load":
+ this.iframe.contentWindow.addEventListener("FirefoxAccountsCommand", this);
+ this.iframe.removeEventListener("load", this);
+ break;
+ case "FirefoxAccountsCommand":
+ this.handleRemoteCommand(evt);
+ break;
+ }
+ },
+
+ /**
+ * onLogin handler receives user credentials from the jelly after a
+ * sucessful login and stores it in the fxaccounts service
+ *
+ * @param accountData the user's account data and credentials
+ */
+ onLogin: function (accountData) {
+ log("Received: 'login'. Data:" + JSON.stringify(accountData));
+
+ if (accountData.customizeSync) {
+ Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, true);
+ }
+ delete accountData.customizeSync;
+ // sessionTokenContext is erroneously sent by the content server.
+ // https://github.com/mozilla/fxa-content-server/issues/2766
+ // To avoid having the FxA storage manager not knowing what to do with
+ // it we delete it here.
+ delete accountData.sessionTokenContext;
+
+ // We need to confirm a relink - see shouldAllowRelink for more
+ let newAccountEmail = accountData.email;
+ // The hosted code may have already checked for the relink situation
+ // by sending the can_link_account command. If it did, then
+ // it will indicate we don't need to ask twice.
+ if (!accountData.verifiedCanLinkAccount && !shouldAllowRelink(newAccountEmail)) {
+ // we need to tell the page we successfully received the message, but
+ // then bail without telling fxAccounts
+ this.injectData("message", { status: "login" });
+ // after a successful login we return to preferences
+ openPrefs();
+ return;
+ }
+ delete accountData.verifiedCanLinkAccount;
+
+ // Remember who it was so we can log out next time.
+ setPreviousAccountNameHash(newAccountEmail);
+
+ // A sync-specific hack - we want to ensure sync has been initialized
+ // before we set the signed-in user.
+ let xps = Cc["@mozilla.org/weave/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject;
+ xps.whenLoaded().then(() => {
+ updateDisplayedEmail(accountData);
+ return fxAccounts.setSignedInUser(accountData);
+ }).then(() => {
+ // If the user data is verified, we want it to immediately look like
+ // they are signed in without waiting for messages to bounce around.
+ if (accountData.verified) {
+ openPrefs();
+ }
+ this.injectData("message", { status: "login" });
+ // until we sort out a better UX, just leave the jelly page in place.
+ // If the account email is not yet verified, it will tell the user to
+ // go check their email, but then it will *not* change state after
+ // the verification completes (the browser will begin syncing, but
+ // won't notify the user). If the email has already been verified,
+ // the jelly will say "Welcome! You are successfully signed in as
+ // EMAIL", but it won't then say "syncing started".
+ }, (err) => this.injectData("message", { status: "error", error: err })
+ );
+ },
+
+ onCanLinkAccount: function(accountData) {
+ // We need to confirm a relink - see shouldAllowRelink for more
+ let ok = shouldAllowRelink(accountData.email);
+ this.injectData("message", { status: "can_link_account", data: { ok: ok } });
+ },
+
+ /**
+ * onSignOut handler erases the current user's session from the fxaccounts service
+ */
+ onSignOut: function () {
+ log("Received: 'sign_out'.");
+
+ fxAccounts.signOut().then(
+ () => this.injectData("message", { status: "sign_out" }),
+ (err) => this.injectData("message", { status: "error", error: err })
+ );
+ },
+
+ handleRemoteCommand: function (evt) {
+ log('command: ' + evt.detail.command);
+ let data = evt.detail.data;
+
+ switch (evt.detail.command) {
+ case "login":
+ this.onLogin(data);
+ break;
+ case "can_link_account":
+ this.onCanLinkAccount(data);
+ break;
+ case "sign_out":
+ this.onSignOut(data);
+ break;
+ default:
+ log("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
+ break;
+ }
+ },
+
+ injectData: function (type, content) {
+ return fxAccounts.promiseAccountsSignUpURI().then(authUrl => {
+ let data = {
+ type: type,
+ content: content
+ };
+ this.iframe.contentWindow.postMessage(data, authUrl);
+ })
+ .catch(e => {
+ console.log("Failed to inject data", e);
+ setErrorPage("configError");
+ });
+ },
+};
+
+
+// Button onclick handlers
+function handleOldSync() {
+ let chromeWin = window
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow);
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
+ chromeWin.switchToTabHavingURI(url, true);
+}
+
+function getStarted() {
+ show("remote");
+}
+
+function retry() {
+ show("remote");
+ wrapper.retry();
+}
+
+function openPrefs() {
+ // Bug 1199303 calls for this tab to always be replaced with Preferences
+ // rather than it opening in a different tab.
+ window.location = "about:preferences#sync";
+}
+
+function init() {
+ fxAccounts.getSignedInUser().then(user => {
+ // tests in particular might cause the window to start closing before
+ // getSignedInUser has returned.
+ if (window.closed) {
+ return Promise.resolve();
+ }
+
+ updateDisplayedEmail(user);
+
+ // Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
+ // searchParams is empty.
+ let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
+ let action = urlParams.get(ACTION_URL_PARAM);
+ urlParams.delete(ACTION_URL_PARAM);
+
+ switch (action) {
+ case "signin":
+ if (user) {
+ // asking to sign-in when already signed in just shows manage.
+ show("stage", "manage");
+ } else {
+ return fxAccounts.promiseAccountsSignInURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ }
+ break;
+ case "signup":
+ if (user) {
+ // asking to sign-up when already signed in just shows manage.
+ show("stage", "manage");
+ } else {
+ return fxAccounts.promiseAccountsSignUpURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ }
+ break;
+ case "reauth":
+ // ideally we would only show this when we know the user is in a
+ // "must reauthenticate" state - but we don't.
+ // As the email address will be included in the URL returned from
+ // promiseAccountsForceSigninURI, just always show it.
+ return fxAccounts.promiseAccountsForceSigninURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ default:
+ // No action specified.
+ if (user) {
+ show("stage", "manage");
+ } else {
+ // Attempt a migration if enabled or show the introductory page
+ // otherwise.
+ return migrateToDevEdition(urlParams).then(migrated => {
+ if (!migrated) {
+ show("stage", "intro");
+ // load the remote frame in the background
+ return fxAccounts.promiseAccountsSignUpURI().then(uri =>
+ wrapper.init(uri, urlParams));
+ }
+ return Promise.resolve();
+ });
+ }
+ break;
+ }
+ return Promise.resolve();
+ }).catch(err => {
+ console.log("Configuration or sign in error", err);
+ setErrorPage("configError");
+ });
+}
+
+function setErrorPage(errorType) {
+ show("stage", errorType);
+}
+
+// Causes the "top-level" element with |id| to be shown - all other top-level
+// elements are hidden. Optionally, ensures that only 1 "second-level" element
+// inside the top-level one is shown.
+function show(id, childId) {
+ // top-level items are either <div> or <iframe>
+ let allTop = document.querySelectorAll("body > div, iframe");
+ for (let elt of allTop) {
+ if (elt.getAttribute("id") == id) {
+ elt.style.display = 'block';
+ } else {
+ elt.style.display = 'none';
+ }
+ }
+ if (childId) {
+ // child items are all <div>
+ let allSecond = document.querySelectorAll("#" + id + " > div");
+ for (let elt of allSecond) {
+ if (elt.getAttribute("id") == childId) {
+ elt.style.display = 'block';
+ } else {
+ elt.style.display = 'none';
+ }
+ }
+ }
+}
+
+// Migrate sync data from the default profile to the dev-edition profile.
+// Returns a promise of a true value if migration succeeded, or false if it
+// failed.
+function migrateToDevEdition(urlParams) {
+ let defaultProfilePath;
+ try {
+ defaultProfilePath = window.getDefaultProfilePath();
+ } catch (e) {} // no default profile.
+ let migrateSyncCreds = false;
+ if (defaultProfilePath) {
+ try {
+ migrateSyncCreds = Services.prefs.getBoolPref("identity.fxaccounts.migrateToDevEdition");
+ } catch (e) {}
+ }
+
+ if (!migrateSyncCreds) {
+ return Promise.resolve(false);
+ }
+
+ Cu.import("resource://gre/modules/osfile.jsm");
+ let fxAccountsStorage = OS.Path.join(defaultProfilePath, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
+ return OS.File.read(fxAccountsStorage, { encoding: "utf-8" }).then(text => {
+ let accountData = JSON.parse(text).accountData;
+ updateDisplayedEmail(accountData);
+ return fxAccounts.setSignedInUser(accountData);
+ }).then(() => {
+ return fxAccounts.promiseAccountsForceSigninURI().then(url => {
+ show("remote");
+ wrapper.init(url, urlParams);
+ });
+ }).then(null, error => {
+ log("Failed to migrate FX Account: " + error);
+ show("stage", "intro");
+ // load the remote frame in the background
+ fxAccounts.promiseAccountsSignUpURI().then(uri => {
+ wrapper.init(uri, urlParams)
+ }).catch(e => {
+ console.log("Failed to load signup page", e);
+ setErrorPage("configError");
+ });
+ }).then(() => {
+ // Reset the pref after migration.
+ Services.prefs.setBoolPref("identity.fxaccounts.migrateToDevEdition", false);
+ return true;
+ }).then(null, err => {
+ Cu.reportError("Failed to reset the migrateToDevEdition pref: " + err);
+ return false;
+ });
+}
+
+// Helper function that returns the path of the default profile on disk. Will be
+// overridden in tests.
+function getDefaultProfilePath() {
+ let defaultProfile = Cc["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Ci.nsIToolkitProfileService)
+ .defaultProfile;
+ return defaultProfile.rootDir.path;
+}
+
+document.addEventListener("DOMContentLoaded", function onload() {
+ document.removeEventListener("DOMContentLoaded", onload, true);
+ init();
+ var buttonGetStarted = document.getElementById('buttonGetStarted');
+ buttonGetStarted.addEventListener('click', getStarted);
+
+ var buttonRetry = document.getElementById('buttonRetry');
+ buttonRetry.addEventListener('click', retry);
+
+ var oldsync = document.getElementById('oldsync');
+ oldsync.addEventListener('click', handleOldSync);
+
+ var buttonOpenPrefs = document.getElementById('buttonOpenPrefs')
+ buttonOpenPrefs.addEventListener('click', openPrefs);
+}, true);
+
+function initObservers() {
+ function observe(subject, topic, data) {
+ log("about:accounts observed " + topic);
+ if (topic == fxAccountsCommon.ONLOGOUT_NOTIFICATION) {
+ // All about:account windows get changed to action=signin on logout.
+ window.location = "about:accounts?action=signin";
+ return;
+ }
+
+ // must be onverified - we want to open preferences.
+ openPrefs();
+ }
+
+ for (let topic of OBSERVER_TOPICS) {
+ Services.obs.addObserver(observe, topic, false);
+ }
+ window.addEventListener("unload", function(event) {
+ log("about:accounts unloading")
+ for (let topic of OBSERVER_TOPICS) {
+ Services.obs.removeObserver(observe, topic);
+ }
+ });
+}
+initObservers();
diff --git a/browser/base/content/aboutaccounts/aboutaccounts.xhtml b/browser/base/content/aboutaccounts/aboutaccounts.xhtml
new file mode 100644
index 000000000..475f0e86f
--- /dev/null
+++ b/browser/base/content/aboutaccounts/aboutaccounts.xhtml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutAccountsDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
+ %aboutAccountsDTD;
+ <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+ %syncBrandDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
+ <head>
+ <title>&syncBrand.fullName.label;</title>
+ <meta name="viewport" content="width=device-width"/>
+
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet"
+ href="chrome://browser/content/aboutaccounts/normalize.css"
+ type="text/css" />
+ <link rel="stylesheet"
+ href="chrome://browser/content/aboutaccounts/main.css"
+ type="text/css" />
+ <link rel="stylesheet"
+ href="chrome://browser/content/aboutaccounts/aboutaccounts.css"
+ type="text/css" />
+ </head>
+ <body>
+ <div id="stage">
+
+ <div id="manage">
+ <header>
+ <h1>&aboutAccounts.connected;</h1>
+ <div id="email"></div>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="button-row">
+ <button id="buttonOpenPrefs" class="button" href="#" tabindex="0">&aboutAccountsConfig.syncPreferences.label;</button>
+ </div>
+ </section>
+ </div>
+
+ <div id="intro">
+ <header>
+ <h1>&aboutAccounts.welcome;</h1>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="description">&aboutAccountsConfig.description;</div>
+
+ <div class="button-row">
+ <button id="buttonGetStarted" class="button" tabindex="1">&aboutAccountsConfig.startButton.label;</button>
+ </div>
+
+ <div class="links">
+ <button id="oldsync" tabindex="2">&aboutAccountsConfig.useOldSync.label;</button>
+ </div>
+ </section>
+ </div>
+
+ <div id="networkError">
+ <header>
+ <h1>&aboutAccounts.noConnection.title;</h1>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="description">&aboutAccounts.noConnection.description;</div>
+
+ <div class="button-row">
+ <button id="buttonRetry" class="button" tabindex="3">&aboutAccounts.noConnection.retry;</button>
+ </div>
+ </section>
+ </div>
+
+ <div id="configError">
+ <header>
+ <h1>&aboutAccounts.badConfig.title;</h1>
+ </header>
+
+ <section>
+ <div class="graphic graphic-sync-intro"> </div>
+
+ <div class="description">&aboutAccounts.badConfig.description;</div>
+
+ </section>
+ </div>
+
+ </div>
+
+ <iframe mozframetype="content" id="remote" />
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/aboutaccounts/aboutaccounts.js" />
+ </body>
+</html>
diff --git a/browser/base/content/aboutaccounts/images/fox.png b/browser/base/content/aboutaccounts/images/fox.png
new file mode 100644
index 000000000..83af78d6c
--- /dev/null
+++ b/browser/base/content/aboutaccounts/images/fox.png
Binary files differ
diff --git a/browser/base/content/aboutaccounts/images/graphic_sync_intro.png b/browser/base/content/aboutaccounts/images/graphic_sync_intro.png
new file mode 100644
index 000000000..ff5f482f0
--- /dev/null
+++ b/browser/base/content/aboutaccounts/images/graphic_sync_intro.png
Binary files differ
diff --git a/browser/base/content/aboutaccounts/images/graphic_sync_intro@2x.png b/browser/base/content/aboutaccounts/images/graphic_sync_intro@2x.png
new file mode 100644
index 000000000..89fda0681
--- /dev/null
+++ b/browser/base/content/aboutaccounts/images/graphic_sync_intro@2x.png
Binary files differ
diff --git a/browser/base/content/aboutaccounts/main.css b/browser/base/content/aboutaccounts/main.css
new file mode 100644
index 000000000..8f4c3b34e
--- /dev/null
+++ b/browser/base/content/aboutaccounts/main.css
@@ -0,0 +1,166 @@
+*,
+*:before,
+*:after {
+ box-sizing: border-box;
+}
+
+html {
+ background-color: #F2F2F2;
+ height: 100%;
+}
+
+body {
+ color: #424f59;
+ font: message-box;
+ font-size: 14px;
+ height: 100%;
+}
+
+a {
+ color: #0095dd;
+ cursor: pointer; /* Use the correct cursor for anchors without an href */
+}
+
+a:active {
+ outline: none;
+}
+
+a:focus {
+ outline: 1px dotted #0095dd;
+}
+
+
+a.no-underline {
+ text-decoration: none;
+}
+
+#stage {
+ background:#fff;
+ border-radius: 5px;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25);
+ margin: 0 auto;
+ min-height: 300px;
+ padding: 60px 40px 40px 40px;
+ position: relative;
+ text-align: center;
+ top: 80px;
+ width: 420px;
+}
+
+header h1
+{
+ font-size: 24px;
+ font-weight: 200;
+ line-height: 1em;
+}
+
+#intro header h1 {
+ margin: 0 0 32px 0;
+}
+
+#manage header h1 {
+ margin: 0 0 12px 0;
+}
+
+#manage header #email {
+ margin-bottom: 23px;
+ color: rgb(138, 155, 168);
+ font-size: 19px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.description {
+ font-size: 18px;
+}
+
+.button-row {
+ margin-top: 45px;
+ margin-bottom:20px;
+}
+
+.button-row button,
+.button-row a.button {
+ background: #0095dd;
+ border: none;
+ border-radius: 5px;
+ color: #FFFFFF;
+ cursor: pointer;
+ font-size: 24px;
+ padding: 15px 0;
+ transition-duration: 150ms;
+ transition-property: background-color;
+ width: 100%;
+}
+
+.button-row a.button {
+ display: inline-block;
+ text-decoration: none;
+}
+
+.button-row a.button:active,
+.button-row a.button:hover,
+.button-row a.button:focus,
+.button-row button:active,
+.button-row button:hover,
+.button-row button:focus {
+ background: #08c;
+}
+
+
+.graphic-sync-intro {
+ background-image: url(images/graphic_sync_intro.png);
+ background-repeat: no-repeat;
+ background-size: 150px 195px;
+ height: 195px;
+ margin: 0 auto;
+ overflow: hidden;
+ text-indent: 100%;
+ white-space: nowrap;
+ width: 150px;
+}
+
+.description,
+.button-row {
+ margin-top: 30px;
+}
+
+.links {
+ margin: 20px 0;
+}
+
+@media only screen and (max-width: 500px) {
+ html {
+ background: #fff;
+ }
+
+ #stage {
+ box-shadow: none;
+ margin: 30px auto 0 auto;
+ min-height: none;
+ min-width: 320px;
+ padding: 0 10px;
+ width: 100%;
+ }
+
+ .button-row {
+ margin-top: 20px;
+ }
+
+ .button-row button,
+ .button-row a.button {
+ padding: 10px 0;
+ }
+
+}
+
+/* Retina */
+@media
+only screen and (min-device-pixel-ratio: 2),
+only screen and ( min-resolution: 192dpi),
+only screen and ( min-resolution: 2dppx) {
+ .graphic-sync-intro {
+ background-image: url(images/graphic_sync_intro@2x.png);
+ }
+}
diff --git a/browser/base/content/aboutaccounts/normalize.css b/browser/base/content/aboutaccounts/normalize.css
new file mode 100644
index 000000000..c02ab25de
--- /dev/null
+++ b/browser/base/content/aboutaccounts/normalize.css
@@ -0,0 +1,402 @@
+/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 8/9.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 8/9.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9.
+ * Hide the `template` element in IE, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background: transparent;
+}
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari 5, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Correct font family set oddly in Safari 5 and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre-wrap;
+}
+
+/**
+ * Set consistent quote types.
+ */
+
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari 5.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome.
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ box-sizing: content-box; /* 2 */
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/browser/base/content/abouthealthreport/abouthealth.css b/browser/base/content/abouthealthreport/abouthealth.css
new file mode 100644
index 000000000..3dd40fc24
--- /dev/null
+++ b/browser/base/content/abouthealthreport/abouthealth.css
@@ -0,0 +1,15 @@
+* {
+ margin: 0;
+ padding: 0;
+}
+
+html, body {
+ height: 100%;
+}
+
+#remote-report {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ display: flex;
+}
diff --git a/browser/base/content/abouthealthreport/abouthealth.js b/browser/base/content/abouthealthreport/abouthealth.js
new file mode 100644
index 000000000..66cbe16f5
--- /dev/null
+++ b/browser/base/content/abouthealthreport/abouthealth.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";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const prefs = new Preferences("datareporting.healthreport.");
+
+const PREF_UNIFIED = "toolkit.telemetry.unified";
+const PREF_REPORTING_URL = "datareporting.healthreport.about.reportUrl";
+
+var healthReportWrapper = {
+ init: function () {
+ let iframe = document.getElementById("remote-report");
+ iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
+ iframe.src = this._getReportURI().spec;
+ prefs.observe("uploadEnabled", this.updatePrefState, healthReportWrapper);
+ },
+
+ uninit: function () {
+ prefs.ignore("uploadEnabled", this.updatePrefState, healthReportWrapper);
+ },
+
+ _getReportURI: function () {
+ let url = Services.urlFormatter.formatURLPref(PREF_REPORTING_URL);
+ return Services.io.newURI(url, null, null);
+ },
+
+ setDataSubmission: function (enabled) {
+ MozSelfSupport.healthReportDataSubmissionEnabled = enabled;
+ this.updatePrefState();
+ },
+
+ updatePrefState: function () {
+ try {
+ let prefs = {
+ enabled: MozSelfSupport.healthReportDataSubmissionEnabled,
+ };
+ healthReportWrapper.injectData("prefs", prefs);
+ }
+ catch (ex) {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PREFS_FAILED);
+ }
+ },
+
+ sendTelemetryPingList: function () {
+ console.log("AboutHealthReport: Collecting Telemetry ping list.");
+ MozSelfSupport.getTelemetryPingList().then((list) => {
+ console.log("AboutHealthReport: Sending Telemetry ping list.");
+ this.injectData("telemetry-ping-list", list);
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Collecting ping list failed: " + ex);
+ });
+ },
+
+ sendTelemetryPingData: function (pingId) {
+ console.log("AboutHealthReport: Collecting Telemetry ping data.");
+ MozSelfSupport.getTelemetryPing(pingId).then((ping) => {
+ console.log("AboutHealthReport: Sending Telemetry ping data.");
+ this.injectData("telemetry-ping-data", {
+ id: pingId,
+ pingData: ping,
+ });
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Loading ping data failed: " + ex);
+ this.injectData("telemetry-ping-data", {
+ id: pingId,
+ error: "error-generic",
+ });
+ });
+ },
+
+ sendCurrentEnvironment: function () {
+ console.log("AboutHealthReport: Sending Telemetry environment data.");
+ MozSelfSupport.getCurrentTelemetryEnvironment().then((environment) => {
+ this.injectData("telemetry-current-environment-data", environment);
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Collecting current environment data failed: " + ex);
+ });
+ },
+
+ sendCurrentPingData: function () {
+ console.log("AboutHealthReport: Sending current Telemetry ping data.");
+ MozSelfSupport.getCurrentTelemetrySubsessionPing().then((ping) => {
+ this.injectData("telemetry-current-ping-data", ping);
+ }).catch((ex) => {
+ console.log("AboutHealthReport: Collecting current ping data failed: " + ex);
+ });
+ },
+
+ injectData: function (type, content) {
+ let report = this._getReportURI();
+
+ // file URIs can't be used for targetOrigin, so we use "*" for this special case
+ // in all other cases, pass in the URL to the report so we properly restrict the message dispatch
+ let reportUrl = report.scheme == "file" ? "*" : report.spec;
+
+ let data = {
+ type: type,
+ content: content
+ }
+
+ let iframe = document.getElementById("remote-report");
+ iframe.contentWindow.postMessage(data, reportUrl);
+ },
+
+ handleRemoteCommand: function (evt) {
+ // Do an origin check to harden against the frame content being loaded from unexpected locations.
+ let allowedPrincipal = Services.scriptSecurityManager.getCodebasePrincipal(this._getReportURI());
+ let targetPrincipal = evt.target.nodePrincipal;
+ if (!allowedPrincipal.equals(targetPrincipal)) {
+ Cu.reportError(`Origin check failed for message "${evt.detail.command}": ` +
+ `target origin is "${targetPrincipal.origin}", expected "${allowedPrincipal.origin}"`);
+ return;
+ }
+
+ switch (evt.detail.command) {
+ case "DisableDataSubmission":
+ this.setDataSubmission(false);
+ break;
+ case "EnableDataSubmission":
+ this.setDataSubmission(true);
+ break;
+ case "RequestCurrentPrefs":
+ this.updatePrefState();
+ break;
+ case "RequestTelemetryPingList":
+ this.sendTelemetryPingList();
+ break;
+ case "RequestTelemetryPingData":
+ this.sendTelemetryPingData(evt.detail.id);
+ break;
+ case "RequestCurrentEnvironment":
+ this.sendCurrentEnvironment();
+ break;
+ case "RequestCurrentPingData":
+ this.sendCurrentPingData();
+ break;
+ default:
+ Cu.reportError("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
+ break;
+ }
+ },
+
+ initRemotePage: function () {
+ let iframe = document.getElementById("remote-report").contentDocument;
+ iframe.addEventListener("RemoteHealthReportCommand",
+ function onCommand(e) { healthReportWrapper.handleRemoteCommand(e); },
+ false);
+ healthReportWrapper.updatePrefState();
+ },
+
+ // error handling
+ ERROR_INIT_FAILED: 1,
+ ERROR_PAYLOAD_FAILED: 2,
+ ERROR_PREFS_FAILED: 3,
+
+ reportFailure: function (error) {
+ let details = {
+ errorType: error,
+ }
+ healthReportWrapper.injectData("error", details);
+ },
+
+ handleInitFailure: function () {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED);
+ },
+
+ handlePayloadFailure: function () {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED);
+ },
+}
+
+window.addEventListener("load", function () { healthReportWrapper.init(); });
+window.addEventListener("unload", function () { healthReportWrapper.uninit(); });
diff --git a/browser/base/content/abouthealthreport/abouthealth.xhtml b/browser/base/content/abouthealthreport/abouthealth.xhtml
new file mode 100644
index 000000000..464635788
--- /dev/null
+++ b/browser/base/content/abouthealthreport/abouthealth.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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
+ %securityPrefsDTD;
+ <!ENTITY % aboutHealthReportDTD SYSTEM "chrome://browser/locale/aboutHealthReport.dtd">
+ %aboutHealthReportDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&abouthealth.pagetitle;</title>
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet"
+ href="chrome://browser/content/abouthealthreport/abouthealth.css"
+ type="text/css" />
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/abouthealthreport/abouthealth.js" />
+ </head>
+ <body>
+ <iframe id="remote-report"/>
+ </body>
+</html>
+
diff --git a/browser/base/content/abouthome/aboutHome.css b/browser/base/content/abouthome/aboutHome.css
new file mode 100644
index 000000000..c0b02e257
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.css
@@ -0,0 +1,454 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+html {
+ font: message-box;
+ font-size: 100%;
+ background-color: hsl(0,0%,95%);
+ color: #000;
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ width: 100%;
+ height: 100%;
+}
+
+input,
+button {
+ font-size: inherit;
+ font-family: inherit;
+}
+
+a {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.spacer {
+ -moz-box-flex: 1;
+}
+
+#topSection {
+ text-align: center;
+}
+
+#brandLogo {
+ height: 192px;
+ width: 192px;
+ margin: 22px auto 31px;
+ background-image: url("chrome://branding/content/about-logo.png");
+ background-size: 192px auto;
+ background-position: center center;
+ background-repeat: no-repeat;
+}
+
+#searchIconAndTextContainer,
+#snippets {
+ width: 470px;
+}
+
+#searchIconAndTextContainer {
+ display: -moz-box;
+ height: 36px;
+ position: relative;
+}
+
+#searchIcon {
+ border: 1px transparent;
+ padding: 0;
+ margin: 0;
+ width: 36px;
+ height: 36px;
+ background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat;
+ position: absolute;
+}
+
+#searchText {
+ margin-left: 0;
+ -moz-box-flex: 1;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 34px;
+ padding-inline-end: 8px;
+ background: hsla(0,0%,100%,.9) padding-box;
+ border: 1px solid;
+ border-radius: 2px 0 0 2px;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+ 0 0 2px hsla(210,65%,9%,.1) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ color: inherit;
+ unicode-bidi: plaintext;
+}
+
+#searchText:dir(rtl) {
+ border-radius: 0 2px 2px 0;
+}
+
+#searchText[aria-expanded="true"] {
+ border-radius: 2px 0 0 0;
+}
+
+#searchText[aria-expanded="true"]:dir(rtl) {
+ border-radius: 0 2px 0 0;
+}
+
+#searchText[keepfocus],
+#searchText:focus,
+#searchText[autofocus] {
+ border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
+}
+
+#searchSubmit {
+ margin-inline-start: -1px;
+ color: transparent;
+ background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+ padding: 0;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ border-radius: 0 2px 2px 0;
+ border-inline-start: 1px solid transparent;
+ box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+ width: 50px;
+}
+
+#searchSubmit:dir(rtl) {
+ border-radius: 2px 0 0 2px;
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl"), linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1));
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[keepfocus] + #searchSubmit,
+#searchText + #searchSubmit:hover,
+#searchText[autofocus] + #searchSubmit {
+ border-color: #59b5fc #45a3e7 #3294d5;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[keepfocus] + #searchSubmit,
+#searchText[autofocus] + #searchSubmit {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03);
+}
+
+#searchText:focus + #searchSubmit:dir(rtl),
+#searchText[keepfocus] + #searchSubmit:dir(rtl),
+#searchText[autofocus] + #searchSubmit:dir(rtl) {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#4cb1ff, #1793e5);
+}
+
+#searchText + #searchSubmit:hover {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#66bdff, #0d9eff);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03),
+ 0 0 4px hsla(206,100%,20%,.2);
+}
+
+#searchText + #searchSubmit:dir(rtl):hover {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#66bdff, #0d9eff);
+}
+
+#searchText + #searchSubmit:hover:active {
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
+ 0 0 1px hsla(211,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+#defaultSnippet1,
+#defaultSnippet2,
+#rightsSnippet {
+ display: block;
+ min-height: 38px;
+ background: 0 center no-repeat;
+ padding: 6px 0;
+ padding-inline-start: 49px;
+}
+
+#rightsSnippet[hidden] {
+ display: none;
+}
+
+#defaultSnippet1:dir(rtl),
+#defaultSnippet2:dir(rtl),
+#rightsSnippet:dir(rtl) {
+ background-position: right 0 center;
+}
+
+#defaultSnippet1 {
+ background-image: url("chrome://browser/content/abouthome/snippet1.png");
+}
+
+#defaultSnippet2 {
+ background-image: url("chrome://browser/content/abouthome/snippet2.png");
+}
+
+#snippets {
+ display: inline-block;
+ text-align: start;
+ margin: 12px 0;
+ color: #3c3c3c;
+ font-size: 75%;
+ /* 12px is the computed font size, 15px the computed line height of the snippets
+ with Segoe UI on a default Windows 7 setup. The 15/12 multiplier approximately
+ converts em from units of font-size to units of line-height. The goal is to
+ preset the height of a three-line snippet to avoid visual moving/flickering as
+ the snippets load. */
+ min-height: calc(15/12 * 3em);
+}
+
+#launcher {
+ display: -moz-box;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ width: 100%;
+ background-color: hsla(0,0%,0%,.03);
+ border-top: 1px solid hsla(0,0%,0%,.03);
+ box-shadow: 0 1px 2px hsla(0,0%,0%,.02) inset,
+ 0 -1px 0 hsla(0,0%,100%,.25);
+}
+
+#launcher:not([session]),
+body[narrow] #launcher[session] {
+ display: block; /* display separator and restore button on separate lines */
+ text-align: center;
+ white-space: nowrap; /* prevent navigational buttons from wrapping */
+}
+
+.launchButton {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ margin: 16px 1px;
+ padding: 14px 6px;
+ min-width: 88px;
+ max-width: 176px;
+ max-height: 85px;
+ vertical-align: top;
+ white-space: normal;
+ background: transparent padding-box;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ color: #525c66;
+ font-size: 75%;
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+body[narrow] #launcher[session] > .launchButton {
+ margin: 4px 1px;
+}
+
+.launchButton:hover {
+ background-color: hsla(211,79%,6%,.03);
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+}
+
+.launchButton:hover:active {
+ background-image: linear-gradient(hsla(211,79%,6%,.02), hsla(211,79%,6%,.05));
+ border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.23) hsla(210,54%,20%,.25);
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.05) inset,
+ 0 0 1px hsla(211,79%,6%,.1) inset;
+ transition-duration: 0ms;
+}
+
+.launchButton[hidden],
+#launcher:not([session]) > #restorePreviousSessionSeparator,
+#launcher:not([session]) > #restorePreviousSession {
+ display: none;
+}
+
+#restorePreviousSessionSeparator {
+ width: 3px;
+ height: 116px;
+ margin: 0 10px;
+ background-image: linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-position: left top, center, right bottom;
+ background-size: 1px auto;
+ background-repeat: no-repeat;
+}
+
+body[narrow] #restorePreviousSessionSeparator {
+ margin: 0 auto;
+ width: 512px;
+ height: 3px;
+ background-image: linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(to right, hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-size: auto 1px;
+}
+
+#restorePreviousSession {
+ max-width: none;
+ font-size: 90%;
+}
+
+body[narrow] #restorePreviousSession {
+ font-size: 80%;
+}
+
+.launchButton::before {
+ display: block;
+ width: 32px;
+ height: 32px;
+ margin: 0 auto 6px;
+ line-height: 0; /* remove extra vertical space due to non-zero font-size */
+}
+
+#downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads.png");
+}
+
+#bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks.png");
+}
+
+#history::before {
+ content: url("chrome://browser/content/abouthome/history.png");
+}
+
+#addons::before {
+ content: url("chrome://browser/content/abouthome/addons.png");
+}
+
+#sync::before {
+ content: url("chrome://browser/content/abouthome/sync.png");
+}
+
+#settings::before {
+ content: url("chrome://browser/content/abouthome/settings.png");
+}
+
+#restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large.png");
+ height: 48px;
+ width: 48px;
+ display: inline-block; /* display on same line as text label */
+ vertical-align: middle;
+ margin-bottom: 0;
+ margin-inline-end: 8px;
+}
+
+#restorePreviousSession:dir(rtl)::before {
+ transform: scaleX(-1);
+}
+
+body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore.png");
+ height: 32px;
+ width: 32px;
+}
+
+#aboutMozilla {
+ display: block;
+ position: relative; /* pin wordmark to edge of document, not of viewport */
+ -moz-box-ordinal-group: 0;
+ opacity: .5;
+ transition: opacity 150ms;
+}
+
+#aboutMozilla:hover {
+ opacity: 1;
+}
+
+#aboutMozilla::before {
+ content: url("chrome://browser/content/abouthome/mozilla.png");
+ display: block;
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 69px;
+ height: 19px;
+}
+
+/* [HiDPI]
+ * At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
+ * rather than upscaling the original-size ones (bug 818940).
+ */
+@media not all and (max-resolution: 1dppx) {
+ #brandLogo {
+ background-image: url("chrome://branding/content/about-logo@2x.png");
+ }
+
+ #defaultSnippet1,
+ #defaultSnippet2,
+ #rightsSnippet {
+ background-size: 40px;
+ }
+
+ #defaultSnippet1 {
+ background-image: url("chrome://browser/content/abouthome/snippet1@2x.png");
+ }
+
+ #defaultSnippet2 {
+ background-image: url("chrome://browser/content/abouthome/snippet2@2x.png");
+ }
+
+ .launchButton::before,
+ #aboutMozilla::before {
+ transform: scale(.5);
+ transform-origin: 0 0;
+ }
+
+ .launchButton:dir(rtl)::before,
+ #aboutMozilla:dir(rtl)::before {
+ transform: scale(.5) translateX(32px);
+ }
+
+ #downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads@2x.png");
+ }
+
+ #bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks@2x.png");
+ }
+
+ #history::before {
+ content: url("chrome://browser/content/abouthome/history@2x.png");
+ }
+
+ #addons::before {
+ content: url("chrome://browser/content/abouthome/addons@2x.png");
+ }
+
+ #sync::before {
+ content: url("chrome://browser/content/abouthome/sync@2x.png");
+ }
+
+ #settings::before {
+ content: url("chrome://browser/content/abouthome/settings@2x.png");
+ }
+
+ #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large@2x.png");
+ }
+
+ body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore@2x.png");
+ }
+
+ #restorePreviousSession:dir(rtl)::before {
+ transform: scale(-0.5, 0.5) translateX(24px);
+ transform-origin: top center;
+ }
+
+ #aboutMozilla::before {
+ content: url("chrome://browser/content/abouthome/mozilla@2x.png");
+ }
+}
+
diff --git a/browser/base/content/abouthome/aboutHome.js b/browser/base/content/abouthome/aboutHome.js
new file mode 100644
index 000000000..50f3e01cd
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../contentSearchUI.js */
+
+// The process of adding a new default snippet involves:
+// * add a new entity to aboutHome.dtd
+// * add a <span/> for it in aboutHome.xhtml
+// * add an entry here in the proper ordering (based on spans)
+// The <a/> part of the snippet will be linked to the corresponding url.
+const DEFAULT_SNIPPETS_URLS = [
+ "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet"
+, "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons"
+];
+
+const SNIPPETS_UPDATE_INTERVAL_MS = 14400000; // 4 hours.
+
+// IndexedDB storage constants.
+const DATABASE_NAME = "abouthome";
+const DATABASE_VERSION = 1;
+const DATABASE_STORAGE = "persistent";
+const SNIPPETS_OBJECTSTORE_NAME = "snippets";
+var searchText;
+
+// This global tracks if the page has been set up before, to prevent double inits
+var gInitialized = false;
+var gObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ // The addition of the restore session button changes our width:
+ if (mutation.attributeName == "session") {
+ fitToWidth();
+ }
+ if (mutation.attributeName == "snippetsVersion") {
+ if (!gInitialized) {
+ ensureSnippetsMapThen(loadSnippets);
+ gInitialized = true;
+ }
+ return;
+ }
+ }
+});
+
+window.addEventListener("pageshow", function () {
+ // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
+ // later and may use asynchronous getters.
+ window.gObserver.observe(document.documentElement, { attributes: true });
+ window.gObserver.observe(document.getElementById("launcher"), { attributes: true });
+ fitToWidth();
+ setupSearch();
+ window.addEventListener("resize", fitToWidth);
+
+ // Ask chrome to update snippets.
+ var event = new CustomEvent("AboutHomeLoad", {bubbles:true});
+ document.dispatchEvent(event);
+});
+
+window.addEventListener("pagehide", function() {
+ window.gObserver.disconnect();
+ window.removeEventListener("resize", fitToWidth);
+});
+
+window.addEventListener("keypress", ev => {
+ if (ev.defaultPrevented) {
+ return;
+ }
+
+ // don't focus the search-box on keypress if something other than the
+ // body or document element has focus - don't want to steal input from other elements
+ // Make an exception for <a> and <button> elements (and input[type=button|submit])
+ // which don't usefully take keypresses anyway.
+ // (except space, which is handled below)
+ if (document.activeElement && document.activeElement != document.body &&
+ document.activeElement != document.documentElement &&
+ !["a", "button"].includes(document.activeElement.localName) &&
+ !document.activeElement.matches("input:-moz-any([type=button],[type=submit])")) {
+ return;
+ }
+
+ let modifiers = ev.ctrlKey + ev.altKey + ev.metaKey;
+ // ignore Ctrl/Cmd/Alt, but not Shift
+ // also ignore Tab, Insert, PageUp, etc., and Space
+ if (modifiers != 0 || ev.charCode == 0 || ev.charCode == 32)
+ return;
+
+ searchText.focus();
+ // need to send the first keypress outside the search-box manually to it
+ searchText.value += ev.key;
+});
+
+// This object has the same interface as Map and is used to store and retrieve
+// the snippets data. It is lazily initialized by ensureSnippetsMapThen(), so
+// be sure its callback returned before trying to use it.
+var gSnippetsMap;
+var gSnippetsMapCallbacks = [];
+
+/**
+ * Ensure the snippets map is properly initialized.
+ *
+ * @param aCallback
+ * Invoked once the map has been initialized, gets the map as argument.
+ * @note Snippets should never directly manage the underlying storage, since
+ * it may change inadvertently.
+ */
+function ensureSnippetsMapThen(aCallback)
+{
+ if (gSnippetsMap) {
+ aCallback(gSnippetsMap);
+ return;
+ }
+
+ // Handle multiple requests during the async initialization.
+ gSnippetsMapCallbacks.push(aCallback);
+ if (gSnippetsMapCallbacks.length > 1) {
+ // We are already updating, the callbacks will be invoked when done.
+ return;
+ }
+
+ let invokeCallbacks = function () {
+ if (!gSnippetsMap) {
+ gSnippetsMap = Object.freeze(new Map());
+ }
+
+ for (let callback of gSnippetsMapCallbacks) {
+ callback(gSnippetsMap);
+ }
+ gSnippetsMapCallbacks.length = 0;
+ }
+
+ let openRequest = indexedDB.open(DATABASE_NAME, {version: DATABASE_VERSION,
+ storage: DATABASE_STORAGE});
+
+ openRequest.onerror = function (event) {
+ // Try to delete the old database so that we can start this process over
+ // next time.
+ indexedDB.deleteDatabase(DATABASE_NAME);
+ invokeCallbacks();
+ };
+
+ openRequest.onupgradeneeded = function (event) {
+ let db = event.target.result;
+ if (!db.objectStoreNames.contains(SNIPPETS_OBJECTSTORE_NAME)) {
+ db.createObjectStore(SNIPPETS_OBJECTSTORE_NAME);
+ }
+ }
+
+ openRequest.onsuccess = function (event) {
+ let db = event.target.result;
+
+ db.onerror = function (event) {
+ invokeCallbacks();
+ }
+
+ db.onversionchange = function (event) {
+ event.target.close();
+ invokeCallbacks();
+ }
+
+ let cache = new Map();
+ let cursorRequest;
+ try {
+ cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME)
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor();
+ } catch (ex) {
+ console.error(ex);
+ invokeCallbacks();
+ return;
+ }
+
+ cursorRequest.onerror = function (event) {
+ invokeCallbacks();
+ }
+
+ cursorRequest.onsuccess = function(event) {
+ let cursor = event.target.result;
+
+ // Populate the cache from the persistent storage.
+ if (cursor) {
+ cache.set(cursor.key, cursor.value);
+ cursor.continue();
+ return;
+ }
+
+ // The cache has been filled up, create the snippets map.
+ gSnippetsMap = Object.freeze({
+ get: (aKey) => cache.get(aKey),
+ set: function (aKey, aValue) {
+ db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).put(aValue, aKey);
+ return cache.set(aKey, aValue);
+ },
+ has: (aKey) => cache.has(aKey),
+ delete: function (aKey) {
+ db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).delete(aKey);
+ return cache.delete(aKey);
+ },
+ clear: function () {
+ db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
+ .objectStore(SNIPPETS_OBJECTSTORE_NAME).clear();
+ return cache.clear();
+ },
+ get size() { return cache.size; },
+ });
+
+ setTimeout(invokeCallbacks, 0);
+ }
+ }
+}
+
+function onSearchSubmit(aEvent)
+{
+ gContentSearchController.search(aEvent);
+}
+
+
+var gContentSearchController;
+
+function setupSearch()
+{
+ // Set submit button label for when CSS background are disabled (e.g.
+ // high contrast mode).
+ document.getElementById("searchSubmit").value =
+ document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
+
+ // The "autofocus" attribute doesn't focus the form element
+ // immediately when the element is first drawn, so the
+ // attribute is also used for styling when the page first loads.
+ searchText = document.getElementById("searchText");
+ searchText.addEventListener("blur", function searchText_onBlur() {
+ searchText.removeEventListener("blur", searchText_onBlur);
+ searchText.removeAttribute("autofocus");
+ });
+
+ if (!gContentSearchController) {
+ gContentSearchController =
+ new ContentSearchUIController(searchText, searchText.parentNode,
+ "abouthome", "homepage");
+ }
+}
+
+/**
+ * Inform the test harness that we're done loading the page.
+ */
+function loadCompleted()
+{
+ var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles:true});
+ document.dispatchEvent(event);
+}
+
+/**
+ * Update the local snippets from the remote storage, then show them through
+ * showSnippets.
+ */
+function loadSnippets()
+{
+ if (!gSnippetsMap)
+ throw new Error("Snippets map has not properly been initialized");
+
+ // Allow tests to modify the snippets map before using it.
+ var event = new CustomEvent("AboutHomeLoadSnippets", {bubbles:true});
+ document.dispatchEvent(event);
+
+ // Check cached snippets version.
+ let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0;
+ let currentVersion = document.documentElement.getAttribute("snippetsVersion");
+ if (cachedVersion < currentVersion) {
+ // The cached snippets are old and unsupported, restart from scratch.
+ gSnippetsMap.clear();
+ }
+
+ // Check last snippets update.
+ let lastUpdate = gSnippetsMap.get("snippets-last-update");
+ let updateURL = document.documentElement.getAttribute("snippetsURL");
+ let shouldUpdate = !lastUpdate ||
+ Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
+ if (updateURL && shouldUpdate) {
+ // Try to update from network.
+ let xhr = new XMLHttpRequest();
+ xhr.timeout = 5000;
+ // Even if fetching should fail we don't want to spam the server, thus
+ // set the last update time regardless its results. Will retry tomorrow.
+ gSnippetsMap.set("snippets-last-update", Date.now());
+ xhr.onloadend = function (event) {
+ if (xhr.status == 200) {
+ gSnippetsMap.set("snippets", xhr.responseText);
+ gSnippetsMap.set("snippets-cached-version", currentVersion);
+ }
+ showSnippets();
+ loadCompleted();
+ };
+ try {
+ xhr.open("GET", updateURL, true);
+ xhr.send(null);
+ } catch (ex) {
+ showSnippets();
+ loadCompleted();
+ return;
+ }
+ } else {
+ showSnippets();
+ loadCompleted();
+ }
+}
+
+/**
+ * Shows locally cached remote snippets, or default ones when not available.
+ *
+ * @note: snippets should never invoke showSnippets(), or they may cause
+ * a "too much recursion" exception.
+ */
+var _snippetsShown = false;
+function showSnippets()
+{
+ let snippetsElt = document.getElementById("snippets");
+
+ // Show about:rights notification, if needed.
+ let showRights = document.documentElement.getAttribute("showKnowYourRights");
+ if (showRights) {
+ let rightsElt = document.getElementById("rightsSnippet");
+ let anchor = rightsElt.getElementsByTagName("a")[0];
+ anchor.href = "about:rights";
+ snippetsElt.appendChild(rightsElt);
+ rightsElt.removeAttribute("hidden");
+ return;
+ }
+
+ if (!gSnippetsMap)
+ throw new Error("Snippets map has not properly been initialized");
+ if (_snippetsShown) {
+ // There's something wrong with the remote snippets, just in case fall back
+ // to the default snippets.
+ showDefaultSnippets();
+ throw new Error("showSnippets should never be invoked multiple times");
+ }
+ _snippetsShown = true;
+
+ let snippets = gSnippetsMap.get("snippets");
+ // If there are remotely fetched snippets, try to to show them.
+ if (snippets) {
+ // Injecting snippets can throw if they're invalid XML.
+ try {
+ snippetsElt.innerHTML = snippets;
+ // Scripts injected by innerHTML are inactive, so we have to relocate them
+ // through DOM manipulation to activate their contents.
+ Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) {
+ let relocatedScript = document.createElement("script");
+ relocatedScript.type = "text/javascript;version=1.8";
+ relocatedScript.text = elt.text;
+ elt.parentNode.replaceChild(relocatedScript, elt);
+ });
+ return;
+ } catch (ex) {
+ // Bad content, continue to show default snippets.
+ }
+ }
+
+ showDefaultSnippets();
+}
+
+/**
+ * Clear snippets element contents and show default snippets.
+ */
+function showDefaultSnippets()
+{
+ // Clear eventual contents...
+ let snippetsElt = document.getElementById("snippets");
+ snippetsElt.innerHTML = "";
+
+ // ...then show default snippets.
+ let defaultSnippetsElt = document.getElementById("defaultSnippets");
+ let entries = defaultSnippetsElt.querySelectorAll("span");
+ // Choose a random snippet. Assume there is always at least one.
+ let randIndex = Math.floor(Math.random() * entries.length);
+ let entry = entries[randIndex];
+ // Inject url in the eventual link.
+ if (DEFAULT_SNIPPETS_URLS[randIndex]) {
+ let links = entry.getElementsByTagName("a");
+ // Default snippets can have only one link, otherwise something is messed
+ // up in the translation.
+ if (links.length == 1) {
+ links[0].href = DEFAULT_SNIPPETS_URLS[randIndex];
+ }
+ }
+ // Move the default snippet to the snippets element.
+ snippetsElt.appendChild(entry);
+}
+
+function fitToWidth() {
+ if (document.documentElement.scrollWidth > window.innerWidth) {
+ document.body.setAttribute("narrow", "true");
+ } else if (document.body.hasAttribute("narrow")) {
+ document.body.removeAttribute("narrow");
+ fitToWidth();
+ }
+}
diff --git a/browser/base/content/abouthome/aboutHome.xhtml b/browser/base/content/abouthome/aboutHome.xhtml
new file mode 100644
index 000000000..c288e732e
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+ %aboutHomeDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+ %browserDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&abouthome.pageTitle;</title>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/content/contentSearchUI.css"/>
+ <link rel="stylesheet" type="text/css" media="all" defer="defer"
+ href="chrome://browser/content/abouthome/aboutHome.css"/>
+
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/abouthome/aboutHome.js"/>
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/contentSearchUI.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div class="spacer"/>
+ <div id="topSection">
+ <div id="brandLogo"></div>
+
+ <div id="searchIconAndTextContainer">
+ <div id="searchIcon"/>
+ <input type="text" name="q" value="" id="searchText" maxlength="256"
+ aria-label="&contentSearchInput.label;" autofocus="autofocus"/>
+ <input id="searchSubmit" type="button" onclick="onSearchSubmit(event)"
+ title="&contentSearchSubmit.tooltip;"/>
+ </div>
+
+ <div id="snippetContainer">
+ <div id="defaultSnippets" hidden="true">
+ <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span>
+ <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span>
+ </div>
+ <span id="rightsSnippet" hidden="true">&abouthome.rightsSnippet;</span>
+ <div id="snippets"/>
+ </div>
+ </div>
+ <div class="spacer"/>
+
+ <div id="launcher">
+ <button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
+ <button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
+ <button class="launchButton" id="history">&abouthome.historyButton.label;</button>
+ <button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
+ <button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
+#ifdef XP_WIN
+ <button class="launchButton" id="settings">&abouthome.preferencesButtonWin.label;</button>
+#else
+ <button class="launchButton" id="settings">&abouthome.preferencesButtonUnix.label;</button>
+#endif
+ <div id="restorePreviousSessionSeparator"/>
+ <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
+ </div>
+
+ <a id="aboutMozilla" href="https://www.mozilla.org/about/?utm_source=about-home&amp;utm_medium=Referral"
+ aria-label="&abouthome.aboutMozilla.label;"/>
+ </body>
+</html>
diff --git a/browser/base/content/abouthome/addons.png b/browser/base/content/abouthome/addons.png
new file mode 100644
index 000000000..41519ce49
--- /dev/null
+++ b/browser/base/content/abouthome/addons.png
Binary files differ
diff --git a/browser/base/content/abouthome/addons@2x.png b/browser/base/content/abouthome/addons@2x.png
new file mode 100644
index 000000000..d4d04ee8c
--- /dev/null
+++ b/browser/base/content/abouthome/addons@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/bookmarks.png b/browser/base/content/abouthome/bookmarks.png
new file mode 100644
index 000000000..5c7e194a6
--- /dev/null
+++ b/browser/base/content/abouthome/bookmarks.png
Binary files differ
diff --git a/browser/base/content/abouthome/bookmarks@2x.png b/browser/base/content/abouthome/bookmarks@2x.png
new file mode 100644
index 000000000..7ede00744
--- /dev/null
+++ b/browser/base/content/abouthome/bookmarks@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/downloads.png b/browser/base/content/abouthome/downloads.png
new file mode 100644
index 000000000..3d4d10e7a
--- /dev/null
+++ b/browser/base/content/abouthome/downloads.png
Binary files differ
diff --git a/browser/base/content/abouthome/downloads@2x.png b/browser/base/content/abouthome/downloads@2x.png
new file mode 100644
index 000000000..d384a22c6
--- /dev/null
+++ b/browser/base/content/abouthome/downloads@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/history.png b/browser/base/content/abouthome/history.png
new file mode 100644
index 000000000..ae742b1aa
--- /dev/null
+++ b/browser/base/content/abouthome/history.png
Binary files differ
diff --git a/browser/base/content/abouthome/history@2x.png b/browser/base/content/abouthome/history@2x.png
new file mode 100644
index 000000000..696902e7c
--- /dev/null
+++ b/browser/base/content/abouthome/history@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/mozilla.png b/browser/base/content/abouthome/mozilla.png
new file mode 100644
index 000000000..f2c348d13
--- /dev/null
+++ b/browser/base/content/abouthome/mozilla.png
Binary files differ
diff --git a/browser/base/content/abouthome/mozilla@2x.png b/browser/base/content/abouthome/mozilla@2x.png
new file mode 100644
index 000000000..f8fc622d0
--- /dev/null
+++ b/browser/base/content/abouthome/mozilla@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore-large.png b/browser/base/content/abouthome/restore-large.png
new file mode 100644
index 000000000..ef593e6e1
--- /dev/null
+++ b/browser/base/content/abouthome/restore-large.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore-large@2x.png b/browser/base/content/abouthome/restore-large@2x.png
new file mode 100644
index 000000000..d5c71d0b0
--- /dev/null
+++ b/browser/base/content/abouthome/restore-large@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore.png b/browser/base/content/abouthome/restore.png
new file mode 100644
index 000000000..5c3d6f437
--- /dev/null
+++ b/browser/base/content/abouthome/restore.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore@2x.png b/browser/base/content/abouthome/restore@2x.png
new file mode 100644
index 000000000..5acb63052
--- /dev/null
+++ b/browser/base/content/abouthome/restore@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/settings.png b/browser/base/content/abouthome/settings.png
new file mode 100644
index 000000000..4b0c30990
--- /dev/null
+++ b/browser/base/content/abouthome/settings.png
Binary files differ
diff --git a/browser/base/content/abouthome/settings@2x.png b/browser/base/content/abouthome/settings@2x.png
new file mode 100644
index 000000000..c77cb9a92
--- /dev/null
+++ b/browser/base/content/abouthome/settings@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet1.png b/browser/base/content/abouthome/snippet1.png
new file mode 100644
index 000000000..ce2ec55c2
--- /dev/null
+++ b/browser/base/content/abouthome/snippet1.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet1@2x.png b/browser/base/content/abouthome/snippet1@2x.png
new file mode 100644
index 000000000..f57cd0a82
--- /dev/null
+++ b/browser/base/content/abouthome/snippet1@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet2.png b/browser/base/content/abouthome/snippet2.png
new file mode 100644
index 000000000..e0724fb6d
--- /dev/null
+++ b/browser/base/content/abouthome/snippet2.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet2@2x.png b/browser/base/content/abouthome/snippet2@2x.png
new file mode 100644
index 000000000..40577f52f
--- /dev/null
+++ b/browser/base/content/abouthome/snippet2@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/sync.png b/browser/base/content/abouthome/sync.png
new file mode 100644
index 000000000..11e40cc93
--- /dev/null
+++ b/browser/base/content/abouthome/sync.png
Binary files differ
diff --git a/browser/base/content/abouthome/sync@2x.png b/browser/base/content/abouthome/sync@2x.png
new file mode 100644
index 000000000..6354f5bf9
--- /dev/null
+++ b/browser/base/content/abouthome/sync@2x.png
Binary files differ
diff --git a/browser/base/content/baseMenuOverlay.xul b/browser/base/content/baseMenuOverlay.xul
new file mode 100644
index 000000000..da74ca077
--- /dev/null
+++ b/browser/base/content/baseMenuOverlay.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/.
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % baseMenuOverlayDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd">
+%baseMenuOverlayDTD;
+]>
+<overlay id="baseMenuOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+#ifdef XP_MACOSX
+<!-- nsMenuBarX hides these and uses them to build the Application menu.
+ When using Carbon widgets for Mac OS X widgets, some of these are not
+ used as they only apply to Cocoa widget builds. All version of Firefox
+ through Firefox 2 will use Carbon widgets. -->
+ <menupopup id="menu_ToolsPopup">
+ <menuitem id="menu_preferences" label="&preferencesCmdMac.label;" key="key_preferencesCmdMac" oncommand="openPreferences();"/>
+ <menuitem id="menu_mac_services" label="&servicesMenuMac.label;"/>
+ <menuitem id="menu_mac_hide_app" label="&hideThisAppCmdMac2.label;" key="key_hideThisAppCmdMac"/>
+ <menuitem id="menu_mac_hide_others" label="&hideOtherAppsCmdMac.label;" key="key_hideOtherAppsCmdMac"/>
+ <menuitem id="menu_mac_show_all" label="&showAllAppsCmdMac.label;"/>
+ </menupopup>
+<!-- Mac window menu -->
+#include ../../../toolkit/content/macWindowMenu.inc
+#endif
+
+#ifdef XP_WIN
+ <menu id="helpMenu"
+ label="&helpMenuWin.label;"
+ accesskey="&helpMenuWin.accesskey;">
+#else
+ <menu id="helpMenu"
+ label="&helpMenu.label;"
+ accesskey="&helpMenu.accesskey;">
+#endif
+ <menupopup id="menu_HelpPopup" onpopupshowing="buildHelpMenu();">
+ <menuitem id="menu_openHelp"
+ oncommand="openHelpLink('firefox-help')"
+ onclick="checkForMiddleClick(this, event);"
+ label="&productHelp2.label;"
+ accesskey="&productHelp2.accesskey;"
+#ifdef XP_MACOSX
+ key="key_openHelpMac"/>
+#else
+ />
+#endif
+ <menuitem id="menu_openTour"
+ oncommand="openTourPage();"
+ label="&helpShowTour2.label;"
+ accesskey="&helpShowTour2.accesskey;"/>
+ <menuitem id="menu_keyboardShortcuts"
+ oncommand="openHelpLink('keyboard-shortcuts')"
+ onclick="checkForMiddleClick(this, event);"
+ label="&helpKeyboardShortcuts.label;"
+ accesskey="&helpKeyboardShortcuts.accesskey;"/>
+#ifdef MOZ_TELEMETRY_REPORTING
+ <menuitem id="healthReport"
+ label="&healthReport2.label;"
+ accesskey="&healthReport2.accesskey;"
+ oncommand="openHealthReport()"
+ onclick="checkForMiddleClick(this, event);"/>
+#endif
+ <menuitem id="troubleShooting"
+ accesskey="&helpTroubleshootingInfo.accesskey;"
+ label="&helpTroubleshootingInfo.label;"
+ oncommand="openTroubleshootingPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="feedbackPage"
+ accesskey="&helpFeedbackPage.accesskey;"
+ label="&helpFeedbackPage.label;"
+ oncommand="openFeedbackPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="helpSafeMode"
+ accesskey="&helpSafeMode.accesskey;"
+ label="&helpSafeMode.label;"
+ stopaccesskey="&helpSafeMode.stop.accesskey;"
+ stoplabel="&helpSafeMode.stop.label;"
+ oncommand="safeModeRestart();"/>
+ <menuseparator id="aboutSeparator"/>
+ <menuitem id="aboutName"
+ accesskey="&aboutProduct2.accesskey;"
+ label="&aboutProduct2.label;"
+ oncommand="openAboutDialog();"/>
+ </menupopup>
+ </menu>
+
+ <keyset id="baseMenuKeyset">
+#ifdef XP_MACOSX
+ <key id="key_openHelpMac"
+ oncommand="openHelpLink('firefox-osxkey');"
+ key="&helpMac.commandkey;"
+ modifiers="accel"/>
+<!-- These are used to build the Application menu under Cocoa widgets -->
+ <key id="key_preferencesCmdMac"
+ key="&preferencesCmdMac.commandkey;"
+ modifiers="accel"/>
+ <key id="key_hideThisAppCmdMac"
+ key="&hideThisAppCmdMac2.commandkey;"
+ modifiers="accel"/>
+ <key id="key_hideOtherAppsCmdMac"
+ key="&hideOtherAppsCmdMac.commandkey;"
+ modifiers="accel,alt"/>
+#endif
+ </keyset>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
+ <stringbundle id="bundle_browser_region" src="chrome://browser-region/locale/region.properties"/>
+ </stringbundleset>
+</overlay>
diff --git a/browser/base/content/blockedSite.xhtml b/browser/base/content/blockedSite.xhtml
new file mode 100644
index 000000000..10a4b33e8
--- /dev/null
+++ b/browser/base/content/blockedSite.xhtml
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+ %brandDTD;
+ <!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+ %blockedSiteDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
+ <head>
+ <link rel="stylesheet" href="chrome://browser/skin/blockedSite.css" type="text/css" media="all" />
+ <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
+
+ <script type="application/javascript"><![CDATA[
+ // Error url MUST be formatted like this:
+ // about:blocked?e=error_code&u=url(&o=1)?
+ // (o=1 when user overrides are allowed)
+
+ // Note that this file uses document.documentURI to get
+ // the URL (with the format from above). This is because
+ // document.location.href gets the current URI off the docshell,
+ // which is the URL displayed in the location bar, i.e.
+ // the URI that the user attempted to load.
+
+ function getErrorCode()
+ {
+ var url = document.documentURI;
+ var error = url.search(/e\=/);
+ var duffUrl = url.search(/\&u\=/);
+ return decodeURIComponent(url.slice(error + 2, duffUrl));
+ }
+
+ function getURL()
+ {
+ var url = document.documentURI;
+ var match = url.match(/&u=([^&]+)&/);
+
+ // match == null if not found; if so, return an empty string
+ // instead of what would turn out to be portions of the URI
+ if (!match)
+ return "";
+
+ url = decodeURIComponent(match[1]);
+
+ // If this is a view-source page, then get then real URI of the page
+ if (url.startsWith("view-source:"))
+ url = url.slice(12);
+ return url;
+ }
+
+ /**
+ * Check whether this warning page should be overridable or whether
+ * the "ignore warning" button should be hidden.
+ */
+ function getOverride()
+ {
+ var url = document.documentURI;
+ var match = url.match(/&o=1&/);
+ return !!match;
+ }
+
+ /**
+ * Attempt to get the hostname via document.location. Fail back
+ * to getURL so that we always return something meaningful.
+ */
+ function getHostString()
+ {
+ try {
+ return document.location.hostname;
+ } catch (e) {
+ return getURL();
+ }
+ }
+
+ function initPage()
+ {
+ var error = "";
+ switch (getErrorCode()) {
+ case "malwareBlocked" :
+ error = "malware";
+ break;
+ case "deceptiveBlocked" :
+ error = "phishing";
+ break;
+ case "unwantedBlocked" :
+ error = "unwanted";
+ break;
+ default:
+ return;
+ }
+
+ var el;
+
+ if (error !== "malware") {
+ el = document.getElementById("errorTitleText_malware");
+ el.parentNode.removeChild(el);
+ el = document.getElementById("errorShortDescText_malware");
+ el.parentNode.removeChild(el);
+ el = document.getElementById("errorLongDescText_malware");
+ el.parentNode.removeChild(el);
+ }
+
+ if (error !== "phishing") {
+ el = document.getElementById("errorTitleText_phishing");
+ el.parentNode.removeChild(el);
+ el = document.getElementById("errorShortDescText_phishing");
+ el.parentNode.removeChild(el);
+ el = document.getElementById("errorLongDescText_phishing");
+ el.parentNode.removeChild(el);
+ }
+
+ if (error !== "unwanted") {
+ el = document.getElementById("errorTitleText_unwanted");
+ el.parentNode.removeChild(el);
+ el = document.getElementById("errorShortDescText_unwanted");
+ el.parentNode.removeChild(el);
+ el = document.getElementById("errorLongDescText_unwanted");
+ el.parentNode.removeChild(el);
+ }
+
+ // Set sitename
+ document.getElementById(error + "_sitename").textContent = getHostString();
+ document.title = document.getElementById("errorTitleText_" + error)
+ .innerHTML;
+
+ if (!getOverride()) {
+ var btn = document.getElementById("ignoreWarningButton");
+ if (btn) {
+ btn.parentNode.removeChild(btn);
+ }
+ }
+
+ // Inform the test harness that we're done loading the page
+ var event = new CustomEvent("AboutBlockedLoaded");
+ document.dispatchEvent(event);
+ }
+ ]]></script>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div id="errorPageContainer" class="container">
+
+ <!-- Error Title -->
+ <div id="errorTitle" class="title">
+ <h1 class="title-text" id="errorTitleText_phishing">&safeb.blocked.phishingPage.title2;</h1>
+ <h1 class="title-text" id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
+ <h1 class="title-text" id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
+ </div>
+
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc2;</p>
+ <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
+ <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
+ </div>
+
+ <!-- Long Description -->
+ <div id="errorLongDesc">
+ <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc2;</p>
+ <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
+ <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
+ </div>
+
+ <!-- Action buttons -->
+ <div id="buttons" class="button-container">
+ <!-- Commands handled in browser.js -->
+ <button id="getMeOutButton" class="primary">&safeb.palm.accept.label;</button>
+ <div class="button-spacer"></div>
+ <button id="reportButton">&safeb.palm.reportPage.label;</button>
+ </div>
+ </div>
+ <div id="ignoreWarning">
+ <button id="ignoreWarningButton">&safeb.palm.decline.label;</button>
+ </div>
+ </div>
+ <!--
+ - Note: It is important to run the script this way, instead of using
+ - an onload handler. This is because error pages are loaded as
+ - LOAD_BACKGROUND, which means that onload handlers will not be executed.
+ -->
+ <script type="application/javascript">
+ initPage();
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js
new file mode 100644
index 000000000..1f81d1fb0
--- /dev/null
+++ b/browser/base/content/browser-addons.js
@@ -0,0 +1,747 @@
+/* -*- 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/. */
+
+// Removes a doorhanger notification if all of the installs it was notifying
+// about have ended in some way.
+function removeNotificationOnEnd(notification, installs) {
+ let count = installs.length;
+
+ function maybeRemove(install) {
+ install.removeListener(this);
+
+ if (--count == 0) {
+ // Check that the notification is still showing
+ let current = PopupNotifications.getNotification(notification.id, notification.browser);
+ if (current === notification)
+ notification.remove();
+ }
+ }
+
+ for (let install of installs) {
+ install.addListener({
+ onDownloadCancelled: maybeRemove,
+ onDownloadFailed: maybeRemove,
+ onInstallFailed: maybeRemove,
+ onInstallEnded: maybeRemove
+ });
+ }
+}
+
+const gXPInstallObserver = {
+ _findChildShell: function (aDocShell, aSoughtShell)
+ {
+ if (aDocShell == aSoughtShell)
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
+ for (var i = 0; i < node.childCount; ++i) {
+ var docShell = node.getChildAt(i);
+ docShell = this._findChildShell(docShell, aSoughtShell);
+ if (docShell == aSoughtShell)
+ return docShell;
+ }
+ return null;
+ },
+
+ _getBrowser: function (aDocShell)
+ {
+ for (let browser of gBrowser.browsers) {
+ if (this._findChildShell(browser.docShell, aDocShell))
+ return browser;
+ }
+ return null;
+ },
+
+ pendingInstalls: new WeakMap(),
+
+ showInstallConfirmation: function(browser, installInfo, height = undefined) {
+ // If the confirmation notification is already open cache the installInfo
+ // and the new confirmation will be shown later
+ if (PopupNotifications.getNotification("addon-install-confirmation", browser)) {
+ let pending = this.pendingInstalls.get(browser);
+ if (pending) {
+ pending.push(installInfo);
+ } else {
+ this.pendingInstalls.set(browser, [installInfo]);
+ }
+ return;
+ }
+
+ let showNextConfirmation = () => {
+ // Make sure the browser is still alive.
+ if (gBrowser.browsers.indexOf(browser) == -1)
+ return;
+
+ let pending = this.pendingInstalls.get(browser);
+ if (pending && pending.length)
+ this.showInstallConfirmation(browser, pending.shift());
+ }
+
+ // If all installs have already been cancelled in some way then just show
+ // the next confirmation
+ if (installInfo.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
+ showNextConfirmation();
+ return;
+ }
+
+ const anchorID = "addons-notification-icon";
+
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ displayURI: installInfo.originatingURI,
+ timeout: Date.now() + 30000,
+ };
+
+ let cancelInstallation = () => {
+ if (installInfo) {
+ for (let install of installInfo.installs) {
+ // The notification may have been closed because the add-ons got
+ // cancelled elsewhere, only try to cancel those that are still
+ // pending install.
+ if (install.state != AddonManager.STATE_CANCELLED)
+ install.cancel();
+ }
+ }
+
+ this.acceptInstallation = null;
+
+ showNextConfirmation();
+ };
+
+ let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
+ let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
+
+ options.eventCallback = (aEvent) => {
+ switch (aEvent) {
+ case "removed":
+ cancelInstallation();
+ break;
+ case "shown":
+ let addonList = document.getElementById("addon-install-confirmation-content");
+ while (addonList.firstChild)
+ addonList.firstChild.remove();
+
+ for (let install of installInfo.installs) {
+ let container = document.createElement("hbox");
+
+ let name = document.createElement("label");
+ name.setAttribute("value", install.addon.name);
+ name.setAttribute("class", "addon-install-confirmation-name");
+ container.appendChild(name);
+
+ if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
+ let unsigned = document.createElement("label");
+ unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned"));
+ unsigned.setAttribute("class", "addon-install-confirmation-unsigned");
+ container.appendChild(unsigned);
+ }
+
+ addonList.appendChild(container);
+ }
+
+ this.acceptInstallation = () => {
+ for (let install of installInfo.installs)
+ install.install();
+ installInfo = null;
+
+ Services.telemetry
+ .getHistogramById("SECURITY_UI")
+ .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL_CLICK_THROUGH);
+ };
+ break;
+ }
+ };
+
+ options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+
+ let messageString;
+ let notification = document.getElementById("addon-install-confirmation-notification");
+ if (unsigned.length == installInfo.installs.length) {
+ // None of the add-ons are verified
+ messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
+ notification.setAttribute("warning", "true");
+ options.learnMoreURL += "unsigned-addons";
+ }
+ else if (unsigned.length == 0) {
+ // All add-ons are verified or don't need to be verified
+ messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
+ notification.removeAttribute("warning");
+ options.learnMoreURL += "find-and-install-add-ons";
+ }
+ else {
+ // Some of the add-ons are unverified, the list of names will indicate
+ // which
+ messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
+ notification.setAttribute("warning", "true");
+ options.learnMoreURL += "unsigned-addons";
+ }
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ messageString = messageString.replace("#1", brandShortName);
+ messageString = messageString.replace("#2", installInfo.installs.length);
+
+ let cancelButton = document.getElementById("addon-install-confirmation-cancel");
+ cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
+ cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
+
+ let acceptButton = document.getElementById("addon-install-confirmation-accept");
+ acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
+ acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
+
+ if (height) {
+ let notification = document.getElementById("addon-install-confirmation-notification");
+ notification.style.minHeight = height + "px";
+ }
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (tab) {
+ gBrowser.selectedTab = tab;
+ }
+
+ let popup = PopupNotifications.show(browser, "addon-install-confirmation",
+ messageString, anchorID, null, null,
+ options);
+
+ removeNotificationOnEnd(popup, installInfo.installs);
+
+ Services.telemetry
+ .getHistogramById("SECURITY_UI")
+ .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ var brandBundle = document.getElementById("bundle_brand");
+ var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+ var browser = installInfo.browser;
+
+ // Make sure the browser is still alive.
+ if (!browser || gBrowser.browsers.indexOf(browser) == -1)
+ return;
+
+ const anchorID = "addons-notification-icon";
+ var messageString, action;
+ var brandShortName = brandBundle.getString("brandShortName");
+
+ var notificationID = aTopic;
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ displayURI: installInfo.originatingURI,
+ timeout: Date.now() + 30000,
+ };
+
+ switch (aTopic) {
+ case "addon-install-disabled": {
+ notificationID = "xpinstall-disabled";
+
+ if (gPrefService.prefIsLocked("xpinstall.enabled")) {
+ messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
+ buttons = [];
+ }
+ else {
+ messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
+
+ action = {
+ label: gNavigatorBundle.getString("xpinstallDisabledButton"),
+ accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
+ callback: function editPrefs() {
+ gPrefService.setBoolPref("xpinstall.enabled", true);
+ }
+ };
+ }
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ break; }
+ case "addon-install-origin-blocked": {
+ messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
+ [brandShortName]);
+
+ let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
+ let popup = PopupNotifications.show(browser, notificationID,
+ messageString, anchorID,
+ null, null, options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ break; }
+ case "addon-install-blocked": {
+ messageString = gNavigatorBundle.getFormattedString("xpinstallPromptMessage",
+ [brandShortName]);
+
+ let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
+ action = {
+ label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
+ accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
+ callback: function() {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
+ installInfo.install();
+ }
+ };
+
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
+ let popup = PopupNotifications.show(browser, notificationID,
+ messageString, anchorID,
+ action, null, options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ break; }
+ case "addon-install-started": {
+ let needsDownload = function needsDownload(aInstall) {
+ return aInstall.state != AddonManager.STATE_DOWNLOADED;
+ }
+ // If all installs have already been downloaded then there is no need to
+ // show the download progress
+ if (!installInfo.installs.some(needsDownload))
+ return;
+ notificationID = "addon-progress";
+ messageString = gNavigatorBundle.getString("addonDownloadingAndVerifying");
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ messageString = messageString.replace("#1", installInfo.installs.length);
+ options.installs = installInfo.installs;
+ options.contentWindow = browser.contentWindow;
+ options.sourceURI = browser.currentURI;
+ options.eventCallback = (aEvent) => {
+ switch (aEvent) {
+ case "removed":
+ options.contentWindow = null;
+ options.sourceURI = null;
+ break;
+ }
+ };
+ let notification = PopupNotifications.show(browser, notificationID, messageString,
+ anchorID, null, null, options);
+ notification._startTime = Date.now();
+
+ let cancelButton = document.getElementById("addon-progress-cancel");
+ cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
+ cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
+
+ let acceptButton = document.getElementById("addon-progress-accept");
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
+ acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
+ } else {
+ acceptButton.hidden = true;
+ }
+ break; }
+ case "addon-install-failed": {
+ // TODO This isn't terribly ideal for the multiple failure case
+ for (let install of installInfo.installs) {
+ let host;
+ try {
+ host = options.displayURI.host;
+ } catch (e) {
+ // displayURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
+ }
+
+ if (!host)
+ host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
+ install.sourceURI.host;
+
+ let error = (host || install.error == 0) ? "addonInstallError" : "addonLocalInstallError";
+ let args;
+ if (install.error < 0) {
+ error += install.error;
+ args = [brandShortName, install.name];
+ } else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ error += "Blocklisted";
+ args = [install.name];
+ } else {
+ error += "Incompatible";
+ args = [brandShortName, Services.appinfo.version, install.name];
+ }
+
+ // Add Learn More link when refusing to install an unsigned add-on
+ if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
+ options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
+ }
+
+ messageString = gNavigatorBundle.getFormattedString(error, args);
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+
+ // Can't have multiple notifications with the same ID, so stop here.
+ break;
+ }
+ this._removeProgressNotification(browser);
+ break; }
+ case "addon-install-confirmation": {
+ let showNotification = () => {
+ let height = undefined;
+
+ if (PopupNotifications.isPanelOpen) {
+ let rect = document.getElementById("addon-progress-notification").getBoundingClientRect();
+ height = rect.height;
+ }
+
+ this._removeProgressNotification(browser);
+ this.showInstallConfirmation(browser, installInfo, height);
+ };
+
+ let progressNotification = PopupNotifications.getNotification("addon-progress", browser);
+ if (progressNotification) {
+ let downloadDuration = Date.now() - progressNotification._startTime;
+ let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay") - downloadDuration;
+ if (securityDelay > 0) {
+ setTimeout(() => {
+ // The download may have been cancelled during the security delay
+ if (PopupNotifications.getNotification("addon-progress", browser))
+ showNotification();
+ }, securityDelay);
+ break;
+ }
+ }
+ showNotification();
+ break; }
+ case "addon-install-complete": {
+ let needsRestart = installInfo.installs.some(function(i) {
+ return i.addon.pendingOperations != AddonManager.PENDING_NONE;
+ });
+
+ if (needsRestart) {
+ notificationID = "addon-install-restart";
+ messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
+ action = {
+ label: gNavigatorBundle.getString("addonInstallRestartButton"),
+ accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
+ callback: function() {
+ BrowserUtils.restartApplication();
+ }
+ };
+ }
+ else {
+ messageString = gNavigatorBundle.getString("addonsInstalled");
+ action = null;
+ }
+
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ messageString = messageString.replace("#1", installInfo.installs[0].name);
+ messageString = messageString.replace("#2", installInfo.installs.length);
+ messageString = messageString.replace("#3", brandShortName);
+
+ // Remove notificaion on dismissal, since it's possible to cancel the
+ // install through the addons manager UI, making the "restart" prompt
+ // irrelevant.
+ options.removeOnDismissal = true;
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ break; }
+ }
+ },
+ _removeProgressNotification(aBrowser) {
+ let notification = PopupNotifications.getNotification("addon-progress", aBrowser);
+ if (notification)
+ notification.remove();
+ }
+};
+
+var LightWeightThemeWebInstaller = {
+ init: function () {
+ let mm = window.messageManager;
+ mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
+ mm.addMessageListener("LightWeightThemeWebInstaller:Preview", this);
+ mm.addMessageListener("LightWeightThemeWebInstaller:ResetPreview", this);
+ },
+
+ receiveMessage: function (message) {
+ // ignore requests from background tabs
+ if (message.target != gBrowser.selectedBrowser) {
+ return;
+ }
+
+ let data = message.data;
+
+ switch (message.name) {
+ case "LightWeightThemeWebInstaller:Install": {
+ this._installRequest(data.themeData, data.baseURI);
+ break;
+ }
+ case "LightWeightThemeWebInstaller:Preview": {
+ this._preview(data.themeData, data.baseURI);
+ break;
+ }
+ case "LightWeightThemeWebInstaller:ResetPreview": {
+ this._resetPreview(data && data.baseURI);
+ break;
+ }
+ }
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "TabSelect": {
+ this._resetPreview();
+ break;
+ }
+ }
+ },
+
+ get _manager () {
+ let temp = {};
+ Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
+ delete this._manager;
+ return this._manager = temp.LightweightThemeManager;
+ },
+
+ _installRequest: function (dataString, baseURI) {
+ let data = this._manager.parseTheme(dataString, baseURI);
+
+ if (!data) {
+ return;
+ }
+
+ let uri = makeURI(baseURI);
+
+ // A notification bar with the option to undo is normally shown after a
+ // theme is installed. But the discovery pane served from the url(s)
+ // below has its own toggle switch for quick undos, so don't show the
+ // notification in that case.
+ let notify = uri.prePath != "https://discovery.addons.mozilla.org";
+ if (notify) {
+ try {
+ if (Services.prefs.getBoolPref("extensions.webapi.testing")
+ && (uri.prePath == "https://discovery.addons.allizom.org"
+ || uri.prePath == "https://discovery.addons-dev.allizom.org")) {
+ notify = false;
+ }
+ } catch (e) {
+ // getBoolPref() throws if the testing pref isn't set. ignore it.
+ }
+ }
+
+ if (this._isAllowed(baseURI)) {
+ this._install(data, notify);
+ return;
+ }
+
+ let allowButtonText =
+ gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
+ let allowButtonAccesskey =
+ gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
+ let message =
+ gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
+ [uri.host]);
+ let buttons = [{
+ label: allowButtonText,
+ accessKey: allowButtonAccesskey,
+ callback: function () {
+ LightWeightThemeWebInstaller._install(data, notify);
+ }
+ }];
+
+ this._removePreviousNotifications();
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let notificationBar =
+ notificationBox.appendNotification(message, "lwtheme-install-request", "",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notificationBar.persistence = 1;
+ },
+
+ _install: function (newLWTheme, notify) {
+ let previousLWTheme = this._manager.currentTheme;
+
+ let listener = {
+ onEnabling: function(aAddon, aRequiresRestart) {
+ if (!aRequiresRestart) {
+ return;
+ }
+
+ let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
+ [aAddon.name], 1);
+
+ let action = {
+ label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
+ accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
+ callback: function () {
+ BrowserUtils.restartApplication();
+ }
+ };
+
+ let options = {
+ timeout: Date.now() + 30000
+ };
+
+ PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
+ messageString, "addons-notification-icon",
+ action, null, options);
+ },
+
+ onEnabled: function(aAddon) {
+ if (notify) {
+ LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
+ }
+ }
+ };
+
+ AddonManager.addAddonListener(listener);
+ this._manager.currentTheme = newLWTheme;
+ AddonManager.removeAddonListener(listener);
+ },
+
+ _postInstallNotification: function (newTheme, previousTheme) {
+ function text(id) {
+ return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
+ }
+
+ let buttons = [{
+ label: text("undoButton"),
+ accessKey: text("undoButton.accesskey"),
+ callback: function () {
+ LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
+ LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
+ }
+ }, {
+ label: text("manageButton"),
+ accessKey: text("manageButton.accesskey"),
+ callback: function () {
+ BrowserOpenAddonsMgr("addons://list/theme");
+ }
+ }];
+
+ this._removePreviousNotifications();
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let notificationBar =
+ notificationBox.appendNotification(text("message"),
+ "lwtheme-install-notification", "",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notificationBar.persistence = 1;
+ notificationBar.timeout = Date.now() + 20000; // 20 seconds
+ },
+
+ _removePreviousNotifications: function () {
+ let box = gBrowser.getNotificationBox();
+
+ ["lwtheme-install-request",
+ "lwtheme-install-notification"].forEach(function (value) {
+ let notification = box.getNotificationWithValue(value);
+ if (notification)
+ box.removeNotification(notification);
+ });
+ },
+
+ _preview: function (dataString, baseURI) {
+ if (!this._isAllowed(baseURI))
+ return;
+
+ let data = this._manager.parseTheme(dataString, baseURI);
+ if (!data)
+ return;
+
+ this._resetPreview();
+ gBrowser.tabContainer.addEventListener("TabSelect", this, false);
+ this._manager.previewTheme(data);
+ },
+
+ _resetPreview: function (baseURI) {
+ if (baseURI && !this._isAllowed(baseURI))
+ return;
+ gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
+ this._manager.resetPreview();
+ },
+
+ _isAllowed: function (srcURIString) {
+ let uri;
+ try {
+ uri = makeURI(srcURIString);
+ }
+ catch (e) {
+ // makeURI fails if srcURIString is a nonsense URI
+ return false;
+ }
+
+ if (!uri.schemeIs("https")) {
+ return false;
+ }
+
+ let pm = Services.perms;
+ return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
+ }
+};
+
+/*
+ * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
+ */
+var LightweightThemeListener = {
+ _modifiedStyles: [],
+
+ init: function () {
+ XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
+ for (let i = document.styleSheets.length - 1; i >= 0; i--) {
+ let sheet = document.styleSheets[i];
+ if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
+ return sheet;
+ }
+ return undefined;
+ });
+
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ Services.obs.addObserver(this, "lightweight-theme-optimized", false);
+ if (document.documentElement.hasAttribute("lwtheme"))
+ this.updateStyleSheet(document.documentElement.style.backgroundImage);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ Services.obs.removeObserver(this, "lightweight-theme-optimized");
+ },
+
+ /**
+ * Append the headerImage to the background-image property of all rulesets in
+ * browser-lightweightTheme.css.
+ *
+ * @param headerImage - a string containing a CSS image for the lightweight theme header.
+ */
+ updateStyleSheet: function(headerImage) {
+ if (!this.styleSheet)
+ return;
+ this.substituteRules(this.styleSheet.cssRules, headerImage);
+ },
+
+ substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) {
+ let styleRulesModified = 0;
+ for (let i = 0; i < ruleList.length; i++) {
+ let rule = ruleList[i];
+ if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
+ // Add the number of modified sub-rules to the modified count
+ styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
+ } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
+ if (!rule.style.backgroundImage)
+ continue;
+ let modifiedIndex = existingStyleRulesModified + styleRulesModified;
+ if (!this._modifiedStyles[modifiedIndex])
+ this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
+
+ rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
+ styleRulesModified++;
+ } else {
+ Cu.reportError("Unsupported rule encountered");
+ }
+ }
+ return styleRulesModified;
+ },
+
+ // nsIObserver
+ observe: function (aSubject, aTopic, aData) {
+ if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
+ !this.styleSheet)
+ return;
+
+ if (aTopic == "lightweight-theme-optimized" && aSubject != window)
+ return;
+
+ let themeData = JSON.parse(aData);
+ if (!themeData)
+ return;
+ this.updateStyleSheet("url(" + themeData.headerURL + ")");
+ },
+};
diff --git a/browser/base/content/browser-captivePortal.js b/browser/base/content/browser-captivePortal.js
new file mode 100644
index 000000000..c2e45c4ed
--- /dev/null
+++ b/browser/base/content/browser-captivePortal.js
@@ -0,0 +1,257 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+XPCOMUtils.defineLazyServiceGetter(this, "cps",
+ "@mozilla.org/network/captive-portal-service;1",
+ "nsICaptivePortalService");
+
+var CaptivePortalWatcher = {
+ /**
+ * This constant is chosen to be large enough for a portal recheck to complete,
+ * and small enough that the delay in opening a tab isn't too noticeable.
+ * Please see comments for _delayedCaptivePortalDetected for more details.
+ */
+ PORTAL_RECHECK_DELAY_MS: Preferences.get("captivedetect.portalRecheckDelayMS", 500),
+
+ // This is the value used to identify the captive portal notification.
+ PORTAL_NOTIFICATION_VALUE: "captive-portal-detected",
+
+ // This holds a weak reference to the captive portal tab so that we
+ // don't leak it if the user closes it.
+ _captivePortalTab: null,
+
+ /**
+ * If a portal is detected when we don't have focus, we first wait for focus
+ * and then add the tab if, after a recheck, the portal is still active. This
+ * is set to true while we wait so that in the unlikely event that we receive
+ * another notification while waiting, we don't do things twice.
+ */
+ _delayedCaptivePortalDetectedInProgress: false,
+
+ // In the situation above, this is set to true while we wait for the recheck.
+ // This flag exists so that tests can appropriately simulate a recheck.
+ _waitingForRecheck: false,
+
+ get _captivePortalNotification() {
+ let nb = document.getElementById("high-priority-global-notificationbox");
+ return nb.getNotificationWithValue(this.PORTAL_NOTIFICATION_VALUE);
+ },
+
+ get canonicalURL() {
+ return Services.prefs.getCharPref("captivedetect.canonicalURL");
+ },
+
+ get _browserBundle() {
+ delete this._browserBundle;
+ return this._browserBundle =
+ Services.strings.createBundle("chrome://browser/locale/browser.properties");
+ },
+
+ init() {
+ Services.obs.addObserver(this, "captive-portal-login", false);
+ Services.obs.addObserver(this, "captive-portal-login-abort", false);
+ Services.obs.addObserver(this, "captive-portal-login-success", false);
+
+ if (cps.state == cps.LOCKED_PORTAL) {
+ // A captive portal has already been detected.
+ this._captivePortalDetected();
+
+ // Automatically open a captive portal tab if there's no other browser window.
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ if (windows.getNext() == window && !windows.hasMoreElements()) {
+ this.ensureCaptivePortalTab();
+ }
+ }
+
+ cps.recheckCaptivePortal();
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, "captive-portal-login");
+ Services.obs.removeObserver(this, "captive-portal-login-abort");
+ Services.obs.removeObserver(this, "captive-portal-login-success");
+
+
+ if (this._delayedCaptivePortalDetectedInProgress) {
+ Services.obs.removeObserver(this, "xul-window-visible");
+ }
+ },
+
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "captive-portal-login":
+ this._captivePortalDetected();
+ break;
+ case "captive-portal-login-abort":
+ case "captive-portal-login-success":
+ this._captivePortalGone();
+ break;
+ case "xul-window-visible":
+ this._delayedCaptivePortalDetected();
+ break;
+ }
+ },
+
+ _captivePortalDetected() {
+ if (this._delayedCaptivePortalDetectedInProgress) {
+ return;
+ }
+
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ // If no browser window has focus, open and show the tab when we regain focus.
+ // This is so that if a different application was focused, when the user
+ // (re-)focuses a browser window, we open the tab immediately in that window
+ // so they can log in before continuing to browse.
+ if (win != Services.ww.activeWindow) {
+ this._delayedCaptivePortalDetectedInProgress = true;
+ Services.obs.addObserver(this, "xul-window-visible", false);
+ }
+
+ this._showNotification();
+ },
+
+ /**
+ * Called after we regain focus if we detect a portal while a browser window
+ * doesn't have focus. Triggers a portal recheck to reaffirm state, and adds
+ * the tab if needed after a short delay to allow the recheck to complete.
+ */
+ _delayedCaptivePortalDetected() {
+ if (!this._delayedCaptivePortalDetectedInProgress) {
+ return;
+ }
+
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (win != Services.ww.activeWindow) {
+ // The window that got focused was not a browser window.
+ return;
+ }
+ Services.obs.removeObserver(this, "xul-window-visible");
+ this._delayedCaptivePortalDetectedInProgress = false;
+
+ if (win != window) {
+ // Some other browser window got focus, we don't have to do anything.
+ return;
+ }
+ // Trigger a portal recheck. The user may have logged into the portal via
+ // another client, or changed networks.
+ cps.recheckCaptivePortal();
+ this._waitingForRecheck = true;
+ let requestTime = Date.now();
+
+ let self = this;
+ Services.obs.addObserver(function observer() {
+ let time = Date.now() - requestTime;
+ Services.obs.removeObserver(observer, "captive-portal-check-complete");
+ self._waitingForRecheck = false;
+ if (cps.state != cps.LOCKED_PORTAL) {
+ // We're free of the portal!
+ return;
+ }
+
+ if (time <= self.PORTAL_RECHECK_DELAY_MS) {
+ // The amount of time elapsed since we requested a recheck (i.e. since
+ // the browser window was focused) was small enough that we can add and
+ // focus a tab with the login page with no noticeable delay.
+ self.ensureCaptivePortalTab();
+ }
+ }, "captive-portal-check-complete", false);
+ },
+
+ _captivePortalGone() {
+ if (this._delayedCaptivePortalDetectedInProgress) {
+ Services.obs.removeObserver(this, "xul-window-visible");
+ this._delayedCaptivePortalDetectedInProgress = false;
+ }
+
+ this._removeNotification();
+ },
+
+ handleEvent(aEvent) {
+ if (aEvent.type != "TabSelect" || !this._captivePortalTab || !this._captivePortalNotification) {
+ return;
+ }
+
+ let tab = this._captivePortalTab.get();
+ let n = this._captivePortalNotification;
+ if (!tab || !n) {
+ return;
+ }
+
+ let doc = tab.ownerDocument;
+ let button = n.querySelector("button.notification-button");
+ if (doc.defaultView.gBrowser.selectedTab == tab) {
+ button.style.visibility = "hidden";
+ } else {
+ button.style.visibility = "visible";
+ }
+ },
+
+ _showNotification() {
+ let buttons = [
+ {
+ label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage"),
+ callback: () => {
+ this.ensureCaptivePortalTab();
+
+ // Returning true prevents the notification from closing.
+ return true;
+ },
+ isDefault: true,
+ },
+ ];
+
+ let message = this._browserBundle.GetStringFromName("captivePortal.infoMessage2");
+
+ let closeHandler = (aEventName) => {
+ if (aEventName != "removed") {
+ return;
+ }
+ gBrowser.tabContainer.removeEventListener("TabSelect", this);
+ };
+
+ let nb = document.getElementById("high-priority-global-notificationbox");
+ nb.appendNotification(message, this.PORTAL_NOTIFICATION_VALUE, "",
+ nb.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
+
+ gBrowser.tabContainer.addEventListener("TabSelect", this);
+ },
+
+ _removeNotification() {
+ let n = this._captivePortalNotification;
+ if (!n || !n.parentNode) {
+ return;
+ }
+ n.close();
+ },
+
+ ensureCaptivePortalTab() {
+ let tab;
+ if (this._captivePortalTab) {
+ tab = this._captivePortalTab.get();
+ }
+
+ // If the tab is gone or going, we need to open a new one.
+ if (!tab || tab.closing || !tab.parentNode) {
+ tab = gBrowser.addTab(this.canonicalURL, { ownerTab: gBrowser.selectedTab });
+ this._captivePortalTab = Cu.getWeakReference(tab);
+ }
+
+ gBrowser.selectedTab = tab;
+
+ let canonicalURI = makeURI(this.canonicalURL);
+
+ // When we are no longer captive, close the tab if it's at the canonical URL.
+ let tabCloser = () => {
+ Services.obs.removeObserver(tabCloser, "captive-portal-login-abort");
+ Services.obs.removeObserver(tabCloser, "captive-portal-login-success");
+ if (!tab || tab.closing || !tab.parentNode || !tab.linkedBrowser ||
+ !tab.linkedBrowser.currentURI.equalsExceptRef(canonicalURI)) {
+ return;
+ }
+ gBrowser.removeTab(tab);
+ }
+ Services.obs.addObserver(tabCloser, "captive-portal-login-abort", false);
+ Services.obs.addObserver(tabCloser, "captive-portal-login-success", false);
+ },
+};
diff --git a/browser/base/content/browser-charsetmenu.inc b/browser/base/content/browser-charsetmenu.inc
new file mode 100644
index 000000000..806b1cf03
--- /dev/null
+++ b/browser/base/content/browser-charsetmenu.inc
@@ -0,0 +1,12 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<menu id="charsetMenu"
+ label="&charsetMenu2.label;"
+ accesskey="&charsetMenu2.accesskey;"
+ oncommand="BrowserSetForcedCharacterSet(event.target.getAttribute('charset'));"
+ onpopupshowing="CharsetMenu.build(event.target); UpdateCurrentCharset(this);">
+ <menupopup>
+ </menupopup>
+</menu>
diff --git a/browser/base/content/browser-context.inc b/browser/base/content/browser-context.inc
new file mode 100644
index 000000000..51b14d152
--- /dev/null
+++ b/browser/base/content/browser-context.inc
@@ -0,0 +1,472 @@
+# -*- 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/.
+
+# NB: IF YOU ADD ITEMS TO THIS FILE, PLEASE UPDATE THE WHITELIST IN
+# BrowserUITelemetry.jsm. SEE BUG 991757 FOR DETAILS.
+
+ <menugroup id="context-navigation">
+ <menuitem id="context-back"
+ class="menuitem-iconic"
+ tooltiptext="&backButton.tooltip;"
+ aria-label="&backCmd.label;"
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-forward"
+ class="menuitem-iconic"
+ tooltiptext="&forwardButton.tooltip;"
+ aria-label="&forwardCmd.label;"
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-reload"
+ class="menuitem-iconic"
+ tooltiptext="&reloadButton.tooltip;"
+ aria-label="&reloadCmd.label;"
+ oncommand="gContextMenu.reload(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-stop"
+ class="menuitem-iconic"
+ tooltiptext="&stopButton.tooltip;"
+ aria-label="&stopCmd.label;"
+ command="Browser:Stop"/>
+ <menuitem id="context-bookmarkpage"
+ class="menuitem-iconic"
+ observes="bookmarkThisPageBroadcaster"
+ aria-label="&bookmarkPageCmd2.label;"
+ oncommand="gContextMenu.bookmarkThisPage();"/>
+ </menugroup>
+ <menuseparator id="context-sep-navigation"/>
+ <menuseparator id="page-menu-separator"/>
+ <menuitem id="spell-no-suggestions"
+ disabled="true"
+ label="&spellNoSuggestions.label;"/>
+ <menuitem id="spell-add-to-dictionary"
+ label="&spellAddToDictionary.label;"
+ accesskey="&spellAddToDictionary.accesskey;"
+ oncommand="InlineSpellCheckerUI.addToDictionary();"/>
+ <menuitem id="spell-undo-add-to-dictionary"
+ label="&spellUndoAddToDictionary.label;"
+ accesskey="&spellUndoAddToDictionary.accesskey;"
+ oncommand="InlineSpellCheckerUI.undoAddToDictionary();" />
+ <menuseparator id="spell-suggestions-separator"/>
+ <menuitem id="context-openlinkincurrent"
+ label="&openLinkCmdInCurrent.label;"
+ accesskey="&openLinkCmdInCurrent.accesskey;"
+ oncommand="gContextMenu.openLinkInCurrent();"/>
+# label and data-usercontextid are dynamically set.
+ <menuitem id="context-openlinkincontainertab"
+ accesskey="&openLinkCmdInTab.accesskey;"
+ oncommand="gContextMenu.openLinkInTab(event);"/>
+ <menuitem id="context-openlinkintab"
+ label="&openLinkCmdInTab.label;"
+ accesskey="&openLinkCmdInTab.accesskey;"
+ data-usercontextid="0"
+ oncommand="gContextMenu.openLinkInTab(event);"/>
+
+ <menu id="context-openlinkinusercontext-menu"
+ label="&openLinkCmdInContainerTab.label;"
+ accesskey="&openLinkCmdInContainerTab.accesskey;"
+ hidden="true">
+ <menupopup oncommand="gContextMenu.openLinkInTab(event);"
+ onpopupshowing="return gContextMenu.createContainerMenu(event);" />
+ </menu>
+
+ <menuitem id="context-openlink"
+ label="&openLinkCmd.label;"
+ accesskey="&openLinkCmd.accesskey;"
+ oncommand="gContextMenu.openLink();"/>
+ <menuitem id="context-openlinkprivate"
+ label="&openLinkInPrivateWindowCmd.label;"
+ accesskey="&openLinkInPrivateWindowCmd.accesskey;"
+ oncommand="gContextMenu.openLinkInPrivateWindow();"/>
+ <menuseparator id="context-sep-open"/>
+ <menuitem id="context-bookmarklink"
+ label="&bookmarkThisLinkCmd.label;"
+ accesskey="&bookmarkThisLinkCmd.accesskey;"
+ oncommand="gContextMenu.bookmarkLink();"/>
+ <menuitem id="context-sharelink"
+ label="&shareLink.label;"
+ accesskey="&shareLink.accesskey;"
+ oncommand="gContextMenu.shareLink();"/>
+ <menuitem id="context-savelink"
+ label="&saveLinkCmd.label;"
+ accesskey="&saveLinkCmd.accesskey;"
+ oncommand="gContextMenu.saveLink();"/>
+ <menuitem id="context-copyemail"
+ label="&copyEmailCmd.label;"
+ accesskey="&copyEmailCmd.accesskey;"
+ oncommand="gContextMenu.copyEmail();"/>
+ <menuitem id="context-copylink"
+ label="&copyLinkCmd.label;"
+ accesskey="&copyLinkCmd.accesskey;"
+ oncommand="gContextMenu.copyLink();"/>
+ <menuseparator id="context-sep-copylink"/>
+ <menuitem id="context-media-play"
+ label="&mediaPlay.label;"
+ accesskey="&mediaPlay.accesskey;"
+ oncommand="gContextMenu.mediaCommand('play');"/>
+ <menuitem id="context-media-pause"
+ label="&mediaPause.label;"
+ accesskey="&mediaPause.accesskey;"
+ oncommand="gContextMenu.mediaCommand('pause');"/>
+ <menuitem id="context-media-mute"
+ label="&mediaMute.label;"
+ accesskey="&mediaMute.accesskey;"
+ oncommand="gContextMenu.mediaCommand('mute');"/>
+ <menuitem id="context-media-unmute"
+ label="&mediaUnmute.label;"
+ accesskey="&mediaUnmute.accesskey;"
+ oncommand="gContextMenu.mediaCommand('unmute');"/>
+ <menu id="context-media-playbackrate" label="&mediaPlaybackRate2.label;" accesskey="&mediaPlaybackRate2.accesskey;">
+ <menupopup>
+ <menuitem id="context-media-playbackrate-050x"
+ label="&mediaPlaybackRate050x2.label;"
+ accesskey="&mediaPlaybackRate050x2.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 0.5);"/>
+ <menuitem id="context-media-playbackrate-100x"
+ label="&mediaPlaybackRate100x2.label;"
+ accesskey="&mediaPlaybackRate100x2.accesskey;"
+ type="radio"
+ name="playbackrate"
+ checked="true"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 1.0);"/>
+ <menuitem id="context-media-playbackrate-125x"
+ label="&mediaPlaybackRate125x2.label;"
+ accesskey="&mediaPlaybackRate125x2.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 1.25);"/>
+ <menuitem id="context-media-playbackrate-150x"
+ label="&mediaPlaybackRate150x2.label;"
+ accesskey="&mediaPlaybackRate150x2.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 1.5);"/>
+ <menuitem id="context-media-playbackrate-200x"
+ label="&mediaPlaybackRate200x2.label;"
+ accesskey="&mediaPlaybackRate200x2.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 2.0);"/>
+ </menupopup>
+ </menu>
+ <menuitem id="context-media-loop"
+ label="&mediaLoop.label;"
+ accesskey="&mediaLoop.accesskey;"
+ type="checkbox"
+ oncommand="gContextMenu.mediaCommand('loop');"/>
+ <menuitem id="context-media-showcontrols"
+ label="&mediaShowControls.label;"
+ accesskey="&mediaShowControls.accesskey;"
+ oncommand="gContextMenu.mediaCommand('showcontrols');"/>
+ <menuitem id="context-media-hidecontrols"
+ label="&mediaHideControls.label;"
+ accesskey="&mediaHideControls.accesskey;"
+ oncommand="gContextMenu.mediaCommand('hidecontrols');"/>
+ <menuitem id="context-video-fullscreen"
+ accesskey="&videoFullScreen.accesskey;"
+ label="&videoFullScreen.label;"
+ oncommand="gContextMenu.mediaCommand('fullscreen');"/>
+ <menuitem id="context-leave-dom-fullscreen"
+ accesskey="&leaveDOMFullScreen.accesskey;"
+ label="&leaveDOMFullScreen.label;"
+ oncommand="gContextMenu.leaveDOMFullScreen();"/>
+ <menuseparator id="context-media-sep-commands"/>
+ <menuitem id="context-reloadimage"
+ label="&reloadImageCmd.label;"
+ accesskey="&reloadImageCmd.accesskey;"
+ oncommand="gContextMenu.reloadImage();"/>
+ <menuitem id="context-viewimage"
+ label="&viewImageCmd.label;"
+ accesskey="&viewImageCmd.accesskey;"
+ oncommand="gContextMenu.viewMedia(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-viewvideo"
+ label="&viewVideoCmd.label;"
+ accesskey="&viewVideoCmd.accesskey;"
+ oncommand="gContextMenu.viewMedia(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+#ifdef CONTEXT_COPY_IMAGE_CONTENTS
+ <menuitem id="context-copyimage-contents"
+ label="&copyImageContentsCmd.label;"
+ accesskey="&copyImageContentsCmd.accesskey;"
+ oncommand="goDoCommand('cmd_copyImage');"/>
+#endif
+ <menuitem id="context-copyimage"
+ label="&copyImageCmd.label;"
+ accesskey="&copyImageCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuitem id="context-copyvideourl"
+ label="&copyVideoURLCmd.label;"
+ accesskey="&copyVideoURLCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuitem id="context-copyaudiourl"
+ label="&copyAudioURLCmd.label;"
+ accesskey="&copyAudioURLCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuseparator id="context-sep-copyimage"/>
+ <menuitem id="context-saveimage"
+ label="&saveImageCmd.label;"
+ accesskey="&saveImageCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-shareimage"
+ label="&shareImage.label;"
+ accesskey="&shareImage.accesskey;"
+ oncommand="gContextMenu.shareImage();"/>
+ <menuitem id="context-sendimage"
+ label="&emailImageCmd.label;"
+ accesskey="&emailImageCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-setDesktopBackground"
+ label="&setDesktopBackgroundCmd.label;"
+ accesskey="&setDesktopBackgroundCmd.accesskey;"
+ oncommand="gContextMenu.setDesktopBackground();"/>
+ <menuitem id="context-viewimageinfo"
+ label="&viewImageInfoCmd.label;"
+ accesskey="&viewImageInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewImageInfo();"/>
+ <menuitem id="context-viewimagedesc"
+ label="&viewImageDescCmd.label;"
+ accesskey="&viewImageDescCmd.accesskey;"
+ oncommand="gContextMenu.viewImageDesc(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-savevideo"
+ label="&saveVideoCmd.label;"
+ accesskey="&saveVideoCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-sharevideo"
+ label="&shareVideo.label;"
+ accesskey="&shareVideo.accesskey;"
+ oncommand="gContextMenu.shareVideo();"/>
+ <menuitem id="context-saveaudio"
+ label="&saveAudioCmd.label;"
+ accesskey="&saveAudioCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-video-saveimage"
+ accesskey="&videoSaveImage.accesskey;"
+ label="&videoSaveImage.label;"
+ oncommand="gContextMenu.saveVideoFrameAsImage();"/>
+ <menuitem id="context-sendvideo"
+ label="&emailVideoCmd.label;"
+ accesskey="&emailVideoCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menu id="context-castvideo"
+ label="&castVideoCmd.label;"
+ accesskey="&castVideoCmd.accesskey;">
+ <menupopup id="context-castvideo-popup" onpopupshowing="gContextMenu.populateCastVideoMenu(this)"/>
+ </menu>
+ <menuitem id="context-sendaudio"
+ label="&emailAudioCmd.label;"
+ accesskey="&emailAudioCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-ctp-play"
+ label="&playPluginCmd.label;"
+ accesskey="&playPluginCmd.accesskey;"
+ oncommand="gContextMenu.playPlugin();"/>
+ <menuitem id="context-ctp-hide"
+ label="&hidePluginCmd.label;"
+ accesskey="&hidePluginCmd.accesskey;"
+ oncommand="gContextMenu.hidePlugin();"/>
+ <menuseparator id="context-sep-ctp"/>
+ <menuitem id="context-sharepage"
+ label="&sharePageCmd.label;"
+ accesskey="&sharePageCmd.accesskey;"
+ oncommand="SocialShare.sharePage();"/>
+ <menuitem id="context-savepage"
+ label="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey2;"
+ oncommand="gContextMenu.savePageAs();"/>
+ <menuseparator id="context-sep-sendpagetodevice" hidden="true"/>
+ <menu id="context-sendpagetodevice"
+ label="&sendPageToDevice.label;"
+ accesskey="&sendPageToDevice.accesskey;"
+ hidden="true">
+ <menupopup id="context-sendpagetodevice-popup"
+ onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gFxAccounts.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
+ </menu>
+ <menuseparator id="context-sep-viewbgimage"/>
+ <menuitem id="context-viewbgimage"
+ label="&viewBGImageCmd.label;"
+ accesskey="&viewBGImageCmd.accesskey;"
+ oncommand="gContextMenu.viewBGImage(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-undo"
+ label="&undoCmd.label;"
+ accesskey="&undoCmd.accesskey;"
+ command="cmd_undo"/>
+ <menuseparator id="context-sep-undo"/>
+ <menuitem id="context-cut"
+ label="&cutCmd.label;"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="context-copy"
+ label="&copyCmd.label;"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="context-paste"
+ label="&pasteCmd.label;"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuitem id="context-delete"
+ label="&deleteCmd.label;"
+ accesskey="&deleteCmd.accesskey;"
+ command="cmd_delete"/>
+ <menuseparator id="context-sep-paste"/>
+ <menuitem id="context-selectall"
+ label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator id="context-sep-selectall"/>
+ <menuitem id="context-keywordfield"
+ label="&keywordfield.label;"
+ accesskey="&keywordfield.accesskey;"
+ oncommand="AddKeywordForSearchField();"/>
+ <menuitem id="context-searchselect"
+ oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
+ <menuseparator id="context-sep-sendlinktodevice" hidden="true"/>
+ <menu id="context-sendlinktodevice"
+ label="&sendLinkToDevice.label;"
+ accesskey="&sendLinkToDevice.accesskey;"
+ hidden="true">
+ <menupopup id="context-sendlinktodevice-popup"
+ onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
+ </menu>
+ <menuitem id="context-shareselect"
+ label="&shareSelect.label;"
+ accesskey="&shareSelect.accesskey;"
+ oncommand="gContextMenu.shareSelect();"/>
+ <menuseparator id="frame-sep"/>
+ <menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
+ <menupopup>
+ <menuitem id="context-showonlythisframe"
+ label="&showOnlyThisFrameCmd.label;"
+ accesskey="&showOnlyThisFrameCmd.accesskey;"
+ oncommand="gContextMenu.showOnlyThisFrame();"/>
+ <menuitem id="context-openframeintab"
+ label="&openFrameCmdInTab.label;"
+ accesskey="&openFrameCmdInTab.accesskey;"
+ oncommand="gContextMenu.openFrameInTab();"/>
+ <menuitem id="context-openframe"
+ label="&openFrameCmd.label;"
+ accesskey="&openFrameCmd.accesskey;"
+ oncommand="gContextMenu.openFrame();"/>
+ <menuseparator id="open-frame-sep"/>
+ <menuitem id="context-reloadframe"
+ label="&reloadFrameCmd.label;"
+ accesskey="&reloadFrameCmd.accesskey;"
+ oncommand="gContextMenu.reloadFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-bookmarkframe"
+ label="&bookmarkThisFrameCmd.label;"
+ accesskey="&bookmarkThisFrameCmd.accesskey;"
+ oncommand="gContextMenu.addBookmarkForFrame();"/>
+ <menuitem id="context-saveframe"
+ label="&saveFrameCmd.label;"
+ accesskey="&saveFrameCmd.accesskey;"
+ oncommand="gContextMenu.saveFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-printframe"
+ label="&printFrameCmd.label;"
+ accesskey="&printFrameCmd.accesskey;"
+ oncommand="gContextMenu.printFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-viewframesource"
+ label="&viewFrameSourceCmd.label;"
+ accesskey="&viewFrameSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewFrameSource();"
+ observes="isFrameImage"/>
+ <menuitem id="context-viewframeinfo"
+ label="&viewFrameInfoCmd.label;"
+ accesskey="&viewFrameInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewFrameInfo();"/>
+ </menupopup>
+ </menu>
+ <menuitem id="context-viewpartialsource-selection"
+ label="&viewPartialSourceForSelectionCmd.label;"
+ accesskey="&viewPartialSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewPartialSource('selection');"
+ observes="isImage"/>
+ <menuitem id="context-viewpartialsource-mathml"
+ label="&viewPartialSourceForMathMLCmd.label;"
+ accesskey="&viewPartialSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewPartialSource('mathml');"
+ observes="isImage"/>
+ <menuseparator id="context-sep-viewsource"/>
+ <menuitem id="context-viewsource"
+ label="&viewPageSourceCmd.label;"
+ accesskey="&viewPageSourceCmd.accesskey;"
+ oncommand="BrowserViewSource(gContextMenu.browser);"
+ observes="canViewSource"/>
+ <menuitem id="context-viewinfo"
+ label="&viewPageInfoCmd.label;"
+ accesskey="&viewPageInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewInfo();"/>
+ <menuseparator id="spell-separator"/>
+ <menuitem id="spell-check-enabled"
+ label="&spellCheckToggle.label;"
+ type="checkbox"
+ accesskey="&spellCheckToggle.accesskey;"
+ oncommand="InlineSpellCheckerUI.toggleEnabled(window);"/>
+ <menuitem id="spell-add-dictionaries-main"
+ label="&spellAddDictionaries.label;"
+ accesskey="&spellAddDictionaries.accesskey;"
+ oncommand="gContextMenu.addDictionaries();"/>
+ <menu id="spell-dictionaries"
+ label="&spellDictionaries.label;"
+ accesskey="&spellDictionaries.accesskey;">
+ <menupopup id="spell-dictionaries-menu">
+ <menuseparator id="spell-language-separator"/>
+ <menuitem id="spell-add-dictionaries"
+ label="&spellAddDictionaries.label;"
+ accesskey="&spellAddDictionaries.accesskey;"
+ oncommand="gContextMenu.addDictionaries();"/>
+ </menupopup>
+ </menu>
+ <menuseparator hidden="true" id="context-sep-bidi"/>
+ <menuitem hidden="true" id="context-bidi-text-direction-toggle"
+ label="&bidiSwitchTextDirectionItem.label;"
+ accesskey="&bidiSwitchTextDirectionItem.accesskey;"
+ command="cmd_switchTextDirection"/>
+ <menuitem hidden="true" id="context-bidi-page-direction-toggle"
+ label="&bidiSwitchPageDirectionItem.label;"
+ accesskey="&bidiSwitchPageDirectionItem.accesskey;"
+ oncommand="gContextMenu.switchPageDirection();"/>
+ <menuseparator id="fill-login-separator" hidden="true"/>
+ <menu id="fill-login"
+ label="&fillLoginMenu.label;"
+ label-login="&fillLoginMenu.label;"
+ label-password="&fillPasswordMenu.label;"
+ label-username="&fillUsernameMenu.label;"
+ accesskey="&fillLoginMenu.accesskey;"
+ accesskey-login="&fillLoginMenu.accesskey;"
+ accesskey-password="&fillPasswordMenu.accesskey;"
+ accesskey-username="&fillUsernameMenu.accesskey;"
+ hidden="true">
+ <menupopup id="fill-login-popup">
+ <menuitem id="fill-login-no-logins"
+ label="&noLoginSuggestions.label;"
+ disabled="true"
+ hidden="true"/>
+ <menuseparator id="saved-logins-separator"/>
+ <menuitem id="fill-login-saved-passwords"
+ label="&viewSavedLogins.label;"
+ oncommand="gContextMenu.openPasswordManager();"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="inspect-separator" hidden="true"/>
+ <menuitem id="context-inspect"
+ hidden="true"
+ label="&inspectContextMenu.label;"
+ accesskey="&inspectContextMenu.accesskey;"
+ oncommand="gContextMenu.inspectNode();"/>
+ <menuseparator id="context-media-eme-separator" hidden="true"/>
+ <menuitem id="context-media-eme-learnmore"
+ class="menuitem-iconic"
+ hidden="true"
+ label="&emeLearnMoreContextMenu.label;"
+ accesskey="&emeLearnMoreContextMenu.accesskey;"
+ oncommand="gContextMenu.drmLearnMore(event);"
+ onclick="checkForMiddleClick(this, event);"/>
diff --git a/browser/base/content/browser-ctrlTab.js b/browser/base/content/browser-ctrlTab.js
new file mode 100644
index 000000000..c761ea095
--- /dev/null
+++ b/browser/base/content/browser-ctrlTab.js
@@ -0,0 +1,587 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 previews utility, produces thumbnails
+ */
+var tabPreviews = {
+ init: function tabPreviews_init() {
+ if (this._selectedTab)
+ return;
+ this._selectedTab = gBrowser.selectedTab;
+
+ gBrowser.tabContainer.addEventListener("TabSelect", this, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
+
+ let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
+ .getService(Ci.nsIScreenManager);
+ let left = {}, top = {}, width = {}, height = {};
+ screenManager.primaryScreen.GetRectDisplayPix(left, top, width, height);
+ this.aspectRatio = height.value / width.value;
+ },
+
+ get: function tabPreviews_get(aTab) {
+ let uri = aTab.linkedBrowser.currentURI.spec;
+
+ if (aTab.__thumbnail_lastURI &&
+ aTab.__thumbnail_lastURI != uri) {
+ aTab.__thumbnail = null;
+ aTab.__thumbnail_lastURI = null;
+ }
+
+ if (aTab.__thumbnail)
+ return aTab.__thumbnail;
+
+ if (aTab.getAttribute("pending") == "true") {
+ let img = new Image;
+ img.src = PageThumbs.getThumbnailURL(uri);
+ return img;
+ }
+
+ return this.capture(aTab, !aTab.hasAttribute("busy"));
+ },
+
+ capture: function tabPreviews_capture(aTab, aShouldCache) {
+ let browser = aTab.linkedBrowser;
+ let uri = browser.currentURI.spec;
+ let canvas = PageThumbs.createCanvas(window);
+ PageThumbs.shouldStoreThumbnail(browser, (aDoStore) => {
+ if (aDoStore && aShouldCache) {
+ PageThumbs.captureAndStore(browser, function () {
+ let img = new Image;
+ img.src = PageThumbs.getThumbnailURL(uri);
+ aTab.__thumbnail = img;
+ aTab.__thumbnail_lastURI = uri;
+ canvas.getContext("2d").drawImage(img, 0, 0);
+ });
+ } else {
+ PageThumbs.captureToCanvas(browser, canvas, () => {
+ if (aShouldCache) {
+ aTab.__thumbnail = canvas;
+ aTab.__thumbnail_lastURI = uri;
+ }
+ });
+ }
+ });
+ return canvas;
+ },
+
+ handleEvent: function tabPreviews_handleEvent(event) {
+ switch (event.type) {
+ case "TabSelect":
+ if (this._selectedTab &&
+ this._selectedTab.parentNode &&
+ !this._pendingUpdate) {
+ // Generate a thumbnail for the tab that was selected.
+ // The timeout keeps the UI snappy and prevents us from generating thumbnails
+ // for tabs that will be closed. During that timeout, don't generate other
+ // thumbnails in case multiple TabSelect events occur fast in succession.
+ this._pendingUpdate = true;
+ setTimeout(function (self, aTab) {
+ self._pendingUpdate = false;
+ if (aTab.parentNode &&
+ !aTab.hasAttribute("busy") &&
+ !aTab.hasAttribute("pending"))
+ self.capture(aTab, true);
+ }, 2000, this, this._selectedTab);
+ }
+ this._selectedTab = event.target;
+ break;
+ case "SSTabRestored":
+ this.capture(event.target, true);
+ break;
+ }
+ }
+};
+
+var tabPreviewPanelHelper = {
+ opening: function (host) {
+ host.panel.hidden = false;
+
+ var handler = this._generateHandler(host);
+ host.panel.addEventListener("popupshown", handler, false);
+ host.panel.addEventListener("popuphiding", handler, false);
+
+ host._prevFocus = document.commandDispatcher.focusedElement;
+ },
+ _generateHandler: function (host) {
+ var self = this;
+ return function (event) {
+ if (event.target == host.panel) {
+ host.panel.removeEventListener(event.type, arguments.callee, false);
+ self["_" + event.type](host);
+ }
+ };
+ },
+ _popupshown: function (host) {
+ if ("setupGUI" in host)
+ host.setupGUI();
+ },
+ _popuphiding: function (host) {
+ if ("suspendGUI" in host)
+ host.suspendGUI();
+
+ if (host._prevFocus) {
+ Services.focus.setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
+ host._prevFocus = null;
+ } else
+ gBrowser.selectedBrowser.focus();
+
+ if (host.tabToSelect) {
+ gBrowser.selectedTab = host.tabToSelect;
+ host.tabToSelect = null;
+ }
+ }
+};
+
+/**
+ * Ctrl-Tab panel
+ */
+var ctrlTab = {
+ get panel () {
+ delete this.panel;
+ return this.panel = document.getElementById("ctrlTab-panel");
+ },
+ get showAllButton () {
+ delete this.showAllButton;
+ return this.showAllButton = document.getElementById("ctrlTab-showAll");
+ },
+ get previews () {
+ delete this.previews;
+ return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
+ },
+ get maxTabPreviews () {
+ delete this.maxTabPreviews;
+ return this.maxTabPreviews = this.previews.length - 1;
+ },
+ get canvasWidth () {
+ delete this.canvasWidth;
+ return this.canvasWidth = Math.ceil(screen.availWidth * .85 / this.maxTabPreviews);
+ },
+ get canvasHeight () {
+ delete this.canvasHeight;
+ return this.canvasHeight = Math.round(this.canvasWidth * tabPreviews.aspectRatio);
+ },
+ get keys () {
+ var keys = {};
+ ["close", "find", "selectAll"].forEach(function (key) {
+ keys[key] = document.getElementById("key_" + key)
+ .getAttribute("key")
+ .toLocaleLowerCase().charCodeAt(0);
+ });
+ delete this.keys;
+ return this.keys = keys;
+ },
+ _selectedIndex: 0,
+ get selected () {
+ return this._selectedIndex < 0 ?
+ document.activeElement :
+ this.previews.item(this._selectedIndex);
+ },
+ get isOpen () {
+ return this.panel.state == "open" || this.panel.state == "showing" || this._timer;
+ },
+ get tabCount () {
+ return this.tabList.length;
+ },
+ get tabPreviewCount () {
+ return Math.min(this.maxTabPreviews, this.tabCount);
+ },
+
+ get tabList () {
+ return this._recentlyUsedTabs;
+ },
+
+ init: function ctrlTab_init() {
+ if (!this._recentlyUsedTabs) {
+ tabPreviews.init();
+
+ this._initRecentlyUsedTabs();
+ this._init(true);
+ }
+ },
+
+ uninit: function ctrlTab_uninit() {
+ this._recentlyUsedTabs = null;
+ this._init(false);
+ },
+
+ prefName: "browser.ctrlTab.previews",
+ readPref: function ctrlTab_readPref() {
+ var enable =
+ gPrefService.getBoolPref(this.prefName) &&
+ (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
+ !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
+
+ if (enable)
+ this.init();
+ else
+ this.uninit();
+ },
+ observe: function (aSubject, aTopic, aPrefName) {
+ this.readPref();
+ },
+
+ updatePreviews: function ctrlTab_updatePreviews() {
+ for (let i = 0; i < this.previews.length; i++)
+ this.updatePreview(this.previews[i], this.tabList[i]);
+
+ var showAllLabel = gNavigatorBundle.getString("ctrlTab.listAllTabs.label");
+ this.showAllButton.label =
+ PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
+ this.showAllButton.hidden = !allTabs.canOpen;
+ },
+
+ updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
+ if (aPreview == this.showAllButton)
+ return;
+
+ aPreview._tab = aTab;
+
+ if (aPreview.firstChild)
+ aPreview.removeChild(aPreview.firstChild);
+ if (aTab) {
+ let canvasWidth = this.canvasWidth;
+ let canvasHeight = this.canvasHeight;
+ aPreview.appendChild(tabPreviews.get(aTab));
+ aPreview.setAttribute("label", aTab.label);
+ aPreview.setAttribute("tooltiptext", aTab.label);
+ aPreview.setAttribute("crop", aTab.crop);
+ aPreview.setAttribute("canvaswidth", canvasWidth);
+ aPreview.setAttribute("canvasstyle",
+ "max-width:" + canvasWidth + "px;" +
+ "min-width:" + canvasWidth + "px;" +
+ "max-height:" + canvasHeight + "px;" +
+ "min-height:" + canvasHeight + "px;");
+ if (aTab.image)
+ aPreview.setAttribute("image", aTab.image);
+ else
+ aPreview.removeAttribute("image");
+ aPreview.hidden = false;
+ } else {
+ aPreview.hidden = true;
+ aPreview.removeAttribute("label");
+ aPreview.removeAttribute("tooltiptext");
+ aPreview.removeAttribute("image");
+ }
+ },
+
+ advanceFocus: function ctrlTab_advanceFocus(aForward) {
+ let selectedIndex = Array.indexOf(this.previews, this.selected);
+ do {
+ selectedIndex += aForward ? 1 : -1;
+ if (selectedIndex < 0)
+ selectedIndex = this.previews.length - 1;
+ else if (selectedIndex >= this.previews.length)
+ selectedIndex = 0;
+ } while (this.previews[selectedIndex].hidden);
+
+ if (this._selectedIndex == -1) {
+ // Focus is already in the panel.
+ this.previews[selectedIndex].focus();
+ } else {
+ this._selectedIndex = selectedIndex;
+ }
+
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ this._openPanel();
+ }
+ },
+
+ _mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
+ if (this._trackMouseOver)
+ aPreview.focus();
+ },
+
+ pick: function ctrlTab_pick(aPreview) {
+ if (!this.tabCount)
+ return;
+
+ var select = (aPreview || this.selected);
+
+ if (select == this.showAllButton)
+ this.showAllTabs();
+ else
+ this.close(select._tab);
+ },
+
+ showAllTabs: function ctrlTab_showAllTabs(aPreview) {
+ this.close();
+ document.getElementById("Browser:ShowAllTabs").doCommand();
+ },
+
+ remove: function ctrlTab_remove(aPreview) {
+ if (aPreview._tab)
+ gBrowser.removeTab(aPreview._tab);
+ },
+
+ attachTab: function ctrlTab_attachTab(aTab, aPos) {
+ if (aTab.closing)
+ return;
+
+ if (aPos == 0)
+ this._recentlyUsedTabs.unshift(aTab);
+ else if (aPos)
+ this._recentlyUsedTabs.splice(aPos, 0, aTab);
+ else
+ this._recentlyUsedTabs.push(aTab);
+ },
+
+ detachTab: function ctrlTab_detachTab(aTab) {
+ var i = this._recentlyUsedTabs.indexOf(aTab);
+ if (i >= 0)
+ this._recentlyUsedTabs.splice(i, 1);
+ },
+
+ open: function ctrlTab_open() {
+ if (this.isOpen)
+ return;
+
+ document.addEventListener("keyup", this, true);
+
+ this.updatePreviews();
+ this._selectedIndex = 1;
+
+ // Add a slight delay before showing the UI, so that a quick
+ // "ctrl-tab" keypress just flips back to the MRU tab.
+ this._timer = setTimeout(function (self) {
+ self._timer = null;
+ self._openPanel();
+ }, 200, this);
+ },
+
+ _openPanel: function ctrlTab_openPanel() {
+ tabPreviewPanelHelper.opening(this);
+
+ this.panel.width = Math.min(screen.availWidth * .99,
+ this.canvasWidth * 1.25 * this.tabPreviewCount);
+ var estimateHeight = this.canvasHeight * 1.25 + 75;
+ this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
+ screen.availTop + (screen.availHeight - estimateHeight) / 2,
+ false);
+ },
+
+ close: function ctrlTab_close(aTabToSelect) {
+ if (!this.isOpen)
+ return;
+
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ this.suspendGUI();
+ if (aTabToSelect)
+ gBrowser.selectedTab = aTabToSelect;
+ return;
+ }
+
+ this.tabToSelect = aTabToSelect;
+ this.panel.hidePopup();
+ },
+
+ setupGUI: function ctrlTab_setupGUI() {
+ this.selected.focus();
+ this._selectedIndex = -1;
+
+ // Track mouse movement after a brief delay so that the item that happens
+ // to be under the mouse pointer initially won't be selected unintentionally.
+ this._trackMouseOver = false;
+ setTimeout(function (self) {
+ if (self.isOpen)
+ self._trackMouseOver = true;
+ }, 0, this);
+ },
+
+ suspendGUI: function ctrlTab_suspendGUI() {
+ document.removeEventListener("keyup", this, true);
+
+ for (let preview of this.previews) {
+ this.updatePreview(preview, null);
+ }
+ },
+
+ onKeyPress: function ctrlTab_onKeyPress(event) {
+ var isOpen = this.isOpen;
+
+ if (isOpen) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ switch (event.keyCode) {
+ case event.DOM_VK_TAB:
+ if (event.ctrlKey && !event.altKey && !event.metaKey) {
+ if (isOpen) {
+ this.advanceFocus(!event.shiftKey);
+ } else if (!event.shiftKey) {
+ event.preventDefault();
+ event.stopPropagation();
+ let tabs = gBrowser.visibleTabs;
+ if (tabs.length > 2) {
+ this.open();
+ } else if (tabs.length == 2) {
+ let index = tabs[0].selected ? 1 : 0;
+ gBrowser.selectedTab = tabs[index];
+ }
+ }
+ }
+ break;
+ default:
+ if (isOpen && event.ctrlKey) {
+ if (event.keyCode == event.DOM_VK_DELETE) {
+ this.remove(this.selected);
+ break;
+ }
+ switch (event.charCode) {
+ case this.keys.close:
+ this.remove(this.selected);
+ break;
+ case this.keys.find:
+ case this.keys.selectAll:
+ this.showAllTabs();
+ break;
+ }
+ }
+ }
+ },
+
+ removeClosingTabFromUI: function ctrlTab_removeClosingTabFromUI(aTab) {
+ if (this.tabCount == 2) {
+ this.close();
+ return;
+ }
+
+ this.updatePreviews();
+
+ if (this.selected.hidden)
+ this.advanceFocus(false);
+ if (this.selected == this.showAllButton)
+ this.advanceFocus(false);
+
+ // If the current tab is removed, another tab can steal our focus.
+ if (aTab.selected && this.panel.state == "open") {
+ setTimeout(function (selected) {
+ selected.focus();
+ }, 0, this.selected);
+ }
+ },
+
+ handleEvent: function ctrlTab_handleEvent(event) {
+ switch (event.type) {
+ case "SSWindowRestored":
+ this._initRecentlyUsedTabs();
+ break;
+ case "TabAttrModified":
+ // tab attribute modified (e.g. label, crop, busy, image, selected)
+ for (let i = this.previews.length - 1; i >= 0; i--) {
+ if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
+ this.updatePreview(this.previews[i], event.target);
+ break;
+ }
+ }
+ break;
+ case "TabSelect":
+ this.detachTab(event.target);
+ this.attachTab(event.target, 0);
+ break;
+ case "TabOpen":
+ this.attachTab(event.target, 1);
+ break;
+ case "TabClose":
+ this.detachTab(event.target);
+ if (this.isOpen)
+ this.removeClosingTabFromUI(event.target);
+ break;
+ case "keypress":
+ this.onKeyPress(event);
+ break;
+ case "keyup":
+ if (event.keyCode == event.DOM_VK_CONTROL)
+ this.pick();
+ break;
+ case "popupshowing":
+ if (event.target.id == "menu_viewPopup")
+ document.getElementById("menu_showAllTabs").hidden = !allTabs.canOpen;
+ break;
+ }
+ },
+
+ filterForThumbnailExpiration: function (aCallback) {
+ // Save a few more thumbnails than we actually display, so that when tabs
+ // are closed, the previews we add instead still get thumbnails.
+ const extraThumbnails = 3;
+ const thumbnailCount = Math.min(this.tabPreviewCount + extraThumbnails,
+ this.tabCount);
+
+ let urls = [];
+ for (let i = 0; i < thumbnailCount; i++)
+ urls.push(this.tabList[i].linkedBrowser.currentURI.spec);
+
+ aCallback(urls);
+ },
+
+ _initRecentlyUsedTabs: function () {
+ this._recentlyUsedTabs =
+ Array.filter(gBrowser.tabs, tab => !tab.closing)
+ .sort((tab1, tab2) => tab2.lastAccessed - tab1.lastAccessed);
+ },
+
+ _init: function ctrlTab__init(enable) {
+ var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
+
+ window[toggleEventListener]("SSWindowRestored", this, false);
+
+ var tabContainer = gBrowser.tabContainer;
+ tabContainer[toggleEventListener]("TabOpen", this, false);
+ tabContainer[toggleEventListener]("TabAttrModified", this, false);
+ tabContainer[toggleEventListener]("TabSelect", this, false);
+ tabContainer[toggleEventListener]("TabClose", this, false);
+
+ document[toggleEventListener]("keypress", this, false);
+ gBrowser.mTabBox.handleCtrlTab = !enable;
+
+ if (enable)
+ PageThumbs.addExpirationFilter(this);
+ else
+ PageThumbs.removeExpirationFilter(this);
+
+ // If we're not running, hide the "Show All Tabs" menu item,
+ // as Shift+Ctrl+Tab will be handled by the tab bar.
+ document.getElementById("menu_showAllTabs").hidden = !enable;
+ document.getElementById("menu_viewPopup")[toggleEventListener]("popupshowing", this);
+
+ // Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
+ // Show All Tabs.
+ var key_showAllTabs = document.getElementById("key_showAllTabs");
+ if (enable)
+ key_showAllTabs.removeAttribute("disabled");
+ else
+ key_showAllTabs.setAttribute("disabled", "true");
+ }
+};
+
+
+/**
+ * All Tabs menu
+ */
+var allTabs = {
+ get toolbarButton() {
+ return document.getElementById("alltabs-button");
+ },
+
+ get canOpen() {
+ return isElementVisible(this.toolbarButton);
+ },
+
+ open: function allTabs_open() {
+ if (this.canOpen) {
+ // Without setTimeout, the menupopup won't stay open when invoking
+ // "View > Show All Tabs" and the menu bar auto-hides.
+ setTimeout(() => {
+ this.toolbarButton.open = true;
+ }, 0);
+ }
+ }
+};
diff --git a/browser/base/content/browser-customization.js b/browser/base/content/browser-customization.js
new file mode 100644
index 000000000..d5d51b893
--- /dev/null
+++ b/browser/base/content/browser-customization.js
@@ -0,0 +1,100 @@
+/* -*- 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/. */
+
+/**
+ * Customization handler prepares this browser window for entering and exiting
+ * customization mode by handling customizationstarting and customizationending
+ * events.
+ */
+var CustomizationHandler = {
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "customizationstarting":
+ this._customizationStarting();
+ break;
+ case "customizationchange":
+ this._customizationChange();
+ break;
+ case "customizationending":
+ this._customizationEnding(aEvent.detail);
+ break;
+ }
+ },
+
+ isCustomizing: function() {
+ return document.documentElement.hasAttribute("customizing");
+ },
+
+ _customizationStarting: function() {
+ // Disable the toolbar context menu items
+ let menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", true);
+
+ let cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.setAttribute("disabled", "true");
+
+ UpdateUrlbarSearchSplitterState();
+
+ CombinedStopReload.uninit();
+ PlacesToolbarHelper.customizeStart();
+ DownloadsButton.customizeStart();
+
+ // The additional padding on the sides of the browser
+ // can cause the customize tab to get clipped.
+ let tabContainer = gBrowser.tabContainer;
+ if (tabContainer.getAttribute("overflow") == "true") {
+ let tabstrip = tabContainer.mTabstrip;
+ tabstrip.ensureElementIsVisible(gBrowser.selectedTab, true);
+ }
+ },
+
+ _customizationChange: function() {
+ PlacesToolbarHelper.customizeChange();
+ },
+
+ _customizationEnding: function(aDetails) {
+ // Update global UI elements that may have been added or removed
+ if (aDetails.changed) {
+ gURLBar = document.getElementById("urlbar");
+
+ gHomeButton.updateTooltip();
+ XULBrowserWindow.init();
+
+ if (AppConstants.platform != "macosx")
+ updateEditUIVisibility();
+
+ // Hacky: update the PopupNotifications' object's reference to the iconBox,
+ // if it already exists, since it may have changed if the URL bar was
+ // added/removed.
+ if (!window.__lookupGetter__("PopupNotifications")) {
+ PopupNotifications.iconBox =
+ document.getElementById("notification-popup-box");
+ }
+
+ }
+
+ PlacesToolbarHelper.customizeDone();
+ DownloadsButton.customizeDone();
+
+ // The url bar splitter state is dependent on whether stop/reload
+ // and the location bar are combined, so we need this ordering
+ CombinedStopReload.init();
+ UpdateUrlbarSearchSplitterState();
+
+ // Update the urlbar
+ URLBarSetURI();
+ XULBrowserWindow.asyncUpdateUI();
+
+ // Re-enable parts of the UI we disabled during the dialog
+ let menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", false);
+ let cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.removeAttribute("disabled");
+
+ gBrowser.selectedBrowser.focus();
+ }
+}
diff --git a/browser/base/content/browser-data-submission-info-bar.js b/browser/base/content/browser-data-submission-info-bar.js
new file mode 100644
index 000000000..0c87d199f
--- /dev/null
+++ b/browser/base/content/browser-data-submission-info-bar.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/. */
+
+const LOGGER_NAME = "Toolkit.Telemetry";
+const LOGGER_PREFIX = "DataNotificationInfoBar::";
+
+/**
+ * Represents an info bar that shows a data submission notification.
+ */
+var gDataNotificationInfoBar = {
+ _OBSERVERS: [
+ "datareporting:notify-data-policy:request",
+ "datareporting:notify-data-policy:close",
+ ],
+
+ _DATA_REPORTING_NOTIFICATION: "data-reporting",
+
+ get _notificationBox() {
+ delete this._notificationBox;
+ return this._notificationBox = document.getElementById("global-notificationbox");
+ },
+
+ get _log() {
+ let Log = Cu.import("resource://gre/modules/Log.jsm", {}).Log;
+ delete this._log;
+ return this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
+ },
+
+ init: function() {
+ window.addEventListener("unload", () => {
+ for (let o of this._OBSERVERS) {
+ Services.obs.removeObserver(this, o);
+ }
+ }, false);
+
+ for (let o of this._OBSERVERS) {
+ Services.obs.addObserver(this, o, true);
+ }
+ },
+
+ _getDataReportingNotification: function (name=this._DATA_REPORTING_NOTIFICATION) {
+ return this._notificationBox.getNotificationWithValue(name);
+ },
+
+ _displayDataPolicyInfoBar: function (request) {
+ if (this._getDataReportingNotification()) {
+ return;
+ }
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let appName = brandBundle.getString("brandShortName");
+ let vendorName = brandBundle.getString("vendorShortName");
+
+ let message = gNavigatorBundle.getFormattedString(
+ "dataReportingNotification.message",
+ [appName, vendorName]);
+
+ this._actionTaken = false;
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("dataReportingNotification.button.label"),
+ accessKey: gNavigatorBundle.getString("dataReportingNotification.button.accessKey"),
+ popup: null,
+ callback: () => {
+ this._actionTaken = true;
+ window.openAdvancedPreferences("dataChoicesTab");
+ },
+ }];
+
+ this._log.info("Creating data reporting policy notification.");
+ this._notificationBox.appendNotification(
+ message,
+ this._DATA_REPORTING_NOTIFICATION,
+ null,
+ this._notificationBox.PRIORITY_INFO_HIGH,
+ buttons,
+ event => {
+ if (event == "removed") {
+ Services.obs.notifyObservers(null, "datareporting:notify-data-policy:close", null);
+ }
+ }
+ );
+ // It is important to defer calling onUserNotifyComplete() until we're
+ // actually sure the notification was displayed. If we ever called
+ // onUserNotifyComplete() without showing anything to the user, that
+ // would be very good for user choice. It may also have legal impact.
+ request.onUserNotifyComplete();
+ },
+
+ _clearPolicyNotification: function () {
+ let notification = this._getDataReportingNotification();
+ if (notification) {
+ this._log.debug("Closing notification.");
+ notification.close();
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "datareporting:notify-data-policy:request":
+ let request = subject.wrappedJSObject.object;
+ try {
+ this._displayDataPolicyInfoBar(request);
+ } catch (ex) {
+ request.onUserNotifyFailed(ex);
+ return;
+ }
+ break;
+
+ case "datareporting:notify-data-policy:close":
+ // If this observer fires, it means something else took care of
+ // responding. Therefore, we don't need to do anything. So, we
+ // act like we took action and clear state.
+ this._actionTaken = true;
+ this._clearPolicyNotification();
+ break;
+
+ default:
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ]),
+};
diff --git a/browser/base/content/browser-devedition.js b/browser/base/content/browser-devedition.js
new file mode 100644
index 000000000..0dc1e94da
--- /dev/null
+++ b/browser/base/content/browser-devedition.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/. */
+
+/**
+ * Listeners for the DevEdition theme. This adds an extra stylesheet
+ * to browser.xul if a pref is set and no other themes are applied.
+ */
+var DevEdition = {
+ _devtoolsThemePrefName: "devtools.theme",
+ styleSheetLocation: "chrome://browser/skin/devedition.css",
+ styleSheet: null,
+ initialized: false,
+
+ get isStyleSheetEnabled() {
+ return this.styleSheet && !this.styleSheet.sheet.disabled;
+ },
+
+ get isThemeCurrentlyApplied() {
+ let theme = LightweightThemeManager.currentTheme;
+ return theme && theme.id == "firefox-devedition@mozilla.org";
+ },
+
+ init: function () {
+ this.initialized = true;
+ Services.prefs.addObserver(this._devtoolsThemePrefName, this, false);
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ Services.obs.addObserver(this, "lightweight-theme-window-updated", false);
+ this._updateDevtoolsThemeAttribute();
+
+ if (this.isThemeCurrentlyApplied) {
+ this._toggleStyleSheet(true);
+ }
+ },
+
+ createStyleSheet: function() {
+ let styleSheetAttr = `href="${this.styleSheetLocation}" type="text/css"`;
+ this.styleSheet = document.createProcessingInstruction(
+ "xml-stylesheet", styleSheetAttr);
+ this.styleSheet.addEventListener("load", this);
+ document.insertBefore(this.styleSheet, document.documentElement);
+ this.styleSheet.sheet.disabled = true;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "lightweight-theme-styling-update") {
+ let newTheme = JSON.parse(data);
+ if (newTheme && newTheme.id == "firefox-devedition@mozilla.org") {
+ this._toggleStyleSheet(true);
+ } else {
+ this._toggleStyleSheet(false);
+ }
+ } else if (topic == "lightweight-theme-window-updated" && subject == window) {
+ this._updateLWTBrightness();
+ }
+
+ if (topic == "nsPref:changed" && data == this._devtoolsThemePrefName) {
+ this._updateDevtoolsThemeAttribute();
+ }
+ },
+
+ _inferBrightness: function() {
+ ToolbarIconColor.inferFromText();
+ // Get an inverted full screen button if the dark theme is applied.
+ if (this.isStyleSheetEnabled &&
+ document.documentElement.getAttribute("devtoolstheme") == "dark") {
+ document.documentElement.setAttribute("brighttitlebarforeground", "true");
+ } else {
+ document.documentElement.removeAttribute("brighttitlebarforeground");
+ }
+ },
+
+ _updateLWTBrightness() {
+ if (this.isThemeCurrentlyApplied) {
+ let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
+ let textColor = devtoolsTheme == "dark" ? "bright" : "dark";
+ document.documentElement.setAttribute("lwthemetextcolor", textColor);
+ }
+ },
+
+ _updateDevtoolsThemeAttribute: function() {
+ // Set an attribute on root element to make it possible
+ // to change colors based on the selected devtools theme.
+ let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
+ if (devtoolsTheme != "dark") {
+ devtoolsTheme = "light";
+ }
+ document.documentElement.setAttribute("devtoolstheme", devtoolsTheme);
+ this._updateLWTBrightness();
+ this._inferBrightness();
+ },
+
+ handleEvent: function(e) {
+ if (e.type === "load") {
+ this.styleSheet.removeEventListener("load", this);
+ this.refreshBrowserDisplay();
+ }
+ },
+
+ refreshBrowserDisplay: function() {
+ // Don't touch things on the browser if gBrowserInit.onLoad hasn't
+ // yet fired.
+ if (this.initialized) {
+ gBrowser.tabContainer._positionPinnedTabs();
+ this._inferBrightness();
+ }
+ },
+
+ _toggleStyleSheet: function(deveditionThemeEnabled) {
+ let wasEnabled = this.isStyleSheetEnabled;
+ if (deveditionThemeEnabled && !wasEnabled) {
+ // The stylesheet may not have been created yet if it wasn't
+ // needed on initial load. Make it now.
+ if (!this.styleSheet) {
+ this.createStyleSheet();
+ }
+ this.styleSheet.sheet.disabled = false;
+ this.refreshBrowserDisplay();
+ } else if (!deveditionThemeEnabled && wasEnabled) {
+ this.styleSheet.sheet.disabled = true;
+ this.refreshBrowserDisplay();
+ }
+ },
+
+ uninit: function () {
+ Services.prefs.removeObserver(this._devtoolsThemePrefName, this);
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update", false);
+ Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
+ if (this.styleSheet) {
+ this.styleSheet.removeEventListener("load", this);
+ }
+ this.styleSheet = null;
+ }
+};
+
+// If the DevEdition theme is going to be applied in gBrowserInit.onLoad,
+// then preload it now. This prevents a flash of unstyled content where the
+// normal theme is applied while the DevEdition stylesheet is loading.
+if (!AppConstants.RELEASE_OR_BETA &&
+ this != Services.appShell.hiddenDOMWindow && DevEdition.isThemeCurrentlyApplied) {
+ DevEdition.createStyleSheet();
+}
diff --git a/browser/base/content/browser-doctype.inc b/browser/base/content/browser-doctype.inc
new file mode 100644
index 000000000..10015d898
--- /dev/null
+++ b/browser/base/content/browser-doctype.inc
@@ -0,0 +1,23 @@
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+%browserDTD;
+<!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
+%baseMenuDTD;
+<!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
+%charsetDTD;
+<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
+%textcontextDTD;
+<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
+ %customizeToolbarDTD;
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+%safebrowsingDTD;
+<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+%aboutHomeDTD;
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+%syncBrandDTD;
+]>
+
diff --git a/browser/base/content/browser-feeds.js b/browser/base/content/browser-feeds.js
new file mode 100644
index 000000000..6f29d8915
--- /dev/null
+++ b/browser/base/content/browser-feeds.js
@@ -0,0 +1,646 @@
+/* -*- 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/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+ "resource://gre/modules/DeferredTask.jsm");
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+
+const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+const PREF_UPDATE_DELAY = 2000;
+
+const SETTABLE_PREFS = new Set([
+ PREF_VIDEO_SELECTED_ACTION,
+ PREF_AUDIO_SELECTED_ACTION,
+ PREF_SELECTED_ACTION,
+ PREF_VIDEO_SELECTED_READER,
+ PREF_AUDIO_SELECTED_READER,
+ PREF_SELECTED_READER,
+ PREF_VIDEO_SELECTED_WEB,
+ PREF_AUDIO_SELECTED_WEB,
+ PREF_SELECTED_WEB
+]);
+
+const EXECUTABLE_PREFS = new Set([
+ PREF_SELECTED_APP,
+ PREF_VIDEO_SELECTED_APP,
+ PREF_AUDIO_SELECTED_APP
+]);
+
+const VALID_ACTIONS = new Set(["ask", "reader", "bookmarks"]);
+const VALID_READERS = new Set(["web", "client", "default", "bookmarks"]);
+
+XPCOMUtils.defineLazyPreferenceGetter(this, "SHOULD_LOG",
+ "feeds.log", false);
+
+function LOG(str) {
+ if (SHOULD_LOG)
+ dump("*** Feeds: " + str + "\n");
+}
+
+function getPrefActionForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_ACTION;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_ACTION;
+
+ default:
+ return PREF_SELECTED_ACTION;
+ }
+}
+
+function getPrefReaderForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_READER;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_READER;
+
+ default:
+ return PREF_SELECTED_READER;
+ }
+}
+
+function getPrefWebForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_WEB;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_WEB;
+
+ default:
+ return PREF_SELECTED_WEB;
+ }
+}
+
+function getPrefAppForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_APP;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_APP;
+
+ default:
+ return PREF_SELECTED_APP;
+ }
+}
+
+/**
+ * Maps a feed type to a maybe-feed mimetype.
+ */
+function getMimeTypeForFeedType(aFeedType) {
+ switch (aFeedType) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return TYPE_MAYBE_VIDEO_FEED;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return TYPE_MAYBE_AUDIO_FEED;
+
+ default:
+ return TYPE_MAYBE_FEED;
+ }
+}
+
+/**
+ * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
+ * and shows UI when they are discovered.
+ */
+var FeedHandler = {
+ _prefChangeCallback: null,
+
+ /** Called when the user clicks on the Subscribe to This Page... menu item,
+ * or when the user clicks the feed button when the page contains multiple
+ * feeds.
+ * Builds a menu of unique feeds associated with the page, and if there
+ * is only one, shows the feed inline in the browser window.
+ * @param container
+ * The feed list container (menupopup or subview) to be populated.
+ * @param isSubview
+ * Whether we're creating a subview (true) or menu (false/undefined)
+ * @return true if the menu/subview should be shown, false if there was only
+ * one feed and the feed should be shown inline in the browser
+ * window (do not show the menupopup/subview).
+ */
+ buildFeedList(container, isSubview) {
+ let feeds = gBrowser.selectedBrowser.feeds;
+ if (!isSubview && feeds == null) {
+ // XXX hack -- menu opening depends on setting of an "open"
+ // attribute, and the menu refuses to open if that attribute is
+ // set (because it thinks it's already open). onpopupshowing gets
+ // called after the attribute is unset, and it doesn't get unset
+ // if we return false. so we unset it here; otherwise, the menu
+ // refuses to work past this point.
+ container.parentNode.removeAttribute("open");
+ return false;
+ }
+
+ for (let i = container.childNodes.length - 1; i >= 0; --i) {
+ let node = container.childNodes[i];
+ if (isSubview && node.localName == "label")
+ continue;
+ container.removeChild(node);
+ }
+
+ if (!feeds || feeds.length <= 1)
+ return false;
+
+ // Build the menu showing the available feed choices for viewing.
+ let itemNodeType = isSubview ? "toolbarbutton" : "menuitem";
+ for (let feedInfo of feeds) {
+ let item = document.createElement(itemNodeType);
+ let baseTitle = feedInfo.title || feedInfo.href;
+ item.setAttribute("label", baseTitle);
+ item.setAttribute("feed", feedInfo.href);
+ item.setAttribute("tooltiptext", feedInfo.href);
+ item.setAttribute("crop", "center");
+ let className = "feed-" + itemNodeType;
+ if (isSubview) {
+ className += " subviewbutton";
+ }
+ item.setAttribute("class", className);
+ container.appendChild(item);
+ }
+ return true;
+ },
+
+ /**
+ * Subscribe to a given feed. Called when
+ * 1. Page has a single feed and user clicks feed icon in location bar
+ * 2. Page has a single feed and user selects Subscribe menu item
+ * 3. Page has multiple feeds and user selects from feed icon popup (or subview)
+ * 4. Page has multiple feeds and user selects from Subscribe submenu
+ * @param href
+ * The feed to subscribe to. May be null, in which case the
+ * event target's feed attribute is examined.
+ * @param event
+ * The event this method is handling. Used to decide where
+ * to open the preview UI. (Optional, unless href is null)
+ */
+ subscribeToFeed(href, event) {
+ // Just load the feed in the content area to either subscribe or show the
+ // preview UI
+ if (!href)
+ href = event.target.getAttribute("feed");
+ urlSecurityCheck(href, gBrowser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ let feedURI = makeURI(href, document.characterSet);
+ // Use the feed scheme so X-Moz-Is-Feed will be set
+ // The value doesn't matter
+ if (/^https?$/.test(feedURI.scheme))
+ href = "feed:" + href;
+ this.loadFeed(href, event);
+ },
+
+ loadFeed(href, event) {
+ let feeds = gBrowser.selectedBrowser.feeds;
+ try {
+ openUILink(href, event, { ignoreAlt: true });
+ }
+ finally {
+ // We might default to a livebookmarks modal dialog,
+ // so reset that if the user happens to click it again
+ gBrowser.selectedBrowser.feeds = feeds;
+ }
+ },
+
+ get _feedMenuitem() {
+ delete this._feedMenuitem;
+ return this._feedMenuitem = document.getElementById("singleFeedMenuitemState");
+ },
+
+ get _feedMenupopup() {
+ delete this._feedMenupopup;
+ return this._feedMenupopup = document.getElementById("multipleFeedsMenuState");
+ },
+
+ /**
+ * Update the browser UI to show whether or not feeds are available when
+ * a page is loaded or the user switches tabs to a page that has feeds.
+ */
+ updateFeeds() {
+ if (this._updateFeedTimeout)
+ clearTimeout(this._updateFeedTimeout);
+
+ let feeds = gBrowser.selectedBrowser.feeds;
+ let haveFeeds = feeds && feeds.length > 0;
+
+ let feedButton = document.getElementById("feed-button");
+ if (feedButton) {
+ if (haveFeeds) {
+ feedButton.removeAttribute("disabled");
+ } else {
+ feedButton.setAttribute("disabled", "true");
+ }
+ }
+
+ if (!haveFeeds) {
+ this._feedMenuitem.setAttribute("disabled", "true");
+ this._feedMenuitem.removeAttribute("hidden");
+ this._feedMenupopup.setAttribute("hidden", "true");
+ return;
+ }
+
+ if (feeds.length > 1) {
+ this._feedMenuitem.setAttribute("hidden", "true");
+ this._feedMenupopup.removeAttribute("hidden");
+ } else {
+ this._feedMenuitem.setAttribute("feed", feeds[0].href);
+ this._feedMenuitem.removeAttribute("disabled");
+ this._feedMenuitem.removeAttribute("hidden");
+ this._feedMenupopup.setAttribute("hidden", "true");
+ }
+ },
+
+ addFeed(link, browserForLink) {
+ if (!browserForLink.feeds)
+ browserForLink.feeds = [];
+
+ browserForLink.feeds.push({ href: link.href, title: link.title });
+
+ // If this addition was for the current browser, update the UI. For
+ // background browsers, we'll update on tab switch.
+ if (browserForLink == gBrowser.selectedBrowser) {
+ // Batch updates to avoid updating the UI for multiple onLinkAdded events
+ // fired within 100ms of each other.
+ if (this._updateFeedTimeout)
+ clearTimeout(this._updateFeedTimeout);
+ this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
+ }
+ },
+
+ /**
+ * Get the human-readable display name of a file. This could be the
+ * application name.
+ * @param file
+ * A nsIFile to look up the name of
+ * @return The display name of the application represented by the file.
+ */
+ _getFileDisplayName(file) {
+ switch (AppConstants.platform) {
+ case "win":
+ if (file instanceof Ci.nsILocalFileWin) {
+ try {
+ return file.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+ break;
+ case "macosx":
+ if (file instanceof Ci.nsILocalFileMac) {
+ try {
+ return file.bundleDisplayName;
+ } catch (e) {}
+ }
+ break;
+ }
+
+ return file.leafName;
+ },
+
+ _chooseClientApp(aTitle, aTypeName, aBrowser) {
+ const prefName = getPrefAppForType(aTypeName);
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+
+ fp.init(window, aTitle, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterApps);
+
+ fp.open((aResult) => {
+ if (aResult == Ci.nsIFilePicker.returnOK) {
+ let selectedApp = fp.file;
+ if (selectedApp) {
+ // XXXben - we need to compare this with the running instance
+ // executable just don't know how to do that via script
+ // XXXmano TBD: can probably add this to nsIShellService
+ let appName = "";
+ switch (AppConstants.platform) {
+ case "win":
+ appName = AppConstants.MOZ_APP_NAME + ".exe";
+ break;
+ case "macosx":
+ appName = AppConstants.MOZ_MACBUNDLE_NAME;
+ break;
+ default:
+ appName = AppConstants.MOZ_APP_NAME + "-bin";
+ break;
+ }
+
+ if (fp.file.leafName != appName) {
+ Services.prefs.setComplexValue(prefName, Ci.nsILocalFile, selectedApp);
+ aBrowser.messageManager.sendAsyncMessage("FeedWriter:SetApplicationLauncherMenuItem",
+ { name: this._getFileDisplayName(selectedApp),
+ type: "SelectedAppMenuItem" });
+ }
+ }
+ }
+ });
+
+ },
+
+ executeClientApp(aSpec, aTitle, aSubtitle, aFeedHandler) {
+ // aFeedHandler is either "default", indicating the system default reader, or a pref-name containing
+ // an nsILocalFile pointing to the feed handler's executable.
+
+ let clientApp = null;
+ if (aFeedHandler == "default") {
+ clientApp = Cc["@mozilla.org/browser/shell-service;1"]
+ .getService(Ci.nsIShellService)
+ .defaultFeedReader;
+ } else {
+ clientApp = Services.prefs.getComplexValue(aFeedHandler, Ci.nsILocalFile);
+ }
+
+ // For the benefit of applications that might know how to deal with more
+ // URLs than just feeds, send feed: URLs in the following format:
+ //
+ // http urls: replace scheme with feed, e.g.
+ // http://foo.com/index.rdf -> feed://foo.com/index.rdf
+ // other urls: prepend feed: scheme, e.g.
+ // https://foo.com/index.rdf -> feed:https://foo.com/index.rdf
+ let feedURI = NetUtil.newURI(aSpec);
+ if (feedURI.schemeIs("http")) {
+ feedURI.scheme = "feed";
+ aSpec = feedURI.spec;
+ } else {
+ aSpec = "feed:" + aSpec;
+ }
+
+ // Retrieving the shell service might fail on some systems, most
+ // notably systems where GNOME is not installed.
+ try {
+ let ss = Cc["@mozilla.org/browser/shell-service;1"]
+ .getService(Ci.nsIShellService);
+ ss.openApplicationWithURI(clientApp, aSpec);
+ } catch (e) {
+ // If we couldn't use the shell service, fallback to using a
+ // nsIProcess instance
+ let p = Cc["@mozilla.org/process/util;1"]
+ .createInstance(Ci.nsIProcess);
+ p.init(clientApp);
+ p.run(false, [aSpec], 1);
+ }
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ init() {
+ window.messageManager.addMessageListener("FeedWriter:ChooseClientApp", this);
+ window.messageManager.addMessageListener("FeedWriter:GetSubscriptionUI", this);
+ window.messageManager.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribe", this);
+ window.messageManager.addMessageListener("FeedWriter:ShownFirstRun", this);
+
+ Services.ppmm.addMessageListener("FeedConverter:ExecuteClientApp", this);
+
+ const prefs = Services.prefs;
+ prefs.addObserver(PREF_SELECTED_ACTION, this, true);
+ prefs.addObserver(PREF_SELECTED_READER, this, true);
+ prefs.addObserver(PREF_SELECTED_WEB, this, true);
+ prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, true);
+ prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, true);
+ prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, true);
+ prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, true);
+ prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, true);
+ prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, true);
+ },
+
+ uninit() {
+ Services.ppmm.removeMessageListener("FeedConverter:ExecuteClientApp", this);
+
+ this._prefChangeCallback = null;
+ },
+
+ // nsIObserver
+ observe(subject, topic, data) {
+ if (topic == "nsPref:changed") {
+ LOG(`Pref changed ${data}`)
+ if (this._prefChangeCallback) {
+ this._prefChangeCallback.disarm();
+ }
+ // Multiple prefs are set at the same time, debounce to reduce noise
+ // This can happen in one feed and we want to message all feed pages
+ this._prefChangeCallback = new DeferredTask(() => {
+ this._prefChanged(data);
+ }, PREF_UPDATE_DELAY);
+ this._prefChangeCallback.arm();
+ }
+ },
+
+ _prefChanged(prefName) {
+ // Don't observe for PREF_*SELECTED_APP as user likely just picked one
+ // That is also handled by SetApplicationLauncherMenuItem call
+ // Rather than the others which happen on subscription
+ switch (prefName) {
+ case PREF_SELECTED_READER:
+ case PREF_SELECTED_WEB:
+ case PREF_VIDEO_SELECTED_READER:
+ case PREF_VIDEO_SELECTED_WEB:
+ case PREF_AUDIO_SELECTED_READER:
+ case PREF_AUDIO_SELECTED_WEB:
+ case PREF_SELECTED_ACTION:
+ case PREF_VIDEO_SELECTED_ACTION:
+ case PREF_AUDIO_SELECTED_ACTION:
+ const response = {
+ default: this._getReaderForType(Ci.nsIFeed.TYPE_FEED),
+ [Ci.nsIFeed.TYPE_AUDIO]: this._getReaderForType(Ci.nsIFeed.TYPE_AUDIO),
+ [Ci.nsIFeed.TYPE_VIDEO]: this._getReaderForType(Ci.nsIFeed.TYPE_VIDEO)
+ };
+ Services.mm.broadcastAsyncMessage("FeedWriter:PreferenceUpdated",
+ response);
+ break;
+ }
+ },
+
+ _initSubscriptionUIResponse(feedType) {
+ const wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService);
+ const handlersRaw = wccr.getContentHandlers(getMimeTypeForFeedType(feedType));
+ const handlers = [];
+ for (let handler of handlersRaw) {
+ LOG(`Handler found: ${handler}`);
+ handlers.push({
+ name: handler.name,
+ uri: handler.uri
+ });
+ }
+ let showFirstRunUI = true;
+ // eslint-disable-next-line mozilla/use-default-preference-values
+ try {
+ showFirstRunUI = Services.prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
+ } catch (ex) { }
+ const response = { handlers, showFirstRunUI };
+ let selectedClientApp;
+ const feedTypePref = getPrefAppForType(feedType);
+ try {
+ selectedClientApp = Services.prefs.getComplexValue(feedTypePref, Ci.nsILocalFile);
+ } catch (ex) {
+ // Just do nothing, then we won't bother populating
+ }
+
+ let defaultClientApp = null;
+ try {
+ // This can sometimes not exist
+ defaultClientApp = Cc["@mozilla.org/browser/shell-service;1"]
+ .getService(Ci.nsIShellService)
+ .defaultFeedReader;
+ } catch (ex) {
+ // Just do nothing, then we don't bother populating
+ }
+
+ if (selectedClientApp && selectedClientApp.exists()) {
+ if (defaultClientApp && selectedClientApp.path != defaultClientApp.path) {
+ // Only set the default menu item if it differs from the selected one
+ response.defaultMenuItem = this._getFileDisplayName(defaultClientApp);
+ }
+ response.selectedMenuItem = this._getFileDisplayName(selectedClientApp);
+ }
+ response.reader = this._getReaderForType(feedType);
+ return response;
+ },
+
+ _setPref(aPrefName, aPrefValue, aIsComplex = false) {
+ LOG(`FeedWriter._setPref ${aPrefName}`);
+ // Ensure we have a pref that is settable
+ if (aPrefName && SETTABLE_PREFS.has(aPrefName)) {
+ if (aIsComplex) {
+ const supportsString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ supportsString.data = aPrefValue;
+ Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, supportsString);
+ } else {
+ Services.prefs.setCharPref(aPrefName, aPrefValue);
+ }
+ } else {
+ LOG(`FeedWriter._setPref ${aPrefName} not allowed`);
+ }
+ },
+
+ _getReaderForType(feedType) {
+ let prefs = Services.prefs;
+ let handler = "bookmarks";
+ let url;
+ // eslint-disable-next-line mozilla/use-default-preference-values
+ try {
+ handler = prefs.getCharPref(getPrefReaderForType(feedType));
+ } catch (ex) { }
+
+ if (handler === "web") {
+ try {
+ url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
+ } catch (ex) {
+ LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
+ url = null;
+ }
+ }
+ const alwaysUse = this._getAlwaysUseState(feedType);
+ const action = prefs.getCharPref(getPrefActionForType(feedType));
+ return { handler, url, alwaysUse, action };
+ },
+
+ _getAlwaysUseState(feedType) {
+ try {
+ return Services.prefs.getCharPref(getPrefActionForType(feedType)) != "ask";
+ } catch (ex) { }
+ return false;
+ },
+
+ receiveMessage(msg) {
+ let handler;
+ switch (msg.name) {
+ case "FeedWriter:GetSubscriptionUI":
+ const response = this._initSubscriptionUIResponse(msg.data.feedType);
+ msg.target.messageManager
+ .sendAsyncMessage("FeedWriter:GetSubscriptionUIResponse",
+ response);
+ break;
+ case "FeedWriter:ChooseClientApp":
+ this._chooseClientApp(msg.data.title, msg.data.feedType, msg.target);
+ break;
+ case "FeedWriter:ShownFirstRun":
+ Services.prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
+ break;
+ case "FeedWriter:SetFeedPrefsAndSubscribe":
+ const settings = msg.data;
+ if (!settings.action || !VALID_ACTIONS.has(settings.action)) {
+ LOG(`Invalid action ${settings.action}`);
+ return;
+ }
+ if (!settings.reader || !VALID_READERS.has(settings.reader)) {
+ LOG(`Invalid reader ${settings.reader}`);
+ return;
+ }
+ const actionPref = getPrefActionForType(settings.feedType);
+ this._setPref(actionPref, settings.action);
+ const readerPref = getPrefReaderForType(settings.feedType);
+ this._setPref(readerPref, settings.reader);
+ handler = null;
+
+ switch (settings.reader) {
+ case "web":
+ // This is a web set URI by content using window.registerContentHandler()
+ // Lets make sure we know about it before setting it
+ const webPref = getPrefWebForType(settings.feedType);
+ let wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService);
+ // If the user provided an invalid web URL this function won't give us a reference
+ handler = wccr.getWebContentHandlerByURI(getMimeTypeForFeedType(settings.feedType), settings.uri);
+ if (handler) {
+ this._setPref(webPref, settings.uri, true);
+ if (settings.useAsDefault) {
+ wccr.setAutoHandler(getMimeTypeForFeedType(settings.feedType), handler);
+ }
+ msg.target.messageManager
+ .sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribeResponse",
+ { redirect: handler.getHandlerURI(settings.feedLocation) });
+ } else {
+ LOG(`No handler found for web ${settings.feedType} ${settings.uri}`);
+ }
+ break;
+ default:
+ const feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+
+ feedService.addToClientReader(settings.feedLocation,
+ settings.feedTitle,
+ settings.feedSubtitle,
+ settings.feedType,
+ settings.reader);
+ }
+ break;
+ case "FeedConverter:ExecuteClientApp":
+ // Always check feedHandler is from a set array of executable prefs
+ if (EXECUTABLE_PREFS.has(msg.data.feedHandler)) {
+ this.executeClientApp(msg.data.spec, msg.data.title,
+ msg.data.subtitle, msg.data.feedHandler);
+ } else {
+ LOG(`FeedConverter:ExecuteClientApp - Will not exec ${msg.data.feedHandler}`);
+ }
+ break;
+ }
+ },
+};
diff --git a/browser/base/content/browser-fullScreenAndPointerLock.js b/browser/base/content/browser-fullScreenAndPointerLock.js
new file mode 100644
index 000000000..497e51121
--- /dev/null
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -0,0 +1,673 @@
+/* -*- 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 PointerlockFsWarning = {
+
+ _element: null,
+ _origin: null,
+
+ init: function() {
+ this.Timeout.prototype = {
+ start: function() {
+ this.cancel();
+ this._id = setTimeout(() => this._handle(), this._delay);
+ },
+ cancel: function() {
+ if (this._id) {
+ clearTimeout(this._id);
+ this._id = 0;
+ }
+ },
+ _handle: function() {
+ this._id = 0;
+ this._func();
+ },
+ get delay() {
+ return this._delay;
+ }
+ };
+ },
+
+ /**
+ * Timeout object for managing timeout request. If it is started when
+ * the previous call hasn't finished, it would automatically cancelled
+ * the previous one.
+ */
+ Timeout: function(func, delay) {
+ this._id = 0;
+ this._func = func;
+ this._delay = delay;
+ },
+
+ showPointerLock: function(aOrigin) {
+ if (!document.fullscreen) {
+ let timeout = gPrefService.getIntPref("pointer-lock-api.warning.timeout");
+ this.show(aOrigin, "pointerlock-warning", timeout, 0);
+ }
+ },
+
+ showFullScreen: function(aOrigin) {
+ let timeout = gPrefService.getIntPref("full-screen-api.warning.timeout");
+ let delay = gPrefService.getIntPref("full-screen-api.warning.delay");
+ this.show(aOrigin, "fullscreen-warning", timeout, delay);
+ },
+
+ // Shows a warning that the site has entered fullscreen or
+ // pointer lock for a short duration.
+ show: function(aOrigin, elementId, timeout, delay) {
+
+ if (!this._element) {
+ this._element = document.getElementById(elementId);
+ // Setup event listeners
+ this._element.addEventListener("transitionend", this);
+ window.addEventListener("mousemove", this, true);
+ // The timeout to hide the warning box after a while.
+ this._timeoutHide = new this.Timeout(() => {
+ this._state = "hidden";
+ }, timeout);
+ // The timeout to show the warning box when the pointer is at the top
+ this._timeoutShow = new this.Timeout(() => {
+ this._state = "ontop";
+ this._timeoutHide.start();
+ }, delay);
+ }
+
+ // Set the strings on the warning UI.
+ if (aOrigin) {
+ this._origin = aOrigin;
+ }
+ let uri = BrowserUtils.makeURI(this._origin);
+ let host = null;
+ try {
+ host = uri.host;
+ } catch (e) { }
+ let textElem = this._element.querySelector(".pointerlockfswarning-domain-text");
+ if (!host) {
+ textElem.setAttribute("hidden", true);
+ } else {
+ textElem.removeAttribute("hidden");
+ let hostElem = this._element.querySelector(".pointerlockfswarning-domain");
+ // Document's principal's URI has a host. Display a warning including it.
+ let utils = {};
+ Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
+ hostElem.textContent = utils.DownloadUtils.getURIHost(uri.spec)[0];
+ }
+
+ this._element.dataset.identity =
+ gIdentityHandler.pointerlockFsWarningClassName;
+
+ // User should be allowed to explicitly disable
+ // the prompt if they really want.
+ if (this._timeoutHide.delay <= 0) {
+ return;
+ }
+
+ // Explicitly set the last state to hidden to avoid the warning
+ // box being hidden immediately because of mousemove.
+ this._state = "onscreen";
+ this._lastState = "hidden";
+ this._timeoutHide.start();
+ },
+
+ close: function() {
+ if (!this._element) {
+ return;
+ }
+ // Cancel any pending timeout
+ this._timeoutHide.cancel();
+ this._timeoutShow.cancel();
+ // Reset state of the warning box
+ this._state = "hidden";
+ this._element.setAttribute("hidden", true);
+ // Remove all event listeners
+ this._element.removeEventListener("transitionend", this);
+ window.removeEventListener("mousemove", this, true);
+ // Clear fields
+ this._element = null;
+ this._timeoutHide = null;
+ this._timeoutShow = null;
+
+ // Ensure focus switches away from the (now hidden) warning box.
+ // If the user clicked buttons in the warning box, it would have
+ // been focused, and any key events would be directed at the (now
+ // hidden) chrome document instead of the target document.
+ gBrowser.selectedBrowser.focus();
+ },
+
+ // State could be one of "onscreen", "ontop", "hiding", and
+ // "hidden". Setting the state to "onscreen" and "ontop" takes
+ // effect immediately, while setting it to "hidden" actually
+ // turns the state to "hiding" before the transition finishes.
+ _lastState: null,
+ _STATES: ["hidden", "ontop", "onscreen"],
+ get _state() {
+ for (let state of this._STATES) {
+ if (this._element.hasAttribute(state)) {
+ return state;
+ }
+ }
+ return "hiding";
+ },
+ set _state(newState) {
+ let currentState = this._state;
+ if (currentState == newState) {
+ return;
+ }
+ if (currentState != "hiding") {
+ this._lastState = currentState;
+ this._element.removeAttribute(currentState);
+ }
+ if (newState != "hidden") {
+ if (currentState != "hidden") {
+ this._element.setAttribute(newState, true);
+ } else {
+ // When the previous state is hidden, the display was none,
+ // thus no box was constructed. We need to wait for the new
+ // display value taking effect first, otherwise, there won't
+ // be any transition. Since requestAnimationFrame callback is
+ // generally triggered before any style flush and layout, we
+ // should wait for the second animation frame.
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ if (this._element) {
+ this._element.setAttribute(newState, true);
+ }
+ });
+ });
+ }
+ }
+ },
+
+ handleEvent: function(event) {
+ switch (event.type) {
+ case "mousemove": {
+ let state = this._state;
+ if (state == "hidden") {
+ // If the warning box is currently hidden, show it after
+ // a short delay if the pointer is at the top.
+ if (event.clientY != 0) {
+ this._timeoutShow.cancel();
+ } else if (this._timeoutShow.delay >= 0) {
+ this._timeoutShow.start();
+ }
+ } else {
+ let elemRect = this._element.getBoundingClientRect();
+ if (state == "hiding" && this._lastState != "hidden") {
+ // If we are on the hiding transition, and the pointer
+ // moved near the box, restore to the previous state.
+ if (event.clientY <= elemRect.bottom + 50) {
+ this._state = this._lastState;
+ this._timeoutHide.start();
+ }
+ } else if (state == "ontop" || this._lastState != "hidden") {
+ // State being "ontop" or the previous state not being
+ // "hidden" indicates this current warning box is shown
+ // in response to user's action. Hide it immediately when
+ // the pointer leaves that area.
+ if (event.clientY > elemRect.bottom + 50) {
+ this._state = "hidden";
+ this._timeoutHide.cancel();
+ }
+ }
+ }
+ break;
+ }
+ case "transitionend": {
+ if (this._state == "hiding") {
+ this._element.setAttribute("hidden", true);
+ }
+ break;
+ }
+ }
+ }
+};
+
+var PointerLock = {
+
+ init: function() {
+ window.messageManager.addMessageListener("PointerLock:Entered", this);
+ window.messageManager.addMessageListener("PointerLock:Exited", this);
+ },
+
+ receiveMessage: function(aMessage) {
+ switch (aMessage.name) {
+ case "PointerLock:Entered": {
+ PointerlockFsWarning.showPointerLock(aMessage.data.originNoSuffix);
+ break;
+ }
+ case "PointerLock:Exited": {
+ PointerlockFsWarning.close();
+ break;
+ }
+ }
+ }
+};
+
+var FullScreen = {
+ _MESSAGES: [
+ "DOMFullscreen:Request",
+ "DOMFullscreen:NewOrigin",
+ "DOMFullscreen:Exit",
+ "DOMFullscreen:Painted",
+ ],
+
+ init: function() {
+ // called when we go into full screen, even if initiated by a web page script
+ window.addEventListener("fullscreen", this, true);
+ window.addEventListener("MozDOMFullscreen:Entered", this,
+ /* useCapture */ true,
+ /* wantsUntrusted */ false);
+ window.addEventListener("MozDOMFullscreen:Exited", this,
+ /* useCapture */ true,
+ /* wantsUntrusted */ false);
+ for (let type of this._MESSAGES) {
+ window.messageManager.addMessageListener(type, this);
+ }
+
+ if (window.fullScreen)
+ this.toggle();
+ },
+
+ uninit: function() {
+ for (let type of this._MESSAGES) {
+ window.messageManager.removeMessageListener(type, this);
+ }
+ this.cleanup();
+ },
+
+ toggle: function () {
+ var enterFS = window.fullScreen;
+
+ // Toggle the View:FullScreen command, which controls elements like the
+ // fullscreen menuitem, and menubars.
+ let fullscreenCommand = document.getElementById("View:FullScreen");
+ if (enterFS) {
+ fullscreenCommand.setAttribute("checked", enterFS);
+ } else {
+ fullscreenCommand.removeAttribute("checked");
+ }
+
+ if (AppConstants.platform == "macosx") {
+ // Make sure the menu items are adjusted.
+ document.getElementById("enterFullScreenItem").hidden = enterFS;
+ document.getElementById("exitFullScreenItem").hidden = !enterFS;
+ }
+
+ if (!this._fullScrToggler) {
+ this._fullScrToggler = document.getElementById("fullscr-toggler");
+ this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
+ this._fullScrToggler.addEventListener("touchmove", this._expandCallback, {passive: true});
+ }
+
+ if (enterFS) {
+ gNavToolbox.setAttribute("inFullscreen", true);
+ document.documentElement.setAttribute("inFullscreen", true);
+ if (!document.fullscreenElement && this.useLionFullScreen)
+ document.documentElement.setAttribute("OSXLionFullscreen", true);
+ } else {
+ gNavToolbox.removeAttribute("inFullscreen");
+ document.documentElement.removeAttribute("inFullscreen");
+ document.documentElement.removeAttribute("OSXLionFullscreen");
+ }
+
+ if (!document.fullscreenElement)
+ this._updateToolbars(enterFS);
+
+ if (enterFS) {
+ document.addEventListener("keypress", this._keyToggleCallback, false);
+ document.addEventListener("popupshown", this._setPopupOpen, false);
+ document.addEventListener("popuphidden", this._setPopupOpen, false);
+ // In DOM fullscreen mode, we hide toolbars with CSS
+ if (!document.fullscreenElement)
+ this.hideNavToolbox(true);
+ }
+ else {
+ this.showNavToolbox(false);
+ // This is needed if they use the context menu to quit fullscreen
+ this._isPopupOpen = false;
+ this.cleanup();
+ // In TabsInTitlebar._update(), we cancel the appearance update on
+ // resize event for exiting fullscreen, since that happens before we
+ // change the UI here in the "fullscreen" event. Hence we need to
+ // call it here to ensure the appearance is properly updated. See
+ // TabsInTitlebar._update() and bug 1173768.
+ TabsInTitlebar.updateAppearance(true);
+ }
+
+ if (enterFS && !document.fullscreenElement) {
+ Services.telemetry.getHistogramById("FX_BROWSER_FULLSCREEN_USED")
+ .add(1);
+ }
+ },
+
+ exitDomFullScreen : function() {
+ document.exitFullscreen();
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "fullscreen":
+ this.toggle();
+ break;
+ case "MozDOMFullscreen:Entered": {
+ // The event target is the element which requested the DOM
+ // fullscreen. If we were entering DOM fullscreen for a remote
+ // browser, the target would be `gBrowser` and the original
+ // target would be the browser which was the parameter of
+ // `remoteFrameFullscreenChanged` call. If the fullscreen
+ // request was initiated from an in-process browser, we need
+ // to get its corresponding browser here.
+ let browser;
+ if (event.target == gBrowser) {
+ browser = event.originalTarget;
+ } else {
+ let topWin = event.target.ownerGlobal.top;
+ browser = gBrowser.getBrowserForContentWindow(topWin);
+ }
+ TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
+ this.enterDomFullscreen(browser);
+ break;
+ }
+ case "MozDOMFullscreen:Exited":
+ TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
+ this.cleanupDomFullscreen();
+ break;
+ }
+ },
+
+ receiveMessage: function(aMessage) {
+ let browser = aMessage.target;
+ switch (aMessage.name) {
+ case "DOMFullscreen:Request": {
+ this._windowUtils.remoteFrameFullscreenChanged(browser);
+ break;
+ }
+ case "DOMFullscreen:NewOrigin": {
+ // Don't show the warning if we've already exited fullscreen.
+ if (document.fullscreen) {
+ PointerlockFsWarning.showFullScreen(aMessage.data.originNoSuffix);
+ }
+ break;
+ }
+ case "DOMFullscreen:Exit": {
+ this._windowUtils.remoteFrameFullscreenReverted();
+ break;
+ }
+ case "DOMFullscreen:Painted": {
+ Services.obs.notifyObservers(window, "fullscreen-painted", "");
+ TelemetryStopwatch.finish("FULLSCREEN_CHANGE_MS");
+ break;
+ }
+ }
+ },
+
+ enterDomFullscreen : function(aBrowser) {
+
+ if (!document.fullscreenElement) {
+ return;
+ }
+
+ // If we have a current pointerlock warning shown then hide it
+ // before transition.
+ PointerlockFsWarning.close();
+
+ // If it is a remote browser, send a message to ask the content
+ // to enter fullscreen state. We don't need to do so if it is an
+ // in-process browser, since all related document should have
+ // entered fullscreen state at this point.
+ // This should be done before the active tab check below to ensure
+ // that the content document handles the pending request. Doing so
+ // before the check is fine since we also check the activeness of
+ // the requesting document in content-side handling code.
+ if (this._isRemoteBrowser(aBrowser)) {
+ aBrowser.messageManager.sendAsyncMessage("DOMFullscreen:Entered");
+ }
+
+ // If we've received a fullscreen notification, we have to ensure that the
+ // element that's requesting fullscreen belongs to the browser that's currently
+ // active. If not, we exit fullscreen since the "full-screen document" isn't
+ // actually visible now.
+ if (!aBrowser || gBrowser.selectedBrowser != aBrowser ||
+ // The top-level window has lost focus since the request to enter
+ // full-screen was made. Cancel full-screen.
+ Services.focus.activeWindow != window) {
+ // This function is called synchronously in fullscreen change, so
+ // we have to avoid calling exitFullscreen synchronously here.
+ setTimeout(() => document.exitFullscreen(), 0);
+ return;
+ }
+
+ document.documentElement.setAttribute("inDOMFullscreen", true);
+
+ if (gFindBarInitialized) {
+ gFindBar.close(true);
+ }
+
+ // Exit DOM full-screen mode upon open, close, or change tab.
+ gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
+ gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
+ gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
+
+ // Add listener to detect when the fullscreen window is re-focused.
+ // If a fullscreen window loses focus, we show a warning when the
+ // fullscreen window is refocused.
+ window.addEventListener("activate", this);
+ },
+
+ cleanup: function () {
+ if (!window.fullScreen) {
+ MousePosTracker.removeListener(this);
+ document.removeEventListener("keypress", this._keyToggleCallback, false);
+ document.removeEventListener("popupshown", this._setPopupOpen, false);
+ document.removeEventListener("popuphidden", this._setPopupOpen, false);
+ }
+ },
+
+ cleanupDomFullscreen: function () {
+ window.messageManager
+ .broadcastAsyncMessage("DOMFullscreen:CleanUp");
+
+ PointerlockFsWarning.close();
+ gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
+ gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
+ gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
+ window.removeEventListener("activate", this);
+
+ document.documentElement.removeAttribute("inDOMFullscreen");
+ },
+
+ _isRemoteBrowser: function (aBrowser) {
+ return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true";
+ },
+
+ get _windowUtils() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ getMouseTargetRect: function()
+ {
+ return this._mouseTargetRect;
+ },
+
+ // Event callbacks
+ _expandCallback: function()
+ {
+ FullScreen.showNavToolbox();
+ },
+ onMouseEnter: function()
+ {
+ FullScreen.hideNavToolbox();
+ },
+ _keyToggleCallback: function(aEvent)
+ {
+ // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
+ // should provide a way to collapse them too.
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ FullScreen.hideNavToolbox();
+ }
+ // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
+ else if (aEvent.keyCode == aEvent.DOM_VK_F6)
+ FullScreen.showNavToolbox();
+ },
+
+ // Checks whether we are allowed to collapse the chrome
+ _isPopupOpen: false,
+ _isChromeCollapsed: false,
+ _safeToCollapse: function () {
+ if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
+ return false;
+
+ // a popup menu is open in chrome: don't collapse chrome
+ if (this._isPopupOpen)
+ return false;
+
+ // On OS X Lion we don't want to hide toolbars.
+ if (this.useLionFullScreen)
+ return false;
+
+ // a textbox in chrome is focused (location bar anyone?): don't collapse chrome
+ if (document.commandDispatcher.focusedElement &&
+ document.commandDispatcher.focusedElement.ownerDocument == document &&
+ document.commandDispatcher.focusedElement.localName == "input") {
+ return false;
+ }
+
+ return true;
+ },
+
+ _setPopupOpen: function(aEvent)
+ {
+ // Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
+ // Otherwise, they would not affect chrome and the user would expect the chrome to go away.
+ // e.g. we wouldn't want the autoscroll icon firing this event, so when the user
+ // toggles chrome when moving mouse to the top, it doesn't go away again.
+ if (aEvent.type == "popupshown" && !FullScreen._isChromeCollapsed &&
+ aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
+ FullScreen._isPopupOpen = true;
+ else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
+ aEvent.target.localName != "window") {
+ FullScreen._isPopupOpen = false;
+ // Try again to hide toolbar when we close the popup.
+ FullScreen.hideNavToolbox(true);
+ }
+ },
+
+ // Autohide helpers for the context menu item
+ getAutohide: function(aItem)
+ {
+ aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
+ },
+ setAutohide: function()
+ {
+ gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
+ // Try again to hide toolbar when we change the pref.
+ FullScreen.hideNavToolbox(true);
+ },
+
+ showNavToolbox: function(trackMouse = true) {
+ this._fullScrToggler.hidden = true;
+ gNavToolbox.removeAttribute("fullscreenShouldAnimate");
+ gNavToolbox.style.marginTop = "";
+
+ if (!this._isChromeCollapsed) {
+ return;
+ }
+
+ // Track whether mouse is near the toolbox
+ if (trackMouse && !this.useLionFullScreen) {
+ let rect = gBrowser.mPanelContainer.getBoundingClientRect();
+ this._mouseTargetRect = {
+ top: rect.top + 50,
+ bottom: rect.bottom,
+ left: rect.left,
+ right: rect.right
+ };
+ MousePosTracker.addListener(this);
+ }
+
+ this._isChromeCollapsed = false;
+ },
+
+ hideNavToolbox: function (aAnimate = false) {
+ if (this._isChromeCollapsed || !this._safeToCollapse())
+ return;
+
+ this._fullScrToggler.hidden = false;
+
+ if (aAnimate && gPrefService.getBoolPref("browser.fullscreen.animate")) {
+ gNavToolbox.setAttribute("fullscreenShouldAnimate", true);
+ // Hide the fullscreen toggler until the transition ends.
+ let listener = () => {
+ gNavToolbox.removeEventListener("transitionend", listener, true);
+ if (this._isChromeCollapsed)
+ this._fullScrToggler.hidden = false;
+ };
+ gNavToolbox.addEventListener("transitionend", listener, true);
+ this._fullScrToggler.hidden = true;
+ }
+
+ gNavToolbox.style.marginTop =
+ -gNavToolbox.getBoundingClientRect().height + "px";
+ this._isChromeCollapsed = true;
+ MousePosTracker.removeListener(this);
+ },
+
+ _updateToolbars: function (aEnterFS) {
+ for (let el of document.querySelectorAll("toolbar[fullscreentoolbar=true]")) {
+ if (aEnterFS) {
+ // Give the main nav bar and the tab bar the fullscreen context menu,
+ // otherwise remove context menu to prevent breakage
+ el.setAttribute("saved-context", el.getAttribute("context"));
+ if (el.id == "nav-bar" || el.id == "TabsToolbar")
+ el.setAttribute("context", "autohide-context");
+ else
+ el.removeAttribute("context");
+
+ // Set the inFullscreen attribute to allow specific styling
+ // in fullscreen mode
+ el.setAttribute("inFullscreen", true);
+ } else {
+ if (el.hasAttribute("saved-context")) {
+ el.setAttribute("context", el.getAttribute("saved-context"));
+ el.removeAttribute("saved-context");
+ }
+ el.removeAttribute("inFullscreen");
+ }
+ }
+
+ ToolbarIconColor.inferFromText();
+
+ // For Lion fullscreen, all fullscreen controls are hidden, don't
+ // bother to touch them. If we don't stop here, the following code
+ // could cause the native fullscreen button be shown unexpectedly.
+ // See bug 1165570.
+ if (this.useLionFullScreen) {
+ return;
+ }
+
+ var fullscreenctls = document.getElementById("window-controls");
+ var navbar = document.getElementById("nav-bar");
+ var ctlsOnTabbar = window.toolbar.visible;
+ if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) {
+ fullscreenctls.removeAttribute("flex");
+ document.getElementById("TabsToolbar").appendChild(fullscreenctls);
+ }
+ else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) {
+ fullscreenctls.setAttribute("flex", "1");
+ navbar.appendChild(fullscreenctls);
+ }
+ fullscreenctls.hidden = !aEnterFS;
+ }
+};
+XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
+ // We'll only use OS X Lion full screen if we're
+ // * on OS X
+ // * on Lion or higher (Darwin 11+)
+ // * have fullscreenbutton="true"
+ return AppConstants.isPlatformAndVersionAtLeast("macosx", 11) &&
+ document.documentElement.getAttribute("fullscreenbutton") == "true";
+});
diff --git a/browser/base/content/browser-fullZoom.js b/browser/base/content/browser-fullZoom.js
new file mode 100644
index 000000000..890cd8440
--- /dev/null
+++ b/browser/base/content/browser-fullZoom.js
@@ -0,0 +1,526 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Controls the "full zoom" setting and its site-specific preferences.
+ */
+var FullZoom = {
+ // Identifies the setting in the content prefs database.
+ name: "browser.content.full-zoom",
+
+ // browser.zoom.siteSpecific preference cache
+ _siteSpecificPref: undefined,
+
+ // browser.zoom.updateBackgroundTabs preference cache
+ updateBackgroundTabs: undefined,
+
+ // This maps the browser to monotonically increasing integer
+ // tokens. _browserTokenMap[browser] is increased each time the zoom is
+ // changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
+ _browserTokenMap: new WeakMap(),
+
+ // Stores initial locations if we receive onLocationChange
+ // events before we're initialized.
+ _initialLocations: new WeakMap(),
+
+ get siteSpecific() {
+ return this._siteSpecificPref;
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
+ Ci.nsIObserver,
+ Ci.nsIContentPrefObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+
+ // Initialization & Destruction
+
+ init: function FullZoom_init() {
+ gBrowser.addEventListener("ZoomChangeUsingMouseWheel", this);
+
+ // Register ourselves with the service so we know when our pref changes.
+ this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ this._cps2.addObserverForName(this.name, this);
+
+ this._siteSpecificPref =
+ gPrefService.getBoolPref("browser.zoom.siteSpecific");
+ this.updateBackgroundTabs =
+ gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
+ // Listen for changes to the browser.zoom branch so we can enable/disable
+ // updating background tabs and per-site saving and restoring of zoom levels.
+ gPrefService.addObserver("browser.zoom.", this, true);
+
+ // If we received onLocationChange events for any of the current browsers
+ // before we were initialized we want to replay those upon initialization.
+ for (let browser of gBrowser.browsers) {
+ if (this._initialLocations.has(browser)) {
+ this.onLocationChange(...this._initialLocations.get(browser), browser);
+ }
+ }
+
+ // This should be nulled after initialization.
+ this._initialLocations = null;
+ },
+
+ destroy: function FullZoom_destroy() {
+ gPrefService.removeObserver("browser.zoom.", this);
+ this._cps2.removeObserverForName(this.name, this);
+ gBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this);
+ },
+
+
+ // Event Handlers
+
+ // nsIDOMEventListener
+
+ handleEvent: function FullZoom_handleEvent(event) {
+ switch (event.type) {
+ case "ZoomChangeUsingMouseWheel":
+ let browser = this._getTargetedBrowser(event);
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ break;
+ }
+ },
+
+ // nsIObserver
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ switch (aData) {
+ case "browser.zoom.siteSpecific":
+ this._siteSpecificPref =
+ gPrefService.getBoolPref("browser.zoom.siteSpecific");
+ break;
+ case "browser.zoom.updateBackgroundTabs":
+ this.updateBackgroundTabs =
+ gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
+ break;
+ }
+ break;
+ }
+ },
+
+ // nsIContentPrefObserver
+
+ onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue, aIsPrivate) {
+ this._onContentPrefChanged(aGroup, aValue, aIsPrivate);
+ },
+
+ onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName, aIsPrivate) {
+ this._onContentPrefChanged(aGroup, undefined, aIsPrivate);
+ },
+
+ /**
+ * Appropriately updates the zoom level after a content preference has
+ * changed.
+ *
+ * @param aGroup The group of the changed preference.
+ * @param aValue The new value of the changed preference. Pass undefined to
+ * indicate the preference's removal.
+ */
+ _onContentPrefChanged: function FullZoom__onContentPrefChanged(aGroup, aValue, aIsPrivate) {
+ if (this._isNextContentPrefChangeInternal) {
+ // Ignore changes that FullZoom itself makes. This works because the
+ // content pref service calls callbacks before notifying observers, and it
+ // does both in the same turn of the event loop.
+ delete this._isNextContentPrefChangeInternal;
+ return;
+ }
+
+ let browser = gBrowser.selectedBrowser;
+ if (!browser.currentURI)
+ return;
+
+ let ctxt = this._loadContextFromBrowser(browser);
+ let domain = this._cps2.extractDomain(browser.currentURI.spec);
+ if (aGroup) {
+ if (aGroup == domain && ctxt.usePrivateBrowsing == aIsPrivate)
+ this._applyPrefToZoom(aValue, browser);
+ return;
+ }
+
+ this._globalValue = aValue === undefined ? aValue :
+ this._ensureValid(aValue);
+
+ // If the current page doesn't have a site-specific preference, then its
+ // zoom should be set to the new global preference now that the global
+ // preference has changed.
+ let hasPref = false;
+ let token = this._getBrowserToken(browser);
+ this._cps2.getByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
+ handleResult: function () { hasPref = true; },
+ handleCompletion: function () {
+ if (!hasPref && token.isCurrent)
+ this._applyPrefToZoom(undefined, browser);
+ }.bind(this)
+ });
+ },
+
+ // location change observer
+
+ /**
+ * Called when the location of a tab changes.
+ * When that happens, we need to update the current zoom level if appropriate.
+ *
+ * @param aURI
+ * A URI object representing the new location.
+ * @param aIsTabSwitch
+ * Whether this location change has happened because of a tab switch.
+ * @param aBrowser
+ * (optional) browser object displaying the document
+ */
+ onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) {
+ let browser = aBrowser || gBrowser.selectedBrowser;
+
+ // If we haven't been initialized yet but receive an onLocationChange
+ // notification then let's store and replay it upon initialization.
+ if (this._initialLocations) {
+ this._initialLocations.set(browser, [aURI, aIsTabSwitch]);
+ return;
+ }
+
+ // Ignore all pending async zoom accesses in the browser. Pending accesses
+ // that started before the location change will be prevented from applying
+ // to the new location.
+ this._ignorePendingZoomAccesses(browser);
+
+ if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
+ this._notifyOnLocationChange(browser);
+ return;
+ }
+
+ // Avoid the cps roundtrip and apply the default/global pref.
+ if (aURI.spec == "about:blank") {
+ this._applyPrefToZoom(undefined, browser,
+ this._notifyOnLocationChange.bind(this, browser));
+ return;
+ }
+
+ // Media documents should always start at 1, and are not affected by prefs.
+ if (!aIsTabSwitch && browser.isSyntheticDocument) {
+ ZoomManager.setZoomForBrowser(browser, 1);
+ // _ignorePendingZoomAccesses already called above, so no need here.
+ this._notifyOnLocationChange(browser);
+ return;
+ }
+
+ // See if the zoom pref is cached.
+ let ctxt = this._loadContextFromBrowser(browser);
+ let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
+ if (pref) {
+ this._applyPrefToZoom(pref.value, browser,
+ this._notifyOnLocationChange.bind(this, browser));
+ return;
+ }
+
+ // It's not cached, so we have to asynchronously fetch it.
+ let value = undefined;
+ let token = this._getBrowserToken(browser);
+ this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
+ handleResult: function (resultPref) { value = resultPref.value; },
+ handleCompletion: function () {
+ if (!token.isCurrent) {
+ this._notifyOnLocationChange(browser);
+ return;
+ }
+ this._applyPrefToZoom(value, browser,
+ this._notifyOnLocationChange.bind(this, browser));
+ }.bind(this)
+ });
+ },
+
+ // update state of zoom type menu item
+
+ updateMenu: function FullZoom_updateMenu() {
+ var menuItem = document.getElementById("toggle_zoom");
+
+ menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
+ },
+
+ // Setting & Pref Manipulation
+
+ /**
+ * Reduces the zoom level of the page in the current browser.
+ */
+ reduce: function FullZoom_reduce() {
+ ZoomManager.reduce();
+ let browser = gBrowser.selectedBrowser;
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Enlarges the zoom level of the page in the current browser.
+ */
+ enlarge: function FullZoom_enlarge() {
+ ZoomManager.enlarge();
+ let browser = gBrowser.selectedBrowser;
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Sets the zoom level for the given browser to the given floating
+ * point value, where 1 is the default zoom level.
+ */
+ setZoom: function (value, browser = gBrowser.selectedBrowser) {
+ ZoomManager.setZoomForBrowser(browser, value);
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Sets the zoom level of the page in the given browser to the global zoom
+ * level.
+ *
+ * @return A promise which resolves when the zoom reset has been applied.
+ */
+ reset: function FullZoom_reset(browser = gBrowser.selectedBrowser) {
+ let token = this._getBrowserToken(browser);
+ let result = this._getGlobalValue(browser).then(value => {
+ if (token.isCurrent) {
+ ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
+ this._ignorePendingZoomAccesses(browser);
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
+ }
+ });
+ this._removePref(browser);
+ return result;
+ },
+
+ /**
+ * Set the zoom level for a given browser.
+ *
+ * Per nsPresContext::setFullZoom, we can set the zoom to its current value
+ * without significant impact on performance, as the setting is only applied
+ * if it differs from the current setting. In fact getting the zoom and then
+ * checking ourselves if it differs costs more.
+ *
+ * And perhaps we should always set the zoom even if it was more expensive,
+ * since nsDocumentViewer::SetTextZoom claims that child documents can have
+ * a different text zoom (although it would be unusual), and it implies that
+ * those child text zooms should get updated when the parent zoom gets set,
+ * and perhaps the same is true for full zoom
+ * (although nsDocumentViewer::SetFullZoom doesn't mention it).
+ *
+ * So when we apply new zoom values to the browser, we simply set the zoom.
+ * We don't check first to see if the new value is the same as the current
+ * one.
+ *
+ * @param aValue The zoom level value.
+ * @param aBrowser The zoom is set in this browser. Required.
+ * @param aCallback If given, it's asynchronously called when complete.
+ */
+ _applyPrefToZoom: function FullZoom__applyPrefToZoom(aValue, aBrowser, aCallback) {
+ if (!this.siteSpecific || gInPrintPreviewMode) {
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ // The browser is sometimes half-destroyed because this method is called
+ // by content pref service callbacks, which themselves can be called at any
+ // time, even after browsers are closed.
+ if (!aBrowser.parentNode || aBrowser.isSyntheticDocument) {
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ if (aValue !== undefined) {
+ ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(aValue));
+ this._ignorePendingZoomAccesses(aBrowser);
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ let token = this._getBrowserToken(aBrowser);
+ this._getGlobalValue(aBrowser).then(value => {
+ if (token.isCurrent) {
+ ZoomManager.setZoomForBrowser(aBrowser, value === undefined ? 1 : value);
+ this._ignorePendingZoomAccesses(aBrowser);
+ }
+ this._executeSoon(aCallback);
+ });
+ },
+
+ /**
+ * Saves the zoom level of the page in the given browser to the content
+ * prefs store.
+ *
+ * @param browser The zoom of this browser will be saved. Required.
+ */
+ _applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomChange", "");
+ if (!this.siteSpecific ||
+ gInPrintPreviewMode ||
+ browser.isSyntheticDocument)
+ return;
+
+ this._cps2.set(browser.currentURI.spec, this.name,
+ ZoomManager.getZoomForBrowser(browser),
+ this._loadContextFromBrowser(browser), {
+ handleCompletion: function () {
+ this._isNextContentPrefChangeInternal = true;
+ }.bind(this),
+ });
+ },
+
+ /**
+ * Removes from the content prefs store the zoom level of the given browser.
+ *
+ * @param browser The zoom of this browser will be removed. Required.
+ */
+ _removePref: function FullZoom__removePref(browser) {
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
+ if (browser.isSyntheticDocument)
+ return;
+ let ctxt = this._loadContextFromBrowser(browser);
+ this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
+ handleCompletion: function () {
+ this._isNextContentPrefChangeInternal = true;
+ }.bind(this),
+ });
+ },
+
+ // Utilities
+
+ /**
+ * Returns the zoom change token of the given browser. Asynchronous
+ * operations that access the given browser's zoom should use this method to
+ * capture the token before starting and use token.isCurrent to determine if
+ * it's safe to access the zoom when done. If token.isCurrent is false, then
+ * after the async operation started, either the browser's zoom was changed or
+ * the browser was destroyed, and depending on what the operation is doing, it
+ * may no longer be safe to set and get its zoom.
+ *
+ * @param browser The token of this browser will be returned.
+ * @return An object with an "isCurrent" getter.
+ */
+ _getBrowserToken: function FullZoom__getBrowserToken(browser) {
+ let map = this._browserTokenMap;
+ if (!map.has(browser))
+ map.set(browser, 0);
+ return {
+ token: map.get(browser),
+ get isCurrent() {
+ // At this point, the browser may have been destructed and unbound but
+ // its outer ID not removed from the map because outer-window-destroyed
+ // hasn't been received yet. In that case, the browser is unusable, it
+ // has no properties, so return false. Check for this case by getting a
+ // property, say, docShell.
+ return map.get(browser) === this.token && browser.parentNode;
+ },
+ };
+ },
+
+ /**
+ * Returns the browser that the supplied zoom event is associated with.
+ * @param event The ZoomChangeUsingMouseWheel event.
+ * @return The associated browser element, if one exists, otherwise null.
+ */
+ _getTargetedBrowser: function FullZoom__getTargetedBrowser(event) {
+ let target = event.originalTarget;
+
+ // With remote content browsers, the event's target is the browser
+ // we're looking for.
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ if (target instanceof window.XULElement &&
+ target.localName == "browser" &&
+ target.namespaceURI == XUL_NS)
+ return target;
+
+ // With in-process content browsers, the event's target is the content
+ // document.
+ if (target.nodeType == Node.DOCUMENT_NODE)
+ return gBrowser.getBrowserForDocument(target);
+
+ throw new Error("Unexpected ZoomChangeUsingMouseWheel event source");
+ },
+
+ /**
+ * Increments the zoom change token for the given browser so that pending
+ * async operations know that it may be unsafe to access they zoom when they
+ * finish.
+ *
+ * @param browser Pending accesses in this browser will be ignored.
+ */
+ _ignorePendingZoomAccesses: function FullZoom__ignorePendingZoomAccesses(browser) {
+ let map = this._browserTokenMap;
+ map.set(browser, (map.get(browser) || 0) + 1);
+ },
+
+ _ensureValid: function FullZoom__ensureValid(aValue) {
+ // Note that undefined is a valid value for aValue that indicates a known-
+ // not-to-exist value.
+ if (isNaN(aValue))
+ return 1;
+
+ if (aValue < ZoomManager.MIN)
+ return ZoomManager.MIN;
+
+ if (aValue > ZoomManager.MAX)
+ return ZoomManager.MAX;
+
+ return aValue;
+ },
+
+ /**
+ * Gets the global browser.content.full-zoom content preference.
+ *
+ * @param browser The browser pertaining to the zoom.
+ * @returns Promise<prefValue>
+ * Resolves to the preference value when done.
+ */
+ _getGlobalValue: function FullZoom__getGlobalValue(browser) {
+ // * !("_globalValue" in this) => global value not yet cached.
+ // * this._globalValue === undefined => global value known not to exist.
+ // * Otherwise, this._globalValue is a number, the global value.
+ return new Promise(resolve => {
+ if ("_globalValue" in this) {
+ resolve(this._globalValue);
+ return;
+ }
+ let value = undefined;
+ this._cps2.getGlobal(this.name, this._loadContextFromBrowser(browser), {
+ handleResult: function (pref) { value = pref.value; },
+ handleCompletion: (reason) => {
+ this._globalValue = this._ensureValid(value);
+ resolve(this._globalValue);
+ }
+ });
+ });
+ },
+
+ /**
+ * Gets the load context from the given Browser.
+ *
+ * @param Browser The Browser whose load context will be returned.
+ * @return The nsILoadContext of the given Browser.
+ */
+ _loadContextFromBrowser: function FullZoom__loadContextFromBrowser(browser) {
+ return browser.loadContext;
+ },
+
+ /**
+ * Asynchronously broadcasts "browser-fullZoom:location-change" so that
+ * listeners can be notified when the zoom levels on those pages change.
+ * The notification is always asynchronous so that observers are guaranteed a
+ * consistent behavior.
+ */
+ _notifyOnLocationChange: function FullZoom__notifyOnLocationChange(browser) {
+ this._executeSoon(function () {
+ Services.obs.notifyObservers(browser, "browser-fullZoom:location-change", "");
+ });
+ },
+
+ _executeSoon: function FullZoom__executeSoon(callback) {
+ if (!callback)
+ return;
+ Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+};
diff --git a/browser/base/content/browser-fxaccounts.js b/browser/base/content/browser-fxaccounts.js
new file mode 100644
index 000000000..0bbce3e26
--- /dev/null
+++ b/browser/base/content/browser-fxaccounts.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/. */
+
+var gFxAccounts = {
+
+ SYNC_MIGRATION_NOTIFICATION_TITLE: "fxa-migration",
+
+ _initialized: false,
+ _inCustomizationMode: false,
+ _cachedProfile: null,
+
+ get weave() {
+ delete this.weave;
+ return this.weave = Cc["@mozilla.org/weave/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject;
+ },
+
+ get topics() {
+ // Do all this dance to lazy-load FxAccountsCommon.
+ delete this.topics;
+ return this.topics = [
+ "weave:service:ready",
+ "weave:service:login:change",
+ "weave:service:setup-complete",
+ "weave:service:sync:error",
+ "weave:ui:login:error",
+ "fxa-migration:state-changed",
+ this.FxAccountsCommon.ONLOGIN_NOTIFICATION,
+ this.FxAccountsCommon.ONLOGOUT_NOTIFICATION,
+ this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
+ ];
+ },
+
+ get panelUIFooter() {
+ delete this.panelUIFooter;
+ return this.panelUIFooter = document.getElementById("PanelUI-footer-fxa");
+ },
+
+ get panelUIStatus() {
+ delete this.panelUIStatus;
+ return this.panelUIStatus = document.getElementById("PanelUI-fxa-status");
+ },
+
+ get panelUIAvatar() {
+ delete this.panelUIAvatar;
+ return this.panelUIAvatar = document.getElementById("PanelUI-fxa-avatar");
+ },
+
+ get panelUILabel() {
+ delete this.panelUILabel;
+ return this.panelUILabel = document.getElementById("PanelUI-fxa-label");
+ },
+
+ get panelUIIcon() {
+ delete this.panelUIIcon;
+ return this.panelUIIcon = document.getElementById("PanelUI-fxa-icon");
+ },
+
+ get strings() {
+ delete this.strings;
+ return this.strings = Services.strings.createBundle(
+ "chrome://browser/locale/accounts.properties"
+ );
+ },
+
+ get loginFailed() {
+ // Referencing Weave.Service will implicitly initialize sync, and we don't
+ // want to force that - so first check if it is ready.
+ let service = Cc["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ if (!service.ready) {
+ return false;
+ }
+ // LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
+ // All other login failures are assumed to be transient and should go
+ // away by themselves, so aren't reflected here.
+ return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+ },
+
+ get sendTabToDeviceEnabled() {
+ return Services.prefs.getBoolPref("services.sync.sendTabToDevice.enabled");
+ },
+
+ get remoteClients() {
+ return Weave.Service.clientsEngine.remoteClients
+ .sort((a, b) => a.name.localeCompare(b.name));
+ },
+
+ init: function () {
+ // Bail out if we're already initialized and for pop-up windows.
+ if (this._initialized || !window.toolbar.visible) {
+ return;
+ }
+
+ for (let topic of this.topics) {
+ Services.obs.addObserver(this, topic, false);
+ }
+
+ gNavToolbox.addEventListener("customizationstarting", this);
+ gNavToolbox.addEventListener("customizationending", this);
+
+ EnsureFxAccountsWebChannel();
+ this._initialized = true;
+
+ this.updateUI();
+ },
+
+ uninit: function () {
+ if (!this._initialized) {
+ return;
+ }
+
+ for (let topic of this.topics) {
+ Services.obs.removeObserver(this, topic);
+ }
+
+ this._initialized = false;
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case "fxa-migration:state-changed":
+ this.onMigrationStateChanged(data, subject);
+ break;
+ case this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION:
+ this._cachedProfile = null;
+ // Fallthrough intended
+ default:
+ this.updateUI();
+ break;
+ }
+ },
+
+ onMigrationStateChanged: function () {
+ // Since we nuked most of the migration code, this notification will fire
+ // once after legacy Sync has been disconnected (and should never fire
+ // again)
+ let nb = window.document.getElementById("global-notificationbox");
+
+ let msg = this.strings.GetStringFromName("autoDisconnectDescription")
+ let signInLabel = this.strings.GetStringFromName("autoDisconnectSignIn.label");
+ let signInAccessKey = this.strings.GetStringFromName("autoDisconnectSignIn.accessKey");
+ let learnMoreLink = this.fxaMigrator.learnMoreLink;
+
+ let buttons = [
+ {
+ label: signInLabel,
+ accessKey: signInAccessKey,
+ callback: () => {
+ this.openPreferences();
+ }
+ }
+ ];
+
+ let fragment = document.createDocumentFragment();
+ let msgNode = document.createTextNode(msg);
+ fragment.appendChild(msgNode);
+ if (learnMoreLink) {
+ let link = document.createElement("label");
+ link.className = "text-link";
+ link.setAttribute("value", learnMoreLink.text);
+ link.href = learnMoreLink.href;
+ fragment.appendChild(link);
+ }
+
+ nb.appendNotification(fragment,
+ this.SYNC_MIGRATION_NOTIFICATION_TITLE,
+ undefined,
+ nb.PRIORITY_WARNING_LOW,
+ buttons);
+
+ // ensure the hamburger menu reflects the newly disconnected state.
+ this.updateAppMenuItem();
+ },
+
+ handleEvent: function (event) {
+ this._inCustomizationMode = event.type == "customizationstarting";
+ this.updateAppMenuItem();
+ },
+
+ updateUI: function () {
+ // It's possible someone signed in to FxA after seeing our notification
+ // about "Legacy Sync migration" (which now is actually "Legacy Sync
+ // auto-disconnect") so kill that notification if it still exists.
+ let nb = window.document.getElementById("global-notificationbox");
+ let n = nb.getNotificationWithValue(this.SYNC_MIGRATION_NOTIFICATION_TITLE);
+ if (n) {
+ nb.removeNotification(n, true);
+ }
+
+ this.updateAppMenuItem();
+ },
+
+ // Note that updateAppMenuItem() returns a Promise that's only used by tests.
+ updateAppMenuItem: function () {
+ let profileInfoEnabled = false;
+ try {
+ profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
+ } catch (e) { }
+
+ // Bail out if FxA is disabled.
+ if (!this.weave.fxAccountsEnabled) {
+ return Promise.resolve();
+ }
+
+ this.panelUIFooter.hidden = false;
+
+ // Make sure the button is disabled in customization mode.
+ if (this._inCustomizationMode) {
+ this.panelUIStatus.setAttribute("disabled", "true");
+ this.panelUILabel.setAttribute("disabled", "true");
+ this.panelUIAvatar.setAttribute("disabled", "true");
+ this.panelUIIcon.setAttribute("disabled", "true");
+ } else {
+ this.panelUIStatus.removeAttribute("disabled");
+ this.panelUILabel.removeAttribute("disabled");
+ this.panelUIAvatar.removeAttribute("disabled");
+ this.panelUIIcon.removeAttribute("disabled");
+ }
+
+ let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
+ let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
+ let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
+ // The localization string is for the signed in text, but it's the default text as well
+ let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
+
+ let updateWithUserData = (userData) => {
+ // Window might have been closed while fetching data.
+ if (window.closed) {
+ return;
+ }
+
+ // Reset the button to its original state.
+ this.panelUILabel.setAttribute("label", defaultLabel);
+ this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
+ this.panelUIFooter.removeAttribute("fxastatus");
+ this.panelUIFooter.removeAttribute("fxaprofileimage");
+ this.panelUIAvatar.style.removeProperty("list-style-image");
+ let showErrorBadge = false;
+ if (userData) {
+ // At this point we consider the user as logged-in (but still can be in an error state)
+ if (this.loginFailed) {
+ let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [userData.email], 1);
+ this.panelUIFooter.setAttribute("fxastatus", "error");
+ this.panelUILabel.setAttribute("label", errorLabel);
+ this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
+ showErrorBadge = true;
+ } else if (!userData.verified) {
+ let tooltipDescription = this.strings.formatStringFromName("verifyDescription", [userData.email], 1);
+ this.panelUIFooter.setAttribute("fxastatus", "error");
+ this.panelUIFooter.setAttribute("unverified", "true");
+ this.panelUILabel.setAttribute("label", unverifiedLabel);
+ this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
+ showErrorBadge = true;
+ } else {
+ this.panelUIFooter.setAttribute("fxastatus", "signedin");
+ this.panelUILabel.setAttribute("label", userData.email);
+ }
+ if (profileInfoEnabled) {
+ this.panelUIFooter.setAttribute("fxaprofileimage", "enabled");
+ }
+ }
+ if (showErrorBadge) {
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
+ } else {
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
+ }
+ }
+
+ let updateWithProfile = (profile) => {
+ if (profileInfoEnabled) {
+ if (profile.displayName) {
+ this.panelUILabel.setAttribute("label", profile.displayName);
+ }
+ if (profile.avatar) {
+ this.panelUIFooter.setAttribute("fxaprofileimage", "set");
+ let bgImage = "url(\"" + profile.avatar + "\")";
+ this.panelUIAvatar.style.listStyleImage = bgImage;
+
+ let img = new Image();
+ img.onerror = () => {
+ // Clear the image if it has trouble loading. Since this callback is asynchronous
+ // we check to make sure the image is still the same before we clear it.
+ if (this.panelUIAvatar.style.listStyleImage === bgImage) {
+ this.panelUIFooter.removeAttribute("fxaprofileimage");
+ this.panelUIAvatar.style.removeProperty("list-style-image");
+ }
+ };
+ img.src = profile.avatar;
+ }
+ }
+ }
+
+ return fxAccounts.getSignedInUser().then(userData => {
+ // userData may be null here when the user is not signed-in, but that's expected
+ updateWithUserData(userData);
+ // unverified users cause us to spew log errors fetching an OAuth token
+ // to fetch the profile, so don't even try in that case.
+ if (!userData || !userData.verified || !profileInfoEnabled) {
+ return null; // don't even try to grab the profile.
+ }
+ if (this._cachedProfile) {
+ return this._cachedProfile;
+ }
+ return fxAccounts.getSignedInUserProfile().catch(err => {
+ // Not fetching the profile is sad but the FxA logs will already have noise.
+ return null;
+ });
+ }).then(profile => {
+ if (!profile) {
+ return;
+ }
+ updateWithProfile(profile);
+ this._cachedProfile = profile; // Try to avoid fetching the profile on every UI update
+ }).catch(error => {
+ // This is most likely in tests, were we quickly log users in and out.
+ // The most likely scenario is a user logged out, so reflect that.
+ // Bug 995134 calls for better errors so we could retry if we were
+ // sure this was the failure reason.
+ this.FxAccountsCommon.log.error("Error updating FxA account info", error);
+ updateWithUserData(null);
+ });
+ },
+
+ onMenuPanelCommand: function () {
+
+ switch (this.panelUIFooter.getAttribute("fxastatus")) {
+ case "signedin":
+ this.openPreferences();
+ break;
+ case "error":
+ if (this.panelUIFooter.getAttribute("unverified")) {
+ this.openPreferences();
+ } else {
+ this.openSignInAgainPage("menupanel");
+ }
+ break;
+ default:
+ this.openPreferences();
+ break;
+ }
+
+ PanelUI.hide();
+ },
+
+ openPreferences: function () {
+ openPreferences("paneSync", { urlParams: { entrypoint: "menupanel" } });
+ },
+
+ openAccountsPage: function (action, urlParams={}) {
+ let params = new URLSearchParams();
+ if (action) {
+ params.set("action", action);
+ }
+ for (let name in urlParams) {
+ if (urlParams[name] !== undefined) {
+ params.set(name, urlParams[name]);
+ }
+ }
+ let url = "about:accounts?" + params;
+ switchToTabHavingURI(url, true, {
+ replaceQueryString: true
+ });
+ },
+
+ openSignInAgainPage: function (entryPoint) {
+ this.openAccountsPage("reauth", { entrypoint: entryPoint });
+ },
+
+ sendTabToDevice: function (url, clientId, title) {
+ Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
+ },
+
+ populateSendTabToDevicesMenu: function (devicesPopup, url, title) {
+ // remove existing menu items
+ while (devicesPopup.hasChildNodes()) {
+ devicesPopup.removeChild(devicesPopup.firstChild);
+ }
+
+ const fragment = document.createDocumentFragment();
+
+ const onTargetDeviceCommand = (event) => {
+ const clientId = event.target.getAttribute("clientId");
+ const clients = clientId
+ ? [clientId]
+ : this.remoteClients.map(client => client.id);
+
+ clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
+ }
+
+ function addTargetDevice(clientId, name) {
+ const targetDevice = document.createElement("menuitem");
+ targetDevice.addEventListener("command", onTargetDeviceCommand, true);
+ targetDevice.setAttribute("class", "sendtab-target");
+ targetDevice.setAttribute("clientId", clientId);
+ targetDevice.setAttribute("label", name);
+ fragment.appendChild(targetDevice);
+ }
+
+ const clients = this.remoteClients;
+ for (let client of clients) {
+ addTargetDevice(client.id, client.name);
+ }
+
+ // "All devices" menu item
+ if (clients.length > 1) {
+ const separator = document.createElement("menuseparator");
+ fragment.appendChild(separator);
+ const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
+ addTargetDevice("", allDevicesLabel);
+ }
+
+ devicesPopup.appendChild(fragment);
+ },
+
+ updateTabContextMenu: function (aPopupMenu) {
+ if (!this.sendTabToDeviceEnabled) {
+ return;
+ }
+
+ const remoteClientPresent = this.remoteClients.length > 0;
+ ["context_sendTabToDevice", "context_sendTabToDevice_separator"]
+ .forEach(id => { document.getElementById(id).hidden = !remoteClientPresent });
+ },
+
+ initPageContextMenu: function (contextMenu) {
+ if (!this.sendTabToDeviceEnabled) {
+ return;
+ }
+
+ const remoteClientPresent = this.remoteClients.length > 0;
+ // showSendLink and showSendPage are mutually exclusive
+ const showSendLink = remoteClientPresent
+ && (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
+ const showSendPage = !showSendLink && remoteClientPresent
+ && !(contextMenu.isContentSelected ||
+ contextMenu.onImage || contextMenu.onCanvas ||
+ contextMenu.onVideo || contextMenu.onAudio ||
+ contextMenu.onLink || contextMenu.onTextInput);
+
+ ["context-sendpagetodevice", "context-sep-sendpagetodevice"]
+ .forEach(id => contextMenu.showItem(id, showSendPage));
+ ["context-sendlinktodevice", "context-sep-sendlinktodevice"]
+ .forEach(id => contextMenu.showItem(id, showSendLink));
+ }
+};
+
+XPCOMUtils.defineLazyGetter(gFxAccounts, "FxAccountsCommon", function () {
+ return Cu.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+XPCOMUtils.defineLazyModuleGetter(gFxAccounts, "fxaMigrator",
+ "resource://services-sync/FxaMigrator.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
+ "resource://gre/modules/FxAccountsWebChannel.jsm");
diff --git a/browser/base/content/browser-gestureSupport.js b/browser/base/content/browser-gestureSupport.js
new file mode 100644
index 000000000..f472e5c9a
--- /dev/null
+++ b/browser/base/content/browser-gestureSupport.js
@@ -0,0 +1,1244 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Simple gestures support
+//
+// As per bug #412486, web content must not be allowed to receive any
+// simple gesture events. Multi-touch gesture APIs are in their
+// infancy and we do NOT want to be forced into supporting an API that
+// will probably have to change in the future. (The current Mac OS X
+// API is undocumented and was reverse-engineered.) Until support is
+// implemented in the event dispatcher to keep these events as
+// chrome-only, we must listen for the simple gesture events during
+// the capturing phase and call stopPropagation on every event.
+
+var gGestureSupport = {
+ _currentRotation: 0,
+ _lastRotateDelta: 0,
+ _rotateMomentumThreshold: .75,
+
+ /**
+ * Add or remove mouse gesture event listeners
+ *
+ * @param aAddListener
+ * True to add/init listeners and false to remove/uninit
+ */
+ init: function GS_init(aAddListener) {
+ const gestureEvents = ["SwipeGestureMayStart", "SwipeGestureStart",
+ "SwipeGestureUpdate", "SwipeGestureEnd", "SwipeGesture",
+ "MagnifyGestureStart", "MagnifyGestureUpdate", "MagnifyGesture",
+ "RotateGestureStart", "RotateGestureUpdate", "RotateGesture",
+ "TapGesture", "PressTapGesture"];
+
+ let addRemove = aAddListener ? window.addEventListener :
+ window.removeEventListener;
+
+ for (let event of gestureEvents) {
+ addRemove("Moz" + event, this, true);
+ }
+ },
+
+ /**
+ * Dispatch events based on the type of mouse gesture event. For now, make
+ * sure to stop propagation of every gesture event so that web content cannot
+ * receive gesture events.
+ *
+ * @param aEvent
+ * The gesture event to handle
+ */
+ handleEvent: function GS_handleEvent(aEvent) {
+ if (!Services.prefs.getBoolPref(
+ "dom.debug.propagate_gesture_events_through_content")) {
+ aEvent.stopPropagation();
+ }
+
+ // Create a preference object with some defaults
+ let def = (aThreshold, aLatched) =>
+ ({ threshold: aThreshold, latched: !!aLatched });
+
+ switch (aEvent.type) {
+ case "MozSwipeGestureMayStart":
+ if (this._shouldDoSwipeGesture(aEvent)) {
+ aEvent.preventDefault();
+ }
+ break;
+ case "MozSwipeGestureStart":
+ aEvent.preventDefault();
+ this._setupSwipeGesture();
+ break;
+ case "MozSwipeGestureUpdate":
+ aEvent.preventDefault();
+ this._doUpdate(aEvent);
+ break;
+ case "MozSwipeGestureEnd":
+ aEvent.preventDefault();
+ this._doEnd(aEvent);
+ break;
+ case "MozSwipeGesture":
+ aEvent.preventDefault();
+ this.onSwipe(aEvent);
+ break;
+ case "MozMagnifyGestureStart":
+ aEvent.preventDefault();
+ let pinchPref = AppConstants.platform == "win"
+ ? def(25, 0)
+ : def(150, 1);
+ this._setupGesture(aEvent, "pinch", pinchPref, "out", "in");
+ break;
+ case "MozRotateGestureStart":
+ aEvent.preventDefault();
+ this._setupGesture(aEvent, "twist", def(25, 0), "right", "left");
+ break;
+ case "MozMagnifyGestureUpdate":
+ case "MozRotateGestureUpdate":
+ aEvent.preventDefault();
+ this._doUpdate(aEvent);
+ break;
+ case "MozTapGesture":
+ aEvent.preventDefault();
+ this._doAction(aEvent, ["tap"]);
+ break;
+ case "MozRotateGesture":
+ aEvent.preventDefault();
+ this._doAction(aEvent, ["twist", "end"]);
+ break;
+ /* case "MozPressTapGesture":
+ break; */
+ }
+ },
+
+ /**
+ * Called at the start of "pinch" and "twist" gestures to setup all of the
+ * information needed to process the gesture
+ *
+ * @param aEvent
+ * The continual motion start event to handle
+ * @param aGesture
+ * Name of the gesture to handle
+ * @param aPref
+ * Preference object with the names of preferences and defaults
+ * @param aInc
+ * Command to trigger for increasing motion (without gesture name)
+ * @param aDec
+ * Command to trigger for decreasing motion (without gesture name)
+ */
+ _setupGesture: function GS__setupGesture(aEvent, aGesture, aPref, aInc, aDec) {
+ // Try to load user-set values from preferences
+ for (let [pref, def] of Object.entries(aPref))
+ aPref[pref] = this._getPref(aGesture + "." + pref, def);
+
+ // Keep track of the total deltas and latching behavior
+ let offset = 0;
+ let latchDir = aEvent.delta > 0 ? 1 : -1;
+ let isLatched = false;
+
+ // Create the update function here to capture closure state
+ this._doUpdate = function GS__doUpdate(aEvent) {
+ // Update the offset with new event data
+ offset += aEvent.delta;
+
+ // Check if the cumulative deltas exceed the threshold
+ if (Math.abs(offset) > aPref["threshold"]) {
+ // Trigger the action if we don't care about latching; otherwise, make
+ // sure either we're not latched and going the same direction of the
+ // initial motion; or we're latched and going the opposite way
+ let sameDir = (latchDir ^ offset) >= 0;
+ if (!aPref["latched"] || (isLatched ^ sameDir)) {
+ this._doAction(aEvent, [aGesture, offset > 0 ? aInc : aDec]);
+
+ // We must be getting latched or leaving it, so just toggle
+ isLatched = !isLatched;
+ }
+
+ // Reset motion counter to prepare for more of the same gesture
+ offset = 0;
+ }
+ };
+
+ // The start event also contains deltas, so handle an update right away
+ this._doUpdate(aEvent);
+ },
+
+ /**
+ * Checks whether a swipe gesture event can navigate the browser history or
+ * not.
+ *
+ * @param aEvent
+ * The swipe gesture event.
+ * @return true if the swipe event may navigate the history, false othwerwise.
+ */
+ _swipeNavigatesHistory: function GS__swipeNavigatesHistory(aEvent) {
+ return this._getCommand(aEvent, ["swipe", "left"])
+ == "Browser:BackOrBackDuplicate" &&
+ this._getCommand(aEvent, ["swipe", "right"])
+ == "Browser:ForwardOrForwardDuplicate";
+ },
+
+ /**
+ * Checks whether we want to start a swipe for aEvent and sets
+ * aEvent.allowedDirections to the right values.
+ *
+ * @param aEvent
+ * The swipe gesture "MayStart" event.
+ * @return true if we're willing to start a swipe for this event, false
+ * otherwise.
+ */
+ _shouldDoSwipeGesture: function GS__shouldDoSwipeGesture(aEvent) {
+ if (!this._swipeNavigatesHistory(aEvent)) {
+ return false;
+ }
+
+ let isVerticalSwipe = false;
+ if (aEvent.direction == aEvent.DIRECTION_UP) {
+ if (gMultiProcessBrowser || content.pageYOffset > 0) {
+ return false;
+ }
+ isVerticalSwipe = true;
+ } else if (aEvent.direction == aEvent.DIRECTION_DOWN) {
+ if (gMultiProcessBrowser || content.pageYOffset < content.scrollMaxY) {
+ return false;
+ }
+ isVerticalSwipe = true;
+ }
+ if (isVerticalSwipe) {
+ // Vertical overscroll has been temporarily disabled until bug 939480 is
+ // fixed.
+ return false;
+ }
+
+ let canGoBack = gHistorySwipeAnimation.canGoBack();
+ let canGoForward = gHistorySwipeAnimation.canGoForward();
+ let isLTR = gHistorySwipeAnimation.isLTR;
+
+ if (canGoBack) {
+ aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_LEFT :
+ aEvent.DIRECTION_RIGHT;
+ }
+ if (canGoForward) {
+ aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_RIGHT :
+ aEvent.DIRECTION_LEFT;
+ }
+
+ return true;
+ },
+
+ /**
+ * Sets up swipe gestures. This includes setting up swipe animations for the
+ * gesture, if enabled.
+ *
+ * @param aEvent
+ * The swipe gesture start event.
+ * @return true if swipe gestures could successfully be set up, false
+ * othwerwise.
+ */
+ _setupSwipeGesture: function GS__setupSwipeGesture() {
+ gHistorySwipeAnimation.startAnimation(false);
+
+ this._doUpdate = function GS__doUpdate(aEvent) {
+ gHistorySwipeAnimation.updateAnimation(aEvent.delta);
+ };
+
+ this._doEnd = function GS__doEnd(aEvent) {
+ gHistorySwipeAnimation.swipeEndEventReceived();
+
+ this._doUpdate = function (aEvent) {};
+ this._doEnd = function (aEvent) {};
+ }
+ },
+
+ /**
+ * Generator producing the powerset of the input array where the first result
+ * is the complete set and the last result (before StopIteration) is empty.
+ *
+ * @param aArray
+ * Source array containing any number of elements
+ * @yield Array that is a subset of the input array from full set to empty
+ */
+ _power: function* GS__power(aArray) {
+ // Create a bitmask based on the length of the array
+ let num = 1 << aArray.length;
+ while (--num >= 0) {
+ // Only select array elements where the current bit is set
+ yield aArray.reduce(function (aPrev, aCurr, aIndex) {
+ if (num & 1 << aIndex)
+ aPrev.push(aCurr);
+ return aPrev;
+ }, []);
+ }
+ },
+
+ /**
+ * Determine what action to do for the gesture based on which keys are
+ * pressed and which commands are set, and execute the command.
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aGesture
+ * Array of gesture name parts (to be joined by periods)
+ * @return Name of the executed command. Returns null if no command is
+ * found.
+ */
+ _doAction: function GS__doAction(aEvent, aGesture) {
+ let command = this._getCommand(aEvent, aGesture);
+ return command && this._doCommand(aEvent, command);
+ },
+
+ /**
+ * Determine what action to do for the gesture based on which keys are
+ * pressed and which commands are set
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aGesture
+ * Array of gesture name parts (to be joined by periods)
+ */
+ _getCommand: function GS__getCommand(aEvent, aGesture) {
+ // Create an array of pressed keys in a fixed order so that a command for
+ // "meta" is preferred over "ctrl" when both buttons are pressed (and a
+ // command for both don't exist)
+ let keyCombos = [];
+ for (let key of ["shift", "alt", "ctrl", "meta"]) {
+ if (aEvent[key + "Key"])
+ keyCombos.push(key);
+ }
+
+ // Try each combination of key presses in decreasing order for commands
+ for (let subCombo of this._power(keyCombos)) {
+ // Convert a gesture and pressed keys into the corresponding command
+ // action where the preference has the gesture before "shift" before
+ // "alt" before "ctrl" before "meta" all separated by periods
+ let command;
+ try {
+ command = this._getPref(aGesture.concat(subCombo).join("."));
+ } catch (e) {}
+
+ if (command)
+ return command;
+ }
+ return null;
+ },
+
+ /**
+ * Execute the specified command.
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aCommand
+ * Name of the command found for the event's keys and gesture.
+ */
+ _doCommand: function GS__doCommand(aEvent, aCommand) {
+ let node = document.getElementById(aCommand);
+ if (node) {
+ if (node.getAttribute("disabled") != "true") {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey,
+ aEvent.shiftKey, aEvent.metaKey, aEvent);
+ node.dispatchEvent(cmdEvent);
+ }
+
+ }
+ else {
+ goDoCommand(aCommand);
+ }
+ },
+
+ /**
+ * Handle continual motion events. This function will be set by
+ * _setupGesture or _setupSwipe.
+ *
+ * @param aEvent
+ * The continual motion update event to handle
+ */
+ _doUpdate: function(aEvent) {},
+
+ /**
+ * Handle gesture end events. This function will be set by _setupSwipe.
+ *
+ * @param aEvent
+ * The gesture end event to handle
+ */
+ _doEnd: function(aEvent) {},
+
+ /**
+ * Convert the swipe gesture into a browser action based on the direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ */
+ onSwipe: function GS_onSwipe(aEvent) {
+ // Figure out which one (and only one) direction was triggered
+ for (let dir of ["UP", "RIGHT", "DOWN", "LEFT"]) {
+ if (aEvent.direction == aEvent["DIRECTION_" + dir]) {
+ this._coordinateSwipeEventWithAnimation(aEvent, dir);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Process a swipe event based on the given direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ processSwipeEvent: function GS_processSwipeEvent(aEvent, aDir) {
+ this._doAction(aEvent, ["swipe", aDir.toLowerCase()]);
+ },
+
+ /**
+ * Coordinates the swipe event with the swipe animation, if any.
+ * If an animation is currently running, the swipe event will be
+ * processed once the animation stops. This will guarantee a fluid
+ * motion of the animation.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ _coordinateSwipeEventWithAnimation:
+ function GS__coordinateSwipeEventWithAnimation(aEvent, aDir) {
+ if ((gHistorySwipeAnimation.isAnimationRunning()) &&
+ (aDir == "RIGHT" || aDir == "LEFT")) {
+ gHistorySwipeAnimation.processSwipeEvent(aEvent, aDir);
+ }
+ else {
+ this.processSwipeEvent(aEvent, aDir);
+ }
+ },
+
+ /**
+ * Get a gesture preference or use a default if it doesn't exist
+ *
+ * @param aPref
+ * Name of the preference to load under the gesture branch
+ * @param aDef
+ * Default value if the preference doesn't exist
+ */
+ _getPref: function GS__getPref(aPref, aDef) {
+ // Preferences branch under which all gestures preferences are stored
+ const branch = "browser.gesture.";
+
+ try {
+ // Determine what type of data to load based on default value's type
+ let type = typeof aDef;
+ let getFunc = "Char";
+ if (type == "boolean")
+ getFunc = "Bool";
+ else if (type == "number")
+ getFunc = "Int";
+ return gPrefService["get" + getFunc + "Pref"](branch + aPref);
+ }
+ catch (e) {
+ return aDef;
+ }
+ },
+
+ /**
+ * Perform rotation for ImageDocuments
+ *
+ * @param aEvent
+ * The MozRotateGestureUpdate event triggering this call
+ */
+ rotate: function(aEvent) {
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+ // If we're currently snapping, cancel that snap
+ if (contentElement.classList.contains("completeRotation"))
+ this._clearCompleteRotation();
+
+ this.rotation = Math.round(this.rotation + aEvent.delta);
+ contentElement.style.transform = "rotate(" + this.rotation + "deg)";
+ this._lastRotateDelta = aEvent.delta;
+ },
+
+ /**
+ * Perform a rotation end for ImageDocuments
+ */
+ rotateEnd: function() {
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+
+ let transitionRotation = 0;
+
+ // The reason that 360 is allowed here is because when rotating between
+ // 315 and 360, setting rotate(0deg) will cause it to rotate the wrong
+ // direction around--spinning wildly.
+ if (this.rotation <= 45)
+ transitionRotation = 0;
+ else if (this.rotation > 45 && this.rotation <= 135)
+ transitionRotation = 90;
+ else if (this.rotation > 135 && this.rotation <= 225)
+ transitionRotation = 180;
+ else if (this.rotation > 225 && this.rotation <= 315)
+ transitionRotation = 270;
+ else
+ transitionRotation = 360;
+
+ // If we're going fast enough, and we didn't already snap ahead of rotation,
+ // then snap ahead of rotation to simulate momentum
+ if (this._lastRotateDelta > this._rotateMomentumThreshold &&
+ this.rotation > transitionRotation)
+ transitionRotation += 90;
+ else if (this._lastRotateDelta < -1 * this._rotateMomentumThreshold &&
+ this.rotation < transitionRotation)
+ transitionRotation -= 90;
+
+ // Only add the completeRotation class if it is is necessary
+ if (transitionRotation != this.rotation) {
+ contentElement.classList.add("completeRotation");
+ contentElement.addEventListener("transitionend", this._clearCompleteRotation);
+ }
+
+ contentElement.style.transform = "rotate(" + transitionRotation + "deg)";
+ this.rotation = transitionRotation;
+ },
+
+ /**
+ * Gets the current rotation for the ImageDocument
+ */
+ get rotation() {
+ return this._currentRotation;
+ },
+
+ /**
+ * Sets the current rotation for the ImageDocument
+ *
+ * @param aVal
+ * The new value to take. Can be any value, but it will be bounded to
+ * 0 inclusive to 360 exclusive.
+ */
+ set rotation(aVal) {
+ this._currentRotation = aVal % 360;
+ if (this._currentRotation < 0)
+ this._currentRotation += 360;
+ return this._currentRotation;
+ },
+
+ /**
+ * When the location/tab changes, need to reload the current rotation for the
+ * image
+ */
+ restoreRotationState: function() {
+ // Bug 863514 - Make gesture support work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ let transformValue = content.window.getComputedStyle(contentElement, null)
+ .transform;
+
+ if (transformValue == "none") {
+ this.rotation = 0;
+ return;
+ }
+
+ // transformValue is a rotation matrix--split it and do mathemagic to
+ // obtain the real rotation value
+ transformValue = transformValue.split("(")[1]
+ .split(")")[0]
+ .split(",");
+ this.rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
+ (180 / Math.PI));
+ },
+
+ /**
+ * Removes the transition rule by removing the completeRotation class
+ */
+ _clearCompleteRotation: function() {
+ let contentElement = content.document &&
+ content.document instanceof ImageDocument &&
+ content.document.body &&
+ content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+ contentElement.classList.remove("completeRotation");
+ contentElement.removeEventListener("transitionend", this._clearCompleteRotation);
+ },
+};
+
+// History Swipe Animation Support (bug 678392)
+var gHistorySwipeAnimation = {
+
+ active: false,
+ isLTR: false,
+
+ /**
+ * Initializes the support for history swipe animations, if it is supported
+ * by the platform/configuration.
+ */
+ init: function HSA_init() {
+ if (!this._isSupported())
+ return;
+
+ this.active = false;
+ this.isLTR = document.documentElement.matches(":-moz-locale-dir(ltr)");
+ this._trackedSnapshots = [];
+ this._startingIndex = -1;
+ this._historyIndex = -1;
+ this._boxWidth = -1;
+ this._boxHeight = -1;
+ this._maxSnapshots = this._getMaxSnapshots();
+ this._lastSwipeDir = "";
+ this._direction = "horizontal";
+
+ // We only want to activate history swipe animations if we store snapshots.
+ // If we don't store any, we handle horizontal swipes without animations.
+ if (this._maxSnapshots > 0) {
+ this.active = true;
+ gBrowser.addEventListener("pagehide", this, false);
+ gBrowser.addEventListener("pageshow", this, false);
+ gBrowser.addEventListener("popstate", this, false);
+ gBrowser.addEventListener("DOMModalDialogClosed", this, false);
+ gBrowser.tabContainer.addEventListener("TabClose", this, false);
+ }
+ },
+
+ /**
+ * Uninitializes the support for history swipe animations.
+ */
+ uninit: function HSA_uninit() {
+ gBrowser.removeEventListener("pagehide", this, false);
+ gBrowser.removeEventListener("pageshow", this, false);
+ gBrowser.removeEventListener("popstate", this, false);
+ gBrowser.removeEventListener("DOMModalDialogClosed", this, false);
+ gBrowser.tabContainer.removeEventListener("TabClose", this, false);
+
+ this.active = false;
+ this.isLTR = false;
+ },
+
+ /**
+ * Starts the swipe animation and handles fast swiping (i.e. a swipe animation
+ * is already in progress when a new one is initiated).
+ *
+ * @param aIsVerticalSwipe
+ * Whether we're dealing with a vertical swipe or not.
+ */
+ startAnimation: function HSA_startAnimation(aIsVerticalSwipe) {
+ this._direction = aIsVerticalSwipe ? "vertical" : "horizontal";
+
+ if (this.isAnimationRunning()) {
+ // If this is a horizontal scroll, or if this is a vertical scroll that
+ // was started while a horizontal scroll was still running, handle it as
+ // as a fast swipe. In the case of the latter scenario, this allows us to
+ // start the vertical animation without first loading the final page, or
+ // taking another snapshot. If vertical scrolls are initiated repeatedly
+ // without prior horizontal scroll we skip this and restart the animation
+ // from 0.
+ if (this._direction == "horizontal" || this._lastSwipeDir != "") {
+ gBrowser.stop();
+ this._lastSwipeDir = "RELOAD"; // just ensure that != ""
+ this._canGoBack = this.canGoBack();
+ this._canGoForward = this.canGoForward();
+ this._handleFastSwiping();
+ }
+ this.updateAnimation(0);
+ }
+ else {
+ // Get the session history from SessionStore.
+ let updateSessionHistory = sessionHistory => {
+ this._startingIndex = sessionHistory.index;
+ this._historyIndex = this._startingIndex;
+ this._canGoBack = this.canGoBack();
+ this._canGoForward = this.canGoForward();
+ if (this.active) {
+ this._addBoxes();
+ this._takeSnapshot();
+ this._installPrevAndNextSnapshots();
+ this._lastSwipeDir = "";
+ }
+ this.updateAnimation(0);
+ }
+ SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory);
+ }
+ },
+
+ /**
+ * Stops the swipe animation.
+ */
+ stopAnimation: function HSA_stopAnimation() {
+ gHistorySwipeAnimation._removeBoxes();
+ this._historyIndex = this._getCurrentHistoryIndex();
+ },
+
+ /**
+ * Updates the animation between two pages in history.
+ *
+ * @param aVal
+ * A floating point value that represents the progress of the
+ * swipe gesture.
+ */
+ updateAnimation: function HSA_updateAnimation(aVal) {
+ if (!this.isAnimationRunning()) {
+ return;
+ }
+
+ // We use the following value to decrease the bounce effect when scrolling
+ // to the top or bottom of the page, or when swiping back/forward past the
+ // browsing history. This value was determined experimentally.
+ let dampValue = 4;
+ if (this._direction == "vertical") {
+ this._prevBox.collapsed = true;
+ this._nextBox.collapsed = true;
+ this._positionBox(this._curBox, -1 * aVal / dampValue);
+ } else if ((aVal >= 0 && this.isLTR) ||
+ (aVal <= 0 && !this.isLTR)) {
+ let tempDampValue = 1;
+ if (this._canGoBack) {
+ this._prevBox.collapsed = false;
+ } else {
+ tempDampValue = dampValue;
+ this._prevBox.collapsed = true;
+ }
+
+ // The current page is pushed to the right (LTR) or left (RTL),
+ // the intention is to go back.
+ // If there is a page to go back to, it should show in the background.
+ this._positionBox(this._curBox, aVal / tempDampValue);
+
+ // The forward page should be pushed offscreen all the way to the right.
+ this._positionBox(this._nextBox, 1);
+ } else if (this._canGoForward) {
+ // The intention is to go forward. If there is a page to go forward to,
+ // it should slide in from the right (LTR) or left (RTL).
+ // Otherwise, the current page should slide to the left (LTR) or
+ // right (RTL) and the backdrop should appear in the background.
+ // For the backdrop to be visible in that case, the previous page needs
+ // to be hidden (if it exists).
+ this._nextBox.collapsed = false;
+ let offset = this.isLTR ? 1 : -1;
+ this._positionBox(this._curBox, 0);
+ this._positionBox(this._nextBox, offset + aVal);
+ } else {
+ this._prevBox.collapsed = true;
+ this._positionBox(this._curBox, aVal / dampValue);
+ }
+ },
+
+ _getCurrentHistoryIndex: function() {
+ return SessionStore.getSessionHistory(gBrowser.selectedTab).index;
+ },
+
+ /**
+ * Event handler for events relevant to the history swipe animation.
+ *
+ * @param aEvent
+ * An event to process.
+ */
+ handleEvent: function HSA_handleEvent(aEvent) {
+ let browser = gBrowser.selectedBrowser;
+ switch (aEvent.type) {
+ case "TabClose":
+ let browserForTab = gBrowser.getBrowserForTab(aEvent.target);
+ this._removeTrackedSnapshot(-1, browserForTab);
+ break;
+ case "DOMModalDialogClosed":
+ this.stopAnimation();
+ break;
+ case "pageshow":
+ if (aEvent.target == browser.contentDocument) {
+ this.stopAnimation();
+ }
+ break;
+ case "popstate":
+ if (aEvent.target == browser.contentDocument.defaultView) {
+ this.stopAnimation();
+ }
+ break;
+ case "pagehide":
+ if (aEvent.target == browser.contentDocument) {
+ // Take and compress a snapshot of a page whenever it's about to be
+ // navigated away from. We already have a snapshot of the page if an
+ // animation is running, so we're left with compressing it.
+ if (!this.isAnimationRunning()) {
+ this._takeSnapshot();
+ }
+ this._compressSnapshotAtCurrentIndex();
+ }
+ break;
+ }
+ },
+
+ /**
+ * Checks whether the history swipe animation is currently running or not.
+ *
+ * @return true if the animation is currently running, false otherwise.
+ */
+ isAnimationRunning: function HSA_isAnimationRunning() {
+ return !!this._container;
+ },
+
+ /**
+ * Process a swipe event based on the given direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ processSwipeEvent: function HSA_processSwipeEvent(aEvent, aDir) {
+ if (aDir == "RIGHT")
+ this._historyIndex += this.isLTR ? 1 : -1;
+ else if (aDir == "LEFT")
+ this._historyIndex += this.isLTR ? -1 : 1;
+ else
+ return;
+ this._lastSwipeDir = aDir;
+ },
+
+ /**
+ * Checks if there is a page in the browser history to go back to.
+ *
+ * @return true if there is a previous page in history, false otherwise.
+ */
+ canGoBack: function HSA_canGoBack() {
+ if (this.isAnimationRunning())
+ return this._doesIndexExistInHistory(this._historyIndex - 1);
+ return gBrowser.webNavigation.canGoBack;
+ },
+
+ /**
+ * Checks if there is a page in the browser history to go forward to.
+ *
+ * @return true if there is a next page in history, false otherwise.
+ */
+ canGoForward: function HSA_canGoForward() {
+ if (this.isAnimationRunning())
+ return this._doesIndexExistInHistory(this._historyIndex + 1);
+ return gBrowser.webNavigation.canGoForward;
+ },
+
+ /**
+ * Used to notify the history swipe animation that the OS sent a swipe end
+ * event and that we should navigate to the page that the user swiped to, if
+ * any. This will also result in the animation overlay to be torn down.
+ */
+ swipeEndEventReceived: function HSA_swipeEndEventReceived() {
+ // Update the session history before continuing.
+ let updateSessionHistory = sessionHistory => {
+ if (this._lastSwipeDir != "" && this._historyIndex != this._startingIndex)
+ this._navigateToHistoryIndex();
+ else
+ this.stopAnimation();
+ }
+ SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory);
+ },
+
+ /**
+ * Checks whether a particular index exists in the browser history or not.
+ *
+ * @param aIndex
+ * The index to check for availability for in the history.
+ * @return true if the index exists in the browser history, false otherwise.
+ */
+ _doesIndexExistInHistory: function HSA__doesIndexExistInHistory(aIndex) {
+ try {
+ return SessionStore.getSessionHistory(gBrowser.selectedTab).entries[aIndex] != null;
+ }
+ catch (ex) {
+ return false;
+ }
+ },
+
+ /**
+ * Navigates to the index in history that is currently being tracked by
+ * |this|.
+ */
+ _navigateToHistoryIndex: function HSA__navigateToHistoryIndex() {
+ if (this._doesIndexExistInHistory(this._historyIndex))
+ gBrowser.webNavigation.gotoIndex(this._historyIndex);
+ else
+ this.stopAnimation();
+ },
+
+ /**
+ * Checks to see if history swipe animations are supported by this
+ * platform/configuration.
+ *
+ * return true if supported, false otherwise.
+ */
+ _isSupported: function HSA__isSupported() {
+ return window.matchMedia("(-moz-swipe-animation-enabled)").matches;
+ },
+
+ /**
+ * Handle fast swiping (i.e. a swipe animation is already in
+ * progress when a new one is initiated). This will swap out the snapshots
+ * used in the previous animation with the appropriate new ones.
+ */
+ _handleFastSwiping: function HSA__handleFastSwiping() {
+ this._installCurrentPageSnapshot(null);
+ this._installPrevAndNextSnapshots();
+ },
+
+ /**
+ * Adds the boxes that contain the snapshots used during the swipe animation.
+ */
+ _addBoxes: function HSA__addBoxes() {
+ let browserStack =
+ document.getAnonymousElementByAttribute(gBrowser.getNotificationBox(),
+ "class", "browserStack");
+ this._container = this._createElement("historySwipeAnimationContainer",
+ "stack");
+ browserStack.appendChild(this._container);
+
+ this._prevBox = this._createElement("historySwipeAnimationPreviousPage",
+ "box");
+ this._container.appendChild(this._prevBox);
+
+ this._curBox = this._createElement("historySwipeAnimationCurrentPage",
+ "box");
+ this._container.appendChild(this._curBox);
+
+ this._nextBox = this._createElement("historySwipeAnimationNextPage",
+ "box");
+ this._container.appendChild(this._nextBox);
+
+ // Cache width and height.
+ this._boxWidth = this._curBox.getBoundingClientRect().width;
+ this._boxHeight = this._curBox.getBoundingClientRect().height;
+ },
+
+ /**
+ * Removes the boxes.
+ */
+ _removeBoxes: function HSA__removeBoxes() {
+ this._curBox = null;
+ this._prevBox = null;
+ this._nextBox = null;
+ if (this._container)
+ this._container.parentNode.removeChild(this._container);
+ this._container = null;
+ this._boxWidth = -1;
+ this._boxHeight = -1;
+ },
+
+ /**
+ * Creates an element with a given identifier and tag name.
+ *
+ * @param aID
+ * An identifier to create the element with.
+ * @param aTagName
+ * The name of the tag to create the element for.
+ * @return the newly created element.
+ */
+ _createElement: function HSA__createElement(aID, aTagName) {
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let element = document.createElementNS(XULNS, aTagName);
+ element.id = aID;
+ return element;
+ },
+
+ /**
+ * Moves a given box to a given X coordinate position.
+ *
+ * @param aBox
+ * The box element to position.
+ * @param aPosition
+ * The position (in X coordinates) to move the box element to.
+ */
+ _positionBox: function HSA__positionBox(aBox, aPosition) {
+ let transform = "";
+
+ if (this._direction == "vertical")
+ transform = "translateY(" + this._boxHeight * aPosition + "px)";
+ else
+ transform = "translateX(" + this._boxWidth * aPosition + "px)";
+
+ aBox.style.transform = transform;
+ },
+
+ /**
+ * Verifies that we're ready to take snapshots based on the global pref and
+ * the current index in history.
+ *
+ * @return true if we're ready to take snapshots, false otherwise.
+ */
+ _readyToTakeSnapshots: function HSA__readyToTakeSnapshots() {
+ return (this._maxSnapshots >= 1 && this._getCurrentHistoryIndex() >= 0);
+ },
+
+ /**
+ * Takes a snapshot of the page the browser is currently on.
+ */
+ _takeSnapshot: function HSA__takeSnapshot() {
+ if (!this._readyToTakeSnapshots()) {
+ return;
+ }
+
+ let canvas = null;
+
+ let browser = gBrowser.selectedBrowser;
+ let r = browser.getBoundingClientRect();
+ canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
+ "canvas");
+ canvas.mozOpaque = true;
+ let scale = window.devicePixelRatio;
+ canvas.width = r.width * scale;
+ canvas.height = r.height * scale;
+ let ctx = canvas.getContext("2d");
+ let zoom = browser.markupDocumentViewer.fullZoom * scale;
+ ctx.scale(zoom, zoom);
+ ctx.drawWindow(browser.contentWindow,
+ 0, 0, canvas.width / zoom, canvas.height / zoom, "white",
+ ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_VIEW |
+ ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
+ ctx.DRAWWINDOW_USE_WIDGET_LAYERS);
+
+ TelemetryStopwatch.start("FX_GESTURE_INSTALL_SNAPSHOT_OF_PAGE");
+ try {
+ this._installCurrentPageSnapshot(canvas);
+ this._assignSnapshotToCurrentBrowser(canvas);
+ } finally {
+ TelemetryStopwatch.finish("FX_GESTURE_INSTALL_SNAPSHOT_OF_PAGE");
+ }
+ },
+
+ /**
+ * Retrieves the maximum number of snapshots that should be kept in memory.
+ * This limit is a global limit and is valid across all open tabs.
+ */
+ _getMaxSnapshots: function HSA__getMaxSnapshots() {
+ return gPrefService.getIntPref("browser.snapshots.limit");
+ },
+
+ /**
+ * Adds a snapshot to the list and initiates the compression of said snapshot.
+ * Once the compression is completed, it will replace the uncompressed
+ * snapshot in the list.
+ *
+ * @param aCanvas
+ * The snapshot to add to the list and compress.
+ */
+ _assignSnapshotToCurrentBrowser:
+ function HSA__assignSnapshotToCurrentBrowser(aCanvas) {
+ let browser = gBrowser.selectedBrowser;
+ let currIndex = this._getCurrentHistoryIndex();
+
+ this._removeTrackedSnapshot(currIndex, browser);
+ this._addSnapshotRefToArray(currIndex, browser);
+
+ if (!("snapshots" in browser))
+ browser.snapshots = [];
+ let snapshots = browser.snapshots;
+ // Temporarily store the canvas as the compressed snapshot.
+ // This avoids a blank page if the user swipes quickly
+ // between pages before the compression could complete.
+ snapshots[currIndex] = {
+ image: aCanvas,
+ scale: window.devicePixelRatio
+ };
+ },
+
+ /**
+ * Compresses the HTMLCanvasElement that's stored at the current history
+ * index in the snapshot array and stores the compressed image in its place.
+ */
+ _compressSnapshotAtCurrentIndex:
+ function HSA__compressSnapshotAtCurrentIndex() {
+ if (!this._readyToTakeSnapshots()) {
+ // We didn't take a snapshot earlier because we weren't ready to, so
+ // there's nothing to compress.
+ return;
+ }
+
+ TelemetryStopwatch.start("FX_GESTURE_COMPRESS_SNAPSHOT_OF_PAGE");
+ try {
+ let browser = gBrowser.selectedBrowser;
+ let snapshots = browser.snapshots;
+ let currIndex = _getCurrentHistoryIndex();
+
+ // Kick off snapshot compression.
+ let canvas = snapshots[currIndex].image;
+ canvas.toBlob(function(aBlob) {
+ if (snapshots[currIndex]) {
+ snapshots[currIndex].image = aBlob;
+ }
+ }, "image/png"
+ );
+ } finally {
+ TelemetryStopwatch.finish("FX_GESTURE_COMPRESS_SNAPSHOT_OF_PAGE");
+ }
+ },
+
+ /**
+ * Removes a snapshot identified by the browser and index in the array of
+ * snapshots for that browser, if present. If no snapshot could be identified
+ * the method simply returns without taking any action. If aIndex is negative,
+ * all snapshots for a particular browser will be removed.
+ *
+ * @param aIndex
+ * The index in history of the new snapshot, or negative value if all
+ * snapshots for a browser should be removed.
+ * @param aBrowser
+ * The browser the new snapshot was taken in.
+ */
+ _removeTrackedSnapshot: function HSA__removeTrackedSnapshot(aIndex, aBrowser) {
+ let arr = this._trackedSnapshots;
+ let requiresExactIndexMatch = aIndex >= 0;
+ for (let i = 0; i < arr.length; i++) {
+ if ((arr[i].browser == aBrowser) &&
+ (aIndex < 0 || aIndex == arr[i].index)) {
+ delete aBrowser.snapshots[arr[i].index];
+ arr.splice(i, 1);
+ if (requiresExactIndexMatch)
+ return; // Found and removed the only element.
+ i--; // Make sure to revisit the index that we just removed an
+ // element at.
+ }
+ }
+ },
+
+ /**
+ * Adds a new snapshot reference for a given index and browser to the array
+ * of references to tracked snapshots.
+ *
+ * @param aIndex
+ * The index in history of the new snapshot.
+ * @param aBrowser
+ * The browser the new snapshot was taken in.
+ */
+ _addSnapshotRefToArray:
+ function HSA__addSnapshotRefToArray(aIndex, aBrowser) {
+ let id = { index: aIndex,
+ browser: aBrowser };
+ let arr = this._trackedSnapshots;
+ arr.unshift(id);
+
+ while (arr.length > this._maxSnapshots) {
+ let lastElem = arr[arr.length - 1];
+ delete lastElem.browser.snapshots[lastElem.index].image;
+ delete lastElem.browser.snapshots[lastElem.index];
+ arr.splice(-1, 1);
+ }
+ },
+
+ /**
+ * Converts a compressed blob to an Image object. In some situations
+ * (especially during fast swiping) aBlob may still be a canvas, not a
+ * compressed blob. In this case, we simply return the canvas.
+ *
+ * @param aBlob
+ * The compressed blob to convert, or a canvas if a blob compression
+ * couldn't complete before this method was called.
+ * @return A new Image object representing the converted blob.
+ */
+ _convertToImg: function HSA__convertToImg(aBlob) {
+ if (!aBlob)
+ return null;
+
+ // Return aBlob if it's still a canvas and not a compressed blob yet.
+ if (aBlob instanceof HTMLCanvasElement)
+ return aBlob;
+
+ let img = new Image();
+ let url = "";
+ try {
+ url = URL.createObjectURL(aBlob);
+ img.onload = function() {
+ URL.revokeObjectURL(url);
+ };
+ }
+ finally {
+ img.src = url;
+ return img;
+ }
+ },
+
+ /**
+ * Scales the background of a given box element (which uses a given snapshot
+ * as background) based on a given scale factor.
+ * @param aSnapshot
+ * The snapshot that is used as background of aBox.
+ * @param aScale
+ * The scale factor to use.
+ * @param aBox
+ * The box element that uses aSnapshot as background.
+ */
+ _scaleSnapshot: function HSA__scaleSnapshot(aSnapshot, aScale, aBox) {
+ if (aSnapshot && aScale != 1 && aBox) {
+ if (aSnapshot instanceof HTMLCanvasElement) {
+ aBox.style.backgroundSize =
+ aSnapshot.width / aScale + "px " + aSnapshot.height / aScale + "px";
+ } else {
+ // snapshot is instanceof HTMLImageElement
+ aSnapshot.addEventListener("load", function() {
+ aBox.style.backgroundSize =
+ aSnapshot.width / aScale + "px " + aSnapshot.height / aScale + "px";
+ });
+ }
+ }
+ },
+
+ /**
+ * Sets the snapshot of the current page to the snapshot passed as parameter,
+ * or to the one previously stored for the current index in history if the
+ * parameter is null.
+ *
+ * @param aCanvas
+ * The snapshot to set the current page to. If this parameter is null,
+ * the previously stored snapshot for this index (if any) will be used.
+ */
+ _installCurrentPageSnapshot:
+ function HSA__installCurrentPageSnapshot(aCanvas) {
+ let currSnapshot = aCanvas;
+ let scale = window.devicePixelRatio;
+ if (!currSnapshot) {
+ let snapshots = gBrowser.selectedBrowser.snapshots || {};
+ let currIndex = this._historyIndex;
+ if (currIndex in snapshots) {
+ currSnapshot = this._convertToImg(snapshots[currIndex].image);
+ scale = snapshots[currIndex].scale;
+ }
+ }
+ this._scaleSnapshot(currSnapshot, scale, this._curBox ? this._curBox :
+ null);
+ document.mozSetImageElement("historySwipeAnimationCurrentPageSnapshot",
+ currSnapshot);
+ },
+
+ /**
+ * Sets the snapshots of the previous and next pages to the snapshots
+ * previously stored for their respective indeces.
+ */
+ _installPrevAndNextSnapshots:
+ function HSA__installPrevAndNextSnapshots() {
+ let snapshots = gBrowser.selectedBrowser.snapshots || [];
+ let currIndex = this._historyIndex;
+ let prevIndex = currIndex - 1;
+ let prevSnapshot = null;
+ if (prevIndex in snapshots) {
+ prevSnapshot = this._convertToImg(snapshots[prevIndex].image);
+ this._scaleSnapshot(prevSnapshot, snapshots[prevIndex].scale,
+ this._prevBox);
+ }
+ document.mozSetImageElement("historySwipeAnimationPreviousPageSnapshot",
+ prevSnapshot);
+
+ let nextIndex = currIndex + 1;
+ let nextSnapshot = null;
+ if (nextIndex in snapshots) {
+ nextSnapshot = this._convertToImg(snapshots[nextIndex].image);
+ this._scaleSnapshot(nextSnapshot, snapshots[nextIndex].scale,
+ this._nextBox);
+ }
+ document.mozSetImageElement("historySwipeAnimationNextPageSnapshot",
+ nextSnapshot);
+ },
+};
diff --git a/browser/base/content/browser-media.js b/browser/base/content/browser-media.js
new file mode 100644
index 000000000..81e7faf17
--- /dev/null
+++ b/browser/base/content/browser-media.js
@@ -0,0 +1,365 @@
+/* -*- 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 gEMEHandler = {
+ get uiEnabled() {
+ let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
+ // Force-disable on WinXP:
+ if (navigator.platform.toLowerCase().startsWith("win")) {
+ emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
+ }
+ return emeUIEnabled;
+ },
+ ensureEMEEnabled: function(browser, keySystem) {
+ Services.prefs.setBoolPref("media.eme.enabled", true);
+ if (keySystem) {
+ if (keySystem.startsWith("com.adobe") &&
+ Services.prefs.getPrefType("media.gmp-eme-adobe.enabled") &&
+ !Services.prefs.getBoolPref("media.gmp-eme-adobe.enabled")) {
+ Services.prefs.setBoolPref("media.gmp-eme-adobe.enabled", true);
+ } else if (keySystem == "com.widevine.alpha" &&
+ Services.prefs.getPrefType("media.gmp-widevinecdm.enabled") &&
+ !Services.prefs.getBoolPref("media.gmp-widevinecdm.enabled")) {
+ Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", true);
+ }
+ }
+ browser.reload();
+ },
+ isKeySystemVisible: function(keySystem) {
+ if (!keySystem) {
+ return false;
+ }
+ if (keySystem.startsWith("com.adobe") &&
+ Services.prefs.getPrefType("media.gmp-eme-adobe.visible")) {
+ return Services.prefs.getBoolPref("media.gmp-eme-adobe.visible");
+ }
+ if (keySystem == "com.widevine.alpha" &&
+ Services.prefs.getPrefType("media.gmp-widevinecdm.visible")) {
+ return Services.prefs.getBoolPref("media.gmp-widevinecdm.visible");
+ }
+ return true;
+ },
+ getLearnMoreLink: function(msgId) {
+ let text = gNavigatorBundle.getString("emeNotifications." + msgId + ".learnMoreLabel");
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ return "<label class='text-link' href='" + baseURL + "drm-content'>" +
+ text + "</label>";
+ },
+ receiveMessage: function({target: browser, data: data}) {
+ let parsedData;
+ try {
+ parsedData = JSON.parse(data);
+ } catch (ex) {
+ Cu.reportError("Malformed EME video message with data: " + data);
+ return;
+ }
+ let {status: status, keySystem: keySystem} = parsedData;
+ // Don't need to show if disabled or keysystem not visible.
+ if (!this.uiEnabled || !this.isKeySystemVisible(keySystem)) {
+ return;
+ }
+
+ let notificationId;
+ let buttonCallback;
+ let params = [];
+ switch (status) {
+ case "available":
+ case "cdm-created":
+ // Only show the chain icon for proprietary CDMs. Clearkey is not one.
+ if (keySystem != "org.w3.clearkey") {
+ this.showPopupNotificationForSuccess(browser, keySystem);
+ }
+ // ... and bail!
+ return;
+
+ case "api-disabled":
+ case "cdm-disabled":
+ notificationId = "drmContentDisabled";
+ buttonCallback = gEMEHandler.ensureEMEEnabled.bind(gEMEHandler, browser, keySystem)
+ params = [this.getLearnMoreLink(notificationId)];
+ break;
+
+ case "cdm-insufficient-version":
+ notificationId = "drmContentCDMInsufficientVersion";
+ params = [this._brandShortName];
+ break;
+
+ case "cdm-not-installed":
+ notificationId = "drmContentCDMInstalling";
+ params = [this._brandShortName];
+ break;
+
+ case "cdm-not-supported":
+ // Not to pop up user-level notification because they cannot do anything
+ // about it.
+ return;
+ default:
+ Cu.reportError(new Error("Unknown message ('" + status + "') dealing with EME key request: " + data));
+ return;
+ }
+
+ this.showNotificationBar(browser, notificationId, keySystem, params, buttonCallback);
+ },
+ showNotificationBar: function(browser, notificationId, keySystem, labelParams, callback) {
+ let box = gBrowser.getNotificationBox(browser);
+ if (box.getNotificationWithValue(notificationId)) {
+ return;
+ }
+
+ let msgPrefix = "emeNotifications." + notificationId + ".";
+ let msgId = msgPrefix + "message";
+
+ let message = labelParams.length ?
+ gNavigatorBundle.getFormattedString(msgId, labelParams) :
+ gNavigatorBundle.getString(msgId);
+
+ let buttons = [];
+ if (callback) {
+ let btnLabelId = msgPrefix + "button.label";
+ let btnAccessKeyId = msgPrefix + "button.accesskey";
+ buttons.push({
+ label: gNavigatorBundle.getString(btnLabelId),
+ accessKey: gNavigatorBundle.getString(btnAccessKeyId),
+ callback: callback
+ });
+ }
+
+ let iconURL = "chrome://browser/skin/drm-icon.svg#chains-black";
+
+ // Do a little dance to get rich content into the notification:
+ let fragment = document.createDocumentFragment();
+ let descriptionContainer = document.createElement("description");
+ descriptionContainer.innerHTML = message;
+ while (descriptionContainer.childNodes.length) {
+ fragment.appendChild(descriptionContainer.childNodes[0]);
+ }
+
+ box.appendNotification(fragment, notificationId, iconURL, box.PRIORITY_WARNING_MEDIUM,
+ buttons);
+ },
+ showPopupNotificationForSuccess: function(browser, keySystem) {
+ // We're playing EME content! Remove any "we can't play because..." messages.
+ var box = gBrowser.getNotificationBox(browser);
+ ["drmContentDisabled",
+ "drmContentCDMInstalling"
+ ].forEach(function (value) {
+ var notification = box.getNotificationWithValue(value);
+ if (notification)
+ box.removeNotification(notification);
+ });
+
+ // Don't bother creating it if it's already there:
+ if (PopupNotifications.getNotification("drmContentPlaying", browser)) {
+ return;
+ }
+
+ let msgPrefix = "emeNotifications.drmContentPlaying.";
+ let msgId = msgPrefix + "message2";
+ let btnLabelId = msgPrefix + "button.label";
+ let btnAccessKeyId = msgPrefix + "button.accesskey";
+
+ let message = gNavigatorBundle.getFormattedString(msgId, [this._brandShortName]);
+ let anchorId = "eme-notification-icon";
+ let firstPlayPref = "browser.eme.ui.firstContentShown";
+ if (!Services.prefs.getPrefType(firstPlayPref) ||
+ !Services.prefs.getBoolPref(firstPlayPref)) {
+ document.getElementById(anchorId).setAttribute("firstplay", "true");
+ Services.prefs.setBoolPref(firstPlayPref, true);
+ } else {
+ document.getElementById(anchorId).removeAttribute("firstplay");
+ }
+
+ let mainAction = {
+ label: gNavigatorBundle.getString(btnLabelId),
+ accessKey: gNavigatorBundle.getString(btnAccessKeyId),
+ callback: function() { openPreferences("paneContent"); },
+ dismiss: true
+ };
+ let options = {
+ dismissed: true,
+ eventCallback: aTopic => aTopic == "swapping",
+ learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content",
+ };
+ PopupNotifications.show(browser, "drmContentPlaying", message, anchorId, mainAction, null, options);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener])
+};
+
+XPCOMUtils.defineLazyGetter(gEMEHandler, "_brandShortName", function() {
+ return document.getElementById("bundle_brand").getString("brandShortName");
+});
+
+const TELEMETRY_DDSTAT_SHOWN = 0;
+const TELEMETRY_DDSTAT_SHOWN_FIRST = 1;
+const TELEMETRY_DDSTAT_CLICKED = 2;
+const TELEMETRY_DDSTAT_CLICKED_FIRST = 3;
+const TELEMETRY_DDSTAT_SOLVED = 4;
+
+let gDecoderDoctorHandler = {
+ getLabelForNotificationBox(type) {
+ if (type == "adobe-cdm-not-found" &&
+ AppConstants.platform == "win") {
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ // We supply our own Learn More button so we don't need to populate the message here.
+ return gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
+ }
+ return gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+ if (type == "adobe-cdm-not-activated" &&
+ AppConstants.platform == "win") {
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ return gNavigatorBundle.getString("decoder.noCodecsXP.message");
+ }
+ if (!AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
+ return gNavigatorBundle.getString("decoder.noCodecsVista.message");
+ }
+ return gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+ if (type == "platform-decoder-not-found") {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.1")) {
+ return gNavigatorBundle.getString("decoder.noHWAcceleration.message");
+ }
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6")) {
+ return gNavigatorBundle.getString("decoder.noHWAccelerationVista.message");
+ }
+ if (AppConstants.platform == "linux") {
+ return gNavigatorBundle.getString("decoder.noCodecsLinux.message");
+ }
+ }
+ if (type == "cannot-initialize-pulseaudio") {
+ return gNavigatorBundle.getString("decoder.noPulseAudio.message");
+ }
+ if (type == "unsupported-libavcodec" &&
+ AppConstants.platform == "linux") {
+ return gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
+ }
+ return "";
+ },
+
+ getSumoForLearnHowButton(type) {
+ if (AppConstants.platform == "win") {
+ return "fix-video-audio-problems-firefox-windows";
+ }
+ if (type == "cannot-initialize-pulseaudio") {
+ return "fix-common-audio-and-video-issues";
+ }
+ return "";
+ },
+
+ receiveMessage({target: browser, data: data}) {
+ let box = gBrowser.getNotificationBox(browser);
+ let notificationId = "decoder-doctor-notification";
+ if (box.getNotificationWithValue(notificationId)) {
+ return;
+ }
+
+ let parsedData;
+ try {
+ parsedData = JSON.parse(data);
+ } catch (ex) {
+ Cu.reportError("Malformed Decoder Doctor message with data: " + data);
+ return;
+ }
+ // parsedData (the result of parsing the incoming 'data' json string)
+ // contains analysis information from Decoder Doctor:
+ // - 'type' is the type of issue, it determines which text to show in the
+ // infobar.
+ // - 'decoderDoctorReportId' is the Decoder Doctor issue identifier, to be
+ // used here as key for the telemetry (counting infobar displays,
+ // "Learn how" buttons clicks, and resolutions) and for the prefs used
+ // to store at-issue formats.
+ // - 'formats' contains a comma-separated list of formats (or key systems)
+ // that suffer the issue. These are kept in a pref, which the backend
+ // uses to later find when an issue is resolved.
+ // - 'isSolved' is true when the notification actually indicates the
+ // resolution of that issue, to be reported as telemetry.
+ let {type, isSolved, decoderDoctorReportId, formats} = parsedData;
+ type = type.toLowerCase();
+ // Error out early on invalid ReportId
+ if (!(/^\w+$/mi).test(decoderDoctorReportId)) {
+ return
+ }
+ let title = gDecoderDoctorHandler.getLabelForNotificationBox(type);
+ if (!title) {
+ return;
+ }
+
+ // We keep the list of formats in prefs for the sake of the decoder itself,
+ // which reads it to determine when issues get solved for these formats.
+ // (Writing prefs from e10s content is now allowed.)
+ let formatsPref = "media.decoder-doctor." + decoderDoctorReportId + ".formats";
+ let buttonClickedPref = "media.decoder-doctor." + decoderDoctorReportId + ".button-clicked";
+ let histogram =
+ Services.telemetry.getKeyedHistogramById("DECODER_DOCTOR_INFOBAR_STATS");
+
+ let formatsInPref = Services.prefs.getPrefType(formatsPref) &&
+ Services.prefs.getCharPref(formatsPref);
+
+ if (!isSolved) {
+ if (!formats) {
+ Cu.reportError("Malformed Decoder Doctor unsolved message with no formats");
+ return;
+ }
+ if (!formatsInPref) {
+ Services.prefs.setCharPref(formatsPref, formats);
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN_FIRST);
+ } else {
+ // Split existing formats into an array of strings.
+ let existing = formatsInPref.split(",").map(String.trim);
+ // Keep given formats that were not already recorded.
+ let newbies = formats.split(",").map(String.trim)
+ .filter(x => !existing.includes(x));
+ // And rewrite pref with the added new formats (if any).
+ if (newbies.length) {
+ Services.prefs.setCharPref(formatsPref,
+ existing.concat(newbies).join(", "));
+ }
+ }
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SHOWN);
+
+ let buttons = [];
+ let sumo = gDecoderDoctorHandler.getSumoForLearnHowButton(type);
+ if (sumo) {
+ buttons.push({
+ label: gNavigatorBundle.getString("decoder.noCodecs.button"),
+ accessKey: gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+ callback() {
+ let clickedInPref = Services.prefs.getPrefType(buttonClickedPref) &&
+ Services.prefs.getBoolPref(buttonClickedPref);
+ if (!clickedInPref) {
+ Services.prefs.setBoolPref(buttonClickedPref, true);
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED_FIRST);
+ }
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_CLICKED);
+
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ openUILinkIn(baseURL + sumo, "tab");
+ }
+ });
+ }
+
+ box.appendNotification(
+ title,
+ notificationId,
+ "", // This uses the info icon as specified below.
+ box.PRIORITY_INFO_LOW,
+ buttons
+ );
+ } else if (formatsInPref) {
+ // Issue is solved, and prefs haven't been cleared yet, meaning it's the
+ // first time we get this resolution -> Clear prefs and report telemetry.
+ Services.prefs.clearUserPref(formatsPref);
+ Services.prefs.clearUserPref(buttonClickedPref);
+ histogram.add(decoderDoctorReportId, TELEMETRY_DDSTAT_SOLVED);
+ }
+ },
+}
+
+window.getGroupMessageManager("browsers").addMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
+window.getGroupMessageManager("browsers").addMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
+window.addEventListener("unload", function() {
+ window.getGroupMessageManager("browsers").removeMessageListener("EMEVideo:ContentMediaKeysRequest", gEMEHandler);
+ window.getGroupMessageManager("browsers").removeMessageListener("DecoderDoctor:Notification", gDecoderDoctorHandler);
+}, false);
diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc
new file mode 100644
index 000000000..8f77ebafe
--- /dev/null
+++ b/browser/base/content/browser-menubar.inc
@@ -0,0 +1,535 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ <menubar id="main-menubar"
+ onpopupshowing="if (event.target.parentNode.parentNode == this &amp;&amp;
+ !('@mozilla.org/widget/nativemenuservice;1' in Cc))
+ this.setAttribute('openedwithkey',
+ event.target.parentNode.openedWithKey);"
+ style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
+ <menu id="file-menu" label="&fileMenu.label;"
+ accesskey="&fileMenu.accesskey;">
+ <menupopup id="menu_FilePopup"
+ onpopupshowing="updateUserContextUIVisibility();">
+ <menuitem id="menu_newNavigatorTab"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ key="key_newNavigatorTab"
+ accesskey="&tabCmd.accesskey;"/>
+ <menu id="menu_newUserContext"
+ label="&newUserContext.label;"
+ accesskey="&newUserContext.accesskey;"
+ hidden="true">
+ <menupopup onpopupshowing="return createUserContextMenu(event);" />
+ </menu>
+ <menuitem id="menu_newNavigator"
+ label="&newNavigatorCmd.label;"
+ accesskey="&newNavigatorCmd.accesskey;"
+ key="key_newNavigator"
+ command="cmd_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"
+ label="&newPrivateWindow.label;"
+ accesskey="&newPrivateWindow.accesskey;"
+ command="Tools:PrivateBrowsing"
+ key="key_privatebrowsing"/>
+#ifdef E10S_TESTING_ONLY
+ <menuitem id="menu_newNonRemoteWindow"
+ label="&newNonRemoteWindow.label;"
+ hidden="true"
+ command="Tools:NonRemoteWindow"/>
+#endif
+#ifdef MAC_NON_BROWSER_WINDOW
+ <menuitem id="menu_openLocation"
+ label="&openLocationCmd.label;"
+ command="Browser:OpenLocation"
+ key="focusURLBar"/>
+#endif
+ <menuitem id="menu_openFile"
+ label="&openFileCmd.label;"
+ command="Browser:OpenFile"
+ key="openFileKb"
+ accesskey="&openFileCmd.accesskey;"/>
+ <menuitem id="menu_close"
+ class="show-only-for-keyboard"
+ label="&closeCmd.label;"
+ key="key_close"
+ accesskey="&closeCmd.accesskey;"
+ command="cmd_close"/>
+ <menuitem id="menu_closeWindow"
+ class="show-only-for-keyboard"
+ hidden="true"
+ command="cmd_closeWindow"
+ key="key_closeWindow"
+ label="&closeWindow.label;"
+ accesskey="&closeWindow.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="menu_savePage"
+ label="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey;"
+ key="key_savePage"
+ command="Browser:SavePage"/>
+ <menuitem id="menu_sendLink"
+ label="&emailPageCmd.label;"
+ accesskey="&emailPageCmd.accesskey;"
+ command="Browser:SendLink"/>
+ <menuseparator/>
+#if !defined(MOZ_WIDGET_GTK)
+ <menuitem id="menu_printSetup"
+ label="&printSetupCmd.label;"
+ accesskey="&printSetupCmd.accesskey;"
+ command="cmd_pageSetup"/>
+#endif
+#ifndef XP_MACOSX
+ <menuitem id="menu_printPreview"
+ label="&printPreviewCmd.label;"
+ accesskey="&printPreviewCmd.accesskey;"
+ command="cmd_printPreview"/>
+#endif
+ <menuitem id="menu_print"
+ label="&printCmd.label;"
+ accesskey="&printCmd.accesskey;"
+ key="printKb"
+ command="cmd_print"/>
+ <menuseparator/>
+ <menuitem id="goOfflineMenuitem"
+ label="&goOfflineCmd.label;"
+ accesskey="&goOfflineCmd.accesskey;"
+ type="checkbox"
+ observes="workOfflineMenuitemState"
+ oncommand="BrowserOffline.toggleOfflineStatus();"/>
+ <menuitem id="menu_FileQuitItem"
+#ifdef XP_WIN
+ label="&quitApplicationCmdWin2.label;"
+ accesskey="&quitApplicationCmdWin2.accesskey;"
+#else
+#ifdef XP_MACOSX
+ label="&quitApplicationCmdMac2.label;"
+#else
+ label="&quitApplicationCmd.label;"
+ accesskey="&quitApplicationCmd.accesskey;"
+#endif
+#ifdef XP_UNIX
+ key="key_quitApplication"
+#endif
+#endif
+ command="cmd_quitApplication"/>
+ </menupopup>
+ </menu>
+
+ <menu id="edit-menu" label="&editMenu.label;"
+ accesskey="&editMenu.accesskey;">
+ <menupopup id="menu_EditPopup"
+ onpopupshowing="updateEditUIVisibility()"
+ onpopuphidden="updateEditUIVisibility()">
+ <menuitem id="menu_undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ accesskey="&undoCmd.accesskey;"
+ command="cmd_undo"/>
+ <menuitem id="menu_redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ accesskey="&redoCmd.accesskey;"
+ command="cmd_redo"/>
+ <menuseparator/>
+ <menuitem id="menu_cut"
+ label="&cutCmd.label;"
+ key="key_cut"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="menu_copy"
+ label="&copyCmd.label;"
+ key="key_copy"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="menu_paste"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuitem id="menu_delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"
+ command="cmd_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem id="menu_find"
+ label="&findOnCmd.label;"
+ accesskey="&findOnCmd.accesskey;"
+ key="key_find"
+ command="cmd_find"/>
+ <menuitem id="menu_findAgain"
+ class="show-only-for-keyboard"
+ label="&findAgainCmd.label;"
+ accesskey="&findAgainCmd.accesskey;"
+ key="key_findAgain"
+ command="cmd_findAgain"/>
+ <menuseparator hidden="true" id="textfieldDirection-separator"/>
+ <menuitem id="textfieldDirection-swap"
+ command="cmd_switchTextDirection"
+ key="key_switchTextDirection"
+ label="&bidiSwitchTextDirectionItem.label;"
+ accesskey="&bidiSwitchTextDirectionItem.accesskey;"
+ hidden="true"/>
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+ <menuseparator/>
+ <menuitem id="menu_preferences"
+ label="&preferencesCmdUnix.label;"
+ accesskey="&preferencesCmdUnix.accesskey;"
+ oncommand="openPreferences();"/>
+#endif
+#endif
+ </menupopup>
+ </menu>
+
+ <menu id="view-menu" label="&viewMenu.label;"
+ accesskey="&viewMenu.accesskey;">
+ <menupopup id="menu_viewPopup"
+ onpopupshowing="updateCharacterEncodingMenuState();">
+ <menu id="viewToolbarsMenu"
+ label="&viewToolbarsMenu.label;"
+ accesskey="&viewToolbarsMenu.accesskey;">
+ <menupopup onpopupshowing="onViewToolbarsPopupShowing(event);">
+ <menuseparator/>
+ <menuitem id="menu_customizeToolbars"
+ label="&viewCustomizeToolbar.label;"
+ accesskey="&viewCustomizeToolbar.accesskey;"
+ command="cmd_CustomizeToolbars"/>
+ </menupopup>
+ </menu>
+ <menu id="viewSidebarMenuMenu"
+ label="&viewSidebarMenu.label;"
+ accesskey="&viewSidebarMenu.accesskey;">
+ <menupopup id="viewSidebarMenu">
+ <menuitem id="menu_bookmarksSidebar"
+ key="viewBookmarksSidebarKb"
+ observes="viewBookmarksSidebar"/>
+ <menuitem id="menu_historySidebar"
+ key="key_gotoHistory"
+ observes="viewHistorySidebar"
+ label="&historyButton.label;"/>
+ <menuitem id="menu_tabsSidebar"
+ observes="viewTabsSidebar"
+ label="&syncedTabs.sidebar.label;"/>
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ <menu id="viewFullZoomMenu" label="&fullZoom.label;"
+ accesskey="&fullZoom.accesskey;"
+ onpopupshowing="FullZoom.updateMenu();">
+ <menupopup>
+ <menuitem id="menu_zoomEnlarge"
+ key="key_fullZoomEnlarge"
+ label="&fullZoomEnlargeCmd.label;"
+ accesskey="&fullZoomEnlargeCmd.accesskey;"
+ command="cmd_fullZoomEnlarge"/>
+ <menuitem id="menu_zoomReduce"
+ key="key_fullZoomReduce"
+ label="&fullZoomReduceCmd.label;"
+ accesskey="&fullZoomReduceCmd.accesskey;"
+ command="cmd_fullZoomReduce"/>
+ <menuseparator/>
+ <menuitem id="menu_zoomReset"
+ key="key_fullZoomReset"
+ label="&fullZoomResetCmd.label;"
+ accesskey="&fullZoomResetCmd.accesskey;"
+ command="cmd_fullZoomReset"/>
+ <menuseparator/>
+ <menuitem id="toggle_zoom"
+ label="&fullZoomToggleCmd.label;"
+ accesskey="&fullZoomToggleCmd.accesskey;"
+ type="checkbox"
+ command="cmd_fullZoomToggle"
+ checked="false"/>
+ </menupopup>
+ </menu>
+ <menu id="pageStyleMenu" label="&pageStyleMenu.label;"
+ accesskey="&pageStyleMenu.accesskey;" observes="isImage">
+ <menupopup onpopupshowing="gPageStyleMenu.fillPopup(this);">
+ <menuitem id="menu_pageStyleNoStyle"
+ label="&pageStyleNoStyle.label;"
+ accesskey="&pageStyleNoStyle.accesskey;"
+ oncommand="gPageStyleMenu.disableStyle();"
+ type="radio"/>
+ <menuitem id="menu_pageStylePersistentOnly"
+ label="&pageStylePersistentOnly.label;"
+ accesskey="&pageStylePersistentOnly.accesskey;"
+ oncommand="gPageStyleMenu.switchStyleSheet('');"
+ type="radio"
+ checked="true"/>
+ <menuseparator/>
+ </menupopup>
+ </menu>
+#include browser-charsetmenu.inc
+ <menuseparator/>
+#ifdef XP_MACOSX
+ <menuitem id="enterFullScreenItem"
+ accesskey="&enterFullScreenCmd.accesskey;"
+ label="&enterFullScreenCmd.label;"
+ key="key_fullScreen">
+ <observes element="View:FullScreen" attribute="oncommand"/>
+ <observes element="View:FullScreen" attribute="disabled"/>
+ </menuitem>
+ <menuitem id="exitFullScreenItem"
+ accesskey="&exitFullScreenCmd.accesskey;"
+ label="&exitFullScreenCmd.label;"
+ key="key_fullScreen"
+ hidden="true">
+ <observes element="View:FullScreen" attribute="oncommand"/>
+ <observes element="View:FullScreen" attribute="disabled"/>
+ </menuitem>
+#else
+ <menuitem id="fullScreenItem"
+ accesskey="&fullScreenCmd.accesskey;"
+ label="&fullScreenCmd.label;"
+ key="key_fullScreen"
+ type="checkbox"
+ observes="View:FullScreen"/>
+#endif
+ <menuitem id="menu_readerModeItem"
+ observes="View:ReaderView"
+ hidden="true"/>
+ <menuitem id="menu_showAllTabs"
+ hidden="true"
+ accesskey="&showAllTabsCmd.accesskey;"
+ label="&showAllTabsCmd.label;"
+ command="Browser:ShowAllTabs"
+ key="key_showAllTabs"/>
+ <menuseparator hidden="true" id="documentDirection-separator"/>
+ <menuitem id="documentDirection-swap"
+ hidden="true"
+ label="&bidiSwitchPageDirectionItem.label;"
+ accesskey="&bidiSwitchPageDirectionItem.accesskey;"
+ oncommand="gBrowser.selectedBrowser
+ .messageManager
+ .sendAsyncMessage('SwitchDocumentDirection');"/>
+ </menupopup>
+ </menu>
+
+ <menu id="history-menu"
+ label="&historyMenu.label;"
+ accesskey="&historyMenu.accesskey;">
+ <menupopup id="goPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ oncommand="this.parentNode._placesView._onCommand(event);"
+ onclick="checkForMiddleClick(this, event);"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new HistoryMenu(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="menu_showAllHistory"
+ label="&showAllHistoryCmd2.label;"
+#ifndef XP_MACOSX
+ key="showAllHistoryKb"
+#endif
+ command="Browser:ShowAllHistory"/>
+ <menuitem id="sanitizeItem"
+ label="&clearRecentHistory.label;"
+ key="key_sanitize"
+ command="Tools:Sanitize"/>
+ <menuseparator id="sanitizeSeparator"/>
+ <menuitem id="sync-tabs-menuitem"
+ class="syncTabsMenuItem"
+ label="&syncTabsMenu3.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ hidden="true"/>
+ <menuitem id="historyRestoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ command="Browser:RestoreLastSession"/>
+ <menu id="historyUndoMenu"
+ class="recentlyClosedTabsMenu"
+ label="&historyUndoMenu.label;"
+ disabled="true">
+ <menupopup id="historyUndoPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoSubmenu();"/>
+ </menu>
+ <menu id="historyUndoWindowMenu"
+ class="recentlyClosedWindowsMenu"
+ label="&historyUndoWindowMenu.label;"
+ disabled="true">
+ <menupopup id="historyUndoWindowPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoWindowSubmenu();"/>
+ </menu>
+ <menuseparator id="startHistorySeparator"
+ class="hide-if-empty-places-result"/>
+ </menupopup>
+ </menu>
+
+ <menu id="bookmarksMenu"
+ label="&bookmarksMenu.label;"
+ accesskey="&bookmarksMenu.accesskey;"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);">
+ <menupopup id="bookmarksMenuPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarkingUI.onMainMenuPopupShowing(event);
+ if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem id="bookmarksShowAll"
+ label="&showAllBookmarks2.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ <menuseparator id="organizeBookmarksSeparator"/>
+ <menuitem id="menu_bookmarkThisPage"
+ command="Browser:AddBookmarkAs"
+ observes="bookmarkThisPageBroadcaster"
+ key="addBookmarkAsKb"/>
+ <menuitem id="subscribeToPageMenuitem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&subscribeToPageMenuitem.label;"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="singleFeedMenuitemState"/>
+ <menu id="subscribeToPageMenupopup"
+#ifndef XP_MACOSX
+ class="menu-iconic"
+#endif
+ label="&subscribeToPageMenupopup.label;"
+ observes="multipleFeedsMenuState">
+ <menupopup id="subscribeToPageSubmenuMenupopup"
+ onpopupshowing="return FeedHandler.buildFeedList(event.target);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuitem id="menu_bookmarkAllTabs"
+ label="&addCurPagesCmd.label;"
+ class="show-only-for-keyboard"
+ command="Browser:BookmarkAllTabs"
+ key="bookmarkAllTabsKb"/>
+ <menuseparator/>
+ <menuitem label="&recentBookmarks.label;"
+ id="menu_recentBookmarks"
+ disabled="true"/>
+ <menuseparator id="bookmarksToolbarSeparator"/>
+ <menu id="bookmarksToolbarFolderMenu"
+ class="menu-iconic bookmark-item"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="bookmarksToolbarFolderPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menu id="menu_unsortedBookmarks"
+ class="menu-iconic bookmark-item"
+ label="&otherBookmarksCmd.label;"
+ container="true">
+ <menupopup id="otherBookmarksFolderPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS');"/>
+ </menu>
+ <menuseparator id="bookmarksMenuItemsSeparator"/>
+ <!-- Bookmarks menu items -->
+ </menupopup>
+ </menu>
+
+ <menu id="tools-menu"
+ label="&toolsMenu.label;"
+ accesskey="&toolsMenu.accesskey;"
+ onpopupshowing="mirrorShow(this)">
+ <menupopup id="menu_ToolsPopup"
+# We have to use setTimeout() here to avoid a flickering menu bar when opening
+# the Tools menu, see bug 970769. This can be removed once we got rid of the
+# event loop spinning in Weave.Status._authManager.
+ onpopupshowing="setTimeout(() => gSyncUI.updateUI());"
+ >
+ <menuitem id="menu_openDownloads"
+ label="&downloads.label;"
+ accesskey="&downloads.accesskey;"
+ key="key_openDownloads"
+ command="Tools:Downloads"/>
+ <menuitem id="menu_openAddons"
+ label="&addons.label;"
+ accesskey="&addons.accesskey;"
+ key="key_openAddons"
+ command="Tools:Addons"/>
+
+ <!-- only one of sync-setup, sync-syncnowitem or sync-reauthitem will be showing at once -->
+ <menuitem id="sync-setup"
+ label="&syncSignIn.label;"
+ accesskey="&syncSignIn.accesskey;"
+ observes="sync-setup-state"
+ oncommand="gSyncUI.openSetup(null, 'menubar')"/>
+ <menuitem id="sync-syncnowitem"
+ label="&syncSyncNowItem.label;"
+ accesskey="&syncSyncNowItem.accesskey;"
+ observes="sync-syncnow-state"
+ oncommand="gSyncUI.doSync(event);"/>
+ <menuitem id="sync-reauthitem"
+ label="&syncReAuthItem.label;"
+ accesskey="&syncReAuthItem.accesskey;"
+ observes="sync-reauth-state"
+ oncommand="gSyncUI.openSignInAgainPage('menubar');"/>
+ <menuseparator id="devToolsSeparator"/>
+ <menu id="webDeveloperMenu"
+ label="&webDeveloperMenu.label;"
+ accesskey="&webDeveloperMenu.accesskey;">
+ <menupopup id="menuWebDeveloperPopup">
+ <menuitem id="menu_pageSource"
+ observes="devtoolsMenuBroadcaster_PageSource"
+ accesskey="&pageSourceCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_pageInfo"
+ accesskey="&pageInfoCmd.accesskey;"
+ label="&pageInfoCmd.label;"
+#ifndef XP_WIN
+ key="key_viewInfo"
+#endif
+ command="View:PageInfo"/>
+ <menu id="menu_mirrorTabCmd"
+ hidden="true"
+ accesskey="&mirrorTabCmd.accesskey;"
+ label="&mirrorTabCmd.label;">
+ <menupopup id="menu_mirrorTab-popup"
+ onpopupshowing="populateMirrorTabMenu(this)"/>
+ </menu>
+#ifndef XP_UNIX
+ <menuseparator id="prefSep"/>
+ <menuitem id="menu_preferences"
+ label="&preferencesCmd2.label;"
+ accesskey="&preferencesCmd2.accesskey;"
+ oncommand="openPreferences();"/>
+#endif
+ </menupopup>
+ </menu>
+
+#ifdef XP_MACOSX
+ <menu id="windowMenu" />
+#endif
+ <menu id="helpMenu" />
+ </menubar>
diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js
new file mode 100644
index 000000000..14e90cde2
--- /dev/null
+++ b/browser/base/content/browser-places.js
@@ -0,0 +1,2021 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 StarUI = {
+ _itemId: -1,
+ uri: null,
+ _batching: false,
+ _isNewBookmark: false,
+ _isComposing: false,
+ _autoCloseTimer: 0,
+ // The autoclose timer is diasbled if the user interacts with the
+ // popup, such as making a change through typing or clicking on
+ // the popup.
+ _autoCloseTimerEnabled: true,
+
+ _element: function(aID) {
+ return document.getElementById(aID);
+ },
+
+ // Edit-bookmark panel
+ get panel() {
+ delete this.panel;
+ var element = this._element("editBookmarkPanel");
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ element.hidden = false;
+ element.addEventListener("keypress", this, false);
+ element.addEventListener("mousedown", this);
+ element.addEventListener("mouseout", this, false);
+ element.addEventListener("mousemove", this, false);
+ element.addEventListener("compositionstart", this, false);
+ element.addEventListener("compositionend", this, false);
+ element.addEventListener("input", this, false);
+ element.addEventListener("popuphidden", this, false);
+ element.addEventListener("popupshown", this, false);
+ return this.panel = element;
+ },
+
+ // Array of command elements to disable when the panel is opened.
+ get _blockedCommands() {
+ delete this._blockedCommands;
+ return this._blockedCommands =
+ ["cmd_close", "cmd_closeWindow"].map(id => this._element(id));
+ },
+
+ _blockCommands: function SU__blockCommands() {
+ this._blockedCommands.forEach(function (elt) {
+ // make sure not to permanently disable this item (see bug 409155)
+ if (elt.hasAttribute("wasDisabled"))
+ return;
+ if (elt.getAttribute("disabled") == "true") {
+ elt.setAttribute("wasDisabled", "true");
+ } else {
+ elt.setAttribute("wasDisabled", "false");
+ elt.setAttribute("disabled", "true");
+ }
+ });
+ },
+
+ _restoreCommandsState: function SU__restoreCommandsState() {
+ this._blockedCommands.forEach(function (elt) {
+ if (elt.getAttribute("wasDisabled") != "true")
+ elt.removeAttribute("disabled");
+ elt.removeAttribute("wasDisabled");
+ });
+ },
+
+ // nsIDOMEventListener
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "mousemove":
+ clearTimeout(this._autoCloseTimer);
+ // The autoclose timer is not disabled on generic mouseout
+ // because the user may not have actually interacted with the popup.
+ break;
+ case "popuphidden":
+ clearTimeout(this._autoCloseTimer);
+ if (aEvent.originalTarget == this.panel) {
+ if (!this._element("editBookmarkPanelContent").hidden)
+ this.quitEditMode();
+
+ if (this._anchorToolbarButton) {
+ this._anchorToolbarButton.removeAttribute("open");
+ this._anchorToolbarButton = null;
+ }
+ this._restoreCommandsState();
+ this._itemId = -1;
+ if (this._batching)
+ this.endBatch();
+
+ if (this._uriForRemoval) {
+ if (this._isNewBookmark) {
+ if (!PlacesUtils.useAsyncTransactions) {
+ PlacesUtils.transactionManager.undoTransaction();
+ break;
+ }
+ PlacesTransactions().undo().catch(Cu.reportError);
+ break;
+ }
+ // Remove all bookmarks for the bookmark's url, this also removes
+ // the tags for the url.
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
+ for (let itemId of itemIds) {
+ let txn = new PlacesRemoveItemTransaction(itemId);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ break;
+ }
+
+ PlacesTransactions.RemoveBookmarksForUrls([this._uriForRemoval])
+ .transact().catch(Cu.reportError);
+ }
+ }
+ break;
+ case "keypress":
+ clearTimeout(this._autoCloseTimer);
+ this._autoCloseTimerEnabled = false;
+
+ if (aEvent.defaultPrevented) {
+ // The event has already been consumed inside of the panel.
+ break;
+ }
+
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_ESCAPE:
+ this.panel.hidePopup();
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ if (aEvent.target.classList.contains("expander-up") ||
+ aEvent.target.classList.contains("expander-down") ||
+ aEvent.target.id == "editBMPanel_newFolderButton" ||
+ aEvent.target.id == "editBookmarkPanelRemoveButton") {
+ // XXX Why is this necessary? The defaultPrevented check should
+ // be enough.
+ break;
+ }
+ this.panel.hidePopup();
+ break;
+ // This case is for catching character-generating keypresses
+ case 0:
+ let accessKey = document.getElementById("key_close");
+ if (eventMatchesKey(aEvent, accessKey)) {
+ this.panel.hidePopup();
+ }
+ break;
+ }
+ break;
+ case "compositionend":
+ // After composition is committed, "mouseout" or something can set
+ // auto close timer.
+ this._isComposing = false;
+ break;
+ case "compositionstart":
+ if (aEvent.defaultPrevented) {
+ // If the composition was canceled, nothing to do here.
+ break;
+ }
+ this._isComposing = true;
+ // Explicit fall-through, during composition, panel shouldn't be
+ // hidden automatically.
+ case "input":
+ // Might have edited some text without keyboard events nor composition
+ // events. Fall-through to cancel auto close in such case.
+ case "mousedown":
+ clearTimeout(this._autoCloseTimer);
+ this._autoCloseTimerEnabled = false;
+ break;
+ case "mouseout":
+ if (!this._autoCloseTimerEnabled) {
+ // Don't autoclose the popup if the user has made a selection
+ // or keypress and then subsequently mouseout.
+ break;
+ }
+ // Explicit fall-through
+ case "popupshown":
+ // Don't handle events for descendent elements.
+ if (aEvent.target != aEvent.currentTarget) {
+ break;
+ }
+ // auto-close if new and not interacted with
+ if (this._isNewBookmark && !this._isComposing) {
+ // 3500ms matches the timeout that Pocket uses in
+ // browser/extensions/pocket/content/panels/js/saved.js
+ let delay = 3500;
+ if (this._closePanelQuickForTesting) {
+ delay /= 10;
+ }
+ clearTimeout(this._autoCloseTimer);
+ this._autoCloseTimer = setTimeout(() => {
+ if (!this.panel.mozMatchesSelector(":hover")) {
+ this.panel.hidePopup();
+ }
+ }, delay);
+ this._autoCloseTimerEnabled = true;
+ }
+ break;
+ }
+ },
+
+ _overlayLoaded: false,
+ _overlayLoading: false,
+ showEditBookmarkPopup: Task.async(function* (aNode, aAnchorElement, aPosition, aIsNewBookmark) {
+ // Slow double-clicks (not true double-clicks) shouldn't
+ // cause the panel to flicker.
+ if (this.panel.state == "showing" ||
+ this.panel.state == "open") {
+ return;
+ }
+
+ this._isNewBookmark = aIsNewBookmark;
+ this._uriForRemoval = "";
+ // TODO: Deprecate this once async transactions are enabled and the legacy
+ // transactions code is gone (bug 1131491) - we don't want addons to to use
+ // the completeNodeLikeObjectForItemId, so it's better if they keep passing
+ // the item-id for now).
+ if (typeof(aNode) == "number") {
+ let itemId = aNode;
+ if (PlacesUIUtils.useAsyncTransactions) {
+ let guid = yield PlacesUtils.promiseItemGuid(itemId);
+ aNode = yield PlacesUIUtils.promiseNodeLike(guid);
+ }
+ else {
+ aNode = { itemId };
+ yield PlacesUIUtils.completeNodeLikeObjectForItemId(aNode);
+ }
+ }
+
+ // Performance: load the overlay the first time the panel is opened
+ // (see bug 392443).
+ if (this._overlayLoading)
+ return;
+
+ if (this._overlayLoaded) {
+ this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition);
+ return;
+ }
+
+ this._overlayLoading = true;
+ document.loadOverlay(
+ "chrome://browser/content/places/editBookmarkOverlay.xul",
+ (function (aSubject, aTopic, aData) {
+ // Move the header (star, title, button) into the grid,
+ // so that it aligns nicely with the other items (bug 484022).
+ let header = this._element("editBookmarkPanelHeader");
+ let rows = this._element("editBookmarkPanelGrid").lastChild;
+ rows.insertBefore(header, rows.firstChild);
+ header.hidden = false;
+
+ this._overlayLoading = false;
+ this._overlayLoaded = true;
+ this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition);
+ }).bind(this)
+ );
+ }),
+
+ _doShowEditBookmarkPanel: Task.async(function* (aNode, aAnchorElement, aPosition) {
+ if (this.panel.state != "closed")
+ return;
+
+ this._blockCommands(); // un-done in the popuphidden handler
+
+ this._element("editBookmarkPanelTitle").value =
+ this._isNewBookmark ?
+ gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
+ gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
+
+ // No description; show the Done, Remove;
+ this._element("editBookmarkPanelDescription").textContent = "";
+ this._element("editBookmarkPanelBottomButtons").hidden = false;
+ this._element("editBookmarkPanelContent").hidden = false;
+
+ // The label of the remove button differs if the URI is bookmarked
+ // multiple times.
+ let bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
+ let forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
+ let label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
+ this._element("editBookmarkPanelRemoveButton").label = label;
+
+ // unset the unstarred state, if set
+ this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
+
+ this._itemId = aNode.itemId;
+ this.beginBatch();
+
+ if (aAnchorElement) {
+ // Set the open=true attribute if the anchor is a
+ // descendent of a toolbarbutton.
+ let parent = aAnchorElement.parentNode;
+ while (parent) {
+ if (parent.localName == "toolbarbutton") {
+ break;
+ }
+ parent = parent.parentNode;
+ }
+ if (parent) {
+ this._anchorToolbarButton = parent;
+ parent.setAttribute("open", "true");
+ }
+ }
+ let panel = this.panel;
+ let target = panel;
+ if (target.parentNode) {
+ // By targeting the panel's parent and using a capturing listener, we
+ // can have our listener called before others waiting for the panel to
+ // be shown (which probably expect the panel to be fully initialized)
+ target = target.parentNode;
+ }
+ target.addEventListener("popupshown", function shownListener(event) {
+ if (event.target == panel) {
+ target.removeEventListener("popupshown", shownListener, true);
+
+ gEditItemOverlay.initPanel({ node: aNode
+ , hiddenRows: ["description", "location",
+ "loadInSidebar", "keyword"]
+ , focusedElement: "preferred"});
+ }
+ }, true);
+
+ this.panel.openPopup(aAnchorElement, aPosition);
+ }),
+
+ panelShown:
+ function SU_panelShown(aEvent) {
+ if (aEvent.target == this.panel) {
+ if (this._element("editBookmarkPanelContent").hidden) {
+ // Note this isn't actually used anymore, we should remove this
+ // once we decide not to bring back the page bookmarked notification
+ this.panel.focus();
+ }
+ }
+ },
+
+ quitEditMode: function SU_quitEditMode() {
+ this._element("editBookmarkPanelContent").hidden = true;
+ this._element("editBookmarkPanelBottomButtons").hidden = true;
+ gEditItemOverlay.uninitPanel(true);
+ },
+
+ removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
+ this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
+ this.panel.hidePopup();
+ },
+
+ // Matching the way it is used in the Library, editBookmarkOverlay implements
+ // an instant-apply UI, having no batched-Undo/Redo support.
+ // However, in this context (the Star UI) we have a Cancel button whose
+ // expected behavior is to undo all the operations done in the panel.
+ // Sometime in the future this needs to be reimplemented using a
+ // non-instant apply code path, but for the time being, we patch-around
+ // editBookmarkOverlay so that all of the actions done in the panel
+ // are treated by PlacesTransactions as a single batch. To do so,
+ // we start a PlacesTransactions batch when the star UI panel is shown, and
+ // we keep the batch ongoing until the panel is hidden.
+ _batchBlockingDeferred: null,
+ beginBatch() {
+ if (this._batching)
+ return;
+ if (PlacesUIUtils.useAsyncTransactions) {
+ this._batchBlockingDeferred = PromiseUtils.defer();
+ PlacesTransactions.batch(function* () {
+ yield this._batchBlockingDeferred.promise;
+ }.bind(this));
+ }
+ else {
+ PlacesUtils.transactionManager.beginBatch(null);
+ }
+ this._batching = true;
+ },
+
+ endBatch() {
+ if (!this._batching)
+ return;
+
+ if (PlacesUIUtils.useAsyncTransactions) {
+ this._batchBlockingDeferred.resolve();
+ this._batchBlockingDeferred = null;
+ }
+ else {
+ PlacesUtils.transactionManager.endBatch(false);
+ }
+ this._batching = false;
+ }
+};
+
+var PlacesCommandHook = {
+ /**
+ * Adds a bookmark to the page loaded in the given browser.
+ *
+ * @param aBrowser
+ * a <browser> element.
+ * @param [optional] aParent
+ * The folder in which to create a new bookmark if the page loaded in
+ * aBrowser isn't bookmarked yet, defaults to the unfiled root.
+ * @param [optional] aShowEditUI
+ * whether or not to show the edit-bookmark UI for the bookmark item
+ */
+ bookmarkPage: Task.async(function* (aBrowser, aParent, aShowEditUI) {
+ if (PlacesUIUtils.useAsyncTransactions) {
+ yield this._bookmarkPagePT(aBrowser, aParent, aShowEditUI);
+ return;
+ }
+
+ var uri = aBrowser.currentURI;
+ var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
+ let isNewBookmark = itemId == -1;
+ if (isNewBookmark) {
+ // Bug 1148838 - Make this code work for full page plugins.
+ var title;
+ var description;
+ var charset;
+
+ let docInfo = yield this._getPageDetails(aBrowser);
+
+ try {
+ title = docInfo.isErrorPage ? PlacesUtils.history.getPageTitle(uri)
+ : aBrowser.contentTitle;
+ title = title || uri.spec;
+ description = docInfo.description;
+ charset = aBrowser.characterSet;
+ }
+ catch (e) { }
+
+ if (aShowEditUI && isNewBookmark) {
+ // If we bookmark the page here but open right into a cancelable
+ // state (i.e. new bookmark in Library), start batching here so
+ // all of the actions can be undone in a single undo step.
+ StarUI.beginBatch();
+ }
+
+ var parent = aParent !== undefined ?
+ aParent : PlacesUtils.unfiledBookmarksFolderId;
+ var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
+ var txn = new PlacesCreateBookmarkTransaction(uri, parent,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title, null, [descAnno]);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ itemId = txn.item.id;
+ // Set the character-set.
+ if (charset && !PrivateBrowsingUtils.isBrowserPrivate(aBrowser))
+ PlacesUtils.setCharsetForURI(uri, charset);
+ }
+
+ // Revert the contents of the location bar
+ gURLBar.handleRevert();
+
+ // If it was not requested to open directly in "edit" mode, we are done.
+ if (!aShowEditUI)
+ return;
+
+ // Try to dock the panel to:
+ // 1. the bookmarks menu button
+ // 2. the identity icon
+ // 3. the content area
+ if (BookmarkingUI.anchor) {
+ StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
+ "bottomcenter topright", isNewBookmark);
+ return;
+ }
+
+ let identityIcon = document.getElementById("identity-icon");
+ if (isElementVisible(identityIcon)) {
+ StarUI.showEditBookmarkPopup(itemId, identityIcon,
+ "bottomcenter topright", isNewBookmark);
+ } else {
+ StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap", isNewBookmark);
+ }
+ }),
+
+ // TODO: Replace bookmarkPage code with this function once legacy
+ // transactions are removed.
+ _bookmarkPagePT: Task.async(function* (aBrowser, aParentId, aShowEditUI) {
+ let url = new URL(aBrowser.currentURI.spec);
+ let info = yield PlacesUtils.bookmarks.fetch({ url });
+ let isNewBookmark = !info;
+ if (!info) {
+ let parentGuid = aParentId !== undefined ?
+ yield PlacesUtils.promiseItemGuid(aParentId) :
+ PlacesUtils.bookmarks.unfiledGuid;
+ info = { url, parentGuid };
+ // Bug 1148838 - Make this code work for full page plugins.
+ let description = null;
+ let charset = null;
+
+ let docInfo = yield this._getPageDetails(aBrowser);
+
+ try {
+ info.title = docInfo.isErrorPage ?
+ (yield PlacesUtils.promisePlaceInfo(aBrowser.currentURI)).title :
+ aBrowser.contentTitle;
+ info.title = info.title || url.href;
+ description = docInfo.description;
+ charset = aBrowser.characterSet;
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ if (aShowEditUI && isNewBookmark) {
+ // If we bookmark the page here but open right into a cancelable
+ // state (i.e. new bookmark in Library), start batching here so
+ // all of the actions can be undone in a single undo step.
+ StarUI.beginBatch();
+ }
+
+ if (description) {
+ info.annotations = [{ name: PlacesUIUtils.DESCRIPTION_ANNO
+ , value: description }];
+ }
+
+ info.guid = yield PlacesTransactions.NewBookmark(info).transact();
+
+ // Set the character-set
+ if (charset && !PrivateBrowsingUtils.isBrowserPrivate(aBrowser))
+ PlacesUtils.setCharsetForURI(makeURI(url.href), charset);
+ }
+
+ // Revert the contents of the location bar
+ gURLBar.handleRevert();
+
+ // If it was not requested to open directly in "edit" mode, we are done.
+ if (!aShowEditUI)
+ return;
+
+ let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(info);
+
+ // Try to dock the panel to:
+ // 1. the bookmarks menu button
+ // 2. the identity icon
+ // 3. the content area
+ if (BookmarkingUI.anchor) {
+ StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
+ "bottomcenter topright", isNewBookmark);
+ return;
+ }
+
+ let identityIcon = document.getElementById("identity-icon");
+ if (isElementVisible(identityIcon)) {
+ StarUI.showEditBookmarkPopup(node, identityIcon,
+ "bottomcenter topright", isNewBookmark);
+ } else {
+ StarUI.showEditBookmarkPopup(node, aBrowser, "overlap", isNewBookmark);
+ }
+ }),
+
+ _getPageDetails(browser) {
+ return new Promise(resolve => {
+ let mm = browser.messageManager;
+ mm.addMessageListener("Bookmarks:GetPageDetails:Result", function listener(msg) {
+ mm.removeMessageListener("Bookmarks:GetPageDetails:Result", listener);
+ resolve(msg.data);
+ });
+
+ mm.sendAsyncMessage("Bookmarks:GetPageDetails", { })
+ });
+ },
+
+ /**
+ * Adds a bookmark to the page loaded in the current tab.
+ */
+ bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
+ this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
+ },
+
+ /**
+ * Adds a bookmark to the page targeted by a link.
+ * @param aParent
+ * The folder in which to create a new bookmark if aURL isn't
+ * bookmarked.
+ * @param aURL (string)
+ * the address of the link target
+ * @param aTitle
+ * The link text
+ * @param [optional] aDescription
+ * The linked page description, if available
+ */
+ bookmarkLink: Task.async(function* (aParentId, aURL, aTitle, aDescription="") {
+ let node = yield PlacesUIUtils.fetchNodeLike({ url: aURL });
+ if (node) {
+ PlacesUIUtils.showBookmarkDialog({ action: "edit"
+ , node
+ }, window.top);
+ return;
+ }
+
+ let ip = new InsertionPoint(aParentId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Components.interfaces.nsITreeView.DROP_ON);
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(aURL)
+ , title: aTitle
+ , description: aDescription
+ , defaultInsertionPoint: ip
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window.top);
+ }),
+
+ /**
+ * List of nsIURI objects characterizing the tabs currently open in the
+ * browser, modulo pinned tabs. The URIs will be in the order in which their
+ * corresponding tabs appeared and duplicates are discarded.
+ */
+ get uniqueCurrentPages() {
+ let uniquePages = {};
+ let URIs = [];
+
+ gBrowser.visibleTabs.forEach(tab => {
+ let browser = tab.linkedBrowser;
+ let uri = browser.currentURI;
+ let title = browser.contentTitle || tab.label;
+ let spec = uri.spec;
+ if (!tab.pinned && !(spec in uniquePages)) {
+ uniquePages[spec] = null;
+ URIs.push({ uri, title });
+ }
+ });
+ return URIs;
+ },
+
+ /**
+ * Adds a folder with bookmarks to all of the currently open tabs in this
+ * window.
+ */
+ bookmarkCurrentPages: function PCH_bookmarkCurrentPages() {
+ let pages = this.uniqueCurrentPages;
+ if (pages.length > 1) {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "folder"
+ , URIList: pages
+ , hiddenRows: [ "description" ]
+ }, window);
+ }
+ },
+
+ /**
+ * Updates disabled state for the "Bookmark All Tabs" command.
+ */
+ updateBookmarkAllTabsCommand:
+ function PCH_updateBookmarkAllTabsCommand() {
+ // There's nothing to do in non-browser windows.
+ if (window.location.href != getBrowserURL())
+ return;
+
+ // Disable "Bookmark All Tabs" if there are less than two
+ // "unique current pages".
+ goSetCommandEnabled("Browser:BookmarkAllTabs",
+ this.uniqueCurrentPages.length >= 2);
+ },
+
+ /**
+ * Adds a Live Bookmark to a feed associated with the current page.
+ * @param url
+ * The nsIURI of the page the feed was attached to
+ * @title title
+ * The title of the feed. Optional.
+ * @subtitle subtitle
+ * A short description of the feed. Optional.
+ */
+ addLiveBookmark: Task.async(function *(url, feedTitle, feedSubtitle) {
+ let toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Components.interfaces.nsITreeView.DROP_ON);
+
+ let feedURI = makeURI(url);
+ let title = feedTitle || gBrowser.contentTitle;
+ let description = feedSubtitle;
+ if (!description) {
+ description = (yield this._getPageDetails(gBrowser.selectedBrowser)).description;
+ }
+
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "livemark"
+ , feedURI: feedURI
+ , siteURI: gBrowser.currentURI
+ , title: title
+ , description: description
+ , defaultInsertionPoint: toolbarIP
+ , hiddenRows: [ "feedLocation"
+ , "siteLocation"
+ , "description" ]
+ }, window);
+ }),
+
+ /**
+ * Opens the Places Organizer.
+ * @param aLeftPaneRoot
+ * The query to select in the organizer window - options
+ * are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
+ * UnfiledBookmarks, Tags and Downloads.
+ */
+ showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
+ var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
+ // Due to bug 528706, getMostRecentWindow can return closed windows.
+ if (!organizer || organizer.closed) {
+ // No currently open places window, so open one with the specified mode.
+ openDialog("chrome://browser/content/places/places.xul",
+ "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
+ }
+ else {
+ organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
+ organizer.focus();
+ }
+ }
+};
+
+XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
+ "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
+
+// View for the history menu.
+function HistoryMenu(aPopupShowingEvent) {
+ // Workaround for Bug 610187. The sidebar does not include all the Places
+ // views definitions, and we don't need them there.
+ // Defining the prototype inheritance in the prototype itself would cause
+ // browser.js to halt on "PlacesMenu is not defined" error.
+ this.__proto__.__proto__ = PlacesMenu.prototype;
+ PlacesMenu.call(this, aPopupShowingEvent,
+ "place:sort=4&maxResults=15");
+}
+
+HistoryMenu.prototype = {
+ _getClosedTabCount() {
+ // SessionStore doesn't track the hidden window, so just return zero then.
+ if (window == Services.appShell.hiddenDOMWindow) {
+ return 0;
+ }
+
+ return SessionStore.getClosedTabCount(window);
+ },
+
+ toggleRecentlyClosedTabs: function HM_toggleRecentlyClosedTabs() {
+ // enable/disable the Recently Closed Tabs sub menu
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
+
+ // no restorable tabs, so disable menu
+ if (this._getClosedTabCount() == 0)
+ undoMenu.setAttribute("disabled", true);
+ else
+ undoMenu.removeAttribute("disabled");
+ },
+
+ /**
+ * Populate when the history menu is opened
+ */
+ populateUndoSubmenu: function PHM_populateUndoSubmenu() {
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
+ var undoPopup = undoMenu.firstChild;
+
+ // remove existing menu items
+ while (undoPopup.hasChildNodes())
+ undoPopup.removeChild(undoPopup.firstChild);
+
+ // no restorable tabs, so make sure menu is disabled, and return
+ if (this._getClosedTabCount() == 0) {
+ undoMenu.setAttribute("disabled", true);
+ return;
+ }
+
+ // enable menu
+ undoMenu.removeAttribute("disabled");
+
+ // populate menu
+ let tabsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getTabsFragment(window, "menuitem");
+ undoPopup.appendChild(tabsFragment);
+ },
+
+ toggleRecentlyClosedWindows: function PHM_toggleRecentlyClosedWindows() {
+ // enable/disable the Recently Closed Windows sub menu
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
+
+ // no restorable windows, so disable menu
+ if (SessionStore.getClosedWindowCount() == 0)
+ undoMenu.setAttribute("disabled", true);
+ else
+ undoMenu.removeAttribute("disabled");
+ },
+
+ /**
+ * Populate when the history menu is opened
+ */
+ populateUndoWindowSubmenu: function PHM_populateUndoWindowSubmenu() {
+ let undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
+ let undoPopup = undoMenu.firstChild;
+
+ // remove existing menu items
+ while (undoPopup.hasChildNodes())
+ undoPopup.removeChild(undoPopup.firstChild);
+
+ // no restorable windows, so make sure menu is disabled, and return
+ if (SessionStore.getClosedWindowCount() == 0) {
+ undoMenu.setAttribute("disabled", true);
+ return;
+ }
+
+ // enable menu
+ undoMenu.removeAttribute("disabled");
+
+ // populate menu
+ let windowsFragment = RecentlyClosedTabsAndWindowsMenuUtils.getWindowsFragment(window, "menuitem");
+ undoPopup.appendChild(windowsFragment);
+ },
+
+ toggleTabsFromOtherComputers: function PHM_toggleTabsFromOtherComputers() {
+ // Enable/disable the Tabs From Other Computers menu. Some of the menus handled
+ // by HistoryMenu do not have this menuitem.
+ let menuitem = this._rootElt.getElementsByClassName("syncTabsMenuItem")[0];
+ if (!menuitem)
+ return;
+
+ if (!PlacesUIUtils.shouldShowTabsFromOtherComputersMenuitem()) {
+ menuitem.setAttribute("hidden", true);
+ return;
+ }
+
+ menuitem.setAttribute("hidden", false);
+ },
+
+ _onPopupShowing: function HM__onPopupShowing(aEvent) {
+ PlacesMenu.prototype._onPopupShowing.apply(this, arguments);
+
+ // Don't handle events for submenus.
+ if (aEvent.target != aEvent.currentTarget)
+ return;
+
+ this.toggleRecentlyClosedTabs();
+ this.toggleRecentlyClosedWindows();
+ this.toggleTabsFromOtherComputers();
+ },
+
+ _onCommand: function HM__onCommand(aEvent) {
+ let placesNode = aEvent.target._placesNode;
+ if (placesNode) {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsTyped(placesNode.uri);
+ openUILink(placesNode.uri, aEvent, { ignoreAlt: true });
+ }
+ }
+};
+
+/**
+ * Functions for handling events in the Bookmarks Toolbar and menu.
+ */
+var BookmarksEventHandler = {
+ /**
+ * Handler for click event for an item in the bookmarks toolbar or menu.
+ * Menus and submenus from the folder buttons bubble up to this handler.
+ * Left-click is handled in the onCommand function.
+ * When items are middle-clicked (or clicked with modifier), open in tabs.
+ * If the click came through a menu, close the menu.
+ * @param aEvent
+ * DOMEvent for the click
+ * @param aView
+ * The places view which aEvent should be associated with.
+ */
+ onClick: function BEH_onClick(aEvent, aView) {
+ // Only handle middle-click or left-click with modifiers.
+ let modifKey;
+ if (AppConstants.platform == "macosx") {
+ modifKey = aEvent.metaKey || aEvent.shiftKey;
+ } else {
+ modifKey = aEvent.ctrlKey || aEvent.shiftKey;
+ }
+
+ if (aEvent.button == 2 || (aEvent.button == 0 && !modifKey))
+ return;
+
+ var target = aEvent.originalTarget;
+ // If this event bubbled up from a menu or menuitem, close the menus.
+ // Do this before opening tabs, to avoid hiding the open tabs confirm-dialog.
+ if (target.localName == "menu" || target.localName == "menuitem") {
+ for (let node = target.parentNode; node; node = node.parentNode) {
+ if (node.localName == "menupopup")
+ node.hidePopup();
+ else if (node.localName != "menu" &&
+ node.localName != "splitmenu" &&
+ node.localName != "hbox" &&
+ node.localName != "vbox" )
+ break;
+ }
+ }
+
+ if (target._placesNode && PlacesUtils.nodeIsContainer(target._placesNode)) {
+ // Don't open the root folder in tabs when the empty area on the toolbar
+ // is middle-clicked or when a non-bookmark item except for Open in Tabs)
+ // in a bookmarks menupopup is middle-clicked.
+ if (target.localName == "menu" || target.localName == "toolbarbutton")
+ PlacesUIUtils.openContainerNodeInTabs(target._placesNode, aEvent, aView);
+ }
+ else if (aEvent.button == 1) {
+ // left-clicks with modifier are already served by onCommand
+ this.onCommand(aEvent, aView);
+ }
+ },
+
+ /**
+ * Handler for command event for an item in the bookmarks toolbar.
+ * Menus and submenus from the folder buttons bubble up to this handler.
+ * Opens the item.
+ * @param aEvent
+ * DOMEvent for the command
+ * @param aView
+ * The places view which aEvent should be associated with.
+ */
+ onCommand: function BEH_onCommand(aEvent, aView) {
+ var target = aEvent.originalTarget;
+ if (target._placesNode)
+ PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
+ },
+
+ fillInBHTooltip: function BEH_fillInBHTooltip(aDocument, aEvent) {
+ var node;
+ var cropped = false;
+ var targetURI;
+
+ if (aDocument.tooltipNode.localName == "treechildren") {
+ var tree = aDocument.tooltipNode.parentNode;
+ var tbo = tree.treeBoxObject;
+ var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
+ if (cell.row == -1)
+ return false;
+ node = tree.view.nodeForTreeIndex(cell.row);
+ cropped = tbo.isCellCropped(cell.row, cell.col);
+ }
+ else {
+ // Check whether the tooltipNode is a Places node.
+ // In such a case use it, otherwise check for targetURI attribute.
+ var tooltipNode = aDocument.tooltipNode;
+ if (tooltipNode._placesNode)
+ node = tooltipNode._placesNode;
+ else {
+ // This is a static non-Places node.
+ targetURI = tooltipNode.getAttribute("targetURI");
+ }
+ }
+
+ if (!node && !targetURI)
+ return false;
+
+ // Show node.label as tooltip's title for non-Places nodes.
+ var title = node ? node.title : tooltipNode.label;
+
+ // Show URL only for Places URI-nodes or nodes with a targetURI attribute.
+ var url;
+ if (targetURI || PlacesUtils.nodeIsURI(node))
+ url = targetURI || node.uri;
+
+ // Show tooltip for containers only if their title is cropped.
+ if (!cropped && !url)
+ return false;
+
+ var tooltipTitle = aDocument.getElementById("bhtTitleText");
+ tooltipTitle.hidden = (!title || (title == url));
+ if (!tooltipTitle.hidden)
+ tooltipTitle.textContent = title;
+
+ var tooltipUrl = aDocument.getElementById("bhtUrlText");
+ tooltipUrl.hidden = !url;
+ if (!tooltipUrl.hidden)
+ tooltipUrl.value = url;
+
+ // Show tooltip.
+ return true;
+ }
+};
+
+// Handles special drag and drop functionality for Places menus that are not
+// part of a Places view (e.g. the bookmarks menu in the menubar).
+var PlacesMenuDNDHandler = {
+ _springLoadDelayMs: 350,
+ _closeDelayMs: 500,
+ _loadTimer: null,
+ _closeTimer: null,
+ _closingTimerNode: null,
+
+ /**
+ * Called when the user enters the <menu> element during a drag.
+ * @param event
+ * The DragEnter event that spawned the opening.
+ */
+ onDragEnter: function PMDH_onDragEnter(event) {
+ // Opening menus in a Places popup is handled by the view itself.
+ if (!this._isStaticContainer(event.target))
+ return;
+
+ // If we re-enter the same menu or anchor before the close timer runs out,
+ // we should ensure that we do not close:
+ if (this._closeTimer && this._closingTimerNode === event.currentTarget) {
+ this._closeTimer.cancel();
+ this._closingTimerNode = null;
+ this._closeTimer = null;
+ }
+
+ PlacesControllerDragHelper.currentDropTarget = event.target;
+ let popup = event.target.lastChild;
+ if (this._loadTimer || popup.state === "showing" || popup.state === "open")
+ return;
+
+ this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._loadTimer.initWithCallback(() => {
+ this._loadTimer = null;
+ popup.setAttribute("autoopened", "true");
+ popup.showPopup(popup);
+ }, this._springLoadDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ /**
+ * Handles dragleave on the <menu> element.
+ */
+ onDragLeave: function PMDH_onDragLeave(event) {
+ // Handle menu-button separate targets.
+ if (event.relatedTarget === event.currentTarget ||
+ (event.relatedTarget &&
+ event.relatedTarget.parentNode === event.currentTarget))
+ return;
+
+ // Closing menus in a Places popup is handled by the view itself.
+ if (!this._isStaticContainer(event.target))
+ return;
+
+ PlacesControllerDragHelper.currentDropTarget = null;
+ let popup = event.target.lastChild;
+
+ if (this._loadTimer) {
+ this._loadTimer.cancel();
+ this._loadTimer = null;
+ }
+ this._closeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._closingTimerNode = event.currentTarget;
+ this._closeTimer.initWithCallback(function() {
+ this._closeTimer = null;
+ this._closingTimerNode = null;
+ let node = PlacesControllerDragHelper.currentDropTarget;
+ let inHierarchy = false;
+ while (node && !inHierarchy) {
+ inHierarchy = node == event.target;
+ node = node.parentNode;
+ }
+ if (!inHierarchy && popup && popup.hasAttribute("autoopened")) {
+ popup.removeAttribute("autoopened");
+ popup.hidePopup();
+ }
+ }, this._closeDelayMs, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ /**
+ * Determines if a XUL element represents a static container.
+ * @returns true if the element is a container element (menu or
+ *` menu-toolbarbutton), false otherwise.
+ */
+ _isStaticContainer: function PMDH__isContainer(node) {
+ let isMenu = node.localName == "menu" ||
+ (node.localName == "toolbarbutton" &&
+ (node.getAttribute("type") == "menu" ||
+ node.getAttribute("type") == "menu-button"));
+ let isStatic = !("_placesNode" in node) && node.lastChild &&
+ node.lastChild.hasAttribute("placespopup") &&
+ !node.parentNode.hasAttribute("placespopup");
+ return isMenu && isStatic;
+ },
+
+ /**
+ * Called when the user drags over the <menu> element.
+ * @param event
+ * The DragOver event.
+ */
+ onDragOver: function PMDH_onDragOver(event) {
+ let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Components.interfaces.nsITreeView.DROP_ON);
+ if (ip && PlacesControllerDragHelper.canDrop(ip, event.dataTransfer))
+ event.preventDefault();
+
+ event.stopPropagation();
+ },
+
+ /**
+ * Called when the user drops on the <menu> element.
+ * @param event
+ * The Drop event.
+ */
+ onDrop: function PMDH_onDrop(event) {
+ // Put the item at the end of bookmark menu.
+ let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Components.interfaces.nsITreeView.DROP_ON);
+ PlacesControllerDragHelper.onDrop(ip, event.dataTransfer);
+ PlacesControllerDragHelper.currentDropTarget = null;
+ event.stopPropagation();
+ }
+};
+
+/**
+ * This object handles the initialization and uninitialization of the bookmarks
+ * toolbar.
+ */
+var PlacesToolbarHelper = {
+ _place: "place:folder=TOOLBAR",
+
+ get _viewElt() {
+ return document.getElementById("PlacesToolbar");
+ },
+
+ get _placeholder() {
+ return document.getElementById("bookmarks-toolbar-placeholder");
+ },
+
+ init: function PTH_init(forceToolbarOverflowCheck) {
+ let viewElt = this._viewElt;
+ if (!viewElt || viewElt._placesView)
+ return;
+
+ // CustomizableUI.addListener is idempotent, so we can safely
+ // call this multiple times.
+ CustomizableUI.addListener(this);
+
+ // If the bookmarks toolbar item is:
+ // - not in a toolbar, or;
+ // - the toolbar is collapsed, or;
+ // - the toolbar is hidden some other way:
+ // don't initialize. Also, there is no need to initialize the toolbar if
+ // customizing, because that will happen when the customization is done.
+ let toolbar = this._getParentToolbar(viewElt);
+ if (!toolbar || toolbar.collapsed || this._isCustomizing ||
+ getComputedStyle(toolbar, "").display == "none")
+ return;
+
+ new PlacesToolbar(this._place);
+ if (forceToolbarOverflowCheck) {
+ viewElt._placesView.updateOverflowStatus();
+ }
+ this._shouldWrap = false;
+ this._setupPlaceholder();
+ },
+
+ uninit: function PTH_uninit() {
+ CustomizableUI.removeListener(this);
+ },
+
+ customizeStart: function PTH_customizeStart() {
+ try {
+ let viewElt = this._viewElt;
+ if (viewElt && viewElt._placesView)
+ viewElt._placesView.uninit();
+ } finally {
+ this._isCustomizing = true;
+ }
+ this._shouldWrap = this._getShouldWrap();
+ },
+
+ customizeChange: function PTH_customizeChange() {
+ this._setupPlaceholder();
+ },
+
+ _setupPlaceholder: function PTH_setupPlaceholder() {
+ let placeholder = this._placeholder;
+ if (!placeholder) {
+ return;
+ }
+
+ let shouldWrapNow = this._getShouldWrap();
+ if (this._shouldWrap != shouldWrapNow) {
+ if (shouldWrapNow) {
+ placeholder.setAttribute("wrap", "true");
+ } else {
+ placeholder.removeAttribute("wrap");
+ }
+ this._shouldWrap = shouldWrapNow;
+ }
+ },
+
+ customizeDone: function PTH_customizeDone() {
+ this._isCustomizing = false;
+ this.init(true);
+ },
+
+ _getShouldWrap: function PTH_getShouldWrap() {
+ let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
+ let area = placement && placement.area;
+ let areaType = area && CustomizableUI.getAreaType(area);
+ return !area || CustomizableUI.TYPE_MENU_PANEL == areaType;
+ },
+
+ onPlaceholderCommand: function () {
+ let widgetGroup = CustomizableUI.getWidget("personal-bookmarks");
+ let widget = widgetGroup.forWindow(window);
+ if (widget.overflowed ||
+ widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ PlacesCommandHook.showPlacesOrganizer("BookmarksToolbar");
+ }
+ },
+
+ _getParentToolbar: function(element) {
+ while (element) {
+ if (element.localName == "toolbar") {
+ return element;
+ }
+ element = element.parentNode;
+ }
+ return null;
+ },
+
+ onWidgetUnderflow: function(aNode, aContainer) {
+ // The view gets broken by being removed and reinserted by the overflowable
+ // toolbar, so we have to force an uninit and reinit.
+ let win = aNode.ownerGlobal;
+ if (aNode.id == "personal-bookmarks" && win == window) {
+ this._resetView();
+ }
+ },
+
+ onWidgetAdded: function(aWidgetId, aArea, aPosition) {
+ if (aWidgetId == "personal-bookmarks" && !this._isCustomizing) {
+ // It's possible (with the "Add to Menu", "Add to Toolbar" context
+ // options) that the Places Toolbar Items have been moved without
+ // letting us prepare and handle it with with customizeStart and
+ // customizeDone. If that's the case, we need to reset the views
+ // since they're probably broken from the DOM reparenting.
+ this._resetView();
+ }
+ },
+
+ _resetView: function() {
+ if (this._viewElt) {
+ // It's possible that the placesView might not exist, and we need to
+ // do a full init. This could happen if the Bookmarks Toolbar Items are
+ // moved to the Menu Panel, and then to the toolbar with the "Add to Toolbar"
+ // context menu option, outside of customize mode.
+ if (this._viewElt._placesView) {
+ this._viewElt._placesView.uninit();
+ }
+ this.init(true);
+ }
+ },
+};
+
+/**
+ * Handles the bookmarks menu-button in the toolbar.
+ */
+
+var BookmarkingUI = {
+ BOOKMARK_BUTTON_ID: "bookmarks-menu-button",
+ BOOKMARK_BUTTON_SHORTCUT: "addBookmarkAsKb",
+ get button() {
+ delete this.button;
+ let widgetGroup = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID);
+ return this.button = widgetGroup.forWindow(window).node;
+ },
+
+ /* Can't make this a self-deleting getter because it's anonymous content
+ * and might lose/regain bindings at some point. */
+ get star() {
+ return document.getAnonymousElementByAttribute(this.button, "anonid",
+ "button");
+ },
+
+ get anchor() {
+ if (!this._shouldUpdateStarState()) {
+ return null;
+ }
+ let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
+ .forWindow(window);
+ if (widget.overflowed)
+ return widget.anchor;
+
+ let star = this.star;
+ return star ? document.getAnonymousElementByAttribute(star, "class",
+ "toolbarbutton-icon")
+ : null;
+ },
+
+ get notifier() {
+ delete this.notifier;
+ return this.notifier = document.getElementById("bookmarked-notification-anchor");
+ },
+
+ get dropmarkerNotifier() {
+ delete this.dropmarkerNotifier;
+ return this.dropmarkerNotifier = document.getElementById("bookmarked-notification-dropmarker-anchor");
+ },
+
+ get broadcaster() {
+ delete this.broadcaster;
+ let broadcaster = document.getElementById("bookmarkThisPageBroadcaster");
+ return this.broadcaster = broadcaster;
+ },
+
+ STATUS_UPDATING: -1,
+ STATUS_UNSTARRED: 0,
+ STATUS_STARRED: 1,
+ get status() {
+ if (!this._shouldUpdateStarState()) {
+ return this.STATUS_UNSTARRED;
+ }
+ if (this._pendingStmt)
+ return this.STATUS_UPDATING;
+ return this.button.hasAttribute("starred") ? this.STATUS_STARRED
+ : this.STATUS_UNSTARRED;
+ },
+
+ get _starredTooltip()
+ {
+ delete this._starredTooltip;
+ return this._starredTooltip =
+ this._getFormattedTooltip("starButtonOn.tooltip2");
+ },
+
+ get _unstarredTooltip()
+ {
+ delete this._unstarredTooltip;
+ return this._unstarredTooltip =
+ this._getFormattedTooltip("starButtonOff.tooltip2");
+ },
+
+ _getFormattedTooltip: function(strId) {
+ let args = [];
+ let shortcut = document.getElementById(this.BOOKMARK_BUTTON_SHORTCUT);
+ if (shortcut)
+ args.push(ShortcutUtils.prettifyShortcut(shortcut));
+ return gNavigatorBundle.getFormattedString(strId, args);
+ },
+
+ /**
+ * The type of the area in which the button is currently located.
+ * When in the panel, we don't update the button's icon.
+ */
+ _currentAreaType: null,
+ _shouldUpdateStarState: function() {
+ return this._currentAreaType == CustomizableUI.TYPE_TOOLBAR;
+ },
+
+ /**
+ * The popup contents must be updated when the user customizes the UI, or
+ * changes the personal toolbar collapsed status. In such a case, any needed
+ * change should be handled in the popupshowing helper, for performance
+ * reasons.
+ */
+ _popupNeedsUpdate: true,
+ onToolbarVisibilityChange: function BUI_onToolbarVisibilityChange() {
+ this._popupNeedsUpdate = true;
+ },
+
+ onPopupShowing: function BUI_onPopupShowing(event) {
+ // Don't handle events for submenus.
+ if (event.target != event.currentTarget)
+ return;
+
+ // Ideally this code would never be reached, but if you click the outer
+ // button's border, some cpp code for the menu button's so-called XBL binding
+ // decides to open the popup even though the dropmarker is invisible.
+ if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
+ this._showSubview();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
+ .forWindow(window);
+ if (widget.overflowed) {
+ // Don't open a popup in the overflow popup, rather just open the Library.
+ event.preventDefault();
+ widget.node.removeAttribute("closemenu");
+ PlacesCommandHook.showPlacesOrganizer("BookmarksMenu");
+ return;
+ }
+
+ this._initRecentBookmarks(document.getElementById("BMB_recentBookmarks"),
+ "subviewbutton");
+
+ if (!this._popupNeedsUpdate)
+ return;
+ this._popupNeedsUpdate = false;
+
+ let popup = event.target;
+ let getPlacesAnonymousElement =
+ aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
+ "placesanonid",
+ aAnonId);
+
+ let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
+ if (viewToolbarMenuitem) {
+ // Update View bookmarks toolbar checkbox menuitem.
+ viewToolbarMenuitem.classList.add("subviewbutton");
+ let personalToolbar = document.getElementById("PersonalToolbar");
+ viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
+ }
+ },
+
+ attachPlacesView: function(event, node) {
+ // If the view is already there, bail out early.
+ if (node.parentNode._placesView)
+ return;
+
+ new PlacesMenu(event, "place:folder=BOOKMARKS_MENU", {
+ extraClasses: {
+ entry: "subviewbutton",
+ footer: "panel-subview-footer"
+ },
+ insertionPoint: ".panel-subview-footer"
+ });
+ },
+
+ RECENTLY_BOOKMARKED_PREF: "browser.bookmarks.showRecentlyBookmarked",
+
+ _initRecentBookmarks(aHeaderItem, aExtraCSSClass) {
+ this._populateRecentBookmarks(aHeaderItem, aExtraCSSClass);
+
+ // Add observers and listeners and remove them again when the menupopup closes.
+
+ let bookmarksMenu = aHeaderItem.parentNode;
+ let placesContextMenu = document.getElementById("placesContext");
+
+ let prefObserver = () => {
+ this._populateRecentBookmarks(aHeaderItem, aExtraCSSClass);
+ };
+
+ this._recentlyBookmarkedObserver = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver,
+ Ci.nsISupportsWeakReference
+ ])
+ };
+ this._recentlyBookmarkedObserver.onItemRemoved = () => {
+ // Update the menu when a bookmark has been removed.
+ // The native menubar on Mac doesn't support live update, so this won't
+ // work there.
+ this._populateRecentBookmarks(aHeaderItem, aExtraCSSClass);
+ };
+
+ let updatePlacesContextMenu = (shouldHidePrefUI = false) => {
+ let prefEnabled = !shouldHidePrefUI && Services.prefs.getBoolPref(this.RECENTLY_BOOKMARKED_PREF);
+ let showItem = document.getElementById("placesContext_showRecentlyBookmarked");
+ let hideItem = document.getElementById("placesContext_hideRecentlyBookmarked");
+ let separator = document.getElementById("placesContext_recentlyBookmarkedSeparator");
+ showItem.hidden = shouldHidePrefUI || prefEnabled;
+ hideItem.hidden = shouldHidePrefUI || !prefEnabled;
+ separator.hidden = shouldHidePrefUI;
+ if (!shouldHidePrefUI) {
+ // Move to the bottom of the menu.
+ separator.parentNode.appendChild(separator);
+ showItem.parentNode.appendChild(showItem);
+ hideItem.parentNode.appendChild(hideItem);
+ }
+ };
+
+ let onPlacesContextMenuShowing = event => {
+ if (event.target == event.currentTarget) {
+ let triggerPopup = event.target.triggerNode;
+ while (triggerPopup && triggerPopup.localName != "menupopup") {
+ triggerPopup = triggerPopup.parentNode;
+ }
+ let shouldHidePrefUI = triggerPopup != bookmarksMenu;
+ updatePlacesContextMenu(shouldHidePrefUI);
+ }
+ };
+
+ let onBookmarksMenuHidden = event => {
+ if (event.target == event.currentTarget) {
+ updatePlacesContextMenu(true);
+
+ Services.prefs.removeObserver(this.RECENTLY_BOOKMARKED_PREF, prefObserver, false);
+ PlacesUtils.bookmarks.removeObserver(this._recentlyBookmarkedObserver);
+ this._recentlyBookmarkedObserver = null;
+ if (placesContextMenu) {
+ placesContextMenu.removeEventListener("popupshowing", onPlacesContextMenuShowing);
+ }
+ bookmarksMenu.removeEventListener("popuphidden", onBookmarksMenuHidden);
+ }
+ };
+
+ Services.prefs.addObserver(this.RECENTLY_BOOKMARKED_PREF, prefObserver, false);
+ PlacesUtils.bookmarks.addObserver(this._recentlyBookmarkedObserver, true);
+
+ // The context menu doesn't exist in non-browser windows on Mac
+ if (placesContextMenu) {
+ placesContextMenu.addEventListener("popupshowing", onPlacesContextMenuShowing);
+ }
+
+ bookmarksMenu.addEventListener("popuphidden", onBookmarksMenuHidden);
+ },
+
+ _populateRecentBookmarks(aHeaderItem, aExtraCSSClass = "") {
+ while (aHeaderItem.nextSibling &&
+ aHeaderItem.nextSibling.localName == "menuitem") {
+ aHeaderItem.nextSibling.remove();
+ }
+
+ let shouldShow = Services.prefs.getBoolPref(this.RECENTLY_BOOKMARKED_PREF);
+ let separator = aHeaderItem.previousSibling;
+ aHeaderItem.hidden = !shouldShow;
+ separator.hidden = !shouldShow;
+
+ if (!shouldShow) {
+ return;
+ }
+
+ const kMaxResults = 5;
+
+ let options = PlacesUtils.history.getNewQueryOptions();
+ options.excludeQueries = true;
+ options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ options.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
+ options.maxResults = kMaxResults;
+ let query = PlacesUtils.history.getNewQuery();
+
+ let fragment = document.createDocumentFragment();
+ let root = PlacesUtils.history.executeQuery(query, options).root;
+ root.containerOpen = true;
+ for (let i = 0; i < root.childCount; i++) {
+ let node = root.getChild(i);
+ let uri = node.uri;
+ let title = node.title;
+ let icon = node.icon;
+
+ let item =
+ document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menuitem");
+ item.setAttribute("label", title || uri);
+ item.setAttribute("targetURI", uri);
+ item.setAttribute("simulated-places-node", true);
+ item.setAttribute("class", "menuitem-iconic menuitem-with-favicon bookmark-item " +
+ aExtraCSSClass);
+ if (icon) {
+ item.setAttribute("image", icon);
+ }
+ item._placesNode = node;
+ fragment.appendChild(item);
+ }
+ root.containerOpen = false;
+ aHeaderItem.parentNode.insertBefore(fragment, aHeaderItem.nextSibling);
+ },
+
+ showRecentlyBookmarked() {
+ Services.prefs.setBoolPref(this.RECENTLY_BOOKMARKED_PREF, true);
+ },
+
+ hideRecentlyBookmarked() {
+ Services.prefs.setBoolPref(this.RECENTLY_BOOKMARKED_PREF, false);
+ },
+
+ _updateCustomizationState: function BUI__updateCustomizationState() {
+ let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
+ this._currentAreaType = placement && CustomizableUI.getAreaType(placement.area);
+ },
+
+ _uninitView: function BUI__uninitView() {
+ // When an element with a placesView attached is removed and re-inserted,
+ // XBL reapplies the binding causing any kind of issues and possible leaks,
+ // so kill current view and let popupshowing generate a new one.
+ if (this.button._placesView)
+ this.button._placesView.uninit();
+
+ // We have to do the same thing for the "special" views underneath the
+ // the bookmarks menu.
+ const kSpecialViewNodeIDs = ["BMB_bookmarksToolbar", "BMB_unsortedBookmarks"];
+ for (let viewNodeID of kSpecialViewNodeIDs) {
+ let elem = document.getElementById(viewNodeID);
+ if (elem && elem._placesView) {
+ elem._placesView.uninit();
+ }
+ }
+ },
+
+ onCustomizeStart: function BUI_customizeStart(aWindow) {
+ if (aWindow == window) {
+ this._uninitView();
+ this._isCustomizing = true;
+ }
+ },
+
+ onWidgetAdded: function BUI_widgetAdded(aWidgetId) {
+ if (aWidgetId == this.BOOKMARK_BUTTON_ID) {
+ this._onWidgetWasMoved();
+ }
+ },
+
+ onWidgetRemoved: function BUI_widgetRemoved(aWidgetId) {
+ if (aWidgetId == this.BOOKMARK_BUTTON_ID) {
+ this._onWidgetWasMoved();
+ }
+ },
+
+ onWidgetReset: function BUI_widgetReset(aNode, aContainer) {
+ if (aNode == this.button) {
+ this._onWidgetWasMoved();
+ }
+ },
+
+ onWidgetUndoMove: function BUI_undoWidgetUndoMove(aNode, aContainer) {
+ if (aNode == this.button) {
+ this._onWidgetWasMoved();
+ }
+ },
+
+ _onWidgetWasMoved: function BUI_widgetWasMoved() {
+ let usedToUpdateStarState = this._shouldUpdateStarState();
+ this._updateCustomizationState();
+ if (!usedToUpdateStarState && this._shouldUpdateStarState()) {
+ this.updateStarState();
+ } else if (usedToUpdateStarState && !this._shouldUpdateStarState()) {
+ this._updateStar();
+ }
+ // If we're moved outside of customize mode, we need to uninit
+ // our view so it gets reconstructed.
+ if (!this._isCustomizing) {
+ this._uninitView();
+ }
+ },
+
+ onCustomizeEnd: function BUI_customizeEnd(aWindow) {
+ if (aWindow == window) {
+ this._isCustomizing = false;
+ this.onToolbarVisibilityChange();
+ }
+ },
+
+ init: function() {
+ CustomizableUI.addListener(this);
+ this._updateCustomizationState();
+ },
+
+ _hasBookmarksObserver: false,
+ _itemIds: [],
+ uninit: function BUI_uninit() {
+ this._updateBookmarkPageMenuItem(true);
+ CustomizableUI.removeListener(this);
+
+ this._uninitView();
+
+ if (this._hasBookmarksObserver) {
+ PlacesUtils.removeLazyBookmarkObserver(this);
+ }
+
+ if (this._pendingStmt) {
+ this._pendingStmt.cancel();
+ delete this._pendingStmt;
+ }
+ },
+
+ onLocationChange: function BUI_onLocationChange() {
+ if (this._uri && gBrowser.currentURI.equals(this._uri)) {
+ return;
+ }
+ this.updateStarState();
+ },
+
+ updateStarState: function BUI_updateStarState() {
+ // Reset tracked values.
+ this._uri = gBrowser.currentURI;
+ this._itemIds = [];
+
+ if (this._pendingStmt) {
+ this._pendingStmt.cancel();
+ delete this._pendingStmt;
+ }
+
+ this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, (aItemIds, aURI) => {
+ // Safety check that the bookmarked URI equals the tracked one.
+ if (!aURI.equals(this._uri)) {
+ Components.utils.reportError("BookmarkingUI did not receive current URI");
+ return;
+ }
+
+ // It's possible that onItemAdded gets called before the async statement
+ // calls back. For such an edge case, retain all unique entries from both
+ // arrays.
+ this._itemIds = this._itemIds.filter(
+ id => !aItemIds.includes(id)
+ ).concat(aItemIds);
+
+ this._updateStar();
+
+ // Start observing bookmarks if needed.
+ if (!this._hasBookmarksObserver) {
+ try {
+ PlacesUtils.addLazyBookmarkObserver(this);
+ this._hasBookmarksObserver = true;
+ } catch (ex) {
+ Components.utils.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
+ }
+ }
+
+ delete this._pendingStmt;
+ });
+ },
+
+ _updateStar: function BUI__updateStar() {
+ if (!this._shouldUpdateStarState()) {
+ if (this.broadcaster.hasAttribute("starred")) {
+ this.broadcaster.removeAttribute("starred");
+ this.broadcaster.removeAttribute("buttontooltiptext");
+ }
+ return;
+ }
+
+ if (this._itemIds.length > 0) {
+ this.broadcaster.setAttribute("starred", "true");
+ this.broadcaster.setAttribute("buttontooltiptext", this._starredTooltip);
+ if (this.button.getAttribute("overflowedItem") == "true") {
+ this.button.setAttribute("label", this._starButtonOverflowedStarredLabel);
+ }
+ }
+ else {
+ this.broadcaster.removeAttribute("starred");
+ this.broadcaster.setAttribute("buttontooltiptext", this._unstarredTooltip);
+ if (this.button.getAttribute("overflowedItem") == "true") {
+ this.button.setAttribute("label", this._starButtonOverflowedLabel);
+ }
+ }
+ },
+
+ /**
+ * forceReset is passed when we're destroyed and the label should go back
+ * to the default (Bookmark This Page) for OS X.
+ */
+ _updateBookmarkPageMenuItem: function BUI__updateBookmarkPageMenuItem(forceReset) {
+ let isStarred = !forceReset && this._itemIds.length > 0;
+ let label = isStarred ? "editlabel" : "bookmarklabel";
+ if (this.broadcaster) {
+ this.broadcaster.setAttribute("label", this.broadcaster.getAttribute(label));
+ }
+ },
+
+ onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
+ // Don't handle events for submenus.
+ if (event.target != event.currentTarget)
+ return;
+
+ this._updateBookmarkPageMenuItem();
+ PlacesCommandHook.updateBookmarkAllTabsCommand();
+ this._initRecentBookmarks(document.getElementById("menu_recentBookmarks"));
+ },
+
+ _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
+ function getCenteringTransformForRects(rectToPosition, referenceRect) {
+ let topDiff = referenceRect.top - rectToPosition.top;
+ let leftDiff = referenceRect.left - rectToPosition.left;
+ let heightDiff = referenceRect.height - rectToPosition.height;
+ let widthDiff = referenceRect.width - rectToPosition.width;
+ return [(leftDiff + .5 * widthDiff) + "px", (topDiff + .5 * heightDiff) + "px"];
+ }
+
+ if (this._notificationTimeout) {
+ clearTimeout(this._notificationTimeout);
+ }
+
+ if (this.notifier.style.transform == '') {
+ // Get all the relevant nodes and computed style objects
+ let dropmarker = document.getAnonymousElementByAttribute(this.button, "anonid", "dropmarker");
+ let dropmarkerIcon = document.getAnonymousElementByAttribute(dropmarker, "class", "dropmarker-icon");
+ let dropmarkerStyle = getComputedStyle(dropmarkerIcon);
+
+ // Check for RTL and get bounds
+ let isRTL = getComputedStyle(this.button).direction == "rtl";
+ let buttonRect = this.button.getBoundingClientRect();
+ let notifierRect = this.notifier.getBoundingClientRect();
+ let dropmarkerRect = dropmarkerIcon.getBoundingClientRect();
+ let dropmarkerNotifierRect = this.dropmarkerNotifier.getBoundingClientRect();
+
+ // Compute, but do not set, transform for star icon
+ let [translateX, translateY] = getCenteringTransformForRects(notifierRect, buttonRect);
+ let starIconTransform = "translate(" + translateX + ", " + translateY + ")";
+ if (isRTL) {
+ starIconTransform += " scaleX(-1)";
+ }
+
+ // Compute, but do not set, transform for dropmarker
+ [translateX, translateY] = getCenteringTransformForRects(dropmarkerNotifierRect, dropmarkerRect);
+ let dropmarkerTransform = "translate(" + translateX + ", " + translateY + ")";
+
+ // Do all layout invalidation in one go:
+ this.notifier.style.transform = starIconTransform;
+ this.dropmarkerNotifier.style.transform = dropmarkerTransform;
+
+ let dropmarkerAnimationNode = this.dropmarkerNotifier.firstChild;
+ dropmarkerAnimationNode.style.MozImageRegion = dropmarkerStyle.MozImageRegion;
+ dropmarkerAnimationNode.style.listStyleImage = dropmarkerStyle.listStyleImage;
+ }
+
+ let isInOverflowPanel = this.button.getAttribute("overflowedItem") == "true";
+ if (!isInOverflowPanel) {
+ this.notifier.setAttribute("notification", "finish");
+ this.button.setAttribute("notification", "finish");
+ this.dropmarkerNotifier.setAttribute("notification", "finish");
+ }
+
+ this._notificationTimeout = setTimeout( () => {
+ this.notifier.removeAttribute("notification");
+ this.dropmarkerNotifier.removeAttribute("notification");
+ this.button.removeAttribute("notification");
+
+ this.dropmarkerNotifier.style.transform = '';
+ this.notifier.style.transform = '';
+ }, 1000);
+ },
+
+ _showSubview: function() {
+ let view = document.getElementById("PanelUI-bookmarks");
+ view.addEventListener("ViewShowing", this);
+ view.addEventListener("ViewHiding", this);
+ let anchor = document.getElementById(this.BOOKMARK_BUTTON_ID);
+ anchor.setAttribute("closemenu", "none");
+ PanelUI.showSubView("PanelUI-bookmarks", anchor,
+ CustomizableUI.AREA_PANEL);
+ },
+
+ onCommand: function BUI_onCommand(aEvent) {
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+
+ // Handle special case when the button is in the panel.
+ let isBookmarked = this._itemIds.length > 0;
+
+ if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
+ this._showSubview();
+ return;
+ }
+ let widget = CustomizableUI.getWidget(this.BOOKMARK_BUTTON_ID)
+ .forWindow(window);
+ if (widget.overflowed) {
+ // Close the overflow panel because the Edit Bookmark panel will appear.
+ widget.node.removeAttribute("closemenu");
+ }
+
+ // Ignore clicks on the star if we are updating its state.
+ if (!this._pendingStmt) {
+ if (!isBookmarked)
+ this._showBookmarkedNotification();
+ PlacesCommandHook.bookmarkCurrentPage(true);
+ }
+ },
+
+ onCurrentPageContextPopupShowing() {
+ this._updateBookmarkPageMenuItem();
+ },
+
+ handleEvent: function BUI_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "ViewShowing":
+ this.onPanelMenuViewShowing(aEvent);
+ break;
+ case "ViewHiding":
+ this.onPanelMenuViewHiding(aEvent);
+ break;
+ }
+ },
+
+ onPanelMenuViewShowing: function BUI_onViewShowing(aEvent) {
+ this._updateBookmarkPageMenuItem();
+ // Update checked status of the toolbar toggle.
+ let viewToolbar = document.getElementById("panelMenu_viewBookmarksToolbar");
+ let personalToolbar = document.getElementById("PersonalToolbar");
+ if (personalToolbar.collapsed)
+ viewToolbar.removeAttribute("checked");
+ else
+ viewToolbar.setAttribute("checked", "true");
+ // Get all statically placed buttons to supply them with keyboard shortcuts.
+ let staticButtons = viewToolbar.parentNode.getElementsByTagName("toolbarbutton");
+ for (let i = 0, l = staticButtons.length; i < l; ++i)
+ CustomizableUI.addShortcut(staticButtons[i]);
+ // Setup the Places view.
+ this._panelMenuView = new PlacesPanelMenuView("place:folder=BOOKMARKS_MENU",
+ "panelMenu_bookmarksMenu",
+ "panelMenu_bookmarksMenu", {
+ extraClasses: {
+ entry: "subviewbutton",
+ footer: "panel-subview-footer"
+ }
+ });
+ aEvent.target.removeEventListener("ViewShowing", this);
+ },
+
+ onPanelMenuViewHiding: function BUI_onViewHiding(aEvent) {
+ this._panelMenuView.uninit();
+ delete this._panelMenuView;
+ aEvent.target.removeEventListener("ViewHiding", this);
+ },
+
+ onPanelMenuViewCommand: function BUI_onPanelMenuViewCommand(aEvent, aView) {
+ let target = aEvent.originalTarget;
+ if (!target._placesNode)
+ return;
+ if (PlacesUtils.nodeIsContainer(target._placesNode))
+ PlacesCommandHook.showPlacesOrganizer([ "BookmarksMenu", target._placesNode.itemId ]);
+ else
+ PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
+ PanelUI.hide();
+ },
+
+ // nsINavBookmarkObserver
+ onItemAdded: function BUI_onItemAdded(aItemId, aParentId, aIndex, aItemType,
+ aURI) {
+ if (aURI && aURI.equals(this._uri)) {
+ // If a new bookmark has been added to the tracked uri, register it.
+ if (!this._itemIds.includes(aItemId)) {
+ this._itemIds.push(aItemId);
+ // Only need to update the UI if it wasn't marked as starred before:
+ if (this._itemIds.length == 1) {
+ this._updateStar();
+ }
+ }
+ }
+ },
+
+ onItemRemoved: function BUI_onItemRemoved(aItemId) {
+ let index = this._itemIds.indexOf(aItemId);
+ // If one of the tracked bookmarks has been removed, unregister it.
+ if (index != -1) {
+ this._itemIds.splice(index, 1);
+ // Only need to update the UI if the page is no longer starred
+ if (this._itemIds.length == 0) {
+ this._updateStar();
+ }
+ }
+ },
+
+ onItemChanged: function BUI_onItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty, aNewValue) {
+ if (aProperty == "uri") {
+ let index = this._itemIds.indexOf(aItemId);
+ // If the changed bookmark was tracked, check if it is now pointing to
+ // a different uri and unregister it.
+ if (index != -1 && aNewValue != this._uri.spec) {
+ this._itemIds.splice(index, 1);
+ // Only need to update the UI if the page is no longer starred
+ if (this._itemIds.length == 0) {
+ this._updateStar();
+ }
+ }
+ // If another bookmark is now pointing to the tracked uri, register it.
+ else if (index == -1 && aNewValue == this._uri.spec) {
+ this._itemIds.push(aItemId);
+ // Only need to update the UI if it wasn't marked as starred before:
+ if (this._itemIds.length == 1) {
+ this._updateStar();
+ }
+ }
+ }
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onBeforeItemRemoved: function () {},
+ onItemVisited: function () {},
+ onItemMoved: function () {},
+
+ // CustomizableUI events:
+ _starButtonLabel: null,
+ get _starButtonOverflowedLabel() {
+ delete this._starButtonOverflowedLabel;
+ return this._starButtonOverflowedLabel =
+ gNavigatorBundle.getString("starButtonOverflowed.label");
+ },
+ get _starButtonOverflowedStarredLabel() {
+ delete this._starButtonOverflowedStarredLabel;
+ return this._starButtonOverflowedStarredLabel =
+ gNavigatorBundle.getString("starButtonOverflowedStarred.label");
+ },
+ onWidgetOverflow: function(aNode, aContainer) {
+ let win = aNode.ownerGlobal;
+ if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
+ return;
+
+ let currentLabel = aNode.getAttribute("label");
+ if (!this._starButtonLabel)
+ this._starButtonLabel = currentLabel;
+
+ if (currentLabel == this._starButtonLabel) {
+ let desiredLabel = this._itemIds.length > 0 ? this._starButtonOverflowedStarredLabel
+ : this._starButtonOverflowedLabel;
+ aNode.setAttribute("label", desiredLabel);
+ }
+ },
+
+ onWidgetUnderflow: function(aNode, aContainer) {
+ let win = aNode.ownerGlobal;
+ if (aNode.id != this.BOOKMARK_BUTTON_ID || win != window)
+ return;
+
+ // The view gets broken by being removed and reinserted. Uninit
+ // here so popupshowing will generate a new one:
+ this._uninitView();
+
+ if (aNode.getAttribute("label") != this._starButtonLabel)
+ aNode.setAttribute("label", this._starButtonLabel);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver
+ ])
+};
+
+var AutoShowBookmarksToolbar = {
+ init() {
+ Services.obs.addObserver(this, "autoshow-bookmarks-toolbar", false);
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, "autoshow-bookmarks-toolbar");
+ },
+
+ observe(subject, topic, data) {
+ let toolbar = document.getElementById("PersonalToolbar");
+ if (!toolbar.collapsed)
+ return;
+
+ let placement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
+ let area = placement && placement.area;
+ if (area != CustomizableUI.AREA_BOOKMARKS)
+ return;
+
+ setToolbarVisibility(toolbar, true);
+ }
+};
diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js
new file mode 100644
index 000000000..ad070df12
--- /dev/null
+++ b/browser/base/content/browser-plugins.js
@@ -0,0 +1,548 @@
+/* -*- 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 gPluginHandler = {
+ PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
+ PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
+ MESSAGES: [
+ "PluginContent:ShowClickToPlayNotification",
+ "PluginContent:RemoveNotification",
+ "PluginContent:UpdateHiddenPluginUI",
+ "PluginContent:HideNotificationBar",
+ "PluginContent:InstallSinglePlugin",
+ "PluginContent:ShowPluginCrashedNotification",
+ "PluginContent:SubmitReport",
+ "PluginContent:LinkClickCallback",
+ ],
+
+ init: function () {
+ const mm = window.messageManager;
+ for (let msg of this.MESSAGES) {
+ mm.addMessageListener(msg, this);
+ }
+ window.addEventListener("unload", this);
+ },
+
+ uninit: function () {
+ const mm = window.messageManager;
+ for (let msg of this.MESSAGES) {
+ mm.removeMessageListener(msg, this);
+ }
+ window.removeEventListener("unload", this);
+ },
+
+ handleEvent: function (event) {
+ if (event.type == "unload") {
+ this.uninit();
+ }
+ },
+
+ receiveMessage: function (msg) {
+ switch (msg.name) {
+ case "PluginContent:ShowClickToPlayNotification":
+ this.showClickToPlayNotification(msg.target, msg.data.plugins, msg.data.showNow,
+ msg.principal, msg.data.location);
+ break;
+ case "PluginContent:RemoveNotification":
+ this.removeNotification(msg.target, msg.data.name);
+ break;
+ case "PluginContent:UpdateHiddenPluginUI":
+ this.updateHiddenPluginUI(msg.target, msg.data.haveInsecure, msg.data.actions,
+ msg.principal, msg.data.location);
+ break;
+ case "PluginContent:HideNotificationBar":
+ this.hideNotificationBar(msg.target, msg.data.name);
+ break;
+ case "PluginContent:InstallSinglePlugin":
+ this.installSinglePlugin(msg.data.pluginInfo);
+ break;
+ case "PluginContent:ShowPluginCrashedNotification":
+ this.showPluginCrashedNotification(msg.target, msg.data.messageString,
+ msg.data.pluginID);
+ break;
+ case "PluginContent:SubmitReport":
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ this.submitReport(msg.data.runID, msg.data.keyVals, msg.data.submitURLOptIn);
+ }
+ break;
+ case "PluginContent:LinkClickCallback":
+ switch (msg.data.name) {
+ case "managePlugins":
+ case "openHelpPage":
+ case "openPluginUpdatePage":
+ this[msg.data.name].call(this, msg.data.pluginTag);
+ break;
+ }
+ break;
+ default:
+ Cu.reportError("gPluginHandler did not expect to handle message " + msg.name);
+ break;
+ }
+ },
+
+ // Callback for user clicking on a disabled plugin
+ managePlugins: function () {
+ BrowserOpenAddonsMgr("addons://list/plugin");
+ },
+
+ // Callback for user clicking on the link in a click-to-play plugin
+ // (where the plugin has an update)
+ openPluginUpdatePage: function(pluginTag) {
+ let url = Services.blocklist.getPluginInfoURL(pluginTag);
+ if (!url) {
+ url = Services.blocklist.getPluginBlocklistURL(pluginTag);
+ }
+ openUILinkIn(url, "tab");
+ },
+
+ submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
+ if (!AppConstants.MOZ_CRASHREPORTER) {
+ return;
+ }
+ Services.prefs.setBoolPref("dom.ipc.plugins.reportCrashURL", submitURLOptIn);
+ PluginCrashReporter.submitCrashReport(runID, keyVals);
+ },
+
+ // Callback for user clicking a "reload page" link
+ reloadPage: function (browser) {
+ browser.reload();
+ },
+
+ // Callback for user clicking the help icon
+ openHelpPage: function () {
+ openHelpLink("plugin-crashed", false);
+ },
+
+ _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
+ if (event == "showing") {
+ Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
+ .add(!this.options.primaryPlugin);
+ // Histograms always start at 0, even though our data starts at 1
+ let histogramCount = this.options.pluginData.size - 1;
+ if (histogramCount > 4) {
+ histogramCount = 4;
+ }
+ Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT")
+ .add(histogramCount);
+ }
+ else if (event == "dismissed") {
+ // Once the popup is dismissed, clicking the icon should show the full
+ // list again
+ this.options.primaryPlugin = null;
+ }
+ },
+
+ /**
+ * Called from the plugin doorhanger to set the new permissions for a plugin
+ * and activate plugins if necessary.
+ * aNewState should be either "allownow" "allowalways" or "block"
+ */
+ _updatePluginPermission: function (aNotification, aPluginInfo, aNewState) {
+ let permission;
+ let expireType;
+ let expireTime;
+ let histogram =
+ Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION");
+
+ // Update the permission manager.
+ // Also update the current state of pluginInfo.fallbackType so that
+ // subsequent opening of the notification shows the current state.
+ switch (aNewState) {
+ case "allownow":
+ permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
+ expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000;
+ histogram.add(0);
+ aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
+ break;
+
+ case "allowalways":
+ permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
+ expireTime = Date.now() +
+ Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000;
+ histogram.add(1);
+ aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
+ break;
+
+ case "block":
+ permission = Ci.nsIPermissionManager.PROMPT_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
+ expireTime = 0;
+ histogram.add(2);
+ switch (aPluginInfo.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE;
+ break;
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
+ break;
+ default:
+ aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
+ }
+ break;
+
+ // In case a plugin has already been allowed in another tab, the "continue allowing" button
+ // shouldn't change any permissions but should run the plugin-enablement code below.
+ case "continue":
+ aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE;
+ break;
+ default:
+ Cu.reportError(Error("Unexpected plugin state: " + aNewState));
+ return;
+ }
+
+ let browser = aNotification.browser;
+ if (aNewState != "continue") {
+ let principal = aNotification.options.principal;
+ Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
+ permission, expireType, expireTime);
+ aPluginInfo.pluginPermissionType = expireType;
+ }
+
+ browser.messageManager.sendAsyncMessage("BrowserPlugins:ActivatePlugins", {
+ pluginInfo: aPluginInfo,
+ newState: aNewState,
+ });
+ },
+
+ showClickToPlayNotification: function (browser, plugins, showNow,
+ principal, location) {
+ // It is possible that we've received a message from the frame script to show
+ // a click to play notification for a principal that no longer matches the one
+ // that the browser's content now has assigned (ie, the browser has browsed away
+ // after the message was sent, but before the message was received). In that case,
+ // we should just ignore the message.
+ if (!principal.equals(browser.contentPrincipal)) {
+ return;
+ }
+
+ // Data URIs, when linked to from some page, inherit the principal of that
+ // page. That means that we also need to compare the actual locations to
+ // ensure we aren't getting a message from a Data URI that we're no longer
+ // looking at.
+ let receivedURI = BrowserUtils.makeURI(location);
+ if (!browser.documentURI.equalsExceptRef(receivedURI)) {
+ return;
+ }
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
+
+ // If this is a new notification, create a pluginData map, otherwise append
+ let pluginData;
+ if (notification) {
+ pluginData = notification.options.pluginData;
+ } else {
+ pluginData = new Map();
+ }
+
+ for (var pluginInfo of plugins) {
+ if (pluginData.has(pluginInfo.permissionString)) {
+ continue;
+ }
+
+ // If a block contains an infoURL, we should always prefer that to the default
+ // URL that we construct in-product, even for other blocklist types.
+ let url = Services.blocklist.getPluginInfoURL(pluginInfo.pluginTag);
+
+ if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+ if (!url) {
+ url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
+ }
+ }
+ else {
+ url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
+ }
+ pluginInfo.detailsLink = url;
+
+ pluginData.set(pluginInfo.permissionString, pluginInfo);
+ }
+
+ let primaryPluginPermission = null;
+ if (showNow) {
+ primaryPluginPermission = plugins[0].permissionString;
+ }
+
+ if (notification) {
+ // Don't modify the notification UI while it's on the screen, that would be
+ // jumpy and might allow clickjacking.
+ if (showNow) {
+ notification.options.primaryPlugin = primaryPluginPermission;
+ notification.reshow();
+ browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
+ }
+ return;
+ }
+
+ let options = {
+ dismissed: !showNow,
+ eventCallback: this._clickToPlayNotificationEventCallback,
+ primaryPlugin: primaryPluginPermission,
+ pluginData: pluginData,
+ principal: principal,
+ };
+ PopupNotifications.show(browser, "click-to-play-plugins",
+ "", "plugins-notification-icon",
+ null, null, options);
+ browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
+ },
+
+ removeNotification: function (browser, name) {
+ let notification = PopupNotifications.getNotification(name, browser);
+ if (notification)
+ PopupNotifications.remove(notification);
+ },
+
+ hideNotificationBar: function (browser, name) {
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ let notification = notificationBox.getNotificationWithValue(name);
+ if (notification)
+ notificationBox.removeNotification(notification, true);
+ },
+
+ updateHiddenPluginUI: function (browser, haveInsecure, actions,
+ principal, location) {
+ let origin = principal.originNoSuffix;
+
+ // It is possible that we've received a message from the frame script to show
+ // the hidden plugin notification for a principal that no longer matches the one
+ // that the browser's content now has assigned (ie, the browser has browsed away
+ // after the message was sent, but before the message was received). In that case,
+ // we should just ignore the message.
+ if (!principal.equals(browser.contentPrincipal)) {
+ return;
+ }
+
+ // Data URIs, when linked to from some page, inherit the principal of that
+ // page. That means that we also need to compare the actual locations to
+ // ensure we aren't getting a message from a Data URI that we're no longer
+ // looking at.
+ let receivedURI = BrowserUtils.makeURI(location);
+ if (!browser.documentURI.equalsExceptRef(receivedURI)) {
+ return;
+ }
+
+ // Set up the icon
+ document.getElementById("plugins-notification-icon").classList.
+ toggle("plugin-blocked", haveInsecure);
+
+ // Now configure the notification bar
+ let notificationBox = gBrowser.getNotificationBox(browser);
+
+ function hideNotification() {
+ let n = notificationBox.getNotificationWithValue("plugin-hidden");
+ if (n) {
+ notificationBox.removeNotification(n, true);
+ }
+ }
+
+ // There are three different cases when showing an infobar:
+ // 1. A single type of plugin is hidden on the page. Show the UI for that
+ // plugin.
+ // 2a. Multiple types of plugins are hidden on the page. Show the multi-UI
+ // with the vulnerable styling.
+ // 2b. Multiple types of plugins are hidden on the page, but none are
+ // vulnerable. Show the nonvulnerable multi-UI.
+ function showNotification() {
+ let n = notificationBox.getNotificationWithValue("plugin-hidden");
+ if (n) {
+ // If something is already shown, just keep it
+ return;
+ }
+
+ Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN").
+ add(true);
+
+ let message;
+ // Icons set directly cannot be manipulated using moz-image-region, so
+ // we use CSS classes instead.
+ let brand = document.getElementById("bundle_brand").getString("brandShortName");
+
+ if (actions.length == 1) {
+ let pluginInfo = actions[0];
+ let pluginName = pluginInfo.pluginName;
+
+ switch (pluginInfo.fallbackType) {
+ case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
+ message = gNavigatorBundle.getFormattedString(
+ "pluginActivateNew.message",
+ [pluginName, origin]);
+ break;
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+ message = gNavigatorBundle.getFormattedString(
+ "pluginActivateOutdated.message",
+ [pluginName, origin, brand]);
+ break;
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+ message = gNavigatorBundle.getFormattedString(
+ "pluginActivateVulnerable.message",
+ [pluginName, origin, brand]);
+ }
+ } else {
+ // Multi-plugin
+ message = gNavigatorBundle.getFormattedString(
+ "pluginActivateMultiple.message", [origin]);
+ }
+
+ let buttons = [
+ {
+ label: gNavigatorBundle.getString("pluginContinueBlocking.label"),
+ accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"),
+ callback: function() {
+ Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK").
+ add(true);
+
+ Services.perms.addFromPrincipal(principal,
+ "plugin-hidden-notification",
+ Services.perms.DENY_ACTION);
+ }
+ },
+ {
+ label: gNavigatorBundle.getString("pluginActivateTrigger.label"),
+ accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"),
+ callback: function() {
+ Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW").
+ add(true);
+
+ let curNotification =
+ PopupNotifications.getNotification("click-to-play-plugins",
+ browser);
+ if (curNotification) {
+ curNotification.reshow();
+ }
+ }
+ }
+ ];
+ n = notificationBox.
+ appendNotification(message, "plugin-hidden", null,
+ notificationBox.PRIORITY_INFO_HIGH, buttons);
+ if (haveInsecure) {
+ n.classList.add('pluginVulnerable');
+ }
+ }
+
+ if (actions.length == 0) {
+ hideNotification();
+ } else {
+ let notificationPermission = Services.perms.testPermissionFromPrincipal(
+ principal, "plugin-hidden-notification");
+ if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) {
+ hideNotification();
+ } else {
+ showNotification();
+ }
+ }
+ },
+
+ contextMenuCommand: function (browser, plugin, command) {
+ browser.messageManager.sendAsyncMessage("BrowserPlugins:ContextMenuCommand",
+ { command: command }, { plugin: plugin });
+ },
+
+ // Crashed-plugin observer. Notified once per plugin crash, before events
+ // are dispatched to individual plugin instances.
+ NPAPIPluginCrashed : function(subject, topic, data) {
+ let propertyBag = subject;
+ if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
+ !(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
+ !propertyBag.hasKey("runID") ||
+ !propertyBag.hasKey("pluginName")) {
+ Cu.reportError("A NPAPI plugin crashed, but the properties of this plugin " +
+ "cannot be read.");
+ return;
+ }
+
+ let runID = propertyBag.getPropertyAsUint32("runID");
+ let uglyPluginName = propertyBag.getPropertyAsAString("pluginName");
+ let pluginName = BrowserUtils.makeNicePluginName(uglyPluginName);
+ let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
+
+ // If we don't have a minidumpID, we can't (or didn't) submit anything.
+ // This can happen if the plugin is killed from the task manager.
+ let state;
+ if (!AppConstants.MOZ_CRASHREPORTER || !gCrashReporter.enabled) {
+ // This state tells the user that crash reporting is disabled, so we
+ // cannot send a report.
+ state = "noSubmit";
+ } else if (!pluginDumpID) {
+ // This state tells the user that there is no crash report available.
+ state = "noReport";
+ } else {
+ // This state asks the user to submit a crash report.
+ state = "please";
+ }
+
+ let mm = window.getGroupMessageManager("browsers");
+ mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed",
+ { pluginName, runID, state });
+ },
+
+ /**
+ * Shows a plugin-crashed notification bar for a browser that has had an
+ * invisiable NPAPI plugin crash, or a GMP plugin crash.
+ *
+ * @param browser
+ * The browser to show the notification for.
+ * @param messageString
+ * The string to put in the notification bar
+ * @param pluginID
+ * The unique-per-process identifier for the NPAPI plugin or GMP.
+ * For a GMP, this is the pluginID. For NPAPI plugins (where "pluginID"
+ * means something different), this is the runID.
+ */
+ showPluginCrashedNotification: function (browser, messageString, pluginID) {
+ // If there's already an existing notification bar, don't do anything.
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ let notification = notificationBox.getNotificationWithValue("plugin-crashed");
+ if (notification) {
+ return;
+ }
+
+ // Configure the notification bar
+ let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
+ let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
+ let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
+
+ let buttons = [{
+ label: reloadLabel,
+ accessKey: reloadKey,
+ popup: null,
+ callback: function() { browser.reload(); },
+ }];
+
+ if (AppConstants.MOZ_CRASHREPORTER &&
+ PluginCrashReporter.hasCrashReport(pluginID)) {
+ let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
+ let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
+ let submitButton = {
+ label: submitLabel,
+ accessKey: submitKey,
+ popup: null,
+ callback: () => {
+ PluginCrashReporter.submitCrashReport(pluginID);
+ },
+ };
+
+ buttons.push(submitButton);
+ }
+
+ notification = notificationBox.appendNotification(messageString, "plugin-crashed",
+ iconURL, priority, buttons);
+
+ // Add the "learn more" link.
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let link = notification.ownerDocument.createElementNS(XULNS, "label");
+ link.className = "text-link";
+ link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
+ let crashurl = formatURL("app.support.baseURL", true);
+ crashurl += "plugin-crashed-notificationbar";
+ link.href = crashurl;
+ let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
+ description.appendChild(link);
+ },
+};
+
+gPluginHandler.init();
diff --git a/browser/base/content/browser-refreshblocker.js b/browser/base/content/browser-refreshblocker.js
new file mode 100644
index 000000000..025d45421
--- /dev/null
+++ b/browser/base/content/browser-refreshblocker.js
@@ -0,0 +1,84 @@
+/* -*- 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/. */
+
+/**
+ * If the user has opted into blocking refresh and redirect attempts by
+ * default, this handles showing the notification to the user which
+ * gives them the option to let the refresh or redirect proceed.
+ */
+var RefreshBlocker = {
+ init() {
+ gBrowser.addEventListener("RefreshBlocked", this);
+ },
+
+ uninit() {
+ gBrowser.removeEventListener("RefreshBlocked", this);
+ },
+
+ handleEvent: function(event) {
+ if (event.type == "RefreshBlocked") {
+ this.block(event.originalTarget, event.detail);
+ }
+ },
+
+ /**
+ * Shows the blocked refresh / redirect notification for some browser.
+ *
+ * @param browser (<xul:browser>)
+ * The browser that had the refresh blocked. This will be the browser
+ * for which we'll show the notification on.
+ * @param data (object)
+ * An object with the following properties:
+ *
+ * URI (string)
+ * The URI that a page is attempting to refresh or redirect to.
+ *
+ * delay (int)
+ * The delay (in milliseconds) before the page was going to reload
+ * or redirect.
+ *
+ * sameURI (bool)
+ * true if we're refreshing the page. false if we're redirecting.
+ *
+ * outerWindowID (int)
+ * The outerWindowID of the frame that requested the refresh or
+ * redirect.
+ */
+ block(browser, data) {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let message =
+ gNavigatorBundle.getFormattedString(data.sameURI ? "refreshBlocked.refreshLabel"
+ : "refreshBlocked.redirectLabel",
+ [brandShortName]);
+
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ let notification = notificationBox.getNotificationWithValue("refresh-blocked");
+
+ if (notification) {
+ notification.label = message;
+ } else {
+ let refreshButtonText =
+ gNavigatorBundle.getString("refreshBlocked.goButton");
+ let refreshButtonAccesskey =
+ gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
+
+ let buttons = [{
+ label: refreshButtonText,
+ accessKey: refreshButtonAccesskey,
+ callback: function (notification, button) {
+ if (browser.messageManager) {
+ browser.messageManager.sendAsyncMessage("RefreshBlocker:Refresh", data);
+ }
+ }
+ }];
+
+ notificationBox.appendNotification(message, "refresh-blocked",
+ "chrome://browser/skin/Info.png",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ }
+ }
+};
diff --git a/browser/base/content/browser-safebrowsing.js b/browser/base/content/browser-safebrowsing.js
new file mode 100644
index 000000000..430d84f13
--- /dev/null
+++ b/browser/base/content/browser-safebrowsing.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/. */
+
+var gSafeBrowsing = {
+
+ setReportPhishingMenu: function() {
+ // In order to detect whether or not we're at the phishing warning
+ // page, we have to check the documentURI instead of the currentURI.
+ // This is because when the DocShell loads an error page, the
+ // currentURI stays at the original target, while the documentURI
+ // will point to the internal error page we loaded instead.
+ var docURI = gBrowser.selectedBrowser.documentURI;
+ var isPhishingPage =
+ docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked");
+
+ // Show/hide the appropriate menu item.
+ document.getElementById("menu_HelpPopup_reportPhishingtoolmenu")
+ .hidden = isPhishingPage;
+ document.getElementById("menu_HelpPopup_reportPhishingErrortoolmenu")
+ .hidden = !isPhishingPage;
+
+ var broadcasterId = isPhishingPage
+ ? "reportPhishingErrorBroadcaster"
+ : "reportPhishingBroadcaster";
+
+ var broadcaster = document.getElementById(broadcasterId);
+ if (!broadcaster)
+ return;
+
+ // Now look at the currentURI to learn which page we were trying
+ // to browse to.
+ let uri = gBrowser.currentURI;
+ if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
+ broadcaster.removeAttribute("disabled");
+ else
+ broadcaster.setAttribute("disabled", true);
+ },
+
+ /**
+ * Used to report a phishing page or a false positive
+ * @param name String One of "Phish", "Error", "Malware" or "MalwareError"
+ * @return String the report phishing URL.
+ */
+ getReportURL: function(name) {
+ return SafeBrowsing.getReportURL(name, gBrowser.currentURI);
+ }
+}
diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc
new file mode 100644
index 000000000..fc5bfeb7e
--- /dev/null
+++ b/browser/base/content/browser-sets.inc
@@ -0,0 +1,380 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+#define XP_GNOME 1
+#endif
+#endif
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_shell" src="chrome://browser/locale/shellservice.properties"/>
+ <stringbundle id="bundle_preferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ </stringbundleset>
+
+ <commandset id="mainCommandSet">
+ <command id="cmd_newNavigator" oncommand="OpenBrowserWindow()" reserved="true"/>
+ <command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
+ <command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
+
+ <command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab(event);" reserved="true"/>
+ <command id="cmd_newNavigatorTabNoEvent" oncommand="BrowserOpenTab();" reserved="true"/>
+ <command id="Browser:OpenFile" oncommand="BrowserOpenFileWindow();"/>
+ <command id="Browser:SavePage" oncommand="saveBrowser(gBrowser.selectedBrowser);"/>
+
+ <command id="Browser:SendLink"
+ oncommand="MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);"/>
+
+ <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
+ <command id="cmd_print" oncommand="PrintUtils.printWindow(window.gBrowser.selectedBrowser.outerWindowID, window.gBrowser.selectedBrowser);"/>
+ <command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
+ <command id="cmd_close" oncommand="BrowserCloseTabOrWindow()" reserved="true"/>
+ <command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()" reserved="true"/>
+ <command id="cmd_toggleMute" oncommand="gBrowser.selectedTab.toggleMuteAudio()"/>
+ <command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/>
+ <command id="cmd_quitApplication" oncommand="goQuitApplication()" reserved="true"/>
+
+
+ <commandset id="editMenuCommands"/>
+
+ <command id="View:PageSource" oncommand="BrowserViewSource(window.gBrowser.selectedBrowser);" observes="canViewSource"/>
+ <command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
+ <command id="View:FullScreen" oncommand="BrowserFullScreen();"/>
+ <command id="View:ReaderView" oncommand="ReaderParent.toggleReaderMode(event);"/>
+ <command id="cmd_find"
+ oncommand="gFindBar.onFindCommand();"
+ observes="isImage"/>
+ <command id="cmd_findAgain"
+ oncommand="gFindBar.onFindAgainCommand(false);"
+ observes="isImage"/>
+ <command id="cmd_findPrevious"
+ oncommand="gFindBar.onFindAgainCommand(true);"
+ observes="isImage"/>
+#ifdef XP_MACOSX
+ <command id="cmd_findSelection" oncommand="gFindBar.onFindSelectionCommand();"/>
+#endif
+ <!-- work-around bug 392512 -->
+ <command id="Browser:AddBookmarkAs"
+ oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
+ <!-- The command disabled state must be manually updated through
+ PlacesCommandHook.updateBookmarkAllTabsCommand() -->
+ <command id="Browser:BookmarkAllTabs"
+ oncommand="PlacesCommandHook.bookmarkCurrentPages();"/>
+ <command id="Browser:Home" oncommand="BrowserHome();"/>
+ <command id="Browser:Back" oncommand="BrowserBack();" disabled="true"/>
+ <command id="Browser:BackOrBackDuplicate" oncommand="BrowserBack(event);" disabled="true">
+ <observes element="Browser:Back" attribute="disabled"/>
+ </command>
+ <command id="Browser:Forward" oncommand="BrowserForward();" disabled="true"/>
+ <command id="Browser:ForwardOrForwardDuplicate" oncommand="BrowserForward(event);" disabled="true">
+ <observes element="Browser:Forward" attribute="disabled"/>
+ </command>
+ <command id="Browser:Stop" oncommand="BrowserStop();" disabled="true"/>
+ <command id="Browser:Reload" oncommand="if (event.shiftKey) BrowserReloadSkipCache(); else BrowserReload()" disabled="true"/>
+ <command id="Browser:ReloadOrDuplicate" oncommand="BrowserReloadOrDuplicate(event)" disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ <command id="Browser:ReloadSkipCache" oncommand="BrowserReloadSkipCache()" disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ <command id="Browser:NextTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(1, true);" reserved="true"/>
+ <command id="Browser:PrevTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(-1, true);" reserved="true"/>
+ <command id="Browser:ShowAllTabs" oncommand="allTabs.open();"/>
+ <command id="cmd_fullZoomReduce" oncommand="FullZoom.reduce()"/>
+ <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
+ <command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/>
+ <command id="cmd_fullZoomToggle" oncommand="ZoomManager.toggleZoom();"/>
+ <command id="cmd_gestureRotateLeft" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
+ <command id="cmd_gestureRotateRight" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
+ <command id="cmd_gestureRotateEnd" oncommand="gGestureSupport.rotateEnd()"/>
+ <command id="Browser:OpenLocation" oncommand="openLocation();"/>
+ <command id="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
+ <command id="Browser:NewUserContextTab" oncommand="openNewUserContextTab(event.sourceEvent);" reserved="true"/>
+ <command id="Browser:OpenAboutContainers" oncommand="openPreferences('paneContainers');"/>
+
+ <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
+ <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
+ <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
+ <command id="Tools:Sanitize"
+ oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
+ <command id="Tools:PrivateBrowsing"
+ oncommand="OpenBrowserWindow({private: true});" reserved="true"/>
+#ifdef E10S_TESTING_ONLY
+ <command id="Tools:NonRemoteWindow"
+ oncommand="OpenBrowserWindow({remote: false});"/>
+#endif
+ <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
+ <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
+ <command id="Social:SharePage" oncommand="SocialShare.sharePage();"/>
+ <command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
+ </commandset>
+
+ <commandset id="placesCommands">
+ <command id="Browser:ShowAllBookmarks"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
+ <command id="Browser:ShowAllHistory"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
+ </commandset>
+
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="Social:PageShareable" disabled="true"/>
+ <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
+ type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
+ oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
+
+ <!-- for both places and non-places, the sidebar lives at
+ chrome://browser/content/history/history-panel.xul so there are no
+ problems when switching between versions -->
+ <broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
+ type="checkbox" group="sidebar"
+ sidebarurl="chrome://browser/content/history/history-panel.xul"
+ oncommand="SidebarUI.toggle('viewHistorySidebar');"/>
+
+ <broadcaster id="viewWebPanelsSidebar" autoCheck="false"
+ type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
+ oncommand="SidebarUI.toggle('viewWebPanelsSidebar');"/>
+
+ <broadcaster id="bookmarkThisPageBroadcaster"
+ label="&bookmarkThisPageCmd.label;"
+ bookmarklabel="&bookmarkThisPageCmd.label;"
+ editlabel="&editThisBookmarkCmd.label;"/>
+
+ <!-- popup blocking menu items -->
+ <broadcaster id="blockedPopupAllowSite"
+ accesskey="&allowPopups.accesskey;"
+ oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
+ <broadcaster id="blockedPopupEditSettings"
+#ifdef XP_WIN
+ label="&editPopupSettings.label;"
+#else
+ label="&editPopupSettingsUnix.label;"
+#endif
+ accesskey="&editPopupSettings.accesskey;"
+ oncommand="gPopupBlockerObserver.editPopupSettings();"/>
+ <broadcaster id="blockedPopupDontShowMessage"
+ accesskey="&dontShowMessage.accesskey;"
+ type="checkbox"
+ oncommand="gPopupBlockerObserver.dontShowMessage();"/>
+ <broadcaster id="blockedPopupsSeparator"/>
+ <broadcaster id="isImage"/>
+ <broadcaster id="canViewSource"/>
+ <broadcaster id="isFrameImage"/>
+ <broadcaster id="singleFeedMenuitemState" disabled="true"/>
+ <broadcaster id="multipleFeedsMenuState" hidden="true"/>
+
+ <!-- Sync broadcasters -->
+ <!-- A broadcaster of a number of attributes suitable for "sync now" UI -
+ A 'syncstatus' attribute is set while actively syncing, and the label
+ attribute which changes from "sync now" to "syncing" etc. -->
+ <broadcaster id="sync-status"/>
+ <!-- broadcasters of the "hidden" attribute to reflect setup state for
+ menus -->
+ <broadcaster id="sync-setup-state"/>
+ <broadcaster id="sync-syncnow-state" hidden="true"/>
+ <broadcaster id="sync-reauth-state" hidden="true"/>
+ <broadcaster id="viewTabsSidebar" autoCheck="false" sidebartitle="&syncedTabs.sidebar.label;"
+ type="checkbox" group="sidebar"
+ sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
+ oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
+ <broadcaster id="workOfflineMenuitemState"/>
+
+ <broadcaster id="devtoolsMenuBroadcaster_PageSource"
+ label="&pageSourceCmd.label;"
+ key="key_viewSource"
+ command="View:PageSource">
+ <observes element="canViewSource" attribute="disabled"/>
+ </broadcaster>
+ </broadcasterset>
+
+ <keyset id="mainKeyset">
+ <key id="key_newNavigator"
+ key="&newNavigatorCmd.key;"
+ command="cmd_newNavigator"
+ modifiers="accel"/>
+ <key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTabNoEvent"/>
+ <key id="focusURLBar" key="&openCmd.commandkey;" command="Browser:OpenLocation"
+ modifiers="accel"/>
+#ifndef XP_MACOSX
+ <key id="focusURLBar2" key="&urlbar.accesskey;" command="Browser:OpenLocation"
+ modifiers="alt"/>
+#endif
+
+#
+# Search Command Key Logic works like this:
+#
+# Unix: Ctrl+K (cross platform binding)
+# Ctrl+J (in case of emacs Ctrl-K conflict)
+# Mac: Cmd+K (cross platform binding)
+# Cmd+Opt+F (platform convention)
+# Win: Ctrl+K (cross platform binding)
+# Ctrl+E (IE compat)
+#
+# We support Ctrl+K on all platforms now and advertise it in the menu since it is
+# our standard - it is a "safe" choice since it is near no harmful keys like "W" as
+# "E" is. People mourning the loss of Ctrl+K for emacs compat can switch their GTK
+# system setting to use emacs emulation, and we should respect it. Focus-Search-Box
+# is a fundamental keybinding and we are maintaining a XP binding so that it is easy
+# for people to switch to Linux.
+#
+ <key id="key_search" key="&searchFocus.commandkey;" command="Tools:Search" modifiers="accel"/>
+#ifdef XP_MACOSX
+ <key id="key_search2" key="&findOnCmd.commandkey;" command="Tools:Search" modifiers="accel,alt"/>
+#endif
+#ifdef XP_WIN
+ <key id="key_search2" key="&searchFocus.commandkey2;" command="Tools:Search" modifiers="accel"/>
+#endif
+#ifdef XP_GNOME
+ <key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
+ <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
+#else
+ <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
+#endif
+ <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
+ <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile" modifiers="accel"/>
+ <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
+ <key id="printKb" key="&printCmd.commandkey;" command="cmd_print" modifiers="accel"/>
+ <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
+ <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
+ <key id="key_toggleMute" key="&toggleMuteCmd.key;" command="cmd_toggleMute" modifiers="control"/>
+ <key id="key_undo"
+ key="&undoCmd.key;"
+ modifiers="accel"/>
+#ifdef XP_UNIX
+ <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
+#else
+ <key id="key_redo" key="&redoCmd.key;" modifiers="accel"/>
+#endif
+ <key id="key_cut"
+ key="&cutCmd.key;"
+ modifiers="accel"/>
+ <key id="key_copy"
+ key="&copyCmd.key;"
+ modifiers="accel"/>
+ <key id="key_paste"
+ key="&pasteCmd.key;"
+ modifiers="accel"/>
+ <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
+ <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
+
+ <key keycode="VK_BACK" command="cmd_handleBackspace"/>
+ <key keycode="VK_BACK" command="cmd_handleShiftBackspace" modifiers="shift"/>
+#ifndef XP_MACOSX
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>
+#else
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="accel" />
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="accel" />
+#endif
+#ifdef XP_UNIX
+ <key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
+ <key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
+#endif
+ <key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
+ <key keycode="VK_F5" command="Browser:Reload"/>
+#ifndef XP_MACOSX
+ <key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
+ <key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
+ <key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
+#else
+ <key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
+ <key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
+ <key keycode="VK_F11" command="View:FullScreen"/>
+#endif
+ <key id="toggleReaderMode" key="&toggleReaderMode.key;" command="View:ReaderView" modifiers="accel,alt" disabled="true"/>
+ <key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
+ <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
+ <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
+#ifndef XP_WIN
+ <key id="key_viewInfo" key="&pageInfoCmd.commandkey;" command="View:PageInfo" modifiers="accel"/>
+#endif
+ <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
+ <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
+ <key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
+#ifdef XP_MACOSX
+ <key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
+#endif
+ <key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
+ <key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
+
+ <key id="addBookmarkAsKb" key="&bookmarkThisPageCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
+# Accel+Shift+A-F are reserved on GTK
+#ifndef MOZ_WIDGET_GTK
+ <key id="bookmarkAllTabsKb" key="&bookmarkThisPageCmd.commandkey;" oncommand="PlacesCommandHook.bookmarkCurrentPages();" modifiers="accel,shift"/>
+ <key id="manBookmarkKb" key="&bookmarksCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
+#else
+ <key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
+#endif
+ <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+#ifdef XP_WIN
+# Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
+# overridden for other purposes there.
+ <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+#endif
+
+ <key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
+
+#ifdef XP_MACOSX
+ <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
+#endif
+
+ <key id="key_gotoHistory"
+ key="&historySidebarCmd.commandKey;"
+#ifdef XP_MACOSX
+ modifiers="accel,shift"
+#else
+ modifiers="accel"
+#endif
+ command="viewHistorySidebar"/>
+
+ <key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
+ <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
+
+ <key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift"/>
+
+ <key id="key_switchTextDirection" key="&bidiSwitchTextDirectionItem.commandkey;" command="cmd_switchTextDirection" modifiers="accel,shift" />
+
+ <key id="key_privatebrowsing" command="Tools:PrivateBrowsing" key="&privateBrowsingCmd.commandkey;" modifiers="accel,shift"/>
+ <key id="key_sanitize" command="Tools:Sanitize" keycode="VK_DELETE" modifiers="accel,shift"/>
+#ifdef XP_MACOSX
+ <key id="key_sanitize_mac" command="Tools:Sanitize" keycode="VK_BACK" modifiers="accel,shift"/>
+ <key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" modifiers="accel" reserved="true"/>
+#elifdef XP_UNIX
+ <key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" command="cmd_quitApplication" modifiers="accel"/>
+#endif
+
+#ifdef FULL_BROWSER_WINDOW
+ <key id="key_undoCloseTab" command="History:UndoCloseTab" key="&tabCmd.commandkey;" modifiers="accel,shift"/>
+#endif
+ <key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
+
+#ifdef XP_GNOME
+#define NUM_SELECT_TAB_MODIFIER alt
+#else
+#define NUM_SELECT_TAB_MODIFIER accel
+#endif
+
+#expand <key id="key_selectTab1" oncommand="gBrowser.selectTabAtIndex(0, event);" key="1" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab2" oncommand="gBrowser.selectTabAtIndex(1, event);" key="2" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab3" oncommand="gBrowser.selectTabAtIndex(2, event);" key="3" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab4" oncommand="gBrowser.selectTabAtIndex(3, event);" key="4" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab5" oncommand="gBrowser.selectTabAtIndex(4, event);" key="5" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab6" oncommand="gBrowser.selectTabAtIndex(5, event);" key="6" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab7" oncommand="gBrowser.selectTabAtIndex(6, event);" key="7" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab8" oncommand="gBrowser.selectTabAtIndex(7, event);" key="8" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectLastTab" oncommand="gBrowser.selectTabAtIndex(-1, event);" key="9" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+
+ </keyset>
+
+# Used by baseMenuOverlay
+#ifdef XP_MACOSX
+ <commandset id="baseMenuCommandSet" />
+#endif
+ <keyset id="baseMenuKeyset" />
diff --git a/browser/base/content/browser-sidebar.js b/browser/base/content/browser-sidebar.js
new file mode 100644
index 000000000..5893e6015
--- /dev/null
+++ b/browser/base/content/browser-sidebar.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/. */
+
+/**
+ * SidebarUI controls showing and hiding the browser sidebar.
+ *
+ * @note
+ * Some of these methods take a commandID argument - we expect to find a
+ * xul:broadcaster element with the specified ID.
+ * The following attributes on that element may be used and/or modified:
+ * - id (required) the string to match commandID. The convention
+ * is to use this naming scheme: 'view<sidebar-name>Sidebar'.
+ * - sidebarurl (required) specifies the URL to load in this sidebar.
+ * - sidebartitle or label (in that order) specify the title to
+ * display on the sidebar.
+ * - checked indicates whether the sidebar is currently displayed.
+ * Note that toggleSidebar updates this attribute when
+ * it changes the sidebar's visibility.
+ * - group this attribute must be set to "sidebar".
+ */
+var SidebarUI = {
+ browser: null,
+
+ _box: null,
+ _title: null,
+ _splitter: null,
+
+ init() {
+ this._box = document.getElementById("sidebar-box");
+ this.browser = document.getElementById("sidebar");
+ this._title = document.getElementById("sidebar-title");
+ this._splitter = document.getElementById("sidebar-splitter");
+
+ if (!this.adoptFromWindow(window.opener)) {
+ let commandID = this._box.getAttribute("sidebarcommand");
+ if (commandID) {
+ let command = document.getElementById(commandID);
+ if (command) {
+ this._delayedLoad = true;
+ this._box.hidden = false;
+ this._splitter.hidden = false;
+ command.setAttribute("checked", "true");
+ } else {
+ // Remove the |sidebarcommand| attribute, because the element it
+ // refers to no longer exists, so we should assume this sidebar
+ // panel has been uninstalled. (249883)
+ this._box.removeAttribute("sidebarcommand");
+ }
+ }
+ }
+ },
+
+ uninit() {
+ let enumerator = Services.wm.getEnumerator(null);
+ enumerator.getNext();
+ if (!enumerator.hasMoreElements()) {
+ document.persist("sidebar-box", "sidebarcommand");
+ document.persist("sidebar-box", "width");
+ document.persist("sidebar-box", "src");
+ document.persist("sidebar-title", "value");
+ }
+ },
+
+ /**
+ * Try and adopt the status of the sidebar from another window.
+ * @param {Window} sourceWindow - Window to use as a source for sidebar status.
+ * @return true if we adopted the state, or false if the caller should
+ * initialize the state itself.
+ */
+ adoptFromWindow(sourceWindow) {
+ // No source window, or it being closed, or not chrome, or in a different
+ // private-browsing context means we can't adopt.
+ if (!sourceWindow || sourceWindow.closed ||
+ !sourceWindow.document.documentURIObject.schemeIs("chrome") ||
+ PrivateBrowsingUtils.isWindowPrivate(window) != PrivateBrowsingUtils.isWindowPrivate(sourceWindow)) {
+ return false;
+ }
+
+ // If the opener had a sidebar, open the same sidebar in our window.
+ // The opener can be the hidden window too, if we're coming from the state
+ // where no windows are open, and the hidden window has no sidebar box.
+ let sourceUI = sourceWindow.SidebarUI;
+ if (!sourceUI || !sourceUI._box) {
+ // no source UI or no _box means we also can't adopt the state.
+ return false;
+ }
+ if (sourceUI._box.hidden) {
+ // just hidden means we have adopted the hidden state.
+ return true;
+ }
+
+ let commandID = sourceUI._box.getAttribute("sidebarcommand");
+ let commandElem = document.getElementById(commandID);
+
+ // dynamically generated sidebars will fail this check, but we still
+ // consider it adopted.
+ if (!commandElem) {
+ return true;
+ }
+
+ this._title.setAttribute("value",
+ sourceUI._title.getAttribute("value"));
+ this._box.setAttribute("width", sourceUI._box.boxObject.width);
+
+ this._box.setAttribute("sidebarcommand", commandID);
+ // Note: we're setting 'src' on this._box, which is a <vbox>, not on
+ // the <browser id="sidebar">. This lets us delay the actual load until
+ // delayedStartup().
+ this._box.setAttribute("src", sourceUI.browser.getAttribute("src"));
+ this._delayedLoad = true;
+
+ this._box.hidden = false;
+ this._splitter.hidden = false;
+ commandElem.setAttribute("checked", "true");
+ return true;
+ },
+
+ /**
+ * If loading a sidebar was delayed on startup, start the load now.
+ */
+ startDelayedLoad() {
+ if (!this._delayedLoad) {
+ return;
+ }
+
+ this.browser.setAttribute("src", this._box.getAttribute("src"));
+ },
+
+ /**
+ * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
+ * a chance to adjust focus as needed. An additional event is needed, because
+ * we don't want to focus the sidebar when it's opened on startup or in a new
+ * window, only when the user opens the sidebar.
+ */
+ _fireFocusedEvent() {
+ let event = new CustomEvent("SidebarFocused", {bubbles: true});
+ this.browser.contentWindow.dispatchEvent(event);
+
+ // Run the original function for backwards compatibility.
+ fireSidebarFocusedEvent();
+ },
+
+ /**
+ * True if the sidebar is currently open.
+ */
+ get isOpen() {
+ return !this._box.hidden;
+ },
+
+ /**
+ * The ID of the current sidebar (ie, the ID of the broadcaster being used).
+ * This can be set even if the sidebar is hidden.
+ */
+ get currentID() {
+ return this._box.getAttribute("sidebarcommand");
+ },
+
+ get title() {
+ return this._title.value;
+ },
+
+ set title(value) {
+ this._title.value = value;
+ },
+
+ /**
+ * Toggle the visibility of the sidebar. If the sidebar is hidden or is open
+ * with a different commandID, then the sidebar will be opened using the
+ * specified commandID. Otherwise the sidebar will be hidden.
+ *
+ * @param {string} commandID ID of the xul:broadcaster element to use.
+ * @return {Promise}
+ */
+ toggle(commandID = this.currentID) {
+ if (this.isOpen && commandID == this.currentID) {
+ this.hide();
+ return Promise.resolve();
+ }
+ return this.show(commandID);
+ },
+
+ /**
+ * Show the sidebar, using the parameters from the specified broadcaster.
+ * @see SidebarUI note.
+ *
+ * @param {string} commandID ID of the xul:broadcaster element to use.
+ */
+ show(commandID) {
+ return new Promise((resolve, reject) => {
+ let sidebarBroadcaster = document.getElementById(commandID);
+ if (!sidebarBroadcaster || sidebarBroadcaster.localName != "broadcaster") {
+ reject(new Error("Invalid sidebar broadcaster specified: " + commandID));
+ return;
+ }
+
+ if (this.isOpen && commandID != this.currentID) {
+ BrowserUITelemetry.countSidebarEvent(this.currentID, "hide");
+ }
+
+ let broadcasters = document.getElementsByAttribute("group", "sidebar");
+ for (let broadcaster of broadcasters) {
+ // skip elements that observe sidebar broadcasters and random
+ // other elements
+ if (broadcaster.localName != "broadcaster") {
+ continue;
+ }
+
+ if (broadcaster != sidebarBroadcaster) {
+ broadcaster.removeAttribute("checked");
+ } else {
+ sidebarBroadcaster.setAttribute("checked", "true");
+ }
+ }
+
+ this._box.hidden = false;
+ this._splitter.hidden = false;
+
+ this._box.setAttribute("sidebarcommand", sidebarBroadcaster.id);
+
+ let title = sidebarBroadcaster.getAttribute("sidebartitle");
+ if (!title) {
+ title = sidebarBroadcaster.getAttribute("label");
+ }
+ this._title.value = title;
+
+ let url = sidebarBroadcaster.getAttribute("sidebarurl");
+ this.browser.setAttribute("src", url); // kick off async load
+
+ // We set this attribute here in addition to setting it on the <browser>
+ // element itself, because the code in SidebarUI.uninit() persists this
+ // attribute, not the "src" of the <browser id="sidebar">. The reason it
+ // does that is that we want to delay sidebar load a bit when a browser
+ // window opens. See delayedStartup() and SidebarUI.startDelayedLoad().
+ this._box.setAttribute("src", url);
+
+ if (this.browser.contentDocument.location.href != url) {
+ let onLoad = event => {
+ this.browser.removeEventListener("load", onLoad, true);
+
+ // We're handling the 'load' event before it bubbles up to the usual
+ // (non-capturing) event handlers. Let it bubble up before firing the
+ // SidebarFocused event.
+ setTimeout(() => this._fireFocusedEvent(), 0);
+
+ // Run the original function for backwards compatibility.
+ sidebarOnLoad(event);
+
+ resolve();
+ };
+
+ this.browser.addEventListener("load", onLoad, true);
+ } else {
+ // Older code handled this case, so we do it too.
+ this._fireFocusedEvent();
+ resolve();
+ }
+
+ let selBrowser = gBrowser.selectedBrowser;
+ selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
+ {commandID: commandID, isOpen: true}
+ );
+ BrowserUITelemetry.countSidebarEvent(commandID, "show");
+ });
+ },
+
+ /**
+ * Hide the sidebar.
+ */
+ hide() {
+ if (!this.isOpen) {
+ return;
+ }
+
+ let commandID = this._box.getAttribute("sidebarcommand");
+ let sidebarBroadcaster = document.getElementById(commandID);
+
+ if (sidebarBroadcaster.getAttribute("checked") != "true") {
+ return;
+ }
+
+ // Replace the document currently displayed in the sidebar with about:blank
+ // so that we can free memory by unloading the page. We need to explicitly
+ // create a new content viewer because the old one doesn't get destroyed
+ // until about:blank has loaded (which does not happen as long as the
+ // element is hidden).
+ this.browser.setAttribute("src", "about:blank");
+ this.browser.docShell.createAboutBlankContentViewer(null);
+
+ sidebarBroadcaster.removeAttribute("checked");
+ this._box.setAttribute("sidebarcommand", "");
+ this._title.value = "";
+ this._box.hidden = true;
+ this._splitter.hidden = true;
+
+ let selBrowser = gBrowser.selectedBrowser;
+ selBrowser.focus();
+ selBrowser.messageManager.sendAsyncMessage("Sidebar:VisibilityChange",
+ {commandID: commandID, isOpen: false}
+ );
+ BrowserUITelemetry.countSidebarEvent(commandID, "hide");
+ },
+};
+
+/**
+ * This exists for backards compatibility - it will be called once a sidebar is
+ * ready, following any request to show it.
+ *
+ * @deprecated
+ */
+function fireSidebarFocusedEvent() {}
+
+/**
+ * This exists for backards compatibility - it gets called when a sidebar has
+ * been loaded.
+ *
+ * @deprecated
+ */
+function sidebarOnLoad(event) {}
+
+/**
+ * This exists for backards compatibility, and is equivilent to
+ * SidebarUI.toggle() without the forceOpen param. With forceOpen set to true,
+ * it is equalivent to SidebarUI.show().
+ *
+ * @deprecated
+ */
+function toggleSidebar(commandID, forceOpen = false) {
+ Deprecated.warning("toggleSidebar() is deprecated, please use SidebarUI.toggle() or SidebarUI.show() instead",
+ "https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Sidebar");
+
+ if (forceOpen) {
+ SidebarUI.show(commandID);
+ } else {
+ SidebarUI.toggle(commandID);
+ }
+}
diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js
new file mode 100644
index 000000000..b470efd3d
--- /dev/null
+++ b/browser/base/content/browser-social.js
@@ -0,0 +1,503 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// the "exported" symbols
+var SocialUI,
+ SocialShare,
+ SocialActivationListener;
+
+(function() {
+
+XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/Social.jsm", tmp);
+ return tmp.OpenGraphBuilder;
+});
+
+XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/Social.jsm", tmp);
+ return tmp.DynamicResizeWatcher;
+});
+
+SocialUI = {
+ _initialized: false,
+
+ // Called on delayed startup to initialize the UI
+ init: function SocialUI_init() {
+ if (this._initialized) {
+ return;
+ }
+ let mm = window.getGroupMessageManager("social");
+ mm.loadFrameScript("chrome://browser/content/content.js", true);
+ mm.loadFrameScript("chrome://browser/content/social-content.js", true);
+
+ Services.obs.addObserver(this, "social:providers-changed", false);
+
+ CustomizableUI.addListener(this);
+ SocialActivationListener.init();
+
+ Social.init().then((update) => {
+ if (update)
+ this._providersChanged();
+ });
+
+ this._initialized = true;
+ },
+
+ // Called on window unload
+ uninit: function SocialUI_uninit() {
+ if (!this._initialized) {
+ return;
+ }
+ Services.obs.removeObserver(this, "social:providers-changed");
+
+ CustomizableUI.removeListener(this);
+ SocialActivationListener.uninit();
+
+ this._initialized = false;
+ },
+
+ observe: function SocialUI_observe(subject, topic, data) {
+ switch (topic) {
+ case "social:providers-changed":
+ this._providersChanged();
+ break;
+ }
+ },
+
+ _providersChanged: function() {
+ SocialShare.populateProviderMenu();
+ },
+
+ showLearnMore: function() {
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
+ openUILinkIn(url, "tab");
+ },
+
+ closeSocialPanelForLinkTraversal: function (target, linkNode) {
+ // No need to close the panel if this traversal was not retargeted
+ if (target == "" || target == "_self")
+ return;
+
+ // Check to see whether this link traversal was in a social panel
+ let win = linkNode.ownerGlobal;
+ let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ let containerParent = container.parentNode;
+ if (containerParent.classList.contains("social-panel") &&
+ containerParent instanceof Ci.nsIDOMXULPopupElement) {
+ // allow the link traversal to finish before closing the panel
+ setTimeout(() => {
+ containerParent.hidePopup();
+ }, 0);
+ }
+ },
+
+ get _chromeless() {
+ // Is this a popup window that doesn't want chrome shown?
+ let docElem = document.documentElement;
+ // extrachrome is not restored during session restore, so we need
+ // to check for the toolbar as well.
+ let chromeless = docElem.getAttribute("chromehidden").includes("extrachrome") ||
+ docElem.getAttribute('chromehidden').includes("toolbar");
+ // This property is "fixed" for a window, so avoid doing the check above
+ // multiple times...
+ delete this._chromeless;
+ this._chromeless = chromeless;
+ return chromeless;
+ },
+
+ get enabled() {
+ // Returns whether social is enabled *for this window*.
+ if (this._chromeless)
+ return false;
+ return Social.providers.length > 0;
+ },
+
+ canSharePage: function(aURI) {
+ return (aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')));
+ },
+
+ onCustomizeEnd: function(aWindow) {
+ if (aWindow != window)
+ return;
+ // customization mode gets buttons out of sync with command updating, fix
+ // the disabled state
+ let canShare = this.canSharePage(gBrowser.currentURI);
+ let shareButton = SocialShare.shareButton;
+ if (shareButton) {
+ if (canShare) {
+ shareButton.removeAttribute("disabled")
+ } else {
+ shareButton.setAttribute("disabled", "true")
+ }
+ }
+ },
+
+ // called on tab/urlbar/location changes and after customization. Update
+ // anything that is tab specific.
+ updateState: function() {
+ goSetCommandEnabled("Social:PageShareable", this.canSharePage(gBrowser.currentURI));
+ }
+}
+
+// message manager handlers
+SocialActivationListener = {
+ init: function() {
+ messageManager.addMessageListener("Social:Activation", this);
+ },
+ uninit: function() {
+ messageManager.removeMessageListener("Social:Activation", this);
+ },
+ receiveMessage: function(aMessage) {
+ let data = aMessage.json;
+ let browser = aMessage.target;
+ data.window = window;
+ // if the source if the message is the share panel, we do a one-click
+ // installation. The source of activations is controlled by the
+ // social.directories preference
+ let options;
+ if (browser == SocialShare.iframe && Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
+ options = { bypassContentCheck: true, bypassInstallPanel: true };
+ }
+
+ Social.installProvider(data, function(manifest) {
+ Social.activateFromOrigin(manifest.origin, function(provider) {
+ if (provider.shareURL) {
+ // Ensure that the share button is somewhere usable.
+ // SocialShare.shareButton may return null if it is in the menu-panel
+ // and has never been visible, so we check the widget directly. If
+ // there is no area for the widget we move it into the toolbar.
+ let widget = CustomizableUI.getWidget("social-share-button");
+ // If the panel is already open, we can be sure that the provider can
+ // already be accessed, possibly anchored to another toolbar button.
+ // In that case we don't move the widget.
+ if (!widget.areaType && SocialShare.panel.state != "open") {
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // Ensure correct state.
+ SocialUI.onCustomizeEnd(window);
+ }
+
+ // make this new provider the selected provider. If the panel hasn't
+ // been opened, we need to make the frame first.
+ SocialShare._createFrame();
+ SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
+ SocialShare.iframe.setAttribute('origin', provider.origin);
+ // get the right button selected
+ SocialShare.populateProviderMenu();
+ if (SocialShare.panel.state == "open") {
+ SocialShare.sharePage(provider.origin);
+ }
+ }
+ if (provider.postActivationURL) {
+ // if activated from an open share panel, we load the landing page in
+ // a background tab
+ gBrowser.loadOneTab(provider.postActivationURL, {inBackground: SocialShare.panel.state == "open"});
+ }
+ });
+ }, options);
+ }
+}
+
+SocialShare = {
+ get _dynamicResizer() {
+ delete this._dynamicResizer;
+ this._dynamicResizer = new DynamicResizeWatcher();
+ return this._dynamicResizer;
+ },
+
+ // Share panel may be attached to the overflow or menu button depending on
+ // customization, we need to manage open state of the anchor.
+ get anchor() {
+ let widget = CustomizableUI.getWidget("social-share-button");
+ return widget.forWindow(window).anchor;
+ },
+ // Holds the anchor node in use whilst the panel is open, because it may vary.
+ _currentAnchor: null,
+
+ get panel() {
+ return document.getElementById("social-share-panel");
+ },
+
+ get iframe() {
+ // panel.firstChild is our toolbar hbox, panel.lastChild is the iframe
+ // container hbox used for an interstitial "loading" graphic
+ return this.panel.lastChild.firstChild;
+ },
+
+ uninit: function () {
+ if (this.iframe) {
+ let mm = this.messageManager;
+ mm.removeMessageListener("PageVisibility:Show", this);
+ mm.removeMessageListener("PageVisibility:Hide", this);
+ mm.removeMessageListener("Social:DOMWindowClose", this);
+ this.iframe.removeEventListener("load", this);
+ this.iframe.remove();
+ }
+ },
+
+ _createFrame: function() {
+ let panel = this.panel;
+ if (this.iframe)
+ return;
+ this.panel.hidden = false;
+ // create and initialize the panel for this window
+ let iframe = document.createElement("browser");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("class", "social-share-frame");
+ iframe.setAttribute("context", "contentAreaContextMenu");
+ iframe.setAttribute("tooltip", "aHTMLTooltip");
+ iframe.setAttribute("disableglobalhistory", "true");
+ iframe.setAttribute("flex", "1");
+ iframe.setAttribute("message", "true");
+ iframe.setAttribute("messagemanagergroup", "social");
+ panel.lastChild.appendChild(iframe);
+ let mm = this.messageManager;
+ mm.addMessageListener("PageVisibility:Show", this);
+ mm.addMessageListener("PageVisibility:Hide", this);
+ mm.sendAsyncMessage("Social:SetErrorURL",
+ { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
+ iframe.addEventListener("load", this, true);
+ mm.addMessageListener("Social:DOMWindowClose", this);
+
+ this.populateProviderMenu();
+ },
+
+ get messageManager() {
+ // The xbl bindings for the iframe may not exist yet, so we can't
+ // access iframe.messageManager directly - but can get at it with this dance.
+ return this.iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
+ },
+
+ receiveMessage: function(aMessage) {
+ let iframe = this.iframe;
+ switch(aMessage.name) {
+ case "PageVisibility:Show":
+ SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
+ break;
+ case "PageVisibility:Hide":
+ SocialShare._dynamicResizer.stop();
+ break;
+ case "Social:DOMWindowClose":
+ this.panel.hidePopup();
+ break;
+ }
+ },
+
+ handleEvent: function(event) {
+ switch (event.type) {
+ case "load": {
+ this.iframe.parentNode.removeAttribute("loading");
+ if (this.currentShare)
+ SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
+ }
+ }
+ },
+
+ getSelectedProvider: function() {
+ let provider;
+ let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
+ if (lastProviderOrigin) {
+ provider = Social._getProviderFromOrigin(lastProviderOrigin);
+ }
+ return provider;
+ },
+
+ createTooltip: function(event) {
+ let tt = event.target;
+ let provider = Social._getProviderFromOrigin(tt.triggerNode.getAttribute("origin"));
+ tt.firstChild.setAttribute("value", provider.name);
+ tt.lastChild.setAttribute("value", provider.origin);
+ },
+
+ populateProviderMenu: function() {
+ if (!this.iframe)
+ return;
+ let providers = Social.providers.filter(p => p.shareURL);
+ let hbox = document.getElementById("social-share-provider-buttons");
+ // remove everything before the add-share-provider button (which should also
+ // be lastChild if any share providers were added)
+ let addButton = document.getElementById("add-share-provider");
+ while (hbox.lastChild != addButton) {
+ hbox.removeChild(hbox.lastChild);
+ }
+ let selectedProvider = this.getSelectedProvider();
+ for (let provider of providers) {
+ let button = document.createElement("toolbarbutton");
+ button.setAttribute("class", "toolbarbutton-1 share-provider-button");
+ button.setAttribute("type", "radio");
+ button.setAttribute("group", "share-providers");
+ button.setAttribute("image", provider.iconURL);
+ button.setAttribute("tooltip", "share-button-tooltip");
+ button.setAttribute("origin", provider.origin);
+ button.setAttribute("label", provider.name);
+ button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin'));");
+ if (provider == selectedProvider) {
+ this.defaultButton = button;
+ }
+ hbox.appendChild(button);
+ }
+ if (!this.defaultButton) {
+ this.defaultButton = addButton;
+ }
+ this.defaultButton.setAttribute("checked", "true");
+ },
+
+ get shareButton() {
+ // web-panels (bookmark/sidebar) don't include customizableui, so
+ // nsContextMenu fails when accessing shareButton, breaking
+ // browser_bug409481.js.
+ if (!window.CustomizableUI)
+ return null;
+ let widget = CustomizableUI.getWidget("social-share-button");
+ if (!widget || !widget.areaType)
+ return null;
+ return widget.forWindow(window).node;
+ },
+
+ _onclick: function() {
+ Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(0);
+ },
+
+ onShowing: function() {
+ (this._currentAnchor || this.anchor).setAttribute("open", "true");
+ this.iframe.addEventListener("click", this._onclick, true);
+ },
+
+ onHidden: function() {
+ (this._currentAnchor || this.anchor).removeAttribute("open");
+ this._currentAnchor = null;
+ this.iframe.docShellIsActive = false;
+ this.iframe.removeEventListener("click", this._onclick, true);
+ this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
+ // make sure that the frame is unloaded after it is hidden
+ this.messageManager.sendAsyncMessage("Social:ClearFrame");
+ this.currentShare = null;
+ // share panel use is over, purge any history
+ this.iframe.purgeSessionHistory();
+ },
+
+ sharePage: function(providerOrigin, graphData, target, anchor) {
+ // if providerOrigin is undefined, we use the last-used provider, or the
+ // current/default provider. The provider selection in the share panel
+ // will call sharePage with an origin for us to switch to.
+ this._createFrame();
+ let iframe = this.iframe;
+
+ // graphData is an optional param that either defines the full set of data
+ // to be shared, or partial data about the current page. It is set by a call
+ // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
+ // define at least url. If it is undefined, we're sharing the current url in
+ // the browser tab.
+ let pageData = graphData ? graphData : this.currentShare;
+ let sharedURI = pageData ? Services.io.newURI(pageData.url, null, null) :
+ gBrowser.currentURI;
+ if (!SocialUI.canSharePage(sharedURI))
+ return;
+
+ let browserMM = gBrowser.selectedBrowser.messageManager;
+
+ // the point of this action type is that we can use existing share
+ // endpoints (e.g. oexchange) that do not support additional
+ // socialapi functionality. One tweak is that we shoot an event
+ // containing the open graph data.
+ let _dataFn;
+ if (!pageData || sharedURI == gBrowser.currentURI) {
+ browserMM.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
+ browserMM.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
+ let pageData = msg.json;
+ if (graphData) {
+ // overwrite data retreived from page with data given to us as a param
+ for (let p in graphData) {
+ pageData[p] = graphData[p];
+ }
+ }
+ this.sharePage(providerOrigin, pageData, target, anchor);
+ });
+ browserMM.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
+ return;
+ }
+ // if this is a share of a selected item, get any microformats
+ if (!pageData.microformats && target) {
+ browserMM.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
+ browserMM.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
+ pageData.microformats = msg.data;
+ this.sharePage(providerOrigin, pageData, target, anchor);
+ });
+ browserMM.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
+ return;
+ }
+ this.currentShare = pageData;
+
+ let provider;
+ if (providerOrigin)
+ provider = Social._getProviderFromOrigin(providerOrigin);
+ else
+ provider = this.getSelectedProvider();
+ if (!provider || !provider.shareURL) {
+ this.showDirectory(anchor);
+ return;
+ }
+ // check the menu button
+ let hbox = document.getElementById("social-share-provider-buttons");
+ let btn = hbox.querySelector("[origin='" + provider.origin + "']");
+ if (btn)
+ btn.checked = true;
+
+ let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
+
+ this._dynamicResizer.stop();
+ let size = provider.getPageSize("share");
+ if (size) {
+ // let the css on the share panel define width, but height
+ // calculations dont work on all sites, so we allow that to be
+ // defined.
+ delete size.width;
+ }
+
+ // if we've already loaded this provider/page share endpoint, we don't want
+ // to add another load event listener.
+ let endpointMatch = shareEndpoint == iframe.getAttribute("src");
+ if (endpointMatch) {
+ this._dynamicResizer.start(iframe.parentNode, iframe, size);
+ iframe.docShellIsActive = true;
+ SocialShare.messageManager.sendAsyncMessage("Social:OpenGraphData", this.currentShare);
+ } else {
+ iframe.parentNode.setAttribute("loading", "true");
+ }
+ // if the user switched between share providers we do not want that history
+ // available.
+ iframe.purgeSessionHistory();
+
+ // always ensure that origin belongs to the endpoint
+ let uri = Services.io.newURI(shareEndpoint, null, null);
+ iframe.setAttribute("origin", provider.origin);
+ iframe.setAttribute("src", shareEndpoint);
+ this._openPanel(anchor);
+ },
+
+ showDirectory: function(anchor) {
+ this._createFrame();
+ let iframe = this.iframe;
+ if (iframe.getAttribute("src") == "about:providerdirectory")
+ return;
+ iframe.removeAttribute("origin");
+ iframe.parentNode.setAttribute("loading", "true");
+
+ iframe.setAttribute("src", "about:providerdirectory");
+ this._openPanel(anchor);
+ },
+
+ _openPanel: function(anchor) {
+ this._currentAnchor = anchor || this.anchor;
+ anchor = document.getAnonymousElementByAttribute(this._currentAnchor, "class", "toolbarbutton-icon");
+ this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
+ Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(0);
+ }
+};
+
+})();
diff --git a/browser/base/content/browser-syncui.js b/browser/base/content/browser-syncui.js
new file mode 100644
index 000000000..c5c2995c8
--- /dev/null
+++ b/browser/base/content/browser-syncui.js
@@ -0,0 +1,544 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
+ XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
+ "resource://gre/modules/CloudSync.jsm");
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+
+const MIN_STATUS_ANIMATION_DURATION = 1600;
+
+// gSyncUI handles updating the tools menu and displaying notifications.
+var gSyncUI = {
+ _obs: ["weave:service:sync:start",
+ "weave:service:sync:finish",
+ "weave:service:sync:error",
+ "weave:service:setup-complete",
+ "weave:service:login:start",
+ "weave:service:login:finish",
+ "weave:service:login:error",
+ "weave:service:logout:finish",
+ "weave:service:start-over",
+ "weave:service:start-over:finish",
+ "weave:ui:login:error",
+ "weave:ui:sync:error",
+ "weave:ui:sync:finish",
+ "weave:ui:clear-error",
+ "weave:engine:sync:finish"
+ ],
+
+ _unloaded: false,
+ // The last sync start time. Used to calculate the leftover animation time
+ // once syncing completes (bug 1239042).
+ _syncStartTime: 0,
+ _syncAnimationTimer: 0,
+
+ init: function () {
+ Cu.import("resource://services-common/stringbundle.js");
+
+ // Proceed to set up the UI if Sync has already started up.
+ // Otherwise we'll do it when Sync is firing up.
+ if (this.weaveService.ready) {
+ this.initUI();
+ return;
+ }
+
+ // Sync isn't ready yet, but we can still update the UI with an initial
+ // state - we haven't called initUI() yet, but that's OK - that's more
+ // about observers for state changes, and will be called once Sync is
+ // ready to start sending notifications.
+ this.updateUI();
+
+ Services.obs.addObserver(this, "weave:service:ready", true);
+ Services.obs.addObserver(this, "quit-application", true);
+
+ // Remove the observer if the window is closed before the observer
+ // was triggered.
+ window.addEventListener("unload", function onUnload() {
+ gSyncUI._unloaded = true;
+ window.removeEventListener("unload", onUnload, false);
+ Services.obs.removeObserver(gSyncUI, "weave:service:ready");
+ Services.obs.removeObserver(gSyncUI, "quit-application");
+
+ if (Weave.Status.ready) {
+ gSyncUI._obs.forEach(function(topic) {
+ Services.obs.removeObserver(gSyncUI, topic);
+ });
+ }
+ }, false);
+ },
+
+ initUI: function SUI_initUI() {
+ // If this is a browser window?
+ if (gBrowser) {
+ this._obs.push("weave:notification:added");
+ }
+
+ this._obs.forEach(function(topic) {
+ Services.obs.addObserver(this, topic, true);
+ }, this);
+
+ // initial label for the sync buttons.
+ let broadcaster = document.getElementById("sync-status");
+ broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
+
+ this.maybeMoveSyncedTabsButton();
+
+ this.updateUI();
+ },
+
+
+ // Returns a promise that resolves with true if Sync needs to be configured,
+ // false otherwise.
+ _needsSetup() {
+ // If Sync is configured for FxAccounts then we do that promise-dance.
+ if (this.weaveService.fxAccountsEnabled) {
+ return fxAccounts.getSignedInUser().then(user => {
+ // We want to treat "account needs verification" as "needs setup".
+ return !(user && user.verified);
+ });
+ }
+ // We are using legacy sync - check that.
+ let firstSync = "";
+ try {
+ firstSync = Services.prefs.getCharPref("services.sync.firstSync");
+ } catch (e) { }
+
+ return Promise.resolve(Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
+ firstSync == "notReady");
+ },
+
+ // Returns a promise that resolves with true if the user currently signed in
+ // to Sync needs to be verified, false otherwise.
+ _needsVerification() {
+ // For callers who care about the distinction between "needs setup" and
+ // "needs verification"
+ if (this.weaveService.fxAccountsEnabled) {
+ return fxAccounts.getSignedInUser().then(user => {
+ // If there is no user, they can't be in a "needs verification" state.
+ if (!user) {
+ return false;
+ }
+ return !user.verified;
+ });
+ }
+
+ // Otherwise we are configured for legacy Sync, which has no verification
+ // concept.
+ return Promise.resolve(false);
+ },
+
+ // Note that we don't show login errors in a notification bar here, but do
+ // still need to track a login-failed state so the "Tools" menu updates
+ // with the correct state.
+ _loginFailed: function () {
+ // If Sync isn't already ready, we don't want to force it to initialize
+ // by referencing Weave.Status - and it isn't going to be accurate before
+ // Sync is ready anyway.
+ if (!this.weaveService.ready) {
+ this.log.debug("_loginFailed has sync not ready, so returning false");
+ return false;
+ }
+ this.log.debug("_loginFailed has sync state=${sync}",
+ { sync: Weave.Status.login});
+ return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+ },
+
+ // Kick off an update of the UI - does *not* return a promise.
+ updateUI() {
+ this._promiseUpdateUI().catch(err => {
+ this.log.error("updateUI failed", err);
+ })
+ },
+
+ // Updates the UI - returns a promise.
+ _promiseUpdateUI() {
+ return this._needsSetup().then(needsSetup => {
+ if (!gBrowser)
+ return Promise.resolve();
+
+ let loginFailed = this._loginFailed();
+
+ // Start off with a clean slate
+ document.getElementById("sync-reauth-state").hidden = true;
+ document.getElementById("sync-setup-state").hidden = true;
+ document.getElementById("sync-syncnow-state").hidden = true;
+
+ if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
+ document.getElementById("sync-syncnow-state").hidden = false;
+ } else if (loginFailed) {
+ // unhiding this element makes the menubar show the login failure state.
+ document.getElementById("sync-reauth-state").hidden = false;
+ } else if (needsSetup) {
+ document.getElementById("sync-setup-state").hidden = false;
+ } else {
+ document.getElementById("sync-syncnow-state").hidden = false;
+ }
+
+ return this._updateSyncButtonsTooltip();
+ });
+ },
+
+ // Functions called by observers
+ onActivityStart() {
+ if (!gBrowser)
+ return;
+
+ this.log.debug("onActivityStart");
+
+ clearTimeout(this._syncAnimationTimer);
+ this._syncStartTime = Date.now();
+
+ let broadcaster = document.getElementById("sync-status");
+ broadcaster.setAttribute("syncstatus", "active");
+ broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncing2.label"));
+ broadcaster.setAttribute("disabled", "true");
+
+ this.updateUI();
+ },
+
+ _updateSyncStatus() {
+ if (!gBrowser)
+ return;
+ let broadcaster = document.getElementById("sync-status");
+ broadcaster.removeAttribute("syncstatus");
+ broadcaster.removeAttribute("disabled");
+ broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
+ this.updateUI();
+ },
+
+ onActivityStop() {
+ if (!gBrowser)
+ return;
+ this.log.debug("onActivityStop");
+
+ let now = Date.now();
+ let syncDuration = now - this._syncStartTime;
+
+ if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
+ let animationTime = MIN_STATUS_ANIMATION_DURATION - syncDuration;
+ clearTimeout(this._syncAnimationTimer);
+ this._syncAnimationTimer = setTimeout(() => this._updateSyncStatus(), animationTime);
+ } else {
+ this._updateSyncStatus();
+ }
+ },
+
+ onLoginError: function SUI_onLoginError() {
+ this.log.debug("onLoginError: login=${login}, sync=${sync}", Weave.Status);
+
+ // We don't show any login errors here; browser-fxaccounts shows them in
+ // the hamburger menu.
+ this.updateUI();
+ },
+
+ onLogout: function SUI_onLogout() {
+ this.updateUI();
+ },
+
+ _getAppName: function () {
+ let brand = new StringBundle("chrome://branding/locale/brand.properties");
+ return brand.get("brandShortName");
+ },
+
+ // Commands
+ // doSync forces a sync - it *does not* return a promise as it is called
+ // via the various UI components.
+ doSync() {
+ this._needsSetup().then(needsSetup => {
+ if (!needsSetup) {
+ setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
+ }
+ Services.obs.notifyObservers(null, "cloudsync:user-sync", null);
+ }).catch(err => {
+ this.log.error("Failed to force a sync", err);
+ });
+ },
+
+ // Handle clicking the toolbar button - which either opens the Sync setup
+ // pages or forces a sync now. Does *not* return a promise as it is called
+ // via the UI.
+ handleToolbarButton() {
+ this._needsSetup().then(needsSetup => {
+ if (needsSetup || this._loginFailed()) {
+ this.openSetup();
+ } else {
+ this.doSync();
+ }
+ }).catch(err => {
+ this.log.error("Failed to handle toolbar button command", err);
+ });
+ },
+
+ /**
+ * Invoke the Sync setup wizard.
+ *
+ * @param wizardType
+ * Indicates type of wizard to launch:
+ * null -- regular set up wizard
+ * "pair" -- pair a device first
+ * "reset" -- reset sync
+ * @param entryPoint
+ * Indicates the entrypoint from where this method was called.
+ */
+
+ openSetup: function SUI_openSetup(wizardType, entryPoint = "syncbutton") {
+ if (this.weaveService.fxAccountsEnabled) {
+ this.openPrefs(entryPoint);
+ } else {
+ let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+ if (win)
+ win.focus();
+ else {
+ window.openDialog("chrome://browser/content/sync/setup.xul",
+ "weaveSetup", "centerscreen,chrome,resizable=no",
+ wizardType);
+ }
+ }
+ },
+
+ // Open the legacy-sync device pairing UI. Note used for FxA Sync.
+ openAddDevice: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return;
+
+ let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+ if (win)
+ win.focus();
+ else
+ window.openDialog("chrome://browser/content/sync/addDevice.xul",
+ "syncAddDevice", "centerscreen,chrome,resizable=no");
+ },
+
+ openPrefs: function (entryPoint) {
+ openPreferences("paneSync", { urlParams: { entrypoint: entryPoint } });
+ },
+
+ openSignInAgainPage: function (entryPoint = "syncbutton") {
+ gFxAccounts.openSignInAgainPage(entryPoint);
+ },
+
+ openSyncedTabsPanel() {
+ let placement = CustomizableUI.getPlacementOfWidget("sync-button");
+ let area = placement ? placement.area : CustomizableUI.AREA_NAVBAR;
+ let anchor = document.getElementById("sync-button") ||
+ document.getElementById("PanelUI-menu-button");
+ if (area == CustomizableUI.AREA_PANEL) {
+ // The button is in the panel, so we need to show the panel UI, then our
+ // subview.
+ PanelUI.show().then(() => {
+ PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
+ }).catch(Cu.reportError);
+ } else {
+ // It is placed somewhere else - just try and show it.
+ PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
+ }
+ },
+
+ /* After Sync is initialized we perform a once-only check for the sync
+ button being in "customize purgatory" and if so, move it to the panel.
+ This is done primarily for profiles created before SyncedTabs landed,
+ where the button defaulted to being in that purgatory.
+ We use a preference to ensure we only do it once, so people can still
+ customize it away and have it stick.
+ */
+ maybeMoveSyncedTabsButton() {
+ const prefName = "browser.migrated-sync-button";
+ let migrated = false;
+ try {
+ migrated = Services.prefs.getBoolPref(prefName);
+ } catch (_) {}
+ if (migrated) {
+ return;
+ }
+ if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ }
+ Services.prefs.setBoolPref(prefName, true);
+ },
+
+ /* Update the tooltip for the sync-status broadcaster (which will update the
+ Sync Toolbar button and the Sync spinner in the FxA hamburger area.)
+ If Sync is configured, the tooltip is when the last sync occurred,
+ otherwise the tooltip reflects the fact that Sync needs to be
+ (re-)configured.
+ */
+ _updateSyncButtonsTooltip: Task.async(function* () {
+ if (!gBrowser)
+ return;
+
+ let email;
+ try {
+ email = Services.prefs.getCharPref("services.sync.username");
+ } catch (ex) {}
+
+ let needsSetup = yield this._needsSetup();
+ let needsVerification = yield this._needsVerification();
+ let loginFailed = this._loginFailed();
+ // This is a little messy as the Sync buttons are 1/2 Sync related and
+ // 1/2 FxA related - so for some strings we use Sync strings, but for
+ // others we reach into gFxAccounts for strings.
+ let tooltiptext;
+ if (needsVerification) {
+ // "needs verification"
+ tooltiptext = gFxAccounts.strings.formatStringFromName("verifyDescription", [email], 1);
+ } else if (needsSetup) {
+ // "needs setup".
+ tooltiptext = this._stringBundle.GetStringFromName("signInToSync.description");
+ } else if (loginFailed) {
+ // "need to reconnect/re-enter your password"
+ tooltiptext = gFxAccounts.strings.formatStringFromName("reconnectDescription", [email], 1);
+ } else {
+ // Sync appears configured - format the "last synced at" time.
+ try {
+ let lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync"));
+ tooltiptext = this.formatLastSyncDate(lastSync);
+ }
+ catch (e) {
+ // pref doesn't exist (which will be the case until we've seen the
+ // first successful sync) or is invalid (which should be impossible!)
+ // Just leave tooltiptext as the empty string in these cases, which
+ // will cause the tooltip to be removed below.
+ }
+ }
+
+ // We've done all our promise-y work and ready to update the UI - make
+ // sure it hasn't been torn down since we started.
+ if (!gBrowser)
+ return;
+
+ let broadcaster = document.getElementById("sync-status");
+ if (broadcaster) {
+ if (tooltiptext) {
+ broadcaster.setAttribute("tooltiptext", tooltiptext);
+ } else {
+ broadcaster.removeAttribute("tooltiptext");
+ }
+ }
+ }),
+
+ formatLastSyncDate: function(date) {
+ let dateFormat;
+ let sixDaysAgo = (() => {
+ let date = new Date();
+ date.setDate(date.getDate() - 6);
+ date.setHours(0, 0, 0, 0);
+ return date;
+ })();
+ // It may be confusing for the user to see "Last Sync: Monday" when the last sync was a indeed a Monday but 3 weeks ago
+ if (date < sixDaysAgo) {
+ dateFormat = {month: 'long', day: 'numeric'};
+ } else {
+ dateFormat = {weekday: 'long', hour: 'numeric', minute: 'numeric'};
+ }
+ let lastSyncDateString = date.toLocaleDateString(undefined, dateFormat);
+ return this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
+ },
+
+ onClientsSynced: function() {
+ let broadcaster = document.getElementById("sync-syncnow-state");
+ if (broadcaster) {
+ if (Weave.Service.clientsEngine.stats.numClients > 1) {
+ broadcaster.setAttribute("devices-status", "multi");
+ } else {
+ broadcaster.setAttribute("devices-status", "single");
+ }
+ }
+ },
+
+ observe: function SUI_observe(subject, topic, data) {
+ this.log.debug("observed", topic);
+ if (this._unloaded) {
+ Cu.reportError("SyncUI observer called after unload: " + topic);
+ return;
+ }
+
+ // Unwrap, just like Svc.Obs, but without pulling in that dependency.
+ if (subject && typeof subject == "object" &&
+ ("wrappedJSObject" in subject) &&
+ ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) {
+ subject = subject.wrappedJSObject.object;
+ }
+
+ // First handle "activity" only.
+ switch (topic) {
+ case "weave:service:sync:start":
+ this.onActivityStart();
+ break;
+ case "weave:service:sync:finish":
+ case "weave:service:sync:error":
+ this.onActivityStop();
+ break;
+ }
+ // Now non-activity state (eg, enabled, errors, etc)
+ // Note that sync uses the ":ui:" notifications for errors because sync.
+ switch (topic) {
+ case "weave:ui:sync:finish":
+ // Do nothing.
+ break;
+ case "weave:ui:sync:error":
+ case "weave:service:setup-complete":
+ case "weave:service:login:finish":
+ case "weave:service:login:start":
+ case "weave:service:start-over":
+ this.updateUI();
+ break;
+ case "weave:ui:login:error":
+ case "weave:service:login:error":
+ this.onLoginError();
+ break;
+ case "weave:service:logout:finish":
+ this.onLogout();
+ break;
+ case "weave:service:start-over:finish":
+ this.updateUI();
+ break;
+ case "weave:service:ready":
+ this.initUI();
+ break;
+ case "weave:notification:added":
+ this.initNotifications();
+ break;
+ case "weave:engine:sync:finish":
+ if (data != "clients") {
+ return;
+ }
+ this.onClientsSynced();
+ break;
+ case "quit-application":
+ // Stop the animation timer on shutdown, since we can't update the UI
+ // after this.
+ clearTimeout(this._syncAnimationTimer);
+ break;
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ])
+};
+
+XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
+ // XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
+ // but for now just make it work
+ return Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle("chrome://weave/locale/services/sync.properties");
+});
+
+XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
+ return Log.repository.getLogger("browserwindow.syncui");
+});
+
+XPCOMUtils.defineLazyGetter(gSyncUI, "weaveService", function() {
+ return Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+});
diff --git a/browser/base/content/browser-tabPreviews.xml b/browser/base/content/browser-tabPreviews.xml
new file mode 100644
index 000000000..f3f2ad180
--- /dev/null
+++ b/browser/base/content/browser-tabPreviews.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+
+# -*- Mode: HTML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<bindings id="tabPreviews"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+ <binding id="ctrlTab-preview" extends="chrome://global/content/bindings/button.xml#button-base">
+ <content pack="center">
+ <xul:stack>
+ <xul:vbox class="ctrlTab-preview-inner" align="center" pack="center"
+ xbl:inherits="width=canvaswidth">
+ <xul:hbox class="tabPreview-canvas" xbl:inherits="style=canvasstyle">
+ <children/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=label,crop" class="plain"/>
+ </xul:vbox>
+ <xul:hbox class="ctrlTab-favicon-container" xbl:inherits="hidden=noicon">
+ <xul:image class="ctrlTab-favicon" xbl:inherits="src=image"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+ <handlers>
+ <handler event="mouseover" action="ctrlTab._mouseOverFocus(this);"/>
+ <handler event="command" action="ctrlTab.pick(this);"/>
+ <handler event="click" button="1" action="ctrlTab.remove(this);"/>
+#ifdef XP_MACOSX
+# Control+click is a right click on OS X
+ <handler event="click" button="2" action="ctrlTab.pick(this);"/>
+#endif
+ </handlers>
+ </binding>
+</bindings>
diff --git a/browser/base/content/browser-tabsintitlebar-stub.js b/browser/base/content/browser-tabsintitlebar-stub.js
new file mode 100644
index 000000000..1e45b17dd
--- /dev/null
+++ b/browser/base/content/browser-tabsintitlebar-stub.js
@@ -0,0 +1,17 @@
+/* -*- 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 is used as a stub object for platforms which
+// don't have CAN_DRAW_IN_TITLEBAR defined.
+
+var TabsInTitlebar = {
+ init: function () {},
+ uninit: function () {},
+ allowedBy: function (condition, allow) {},
+ updateAppearance: function updateAppearance(aForce) {},
+ get enabled() {
+ return document.documentElement.getAttribute("tabsintitlebar") == "true";
+ },
+};
diff --git a/browser/base/content/browser-tabsintitlebar.js b/browser/base/content/browser-tabsintitlebar.js
new file mode 100644
index 000000000..5c0d94514
--- /dev/null
+++ b/browser/base/content/browser-tabsintitlebar.js
@@ -0,0 +1,307 @@
+/* -*- 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/. */
+
+// Note: the file browser-tabsintitlebar-stub.js is used instead of
+// this one on platforms which don't have CAN_DRAW_IN_TITLEBAR defined.
+
+var TabsInTitlebar = {
+ init: function () {
+ if (this._initialized) {
+ return;
+ }
+ this._readPref();
+ Services.prefs.addObserver(this._prefName, this, false);
+
+ // We need to update the appearance of the titlebar when the menu changes
+ // from the active to the inactive state. We can't, however, rely on
+ // DOMMenuBarInactive, because the menu fires this event and then removes
+ // the inactive attribute after an event-loop spin.
+ //
+ // Because updating the appearance involves sampling the heights and margins
+ // of various elements, it's important that the layout be more or less
+ // settled before updating the titlebar. So instead of listening to
+ // DOMMenuBarActive and DOMMenuBarInactive, we use a MutationObserver to
+ // watch the "invalid" attribute directly.
+ let menu = document.getElementById("toolbar-menubar");
+ this._menuObserver = new MutationObserver(this._onMenuMutate);
+ this._menuObserver.observe(menu, {attributes: true});
+
+ this.onAreaReset = function(aArea) {
+ if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
+ this._update(true);
+ };
+ this.onWidgetAdded = this.onWidgetRemoved = function(aWidgetId, aArea) {
+ if (aArea == CustomizableUI.AREA_TABSTRIP || aArea == CustomizableUI.AREA_MENUBAR)
+ this._update(true);
+ };
+ CustomizableUI.addListener(this);
+
+ addEventListener("resolutionchange", this, false);
+
+ this._initialized = true;
+ if (this._updateOnInit) {
+ // We don't need to call this with 'true', even if original calls
+ // (before init()) did, because this will be the first call and so
+ // we will update anyway.
+ this._update();
+ }
+ },
+
+ allowedBy: function (condition, allow) {
+ if (allow) {
+ if (condition in this._disallowed) {
+ delete this._disallowed[condition];
+ this._update(true);
+ }
+ } else if (!(condition in this._disallowed)) {
+ this._disallowed[condition] = null;
+ this._update(true);
+ }
+ },
+
+ updateAppearance: function updateAppearance(aForce) {
+ this._update(aForce);
+ },
+
+ get enabled() {
+ return document.documentElement.getAttribute("tabsintitlebar") == "true";
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this._readPref();
+ },
+
+ handleEvent: function (aEvent) {
+ if (aEvent.type == "resolutionchange" && aEvent.target == window) {
+ this._update(true);
+ }
+ },
+
+ _onMenuMutate: function (aMutations) {
+ for (let mutation of aMutations) {
+ if (mutation.attributeName == "inactive" ||
+ mutation.attributeName == "autohide") {
+ TabsInTitlebar._update(true);
+ return;
+ }
+ }
+ },
+
+ _initialized: false,
+ _updateOnInit: false,
+ _disallowed: {},
+ _prefName: "browser.tabs.drawInTitlebar",
+ _lastSizeMode: null,
+
+ _readPref: function () {
+ this.allowedBy("pref",
+ Services.prefs.getBoolPref(this._prefName));
+ },
+
+ _update: function (aForce=false) {
+ let $ = id => document.getElementById(id);
+ let rect = ele => ele.getBoundingClientRect();
+ let verticalMargins = cstyle => parseFloat(cstyle.marginBottom) + parseFloat(cstyle.marginTop);
+
+ if (window.fullScreen)
+ return;
+
+ // In some edgecases it is possible for this to fire before we've initialized.
+ // Don't run now, but don't forget to run it when we do initialize.
+ if (!this._initialized) {
+ this._updateOnInit = true;
+ return;
+ }
+
+ if (!aForce) {
+ // _update is called on resize events, because the window is not ready
+ // after sizemode events. However, we only care about the event when the
+ // sizemode is different from the last time we updated the appearance of
+ // the tabs in the titlebar.
+ let sizemode = document.documentElement.getAttribute("sizemode");
+ if (this._lastSizeMode == sizemode) {
+ return;
+ }
+ let oldSizeMode = this._lastSizeMode;
+ this._lastSizeMode = sizemode;
+ // Don't update right now if we are leaving fullscreen, since the UI is
+ // still changing in the consequent "fullscreen" event. Code there will
+ // call this function again when everything is ready.
+ // See browser-fullScreen.js: FullScreen.toggle and bug 1173768.
+ if (oldSizeMode == "fullscreen") {
+ return;
+ }
+ }
+
+ let allowed = (Object.keys(this._disallowed)).length == 0;
+
+ let titlebar = $("titlebar");
+ let titlebarContent = $("titlebar-content");
+ let menubar = $("toolbar-menubar");
+
+ if (allowed) {
+ // We set the tabsintitlebar attribute first so that our CSS for
+ // tabsintitlebar manifests before we do our measurements.
+ document.documentElement.setAttribute("tabsintitlebar", "true");
+ updateTitlebarDisplay();
+
+ // Try to avoid reflows in this code by calculating dimensions first and
+ // then later set the properties affecting layout together in a batch.
+
+ // Get the full height of the tabs toolbar:
+ let tabsToolbar = $("TabsToolbar");
+ let tabsStyles = window.getComputedStyle(tabsToolbar);
+ let fullTabsHeight = rect(tabsToolbar).height + verticalMargins(tabsStyles);
+ // Buttons first:
+ let captionButtonsBoxWidth = rect($("titlebar-buttonbox-container")).width;
+
+ let secondaryButtonsWidth, menuHeight, fullMenuHeight, menuStyles;
+ if (AppConstants.platform == "macosx") {
+ secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
+ // No need to look up the menubar stuff on OS X:
+ menuHeight = 0;
+ fullMenuHeight = 0;
+ } else {
+ // Otherwise, get the height and margins separately for the menubar
+ menuHeight = rect(menubar).height;
+ menuStyles = window.getComputedStyle(menubar);
+ fullMenuHeight = verticalMargins(menuStyles) + menuHeight;
+ }
+
+ // And get the height of what's in the titlebar:
+ let titlebarContentHeight = rect(titlebarContent).height;
+
+ // Begin setting CSS properties which will cause a reflow
+
+ // If the menubar is around (menuHeight is non-zero), try to adjust
+ // its full height (i.e. including margins) to match the titlebar,
+ // by changing the menubar's bottom padding
+ if (menuHeight) {
+ // Calculate the difference between the titlebar's height and that of the menubar
+ let menuTitlebarDelta = titlebarContentHeight - fullMenuHeight;
+ let paddingBottom;
+ // The titlebar is bigger:
+ if (menuTitlebarDelta > 0) {
+ fullMenuHeight += menuTitlebarDelta;
+ // If there is already padding on the menubar, we need to add that
+ // to the difference so the total padding is correct:
+ if ((paddingBottom = menuStyles.paddingBottom)) {
+ menuTitlebarDelta += parseFloat(paddingBottom);
+ }
+ menubar.style.paddingBottom = menuTitlebarDelta + "px";
+ // The menubar is bigger, but has bottom padding we can remove:
+ } else if (menuTitlebarDelta < 0 && (paddingBottom = menuStyles.paddingBottom)) {
+ let existingPadding = parseFloat(paddingBottom);
+ // menuTitlebarDelta is negative; work out what's left, but don't set negative padding:
+ let desiredPadding = Math.max(0, existingPadding + menuTitlebarDelta);
+ menubar.style.paddingBottom = desiredPadding + "px";
+ // We've changed the menu height now:
+ fullMenuHeight += desiredPadding - existingPadding;
+ }
+ }
+
+ // Next, we calculate how much we need to stretch the titlebar down to
+ // go all the way to the bottom of the tab strip, if necessary.
+ let tabAndMenuHeight = fullTabsHeight + fullMenuHeight;
+
+ if (tabAndMenuHeight > titlebarContentHeight) {
+ // We need to increase the titlebar content's outer height (ie including margins)
+ // to match the tab and menu height:
+ let extraMargin = tabAndMenuHeight - titlebarContentHeight;
+ if (AppConstants.platform != "macosx") {
+ titlebarContent.style.marginBottom = extraMargin + "px";
+ }
+
+ titlebarContentHeight += extraMargin;
+ } else {
+ titlebarContent.style.removeProperty("margin-bottom");
+ }
+
+ // Then add a negative margin to the titlebar, so that the following elements
+ // will overlap it by the lesser of the titlebar height or the tabstrip+menu.
+ let minTitlebarOrTabsHeight = Math.min(titlebarContentHeight, tabAndMenuHeight);
+ titlebar.style.marginBottom = "-" + minTitlebarOrTabsHeight + "px";
+
+ // Finally, size the placeholders:
+ if (AppConstants.platform == "macosx") {
+ this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
+ }
+ this._sizePlaceholder("caption-buttons", captionButtonsBoxWidth);
+
+ } else {
+ document.documentElement.removeAttribute("tabsintitlebar");
+ updateTitlebarDisplay();
+
+ if (AppConstants.platform == "macosx") {
+ let secondaryButtonsWidth = rect($("titlebar-secondary-buttonbox")).width;
+ this._sizePlaceholder("fullscreen-button", secondaryButtonsWidth);
+ }
+
+ // Reset the margins and padding that might have been modified:
+ titlebarContent.style.marginTop = "";
+ titlebarContent.style.marginBottom = "";
+ titlebar.style.marginBottom = "";
+ menubar.style.paddingBottom = "";
+ }
+
+ ToolbarIconColor.inferFromText();
+ if (CustomizationHandler.isCustomizing()) {
+ gCustomizeMode.updateLWTStyling();
+ }
+ },
+
+ _sizePlaceholder: function (type, width) {
+ Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
+ function (node) { node.width = width; });
+ },
+
+ uninit: function () {
+ this._initialized = false;
+ removeEventListener("resolutionchange", this);
+ Services.prefs.removeObserver(this._prefName, this);
+ this._menuObserver.disconnect();
+ CustomizableUI.removeListener(this);
+ }
+};
+
+function updateTitlebarDisplay() {
+ if (AppConstants.platform == "macosx") {
+ // OS X and the other platforms differ enough to necessitate this kind of
+ // special-casing. Like the other platforms where we CAN_DRAW_IN_TITLEBAR,
+ // we draw in the OS X titlebar when putting the tabs up there. However, OS X
+ // also draws in the titlebar when a lightweight theme is applied, regardless
+ // of whether or not the tabs are drawn in the titlebar.
+ if (TabsInTitlebar.enabled) {
+ document.documentElement.setAttribute("chromemargin-nonlwtheme", "0,-1,-1,-1");
+ document.documentElement.setAttribute("chromemargin", "0,-1,-1,-1");
+ document.documentElement.removeAttribute("drawtitle");
+ } else {
+ // We set chromemargin-nonlwtheme to "" instead of removing it as a way of
+ // making sure that LightweightThemeConsumer doesn't take it upon itself to
+ // detect this value again if and when we do a lwtheme state change.
+ document.documentElement.setAttribute("chromemargin-nonlwtheme", "");
+ let isCustomizing = document.documentElement.hasAttribute("customizing");
+ let hasLWTheme = document.documentElement.hasAttribute("lwtheme");
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ if ((!hasLWTheme || isCustomizing) && !isPrivate) {
+ document.documentElement.removeAttribute("chromemargin");
+ }
+ document.documentElement.setAttribute("drawtitle", "true");
+ }
+ } else if (TabsInTitlebar.enabled) {
+ // not OS X
+ document.documentElement.setAttribute("chromemargin", "0,2,2,2");
+ } else {
+ document.documentElement.removeAttribute("chromemargin");
+ }
+}
+
+function onTitlebarMaxClick() {
+ if (window.windowState == window.STATE_MAXIMIZED)
+ window.restore();
+ else
+ window.maximize();
+}
diff --git a/browser/base/content/browser-thumbnails.js b/browser/base/content/browser-thumbnails.js
new file mode 100644
index 000000000..ebefb193e
--- /dev/null
+++ b/browser/base/content/browser-thumbnails.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/. */
+
+/**
+ * Keeps thumbnails of open web pages up-to-date.
+ */
+var gBrowserThumbnails = {
+ /**
+ * Pref that controls whether we can store SSL content on disk
+ */
+ PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
+
+ _captureDelayMS: 1000,
+
+ /**
+ * Used to keep track of disk_cache_ssl preference
+ */
+ _sslDiskCacheEnabled: null,
+
+ /**
+ * Map of capture() timeouts assigned to their browsers.
+ */
+ _timeouts: null,
+
+ /**
+ * List of tab events we want to listen for.
+ */
+ _tabEvents: ["TabClose", "TabSelect"],
+
+ init: function Thumbnails_init() {
+ PageThumbs.addExpirationFilter(this);
+ gBrowser.addTabsProgressListener(this);
+ Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
+
+ this._sslDiskCacheEnabled =
+ Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+
+ this._tabEvents.forEach(function (aEvent) {
+ gBrowser.tabContainer.addEventListener(aEvent, this, false);
+ }, this);
+
+ this._timeouts = new WeakMap();
+ },
+
+ uninit: function Thumbnails_uninit() {
+ PageThumbs.removeExpirationFilter(this);
+ gBrowser.removeTabsProgressListener(this);
+ Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
+
+ this._tabEvents.forEach(function (aEvent) {
+ gBrowser.tabContainer.removeEventListener(aEvent, this, false);
+ }, this);
+ },
+
+ handleEvent: function Thumbnails_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "scroll":
+ let browser = aEvent.currentTarget;
+ if (this._timeouts.has(browser))
+ this._delayedCapture(browser);
+ break;
+ case "TabSelect":
+ this._delayedCapture(aEvent.target.linkedBrowser);
+ break;
+ case "TabClose": {
+ this._clearTimeout(aEvent.target.linkedBrowser);
+ break;
+ }
+ }
+ },
+
+ observe: function Thumbnails_observe() {
+ this._sslDiskCacheEnabled =
+ Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+ },
+
+ filterForThumbnailExpiration:
+ function Thumbnails_filterForThumbnailExpiration(aCallback) {
+ aCallback(this._topSiteURLs);
+ },
+
+ /**
+ * State change progress listener for all tabs.
+ */
+ onStateChange: function Thumbnails_onStateChange(aBrowser, aWebProgress,
+ aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
+ this._delayedCapture(aBrowser);
+ },
+
+ _capture: function Thumbnails_capture(aBrowser) {
+ // Only capture about:newtab top sites.
+ if (this._topSiteURLs.indexOf(aBrowser.currentURI.spec) == -1)
+ return;
+ this._shouldCapture(aBrowser, function (aResult) {
+ if (aResult) {
+ PageThumbs.captureAndStoreIfStale(aBrowser);
+ }
+ });
+ },
+
+ _delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
+ if (this._timeouts.has(aBrowser))
+ clearTimeout(this._timeouts.get(aBrowser));
+ else
+ aBrowser.addEventListener("scroll", this, true);
+
+ let timeout = setTimeout(function () {
+ this._clearTimeout(aBrowser);
+ this._capture(aBrowser);
+ }.bind(this), this._captureDelayMS);
+
+ this._timeouts.set(aBrowser, timeout);
+ },
+
+ _shouldCapture: function Thumbnails_shouldCapture(aBrowser, aCallback) {
+ // Capture only if it's the currently selected tab.
+ if (aBrowser != gBrowser.selectedBrowser) {
+ aCallback(false);
+ return;
+ }
+ PageThumbs.shouldStoreThumbnail(aBrowser, aCallback);
+ },
+
+ get _topSiteURLs() {
+ return NewTabUtils.links.getLinks().reduce((urls, link) => {
+ if (link)
+ urls.push(link.url);
+ return urls;
+ }, []);
+ },
+
+ _clearTimeout: function Thumbnails_clearTimeout(aBrowser) {
+ if (this._timeouts.has(aBrowser)) {
+ aBrowser.removeEventListener("scroll", this, false);
+ clearTimeout(this._timeouts.get(aBrowser));
+ this._timeouts.delete(aBrowser);
+ }
+ }
+};
diff --git a/browser/base/content/browser-trackingprotection.js b/browser/base/content/browser-trackingprotection.js
new file mode 100644
index 000000000..cacb0522c
--- /dev/null
+++ b/browser/base/content/browser-trackingprotection.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/. */
+
+var TrackingProtection = {
+ // If the user ignores the doorhanger, we stop showing it after some time.
+ MAX_INTROS: 20,
+ PREF_ENABLED_GLOBALLY: "privacy.trackingprotection.enabled",
+ PREF_ENABLED_IN_PRIVATE_WINDOWS: "privacy.trackingprotection.pbmode.enabled",
+ enabledGlobally: false,
+ enabledInPrivateWindows: false,
+ container: null,
+ content: null,
+ icon: null,
+ activeTooltipText: null,
+ disabledTooltipText: null,
+
+ init() {
+ let $ = selector => document.querySelector(selector);
+ this.container = $("#tracking-protection-container");
+ this.content = $("#tracking-protection-content");
+ this.icon = $("#tracking-protection-icon");
+
+ this.updateEnabled();
+ Services.prefs.addObserver(this.PREF_ENABLED_GLOBALLY, this, false);
+ Services.prefs.addObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this, false);
+
+ this.activeTooltipText =
+ gNavigatorBundle.getString("trackingProtection.icon.activeTooltip");
+ this.disabledTooltipText =
+ gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip");
+
+ this.enabledHistogramAdd(this.enabledGlobally);
+ this.disabledPBMHistogramAdd(!this.enabledInPrivateWindows);
+ },
+
+ uninit() {
+ Services.prefs.removeObserver(this.PREF_ENABLED_GLOBALLY, this);
+ Services.prefs.removeObserver(this.PREF_ENABLED_IN_PRIVATE_WINDOWS, this);
+ },
+
+ observe() {
+ this.updateEnabled();
+ },
+
+ get enabled() {
+ return this.enabledGlobally ||
+ (this.enabledInPrivateWindows &&
+ PrivateBrowsingUtils.isWindowPrivate(window));
+ },
+
+ updateEnabled() {
+ this.enabledGlobally =
+ Services.prefs.getBoolPref(this.PREF_ENABLED_GLOBALLY);
+ this.enabledInPrivateWindows =
+ Services.prefs.getBoolPref(this.PREF_ENABLED_IN_PRIVATE_WINDOWS);
+ this.container.hidden = !this.enabled;
+ },
+
+ enabledHistogramAdd(value) {
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+ Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").add(value);
+ },
+
+ disabledPBMHistogramAdd(value) {
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+ Services.telemetry.getHistogramById("TRACKING_PROTECTION_PBM_DISABLED").add(value);
+ },
+
+ eventsHistogramAdd(value) {
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+ Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS").add(value);
+ },
+
+ shieldHistogramAdd(value) {
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+ Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD").add(value);
+ },
+
+ onSecurityChange(state, isSimulated) {
+ if (!this.enabled) {
+ return;
+ }
+
+ // Only animate the shield if the event was not fired directly from
+ // the tabbrowser (due to a browser change).
+ if (isSimulated) {
+ this.icon.removeAttribute("animate");
+ } else {
+ this.icon.setAttribute("animate", "true");
+ }
+
+ let isBlocking = state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
+ let isAllowing = state & Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT;
+
+ if (isBlocking) {
+ this.icon.setAttribute("tooltiptext", this.activeTooltipText);
+ this.icon.setAttribute("state", "blocked-tracking-content");
+ this.content.setAttribute("state", "blocked-tracking-content");
+
+ // Open the tracking protection introduction panel, if applicable.
+ if (this.enabledGlobally) {
+ let introCount = gPrefService.getIntPref("privacy.trackingprotection.introCount");
+ if (introCount < TrackingProtection.MAX_INTROS) {
+ gPrefService.setIntPref("privacy.trackingprotection.introCount", ++introCount);
+ gPrefService.savePrefFile(null);
+ this.showIntroPanel();
+ }
+ }
+
+ this.shieldHistogramAdd(2);
+ } else if (isAllowing) {
+ this.icon.setAttribute("tooltiptext", this.disabledTooltipText);
+ this.icon.setAttribute("state", "loaded-tracking-content");
+ this.content.setAttribute("state", "loaded-tracking-content");
+
+ this.shieldHistogramAdd(1);
+ } else {
+ this.icon.removeAttribute("tooltiptext");
+ this.icon.removeAttribute("state");
+ this.content.removeAttribute("state");
+
+ // We didn't show the shield
+ this.shieldHistogramAdd(0);
+ }
+
+ // Telemetry for state change.
+ this.eventsHistogramAdd(0);
+ },
+
+ disableForCurrentPage() {
+ // Convert document URI into the format used by
+ // nsChannelClassifier::ShouldEnableTrackingProtection.
+ // Any scheme turned into https is correct.
+ let normalizedUrl = Services.io.newURI(
+ "https://" + gBrowser.selectedBrowser.currentURI.hostPort,
+ null, null);
+
+ // Add the current host in the 'trackingprotection' consumer of
+ // the permission manager using a normalized URI. This effectively
+ // places this host on the tracking protection allowlist.
+ if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
+ PrivateBrowsingUtils.addToTrackingAllowlist(normalizedUrl);
+ } else {
+ Services.perms.add(normalizedUrl,
+ "trackingprotection", Services.perms.ALLOW_ACTION);
+ }
+
+ // Telemetry for disable protection.
+ this.eventsHistogramAdd(1);
+
+ // Hide the control center.
+ document.getElementById("identity-popup").hidePopup();
+
+ BrowserReload();
+ },
+
+ enableForCurrentPage() {
+ // Remove the current host from the 'trackingprotection' consumer
+ // of the permission manager. This effectively removes this host
+ // from the tracking protection allowlist.
+ let normalizedUrl = Services.io.newURI(
+ "https://" + gBrowser.selectedBrowser.currentURI.hostPort,
+ null, null);
+
+ if (PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser)) {
+ PrivateBrowsingUtils.removeFromTrackingAllowlist(normalizedUrl);
+ } else {
+ Services.perms.remove(normalizedUrl, "trackingprotection");
+ }
+
+ // Telemetry for enable protection.
+ this.eventsHistogramAdd(2);
+
+ // Hide the control center.
+ document.getElementById("identity-popup").hidePopup();
+
+ BrowserReload();
+ },
+
+ dontShowIntroPanelAgain() {
+ // This function may be called in private windows, but it does not change
+ // any preference unless Tracking Protection is enabled globally.
+ if (this.enabledGlobally) {
+ gPrefService.setIntPref("privacy.trackingprotection.introCount",
+ this.MAX_INTROS);
+ gPrefService.savePrefFile(null);
+ }
+ },
+
+ showIntroPanel: Task.async(function*() {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+
+ let openStep2 = () => {
+ // When the user proceeds in the tour, adjust the counter to indicate that
+ // the user doesn't need to see the intro anymore.
+ this.dontShowIntroPanelAgain();
+
+ let nextURL = Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL") +
+ "?step=2&newtab=true";
+ switchToTabHavingURI(nextURL, true, {
+ // Ignore the fragment in case the intro is shown on the tour page
+ // (e.g. if the user manually visited the tour or clicked the link from
+ // about:privatebrowsing) so we can avoid a reload.
+ ignoreFragment: "whenComparingAndReplace",
+ });
+ };
+
+ let buttons = [
+ {
+ label: gNavigatorBundle.getString("trackingProtection.intro.step1of3"),
+ style: "text",
+ },
+ {
+ callback: openStep2,
+ label: gNavigatorBundle.getString("trackingProtection.intro.nextButton.label"),
+ style: "primary",
+ },
+ ];
+
+ let panelTarget = yield UITour.getTarget(window, "trackingProtection");
+ UITour.initForBrowser(gBrowser.selectedBrowser, window);
+ UITour.showInfo(window, panelTarget,
+ gNavigatorBundle.getString("trackingProtection.intro.title"),
+ gNavigatorBundle.getFormattedString("trackingProtection.intro.description2",
+ [brandShortName]),
+ undefined, buttons,
+ { closeButtonCallback: () => this.dontShowIntroPanelAgain() });
+ }),
+};
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
new file mode 100644
index 000000000..a05b031b2
--- /dev/null
+++ b/browser/base/content/browser.css
@@ -0,0 +1,1244 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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");
+@namespace svg url("http://www.w3.org/2000/svg");
+
+:root {
+ --identity-popup-expander-width: 38px;
+ --panelui-subview-transition-duration: 150ms;
+}
+
+#main-window:not([chromehidden~="toolbar"]) {
+%ifdef XP_MACOSX
+ min-width: 335px;
+%else
+ min-width: 300px;
+%endif
+}
+
+#main-window[customize-entered] {
+ min-width: -moz-fit-content;
+}
+
+searchbar {
+ -moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
+}
+
+/* Prevent shrinking the page content to 0 height and width */
+.browserStack > browser {
+ min-height: 25px;
+ min-width: 25px;
+}
+
+.browserStack > browser {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-browser");
+}
+
+.browserStack > browser[remote="true"] {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-remote-browser");
+}
+
+toolbar[customizable="true"] {
+ -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar");
+}
+
+%ifdef XP_MACOSX
+#toolbar-menubar {
+ -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-menubar-stub");
+}
+%endif
+
+#toolbar-menubar[autohide="true"] {
+ -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-menubar-autohide");
+}
+
+#addon-bar {
+ -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#addonbar-delegating");
+ visibility: visible;
+ margin: 0;
+ height: 0 !important;
+ overflow: hidden;
+ padding: 0;
+ border: 0 none;
+}
+
+#addonbar-closebutton {
+ visibility: visible;
+ height: 0 !important;
+}
+
+#status-bar {
+ height: 0 !important;
+ -moz-binding: none;
+ padding: 0;
+ margin: 0;
+}
+
+panelmultiview {
+ -moz-binding: url("chrome://browser/content/customizableui/panelUI.xml#panelmultiview");
+}
+
+panelview {
+ -moz-binding: url("chrome://browser/content/customizableui/panelUI.xml#panelview");
+ -moz-box-orient: vertical;
+}
+
+.panel-mainview {
+ transition: transform var(--panelui-subview-transition-duration);
+}
+
+panelview:not([mainview]):not([current]) {
+ transition: visibility 0s linear var(--panelui-subview-transition-duration);
+ visibility: collapse;
+}
+
+browser[frameType="social"][remote="true"] {
+ -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
+}
+
+tabbrowser {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser");
+}
+
+.tabbrowser-tabs {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
+}
+
+#tabbrowser-tabs:not([overflow="true"]) ~ #alltabs-button,
+#tabbrowser-tabs:not([overflow="true"]) + #new-tab-button,
+#tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
+#TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
+#TabsToolbar[customizing="true"] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
+ visibility: collapse;
+}
+
+#tabbrowser-tabs:not([overflow="true"])[using-closing-tabs-spacer] ~ #alltabs-button {
+ visibility: hidden; /* temporary space to keep a tab's close button under the cursor */
+}
+
+.tabs-newtab-button > .toolbarbutton-menu-dropmarker,
+#new-tab-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+/* override drop marker image padding */
+.tabs-newtab-button > .toolbarbutton-icon {
+ margin-inline-end: 0;
+}
+
+.tabbrowser-tab {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
+}
+
+.tabbrowser-tab:not([pinned]) {
+ -moz-box-flex: 100;
+ max-width: 210px;
+ min-width: 100px;
+ width: 0;
+ transition: min-width 100ms ease-out,
+ max-width 100ms ease-out;
+}
+
+.tabbrowser-tab:not([pinned]):not([fadein]) {
+ max-width: 0.1px;
+ min-width: 0.1px;
+ visibility: hidden;
+}
+
+.tab-close-button,
+.tab-background {
+ /* Explicitly set the visibility to override the value (collapsed)
+ * we inherit from #TabsToolbar[collapsed] upon opening a browser window. */
+ visibility: visible;
+}
+
+.tab-close-button[fadein],
+.tab-background[fadein] {
+ /* This transition is only wanted for opening tabs. */
+ transition: visibility 0ms 25ms;
+}
+
+.tab-close-button:not([fadein]),
+.tab-background:not([fadein]) {
+ visibility: hidden;
+}
+
+.tab-label:not([fadein]),
+.tab-throbber:not([fadein]),
+.tab-icon-image:not([fadein]) {
+ display: none;
+}
+
+.tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
+ position: fixed !important;
+ display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
+}
+
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
+ position: relative;
+ z-index: 2;
+ pointer-events: none; /* avoid blocking dragover events on scroll buttons */
+}
+
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
+ transition: transform 200ms ease-out;
+}
+
+.new-tab-popup,
+#alltabs-popup {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
+}
+
+toolbar[printpreview="true"] {
+ -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
+}
+
+toolbar[overflowable] > .customization-target {
+ overflow: hidden;
+}
+
+toolbar:not([overflowing]) > .overflow-button,
+toolbar[customizing] > .overflow-button {
+ display: none;
+}
+
+%ifdef CAN_DRAW_IN_TITLEBAR
+#main-window:not([chromemargin]) > #titlebar,
+#main-window[inFullscreen] > #titlebar,
+#main-window[inFullscreen] .titlebar-placeholder,
+#main-window:not([tabsintitlebar]) .titlebar-placeholder {
+ display: none;
+}
+
+#titlebar {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
+ -moz-window-dragging: drag;
+}
+
+#titlebar-spacer {
+ pointer-events: none;
+}
+
+#main-window[tabsintitlebar] #titlebar-buttonbox {
+ position: relative;
+}
+
+#titlebar-buttonbox {
+ -moz-appearance: -moz-window-button-box;
+}
+
+#personal-bookmarks {
+ -moz-window-dragging: inherit;
+}
+
+%ifdef XP_MACOSX
+#titlebar-fullscreen-button {
+ -moz-appearance: -moz-mac-fullscreen-button;
+}
+
+/* Fullscreen and caption buttons don't move with RTL on OS X so override the automatic ordering. */
+#titlebar-secondary-buttonbox:-moz-locale-dir(ltr),
+#titlebar-buttonbox-container:-moz-locale-dir(rtl),
+.titlebar-placeholder[type="fullscreen-button"]:-moz-locale-dir(ltr),
+.titlebar-placeholder[type="caption-buttons"]:-moz-locale-dir(rtl) {
+ -moz-box-ordinal-group: 1000;
+}
+
+#titlebar-secondary-buttonbox:-moz-locale-dir(rtl),
+#titlebar-buttonbox-container:-moz-locale-dir(ltr),
+.titlebar-placeholder[type="caption-buttons"]:-moz-locale-dir(ltr),
+.titlebar-placeholder[type="fullscreen-button"]:-moz-locale-dir(rtl) {
+ -moz-box-ordinal-group: 0;
+}
+
+%else
+/* On non-OSX, these should be start-aligned */
+#titlebar-buttonbox-container {
+ -moz-box-align: start;
+}
+%endif
+
+%if !defined(MOZ_WIDGET_GTK)
+#TabsToolbar > .private-browsing-indicator {
+ -moz-box-ordinal-group: 1000;
+}
+%endif
+
+%ifdef XP_WIN
+#main-window[sizemode="maximized"] #titlebar-buttonbox {
+ -moz-appearance: -moz-window-button-box-maximized;
+}
+
+#main-window[tabletmode] #titlebar-min,
+#main-window[tabletmode] #titlebar-max {
+ display: none !important;
+}
+
+#main-window[tabsintitlebar] #TabsToolbar,
+#main-window[tabsintitlebar] #toolbar-menubar,
+#main-window[tabsintitlebar] #navigator-toolbox > toolbar:-moz-lwtheme {
+ -moz-window-dragging: drag;
+}
+%endif
+
+%endif
+
+#main-window[inFullscreen][inDOMFullscreen] #navigator-toolbox,
+#main-window[inFullscreen][inDOMFullscreen] #fullscr-toggler,
+#main-window[inFullscreen][inDOMFullscreen] #sidebar-box,
+#main-window[inFullscreen][inDOMFullscreen] #sidebar-splitter,
+#main-window[inFullscreen]:not([OSXLionFullscreen]) toolbar:not([fullscreentoolbar=true]),
+#main-window[inFullscreen] #global-notificationbox,
+#main-window[inFullscreen] #high-priority-global-notificationbox {
+ visibility: collapse;
+}
+
+#navigator-toolbox[fullscreenShouldAnimate] {
+ transition: 1.5s margin-top ease-out;
+}
+
+/* Rules to help integrate SDK widgets */
+toolbaritem[sdkstylewidget="true"] > toolbarbutton,
+toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > iframe,
+toolbarpaletteitem > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-text {
+ display: none;
+}
+
+toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > toolbarbutton {
+ display: -moz-box;
+}
+
+toolbarpaletteitem > toolbaritem[sdkstylewidget="true"][cui-areatype="toolbar"] > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+@media not all and (min-resolution: 1.1dppx) {
+ .webextension-browser-action {
+ list-style-image: var(--webextension-toolbar-image);
+ }
+
+ .webextension-browser-action[cui-areatype="menu-panel"],
+ toolbarpaletteitem[place="palette"] > .webextension-browser-action {
+ list-style-image: var(--webextension-menupanel-image);
+ }
+
+ .webextension-page-action {
+ list-style-image: var(--webextension-urlbar-image);
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ .webextension-browser-action {
+ list-style-image: var(--webextension-toolbar-image-2x);
+ }
+
+ .webextension-browser-action[cui-areatype="menu-panel"],
+ toolbarpaletteitem[place="palette"] > .webextension-browser-action {
+ list-style-image: var(--webextension-menupanel-image-2x);
+ }
+
+ .webextension-page-action {
+ list-style-image: var(--webextension-urlbar-image-2x);
+ }
+}
+
+toolbarpaletteitem[removable="false"] {
+ opacity: 0.5;
+ cursor: default;
+}
+
+%ifndef XP_MACOSX
+toolbarpaletteitem[place="palette"],
+toolbarpaletteitem[place="panel"],
+toolbarpaletteitem[place="toolbar"] {
+ -moz-user-focus: normal;
+}
+%endif
+
+#bookmarks-toolbar-placeholder,
+toolbarpaletteitem > #personal-bookmarks > #PlacesToolbar,
+#personal-bookmarks[cui-areatype="menu-panel"] > #PlacesToolbar,
+#personal-bookmarks[cui-areatype="toolbar"][overflowedItem=true] > #PlacesToolbar {
+ display: none;
+}
+
+#PlacesToolbarDropIndicatorHolder {
+ position: absolute;
+ top: 25%;
+}
+
+toolbarpaletteitem > #personal-bookmarks > #bookmarks-toolbar-placeholder,
+#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder,
+#personal-bookmarks[cui-areatype="toolbar"][overflowedItem=true] > #bookmarks-toolbar-placeholder {
+ display: -moz-box;
+}
+
+#nav-bar-customization-target > #personal-bookmarks,
+toolbar:not(#TabsToolbar) > #wrapper-personal-bookmarks,
+toolbar:not(#TabsToolbar) > #personal-bookmarks {
+ -moz-box-flex: 1;
+}
+
+#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+#urlbar-reload-button:not([displaystop]) + #urlbar-stop-button,
+#urlbar-reload-button[displaystop] {
+ visibility: collapse;
+}
+
+#PanelUI-feeds > .feed-toolbarbutton:-moz-locale-dir(rtl) {
+ direction: rtl;
+}
+
+#panelMenu_bookmarksMenu > .bookmark-item {
+ max-width: none;
+}
+
+#urlbar-container {
+ min-width: 50ch;
+}
+
+#search-container {
+ min-width: 25ch;
+}
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ .searchbar-engine-image {
+ image-rendering: -moz-crisp-edges;
+ }
+}
+
+#urlbar,
+.searchbar-textbox {
+ /* Setting a width and min-width to let the location & search bars maintain
+ a constant width in case they haven't be resized manually. (bug 965772) */
+ width: 1px;
+ min-width: 1px;
+}
+
+#main-window:-moz-lwtheme {
+ background-repeat: no-repeat;
+ background-position: top right;
+}
+
+%ifdef XP_MACOSX
+#main-window[inFullscreen="true"] {
+ padding-top: 0; /* override drawintitlebar="true" */
+}
+%endif
+
+#browser-bottombox[lwthemefooter="true"] {
+ background-repeat: no-repeat;
+ background-position: bottom left;
+}
+
+.menuitem-tooltip {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-tooltip");
+}
+
+.menuitem-iconic-tooltip,
+.menuitem-tooltip[type="checkbox"],
+.menuitem-tooltip[type="radio"] {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip");
+}
+
+/* Hide menu elements intended for keyboard access support */
+#main-menubar[openedwithkey=false] .show-only-for-keyboard {
+ display: none;
+}
+
+/* ::::: location bar ::::: */
+#urlbar {
+ -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar);
+}
+
+/* Always show URLs LTR. */
+.ac-url-text:-moz-locale-dir(rtl),
+.ac-title-text[lookslikeurl]:-moz-locale-dir(rtl) {
+ direction: ltr !important;
+}
+
+/* For non-action items, hide the action text; for action items, hide the URL
+ text. */
+.ac-url[actiontype],
+.ac-action:not([actiontype]) {
+ display: none;
+}
+
+/* For action items in a noactions popup, show the URL text and hide the action
+ text and type icon. */
+#PopupAutoCompleteRichResult[noactions] > richlistbox > richlistitem.overridable-action > .ac-url {
+ display: -moz-box;
+}
+#PopupAutoCompleteRichResult[noactions] > richlistbox > richlistitem.overridable-action > .ac-action {
+ display: none;
+}
+#PopupAutoCompleteRichResult[noactions] > richlistbox > richlistitem.overridable-action > .ac-type-icon {
+ list-style-image: none;
+}
+
+#urlbar:not([actiontype="switchtab"]):not([actiontype="extension"]) > #urlbar-display-box {
+ display: none;
+}
+
+#urlbar:not([actiontype="switchtab"]) > #urlbar-display-box > #switchtab {
+ display: none;
+}
+
+#urlbar:not([actiontype="extension"]) > #urlbar-display-box > #extension {
+ display: none;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .ac-type-icon,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-site-icon,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-tags,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-separator,
+#PopupAutoComplete > richlistbox > richlistitem > .ac-url {
+ display: none;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem-insecure-field");
+ height: auto;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-site-icon {
+ display: initial;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-title > .ac-text-overflow-container > .ac-title-text {
+ text-overflow: initial;
+ white-space: initial;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-title > label {
+ margin-inline-start: 0;
+}
+
+#PopupSearchAutoComplete {
+ -moz-binding: url("chrome://browser/content/search/search.xml#browser-search-autocomplete-result-popup");
+}
+
+/* Overlay a badge on top of the icon of additional open search providers
+ in the search panel. */
+.addengine-item > .button-box > .button-icon {
+ -moz-binding: url("chrome://browser/content/search/search.xml#addengine-icon");
+ display: -moz-stack;
+}
+
+#PopupAutoCompleteRichResult {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
+}
+
+#PopupAutoCompleteRichResult.showSearchSuggestionsNotification {
+ transition: height 100ms;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
+ visibility: collapse;
+ transition: margin-top 100ms;
+}
+
+#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > hbox[anonid="search-suggestions-notification"] {
+ visibility: visible;
+}
+
+#PopupAutoCompleteRichResult > richlistbox {
+ transition: height 100ms;
+}
+
+#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > richlistbox {
+ transition: none;
+}
+
+#DateTimePickerPanel {
+ -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
+}
+
+#urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon,
+#urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
+#urlbar[pageproxystate="valid"] > #urlbar-go-button,
+#urlbar:not([focused="true"]) > #urlbar-go-button {
+ visibility: collapse;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
+ visibility: collapse;
+}
+
+#identity-box {
+ -moz-user-focus: normal;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box {
+ pointer-events: none;
+ -moz-user-focus: ignore;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box {
+ pointer-events: auto;
+}
+
+#identity-icon-labels {
+ max-width: 18em;
+}
+@media (max-width: 700px) {
+ #urlbar-container {
+ min-width: 45ch;
+ }
+ #identity-icon-labels {
+ max-width: 70px;
+ }
+}
+@media (max-width: 600px) {
+ #urlbar-container {
+ min-width: 40ch;
+ }
+ #identity-icon-labels {
+ max-width: 60px;
+ }
+}
+@media (max-width: 500px) {
+ #urlbar-container {
+ min-width: 35ch;
+ }
+ #identity-icon-labels {
+ max-width: 50px;
+ }
+}
+@media (max-width: 400px) {
+ #urlbar-container {
+ min-width: 28ch;
+ }
+ #identity-icon-labels {
+ max-width: 40px;
+ }
+}
+
+#identity-icon-country-label {
+ direction: ltr;
+}
+
+#identity-box.verifiedIdentity > #identity-icon-labels > #identity-icon-label {
+ margin-inline-end: 0.25em !important;
+}
+
+#main-window[customizing] :-moz-any(#urlbar, .searchbar-textbox) > .autocomplete-textbox-container > .textbox-input-box {
+ visibility: hidden;
+}
+
+/* ::::: Unified Back-/Forward Button ::::: */
+#back-button > .toolbarbutton-menu-dropmarker,
+#forward-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+.unified-nav-current {
+ font-weight: bold;
+}
+
+toolbarbutton.bookmark-item {
+ max-width: 13em;
+}
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ #alltabs-popup > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon,
+ .menuitem-with-favicon > .menu-iconic-left > .menu-iconic-icon {
+ image-rendering: -moz-crisp-edges;
+ }
+
+ .bookmark-item > .toolbarbutton-icon,
+ .bookmark-item > .menu-iconic-left > .menu-iconic-icon,
+ #personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
+ image-rendering: -moz-crisp-edges;
+ }
+ /* Synced Tabs sidebar */
+ html|*.tabs-container html|*.item-tabs-list html|*.item-icon-container {
+ image-rendering: -moz-crisp-edges;
+ }
+}
+
+#editBMPanel_tagsSelector {
+ /* override default listbox width from xul.css */
+ width: auto;
+}
+
+menupopup[emptyplacesresult="true"] > .hide-if-empty-places-result {
+ display: none;
+}
+menuitem.spell-suggestion {
+ font-weight: bold;
+}
+
+/* Hide extension toolbars that neglected to set the proper class */
+window[chromehidden~="location"][chromehidden~="toolbar"] toolbar:not(.chromeclass-menubar),
+window[chromehidden~="toolbar"] toolbar:not(#nav-bar):not(#TabsToolbar):not(#print-preview-toolbar):not(.chromeclass-menubar) {
+ display: none;
+}
+
+#navigator-toolbox ,
+#mainPopupSet {
+ min-width: 1px;
+}
+
+/* History Swipe Animation */
+
+#historySwipeAnimationContainer {
+ overflow: hidden;
+}
+
+#historySwipeAnimationPreviousPage,
+#historySwipeAnimationCurrentPage,
+#historySwipeAnimationNextPage {
+ background: none top left no-repeat white;
+}
+
+#historySwipeAnimationPreviousPage {
+ background-image: -moz-element(#historySwipeAnimationPreviousPageSnapshot);
+}
+
+#historySwipeAnimationCurrentPage {
+ background-image: -moz-element(#historySwipeAnimationCurrentPageSnapshot);
+}
+
+#historySwipeAnimationNextPage {
+ background-image: -moz-element(#historySwipeAnimationNextPageSnapshot);
+}
+
+/* Full Screen UI */
+
+#fullscr-toggler {
+ height: 1px;
+ background: black;
+}
+
+html|*.pointerlockfswarning {
+ position: fixed;
+ z-index: 2147483647 !important;
+ visibility: visible;
+ transition: transform 300ms ease-in;
+ /* To center the warning box horizontally,
+ we use left: 50% with translateX(-50%). */
+ top: 0; left: 50%;
+ transform: translate(-50%, -100%);
+ box-sizing: border-box;
+ width: -moz-max-content;
+ max-width: 95%;
+ pointer-events: none;
+}
+html|*.pointerlockfswarning:not([hidden]) {
+ display: flex;
+ will-change: transform;
+}
+html|*.pointerlockfswarning[onscreen] {
+ transform: translate(-50%, 50px);
+}
+html|*.pointerlockfswarning[ontop] {
+ /* Use -10px to hide the border and border-radius on the top */
+ transform: translate(-50%, -10px);
+}
+#main-window[OSXLionFullscreen] html|*.pointerlockfswarning[ontop] {
+ transform: translate(-50%, 80px);
+}
+
+html|*.pointerlockfswarning-domain-text,
+html|*.pointerlockfswarning-generic-text {
+ word-wrap: break-word;
+ /* We must specify a min-width, otherwise word-wrap:break-word doesn't work. Bug 630864. */
+ min-width: 1px
+}
+html|*.pointerlockfswarning-domain-text:not([hidden]) + html|*.pointerlockfswarning-generic-text {
+ display: none;
+}
+
+html|*#fullscreen-exit-button {
+ pointer-events: auto;
+}
+
+/* ::::: Ctrl-Tab Panel ::::: */
+
+.ctrlTab-preview > html|img,
+.ctrlTab-preview > html|canvas {
+ min-width: inherit;
+ max-width: inherit;
+ min-height: inherit;
+ max-height: inherit;
+}
+
+.ctrlTab-favicon-container {
+ -moz-box-align: start;
+%ifdef XP_MACOSX
+ -moz-box-pack: end;
+%else
+ -moz-box-pack: start;
+%endif
+}
+
+.ctrlTab-favicon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ .ctrlTab-favicon {
+ image-rendering: -moz-crisp-edges;
+ }
+}
+
+.ctrlTab-preview {
+ -moz-binding: url("chrome://browser/content/browser-tabPreviews.xml#ctrlTab-preview");
+}
+
+
+/* notification anchors should only be visible when their associated
+ notifications are */
+.notification-anchor-icon {
+ -moz-user-focus: normal;
+}
+
+#blocked-permissions-container > .blocked-permission-icon:not([showing]),
+.notification-anchor-icon:not([showing]) {
+ display: none;
+}
+
+#invalid-form-popup > description {
+ max-width: 280px;
+}
+
+.popup-anchor {
+ /* should occupy space but not be visible */
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ -moz-stack-sizing: ignore;
+}
+
+#addon-progress-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#addon-progress-notification");
+}
+
+#click-to-play-plugins-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
+}
+
+
+.plugin-popupnotification-centeritem {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item");
+}
+
+browser[tabmodalPromptShowing] {
+ -moz-user-focus: none !important;
+}
+
+/* Status panel */
+
+statuspanel {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#statuspanel");
+ position: fixed;
+ margin-top: -3em;
+ max-width: calc(100% - 5px);
+ pointer-events: none;
+}
+
+statuspanel:-moz-locale-dir(ltr)[mirror],
+statuspanel:-moz-locale-dir(rtl):not([mirror]) {
+ left: auto;
+ right: 0;
+}
+
+statuspanel[sizelimit] {
+ max-width: 50%;
+}
+
+statuspanel[type=status] {
+ min-width: 23em;
+}
+
+@media all and (max-width: 800px) {
+ statuspanel[type=status] {
+ min-width: 33%;
+ }
+}
+
+statuspanel[type=overLink] {
+ transition: opacity 120ms ease-out;
+ direction: ltr;
+}
+
+statuspanel[inactive] {
+ transition: none;
+ opacity: 0;
+}
+
+statuspanel[inactive][previoustype=overLink] {
+ transition: opacity 200ms ease-out;
+}
+
+.statuspanel-inner {
+ height: 3em;
+ width: 100%;
+ -moz-box-align: end;
+}
+
+/* gcli */
+
+html|*#gcli-tooltip-frame,
+html|*#gcli-output-frame,
+#gcli-output,
+#gcli-tooltip {
+ overflow-x: hidden;
+}
+
+.gclitoolbar-input-node,
+.gclitoolbar-complete-node {
+ direction: ltr;
+}
+
+#developer-toolbar-toolbox-button[error-count] > .toolbarbutton-icon {
+ display: none;
+}
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ content: attr(error-count);
+ display: -moz-box;
+ -moz-box-pack: center;
+}
+
+/* Responsive Mode */
+
+.browserContainer[responsivemode] {
+ overflow: auto;
+}
+
+.devtools-responsiveui-toolbar:-moz-locale-dir(rtl) {
+ -moz-box-pack: end;
+}
+
+.browserStack[responsivemode] {
+ transition-duration: 200ms;
+ transition-timing-function: linear;
+}
+
+.browserStack[responsivemode] {
+ transition-property: min-width, max-width, min-height, max-height;
+}
+
+.browserStack[responsivemode][notransition] {
+ transition: none;
+}
+
+panelview > .social-panel-frame {
+ width: auto;
+ height: auto;
+}
+
+/* Translation */
+notification[value="translation"] {
+ -moz-binding: url("chrome://browser/content/translation-infobar.xml#translationbar");
+}
+
+/** See bug 872317 for why the following rule is necessary. */
+
+#downloads-button {
+ -moz-binding: url("chrome://browser/content/downloads/download.xml#download-toolbarbutton");
+}
+
+/*** Visibility of downloads indicator controls ***/
+
+/* Bug 924050: If we've loaded the indicator, for now we hide it in the menu panel,
+ and just show the icon. This is a hack to side-step very weird layout bugs that
+ seem to be caused by the indicator stack interacting with the menu panel. */
+#downloads-button[indicator]:not([cui-areatype="menu-panel"]) > .toolbarbutton-badge-stack > image.toolbarbutton-icon,
+#downloads-button[indicator][cui-areatype="menu-panel"] > #downloads-indicator-anchor {
+ display: none;
+}
+
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > .toolbarbutton-badge-stack > image.toolbarbutton-icon {
+ display: -moz-box;
+}
+
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-indicator-anchor {
+ display: none;
+}
+
+#downloads-button:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
+#downloads-button:not(:-moz-any([progress], [counter], [paused]))
+ #downloads-indicator-progress-area
+{
+ visibility: hidden;
+}
+
+/* Combobox dropdown renderer */
+#ContentSelectDropdown > menupopup {
+ /* The menupopup itself should always be rendered LTR to ensure the scrollbar aligns with
+ * the dropdown arrow on the dropdown widget. If a menuitem is RTL, its style will be set accordingly */
+ direction: ltr;
+}
+
+/* Indent options in optgroups */
+.contentSelectDropdown-ingroup .menu-iconic-text {
+ padding-inline-start: 2em;
+}
+
+/* Give this menupopup an arrow panel styling */
+#BMB_bookmarksPopup {
+ -moz-appearance: none;
+ -moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-arrow");
+ background: transparent;
+ border: none;
+ /* The popup inherits -moz-image-region from the button, must reset it */
+ -moz-image-region: auto;
+}
+
+%ifndef MOZ_WIDGET_GTK
+
+#BMB_bookmarksPopup {
+ transform: scale(.4);
+ opacity: 0;
+ transition-property: transform, opacity;
+ transition-duration: 0.15s;
+ transition-timing-function: ease-out;
+}
+
+#BMB_bookmarksPopup[animate="open"] {
+ transform: none;
+ opacity: 1.0;
+}
+
+#BMB_bookmarksPopup[animate="cancel"] {
+ transform: none;
+}
+
+#BMB_bookmarksPopup[arrowposition="after_start"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="after_end"]:-moz-locale-dir(rtl) {
+ transform-origin: 20px top;
+}
+
+#BMB_bookmarksPopup[arrowposition="after_end"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="after_start"]:-moz-locale-dir(rtl) {
+ transform-origin: calc(100% - 20px) top;
+}
+
+#BMB_bookmarksPopup[arrowposition="before_start"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="before_end"]:-moz-locale-dir(rtl) {
+ transform-origin: 20px bottom;
+}
+
+#BMB_bookmarksPopup[arrowposition="before_end"]:-moz-locale-dir(ltr),
+#BMB_bookmarksPopup[arrowposition="before_start"]:-moz-locale-dir(rtl) {
+ transform-origin: calc(100% - 20px) bottom;
+}
+
+%endif
+
+/* Customize mode */
+#navigator-toolbox,
+#browser-bottombox,
+#content-deck {
+ transition-property: margin-left, margin-right;
+ transition-duration: 200ms;
+ transition-timing-function: linear;
+}
+
+#tab-view-deck[fastcustomizeanimation] #navigator-toolbox,
+#tab-view-deck[fastcustomizeanimation] #content-deck {
+ transition-duration: 1ms;
+ transition-timing-function: linear;
+}
+
+#PanelUI-contents > .panel-customization-placeholder > .panel-customization-placeholder-child {
+ list-style-image: none;
+}
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ #PanelUI-remotetabs-tabslist > toolbarbutton > .toolbarbutton-icon,
+ #PanelUI-recentlyClosedWindows > toolbarbutton > .toolbarbutton-icon,
+ #PanelUI-recentlyClosedTabs > toolbarbutton > .toolbarbutton-icon,
+ #PanelUI-historyItems > toolbarbutton > .toolbarbutton-icon {
+ image-rendering: -moz-crisp-edges;
+ }
+}
+
+#customization-panelHolder {
+ overflow-y: hidden;
+}
+
+#customization-panelWrapper,
+#customization-panelWrapper > .panel-arrowcontent {
+ -moz-box-flex: 1;
+}
+
+#customization-panelWrapper > .panel-arrowcontent {
+ padding: 0 !important;
+ overflow: hidden;
+}
+
+#customization-panelHolder > #PanelUI-mainView {
+ display: flex;
+ flex-direction: column;
+ /* Hack alert - by manually setting the preferred height to 0, we convince
+ #PanelUI-mainView to shrink when the window gets smaller in customization
+ mode. Not sure why that is - might have to do with our intermingling of
+ XUL flex, and CSS3 Flexbox. */
+ height: 0;
+}
+
+#customization-panelHolder > #PanelUI-mainView > #PanelUI-contents-scroller {
+ display: flex;
+ flex: auto;
+ flex-direction: column;
+}
+
+#customization-panel-container {
+ overflow-y: auto;
+}
+
+toolbarpaletteitem[dragover] {
+ border-left-color: transparent;
+ border-right-color: transparent;
+}
+
+#customization-palette-container {
+ display: flex;
+ flex-direction: column;
+}
+
+#customization-palette:not([hidden]) {
+ display: block;
+ flex: 1 1 auto;
+ overflow: auto;
+ min-height: 3em;
+}
+
+#customization-footer-spacer,
+#customization-spacer {
+ flex: 1 1 auto;
+}
+
+#customization-footer {
+ display: flex;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+#customization-toolbar-visibility-button > .box-inherit > .button-menu-dropmarker {
+ display: -moz-box;
+}
+
+toolbarpaletteitem[place="palette"] {
+ width: 10em;
+ /* icon (32) + margin (2 * 4) + button padding/border (2 * 4) + label margin (~2) + label
+ * line-height (1.5em): */
+ height: calc(50px + 1.5em);
+ margin-bottom: 5px;
+ overflow: hidden;
+ display: inline-block;
+}
+
+toolbarpaletteitem[place="palette"][hidden] {
+ display: none;
+}
+
+#customization-palette .toolbarpaletteitem-box {
+ -moz-box-pack: center;
+ -moz-box-flex: 1;
+ width: 10em;
+ max-width: 10em;
+}
+
+#main-window[customizing=true] #PanelUI-update-status {
+ display: none;
+}
+
+/* UI Tour */
+
+@keyframes uitour-wobble {
+ from {
+ transform: rotate(0deg) translateX(3px) rotate(0deg);
+ }
+ 50% {
+ transform: rotate(360deg) translateX(3px) rotate(-360deg);
+ }
+ to {
+ transform: rotate(720deg) translateX(0px) rotate(-720deg);
+ }
+}
+
+@keyframes uitour-zoom {
+ from {
+ transform: scale(0.8);
+ }
+ 50% {
+ transform: scale(1.0);
+ }
+ to {
+ transform: scale(0.8);
+ }
+}
+
+@keyframes uitour-color {
+ from {
+ border-color: #5B9CD9;
+ }
+ 50% {
+ border-color: #FF0000;
+ }
+ to {
+ border-color: #5B9CD9;
+ }
+}
+
+#UITourHighlightContainer,
+#UITourHighlight {
+ pointer-events: none;
+}
+
+#UITourHighlight[active] {
+ animation-delay: 2s;
+ animation-fill-mode: forwards;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+}
+
+#UITourHighlight[active="wobble"] {
+ animation-name: uitour-wobble;
+ animation-delay: 0s;
+ animation-duration: 1.5s;
+ animation-iteration-count: 1;
+}
+#UITourHighlight[active="zoom"] {
+ animation-name: uitour-zoom;
+ animation-duration: 1s;
+}
+#UITourHighlight[active="color"] {
+ animation-name: uitour-color;
+ animation-duration: 2s;
+}
+
+/* Combined context-menu items */
+#context-navigation > .menuitem-iconic > .menu-iconic-text,
+#context-navigation > .menuitem-iconic > .menu-accel-container {
+ display: none;
+}
+
+.popup-notification-invalid-input {
+ box-shadow: 0 0 1.5px 1px red;
+}
+
+.popup-notification-invalid-input[focused] {
+ box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
+}
+
+.dragfeedback-tab {
+ -moz-appearance: none;
+ opacity: 0.65;
+ -moz-window-shadow: none;
+}
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
new file mode 100755
index 000000000..b794386f7
--- /dev/null
+++ b/browser/base/content/browser.js
@@ -0,0 +1,8281 @@
+/* -*- 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 Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
+Cu.import("resource://gre/modules/NotificationDB.jsm");
+
+// lazy module getters
+[
+ ["AboutHome", "resource:///modules/AboutHome.jsm"],
+ ["AddonWatcher", "resource://gre/modules/AddonWatcher.jsm"],
+ ["AppConstants", "resource://gre/modules/AppConstants.jsm"],
+ ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
+ ["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
+ ["BrowserUtils", "resource://gre/modules/BrowserUtils.jsm"],
+ ["CastingApps", "resource:///modules/CastingApps.jsm"],
+ ["CharsetMenu", "resource://gre/modules/CharsetMenu.jsm"],
+ ["Color", "resource://gre/modules/Color.jsm"],
+ ["ContentSearch", "resource:///modules/ContentSearch.jsm"],
+ ["Deprecated", "resource://gre/modules/Deprecated.jsm"],
+ ["E10SUtils", "resource:///modules/E10SUtils.jsm"],
+ ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
+ ["GMPInstallManager", "resource://gre/modules/GMPInstallManager.jsm"],
+ ["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
+ ["Log", "resource://gre/modules/Log.jsm"],
+ ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
+ ["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"],
+ ["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
+ ["PluralForm", "resource://gre/modules/PluralForm.jsm"],
+ ["Preferences", "resource://gre/modules/Preferences.jsm"],
+ ["PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"],
+ ["ProcessHangMonitor", "resource:///modules/ProcessHangMonitor.jsm"],
+ ["PromiseUtils", "resource://gre/modules/PromiseUtils.jsm"],
+ ["ReaderMode", "resource://gre/modules/ReaderMode.jsm"],
+ ["ReaderParent", "resource:///modules/ReaderParent.jsm"],
+ ["RecentWindow", "resource:///modules/RecentWindow.jsm"],
+ ["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
+ ["ShortcutUtils", "resource://gre/modules/ShortcutUtils.jsm"],
+ ["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
+ ["SitePermissions", "resource:///modules/SitePermissions.jsm"],
+ ["Social", "resource:///modules/Social.jsm"],
+ ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
+ ["Task", "resource://gre/modules/Task.jsm"],
+ ["TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm"],
+ ["Translation", "resource:///modules/translation/Translation.jsm"],
+ ["UITour", "resource:///modules/UITour.jsm"],
+ ["UpdateUtils", "resource://gre/modules/UpdateUtils.jsm"],
+ ["Weave", "resource://services-sync/main.js"],
+ ["fxAccounts", "resource://gre/modules/FxAccounts.jsm"],
+ ["gDevTools", "resource://devtools/client/framework/gDevTools.jsm"],
+ ["gDevToolsBrowser", "resource://devtools/client/framework/gDevTools.jsm"],
+ ["webrtcUI", "resource:///modules/webrtcUI.jsm", ]
+].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
+
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+ "resource://gre/modules/SafeBrowsing.jsm");
+
+if (AppConstants.MOZ_CRASHREPORTER) {
+ XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
+ "resource:///modules/ContentCrashHandlers.jsm");
+}
+
+// lazy service getters
+[
+ ["Favicons", "@mozilla.org/browser/favicon-service;1", "mozIAsyncFavicons"],
+ ["WindowsUIUtils", "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
+ ["gAboutNewTabService", "@mozilla.org/browser/aboutnewtab-service;1", "nsIAboutNewTabService"],
+ ["gDNSService", "@mozilla.org/network/dns-service;1", "nsIDNSService"],
+].forEach(([name, cc, ci]) => XPCOMUtils.defineLazyServiceGetter(this, name, cc, ci));
+
+if (AppConstants.MOZ_CRASHREPORTER) {
+ XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
+ "@mozilla.org/xre/app-info;1",
+ "nsICrashReporter");
+}
+
+
+XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
+ let tmp = {};
+ Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp);
+ return tmp.BrowserToolboxProcess;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
+ return Services.strings.createBundle('chrome://browser/locale/browser.properties');
+});
+
+XPCOMUtils.defineLazyGetter(this, "gCustomizeMode", function() {
+ let scope = {};
+ Cu.import("resource:///modules/CustomizeMode.jsm", scope);
+ return new scope.CustomizeMode(window);
+});
+
+XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
+ // Only show resizers on Windows 2000 and XP
+ return AppConstants.isPlatformAndVersionAtMost("win", "5.9");
+});
+
+XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
+ return Services.prefs;
+});
+
+XPCOMUtils.defineLazyGetter(this, "InlineSpellCheckerUI", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/InlineSpellChecker.jsm", tmp);
+ return new tmp.InlineSpellChecker();
+});
+
+XPCOMUtils.defineLazyGetter(this, "PageMenuParent", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
+ return new tmp.PageMenuParent();
+});
+
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
+ try {
+ return new tmp.PopupNotifications(gBrowser,
+ document.getElementById("notification-popup"),
+ document.getElementById("notification-popup-box"));
+ } catch (ex) {
+ Cu.reportError(ex);
+ return null;
+ }
+});
+
+XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
+ if (AppConstants.platform != "win")
+ return null;
+
+ const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+ if (WINTASKBAR_CONTRACTID in Cc &&
+ Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
+ let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek;
+ return {
+ onOpenWindow: function () {
+ AeroPeek.onOpenWindow(window);
+ },
+ onCloseWindow: function () {
+ AeroPeek.onCloseWindow(window);
+ }
+ };
+ }
+ return null;
+});
+
+const nsIWebNavigation = Ci.nsIWebNavigation;
+
+var gLastBrowserCharset = null;
+var gLastValidURLStr = "";
+var gInPrintPreviewMode = false;
+var gContextMenu = null; // nsContextMenu instance
+var gMultiProcessBrowser =
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .useRemoteTabs;
+var gAppInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo)
+ .QueryInterface(Ci.nsIXULRuntime);
+
+if (AppConstants.platform != "macosx") {
+ var gEditUIVisible = true;
+}
+
+/* globals gBrowser, gNavToolbox, gURLBar, gNavigatorBundle*/
+[
+ ["gBrowser", "content"],
+ ["gNavToolbox", "navigator-toolbox"],
+ ["gURLBar", "urlbar"],
+ ["gNavigatorBundle", "bundle_browser"]
+].forEach(function (elementGlobal) {
+ var [name, id] = elementGlobal;
+ window.__defineGetter__(name, function () {
+ var element = document.getElementById(id);
+ if (!element)
+ return null;
+ delete window[name];
+ return window[name] = element;
+ });
+ window.__defineSetter__(name, function (val) {
+ delete window[name];
+ return window[name] = val;
+ });
+});
+
+// Smart getter for the findbar. If you don't wish to force the creation of
+// the findbar, check gFindBarInitialized first.
+
+this.__defineGetter__("gFindBar", function() {
+ return window.gBrowser.getFindBar();
+});
+
+this.__defineGetter__("gFindBarInitialized", function() {
+ return window.gBrowser.isFindBarInitialized();
+});
+
+this.__defineGetter__("AddonManager", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/AddonManager.jsm", tmp);
+ return this.AddonManager = tmp.AddonManager;
+});
+this.__defineSetter__("AddonManager", function (val) {
+ delete this.AddonManager;
+ return this.AddonManager = val;
+});
+
+
+var gInitialPages = [
+ "about:blank",
+ "about:newtab",
+ "about:home",
+ "about:privatebrowsing",
+ "about:welcomeback",
+ "about:sessionrestore"
+];
+
+function* browserWindows() {
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements())
+ yield windows.getNext();
+}
+
+/**
+* We can avoid adding multiple load event listeners and save some time by adding
+* one listener that calls all real handlers.
+*/
+function pageShowEventHandlers(persisted) {
+ XULBrowserWindow.asyncUpdateUI();
+}
+
+function UpdateBackForwardCommands(aWebNavigation) {
+ var backBroadcaster = document.getElementById("Browser:Back");
+ var forwardBroadcaster = document.getElementById("Browser:Forward");
+
+ // Avoid setting attributes on broadcasters if the value hasn't changed!
+ // Remember, guys, setting attributes on elements is expensive! They
+ // get inherited into anonymous content, broadcast to other widgets, etc.!
+ // Don't do it if the value hasn't changed! - dwh
+
+ var backDisabled = backBroadcaster.hasAttribute("disabled");
+ var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
+ if (backDisabled == aWebNavigation.canGoBack) {
+ if (backDisabled)
+ backBroadcaster.removeAttribute("disabled");
+ else
+ backBroadcaster.setAttribute("disabled", true);
+ }
+
+ if (forwardDisabled == aWebNavigation.canGoForward) {
+ if (forwardDisabled)
+ forwardBroadcaster.removeAttribute("disabled");
+ else
+ forwardBroadcaster.setAttribute("disabled", true);
+ }
+}
+
+/**
+ * Click-and-Hold implementation for the Back and Forward buttons
+ * XXXmano: should this live in toolbarbutton.xml?
+ */
+function SetClickAndHoldHandlers() {
+ // Bug 414797: Clone the back/forward buttons' context menu into both buttons.
+ let popup = document.getElementById("backForwardMenu").cloneNode(true);
+ popup.removeAttribute("id");
+ // Prevent the back/forward buttons' context attributes from being inherited.
+ popup.setAttribute("context", "");
+
+ let backButton = document.getElementById("back-button");
+ backButton.setAttribute("type", "menu");
+ backButton.appendChild(popup);
+ gClickAndHoldListenersOnElement.add(backButton);
+
+ let forwardButton = document.getElementById("forward-button");
+ popup = popup.cloneNode(true);
+ forwardButton.setAttribute("type", "menu");
+ forwardButton.appendChild(popup);
+ gClickAndHoldListenersOnElement.add(forwardButton);
+}
+
+
+const gClickAndHoldListenersOnElement = {
+ _timers: new Map(),
+
+ _mousedownHandler(aEvent) {
+ if (aEvent.button != 0 ||
+ aEvent.currentTarget.open ||
+ aEvent.currentTarget.disabled)
+ return;
+
+ // Prevent the menupopup from opening immediately
+ aEvent.currentTarget.firstChild.hidden = true;
+
+ aEvent.currentTarget.addEventListener("mouseout", this, false);
+ aEvent.currentTarget.addEventListener("mouseup", this, false);
+ this._timers.set(aEvent.currentTarget, setTimeout((b) => this._openMenu(b), 500, aEvent.currentTarget));
+ },
+
+ _clickHandler(aEvent) {
+ if (aEvent.button == 0 &&
+ aEvent.target == aEvent.currentTarget &&
+ !aEvent.currentTarget.open &&
+ !aEvent.currentTarget.disabled) {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+
+ // This is here to cancel the XUL default event
+ // dom.click() triggers a command even if there is a click handler
+ // however this can now be prevented with preventDefault().
+ aEvent.preventDefault();
+ }
+ },
+
+ _openMenu(aButton) {
+ this._cancelHold(aButton);
+ aButton.firstChild.hidden = false;
+ aButton.open = true;
+ },
+
+ _mouseoutHandler(aEvent) {
+ let buttonRect = aEvent.currentTarget.getBoundingClientRect();
+ if (aEvent.clientX >= buttonRect.left &&
+ aEvent.clientX <= buttonRect.right &&
+ aEvent.clientY >= buttonRect.bottom)
+ this._openMenu(aEvent.currentTarget);
+ else
+ this._cancelHold(aEvent.currentTarget);
+ },
+
+ _mouseupHandler(aEvent) {
+ this._cancelHold(aEvent.currentTarget);
+ },
+
+ _cancelHold(aButton) {
+ clearTimeout(this._timers.get(aButton));
+ aButton.removeEventListener("mouseout", this, false);
+ aButton.removeEventListener("mouseup", this, false);
+ },
+
+ handleEvent(e) {
+ switch (e.type) {
+ case "mouseout":
+ this._mouseoutHandler(e);
+ break;
+ case "mousedown":
+ this._mousedownHandler(e);
+ break;
+ case "click":
+ this._clickHandler(e);
+ break;
+ case "mouseup":
+ this._mouseupHandler(e);
+ break;
+ }
+ },
+
+ remove(aButton) {
+ aButton.removeEventListener("mousedown", this, true);
+ aButton.removeEventListener("click", this, true);
+ },
+
+ add(aElm) {
+ this._timers.delete(aElm);
+
+ aElm.addEventListener("mousedown", this, true);
+ aElm.addEventListener("click", this, true);
+ }
+};
+
+const gSessionHistoryObserver = {
+ observe: function(subject, topic, data)
+ {
+ if (topic != "browser:purge-session-history")
+ return;
+
+ var backCommand = document.getElementById("Browser:Back");
+ backCommand.setAttribute("disabled", "true");
+ var fwdCommand = document.getElementById("Browser:Forward");
+ fwdCommand.setAttribute("disabled", "true");
+
+ // Hide session restore button on about:home
+ window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton");
+
+ // Clear undo history of the URL bar
+ gURLBar.editor.transactionManager.clear()
+ }
+};
+
+/**
+ * Given a starting docshell and a URI to look up, find the docshell the URI
+ * is loaded in.
+ * @param aDocument
+ * A document to find instead of using just a URI - this is more specific.
+ * @param aDocShell
+ * The doc shell to start at
+ * @param aSoughtURI
+ * The URI that we're looking for
+ * @returns The doc shell that the sought URI is loaded in. Can be in
+ * subframes.
+ */
+function findChildShell(aDocument, aDocShell, aSoughtURI) {
+ aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument);
+ if ((aDocument && doc == aDocument) ||
+ (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec))
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
+ for (var i = 0; i < node.childCount; ++i) {
+ var docShell = node.getChildAt(i);
+ docShell = findChildShell(aDocument, docShell, aSoughtURI);
+ if (docShell)
+ return docShell;
+ }
+ return null;
+}
+
+var gPopupBlockerObserver = {
+ _reportButton: null,
+
+ onReportButtonMousedown: function (aEvent)
+ {
+ // If this method is called on the same event tick as the popup gets
+ // hidden, do nothing to avoid re-opening the popup.
+ if (aEvent.button != 0 || aEvent.target != this._reportButton || this.isPopupHidingTick)
+ return;
+
+ document.getElementById("blockedPopupOptions")
+ .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent);
+ },
+
+ handleEvent: function (aEvent)
+ {
+ if (aEvent.originalTarget != gBrowser.selectedBrowser)
+ return;
+
+ if (!this._reportButton)
+ this._reportButton = document.getElementById("page-report-button");
+
+ if (!gBrowser.selectedBrowser.blockedPopups ||
+ !gBrowser.selectedBrowser.blockedPopups.length) {
+ // Hide the icon in the location bar (if the location bar exists)
+ this._reportButton.hidden = true;
+
+ // Hide the notification box (if it's visible).
+ let notificationBox = gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("popup-blocked");
+ if (notification) {
+ notificationBox.removeNotification(notification, false);
+ }
+ return;
+ }
+
+ this._reportButton.hidden = false;
+
+ // Only show the notification again if we've not already shown it. Since
+ // notifications are per-browser, we don't need to worry about re-adding
+ // it.
+ if (!gBrowser.selectedBrowser.blockedPopups.reported) {
+ if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) {
+ var brandBundle = document.getElementById("bundle_brand");
+ var brandShortName = brandBundle.getString("brandShortName");
+ var popupCount = gBrowser.selectedBrowser.blockedPopups.length;
+
+ var stringKey = AppConstants.platform == "win"
+ ? "popupWarningButton"
+ : "popupWarningButtonUnix";
+
+ var popupButtonText = gNavigatorBundle.getString(stringKey);
+ var popupButtonAccesskey = gNavigatorBundle.getString(stringKey + ".accesskey");
+
+ var messageBase = gNavigatorBundle.getString("popupWarning.message");
+ var message = PluralForm.get(popupCount, messageBase)
+ .replace("#1", brandShortName)
+ .replace("#2", popupCount);
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("popup-blocked");
+ if (notification) {
+ notification.label = message;
+ }
+ else {
+ var buttons = [{
+ label: popupButtonText,
+ accessKey: popupButtonAccesskey,
+ popup: "blockedPopupOptions",
+ callback: null
+ }];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, "popup-blocked",
+ "chrome://browser/skin/Info.png",
+ priority, buttons);
+ }
+ }
+
+ // Record the fact that we've reported this blocked popup, so we don't
+ // show it again.
+ gBrowser.selectedBrowser.blockedPopups.reported = true;
+ }
+ },
+
+ toggleAllowPopupsForSite: function (aEvent)
+ {
+ var pm = Services.perms;
+ var shouldBlock = aEvent.target.getAttribute("block") == "true";
+ var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
+ pm.add(gBrowser.currentURI, "popup", perm);
+
+ if (!shouldBlock)
+ this.showAllBlockedPopups(gBrowser.selectedBrowser);
+
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ },
+
+ fillPopupList: function (aEvent)
+ {
+ // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
+ // we should really walk the blockedPopups and create a list of "allow for <host>"
+ // menuitems for the common subset of hosts present in the report, this will
+ // make us frame-safe.
+ //
+ // XXXjst - Note that when this is fixed to work with multi-framed sites,
+ // also back out the fix for bug 343772 where
+ // nsGlobalWindow::CheckOpenAllow() was changed to also
+ // check if the top window's location is whitelisted.
+ let browser = gBrowser.selectedBrowser;
+ var uri = browser.currentURI;
+ var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite");
+ try {
+ blockedPopupAllowSite.removeAttribute("hidden");
+
+ var pm = Services.perms;
+ if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) {
+ // Offer an item to block popups for this site, if a whitelist entry exists
+ // already for it.
+ let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", blockString);
+ blockedPopupAllowSite.setAttribute("block", "true");
+ }
+ else {
+ // Offer an item to allow popups for this site
+ let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", allowString);
+ blockedPopupAllowSite.removeAttribute("block");
+ }
+ }
+ catch (e) {
+ blockedPopupAllowSite.setAttribute("hidden", "true");
+ }
+
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ blockedPopupAllowSite.setAttribute("disabled", "true");
+ else
+ blockedPopupAllowSite.removeAttribute("disabled");
+
+ let blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage");
+ let showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
+ if (aEvent.target.anchorNode.id == "page-report-button") {
+ aEvent.target.anchorNode.setAttribute("open", "true");
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar"));
+ } else {
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage"));
+ }
+
+ let blockedPopupsSeparator =
+ document.getElementById("blockedPopupsSeparator");
+ blockedPopupsSeparator.setAttribute("hidden", true);
+
+ gBrowser.selectedBrowser.retrieveListOfBlockedPopups().then(blockedPopups => {
+ let foundUsablePopupURI = false;
+ if (blockedPopups) {
+ for (let i = 0; i < blockedPopups.length; i++) {
+ let blockedPopup = blockedPopups[i];
+
+ // popupWindowURI will be null if the file picker popup is blocked.
+ // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
+ if (!blockedPopup.popupWindowURIspec)
+ continue;
+
+ var popupURIspec = blockedPopup.popupWindowURIspec;
+
+ // Sometimes the popup URI that we get back from the blockedPopup
+ // isn't useful (for instance, netscape.com's popup URI ends up
+ // being "http://www.netscape.com", which isn't really the URI of
+ // the popup they're trying to show). This isn't going to be
+ // useful to the user, so we won't create a menu item for it.
+ if (popupURIspec == "" || popupURIspec == "about:blank" ||
+ popupURIspec == "<self>" ||
+ popupURIspec == uri.spec)
+ continue;
+
+ // Because of the short-circuit above, we may end up in a situation
+ // in which we don't have any usable popup addresses to show in
+ // the menu, and therefore we shouldn't show the separator. However,
+ // since we got past the short-circuit, we must've found at least
+ // one usable popup URI and thus we'll turn on the separator later.
+ foundUsablePopupURI = true;
+
+ var menuitem = document.createElement("menuitem");
+ var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix",
+ [popupURIspec]);
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
+ menuitem.setAttribute("popupReportIndex", i);
+ menuitem.popupReportBrowser = browser;
+ aEvent.target.appendChild(menuitem);
+ }
+ }
+
+ // Show the separator if we added any
+ // showable popup addresses to the menu.
+ if (foundUsablePopupURI)
+ blockedPopupsSeparator.removeAttribute("hidden");
+ }, null);
+ },
+
+ onPopupHiding: function (aEvent) {
+ if (aEvent.target.anchorNode.id == "page-report-button")
+ aEvent.target.anchorNode.removeAttribute("open");
+
+ this.isPopupHidingTick = true;
+ setTimeout(() => this.isPopupHidingTick = false, 0);
+
+ let item = aEvent.target.lastChild;
+ while (item && item.getAttribute("observes") != "blockedPopupsSeparator") {
+ let next = item.previousSibling;
+ item.parentNode.removeChild(item);
+ item = next;
+ }
+ },
+
+ showBlockedPopup: function (aEvent)
+ {
+ var target = aEvent.target;
+ var popupReportIndex = target.getAttribute("popupReportIndex");
+ let browser = target.popupReportBrowser;
+ browser.unblockPopup(popupReportIndex);
+ },
+
+ showAllBlockedPopups: function (aBrowser)
+ {
+ aBrowser.retrieveListOfBlockedPopups().then(popups => {
+ for (let i = 0; i < popups.length; i++) {
+ if (popups[i].popupWindowURIspec)
+ aBrowser.unblockPopup(i);
+ }
+ }, null);
+ },
+
+ editPopupSettings: function ()
+ {
+ var host = "";
+ try {
+ host = gBrowser.currentURI.host;
+ }
+ catch (e) { }
+
+ var bundlePreferences = document.getElementById("bundle_preferences");
+ var params = { blockVisible : false,
+ sessionVisible : false,
+ allowVisible : true,
+ prefilledHost : host,
+ permissionType : "popup",
+ windowTitle : bundlePreferences.getString("popuppermissionstitle"),
+ introText : bundlePreferences.getString("popuppermissionstext") };
+ var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
+ if (existingWindow) {
+ existingWindow.initWithParams(params);
+ existingWindow.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/permissions.xul",
+ "_blank", "resizable,dialog=no,centerscreen", params);
+ },
+
+ dontShowMessage: function ()
+ {
+ var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage);
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ }
+};
+
+function gKeywordURIFixup({ target: browser, data: fixupInfo }) {
+ let deserializeURI = (spec) => spec ? makeURI(spec) : null;
+
+ // We get called irrespective of whether we did a keyword search, or
+ // whether the original input would be vaguely interpretable as a URL,
+ // so figure that out first.
+ let alternativeURI = deserializeURI(fixupInfo.fixedURI);
+ if (!fixupInfo.keywordProviderName || !alternativeURI || !alternativeURI.host) {
+ return;
+ }
+
+ // At this point we're still only just about to load this URI.
+ // When the async DNS lookup comes back, we may be in any of these states:
+ // 1) still on the previous URI, waiting for the preferredURI (keyword
+ // search) to respond;
+ // 2) at the keyword search URI (preferredURI)
+ // 3) at some other page because the user stopped navigation.
+ // We keep track of the currentURI to detect case (1) in the DNS lookup
+ // callback.
+ let previousURI = browser.currentURI;
+ let preferredURI = deserializeURI(fixupInfo.preferredURI);
+
+ // now swap for a weak ref so we don't hang on to browser needlessly
+ // even if the DNS query takes forever
+ let weakBrowser = Cu.getWeakReference(browser);
+ browser = null;
+
+ // Additionally, we need the host of the parsed url
+ let hostName = alternativeURI.host;
+ // and the ascii-only host for the pref:
+ let asciiHost = alternativeURI.asciiHost;
+ // Normalize out a single trailing dot - NB: not using endsWith/lastIndexOf
+ // because we need to be sure this last dot is the *only* dot, too.
+ // More generally, this is used for the pref and should stay in sync with
+ // the code in nsDefaultURIFixup::KeywordURIFixup .
+ if (asciiHost.indexOf('.') == asciiHost.length - 1) {
+ asciiHost = asciiHost.slice(0, -1);
+ }
+
+ let isIPv4Address = host => {
+ let parts = host.split(".");
+ if (parts.length != 4) {
+ return false;
+ }
+ return parts.every(part => {
+ let n = parseInt(part, 10);
+ return n >= 0 && n <= 255;
+ });
+ };
+ // Avoid showing fixup information if we're suggesting an IP. Note that
+ // decimal representations of IPs are normalized to a 'regular'
+ // dot-separated IP address by network code, but that only happens for
+ // numbers that don't overflow. Longer numbers do not get normalized,
+ // but still work to access IP addresses. So for instance,
+ // 1097347366913 (ff7f000001) gets resolved by using the final bytes,
+ // making it the same as 7f000001, which is 127.0.0.1 aka localhost.
+ // While 2130706433 would get normalized by network, 1097347366913
+ // does not, and we have to deal with both cases here:
+ if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost))
+ return;
+
+ let onLookupComplete = (request, record, status) => {
+ let browser = weakBrowser.get();
+ if (!Components.isSuccessCode(status) || !browser)
+ return;
+
+ let currentURI = browser.currentURI;
+ // If we're in case (3) (see above), don't show an info bar.
+ if (!currentURI.equals(previousURI) &&
+ !currentURI.equals(preferredURI)) {
+ return;
+ }
+
+ // show infobar offering to visit the host
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ if (notificationBox.getNotificationWithValue("keyword-uri-fixup"))
+ return;
+
+ let message = gNavigatorBundle.getFormattedString(
+ "keywordURIFixup.message", [hostName]);
+ let yesMessage = gNavigatorBundle.getFormattedString(
+ "keywordURIFixup.goTo", [hostName])
+
+ let buttons = [
+ {
+ label: yesMessage,
+ accessKey: gNavigatorBundle.getString("keywordURIFixup.goTo.accesskey"),
+ callback: function() {
+ // Do not set this preference while in private browsing.
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ let pref = "browser.fixup.domainwhitelist." + asciiHost;
+ Services.prefs.setBoolPref(pref, true);
+ }
+ openUILinkIn(alternativeURI.spec, "current");
+ }
+ },
+ {
+ label: gNavigatorBundle.getString("keywordURIFixup.dismiss"),
+ accessKey: gNavigatorBundle.getString("keywordURIFixup.dismiss.accesskey"),
+ callback: function() {
+ let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
+ notificationBox.removeNotification(notification, true);
+ }
+ }
+ ];
+ let notification =
+ notificationBox.appendNotification(message, "keyword-uri-fixup", null,
+ notificationBox.PRIORITY_INFO_HIGH,
+ buttons);
+ notification.persistence = 1;
+ };
+
+ try {
+ gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
+ } catch (ex) {
+ // Do nothing if the URL is invalid (we don't want to show a notification in that case).
+ if (ex.result != Cr.NS_ERROR_UNKNOWN_HOST) {
+ // ... otherwise, report:
+ Cu.reportError(ex);
+ }
+ }
+}
+
+// A shared function used by both remote and non-remote browser XBL bindings to
+// load a URI or redirect it to the correct process.
+function _loadURIWithFlags(browser, uri, params) {
+ if (!uri) {
+ uri = "about:blank";
+ }
+ let flags = params.flags || 0;
+ let referrer = params.referrerURI;
+ let referrerPolicy = ('referrerPolicy' in params ? params.referrerPolicy :
+ Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ let postData = params.postData;
+
+ let wasRemote = browser.isRemoteBrowser;
+
+ let process = browser.isRemoteBrowser ? Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
+ : Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+ let mustChangeProcess = gMultiProcessBrowser &&
+ !E10SUtils.canLoadURIInProcess(uri, process);
+ if ((!wasRemote && !mustChangeProcess) ||
+ (wasRemote && mustChangeProcess)) {
+ browser.inLoadURI = true;
+ }
+ try {
+ if (!mustChangeProcess) {
+ if (params.userContextId) {
+ browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId });
+ }
+
+ browser.webNavigation.loadURIWithOptions(uri, flags,
+ referrer, referrerPolicy,
+ postData, null, null);
+ } else {
+ // Check if the current browser is allowed to unload.
+ let {permitUnload, timedOut} = browser.permitUnload();
+ if (!timedOut && !permitUnload) {
+ return;
+ }
+
+ if (postData) {
+ postData = NetUtil.readInputStreamToString(postData, postData.available());
+ }
+
+ let loadParams = {
+ uri: uri,
+ flags: flags,
+ referrer: referrer ? referrer.spec : null,
+ referrerPolicy: referrerPolicy,
+ postData: postData
+ }
+
+ if (params.userContextId) {
+ loadParams.userContextId = params.userContextId;
+ }
+
+ LoadInOtherProcess(browser, loadParams);
+ }
+ } catch (e) {
+ // If anything goes wrong when switching remoteness, just switch remoteness
+ // manually and load the URI.
+ // We might lose history that way but at least the browser loaded a page.
+ // This might be necessary if SessionStore wasn't initialized yet i.e.
+ // when the homepage is a non-remote page.
+ if (mustChangeProcess) {
+ Cu.reportError(e);
+ gBrowser.updateBrowserRemotenessByURL(browser, uri);
+
+ if (params.userContextId) {
+ browser.webNavigation.setOriginAttributesBeforeLoading({ userContextId: params.userContextId });
+ }
+
+ browser.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
+ postData, null, null);
+ } else {
+ throw e;
+ }
+ } finally {
+ if ((!wasRemote && !mustChangeProcess) ||
+ (wasRemote && mustChangeProcess)) {
+ browser.inLoadURI = false;
+ }
+ }
+}
+
+// Starts a new load in the browser first switching the browser to the correct
+// process
+function LoadInOtherProcess(browser, loadOptions, historyIndex = -1) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ SessionStore.navigateAndRestore(tab, loadOptions, historyIndex);
+}
+
+// Called when a docshell has attempted to load a page in an incorrect process.
+// This function is responsible for loading the page in the correct process.
+function RedirectLoad({ target: browser, data }) {
+ // We should only start the redirection if the browser window has finished
+ // starting up. Otherwise, we should wait until the startup is done.
+ if (gBrowserInit.delayedStartupFinished) {
+ LoadInOtherProcess(browser, data.loadOptions, data.historyIndex);
+ } else {
+ let delayedStartupFinished = (subject, topic) => {
+ if (topic == "browser-delayed-startup-finished" &&
+ subject == window) {
+ Services.obs.removeObserver(delayedStartupFinished, topic);
+ LoadInOtherProcess(browser, data.loadOptions, data.historyIndex);
+ }
+ };
+ Services.obs.addObserver(delayedStartupFinished,
+ "browser-delayed-startup-finished",
+ false);
+ }
+}
+
+addEventListener("DOMContentLoaded", function onDCL() {
+ removeEventListener("DOMContentLoaded", onDCL);
+
+ // There are some windows, like macBrowserOverlay.xul, that
+ // load browser.js, but never load tabbrowser.xml. We can ignore
+ // those cases.
+ if (!gBrowser || !gBrowser.updateBrowserRemoteness) {
+ return;
+ }
+
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = window.XULBrowserWindow;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
+ new nsBrowserAccess();
+
+ let initBrowser =
+ document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
+
+ // The window's first argument is a tab if and only if we are swapping tabs.
+ // We must set the browser's usercontextid before updateBrowserRemoteness(),
+ // so that the newly created remote tab child has the correct usercontextid.
+ if (window.arguments) {
+ let tabToOpen = window.arguments[0];
+ if (tabToOpen instanceof XULElement && tabToOpen.hasAttribute("usercontextid")) {
+ initBrowser.setAttribute("usercontextid", tabToOpen.getAttribute("usercontextid"));
+ }
+ }
+
+ gBrowser.updateBrowserRemoteness(initBrowser, gMultiProcessBrowser);
+});
+
+var gBrowserInit = {
+ delayedStartupFinished: false,
+
+ onLoad: function() {
+ gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
+
+ Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed", false);
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ // These routines add message listeners. They must run before
+ // loading the frame script to ensure that we don't miss any
+ // message sent between when the frame script is loaded and when
+ // the listener is registered.
+ DOMLinkHandler.init();
+ gPageStyleMenu.init();
+ LanguageDetectionListener.init();
+ BrowserOnClick.init();
+ FeedHandler.init();
+ DevEdition.init();
+ AboutPrivateBrowsingListener.init();
+ TrackingProtection.init();
+ RefreshBlocker.init();
+ CaptivePortalWatcher.init();
+
+ let mm = window.getGroupMessageManager("browsers");
+ mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
+ mm.loadFrameScript("chrome://browser/content/content.js", true);
+ mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);
+ mm.loadFrameScript("chrome://global/content/manifestMessages.js", true);
+
+ // initialize observers and listeners
+ // and give C++ access to gBrowser
+ XULBrowserWindow.init();
+
+ window.messageManager.addMessageListener("Browser:LoadURI", RedirectLoad);
+
+ if (!gMultiProcessBrowser) {
+ // There is a Content:Click message manually sent from content.
+ Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
+ }
+
+ // hook up UI through progress listener
+ gBrowser.addProgressListener(window.XULBrowserWindow);
+ gBrowser.addTabsProgressListener(window.TabsProgressListener);
+
+ // setup simple gestures support
+ gGestureSupport.init(true);
+
+ // setup history swipe animation
+ gHistorySwipeAnimation.init();
+
+ SidebarUI.init();
+
+ // Certain kinds of automigration rely on this notification to complete
+ // their tasks BEFORE the browser window is shown. SessionStore uses it to
+ // restore tabs into windows AFTER important parts like gMultiProcessBrowser
+ // have been initialized.
+ Services.obs.notifyObservers(window, "browser-window-before-show", "");
+
+ // Set a sane starting width/height for all resolutions on new profiles.
+ if (!document.documentElement.hasAttribute("width")) {
+ const TARGET_WIDTH = 1280;
+ const TARGET_HEIGHT = 1040;
+ let width = Math.min(screen.availWidth * .9, TARGET_WIDTH);
+ let height = Math.min(screen.availHeight * .9, TARGET_HEIGHT);
+
+ document.documentElement.setAttribute("width", width);
+ document.documentElement.setAttribute("height", height);
+
+ if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
+ document.documentElement.setAttribute("sizemode", "maximized");
+ }
+ }
+
+ if (!window.toolbar.visible) {
+ // adjust browser UI for popups
+ gURLBar.setAttribute("readonly", "true");
+ gURLBar.setAttribute("enablehistory", "false");
+ }
+
+ // Misc. inits.
+ TabletModeUpdater.init();
+ CombinedStopReload.init();
+ gPrivateBrowsingUI.init();
+
+ if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
+ window.matchMedia("(-moz-windows-default-theme)").matches) {
+ let windowFrameColor = new Color(...Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {})
+ .Windows8WindowFrameColor.get());
+ // Default to black for foreground text.
+ if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) {
+ document.documentElement.setAttribute("darkwindowframe", "true");
+ }
+ }
+
+ ToolbarIconColor.init();
+
+ // Wait until chrome is painted before executing code not critical to making the window visible
+ this._boundDelayedStartup = this._delayedStartup.bind(this);
+ window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
+
+ this._loadHandled = true;
+ },
+
+ _cancelDelayedStartup: function () {
+ window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
+ this._boundDelayedStartup = null;
+ },
+
+ _delayedStartup: function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp);
+ let TelemetryTimestamps = tmp.TelemetryTimestamps;
+ TelemetryTimestamps.add("delayedStartupStarted");
+
+ this._cancelDelayedStartup();
+
+ // We need to set the OfflineApps message listeners up before we
+ // load homepages, which might need them.
+ OfflineApps.init();
+
+ // This pageshow listener needs to be registered before we may call
+ // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
+ let mm = window.messageManager;
+ mm.addMessageListener("PageVisibility:Show", function(message) {
+ if (message.target == gBrowser.selectedBrowser) {
+ setTimeout(pageShowEventHandlers, 0, message.data.persisted);
+ }
+ });
+
+ gBrowser.addEventListener("AboutTabCrashedLoad", function(event) {
+ let ownerDoc = event.originalTarget;
+
+ if (!ownerDoc.documentURI.startsWith("about:tabcrashed")) {
+ return;
+ }
+
+ let browser = gBrowser.getBrowserForDocument(event.target);
+ // Reset the zoom for the tabcrashed page.
+ ZoomManager.setZoomForBrowser(browser, 1);
+ }, false, true);
+
+ gBrowser.addEventListener("InsecureLoginFormsStateChange", function() {
+ gIdentityHandler.refreshForInsecureLoginForms();
+ });
+
+ let uriToLoad = this._getUriToLoad();
+ if (uriToLoad && uriToLoad != "about:blank") {
+ if (uriToLoad instanceof Ci.nsIArray) {
+ let count = uriToLoad.length;
+ let specs = [];
+ for (let i = 0; i < count; i++) {
+ let urisstring = uriToLoad.queryElementAt(i, Ci.nsISupportsString);
+ specs.push(urisstring.data);
+ }
+
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(specs, false, true);
+ } catch (e) {}
+ }
+ else if (uriToLoad instanceof XULElement) {
+ // swap the given tab with the default about:blank tab and then close
+ // the original tab in the other window.
+ let tabToOpen = uriToLoad;
+
+ // If this tab was passed as a window argument, clear the
+ // reference to it from the arguments array.
+ if (window.arguments[0] == tabToOpen) {
+ window.arguments[0] = null;
+ }
+
+ // Stop the about:blank load
+ gBrowser.stop();
+ // make sure it has a docshell
+ gBrowser.docShell;
+
+ // We must set usercontextid before updateBrowserRemoteness()
+ // so that the newly created remote tab child has correct usercontextid
+ if (tabToOpen.hasAttribute("usercontextid")) {
+ let usercontextid = tabToOpen.getAttribute("usercontextid");
+ gBrowser.selectedBrowser.setAttribute("usercontextid", usercontextid);
+ }
+
+ // If the browser that we're swapping in was remote, then we'd better
+ // be able to support remote browsers, and then make our selectedTab
+ // remote.
+ try {
+ if (tabToOpen.linkedBrowser.isRemoteBrowser) {
+ if (!gMultiProcessBrowser) {
+ throw new Error("Cannot drag a remote browser into a window " +
+ "without the remote tabs load context.");
+ }
+ gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, true);
+ } else if (gBrowser.selectedBrowser.isRemoteBrowser) {
+ // If the browser is remote, then it's implied that
+ // gMultiProcessBrowser is true. We need to flip the remoteness
+ // of this tab to false in order for the tab drag to work.
+ gBrowser.updateBrowserRemoteness(gBrowser.selectedBrowser, false);
+ }
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, tabToOpen);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ // window.arguments[2]: referrer (nsIURI | string)
+ // [3]: postData (nsIInputStream)
+ // [4]: allowThirdPartyFixup (bool)
+ // [5]: referrerPolicy (int)
+ // [6]: userContextId (int)
+ // [7]: originPrincipal (nsIPrincipal)
+ else if (window.arguments.length >= 3) {
+ let referrerURI = window.arguments[2];
+ if (typeof(referrerURI) == "string") {
+ try {
+ referrerURI = makeURI(referrerURI);
+ } catch (e) {
+ referrerURI = null;
+ }
+ }
+ let referrerPolicy = (window.arguments[5] != undefined ?
+ window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ let userContextId = (window.arguments[6] != undefined ?
+ window.arguments[6] : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID);
+ loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
+ window.arguments[4] || false, referrerPolicy, userContextId,
+ // pass the origin principal (if any) and force its use to create
+ // an initial about:blank viewer if present:
+ window.arguments[7], !!window.arguments[7]);
+ window.focus();
+ }
+ // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
+ // Such callers expect that window.arguments[0] is handled as a single URI.
+ else {
+ loadOneOrMoreURIs(uriToLoad);
+ }
+ }
+
+ // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+ setTimeout(function() { SafeBrowsing.init(); }, 2000);
+
+ Services.obs.addObserver(gIdentityHandler, "perm-changed", false);
+ Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-origin-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+ window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
+
+ BrowserOffline.init();
+ IndexedDBPromptHelper.init();
+
+ if (AppConstants.E10S_TESTING_ONLY)
+ gRemoteTabsUI.init();
+
+ // Initialize the full zoom setting.
+ // We do this before the session restore service gets initialized so we can
+ // apply full zoom settings to tabs restored by the session restore service.
+ FullZoom.init();
+ PanelUI.init();
+ LightweightThemeListener.init();
+
+ Services.telemetry.getHistogramById("E10S_WINDOW").add(gMultiProcessBrowser);
+
+ SidebarUI.startDelayedLoad();
+
+ UpdateUrlbarSearchSplitterState();
+
+ if (!(isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") ||
+ !focusAndSelectUrlBar()) {
+ if (gBrowser.selectedBrowser.isRemoteBrowser) {
+ // If the initial browser is remote, in order to optimize for first paint,
+ // we'll defer switching focus to that browser until it has painted.
+ let focusedElement = document.commandDispatcher.focusedElement;
+ let mm = window.messageManager;
+ mm.addMessageListener("Browser:FirstPaint", function onFirstPaint() {
+ mm.removeMessageListener("Browser:FirstPaint", onFirstPaint);
+ // If focus didn't move while we were waiting for first paint, we're okay
+ // to move to the browser.
+ if (document.commandDispatcher.focusedElement == focusedElement) {
+ gBrowser.selectedBrowser.focus();
+ }
+ });
+ } else {
+ // If the initial browser is not remote, we can focus the browser
+ // immediately with no paint performance impact.
+ gBrowser.selectedBrowser.focus();
+ }
+ }
+
+ // Enable/Disable auto-hide tabbar
+ gBrowser.tabContainer.updateVisibility();
+
+ BookmarkingUI.init();
+ AutoShowBookmarksToolbar.init();
+
+ gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
+
+ var homeButton = document.getElementById("home-button");
+ gHomeButton.updateTooltip(homeButton);
+
+ let safeMode = document.getElementById("helpSafeMode");
+ if (Services.appinfo.inSafeMode) {
+ safeMode.label = safeMode.getAttribute("stoplabel");
+ safeMode.accesskey = safeMode.getAttribute("stopaccesskey");
+ }
+
+ // BiDi UI
+ gBidiUI = isBidiEnabled();
+ if (gBidiUI) {
+ document.getElementById("documentDirection-separator").hidden = false;
+ document.getElementById("documentDirection-swap").hidden = false;
+ document.getElementById("textfieldDirection-separator").hidden = false;
+ document.getElementById("textfieldDirection-swap").hidden = false;
+ }
+
+ // Setup click-and-hold gestures access to the session history
+ // menus if global click-and-hold isn't turned on
+ if (!getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ let NP = {};
+ Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
+ NP.trackBrowserWindow(window);
+
+ PlacesToolbarHelper.init();
+
+ ctrlTab.readPref();
+ gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
+
+ // Initialize the download manager some time after the app starts so that
+ // auto-resume downloads begin (such as after crashing or quitting with
+ // active downloads) and speeds up the first-load of the download manager UI.
+ // If the user manually opens the download manager before the timeout, the
+ // downloads will start right away, and initializing again won't hurt.
+ setTimeout(function() {
+ try {
+ Cu.import("resource:///modules/DownloadsCommon.jsm", {})
+ .DownloadsCommon.initializeAllDataLinks();
+ Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
+ .DownloadsTaskbar.registerIndicator(window);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }, 10000);
+
+ // Load the Login Manager data from disk off the main thread, some time
+ // after startup. If the data is required before the timeout, for example
+ // because a restored page contains a password field, it will be loaded on
+ // the main thread, and this initialization request will be ignored.
+ setTimeout(function() {
+ try {
+ Services.logins;
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }, 3000);
+
+ // The object handling the downloads indicator is also initialized here in the
+ // delayed startup function, but the actual indicator element is not loaded
+ // unless there are downloads to be displayed.
+ DownloadsButton.initializeIndicator();
+
+ if (AppConstants.platform != "macosx") {
+ updateEditUIVisibility();
+ let placesContext = document.getElementById("placesContext");
+ placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
+ placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
+ }
+
+ LightWeightThemeWebInstaller.init();
+
+ if (Win7Features)
+ Win7Features.onOpenWindow();
+
+ PointerlockFsWarning.init();
+ FullScreen.init();
+ PointerLock.init();
+
+ // initialize the sync UI
+ gSyncUI.init();
+ gFxAccounts.init();
+
+ if (AppConstants.MOZ_DATA_REPORTING)
+ gDataNotificationInfoBar.init();
+
+ gBrowserThumbnails.init();
+
+ gMenuButtonBadgeManager.init();
+
+ gMenuButtonUpdateBadge.init();
+
+ window.addEventListener("mousemove", MousePosTracker, false);
+ window.addEventListener("dragover", MousePosTracker, false);
+
+ gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
+ gNavToolbox.addEventListener("customizationchange", CustomizationHandler);
+ gNavToolbox.addEventListener("customizationending", CustomizationHandler);
+
+ // End startup crash tracking after a delay to catch crashes while restoring
+ // tabs and to postpone saving the pref to disk.
+ try {
+ const startupCrashEndDelay = 30 * 1000;
+ setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay);
+ } catch (ex) {
+ Cu.reportError("Could not end startup crash tracking: " + ex);
+ }
+
+ // Delay this a minute because there's no rush
+ setTimeout(() => {
+ this.gmpInstallManager = new GMPInstallManager();
+ // We don't really care about the results, if someone is interested they
+ // can check the log.
+ this.gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
+ }, 1000 * 60);
+
+ // Report via telemetry whether we're able to play MP4/H.264/AAC video.
+ // We suspect that some Windows users have a broken or have not installed
+ // Windows Media Foundation, and we'd like to know how many. We'd also like
+ // to know how good our coverage is on other platforms.
+ // Note: we delay by 90 seconds reporting this, as calling canPlayType()
+ // on Windows will cause DLLs to load, i.e. cause disk I/O.
+ setTimeout(() => {
+ let v = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ let aacWorks = v.canPlayType("audio/mp4") != "";
+ Services.telemetry.getHistogramById("VIDEO_CAN_CREATE_AAC_DECODER").add(aacWorks);
+ let h264Works = v.canPlayType("video/mp4") != "";
+ Services.telemetry.getHistogramById("VIDEO_CAN_CREATE_H264_DECODER").add(h264Works);
+ }, 90 * 1000);
+
+ SessionStore.promiseInitialized.then(() => {
+ // Bail out if the window has been closed in the meantime.
+ if (window.closed) {
+ return;
+ }
+
+ // Enable the Restore Last Session command if needed
+ RestoreLastSessionObserver.init();
+
+ SocialUI.init();
+
+ // Start monitoring slow add-ons
+ AddonWatcher.init();
+
+ // Telemetry for master-password - we do this after 5 seconds as it
+ // can cause IO if NSS/PSM has not already initialized.
+ setTimeout(() => {
+ if (window.closed) {
+ return;
+ }
+ let secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
+ .getService(Ci.nsIPKCS11ModuleDB);
+ let slot = secmodDB.findSlotByName("");
+ let mpEnabled = slot &&
+ slot.status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
+ slot.status != Ci.nsIPKCS11Slot.SLOT_READY;
+ if (mpEnabled) {
+ Services.telemetry.getHistogramById("MASTER_PASSWORD_ENABLED").add(mpEnabled);
+ }
+ }, 5000);
+
+ PanicButtonNotifier.init();
+ });
+
+ gBrowser.tabContainer.addEventListener("TabSelect", function() {
+ for (let panel of document.querySelectorAll("panel[tabspecific='true']")) {
+ if (panel.state == "open") {
+ panel.hidePopup();
+ }
+ }
+ });
+
+ this.delayedStartupFinished = true;
+
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
+ TelemetryTimestamps.add("delayedStartupFinished");
+ },
+
+ // Returns the URI(s) to load at startup.
+ _getUriToLoad: function () {
+ // window.arguments[0]: URI to load (string), or an nsIArray of
+ // nsISupportsStrings to load, or a xul:tab of
+ // a tabbrowser, which will be replaced by this
+ // window (for this case, all other arguments are
+ // ignored).
+ if (!window.arguments || !window.arguments[0])
+ return null;
+
+ let uri = window.arguments[0];
+ let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
+ .getService(Ci.nsISessionStartup);
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"]
+ .getService(Ci.nsIBrowserHandler)
+ .defaultArgs;
+
+ // If the given URI matches defaultArgs (the default homepage) we want
+ // to block its load if we're going to restore a session anyway.
+ if (uri == defaultArgs && sessionStartup.willOverrideHomepage)
+ return null;
+
+ return uri;
+ },
+
+ onUnload: function() {
+ // In certain scenarios it's possible for unload to be fired before onload,
+ // (e.g. if the window is being closed after browser.js loads but before the
+ // load completes). In that case, there's nothing to do here.
+ if (!this._loadHandled)
+ return;
+
+ // First clean up services initialized in gBrowserInit.onLoad (or those whose
+ // uninit methods don't depend on the services having been initialized).
+
+ CombinedStopReload.uninit();
+
+ gGestureSupport.init(false);
+
+ gHistorySwipeAnimation.uninit();
+
+ FullScreen.uninit();
+
+ gFxAccounts.uninit();
+
+ Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
+
+ try {
+ gBrowser.removeProgressListener(window.XULBrowserWindow);
+ gBrowser.removeTabsProgressListener(window.TabsProgressListener);
+ } catch (ex) {
+ }
+
+ PlacesToolbarHelper.uninit();
+
+ BookmarkingUI.uninit();
+
+ TabsInTitlebar.uninit();
+
+ ToolbarIconColor.uninit();
+
+ TabletModeUpdater.uninit();
+
+ gTabletModePageCounter.finish();
+
+ BrowserOnClick.uninit();
+
+ FeedHandler.uninit();
+
+ DevEdition.uninit();
+
+ TrackingProtection.uninit();
+
+ RefreshBlocker.uninit();
+
+ CaptivePortalWatcher.uninit();
+
+ gMenuButtonUpdateBadge.uninit();
+
+ gMenuButtonBadgeManager.uninit();
+
+ SidebarUI.uninit();
+
+ // Now either cancel delayedStartup, or clean up the services initialized from
+ // it.
+ if (this._boundDelayedStartup) {
+ this._cancelDelayedStartup();
+ } else {
+ if (Win7Features)
+ Win7Features.onCloseWindow();
+
+ gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
+ ctrlTab.uninit();
+ SocialUI.uninit();
+ gBrowserThumbnails.uninit();
+ FullZoom.destroy();
+
+ Services.obs.removeObserver(gIdentityHandler, "perm-changed");
+ Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-origin-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-confirmation");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
+ window.messageManager.removeMessageListener("Browser:URIFixup", gKeywordURIFixup);
+ window.messageManager.removeMessageListener("Browser:LoadURI", RedirectLoad);
+
+ try {
+ gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ if (this.gmpInstallManager) {
+ this.gmpInstallManager.uninit();
+ }
+
+ BrowserOffline.uninit();
+ IndexedDBPromptHelper.uninit();
+ LightweightThemeListener.uninit();
+ PanelUI.uninit();
+ AutoShowBookmarksToolbar.uninit();
+ }
+
+ // Final window teardown, do this last.
+ window.XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null;
+ },
+};
+
+if (AppConstants.platform == "macosx") {
+ // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
+ // nonBrowserWindowShutdown() are used for non-browser windows in
+ // macBrowserOverlay
+ gBrowserInit.nonBrowserWindowStartup = function() {
+ // Disable inappropriate commands / submenus
+ var disabledItems = ['Browser:SavePage',
+ 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
+ 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload',
+ 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen',
+ 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs',
+ 'View:PageInfo'];
+ var element;
+
+ for (let disabledItem of disabledItems) {
+ element = document.getElementById(disabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // If no windows are active (i.e. we're the hidden window), disable the close, minimize
+ // and zoom menu commands as well
+ if (window.location.href == "chrome://browser/content/hiddenWindow.xul") {
+ var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow'];
+ for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) {
+ element = document.getElementById(hiddenWindowDisabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // also hide the window-list separator
+ element = document.getElementById("sep-window-list");
+ element.setAttribute("hidden", "true");
+
+ // Setup the dock menu.
+ let dockMenuElement = document.getElementById("menu_mac_dockmenu");
+ if (dockMenuElement != null) {
+ let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"]
+ .createInstance(Ci.nsIStandaloneNativeMenu);
+
+ try {
+ nativeMenu.init(dockMenuElement);
+
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.dockMenu = nativeMenu;
+ }
+ catch (e) {
+ }
+ }
+ }
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.getElementById("macDockMenuNewWindow").hidden = true;
+ }
+
+ this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0);
+ };
+
+ gBrowserInit.nonBrowserWindowDelayedStartup = function() {
+ this._delayedStartupTimeoutId = null;
+
+ // initialise the offline listener
+ BrowserOffline.init();
+
+ // initialize the private browsing UI
+ gPrivateBrowsingUI.init();
+
+ // initialize the sync UI
+ gSyncUI.init();
+
+ if (AppConstants.E10S_TESTING_ONLY) {
+ gRemoteTabsUI.init();
+ }
+ };
+
+ gBrowserInit.nonBrowserWindowShutdown = function() {
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.dockMenu = null;
+
+ // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do -
+ // just cancel the pending timeout and return;
+ if (this._delayedStartupTimeoutId) {
+ clearTimeout(this._delayedStartupTimeoutId);
+ return;
+ }
+
+ BrowserOffline.uninit();
+ };
+}
+
+
+/* Legacy global init functions */
+var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit);
+var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit);
+
+if (AppConstants.platform == "macosx") {
+ var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit);
+ var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit);
+ var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit);
+}
+
+function HandleAppCommandEvent(evt) {
+ switch (evt.command) {
+ case "Back":
+ BrowserBack();
+ break;
+ case "Forward":
+ BrowserForward();
+ break;
+ case "Reload":
+ BrowserReloadSkipCache();
+ break;
+ case "Stop":
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ BrowserStop();
+ break;
+ case "Search":
+ BrowserSearch.webSearch();
+ break;
+ case "Bookmarks":
+ SidebarUI.toggle("viewBookmarksSidebar");
+ break;
+ case "Home":
+ BrowserHome();
+ break;
+ case "New":
+ BrowserOpenTab();
+ break;
+ case "Close":
+ BrowserCloseTabOrWindow();
+ break;
+ case "Find":
+ gFindBar.onFindCommand();
+ break;
+ case "Help":
+ openHelpLink('firefox-help');
+ break;
+ case "Open":
+ BrowserOpenFileWindow();
+ break;
+ case "Print":
+ PrintUtils.printWindow(gBrowser.selectedBrowser.outerWindowID,
+ gBrowser.selectedBrowser);
+ break;
+ case "Save":
+ saveBrowser(gBrowser.selectedBrowser);
+ break;
+ case "SendMail":
+ MailIntegration.sendLinkForBrowser(gBrowser.selectedBrowser);
+ break;
+ default:
+ return;
+ }
+ evt.stopPropagation();
+ evt.preventDefault();
+}
+
+function gotoHistoryIndex(aEvent) {
+ let index = aEvent.target.getAttribute("index");
+ if (!index)
+ return false;
+
+ let where = whereToOpenLink(aEvent);
+
+ if (where == "current") {
+ // Normal click. Go there in the current tab and update session history.
+
+ try {
+ gBrowser.gotoIndex(index);
+ }
+ catch (ex) {
+ return false;
+ }
+ return true;
+ }
+ // Modified click. Go there in a new tab/window.
+
+ let historyindex = aEvent.target.getAttribute("historyindex");
+ duplicateTabIn(gBrowser.selectedTab, where, Number(historyindex));
+ return true;
+}
+
+function BrowserForward(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goForward();
+ }
+ catch (ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, 1);
+ }
+}
+
+function BrowserBack(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goBack();
+ }
+ catch (ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, -1);
+ }
+}
+
+function BrowserHandleBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserBack();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageUp");
+ break;
+ }
+}
+
+function BrowserHandleShiftBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserForward();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageDown");
+ break;
+ }
+}
+
+function BrowserStop() {
+ const stopFlags = nsIWebNavigation.STOP_ALL;
+ gBrowser.webNavigation.stop(stopFlags);
+}
+
+function BrowserReloadOrDuplicate(aEvent) {
+ let metaKeyPressed = AppConstants.platform == "macosx"
+ ? aEvent.metaKey
+ : aEvent.ctrlKey;
+ var backgroundTabModifier = aEvent.button == 1 || metaKeyPressed;
+
+ if (aEvent.shiftKey && !backgroundTabModifier) {
+ BrowserReloadSkipCache();
+ return;
+ }
+
+ let where = whereToOpenLink(aEvent, false, true);
+ if (where == "current")
+ BrowserReload();
+ else
+ duplicateTabIn(gBrowser.selectedTab, where);
+}
+
+function BrowserReload() {
+ if (gBrowser.currentURI.schemeIs("view-source")) {
+ // Bug 1167797: For view source, we always skip the cache
+ return BrowserReloadSkipCache();
+ }
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+function BrowserReloadSkipCache() {
+ // Bypass proxy and cache.
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+var BrowserHome = BrowserGoHome;
+function BrowserGoHome(aEvent) {
+ if (aEvent && "button" in aEvent &&
+ aEvent.button == 2) // right-click: do nothing
+ return;
+
+ var homePage = gHomeButton.getHomePage();
+ var where = whereToOpenLink(aEvent, false, true);
+ var urls;
+
+ // Home page should open in a new tab when current tab is an app tab
+ if (where == "current" &&
+ gBrowser &&
+ gBrowser.selectedTab.pinned)
+ where = "tab";
+
+ // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages
+ switch (where) {
+ case "current":
+ loadOneOrMoreURIs(homePage);
+ break;
+ case "tabshifted":
+ case "tab":
+ urls = homePage.split("|");
+ var loadInBackground = getBoolPref("browser.tabs.loadBookmarksInBackground", false);
+ gBrowser.loadTabs(urls, loadInBackground);
+ break;
+ case "window":
+ OpenBrowserWindow();
+ break;
+ }
+}
+
+function loadOneOrMoreURIs(aURIString)
+{
+ // we're not a browser window, pass the URI string to a new browser window
+ if (window.location.href != getBrowserURL())
+ {
+ window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString);
+ return;
+ }
+
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(aURIString.split("|"), false, true);
+ }
+ catch (e) {
+ }
+}
+
+function focusAndSelectUrlBar() {
+ // In customize mode, the url bar is disabled. If a new tab is opened or the
+ // user switches to a different tab, this function gets called before we've
+ // finished leaving customize mode, and the url bar will still be disabled.
+ // We can't focus it when it's disabled, so we need to re-run ourselves when
+ // we've finished leaving customize mode.
+ if (CustomizationHandler.isExitingCustomizeMode) {
+ gNavToolbox.addEventListener("aftercustomization", function afterCustomize() {
+ gNavToolbox.removeEventListener("aftercustomization", afterCustomize);
+ focusAndSelectUrlBar();
+ });
+
+ return true;
+ }
+
+ if (gURLBar) {
+ if (window.fullScreen)
+ FullScreen.showNavToolbox();
+
+ gURLBar.select();
+ if (document.activeElement == gURLBar.inputField)
+ return true;
+ }
+ return false;
+}
+
+function openLocation() {
+ if (focusAndSelectUrlBar())
+ return;
+
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus()
+ win.openLocation();
+ }
+ else {
+ // If there are no open browser windows, open a new one
+ window.openDialog("chrome://browser/content/", "_blank",
+ "chrome,all,dialog=no", BROWSER_NEW_TAB_URL);
+ }
+ }
+}
+
+function BrowserOpenTab(event) {
+ let where = "tab";
+ let relatedToCurrent = false;
+
+ if (event) {
+ where = whereToOpenLink(event, false, true);
+
+ switch (where) {
+ case "tab":
+ case "tabshifted":
+ // When accel-click or middle-click are used, open the new tab as
+ // related to the current tab.
+ relatedToCurrent = true;
+ break;
+ case "current":
+ where = "tab";
+ break;
+ }
+ }
+
+ openUILinkIn(BROWSER_NEW_TAB_URL, where, { relatedToCurrent });
+}
+
+/* Called from the openLocation dialog. This allows that dialog to instruct
+ its opener to open a new window and then step completely out of the way.
+ Anything less byzantine is causing horrible crashes, rather believably,
+ though oddly only on Linux. */
+function delayedOpenWindow(chrome, flags, href, postData)
+{
+ // The other way to use setTimeout,
+ // setTimeout(openDialog, 10, chrome, "_blank", flags, url),
+ // doesn't work here. The extra "magic" extra argument setTimeout adds to
+ // the callback function would confuse gBrowserInit.onLoad() by making
+ // window.arguments[1] be an integer instead of null.
+ setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10);
+}
+
+/* Required because the tab needs time to set up its content viewers and get the load of
+ the URI kicked off before becoming the active content area. */
+function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup)
+{
+ gBrowser.loadOneTab(aUrl, {
+ referrerURI: aReferrer,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: false,
+ allowThirdPartyFixup: aAllowThirdPartyFixup});
+}
+
+var gLastOpenDirectory = {
+ _lastDir: null,
+ get path() {
+ if (!this._lastDir || !this._lastDir.exists()) {
+ try {
+ this._lastDir = gPrefService.getComplexValue("browser.open.lastDir",
+ Ci.nsILocalFile);
+ if (!this._lastDir.exists())
+ this._lastDir = null;
+ }
+ catch (e) {}
+ }
+ return this._lastDir;
+ },
+ set path(val) {
+ try {
+ if (!val || !val.isDirectory())
+ return;
+ } catch (e) {
+ return;
+ }
+ this._lastDir = val.clone();
+
+ // Don't save the last open directory pref inside the Private Browsing mode
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile,
+ this._lastDir);
+ },
+ reset: function() {
+ this._lastDir = null;
+ }
+};
+
+function BrowserOpenFileWindow()
+{
+ // Get filepicker component.
+ try {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ try {
+ if (fp.file) {
+ gLastOpenDirectory.path =
+ fp.file.parent.QueryInterface(Ci.nsILocalFile);
+ }
+ } catch (ex) {
+ }
+ openUILinkIn(fp.fileURL.spec, "current");
+ }
+ };
+
+ fp.init(window, gNavigatorBundle.getString("openFile"),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
+ nsIFilePicker.filterImages | nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML);
+ fp.displayDirectory = gLastOpenDirectory.path;
+ fp.open(fpCallback);
+ } catch (ex) {
+ }
+}
+
+function BrowserCloseTabOrWindow() {
+ // If we're not a browser window, just close the window
+ if (window.location.href != getBrowserURL()) {
+ closeWindow(true);
+ return;
+ }
+
+ // If the current tab is the last one, this will close the window.
+ gBrowser.removeCurrentTab({animate: true});
+}
+
+function BrowserTryToCloseWindow()
+{
+ if (WindowIsClosing())
+ window.close(); // WindowIsClosing does all the necessary checks
+}
+
+function loadURI(uri, referrer, postData, allowThirdPartyFixup, referrerPolicy,
+ userContextId, originPrincipal, forceAboutBlankViewerInCurrent) {
+ try {
+ openLinkIn(uri, "current",
+ { referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ postData: postData,
+ allowThirdPartyFixup: allowThirdPartyFixup,
+ userContextId: userContextId,
+ originPrincipal,
+ forceAboutBlankViewerInCurrent,
+ });
+ } catch (e) {}
+}
+
+/**
+ * Given a string, will generate a more appropriate urlbar value if a Places
+ * keyword or a search alias is found at the beginning of it.
+ *
+ * @param url
+ * A string that may begin with a keyword or an alias.
+ *
+ * @return {Promise}
+ * @resolves { url, postData, mayInheritPrincipal }. If it's not possible
+ * to discern a keyword or an alias, url will be the input string.
+ */
+function getShortcutOrURIAndPostData(url, callback = null) {
+ if (callback) {
+ Deprecated.warning("Please use the Promise returned by " +
+ "getShortcutOrURIAndPostData() instead of passing a " +
+ "callback",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294");
+ }
+ return Task.spawn(function* () {
+ let mayInheritPrincipal = false;
+ let postData = null;
+ // Split on the first whitespace.
+ let [keyword, param = ""] = url.trim().split(/\s(.+)/, 2);
+
+ if (!keyword) {
+ return { url, postData, mayInheritPrincipal };
+ }
+
+ let engine = Services.search.getEngineByAlias(keyword);
+ if (engine) {
+ let submission = engine.getSubmission(param, null, "keyword");
+ return { url: submission.uri.spec,
+ postData: submission.postData,
+ mayInheritPrincipal };
+ }
+
+ // A corrupt Places database could make this throw, breaking navigation
+ // from the location bar.
+ let entry = null;
+ try {
+ entry = yield PlacesUtils.keywords.fetch(keyword);
+ } catch (ex) {
+ Cu.reportError(`Unable to fetch Places keyword "${keyword}": ${ex}`);
+ }
+ if (!entry || !entry.url) {
+ // This is not a Places keyword.
+ return { url, postData, mayInheritPrincipal };
+ }
+
+ try {
+ [url, postData] =
+ yield BrowserUtils.parseUrlAndPostData(entry.url.href,
+ entry.postData,
+ param);
+ if (postData) {
+ postData = getPostDataStream(postData);
+ }
+
+ // Since this URL came from a bookmark, it's safe to let it inherit the
+ // current document's principal.
+ mayInheritPrincipal = true;
+ } catch (ex) {
+ // It was not possible to bind the param, just use the original url value.
+ }
+
+ return { url, postData, mayInheritPrincipal };
+ }).then(data => {
+ if (callback) {
+ callback(data);
+ }
+ return data;
+ });
+}
+
+function getPostDataStream(aPostDataString,
+ aType = "application/x-www-form-urlencoded") {
+ let dataStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ dataStream.data = aPostDataString;
+
+ let mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"]
+ .createInstance(Ci.nsIMIMEInputStream);
+ mimeStream.addHeader("Content-Type", aType);
+ mimeStream.addContentLength = true;
+ mimeStream.setData(dataStream);
+ return mimeStream.QueryInterface(Ci.nsIInputStream);
+}
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function readFromClipboard()
+{
+ var url;
+
+ try {
+ // Create transferable that will transfer the text.
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+
+ trans.addDataFlavor("text/unicode");
+
+ // If available, use selection clipboard, otherwise global one
+ if (Services.clipboard.supportsSelectionClipboard())
+ Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard);
+ else
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ var data = {};
+ var dataLen = {};
+ trans.getTransferData("text/unicode", data, dataLen);
+
+ if (data) {
+ data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
+ url = data.data.substring(0, dataLen.value / 2);
+ }
+ } catch (ex) {
+ }
+
+ return url;
+}
+
+/**
+ * Open the View Source dialog.
+ *
+ * @param aArgsOrDocument
+ * Either an object or a Document. Passing a Document is deprecated,
+ * and is not supported with e10s. This function will throw if
+ * aArgsOrDocument is a CPOW.
+ *
+ * If aArgsOrDocument is an object, that object can take 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. You only need to provide this if you
+ * want to attempt to retrieve the document source from the network
+ * cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ */
+function BrowserViewSourceOfDocument(aArgsOrDocument) {
+ let args;
+
+ if (aArgsOrDocument instanceof Document) {
+ let doc = aArgsOrDocument;
+ // Deprecated API - callers should pass args object instead.
+ if (Cu.isCrossProcessWrapper(doc)) {
+ throw new Error("BrowserViewSourceOfDocument cannot accept a CPOW " +
+ "as a document.");
+ }
+
+ let requestor = doc.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor);
+ let browser = requestor.getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ let outerWindowID = requestor.getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ let URL = browser.currentURI.spec;
+ args = { browser, outerWindowID, URL };
+ } else {
+ args = aArgsOrDocument;
+ }
+
+ let viewInternal = () => {
+ let inTab = Services.prefs.getBoolPref("view_source.tab");
+ if (inTab) {
+ let tabBrowser = gBrowser;
+ let forceNotRemote = false;
+ if (!tabBrowser) {
+ if (!args.browser) {
+ throw new Error("BrowserViewSourceOfDocument should be passed the " +
+ "subject browser if called from a window without " +
+ "gBrowser defined.");
+ }
+ forceNotRemote = !args.browser.isRemoteBrowser;
+ } else {
+ // Some internal URLs (such as specific chrome: and about: URLs that are
+ // not yet remote ready) cannot be loaded in a remote browser. View
+ // source in tab expects the new view source browser's remoteness to match
+ // that of the original URL, so disable remoteness if necessary for this
+ // URL.
+ let contentProcess = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
+ forceNotRemote =
+ gMultiProcessBrowser &&
+ !E10SUtils.canLoadURIInProcess(args.URL, contentProcess)
+ }
+
+ // In the case of popups, we need to find a non-popup browser window.
+ if (!tabBrowser || !window.toolbar.visible) {
+ // This returns only non-popup browser windows by default.
+ let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+ tabBrowser = browserWindow.gBrowser;
+ }
+
+ // `viewSourceInBrowser` will load the source content from the page
+ // descriptor for the tab (when possible) or fallback to the network if
+ // that fails. Either way, the view source module will manage the tab's
+ // location, so use "about:blank" here to avoid unnecessary redundant
+ // requests.
+ let tab = tabBrowser.loadOneTab("about:blank", {
+ relatedToCurrent: true,
+ inBackground: false,
+ forceNotRemote,
+ relatedBrowser: args.browser
+ });
+ args.viewSourceBrowser = tabBrowser.getBrowserForTab(tab);
+ top.gViewSourceUtils.viewSourceInBrowser(args);
+ } else {
+ top.gViewSourceUtils.viewSource(args);
+ }
+ }
+
+ // Check if external view source is enabled. If so, try it. If it fails,
+ // fallback to internal view source.
+ if (Services.prefs.getBoolPref("view_source.editor.external")) {
+ top.gViewSourceUtils
+ .openInExternalEditor(args, null, null, null, result => {
+ if (!result) {
+ viewInternal();
+ }
+ });
+ } else {
+ // Display using internal view source
+ viewInternal();
+ }
+}
+
+/**
+ * Opens the View Source dialog for the source loaded in the root
+ * top-level document of the browser. This is really just a
+ * convenience wrapper around BrowserViewSourceOfDocument.
+ *
+ * @param browser
+ * The browser that we want to load the source of.
+ */
+function BrowserViewSource(browser) {
+ BrowserViewSourceOfDocument({
+ browser: browser,
+ outerWindowID: browser.outerWindowID,
+ URL: browser.currentURI.spec,
+ });
+}
+
+// documentURL - URL of the document to view, or null for this window's document
+// initialTab - name of the initial tab to display, or null for the first tab
+// imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
+// frameOuterWindowID - the id of the frame that the context menu opened in; can be null/omitted
+// browser - the browser containing the document we're interested in inspecting; can be null/omitted
+function BrowserPageInfo(documentURL, initialTab, imageElement, frameOuterWindowID, browser) {
+ if (documentURL instanceof HTMLDocument) {
+ Deprecated.warning("Please pass the location URL instead of the document " +
+ "to BrowserPageInfo() as the first argument.",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1238180");
+ documentURL = documentURL.location;
+ }
+
+ let args = { initialTab, imageElement, frameOuterWindowID, browser };
+ var windows = Services.wm.getEnumerator("Browser:page-info");
+
+ documentURL = documentURL || window.gBrowser.selectedBrowser.currentURI.spec;
+
+ // Check for windows matching the url
+ while (windows.hasMoreElements()) {
+ var currentWindow = windows.getNext();
+ if (currentWindow.closed) {
+ continue;
+ }
+ if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) {
+ currentWindow.focus();
+ currentWindow.resetPageInfo(args);
+ return currentWindow;
+ }
+ }
+
+ // We didn't find a matching window, so open a new one.
+ return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "",
+ "chrome,toolbar,dialog=no,resizable", args);
+}
+
+function URLBarSetURI(aURI) {
+ var value = gBrowser.userTypedValue;
+ var valid = false;
+
+ if (value == null) {
+ let uri = aURI || gBrowser.currentURI;
+ // Strip off "wyciwyg://" and passwords for the location bar
+ try {
+ uri = Services.uriFixup.createExposableURI(uri);
+ } catch (e) {}
+
+ // Replace initial page URIs with an empty string
+ // 1. only if there's no opener (bug 370555).
+ // 2. if remote newtab is enabled and it's the default remote newtab page
+ let defaultRemoteURL = gAboutNewTabService.remoteEnabled &&
+ uri.spec === gAboutNewTabService.newTabURL;
+ if ((gInitialPages.includes(uri.spec) || defaultRemoteURL) &&
+ checkEmptyPageOrigin(gBrowser.selectedBrowser, uri)) {
+ value = "";
+ } else {
+ // We should deal with losslessDecodeURI throwing for exotic URIs
+ try {
+ value = losslessDecodeURI(uri);
+ } catch (ex) {
+ value = "about:blank";
+ }
+ }
+
+ valid = !isBlankPageURL(uri.spec);
+ }
+
+ let isDifferentValidValue = valid && value != gURLBar.value;
+ gURLBar.value = value;
+ gURLBar.valueIsTyped = !valid;
+ if (isDifferentValidValue) {
+ gURLBar.selectionStart = gURLBar.selectionEnd = 0;
+ }
+
+ SetPageProxyState(valid ? "valid" : "invalid");
+}
+
+function losslessDecodeURI(aURI) {
+ let scheme = aURI.scheme;
+ if (scheme == "moz-action")
+ throw new Error("losslessDecodeURI should never get a moz-action URI");
+
+ var value = aURI.spec;
+
+ let decodeASCIIOnly = !["https", "http", "file", "ftp"].includes(scheme);
+ // Try to decode as UTF-8 if there's no encoding sequence that we would break.
+ if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value)) {
+ if (decodeASCIIOnly) {
+ // This only decodes ascii characters (hex) 20-7e, except 25 (%).
+ // This avoids both cases stipulated below (%-related issues, and \r, \n
+ // and \t, which would be %0d, %0a and %09, respectively) as well as any
+ // non-US-ascii characters.
+ value = value.replace(/%(2[0-4]|2[6-9a-f]|[3-6][0-9a-f]|7[0-9a-e])/g, decodeURI);
+ } else {
+ try {
+ value = decodeURI(value)
+ // 1. decodeURI decodes %25 to %, which creates unintended
+ // encoding sequences. Re-encode it, unless it's part of
+ // a sequence that survived decodeURI, i.e. one for:
+ // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
+ // (RFC 3987 section 3.2)
+ // 2. Re-encode select whitespace so that it doesn't get eaten
+ // away by the location bar (bug 410726). Re-encode all
+ // adjacent whitespace, to prevent spoofing attempts where
+ // invisible characters would push part of the URL to
+ // overflow the location bar (bug 1395508).
+ .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]|\s(?=\s)|\s$/ig,
+ encodeURIComponent);
+ } catch (e) {}
+ }
+ }
+
+ // Encode invisible characters (C0/C1 control characters, U+007F [DEL],
+ // U+00A0 [no-break space], line and paragraph separator,
+ // object replacement character) (bug 452979, bug 909264)
+ value = value.replace(/[\u0000-\u001f\u007f-\u00a0\u2028\u2029\ufffc]/g,
+ encodeURIComponent);
+
+ // Encode default ignorable characters (bug 546013)
+ // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186).
+ // This includes all bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ value = value.replace(/[\u00ad\u034f\u061c\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g,
+ encodeURIComponent);
+ return value;
+}
+
+function UpdateUrlbarSearchSplitterState()
+{
+ var splitter = document.getElementById("urlbar-search-splitter");
+ var urlbar = document.getElementById("urlbar-container");
+ var searchbar = document.getElementById("search-container");
+
+ if (document.documentElement.getAttribute("customizing") == "true") {
+ if (splitter) {
+ splitter.remove();
+ }
+ return;
+ }
+
+ // If the splitter is already in the right place, we don't need to do anything:
+ if (splitter &&
+ ((splitter.nextSibling == searchbar && splitter.previousSibling == urlbar) ||
+ (splitter.nextSibling == urlbar && splitter.previousSibling == searchbar))) {
+ return;
+ }
+
+ var ibefore = null;
+ if (urlbar && searchbar) {
+ if (urlbar.nextSibling == searchbar)
+ ibefore = searchbar;
+ else if (searchbar.nextSibling == urlbar)
+ ibefore = urlbar;
+ }
+
+ if (ibefore) {
+ if (!splitter) {
+ splitter = document.createElement("splitter");
+ splitter.id = "urlbar-search-splitter";
+ splitter.setAttribute("resizebefore", "flex");
+ splitter.setAttribute("resizeafter", "flex");
+ splitter.setAttribute("skipintoolbarset", "true");
+ splitter.setAttribute("overflows", "false");
+ splitter.className = "chromeclass-toolbar-additional";
+ }
+ urlbar.parentNode.insertBefore(splitter, ibefore);
+ } else if (splitter)
+ splitter.parentNode.removeChild(splitter);
+}
+
+function UpdatePageProxyState()
+{
+ if (gURLBar && gURLBar.value != gLastValidURLStr)
+ SetPageProxyState("invalid");
+}
+
+function SetPageProxyState(aState)
+{
+ if (!gURLBar)
+ return;
+
+ gURLBar.setAttribute("pageproxystate", aState);
+
+ // the page proxy state is set to valid via OnLocationChange, which
+ // gets called when we switch tabs.
+ if (aState == "valid") {
+ gLastValidURLStr = gURLBar.value;
+ gURLBar.addEventListener("input", UpdatePageProxyState, false);
+ } else if (aState == "invalid") {
+ gURLBar.removeEventListener("input", UpdatePageProxyState, false);
+ }
+}
+
+function PageProxyClickHandler(aEvent)
+{
+ if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
+ middleMousePaste(aEvent);
+}
+
+var gMenuButtonBadgeManager = {
+ BADGEID_APPUPDATE: "update",
+ BADGEID_DOWNLOAD: "download",
+ BADGEID_FXA: "fxa",
+
+ fxaBadge: null,
+ downloadBadge: null,
+ appUpdateBadge: null,
+
+ init: function () {
+ PanelUI.panel.addEventListener("popupshowing", this, true);
+ },
+
+ uninit: function () {
+ PanelUI.panel.removeEventListener("popupshowing", this, true);
+ },
+
+ handleEvent: function (e) {
+ if (e.type === "popupshowing") {
+ this.clearBadges();
+ }
+ },
+
+ _showBadge: function () {
+ let badgeToShow = this.downloadBadge || this.appUpdateBadge || this.fxaBadge;
+
+ if (badgeToShow) {
+ PanelUI.menuButton.setAttribute("badge-status", badgeToShow);
+ } else {
+ PanelUI.menuButton.removeAttribute("badge-status");
+ }
+ },
+
+ _changeBadge: function (badgeId, badgeStatus = null) {
+ if (badgeId == this.BADGEID_APPUPDATE) {
+ this.appUpdateBadge = badgeStatus;
+ } else if (badgeId == this.BADGEID_DOWNLOAD) {
+ this.downloadBadge = badgeStatus;
+ } else if (badgeId == this.BADGEID_FXA) {
+ this.fxaBadge = badgeStatus;
+ } else {
+ Cu.reportError("The badge ID '" + badgeId + "' is unknown!");
+ }
+ this._showBadge();
+ },
+
+ addBadge: function (badgeId, badgeStatus) {
+ if (!badgeStatus) {
+ Cu.reportError("badgeStatus must be defined");
+ return;
+ }
+ this._changeBadge(badgeId, badgeStatus);
+ },
+
+ removeBadge: function (badgeId) {
+ this._changeBadge(badgeId);
+ },
+
+ clearBadges: function () {
+ this.appUpdateBadge = null;
+ this.downloadBadge = null;
+ this.fxaBadge = null;
+ this._showBadge();
+ }
+};
+
+// Setup the hamburger button badges for updates, if enabled.
+var gMenuButtonUpdateBadge = {
+ enabled: false,
+ badgeWaitTime: 0,
+ timer: null,
+ cancelObserverRegistered: false,
+
+ init: function () {
+ try {
+ this.enabled = Services.prefs.getBoolPref("app.update.badge");
+ } catch (e) {}
+ if (this.enabled) {
+ try {
+ this.badgeWaitTime = Services.prefs.getIntPref("app.update.badgeWaitTime");
+ } catch (e) {
+ this.badgeWaitTime = 345600; // 4 days
+ }
+ Services.obs.addObserver(this, "update-staged", false);
+ Services.obs.addObserver(this, "update-downloaded", false);
+ }
+ },
+
+ uninit: function () {
+ if (this.timer)
+ this.timer.cancel();
+ if (this.enabled) {
+ Services.obs.removeObserver(this, "update-staged");
+ Services.obs.removeObserver(this, "update-downloaded");
+ this.enabled = false;
+ }
+ if (this.cancelObserverRegistered) {
+ Services.obs.removeObserver(this, "update-canceled");
+ this.cancelObserverRegistered = false;
+ }
+ },
+
+ onMenuPanelCommand: function(event) {
+ if (event.originalTarget.getAttribute("update-status") === "succeeded") {
+ // restart the app
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ if (!cancelQuit.data) {
+ Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
+ }
+ } else {
+ // open the page for manual update
+ let url = Services.urlFormatter.formatURLPref("app.update.url.manual");
+ openUILinkIn(url, "tab");
+ }
+ },
+
+ observe: function (subject, topic, status) {
+ if (topic == "update-canceled") {
+ this.reset();
+ return;
+ }
+ if (status == "failed") {
+ // Background update has failed, let's show the UI responsible for
+ // prompting the user to update manually.
+ this.uninit();
+ this.displayBadge(false);
+ return;
+ }
+
+ // Give the user badgeWaitTime seconds to react before prompting.
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.timer.initWithCallback(this, this.badgeWaitTime * 1000,
+ this.timer.TYPE_ONE_SHOT);
+ // The timer callback will call uninit() when it completes.
+ },
+
+ notify: function () {
+ // If the update is successfully applied, or if the updater has fallen back
+ // to non-staged updates, add a badge to the hamburger menu to indicate an
+ // update will be applied once the browser restarts.
+ this.uninit();
+ this.displayBadge(true);
+ },
+
+ displayBadge: function (succeeded) {
+ let status = succeeded ? "succeeded" : "failed";
+ let badgeStatus = "update-" + status;
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, badgeStatus);
+
+ let stringId;
+ let updateButtonText;
+ if (succeeded) {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ stringId = "appmenu.restartNeeded.description";
+ updateButtonText = gNavigatorBundle.getFormattedString(stringId,
+ [brandShortName]);
+ Services.obs.addObserver(this, "update-canceled", false);
+ this.cancelObserverRegistered = true;
+ } else {
+ stringId = "appmenu.updateFailed.description";
+ updateButtonText = gNavigatorBundle.getString(stringId);
+ }
+
+ let updateButton = document.getElementById("PanelUI-update-status");
+ updateButton.setAttribute("label", updateButtonText);
+ updateButton.setAttribute("update-status", status);
+ updateButton.hidden = false;
+ },
+
+ reset: function () {
+ gMenuButtonBadgeManager.removeBadge(
+ gMenuButtonBadgeManager.BADGEID_APPUPDATE);
+ let updateButton = document.getElementById("PanelUI-update-status");
+ updateButton.hidden = true;
+ this.uninit();
+ this.init();
+ }
+};
+
+// Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
+const TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED = 2;
+const TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED = 3;
+const TLS_ERROR_REPORT_TELEMETRY_MANUAL_SEND = 4;
+const TLS_ERROR_REPORT_TELEMETRY_AUTO_SEND = 5;
+
+const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
+
+const PREF_SSL_IMPACT = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
+ return prefs.concat(Services.prefs.getChildList(root));
+}, []);
+
+/**
+ * Handle command events bubbling up from error page content
+ * or from about:newtab or from remote error pages that invoke
+ * us via async messaging.
+ */
+var BrowserOnClick = {
+ init: function () {
+ let mm = window.messageManager;
+ mm.addMessageListener("Browser:CertExceptionError", this);
+ mm.addMessageListener("Browser:OpenCaptivePortalPage", this);
+ mm.addMessageListener("Browser:SiteBlockedError", this);
+ mm.addMessageListener("Browser:EnableOnlineMode", this);
+ mm.addMessageListener("Browser:SendSSLErrorReport", this);
+ mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
+ mm.addMessageListener("Browser:ResetSSLPreferences", this);
+ mm.addMessageListener("Browser:SSLErrorReportTelemetry", this);
+ mm.addMessageListener("Browser:OverrideWeakCrypto", this);
+ mm.addMessageListener("Browser:SSLErrorGoBack", this);
+
+ Services.obs.addObserver(this, "captive-portal-login-abort", false);
+ Services.obs.addObserver(this, "captive-portal-login-success", false);
+ },
+
+ uninit: function () {
+ let mm = window.messageManager;
+ mm.removeMessageListener("Browser:CertExceptionError", this);
+ mm.removeMessageListener("Browser:SiteBlockedError", this);
+ mm.removeMessageListener("Browser:EnableOnlineMode", this);
+ mm.removeMessageListener("Browser:SendSSLErrorReport", this);
+ mm.removeMessageListener("Browser:SetSSLErrorReportAuto", this);
+ mm.removeMessageListener("Browser:ResetSSLPreferences", this);
+ mm.removeMessageListener("Browser:SSLErrorReportTelemetry", this);
+ mm.removeMessageListener("Browser:OverrideWeakCrypto", this);
+ mm.removeMessageListener("Browser:SSLErrorGoBack", this);
+
+ Services.obs.removeObserver(this, "captive-portal-login-abort");
+ Services.obs.removeObserver(this, "captive-portal-login-success");
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "captive-portal-login-abort":
+ case "captive-portal-login-success":
+ // Broadcast when a captive portal is freed so that error pages
+ // can refresh themselves.
+ window.messageManager.broadcastAsyncMessage("Browser:CaptivePortalFreed");
+ break;
+ }
+ },
+
+ handleEvent: function (event) {
+ if (!event.isTrusted || // Don't trust synthetic events
+ event.button == 2) {
+ return;
+ }
+
+ let originalTarget = event.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+ if (!ownerDoc) {
+ return;
+ }
+
+ if (gMultiProcessBrowser &&
+ ownerDoc.documentURI.toLowerCase() == "about:newtab") {
+ this.onE10sAboutNewTab(event, ownerDoc);
+ }
+ },
+
+ receiveMessage: function (msg) {
+ switch (msg.name) {
+ case "Browser:CertExceptionError":
+ this.onCertError(msg.target, msg.data.elementId,
+ msg.data.isTopFrame, msg.data.location,
+ msg.data.securityInfoAsString);
+ break;
+ case "Browser:OpenCaptivePortalPage":
+ CaptivePortalWatcher.ensureCaptivePortalTab();
+ break;
+ case "Browser:SiteBlockedError":
+ this.onAboutBlocked(msg.data.elementId, msg.data.reason,
+ msg.data.isTopFrame, msg.data.location);
+ break;
+ case "Browser:EnableOnlineMode":
+ if (Services.io.offline) {
+ // Reset network state and refresh the page.
+ Services.io.offline = false;
+ msg.target.reload();
+ }
+ break;
+ case "Browser:SendSSLErrorReport":
+ this.onSSLErrorReport(msg.target,
+ msg.data.uri,
+ msg.data.securityInfo);
+ break;
+ case "Browser:ResetSSLPreferences":
+ for (let prefName of PREF_SSL_IMPACT) {
+ Services.prefs.clearUserPref(prefName);
+ }
+ msg.target.reload();
+ break;
+ case "Browser:SetSSLErrorReportAuto":
+ Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", msg.json.automatic);
+ let bin = TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED;
+ if (msg.json.automatic) {
+ bin = TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED;
+ }
+ Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI").add(bin);
+ break;
+ case "Browser:SSLErrorReportTelemetry":
+ let reportStatus = msg.data.reportStatus;
+ Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI")
+ .add(reportStatus);
+ break;
+ case "Browser:OverrideWeakCrypto":
+ let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
+ .getService(Ci.nsIWeakCryptoOverride);
+ weakCryptoOverride.addWeakCryptoOverride(
+ msg.data.uri.host,
+ PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
+ break;
+ case "Browser:SSLErrorGoBack":
+ goBackFromErrorPage();
+ break;
+ }
+ },
+
+ onSSLErrorReport: function(browser, uri, securityInfo) {
+ if (!Services.prefs.getBoolPref("security.ssl.errorReporting.enabled")) {
+ Cu.reportError("User requested certificate error report sending, but certificate error reporting is disabled");
+ return;
+ }
+
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let transportSecurityInfo = serhelper.deserializeObject(securityInfo);
+ transportSecurityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
+
+ let errorReporter = Cc["@mozilla.org/securityreporter;1"]
+ .getService(Ci.nsISecurityReporter);
+ errorReporter.reportTLSError(transportSecurityInfo,
+ uri.host, uri.port);
+ },
+
+ onCertError: function (browser, elementId, isTopFrame, location, securityInfoAsString) {
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+ let securityInfo;
+
+ switch (elementId) {
+ case "exceptionDialogButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
+ }
+
+ securityInfo = getSecurityInfo(securityInfoAsString);
+ let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus;
+ let params = { exceptionAdded : false,
+ sslStatus : sslStatus };
+
+ try {
+ switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
+ case 2 : // Pre-fetch & pre-populate
+ params.prefetchCert = true;
+ case 1 : // Pre-populate
+ params.location = location;
+ }
+ } catch (e) {
+ Components.utils.reportError("Couldn't get ssl_override pref: " + e);
+ }
+
+ window.openDialog('chrome://pippki/content/exceptionDialog.xul',
+ '', 'chrome,centerscreen,modal', params);
+
+ // If the user added the exception cert, attempt to reload the page
+ if (params.exceptionAdded) {
+ browser.reload();
+ }
+ break;
+
+ case "returnButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE);
+ }
+ goBackFromErrorPage();
+ break;
+
+ case "advancedButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
+ }
+
+ securityInfo = getSecurityInfo(securityInfoAsString);
+ let errorInfo = getDetailedCertErrorInfo(location,
+ securityInfo);
+ browser.messageManager.sendAsyncMessage( "CertErrorDetails", {
+ code: securityInfo.errorCode,
+ info: errorInfo
+ });
+ break;
+
+ case "copyToClipboard":
+ const gClipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper);
+ securityInfo = getSecurityInfo(securityInfoAsString);
+ let detailedInfo = getDetailedCertErrorInfo(location,
+ securityInfo);
+ gClipboardHelper.copyString(detailedInfo);
+ break;
+
+ }
+ },
+
+ onAboutBlocked: function (elementId, reason, isTopFrame, location) {
+ // Depending on what page we are displaying here (malware/phishing/unwanted)
+ // use the right strings and links for each.
+ let bucketName = "";
+ let sendTelemetry = false;
+ if (reason === 'malware') {
+ sendTelemetry = true;
+ bucketName = "WARNING_MALWARE_PAGE_";
+ } else if (reason === 'phishing') {
+ sendTelemetry = true;
+ bucketName = "WARNING_PHISHING_PAGE_";
+ } else if (reason === 'unwanted') {
+ sendTelemetry = true;
+ bucketName = "WARNING_UNWANTED_PAGE_";
+ }
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+ let nsISecTel = Ci.nsISecurityUITelemetry;
+ bucketName += isTopFrame ? "TOP_" : "FRAME_";
+ switch (elementId) {
+ case "getMeOutButton":
+ if (sendTelemetry) {
+ secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
+ }
+ getMeOutOfHere();
+ break;
+
+ case "reportButton":
+ // This is the "Why is this site blocked" button. We redirect
+ // to the generic page describing phishing/malware protection.
+
+ // We log even if malware/phishing/unwanted info URL couldn't be found:
+ // the measurement is for how many users clicked the WHY BLOCKED button
+ if (sendTelemetry) {
+ secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
+ }
+ openHelpLink("phishing-malware", false, "current");
+ break;
+
+ case "ignoreWarningButton":
+ if (gPrefService.getBoolPref("browser.safebrowsing.allowOverride")) {
+ if (sendTelemetry) {
+ secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
+ }
+ this.ignoreWarningButton(reason);
+ }
+ break;
+ }
+ },
+
+ /**
+ * This functions prevents navigation from happening directly through the <a>
+ * link in about:newtab (which is loaded in the parent and therefore would load
+ * the next page also in the parent) and instructs the browser to open the url
+ * in the current tab which will make it update the remoteness of the tab.
+ */
+ onE10sAboutNewTab: function(event, ownerDoc) {
+ let isTopFrame = (ownerDoc.defaultView.parent === ownerDoc.defaultView);
+ if (!isTopFrame) {
+ return;
+ }
+
+ let anchorTarget = event.originalTarget.parentNode;
+
+ if (anchorTarget instanceof HTMLAnchorElement &&
+ anchorTarget.classList.contains("newtab-link")) {
+ event.preventDefault();
+ let where = whereToOpenLink(event, false, false);
+ openLinkIn(anchorTarget.href, where, { charset: ownerDoc.characterSet, referrerURI: ownerDoc.documentURIObject });
+ }
+ },
+
+ ignoreWarningButton: function (reason) {
+ // Allow users to override and continue through to the site,
+ // but add a notify bar as a reminder, so that they don't lose
+ // track after, e.g., tab switching.
+ gBrowser.loadURIWithFlags(gBrowser.currentURI.spec,
+ nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
+ null, null, null);
+
+ Services.perms.add(gBrowser.currentURI, "safe-browsing",
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
+ callback: function() { getMeOutOfHere(); }
+ }];
+
+ let title;
+ if (reason === 'malware') {
+ title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
+ buttons[1] = {
+ label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
+ callback: function() {
+ openUILinkIn(gSafeBrowsing.getReportURL('MalwareMistake'), 'tab');
+ }
+ };
+ } else if (reason === 'phishing') {
+ title = gNavigatorBundle.getString("safebrowsing.deceptiveSite");
+ buttons[1] = {
+ label: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.notADeceptiveSiteButton.accessKey"),
+ callback: function() {
+ openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');
+ }
+ };
+ } else if (reason === 'unwanted') {
+ title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
+ // There is no button for reporting errors since Google doesn't currently
+ // provide a URL endpoint for these reports.
+ }
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let value = "blocked-badware-page";
+
+ let previousNotification = notificationBox.getNotificationWithValue(value);
+ if (previousNotification) {
+ notificationBox.removeNotification(previousNotification);
+ }
+
+ let notification = notificationBox.appendNotification(
+ title,
+ value,
+ "chrome://global/skin/icons/blacklist_favicon.png",
+ notificationBox.PRIORITY_CRITICAL_HIGH,
+ buttons
+ );
+ // Persist the notification until the user removes so it
+ // doesn't get removed on redirects.
+ notification.persistence = -1;
+ },
+};
+
+/**
+ * Re-direct the browser to a known-safe page. This function is
+ * used when, for example, the user browses to a known malware page
+ * and is presented with about:blocked. The "Get me out of here!"
+ * button should take the user to the default start page so that even
+ * when their own homepage is infected, we can get them somewhere safe.
+ */
+function getMeOutOfHere() {
+ gBrowser.loadURI(getDefaultHomePage());
+}
+
+/**
+ * Re-direct the browser to the previous page or a known-safe page if no
+ * previous page is found in history. This function is used when the user
+ * browses to a secure page with certificate issues and is presented with
+ * about:certerror. The "Go Back" button should take the user to the previous
+ * or a default start page so that even when their own homepage is on a server
+ * that has certificate errors, we can get them somewhere safe.
+ */
+function goBackFromErrorPage() {
+ const ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ let state = JSON.parse(ss.getTabState(gBrowser.selectedTab));
+ if (state.index == 1) {
+ // If the unsafe page is the first or the only one in history, go to the
+ // start page.
+ gBrowser.loadURI(getDefaultHomePage());
+ } else {
+ BrowserBack();
+ }
+}
+
+/**
+ * Return the default start page for the cases when the user's own homepage is
+ * infected, so we can get them somewhere safe.
+ */
+function getDefaultHomePage() {
+ // Get the start page from the *default* pref branch, not the user's
+ var prefs = Services.prefs.getDefaultBranch(null);
+ var url = BROWSER_NEW_TAB_URL;
+ try {
+ url = prefs.getComplexValue("browser.startup.homepage",
+ Ci.nsIPrefLocalizedString).data;
+ // If url is a pipe-delimited set of pages, just take the first one.
+ if (url.includes("|"))
+ url = url.split("|")[0];
+ } catch (e) {
+ Components.utils.reportError("Couldn't get homepage pref: " + e);
+ }
+ return url;
+}
+
+function BrowserFullScreen()
+{
+ window.fullScreen = !window.fullScreen;
+}
+
+function mirrorShow(popup) {
+ let services = [];
+ if (Services.prefs.getBoolPref("browser.casting.enabled")) {
+ services = CastingApps.getServicesForMirroring();
+ }
+ popup.ownerDocument.getElementById("menu_mirrorTabCmd").hidden = !services.length;
+}
+
+function mirrorMenuItemClicked(event) {
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage("SecondScreen:tab-mirror",
+ {service: event.originalTarget._service});
+}
+
+function populateMirrorTabMenu(popup) {
+ popup.innerHTML = null;
+ if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
+ return;
+ }
+ let doc = popup.ownerDocument;
+ let services = CastingApps.getServicesForMirroring();
+ services.forEach(service => {
+ let item = doc.createElement("menuitem");
+ item.setAttribute("label", service.friendlyName);
+ item._service = service;
+ item.addEventListener("command", mirrorMenuItemClicked);
+ popup.appendChild(item);
+ });
+}
+
+function getWebNavigation()
+{
+ return gBrowser.webNavigation;
+}
+
+function BrowserReloadWithFlags(reloadFlags) {
+ let url = gBrowser.currentURI.spec;
+ if (gBrowser.updateBrowserRemotenessByURL(gBrowser.selectedBrowser, url)) {
+ // If the remoteness has changed, the new browser doesn't have any
+ // information of what was loaded before, so we need to load the previous
+ // URL again.
+ gBrowser.loadURIWithFlags(url, reloadFlags);
+ return;
+ }
+
+ let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ gBrowser.selectedBrowser
+ .messageManager
+ .sendAsyncMessage("Browser:Reload",
+ { flags: reloadFlags,
+ handlingUserInput: windowUtils.isHandlingUserInput });
+}
+
+function getSecurityInfo(securityInfoAsString) {
+ if (!securityInfoAsString)
+ return null;
+
+ const serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let securityInfo = serhelper.deserializeObject(securityInfoAsString);
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+
+ return securityInfo;
+}
+
+/**
+ * Returns a string with detailed information about the certificate validation
+ * failure from the specified URI that can be used to send a report.
+ */
+function getDetailedCertErrorInfo(location, securityInfo) {
+ if (!securityInfo)
+ return "";
+
+ let certErrorDetails = location;
+ let code = securityInfo.errorCode;
+ let errors = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+
+ certErrorDetails += "\r\n\r\n" + errors.getErrorMessage(errors.getXPCOMFromNSSError(code));
+
+ const sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+ // SiteSecurityService uses different storage if the channel is
+ // private. Thus we must give isSecureHost correct flags or we
+ // might get incorrect results.
+ let flags = PrivateBrowsingUtils.isWindowPrivate(window) ?
+ Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
+
+ let uri = Services.io.newURI(location, null, null);
+
+ let hasHSTS = sss.isSecureHost(sss.HEADER_HSTS, uri.host, flags);
+ let hasHPKP = sss.isSecureHost(sss.HEADER_HPKP, uri.host, flags);
+ certErrorDetails += "\r\n\r\n" +
+ gNavigatorBundle.getFormattedString("certErrorDetailsHSTS.label",
+ [hasHSTS]);
+ certErrorDetails += "\r\n" +
+ gNavigatorBundle.getFormattedString("certErrorDetailsKeyPinning.label",
+ [hasHPKP]);
+
+ let certChain = "";
+ if (securityInfo.failedCertChain) {
+ let certs = securityInfo.failedCertChain.getEnumerator();
+ while (certs.hasMoreElements()) {
+ let cert = certs.getNext();
+ cert.QueryInterface(Ci.nsIX509Cert);
+ certChain += getPEMString(cert);
+ }
+ }
+
+ certErrorDetails += "\r\n\r\n" +
+ gNavigatorBundle.getString("certErrorDetailsCertChain.label") +
+ "\r\n\r\n" + certChain;
+
+ return certErrorDetails;
+}
+
+// TODO: can we pull getDERString and getPEMString in from pippki.js instead of
+// duplicating them here?
+function getDERString(cert)
+{
+ var length = {};
+ var derArray = cert.getRawDER(length);
+ var derString = '';
+ for (var i = 0; i < derArray.length; i++) {
+ derString += String.fromCharCode(derArray[i]);
+ }
+ return derString;
+}
+
+function getPEMString(cert)
+{
+ var derb64 = btoa(getDERString(cert));
+ // Wrap the Base64 string into lines of 64 characters,
+ // with CRLF line breaks (as specified in RFC 1421).
+ var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
+ return "-----BEGIN CERTIFICATE-----\r\n"
+ + wrapped
+ + "\r\n-----END CERTIFICATE-----\r\n";
+}
+
+var PrintPreviewListener = {
+ _printPreviewTab: null,
+ _tabBeforePrintPreview: null,
+ _simplifyPageTab: null,
+
+ getPrintPreviewBrowser: function () {
+ if (!this._printPreviewTab) {
+ let browser = gBrowser.selectedTab.linkedBrowser;
+ let forceNotRemote = gMultiProcessBrowser && !browser.isRemoteBrowser;
+ this._tabBeforePrintPreview = gBrowser.selectedTab;
+ this._printPreviewTab = gBrowser.loadOneTab("about:blank",
+ { inBackground: false,
+ forceNotRemote,
+ relatedBrowser: browser });
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ return gBrowser.getBrowserForTab(this._printPreviewTab);
+ },
+ createSimplifiedBrowser: function () {
+ this._simplifyPageTab = gBrowser.loadOneTab("about:blank",
+ { inBackground: true });
+ return this.getSimplifiedSourceBrowser();
+ },
+ getSourceBrowser: function () {
+ return this._tabBeforePrintPreview ?
+ this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
+ },
+ getSimplifiedSourceBrowser: function () {
+ return this._simplifyPageTab ?
+ gBrowser.getBrowserForTab(this._simplifyPageTab) : null;
+ },
+ getNavToolbox: function () {
+ return gNavToolbox;
+ },
+ onEnter: function () {
+ // We might have accidentally switched tabs since the user invoked print
+ // preview
+ if (gBrowser.selectedTab != this._printPreviewTab) {
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ gInPrintPreviewMode = true;
+ this._toggleAffectedChrome();
+ },
+ onExit: function () {
+ gBrowser.selectedTab = this._tabBeforePrintPreview;
+ this._tabBeforePrintPreview = null;
+ gInPrintPreviewMode = false;
+ this._toggleAffectedChrome();
+ if (this._simplifyPageTab) {
+ gBrowser.removeTab(this._simplifyPageTab);
+ this._simplifyPageTab = null;
+ }
+ gBrowser.removeTab(this._printPreviewTab);
+ gBrowser.deactivatePrintPreviewBrowsers();
+ this._printPreviewTab = null;
+ },
+ _toggleAffectedChrome: function () {
+ gNavToolbox.collapsed = gInPrintPreviewMode;
+
+ if (gInPrintPreviewMode)
+ this._hideChrome();
+ else
+ this._showChrome();
+
+ TabsInTitlebar.allowedBy("print-preview", !gInPrintPreviewMode);
+ },
+ _hideChrome: function () {
+ this._chromeState = {};
+
+ this._chromeState.sidebarOpen = SidebarUI.isOpen;
+ this._sidebarCommand = SidebarUI.currentID;
+ SidebarUI.hide();
+
+ var notificationBox = gBrowser.getNotificationBox();
+ this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
+ notificationBox.notificationsHidden = true;
+
+ gBrowser.updateWindowResizers();
+
+ this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
+ if (gFindBarInitialized)
+ gFindBar.close();
+
+ var globalNotificationBox = document.getElementById("global-notificationbox");
+ this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden;
+ globalNotificationBox.notificationsHidden = true;
+
+ this._chromeState.syncNotificationsOpen = false;
+ var syncNotifications = document.getElementById("sync-notifications");
+ if (syncNotifications) {
+ this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden;
+ syncNotifications.notificationsHidden = true;
+ }
+ },
+ _showChrome: function () {
+ if (this._chromeState.notificationsOpen)
+ gBrowser.getNotificationBox().notificationsHidden = false;
+
+ if (this._chromeState.findOpen)
+ gFindBar.open();
+
+ if (this._chromeState.globalNotificationsOpen)
+ document.getElementById("global-notificationbox").notificationsHidden = false;
+
+ if (this._chromeState.syncNotificationsOpen)
+ document.getElementById("sync-notifications").notificationsHidden = false;
+
+ if (this._chromeState.sidebarOpen)
+ SidebarUI.show(this._sidebarCommand);
+ },
+
+ activateBrowser(browser) {
+ gBrowser.activateBrowserForPrintPreview(browser);
+ },
+}
+
+function getMarkupDocumentViewer()
+{
+ return gBrowser.markupDocumentViewer;
+}
+
+// This function is obsolete. Newer code should use <tooltip page="true"/> instead.
+function FillInHTMLTooltip(tipElement)
+{
+ document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement);
+}
+
+var browserDragAndDrop = {
+ canDropLink: aEvent => Services.droppedLinkHandler.canDropLink(aEvent, true),
+
+ dragOver: function (aEvent)
+ {
+ if (this.canDropLink(aEvent)) {
+ aEvent.preventDefault();
+ }
+ },
+
+ dropLinks: function (aEvent, aDisallowInherit) {
+ return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
+ }
+};
+
+var homeButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ // disallow setting home pages that inherit the principal
+ let links = browserDragAndDrop.dropLinks(aEvent, true);
+ if (links.length) {
+ setTimeout(openHomeDialog, 0, links.map(link => link.url).join("|"));
+ }
+ },
+
+ onDragOver: function (aEvent)
+ {
+ if (gPrefService.prefIsLocked("browser.startup.homepage")) {
+ return;
+ }
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+function openHomeDialog(aURL)
+{
+ var promptTitle = gNavigatorBundle.getString("droponhometitle");
+ var promptMsg;
+ if (aURL.includes("|")) {
+ promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
+ } else {
+ promptMsg = gNavigatorBundle.getString("droponhomemsg");
+ }
+
+ var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg,
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null, null, null, null, {value:0});
+
+ if (pressedVal == 0) {
+ try {
+ var homepageStr = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ homepageStr.data = aURL;
+ gPrefService.setComplexValue("browser.startup.homepage",
+ Components.interfaces.nsISupportsString, homepageStr);
+ } catch (ex) {
+ dump("Failed to set the home page.\n"+ex+"\n");
+ }
+ }
+}
+
+var newTabButtonObserver = {
+ onDragOver(aEvent) {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit(aEvent) {},
+ onDrop: Task.async(function* (aEvent) {
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ for (let link of links) {
+ if (link.url) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ // Allow third-party services to fixup this URL.
+ openNewTabWith(data.url, null, data.postData, aEvent, true);
+ }
+ }
+ })
+}
+
+var newWindowButtonObserver = {
+ onDragOver(aEvent) {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit(aEvent) {},
+ onDrop: Task.async(function* (aEvent) {
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ for (let link of links) {
+ if (link.url) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ // Allow third-party services to fixup this URL.
+ openNewWindowWith(data.url, null, data.postData, true);
+ }
+ }
+ })
+}
+
+const DOMLinkHandler = {
+ init: function() {
+ let mm = window.messageManager;
+ mm.addMessageListener("Link:AddFeed", this);
+ mm.addMessageListener("Link:SetIcon", this);
+ mm.addMessageListener("Link:AddSearch", this);
+ },
+
+ receiveMessage: function (aMsg) {
+ switch (aMsg.name) {
+ case "Link:AddFeed":
+ let link = {type: aMsg.data.type, href: aMsg.data.href, title: aMsg.data.title};
+ FeedHandler.addFeed(link, aMsg.target);
+ break;
+
+ case "Link:SetIcon":
+ this.setIcon(aMsg.target, aMsg.data.url, aMsg.data.loadingPrincipal);
+ break;
+
+ case "Link:AddSearch":
+ this.addSearch(aMsg.target, aMsg.data.engine, aMsg.data.url);
+ break;
+ }
+ },
+
+ setIcon: function(aBrowser, aURL, aLoadingPrincipal) {
+ if (gBrowser.isFailedIcon(aURL))
+ return false;
+
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ if (!tab)
+ return false;
+
+ gBrowser.setIcon(tab, aURL, aLoadingPrincipal);
+ return true;
+ },
+
+ addSearch: function(aBrowser, aEngine, aURL) {
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ if (!tab)
+ return;
+
+ BrowserSearch.addEngine(aBrowser, aEngine, makeURI(aURL));
+ },
+}
+
+const BrowserSearch = {
+ addEngine: function(browser, engine, uri) {
+ // Check to see whether we've already added an engine with this title
+ if (browser.engines) {
+ if (browser.engines.some(e => e.title == engine.title))
+ return;
+ }
+
+ var hidden = false;
+ // If this engine (identified by title) is already in the list, add it
+ // to the list of hidden engines rather than to the main list.
+ // XXX This will need to be changed when engines are identified by URL;
+ // see bug 335102.
+ if (Services.search.getEngineByName(engine.title))
+ hidden = true;
+
+ var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
+
+ engines.push({ uri: engine.href,
+ title: engine.title,
+ get icon() { return browser.mIconURL; }
+ });
+
+ if (hidden)
+ browser.hiddenEngines = engines;
+ else {
+ browser.engines = engines;
+ if (browser == gBrowser.selectedBrowser)
+ this.updateOpenSearchBadge();
+ }
+ },
+
+ /**
+ * Update the browser UI to show whether or not additional engines are
+ * available when a page is loaded or the user switches tabs to a page that
+ * has search engines.
+ */
+ updateOpenSearchBadge: function() {
+ var searchBar = this.searchBar;
+ if (!searchBar)
+ return;
+
+ var engines = gBrowser.selectedBrowser.engines;
+ if (engines && engines.length > 0)
+ searchBar.setAttribute("addengines", "true");
+ else
+ searchBar.removeAttribute("addengines");
+ },
+
+ /**
+ * Gives focus to the search bar, if it is present on the toolbar, or loads
+ * the default engine's search form otherwise. For Mac, opens a new window
+ * or focuses an existing window, if necessary.
+ */
+ webSearch: function BrowserSearch_webSearch() {
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus();
+ win.BrowserSearch.webSearch();
+ } else {
+ // If there are no open browser windows, open a new one
+ var observer = function observer(subject, topic, data) {
+ if (subject == win) {
+ BrowserSearch.webSearch();
+ Services.obs.removeObserver(observer, "browser-delayed-startup-finished");
+ }
+ }
+ win = window.openDialog(getBrowserURL(), "_blank",
+ "chrome,all,dialog=no", "about:blank");
+ Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
+ }
+ return;
+ }
+
+ let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
+ if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
+ focusAndSelectUrlBar();
+ }
+ };
+
+ let searchBar = this.searchBar;
+ let placement = CustomizableUI.getPlacementOfWidget("search-container");
+ let focusSearchBar = () => {
+ searchBar = this.searchBar;
+ searchBar.select();
+ focusUrlBarIfSearchFieldIsNotActive(searchBar);
+ };
+ if (placement && placement.area == CustomizableUI.AREA_PANEL) {
+ // The panel is not constructed until the first time it is shown.
+ PanelUI.show().then(focusSearchBar);
+ return;
+ }
+ if (placement && placement.area == CustomizableUI.AREA_NAVBAR && searchBar &&
+ searchBar.parentNode.getAttribute("overflowedItem") == "true") {
+ let navBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ navBar.overflowable.show().then(() => {
+ focusSearchBar();
+ });
+ return;
+ }
+ if (searchBar) {
+ if (window.fullScreen)
+ FullScreen.showNavToolbox();
+ searchBar.select();
+ }
+ focusUrlBarIfSearchFieldIsNotActive(searchBar);
+ },
+
+ /**
+ * Loads a search results page, given a set of search terms. Uses the current
+ * engine if the search bar is visible, or the default engine otherwise.
+ *
+ * @param searchText
+ * The search terms to use for the search.
+ *
+ * @param useNewTab
+ * Boolean indicating whether or not the search should load in a new
+ * tab.
+ *
+ * @param purpose [optional]
+ * A string meant to indicate the context of the search request. This
+ * allows the search service to provide a different nsISearchSubmission
+ * depending on e.g. where the search is triggered in the UI.
+ *
+ * @return engine The search engine used to perform a search, or null if no
+ * search was performed.
+ */
+ _loadSearch: function (searchText, useNewTab, purpose) {
+ let engine;
+
+ // If the search bar is visible, use the current engine, otherwise, fall
+ // back to the default engine.
+ if (isElementVisible(this.searchBar))
+ engine = Services.search.currentEngine;
+ else
+ engine = Services.search.defaultEngine;
+
+ let submission = engine.getSubmission(searchText, null, purpose); // HTML response
+
+ // getSubmission can return null if the engine doesn't have a URL
+ // with a text/html response type. This is unlikely (since
+ // SearchService._addEngineToStore() should fail for such an engine),
+ // but let's be on the safe side.
+ if (!submission) {
+ return null;
+ }
+
+ let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
+ openLinkIn(submission.uri.spec,
+ useNewTab ? "tab" : "current",
+ { postData: submission.postData,
+ inBackground: inBackground,
+ relatedToCurrent: true });
+
+ return engine;
+ },
+
+ /**
+ * Just like _loadSearch, but preserving an old API.
+ *
+ * @return string Name of the search engine used to perform a search or null
+ * if a search was not performed.
+ */
+ loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
+ let engine = BrowserSearch._loadSearch(searchText, useNewTab, purpose);
+ if (!engine) {
+ return null;
+ }
+ return engine.name;
+ },
+
+ /**
+ * Perform a search initiated from the context menu.
+ *
+ * This should only be called from the context menu. See
+ * BrowserSearch.loadSearch for the preferred API.
+ */
+ loadSearchFromContext: function (terms) {
+ let engine = BrowserSearch._loadSearch(terms, true, "contextmenu");
+ if (engine) {
+ BrowserSearch.recordSearchInTelemetry(engine, "contextmenu");
+ }
+ },
+
+ pasteAndSearch: function (event) {
+ BrowserSearch.searchBar.select();
+ goDoCommand("cmd_paste");
+ BrowserSearch.searchBar.handleSearchCommand(event);
+ },
+
+ /**
+ * Returns the search bar element if it is present in the toolbar, null otherwise.
+ */
+ get searchBar() {
+ return document.getElementById("searchbar");
+ },
+
+ get searchEnginesURL() {
+ return formatURL("browser.search.searchEnginesURL", true);
+ },
+
+ loadAddEngines: function BrowserSearch_loadAddEngines() {
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+ openUILinkIn(this.searchEnginesURL, where);
+ },
+
+ _getSearchEngineId: function (engine) {
+ if (engine && engine.identifier) {
+ return engine.identifier;
+ }
+
+ if (!engine || (engine.name === undefined) ||
+ !Services.prefs.getBoolPref("toolkit.telemetry.enabled"))
+ return "other";
+
+ return "other-" + engine.name;
+ },
+
+ /**
+ * Helper to record a search with Telemetry.
+ *
+ * Telemetry records only search counts and nothing pertaining to the search itself.
+ *
+ * @param engine
+ * (nsISearchEngine) The engine handling the search.
+ * @param source
+ * (string) Where the search originated from. See BrowserUsageTelemetry for
+ * allowed values.
+ * @param details [optional]
+ * An optional parameter passed to |BrowserUsageTelemetry.recordSearch|.
+ * See its documentation for allowed options.
+ * Additionally, if the search was a suggested search, |details.selection|
+ * indicates where the item was in the suggestion list and how the user
+ * selected it: {selection: {index: The selected index, kind: "key" or "mouse"}}
+ */
+ recordSearchInTelemetry: function (engine, source, details={}) {
+ BrowserUITelemetry.countSearchEvent(source, null, details.selection);
+ try {
+ BrowserUsageTelemetry.recordSearch(engine, source, details);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+
+ /**
+ * Helper to record a one-off search with Telemetry.
+ *
+ * Telemetry records only search counts and nothing pertaining to the search itself.
+ *
+ * @param engine
+ * (nsISearchEngine) The engine handling the search.
+ * @param source
+ * (string) Where the search originated from. See BrowserUsageTelemetry for
+ * allowed values.
+ * @param type
+ * (string) Indicates how the user selected the search item.
+ * @param where
+ * (string) Where was the search link opened (e.g. new tab, current tab, ..).
+ */
+ recordOneoffSearchInTelemetry: function (engine, source, type, where) {
+ let id = this._getSearchEngineId(engine) + "." + source;
+ BrowserUITelemetry.countOneoffSearchEvent(id, type, where);
+ try {
+ const details = {type, isOneOff: true};
+ BrowserUsageTelemetry.recordSearch(engine, source, details);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+};
+
+XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
+
+function FillHistoryMenu(aParent) {
+ // Lazily add the hover listeners on first showing and never remove them
+ if (!aParent.hasStatusListener) {
+ // Show history item's uri in the status bar when hovering, and clear on exit
+ aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
+ // Only the current page should have the checked attribute, so skip it
+ if (!aEvent.target.hasAttribute("checked"))
+ XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
+ }, false);
+ aParent.addEventListener("DOMMenuItemInactive", function() {
+ XULBrowserWindow.setOverLink("");
+ }, false);
+
+ aParent.hasStatusListener = true;
+ }
+
+ // Remove old entries if any
+ let children = aParent.childNodes;
+ for (var i = children.length - 1; i >= 0; --i) {
+ if (children[i].hasAttribute("index"))
+ aParent.removeChild(children[i]);
+ }
+
+ const MAX_HISTORY_MENU_ITEMS = 15;
+
+ const tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
+ const tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
+ const tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
+
+ function updateSessionHistory(sessionHistory, initial)
+ {
+ let count = sessionHistory.entries.length;
+
+ if (!initial) {
+ if (count <= 1) {
+ // if there is only one entry now, close the popup.
+ aParent.hidePopup();
+ return;
+ } else if (aParent.id != "backForwardMenu" && !aParent.parentNode.open) {
+ // if the popup wasn't open before, but now needs to be, reopen the menu.
+ // It should trigger FillHistoryMenu again. This might happen with the
+ // delay from click-and-hold menus but skip this for the context menu
+ // (backForwardMenu) rather than figuring out how the menu should be
+ // positioned and opened as it is an extreme edgecase.
+ aParent.parentNode.open = true;
+ return;
+ }
+ }
+
+ let index = sessionHistory.index;
+ let half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
+ let start = Math.max(index - half_length, 0);
+ let end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count);
+ if (end == count) {
+ start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
+ }
+
+ let existingIndex = 0;
+
+ for (let j = end - 1; j >= start; j--) {
+ let entry = sessionHistory.entries[j];
+ let uri = entry.url;
+
+ let item = existingIndex < children.length ?
+ children[existingIndex] : document.createElement("menuitem");
+
+ let entryURI = BrowserUtils.makeURI(entry.url, entry.charset, null);
+ item.setAttribute("uri", uri);
+ item.setAttribute("label", entry.title || uri);
+ item.setAttribute("index", j);
+
+ // Cache this so that gotoHistoryIndex doesn't need the original index
+ item.setAttribute("historyindex", j - index);
+
+ if (j != index) {
+ PlacesUtils.favicons.getFaviconURLForPage(entryURI, function (aURI) {
+ if (aURI) {
+ let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
+ item.style.listStyleImage = "url(" + iconURL + ")";
+ }
+ });
+ }
+
+ if (j < index) {
+ item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipBack);
+ } else if (j == index) {
+ item.setAttribute("type", "radio");
+ item.setAttribute("checked", "true");
+ item.className = "unified-nav-current";
+ item.setAttribute("tooltiptext", tooltipCurrent);
+ } else {
+ item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipForward);
+ }
+
+ if (!item.parentNode) {
+ aParent.appendChild(item);
+ }
+
+ existingIndex++;
+ }
+
+ if (!initial) {
+ let existingLength = children.length;
+ while (existingIndex < existingLength) {
+ aParent.removeChild(aParent.lastChild);
+ existingIndex++;
+ }
+ }
+ }
+
+ let sessionHistory = SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory);
+ if (!sessionHistory)
+ return false;
+
+ // don't display the popup for a single item
+ if (sessionHistory.entries.length <= 1)
+ return false;
+
+ updateSessionHistory(sessionHistory, true);
+ return true;
+}
+
+function addToUrlbarHistory(aUrlToAdd) {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window) &&
+ aUrlToAdd &&
+ !aUrlToAdd.includes(" ") &&
+ !/[\x00-\x1F]/.test(aUrlToAdd))
+ PlacesUIUtils.markPageAsTyped(aUrlToAdd);
+}
+
+function BrowserDownloadsUI()
+{
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ openUILinkIn("about:downloads", "tab");
+ } else {
+ PlacesCommandHook.showPlacesOrganizer("Downloads");
+ }
+}
+
+function toOpenWindowByType(inType, uri, features)
+{
+ var topWindow = Services.wm.getMostRecentWindow(inType);
+
+ if (topWindow)
+ topWindow.focus();
+ else if (features)
+ window.open(uri, "_blank", features);
+ else
+ window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
+}
+
+function OpenBrowserWindow(options)
+{
+ var telemetryObj = {};
+ TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
+
+ function newDocumentShown(doc, topic, data) {
+ if (topic == "document-shown" &&
+ doc != document &&
+ doc.defaultView == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ Services.obs.removeObserver(windowClosed, "domwindowclosed");
+ TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
+ }
+ }
+
+ function windowClosed(subject) {
+ if (subject == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ Services.obs.removeObserver(windowClosed, "domwindowclosed");
+ }
+ }
+
+ // Make sure to remove the 'document-shown' observer in case the window
+ // is being closed right after it was opened to avoid leaking.
+ Services.obs.addObserver(newDocumentShown, "document-shown", false);
+ Services.obs.addObserver(windowClosed, "domwindowclosed", false);
+
+ var charsetArg = new String();
+ var handler = Components.classes["@mozilla.org/browser/clh;1"]
+ .getService(Components.interfaces.nsIBrowserHandler);
+ var defaultArgs = handler.defaultArgs;
+ var wintype = document.documentElement.getAttribute('windowtype');
+
+ var extraFeatures = "";
+ if (options && options.private) {
+ extraFeatures = ",private";
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Force the new window to load about:privatebrowsing instead of the default home page
+ defaultArgs = "about:privatebrowsing";
+ }
+ } else {
+ extraFeatures = ",non-private";
+ }
+
+ if (options && options.remote) {
+ extraFeatures += ",remote";
+ } else if (options && options.remote === false) {
+ extraFeatures += ",non-remote";
+ }
+
+ // if and only if the current window is a browser window and it has a document with a character
+ // set, then extract the current charset menu setting from the current document and use it to
+ // initialize the new browser window...
+ var win;
+ if (window && (wintype == "navigator:browser") && window.content && window.content.document)
+ {
+ var DocCharset = window.content.document.characterSet;
+ charsetArg = "charset="+DocCharset;
+
+ // we should "inherit" the charset menu setting in a new window
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg);
+ }
+ else // forget about the charset information.
+ {
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs);
+ }
+
+ return win;
+}
+
+// Only here for backwards compat, we should remove this soon
+function BrowserCustomizeToolbar() {
+ gCustomizeMode.enter();
+}
+
+/**
+ * Update the global flag that tracks whether or not any edit UI (the Edit menu,
+ * edit-related items in the context menu, and edit-related toolbar buttons
+ * is visible, then update the edit commands' enabled state accordingly. We use
+ * this flag to skip updating the edit commands on focus or selection changes
+ * when no UI is visible to improve performance (including pageload performance,
+ * since focus changes when you load a new page).
+ *
+ * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
+ * enabled state so the UI will reflect it appropriately.
+ *
+ * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
+ * still work and just lazily disable them as needed when the user presses a
+ * shortcut.
+ *
+ * This doesn't work on Mac, since Mac menus flash when users press their
+ * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
+ * and we need to always update the edit commands. Thus on Mac this function
+ * is a no op.
+ */
+function updateEditUIVisibility()
+{
+ if (AppConstants.platform == "macosx")
+ return;
+
+ let editMenuPopupState = document.getElementById("menu_EditPopup").state;
+ let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state;
+ let placesContextMenuPopupState = document.getElementById("placesContext").state;
+
+ // The UI is visible if the Edit menu is opening or open, if the context menu
+ // is open, or if the toolbar has been customized to include the Cut, Copy,
+ // or Paste toolbar buttons.
+ gEditUIVisible = editMenuPopupState == "showing" ||
+ editMenuPopupState == "open" ||
+ contextMenuPopupState == "showing" ||
+ contextMenuPopupState == "open" ||
+ placesContextMenuPopupState == "showing" ||
+ placesContextMenuPopupState == "open" ||
+ document.getElementById("edit-controls") ? true : false;
+
+ // If UI is visible, update the edit commands' enabled state to reflect
+ // whether or not they are actually enabled for the current focus/selection.
+ if (gEditUIVisible)
+ goUpdateGlobalEditMenuItems();
+
+ // Otherwise, enable all commands, so that keyboard shortcuts still work,
+ // then lazily determine their actual enabled state when the user presses
+ // a keyboard shortcut.
+ else {
+ goSetCommandEnabled("cmd_undo", true);
+ goSetCommandEnabled("cmd_redo", true);
+ goSetCommandEnabled("cmd_cut", true);
+ goSetCommandEnabled("cmd_copy", true);
+ goSetCommandEnabled("cmd_paste", true);
+ goSetCommandEnabled("cmd_selectAll", true);
+ goSetCommandEnabled("cmd_delete", true);
+ goSetCommandEnabled("cmd_switchTextDirection", true);
+ }
+}
+
+/**
+ * Opens a new tab with the userContextId specified as an attribute of
+ * sourceEvent. This attribute is propagated to the top level originAttributes
+ * living on the tab's docShell.
+ *
+ * @param event
+ * A click event on a userContext File Menu option
+ */
+function openNewUserContextTab(event)
+{
+ openUILinkIn(BROWSER_NEW_TAB_URL, "tab", {
+ userContextId: parseInt(event.target.getAttribute('data-usercontextid')),
+ });
+}
+
+/**
+ * Updates File Menu User Context UI visibility depending on
+ * privacy.userContext.enabled pref state.
+ */
+function updateUserContextUIVisibility()
+{
+ let menu = document.getElementById("menu_newUserContext");
+ menu.hidden = !Services.prefs.getBoolPref("privacy.userContext.enabled");
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ menu.setAttribute("disabled", "true");
+ }
+}
+
+/**
+ * Updates the User Context UI indicators if the browser is in a non-default context
+ */
+function updateUserContextUIIndicator()
+{
+ let hbox = document.getElementById("userContext-icons");
+
+ let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
+ if (!userContextId) {
+ hbox.setAttribute("data-identity-color", "");
+ hbox.hidden = true;
+ return;
+ }
+
+ let identity = ContextualIdentityService.getIdentityFromId(userContextId);
+ if (!identity) {
+ hbox.setAttribute("data-identity-color", "");
+ hbox.hidden = true;
+ return;
+ }
+
+ hbox.setAttribute("data-identity-color", identity.color);
+
+ let label = document.getElementById("userContext-label");
+ label.setAttribute("value", ContextualIdentityService.getUserContextLabel(userContextId));
+
+ let indicator = document.getElementById("userContext-indicator");
+ indicator.setAttribute("data-identity-icon", identity.icon);
+
+ hbox.hidden = false;
+}
+
+/**
+ * Makes the Character Encoding menu enabled or disabled as appropriate.
+ * To be called when the View menu or the app menu is opened.
+ */
+function updateCharacterEncodingMenuState()
+{
+ let charsetMenu = document.getElementById("charsetMenu");
+ // gBrowser is null on Mac when the menubar shows in the context of
+ // non-browser windows. The above elements may be null depending on
+ // what parts of the menubar are present. E.g. no app menu on Mac.
+ if (gBrowser && gBrowser.selectedBrowser.mayEnableCharacterEncodingMenu) {
+ if (charsetMenu) {
+ charsetMenu.removeAttribute("disabled");
+ }
+ } else if (charsetMenu) {
+ charsetMenu.setAttribute("disabled", "true");
+ }
+}
+
+var XULBrowserWindow = {
+ // Stored Status, Link and Loading values
+ status: "",
+ defaultStatus: "",
+ overLink: "",
+ startTime: 0,
+ statusText: "",
+ isBusy: false,
+ // Left here for add-on compatibility, see bug 752434
+ inContentWhitelist: [],
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsIWebProgressListener2) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsIXULBrowserWindow) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ get stopCommand () {
+ delete this.stopCommand;
+ return this.stopCommand = document.getElementById("Browser:Stop");
+ },
+ get reloadCommand () {
+ delete this.reloadCommand;
+ return this.reloadCommand = document.getElementById("Browser:Reload");
+ },
+ get statusTextField () {
+ return gBrowser.getStatusPanel();
+ },
+ get isImage () {
+ delete this.isImage;
+ return this.isImage = document.getElementById("isImage");
+ },
+ get canViewSource () {
+ delete this.canViewSource;
+ return this.canViewSource = document.getElementById("canViewSource");
+ },
+
+ init: function () {
+ // Initialize the security button's state and tooltip text.
+ var securityUI = gBrowser.securityUI;
+ this.onSecurityChange(null, null, securityUI.state, true);
+ },
+
+ setJSStatus: function () {
+ // unsupported
+ },
+
+ forceInitialBrowserRemote: function() {
+ let initBrowser =
+ document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
+ return initBrowser.frameLoader.tabParent;
+ },
+
+ forceInitialBrowserNonRemote: function(aOpener) {
+ let initBrowser =
+ document.getAnonymousElementByAttribute(gBrowser, "anonid", "initialBrowser");
+ gBrowser.updateBrowserRemoteness(initBrowser, false, aOpener);
+ },
+
+ setDefaultStatus: function (status) {
+ this.defaultStatus = status;
+ this.updateStatusField();
+ },
+
+ setOverLink: function (url, anchorElt) {
+ // Encode bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
+ encodeURIComponent);
+
+ if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
+ url = trimURL(url);
+
+ this.overLink = url;
+ LinkTargetDisplay.update();
+ },
+
+ showTooltip: function (x, y, tooltip, direction) {
+ if (Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService).
+ getCurrentSession()) {
+ return;
+ }
+
+ // The x,y coordinates are relative to the <browser> element using
+ // the chrome zoom level.
+ let elt = document.getElementById("remoteBrowserTooltip");
+ elt.label = tooltip;
+ elt.style.direction = direction;
+
+ let anchor = gBrowser.selectedBrowser;
+ elt.openPopupAtScreen(anchor.boxObject.screenX + x, anchor.boxObject.screenY + y, false, null);
+ },
+
+ hideTooltip: function () {
+ let elt = document.getElementById("remoteBrowserTooltip");
+ elt.hidePopup();
+ },
+
+ getTabCount: function () {
+ return gBrowser.tabs.length;
+ },
+
+ updateStatusField: function () {
+ var text, type, types = ["overLink"];
+ if (this._busyUI)
+ types.push("status");
+ types.push("defaultStatus");
+ for (type of types) {
+ text = this[type];
+ if (text)
+ break;
+ }
+
+ // check the current value so we don't trigger an attribute change
+ // and cause needless (slow!) UI updates
+ if (this.statusText != text) {
+ let field = this.statusTextField;
+ field.setAttribute("previoustype", field.getAttribute("type"));
+ field.setAttribute("type", type);
+ field.label = text;
+ field.setAttribute("crop", type == "overLink" ? "center" : "end");
+ this.statusText = text;
+ }
+ },
+
+ // Called before links are navigated to to allow us to retarget them if needed.
+ onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ let target = BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+ SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
+ return target;
+ },
+
+ // Check whether this URI should load in the current process
+ shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+ if (!gMultiProcessBrowser)
+ return true;
+
+ let browser = aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+
+ // Ignore loads that aren't in the main tabbrowser
+ if (browser.localName != "browser" || !browser.getTabBrowser || browser.getTabBrowser() != gBrowser)
+ return true;
+
+ if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+ E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
+ return false;
+ }
+
+ return true;
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ // Do nothing.
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ // This function fires only for the currently selected tab.
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ const nsIChannel = Ci.nsIChannel;
+
+ let browser = gBrowser.selectedBrowser;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (aRequest && aWebProgress.isTopLevel) {
+ // clear out feed data
+ browser.feeds = null;
+
+ // clear out search-engine data
+ browser.engines = null;
+ }
+
+ this.isBusy = true;
+
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this._busyUI = true;
+
+ // XXX: This needs to be based on window activity...
+ this.stopCommand.removeAttribute("disabled");
+ CombinedStopReload.switchToStop();
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ // This (thanks to the filter) is a network stop or the last
+ // request stop outside of loading the document, stop throbbers
+ // and progress bars and such
+ if (aRequest) {
+ let msg = "";
+ let location;
+ let canViewSource = true;
+ // Get the URI either from a channel or a pseudo-object
+ if (aRequest instanceof nsIChannel || "URI" in aRequest) {
+ location = aRequest.URI;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword" && aWebProgress.isTopLevel)
+ gBrowser.userTypedValue = null;
+
+ canViewSource = !Services.prefs.getBoolPref("view_source.tab") ||
+ location.scheme != "view-source";
+
+ if (location.spec != "about:blank") {
+ switch (aStatus) {
+ case Components.results.NS_ERROR_NET_TIMEOUT:
+ msg = gNavigatorBundle.getString("nv_timeout");
+ break;
+ }
+ }
+ }
+
+ this.status = "";
+ this.setDefaultStatus(msg);
+
+ // Disable menu entries for images, enable otherwise
+ if (browser.documentContentType && BrowserUtils.mimeTypeIsTextBased(browser.documentContentType)) {
+ this.isImage.removeAttribute('disabled');
+ } else {
+ canViewSource = false;
+ this.isImage.setAttribute('disabled', 'true');
+ }
+
+ if (canViewSource) {
+ this.canViewSource.removeAttribute('disabled');
+ } else {
+ this.canViewSource.setAttribute('disabled', 'true');
+ }
+ }
+
+ this.isBusy = false;
+
+ if (this._busyUI) {
+ this._busyUI = false;
+
+ this.stopCommand.setAttribute("disabled", "true");
+ CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest);
+ }
+ }
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var location = aLocationURI ? aLocationURI.spec : "";
+
+ // If displayed, hide the form validation popup.
+ FormValidationHandler.hidePopup();
+
+ let pageTooltip = document.getElementById("aHTMLTooltip");
+ let tooltipNode = pageTooltip.triggerNode;
+ if (tooltipNode) {
+ // Optimise for the common case
+ if (aWebProgress.isTopLevel) {
+ pageTooltip.hidePopup();
+ }
+ else {
+ for (let tooltipWindow = tooltipNode.ownerGlobal;
+ tooltipWindow != tooltipWindow.parent;
+ tooltipWindow = tooltipWindow.parent) {
+ if (tooltipWindow == aWebProgress.DOMWindow) {
+ pageTooltip.hidePopup();
+ break;
+ }
+ }
+ }
+ }
+
+ let browser = gBrowser.selectedBrowser;
+
+ // Disable menu entries for images, enable otherwise
+ if (browser.documentContentType && BrowserUtils.mimeTypeIsTextBased(browser.documentContentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+
+ this.hideOverLinkImmediately = true;
+ this.setOverLink("", null);
+ this.hideOverLinkImmediately = false;
+
+ // We should probably not do this if the value has changed since the user
+ // searched
+ // Update urlbar only if a new page was loaded on the primary content area
+ // Do not update urlbar if there was a subframe navigation
+
+ if (aWebProgress.isTopLevel) {
+ if ((location == "about:blank" && checkEmptyPageOrigin()) ||
+ location == "") { // Second condition is for new tabs, otherwise
+ // reload function is enabled until tab is refreshed.
+ this.reloadCommand.setAttribute("disabled", "true");
+ } else {
+ this.reloadCommand.removeAttribute("disabled");
+ }
+
+ URLBarSetURI(aLocationURI);
+
+ BookmarkingUI.onLocationChange();
+
+ gIdentityHandler.onLocationChange();
+
+ SocialUI.updateState();
+
+ UITour.onLocationChange(location);
+
+ gTabletModePageCounter.inc();
+
+ // Utility functions for disabling find
+ var shouldDisableFind = function shouldDisableFind(aDocument) {
+ let docElt = aDocument.documentElement;
+ return docElt && docElt.getAttribute("disablefastfind") == "true";
+ }
+
+ var disableFindCommands = function disableFindCommands(aDisable) {
+ let findCommands = [document.getElementById("cmd_find"),
+ document.getElementById("cmd_findAgain"),
+ document.getElementById("cmd_findPrevious")];
+ for (let elt of findCommands) {
+ if (aDisable)
+ elt.setAttribute("disabled", "true");
+ else
+ elt.removeAttribute("disabled");
+ }
+ }
+
+ var onContentRSChange = function onContentRSChange(e) {
+ if (e.target.readyState != "interactive" && e.target.readyState != "complete")
+ return;
+
+ e.target.removeEventListener("readystatechange", onContentRSChange);
+ disableFindCommands(shouldDisableFind(e.target));
+ }
+
+ // Disable find commands in documents that ask for them to be disabled.
+ if (!gMultiProcessBrowser && aLocationURI &&
+ (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) {
+ // Don't need to re-enable/disable find commands for same-document location changes
+ // (e.g. the replaceStates in about:addons)
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+ if (content.document.readyState == "interactive" || content.document.readyState == "complete")
+ disableFindCommands(shouldDisableFind(content.document));
+ else {
+ content.document.addEventListener("readystatechange", onContentRSChange);
+ }
+ }
+ } else
+ disableFindCommands(false);
+
+ // Try not to instantiate gCustomizeMode as much as possible,
+ // so don't use CustomizeMode.jsm to check for URI or customizing.
+ if (location == "about:blank" &&
+ gBrowser.selectedTab.hasAttribute("customizemode")) {
+ gCustomizeMode.enter();
+ } else if (CustomizationHandler.isEnteringCustomizeMode ||
+ CustomizationHandler.isCustomizing()) {
+ gCustomizeMode.exit();
+ }
+ }
+ UpdateBackForwardCommands(gBrowser.webNavigation);
+ ReaderParent.updateReaderButton(gBrowser.selectedBrowser);
+
+ gGestureSupport.restoreRotationState();
+
+ // See bug 358202, when tabs are switched during a drag operation,
+ // timers don't fire on windows (bug 203573)
+ if (aRequest)
+ setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0);
+ else
+ this.asyncUpdateUI();
+
+ if (AppConstants.MOZ_CRASHREPORTER && aLocationURI) {
+ let uri = aLocationURI.clone();
+ try {
+ // If the current URI contains a username/password, remove it.
+ uri.userPass = "";
+ } catch (ex) { /* Ignore failures on about: URIs. */ }
+
+ try {
+ gCrashReporter.annotateCrashReport("URL", uri.spec);
+ } catch (ex) {
+ // Don't make noise when the crash reporter is built but not enabled.
+ if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
+ throw ex;
+ }
+ }
+ }
+ },
+
+ asyncUpdateUI: function () {
+ FeedHandler.updateFeeds();
+ BrowserSearch.updateOpenSearchBadge();
+ },
+
+ // Left here for add-on compatibility, see bug 752434
+ hideChromeForLocation: function() {},
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ this.status = aMessage;
+ this.updateStatusField();
+ },
+
+ // Properties used to cache security state used to update the UI
+ _state: null,
+ _lastLocation: null,
+
+ // This is called in multiple ways:
+ // 1. Due to the nsIWebProgressListener.onSecurityChange notification.
+ // 2. Called by tabbrowser.xml when updating the current browser.
+ // 3. Called directly during this object's initializations.
+ // aRequest will be null always in case 2 and 3, and sometimes in case 1 (for
+ // instance, there won't be a request when STATE_BLOCKED_TRACKING_CONTENT is observed).
+ onSecurityChange: function (aWebProgress, aRequest, aState, aIsSimulated) {
+ // Don't need to do anything if the data we use to update the UI hasn't
+ // changed
+ let uri = gBrowser.currentURI;
+ let spec = uri.spec;
+ if (this._state == aState &&
+ this._lastLocation == spec)
+ return;
+ this._state = aState;
+ this._lastLocation = spec;
+
+ if (typeof(aIsSimulated) != "boolean" && typeof(aIsSimulated) != "undefined") {
+ throw "onSecurityChange: aIsSimulated receieved an unexpected type";
+ }
+
+ // Make sure the "https" part of the URL is striked out or not,
+ // depending on the current mixed active content blocking state.
+ gURLBar.formatValue();
+
+ try {
+ uri = Services.uriFixup.createExposableURI(uri);
+ } catch (e) {}
+ gIdentityHandler.updateIdentity(this._state, uri);
+ TrackingProtection.onSecurityChange(this._state, aIsSimulated);
+ },
+
+ // simulate all change notifications after switching tabs
+ onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
+ if (FullZoom.updateBackgroundTabs)
+ FullZoom.onLocationChange(gBrowser.currentURI, true);
+ var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
+ // use a pseudo-object instead of a (potentially nonexistent) channel for getting
+ // a correct error message - and make sure that the UI is always either in
+ // loading (STATE_START) or done (STATE_STOP) mode
+ this.onStateChange(
+ gBrowser.webProgress,
+ { URI: gBrowser.currentURI },
+ loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START,
+ aStatus
+ );
+ // status message and progress value are undefined if we're done with loading
+ if (loadingDone)
+ return;
+ this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
+ }
+};
+
+var LinkTargetDisplay = {
+ get DELAY_SHOW() {
+ delete this.DELAY_SHOW;
+ return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay");
+ },
+
+ DELAY_HIDE: 250,
+ _timer: 0,
+
+ get _isVisible () {
+ return XULBrowserWindow.statusTextField.label != "";
+ },
+
+ update: function () {
+ clearTimeout(this._timer);
+ window.removeEventListener("mousemove", this, true);
+
+ if (!XULBrowserWindow.overLink) {
+ if (XULBrowserWindow.hideOverLinkImmediately)
+ this._hide();
+ else
+ this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
+ return;
+ }
+
+ if (this._isVisible) {
+ XULBrowserWindow.updateStatusField();
+ } else {
+ // Let the display appear when the mouse doesn't move within the delay
+ this._showDelayed();
+ window.addEventListener("mousemove", this, true);
+ }
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "mousemove":
+ // Restart the delay since the mouse was moved
+ clearTimeout(this._timer);
+ this._showDelayed();
+ break;
+ }
+ },
+
+ _showDelayed: function () {
+ this._timer = setTimeout(function (self) {
+ XULBrowserWindow.updateStatusField();
+ window.removeEventListener("mousemove", self, true);
+ }, this.DELAY_SHOW, this);
+ },
+
+ _hide: function () {
+ clearTimeout(this._timer);
+
+ XULBrowserWindow.updateStatusField();
+ }
+};
+
+var CombinedStopReload = {
+ init: function () {
+ if (this._initialized)
+ return;
+
+ let reload = document.getElementById("urlbar-reload-button");
+ let stop = document.getElementById("urlbar-stop-button");
+ if (!stop || !reload || reload.nextSibling != stop)
+ return;
+
+ this._initialized = true;
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ reload.setAttribute("displaystop", "true");
+ stop.addEventListener("click", this, false);
+ this.reload = reload;
+ this.stop = stop;
+ },
+
+ uninit: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this._initialized = false;
+ this.stop.removeEventListener("click", this, false);
+ this.reload = null;
+ this.stop = null;
+ },
+
+ handleEvent: function (event) {
+ // the only event we listen to is "click" on the stop button
+ if (event.button == 0 &&
+ !this.stop.disabled)
+ this._stopClicked = true;
+ },
+
+ switchToStop: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this.reload.setAttribute("displaystop", "true");
+ },
+
+ switchToReload: function (aDelay) {
+ if (!this._initialized)
+ return;
+
+ this.reload.removeAttribute("displaystop");
+
+ if (!aDelay || this._stopClicked) {
+ this._stopClicked = false;
+ this._cancelTransition();
+ this.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ return;
+ }
+
+ if (this._timer)
+ return;
+
+ // Temporarily disable the reload button to prevent the user from
+ // accidentally reloading the page when intending to click the stop button
+ this.reload.disabled = true;
+ this._timer = setTimeout(function (self) {
+ self._timer = 0;
+ self.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ }, 650, this);
+ },
+
+ _cancelTransition: function () {
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = 0;
+ }
+ }
+};
+
+var TabsProgressListener = {
+ // Keep track of which browsers we've started load timers for, since
+ // we won't see STATE_START events for pre-rendered tabs.
+ _startedLoadTimer: new WeakSet(),
+
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ // Collect telemetry data about tab load times.
+ if (aWebProgress.isTopLevel && (!aRequest.originalURI || aRequest.originalURI.spec.scheme != "about")) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
+ this._startedLoadTimer.add(aBrowser);
+ TelemetryStopwatch.start("FX_PAGE_LOAD_MS", aBrowser);
+ Services.telemetry.getHistogramById("FX_TOTAL_TOP_VISITS").add(true);
+ } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ this._startedLoadTimer.has(aBrowser)) {
+ this._startedLoadTimer.delete(aBrowser);
+ TelemetryStopwatch.finish("FX_PAGE_LOAD_MS", aBrowser);
+ }
+ } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStatus == Cr.NS_BINDING_ABORTED &&
+ this._startedLoadTimer.has(aBrowser)) {
+ this._startedLoadTimer.delete(aBrowser);
+ TelemetryStopwatch.cancel("FX_PAGE_LOAD_MS", aBrowser);
+ }
+ }
+
+ // Attach a listener to watch for "click" events bubbling up from error
+ // pages and other similar pages (like about:newtab). This lets us fix bugs
+ // like 401575 which require error page UI to do privileged things, without
+ // letting error pages have any privilege themselves.
+ // We can't look for this during onLocationChange since at that point the
+ // document URI is not yet the about:-uri of the error page.
+
+ let isRemoteBrowser = aBrowser.isRemoteBrowser;
+ // We check isRemoteBrowser here to avoid requesting the doc CPOW
+ let doc = isRemoteBrowser ? null : aWebProgress.DOMWindow.document;
+
+ if (!isRemoteBrowser &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ Components.isSuccessCode(aStatus) &&
+ doc.documentURI.startsWith("about:") &&
+ !doc.documentURI.toLowerCase().startsWith("about:blank") &&
+ !doc.documentURI.toLowerCase().startsWith("about:home") &&
+ !doc.documentElement.hasAttribute("hasBrowserHandlers")) {
+ // STATE_STOP may be received twice for documents, thus store an
+ // attribute to ensure handling it just once.
+ doc.documentElement.setAttribute("hasBrowserHandlers", "true");
+ aBrowser.addEventListener("click", BrowserOnClick, true);
+ aBrowser.addEventListener("pagehide", function onPageHide(event) {
+ if (event.target.defaultView.frameElement)
+ return;
+ aBrowser.removeEventListener("click", BrowserOnClick, true);
+ aBrowser.removeEventListener("pagehide", onPageHide, true);
+ if (event.target.documentElement)
+ event.target.documentElement.removeAttribute("hasBrowserHandlers");
+ }, true);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ // Filter out location changes caused by anchor navigation
+ // or history.push/pop/replaceState.
+ if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
+ // Reader mode actually cares about these:
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("Reader:PushState", {isArticle: gBrowser.selectedBrowser.isArticle});
+ return;
+ }
+
+ // Filter out location changes in sub documents.
+ if (!aWebProgress.isTopLevel)
+ return;
+
+ // Only need to call locationChange if the PopupNotifications object
+ // for this window has already been initialized (i.e. its getter no
+ // longer exists)
+ if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get)
+ PopupNotifications.locationChange(aBrowser);
+
+ let tab = gBrowser.getTabForBrowser(aBrowser);
+ if (tab && tab._sharingState) {
+ gBrowser.setBrowserSharing(aBrowser, {});
+ webrtcUI.forgetStreamsFromBrowser(aBrowser);
+ }
+
+ gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
+
+ FullZoom.onLocationChange(aLocationURI, false, aBrowser);
+ },
+}
+
+function nsBrowserAccess() { }
+
+nsBrowserAccess.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
+
+ _openURIInNewTab: function(aURI, aReferrer, aReferrerPolicy, aIsPrivate,
+ aIsExternal, aForceNotRemote=false,
+ aUserContextId=Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
+ aOpener=null) {
+ let win, needToFocusWin;
+
+ // try the current window. if we're in a popup, fall back on the most recent browser window
+ if (window.toolbar.visible)
+ win = window;
+ else {
+ win = RecentWindow.getMostRecentBrowserWindow({private: aIsPrivate});
+ needToFocusWin = true;
+ }
+
+ if (!win) {
+ // we couldn't find a suitable window, a new one needs to be opened.
+ return null;
+ }
+
+ if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
+ win.BrowserOpenTab(); // this also focuses the location bar
+ win.focus();
+ return win.gBrowser.selectedBrowser;
+ }
+
+ let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
+
+ let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
+ referrerURI: aReferrer,
+ referrerPolicy: aReferrerPolicy,
+ userContextId: aUserContextId,
+ fromExternal: aIsExternal,
+ inBackground: loadInBackground,
+ forceNotRemote: aForceNotRemote,
+ opener: aOpener,
+ });
+ let browser = win.gBrowser.getBrowserForTab(tab);
+
+ if (needToFocusWin || (!loadInBackground && aIsExternal))
+ win.focus();
+
+ return browser;
+ },
+
+ openURI: function (aURI, aOpener, aWhere, aFlags) {
+ // This function should only ever be called if we're opening a URI
+ // from a non-remote browser window (via nsContentTreeOwner).
+ if (aOpener && Cu.isCrossProcessWrapper(aOpener)) {
+ Cu.reportError("nsBrowserAccess.openURI was passed a CPOW for aOpener. " +
+ "openURI should only ever be called from non-remote browsers.");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ var newWindow = null;
+ var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ if (aOpener && isExternal) {
+ Cu.reportError("nsBrowserAccess.openURI did not expect an opener to be " +
+ "passed if the context is OPEN_EXTERNAL.");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ if (isExternal && aURI && aURI.schemeIs("chrome")) {
+ dump("use --chrome command-line option to load external chrome urls\n");
+ return null;
+ }
+
+ if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+ if (isExternal &&
+ gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
+ else
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
+ }
+
+ let referrer = aOpener ? makeURI(aOpener.location.href) : null;
+ let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
+ if (aOpener && aOpener.document) {
+ referrerPolicy = aOpener.document.referrerPolicy;
+ }
+ let isPrivate = aOpener
+ ? PrivateBrowsingUtils.isContentWindowPrivate(aOpener)
+ : PrivateBrowsingUtils.isWindowPrivate(window);
+
+ switch (aWhere) {
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
+ // FIXME: Bug 408379. So how come this doesn't send the
+ // referrer like the other loads do?
+ var url = aURI ? aURI.spec : "about:blank";
+ let features = "all,dialog=no";
+ if (isPrivate) {
+ features += ",private";
+ }
+ // Pass all params to openDialog to ensure that "url" isn't passed through
+ // loadOneOrMoreURIs, which splits based on "|"
+ newWindow = openDialog(getBrowserURL(), "_blank", features, url, null, null, null);
+ break;
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
+ // If we have an opener, that means that the caller is expecting access
+ // to the nsIDOMWindow of the opened tab right away. For e10s windows,
+ // this means forcing the newly opened browser to be non-remote so that
+ // we can hand back the nsIDOMWindow. The XULBrowserWindow.shouldLoadURI
+ // will do the job of shuttling off the newly opened browser to run in
+ // the right process once it starts loading a URI.
+ let forceNotRemote = !!aOpener;
+ let userContextId = aOpener && aOpener.document
+ ? aOpener.document.nodePrincipal.originAttributes.userContextId
+ : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
+ let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
+ let browser = this._openURIInNewTab(aURI, referrer, referrerPolicy,
+ isPrivate, isExternal,
+ forceNotRemote, userContextId,
+ openerWindow);
+ if (browser)
+ newWindow = browser.contentWindow;
+ break;
+ default : // OPEN_CURRENTWINDOW or an illegal value
+ newWindow = content;
+ if (aURI) {
+ let loadflags = isExternal ?
+ Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ gBrowser.loadURIWithFlags(aURI.spec, {
+ flags: loadflags,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ });
+ }
+ if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
+ window.focus();
+ }
+ return newWindow;
+ },
+
+ openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aFlags) {
+ if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
+ dump("Error: openURIInFrame can only open in new tabs");
+ return null;
+ }
+
+ var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ var userContextId = aParams.openerOriginAttributes &&
+ ("userContextId" in aParams.openerOriginAttributes)
+ ? aParams.openerOriginAttributes.userContextId
+ : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID
+
+ let browser = this._openURIInNewTab(aURI, aParams.referrer,
+ aParams.referrerPolicy,
+ aParams.isPrivate,
+ isExternal, false,
+ userContextId);
+ if (browser)
+ return browser.QueryInterface(Ci.nsIFrameLoaderOwner);
+
+ return null;
+ },
+
+ isTabContentWindow: function (aWindow) {
+ return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
+ },
+
+ canClose() {
+ return CanCloseWindow();
+ },
+}
+
+function getTogglableToolbars() {
+ let toolbarNodes = Array.slice(gNavToolbox.childNodes);
+ toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars);
+ toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname"));
+ return toolbarNodes;
+}
+
+function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
+ var popup = aEvent.target;
+ if (popup != aEvent.currentTarget)
+ return;
+
+ // Empty the menu
+ for (var i = popup.childNodes.length-1; i >= 0; --i) {
+ var deadItem = popup.childNodes[i];
+ if (deadItem.hasAttribute("toolbarId"))
+ popup.removeChild(deadItem);
+ }
+
+ var firstMenuItem = aInsertPoint || popup.firstChild;
+
+ let toolbarNodes = getTogglableToolbars();
+
+ for (let toolbar of toolbarNodes) {
+ let menuItem = document.createElement("menuitem");
+ let hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+ menuItem.setAttribute("id", "toggle_" + toolbar.id);
+ menuItem.setAttribute("toolbarId", toolbar.id);
+ menuItem.setAttribute("type", "checkbox");
+ menuItem.setAttribute("label", toolbar.getAttribute("toolbarname"));
+ menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
+ menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
+ if (popup.id != "toolbar-context-menu")
+ menuItem.setAttribute("key", toolbar.getAttribute("key"));
+
+ popup.insertBefore(menuItem, firstMenuItem);
+
+ menuItem.addEventListener("command", onViewToolbarCommand, false);
+ }
+
+
+ let moveToPanel = popup.querySelector(".customize-context-moveToPanel");
+ let removeFromToolbar = popup.querySelector(".customize-context-removeFromToolbar");
+ // View -> Toolbars menu doesn't have the moveToPanel or removeFromToolbar items.
+ if (!moveToPanel || !removeFromToolbar) {
+ return;
+ }
+
+ // triggerNode can be a nested child element of a toolbaritem.
+ let toolbarItem = popup.triggerNode;
+
+ if (toolbarItem && toolbarItem.localName == "toolbarpaletteitem") {
+ toolbarItem = toolbarItem.firstChild;
+ } else if (toolbarItem && toolbarItem.localName != "toolbar") {
+ while (toolbarItem && toolbarItem.parentNode) {
+ let parent = toolbarItem.parentNode;
+ if ((parent.classList && parent.classList.contains("customization-target")) ||
+ parent.getAttribute("overflowfortoolbar") || // Needs to work in the overflow list as well.
+ parent.localName == "toolbarpaletteitem" ||
+ parent.localName == "toolbar")
+ break;
+ toolbarItem = parent;
+ }
+ } else {
+ toolbarItem = null;
+ }
+
+ let showTabStripItems = toolbarItem && toolbarItem.id == "tabbrowser-tabs";
+ for (let node of popup.querySelectorAll('menuitem[contexttype="toolbaritem"]')) {
+ node.hidden = showTabStripItems;
+ }
+
+ for (let node of popup.querySelectorAll('menuitem[contexttype="tabbar"]')) {
+ node.hidden = !showTabStripItems;
+ }
+
+ if (showTabStripItems) {
+ PlacesCommandHook.updateBookmarkAllTabsCommand();
+
+ let haveMultipleTabs = gBrowser.visibleTabs.length > 1;
+ document.getElementById("toolbar-context-reloadAllTabs").disabled = !haveMultipleTabs;
+
+ document.getElementById("toolbar-context-undoCloseTab").disabled =
+ SessionStore.getClosedTabCount(window) == 0;
+ return;
+ }
+
+ // In some cases, we will exit the above loop with toolbarItem being the
+ // xul:document. That has no parentNode, and we should disable the items in
+ // this case.
+ let movable = toolbarItem && toolbarItem.parentNode &&
+ CustomizableUI.isWidgetRemovable(toolbarItem);
+ if (movable) {
+ moveToPanel.removeAttribute("disabled");
+ removeFromToolbar.removeAttribute("disabled");
+ } else {
+ moveToPanel.setAttribute("disabled", true);
+ removeFromToolbar.setAttribute("disabled", true);
+ }
+}
+
+function onViewToolbarCommand(aEvent) {
+ var toolbarId = aEvent.originalTarget.getAttribute("toolbarId");
+ var isVisible = aEvent.originalTarget.getAttribute("checked") == "true";
+ CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
+}
+
+function setToolbarVisibility(toolbar, isVisible, persist=true) {
+ let hidingAttribute;
+ if (toolbar.getAttribute("type") == "menubar") {
+ hidingAttribute = "autohide";
+ if (AppConstants.platform == "linux") {
+ Services.prefs.setBoolPref("ui.key.menuAccessKeyFocuses", !isVisible);
+ }
+ } else {
+ hidingAttribute = "collapsed";
+ }
+
+ toolbar.setAttribute(hidingAttribute, !isVisible);
+ if (persist) {
+ document.persist(toolbar.id, hidingAttribute);
+ }
+
+ let eventParams = {
+ detail: {
+ visible: isVisible
+ },
+ bubbles: true
+ };
+ let event = new CustomEvent("toolbarvisibilitychange", eventParams);
+ toolbar.dispatchEvent(event);
+
+ PlacesToolbarHelper.init();
+ BookmarkingUI.onToolbarVisibilityChange();
+ gBrowser.updateWindowResizers();
+ if (isVisible)
+ ToolbarIconColor.inferFromText();
+}
+
+var TabletModeUpdater = {
+ init() {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+ this.update(WindowsUIUtils.inTabletMode);
+ Services.obs.addObserver(this, "tablet-mode-change", false);
+ }
+ },
+
+ uninit() {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
+ Services.obs.removeObserver(this, "tablet-mode-change");
+ }
+ },
+
+ observe(subject, topic, data) {
+ this.update(data == "tablet-mode");
+ },
+
+ update(isInTabletMode) {
+ let wasInTabletMode = document.documentElement.hasAttribute("tabletmode");
+ if (isInTabletMode) {
+ document.documentElement.setAttribute("tabletmode", "true");
+ } else {
+ document.documentElement.removeAttribute("tabletmode");
+ }
+ if (wasInTabletMode != isInTabletMode) {
+ TabsInTitlebar.updateAppearance(true);
+ }
+ },
+};
+
+var gTabletModePageCounter = {
+ enabled: false,
+ inc() {
+ this.enabled = AppConstants.isPlatformAndVersionAtLeast("win", "10.0");
+ if (!this.enabled) {
+ this.inc = () => {};
+ return;
+ }
+ this.inc = this._realInc;
+ this.inc();
+ },
+
+ _desktopCount: 0,
+ _tabletCount: 0,
+ _realInc() {
+ let inTabletMode = document.documentElement.hasAttribute("tabletmode");
+ this[inTabletMode ? "_tabletCount" : "_desktopCount"]++;
+ },
+
+ finish() {
+ if (this.enabled) {
+ let histogram = Services.telemetry.getKeyedHistogramById("FX_TABLETMODE_PAGE_LOAD");
+ histogram.add("tablet", this._tabletCount);
+ histogram.add("desktop", this._desktopCount);
+ }
+ },
+};
+
+function displaySecurityInfo()
+{
+ BrowserPageInfo(null, "securityTab");
+}
+
+
+var gHomeButton = {
+ prefDomain: "browser.startup.homepage",
+ observe: function (aSubject, aTopic, aPrefName)
+ {
+ if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
+ return;
+
+ this.updateTooltip();
+ },
+
+ updateTooltip: function (homeButton)
+ {
+ if (!homeButton)
+ homeButton = document.getElementById("home-button");
+ if (homeButton) {
+ var homePage = this.getHomePage();
+ homePage = homePage.replace(/\|/g, ', ');
+ if (["about:home", "about:newtab"].includes(homePage.toLowerCase()))
+ homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip"));
+ else
+ homeButton.setAttribute("tooltiptext", homePage);
+ }
+ },
+
+ getHomePage: function ()
+ {
+ var url;
+ try {
+ url = gPrefService.getComplexValue(this.prefDomain,
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) {
+ }
+
+ // use this if we can't find the pref
+ if (!url) {
+ var configBundle = Services.strings
+ .createBundle("chrome://branding/locale/browserconfig.properties");
+ url = configBundle.GetStringFromName(this.prefDomain);
+ }
+
+ return url;
+ },
+};
+
+const nodeToTooltipMap = {
+ "bookmarks-menu-button": "bookmarksMenuButton.tooltip",
+ "new-window-button": "newWindowButton.tooltip",
+ "new-tab-button": "newTabButton.tooltip",
+ "tabs-newtab-button": "newTabButton.tooltip",
+ "fullscreen-button": "fullscreenButton.tooltip",
+ "downloads-button": "downloads.tooltip",
+};
+const nodeToShortcutMap = {
+ "bookmarks-menu-button": "manBookmarkKb",
+ "new-window-button": "key_newNavigator",
+ "new-tab-button": "key_newNavigatorTab",
+ "tabs-newtab-button": "key_newNavigatorTab",
+ "fullscreen-button": "key_fullScreen",
+ "downloads-button": "key_openDownloads"
+};
+
+if (AppConstants.platform == "macosx") {
+ nodeToTooltipMap["print-button"] = "printButton.tooltip";
+ nodeToShortcutMap["print-button"] = "printKb";
+}
+
+const gDynamicTooltipCache = new Map();
+function UpdateDynamicShortcutTooltipText(aTooltip) {
+ let nodeId = aTooltip.triggerNode.id || aTooltip.triggerNode.getAttribute("anonid");
+ if (!gDynamicTooltipCache.has(nodeId) && nodeId in nodeToTooltipMap) {
+ let strId = nodeToTooltipMap[nodeId];
+ let args = [];
+ if (nodeId in nodeToShortcutMap) {
+ let shortcutId = nodeToShortcutMap[nodeId];
+ let shortcut = document.getElementById(shortcutId);
+ if (shortcut) {
+ args.push(ShortcutUtils.prettifyShortcut(shortcut));
+ }
+ }
+ gDynamicTooltipCache.set(nodeId, gNavigatorBundle.getFormattedString(strId, args));
+ }
+ aTooltip.setAttribute("label", gDynamicTooltipCache.get(nodeId));
+}
+
+function getBrowserSelection(aCharLen) {
+ Deprecated.warning("getBrowserSelection",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1134769");
+
+ let focusedElement = document.activeElement;
+ if (focusedElement && focusedElement.localName == "browser" &&
+ focusedElement.isRemoteBrowser) {
+ throw "getBrowserSelection doesn't support child process windows";
+ }
+
+ return BrowserUtils.getSelectionDetails(window, aCharLen).text;
+}
+
+var gWebPanelURI;
+function openWebPanel(title, uri) {
+ // Ensure that the web panels sidebar is open.
+ SidebarUI.show("viewWebPanelsSidebar");
+
+ // Set the title of the panel.
+ SidebarUI.title = title;
+
+ // Tell the Web Panels sidebar to load the bookmark.
+ if (SidebarUI.browser.docShell && SidebarUI.browser.contentDocument &&
+ SidebarUI.browser.contentDocument.getElementById("web-panels-browser")) {
+ SidebarUI.browser.contentWindow.loadWebPanel(uri);
+ if (gWebPanelURI) {
+ gWebPanelURI = "";
+ SidebarUI.browser.removeEventListener("load", asyncOpenWebPanel, true);
+ }
+ } else {
+ // The panel is still being constructed. Attach an onload handler.
+ if (!gWebPanelURI) {
+ SidebarUI.browser.addEventListener("load", asyncOpenWebPanel, true);
+ }
+ gWebPanelURI = uri;
+ }
+}
+
+function asyncOpenWebPanel(event) {
+ if (gWebPanelURI && SidebarUI.browser.contentDocument &&
+ SidebarUI.browser.contentDocument.getElementById("web-panels-browser")) {
+ SidebarUI.browser.contentWindow.loadWebPanel(gWebPanelURI);
+ }
+ gWebPanelURI = "";
+ SidebarUI.browser.removeEventListener("load", asyncOpenWebPanel, true);
+}
+
+/*
+ * - [ Dependencies ] ---------------------------------------------------------
+ * utilityOverlay.js:
+ * - gatherTextUnder
+ */
+
+/**
+ * Extracts linkNode and href for the current click target.
+ *
+ * @param event
+ * The click event.
+ * @return [href, linkNode].
+ *
+ * @note linkNode will be null if the click wasn't on an anchor
+ * element (or XLink).
+ */
+function hrefAndLinkNodeForClickEvent(event)
+{
+ function isHTMLLink(aNode)
+ {
+ // Be consistent with what nsContextMenu.js does.
+ return ((aNode instanceof HTMLAnchorElement && aNode.href) ||
+ (aNode instanceof HTMLAreaElement && aNode.href) ||
+ aNode instanceof HTMLLinkElement);
+ }
+
+ let node = event.target;
+ while (node && !isHTMLLink(node)) {
+ node = node.parentNode;
+ }
+
+ if (node)
+ return [node.href, node];
+
+ // If there is no linkNode, try simple XLink.
+ let href, baseURI;
+ node = event.target;
+ while (node && !href) {
+ if (node.nodeType == Node.ELEMENT_NODE &&
+ (node.localName == "a" ||
+ node.namespaceURI == "http://www.w3.org/1998/Math/MathML")) {
+ href = node.getAttribute("href") ||
+ node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+
+ if (href) {
+ baseURI = node.baseURI;
+ break;
+ }
+ }
+ node = node.parentNode;
+ }
+
+ // In case of XLink, we don't return the node we got href from since
+ // callers expect <a>-like elements.
+ return [href ? makeURLAbsolute(baseURI, href) : null, null];
+}
+
+/**
+ * Called whenever the user clicks in the content area.
+ *
+ * @param event
+ * The click event.
+ * @param isPanelClick
+ * Whether the event comes from a web panel.
+ * @note default event is prevented if the click is handled.
+ */
+function contentAreaClick(event, isPanelClick)
+{
+ if (!event.isTrusted || event.defaultPrevented || event.button == 2)
+ return;
+
+ let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
+ if (!href) {
+ // Not a link, handle middle mouse navigation.
+ if (event.button == 1 &&
+ gPrefService.getBoolPref("middlemouse.contentLoadURL") &&
+ !gPrefService.getBoolPref("general.autoScroll")) {
+ middleMousePaste(event);
+ event.preventDefault();
+ }
+ return;
+ }
+
+ // This code only applies if we have a linkNode (i.e. clicks on real anchor
+ // elements, as opposed to XLink).
+ if (linkNode && event.button == 0 &&
+ !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
+ // A Web panel's links should target the main content area. Do this
+ // if no modifier keys are down and if there's no target or the target
+ // equals _main (the IE convention) or _content (the Mozilla convention).
+ let target = linkNode.target;
+ let mainTarget = !target || target == "_content" || target == "_main";
+ if (isPanelClick && mainTarget) {
+ // javascript and data links should be executed in the current browser.
+ if (linkNode.getAttribute("onclick") ||
+ href.startsWith("javascript:") ||
+ href.startsWith("data:"))
+ return;
+
+ try {
+ urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
+ }
+ catch (ex) {
+ // Prevent loading unsecure destinations.
+ event.preventDefault();
+ return;
+ }
+
+ loadURI(href, null, null, false);
+ event.preventDefault();
+ return;
+ }
+
+ if (linkNode.getAttribute("rel") == "sidebar") {
+ // This is the Opera convention for a special link that, when clicked,
+ // allows to add a sidebar panel. The link's title attribute contains
+ // the title that should be used for the sidebar panel.
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(href)
+ , title: linkNode.getAttribute("title")
+ , loadBookmarkInSidebar: true
+ , hiddenRows: [ "description"
+ , "location"
+ , "keyword" ]
+ }, window);
+ event.preventDefault();
+ return;
+ }
+ }
+
+ handleLinkClick(event, href, linkNode);
+
+ // Mark the page as a user followed link. This is done so that history can
+ // distinguish automatic embed visits from user activated ones. For example
+ // pages loaded in frames are embed visits and lost with the session, while
+ // visits across frames should be preserved.
+ try {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsFollowedLink(href);
+ } catch (ex) { /* Skip invalid URIs. */ }
+}
+
+/**
+ * Handles clicks on links.
+ *
+ * @return true if the click event was handled, false otherwise.
+ */
+function handleLinkClick(event, href, linkNode) {
+ if (event.button == 2) // right click
+ return false;
+
+ var where = whereToOpenLink(event);
+ if (where == "current")
+ return false;
+
+ var doc = event.target.ownerDocument;
+
+ if (where == "save") {
+ saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true,
+ true, doc.documentURIObject, doc);
+ event.preventDefault();
+ return true;
+ }
+
+ var referrerURI = doc.documentURIObject;
+ // if the mixedContentChannel is present and the referring URI passes
+ // a same origin check with the target URI, we can preserve the users
+ // decision of disabling MCB on a page for it's child tabs.
+ var persistAllowMixedContentInChildTab = false;
+
+ if (where == "tab" && gBrowser.docShell.mixedContentChannel) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ var targetURI = makeURI(href);
+ sm.checkSameOriginURI(referrerURI, targetURI, false);
+ persistAllowMixedContentInChildTab = true;
+ }
+ catch (e) { }
+ }
+
+ // first get document wide referrer policy, then
+ // get referrer attribute from clicked link and parse it and
+ // allow per element referrer to overrule the document wide referrer if enabled
+ let referrerPolicy = doc.referrerPolicy;
+ if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer") &&
+ linkNode) {
+ let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode.
+ getAttribute("referrerpolicy"));
+ if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
+ referrerPolicy = referrerAttrValue;
+ }
+ }
+
+ urlSecurityCheck(href, doc.nodePrincipal);
+ let params = {
+ charset: doc.characterSet,
+ allowMixedContent: persistAllowMixedContentInChildTab,
+ referrerURI: referrerURI,
+ referrerPolicy: referrerPolicy,
+ noReferrer: BrowserUtils.linkHasNoReferrer(linkNode),
+ originPrincipal: doc.nodePrincipal,
+ };
+
+ // The new tab/window must use the same userContextId
+ if (doc.nodePrincipal.originAttributes.userContextId) {
+ params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
+ }
+
+ openLinkIn(href, where, params);
+ event.preventDefault();
+ return true;
+}
+
+function middleMousePaste(event) {
+ let clipboard = readFromClipboard();
+ if (!clipboard)
+ return;
+
+ // Strip embedded newlines and surrounding whitespace, to match the URL
+ // bar's behavior (stripsurroundingwhitespace)
+ clipboard = clipboard.replace(/\s*\n\s*/g, "");
+
+ clipboard = stripUnsafeProtocolOnPaste(clipboard);
+
+ // if it's not the current tab, we don't need to do anything because the
+ // browser doesn't exist.
+ let where = whereToOpenLink(event, true, false);
+ let lastLocationChange;
+ if (where == "current") {
+ lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+ }
+
+ getShortcutOrURIAndPostData(clipboard).then(data => {
+ try {
+ makeURI(data.url);
+ } catch (ex) {
+ // Not a valid URI.
+ return;
+ }
+
+ try {
+ addToUrlbarHistory(data.url);
+ } catch (ex) {
+ // Things may go wrong when adding url to session history,
+ // but don't let that interfere with the loading of the url.
+ Cu.reportError(ex);
+ }
+
+ if (where != "current" ||
+ lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+ openUILink(data.url, event,
+ { ignoreButton: true,
+ disallowInheritPrincipal: !data.mayInheritPrincipal });
+ }
+ });
+
+ event.stopPropagation();
+}
+
+function stripUnsafeProtocolOnPaste(pasteData) {
+ // Don't allow pasting javascript URIs since we don't support
+ // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL for those.
+ return pasteData.replace(/^(?:\s*javascript:)+/i, "");
+}
+
+// handleDroppedLink has the following 2 overloads:
+// handleDroppedLink(event, url, name)
+// handleDroppedLink(event, links)
+function handleDroppedLink(event, urlOrLinks, name)
+{
+ let links;
+ if (Array.isArray(urlOrLinks)) {
+ links = urlOrLinks;
+ } else {
+ links = [{ url: urlOrLinks, name, type: "" }];
+ }
+
+ let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+
+ let userContextId = gBrowser.selectedBrowser.getAttribute("usercontextid");
+
+ // event is null if links are dropped in content process.
+ // inBackground should be false, as it's loading into current browser.
+ let inBackground = false;
+ if (event) {
+ inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ if (event.shiftKey)
+ inBackground = !inBackground;
+ }
+
+ Task.spawn(function*() {
+ let urls = [];
+ let postDatas = [];
+ for (let link of links) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ urls.push(data.url);
+ postDatas.push(data.postData);
+ }
+ if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+ gBrowser.loadTabs(urls, {
+ inBackground,
+ replace: true,
+ allowThirdPartyFixup: false,
+ postDatas,
+ userContextId,
+ });
+ }
+ });
+
+ // If links are dropped in content process, event.preventDefault() should be
+ // called in content process.
+ if (event) {
+ // Keep the event from being handled by the dragDrop listeners
+ // built-in to gecko if they happen to be above us.
+ event.preventDefault();
+ }
+}
+
+function BrowserSetForcedCharacterSet(aCharset)
+{
+ if (aCharset) {
+ gBrowser.selectedBrowser.characterSet = aCharset;
+ // Save the forced character-set
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset);
+ }
+ BrowserCharsetReload();
+}
+
+function BrowserCharsetReload()
+{
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+}
+
+function UpdateCurrentCharset(target) {
+ let selectedCharset = CharsetMenu.foldCharset(gBrowser.selectedBrowser.characterSet);
+ for (let menuItem of target.getElementsByTagName("menuitem")) {
+ let isSelected = menuItem.getAttribute("charset") === selectedCharset;
+ menuItem.setAttribute("checked", isSelected);
+ }
+}
+
+var gPageStyleMenu = {
+ // This maps from a <browser> element (or, more specifically, a
+ // browser's permanentKey) to an Object that contains the most recent
+ // information about the browser content's stylesheets. That Object
+ // is populated via the PageStyle:StyleSheets message from the content
+ // process. The Object should have the following structure:
+ //
+ // filteredStyleSheets (Array):
+ // An Array of objects with a filtered list representing all stylesheets
+ // that the current page offers. Each object has the following members:
+ //
+ // title (String):
+ // The title of the stylesheet
+ //
+ // disabled (bool):
+ // Whether or not the stylesheet is currently applied
+ //
+ // href (String):
+ // The URL of the stylesheet. Stylesheets loaded via a data URL will
+ // have this property set to null.
+ //
+ // authorStyleDisabled (bool):
+ // Whether or not the user currently has "No Style" selected for
+ // the current page.
+ //
+ // preferredStyleSheetSet (bool):
+ // Whether or not the user currently has the "Default" style selected
+ // for the current page.
+ //
+ _pageStyleSheets: new WeakMap(),
+
+ init: function() {
+ let mm = window.messageManager;
+ mm.addMessageListener("PageStyle:StyleSheets", (msg) => {
+ this._pageStyleSheets.set(msg.target.permanentKey, msg.data);
+ });
+ },
+
+ /**
+ * Returns an array of Objects representing stylesheets in a
+ * browser. Note that the pageshow event needs to fire in content
+ * before this information will be available.
+ *
+ * @param browser (optional)
+ * The <xul:browser> to search for stylesheets. If omitted, this
+ * defaults to the currently selected tab's browser.
+ * @returns Array
+ * An Array of Objects representing stylesheets in the browser.
+ * See the documentation for gPageStyleMenu for a description
+ * of the Object structure.
+ */
+ getBrowserStyleSheets: function (browser) {
+ if (!browser) {
+ browser = gBrowser.selectedBrowser;
+ }
+
+ let data = this._pageStyleSheets.get(browser.permanentKey);
+ if (!data) {
+ return [];
+ }
+ return data.filteredStyleSheets;
+ },
+
+ _getStyleSheetInfo: function (browser) {
+ let data = this._pageStyleSheets.get(browser.permanentKey);
+ if (!data) {
+ return {
+ filteredStyleSheets: [],
+ authorStyleDisabled: false,
+ preferredStyleSheetSet: true
+ };
+ }
+
+ return data;
+ },
+
+ fillPopup: function (menuPopup) {
+ let styleSheetInfo = this._getStyleSheetInfo(gBrowser.selectedBrowser);
+ var noStyle = menuPopup.firstChild;
+ var persistentOnly = noStyle.nextSibling;
+ var sep = persistentOnly.nextSibling;
+ while (sep.nextSibling)
+ menuPopup.removeChild(sep.nextSibling);
+
+ let styleSheets = styleSheetInfo.filteredStyleSheets;
+ var currentStyleSheets = {};
+ var styleDisabled = styleSheetInfo.authorStyleDisabled;
+ var haveAltSheets = false;
+ var altStyleSelected = false;
+
+ for (let currentStyleSheet of styleSheets) {
+ if (!currentStyleSheet.disabled)
+ altStyleSelected = true;
+
+ haveAltSheets = true;
+
+ let lastWithSameTitle = null;
+ if (currentStyleSheet.title in currentStyleSheets)
+ lastWithSameTitle = currentStyleSheets[currentStyleSheet.title];
+
+ if (!lastWithSameTitle) {
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("type", "radio");
+ menuItem.setAttribute("label", currentStyleSheet.title);
+ menuItem.setAttribute("data", currentStyleSheet.title);
+ menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled);
+ menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));");
+ menuPopup.appendChild(menuItem);
+ currentStyleSheets[currentStyleSheet.title] = menuItem;
+ } else if (currentStyleSheet.disabled) {
+ lastWithSameTitle.removeAttribute("checked");
+ }
+ }
+
+ noStyle.setAttribute("checked", styleDisabled);
+ persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled);
+ persistentOnly.hidden = styleSheetInfo.preferredStyleSheetSet ? haveAltSheets : false;
+ sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
+ },
+
+ switchStyleSheet: function (title) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("PageStyle:Switch", {title: title});
+ },
+
+ disableStyle: function () {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("PageStyle:Disable");
+ },
+};
+
+/* Legacy global page-style functions */
+var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu);
+function stylesheetSwitchAll(contentWindow, title) {
+ // We ignore the contentWindow param. Add-ons don't appear to use
+ // it, and it's difficult to support in e10s (where it will be a
+ // CPOW).
+ gPageStyleMenu.switchStyleSheet(title);
+}
+function setStyleDisabled(disabled) {
+ if (disabled)
+ gPageStyleMenu.disableStyle();
+}
+
+
+var LanguageDetectionListener = {
+ init: function() {
+ window.messageManager.addMessageListener("Translation:DocumentState", msg => {
+ Translation.documentStateReceived(msg.target, msg.data);
+ });
+ }
+};
+
+
+var BrowserOffline = {
+ _inited: false,
+
+ // BrowserOffline Public Methods
+ init: function ()
+ {
+ if (!this._uiElement)
+ this._uiElement = document.getElementById("workOfflineMenuitemState");
+
+ Services.obs.addObserver(this, "network:offline-status-changed", false);
+
+ this._updateOfflineUI(Services.io.offline);
+
+ this._inited = true;
+ },
+
+ uninit: function ()
+ {
+ if (this._inited) {
+ Services.obs.removeObserver(this, "network:offline-status-changed");
+ }
+ },
+
+ toggleOfflineStatus: function ()
+ {
+ var ioService = Services.io;
+
+ if (!ioService.offline && !this._canGoOffline()) {
+ this._updateOfflineUI(false);
+ return;
+ }
+
+ ioService.offline = !ioService.offline;
+ },
+
+ // nsIObserver
+ observe: function (aSubject, aTopic, aState)
+ {
+ if (aTopic != "network:offline-status-changed")
+ return;
+
+ // This notification is also received because of a loss in connectivity,
+ // which we ignore by updating the UI to the current value of io.offline
+ this._updateOfflineUI(Services.io.offline);
+ },
+
+ // BrowserOffline Implementation Methods
+ _canGoOffline: function ()
+ {
+ try {
+ var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null);
+
+ // Something aborted the quit process.
+ if (cancelGoOffline.data)
+ return false;
+ }
+ catch (ex) {
+ }
+
+ return true;
+ },
+
+ _uiElement: null,
+ _updateOfflineUI: function (aOffline)
+ {
+ var offlineLocked = gPrefService.prefIsLocked("network.online");
+ if (offlineLocked)
+ this._uiElement.setAttribute("disabled", "true");
+
+ this._uiElement.setAttribute("checked", aOffline);
+ }
+};
+
+var OfflineApps = {
+ warnUsage(browser, uri) {
+ if (!browser)
+ return;
+
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.manageUsage"),
+ accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
+ callback: this.manage
+ };
+
+ let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
+ // This message shows the quota in MB, and so we divide the quota (in kb) by 1024.
+ let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
+ [ uri.host,
+ warnQuotaKB / 1024 ]);
+
+ let anchorID = "indexedDB-notification-icon";
+ PopupNotifications.show(browser, "offline-app-usage", message,
+ anchorID, mainAction);
+
+ // Now that we've warned once, prevent the warning from showing up
+ // again.
+ Services.perms.add(uri, "offline-app",
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+ },
+
+ // XXX: duplicated in preferences/advanced.js
+ _getOfflineAppUsage(host, groups) {
+ let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService);
+ if (!groups) {
+ try {
+ groups = cacheService.getGroups();
+ } catch (ex) {
+ return 0;
+ }
+ }
+
+ let usage = 0;
+ for (let group of groups) {
+ let uri = Services.io.newURI(group, null, null);
+ if (uri.asciiHost == host) {
+ let cache = cacheService.getActiveCache(group);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ _usedMoreThanWarnQuota(uri) {
+ // if the user has already allowed excessive usage, don't bother checking
+ if (Services.perms.testExactPermission(uri, "offline-app") !=
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
+ let usageBytes = this._getOfflineAppUsage(uri.asciiHost);
+ let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
+ // The pref is in kb, the usage we get is in bytes, so multiply the quota
+ // to compare correctly:
+ if (usageBytes >= warnQuotaKB * 1024) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ requestPermission(browser, docId, uri) {
+ let host = uri.asciiHost;
+ let notificationID = "offline-app-requested-" + host;
+ let notification = PopupNotifications.getNotification(notificationID, browser);
+
+ if (notification) {
+ notification.options.controlledItems.push([
+ Cu.getWeakReference(browser), docId, uri
+ ]);
+ } else {
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ for (let [browser, docId, uri] of notification.options.controlledItems) {
+ OfflineApps.allowSite(browser, docId, uri);
+ }
+ }
+ };
+ let secondaryActions = [{
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ for (let [, , uri] of notification.options.controlledItems) {
+ OfflineApps.disallowSite(uri);
+ }
+ }
+ }];
+ let message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [host]);
+ let anchorID = "indexedDB-notification-icon";
+ let options = {
+ controlledItems : [[Cu.getWeakReference(browser), docId, uri]]
+ };
+ notification = PopupNotifications.show(browser, notificationID, message,
+ anchorID, mainAction,
+ secondaryActions, options);
+ }
+ },
+
+ disallowSite(uri) {
+ Services.perms.add(uri, "offline-app", Services.perms.DENY_ACTION);
+ },
+
+ allowSite(browserRef, docId, uri) {
+ Services.perms.add(uri, "offline-app", Services.perms.ALLOW_ACTION);
+
+ // When a site is enabled while loading, manifest resources will
+ // start fetching immediately. This one time we need to do it
+ // ourselves.
+ let browser = browserRef.get();
+ if (browser && browser.messageManager) {
+ browser.messageManager.sendAsyncMessage("OfflineApps:StartFetching", {
+ docId,
+ });
+ }
+ },
+
+ manage() {
+ openAdvancedPreferences("networkTab");
+ },
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "OfflineApps:CheckUsage":
+ let uri = makeURI(msg.data.uri);
+ if (this._usedMoreThanWarnQuota(uri)) {
+ this.warnUsage(msg.target, uri);
+ }
+ break;
+ case "OfflineApps:RequestPermission":
+ this.requestPermission(msg.target, msg.data.docId, makeURI(msg.data.uri));
+ break;
+ }
+ },
+
+ init() {
+ let mm = window.messageManager;
+ mm.addMessageListener("OfflineApps:CheckUsage", this);
+ mm.addMessageListener("OfflineApps:RequestPermission", this);
+ },
+};
+
+var IndexedDBPromptHelper = {
+ _permissionsPrompt: "indexedDB-permissions-prompt",
+ _permissionsResponse: "indexedDB-permissions-response",
+
+ _notificationIcon: "indexedDB-notification-icon",
+
+ init:
+ function IndexedDBPromptHelper_init() {
+ Services.obs.addObserver(this, this._permissionsPrompt, false);
+ },
+
+ uninit:
+ function IndexedDBPromptHelper_uninit() {
+ Services.obs.removeObserver(this, this._permissionsPrompt);
+ },
+
+ observe:
+ function IndexedDBPromptHelper_observe(subject, topic, data) {
+ if (topic != this._permissionsPrompt) {
+ throw new Error("Unexpected topic!");
+ }
+
+ var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
+
+ var browser = requestor.getInterface(Ci.nsIDOMNode);
+ if (browser.ownerGlobal != window) {
+ // Only listen for notifications for browsers in our chrome window.
+ return;
+ }
+
+ var host = browser.currentURI.asciiHost;
+
+ var message;
+ var responseTopic;
+ if (topic == this._permissionsPrompt) {
+ message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ responseTopic = this._permissionsResponse;
+ }
+
+ const hiddenTimeoutDuration = 30000; // 30 seconds
+ const firstTimeoutDuration = 300000; // 5 minutes
+
+ var timeoutId;
+
+ var observer = requestor.getInterface(Ci.nsIObserver);
+
+ var mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+ };
+
+ var secondaryActions = [
+ {
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.DENY_ACTION);
+ }
+ }
+ ];
+
+ // This will be set to the result of PopupNotifications.show().
+ var notification;
+
+ function timeoutNotification() {
+ // Remove the notification.
+ if (notification) {
+ notification.remove();
+ }
+
+ // Clear all of our timeout stuff. We may be called directly, not just
+ // when the timeout actually elapses.
+ clearTimeout(timeoutId);
+
+ // And tell the page that the popup timed out.
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ }
+
+ var options = {
+ eventCallback: function(state) {
+ // Don't do anything if the timeout has not been set yet.
+ if (!timeoutId) {
+ return;
+ }
+
+ // If the popup is being dismissed start the short timeout.
+ if (state == "dismissed") {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration);
+ return;
+ }
+
+ // If the popup is being re-shown then clear the timeout allowing
+ // unlimited waiting.
+ if (state == "shown") {
+ clearTimeout(timeoutId);
+ }
+ }
+ };
+
+ notification = PopupNotifications.show(browser, topic, message,
+ this._notificationIcon, mainAction,
+ secondaryActions, options);
+
+ // Set the timeoutId after the popup has been created, and use the long
+ // timeout value. If the user doesn't notice the popup after this amount of
+ // time then it is most likely not visible and we want to alert the page.
+ timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
+ }
+};
+
+function CanCloseWindow()
+{
+ // Avoid redundant calls to canClose from showing multiple
+ // PermitUnload dialogs.
+ if (Services.startup.shuttingDown || window.skipNextCanClose) {
+ return true;
+ }
+
+ for (let browser of gBrowser.browsers) {
+ let {permitUnload, timedOut} = browser.permitUnload();
+ if (timedOut) {
+ return true;
+ }
+ if (!permitUnload) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function WindowIsClosing()
+{
+ if (!closeWindow(false, warnAboutClosingWindow))
+ return false;
+
+ // In theory we should exit here and the Window's internal Close
+ // method should trigger canClose on nsBrowserAccess. However, by
+ // that point it's too late to be able to show a prompt for
+ // PermitUnload. So we do it here, when we still can.
+ if (CanCloseWindow()) {
+ // This flag ensures that the later canClose call does nothing.
+ // It's only needed to make tests pass, since they detect the
+ // prompt even when it's not actually shown.
+ window.skipNextCanClose = true;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Checks if this is the last full *browser* window around. If it is, this will
+ * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
+ * @returns true if closing can proceed, false if it got cancelled.
+ */
+function warnAboutClosingWindow() {
+ // Popups aren't considered full browser windows; we also ignore private windows.
+ let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing;
+ if (!isPBWindow && !toolbar.visible)
+ return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+
+ // Figure out if there's at least one other browser window around.
+ let otherPBWindowExists = false;
+ let nonPopupPresent = false;
+ for (let win of browserWindows()) {
+ if (!win.closed && win != window) {
+ if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win))
+ otherPBWindowExists = true;
+ if (win.toolbar.visible)
+ nonPopupPresent = true;
+ // If the current window is not in private browsing mode we don't need to
+ // look for other pb windows, we can leave the loop when finding the
+ // first non-popup window. If however the current window is in private
+ // browsing mode then we need at least one other pb and one non-popup
+ // window to break out early.
+ if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent)
+ break;
+ }
+ }
+
+ if (isPBWindow && !otherPBWindowExists) {
+ let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ exitingCanceled.data = false;
+ Services.obs.notifyObservers(exitingCanceled,
+ "last-pb-context-exiting",
+ null);
+ if (exitingCanceled.data)
+ return false;
+ }
+
+ if (nonPopupPresent) {
+ return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+ }
+
+ let os = Services.obs;
+
+ let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ os.notifyObservers(closingCanceled,
+ "browser-lastwindow-close-requested", null);
+ if (closingCanceled.data)
+ return false;
+
+ os.notifyObservers(null, "browser-lastwindow-close-granted", null);
+
+ // OS X doesn't quit the application when the last window is closed, but keeps
+ // the session alive. Hence don't prompt users to save tabs, but warn about
+ // closing multiple tabs.
+ return AppConstants.platform != "macosx"
+ || (isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL));
+}
+
+var MailIntegration = {
+ sendLinkForBrowser: function (aBrowser) {
+ this.sendMessage(aBrowser.currentURI.spec, aBrowser.contentTitle);
+ },
+
+ sendMessage: function (aBody, aSubject) {
+ // generate a mailto url based on the url and the url's title
+ var mailtoUrl = "mailto:";
+ if (aBody) {
+ mailtoUrl += "?body=" + encodeURIComponent(aBody);
+ mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
+ }
+
+ var uri = makeURI(mailtoUrl);
+
+ // now pass this uri to the operating system
+ this._launchExternalUrl(uri);
+ },
+
+ // a generic method which can be used to pass arbitrary urls to the operating
+ // system.
+ // aURL --> a nsIURI which represents the url to launch
+ _launchExternalUrl: function (aURL) {
+ var extProtocolSvc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ if (extProtocolSvc)
+ extProtocolSvc.loadUrl(aURL);
+ }
+};
+
+function BrowserOpenAddonsMgr(aView) {
+ return new Promise(resolve => {
+ if (aView) {
+ let emWindow;
+ let browserWindow;
+
+ var receivePong = function receivePong(aSubject, aTopic, aData) {
+ let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ if (!emWindow || browserWin == window /* favor the current window */) {
+ emWindow = aSubject;
+ browserWindow = browserWin;
+ }
+ }
+ Services.obs.addObserver(receivePong, "EM-pong", false);
+ Services.obs.notifyObservers(null, "EM-ping", "");
+ Services.obs.removeObserver(receivePong, "EM-pong");
+
+ if (emWindow) {
+ emWindow.loadView(aView);
+ browserWindow.gBrowser.selectedTab =
+ browserWindow.gBrowser._getTabForContentWindow(emWindow);
+ emWindow.focus();
+ resolve(emWindow);
+ return;
+ }
+ }
+
+ switchToTabHavingURI("about:addons", true);
+
+ if (aView) {
+ // This must be a new load, else the ping/pong would have
+ // found the window above.
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observer, aTopic);
+ aSubject.loadView(aView);
+ resolve(aSubject);
+ }, "EM-loaded", false);
+ } else {
+ resolve();
+ }
+ });
+}
+
+function AddKeywordForSearchField() {
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+ let bookmarkData = message.data;
+ let title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
+ [bookmarkData.title]);
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(bookmarkData.spec)
+ , title: title
+ , description: bookmarkData.description
+ , keyword: ""
+ , postData: bookmarkData.postData
+ , charSet: bookmarkData.charset
+ , hiddenRows: [ "location"
+ , "description"
+ , "tags"
+ , "loadInSidebar" ]
+ }, window);
+ }
+ mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+ mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", {}, { target: gContextMenu.target });
+}
+
+/**
+ * Re-open a closed tab.
+ * @param aIndex
+ * The index of the tab (via SessionStore.getClosedTabData)
+ * @returns a reference to the reopened tab.
+ */
+function undoCloseTab(aIndex) {
+ // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
+ var blankTabToRemove = null;
+ if (gBrowser.tabs.length == 1 && isTabEmpty(gBrowser.selectedTab))
+ blankTabToRemove = gBrowser.selectedTab;
+
+ var tab = null;
+ if (SessionStore.getClosedTabCount(window) > (aIndex || 0)) {
+ tab = SessionStore.undoCloseTab(window, aIndex || 0);
+
+ if (blankTabToRemove)
+ gBrowser.removeTab(blankTabToRemove);
+ }
+
+ return tab;
+}
+
+/**
+ * Re-open a closed window.
+ * @param aIndex
+ * The index of the window (via SessionStore.getClosedWindowData)
+ * @returns a reference to the reopened window.
+ */
+function undoCloseWindow(aIndex) {
+ let window = null;
+ if (SessionStore.getClosedWindowCount() > (aIndex || 0))
+ window = SessionStore.undoCloseWindow(aIndex || 0);
+
+ return window;
+}
+
+/*
+ * Determines if a tab is "empty", usually used in the context of determining
+ * if it's ok to close the tab.
+ */
+function isTabEmpty(aTab) {
+ if (aTab.hasAttribute("busy"))
+ return false;
+
+ if (aTab.hasAttribute("customizemode"))
+ return false;
+
+ let browser = aTab.linkedBrowser;
+ if (!isBlankPageURL(browser.currentURI.spec))
+ return false;
+
+ if (!checkEmptyPageOrigin(browser))
+ return false;
+
+ if (browser.canGoForward || browser.canGoBack)
+ return false;
+
+ return true;
+}
+
+/**
+ * Check whether a page can be considered as 'empty', that its URI
+ * reflects its origin, and that if it's loaded in a tab, that tab
+ * could be considered 'empty' (e.g. like the result of opening
+ * a 'blank' new tab).
+ *
+ * We have to do more than just check the URI, because especially
+ * for things like about:blank, it is possible that the opener or
+ * some other page has control over the contents of the page.
+ *
+ * @param browser {Browser}
+ * The browser whose page we're checking (the selected browser
+ * in this window if omitted).
+ * @param uri {nsIURI}
+ * The URI against which we're checking (the browser's currentURI
+ * if omitted).
+ *
+ * @return false if the page was opened by or is controlled by arbitrary web
+ * content, unless that content corresponds with the URI.
+ * true if the page is blank and controlled by a principal matching
+ * that URI (or the system principal if the principal has no URI)
+ */
+function checkEmptyPageOrigin(browser = gBrowser.selectedBrowser,
+ uri = browser.currentURI) {
+ // If another page opened this page with e.g. window.open, this page might
+ // be controlled by its opener - return false.
+ if (browser.hasContentOpener) {
+ return false;
+ }
+ let contentPrincipal = browser.contentPrincipal;
+ // Not all principals have URIs...
+ if (contentPrincipal.URI) {
+ // There are two specialcases involving about:blank. One is where
+ // the user has manually loaded it and it got created with a null
+ // principal. The other involves the case where we load
+ // some other empty page in a browser and the current page is the
+ // initial about:blank page (which has that as its principal, not
+ // just URI in which case it could be web-based). Especially in
+ // e10s, we need to tackle that case specifically to avoid race
+ // conditions when updating the URL bar.
+ if ((uri.spec == "about:blank" && contentPrincipal.isNullPrincipal) ||
+ contentPrincipal.URI.spec == "about:blank") {
+ return true;
+ }
+ return contentPrincipal.URI.equals(uri);
+ }
+ // ... so for those that don't have them, enforce that the page has the
+ // system principal (this matches e.g. on about:newtab).
+ let ssm = Services.scriptSecurityManager;
+ return ssm.isSystemPrincipal(contentPrincipal);
+}
+
+function BrowserOpenSyncTabs() {
+ gSyncUI.openSyncedTabsPanel();
+}
+
+/**
+ * Format a URL
+ * eg:
+ * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
+ * > https://addons.mozilla.org/en-US/firefox/3.0a1/
+ *
+ * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
+ */
+function formatURL(aFormat, aIsPref) {
+ var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
+ return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat);
+}
+
+/**
+ * Utility object to handle manipulations of the identity indicators in the UI
+ */
+var gIdentityHandler = {
+ /**
+ * nsIURI for which the identity UI is displayed. This has been already
+ * processed by nsIURIFixup.createExposableURI.
+ */
+ _uri: null,
+
+ /**
+ * We only know the connection type if this._uri has a defined "host" part.
+ *
+ * These URIs, like "about:" and "data:" URIs, will usually be treated as a
+ * non-secure connection, unless they refer to an internally implemented
+ * browser page or resolve to "file:" URIs.
+ */
+ _uriHasHost: false,
+
+ /**
+ * Whether this._uri refers to an internally implemented browser page.
+ *
+ * Note that this is set for some "about:" pages, but general "chrome:" URIs
+ * are not included in this category by default.
+ */
+ _isSecureInternalUI: false,
+
+ /**
+ * nsISSLStatus metadata provided by gBrowser.securityUI the last time the
+ * identity UI was updated, or null if the connection is not secure.
+ */
+ _sslStatus: null,
+
+ /**
+ * Bitmask provided by nsIWebProgressListener.onSecurityChange.
+ */
+ _state: 0,
+
+ /**
+ * This flag gets set if the identity popup was opened by a keypress,
+ * to be able to focus it on the popupshown event.
+ */
+ _popupTriggeredByKeyboard: false,
+
+ /**
+ * Whether a permission is just removed from permission list.
+ */
+ _permissionJustRemoved: false,
+
+ get _isBroken() {
+ return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
+ },
+
+ get _isSecure() {
+ // If a <browser> is included within a chrome document, then this._state
+ // will refer to the security state for the <browser> and not the top level
+ // document. In this case, don't upgrade the security state in the UI
+ // with the secure state of the embedded <browser>.
+ return !this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
+ },
+
+ get _isEV() {
+ // If a <browser> is included within a chrome document, then this._state
+ // will refer to the security state for the <browser> and not the top level
+ // document. In this case, don't upgrade the security state in the UI
+ // with the EV state of the embedded <browser>.
+ return !this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
+ },
+
+ get _isMixedActiveContentLoaded() {
+ return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
+ },
+
+ get _isMixedActiveContentBlocked() {
+ return this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+ },
+
+ get _isMixedPassiveContentLoaded() {
+ return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
+ },
+
+ get _isCertUserOverridden() {
+ return this._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
+ },
+
+ get _hasInsecureLoginForms() {
+ // checks if the page has been flagged for an insecure login. Also checks
+ // if the pref to degrade the UI is set to true
+ return LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser) &&
+ Services.prefs.getBoolPref("security.insecure_password.ui.enabled");
+ },
+
+ // smart getters
+ get _identityPopup () {
+ delete this._identityPopup;
+ return this._identityPopup = document.getElementById("identity-popup");
+ },
+ get _identityBox () {
+ delete this._identityBox;
+ return this._identityBox = document.getElementById("identity-box");
+ },
+ get _identityPopupMultiView () {
+ delete _identityPopupMultiView;
+ return document.getElementById("identity-popup-multiView");
+ },
+ get _identityPopupContentHosts () {
+ delete this._identityPopupContentHosts;
+ let selector = ".identity-popup-headline.host";
+ return this._identityPopupContentHosts = [
+ ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+ ...document.querySelectorAll(selector)
+ ];
+ },
+ get _identityPopupContentHostless () {
+ delete this._identityPopupContentHostless;
+ let selector = ".identity-popup-headline.hostless";
+ return this._identityPopupContentHostless = [
+ ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+ ...document.querySelectorAll(selector)
+ ];
+ },
+ get _identityPopupContentOwner () {
+ delete this._identityPopupContentOwner;
+ return this._identityPopupContentOwner =
+ document.getElementById("identity-popup-content-owner");
+ },
+ get _identityPopupContentSupp () {
+ delete this._identityPopupContentSupp;
+ return this._identityPopupContentSupp =
+ document.getElementById("identity-popup-content-supplemental");
+ },
+ get _identityPopupContentVerif () {
+ delete this._identityPopupContentVerif;
+ return this._identityPopupContentVerif =
+ document.getElementById("identity-popup-content-verifier");
+ },
+ get _identityPopupMixedContentLearnMore () {
+ delete this._identityPopupMixedContentLearnMore;
+ return this._identityPopupMixedContentLearnMore =
+ document.getElementById("identity-popup-mcb-learn-more");
+ },
+ get _identityPopupInsecureLoginFormsLearnMore () {
+ delete this._identityPopupInsecureLoginFormsLearnMore;
+ return this._identityPopupInsecureLoginFormsLearnMore =
+ document.getElementById("identity-popup-insecure-login-forms-learn-more");
+ },
+ get _identityIconLabels () {
+ delete this._identityIconLabels;
+ return this._identityIconLabels = document.getElementById("identity-icon-labels");
+ },
+ get _identityIconLabel () {
+ delete this._identityIconLabel;
+ return this._identityIconLabel = document.getElementById("identity-icon-label");
+ },
+ get _connectionIcon () {
+ delete this._connectionIcon;
+ return this._connectionIcon = document.getElementById("connection-icon");
+ },
+ get _overrideService () {
+ delete this._overrideService;
+ return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ },
+ get _identityIconCountryLabel () {
+ delete this._identityIconCountryLabel;
+ return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
+ },
+ get _identityIcon () {
+ delete this._identityIcon;
+ return this._identityIcon = document.getElementById("identity-icon");
+ },
+ get _permissionList () {
+ delete this._permissionList;
+ return this._permissionList = document.getElementById("identity-popup-permission-list");
+ },
+ get _permissionEmptyHint() {
+ delete this._permissionEmptyHint;
+ return this._permissionEmptyHint = document.getElementById("identity-popup-permission-empty-hint");
+ },
+ get _permissionReloadHint () {
+ delete this._permissionReloadHint;
+ return this._permissionReloadHint = document.getElementById("identity-popup-permission-reload-hint");
+ },
+ get _permissionAnchors () {
+ delete this._permissionAnchors;
+ let permissionAnchors = {};
+ for (let anchor of document.getElementById("blocked-permissions-container").children) {
+ permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
+ }
+ return this._permissionAnchors = permissionAnchors;
+ },
+
+ /**
+ * Handler for mouseclicks on the "More Information" button in the
+ * "identity-popup" panel.
+ */
+ handleMoreInfoClick : function(event) {
+ displaySecurityInfo();
+ event.stopPropagation();
+ this._identityPopup.hidePopup();
+ },
+
+ toggleSubView(name, anchor) {
+ let view = this._identityPopupMultiView;
+ if (view.showingSubView) {
+ view.showMainView();
+ } else {
+ view.showSubView(`identity-popup-${name}View`, anchor);
+ }
+
+ // If an element is focused that's not the anchor, clear the focus.
+ // Elements of hidden views have -moz-user-focus:ignore but setting that
+ // per CSS selector doesn't blur a focused element in those hidden views.
+ if (Services.focus.focusedElement != anchor) {
+ Services.focus.clearFocus(window);
+ }
+ },
+
+ disableMixedContentProtection() {
+ // Use telemetry to measure how often unblocking happens
+ const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
+ let histogram =
+ Services.telemetry.getHistogramById(
+ "MIXED_CONTENT_UNBLOCK_COUNTER");
+ histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
+ // Reload the page with the content unblocked
+ BrowserReloadWithFlags(
+ Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
+ this._identityPopup.hidePopup();
+ },
+
+ enableMixedContentProtection() {
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
+ "MixedContent:ReenableProtection", {});
+ BrowserReload();
+ this._identityPopup.hidePopup();
+ },
+
+ removeCertException() {
+ if (!this._uriHasHost) {
+ Cu.reportError("Trying to revoke a cert exception on a URI without a host?");
+ return;
+ }
+ let host = this._uri.host;
+ let port = this._uri.port > 0 ? this._uri.port : 443;
+ this._overrideService.clearValidityOverride(host, port);
+ BrowserReloadSkipCache();
+ this._identityPopup.hidePopup();
+ },
+
+ /**
+ * Helper to parse out the important parts of _sslStatus (of the SSL cert in
+ * particular) for use in constructing identity UI strings
+ */
+ getIdentityData : function() {
+ var result = {};
+ var cert = this._sslStatus.serverCert;
+
+ // Human readable name of Subject
+ result.subjectOrg = cert.organization;
+
+ // SubjectName fields, broken up for individual access
+ if (cert.subjectName) {
+ result.subjectNameFields = {};
+ cert.subjectName.split(",").forEach(function(v) {
+ var field = v.split("=");
+ this[field[0]] = field[1];
+ }, result.subjectNameFields);
+
+ // Call out city, state, and country specifically
+ result.city = result.subjectNameFields.L;
+ result.state = result.subjectNameFields.ST;
+ result.country = result.subjectNameFields.C;
+ }
+
+ // Human readable name of Certificate Authority
+ result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
+ result.cert = cert;
+
+ return result;
+ },
+
+ /**
+ * Update the identity user interface for the page currently being displayed.
+ *
+ * This examines the SSL certificate metadata, if available, as well as the
+ * connection type and other security-related state information for the page.
+ *
+ * @param state
+ * Bitmask provided by nsIWebProgressListener.onSecurityChange.
+ * @param uri
+ * nsIURI for which the identity UI should be displayed, already
+ * processed by nsIURIFixup.createExposableURI.
+ */
+ updateIdentity(state, uri) {
+ let shouldHidePopup = this._uri && (this._uri.spec != uri.spec);
+ this._state = state;
+
+ // Firstly, populate the state properties required to display the UI. See
+ // the documentation of the individual properties for details.
+ this.setURI(uri);
+ this._sslStatus = gBrowser.securityUI
+ .QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus;
+ if (this._sslStatus) {
+ this._sslStatus.QueryInterface(Ci.nsISSLStatus);
+ }
+
+ // Then, update the user interface with the available data.
+ this.refreshIdentityBlock();
+ // Handle a location change while the Control Center is focused
+ // by closing the popup (bug 1207542)
+ if (shouldHidePopup) {
+ this._identityPopup.hidePopup();
+ }
+ this.showWeakCryptoInfoBar();
+
+ // NOTE: We do NOT update the identity popup (the control center) when
+ // we receive a new security state on the existing page (i.e. from a
+ // subframe). If the user opened the popup and looks at the provided
+ // information we don't want to suddenly change the panel contents.
+ },
+
+ /**
+ * This is called asynchronously when requested by the Logins module, after
+ * the insecure login forms state for the page has been updated.
+ */
+ refreshForInsecureLoginForms() {
+ // Check this._uri because we don't want to refresh the user interface if
+ // this is called before the first page load in the window for any reason.
+ if (!this._uri) {
+ Cu.reportError("Unexpected early call to refreshForInsecureLoginForms.");
+ return;
+ }
+ this.refreshIdentityBlock();
+ },
+
+ updateSharingIndicator() {
+ let tab = gBrowser.selectedTab;
+ let sharing = tab.getAttribute("sharing");
+ if (sharing)
+ this._identityBox.setAttribute("sharing", sharing);
+ else
+ this._identityBox.removeAttribute("sharing");
+
+ this._sharingState = tab._sharingState;
+
+ if (this._identityPopup.state == "open") {
+ this._handleHeightChange(() => this.updateSitePermissions());
+ }
+ },
+
+ /**
+ * Attempt to provide proper IDN treatment for host names
+ */
+ getEffectiveHost: function() {
+ if (!this._IDNService)
+ this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ try {
+ return this._IDNService.convertToDisplayIDN(this._uri.host, {});
+ } catch (e) {
+ // If something goes wrong (e.g. host is an IP address) just fail back
+ // to the full domain.
+ return this._uri.host;
+ }
+ },
+
+ /**
+ * Return the CSS class name to set on the "fullscreen-warning" element to
+ * display information about connection security in the notification shown
+ * when a site enters the fullscreen mode.
+ */
+ get pointerlockFsWarningClassName() {
+ // Note that the fullscreen warning does not handle _isSecureInternalUI.
+ if (this._uriHasHost && this._isEV) {
+ return "verifiedIdentity";
+ }
+ if (this._uriHasHost && this._isSecure) {
+ return "verifiedDomain";
+ }
+ return "unknownIdentity";
+ },
+
+ /**
+ * Updates the identity block user interface with the data from this object.
+ */
+ refreshIdentityBlock() {
+ if (!this._identityBox) {
+ return;
+ }
+
+ let icon_label = "";
+ let tooltip = "";
+ let icon_country_label = "";
+ let icon_labels_dir = "ltr";
+
+ if (this._isSecureInternalUI) {
+ this._identityBox.className = "chromeUI";
+ let brandBundle = document.getElementById("bundle_brand");
+ icon_label = brandBundle.getString("brandShorterName");
+ } else if (this._uriHasHost && this._isEV) {
+ this._identityBox.className = "verifiedIdentity";
+ if (this._isMixedActiveContentBlocked) {
+ this._identityBox.classList.add("mixedActiveBlocked");
+ }
+
+ if (!this._isCertUserOverridden) {
+ // If it's identified, then we can populate the dialog with credentials
+ let iData = this.getIdentityData();
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [iData.caOrg]);
+ icon_label = iData.subjectOrg;
+ if (iData.country)
+ icon_country_label = "(" + iData.country + ")";
+
+ // If the organization name starts with an RTL character, then
+ // swap the positions of the organization and country code labels.
+ // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
+ // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
+ // fixed, this test should be replaced by one adhering to the
+ // Unicode Bidirectional Algorithm proper (at the paragraph level).
+ icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
+ "rtl" : "ltr";
+ }
+
+ } else if (this._uriHasHost && this._isSecure) {
+ this._identityBox.className = "verifiedDomain";
+ if (this._isMixedActiveContentBlocked) {
+ this._identityBox.classList.add("mixedActiveBlocked");
+ }
+ if (!this._isCertUserOverridden) {
+ // It's a normal cert, verifier is the CA Org.
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [this.getIdentityData().caOrg]);
+ }
+ } else {
+ this._identityBox.className = "unknownIdentity";
+ if (this._isBroken) {
+ if (this._isMixedActiveContentLoaded) {
+ this._identityBox.classList.add("mixedActiveContent");
+ } else if (this._isMixedActiveContentBlocked) {
+ this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked");
+ } else if (this._isMixedPassiveContentLoaded) {
+ this._identityBox.classList.add("mixedDisplayContent");
+ } else {
+ this._identityBox.classList.add("weakCipher");
+ }
+ }
+ if (this._hasInsecureLoginForms) {
+ // Insecure login forms can only be present on "unknown identity"
+ // pages, either already insecure or with mixed active content loaded.
+ this._identityBox.classList.add("insecureLoginForms");
+ }
+ }
+
+ if (this._isCertUserOverridden) {
+ this._identityBox.classList.add("certUserOverridden");
+ // Cert is trusted because of a security exception, verifier is a special string.
+ tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
+ }
+
+ let permissionAnchors = this._permissionAnchors;
+
+ // hide all permission icons
+ for (let icon of Object.values(permissionAnchors)) {
+ icon.removeAttribute("showing");
+ }
+
+ // keeps track if we should show an indicator that there are active permissions
+ let hasGrantedPermissions = false;
+
+ // show permission icons
+ for (let permission of SitePermissions.getAllByURI(this._uri)) {
+ if (permission.state === SitePermissions.BLOCK) {
+
+ let icon = permissionAnchors[permission.id];
+ if (icon) {
+ icon.setAttribute("showing", "true");
+ }
+
+ } else if (permission.state === SitePermissions.ALLOW ||
+ permission.state === SitePermissions.SESSION) {
+ hasGrantedPermissions = true;
+ }
+ }
+
+ if (hasGrantedPermissions) {
+ this._identityBox.classList.add("grantedPermissions");
+ }
+
+ // Push the appropriate strings out to the UI
+ this._connectionIcon.tooltipText = tooltip;
+ this._identityIconLabels.tooltipText = tooltip;
+ this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip");
+ this._identityIconLabel.value = icon_label;
+ this._identityIconCountryLabel.value = icon_country_label;
+ // Set cropping and direction
+ this._identityIconLabel.crop = icon_country_label ? "end" : "center";
+ this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
+ // Hide completely if the organization label is empty
+ this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
+ },
+
+ /**
+ * Show the weak crypto notification bar.
+ */
+ showWeakCryptoInfoBar() {
+ if (!this._uriHasHost || !this._isBroken || !this._sslStatus.cipherName ||
+ this._sslStatus.cipherName.indexOf("_RC4_") < 0) {
+ return;
+ }
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("weak-crypto");
+ if (notification) {
+ return;
+ }
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let message = gNavigatorBundle.getFormattedString("weakCryptoOverriding.message",
+ [brandShortName]);
+
+ let host = this._uri.host;
+ let port = 443;
+ try {
+ if (this._uri.port > 0) {
+ port = this._uri.port;
+ }
+ } catch (e) {}
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("revokeOverride.label"),
+ accessKey: gNavigatorBundle.getString("revokeOverride.accesskey"),
+ callback: function (aNotification, aButton) {
+ try {
+ let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
+ .getService(Ci.nsIWeakCryptoOverride);
+ weakCryptoOverride.removeWeakCryptoOverride(host, port,
+ PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, "weak-crypto", null,
+ priority, buttons);
+ },
+
+ /**
+ * Set up the title and content messages for the identity message popup,
+ * based on the specified mode, and the details of the SSL cert, where
+ * applicable
+ */
+ refreshIdentityPopup() {
+ // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ this._identityPopupMixedContentLearnMore
+ .setAttribute("href", baseURL + "mixed-content");
+ this._identityPopupInsecureLoginFormsLearnMore
+ .setAttribute("href", baseURL + "insecure-password");
+
+ // Determine connection security information.
+ let connection = "not-secure";
+ if (this._isSecureInternalUI) {
+ connection = "chrome";
+ } else if (this._isURILoadedFromFile) {
+ connection = "file";
+ } else if (this._isEV) {
+ connection = "secure-ev";
+ } else if (this._isCertUserOverridden) {
+ connection = "secure-cert-user-overridden";
+ } else if (this._isSecure) {
+ connection = "secure";
+ }
+
+ // Determine if there are insecure login forms.
+ let loginforms = "secure";
+ if (this._hasInsecureLoginForms) {
+ loginforms = "insecure";
+ }
+
+ // Determine the mixed content state.
+ let mixedcontent = [];
+ if (this._isMixedPassiveContentLoaded) {
+ mixedcontent.push("passive-loaded");
+ }
+ if (this._isMixedActiveContentLoaded) {
+ mixedcontent.push("active-loaded");
+ } else if (this._isMixedActiveContentBlocked) {
+ mixedcontent.push("active-blocked");
+ }
+ mixedcontent = mixedcontent.join(" ");
+
+ // We have no specific flags for weak ciphers (yet). If a connection is
+ // broken and we can't detect any mixed content loaded then it's a weak
+ // cipher.
+ let ciphers = "";
+ if (this._isBroken && !this._isMixedActiveContentLoaded && !this._isMixedPassiveContentLoaded) {
+ ciphers = "weak";
+ }
+
+ // Update all elements.
+ let elementIDs = [
+ "identity-popup",
+ "identity-popup-securityView-body",
+ ];
+
+ function updateAttribute(elem, attr, value) {
+ if (value) {
+ elem.setAttribute(attr, value);
+ } else {
+ elem.removeAttribute(attr);
+ }
+ }
+
+ for (let id of elementIDs) {
+ let element = document.getElementById(id);
+ updateAttribute(element, "connection", connection);
+ updateAttribute(element, "loginforms", loginforms);
+ updateAttribute(element, "ciphers", ciphers);
+ updateAttribute(element, "mixedcontent", mixedcontent);
+ updateAttribute(element, "isbroken", this._isBroken);
+ }
+
+ // Initialize the optional strings to empty values
+ let supplemental = "";
+ let verifier = "";
+ let host = "";
+ let owner = "";
+ let hostless = false;
+
+ try {
+ host = this.getEffectiveHost();
+ } catch (e) {
+ // Some URIs might have no hosts.
+ }
+
+ // Fallback for special protocols.
+ if (!host) {
+ host = this._uri.specIgnoringRef;
+ // Special URIs without a host (eg, about:) should crop the end so
+ // the protocol can be seen.
+ hostless = true;
+ }
+
+ // Fill in the CA name if we have a valid TLS certificate.
+ if (this._isSecure || this._isCertUserOverridden) {
+ verifier = this._identityIconLabels.tooltipText;
+ }
+
+ // Fill in organization information if we have a valid EV certificate.
+ if (this._isEV) {
+ let iData = this.getIdentityData();
+ host = owner = iData.subjectOrg;
+ verifier = this._identityIconLabels.tooltipText;
+
+ // Build an appropriate supplemental block out of whatever location data we have
+ if (iData.city)
+ supplemental += iData.city + "\n";
+ if (iData.state && iData.country)
+ supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
+ [iData.state, iData.country]);
+ else if (iData.state) // State only
+ supplemental += iData.state;
+ else if (iData.country) // Country only
+ supplemental += iData.country;
+ }
+
+ // Push the appropriate strings out to the UI.
+ this._identityPopupContentHosts.forEach((el) => {
+ el.textContent = host;
+ el.hidden = hostless;
+ });
+ this._identityPopupContentHostless.forEach((el) => {
+ el.setAttribute("value", host);
+ el.hidden = !hostless;
+ });
+ this._identityPopupContentOwner.textContent = owner;
+ this._identityPopupContentSupp.textContent = supplemental;
+ this._identityPopupContentVerif.textContent = verifier;
+
+ // Update per-site permissions section.
+ this.updateSitePermissions();
+ },
+
+ setURI(uri) {
+ this._uri = uri;
+
+ try {
+ this._uri.host;
+ this._uriHasHost = true;
+ } catch (ex) {
+ this._uriHasHost = false;
+ }
+
+ let whitelist = /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|searchreset|sessionrestore|support|welcomeback)(?:[?#]|$)/i;
+ this._isSecureInternalUI = uri.schemeIs("about") && whitelist.test(uri.path);
+
+ // Create a channel for the sole purpose of getting the resolved URI
+ // of the request to determine if it's loaded from the file system.
+ this._isURILoadedFromFile = false;
+ let chanOptions = {uri: this._uri, loadUsingSystemPrincipal: true};
+ let resolvedURI;
+ try {
+ resolvedURI = NetUtil.newChannel(chanOptions).URI;
+ if (resolvedURI.schemeIs("jar")) {
+ // Given a URI "jar:<jar-file-uri>!/<jar-entry>"
+ // create a new URI using <jar-file-uri>!/<jar-entry>
+ resolvedURI = NetUtil.newURI(resolvedURI.path);
+ }
+ // Check the URI again after resolving.
+ this._isURILoadedFromFile = resolvedURI.schemeIs("file");
+ } catch (ex) {
+ // NetUtil's methods will throw for malformed URIs and the like
+ }
+ },
+
+ /**
+ * Click handler for the identity-box element in primary chrome.
+ */
+ handleIdentityButtonEvent : function(event) {
+ event.stopPropagation();
+
+ if ((event.type == "click" && event.button != 0) ||
+ (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
+ event.keyCode != KeyEvent.DOM_VK_RETURN)) {
+ return; // Left click, space or enter only
+ }
+
+ // Don't allow left click, space or enter if the location has been modified.
+ if (gURLBar.getAttribute("pageproxystate") != "valid") {
+ return;
+ }
+
+ this._popupTriggeredByKeyboard = event.type == "keypress";
+
+ // Make sure that the display:none style we set in xul is removed now that
+ // the popup is actually needed
+ this._identityPopup.hidden = false;
+
+ // Update the popup strings
+ this.refreshIdentityPopup();
+
+ // Add the "open" attribute to the identity box for styling
+ this._identityBox.setAttribute("open", "true");
+
+ // Now open the popup, anchored off the primary chrome element
+ this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
+ },
+
+ onPopupShown(event) {
+ if (event.target == this._identityPopup) {
+ if (this._popupTriggeredByKeyboard) {
+ // Move focus to the next available element in the identity popup.
+ // This is required by role=alertdialog and fixes an issue where
+ // an already open panel would steal focus from the identity popup.
+ document.commandDispatcher.advanceFocusIntoSubtree(this._identityPopup);
+ }
+
+ window.addEventListener("focus", this, true);
+ }
+ },
+
+ onPopupHidden(event) {
+ if (event.target == this._identityPopup) {
+ window.removeEventListener("focus", this, true);
+ this._identityBox.removeAttribute("open");
+ }
+ },
+
+ handleEvent(event) {
+ let elem = document.activeElement;
+ let position = elem.compareDocumentPosition(this._identityPopup);
+
+ if (!(position & (Node.DOCUMENT_POSITION_CONTAINS |
+ Node.DOCUMENT_POSITION_CONTAINED_BY)) &&
+ !this._identityPopup.hasAttribute("noautohide")) {
+ // Hide the panel when focusing an element that is
+ // neither an ancestor nor descendant unless the panel has
+ // @noautohide (e.g. for a tour).
+ this._identityPopup.hidePopup();
+ }
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "perm-changed") {
+ this.refreshIdentityBlock();
+ }
+ },
+
+ onDragStart: function (event) {
+ if (gURLBar.getAttribute("pageproxystate") != "valid")
+ return;
+
+ let value = gBrowser.currentURI.spec;
+ let urlString = value + "\n" + gBrowser.contentTitle;
+ let htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+
+ let dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", urlString);
+ dt.setData("text/uri-list", value);
+ dt.setData("text/plain", value);
+ dt.setData("text/html", htmlString);
+ dt.setDragImage(this._identityIcon, 16, 16);
+ },
+
+ onLocationChange: function () {
+ this._permissionJustRemoved = false;
+ this.updatePermissionHint();
+ },
+
+ updatePermissionHint: function () {
+ if (!this._permissionList.hasChildNodes() && !this._permissionJustRemoved) {
+ this._permissionEmptyHint.removeAttribute("hidden");
+ } else {
+ this._permissionEmptyHint.setAttribute("hidden", "true");
+ }
+
+ if (this._permissionJustRemoved) {
+ this._permissionReloadHint.removeAttribute("hidden");
+ } else {
+ this._permissionReloadHint.setAttribute("hidden", "true");
+ }
+ },
+
+ updateSitePermissions: function () {
+ while (this._permissionList.hasChildNodes())
+ this._permissionList.removeChild(this._permissionList.lastChild);
+
+ let uri = gBrowser.currentURI;
+
+ let permissions = SitePermissions.getPermissionDetailsByURI(uri);
+ if (this._sharingState) {
+ // If WebRTC device or screen permissions are in use, we need to find
+ // the associated permission item to set the inUse field to true.
+ for (let id of ["camera", "microphone", "screen"]) {
+ if (this._sharingState[id]) {
+ let found = false;
+ for (let permission of permissions) {
+ if (permission.id != id)
+ continue;
+ found = true;
+ permission.inUse = true;
+ break;
+ }
+ if (!found) {
+ // If the permission item we were looking for doesn't exist,
+ // the user has temporarily allowed sharing and we need to add
+ // an item in the permissions array to reflect this.
+ let permission = SitePermissions.getPermissionItem(id);
+ permission.inUse = true;
+ permissions.push(permission);
+ }
+ }
+ }
+ }
+ for (let permission of permissions) {
+ let item = this._createPermissionItem(permission);
+ this._permissionList.appendChild(item);
+ }
+
+ this.updatePermissionHint();
+ },
+
+ _handleHeightChange: function(aFunction, aWillShowReloadHint) {
+ let heightBefore = getComputedStyle(this._permissionList).height;
+ aFunction();
+ let heightAfter = getComputedStyle(this._permissionList).height;
+ // Showing the reload hint increases the height, we need to account for it.
+ if (aWillShowReloadHint) {
+ heightAfter = parseInt(heightAfter) +
+ parseInt(getComputedStyle(this._permissionList.nextSibling).height);
+ }
+ let heightChange = parseInt(heightAfter) - parseInt(heightBefore);
+ if (heightChange)
+ this._identityPopupMultiView.setHeightToFit(heightChange);
+ },
+
+ _createPermissionItem: function (aPermission) {
+ let container = document.createElement("hbox");
+ container.setAttribute("class", "identity-popup-permission-item");
+ container.setAttribute("align", "center");
+
+ let img = document.createElement("image");
+ let classes = "identity-popup-permission-icon " + aPermission.id + "-icon";
+ if (aPermission.state == SitePermissions.BLOCK)
+ classes += " blocked-permission-icon";
+ if (aPermission.inUse)
+ classes += " in-use";
+ img.setAttribute("class", classes);
+
+ let nameLabel = document.createElement("label");
+ nameLabel.setAttribute("flex", "1");
+ nameLabel.setAttribute("class", "identity-popup-permission-label");
+ nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
+
+ let stateLabel = document.createElement("label");
+ stateLabel.setAttribute("flex", "1");
+ stateLabel.setAttribute("class", "identity-popup-permission-state-label");
+ stateLabel.textContent = SitePermissions.getStateLabel(
+ aPermission.id, aPermission.state, aPermission.inUse || false);
+
+ let button = document.createElement("button");
+ button.setAttribute("class", "identity-popup-permission-remove-button");
+ let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
+ button.setAttribute("tooltiptext", tooltiptext);
+ button.addEventListener("command", () => {
+ this._handleHeightChange(() =>
+ this._permissionList.removeChild(container), !this._permissionJustRemoved);
+ if (aPermission.inUse &&
+ ["camera", "microphone", "screen"].includes(aPermission.id)) {
+ let windowId = this._sharingState.windowId;
+ if (aPermission.id == "screen") {
+ windowId = "screen:" + windowId;
+ } else {
+ // If we set persistent permissions or the sharing has
+ // started due to existing persistent permissions, we need
+ // to handle removing these even for frames with different hostnames.
+ let uris = gBrowser.selectedBrowser._devicePermissionURIs || [];
+ for (let uri of uris) {
+ // It's not possible to stop sharing one of camera/microphone
+ // without the other.
+ for (let id of ["camera", "microphone"]) {
+ if (this._sharingState[id] &&
+ SitePermissions.get(uri, id) == SitePermissions.ALLOW)
+ SitePermissions.remove(uri, id);
+ }
+ }
+ }
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.sendAsyncMessage("webrtc:StopSharing", windowId);
+ }
+ SitePermissions.remove(gBrowser.currentURI, aPermission.id);
+ this._permissionJustRemoved = true;
+ this.updatePermissionHint();
+
+ // Set telemetry values for clearing a permission
+ let histogram = Services.telemetry.getKeyedHistogramById("WEB_PERMISSION_CLEARED");
+
+ let permissionType = 0;
+ if (aPermission.state == SitePermissions.ALLOW) {
+ // 1 : clear permanently allowed permission
+ permissionType = 1;
+ } else if (aPermission.state == SitePermissions.BLOCK) {
+ // 2 : clear permanently blocked permission
+ permissionType = 2;
+ }
+ // 3 : TODO clear temporary allowed permission
+ // 4 : TODO clear temporary blocked permission
+
+ histogram.add("(all)", permissionType);
+ histogram.add(aPermission.id, permissionType);
+ });
+
+ container.appendChild(img);
+ container.appendChild(nameLabel);
+ container.appendChild(stateLabel);
+ container.appendChild(button);
+
+ return container;
+ }
+};
+
+function getNotificationBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getNotificationBox(foundBrowser)
+ return null;
+}
+
+function getTabModalPromptBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getTabModalPromptBox(foundBrowser);
+ return null;
+}
+
+/* DEPRECATED */
+function getBrowser() {
+ return gBrowser;
+}
+function getNavToolbox() {
+ return gNavToolbox;
+}
+
+var gPrivateBrowsingUI = {
+ init: function PBUI_init() {
+ // Do nothing for normal windows
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+
+ // Disable the Clear Recent History... menu item when in PB mode
+ // temporary fix until bug 463607 is fixed
+ document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
+
+ if (window.location.href == getBrowserURL()) {
+ // Adjust the window's title
+ let docElement = document.documentElement;
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ docElement.setAttribute("title",
+ docElement.getAttribute("title_privatebrowsing"));
+ docElement.setAttribute("titlemodifier",
+ docElement.getAttribute("titlemodifier_privatebrowsing"));
+ }
+ docElement.setAttribute("privatebrowsingmode",
+ PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary");
+ gBrowser.updateTitlebar();
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Adjust the New Window menu entries
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow" },
+ ].forEach(function(menu) {
+ let newWindow = document.getElementById(menu.normal);
+ let newPrivateWindow = document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ newPrivateWindow.hidden = true;
+ newWindow.label = newPrivateWindow.label;
+ newWindow.accessKey = newPrivateWindow.accessKey;
+ newWindow.command = newPrivateWindow.command;
+ }
+ });
+ }
+ }
+
+ let urlBarSearchParam = gURLBar.getAttribute("autocompletesearchparam") || "";
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing &&
+ !urlBarSearchParam.includes("disable-private-actions")) {
+ // Disable switch to tab autocompletion for private windows.
+ // We leave it enabled for permanent private browsing mode though.
+ urlBarSearchParam += " disable-private-actions";
+ }
+ if (!urlBarSearchParam.includes("private-window")) {
+ urlBarSearchParam += " private-window";
+ }
+ gURLBar.setAttribute("autocompletesearchparam", urlBarSearchParam);
+ }
+};
+
+var gRemoteTabsUI = {
+ init: function() {
+ if (window.location.href != getBrowserURL() &&
+ // Also check hidden window for the Mac no-window case
+ window.location.href != "chrome://browser/content/hiddenWindow.xul") {
+ return;
+ }
+
+ if (AppConstants.platform == "macosx" &&
+ Services.prefs.getBoolPref("layers.acceleration.disabled")) {
+ // On OS X, "Disable Hardware Acceleration" also disables OMTC and forces
+ // a fallback to Basic Layers. This is incompatible with e10s.
+ return;
+ }
+
+ let newNonRemoteWindow = document.getElementById("menu_newNonRemoteWindow");
+ let autostart = Services.appinfo.browserTabsRemoteAutostart;
+ newNonRemoteWindow.hidden = !autostart;
+ }
+};
+
+/**
+ * Switch to a tab that has a given URI, and focuses its browser window.
+ * If a matching tab is in this window, it will be switched to. Otherwise, other
+ * windows will be searched.
+ *
+ * @param aURI
+ * URI to search for
+ * @param aOpenNew
+ * True to open a new tab and switch to it, if no existing tab is found.
+ * If no suitable window is found, a new one will be opened.
+ * @param aOpenParams
+ * If switching to this URI results in us opening a tab, aOpenParams
+ * will be the parameter object that gets passed to openUILinkIn. Please
+ * see the documentation for openUILinkIn to see what parameters can be
+ * passed via this object.
+ * This object also allows:
+ * - 'ignoreFragment' property to be set to true to exclude fragment-portion
+ * matching when comparing URIs.
+ * If set to "whenComparing", the fragment will be unmodified.
+ * If set to "whenComparingAndReplace", the fragment will be replaced.
+ * - 'ignoreQueryString' boolean property to be set to true to exclude query string
+ * matching when comparing URIs.
+ * - 'replaceQueryString' boolean property to be set to true to exclude query string
+ * matching when comparing URIs and overwrite the initial query string with
+ * the one from the new URI.
+ * @return True if an existing tab was found, false otherwise
+ */
+function switchToTabHavingURI(aURI, aOpenNew, aOpenParams={}) {
+ // Certain URLs can be switched to irrespective of the source or destination
+ // window being in private browsing mode:
+ const kPrivateBrowsingWhitelist = new Set([
+ "about:addons",
+ ]);
+
+ let ignoreFragment = aOpenParams.ignoreFragment;
+ let ignoreQueryString = aOpenParams.ignoreQueryString;
+ let replaceQueryString = aOpenParams.replaceQueryString;
+
+ // These properties are only used by switchToTabHavingURI and should
+ // not be used as a parameter for the new load.
+ delete aOpenParams.ignoreFragment;
+ delete aOpenParams.ignoreQueryString;
+ delete aOpenParams.replaceQueryString;
+
+ // This will switch to the tab in aWindow having aURI, if present.
+ function switchIfURIInWindow(aWindow) {
+ // Only switch to the tab if neither the source nor the destination window
+ // are private and they are not in permanent private browsing mode
+ if (!kPrivateBrowsingWhitelist.has(aURI.spec) &&
+ (PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return false;
+ }
+
+ // Remove the query string, fragment, both, or neither from a given url.
+ function cleanURL(url, removeQuery, removeFragment) {
+ let ret = url;
+ if (removeFragment) {
+ ret = ret.split("#")[0];
+ if (removeQuery) {
+ // This removes a query, if present before the fragment.
+ ret = ret.split("?")[0];
+ }
+ } else if (removeQuery) {
+ // This is needed in case there is a fragment after the query.
+ let fragment = ret.split("#")[1];
+ ret = ret.split("?")[0].concat(
+ (fragment != undefined) ? "#".concat(fragment) : "");
+ }
+ return ret;
+ }
+
+ // Need to handle nsSimpleURIs here too (e.g. about:...), which don't
+ // work correctly with URL objects - so treat them as strings
+ let ignoreFragmentWhenComparing = typeof ignoreFragment == "string" &&
+ ignoreFragment.startsWith("whenComparing");
+ let requestedCompare = cleanURL(
+ aURI.spec, ignoreQueryString || replaceQueryString, ignoreFragmentWhenComparing);
+ let browsers = aWindow.gBrowser.browsers;
+ for (let i = 0; i < browsers.length; i++) {
+ let browser = browsers[i];
+ let browserCompare = cleanURL(
+ browser.currentURI.spec, ignoreQueryString || replaceQueryString, ignoreFragmentWhenComparing);
+ if (requestedCompare == browserCompare) {
+ aWindow.focus();
+ if (ignoreFragment == "whenComparingAndReplace" || replaceQueryString) {
+ browser.loadURI(aURI.spec);
+ }
+ aWindow.gBrowser.tabContainer.selectedIndex = i;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // This can be passed either nsIURI or a string.
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = Services.io.newURI(aURI, null, null);
+
+ let isBrowserWindow = !!window.gBrowser;
+
+ // Prioritise this window.
+ if (isBrowserWindow && switchIfURIInWindow(window))
+ return true;
+
+ for (let browserWin of browserWindows()) {
+ // Skip closed (but not yet destroyed) windows,
+ // and the current window (which was checked earlier).
+ if (browserWin.closed || browserWin == window)
+ continue;
+ if (switchIfURIInWindow(browserWin))
+ return true;
+ }
+
+ // No opened tab has that url.
+ if (aOpenNew) {
+ if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab))
+ openUILinkIn(aURI.spec, "current", aOpenParams);
+ else
+ openUILinkIn(aURI.spec, "tab", aOpenParams);
+ }
+
+ return false;
+}
+
+var RestoreLastSessionObserver = {
+ init: function () {
+ if (SessionStore.canRestoreLastSession &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ Services.obs.addObserver(this, "sessionstore-last-session-cleared", true);
+ goSetCommandEnabled("Browser:RestoreLastSession", true);
+ }
+ },
+
+ observe: function () {
+ // The last session can only be restored once so there's
+ // no way we need to re-enable our menu item.
+ Services.obs.removeObserver(this, "sessionstore-last-session-cleared");
+ goSetCommandEnabled("Browser:RestoreLastSession", false);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+function restoreLastSession() {
+ SessionStore.restoreLastSession();
+}
+
+var TabContextMenu = {
+ contextTab: null,
+ _updateToggleMuteMenuItem(aTab, aConditionFn) {
+ ["muted", "soundplaying"].forEach(attr => {
+ if (!aConditionFn || aConditionFn(attr)) {
+ if (aTab.hasAttribute(attr)) {
+ aTab.toggleMuteMenuItem.setAttribute(attr, "true");
+ } else {
+ aTab.toggleMuteMenuItem.removeAttribute(attr);
+ }
+ }
+ });
+ },
+ updateContextMenu: function updateContextMenu(aPopupMenu) {
+ this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
+ aPopupMenu.triggerNode : gBrowser.selectedTab;
+ let disabled = gBrowser.tabs.length == 1;
+
+ var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ if (AppConstants.E10S_TESTING_ONLY) {
+ menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-remote");
+ for (let menuItem of menuItems) {
+ menuItem.hidden = !gMultiProcessBrowser;
+ if (menuItem.id == "context_openNonRemoteWindow") {
+ menuItem.disabled = !!parseInt(this.contextTab.getAttribute("usercontextid"));
+ }
+ }
+ }
+
+ disabled = gBrowser.visibleTabs.length == 1;
+ menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ // Session store
+ document.getElementById("context_undoCloseTab").disabled =
+ SessionStore.getClosedTabCount(window) == 0;
+
+ // Only one of pin/unpin should be visible
+ document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
+ document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
+
+ // Disable "Close Tabs to the Right" if there are no tabs
+ // following it and hide it when the user rightclicked on a pinned
+ // tab.
+ document.getElementById("context_closeTabsToTheEnd").disabled =
+ gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
+ document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned;
+
+ // Disable "Close other Tabs" if there is only one unpinned tab and
+ // hide it when the user rightclicked on a pinned tab.
+ let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
+ document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1;
+ document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned;
+
+ // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible.
+ let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs");
+ bookmarkAllTabs.hidden = this.contextTab.pinned;
+ if (!bookmarkAllTabs.hidden)
+ PlacesCommandHook.updateBookmarkAllTabsCommand();
+
+ // Adjust the state of the toggle mute menu item.
+ let toggleMute = document.getElementById("context_toggleMuteTab");
+ if (this.contextTab.hasAttribute("muted")) {
+ toggleMute.label = gNavigatorBundle.getString("unmuteTab.label");
+ toggleMute.accessKey = gNavigatorBundle.getString("unmuteTab.accesskey");
+ } else {
+ toggleMute.label = gNavigatorBundle.getString("muteTab.label");
+ toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey");
+ }
+
+ this.contextTab.toggleMuteMenuItem = toggleMute;
+ this._updateToggleMuteMenuItem(this.contextTab);
+
+ this.contextTab.addEventListener("TabAttrModified", this, false);
+ aPopupMenu.addEventListener("popuphiding", this, false);
+
+ gFxAccounts.updateTabContextMenu(aPopupMenu);
+ },
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "popuphiding":
+ gBrowser.removeEventListener("TabAttrModified", this);
+ aEvent.target.removeEventListener("popuphiding", this);
+ break;
+ case "TabAttrModified":
+ let tab = aEvent.target;
+ this._updateToggleMuteMenuItem(tab,
+ attr => aEvent.detail.changed.indexOf(attr) >= 0);
+ break;
+ }
+ }
+};
+
+Object.defineProperty(this, "HUDService", {
+ get: function HUDService_getter() {
+ let devtools = Cu.import("resource://devtools/shared/Loader.jsm", {}).devtools;
+ return devtools.require("devtools/client/webconsole/hudservice");
+ },
+ configurable: true,
+ enumerable: true
+});
+
+// Prompt user to restart the browser in safe mode
+function safeModeRestart() {
+ if (Services.appinfo.inSafeMode) {
+ 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.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
+ return;
+ }
+
+ Services.obs.notifyObservers(null, "restart-in-safe-mode", "");
+}
+
+/* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "tab" new tab
+ * "tabshifted" same as "tab" but in background if default is to select new
+ * tabs, and vice versa
+ * "window" new window
+ *
+ * delta is the offset to the history entry that you want to load.
+ */
+function duplicateTabIn(aTab, where, delta) {
+ switch (where) {
+ case "window":
+ let otherWin = OpenBrowserWindow();
+ let delayedStartupFinished = (subject, topic) => {
+ if (topic == "browser-delayed-startup-finished" &&
+ subject == otherWin) {
+ Services.obs.removeObserver(delayedStartupFinished, topic);
+ let otherGBrowser = otherWin.gBrowser;
+ let otherTab = otherGBrowser.selectedTab;
+ SessionStore.duplicateTab(otherWin, aTab, delta);
+ otherGBrowser.removeTab(otherTab, { animate: false });
+ }
+ };
+
+ Services.obs.addObserver(delayedStartupFinished,
+ "browser-delayed-startup-finished",
+ false);
+ break;
+ case "tabshifted":
+ SessionStore.duplicateTab(window, aTab, delta);
+ // A background tab has been opened, nothing else to do here.
+ break;
+ case "tab":
+ let newTab = SessionStore.duplicateTab(window, aTab, delta);
+ gBrowser.selectedTab = newTab;
+ break;
+ }
+}
+
+var Scratchpad = {
+ openScratchpad: function SP_openScratchpad() {
+ return this.ScratchpadManager.openScratchpad();
+ }
+};
+
+XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() {
+ let tmp = {};
+ Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm", tmp);
+ return tmp.ScratchpadManager;
+});
+
+var ResponsiveUI = {
+ toggle: function RUI_toggle() {
+ this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab);
+ }
+};
+
+XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() {
+ let tmp = {};
+ Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", tmp);
+ return tmp.ResponsiveUIManager;
+});
+
+var MousePosTracker = {
+ _listeners: new Set(),
+ _x: 0,
+ _y: 0,
+ get _windowUtils() {
+ delete this._windowUtils;
+ return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ addListener: function (listener) {
+ if (this._listeners.has(listener))
+ return;
+
+ listener._hover = false;
+ this._listeners.add(listener);
+
+ this._callListener(listener);
+ },
+
+ removeListener: function (listener) {
+ this._listeners.delete(listener);
+ },
+
+ handleEvent: function (event) {
+ var fullZoom = this._windowUtils.fullZoom;
+ this._x = event.screenX / fullZoom - window.mozInnerScreenX;
+ this._y = event.screenY / fullZoom - window.mozInnerScreenY;
+
+ this._listeners.forEach(function (listener) {
+ try {
+ this._callListener(listener);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }, this);
+ },
+
+ _callListener: function (listener) {
+ let rect = listener.getMouseTargetRect();
+ let hover = this._x >= rect.left &&
+ this._x <= rect.right &&
+ this._y >= rect.top &&
+ this._y <= rect.bottom;
+
+ if (hover == listener._hover)
+ return;
+
+ listener._hover = hover;
+
+ if (hover) {
+ if (listener.onMouseEnter)
+ listener.onMouseEnter();
+ } else if (listener.onMouseLeave) {
+ listener.onMouseLeave();
+ }
+ }
+};
+
+var ToolbarIconColor = {
+ init: function () {
+ this._initialized = true;
+
+ window.addEventListener("activate", this);
+ window.addEventListener("deactivate", this);
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+
+ // If the window isn't active now, we assume that it has never been active
+ // before and will soon become active such that inferFromText will be
+ // called from the initial activate event.
+ if (Services.focus.activeWindow == window)
+ this.inferFromText();
+ },
+
+ uninit: function () {
+ this._initialized = false;
+
+ window.removeEventListener("activate", this);
+ window.removeEventListener("deactivate", this);
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "activate":
+ case "deactivate":
+ this.inferFromText();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "lightweight-theme-styling-update":
+ // inferFromText needs to run after LightweightThemeConsumer.jsm's
+ // lightweight-theme-styling-update observer.
+ setTimeout(() => { this.inferFromText(); }, 0);
+ break;
+ }
+ },
+
+ inferFromText: function () {
+ if (!this._initialized)
+ return;
+
+ function parseRGB(aColorString) {
+ let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
+ rgb.shift();
+ return rgb.map(x => parseInt(x));
+ }
+
+ let toolbarSelector = "#navigator-toolbox > toolbar:not([collapsed=true]):not(#addon-bar)";
+ if (AppConstants.platform == "macosx")
+ toolbarSelector += ":not([type=menubar])";
+
+ // The getComputedStyle calls and setting the brighttext are separated in
+ // two loops to avoid flushing layout and making it dirty repeatedly.
+
+ let luminances = new Map;
+ for (let toolbar of document.querySelectorAll(toolbarSelector)) {
+ let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
+ let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+ luminances.set(toolbar, luminance);
+ }
+
+ for (let [toolbar, luminance] of luminances) {
+ if (luminance <= 110)
+ toolbar.removeAttribute("brighttext");
+ else
+ toolbar.setAttribute("brighttext", "true");
+ }
+ }
+}
+
+var PanicButtonNotifier = {
+ init: function() {
+ this._initialized = true;
+ if (window.PanicButtonNotifierShouldNotify) {
+ delete window.PanicButtonNotifierShouldNotify;
+ this.notify();
+ }
+ },
+ notify: function() {
+ if (!this._initialized) {
+ window.PanicButtonNotifierShouldNotify = true;
+ return;
+ }
+ // Display notification panel here...
+ try {
+ let popup = document.getElementById("panic-button-success-notification");
+ popup.hidden = false;
+ let widget = CustomizableUI.getWidget("panic-button").forWindow(window);
+ let anchor = widget.anchor;
+ anchor = document.getAnonymousElementByAttribute(anchor, "class", "toolbarbutton-icon");
+ popup.openPopup(anchor, popup.getAttribute("position"));
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+ close: function() {
+ let popup = document.getElementById("panic-button-success-notification");
+ popup.hidePopup();
+ },
+};
+
+var AboutPrivateBrowsingListener = {
+ init: function () {
+ window.messageManager.addMessageListener(
+ "AboutPrivateBrowsing:OpenPrivateWindow",
+ msg => {
+ OpenBrowserWindow({private: true});
+ });
+ window.messageManager.addMessageListener(
+ "AboutPrivateBrowsing:ToggleTrackingProtection",
+ msg => {
+ const PREF = "privacy.trackingprotection.pbmode.enabled";
+ Services.prefs.setBoolPref(PREF, !Services.prefs.getBoolPref(PREF));
+ });
+ window.messageManager.addMessageListener(
+ "AboutPrivateBrowsing:DontShowIntroPanelAgain",
+ msg => {
+ TrackingProtection.dontShowIntroPanelAgain();
+ });
+ }
+};
+
+function TabModalPromptBox(browser) {
+ this._weakBrowserRef = Cu.getWeakReference(browser);
+}
+
+TabModalPromptBox.prototype = {
+ _promptCloseCallback(onCloseCallback, principalToAllowFocusFor, allowFocusCheckbox, ...args) {
+ if (principalToAllowFocusFor && allowFocusCheckbox &&
+ allowFocusCheckbox.checked) {
+ Services.perms.addFromPrincipal(principalToAllowFocusFor, "focus-tab-by-prompt",
+ Services.perms.ALLOW_ACTION);
+ }
+ onCloseCallback.apply(this, args);
+ },
+
+ appendPrompt(args, onCloseCallback) {
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
+ let browser = this.browser;
+ browser.parentNode.insertBefore(newPrompt, browser.nextSibling);
+ browser.setAttribute("tabmodalPromptShowing", true);
+
+ newPrompt.clientTop; // style flush to assure binding is attached
+
+ let prompts = this.listPrompts();
+ if (prompts.length > 1) {
+ // Let's hide ourself behind the current prompt.
+ newPrompt.hidden = true;
+ }
+
+ let principalToAllowFocusFor = this._allowTabFocusByPromptPrincipal;
+ delete this._allowTabFocusByPromptPrincipal;
+
+ let allowFocusCheckbox; // Define outside the if block so we can bind it into the callback.
+ let hostForAllowFocusCheckbox = "";
+ try {
+ hostForAllowFocusCheckbox = principalToAllowFocusFor.URI.host;
+ } catch (ex) { /* Ignore exceptions for host-less URIs */ }
+ if (hostForAllowFocusCheckbox) {
+ let allowFocusRow = document.createElementNS(XUL_NS, "row");
+ allowFocusCheckbox = document.createElementNS(XUL_NS, "checkbox");
+ let spacer = document.createElementNS(XUL_NS, "spacer");
+ allowFocusRow.appendChild(spacer);
+ let label = gBrowser.mStringBundle.getFormattedString("tabs.allowTabFocusByPromptForSite",
+ [hostForAllowFocusCheckbox]);
+ allowFocusCheckbox.setAttribute("label", label);
+ allowFocusRow.appendChild(allowFocusCheckbox);
+ newPrompt.appendChild(allowFocusRow);
+ }
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ let closeCB = this._promptCloseCallback.bind(null, onCloseCallback, principalToAllowFocusFor,
+ allowFocusCheckbox);
+ newPrompt.init(args, tab, closeCB);
+ return newPrompt;
+ },
+
+ removePrompt(aPrompt) {
+ let browser = this.browser;
+ browser.parentNode.removeChild(aPrompt);
+
+ let prompts = this.listPrompts();
+ if (prompts.length) {
+ let prompt = prompts[prompts.length - 1];
+ prompt.hidden = false;
+ prompt.Dialog.setDefaultFocus();
+ } else {
+ browser.removeAttribute("tabmodalPromptShowing");
+ browser.focus();
+ }
+ },
+
+ listPrompts(aPrompt) {
+ // Get the nodelist, then return as an array
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let els = this.browser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+ return Array.from(els);
+ },
+
+ onNextPromptShowAllowFocusCheckboxFor(principal) {
+ this._allowTabFocusByPromptPrincipal = principal;
+ },
+
+ get browser() {
+ let browser = this._weakBrowserRef.get();
+ if (!browser) {
+ throw "Stale promptbox! The associated browser is gone.";
+ }
+ return browser;
+ },
+};
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
new file mode 100644
index 000000000..2c74aecdf
--- /dev/null
+++ b/browser/base/content/browser.xul
@@ -0,0 +1,1134 @@
+#filter substitution
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/controlcenter/panel.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/customizableui/panelUI.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/browser-lightweightTheme.css" type="text/css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+# All DTD information is stored in a separate file so that it can be shared by
+# hiddenWindow.xul.
+#include browser-doctype.inc
+
+<window id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="gBrowserInit.onLoad()" onunload="gBrowserInit.onUnload()" onclose="return WindowIsClosing();"
+ title="&mainWindow.title;"
+ title_normal="&mainWindow.title;"
+#ifdef XP_MACOSX
+ title_privatebrowsing="&mainWindow.title;&mainWindow.titlemodifiermenuseparator;&mainWindow.titlePrivateBrowsingSuffix;"
+ titledefault="&mainWindow.title;"
+ titlemodifier=""
+ titlemodifier_normal=""
+ titlemodifier_privatebrowsing="&mainWindow.titlePrivateBrowsingSuffix;"
+#else
+ title_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
+ titlemodifier="&mainWindow.titlemodifier;"
+ titlemodifier_normal="&mainWindow.titlemodifier;"
+ titlemodifier_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
+#endif
+#ifdef CAN_DRAW_IN_TITLEBAR
+#ifdef XP_WIN
+ chromemargin="0,2,2,2"
+#else
+ chromemargin="0,-1,-1,-1"
+#endif
+ tabsintitlebar="true"
+#endif
+ titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
+ lightweightthemes="true"
+ lightweightthemesfooter="browser-bottombox"
+ windowtype="navigator:browser"
+ macanimationtype="document"
+ screenX="4" screenY="4"
+ fullscreenbutton="true"
+ sizemode="normal"
+ retargetdocumentfocus="urlbar"
+ persist="screenX screenY width height sizemode">
+
+# All JS files which are not content (only) dependent that browser.xul
+# wishes to include *must* go into the global-scripts.inc file
+# so that they can be shared by macBrowserOverlay.xul.
+#include global-scripts.inc
+<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
+
+<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+<script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
+<script type="application/javascript" src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
+# browser-sets.inc file for sharing with hiddenWindow.xul.
+#define FULL_BROWSER_WINDOW
+#include browser-sets.inc
+#undef FULL_BROWSER_WINDOW
+
+ <popupset id="mainPopupSet">
+ <menupopup id="tabContextMenu"
+ onpopupshowing="if (event.target == this) TabContextMenu.updateContextMenu(this);"
+ onpopuphidden="if (event.target == this) TabContextMenu.contextTab = null;">
+ <menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
+ oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
+ <menuitem id="context_toggleMuteTab" oncommand="TabContextMenu.contextTab.toggleMuteAudio();"/>
+ <menuseparator/>
+ <menuitem id="context_pinTab" label="&pinTab.label;"
+ accesskey="&pinTab.accesskey;"
+ oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/>
+ <menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true"
+ accesskey="&unpinTab.accesskey;"
+ oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/>
+ <menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
+ accesskey="&moveToNewWindow.accesskey;"
+ tbattr="tabbrowser-multiple"
+ oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/>
+#ifdef E10S_TESTING_ONLY
+ <menuitem id="context_openNonRemoteWindow" label="Open in new non-e10s window"
+ tbattr="tabbrowser-remote"
+ hidden="true"
+ oncommand="gBrowser.openNonRemoteWindow(TabContextMenu.contextTab);"/>
+#endif
+ <menuseparator id="context_sendTabToDevice_separator" hidden="true"/>
+ <menu id="context_sendTabToDevice" label="&sendTabToDevice.label;"
+ accesskey="&sendTabToDevice.accesskey;" hidden="true">
+ <menupopup id="context_sendTabToDevicePopupMenu"
+ onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
+ </menu>
+ <menuseparator/>
+ <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
+ tbattr="tabbrowser-multiple-visible"
+ oncommand="gBrowser.reloadAllTabs();"/>
+ <menuitem id="context_bookmarkAllTabs"
+ label="&bookmarkAllTabs.label;"
+ accesskey="&bookmarkAllTabs.accesskey;"
+ command="Browser:BookmarkAllTabs"/>
+ <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
+ oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
+ <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
+ oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
+ <menuseparator/>
+ <menuitem id="context_undoCloseTab"
+ label="&undoCloseTab.label;"
+ accesskey="&undoCloseTab.accesskey;"
+ observes="History:UndoCloseTab"/>
+ <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
+ oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>
+ </menupopup>
+
+ <!-- bug 415444/582485: event.stopPropagation is here for the cloned version
+ of this menupopup -->
+ <menupopup id="backForwardMenu"
+ onpopupshowing="return FillHistoryMenu(event.target);"
+ oncommand="gotoHistoryIndex(event); event.stopPropagation();"
+ onclick="checkForMiddleClick(this, event);"/>
+ <tooltip id="aHTMLTooltip" page="true"/>
+ <tooltip id="remoteBrowserTooltip"/>
+
+ <!-- for search and content formfill/pw manager -->
+
+ <panel type="autocomplete-richlistbox"
+ id="PopupAutoComplete"
+ noautofocus="true"
+ hidden="true"
+ overflowpadding="4"
+ norolluponanchor="true" />
+
+ <!-- for search with one-off buttons -->
+ <panel type="autocomplete" id="PopupSearchAutoComplete" noautofocus="true" hidden="true"/>
+
+ <!-- for url bar autocomplete -->
+ <panel type="autocomplete-richlistbox"
+ id="PopupAutoCompleteRichResult"
+ noautofocus="true"
+ hidden="true"
+ flip="none"
+ level="parent"
+ overflowpadding="30" />
+
+ <panel id="DateTimePickerPanel"
+ type="arrow"
+ hidden="true"
+ orient="vertical"
+ noautofocus="true"
+ consumeoutsideclicks="false"
+ level="parent">
+ <iframe id="dateTimePopupFrame"/>
+ </panel>
+
+ <!-- for select dropdowns. The menupopup is what shows the list of options,
+ and the popuponly menulist makes things like the menuactive attributes
+ work correctly on the menupopup. ContentSelectDropdown expects the
+ popuponly menulist to be its immediate parent. -->
+ <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
+ <menupopup rolluponmousewheel="true"
+ activateontab="true" position="after_start"
+#ifdef XP_WIN
+ consumeoutsideclicks="false" ignorekeys="shortcuts"
+#endif
+ />
+ </menulist>
+
+ <!-- for invalid form error message -->
+ <panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
+ <description/>
+ </panel>
+
+ <panel id="editBookmarkPanel"
+ type="arrow"
+ orient="vertical"
+ ignorekeys="true"
+ hidden="true"
+ tabspecific="true"
+ onpopupshown="StarUI.panelShown(event);"
+ aria-labelledby="editBookmarkPanelTitle">
+ <row id="editBookmarkPanelHeader" align="center" hidden="true">
+ <vbox align="center">
+ <image id="editBookmarkPanelStarIcon"/>
+ </vbox>
+ <vbox>
+ <label id="editBookmarkPanelTitle"/>
+ <description id="editBookmarkPanelDescription"/>
+ </vbox>
+ </row>
+ <vbox id="editBookmarkPanelContent" flex="1" hidden="true"/>
+ <hbox id="editBookmarkPanelBottomButtons" pack="end">
+#ifndef XP_UNIX
+ <button id="editBookmarkPanelDoneButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.done.label;"
+ default="true"
+ oncommand="StarUI.panel.hidePopup();"/>
+ <button id="editBookmarkPanelRemoveButton"
+ class="editBookmarkPanelBottomButton"
+ oncommand="StarUI.removeBookmarkButtonCommand();"
+ accesskey="&editBookmark.removeBookmark.accessKey;"/>
+#else
+ <button id="editBookmarkPanelRemoveButton"
+ class="editBookmarkPanelBottomButton"
+ oncommand="StarUI.removeBookmarkButtonCommand();"
+ accesskey="&editBookmark.removeBookmark.accessKey;"/>
+ <button id="editBookmarkPanelDoneButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.done.label;"
+ default="true"
+ oncommand="StarUI.panel.hidePopup();"/>
+#endif
+ </hbox>
+ </panel>
+
+ <!-- UI tour experience -->
+ <panel id="UITourTooltip"
+ type="arrow"
+ hidden="true"
+ noautofocus="true"
+ noautohide="true"
+ align="start"
+ orient="vertical"
+ role="alert">
+ <vbox>
+ <hbox id="UITourTooltipBody">
+ <image id="UITourTooltipIcon"/>
+ <vbox flex="1">
+ <hbox id="UITourTooltipTitleContainer">
+ <label id="UITourTooltipTitle" flex="1"/>
+ <toolbarbutton id="UITourTooltipClose" class="close-icon"
+ tooltiptext="&uiTour.infoPanel.close;"/>
+ </hbox>
+ <description id="UITourTooltipDescription" flex="1"/>
+ </vbox>
+ </hbox>
+ <hbox id="UITourTooltipButtons" flex="1" align="center"/>
+ </vbox>
+ </panel>
+ <!-- type="default" forces frames to be created so that the panel's size can be determined -->
+ <panel id="UITourHighlightContainer"
+ type="default"
+ hidden="true"
+ noautofocus="true"
+ noautohide="true"
+ flip="none"
+ consumeoutsideclicks="false"
+ mousethrough="always">
+ <box id="UITourHighlight"></box>
+ </panel>
+
+ <panel id="social-share-panel"
+ class="social-panel"
+ type="arrow"
+ orient="vertical"
+ onpopupshowing="SocialShare.onShowing()"
+ onpopuphidden="SocialShare.onHidden()"
+ hidden="true">
+ <hbox class="social-share-toolbar">
+ <toolbarbutton id="manage-share-providers" class="share-provider-button"
+ tooltiptext="&social.addons.label;"
+ oncommand="BrowserOpenAddonsMgr('addons://list/service');
+ this.parentNode.parentNode.hidePopup();"/>
+ <arrowscrollbox id="social-share-provider-buttons" orient="horizontal" flex="1" pack="end">
+ <toolbarbutton id="add-share-provider" class="share-provider-button" type="radio"
+ group="share-providers" tooltiptext="&findShareServices.label;"
+ oncommand="SocialShare.showDirectory()"/>
+ </arrowscrollbox>
+ </hbox>
+ <hbox id="share-container" flex="1"/>
+ </panel>
+
+ <menupopup id="toolbar-context-menu"
+ onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
+ <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
+ accesskey="&customizeMenu.moveToPanel.accesskey;"
+ label="&customizeMenu.moveToPanel.label;"
+ contexttype="toolbaritem"
+ class="customize-context-moveToPanel"/>
+ <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
+ accesskey="&customizeMenu.removeFromToolbar.accesskey;"
+ label="&customizeMenu.removeFromToolbar.label;"
+ contexttype="toolbaritem"
+ class="customize-context-removeFromToolbar"/>
+ <menuitem id="toolbar-context-reloadAllTabs"
+ class="toolbaritem-tabsmenu"
+ contexttype="tabbar"
+ oncommand="gBrowser.reloadAllTabs();"
+ label="&toolbarContextMenu.reloadAllTabs.label;"
+ accesskey="&toolbarContextMenu.reloadAllTabs.accesskey;"/>
+ <menuitem id="toolbar-context-bookmarkAllTabs"
+ class="toolbaritem-tabsmenu"
+ contexttype="tabbar"
+ command="Browser:BookmarkAllTabs"
+ label="&toolbarContextMenu.bookmarkAllTabs.label;"
+ accesskey="&toolbarContextMenu.bookmarkAllTabs.accesskey;"/>
+ <menuitem id="toolbar-context-undoCloseTab"
+ class="toolbaritem-tabsmenu"
+ contexttype="tabbar"
+ label="&toolbarContextMenu.undoCloseTab.label;"
+ accesskey="&toolbarContextMenu.undoCloseTab.accesskey;"
+ observes="History:UndoCloseTab"/>
+ <menuseparator/>
+ <menuseparator id="viewToolbarsMenuSeparator"/>
+ <!-- XXXgijs: we're using oncommand handler here to avoid the event being
+ redirected to the command element, thus preventing
+ listeners on the menupopup or further up the tree from
+ seeing the command event pass by. The observes attribute is
+ here so that the menuitem is still disabled and re-enabled
+ correctly. -->
+ <menuitem oncommand="BrowserCustomizeToolbar()"
+ observes="cmd_CustomizeToolbars"
+ class="viewCustomizeToolbar"
+ label="&viewCustomizeToolbar.label;"
+ accesskey="&viewCustomizeToolbar.accesskey;"/>
+ </menupopup>
+
+ <menupopup id="blockedPopupOptions"
+ onpopupshowing="gPopupBlockerObserver.fillPopupList(event);"
+ onpopuphiding="gPopupBlockerObserver.onPopupHiding(event);">
+ <menuitem observes="blockedPopupAllowSite"/>
+ <menuitem observes="blockedPopupEditSettings"/>
+ <menuitem observes="blockedPopupDontShowMessage"/>
+ <menuseparator observes="blockedPopupsSeparator"/>
+ </menupopup>
+
+ <menupopup id="autohide-context"
+ onpopupshowing="FullScreen.getAutohide(this.firstChild);">
+ <menuitem type="checkbox" label="&fullScreenAutohide.label;"
+ accesskey="&fullScreenAutohide.accesskey;"
+ oncommand="FullScreen.setAutohide();"/>
+ <menuseparator/>
+ <menuitem label="&fullScreenExit.label;"
+ accesskey="&fullScreenExit.accesskey;"
+ oncommand="BrowserFullScreen();"/>
+ </menupopup>
+
+ <menupopup id="contentAreaContextMenu" pagemenu="#page-menu-separator"
+ onpopupshowing="if (event.target != this)
+ return true;
+ gContextMenu = new nsContextMenu(this, event.shiftKey);
+ if (gContextMenu.shouldDisplay)
+ updateEditUIVisibility();
+ return gContextMenu.shouldDisplay;"
+ onpopuphiding="if (event.target != this)
+ return;
+ gContextMenu.hiding();
+ gContextMenu = null;
+ updateEditUIVisibility();">
+#include browser-context.inc
+ </menupopup>
+
+ <menupopup id="placesContext">
+ <menuseparator id="placesContext_recentlyBookmarkedSeparator"
+ ignoreitem="true"
+ hidden="true"/>
+ <menuitem id="placesContext_hideRecentlyBookmarked"
+ label="&hideRecentlyBookmarked.label;"
+ accesskey="&hideRecentlyBookmarked.accesskey;"
+ oncommand="BookmarkingUI.hideRecentlyBookmarked();"
+ closemenu="single"
+ ignoreitem="true"
+ hidden="true"/>
+ <menuitem id="placesContext_showRecentlyBookmarked"
+ label="&showRecentlyBookmarked.label;"
+ accesskey="&showRecentlyBookmarked.accesskey;"
+ oncommand="BookmarkingUI.showRecentlyBookmarked();"
+ closemenu="single"
+ ignoreitem="true"
+ hidden="true"/>
+ </menupopup>
+
+ <panel id="ctrlTab-panel" hidden="true" norestorefocus="true" level="top">
+ <hbox>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ </hbox>
+ <hbox pack="center">
+ <button id="ctrlTab-showAll" class="ctrlTab-preview" noicon="true"/>
+ </hbox>
+ </panel>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
+
+ <tooltip id="back-button-tooltip">
+ <label class="tooltip-label" value="&backButton.tooltip;"/>
+#ifdef XP_MACOSX
+ <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
+#else
+ <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
+#endif
+ </tooltip>
+
+ <tooltip id="forward-button-tooltip">
+ <label class="tooltip-label" value="&forwardButton.tooltip;"/>
+#ifdef XP_MACOSX
+ <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
+#else
+ <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
+#endif
+ </tooltip>
+
+ <tooltip id="share-button-tooltip" onpopupshowing="SocialShare.createTooltip(event);">
+ <label class="tooltip-label"/>
+ <label class="tooltip-label"/>
+ </tooltip>
+
+#include popup-notifications.inc
+
+#include ../../components/customizableui/content/panelUI.inc.xul
+#include ../../components/controlcenter/content/panel.inc.xul
+
+ <hbox id="downloads-animation-container" mousethrough="always">
+ <vbox id="downloads-notification-anchor">
+ <vbox id="downloads-indicator-notification"/>
+ </vbox>
+ </hbox>
+
+ <hbox id="bookmarked-notification-container" mousethrough="always">
+ <vbox id="bookmarked-notification-anchor">
+ <vbox id="bookmarked-notification"/>
+ </vbox>
+ <vbox id="bookmarked-notification-dropmarker-anchor">
+ <image id="bookmarked-notification-dropmarker-icon"/>
+ </vbox>
+ </hbox>
+
+ <tooltip id="dynamic-shortcut-tooltip"
+ onpopupshowing="UpdateDynamicShortcutTooltipText(this);"/>
+
+ <menupopup id="SyncedTabsSidebarContext">
+ <menuitem label="&syncedTabs.context.open.label;"
+ accesskey="&syncedTabs.context.open.accesskey;"
+ id="syncedTabsOpenSelected" where="current"/>
+ <menuitem label="&syncedTabs.context.openInNewTab.label;"
+ accesskey="&syncedTabs.context.openInNewTab.accesskey;"
+ id="syncedTabsOpenSelectedInTab" where="tab"/>
+ <menuitem label="&syncedTabs.context.openInNewWindow.label;"
+ accesskey="&syncedTabs.context.openInNewWindow.accesskey;"
+ id="syncedTabsOpenSelectedInWindow" where="window"/>
+ <menuitem label="&syncedTabs.context.openInNewPrivateWindow.label;"
+ accesskey="&syncedTabs.context.openInNewPrivateWindow.accesskey;"
+ id="syncedTabsOpenSelectedInPrivateWindow" where="window" private="true"/>
+ <menuseparator/>
+ <menuitem label="&syncedTabs.context.bookmarkSingleTab.label;"
+ accesskey="&syncedTabs.context.bookmarkSingleTab.accesskey;"
+ id="syncedTabsBookmarkSelected"/>
+ <menuitem label="&syncedTabs.context.copy.label;"
+ accesskey="&syncedTabs.context.copy.accesskey;"
+ id="syncedTabsCopySelected"/>
+ <menuseparator/>
+ <menuitem label="&syncSyncNowItem.label;"
+ accesskey="&syncSyncNowItem.accesskey;"
+ id="syncedTabsRefresh"/>
+ </menupopup>
+ <menupopup id="SyncedTabsSidebarTabsFilterContext"
+ class="textbox-contextmenu">
+ <menuitem label="&undoCmd.label;"
+ accesskey="&undoCmd.accesskey;"
+ cmd="cmd_undo"/>
+ <menuseparator/>
+ <menuitem label="&cutCmd.label;"
+ accesskey="&cutCmd.accesskey;"
+ cmd="cmd_cut"/>
+ <menuitem label="&copyCmd.label;"
+ accesskey="&copyCmd.accesskey;"
+ cmd="cmd_copy"/>
+ <menuitem label="&pasteCmd.label;"
+ accesskey="&pasteCmd.accesskey;"
+ cmd="cmd_paste"/>
+ <menuitem label="&deleteCmd.label;"
+ accesskey="&deleteCmd.accesskey;"
+ cmd="cmd_delete"/>
+ <menuseparator/>
+ <menuitem label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ cmd="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem label="&syncSyncNowItem.label;"
+ accesskey="&syncSyncNowItem.accesskey;"
+ id="syncedTabsRefreshFilter"/>
+ </menupopup>
+ </popupset>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+<vbox id="titlebar">
+ <hbox id="titlebar-content">
+ <spacer id="titlebar-spacer" flex="1"/>
+ <hbox id="titlebar-buttonbox-container">
+#ifdef XP_WIN
+ <hbox id="private-browsing-indicator-titlebar">
+ <hbox class="private-browsing-indicator"/>
+ </hbox>
+#endif
+ <hbox id="titlebar-buttonbox">
+ <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
+ <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
+ <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
+ </hbox>
+ </hbox>
+#ifdef XP_MACOSX
+ <!-- OS X does not natively support RTL for its titlebar items, so we prevent this secondary
+ buttonbox from reversing order in RTL by forcing an LTR direction. -->
+ <hbox id="titlebar-secondary-buttonbox" dir="ltr">
+ <hbox class="private-browsing-indicator"/>
+ <hbox id="titlebar-fullscreen-button"/>
+ </hbox>
+#endif
+ </hbox>
+</vbox>
+#endif
+
+<deck flex="1" id="tab-view-deck">
+<vbox flex="1" id="browser-panel">
+
+ <toolbox id="navigator-toolbox" mode="icons">
+ <!-- Menu -->
+ <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
+ mode="icons" iconsize="small"
+#ifdef MENUBAR_CAN_AUTOHIDE
+ toolbarname="&menubarCmd.label;"
+ accesskey="&menubarCmd.accesskey;"
+#if defined(MOZ_WIDGET_GTK)
+ autohide="true"
+#endif
+#endif
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items" align="center">
+# The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
+# hiddenWindow.xul.
+#include browser-menubar.inc
+ </toolbaritem>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+#ifndef XP_MACOSX
+ <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"
+ id="titlebar-placeholder-on-menubar-for-caption-buttons" persist="width"
+ skipintoolbarset="true"/>
+#endif
+#endif
+ </toolbar>
+
+ <toolbar id="TabsToolbar"
+ fullscreentoolbar="true"
+ customizable="true"
+ mode="icons"
+ iconsize="small"
+ aria-label="&tabsToolbar.label;"
+ context="toolbar-context-menu"
+ collapsed="true">
+
+#if defined(MOZ_WIDGET_GTK)
+ <hbox id="private-browsing-indicator"
+ skipintoolbarset="true"/>
+#endif
+
+ <tabs id="tabbrowser-tabs"
+ class="tabbrowser-tabs"
+ tabbrowser="content"
+ flex="1"
+ setfocus="false"
+ tooltip="tabbrowser-tab-tooltip"
+ stopwatchid="FX_TAB_CLICK_MS">
+ <tab class="tabbrowser-tab" selected="true" visuallyselected="true" fadein="true"/>
+ </tabs>
+
+ <toolbarbutton id="new-tab-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ onclick="checkForMiddleClick(this, event);"
+ tooltip="dynamic-shortcut-tooltip"
+ ondrop="newTabButtonObserver.onDrop(event)"
+ ondragover="newTabButtonObserver.onDragOver(event)"
+ ondragenter="newTabButtonObserver.onDragOver(event)"
+ ondragexit="newTabButtonObserver.onDragExit(event)"
+ cui-areatype="toolbar"
+ removable="true"/>
+
+ <toolbarbutton id="alltabs-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button"
+ type="menu"
+ label="&listAllTabs.label;"
+ tooltiptext="&listAllTabs.label;"
+ removable="false">
+ <menupopup id="alltabs-popup"
+ position="after_end">
+ <menuitem id="alltabs_undoCloseTab"
+ class="menuitem-iconic"
+ key="key_undoCloseTab"
+ label="&undoCloseTab.label;"
+ observes="History:UndoCloseTab"/>
+ <menuseparator id="alltabs-popup-separator-1"/>
+ <menu id="alltabs_containersTab"
+ label="&newUserContext.label;">
+ <menupopup id="alltabs_containersMenuTab" />
+ </menu>
+ <menuseparator id="alltabs-popup-separator-2"/>
+ </menupopup>
+ </toolbarbutton>
+
+#if !defined(MOZ_WIDGET_GTK)
+ <hbox class="private-browsing-indicator" skipintoolbarset="true"/>
+#endif
+#ifdef CAN_DRAW_IN_TITLEBAR
+ <hbox class="titlebar-placeholder" type="caption-buttons"
+ id="titlebar-placeholder-on-TabsToolbar-for-captions-buttons" persist="width"
+#ifndef XP_MACOSX
+ ordinal="1000"
+#endif
+ skipintoolbarset="true"/>
+
+#ifdef XP_MACOSX
+ <hbox class="titlebar-placeholder" type="fullscreen-button"
+ id="titlebar-placeholder-on-TabsToolbar-for-fullscreen-button" persist="width"
+ skipintoolbarset="true"/>
+#endif
+#endif
+ </toolbar>
+
+ <toolbar id="nav-bar"
+ aria-label="&navbarCmd.label;"
+ fullscreentoolbar="true" mode="icons" customizable="true"
+ iconsize="small"
+ customizationtarget="nav-bar-customization-target"
+ overflowable="true"
+ overflowbutton="nav-bar-overflow-button"
+ overflowtarget="widget-overflow-list"
+ overflowpanel="widget-overflow"
+ context="toolbar-context-menu">
+
+ <hbox id="nav-bar-customization-target" flex="1">
+ <toolbaritem id="urlbar-container" flex="400" persist="width"
+ removable="false"
+ class="chromeclass-location" overflows="false">
+ <toolbarbutton id="back-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&backCmd.label;"
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltip="back-button-tooltip"
+ context="backForwardMenu"/>
+ <hbox id="urlbar-wrapper" flex="1">
+ <toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&forwardCmd.label;"
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltip="forward-button-tooltip"
+ context="backForwardMenu"/>
+ <textbox id="urlbar" flex="1"
+ placeholder="&urlbar.placeholder2;"
+ type="autocomplete"
+ autocompletesearch="unifiedcomplete"
+ autocompletesearchparam="enable-actions"
+ autocompletepopup="PopupAutoCompleteRichResult"
+ completeselectedindex="true"
+ shrinkdelay="250"
+ tabscrolling="true"
+ showcommentcolumn="true"
+ showimagecolumn="true"
+ enablehistory="true"
+ maxrows="10"
+ newlines="stripsurroundingwhitespace"
+ ontextentered="this.handleCommand(param);"
+ ontextreverted="return this.handleRevert();"
+ pageproxystate="invalid">
+ <!-- Use onclick instead of normal popup= syntax since the popup
+ code fires onmousedown, and hence eats our favicon drag events. -->
+ <box id="identity-box" role="button"
+ align="center"
+ aria-label="&urlbar.viewSiteInfo.label;"
+ onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
+ onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
+ ondragstart="gIdentityHandler.onDragStart(event);">
+ <image id="identity-icon"
+ consumeanchor="identity-box"
+ onclick="PageProxyClickHandler(event);"/>
+ <image id="sharing-icon" mousethrough="always"/>
+ <box id="blocked-permissions-container" align="center">
+ <image data-permission-id="geo" class="blocked-permission-icon geo-icon" role="button"
+ tooltiptext="&urlbar.geolocationBlocked.tooltip;"/>
+ <image data-permission-id="desktop-notification" class="blocked-permission-icon desktop-notification-icon" role="button"
+ tooltiptext="&urlbar.webNotificationsBlocked.tooltip;"/>
+ <image data-permission-id="camera" class="blocked-permission-icon camera-icon" role="button"
+ tooltiptext="&urlbar.cameraBlocked.tooltip;"/>
+ <image data-permission-id="indexedDB" class="blocked-permission-icon indexedDB-icon" role="button"
+ tooltiptext="&urlbar.indexedDBBlocked.tooltip;"/>
+ <image data-permission-id="microphone" class="blocked-permission-icon microphone-icon" role="button"
+ tooltiptext="&urlbar.microphoneBlocked.tooltip;"/>
+ <image data-permission-id="screen" class="blocked-permission-icon screen-icon" role="button"
+ tooltiptext="&urlbar.screenBlocked.tooltip;"/>
+ </box>
+ <box id="notification-popup-box"
+ hidden="true"
+ onmouseover="document.getElementById('identity-icon').classList.add('no-hover');"
+ onmouseout="document.getElementById('identity-icon').classList.remove('no-hover');"
+ align="center">
+ <image id="default-notification-icon" class="notification-anchor-icon" role="button"
+ tooltiptext="&urlbar.defaultNotificationAnchor.tooltip;"/>
+ <image id="geo-notification-icon" class="notification-anchor-icon geo-icon" role="button"
+ tooltiptext="&urlbar.geolocationNotificationAnchor.tooltip;"/>
+ <image id="addons-notification-icon" class="notification-anchor-icon install-icon" role="button"
+ tooltiptext="&urlbar.addonsNotificationAnchor.tooltip;"/>
+ <image id="indexedDB-notification-icon" class="notification-anchor-icon indexedDB-icon" role="button"
+ tooltiptext="&urlbar.indexedDBNotificationAnchor.tooltip;"/>
+ <image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button"
+ tooltiptext="&urlbar.passwordNotificationAnchor.tooltip;"/>
+ <image id="plugins-notification-icon" class="notification-anchor-icon plugin-icon" role="button"
+ tooltiptext="&urlbar.pluginsNotificationAnchor.tooltip;"/>
+ <image id="web-notifications-notification-icon" class="notification-anchor-icon desktop-notification-icon" role="button"
+ tooltiptext="&urlbar.webNotificationAnchor.tooltip;"/>
+ <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon camera-icon" role="button"
+ tooltiptext="&urlbar.webRTCShareDevicesNotificationAnchor.tooltip;"/>
+ <image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon microphone-icon" role="button"
+ tooltiptext="&urlbar.webRTCShareMicrophoneNotificationAnchor.tooltip;"/>
+ <image id="webRTC-shareScreen-notification-icon" class="notification-anchor-icon screen-icon" role="button"
+ tooltiptext="&urlbar.webRTCShareScreenNotificationAnchor.tooltip;"/>
+ <image id="servicesInstall-notification-icon" class="notification-anchor-icon service-icon" role="button"
+ tooltiptext="&urlbar.servicesNotificationAnchor.tooltip;"/>
+ <image id="translate-notification-icon" class="notification-anchor-icon translation-icon" role="button"
+ tooltiptext="&urlbar.translateNotificationAnchor.tooltip;"/>
+ <image id="translated-notification-icon" class="notification-anchor-icon translation-icon in-use" role="button"
+ tooltiptext="&urlbar.translatedNotificationAnchor.tooltip;"/>
+ <image id="eme-notification-icon" class="notification-anchor-icon drm-icon" role="button"
+ tooltiptext="&urlbar.emeNotificationAnchor.tooltip;"/>
+ </box>
+ <image id="tracking-protection-icon"/>
+ <image id="connection-icon"/>
+ <hbox id="identity-icon-labels">
+ <label id="identity-icon-label" class="plain" flex="1"/>
+ <label id="identity-icon-country-label" class="plain"/>
+ </hbox>
+ </box>
+ <box id="urlbar-display-box" align="center">
+ <label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
+ <label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
+ </box>
+ <hbox id="urlbar-icons">
+ <image id="page-report-button"
+ class="urlbar-icon"
+ hidden="true"
+ tooltiptext="&pageReportIcon.tooltip;"
+ onmousedown="gPopupBlockerObserver.onReportButtonMousedown(event);"/>
+ <image id="reader-mode-button"
+ class="urlbar-icon"
+ hidden="true"
+ onclick="ReaderParent.buttonClick(event);"/>
+ <toolbarbutton id="urlbar-zoom-button"
+ onclick="FullZoom.reset();"
+ tooltiptext="&urlbar.zoomReset.tooltip;"
+ hidden="true"/>
+ </hbox>
+ <hbox id="userContext-icons" hidden="true">
+ <label id="userContext-label"/>
+ <image id="userContext-indicator"/>
+ </hbox>
+ <toolbarbutton id="urlbar-go-button"
+ class="chromeclass-toolbar-additional"
+ onclick="gURLBar.handleCommand(event);"
+ tooltiptext="&goEndCap.tooltip;"/>
+ <toolbarbutton id="urlbar-reload-button"
+ class="chromeclass-toolbar-additional"
+ command="Browser:ReloadOrDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&reloadButton.tooltip;"/>
+ <toolbarbutton id="urlbar-stop-button"
+ class="chromeclass-toolbar-additional"
+ command="Browser:Stop"
+ tooltiptext="&stopButton.tooltip;"/>
+ </textbox>
+ </hbox>
+ </toolbaritem>
+
+ <toolbaritem id="search-container" title="&searchItem.title;"
+ align="center" class="chromeclass-toolbar-additional panel-wide-item"
+ cui-areatype="toolbar"
+ flex="100" persist="width" removable="true">
+ <searchbar id="searchbar" flex="1"/>
+ </toolbaritem>
+
+ <toolbarbutton id="bookmarks-menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ removable="true"
+ type="menu-button"
+ label="&bookmarksMenuButton.label;"
+ tooltip="dynamic-shortcut-tooltip"
+ anchor="dropmarker"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondragleave="PlacesMenuDNDHandler.onDragLeave(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);"
+ cui-areatype="toolbar"
+ oncommand="BookmarkingUI.onCommand(event);">
+ <observes element="bookmarkThisPageBroadcaster" attribute="starred"/>
+ <observes element="bookmarkThisPageBroadcaster" attribute="buttontooltiptext"/>
+ <menupopup id="BMB_bookmarksPopup"
+ class="cui-widget-panel cui-widget-panelview cui-widget-panelWithFooter PanelUI-subView"
+ placespopup="true"
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarkingUI.onPopupShowing(event);
+ BookmarkingUI.attachPlacesView(event, this);"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem id="BMB_viewBookmarksSidebar"
+ class="subviewbutton"
+ label="&viewBookmarksSidebar2.label;"
+ type="checkbox"
+ oncommand="SidebarUI.toggle('viewBookmarksSidebar');">
+ <observes element="viewBookmarksSidebar" attribute="checked"/>
+ </menuitem>
+ <!-- NB: temporary solution for bug 985024, this should go away soon. -->
+ <menuitem id="BMB_bookmarksShowAllTop"
+ class="menuitem-iconic subviewbutton"
+ label="&showAllBookmarks2.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ <menuseparator/>
+ <menuitem label="&recentBookmarks.label;"
+ id="BMB_recentBookmarks"
+ disabled="true"
+ class="menuitem-iconic subviewbutton"/>
+ <menuseparator/>
+ <menu id="BMB_bookmarksToolbar"
+ class="menu-iconic bookmark-item subviewbutton"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="BMB_bookmarksToolbarPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR',
+ PlacesUIUtils.getViewForNode(this.parentNode.parentNode).options);">
+ <menuitem id="BMB_viewBookmarksToolbar"
+ placesanonid="view-toolbar"
+ toolbarId="PersonalToolbar"
+ type="checkbox"
+ oncommand="onViewToolbarCommand(event)"
+ label="&viewBookmarksToolbar.label;"/>
+ <menuseparator/>
+ <!-- Bookmarks toolbar items -->
+ </menupopup>
+ </menu>
+ <menu id="BMB_unsortedBookmarks"
+ class="menu-iconic bookmark-item subviewbutton"
+ label="&bookmarksMenuButton.other.label;"
+ container="true">
+ <menupopup id="BMB_unsortedBookmarksPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=UNFILED_BOOKMARKS',
+ PlacesUIUtils.getViewForNode(this.parentNode.parentNode).options);"/>
+ </menu>
+ <menuseparator/>
+ <!-- Bookmarks menu items will go here -->
+ <menuitem id="BMB_bookmarksShowAll"
+ class="subviewbutton panel-subview-footer"
+ label="&showAllBookmarks2.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <!-- This is a placeholder for the Downloads Indicator. It is visible
+ during the customization of the toolbar, in the palette, and before
+ the Downloads Indicator overlay is loaded. -->
+ <toolbarbutton id="downloads-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional badged-button"
+ key="key_openDownloads"
+ oncommand="DownloadsIndicatorView.onCommand(event);"
+ ondrop="DownloadsIndicatorView.onDrop(event);"
+ ondragover="DownloadsIndicatorView.onDragOver(event);"
+ ondragenter="DownloadsIndicatorView.onDragOver(event);"
+ label="&downloads.label;"
+ removable="true"
+ cui-areatype="toolbar"
+ tooltip="dynamic-shortcut-tooltip"/>
+
+ <toolbarbutton id="home-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ removable="true"
+ label="&homeButton.label;"
+ ondragover="homeButtonObserver.onDragOver(event)"
+ ondragenter="homeButtonObserver.onDragOver(event)"
+ ondrop="homeButtonObserver.onDrop(event)"
+ ondragexit="homeButtonObserver.onDragExit(event)"
+ key="goHome"
+ onclick="BrowserGoHome(event);"
+ cui-areatype="toolbar"
+ aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
+ </hbox>
+
+ <toolbarbutton id="nav-bar-overflow-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional overflow-button"
+ skipintoolbarset="true"
+ tooltiptext="&navbarOverflow.label;"/>
+
+ <toolbaritem id="PanelUI-button"
+ class="chromeclass-toolbar-additional"
+ removable="false">
+ <toolbarbutton id="PanelUI-menu-button"
+ class="toolbarbutton-1 badged-button"
+ consumeanchor="PanelUI-button"
+ label="&brandShortName;"
+ tooltiptext="&appmenu.tooltip;"/>
+ </toolbaritem>
+
+ <hbox id="window-controls" hidden="true" pack="end" skipintoolbarset="true"
+ ordinal="1000">
+ <toolbarbutton id="minimize-button"
+ tooltiptext="&fullScreenMinimize.tooltip;"
+ oncommand="window.minimize();"/>
+
+ <toolbarbutton id="restore-button"
+#ifdef XP_MACOSX
+# Prior to 10.7 there wasn't a native fullscreen button so we use #restore-button
+# to exit fullscreen and want it to behave like other toolbar buttons.
+ class="toolbarbutton-1"
+#endif
+ tooltiptext="&fullScreenRestore.tooltip;"
+ oncommand="BrowserFullScreen();"/>
+
+ <toolbarbutton id="close-button"
+ tooltiptext="&fullScreenClose.tooltip;"
+ oncommand="BrowserTryToCloseWindow();"/>
+ </hbox>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbar id="PersonalToolbar"
+ mode="icons" iconsize="small"
+ class="chromeclass-directories"
+ context="toolbar-context-menu"
+ toolbarname="&personalbarCmd.label;" accesskey="&personalbarCmd.accesskey;"
+ collapsed="true"
+ customizable="true">
+ <toolbaritem id="personal-bookmarks"
+ title="&bookmarksToolbarItem.label;"
+ cui-areatype="toolbar"
+ removable="true">
+ <toolbarbutton id="bookmarks-toolbar-placeholder"
+ class="toolbarbutton-1"
+ mousethrough="never"
+ label="&bookmarksToolbarItem.label;"
+ oncommand="PlacesToolbarHelper.onPlaceholderCommand();"/>
+ <hbox flex="1"
+ id="PlacesToolbar"
+ context="placesContext"
+ onclick="BookmarksEventHandler.onClick(event, this._placesView);"
+ oncommand="BookmarksEventHandler.onCommand(event, this._placesView);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <hbox flex="1">
+ <hbox id="PlacesToolbarDropIndicatorHolder" align="center" collapsed="true">
+ <image id="PlacesToolbarDropIndicator"
+ mousethrough="always"
+ collapsed="true"/>
+ </hbox>
+ <scrollbox orient="horizontal"
+ id="PlacesToolbarItems"
+ flex="1"/>
+ <toolbarbutton type="menu"
+ id="PlacesChevron"
+ class="chevron"
+ mousethrough="never"
+ collapsed="true"
+ tooltiptext="&bookmarksToolbarChevron.tooltip;"
+ onpopupshowing="document.getElementById('PlacesToolbar')
+ ._placesView._onChevronPopupShowing(event);">
+ <menupopup id="PlacesChevronPopup"
+ placespopup="true"
+ tooltip="bhTooltip" popupsinherittooltip="true"
+ context="placesContext"/>
+ </toolbarbutton>
+ </hbox>
+ </hbox>
+ </toolbaritem>
+ </toolbar>
+
+ <!-- This is a shim which will go away ASAP. See bug 749804 for details -->
+ <toolbar id="addon-bar" toolbar-delegate="nav-bar" mode="icons" iconsize="small"
+ customizable="true">
+ <hbox id="addonbar-closebutton"/>
+ <statusbar id="status-bar"/>
+ </toolbar>
+
+ <toolbarpalette id="BrowserToolbarPalette">
+
+# Update primaryToolbarButtons in browser/themes/shared/browser.inc when adding
+# or removing default items with the toolbarbutton-1 class.
+
+ <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+#ifdef XP_MACOSX
+ command="cmd_print"
+ tooltip="dynamic-shortcut-tooltip"
+#else
+ command="cmd_printPreview"
+ tooltiptext="&printButton.tooltip;"
+#endif
+ label="&printButton.label;"/>
+
+
+ <toolbarbutton id="new-window-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&newNavigatorCmd.label;"
+ command="key_newNavigator"
+ tooltip="dynamic-shortcut-tooltip"
+ ondrop="newWindowButtonObserver.onDrop(event)"
+ ondragover="newWindowButtonObserver.onDragOver(event)"
+ ondragenter="newWindowButtonObserver.onDragOver(event)"
+ ondragexit="newWindowButtonObserver.onDragExit(event)"/>
+
+ <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ observes="View:FullScreen"
+ type="checkbox"
+ label="&fullScreenCmd.label;"
+ tooltip="dynamic-shortcut-tooltip"/>
+ </toolbarpalette>
+ </toolbox>
+
+ <hbox id="fullscr-toggler" hidden="true"/>
+
+ <deck id="content-deck" flex="1">
+ <hbox flex="1" id="browser">
+ <vbox id="browser-border-start" hidden="true" layer="true"/>
+ <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
+ <sidebarheader id="sidebar-header" align="center">
+ <label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
+ <image id="sidebar-throbber"/>
+ <toolbarbutton class="close-icon tabbable" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="SidebarUI.hide();"/>
+ </sidebarheader>
+ <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true" disablefullscreen="true"
+ style="min-width: 14em; width: 18em; max-width: 36em;" tooltip="aHTMLTooltip"/>
+ </vbox>
+
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
+ <vbox id="appcontent" flex="1">
+ <notificationbox id="high-priority-global-notificationbox" notificationside="top"/>
+ <tabbrowser id="content"
+ flex="1" contenttooltip="aHTMLTooltip"
+ tabcontainer="tabbrowser-tabs"
+ contentcontextmenu="contentAreaContextMenu"
+ autocompletepopup="PopupAutoComplete"
+ selectmenulist="ContentSelectDropdown"
+ datetimepicker="DateTimePickerPanel"/>
+ </vbox>
+ <vbox id="browser-border-end" hidden="true" layer="true"/>
+ </hbox>
+#include ../../components/customizableui/content/customizeMode.inc.xul
+ </deck>
+
+ <html:div id="fullscreen-warning" class="pointerlockfswarning" hidden="true">
+ <html:div class="pointerlockfswarning-domain-text">
+ &fullscreenWarning.beforeDomain.label;
+ <html:span class="pointerlockfswarning-domain"/>
+ &fullscreenWarning.afterDomain.label;
+ </html:div>
+ <html:div class="pointerlockfswarning-generic-text">
+ &fullscreenWarning.generic.label;
+ </html:div>
+ <html:button id="fullscreen-exit-button"
+ onclick="FullScreen.exitDomFullScreen();">
+#ifdef XP_MACOSX
+ &exitDOMFullscreenMac.button;
+#else
+ &exitDOMFullscreen.button;
+#endif
+ </html:button>
+ </html:div>
+
+ <html:div id="pointerlock-warning" class="pointerlockfswarning" hidden="true">
+ <html:div class="pointerlockfswarning-domain-text">
+ &pointerlockWarning.beforeDomain.label;
+ <html:span class="pointerlockfswarning-domain"/>
+ &pointerlockWarning.afterDomain.label;
+ </html:div>
+ <html:div class="pointerlockfswarning-generic-text">
+ &pointerlockWarning.generic.label;
+ </html:div>
+ </html:div>
+
+ <vbox id="browser-bottombox" layer="true">
+ <notificationbox id="global-notificationbox" notificationside="bottom"/>
+ </vbox>
+
+ <svg:svg height="0">
+#include tab-shape.inc.svg
+ <svg:clipPath id="urlbar-back-button-clip-path">
+#ifndef XP_MACOSX
+ <svg:path d="M -9,-4 l 0,1 a 15 15 0 0,1 0,30 l 0,1 l 10000,0 l 0,-32 l -10000,0 z" />
+#else
+ <svg:path d="M -11,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
+#endif
+ </svg:clipPath>
+#ifdef XP_WIN
+ <svg:clipPath id="urlbar-back-button-clip-path-win10">
+ <svg:path d="M -6,-2 l 0,1 a 15 15 0 0,1 0,30 l 0,1 l 10000,0 l 0,-32 l -10000,0 z" />
+ </svg:clipPath>
+#endif
+ </svg:svg>
+
+</vbox>
+# <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
+# Introducing the iframe dynamically, as needed, was found to be better than
+# starting with an empty iframe here in browser.xul from a Ts standpoint.
+</deck>
+
+</window>
diff --git a/browser/base/content/browserMountPoints.inc b/browser/base/content/browserMountPoints.inc
new file mode 100644
index 000000000..e4315b04a
--- /dev/null
+++ b/browser/base/content/browserMountPoints.inc
@@ -0,0 +1,12 @@
+<stringbundleset id="stringbundleset"/>
+
+<commandset id="mainCommandSet"/>
+<commandset id="baseMenuCommandSet"/>
+<commandset id="placesCommands"/>
+
+<broadcasterset id="mainBroadcasterSet"/>
+
+<keyset id="mainKeyset"/>
+<keyset id="baseMenuKeyset"/>
+
+<menubar id="main-menubar"/> \ No newline at end of file
diff --git a/browser/base/content/content.js b/browser/base/content/content.js
new file mode 100644
index 000000000..658d2014d
--- /dev/null
+++ b/browser/base/content/content.js
@@ -0,0 +1,1503 @@
+/* -*- 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 content script should work in any browser or iframe and should not
+ * depend on the frame being contained in tabbrowser. */
+
+var {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:///modules/ContentWebRTC.jsm");
+Cu.import("resource:///modules/ContentObservers.jsm");
+Cu.import("resource://gre/modules/InlineSpellChecker.jsm");
+Cu.import("resource://gre/modules/InlineSpellCheckerContent.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+ "resource:///modules/E10SUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
+ "resource:///modules/ContentLinkHandler.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginFormFactory",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+ "resource://gre/modules/InsecurePasswordUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluginContent",
+ "resource:///modules/PluginContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
+ "resource:///modules/FormSubmitObserver.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
+ "resource://gre/modules/PageMetadata.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
+ "resource:///modules/PlacesUIUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "PageMenuChild", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
+ return new tmp.PageMenuChild();
+});
+XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
+ "resource:///modules/Feeds.jsm");
+
+Cu.importGlobalProperties(["URL"]);
+
+// TabChildGlobal
+var global = this;
+
+// Load the form validation popup handler
+var formSubmitObserver = new FormSubmitObserver(content, this);
+
+addMessageListener("ContextMenu:DoCustomCommand", function(message) {
+ E10SUtils.wrapHandlingUserInput(
+ content, message.data.handlingUserInput,
+ () => PageMenuChild.executeMenu(message.data.generatedItemId));
+});
+
+addMessageListener("RemoteLogins:fillForm", function(message) {
+ LoginManagerContent.receiveMessage(message, content);
+});
+addEventListener("DOMFormHasPassword", function(event) {
+ LoginManagerContent.onDOMFormHasPassword(event, content);
+ let formLike = LoginFormFactory.createFromForm(event.target);
+ InsecurePasswordUtils.reportInsecurePasswords(formLike);
+});
+addEventListener("DOMInputPasswordAdded", function(event) {
+ LoginManagerContent.onDOMInputPasswordAdded(event, content);
+ let formLike = LoginFormFactory.createFromField(event.target);
+ InsecurePasswordUtils.reportInsecurePasswords(formLike);
+});
+addEventListener("pageshow", function(event) {
+ LoginManagerContent.onPageShow(event, content);
+});
+addEventListener("DOMAutoComplete", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
+addEventListener("blur", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
+
+var handleContentContextMenu = function (event) {
+ let defaultPrevented = event.defaultPrevented;
+ if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
+ let plugin = null;
+ try {
+ plugin = event.target.QueryInterface(Ci.nsIObjectLoadingContent);
+ } catch (e) {}
+ if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
+ // Don't open a context menu for plugins.
+ return;
+ }
+
+ defaultPrevented = false;
+ }
+
+ if (defaultPrevented)
+ return;
+
+ let addonInfo = {};
+ let subject = {
+ event: event,
+ addonInfo: addonInfo,
+ };
+ subject.wrappedJSObject = subject;
+ Services.obs.notifyObservers(subject, "content-contextmenu", null);
+
+ let doc = event.target.ownerDocument;
+ let docLocation = doc.mozDocumentURIIfNotForErrorPages;
+ docLocation = docLocation && docLocation.spec;
+ let charSet = doc.characterSet;
+ let baseURI = doc.baseURI;
+ let referrer = doc.referrer;
+ let referrerPolicy = doc.referrerPolicy;
+ let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ let loginFillInfo = LoginManagerContent.getFieldContext(event.target);
+
+ // The same-origin check will be done in nsContextMenu.openLinkInTab.
+ let parentAllowsMixedContent = !!docShell.mixedContentChannel;
+
+ // get referrer attribute from clicked link and parse it
+ // if per element referrer is enabled, the element referrer overrules
+ // the document wide referrer
+ if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer")) {
+ let referrerAttrValue = Services.netUtils.parseAttributePolicyString(event.target.
+ getAttribute("referrerpolicy"));
+ if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
+ referrerPolicy = referrerAttrValue;
+ }
+ }
+
+ let disableSetDesktopBg = null;
+ // Media related cache info parent needs for saving
+ let contentType = null;
+ let contentDisposition = null;
+ if (event.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
+ event.target instanceof Ci.nsIImageLoadingContent &&
+ event.target.currentURI) {
+ disableSetDesktopBg = disableSetDesktopBackground(event.target);
+
+ try {
+ let imageCache =
+ Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .getImgCacheForDocument(doc);
+ let props =
+ imageCache.findEntryProperties(event.target.currentURI, doc);
+ try {
+ contentType = props.get("type", Ci.nsISupportsCString).data;
+ } catch (e) {}
+ try {
+ contentDisposition =
+ props.get("content-disposition", Ci.nsISupportsCString).data;
+ } catch (e) {}
+ } catch (e) {}
+ }
+
+ let selectionInfo = BrowserUtils.getSelectionDetails(content);
+
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ let userContextId = loadContext.originAttributes.userContextId;
+
+ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ let editFlags = SpellCheckHelper.isEditable(event.target, content);
+ let spellInfo;
+ if (editFlags &
+ (SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
+ spellInfo =
+ InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
+ }
+
+ // Set the event target first as the copy image command needs it to
+ // determine what was context-clicked on. Then, update the state of the
+ // commands on the context menu.
+ docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
+ .setCommandNode(event.target);
+ event.target.ownerGlobal.updateCommands("contentcontextmenu");
+
+ let customMenuItems = PageMenuChild.build(event.target);
+ let principal = doc.nodePrincipal;
+ sendRpcMessage("contextmenu",
+ { editFlags, spellInfo, customMenuItems, addonInfo,
+ principal, docLocation, charSet, baseURI, referrer,
+ referrerPolicy, contentType, contentDisposition,
+ frameOuterWindowID, selectionInfo, disableSetDesktopBg,
+ loginFillInfo, parentAllowsMixedContent, userContextId },
+ { event, popupNode: event.target });
+ }
+ else {
+ // Break out to the parent window and pass the add-on info along
+ let browser = docShell.chromeEventHandler;
+ let mainWin = browser.ownerGlobal;
+ mainWin.gContextMenuContentData = {
+ isRemote: false,
+ event: event,
+ popupNode: event.target,
+ browser: browser,
+ addonInfo: addonInfo,
+ documentURIObject: doc.documentURIObject,
+ docLocation: docLocation,
+ charSet: charSet,
+ referrer: referrer,
+ referrerPolicy: referrerPolicy,
+ contentType: contentType,
+ contentDisposition: contentDisposition,
+ selectionInfo: selectionInfo,
+ disableSetDesktopBackground: disableSetDesktopBg,
+ loginFillInfo,
+ parentAllowsMixedContent,
+ userContextId,
+ };
+ }
+}
+
+Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(global, "contextmenu", handleContentContextMenu, false);
+
+// Values for telemtery bins: see TLS_ERROR_REPORT_UI in Histograms.json
+const TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN = 0;
+const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1;
+const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6;
+const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7;
+
+const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
+const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
+
+const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11;
+const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
+const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30;
+const SEC_ERROR_OCSP_FUTURE_RESPONSE = SEC_ERROR_BASE + 131;
+const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132;
+const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 5;
+const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = MOZILLA_PKIX_ERROR_BASE + 6;
+
+const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
+
+const PREF_SSL_IMPACT_ROOTS = ["security.tls.version.", "security.ssl3."];
+
+const PREF_SSL_IMPACT = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
+ return prefs.concat(Services.prefs.getChildList(root));
+}, []);
+
+
+function getSerializedSecurityInfo(docShell) {
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+
+ let securityInfo = docShell.failedChannel && docShell.failedChannel.securityInfo;
+ if (!securityInfo) {
+ return "";
+ }
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
+ .QueryInterface(Ci.nsISerializable);
+
+ return serhelper.serializeToString(securityInfo);
+}
+
+var AboutNetAndCertErrorListener = {
+ init: function(chromeGlobal) {
+ addMessageListener("CertErrorDetails", this);
+ addMessageListener("Browser:CaptivePortalFreed", this);
+ chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);
+ chromeGlobal.addEventListener('AboutNetErrorOpenCaptivePortal', this, false, true);
+ chromeGlobal.addEventListener('AboutNetErrorSetAutomatic', this, false, true);
+ chromeGlobal.addEventListener('AboutNetErrorOverride', this, false, true);
+ chromeGlobal.addEventListener('AboutNetErrorResetPreferences', this, false, true);
+ },
+
+ get isAboutNetError() {
+ return content.document.documentURI.startsWith("about:neterror");
+ },
+
+ get isAboutCertError() {
+ return content.document.documentURI.startsWith("about:certerror");
+ },
+
+ receiveMessage: function(msg) {
+ if (!this.isAboutCertError) {
+ return;
+ }
+
+ switch (msg.name) {
+ case "CertErrorDetails":
+ this.onCertErrorDetails(msg);
+ break;
+ case "Browser:CaptivePortalFreed":
+ this.onCaptivePortalFreed(msg);
+ break;
+ }
+ },
+
+ onCertErrorDetails(msg) {
+ let div = content.document.getElementById("certificateErrorText");
+ div.textContent = msg.data.info;
+ let learnMoreLink = content.document.getElementById("learnMoreLink");
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+
+ switch (msg.data.code) {
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ learnMoreLink.href = baseURL + "security-error";
+ break;
+
+ // in case the certificate expired we make sure the system clock
+ // matches settings server (kinto) time
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_OCSP_FUTURE_RESPONSE:
+ case SEC_ERROR_OCSP_OLD_RESPONSE:
+ case MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE:
+ case MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE:
+
+ // use blocklist stats if available
+ if (Services.prefs.getPrefType(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS)) {
+ let difference = Services.prefs.getIntPref(PREF_BLOCKLIST_CLOCK_SKEW_SECONDS);
+
+ // if the difference is more than a day
+ if (Math.abs(difference) > 60 * 60 * 24) {
+ let formatter = new Intl.DateTimeFormat();
+ let systemDate = formatter.format(new Date());
+ // negative difference means local time is behind server time
+ let actualDate = formatter.format(new Date(Date.now() - difference * 1000));
+
+ content.document.getElementById("wrongSystemTime_URL")
+ .textContent = content.document.location.hostname;
+ content.document.getElementById("wrongSystemTime_systemDate")
+ .textContent = systemDate;
+ content.document.getElementById("wrongSystemTime_actualDate")
+ .textContent = actualDate;
+
+ content.document.getElementById("errorShortDesc")
+ .style.display = "none";
+ content.document.getElementById("wrongSystemTimePanel")
+ .style.display = "block";
+ }
+ }
+ learnMoreLink.href = baseURL + "time-errors";
+ break;
+ }
+ },
+
+ onCaptivePortalFreed(msg) {
+ content.dispatchEvent(new content.CustomEvent("AboutNetErrorCaptivePortalFreed"));
+ },
+
+ handleEvent: function(aEvent) {
+ if (!this.isAboutNetError && !this.isAboutCertError) {
+ return;
+ }
+
+ switch (aEvent.type) {
+ case "AboutNetErrorLoad":
+ this.onPageLoad(aEvent);
+ break;
+ case "AboutNetErrorOpenCaptivePortal":
+ this.openCaptivePortalPage(aEvent);
+ break;
+ case "AboutNetErrorSetAutomatic":
+ this.onSetAutomatic(aEvent);
+ break;
+ case "AboutNetErrorOverride":
+ this.onOverride(aEvent);
+ break;
+ case "AboutNetErrorResetPreferences":
+ this.onResetPreferences(aEvent);
+ break;
+ }
+ },
+
+ changedCertPrefs: function () {
+ for (let prefName of PREF_SSL_IMPACT) {
+ if (Services.prefs.prefHasUserValue(prefName)) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ onPageLoad: function(evt) {
+ if (this.isAboutCertError) {
+ let originalTarget = evt.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+ ClickEventHandler.onCertError(originalTarget, ownerDoc);
+ }
+
+ let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
+ content.dispatchEvent(new content.CustomEvent("AboutNetErrorOptions", {
+ detail: JSON.stringify({
+ enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"),
+ changedCertPrefs: this.changedCertPrefs(),
+ automatic: automatic
+ })
+ }));
+
+ sendAsyncMessage("Browser:SSLErrorReportTelemetry",
+ {reportStatus: TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN});
+ },
+
+ openCaptivePortalPage: function(evt) {
+ sendAsyncMessage("Browser:OpenCaptivePortalPage");
+ },
+
+
+ onResetPreferences: function(evt) {
+ sendAsyncMessage("Browser:ResetSSLPreferences");
+ },
+
+ onSetAutomatic: function(evt) {
+ sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
+ automatic: evt.detail
+ });
+
+ // if we're enabling reports, send a report for this failure
+ if (evt.detail) {
+ let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
+ sendAsyncMessage("Browser:SendSSLErrorReport", {
+ uri: { host, port },
+ securityInfo: getSerializedSecurityInfo(docShell),
+ });
+
+ }
+ },
+
+ onOverride: function(evt) {
+ let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
+ sendAsyncMessage("Browser:OverrideWeakCrypto", { uri: {host, port} });
+ }
+}
+
+AboutNetAndCertErrorListener.init(this);
+
+
+var ClickEventHandler = {
+ init: function init() {
+ Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(global, "click", this, true);
+ },
+
+ handleEvent: function(event) {
+ if (!event.isTrusted || event.defaultPrevented || event.button == 2) {
+ return;
+ }
+
+ let originalTarget = event.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+ if (!ownerDoc) {
+ return;
+ }
+
+ // Handle click events from about pages
+ if (ownerDoc.documentURI.startsWith("about:certerror")) {
+ this.onCertError(originalTarget, ownerDoc);
+ return;
+ } else if (ownerDoc.documentURI.startsWith("about:blocked")) {
+ this.onAboutBlocked(originalTarget, ownerDoc);
+ return;
+ } else if (ownerDoc.documentURI.startsWith("about:neterror")) {
+ this.onAboutNetError(event, ownerDoc.documentURI);
+ return;
+ }
+
+ let [href, node, principal] = this._hrefAndLinkNodeForClickEvent(event);
+
+ // get referrer attribute from clicked link and parse it
+ // if per element referrer is enabled, the element referrer overrules
+ // the document wide referrer
+ let referrerPolicy = ownerDoc.referrerPolicy;
+ if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer") &&
+ node) {
+ let referrerAttrValue = Services.netUtils.parseAttributePolicyString(node.
+ getAttribute("referrerpolicy"));
+ if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
+ referrerPolicy = referrerAttrValue;
+ }
+ }
+
+ let json = { button: event.button, shiftKey: event.shiftKey,
+ ctrlKey: event.ctrlKey, metaKey: event.metaKey,
+ altKey: event.altKey, href: null, title: null,
+ bookmark: false, referrerPolicy: referrerPolicy,
+ originAttributes: principal ? principal.originAttributes : {},
+ isContentWindowPrivate: PrivateBrowsingUtils.isContentWindowPrivate(ownerDoc.defaultView)};
+
+ if (href) {
+ try {
+ BrowserUtils.urlSecurityCheck(href, principal);
+ } catch (e) {
+ return;
+ }
+
+ json.href = href;
+ if (node) {
+ json.title = node.getAttribute("title");
+ if (event.button == 0 && !event.ctrlKey && !event.shiftKey &&
+ !event.altKey && !event.metaKey) {
+ json.bookmark = node.getAttribute("rel") == "sidebar";
+ if (json.bookmark) {
+ event.preventDefault(); // Need to prevent the pageload.
+ }
+ }
+ }
+ json.noReferrer = BrowserUtils.linkHasNoReferrer(node)
+
+ // Check if the link needs to be opened with mixed content allowed.
+ // Only when the owner doc has |mixedContentChannel| and the same origin
+ // should we allow mixed content.
+ json.allowMixedContent = false;
+ let docshell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ if (docShell.mixedContentChannel) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ let targetURI = BrowserUtils.makeURI(href);
+ sm.checkSameOriginURI(docshell.mixedContentChannel.URI, targetURI, false);
+ json.allowMixedContent = true;
+ } catch (e) {}
+ }
+ json.originPrincipal = ownerDoc.nodePrincipal;
+
+ sendAsyncMessage("Content:Click", json);
+ return;
+ }
+
+ // This might be middle mouse navigation.
+ if (event.button == 1) {
+ sendAsyncMessage("Content:Click", json);
+ }
+ },
+
+ onCertError: function (targetElement, ownerDoc) {
+ let docShell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ sendAsyncMessage("Browser:CertExceptionError", {
+ location: ownerDoc.location.href,
+ elementId: targetElement.getAttribute("id"),
+ isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
+ securityInfoAsString: getSerializedSecurityInfo(docShell),
+ });
+ },
+
+ onAboutBlocked: function (targetElement, ownerDoc) {
+ var reason = 'phishing';
+ if (/e=malwareBlocked/.test(ownerDoc.documentURI)) {
+ reason = 'malware';
+ } else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
+ reason = 'unwanted';
+ }
+ sendAsyncMessage("Browser:SiteBlockedError", {
+ location: ownerDoc.location.href,
+ reason: reason,
+ elementId: targetElement.getAttribute("id"),
+ isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
+ });
+ },
+
+ onAboutNetError: function (event, documentURI) {
+ let elmId = event.originalTarget.getAttribute("id");
+ if (elmId == "returnButton") {
+ sendAsyncMessage("Browser:SSLErrorGoBack", {});
+ return;
+ }
+ if (elmId != "errorTryAgain" || !/e=netOffline/.test(documentURI)) {
+ return;
+ }
+ // browser front end will handle clearing offline mode and refreshing
+ // the page *if* we're in offline mode now. Otherwise let the error page
+ // handle the click.
+ if (Services.io.offline) {
+ event.preventDefault();
+ sendAsyncMessage("Browser:EnableOnlineMode", {});
+ }
+ },
+
+ /**
+ * Extracts linkNode and href for the current click target.
+ *
+ * @param event
+ * The click event.
+ * @return [href, linkNode, linkPrincipal].
+ *
+ * @note linkNode will be null if the click wasn't on an anchor
+ * element. This includes SVG links, because callers expect |node|
+ * to behave like an <a> element, which SVG links (XLink) don't.
+ */
+ _hrefAndLinkNodeForClickEvent: function(event) {
+ function isHTMLLink(aNode) {
+ // Be consistent with what nsContextMenu.js does.
+ return ((aNode instanceof content.HTMLAnchorElement && aNode.href) ||
+ (aNode instanceof content.HTMLAreaElement && aNode.href) ||
+ aNode instanceof content.HTMLLinkElement);
+ }
+
+ let node = event.target;
+ while (node && !isHTMLLink(node)) {
+ node = node.parentNode;
+ }
+
+ if (node)
+ return [node.href, node, node.ownerDocument.nodePrincipal];
+
+ // If there is no linkNode, try simple XLink.
+ let href, baseURI;
+ node = event.target;
+ while (node && !href) {
+ if (node.nodeType == content.Node.ELEMENT_NODE &&
+ (node.localName == "a" ||
+ node.namespaceURI == "http://www.w3.org/1998/Math/MathML")) {
+ href = node.getAttribute("href") ||
+ node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+ if (href) {
+ baseURI = node.ownerDocument.baseURIObject;
+ break;
+ }
+ }
+ node = node.parentNode;
+ }
+
+ // In case of XLink, we don't return the node we got href from since
+ // callers expect <a>-like elements.
+ // Note: makeURI() will throw if aUri is not a valid URI.
+ return [href ? BrowserUtils.makeURI(href, null, baseURI).spec : null, null,
+ node && node.ownerDocument.nodePrincipal];
+ }
+};
+ClickEventHandler.init();
+
+ContentLinkHandler.init(this);
+
+// TODO: Load this lazily so the JSM is run only if a relevant event/message fires.
+var pluginContent = new PluginContent(global);
+
+addEventListener("DOMWebNotificationClicked", function(event) {
+ sendAsyncMessage("DOMWebNotificationClicked", {});
+}, false);
+
+addEventListener("DOMServiceWorkerFocusClient", function(event) {
+ sendAsyncMessage("DOMServiceWorkerFocusClient", {});
+}, false);
+
+ContentWebRTC.init();
+addMessageListener("rtcpeer:Allow", ContentWebRTC);
+addMessageListener("rtcpeer:Deny", ContentWebRTC);
+addMessageListener("webrtc:Allow", ContentWebRTC);
+addMessageListener("webrtc:Deny", ContentWebRTC);
+addMessageListener("webrtc:StopSharing", ContentWebRTC);
+addMessageListener("webrtc:StartBrowserSharing", () => {
+ let windowID = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+ sendAsyncMessage("webrtc:response:StartBrowserSharing", {
+ windowID: windowID
+ });
+});
+
+addEventListener("pageshow", function(event) {
+ if (event.target == content.document) {
+ sendAsyncMessage("PageVisibility:Show", {
+ persisted: event.persisted,
+ });
+ }
+});
+addEventListener("pagehide", function(event) {
+ if (event.target == content.document) {
+ sendAsyncMessage("PageVisibility:Hide", {
+ persisted: event.persisted,
+ });
+ }
+});
+
+var PageMetadataMessenger = {
+ init() {
+ addMessageListener("PageMetadata:GetPageData", this);
+ addMessageListener("PageMetadata:GetMicroformats", this);
+ },
+ receiveMessage(message) {
+ switch (message.name) {
+ case "PageMetadata:GetPageData": {
+ let target = message.objects.target;
+ let result = PageMetadata.getData(content.document, target);
+ sendAsyncMessage("PageMetadata:PageDataResult", result);
+ break;
+ }
+ case "PageMetadata:GetMicroformats": {
+ let target = message.objects.target;
+ let result = PageMetadata.getMicroformats(content.document, target);
+ sendAsyncMessage("PageMetadata:MicroformatsResult", result);
+ break;
+ }
+ }
+ }
+}
+PageMetadataMessenger.init();
+
+addEventListener("ActivateSocialFeature", function (aEvent) {
+ let document = content.document;
+ let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ if (!dwu.isHandlingUserInput) {
+ Cu.reportError("attempt to activate provider without user input from " + document.nodePrincipal.origin);
+ return;
+ }
+
+ let node = aEvent.target;
+ let ownerDocument = node.ownerDocument;
+ let data = node.getAttribute("data-service");
+ if (data) {
+ try {
+ data = JSON.parse(data);
+ } catch (e) {
+ Cu.reportError("Social Service manifest parse error: " + e);
+ return;
+ }
+ } else {
+ Cu.reportError("Social Service manifest not available");
+ return;
+ }
+
+ sendAsyncMessage("Social:Activation", {
+ url: ownerDocument.location.href,
+ origin: ownerDocument.nodePrincipal.origin,
+ manifest: data
+ });
+}, true, true);
+
+addMessageListener("ContextMenu:SaveVideoFrameAsImage", (message) => {
+ let video = message.objects.target;
+ let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+
+ let ctxDraw = canvas.getContext("2d");
+ ctxDraw.drawImage(video, 0, 0);
+ sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
+ dataURL: canvas.toDataURL("image/jpeg", ""),
+ });
+});
+
+addMessageListener("ContextMenu:MediaCommand", (message) => {
+ E10SUtils.wrapHandlingUserInput(
+ content, message.data.handlingUserInput,
+ () => {
+ let media = message.objects.element;
+ switch (message.data.command) {
+ case "play":
+ media.play();
+ break;
+ case "pause":
+ media.pause();
+ break;
+ case "loop":
+ media.loop = !media.loop;
+ break;
+ case "mute":
+ media.muted = true;
+ break;
+ case "unmute":
+ media.muted = false;
+ break;
+ case "playbackRate":
+ media.playbackRate = message.data.data;
+ break;
+ case "hidecontrols":
+ media.removeAttribute("controls");
+ break;
+ case "showcontrols":
+ media.setAttribute("controls", "true");
+ break;
+ case "fullscreen":
+ if (content.document.fullscreenEnabled)
+ media.requestFullscreen();
+ break;
+ }
+ });
+});
+
+addMessageListener("ContextMenu:Canvas:ToBlobURL", (message) => {
+ message.objects.target.toBlob((blob) => {
+ let blobURL = URL.createObjectURL(blob);
+ sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
+ });
+});
+
+addMessageListener("ContextMenu:ReloadFrame", (message) => {
+ message.objects.target.ownerDocument.location.reload();
+});
+
+addMessageListener("ContextMenu:ReloadImage", (message) => {
+ let image = message.objects.target;
+ if (image instanceof Ci.nsIImageLoadingContent)
+ image.forceReload();
+});
+
+addMessageListener("ContextMenu:BookmarkFrame", (message) => {
+ let frame = message.objects.target.ownerDocument;
+ sendAsyncMessage("ContextMenu:BookmarkFrame:Result",
+ { title: frame.title,
+ description: PlacesUIUtils.getDescriptionFromDocument(frame) });
+});
+
+addMessageListener("ContextMenu:SearchFieldBookmarkData", (message) => {
+ let node = message.objects.target;
+
+ let charset = node.ownerDocument.characterSet;
+
+ let formBaseURI = BrowserUtils.makeURI(node.form.baseURI,
+ charset);
+
+ let formURI = BrowserUtils.makeURI(node.form.getAttribute("action"),
+ charset,
+ formBaseURI);
+
+ let spec = formURI.spec;
+
+ let isURLEncoded =
+ (node.form.method.toUpperCase() == "POST"
+ && (node.form.enctype == "application/x-www-form-urlencoded" ||
+ node.form.enctype == ""));
+
+ let title = node.ownerDocument.title;
+ let description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
+
+ let formData = [];
+
+ function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
+ if (aIsFormUrlEncoded) {
+ return escape(aName + "=" + aValue);
+ }
+ return escape(aName) + "=" + escape(aValue);
+ }
+
+ for (let el of node.form.elements) {
+ if (!el.type) // happens with fieldsets
+ continue;
+
+ if (el == node) {
+ formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
+ // Don't escape "%s", just append
+ escapeNameValuePair(el.name, "", false) + "%s");
+ continue;
+ }
+
+ let type = el.type.toLowerCase();
+
+ if (((el instanceof content.HTMLInputElement && el.mozIsTextField(true)) ||
+ type == "hidden" || type == "textarea") ||
+ ((type == "checkbox" || type == "radio") && el.checked)) {
+ formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
+ } else if (el instanceof content.HTMLSelectElement && el.selectedIndex >= 0) {
+ for (let j=0; j < el.options.length; j++) {
+ if (el.options[j].selected)
+ formData.push(escapeNameValuePair(el.name, el.options[j].value,
+ isURLEncoded));
+ }
+ }
+ }
+
+ let postData;
+
+ if (isURLEncoded)
+ postData = formData.join("&");
+ else {
+ let separator = spec.includes("?") ? "&" : "?";
+ spec += separator + formData.join("&");
+ }
+
+ sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
+ { spec, title, description, postData, charset });
+});
+
+addMessageListener("Bookmarks:GetPageDetails", (message) => {
+ let doc = content.document;
+ let isErrorPage = /^about:(neterror|certerror|blocked)/.test(doc.documentURI);
+ sendAsyncMessage("Bookmarks:GetPageDetails:Result",
+ { isErrorPage: isErrorPage,
+ description: PlacesUIUtils.getDescriptionFromDocument(doc) });
+});
+
+var LightWeightThemeWebInstallListener = {
+ _previewWindow: null,
+
+ init: function() {
+ addEventListener("InstallBrowserTheme", this, false, true);
+ addEventListener("PreviewBrowserTheme", this, false, true);
+ addEventListener("ResetBrowserThemePreview", this, false, true);
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "InstallBrowserTheme": {
+ sendAsyncMessage("LightWeightThemeWebInstaller:Install", {
+ baseURI: event.target.baseURI,
+ themeData: event.target.getAttribute("data-browsertheme"),
+ });
+ break;
+ }
+ case "PreviewBrowserTheme": {
+ sendAsyncMessage("LightWeightThemeWebInstaller:Preview", {
+ baseURI: event.target.baseURI,
+ themeData: event.target.getAttribute("data-browsertheme"),
+ });
+ this._previewWindow = event.target.ownerGlobal;
+ this._previewWindow.addEventListener("pagehide", this, true);
+ break;
+ }
+ case "pagehide": {
+ sendAsyncMessage("LightWeightThemeWebInstaller:ResetPreview");
+ this._resetPreviewWindow();
+ break;
+ }
+ case "ResetBrowserThemePreview": {
+ if (this._previewWindow) {
+ sendAsyncMessage("LightWeightThemeWebInstaller:ResetPreview",
+ {baseURI: event.target.baseURI});
+ this._resetPreviewWindow();
+ }
+ break;
+ }
+ }
+ },
+
+ _resetPreviewWindow: function () {
+ this._previewWindow.removeEventListener("pagehide", this, true);
+ this._previewWindow = null;
+ }
+};
+
+LightWeightThemeWebInstallListener.init();
+
+function disableSetDesktopBackground(aTarget) {
+ // Disable the Set as Desktop Background menu item if we're still trying
+ // to load the image or the load failed.
+ if (!(aTarget instanceof Ci.nsIImageLoadingContent))
+ return true;
+
+ if (("complete" in aTarget) && !aTarget.complete)
+ return true;
+
+ if (aTarget.currentURI.schemeIs("javascript"))
+ return true;
+
+ let request = aTarget.QueryInterface(Ci.nsIImageLoadingContent)
+ .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (!request)
+ return true;
+
+ return false;
+}
+
+addMessageListener("ContextMenu:SetAsDesktopBackground", (message) => {
+ let target = message.objects.target;
+
+ // Paranoia: check disableSetDesktopBackground again, in case the
+ // image changed since the context menu was initiated.
+ let disable = disableSetDesktopBackground(target);
+
+ if (!disable) {
+ try {
+ BrowserUtils.urlSecurityCheck(target.currentURI.spec, target.ownerDocument.nodePrincipal);
+ let canvas = content.document.createElement("canvas");
+ canvas.width = target.naturalWidth;
+ canvas.height = target.naturalHeight;
+ let ctx = canvas.getContext("2d");
+ ctx.drawImage(target, 0, 0);
+ let dataUrl = canvas.toDataURL();
+ sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+ { dataUrl });
+ }
+ catch (e) {
+ Cu.reportError(e);
+ disable = true;
+ }
+ }
+
+ if (disable)
+ sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result", { disable });
+});
+
+var PageInfoListener = {
+
+ init: function() {
+ addMessageListener("PageInfo:getData", this);
+ },
+
+ receiveMessage: function(message) {
+ let strings = message.data.strings;
+ let window;
+ let document;
+
+ let frameOuterWindowID = message.data.frameOuterWindowID;
+
+ // If inside frame then get the frame's window and document.
+ if (frameOuterWindowID) {
+ window = Services.wm.getOuterWindowWithId(frameOuterWindowID);
+ document = window.document;
+ }
+ else {
+ window = content.window;
+ document = content.document;
+ }
+
+ let imageElement = message.objects.imageElement;
+
+ let pageInfoData = {metaViewRows: this.getMetaInfo(document),
+ docInfo: this.getDocumentInfo(document),
+ feeds: this.getFeedsInfo(document, strings),
+ windowInfo: this.getWindowInfo(window),
+ imageInfo: this.getImageInfo(imageElement)};
+
+ sendAsyncMessage("PageInfo:data", pageInfoData);
+
+ // Separate step so page info dialog isn't blank while waiting for this to finish.
+ this.getMediaInfo(document, window, strings);
+ },
+
+ getImageInfo: function(imageElement) {
+ let imageInfo = null;
+ if (imageElement) {
+ imageInfo = {
+ currentSrc: imageElement.currentSrc,
+ width: imageElement.width,
+ height: imageElement.height,
+ imageText: imageElement.title || imageElement.alt
+ };
+ }
+ return imageInfo;
+ },
+
+ getMetaInfo: function(document) {
+ let metaViewRows = [];
+
+ // Get the meta tags from the page.
+ let metaNodes = document.getElementsByTagName("meta");
+
+ for (let metaNode of metaNodes) {
+ metaViewRows.push([metaNode.name || metaNode.httpEquiv || metaNode.getAttribute("property"),
+ metaNode.content]);
+ }
+
+ return metaViewRows;
+ },
+
+ getWindowInfo: function(window) {
+ let windowInfo = {};
+ windowInfo.isTopWindow = window == window.top;
+
+ let hostName = null;
+ try {
+ hostName = window.location.host;
+ }
+ catch (exception) { }
+
+ windowInfo.hostName = hostName;
+ return windowInfo;
+ },
+
+ getDocumentInfo: function(document) {
+ let docInfo = {};
+ docInfo.title = document.title;
+ docInfo.location = document.location.toString();
+ docInfo.referrer = document.referrer;
+ docInfo.compatMode = document.compatMode;
+ docInfo.contentType = document.contentType;
+ docInfo.characterSet = document.characterSet;
+ docInfo.lastModified = document.lastModified;
+ docInfo.principal = document.nodePrincipal;
+
+ let documentURIObject = {};
+ documentURIObject.spec = document.documentURIObject.spec;
+ documentURIObject.originCharset = document.documentURIObject.originCharset;
+ docInfo.documentURIObject = documentURIObject;
+
+ docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(content);
+
+ return docInfo;
+ },
+
+ getFeedsInfo: function(document, strings) {
+ let feeds = [];
+ // Get the feeds from the page.
+ let linkNodes = document.getElementsByTagName("link");
+ let length = linkNodes.length;
+ for (let i = 0; i < length; i++) {
+ let link = linkNodes[i];
+ if (!link.href) {
+ continue;
+ }
+ let rel = link.rel && link.rel.toLowerCase();
+ let rels = {};
+
+ if (rel) {
+ for (let relVal of rel.split(/\s+/)) {
+ rels[relVal] = true;
+ }
+ }
+
+ if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) {
+ let type = Feeds.isValidFeed(link, document.nodePrincipal, "feed" in rels);
+ if (type) {
+ type = strings[type] || strings["application/rss+xml"];
+ feeds.push([link.title, type, link.href]);
+ }
+ }
+ }
+ return feeds;
+ },
+
+ // Only called once to get the media tab's media elements from the content page.
+ getMediaInfo: function(document, window, strings)
+ {
+ let frameList = this.goThroughFrames(document, window);
+ Task.spawn(() => this.processFrames(document, frameList, strings));
+ },
+
+ goThroughFrames: function(document, window)
+ {
+ let frameList = [document];
+ if (window && window.frames.length > 0) {
+ let num = window.frames.length;
+ for (let i = 0; i < num; i++) {
+ // Recurse through the frames.
+ frameList.concat(this.goThroughFrames(window.frames[i].document,
+ window.frames[i]));
+ }
+ }
+ return frameList;
+ },
+
+ processFrames: function*(document, frameList, strings)
+ {
+ let nodeCount = 0;
+ for (let doc of frameList) {
+ let iterator = doc.createTreeWalker(doc, content.NodeFilter.SHOW_ELEMENT);
+
+ // Goes through all the elements on the doc. imageViewRows takes only the media elements.
+ while (iterator.nextNode()) {
+ let mediaItems = this.getMediaItems(document, strings, iterator.currentNode);
+
+ if (mediaItems.length) {
+ sendAsyncMessage("PageInfo:mediaData",
+ {mediaItems, isComplete: false});
+ }
+
+ if (++nodeCount % 500 == 0) {
+ // setTimeout every 500 elements so we don't keep blocking the content process.
+ yield new Promise(resolve => setTimeout(resolve, 10));
+ }
+ }
+ }
+ // Send that page info media fetching has finished.
+ sendAsyncMessage("PageInfo:mediaData", {isComplete: true});
+ },
+
+ getMediaItems: function(document, strings, elem)
+ {
+ // Check for images defined in CSS (e.g. background, borders)
+ let computedStyle = elem.ownerGlobal.getComputedStyle(elem);
+ // A node can have multiple media items associated with it - for example,
+ // multiple background images.
+ let mediaItems = [];
+
+ let addImage = (url, type, alt, elem, isBg) => {
+ let element = this.serializeElementInfo(document, url, type, alt, elem, isBg);
+ mediaItems.push([url, type, alt, element, isBg]);
+ };
+
+ if (computedStyle) {
+ let addImgFunc = (label, val) => {
+ if (val.primitiveType == content.CSSPrimitiveValue.CSS_URI) {
+ addImage(val.getStringValue(), label, strings.notSet, elem, true);
+ }
+ else if (val.primitiveType == content.CSSPrimitiveValue.CSS_STRING) {
+ // This is for -moz-image-rect.
+ // TODO: Reimplement once bug 714757 is fixed.
+ let strVal = val.getStringValue();
+ if (strVal.search(/^.*url\(\"?/) > -1) {
+ let url = strVal.replace(/^.*url\(\"?/, "").replace(/\"?\).*$/, "");
+ addImage(url, label, strings.notSet, elem, true);
+ }
+ }
+ else if (val.cssValueType == content.CSSValue.CSS_VALUE_LIST) {
+ // Recursively resolve multiple nested CSS value lists.
+ for (let i = 0; i < val.length; i++) {
+ addImgFunc(label, val.item(i));
+ }
+ }
+ };
+
+ addImgFunc(strings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
+ addImgFunc(strings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
+ addImgFunc(strings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
+ addImgFunc(strings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
+ }
+
+ // One swi^H^H^Hif-else to rule them all.
+ if (elem instanceof content.HTMLImageElement) {
+ addImage(elem.src, strings.mediaImg,
+ (elem.hasAttribute("alt")) ? elem.alt : strings.notSet, elem, false);
+ }
+ else if (elem instanceof content.SVGImageElement) {
+ try {
+ // Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
+ // or the URI formed from the baseURI and the URL is not a valid URI.
+ if (elem.href.baseVal) {
+ let href = Services.io.newURI(elem.href.baseVal, null, Services.io.newURI(elem.baseURI)).spec;
+ addImage(href, strings.mediaImg, "", elem, false);
+ }
+ } catch (e) { }
+ }
+ else if (elem instanceof content.HTMLVideoElement) {
+ addImage(elem.currentSrc, strings.mediaVideo, "", elem, false);
+ }
+ else if (elem instanceof content.HTMLAudioElement) {
+ addImage(elem.currentSrc, strings.mediaAudio, "", elem, false);
+ }
+ else if (elem instanceof content.HTMLLinkElement) {
+ if (elem.rel && /\bicon\b/i.test(elem.rel)) {
+ addImage(elem.href, strings.mediaLink, "", elem, false);
+ }
+ }
+ else if (elem instanceof content.HTMLInputElement || elem instanceof content.HTMLButtonElement) {
+ if (elem.type.toLowerCase() == "image") {
+ addImage(elem.src, strings.mediaInput,
+ (elem.hasAttribute("alt")) ? elem.alt : strings.notSet, elem, false);
+ }
+ }
+ else if (elem instanceof content.HTMLObjectElement) {
+ addImage(elem.data, strings.mediaObject, this.getValueText(elem), elem, false);
+ }
+ else if (elem instanceof content.HTMLEmbedElement) {
+ addImage(elem.src, strings.mediaEmbed, "", elem, false);
+ }
+
+ return mediaItems;
+ },
+
+ /**
+ * Set up a JSON element object with all the instanceOf and other infomation that
+ * makePreview in pageInfo.js uses to figure out how to display the preview.
+ */
+
+ serializeElementInfo: function(document, url, type, alt, item, isBG)
+ {
+ let result = {};
+
+ let imageText;
+ if (!isBG &&
+ !(item instanceof content.SVGImageElement) &&
+ !(document instanceof content.ImageDocument)) {
+ imageText = item.title || item.alt;
+
+ if (!imageText && !(item instanceof content.HTMLImageElement)) {
+ imageText = this.getValueText(item);
+ }
+ }
+
+ result.imageText = imageText;
+ result.longDesc = item.longDesc;
+ result.numFrames = 1;
+
+ if (item instanceof content.HTMLObjectElement ||
+ item instanceof content.HTMLEmbedElement ||
+ item instanceof content.HTMLLinkElement) {
+ result.mimeType = item.type;
+ }
+
+ if (!result.mimeType && !isBG && item instanceof Ci.nsIImageLoadingContent) {
+ // Interface for image loading content.
+ let imageRequest = item.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (imageRequest) {
+ result.mimeType = imageRequest.mimeType;
+ let image = !(imageRequest.imageStatus & imageRequest.STATUS_ERROR) && imageRequest.image;
+ if (image) {
+ result.numFrames = image.numFrames;
+ }
+ }
+ }
+
+ // If we have a data url, get the MIME type from the url.
+ if (!result.mimeType && url.startsWith("data:")) {
+ let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
+ if (dataMimeType)
+ result.mimeType = dataMimeType[1].toLowerCase();
+ }
+
+ result.HTMLLinkElement = item instanceof content.HTMLLinkElement;
+ result.HTMLInputElement = item instanceof content.HTMLInputElement;
+ result.HTMLImageElement = item instanceof content.HTMLImageElement;
+ result.HTMLObjectElement = item instanceof content.HTMLObjectElement;
+ result.SVGImageElement = item instanceof content.SVGImageElement;
+ result.HTMLVideoElement = item instanceof content.HTMLVideoElement;
+ result.HTMLAudioElement = item instanceof content.HTMLAudioElement;
+
+ if (isBG) {
+ // Items that are showing this image as a background
+ // image might not necessarily have a width or height,
+ // so we'll dynamically generate an image and send up the
+ // natural dimensions.
+ let img = content.document.createElement("img");
+ img.src = url;
+ result.naturalWidth = img.naturalWidth;
+ result.naturalHeight = img.naturalHeight;
+ } else {
+ // Otherwise, we can use the current width and height
+ // of the image.
+ result.width = item.width;
+ result.height = item.height;
+ }
+
+ if (item instanceof content.SVGImageElement) {
+ result.SVGImageElementWidth = item.width.baseVal.value;
+ result.SVGImageElementHeight = item.height.baseVal.value;
+ }
+
+ result.baseURI = item.baseURI;
+
+ return result;
+ },
+
+ // Other Misc Stuff
+ // Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
+ // parse a node to extract the contents of the node
+ getValueText: function(node)
+ {
+
+ let valueText = "";
+
+ // Form input elements don't generally contain information that is useful to our callers, so return nothing.
+ if (node instanceof content.HTMLInputElement ||
+ node instanceof content.HTMLSelectElement ||
+ node instanceof content.HTMLTextAreaElement) {
+ return valueText;
+ }
+
+ // Otherwise recurse for each child.
+ let length = node.childNodes.length;
+
+ for (let i = 0; i < length; i++) {
+ let childNode = node.childNodes[i];
+ let nodeType = childNode.nodeType;
+
+ // Text nodes are where the goods are.
+ if (nodeType == content.Node.TEXT_NODE) {
+ valueText += " " + childNode.nodeValue;
+ }
+ // And elements can have more text inside them.
+ else if (nodeType == content.Node.ELEMENT_NODE) {
+ // Images are special, we want to capture the alt text as if the image weren't there.
+ if (childNode instanceof content.HTMLImageElement) {
+ valueText += " " + this.getAltText(childNode);
+ }
+ else {
+ valueText += " " + this.getValueText(childNode);
+ }
+ }
+ }
+
+ return this.stripWS(valueText);
+ },
+
+ // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
+ // Traverse the tree in search of an img or area element and grab its alt tag.
+ getAltText: function(node)
+ {
+ let altText = "";
+
+ if (node.alt) {
+ return node.alt;
+ }
+ let length = node.childNodes.length;
+ for (let i = 0; i < length; i++) {
+ if ((altText = this.getAltText(node.childNodes[i]) != undefined)) { // stupid js warning...
+ return altText;
+ }
+ }
+ return "";
+ },
+
+ // Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html.
+ // Strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space.
+ stripWS: function(text)
+ {
+ let middleRE = /\s+/g;
+ let endRE = /(^\s+)|(\s+$)/g;
+
+ text = text.replace(middleRE, " ");
+ return text.replace(endRE, "");
+ }
+};
+PageInfoListener.init();
+
+let OfflineApps = {
+ _docId: 0,
+ _docIdMap: new Map(),
+
+ _docManifestSet: new Set(),
+
+ _observerAdded: false,
+ registerWindow(aWindow) {
+ if (!this._observerAdded) {
+ this._observerAdded = true;
+ Services.obs.addObserver(this, "offline-cache-update-completed", true);
+ }
+ let manifestURI = this._getManifestURI(aWindow);
+ this._docManifestSet.add(manifestURI.spec);
+ },
+
+ handleEvent(event) {
+ if (event.type == "MozApplicationManifest") {
+ this.offlineAppRequested(event.originalTarget.defaultView);
+ }
+ },
+
+ _getManifestURI(aWindow) {
+ if (!aWindow.document.documentElement)
+ return null;
+
+ var attr = aWindow.document.documentElement.getAttribute("manifest");
+ if (!attr)
+ return null;
+
+ try {
+ var contentURI = BrowserUtils.makeURI(aWindow.location.href, null, null);
+ return BrowserUtils.makeURI(attr, aWindow.document.characterSet, contentURI);
+ } catch (e) {
+ return null;
+ }
+ },
+
+ offlineAppRequested(aContentWindow) {
+ this.registerWindow(aContentWindow);
+ if (!Services.prefs.getBoolPref("browser.offline-apps.notify")) {
+ return;
+ }
+
+ let currentURI = aContentWindow.document.documentURIObject;
+ // don't bother showing UI if the user has already made a decision
+ if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
+ return;
+
+ try {
+ if (Services.prefs.getBoolPref("offline-apps.allow_by_default")) {
+ // all pages can use offline capabilities, no need to ask the user
+ return;
+ }
+ } catch (e) {
+ // this pref isn't set by default, ignore failures
+ }
+ let docId = ++this._docId;
+ this._docIdMap.set(docId, Cu.getWeakReference(aContentWindow.document));
+ sendAsyncMessage("OfflineApps:RequestPermission", {
+ uri: currentURI.spec,
+ docId,
+ });
+ },
+
+ _startFetching(aDocument) {
+ if (!aDocument.documentElement)
+ return;
+
+ let manifestURI = this._getManifestURI(aDocument.defaultView);
+ if (!manifestURI)
+ return;
+
+ var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject,
+ aDocument.nodePrincipal, aDocument.defaultView);
+ },
+
+ receiveMessage(aMessage) {
+ if (aMessage.name == "OfflineApps:StartFetching") {
+ let doc = this._docIdMap.get(aMessage.data.docId);
+ doc = doc && doc.get();
+ if (doc) {
+ this._startFetching(doc);
+ }
+ this._docIdMap.delete(aMessage.data.docId);
+ }
+ },
+
+ observe(aSubject, aTopic, aState) {
+ if (aTopic == "offline-cache-update-completed") {
+ let cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
+ let uri = cacheUpdate.manifestURI;
+ if (uri && this._docManifestSet.has(uri.spec)) {
+ sendAsyncMessage("OfflineApps:CheckUsage", {uri: uri.spec});
+ }
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+};
+
+addEventListener("MozApplicationManifest", OfflineApps, false);
+addMessageListener("OfflineApps:StartFetching", OfflineApps);
diff --git a/browser/base/content/contentSearchUI.css b/browser/base/content/contentSearchUI.css
new file mode 100644
index 000000000..cd5cf5008
--- /dev/null
+++ b/browser/base/content/contentSearchUI.css
@@ -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/. */
+
+.contentSearchSuggestionTable {
+ background-color: hsla(0,0%,100%,.99);
+ border: 1px solid hsla(0, 0%, 0%, .2);
+ border-top: none;
+ box-shadow: 0 5px 10px hsla(0, 0%, 0%, .1);
+ position: absolute;
+ left: 0;
+ z-index: 1001;
+ -moz-user-select: none;
+ cursor: default;
+}
+
+.contentSearchSuggestionTable:-moz-dir(rtl) {
+ left: auto;
+ right: 0;
+}
+
+.contentSearchSuggestionsList {
+ border-bottom: 1px solid hsl(0, 0%, 92%);
+ width: 100%;
+ height: 100%;
+}
+
+.contentSearchSuggestionTable,
+.contentSearchSuggestionsList {
+ border-spacing: 0;
+ overflow: hidden;
+ padding: 0;
+ margin: 0;
+ text-align: start;
+}
+
+.contentSearchHeaderRow,
+.contentSearchSuggestionRow {
+ margin: 0;
+ max-width: inherit;
+ padding: 0;
+}
+
+.contentSearchHeaderRow > td > img,
+.contentSearchSuggestionRow > td > .historyIcon {
+ margin-inline-end: 8px;
+ margin-bottom: -3px;
+}
+
+.contentSearchSuggestionTable .historyIcon {
+ width: 16px;
+ height: 16px;
+ display: inline-block;
+ background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon");
+}
+
+.contentSearchSuggestionRow.selected > td > .historyIcon {
+ background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active");
+}
+
+.contentSearchHeader > img {
+ height: 16px;
+ width: 16px;
+ margin: 0;
+ padding: 0;
+}
+
+.contentSearchSuggestionRow.remote > td > .historyIcon {
+ visibility: hidden;
+}
+
+.contentSearchSuggestionRow.selected {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.contentSearchHeader,
+.contentSearchSuggestionEntry {
+ margin: 0;
+ max-width: inherit;
+ overflow: hidden;
+ padding: 4px 10px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: 75%;
+}
+
+.contentSearchHeader {
+ background-color: hsl(0, 0%, 97%);
+ color: #666;
+ border-bottom: 1px solid hsl(0, 0%, 92%);
+}
+
+.contentSearchSuggestionsContainer {
+ margin: 0;
+ padding: 0;
+ border-spacing: 0;
+ width: 100%;
+}
+
+.contentSearchSearchWithHeaderSearchText {
+ white-space: pre;
+ font-weight: bold;
+}
+
+.contentSearchOneOffItem {
+ -moz-appearance: none;
+ height: 32px;
+ margin: 0;
+ padding: 0;
+ border: none;
+ background: none;
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAWCAYAAAABxvaqAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gofECQNNVW2/AAAABBJREFUGFdjOHPmzH8GehEA/KpKg9YTf4AAAAAASUVORK5CYII=');
+ background-repeat: no-repeat;
+ background-position: right center;
+}
+
+.contentSearchOneOffItem:-moz-dir(rtl) {
+ background-position: left center;
+}
+
+.contentSearchOneOffItem > img {
+ width: 16px;
+ height: 16px;
+ margin-bottom: -2px;
+}
+
+.contentSearchOneOffItem:not(.last-row) {
+ border-bottom: 1px solid hsl(0, 0%, 92%);
+}
+
+.contentSearchOneOffItem.end-of-row {
+ background-image: none;
+}
+
+.contentSearchOneOffItem.selected {
+ background-color: Highlight;
+ background-image: none;
+}
+
+.contentSearchOneOffsTable {
+ width: 100%;
+}
+
+.contentSearchSettingsButton {
+ margin: 0;
+ padding: 0;
+ height: 32px;
+ border: none;
+ border-top: 1px solid hsla(0, 0%, 0%, .08);
+ text-align: center;
+ width: 100%;
+}
+
+.contentSearchSettingsButton.selected {
+ background-color: hsl(0, 0%, 90%);
+}
+
+.contentSearchSettingsButton:active {
+ background-color: hsl(0, 0%, 85%);
+}
diff --git a/browser/base/content/contentSearchUI.js b/browser/base/content/contentSearchUI.js
new file mode 100644
index 000000000..9136ea8f2
--- /dev/null
+++ b/browser/base/content/contentSearchUI.js
@@ -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/. */
+
+"use strict";
+
+this.ContentSearchUIController = (function () {
+
+const MAX_DISPLAYED_SUGGESTIONS = 6;
+const SUGGESTION_ID_PREFIX = "searchSuggestion";
+const ONE_OFF_ID_PREFIX = "oneOff";
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+/**
+ * Creates a new object that manages search suggestions and their UI for a text
+ * box.
+ *
+ * The UI consists of an html:table that's inserted into the DOM after the given
+ * text box and styled so that it appears as a dropdown below the text box.
+ *
+ * @param inputElement
+ * Search suggestions will be based on the text in this text box.
+ * Assumed to be an html:input. xul:textbox is untested but might work.
+ * @param tableParent
+ * The suggestion table is appended as a child to this element. Since
+ * the table is absolutely positioned and its top and left values are set
+ * to be relative to the top and left of the page, either the parent and
+ * all its ancestors should not be positioned elements (i.e., their
+ * positions should be "static"), or the parent's position should be the
+ * top left of the page.
+ * @param healthReportKey
+ * This will be sent with the search data for FHR to record the search.
+ * @param searchPurpose
+ * Sent with search data, see nsISearchEngine.getSubmission.
+ * @param idPrefix
+ * The IDs of elements created by the object will be prefixed with this
+ * string.
+ */
+function ContentSearchUIController(inputElement, tableParent, healthReportKey,
+ searchPurpose, idPrefix="") {
+ this.input = inputElement;
+ this._idPrefix = idPrefix;
+ this._healthReportKey = healthReportKey;
+ this._searchPurpose = searchPurpose;
+
+ let tableID = idPrefix + "searchSuggestionTable";
+ this.input.autocomplete = "off";
+ this.input.setAttribute("aria-autocomplete", "true");
+ this.input.setAttribute("aria-controls", tableID);
+ tableParent.appendChild(this._makeTable(tableID));
+
+ this.input.addEventListener("keypress", this);
+ this.input.addEventListener("input", this);
+ this.input.addEventListener("focus", this);
+ this.input.addEventListener("blur", this);
+ window.addEventListener("ContentSearchService", this);
+
+ this._stickyInputValue = "";
+ this._hideSuggestions();
+
+ this._getSearchEngines();
+ this._getStrings();
+}
+
+ContentSearchUIController.prototype = {
+
+ // The timeout (ms) of the remote suggestions. Corresponds to
+ // SearchSuggestionController.remoteTimeout. Uses
+ // SearchSuggestionController's default timeout if falsey.
+ remoteTimeout: undefined,
+ _oneOffButtons: [],
+ // Setting up the one off buttons causes an uninterruptible reflow. If we
+ // receive the list of engines while the newtab page is loading, this reflow
+ // may regress performance - so we set this flag and only set up the buttons
+ // if it's set when the suggestions table is actually opened.
+ _pendingOneOffRefresh: undefined,
+
+ get defaultEngine() {
+ return this._defaultEngine;
+ },
+
+ set defaultEngine(engine) {
+ if (this._defaultEngine && this._defaultEngine.icon) {
+ URL.revokeObjectURL(this._defaultEngine.icon);
+ }
+ let icon;
+ if (engine.iconBuffer) {
+ icon = this._getFaviconURIFromBuffer(engine.iconBuffer);
+ }
+ else {
+ icon = this._getImageURIForCurrentResolution(
+ "chrome://mozapps/skin/places/defaultFavicon.png");
+ }
+ this._defaultEngine = {
+ name: engine.name,
+ icon: icon,
+ };
+ this._updateDefaultEngineHeader();
+
+ if (engine && document.activeElement == this.input) {
+ this._speculativeConnect();
+ }
+ },
+
+ get engines() {
+ return this._engines;
+ },
+
+ set engines(val) {
+ this._engines = val;
+ this._pendingOneOffRefresh = true;
+ },
+
+ // The selectedIndex is the index of the element with the "selected" class in
+ // the list obtained by concatenating the suggestion rows, one-off buttons, and
+ // search settings button.
+ get selectedIndex() {
+ let allElts = [...this._suggestionsList.children,
+ ...this._oneOffButtons,
+ document.getElementById("contentSearchSettingsButton")];
+ for (let i = 0; i < allElts.length; ++i) {
+ let elt = allElts[i];
+ if (elt.classList.contains("selected")) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ set selectedIndex(idx) {
+ // Update the table's rows, and the input when there is a selection.
+ this._table.removeAttribute("aria-activedescendant");
+ this.input.removeAttribute("aria-activedescendant");
+
+ let allElts = [...this._suggestionsList.children,
+ ...this._oneOffButtons,
+ document.getElementById("contentSearchSettingsButton")];
+ // If we are selecting a suggestion and a one-off is selected, don't deselect it.
+ let excludeIndex = idx < this.numSuggestions && this.selectedButtonIndex > -1 ?
+ this.numSuggestions + this.selectedButtonIndex : -1;
+ for (let i = 0; i < allElts.length; ++i) {
+ let elt = allElts[i];
+ let ariaSelectedElt = i < this.numSuggestions ? elt.firstChild : elt;
+ if (i == idx) {
+ elt.classList.add("selected");
+ ariaSelectedElt.setAttribute("aria-selected", "true");
+ this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id);
+ }
+ else if (i != excludeIndex) {
+ elt.classList.remove("selected");
+ ariaSelectedElt.setAttribute("aria-selected", "false");
+ }
+ }
+ },
+
+ get selectedButtonIndex() {
+ let elts = [...this._oneOffButtons,
+ document.getElementById("contentSearchSettingsButton")];
+ for (let i = 0; i < elts.length; ++i) {
+ if (elts[i].classList.contains("selected")) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ set selectedButtonIndex(idx) {
+ let elts = [...this._oneOffButtons,
+ document.getElementById("contentSearchSettingsButton")];
+ for (let i = 0; i < elts.length; ++i) {
+ let elt = elts[i];
+ if (i == idx) {
+ elt.classList.add("selected");
+ elt.setAttribute("aria-selected", "true");
+ }
+ else {
+ elt.classList.remove("selected");
+ elt.setAttribute("aria-selected", "false");
+ }
+ }
+ },
+
+ get selectedEngineName() {
+ let selectedElt = this._oneOffsTable.querySelector(".selected");
+ if (selectedElt) {
+ return selectedElt.engineName;
+ }
+ return this.defaultEngine.name;
+ },
+
+ get numSuggestions() {
+ return this._suggestionsList.children.length;
+ },
+
+ selectAndUpdateInput: function (idx) {
+ this.selectedIndex = idx;
+ let newValue = this.suggestionAtIndex(idx) || this._stickyInputValue;
+ // Setting the input value when the value has not changed commits the current
+ // IME composition, which we don't want to do.
+ if (this.input.value != newValue) {
+ this.input.value = newValue;
+ }
+ this._updateSearchWithHeader();
+ },
+
+ suggestionAtIndex: function (idx) {
+ let row = this._suggestionsList.children[idx];
+ return row ? row.textContent : null;
+ },
+
+ deleteSuggestionAtIndex: function (idx) {
+ // Only form history suggestions can be deleted.
+ if (this.isFormHistorySuggestionAtIndex(idx)) {
+ let suggestionStr = this.suggestionAtIndex(idx);
+ this._sendMsg("RemoveFormHistoryEntry", suggestionStr);
+ this._suggestionsList.children[idx].remove();
+ this.selectAndUpdateInput(-1);
+ }
+ },
+
+ isFormHistorySuggestionAtIndex: function (idx) {
+ let row = this._suggestionsList.children[idx];
+ return row && row.classList.contains("formHistory");
+ },
+
+ addInputValueToFormHistory: function () {
+ this._sendMsg("AddFormHistoryEntry", this.input.value);
+ },
+
+ handleEvent: function (event) {
+ this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event);
+ },
+
+ _onCommand: function(aEvent) {
+ if (this.selectedButtonIndex == this._oneOffButtons.length) {
+ // Settings button was selected.
+ this._sendMsg("ManageEngines");
+ return;
+ }
+
+ this.search(aEvent);
+
+ if (aEvent) {
+ aEvent.preventDefault();
+ }
+ },
+
+ search: function (aEvent) {
+ if (!this.defaultEngine) {
+ return; // Not initialized yet.
+ }
+
+ let searchText = this.input;
+ let searchTerms;
+ if (this._table.hidden ||
+ aEvent.originalTarget.id == "contentSearchDefaultEngineHeader" ||
+ aEvent instanceof KeyboardEvent) {
+ searchTerms = searchText.value;
+ }
+ else {
+ searchTerms = this.suggestionAtIndex(this.selectedIndex) || searchText.value;
+ }
+ // Send an event that will perform a search and Firefox Health Report will
+ // record that a search from the healthReportKey passed to the constructor.
+ let eventData = {
+ engineName: this.selectedEngineName,
+ searchString: searchTerms,
+ healthReportKey: this._healthReportKey,
+ searchPurpose: this._searchPurpose,
+ originalEvent: {
+ shiftKey: aEvent.shiftKey,
+ ctrlKey: aEvent.ctrlKey,
+ metaKey: aEvent.metaKey,
+ altKey: aEvent.altKey,
+ },
+ };
+ if ("button" in aEvent) {
+ eventData.originalEvent.button = aEvent.button;
+ }
+
+ if (this.suggestionAtIndex(this.selectedIndex)) {
+ eventData.selection = {
+ index: this.selectedIndex,
+ kind: undefined,
+ };
+ if (aEvent instanceof MouseEvent) {
+ eventData.selection.kind = "mouse";
+ } else if (aEvent instanceof KeyboardEvent) {
+ eventData.selection.kind = "key";
+ }
+ }
+
+ this._sendMsg("Search", eventData);
+ this.addInputValueToFormHistory();
+ },
+
+ _onInput: function () {
+ if (!this.input.value) {
+ this._stickyInputValue = "";
+ this._hideSuggestions();
+ }
+ else if (this.input.value != this._stickyInputValue) {
+ // Only fetch new suggestions if the input value has changed.
+ this._getSuggestions();
+ this.selectAndUpdateInput(-1);
+ }
+ this._updateSearchWithHeader();
+ },
+
+ _onKeypress: function (event) {
+ let selectedIndexDelta = 0;
+ let selectedSuggestionDelta = 0;
+ let selectedOneOffDelta = 0;
+
+ switch (event.keyCode) {
+ case event.DOM_VK_UP:
+ if (this._table.hidden) {
+ return;
+ }
+ if (event.getModifierState("Accel")) {
+ if (event.shiftKey) {
+ selectedSuggestionDelta = -1;
+ break;
+ }
+ this._cycleCurrentEngine(true);
+ break;
+ }
+ if (event.altKey) {
+ selectedOneOffDelta = -1;
+ break;
+ }
+ selectedIndexDelta = -1;
+ break;
+ case event.DOM_VK_DOWN:
+ if (this._table.hidden) {
+ this._getSuggestions();
+ return;
+ }
+ if (event.getModifierState("Accel")) {
+ if (event.shiftKey) {
+ selectedSuggestionDelta = 1;
+ break;
+ }
+ this._cycleCurrentEngine(false);
+ break;
+ }
+ if (event.altKey) {
+ selectedOneOffDelta = 1;
+ break;
+ }
+ selectedIndexDelta = 1;
+ break;
+ case event.DOM_VK_TAB:
+ if (this._table.hidden) {
+ return;
+ }
+ // Shift+tab when either the first or no one-off is selected, as well as
+ // tab when the settings button is selected, should change focus as normal.
+ if ((this.selectedButtonIndex <= 0 && event.shiftKey) ||
+ this.selectedButtonIndex == this._oneOffButtons.length && !event.shiftKey) {
+ return;
+ }
+ selectedOneOffDelta = event.shiftKey ? -1 : 1;
+ break;
+ case event.DOM_VK_RIGHT:
+ // Allow normal caret movement until the caret is at the end of the input.
+ if (this.input.selectionStart != this.input.selectionEnd ||
+ this.input.selectionEnd != this.input.value.length) {
+ return;
+ }
+ if (this.numSuggestions && this.selectedIndex >= 0 &&
+ this.selectedIndex < this.numSuggestions) {
+ this.input.value = this.suggestionAtIndex(this.selectedIndex);
+ this.input.setAttribute("selection-index", this.selectedIndex);
+ this.input.setAttribute("selection-kind", "key");
+ } else {
+ // If we didn't select anything, make sure to remove the attributes
+ // in case they were populated last time.
+ this.input.removeAttribute("selection-index");
+ this.input.removeAttribute("selection-kind");
+ }
+ this._stickyInputValue = this.input.value;
+ this._hideSuggestions();
+ return;
+ case event.DOM_VK_RETURN:
+ this._onCommand(event);
+ return;
+ case event.DOM_VK_DELETE:
+ if (this.selectedIndex >= 0) {
+ this.deleteSuggestionAtIndex(this.selectedIndex);
+ }
+ return;
+ case event.DOM_VK_ESCAPE:
+ if (!this._table.hidden) {
+ this._hideSuggestions();
+ }
+ return;
+ default:
+ return;
+ }
+
+ let currentIndex = this.selectedIndex;
+ if (selectedIndexDelta) {
+ let newSelectedIndex = currentIndex + selectedIndexDelta;
+ if (newSelectedIndex < -1) {
+ newSelectedIndex = this.numSuggestions + this._oneOffButtons.length;
+ }
+ // If are moving up from the first one off, we have to deselect the one off
+ // manually because the selectedIndex setter tries to exclude the selected
+ // one-off (which is desirable for accel+shift+up/down).
+ if (currentIndex == this.numSuggestions && selectedIndexDelta == -1) {
+ this.selectedButtonIndex = -1;
+ }
+ this.selectAndUpdateInput(newSelectedIndex);
+ }
+
+ else if (selectedSuggestionDelta) {
+ let newSelectedIndex;
+ if (currentIndex >= this.numSuggestions || currentIndex == -1) {
+ // No suggestion already selected, select the first/last one appropriately.
+ newSelectedIndex = selectedSuggestionDelta == 1 ?
+ 0 : this.numSuggestions - 1;
+ }
+ else {
+ newSelectedIndex = currentIndex + selectedSuggestionDelta;
+ }
+ if (newSelectedIndex >= this.numSuggestions) {
+ newSelectedIndex = -1;
+ }
+ this.selectAndUpdateInput(newSelectedIndex);
+ }
+
+ else if (selectedOneOffDelta) {
+ let newSelectedIndex;
+ let currentButton = this.selectedButtonIndex;
+ if (currentButton == -1 || currentButton == this._oneOffButtons.length) {
+ // No one-off already selected, select the first/last one appropriately.
+ newSelectedIndex = selectedOneOffDelta == 1 ?
+ 0 : this._oneOffButtons.length - 1;
+ }
+ else {
+ newSelectedIndex = currentButton + selectedOneOffDelta;
+ }
+ // Allow selection of the settings button via the tab key.
+ if (newSelectedIndex == this._oneOffButtons.length &&
+ event.keyCode != event.DOM_VK_TAB) {
+ newSelectedIndex = -1;
+ }
+ this.selectedButtonIndex = newSelectedIndex;
+ }
+
+ // Prevent the input's caret from moving.
+ event.preventDefault();
+ },
+
+ _currentEngineIndex: -1,
+ _cycleCurrentEngine: function (aReverse) {
+ if ((this._currentEngineIndex == this._engines.length - 1 && !aReverse) ||
+ (this._currentEngineIndex == 0 && aReverse)) {
+ return;
+ }
+ this._currentEngineIndex += aReverse ? -1 : 1;
+ let engineName = this._engines[this._currentEngineIndex].name;
+ this._sendMsg("SetCurrentEngine", engineName);
+ },
+
+ _onFocus: function () {
+ if (this._mousedown) {
+ return;
+ }
+ // When the input box loses focus to something in our table, we refocus it
+ // immediately. This causes the focus highlight to flicker, so we set a
+ // custom attribute which consumers should use for focus highlighting. This
+ // attribute is removed only when we do not immediately refocus the input
+ // box, thus eliminating flicker.
+ this.input.setAttribute("keepfocus", "true");
+ this._speculativeConnect();
+ },
+
+ _onBlur: function () {
+ if (this._mousedown) {
+ // At this point, this.input has lost focus, but a new element has not yet
+ // received it. If we re-focus this.input directly, the new element will
+ // steal focus immediately, so we queue it instead.
+ setTimeout(() => this.input.focus(), 0);
+ return;
+ }
+ this.input.removeAttribute("keepfocus");
+ this._hideSuggestions();
+ },
+
+ _onMousemove: function (event) {
+ let idx = this._indexOfTableItem(event.target);
+ if (idx >= this.numSuggestions) {
+ this.selectedButtonIndex = idx - this.numSuggestions;
+ return;
+ }
+ this.selectedIndex = idx;
+ },
+
+ _onMouseup: function (event) {
+ if (event.button == 2) {
+ return;
+ }
+ this._onCommand(event);
+ },
+
+ _onMouseout: function (event) {
+ // We only deselect one-off buttons and the settings button when they are
+ // moused out.
+ let idx = this._indexOfTableItem(event.originalTarget);
+ if (idx >= this.numSuggestions) {
+ this.selectedButtonIndex = -1;
+ }
+ },
+
+ _onClick: function (event) {
+ this._onMouseup(event);
+ },
+
+ _onContentSearchService: function (event) {
+ let methodName = "_onMsg" + event.detail.type;
+ if (methodName in this) {
+ this[methodName](event.detail.data);
+ }
+ },
+
+ _onMsgFocusInput: function (event) {
+ this.input.focus();
+ },
+
+ _onMsgSuggestions: function (suggestions) {
+ // Ignore the suggestions if their search string or engine doesn't match
+ // ours. Due to the async nature of message passing, this can easily happen
+ // when the user types quickly.
+ if (this._stickyInputValue != suggestions.searchString ||
+ this.defaultEngine.name != suggestions.engineName) {
+ return;
+ }
+
+ this._clearSuggestionRows();
+
+ // Position and size the table.
+ let { left } = this.input.getBoundingClientRect();
+ this._table.style.top = this.input.offsetHeight + "px";
+ this._table.style.minWidth = this.input.offsetWidth + "px";
+ this._table.style.maxWidth = (window.innerWidth - left - 40) + "px";
+
+ // Add the suggestions to the table.
+ let searchWords =
+ new Set(suggestions.searchString.trim().toLowerCase().split(/\s+/));
+ for (let i = 0; i < MAX_DISPLAYED_SUGGESTIONS; i++) {
+ let type, idx;
+ if (i < suggestions.formHistory.length) {
+ [type, idx] = ["formHistory", i];
+ }
+ else {
+ let j = i - suggestions.formHistory.length;
+ if (j < suggestions.remote.length) {
+ [type, idx] = ["remote", j];
+ }
+ else {
+ break;
+ }
+ }
+ this._suggestionsList.appendChild(
+ this._makeTableRow(type, suggestions[type][idx], i, searchWords));
+ }
+
+ if (this._table.hidden) {
+ this.selectedIndex = -1;
+ if (this._pendingOneOffRefresh) {
+ this._setUpOneOffButtons();
+ delete this._pendingOneOffRefresh;
+ }
+ this._currentEngineIndex =
+ this._engines.findIndex(aEngine => aEngine.name == this.defaultEngine.name);
+ this._table.hidden = false;
+ this.input.setAttribute("aria-expanded", "true");
+ this._originalDefaultEngine = {
+ name: this.defaultEngine.name,
+ icon: this.defaultEngine.icon,
+ };
+ }
+ },
+
+ _onMsgSuggestionsCancelled: function () {
+ if (!this._table.hidden) {
+ this._hideSuggestions();
+ }
+ },
+
+ _onMsgState: function (state) {
+ this.engines = state.engines;
+ // No point updating the default engine (and the header) if there's no change.
+ if (this.defaultEngine &&
+ this.defaultEngine.name == state.currentEngine.name &&
+ this.defaultEngine.icon == state.currentEngine.icon) {
+ return;
+ }
+ this.defaultEngine = state.currentEngine;
+ },
+
+ _onMsgCurrentState: function (state) {
+ this._onMsgState(state);
+ },
+
+ _onMsgCurrentEngine: function (engine) {
+ this.defaultEngine = engine;
+ this._pendingOneOffRefresh = true;
+ },
+
+ _onMsgStrings: function (strings) {
+ this._strings = strings;
+ this._updateDefaultEngineHeader();
+ this._updateSearchWithHeader();
+ document.getElementById("contentSearchSettingsButton").textContent =
+ this._strings.searchSettings;
+ this.input.setAttribute("placeholder", this._strings.searchPlaceholder);
+ },
+
+ _updateDefaultEngineHeader: function () {
+ let header = document.getElementById("contentSearchDefaultEngineHeader");
+ header.firstChild.setAttribute("src", this.defaultEngine.icon);
+ if (!this._strings) {
+ return;
+ }
+ while (header.firstChild.nextSibling) {
+ header.firstChild.nextSibling.remove();
+ }
+ header.appendChild(document.createTextNode(
+ this._strings.searchHeader.replace("%S", this.defaultEngine.name)));
+ },
+
+ _updateSearchWithHeader: function () {
+ if (!this._strings) {
+ return;
+ }
+ let searchWithHeader = document.getElementById("contentSearchSearchWithHeader");
+ if (this.input.value) {
+ searchWithHeader.innerHTML = this._strings.searchForSomethingWith;
+ searchWithHeader.querySelector('.contentSearchSearchWithHeaderSearchText').textContent = this.input.value;
+ } else {
+ searchWithHeader.textContent = this._strings.searchWithHeader;
+ }
+ },
+
+ _speculativeConnect: function () {
+ if (this.defaultEngine) {
+ this._sendMsg("SpeculativeConnect", this.defaultEngine.name);
+ }
+ },
+
+ _makeTableRow: function (type, suggestionStr, currentRow, searchWords) {
+ let row = document.createElementNS(HTML_NS, "tr");
+ row.dir = "auto";
+ row.classList.add("contentSearchSuggestionRow");
+ row.classList.add(type);
+ row.setAttribute("role", "presentation");
+ row.addEventListener("mousemove", this);
+ row.addEventListener("mouseup", this);
+
+ let entry = document.createElementNS(HTML_NS, "td");
+ let img = document.createElementNS(HTML_NS, "div");
+ img.setAttribute("class", "historyIcon");
+ entry.appendChild(img);
+ entry.classList.add("contentSearchSuggestionEntry");
+ entry.setAttribute("role", "option");
+ entry.id = this._idPrefix + SUGGESTION_ID_PREFIX + currentRow;
+ entry.setAttribute("aria-selected", "false");
+
+ let suggestionWords = suggestionStr.trim().toLowerCase().split(/\s+/);
+ for (let i = 0; i < suggestionWords.length; i++) {
+ let word = suggestionWords[i];
+ let wordSpan = document.createElementNS(HTML_NS, "span");
+ if (searchWords.has(word)) {
+ wordSpan.classList.add("typed");
+ }
+ wordSpan.textContent = word;
+ entry.appendChild(wordSpan);
+ if (i < suggestionWords.length - 1) {
+ entry.appendChild(document.createTextNode(" "));
+ }
+ }
+
+ row.appendChild(entry);
+ return row;
+ },
+
+ // Converts favicon array buffer into a data URI.
+ _getFaviconURIFromBuffer: function (buffer) {
+ let blob = new Blob([buffer]);
+ return URL.createObjectURL(blob);
+ },
+
+ // Adds "@2x" to the name of the given PNG url for "retina" screens.
+ _getImageURIForCurrentResolution: function (uri) {
+ if (window.devicePixelRatio > 1) {
+ return uri.replace(/\.png$/, "@2x.png");
+ }
+ return uri;
+ },
+
+ _getSearchEngines: function () {
+ this._sendMsg("GetState");
+ },
+
+ _getStrings: function () {
+ this._sendMsg("GetStrings");
+ },
+
+ _getSuggestions: function () {
+ this._stickyInputValue = this.input.value;
+ if (this.defaultEngine) {
+ this._sendMsg("GetSuggestions", {
+ engineName: this.defaultEngine.name,
+ searchString: this.input.value,
+ remoteTimeout: this.remoteTimeout,
+ });
+ }
+ },
+
+ _clearSuggestionRows: function() {
+ while (this._suggestionsList.firstElementChild) {
+ this._suggestionsList.firstElementChild.remove();
+ }
+ },
+
+ _hideSuggestions: function () {
+ this.input.setAttribute("aria-expanded", "false");
+ this.selectedIndex = -1;
+ this.selectedButtonIndex = -1;
+ this._currentEngineIndex = -1;
+ this._table.hidden = true;
+ },
+
+ _indexOfTableItem: function (elt) {
+ if (elt.classList.contains("contentSearchOneOffItem")) {
+ return this.numSuggestions + this._oneOffButtons.indexOf(elt);
+ }
+ if (elt.classList.contains("contentSearchSettingsButton")) {
+ return this.numSuggestions + this._oneOffButtons.length;
+ }
+ while (elt && elt.localName != "tr") {
+ elt = elt.parentNode;
+ }
+ if (!elt) {
+ throw new Error("Element is not a row");
+ }
+ return elt.rowIndex;
+ },
+
+ _makeTable: function (id) {
+ this._table = document.createElementNS(HTML_NS, "table");
+ this._table.id = id;
+ this._table.hidden = true;
+ this._table.classList.add("contentSearchSuggestionTable");
+ this._table.setAttribute("role", "presentation");
+
+ // When the search input box loses focus, we want to immediately give focus
+ // back to it if the blur was because the user clicked somewhere in the table.
+ // onBlur uses the _mousedown flag to detect this.
+ this._table.addEventListener("mousedown", () => { this._mousedown = true; });
+ document.addEventListener("mouseup", () => { delete this._mousedown; });
+
+ // Deselect the selected element on mouseout if it wasn't a suggestion.
+ this._table.addEventListener("mouseout", this);
+
+ // If a search is loaded in the same tab, ensure the suggestions dropdown
+ // is hidden immediately when the page starts loading and not when it first
+ // appears, in order to provide timely feedback to the user.
+ window.addEventListener("beforeunload", () => { this._hideSuggestions(); });
+
+ let headerRow = document.createElementNS(HTML_NS, "tr");
+ let header = document.createElementNS(HTML_NS, "td");
+ headerRow.setAttribute("class", "contentSearchHeaderRow");
+ header.setAttribute("class", "contentSearchHeader");
+ let iconImg = document.createElementNS(HTML_NS, "img");
+ header.appendChild(iconImg);
+ header.id = "contentSearchDefaultEngineHeader";
+ headerRow.appendChild(header);
+ headerRow.addEventListener("click", this);
+ this._table.appendChild(headerRow);
+
+ let row = document.createElementNS(HTML_NS, "tr");
+ row.setAttribute("class", "contentSearchSuggestionsContainer");
+ let cell = document.createElementNS(HTML_NS, "td");
+ cell.setAttribute("class", "contentSearchSuggestionsContainer");
+ this._suggestionsList = document.createElementNS(HTML_NS, "table");
+ this._suggestionsList.setAttribute("class", "contentSearchSuggestionsList");
+ cell.appendChild(this._suggestionsList);
+ row.appendChild(cell);
+ this._table.appendChild(row);
+ this._suggestionsList.setAttribute("role", "listbox");
+
+ this._oneOffsTable = document.createElementNS(HTML_NS, "table");
+ this._oneOffsTable.setAttribute("class", "contentSearchOneOffsTable");
+ this._oneOffsTable.classList.add("contentSearchSuggestionsContainer");
+ this._oneOffsTable.setAttribute("role", "group");
+ this._table.appendChild(this._oneOffsTable);
+
+ headerRow = document.createElementNS(HTML_NS, "tr");
+ header = document.createElementNS(HTML_NS, "td");
+ headerRow.setAttribute("class", "contentSearchHeaderRow");
+ header.setAttribute("class", "contentSearchHeader");
+ headerRow.appendChild(header);
+ header.id = "contentSearchSearchWithHeader";
+ this._oneOffsTable.appendChild(headerRow);
+
+ let button = document.createElementNS(HTML_NS, "button");
+ button.setAttribute("class", "contentSearchSettingsButton");
+ button.classList.add("contentSearchHeaderRow");
+ button.classList.add("contentSearchHeader");
+ button.id = "contentSearchSettingsButton";
+ button.addEventListener("click", this);
+ button.addEventListener("mousemove", this);
+ this._table.appendChild(button);
+
+ return this._table;
+ },
+
+ _setUpOneOffButtons: function () {
+ // Sometimes we receive a CurrentEngine message from the ContentSearch service
+ // before we've received a State message - i.e. before we have our engines.
+ if (!this._engines) {
+ return;
+ }
+
+ while (this._oneOffsTable.firstChild.nextSibling) {
+ this._oneOffsTable.firstChild.nextSibling.remove();
+ }
+
+ this._oneOffButtons = [];
+
+ let engines = this._engines.filter(aEngine => aEngine.name != this.defaultEngine.name)
+ .filter(aEngine => !aEngine.hidden);
+ if (!engines.length) {
+ this._oneOffsTable.hidden = true;
+ return;
+ }
+
+ const kDefaultButtonWidth = 49; // 48px + 1px border.
+ let rowWidth = this.input.offsetWidth - 2; // 2px border.
+ let enginesPerRow = Math.floor(rowWidth / kDefaultButtonWidth);
+ let buttonWidth = Math.floor(rowWidth / enginesPerRow);
+
+ let row = document.createElementNS(HTML_NS, "tr");
+ let cell = document.createElementNS(HTML_NS, "td");
+ row.setAttribute("class", "contentSearchSuggestionsContainer");
+ cell.setAttribute("class", "contentSearchSuggestionsContainer");
+
+ for (let i = 0; i < engines.length; ++i) {
+ let engine = engines[i];
+ if (i > 0 && i % enginesPerRow == 0) {
+ row.appendChild(cell);
+ this._oneOffsTable.appendChild(row);
+ row = document.createElementNS(HTML_NS, "tr");
+ cell = document.createElementNS(HTML_NS, "td");
+ row.setAttribute("class", "contentSearchSuggestionsContainer");
+ cell.setAttribute("class", "contentSearchSuggestionsContainer");
+ }
+ let button = document.createElementNS(HTML_NS, "button");
+ button.setAttribute("class", "contentSearchOneOffItem");
+ let img = document.createElementNS(HTML_NS, "img");
+ let uri;
+ if (engine.iconBuffer) {
+ uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
+ }
+ else {
+ uri = this._getImageURIForCurrentResolution(
+ "chrome://browser/skin/search-engine-placeholder.png");
+ }
+ img.setAttribute("src", uri);
+ img.addEventListener("load", function imgLoad() {
+ img.removeEventListener("load", imgLoad);
+ URL.revokeObjectURL(uri);
+ });
+ button.appendChild(img);
+ button.style.width = buttonWidth + "px";
+ button.setAttribute("title", engine.name);
+
+ button.engineName = engine.name;
+ button.addEventListener("click", this);
+ button.addEventListener("mousemove", this);
+
+ if (engines.length - i <= enginesPerRow - (i % enginesPerRow)) {
+ button.classList.add("last-row");
+ }
+
+ if ((i + 1) % enginesPerRow == 0) {
+ button.classList.add("end-of-row");
+ }
+
+ button.id = ONE_OFF_ID_PREFIX + i;
+ cell.appendChild(button);
+ this._oneOffButtons.push(button);
+ }
+ row.appendChild(cell);
+ this._oneOffsTable.appendChild(row);
+ this._oneOffsTable.hidden = false;
+ },
+
+ _sendMsg: function (type, data=null) {
+ dispatchEvent(new CustomEvent("ContentSearchClient", {
+ detail: {
+ type: type,
+ data: data,
+ },
+ }));
+ },
+};
+
+return ContentSearchUIController;
+})();
diff --git a/browser/base/content/defaultthemes/1.footer.jpg b/browser/base/content/defaultthemes/1.footer.jpg
new file mode 100644
index 000000000..cb5ff2705
--- /dev/null
+++ b/browser/base/content/defaultthemes/1.footer.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/1.header.jpg b/browser/base/content/defaultthemes/1.header.jpg
new file mode 100644
index 000000000..58c52f86a
--- /dev/null
+++ b/browser/base/content/defaultthemes/1.header.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/1.icon.jpg b/browser/base/content/defaultthemes/1.icon.jpg
new file mode 100644
index 000000000..67b316d9f
--- /dev/null
+++ b/browser/base/content/defaultthemes/1.icon.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/1.preview.jpg b/browser/base/content/defaultthemes/1.preview.jpg
new file mode 100644
index 000000000..1394c5936
--- /dev/null
+++ b/browser/base/content/defaultthemes/1.preview.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/2.footer.jpg b/browser/base/content/defaultthemes/2.footer.jpg
new file mode 100644
index 000000000..a8cce0ef8
--- /dev/null
+++ b/browser/base/content/defaultthemes/2.footer.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/2.header.jpg b/browser/base/content/defaultthemes/2.header.jpg
new file mode 100644
index 000000000..8a4aec353
--- /dev/null
+++ b/browser/base/content/defaultthemes/2.header.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/2.icon.jpg b/browser/base/content/defaultthemes/2.icon.jpg
new file mode 100644
index 000000000..4eeed30ca
--- /dev/null
+++ b/browser/base/content/defaultthemes/2.icon.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/2.preview.jpg b/browser/base/content/defaultthemes/2.preview.jpg
new file mode 100644
index 000000000..cc45cfc94
--- /dev/null
+++ b/browser/base/content/defaultthemes/2.preview.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/3.footer.png b/browser/base/content/defaultthemes/3.footer.png
new file mode 100644
index 000000000..235a5ad54
--- /dev/null
+++ b/browser/base/content/defaultthemes/3.footer.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/3.header.png b/browser/base/content/defaultthemes/3.header.png
new file mode 100644
index 000000000..b25d673c4
--- /dev/null
+++ b/browser/base/content/defaultthemes/3.header.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/3.icon.png b/browser/base/content/defaultthemes/3.icon.png
new file mode 100644
index 000000000..186519d3e
--- /dev/null
+++ b/browser/base/content/defaultthemes/3.icon.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/3.preview.png b/browser/base/content/defaultthemes/3.preview.png
new file mode 100644
index 000000000..46850f139
--- /dev/null
+++ b/browser/base/content/defaultthemes/3.preview.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/4.footer.png b/browser/base/content/defaultthemes/4.footer.png
new file mode 100644
index 000000000..bd944d58b
--- /dev/null
+++ b/browser/base/content/defaultthemes/4.footer.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/4.header.png b/browser/base/content/defaultthemes/4.header.png
new file mode 100644
index 000000000..1487ff10e
--- /dev/null
+++ b/browser/base/content/defaultthemes/4.header.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/4.icon.png b/browser/base/content/defaultthemes/4.icon.png
new file mode 100644
index 000000000..8dd688ef1
--- /dev/null
+++ b/browser/base/content/defaultthemes/4.icon.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/4.preview.png b/browser/base/content/defaultthemes/4.preview.png
new file mode 100644
index 000000000..36ac2a0bf
--- /dev/null
+++ b/browser/base/content/defaultthemes/4.preview.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/5.footer.png b/browser/base/content/defaultthemes/5.footer.png
new file mode 100644
index 000000000..8e87c69a0
--- /dev/null
+++ b/browser/base/content/defaultthemes/5.footer.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/5.header.png b/browser/base/content/defaultthemes/5.header.png
new file mode 100644
index 000000000..8e87c69a0
--- /dev/null
+++ b/browser/base/content/defaultthemes/5.header.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/5.icon.jpg b/browser/base/content/defaultthemes/5.icon.jpg
new file mode 100644
index 000000000..b3e103ee5
--- /dev/null
+++ b/browser/base/content/defaultthemes/5.icon.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/5.preview.jpg b/browser/base/content/defaultthemes/5.preview.jpg
new file mode 100644
index 000000000..78c2f1248
--- /dev/null
+++ b/browser/base/content/defaultthemes/5.preview.jpg
Binary files differ
diff --git a/browser/base/content/defaultthemes/devedition.header.png b/browser/base/content/defaultthemes/devedition.header.png
new file mode 100644
index 000000000..e4e8dcaa3
--- /dev/null
+++ b/browser/base/content/defaultthemes/devedition.header.png
Binary files differ
diff --git a/browser/base/content/defaultthemes/devedition.icon.png b/browser/base/content/defaultthemes/devedition.icon.png
new file mode 100644
index 000000000..04cfba796
--- /dev/null
+++ b/browser/base/content/defaultthemes/devedition.icon.png
Binary files differ
diff --git a/browser/base/content/docs/sslerrorreport/dataformat.rst b/browser/base/content/docs/sslerrorreport/dataformat.rst
new file mode 100644
index 000000000..f69dc7417
--- /dev/null
+++ b/browser/base/content/docs/sslerrorreport/dataformat.rst
@@ -0,0 +1,54 @@
+.. _sslerrorreport_dataformat:
+
+==============
+Payload Format
+==============
+
+An example report::
+
+ {
+ "hostname":"example.com",
+ "port":443,
+ "timestamp":1413490449,
+ "errorCode":-16384,
+ "failedCertChain":[
+ ],
+ "userAgent":"Mozilla/5.0 (X11; Linux x86_64; rv:36.0) Gecko/20100101 Firefox/36.0",
+ "version":1,
+ "build":"20141022164419",
+ "product":"Firefox",
+ "channel":"default"
+ }
+
+Where the data represents the following:
+
+"hostname"
+ The name of the host the connection was being made to.
+
+"port"
+ The TCP port the connection was being made to.
+
+"timestamp"
+ The (local) time at which the report was generated. Seconds since 1 Jan 1970,
+ UTC.
+
+"errorCode"
+ The error code. This is the error code from certificate verification. Here's a small list of the most commonly-encountered errors:
+ https://wiki.mozilla.org/SecurityEngineering/x509Certs#Error_Codes_in_Firefox
+ In theory many of the errors from sslerr.h, secerr.h, and pkixnss.h could be encountered. We're starting with just MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE, which means that key pinning failed (i.e. there wasn't an intersection between the keys in any computed trusted certificate chain and the expected list of keys for the domain the user is attempting to connect to).
+
+"failedCertChain"
+ The certificate chain which caused the pinning violation (array of base64
+ encoded PEM)
+
+"user agent"
+ The user agent string of the browser sending the report
+
+"build"
+ The build ID
+
+"product"
+ The product name
+
+"channel"
+ The user's release channel
diff --git a/browser/base/content/docs/sslerrorreport/index.rst b/browser/base/content/docs/sslerrorreport/index.rst
new file mode 100644
index 000000000..2c4210113
--- /dev/null
+++ b/browser/base/content/docs/sslerrorreport/index.rst
@@ -0,0 +1,15 @@
+.. _sslerrorreport:
+
+===================
+SSL Error Reporting
+===================
+
+With the introduction of HPKP, it becomes useful to be able to capture data
+on pin violations. SSL Error Reporting is an opt-in mechanism to allow users
+to send data on such violations to mozilla.
+
+.. toctree::
+ :maxdepth: 1
+
+ dataformat
+ preferences
diff --git a/browser/base/content/docs/sslerrorreport/preferences.rst b/browser/base/content/docs/sslerrorreport/preferences.rst
new file mode 100644
index 000000000..ed6f384c2
--- /dev/null
+++ b/browser/base/content/docs/sslerrorreport/preferences.rst
@@ -0,0 +1,23 @@
+.. _healthreport_preferences:
+
+===========
+Preferences
+===========
+
+The following preferences are used by SSL Error reporting:
+
+"security.ssl.errorReporting.enabled"
+ Should the SSL Error Reporting UI be shown on pin violations? Default
+ value: ``true``
+
+"security.ssl.errorReporting.url"
+ Where should SSL error reports be sent? Default value:
+ ``https://incoming.telemetry.mozilla.org/submit/sslreports/``
+
+"security.ssl.errorReporting.automatic"
+ Should error reports be sent without user interaction. Default value:
+ ``false``. Note: this pref is overridden by the value of
+ ``security.ssl.errorReporting.enabled``
+ This is only set when specifically requested by the user. The user can set
+ this value (or unset it) by checking the "Automatically report errors in the
+ future" checkbox when about:neterror is displayed for SSL Errors.
diff --git a/browser/base/content/downloadManagerOverlay.xul b/browser/base/content/downloadManagerOverlay.xul
new file mode 100644
index 000000000..9987820cb
--- /dev/null
+++ b/browser/base/content/downloadManagerOverlay.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="downloadManagerOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="downloadManager">
+
+#include browserMountPoints.inc
+
+<script type="application/javascript"><![CDATA[
+ window.addEventListener("load", function(event) {
+ // Bug 405696: Map Edit -> Find command to the download manager's command
+ var findMenuItem = document.getElementById("menu_find");
+ findMenuItem.setAttribute("command", "cmd_findDownload");
+ findMenuItem.setAttribute("key", "key_findDownload");
+
+ // Bug 429614: Map Edit -> Select All command to download manager's command
+ let selectAllMenuItem = document.getElementById("menu_selectAll");
+ selectAllMenuItem.setAttribute("command", "cmd_selectAllDownloads");
+ selectAllMenuItem.setAttribute("key", "key_selectAllDownloads");
+ }, false);
+]]></script>
+
+</window>
+
+</overlay>
diff --git a/browser/base/content/gcli_sec_bad.svg b/browser/base/content/gcli_sec_bad.svg
new file mode 100644
index 000000000..4f440eb6b
--- /dev/null
+++ b/browser/base/content/gcli_sec_bad.svg
@@ -0,0 +1,7 @@
+<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="15" cy="15" r="15" fill="#e74c3c"/>
+ <g stroke="white" stroke-width="3">
+ <line x1="9" y1="9" x2="21" y2="21"/>
+ <line x1="21" y1="9" x2="9" y2="21"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/browser/base/content/gcli_sec_good.svg b/browser/base/content/gcli_sec_good.svg
new file mode 100644
index 000000000..f1b33d073
--- /dev/null
+++ b/browser/base/content/gcli_sec_good.svg
@@ -0,0 +1,4 @@
+<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
+ <circle cx="30" cy="30" r="30" fill="#2CBB0F"/>
+ <polygon points="17,32 25,39 26,39 44,18 45,18 48,21 27,46 25,46 14,36 13,36 16,33 16,32" fill="white"/>
+</svg> \ No newline at end of file
diff --git a/browser/base/content/gcli_sec_moderate.svg b/browser/base/content/gcli_sec_moderate.svg
new file mode 100644
index 000000000..3a88aa468
--- /dev/null
+++ b/browser/base/content/gcli_sec_moderate.svg
@@ -0,0 +1,4 @@
+<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="15" cy="15" r="15" fill="#F5B400"/>
+ <rect x="7.5" y="13" width="15" height="4" fill="white"/>
+</svg> \ No newline at end of file
diff --git a/browser/base/content/global-scripts.inc b/browser/base/content/global-scripts.inc
new file mode 100755
index 000000000..dac75878d
--- /dev/null
+++ b/browser/base/content/global-scripts.inc
@@ -0,0 +1,38 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<script type="application/javascript" src="chrome://global/content/printUtils.js"/>
+<script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
+<script type="application/javascript" src="chrome://browser/content/places/browserPlacesViews.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser.js"/>
+<script type="application/javascript" src="chrome://browser/content/customizableui/panelUI.js"/>
+<script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
+
+<script type="application/javascript" src="chrome://browser/content/browser-addons.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-captivePortal.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-ctrlTab.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-customization.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-devedition.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-feeds.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-fullScreenAndPointerLock.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-fullZoom.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-gestureSupport.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-media.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-plugins.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-refreshblocker.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-sidebar.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-syncui.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-tabsintitlebar.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-thumbnails.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-trackingprotection.js"/>
+
+#ifdef MOZ_DATA_REPORTING
+<script type="application/javascript" src="chrome://browser/content/browser-data-submission-info-bar.js"/>
+#endif
+
+<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
diff --git a/browser/base/content/hiddenWindow.xul b/browser/base/content/hiddenWindow.xul
new file mode 100644
index 000000000..c708071cd
--- /dev/null
+++ b/browser/base/content/hiddenWindow.xul
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+<?xml-stylesheet href="chrome://browser/skin/webRTC-indicator.css" type="text/css"?>
+
+<window id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+#include browserMountPoints.inc
+
+</window>
+
+#endif
diff --git a/browser/base/content/macBrowserOverlay.xul b/browser/base/content/macBrowserOverlay.xul
new file mode 100644
index 000000000..4b2cb0d89
--- /dev/null
+++ b/browser/base/content/macBrowserOverlay.xul
@@ -0,0 +1,66 @@
+<?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/.
+
+#define MAC_NON_BROWSER_WINDOW
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+# All DTD information is stored in a separate file so that it can be shared by
+# hiddenWindow.xul.
+#include browser-doctype.inc
+
+<overlay id="hidden-overlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+# All JS files which are not content (only) dependent that browser.xul
+# wishes to include *must* go into the global-scripts.inc file
+# so that they can be shared by this overlay.
+#include global-scripts.inc
+
+<script type="application/javascript">
+ function OpenBrowserWindowFromDockMenu(options) {
+ let win = OpenBrowserWindow(options);
+ win.addEventListener("load", function listener() {
+ win.removeEventListener("load", listener);
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.activateApplication(true);
+ });
+
+ return win;
+ }
+
+ addEventListener("load", function() { gBrowserInit.nonBrowserWindowStartup() }, false);
+ addEventListener("unload", function() { gBrowserInit.nonBrowserWindowShutdown() }, false);
+</script>
+
+# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
+# browser-sets.inc file for sharing with hiddenWindow.xul.
+#include browser-sets.inc
+
+# The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
+# hiddenWindow.xul.
+#include browser-menubar.inc
+
+<!-- Dock menu -->
+<popupset>
+ <menupopup id="menu_mac_dockmenu">
+ <!-- The command cannot be cmd_newNavigator because we need to activate
+ the application. -->
+ <menuitem label="&newNavigatorCmd.label;" oncommand="OpenBrowserWindowFromDockMenu();"
+ id="macDockMenuNewWindow" />
+ <menuitem label="&newPrivateWindow.label;" oncommand="OpenBrowserWindowFromDockMenu({private: true});" />
+ </menupopup>
+</popupset>
+
+</overlay>
diff --git a/browser/base/content/newtab/alternativeDefaultSites.json b/browser/base/content/newtab/alternativeDefaultSites.json
new file mode 100644
index 000000000..018d3edcc
--- /dev/null
+++ b/browser/base/content/newtab/alternativeDefaultSites.json
@@ -0,0 +1,50 @@
+{
+ "directory": [
+ {
+ "bgColor": "#ffffff",
+ "directoryId": 10000000,
+ "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAIX0lEQVR4nO1afXDbZh1+sxbWsbGvsrLbuBt36766sR3sOEgiuU4kx2vTpFGKm0SKsyyOlI8laUZTulLojQEHx4CyAaNnt72yrivklo1YSpxcW7YVbKXHgCvduCvroDt6o6OtLSWN1MRWX/5I4imyJMu2bPNHnrvfSZbf6P09z+/jfV+3ACxhCUsoAEouNWGlMRLri5GuZwUSe1Ug8bcFEjsjUPgHAoX/UyCxkyKJjQoU/iOhCd8WJSvW/8vpXFFsx7PGeaJsVaytul+g8KMiic+IFA7NTNB/rggk/jeRcu26RGFris3JEqIUtkFsdo2KFK6kI52pCSR2UmirZgY9YFmxeaYg1uyqFSj8lMXI5mofRb3rOwEAJcXmDS72ex8QvO43skjv3MeT2DtRX2150cgLzVUDIonF8xBh64KQ2FWRwncPejyFK4tYq/Nmoa1mPE/pnZ1ILe4TFxpdd+SdvOhxrhabXaeLTVjfsHfzSj5K4V8QKfyjnGs3P2UiCt/v/1LeyAsNlXebkbeRSOZ/Q+JCrBH/Yt7IX+qlbhS96dM+Y+dJLC6S+BWRwmazFizf5AEAQPRtHLEhukqMwo8KTfg2oamy8t+e0uvUczwDwDWxhopHBBLvElrc+wUSm/r/IN9a3ZtrbYqUe9eHTc7PZDLvoAcsi/lq2wQSO1M88k93r7ayjzdI76sxEv/5hdryT+foRknM696q9qMg5AEAQHx83WhWtU5i56PtG1E7fYkO+B4SKNd7AoWLBSEv9G3Asol8zOs+ddFTeWc+fDrXgq2MNmEP5+PdKZB/XR4RvRnWe8tjJ8+1ECsL4mA+EQ+vcCg8gHHuPjjZvc5S6guU6/3/POG5rdi+24IEv+KwEgFQ4QFU/rAKTu90pal5fCY60PlQsf22BefHwfUKD2YVfl6ACIAKfx2UX0QNBYj6arqL7bdtSIQBnYy+2iIAzg49Cid9bm3qv11sn21FggeHUwSILGQCgIljd8Kpreq+4Koqts+2QuGBmBL9FLsFSj+sgAKFnyq2v7ZCDoO7dKOvvc7fS3vcW4rts61I8OBxXdI6/UCJADgVvmGVHfMSAelgfUA6VBeQDqmv2nuzZ3rf6Y2r3SM7DB1RwmCnOsLaiGuenbWD/JwAcqI+IEPCL8H6gAwX7omADIn5z/Wqe0Iz1sgI3edSv6EjcR78OEk4jQiJCNhnlwD1ATlBmDqdCUHzcYRf3mXoSCIMXjdK95SeEAbfslUAk4iaEc1EsLnx0vNmAhw3q3mNAFvtFCBd2i8i4bdWAgYCDBkLwIMJw8antTDos12ARSTNM8JK5Amd8URAPmosQAS8Zdr41N/lIQOS0Z2/pkZe0iWoS35+rIY8rPfLvzcWgAdDljMgAr5tlwCEWgArjUxnnJXSmX8eMnQkPrH8OasCJMJgfyEE0KZxMrJ+i2WgyZq6gHTQ0BGFv+abujs//ab4gV0CqEvAar1n0gQXizj9PUNHEmHgTSFt1At4O3eCH+8D0tW2YUmoMkKvfyQzwC/vMHREegN8Tpe4zjIY55fD14+X9tgiwC8mV2ZrHS/LGOGXRd3y8aeKVee/3GrqjBIGMcMsmL/GJu6AW4+1QZRl/mKHADmipD4gz1jJHCIgw9oXJ+8zfVsi/ImXU7bDquvfI2ugZ7wHoiwDUY6BTrbTWRie+ti0d7rGrHTU5UD45StpX5iILPMZrQTD4Q0QG+2GKMckzcF28AXgaYiv7ZNesdokCb90PO0LP2TBpxQezKpFkPmb4Q/ebFlEHOWYZBY8FtriKwDXFDAHhLvr/bJi9TzQtPeycQNUI3HihkML5M9NfB4+caRTl/jHRsvtb333gTzzTUHDPonNZFkkfjVl7Zfr+MRtiMID+McICqtDvfrE2cWfHRxz2j321K155pxE84HLLWb7BK0odX75TEYTBI43hB1ch0HE554h7GJhKrkn/4wPMjfliXMSO4alcsIvx/UanZE17pcGMppkU2iL2zDt9QSZtwqu66/Okdbb88Qd7PydVFYfkGVthLWHHk1JyOteuHRjxpNh3JNjRtE3EgDlGIiwzLlqrq/MbvKtLwkewi/PalPc8PyQPDdIP8lqQvdw92qEZWbNyBqJgnD0VTTI7H74Je/1uRL/8mDr7VWjfYeJkecgdfC/6c8JavPL0+6fitn3JtdIzxbdqLPqiNMmwtAxR7BzZ+mgL2MnHGzHPQhL/wzlmCsLc64PbYPU4Xd1zwN6WdCwT/p61uQXsH6sL5TS/dOUgY4QCZRlxsuD9EDZsM/x4KDnk5ppSsqGfHehw3QjytHfQVj6HaP+U8F1wZbhMf1Dkerzpr3Sn4Ad/5/Yc2T7TQ6OOW2YBWZimAsVRzlaRoL0DMLRV/WW19SVh05+v5nbAxsPCPpZ4Jclwj95b87kF+BgO+5Bg8yFFHKZZkO68XqimoyvGd0FqUNnU/pA1yvTdbaRXwDCtj2CsszFjFLfSjZoRbEiqEqoqtE+6H2VT5LfvPfydtvJJ0V4jb537QhzxjLRdMSsimIhq5q538DNgSnbfqs0FoHrusUd6j2ScdTNyGZTNovfHa8Z6/fmnbwaTq5r+1wj0ziZrjlmvIKYP3ew9Hvk+NOPFpT8AjxHd6ypGOl607Z+YDUL5s4iChpkdq8b7b22KOTVQIPtGxet3bmQtVA6eKhnyBP6hvnPW8UAwvpq1nIdoeS6bpWY0fK3aMfJTCMc/csqtuf+YvNMi68Mt38WDTJPoSx9DGHpmaxLI8hcqBzpCiDB9ibnM87lxeaVLUrKg+1fRVimby3LPItw9G9Rlj6Bssw/EI4+i3DM+0iQPomy9JiD69hdztI7SoPtTWWvddry7w5LWIIx/gdCuvcjoZqlLQAAAABJRU5ErkJggg==",
+ "type": "affiliate",
+ "title": "Google",
+ "url": "https://www.google.com/"
+ },
+ {
+ "bgColor": "#E62117",
+ "directoryId": 10000001,
+ "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAACd0lEQVR4nO3av0tVYRzH8WOIBEVDUUIGCa32FwQ5OrS42NKeRUOTdGmoqUGiLVBqa5UG+7WUREGiEqhTDkVJEA2iYZBkxKvhHumo96o3uM9z0ucNZ7zn834+XO59znm+WZZIJBKJRCKRSGwCx3AWfbiIaxjEPYzgOd7gLWbwIb8WsFjjWrU9q3U+u1C4/0yeOZ47jOROg6jkrn25e3uji27NbzC9A9n/hWnVNbVut/hOTMZ1bSqT6Ky3+A58jOsXhE/o2Lj4FryILBaSF2gpFtAT2ygCPcUCHse2icDTtcW3YSW2TQRW0JahK7ZJRLoy9Ma2iEhvhoHYFhEZyDAU2yIiwxkeBgj6FSDjXxjN8CpA0A1cxucAWY0wkeFdgKBK/pe7H1fxNUDmTpjLMB8gqLJh631A9dF6IUD2Vsxn+BYgaF0BhSIO4WYgh1osZVgOEFSzgEIRh3EL3wO4FFnOAgVtWUChiKO4gx+BvJSqgEIRx3EXP5stVsoCCkWcxH1N3EeUuoBCEafwshlipS8AJzTxW1DaAnAEtzX5B7F0BeAgrmMphFhpClB9M3UFXwI5oQQFYB8u4H0gl3VE3QninOoxVyyWozwL4AxeB8jdjqWgT4M4jUcB8nbKfKj3AUN4gN8BshphLtQbobIyEeqdYFkZzTAc2yIiw+lcQDoZSmeDe/t0ON+gPIltE4Fnxa3pnp8QacFYbKOAjCnOCOUldKhOUO12Nk+JFUroxFRkwWYypd6cYKGEVvTbfZOil2w3KVqjjHZ047xqKRWbZ4XHVed2Z/2d5a0167vYgHC9z6/df9bWs8L9uXO3RmeFE4lEIpFIJPYKfwAcall+TY3q/wAAAABJRU5ErkJggg==",
+ "type": "affiliate",
+ "title": "YouTube",
+ "url": "https://www.youtube.com/"
+ },
+ {
+ "directoryId": 10000002,
+ "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAACT0lEQVR4nO3bzWsTQRgG8PlPHMVLqRVMoQiFehJyUBAFtRCFHspExaaSoFg8CMGPSg+R3kQhh3osIbOlMVrbGKtrsbYESTbB0gqNVGu1WCkWutnXg+ihQjqxi7Oz8w481533+V32iyGEEEIZ76Rho0SZ4ewOG+D3UMatXSx9nPwqb5ySPZC0dPMzhDJelD6IrDCjQijjtvRBJIWGeY3IHkJ2EED2ALKDALIHkB2lAJoujkIwnoNQwoQL96aha3AKQgkTTtyZhKM383D4+gQEoll/AQRiWbg1XILZ+VWwaw6IrE3bgZbejNoAe8+PwEC6DOsbm0Klt679lxQGCESzMD335Z+KKw+wrzcDpcVvOyqvNMCwubjj8soCnBx44Up5ZQGeFZf1BWi7/BhqjthtzpcAseRsQwVrjgNzS9/hZWUFJq3Pf6U5MqoWQHJ8Qbh8aqoKB688cWVfzwBMvP0kVN6srMCec+7t6xmAmfmvQgCRBzOu7usZgHJ1TQjgyI283gCHrj3VG6C9bwwBEAABEEAtgAPRR9A3VKibpdUfQgD9KWvba529+8pbAMF4TqicW+th/r3eAP0pS2+Anvtv9AY4dvu53gCtMfGfI74DWN+wG5rNdwDl6preAGOFj3oDJMcXvAfQHMlAKGHWTeWD2KPw1aFC3et0NPi9wDPvAq/fif0LDMZzru6LALKLIwACIAACIAACIAACIAACIAACIAACIAACIMD/BvDKwUkZAJRx2zNHZyUBFAntTp+WXV4iQCf5c3yecUsXAMq49bv8Txsl6ZCTabOAAAAAAElFTkSuQmCC",
+ "title": "Facebook",
+ "type": "affiliate",
+ "url": "https://www.facebook.com/"
+ },
+ {
+ "bgColor": "#ffffff",
+ "directoryId": 10000003,
+ "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA/CAYAAABQHc7KAAAEyUlEQVRoge2abYhVRRjHf7veVqSFhIQwa7E3ojXNpSiNgl0S+tAHKUjohbQohUgqKKwMSXpbsATdqOgFU4zKpAzMioploxI/WFBZmhRlL/S2lbpsudvd24dzjve5c58558ycey9B84MB95x5/vM/d+bMzDNHCAQCgUAgEAgE/rf0AOd6lJmKVrdDfI8SfxawIKNcEscfa8TOzBFrlj6Aimd5XnmAbx01TD5xiL3IiH3Y8zm4DXgaGALGMip/DDwHDACXKQ9wA/AiMJyi8T3wAnCvEr841t9tiR2JY/uB443YC4BHgA3ApyntTxD90FuAlaaBU4EfLIHvKIZtnAT8ZtE5O6fGCiNuFJiTM7YNWKe0PQScmRV8ncX4+zkbT1hq0bkyZ/wyI+5ux/bPM+I/ACbnCSyhv8tloMvBQAnYr+h8mDP+PRHzGXCMQ9sA14j4ceAMl+A70HtvjaMJ22jqy4i70Kh/sWO7AO+K+Jdcg6cCh6k3/gfQ6aBTAr5UdN7OiHtL1H3NxXhMN9Fkl2jM89BgPXrv3eKoc71Fx2ZqvqhTJv+kKRkQGjs94gE4PTZgGt9DNMvmpQR8pehst9R/Q9TZ5OG7EzgkNK720DjKNvTeu9RRZ4miMUH9bnCeuH8EOMXD881C40egw0PjKL3oP8Cbjjq2FWGrUW+HuDfg6XmP0NA2W060GYKy97odtbQVoQzMiu+fL66PACd4+O0VGqPANA+NOm5CHwVPOeqUgH2Kzub4/uvi2gOeXrcU8GdlCtG7ZBr/C5juqHWtojMOXCX+HgaO8/B5YqyV6GjZpjf3oI+C+x11JgF7FZ1/xL/v9PS4WmgMempYsW2MfsdtYwTRsqT9mBXgANGIc6WD2lG60EMjk0fRTS931GkHPrdo3ejpbZHQ2B+30XC6iNZm0/Q3RBOcC7Yc4WRPb0NC43ZPjVxsRDe+yFHHHLJJWevhabaIP0z0ujaNWdQmGUn5yEPrPkVnBPe1+0kRv97DhzPb0UdBr6OObZv9kIOGnJzLRPlL0+lFN+6yPe5BH0kV4E/yD+PlIm6bQ/uF2Um98Qlgbs74V5V4WVbl0GgDvsB/BBbiCnTj2lG5yVyqvX+QaOSYOsNk7y8WiPo+c1Ah2tFPesbJTmFfEfX7qU2BZVmRoSPnkMU+D1EUW5L0WErMHKq9P0o149NeqZ+w7wq7qO77f02p11Qmo6/laWnoVlFvnbh+uaJTAW616Dwo6rjmIw3lLnTjminZ+38DM8Q92yv1HfUnOh3AL/H9I7hnpA1lKrXnb0nRkqSXxf0nFC3bK7XMqCdT6o2NeIiirEE3Lvfks6kesI6hf1m2nTt8TW2uIeeLhub8vsxAT5IOUB2+8qTm2RStlYqOnOV7xLXBRj5EUWxJ0hKic/2k98dJ367azh32Es0Tz4hrTcn5fZE9I8s+ajc6m20CgrUWrdVEK0yFJub8RRhEN56UMjk+TVO7xtuK6yFMS1hIuuk82+SETSk6h3A/hmsJ7egfPypEh555ej/hHOzZYktyfl9keiqLzzc+LUlqWc7vSyfRJ3Rpegw4zUOrj/ofoKU5vy/91Jp+vIDWLkPL/J9h/0mmAT9TXa58vvIkzKe6ydpQ3FogEAgEAoFAIBCI+Rf5q90lTPZHIwAAAABJRU5ErkJggg==",
+ "title": "Wikipedia",
+ "type": "affiliate",
+ "url": "https://www.wikipedia.org/"
+ },
+ {
+ "bgColor": "#400090",
+ "directoryId": 10000004,
+ "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAD3ElEQVR4nO3bS4xecxjH8WmLBVVEx63JBIkEISkhJqURpAsZQioWqpeNSxraYlKpSwiDIRY0aSakqgS9qHRBIhKxYdFFFxWXIGkilYZEUCKj4zLzsfi/LN55zpiZc/lr+/6Ss3nfc57ze77n9r88/y7swxaswgLM7jpMhTlYiNWtnPd1Ga9RfIVtuB+L0J3b/FSFeejDQ9iBvRhrTzYCUKTv8RFewlrcgAtxQsYk5+Ii3IQHsBm78ONkk5oKgIn0Mz7BO3gBA7gHS3EtLsXZre1UnITj2pI5vvV7D85BL67DCvRjEJvwHj7HcBXGqwJwyKoDILeB3OoAyG0gt8oAGMYj0qdnJ17DY3i2vK1CPYmn8Cq2YwgP4rfpBix7BwwE3+aZ2F0ybqQPMSM439NlgpYFMILzAlO9glZXCf2F+cF5LsafZQJX8Q4oujKbK4j9j4aC+EdhT9nAVb0Ebw8MduNABbF/wMlB/LUVxK4MwE84JTC5uoLYK4O48/BrBbEr/Qy+XnCbfloi5h7MCuJurcSxagGMYWFg9qr/SbxQVTeEiq7Ym9OI9UYQ52ipJ1iZ6mgJ3h0Y7zG17usvOC2I01+12ToAHBCMIGHdFGKsCo4/QwJTqerqC2wMEjgGX0zi2KLH6JU6jNYFYBSXBElc/R/HjaE3OO4y1bYs/1WdvcFd4hbilgmOie6cGa1Ytaju7vDSIKHTxc9yUYtvWZ0G6wawH8cGSa0J9r0t2G92K0ZtamJA5PEgsfaOTNHjMlC3uSYADKMnSG6B9GIr6uqehYN1m2tqSGxre4KtJDdhfcF/O5ow1hSAMVweJNmNOcHvlbb3J1KTg6K7MTO62m3Jz1LBQMdk1fSo8IpJALizSUNNA/jWBNPvOFGahG1MOeYFnpgAwHNNm8kB4CDODJI/F380bSbXzNAzAYD1OYzkAjAYABjKYaQDIMdJxQDqnFMsVC4A6wIAgzmMdADkOKkYwKM5jOQCcFcAYCqjxpUpF4Bovu+IB3BfDiO5AIzrFWJlDiO5ACzpABgP4I4cRnIBWBwAWJLDSC4AfR0A4wHcksNILgCLAgB9OYzkAnDFkQ5gDZbj3ta2XCp5bVydYuncBnKrAyC3gdzqAMhtILfqBDCCb6SZ3velatGXsUFa5NC+vdjatrf2/QAfSyUyv9dlsgyAEXyGt/G8VBl+PS7A3PaGTllJCy7n40Zp8GQD3sWXSgCaDIBRqcBxGx7GYmll57hixlySaojPx83SuqW3pLXC0wKwX7oF+3GloILjUJE03X6NtAh8J76LAOzFRtwqKGY63CStX14mLen5+m9Vgqdbrd+9cgAAAABJRU5ErkJggg==",
+ "title": "Yahoo!",
+ "type": "affiliate",
+ "url": "https://www.yahoo.com/"
+ },
+ {
+ "directoryId": 10000005,
+ "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAJdklEQVR4nO1ba1QTZwL9iCY8hCDxDUpYOIUDBOVZAlJ5NQXRFlEQ8YGiRaqESlhxeRQ3FAWURRC6FYglqBHTEaI8Eg+CRMRatfKwLNL6oqer1h57XEWYbwJK9sdKixiSmRCCbrnn3D8w351770wy3zd8ADCJSUxiPMHhcPS3bdv2/tq1a9MWL16MODs7t5iamt4zMjLq0dfX76VSqU8sLCzueHt7S+3t7QtjYmLWJyYmzp1o32NGSUmJd0BAQLmRkVEPAEBOhCQS6bm3t7c0MjIyHACgMyEB1AGCILTY2NgUOzu7fwGCoUfj/Pnz/x0XF/fXgoICXW3nIYTs7Owlpqam94CGgo+ktbX1j4mJiY7azIQbMTExu3V0dF6AcQo/RF1dXYggSIj2kuFAfHx8Lhjn4ODVElCxWOygpXjKkZWVxQZaDD9EOp3eXV9fb6yVkKOho6Njgb6+fp8ig9pgfHx8gjZyjgYdX1/fhtHMqSKJRHpOJpNl6o4HAMidnJxatJJUEWpraz3xmBxOExOT30JDQ/+elpZmD14+26uqqubExcVFzJs3T53H5uCJEydMtZN4BKKiovKJmA0KChLu3bt3zmh6Bw4c0F+3bl0BEU0AgLygoCByXAKqgq2tbSdek0FBQTV4dZlM5kW8ugAAOZPJ/IeGIuHH9evXp+no6DzHa7K+vt4Lr3ZiYuIOvLoAALmnp6dQQ7HwIzc31xqvQRsbmy5AYC6fnJzsjlcbACD38/M7p5lUBBEeHp5lbm7+E4lEUnonREdHpxPRLSwsNFWmN5Kurq5XNZNITfj4+EwtLCw0dXFxcebz+UFhYWFbmExm6qZNm/IjIiKyampqDIjonTp1ajp4mwrQNP70BfD5/D9PAcXFxQZcLteOwWCw9u/f/3FwcHC6paVlOfg/K4CUlpbmEB0dvSEkJCTLxsamwtnZuXXmzJmPAACDgEBYRXxjC8jJyfFYtmzZYWNj48dgjCGV8Y0qICsryyQiIiKVTqf/BMYxNHgTC8jPz/+ISqU+AVoKDt6kAnbs2MGeMmXKANByePAmFBAbG5utyJi2OKEFJCcnx+MxqYizZs361d/fvyw9PX0bg8FgZWRk2FZVVc1pampaQERnwgoQi8VLVK0DRpJMJstWrlx5hMPhvMflckmKdN+amSDRdbuenl5fRkaGnyrdt6KAsrKyD4iYpFKpTy9cuPAuHu23ogAmk9lIxGRCQsLneLVLS0tnEdHWegFisXgu0b8ANTc3W+LVT0pKsiSirfUCDh06tJyIQSsrq5tE9PPy8jyI6Gu9gMDAQA5Bg9VE9Hfu3BlFRJ/BYHSMPRUBsNnsfUQMksnkciL6q1ev5hHRNzQ07AHa3D9A9N19QEAA7lfiAAAdOp3eTUQfACBHEMR87MlwgmgBZmZm9wHOK1RUVLSFiPYQd+/eHaOheKrB4XCyiBrk8XguqnTb29vNDAwMeolqAwDkCxcuvKbBiMoREhLyKVGDy5cvr1Cm2d3dPd3Ly+sCUd3h3LNnT7xGg46GoqKiQHUMbt68uZjL5VJG6qWkpPjS6fS76miO4Ivm5maVd9qYUV5ePlPdtb+FhUV3cHBwkbm5efrGjRvz7O3tr2og+O90dHRslUqlUxX5lkq5U7HmJEtUsnTxwG0kEJ7b4Cer32ArlXIVHq8ULBarVpPGNcng4GDP4V6xloxArNq3oq+M9hTlUeSvsfwvPxMuoKGhwWO8N0RRKBSM6Bg6nf4jgiC0zk6Egl3czsYQhxsKQ48g4QIAAMDf3//0eIWn0WiPjh075jx79uyHeMfY2dl1IAgyFwAAZA1rjqE8ihw9rPscrXRp7xO5lckaN6X0t+dt7m/LiYanPI+iPPLgmApoa2t75+W7fY2GX7RoUcvx48fpAAAgEom88NwJLBbr65ycnGlD3gZuli15VhfC6JZy9Ubzj1W6to6pAAAAEAgEDsbGxv/RVHh3d/ezEomEOvwcqamp6crGsNns/NHeLg0HPPOhV98Pf2ynQRHG+ZcFDKpdAAAAFBcX+6qzF3gkly5dqjCIVCrVs7e3v6RozKpVqw6q8idrybCFonfrUB5FDqsWfzv0c1jh2IHyKHIotL0J+lsyY7CqJXmyO4hamw/Ly8vnhIeH51Op1KdEQtNotN9WrFjxpVAodFOmv337dkMmk1k9NI5CoWCZmZk7EQSZouj4bilfT/ZtUjhW4yNCDxv0ozyKHJaZ9Mq+S1sDAACdnQgF8o1RlEeRy5picsDA3dPv9QnMHqI88iAUudYNdJV4q1NEXFyc7q5duzZ4eXmdtLKyukUikV6ZL1Cp1CeOjo6tHh4eRVwudwWfzx/1MzoSXC53alRUVCaLxapjs9kBio55fC3bGPvm0wRUQL/3yjc9n9Yz0HX090zolTSP//2OPIjdEVkDAAB42nmYJjsbWjo0CEMcfoCNkXvR5lh3BAlT2LQqhIWFTZFIJFSJRELdunUrWR0NVXhc/zdj9MxHa6B4qRAemfHasx6rYUnQjn8uGD5G9k3CHpRHkcvqQk+8Joh1lX2AHl9wf+RkAWtYvx9eSvaWSn2Iz5w0jId1O6fJLn8Wila6iVCe/oCiZ3sff8YT7CLnE6BgJQoRhxtoqcmz3l8vK9629+xW6Sx4dnUmLJv55DXxo3MeQ8ThDKxlfS77Pm8F1pxkqegkmsItSZxu75nAhf1NMZFY47ov4MlFzejLz69ClhrB/vObc3uuZM5QpIf9fM4K5VHk/W25sSpP3tdaaArPrBSgJbovlM6m+LQeWO13HqtwO4i1Zn0CRZ5+2Hf7bB5cK8a9P6hbytfDOgqsMJErC7vyWRw87VUERR5X0VJDiGc2h/IocljL+rr3Klfpv9pg9xttsLOrVIcfDtltEQOtcP0CFZg/wGvmjysyvQ8VmP8ChTa3McThBiZya8NEbm3YSUYXFNrcRgXmv8Ay2jOUp0tMd9gdidWHFsiuH2QQCqUOuFwuSXY5ZT1a6dyullkNEiKMLky6NaYTeX2JrRXILqcslIkDk2GFYyP6lSE27oGPzHiKVTiJZdV+qf2dh50mJPRo6Jby9bBLycvRSpcCeNLpe7SEovw7Aw+/MpDBUx5XsNqAbHgp2buzc4KutDp4dHGXEVq3xh02fbwFq2Htg5WuQljlcwFWLOqAJ97p7hOYPewTmD2EQuu76Emn67DauxmtdEawap8ceH5TNFofznxQs5XQBstJTGISk1AX/wUPVWOeSpK50AAAAABJRU5ErkJggg==",
+ "title": "Amazon",
+ "type": "affiliate",
+ "url": "https://www.amazon.com/"
+ }
+ ]
+}
diff --git a/browser/base/content/newtab/cells.js b/browser/base/content/newtab/cells.js
new file mode 100644
index 000000000..47d4ef52d
--- /dev/null
+++ b/browser/base/content/newtab/cells.js
@@ -0,0 +1,126 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This class manages a cell's DOM node (not the actually cell content, a site).
+ * It's mostly read-only, i.e. all manipulation of both position and content
+ * aren't handled here.
+ */
+function Cell(aGrid, aNode) {
+ this._grid = aGrid;
+ this._node = aNode;
+ this._node._newtabCell = this;
+
+ // Register drag-and-drop event handlers.
+ ["dragenter", "dragover", "dragexit", "drop"].forEach(function (aType) {
+ this._node.addEventListener(aType, this, false);
+ }, this);
+}
+
+Cell.prototype = {
+ /**
+ * The grid.
+ */
+ _grid: null,
+
+ /**
+ * The cell's DOM node.
+ */
+ get node() { return this._node; },
+
+ /**
+ * The cell's offset in the grid.
+ */
+ get index() {
+ let index = this._grid.cells.indexOf(this);
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "index", {value: index, enumerable: true});
+
+ return index;
+ },
+
+ /**
+ * The previous cell in the grid.
+ */
+ get previousSibling() {
+ let prev = this.node.previousElementSibling;
+ prev = prev && prev._newtabCell;
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "previousSibling", {value: prev, enumerable: true});
+
+ return prev;
+ },
+
+ /**
+ * The next cell in the grid.
+ */
+ get nextSibling() {
+ let next = this.node.nextElementSibling;
+ next = next && next._newtabCell;
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "nextSibling", {value: next, enumerable: true});
+
+ return next;
+ },
+
+ /**
+ * The site contained in the cell, if any.
+ */
+ get site() {
+ let firstChild = this.node.firstElementChild;
+ return firstChild && firstChild._newtabSite;
+ },
+
+ /**
+ * Checks whether the cell contains a pinned site.
+ * @return Whether the cell contains a pinned site.
+ */
+ containsPinnedSite: function Cell_containsPinnedSite() {
+ let site = this.site;
+ return site && site.isPinned();
+ },
+
+ /**
+ * Checks whether the cell contains a site (is empty).
+ * @return Whether the cell is empty.
+ */
+ isEmpty: function Cell_isEmpty() {
+ return !this.site;
+ },
+
+ /**
+ * Handles all cell events.
+ */
+ handleEvent: function Cell_handleEvent(aEvent) {
+ // We're not responding to external drag/drop events
+ // when our parent window is in private browsing mode.
+ if (inPrivateBrowsingMode() && !gDrag.draggedSite)
+ return;
+
+ if (aEvent.type != "dragexit" && !gDrag.isValid(aEvent))
+ return;
+
+ switch (aEvent.type) {
+ case "dragenter":
+ aEvent.preventDefault();
+ gDrop.enter(this, aEvent);
+ break;
+ case "dragover":
+ aEvent.preventDefault();
+ break;
+ case "dragexit":
+ gDrop.exit(this, aEvent);
+ break;
+ case "drop":
+ aEvent.preventDefault();
+ gDrop.drop(this, aEvent);
+ break;
+ }
+ }
+};
diff --git a/browser/base/content/newtab/customize.js b/browser/base/content/newtab/customize.js
new file mode 100644
index 000000000..28a52373c
--- /dev/null
+++ b/browser/base/content/newtab/customize.js
@@ -0,0 +1,133 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+var gCustomize = {
+ _nodeIDSuffixes: [
+ "blank",
+ "button",
+ "classic",
+ "enhanced",
+ "panel",
+ "overlay",
+ "learn"
+ ],
+
+ _nodes: {},
+
+ init: function() {
+ for (let idSuffix of this._nodeIDSuffixes) {
+ this._nodes[idSuffix] = document.getElementById("newtab-customize-" + idSuffix);
+ }
+
+ this._nodes.button.addEventListener("click", e => this.showPanel(e));
+ this._nodes.blank.addEventListener("click", this);
+ this._nodes.classic.addEventListener("click", this);
+ this._nodes.enhanced.addEventListener("click", this);
+ this._nodes.learn.addEventListener("click", this);
+
+ this.updateSelected();
+ },
+
+ hidePanel: function() {
+ this._nodes.overlay.addEventListener("transitionend", function onTransitionEnd() {
+ gCustomize._nodes.overlay.removeEventListener("transitionend", onTransitionEnd);
+ gCustomize._nodes.overlay.style.display = "none";
+ });
+ this._nodes.overlay.style.opacity = 0;
+ this._nodes.button.removeAttribute("active");
+ this._nodes.panel.removeAttribute("open");
+ document.removeEventListener("click", this);
+ document.removeEventListener("keydown", this);
+ },
+
+ showPanel: function(event) {
+ if (this._nodes.panel.getAttribute("open") == "true") {
+ return;
+ }
+
+ let {panel, button, overlay} = this._nodes;
+ overlay.style.display = "block";
+ panel.setAttribute("open", "true");
+ button.setAttribute("active", "true");
+ setTimeout(() => {
+ // Wait for display update to take place, then animate.
+ overlay.style.opacity = 0.8;
+ }, 0);
+
+ document.addEventListener("click", this);
+ document.addEventListener("keydown", this);
+
+ // Stop the event propogation to prevent panel from immediately closing
+ // via the document click event that we just added.
+ event.stopPropagation();
+ },
+
+ handleEvent: function(event) {
+ switch (event.type) {
+ case "click":
+ this.onClick(event);
+ break;
+ case "keydown":
+ this.onKeyDown(event);
+ break;
+ }
+ },
+
+ onClick: function(event) {
+ if (event.currentTarget == document) {
+ if (!this._nodes.panel.contains(event.target)) {
+ this.hidePanel();
+ }
+ }
+ switch (event.currentTarget.id) {
+ case "newtab-customize-blank":
+ sendAsyncMessage("NewTab:Customize", {enabled: false, enhanced: false});
+ break;
+ case "newtab-customize-classic":
+ if (this._nodes.enhanced.getAttribute("selected")){
+ sendAsyncMessage("NewTab:Customize", {enabled: true, enhanced: true});
+ } else {
+ sendAsyncMessage("NewTab:Customize", {enabled: true, enhanced: false});
+ }
+ break;
+ case "newtab-customize-enhanced":
+ sendAsyncMessage("NewTab:Customize", {enabled: true, enhanced: !gAllPages.enhanced});
+ break;
+ case "newtab-customize-learn":
+ this.showLearn();
+ break;
+ }
+ },
+
+ onKeyDown: function(event) {
+ if (event.keyCode == event.DOM_VK_ESCAPE) {
+ this.hidePanel();
+ }
+ },
+
+ showLearn: function() {
+ window.open(TILES_INTRO_LINK, 'new_window');
+ this.hidePanel();
+ },
+
+ updateSelected: function() {
+ let {enabled, enhanced} = gAllPages;
+ let selected = enabled ? enhanced ? "enhanced" : "classic" : "blank";
+ ["enhanced", "classic", "blank"].forEach(id => {
+ let node = this._nodes[id];
+ if (id == selected) {
+ node.setAttribute("selected", true);
+ }
+ else {
+ node.removeAttribute("selected");
+ }
+ });
+ if (selected == "enhanced") {
+ // If enhanced is selected, so is classic (since enhanced is a subitem of classic)
+ this._nodes.classic.setAttribute("selected", true);
+ }
+ },
+};
diff --git a/browser/base/content/newtab/drag.js b/browser/base/content/newtab/drag.js
new file mode 100644
index 000000000..e3928ebd0
--- /dev/null
+++ b/browser/base/content/newtab/drag.js
@@ -0,0 +1,151 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton implements site dragging functionality.
+ */
+var gDrag = {
+ /**
+ * The site offset to the drag start point.
+ */
+ _offsetX: null,
+ _offsetY: null,
+
+ /**
+ * The site that is dragged.
+ */
+ _draggedSite: null,
+ get draggedSite() { return this._draggedSite; },
+
+ /**
+ * The cell width/height at the point the drag started.
+ */
+ _cellWidth: null,
+ _cellHeight: null,
+ get cellWidth() { return this._cellWidth; },
+ get cellHeight() { return this._cellHeight; },
+
+ /**
+ * Start a new drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragstart' event.
+ */
+ start: function Drag_start(aSite, aEvent) {
+ this._draggedSite = aSite;
+
+ // Mark nodes as being dragged.
+ let selector = ".newtab-site, .newtab-control, .newtab-thumbnail";
+ let parentCell = aSite.node.parentNode;
+ let nodes = parentCell.querySelectorAll(selector);
+ for (let i = 0; i < nodes.length; i++)
+ nodes[i].setAttribute("dragged", "true");
+
+ parentCell.setAttribute("dragged", "true");
+
+ this._setDragData(aSite, aEvent);
+
+ // Store the cursor offset.
+ let node = aSite.node;
+ let rect = node.getBoundingClientRect();
+ this._offsetX = aEvent.clientX - rect.left;
+ this._offsetY = aEvent.clientY - rect.top;
+
+ // Store the cell dimensions.
+ let cellNode = aSite.cell.node;
+ this._cellWidth = cellNode.offsetWidth;
+ this._cellHeight = cellNode.offsetHeight;
+
+ gTransformation.freezeSitePosition(aSite);
+ },
+
+ /**
+ * Handles the 'drag' event.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'drag' event.
+ */
+ drag: function Drag_drag(aSite, aEvent) {
+ // Get the viewport size.
+ let {clientWidth, clientHeight} = document.documentElement;
+
+ // We'll want a padding of 5px.
+ let border = 5;
+
+ // Enforce minimum constraints to keep the drag image inside the window.
+ let left = Math.max(scrollX + aEvent.clientX - this._offsetX, border);
+ let top = Math.max(scrollY + aEvent.clientY - this._offsetY, border);
+
+ // Enforce maximum constraints to keep the drag image inside the window.
+ left = Math.min(left, scrollX + clientWidth - this.cellWidth - border);
+ top = Math.min(top, scrollY + clientHeight - this.cellHeight - border);
+
+ // Update the drag image's position.
+ gTransformation.setSitePosition(aSite, {left: left, top: top});
+ },
+
+ /**
+ * Ends the current drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragend' event.
+ */
+ end: function Drag_end(aSite, aEvent) {
+ let nodes = gGrid.node.querySelectorAll("[dragged]")
+ for (let i = 0; i < nodes.length; i++)
+ nodes[i].removeAttribute("dragged");
+
+ // Slide the dragged site back into its cell (may be the old or the new cell).
+ gTransformation.slideSiteTo(aSite, aSite.cell, {unfreeze: true});
+
+ this._draggedSite = null;
+ },
+
+ /**
+ * Checks whether we're responsible for a given drag event.
+ * @param aEvent The drag event to check.
+ * @return Whether we should handle this drag and drop operation.
+ */
+ isValid: function Drag_isValid(aEvent) {
+ let link = gDragDataHelper.getLinkFromDragEvent(aEvent);
+
+ // Check that the drag data is non-empty.
+ // Can happen when dragging places folders.
+ if (!link || !link.url) {
+ return false;
+ }
+
+ // Check that we're not accepting URLs which would inherit the caller's
+ // principal (such as javascript: or data:).
+ return gLinkChecker.checkLoadURI(link.url);
+ },
+
+ /**
+ * Initializes the drag data for the current drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragstart' event.
+ */
+ _setDragData: function Drag_setDragData(aSite, aEvent) {
+ let {url, title} = aSite;
+
+ let dt = aEvent.dataTransfer;
+ dt.mozCursor = "default";
+ dt.effectAllowed = "move";
+ dt.setData("text/plain", url);
+ dt.setData("text/uri-list", url);
+ dt.setData("text/x-moz-url", url + "\n" + title);
+ dt.setData("text/html", "<a href=\"" + url + "\">" + url + "</a>");
+
+ // Create and use an empty drag element. We don't want to use the default
+ // drag image with its default opacity.
+ let dragElement = document.createElementNS(HTML_NAMESPACE, "div");
+ dragElement.classList.add("newtab-drag");
+ let scrollbox = document.getElementById("newtab-vertical-margin");
+ scrollbox.appendChild(dragElement);
+ dt.setDragImage(dragElement, 0, 0);
+
+ // After the 'dragstart' event has been processed we can remove the
+ // temporary drag element from the DOM.
+ setTimeout(() => scrollbox.removeChild(dragElement), 0);
+ }
+};
diff --git a/browser/base/content/newtab/dragDataHelper.js b/browser/base/content/newtab/dragDataHelper.js
new file mode 100644
index 000000000..675ff2671
--- /dev/null
+++ b/browser/base/content/newtab/dragDataHelper.js
@@ -0,0 +1,22 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+var gDragDataHelper = {
+ get mimeType() {
+ return "text/x-moz-url";
+ },
+
+ getLinkFromDragEvent: function DragDataHelper_getLinkFromDragEvent(aEvent) {
+ let dt = aEvent.dataTransfer;
+ if (!dt || !dt.types.includes(this.mimeType)) {
+ return null;
+ }
+
+ let data = dt.getData(this.mimeType) || "";
+ let [url, title] = data.split(/[\r\n]+/);
+ return {url: url, title: title};
+ }
+};
diff --git a/browser/base/content/newtab/drop.js b/browser/base/content/newtab/drop.js
new file mode 100644
index 000000000..748652455
--- /dev/null
+++ b/browser/base/content/newtab/drop.js
@@ -0,0 +1,150 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+// A little delay that prevents the grid from being too sensitive when dragging
+// sites around.
+const DELAY_REARRANGE_MS = 100;
+
+/**
+ * This singleton implements site dropping functionality.
+ */
+var gDrop = {
+ /**
+ * The last drop target.
+ */
+ _lastDropTarget: null,
+
+ /**
+ * Handles the 'dragenter' event.
+ * @param aCell The drop target cell.
+ */
+ enter: function Drop_enter(aCell) {
+ this._delayedRearrange(aCell);
+ },
+
+ /**
+ * Handles the 'dragexit' event.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ exit: function Drop_exit(aCell, aEvent) {
+ if (aEvent.dataTransfer && !aEvent.dataTransfer.mozUserCancelled) {
+ this._delayedRearrange();
+ } else {
+ // The drag operation has been cancelled.
+ this._cancelDelayedArrange();
+ this._rearrange();
+ }
+ },
+
+ /**
+ * Handles the 'drop' event.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ drop: function Drop_drop(aCell, aEvent) {
+ // The cell that is the drop target could contain a pinned site. We need
+ // to find out where that site has gone and re-pin it there.
+ if (aCell.containsPinnedSite())
+ this._repinSitesAfterDrop(aCell);
+
+ // Pin the dragged or insert the new site.
+ this._pinDraggedSite(aCell, aEvent);
+
+ this._cancelDelayedArrange();
+
+ // Update the grid and move all sites to their new places.
+ gUpdater.updateGrid();
+ },
+
+ /**
+ * Re-pins all pinned sites in their (new) positions.
+ * @param aCell The drop target cell.
+ */
+ _repinSitesAfterDrop: function Drop_repinSitesAfterDrop(aCell) {
+ let sites = gDropPreview.rearrange(aCell);
+
+ // Filter out pinned sites.
+ let pinnedSites = sites.filter(function (aSite) {
+ return aSite && aSite.isPinned();
+ });
+
+ // Re-pin all shifted pinned cells.
+ pinnedSites.forEach(aSite => aSite.pin(sites.indexOf(aSite)));
+ },
+
+ /**
+ * Pins the dragged site in its new place.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ _pinDraggedSite: function Drop_pinDraggedSite(aCell, aEvent) {
+ let index = aCell.index;
+ let draggedSite = gDrag.draggedSite;
+
+ if (draggedSite) {
+ // Pin the dragged site at its new place.
+ if (aCell != draggedSite.cell)
+ draggedSite.pin(index);
+ } else {
+ let link = gDragDataHelper.getLinkFromDragEvent(aEvent);
+ if (link) {
+ // A new link was dragged onto the grid. Create it by pinning its URL.
+ gPinnedLinks.pin(link, index);
+
+ // Make sure the newly added link is not blocked.
+ gBlockedLinks.unblock(link);
+ }
+ }
+ },
+
+ /**
+ * Time a rearrange with a little delay.
+ * @param aCell The drop target cell.
+ */
+ _delayedRearrange: function Drop_delayedRearrange(aCell) {
+ // The last drop target didn't change so there's no need to re-arrange.
+ if (this._lastDropTarget == aCell)
+ return;
+
+ let self = this;
+
+ function callback() {
+ self._rearrangeTimeout = null;
+ self._rearrange(aCell);
+ }
+
+ this._cancelDelayedArrange();
+ this._rearrangeTimeout = setTimeout(callback, DELAY_REARRANGE_MS);
+
+ // Store the last drop target.
+ this._lastDropTarget = aCell;
+ },
+
+ /**
+ * Cancels a timed rearrange, if any.
+ */
+ _cancelDelayedArrange: function Drop_cancelDelayedArrange() {
+ if (this._rearrangeTimeout) {
+ clearTimeout(this._rearrangeTimeout);
+ this._rearrangeTimeout = null;
+ }
+ },
+
+ /**
+ * Rearrange all sites in the grid depending on the current drop target.
+ * @param aCell The drop target cell.
+ */
+ _rearrange: function Drop_rearrange(aCell) {
+ let sites = gGrid.sites;
+
+ // We need to rearrange the grid only if there's a current drop target.
+ if (aCell)
+ sites = gDropPreview.rearrange(aCell);
+
+ gTransformation.rearrangeSites(sites, {unfreeze: !aCell});
+ }
+};
diff --git a/browser/base/content/newtab/dropPreview.js b/browser/base/content/newtab/dropPreview.js
new file mode 100644
index 000000000..fd7587a35
--- /dev/null
+++ b/browser/base/content/newtab/dropPreview.js
@@ -0,0 +1,222 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides the ability to re-arrange the current grid to
+ * indicate the transformation that results from dropping a cell at a certain
+ * position.
+ */
+var gDropPreview = {
+ /**
+ * Rearranges the sites currently contained in the grid when a site would be
+ * dropped onto the given cell.
+ * @param aCell The drop target cell.
+ * @return The re-arranged array of sites.
+ */
+ rearrange: function DropPreview_rearrange(aCell) {
+ let sites = gGrid.sites;
+
+ // Insert the dragged site into the current grid.
+ this._insertDraggedSite(sites, aCell);
+
+ // After the new site has been inserted we need to correct the positions
+ // of all pinned tabs that have been moved around.
+ this._repositionPinnedSites(sites, aCell);
+
+ return sites;
+ },
+
+ /**
+ * Inserts the currently dragged site into the given array of sites.
+ * @param aSites The array of sites to insert into.
+ * @param aCell The drop target cell.
+ */
+ _insertDraggedSite: function DropPreview_insertDraggedSite(aSites, aCell) {
+ let dropIndex = aCell.index;
+ let draggedSite = gDrag.draggedSite;
+
+ // We're currently dragging a site.
+ if (draggedSite) {
+ let dragCell = draggedSite.cell;
+ let dragIndex = dragCell.index;
+
+ // Move the dragged site into its new position.
+ if (dragIndex != dropIndex) {
+ aSites.splice(dragIndex, 1);
+ aSites.splice(dropIndex, 0, draggedSite);
+ }
+ // We're handling an external drag item.
+ } else {
+ aSites.splice(dropIndex, 0, null);
+ }
+ },
+
+ /**
+ * Correct the position of all pinned sites that might have been moved to
+ * different positions after the dragged site has been inserted.
+ * @param aSites The array of sites containing the dragged site.
+ * @param aCell The drop target cell.
+ */
+ _repositionPinnedSites:
+ function DropPreview_repositionPinnedSites(aSites, aCell) {
+
+ // Collect all pinned sites.
+ let pinnedSites = this._filterPinnedSites(aSites, aCell);
+
+ // Correct pinned site positions.
+ pinnedSites.forEach(function (aSite) {
+ aSites[aSites.indexOf(aSite)] = aSites[aSite.cell.index];
+ aSites[aSite.cell.index] = aSite;
+ }, this);
+
+ // There might be a pinned cell that got pushed out of the grid, try to
+ // sneak it in by removing a lower-priority cell.
+ if (this._hasOverflowedPinnedSite(aSites, aCell))
+ this._repositionOverflowedPinnedSite(aSites, aCell);
+ },
+
+ /**
+ * Filter pinned sites out of the grid that are still on their old positions
+ * and have not moved.
+ * @param aSites The array of sites to filter.
+ * @param aCell The drop target cell.
+ * @return The filtered array of sites.
+ */
+ _filterPinnedSites: function DropPreview_filterPinnedSites(aSites, aCell) {
+ let draggedSite = gDrag.draggedSite;
+
+ // When dropping on a cell that contains a pinned site make sure that all
+ // pinned cells surrounding the drop target are moved as well.
+ let range = this._getPinnedRange(aCell);
+
+ return aSites.filter(function (aSite, aIndex) {
+ // The site must be valid, pinned and not the dragged site.
+ if (!aSite || aSite == draggedSite || !aSite.isPinned())
+ return false;
+
+ let index = aSite.cell.index;
+
+ // If it's not in the 'pinned range' it's a valid pinned site.
+ return (index > range.end || index < range.start);
+ });
+ },
+
+ /**
+ * Determines the range of pinned sites surrounding the drop target cell.
+ * @param aCell The drop target cell.
+ * @return The range of pinned cells.
+ */
+ _getPinnedRange: function DropPreview_getPinnedRange(aCell) {
+ let dropIndex = aCell.index;
+ let range = {start: dropIndex, end: dropIndex};
+
+ // We need a pinned range only when dropping on a pinned site.
+ if (aCell.containsPinnedSite()) {
+ let links = gPinnedLinks.links;
+
+ // Find all previous siblings of the drop target that are pinned as well.
+ while (range.start && links[range.start - 1])
+ range.start--;
+
+ let maxEnd = links.length - 1;
+
+ // Find all next siblings of the drop target that are pinned as well.
+ while (range.end < maxEnd && links[range.end + 1])
+ range.end++;
+ }
+
+ return range;
+ },
+
+ /**
+ * Checks if the given array of sites contains a pinned site that has
+ * been pushed out of the grid.
+ * @param aSites The array of sites to check.
+ * @param aCell The drop target cell.
+ * @return Whether there is an overflowed pinned cell.
+ */
+ _hasOverflowedPinnedSite:
+ function DropPreview_hasOverflowedPinnedSite(aSites, aCell) {
+
+ // If the drop target isn't pinned there's no way a pinned site has been
+ // pushed out of the grid so we can just exit here.
+ if (!aCell.containsPinnedSite())
+ return false;
+
+ let cells = gGrid.cells;
+
+ // No cells have been pushed out of the grid, nothing to do here.
+ if (aSites.length <= cells.length)
+ return false;
+
+ let overflowedSite = aSites[cells.length];
+
+ // Nothing to do if the site that got pushed out of the grid is not pinned.
+ return (overflowedSite && overflowedSite.isPinned());
+ },
+
+ /**
+ * We have a overflowed pinned site that we need to re-position so that it's
+ * visible again. We try to find a lower-priority cell (empty or containing
+ * an unpinned site) that we can move it to.
+ * @param aSites The array of sites.
+ * @param aCell The drop target cell.
+ */
+ _repositionOverflowedPinnedSite:
+ function DropPreview_repositionOverflowedPinnedSite(aSites, aCell) {
+
+ // Try to find a lower-priority cell (empty or containing an unpinned site).
+ let index = this._indexOfLowerPrioritySite(aSites, aCell);
+
+ if (index > -1) {
+ let cells = gGrid.cells;
+ let dropIndex = aCell.index;
+
+ // Move all pinned cells to their new positions to let the overflowed
+ // site fit into the grid.
+ for (let i = index + 1, lastPosition = index; i < aSites.length; i++) {
+ if (i != dropIndex) {
+ aSites[lastPosition] = aSites[i];
+ lastPosition = i;
+ }
+ }
+
+ // Finally, remove the overflowed site from its previous position.
+ aSites.splice(cells.length, 1);
+ }
+ },
+
+ /**
+ * Finds the index of the last cell that is empty or contains an unpinned
+ * site. These are considered to be of a lower priority.
+ * @param aSites The array of sites.
+ * @param aCell The drop target cell.
+ * @return The cell's index.
+ */
+ _indexOfLowerPrioritySite:
+ function DropPreview_indexOfLowerPrioritySite(aSites, aCell) {
+
+ let cells = gGrid.cells;
+ let dropIndex = aCell.index;
+
+ // Search (beginning with the last site in the grid) for a site that is
+ // empty or unpinned (an thus lower-priority) and can be pushed out of the
+ // grid instead of the pinned site.
+ for (let i = cells.length - 1; i >= 0; i--) {
+ // The cell that is our drop target is not a good choice.
+ if (i == dropIndex)
+ continue;
+
+ let site = aSites[i];
+
+ // We can use the cell only if it's empty or the site is un-pinned.
+ if (!site || !site.isPinned())
+ return i;
+ }
+
+ return -1;
+ }
+};
diff --git a/browser/base/content/newtab/dropTargetShim.js b/browser/base/content/newtab/dropTargetShim.js
new file mode 100644
index 000000000..57a97fa00
--- /dev/null
+++ b/browser/base/content/newtab/dropTargetShim.js
@@ -0,0 +1,232 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides a custom drop target detection. We need this because
+ * the default DnD target detection relies on the cursor's position. We want
+ * to pick a drop target based on the dragged site's position.
+ */
+var gDropTargetShim = {
+ /**
+ * Cache for the position of all cells, cleaned after drag finished.
+ */
+ _cellPositions: null,
+
+ /**
+ * The last drop target that was hovered.
+ */
+ _lastDropTarget: null,
+
+ /**
+ * Initializes the drop target shim.
+ */
+ init: function () {
+ gGrid.node.addEventListener("dragstart", this, true);
+ },
+
+ /**
+ * Add all event listeners needed during a drag operation.
+ */
+ _addEventListeners: function () {
+ gGrid.node.addEventListener("dragend", this);
+
+ let docElement = document.documentElement;
+ docElement.addEventListener("dragover", this);
+ docElement.addEventListener("dragenter", this);
+ docElement.addEventListener("drop", this);
+ },
+
+ /**
+ * Remove all event listeners that were needed during a drag operation.
+ */
+ _removeEventListeners: function () {
+ gGrid.node.removeEventListener("dragend", this);
+
+ let docElement = document.documentElement;
+ docElement.removeEventListener("dragover", this);
+ docElement.removeEventListener("dragenter", this);
+ docElement.removeEventListener("drop", this);
+ },
+
+ /**
+ * Handles all shim events.
+ */
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "dragstart":
+ this._dragstart(aEvent);
+ break;
+ case "dragenter":
+ aEvent.preventDefault();
+ break;
+ case "dragover":
+ this._dragover(aEvent);
+ break;
+ case "drop":
+ this._drop(aEvent);
+ break;
+ case "dragend":
+ this._dragend(aEvent);
+ break;
+ }
+ },
+
+ /**
+ * Handles the 'dragstart' event.
+ * @param aEvent The 'dragstart' event.
+ */
+ _dragstart: function (aEvent) {
+ if (aEvent.target.classList.contains("newtab-link")) {
+ gGrid.lock();
+ this._addEventListeners();
+ }
+ },
+
+ /**
+ * Handles the 'dragover' event.
+ * @param aEvent The 'dragover' event.
+ */
+ _dragover: function (aEvent) {
+ // XXX bug 505521 - Use the dragover event to retrieve the
+ // current mouse coordinates while dragging.
+ let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
+ gDrag.drag(sourceNode._newtabSite, aEvent);
+
+ // Find the current drop target, if there's one.
+ this._updateDropTarget(aEvent);
+
+ // If we have a valid drop target,
+ // let the drag-and-drop service know.
+ if (this._lastDropTarget) {
+ aEvent.preventDefault();
+ }
+ },
+
+ /**
+ * Handles the 'drop' event.
+ * @param aEvent The 'drop' event.
+ */
+ _drop: function (aEvent) {
+ // We're accepting all drops.
+ aEvent.preventDefault();
+
+ // remember that drop event was seen, this explicitly
+ // assumes that drop event preceeds dragend event
+ this._dropSeen = true;
+
+ // Make sure to determine the current drop target
+ // in case the dragover event hasn't been fired.
+ this._updateDropTarget(aEvent);
+
+ // A site was successfully dropped.
+ this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
+ },
+
+ /**
+ * Handles the 'dragend' event.
+ * @param aEvent The 'dragend' event.
+ */
+ _dragend: function (aEvent) {
+ if (this._lastDropTarget) {
+ if (aEvent.dataTransfer.mozUserCancelled || !this._dropSeen) {
+ // The drag operation was cancelled or no drop event was generated
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+ }
+
+ // Clean up.
+ this._lastDropTarget = null;
+ this._cellPositions = null;
+ }
+
+ this._dropSeen = false;
+ gGrid.unlock();
+ this._removeEventListeners();
+ },
+
+ /**
+ * Tries to find the current drop target and will fire
+ * appropriate dragenter, dragexit, and dragleave events.
+ * @param aEvent The current drag event.
+ */
+ _updateDropTarget: function (aEvent) {
+ // Let's see if we find a drop target.
+ let target = this._findDropTarget(aEvent);
+
+ if (target != this._lastDropTarget) {
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+
+ if (target)
+ // We're now hovering a (new) drop target.
+ this._dispatchEvent(aEvent, "dragenter", target);
+
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+
+ this._lastDropTarget = target;
+ }
+ },
+
+ /**
+ * Determines the current drop target by matching the dragged site's position
+ * against all cells in the grid.
+ * @return The currently hovered drop target or null.
+ */
+ _findDropTarget: function () {
+ // These are the minimum intersection values - we want to use the cell if
+ // the site is >= 50% hovering its position.
+ let minWidth = gDrag.cellWidth / 2;
+ let minHeight = gDrag.cellHeight / 2;
+
+ let cellPositions = this._getCellPositions();
+ let rect = gTransformation.getNodePosition(gDrag.draggedSite.node);
+
+ // Compare each cell's position to the dragged site's position.
+ for (let i = 0; i < cellPositions.length; i++) {
+ let inter = rect.intersect(cellPositions[i].rect);
+
+ // If the intersection is big enough we found a drop target.
+ if (inter.width >= minWidth && inter.height >= minHeight)
+ return cellPositions[i].cell;
+ }
+
+ // No drop target found.
+ return null;
+ },
+
+ /**
+ * Gets the positions of all cell nodes.
+ * @return The (cached) cell positions.
+ */
+ _getCellPositions: function DropTargetShim_getCellPositions() {
+ if (this._cellPositions)
+ return this._cellPositions;
+
+ return this._cellPositions = gGrid.cells.map(function (cell) {
+ return {cell: cell, rect: gTransformation.getNodePosition(cell.node)};
+ });
+ },
+
+ /**
+ * Dispatches a custom DragEvent on the given target node.
+ * @param aEvent The source event.
+ * @param aType The event type.
+ * @param aTarget The target node that receives the event.
+ */
+ _dispatchEvent: function (aEvent, aType, aTarget) {
+ let node = aTarget.node;
+ let event = document.createEvent("DragEvent");
+
+ // The event should not bubble to prevent recursion.
+ event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false,
+ false, false, 0, node, aEvent.dataTransfer);
+
+ node.dispatchEvent(event);
+ }
+};
diff --git a/browser/base/content/newtab/grid.js b/browser/base/content/newtab/grid.js
new file mode 100644
index 000000000..b6f98fa17
--- /dev/null
+++ b/browser/base/content/newtab/grid.js
@@ -0,0 +1,279 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * Define various fixed dimensions
+ */
+const GRID_BOTTOM_EXTRA = 7; // title's line-height extends 7px past the margin
+const GRID_WIDTH_EXTRA = 1; // provide 1px buffer to allow for rounding error
+const SPONSORED_TAG_BUFFER = 2; // 2px buffer to clip off top of sponsored tag
+
+/**
+ * This singleton represents the grid that contains all sites.
+ */
+var gGrid = {
+ /**
+ * The DOM node of the grid.
+ */
+ _node: null,
+ _gridDefaultContent: null,
+ get node() { return this._node; },
+
+ /**
+ * The cached DOM fragment for sites.
+ */
+ _siteFragment: null,
+
+ /**
+ * All cells contained in the grid.
+ */
+ _cells: [],
+ get cells() { return this._cells; },
+
+ /**
+ * All sites contained in the grid's cells. Sites may be empty.
+ */
+ get sites() { return [for (cell of this.cells) cell.site]; },
+
+ // Tells whether the grid has already been initialized.
+ get ready() { return !!this._ready; },
+
+ // Returns whether the page has finished loading yet.
+ get isDocumentLoaded() { return document.readyState == "complete"; },
+
+ /**
+ * Initializes the grid.
+ * @param aSelector The query selector of the grid.
+ */
+ init: function Grid_init() {
+ this._node = document.getElementById("newtab-grid");
+ this._gridDefaultContent = this._node.lastChild;
+ this._createSiteFragment();
+
+ gLinks.populateCache(() => {
+ this._refreshGrid();
+ this._ready = true;
+
+ // If fetching links took longer than loading the page itself then
+ // we need to resize the grid as that was blocked until now.
+ // We also want to resize now if the page was already loaded when
+ // initializing the grid (the user toggled the page).
+ this._resizeGrid();
+
+ addEventListener("resize", this);
+ });
+
+ // Resize the grid as soon as the page loads.
+ if (!this.isDocumentLoaded) {
+ addEventListener("load", this);
+ }
+ },
+
+ /**
+ * Creates a new site in the grid.
+ * @param aLink The new site's link.
+ * @param aCell The cell that will contain the new site.
+ * @return The newly created site.
+ */
+ createSite: function Grid_createSite(aLink, aCell) {
+ let node = aCell.node;
+ node.appendChild(this._siteFragment.cloneNode(true));
+ return new Site(node.firstElementChild, aLink);
+ },
+
+ /**
+ * Handles all grid events.
+ */
+ handleEvent: function Grid_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "load":
+ case "resize":
+ this._resizeGrid();
+ break;
+ }
+ },
+
+ /**
+ * Locks the grid to block all pointer events.
+ */
+ lock: function Grid_lock() {
+ this.node.setAttribute("locked", "true");
+ },
+
+ /**
+ * Unlocks the grid to allow all pointer events.
+ */
+ unlock: function Grid_unlock() {
+ this.node.removeAttribute("locked");
+ },
+
+ /**
+ * Renders and resizes the gird. _resizeGrid() call is needed to ensure
+ * that scrollbar disappears when the bottom row becomes empty following
+ * the block action, or tile display is turmed off via cog menu
+ */
+
+ refresh() {
+ this._refreshGrid();
+ this._resizeGrid();
+ },
+
+ /**
+ * Renders the grid, including cells and sites.
+ */
+ _refreshGrid() {
+ let cell = document.createElementNS(HTML_NAMESPACE, "div");
+ cell.classList.add("newtab-cell");
+
+ // Creates all the cells up to the maximum
+ let fragment = document.createDocumentFragment();
+ for (let i = 0; i < gGridPrefs.gridColumns * gGridPrefs.gridRows; i++) {
+ fragment.appendChild(cell.cloneNode(true));
+ }
+
+ // Create cells.
+ let cells = Array.from(fragment.childNodes, (cell) => new Cell(this, cell));
+
+ // Fetch links.
+ let links = gLinks.getLinks();
+
+ // Create sites.
+ let numLinks = Math.min(links.length, cells.length);
+ let hasHistoryTiles = false;
+ for (let i = 0; i < numLinks; i++) {
+ if (links[i]) {
+ this.createSite(links[i], cells[i]);
+ if (links[i].type == "history") {
+ hasHistoryTiles = true;
+ }
+ }
+ }
+
+ this._cells = cells;
+ while (this._gridDefaultContent.nextSibling) {
+ this._gridDefaultContent.nextSibling.remove();
+ }
+ this._node.appendChild(fragment);
+
+ document.getElementById("topsites-heading").textContent =
+ hasHistoryTiles ? "Your Top Sites" : "Top Sites";
+ },
+
+ /**
+ * Calculate the height for a number of rows up to the maximum rows
+ * @param rows Number of rows defaulting to the max
+ */
+ _computeHeight: function Grid_computeHeight(aRows) {
+ let {gridRows} = gGridPrefs;
+ aRows = aRows === undefined ? gridRows : Math.min(gridRows, aRows);
+ return aRows * this._cellHeight + GRID_BOTTOM_EXTRA;
+ },
+
+ /**
+ * Creates the DOM fragment that is re-used when creating sites.
+ */
+ _createSiteFragment: function Grid_createSiteFragment() {
+ let site = document.createElementNS(HTML_NAMESPACE, "div");
+ site.classList.add("newtab-site");
+ site.setAttribute("draggable", "true");
+
+ // Create the site's inner HTML code.
+ site.innerHTML =
+ '<span class="newtab-sponsored">' + newTabString("sponsored.button") + '</span>' +
+ '<a class="newtab-link">' +
+ ' <span class="newtab-thumbnail placeholder"/>' +
+ ' <span class="newtab-thumbnail thumbnail"/>' +
+ ' <span class="newtab-thumbnail enhanced-content"/>' +
+ ' <span class="newtab-title"/>' +
+ '</a>' +
+ '<input type="button" title="' + newTabString("pin") + '"' +
+ ' class="newtab-control newtab-control-pin"/>' +
+ '<input type="button" title="' + newTabString("block") + '"' +
+ ' class="newtab-control newtab-control-block"/>' +
+ '<span class="newtab-suggested"/>';
+
+ this._siteFragment = document.createDocumentFragment();
+ this._siteFragment.appendChild(site);
+ },
+
+ /**
+ * Test a tile at a given position for being pinned or history
+ * @param position Position in sites array
+ */
+ _isHistoricalTile: function Grid_isHistoricalTile(aPos) {
+ let site = this.sites[aPos];
+ return site && (site.isPinned() || site.link && site.link.type == "history");
+ },
+
+ /**
+ * Make sure the correct number of rows and columns are visible
+ */
+ _resizeGrid: function Grid_resizeGrid() {
+ // If we're somehow called before the page has finished loading,
+ // let's bail out to avoid caching zero heights and widths.
+ // We'll be called again when DOMContentLoaded fires.
+ // Same goes for the grid if that's not ready yet.
+ if (!this.isDocumentLoaded || !this._ready) {
+ return;
+ }
+
+ // Save the cell's computed height/width including margin and border
+ if (this._cellHeight === undefined) {
+ let refCell = document.querySelector(".newtab-cell");
+ let style = getComputedStyle(refCell);
+ this._cellHeight = refCell.offsetHeight +
+ parseFloat(style.marginTop) + parseFloat(style.marginBottom);
+ this._cellWidth = refCell.offsetWidth +
+ parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+ }
+
+ let searchContainer = document.querySelector("#newtab-search-container");
+ // Save search-container margin height
+ if (this._searchContainerMargin === undefined) {
+ let style = getComputedStyle(searchContainer);
+ this._searchContainerMargin = parseFloat(style.marginBottom) +
+ parseFloat(style.marginTop);
+ }
+
+ // Find the number of rows we can place into view port
+ let availHeight = document.documentElement.clientHeight -
+ searchContainer.offsetHeight - this._searchContainerMargin;
+ let visibleRows = Math.floor(availHeight / this._cellHeight);
+
+ // Find the number of columns that fit into view port
+ let maxGridWidth = gGridPrefs.gridColumns * this._cellWidth + GRID_WIDTH_EXTRA;
+ // available width is current grid width, but no greater than maxGridWidth
+ let availWidth = Math.min(document.querySelector("#newtab-grid").clientWidth,
+ maxGridWidth);
+ // finally get the number of columns we can fit into view port
+ let gridColumns = Math.floor(availWidth / this._cellWidth);
+ // walk sites backwords until a pinned or history tile is found or visibleRows reached
+ let tileIndex = Math.min(gGridPrefs.gridRows * gridColumns, this.sites.length) - 1;
+ while (tileIndex >= visibleRows * gridColumns) {
+ if (this._isHistoricalTile(tileIndex)) {
+ break;
+ }
+ tileIndex--;
+ }
+
+ // Compute the actual number of grid rows we will display (potentially
+ // with a scroll bar). tileIndex now points to a historical tile with
+ // heighest index or to the last index of the visible row, if none found
+ // Dividing tileIndex by number of tiles in a column gives the rows
+ let gridRows = Math.floor(tileIndex / gridColumns) + 1;
+
+ // we need to set grid width, for otherwise the scrollbar may shrink
+ // the grid when shown and cause grid layout to be different from
+ // what being computed above. This, in turn, may cause scrollbar shown
+ // for directory tiles, and introduce jitter when grid width is aligned
+ // exactly on the column boundary
+ this._node.style.width = gridColumns * this._cellWidth + "px";
+ this._node.style.maxWidth = gGridPrefs.gridColumns * this._cellWidth +
+ GRID_WIDTH_EXTRA + "px";
+ this._node.style.height = this._computeHeight() + "px";
+ this._node.style.maxHeight = this._computeHeight(gridRows) - SPONSORED_TAG_BUFFER + "px";
+ }
+};
diff --git a/browser/base/content/newtab/newTab.css b/browser/base/content/newtab/newTab.css
new file mode 100644
index 000000000..658ad2ed3
--- /dev/null
+++ b/browser/base/content/newtab/newTab.css
@@ -0,0 +1,654 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+html {
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ font: message-box;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ background-color: #F9F9F9;
+ display: -moz-box;
+ position: relative;
+ -moz-box-flex: 1;
+ -moz-user-focus: normal;
+ -moz-box-orient: vertical;
+}
+
+input {
+ font: message-box;
+ font-size: 16px;
+}
+
+input[type=button] {
+ cursor: pointer;
+}
+
+/* UNDO */
+#newtab-undo-container {
+ transition: opacity 100ms ease-out;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+#newtab-undo-container[undo-disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+/* CUSTOMIZE */
+#newtab-customize-button {
+ position: absolute;
+ top: 10px;
+ right: 20px;
+ z-index: 101;
+}
+
+#newtab-customize-button:dir(rtl) {
+ left: 20px;
+ right: auto;
+}
+
+/* MARGINS */
+#newtab-vertical-margin {
+ display: -moz-box;
+ position: relative;
+ -moz-box-flex: 1;
+ -moz-box-orient: vertical;
+}
+
+#newtab-margin-undo-container {
+ display: -moz-box;
+ left: 6px;
+ position: absolute;
+ top: 6px;
+ z-index: 1;
+}
+
+#newtab-margin-undo-container:dir(rtl) {
+ left: auto;
+ right: 6px;
+}
+
+#newtab-undo-close-button:dir(rtl) {
+ float:left;
+}
+
+#newtab-horizontal-margin {
+ display: -moz-box;
+ -moz-box-flex: 1;
+}
+
+#newtab-margin-top,
+#newtab-margin-bottom {
+ display: -moz-box;
+ position: relative;
+}
+
+#newtab-margin-top {
+ -moz-box-flex: 1;
+}
+
+#newtab-margin-bottom {
+ -moz-box-flex: 2;
+}
+
+.newtab-side-margin {
+ min-width: 10px;
+ -moz-box-flex: 1;
+}
+
+/* GRID */
+#newtab-grid {
+ -moz-box-flex: 5;
+ overflow: hidden;
+ text-align: center;
+ transition: 100ms ease-out;
+ transition-property: opacity;
+}
+
+#newtab-grid[page-disabled] {
+ opacity: 0;
+}
+
+#newtab-grid[locked],
+#newtab-grid[page-disabled] {
+ pointer-events: none;
+}
+
+body:not(.compact) #topsites-heading {
+ display: none;
+}
+
+/*
+ * If you change the sizes here, make sure you
+ * change the preferences:
+ * toolkit.pageThumbs.minWidth
+ * toolkit.pageThumbs.minHeight
+ */
+/* CELLS */
+.newtab-cell {
+ display: -moz-box;
+ height: 210px;
+ margin: 20px 10px 35px;
+ width: 290px;
+}
+
+body.compact .newtab-cell {
+ width: 110px;
+ height: 110px;
+ margin: 12px;
+}
+
+/* SITES */
+.newtab-site {
+ position: relative;
+ -moz-box-flex: 1;
+ transition: 100ms ease-out;
+ transition-property: top, left, opacity;
+}
+
+.newtab-site[frozen] {
+ position: absolute;
+ pointer-events: none;
+}
+
+.newtab-site[dragged] {
+ transition-property: none;
+ z-index: 10;
+}
+
+/* LINK + THUMBNAILS */
+.newtab-link,
+.newtab-thumbnail {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+}
+
+/* TITLES */
+.newtab-sponsored,
+.newtab-title,
+.newtab-suggested {
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ text-align: center;
+}
+
+.newtab-sponsored,
+.newtab-title {
+ bottom: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+}
+
+.newtab-suggested {
+ border: 1px solid transparent;
+ border-radius: 2px;
+ font-size: 12px;
+ height: 17px;
+ line-height: 17px;
+ margin-bottom: -1px;
+ padding: 2px 8px;
+ display: none;
+ margin-left: auto;
+ margin-right: auto;
+ left: 0;
+ top: 215px;
+ -moz-user-select: none;
+}
+
+.newtab-suggested-bounds {
+ max-height: 34px; /* 34 / 17 = 2 lines maximum */
+}
+
+.newtab-title {
+ left: 0;
+ padding: 0 4px;
+}
+
+.newtab-sponsored {
+ background-color: #FFFFFF;
+ border: 1px solid #E2E2E2;
+ border-radius: 3px;
+ color: #4A4A4A;
+ cursor: pointer;
+ display: none;
+ font-family: Arial;
+ font-size: 9px;
+ height: 17px;
+ left: 0;
+ line-height: 6px;
+ padding: 4px;
+ right: auto;
+ top: -15px;
+}
+
+.newtab-site[suggested=true] > .newtab-sponsored {
+ background-color: #E2E2E2;
+ border: none;
+}
+
+.newtab-site > .newtab-sponsored:-moz-any(:hover, [active]) {
+ background-color: #4A90E2;
+ border: 0;
+ color: white;
+}
+
+.newtab-site > .newtab-sponsored[active] {
+ background-color: #000000;
+}
+
+.newtab-sponsored:dir(rtl) {
+ right: 0;
+ left: auto;
+}
+
+.newtab-site:-moz-any([type=enhanced], [type=sponsored], [suggested]) .newtab-sponsored {
+ display: block;
+}
+
+.newtab-site[suggested] .newtab-suggested {
+ display: table;
+}
+
+.sponsored-explain,
+.sponsored-explain a,
+.suggested-explain,
+.suggested-explain a {
+ color: white;
+}
+
+.sponsored-explain,
+.suggested-explain {
+ background-color: rgba(51, 51, 51, 0.95);
+ bottom: 30px;
+ line-height: 20px;
+ padding: 15px 10px;
+ position: absolute;
+ text-align: start;
+}
+
+.sponsored-explain input,
+.suggested-explain input {
+ background-size: 18px;
+ height: 18px;
+ opacity: 1;
+ pointer-events: none;
+ position: static;
+ width: 18px;
+}
+
+/* CONTROLS */
+.newtab-control {
+ position: absolute;
+ opacity: 0;
+ transition: opacity 100ms ease-out;
+}
+
+.newtab-control:-moz-focusring,
+.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-control {
+ opacity: 1;
+}
+
+.newtab-control[dragged] {
+ opacity: 0 !important;
+}
+
+@media (-moz-touch-enabled) {
+ .newtab-control {
+ opacity: 1;
+ }
+}
+
+/* DRAG & DROP */
+
+/*
+ * This is just a temporary drag element used for dataTransfer.setDragImage()
+ * so that we can use custom drag images and elements. It needs an opacity of
+ * 0.01 so that the core code detects that it's in fact a visible element.
+ */
+.newtab-drag {
+ width: 1px;
+ height: 1px;
+ background-color: #fff;
+ opacity: 0.01;
+}
+
+/* SEARCH */
+#newtab-search-container {
+ display: -moz-box;
+ position: relative;
+ -moz-box-pack: center;
+ margin: 40px 0 15px;
+}
+
+body.compact #newtab-search-container {
+ margin-top: 0;
+ margin-bottom: 80px;
+}
+
+#newtab-search-container[page-disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+#newtab-search-form {
+ display: -moz-box;
+ position: relative;
+ height: 36px;
+ -moz-box-flex: 1;
+ max-width: 600px; /* 2 * (290 cell width + 10 cell margin) */
+}
+
+#newtab-search-icon {
+ border: 1px transparent;
+ padding: 0;
+ margin: 0;
+ width: 36px;
+ height: 36px;
+ background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat;
+ position: absolute;
+}
+
+#newtab-search-text {
+ -moz-box-flex: 1;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 34px;
+ padding-inline-end: 8px;
+ background: hsla(0,0%,100%,.9) padding-box;
+ border: 1px solid;
+ border-spacing: 0;
+ border-radius: 2px 0 0 2px;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+ 0 0 2px hsla(210,65%,9%,.1) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ color: inherit;
+ unicode-bidi: plaintext;
+}
+
+#newtab-search-text:dir(rtl) {
+ border-radius: 0 2px 2px 0;
+}
+
+#newtab-search-text[aria-expanded="true"] {
+ border-radius: 2px 0 0 0;
+}
+
+#newtab-search-text[aria-expanded="true"]:dir(rtl) {
+ border-radius: 0 2px 0 0;
+}
+
+#newtab-search-text[keepfocus],
+#newtab-search-text:focus,
+#newtab-search-text[autofocus] {
+ border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
+}
+
+#newtab-search-submit {
+ margin-inline-start: -1px;
+ color: transparent;
+ background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+ padding: 0;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ border-radius: 0 2px 2px 0;
+ border-inline-start: 1px solid transparent;
+ box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+ width: 50px;
+}
+
+#newtab-search-submit:dir(rtl) {
+ border-radius: 2px 0 0 2px;
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl"), linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1));
+}
+
+#newtab-search-text:focus + #newtab-search-submit,
+#newtab-search-text + #newtab-search-submit:hover,
+#newtab-search-text[autofocus] + #newtab-search-submit {
+ border-color: #59b5fc #45a3e7 #3294d5;
+}
+
+#newtab-search-text:focus + #newtab-search-submit,
+#newtab-search-text[keepfocus] + #newtab-search-submit,
+#newtab-search-text[autofocus] + #newtab-search-submit {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03);
+}
+
+#newtab-search-text + #newtab-search-submit:hover {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03),
+ 0 0 4px hsla(206,100%,20%,.2);
+}
+
+#newtab-search-text + #newtab-search-submit:hover:active {
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
+ 0 0 1px hsla(211,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+#newtab-search-text:focus + #newtab-search-submit:dir(rtl),
+#newtab-search-text[keepfocus] + #newtab-search-submit:dir(rtl),
+#newtab-search-text[autofocus] + #newtab-search-submit:dir(rtl),
+#newtab-search-text + #newtab-search-submit:dir(rtl):hover {
+ background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-rtl-inverted"), linear-gradient(#4cb1ff, #1793e5);
+}
+
+/* CUSTOMIZE */
+#newtab-customize-overlay {
+ opacity: 0;
+ display: none;
+ width: 100%;
+ height: 100%;
+ background: #F9F9F9;
+ z-index: 100;
+ position: fixed;
+ transition: opacity .07s linear;
+}
+
+.newtab-customize-panel-container {
+ position: absolute;
+ margin-right: 40px;
+ right: 0;
+}
+
+.newtab-customize-panel-container:dir(rtl) {
+ right: auto;
+ left: 0;
+}
+
+#newtab-customize-panel {
+ z-index: 999;
+ margin-top: 55px;
+ min-width: 270px;
+ position: absolute;
+ top: 100%;
+ right: -25px;
+ filter: drop-shadow(0 0 1px rgba(0,0,0,0.4)) drop-shadow(0 3px 4px rgba(0,0,0,0.4));
+ transition: all 200ms ease-in-out;
+ transform-origin: top right;
+ transform: translate(-30px, -20px) scale(0) translate(30px, 20px);
+}
+
+#newtab-customize-panel:dir(rtl) {
+ transform-origin: 40px top 20px;
+}
+
+#newtab-customize-panel:dir(rtl),
+#newtab-customize-panel-anchor:dir(rtl) {
+ left: 15px;
+ right: auto;
+}
+
+#newtab-customize-panel[open="true"] {
+ transform: translate(-30px, -20px) scale(1) translate(30px, 20px);
+}
+
+#newtab-customize-panel-anchor {
+ width: 18px;
+ height: 18px;
+ background-color: white;
+ transform: rotate(45deg);
+ position: absolute;
+ top: -6px;
+ right: 15px;
+}
+
+#newtab-customize-title {
+ color: #7A7A7A;
+ font-size: 14px;
+ background-color: #FFFFFF;
+ line-height: 25px;
+ padding: 15px;
+ font-weight: 600;
+ cursor: default;
+ border-radius: 5px 5px 0px 0px;
+ max-width: 300px;
+ overflow: hidden;
+ display: table-cell;
+ border-top: none;
+}
+
+#newtab-customize-panel-inner-wrapper {
+ background-color: #FFFFFF;
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+#newtab-customize-title > label {
+ cursor: default;
+}
+
+#newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+.newtab-customize-panel-item {
+ line-height: 25px;
+ padding: 15px;
+ padding-inline-start: 40px;
+ font-size: 14px;
+ cursor: pointer;
+ max-width: 300px;
+}
+
+.newtab-customize-panel-item:not(:first-child) {
+ border-top: 1px solid threedshadow;
+}
+
+.newtab-customize-panel-subitem > label,
+.newtab-customize-panel-item > label,
+.newtab-customize-complex-option {
+ padding: 0;
+ margin: 0;
+ cursor: pointer;
+}
+
+.newtab-customize-panel-item,
+.newtab-customize-complex-option {
+ display: block;
+ text-align: start;
+ background-color: #F9F9F9;
+}
+
+.newtab-customize-panel-item[selected]:-moz-locale-dir(rtl) {
+ background-position: right 15px center;
+}
+
+.newtab-customize-complex-option:hover > .selectable:not([selected]):-moz-locale-dir(rtl),
+.selectable:not([selected]):hover:-moz-locale-dir(rtl) {
+ background-position: right 15px center;
+}
+
+.newtab-customize-panel-item:not([selected]),
+.newtab-customize-panel-subitem:not([selected]){
+ color: #7A7A7A;
+}
+
+.newtab-customize-panel-item:not([selected]):hover {
+ color: #FFFFFF;
+ background-color: #4A90E2
+}
+
+.newtab-customize-complex-option:hover > .selectable:not([selected]),
+.selectable:not([selected]):hover {
+ background: url("chrome://global/skin/menu/shared-menu-check-hover.svg") no-repeat #FFFFFF;
+ background-size: 16px 16px;
+ background-position: 15px 15px;
+ color: #171F26;
+}
+
+.newtab-customize-complex-option:hover > .selectable:not([selected]) + .newtab-customize-panel-subitem {
+ background-color: #FFFFFF;
+}
+
+.newtab-customize-panel-item[selected] {
+ background: url("chrome://global/skin/menu/shared-menu-check-active.svg") no-repeat transparent;
+ background-size: 16px 16px;
+ background-position: 15px 15px;
+ color: black;
+ font-weight: 600;
+}
+
+.newtab-customize-panel-subitem > .checkbox {
+ width: 18px;
+ height: 18px;
+ background-color: #FFFFFF;
+ border: solid 1px threedshadow;
+}
+
+.newtab-customize-panel-subitem[selected] > .checkbox {
+ background: url("chrome://global/skin/menu/shared-menu-check-black.svg") no-repeat #FFFFFF;
+ background-size: 9px 9px;
+ background-position: center;
+ color: #333333;
+}
+
+.newtab-customize-panel-subitem {
+ font-size: 12px;
+ padding: 0px 15px 15px 15px;
+ padding-inline-start: 40px;
+ display: block;
+ max-width: 300px;
+}
+
+.newtab-customize-panel-subitem > label {
+ padding: 0px 10px;
+ line-height: 20px;
+ vertical-align: middle;
+ max-width: 225px;
+}
+
+.newtab-customize-panel-superitem {
+ line-height: 20px;
+ border-bottom: medium none !important;
+ padding: 15px 15px 10px 15px;
+ padding-inline-start: 40px;
+ border-top: 1px solid threedshadow;
+}
+
+.contentSearchSuggestionTable {
+ font: message-box;
+ font-size: 16px;
+}
diff --git a/browser/base/content/newtab/newTab.inadjacent.json b/browser/base/content/newtab/newTab.inadjacent.json
new file mode 100644
index 000000000..53fb542af
--- /dev/null
+++ b/browser/base/content/newtab/newTab.inadjacent.json
@@ -0,0 +1,3209 @@
+{
+ "domains": [
+ "rp5slFCxq/e7hYhXJCd0vQ==",
+ "2rEimAJDNX5g8HPZehOrGg==",
+ "nvLEpj6ZZF3LWH3wUB6lKg==",
+ "9Cqd4Lm3VvXuJxz79Bbqyg==",
+ "vNRy4LR+7TOKTixqsr5ybw==",
+ "N4zSgsZCo6Z4XRwZ4fu8WQ==",
+ "jsDtRfVbMsFg3KkEl2UiZQ==",
+ "TckkKpiq0a6J6NTw7uOZqw==",
+ "9Or7IAYuuIgZA370w9rNIg==",
+ "ul8WvOjCkxTz9LjT4RqTHg==",
+ "ZGJrbwb5878Nsqm0z+A7nQ==",
+ "5iT64HTeeG5SIFXG7A9o3w==",
+ "YSeSEghPe1kV6g8ghFcNAA==",
+ "0jIUl1NDmJZQkDY12VDeIQ==",
+ "aos6UyDyIw0R1nTK5wTawA==",
+ "G1xxubsq65ugK06UT2DO5A==",
+ "lbhavoDrDPP/8m0onwo63w==",
+ "ObcLsjW0SkdvY0nkZmiTGQ==",
+ "FHZ5084LC0nTAzZlnSKN3Q==",
+ "cdEr+0Fv5iaVZzalZToseg==",
+ "Co8WbNYbCPTFPcHpeK3hRQ==",
+ "qXSzhCEhByLQq9N84tqV+Q==",
+ "h3ufhRk5IEFaNH11rIACtQ==",
+ "fQ1PJ/JwazIaYoy/zy49QQ==",
+ "zAJqfbn54Nsm2ddGtkb59A==",
+ "ixPM9T8ik/gWGZ7BRIcaig==",
+ "/E9pwA3E3hVAZoYq3FmCyw==",
+ "U6ygonI8CxpruhpGB2+Q6A==",
+ "Igi4voB8oVMVw6WUeDSjZg==",
+ "jtuHIJhwoTGzavFpM7ilNw==",
+ "eBvTV27n6Gs+ZsBkpVynvw==",
+ "sFbzw0AUOGG0NEzkaSxVDg==",
+ "yAkIS+Ezj6woEff9YvdO7Q==",
+ "IP1+BwG6q60QzDADi8j7oA==",
+ "Q/teQEBFepHtwZ7UHa2TEA==",
+ "B1vDep5a1Gok5Gnth39+LA==",
+ "cyEIyQ2MZaPGf+K1x9Bbkg==",
+ "aaM+oEJnF4/nwMWyXJU8rA==",
+ "qpDNIpxah8FUiqXm5IRaUg==",
+ "ZTeJ35gMPqIv2WWbeNyIEg==",
+ "nzoAGQAnC/Xgg5PmOgXqkA==",
+ "J5pJDuNi3cqQiyaRJAJk4g==",
+ "2vqN53BXhXzPrKYsh6QH1A==",
+ "QlrzHNYxCwCBMVENvbXjQA==",
+ "Ou2HGn43nmsL3RWSNvMdXw==",
+ "3qk9lsvGTMqVMAZW+xihfw==",
+ "RncMe42RB2bhmUbYtGVnKQ==",
+ "hzNXR6dqPq1+vf4Qh5ByWA==",
+ "sRq3S2ZRs3H39cEQHv4Vig==",
+ "B4ThUBTVJOUPyOsHxikHXA==",
+ "A2lU9GkAdSibLO1JJfFnIA==",
+ "ef3HNkSvuWQrAzkuty2iqg==",
+ "yKDiRM6bf2xc0QXIwHYuaA==",
+ "AdCk4ccJuhA0bIT/61J+RQ==",
+ "UXvAZ7ULCVz2f505K0Wkvg==",
+ "ueKWblrOwVJNgiOvkXKLBQ==",
+ "s8u/jPuBAxu1d18HfV5Z0g==",
+ "hUT0Uc5YMUdNZQEGLz4hJw==",
+ "6jo/phmMTrEXKrNRsionGQ==",
+ "s/Ea/3fkyJ9honzPJkgEQQ==",
+ "hgu2/Jf+WrQAHfO+asW2zw==",
+ "kiVuTNwZ1r2lqYEZxIHyiQ==",
+ "24T5KVrVE2mYwJ5Goj3xJw==",
+ "fiWBVlfj97GGjEvf/Q9Spg==",
+ "5VWdlvJe7eoXMGkTtHzCUg==",
+ "+cFQxKa5RWVtc1z00Jujew==",
+ "nVa+rLH5p+yXBksLwQsjRQ==",
+ "5tyI6bMdb3tMIi4ewvr/SQ==",
+ "S6Roj31yS5bZbSFcd3f4Hg==",
+ "uW1Zl8iuEF8ZT/gwCBEqwA==",
+ "YwL+FJgxlZ8JVig+9iP5Cw==",
+ "ThIYK/mQsp9cMf8+rws/4Q==",
+ "w0oSxOhRG6kE9B868aoYVQ==",
+ "DJUDGQ0J32dF1kfItyxALg==",
+ "34/ab69lPkuAKt6WBxJPpA==",
+ "25jH4C9apgqWZGZP15lM6Q==",
+ "GxvwleSaSwILD1pG9k9buA==",
+ "YRAMt2ArEINo83ms6AqJ0A==",
+ "15HyTJNoMYzi3XCkeU5Z7A==",
+ "/SqjXGD+TKC90uz1vsjqUw==",
+ "karhKOknkhtg/LSFo9BGRA==",
+ "+tD1d0t3vfJvc1hUAvTT4Q==",
+ "rkaKbtlnyVr53D0rexLqdQ==",
+ "fAugw4rtnXzzRXfC1wRgOQ==",
+ "RgxoepF/XOwIsGat5r5HpQ==",
+ "Y49/EnzVz3ugXCYxFjFN7g==",
+ "tHMyzBm/2wNDw7TeNeujbg==",
+ "LlqzYV4uZpiJy4ORWPSekA==",
+ "M3Huar7/ded9OGgDwJhZgw==",
+ "QkNNATSx/PJ1XjgZyTtkUQ==",
+ "skVw0v6Wx00sfHAScPK1Bw==",
+ "v1gsIvg+C68T9wixMzL2sQ==",
+ "hDL75EXhl7BaYnAxkoGwbw==",
+ "tReG97snx2ESpXbfllCL7Q==",
+ "EiZBXR15dT1TMrkgkzmkvw==",
+ "5hRHirfD90/sdp0ILJQU8A==",
+ "rabElvtYtG0jW6dxAOHofg==",
+ "JpxoTRKWN+SEeBQ453R1YQ==",
+ "Faz4Lm0cpjvF0IjVkHiZMg==",
+ "jGErFAIoXx+50KFpVIGZiA==",
+ "5GzBkduKpUX7u1uwtYIFug==",
+ "cRBe0J9/KWRX19N2vPkCiw==",
+ "t/7g8t4Kr3/+SCnOn3XFWQ==",
+ "sd08c6jUXs5/hxND0fBkPA==",
+ "nTxKpqIdnHNdpDk7Dx3TEg==",
+ "5l+RALcce+lTDnmXI+Wqqg==",
+ "pzJ4QmEBGRNiiX6z0xHh8g==",
+ "Vfl3YbqR6JRR7SIdsUA/vA==",
+ "cgfhOdB376a4GAcuACADvA==",
+ "inAefsQM6tiIhQCMtcPcyA==",
+ "FTSULGL8CMhmcc7Cyf/X8A==",
+ "9XSWpaZyHEy7V/tuw5uZEw==",
+ "+VtM1opKlgb/jrCwc4YjFA==",
+ "oF46xheuI/NUxUOnOttzvA==",
+ "Qy+lvhDbJCumr6kiPLd1oA==",
+ "swps7UEKpIbVBJ9SnPK3zQ==",
+ "b7wyIiJvJs+29QePxsdWtQ==",
+ "x3iDZxYyuHtG/9rNW5HMYg==",
+ "r1dx1g5UOksywvOaQTamfA==",
+ "KvVF3Si/fr4JQtr7jCJiog==",
+ "spvZ7hhtG5QY7JXs96lBUg==",
+ "ECL8mA5B6CswyDH6yJ4hVw==",
+ "7Uu+YsdS69dMSDYUr6vTag==",
+ "Rnm9pSvQRRbkHpOijraLZw==",
+ "aQJqpnXdzqNSFhMn3EJA2Q==",
+ "TctnXpd7Wd5ZXKMnOFHAQA==",
+ "+lPqG8l6mf2FWVGWflyF/g==",
+ "mPmnmL2oRRJmKYjQ6TfN3g==",
+ "fyXFcT5ZCawDBg74n1WSpg==",
+ "uq5Zrxq10pO1HoPxReT5og==",
+ "3eoCsOKXY8RDrHSdlXqmrA==",
+ "9nQv2BFG56xsHViN5UpHYw==",
+ "RtP/nJgy/ItyuDrpBbAotg==",
+ "5E/drRptfHmBhJ7qplujGg==",
+ "cUxyZvoqXbQ0a/0I9s6Zbg==",
+ "womzqSigwEF30V422YmxKw==",
+ "FPvZqDfN8dTFHLVOuYEbUA==",
+ "YZMXx+scKXp/v9GaJjb1bA==",
+ "bjURu5MRsNIZavG5HV0eZw==",
+ "iY0C9uSMEOn8ikT+J7+/Eg==",
+ "aXkD6BzsdkMEv7A+eYqQQQ==",
+ "dOcOfEDGHYG2kgmrglDkPw==",
+ "c7GjtY05Mh+cp6SNuWY3Ig==",
+ "lM1uY1oVncHXNzKs/cCEtQ==",
+ "7jXnQJkutLsi+r9aYmrMxw==",
+ "NgrugWWduj2qdWnEQf9dLA==",
+ "faYjmy/yn5iXdS28QCIdWw==",
+ "68XbaOvIZpCGb4G1gaKErA==",
+ "Yi67HkOLtGYXeL7WD4GPrA==",
+ "Puo8gXuUkwcoQViaXwkdSQ==",
+ "L202Et5aZh60Vl20LTKNFg==",
+ "4agAzQ5+dnTmLZEjsZs26g==",
+ "LegGM1ft8Y7Ka3CUxpObvg==",
+ "KRdILc1QDOpow5im/qY+Kw==",
+ "peMW+rpwmXrSwplVuB/gTA==",
+ "Pic1ncr+Zn6wv75zjAdzQA==",
+ "ilSPlWYbiPzIC13vQUBlOw==",
+ "GUlDufLoTalBqrG/h3mZ6w==",
+ "5twANNlT57T9BG4r2D9+Hw==",
+ "ENrnM8HlMi+5y8Hsu4Pn4A==",
+ "K/DzpLEbz1MpRjA6qyYn4Q==",
+ "yN1cHJRHDXoFxFZacL6wsw==",
+ "Rc6r+KqIePH+dnj1aNYCsQ==",
+ "8u/z5htgqXVU5Dqwd9whJQ==",
+ "jV575O42EYoqDNxCm9643Q==",
+ "xCxGo0h3lS8N6X+ivKfpjA==",
+ "us+2nfpj2gjI7s14Hw0gmA==",
+ "bp90A/rbESwVU7eh9xRTfQ==",
+ "5QtMXzbTafvKDQOWZP7M8w==",
+ "1gFCxPLjQlQGKmSGmHwmJQ==",
+ "m+/dnOIe6SaIFhfvg+ybDg==",
+ "9Dcg87+RPq9U+swRg4dH3Q==",
+ "mnjbL7WFmrWp0RUqS8AMGA==",
+ "/0e0E+NFmq8GeE5+y2Gekw==",
+ "y11mbpHHtka9Ep8cr2nEvQ==",
+ "GdmjRyliw+W21Q+dHO4CWA==",
+ "X65wWQTpkg756V/Nfn92kQ==",
+ "xj06KvacQOxRSofbhzBNgA==",
+ "nVDxVhaa2o38gd1XJgE3aw==",
+ "4IV+JOGXrltpkQamBRXMgA==",
+ "jIfp8LqaYXT88r/K3a8gNw==",
+ "vhT4dDtbMFVyevS6yCGy0g==",
+ "zMs7/x8hDt8xj2FFc5+6vA==",
+ "1J7u2N62JGb2VrnCRlJIrw==",
+ "3hJs9P/RRxB0CO4q0Icb+g==",
+ "ZpuVY1ZyoKD3hqosdsfT6Q==",
+ "6KIM7C7eWgxZtqZboiJvZQ==",
+ "vtb6fdqirkuUkqITAmXTlw==",
+ "C/rEVr22mw2u/1dwUx9VTg==",
+ "NrY4Q5C67haCWLK8HXHq9g==",
+ "X9qvEftCEFWX3gBU5hXy+Q==",
+ "0zgw7xNB3xVGaH48TyxaNQ==",
+ "g7J9Jy/PJrAGRgVdvA+bEg==",
+ "9zb+anAyZVBzuU9rW4cJtg==",
+ "6Zc5FzT/m0YIjxEPYA6zDQ==",
+ "R2YPNlvCbVK0EodTR7czIw==",
+ "gsI6EGgXMtDu+1u364A8mw==",
+ "Bg2wBFb1/xaxeEiHfBHX+A==",
+ "64aapfVI6dV3LpTK56KZlg==",
+ "HdgcJU0W3yVnH69VYStmug==",
+ "qTDCcv+LK3JPFB/++t66IQ==",
+ "P0HEIXMnAmbvq+QYREwFzw==",
+ "aaU7CAmtyE35jNKTkyXOkg==",
+ "r9G97WKDiQ48qJHP9LBRNg==",
+ "8mPgQhYVDn8KshDDvvf5SA==",
+ "GCQiiOLDguXLiYwuLcFPsA==",
+ "R2Use39If2C0FVBP7KDerA==",
+ "23C4eh3yBb5n/RNZeTyJkA==",
+ "2QQtKtBAm2AjJ5c0WQ6BQA==",
+ "Qc+XYy2qyWJ5VVwd2PExbw==",
+ "zJ7ScHNxr2leCDNNcuDApA==",
+ "vFtC0B2oe1gck28JOM1dyg==",
+ "bLEntCrCHFy9pg3T3gbBzg==",
+ "G3PmmPGHaWHpPW30xQgm3Q==",
+ "me61ST+JrXM5k3/a11gRAA==",
+ "+LJYVZl1iPrdMU3L5+nxZw==",
+ "CLPzjXKGGpJ0VrkSJp7wPQ==",
+ "Pc+u0MAzp4lndTz4m6oQ5w==",
+ "cwBNvZc0u4bGABo88YUsVQ==",
+ "q7m/EtZySBjZNBjQ5m1hKw==",
+ "8ZBiwr842ZMKphlqmNngHw==",
+ "LMCZqd3UoF/kHHwzTdj7Tw==",
+ "0ODJyWKJSfObo+FNdRQkkA==",
+ "ViweSJuNWbx5Lc49ETEs/A==",
+ "x+8rwkqKCv0juoT5m1A4eg==",
+ "pxuSWn1u+bHtRjyh2Z8veA==",
+ "GKzs8mlnQQc58CyOBTlfIg==",
+ "Owg8qCpjZa+PmbhZew6/sw==",
+ "YLz+HA6qIneP+4naavq44Q==",
+ "9ajIS45NTicqRANzRhDWFA==",
+ "DjeSrUoWW2QAZOAybeLGJg==",
+ "qxALQrqHoDq9d91nU0DckA==",
+ "yPIeWcW8+3HjDagegrN8bw==",
+ "ocpLRASvTgqfkY20YlVFHQ==",
+ "RuLeQHP1wHsxhdmYMcgtrQ==",
+ "3WwITQML938W9+MUM56a3A==",
+ "ZbLVNTQSVZQWTNgC4ZGfQg==",
+ "X6Ln4si8G5aKar52ZH/FEQ==",
+ "+gbitI/gpxebN/rK7qj8Fw==",
+ "7cnUHeaPO8txZGGWHL9tKg==",
+ "epY+dsm5EMoXnZCnO4WSHw==",
+ "nf8x+F03kOpMhsCSUWEhVg==",
+ "VE4sLM5bKlLdk85sslxiLQ==",
+ "Hs3vUOOs2TWQdQZHs+FaQQ==",
+ "hkOBNoHbno2iNR7t3/d4vg==",
+ "Ar9N1VYgE7riwmcrM3bA2Q==",
+ "SbMjjI8/P8B9a9H2G0wHEQ==",
+ "tU31r8zla146sqczdKXufg==",
+ "tFmWYH82I3zb+ymk5dhepA==",
+ "XHjrTLXkm/bBY/BewmJcCQ==",
+ "FV/D5uSco+Iz8L+5t7E8SA==",
+ "yKLLiqzxfrCsr6+Rm6kx1Q==",
+ "B6reUwMkQFaCHb9BYZExpw==",
+ "5jyuDp82Fux+B0+zlx8EXw==",
+ "WGKFTWJac8uehn3N59yHJw==",
+ "JQf9UmutPh3tAnu7FDk3nA==",
+ "hv5GrLEIjPb4bGOi8RSO0w==",
+ "p3V7NfveB6cNxFW7+XQNeQ==",
+ "DinJuuBX9OKsK5fUtcaTcQ==",
+ "UEMwF4kwgIGxGT4jrBhMPQ==",
+ "Y78dviyBS3Jq9zoRD5sZtQ==",
+ "zbjXhZaeyMfdTb2zxvmRMg==",
+ "kydoXVaNcx1peR5g6i588g==",
+ "M2suCoFHJ5fh9oKEpUG3xA==",
+ "/VnKh/NDv7y/bfO6CWsLaQ==",
+ "S+b37XhKRm8cDwRb1gSsKQ==",
+ "jz7QlwxCIzysP39Cgro8jg==",
+ "IjmLaf3stWDAwvjzNbJpQA==",
+ "cHSj5dpQ04h/WyefjABfmQ==",
+ "+gO0bg8LY+py2dLM1sM7Ag==",
+ "fSANOaHD0Koaqg7AoieY9A==",
+ "vqYHQ3MnHrAIAr1QHwfIag==",
+ "Uh1mvZNGehK1AaI4a1auKQ==",
+ "HCbHUfsTDl6+bxPjT57lrA==",
+ "S7Vjy/gOWp0HozPP1RUOZw==",
+ "KPh6TwYpspne4KZA6NyMbw==",
+ "cfh5VZFmIqJH/bKboDvtlA==",
+ "H1zH9I8RwfEy5DGz3z+dHw==",
+ "2ksediOVrh4asSBxKcudTg==",
+ "+jVN/3ASc2O44sX6ab8/cg==",
+ "uvKYnKE01D5r7kR9UQyo5A==",
+ "BB9PTlwKAWkExt3kKC/Wog==",
+ "yqQPU4jT9XvRABZgNQXjgg==",
+ "6v3eTZtPYBfKFSjfOo2UaA==",
+ "49z/15Nx9Og7dN9ebVqIzg==",
+ "VjclDY8HN4fSpB263jsEiQ==",
+ "vSKsa0JhLCe9QFZKkcj58Q==",
+ "PolhKCedOsplEcaX4hQ0YQ==",
+ "D0Qt9sRlMaPnOv1xaq+XUg==",
+ "gBgJF0PiGEfcUnXF0RO7/w==",
+ "sC11Rf/mau3FG5SnON4+vQ==",
+ "rKb3TBM4EPx/RErFOFVCnQ==",
+ "+n0K7OB2ItzhySZ4rhUrMg==",
+ "Epm0d/DvXkOFeM4hoPCBrg==",
+ "K8PVQhEJCEH1ghwOdztjRw==",
+ "xjA21QjNdThLW3VV7SCnrg==",
+ "nE72uQToQFVLOzcu/nMjww==",
+ "2Hc5oyl0AYRy2VzcDKy+VA==",
+ "Y7XpxIwsGK3Lm/7jX/rRmg==",
+ "MK7AqlJIGqK2+K5mCvMXRQ==",
+ "mXycPfF5zOvcj1p4hnikWw==",
+ "V1fvtnJ0L3sluj9nI5KzRw==",
+ "TahqPgS7kEg+y6Df0HBASw==",
+ "EKU3OVlT4b/8j3MTBqpMNg==",
+ "EdvIAKdRAXj7e42mMlFOGQ==",
+ "uPm+cF4Jq08S5pQhYFjU8A==",
+ "CnIwpRVC2URVfoiymnsdYQ==",
+ "wyx5mnUMgP5wjykjAfTO7w==",
+ "OwIGvTh8FPFqa4ijNkguAw==",
+ "4ID0PHTzIMZz2rQqDGBVfA==",
+ "rlXt6zKE7DswUl0oWGOQUQ==",
+ "4NP8EFFJyPcuQKnBSxzKgQ==",
+ "bJgsuw29cO2WozqsGZxl7w==",
+ "b3q8kjHJPj9DWrz3yNgwjQ==",
+ "QGYFMpkv37CS2wmyp42ppg==",
+ "Kzs+/IZJO8v4uIv9mlyJ2Q==",
+ "ZJY+hujfd58mTKTdsmHoQQ==",
+ "R8FxgXWKBpEVbnl41+tWEw==",
+ "+CvLiih/gf2ugXAF+LgWqw==",
+ "BDbfe/xa9Mz1lVD82ZYRGA==",
+ "Dz90OhYEjpaJ/pxwg1Qxhg==",
+ "MLHt6Ak288G0RGhCVaOeqA==",
+ "r0QffVKB9OD9yGsOtqzlhA==",
+ "hK8KhTFcR06onlIJjTji/Q==",
+ "wMum67lfk5E1ohUObJgrOg==",
+ "JKmZqz9cUnj6eTsWnFaB0A==",
+ "rtJdfki8fG6CB36CADp0QA==",
+ "cUyqCa7Oue934riyC17F8g==",
+ "y4Y4mSSTw/WrIdRpktc5Hw==",
+ "r36kVMpF+9J+sfI3GeGqow==",
+ "ydVj2odhergi+2zGUwK4/A==",
+ "J2NFyb8cXEpZyxWDthYQiA==",
+ "qYuo5vY8V3tZx41Kh9/4Dw==",
+ "jrfRznO0nAz6tZM1mHOKIA==",
+ "JSr/lqDej81xqUvd/O2s7w==",
+ "vHGjRRSlZHJIliCwIkCAmQ==",
+ "sQAxqWXeiu/Su0pnnXgI9A==",
+ "xPe76nHyHmald6kmMQsKdg==",
+ "50jASqzGm4VyHJbFv8qVRA==",
+ "uuiJ+yB7JLDh2ulthM0mjg==",
+ "TI90EuS/bHq/CAlX32UFXg==",
+ "JgxNrUlL8wutG04ogKFPvw==",
+ "aMa1yVA71/w6Uf1Szc9rMA==",
+ "k/Aou2Jmyh8Bu3k8/+ndsQ==",
+ "iANKiuMqWzrHSk9nbPe3bQ==",
+ "7GgNLBppgAKcgJCDSsRqOQ==",
+ "bzVeU2qM9zHuzf7cVIsSZw==",
+ "rkeLYwMZ1/pW2EmIibALfA==",
+ "91+Yms6Oy/rP0rVjha5z9w==",
+ "JgXSPXDqaS1G9NqmJXZG0A==",
+ "ZzduJxTnXLD9EPKMn1LI4Q==",
+ "6W79FmpUN1ByNtv5IEXY4w==",
+ "Y1Nm3omeWX2MXaCjDDYnWQ==",
+ "ejfikwrSPMqEHjZAk3DMkA==",
+ "WNfDNaWUOqABQ6c6kR+eyw==",
+ "4BkqgraeXY7yaI1FE07Evw==",
+ "AjHz9GkRTFPjrqBokCDzFw==",
+ "T/6gSz2HwWJDFIVrmcm8Ug==",
+ "VWy9lB5t4fNCp4O/4n8S4w==",
+ "/FdZzSprPnNDPwbhV1C0Cg==",
+ "LUWxfy4lfgB5wUrqCOUisw==",
+ "r1VGXWeqGeGbfKjigaAS+Q==",
+ "ztULoqHvCOE6qV7ocqa4/w==",
+ "QCpzCTReHxGm5lcLsgwPCA==",
+ "Hst3yfyTB7yBUinvVzYROQ==",
+ "gf1Ypna/Tt+TZ08Y+GcvGg==",
+ "3rbml1D0gfXnwOs5jRZ3gA==",
+ "2vm7g3rk1ACJOTCXkLB3zA==",
+ "11FE2kknwYi2Qu0JUKMn3A==",
+ "1b2uf+CdVjufqiVpUShvHw==",
+ "0a4SafpDIe8V4FlFWYkMHw==",
+ "7btpMFgeGkUsiTtsmNxGQA==",
+ "dUx1REyXKiDFAABooqrKEA==",
+ "knYKU74onR6NkGVjQLezZg==",
+ "Scto+9TWxj1eZgvNKo+a9A==",
+ "cvZT1pvNbIL8TWg+SoTZdA==",
+ "1nXByug2eKq0kR3H3VjnWQ==",
+ "tG+rpfJBXlyGXxTmkceiKA==",
+ "7W9aF7dxnL+E8lbS/F7brg==",
+ "8vr+ERVrM99dp+IGnCWDGQ==",
+ "oFNMOKbQXcydxnp8fUNOHw==",
+ "uJZGw3IY2nCcdVeWW1geNQ==",
+ "q6LG0VzO1oxiogAAU63hyg==",
+ "f0H/AFSx2KLZi9kVx5BAZg==",
+ "1RQZ2pWSxT+RKyhBigtSFg==",
+ "scCQPl0em2Zmv/RQYar60g==",
+ "A2ODff+ImIkreJtDPUVrlg==",
+ "vRgkZZGVN7YZrlml0vxrKA==",
+ "68jPYo3znYoU4uWI7FH3/g==",
+ "iJ2nT8w8LuK11IXYqBK+YA==",
+ "54XELlPm8gBvx8D5bN3aUg==",
+ "PTAm/jGkie7OlgVOvPKpaA==",
+ "v7BrkRmK0FfWSHunTRHQFQ==",
+ "dVh/XMTUIx1nYN4q1iH1bA==",
+ "TSGL3iQYUgVg/O9SBKP9EA==",
+ "wTO49YX/ePHMWtcoxUAHpw==",
+ "bMb1ia0rElr2ZpZVhva0Jw==",
+ "sNmW2b2Ud7dZi3qOF8O8EQ==",
+ "3djRJvkZk9O2bZeUTe+7xQ==",
+ "I9KNZC1tijiG1T72C4cVqQ==",
+ "sQzCwNDlRsSH7iB9cTbBcg==",
+ "mk1CKDah7EzDJEdhL22B7w==",
+ "lON3WM0uMJ30F8poBMvAjQ==",
+ "88PNi9+yn3Bp4/upgxtWGA==",
+ "C+Ssp+v1r+00+qiTy2d7kA==",
+ "11U5XEwfMI7avx014LfC8g==",
+ "xsf0m31Am0W9eLhopAkfnA==",
+ "d13Rj3NJdcat0K/kxlHLFw==",
+ "UP7NXAE0uxHRXUAWPhto0w==",
+ "ZKXxq9yr7NGBOHidht34uQ==",
+ "Fd2fYFs8vtjws2kx1gf6Rw==",
+ "ojf6uL85EuEYgLvHoGhUrw==",
+ "KjnL3x+56r3M2pDj1pPihA==",
+ "WdCWezJU4JK43EOZ9YHVdg==",
+ "/jH6imhTPZ/tHI4gYz2+HA==",
+ "+OLntmlsMBBYPREPnS6iVw==",
+ "5lfLJAk1L3QzGMML3fOuSw==",
+ "AZs3v4KJYxdi8T1gjVjI2Q==",
+ "7pkUY2UzSbGnwLvyRrbxfA==",
+ "BjfOelfc1IBgmUxMJFjlbQ==",
+ "TcGhAJHRr7eMwGeFgpFBhg==",
+ "Y7iDCWYrO1coopM3RZWIPg==",
+ "mnalaO6xJucSiZ0+99r3Cg==",
+ "plXHHzA8X9QGwWzlJxhLRw==",
+ "Zqd6+81TwYuiIgLrToFOTQ==",
+ "1Pmnur6TbZ9cmemvu0+dSA==",
+ "OaNpzwshdHUZMphQXa6i8w==",
+ "WKehT4nGF2T7aKuzABDMlA==",
+ "4LvQSicqsgxQFWauqlcEjw==",
+ "BMZB1FwvAuEqyrd0rZrEzw==",
+ "YfbfE3WyYOW7083Y8sGfwQ==",
+ "46FCwqh+eMkf+czjhjworw==",
+ "734u4Y1R3u7UNUnD+wWUoA==",
+ "yf06Slv9l3IZEjVqvxP2aA==",
+ "bIk7Fa6SW7X18hfDjTKowg==",
+ "DnF6TYSJxlc+cwdfevLYng==",
+ "ionqS0piAOY2LeSReAz4zg==",
+ "hlMumZ7RJFpILuKs09ABtw==",
+ "NjeDgQ1nzH1XGRnLNqCmSg==",
+ "o7y4zQXQAryST2cak4gVbw==",
+ "29EybnMEO95Ng4l/qK4NWQ==",
+ "udU65VtsvJspYmamiOsgXw==",
+ "v1AWe5qb5y3vSKFb7ADeEw==",
+ "wK6Srd83eLigZ11Q20XGrg==",
+ "GmC+0rNDMIR+YbUudoNUXw==",
+ "W4utAK3ws0zjiba/3i91YA==",
+ "MlKWxeEh8404vXenBLq4bw==",
+ "Gdf4VEDLBrKJNQ8qzDsIyw==",
+ "Z9bDWIgcq6XwMoU2ECDR5Q==",
+ "VIkS30v268x+M1GCcq/A8A==",
+ "iPwX3SbbG9ez9HoHsrHbKw==",
+ "yKrsKX4/1B1C0TyvciNz5w==",
+ "BophnnMszW5o+ywgb+3Qbw==",
+ "eJLrGwPRa6NgWiOrw1pA7w==",
+ "eV+RwWPiGEB+76bqvw+hbA==",
+ "oad5SwflzN0vfNcyEyF4EA==",
+ "Uw6Iw+TP9ZdZGm2b/DAmkg==",
+ "9qWLbRLXWIBJUXYjYhY2pg==",
+ "dxWv00FN/2Cgmgq9U3NVDQ==",
+ "AX1HxQKXD12Yv5HWi39aPQ==",
+ "J0NauydfKsACUUEpMhQg8A==",
+ "mxug34EekabLz0JynutfBg==",
+ "bNq/hj0Cjt4lkLQeVxDVdQ==",
+ "nW3zZshjZEoM8KVJoVfnuQ==",
+ "ghp8sWGKWw20S/z1tbTxFg==",
+ "S4rFuiKLFKZ+cL7ldiTwpg==",
+ "8ZqmPJDnQSOFXvNMRQYG2Q==",
+ "6XYqR2WvDzx4fWO7BIOTjA==",
+ "Uo+FIhw1mfjF6/M8cE1c/Q==",
+ "bsHIShcLS134C+dTxFQHyA==",
+ "19yQHaBemtlgo2QkU5M6jQ==",
+ "sWLcS+m4aWk31BiBF+vfJQ==",
+ "BlCgDd7EYDIqnoAiKOXX6Q==",
+ "MrxR3cJaDHp0t3jQNThEyg==",
+ "cMo6l1EQESx1rIo+R4Vogg==",
+ "VOvrzqiZ1EHw+ZzzTWtpsw==",
+ "1/ZheMsbojazxt31j/l3iA==",
+ "0QxPAqRF8inBuFEEzNmLjA==",
+ "UXUNYEOffgW3AdBs7zTMFA==",
+ "lOPJhHqCtMRFZfWMX/vFZQ==",
+ "rXSbbRABEf4Ymtda45w8Fw==",
+ "jfegbZSZWkDoPulFomVntA==",
+ "hfcH5Az2M7rp+EjtVpPwsg==",
+ "VsXEBIaMkVftkxt1kIh7TA==",
+ "M20iX2sUfw5SXaZLZYlTaA==",
+ "VUDsc9RMS1fSM43c+Jo9dQ==",
+ "itPtn+JaO4i7wz2wOPOmDQ==",
+ "rCxoo4TP/+fupXMuIM0sDA==",
+ "cSHSg9xJz/3F6kc+hKXkwg==",
+ "b4BoZmzVErvuynxirLxn0w==",
+ "e4B3HmWjW+6hQzcOLru6Xg==",
+ "lTE6u9G/RzvmbuAzq2J2/Q==",
+ "897ptlztTjr7yk+pk8MT0Q==",
+ "jd6IpPJwOJW1otHKtKZ5Gw==",
+ "b4aFwwcWMXsSdgS1AdFOXA==",
+ "FltEN+7NKvzt+XAktHpfHA==",
+ "ZyDh3vCQWzS5DI1zSasXWA==",
+ "kcJ1acgBv6FtUhV8KuWoow==",
+ "zgEyxj/sCs63O98sZS94Yw==",
+ "/kGxvyEokQsVz0xlKzCn2A==",
+ "cxqHS4UbPolcYUwMMzgoOA==",
+ "62RHCbpGU8Hb+Ubn+SCTBg==",
+ "ePlsM/iOMme2jEUYwi15ng==",
+ "0fN+eHlbRS6mVZBbH/B9FQ==",
+ "k0XIjxp2vFG7sTrKcfAihA==",
+ "0rfG4gRugAwVP0i3AGVxxg==",
+ "M98hjSxCwvZ27aBaJTGozQ==",
+ "kzGNkWh3fz27cZer4BspUQ==",
+ "3CJbrUdW68E3Drhe4ahUnQ==",
+ "NGApiVkDSwzO45GT57GDQw==",
+ "lMjip5hbCjkD9JQjuhewDg==",
+ "GrSbnecYAC3j5gtoKntL0A==",
+ "9dbn0Kzwr9adCEfBJh78uQ==",
+ "64QzHOYX0A9++FqRzZRHlQ==",
+ "YZt6HwCvdI5DRQqndA/hBQ==",
+ "6GXHGF62/+jZ7PfIBlMxZw==",
+ "PBULPuFXb6V3Di713n3Gug==",
+ "8Cm19vJW8ivhFPy0oQXVNA==",
+ "zDSQ3NJuUGkVOlvVCATRwA==",
+ "6QAtjOK9enNLRhcVa2iaTg==",
+ "v/PshI6JjkL9nojLlMNfhg==",
+ "yTgN5xFIdz1MzFS6xMl5uQ==",
+ "SCO9nQncEcyVXGCtx30Jdg==",
+ "7b0oo4+qphu6HRvJq6qkHQ==",
+ "ol9xhVTG9e1wNo50JdZbOA==",
+ "hIABph+vhtSF5kkZQtOCTA==",
+ "k+IBS52XdOe5/hLp28ufnA==",
+ "6HnWgYNKohqhoa1tnjjU3A==",
+ "HDxGhvdQwGh0aLRYEGFqnw==",
+ "LDuBcL5r3PUuzKKZ9x6Kfw==",
+ "HPvYV94ufwiNHEImu4OYvQ==",
+ "h2cnQQF2/R3Mq2hWdDdrTg==",
+ "nqpKfidczdgrNaAyPi7BOQ==",
+ "2ywo4t5PPSVUCWDwUlOVwQ==",
+ "jZMDIu95ITTjaUX0pk4V5g==",
+ "bA2kaTpeXflTElTnQRp6GQ==",
+ "lwYQm2ynA3ik2gE1m11IEg==",
+ "5ugVOraop5P5z5XLlYPJyQ==",
+ "l2NppPcweAtmA1V2CNdk2Q==",
+ "DbWQI3H2tcJsVJThszfHGA==",
+ "H6HPFAcdHFbQUNrYnB74dA==",
+ "H1NJEI+fvOQbI51kaNQQjQ==",
+ "53UccFNzMi9mKmdeD82vAw==",
+ "lffapwUUgaQOIqLz2QPbAg==",
+ "rSvhrHyIlnIBlfNJqemEbw==",
+ "BLJk9wA88z6e0IQNrWJIVw==",
+ "5m1ijXEW+4RTNGZsDA/rxQ==",
+ "GG8a3BlwGrYIwZH9j3cnPA==",
+ "HhBHt5lQauNl7EZXpsDHJA==",
+ "/XjB6c5fxFGcKVAQ4o+OMw==",
+ "+tuUmnRDRWVLA+1k0dcUvg==",
+ "SM7E98MyViSSS9G0Pwzwyw==",
+ "c5q/8n7Oeffv3B1snHM/lA==",
+ "kwlAQhR2jPMmfLTAwcmoxw==",
+ "0b/xj6fd0x+aB8EB0LC4SA==",
+ "S8jlvuYuankCnvIvMVMzmg==",
+ "kZkmDatUOdIqs7GzH3nI1A==",
+ "obW3kzv2KBvuckU7F+tfjA==",
+ "pa8nkpAAzDKUldWjIvYMYg==",
+ "m+eh+ZqS74w2q0vejBkjaw==",
+ "LcoJBEPTlSsQwfuoKQUxEw==",
+ "KO2XVYyNZadcQv8aCNn5JA==",
+ "uvzmRcvgepW6mZbMfYgcNw==",
+ "KhUT2buOXavGCpcDOcbOYg==",
+ "fo3JL+2kPgDWfP+CCrFlFw==",
+ "wIfvvLKC61gOpsddUFjVog==",
+ "SPHU6ES1WVm0Mu2LB+YjrA==",
+ "LWWfRqgtph1XrpxF4N64TA==",
+ "LCvz/h9hbouXCmdWDPGWqg==",
+ "PXC6ZpdMH0ATis/jGW12iA==",
+ "z920R8eahJPiTsifrPYdxA==",
+ "GIHKW6plyLra0BmMOurFgA==",
+ "k6OmSlaSZ5CB0i7SD9LczQ==",
+ "YZ39RIXpeLAhyMgmW2vfkQ==",
+ "bs2QG8yYWxPzhtyMqO6u3A==",
+ "pKaTI+TfcV3p/sxbd2e7YQ==",
+ "xWYecfzAtXT9WyQ8NYY/hw==",
+ "Fz8EI+ZpYlbcttSHs5PfpA==",
+ "wfwuxn+Vja1DNwiDwL2pcQ==",
+ "wux5Y8AipBnc5tJapTzgEQ==",
+ "U+oTpcjhc0E+6UjP11OE/Q==",
+ "yTVJKBn72RjakMBXDoBKHg==",
+ "0TxcYwG72dT7Tg+eG8pP1w==",
+ "imZ+mwiT22sW2M9alcUFfg==",
+ "CkDIoAFLlIRXra78bxT/ZA==",
+ "4qMSNAxichi3ori/pR+o0w==",
+ "zNLlWGW/aKBhUwQZ4DZWoQ==",
+ "D31ZticrjGWAO45l5hFh7A==",
+ "HdXg64DBy5WcL5fRRiUVOg==",
+ "yhI5jHlfFJxu4eV5VJO2zQ==",
+ "e9GqAEnk8XI5ix6kJuieNQ==",
+ "EC0+iUdSZvmIEzipXgj7Gg==",
+ "chwv4+xbEAa93PHg8q9zgQ==",
+ "B1VVUbl8pU0Phyl1RYrmBg==",
+ "A+DLpIlYyCb9DaarpLN76g==",
+ "wHA+D5cObfV3kGORCdEknw==",
+ "+Mp+JIyO0XC5urvMyi3wvQ==",
+ "vUE8Iw3NyWXURpXyoNJdaw==",
+ "ParhxI6RtLETBSwB0vwChQ==",
+ "NxSdT2+MUkQN49pyNO2bJw==",
+ "JSyhTcHLTfzHsPrxJyiVrA==",
+ "PAlx9+U+yQCAc5Fi0BOG0w==",
+ "W/0s1x3Qm+wN8DhROk6FrQ==",
+ "L3Jt5dHQpWQk74IAuDOL8g==",
+ "VWb8U4jF/Ic0+wpoXi/y/g==",
+ "1wBuHqS1ciup31WTfm3NPg==",
+ "BDNM1u/9mefjuW1YM2DuBg==",
+ "SDi5+FoP9bMyKYp+vVv1XA==",
+ "23d9B9Gz5kUOi1I//EYsSQ==",
+ "/a9O7kWeXa0le45ab3+nVw==",
+ "PcoVtZrS1x1Q+6nfm4f80w==",
+ "A6TLWhipfymkjPYq8kaoDQ==",
+ "lzUQ1o7JAbdJYpmEqi6KnQ==",
+ "/2jGyMekNu7U136K+2N3Jg==",
+ "ZItMIn1vhGqAlpDHclg0Ig==",
+ "Ee4A3lTMLQ7iDQ7b8QP8Qg==",
+ "bO55S58bqDiRWXSAIUGJKw==",
+ "zeHF6fdeqcOId3fRUGscRw==",
+ "BxsDnI8jXr4lBwDbyHaYXw==",
+ "ylA6sU7Kaf9fMNIx1+sIlw==",
+ "ZWXfE3uGU91WpPMGyknmqw==",
+ "f1+fHgR5rDPsCZOzqrHM7Q==",
+ "8VqeoQELbCs232+Mu+HblA==",
+ "beSrliUu0BOadCWmx+yZyA==",
+ "NQVQfN3nIg9ipHiFh4BvfQ==",
+ "4wnUAbPT3AHRJrPwTTEjyw==",
+ "/cdR1i5TuQvO+u3Ov3b0KQ==",
+ "wtyAZIfhomcHe9dLbYoSvA==",
+ "ulpDxLeQnIRPnq6oaah2AA==",
+ "pdPwUHauXOowaq9hpL2yFw==",
+ "1+A9FCGP3bZhk6gU3LQtNg==",
+ "raYifKqev8pASjjuV+UTKQ==",
+ "+OERSmo7OQUUjudkccSMOA==",
+ "FeRovookFQIsXmHXUJhGOw==",
+ "USCvrMEm/Wqeu9oX6FrgcQ==",
+ "kly/2kE4/7ffbO34WTgoGg==",
+ "IindlAnepkazs5DssBCPhA==",
+ "Bq82MoMcDjIo/exqd/6UoA==",
+ "ocvA1/NbyxM0hanwwY6EiA==",
+ "rtd6mqFgGe98mqO0pFGbSw==",
+ "nvLEpj6ZZF3LWH3wUB6lKg==",
+ "AGd0rcLnQ0n+meYyJur1Pw==",
+ "wI7JrSPQwYHpv2lRsQu9nQ==",
+ "OnmvXbyT2BYsSDJYZhLScA==",
+ "CmBf5qchS1V3C2mS6Rl4bw==",
+ "TafM7nTE5d+tBpRCsb8TjQ==",
+ "wxkb8evGEaGf/rg/1XUWiA==",
+ "y1J+o6DC2sETFsySgpDZyA==",
+ "SVLHWPCCH7GPVCF7QApPbw==",
+ "HMWOlMmzocOIiJ7yG1YaDQ==",
+ "DJmrmNRKARzsTCKSMLmcNA==",
+ "/XC/FmMIOdhMTPqmy4DfUA==",
+ "63OTPaKM0xCfJOy9EDto+Q==",
+ "PxReytUUn/BbxYTFMu1r2Q==",
+ "WjDqf1LyFyhdd8qkwWk+MA==",
+ "/DiUApY7cVp5W9o24rkgRA==",
+ "alJtvTAD7dH/zss/Ek1DMQ==",
+ "xLm/bJBonpTs0PwsF0DvRg==",
+ "eAOEgF5N80A/oDVnlZYRAw==",
+ "LqgzKxbI6WTMz0AMIDJR5w==",
+ "MJ1FuK8PXcmnBAG9meU84A==",
+ "JLq/DrW2f26NaRwfpDXIEA==",
+ "fsrX00onlGvfsuiCc35pGg==",
+ "tXVb5f90k9l3e1oK2NGXog==",
+ "1JRgSHnfAQFQtSkFTttkqQ==",
+ "B0TaUQ6dKhPfSc5V/MjLEQ==",
+ "nkbLVLvh3ClKED97+nH+7Q==",
+ "avFTp3rS6z5zxQUZQuaBHQ==",
+ "lNF8PvUIN02NattcGi5u4g==",
+ "bBEndaOStXBpAK79FrgHaw==",
+ "dM9up4vKQV5LeX82j//1jQ==",
+ "4WO6eT0Rh6sokb29zSJQnQ==",
+ "RHKCMAqrPjvUYt13BVcmvw==",
+ "Ju4YwtPw+MKzpbC0wJsZow==",
+ "tzV7ixFH37ze4zuLILTlfA==",
+ "oPlhC4ebXdkIDazeMSn1fQ==",
+ "5pje7qyz8BRsa8U4a4rmoA==",
+ "7E6V6/zSjbtqraG7Umj+Jw==",
+ "8QK7emHS6rAcAF5QQemW/A==",
+ "LhqRc9oewY4XaaXTcnXIHQ==",
+ "p/7qM5+Lwzw1/lIPY91YxQ==",
+ "fy54Milpa7KZH/zgrDmMXQ==",
+ "LyPXOoOPMieqINtX8C9Zag==",
+ "aD4QvtMlr8Lk/zZgZ6zIMg==",
+ "dsueq9eygFXILDC7ZpamuA==",
+ "+mJLK+6qq8xFv7O/mbILTw==",
+ "nHUpYmfV59fe3RWaXhPs3Q==",
+ "VbCoGr8apEcN7xfdaVwVXw==",
+ "/2Chaw2M9DzsadFFkCu6WQ==",
+ "rKAQxu80Q8g1EEhW5Wh8tg==",
+ "RJJqFMeiCZHdsqs72J17MQ==",
+ "GF2yvI9UWf1WY7V7HXmKPA==",
+ "JyIDGL1m/w+pQDOyyeYupA==",
+ "wR2Gxb07nkaPcZHlEjr8iA==",
+ "PbDVq2Iw1eeM8c2o/XYdTA==",
+ "BL3buzSCV78rCXNEhUhuKQ==",
+ "i42XumprV/aDT5R0HcmfIQ==",
+ "DuEKxykezAvyaFO2/5ZmKQ==",
+ "6ACvJNfryPSjGOK39ov8Qg==",
+ "YaUKOTyByjUvp1XaoLiW5Q==",
+ "jNcMS2zX1iSZN9uYnb2EIg==",
+ "VRnx+kd6VdxChwsfbo1oeQ==",
+ "4Qinl7cWmVeLJgah8bcNkw==",
+ "Fiy3hkcGZQjNKSQP9vRqyA==",
+ "HaSc7MZphCMysTy2JbTJkw==",
+ "VhYGC8KYe5Up+UJ2OTLKUw==",
+ "K2gk9zWGd0lJFRMQ1AjQ/Q==",
+ "NfxVYc3RNWZwzh2RmfXpiA==",
+ "JGeqHRQpf4No74aCs+YTfA==",
+ "7VHlLw20dWck+I8tCEZilA==",
+ "V5HKdaTHjA8IzvHNd9C51g==",
+ "9TalxEyFgy6hFCM73hgb7Q==",
+ "R/y6+JJP8rzz1KITJ4qWBw==",
+ "7bM/pn4G7g7Zl6Xf1r62Lg==",
+ "CHsFJfsvZkPWDXkA6ZMsDQ==",
+ "uXuPA/2KJbb7ZX+NymN3dw==",
+ "o+nYS4TqJc6XOiuUzEpC3A==",
+ "8N3mhHt29FZDHn1P2WH1wQ==",
+ "uZ2gUA74/7Q33tI2TcGQlg==",
+ "8B12CamjOGzJDnQ+RkUf4w==",
+ "9FdpxlIFu11qIPdO7WC5nw==",
+ "G+sGF13VXPH4Ih6XgFEXxg==",
+ "y+1I05LDAYJ09tKMs3zW6g==",
+ "gnkadeCgjdmLdlu/AjBZJg==",
+ "1I+UVx3krrD4NhzO7dgfHQ==",
+ "8LNNoHe6rEQyJ0ebl151Mw==",
+ "yOE90OHQdyOfrAgwDvn2gA==",
+ "ayBGGPEy++biljvGcwIjXA==",
+ "o/Y4U6rWfsUCXJ72p5CUGw==",
+ "5kvyy902llnYGQdn2Py04w==",
+ "6k2cuk0McTThSMW/QRHfjA==",
+ "2XrR2hjDEvx8MQpHk9dnjw==",
+ "fv/PW8oexJYWf5De30fdLQ==",
+ "861mBNvjIkVgkBiocCUj/Q==",
+ "NKGY0ANVZ0gnUtzVx1pKSw==",
+ "4DIPP/yWRgRuFqVeqIyxMQ==",
+ "cgSEbLqqvDsNUyeA3ryJ6Q==",
+ "xbBxUP9JyY0wDgHDipBHeg==",
+ "c3WVxyC5ZFtzGeQlH5Gw+w==",
+ "ZKeTDCboOgCptrjSfgu0xw==",
+ "DjHszpS8Dgocv3oQkW/VZQ==",
+ "Iqszlv4R49UevjGxIPMhIA==",
+ "uChFnF0oCwARhAOz/d47eA==",
+ "0egBaMnAf0CQEXf1pCIKnA==",
+ "FnVNxl5AFH1AieYru2ZG+A==",
+ "2Ct+pLXrK6Ku1f4qehjurQ==",
+ "x2nSgcTjA3oGgI8mMgiqjw==",
+ "AUGmvZkpkKBry5bHZn4DJA==",
+ "x8kRVzohTdhkryvYeMvkMw==",
+ "rXfWkabSPN+23Ei1bdxfmQ==",
+ "ElTNyMR4Rg8ApKrPw88WPg==",
+ "9jxA/t3TQx8dQ+FBsn/YCg==",
+ "I07W2eDQwe6DVsm1zHKM8A==",
+ "0p1jMr06OyBoXQuSLYN4aQ==",
+ "odGhKtO4bDW5R8SYiI5yCg==",
+ "5Q/Y2V0iSVTK8HE8JerEig==",
+ "Ily2MKoFI1zr5LxBy93EmQ==",
+ "8dUcSkd2qnX5lD9B+fUe+Q==",
+ "80UE+Ivby3nwplO/HA7cPw==",
+ "sS6QcitMPdvUBLiMXkWQkw==",
+ "5VY++KiWgo7jXSdFJsPN3A==",
+ "aY6B28XdPnuYnbOy9uSP8A==",
+ "ZfRlID+pC1Rr4IY14jolMw==",
+ "/YuQw7oAF08KDptxJEBS9g==",
+ "16d+fhFlgayu3ttKVV/pbg==",
+ "8dBIsHMEAk7aoArLZKDZtg==",
+ "wRqaDZVHHurp5whOQ1kDbQ==",
+ "lFUq6PGk9dBRtUuiEW7Cug==",
+ "FoJZ61VrU8i084pAuoWhDQ==",
+ "4mig4AMLUw+T/ect9p4CfA==",
+ "Po0lhBfiMaXhl+vYh1D8gA==",
+ "z9cd+Qj+ueX34Zf3997MNQ==",
+ "1dsKN1nG6upj7kKTKuJWsQ==",
+ "UtLYUlQJ02oKcjNR3l+ktg==",
+ "O538ibsrI4gkE5tfwjxjmg==",
+ "G736AX070whraDxChqUrqw==",
+ "THs1r8ZEPChSGrrhrNTlsA==",
+ "pVG1hL96/+hQ+58rJJy6/A==",
+ "1BjsijOzgHt/0i36ZGffoQ==",
+ "6rIWazDEWU5WPZHLkqznuQ==",
+ "cdWUm6uLNzR/knuj2x75eA==",
+ "nsnX3tKkN1elr18E31tXDw==",
+ "0fnruVOCxEczscBuv4yL9A==",
+ "SVuEYfQ9FGyVMo1672n0Yg==",
+ "ZRWyfXyXqAaOEjkzWl949Q==",
+ "S2MAIYeDQeJ1pl9vhtYtUg==",
+ "vsRNZx4thFFFPneubKq1Fw==",
+ "kuWGANwzNRpG4XmY7KjjNg==",
+ "i6r+mZfyhZyqlYv56o0H+w==",
+ "wqWqe0KRjZlUIrGgEOG9Mg==",
+ "t5wh9JGSkQO78QoQoEqvXA==",
+ "AGoVLd0QPcXnTedT5T95JQ==",
+ "aRrcmH+Ud3mF1vEXcpEm4w==",
+ "C65PZm8rZxJ6tTEb6d08Eg==",
+ "oAHVGBSJ2cf4dVnb/KEYmw==",
+ "BuDVDLl0OGdomEcr+73XhQ==",
+ "bLsStF0DDebpO+xulqGNtg==",
+ "xukOAM0QVsA72qEy0yku9A==",
+ "LpoayYsTO8WLFLCSh2kf2w==",
+ "LEVYAE54618FrlXkDN01Kw==",
+ "Jm862vBTCYbv/V4T1t46+Q==",
+ "X4kdXUuhcUqMSduqhfLpxA==",
+ "cLR0Ry4/N5swqga1R6QDMw==",
+ "0klouNfZRHFFpdHi4ZR2hA==",
+ "JGx8sTyvr4bLREIhSqpFkw==",
+ "ZiJ/kJ9GneF3TIEm08lfvQ==",
+ "hP7dSa8lLn9KTE/Z0s4GVQ==",
+ "600bwlyhcy754W1E6tuyYg==",
+ "U49SfOBeqQV9wzsNkboi8Q==",
+ "5DDb7fFJQEb3XTc3YyOTjg==",
+ "6uT7LZiWjLnnqnnSEW4e/Q==",
+ "tq5xUJt8GtjDIh1b48SthQ==",
+ "eJFIQh/TR7JriMzYiTw4Sg==",
+ "jdRzkUJrWxrqoyNH9paHfQ==",
+ "RKVDdE1AkILTFndYWi9wFg==",
+ "AEpTVUQhIEJGlXJB6rS26A==",
+ "PD+yHtJxZJ2XEvjIPIJHsQ==",
+ "dOS+mVCy3rFX9FvpkTxGXA==",
+ "lz+SeifYXxamOLs1FsFmSQ==",
+ "QTz21WkhpPjfK8YoBrpo+w==",
+ "9wUIeSgNN36SFxy8v2unVg==",
+ "ash1r2J6B0PUxJe8P0otVQ==",
+ "y7yS9x3yshVhMpDbQtfYOQ==",
+ "f07bdNVAe9x+cAMdF1bByQ==",
+ "N2KovXW14hN/6+iWa1Yv3g==",
+ "2DNbXVgesUa7PgYQ4zX5Lw==",
+ "WQznrwqvMhUlM3CzmbhAOQ==",
+ "FpWDTLTDmkUhH/Sgo+g1Gg==",
+ "OVHqwV8oQMC5KSMzd5VemA==",
+ "Bv4mNIC72KppYw/nHQxfpQ==",
+ "MI+HSMRh8KTW+Afiaxd/Fw==",
+ "10OltdxPXOvfatJuwPVKbQ==",
+ "y4/HohCJxtt+cT7nLJB08w==",
+ "RhcqXY4OsZlVVF7ZlkTeRw==",
+ "/mrqas0eDX+sFUNJvCQY8g==",
+ "ZIZx4MehWTVXPN9cVQBmyA==",
+ "z20AAnvj7WsfJeOu3vemlA==",
+ "dL6n/JsK+Iq6UTbQuo/GOw==",
+ "rMm9bHK69h0fcMkMdGgeeA==",
+ "ftsf2qztw3NC78ep/CZXWQ==",
+ "/n1RLTTVpygre1dl36PDwQ==",
+ "/FsJYFNe+7UvsSkiotNJEQ==",
+ "Yy2pPhITTmkEwoudXizHqQ==",
+ "lizovLQxu6L9sbafNQuShQ==",
+ "XV5MYe0Q7YMtoBD6/iMdSw==",
+ "5jHgQF4SfO/zy9xy9t+9dw==",
+ "16iT/jCcPDrJEfi2bE5F+Q==",
+ "syeBfQBUmkXNWCZ1GV8xSA==",
+ "sr3UXbMg5zzkRduFx/as7g==",
+ "xUXEE7OBBCudsQnuj5ycOA==",
+ "ojZY7Gi2QJXE/fp6Wy31iA==",
+ "RlNPyhgYOIn28R4vKCVtYA==",
+ "KOm8PTa+ICgDrgK9QxCJZw==",
+ "DJoy1NSZZw87oxWGlNHhfg==",
+ "jEdanvXKyZdZJG6mj/3FWw==",
+ "Omr+zPWVucPCSfkgOzLmSQ==",
+ "71w3aSvuh2mBLtdqJCN3wA==",
+ "xjTMO2mvtpvwQrounD4e8g==",
+ "Zz/5VMbw1TqwazReplvsEg==",
+ "hIjgi20+km+Ks23NJ4VQ6Q==",
+ "00TVKawojyqrJkC7YqT41Q==",
+ "YgVpC5d5V6K/BpOD663yQA==",
+ "wX70jKLKJApHnhyK0r6t3A==",
+ "lacCCRiWdquNm4YRO7FoKA==",
+ "cWdlhVZD7NWHUGte24tMjg==",
+ "t5U+VMsTtlWAAWSW+00SfQ==",
+ "AMfL0rH+g8c0VqOUSgNzQw==",
+ "0G93AxGPVwmr66ZOleM90A==",
+ "9tiibT8V9VwnPOErWGNT3w==",
+ "+dBv88reDrjEz6a2xX3Hzw==",
+ "xX6atcCApI08oVLjjLteLg==",
+ "+YrqTEJlJCv0A2RHQ8tr1A==",
+ "aqcOby9QyEbizPsgO3g0yw==",
+ "s/BZAhh1cTV3JCDUQsV8mA==",
+ "x9VwDdFPp/rJ+SF16ooWYg==",
+ "k/OVIllJvW6BefaLEPq7DA==",
+ "rIMXaCaozDvrdpvpWvyZOQ==",
+ "qQQwJ/aF87BbnLu3okXxaw==",
+ "TIWSM78m0RprwgPGK/e0JA==",
+ "r/b5px/UImGNjT/X5sYjuA==",
+ "7K8l6KoP0BH82/WMLntfrg==",
+ "gEHGeR2F82OgBeAlnYhRSw==",
+ "1/SGIab+NnizimUmNDC4wA==",
+ "WADmxH7R6B4LR+W6HqQQ6A==",
+ "pcoBh5ic7baSD4TZWb3BSw==",
+ "es/L9iW8wsyLeC5S4Q8t+g==",
+ "D175i+2bZ7aWa4quSSkQpA==",
+ "WQMffxULFKJ+bun6NrCURA==",
+ "82hTTe1Nr4N2g7zwgGjxkw==",
+ "oyYtf08AkWLR52bXm5+sKw==",
+ "8uP4HUnSodw88yoiWXOIcw==",
+ "x2NpqNnqRihktNzpxmepkQ==",
+ "x5zMDuW66467ofgL3spLUQ==",
+ "OMO4pqzfcbQ11YO4nkTXfg==",
+ "N4/mQFyhDpPzmihjFJJn6w==",
+ "NN/ymVQNa17JOTGr6ki3eQ==",
+ "htDbVu1xGhCRd8qoMlBoMg==",
+ "S47hklz3Ow+n5aY6+qsCoA==",
+ "ji+1YHlRvzevs3q5Uw1gfA==",
+ "3Y4w0nETru3SiSVUMcWXqw==",
+ "XfBOCJwi2dezYzLe316ivw==",
+ "kMUdiwM7WR8KGOucLK4Brw==",
+ "V/xG5QFyx1pihimKmAo8ZA==",
+ "sQskMBELEq86o1SJGQqfzg==",
+ "6+jhreeBLfw64tJ+Nhyipw==",
+ "8iYdEleTXGM+Wc85/7vU9w==",
+ "D7piVoB2NJlBxK5owyo4+g==",
+ "hDGa2yLwNvgBd/v6mxmQaQ==",
+ "WLsh3UF4WXdHwgnbKEwRlQ==",
+ "D5jaV+HtXkSpSxJPmaBDXg==",
+ "jCgdKXsBCgf7giUKnr6paQ==",
+ "XqW7UBTobbV4lt1yfh0LZw==",
+ "EbGG4X18upaiVQmPfwKytg==",
+ "dXDPnL1ggEoBqR13aaW9HA==",
+ "Vik8tGNxO0xfdV0pFmmFDw==",
+ "Swjn3YkWgj0uxbZ1Idtk+A==",
+ "JPxEncA4IkfBDvpjHsQzig==",
+ "F5FcNti7lUa9DyF2iEpBug==",
+ "HJYgUxFZ66fRT8Ka73RaUg==",
+ "Jbxl8Nw1vlHO9rtu0q/Fpg==",
+ "fmC+85h5WBuk8fDEUWPjtQ==",
+ "dZgMquvZmfLqP4EcFaWCiA==",
+ "XF/yncdoT4ruPeXCxEhl9Q==",
+ "QJEbr3+42P9yiAfrekKdRQ==",
+ "Sr9c0ReRpkDYGAiqSy683g==",
+ "Nr4zGo5VUrjXbI8Lr4YVWQ==",
+ "NDZWIhhixq7NT8baJUR4VQ==",
+ "GFRJoPcXlkKSvJRuBOAYHQ==",
+ "WHutPin+uUEqtrA7L8878A==",
+ "2rhjiY0O0Lo36wTHjmlNyw==",
+ "XsF7R12agx/KkRWl0TyXRA==",
+ "R6cO8GzYfOGTIi773jtkXw==",
+ "zrZWcqQsUE3ocWE0fG+SOA==",
+ "uNzpptKjihEfKRo5A1nWmw==",
+ "gICaI06E9scnisonpvqCsA==",
+ "TA9WjiLAFgJubLN4StPwLw==",
+ "sBpytpE38xz0zYeT+0qc2A==",
+ "Ej7W3+67kCIng3yulXGpRQ==",
+ "nR3ACzeVF5YcLX6Gj6AGyQ==",
+ "b0vZfEyuTja2JYMa20Rtbg==",
+ "f1h+Vp+xmdZsZIziHrB2+g==",
+ "WzjvUJ4jZAEK7sBqw+m07A==",
+ "OzMR5D2LriC5yrVd5hchnA==",
+ "cw1gBLtxH/m4H7dSM7yvFg==",
+ "CZbd+UoTz0Qu1kkCS3k8Xg==",
+ "WtT0QAERZSiIt2SFDiAizg==",
+ "QsquNcCZL9wv7oZFqm64vQ==",
+ "FXzaxi3nAXBc8WZfFElQeA==",
+ "Ml3mi1lGS1IspHp3dYYClg==",
+ "XGAXhUFjORwKmAq9gGEcRg==",
+ "wOhbpTzmFla8R0kI9OiHaA==",
+ "qoK2keBg3hdbn7Q24kkVXg==",
+ "ZAQHWU6RMg4IadOxuaukyw==",
+ "RiahBXX2JbPzt8baPiP/8g==",
+ "Qx6rVv9Xj8CBjqikWI9KFA==",
+ "ZRnR6i+5WKMRfs3BDRBCJg==",
+ "91LQuW6bMSxl10J/UDX23A==",
+ "0dIeIM5Zvm5nSVWLy94LWg==",
+ "Ja3ECL7ClwDrWMTdcSQ6Ug==",
+ "f6iLrMpxKhFxIlfRsFAuew==",
+ "iSeH0JFSGK73F470Rhtesw==",
+ "DwOTyyCoUfaSShHZx9u6xg==",
+ "rdeftHE7gwAT67wwhCmkYQ==",
+ "kUhyc3G8Zvx8+q5q5nVEhw==",
+ "W8bATujVUT80v2XGJTKXDg==",
+ "dMRx4Mf6LrN64tiJuyWmDw==",
+ "9cvHJmim9e0pOaoUEtiM6A==",
+ "RHToSGASrwEmvzjX6VPvNQ==",
+ "V7eji28JSg3vTi30BCS7gw==",
+ "4+htiqjEz9oq0YcI/ErBVg==",
+ "jKJn4czwUl/6wtZklcMsSg==",
+ "bvyB6OEwhwCIfJ6KRhjnRw==",
+ "59ipbMH7cKBsF9bNf4PLeQ==",
+ "M/cQja3uIk1im9++brbBOA==",
+ "AChOz8avRYsvxlbWcorQ3w==",
+ "FcKjlHKfQAGoovtpf+DxWQ==",
+ "y+cl1/Knb9MZPz8nBB0M+w==",
+ "b8BZV1NfBdLi70ir4vYvZg==",
+ "aFJuE/s+Kbge4ppn+wulkA==",
+ "CWBGcRFYwZ0va6115vV/oQ==",
+ "glnqaRfwm6NxivtB2nySzw==",
+ "mPk1IsU5DmDFA/Ym5+1ojw==",
+ "LGwcvetzQ3QqKjNh5vA8vw==",
+ "yctId8ltkl3+xqi9bj+RqA==",
+ "spJI3xFUlpCDqzg0XCxopA==",
+ "V8m51xgUgywRoV6BGKUrgg==",
+ "rgcXxjx3pDLotH7TTfAoZw==",
+ "/TSsi/AwKHtP6kQaeReI3w==",
+ "8dbyfox/isKLsnVjQNsEXg==",
+ "MOrAbuJTyGKPC6MgYJlx5Q==",
+ "uNWFZlP7DA96sf+LWiAhtQ==",
+ "hNHqznsrIVRSQdII6crkww==",
+ "GT6WUDXiheKAM7tPg3he9A==",
+ "JC8Q+8yOJ52NvtVeyHo68w==",
+ "HMQarkPWOUDIg5+5ja2dBQ==",
+ "nknBKPgb7US42v8A0fTl/w==",
+ "fDOUzPTU2ndpbH0vgkgrJQ==",
+ "GTNttXfMniNhrbhn92Aykg==",
+ "D2JcY4zWwqaCKebLM8lPiQ==",
+ "/c34NtdUZAHWIwGl3JM8Tw==",
+ "/G26n5Xoviqldr5sg/Jl3w==",
+ "GF0lY77rx1NQzAsZpFtXIQ==",
+ "BMOi5JmFUg5sCkbTTffXHw==",
+ "R+beucURp/H5jLs4kW6wmg==",
+ "xfYZ6qhWNBqqJ0PdWRjOwA==",
+ "Ahpi9+nl13kPTdzL+jgqMw==",
+ "oIU19xAvLJwQSZzIH577aA==",
+ "50xwiYvGQytEDyVgeeOnMg==",
+ "M0ESOGwJ4WZ4Ons1ljP0bQ==",
+ "fS471/rN4K2m10mUwGFuLg==",
+ "RrE3B3X/SJi3CqCUlTYwaw==",
+ "oDca3JEdRb4vONT9GUUsaQ==",
+ "pHo1O5zrCHCiLvopP2xaWw==",
+ "7sCJ4RxbxRqVnF4MBoKfuQ==",
+ "7R5rFaXCxM3moIUtoCfM2g==",
+ "4rrSL6N0wyucuxeRELfAmw==",
+ "9Gkw+hvsR/tFY1cO89topg==",
+ "aw4CzX8pYbPVMuNrGCEcWg==",
+ "KyLQxi5UP+qOiyZl0PoHNQ==",
+ "T1pMWdoNDpIsHF8nKuOn2A==",
+ "Qv6wWP4PpycDGxe7EZNSCw==",
+ "ZJc7GV0Yb6MrXkpDVIuc8g==",
+ "aXrbsro7KLV8s4I4NMi4Eg==",
+ "7k5rBuh8FbTTI4TP87wBPQ==",
+ "NRyFx6jqO/oo9ojvbYzsAg==",
+ "P7eMlOz9YUcJO+pJy0Kpkw==",
+ "jpjpNjL1IKzJdGqWujhxCw==",
+ "9k1u/5TgPmXrsx3/NsYUhg==",
+ "c1wbFbN7AdUERO/xVPJlgw==",
+ "Yw4ztKv6yqxK9U1L0noFXg==",
+ "GnJKlRzmgKN9vWyGfMq3aA==",
+ "91VcAVv7YDzkC1XtluPigw==",
+ "h1NNwMy0RjQmLloSw1hvdg==",
+ "pzC8Y0Vj9MPBy3YXR32z6w==",
+ "UTmTgvl+vGiCDQpLXyVgOg==",
+ "CzWhuxwYbNB/Ffj/uSCtbw==",
+ "VOB+9Bcfu8aHKGdNO0iMRw==",
+ "X2Tawm2Cra6H7WtXi1Z4Qw==",
+ "6cTETZ9iebhWl+4W5CB+YQ==",
+ "X4hrgqMIcApsjA9qOWBoCw==",
+ "1buQEv2YlH/ljTgH0uJEtw==",
+ "FH5Z60RXXUiDk+dSZBxD3g==",
+ "FI2WhaSMb3guFLe3e9il8Q==",
+ "O/EizzJSuFY8MpusBRn7Tg==",
+ "b6rrRA0W247O+FfvDHbVCQ==",
+ "ng1Q0A7ljho3TUWWYl46sw==",
+ "1Ym0lyBJ9aFjhJb/GdUPvQ==",
+ "+OXdvbTxHtSoLg7bZMho4w==",
+ "cuQslgfqD2VOMhAdnApHrA==",
+ "pCQmlnn3BxhsV2GwqjRhXg==",
+ "6PzjncEw2wHZg7SP7SQk9w==",
+ "nqtQI1bSM7DCO9P1jGV97Q==",
+ "O1ckWUwuhD44MswpaD6/rw==",
+ "RUmhye56tQu9xXs4SRJpOQ==",
+ "llujnWE17U8MIHmx4SbrSA==",
+ "UwqBVd4Wfias4ElOjk2BzQ==",
+ "kBAB2PSjXwqoQOXNrv80AA==",
+ "w1zN28mSrI/gqHsgs4ME3A==",
+ "301utVPZ93AnPLYbsiJggw==",
+ "qIFpKKwUmztsBpJgMaVvSg==",
+ "QmcURiMzmVeUNaYPSOtTTg==",
+ "x/MpsQvziUpW40nNUHDS5Q==",
+ "t1O9jSNjg4DTIv/Za4NbtA==",
+ "1B5gxGQSGzVKoNd5Ol4N7g==",
+ "81iQLU+YwxNwq4of6e9z7A==",
+ "x0eIHCvQLd2jdDaXwSWTYQ==",
+ "96ORaz1JRHY1Gk8H74+C2g==",
+ "bNDKcFu8T5Y6OoLSV+o/Sw==",
+ "WrJMOuXSLKKzgmIDALkyNw==",
+ "+gpHnUj2GWocP74t5XWz4w==",
+ "z5DveTu377UW8IHnsiUGZg==",
+ "irnD9K8bsT+up/JUrxPw6A==",
+ "ginkFyNVMwkZLE49AbfqfA==",
+ "2hEzujfG3mR5uQJXbvOPTQ==",
+ "E9yeifEZtpqlD0N3pomnGw==",
+ "OpC/sL320wl5anx6AVEL+A==",
+ "D7wN7b5u5PKkMaLJBP9Ksw==",
+ "83WGpQGWyt6mCV+emaomog==",
+ "X6ulLp4noBgefQTsbuIbYQ==",
+ "BH+rkZWQjTp7au6vtll/CQ==",
+ "Ex3x5HeDPhgO2S9jjCFy4g==",
+ "YNqIHCmBp/EbCgaPKJ7phw==",
+ "312g8iTB9oJgk/OqcgR7Cw==",
+ "LcF0OqPWrcpHby8RwXz1Yg==",
+ "gaEtlJtD6ZjF5Ftx0IFt0A==",
+ "bvbMJZMHScwjJALxEyGIyg==",
+ "StoXC7TBzyRViPzytAlzyQ==",
+ "XqFSbgvgZn0CpaZoZiRauQ==",
+ "AqHVaj3JcR44hnMzUPvVYg==",
+ "jTg9Y6EfpON4CRFOq0QovA==",
+ "q/siBRjx6wNu+OTvpFKDwA==",
+ "goSgZ8N5UbT5NMnW3PjIlQ==",
+ "9onh6QKp70glZk9cX3s34A==",
+ "o5XVEpdP4OXH0NEO4Yfc/A==",
+ "a5gZ5uuRrXEAjgaoh7PXAg==",
+ "PaROi5U16Tk35p0EKX5JpA==",
+ "dtnE401dC0zRWU0S/QOTAg==",
+ "7J3FoFGuTIW36q0PZkgBiw==",
+ "hiYg+aVzdBUDCG0CXz9kCw==",
+ "vhdFtKVH4bVatb4n8KzeXw==",
+ "DWKsPfKDAtfuwgmc2dKUNg==",
+ "M2JMnViESVHTZaru6LDM6w==",
+ "G/PA+kt0N+jXDVKjR/054A==",
+ "6rqK8sjLPJUIp7ohkEwfZg==",
+ "wajwXfWz2J+O+NVaj6j2UQ==",
+ "C4QEzQKGxyRi2rjwioHttA==",
+ "N/HgDydvaXuJvTCBhG/KtA==",
+ "6erpZS36qZRXeZ9RN9L+kw==",
+ "bbBsi6tXMVWyq3SDVTIXUg==",
+ "aySnrShOW4/xRSzl/dtSKQ==",
+ "rxfACPLtKXbYua18l3WlUw==",
+ "L4+C6I7ausPl6JbIbmozAg==",
+ "R3ijnutzvK6IKV3AKHQZSA==",
+ "leDlMcM+B1mDE8k5SWtUeg==",
+ "KGI/cXVz6v6CfL8H6akcUQ==",
+ "NtwqUO3SKZE/9MXLbTJo/g==",
+ "dJHKDkfMFJeoULg7U4wwDQ==",
+ "IEz72W2/W8xBx5aCobUFOQ==",
+ "wUYhs4j3W9nIywu1HIv2JA==",
+ "GzbeM7snhe+M+J7X+gAsQw==",
+ "3/1puZTGSrD9qNKPGaUZww==",
+ "eKQCVzLuzoCLcB4im8147A==",
+ "CCK+6Dr72G3WlNCzV7nmqw==",
+ "CJoZn5wdTXbhrWO5LkiW0g==",
+ "bJ1cZW7KsXmoLw0BcoppJg==",
+ "OlpA9HsF8MBh7b45WZSSlg==",
+ "JZRjdJLgZ+S0ieWVDj8IJg==",
+ "uhT12XY79CtbwhcSfAmAXQ==",
+ "isep9d+Q7DEUf0W7CJJYzw==",
+ "K9A87aMlJC8XB9LuFM913g==",
+ "uqe3rFveJ2JIkcZQ3ZMXHQ==",
+ "0e8hM3E5tnABRyy29A8yFw==",
+ "4iiCq+HhC+hPMldNQMt0NA==",
+ "X4o0OkTz0ec70mzgwRfltA==",
+ "1E3pMgAHOnHx3ALdNoHr8Q==",
+ "xNilc7UOu1kyP0+nK5MrLw==",
+ "DQlZWBgdTCoYB1tJrNS5YQ==",
+ "iruDC5MeywV4yA8o1tw/KQ==",
+ "z+1oDVy8GJ5u/UDF+bIQdA==",
+ "uExgqZkkJnZj252l5dKAGg==",
+ "ZgdpqFrVGiaHkh9o3rDszg==",
+ "5N2oi2pB69NxeNt08yPLhw==",
+ "G37U8XTFyshfCs7qzFxATg==",
+ "0ZEC3hy411LkOhKblvTcqg==",
+ "ITZ3P47ALS0JguFms6/cDA==",
+ "WWN44lbUnEdHmxSfMCZc6w==",
+ "r2f2MyT+ww1g9uEBzdYI1w==",
+ "ZvvxwDd0I6MsYd7aobjLUA==",
+ "uQs79rbD/wEakMUxqMI48A==",
+ "022B0oiRMx8Xb4Af98mTvQ==",
+ "afMd/Hr3rYz/l7a3CfdDjg==",
+ "xmsYnsJq78/f9xuKuQ2pBQ==",
+ "dFetwmFw+D6bPMAZodUMZQ==",
+ "TBQpcKq2huNC5OmI2wzRQw==",
+ "skrQRB9xbOsiSA19YgAdIQ==",
+ "anyANMnNkUqr3JuPJz5Qzw==",
+ "6QUGE2S8oFYx4T4nW56cCw==",
+ "rwtF86ZAbWyKI6kLn4+KBw==",
+ "6txm8z4/LGCH0cpaet/Hsg==",
+ "wdRyYjaM11VmqkkxV/5bsA==",
+ "+k5lDb+QdNc9iZ01hL5yBg==",
+ "k/pBSWE2BvUsvJhA9Zl5uw==",
+ "jQjyjWCEo9nWFjP4O8lehw==",
+ "R6Me6sSGP5xpNI8R0xGOWw==",
+ "9+hjTVMQUsvVKs7Tmp52tg==",
+ "VQIpquUqmeyt/q6OgxzduQ==",
+ "KXvdjZ3rRKn60djPTCENGA==",
+ "5HovoyHtul8lXh+z8ywq9A==",
+ "1+XWdu4qCqLLVjqkKz3nmA==",
+ "LCj4hI520tA685Sscq6uLw==",
+ "b53qqLnrTBthRXmmnuXWvw==",
+ "WTr3q/gDkmB4Zyj7Ly20+w==",
+ "FbxScyuRacAQkdQ034ShTA==",
+ "qaTdVEeZ6S8NMOxfm+wOMA==",
+ "ZNrjP1fLdQpGykFXoLBNPw==",
+ "/Bwpt5fllzDHq2Ul6v86fA==",
+ "/mFp3GFkGNLhx2CiDvJv4A==",
+ "RppDe/WGt1Ed6Vqg1+cCkQ==",
+ "6M6QapJ5xtMXfiD3bMaiLA==",
+ "Ghuj9hAyfehmYgebBktfgA==",
+ "GncGQgmWpI/fZyb/6zaFCg==",
+ "R1TCCfgltnXBvt5AiUnCtQ==",
+ "5NEP7Xt7ynj6xCzWzt21hQ==",
+ "4yEkKp2FYZ09mAhw2IcrrA==",
+ "y2Tn2gmhKs5WKc01ce74rg==",
+ "wnfYUctNK+UPwefX5y4/Rw==",
+ "BV1moliPL15M14xkL+H1zw==",
+ "80C9TB9/XT1gGFfQDJxRoA==",
+ "yL1DwlIIREPuyuCFULi0uw==",
+ "D09afzGpwCEH0EgZUSmIZA==",
+ "eCy/T+a8kXggn1L8SQwgvA==",
+ "+dIEf5FBrHpkjmwUmGS6eg==",
+ "kzXsrxWRnWhkA82LsLRYog==",
+ "Nf9fbRHm844KZ2sqUjNgkA==",
+ "XAq/C+XyR6m3uzzLlMWO5Q==",
+ "jiV+b/1EFMnHG6J0hHpzBg==",
+ "HK0yf7F97bkf1VYCrEFoWA==",
+ "Cz1G77hsDtAjpe0WzEgQog==",
+ "xdCCdP8SNBOK3IsX6PiPQA==",
+ "8snljTGo/uICl9q0Hxy7/A==",
+ "sLdxIKap0ZfC3GpUk3gjog==",
+ "IA1jmtfpYkz/E2wD0+27WA==",
+ "PPa7BDMpRdxJdBxkuWCxKA==",
+ "CuGIxWhRLN7AalafBZLCKQ==",
+ "MWcV03ULc0vSt/pFPYPvFA==",
+ "QVwuN66yPajcjiRnVk/V8g==",
+ "aLY2pCT0WfFO5EJyinLpPg==",
+ "dGrf9SWJ13+eWS6BtmKCNw==",
+ "YtZ8CYfnIpMd2FFA5fJ+1Q==",
+ "Umd+5fTcxa3mzRFDL9Z8Ww==",
+ "Al8+d/dlOA5BXsUc5GL8Tg==",
+ "/KYZdUWrkfxSsIrp46xxow==",
+ "kr8tw1+3NxoPExnAtTmfxg==",
+ "PwvPBc+4L73xK22S9kTrdA==",
+ "VWNDBOtjiiI4uVNntOlu/A==",
+ "lJFPmPWcDzDp5B2S8Ad8AA==",
+ "Mofqu40zMRrlcGRLS42eBw==",
+ "BuENxPg7JNrWXcCxBltOPg==",
+ "nmD7fEU4u7/4+W/pkC4/0Q==",
+ "axEl7xXt/bwlvxKhI7hx4g==",
+ "W04GeDh+Tk/I1S85KlozRA==",
+ "tVw8U1AsslIFmQs4H1xshg==",
+ "TSPFvkgw6uLsJh66Ou0H9w==",
+ "IYIbEaErHoFBn8sTT9ICIQ==",
+ "WBu0gJmmjVdVbjDmQOkU6w==",
+ "ZgjifTVKmxOieco81gnccQ==",
+ "ZrCnZB/U/vcqEtI1cSvnww==",
+ "2D6yhuABiaFFoXz0Lh0C+w==",
+ "SfwnYZCKP1iUJyU1yq4eKg==",
+ "tsiqwelcBAMU/HpLGBtMGw==",
+ "S9L29U2P5K8wNW+sWbiH7w==",
+ "sGLPmr568+SalaQr8SE/PA==",
+ "Hm6MG6BXbAGURVJKWRM6ZA==",
+ "euxzbIq4vfGYoY3s1QmLcw==",
+ "/FchS2nPezycB8Bcqc2dbg==",
+ "ZKvox7BaQg4/p5jIX69Umw==",
+ "HkbdaMuDTPBDnt3wAn5RpQ==",
+ "eddhS+FkXxiUnbPoCd5JJw==",
+ "Muf2Eafcf9G3U2ZvQ9OgtQ==",
+ "a7Pv1SOWYnkhIUC22dhdDA==",
+ "O839JUrR+JS30/nOp428QA==",
+ "2qK2ZEY9LgdKSTaLf6VnLA==",
+ "BTiGLT6XdZIpFBc91IJY6g==",
+ "EqYq2aVOrdX5r7hBqUJP7g==",
+ "SIuKH/Qediq0TyvqUF93HQ==",
+ "c5ymZKqx/td1MiS2ERiz9A==",
+ "rqucO37p86LpzehR/asCSQ==",
+ "1tpM0qgdo7JDFwvT0TD78g==",
+ "Ar1Eb/f/LtuIjXnnVPYQlA==",
+ "V8q+xz4ljszLZMrOMOngug==",
+ "P5WPQc5NOaK7WQiRtFabkw==",
+ "Xo8ZjXOIoXlBjFCGdlPuZw==",
+ "jTmPbq+wh30+yJ/dRXk1cA==",
+ "KSumhnbKxMXQDkZIpDSWmQ==",
+ "Kh/J1NpDBGoyDU+Mrnnxkg==",
+ "3BjLFon1Il0SsjxHE2A1LQ==",
+ "dml2gqLPsKpbIZ93zTXwCQ==",
+ "ZyoaR1cMiKAsElmYZqKjLA==",
+ "vnOJ3e9Zd4wPx8PX7QgZzQ==",
+ "2melaInV0wnhBpiI3da6/A==",
+ "mUek9NkXm8HiVhQ6YXiyzA==",
+ "RZTpYKxOAH9JgF1QFGN+hw==",
+ "a/Y6IAVFv0ykRs9WD+ming==",
+ "yhRi5M9Etuu9HSu4d24i3w==",
+ "+1gcqAqaRZwCj5BGiZp3CA==",
+ "o1zeXHJEKevURAAbUE/Vog==",
+ "cvOg7N4DmTM+ok1NBLyBiQ==",
+ "uPdjKJIGzN7pbGZDZdCGaA==",
+ "REnDNe9mGfqVGZt+GdsmjQ==",
+ "XqTK/2QuGWj50tGmiDxysA==",
+ "bL2FuwsPT7a7oserJQnPcw==",
+ "uO+uK1DntCxVRr1KttfUIw==",
+ "Xconi1dtldH90Wou9swggw==",
+ "HRF3WL/ue3/QlYyu7NUTrA==",
+ "5LuFDNKzMd2BzpWEIYO2Ww==",
+ "dNTU+/2DdZyGGTdc+3KMhQ==",
+ "H+NHjk/GJDh/GaNzMQSzjg==",
+ "/Ph/6l/lFNVqxAje1+PgFA==",
+ "4WRdAjiUmOQg2MahsunjAg==",
+ "j+lDhAnWAyso+1N8cm85hQ==",
+ "nFBXCPeiwxK9mLXPScXzTA==",
+ "vGKknndb4j6VTV8DxeT4fQ==",
+ "fdqt93OrpG13KAJ5cASvkg==",
+ "1MIn73MLroxXirrb+vyg2Q==",
+ "Q7teXmTHAC5qBy+t7ugf0w==",
+ "bWwtTFlhO3xEh/pdw0uWaQ==",
+ "Omi2ZB9kdR1HrVP2nueQkA==",
+ "+ZozWaPWw8ws1cE5DJACeg==",
+ "3FH4D31nKV13sC9RpRZFIg==",
+ "4kXlJNuT79XXf1HuuFOlHw==",
+ "36XDmX6j542q+Oei1/x0gw==",
+ "MqqDg9Iyt4k3vYVW5F+LDw==",
+ "cvrGmub2LoJ+FaM5HTPt9A==",
+ "uC2lzm7HaMAoczJO6Z/IhQ==",
+ "MnStiFQAr3QlaRZ02SYGaQ==",
+ "ZuayB6IpbeITokKGVi9R5w==",
+ "FtxpWdhEmC6MT61qQv4DGA==",
+ "KujFdhhgB9q4oJfjYMSsLg==",
+ "ZV8mEgJweIYk0/l0BFKetA==",
+ "gDLjxT7vm07arF4SRX5/Vg==",
+ "/MEOgAhwb7F0nBnV4tIRZA==",
+ "k2KP9oPMnHmFlZO6u6tgyw==",
+ "fbTm027Ms0/tEzbGnKZMDA==",
+ "HOi+vsGAae4vhr+lJ5ATnQ==",
+ "9Bet5waJF5/ZvsYaHUVEjQ==",
+ "Wd0dOs7eIMqW5wnILTQBtg==",
+ "z/e5M2lE9qh3bzB97jZCKA==",
+ "b16O4LF7sVqB7aLU2f3F1A==",
+ "lsBTMnse2BgPS6wvPbe7JA==",
+ "0nOg18ZJ/NicqVUz5Jr0Hg==",
+ "MFeXfNZy6Q9wBfZmPQy3xg==",
+ "ksOFI9C7IrDNk4OP6SpPgw==",
+ "NquRbPn8fFQhBrUCQeRRoQ==",
+ "ccmy4GVuX967KaQyycmO0w==",
+ "DY0IolKTYlW+jbKLPAlYjQ==",
+ "aJFbBhYtMbTyMFBFIz/dTA==",
+ "9pdeedz1UZUlv8jPfPeZ1g==",
+ "qZ2q5j2gH3O56xqxkNhlIA==",
+ "N7fHwb397tuQHtBz1P80ZQ==",
+ "uOkMpYy/7DYYoethJdixfQ==",
+ "E9ajQQMe02gyUiW3YLjO/A==",
+ "dFSavcNwGd8OaLUdWq3sng==",
+ "TAD0Lk95CD86vbwrcRogaQ==",
+ "jLI3XpVfjJ6IzrwOc4g9Pw==",
+ "CzP13PM/mNpJcJg8JD3s6w==",
+ "GSWncBq4nwomZCBoxCULww==",
+ "9k17UqdR1HzlF7OBAjpREA==",
+ "TrWS+reCJ0vbrDNT5HDR9w==",
+ "CXMKIdGvm60bgfsNc+Imvg==",
+ "6NP81geiL14BeQW6TpLnUA==",
+ "hW9DJA1YCxHmVUAF7rhSmQ==",
+ "8M0kSvjn5KN8bjsMdUqKZQ==",
+ "eS/vTdSlMUnpmnl1PbHjyw==",
+ "h2B0ty0GobQhDnFqmKOpKQ==",
+ "n7KL1Kv027TSxBVwzt9qeA==",
+ "yYmnM/WOgi+48Rw7foGyXA==",
+ "FhthAO5IkMyW4dFwpFS7RA==",
+ "81ZH3SO0NrOO+xoR/Ngw1g==",
+ "t7HaNlXL16fVwjgSXmeOAQ==",
+ "N+K1ibXAOyMWdfYctNDSZQ==",
+ "yQCLV9IoPyXEOaj3IdFMWw==",
+ "3+zsjCi7TnJhti//YXK35w==",
+ "600mjiWke4u0CDaSQKLOOg==",
+ "K4VS+DDkTdBblG93l2eNkA==",
+ "5KOgetfZR+O2wHQSKt41BQ==",
+ "kj5WqpRCjWAfjM7ULMcuPQ==",
+ "AxEjImKz4tMFieSo7m60Sg==",
+ "jp5Em/0Ml4Txr1ptTUQjpg==",
+ "jQVlDU+HjZ2OHSDBidxX5A==",
+ "4NHQwbb3zWq2klqbT/pG6g==",
+ "PeJS+mXnAA6jQ0WxybRQ8w==",
+ "l6Ssc04/CnsqUua9ELu2iQ==",
+ "nFPDZGZowr3XXLmDVpo7hg==",
+ "yYBIS9PZbKo7Gram7IXWPA==",
+ "/HU2+fBqfWTEuqINc0UZSA==",
+ "adT+OjEB2kqpeYi4kQ6FPg==",
+ "GW1Uaq622QamiiF24QUA0g==",
+ "rTwJggSxTbwIYdp07ly0LA==",
+ "4yrFNgqWq17zVCyffULocA==",
+ "vvh9vAIrXjIwLVkuJb5oDQ==",
+ "C7UaoIEXsVRxjeA0u99Qmw==",
+ "x1A74vg/hwwjAx6GrkU8zw==",
+ "7XRiYvytcwscemlxd9iXIQ==",
+ "64AA4jLHXc1Dp15aMaGVcA==",
+ "u/QxrP1NOM/bOJlJlsi/jQ==",
+ "5M3dFrAOemzQ0MAbA8bI5w==",
+ "wyqmQGB6vgRVrYtmB2vB7w==",
+ "8vLA9MOdmLTo3Qg+/2GzLA==",
+ "/u5W2Gab4GgCMIc4KTp2mg==",
+ "lhAOM81Ej6YZYBu45pQYgg==",
+ "MArbGuIAGnw4+fw6mZIxaw==",
+ "ZZImGypBWwYOAW43xDRWCQ==",
+ "L2IeUnATZHqOPcrnW2APbA==",
+ "bQKkL+/KUCsAXlwwIH0N3w==",
+ "f09F7+1LRolRL5nZTcfKGA==",
+ "hPnPQOhz4QKhZi02KD6C+A==",
+ "78b8sDBp28zUlYPV5UTnYw==",
+ "iVDd2Zk7vwmEh97LkOONpQ==",
+ "LHQETSI5zsejvDaPpsO29g==",
+ "Yjm5tSq1ejZn3aWqqysNvA==",
+ "gkrg0NR0iCaL7edq0vtewA==",
+ "Lo1xTCEWSxVuIGEbBEkVxA==",
+ "8GyPup4QAiolFJ9v80/Nkw==",
+ "3L3KEBHhgDwH615w4OvgZA==",
+ "hJSP7CostefBkJrwVEjKHA==",
+ "9oQ/SVNJ4Ye9lq8AaguGAQ==",
+ "n7Bns42aTungqxKkRfQ5OQ==",
+ "K5lhaAIZkGeP5rH2ebSJFw==",
+ "ZaPsR9X77SNt7dLjMJUh8A==",
+ "18ndtDM9UaNfBR1cr3SHdA==",
+ "0QbH4oI8IjZ9BRcqRyvvDQ==",
+ "J/eAtAPswMELIj8K2ai+Xg==",
+ "qenHZKKlTUiEFv6goKM/Mw==",
+ "vjrSYGUpeKOtJ2cNgLFg2g==",
+ "DA+3fjr7mgpwf6BZcExj0w==",
+ "rh7bzsTQ1UZjG7amysr0Gg==",
+ "tFMJRXfWE9g78O1uBUxeqQ==",
+ "e/nWuo5YalCAFKsoJmFyFA==",
+ "gqehq46BhFX2YLknuMv02w==",
+ "Uudn69Kcv2CGz2FbfJSSEA==",
+ "Otz/PgYOEZ1CQDW54FWJIQ==",
+ "IwfeA6d0cT4nDTCCRhK+pA==",
+ "jgNijyoj2JrQNSlUv4gk4A==",
+ "KzWdWPP2gH0DoMYV4ndJRg==",
+ "pv/m2mA/RJiEQu2Qyfv9RA==",
+ "ATmMzriwGLl+M3ppkfcZNA==",
+ "tVvWdA+JqH0HR2OlNVRoag==",
+ "n6QVaozMGniCO0PCwGQZ6w==",
+ "gU3gu8Y5CYVPqHrZmLYHbQ==",
+ "cBBOQn7ZjxDku0CUrxq2ng==",
+ "w+jzM0I5DRzoUiLS/9QIMQ==",
+ "MLlVniZ08FHAS5xe+ZKRaA==",
+ "wMyJLQJdmrC2TSeFkIuSvQ==",
+ "dG98w8MynOoX7aWmkvt+jg==",
+ "zm+z+OOyHhljV2TjA3U9zw==",
+ "Tk5MAqd1gyHpkYi8ErlbWg==",
+ "g6zSo8BvLuKqdmBFM1ejLA==",
+ "d0VAZLbLcDUgLgIfT1GmVQ==",
+ "SNPYH4r/J9vpciGN2ybP5Q==",
+ "XA2hUgq3GVPpxtRYiqnclg==",
+ "fVCRaPsTCKEVLkoF4y3zEw==",
+ "FpgdsQ2OG+bVEy3AeuLXFQ==",
+ "JquDByOmaQEpFb47ZJ4+JA==",
+ "e369ZIQjxMZJtopA//G55Q==",
+ "Nsd+DfRX6L54xs+iWeMjCQ==",
+ "+/UCpAhZhz368iGioEO8aQ==",
+ "e5l9ZiNWXglpw6nVCtO8JQ==",
+ "Cl1u5nGyXaoGyDmNdt38Bw==",
+ "6sNP0rzCCm3w976I2q2s/w==",
+ "qcpeZWUlPllQYZU6mHVwUw==",
+ "kzYddqiMsY3EYrpxve2/CQ==",
+ "3iC21ByW/YVL+pSyppanWw==",
+ "3HPOzIZxoaQAmWRy9OkoSg==",
+ "xsCZVhCk2qJmOqvUjK3Y8Q==",
+ "i2sSvrTh/RdLJX0uKhbrew==",
+ "7Y87wVJok20UfuwkGbXxLg==",
+ "ibsb1ncaLZXAYgGkMO7tjQ==",
+ "+VfRcTBQ80KSeJRdg0cDfw==",
+ "kgKWQJJQKLUuD2VYKIKvxA==",
+ "ARKIvf4+zRF8eCvUITWPng==",
+ "1fztTtQWNMIMSAc5Hr6jMQ==",
+ "md6zNd7ZBn3qArYqQz7/fw==",
+ "kvAaIJb+aRAfKK104dxFAA==",
+ "UIXytIHyVODxlrg+eQoARA==",
+ "Dk0L/lQizPEb3Qud6VHb1Q==",
+ "64YsV2qeDxk2Q6WK/h7OqA==",
+ "90dtIMq0ozJXezT2r79vMQ==",
+ "wy/Z8505o4sVovk4UuBp1A==",
+ "ytDXLDBqWiU1w3sTurYmaw==",
+ "9pk75mBzhmcdT+koHvgDlw==",
+ "DQeib845UqBMEl96sqsaSg==",
+ "UPYR575ASaBSZIR3aX1IgQ==",
+ "swsVVsPi/5aPFBGP+jmPIw==",
+ "1cj1Fpd3+UiBAOahEhsluA==",
+ "ifuJCv9ZA84Vz1FYAPsyEA==",
+ "uu+ncs63SdQIvG6z4r7Q3Q==",
+ "UvC1WADanMrhT+gPp/yVqA==",
+ "llOvGOUDVfX68jKnAlvVRA==",
+ "SusSOsWNoAerAIMBVWHtfA==",
+ "VznvTPAAwAev+yhl9oZT0w==",
+ "luR/kvHLwA6tSdLeTM4TzA==",
+ "PcdBtV8pfKU0YbDpsjPgwg==",
+ "5l6kDfjtZjkTZPJvNNOVFw==",
+ "4FBBtWPvqJ3dv4w25tRHiQ==",
+ "JJbzQ/trOeqQomsKXKwUpQ==",
+ "0bj069wXgEJbw7dpiPr8Tg==",
+ "tejpAZp7y32SO2+o4OGvwQ==",
+ "kq26VyDyJTH/eM6QvS2cMw==",
+ "+zBkeHF4P8vLzk1iO1Zn3Q==",
+ "BzkNYH03gF/mQY71RwO3VA==",
+ "RnxOYPSQdHS6fw4KkDJtrA==",
+ "65KhGKUBFQubRRIEdh9SwQ==",
+ "k1DPiH6NkOFXP/r3N12GyA==",
+ "DqzWt1gfyu/e7RQl5zWnuQ==",
+ "gnez1VrH+UHT8C/SB9qGdA==",
+ "vZtL0yWpSIA+9v8i23bZSg==",
+ "FNvQqYoe0s/SogpAB7Hr1Q==",
+ "6nwR+e9Qw0qp8qIwH9S/Mg==",
+ "BPT4PQxeQcsZsUQl33VGmg==",
+ "rOYeIcB+Rg5V6JG2k4zS2w==",
+ "Je1UESovkBa9T6wS0hevLw==",
+ "HFHMGgfOeO0UPrray1G+Zw==",
+ "NBmB/cQfS+ipERd7j9+oVg==",
+ "iIm8c9uDotr87Aij+4vnMw==",
+ "S3VQa6DH+BdlSrxT/g6B5g==",
+ "BwRA+tMtwEvth28IwpZx+w==",
+ "vg3jozLXEmAnmJwdfcEN0g==",
+ "gW0oKhtQQ7BxozxUWw5XvQ==",
+ "Q6vGRQiNwoyz7bDETGvi5g==",
+ "Ak3rlzEOds6ykivfg39xmw==",
+ "G4qzBI1sFP2faN+tlRL/Bw==",
+ "ND9l4JWcncRaSLATsq0LVw==",
+ "yQmNZnp/JZywbBiZs3gecA==",
+ "ZoNSxARrRiKZF5Wvpg7bew==",
+ "GhpJfRSWZigLg/azTssyVA==",
+ "QyyiJ5I/OZC50o89fa5EmQ==",
+ "4kj0S8XlmhHXoUP7dQItUw==",
+ "Dt8Q5ORzTmpPR2Wdk0k+Aw==",
+ "/hFhjFGJx2wRfz6hyrIpvA==",
+ "eFimq+LuHi42byKnBeqnZQ==",
+ "JrKGKAKdjfAaYeQH8Y2ZRQ==",
+ "JFFeXsFsMA59iNtZey7LAA==",
+ "91SdBFJEZ65M+ixGaprY/A==",
+ "+S+WXgVDSU1oGmCzGwuT3g==",
+ "1X14kHeKwGmLeYqpe60XEA==",
+ "4xojeUxTFmMLGm6jiMYh/Q==",
+ "+1e7jvUo8f2/2l0TFrQqfA==",
+ "8WU1vLKV1GhrL7oS9PpABg==",
+ "DYWCPUq/hpjr6puBE7KBHg==",
+ "birqO8GOwGEI97zYaHyAuw==",
+ "6e8boFcyc8iF0/tHVje4eQ==",
+ "FLvED9nB9FEl9LqPn7OOrA==",
+ "ji306HRiq965zb8EZD2uig==",
+ "AklOdt9/2//3ylUhWebHRw==",
+ "VGRCSrgGTkBNb8sve0fYnQ==",
+ "oqlkgrYe9aCOwHXddxuyag==",
+ "KXuFON8tMBizNkCC48ICLA==",
+ "9aKH1u5+4lgYhhLztQ4KWA==",
+ "3hVslsq98QCDIiO40JNOuA==",
+ "OOS6wQCJsXH8CsWEidB35A==",
+ "YXHQ3JI9+oca8pc/jMH6mA==",
+ "V9vkAanK+Pkc4FGAokJsTA==",
+ "OFLn4wun6lq484I7f6yEwg==",
+ "3WVBP9fyAiBPZAq3DpMwOQ==",
+ "5gGoDPTc/sOIDLngmlEq4A==",
+ "E2lvMXqHdTw0x+KCKVnblg==",
+ "f1Gs++Iilgq9GHukcnBG3w==",
+ "uIkVijg7RPi/1j7c18G1qA==",
+ "9T7gB0ZkdWB0VpbKIXiujQ==",
+ "KCJJfgLe00+tjSfP6EBcUg==",
+ "WbAdlac/PhYUq7J2+n5f+w==",
+ "GLnS9wDCje7TOMvBX9jJVA==",
+ "VAg/aU5nl72O+cdNuPRO4g==",
+ "kzTl7WH/JXsX1fqgnuTOgw==",
+ "1HDgfU7xU7LWO/BXsODZAQ==",
+ "D0W5F7gKMljoG5rlue1jrg==",
+ "9reBKZ1Rp6xcdH1pFQacjw==",
+ "SSKhl2L3Mvy93DcZulADtA==",
+ "hlu7os0KtAkpBTBV6D2jyQ==",
+ "sfte/o9vVNyida/yLvqADA==",
+ "gYGQBLo5TdMyXks0LsZhsQ==",
+ "dNq2InSVDGnYXjkxPNPRxA==",
+ "fiv0DJivQeqUkrzDNlluRw==",
+ "msstzxq++XO0AqNTmA7Bmg==",
+ "DCjgaGV5hgSVtFY5tcwkuA==",
+ "aMmrAzoRWLOMPHhBuxczKg==",
+ "qNOSm15bdkIDSc/iUr+UTQ==",
+ "2nSTEYzLK77h5Rgyti+ULQ==",
+ "BhKO1s1O693Fjy1LItR/Jw==",
+ "kRnBEH6ILR5GNSmjHYOclw==",
+ "R97chlspND/sE9/HMScXjQ==",
+ "1Oykse0jQVbuR3MvW5ot4A==",
+ "Dmyb+a7/QFsU4d2cVQsxDw==",
+ "W5now3RWSzzMDAxsHSl++Q==",
+ "IrDuBrVu1HWm0BthAHyOLQ==",
+ "V6zyoX6MERIybGhhULnZiw==",
+ "ZQSDYgpsimK+lYGdXBWE/w==",
+ "lV70RNlE++04G1KFB3BMXA==",
+ "QmSBVvdk0tqH9RAicXq2zA==",
+ "qNyy6Fc0b8oOMWqqaliZ/w==",
+ "xvipmmwKdYt4eoKvvRnjEg==",
+ "Q7Df6zGwvb4rC+EtIKfaSw==",
+ "n1M2dgFPpmaICP+JwxHUug==",
+ "1k8tL2xmGFVYMgKUcmDcEw==",
+ "fFvXa1dbMoOOoWZdHxPGjw==",
+ "UP9mmAKzeQqGhod7NCqzhg==",
+ "PMCWKgog/G+GFZcIruSONw==",
+ "dnvatwSEcl73ROwcZ4bbIQ==",
+ "hY82j+sUQQRpCi6CCGea5A==",
+ "QoUC9nyK1BAzoUVnBLV2zw==",
+ "+aF4ilbjQbLpAuFXQEYMWQ==",
+ "XTCcsVfEvqxnjc0K5PLcyw==",
+ "ML7ipnY/g8mA1PUIju1j8Q==",
+ "tOkYq1BZY152/7IJ6ZYKUg==",
+ "2bsIpvnGcFhTCSrK9EW1FQ==",
+ "Af9j1naGtnZf0u1LyYmK1w==",
+ "ZmblZauRqO5tGysY3/0kDw==",
+ "PF0lpolQQXlpc3qTLMBk8w==",
+ "emVLJVzha7ui5OFHPJzeRQ==",
+ "gR0sgItXIH8hE4FVs9Q07w==",
+ "PTW+fhZq/ErxHqpM0DZwHQ==",
+ "g0kHTNRI7x/lAsr92EEppw==",
+ "24H9q+E8pgCEdFS7JO5kzQ==",
+ "HtDXgMuF8PJ1haWk88S0Ew==",
+ "pulldyBt2sw6QDvTrCh6zw==",
+ "ehwc2vvwNUAI7MxU4MWQZw==",
+ "enj9VEzLbmeOyYugTmdGfQ==",
+ "auvG6kWMnhCMi7c7e9eHrw==",
+ "R36O31Pj8jn0AWSuqI7X2Q==",
+ "3AVYtcIv7A5mVbVnQMaCeA==",
+ "T9WoUJNwp8h4Yydixbx6nA==",
+ "t0WN8TwMLgi8UVEImoFXKg==",
+ "mS99D+CXhwyfVt8xJ+dJZA==",
+ "AFdelaqvxRj6T3YdLgCFyg==",
+ "Lu02ic/E94s42A14m7NGCA==",
+ "7w3b73nN/fIBvuLuGZDCYQ==",
+ "O209ftgvu0vSr0UZywRFXA==",
+ "MQvAr+OOfnYnr/Il/2Ubkg==",
+ "e5txnNRcGs2a9+mBFcF1Qg==",
+ "YA0kMTJ82PYuLA4pkn4rfw==",
+ "QIKjir/ppRyS63BwUcHWmw==",
+ "P3y5MoXrkRTSLhCdLlnc4A==",
+ "WY7mCUGvpXrC8gkBB46euw==",
+ "g0GbRp2hFVIdc7ct7Ky7ag==",
+ "Cv079ZF55RnbsDT27MOQIA==",
+ "cvMJ714elj/HUh89a9lzOQ==",
+ "9inw7xzbqAnZDKOl/MfCqA==",
+ "F58ktE4O0f7C9HdsXYm+lw==",
+ "CsPkyTZADMnKcgSuNu1qxg==",
+ "mAzsVkijuqihhmhNTTz65g==",
+ "FxnbKnuDct4OWcnFMT/a5w==",
+ "P5wS+xB8srW4a5KDp/JVkA==",
+ "ctJYJegZhG42i+vnPFWAWw==",
+ "OrqJKjRndcZ8OjE3cSQv7g==",
+ "aXqiibI6BpW3qilV6izHaQ==",
+ "BA18GEAOOyVXO2yZt2U35w==",
+ "saEpnDGBSZWqeXSJm34eOA==",
+ "CUEueo8QXRxkfVdfNIk/gg==",
+ "H0UMAUfHFQH92A2AXRCBKA==",
+ "CT9g8mKsIN/VeHLSTFJcNQ==",
+ "E4NtzxQruLcetC23zKVIng==",
+ "203EqmJI9Q4tWxTJaBdSzA==",
+ "Do3aqbRKtmlQI2fXtSZfxQ==",
+ "JaYQXntiyznQzrTlEeZMIw==",
+ "VK95g27ws2C6J2h/7rC2qA==",
+ "CQ0PPwgdG3N6Ohfwx1C8xA==",
+ "/MeHciFhvFzQsCIw39xIZA==",
+ "u5cUPxM6/spLIV8VidPrAA==",
+ "OwArFF1hpdBupCkanpwT+Q==",
+ "PdBgXFq5mBqNxgCiqaRnkw==",
+ "lC5EumoIcctvxYqwELqIqw==",
+ "xoPSM86Se+1hHX0y3hhdkw==",
+ "F5bs0GGWBx9eBwcJJpXbqg==",
+ "1mw6LfTiirFyfjejf8QNGA==",
+ "daBhAvmE9shDgmciDAC5eg==",
+ "AvdeYb9XNOUFWiiz+XGfng==",
+ "JJJkp1TpuDx5wrua2Wml7g==",
+ "3y5Xk65ShGvWFbQxcZaQAQ==",
+ "l6QHU5JsJExNoOnqxBPVbw==",
+ "X2YfnPXgF2VHVX95ZcBaxQ==",
+ "g6udffWh7qUnSIo1Ldn3eA==",
+ "V2P75JFB4Se9h7TCUMfeNA==",
+ "IUZ5aGpkJ9rLgSg6oAmMlw==",
+ "pyrUqiZ98gVXxlXQNXv5fA==",
+ "83ERX2XJV3ST4XwvN7YWCg==",
+ "eJDUejE/Ez/7kV+S74PDYg==",
+ "M9oqlPb63e0kZE0zWOm+JQ==",
+ "0rTYcuVYdilO7zEfKrxY3A==",
+ "rfPTskbnoh3hRJH6ZAzQRg==",
+ "QtD35QhE8sAccPrDnhtQmQ==",
+ "jpNUgFnanr9Sxvj2xbBXZw==",
+ "nykEOLL/o7h0cs0yvdeT2g==",
+ "wX2URK6eDDHeEOF3cgPgHA==",
+ "jqPQ0aOuvOJte/ghI1RVng==",
+ "nHTsDl0xeQPC5zNRnoa0Rw==",
+ "mNv2Q67zePjk/jbQuvkAFA==",
+ "HjlPM2FQWdILUXHalIhQ5w==",
+ "cHkOsVd80Rgwepeweq4S1g==",
+ "kTCHqcb3Cos51o8cL+MXcg==",
+ "nvmBgp0YlUrdZ05INsEE8Q==",
+ "kFrRjz7Cf2KvLtz9X6oD+w==",
+ "Tmx0suRHzlUK4FdBivwOwA==",
+ "bG+P+p34t/IJ1ubRiWg6IA==",
+ "uESeJe/nYrHCq4RQbrNpGA==",
+ "ehfPlu6YctzzpQmFiQDxGA==",
+ "ZH5Es/4lJ+D5KEkF1BVSGg==",
+ "HHxn4iIQ7m0tF1rSd+BZBg==",
+ "DQJRsUwO1fOuGlkgJavcwQ==",
+ "HITIVoFoWNg04NExe13dNA==",
+ "MeKXnEfxeuQu9t3r/qWvcw==",
+ "Y7OofF9eUvp7qlpgdrzvkg==",
+ "XSb71ae0v+yDxNF5HJXGbQ==",
+ "p8W1LgFuW6JSOKjHkx3+aA==",
+ "y2JOIoIiT9cV1VxplZPraQ==",
+ "MN94B0r5CNAF9sl3Kccdbw==",
+ "Q1pdQadt12anX1QRmU2Y/A==",
+ "JIC8R48jGVqro6wmG2KXIw==",
+ "eWgLAqJOU+fdn8raHb9HCw==",
+ "5CMadLqS2KWwwMCpzlDmLw==",
+ "H1y2iXVaQYwP0SakN6sa+Q==",
+ "CUCjG2UaEBmiYWQc6+AS1Q==",
+ "yV3IbbTWAbHMhMGVvgb/ZQ==",
+ "80PCwYh4llIKAplcDvMj4g==",
+ "fgdUFvQPb5h+Rqz8pzLsmw==",
+ "2SI4F7Vvde2yjzMLAwxOog==",
+ "kJdY3XEdJS/hyHdR+IN0GA==",
+ "IKgNa2oPaFVGYnOsL+GC5Q==",
+ "eXFOya6x5inTdGwJx/xtUQ==",
+ "uTA0XbiH3fTeVV7u5z0b3w==",
+ "onFcHOO1c3pDdfCb5N4WkQ==",
+ "Slu3z535ijcs5kzDnR7kfA==",
+ "SElc2+YVi3afE1eG1MI7dQ==",
+ "ND2hYtAIQGMxBF7o7+u7nQ==",
+ "Pv9FWQEDLKnG/9K9EIz4Gw==",
+ "6CjtF1S2Y6RCbhl7hMsD+g==",
+ "rs2QrN4qzAHCHhkcrAvIfA==",
+ "eTMPXa60OTGjSPmvR4IgGw==",
+ "pvXHwJ3dwf9GDzfDD9JI3g==",
+ "CRmAj3JcasAb4iZ9ZbNIbw==",
+ "rcY4Ot40678ByCfqvGOGdg==",
+ "l4ddTxbTCW5UmZW+KRmx6A==",
+ "NKRzJndo2uXNiNppVnqy1g==",
+ "0NrvBuyjcJ2q6yaHpz/FOA==",
+ "3YXp1PmMldUjBz3hC6ItbA==",
+ "CmVD6nh8b/04/6JV9SovlA==",
+ "HjyxyL0db2hGDq2ZjwOOhg==",
+ "4PBaoeEwUj79njftnYYqLg==",
+ "vFFzkWgGyw6OPADONtEojQ==",
+ "czBWiYsQtNFrksWwoQxlOw==",
+ "9iB7+VwXRbi6HLkWyh9/kg==",
+ "zwY6tCjjya/bgrYaCncaag==",
+ "mW6TCje9Zg2Ep7nzmDjSYQ==",
+ "5LJqHFRyIwQKA4HbtqAYQQ==",
+ "INNBBin5ePwTyhPIyndHHg==",
+ "dChBe9QR29ObPFu/9PusLg==",
+ "1dhq3ozNCx0o4dV1syLVDA==",
+ "nyaekSYTKzfSeSfPrB114Q==",
+ "TfNHjSTV8w6Pg6+FaGlxvA==",
+ "m/Lp4U75AQyk9c8cX14HJg==",
+ "uU1TX5DoDg6EcFKgFcn0GA==",
+ "B+TsxQZf0IiQrU8X9S4dsQ==",
+ "6b7ue29cBDsvmj1VSa5njw==",
+ "RvXWAFwM+mUAPW1MjPBaHA==",
+ "pdaY6kZ8+QqkMOInvvACNA==",
+ "7nr3zyWL+HHtJhRrCPhYZA==",
+ "BXGlq54wIH6R3OdYfSSDRw==",
+ "b06KGv5zDYsTxyTbQ9/eyA==",
+ "8ylI1AS3QJpAi3I/NLMYdg==",
+ "0fpe9E6m3eLp/5j5rLrz2Q==",
+ "Qrh7OEHjp80IW+YzQwzlJg==",
+ "lqhgbgEqROAdfzEnJ17eXA==",
+ "Dulw855DfgIwiK7hr3X8vg==",
+ "wsp+vmW8sEqXYVURd/gjHA==",
+ "VoPth5hDHhkQcrQTxHXbuw==",
+ "TgWe70YalDPyyUz6n88ujg==",
+ "9lLhHcrPWI4EsA4fHIIXuw==",
+ "UymZUnEEQWVnLDdRemv+Tw==",
+ "qnkFUlJ8QT322JuCI3LQgg==",
+ "/p/aCTIhi1bU0/liuO/a2Q==",
+ "hWoxz5HhE50oYBNRoPp1JQ==",
+ "88tB/HgUIUnqWXEX++b5Aw==",
+ "Z8T1b9RsUWf59D06MUrXCQ==",
+ "BZTzHJGhzhs3mCXHDqMjnQ==",
+ "XfY+QUriCAA1+3QAsswdgg==",
+ "TZ3ATPOFjNqFGSKY3vP2Hw==",
+ "cl4t9FXabQg7tbh1g7a0OA==",
+ "9SgfpAY0UhNC6sYGus9GgQ==",
+ "d/Wd3Ma1xYyoMByPQnA9Cw==",
+ "DDitrRSvovaiXe2nfAtp4g==",
+ "s+eHg5K9zZ2Jozu5Oya9ZQ==",
+ "z3L2BNjQOMOfTVBUxcpnRA==",
+ "v4xIYrfPGILEbD/LwVDDzA==",
+ "HoaBBw2aPCyhh0f5GxF+/Q==",
+ "i9IRqAqKjBTppsxtPB7rdw==",
+ "cWUg7AfqhiiEmBIu+ryImA==",
+ "E+02smwQGBIxv42LIF2Y4Q==",
+ "W4CfeVp9mXgk04flryL7iA==",
+ "9SUOfKtfKmkGICJnvbIDMg==",
+ "xweGAZf+Yb3TtwR/sGmGIA==",
+ "EJgedRYsZPc4cT9rlwaZhg==",
+ "wv4NC9CIpwuGf/nOQYe/oA==",
+ "ZXeMG5eqQpZO/SGKC4WQkA==",
+ "bzXXzQGZs8ustv0K4leklA==",
+ "RkQK9S1ezo+dFYHQP57qrw==",
+ "mrinv7KooPQPrLCNTRWCFg==",
+ "qIUJPanWmGzTD1XxvHp+6w==",
+ "Js7g8Dr6XsnGURA4UNF0Ug==",
+ "dpSTNOCPFHN5yGoMpl1EUA==",
+ "ugY8rTtJkN4CXWMVcRZiZw==",
+ "rqHKB91H3qVuQAm+Ym5cUA==",
+ "UjmDFO7uzjl4RZDPeMeNyg==",
+ "cu4ZluwohhfIYLkWp72pqA==",
+ "ZydKlOpn2ySBW0G3uAqwuw==",
+ "LWd0+N3M94n81qd346LfJQ==",
+ "VbHoWmtiiPdABvkbt+3XKQ==",
+ "J4MC9He6oqjOWsYQh9nl3Q==",
+ "ahAbmGJZvUOXrcK6OydNGQ==",
+ "Byhi4ymFqqH8uIeoMRvPug==",
+ "LSN9GmT6LUHlCAMFqpuPIA==",
+ "IAMInfSYb76GxDlAr1dsTg==",
+ "qYHdgFAXhF/XcW4lxqfvWQ==",
+ "26+yXbqI+fmIZsYl4UhUzw==",
+ "AwPTZpC28NJQhf5fNiJuLA==",
+ "SESKbGF35rjO64gktmLTWA==",
+ "YVlRQHQglkbj3J2nHiP/Hw==",
+ "DdaT4JLC7U0EkF50LzIj9w==",
+ "G0LChrb0OE5YFqsfTpIL1Q==",
+ "5Yrj6uevT8wHRyqqgnSfeg==",
+ "NmWmDxwK5FpKlZbo0Rt8RA==",
+ "iUsUCB0mfRsE9KPEQctIzw==",
+ "Tm4zk2Lmg8w4ITMI31NfTA==",
+ "Vu0E+IJXBnc25x4n41kQig==",
+ "6wkfN8hyKmKU6tG3YetCmw==",
+ "trjM81KANPZrg9iSThWx6Q==",
+ "iGuY4VxcotHvMFXuXum7KA==",
+ "ICPdBCdONUqPwD5BXU5lrw==",
+ "alqHQBz8V446EdzuVfeY5Q==",
+ "74FW/QYTzr/P1k6QwVHMcw==",
+ "avZp5K7zJvRvJvpLSldNAw==",
+ "TIKadc6FAaRWSQUg5OATgg==",
+ "PfkWkSbAxIt1Iso0znW0+Q==",
+ "Z+bsbVP91KrJvxrujBLrrQ==",
+ "mrxlFD3FBqpSZr1kuuwxGg==",
+ "nUgYO7/oVNSX8fJqP2dbdg==",
+ "tVhXk9Ff3wAg56FbdNtcFg==",
+ "DdiNGiOSoIZxrMrGNvqkXw==",
+ "CDsanJz7e3r/eQe+ZYFeVQ==",
+ "wVfSZYjMjbTsD2gaSbwuqQ==",
+ "6c0iuya20Ys8BsvoI4iQaQ==",
+ "qCPfJTR8ecTw6u6b1yHibA==",
+ "fZrj3wGQSt8RXv0ykJROcQ==",
+ "gR3B8usSEb0NLos51BmJQg==",
+ "vTAmgfq3GxL4+ubXpzwk5w==",
+ "jLkmUZ6fV56GfhC0nkh4GA==",
+ "3v09RHCPTLUztqapThYaHg==",
+ "nULSbtw2dXbfVjZh33pDiA==",
+ "IHhyR6+5sZXTH+/NrghIPg==",
+ "tnUtJ/DQX9WaVJyTgemsUA==",
+ "7xTKFcog69nTmMfr5qFUTA==",
+ "IshzWega6zr3979khNVFQQ==",
+ "Ng5v/B9Z10TTfsDFQ/XrXQ==",
+ "hnCUnoxofUiqQvrxl73M8w==",
+ "VPa7DG6v7KnzMvtJPb88LQ==",
+ "4LtQrahKXVtsbXrEzYU1zQ==",
+ "Ev/xjTi7akYBI7IeZJ4Igw==",
+ "41WEjhYUlG6jp2UPGj11eQ==",
+ "JvXTdChcE3AqMbFYTT3/wg==",
+ "2rOkEVl90EPqfHOF5q2FYw==",
+ "mjFBVRJ7TgnJx+Q74xllPg==",
+ "Uy4QI8D2y1bq/HDNItCtAw==",
+ "wMOE/pEKVIklE75xjt6b6w==",
+ "ZcuIvc8fDI+2uF0I0uLiVA==",
+ "CX/N/lHckmAtHKysYtGdZA==",
+ "j8to4gtSIRYpCogv2TESuQ==",
+ "iS9wumBV5ktCTefFzKYfkA==",
+ "ewPT4dM12nDWEDoRfiZZnA==",
+ "vWn9OPnrJgfPavg4D6T/HQ==",
+ "J/PNYu4y6ZMWFFXsAhaoow==",
+ "catI+QUNk3uJ+mUBY3bY8Q==",
+ "F8tEIT5EhcvLNRU5f0zlXQ==",
+ "zyA9f5J7mw5InjhcfeumAQ==",
+ "MlOOZOwcRGIkifaktEq0aQ==",
+ "Pt3i49uweYVgWze3OjkjJA==",
+ "sfIClgTMtZo9CM9MHaoqhQ==",
+ "HeQbUuBM9sqfXFXRBDISSw==",
+ "SFn78uklZfMtKoz2N0xDaQ==",
+ "H6j2nPbBaxHecXruxiWYkA==",
+ "fU32wmMeD44UsFSqFY0wBA==",
+ "hDILjSpTLqJpiSSSGu445A==",
+ "ieEAgvK9LsWh2t6DsQOpWA==",
+ "xfjBQk3CrNjhufdPIhr91A==",
+ "j+8/VARfbQSYhHzj0KPurQ==",
+ "/zFLRvi75UL8qvg+a6zqGg==",
+ "U0KmEI6e5zJkaI4YJyA5Ew==",
+ "uXvr6vi5kazZ9BCg2PWPJA==",
+ "jEqP0dyHKHiUjZ9dNNGTlQ==",
+ "1xWx5V3G9murZP7srljFmA==",
+ "OIwtfdq37eQ0qoXuB2j7Hw==",
+ "fUAy3f9bAglLvZWvkO2Lug==",
+ "duRFqmvqF93uf/vWn8aOmg==",
+ "ysRQ+7Aq7eVLOp88KnFVMA==",
+ "CkZUmKBAGu0FLpgPDrybpw==",
+ "TrLmfgwaNATh24eSrOT+pw==",
+ "83wtvSoSP9FVBsdWaiWfpA==",
+ "pUfWmRXo70yGkUD/x5oIvA==",
+ "PybPZhJErbRTuAafrrkb3g==",
+ "8hsfXqi4uiuL+bV1VrHqCw==",
+ "TVlHoi8J7sOZ2Ti7Dm92cQ==",
+ "za4rzveYVMFe3Gw531DQJQ==",
+ "JKphO0UYjFqcbPr6EeBuqg==",
+ "hqeSvwu8eqA072iidlJBAw==",
+ "bUF0JIfS4uKd3JZj2xotLQ==",
+ "hKOsXOBoFTl/K4xE+RNHDA==",
+ "JHBjKpCgSgrNNACZW1W+1w==",
+ "Rrq0ak9YexLqqbSD4SSXlw==",
+ "+NmjwjsPhGJh9bM10SFkLw==",
+ "xMIHeno2qj3V8q9H1xezeg==",
+ "TcFinyBrUoAEcLzWdFymow==",
+ "Rvchz/xjcY9uKiDAkRBMmA==",
+ "TYlnrwgyeZoRgOpBYneRAg==",
+ "PbnxuVerGwHyshkumqAARg==",
+ "iFtadcw8v6betKka9yaJfg==",
+ "7wgT9WIiMVcrj48PVAMIgw==",
+ "2HHqeGRMfzf3RXwVybx+ZQ==",
+ "tdgI9v7cqJsgCAeW1Fii1A==",
+ "4ZFYKa7ZgvHyZLS6WpM8gA==",
+ "gB8wkuIzvuDAIhDtNT1gyA==",
+ "g1ELwsk6hQ+RAY1BH640Pg==",
+ "UZoibx+y1YJy/uRSa9Oa2w==",
+ "yS/yMnJDHW0iaOsbj4oPTg==",
+ "JzW+yhrjXW1ivKu3mUXPXg==",
+ "/wIZAye9h1TUiZmDW0ZmYA==",
+ "YK+q7uJObkQZvOwQ9hplMg==",
+ "Rs8deApkoosIJSfX7NXtAA==",
+ "MsCloSmTFoBpm7XWYb+ueQ==",
+ "3ltw31yJuAl4VT6MieEXXw==",
+ "1+qmrbC8c7MJ6pxmDMcKuA==",
+ "AYxGETZs477n2sa1Ulu/RQ==",
+ "Q0TJZxpn3jk67L7N+YDaNA==",
+ "OGpsXRHlaN8BvZftxh1e7A==",
+ "UbABE6ECnjB+9YvblE9CYw==",
+ "kZ0D191c/uv4YMG15yVLDw==",
+ "QWURrsEgxbJ8MWcaRmOWqw==",
+ "xiFlcSfa/gnPiO+LwbixcQ==",
+ "Szko0IPE7RX2+mfsWczrMg==",
+ "Ugt8HVC/aUzyWpiHd0gCOQ==",
+ "8j9GVPiFdfIRm/+ho7hpoA==",
+ "KR401XBdgCrtVDSaXqPEiA==",
+ "d0NBFiwGlQNclKObRtGVMQ==",
+ "XEwOJG24eaEtAuBWtMxhwg==",
+ "0Y6iiZjCwPDwD/CwJzfioQ==",
+ "MvMbvZNKbXFe2XdN+HtnpQ==",
+ "fsoXIbq0T0nmSpW8b+bj+g==",
+ "Uje3Ild84sN41JEg3PEHDg==",
+ "i6ZYpFwsyWyMJNgqUMSV1A==",
+ "+P5q4YD1Rr5SX26Xr+tzlw==",
+ "z4oKy2wKH+sbNSgGjbdHGw==",
+ "XwKWd03sAz8MmvJEuN08xA==",
+ "Xv0mNYedaBc57RrcbHr9OA==",
+ "9oUawSwUGOmb0sDn3XS6og==",
+ "9RGIQ2qyevNbSSEF36xk/A==",
+ "q8YF9G2jqydAxSqwyyys5Q==",
+ "m5JIUETVXcRza4VL4xlJbg==",
+ "aRpdnrOyu5mWB1P5YMbvOA==",
+ "rM/BOovNgnvebKMxZQdk7g==",
+ "fQS0jnQMnHBn7+JZWkiE/g==",
+ "gAoV4BZYdW1Wm712YXOhWQ==",
+ "hCzsi1yDv9ja5/o7t94j9Q==",
+ "CoLvjQDQGldGDqRxfQo+WQ==",
+ "pfGcaa49SM3S6yJIPk/EJQ==",
+ "yYp4iuI5f/y/l1AEJxYolQ==",
+ "Jj4IrSVpqQnhFrzNvylSzA==",
+ "4jeOFKuKpCmMXUVJSh9y0g==",
+ "+NMUaQ7XPsAi0rk7tTT9wQ==",
+ "Jt4Eg6MJn8O4Ph/K2LeSUA==",
+ "CiiUeJ0LeWfm7+gmEmYXtg==",
+ "c5Tc7rTFXNJqYyc0ppW+Iw==",
+ "4KJZPCE9NKTfzFxl76GWjg==",
+ "aXs9qTEXLTkN956ch3pnOA==",
+ "f5Xo7F1uaiM760Qbt978iw==",
+ "wpZqFkKafFpLcykN2IISqg==",
+ "vIORTYSHFIXk5E2NyIvWcQ==",
+ "prOsOG0adI4o+oz50moipw==",
+ "blygTgAHZJ3NzyAT33Bfww==",
+ "rBt6L/KLT7eybxKt5wtFdg==",
+ "vMuaLvAntJB5o7lmt/kVXA==",
+ "iujlt9fXcUXEYc+T2s5UjA==",
+ "LyYPOZKm8bBegMr5NTSBfg==",
+ "ZtWvgitOSRDWq7LAKYYd4Q==",
+ "kh51WUI5TRnKhur6ZEpRTQ==",
+ "VzQ1NwNv9btxUzxwVqvHQg==",
+ "8fJLQeIHaTnJ8wGqUiKU6g==",
+ "vvEH5A39TTe1AOC11rRCLA==",
+ "dihDsG7+6aocG6M9BWrCzQ==",
+ "3jqsY8/xTWELmu/az3Daug==",
+ "mpOtwBvle+nyY6lUBwTemw==",
+ "E1CvxFbuu9AYW604mnpGTw==",
+ "1LPC0BzhJbepHTSAiZ3QTw==",
+ "XpGXh76RDgXC4qnTCsnNHA==",
+ "3Gg9N7vjAfQEYOtQKuF/Eg==",
+ "+WpF8+poKmHPUBB4UYh/ig==",
+ "UNt7CNMtltJWq8giDciGyA==",
+ "RIZYDgXqsIdTf9o2Tp/S7g==",
+ "0QCQORCYfLuSbq94Sbt0bQ==",
+ "hvsZ5JmVevK1zclFYmxHaw==",
+ "3+9nURtBK3FKn0J9DQDa3g==",
+ "jdVMQqApseHH3fd91NFhxg==",
+ "VX+cVXV8p9i5EBTMoiQOQQ==",
+ "I5qDndyelK4Njv4YrX7S6w==",
+ "rWliqgfZ3/uCRBOZ9sMmdA==",
+ "vwno3vugCvt6ooT3CD4qIQ==",
+ "cffrYrBX3UQhfX1TbAF+GQ==",
+ "nOiwBFnXxCBfPCHYITgqNg==",
+ "LQttmX92SI94+hDNVd8Gtw==",
+ "iCF+GWw9/YGQXsOOPAnPHQ==",
+ "nwtCsN1xEYaHvEOPzBv+qQ==",
+ "CQpJFrpOvcQhsTXIlJli+Q==",
+ "tYeIZjIm0tVEsYxH1iIiUQ==",
+ "iCnm5fPmSmxsIzuRK6osrA==",
+ "tX8X8KoxUQ8atFSCxgwE1Q==",
+ "hZlX6qOfwxW5SPfqtRqaMw==",
+ "2aIx9UdMxxZWvrfeJ+DcTw==",
+ "TlJizlASbPtShZhkPww4UA==",
+ "p+bx+/WQWALXEBCTnIMr4w==",
+ "4VR5LiXLew6Nyn91zH9L4w==",
+ "bfUD03N2PRDT+MZ+WFVtow==",
+ "cTvDd8okNUx0RCMer6O8sw==",
+ "49jZr/mEW6fvnyzskyN40w==",
+ "vHmQUl4WHXs1E/Shh+TeyA==",
+ "fgXfRuqFfAu8qxbTi4bmhA==",
+ "Wn+Vj4eiWx0WPUHr3nFbyA==",
+ "2SwIiUwT4vRZPrg7+vZqDA==",
+ "nkedTagkmf6YE4tEY+0fKw==",
+ "8nOTDhFyZ8YUA4b6M5p84w==",
+ "qnzWszsyJhYtx8wkMN6b1g==",
+ "ka7pMp8eSiv92WgAsz2vdA==",
+ "pGQEWJ38hb/ZYy2P1+FIuw==",
+ "cVhdRFuZaW/09CYPmtNv5g==",
+ "prCOYlboBnzmLEBG/OeVrQ==",
+ "oIWwTbkVS5DDL47mY9/1KQ==",
+ "PKtXc4x4DEjM45dnmPWzyg==",
+ "f9ywiGXsz+PuEsLTV3zIbQ==",
+ "6G2bD3Y7qbGmfPqH9TqLFA==",
+ "DMHmyn2U2n+UXxkqdvKpnA==",
+ "XOG1PYgqoG8gVLIbVLTQgg==",
+ "1FSrgkUXgZot2CsmbAtkPw==",
+ "BxFP+4o6PSlGN78eSVT1pA==",
+ "EZVQGsXTZvht1qedRLF8bQ==",
+ "eYAQWuWZX2346VMCD6s7/A==",
+ "jkUpkLoIXuu7aSH8ZghIAQ==",
+ "mXPtbPaoNAAlGmUMmJEWBQ==",
+ "HLesnV3DL+FhWF3h6RXe8g==",
+ "nDAsSla+9XfAlQSPsXtzPA==",
+ "RAECgYZmcF4WxcFcZ4A0Ww==",
+ "W+M4BcYNmjj7xAximDGWsA==",
+ "ueODvMv/f9ZD8O0aIHn4sg==",
+ "cszpMdGbsbe6BygqMlnC9Q==",
+ "siHwJx6EgeB1gBT9z/vTyw==",
+ "FN7oLGBQGHXXn5dLnr/ElA==",
+ "Tud+AMyuFkWYYZ73yoJGpQ==",
+ "TuaG3wRdM9BWKAxh2UmAsg==",
+ "8CjmgWQSAAGcXX9kz3kssw==",
+ "ays5/F7JANIgPHN0vp2dqQ==",
+ "PCOGl7GIqbizAKj/sZmlwQ==",
+ "rZKD8oJnIj5fSNGiccfcvA==",
+ "gFEnTI8os2BfRGqx9p5x8w==",
+ "5r1ZsGkrzNQEpgt/gENibw==",
+ "1YO9G8qAhLIu2rShvekedw==",
+ "6ZKmm7IW7IdWuVytLr68CQ==",
+ "mMfn8OaKBxtetweulho+xQ==",
+ "GQJxu1SoMBH14KPV/G/KrQ==",
+ "IYIP2UBRyWetVfYLRsi1SQ==",
+ "Jit0X0srSNFnn8Ymi1EY+g==",
+ "ARCWkHAnVgBOIkCDQ19ZuA==",
+ "qA0sTaeNPNIiQbjIe1bOgQ==",
+ "iGI9uqMoBBAjPszpxjZBWQ==",
+ "+L1FDsr5VQtuYc2Is5QGjw==",
+ "4XNUmgwxsqDYsNmPkgNQYQ==",
+ "Yig+Wh18VIqdsmwtwfoUQw==",
+ "uqp92lAqjec8UQYfyjaEZw==",
+ "QiozlNcQCbqXtwItWExqJQ==",
+ "JFHutgSe1/SlcYKIbNNYwQ==",
+ "Y26jxXvl79RcffH8O8b9Ew==",
+ "bQ7J5mebp38rfP/fuqQOsg==",
+ "HI4ZIE5s8ez8Rb+Mv39FxA==",
+ "OzH7jTcyeM7RPVFtBdakpQ==",
+ "HLxROy6fx/mLXFTDSX4eLA==",
+ "s5RUHVRNAoKMuPR/Jkfc2Q==",
+ "X9QAaNjgiOeAWSphrGtyVw==",
+ "ALJWKUImVE40MbEooqsrng==",
+ "9MDG0WeBPpjGJLEmUJgBWg==",
+ "9RXymE9kCkDvBzWGyMgIWA==",
+ "vFox1d3llOeBeCUZGvTy0A==",
+ "r3lQAYOYhwlLnDWQIunKqg==",
+ "2os5s7j7Tl46ZmoZJH8FjA==",
+ "O5N2yd+QQggPBinQ+zIhtQ==",
+ "ZygAjaN62XhW5smlLkks+Q==",
+ "AgDJsaW0LkpGE65Kxk5+IA==",
+ "omAjyj1l6gyQAlBGfdxJTw==",
+ "fY9VATklOvceDfHZDDk57A==",
+ "StpQm/cQF8cT0LFzKUhC5w==",
+ "CYJB3qy5GalPLAv1KGFEZA==",
+ "coGEgMVs2b314qrXMjNumQ==",
+ "DQQB/l55iPN9XcySieNX3A==",
+ "6dshA8knH5qqD+KmR/kdSQ==",
+ "qyRmvxh8p4j4f+61c10ZFQ==",
+ "apWEPWUvMC24Y+2vTSLXoA==",
+ "RzX2OfSFEd//LhZwRwzBVw==",
+ "NdULoUDGhIolzw1PyYKV0A==",
+ "5w/c9WkI/FA+4lOtdPxoww==",
+ "bV9r7j2kNJpDCEM5E2339Q==",
+ "vbyiKeDCQ4q9dDRI1Q0Ong==",
+ "9xIgKpZGqq0/OU6wM5ZSHw==",
+ "RYkDwwng6eeffPHxt8iD9A==",
+ "w5N/aHbtOIKzcvG3GlMjGA==",
+ "3P2aJxV8Trll2GH9ptElYA==",
+ "yteeQr3ub2lDXgLziZV+DQ==",
+ "yqtj8GfLaUHYv/BsdjxIVw==",
+ "NyF+4VRog7etp90B9FuEjA==",
+ "uwA6N5LptSXqIBkTO0Jd7Q==",
+ "6lVSzYUQ/r0ep4W2eCzFpg==",
+ "1d7RPHdZ9qzAbG3Vi9BdFA==",
+ "7br49X11xc2GxQLSpZWjKQ==",
+ "peMW+rpwmXrSwplVuB/gTA==",
+ "RqYpA5AY7mKPaSxoQfI1CA==",
+ "dqVw2q2nhCvTcW82MT7z0g==",
+ "5S5/asYfWjOwnzYpbK6JDw==",
+ "NvkR0inSzAdetpI4SOXGhw==",
+ "tIqwBotg052wGBL65DZ+yA==",
+ "S4RvORcJ3m6WhnAgV4YfYA==",
+ "UAqf4owQ+EmrE45hBcUMEw==",
+ "4aPU6053cfMLHgLwAZJRNg==",
+ "3Y6/HqS1trYc9Dh778sefg==",
+ "ck86G8HsbXflyrK7MBntLg==",
+ "GLmWLXURlUOJ+PMjpWEXVA==",
+ "jNJQ6otieHBYIXA9LjXprg==",
+ "AsAHrIkMgc3RRWnklY9lJw==",
+ "FCLQocqxxhJeleARZ6kSPg==",
+ "3Leu2Sc+YOntJFlrvhaXeg==",
+ "hSkY45CeB6Ilvh0Io4W6cg==",
+ "DwrNdmU5VFFf3TwCCcptPA==",
+ "u2WQlcMxOACy6VbJXK4FwA==",
+ "E9IlDyULLdeaVUzN6eky8g==",
+ "EXveRXjzsjh8zbbQY2pM9g==",
+ "5VO1inwXMvLDBQSOahT6rg==",
+ "HaHTsLzx7V3G1SFknXpGxA==",
+ "MMaegl2Md9s/wOx5o9564w==",
+ "mpWNaUH9kn4WY26DWNAh3Q==",
+ "w3G+qXXqqKi8F5s+qvkBUg==",
+ "wM8tnXO4PDlLVHspZFcjYw==",
+ "LFcpCtnSnsCPD2gT/RA+Zg==",
+ "bhVbgJ4Do4v56D9mBuR/EA==",
+ "yU3N0HMSP5etuHPNrVkZtg==",
+ "FzqIpOcTsckSNHExrl+9jg==",
+ "BYz52gYI/Z6AbYbjWefcEA==",
+ "h3vYYI9yhpSZV2MQMJtwFQ==",
+ "adJAjAFyR2ne1puEgRiH+g==",
+ "eDcyiPaB954q5cPXcuxAQw==",
+ "40gCrW4YWi+2lkqMSPKBPg==",
+ "ulLuTZqhEDkX0EJ3xwRP9A==",
+ "y4iBxAMn/KzMmaWShdYiIw==",
+ "ilBBNK/IV69xKTShvI94fQ==",
+ "0HN6MIGtkdzNPsrGs611xA==",
+ "twPn6wTGqI0aR//0wP3xtA==",
+ "3UNJ37f+gnNyYk9yLFeoYA==",
+ "4SdHWowXgCpCDL28jEFpAw==",
+ "Mr5mCtC53+wwmwujOU/fWw==",
+ "81pAhreEPxcKse+++h1qBg==",
+ "KmcGEE0pacQ/HDUgjlt7Pg==",
+ "Gt4/MMrLBErhbFjGbiNqQQ==",
+ "lf1fwA0YoWUZaEybE+LyMQ==",
+ "RIVYGO2smx9rmRoDVYMPXw==",
+ "rJ9qVn8/2nOxexWzqIHlcQ==",
+ "lfOLLyZNbsWQgHRhicr4ag==",
+ "wgH1GlUxWi6/yLLFzE76uQ==",
+ "Qg1ubGl+orphvT990e5ZPA==",
+ "Z5B+uOmPZbpbFWHpI9WhPw==",
+ "snGTzo540cCqgBjxrfNpKw==",
+ "ZqkmoGB0p5uT5J6XBGh7Tw==",
+ "uPi8TsGY3vQsMVo/nsbgVQ==",
+ "Y5XR8Igvau/h+c1pRgKayg==",
+ "ZmVpw1TUVuT13Zw/MNI5hQ==",
+ "60suecbWRfexSh7C67RENA==",
+ "kZ/mZZg9YSDmk2rCGChYAg==",
+ "OpL+vHwPasW30s2E1TYgpA==",
+ "ZVnErH1Si4u51QoT0OT7pA==",
+ "3pi3aNVq1QNJmu1j0iyL0g==",
+ "tb5+2dmYALJibez1W4zXgA==",
+ "jOPdd330tB6+7C29a9wn0Q==",
+ "5oD/aGqoakxaezq43x0Tvw==",
+ "HdB7Se47cWjPgpJN0pZuiA==",
+ "6WhHPWlqEUqXC52rHGRHjA==",
+ "WLwpjgr9KzevuogoHZaVUw==",
+ "E8yMPK7W0SIGTK6gIqhxiQ==",
+ "1/Hxu8M9N/oNwk8bCj4FNQ==",
+ "Uo1ebgsOxc3eDRds1ah3ag==",
+ "5pqqzC/YmRIMA9tMFPi7rg==",
+ "ri4AOITPdB1YHyXV+5S51g==",
+ "HfvsiCQN/3mT0FabCU5ygQ==",
+ "UQTQk5rrs6lEb1a+nkLwfg==",
+ "VH70dN82yPCRctmAHMfCig==",
+ "yD3Dd4ToRrl53k/2NSCJiw==",
+ "fO0+6TsjL+45p9mSsMRiIg==",
+ "fM5uYpkvJFArnYiQ3MrQnA==",
+ "V+QzdKh5gxTPp2yPC9ZNEg==",
+ "XHHEg/8KZioW/4/wgSEkbQ==",
+ "2abfl3N46tznOpr+94VONQ==",
+ "gxwbqZDHLbQVqXjaq42BCg==",
+ "WnHK5ZQDR6Da5cGODXeo0A==",
+ "SChDh/Np1HyTPWfICfE1uA==",
+ "yhexr/OFKfZl0o3lS70e4w==",
+ "N65PqIWiQeS082D6qpfrAg==",
+ "RM5CpIiB94Sqxi462G7caA==",
+ "CBAGa5l95f3hVzNi6MPWeQ==",
+ "OHJBT2SEv5b5NxBpiAf7oQ==",
+ "p48i7AfSSAyTdJSyHvOONw==",
+ "/SP6pOdYFzcAl2OL05z4uQ==",
+ "N8dXCawxSBX40fgRRSDqlQ==",
+ "bMWFvjM8eVezU1ZXKmdgqw==",
+ "Um1ftRBycvb+363a90Osog==",
+ "QAz7FA+jpz9GgLvwdoNTEQ==",
+ "qO4HlyHMK5ygX+6HbwQe8w==",
+ "UgvtdE2eBZBUCAJG/6c0og==",
+ "q5g3c8tnQTW2EjNfb2sukw==",
+ "gsC/mWD8KFblxB0JxNuqJw==",
+ "SVFbcjXbV7HRg+7jUrzpwg==",
+ "bz294kSG4egZnH2dJ8HwEg==",
+ "ybpTgPr3SjJ12Rj5lC/IMA==",
+ "yDrAd1ot38soBk7zKdnT8A==",
+ "BB/R8oQOcoE4j63Hrh8ifg==",
+ "GNrMvNXQkW7PydlyJa+f1w==",
+ "w0PKdssv+Zc5J/BbphoxpA==",
+ "D5ibbo8UJMfFZ48RffuhgQ==",
+ "MdvhC1cuXqni/0mtQlSOCw==",
+ "wQKL8Ga6JQkpZ7yymDkC3w==",
+ "o1uhaQg5/zfne84BFAINUQ==",
+ "Ft2wXUokFdUf6d2Y/lwriw==",
+ "sLJrshdEANp0qk2xOUtTnQ==",
+ "jx7rpxbm1NaUMcE2ktg5sA==",
+ "ZQ0ZnTsZKWxbRj7Tilh24Q==",
+ "KhrIIHfqXl9zGE9aGrkRVg==",
+ "jS0JuioLGAVaHdo/96JFoQ==",
+ "tr+U/vt+MIGXPRQYYWJfRg==",
+ "TXab/hqNGWaSK+fXAoB2bg==",
+ "0K4NBxqEa3RYpnrkrD/XjQ==",
+ "3oMTbWf7Bv83KRlfjNWQZA==",
+ "yLAhLNezvqVHmN1SfMRrPw==",
+ "ZYW30FfgwHmW6nAbUGmwzA==",
+ "CZNoTy26VUQirvYxSPc/5A==",
+ "CF1sAlhjDQY/KWOBnSSveA==",
+ "+CLf5witKkuOvPCulTlkqw==",
+ "1m1yD4L9A7Q1Ot+wCsrxJQ==",
+ "2E41e0MgM3WhFx2oasIQeA==",
+ "mDXHuOmI4ayjy2kLSHku1Q==",
+ "sCLMrLjEUQ6P1L8tz90Kxg==",
+ "zDUZCzQesFjO1JI3PwDjfg==",
+ "x/BIDm6TKMhqu/gtb3kGyw==",
+ "DEaZD/8aWV6+zkiLSVN/gA==",
+ "7dz+W494zwU5sg63v5flCg==",
+ "Y5iDQySR2c3MK7RPMCgSrw==",
+ "GglPoW5fvr4JSM3Zv99oiA==",
+ "myzvc+2MfxGD9uuvZYdnqQ==",
+ "V9G1we3DOIQGKXjjPqIppQ==",
+ "gYvdNJCDDQmNhtJ6NKSuTA==",
+ "rXtGpN17Onx8LnccJnXwJQ==",
+ "/a+bLXOq02sa/s8h7PhUTg==",
+ "htNVAogFakQkTX6GHoCVXg==",
+ "eshD40tvOA6bXb0Fs/cH3A==",
+ "K1CGbMfhlhIuS0YHLG30PQ==",
+ "aOeJZUIZM9YWjIEokFPnzQ==",
+ "r0hAwlS0mPZVfCSB+2G6uQ==",
+ "0q+erphtrB+6HBnnYg7O6w==",
+ "bkRdUHAksJZGzE1gugizYQ==",
+ "J8v2f6hWFu8oLuwhOeoQjA==",
+ "qkvEep4vvXhc2ZJ6R449Mg==",
+ "6HGeEPyTAu9oiKhNVLjQnA==",
+ "JoATsk/aJH0UcDchFMksWA==",
+ "QozQL0DTtr+PXNKifv6l6g==",
+ "HiAgt86AyznvbI2pnLalVQ==",
+ "lY+tivtsfvU0LJzBQ6itYQ==",
+ "EfXDc6h69aBPE6qsB+6+Ig==",
+ "gnAIpoCyl3mQytLFgBEgGA==",
+ "p2JPOX8yDQ0agG+tUyyT/g==",
+ "zeELfk015D5krExLKRUYtg==",
+ "wDiGoFEfIVEDyyc4VpwhWQ==",
+ "7Ephy+mklG2Y3MFdqmXqlA==",
+ "8ZFPMJJYVJHsfRpU4DigSg==",
+ "ocRh5LR1ZIN9Johnht8fhQ==",
+ "l5f3I6osM9oxLRAwnUnc5A==",
+ "yxCyBXqGWA735JEyljDP7Q==",
+ "qE/h/Z+6buZWf+cmPdhxog==",
+ "HCu4ZMrcLMZbPXbTlWuvvQ==",
+ "TDrq23VUdzEU/8L5i8jRJQ==",
+ "L+N/6geuokiLPPSDXM9Qkg==",
+ "v6jZicMNM3ysm3U5xu0HoQ==",
+ "b85nxzs8xiHxaqezuDVWvg==",
+ "ca+kx+kf7JuZ3pfYKDwFlg==",
+ "KlY5TGg0pR/57TVX+ik1KQ==",
+ "3jmCreW5ytSuGfmeLv7NfQ==",
+ "ucLMWnNDSqE4NOCGWvcGWw==",
+ "NSrzwNlB0bde3ph8k6ZQcQ==",
+ "nL4iEd3b5v4Y9fHWDs+Lrw==",
+ "W2x0SBzSIsTRgyWUCOZ/lg==",
+ "ifZM0gBm9g9L09YlL+vXBg==",
+ "4WcFEswYU/HHQPw77DYnyA==",
+ "TLJbasOoVO435E5NE5JDcA==",
+ "WyCFB4+6lVtlzu3ExHAGbQ==",
+ "BW0A06zoQw7S+YMGaegT7g==",
+ "qP1cCE4zsKGTPhjbcpczMw==",
+ "UVEZPoH9cysC+17MKHFraw==",
+ "eQ45Mvf5in9xKrP6/qjYbg==",
+ "fOARCnIg/foF/6tm7m9+3w==",
+ "lK2xe+OuPutp4os0ZAZx5w==",
+ "Tug3eh+28ttyf+U7jfpg5w==",
+ "ENFfP93LA257G6pXQkmIdg==",
+ "FuWspiqu5g8Eeli5Az+BkA==",
+ "kIGxCUxSlNgsKZ45Al1lWw==",
+ "RzeH+G3gvuK1z+nJGYqARQ==",
+ "0ofMbUCA3/v5L8lHnX4S5w==",
+ "VI8pgqBZeGWNaxkuqQVe7g==",
+ "x6lNRGgJcRxgKTlzhc1WPg==",
+ "La0gzdbDyXUq6YAXeKPuJA==",
+ "dAq8/1JSQf1f4QPLUitp0g==",
+ "WN7lFJfw4lSnTCcbmt5nsg==",
+ "2aDK0tGNgMLyxT+BQPDE8Q==",
+ "9W57pTzc572EvSURqwrRhw==",
+ "37Nkh06O979nt7xzspOFyQ==",
+ "4TQkMnRsXBobbtnBmfPKnA==",
+ "f/BjtP5fmFw2dRHgocbFlg==",
+ "9vEgJVJLEfed6wJ7hBUGgQ==",
+ "HRWYX2XOdsOqYzCcqkwIyw==",
+ "StDtLMlCI75g4XC59mESEQ==",
+ "99+SBN45LwKCPfrjUKRPmw==",
+ "HbT6W1Ssd3W7ApKzrmsbcg==",
+ "l8/KMItWaW3n4g1Yot/rcQ==",
+ "s7iW1M6gkAMp+D/3jHY58w==",
+ "GWwJ32SZqD5wldrXUdNTLA==",
+ "YhLEPsi/TNyeUJw69SPYzQ==",
+ "g0aTR8aJ0uVy3YvGYu5xrw==",
+ "m6get5wjq5j1i5abnpXuZQ==",
+ "ymtA8EMPMgmMcimWZZ0A1Q==",
+ "HEcOaEd9zCoOVbEmroSvJg==",
+ "F8l+Qd9TZgzV+r8G584lKA==",
+ "3yDD+xT8iRfUVdxcc7RxKw==",
+ "1eRUCdIJe3YGD5jOMbkkOg==",
+ "DO1/jfP/xBI9N0RJNqB2Rw==",
+ "SiSlasZ+6U2IZYogqr2UPg==",
+ "tBQDfy48FnIOZI04rxfdcA==",
+ "HEghmKg3GN60K7otpeNhaA==",
+ "mTLBkP+yGHsdk5g7zLjVUw==",
+ "RgtwfY5pTolKrUGT+6Pp6g==",
+ "EyIsYQxgFa4huyo/Lomv7g==",
+ "HwLSUie8bzH+pOJT3XQFyg==",
+ "7Tauesu7bgs5lJmQROVFiQ==",
+ "ojugpLIfzflgU2lonfdGxA==",
+ "ZqjnqxZE/BjOUY0CMdVl0g==",
+ "oQjugfjraFziga1BcwRLRA==",
+ "JXCYeWjFqcdSf6QwB54G+A==",
+ "TeBGJCqSqbzvljIh9viAqA==",
+ "1Gpj4TPXhdPEI4zfQFsOCg==",
+ "asouSfUjJa8yfMG7BBe+fA==",
+ "ccy3Ke2k4+evIw0agHlh3w==",
+ "CzSumIcYrZlxOUwUnLR2Zw==",
+ "9QFYrCXsGsInUb4SClS3cQ==",
+ "3RTtSaMp1TZegJo5gFtwwA==",
+ "aTWiWjyeSDVY/q8y9xc2zg==",
+ "UK+R+hAoVeZ4xvsoZjdWpw==",
+ "rHagXw+CkF3uEWPWDKXvog==",
+ "MfkyURTBfkNZwB+wZKjP4g==",
+ "Qf7JFJJuuacSzl6djUT2EQ==",
+ "K1RL+tLjICBvMupe7QppIQ==",
+ "R2OOV18CV/YpWL1xzr/VQg==",
+ "o+areESiXgSO0Lby56cBeg==",
+ "VPqyIomYm7HbK5biVDvlpw==",
+ "pw1jplCdTC+b0ThX0FXOjw==",
+ "gTnsH3IzALFscTZ1JkA9pw==",
+ "JYJvOZ4CHktLrYJyAbdOnA==",
+ "P8lUiLFoL100c9YSQWYqDA==",
+ "LATQEY7f47i77M6p11wjWA==",
+ "U9kE50Wq5/EHO03c5hE4Ug==",
+ "pFKzcRHSUBqSMtkEJvrR1Q==",
+ "vHVXsAMQqc0qp7HA5Q+YkA==",
+ "3XyoREdvhmSbyvAbgw2y/A==",
+ "qOEIUWtGm5vx/+fg4tuazg==",
+ "a6IszND1m+6w+W+CvseC7g==",
+ "KuNY8qAJBce+yUIluW8AYw==",
+ "5Wcq+6hgnWsQZ/bojERpUw==",
+ "l2ZB9TvT68rn8AAN4MdxWw==",
+ "h5HsEsObPuPFqREfynVblw==",
+ "fvm0IQfnbfZFETg9v3z/Fg==",
+ "QV0OG5bpjrjku4AzDvp9yw==",
+ "nMuMtK/Zkb3Xr34oFuX/Lg==",
+ "jMZKSMP2THqwpWqJNJRWdw==",
+ "fX4G68hFL7DmEmjbWlCBJQ==",
+ "ZlBNHAiYsfaEEiPQ1z+rCA==",
+ "ckugAisBNX18eQz+EnEjjw==",
+ "Dt6hvhPJu94CJpiyJ5uUkg==",
+ "eYE9No9sN5kUZ5ePEyS3+Q==",
+ "Tp52d1NndiC9w3crFqFm9g==",
+ "MBjMU/17AXBK0tqyARZP5w==",
+ "1EI9aa955ejNo1dJepcZJw==",
+ "FqWLkhWl0iiD/u2cp+XK9A==",
+ "j8nMH8mK/0Aae7ZkqyPgdg==",
+ "ZtmnX24AwYAXHb2ZDC6MeQ==",
+ "who8uUamlHWHXnBf7dwy4A==",
+ "CmkmWcMK4eqPBcRbdnQvhw==",
+ "61V74uIjaSfZM8au1dxr1A==",
+ "778O1hdVKHLG2q9dycUS0Q==",
+ "IdadoCPmSgHDHzn1zyf8Jw==",
+ "Z2rwGmVEMCY6nCfHO3qOzw==",
+ "Q3TpCE+wnmH/1h/EPWsBtQ==",
+ "HnVfyqgJ+1xSsN4deTXcIA==",
+ "XgPHx2+ULpm14IOZU2lrDg==",
+ "IbN736G1Px5bsYqE5gW1JQ==",
+ "nY/H7vThZ+dDxoPRyql+Cg==",
+ "wlWxtQDJ+siGhN2fJn3qtw==",
+ "MrbEUlTagbesBNg0OemHpw==",
+ "LJtRcR70ug6UHiuqbT6NGw==",
+ "hSNZWNKUtDtMo6otkXA/DA==",
+ "LawT9ZygiVtBk0XJ+KkQgQ==",
+ "DLzHkTjjuH6LpWHo2ITD0Q==",
+ "i8XXN7jcrmhnrOVDV8a2Hw==",
+ "ogcuGHUZJkmv+vCz567a2g==",
+ "rUp5Mfc57+A8Q29SPcvH/Q==",
+ "6706ncrH1OANFnaK6DUMqQ==",
+ "gK7dhke5ChQzlYc/bcIkcg==",
+ "t3Txxjq43e/CtQmfQTKwWg==",
+ "6ZMs9vCzK9lsbS6eyzZlIA==",
+ "uTHBqApdKOAgdwX3cjrCYQ==",
+ "zirOtGUXeRL22ezfotZfQg==",
+ "iK0dWKHjVVexuXvMWJV9pg==",
+ "uzEgwx1iAXAvWPKSVwYSeQ==",
+ "FHvI0IVNvih8tC7JgzvCOw==",
+ "jjNMPXbmpFNsCpWY0cv3eg==",
+ "/cJ0Nn5YbXeUpOHMfWXNHQ==",
+ "WkSJpxBa45XJRWWZFee7hw==",
+ "edlXkskLx287vOBZ9+gVYg==",
+ "+Pl0bSMBAdXpRIA+zE02JA==",
+ "3xw8+0/WU51Yz4TWIMK8mw==",
+ "GdTanUprpE3X/YjJDPpkhQ==",
+ "qnsBdl050y9cUaWxbCczRw==",
+ "pnJnBzAJlO4j3IRqcfmhkQ==",
+ "USq1iF90eUv41QBebs3bhw==",
+ "QH3lAwOYBAJ0Fd5pULAZqw==",
+ "gvvyX5ATi4q9NhnwxRxC8w==",
+ "7xDIG/80SnhgxAYPL9YJtg==",
+ "WVhfn2yJZ43qCTu0TVWJwA==",
+ "twjiDKJM7528oIu/el4Zbg==",
+ "6sBemZt4qY/TBwqk3YcLOQ==",
+ "m3XYojKO+I6PXlVRUQBC3w==",
+ "gUNP5w7ANJm257qjFxSJrA==",
+ "mMLhjdWNnZ8zts9q+a2v3g==",
+ "kjWYVC7Eok2w2YT4rrI+IA==",
+ "ZzT5b0dYQXkQHTXySpWEaA==",
+ "YzTV0esAxBFVls3e0qRsnA==",
+ "9xmtuClkFlpz/X5E9JBWBA==",
+ "nhAnHuCGXcYlqzOxrrEe1g==",
+ "cbBXgB1WQ/i8Xul0bYY2fg==",
+ "AkAes5oErTaJiGD2I4A1Pw==",
+ "Wx9jh/teM0LJHrvTScssyQ==",
+ "fU5ZZ1bIVsV+eXxOpGWo/Q==",
+ "k8eZxqwxiN/ievXdLSEL/w==",
+ "E2LR1aZ3DcdCBuVT7BhReA==",
+ "1eCHcz4swFH+uRhiilOinQ==",
+ "JipruVZx4ban3Zo5nNM37g==",
+ "IPLD9nT5EEYG9ioaSIYuuA==",
+ "pHozgRyMiEmyzThtJnY4MQ==",
+ "p0eNK7zJd7D/HEGaVOrtrQ==",
+ "dGjcKAOGBd4gIjJq7fL+qQ==",
+ "uMq8cDVWFD+tpn8aeP8Pqg==",
+ "gC7gUwGumN7GNlWwfIOjJQ==",
+ "It+K/RCYMOfNrDZxo7lbcA==",
+ "4CfEP8TeMKX33ktwgifGgA==",
+ "nxDGRpePV3H4NChn4eLwag==",
+ "300hoYyMR/mk1mfWJxS8/w==",
+ "DmxgZsQg+Qy1GP0fPkW3VA==",
+ "1vqRt79ukuvdJNyIlIag8Q==",
+ "RWI0HfpP7643OSEZR8kxzw==",
+ "zZtYkKU50PPEj6qSbO5/Sw==",
+ "UNRlg6+CYVOt68NwgufGNA==",
+ "kkbX+a00dfiTgbMI+aJpMg==",
+ "VIC7inSiqzM6v9VqtXDyCw==",
+ "l+x2QhxG8wb5AQbcRxXlmA==",
+ "GUiinC3vgBjbQC2ybMrMNQ==",
+ "6uMF5i0b/xsk55DlPumT7A==",
+ "aK9nybtiIBUvxgs1iQFgsw==",
+ "BLbTFLSb4mkxMaq4/B2khg==",
+ "mTAqtg6oi0iytHQCaSVUsA==",
+ "eBapvE+hdyFTsZ0y5yrahg==",
+ "lHN2dn2cUKJ8ocVL3vEhUQ==",
+ "Mj87ajJ/yR41XwAbFzJbcA==",
+ "FA+nK6mpFWdD0kLFcEdhxA==",
+ "FrTgaF5YZCNkyfR1kVzTLQ==",
+ "5eHStFN7wEmIE+uuRwIlPQ==",
+ "AyWlT+EGzIXc395zTlEU5Q==",
+ "I+wVQA+jpPTJ6xEsAlYucg==",
+ "Y1flEyZZAYxauMo4cmtJ1w==",
+ "1AeReq55UQotRQVKJ66pmg==",
+ "xzGzN5Hhbh0m/KezjNvXbQ==",
+ "meHzY9dIF7llDpFQo1gyMg==",
+ "RnOXOygwJFqrD+DlM3R5Ew==",
+ "JKg64m6mU7C/CkTwVn4ASg==",
+ "gGLz3Ss+amU7y6JF09jq7A==",
+ "Pu9pEf+Tek3J+3jmQNqrKw==",
+ "EATnlYm0p3h04cLAL95JgA==",
+ "o64LDtKq/Fulf1PkVfFcyg==",
+ "hUWqqG1QwYgGC5uXJpCvJw==",
+ "RfSwpO/ywQx4lfgeYlBr2w==",
+ "VaJc9vtYlqJbRPGb5Tf0ow==",
+ "9JKIJrlQjhNSC46H3Cstcw==",
+ "6Z9myGCF5ylWljgIYAmhqw==",
+ "9bAWYElyRN1oJ6eJwPtCtQ==",
+ "ohK6EftXOqBzIMI+5XnESw==",
+ "AVjwqrTBQH1VREuBlOyUOg==",
+ "G2UponGde3/Z+9b2m9abpQ==",
+ "DoiItHSms0B9gYmunVbRkQ==",
+ "vUC0HlTTHj6qNHwfviDtAw==",
+ "hq35Fjgvrcx6I9e6egWS4w==",
+ "sw+bmpzqsM4gEQtnqocQLQ==",
+ "ApiuEPWr8UjuRyJjsYZQBw==",
+ "VXu4ARjq7DS2IR/gT24Pfw==",
+ "3TbRZtFtsh9ez8hqZuTDeA==",
+ "CazLJMJjQMeHhYLwXW7YNg==",
+ "ROSt+NlEoiPFtpRqKtDUrQ==",
+ "IUwVHH6+8/0c+nOrjclOWA==",
+ "lkzFdvtBx5bV6xZO0cxK7g==",
+ "4ekt4m38G9m599xJCmhlug==",
+ "fzkmVWKhJsxyCwiqB/ULnQ==",
+ "LZAKplVoNjeQgfaHqkyEJA==",
+ "91vfsZ7Lx9x5gqWTOdM4sg==",
+ "MVoxyIA+emaulH8Oks8Weg==",
+ "oGH7SMLI2/qjd9Vnhi3s0A==",
+ "vmqfGJE6r4yDahtU/HLrxw==",
+ "Y5KKN7t/v9JSxG/m1GMPSA==",
+ "gXlb7bbRqHXusTE5deolGA==",
+ "/2c4oNniwhL3z5IOngfggg==",
+ "HgIFX42oUdRPu7sKAXhNWg==",
+ "A3dX2ShyL9+WOi6MNJBoYQ==",
+ "hN9bmMHfmnVBVr+7Ibd2Ng==",
+ "DB706G73NpBSRS8TKQOVZw==",
+ "JSyq2MIuObPnEgEUDyALjQ==",
+ "kSUectNPXpXNg+tIveTFRw==",
+ "XVVy3e6dTnO3HpgD6BtwQw==",
+ "td7nDgTDmKPSODRusMcupw==",
+ "Lt/pVD4TFRoiikmgAxEWEw==",
+ "mmRob7iyTkTLDu8ObmTPow==",
+ "Fd0c8f2eykUp9GYhqOcKoA==",
+ "18RKixTv12q3xoBLz6eKiA==",
+ "RClzwwKh51rbB4ekl99EZA==",
+ "oONlXCW4aAqGczQ/bUllBw==",
+ "foPAmiABJ3IXBoed2EgQXA==",
+ "wEJDulZafLuXCvcqBYioFQ==",
+ "K1RgR6HR5uDEQgZ32TAFgA==",
+ "SEIZhyguLoyH7So0p1KY0A==",
+ "ggIfX1J4dX3xQoHnHUI7VA==",
+ "HBRzLacCVYfwUVGzrefZYg==",
+ "aWZRql2IUPVe9hS3dxgVfQ==",
+ "Err1mbWJud80JNsDEmXcYg==",
+ "Z2MkqmpQXdlctCTCUDPyzw==",
+ "JnE6BK0vpWIhNkaeaYNUzw==",
+ "5dUry23poD+0wxZ3hH6WmA==",
+ "DwP0MQf71VsqvAbAMtC3QQ==",
+ "kHcBZXoxnFJ+GMwBZ/xhfQ==",
+ "SUAwMWLMml8uGqagz5oqhQ==",
+ "79uTykH43voFC3XhHHUzKg==",
+ "P5fucOJhtcRIoElFJS4ffg==",
+ "s8NpalwgPdHPla7Zi9FJ3w==",
+ "8cXqZub6rjgJXmh1CYJBOg==",
+ "tY916jrSySzrL+YTcVmYKQ==",
+ "DRiFNojs7wM8sfkWcmLnhQ==",
+ "wqUJ1Gq1Yz2cXFkbcCmzHQ==",
+ "0u+0WHr7WI6IlVBBgiRi6w==",
+ "GCYI9Dn1h3gOuueKc7pdKA==",
+ "nVDxVhaa2o38gd1XJgE3aw==",
+ "5I/heFSQG/UpWGx0uhAqGQ==",
+ "1PvTn90xwZJPoVfyT5/uIQ==",
+ "jHOoSl3ldFYr9YErEBnD3w==",
+ "swJhrPwllq5JORWiP5EkDA==",
+ "tj2rWvF2Fl+XIccctj8Mhw==",
+ "QvYZxsLdu+3nV/WhY1DsYg==",
+ "fKalNdhsyxTt1w08bv9fJA==",
+ "CHLHizLruvCrVi9chj9sXA==",
+ "sa2DECaqYH1z1/AFhpHi+g==",
+ "LbPp1oL0t3K2BAlIN+l8DA==",
+ "5SbwLDNT6sBOy6nONtUcTg==",
+ "AfVPdxD3FyfwwNrQnVNQ7A==",
+ "jt9Ocr9D8EwGRgrXVz//aQ==",
+ "KkwQL0DeUM3nPFfHb2ej+A==",
+ "WwraoO97OTalvavjUsqhxQ==",
+ "fAKFfwlCOyhtdBK6yNnsNg==",
+ "EqMlrz1to7HG4GIFTPaehQ==",
+ "YmjZJyNfHN5FaTL/HAm8ww==",
+ "L2D7G0btrwxl9V4dP3XM5Q==",
+ "oUqO4HrBvkpSL781qAC9+w==",
+ "c6Yhwy/q3j7skXq52l36Ww==",
+ "FWphIPZMumqnXr1glnbK4w==",
+ "AcKwfS8FRVqb72uSkDNY/Q==",
+ "uSIiF1r9F18avZczmlEuMQ==",
+ "XrFDomoH2qFjQ2jJ2yp9lA==",
+ "N2X7KWekNN+fMmwyXgKD5w==",
+ "IdmcpJXyVDajzeiGZixhSA==",
+ "Wf2olJCYZRGTTZxZoBePuQ==",
+ "oVlG+0rjrg2tdFImxIeVBA==",
+ "7w4PDRJxptG8HMe/ijL6cQ==",
+ "rueNryrchijjmWaA3kljYg==",
+ "ZybIEGf1Rn/26vlHmuMxhw==",
+ "yYVW07lOZHdgtX42xJONIA==",
+ "4ifNsmjYf1iOn2YpMfzihg==",
+ "KTjwL+qswa+Bid8xLdjMTg==",
+ "THfzE2G2NVKKfO+A2TjeFw==",
+ "QoqHzpHDHTwQD5UF30NruQ==",
+ "dTMoNd6DDr1Tu8tuZWLudw==",
+ "wOc4TbwQGUwOC1B3BEZ4OQ==",
+ "gfhkPuMvjoC3CGcnOvki3Q==",
+ "vljJciS+uuIvL7XXm5688g==",
+ "EGLOaMe6Nvzs/cmb7pNpbg==",
+ "oLWWIn/2AbKRHnddr2og9g==",
+ "7l0RMKbONGS/goW/M+gnMQ==",
+ "eFkXKRd2dwu/KWI5ZFpEzw==",
+ "jWsC7kdp2YmIZpfXGUimiA==",
+ "Jcxjli2tcIAjCe+5LyvqdQ==",
+ "MUkRa/PjeWMhbCTq43g6Aw==",
+ "g2nh2xENCFOpHZfdEXnoQA==",
+ "x6M66krXSi0EhppwmDmsxA==",
+ "26Wmdp6SkKN74W0/XPcnmA==",
+ "ycjv4XkS5O7zcF3sqq9MwQ==",
+ "gfnbviaVhKvv1UvlRGznww==",
+ "aIPde9CtyZrhbHLK740bfw==",
+ "0p8YbEMxeb73HbAfvPLQRw==",
+ "Is3uxoSNqoIo5I15z6Z2UQ==",
+ "NZtcY8fIpSKPso/KA6ZfzA==",
+ "iQ304I1hmLZktA1d1cuOJA==",
+ "0QB0OUW5x2JLHfrtmpZQ+w==",
+ "kgyUtd8MFe0tuuxDEUZA9w==",
+ "AcbG0e6xN8pZfYAv7QJe1Q==",
+ "bb/U8UynPHwczew/hxLQxw==",
+ "NuBYjwlxadAH+vLWYRZ3bg==",
+ "Ao1Zc0h5AdSHtYt1caWZnQ==",
+ "FL/j3GJBuXdAo54JYiWklQ==",
+ "E2v8Kk60qVpQ232YzjS2ow==",
+ "zVupSPz7cD0v/mD/eUIIjg==",
+ "sEeblUmISi1HK4omrWuPTA==",
+ "xQpYjaAmrQudWgsdu24J0A==",
+ "vCekQ2nOQKiN/q8Be/qwZg==",
+ "8g08gjG/QtvAYer32xgNAg==",
+ "miiOqnhtef1ODjFzMHnxjA==",
+ "sXlFMSTBFnq0STHj6cS/8w==",
+ "+SclwwY8R2RPrnX54Z+A6w==",
+ "g8TcogVxHpw7uhgNFt5VCQ==",
+ "9viAzLFGYYudBYFu7kFamg==",
+ "BAJ+/jbk2HyobezZyB9LiQ==",
+ "/DJgKE9ouibewuZ2QEnk6w==",
+ "fxg/vQq9WPpmQsqQ4RFYaA==",
+ "lM/EhwTsbivA7MDecaVTPw==",
+ "pVgjGg4TeTNhKimyOu3AAw==",
+ "gYnznEt9r97haD/j2Cko7g==",
+ "/ngbFuKIAVpdSwsA3VxvNw==",
+ "VCL3xfPVCL5RjihQM59fgg==",
+ "eDWsx4isnr2xPveBOGc7Hw==",
+ "FIOCTEbzb2+KMCnEdJ7jZw==",
+ "40HzgVKYnqIb6NJhpSIF0A==",
+ "ccK42Lm8Tsv73YMVZRwL6A==",
+ "MpAwWMt7bcs4eL7hCSLudQ==",
+ "zxsSqovedB3HT99jVblCnQ==",
+ "4erEA42TqGA9K4iFKkxMMA==",
+ "BaRwTrc5ulyKbW4+QqD0dw==",
+ "CT3ldhWpS1SEEmPtjejR/Q==",
+ "lkl6XkrTMUpXi46dPxTPxg==",
+ "3EhLkC9NqD3A6ApV6idmgg==",
+ "fsW2DaKYTCC7gswCT+ByQQ==",
+ "pW4gDKtVLj48gNz6V17QdA==",
+ "KjfL7YyVqmCJGBGDFdJ0gw==",
+ "bGGUhiG9SqJMHQWitXTcYQ==",
+ "8RtLlzkGEiisy1v9Xo0sbw==",
+ "R81DX/5a7DYKkS4CU+TL+w==",
+ "Tu6w6DtX2RJJ3Ym3o3QAWw==",
+ "nx/U4Tode5ILux4DSR+QMg==",
+ "mjQS8CpyGnsZIDOIEdYUxg==",
+ "wJpepvmtQQ3sz3tVFDnFqw==",
+ "a4rPqbDWiMivVzaRxvAj7g==",
+ "6o5g9JfKLKQ2vBPqKs6kjg==",
+ "UzPPFSXgeV7KW4CN5GIQXA==",
+ "NdVyHoTbBhX6Umz/9vbi0g==",
+ "Fzuq+Wg7clo6DTujNrxsSA==",
+ "XXFr0WUuGsH5nXPas7hR3Q==",
+ "JVSLiwurnCelNBiG2nflpQ==",
+ "NiawWuMBDo0Q3P2xK/vnLQ==",
+ "nNaGqigseHw30DaAhjBU3g==",
+ "+edqJYGvcy1AH2mEjJtSIg==",
+ "1WIi4I62GqkjDXOYqHWJfQ==",
+ "rwplpbNJz0ADUHTmzAj15Q==",
+ "iWNlSnwrtCmVF89B+DZqOQ==",
+ "tHDbi43e6k6uBgO0hA+Uiw==",
+ "fHNpW230mNib08aB7IM3XQ==",
+ "OChiB4BzcRE8Qxilu6TgJg==",
+ "d+ctfXU0j07rpRRzb5/HDA==",
+ "GDMqfhPQN0PxfJPnK1Bb9A==",
+ "bLd38ZNkVeuhf0joEAxnBQ==",
+ "nvUKoKfC6j8fz3gEDQrc/w==",
+ "fhcbn9xE/6zobqQ2niSBgA==",
+ "HGxe+5/kkh6R9GXzEOOFHA==",
+ "mPwCyD0yrIDonVi+fhXyEQ==",
+ "5PfGtbH9fmVuNnq83xIIgQ==",
+ "XePy/hhnQwHXFeXUQQ55Vg==",
+ "yfAaL0MMtSXPQ37pBdmHxQ==",
+ "NiQ/m4DZXUbpca9aZdzWAw==",
+ "uT6WRh5UpVdeABssoP2VTg==",
+ "oxoZP897lgMg/KLcZAtkAg==",
+ "oKt57TPe4PogmsGssc3Cbg==",
+ "RxmdoO8ak8y/HzMSIm+yBQ==",
+ "6leyDVmC5jglAa98NQ3+Hg==",
+ "+QosBAnSM2h4lsKuBlqEZw==",
+ "hy303iin+Wm7JA6MeelwiQ==",
+ "m9iuy4UtsjmyPzy6FTTZvw==",
+ "f6Ye5F0Lkn34uLVDCzogFQ==",
+ "iGykaF+h4p46HhrWqL8Ffg==",
+ "LPYFDbTEp5nGtG6uO8epSw==",
+ "t2vWMIh2BvfDSQaz5T1TZw==",
+ "OONAvFS/kmH7+vPhAGTNSg==",
+ "g/z9yk94XaeBRFj4hqPzdw==",
+ "2wesXiib76wM9sqRZ7JYwQ==",
+ "n7h9v2N1gOcvMuBEf8uThw==",
+ "ITYL3tDwddEdWSD6J6ULaA==",
+ "inrUwXyKikpOW0y2Kl1wGw==",
+ "iwKBOGDTFzV4aXgDGfyUkw==",
+ "+fcjH2kZKNj8quOytUk4nQ==",
+ "Srl4HivgHMxMOUHyM3jvNw==",
+ "qngzBJbiTB4fivrdnE5gOg==",
+ "G0MlFNCbRjXk4ekcPO/chQ==",
+ "t+bYn9UqrzKiuxAYGF7RLA==",
+ "RVD3Ij6sRwwxTUDAxwELtA==",
+ "RNdyt6ZRGvwYG5Ws3QTuEA==",
+ "9DRHdyX8ECKHUoEsGuqR4Q==",
+ "oMJLQTH1wW7LvOV0KRx/dw==",
+ "bjLZ7ot/X/vWSVx4EYwMCg==",
+ "+p8pofUlwn8vV6Rp6+sz9g==",
+ "cchuqe+CWCJpoakjHLvUfA==",
+ "NvurnIHin4O+wNP7MnrZ1w==",
+ "RBMv0IxXEO3o7MnV47Bzow==",
+ "xTizUioizbMQxD0T6fy/EQ==",
+ "ZCdad3AwhVArttapWFwT/Q==",
+ "Hy1nqC40l5ItxumkIC2LAA==",
+ "W/5ThNLu43uT1O+fg0Fzwg==",
+ "b3BQG9/9qDNC/bNSTBY/sQ==",
+ "neQoa8pvETr07blVMN3pgA==",
+ "oR8rvIZoeoaZ/ufpo0htfQ==",
+ "zEzWZ6l7EKoVUxvk/l78Mw==",
+ "IHyIeMad23fSDisblwyfpA==",
+ "m6srF+pMehggHB1tdoxlPg==",
+ "kggaIvN2tlbZdZRI8S5Apw==",
+ "2RFaMPlSbVuoEqKXgkIa5A==",
+ "//eHwmDOQRSrv+k9C/k3ZQ==",
+ "X/Gha4Ajjm/GStp/tv+Jvw==",
+ "+H0Rglt/HnhZwdty2hsDHg==",
+ "a1aL8zQ+ie3YPogE3hyFFg==",
+ "HxEU37uBMeiR5y8q/pM42g==",
+ "68nqDtXOuxF7DSw6muEZvg==",
+ "s5+78jS4hQYrFtxqTW3g1Q==",
+ "drfODfDI6GyMW7hzkmzQvA==",
+ "pT1raq2fChffFSIBX3fRiA==",
+ "sfowXUMdN2mCoBVrUzulZg==",
+ "AV/YJfdoDUdRcrXVwinhQg==",
+ "3AKEYQqpkfW7CZMFQZoxOw==",
+ "PHwJ5ZAqqftZ4ypr8H1qiQ==",
+ "AoN/pnK4KEUaGw4V9SFjpg==",
+ "soBA65OmZdfBGJkBmY/4Iw==",
+ "mSstwJq7IkJ0JBJ5T8xDKg==",
+ "h13Xuonj+0dD1xH86IhSyQ==",
+ "HK9xG03FjgCy8vSR+hx8+Q==",
+ "oFanDWdePmmZN0xqwpUukA==",
+ "zCRZgVsHbQZcVMHd9pGD3A==",
+ "EvSB+rCggob2RBeXyDQRvQ==",
+ "tXuu7YpZOuMLTv87NjKerA==",
+ "DJ+a37tCaGF5OgUhG+T0NA==",
+ "KkXlgPJPen6HLxbNn5llBw==",
+ "2W6lz1Z7PhkvObEAg2XKJw==",
+ "n+xYzfKmMoB3lWkdZ+D3rg==",
+ "CPDs+We/1wvsGdaiqxzeCQ==",
+ "2Wvk/kouEEOY0evUkQLhOQ==",
+ "ezsm4aFd6+DO9FUxz0A8Pg==",
+ "9sYLg75/hudZaBA3FrzKHw==",
+ "Pp1ZMxJ8yajdbfKM4HAQxA==",
+ "xiyRfVG0EfBA+rCk+tgWRQ==",
+ "/IarsLzJB8bf0AupJJ+/Eg==",
+ "LJeLdqmriyAQp+QjZGFkdQ==",
+ "IhHyHbHGyQS+VawxteLP0w==",
+ "nGzPc0kI/EduVjiK7bzM6Q==",
+ "m06wctjNc3o7iyBHDMZs2w==",
+ "mSJF9dJnxZ15lTC6ilbJ2A==",
+ "xdmY+qyoxxuRZa9kuNpDEg==",
+ "oNOI17POQCAkDwj6lJsYOA==",
+ "p73gSu4d+4T/ZNNkIv9Nlw==",
+ "vOJ55zFdgPPauPyFYBf01w==",
+ "4A+RHIw+aDzw0rSRYfbc7g==",
+ "/gi3UZmunVOIXhZSktZ8zQ==",
+ "a6vem8n6WmRZAalDrHNP0g==",
+ "kGeXrHEN6o7h5qJYcThCPw==",
+ "wrewZ0hoHODf7qmoGcOd7g==",
+ "Z0sjccxzKylgEiPCFBqPSA==",
+ "LKyOFgUKKGUU/PxpFYMILw==",
+ "L2RofFWDO0fVgSz4D2mtdw==",
+ "KI7tQFYW38zYHOzkKp9/lQ==",
+ "ewe/P3pJLYu/kMb5tpvVog==",
+ "IADk81pIu8NIL/+9Fi94pA==",
+ "0L0FVcH5Dlj3oL8+e9Na7g==",
+ "tdiTXKrkqxstDasT0D5BPA==",
+ "R906Kxp2VFVR3VD+o6Vxcw==",
+ "wc+8ohFWgOF4VlSYiZIGwQ==",
+ "wJKFMqh6MGctWfasjHrPEg==",
+ "UHpge5Bldt9oPGo2oxnYvQ==",
+ "vX7RIhatQeXAMr1+OjzhZw==",
+ "s2AKVTwrY65/SWqQxDGJQg==",
+ "Q4bfQslDSqU64MOQbBQEUw==",
+ "mVT74Eht+gAowINoMKV7IQ==",
+ "EuGWtIbyKToOe6DN3NkVpQ==",
+ "ALlGgVDO8So71ccX0D6u2g==",
+ "Rww3qkF3kWSd+AaMT0kfdw==",
+ "hlvtFGW8r0PkbUAYXEM+Hw==",
+ "Oc3BqTF3ZBW3xE0QsnFn/A==",
+ "3j0kFUZ6g+yeeEljx+WXGg==",
+ "8BLkvEkfnOizJq0OTCYGzw==",
+ "Lqel4GdU0ZkfoJVXI5WC/Q==",
+ "rvE64KQGkVkbl07y7JwBqw==",
+ "HbXv8InyZqFT7i3VrllBgg==",
+ "zwQ/3MzTJ9rfBmrANIh14w==",
+ "gglLMohmJDPRGMY1XKndjQ==",
+ "lyfqic/AbEJbCiw+wA01FA==",
+ "XqUO7ULEYhDOuT/I2J8BOA==",
+ "wPhJcp7U7IVX83szbIOOxQ==",
+ "1gA65t5FiBTEgMELTQFUPQ==",
+ "ll2M0QQzBsj5OFi02fv3Yg==",
+ "wt+qDLU38kzNU75ZYi3Hbw==",
+ "a4EYNljinYTx9vb1VvUA6A==",
+ "T6LA+daQqRI38iDKZTdg1A==",
+ "gwyVIrTk5o0YMKQq4lpJ+Q==",
+ "bPRX2zl+K1S0iWAWUn1DZw==",
+ "KQw25X4LnQ9is+qdqfxo0w==",
+ "6tfM6dx3R5TiVKaqYQjnCg==",
+ "OlwHO6Sg2zIwsCOCRu0HiQ==",
+ "mr1qjhliRfl87wPOrJbFQg==",
+ "8c+lvG5sZNimvx9NKNH3ug==",
+ "5Nk2Z94DhlIdfG5HNgvBbQ==",
+ "F50iXjRo1aSTr37GQQXuJA==",
+ "tfgO55QqUyayjDfQh+Zo1Q==",
+ "h7Fc+eT/GuC8iWI+YTD0UQ==",
+ "3TjntNWtpG7VqBt3729L6Q==",
+ "+DWs0vvFGt6d3mzdcsdsyA==",
+ "VJt2kPVBLEBpGpgvuv1oUw==",
+ "XLq/nWX8lQqjxsK9jlCqUg==",
+ "9s3ar9q32Y5A3tla5GW/2Q==",
+ "51yLpfEdvqXmtB6+q27/AQ==",
+ "AiMtfedwGcddA+XYNc+21g==",
+ "p/48hurJ1kh2FFPpyChzJg==",
+ "CRiL6zpjfznhGXhCIbz8pQ==",
+ "/jDVt9dRIn+o4IQ1DPwbsg==",
+ "UNdKik7Vy23LjjPzEdzNsg==",
+ "Koiog/hpN7ew5kgJbty34A==",
+ "4itEKfbRCJvqlgKnyEdIOQ==",
+ "zi04Yc01ZheuFAQc59E45A==",
+ "etRjRvfL/IwceY/IJ1tgzQ==",
+ "3sNJJIx1NnjYcgJhjOLJOg==",
+ "4yVqq66iHYQjiTSxGgX2oA==",
+ "Q8RVI/kRbKuXa8HAQD7zUA==",
+ "OERGn45uzfDfglzFFn6JAg==",
+ "JGEy6VP3sz3LHiyT2UwNHQ==",
+ "1zDfWw5LdG20ClNP1HYxgw==",
+ "TGB+FIzzKnouLh5bAiVOQg==",
+ "n5GA+pA9mO/f4RN9NL9lNg==",
+ "bUxQBaqKyvlSHcuRL9whjg==",
+ "tOdlnsE3L3XCBDJRmb/OqA==",
+ "XdkxmYYooeDKzy7PXVigBQ==",
+ "PMvG4NqJP76kMRAup6TSZA==",
+ "qpFJZqzkklby+u1UT3c1iA==",
+ "fW3QZyq5UixIA1mP6eWgqQ==",
+ "9nMltdrrBmM5ESBY2FRjGA==",
+ "1Vtrv6QUAfiYQjlLTpNovg==",
+ "ur9JDCVNwzSH4q4ngDlHNQ==",
+ "4u3eyKc+y3uRnkASrgBVUw==",
+ "XddlSluOH6VkR7spFIFmdQ==",
+ "NOmu8oZc6CcKLu+Wfz2YOQ==",
+ "3Ejtsqw3Iep/UQd0tXnSlg==",
+ "y/e3HSdg7T19FanRpJ7+7Q==",
+ "YodhkayN5wsgPZEYN7/KNA==",
+ "pZfn6IiG+V28fN8E2hawDQ==",
+ "jGHMJqbj6X1NdTDyWmXYAQ==",
+ "olTSlmirL9MFhKORiOKYkQ==",
+ "CrJDgdfzOea2M2hVedTrIg==",
+ "fpXijBOM3Ai1RkmHven5Ww==",
+ "eLYKLr4labZeLiRrDJ9mnA==",
+ "9vmJUS7WIVOlhMqwipAknQ==",
+ "G7J/za99BFbAZH+Q+/B8WA==",
+ "Hb+pdSavvJ9lUXkSVZW8Og==",
+ "gTB2zM3RPm27mUQRXc/YRg==",
+ "e5KCqQ/1GAyVMRNgQpYf6g==",
+ "1ApqwW7pE+XUB2Cs2M6y7g==",
+ "/wiA2ltAuWyBhIvQAYBTQw==",
+ "HFCQEiZf7/SNc+oNSkkwlA==",
+ "JFi6N1PlrpKaYECOnI7GFg==",
+ "E4ojRDwGsIiyuxBuXHsKBA==",
+ "+25t/2lo0FUEtWYK8LdQZQ==",
+ "up2MVDi9ve+s83/nwNtZ7Q==",
+ "cXpfd6Io6Glj2/QzrDMCvA==",
+ "DCvI9byhw0wOFwF1uP6xIQ==",
+ "PibGJQNw7VHPTgqeCzGUGA==",
+ "0ZRGz+oj2infCAkuKKuHiQ==",
+ "2QS/6OBA1T01NlIbfkTYJg==",
+ "P14k+fyz0TG9yIPdojp52w==",
+ "g5EzTJ0KA4sO3+Opss3LMg==",
+ "R5oOM58zdbVxFSDQnNWqeA==",
+ "Vg2E5qEDfC+QxZTZDCu9yQ==",
+ "YPgMthbpcBN2CMkugV60hQ==",
+ "gZWTFt5CuLqMz6OhWL+hqQ==",
+ "YrEP9z2WPQ8l7TY1qWncDA==",
+ "7p4NpnoNSQR7ISg+w+4yFg==",
+ "9L6yLO93sRN70+3qq3ObfA==",
+ "QH36wzyIhh6I56Vnx79hRA==",
+ "9DtM1vls4rFTdrSnQ7uWXw==",
+ "ZlOAnCLV1PkR0kb3E+Nfuw==",
+ "9UhKmKtr4vMzXTEn74BEhg==",
+ "Ndx5LDiVyyTz/Fh3oBTgvA==",
+ "mXZ4JeBwT2WJQL4a/Tm4jQ==",
+ "N9nD7BGEM7LDwWIMDB+rEQ==",
+ "dmAfbd9F0OJHRAhNMEkRsA==",
+ "jV/D2B11NLXZRH77sG9lBw==",
+ "1C50kisi9nvyVJNfq2hOEQ==",
+ "NMbAjbnuK7EkVeY3CQI5VA==",
+ "J1nYqJ7tIQK1+a/3sMXI/Q==",
+ "m416yrrAlv+YPClGvGh+qQ==",
+ "rLZII1R6EGus+tYCiUtm6g==",
+ "xktOghh1S9nIX6fXWnT+Ug==",
+ "FcFcn4qmPse5mJCX5yNlsA==",
+ "xAAipGfHTGTjp9Qk1MR8RQ==",
+ "RQOlmzHwQKFpafKPJj0D8w==",
+ "WRjYdKdtnd1G9e/vFXCt0g==",
+ "z0BU//aSjYHAkGGk3ZSGNg==",
+ "M55eersiJuN9v61r8DoAjQ==",
+ "l2mAbuFF3QBIUILDODiUHQ==",
+ "IhpXs1TK7itQ3uTzZPRP5Q==",
+ "t2EkpUsLOEOsrnep0nZSmA==",
+ "lMaO8Yf+6YNowGyhDkPhQA==",
+ "UbSFw5jtyLk5MealqJw++A==",
+ "5u2PdDcIY3RQgtchSGDCGg==",
+ "MQYM3BT77i35LG9HcqxY2Q==",
+ "8AfCSZC0uasVON9Y/0P2Pw==",
+ "evaWFoxZNQcRszIRnxqB+A==",
+ "+8PiQt6O7pJI/nIvQpDaAg==",
+ "eRwaYiog2DdlGQyaltCMJg==",
+ "JyUJEnU6hJu8x2NCnGrYFw==",
+ "l0E0U/CJsyCVSTsXW4Fp+w==",
+ "XV13yK0QypJXmgI+dj4KYw==",
+ "jrRH0aTUYCOpPLZwzwPRfQ==",
+ "N3YDSkBUqSmrmNvZZx4a1Q==",
+ "0yJ7TQYzcp3DXVSvwavr+w==",
+ "rhgtLQh0F9bRA6IllM7AGw==",
+ "IWZnTJ3Hb9qw9HAK/M9gTw==",
+ "izeyFvXOumNgVyLrbKW45g==",
+ "xYD8jrCDmuQna+p1ebnKDQ==",
+ "SOdpdrk2ayeyv0xWdNuy9g==",
+ "HYylUirJRqLm+dkp39fSOQ==",
+ "q4z6A4l3nhX3smTmXr+Sig==",
+ "Zyo0fzewcqXiKe2mAwKx5g==",
+ "LMEtzh0+J27+4zORfcjITw==",
+ "LoUv/f2lcWpjftzpdivMww==",
+ "mXBfDUt/sBW5OUZs2sihvw==",
+ "PggVPQL5YKqSU/1asihcrg==",
+ "mI0eT4Rlr7QerMIngcu/ng==",
+ "NmQrsmb8PVP05qnSulPe5Q==",
+ "TcyyXrSsQsnz0gJ36w4Dxw==",
+ "y4mfEDerrhaqApDdhP5vjA==",
+ "ynaj4XjU27b7XbqPyxI8Ig==",
+ "Ua6aO6HwM+rY4sPR19CNFA==",
+ "3go7bJ9WqH/PPUTjNP3q/Q==",
+ "n1ixvP7SfwYT3L2iWpJg6A==",
+ "W8y32OLHihfeV0XFw7LmOg==",
+ "uzkNhmo2d08tv5AmnyqkoQ==",
+ "hJ8leLNuJ6DK5V8scnDaZQ==",
+ "KodYHHN62zESrXUye7M01g==",
+ "H+yPRiooEh5J7lAJB4RZ7Q==",
+ "dZg5w8rFETMp9SgW7m0gfg==",
+ "LsmsPokAwWNCuC74MaqFCQ==",
+ "1QGhj9NONF2rC44UdO+Izw==",
+ "uwGivY3/C9WK+dirRPJZ4A==",
+ "rXGWY/Gq+ZEsmvBHUfFMmQ==",
+ "j4FBMnNfdBwx0VsDeTvhFg==",
+ "81nkjWtpBhqhvOp6K8dcWg==",
+ "dCDaYYrgASXPMGFRV0RCGg==",
+ "Kj1QI+s9261S3lTtPKd9eg==",
+ "LblwOqNiciHmt2NXjd89tg==",
+ "46piyANQVvvLqcoMq5G8tQ==",
+ "XJihma9zSRrXLC+T+VcFDA==",
+ "K3NBEG8jJTJbSrYSOC3FKw==",
+ "cT3PwwS6ALZA/na9NjtdzA==",
+ "wJ4uCrl4DPg70ltw1dZO3w==",
+ "JATLdpQm//SQnkyCfI5x7Q==",
+ "X1PaCfEDScclLtOTiF5JUw==",
+ "444F9T6Y7J67Y9sULG81qg==",
+ "8JVHFRwAd/SCLU0CRJYofg==",
+ "aLh1XEUrfR9W82gzusKcOg==",
+ "U+bB5NjFIuQr/Y5UpXHwxA==",
+ "Egs14xVbRWjfBBX7X5Z60g==",
+ "KSorNz/PLR/YYkxaj1fuqw==",
+ "RDgGGxTtcPvRg/5KRRlz4w==",
+ "5T39s5CtSrK5awMPUcEWJg==",
+ "+PUVXkoTqHxJHO18z4KMfw==",
+ "Bvk8NX4l6WktLcRDRKsK/A==",
+ "kNGIV3+jQmJlZDTXy1pnyA==",
+ "E3jMjAgXwvwR8PA53g4+PQ==",
+ "MbI04HlTGCoc/6WDejwtaQ==",
+ "aEnHUfn7UE/Euh6jsMuZ7g==",
+ "z4Bft++f72QeDh4PWGr/sw==",
+ "1lCcQWGDePPYco4vYrA5vw==",
+ "iu5csar0IQQBOTgw5OvJwQ==",
+ "raKMXnnX6PFFsbloDqyVzQ==",
+ "uPnL9tboMZo0Kl2fe24CmA==",
+ "8OFxXwnPmrogpNoueZlC4Q==",
+ "V6CRKrKezPwsRdbm0DJ2Yg==",
+ "xmGgK3W5y+oCd0K2u8XjZQ==",
+ "Ry3zgZ6KHrpNyb7+Tt2Pkw==",
+ "IwLbkL33z+LdTjaFYh93kg==",
+ "caepyBOAFu0MxbcXrGf6TA==",
+ "iIWxFdolLcnXqIjPMg+5kQ==",
+ "P430CeF2MDkuq11YdjvV8A==",
+ "yCu+DVU/ceMTOZ5h/7wQTg==",
+ "4mQVNv7FHj+/O6XFqWFt/Q==",
+ "OEJ40VmMDYzc2ESEMontRA==",
+ "D66Suu3tWBD+eurBpPXfjA==",
+ "RNK9G1hfuz3ETY/RmA9+aA==",
+ "BYpHADmEnzBsegdYTv8B5Q==",
+ "DBKrdpCE0awppxST4o/zzg==",
+ "KOmdvm+wJuZ/nT/o1+xOuw==",
+ "gDxqUdxxeXDYhJk9zcrNyA==",
+ "UPzS4LR3p/h0u69+7YemrQ==",
+ "hf9HFxWRNX2ucH8FLS7ytA==",
+ "ozVqYsmUueKifb4lDyVyrg==",
+ "TfHvdbl2M4deg65QKBTPng==",
+ "SzCGM8ypE58FLaR1+1ccxQ==",
+ "3nthUmLZ30HxQrzr2d7xFA==",
+ "1jBaRO8Bg5l6TH7qJ8EPiw==",
+ "eJlcN+gJnqAnctbWSIO9uA==",
+ "G8LFBop8u6IIng+gQuVg3w==",
+ "3JhnM6G4L06NHt31lR0zXA==",
+ "342VOUOxoLHUqtHANt83Hw==",
+ "hRxbdeniAVFgKUgB9Q3Y+g==",
+ "cFFE2R4GztNoftYkqalqUQ==",
+ "YmaksRzoU+OwlpiEaBDYaQ==",
+ "jon1y9yMEGfiIBjsDeeJdA==",
+ "oSnrpW4UmmVXtUGWqLq+tQ==",
+ "zaqyy3GaJ7cp8qDoLJWcTw==",
+ "luO1R8dUM9gy1E2lojRQoA==",
+ "YHM6NNHjmodv+G0mRLK7kw==",
+ "ZSmN8mmI9lDEHkJqBBg0Nw==",
+ "520wTzrysiRi2Td92Zq0HQ==",
+ "RAAw14BA1ws5Wu/rU7oegw==",
+ "vb6Agwzk4JG0Nn7qRPPFMQ==",
+ "joDXdLpXvRjOqkRiYaD/Sw==",
+ "dK2DU3t1ns+DWDwfBvH3SQ==",
+ "gZNJ1Qq6OcnwXqc+jXzMLQ==",
+ "R8ULpSNu9FcCwXZM0QedSg==",
+ "mc45FSMtzdw2PTcEBwHWPw==",
+ "d0qvm3bl38rRCpYdWqolCQ==",
+ "o9tdzmIu+3J/EYU4YWyTkA==",
+ "5eXpiczlRdmqMYSaodOUiQ==",
+ "KYuUNrkTvjUWQovw9dNakA==",
+ "02im2RooJQ/9UfUrh5LO+A==",
+ "kWPUUi7x9kKKa6nJ+FDR5Q==",
+ "6z8CRivao3IMyV4p4gMh7g==",
+ "SmRWEzqddY9ucGAP5jXjAg==",
+ "DJscTYNFPyPmTb57g/1w+Q==",
+ "uOHrw37yF9oLLVd16nUpeg==",
+ "HaIRV9SNPRTPDOSX9sK/bg==",
+ "K4yZNVoqHjXNhrZzz2gTew==",
+ "bTNRjJm+FfSQVfd56nNNqQ==",
+ "x5lyMArsv1MuJmEFlWCnNw==",
+ "cxpZ4bloGv734LBf4NpVhA==",
+ "kUudvRfA33uJDzHIShQd3Q==",
+ "3Wfj05vCLFAB9vII5AU9tw==",
+ "FUQySDFodnRhr+NUsWt0KA==",
+ "eC/RcoCVQBlXdE9WtcgXIw==",
+ "NoX8lkY+kd2GPuGjp+s0tQ==",
+ "EzjbinBHx3Wr08eXpH3HXA==",
+ "0VsaJHR0Ms8zegsCpAKoyg==",
+ "e2xLFVavnZIUUtxJx+qa1g==",
+ "Kt6BTG1zdeBZ3nlVk+BZKQ==",
+ "EUXQZwLgnDG+C8qxVoBNdw==",
+ "0SkC/4PtnX1bMYgD6r6CLA==",
+ "rzj6mjHCcMEouL66083BAg==",
+ "V5HEaY3v9agOhsbYOAZgJA==",
+ "tJt6VDdAPEemBUvnoc4viA==",
+ "g0lWrzEYMntVIahC7i0O2g==",
+ "zCpibjrZOA3FQ4lYt0WoVA==",
+ "4Xh/B3C16rrjbES+FM1W8g==",
+ "GHEdXgGWOeOa6RuPMF0xXg==",
+ "3kREs/qaMX0AwFXN0LO5ow==",
+ "GLDNTSwygNBmuFwCIm7HtA==",
+ "JBkbaBiorCtFq9M9lSUdMg==",
+ "rJCuanCy51ydVD4nInf9IQ==",
+ "OzFRv+PzPqTNmOnvZGoo5g==",
+ "7mxU5fJl/c6dXss9H3vGcQ==",
+ "9J53kk+InE3CKa7cPyCXMw==",
+ "x9TIZ9Ua++3BX+MpjgTuWA==",
+ "h0MH5NGFfChgmRJ3E/R3HQ==",
+ "25w3ZRUzCvJwAVHYCIO5uw==",
+ "1Wc8jQlDSB4Dp32wkL2odw==",
+ "ipPPjxpXHS1tcykXmrHPMQ==",
+ "r95wJtP5rsTExKMS7QhHcw==",
+ "TZT86wXfzFffjt0f95UF5w==",
+ "VpmBstwR7qPVqPgKYQTA3g==",
+ "3++dZXzZ6AFEz7hK+i5hww==",
+ "mAiD16zf+rCc7Qzxjd5buA==",
+ "1JI9bT92UzxI8txjhst9LQ==",
+ "TNyvLixb03aP2f8cDozzfA==",
+ "spHVvA/pc7nF9Q4ON020+w==",
+ "GA8k6GQ20DGduVoC+gieRA==",
+ "T7waQc3PvTFr0yWGKmFQdQ==",
+ "P0Pc8owrqt6spdf7FgBFSw==",
+ "DKApp/alXiaPSRNm3MfSuA==",
+ "UreSZCIdDgloih8KLeX7gg==",
+ "xJi0T+psHOXMivSOVpMWeQ==",
+ "cNsC9bH30eM1EZS6IdEdtQ==",
+ "XjjrIpsmATV/lyln4tPb+g==",
+ "qt5CsMts2aD4lw/4Q6bHYQ==",
+ "h+KRDKIvyVUBmRjv1LcCyg==",
+ "2j83jrPwPfYlpJJ2clEBYQ==",
+ "ZrCezGLz38xKmzAom6yCTQ==",
+ "SEGu+cSbeeeZg4xWwsSErQ==",
+ "Duz/8Ebbd0w6oHwOs0Wnwg==",
+ "Ci7sS7Yi1+IwAM3VMAB4ew==",
+ "DG2Qe2DqPs5MkZPOqX363Q==",
+ "v0Bvws1WYVoEgDt8xmVKew==",
+ "CtDj/h2Q/lRey20G8dzSgA==",
+ "WRoJMO0BCJyn5V6qnpUi4Q==",
+ "RQywrOLZEKw9+kG6qTzr3g==",
+ "mU4CqbAwpwqegxJaOz9ofQ==",
+ "aN5x46Gw1VihRalwCt1CGg==",
+ "U6VQghxOXsydh3Naa5Nz4A==",
+ "YA+zdEC+yEgFWRIgS1Eiqw==",
+ "oPcxgoismve6+jXyIKK6AQ==",
+ "PqLCd/pwc+q5GkL6MB0jTg==",
+ "fHL+fHtDxhALZFb9W/uHuw==",
+ "dhTevyxTYAuKbdLWhG47Kw==",
+ "VllbOAjeW3Dpbj5lp2OSmA==",
+ "3itfXtlLPRmPCSYaSvc39Q==",
+ "GNak/LFeoHWlTdLW1iU4eg==",
+ "HuDuxs2KiGqmeyY1s1PjpQ==",
+ "xs8J3cesq7lDhP/dNltqOw==",
+ "foXSDEUwMhfHWJSmSejsQg==",
+ "6fWom3YoKvW6NIg6y9o9CQ==",
+ "NhZbSq0CjDNOAIvBHBM9zA==",
+ "5w4FbRhWACP7k2WnNitiHg==",
+ "0UeRwDID2RBIikInqFI7uw==",
+ "/y/jHHEpUu5TR+R2o96kXA==",
+ "voO3krg4sdy4Iu+MZEr8+g==",
+ "hdzol5dk//Q6tCm4+OndIA==",
+ "Nc5kiwXCAyjpzt43G5RF1A==",
+ "3UBYBMejKInSbCHRoJJ7dg==",
+ "dRFCIbVu0Y8XbjG5i+UFCQ==",
+ "t8pjhdyNJirkvYgWIO/eKg==",
+ "FAXzjjIr8l1nsQFPpgxM/g==",
+ "SPGpjEJrpflv1hF0qsFlPw==",
+ "9Y1ZmfiHJd9vCiZ6KfO1xQ==",
+ "7Eqzyb+Kep+dIahYJWNNxQ==",
+ "9rL8nC/VbSqrvnUtH9WsxQ==",
+ "H4FZ5Wcnb40hQM1DMGGe8A==",
+ "AjoXWGb/l9xH/hscgEc6kQ==",
+ "6nzFl41uutgDdC30oOeCqg==",
+ "3jo1jRy3MybXtoLR+JIbJw==",
+ "mXdE08dv+OlIhlcqMBH2Gg==",
+ "Ifd7DI6o8N5gnyAKqZTlRw==",
+ "JNUvg/kxL3rdcZnD4IqUxw==",
+ "ry8B+sAHNeFIZHCCDynFyw==",
+ "TXaEd5lIKhzjcncfNcBgSg==",
+ "Mr3ehuDMUimOSn+FlkchdA==",
+ "cwiGhjmX9v8I7E/ekQ0h+g==",
+ "I/r5+1jnqumCPprKC/2BqA==",
+ "S4V3MfGYk8I4fd3WH09yYw==",
+ "A+crVyUeynAkEMYKbnFjZw==",
+ "vtyHcNQPcUTRuZcQvRUX4Q==",
+ "UNKx1ZVv3HNp21zrUSm6ew==",
+ "rsAlvGLv2D0swd6ol3WlvA==",
+ "2qwqb8ENAR2fpQnw55sPDw==",
+ "xBJJuYYnsTJOeFggZSKC4Q==",
+ "omvtZZKruPiEt6fV0YXTdg==",
+ "JZEgKUhUN+USJsvtF4HZOg==",
+ "euG/kpJ5elSDOGNbWWDfNQ==",
+ "DiiVmM6/WNcp0MUjSaFq6w==",
+ "QCNS8gAml1M2pJ+MxZsueg==",
+ "M6+pggFsHfM3alFxcMOFNQ==",
+ "YLoWpDTwXnszEQm8FA164Q==",
+ "N08oUZtlXbQvO9t3vXnGog==",
+ "jkjuJowWuOa4CLY+RZiErQ==",
+ "mPf+S+6oAoVIYEVveaiNFA==",
+ "R0iVyo5qreP/68uZlZphDA==",
+ "GYlqhQgp03B0mXpUhQ+ZCA==",
+ "lQNbmWD7PhwNGye+zbc3GQ==",
+ "cNeaOJEOzUSDdRmenPQyuw==",
+ "Gp66/Txv6ebv5bn85TuQtA==",
+ "xAda6DVkcvvqhI8vWZeGyA==",
+ "Ggk1Qa0lEdAgCXG6SmCkBA==",
+ "MYuO7ZURXtyaf56q7hH4Zw==",
+ "RUIdZRTgJBudWUZQFgiFaQ==",
+ "bgFJxLirUom2zT0h7LdOpw==",
+ "A2gaOpIlrS7TKVQgy9XMSw==",
+ "zevXp0lqqnXv9X6Bgmjtqg==",
+ "a5iuFqWAdFFsRgp7SFYwNg==",
+ "TxTy0TaDsWTcRH3wdBEQLQ==",
+ "jephVdKDeJIhXPrdMOJ4qA==",
+ "C4KdamfqUPuJ3RGFdpIEdw==",
+ "zl6l2Ioz1qovRUIWrSyxVA==",
+ "+gGaDxUe0UnNrf3PPg1qQQ==",
+ "1HgbrlaLMHS6Qj/0kkaJxg==",
+ "eGxTly6Pnu7eV/MKYMmuYw==",
+ "RAMKfnlrzNjpyh2BWt6JHg==",
+ "4pZQm9ogCZ/EAR9pjJm1eA==",
+ "l1zv3erwXIegQFd02NlCag==",
+ "uHGyRZchuA4ulmuD5LqquQ==",
+ "/vFu89tsV+lbcoiqM/XWog==",
+ "63SUgqfQimrmjvy/bEDQ0w==",
+ "JLHuf+FlChFDa9LYfTQ4Eg==",
+ "I+ZnPePTFX8ZODe14bxgyA==",
+ "CtoK1k3U82BkvzuPfQ4pjQ==",
+ "6nqQm4C7y+wZ+qX0kVjwmA==",
+ "+C3kBxRXIjqBk0EJxe3Xfg==",
+ "qVu748pIxEZtiywg4/4qhw==",
+ "07o+sKjjRCYkwy/ACyoYhg==",
+ "CiLF4dkbLURekBcQbwPUVA==",
+ "W/N5/nkp4iQIPYfAagVV7A==",
+ "3PJOphhEjw0E4arTfVVwdg==",
+ "YdMbARHwB+bSOd0PlTlXiA==",
+ "41hbx5Yr7UWxsV6+bWUYUA==",
+ "SqJHXD0MorNwHtHL9TbWLg==",
+ "pWKGUzm/muwOiBtzkRMnRg==",
+ "az9zZ7HTa4FJGRQMcamvEw==",
+ "zavAAN8C9Wo8oBLyztp63Q==",
+ "yBAnPmwrMJ8kpPP292S/Lw==",
+ "E6szQhjuUAz2e0h9ffQfEQ==",
+ "Fs3cQxQyS9kM4T8j5R7rWw==",
+ "GB5fRLZxnjRUfEe0SwcePQ==",
+ "+9OY8xkT9dM/rb2T6ACtOQ==",
+ "If2xFBD1p91iDD7ZrsfgjA==",
+ "QCFfoMhy8EleZAOpfRY88w==",
+ "NobWPk1Z6bHt5s9NHXt/pg==",
+ "nK6T4vV4384OIcqO5tQMhA==",
+ "Zov1EzK+VomiuwT1+ulQ8g==",
+ "pF98OKDvLUlnTzo7wmlpOw==",
+ "Wrq9YDsieAMC3Y2DSY5Rcg=="
+ ]
+}
diff --git a/browser/base/content/newtab/newTab.js b/browser/base/content/newtab/newTab.js
new file mode 100644
index 000000000..bbd2ef39d
--- /dev/null
+++ b/browser/base/content/newtab/newTab.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";
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
+Cu.import("resource:///modules/DirectoryLinksProvider.jsm");
+Cu.import("resource://gre/modules/NewTabUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Rect",
+ "resource://gre/modules/Geometry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var {
+ links: gLinks,
+ allPages: gAllPages,
+ linkChecker: gLinkChecker,
+ pinnedLinks: gPinnedLinks,
+ blockedLinks: gBlockedLinks,
+ gridPrefs: gGridPrefs
+} = NewTabUtils;
+
+XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
+ return Services.strings.
+ createBundle("chrome://browser/locale/newTab.properties");
+});
+
+function newTabString(name, args) {
+ let stringName = "newtab." + name;
+ if (!args) {
+ return gStringBundle.GetStringFromName(stringName);
+ }
+ return gStringBundle.formatStringFromName(stringName, args, args.length);
+}
+
+function inPrivateBrowsingMode() {
+ return PrivateBrowsingUtils.isContentWindowPrivate(window);
+}
+
+const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
+const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-tiles-work-firefox";
+const TILES_INTRO_LINK = "https://www.mozilla.org/firefox/tiles/";
+const TILES_PRIVACY_LINK = "https://www.mozilla.org/privacy/";
+
+#include transformations.js
+#include page.js
+#include grid.js
+#include cells.js
+#include sites.js
+#include drag.js
+#include dragDataHelper.js
+#include drop.js
+#include dropTargetShim.js
+#include dropPreview.js
+#include updater.js
+#include undo.js
+#include search.js
+#include customize.js
+
+// Everything is loaded. Initialize the New Tab Page.
+gPage.init();
diff --git a/browser/base/content/newtab/newTab.xhtml b/browser/base/content/newtab/newTab.xhtml
new file mode 100644
index 000000000..07fb0093e
--- /dev/null
+++ b/browser/base/content/newtab/newTab.xhtml
@@ -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/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
+ %newTabDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+ %browserDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&newtab.pageTitle;</title>
+
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/contentSearchUI.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/newtab/newTab.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/newtab/newTab.css" />
+</head>
+
+<body dir="&locale.dir;">
+ <div id="newtab-customize-overlay"></div>
+
+ <div class="newtab-customize-panel-container">
+ <div id="newtab-customize-panel" orient="vertical">
+ <div id="newtab-customize-panel-anchor"></div>
+ <div id="newtab-customize-panel-inner-wrapper">
+ <div id="newtab-customize-title" class="newtab-customize-panel-item">
+ <label>&newtab.customize.cog.title2;</label>
+ </div>
+
+ <div class="newtab-customize-complex-option">
+ <div id="newtab-customize-classic" class="newtab-customize-panel-superitem newtab-customize-panel-item selectable">
+ <label>&newtab.customize.classic;</label>
+ </div>
+ <div id="newtab-customize-enhanced" class="newtab-customize-panel-subitem">
+ <label class="checkbox"></label>
+ <label>&newtab.customize.cog.enhanced;</label>
+ </div>
+ </div>
+ <div id="newtab-customize-blank" class="newtab-customize-panel-item selectable">
+ <label>&newtab.customize.blank2;</label>
+ </div>
+ <div id="newtab-customize-learn" class="newtab-customize-panel-item">
+ <label>&newtab.customize.cog.learn;</label>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div id="newtab-vertical-margin">
+ <div id="newtab-margin-top"/>
+
+ <div id="newtab-margin-undo-container">
+ <div id="newtab-undo-container" undo-disabled="true">
+ <label id="newtab-undo-label">&newtab.undo.removedLabel;</label>
+ <button id="newtab-undo-button" tabindex="-1"
+ class="newtab-undo-button">&newtab.undo.undoButton;</button>
+ <button id="newtab-undo-restore-button" tabindex="-1"
+ class="newtab-undo-button">&newtab.undo.restoreButton;</button>
+ <button id="newtab-undo-close-button" tabindex="-1" title="&newtab.undo.closeTooltip;"/>
+ </div>
+ </div>
+
+ <div id="newtab-search-container">
+ <div id="newtab-search-form">
+ <div id="newtab-search-icon"/>
+ <input type="text" name="q" value="" id="newtab-search-text"
+ aria-label="&contentSearchInput.label;" maxlength="256"/>
+ <input id="newtab-search-submit" type="button"
+ title="&contentSearchSubmit.tooltip;"/>
+ </div>
+ </div>
+
+ <div id="newtab-horizontal-margin">
+ <div class="newtab-side-margin"/>
+ <div id="newtab-grid">
+ <h1 id="topsites-heading"/>
+ </div>
+ <div class="newtab-side-margin"/>
+ </div>
+
+ <div id="newtab-margin-bottom"/>
+ </div>
+ <input id="newtab-customize-button" type="button" dir="&locale.dir;"
+ value="&#x2699;"
+ title="&newtab.customize.title;"/>
+</body>
+<script type="text/javascript;version=1.8" src="chrome://browser/content/contentSearchUI.js"/>
+<script type="text/javascript;version=1.8" src="chrome://browser/content/newtab/newTab.js"/>
+</html>
diff --git a/browser/base/content/newtab/page.js b/browser/base/content/newtab/page.js
new file mode 100644
index 000000000..f7626ced2
--- /dev/null
+++ b/browser/base/content/newtab/page.js
@@ -0,0 +1,297 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+// The amount of time we wait while coalescing updates for hidden pages.
+const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
+
+/**
+ * This singleton represents the whole 'New Tab Page' and takes care of
+ * initializing all its components.
+ */
+var gPage = {
+ /**
+ * Initializes the page.
+ */
+ init: function Page_init() {
+ // Add ourselves to the list of pages to receive notifications.
+ gAllPages.register(this);
+
+ // Listen for 'unload' to unregister this page.
+ addEventListener("unload", this, false);
+
+ // XXX bug 991111 - Not all click events are correctly triggered when
+ // listening from xhtml nodes -- in particular middle clicks on sites, so
+ // listen from the xul window and filter then delegate
+ addEventListener("click", this, false);
+
+ // Check if the new tab feature is enabled.
+ let enabled = gAllPages.enabled;
+ if (enabled)
+ this._init();
+
+ this._updateAttributes(enabled);
+
+ // Initialize customize controls.
+ gCustomize.init();
+ },
+
+ /**
+ * Listens for notifications specific to this page.
+ */
+ observe: function Page_observe(aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed") {
+ gCustomize.updateSelected();
+
+ let enabled = gAllPages.enabled;
+ this._updateAttributes(enabled);
+
+ // Update thumbnails to the new enhanced setting
+ if (aData == "browser.newtabpage.enhanced") {
+ this.update();
+ }
+
+ // Initialize the whole page if we haven't done that, yet.
+ if (enabled) {
+ this._init();
+ } else {
+ gUndoDialog.hide();
+ }
+ } else if (aTopic == "page-thumbnail:create" && gGrid.ready) {
+ for (let site of gGrid.sites) {
+ if (site && site.url === aData) {
+ site.refreshThumbnail();
+ }
+ }
+ }
+ },
+
+ /**
+ * Updates the page's grid right away for visible pages. If the page is
+ * currently hidden, i.e. in a background tab or in the preloader, then we
+ * batch multiple update requests and refresh the grid once after a short
+ * delay. Accepts a single parameter the specifies the reason for requesting
+ * a page update. The page may decide to delay or prevent a requested updated
+ * based on the given reason.
+ */
+ update(reason = "") {
+ // Update immediately if we're visible.
+ if (!document.hidden) {
+ // Ignore updates where reason=links-changed as those signal that the
+ // provider's set of links changed. We don't want to update visible pages
+ // in that case, it is ok to wait until the user opens the next tab.
+ if (reason != "links-changed" && gGrid.ready) {
+ gGrid.refresh();
+ }
+
+ return;
+ }
+
+ // Bail out if we scheduled before.
+ if (this._scheduleUpdateTimeout) {
+ return;
+ }
+
+ this._scheduleUpdateTimeout = setTimeout(() => {
+ // Refresh if the grid is ready.
+ if (gGrid.ready) {
+ gGrid.refresh();
+ }
+
+ this._scheduleUpdateTimeout = null;
+ }, SCHEDULE_UPDATE_TIMEOUT_MS);
+ },
+
+ /**
+ * Internally initializes the page. This runs only when/if the feature
+ * is/gets enabled.
+ */
+ _init: function Page_init() {
+ if (this._initialized)
+ return;
+
+ this._initialized = true;
+
+ // Set submit button label for when CSS background are disabled (e.g.
+ // high contrast mode).
+ document.getElementById("newtab-search-submit").value =
+ document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
+
+ if (Services.prefs.getBoolPref("browser.newtabpage.compact")) {
+ document.body.classList.add("compact");
+ }
+
+ // Initialize search.
+ gSearch.init();
+
+ if (document.hidden) {
+ addEventListener("visibilitychange", this);
+ } else {
+ setTimeout(() => this.onPageFirstVisible());
+ }
+
+ // Initialize and render the grid.
+ gGrid.init();
+
+ // Initialize the drop target shim.
+ gDropTargetShim.init();
+
+#ifdef XP_MACOSX
+ // Workaround to prevent a delay on MacOSX due to a slow drop animation.
+ document.addEventListener("dragover", this, false);
+ document.addEventListener("drop", this, false);
+#endif
+ },
+
+ /**
+ * Updates the 'page-disabled' attributes of the respective DOM nodes.
+ * @param aValue Whether the New Tab Page is enabled or not.
+ */
+ _updateAttributes: function Page_updateAttributes(aValue) {
+ // Set the nodes' states.
+ let nodeSelector = "#newtab-grid, #newtab-search-container";
+ for (let node of document.querySelectorAll(nodeSelector)) {
+ if (aValue)
+ node.removeAttribute("page-disabled");
+ else
+ node.setAttribute("page-disabled", "true");
+ }
+
+ // Enables/disables the control and link elements.
+ let inputSelector = ".newtab-control, .newtab-link";
+ for (let input of document.querySelectorAll(inputSelector)) {
+ if (aValue)
+ input.removeAttribute("tabindex");
+ else
+ input.setAttribute("tabindex", "-1");
+ }
+ },
+
+ /**
+ * Handles unload event
+ */
+ _handleUnloadEvent: function Page_handleUnloadEvent() {
+ gAllPages.unregister(this);
+ // compute page life-span and send telemetry probe: using milli-seconds will leave
+ // many low buckets empty. Instead we use half-second precision to make low end
+ // of histogram linear and not lose the change in user attention
+ let delta = Math.round((Date.now() - this._firstVisibleTime) / 500);
+ if (this._suggestedTilePresent) {
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_LIFE_SPAN_SUGGESTED").add(delta);
+ }
+ else {
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_LIFE_SPAN").add(delta);
+ }
+ },
+
+ /**
+ * Handles all page events.
+ */
+ handleEvent: function Page_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "load":
+ this.onPageVisibleAndLoaded();
+ break;
+ case "unload":
+ this._handleUnloadEvent();
+ break;
+ case "click":
+ let {button, target} = aEvent;
+ // Go up ancestors until we find a Site or not
+ while (target) {
+ if (target.hasOwnProperty("_newtabSite")) {
+ target._newtabSite.onClick(aEvent);
+ break;
+ }
+ target = target.parentNode;
+ }
+ break;
+ case "dragover":
+ if (gDrag.isValid(aEvent) && gDrag.draggedSite)
+ aEvent.preventDefault();
+ break;
+ case "drop":
+ if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ break;
+ case "visibilitychange":
+ // Cancel any delayed updates for hidden pages now that we're visible.
+ if (this._scheduleUpdateTimeout) {
+ clearTimeout(this._scheduleUpdateTimeout);
+ this._scheduleUpdateTimeout = null;
+
+ // An update was pending so force an update now.
+ this.update();
+ }
+
+ setTimeout(() => this.onPageFirstVisible());
+ removeEventListener("visibilitychange", this);
+ break;
+ }
+ },
+
+ onPageFirstVisible: function () {
+ // Record another page impression.
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
+
+ for (let site of gGrid.sites) {
+ if (site) {
+ // The site may need to modify and/or re-render itself if
+ // something changed after newtab was created by preloader.
+ // For example, the suggested tile endTime may have passed.
+ site.onFirstVisible();
+ }
+ }
+
+ // save timestamp to compute page life-span delta
+ this._firstVisibleTime = Date.now();
+
+ if (document.readyState == "complete") {
+ this.onPageVisibleAndLoaded();
+ } else {
+ addEventListener("load", this);
+ }
+ },
+
+ onPageVisibleAndLoaded() {
+ // Send the index of the last visible tile.
+ this.reportLastVisibleTileIndex();
+ // Maybe tell the user they can undo an initial automigration
+ this.maybeShowAutoMigrationUndoNotification();
+ },
+
+ reportLastVisibleTileIndex() {
+ let cwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let rect = cwu.getBoundsWithoutFlushing(gGrid.node);
+ let nodes = cwu.nodesFromRect(rect.left, rect.top, 0, rect.width,
+ rect.height, 0, true, false);
+
+ let i = -1;
+ let lastIndex = -1;
+ let sites = gGrid.sites;
+
+ for (let node of nodes) {
+ if (node.classList && node.classList.contains("newtab-cell")) {
+ if (sites[++i]) {
+ lastIndex = i;
+ if (sites[i].link.targetedSite) {
+ // record that suggested tile is shown to use suggested-tiles-histogram
+ this._suggestedTilePresent = true;
+ }
+ }
+ }
+ }
+
+ DirectoryLinksProvider.reportSitesAction(sites, "view", lastIndex);
+ },
+
+ maybeShowAutoMigrationUndoNotification() {
+ sendAsyncMessage("NewTab:MaybeShowAutoMigrationUndoNotification");
+ },
+};
diff --git a/browser/base/content/newtab/search.js b/browser/base/content/newtab/search.js
new file mode 100644
index 000000000..cbbb6e243
--- /dev/null
+++ b/browser/base/content/newtab/search.js
@@ -0,0 +1,15 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+var gSearch = {
+ init: function () {
+ document.getElementById("newtab-search-submit")
+ .addEventListener("click", e => this._contentSearchController.search(e));
+ let textbox = document.getElementById("newtab-search-text");
+ this._contentSearchController =
+ new ContentSearchUIController(textbox, textbox.parentNode, "newtab", "newtab");
+ },
+};
diff --git a/browser/base/content/newtab/sites.js b/browser/base/content/newtab/sites.js
new file mode 100644
index 000000000..9d103ce9b
--- /dev/null
+++ b/browser/base/content/newtab/sites.js
@@ -0,0 +1,440 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+const THUMBNAIL_PLACEHOLDER_ENABLED =
+ Services.prefs.getBoolPref("browser.newtabpage.thumbnailPlaceholder");
+
+/**
+ * This class represents a site that is contained in a cell and can be pinned,
+ * moved around or deleted.
+ */
+function Site(aNode, aLink) {
+ this._node = aNode;
+ this._node._newtabSite = this;
+
+ this._link = aLink;
+
+ this._render();
+ this._addEventHandlers();
+}
+
+Site.prototype = {
+ /**
+ * The site's DOM node.
+ */
+ get node() { return this._node; },
+
+ /**
+ * The site's link.
+ */
+ get link() { return this._link; },
+
+ /**
+ * The url of the site's link.
+ */
+ get url() { return this.link.url; },
+
+ /**
+ * The title of the site's link.
+ */
+ get title() { return this.link.title || this.link.url; },
+
+ /**
+ * The site's parent cell.
+ */
+ get cell() {
+ let parentNode = this.node.parentNode;
+ return parentNode && parentNode._newtabCell;
+ },
+
+ /**
+ * Pins the site on its current or a given index.
+ * @param aIndex The pinned index (optional).
+ * @return true if link changed type after pin
+ */
+ pin: function Site_pin(aIndex) {
+ if (typeof aIndex == "undefined")
+ aIndex = this.cell.index;
+
+ this._updateAttributes(true);
+ let changed = gPinnedLinks.pin(this._link, aIndex);
+ if (changed) {
+ // render site again to remove suggested/sponsored tags
+ this._render();
+ }
+ return changed;
+ },
+
+ /**
+ * Unpins the site and calls the given callback when done.
+ */
+ unpin: function Site_unpin() {
+ if (this.isPinned()) {
+ this._updateAttributes(false);
+ gPinnedLinks.unpin(this._link);
+ gUpdater.updateGrid();
+ }
+ },
+
+ /**
+ * Checks whether this site is pinned.
+ * @return Whether this site is pinned.
+ */
+ isPinned: function Site_isPinned() {
+ return gPinnedLinks.isPinned(this._link);
+ },
+
+ /**
+ * Blocks the site (removes it from the grid) and calls the given callback
+ * when done.
+ */
+ block: function Site_block() {
+ if (!gBlockedLinks.isBlocked(this._link)) {
+ gUndoDialog.show(this);
+ gBlockedLinks.block(this._link);
+ gUpdater.updateGrid();
+ }
+ },
+
+ /**
+ * Gets the DOM node specified by the given query selector.
+ * @param aSelector The query selector.
+ * @return The DOM node we found.
+ */
+ _querySelector: function Site_querySelector(aSelector) {
+ return this.node.querySelector(aSelector);
+ },
+
+ /**
+ * Updates attributes for all nodes which status depends on this site being
+ * pinned or unpinned.
+ * @param aPinned Whether this site is now pinned or unpinned.
+ */
+ _updateAttributes: function (aPinned) {
+ let control = this._querySelector(".newtab-control-pin");
+
+ if (aPinned) {
+ this.node.setAttribute("pinned", true);
+ control.setAttribute("title", newTabString("unpin"));
+ } else {
+ this.node.removeAttribute("pinned");
+ control.setAttribute("title", newTabString("pin"));
+ }
+ },
+
+ _newTabString: function(str, substrArr) {
+ let regExp = /%[0-9]\$S/g;
+ let matches;
+ while ((matches = regExp.exec(str))) {
+ let match = matches[0];
+ let index = match.charAt(1); // Get the digit in the regExp.
+ str = str.replace(match, substrArr[index - 1]);
+ }
+ return str;
+ },
+
+ _getSuggestedTileExplanation: function() {
+ let targetedName = `<strong> ${this.link.targetedName} </strong>`;
+ let targetedSite = `<strong> ${this.link.targetedSite} </strong>`;
+ if (this.link.explanation) {
+ return this._newTabString(this.link.explanation, [targetedName, targetedSite]);
+ }
+ return newTabString("suggested.button", [targetedName]);
+ },
+
+ /**
+ * Checks for and modifies link at campaign end time
+ */
+ _checkLinkEndTime: function Site_checkLinkEndTime() {
+ if (this.link.endTime && this.link.endTime < Date.now()) {
+ let oldUrl = this.url;
+ // chop off the path part from url
+ this.link.url = Services.io.newURI(this.url, null, null).resolve("/");
+ // clear supplied images - this triggers thumbnail download for new url
+ delete this.link.imageURI;
+ delete this.link.enhancedImageURI;
+ // remove endTime to avoid further time checks
+ delete this.link.endTime;
+ // clear enhanced-content image that may still exist in preloaded page
+ this._querySelector(".enhanced-content").style.backgroundImage = "";
+ gPinnedLinks.replace(oldUrl, this.link);
+ }
+ },
+
+ /**
+ * Renders the site's data (fills the HTML fragment).
+ */
+ _render: function Site_render() {
+ // first check for end time, as it may modify the link
+ this._checkLinkEndTime();
+ // setup display variables
+ let enhanced = gAllPages.enhanced && DirectoryLinksProvider.getEnhancedLink(this.link);
+ let url = this.url;
+ let title = enhanced && enhanced.title ? enhanced.title :
+ this.link.type == "history" ? this.link.baseDomain :
+ this.title;
+ let tooltip = (this.title == url ? this.title : this.title + "\n" + url);
+
+ let link = this._querySelector(".newtab-link");
+ link.setAttribute("title", tooltip);
+ link.setAttribute("href", url);
+ this.node.setAttribute("type", this.link.type);
+
+ let titleNode = this._querySelector(".newtab-title");
+ titleNode.textContent = title;
+ if (this.link.titleBgColor) {
+ titleNode.style.backgroundColor = this.link.titleBgColor;
+ }
+
+ // remove "suggested" attribute to avoid showing "suggested" tag
+ // after site was pinned or dropped
+ this.node.removeAttribute("suggested");
+
+ if (this.link.targetedSite) {
+ if (this.node.getAttribute("type") != "sponsored") {
+ this._querySelector(".newtab-sponsored").textContent =
+ newTabString("suggested.tag");
+ }
+
+ this.node.setAttribute("suggested", true);
+ let explanation = this._getSuggestedTileExplanation();
+ this._querySelector(".newtab-suggested").innerHTML =
+ `<div class='newtab-suggested-bounds'> ${explanation} </div>`;
+ }
+
+ if (this.isPinned())
+ this._updateAttributes(true);
+ // Capture the page if the thumbnail is missing, which will cause page.js
+ // to be notified and call our refreshThumbnail() method.
+ this.captureIfMissing();
+ // but still display whatever thumbnail might be available now.
+ this.refreshThumbnail();
+ },
+
+ /**
+ * Called when the site's tab becomes visible for the first time.
+ * Since the newtab may be preloaded long before it's displayed,
+ * check for changed conditions and re-render if needed
+ */
+ onFirstVisible: function Site_onFirstVisible() {
+ if (this.link.endTime && this.link.endTime < Date.now()) {
+ // site needs to change landing url and background image
+ this._render();
+ }
+ else {
+ this.captureIfMissing();
+ }
+ },
+
+ /**
+ * Captures the site's thumbnail in the background, but only if there's no
+ * existing thumbnail and the page allows background captures.
+ */
+ captureIfMissing: function Site_captureIfMissing() {
+ if (!document.hidden && !this.link.imageURI) {
+ BackgroundPageThumbs.captureIfMissing(this.url);
+ }
+ },
+
+ /**
+ * Refreshes the thumbnail for the site.
+ */
+ refreshThumbnail: function Site_refreshThumbnail() {
+ // Only enhance tiles if that feature is turned on
+ let link = gAllPages.enhanced && DirectoryLinksProvider.getEnhancedLink(this.link) ||
+ this.link;
+
+ let thumbnail = this._querySelector(".newtab-thumbnail.thumbnail");
+ if (link.bgColor) {
+ thumbnail.style.backgroundColor = link.bgColor;
+ }
+ let uri = link.imageURI || PageThumbs.getThumbnailURL(this.url);
+ thumbnail.style.backgroundImage = 'url("' + uri + '")';
+
+ if (THUMBNAIL_PLACEHOLDER_ENABLED &&
+ link.type == "history" &&
+ link.baseDomain) {
+ let placeholder = this._querySelector(".newtab-thumbnail.placeholder");
+ let charCodeSum = 0;
+ for (let c of link.baseDomain) {
+ charCodeSum += c.charCodeAt(0);
+ }
+ const COLORS = 16;
+ let hue = Math.round((charCodeSum % COLORS) / COLORS * 360);
+ placeholder.style.backgroundColor = "hsl(" + hue + ",80%,40%)";
+ placeholder.textContent = link.baseDomain.substr(0,1).toUpperCase();
+ }
+
+ if (link.enhancedImageURI) {
+ let enhanced = this._querySelector(".enhanced-content");
+ enhanced.style.backgroundImage = 'url("' + link.enhancedImageURI + '")';
+
+ if (this.link.type != link.type) {
+ this.node.setAttribute("type", "enhanced");
+ this.enhancedId = link.directoryId;
+ }
+ }
+ },
+
+ _ignoreHoverEvents: function(element) {
+ element.addEventListener("mouseover", () => {
+ this.cell.node.setAttribute("ignorehover", "true");
+ });
+ element.addEventListener("mouseout", () => {
+ this.cell.node.removeAttribute("ignorehover");
+ });
+ },
+
+ /**
+ * Adds event handlers for the site and its buttons.
+ */
+ _addEventHandlers: function Site_addEventHandlers() {
+ // Register drag-and-drop event handlers.
+ this._node.addEventListener("dragstart", this, false);
+ this._node.addEventListener("dragend", this, false);
+ this._node.addEventListener("mouseover", this, false);
+
+ // Specially treat the sponsored icon & suggested explanation
+ // text to prevent regular hover effects
+ let sponsored = this._querySelector(".newtab-sponsored");
+ let suggested = this._querySelector(".newtab-suggested");
+ this._ignoreHoverEvents(sponsored);
+ this._ignoreHoverEvents(suggested);
+ },
+
+ /**
+ * Speculatively opens a connection to the current site.
+ */
+ _speculativeConnect: function Site_speculativeConnect() {
+ let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
+ let uri = Services.io.newURI(this.url, null, null);
+ try {
+ // This can throw for certain internal URLs, when they wind up in
+ // about:newtab. Be sure not to propagate the error.
+ sc.speculativeConnect(uri, null);
+ } catch (e) {}
+ },
+
+ /**
+ * Record interaction with site using telemetry.
+ */
+ _recordSiteClicked: function Site_recordSiteClicked(aIndex) {
+ if (Services.prefs.prefHasUserValue("browser.newtabpage.rows") ||
+ Services.prefs.prefHasUserValue("browser.newtabpage.columns") ||
+ aIndex > 8) {
+ // We only want to get indices for the default configuration, everything
+ // else goes in the same bucket.
+ aIndex = 9;
+ }
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
+ .add(aIndex);
+ },
+
+ _toggleLegalText: function(buttonClass, explanationTextClass) {
+ let button = this._querySelector(buttonClass);
+ if (button.hasAttribute("active")) {
+ let explain = this._querySelector(explanationTextClass);
+ explain.parentNode.removeChild(explain);
+
+ button.removeAttribute("active");
+ }
+ else {
+ let explain = document.createElementNS(HTML_NAMESPACE, "div");
+ explain.className = explanationTextClass.slice(1); // Slice off the first character, '.'
+ this.node.appendChild(explain);
+
+ let link = '<a href="' + TILES_EXPLAIN_LINK + '">' +
+ newTabString("learn.link") + "</a>";
+ let type = (this.node.getAttribute("suggested") && this.node.getAttribute("type") == "affiliate") ?
+ "suggested" : this.node.getAttribute("type");
+ let icon = '<input type="button" class="newtab-control newtab-' +
+ (type == "enhanced" ? "customize" : "control-block") + '"/>';
+ explain.innerHTML = newTabString(type + (type == "sponsored" ? ".explain2" : ".explain"), [icon, link]);
+
+ button.setAttribute("active", "true");
+ }
+ },
+
+ /**
+ * Handles site click events.
+ */
+ onClick: function Site_onClick(aEvent) {
+ let action;
+ let pinned = this.isPinned();
+ let tileIndex = this.cell.index;
+ let {button, target} = aEvent;
+
+ // Handle tile/thumbnail link click
+ if (target.classList.contains("newtab-link") ||
+ target.parentElement.classList.contains("newtab-link")) {
+ // Record for primary and middle clicks
+ if (button == 0 || button == 1) {
+ this._recordSiteClicked(tileIndex);
+ action = "click";
+ }
+ }
+ // Handle sponsored explanation link click
+ else if (target.parentElement.classList.contains("sponsored-explain")) {
+ action = "sponsored_link";
+ }
+ else if (target.parentElement.classList.contains("suggested-explain")) {
+ action = "suggested_link";
+ }
+ // Only handle primary clicks for the remaining targets
+ else if (button == 0) {
+ aEvent.preventDefault();
+ if (target.classList.contains("newtab-control-block")) {
+ // Notify DirectoryLinksProvider of suggested tile block, this may
+ // affect if and how suggested tiles are recommended and needs to
+ // be reported before pages are updated inside block() call
+ if (this.link.targetedSite) {
+ DirectoryLinksProvider.handleSuggestedTileBlock();
+ }
+ this.block();
+ action = "block";
+ }
+ else if (target.classList.contains("sponsored-explain") ||
+ target.classList.contains("newtab-sponsored")) {
+ this._toggleLegalText(".newtab-sponsored", ".sponsored-explain");
+ action = "sponsored";
+ }
+ else if (pinned && target.classList.contains("newtab-control-pin")) {
+ this.unpin();
+ action = "unpin";
+ }
+ else if (!pinned && target.classList.contains("newtab-control-pin")) {
+ if (this.pin()) {
+ // suggested link has changed - update rest of the pages
+ gAllPages.update(gPage);
+ }
+ action = "pin";
+ }
+ }
+
+ // Report all link click actions
+ if (action) {
+ DirectoryLinksProvider.reportSitesAction(gGrid.sites, action, tileIndex);
+ }
+ },
+
+ /**
+ * Handles all site events.
+ */
+ handleEvent: function Site_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "mouseover":
+ this._node.removeEventListener("mouseover", this, false);
+ this._speculativeConnect();
+ break;
+ case "dragstart":
+ gDrag.start(this, aEvent);
+ break;
+ case "dragend":
+ gDrag.end(this, aEvent);
+ break;
+ }
+ }
+};
diff --git a/browser/base/content/newtab/transformations.js b/browser/base/content/newtab/transformations.js
new file mode 100644
index 000000000..f7db0ad84
--- /dev/null
+++ b/browser/base/content/newtab/transformations.js
@@ -0,0 +1,270 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton allows to transform the grid by repositioning a site's node
+ * in the DOM and by showing or hiding the node. It additionally provides
+ * convenience methods to work with a site's DOM node.
+ */
+var gTransformation = {
+ /**
+ * Returns the width of the left and top border of a cell. We need to take it
+ * into account when measuring and comparing site and cell positions.
+ */
+ get _cellBorderWidths() {
+ let cstyle = window.getComputedStyle(gGrid.cells[0].node, null);
+ let widths = {
+ left: parseInt(cstyle.getPropertyValue("border-left-width")),
+ top: parseInt(cstyle.getPropertyValue("border-top-width"))
+ };
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "_cellBorderWidths",
+ {value: widths, enumerable: true});
+
+ return widths;
+ },
+
+ /**
+ * Gets a DOM node's position.
+ * @param aNode The DOM node.
+ * @return A Rect instance with the position.
+ */
+ getNodePosition: function Transformation_getNodePosition(aNode) {
+ let {left, top, width, height} = aNode.getBoundingClientRect();
+ return new Rect(left + scrollX, top + scrollY, width, height);
+ },
+
+ /**
+ * Fades a given node from zero to full opacity.
+ * @param aNode The node to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ fadeNodeIn: function Transformation_fadeNodeIn(aNode, aCallback) {
+ this._setNodeOpacity(aNode, 1, function () {
+ // Clear the style property.
+ aNode.style.opacity = "";
+
+ if (aCallback)
+ aCallback();
+ });
+ },
+
+ /**
+ * Fades a given node from full to zero opacity.
+ * @param aNode The node to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ fadeNodeOut: function Transformation_fadeNodeOut(aNode, aCallback) {
+ this._setNodeOpacity(aNode, 0, aCallback);
+ },
+
+ /**
+ * Fades a given site from zero to full opacity.
+ * @param aSite The site to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ showSite: function Transformation_showSite(aSite, aCallback) {
+ this.fadeNodeIn(aSite.node, aCallback);
+ },
+
+ /**
+ * Fades a given site from full to zero opacity.
+ * @param aSite The site to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ hideSite: function Transformation_hideSite(aSite, aCallback) {
+ this.fadeNodeOut(aSite.node, aCallback);
+ },
+
+ /**
+ * Allows to set a site's position.
+ * @param aSite The site to re-position.
+ * @param aPosition The desired position for the given site.
+ */
+ setSitePosition: function Transformation_setSitePosition(aSite, aPosition) {
+ let style = aSite.node.style;
+ let {top, left} = aPosition;
+
+ style.top = top + "px";
+ style.left = left + "px";
+ },
+
+ /**
+ * Freezes a site in its current position by positioning it absolute.
+ * @param aSite The site to freeze.
+ */
+ freezeSitePosition: function Transformation_freezeSitePosition(aSite) {
+ if (this._isFrozen(aSite))
+ return;
+
+ let style = aSite.node.style;
+ let comp = getComputedStyle(aSite.node, null);
+ style.width = comp.getPropertyValue("width");
+ style.height = comp.getPropertyValue("height");
+
+ aSite.node.setAttribute("frozen", "true");
+ this.setSitePosition(aSite, this.getNodePosition(aSite.node));
+ },
+
+ /**
+ * Unfreezes a site by removing its absolute positioning.
+ * @param aSite The site to unfreeze.
+ */
+ unfreezeSitePosition: function Transformation_unfreezeSitePosition(aSite) {
+ if (!this._isFrozen(aSite))
+ return;
+
+ let style = aSite.node.style;
+ style.left = style.top = style.width = style.height = "";
+ aSite.node.removeAttribute("frozen");
+ },
+
+ /**
+ * Slides the given site to the target node's position.
+ * @param aSite The site to move.
+ * @param aTarget The slide target.
+ * @param aOptions Set of options (see below).
+ * unfreeze - unfreeze the site after sliding
+ * callback - the callback to call when finished
+ */
+ slideSiteTo: function Transformation_slideSiteTo(aSite, aTarget, aOptions) {
+ let currentPosition = this.getNodePosition(aSite.node);
+ let targetPosition = this.getNodePosition(aTarget.node)
+ let callback = aOptions && aOptions.callback;
+
+ let self = this;
+
+ function finish() {
+ if (aOptions && aOptions.unfreeze)
+ self.unfreezeSitePosition(aSite);
+
+ if (callback)
+ callback();
+ }
+
+ // We need to take the width of a cell's border into account.
+ targetPosition.left += this._cellBorderWidths.left;
+ targetPosition.top += this._cellBorderWidths.top;
+
+ // Nothing to do here if the positions already match.
+ if (currentPosition.left == targetPosition.left &&
+ currentPosition.top == targetPosition.top) {
+ finish();
+ } else {
+ this.setSitePosition(aSite, targetPosition);
+ this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
+ }
+ },
+
+ /**
+ * Rearranges a given array of sites and moves them to their new positions or
+ * fades in/out new/removed sites.
+ * @param aSites An array of sites to rearrange.
+ * @param aOptions Set of options (see below).
+ * unfreeze - unfreeze the site after rearranging
+ * callback - the callback to call when finished
+ */
+ rearrangeSites: function Transformation_rearrangeSites(aSites, aOptions) {
+ let batch = [];
+ let cells = gGrid.cells;
+ let callback = aOptions && aOptions.callback;
+ let unfreeze = aOptions && aOptions.unfreeze;
+
+ aSites.forEach(function (aSite, aIndex) {
+ // Do not re-arrange empty cells or the dragged site.
+ if (!aSite || aSite == gDrag.draggedSite)
+ return;
+
+ batch.push(new Promise(resolve => {
+ if (!cells[aIndex]) {
+ // The site disappeared from the grid, hide it.
+ this.hideSite(aSite, resolve);
+ } else if (this._getNodeOpacity(aSite.node) != 1) {
+ // The site disappeared before but is now back, show it.
+ this.showSite(aSite, resolve);
+ } else {
+ // The site's position has changed, move it around.
+ this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: resolve});
+ }
+ }));
+ }, this);
+
+ if (callback) {
+ Promise.all(batch).then(callback);
+ }
+ },
+
+ /**
+ * Listens for the 'transitionend' event on a given node and calls the given
+ * callback.
+ * @param aNode The node that is transitioned.
+ * @param aProperties The properties we'll wait to be transitioned.
+ * @param aCallback The callback to call when finished.
+ */
+ _whenTransitionEnded:
+ function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
+
+ let props = new Set(aProperties);
+ aNode.addEventListener("transitionend", function onEnd(e) {
+ if (props.has(e.propertyName)) {
+ aNode.removeEventListener("transitionend", onEnd);
+ aCallback();
+ }
+ });
+ },
+
+ /**
+ * Gets a given node's opacity value.
+ * @param aNode The node to get the opacity value from.
+ * @return The node's opacity value.
+ */
+ _getNodeOpacity: function Transformation_getNodeOpacity(aNode) {
+ let cstyle = window.getComputedStyle(aNode, null);
+ return cstyle.getPropertyValue("opacity");
+ },
+
+ /**
+ * Sets a given node's opacity.
+ * @param aNode The node to set the opacity value for.
+ * @param aOpacity The opacity value to set.
+ * @param aCallback The callback to call when finished.
+ */
+ _setNodeOpacity:
+ function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) {
+
+ if (this._getNodeOpacity(aNode) == aOpacity) {
+ if (aCallback)
+ aCallback();
+ } else {
+ if (aCallback) {
+ this._whenTransitionEnded(aNode, ["opacity"], aCallback);
+ }
+
+ aNode.style.opacity = aOpacity;
+ }
+ },
+
+ /**
+ * Moves a site to the cell with the given index.
+ * @param aSite The site to move.
+ * @param aIndex The target cell's index.
+ * @param aOptions Options that are directly passed to slideSiteTo().
+ */
+ _moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) {
+ this.freezeSitePosition(aSite);
+ this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
+ },
+
+ /**
+ * Checks whether a site is currently frozen.
+ * @param aSite The site to check.
+ * @return Whether the given site is frozen.
+ */
+ _isFrozen: function Transformation_isFrozen(aSite) {
+ return aSite.node.hasAttribute("frozen");
+ }
+};
diff --git a/browser/base/content/newtab/undo.js b/browser/base/content/newtab/undo.js
new file mode 100644
index 000000000..b856914d2
--- /dev/null
+++ b/browser/base/content/newtab/undo.js
@@ -0,0 +1,116 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * Dialog allowing to undo the removal of single site or to completely restore
+ * the grid's original state.
+ */
+var gUndoDialog = {
+ /**
+ * The undo dialog's timeout in miliseconds.
+ */
+ HIDE_TIMEOUT_MS: 15000,
+
+ /**
+ * Contains undo information.
+ */
+ _undoData: null,
+
+ /**
+ * Initializes the undo dialog.
+ */
+ init: function UndoDialog_init() {
+ this._undoContainer = document.getElementById("newtab-undo-container");
+ this._undoContainer.addEventListener("click", this, false);
+ this._undoButton = document.getElementById("newtab-undo-button");
+ this._undoCloseButton = document.getElementById("newtab-undo-close-button");
+ this._undoRestoreButton = document.getElementById("newtab-undo-restore-button");
+ },
+
+ /**
+ * Shows the undo dialog.
+ * @param aSite The site that just got removed.
+ */
+ show: function UndoDialog_show(aSite) {
+ if (this._undoData)
+ clearTimeout(this._undoData.timeout);
+
+ this._undoData = {
+ index: aSite.cell.index,
+ wasPinned: aSite.isPinned(),
+ blockedLink: aSite.link,
+ timeout: setTimeout(this.hide.bind(this), this.HIDE_TIMEOUT_MS)
+ };
+
+ this._undoContainer.removeAttribute("undo-disabled");
+ this._undoButton.removeAttribute("tabindex");
+ this._undoCloseButton.removeAttribute("tabindex");
+ this._undoRestoreButton.removeAttribute("tabindex");
+ },
+
+ /**
+ * Hides the undo dialog.
+ */
+ hide: function UndoDialog_hide() {
+ if (!this._undoData)
+ return;
+
+ clearTimeout(this._undoData.timeout);
+ this._undoData = null;
+ this._undoContainer.setAttribute("undo-disabled", "true");
+ this._undoButton.setAttribute("tabindex", "-1");
+ this._undoCloseButton.setAttribute("tabindex", "-1");
+ this._undoRestoreButton.setAttribute("tabindex", "-1");
+ },
+
+ /**
+ * The undo dialog event handler.
+ * @param aEvent The event to handle.
+ */
+ handleEvent: function UndoDialog_handleEvent(aEvent) {
+ switch (aEvent.target.id) {
+ case "newtab-undo-button":
+ this._undo();
+ break;
+ case "newtab-undo-restore-button":
+ this._undoAll();
+ break;
+ case "newtab-undo-close-button":
+ this.hide();
+ break;
+ }
+ },
+
+ /**
+ * Undo the last blocked site.
+ */
+ _undo: function UndoDialog_undo() {
+ if (!this._undoData)
+ return;
+
+ let {index, wasPinned, blockedLink} = this._undoData;
+ gBlockedLinks.unblock(blockedLink);
+
+ if (wasPinned) {
+ gPinnedLinks.pin(blockedLink, index);
+ }
+
+ gUpdater.updateGrid();
+ this.hide();
+ },
+
+ /**
+ * Undo all blocked sites.
+ */
+ _undoAll: function UndoDialog_undoAll() {
+ NewTabUtils.undoAll(function() {
+ gUpdater.updateGrid();
+ this.hide();
+ }.bind(this));
+ }
+};
+
+gUndoDialog.init();
diff --git a/browser/base/content/newtab/updater.js b/browser/base/content/newtab/updater.js
new file mode 100644
index 000000000..2bab74d70
--- /dev/null
+++ b/browser/base/content/newtab/updater.js
@@ -0,0 +1,177 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides functionality to update the current grid to a new
+ * set of pinned and blocked sites. It adds, moves and removes sites.
+ */
+var gUpdater = {
+ /**
+ * Updates the current grid according to its pinned and blocked sites.
+ * This removes old, moves existing and creates new sites to fill gaps.
+ * @param aCallback The callback to call when finished.
+ */
+ updateGrid: function Updater_updateGrid(aCallback) {
+ let links = gLinks.getLinks().slice(0, gGrid.cells.length);
+
+ // Find all sites that remain in the grid.
+ let sites = this._findRemainingSites(links);
+
+ // Remove sites that are no longer in the grid.
+ this._removeLegacySites(sites, () => {
+ // Freeze all site positions so that we can move their DOM nodes around
+ // without any visual impact.
+ this._freezeSitePositions(sites);
+
+ // Move the sites' DOM nodes to their new position in the DOM. This will
+ // have no visual effect as all the sites have been frozen and will
+ // remain in their current position.
+ this._moveSiteNodes(sites);
+
+ // Now it's time to animate the sites actually moving to their new
+ // positions.
+ this._rearrangeSites(sites, () => {
+ // Try to fill empty cells and finish.
+ this._fillEmptyCells(links, aCallback);
+
+ // Update other pages that might be open to keep them synced.
+ gAllPages.update(gPage);
+ });
+ });
+ },
+
+ /**
+ * Takes an array of links and tries to correlate them to sites contained in
+ * the current grid. If no corresponding site can be found (i.e. the link is
+ * new and a site will be created) then just set it to null.
+ * @param aLinks The array of links to find sites for.
+ * @return Array of sites mapped to the given links (can contain null values).
+ */
+ _findRemainingSites: function Updater_findRemainingSites(aLinks) {
+ let map = {};
+
+ // Create a map to easily retrieve the site for a given URL.
+ gGrid.sites.forEach(function (aSite) {
+ if (aSite)
+ map[aSite.url] = aSite;
+ });
+
+ // Map each link to its corresponding site, if any.
+ return aLinks.map(function (aLink) {
+ return aLink && (aLink.url in map) && map[aLink.url];
+ });
+ },
+
+ /**
+ * Freezes the given sites' positions.
+ * @param aSites The array of sites to freeze.
+ */
+ _freezeSitePositions: function Updater_freezeSitePositions(aSites) {
+ aSites.forEach(function (aSite) {
+ if (aSite)
+ gTransformation.freezeSitePosition(aSite);
+ });
+ },
+
+ /**
+ * Moves the given sites' DOM nodes to their new positions.
+ * @param aSites The array of sites to move.
+ */
+ _moveSiteNodes: function Updater_moveSiteNodes(aSites) {
+ let cells = gGrid.cells;
+
+ // Truncate the given array of sites to not have more sites than cells.
+ // This can happen when the user drags a bookmark (or any other new kind
+ // of link) onto the grid.
+ let sites = aSites.slice(0, cells.length);
+
+ sites.forEach(function (aSite, aIndex) {
+ let cell = cells[aIndex];
+ let cellSite = cell.site;
+
+ // The site's position didn't change.
+ if (!aSite || cellSite != aSite) {
+ let cellNode = cell.node;
+
+ // Empty the cell if necessary.
+ if (cellSite)
+ cellNode.removeChild(cellSite.node);
+
+ // Put the new site in place, if any.
+ if (aSite)
+ cellNode.appendChild(aSite.node);
+ }
+ }, this);
+ },
+
+ /**
+ * Rearranges the given sites and slides them to their new positions.
+ * @param aSites The array of sites to re-arrange.
+ * @param aCallback The callback to call when finished.
+ */
+ _rearrangeSites: function Updater_rearrangeSites(aSites, aCallback) {
+ let options = {callback: aCallback, unfreeze: true};
+ gTransformation.rearrangeSites(aSites, options);
+ },
+
+ /**
+ * Removes all sites from the grid that are not in the given links array or
+ * exceed the grid.
+ * @param aSites The array of sites remaining in the grid.
+ * @param aCallback The callback to call when finished.
+ */
+ _removeLegacySites: function Updater_removeLegacySites(aSites, aCallback) {
+ let batch = [];
+
+ // Delete sites that were removed from the grid.
+ gGrid.sites.forEach(function (aSite) {
+ // The site must be valid and not in the current grid.
+ if (!aSite || aSites.indexOf(aSite) != -1)
+ return;
+
+ batch.push(new Promise(resolve => {
+ // Fade out the to-be-removed site.
+ gTransformation.hideSite(aSite, function () {
+ let node = aSite.node;
+
+ // Remove the site from the DOM.
+ node.parentNode.removeChild(node);
+ resolve();
+ });
+ }));
+ });
+
+ Promise.all(batch).then(aCallback);
+ },
+
+ /**
+ * Tries to fill empty cells with new links if available.
+ * @param aLinks The array of links.
+ * @param aCallback The callback to call when finished.
+ */
+ _fillEmptyCells: function Updater_fillEmptyCells(aLinks, aCallback) {
+ let {cells, sites} = gGrid;
+
+ // Find empty cells and fill them.
+ Promise.all(sites.map((aSite, aIndex) => {
+ if (aSite || !aLinks[aIndex])
+ return null;
+
+ return new Promise(resolve => {
+ // Create the new site and fade it in.
+ let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
+
+ // Set the site's initial opacity to zero.
+ site.node.style.opacity = 0;
+
+ // Flush all style changes for the dynamically inserted site to make
+ // the fade-in transition work.
+ window.getComputedStyle(site.node).opacity;
+ gTransformation.showSite(site, resolve);
+ });
+ })).then(aCallback).catch(console.exception);
+ }
+};
diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
new file mode 100644
index 000000000..8eb9b034f
--- /dev/null
+++ b/browser/base/content/nsContextMenu.js
@@ -0,0 +1,1878 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
+Components.utils.import("resource://gre/modules/LoginManagerContextMenu.jsm");
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+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");
+
+var gContextMenuContentData = null;
+
+function nsContextMenu(aXulMenu, aIsShift) {
+ this.shouldDisplay = true;
+ this.initMenu(aXulMenu, aIsShift);
+}
+
+// Prototype for nsContextMenu "class."
+nsContextMenu.prototype = {
+ initMenu: function CM_initMenu(aXulMenu, aIsShift) {
+ // Get contextual info.
+ this.setTarget(document.popupNode, document.popupRangeParent,
+ document.popupRangeOffset);
+ if (!this.shouldDisplay)
+ return;
+
+ this.hasPageMenu = false;
+ this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
+ if (!aIsShift) {
+ if (this.isRemote) {
+ this.hasPageMenu =
+ PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
+ this.browser, aXulMenu);
+ }
+ else {
+ this.hasPageMenu = PageMenuParent.buildAndAddToPopup(this.target, aXulMenu);
+ }
+
+ let subject = {
+ menu: aXulMenu,
+ tab: gBrowser ? gBrowser.getTabForBrowser(this.browser) : undefined,
+ isContentSelected: this.isContentSelected,
+ inFrame: this.inFrame,
+ isTextSelected: this.isTextSelected,
+ onTextInput: this.onTextInput,
+ onLink: this.onLink,
+ onImage: this.onImage,
+ onVideo: this.onVideo,
+ onAudio: this.onAudio,
+ onCanvas: this.onCanvas,
+ onEditableArea: this.onEditableArea,
+ srcUrl: this.mediaURL,
+ frameUrl: gContextMenuContentData ? gContextMenuContentData.docLocation : undefined,
+ pageUrl: this.browser ? this.browser.currentURI.spec : undefined,
+ linkUrl: this.linkURL,
+ selectionText: this.isTextSelected ? this.selectionInfo.text : undefined,
+ };
+ subject.wrappedJSObject = subject;
+ Services.obs.notifyObservers(subject, "on-build-contextmenu", null);
+ }
+
+ this.isFrameImage = document.getElementById("isFrameImage");
+ this.ellipsis = "\u2026";
+ try {
+ this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+ } catch (e) { }
+
+ // Reset after "on-build-contextmenu" notification in case selection was
+ // changed during the notification.
+ this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
+ this.onPlainTextLink = false;
+
+ let bookmarkPage = document.getElementById("context-bookmarkpage");
+ if (bookmarkPage)
+ BookmarkingUI.onCurrentPageContextPopupShowing();
+
+ // Initialize (disable/remove) menu items.
+ this.initItems();
+
+ // Register this opening of the menu with telemetry:
+ this._checkTelemetryForMenu(aXulMenu);
+ },
+
+ hiding: function CM_hiding() {
+ gContextMenuContentData = null;
+ InlineSpellCheckerUI.clearSuggestionsFromMenu();
+ InlineSpellCheckerUI.clearDictionaryListFromMenu();
+ InlineSpellCheckerUI.uninit();
+ LoginManagerContextMenu.clearLoginsFromMenu(document);
+
+ // This handler self-deletes, only run it if it is still there:
+ if (this._onPopupHiding) {
+ this._onPopupHiding();
+ }
+ },
+
+ initItems: function CM_initItems() {
+ this.initPageMenuSeparator();
+ this.initOpenItems();
+ this.initNavigationItems();
+ this.initViewItems();
+ this.initMiscItems();
+ this.initSpellingItems();
+ this.initSaveItems();
+ this.initClipboardItems();
+ this.initMediaPlayerItems();
+ this.initLeaveDOMFullScreenItems();
+ this.initClickToPlayItems();
+ this.initPasswordManagerItems();
+ this.initSyncItems();
+ },
+
+ initPageMenuSeparator: function CM_initPageMenuSeparator() {
+ this.showItem("page-menu-separator", this.hasPageMenu);
+ },
+
+ initOpenItems: function CM_initOpenItems() {
+ var isMailtoInternal = false;
+ if (this.onMailtoLink) {
+ var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService).
+ getProtocolHandlerInfo("mailto");
+ isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
+ mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
+ (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
+ }
+
+ if (this.isTextSelected && !this.onLink &&
+ this.selectionInfo && this.selectionInfo.linkURL) {
+ this.linkURL = this.selectionInfo.linkURL;
+ try {
+ this.linkURI = makeURI(this.linkURL);
+ } catch (ex) {}
+
+ this.linkTextStr = this.selectionInfo.linkText;
+ this.onPlainTextLink = true;
+ }
+
+ var inContainer = false;
+ if (gContextMenuContentData.userContextId) {
+ inContainer = true;
+ var item = document.getElementById("context-openlinkincontainertab");
+
+ item.setAttribute("data-usercontextid", gContextMenuContentData.userContextId);
+
+ var label =
+ ContextualIdentityService.getUserContextLabel(gContextMenuContentData.userContextId);
+ item.setAttribute("label",
+ gBrowserBundle.formatStringFromName("userContextOpenLink.label",
+ [label], 1));
+ }
+
+ var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
+ var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ var showContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
+ this.showItem("context-openlink", shouldShow && !isWindowPrivate);
+ this.showItem("context-openlinkprivate", shouldShow);
+ this.showItem("context-openlinkintab", shouldShow && !inContainer);
+ this.showItem("context-openlinkincontainertab", shouldShow && inContainer);
+ this.showItem("context-openlinkinusercontext-menu", shouldShow && !isWindowPrivate && showContainers);
+ this.showItem("context-openlinkincurrent", this.onPlainTextLink);
+ this.showItem("context-sep-open", shouldShow);
+ },
+
+ initNavigationItems: function CM_initNavigationItems() {
+ var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
+ this.onCanvas || this.onVideo || this.onAudio ||
+ this.onTextInput || this.onSocial);
+ this.showItem("context-navigation", shouldShow);
+ this.showItem("context-sep-navigation", shouldShow);
+
+ let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
+
+ let stopReloadItem = "";
+ if (shouldShow || this.onSocial) {
+ stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
+ }
+
+ this.showItem("context-reload", stopReloadItem == "reload");
+ this.showItem("context-stop", stopReloadItem == "stop");
+
+ // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
+ //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
+ },
+
+ initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
+ // only show the option if the user is in DOM fullscreen
+ var shouldShow = (this.target.ownerDocument.fullscreenElement != null);
+ this.showItem("context-leave-dom-fullscreen", shouldShow);
+
+ // Explicitly show if in DOM fullscreen, but do not hide it has already been shown
+ if (shouldShow)
+ this.showItem("context-media-sep-commands", true);
+ },
+
+ initSaveItems: function CM_initSaveItems() {
+ var shouldShow = !(this.onTextInput || this.onLink ||
+ this.isContentSelected || this.onImage ||
+ this.onCanvas || this.onVideo || this.onAudio);
+ this.showItem("context-savepage", shouldShow);
+
+ // Save link depends on whether we're in a link, or selected text matches valid URL pattern.
+ this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink);
+
+ // Save image depends on having loaded its content, video and audio don't.
+ this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
+ this.showItem("context-savevideo", this.onVideo);
+ this.showItem("context-saveaudio", this.onAudio);
+ this.showItem("context-video-saveimage", this.onVideo);
+ this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
+ this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
+ // Send media URL (but not for canvas, since it's a big data: URL)
+ this.showItem("context-sendimage", this.onImage);
+ this.showItem("context-sendvideo", this.onVideo);
+ this.showItem("context-castvideo", this.onVideo);
+ this.showItem("context-sendaudio", this.onAudio);
+ let mediaIsBlob = this.mediaURL.startsWith("blob:");
+ this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL || mediaIsBlob);
+ this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL || mediaIsBlob);
+ let shouldShowCast = Services.prefs.getBoolPref("browser.casting.enabled");
+ // getServicesForVideo alone would be sufficient here (it depends on
+ // SimpleServiceDiscovery.services), but SimpleServiceDiscovery is guaranteed
+ // to be already loaded, since we load it on startup in nsBrowserGlue,
+ // and CastingApps isn't, so check SimpleServiceDiscovery.services first
+ // to avoid needing to load CastingApps.jsm if we don't need to.
+ shouldShowCast = shouldShowCast && this.mediaURL &&
+ SimpleServiceDiscovery.services.length > 0 &&
+ CastingApps.getServicesForVideo(this.target).length > 0;
+ this.setItemAttr("context-castvideo", "disabled", !shouldShowCast);
+ },
+
+ initViewItems: function CM_initViewItems() {
+ // View source is always OK, unless in directory listing.
+ this.showItem("context-viewpartialsource-selection",
+ this.isContentSelected);
+ this.showItem("context-viewpartialsource-mathml",
+ this.onMathML && !this.isContentSelected);
+
+ var shouldShow = !(this.isContentSelected ||
+ this.onImage || this.onCanvas ||
+ this.onVideo || this.onAudio ||
+ this.onLink || this.onTextInput);
+ var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
+ this.showItem("context-viewsource", shouldShow);
+ this.showItem("context-viewinfo", shouldShow);
+ this.showItem("inspect-separator", showInspect);
+ this.showItem("context-inspect", showInspect);
+
+ this.showItem("context-sep-viewsource", shouldShow);
+
+ // Set as Desktop background depends on whether an image was clicked on,
+ // and only works if we have a shell service.
+ var haveSetDesktopBackground = false;
+#ifdef HAVE_SHELL_SERVICE
+ // Only enable Set as Desktop Background if we can get the shell service.
+ var shell = getShellService();
+ if (shell)
+ haveSetDesktopBackground = shell.canSetDesktopBackground;
+#endif
+ this.showItem("context-setDesktopBackground",
+ haveSetDesktopBackground && this.onLoadedImage);
+
+ if (haveSetDesktopBackground && this.onLoadedImage) {
+ document.getElementById("context-setDesktopBackground")
+ .disabled = gContextMenuContentData.disableSetDesktopBackground;
+ }
+
+ // Reload image depends on an image that's not fully loaded
+ this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
+
+ // View image depends on having an image that's not standalone
+ // (or is in a frame), or a canvas.
+ this.showItem("context-viewimage", (this.onImage &&
+ (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
+
+ // View video depends on not having a standalone video.
+ this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
+ this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL);
+
+ // View background image depends on whether there is one, but don't make
+ // background images of a stand-alone media document available.
+ this.showItem("context-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
+ this.showItem("context-sep-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
+ document.getElementById("context-viewbgimage")
+ .disabled = !this.hasBGImage;
+
+ this.showItem("context-viewimageinfo", this.onImage);
+ this.showItem("context-viewimagedesc", this.onImage && this.imageDescURL !== "");
+ },
+
+ initMiscItems: function CM_initMiscItems() {
+ // Use "Bookmark This Link" if on a link.
+ let bookmarkPage = document.getElementById("context-bookmarkpage");
+ this.showItem(bookmarkPage,
+ !(this.isContentSelected || this.onTextInput || this.onLink ||
+ this.onImage || this.onVideo || this.onAudio || this.onSocial ||
+ this.onCanvas));
+ bookmarkPage.setAttribute("tooltiptext", bookmarkPage.getAttribute("buttontooltiptext"));
+
+ this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
+ !this.onSocial) || this.onPlainTextLink);
+ this.showItem("context-keywordfield",
+ this.onTextInput && this.onKeywordField);
+ this.showItem("frame", this.inFrame);
+
+ let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
+ this.showItem("context-searchselect", showSearchSelect);
+ if (showSearchSelect) {
+ this.formatSearchContextItem();
+ }
+
+ // srcdoc cannot be opened separately due to concerns about web
+ // content with about:srcdoc in location bar masquerading as trusted
+ // chrome/addon content.
+ // No need to also test for this.inFrame as this is checked in the parent
+ // submenu.
+ this.showItem("context-showonlythisframe", !this.inSrcdocFrame);
+ this.showItem("context-openframeintab", !this.inSrcdocFrame);
+ this.showItem("context-openframe", !this.inSrcdocFrame);
+ this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
+ this.showItem("open-frame-sep", !this.inSrcdocFrame);
+
+ this.showItem("frame-sep", this.inFrame && this.isTextSelected);
+
+ // Hide menu entries for images, show otherwise
+ if (this.inFrame) {
+ if (BrowserUtils.mimeTypeIsTextBased(this.target.ownerDocument.contentType))
+ this.isFrameImage.removeAttribute('hidden');
+ else
+ this.isFrameImage.setAttribute('hidden', 'true');
+ }
+
+ // BiDi UI
+ this.showItem("context-sep-bidi", !this.onNumeric && top.gBidiUI);
+ this.showItem("context-bidi-text-direction-toggle",
+ this.onTextInput && !this.onNumeric && top.gBidiUI);
+ this.showItem("context-bidi-page-direction-toggle",
+ !this.onTextInput && top.gBidiUI);
+
+ // SocialShare
+ let shareButton = SocialShare.shareButton;
+ let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
+ let pageShare = shareEnabled && !(this.isContentSelected ||
+ this.onTextInput || this.onLink || this.onImage ||
+ this.onVideo || this.onAudio || this.onCanvas);
+ this.showItem("context-sharepage", pageShare);
+ this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
+ this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
+ this.showItem("context-shareimage", shareEnabled && this.onImage);
+ this.showItem("context-sharevideo", shareEnabled && this.onVideo);
+ this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:"));
+ },
+
+ initSpellingItems: function() {
+ var canSpell = InlineSpellCheckerUI.canSpellCheck &&
+ !InlineSpellCheckerUI.initialSpellCheckPending &&
+ this.canSpellCheck;
+ let showDictionaries = canSpell && InlineSpellCheckerUI.enabled;
+ var onMisspelling = InlineSpellCheckerUI.overMisspelling;
+ var showUndo = canSpell && InlineSpellCheckerUI.canUndo();
+ this.showItem("spell-check-enabled", canSpell);
+ this.showItem("spell-separator", canSpell);
+ document.getElementById("spell-check-enabled")
+ .setAttribute("checked", canSpell && InlineSpellCheckerUI.enabled);
+
+ this.showItem("spell-add-to-dictionary", onMisspelling);
+ this.showItem("spell-undo-add-to-dictionary", showUndo);
+
+ // suggestion list
+ this.showItem("spell-suggestions-separator", onMisspelling || showUndo);
+ if (onMisspelling) {
+ var suggestionsSeparator =
+ document.getElementById("spell-add-to-dictionary");
+ var numsug =
+ InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode,
+ suggestionsSeparator, 5);
+ this.showItem("spell-no-suggestions", numsug == 0);
+ }
+ else
+ this.showItem("spell-no-suggestions", false);
+
+ // dictionary list
+ this.showItem("spell-dictionaries", showDictionaries);
+ if (canSpell) {
+ var dictMenu = document.getElementById("spell-dictionaries-menu");
+ var dictSep = document.getElementById("spell-language-separator");
+ let count = InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
+ this.showItem(dictSep, count > 0);
+ this.showItem("spell-add-dictionaries-main", false);
+ }
+ else if (this.onEditableArea) {
+ // when there is no spellchecker but we might be able to spellcheck
+ // add the add to dictionaries item. This will ensure that people
+ // with no dictionaries will be able to download them
+ this.showItem("spell-language-separator", showDictionaries);
+ this.showItem("spell-add-dictionaries-main", showDictionaries);
+ }
+ else
+ this.showItem("spell-add-dictionaries-main", false);
+ },
+
+ initClipboardItems: function() {
+ // Copy depends on whether there is selected text.
+ // Enabling this context menu item is now done through the global
+ // command updating system
+ // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
+ goUpdateGlobalEditMenuItems();
+
+ this.showItem("context-undo", this.onTextInput);
+ this.showItem("context-sep-undo", this.onTextInput);
+ this.showItem("context-cut", this.onTextInput);
+ this.showItem("context-copy",
+ this.isContentSelected || this.onTextInput);
+ this.showItem("context-paste", this.onTextInput);
+ this.showItem("context-delete", this.onTextInput);
+ this.showItem("context-sep-paste", this.onTextInput);
+ this.showItem("context-selectall", !(this.onLink || this.onImage ||
+ this.onVideo || this.onAudio ||
+ this.inSyntheticDoc) ||
+ this.isDesignMode);
+ this.showItem("context-sep-selectall", this.isContentSelected );
+
+ // XXX dr
+ // ------
+ // nsDocumentViewer.cpp has code to determine whether we're
+ // on a link or an image. we really ought to be using that...
+
+ // Copy email link depends on whether we're on an email link.
+ this.showItem("context-copyemail", this.onMailtoLink);
+
+ // Copy link location depends on whether we're on a non-mailto link.
+ this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
+ this.showItem("context-sep-copylink", this.onLink &&
+ (this.onImage || this.onVideo || this.onAudio));
+
+#ifdef CONTEXT_COPY_IMAGE_CONTENTS
+ // Copy image contents depends on whether we're on an image.
+ this.showItem("context-copyimage-contents", this.onImage);
+#endif
+ // Copy image location depends on whether we're on an image.
+ this.showItem("context-copyimage", this.onImage);
+ this.showItem("context-copyvideourl", this.onVideo);
+ this.showItem("context-copyaudiourl", this.onAudio);
+ this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL);
+ this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL);
+ this.showItem("context-sep-copyimage", this.onImage ||
+ this.onVideo || this.onAudio);
+ },
+
+ initMediaPlayerItems: function() {
+ var onMedia = (this.onVideo || this.onAudio);
+ // Several mutually exclusive items... play/pause, mute/unmute, show/hide
+ this.showItem("context-media-play", onMedia && (this.target.paused || this.target.ended));
+ this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
+ this.showItem("context-media-mute", onMedia && !this.target.muted);
+ this.showItem("context-media-unmute", onMedia && this.target.muted);
+ this.showItem("context-media-playbackrate", onMedia && this.target.duration != Number.POSITIVE_INFINITY);
+ this.showItem("context-media-loop", onMedia);
+ this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
+ this.showItem("context-media-hidecontrols", this.target.controls && (this.onVideo || (this.onAudio && !this.inSyntheticDoc)));
+ this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.fullscreenElement == null);
+ this.showItem("context-media-eme-learnmore", this.onDRMMedia);
+ this.showItem("context-media-eme-separator", this.onDRMMedia);
+
+ // Disable them when there isn't a valid media source loaded.
+ if (onMedia) {
+ this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
+ this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
+ this.setItemAttr("context-media-playbackrate-125x", "checked", this.target.playbackRate == 1.25);
+ this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
+ this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
+ this.setItemAttr("context-media-loop", "checked", this.target.loop);
+ var hasError = this.target.error != null ||
+ this.target.networkState == this.target.NETWORK_NO_SOURCE;
+ this.setItemAttr("context-media-play", "disabled", hasError);
+ this.setItemAttr("context-media-pause", "disabled", hasError);
+ this.setItemAttr("context-media-mute", "disabled", hasError);
+ this.setItemAttr("context-media-unmute", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-125x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
+ this.setItemAttr("context-media-showcontrols", "disabled", hasError);
+ this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
+ if (this.onVideo) {
+ let canSaveSnapshot = !this.onDRMMedia && this.target.readyState >= this.target.HAVE_CURRENT_DATA;
+ this.setItemAttr("context-video-saveimage", "disabled", !canSaveSnapshot);
+ this.setItemAttr("context-video-fullscreen", "disabled", hasError);
+ }
+ }
+ this.showItem("context-media-sep-commands", onMedia);
+ },
+
+ initClickToPlayItems: function() {
+ this.showItem("context-ctp-play", this.onCTPPlugin);
+ this.showItem("context-ctp-hide", this.onCTPPlugin);
+ this.showItem("context-sep-ctp", this.onCTPPlugin);
+ },
+
+ initPasswordManagerItems: function() {
+ let loginFillInfo = gContextMenuContentData && gContextMenuContentData.loginFillInfo;
+
+ // If we could not find a password field we
+ // don't want to show the form fill option.
+ let showFill = loginFillInfo && loginFillInfo.passwordField.found;
+
+ // Disable the fill option if the user has set a master password
+ // or if the password field or target field are disabled.
+ let disableFill = !loginFillInfo ||
+ !Services.logins ||
+ !Services.logins.isLoggedIn ||
+ loginFillInfo.passwordField.disabled ||
+ (!this.onPassword && loginFillInfo.usernameField.disabled);
+
+ this.showItem("fill-login-separator", showFill);
+ this.showItem("fill-login", showFill);
+ this.setItemAttr("fill-login", "disabled", disableFill);
+
+ // Set the correct label for the fill menu
+ let fillMenu = document.getElementById("fill-login");
+ if (this.onPassword) {
+ fillMenu.setAttribute("label", fillMenu.getAttribute("label-password"));
+ fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-password"));
+ } else {
+ fillMenu.setAttribute("label", fillMenu.getAttribute("label-login"));
+ fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-login"));
+ }
+
+ if (!showFill || disableFill) {
+ return;
+ }
+ let documentURI = gContextMenuContentData.documentURIObject;
+ let fragment = LoginManagerContextMenu.addLoginsToMenu(this.target, this.browser, documentURI);
+
+ this.showItem("fill-login-no-logins", !fragment);
+
+ if (!fragment) {
+ return;
+ }
+ let popup = document.getElementById("fill-login-popup");
+ let insertBeforeElement = document.getElementById("fill-login-no-logins");
+ popup.insertBefore(fragment, insertBeforeElement);
+ },
+
+ initSyncItems: function() {
+ gFxAccounts.initPageContextMenu(this);
+ },
+
+ openPasswordManager: function() {
+ LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
+ },
+
+ inspectNode: function() {
+ let {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ let gBrowser = this.browser.ownerGlobal.gBrowser;
+ let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+
+ return gDevTools.showToolbox(target, "inspector").then(toolbox => {
+ let inspector = toolbox.getCurrentPanel();
+
+ // new-node-front tells us when the node has been selected, whether the
+ // browser is remote or not.
+ let onNewNode = inspector.selection.once("new-node-front");
+
+ this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target});
+ inspector.walker.findInspectingNode().then(nodeFront => {
+ inspector.selection.setNodeFront(nodeFront, "browser-context-menu");
+ });
+
+ return onNewNode.then(() => {
+ // Now that the node has been selected, wait until the inspector is
+ // fully updated.
+ return inspector.once("inspector-updated");
+ });
+ });
+ },
+
+ // Set various context menu attributes based on the state of the world.
+ setTarget: function (aNode, aRangeParent, aRangeOffset) {
+ // gContextMenuContentData.isRemote tells us if the event came from a remote
+ // process. gContextMenuContentData can be null if something (like tests)
+ // opens the context menu directly.
+ let editFlags;
+ this.isRemote = gContextMenuContentData && gContextMenuContentData.isRemote;
+ if (this.isRemote) {
+ aNode = gContextMenuContentData.event.target;
+ aRangeParent = gContextMenuContentData.event.rangeParent;
+ aRangeOffset = gContextMenuContentData.event.rangeOffset;
+ editFlags = gContextMenuContentData.editFlags;
+ }
+
+ const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ if (aNode.nodeType == Node.DOCUMENT_NODE ||
+ // Not display on XUL element but relax for <label class="text-link">
+ (aNode.namespaceURI == xulNS && !isXULTextLinkLabel(aNode))) {
+ this.shouldDisplay = false;
+ return;
+ }
+
+ // Initialize contextual info.
+ this.onImage = false;
+ this.onLoadedImage = false;
+ this.onCompletedImage = false;
+ this.imageDescURL = "";
+ this.onCanvas = false;
+ this.onVideo = false;
+ this.onAudio = false;
+ this.onDRMMedia = false;
+ this.onTextInput = false;
+ this.onNumeric = false;
+ this.onKeywordField = false;
+ this.mediaURL = "";
+ this.onLink = false;
+ this.onMailtoLink = false;
+ this.onSaveableLink = false;
+ this.link = null;
+ this.linkURL = "";
+ this.linkURI = null;
+ this.linkTextStr = "";
+ this.linkProtocol = "";
+ this.linkDownload = "";
+ this.linkHasNoReferrer = false;
+ this.onMathML = false;
+ this.inFrame = false;
+ this.inSrcdocFrame = false;
+ this.inSyntheticDoc = false;
+ this.hasBGImage = false;
+ this.bgImageURL = "";
+ this.onEditableArea = false;
+ this.isDesignMode = false;
+ this.onCTPPlugin = false;
+ this.canSpellCheck = false;
+ this.onPassword = false;
+
+ if (this.isRemote) {
+ this.selectionInfo = gContextMenuContentData.selectionInfo;
+ } else {
+ this.selectionInfo = BrowserUtils.getSelectionDetails(window);
+ }
+
+ this.textSelected = this.selectionInfo.text;
+ this.isTextSelected = this.textSelected.length != 0;
+
+ // Remember the node that was clicked.
+ this.target = aNode;
+
+ let ownerDoc = this.target.ownerDocument;
+ this.ownerDoc = ownerDoc;
+
+ // If this is a remote context menu event, use the information from
+ // gContextMenuContentData instead.
+ if (this.isRemote) {
+ this.browser = gContextMenuContentData.browser;
+ this.principal = gContextMenuContentData.principal;
+ this.frameOuterWindowID = gContextMenuContentData.frameOuterWindowID;
+ } else {
+ editFlags = SpellCheckHelper.isEditable(this.target, window);
+ this.browser = ownerDoc.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ this.principal = ownerDoc.nodePrincipal;
+ this.frameOuterWindowID = ownerDoc.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ }
+ this.onSocial = !!this.browser.getAttribute("origin");
+
+ // Check if we are in a synthetic document (stand alone image, video, etc.).
+ this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
+ // First, do checks for nodes that never have children.
+ if (this.target.nodeType == Node.ELEMENT_NODE) {
+ // See if the user clicked on an image. This check mirrors
+ // nsDocumentViewer::GetInImage. Make sure to update both if this is
+ // changed.
+ if (this.target instanceof Ci.nsIImageLoadingContent &&
+ this.target.currentURI) {
+ this.onImage = true;
+
+ var request =
+ this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
+ this.onLoadedImage = true;
+ if (request &&
+ (request.imageStatus & request.STATUS_LOAD_COMPLETE) &&
+ !(request.imageStatus & request.STATUS_ERROR)) {
+ this.onCompletedImage = true;
+ }
+
+ this.mediaURL = this.target.currentURI.spec;
+
+ var descURL = this.target.getAttribute("longdesc");
+ if (descURL) {
+ this.imageDescURL = makeURLAbsolute(ownerDoc.body.baseURI, descURL);
+ }
+ }
+ else if (this.target instanceof HTMLCanvasElement) {
+ this.onCanvas = true;
+ }
+ else if (this.target instanceof HTMLVideoElement) {
+ let mediaURL = this.target.currentSrc || this.target.src;
+ if (this.isMediaURLReusable(mediaURL)) {
+ this.mediaURL = mediaURL;
+ }
+ if (this._isProprietaryDRM()) {
+ this.onDRMMedia = true;
+ }
+ // Firefox always creates a HTMLVideoElement when loading an ogg file
+ // directly. If the media is actually audio, be smarter and provide a
+ // context menu with audio operations.
+ if (this.target.readyState >= this.target.HAVE_METADATA &&
+ (this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
+ this.onAudio = true;
+ } else {
+ this.onVideo = true;
+ }
+ }
+ else if (this.target instanceof HTMLAudioElement) {
+ this.onAudio = true;
+ let mediaURL = this.target.currentSrc || this.target.src;
+ if (this.isMediaURLReusable(mediaURL)) {
+ this.mediaURL = mediaURL;
+ }
+ if (this._isProprietaryDRM()) {
+ this.onDRMMedia = true;
+ }
+ }
+ else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
+ this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
+ this.onNumeric = (editFlags & SpellCheckHelper.NUMERIC) !== 0;
+ this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
+ this.onPassword = (editFlags & SpellCheckHelper.PASSWORD) !== 0;
+ if (this.onEditableArea) {
+ if (this.isRemote) {
+ InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
+ }
+ else {
+ InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ }
+ }
+ this.onKeywordField = (editFlags & SpellCheckHelper.KEYWORD);
+ }
+ else if (this.target instanceof HTMLHtmlElement) {
+ var bodyElt = ownerDoc.body;
+ if (bodyElt) {
+ let computedURL;
+ try {
+ computedURL = this.getComputedURL(bodyElt, "background-image");
+ this._hasMultipleBGImages = false;
+ } catch (e) {
+ this._hasMultipleBGImages = true;
+ }
+ if (computedURL) {
+ this.hasBGImage = true;
+ this.bgImageURL = makeURLAbsolute(bodyElt.baseURI,
+ computedURL);
+ }
+ }
+ }
+ else if ((this.target instanceof HTMLEmbedElement ||
+ this.target instanceof HTMLObjectElement ||
+ this.target instanceof HTMLAppletElement) &&
+ this.target.displayedType == HTMLObjectElement.TYPE_NULL &&
+ this.target.pluginFallbackType == HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
+ this.onCTPPlugin = true;
+ }
+
+ this.canSpellCheck = this._isSpellCheckEnabled(this.target);
+ }
+ else if (this.target.nodeType == Node.TEXT_NODE) {
+ // For text nodes, look at the parent node to determine the spellcheck attribute.
+ this.canSpellCheck = this.target.parentNode &&
+ this._isSpellCheckEnabled(this.target);
+ }
+
+ // Second, bubble out, looking for items of interest that can have childen.
+ // Always pick the innermost link, background image, etc.
+ const XMLNS = "http://www.w3.org/XML/1998/namespace";
+ var elem = this.target;
+ while (elem) {
+ if (elem.nodeType == Node.ELEMENT_NODE) {
+ // Link?
+ if (!this.onLink &&
+ // Be consistent with what hrefAndLinkNodeForClickEvent
+ // does in browser.js
+ (isXULTextLinkLabel(elem) ||
+ (elem instanceof HTMLAnchorElement && elem.href) ||
+ (elem instanceof HTMLAreaElement && elem.href) ||
+ elem instanceof HTMLLinkElement ||
+ elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
+
+ // Target is a link or a descendant of a link.
+ this.onLink = true;
+
+ // Remember corresponding element.
+ this.link = elem;
+ this.linkURL = this.getLinkURL();
+ this.linkURI = this.getLinkURI();
+ this.linkTextStr = this.getLinkText();
+ this.linkProtocol = this.getLinkProtocol();
+ this.onMailtoLink = (this.linkProtocol == "mailto");
+ this.onSaveableLink = this.isLinkSaveable( this.link );
+ this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
+ try {
+ if (elem.download) {
+ // Ignore download attribute on cross-origin links
+ this.principal.checkMayLoad(this.linkURI, false, true);
+ this.linkDownload = elem.download;
+ }
+ }
+ catch (ex) {}
+ }
+
+ // Background image? Don't bother if we've already found a
+ // background image further down the hierarchy. Otherwise,
+ // we look for the computed background-image style.
+ if (!this.hasBGImage &&
+ !this._hasMultipleBGImages) {
+ let bgImgUrl;
+ try {
+ bgImgUrl = this.getComputedURL(elem, "background-image");
+ this._hasMultipleBGImages = false;
+ } catch (e) {
+ this._hasMultipleBGImages = true;
+ }
+ if (bgImgUrl) {
+ this.hasBGImage = true;
+ this.bgImageURL = makeURLAbsolute(elem.baseURI,
+ bgImgUrl);
+ }
+ }
+ }
+
+ elem = elem.parentNode;
+ }
+
+ // See if the user clicked on MathML
+ const NS_MathML = "http://www.w3.org/1998/Math/MathML";
+ if ((this.target.nodeType == Node.TEXT_NODE &&
+ this.target.parentNode.namespaceURI == NS_MathML)
+ || (this.target.namespaceURI == NS_MathML))
+ this.onMathML = true;
+
+ // See if the user clicked in a frame.
+ var docDefaultView = ownerDoc.defaultView;
+ if (docDefaultView != docDefaultView.top) {
+ this.inFrame = true;
+
+ if (ownerDoc.isSrcdocDocument) {
+ this.inSrcdocFrame = true;
+ }
+ }
+
+ // if the document is editable, show context menu like in text inputs
+ if (!this.onEditableArea) {
+ if (editFlags & SpellCheckHelper.CONTENTEDITABLE) {
+ // If this.onEditableArea is false but editFlags is CONTENTEDITABLE, then
+ // the document itself must be editable.
+ this.onTextInput = true;
+ this.onKeywordField = false;
+ this.onImage = false;
+ this.onLoadedImage = false;
+ this.onCompletedImage = false;
+ this.onMathML = false;
+ this.inFrame = false;
+ this.inSrcdocFrame = false;
+ this.hasBGImage = false;
+ this.isDesignMode = true;
+ this.onEditableArea = true;
+ if (this.isRemote) {
+ InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
+ }
+ else {
+ var targetWin = ownerDoc.defaultView;
+ var editingSession = targetWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ }
+ var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
+ this.showItem("spell-check-enabled", canSpell);
+ this.showItem("spell-separator", canSpell);
+ }
+ }
+
+ function isXULTextLinkLabel(node) {
+ return node.namespaceURI == xulNS &&
+ node.tagName == "label" &&
+ node.classList.contains('text-link') &&
+ node.href;
+ }
+ },
+
+ // Returns the computed style attribute for the given element.
+ getComputedStyle: function(aElem, aProp) {
+ return aElem.ownerDocument
+ .defaultView
+ .getComputedStyle(aElem, "").getPropertyValue(aProp);
+ },
+
+ // Returns a "url"-type computed style attribute value, with the url() stripped.
+ getComputedURL: function(aElem, aProp) {
+ var url = aElem.ownerDocument
+ .defaultView.getComputedStyle(aElem, "")
+ .getPropertyCSSValue(aProp);
+ if (url instanceof CSSValueList) {
+ if (url.length != 1)
+ throw "found multiple URLs";
+ url = url[0];
+ }
+ return url.primitiveType == CSSPrimitiveValue.CSS_URI ?
+ url.getStringValue() : null;
+ },
+
+ // Returns true if clicked-on link targets a resource that can be saved.
+ isLinkSaveable: function(aLink) {
+ // We don't do the Right Thing for news/snews yet, so turn them off
+ // until we do.
+ return this.linkProtocol && !(
+ this.linkProtocol == "mailto" ||
+ this.linkProtocol == "javascript" ||
+ this.linkProtocol == "news" ||
+ this.linkProtocol == "snews" );
+ },
+
+ _isSpellCheckEnabled: function(aNode) {
+ // We can always force-enable spellchecking on textboxes
+ if (this.isTargetATextBox(aNode)) {
+ return true;
+ }
+ // We can never spell check something which is not content editable
+ var editable = aNode.isContentEditable;
+ if (!editable && aNode.ownerDocument) {
+ editable = aNode.ownerDocument.designMode == "on";
+ }
+ if (!editable) {
+ return false;
+ }
+ // Otherwise make sure that nothing in the parent chain disables spellchecking
+ return aNode.spellcheck;
+ },
+
+ _isProprietaryDRM: function() {
+ return this.target.isEncrypted && this.target.mediaKeys &&
+ this.target.mediaKeys.keySystem != "org.w3.clearkey";
+ },
+
+ _openLinkInParameters : function (extra) {
+ let params = { charset: gContextMenuContentData.charSet,
+ originPrincipal: this.principal,
+ referrerURI: gContextMenuContentData.documentURIObject,
+ referrerPolicy: gContextMenuContentData.referrerPolicy,
+ noReferrer: this.linkHasNoReferrer };
+ for (let p in extra) {
+ params[p] = extra[p];
+ }
+
+ // If we want to change userContextId, we must be sure that we don't
+ // propagate the referrer.
+ if ("userContextId" in params &&
+ params.userContextId != gContextMenuContentData.userContextId) {
+ params.noReferrer = true;
+ }
+
+ return params;
+ },
+
+ // Open linked-to URL in a new window.
+ openLink : function () {
+ urlSecurityCheck(this.linkURL, this.principal);
+ openLinkIn(this.linkURL, "window", this._openLinkInParameters());
+ },
+
+ // Open linked-to URL in a new private window.
+ openLinkInPrivateWindow : function () {
+ urlSecurityCheck(this.linkURL, this.principal);
+ openLinkIn(this.linkURL, "window",
+ this._openLinkInParameters({ private: true }));
+ },
+
+ // Open linked-to URL in a new tab.
+ openLinkInTab: function(event) {
+ urlSecurityCheck(this.linkURL, this.principal);
+ let referrerURI = gContextMenuContentData.documentURIObject;
+
+ // if its parent allows mixed content and the referring URI passes
+ // a same origin check with the target URI, we can preserve the users
+ // decision of disabling MCB on a page for it's child tabs.
+ let persistAllowMixedContentInChildTab = false;
+
+ if (gContextMenuContentData.parentAllowsMixedContent) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ let targetURI = this.linkURI;
+ sm.checkSameOriginURI(referrerURI, targetURI, false);
+ persistAllowMixedContentInChildTab = true;
+ }
+ catch (e) { }
+ }
+
+ let params = {
+ allowMixedContent: persistAllowMixedContentInChildTab,
+ userContextId: parseInt(event.target.getAttribute('data-usercontextid')),
+ };
+
+ openLinkIn(this.linkURL, "tab", this._openLinkInParameters(params));
+ },
+
+ // open URL in current tab
+ openLinkInCurrent: function() {
+ urlSecurityCheck(this.linkURL, this.principal);
+ openLinkIn(this.linkURL, "current", this._openLinkInParameters());
+ },
+
+ // Open frame in a new tab.
+ openFrameInTab: function() {
+ let referrer = gContextMenuContentData.referrer;
+ openLinkIn(gContextMenuContentData.docLocation, "tab",
+ { charset: gContextMenuContentData.charSet,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ // Reload clicked-in frame.
+ reloadFrame: function() {
+ this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadFrame",
+ null, { target: this.target });
+ },
+
+ // Open clicked-in frame in its own window.
+ openFrame: function() {
+ let referrer = gContextMenuContentData.referrer;
+ openLinkIn(gContextMenuContentData.docLocation, "window",
+ { charset: gContextMenuContentData.charSet,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ // Open clicked-in frame in the same window.
+ showOnlyThisFrame: function() {
+ urlSecurityCheck(gContextMenuContentData.docLocation,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ let referrer = gContextMenuContentData.referrer;
+ openUILinkIn(gContextMenuContentData.docLocation, "current",
+ { disallowInheritPrincipal: true,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ reload: function(event) {
+ BrowserReloadOrDuplicate(event);
+ },
+
+ // View Partial Source
+ viewPartialSource: function(aContext) {
+ let inWindow = !Services.prefs.getBoolPref("view_source.tab");
+ let openSelectionFn = inWindow ? null : function() {
+ let tabBrowser = gBrowser;
+ // In the case of popups, we need to find a non-popup browser window.
+ if (!tabBrowser || !window.toolbar.visible) {
+ // This returns only non-popup browser windows by default.
+ let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+ tabBrowser = browserWindow.gBrowser;
+ }
+ let tab = tabBrowser.loadOneTab("about:blank", {
+ relatedToCurrent: true,
+ inBackground: false
+ });
+ return tabBrowser.getBrowserForTab(tab);
+ }
+
+ let target = aContext == "mathml" ? this.target : null;
+ top.gViewSourceUtils.viewPartialSourceInBrowser(gBrowser.selectedBrowser, target, openSelectionFn);
+ },
+
+ // Open new "view source" window with the frame's URL.
+ viewFrameSource: function() {
+ BrowserViewSourceOfDocument({
+ browser: this.browser,
+ URL: gContextMenuContentData.docLocation,
+ outerWindowID: this.frameOuterWindowID,
+ });
+ },
+
+ viewInfo: function() {
+ BrowserPageInfo(gContextMenuContentData.docLocation, null, null, null, this.browser);
+ },
+
+ viewImageInfo: function() {
+ BrowserPageInfo(gContextMenuContentData.docLocation, "mediaTab",
+ this.target, null, this.browser);
+ },
+
+ viewImageDesc: function(e) {
+ urlSecurityCheck(this.imageDescURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ openUILink(this.imageDescURL, e, { disallowInheritPrincipal: true,
+ referrerURI: gContextMenuContentData.documentURIObject });
+ },
+
+ viewFrameInfo: function() {
+ BrowserPageInfo(gContextMenuContentData.docLocation, null, null,
+ this.frameOuterWindowID, this.browser);
+ },
+
+ reloadImage: function() {
+ urlSecurityCheck(this.mediaURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+
+ this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadImage",
+ null, { target: this.target });
+ },
+
+ _canvasToBlobURL: function(target) {
+ let mm = this.browser.messageManager;
+ return new Promise(function(resolve) {
+ mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL", {}, { target });
+
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
+ resolve(message.data.blobURL);
+ };
+ mm.addMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
+ });
+ },
+
+ // Change current window to the URL of the image, video, or audio.
+ viewMedia: function(e) {
+ let referrerURI = gContextMenuContentData.documentURIObject;
+ if (this.onCanvas) {
+ this._canvasToBlobURL(this.target).then(function(blobURL) {
+ openUILink(blobURL, e, { disallowInheritPrincipal: true,
+ referrerURI: referrerURI });
+ }, Cu.reportError);
+ }
+ else {
+ urlSecurityCheck(this.mediaURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ openUILink(this.mediaURL, e, { disallowInheritPrincipal: true,
+ referrerURI: referrerURI });
+ }
+ },
+
+ saveVideoFrameAsImage: function () {
+ let mm = this.browser.messageManager;
+ let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
+
+ let name = "";
+ if (this.mediaURL) {
+ try {
+ let uri = makeURI(this.mediaURL);
+ let url = uri.QueryInterface(Ci.nsIURL);
+ if (url.fileBaseName)
+ name = decodeURI(url.fileBaseName) + ".jpg";
+ } catch (e) { }
+ }
+ if (!name)
+ name = "snapshot.jpg";
+
+ mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage", {}, {
+ target: this.target,
+ });
+
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
+ let dataURL = message.data.dataURL;
+ saveImageURL(dataURL, name, "SaveImageTitle", true, false,
+ document.documentURIObject, null, null, null,
+ isPrivate);
+ };
+ mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
+ },
+
+ leaveDOMFullScreen: function() {
+ document.exitFullscreen();
+ },
+
+ // Change current window to the URL of the background image.
+ viewBGImage: function(e) {
+ urlSecurityCheck(this.bgImageURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ openUILink(this.bgImageURL, e, { disallowInheritPrincipal: true,
+ referrerURI: gContextMenuContentData.documentURIObject });
+ },
+
+ setDesktopBackground: function() {
+ let mm = this.browser.messageManager;
+
+ mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground", null,
+ { target: this.target });
+
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:SetAsDesktopBackground:Result",
+ onMessage);
+
+ if (message.data.disable)
+ return;
+
+ let image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img');
+ image.src = message.data.dataUrl;
+
+ // Confirm since it's annoying if you hit this accidentally.
+ const kDesktopBackgroundURL =
+ "chrome://browser/content/setDesktopBackground.xul";
+#ifdef XP_MACOSX
+ // On Mac, the Set Desktop Background window is not modal.
+ // Don't open more than one Set Desktop Background window.
+ const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ let dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
+ if (dbWin) {
+ dbWin.gSetBackground.init(image);
+ dbWin.focus();
+ }
+ else {
+ openDialog(kDesktopBackgroundURL, "",
+ "centerscreen,chrome,dialog=no,dependent,resizable=no",
+ image);
+ }
+#else
+ // On non-Mac platforms, the Set Wallpaper dialog is modal.
+ openDialog(kDesktopBackgroundURL, "",
+ "centerscreen,chrome,dialog,modal,dependent",
+ image);
+#endif
+ };
+
+ mm.addMessageListener("ContextMenu:SetAsDesktopBackground:Result", onMessage);
+ },
+
+ // Save URL of clicked-on frame.
+ saveFrame: function () {
+ saveBrowser(this.browser, false, this.frameOuterWindowID);
+ },
+
+ // Helper function to wait for appropriate MIME-type headers and
+ // then prompt the user with a file picker
+ saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc, docURI,
+ windowID, linkDownload) {
+ // canonical def in nsURILoader.h
+ const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
+
+ // an object to proxy the data through to
+ // nsIExternalHelperAppService.doContent, which will wait for the
+ // appropriate MIME-type headers and then prompt the user with a
+ // file picker
+ function saveAsListener() {}
+ saveAsListener.prototype = {
+ extListener: null,
+
+ onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
+
+ // if the timer fired, the error status will have been caused by that,
+ // and we'll be restarting in onStopRequest, so no reason to notify
+ // the user
+ if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
+ return;
+
+ timer.cancel();
+
+ // some other error occured; notify the user...
+ if (!Components.isSuccessCode(aRequest.status)) {
+ try {
+ const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ const bundle = sbs.createBundle(
+ "chrome://mozapps/locale/downloads/downloads.properties");
+
+ const title = bundle.GetStringFromName("downloadErrorAlertTitle");
+ const msg = bundle.GetStringFromName("downloadErrorGeneric");
+
+ const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ let window = wm.getOuterWindowWithId(windowID);
+ promptSvc.alert(window, title, msg);
+ } catch (ex) {}
+ return;
+ }
+
+ let extHelperAppSvc =
+ Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+ getService(Ci.nsIExternalHelperAppService);
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ this.extListener =
+ extHelperAppSvc.doContent(channel.contentType, aRequest,
+ null, true, window);
+ this.extListener.onStartRequest(aRequest, aContext);
+ },
+
+ onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
+ aStatusCode) {
+ if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
+ // do it the old fashioned way, which will pick the best filename
+ // it can without waiting.
+ saveURL(linkURL, linkText, dialogTitle, bypassCache, false, docURI,
+ doc);
+ }
+ if (this.extListener)
+ this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+ },
+
+ onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
+ aInputStream,
+ aOffset, aCount) {
+ this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ }
+ }
+
+ function callbacks() {}
+ callbacks.prototype = {
+ getInterface: function sLA_callbacks_getInterface(aIID) {
+ if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
+ // If the channel demands authentication prompt, we must cancel it
+ // because the save-as-timer would expire and cancel the channel
+ // before we get credentials from user. Both authentication dialog
+ // and save as dialog would appear on the screen as we fall back to
+ // the old fashioned way after the timeout.
+ timer.cancel();
+ channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ }
+
+ // if it we don't have the headers after a short time, the user
+ // won't have received any feedback from their click. that's bad. so
+ // we give up waiting for the filename.
+ function timerCallback() {}
+ timerCallback.prototype = {
+ notify: function sLA_timer_notify(aTimer) {
+ channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
+ return;
+ }
+ }
+
+ // setting up a new channel for 'right click - save link as ...'
+ // ideally we should use:
+ // * doc - as the loadingNode, and/or
+ // * this.principal - as the loadingPrincipal
+ // for now lets use systemPrincipal to bypass mixedContentBlocker
+ // checks after redirects, see bug: 1136055
+ var channel = NetUtil.newChannel({
+ uri: makeURI(linkURL),
+ loadUsingSystemPrincipal: true
+ });
+
+ if (linkDownload)
+ channel.contentDispositionFilename = linkDownload;
+ if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
+ let docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser);
+ channel.setPrivate(docIsPrivate);
+ }
+ channel.notificationCallbacks = new callbacks();
+
+ let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ if (bypassCache)
+ flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+
+ if (channel instanceof Ci.nsICachingChannel)
+ flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
+
+ channel.loadFlags |= flags;
+
+ if (channel instanceof Ci.nsIHttpChannel) {
+ channel.referrer = docURI;
+ if (channel instanceof Ci.nsIHttpChannelInternal)
+ channel.forceAllowThirdPartyCookie = true;
+ }
+
+ // fallback to the old way if we don't see the headers quickly
+ var timeToWait =
+ gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(new timerCallback(), timeToWait,
+ timer.TYPE_ONE_SHOT);
+
+ // kick off the channel with our proxy object as the listener
+ channel.asyncOpen2(new saveAsListener());
+ },
+
+ // Save URL of clicked-on link.
+ saveLink: function() {
+ urlSecurityCheck(this.linkURL, this.principal);
+ this.saveHelper(this.linkURL, this.linkTextStr, null, true, this.ownerDoc,
+ gContextMenuContentData.documentURIObject,
+ this.frameOuterWindowID,
+ this.linkDownload);
+ },
+
+ // Backwards-compatibility wrapper
+ saveImage : function() {
+ if (this.onCanvas || this.onImage)
+ this.saveMedia();
+ },
+
+ // Save URL of the clicked upon image, video, or audio.
+ saveMedia: function() {
+ let doc = this.ownerDoc;
+ let referrerURI = gContextMenuContentData.documentURIObject;
+ let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
+ if (this.onCanvas) {
+ // Bypass cache, since it's a data: URL.
+ this._canvasToBlobURL(this.target).then(function(blobURL) {
+ saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
+ true, false, referrerURI, null, null, null,
+ isPrivate);
+ }, Cu.reportError);
+ }
+ else if (this.onImage) {
+ urlSecurityCheck(this.mediaURL, this.principal);
+ saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
+ false, referrerURI, null, gContextMenuContentData.contentType,
+ gContextMenuContentData.contentDisposition, isPrivate);
+ }
+ else if (this.onVideo || this.onAudio) {
+ urlSecurityCheck(this.mediaURL, this.principal);
+ var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
+ this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, referrerURI,
+ this.frameOuterWindowID, "");
+ }
+ },
+
+ // Backwards-compatibility wrapper
+ sendImage : function() {
+ if (this.onCanvas || this.onImage)
+ this.sendMedia();
+ },
+
+ sendMedia: function() {
+ MailIntegration.sendMessage(this.mediaURL, "");
+ },
+
+ castVideo: function() {
+ CastingApps.openExternal(this.target, window);
+ },
+
+ populateCastVideoMenu: function(popup) {
+ let videoEl = this.target;
+ popup.innerHTML = null;
+ let doc = popup.ownerDocument;
+ let services = CastingApps.getServicesForVideo(videoEl);
+ services.forEach(service => {
+ let item = doc.createElement("menuitem");
+ item.setAttribute("label", service.friendlyName);
+ item.addEventListener("command", event => {
+ CastingApps.sendVideoToService(videoEl, service);
+ });
+ popup.appendChild(item);
+ });
+ },
+
+ playPlugin: function() {
+ gPluginHandler.contextMenuCommand(this.browser, this.target, "play");
+ },
+
+ hidePlugin: function() {
+ gPluginHandler.contextMenuCommand(this.browser, this.target, "hide");
+ },
+
+ // Generate email address and put it on clipboard.
+ copyEmail: function() {
+ // Copy the comma-separated list of email addresses only.
+ // There are other ways of embedding email addresses in a mailto:
+ // link, but such complex parsing is beyond us.
+ var url = this.linkURL;
+ var qmark = url.indexOf("?");
+ var addresses;
+
+ // 7 == length of "mailto:"
+ addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
+
+ // Let's try to unescape it using a character set
+ // in case the address is not ASCII.
+ try {
+ const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
+ getService(Ci.nsITextToSubURI);
+ addresses = textToSubURI.unEscapeURIForUI(gContextMenuContentData.charSet,
+ addresses);
+ }
+ catch(ex) {
+ // Do nothing.
+ }
+
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(addresses);
+ },
+
+ copyLink: function() {
+ // If we're in a view source tab, remove the view-source: prefix
+ let linkURL = this.linkURL.replace(/^view-source:/, "");
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(linkURL);
+ },
+
+ ///////////////
+ // Utilities //
+ ///////////////
+
+ // Show/hide one item (specified via name or the item element itself).
+ showItem: function(aItemOrId, aShow) {
+ var item = aItemOrId.constructor == String ?
+ document.getElementById(aItemOrId) : aItemOrId;
+ if (item)
+ item.hidden = !aShow;
+ },
+
+ // Set given attribute of specified context-menu item. If the
+ // value is null, then it removes the attribute (which works
+ // nicely for the disabled attribute).
+ setItemAttr: function(aID, aAttr, aVal ) {
+ var elem = document.getElementById(aID);
+ if (elem) {
+ if (aVal == null) {
+ // null indicates attr should be removed.
+ elem.removeAttribute(aAttr);
+ }
+ else {
+ // Set attr=val.
+ elem.setAttribute(aAttr, aVal);
+ }
+ }
+ },
+
+ // Set context menu attribute according to like attribute of another node
+ // (such as a broadcaster).
+ setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) {
+ var elem = document.getElementById(aOther_id);
+ if (elem && elem.getAttribute(aAttr) == "true")
+ this.setItemAttr(aItem_id, aAttr, "true");
+ else
+ this.setItemAttr(aItem_id, aAttr, null);
+ },
+
+ // Temporary workaround for DOM api not yet implemented by XUL nodes.
+ cloneNode: function(aItem) {
+ // Create another element like the one we're cloning.
+ var node = document.createElement(aItem.tagName);
+
+ // Copy attributes from argument item to the new one.
+ var attrs = aItem.attributes;
+ for (var i = 0; i < attrs.length; i++) {
+ var attr = attrs.item(i);
+ node.setAttribute(attr.nodeName, attr.nodeValue);
+ }
+
+ // Voila!
+ return node;
+ },
+
+ // Generate fully qualified URL for clicked-on link.
+ getLinkURL: function() {
+ var href = this.link.href;
+ if (href)
+ return href;
+
+ href = this.link.getAttribute("href") ||
+ this.link.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+
+ if (!href || !href.match(/\S/)) {
+ // Without this we try to save as the current doc,
+ // for example, HTML case also throws if empty
+ throw "Empty href";
+ }
+
+ return makeURLAbsolute(this.link.baseURI, href);
+ },
+
+ getLinkURI: function() {
+ try {
+ return makeURI(this.linkURL);
+ }
+ catch (ex) {
+ // e.g. empty URL string
+ }
+
+ return null;
+ },
+
+ getLinkProtocol: function() {
+ if (this.linkURI)
+ return this.linkURI.scheme; // can be |undefined|
+
+ return null;
+ },
+
+ // Get text of link.
+ getLinkText: function() {
+ var text = gatherTextUnder(this.link);
+ if (!text || !text.match(/\S/)) {
+ text = this.link.getAttribute("title");
+ if (!text || !text.match(/\S/)) {
+ text = this.link.getAttribute("alt");
+ if (!text || !text.match(/\S/))
+ text = this.linkURL;
+ }
+ }
+
+ return text;
+ },
+
+ // Kept for addon compat
+ linkText: function() {
+ return this.linkTextStr;
+ },
+
+ isMediaURLReusable: function(aURL) {
+ if (aURL.startsWith("blob:")) {
+ return URL.isValidURL(aURL);
+ }
+ return true;
+ },
+
+ toString: function () {
+ return "contextMenu.target = " + this.target + "\n" +
+ "contextMenu.onImage = " + this.onImage + "\n" +
+ "contextMenu.onLink = " + this.onLink + "\n" +
+ "contextMenu.link = " + this.link + "\n" +
+ "contextMenu.inFrame = " + this.inFrame + "\n" +
+ "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
+ },
+
+ isTargetATextBox: function(node) {
+ if (node instanceof HTMLInputElement)
+ return node.mozIsTextField(false);
+
+ return (node instanceof HTMLTextAreaElement);
+ },
+
+ // Determines whether or not the separator with the specified ID should be
+ // shown or not by determining if there are any non-hidden items between it
+ // and the previous separator.
+ shouldShowSeparator: function (aSeparatorID) {
+ var separator = document.getElementById(aSeparatorID);
+ if (separator) {
+ var sibling = separator.previousSibling;
+ while (sibling && sibling.localName != "menuseparator") {
+ if (!sibling.hidden)
+ return true;
+ sibling = sibling.previousSibling;
+ }
+ }
+ return false;
+ },
+
+ addDictionaries: function() {
+ var uri = formatURL("browser.dictionaries.download.url", true);
+
+ var locale = "-";
+ try {
+ locale = gPrefService.getComplexValue("intl.accept_languages",
+ Ci.nsIPrefLocalizedString).data;
+ }
+ catch (e) { }
+
+ var version = "-";
+ try {
+ version = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo).version;
+ }
+ catch (e) { }
+
+ uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
+
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+
+ openUILinkIn(uri, where);
+ },
+
+ bookmarkThisPage: function CM_bookmarkThisPage() {
+ window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
+ },
+
+ bookmarkLink: function CM_bookmarkLink() {
+ window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId,
+ this.linkURL, this.linkTextStr);
+ },
+
+ addBookmarkForFrame: function CM_addBookmarkForFrame() {
+ let uri = gContextMenuContentData.documentURIObject;
+ let mm = this.browser.messageManager;
+
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
+
+ window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId,
+ uri.spec,
+ message.data.title,
+ message.data.description)
+ .catch(Components.utils.reportError);
+ };
+ mm.addMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
+
+ mm.sendAsyncMessage("ContextMenu:BookmarkFrame", null, { target: this.target });
+ },
+
+ shareLink: function CM_shareLink() {
+ SocialShare.sharePage(null, { url: this.linkURI.spec }, this.target);
+ },
+
+ shareImage: function CM_shareImage() {
+ SocialShare.sharePage(null, { url: this.imageURL, previews: [ this.mediaURL ] }, this.target);
+ },
+
+ shareVideo: function CM_shareVideo() {
+ SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL }, this.target);
+ },
+
+ shareSelect: function CM_shareSelect() {
+ SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: this.textSelected }, this.target);
+ },
+
+ savePageAs: function CM_savePageAs() {
+ saveBrowser(this.browser);
+ },
+
+ printFrame: function CM_printFrame() {
+ PrintUtils.printWindow(this.frameOuterWindowID, this.browser);
+ },
+
+ switchPageDirection: function CM_switchPageDirection() {
+ this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
+ },
+
+ mediaCommand : function CM_mediaCommand(command, data) {
+ let mm = this.browser.messageManager;
+ let win = this.browser.ownerGlobal;
+ let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ mm.sendAsyncMessage("ContextMenu:MediaCommand",
+ {command: command,
+ data: data,
+ handlingUserInput: windowUtils.isHandlingUserInput},
+ {element: this.target});
+ },
+
+ copyMediaLocation : function () {
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.mediaURL);
+ },
+
+ drmLearnMore: function(aEvent) {
+ let drmInfoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
+ let dest = whereToOpenLink(aEvent);
+ // Don't ever want this to open in the same tab as it'll unload the
+ // DRM'd video, which is going to be a bad idea in most cases.
+ if (dest == "current") {
+ dest = "tab";
+ }
+ openUILinkIn(drmInfoURL, dest);
+ },
+
+ get imageURL() {
+ if (this.onImage)
+ return this.mediaURL;
+ return "";
+ },
+
+ // Formats the 'Search <engine> for "<selection or link text>"' context menu.
+ formatSearchContextItem: function() {
+ var menuItem = document.getElementById("context-searchselect");
+ let selectedText = this.isTextSelected ? this.textSelected : this.linkTextStr;
+
+ // Store searchTerms in context menu item so we know what to search onclick
+ menuItem.searchTerms = selectedText;
+
+ // Copied to alert.js' prefillAlertInfo().
+ // If the JS character after our truncation point is a trail surrogate,
+ // include it in the truncated string to avoid splitting a surrogate pair.
+ if (selectedText.length > 15) {
+ let truncLength = 15;
+ let truncChar = selectedText[15].charCodeAt(0);
+ if (truncChar >= 0xDC00 && truncChar <= 0xDFFF)
+ truncLength++;
+ selectedText = selectedText.substr(0,truncLength) + this.ellipsis;
+ }
+
+ // format "Search <engine> for <selection>" string to show in menu
+ let engineName = Services.search.currentEngine.name;
+ var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
+ [engineName,
+ selectedText]);
+ menuItem.label = menuLabel;
+ menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
+ },
+
+ _getTelemetryClickInfo: function(aXulMenu) {
+ this._onPopupHiding = () => {
+ aXulMenu.ownerDocument.removeEventListener("command", activationHandler, true);
+ aXulMenu.removeEventListener("popuphiding", this._onPopupHiding, true);
+ delete this._onPopupHiding;
+
+ let eventKey = [
+ this._telemetryPageContext,
+ this._telemetryHadCustomItems ? "withcustom" : "withoutcustom"
+ ];
+ let target = this._telemetryClickID || "close-without-interaction";
+ BrowserUITelemetry.registerContextMenuInteraction(eventKey, target);
+ };
+ let activationHandler = (e) => {
+ // Deal with command events being routed to command elements; figure out
+ // what triggered the event (which will have the right e.target)
+ if (e.sourceEvent) {
+ e = e.sourceEvent;
+ }
+ // Target should be in the menu (this catches using shortcuts for items
+ // not in the menu while the menu is up)
+ if (!aXulMenu.contains(e.target)) {
+ return;
+ }
+
+ // Check if this is a page menu item:
+ if (e.target.hasAttribute(PageMenuParent.GENERATEDITEMID_ATTR)) {
+ this._telemetryClickID = "custom-page-item";
+ } else {
+ this._telemetryClickID = (e.target.id || "unknown").replace(/^context-/i, "");
+ }
+ };
+ aXulMenu.ownerDocument.addEventListener("command", activationHandler, true);
+ aXulMenu.addEventListener("popuphiding", this._onPopupHiding, true);
+ },
+
+ _getTelemetryPageContextInfo: function() {
+ let rv = [];
+ for (let k of ["isContentSelected", "onLink", "onImage", "onCanvas", "onVideo", "onAudio",
+ "onTextInput", "onSocial"]) {
+ if (this[k]) {
+ rv.push(k.replace(/^(?:is|on)(.)/, (match, firstLetter) => firstLetter.toLowerCase()));
+ }
+ }
+ if (!rv.length) {
+ rv.push('other');
+ }
+
+ return JSON.stringify(rv);
+ },
+
+ _checkTelemetryForMenu: function(aXulMenu) {
+ this._telemetryClickID = null;
+ this._telemetryPageContext = this._getTelemetryPageContextInfo();
+ this._telemetryHadCustomItems = this.hasPageMenu;
+ this._getTelemetryClickInfo(aXulMenu);
+ },
+
+ createContainerMenu: function(aEvent) {
+ return createUserContextMenu(aEvent, true,
+ gContextMenuContentData.userContextId);
+ },
+};
diff --git a/browser/base/content/overrides/app-license.html b/browser/base/content/overrides/app-license.html
new file mode 100644
index 000000000..e7a158c79
--- /dev/null
+++ b/browser/base/content/overrides/app-license.html
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+ <p><b>Binaries</b> of this product have been made available to you by the
+ <a href="http://www.mozilla.org/">Mozilla Project</a> under the Mozilla
+ Public License 2.0 (MPL). <a href="about:rights">Know your rights</a>.</p>
diff --git a/browser/base/content/pageinfo/feeds.js b/browser/base/content/pageinfo/feeds.js
new file mode 100644
index 000000000..c9731b4ef
--- /dev/null
+++ b/browser/base/content/pageinfo/feeds.js
@@ -0,0 +1,32 @@
+/* -*- 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 initFeedTab(feeds)
+{
+ for (let feed of feeds) {
+ let [name, type, url] = feed;
+ addRow(name, type, url);
+ }
+
+ var feedListbox = document.getElementById("feedListbox");
+ document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
+}
+
+function onSubscribeFeed()
+{
+ var listbox = document.getElementById("feedListbox");
+ openUILinkIn(listbox.selectedItem.getAttribute("feedURL"), "current",
+ { ignoreAlt: true });
+}
+
+function addRow(name, type, url)
+{
+ var item = document.createElement("richlistitem");
+ item.setAttribute("feed", "true");
+ item.setAttribute("name", name);
+ item.setAttribute("type", type);
+ item.setAttribute("feedURL", url);
+ document.getElementById("feedListbox").appendChild(item);
+}
diff --git a/browser/base/content/pageinfo/feeds.xml b/browser/base/content/pageinfo/feeds.xml
new file mode 100644
index 000000000..782c05a73
--- /dev/null
+++ b/browser/base/content/pageinfo/feeds.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+<bindings id="feedBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="feed" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:vbox flex="1">
+ <xul:hbox flex="1">
+ <xul:textbox flex="1" readonly="true" xbl:inherits="value=name"
+ class="feedTitle"/>
+ <xul:label xbl:inherits="value=type"/>
+ </xul:hbox>
+ <xul:vbox>
+ <xul:vbox align="start">
+ <xul:hbox>
+ <xul:label xbl:inherits="value=feedURL,tooltiptext=feedURL" class="text-link" flex="1"
+ onclick="openUILink(this.value, event);" crop="end"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:hbox flex="1" class="feed-subscribe">
+ <xul:spacer flex="1"/>
+ <xul:button label="&feedSubscribe;" accesskey="&feedSubscribe.accesskey;"
+ oncommand="onSubscribeFeed()"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/browser/base/content/pageinfo/pageInfo.css b/browser/base/content/pageinfo/pageInfo.css
new file mode 100644
index 000000000..622b56bb5
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#viewGroup > radio {
+ -moz-binding: url("chrome://browser/content/pageinfo/pageInfo.xml#viewbutton");
+}
+
+richlistitem[feed] {
+ -moz-binding: url("chrome://browser/content/pageinfo/feeds.xml#feed");
+}
+
+richlistitem[feed]:not([selected="true"]) .feed-subscribe {
+ display: none;
+}
+
+groupbox[closed="true"] > .groupbox-body {
+ visibility: collapse;
+}
+
+#thepreviewimage {
+ display: block;
+/* This following entry can be removed when Bug 522850 is fixed. */
+ min-width: 1px;
+}
diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js
new file mode 100644
index 000000000..7a6d0a063
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -0,0 +1,1140 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/LoadContextInfo.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// define a js object to implement nsITreeView
+function pageInfoTreeView(treeid, copycol)
+{
+ // copycol is the index number for the column that we want to add to
+ // the copy-n-paste buffer when the user hits accel-c
+ this.treeid = treeid;
+ this.copycol = copycol;
+ this.rows = 0;
+ this.tree = null;
+ this.data = [ ];
+ this.selection = null;
+ this.sortcol = -1;
+ this.sortdir = false;
+}
+
+pageInfoTreeView.prototype = {
+ set rowCount(c) { throw "rowCount is a readonly property"; },
+ get rowCount() { return this.rows; },
+
+ setTree: function(tree)
+ {
+ this.tree = tree;
+ },
+
+ getCellText: function(row, column)
+ {
+ // row can be null, but js arrays are 0-indexed.
+ // colidx cannot be null, but can be larger than the number
+ // of columns in the array. In this case it's the fault of
+ // whoever typoed while calling this function.
+ return this.data[row][column.index] || "";
+ },
+
+ setCellValue: function(row, column, value)
+ {
+ },
+
+ setCellText: function(row, column, value)
+ {
+ this.data[row][column.index] = value;
+ },
+
+ addRow: function(row)
+ {
+ this.rows = this.data.push(row);
+ this.rowCountChanged(this.rows - 1, 1);
+ if (this.selection.count == 0 && this.rowCount && !gImageElement) {
+ this.selection.select(0);
+ }
+ },
+
+ addRows: function(rows)
+ {
+ for (let row of rows) {
+ this.addRow(row);
+ }
+ },
+
+ rowCountChanged: function(index, count)
+ {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function()
+ {
+ this.tree.invalidate();
+ },
+
+ clear: function()
+ {
+ if (this.tree)
+ this.tree.rowCountChanged(0, -this.rows);
+ this.rows = 0;
+ this.data = [ ];
+ },
+
+ handleCopy: function(row)
+ {
+ return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
+ },
+
+ performActionOnRow: function(action, row)
+ {
+ if (action == "copy") {
+ var data = this.handleCopy(row)
+ this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
+ }
+ },
+
+ onPageMediaSort : function(columnname)
+ {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ this.sortdir =
+ gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ treecol.index,
+ function textComparator(a, b) { return (a || "").toLowerCase().localeCompare((b || "").toLowerCase()); },
+ this.sortcol,
+ this.sortdir
+ );
+
+ Array.forEach(tree.columns, function(col) {
+ col.element.removeAttribute("sortActive");
+ col.element.removeAttribute("sortDirection");
+ });
+ treecol.element.setAttribute("sortActive", "true");
+ treecol.element.setAttribute("sortDirection", this.sortdir ?
+ "ascending" : "descending");
+
+ this.sortcol = treecol.index;
+ },
+
+ getRowProperties: function(row) { return ""; },
+ getCellProperties: function(row, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function() { return this.sortcol > -1 },
+ canDrop: function(index, orientation) { return false; },
+ drop: function(row, orientation) { return false; },
+ getParentIndex: function(index) { return 0; },
+ hasNextSibling: function(index, after) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, column) { },
+ getProgressMode: function(row, column) { },
+ getCellValue: function(row, column) { },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(col) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(row, column) { return false; },
+ isSelectable: function(row, column) { return false; },
+ performAction: function(action) { },
+ performActionOnCell: function(action, row, column) { }
+};
+
+// mmm, yummy. global variables.
+var gDocInfo = null;
+var gImageElement = null;
+
+// column number to help using the data array
+const COL_IMAGE_ADDRESS = 0;
+const COL_IMAGE_TYPE = 1;
+const COL_IMAGE_SIZE = 2;
+const COL_IMAGE_ALT = 3;
+const COL_IMAGE_COUNT = 4;
+const COL_IMAGE_NODE = 5;
+const COL_IMAGE_BG = 6;
+
+// column number to copy from, second argument to pageInfoTreeView's constructor
+const COPYCOL_NONE = -1;
+const COPYCOL_META_CONTENT = 1;
+const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
+
+// one nsITreeView for each tree in the window
+var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT);
+var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE);
+
+gImageView.getCellProperties = function(row, col) {
+ var data = gImageView.data[row];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var props = "";
+ if (!checkProtocol(data) ||
+ item instanceof HTMLEmbedElement ||
+ (item instanceof HTMLObjectElement && !item.type.startsWith("image/")))
+ props += "broken";
+
+ if (col.element.id == "image-address")
+ props += " ltr";
+
+ return props;
+};
+
+gImageView.getCellText = function(row, column) {
+ var value = this.data[row][column.index];
+ if (column.index == COL_IMAGE_SIZE) {
+ if (value == -1) {
+ return gStrings.unknown;
+ }
+ var kbSize = Number(Math.round(value / 1024 * 100) / 100);
+ return gBundle.getFormattedString("mediaFileSize", [kbSize]);
+ }
+ return value || "";
+};
+
+gImageView.onPageMediaSort = function(columnname) {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ var comparator;
+ var index = treecol.index;
+ if (index == COL_IMAGE_SIZE || index == COL_IMAGE_COUNT) {
+ comparator = function numComparator(a, b) { return a - b; };
+ } else {
+ comparator = function textComparator(a, b) { return (a || "").toLowerCase().localeCompare((b || "").toLowerCase()); };
+ }
+
+ this.sortdir =
+ gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ index,
+ comparator,
+ this.sortcol,
+ this.sortdir
+ );
+
+ Array.forEach(tree.columns, function(col) {
+ col.element.removeAttribute("sortActive");
+ col.element.removeAttribute("sortDirection");
+ });
+ treecol.element.setAttribute("sortActive", "true");
+ treecol.element.setAttribute("sortDirection", this.sortdir ?
+ "ascending" : "descending");
+
+ this.sortcol = index;
+};
+
+var gImageHash = { };
+
+// localized strings (will be filled in when the document is loaded)
+// this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
+var gStrings = { };
+var gBundle;
+
+const PERMISSION_CONTRACTID = "@mozilla.org/permissionmanager;1";
+const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1";
+const ATOM_CONTRACTID = "@mozilla.org/atom-service;1";
+
+// a number of services I'll need later
+// the cache services
+const nsICacheStorageService = Components.interfaces.nsICacheStorageService;
+const nsICacheStorage = Components.interfaces.nsICacheStorage;
+const cacheService = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"].getService(nsICacheStorageService);
+
+var loadContextInfo = LoadContextInfo.fromLoadContext(
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext), false);
+var diskStorage = cacheService.diskCacheStorage(loadContextInfo, false);
+
+const nsICookiePermission = Components.interfaces.nsICookiePermission;
+const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+
+const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
+const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
+
+// clipboard helper
+function getClipboardHelper() {
+ try {
+ return Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
+ } catch (e) {
+ // do nothing, later code will handle the error
+ return null;
+ }
+}
+const gClipboardHelper = getClipboardHelper();
+
+// Interface for image loading content
+const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
+
+// namespaces, don't need all of these yet...
+const XLinkNS = "http://www.w3.org/1999/xlink";
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XMLNS = "http://www.w3.org/XML/1998/namespace";
+const XHTMLNS = "http://www.w3.org/1999/xhtml";
+const XHTML2NS = "http://www.w3.org/2002/06/xhtml2"
+
+const XHTMLNSre = "^http\:\/\/www\.w3\.org\/1999\/xhtml$";
+const XHTML2NSre = "^http\:\/\/www\.w3\.org\/2002\/06\/xhtml2$";
+const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, "");
+
+/* Overlays register functions here.
+ * These arrays are used to hold callbacks that Page Info will call at
+ * various stages. Use them by simply appending a function to them.
+ * For example, add a function to onLoadRegistry by invoking
+ * "onLoadRegistry.push(XXXLoadFunc);"
+ * The XXXLoadFunc should be unique to the overlay module, and will be
+ * invoked as "XXXLoadFunc();"
+ */
+
+// These functions are called to build the data displayed in the Page Info window.
+var onLoadRegistry = [ ];
+
+// These functions are called to remove old data still displayed in
+// the window when the document whose information is displayed
+// changes. For example, at this time, the list of images of the Media
+// tab is cleared.
+var onResetRegistry = [ ];
+
+// These functions are called once when all the elements in all of the target
+// document (and all of its subframes, if any) have been processed
+var onFinished = [ ];
+
+// These functions are called once when the Page Info window is closed.
+var onUnloadRegistry = [ ];
+
+/* Called when PageInfo window is loaded. Arguments are:
+ * window.arguments[0] - (optional) an object consisting of
+ * - doc: (optional) document to use for source. if not provided,
+ * the calling window's document will be used
+ * - initialTab: (optional) id of the inital tab to display
+ */
+function onLoadPageInfo()
+{
+ gBundle = document.getElementById("pageinfobundle");
+ gStrings.unknown = gBundle.getString("unknown");
+ gStrings.notSet = gBundle.getString("notset");
+ gStrings.mediaImg = gBundle.getString("mediaImg");
+ gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
+ gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
+ gStrings.mediaListImg = gBundle.getString("mediaListImg");
+ gStrings.mediaCursor = gBundle.getString("mediaCursor");
+ gStrings.mediaObject = gBundle.getString("mediaObject");
+ gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
+ gStrings.mediaLink = gBundle.getString("mediaLink");
+ gStrings.mediaInput = gBundle.getString("mediaInput");
+ gStrings.mediaVideo = gBundle.getString("mediaVideo");
+ gStrings.mediaAudio = gBundle.getString("mediaAudio");
+
+ var args = "arguments" in window &&
+ window.arguments.length >= 1 &&
+ window.arguments[0];
+
+ // init media view
+ var imageTree = document.getElementById("imagetree");
+ imageTree.view = gImageView;
+
+ /* Select the requested tab, if the name is specified */
+ loadTab(args);
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(window, "page-info-dialog-loaded", null);
+}
+
+function loadPageInfo(frameOuterWindowID, imageElement, browser)
+{
+ browser = browser || window.opener.gBrowser.selectedBrowser;
+ let mm = browser.messageManager;
+
+ gStrings["application/rss+xml"] = gBundle.getString("feedRss");
+ gStrings["application/atom+xml"] = gBundle.getString("feedAtom");
+ gStrings["text/xml"] = gBundle.getString("feedXML");
+ gStrings["application/xml"] = gBundle.getString("feedXML");
+ gStrings["application/rdf+xml"] = gBundle.getString("feedXML");
+
+ // Look for pageInfoListener in content.js. Sends message to listener with arguments.
+ mm.sendAsyncMessage("PageInfo:getData", {strings: gStrings,
+ frameOuterWindowID: frameOuterWindowID},
+ { imageElement });
+
+ let pageInfoData;
+
+ // Get initial pageInfoData needed to display the general, feeds, permission and security tabs.
+ mm.addMessageListener("PageInfo:data", function onmessage(message) {
+ mm.removeMessageListener("PageInfo:data", onmessage);
+ pageInfoData = message.data;
+ let docInfo = pageInfoData.docInfo;
+ let windowInfo = pageInfoData.windowInfo;
+ let uri = makeURI(docInfo.documentURIObject.spec,
+ docInfo.documentURIObject.originCharset);
+ let principal = docInfo.principal;
+ gDocInfo = docInfo;
+
+ gImageElement = pageInfoData.imageInfo;
+
+ var titleFormat = windowInfo.isTopWindow ? "pageInfo.page.title"
+ : "pageInfo.frame.title";
+ document.title = gBundle.getFormattedString(titleFormat, [docInfo.location]);
+
+ document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location);
+
+ makeGeneralTab(pageInfoData.metaViewRows, docInfo);
+ initFeedTab(pageInfoData.feeds);
+ onLoadPermission(uri, principal);
+ securityOnLoad(uri, windowInfo);
+ });
+
+ // Get the media elements from content script to setup the media tab.
+ mm.addMessageListener("PageInfo:mediaData", function onmessage(message) {
+ // Page info window was closed.
+ if (window.closed) {
+ mm.removeMessageListener("PageInfo:mediaData", onmessage);
+ return;
+ }
+
+ // The page info media fetching has been completed.
+ if (message.data.isComplete) {
+ mm.removeMessageListener("PageInfo:mediaData", onmessage);
+ onFinished.forEach(function(func) { func(pageInfoData); });
+ return;
+ }
+
+ for (let item of message.data.mediaItems) {
+ addImage(item);
+ }
+
+ selectImage();
+ });
+
+ /* Call registered overlay init functions */
+ onLoadRegistry.forEach(function(func) { func(); });
+}
+
+function resetPageInfo(args)
+{
+ /* Reset Meta tags part */
+ gMetaView.clear();
+
+ /* Reset Media tab */
+ var mediaTab = document.getElementById("mediaTab");
+ if (!mediaTab.hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ mediaTab.hidden = true;
+ }
+ gImageView.clear();
+ gImageHash = {};
+
+ /* Reset Feeds Tab */
+ var feedListbox = document.getElementById("feedListbox");
+ while (feedListbox.firstChild)
+ feedListbox.removeChild(feedListbox.firstChild);
+
+ /* Call registered overlay reset functions */
+ onResetRegistry.forEach(function(func) { func(); });
+
+ /* Rebuild the data */
+ loadTab(args);
+}
+
+function onUnloadPageInfo()
+{
+ // Remove the observer, only if there is at least 1 image.
+ if (!document.getElementById("mediaTab").hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ }
+
+ /* Call registered overlay unload functions */
+ onUnloadRegistry.forEach(function(func) { func(); });
+}
+
+function doHelpButton()
+{
+ const helpTopics = {
+ "generalPanel": "pageinfo_general",
+ "mediaPanel": "pageinfo_media",
+ "feedPanel": "pageinfo_feed",
+ "permPanel": "pageinfo_permissions",
+ "securityPanel": "pageinfo_security"
+ };
+
+ var deck = document.getElementById("mainDeck");
+ var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general";
+ openHelpLink(helpdoc);
+}
+
+function showTab(id)
+{
+ var deck = document.getElementById("mainDeck");
+ var pagel = document.getElementById(id + "Panel");
+ deck.selectedPanel = pagel;
+}
+
+function loadTab(args)
+{
+ // If the "View Image Info" context menu item was used, the related image
+ // element is provided as an argument. This can't be a background image.
+ let imageElement = args && args.imageElement;
+ let frameOuterWindowID = args && args.frameOuterWindowID;
+ let browser = args && args.browser;
+
+ /* Load the page info */
+ loadPageInfo(frameOuterWindowID, imageElement, browser);
+
+ var initialTab = (args && args.initialTab) || "generalTab";
+ var radioGroup = document.getElementById("viewGroup");
+ initialTab = document.getElementById(initialTab) || document.getElementById("generalTab");
+ radioGroup.selectedItem = initialTab;
+ radioGroup.selectedItem.doCommand();
+ radioGroup.focus();
+}
+
+function toggleGroupbox(id)
+{
+ var elt = document.getElementById(id);
+ if (elt.hasAttribute("closed")) {
+ elt.removeAttribute("closed");
+ if (elt.flexWhenOpened)
+ elt.flex = elt.flexWhenOpened;
+ }
+ else {
+ elt.setAttribute("closed", "true");
+ if (elt.flex) {
+ elt.flexWhenOpened = elt.flex;
+ elt.flex = 0;
+ }
+ }
+}
+
+function openCacheEntry(key, cb)
+{
+ var checkCacheListener = {
+ onCacheEntryCheck: function(entry, appCache) {
+ return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable: function(entry, isNew, appCache, status) {
+ cb(entry);
+ }
+ };
+ diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
+}
+
+function makeGeneralTab(metaViewRows, docInfo)
+{
+ var title = (docInfo.title) ? docInfo.title : gBundle.getString("noPageTitle");
+ document.getElementById("titletext").value = title;
+
+ var url = docInfo.location;
+ setItemValue("urltext", url);
+
+ var referrer = ("referrer" in docInfo && docInfo.referrer);
+ setItemValue("refertext", referrer);
+
+ var mode = ("compatMode" in docInfo && docInfo.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
+ document.getElementById("modetext").value = gBundle.getString(mode);
+
+ // find out the mime type
+ var mimeType = docInfo.contentType;
+ setItemValue("typetext", mimeType);
+
+ // get the document characterset
+ var encoding = docInfo.characterSet;
+ document.getElementById("encodingtext").value = encoding;
+
+ let length = metaViewRows.length;
+
+ var metaGroup = document.getElementById("metaTags");
+ if (!length)
+ metaGroup.collapsed = true;
+ else {
+ var metaTagsCaption = document.getElementById("metaTagsCaption");
+ if (length == 1)
+ metaTagsCaption.label = gBundle.getString("generalMetaTag");
+ else
+ metaTagsCaption.label = gBundle.getFormattedString("generalMetaTags", [length]);
+ var metaTree = document.getElementById("metatree");
+ metaTree.view = gMetaView;
+
+ // Add the metaViewRows onto the general tab's meta info tree.
+ gMetaView.addRows(metaViewRows);
+
+ metaGroup.collapsed = false;
+ }
+
+ // get the date of last modification
+ var modifiedText = formatDate(docInfo.lastModified, gStrings.notSet);
+ document.getElementById("modifiedtext").value = modifiedText;
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ var sizeText;
+ if (cacheEntry) {
+ var pageSize = cacheEntry.dataSize;
+ var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
+ sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
+ }
+ setItemValue("sizetext", sizeText);
+ });
+}
+
+function addImage(imageViewRow)
+{
+ let [url, type, alt, elem, isBg] = imageViewRow;
+
+ if (!url)
+ return;
+
+ if (!gImageHash.hasOwnProperty(url))
+ gImageHash[url] = { };
+ if (!gImageHash[url].hasOwnProperty(type))
+ gImageHash[url][type] = { };
+ if (!gImageHash[url][type].hasOwnProperty(alt)) {
+ gImageHash[url][type][alt] = gImageView.data.length;
+ var row = [url, type, -1, alt, 1, elem, isBg];
+ gImageView.addRow(row);
+
+ // Fill in cache data asynchronously
+ openCacheEntry(url, function(cacheEntry) {
+ // The data at row[2] corresponds to the data size.
+ if (cacheEntry) {
+ row[2] = cacheEntry.dataSize;
+ // Invalidate the row to trigger a repaint.
+ gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+ }
+ });
+
+ // Add the observer, only once.
+ if (gImageView.data.length == 1) {
+ document.getElementById("mediaTab").hidden = false;
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .addObserver(imagePermissionObserver, "perm-changed", false);
+ }
+ }
+ else {
+ var i = gImageHash[url][type][alt];
+ gImageView.data[i][COL_IMAGE_COUNT]++;
+ // The same image can occur several times on the page at different sizes.
+ // If the "View Image Info" context menu item was used, ensure we select
+ // the correct element.
+ if (!gImageView.data[i][COL_IMAGE_BG] &&
+ gImageElement && url == gImageElement.currentSrc &&
+ gImageElement.width == elem.width &&
+ gImageElement.height == elem.height &&
+ gImageElement.imageText == elem.imageText) {
+ gImageView.data[i][COL_IMAGE_NODE] = elem;
+ }
+ }
+}
+
+// Link Stuff
+function openURL(target)
+{
+ var url = target.parentNode.childNodes[2].value;
+ window.open(url, "_blank", "chrome");
+}
+
+function onBeginLinkDrag(event, urlField, descField)
+{
+ if (event.originalTarget.localName != "treechildren")
+ return;
+
+ var tree = event.target;
+ if (!("treeBoxObject" in tree))
+ tree = tree.parentNode;
+
+ var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (row == -1)
+ return;
+
+ // Adding URL flavor
+ var col = tree.columns[urlField];
+ var url = tree.view.getCellText(row, col);
+ col = tree.columns[descField];
+ var desc = tree.view.getCellText(row, col);
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", url + "\n" + desc);
+ dt.setData("text/url-list", url);
+ dt.setData("text/plain", url);
+}
+
+// Image Stuff
+function getSelectedRows(tree)
+{
+ var start = { };
+ var end = { };
+ var numRanges = tree.view.selection.getRangeCount();
+
+ var rowArray = [ ];
+ for (var t = 0; t < numRanges; t++) {
+ tree.view.selection.getRangeAt(t, start, end);
+ for (var v = start.value; v <= end.value; v++)
+ rowArray.push(v);
+ }
+
+ return rowArray;
+}
+
+function getSelectedRow(tree)
+{
+ var rows = getSelectedRows(tree);
+ return (rows.length == 1) ? rows[0] : -1;
+}
+
+function selectSaveFolder(aCallback)
+{
+ const nsILocalFile = Components.interfaces.nsILocalFile;
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ let titleText = gBundle.getString("mediaSelectFolder");
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ aCallback(fp.file.QueryInterface(nsILocalFile));
+ } else {
+ aCallback(null);
+ }
+ };
+
+ fp.init(window, titleText, nsIFilePicker.modeGetFolder);
+ fp.appendFilters(nsIFilePicker.filterAll);
+ try {
+ let prefs = Components.classes[PREFERENCES_CONTRACTID].
+ getService(Components.interfaces.nsIPrefBranch);
+ let initialDir = prefs.getComplexValue("browser.download.dir", nsILocalFile);
+ if (initialDir) {
+ fp.displayDirectory = initialDir;
+ }
+ } catch (ex) {
+ }
+ fp.open(fpCallback);
+}
+
+function saveMedia()
+{
+ var tree = document.getElementById("imagetree");
+ var rowArray = getSelectedRows(tree);
+ if (rowArray.length == 1) {
+ var row = rowArray[0];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+
+ if (url) {
+ var titleKey = "SaveImageTitle";
+
+ if (item instanceof HTMLVideoElement)
+ titleKey = "SaveVideoTitle";
+ else if (item instanceof HTMLAudioElement)
+ titleKey = "SaveAudioTitle";
+
+ saveURL(url, null, titleKey, false, false, makeURI(item.baseURI),
+ null, gDocInfo.isContentWindowPrivate);
+ }
+ } else {
+ selectSaveFolder(function(aDirectory) {
+ if (aDirectory) {
+ var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
+ uniqueFile(aChosenData.file);
+ internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
+ aChosenData, aBaseURI, null, false, null, gDocInfo.isContentWindowPrivate);
+ };
+
+ for (var i = 0; i < rowArray.length; i++) {
+ var v = rowArray[i];
+ var dir = aDirectory.clone();
+ var item = gImageView.data[v][COL_IMAGE_NODE];
+ var uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
+ var uri = makeURI(uriString);
+
+ try {
+ uri.QueryInterface(Components.interfaces.nsIURL);
+ dir.append(decodeURIComponent(uri.fileName));
+ } catch (ex) {
+ // data:/blob: uris
+ // Supply a dummy filename, otherwise Download Manager
+ // will try to delete the base directory on failure.
+ dir.append(gImageView.data[v][COL_IMAGE_TYPE]);
+ }
+
+ if (i == 0) {
+ saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI));
+ } else {
+ // This delay is a hack which prevents the download manager
+ // from opening many times. See bug 377339.
+ setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri),
+ makeURI(item.baseURI));
+ }
+ }
+ }
+ });
+ }
+}
+
+function onBlockImage()
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var checkbox = document.getElementById("blockImage");
+ var uri = makeURI(document.getElementById("imageurltext").value);
+ if (checkbox.checked)
+ permissionManager.add(uri, "image", nsIPermissionManager.DENY_ACTION);
+ else
+ permissionManager.remove(uri, "image");
+}
+
+function onImageSelect()
+{
+ var previewBox = document.getElementById("mediaPreviewBox");
+ var mediaSaveBox = document.getElementById("mediaSaveBox");
+ var splitter = document.getElementById("mediaSplitter");
+ var tree = document.getElementById("imagetree");
+ var count = tree.view.selection.count;
+ if (count == 0) {
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = true;
+ tree.flex = 1;
+ }
+ else if (count > 1) {
+ splitter.collapsed = true;
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = false;
+ tree.flex = 1;
+ }
+ else {
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = false;
+ previewBox.collapsed = false;
+ tree.flex = 0;
+ makePreview(getSelectedRows(tree)[0]);
+ }
+}
+
+// Makes the media preview (image, video, etc) for the selected row on the media tab.
+function makePreview(row)
+{
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ var isBG = gImageView.data[row][COL_IMAGE_BG];
+ var isAudio = false;
+
+ setItemValue("imageurltext", url);
+ setItemValue("imagetext", item.imageText);
+ setItemValue("imagelongdesctext", item.longDesc);
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ // find out the file size
+ var sizeText;
+ if (cacheEntry) {
+ let imageSize = cacheEntry.dataSize;
+ var kbSize = Math.round(imageSize / 1024 * 100) / 100;
+ sizeText = gBundle.getFormattedString("generalSize",
+ [formatNumber(kbSize), formatNumber(imageSize)]);
+ }
+ else
+ sizeText = gBundle.getString("mediaUnknownNotCached");
+ setItemValue("imagesizetext", sizeText);
+
+ var mimeType = item.mimeType || this.getContentTypeFromHeaders(cacheEntry);
+ var numFrames = item.numFrames;
+
+ var imageType;
+ if (mimeType) {
+ // We found the type, try to display it nicely
+ let imageMimeType = /^image\/(.*)/i.exec(mimeType);
+ if (imageMimeType) {
+ imageType = imageMimeType[1].toUpperCase();
+ if (numFrames > 1)
+ imageType = gBundle.getFormattedString("mediaAnimatedImageType",
+ [imageType, numFrames]);
+ else
+ imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
+ }
+ else {
+ // the MIME type doesn't begin with image/, display the raw type
+ imageType = mimeType;
+ }
+ }
+ else {
+ // We couldn't find the type, fall back to the value in the treeview
+ imageType = gImageView.data[row][COL_IMAGE_TYPE];
+ }
+ setItemValue("imagetypetext", imageType);
+
+ var imageContainer = document.getElementById("theimagecontainer");
+ var oldImage = document.getElementById("thepreviewimage");
+
+ var isProtocolAllowed = checkProtocol(gImageView.data[row]);
+
+ var newImage = new Image;
+ newImage.id = "thepreviewimage";
+ var physWidth = 0, physHeight = 0;
+ var width = 0, height = 0;
+
+ if ((item.HTMLLinkElement || item.HTMLInputElement ||
+ item.HTMLImageElement || item.SVGImageElement ||
+ (item.HTMLObjectElement && mimeType && mimeType.startsWith("image/")) ||
+ isBG) && isProtocolAllowed) {
+ newImage.setAttribute("src", url);
+ physWidth = newImage.width || 0;
+ physHeight = newImage.height || 0;
+
+ // "width" and "height" attributes must be set to newImage,
+ // even if there is no "width" or "height attribute in item;
+ // otherwise, the preview image cannot be displayed correctly.
+ // Since the image might have been loaded out-of-process, we expect
+ // the item to tell us its width / height dimensions. Failing that
+ // the item should tell us the natural dimensions of the image. Finally
+ // failing that, we'll assume that the image was never loaded in the
+ // other process (this can be true for favicons, for example), and so
+ // we'll assume that we can use the natural dimensions of the newImage
+ // we just created. If the natural dimensions of newImage are not known
+ // then the image is probably broken.
+ if (!isBG) {
+ newImage.width = ("width" in item && item.width) || newImage.naturalWidth;
+ newImage.height = ("height" in item && item.height) || newImage.naturalHeight;
+ }
+ else {
+ // the Width and Height of an HTML tag should not be used for its background image
+ // (for example, "table" can have "width" or "height" attributes)
+ newImage.width = item.naturalWidth || newImage.naturalWidth;
+ newImage.height = item.naturalHeight || newImage.naturalHeight;
+ }
+
+ if (item.SVGImageElement) {
+ newImage.width = item.SVGImageElementWidth;
+ newImage.height = item.SVGImageElementHeight;
+ }
+
+ width = newImage.width;
+ height = newImage.height;
+
+ document.getElementById("theimagecontainer").collapsed = false
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else if (item.HTMLVideoElement && isProtocolAllowed) {
+ newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ width = physWidth = item.videoWidth;
+ height = physHeight = item.videoHeight;
+
+ document.getElementById("theimagecontainer").collapsed = false;
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else if (item.HTMLAudioElement && isProtocolAllowed) {
+ newImage = new Audio;
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ isAudio = true;
+
+ document.getElementById("theimagecontainer").collapsed = false;
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else {
+ // fallback image for protocols not allowed (e.g., javascript:)
+ // or elements not [yet] handled (e.g., object, embed).
+ document.getElementById("brokenimagecontainer").collapsed = false;
+ document.getElementById("theimagecontainer").collapsed = true;
+ }
+
+ let imageSize = "";
+ if (url && !isAudio) {
+ if (width != physWidth || height != physHeight) {
+ imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
+ [formatNumber(physWidth),
+ formatNumber(physHeight),
+ formatNumber(width),
+ formatNumber(height)]);
+ }
+ else {
+ imageSize = gBundle.getFormattedString("mediaDimensions",
+ [formatNumber(width),
+ formatNumber(height)]);
+ }
+ }
+ setItemValue("imagedimensiontext", imageSize);
+
+ makeBlockImage(url);
+
+ imageContainer.removeChild(oldImage);
+ imageContainer.appendChild(newImage);
+ });
+}
+
+function makeBlockImage(url)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ var prefs = Components.classes[PREFERENCES_CONTRACTID]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ var checkbox = document.getElementById("blockImage");
+ var imagePref = prefs.getIntPref("permissions.default.image");
+ if (!(/^https?:/.test(url)) || imagePref == 2)
+ // We can't block the images from this host because either is is not
+ // for http(s) or we don't load images at all
+ checkbox.hidden = true;
+ else {
+ var uri = makeURI(url);
+ if (uri.host) {
+ checkbox.hidden = false;
+ checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
+ var perm = permissionManager.testPermission(uri, "image");
+ checkbox.checked = perm == nsIPermissionManager.DENY_ACTION;
+ }
+ else
+ checkbox.hidden = true;
+ }
+}
+
+var imagePermissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (document.getElementById("mediaPreviewBox").collapsed)
+ return;
+
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+ if (permission.type == "image") {
+ var imageTree = document.getElementById("imagetree");
+ var row = getSelectedRow(imageTree);
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ if (permission.matchesURI(makeURI(url), true)) {
+ makeBlockImage(url);
+ }
+ }
+ }
+ }
+}
+
+function getContentTypeFromHeaders(cacheEntryDescriptor)
+{
+ if (!cacheEntryDescriptor)
+ return null;
+
+ let headers = cacheEntryDescriptor.getMetaDataElement("response-head");
+ let type = /^Content-Type:\s*(.*?)\s*(?:\;|$)/mi.exec(headers);
+ return type && type[1];
+}
+
+function setItemValue(id, value)
+{
+ var item = document.getElementById(id);
+ if (value) {
+ item.parentNode.collapsed = false;
+ item.value = value;
+ }
+ else
+ item.parentNode.collapsed = true;
+}
+
+function formatNumber(number)
+{
+ return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
+}
+
+function formatDate(datestr, unknown)
+{
+ var date = new Date(datestr);
+ if (!date.valueOf())
+ return unknown;
+
+ const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Components.interfaces.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric' };
+ return date.toLocaleString(locale, dtOptions);
+}
+
+function doCopy()
+{
+ if (!gClipboardHelper)
+ return;
+
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem) {
+ var view = elem.view;
+ var selection = view.selection;
+ var text = [], tmp = '';
+ var min = {}, max = {};
+
+ var count = selection.getRangeCount();
+
+ for (var i = 0; i < count; i++) {
+ selection.getRangeAt(i, min, max);
+
+ for (var row = min.value; row <= max.value; row++) {
+ view.performActionOnRow("copy", row);
+
+ tmp = elem.getAttribute("copybuffer");
+ if (tmp)
+ text.push(tmp);
+ elem.removeAttribute("copybuffer");
+ }
+ }
+ gClipboardHelper.copyString(text.join("\n"));
+ }
+}
+
+function doSelectAllMedia()
+{
+ var tree = document.getElementById("imagetree");
+
+ if (tree)
+ tree.view.selection.selectAll();
+}
+
+function doSelectAll()
+{
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem)
+ elem.view.selection.selectAll();
+}
+
+function selectImage()
+{
+ if (!gImageElement)
+ return;
+
+ var tree = document.getElementById("imagetree");
+ for (var i = 0; i < tree.view.rowCount; i++) {
+ // If the image row element is the image selected from the "View Image Info" context menu item.
+ let image = gImageView.data[i][COL_IMAGE_NODE];
+ if (!gImageView.data[i][COL_IMAGE_BG] &&
+ gImageElement.currentSrc == gImageView.data[i][COL_IMAGE_ADDRESS] &&
+ gImageElement.width == image.width &&
+ gImageElement.height == image.height &&
+ gImageElement.imageText == image.imageText) {
+ tree.view.selection.select(i);
+ tree.treeBoxObject.ensureRowIsVisible(i);
+ tree.focus();
+ return;
+ }
+ }
+}
+
+function checkProtocol(img)
+{
+ var url = img[COL_IMAGE_ADDRESS];
+ return /^data:image\//i.test(url) ||
+ /^(https?|ftp|file|about|chrome|resource):/.test(url);
+}
diff --git a/browser/base/content/pageinfo/pageInfo.xml b/browser/base/content/pageinfo/pageInfo.xml
new file mode 100644
index 000000000..62c699bf2
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.xml
@@ -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/. -->
+
+
+<bindings id="pageInfoBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <!-- based on preferences.xml paneButton -->
+ <binding id="viewbutton" extends="chrome://global/content/bindings/radio.xml#radio" role="xullistitem">
+ <content>
+ <xul:image class="viewButtonIcon" xbl:inherits="src"/>
+ <xul:label class="viewButtonLabel" xbl:inherits="value=label"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/pageinfo/pageInfo.xul b/browser/base/content/pageinfo/pageInfo.xul
new file mode 100644
index 000000000..8352a8aa7
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -0,0 +1,438 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/pageinfo/pageInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/pageInfo.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<window id="main-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="Browser:page-info"
+ onload="onLoadPageInfo()"
+ onunload="onUnloadPageInfo()"
+ align="stretch"
+ screenX="10" screenY="10"
+ width="&pageInfoWindow.width;" height="&pageInfoWindow.height;"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <stringbundleset id="pageinfobundleset">
+ <stringbundle id="pageinfobundle" src="chrome://browser/locale/pageInfo.properties"/>
+ <stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
+ <stringbundle id="browserBundle" src="chrome://browser/locale/browser.properties"/>
+ </stringbundleset>
+
+ <commandset id="pageInfoCommandSet">
+ <command id="cmd_close" oncommand="window.close();"/>
+ <command id="cmd_help" oncommand="doHelpButton();"/>
+ <command id="cmd_copy" oncommand="doCopy();"/>
+ <command id="cmd_selectall" oncommand="doSelectAll();"/>
+
+ <!-- permissions tab -->
+ <command id="cmd_pluginsDef" oncommand="onCheckboxClick('plugins');"/>
+ <command id="cmd_pluginsToggle" oncommand="onPluginRadioClick(event);"/>
+ </commandset>
+
+ <keyset id="pageInfoKeySet">
+ <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
+ <key keycode="VK_ESCAPE" command="cmd_close"/>
+#ifdef XP_MACOSX
+ <key key="." modifiers="meta" command="cmd_close"/>
+#else
+ <key keycode="VK_F1" command="cmd_help"/>
+#endif
+ <key key="&copy.key;" modifiers="accel" command="cmd_copy"/>
+ <key key="&selectall.key;" modifiers="accel" command="cmd_selectall"/>
+ <key key="&selectall.key;" modifiers="alt" command="cmd_selectall"/>
+ </keyset>
+
+ <menupopup id="picontext">
+ <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
+ <menuitem id="menu_copy" label="&copy.label;" command="cmd_copy" accesskey="&copy.accesskey;"/>
+ </menupopup>
+
+ <windowdragbox id="topBar" class="viewGroupWrapper">
+ <radiogroup id="viewGroup" class="chromeclass-toolbar" orient="horizontal">
+ <radio id="generalTab" label="&generalTab;" accesskey="&generalTab.accesskey;"
+ oncommand="showTab('general');"/>
+ <radio id="mediaTab" label="&mediaTab;" accesskey="&mediaTab.accesskey;"
+ oncommand="showTab('media');" hidden="true"/>
+ <radio id="feedTab" label="&feedTab;" accesskey="&feedTab.accesskey;"
+ oncommand="showTab('feed');" hidden="true"/>
+ <radio id="permTab" label="&permTab;" accesskey="&permTab.accesskey;"
+ oncommand="showTab('perm');"/>
+ <radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
+ oncommand="showTab('security');"/>
+ <!-- Others added by overlay -->
+ </radiogroup>
+ </windowdragbox>
+
+ <deck id="mainDeck" flex="1">
+ <!-- General page information -->
+ <vbox id="generalPanel">
+ <grid id="generalGrid">
+ <columns>
+ <column/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="generalRows">
+ <row id="generalTitle">
+ <label control="titletext" value="&generalTitle;"/>
+ <separator/>
+ <textbox readonly="true" id="titletext"/>
+ </row>
+ <row id="generalURLRow">
+ <label control="urltext" value="&generalURL;"/>
+ <separator/>
+ <textbox readonly="true" id="urltext"/>
+ </row>
+ <row id="generalSeparatorRow1">
+ <separator class="thin"/>
+ </row>
+ <row id="generalTypeRow">
+ <label control="typetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="typetext"/>
+ </row>
+ <row id="generalModeRow">
+ <label control="modetext" value="&generalMode;"/>
+ <separator/>
+ <textbox readonly="true" crop="end" id="modetext"/>
+ </row>
+ <row id="generalEncodingRow">
+ <label control="encodingtext" value="&generalEncoding2;"/>
+ <separator/>
+ <textbox readonly="true" id="encodingtext"/>
+ </row>
+ <row id="generalSizeRow">
+ <label control="sizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="sizetext"/>
+ </row>
+ <row id="generalReferrerRow">
+ <label control="refertext" value="&generalReferrer;"/>
+ <separator/>
+ <textbox readonly="true" id="refertext"/>
+ </row>
+ <row id="generalSeparatorRow2">
+ <separator class="thin"/>
+ </row>
+ <row id="generalModifiedRow">
+ <label control="modifiedtext" value="&generalModified;"/>
+ <separator/>
+ <textbox readonly="true" id="modifiedtext"/>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <groupbox id="metaTags" flex="1" class="collapsable treebox">
+ <caption id="metaTagsCaption" onclick="toggleGroupbox('metaTags');"/>
+ <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
+ <treecols>
+ <treecol id="meta-name" label="&generalMetaName;"
+ persist="width" flex="1"
+ onclick="gMetaView.onPageMediaSort('meta-name');"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="meta-content" label="&generalMetaContent;"
+ persist="width" flex="4"
+ onclick="gMetaView.onPageMediaSort('meta-content');"/>
+ </treecols>
+ <treechildren id="metatreechildren" flex="1"/>
+ </tree>
+ </groupbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+
+ <!-- Media information -->
+ <vbox id="mediaPanel">
+ <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
+ ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
+ <treecols>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="10"
+ width="10" id="image-address" label="&mediaAddress;"
+ onclick="gImageView.onPageMediaSort('image-address');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="image-type" label="&mediaType;"
+ onclick="gImageView.onPageMediaSort('image-type');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="2"
+ width="2" id="image-size" label="&mediaSize;" value="size"
+ onclick="gImageView.onPageMediaSort('image-size');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
+ width="4" id="image-alt" label="&mediaAltHeader;"
+ onclick="gImageView.onPageMediaSort('image-alt');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
+ width="1" id="image-count" label="&mediaCount;"
+ onclick="gImageView.onPageMediaSort('image-count');"/>
+ </treecols>
+ <treechildren id="imagetreechildren" flex="1"/>
+ </tree>
+ <splitter orient="vertical" id="mediaSplitter"/>
+ <vbox flex="1" id="mediaPreviewBox" collapsed="true">
+ <grid id="mediaGrid">
+ <columns>
+ <column id="mediaLabelColumn"/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="mediaRows">
+ <row id="mediaLocationRow">
+ <label control="imageurltext" value="&mediaLocation;"/>
+ <separator/>
+ <textbox readonly="true" id="imageurltext"/>
+ </row>
+ <row id="mediaTypeRow">
+ <label control="imagetypetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetypetext"/>
+ </row>
+ <row id="mediaSizeRow">
+ <label control="imagesizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="imagesizetext"/>
+ </row>
+ <row id="mediaDimensionRow">
+ <label control="imagedimensiontext" value="&mediaDimension;"/>
+ <separator/>
+ <textbox readonly="true" id="imagedimensiontext"/>
+ </row>
+ <row id="mediaTextRow">
+ <label control="imagetext" value="&mediaText;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetext"/>
+ </row>
+ <row id="mediaLongdescRow">
+ <label control="imagelongdesctext" value="&mediaLongdesc;"/>
+ <separator/>
+ <textbox readonly="true" id="imagelongdesctext"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox id="imageSaveBox" align="end">
+ <vbox id="blockImageBox">
+ <checkbox id="blockImage" hidden="true" oncommand="onBlockImage()"
+ accesskey="&mediaBlockImage.accesskey;"/>
+ <label control="thepreviewimage" value="&mediaPreview;" class="header"/>
+ </vbox>
+ <spacer id="imageSaveBoxSpacer" flex="1"/>
+ <button label="&selectall.label;" accesskey="&selectall.accesskey;"
+ id="selectallbutton"
+ oncommand="doSelectAllMedia();"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;"
+ icon="save" id="imagesaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <vbox id="imagecontainerbox" class="inset iframe" flex="1" pack="center">
+ <hbox id="theimagecontainer" pack="center">
+ <image id="thepreviewimage"/>
+ </hbox>
+ <hbox id="brokenimagecontainer" pack="center" collapsed="true">
+ <image id="brokenimage" src="resource://gre-resources/broken-image.png"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ <hbox id="mediaSaveBox" collapsed="true">
+ <spacer id="mediaSaveBoxSpacer" flex="1"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;"
+ icon="save" id="mediasaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+
+ <!-- Feeds -->
+ <vbox id="feedPanel">
+ <richlistbox id="feedListbox" flex="1"/>
+ </vbox>
+
+ <!-- Permissions -->
+ <vbox id="permPanel">
+ <hbox id="permHostBox">
+ <label value="&permissionsFor;" control="hostText" />
+ <textbox id="hostText" class="header" readonly="true"
+ crop="end" flex="1"/>
+ </hbox>
+
+ <vbox id="permList" flex="1">
+ <hbox id="perm-indexedDB-extras">
+ <spacer flex="1"/>
+ <vbox id="permIndexedDBStatusBox" pack="center">
+ <label id="indexedDBStatus" control="indexedDBClear" hidden="true"/>
+ </vbox>
+ <button id="indexedDBClear" label="&permClearStorage;" hidden="true"
+ accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/>
+ </hbox>
+ <vbox class="permission" id="perm-plugins-row">
+ <label class="permissionLabel" id="permPluginsLabel"
+ value="&permPlugins;" control="pluginsRadioGroup"/>
+ <hbox id="permPluginTemplate" role="group" aria-labelledby="permPluginsLabel" align="baseline">
+ <label class="permPluginTemplateLabel"/>
+ <spacer flex="1"/>
+ <radiogroup class="permPluginTemplateRadioGroup" orient="horizontal" command="cmd_pluginsToggle">
+ <radio class="permPluginTemplateRadioDefault" label="&permUseDefault;"/>
+ <radio class="permPluginTemplateRadioAsk" label="&permAskAlways;"/>
+ <radio class="permPluginTemplateRadioAllow" label="&permAllow;"/>
+ <radio class="permPluginTemplateRadioBlock" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ </vbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+
+ <!-- Security & Privacy -->
+ <vbox id="securityPanel">
+ <!-- Identity Section -->
+ <groupbox id="security-identity-groupbox" flex="1">
+ <caption id="security-identity" label="&securityView.identity.header;"/>
+ <grid id="security-identity-grid" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-identity-rows">
+ <!-- Domain -->
+ <row id="security-identity-domain-row">
+ <label id="security-identity-domain-label"
+ class="fieldLabel"
+ value="&securityView.identity.domain;"
+ control="security-identity-domain-value"/>
+ <textbox id="security-identity-domain-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Owner -->
+ <row id="security-identity-owner-row">
+ <label id="security-identity-owner-label"
+ class="fieldLabel"
+ value="&securityView.identity.owner;"
+ control="security-identity-owner-value"/>
+ <textbox id="security-identity-owner-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Verifier -->
+ <row id="security-identity-verifier-row">
+ <label id="security-identity-verifier-label"
+ class="fieldLabel"
+ value="&securityView.identity.verifier;"
+ control="security-identity-verifier-value"/>
+ <textbox id="security-identity-verifier-value"
+ class="fieldValue" readonly="true" />
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ <!-- Cert button -->
+ <hbox id="security-view-cert-box" pack="end">
+ <button id="security-view-cert" label="&securityView.certView;"
+ accesskey="&securityView.accesskey;"
+ oncommand="security.viewCert();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Privacy & History section -->
+ <groupbox id="security-privacy-groupbox" flex="1">
+ <caption id="security-privacy" label="&securityView.privacy.header;" />
+ <grid id="security-privacy-grid">
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-privacy-rows">
+ <!-- History -->
+ <row id="security-privacy-history-row">
+ <label id="security-privacy-history-label"
+ control="security-privacy-history-value"
+ class="fieldLabel">&securityView.privacy.history;</label>
+ <textbox id="security-privacy-history-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ readonly="true"/>
+ </row>
+ <!-- Cookies -->
+ <row id="security-privacy-cookies-row">
+ <label id="security-privacy-cookies-label"
+ control="security-privacy-cookies-value"
+ class="fieldLabel">&securityView.privacy.cookies;</label>
+ <hbox id="security-privacy-cookies-box" align="center">
+ <textbox id="security-privacy-cookies-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-cookies"
+ label="&securityView.privacy.viewCookies;"
+ accesskey="&securityView.privacy.viewCookies.accessKey;"
+ oncommand="security.viewCookies();"/>
+ </hbox>
+ </row>
+ <!-- Passwords -->
+ <row id="security-privacy-passwords-row">
+ <label id="security-privacy-passwords-label"
+ control="security-privacy-passwords-value"
+ class="fieldLabel">&securityView.privacy.passwords;</label>
+ <hbox id="security-privacy-passwords-box" align="center">
+ <textbox id="security-privacy-passwords-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-password"
+ label="&securityView.privacy.viewPasswords;"
+ accesskey="&securityView.privacy.viewPasswords.accessKey;"
+ oncommand="security.viewPasswords();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- Technical Details section -->
+ <groupbox id="security-technical-groupbox" flex="1">
+ <caption id="security-technical" label="&securityView.technical.header;" />
+ <vbox id="security-technical-box" flex="1">
+ <label id="security-technical-shortform" class="fieldValue"/>
+ <description id="security-technical-longform1" class="fieldLabel"/>
+ <description id="security-technical-longform2" class="fieldLabel"/>
+ <description id="security-technical-certificate-transparency" class="fieldLabel"/>
+ </vbox>
+ </groupbox>
+ <hbox pack="end">
+ <button command="cmd_help" label="&helpButton.label;" dlgtype="help"/>
+ </hbox>
+ </vbox>
+ <!-- Others added by overlay -->
+ </deck>
+
+#ifdef XP_MACOSX
+#include ../browserMountPoints.inc
+#endif
+
+</window>
diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js
new file mode 100644
index 000000000..0e6b9cba1
--- /dev/null
+++ b/browser/base/content/pageinfo/permissions.js
@@ -0,0 +1,334 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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:///modules/SitePermissions.jsm");
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+
+const nsIQuotaManagerService = Components.interfaces.nsIQuotaManagerService;
+
+var gPermURI;
+var gPermPrincipal;
+var gUsageRequest;
+
+// Array of permissionIDs sorted alphabetically by label.
+var gPermissions = SitePermissions.listPermissions().sort((a, b) => {
+ let firstLabel = SitePermissions.getPermissionLabel(a);
+ let secondLabel = SitePermissions.getPermissionLabel(b);
+ return firstLabel.localeCompare(secondLabel);
+});
+gPermissions.push("plugins");
+
+var permissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+ if (permission.matchesURI(gPermURI, true)) {
+ if (gPermissions.indexOf(permission.type) > -1)
+ initRow(permission.type);
+ else if (permission.type.startsWith("plugin"))
+ setPluginsRadioState();
+ }
+ }
+ }
+};
+
+function onLoadPermission(uri, principal)
+{
+ var permTab = document.getElementById("permTab");
+ if (SitePermissions.isSupportedURI(uri)) {
+ gPermURI = uri;
+ gPermPrincipal = principal;
+ var hostText = document.getElementById("hostText");
+ hostText.value = gPermURI.prePath;
+
+ for (var i of gPermissions)
+ initRow(i);
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(permissionObserver, "perm-changed", false);
+ onUnloadRegistry.push(onUnloadPermission);
+ permTab.hidden = false;
+ }
+ else
+ permTab.hidden = true;
+}
+
+function onUnloadPermission()
+{
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(permissionObserver, "perm-changed");
+
+ if (gUsageRequest) {
+ gUsageRequest.cancel();
+ gUsageRequest = null;
+ }
+}
+
+function initRow(aPartId)
+{
+ if (aPartId == "plugins") {
+ initPluginsRow();
+ return;
+ }
+
+ createRow(aPartId);
+
+ var checkbox = document.getElementById(aPartId + "Def");
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var perm = SitePermissions.get(gPermURI, aPartId);
+
+ if (perm) {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ }
+ else {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ perm = SitePermissions.getDefault(aPartId);
+ }
+ setRadioState(aPartId, perm);
+
+ if (aPartId == "indexedDB") {
+ initIndexedDBRow();
+ }
+}
+
+function createRow(aPartId) {
+ let rowId = "perm-" + aPartId + "-row";
+ if (document.getElementById(rowId))
+ return;
+
+ let commandId = "cmd_" + aPartId + "Toggle";
+ let labelId = "perm-" + aPartId + "-label";
+ let radiogroupId = aPartId + "RadioGroup";
+
+ let command = document.createElement("command");
+ command.setAttribute("id", commandId);
+ command.setAttribute("oncommand", "onRadioClick('" + aPartId + "');");
+ document.getElementById("pageInfoCommandSet").appendChild(command);
+
+ let row = document.createElement("vbox");
+ row.setAttribute("id", rowId);
+ row.setAttribute("class", "permission");
+
+ let label = document.createElement("label");
+ label.setAttribute("id", labelId);
+ label.setAttribute("control", radiogroupId);
+ label.setAttribute("value", SitePermissions.getPermissionLabel(aPartId));
+ label.setAttribute("class", "permissionLabel");
+ row.appendChild(label);
+
+ let controls = document.createElement("hbox");
+ controls.setAttribute("role", "group");
+ controls.setAttribute("aria-labelledby", labelId);
+
+ let checkbox = document.createElement("checkbox");
+ checkbox.setAttribute("id", aPartId + "Def");
+ checkbox.setAttribute("oncommand", "onCheckboxClick('" + aPartId + "');");
+ checkbox.setAttribute("label", gBundle.getString("permissions.useDefault"));
+ controls.appendChild(checkbox);
+
+ let spacer = document.createElement("spacer");
+ spacer.setAttribute("flex", "1");
+ controls.appendChild(spacer);
+
+ let radiogroup = document.createElement("radiogroup");
+ radiogroup.setAttribute("id", radiogroupId);
+ radiogroup.setAttribute("orient", "horizontal");
+ for (let state of SitePermissions.getAvailableStates(aPartId)) {
+ let radio = document.createElement("radio");
+ radio.setAttribute("id", aPartId + "#" + state);
+ radio.setAttribute("label", SitePermissions.getStateLabel(aPartId, state));
+ radio.setAttribute("command", commandId);
+ radiogroup.appendChild(radio);
+ }
+ controls.appendChild(radiogroup);
+
+ row.appendChild(controls);
+
+ document.getElementById("permList").appendChild(row);
+}
+
+function onCheckboxClick(aPartId)
+{
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var checkbox = document.getElementById(aPartId + "Def");
+ if (checkbox.checked) {
+ SitePermissions.remove(gPermURI, aPartId);
+ command.setAttribute("disabled", "true");
+ var perm = SitePermissions.getDefault(aPartId);
+ setRadioState(aPartId, perm);
+ }
+ else {
+ onRadioClick(aPartId);
+ command.removeAttribute("disabled");
+ }
+}
+
+function onPluginRadioClick(aEvent) {
+ onRadioClick(aEvent.originalTarget.getAttribute("id").split('#')[0]);
+}
+
+function onRadioClick(aPartId)
+{
+ var radioGroup = document.getElementById(aPartId + "RadioGroup");
+ var id = radioGroup.selectedItem.id;
+ var permission = id.split('#')[1];
+ SitePermissions.set(gPermURI, aPartId, permission);
+}
+
+function setRadioState(aPartId, aValue)
+{
+ var radio = document.getElementById(aPartId + "#" + aValue);
+ if (radio) {
+ radio.radioGroup.selectedItem = radio;
+ }
+}
+
+function initIndexedDBRow()
+{
+ let row = document.getElementById("perm-indexedDB-row");
+ let extras = document.getElementById("perm-indexedDB-extras");
+
+ row.appendChild(extras);
+
+ var quotaManagerService =
+ Components.classes["@mozilla.org/dom/quota-manager-service;1"]
+ .getService(nsIQuotaManagerService);
+ gUsageRequest =
+ quotaManagerService.getUsageForPrincipal(gPermPrincipal,
+ onIndexedDBUsageCallback);
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value = "";
+ status.setAttribute("hidden", "true");
+ button.setAttribute("hidden", "true");
+}
+
+function onIndexedDBClear()
+{
+ Components.classes["@mozilla.org/dom/quota-manager-service;1"]
+ .getService(nsIQuotaManagerService)
+ .clearStoragesForPrincipal(gPermPrincipal);
+
+ Components.classes["@mozilla.org/serviceworkers/manager;1"]
+ .getService(Components.interfaces.nsIServiceWorkerManager)
+ .removeAndPropagate(gPermURI.host);
+
+ SitePermissions.remove(gPermURI, "indexedDB");
+ initIndexedDBRow();
+}
+
+function onIndexedDBUsageCallback(request)
+{
+ let uri = request.principal.URI;
+ if (!uri.equals(gPermURI)) {
+ throw new Error("Callback received for bad URI: " + uri);
+ }
+
+ let usage = request.result.usage;
+ if (usage) {
+ if (!("DownloadUtils" in window)) {
+ Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+ }
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value =
+ gBundle.getFormattedString("indexedDBUsage",
+ DownloadUtils.convertByteUnits(usage));
+ status.removeAttribute("hidden");
+ button.removeAttribute("hidden");
+ }
+}
+
+function fillInPluginPermissionTemplate(aPluginName, aPermissionString) {
+ let permPluginTemplate = document.getElementById("permPluginTemplate").cloneNode(true);
+ permPluginTemplate.setAttribute("permString", aPermissionString);
+ let attrs = [
+ [ ".permPluginTemplateLabel", "value", aPluginName ],
+ [ ".permPluginTemplateRadioGroup", "id", aPermissionString + "RadioGroup" ],
+ [ ".permPluginTemplateRadioDefault", "id", aPermissionString + "#0" ],
+ [ ".permPluginTemplateRadioAsk", "id", aPermissionString + "#3" ],
+ [ ".permPluginTemplateRadioAllow", "id", aPermissionString + "#1" ],
+ [ ".permPluginTemplateRadioBlock", "id", aPermissionString + "#2" ]
+ ];
+
+ for (let attr of attrs) {
+ permPluginTemplate.querySelector(attr[0]).setAttribute(attr[1], attr[2]);
+ }
+
+ return permPluginTemplate;
+}
+
+function clearPluginPermissionTemplate() {
+ let permPluginTemplate = document.getElementById("permPluginTemplate");
+ permPluginTemplate.hidden = true;
+ permPluginTemplate.removeAttribute("permString");
+ document.querySelector(".permPluginTemplateLabel").removeAttribute("value");
+ document.querySelector(".permPluginTemplateRadioGroup").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAsk").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAllow").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioBlock").removeAttribute("id");
+}
+
+function initPluginsRow() {
+ let vulnerableLabel = document.getElementById("browserBundle").getString("pluginActivateVulnerable.label");
+ let pluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+ let permissionMap = new Map();
+
+ for (let plugin of pluginHost.getPluginTags()) {
+ if (plugin.disabled) {
+ continue;
+ }
+ for (let mimeType of plugin.getMimeTypes()) {
+ let permString = pluginHost.getPermissionStringForType(mimeType);
+ if (!permissionMap.has(permString)) {
+ let name = BrowserUtils.makeNicePluginName(plugin.name);
+ if (permString.startsWith("plugin-vulnerable:")) {
+ name += " \u2014 " + vulnerableLabel;
+ }
+ permissionMap.set(permString, name);
+ }
+ }
+ }
+
+ let entries = Array.from(permissionMap, item => ({ name: item[1], permission: item[0] }));
+
+ entries.sort(function(a, b) {
+ return a.name.localeCompare(b.name);
+ });
+
+ let permissionEntries = entries.map(p => fillInPluginPermissionTemplate(p.name, p.permission));
+
+ let permPluginsRow = document.getElementById("perm-plugins-row");
+ clearPluginPermissionTemplate();
+ if (permissionEntries.length < 1) {
+ permPluginsRow.hidden = true;
+ return;
+ }
+
+ for (let permissionEntry of permissionEntries) {
+ permPluginsRow.appendChild(permissionEntry);
+ }
+
+ setPluginsRadioState();
+}
+
+function setPluginsRadioState() {
+ let box = document.getElementById("perm-plugins-row");
+ for (let permissionEntry of box.childNodes) {
+ if (permissionEntry.hasAttribute("permString")) {
+ let permString = permissionEntry.getAttribute("permString");
+ let permission = SitePermissions.get(gPermURI, permString);
+ setRadioState(permString, permission);
+ }
+ }
+}
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js
new file mode 100644
index 000000000..5295a8fe6
--- /dev/null
+++ b/browser/base/content/pageinfo/security.js
@@ -0,0 +1,388 @@
+/* -*- 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/BrowserUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+var security = {
+ init: function(uri, windowInfo) {
+ this.uri = uri;
+ this.windowInfo = windowInfo;
+ },
+
+ // Display the server certificate (static)
+ viewCert : function () {
+ var cert = security._cert;
+ viewCertHelper(window, cert);
+ },
+
+ _getSecurityInfo : function() {
+ const nsISSLStatusProvider = Components.interfaces.nsISSLStatusProvider;
+ const nsISSLStatus = Components.interfaces.nsISSLStatus;
+
+ // We don't have separate info for a frame, return null until further notice
+ // (see bug 138479)
+ if (!this.windowInfo.isTopWindow)
+ return null;
+
+ var hostName = this.windowInfo.hostName;
+
+ var ui = security._getSecurityUI();
+ if (!ui)
+ return null;
+
+ var isBroken =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_BROKEN);
+ var isMixed =
+ (ui.state & (Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
+ Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ var isInsecure =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_INSECURE);
+ var isEV =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
+ ui.QueryInterface(nsISSLStatusProvider);
+ var status = ui.SSLStatus;
+
+ if (!isInsecure && status) {
+ status.QueryInterface(nsISSLStatus);
+ var cert = status.serverCert;
+ var issuerName =
+ this.mapIssuerOrganization(cert.issuerOrganization) || cert.issuerName;
+
+ var retval = {
+ hostName : hostName,
+ cAName : issuerName,
+ encryptionAlgorithm : undefined,
+ encryptionStrength : undefined,
+ version: undefined,
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : cert,
+ certificateTransparency : undefined
+ };
+
+ var version;
+ try {
+ retval.encryptionAlgorithm = status.cipherName;
+ retval.encryptionStrength = status.secretKeyLength;
+ version = status.protocolVersion;
+ }
+ catch (e) {
+ }
+
+ switch (version) {
+ case nsISSLStatus.SSL_VERSION_3:
+ retval.version = "SSL 3";
+ break;
+ case nsISSLStatus.TLS_VERSION_1:
+ retval.version = "TLS 1.0";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_1:
+ retval.version = "TLS 1.1";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_2:
+ retval.version = "TLS 1.2"
+ break;
+ case nsISSLStatus.TLS_VERSION_1_3:
+ retval.version = "TLS 1.3"
+ break;
+ }
+
+ // Select status text to display for Certificate Transparency.
+ switch (status.certificateTransparencyStatus) {
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
+ // CT compliance checks were not performed,
+ // do not display any status text.
+ retval.certificateTransparency = null;
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NONE:
+ retval.certificateTransparency = "None";
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_OK:
+ retval.certificateTransparency = "OK";
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG:
+ retval.certificateTransparency = "UnknownLog";
+ break;
+ case nsISSLStatus.CERTIFICATE_TRANSPARENCY_INVALID:
+ retval.certificateTransparency = "Invalid";
+ break;
+ }
+
+ return retval;
+ }
+ return {
+ hostName : hostName,
+ cAName : "",
+ encryptionAlgorithm : "",
+ encryptionStrength : 0,
+ version: "",
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : null,
+ certificateTransparency : null
+ };
+ },
+
+ // Find the secureBrowserUI object (if present)
+ _getSecurityUI : function() {
+ if (window.opener.gBrowser)
+ return window.opener.gBrowser.securityUI;
+ return null;
+ },
+
+ // Interface for mapping a certificate issuer organization to
+ // the value to be displayed.
+ // Bug 82017 - this implementation should be moved to pipnss C++ code
+ mapIssuerOrganization: function(name) {
+ if (!name) return null;
+
+ if (name == "RSA Data Security, Inc.") return "Verisign, Inc.";
+
+ // No mapping required
+ return name;
+ },
+
+ /**
+ * Open the cookie manager window
+ */
+ viewCookies : function()
+ {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("Browser:Cookies");
+ var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"].
+ getService(Components.interfaces.nsIEffectiveTLDService);
+
+ var eTLD;
+ try {
+ eTLD = eTLDService.getBaseDomain(this.uri);
+ }
+ catch (e) {
+ // getBaseDomain will fail if the host is an IP address or is empty
+ eTLD = this.uri.asciiHost;
+ }
+
+ if (win) {
+ win.gCookiesWindow.setFilter(eTLD);
+ win.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/cookies.xul",
+ "Browser:Cookies", "", {filterString : eTLD});
+ },
+
+ /**
+ * Open the login manager window
+ */
+ viewPasswords : function() {
+ LoginHelper.openPasswordManager(window, this._getSecurityInfo().hostName);
+ },
+
+ _cert : null
+};
+
+function securityOnLoad(uri, windowInfo) {
+ security.init(uri, windowInfo);
+
+ var info = security._getSecurityInfo();
+ if (!info) {
+ document.getElementById("securityTab").hidden = true;
+ return;
+ }
+ document.getElementById("securityTab").hidden = false;
+
+ const pageInfoBundle = document.getElementById("pageinfobundle");
+
+ /* Set Identity section text */
+ setText("security-identity-domain-value", info.hostName);
+
+ var owner, verifier;
+ if (info.cert && !info.isBroken) {
+ // Try to pull out meaningful values. Technically these fields are optional
+ // so we'll employ fallbacks where appropriate. The EV spec states that Org
+ // fields must be specified for subject and issuer so that case is simpler.
+ if (info.isEV) {
+ owner = info.cert.organization;
+ verifier = security.mapIssuerOrganization(info.cAName);
+ }
+ else {
+ // Technically, a non-EV cert might specify an owner in the O field or not,
+ // depending on the CA's issuing policies. However we don't have any programmatic
+ // way to tell those apart, and no policy way to establish which organization
+ // vetting standards are good enough (that's what EV is for) so we default to
+ // treating these certs as domain-validated only.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = security.mapIssuerOrganization(info.cAName ||
+ info.cert.issuerCommonName ||
+ info.cert.issuerName);
+ }
+ }
+ else {
+ // We don't have valid identity credentials.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = pageInfoBundle.getString("notset");
+ }
+
+ setText("security-identity-owner-value", owner);
+ setText("security-identity-verifier-value", verifier);
+
+ /* Manage the View Cert button*/
+ var viewCert = document.getElementById("security-view-cert");
+ if (info.cert) {
+ security._cert = info.cert;
+ viewCert.collapsed = false;
+ }
+ else
+ viewCert.collapsed = true;
+
+ /* Set Privacy & History section text */
+ var yesStr = pageInfoBundle.getString("yes");
+ var noStr = pageInfoBundle.getString("no");
+
+ setText("security-privacy-cookies-value",
+ hostHasCookies(uri) ? yesStr : noStr);
+ setText("security-privacy-passwords-value",
+ realmHasPasswords(uri) ? yesStr : noStr);
+
+ var visitCount = previousVisitCount(info.hostName);
+ if (visitCount > 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getFormattedString("securityNVisits", [visitCount.toLocaleString()]));
+ }
+ else if (visitCount == 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getString("securityOneVisit"));
+ }
+ else {
+ setText("security-privacy-history-value", noStr);
+ }
+
+ /* Set the Technical Detail section messages */
+ const pkiBundle = document.getElementById("pkiBundle");
+ var hdr;
+ var msg1;
+ var msg2;
+
+ if (info.isBroken) {
+ if (info.isMixed) {
+ hdr = pkiBundle.getString("pageInfo_MixedContent");
+ msg1 = pkiBundle.getString("pageInfo_MixedContent2");
+ } else {
+ hdr = pkiBundle.getFormattedString("pageInfo_BrokenEncryption",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ msg1 = pkiBundle.getString("pageInfo_WeakCipher");
+ }
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ else if (info.encryptionStrength > 0) {
+ hdr = pkiBundle.getFormattedString("pageInfo_EncryptionWithBitsAndProtocol",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
+ security._cert = info.cert;
+ }
+ else {
+ hdr = pkiBundle.getString("pageInfo_NoEncryption");
+ if (info.hostName != null)
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [info.hostName]);
+ else
+ msg1 = pkiBundle.getString("pageInfo_Privacy_None4");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ setText("security-technical-shortform", hdr);
+ setText("security-technical-longform1", msg1);
+ setText("security-technical-longform2", msg2);
+
+ const ctStatus =
+ document.getElementById("security-technical-certificate-transparency");
+ if (info.certificateTransparency) {
+ ctStatus.hidden = false;
+ ctStatus.value = pkiBundle.getString(
+ "pageInfo_CertificateTransparency_" + info.certificateTransparency);
+ } else {
+ ctStatus.hidden = true;
+ }
+}
+
+function setText(id, value)
+{
+ var element = document.getElementById(id);
+ if (!element)
+ return;
+ if (element.localName == "textbox" || element.localName == "label")
+ element.value = value;
+ else {
+ if (element.hasChildNodes())
+ element.removeChild(element.firstChild);
+ var textNode = document.createTextNode(value);
+ element.appendChild(textNode);
+ }
+}
+
+function viewCertHelper(parent, cert)
+{
+ if (!cert)
+ return;
+
+ var cd = Components.classes[CERTIFICATEDIALOGS_CONTRACTID].getService(nsICertificateDialogs);
+ cd.viewCert(parent, cert);
+}
+
+/**
+ * Return true iff we have cookies for uri
+ */
+function hostHasCookies(uri) {
+ var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager2);
+
+ return cookieManager.countCookiesFromHost(uri.asciiHost) > 0;
+}
+
+/**
+ * Return true iff realm (proto://host:port) (extracted from uri) has
+ * saved passwords
+ */
+function realmHasPasswords(uri) {
+ var passwordManager = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ return passwordManager.countLogins(uri.prePath, "", "") > 0;
+}
+
+/**
+ * Return the number of previous visits recorded for host before today.
+ *
+ * @param host - the domain name to look for in history
+ */
+function previousVisitCount(host, endTimeReference) {
+ if (!host)
+ return false;
+
+ var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Components.interfaces.nsINavHistoryService);
+
+ var options = historyService.getNewQueryOptions();
+ options.resultType = options.RESULTS_AS_VISIT;
+
+ // Search for visits to this host before today
+ var query = historyService.getNewQuery();
+ query.endTimeReference = query.TIME_RELATIVE_TODAY;
+ query.endTime = 0;
+ query.domain = host;
+
+ var result = historyService.executeQuery(query, options);
+ result.root.containerOpen = true;
+ var cc = result.root.childCount;
+ result.root.containerOpen = false;
+ return cc;
+}
diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc
new file mode 100644
index 000000000..bdc2d0bd3
--- /dev/null
+++ b/browser/base/content/popup-notifications.inc
@@ -0,0 +1,81 @@
+# to be included inside a popupset element
+
+ <panel id="notification-popup"
+ type="arrow"
+ position="after_start"
+ hidden="true"
+ orient="vertical"
+ noautofocus="true"
+ role="alert"/>
+
+ <popupnotification id="webRTC-shareDevices-notification" hidden="true">
+ <popupnotificationcontent id="webRTC-selectCamera" orient="vertical">
+ <label value="&getUserMedia.selectCamera.label;"
+ accesskey="&getUserMedia.selectCamera.accesskey;"
+ control="webRTC-selectCamera-menulist"/>
+ <menulist id="webRTC-selectCamera-menulist">
+ <menupopup id="webRTC-selectCamera-menupopup"/>
+ </menulist>
+ </popupnotificationcontent>
+
+ <popupnotificationcontent id="webRTC-selectWindowOrScreen" orient="vertical">
+ <label id="webRTC-selectWindow-label"
+ control="webRTC-selectWindow-menulist"/>
+ <menulist id="webRTC-selectWindow-menulist"
+ oncommand="webrtcUI.updateMainActionLabel(this);">
+ <menupopup id="webRTC-selectWindow-menupopup"/>
+ </menulist>
+ <description id="webRTC-all-windows-shared" hidden="true">&getUserMedia.allWindowsShared.message;</description>
+ </popupnotificationcontent>
+
+ <popupnotificationcontent id="webRTC-preview" hidden="true">
+ <html:video id="webRTC-previewVideo"/>
+ <vbox id="webRTC-previewWarningBox">
+ <spacer flex="1"/>
+ <description id="webRTC-previewWarning"/>
+ </vbox>
+ </popupnotificationcontent>
+
+ <popupnotificationcontent id="webRTC-selectMicrophone" orient="vertical">
+ <label value="&getUserMedia.selectMicrophone.label;"
+ accesskey="&getUserMedia.selectMicrophone.accesskey;"
+ control="webRTC-selectMicrophone-menulist"/>
+ <menulist id="webRTC-selectMicrophone-menulist">
+ <menupopup id="webRTC-selectMicrophone-menupopup"/>
+ </menulist>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="servicesInstall-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <!-- XXX bug 974146, tests are looking for this, can't remove yet. -->
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="password-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <textbox id="password-notification-username"/>
+ <textbox id="password-notification-password" type="password" show-content=""/>
+ <checkbox id="password-notification-visibilityToggle" hidden="true"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+
+ <popupnotification id="addon-progress-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <progressmeter id="addon-progress-notification-progressmeter"/>
+ <label id="addon-progress-notification-progresstext" crop="end"/>
+ </popupnotificationcontent>
+ <button id="addon-progress-cancel"
+ oncommand="this.parentNode.cancel();"/>
+ <button id="addon-progress-accept" disabled="true"/>
+ </popupnotification>
+
+ <popupnotification id="addon-install-confirmation-notification" hidden="true">
+ <popupnotificationcontent id="addon-install-confirmation-content" orient="vertical"/>
+ <button id="addon-install-confirmation-cancel"
+ oncommand="PopupNotifications.getNotification('addon-install-confirmation').remove();"/>
+ <button id="addon-install-confirmation-accept"
+ oncommand="gXPInstallObserver.acceptInstallation();
+ PopupNotifications.getNotification('addon-install-confirmation').remove();"/>
+ </popupnotification>
diff --git a/browser/base/content/report-phishing-overlay.xul b/browser/base/content/report-phishing-overlay.xul
new file mode 100644
index 000000000..712079f82
--- /dev/null
+++ b/browser/base/content/report-phishing-overlay.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % reportphishDTD SYSTEM "chrome://browser/locale/safebrowsing/report-phishing.dtd">
+%reportphishDTD;
+<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+%safebrowsingDTD;
+]>
+
+<overlay id="reportPhishingMenuOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="reportPhishingBroadcaster" disabled="true"/>
+ <broadcaster id="reportPhishingErrorBroadcaster" disabled="true"/>
+ </broadcasterset>
+ <menupopup id="menu_HelpPopup">
+ <menuitem id="menu_HelpPopup_reportPhishingtoolmenu"
+ label="&reportDeceptiveSiteMenu.title;"
+ accesskey="&reportDeceptiveSiteMenu.accesskey;"
+ insertbefore="aboutSeparator"
+ observes="reportPhishingBroadcaster"
+ oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu"
+ label="&safeb.palm.notdeceptive.label;"
+ accesskey="&safeb.palm.notdeceptive.accesskey;"
+ insertbefore="aboutSeparator"
+ observes="reportPhishingErrorBroadcaster"
+ oncommand="openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menupopup>
+</overlay>
diff --git a/browser/base/content/safeMode.css b/browser/base/content/safeMode.css
new file mode 100644
index 000000000..4f093a452
--- /dev/null
+++ b/browser/base/content/safeMode.css
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#resetProfileFooter {
+ font-weight: bold;
+}
+
diff --git a/browser/base/content/safeMode.js b/browser/base/content/safeMode.js
new file mode 100644
index 000000000..7f34c2c58
--- /dev/null
+++ b/browser/base/content/safeMode.js
@@ -0,0 +1,82 @@
+/* -*- 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;
+
+const appStartup = Services.startup;
+
+Cu.import("resource://gre/modules/ResetProfile.jsm");
+
+var defaultToReset = false;
+
+function restartApp() {
+ appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+}
+
+function resetProfile() {
+ // Set the reset profile environment variable.
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ env.set("MOZ_RESET_PROFILE_RESTART", "1");
+}
+
+function showResetDialog() {
+ // Prompt the user to confirm the reset.
+ let retVals = {
+ reset: false,
+ };
+ window.openDialog("chrome://global/content/resetProfile.xul", null,
+ "chrome,modal,centerscreen,titlebar,dialog=yes", retVals);
+ if (!retVals.reset)
+ return;
+ resetProfile();
+ restartApp();
+}
+
+function onDefaultButton() {
+ if (defaultToReset) {
+ // Restart to reset the profile.
+ resetProfile();
+ restartApp();
+ // Return false to prevent starting into safe mode while restarting.
+ return false;
+ }
+ // Continue in safe mode. No restart needed.
+ return true;
+}
+
+function onCancel() {
+ appStartup.quit(appStartup.eForceQuit);
+}
+
+function onExtra1() {
+ if (defaultToReset) {
+ // Continue in safe mode
+ window.close();
+ return true;
+ }
+ // The reset dialog will handle starting the reset process if the user confirms.
+ showResetDialog();
+ return false;
+}
+
+function onLoad() {
+ if (appStartup.automaticSafeModeNecessary) {
+ document.getElementById("autoSafeMode").hidden = false;
+ document.getElementById("safeMode").hidden = true;
+ if (ResetProfile.resetSupported()) {
+ document.getElementById("resetProfile").hidden = false;
+ } else {
+ // Hide the reset button is it's not supported.
+ document.documentElement.getButton("extra1").hidden = true;
+ }
+ } else if (!ResetProfile.resetSupported()) {
+ // Hide the reset button and text if it's not supported.
+ document.documentElement.getButton("extra1").hidden = true;
+ document.getElementById("resetProfileInstead").hidden = true;
+ }
+}
diff --git a/browser/base/content/safeMode.xul b/browser/base/content/safeMode.xul
new file mode 100644
index 000000000..a94de5fba
--- /dev/null
+++ b/browser/base/content/safeMode.xul
@@ -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 prefwindow [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % safeModeDTD SYSTEM "chrome://browser/locale/safeMode.dtd" >
+%safeModeDTD;
+<!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd" >
+%resetProfileDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/content/safeMode.css"?>
+
+<dialog id="safeModeDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&safeModeDialog.title;"
+ buttons="accept,extra1"
+ buttonlabelaccept="&startSafeMode.label;"
+ buttonlabelextra1="&refreshProfile.label;"
+ maxwidth="&window.maxWidth;"
+ ondialogaccept="return onDefaultButton()"
+ ondialogcancel="onCancel();"
+ ondialogextra1="return onExtra1()"
+ onload="onLoad()">
+
+ <script type="application/javascript" src="chrome://global/content/resetProfile.js"/>
+ <script type="application/javascript" src="chrome://browser/content/safeMode.js"/>
+
+ <vbox id="autoSafeMode" hidden="true">
+ <description>&autoSafeModeDescription3.label;</description>
+ </vbox>
+
+ <vbox id="safeMode">
+ <label>&safeModeDescription3.label;</label>
+ <separator class="thin"/>
+ <label>&safeModeDescription4.label;</label>
+ <separator class="thin"/>
+ <label id="resetProfileInstead">&refreshProfileInstead.label;</label>
+ </vbox>
+
+ <vbox id="resetProfile" hidden="true">
+ <label id="resetProfileInstead">&refreshProfileInstead.label;</label>
+ </vbox>
+
+ <separator class="thin"/>
+</dialog>
diff --git a/browser/base/content/sanitize.js b/browser/base/content/sanitize.js
new file mode 100644
index 000000000..841376580
--- /dev/null
+++ b/browser/base/content/sanitize.js
@@ -0,0 +1,910 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+ "resource://gre/modules/TelemetryStopwatch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+ "resource://gre/modules/Timer.jsm");
+
+
+XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager");
+XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
+ "@mozilla.org/dom/quota-manager-service;1",
+ "nsIQuotaManagerService");
+
+var {classes: Cc, interfaces: Ci, results: Cr} = Components;
+
+/**
+ * A number of iterations after which to yield time back
+ * to the system.
+ */
+const YIELD_PERIOD = 10;
+
+function Sanitizer() {
+}
+Sanitizer.prototype = {
+ // warning to the caller: this one may raise an exception (e.g. bug #265028)
+ clearItem: function (aItemName)
+ {
+ this.items[aItemName].clear();
+ },
+
+ prefDomain: "",
+
+ getNameFromPreference: function (aPreferenceName)
+ {
+ return aPreferenceName.substr(this.prefDomain.length);
+ },
+
+ /**
+ * Deletes privacy sensitive data in a batch, according to user preferences.
+ * Returns a promise which is resolved if no errors occurred. If an error
+ * occurs, a message is reported to the console and all other items are still
+ * cleared before the promise is finally rejected.
+ *
+ * @param [optional] itemsToClear
+ * Array of items to be cleared. if specified only those
+ * items get cleared, irrespectively of the preference settings.
+ * @param [optional] options
+ * Object whose properties are options for this sanitization.
+ * TODO (bug 1167238) document options here.
+ */
+ sanitize: Task.async(function*(itemsToClear = null, options = {}) {
+ let progress = options.progress || {};
+ let promise = this._sanitize(itemsToClear, progress);
+
+ // Depending on preferences, the sanitizer may perform asynchronous
+ // work before it starts cleaning up the Places database (e.g. closing
+ // windows). We need to make sure that the connection to that database
+ // hasn't been closed by the time we use it.
+ // Though, if this is a sanitize on shutdown, we already have a blocker.
+ if (!progress.isShutdown) {
+ let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Ci.nsPIPlacesDatabase)
+ .shutdownClient
+ .jsclient;
+ shutdownClient.addBlocker("sanitize.js: Sanitize",
+ promise,
+ {
+ fetchState: () => ({ progress })
+ }
+ );
+ }
+
+ try {
+ yield promise;
+ } finally {
+ Services.obs.notifyObservers(null, "sanitizer-sanitization-complete", "");
+ }
+ }),
+
+ _sanitize: Task.async(function*(aItemsToClear, progress = {}) {
+ let seenError = false;
+ let itemsToClear;
+ if (Array.isArray(aItemsToClear)) {
+ // Shallow copy the array, as we are going to modify
+ // it in place later.
+ itemsToClear = [...aItemsToClear];
+ } else {
+ let branch = Services.prefs.getBranch(this.prefDomain);
+ itemsToClear = Object.keys(this.items).filter(itemName => {
+ try {
+ return branch.getBoolPref(itemName);
+ } catch (ex) {
+ return false;
+ }
+ });
+ }
+
+ // Store the list of items to clear, in case we are killed before we
+ // get a chance to complete.
+ Preferences.set(Sanitizer.PREF_SANITIZE_IN_PROGRESS,
+ JSON.stringify(itemsToClear));
+
+ // Store the list of items to clear, for debugging/forensics purposes
+ for (let k of itemsToClear) {
+ progress[k] = "ready";
+ }
+
+ // Ensure open windows get cleared first, if they're in our list, so that they don't stick
+ // around in the recently closed windows list, and so we can cancel the whole thing
+ // if the user selects to keep a window open from a beforeunload prompt.
+ let openWindowsIndex = itemsToClear.indexOf("openWindows");
+ if (openWindowsIndex != -1) {
+ itemsToClear.splice(openWindowsIndex, 1);
+ yield this.items.openWindows.clear();
+ progress.openWindows = "cleared";
+ }
+
+ // Cache the range of times to clear
+ let range = null;
+ // If we ignore timespan, clear everything,
+ // otherwise, pick a range.
+ if (!this.ignoreTimespan) {
+ range = this.range || Sanitizer.getClearRange();
+ }
+
+ // For performance reasons we start all the clear tasks at once, then wait
+ // for their promises later.
+ // Some of the clear() calls may raise exceptions (for example bug 265028),
+ // we catch and store them, but continue to sanitize as much as possible.
+ // Callers should check returned errors and give user feedback
+ // about items that could not be sanitized
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
+
+ let annotateError = (name, ex) => {
+ progress[name] = "failed";
+ seenError = true;
+ console.error("Error sanitizing " + name, ex);
+ };
+
+ // Array of objects in form { name, promise }.
+ // `name` is the item's name and `promise` may be a promise, if the
+ // sanitization is asynchronous, or the function return value, otherwise.
+ let handles = [];
+ for (let itemName of itemsToClear) {
+ // Workaround for bug 449811.
+ let name = itemName;
+ let item = this.items[name];
+ try {
+ // Catch errors here, so later we can just loop through these.
+ handles.push({ name,
+ promise: item.clear(range)
+ .then(() => progress[name] = "cleared",
+ ex => annotateError(name, ex))
+ });
+ } catch (ex) {
+ annotateError(name, ex);
+ }
+ }
+ for (let handle of handles) {
+ progress[handle.name] = "blocking";
+ yield handle.promise;
+ }
+
+ // Sanitization is complete.
+ TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
+ // Reset the inProgress preference since we were not killed during
+ // sanitization.
+ Preferences.reset(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
+ progress = {};
+ if (seenError) {
+ throw new Error("Error sanitizing");
+ }
+ }),
+
+ // Time span only makes sense in certain cases. Consumers who want
+ // to only clear some private data can opt in by setting this to false,
+ // and can optionally specify a specific range. If timespan is not ignored,
+ // and range is not set, sanitize() will use the value of the timespan
+ // pref to determine a range
+ ignoreTimespan : true,
+ range : null,
+
+ items: {
+ cache: {
+ clear: Task.async(function* (range) {
+ let seenException;
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
+
+ try {
+ // Cache doesn't consult timespan, nor does it have the
+ // facility for timespan-based eviction. Wipe it.
+ let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ cache.clear();
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ try {
+ let imageCache = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .getImgCacheForDocument(null);
+ imageCache.clearCache(false); // true=chrome, false=content
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
+ if (seenException) {
+ throw seenException;
+ }
+ })
+ },
+
+ cookies: {
+ clear: Task.async(function* (range) {
+ let seenException;
+ let yieldCounter = 0;
+ let refObj = {};
+
+ // Clear cookies.
+ TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
+ try {
+ let cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ if (range) {
+ // Iterate through the cookies and delete any created after our cutoff.
+ let cookiesEnum = cookieMgr.enumerator;
+ while (cookiesEnum.hasMoreElements()) {
+ let cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+
+ if (cookie.creationTime > range[0]) {
+ // This cookie was created after our cutoff, clear it
+ cookieMgr.remove(cookie.host, cookie.name, cookie.path,
+ false, cookie.originAttributes);
+
+ if (++yieldCounter % YIELD_PERIOD == 0) {
+ yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
+ }
+ }
+ }
+ }
+ else {
+ // Remove everything
+ cookieMgr.removeAll();
+ yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
+ }
+ } catch (ex) {
+ seenException = ex;
+ } finally {
+ TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
+ }
+
+ // Clear deviceIds. Done asynchronously (returns before complete).
+ try {
+ let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
+ .getService(Ci.nsIMediaManagerService);
+ mediaMgr.sanitizeDeviceIds(range && range[0]);
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ // Clear plugin data.
+ // As evidenced in bug 1253204, clearing plugin data can sometimes be
+ // very, very long, for mysterious reasons. Unfortunately, this is not
+ // something actionable by Mozilla, so crashing here serves no purpose.
+ //
+ // For this reason, instead of waiting for sanitization to always
+ // complete, we introduce a soft timeout. Once this timeout has
+ // elapsed, we proceed with the shutdown of Firefox.
+ let promiseClearPluginCookies;
+ try {
+ // We don't want to wait for this operation to complete...
+ promiseClearPluginCookies = this.promiseClearPluginCookies(range);
+
+ // ... at least, not for more than 10 seconds.
+ yield Promise.race([
+ promiseClearPluginCookies,
+ new Promise(resolve => setTimeout(resolve, 10000 /* 10 seconds */))
+ ]);
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ // Detach waiting for plugin cookies to be cleared.
+ promiseClearPluginCookies.catch(() => {
+ // If this exception is raised before the soft timeout, it
+ // will appear in `seenException`. Otherwise, it's too late
+ // to do anything about it.
+ });
+
+ if (seenException) {
+ throw seenException;
+ }
+ }),
+
+ promiseClearPluginCookies: Task.async(function* (range) {
+ const FLAG_CLEAR_ALL = Ci.nsIPluginHost.FLAG_CLEAR_ALL;
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+ // Determine age range in seconds. (-1 means clear all.) We don't know
+ // that range[1] is actually now, so we compute age range based
+ // on the lower bound. If range results in a negative age, do nothing.
+ let age = range ? (Date.now() / 1000 - range[0] / 1000000) : -1;
+ if (!range || age >= 0) {
+ let tags = ph.getPluginTags();
+ for (let tag of tags) {
+ let refObj = {};
+ let probe = "";
+ if (/\bFlash\b/.test(tag.name)) {
+ probe = tag.loaded ? "FX_SANITIZE_LOADED_FLASH"
+ : "FX_SANITIZE_UNLOADED_FLASH";
+ TelemetryStopwatch.start(probe, refObj);
+ }
+ try {
+ let rv = yield new Promise(resolve =>
+ ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, age, resolve)
+ );
+ // If the plugin doesn't support clearing by age, clear everything.
+ if (rv == Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
+ yield new Promise(resolve =>
+ ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, resolve)
+ );
+ }
+ if (probe) {
+ TelemetryStopwatch.finish(probe, refObj);
+ }
+ } catch (ex) {
+ // Ignore errors from plug-ins
+ if (probe) {
+ TelemetryStopwatch.cancel(probe, refObj);
+ }
+ }
+ }
+ }
+ })
+ },
+
+ offlineApps: {
+ clear: Task.async(function* (range) {
+ // AppCache
+ Components.utils.import("resource:///modules/offlineAppCache.jsm");
+ // This doesn't wait for the cleanup to be complete.
+ OfflineAppCacheHelper.clear();
+
+ // LocalStorage
+ Services.obs.notifyObservers(null, "extension:purge-localStorage", null);
+
+ // ServiceWorkers
+ let serviceWorkers = serviceWorkerManager.getAllRegistrations();
+ for (let i = 0; i < serviceWorkers.length; i++) {
+ let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+ let host = sw.principal.URI.host;
+ serviceWorkerManager.removeAndPropagate(host);
+ }
+
+ // QuotaManager
+ let promises = [];
+ yield new Promise(resolve => {
+ quotaManagerService.getUsage(request => {
+ if (request.resultCode != Cr.NS_OK) {
+ // We are probably shutting down. We don't want to propagate the
+ // error, rejecting the promise.
+ resolve();
+ return;
+ }
+
+ for (let item of request.result) {
+ let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
+ let uri = principal.URI;
+ if (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "file") {
+ promises.push(new Promise(r => {
+ let req = quotaManagerService.clearStoragesForPrincipal(principal, null, true);
+ req.callback = () => { r(); };
+ }));
+ }
+ }
+ resolve();
+ });
+ });
+
+ yield Promise.all(promises);
+ })
+ },
+
+ history: {
+ clear: Task.async(function* (range) {
+ let seenException;
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
+ try {
+ if (range) {
+ yield PlacesUtils.history.removeVisitsByFilter({
+ beginDate: new Date(range[0] / 1000),
+ endDate: new Date(range[1] / 1000)
+ });
+ } else {
+ // Remove everything.
+ yield PlacesUtils.history.clear();
+ }
+ } catch (ex) {
+ seenException = ex;
+ } finally {
+ TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
+ }
+
+ try {
+ let clearStartingTime = range ? String(range[0]) : "";
+ Services.obs.notifyObservers(null, "browser:purge-session-history", clearStartingTime);
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ try {
+ let predictor = Components.classes["@mozilla.org/network/predictor;1"]
+ .getService(Components.interfaces.nsINetworkPredictor);
+ predictor.reset();
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ if (seenException) {
+ throw seenException;
+ }
+ })
+ },
+
+ formdata: {
+ clear: Task.async(function* (range) {
+ let seenException;
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
+ try {
+ // Clear undo history of all searchBars
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let currentWindow = windows.getNext();
+ let currentDocument = currentWindow.document;
+ let searchBar = currentDocument.getElementById("searchbar");
+ if (searchBar)
+ searchBar.textbox.reset();
+ let tabBrowser = currentWindow.gBrowser;
+ if (!tabBrowser) {
+ // No tab browser? This means that it's too early during startup (typically,
+ // Session Restore hasn't completed yet). Since we don't have find
+ // bars at that stage and since Session Restore will not restore
+ // find bars further down during startup, we have nothing to clear.
+ continue;
+ }
+ for (let tab of tabBrowser.tabs) {
+ if (tabBrowser.isFindBarInitialized(tab))
+ tabBrowser.getFindBar(tab).clear();
+ }
+ // Clear any saved find value
+ tabBrowser._lastFindValue = "";
+ }
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ try {
+ let change = { op: "remove" };
+ if (range) {
+ [ change.firstUsedStart, change.firstUsedEnd ] = range;
+ }
+ yield new Promise(resolve => {
+ FormHistory.update(change, {
+ handleError(e) {
+ seenException = new Error("Error " + e.result + ": " + e.message);
+ },
+ handleCompletion() {
+ resolve();
+ }
+ });
+ });
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
+ if (seenException) {
+ throw seenException;
+ }
+ })
+ },
+
+ downloads: {
+ clear: Task.async(function* (range) {
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
+ try {
+ let filterByTime = null;
+ if (range) {
+ // Convert microseconds back to milliseconds for date comparisons.
+ let rangeBeginMs = range[0] / 1000;
+ let rangeEndMs = range[1] / 1000;
+ filterByTime = download => download.startTime >= rangeBeginMs &&
+ download.startTime <= rangeEndMs;
+ }
+
+ // Clear all completed/cancelled downloads
+ let list = yield Downloads.getList(Downloads.ALL);
+ list.removeFinished(filterByTime);
+ } finally {
+ TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
+ }
+ })
+ },
+
+ sessions: {
+ clear: Task.async(function* (range) {
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
+
+ try {
+ // clear all auth tokens
+ let sdr = Components.classes["@mozilla.org/security/sdr;1"]
+ .getService(Components.interfaces.nsISecretDecoderRing);
+ sdr.logoutAndTeardown();
+
+ // clear FTP and plain HTTP auth sessions
+ Services.obs.notifyObservers(null, "net:clear-active-logins", null);
+ } finally {
+ TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS", refObj);
+ }
+ })
+ },
+
+ siteSettings: {
+ clear: Task.async(function* (range) {
+ let seenException;
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
+
+ let startDateMS = range ? range[0] / 1000 : null;
+
+ try {
+ // Clear site-specific permissions like "Allow this site to open popups"
+ // we ignore the "end" range and hope it is now() - none of the
+ // interfaces used here support a true range anyway.
+ if (startDateMS == null) {
+ Services.perms.removeAll();
+ } else {
+ Services.perms.removeAllSince(startDateMS);
+ }
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ try {
+ // Clear site-specific settings like page-zoom level
+ let cps = Components.classes["@mozilla.org/content-pref/service;1"]
+ .getService(Components.interfaces.nsIContentPrefService2);
+ if (startDateMS == null) {
+ cps.removeAllDomains(null);
+ } else {
+ cps.removeAllDomainsSince(startDateMS, null);
+ }
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ try {
+ // Clear site security settings - no support for ranges in this
+ // interface either, so we clearAll().
+ let sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+ sss.clearAll();
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ // Clear all push notification subscriptions
+ try {
+ yield new Promise((resolve, reject) => {
+ let push = Cc["@mozilla.org/push/Service;1"]
+ .getService(Ci.nsIPushService);
+ push.clearForDomain("*", status => {
+ if (Components.isSuccessCode(status)) {
+ resolve();
+ } else {
+ reject(new Error("Error clearing push subscriptions: " +
+ status));
+ }
+ });
+ });
+ } catch (ex) {
+ seenException = ex;
+ }
+
+ TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
+ if (seenException) {
+ throw seenException;
+ }
+ })
+ },
+
+ openWindows: {
+ privateStateForNewWindow: "non-private",
+ _canCloseWindow: function(aWindow) {
+ if (aWindow.CanCloseWindow()) {
+ // We already showed PermitUnload for the window, so let's
+ // make sure we don't do it again when we actually close the
+ // window.
+ aWindow.skipNextCanClose = true;
+ return true;
+ }
+ return false;
+ },
+ _resetAllWindowClosures: function(aWindowList) {
+ for (let win of aWindowList) {
+ win.skipNextCanClose = false;
+ }
+ },
+ clear: Task.async(function* () {
+ // NB: this closes all *browser* windows, not other windows like the library, about window,
+ // browser console, etc.
+
+ // Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
+ // dialogs
+ let existingWindow = Services.appShell.hiddenDOMWindow;
+ let startDate = existingWindow.performance.now();
+
+ // First check if all these windows are OK with being closed:
+ let windowEnumerator = Services.wm.getEnumerator("navigator:browser");
+ let windowList = [];
+ while (windowEnumerator.hasMoreElements()) {
+ let someWin = windowEnumerator.getNext();
+ windowList.push(someWin);
+ // If someone says "no" to a beforeunload prompt, we abort here:
+ if (!this._canCloseWindow(someWin)) {
+ this._resetAllWindowClosures(windowList);
+ throw new Error("Sanitize could not close windows: cancelled by user");
+ }
+
+ // ...however, beforeunload prompts spin the event loop, and so the code here won't get
+ // hit until the prompt has been dismissed. If more than 1 minute has elapsed since we
+ // started prompting, stop, because the user might not even remember initiating the
+ // 'forget', and the timespans will be all wrong by now anyway:
+ if (existingWindow.performance.now() > (startDate + 60 * 1000)) {
+ this._resetAllWindowClosures(windowList);
+ throw new Error("Sanitize could not close windows: timeout");
+ }
+ }
+
+ // If/once we get here, we should actually be able to close all windows.
+
+ let refObj = {};
+ TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS", refObj);
+
+ // First create a new window. We do this first so that on non-mac, we don't
+ // accidentally close the app by closing all the windows.
+ let handler = Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler);
+ let defaultArgs = handler.defaultArgs;
+ let features = "chrome,all,dialog=no," + this.privateStateForNewWindow;
+ let newWindow = existingWindow.openDialog("chrome://browser/content/", "_blank",
+ features, defaultArgs);
+
+ let onFullScreen = null;
+ if (AppConstants.platform == "macosx") {
+ onFullScreen = function(e) {
+ newWindow.removeEventListener("fullscreen", onFullScreen);
+ let docEl = newWindow.document.documentElement;
+ let sizemode = docEl.getAttribute("sizemode");
+ if (!newWindow.fullScreen && sizemode == "fullscreen") {
+ docEl.setAttribute("sizemode", "normal");
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ return undefined;
+ }
+ newWindow.addEventListener("fullscreen", onFullScreen);
+ }
+
+ let promiseReady = new Promise(resolve => {
+ // Window creation and destruction is asynchronous. We need to wait
+ // until all existing windows are fully closed, and the new window is
+ // fully open, before continuing. Otherwise the rest of the sanitizer
+ // could run too early (and miss new cookies being set when a page
+ // closes) and/or run too late (and not have a fully-formed window yet
+ // in existence). See bug 1088137.
+ let newWindowOpened = false;
+ let onWindowOpened = function(subject, topic, data) {
+ if (subject != newWindow)
+ return;
+
+ Services.obs.removeObserver(onWindowOpened, "browser-delayed-startup-finished");
+ if (AppConstants.platform == "macosx") {
+ newWindow.removeEventListener("fullscreen", onFullScreen);
+ }
+ newWindowOpened = true;
+ // If we're the last thing to happen, invoke callback.
+ if (numWindowsClosing == 0) {
+ TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
+ resolve();
+ }
+ }
+
+ let numWindowsClosing = windowList.length;
+ let onWindowClosed = function() {
+ numWindowsClosing--;
+ if (numWindowsClosing == 0) {
+ Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
+ // If we're the last thing to happen, invoke callback.
+ if (newWindowOpened) {
+ TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
+ resolve();
+ }
+ }
+ }
+ Services.obs.addObserver(onWindowOpened, "browser-delayed-startup-finished", false);
+ Services.obs.addObserver(onWindowClosed, "xul-window-destroyed", false);
+ });
+
+ // Start the process of closing windows
+ while (windowList.length) {
+ windowList.pop().close();
+ }
+ newWindow.focus();
+ yield promiseReady;
+ })
+ },
+ }
+};
+
+// The preferences branch for the sanitizer.
+Sanitizer.PREF_DOMAIN = "privacy.sanitize.";
+// Whether we should sanitize on shutdown.
+Sanitizer.PREF_SANITIZE_ON_SHUTDOWN = "privacy.sanitize.sanitizeOnShutdown";
+// During a sanitization this is set to a json containing the array of items
+// being sanitized, then cleared once the sanitization is complete.
+// This allows to retry a sanitization on startup in case it was interrupted
+// by a crash.
+Sanitizer.PREF_SANITIZE_IN_PROGRESS = "privacy.sanitize.sanitizeInProgress";
+// Whether the previous shutdown sanitization completed successfully.
+// This is used to detect cases where we were supposed to sanitize on shutdown
+// but due to a crash we were unable to. In such cases there may not be any
+// sanitization in progress, cause we didn't have a chance to start it yet.
+Sanitizer.PREF_SANITIZE_DID_SHUTDOWN = "privacy.sanitize.didShutdownSanitize";
+
+// Time span constants corresponding to values of the privacy.sanitize.timeSpan
+// pref. Used to determine how much history to clear, for various items
+Sanitizer.TIMESPAN_EVERYTHING = 0;
+Sanitizer.TIMESPAN_HOUR = 1;
+Sanitizer.TIMESPAN_2HOURS = 2;
+Sanitizer.TIMESPAN_4HOURS = 3;
+Sanitizer.TIMESPAN_TODAY = 4;
+Sanitizer.TIMESPAN_5MIN = 5;
+Sanitizer.TIMESPAN_24HOURS = 6;
+
+// Return a 2 element array representing the start and end times,
+// in the uSec-since-epoch format that PRTime likes. If we should
+// clear everything, return null. Use ts if it is defined; otherwise
+// use the timeSpan pref.
+Sanitizer.getClearRange = function (ts) {
+ if (ts === undefined)
+ ts = Sanitizer.prefs.getIntPref("timeSpan");
+ if (ts === Sanitizer.TIMESPAN_EVERYTHING)
+ return null;
+
+ // PRTime is microseconds while JS time is milliseconds
+ var endDate = Date.now() * 1000;
+ switch (ts) {
+ case Sanitizer.TIMESPAN_5MIN :
+ var startDate = endDate - 300000000; // 5*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_HOUR :
+ startDate = endDate - 3600000000; // 1*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_2HOURS :
+ startDate = endDate - 7200000000; // 2*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_4HOURS :
+ startDate = endDate - 14400000000; // 4*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_TODAY :
+ var d = new Date(); // Start with today
+ d.setHours(0); // zero us back to midnight...
+ d.setMinutes(0);
+ d.setSeconds(0);
+ startDate = d.valueOf() * 1000; // convert to epoch usec
+ break;
+ case Sanitizer.TIMESPAN_24HOURS :
+ startDate = endDate - 86400000000; // 24*60*60*1000000
+ break;
+ default:
+ throw "Invalid time span for clear private data: " + ts;
+ }
+ return [startDate, endDate];
+};
+
+Sanitizer._prefs = null;
+Sanitizer.__defineGetter__("prefs", function()
+{
+ return Sanitizer._prefs ? Sanitizer._prefs
+ : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch(Sanitizer.PREF_DOMAIN);
+});
+
+// Shows sanitization UI
+Sanitizer.showUI = function(aParentWindow)
+{
+ let win = AppConstants.platform == "macosx" ?
+ null: // make this an app-modal window on Mac
+ aParentWindow;
+ Services.ww.openWindow(win,
+ "chrome://browser/content/sanitize.xul",
+ "Sanitize",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+};
+
+/**
+ * Deletes privacy sensitive data in a batch, optionally showing the
+ * sanitize UI, according to user preferences
+ */
+Sanitizer.sanitize = function(aParentWindow)
+{
+ Sanitizer.showUI(aParentWindow);
+};
+
+Sanitizer.onStartup = Task.async(function*() {
+ // Check if we were interrupted during the last shutdown sanitization.
+ let shutownSanitizationWasInterrupted =
+ Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false) &&
+ !Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
+
+ if (Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) {
+ // Reset the pref, so that if we crash before having a chance to
+ // sanitize on shutdown, we will do at the next startup.
+ // Flushing prefs has a cost, so do this only if necessary.
+ Preferences.reset(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
+ Services.prefs.savePrefFile(null);
+ }
+
+ // Make sure that we are triggered during shutdown.
+ let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Ci.nsPIPlacesDatabase)
+ .shutdownClient
+ .jsclient;
+ // We need to pass to sanitize() (through sanitizeOnShutdown) a state object
+ // that tracks the status of the shutdown blocker. This `progress` object
+ // will be updated during sanitization and reported with the crash in case of
+ // a shutdown timeout.
+ // We use the `options` argument to pass the `progress` object to sanitize().
+ let progress = { isShutdown: true };
+ shutdownClient.addBlocker("sanitize.js: Sanitize on shutdown",
+ () => sanitizeOnShutdown({ progress }),
+ {
+ fetchState: () => ({ progress })
+ }
+ );
+
+ // Check if Firefox crashed during a sanitization.
+ let lastInterruptedSanitization = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS, "");
+ if (lastInterruptedSanitization) {
+ let s = new Sanitizer();
+ // If the json is invalid this will just throw and reject the Task.
+ let itemsToClear = JSON.parse(lastInterruptedSanitization);
+ yield s.sanitize(itemsToClear);
+ } else if (shutownSanitizationWasInterrupted) {
+ // Otherwise, could be we were supposed to sanitize on shutdown but we
+ // didn't have a chance, due to an earlier crash.
+ // In such a case, just redo a shutdown sanitize now, during startup.
+ yield sanitizeOnShutdown();
+ }
+});
+
+var sanitizeOnShutdown = Task.async(function*(options = {}) {
+ if (!Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN)) {
+ return;
+ }
+ // Need to sanitize upon shutdown
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.clearOnShutdown.";
+ yield s.sanitize(null, options);
+ // We didn't crash during shutdown sanitization, so annotate it to avoid
+ // sanitizing again on startup.
+ Preferences.set(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true);
+ Services.prefs.savePrefFile(null);
+});
diff --git a/browser/base/content/sanitize.xul b/browser/base/content/sanitize.xul
new file mode 100644
index 000000000..c00c6cda7
--- /dev/null
+++ b/browser/base/content/sanitize.xul
@@ -0,0 +1,183 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/sanitizeDialog.css"?>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+#endif
+
+<?xml-stylesheet href="chrome://browser/content/sanitizeDialog.css"?>
+
+<!DOCTYPE prefwindow [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
+ %brandDTD;
+ %sanitizeDTD;
+]>
+
+<prefwindow id="SanitizeDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ dlgbuttons="accept,cancel"
+ title="&sanitizeDialog2.title;"
+ noneverythingtitle="&sanitizeDialog2.title;"
+ style="width: &sanitizeDialog2.width;;"
+ ondialogaccept="return gSanitizePromptDialog.sanitize();">
+
+ <prefpane id="SanitizeDialogPane" onpaneload="gSanitizePromptDialog.init();">
+ <stringbundle id="bundleBrowser"
+ src="chrome://browser/locale/browser.properties"/>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sanitize.js"/>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ <script type="application/javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/treeView.js"/>
+ <script type="application/javascript"><![CDATA[
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
+ ]]></script>
+#endif
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sanitizeDialog.js"/>
+
+ <preferences id="sanitizePreferences">
+ <preference id="privacy.cpd.history" name="privacy.cpd.history" type="bool"/>
+ <preference id="privacy.cpd.formdata" name="privacy.cpd.formdata" type="bool"/>
+ <preference id="privacy.cpd.downloads" name="privacy.cpd.downloads" type="bool" disabled="true"/>
+ <preference id="privacy.cpd.cookies" name="privacy.cpd.cookies" type="bool"/>
+ <preference id="privacy.cpd.cache" name="privacy.cpd.cache" type="bool"/>
+ <preference id="privacy.cpd.sessions" name="privacy.cpd.sessions" type="bool"/>
+ <preference id="privacy.cpd.offlineApps" name="privacy.cpd.offlineApps" type="bool"/>
+ <preference id="privacy.cpd.siteSettings" name="privacy.cpd.siteSettings" type="bool"/>
+ </preferences>
+
+ <preferences id="nonItemPreferences">
+ <preference id="privacy.sanitize.timeSpan"
+ name="privacy.sanitize.timeSpan"
+ type="int"/>
+ </preferences>
+
+ <hbox id="SanitizeDurationBox" align="center">
+ <label value="&clearTimeDuration.label;"
+ accesskey="&clearTimeDuration.accesskey;"
+ control="sanitizeDurationChoice"
+ id="sanitizeDurationLabel"/>
+ <menulist id="sanitizeDurationChoice"
+ preference="privacy.sanitize.timeSpan"
+ onselect="gSanitizePromptDialog.selectByTimespan();"
+ flex="1">
+ <menupopup id="sanitizeDurationPopup">
+#ifdef CRH_DIALOG_TREE_VIEW
+ <menuitem label="" value="-1" id="sanitizeDurationCustom"/>
+#endif
+ <menuitem label="&clearTimeDuration.lastHour;" value="1"/>
+ <menuitem label="&clearTimeDuration.last2Hours;" value="2"/>
+ <menuitem label="&clearTimeDuration.last4Hours;" value="3"/>
+ <menuitem label="&clearTimeDuration.today;" value="4"/>
+ <menuseparator/>
+ <menuitem label="&clearTimeDuration.everything;" value="0"/>
+ </menupopup>
+ </menulist>
+ <label id="sanitizeDurationSuffixLabel"
+ value="&clearTimeDuration.suffix;"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ <deck id="durationDeck">
+ <tree id="placesTree" flex="1" hidecolumnpicker="true" rows="10"
+ disabled="true" disableKeyNavigation="true">
+ <treecols>
+ <treecol id="date" label="&clearTimeDuration.dateColumn;" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="title" label="&clearTimeDuration.nameColumn;" flex="5"/>
+ </treecols>
+ <treechildren id="placesTreechildren"
+ ondragstart="gSanitizePromptDialog.grippyMoved('ondragstart', event);"
+ ondragover="gSanitizePromptDialog.grippyMoved('ondragover', event);"
+ onkeypress="gSanitizePromptDialog.grippyMoved('onkeypress', event);"
+ onmousedown="gSanitizePromptDialog.grippyMoved('onmousedown', event);"/>
+ </tree>
+#endif
+
+ <vbox id="sanitizeEverythingWarningBox">
+ <spacer flex="1"/>
+ <hbox align="center">
+ <image id="sanitizeEverythingWarningIcon"/>
+ <vbox id="sanitizeEverythingWarningDescBox" flex="1">
+ <description id="sanitizeEverythingWarning"/>
+ <description id="sanitizeEverythingUndoWarning">&sanitizeEverythingUndoWarning;</description>
+ </vbox>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ </deck>
+#endif
+
+ <separator class="thin"/>
+
+ <hbox id="detailsExpanderWrapper" align="center">
+ <button type="image"
+ id="detailsExpander"
+ class="expander-down"
+ persist="class"
+ oncommand="gSanitizePromptDialog.toggleItemList();"/>
+ <label id="detailsExpanderLabel"
+ value="&detailsProgressiveDisclosure.label;"
+ accesskey="&detailsProgressiveDisclosure.accesskey;"
+ control="detailsExpander"/>
+ </hbox>
+ <listbox id="itemList" rows="7" collapsed="true" persist="collapsed">
+ <listitem label="&itemHistoryAndDownloads.label;"
+ type="checkbox"
+ accesskey="&itemHistoryAndDownloads.accesskey;"
+ preference="privacy.cpd.history"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemFormSearchHistory.label;"
+ type="checkbox"
+ accesskey="&itemFormSearchHistory.accesskey;"
+ preference="privacy.cpd.formdata"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemCookies.label;"
+ type="checkbox"
+ accesskey="&itemCookies.accesskey;"
+ preference="privacy.cpd.cookies"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemCache.label;"
+ type="checkbox"
+ accesskey="&itemCache.accesskey;"
+ preference="privacy.cpd.cache"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemActiveLogins.label;"
+ type="checkbox"
+ accesskey="&itemActiveLogins.accesskey;"
+ preference="privacy.cpd.sessions"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemOfflineApps.label;"
+ type="checkbox"
+ accesskey="&itemOfflineApps.accesskey;"
+ preference="privacy.cpd.offlineApps"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemSitePreferences.label;"
+ type="checkbox"
+ accesskey="&itemSitePreferences.accesskey;"
+ preference="privacy.cpd.siteSettings"
+ noduration="true"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ </listbox>
+
+ </prefpane>
+</prefwindow>
diff --git a/browser/base/content/sanitizeDialog.css b/browser/base/content/sanitizeDialog.css
new file mode 100644
index 000000000..a7c17f094
--- /dev/null
+++ b/browser/base/content/sanitizeDialog.css
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Places tree */
+
+#placesTreechildren {
+ -moz-user-focus: normal;
+}
+
+#placesTreechildren::-moz-tree-cell(grippyRow),
+#placesTreechildren::-moz-tree-cell-text(grippyRow),
+#placesTreechildren::-moz-tree-image(grippyRow) {
+ cursor: grab;
+}
+
+
+/* Sanitize everything warnings */
+
+#sanitizeEverythingWarning,
+#sanitizeEverythingUndoWarning {
+ white-space: pre-wrap;
+}
diff --git a/browser/base/content/sanitizeDialog.js b/browser/base/content/sanitizeDialog.js
new file mode 100644
index 000000000..279f1efd6
--- /dev/null
+++ b/browser/base/content/sanitizeDialog.js
@@ -0,0 +1,889 @@
+/* -*- 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 {Sanitizer} = Cu.import("resource:///modules/Sanitizer.jsm", {});
+
+var gSanitizePromptDialog = {
+
+ get bundleBrowser()
+ {
+ if (!this._bundleBrowser)
+ this._bundleBrowser = document.getElementById("bundleBrowser");
+ return this._bundleBrowser;
+ },
+
+ get selectedTimespan()
+ {
+ var durList = document.getElementById("sanitizeDurationChoice");
+ return parseInt(durList.value);
+ },
+
+ get sanitizePreferences()
+ {
+ if (!this._sanitizePreferences) {
+ this._sanitizePreferences =
+ document.getElementById("sanitizePreferences");
+ }
+ return this._sanitizePreferences;
+ },
+
+ get warningBox()
+ {
+ return document.getElementById("sanitizeEverythingWarningBox");
+ },
+
+ init: function ()
+ {
+ // This is used by selectByTimespan() to determine if the window has loaded.
+ this._inited = true;
+
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ document.documentElement.getButton("accept").label =
+ this.bundleBrowser.getString("sanitizeButtonOK");
+
+ if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ this.warningBox.hidden = false;
+ document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ }
+ else
+ this.warningBox.hidden = true;
+ },
+
+ selectByTimespan: function ()
+ {
+ // This method is the onselect handler for the duration dropdown. As a
+ // result it's called a couple of times before onload calls init().
+ if (!this._inited)
+ return;
+
+ var warningBox = this.warningBox;
+
+ // If clearing everything
+ if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ if (warningBox.hidden) {
+ warningBox.hidden = false;
+ window.resizeBy(0, warningBox.boxObject.height);
+ }
+ window.document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ return;
+ }
+
+ // If clearing a specific time range
+ if (!warningBox.hidden) {
+ window.resizeBy(0, -warningBox.boxObject.height);
+ warningBox.hidden = true;
+ }
+ window.document.title =
+ window.document.documentElement.getAttribute("noneverythingtitle");
+ },
+
+ sanitize: function ()
+ {
+ // Update pref values before handing off to the sanitizer (bug 453440)
+ this.updatePrefs();
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ s.range = Sanitizer.getClearRange(this.selectedTimespan);
+ s.ignoreTimespan = !s.range;
+
+ // As the sanitize is async, we disable the buttons, update the label on
+ // the 'accept' button to indicate things are happening and return false -
+ // once the async operation completes (either with or without errors)
+ // we close the window.
+ let docElt = document.documentElement;
+ let acceptButton = docElt.getButton("accept");
+ acceptButton.disabled = true;
+ acceptButton.setAttribute("label",
+ this.bundleBrowser.getString("sanitizeButtonClearing"));
+ docElt.getButton("cancel").disabled = true;
+
+ try {
+ s.sanitize().then(null, Components.utils.reportError)
+ .then(() => window.close())
+ .then(null, Components.utils.reportError);
+ } catch (er) {
+ Components.utils.reportError("Exception during sanitize: " + er);
+ return true; // We *do* want to close immediately on error.
+ }
+ },
+
+ /**
+ * If the panel that displays a warning when the duration is "Everything" is
+ * not set up, sets it up. Otherwise does nothing.
+ *
+ * @param aDontShowItemList Whether only the warning message should be updated.
+ * True means the item list visibility status should not
+ * be changed.
+ */
+ prepareWarning: function (aDontShowItemList) {
+ // If the date and time-aware locale warning string is ever used again,
+ // initialize it here. Currently we use the no-visits warning string,
+ // which does not include date and time. See bug 480169 comment 48.
+
+ var warningStringID;
+ if (this.hasNonSelectedItems()) {
+ warningStringID = "sanitizeSelectedWarning";
+ if (!aDontShowItemList)
+ this.showItemList();
+ }
+ else {
+ warningStringID = "sanitizeEverythingWarning2";
+ }
+
+ var warningDesc = document.getElementById("sanitizeEverythingWarning");
+ warningDesc.textContent =
+ this.bundleBrowser.getString(warningStringID);
+ },
+
+ /**
+ * Called when the value of a preference element is synced from the actual
+ * pref. Enables or disables the OK button appropriately.
+ */
+ onReadGeneric: function ()
+ {
+ var found = false;
+
+ // Find any other pref that's checked and enabled.
+ var i = 0;
+ while (!found && i < this.sanitizePreferences.childNodes.length) {
+ var preference = this.sanitizePreferences.childNodes[i];
+
+ found = !!preference.value &&
+ !preference.disabled;
+ i++;
+ }
+
+ try {
+ document.documentElement.getButton("accept").disabled = !found;
+ }
+ catch (e) { }
+
+ // Update the warning prompt if needed
+ this.prepareWarning(true);
+
+ return undefined;
+ },
+
+ /**
+ * Sanitizer.prototype.sanitize() requires the prefs to be up-to-date.
+ * Because the type of this prefwindow is "child" -- and that's needed because
+ * without it the dialog has no OK and Cancel buttons -- the prefs are not
+ * updated on dialogaccept on platforms that don't support instant-apply
+ * (i.e., Windows). We must therefore manually set the prefs from their
+ * corresponding preference elements.
+ */
+ updatePrefs : function ()
+ {
+ var tsPref = document.getElementById("privacy.sanitize.timeSpan");
+ Sanitizer.prefs.setIntPref("timeSpan", this.selectedTimespan);
+
+ // Keep the pref for the download history in sync with the history pref.
+ document.getElementById("privacy.cpd.downloads").value =
+ document.getElementById("privacy.cpd.history").value;
+
+ // Now manually set the prefs from their corresponding preference
+ // elements.
+ var prefs = this.sanitizePreferences.rootBranch;
+ for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
+ var p = this.sanitizePreferences.childNodes[i];
+ prefs.setBoolPref(p.name, p.value);
+ }
+ },
+
+ /**
+ * Check if all of the history items have been selected like the default status.
+ */
+ hasNonSelectedItems: function () {
+ let checkboxes = document.querySelectorAll("#itemList > [preference]");
+ for (let i = 0; i < checkboxes.length; ++i) {
+ let pref = document.getElementById(checkboxes[i].getAttribute("preference"));
+ if (!pref.value)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Show the history items list.
+ */
+ showItemList: function () {
+ var itemList = document.getElementById("itemList");
+ var expanderButton = document.getElementById("detailsExpander");
+
+ if (itemList.collapsed) {
+ expanderButton.className = "expander-up";
+ itemList.setAttribute("collapsed", "false");
+ if (document.documentElement.boxObject.height)
+ window.resizeBy(0, itemList.boxObject.height);
+ }
+ },
+
+ /**
+ * Hide the history items list.
+ */
+ hideItemList: function () {
+ var itemList = document.getElementById("itemList");
+ var expanderButton = document.getElementById("detailsExpander");
+
+ if (!itemList.collapsed) {
+ expanderButton.className = "expander-down";
+ window.resizeBy(0, -itemList.boxObject.height);
+ itemList.setAttribute("collapsed", "true");
+ }
+ },
+
+ /**
+ * Called by the item list expander button to toggle the list's visibility.
+ */
+ toggleItemList: function ()
+ {
+ var itemList = document.getElementById("itemList");
+
+ if (itemList.collapsed)
+ this.showItemList();
+ else
+ this.hideItemList();
+ },
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ // A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
+ // Sanitizer.TIMESPAN_2HOURS, et al. This should match the value attribute
+ // of the sanitizeDurationCustom menuitem.
+ get TIMESPAN_CUSTOM()
+ {
+ return -1;
+ },
+
+ get placesTree()
+ {
+ if (!this._placesTree)
+ this._placesTree = document.getElementById("placesTree");
+ return this._placesTree;
+ },
+
+ init: function ()
+ {
+ // This is used by selectByTimespan() to determine if the window has loaded.
+ this._inited = true;
+
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ document.documentElement.getButton("accept").label =
+ this.bundleBrowser.getString("sanitizeButtonOK");
+
+ this.selectByTimespan();
+ },
+
+ /**
+ * Sets up the hashes this.durationValsToRows, which maps duration values
+ * to rows in the tree, this.durationRowsToVals, which maps rows in
+ * the tree to duration values, and this.durationStartTimes, which maps
+ * duration values to their corresponding start times.
+ */
+ initDurationDropdown: function ()
+ {
+ // First, calculate the start times for each duration.
+ this.durationStartTimes = {};
+ var durVals = [];
+ var durPopup = document.getElementById("sanitizeDurationPopup");
+ var durMenuitems = durPopup.childNodes;
+ for (let i = 0; i < durMenuitems.length; i++) {
+ let durMenuitem = durMenuitems[i];
+ let durVal = parseInt(durMenuitem.value);
+ if (durMenuitem.localName === "menuitem" &&
+ durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
+ durVal !== this.TIMESPAN_CUSTOM) {
+ durVals.push(durVal);
+ let durTimes = Sanitizer.getClearRange(durVal);
+ this.durationStartTimes[durVal] = durTimes[0];
+ }
+ }
+
+ // Sort the duration values ascending. Because one tree index can map to
+ // more than one duration, this ensures that this.durationRowsToVals maps
+ // a row index to the largest duration possible in the code below.
+ durVals.sort();
+
+ // Now calculate the rows in the tree of the durations' start times. For
+ // each duration, we are looking for the node in the tree whose time is the
+ // smallest time greater than or equal to the duration's start time.
+ this.durationRowsToVals = {};
+ this.durationValsToRows = {};
+ var view = this.placesTree.view;
+ // For all rows in the tree except the grippy row...
+ for (let i = 0; i < view.rowCount - 1; i++) {
+ let unfoundDurVals = [];
+ let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
+ nodeForTreeIndex(i).time;
+ // For all durations whose rows have not yet been found in the tree, see
+ // if index i is their index. An index may map to more than one duration,
+ // in which case the final duration (the largest) wins.
+ for (let j = 0; j < durVals.length; j++) {
+ let durVal = durVals[j];
+ let durStartTime = this.durationStartTimes[durVal];
+ if (nodeTime < durStartTime) {
+ this.durationValsToRows[durVal] = i - 1;
+ this.durationRowsToVals[i - 1] = durVal;
+ }
+ else
+ unfoundDurVals.push(durVal);
+ }
+ durVals = unfoundDurVals;
+ }
+
+ // If any durations were not found above, then every node in the tree has a
+ // time greater than or equal to the duration. In other words, those
+ // durations include the entire tree (except the grippy row).
+ for (let i = 0; i < durVals.length; i++) {
+ let durVal = durVals[i];
+ this.durationValsToRows[durVal] = view.rowCount - 2;
+ this.durationRowsToVals[view.rowCount - 2] = durVal;
+ }
+ },
+
+ /**
+ * If the Places tree is not set up, sets it up. Otherwise does nothing.
+ */
+ ensurePlacesTreeIsInited: function ()
+ {
+ if (this._placesTreeIsInited)
+ return;
+
+ this._placesTreeIsInited = true;
+
+ // Either "Last Four Hours" or "Today" will have the most history. If
+ // it's been more than 4 hours since today began, "Today" will. Otherwise
+ // "Last Four Hours" will.
+ var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
+
+ // If it's been less than 4 hours since today began, use the past 4 hours.
+ if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
+ times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
+ }
+
+ var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsINavHistoryService);
+ var query = histServ.getNewQuery();
+ query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
+ query.beginTime = times[0];
+ query.endTimeReference = query.TIME_RELATIVE_EPOCH;
+ query.endTime = times[1];
+ var opts = histServ.getNewQueryOptions();
+ opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
+ opts.queryType = opts.QUERY_TYPE_HISTORY;
+ var result = histServ.executeQuery(query, opts);
+
+ var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
+ new PlacesTreeView());
+ result.addObserver(view, false);
+ this.initDurationDropdown();
+ },
+
+ /**
+ * Called on select of the duration dropdown and when grippyMoved() sets a
+ * duration based on the location of the grippy row. Selects all the nodes in
+ * the tree that are contained in the selected duration. If clearing
+ * everything, the warning panel is shown instead.
+ */
+ selectByTimespan: function ()
+ {
+ // This method is the onselect handler for the duration dropdown. As a
+ // result it's called a couple of times before onload calls init().
+ if (!this._inited)
+ return;
+
+ var durDeck = document.getElementById("durationDeck");
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durVal = parseInt(durList.value);
+ var durCustom = document.getElementById("sanitizeDurationCustom");
+
+ // If grippy row is not at a duration boundary, show the custom menuitem;
+ // otherwise, hide it. Since the user cannot specify a custom duration by
+ // using the dropdown, this conditional is true only when this method is
+ // called onselect from grippyMoved(), so no selection need be made.
+ if (durVal === this.TIMESPAN_CUSTOM) {
+ durCustom.hidden = false;
+ return;
+ }
+ durCustom.hidden = true;
+
+ // If clearing everything, show the warning and change the dialog's title.
+ if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ durDeck.selectedIndex = 1;
+ window.document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ document.documentElement.getButton("accept").disabled = false;
+ return;
+ }
+
+ // Otherwise -- if clearing a specific time range -- select that time range
+ // in the tree.
+ this.ensurePlacesTreeIsInited();
+ durDeck.selectedIndex = 0;
+ window.document.title =
+ window.document.documentElement.getAttribute("noneverythingtitle");
+ var durRow = this.durationValsToRows[durVal];
+ gContiguousSelectionTreeHelper.rangedSelect(durRow);
+ gContiguousSelectionTreeHelper.scrollToGrippy();
+
+ // If duration is empty (there are no selected rows), disable the dialog's
+ // OK button.
+ document.documentElement.getButton("accept").disabled = durRow < 0;
+ },
+
+ sanitize: function ()
+ {
+ // Update pref values before handing off to the sanitizer (bug 453440)
+ this.updatePrefs();
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durValue = parseInt(durList.value);
+ s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
+
+ // Set the sanitizer's time range if we're not clearing everything.
+ if (!s.ignoreTimespan) {
+ // If user selected a custom timespan, use that.
+ if (durValue === this.TIMESPAN_CUSTOM) {
+ var view = this.placesTree.view;
+ var now = Date.now() * 1000;
+ // We disable the dialog's OK button if there's no selection, but we'll
+ // handle that case just in... case.
+ if (view.selection.getRangeCount() === 0)
+ s.range = [now, now];
+ else {
+ var startIndexRef = {};
+ // Tree sorted by visit date DEscending, so start time time comes last.
+ view.selection.getRangeAt(0, {}, startIndexRef);
+ view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
+ var startNode = view.nodeForTreeIndex(startIndexRef.value);
+ s.range = [startNode.time, now];
+ }
+ }
+ // Otherwise use the predetermined range.
+ else
+ s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
+ }
+
+ try {
+ s.sanitize(); // We ignore the resulting Promise
+ } catch (er) {
+ Components.utils.reportError("Exception during sanitize: " + er);
+ }
+ return true;
+ },
+
+ /**
+ * In order to mark the custom Places tree view and its nsINavHistoryResult
+ * for garbage collection, we need to break the reference cycle between the
+ * two.
+ */
+ unload: function ()
+ {
+ let result = this.placesTree.getResult();
+ result.removeObserver(this.placesTree.view);
+ this.placesTree.view = null;
+ },
+
+ /**
+ * Called when the user moves the grippy by dragging it, clicking in the tree,
+ * or on keypress. Updates the duration dropdown so that it displays the
+ * appropriate specific or custom duration.
+ *
+ * @param aEventName
+ * The name of the event whose handler called this method, e.g.,
+ * "ondragstart", "onkeypress", etc.
+ * @param aEvent
+ * The event captured in the event handler.
+ */
+ grippyMoved: function (aEventName, aEvent)
+ {
+ gContiguousSelectionTreeHelper[aEventName](aEvent);
+ var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durValue = parseInt(durList.value);
+
+ // Multiple durations can map to the same row. Don't update the dropdown
+ // if the current duration is valid for lastSelRow.
+ if ((durValue !== this.TIMESPAN_CUSTOM ||
+ lastSelRow in this.durationRowsToVals) &&
+ (durValue === this.TIMESPAN_CUSTOM ||
+ this.durationValsToRows[durValue] !== lastSelRow)) {
+ // Setting durList.value causes its onselect handler to fire, which calls
+ // selectByTimespan().
+ if (lastSelRow in this.durationRowsToVals)
+ durList.value = this.durationRowsToVals[lastSelRow];
+ else
+ durList.value = this.TIMESPAN_CUSTOM;
+ }
+
+ // If there are no selected rows, disable the dialog's OK button.
+ document.documentElement.getButton("accept").disabled = lastSelRow < 0;
+ }
+#endif
+
+};
+
+
+#ifdef CRH_DIALOG_TREE_VIEW
+/**
+ * A helper for handling contiguous selection in the tree.
+ */
+var gContiguousSelectionTreeHelper = {
+
+ /**
+ * Gets the tree associated with this helper.
+ */
+ get tree()
+ {
+ return this._tree;
+ },
+
+ /**
+ * Sets the tree that this module handles. The tree is assigned a new view
+ * that is equipped to handle contiguous selection. You can pass in an
+ * object that will be used as the prototype of the new view. Otherwise
+ * the tree's current view is used as the prototype.
+ *
+ * @param aTreeElement
+ * The tree element
+ * @param aProtoTreeView
+ * If defined, this will be used as the prototype of the tree's new
+ * view
+ * @return The new view
+ */
+ setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
+ {
+ this._tree = aTreeElement;
+ var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
+ aTreeElement.view = newView;
+ return newView;
+ },
+
+ /**
+ * The index of the row that the grippy occupies. Note that the index of the
+ * last selected row is getGrippyRow() - 1. If getGrippyRow() is 0, then
+ * no selection exists.
+ *
+ * @return The row index of the grippy
+ */
+ getGrippyRow: function CSTH_getGrippyRow()
+ {
+ var sel = this.tree.view.selection;
+ var rangeCount = sel.getRangeCount();
+ if (rangeCount === 0)
+ return 0;
+ if (rangeCount !== 1) {
+ throw "contiguous selection tree helper: getGrippyRow called with " +
+ "multiple selection ranges";
+ }
+ var max = {};
+ sel.getRangeAt(0, {}, max);
+ return max.value + 1;
+ },
+
+ /**
+ * Helper function for the dragover event. Your dragover listener should
+ * call this. It updates the selection in the tree under the mouse.
+ *
+ * @param aEvent
+ * The observed dragover event
+ */
+ ondragover: function CSTH_ondragover(aEvent)
+ {
+ // Without this when dragging on Windows the mouse cursor is a "no" sign.
+ // This makes it a drop symbol.
+ var ds = Cc["@mozilla.org/widget/dragservice;1"].
+ getService(Ci.nsIDragService).
+ getCurrentSession();
+ ds.canDrop = true;
+ ds.dragAction = 0;
+
+ var tbo = this.tree.treeBoxObject;
+ aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
+ var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (hoverRow < 0)
+ return;
+
+ this.rangedSelect(hoverRow - 1);
+ },
+
+ /**
+ * Helper function for the dragstart event. Your dragstart listener should
+ * call this. It starts a drag session.
+ *
+ * @param aEvent
+ * The observed dragstart event
+ */
+ ondragstart: function CSTH_ondragstart(aEvent)
+ {
+ var tbo = this.tree.treeBoxObject;
+ var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (clickedRow !== this.getGrippyRow())
+ return;
+
+ // This part is a hack. What we really want is a grab and slide, not
+ // drag and drop. Start a move drag session with dummy data and a
+ // dummy region. Set the region's coordinates to (Infinity, Infinity)
+ // so it's drawn offscreen and its size to (1, 1).
+ var arr = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ var trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(null);
+ trans.setTransferData('dummy-flavor', null, 0);
+ arr.appendElement(trans, /* weak = */ false);
+ var reg = Cc["@mozilla.org/gfx/region;1"].
+ createInstance(Ci.nsIScriptableRegion);
+ reg.setToRect(Infinity, Infinity, 1, 1);
+ var ds = Cc["@mozilla.org/widget/dragservice;1"].
+ getService(Ci.nsIDragService);
+ ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
+ },
+
+ /**
+ * Helper function for the keypress event. Your keypress listener should
+ * call this. Users can use Up, Down, Page Up/Down, Home, and End to move
+ * the bottom of the selection window.
+ *
+ * @param aEvent
+ * The observed keypress event
+ */
+ onkeypress: function CSTH_onkeypress(aEvent)
+ {
+ var grippyRow = this.getGrippyRow();
+ var tbo = this.tree.treeBoxObject;
+ var rangeEnd;
+ switch (aEvent.keyCode) {
+ case aEvent.DOM_VK_HOME:
+ rangeEnd = 0;
+ break;
+ case aEvent.DOM_VK_PAGE_UP:
+ rangeEnd = grippyRow - tbo.getPageLength();
+ break;
+ case aEvent.DOM_VK_UP:
+ rangeEnd = grippyRow - 2;
+ break;
+ case aEvent.DOM_VK_DOWN:
+ rangeEnd = grippyRow;
+ break;
+ case aEvent.DOM_VK_PAGE_DOWN:
+ rangeEnd = grippyRow + tbo.getPageLength();
+ break;
+ case aEvent.DOM_VK_END:
+ rangeEnd = this.tree.view.rowCount - 2;
+ break;
+ default:
+ return;
+ break;
+ }
+
+ aEvent.stopPropagation();
+
+ // First, clip rangeEnd. this.rangedSelect() doesn't clip the range if we
+ // select past the ends of the tree.
+ if (rangeEnd < 0)
+ rangeEnd = -1;
+ else if (this.tree.view.rowCount - 2 < rangeEnd)
+ rangeEnd = this.tree.view.rowCount - 2;
+
+ // Next, (de)select.
+ this.rangedSelect(rangeEnd);
+
+ // Finally, scroll the tree. We always want one row above and below the
+ // grippy row to be visible if possible.
+ if (rangeEnd < grippyRow) // moved up
+ tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
+ else { // moved down
+ if (rangeEnd + 2 < this.tree.view.rowCount)
+ tbo.ensureRowIsVisible(rangeEnd + 2);
+ else if (rangeEnd + 1 < this.tree.view.rowCount)
+ tbo.ensureRowIsVisible(rangeEnd + 1);
+ }
+ },
+
+ /**
+ * Helper function for the mousedown event. Your mousedown listener should
+ * call this. Users can click on individual rows to make the selection
+ * jump to them immediately.
+ *
+ * @param aEvent
+ * The observed mousedown event
+ */
+ onmousedown: function CSTH_onmousedown(aEvent)
+ {
+ var tbo = this.tree.treeBoxObject;
+ var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
+ return;
+
+ if (clickedRow < this.getGrippyRow())
+ this.rangedSelect(clickedRow);
+ else if (clickedRow > this.getGrippyRow())
+ this.rangedSelect(clickedRow - 1);
+ },
+
+ /**
+ * Selects range [0, aEndRow] in the tree. The grippy row will then be at
+ * index aEndRow + 1. aEndRow may be -1, in which case the selection is
+ * cleared and the grippy row will be at index 0.
+ *
+ * @param aEndRow
+ * The range [0, aEndRow] will be selected.
+ */
+ rangedSelect: function CSTH_rangedSelect(aEndRow)
+ {
+ var tbo = this.tree.treeBoxObject;
+ if (aEndRow < 0)
+ this.tree.view.selection.clearSelection();
+ else
+ this.tree.view.selection.rangedSelect(0, aEndRow, false);
+ tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
+ },
+
+ /**
+ * Scrolls the tree so that the grippy row is in the center of the view.
+ */
+ scrollToGrippy: function CSTH_scrollToGrippy()
+ {
+ var rowCount = this.tree.view.rowCount;
+ var tbo = this.tree.treeBoxObject;
+ var pageLen = tbo.getPageLength() ||
+ parseInt(this.tree.getAttribute("rows")) ||
+ 10;
+
+ // All rows fit on a single page.
+ if (rowCount <= pageLen)
+ return;
+
+ var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
+
+ // Grippy row is in first half of first page.
+ if (scrollToRow < 0)
+ scrollToRow = 0;
+
+ // Grippy row is in last half of last page.
+ else if (rowCount < scrollToRow + pageLen)
+ scrollToRow = rowCount - pageLen;
+
+ tbo.scrollToRow(scrollToRow);
+ },
+
+ /**
+ * Creates a new tree view suitable for contiguous selection. If
+ * aProtoTreeView is specified, it's used as the new view's prototype.
+ * Otherwise the tree's current view is used as the prototype.
+ *
+ * @param aProtoTreeView
+ * Used as the new view's prototype if specified
+ */
+ _makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
+ {
+ var view = aProtoTreeView;
+ var that = this;
+
+ //XXXadw: When Alex gets the grippy icon done, this may or may not change,
+ // depending on how we style it.
+ view.isSeparator = function CSTH_View_isSeparator(aRow)
+ {
+ return aRow === that.getGrippyRow();
+ };
+
+ // rowCount includes the grippy row.
+ view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
+ view.__defineGetter__("rowCount",
+ function CSTH_View_rowCount()
+ {
+ return this._rowCount + 1;
+ });
+
+ // This has to do with visual feedback in the view itself, e.g., drawing
+ // a small line underneath the dropzone. Not what we want.
+ view.canDrop = function CSTH_View_canDrop() { return false; };
+
+ // No clicking headers to sort the tree or sort feedback on columns.
+ view.cycleHeader = function CSTH_View_cycleHeader() {};
+ view.sortingChanged = function CSTH_View_sortingChanged() {};
+
+ // Override a bunch of methods to account for the grippy row.
+
+ view._getCellProperties = view.getCellProperties;
+ view.getCellProperties =
+ function CSTH_View_getCellProperties(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "grippyRow";
+ if (aRow < grippyRow)
+ return this._getCellProperties(aRow, aCol);
+
+ return this._getCellProperties(aRow - 1, aCol);
+ };
+
+ view._getRowProperties = view.getRowProperties;
+ view.getRowProperties =
+ function CSTH_View_getRowProperties(aRow)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "grippyRow";
+
+ if (aRow < grippyRow)
+ return this._getRowProperties(aRow);
+
+ return this._getRowProperties(aRow - 1);
+ };
+
+ view._getCellText = view.getCellText;
+ view.getCellText =
+ function CSTH_View_getCellText(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "";
+ aRow = aRow < grippyRow ? aRow : aRow - 1;
+ return this._getCellText(aRow, aCol);
+ };
+
+ view._getImageSrc = view.getImageSrc;
+ view.getImageSrc =
+ function CSTH_View_getImageSrc(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "";
+ aRow = aRow < grippyRow ? aRow : aRow - 1;
+ return this._getImageSrc(aRow, aCol);
+ };
+
+ view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
+ view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
+ view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
+ view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
+ {
+ return aRow < this.rowCount - 1;
+ };
+
+ return view;
+ }
+};
+#endif
diff --git a/browser/base/content/social-content.js b/browser/base/content/social-content.js
new file mode 100644
index 000000000..b5fa6a5c4
--- /dev/null
+++ b/browser/base/content/social-content.js
@@ -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/. */
+
+/* This content script is intended for use by iframes in the share panel. */
+
+var {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// social frames are always treated as app tabs
+docShell.isAppTab = true;
+
+addEventListener("DOMContentLoaded", function(event) {
+ if (event.target != content.document)
+ return;
+ // Some share panels (e.g. twitter and facebook) check content.opener, and if
+ // it doesn't exist they act like they are in a browser tab. We want them to
+ // act like they are in a dialog (which is the typical case).
+ if (content && !content.opener) {
+ content.opener = content;
+ }
+ hookWindowClose();
+ disableDialogs();
+});
+
+addMessageListener("Social:OpenGraphData", (message) => {
+ let ev = new content.CustomEvent("OpenGraphData", { detail: JSON.stringify(message.data) });
+ content.dispatchEvent(ev);
+});
+
+addMessageListener("Social:ClearFrame", () => {
+ docShell.createAboutBlankContentViewer(null);
+});
+
+addEventListener("DOMWindowClose", (evt) => {
+ // preventDefault stops the default window.close() function being called,
+ // which doesn't actually close anything but causes things to get into
+ // a bad state (an internal 'closed' flag is set and debug builds start
+ // asserting as the window is used.).
+ // None of the windows we inject this API into are suitable for this
+ // default close behaviour, so even if we took no action above, we avoid
+ // the default close from doing anything.
+ evt.preventDefault();
+
+ // Tells the SocialShare class to close the panel
+ sendAsyncMessage("Social:DOMWindowClose");
+});
+
+function hookWindowClose() {
+ // Allow scripts to close the "window". Because we are in a panel and not
+ // in a full dialog, the DOMWindowClose listener above will only receive the
+ // event if we do this.
+ let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ dwu.allowScriptsToClose();
+}
+
+function disableDialogs() {
+ let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+ windowUtils.disableDialogs();
+}
+
+// Error handling class used to listen for network errors in the social frames
+// and replace them with a social-specific error page
+const SocialErrorListener = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+
+ defaultTemplate: "about:socialerror?mode=tryAgainOnly&url=%{url}&origin=%{origin}",
+ urlTemplate: null,
+
+ init() {
+ addMessageListener("Social:SetErrorURL", this);
+ let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebProgress);
+ webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+ Ci.nsIWebProgress.NOTIFY_LOCATION);
+ },
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "Social:SetErrorURL":
+ // Either a url or null to reset to default template.
+ this.urlTemplate = message.data.template;
+ break;
+ }
+ },
+
+ setErrorPage() {
+ // if this is about:providerdirectory, use the directory iframe
+ let frame = docShell.chromeEventHandler;
+ let origin = frame.getAttribute("origin");
+ let src = frame.getAttribute("src");
+ if (src == "about:providerdirectory") {
+ frame = content.document.getElementById("activation-frame");
+ src = frame.getAttribute("src");
+ }
+
+ let url = this.urlTemplate || this.defaultTemplate;
+ url = url.replace("%{url}", encodeURIComponent(src));
+ url = url.replace("%{origin}", encodeURIComponent(origin));
+ if (frame != docShell.chromeEventHandler) {
+ // Unable to access frame.docShell here. This is our own frame and doesn't
+ // provide reload, so we'll just set the src.
+ frame.setAttribute("src", url);
+ } else {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(url, null, null, null, null);
+ }
+ sendAsyncMessage("Social:ErrorPageNotify", {
+ origin: origin,
+ url: src
+ });
+ },
+
+ onStateChange(aWebProgress, aRequest, aState, aStatus) {
+ let failure = false;
+ if ((aState & Ci.nsIWebProgressListener.STATE_IS_REQUEST))
+ return;
+ if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
+ if (aRequest instanceof Ci.nsIHttpChannel) {
+ try {
+ // Change the frame to an error page on 4xx (client errors)
+ // and 5xx (server errors). responseStatus throws if it is not set.
+ failure = aRequest.responseStatus >= 400 &&
+ aRequest.responseStatus < 600;
+ } catch (e) {
+ failure = aStatus != Components.results.NS_OK;
+ }
+ }
+ }
+
+ // Calling cancel() will raise some OnStateChange notifications by itself,
+ // so avoid doing that more than once
+ if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
+ // if tp is enabled and we get a failure, ignore failures (ie. STATE_STOP)
+ // on child resources since they *may* have been blocked. We don't have an
+ // easy way to know if a particular url is blocked by TP, only that
+ // something was.
+ if (docShell.hasTrackingContentBlocked) {
+ let frame = docShell.chromeEventHandler;
+ let src = frame.getAttribute("src");
+ if (aRequest && aRequest.name != src) {
+ Cu.reportError("SocialErrorListener ignoring blocked content error for " + aRequest.name);
+ return;
+ }
+ }
+
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ this.setErrorPage();
+ }
+ },
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ if (aRequest && aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ this.setErrorPage();
+ }
+ },
+
+ onProgressChange() {},
+ onStatusChange() {},
+ onSecurityChange() {},
+};
+
+SocialErrorListener.init();
diff --git a/browser/base/content/softwareUpdateOverlay.xul b/browser/base/content/softwareUpdateOverlay.xul
new file mode 100644
index 000000000..01170e46c
--- /dev/null
+++ b/browser/base/content/softwareUpdateOverlay.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="softwareUpdateOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="updates">
+
+#include browserMountPoints.inc
+
+</window>
+
+</overlay>
diff --git a/browser/base/content/sync/aboutSyncTabs-bindings.xml b/browser/base/content/sync/aboutSyncTabs-bindings.xml
new file mode 100644
index 000000000..e6108209a
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs-bindings.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="tabBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tab-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="start">
+ <xul:image class="tabIcon"
+ xbl:inherits="src=icon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:label xbl:inherits="value=title,selected"
+ crop="end" flex="1" class="title"/>
+ <xul:label xbl:inherits="value=url,selected"
+ crop="end" flex="1" class="url"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ <handlers>
+ <handler event="dblclick" button="0">
+ <![CDATA[
+ RemoteTabViewer.openSelected();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="client-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox pack="start" align="center" onfocus="event.target.blur()" onselect="return false;">
+ <xul:image/>
+ <xul:label xbl:inherits="value=clientName"
+ class="clientName"
+ crop="center" flex="1"/>
+ </xul:hbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/browser/base/content/sync/aboutSyncTabs.css b/browser/base/content/sync/aboutSyncTabs.css
new file mode 100644
index 000000000..5a353175b
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs.css
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+richlistitem[type="tab"] {
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
+}
+
+richlistitem[type="client"] {
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
+}
diff --git a/browser/base/content/sync/aboutSyncTabs.js b/browser/base/content/sync/aboutSyncTabs.js
new file mode 100644
index 000000000..0c5dbb2d8
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs.js
@@ -0,0 +1,361 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/AppConstants.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
+ XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
+ "resource://gre/modules/CloudSync.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);
+
+ Services.obs.addObserver(this, "cloudsync:tabs:update", 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");
+
+ Services.obs.removeObserver(this, "cloudsync:tabs:update");
+ },
+
+ 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 (forceSync) {
+ if (this._waitingForBuildList) {
+ this._buildListRequested = true;
+ return;
+ }
+
+ this._waitingForBuildList = true;
+ this._buildListRequested = false;
+
+ this._clearTabList();
+
+ if (Weave.Service.isLoggedIn) {
+ this._refetchTabs(forceSync);
+ this._generateWeaveTabList();
+ } else {
+ // XXXzpao We should say something about not being logged in & not having data
+ // or tell the appropriate condition. (bug 583344)
+ }
+
+ let complete = () => {
+ this._waitingForBuildList = false;
+ if (this._buildListRequested) {
+ CommonUtils.nextTick(this.buildList, this);
+ }
+ }
+
+ if (CloudSync && CloudSync.ready && CloudSync().tabsReady && CloudSync().tabs.hasRemoteTabs()) {
+ this._generateCloudSyncTabList()
+ .then(complete, complete);
+ } else {
+ 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 [, client] of Object.entries(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);
+ }
+ },
+
+ _generateCloudSyncTabList: function () {
+ let updateTabList = function (remoteTabs) {
+ let list = this._tabsList;
+
+ for (let client of remoteTabs) {
+ let clientAttrs = {
+ type: "client",
+ clientName: client.name,
+ };
+
+ let clientEnt = this.createItem(clientAttrs);
+ list.appendChild(clientEnt);
+
+ for (let tab of client.tabs) {
+ let tabAttrs = {
+ type: "tab",
+ title: tab.title,
+ url: tab.url,
+ icon: this.getIcon(tab.icon),
+ };
+ let tabEnt = this.createItem(tabAttrs);
+ list.appendChild(tabEnt);
+ }
+ }
+ }.bind(this);
+
+ return CloudSync().tabs.getRemoteTabs()
+ .then(updateTabList, Promise.reject.bind(Promise));
+ },
+
+ 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;
+ }
+ }
+
+ // Ask Sync to just do the tabs engine if it can.
+ Weave.Service.sync(["tabs"]);
+ 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":
+ // A login has finished, which means that a Sync is about to start and
+ // we will eventually get to the "tabs" engine - but try and force the
+ // tab engine to sync first by passing |true| for the forceSync param.
+ this.buildList(true);
+ break;
+ case "weave:engine:sync:finish":
+ if (data == "tabs") {
+ // The tabs engine just finished, so re-build the list without
+ // forcing a new sync of the tabs engine.
+ this.buildList(false);
+ }
+ break;
+ case "cloudsync:tabs:update":
+ 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/browser/base/content/sync/aboutSyncTabs.xul b/browser/base/content/sync/aboutSyncTabs.xul
new file mode 100644
index 000000000..a4aa0032f
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
+ %aboutSyncTabsDTD;
+]>
+
+<window id="tabs-display"
+ onload="RemoteTabViewer.init()"
+ onunload="RemoteTabViewer.uninit()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&tabs.otherDevices.label;">
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <html:head>
+ <html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
+ </html:head>
+
+ <popupset id="contextmenus">
+ <menupopup id="tabListContext">
+ <menuitem label="&tabs.context.openTab.label;"
+ accesskey="&tabs.context.openTab.accesskey;"
+ oncommand="RemoteTabViewer.openSelected()"
+ showFor="single"/>
+ <menuitem label="&tabs.context.bookmarkSingleTab.label;"
+ accesskey="&tabs.context.bookmarkSingleTab.accesskey;"
+ oncommand="RemoteTabViewer.bookmarkSingleTab(event)"
+ showFor="single"/>
+ <menuitem label="&tabs.context.openMultipleTabs.label;"
+ accesskey="&tabs.context.openMultipleTabs.accesskey;"
+ oncommand="RemoteTabViewer.openSelected()"
+ showFor="multiple"/>
+ <menuitem label="&tabs.context.bookmarkMultipleTabs.label;"
+ accesskey="&tabs.context.bookmarkMultipleTabs.accesskey;"
+ oncommand="RemoteTabViewer.bookmarkSelectedTabs()"
+ showFor="multiple"/>
+ <menuseparator/>
+ <menuitem label="&tabs.context.refreshList.label;"
+ accesskey="&tabs.context.refreshList.accesskey;"
+ oncommand="RemoteTabViewer.buildList()"
+ showFor="all"/>
+ </menupopup>
+ </popupset>
+ <richlistbox context="tabListContext" id="tabsList" seltype="multiple"
+ align="center" flex="1"
+ onclick="RemoteTabViewer.handleClick(event)"
+ oncontextmenu="RemoteTabViewer.adjustContextMenu(event)">
+ <hbox id="headers" align="center">
+ <label id="tabsListHeading"
+ value="&tabs.otherDevices.label;"/>
+ <spacer flex="1"/>
+ <textbox type="search"
+ emptytext="&tabs.searchText.label;"
+ oncommand="RemoteTabViewer.filterTabs(event)"/>
+ </hbox>
+
+ </richlistbox>
+</window>
+
diff --git a/browser/base/content/sync/addDevice.js b/browser/base/content/sync/addDevice.js
new file mode 100644
index 000000000..0390d4397
--- /dev/null
+++ b/browser/base/content/sync/addDevice.js
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PIN_PART_LENGTH = 4;
+
+const ADD_DEVICE_PAGE = 0;
+const SYNC_KEY_PAGE = 1;
+const DEVICE_CONNECTED_PAGE = 2;
+
+var gSyncAddDevice = {
+
+ init: function init() {
+ this.pin1.setAttribute("maxlength", PIN_PART_LENGTH);
+ this.pin2.setAttribute("maxlength", PIN_PART_LENGTH);
+ this.pin3.setAttribute("maxlength", PIN_PART_LENGTH);
+
+ this.nextFocusEl = {pin1: this.pin2,
+ pin2: this.pin3,
+ pin3: this.wizard.getButton("next")};
+
+ this.throbber = document.getElementById("pairDeviceThrobber");
+ this.errorRow = document.getElementById("errorRow");
+
+ // Kick off a sync. That way the server will have the most recent data from
+ // this computer and it will show up immediately on the new device.
+ Weave.Service.scheduler.scheduleNextSync(0);
+ },
+
+ onPageShow: function onPageShow() {
+ this.wizard.getButton("back").hidden = true;
+
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.onTextBoxInput();
+ this.wizard.canRewind = false;
+ this.wizard.getButton("next").hidden = false;
+ this.pin1.focus();
+ break;
+ case SYNC_KEY_PAGE:
+ this.wizard.canAdvance = false;
+ this.wizard.canRewind = true;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("next").hidden = true;
+ document.getElementById("weavePassphrase").value =
+ Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+ break;
+ case DEVICE_CONNECTED_PAGE:
+ this.wizard.canAdvance = true;
+ this.wizard.canRewind = false;
+ this.wizard.getButton("cancel").hidden = true;
+ break;
+ }
+ },
+
+ onWizardAdvance: function onWizardAdvance() {
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.startTransfer();
+ return false;
+ case DEVICE_CONNECTED_PAGE:
+ window.close();
+ return false;
+ }
+ return true;
+ },
+
+ startTransfer: function startTransfer() {
+ this.errorRow.hidden = true;
+ // When onAbort is called, Weave may already be gone.
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+ onPaired: function onPaired() {
+ let credentials = {account: Weave.Service.identity.account,
+ password: Weave.Service.identity.basicPassword,
+ synckey: Weave.Service.identity.syncKey,
+ serverURL: Weave.Service.serverURL};
+ jpakeclient.sendAndComplete(credentials);
+ },
+ onComplete: function onComplete() {
+ delete self._jpakeclient;
+ self.wizard.pageIndex = DEVICE_CONNECTED_PAGE;
+
+ // Schedule a Sync for soonish to fetch the data uploaded by the
+ // device with which we just paired.
+ Weave.Service.scheduler.scheduleNextSync(Weave.Service.scheduler.activeInterval);
+ },
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Aborted by user, ignore.
+ if (error == JPAKE_ERROR_USERABORT) {
+ return;
+ }
+
+ self.errorRow.hidden = false;
+ self.throbber.hidden = true;
+ self.pin1.value = self.pin2.value = self.pin3.value = "";
+ self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+ self.pin1.focus();
+ }
+ });
+ this.throbber.hidden = false;
+ this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+ this.wizard.canAdvance = false;
+
+ let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+ let expectDelay = false;
+ jpakeclient.pairWithPIN(pin, expectDelay);
+ },
+
+ onWizardBack: function onWizardBack() {
+ if (this.wizard.pageIndex != SYNC_KEY_PAGE)
+ return true;
+
+ this.wizard.pageIndex = ADD_DEVICE_PAGE;
+ return false;
+ },
+
+ onWizardCancel: function onWizardCancel() {
+ if (this._jpakeclient) {
+ this._jpakeclient.abort();
+ delete this._jpakeclient;
+ }
+ return true;
+ },
+
+ onTextBoxInput: function onTextBoxInput(textbox) {
+ if (textbox && textbox.value.length == PIN_PART_LENGTH)
+ this.nextFocusEl[textbox.id].focus();
+
+ this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH
+ && this.pin2.value.length == PIN_PART_LENGTH
+ && this.pin3.value.length == PIN_PART_LENGTH);
+ },
+
+ goToSyncKeyPage: function goToSyncKeyPage() {
+ this.wizard.pageIndex = SYNC_KEY_PAGE;
+ }
+
+};
+// onWizardAdvance() and onPageShow() are run before init() so we'll set
+// these up as lazy getters.
+["wizard", "pin1", "pin2", "pin3"].forEach(function (id) {
+ XPCOMUtils.defineLazyGetter(gSyncAddDevice, id, function() {
+ return document.getElementById(id);
+ });
+});
diff --git a/browser/base/content/sync/addDevice.xul b/browser/base/content/sync/addDevice.xul
new file mode 100644
index 000000000..83c3b7b3c
--- /dev/null
+++ b/browser/base/content/sync/addDevice.xul
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="wizard"
+ title="&pairDevice.title.label;"
+ windowtype="Sync:AddDevice"
+ persist="screenX screenY"
+ onwizardnext="return gSyncAddDevice.onWizardAdvance();"
+ onwizardback="return gSyncAddDevice.onWizardBack();"
+ onwizardcancel="gSyncAddDevice.onWizardCancel();"
+ onload="gSyncAddDevice.init();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/addDevice.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="addDevicePage"
+ label="&pairDevice.title.label;"
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <description>
+ &pairDevice.dialog.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/add-device"/>
+ </description>
+ <separator class="groove-thin"/>
+ <description>
+ &addDevice.dialog.enterCode.label;
+ </description>
+ <separator class="groove-thin"/>
+ <vbox align="center">
+ <textbox id="pin1"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin2"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin3"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ </vbox>
+ <separator class="groove-thin"/>
+ <vbox id="pairDeviceThrobber" align="center" hidden="true">
+ <image/>
+ </vbox>
+ <hbox id="errorRow" pack="center" hidden="true">
+ <image class="statusIcon" status="error"/>
+ <label class="status"
+ value="&addDevice.dialog.tryAgain.label;"/>
+ </hbox>
+ <spacer flex="3"/>
+ <label class="text-link"
+ value="&addDevice.dontHaveDevice.label;"
+ onclick="gSyncAddDevice.goToSyncKeyPage();"/>
+ </wizardpage>
+
+ <!-- Need a non-empty label here, otherwise we get a default label on Mac -->
+ <wizardpage id="syncKeyPage"
+ label=" "
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <description>
+ &addDevice.dialog.recoveryKey.label;
+ </description>
+ <spacer/>
+
+ <groupbox>
+ <label value="&recoveryKeyEntry.label;"
+ accesskey="&recoveryKeyEntry.accesskey;"
+ control="weavePassphrase"/>
+ <textbox id="weavePassphrase"
+ readonly="true"/>
+ </groupbox>
+
+ <groupbox align="center">
+ <description>&recoveryKeyBackup.description;</description>
+ <hbox>
+ <button id="printSyncKeyButton"
+ label="&button.syncKeyBackup.print.label;"
+ accesskey="&button.syncKeyBackup.print.accesskey;"
+ oncommand="gSyncUtils.passphrasePrint('weavePassphrase');"/>
+ <button id="saveSyncKeyButton"
+ label="&button.syncKeyBackup.save.label;"
+ accesskey="&button.syncKeyBackup.save.accesskey;"
+ oncommand="gSyncUtils.passphraseSave('weavePassphrase');"/>
+ </hbox>
+ </groupbox>
+ </wizardpage>
+
+ <wizardpage id="deviceConnectedPage"
+ label="&addDevice.dialog.connected.label;"
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <vbox align="center">
+ <image id="successPageIcon"/>
+ </vbox>
+ <separator/>
+ <description class="normal">
+ &addDevice.dialog.successful.label;
+ </description>
+ </wizardpage>
+
+</wizard>
diff --git a/browser/base/content/sync/customize.css b/browser/base/content/sync/customize.css
new file mode 100644
index 000000000..2bb62595d
--- /dev/null
+++ b/browser/base/content/sync/customize.css
@@ -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/. */
+
+:root {
+ font-size: 80%;
+}
+
+#sync-customize-pane {
+ padding-inline-start: 74px;
+ background: top left url(chrome://browser/skin/sync-128.png) no-repeat;
+ background-size: 64px;
+}
+
+#sync-customize-title {
+ margin-inline-start: 0;
+ padding-bottom: 0.5em;
+ font-weight: bold;
+}
+
+#sync-customize-subtitle {
+ font-size: 90%;
+}
+
+checkbox {
+ margin: 0;
+ padding: 0.5em 0 0;
+}
diff --git a/browser/base/content/sync/customize.js b/browser/base/content/sync/customize.js
new file mode 100644
index 000000000..f431ac58c
--- /dev/null
+++ b/browser/base/content/sync/customize.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";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+addEventListener("dialogaccept", function () {
+ let pane = document.getElementById("sync-customize-pane");
+ // First determine what the preference for the "global" sync enabled pref
+ // should be based on the engines selected.
+ let prefElts = pane.querySelectorAll("preferences > preference");
+ let syncEnabled = false;
+ for (let elt of prefElts) {
+ if (elt.name.startsWith("services.sync.") && elt.value) {
+ syncEnabled = true;
+ break;
+ }
+ }
+ Services.prefs.setBoolPref("services.sync.enabled", syncEnabled);
+ // and write the individual prefs.
+ pane.writePreferences(true);
+ window.arguments[0].accepted = true;
+});
diff --git a/browser/base/content/sync/customize.xul b/browser/base/content/sync/customize.xul
new file mode 100644
index 000000000..d95536d9a
--- /dev/null
+++ b/browser/base/content/sync/customize.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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/customize.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % syncCustomizeDTD SYSTEM "chrome://browser/locale/syncCustomize.dtd">
+%syncCustomizeDTD;
+]>
+<dialog id="sync-customize"
+ windowtype="Sync:Customize"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&syncCustomize.dialog.title;"
+ buttonlabelaccept="&syncCustomize.acceptButton.label;"
+ buttons="accept">
+
+ <prefpane id="sync-customize-pane">
+ <preferences>
+ <preference id="engine.bookmarks" name="services.sync.engine.bookmarks" type="bool"/>
+ <preference id="engine.history" name="services.sync.engine.history" type="bool"/>
+ <preference id="engine.tabs" name="services.sync.engine.tabs" type="bool"/>
+ <preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
+ <preference id="engine.addons" name="services.sync.engine.addons" type="bool"/>
+ <preference id="engine.prefs" name="services.sync.engine.prefs" type="bool"/>
+ </preferences>
+
+ <label id="sync-customize-title" value="&syncCustomize.title;"/>
+ <description id="sync-customize-subtitle"
+#ifdef XP_UNIX
+ value="&syncCustomizeUnix.description;"
+#else
+ value="&syncCustomize.description;"
+#endif
+ />
+
+ <vbox align="start">
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ preference="engine.tabs"/>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ preference="engine.bookmarks"/>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ preference="engine.passwords"/>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ preference="engine.history"/>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ preference="engine.addons"/>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ preference="engine.prefs"/>
+ </vbox>
+
+ </prefpane>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/customize.js" />
+
+</dialog>
diff --git a/browser/base/content/sync/genericChange.js b/browser/base/content/sync/genericChange.js
new file mode 100644
index 000000000..51a74f1b1
--- /dev/null
+++ b/browser/base/content/sync/genericChange.js
@@ -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/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Change = {
+ _dialog: null,
+ _dialogType: null,
+ _status: null,
+ _statusIcon: null,
+ _firstBox: null,
+ _secondBox: null,
+
+ get _passphraseBox() {
+ delete this._passphraseBox;
+ return this._passphraseBox = document.getElementById("passphraseBox");
+ },
+
+ get _currentPasswordInvalid() {
+ return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+ },
+
+ get _updatingPassphrase() {
+ return this._dialogType == "UpdatePassphrase";
+ },
+
+ onLoad: function Change_onLoad() {
+ /* Load labels */
+ let introText = document.getElementById("introText");
+ let warningText = document.getElementById("warningText");
+
+ // load some other elements & info from the window
+ this._dialog = document.getElementById("change-dialog");
+ this._dialogType = window.arguments[0];
+ this._duringSetup = window.arguments[1];
+ this._status = document.getElementById("status");
+ this._statusIcon = document.getElementById("statusIcon");
+ this._statusRow = document.getElementById("statusRow");
+ this._firstBox = document.getElementById("textBox1");
+ this._secondBox = document.getElementById("textBox2");
+
+ this._dialog.getButton("finish").disabled = true;
+ this._dialog.getButton("back").hidden = true;
+
+ this._stringBundle =
+ Services.strings.createBundle("chrome://browser/locale/syncGenericChange.properties");
+
+ switch (this._dialogType) {
+ case "UpdatePassphrase":
+ case "ResetPassphrase":
+ document.getElementById("textBox1Row").hidden = true;
+ document.getElementById("textBox2Row").hidden = true;
+ document.getElementById("passphraseLabel").value
+ = this._str("new.recoverykey.label");
+ document.getElementById("passphraseSpacer").hidden = false;
+
+ if (this._updatingPassphrase) {
+ document.getElementById("passphraseHelpBox").hidden = false;
+ document.title = this._str("new.recoverykey.title");
+ introText.textContent = this._str("new.recoverykey.introText");
+ this._dialog.getButton("finish").label
+ = this._str("new.recoverykey.acceptButton");
+ }
+ else {
+ document.getElementById("generatePassphraseButton").hidden = false;
+ document.getElementById("passphraseBackupButtons").hidden = false;
+ this._passphraseBox.setAttribute("readonly", "true");
+ let pp = Weave.Service.identity.syncKey;
+ if (Weave.Utils.isPassphrase(pp))
+ pp = Weave.Utils.hyphenatePassphrase(pp);
+ this._passphraseBox.value = pp;
+ this._passphraseBox.focus();
+ document.title = this._str("change.recoverykey.title");
+ introText.textContent = this._str("change.synckey.introText2");
+ warningText.textContent = this._str("change.recoverykey.warningText");
+ this._dialog.getButton("finish").label
+ = this._str("change.recoverykey.acceptButton");
+ if (this._duringSetup) {
+ this._dialog.getButton("finish").disabled = false;
+ }
+ }
+ break;
+ case "ChangePassword":
+ document.getElementById("passphraseRow").hidden = true;
+ let box1label = document.getElementById("textBox1Label");
+ let box2label = document.getElementById("textBox2Label");
+ box1label.value = this._str("new.password.label");
+
+ if (this._currentPasswordInvalid) {
+ document.title = this._str("new.password.title");
+ introText.textContent = this._str("new.password.introText");
+ this._dialog.getButton("finish").label
+ = this._str("new.password.acceptButton");
+ document.getElementById("textBox2Row").hidden = true;
+ }
+ else {
+ document.title = this._str("change.password.title");
+ box2label.value = this._str("new.password.confirm");
+ introText.textContent = this._str("change.password3.introText");
+ warningText.textContent = this._str("change.password.warningText");
+ this._dialog.getButton("finish").label
+ = this._str("change.password.acceptButton");
+ }
+ break;
+ }
+ document.getElementById("change-page")
+ .setAttribute("label", document.title);
+ },
+
+ _clearStatus: function _clearStatus() {
+ this._status.value = "";
+ this._statusIcon.removeAttribute("status");
+ },
+
+ _updateStatus: function Change__updateStatus(str, state) {
+ this._updateStatusWithString(this._str(str), state);
+ },
+
+ _updateStatusWithString: function Change__updateStatusWithString(string, state) {
+ this._statusRow.hidden = false;
+ this._status.value = string;
+ this._statusIcon.setAttribute("status", state);
+
+ let error = state == "error";
+ this._dialog.getButton("cancel").disabled = !error;
+ this._dialog.getButton("finish").disabled = !error;
+ document.getElementById("printSyncKeyButton").disabled = !error;
+ document.getElementById("saveSyncKeyButton").disabled = !error;
+
+ if (state == "success")
+ window.setTimeout(window.close, 1500);
+ },
+
+ onDialogAccept: function() {
+ switch (this._dialogType) {
+ case "UpdatePassphrase":
+ case "ResetPassphrase":
+ return this.doChangePassphrase();
+ case "ChangePassword":
+ return this.doChangePassword();
+ }
+ return undefined;
+ },
+
+ doGeneratePassphrase: function () {
+ let passphrase = Weave.Utils.generatePassphrase();
+ this._passphraseBox.value = Weave.Utils.hyphenatePassphrase(passphrase);
+ this._dialog.getButton("finish").disabled = false;
+ },
+
+ doChangePassphrase: function Change_doChangePassphrase() {
+ let pp = Weave.Utils.normalizePassphrase(this._passphraseBox.value);
+ if (this._updatingPassphrase) {
+ Weave.Service.identity.syncKey = pp;
+ if (Weave.Service.login()) {
+ this._updateStatus("change.recoverykey.success", "success");
+ Weave.Service.persistLogin();
+ Weave.Service.scheduler.delayedAutoConnect(0);
+ }
+ else {
+ this._updateStatus("new.passphrase.status.incorrect", "error");
+ }
+ }
+ else {
+ this._updateStatus("change.recoverykey.label", "active");
+
+ if (Weave.Service.changePassphrase(pp))
+ this._updateStatus("change.recoverykey.success", "success");
+ else
+ this._updateStatus("change.recoverykey.error", "error");
+ }
+
+ return false;
+ },
+
+ doChangePassword: function Change_doChangePassword() {
+ if (this._currentPasswordInvalid) {
+ Weave.Service.identity.basicPassword = this._firstBox.value;
+ if (Weave.Service.login()) {
+ this._updateStatus("change.password.status.success", "success");
+ Weave.Service.persistLogin();
+ }
+ else {
+ this._updateStatus("new.password.status.incorrect", "error");
+ }
+ }
+ else {
+ this._updateStatus("change.password.status.active", "active");
+
+ if (Weave.Service.changePassword(this._firstBox.value))
+ this._updateStatus("change.password.status.success", "success");
+ else
+ this._updateStatus("change.password.status.error", "error");
+ }
+
+ return false;
+ },
+
+ validate: function (event) {
+ let valid = false;
+ let errorString = "";
+
+ if (this._dialogType == "ChangePassword") {
+ if (this._currentPasswordInvalid)
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox);
+ else
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox, this._secondBox);
+ }
+ else {
+ if (!this._updatingPassphrase)
+ return;
+
+ valid = this._passphraseBox.value != "";
+ }
+
+ if (errorString == "")
+ this._clearStatus();
+ else
+ this._updateStatusWithString(errorString, "error");
+
+ this._statusRow.hidden = valid;
+ this._dialog.getButton("finish").disabled = !valid;
+ },
+
+ _str: function Change__string(str) {
+ return this._stringBundle.GetStringFromName(str);
+ }
+};
diff --git a/browser/base/content/sync/genericChange.xul b/browser/base/content/sync/genericChange.xul
new file mode 100644
index 000000000..db74a1b31
--- /dev/null
+++ b/browser/base/content/sync/genericChange.xul
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="change-dialog"
+ windowtype="Weave:ChangeSomething"
+ persist="screenX screenY"
+ onwizardnext="Change.onLoad()"
+ onwizardfinish="return Change.onDialogAccept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/genericChange.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="change-page"
+ label="">
+
+ <description id="introText">
+ </description>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <grid>
+ <columns>
+ <column align="right"/>
+ <column flex="3"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="textBox1Row" align="center">
+ <label id="textBox1Label" control="textBox1"/>
+ <textbox id="textBox1" type="password" oninput="Change.validate()"/>
+ <spacer/>
+ </row>
+ <row id="textBox2Row" align="center">
+ <label id="textBox2Label" control="textBox2"/>
+ <textbox id="textBox2" type="password" oninput="Change.validate()"/>
+ <spacer/>
+ </row>
+ </rows>
+ </grid>
+
+ <vbox id="passphraseRow">
+ <hbox flex="1">
+ <label id="passphraseLabel" control="passphraseBox"/>
+ <spacer flex="1"/>
+ <label id="generatePassphraseButton"
+ hidden="true"
+ value="&syncGenerateNewKey.label;"
+ class="text-link"
+ onclick="event.stopPropagation();
+ Change.doGeneratePassphrase();"/>
+ </hbox>
+ <textbox id="passphraseBox"
+ flex="1"
+ onfocus="this.select()"
+ oninput="Change.validate()"/>
+ </vbox>
+
+ <vbox id="feedback" pack="center">
+ <hbox id="statusRow" align="center">
+ <image id="statusIcon" class="statusIcon"/>
+ <label id="status" class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox id="passphraseBackupButtons"
+ hidden="true"
+ pack="center">
+ <button id="printSyncKeyButton"
+ label="&button.syncKeyBackup.print.label;"
+ accesskey="&button.syncKeyBackup.print.accesskey;"
+ oncommand="gSyncUtils.passphrasePrint('passphraseBox');"/>
+ <button id="saveSyncKeyButton"
+ label="&button.syncKeyBackup.save.label;"
+ accesskey="&button.syncKeyBackup.save.accesskey;"
+ oncommand="gSyncUtils.passphraseSave('passphraseBox');"/>
+ </hbox>
+
+ <vbox id="passphraseHelpBox"
+ hidden="true">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="https://services.mozilla.com/sync/help/manual-setup">
+ &addDevice.showMeHow.label;
+ </label>
+ </description>
+ </vbox>
+
+ <spacer id="passphraseSpacer"
+ flex="1"
+ hidden="true"/>
+
+ <description id="warningText" class="data">
+ </description>
+
+ <spacer flex="1"/>
+ </wizardpage>
+</wizard>
diff --git a/browser/base/content/sync/key.xhtml b/browser/base/content/sync/key.xhtml
new file mode 100644
index 000000000..1363132e7
--- /dev/null
+++ b/browser/base/content/sync/key.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+ %syncBrandDTD;
+ <!ENTITY % syncKeyDTD SYSTEM "chrome://browser/locale/syncKey.dtd">
+ %syncKeyDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
+ %globalDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&syncKey.page.title;</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="robots" content="noindex"/>
+ <style type="text/css">
+ #synckey { font-size: 150% }
+ footer { font-size: 70% }
+ /* Bug 575675: Need to have an a:visited rule in a chrome document. */
+ a:visited { color: purple; }
+ </style>
+</head>
+
+<body dir="&locale.dir;">
+<h1>&syncKey.page.title;</h1>
+
+<p id="synckey" dir="ltr">SYNCKEY</p>
+
+<p>&syncKey.page.description2;</p>
+
+<div id="column1">
+ <h2>&syncKey.keepItSecret.heading;</h2>
+ <p>&syncKey.keepItSecret.description;</p>
+</div>
+
+<div id="column2">
+ <h2>&syncKey.keepItSafe.heading;</h2>
+ <p><em>&syncKey.keepItSafe1.description;</em>&syncKey.keepItSafe2.description;<em>&syncKey.keepItSafe3.description;</em>&syncKey.keepItSafe4a.description;</p>
+</div>
+
+<p>&syncKey.findOutMore1.label;<a href="https://services.mozilla.com">https://services.mozilla.com</a>&syncKey.findOutMore2.label;</p>
+
+<footer>
+ &syncKey.footer1.label;<a id="tosLink" href="termsURL">termsURL</a>&syncKey.footer2.label;<a id="ppLink" href="privacyURL">privacyURL</a>&syncKey.footer3.label;
+</footer>
+
+</body>
+</html>
diff --git a/browser/base/content/sync/setup.js b/browser/base/content/sync/setup.js
new file mode 100644
index 000000000..f9dae1bd4
--- /dev/null
+++ b/browser/base/content/sync/setup.js
@@ -0,0 +1,1060 @@
+// -*- 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 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() {
+ return [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", () => 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;
+ this._settingUpNew = true;
+ this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE;
+ },
+
+ useExistingAccount: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return;
+ this._settingUpNew = false;
+ if (this.wizardType == "pair") {
+ // We're already pairing, so there's no point in pairing again.
+ // Go straight to the manual login page.
+ this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ } else {
+ this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE;
+ }
+ },
+
+ resetPassphrase: function resetPassphrase() {
+ // Apply the existing form fields so that
+ // Weave.Service.changePassphrase() has the necessary credentials.
+ Weave.Service.identity.account = document.getElementById("existingAccountName").value;
+ Weave.Service.identity.basicPassword = document.getElementById("existingPassword").value;
+
+ // Generate a new passphrase so that Weave.Service.login() will
+ // actually do something.
+ let passphrase = Weave.Utils.generatePassphrase();
+ Weave.Service.identity.syncKey = passphrase;
+
+ // Only open the dialog if username + password are actually correct.
+ Weave.Service.login();
+ if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE,
+ Weave.LOGIN_FAILED_NO_PASSPHRASE,
+ Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) {
+ return;
+ }
+
+ // Hide any errors about the passphrase, we know it's not right.
+ let feedback = document.getElementById("existingPassphraseFeedbackRow");
+ feedback.hidden = true;
+ let el = document.getElementById("existingPassphrase");
+ el.value = Weave.Utils.hyphenatePassphrase(passphrase);
+
+ // changePassphrase() will sync, make sure we set the "firstSync" pref
+ // according to the user's pref.
+ Weave.Svc.Prefs.reset("firstSync");
+ this.setupInitialSync();
+ gSyncUtils.resetPassphrase(true);
+ },
+
+ onResetPassphrase: function () {
+ document.getElementById("existingPassphrase").value =
+ Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+ this.checkFields();
+ this.wizard.advance();
+ },
+
+ onLoginStart: function () {
+ this.toggleLoginFeedback(false);
+ },
+
+ onLoginEnd: function () {
+ this.toggleLoginFeedback(true);
+ },
+
+ sendCredentialsAfterSync: function () {
+ let send = function() {
+ Services.obs.removeObserver("weave:service:sync:finish", send);
+ Services.obs.removeObserver("weave:service:sync:error", send);
+ let credentials = {account: Weave.Service.identity.account,
+ password: Weave.Service.identity.basicPassword,
+ synckey: Weave.Service.identity.syncKey,
+ serverURL: Weave.Service.serverURL};
+ this._jpakeclient.sendAndComplete(credentials);
+ }.bind(this);
+ Services.obs.addObserver("weave:service:sync:finish", send, false);
+ Services.obs.addObserver("weave:service:sync:error", send, false);
+ },
+
+ toggleLoginFeedback: function (stop) {
+ document.getElementById("login-throbber").hidden = stop;
+ let password = document.getElementById("existingPasswordFeedbackRow");
+ let server = document.getElementById("existingServerFeedbackRow");
+ let passphrase = document.getElementById("existingPassphraseFeedbackRow");
+
+ if (!stop || (Weave.Status.login == Weave.LOGIN_SUCCEEDED)) {
+ password.hidden = server.hidden = passphrase.hidden = true;
+ return;
+ }
+
+ let feedback;
+ switch (Weave.Status.login) {
+ case Weave.LOGIN_FAILED_NETWORK_ERROR:
+ case Weave.LOGIN_FAILED_SERVER_ERROR:
+ feedback = server;
+ break;
+ case Weave.LOGIN_FAILED_LOGIN_REJECTED:
+ case Weave.LOGIN_FAILED_NO_USERNAME:
+ case Weave.LOGIN_FAILED_NO_PASSWORD:
+ feedback = password;
+ break;
+ case Weave.LOGIN_FAILED_INVALID_PASSPHRASE:
+ feedback = passphrase;
+ break;
+ }
+ this._setFeedbackMessage(feedback, false, Weave.Status.login);
+ },
+
+ setupInitialSync: function () {
+ let action = document.getElementById("mergeChoiceRadio").selectedItem.id;
+ switch (action) {
+ case "resetClient":
+ // if we're not resetting sync, we don't need to explicitly
+ // call resetClient
+ if (!this._resettingSync)
+ return;
+ // otherwise, fall through
+ case "wipeClient":
+ case "wipeRemote":
+ Weave.Svc.Prefs.set("firstSync", action);
+ break;
+ }
+ },
+
+ // fun with validation!
+ checkFields: function () {
+ this.wizard.canAdvance = this.readyToAdvance();
+ },
+
+ readyToAdvance: function () {
+ switch (this.wizard.pageIndex) {
+ case INTRO_PAGE:
+ return false;
+ case NEW_ACCOUNT_START_PAGE:
+ for (let i in this.status) {
+ if (!this.status[i])
+ return false;
+ }
+ if (this._usingMainServers)
+ return document.getElementById("tos").checked;
+
+ return true;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ let hasUser = document.getElementById("existingAccountName").value != "";
+ let hasPass = document.getElementById("existingPassword").value != "";
+ let hasKey = document.getElementById("existingPassphrase").value != "";
+
+ if (hasUser && hasPass && hasKey) {
+ if (this._usingMainServers)
+ return true;
+
+ if (this._validateServer(document.getElementById("existingServer"))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // Default, e.g. wizard's special page -1 etc.
+ return true;
+ },
+
+ onPINInput: function onPINInput(textbox) {
+ if (textbox && textbox.value.length == PIN_PART_LENGTH) {
+ this.nextFocusEl[textbox.id].focus();
+ }
+ this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH &&
+ this.pin2.value.length == PIN_PART_LENGTH &&
+ this.pin3.value.length == PIN_PART_LENGTH);
+ },
+
+ onEmailInput: function () {
+ // Check account validity when the user stops typing for 1 second.
+ if (this._checkAccountTimer)
+ window.clearTimeout(this._checkAccountTimer);
+ this._checkAccountTimer = window.setTimeout(function () {
+ gSyncSetup.checkAccount();
+ }, 1000);
+ },
+
+ checkAccount: function() {
+ delete this._checkAccountTimer;
+ let value = Weave.Utils.normalizeAccount(
+ document.getElementById("weaveEmail").value);
+ if (!value) {
+ this.status.email = false;
+ this.checkFields();
+ return;
+ }
+
+ let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ let feedback = document.getElementById("emailFeedbackRow");
+ let valid = re.test(value);
+
+ let str = "";
+ if (!valid) {
+ str = "invalidEmail.label";
+ } else {
+ let availCheck = Weave.Service.checkAccount(value);
+ valid = availCheck == "available";
+ if (!valid) {
+ if (availCheck == "notAvailable")
+ str = "usernameNotAvailable.label";
+ else
+ str = availCheck;
+ }
+ }
+
+ this._setFeedbackMessage(feedback, valid, str);
+ this.status.email = valid;
+ if (valid)
+ Weave.Service.identity.account = value;
+ this.checkFields();
+ },
+
+ onPasswordChange: function () {
+ let password = document.getElementById("weavePassword");
+ let pwconfirm = document.getElementById("weavePasswordConfirm");
+ let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm);
+
+ let feedback = document.getElementById("passwordFeedbackRow");
+ this._setFeedback(feedback, valid, errorString);
+
+ this.status.password = valid;
+ this.checkFields();
+ },
+
+ onPageShow: function() {
+ switch (this.wizard.pageIndex) {
+ case PAIR_PAGE:
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("extra1").hidden = true;
+ this.onPINInput();
+ this.pin1.focus();
+ break;
+ case INTRO_PAGE:
+ // We may not need the captcha in the Existing Account branch of the
+ // wizard. However, we want to preload it to avoid any flickering while
+ // the Create Account page is shown.
+ this.loadCaptcha();
+ this.wizard.getButton("next").hidden = true;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("extra1").hidden = true;
+ this.checkFields();
+ break;
+ case NEW_ACCOUNT_START_PAGE:
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.onServerCommand();
+ this.wizard.canRewind = true;
+ this.checkFields();
+ break;
+ case EXISTING_ACCOUNT_CONNECT_PAGE:
+ Weave.Svc.Prefs.set("firstSync", "existingAccount");
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.canAdvance = false;
+ this.wizard.canRewind = true;
+ this.startEasySetup();
+ break;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.canRewind = true;
+ this.checkFields();
+ break;
+ case OPTIONS_PAGE:
+ this.wizard.canRewind = false;
+ this.wizard.canAdvance = true;
+ if (!this._resettingSync) {
+ this.wizard.getButton("next").label =
+ this._stringBundle.GetStringFromName("button.syncOptionsDone.label");
+ this.wizard.getButton("next").removeAttribute("accesskey");
+ }
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("cancel").hidden = !this._resettingSync;
+ this.wizard.getButton("extra1").hidden = true;
+ document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
+ document.getElementById("syncOptions").collapsed = this._resettingSync;
+ document.getElementById("mergeOptions").collapsed = this._settingUpNew;
+ break;
+ case OPTIONS_CONFIRM_PAGE:
+ this.wizard.canRewind = true;
+ this.wizard.canAdvance = true;
+ this.wizard.getButton("back").label =
+ this._stringBundle.GetStringFromName("button.syncOptionsCancel.label");
+ this.wizard.getButton("back").removeAttribute("accesskey");
+ this.wizard.getButton("back").hidden = this._resettingSync;
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("finish").hidden = true;
+ break;
+ }
+ },
+
+ onWizardAdvance: function () {
+ // Check pageIndex so we don't prompt before the Sync setup wizard appears.
+ // This is a fallback in case the Master Password gets locked mid-wizard.
+ if ((this.wizard.pageIndex >= 0) &&
+ !Weave.Utils.ensureMPUnlocked()) {
+ return false;
+ }
+
+ switch (this.wizard.pageIndex) {
+ case PAIR_PAGE:
+ this.startPairing();
+ return false;
+ case NEW_ACCOUNT_START_PAGE:
+ // If the user selects Next (e.g. by hitting enter) when we haven't
+ // executed the delayed checks yet, execute them immediately.
+ if (this._checkAccountTimer) {
+ this.checkAccount();
+ }
+ if (this._checkServerTimer) {
+ this.checkServer();
+ }
+ if (!this.wizard.canAdvance) {
+ return false;
+ }
+
+ let doc = this.captchaBrowser.contentDocument;
+ let getField = function getField(field) {
+ let node = doc.getElementById("recaptcha_" + field + "_field");
+ return node && node.value;
+ };
+
+ // Display throbber
+ let feedback = document.getElementById("captchaFeedback");
+ let image = feedback.firstChild;
+ let label = image.nextSibling;
+ image.setAttribute("status", "active");
+ label.value = this._stringBundle.GetStringFromName("verifying.label");
+ setVisibility(feedback, true);
+
+ let password = document.getElementById("weavePassword").value;
+ let email = Weave.Utils.normalizeAccount(
+ document.getElementById("weaveEmail").value);
+ let challenge = getField("challenge");
+ let response = getField("response");
+
+ let error = Weave.Service.createAccount(email, password,
+ challenge, response);
+
+ if (error == null) {
+ Weave.Service.identity.account = email;
+ Weave.Service.identity.basicPassword = password;
+ Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase();
+ this._handleNoScript(false);
+ Weave.Svc.Prefs.set("firstSync", "newAccount");
+ this.wizardFinish();
+ return false;
+ }
+
+ image.setAttribute("status", "error");
+ label.value = Weave.Utils.getErrorString(error);
+ return false;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ Weave.Service.identity.account = Weave.Utils.normalizeAccount(
+ document.getElementById("existingAccountName").value);
+ Weave.Service.identity.basicPassword =
+ document.getElementById("existingPassword").value;
+ let pp = document.getElementById("existingPassphrase").value;
+ Weave.Service.identity.syncKey = Weave.Utils.normalizePassphrase(pp);
+ if (Weave.Service.login()) {
+ this.wizardFinish();
+ }
+ return false;
+ case OPTIONS_PAGE:
+ let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
+ // No confirmation needed on new account setup or merge option
+ // with existing account.
+ if (this._settingUpNew || (!this._resettingSync && desc == 0))
+ return this.returnFromOptions();
+ return this._handleChoice();
+ case OPTIONS_CONFIRM_PAGE:
+ if (this._resettingSync) {
+ this.wizardFinish();
+ return false;
+ }
+ return this.returnFromOptions();
+ }
+ return true;
+ },
+
+ onWizardBack: function () {
+ switch (this.wizard.pageIndex) {
+ case NEW_ACCOUNT_START_PAGE:
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ case EXISTING_ACCOUNT_CONNECT_PAGE:
+ this.abortEasySetup();
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ // If we were already pairing on entry, we went straight to the manual
+ // login page. If subsequently we go back, return to the page that lets
+ // us choose whether we already have an account.
+ if (this.wizardType == "pair") {
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ }
+ return true;
+ case OPTIONS_CONFIRM_PAGE:
+ // Backing up from the confirmation page = resetting first sync to merge.
+ document.getElementById("mergeChoiceRadio").selectedIndex = 0;
+ return this.returnFromOptions();
+ }
+ return true;
+ },
+
+ wizardFinish: function () {
+ this.setupInitialSync();
+
+ if (this.wizardType == "pair") {
+ this.completePairing();
+ }
+
+ if (!this._resettingSync) {
+ function isChecked(element) {
+ return document.getElementById(element).hasAttribute("checked");
+ }
+
+ let prefs = ["engine.bookmarks", "engine.passwords", "engine.history",
+ "engine.tabs", "engine.prefs", "engine.addons"];
+ for (let i = 0;i < prefs.length;i++) {
+ Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i]));
+ }
+ this._handleNoScript(false);
+ if (Weave.Svc.Prefs.get("firstSync", "") == "notReady")
+ Weave.Svc.Prefs.reset("firstSync");
+
+ Weave.Service.persistLogin();
+ Weave.Svc.Obs.notify("weave:service:setup-complete");
+ }
+ Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
+ window.close();
+ },
+
+ onWizardCancel: function () {
+ if (this._resettingSync)
+ return;
+
+ this.abortEasySetup();
+ this._handleNoScript(false);
+ Weave.Service.startOver();
+ },
+
+ onSyncOptions: function () {
+ this._beforeOptionsPage = this.wizard.pageIndex;
+ this.wizard.pageIndex = OPTIONS_PAGE;
+ },
+
+ returnFromOptions: function() {
+ this.wizard.getButton("next").label = this._nextButtonLabel;
+ this.wizard.getButton("next").setAttribute("accesskey",
+ this._nextButtonAccesskey);
+ this.wizard.getButton("back").label = this._backButtonLabel;
+ this.wizard.getButton("back").setAttribute("accesskey",
+ this._backButtonAccesskey);
+ this.wizard.getButton("cancel").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.pageIndex = this._beforeOptionsPage;
+ return false;
+ },
+
+ startPairing: function startPairing() {
+ this.pairDeviceErrorRow.hidden = true;
+ // When onAbort is called, Weave may already be gone.
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+ onPaired: function onPaired() {
+ self.wizard.pageIndex = INTRO_PAGE;
+ },
+ onComplete: function onComplete() {
+ // This method will never be called since SendCredentialsController
+ // will take over after the wizard completes.
+ },
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Aborted by user, ignore. The window is almost certainly going to close
+ // or is already closed.
+ if (error == JPAKE_ERROR_USERABORT) {
+ return;
+ }
+
+ self.pairDeviceErrorRow.hidden = false;
+ self.pairDeviceThrobber.hidden = true;
+ self.pin1.value = self.pin2.value = self.pin3.value = "";
+ self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+ if (self.wizard.pageIndex == PAIR_PAGE) {
+ self.pin1.focus();
+ }
+ }
+ });
+ this.pairDeviceThrobber.hidden = false;
+ this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+ this.wizard.canAdvance = false;
+
+ let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+ let expectDelay = true;
+ jpakeclient.pairWithPIN(pin, expectDelay);
+ },
+
+ completePairing: function completePairing() {
+ if (!this._jpakeclient) {
+ // The channel was aborted while we were setting up the account
+ // locally. XXX TODO should we do anything here, e.g. tell
+ // the user on the last wizard page that it's ok, they just
+ // have to pair again?
+ return;
+ }
+ let controller = new Weave.SendCredentialsController(this._jpakeclient,
+ Weave.Service);
+ this._jpakeclient.controller = controller;
+ },
+
+ startEasySetup: function () {
+ // Don't do anything if we have a client already (e.g. we went to
+ // Sync Options and just came back).
+ if (this._jpakeclient)
+ return;
+
+ // When onAbort is called, Weave may already be gone
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ this._jpakeclient = new Weave.JPAKEClient({
+ displayPIN: function displayPIN(pin) {
+ document.getElementById("easySetupPIN1").value = pin.slice(0, 4);
+ document.getElementById("easySetupPIN2").value = pin.slice(4, 8);
+ document.getElementById("easySetupPIN3").value = pin.slice(8);
+ },
+
+ onPairingStart: function onPairingStart() {},
+
+ onComplete: function onComplete(credentials) {
+ Weave.Service.identity.account = credentials.account;
+ Weave.Service.identity.basicPassword = credentials.password;
+ Weave.Service.identity.syncKey = credentials.synckey;
+ Weave.Service.serverURL = credentials.serverURL;
+ gSyncSetup.wizardFinish();
+ },
+
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Ignore if wizard is aborted.
+ if (error == JPAKE_ERROR_USERABORT)
+ return;
+
+ // Automatically go to manual setup if we couldn't acquire a channel.
+ if (error == Weave.JPAKE_ERROR_CHANNEL) {
+ self.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ return;
+ }
+
+ // Restart on all other errors.
+ self.startEasySetup();
+ }
+ });
+ this._jpakeclient.receiveNoPIN();
+ },
+
+ abortEasySetup: function () {
+ document.getElementById("easySetupPIN1").value = "";
+ document.getElementById("easySetupPIN2").value = "";
+ document.getElementById("easySetupPIN3").value = "";
+ if (!this._jpakeclient)
+ return;
+
+ this._jpakeclient.abort();
+ delete this._jpakeclient;
+ },
+
+ manualSetup: function () {
+ this.abortEasySetup();
+ this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ },
+
+ // _handleNoScript is needed because it blocks the captcha. So we temporarily
+ // allow the necessary sites so that we can verify the user is in fact a human.
+ // This was done with the help of Giorgio (NoScript author). See bug 508112.
+ _handleNoScript: function (addExceptions) {
+ // if NoScript isn't installed, or is disabled, bail out.
+ let ns = Cc["@maone.net/noscript-service;1"];
+ if (ns == null)
+ return;
+
+ ns = ns.getService().wrappedJSObject;
+ if (addExceptions) {
+ this._remoteSites.forEach(function(site) {
+ site = ns.getSite(site);
+ if (!ns.isJSEnabled(site)) {
+ this._disabledSites.push(site); // save status
+ ns.setJSEnabled(site, true); // allow site
+ }
+ }, this);
+ }
+ else {
+ this._disabledSites.forEach(function(site) {
+ ns.setJSEnabled(site, false);
+ });
+ this._disabledSites = [];
+ }
+ },
+
+ onExistingServerCommand: function () {
+ let control = document.getElementById("existingServer");
+ if (control.selectedIndex == 0) {
+ control.removeAttribute("editable");
+ Weave.Svc.Prefs.reset("serverURL");
+ } else {
+ control.setAttribute("editable", "true");
+ // Force a style flush to ensure that the binding is attached.
+ control.clientTop;
+ control.value = "";
+ control.inputField.focus();
+ }
+ document.getElementById("existingServerFeedbackRow").hidden = true;
+ this.checkFields();
+ },
+
+ onExistingServerInput: function () {
+ // Check custom server validity when the user stops typing for 1 second.
+ if (this._existingServerTimer)
+ window.clearTimeout(this._existingServerTimer);
+ this._existingServerTimer = window.setTimeout(function () {
+ gSyncSetup.checkFields();
+ }, 1000);
+ },
+
+ onServerCommand: function () {
+ setVisibility(document.getElementById("TOSRow"), this._usingMainServers);
+ let control = document.getElementById("server");
+ if (!this._usingMainServers) {
+ control.setAttribute("editable", "true");
+ // Force a style flush to ensure that the binding is attached.
+ control.clientTop;
+ control.value = "";
+ control.inputField.focus();
+ // checkServer() will call checkAccount() and checkFields().
+ this.checkServer();
+ return;
+ }
+ control.removeAttribute("editable");
+ Weave.Svc.Prefs.reset("serverURL");
+ if (this._settingUpNew) {
+ this.loadCaptcha();
+ }
+ this.checkAccount();
+ this.status.server = true;
+ document.getElementById("serverFeedbackRow").hidden = true;
+ this.checkFields();
+ },
+
+ onServerInput: function () {
+ // Check custom server validity when the user stops typing for 1 second.
+ if (this._checkServerTimer)
+ window.clearTimeout(this._checkServerTimer);
+ this._checkServerTimer = window.setTimeout(function () {
+ gSyncSetup.checkServer();
+ }, 1000);
+ },
+
+ checkServer: function () {
+ delete this._checkServerTimer;
+ let el = document.getElementById("server");
+ let valid = false;
+ let feedback = document.getElementById("serverFeedbackRow");
+ 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 = Object.keys(ids).filter(id => ids[id]).length;
+ // 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 (let name of Weave.Service.clientsEngine.stats.names) {
+ // Don't list the current client
+ if (name == Weave.Service.clientsEngine.localName)
+ continue;
+
+ // Only show the first several client names
+ if (++count <= 5)
+ appendNode(name);
+ }
+ if (count > 5) {
+ // Support %S for historical reasons (see bug 600141)
+ let label =
+ PluralForm.get(count - 5,
+ this._stringBundle.GetStringFromName("additionalClientCount.label"))
+ .replace("%S", count - 5)
+ .replace("#1", count - 5);
+ appendNode(label);
+ }
+ this._case2Setup = true;
+ break;
+ }
+
+ return true;
+ },
+
+ // sets class and string on a feedback element
+ // if no property string is passed in, we clear label/style
+ _setFeedback: function (element, success, string) {
+ element.hidden = success || !string;
+ let classname = success ? "success" : "error";
+ let image = element.getElementsByAttribute("class", "statusIcon")[0];
+ image.setAttribute("status", classname);
+ let label = element.getElementsByAttribute("class", "status")[0];
+ label.value = string;
+ },
+
+ // shim
+ _setFeedbackMessage: function (element, success, string) {
+ let str = "";
+ if (string) {
+ try {
+ str = this._stringBundle.GetStringFromName(string);
+ } catch (e) {}
+
+ if (!str)
+ str = Weave.Utils.getErrorString(string);
+ }
+ this._setFeedback(element, success, str);
+ },
+
+ loadCaptcha: function loadCaptcha() {
+ let captchaURI = Weave.Service.miscAPI + "captcha_html";
+ // First check for NoScript and whitelist the right sites.
+ this._handleNoScript(true);
+ if (this.captchaBrowser.currentURI.spec != captchaURI) {
+ this.captchaBrowser.loadURI(captchaURI);
+ }
+ },
+
+ onStateChange: function(webProgress, request, stateFlags, status) {
+ // We're only looking for the end of the frame load
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0)
+ return;
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) == 0)
+ return;
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) == 0)
+ return;
+
+ // If we didn't find a captcha, assume it's not needed and don't show it.
+ let responseStatus = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
+ setVisibility(this.captchaBrowser, responseStatus != 404);
+ // XXX TODO we should really log any responseStatus other than 200
+ },
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+ onLocationChange: function () {}
+};
+
+// Define lazy getters for various XUL elements.
+//
+// onWizardAdvance() and onPageShow() are run before init(), so we'll even
+// define things that will almost certainly be used (like 'wizard') as a lazy
+// getter here.
+["wizard",
+ "pin1",
+ "pin2",
+ "pin3",
+ "pairDeviceErrorRow",
+ "pairDeviceThrobber"].forEach(function (id) {
+ XPCOMUtils.defineLazyGetter(gSyncSetup, id, function() {
+ return document.getElementById(id);
+ });
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "nextFocusEl", function () {
+ return {pin1: this.pin2,
+ pin2: this.pin3,
+ pin3: this.wizard.getButton("next")};
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() {
+ return Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+});
diff --git a/browser/base/content/sync/setup.xul b/browser/base/content/sync/setup.xul
new file mode 100644
index 000000000..11c085931
--- /dev/null
+++ b/browser/base/content/sync/setup.xul
@@ -0,0 +1,490 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard id="wizard"
+ title="&accountSetupTitle.label;"
+ windowtype="Weave:AccountSetup"
+ persist="screenX screenY"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onwizardnext="return gSyncSetup.onWizardAdvance()"
+ onwizardback="return gSyncSetup.onWizardBack()"
+ onwizardcancel="gSyncSetup.onWizardCancel()"
+ onload="gSyncSetup.init()">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/setup.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="addDevicePage"
+ label="&pairDevice.title.label;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <description>
+ &pairDevice.dialog.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/add-device"/>
+ </description>
+ <separator class="groove-thin"/>
+ <description>
+ &addDevice.dialog.enterCode.label;
+ </description>
+ <separator class="groove-thin"/>
+ <vbox align="center">
+ <textbox id="pin1"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin2"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin3"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ </vbox>
+ <separator class="groove-thin"/>
+ <vbox id="pairDeviceThrobber" align="center" hidden="true">
+ <image/>
+ </vbox>
+ <hbox id="pairDeviceErrorRow" pack="center" hidden="true">
+ <image class="statusIcon" status="error"/>
+ <label class="status"
+ value="&addDevice.dialog.tryAgain.label;"/>
+ </hbox>
+ </wizardpage>
+
+ <wizardpage id="pickSetupType"
+ label="&syncBrand.fullName.label;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <vbox align="center" flex="1">
+ <description style="padding: 0 7em;">
+ &setup.pickSetupType.description2;
+ </description>
+ <spacer flex="3"/>
+ <button id="newAccount"
+ class="accountChoiceButton"
+ label="&button.createNewAccount.label;"
+ oncommand="gSyncSetup.startNewAccountSetup()"
+ align="center"/>
+ <spacer flex="1"/>
+ </vbox>
+ <separator class="groove"/>
+ <vbox align="center" flex="1">
+ <spacer flex="1"/>
+ <button id="existingAccount"
+ class="accountChoiceButton"
+ label="&button.haveAccount.label;"
+ oncommand="gSyncSetup.useExistingAccount()"/>
+ <spacer flex="3"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage label="&setup.newAccountDetailsPage.title.label;"
+ id="newAccountStart"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow();">
+ <grid>
+ <columns>
+ <column/>
+ <column class="inputColumn" flex="1"/>
+ </columns>
+ <rows>
+ <row id="emailRow" align="center">
+ <label value="&setup.emailAddress.label;"
+ accesskey="&setup.emailAddress.accesskey;"
+ control="weaveEmail"/>
+ <textbox id="weaveEmail"
+ oninput="gSyncSetup.onEmailInput()"/>
+ </row>
+ <row id="emailFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row id="passwordRow" align="center">
+ <label value="&setup.choosePassword.label;"
+ accesskey="&setup.choosePassword.accesskey;"
+ control="weavePassword"/>
+ <textbox id="weavePassword"
+ type="password"
+ onchange="gSyncSetup.onPasswordChange()"/>
+ </row>
+ <row id="confirmRow" align="center">
+ <label value="&setup.confirmPassword.label;"
+ accesskey="&setup.confirmPassword.accesskey;"
+ control="weavePasswordConfirm"/>
+ <textbox id="weavePasswordConfirm"
+ type="password"
+ onchange="gSyncSetup.onPasswordChange()"/>
+ </row>
+ <row id="passwordFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="server"
+ value="&server.label;"/>
+ <menulist id="server"
+ oncommand="gSyncSetup.onServerCommand()"
+ oninput="gSyncSetup.onServerInput()">
+ <menupopup>
+ <menuitem label="&serverType.default.label;"
+ value="main"/>
+ <menuitem label="&serverType.custom2.label;"
+ value="custom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="serverFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <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"
+ onclick="event.stopPropagation();gSyncUtils.openToS();">
+ &setup.tosLink.label;
+ </label>
+ &setup.tosAgree2.label;
+ <label class="text-link"
+ onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();">
+ &setup.ppLink.label;
+ </label>
+ &setup.tosAgree3.label;
+ </description>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ <vbox flex="1" align="center">
+ <browser height="150"
+ width="500"
+ id="captcha"
+ type="content"
+ disablehistory="true"/>
+ <spacer flex="1"/>
+ <hbox id="captchaFeedback">
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="addDevice"
+ label="&pairDevice.title.label;"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow()">
+ <description>
+ &pairDevice.setup.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/easy-setup"/>
+ </description>
+ <label value="&addDevice.setup.enterCode.label;"
+ control="easySetupPIN1"/>
+ <spacer flex="1"/>
+ <vbox align="center" flex="1">
+ <textbox id="easySetupPIN1"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ <textbox id="easySetupPIN2"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ <textbox id="easySetupPIN3"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ </vbox>
+ <spacer flex="3"/>
+ <label class="text-link"
+ value="&addDevice.dontHaveDevice.label;"
+ onclick="gSyncSetup.manualSetup();"/>
+ </wizardpage>
+
+ <wizardpage id="existingAccount"
+ label="&setup.signInPage.title.label;"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow()">
+ <grid>
+ <columns>
+ <column/>
+ <column class="inputColumn" flex="1"/>
+ </columns>
+ <rows>
+ <row id="existingAccountRow" align="center">
+ <label id="existingAccountLabel"
+ value="&signIn.account2.label;"
+ accesskey="&signIn.account2.accesskey;"
+ control="existingAccount"/>
+ <textbox id="existingAccountName"
+ oninput="gSyncSetup.checkFields(event)"
+ onchange="gSyncSetup.checkFields(event)"/>
+ </row>
+ <row id="existingPasswordRow" align="center">
+ <label id="existingPasswordLabel"
+ value="&signIn.password.label;"
+ accesskey="&signIn.password.accesskey;"
+ control="existingPassword"/>
+ <textbox id="existingPassword"
+ type="password"
+ onkeyup="gSyncSetup.checkFields(event)"
+ onchange="gSyncSetup.checkFields(event)"/>
+ </row>
+ <row id="existingPasswordFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row align="center">
+ <spacer/>
+ <label class="text-link"
+ value="&resetPassword.label;"
+ onclick="gSyncUtils.resetPassword(); return false;"/>
+ </row>
+ <row align="center">
+ <label control="existingServer"
+ value="&server.label;"/>
+ <menulist id="existingServer"
+ oncommand="gSyncSetup.onExistingServerCommand()"
+ oninput="gSyncSetup.onExistingServerInput()">
+ <menupopup>
+ <menuitem label="&serverType.default.label;"
+ value="main"/>
+ <menuitem label="&serverType.custom2.label;"
+ value="custom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="existingServerFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <vbox>
+ <label class="status" value=" "/>
+ </vbox>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <groupbox>
+ <label id="existingPassphraseLabel"
+ value="&signIn.recoveryKey.label;"
+ accesskey="&signIn.recoveryKey.accesskey;"
+ control="existingPassphrase"/>
+ <textbox id="existingPassphrase"
+ oninput="gSyncSetup.checkFields()"/>
+ <hbox id="login-throbber" hidden="true">
+ <image/>
+ <label value="&verifying.label;"/>
+ </hbox>
+ <vbox align="left" id="existingPassphraseFeedbackRow" hidden="true">
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <vbox id="passphraseHelpBox">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="https://services.mozilla.com/sync/help/manual-setup">
+ &addDevice.showMeHow.label;
+ </label>
+ <spacer id="passphraseHelpSpacer"/>
+ <label class="text-link"
+ onclick="gSyncSetup.resetPassphrase(); return false;">
+ &resetSyncKey.label;
+ </label>
+ </description>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="syncOptionsPage"
+ label="&setup.optionsPage.title;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <groupbox id="syncOptions">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1" style="margin-inline-end: 2px"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&syncDeviceName.label;"
+ accesskey="&syncDeviceName.accesskey;"
+ control="syncComputerName"/>
+ <textbox id="syncComputerName" flex="1"
+ onchange="gSyncUtils.changeName(this)"/>
+ </row>
+ <row>
+ <label value="&syncMy.label;" />
+ <vbox>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ id="engine.addons"
+ checked="true"/>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ id="engine.bookmarks"
+ checked="true"/>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ id="engine.passwords"
+ checked="true"/>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ id="engine.prefs"
+ checked="true"/>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ id="engine.history"
+ checked="true"/>
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ id="engine.tabs"
+ checked="true"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox id="mergeOptions">
+ <radiogroup id="mergeChoiceRadio" pack="start">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows flex="1">
+ <row align="center">
+ <radio id="resetClient"
+ class="mergeChoiceButton"
+ aria-labelledby="resetClientLabel"/>
+ <label id="resetClientLabel" control="resetClient">
+ <html:strong>&choice2.merge.recommended.label;</html:strong>
+ &choice2a.merge.main.label;
+ </label>
+ </row>
+ <row align="center">
+ <radio id="wipeClient"
+ class="mergeChoiceButton"
+ aria-labelledby="wipeClientLabel"/>
+ <label id="wipeClientLabel"
+ control="wipeClient">
+ &choice2a.client.main.label;
+ </label>
+ </row>
+ <row align="center">
+ <radio id="wipeRemote"
+ class="mergeChoiceButton"
+ aria-labelledby="wipeRemoteLabel"/>
+ <label id="wipeRemoteLabel"
+ control="wipeRemote">
+ &choice2a.server.main.label;
+ </label>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ </groupbox>
+ </wizardpage>
+
+ <wizardpage id="syncOptionsConfirm"
+ label="&setup.optionsConfirmPage.title;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <deck id="chosenActionDeck">
+ <vbox id="chosenActionMerge" class="confirm">
+ <description class="normal">
+ &confirm.merge2.label;
+ </description>
+ </vbox>
+ <vbox id="chosenActionWipeClient" class="confirm">
+ <description class="normal">
+ &confirm.client3.label;
+ </description>
+ <separator class="thin"/>
+ <vbox id="dataList">
+ <label class="data indent" id="bookmarkCount"/>
+ <label class="data indent" id="historyCount"/>
+ <label class="data indent" id="passwordCount"/>
+ <label class="data indent" id="addonCount"/>
+ <label class="data indent" id="prefsWipe"
+ value="&engine.prefs.label;"/>
+ </vbox>
+ <separator class="thin"/>
+ <description class="normal">
+ &confirm.client2.moreinfo.label;
+ </description>
+ </vbox>
+ <vbox id="chosenActionWipeServer" class="confirm">
+ <description class="normal">
+ &confirm.server2.label;
+ </description>
+ <separator class="thin"/>
+ <vbox id="clientList">
+ </vbox>
+ </vbox>
+ </deck>
+ </wizardpage>
+ <!-- In terms of the wizard flow shown to the user, the 'syncOptionsConfirm'
+ page above is not the last wizard page. To prevent the wizard binding from
+ assuming that it is, we're inserting this dummy page here. This also means
+ that the wizard needs to always be closed manually via wizardFinish(). -->
+ <wizardpage>
+ </wizardpage>
+</wizard>
+
diff --git a/browser/base/content/sync/utils.js b/browser/base/content/sync/utils.js
new file mode 100644
index 000000000..92981f7b4
--- /dev/null
+++ b/browser/base/content/sync/utils.js
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Equivalent to 0o600 permissions; used for saved Sync Recovery Key.
+// This constant can be replaced when the equivalent values are available to
+// chrome JS; see Bug 433295 and Bug 757351.
+const PERMISSIONS_RWUSR = 0x180;
+
+// Weave should always exist before before this file gets included.
+var gSyncUtils = {
+ get bundle() {
+ delete this.bundle;
+ return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+ },
+
+ get fxAccountsEnabled() {
+ let service = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ return service.fxAccountsEnabled;
+ },
+
+ // opens in a new window if we're in a modal prefwindow world, in a new tab otherwise
+ _openLink: function (url) {
+ let thisDocEl = document.documentElement,
+ openerDocEl = window.opener && window.opener.document.documentElement;
+ if (thisDocEl.id == "accountSetup" && window.opener &&
+ openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
+ openUILinkIn(url, "window");
+ else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
+ openUILinkIn(url, "window");
+ else if (document.documentElement.id == "change-dialog")
+ Services.wm.getMostRecentWindow("navigator:browser")
+ .openUILinkIn(url, "tab");
+ else
+ openUILinkIn(url, "tab");
+ },
+
+ changeName: function changeName(input) {
+ // Make sure to update to a modified name, e.g., empty-string -> default
+ Weave.Service.clientsEngine.localName = input.value;
+ input.value = Weave.Service.clientsEngine.localName;
+ },
+
+ openChange: function openChange(type, duringSetup) {
+ // Just re-show the dialog if it's already open
+ let openedDialog = Services.wm.getMostRecentWindow("Sync:" + type);
+ if (openedDialog != null) {
+ openedDialog.focus();
+ return;
+ }
+
+ // Open up the change dialog
+ let changeXUL = "chrome://browser/content/sync/genericChange.xul";
+ let changeOpt = "centerscreen,chrome,resizable=no";
+ Services.ww.activeWindow.openDialog(changeXUL, "", changeOpt,
+ type, duringSetup);
+ },
+
+ changePassword: function () {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("ChangePassword");
+ },
+
+ resetPassphrase: function (duringSetup) {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("ResetPassphrase", duringSetup);
+ },
+
+ updatePassphrase: function () {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("UpdatePassphrase");
+ },
+
+ resetPassword: function () {
+ this._openLink(Weave.Service.pwResetURL);
+ },
+
+ get tosURL() {
+ let root = this.fxAccountsEnabled ? "fxa." : "";
+ return Weave.Svc.Prefs.get(root + "termsURL");
+ },
+
+ openToS: function () {
+ this._openLink(this.tosURL);
+ },
+
+ get privacyPolicyURL() {
+ let root = this.fxAccountsEnabled ? "fxa." : "";
+ return Weave.Svc.Prefs.get(root + "privacyURL");
+ },
+
+ openPrivacyPolicy: function () {
+ this._openLink(this.privacyPolicyURL);
+ },
+
+ /**
+ * Prepare an invisible iframe with the passphrase backup document.
+ * Used by both the print and saving methods.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ * @param callback : Function called once the iframe has loaded.
+ */
+ _preparePPiframe: function(elid, callback) {
+ let pp = document.getElementById(elid).value;
+
+ // Create an invisible iframe whose contents we can print.
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "chrome://browser/content/sync/key.xhtml");
+ iframe.collapsed = true;
+ document.documentElement.appendChild(iframe);
+ iframe.contentWindow.addEventListener("load", function() {
+ iframe.contentWindow.removeEventListener("load", arguments.callee, false);
+
+ // Insert the Sync Key into the page.
+ let el = iframe.contentDocument.getElementById("synckey");
+ el.firstChild.nodeValue = pp;
+
+ // Insert the TOS and Privacy Policy URLs into the page.
+ let termsURL = Weave.Svc.Prefs.get("termsURL");
+ el = iframe.contentDocument.getElementById("tosLink");
+ el.setAttribute("href", termsURL);
+ el.firstChild.nodeValue = termsURL;
+
+ let privacyURL = Weave.Svc.Prefs.get("privacyURL");
+ el = iframe.contentDocument.getElementById("ppLink");
+ el.setAttribute("href", privacyURL);
+ el.firstChild.nodeValue = privacyURL;
+
+ callback(iframe);
+ }, false);
+ },
+
+ /**
+ * Print passphrase backup document.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ */
+ passphrasePrint: function(elid) {
+ this._preparePPiframe(elid, function(iframe) {
+ let webBrowserPrint = iframe.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserPrint);
+ let printSettings = PrintUtils.getPrintSettings();
+
+ // Display no header/footer decoration except for the date.
+ printSettings.headerStrLeft
+ = printSettings.headerStrCenter
+ = printSettings.headerStrRight
+ = printSettings.footerStrLeft
+ = printSettings.footerStrCenter = "";
+ printSettings.footerStrRight = "&D";
+
+ try {
+ webBrowserPrint.print(printSettings, null);
+ } catch (ex) {
+ // print()'s return codes are expressed as exceptions. Ignore.
+ }
+ });
+ },
+
+ /**
+ * Save passphrase backup document to disk as HTML file.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ */
+ passphraseSave: function(elid) {
+ let dialogTitle = this.bundle.GetStringFromName("save.recoverykey.title");
+ let defaultSaveName = this.bundle.GetStringFromName("save.recoverykey.defaultfilename");
+ this._preparePPiframe(elid, function(iframe) {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK ||
+ aResult == Ci.nsIFilePicker.returnReplace) {
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(fp.file, -1, PERMISSIONS_RWUSR, 0);
+
+ let serializer = new XMLSerializer();
+ let output = serializer.serializeToString(iframe.contentDocument);
+ output = output.replace(/<!DOCTYPE (.|\n)*?]>/,
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' +
+ '"DTD/xhtml1-strict.dtd">');
+ output = Weave.Utils.encodeUTF8(output);
+ stream.write(output, output.length);
+ }
+ };
+
+ fp.init(window, dialogTitle, Ci.nsIFilePicker.modeSave);
+ fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+ fp.defaultString = defaultSaveName;
+ fp.open(fpCallback);
+ return false;
+ });
+ },
+
+ /**
+ * validatePassword
+ *
+ * @param el1 : the first textbox element in the form
+ * @param el2 : the second textbox element, if omitted it's an update form
+ *
+ * returns [valid, errorString]
+ */
+ validatePassword: function (el1, el2) {
+ let valid = false;
+ let val1 = el1.value;
+ let val2 = el2 ? el2.value : "";
+ let error = "";
+
+ if (!el2)
+ valid = val1.length >= Weave.MIN_PASS_LENGTH;
+ else if (val1 && val1 == Weave.Service.identity.username)
+ error = "change.password.pwSameAsUsername";
+ else if (val1 && val1 == Weave.Service.identity.account)
+ error = "change.password.pwSameAsEmail";
+ else if (val1 && val1 == Weave.Service.identity.basicPassword)
+ error = "change.password.pwSameAsPassword";
+ else if (val1 && val2) {
+ if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH)
+ valid = true;
+ else if (val1.length < Weave.MIN_PASS_LENGTH)
+ error = "change.password.tooShort";
+ else if (val1 != val2)
+ error = "change.password.mismatch";
+ }
+ let errorString = error ? Weave.Utils.getErrorString(error) : "";
+ return [valid, errorString];
+ }
+};
diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js
new file mode 100644
index 000000000..06fa3d9cc
--- /dev/null
+++ b/browser/base/content/tab-content.js
@@ -0,0 +1,947 @@
+/* -*- 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 content script contains code that requires a tab browser. */
+
+var {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/ExtensionContent.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+ "resource:///modules/E10SUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutReader",
+ "resource://gre/modules/AboutReader.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+ "resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyGetter(this, "SimpleServiceDiscovery", function() {
+ let ssdp = Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm", {}).SimpleServiceDiscovery;
+ // Register targets
+ ssdp.registerDevice({
+ id: "roku:ecp",
+ target: "roku:ecp",
+ factory: function(aService) {
+ Cu.import("resource://gre/modules/RokuApp.jsm");
+ return new RokuApp(aService);
+ },
+ types: ["video/mp4"],
+ extensions: ["mp4"]
+ });
+ return ssdp;
+});
+
+// TabChildGlobal
+var global = this;
+
+
+addEventListener("MozDOMPointerLock:Entered", function(aEvent) {
+ sendAsyncMessage("PointerLock:Entered", {
+ originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix
+ });
+});
+
+addEventListener("MozDOMPointerLock:Exited", function(aEvent) {
+ sendAsyncMessage("PointerLock:Exited");
+});
+
+
+addMessageListener("Browser:HideSessionRestoreButton", function (message) {
+ // Hide session restore button on about:home
+ let doc = content.document;
+ let container;
+ if (doc.documentURI.toLowerCase() == "about:home" &&
+ (container = doc.getElementById("sessionRestoreContainer"))) {
+ container.hidden = true;
+ }
+});
+
+
+addMessageListener("Browser:Reload", function(message) {
+ /* First, we'll try to use the session history object to reload so
+ * that framesets are handled properly. If we're in a special
+ * window (such as view-source) that has no session history, fall
+ * back on using the web navigation's reload method.
+ */
+
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ try {
+ let sh = webNav.sessionHistory;
+ if (sh)
+ webNav = sh.QueryInterface(Ci.nsIWebNavigation);
+ } catch (e) {
+ }
+
+ let reloadFlags = message.data.flags;
+ try {
+ E10SUtils.wrapHandlingUserInput(content, message.data.handlingUserInput,
+ () => webNav.reload(reloadFlags));
+ } catch (e) {
+ }
+});
+
+addMessageListener("MixedContent:ReenableProtection", function() {
+ docShell.mixedContentChannel = null;
+});
+
+addMessageListener("SecondScreen:tab-mirror", function(message) {
+ if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
+ return;
+ }
+ let app = SimpleServiceDiscovery.findAppForService(message.data.service);
+ if (app) {
+ let width = content.innerWidth;
+ let height = content.innerHeight;
+ let viewport = {cssWidth: width, cssHeight: height, width: width, height: height};
+ app.mirror(function() {}, content, viewport, function() {}, content);
+ }
+});
+
+var AboutHomeListener = {
+ init: function(chromeGlobal) {
+ chromeGlobal.addEventListener('AboutHomeLoad', this, false, true);
+ },
+
+ get isAboutHome() {
+ return content.document.documentURI.toLowerCase() == "about:home";
+ },
+
+ handleEvent: function(aEvent) {
+ if (!this.isAboutHome) {
+ return;
+ }
+ switch (aEvent.type) {
+ case "AboutHomeLoad":
+ this.onPageLoad();
+ break;
+ case "click":
+ this.onClick(aEvent);
+ break;
+ case "pagehide":
+ this.onPageHide(aEvent);
+ break;
+ }
+ },
+
+ receiveMessage: function(aMessage) {
+ if (!this.isAboutHome) {
+ return;
+ }
+ switch (aMessage.name) {
+ case "AboutHome:Update":
+ this.onUpdate(aMessage.data);
+ break;
+ }
+ },
+
+ onUpdate: function(aData) {
+ let doc = content.document;
+ if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isContentWindowPrivate(content))
+ doc.getElementById("launcher").setAttribute("session", "true");
+
+ // Inject search engine and snippets URL.
+ let docElt = doc.documentElement;
+ // Set snippetsVersion last, which triggers to show the snippets when it's set.
+ docElt.setAttribute("snippetsURL", aData.snippetsURL);
+ if (aData.showKnowYourRights)
+ docElt.setAttribute("showKnowYourRights", "true");
+ docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
+ },
+
+ onPageLoad: function() {
+ addMessageListener("AboutHome:Update", this);
+ addEventListener("click", this, true);
+ addEventListener("pagehide", this, true);
+
+ sendAsyncMessage("AboutHome:MaybeShowAutoMigrationUndoNotification");
+ sendAsyncMessage("AboutHome:RequestUpdate");
+ },
+
+ onClick: function(aEvent) {
+ if (!aEvent.isTrusted || // Don't trust synthetic events
+ aEvent.button == 2 || aEvent.target.localName != "button") {
+ return;
+ }
+
+ let originalTarget = aEvent.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+ if (ownerDoc.documentURI != "about:home") {
+ // This shouldn't happen, but we're being defensive.
+ return;
+ }
+
+ let elmId = originalTarget.getAttribute("id");
+
+ switch (elmId) {
+ case "restorePreviousSession":
+ sendAsyncMessage("AboutHome:RestorePreviousSession");
+ ownerDoc.getElementById("launcher").removeAttribute("session");
+ break;
+
+ case "downloads":
+ sendAsyncMessage("AboutHome:Downloads");
+ break;
+
+ case "bookmarks":
+ sendAsyncMessage("AboutHome:Bookmarks");
+ break;
+
+ case "history":
+ sendAsyncMessage("AboutHome:History");
+ break;
+
+ case "addons":
+ sendAsyncMessage("AboutHome:Addons");
+ break;
+
+ case "sync":
+ sendAsyncMessage("AboutHome:Sync");
+ break;
+
+ case "settings":
+ sendAsyncMessage("AboutHome:Settings");
+ break;
+ }
+ },
+
+ onPageHide: function(aEvent) {
+ if (aEvent.target.defaultView.frameElement) {
+ return;
+ }
+ removeMessageListener("AboutHome:Update", this);
+ removeEventListener("click", this, true);
+ removeEventListener("pagehide", this, true);
+ },
+};
+AboutHomeListener.init(this);
+
+var AboutPrivateBrowsingListener = {
+ init(chromeGlobal) {
+ chromeGlobal.addEventListener("AboutPrivateBrowsingOpenWindow", this,
+ false, true);
+ chromeGlobal.addEventListener("AboutPrivateBrowsingToggleTrackingProtection", this,
+ false, true);
+ chromeGlobal.addEventListener("AboutPrivateBrowsingDontShowIntroPanelAgain", this,
+ false, true);
+ },
+
+ get isAboutPrivateBrowsing() {
+ return content.document.documentURI.toLowerCase() == "about:privatebrowsing";
+ },
+
+ handleEvent(aEvent) {
+ if (!this.isAboutPrivateBrowsing) {
+ return;
+ }
+ switch (aEvent.type) {
+ case "AboutPrivateBrowsingOpenWindow":
+ sendAsyncMessage("AboutPrivateBrowsing:OpenPrivateWindow");
+ break;
+ case "AboutPrivateBrowsingToggleTrackingProtection":
+ sendAsyncMessage("AboutPrivateBrowsing:ToggleTrackingProtection");
+ break;
+ case "AboutPrivateBrowsingDontShowIntroPanelAgain":
+ sendAsyncMessage("AboutPrivateBrowsing:DontShowIntroPanelAgain");
+ break;
+ }
+ },
+};
+AboutPrivateBrowsingListener.init(this);
+
+var AboutReaderListener = {
+
+ _articlePromise: null,
+
+ _isLeavingReaderMode: false,
+
+ init: function() {
+ addEventListener("AboutReaderContentLoaded", this, false, true);
+ addEventListener("DOMContentLoaded", this, false);
+ addEventListener("pageshow", this, false);
+ addEventListener("pagehide", this, false);
+ addMessageListener("Reader:ToggleReaderMode", this);
+ addMessageListener("Reader:PushState", this);
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Reader:ToggleReaderMode":
+ if (!this.isAboutReader) {
+ this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
+ ReaderMode.enterReaderMode(docShell, content);
+ } else {
+ this._isLeavingReaderMode = true;
+ ReaderMode.leaveReaderMode(docShell, content);
+ }
+ break;
+
+ case "Reader:PushState":
+ this.updateReaderButton(!!(message.data && message.data.isArticle));
+ break;
+ }
+ },
+
+ get isAboutReader() {
+ if (!content) {
+ return false;
+ }
+ return content.document.documentURI.startsWith("about:reader");
+ },
+
+ handleEvent: function(aEvent) {
+ if (aEvent.originalTarget.defaultView != content) {
+ return;
+ }
+
+ switch (aEvent.type) {
+ case "AboutReaderContentLoaded":
+ if (!this.isAboutReader) {
+ return;
+ }
+
+ if (content.document.body) {
+ // Update the toolbar icon to show the "reader active" icon.
+ sendAsyncMessage("Reader:UpdateReaderButton");
+ new AboutReader(global, content, this._articlePromise);
+ this._articlePromise = null;
+ }
+ break;
+
+ case "pagehide":
+ this.cancelPotentialPendingReadabilityCheck();
+ // this._isLeavingReaderMode is used here to keep the Reader Mode icon
+ // visible in the location bar when transitioning from reader-mode page
+ // back to the source page.
+ sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderMode });
+ if (this._isLeavingReaderMode) {
+ this._isLeavingReaderMode = false;
+ }
+ break;
+
+ case "pageshow":
+ // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
+ // event, so we need to rely on "pageshow" in this case.
+ if (aEvent.persisted) {
+ this.updateReaderButton();
+ }
+ break;
+ case "DOMContentLoaded":
+ this.updateReaderButton();
+ break;
+
+ }
+ },
+
+ /**
+ * NB: this function will update the state of the reader button asynchronously
+ * after the next mozAfterPaint call (assuming reader mode is enabled and
+ * this is a suitable document). Calling it on things which won't be
+ * painted is not going to work.
+ */
+ updateReaderButton: function(forceNonArticle) {
+ if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
+ !content || !(content.document instanceof content.HTMLDocument) ||
+ content.document.mozSyntheticDocument) {
+ return;
+ }
+
+ this.scheduleReadabilityCheckPostPaint(forceNonArticle);
+ },
+
+ cancelPotentialPendingReadabilityCheck: function() {
+ if (this._pendingReadabilityCheck) {
+ removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
+ delete this._pendingReadabilityCheck;
+ }
+ },
+
+ scheduleReadabilityCheckPostPaint: function(forceNonArticle) {
+ if (this._pendingReadabilityCheck) {
+ // We need to stop this check before we re-add one because we don't know
+ // if forceNonArticle was true or false last time.
+ this.cancelPotentialPendingReadabilityCheck();
+ }
+ this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(this, forceNonArticle);
+ addEventListener("MozAfterPaint", this._pendingReadabilityCheck);
+ },
+
+ onPaintWhenWaitedFor: function(forceNonArticle, event) {
+ // In non-e10s, we'll get called for paints other than ours, and so it's
+ // possible that this page hasn't been laid out yet, in which case we
+ // should wait until we get an event that does relate to our layout. We
+ // determine whether any of our content got painted by checking if there
+ // are any painted rects.
+ if (!event.clientRects.length) {
+ return;
+ }
+
+ this.cancelPotentialPendingReadabilityCheck();
+ // Only send updates when there are articles; there's no point updating with
+ // |false| all the time.
+ if (ReaderMode.isProbablyReaderable(content.document)) {
+ sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
+ } else if (forceNonArticle) {
+ sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
+ }
+ },
+};
+AboutReaderListener.init();
+
+
+var ContentSearchMediator = {
+
+ whitelist: new Set([
+ "about:home",
+ "about:newtab",
+ ]),
+
+ init: function (chromeGlobal) {
+ chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
+ addMessageListener("ContentSearch", this);
+ },
+
+ handleEvent: function (event) {
+ if (this._contentWhitelisted) {
+ this._sendMsg(event.detail.type, event.detail.data);
+ }
+ },
+
+ receiveMessage: function (msg) {
+ if (msg.data.type == "AddToWhitelist") {
+ for (let uri of msg.data.data) {
+ this.whitelist.add(uri);
+ }
+ this._sendMsg("AddToWhitelistAck");
+ return;
+ }
+ if (this._contentWhitelisted) {
+ this._fireEvent(msg.data.type, msg.data.data);
+ }
+ },
+
+ get _contentWhitelisted() {
+ return this.whitelist.has(content.document.documentURI);
+ },
+
+ _sendMsg: function (type, data=null) {
+ sendAsyncMessage("ContentSearch", {
+ type: type,
+ data: data,
+ });
+ },
+
+ _fireEvent: function (type, data=null) {
+ let event = Cu.cloneInto({
+ detail: {
+ type: type,
+ data: data,
+ },
+ }, content);
+ content.dispatchEvent(new content.CustomEvent("ContentSearchService",
+ event));
+ },
+};
+ContentSearchMediator.init(this);
+
+var PageStyleHandler = {
+ init: function() {
+ addMessageListener("PageStyle:Switch", this);
+ addMessageListener("PageStyle:Disable", this);
+ addEventListener("pageshow", () => this.sendStyleSheetInfo());
+ },
+
+ get markupDocumentViewer() {
+ return docShell.contentViewer;
+ },
+
+ sendStyleSheetInfo: function() {
+ let filteredStyleSheets = this._filterStyleSheets(this.getAllStyleSheets());
+
+ sendAsyncMessage("PageStyle:StyleSheets", {
+ filteredStyleSheets: filteredStyleSheets,
+ authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled,
+ preferredStyleSheetSet: content.document.preferredStyleSheetSet
+ });
+ },
+
+ getAllStyleSheets: function(frameset = content) {
+ let selfSheets = Array.slice(frameset.document.styleSheets);
+ let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
+ return selfSheets.concat(...subSheets);
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "PageStyle:Switch":
+ this.markupDocumentViewer.authorStyleDisabled = false;
+ this._stylesheetSwitchAll(content, msg.data.title);
+ break;
+
+ case "PageStyle:Disable":
+ this.markupDocumentViewer.authorStyleDisabled = true;
+ break;
+ }
+
+ this.sendStyleSheetInfo();
+ },
+
+ _stylesheetSwitchAll: function (frameset, title) {
+ if (!title || this._stylesheetInFrame(frameset, title)) {
+ this._stylesheetSwitchFrame(frameset, title);
+ }
+
+ for (let i = 0; i < frameset.frames.length; i++) {
+ // Recurse into sub-frames.
+ this._stylesheetSwitchAll(frameset.frames[i], title);
+ }
+ },
+
+ _stylesheetSwitchFrame: function (frame, title) {
+ var docStyleSheets = frame.document.styleSheets;
+
+ for (let i = 0; i < docStyleSheets.length; ++i) {
+ let docStyleSheet = docStyleSheets[i];
+ if (docStyleSheet.title) {
+ docStyleSheet.disabled = (docStyleSheet.title != title);
+ } else if (docStyleSheet.disabled) {
+ docStyleSheet.disabled = false;
+ }
+ }
+ },
+
+ _stylesheetInFrame: function (frame, title) {
+ return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
+ },
+
+ _filterStyleSheets: function(styleSheets) {
+ let result = [];
+
+ for (let currentStyleSheet of styleSheets) {
+ if (!currentStyleSheet.title)
+ continue;
+
+ // Skip any stylesheets that don't match the screen media type.
+ if (currentStyleSheet.media.length > 0) {
+ let mediaQueryList = currentStyleSheet.media.mediaText;
+ if (!content.matchMedia(mediaQueryList).matches) {
+ continue;
+ }
+ }
+
+ let URI;
+ try {
+ if (!currentStyleSheet.ownerNode ||
+ // special-case style nodes, which have no href
+ currentStyleSheet.ownerNode.nodeName.toLowerCase() != "style") {
+ URI = Services.io.newURI(currentStyleSheet.href, null, null);
+ }
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_MALFORMED_URI) {
+ throw e;
+ }
+ continue;
+ }
+
+ // We won't send data URIs all of the way up to the parent, as these
+ // can be arbitrarily large.
+ let sentURI = (!URI || URI.scheme == "data") ? null : URI.spec;
+
+ result.push({
+ title: currentStyleSheet.title,
+ disabled: currentStyleSheet.disabled,
+ href: sentURI,
+ });
+ }
+
+ return result;
+ },
+};
+PageStyleHandler.init();
+
+// Keep a reference to the translation content handler to avoid it it being GC'ed.
+var trHandler = null;
+if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
+ Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
+ trHandler = new TranslationContentHandler(global, docShell);
+}
+
+function gKeywordURIFixup(fixupInfo) {
+ fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
+ if (!fixupInfo.consumer) {
+ return;
+ }
+
+ // Ignore info from other docshells
+ let parent = fixupInfo.consumer.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeRootTreeItem;
+ if (parent != docShell)
+ return;
+
+ let data = {};
+ for (let f of Object.keys(fixupInfo)) {
+ if (f == "consumer" || typeof fixupInfo[f] == "function")
+ continue;
+
+ if (fixupInfo[f] && fixupInfo[f] instanceof Ci.nsIURI) {
+ data[f] = fixupInfo[f].spec;
+ } else {
+ data[f] = fixupInfo[f];
+ }
+ }
+
+ sendAsyncMessage("Browser:URIFixup", data);
+}
+Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup", false);
+addEventListener("unload", () => {
+ Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
+}, false);
+
+addMessageListener("Browser:AppTab", function(message) {
+ if (docShell) {
+ docShell.isAppTab = message.data.isAppTab;
+ }
+});
+
+var WebBrowserChrome = {
+ onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+ },
+
+ // Check whether this URI should load in the current process
+ shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+ if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+ E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
+ return false;
+ }
+
+ return true;
+ },
+
+ // Try to reload the currently active or currently loading page in a new process.
+ reloadInFreshProcess: function(aDocShell, aURI, aReferrer) {
+ E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, true);
+ return true;
+ }
+};
+
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsITabChild);
+ tabchild.webBrowserChrome = WebBrowserChrome;
+}
+
+
+var DOMFullscreenHandler = {
+
+ init: function() {
+ addMessageListener("DOMFullscreen:Entered", this);
+ addMessageListener("DOMFullscreen:CleanUp", this);
+ addEventListener("MozDOMFullscreen:Request", this);
+ addEventListener("MozDOMFullscreen:Entered", this);
+ addEventListener("MozDOMFullscreen:NewOrigin", this);
+ addEventListener("MozDOMFullscreen:Exit", this);
+ addEventListener("MozDOMFullscreen:Exited", this);
+ },
+
+ get _windowUtils() {
+ if (!content) {
+ return null;
+ }
+ return content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ receiveMessage: function(aMessage) {
+ let windowUtils = this._windowUtils;
+ switch (aMessage.name) {
+ case "DOMFullscreen:Entered": {
+ this._lastTransactionId = windowUtils.lastTransactionId;
+ if (!windowUtils.handleFullscreenRequests() &&
+ !content.document.fullscreenElement) {
+ // If we don't actually have any pending fullscreen request
+ // to handle, neither we have been in fullscreen, tell the
+ // parent to just exit.
+ sendAsyncMessage("DOMFullscreen:Exit");
+ }
+ break;
+ }
+ case "DOMFullscreen:CleanUp": {
+ // If we've exited fullscreen at this point, no need to record
+ // transaction id or call exit fullscreen. This is especially
+ // important for non-e10s, since in that case, it is possible
+ // that no more paint would be triggered after this point.
+ if (content.document.fullscreenElement && windowUtils) {
+ this._lastTransactionId = windowUtils.lastTransactionId;
+ windowUtils.exitFullscreen();
+ }
+ break;
+ }
+ }
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "MozDOMFullscreen:Request": {
+ sendAsyncMessage("DOMFullscreen:Request");
+ break;
+ }
+ case "MozDOMFullscreen:NewOrigin": {
+ sendAsyncMessage("DOMFullscreen:NewOrigin", {
+ originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix,
+ });
+ break;
+ }
+ case "MozDOMFullscreen:Exit": {
+ sendAsyncMessage("DOMFullscreen:Exit");
+ break;
+ }
+ case "MozDOMFullscreen:Entered":
+ case "MozDOMFullscreen:Exited": {
+ addEventListener("MozAfterPaint", this);
+ if (!content || !content.document.fullscreenElement) {
+ // If we receive any fullscreen change event, and find we are
+ // actually not in fullscreen, also ask the parent to exit to
+ // ensure that the parent always exits fullscreen when we do.
+ sendAsyncMessage("DOMFullscreen:Exit");
+ }
+ break;
+ }
+ case "MozAfterPaint": {
+ // Only send Painted signal after we actually finish painting
+ // the transition for the fullscreen change.
+ // Note that this._lastTransactionId is not set when in non-e10s
+ // mode, so we need to check that explicitly.
+ if (!this._lastTransactionId ||
+ aEvent.transactionId > this._lastTransactionId) {
+ removeEventListener("MozAfterPaint", this);
+ sendAsyncMessage("DOMFullscreen:Painted");
+ }
+ break;
+ }
+ }
+ }
+};
+DOMFullscreenHandler.init();
+
+var RefreshBlocker = {
+ PREF: "accessibility.blockautorefresh",
+
+ // Bug 1247100 - When a refresh is caused by an HTTP header,
+ // onRefreshAttempted will be fired before onLocationChange.
+ // When a refresh is caused by a <meta> tag in the document,
+ // onRefreshAttempted will be fired after onLocationChange.
+ //
+ // We only ever want to send a message to the parent after
+ // onLocationChange has fired, since the parent uses the
+ // onLocationChange update to clear transient notifications.
+ // Sending the message before onLocationChange will result in
+ // us creating the notification, and then clearing it very
+ // soon after.
+ //
+ // To account for both cases (onRefreshAttempted before
+ // onLocationChange, and onRefreshAttempted after onLocationChange),
+ // we'll hold a mapping of DOM Windows that we see get
+ // sent through both onLocationChange and onRefreshAttempted.
+ // When either run, they'll check the WeakMap for the existence
+ // of the DOM Window. If it doesn't exist, it'll add it. If
+ // it finds it, it'll know that it's safe to send the message
+ // to the parent, since we know that both have fired.
+ //
+ // The DOM Window is removed from blockedWindows when we notice
+ // the nsIWebProgress change state to STATE_STOP for the
+ // STATE_IS_WINDOW case.
+ //
+ // DOM Windows are mapped to a JS object that contains the data
+ // to be sent to the parent to show the notification. Since that
+ // data is only known when onRefreshAttempted is fired, it's only
+ // ever stashed in the map if onRefreshAttempted fires first -
+ // otherwise, null is set as the value of the mapping.
+ blockedWindows: new WeakMap(),
+
+ init() {
+ if (Services.prefs.getBoolPref(this.PREF)) {
+ this.enable();
+ }
+
+ Services.prefs.addObserver(this.PREF, this, false);
+ },
+
+ uninit() {
+ if (Services.prefs.getBoolPref(this.PREF)) {
+ this.disable();
+ }
+
+ Services.prefs.removeObserver(this.PREF, this);
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "nsPref:changed" && data == this.PREF) {
+ if (Services.prefs.getBoolPref(this.PREF)) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ }
+ },
+
+ enable() {
+ 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);
+
+ addMessageListener("RefreshBlocker:Refresh", this);
+ },
+
+ disable() {
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.removeProgressListener(this._filter);
+
+ this._filter.removeProgressListener(this);
+ this._filter = null;
+
+ removeMessageListener("RefreshBlocker:Refresh", this);
+ },
+
+ send(data) {
+ sendAsyncMessage("RefreshBlocker:Blocked", data);
+ },
+
+ /**
+ * Notices when the nsIWebProgress transitions to STATE_STOP for
+ * the STATE_IS_WINDOW case, which will clear any mappings from
+ * blockedWindows.
+ */
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this.blockedWindows.delete(aWebProgress.DOMWindow);
+ }
+ },
+
+ /**
+ * Notices when the location has changed. If, when running,
+ * onRefreshAttempted has already fired for this DOM Window, will
+ * send the appropriate refresh blocked data to the parent.
+ */
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ let win = aWebProgress.DOMWindow;
+ if (this.blockedWindows.has(win)) {
+ let data = this.blockedWindows.get(win);
+ if (data) {
+ // We saw onRefreshAttempted before onLocationChange, so
+ // send the message to the parent to show the notification.
+ this.send(data);
+ }
+ } else {
+ this.blockedWindows.set(win, null);
+ }
+ },
+
+ /**
+ * Notices when a refresh / reload was attempted. If, when running,
+ * onLocationChange has not yet run, will stash the appropriate data
+ * into the blockedWindows map to be sent when onLocationChange fires.
+ */
+ onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
+ let win = aWebProgress.DOMWindow;
+ let outerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+
+ let data = {
+ URI: aURI.spec,
+ originCharset: aURI.originCharset,
+ delay: aDelay,
+ sameURI: aSameURI,
+ outerWindowID,
+ };
+
+ if (this.blockedWindows.has(win)) {
+ // onLocationChange must have fired before, so we can tell the
+ // parent to show the notification.
+ this.send(data);
+ } else {
+ // onLocationChange hasn't fired yet, so stash the data in the
+ // map so that onLocationChange can send it when it fires.
+ this.blockedWindows.set(win, data);
+ }
+
+ return false;
+ },
+
+ receiveMessage(message) {
+ let data = message.data;
+
+ if (message.name == "RefreshBlocker:Refresh") {
+ let win = Services.wm.getOuterWindowWithId(data.outerWindowID);
+ let refreshURI = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIRefreshURI);
+
+ let URI = BrowserUtils.makeURI(data.URI, data.originCharset, null);
+
+ refreshURI.forceRefreshURI(URI, data.delay, true);
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener2,
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+};
+
+RefreshBlocker.init();
+
+var UserContextIdNotifier = {
+ init() {
+ addEventListener("DOMWindowCreated", this);
+ },
+
+ uninit() {
+ removeEventListener("DOMWindowCreated", this);
+ },
+
+ handleEvent(aEvent) {
+ // When the window is created, we want to inform the tabbrowser about
+ // the userContextId in use in order to update the UI correctly.
+ // Just because we cannot change the userContextId from an active docShell,
+ // we don't need to check DOMContentLoaded again.
+ this.uninit();
+
+ // We use the docShell because content.document can have been loaded before
+ // setting the originAttributes.
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ let userContextId = loadContext.originAttributes.userContextId;
+
+ sendAsyncMessage("Browser:WindowCreated", { userContextId });
+ }
+};
+
+UserContextIdNotifier.init();
+
+ExtensionContent.init(this);
+addEventListener("unload", () => {
+ ExtensionContent.uninit(this);
+ RefreshBlocker.uninit();
+});
+
+addMessageListener("AllowScriptsToClose", () => {
+ content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .allowScriptsToClose();
+});
+
+addEventListener("MozAfterPaint", function onFirstPaint() {
+ removeEventListener("MozAfterPaint", onFirstPaint);
+ sendAsyncMessage("Browser:FirstPaint");
+});
diff --git a/browser/base/content/tab-shape.inc.svg b/browser/base/content/tab-shape.inc.svg
new file mode 100644
index 000000000..f97889389
--- /dev/null
+++ b/browser/base/content/tab-shape.inc.svg
@@ -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/. -->
+
+<svg:clipPath id="tab-curve-clip-path-start" clipPathUnits="objectBoundingBox">
+ <svg:path d="m 1,0.0625 0.05,0 0,0.938 -1,0 0,-0.028 C 0.32082458,0.95840561 0.4353096,0.81970962 0.48499998,0.5625 0.51819998,0.3905 0.535,0.0659 1,0.0625 z"/>
+</svg:clipPath>
+
+<svg:clipPath id="tab-curve-clip-path-end" clipPathUnits="objectBoundingBox">
+ <svg:path d="m 0,0.0625 -0.05,0 0,0.938 1,0 0,-0.028 C 0.67917542,0.95840561 0.56569036,0.81970962 0.51599998,0.5625 0.48279998,0.3905 0.465,0.0659 0,0.0625 z"/>
+</svg:clipPath>
diff --git a/browser/base/content/tabbrowser.css b/browser/base/content/tabbrowser.css
new file mode 100644
index 000000000..9085304f6
--- /dev/null
+++ b/browser/base/content/tabbrowser.css
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.tabbrowser-tabbox {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabbox");
+}
+
+.tabbrowser-tabpanels {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabpanels");
+}
+
+.tabbrowser-arrowscrollbox {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-arrowscrollbox");
+}
+
+.tab-close-button {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-close-tab-button");
+}
+
+.tab-close-button[pinned],
+.tabbrowser-tabs[closebuttons="activetab"] > * > * > * > .tab-close-button:not([selected="true"]),
+.tab-icon-image:not([src]):not([pinned]):not([crashed])[selected],
+.tab-icon-image:not([src]):not([pinned]):not([crashed]):not([sharing]),
+.tab-icon-image[busy],
+.tab-throbber:not([busy]),
+.tab-icon-sound:not([soundplaying]):not([muted]):not([blocked]),
+.tab-icon-sound[pinned],
+.tab-sharing-icon-overlay,
+.tab-icon-overlay {
+ display: none;
+}
+
+.tab-sharing-icon-overlay[sharing]:not([selected]),
+.tab-icon-overlay[soundplaying][pinned],
+.tab-icon-overlay[muted][pinned],
+.tab-icon-overlay[blocked][pinned],
+.tab-icon-overlay[crashed] {
+ display: -moz-box;
+}
+
+.tab-label[pinned] {
+ width: 0;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.tab-stack {
+ vertical-align: top; /* for pinned tabs */
+}
+
+tabpanels {
+ background-color: transparent;
+}
+
+.tab-drop-indicator {
+ position: relative;
+ z-index: 2;
+}
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ .tab-icon-image {
+ image-rendering: -moz-crisp-edges;
+ }
+}
+
+.closing-tabs-spacer {
+ pointer-events: none;
+}
+
+.tabbrowser-tabs:not(:hover) > .tabbrowser-arrowscrollbox > .closing-tabs-spacer {
+ transition: width .15s ease-out;
+}
+
+/**
+ * Optimization for tabs that are restored lazily. We can save a good amount of
+ * memory that to-be-restored tabs would otherwise consume simply by setting
+ * their browsers to 'display: none' as that will prevent them from having to
+ * create a presentation and the like.
+ */
+browser[pending] {
+ display: none;
+}
+
+browser[pendingpaint] {
+ opacity: 0;
+}
+
+tabbrowser[pendingpaint] {
+ background-image: url(chrome://browser/skin/tabbrowser/pendingpaint.png);
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-color: #f9f9f9 !important;
+ background-size: 30px;
+}
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
new file mode 100644
index 000000000..3f4c3518e
--- /dev/null
+++ b/browser/base/content/tabbrowser.xml
@@ -0,0 +1,7417 @@
+<?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="tabBrowserBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tabbrowser">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content>
+ <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
+ <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
+ flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
+ onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
+ <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
+ <xul:notificationbox flex="1" notificationside="top">
+ <xul:hbox flex="1" class="browserSidebarContainer">
+ <xul:vbox flex="1" class="browserContainer">
+ <xul:stack flex="1" class="browserStack" anonid="browserStack">
+ <xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
+ xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist,datetimepicker"/>
+ </xul:stack>
+ </xul:vbox>
+ </xul:hbox>
+ </xul:notificationbox>
+ </xul:tabpanels>
+ </xul:tabbox>
+ <children/>
+ </content>
+ <implementation implements="nsIDOMEventListener, nsIMessageListener, nsIObserver">
+
+ <property name="tabContextMenu" readonly="true"
+ onget="return this.tabContainer.contextMenu;"/>
+
+ <field name="tabContainer" readonly="true">
+ document.getElementById(this.getAttribute("tabcontainer"));
+ </field>
+ <field name="tabs" readonly="true">
+ this.tabContainer.childNodes;
+ </field>
+
+ <property name="visibleTabs" readonly="true">
+ <getter><![CDATA[
+ if (!this._visibleTabs)
+ this._visibleTabs = Array.filter(this.tabs,
+ tab => !tab.hidden && !tab.closing);
+ return this._visibleTabs;
+ ]]></getter>
+ </property>
+
+ <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>
+
+ <field name="_visibleTabs">null</field>
+
+ <field name="mURIFixup" readonly="true">
+ Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(Components.interfaces.nsIURIFixup);
+ </field>
+ <field name="_unifiedComplete" readonly="true">
+ Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+ </field>
+ <field name="AppConstants" readonly="true">
+ (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
+ </field>
+ <field name="mTabBox" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
+ </field>
+ <field name="mPanelContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
+ </field>
+ <field name="mStringBundle">
+ document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
+ </field>
+ <field name="mCurrentTab">
+ null
+ </field>
+ <field name="_lastRelatedTab">
+ null
+ </field>
+ <field name="mCurrentBrowser">
+ null
+ </field>
+ <field name="mProgressListeners">
+ []
+ </field>
+ <field name="mActiveResizeDisplayportSuppression">
+ null
+ </field>
+ <field name="mTabsProgressListeners">
+ []
+ </field>
+ <field name="_tabListeners">
+ new Map()
+ </field>
+ <field name="_tabFilters">
+ new Map()
+ </field>
+ <field name="mIsBusy">
+ false
+ </field>
+ <field name="_outerWindowIDBrowserMap">
+ new Map();
+ </field>
+ <field name="arrowKeysShouldWrap" readonly="true">
+ this.AppConstants.platform == "macosx";
+ </field>
+
+ <field name="_autoScrollPopup">
+ null
+ </field>
+
+ <field name="_previewMode">
+ false
+ </field>
+
+ <field name="_lastFindValue">
+ ""
+ </field>
+
+ <field name="_contentWaitingCount">
+ 0
+ </field>
+
+ <property name="_numPinnedTabs" readonly="true">
+ <getter><![CDATA[
+ for (var i = 0; i < this.tabs.length; i++) {
+ if (!this.tabs[i].pinned)
+ break;
+ }
+ return i;
+ ]]></getter>
+ </property>
+
+ <property name="popupAnchor" readonly="true">
+ <getter><![CDATA[
+ if (this.mCurrentTab._popupAnchor) {
+ return this.mCurrentTab._popupAnchor;
+ }
+ let stack = this.mCurrentBrowser.parentNode;
+ // Create an anchor for the popup
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let popupAnchor = document.createElementNS(NS_XUL, "hbox");
+ popupAnchor.className = "popup-anchor";
+ popupAnchor.hidden = true;
+ stack.appendChild(popupAnchor);
+ return this.mCurrentTab._popupAnchor = popupAnchor;
+ ]]></getter>
+ </property>
+
+ <method name="isFindBarInitialized">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ return (aTab || this.selectedTab)._findBar != undefined;
+ ]]></body>
+ </method>
+
+ <method name="getFindBar">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (!aTab)
+ aTab = this.selectedTab;
+
+ if (aTab._findBar)
+ return aTab._findBar;
+
+ let findBar = document.createElementNS(this.namespaceURI, "findbar");
+ let browser = this.getBrowserForTab(aTab);
+ let browserContainer = this.getBrowserContainer(browser);
+ browserContainer.appendChild(findBar);
+
+ // Force a style flush to ensure that our binding is attached.
+ findBar.clientTop;
+
+ findBar.browser = browser;
+ findBar._findField.value = this._lastFindValue;
+
+ aTab._findBar = findBar;
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabFindInitialized", true, false);
+ aTab.dispatchEvent(event);
+
+ return findBar;
+ ]]></body>
+ </method>
+
+ <method name="getStatusPanel">
+ <body><![CDATA[
+ if (!this._statusPanel) {
+ this._statusPanel = document.createElementNS(this.namespaceURI, "statuspanel");
+ this._statusPanel.setAttribute("inactive", "true");
+ this._statusPanel.setAttribute("layer", "true");
+ this._appendStatusPanel();
+ }
+ return this._statusPanel;
+ ]]></body>
+ </method>
+
+ <method name="_appendStatusPanel">
+ <body><![CDATA[
+ if (this._statusPanel) {
+ let browser = this.selectedBrowser;
+ let browserContainer = this.getBrowserContainer(browser);
+ browserContainer.insertBefore(this._statusPanel, browser.parentNode.nextSibling);
+ }
+ ]]></body>
+ </method>
+
+ <method name="updateWindowResizers">
+ <body><![CDATA[
+ if (!window.gShowPageResizers)
+ return;
+
+ var show = window.windowState == window.STATE_NORMAL;
+ for (let i = 0; i < this.browsers.length; i++) {
+ this.browsers[i].showWindowResizer = show;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_setCloseKeyState">
+ <parameter name="aEnabled"/>
+ <body><![CDATA[
+ let keyClose = document.getElementById("key_close");
+ let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
+ if (closeKeyEnabled == aEnabled)
+ return;
+
+ if (aEnabled)
+ keyClose.removeAttribute("disabled");
+ else
+ keyClose.setAttribute("disabled", "true");
+
+ // We also want to remove the keyboard shortcut from the file menu
+ // when the shortcut is disabled, and bring it back when it's
+ // renabled.
+ //
+ // Fixing bug 630826 could make that happen automatically.
+ // Fixing bug 630830 could avoid the ugly hack below.
+
+ let closeMenuItem = document.getElementById("menu_close");
+ let parentPopup = closeMenuItem.parentNode;
+ let nextItem = closeMenuItem.nextSibling;
+ let clonedItem = closeMenuItem.cloneNode(true);
+
+ parentPopup.removeChild(closeMenuItem);
+
+ if (aEnabled)
+ clonedItem.setAttribute("key", "key_close");
+ else
+ clonedItem.removeAttribute("key");
+
+ parentPopup.insertBefore(clonedItem, nextItem);
+ ]]></body>
+ </method>
+
+ <method name="pinTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.pinned)
+ return;
+
+ if (aTab.hidden)
+ this.showTab(aTab);
+
+ this.moveTabTo(aTab, this._numPinnedTabs);
+ aTab.setAttribute("pinned", "true");
+ this.tabContainer._unlockTabSizing();
+ this.tabContainer._positionPinnedTabs();
+ this.tabContainer.adjustTabstrip();
+
+ this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: true })
+
+ if (aTab.selected)
+ this._setCloseKeyState(false);
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabPinned", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="unpinTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (!aTab.pinned)
+ return;
+
+ this.moveTabTo(aTab, this._numPinnedTabs - 1);
+ aTab.removeAttribute("pinned");
+ aTab.style.marginInlineStart = "";
+ this.tabContainer._unlockTabSizing();
+ this.tabContainer._positionPinnedTabs();
+ this.tabContainer.adjustTabstrip();
+
+ this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: false })
+
+ if (aTab.selected)
+ this._setCloseKeyState(true);
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabUnpinned", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="previewTab">
+ <parameter name="aTab"/>
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ let currentTab = this.selectedTab;
+ try {
+ // Suppress focus, ownership and selected tab changes
+ this._previewMode = true;
+ this.selectedTab = aTab;
+ aCallback();
+ } finally {
+ this.selectedTab = currentTab;
+ this._previewMode = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserAtIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.browsers[aIndex];
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserIndexForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aDocument.defaultView);
+ return tab ? tab._tPos : -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aDocument.defaultView);
+ return tab ? tab.linkedBrowser : null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aWindow);
+ return tab ? tab.linkedBrowser : null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForOuterWindowID">
+ <parameter name="aID"/>
+ <body>
+ <![CDATA[
+ return this._outerWindowIDBrowserMap.get(aID);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getTabForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ // When not using remote browsers, we can take a fast path by getting
+ // directly from the content window to the browser without looping
+ // over all browsers.
+ if (!gMultiProcessBrowser) {
+ let browser = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ return this.getTabForBrowser(browser);
+ }
+
+ for (let i = 0; i < this.browsers.length; i++) {
+ // NB: We use contentWindowAsCPOW so that this code works both
+ // for remote browsers as well. aWindow may be a CPOW.
+ if (this.browsers[i].contentWindowAsCPOW == aWindow)
+ return this.tabs[i];
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <!-- Binding from browser to tab -->
+ <field name="_tabForBrowser" readonly="true">
+ <![CDATA[
+ new WeakMap();
+ ]]>
+ </field>
+
+ <method name="_getTabForBrowser">
+ <parameter name="aBrowser" />
+ <body>
+ <![CDATA[
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ let text = "_getTabForBrowser` is now deprecated, please use `getTabForBrowser";
+ let url = "https://developer.mozilla.org/docs/Mozilla/Tech/XUL/Method/getTabForBrowser";
+ Deprecated.warning(text, url);
+ return this.getTabForBrowser(aBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this._tabForBrowser.get(aBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getNotificationBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this.getSidebarContainer(aBrowser).parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getSidebarContainer">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this.getBrowserContainer(aBrowser).parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserContainer">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabModalPromptBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ let browser = (aBrowser || this.mCurrentBrowser);
+ if (!browser.tabModalPromptBox) {
+ browser.tabModalPromptBox = new TabModalPromptBox(browser);
+ }
+ return browser.tabModalPromptBox;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabFromAudioEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
+ !aEvent.isTrusted) {
+ return null;
+ }
+
+ var browser = aEvent.originalTarget;
+ var tab = this.getTabForBrowser(browser);
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_callProgressListeners">
+ <parameter name="aBrowser"/>
+ <parameter name="aMethod"/>
+ <parameter name="aArguments"/>
+ <parameter name="aCallGlobalListeners"/>
+ <parameter name="aCallTabsListeners"/>
+ <body><![CDATA[
+ var rv = true;
+
+ function callListeners(listeners, args) {
+ for (let p of listeners) {
+ if (aMethod in p) {
+ try {
+ if (!p[aMethod].apply(p, args))
+ rv = false;
+ } catch (e) {
+ // don't inhibit other listeners
+ Components.utils.reportError(e);
+ }
+ }
+ }
+ }
+
+ if (!aBrowser)
+ aBrowser = this.mCurrentBrowser;
+
+ if (aCallGlobalListeners != false &&
+ aBrowser == this.mCurrentBrowser) {
+ callListeners(this.mProgressListeners, aArguments);
+ }
+
+ if (aCallTabsListeners != false) {
+ aArguments.unshift(aBrowser);
+
+ callListeners(this.mTabsProgressListeners, aArguments);
+ }
+
+ return rv;
+ ]]></body>
+ </method>
+
+ <!-- A web progress listener object definition for a given tab. -->
+ <method name="mTabProgressListener">
+ <parameter name="aTab"/>
+ <parameter name="aBrowser"/>
+ <parameter name="aStartsBlank"/>
+ <parameter name="aWasPreloadedBrowser"/>
+ <parameter name="aOrigStateFlags"/>
+ <body>
+ <![CDATA[
+ let stateFlags = aOrigStateFlags || 0;
+ // Initialize mStateFlags to non-zero e.g. when creating a progress
+ // listener for preloaded browsers as there was no progress listener
+ // around when the content started loading. If the content didn't
+ // quite finish loading yet, mStateFlags will very soon be overridden
+ // with the correct value and end up at STATE_STOP again.
+ if (aWasPreloadedBrowser) {
+ stateFlags = Ci.nsIWebProgressListener.STATE_STOP |
+ Ci.nsIWebProgressListener.STATE_IS_REQUEST;
+ }
+
+ return ({
+ mTabBrowser: this,
+ mTab: aTab,
+ mBrowser: aBrowser,
+ mBlank: aStartsBlank,
+
+ // cache flags for correct status UI update after tab switching
+ mStateFlags: stateFlags,
+ mStatus: 0,
+ mMessage: "",
+ mTotalProgress: 0,
+
+ // count of open requests (should always be 0 or 1)
+ mRequestCount: 0,
+
+ destroy: function () {
+ delete this.mTab;
+ delete this.mBrowser;
+ delete this.mTabBrowser;
+ },
+
+ _callProgressListeners: function () {
+ Array.unshift(arguments, this.mBrowser);
+ return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
+ },
+
+ _shouldShowProgress: function (aRequest) {
+ if (this.mBlank)
+ return false;
+
+ // Don't show progress indicators in tabs for about: URIs
+ // pointing to local resources.
+ if ((aRequest instanceof Ci.nsIChannel) &&
+ aRequest.originalURI.schemeIs("about") &&
+ (aRequest.URI.schemeIs("jar") || aRequest.URI.schemeIs("file")))
+ return false;
+
+ return true;
+ },
+
+ _isForInitialAboutBlank: function (aWebProgress, aLocation) {
+ if (!this.mBlank || !aWebProgress.isTopLevel) {
+ return false;
+ }
+
+ let location = aLocation ? aLocation.spec : "";
+ return location == "about:blank";
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
+
+ if (!this._shouldShowProgress(aRequest))
+ return;
+
+ if (this.mTotalProgress)
+ this.mTab.setAttribute("progress", "true");
+
+ this._callProgressListeners("onProgressChange",
+ [aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress]);
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (!aRequest)
+ return;
+
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ const nsIChannel = Components.interfaces.nsIChannel;
+ let location, originalLocation;
+ try {
+ aRequest.QueryInterface(nsIChannel)
+ location = aRequest.URI;
+ originalLocation = aRequest.originalURI;
+ } catch (ex) {}
+
+ let ignoreBlank = this._isForInitialAboutBlank(aWebProgress, location);
+ // If we were ignoring some messages about the initial about:blank, and we
+ // got the STATE_STOP for it, we'll want to pay attention to those messages
+ // from here forward. Similarly, if we conclude that this state change
+ // is one that we shouldn't be ignoring, then stop ignoring.
+ if ((ignoreBlank &&
+ aStateFlags & nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) ||
+ !ignoreBlank && this.mBlank) {
+ this.mBlank = false;
+ }
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START) {
+ this.mRequestCount++;
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ const NS_ERROR_UNKNOWN_HOST = 2152398878;
+ if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
+ // to prevent bug 235825: wait for the request handled
+ // by the automatic keyword resolver
+ return;
+ }
+ // since we (try to) only handle STATE_STOP of the last request,
+ // the count of open requests should now be 0
+ this.mRequestCount = 0;
+ }
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ if (aWebProgress.isTopLevel) {
+ // Need to use originalLocation rather than location because things
+ // like about:home and about:privatebrowsing arrive with nsIRequest
+ // pointing to their resolved jar: or file: URIs.
+ if (!(originalLocation && gInitialPages.includes(originalLocation.spec) &&
+ originalLocation != "about:blank" &&
+ this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec &&
+ this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
+ // Indicating that we started a load will allow the location
+ // bar to be cleared when the load finishes.
+ // In order to not overwrite user-typed content, we avoid it
+ // (see if condition above) in a very specific case:
+ // If the load is of an 'initial' page (e.g. about:privatebrowsing,
+ // about:newtab, etc.), was not explicitly typed in the location
+ // bar by the user, is not about:blank (because about:blank can be
+ // loaded by websites under their principal), and the current
+ // page in the browser is about:blank (indicating it is a newly
+ // created or re-created browser, e.g. because it just switched
+ // remoteness or is a new tab/window).
+ this.mBrowser.urlbarChangeTracker.startedLoad();
+ }
+ delete this.mBrowser.initialPageLoadedFromURLBar;
+ // If the browser is loading it must not be crashed anymore
+ this.mTab.removeAttribute("crashed");
+ }
+
+ if (this._shouldShowProgress(aRequest)) {
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this.mTab.setAttribute("busy", "true");
+
+ if (aWebProgress.isTopLevel &&
+ !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
+ this.mTabBrowser.setTabTitleLoading(this.mTab);
+ }
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = true;
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (this.mTab.hasAttribute("busy")) {
+ this.mTab.removeAttribute("busy");
+ this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
+ if (!this.mTab.selected)
+ this.mTab.setAttribute("unread", "true");
+ }
+ this.mTab.removeAttribute("progress");
+
+ if (aWebProgress.isTopLevel) {
+ let isSuccessful = Components.isSuccessCode(aStatus);
+ if (!isSuccessful && !isTabEmpty(this.mTab)) {
+ // Restore the current document's location in case the
+ // request was stopped (possibly from a content script)
+ // before the location changed.
+
+ this.mBrowser.userTypedValue = null;
+
+ let inLoadURI = this.mBrowser.inLoadURI;
+ if (this.mTab.selected && gURLBar && !inLoadURI) {
+ URLBarSetURI();
+ }
+ } else if (isSuccessful) {
+ this.mBrowser.urlbarChangeTracker.finishedLoad();
+ }
+
+ if (!this.mBrowser.mIconURL)
+ this.mTabBrowser.useDefaultIcon(this.mTab);
+ }
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword")
+ this.mBrowser.userTypedValue = null;
+
+ if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
+ this.mTabBrowser.setTabTitle(this.mTab);
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = false;
+ }
+
+ if (ignoreBlank) {
+ this._callProgressListeners("onUpdateCurrentBrowser",
+ [aStateFlags, aStatus, "", 0],
+ true, false);
+ } else {
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ true, false);
+ }
+
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ false);
+
+ if (aStateFlags & (nsIWebProgressListener.STATE_START |
+ nsIWebProgressListener.STATE_STOP)) {
+ // reset cached temporary values at beginning and end
+ this.mMessage = "";
+ this.mTotalProgress = 0;
+ }
+ this.mStateFlags = aStateFlags;
+ this.mStatus = aStatus;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocation,
+ aFlags) {
+ // OnLocationChange is called for both the top-level content
+ // and the subframes.
+ let topLevel = aWebProgress.isTopLevel;
+
+ if (topLevel) {
+ let isSameDocument =
+ !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
+ // We need to clear the typed value
+ // if the document failed to load, to make sure the urlbar reflects the
+ // failed URI (particularly for SSL errors). However, don't clear the value
+ // if the error page's URI is about:blank, because that causes complete
+ // loss of urlbar contents for invalid URI errors (see bug 867957).
+ // Another reason to clear the userTypedValue is if this was an anchor
+ // navigation initiated by the user.
+ if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
+ ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
+ aLocation.spec != "about:blank") ||
+ (isSameDocument && this.mBrowser.inLoadURI)) {
+ this.mBrowser.userTypedValue = null;
+ }
+
+ // If the browser was playing audio, we should remove the playing state.
+ if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) {
+ clearTimeout(this.mTab._soundPlayingAttrRemovalTimer);
+ this.mTab._soundPlayingAttrRemovalTimer = 0;
+ this.mTab.removeAttribute("soundplaying");
+ this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]);
+ }
+
+ // If the browser was previously muted, we should restore the muted state.
+ if (this.mTab.hasAttribute("muted")) {
+ this.mTab.linkedBrowser.mute();
+ }
+
+ if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
+ let findBar = this.mTabBrowser.getFindBar(this.mTab);
+
+ // Close the Find toolbar if we're in old-style TAF mode
+ if (findBar.findMode != findBar.FIND_NORMAL) {
+ findBar.close();
+ }
+ }
+
+ // Don't clear the favicon if this onLocationChange was
+ // triggered by a pushState or a replaceState (bug 550565) or
+ // a hash change (bug 408415).
+ if (aWebProgress.isLoadingDocument && !isSameDocument) {
+ this.mBrowser.mIconURL = null;
+ }
+
+ let unifiedComplete = this.mTabBrowser._unifiedComplete;
+ let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
+ if (this.mBrowser.registeredOpenURI) {
+ unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI,
+ userContextId);
+ delete this.mBrowser.registeredOpenURI;
+ }
+ // Tabs in private windows aren't registered as "Open" so
+ // that they don't appear as switch-to-tab candidates.
+ if (!isBlankPageURL(aLocation.spec) &&
+ (!PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing)) {
+ unifiedComplete.registerOpenPage(aLocation, userContextId);
+ this.mBrowser.registeredOpenURI = aLocation;
+ }
+ }
+
+ if (!this.mBlank) {
+ this._callProgressListeners("onLocationChange",
+ [aWebProgress, aRequest, aLocation,
+ aFlags]);
+ }
+
+ if (topLevel) {
+ this.mBrowser.lastURI = aLocation;
+ this.mBrowser.lastLocationChange = Date.now();
+ }
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ if (this.mBlank)
+ return;
+
+ this._callProgressListeners("onStatusChange",
+ [aWebProgress, aRequest, aStatus, aMessage]);
+
+ this.mMessage = aMessage;
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ this._callProgressListeners("onSecurityChange",
+ [aWebProgress, aRequest, aState]);
+ },
+
+ onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
+ return this._callProgressListeners("onRefreshAttempted",
+ [aWebProgress, aURI, aDelay, aSameURI]);
+ },
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <field name="serializationHelper">
+ Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ </field>
+
+ <field name="mIconLoadingPrincipal">
+ null
+ </field>
+
+ <method name="setIcon">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <parameter name="aLoadingPrincipal"/>
+ <body>
+ <![CDATA[
+ let browser = this.getBrowserForTab(aTab);
+ browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+ let loadingPrincipal = aLoadingPrincipal
+ ? aLoadingPrincipal
+ : Services.scriptSecurityManager.getSystemPrincipal();
+
+ if (aURI) {
+ if (!(aURI instanceof Ci.nsIURI)) {
+ aURI = makeURI(aURI);
+ }
+ PlacesUIUtils.loadFavicon(browser, loadingPrincipal, aURI);
+ }
+
+ let sizedIconUrl = browser.mIconURL || "";
+ if (sizedIconUrl != aTab.getAttribute("image")) {
+ if (sizedIconUrl) {
+ aTab.setAttribute("image", sizedIconUrl);
+ if (!browser.mIconLoadingPrincipal ||
+ !browser.mIconLoadingPrincipal.equals(loadingPrincipal)) {
+ aTab.setAttribute("iconLoadingPrincipal",
+ this.serializationHelper.serializeToString(loadingPrincipal));
+ browser.mIconLoadingPrincipal = loadingPrincipal;
+ }
+ }
+ else {
+ aTab.removeAttribute("image");
+ aTab.removeAttribute("iconLoadingPrincipal");
+ delete browser.mIconLoadingPrincipal;
+ }
+ this._tabAttrModified(aTab, ["image"]);
+ }
+
+ this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
+ return browser.mIconURL;
+ ]]>
+ </body>
+ </method>
+
+ <method name="shouldLoadFavIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ return (aURI &&
+ Services.prefs.getBoolPref("browser.chrome.site_icons") &&
+ Services.prefs.getBoolPref("browser.chrome.favicons") &&
+ ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
+ ]]>
+ </body>
+ </method>
+
+ <method name="useDefaultIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ var documentURI = browser.documentURI;
+ var icon = null;
+
+ if (browser.imageDocument) {
+ if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
+ let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
+ if (browser.imageDocument.width <= sz &&
+ browser.imageDocument.height <= sz) {
+ icon = browser.currentURI;
+ }
+ }
+ }
+
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ if (!icon && this.shouldLoadFavIcon(documentURI)) {
+ let url = documentURI.prePath + "/favicon.ico";
+ if (!this.isFailedIcon(url))
+ icon = url;
+ }
+ this.setIcon(aTab, icon, browser.contentPrincipal);
+ ]]>
+ </body>
+ </method>
+
+ <method name="isFailedIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = makeURI(aURI);
+ return PlacesUtils.favicons.isFailedFavicon(aURI);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getWindowTitleForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ var newTitle = "";
+ var docElement = this.ownerDocument.documentElement;
+ var sep = docElement.getAttribute("titlemenuseparator");
+
+ // Strip out any null bytes in the content title, since the
+ // underlying widget implementations of nsWindow::SetTitle pass
+ // null-terminated strings to system APIs.
+ var docTitle = aBrowser.contentTitle.replace(/\0/g, "");
+
+ if (!docTitle)
+ docTitle = docElement.getAttribute("titledefault");
+
+ var modifier = docElement.getAttribute("titlemodifier");
+ if (docTitle) {
+ newTitle += docElement.getAttribute("titlepreface");
+ newTitle += docTitle;
+ if (modifier)
+ newTitle += sep;
+ }
+ newTitle += modifier;
+
+ // If location bar is hidden and the URL type supports a host,
+ // add the scheme and host to the title to prevent spoofing.
+ // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
+ try {
+ if (docElement.getAttribute("chromehidden").includes("location")) {
+ var uri = this.mURIFixup.createExposableURI(
+ aBrowser.currentURI);
+ if (uri.scheme == "about")
+ newTitle = uri.spec + sep + newTitle;
+ else
+ newTitle = uri.prePath + sep + newTitle;
+ }
+ } catch (e) {}
+
+ return newTitle;
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateTitlebar">
+ <body>
+ <![CDATA[
+ this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <!-- Holds a unique ID for the tab change that's currently being timed.
+ Used to make sure that multiple, rapid tab switches do not try to
+ create overlapping timers. -->
+ <field name="_tabSwitchID">null</field>
+
+ <method name="updateCurrentBrowser">
+ <parameter name="aForceUpdate"/>
+ <body>
+ <![CDATA[
+ var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
+ if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
+ return;
+
+ if (!aForceUpdate) {
+ TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
+ if (!gMultiProcessBrowser) {
+ // old way of measuring tab paint which is not valid with e10s.
+ // Waiting until the next MozAfterPaint ensures that we capture
+ // the time it takes to paint, upload the textures to the compositor,
+ // and then composite.
+ if (this._tabSwitchID) {
+ TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_MS");
+ }
+
+ let tabSwitchID = Symbol();
+
+ TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_MS");
+ this._tabSwitchID = tabSwitchID;
+
+ let onMozAfterPaint = () => {
+ if (this._tabSwitchID === tabSwitchID) {
+ TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_MS");
+ this._tabSwitchID = null;
+ }
+ window.removeEventListener("MozAfterPaint", onMozAfterPaint);
+ }
+ window.addEventListener("MozAfterPaint", onMozAfterPaint);
+ }
+ }
+
+ var oldTab = this.mCurrentTab;
+
+ // Preview mode should not reset the owner
+ if (!this._previewMode && !oldTab.selected)
+ oldTab.owner = null;
+
+ if (this._lastRelatedTab) {
+ if (!this._lastRelatedTab.selected)
+ this._lastRelatedTab.owner = null;
+ this._lastRelatedTab = null;
+ }
+
+ var oldBrowser = this.mCurrentBrowser;
+
+ if (!gMultiProcessBrowser) {
+ oldBrowser.setAttribute("type", "content-targetable");
+ oldBrowser.docShellIsActive = false;
+ newBrowser.setAttribute("type", "content-primary");
+ newBrowser.docShellIsActive =
+ (window.windowState != window.STATE_MINIMIZED);
+ }
+
+ var updateBlockedPopups = false;
+ if ((oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
+ (!oldBrowser.blockedPopups && newBrowser.blockedPopups))
+ updateBlockedPopups = true;
+
+ this.mCurrentBrowser = newBrowser;
+ this.mCurrentTab = this.tabContainer.selectedItem;
+ this.showTab(this.mCurrentTab);
+
+ var forwardButtonContainer = document.getElementById("urlbar-wrapper");
+ if (forwardButtonContainer) {
+ forwardButtonContainer.setAttribute("switchingtabs", "true");
+ window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
+ window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
+ forwardButtonContainer.removeAttribute("switchingtabs");
+ });
+ }
+
+ this._appendStatusPanel();
+
+ if (updateBlockedPopups)
+ this.mCurrentBrowser.updateBlockedPopups();
+
+ // Update the URL bar.
+ var loc = this.mCurrentBrowser.currentURI;
+
+ var webProgress = this.mCurrentBrowser.webProgress;
+ var securityUI = this.mCurrentBrowser.securityUI;
+
+ this._callProgressListeners(null, "onLocationChange",
+ [webProgress, null, loc, 0], true,
+ false);
+
+ if (securityUI) {
+ // Include the true final argument to indicate that this event is
+ // simulated (instead of being observed by the webProgressListener).
+ this._callProgressListeners(null, "onSecurityChange",
+ [webProgress, null, securityUI.state, true],
+ true, false);
+ }
+
+ var listener = this._tabListeners.get(this.mCurrentTab);
+ if (listener && listener.mStateFlags) {
+ this._callProgressListeners(null, "onUpdateCurrentBrowser",
+ [listener.mStateFlags, listener.mStatus,
+ listener.mMessage, listener.mTotalProgress],
+ true, false);
+ }
+
+ if (!this._previewMode) {
+ this._recordTabAccess(this.mCurrentTab);
+
+ this.mCurrentTab.updateLastAccessed();
+ this.mCurrentTab.removeAttribute("unread");
+ oldTab.updateLastAccessed();
+
+ let oldFindBar = oldTab._findBar;
+ if (oldFindBar &&
+ oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
+ !oldFindBar.hidden)
+ this._lastFindValue = oldFindBar._findField.value;
+
+ this.updateTitlebar();
+
+ this.mCurrentTab.removeAttribute("titlechanged");
+ this.mCurrentTab.removeAttribute("attention");
+ }
+
+ // If the new tab is busy, and our current state is not busy, then
+ // we need to fire a start to all progress listeners.
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
+ this.mIsBusy = true;
+ this._callProgressListeners(null, "onStateChange",
+ [webProgress, null,
+ nsIWebProgressListener.STATE_START |
+ nsIWebProgressListener.STATE_IS_NETWORK, 0],
+ true, false);
+ }
+
+ // If the new tab is not busy, and our current state is busy, then
+ // we need to fire a stop to all progress listeners.
+ if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
+ this.mIsBusy = false;
+ this._callProgressListeners(null, "onStateChange",
+ [webProgress, null,
+ nsIWebProgressListener.STATE_STOP |
+ nsIWebProgressListener.STATE_IS_NETWORK, 0],
+ true, false);
+ }
+
+ this._setCloseKeyState(!this.mCurrentTab.pinned);
+
+ // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
+ // that might rely upon the other changes suppressed.
+ // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
+ if (!this._previewMode) {
+ // We've selected the new tab, so go ahead and notify listeners.
+ let event = new CustomEvent("TabSelect", {
+ bubbles: true,
+ cancelable: false,
+ detail: {
+ previousTab: oldTab
+ }
+ });
+ this.mCurrentTab.dispatchEvent(event);
+
+ this._tabAttrModified(oldTab, ["selected"]);
+ this._tabAttrModified(this.mCurrentTab, ["selected"]);
+
+ if (oldBrowser != newBrowser &&
+ oldBrowser.getInPermitUnload) {
+ oldBrowser.getInPermitUnload(inPermitUnload => {
+ if (!inPermitUnload) {
+ return;
+ }
+ // Since the user is switching away from a tab that has
+ // a beforeunload prompt active, we remove the prompt.
+ // This prevents confusing user flows like the following:
+ // 1. User attempts to close Firefox
+ // 2. User switches tabs (ingoring a beforeunload prompt)
+ // 3. User returns to tab, presses "Leave page"
+ let promptBox = this.getTabModalPromptBox(oldBrowser);
+ let prompts = promptBox.listPrompts();
+ // There might not be any prompts here if the tab was closed
+ // while in an onbeforeunload prompt, which will have
+ // destroyed aforementioned prompt already, so check there's
+ // something to remove, first:
+ if (prompts.length) {
+ // NB: This code assumes that the beforeunload prompt
+ // is the top-most prompt on the tab.
+ prompts[prompts.length - 1].abortPrompt();
+ }
+ });
+ }
+
+ oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
+ if (this.isFindBarInitialized(oldTab)) {
+ let findBar = this.getFindBar(oldTab);
+ oldTab._findBarFocused = (!findBar.hidden &&
+ findBar._findField.getAttribute("focused") == "true");
+ }
+
+ // If focus is in the tab bar, retain it there.
+ if (document.activeElement == oldTab) {
+ // We need to explicitly focus the new tab, because
+ // tabbox.xml does this only in some cases.
+ this.mCurrentTab.focus();
+ } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) {
+ // Clear focus so that _adjustFocusAfterTabSwitch can detect if
+ // some element has been focused and respect that.
+ document.activeElement.blur();
+ }
+
+ if (!gMultiProcessBrowser)
+ this._adjustFocusAfterTabSwitch(this.mCurrentTab);
+ }
+
+ updateUserContextUIIndicator();
+ gIdentityHandler.updateSharingIndicator();
+
+ this.tabContainer._setPositionalAttributes();
+
+ if (!gMultiProcessBrowser) {
+ let event = new CustomEvent("TabSwitchDone", {
+ bubbles: true,
+ cancelable: true
+ });
+ this.dispatchEvent(event);
+ }
+
+ if (!aForceUpdate)
+ TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
+ ]]>
+ </body>
+ </method>
+
+ <method name="_adjustFocusAfterTabSwitch">
+ <parameter name="newTab"/>
+ <body><![CDATA[
+ // Don't steal focus from the tab bar.
+ if (document.activeElement == newTab)
+ return;
+
+ let newBrowser = this.getBrowserForTab(newTab);
+
+ // If there's a tabmodal prompt showing, focus it.
+ if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
+ let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+ let prompt = prompts[prompts.length - 1];
+ prompt.Dialog.setDefaultFocus();
+ return;
+ }
+
+ // Focus the location bar if it was previously focused for that tab.
+ // In full screen mode, only bother making the location bar visible
+ // if the tab is a blank one.
+ if (newBrowser._urlbarFocused && gURLBar) {
+ // Explicitly close the popup if the URL bar retains focus
+ gURLBar.closePopup();
+
+ if (!window.fullScreen) {
+ gURLBar.focus();
+ return;
+ }
+
+ if (isTabEmpty(this.mCurrentTab)) {
+ focusAndSelectUrlBar();
+ return;
+ }
+ }
+
+ // Focus the find bar if it was previously focused for that tab.
+ if (gFindBarInitialized && !gFindBar.hidden &&
+ this.selectedTab._findBarFocused) {
+ gFindBar._findField.focus();
+ return;
+ }
+
+ // Don't focus the content area if something has been focused after the
+ // tab switch was initiated.
+ if (gMultiProcessBrowser &&
+ document.activeElement != document.documentElement)
+ return;
+
+ // We're now committed to focusing the content area.
+ let fm = Services.focus;
+ let focusFlags = fm.FLAG_NOSCROLL;
+
+ if (!gMultiProcessBrowser) {
+ let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
+
+ // for anchors, use FLAG_SHOWRING so that it is clear what link was
+ // last clicked when switching back to that tab
+ if (newFocusedElement &&
+ (newFocusedElement instanceof HTMLAnchorElement ||
+ newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
+ focusFlags |= fm.FLAG_SHOWRING;
+ }
+
+ fm.setFocus(newBrowser, focusFlags);
+ ]]></body>
+ </method>
+
+ <!--
+ This function assumes we have an LRU cache of tabs (either
+ images of tab content or their layers). The goal is to find
+ out how far into the cache we need to look in order to find
+ aTab. We record this number in telemetry and also move aTab to
+ the front of the cache.
+
+ A newly created tab has position Infinity in the cache.
+ If a tab is closed, it has no effect on the position of other
+ tabs in the cache since we assume that closing a tab doesn't
+ cause us to load in any other tabs.
+
+ We ignore the effect of dragging tabs between windows.
+ -->
+ <method name="_recordTabAccess">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (!Services.telemetry.canRecordExtended) {
+ return;
+ }
+
+ let tabs = Array.from(this.visibleTabs);
+
+ let pos = aTab.cachePosition;
+ for (let i = 0; i < tabs.length; i++) {
+ // If aTab is moving to the front, everything that was
+ // previously in front of it is bumped up one position.
+ if (tabs[i].cachePosition < pos) {
+ tabs[i].cachePosition++;
+ }
+ }
+ aTab.cachePosition = 0;
+
+ if (isFinite(pos)) {
+ Services.telemetry.getHistogramById("TAB_SWITCH_CACHE_POSITION").add(pos);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_tabAttrModified">
+ <parameter name="aTab"/>
+ <parameter name="aChanged"/>
+ <body><![CDATA[
+ if (aTab.closing)
+ return;
+
+ let event = new CustomEvent("TabAttrModified", {
+ bubbles: true,
+ cancelable: false,
+ detail: {
+ changed: aChanged,
+ }
+ });
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="setBrowserSharing">
+ <parameter name="aBrowser"/>
+ <parameter name="aState"/>
+ <body><![CDATA[
+ let tab = this.getTabForBrowser(aBrowser);
+ if (!tab)
+ return;
+
+ let sharing;
+ if (aState.screen) {
+ sharing = "screen";
+ } else if (aState.camera) {
+ sharing = "camera";
+ } else if (aState.microphone) {
+ sharing = "microphone";
+ }
+
+ if (sharing) {
+ tab.setAttribute("sharing", sharing);
+ tab._sharingState = aState;
+ } else {
+ tab.removeAttribute("sharing");
+ tab._sharingState = null;
+ }
+ this._tabAttrModified(tab, ["sharing"]);
+
+ if (aBrowser == this.mCurrentBrowser)
+ gIdentityHandler.updateSharingIndicator();
+ ]]></body>
+ </method>
+
+
+ <method name="setTabTitleLoading">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ aTab.label = this.mStringBundle.getString("tabs.connecting");
+ aTab.crop = "end";
+ this._tabAttrModified(aTab, ["label", "crop"]);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitle">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ var crop = "end";
+ var title = browser.contentTitle;
+
+ if (!title) {
+ if (browser.currentURI.spec) {
+ try {
+ title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
+ } catch (ex) {
+ title = browser.currentURI.spec;
+ }
+ }
+
+ if (title && !isBlankPageURL(title)) {
+ // At this point, we now have a URI.
+ // Let's try to unescape it using a character set
+ // in case the URI is not ASCII.
+ try {
+ var characterSet = browser.characterSet;
+ const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
+ .getService(Components.interfaces.nsITextToSubURI);
+ title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
+ } catch (ex) { /* Do nothing. */ }
+
+ crop = "center";
+
+ } else if (aTab.hasAttribute("customizemode")) {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ title = gNavigatorBundle.getFormattedString("customizeMode.tabTitle",
+ [ brandShortName ]);
+ } else // Still no title? Fall back to our untitled string.
+ title = this.mStringBundle.getString("tabs.emptyTabTitle");
+ }
+
+ if (aTab.label == title &&
+ aTab.crop == crop)
+ return false;
+
+ aTab.label = title;
+ aTab.crop = crop;
+ this._tabAttrModified(aTab, ["label", "crop"]);
+
+ if (aTab.selected)
+ this.updateTitlebar();
+
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadOneTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ var aReferrerPolicy;
+ var aFromExternal;
+ var aRelatedToCurrent;
+ var aAllowMixedContent;
+ var aSkipAnimation;
+ var aForceNotRemote;
+ var aNoReferrer;
+ var aUserContextId;
+ var aRelatedBrowser;
+ var aOriginPrincipal;
+ var aOpener;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aReferrerURI = params.referrerURI;
+ aReferrerPolicy = params.referrerPolicy;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aLoadInBackground = params.inBackground;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aAllowMixedContent = params.allowMixedContent;
+ aSkipAnimation = params.skipAnimation;
+ aForceNotRemote = params.forceNotRemote;
+ aNoReferrer = params.noReferrer;
+ aUserContextId = params.userContextId;
+ aRelatedBrowser = params.relatedBrowser;
+ aOriginPrincipal = params.originPrincipal;
+ aOpener = params.opener;
+ }
+
+ var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
+ Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ var owner = bgLoad ? null : this.selectedTab;
+ var tab = this.addTab(aURI, {
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ ownerTab: owner,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ fromExternal: aFromExternal,
+ relatedToCurrent: aRelatedToCurrent,
+ skipAnimation: aSkipAnimation,
+ allowMixedContent: aAllowMixedContent,
+ forceNotRemote: aForceNotRemote,
+ noReferrer: aNoReferrer,
+ userContextId: aUserContextId,
+ originPrincipal: aOriginPrincipal,
+ relatedBrowser: aRelatedBrowser,
+ opener: aOpener });
+ if (!bgLoad)
+ this.selectedTab = tab;
+
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadTabs">
+ <parameter name="aURIs"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aReplace"/>
+ <body><![CDATA[
+ let aAllowThirdPartyFixup;
+ let aTargetTab;
+ let aNewIndex = -1;
+ let aPostDatas = [];
+ let aUserContextId;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object") {
+ let params = arguments[1];
+ aLoadInBackground = params.inBackground;
+ aReplace = params.replace;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aTargetTab = params.targetTab;
+ aNewIndex = typeof params.newIndex === "number" ?
+ params.newIndex : aNewIndex;
+ aPostDatas = params.postDatas || aPostDatas;
+ aUserContextId = params.userContextId;
+ }
+
+ if (!aURIs.length)
+ return;
+
+ // The tab selected after this new tab is closed (i.e. the new tab's
+ // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
+ // when several urls are opened here (i.e. closing the first should select
+ // the next of many URLs opened) or if the pref to have UI links opened in
+ // the background is set (i.e. the link is not being opened modally)
+ //
+ // i.e.
+ // Number of URLs Load UI Links in BG Focus Last Viewed?
+ // == 1 false YES
+ // == 1 true NO
+ // > 1 false/true NO
+ var multiple = aURIs.length > 1;
+ var owner = multiple || aLoadInBackground ? null : this.selectedTab;
+ var firstTabAdded = null;
+ var targetTabIndex = -1;
+
+ if (aReplace) {
+ let browser;
+ if (aTargetTab) {
+ browser = this.getBrowserForTab(aTargetTab);
+ targetTabIndex = aTargetTab._tPos;
+ } else {
+ browser = this.mCurrentBrowser;
+ targetTabIndex = this.tabContainer.selectedIndex;
+ }
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ try {
+ browser.loadURIWithFlags(aURIs[0], {
+ flags, postData: aPostDatas[0]
+ });
+ } catch (e) {
+ // Ignore failure in case a URI is wrong, so we can continue
+ // opening the next ones.
+ }
+ } else {
+ firstTabAdded = this.addTab(aURIs[0], {
+ ownerTab: owner,
+ skipAnimation: multiple,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostDatas[0],
+ userContextId: aUserContextId
+ });
+ if (aNewIndex !== -1) {
+ this.moveTabTo(firstTabAdded, aNewIndex);
+ targetTabIndex = firstTabAdded._tPos;
+ }
+ }
+
+ let tabNum = targetTabIndex;
+ for (let i = 1; i < aURIs.length; ++i) {
+ let tab = this.addTab(aURIs[i], {
+ skipAnimation: true,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostDatas[i],
+ userContextId: aUserContextId
+ });
+ if (targetTabIndex !== -1)
+ this.moveTabTo(tab, ++tabNum);
+ }
+
+ if (!aLoadInBackground) {
+ if (firstTabAdded) {
+ // .selectedTab setter focuses the content area
+ this.selectedTab = firstTabAdded;
+ }
+ else
+ this.selectedBrowser.focus();
+ }
+ ]]></body>
+ </method>
+
+ <method name="updateBrowserRemoteness">
+ <parameter name="aBrowser"/>
+ <parameter name="aShouldBeRemote"/>
+ <parameter name="aOpener"/>
+ <parameter name="aFreshProcess"/>
+ <body>
+ <![CDATA[
+ let isRemote = aBrowser.getAttribute("remote") == "true";
+
+ // If we are passed an opener, we must be making the browser non-remote, and
+ // if the browser is _currently_ non-remote, we need the openers to match,
+ // because it is already too late to change it.
+ if (aOpener) {
+ if (aShouldBeRemote) {
+ throw new Exception("Cannot set an opener on a browser which should be remote!");
+ }
+ if (!isRemote && aBrowser.contentWindow.opener != aOpener) {
+ throw new Exception("Cannot change opener on an already non-remote browser!");
+ }
+ }
+
+ // Abort if we're not going to change anything
+ if (isRemote == aShouldBeRemote && !aFreshProcess) {
+ return false;
+ }
+
+ let tab = this.getTabForBrowser(aBrowser);
+ let evt = document.createEvent("Events");
+ evt.initEvent("BeforeTabRemotenessChange", true, false);
+ tab.dispatchEvent(evt);
+
+ let wasActive = document.activeElement == aBrowser;
+
+ // Unmap the old outerWindowID.
+ this._outerWindowIDBrowserMap.delete(aBrowser.outerWindowID);
+
+ // Unhook our progress listener.
+ let filter = this._tabFilters.get(tab);
+ let listener = this._tabListeners.get(tab);
+ aBrowser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(listener);
+
+ // We'll be creating a new listener, so destroy the old one.
+ listener.destroy();
+
+ let oldUserTypedValue = aBrowser.userTypedValue;
+ let hadStartedLoad = aBrowser.didStartLoadSinceLastUserTyping();
+
+ // Make sure the browser is destroyed so it unregisters from observer notifications
+ aBrowser.destroy();
+
+ // Make sure to restore the original droppedLinkHandler and
+ // relatedBrowser.
+ let droppedLinkHandler = aBrowser.droppedLinkHandler;
+ let relatedBrowser = aBrowser.relatedBrowser;
+
+ // Change the "remote" attribute.
+ let parent = aBrowser.parentNode;
+ parent.removeChild(aBrowser);
+ aBrowser.setAttribute("remote", aShouldBeRemote ? "true" : "false");
+
+ // NB: This works with the hack in the browser constructor that
+ // turns this normal property into a field.
+ aBrowser.relatedBrowser = relatedBrowser;
+
+ // Set the opener window on the browser, such that when the frame
+ // loader is created the opener is set correctly.
+ aBrowser.presetOpenerWindow(aOpener);
+
+ // Set the freshProcess attribute so that the frameloader knows to
+ // create a new process
+ if (aFreshProcess) {
+ aBrowser.setAttribute("freshProcess", "true");
+ }
+
+ parent.appendChild(aBrowser);
+
+ // Remove the freshProcess attribute if we set it, as we don't
+ // want it to apply for the next time the frameloader is created
+ aBrowser.removeAttribute("freshProcess");
+
+ aBrowser.userTypedValue = oldUserTypedValue;
+ if (hadStartedLoad) {
+ aBrowser.urlbarChangeTracker.startedLoad();
+ }
+
+ aBrowser.droppedLinkHandler = droppedLinkHandler;
+
+ // Switching a browser's remoteness will create a new frameLoader.
+ // As frameLoaders start out with an active docShell we have to
+ // deactivate it if this is not the selected tab's browser or the
+ // browser window is minimized.
+ aBrowser.docShellIsActive = this.shouldActivateDocShell(aBrowser);
+
+ // Create a new tab progress listener for the new browser we just injected,
+ // since tab progress listeners have logic for handling the initial about:blank
+ // load
+ listener = this.mTabProgressListener(tab, aBrowser, true, false);
+ this._tabListeners.set(tab, listener);
+ filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
+
+ // Restore the progress listener.
+ aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
+
+ // Restore the securityUI state.
+ let securityUI = aBrowser.securityUI;
+ let state = securityUI ? securityUI.state
+ : Ci.nsIWebProgressListener.STATE_IS_INSECURE;
+ // Include the true final argument to indicate that this event is
+ // simulated (instead of being observed by the webProgressListener).
+ this._callProgressListeners(aBrowser, "onSecurityChange",
+ [aBrowser.webProgress, null, state, true],
+ true, false);
+
+ if (aShouldBeRemote) {
+ // Switching the browser to be remote will connect to a new child
+ // process so the browser can no longer be considered to be
+ // crashed.
+ tab.removeAttribute("crashed");
+ } else {
+ aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
+
+ // Register the new outerWindowID.
+ this._outerWindowIDBrowserMap.set(aBrowser.outerWindowID, aBrowser);
+ }
+
+ if (wasActive)
+ aBrowser.focus();
+
+ // If the findbar has been initialised, reset its browser reference.
+ if (this.isFindBarInitialized(tab)) {
+ this.getFindBar(tab).browser = aBrowser;
+ }
+
+ evt = document.createEvent("Events");
+ evt.initEvent("TabRemotenessChange", true, false);
+ tab.dispatchEvent(evt);
+
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="switchBrowserIntoFreshProcess">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ if (!gMultiProcessBrowser) {
+ return this.updateBrowserRemoteness(aBrowser, false);
+ }
+
+ return this.updateBrowserRemoteness(aBrowser,
+ /* aShouldBeRemote */ true,
+ /* aOpener */ null,
+ /* aFreshProcess */ true);
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateBrowserRemotenessByURL">
+ <parameter name="aBrowser"/>
+ <parameter name="aURL"/>
+ <body>
+ <![CDATA[
+ if (!gMultiProcessBrowser)
+ return this.updateBrowserRemoteness(aBrowser, false);
+
+ let process = aBrowser.isRemoteBrowser ? Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT
+ : Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
+ // If this URL can't load in the browser's current process then flip
+ // it to the other process
+ if (!E10SUtils.canLoadURIInProcess(aURL, process))
+ return this.updateBrowserRemoteness(aBrowser, !aBrowser.isRemoteBrowser);
+
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <field name="_preloadedBrowser">null</field>
+ <method name="_getPreloadedBrowser">
+ <body>
+ <![CDATA[
+ if (!this._isPreloadingEnabled()) {
+ return null;
+ }
+
+ // The preloaded browser might be null.
+ let browser = this._preloadedBrowser;
+
+ // Consume the browser.
+ this._preloadedBrowser = null;
+
+ // Attach the nsIFormFillController now that we know the browser
+ // will be used. If we do that before and the preloaded browser
+ // won't be consumed until shutdown then we leak a docShell.
+ // Also, we do not need to take care of attaching nsIFormFillControllers
+ // in the case that the browser is remote, as remote browsers take
+ // care of that themselves.
+ if (browser && this.hasAttribute("autocompletepopup")) {
+ browser.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
+ }
+
+ return browser;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_isPreloadingEnabled">
+ <body>
+ <![CDATA[
+ // Preloading for the newtab page is enabled when the pref is true
+ // and the URL is "about:newtab". We do not support preloading for
+ // custom newtab URLs.
+ return Services.prefs.getBoolPref("browser.newtab.preload") &&
+ !aboutNewTabService.overridden;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_createPreloadBrowser">
+ <body>
+ <![CDATA[
+ // Do nothing if we have a preloaded browser already
+ // or preloading of newtab pages is disabled.
+ if (this._preloadedBrowser || !this._isPreloadingEnabled()) {
+ return;
+ }
+
+ let remote = gMultiProcessBrowser &&
+ E10SUtils.canLoadURIInProcess(BROWSER_NEW_TAB_URL, Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT);
+ let browser = this._createBrowser({isPreloadBrowser: true, remote: remote});
+ this._preloadedBrowser = browser;
+
+ let notificationbox = this.getNotificationBox(browser);
+ this.mPanelContainer.appendChild(notificationbox);
+
+ if (remote) {
+ // For remote browsers, we need to make sure that the webProgress is
+ // instantiated, otherwise the parent won't get informed about the state
+ // of the preloaded browser until it gets attached to a tab.
+ browser.webProgress;
+ }
+
+ browser.loadURI(BROWSER_NEW_TAB_URL);
+ browser.docShellIsActive = false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_createBrowser">
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ // Supported parameters:
+ // userContextId, remote, isPreloadBrowser, uriIsAboutBlank, permanentKey
+
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ let b = document.createElementNS(NS_XUL, "browser");
+ b.permanentKey = aParams.permanentKey || {};
+ b.setAttribute("type", "content-targetable");
+ b.setAttribute("message", "true");
+ b.setAttribute("messagemanagergroup", "browsers");
+ b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
+ b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
+
+ if (aParams.userContextId) {
+ b.setAttribute("usercontextid", aParams.userContextId);
+ }
+
+ if (aParams.remote) {
+ b.setAttribute("remote", "true");
+ }
+
+ if (aParams.opener) {
+ if (aParams.remote) {
+ throw new Exception("Cannot set opener window on a remote browser!");
+ }
+ b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.opener);
+ }
+
+ if (window.gShowPageResizers && window.windowState == window.STATE_NORMAL) {
+ b.setAttribute("showresizer", "true");
+ }
+
+ if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
+ b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
+ }
+
+ if (this.hasAttribute("selectmenulist"))
+ b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
+
+ if (this.hasAttribute("datetimepicker")) {
+ b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
+ }
+
+ b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+
+ if (aParams.relatedBrowser) {
+ b.relatedBrowser = aParams.relatedBrowser;
+ }
+
+ // Create the browserStack container
+ var stack = document.createElementNS(NS_XUL, "stack");
+ stack.className = "browserStack";
+ stack.appendChild(b);
+ stack.setAttribute("flex", "1");
+
+ // Create the browserContainer
+ var browserContainer = document.createElementNS(NS_XUL, "vbox");
+ browserContainer.className = "browserContainer";
+ browserContainer.appendChild(stack);
+ browserContainer.setAttribute("flex", "1");
+
+ // Create the sidebar container
+ var browserSidebarContainer = document.createElementNS(NS_XUL,
+ "hbox");
+ browserSidebarContainer.className = "browserSidebarContainer";
+ browserSidebarContainer.appendChild(browserContainer);
+ browserSidebarContainer.setAttribute("flex", "1");
+
+ // Add the Message and the Browser to the box
+ var notificationbox = document.createElementNS(NS_XUL,
+ "notificationbox");
+ notificationbox.setAttribute("flex", "1");
+ notificationbox.setAttribute("notificationside", "top");
+ notificationbox.appendChild(browserSidebarContainer);
+
+ // Prevent the superfluous initial load of a blank document
+ // if we're going to load something other than about:blank.
+ if (!aParams.uriIsAboutBlank) {
+ b.setAttribute("nodefaultsrc", "true");
+ }
+
+ return b;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_linkBrowserToTab">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ "use strict";
+
+ // Supported parameters:
+ // forceNotRemote, userContextId
+
+ let uriIsAboutBlank = !aURI || aURI == "about:blank";
+
+ // The new browser should be remote if this is an e10s window and
+ // the uri to load can be loaded remotely.
+ let remote = gMultiProcessBrowser &&
+ !aParams.forceNotRemote &&
+ E10SUtils.canLoadURIInProcess(aURI, Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT);
+
+ let browser;
+ let usingPreloadedContent = false;
+
+ // If we open a new tab with the newtab URL in the default
+ // userContext, check if there is a preloaded browser ready.
+ // Private windows are not included because both the label and the
+ // icon for the tab would be set incorrectly (see bug 1195981).
+ if (aURI == BROWSER_NEW_TAB_URL &&
+ !aParams.userContextId &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ browser = this._getPreloadedBrowser();
+ if (browser) {
+ usingPreloadedContent = true;
+ aTab.permanentKey = browser.permanentKey;
+ }
+ }
+
+ if (!browser) {
+ // No preloaded browser found, create one.
+ browser = this._createBrowser({permanentKey: aTab.permanentKey,
+ remote: remote,
+ uriIsAboutBlank: uriIsAboutBlank,
+ userContextId: aParams.userContextId,
+ relatedBrowser: aParams.relatedBrowser,
+ opener: aParams.opener});
+ }
+
+ let notificationbox = this.getNotificationBox(browser);
+ let uniqueId = this._generateUniquePanelID();
+ notificationbox.id = uniqueId;
+ aTab.linkedPanel = uniqueId;
+ aTab.linkedBrowser = browser;
+ aTab.hasBrowser = true;
+ this._tabForBrowser.set(browser, aTab);
+
+ // Inject the <browser> into the DOM if necessary.
+ if (!notificationbox.parentNode) {
+ // NB: this appendChild call causes us to run constructors for the
+ // browser element, which fires off a bunch of notifications. Some
+ // of those notifications can cause code to run that inspects our
+ // state, so it is important that the tab element is fully
+ // initialized by this point.
+ this.mPanelContainer.appendChild(notificationbox);
+ }
+
+ // wire up a progress listener for the new browser object.
+ let tabListener = this.mTabProgressListener(aTab, browser, uriIsAboutBlank, usingPreloadedContent);
+ const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
+ browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
+ this._tabListeners.set(aTab, tabListener);
+ this._tabFilters.set(aTab, filter);
+
+ browser.droppedLinkHandler = handleDroppedLink;
+
+ // We start our browsers out as inactive, and then maintain
+ // activeness in the tab switcher.
+ browser.docShellIsActive = false;
+
+ // When addTab() is called with an URL that is not "about:blank" we
+ // set the "nodefaultsrc" attribute that prevents a frameLoader
+ // from being created as soon as the linked <browser> is inserted
+ // into the DOM. We thus have to register the new outerWindowID
+ // for non-remote browsers after we have called browser.loadURI().
+ if (!remote) {
+ this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser);
+ }
+
+ var evt = new CustomEvent("TabBrowserInserted", { bubbles: true, detail: {} });
+ aTab.dispatchEvent(evt);
+
+ return { usingPreloadedContent: usingPreloadedContent };
+ ]]>
+ </body>
+ </method>
+
+ <method name="addTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aOwner"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ "use strict";
+
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var aReferrerPolicy;
+ var aFromExternal;
+ var aRelatedToCurrent;
+ var aSkipAnimation;
+ var aAllowMixedContent;
+ var aForceNotRemote;
+ var aNoReferrer;
+ var aUserContextId;
+ var aEventDetail;
+ var aRelatedBrowser;
+ var aOriginPrincipal;
+ var aOpener;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aReferrerURI = params.referrerURI;
+ aReferrerPolicy = params.referrerPolicy;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aOwner = params.ownerTab;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aSkipAnimation = params.skipAnimation;
+ aAllowMixedContent = params.allowMixedContent;
+ aForceNotRemote = params.forceNotRemote;
+ aNoReferrer = params.noReferrer;
+ aUserContextId = params.userContextId;
+ aEventDetail = params.eventDetail;
+ aRelatedBrowser = params.relatedBrowser;
+ aOriginPrincipal = params.originPrincipal;
+ aOpener = params.opener;
+ }
+
+ // if we're adding tabs, we're past interrupt mode, ditch the owner
+ if (this.mCurrentTab.owner)
+ this.mCurrentTab.owner = null;
+
+ var t = document.createElementNS(NS_XUL, "tab");
+
+ var uriIsAboutBlank = !aURI || aURI == "about:blank";
+ let aURIObject = null;
+ try {
+ aURIObject = Services.io.newURI(aURI || "about:blank");
+ } catch (ex) { /* we'll try to fix up this URL later */ }
+
+ if (!aURI || isBlankPageURL(aURI)) {
+ t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
+ } else if (aURI.toLowerCase().startsWith("javascript:")) {
+ // This can go away when bug 672618 or bug 55696 are fixed.
+ t.setAttribute("label", aURI);
+ }
+
+ if (aUserContextId) {
+ t.setAttribute("usercontextid", aUserContextId);
+ ContextualIdentityService.setTabStyle(t);
+ }
+
+ t.setAttribute("crop", "end");
+ t.setAttribute("onerror", "this.removeAttribute('image');");
+ t.className = "tabbrowser-tab";
+
+ this.tabContainer._unlockTabSizing();
+
+ // When overflowing, new tabs are scrolled into view smoothly, which
+ // doesn't go well together with the width transition. So we skip the
+ // transition in that case.
+ let animate = !aSkipAnimation &&
+ this.tabContainer.getAttribute("overflow") != "true" &&
+ Services.prefs.getBoolPref("browser.tabs.animate");
+ if (!animate) {
+ t.setAttribute("fadein", "true");
+ setTimeout(function (tabContainer) {
+ tabContainer._handleNewTab(t);
+ }, 0, this.tabContainer);
+ }
+
+ // invalidate cache
+ this._visibleTabs = null;
+
+ this.tabContainer.appendChild(t);
+
+ // If this new tab is owned by another, assert that relationship
+ if (aOwner)
+ t.owner = aOwner;
+
+ var position = this.tabs.length - 1;
+ t._tPos = position;
+ t.permanentKey = {};
+ this.tabContainer._setPositionalAttributes();
+
+ this.tabContainer.updateVisibility();
+
+ // Currently in this incarnation of bug 906076, we are forcing the
+ // browser to immediately be linked. In future incarnations of this
+ // bug this will be removed so we can leave the tab in its "lazy"
+ // state to be exploited for startup optimization. Note that for
+ // now this must occur before "TabOpen" event is fired, as that will
+ // trigger SessionStore.jsm to run code that expects the existence
+ // of tab.linkedBrowser.
+ let browserParams = {
+ forceNotRemote: aForceNotRemote,
+ userContextId: aUserContextId,
+ relatedBrowser: aRelatedBrowser,
+ opener: aOpener,
+ };
+ let { usingPreloadedContent } = this._linkBrowserToTab(t, aURI, browserParams);
+ let b = t.linkedBrowser;
+
+ // Dispatch a new tab notification. We do this once we're
+ // entirely done, so that things are in a consistent state
+ // even if the event listener opens or closes tabs.
+ var detail = aEventDetail || {};
+ var evt = new CustomEvent("TabOpen", { bubbles: true, detail });
+ t.dispatchEvent(evt);
+
+ if (!usingPreloadedContent && aOriginPrincipal && aURI) {
+ let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+ // Unless we know for sure we're not inheriting principals,
+ // force the about:blank viewer to have the right principal:
+ if (!aURIObject ||
+ (Services.io.getProtocolFlags(aURIObject.scheme) & URI_INHERITS_SECURITY_CONTEXT)) {
+ b.createAboutBlankContentViewer(aOriginPrincipal);
+ }
+ }
+
+ // If we didn't swap docShells with a preloaded browser
+ // then let's just continue loading the page normally.
+ if (!usingPreloadedContent && !uriIsAboutBlank) {
+ // pretend the user typed this so it'll be available till
+ // the document successfully loads
+ if (aURI && gInitialPages.indexOf(aURI) == -1)
+ b.userTypedValue = aURI;
+
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ if (aFromExternal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
+ if (aAllowMixedContent)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
+ try {
+ b.loadURIWithFlags(aURI, {
+ flags,
+ referrerURI: aNoReferrer ? null: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ });
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ // Check if we're opening a tab related to the current tab and
+ // move it to after the current tab.
+ // aReferrerURI is null or undefined if the tab is opened from
+ // an external application or bookmark, i.e. somewhere other
+ // than the current tab.
+ if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
+ Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
+ let newTabPos = (this._lastRelatedTab ||
+ this.selectedTab)._tPos + 1;
+ if (this._lastRelatedTab)
+ this._lastRelatedTab.owner = null;
+ else
+ t.owner = this.selectedTab;
+ this.moveTabTo(t, newTabPos);
+ this._lastRelatedTab = t;
+ }
+
+ if (animate) {
+ requestAnimationFrame(function () {
+ this.tabContainer._handleTabTelemetryStart(t, aURI);
+
+ // kick the animation off
+ t.setAttribute("fadein", "true");
+ }.bind(this));
+ }
+
+ return t;
+ ]]>
+ </body>
+ </method>
+
+ <method name="warnAboutClosingTabs">
+ <parameter name="aCloseTabs"/>
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var tabsToClose;
+ switch (aCloseTabs) {
+ case this.closingTabsEnum.ALL:
+ tabsToClose = this.tabs.length - this._removingTabs.length -
+ gBrowser._numPinnedTabs;
+ break;
+ case this.closingTabsEnum.OTHER:
+ tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
+ break;
+ case this.closingTabsEnum.TO_END:
+ if (!aTab)
+ throw new Error("Required argument missing: aTab");
+
+ tabsToClose = this.getTabsToTheEndFrom(aTab).length;
+ break;
+ default:
+ throw new Error("Invalid argument: " + aCloseTabs);
+ }
+
+ if (tabsToClose <= 1)
+ return true;
+
+ const pref = aCloseTabs == this.closingTabsEnum.ALL ?
+ "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
+ var shouldPrompt = Services.prefs.getBoolPref(pref);
+ if (!shouldPrompt)
+ return true;
+
+ var ps = Services.prompt;
+
+ // default to true: if it were false, we wouldn't get this far
+ var warnOnClose = { value: true };
+ var bundle = this.mStringBundle;
+
+ // focus the window before prompting.
+ // this will raise any minimized window, which will
+ // make it obvious which window the prompt is for and will
+ // solve the problem of windows "obscuring" the prompt.
+ // see bug #350299 for more details
+ window.focus();
+ var warningMessage =
+ PluralForm.get(tabsToClose, bundle.getString("tabs.closeWarningMultiple"))
+ .replace("#1", tabsToClose);
+ var buttonPressed =
+ ps.confirmEx(window,
+ bundle.getString("tabs.closeWarningTitle"),
+ warningMessage,
+ (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
+ + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
+ bundle.getString("tabs.closeButtonMultiple"),
+ null, null,
+ aCloseTabs == this.closingTabsEnum.ALL ?
+ bundle.getString("tabs.closeWarningPromptMe") : null,
+ warnOnClose);
+ var reallyClose = (buttonPressed == 0);
+
+ // don't set the pref unless they press OK and it's false
+ if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
+ Services.prefs.setBoolPref(pref, false);
+
+ return reallyClose;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var tabsToEnd = [];
+ let tabs = this.visibleTabs;
+ for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
+ tabsToEnd.push(tabs[i]);
+ }
+ return tabsToEnd.reverse();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
+ let tabs = this.getTabsToTheEndFrom(aTab);
+ for (let i = tabs.length - 1; i >= 0; --i) {
+ this.removeTab(tabs[i], aParams);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeAllTabsBut">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.pinned)
+ return;
+
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
+ let tabs = this.visibleTabs;
+ this.selectedTab = aTab;
+
+ for (let i = tabs.length - 1; i >= 0; --i) {
+ if (tabs[i] != aTab && !tabs[i].pinned)
+ this.removeTab(tabs[i], {animate: true});
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentTab">
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ this.removeTab(this.mCurrentTab, aParams);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_removingTabs">
+ []
+ </field>
+
+ <method name="removeTab">
+ <parameter name="aTab"/>
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ if (aParams) {
+ var animate = aParams.animate;
+ var byMouse = aParams.byMouse;
+ var skipPermitUnload = aParams.skipPermitUnload;
+ }
+
+ // Handle requests for synchronously removing an already
+ // asynchronously closing tab.
+ if (!animate &&
+ aTab.closing) {
+ this._endRemoveTab(aTab);
+ return;
+ }
+
+ var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
+
+ if (!this._beginRemoveTab(aTab, null, null, true, skipPermitUnload))
+ return;
+
+ if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
+ this.tabContainer._lockTabSizing(aTab);
+ else
+ this.tabContainer._unlockTabSizing();
+
+ if (!animate /* the caller didn't opt in */ ||
+ isLastTab ||
+ aTab.pinned ||
+ aTab.hidden ||
+ this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
+ aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
+ window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
+ !Services.prefs.getBoolPref("browser.tabs.animate")) {
+ this._endRemoveTab(aTab);
+ return;
+ }
+
+ this.tabContainer._handleTabTelemetryStart(aTab);
+
+ this._blurTab(aTab);
+ aTab.style.maxWidth = ""; // ensure that fade-out transition happens
+ aTab.removeAttribute("fadein");
+
+ setTimeout(function (tab, tabbrowser) {
+ if (tab.parentNode &&
+ window.getComputedStyle(tab).maxWidth == "0.1px") {
+ NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
+ tabbrowser._endRemoveTab(tab);
+ }
+ }, 3000, aTab, this);
+ ]]>
+ </body>
+ </method>
+
+ <!-- Tab close requests are ignored if the window is closing anyway,
+ e.g. when holding Ctrl+W. -->
+ <field name="_windowIsClosing">
+ false
+ </field>
+
+ <method name="_beginRemoveTab">
+ <parameter name="aTab"/>
+ <parameter name="aAdoptedByTab"/>
+ <parameter name="aCloseWindowWithLastTab"/>
+ <parameter name="aCloseWindowFastpath"/>
+ <parameter name="aSkipPermitUnload"/>
+ <body>
+ <![CDATA[
+ if (aTab.closing ||
+ this._windowIsClosing)
+ return false;
+
+ var browser = this.getBrowserForTab(aTab);
+
+ if (!aTab._pendingPermitUnload && !aAdoptedByTab && !aSkipPermitUnload) {
+ // We need to block while calling permitUnload() because it
+ // processes the event queue and may lead to another removeTab()
+ // call before permitUnload() returns.
+ aTab._pendingPermitUnload = true;
+ let {permitUnload, timedOut} = browser.permitUnload();
+ delete aTab._pendingPermitUnload;
+ // If we were closed during onbeforeunload, we return false now
+ // so we don't (try to) close the same tab again. Of course, we
+ // also stop if the unload was cancelled by the user:
+ if (aTab.closing || (!timedOut && !permitUnload)) {
+ // NB: deliberately keep the _closedDuringPermitUnload set to
+ // true so we keep exiting early in case of multiple calls.
+ return false;
+ }
+ }
+
+
+ var closeWindow = false;
+ var newTab = false;
+ if (this.tabs.length - this._removingTabs.length == 1) {
+ closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
+ !window.toolbar.visible ||
+ Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
+
+ if (closeWindow) {
+ // We've already called beforeunload on all the relevant tabs if we get here,
+ // so avoid calling it again:
+ window.skipNextCanClose = true;
+ }
+
+ // Closing the tab and replacing it with a blank one is notably slower
+ // than closing the window right away. If the caller opts in, take
+ // the fast path.
+ if (closeWindow &&
+ aCloseWindowFastpath &&
+ this._removingTabs.length == 0) {
+ // This call actually closes the window, unless the user
+ // cancels the operation. We are finished here in both cases.
+ this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
+ return null;
+ }
+
+ newTab = true;
+ }
+
+ aTab.closing = true;
+ this._removingTabs.push(aTab);
+ this._visibleTabs = null; // invalidate cache
+
+ // Invalidate hovered tab state tracking for this closing tab.
+ if (this.tabContainer._hoveredTab == aTab)
+ aTab._mouseleave();
+
+ if (newTab)
+ this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
+ else
+ this.tabContainer.updateVisibility();
+
+ // We're committed to closing the tab now.
+ // Dispatch a notification.
+ // We dispatch it before any teardown so that event listeners can
+ // inspect the tab that's about to close.
+ var evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: aAdoptedByTab } });
+ aTab.dispatchEvent(evt);
+
+ if (!aAdoptedByTab && !gMultiProcessBrowser) {
+ // Prevent this tab from showing further dialogs, since we're closing it
+ var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+ windowUtils.disableDialogs();
+ }
+
+ // Remove the tab's filter and progress listener.
+ const filter = this._tabFilters.get(aTab);
+
+ browser.webProgress.removeProgressListener(filter);
+
+ const listener = this._tabListeners.get(aTab);
+ filter.removeProgressListener(listener);
+ listener.destroy();
+
+ if (browser.registeredOpenURI && !aAdoptedByTab) {
+ this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI,
+ browser.getAttribute("usercontextid") || 0);
+ delete browser.registeredOpenURI;
+ }
+
+ // We are no longer the primary content area.
+ browser.setAttribute("type", "content-targetable");
+
+ // Remove this tab as the owner of any other tabs, since it's going away.
+ for (let tab of this.tabs) {
+ if ("owner" in tab && tab.owner == aTab)
+ // |tab| is a child of the tab we're removing, make it an orphan
+ tab.owner = null;
+ }
+
+ aTab._endRemoveArgs = [closeWindow, newTab];
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_endRemoveTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab || !aTab._endRemoveArgs)
+ return;
+
+ var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
+ aTab._endRemoveArgs = null;
+
+ if (this._windowIsClosing) {
+ aCloseWindow = false;
+ aNewTab = false;
+ }
+
+ this._lastRelatedTab = null;
+
+ // update the UI early for responsiveness
+ aTab.collapsed = true;
+ this.tabContainer._fillTrailingGap();
+ this._blurTab(aTab);
+
+ this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
+
+ if (aCloseWindow) {
+ this._windowIsClosing = true;
+ while (this._removingTabs.length)
+ this._endRemoveTab(this._removingTabs[0]);
+ } else if (!this._windowIsClosing) {
+ if (aNewTab)
+ focusAndSelectUrlBar();
+
+ // workaround for bug 345399
+ this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
+ }
+
+ // We're going to remove the tab and the browser now.
+ this._tabFilters.delete(aTab);
+ this._tabListeners.delete(aTab);
+
+ var browser = this.getBrowserForTab(aTab);
+ this._outerWindowIDBrowserMap.delete(browser.outerWindowID);
+
+ // Because of the way XBL works (fields just set JS
+ // properties on the element) and the code we have in place
+ // to preserve the JS objects for any elements that have
+ // JS properties set on them, the browser element won't be
+ // destroyed until the document goes away. So we force a
+ // cleanup ourselves.
+ // This has to happen before we remove the child so that the
+ // XBL implementation of nsIObserver still works.
+ browser.destroy();
+
+ var wasPinned = aTab.pinned;
+
+ // Remove the tab ...
+ this.tabContainer.removeChild(aTab);
+
+ // ... and fix up the _tPos properties immediately.
+ for (let i = aTab._tPos; i < this.tabs.length; i++)
+ this.tabs[i]._tPos = i;
+
+ if (!this._windowIsClosing) {
+ if (wasPinned)
+ this.tabContainer._positionPinnedTabs();
+
+ // update tab close buttons state
+ this.tabContainer.adjustTabstrip();
+
+ setTimeout(function(tabs) {
+ tabs._lastTabClosedByMouse = false;
+ }, 0, this.tabContainer);
+ }
+
+ // update tab positional properties and attributes
+ this.selectedTab._selected = true;
+ this.tabContainer._setPositionalAttributes();
+
+ // Removing the panel requires fixing up selectedPanel immediately
+ // (see below), which would be hindered by the potentially expensive
+ // browser removal. So we remove the browser and the panel in two
+ // steps.
+
+ var panel = this.getNotificationBox(browser);
+
+ // In the multi-process case, it's possible an asynchronous tab switch
+ // is still underway. If so, then it's possible that the last visible
+ // browser is the one we're in the process of removing. There's the
+ // risk of displaying preloaded browsers that are at the end of the
+ // deck if we remove the browser before the switch is complete, so
+ // we alert the switcher in order to show a spinner instead.
+ if (this._switcher) {
+ this._switcher.onTabRemoved(aTab);
+ }
+
+ // This will unload the document. An unload handler could remove
+ // dependant tabs, so it's important that the tabbrowser is now in
+ // a consistent state (tab removed, tab positions updated, etc.).
+ browser.parentNode.removeChild(browser);
+
+ // Release the browser in case something is erroneously holding a
+ // reference to the tab after its removal.
+ this._tabForBrowser.delete(aTab.linkedBrowser);
+ aTab.linkedBrowser = null;
+
+ // As the browser is removed, the removal of a dependent document can
+ // cause the whole window to close. So at this point, it's possible
+ // that the binding is destructed.
+ if (this.mTabBox) {
+ this.mPanelContainer.removeChild(panel);
+ }
+
+ if (aCloseWindow)
+ this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_blurTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab.selected)
+ return;
+
+ if (aTab.owner &&
+ !aTab.owner.hidden &&
+ !aTab.owner.closing &&
+ Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
+ this.selectedTab = aTab.owner;
+ return;
+ }
+
+ // Switch to a visible tab unless there aren't any others remaining
+ let remainingTabs = this.visibleTabs;
+ let numTabs = remainingTabs.length;
+ if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
+ remainingTabs = Array.filter(this.tabs, function(tab) {
+ return !tab.closing;
+ }, this);
+ }
+
+ // Try to find a remaining tab that comes after the given tab
+ var tab = aTab;
+ do {
+ tab = tab.nextSibling;
+ } while (tab && remainingTabs.indexOf(tab) == -1);
+
+ if (!tab) {
+ tab = aTab;
+
+ do {
+ tab = tab.previousSibling;
+ } while (tab && remainingTabs.indexOf(tab) == -1);
+ }
+
+ this.selectedTab = tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="swapBrowsersAndCloseOther">
+ <parameter name="aOurTab"/>
+ <parameter name="aOtherTab"/>
+ <body>
+ <![CDATA[
+ // Do not allow transfering a private tab to a non-private window
+ // and vice versa.
+ if (PrivateBrowsingUtils.isWindowPrivate(window) !=
+ PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
+ return;
+
+ let ourBrowser = this.getBrowserForTab(aOurTab);
+ let otherBrowser = aOtherTab.linkedBrowser;
+
+ // Can't swap between chrome and content processes.
+ if (ourBrowser.isRemoteBrowser != otherBrowser.isRemoteBrowser)
+ return;
+
+ // Keep the userContextId if set on other browser
+ if (otherBrowser.hasAttribute("usercontextid")) {
+ ourBrowser.setAttribute("usercontextid", otherBrowser.getAttribute("usercontextid"));
+ }
+
+ // That's gBrowser for the other window, not the tab's browser!
+ var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
+ var isPending = aOtherTab.hasAttribute("pending");
+
+ let otherTabListener = remoteBrowser._tabListeners.get(aOtherTab);
+ let stateFlags = otherTabListener.mStateFlags;
+
+ // Expedite the removal of the icon if it was already scheduled.
+ if (aOtherTab._soundPlayingAttrRemovalTimer) {
+ clearTimeout(aOtherTab._soundPlayingAttrRemovalTimer);
+ aOtherTab._soundPlayingAttrRemovalTimer = 0;
+ aOtherTab.removeAttribute("soundplaying");
+ remoteBrowser._tabAttrModified(aOtherTab, ["soundplaying"]);
+ }
+
+ // First, start teardown of the other browser. Make sure to not
+ // fire the beforeunload event in the process. Close the other
+ // window if this was its last tab.
+ if (!remoteBrowser._beginRemoveTab(aOtherTab, aOurTab, true))
+ return;
+
+ let modifiedAttrs = [];
+ if (aOtherTab.hasAttribute("muted")) {
+ aOurTab.setAttribute("muted", "true");
+ aOurTab.muteReason = aOtherTab.muteReason;
+ ourBrowser.mute();
+ modifiedAttrs.push("muted");
+ }
+ if (aOtherTab.hasAttribute("soundplaying")) {
+ aOurTab.setAttribute("soundplaying", "true");
+ modifiedAttrs.push("soundplaying");
+ }
+ if (aOtherTab.hasAttribute("usercontextid")) {
+ aOurTab.setUserContextId(aOtherTab.getAttribute("usercontextid"));
+ modifiedAttrs.push("usercontextid");
+ }
+ if (aOtherTab.hasAttribute("sharing")) {
+ aOurTab.setAttribute("sharing", aOtherTab.getAttribute("sharing"));
+ modifiedAttrs.push("sharing");
+ aOurTab._sharingState = aOtherTab._sharingState;
+ webrtcUI.swapBrowserForNotification(otherBrowser, ourBrowser);
+ }
+
+ // If the other tab is pending (i.e. has not been restored, yet)
+ // then do not switch docShells but retrieve the other tab's state
+ // and apply it to our tab.
+ if (isPending) {
+ SessionStore.setTabState(aOurTab, SessionStore.getTabState(aOtherTab));
+
+ // Make sure to unregister any open URIs.
+ this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
+ } else {
+ // Workarounds for bug 458697
+ // Icon might have been set on DOMLinkAdded, don't override that.
+ if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
+ this.setIcon(aOurTab, otherBrowser.mIconURL, otherBrowser.contentPrincipal);
+ var isBusy = aOtherTab.hasAttribute("busy");
+ if (isBusy) {
+ aOurTab.setAttribute("busy", "true");
+ modifiedAttrs.push("busy");
+ if (aOurTab.selected)
+ this.mIsBusy = true;
+ }
+
+ this._swapBrowserDocShells(aOurTab, otherBrowser, stateFlags);
+ }
+
+ // Handle findbar data (if any)
+ let otherFindBar = aOtherTab._findBar;
+ if (otherFindBar &&
+ otherFindBar.findMode == otherFindBar.FIND_NORMAL) {
+ let ourFindBar = this.getFindBar(aOurTab);
+ ourFindBar._findField.value = otherFindBar._findField.value;
+ if (!otherFindBar.hidden)
+ ourFindBar.onFindCommand();
+ }
+
+ // Finish tearing down the tab that's going away.
+ remoteBrowser._endRemoveTab(aOtherTab);
+
+ if (isBusy)
+ this.setTabTitleLoading(aOurTab);
+ else
+ this.setTabTitle(aOurTab);
+
+ // If the tab was already selected (this happpens in the scenario
+ // of replaceTabWithWindow), notify onLocationChange, etc.
+ if (aOurTab.selected)
+ this.updateCurrentBrowser(true);
+
+ if (modifiedAttrs.length) {
+ this._tabAttrModified(aOurTab, modifiedAttrs);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_swapBrowserDocShells">
+ <parameter name="aOurTab"/>
+ <parameter name="aOtherBrowser"/>
+ <parameter name="aStateFlags"/>
+ <body>
+ <![CDATA[
+ // Unhook our progress listener
+ const filter = this._tabFilters.get(aOurTab);
+ let tabListener = this._tabListeners.get(aOurTab);
+ let ourBrowser = this.getBrowserForTab(aOurTab);
+ ourBrowser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(tabListener);
+
+ // Make sure to unregister any open URIs.
+ this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
+
+ // Unmap old outerWindowIDs.
+ this._outerWindowIDBrowserMap.delete(ourBrowser.outerWindowID);
+ let remoteBrowser = aOtherBrowser.ownerDocument.defaultView.gBrowser;
+ if (remoteBrowser) {
+ remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
+ }
+
+ // If switcher is active, it will intercept swap events and
+ // react as needed.
+ if (!this._switcher) {
+ aOtherBrowser.docShellIsActive = this.shouldActivateDocShell(ourBrowser);
+ }
+
+ // Swap the docshells
+ ourBrowser.swapDocShells(aOtherBrowser);
+
+ if (ourBrowser.isRemoteBrowser) {
+ // Switch outerWindowIDs for remote browsers.
+ let ourOuterWindowID = ourBrowser._outerWindowID;
+ ourBrowser._outerWindowID = aOtherBrowser._outerWindowID;
+ aOtherBrowser._outerWindowID = ourOuterWindowID;
+ }
+
+ // Register new outerWindowIDs.
+ this._outerWindowIDBrowserMap.set(ourBrowser.outerWindowID, ourBrowser);
+ if (remoteBrowser) {
+ remoteBrowser._outerWindowIDBrowserMap.set(aOtherBrowser.outerWindowID, aOtherBrowser);
+ }
+
+ // Swap permanentKey properties.
+ let ourPermanentKey = ourBrowser.permanentKey;
+ ourBrowser.permanentKey = aOtherBrowser.permanentKey;
+ aOtherBrowser.permanentKey = ourPermanentKey;
+ aOurTab.permanentKey = ourBrowser.permanentKey;
+ if (remoteBrowser) {
+ let otherTab = remoteBrowser.getTabForBrowser(aOtherBrowser);
+ if (otherTab) {
+ otherTab.permanentKey = aOtherBrowser.permanentKey;
+ }
+ }
+
+ // Restore the progress listener
+ tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false, false,
+ aStateFlags);
+ this._tabListeners.set(aOurTab, tabListener);
+
+ const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
+ filter.addProgressListener(tabListener, notifyAll);
+ ourBrowser.webProgress.addProgressListener(filter, notifyAll);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_swapRegisteredOpenURIs">
+ <parameter name="aOurBrowser"/>
+ <parameter name="aOtherBrowser"/>
+ <body>
+ <![CDATA[
+ // If the current URI is registered as open remove it from the list.
+ if (aOurBrowser.registeredOpenURI) {
+ this._unifiedComplete.unregisterOpenPage(aOurBrowser.registeredOpenURI,
+ aOurBrowser.getAttribute("usercontextid") || 0);
+ delete aOurBrowser.registeredOpenURI;
+ }
+
+ // If the other/new URI is registered as open then copy it over.
+ if (aOtherBrowser.registeredOpenURI) {
+ aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
+ delete aOtherBrowser.registeredOpenURI;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadAllTabs">
+ <body>
+ <![CDATA[
+ let tabs = this.visibleTabs;
+ let l = tabs.length;
+ for (var i = 0; i < l; i++) {
+ try {
+ this.getBrowserForTab(tabs[i]).reload();
+ } catch (e) {
+ // ignore failure to reload so others will be reloaded
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ this.getBrowserForTab(aTab).reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="addProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ if (arguments.length != 1) {
+ Components.utils.reportError("gBrowser.addProgressListener was " +
+ "called with a second argument, " +
+ "which is not supported. See bug " +
+ "608628. Call stack: " + new Error().stack);
+ }
+
+ this.mProgressListeners.push(aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ this.mProgressListeners =
+ this.mProgressListeners.filter(l => l != aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ this.mTabsProgressListeners.push(aListener);
+ </body>
+ </method>
+
+ <method name="removeTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ this.mTabsProgressListeners =
+ this.mTabsProgressListeners.filter(l => l != aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return aTab.linkedBrowser;
+ ]]>
+ </body>
+ </method>
+
+ <method name="showOnlyTheseTabs">
+ <parameter name="aTabs"/>
+ <body>
+ <![CDATA[
+ for (let tab of this.tabs) {
+ if (aTabs.indexOf(tab) == -1)
+ this.hideTab(tab);
+ else
+ this.showTab(tab);
+ }
+
+ this.tabContainer._handleTabSelect(false);
+ ]]>
+ </body>
+ </method>
+
+ <method name="showTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.hidden) {
+ aTab.removeAttribute("hidden");
+ this._visibleTabs = null; // invalidate cache
+
+ this.tabContainer.adjustTabstrip();
+
+ this.tabContainer._setPositionalAttributes();
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabShow", true, false);
+ aTab.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="hideTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
+ !aTab.closing) {
+ aTab.setAttribute("hidden", "true");
+ this._visibleTabs = null; // invalidate cache
+
+ this.tabContainer.adjustTabstrip();
+
+ this.tabContainer._setPositionalAttributes();
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabHide", true, false);
+ aTab.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectTabAtIndex">
+ <parameter name="aIndex"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ let tabs = this.visibleTabs;
+
+ // count backwards for aIndex < 0
+ if (aIndex < 0) {
+ aIndex += tabs.length;
+ // clamp at index 0 if still negative.
+ if (aIndex < 0)
+ aIndex = 0;
+ } else if (aIndex >= tabs.length) {
+ // clamp at right-most tab if out of range.
+ aIndex = tabs.length - 1;
+ }
+
+ this.selectedTab = tabs[aIndex];
+
+ if (aEvent) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="selectedTab">
+ <getter>
+ return this.mCurrentTab;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (gNavToolbox.collapsed) {
+ return this.mTabBox.selectedTab;
+ }
+ // Update the tab
+ this.mTabBox.selectedTab = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedBrowser"
+ onget="return this.mCurrentBrowser;"
+ readonly="true"/>
+
+ <field name="browsers" readonly="true">
+ <![CDATA[
+ // This defines a proxy which allows us to access browsers by
+ // index without actually creating a full array of browsers.
+ new Proxy([], {
+ has: (target, name) => {
+ if (typeof name == "string" && Number.isInteger(parseInt(name))) {
+ return (name in this.tabs);
+ }
+ return false;
+ },
+ get: (target, name) => {
+ if (name == "length") {
+ return this.tabs.length;
+ }
+ if (typeof name == "string" && Number.isInteger(parseInt(name))) {
+ if (!(name in this.tabs)) {
+ return undefined;
+ }
+ return this.tabs[name].linkedBrowser;
+ }
+ return target[name];
+ }
+ });
+ ]]>
+ </field>
+
+ <!-- Moves a tab to a new browser window, unless it's already the only tab
+ in the current window, in which case this will do nothing. -->
+ <method name="replaceTabWithWindow">
+ <parameter name="aTab"/>
+ <parameter name="aOptions"/>
+ <body>
+ <![CDATA[
+ if (this.tabs.length == 1)
+ return null;
+
+ var options = "chrome,dialog=no,all";
+ for (var name in aOptions)
+ options += "," + name + "=" + aOptions[name];
+
+ // tell a new window to take the "dropped" tab
+ return window.openDialog(getBrowserURL(), "_blank", options, aTab);
+ ]]>
+ </body>
+ </method>
+
+ <!-- Opens a given tab to a non-remote window. -->
+ <method name="openNonRemoteWindow">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!this.AppConstants.E10S_TESTING_ONLY) {
+ throw "This method is intended only for e10s testing!";
+ }
+ let url = aTab.linkedBrowser.currentURI.spec;
+ return window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabTo">
+ <parameter name="aTab"/>
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ var oldPosition = aTab._tPos;
+ if (oldPosition == aIndex)
+ return;
+
+ // Don't allow mixing pinned and unpinned tabs.
+ if (aTab.pinned)
+ aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
+ else
+ aIndex = Math.max(aIndex, this._numPinnedTabs);
+ if (oldPosition == aIndex)
+ return;
+
+ this._lastRelatedTab = null;
+
+ let wasFocused = (document.activeElement == this.mCurrentTab);
+
+ aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
+
+ // invalidate cache
+ this._visibleTabs = null;
+
+ // use .item() instead of [] because dragging to the end of the strip goes out of
+ // bounds: .item() returns null (so it acts like appendChild), but [] throws
+ this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
+
+ for (let i = 0; i < this.tabs.length; i++) {
+ this.tabs[i]._tPos = i;
+ this.tabs[i]._selected = false;
+ }
+
+ // If we're in the midst of an async tab switch while calling
+ // moveTabTo, we can get into a case where _visuallySelected
+ // is set to true on two different tabs.
+ //
+ // What we want to do in moveTabTo is to remove logical selection
+ // from all tabs, and then re-add logical selection to mCurrentTab
+ // (and visual selection as well if we're not running with e10s, which
+ // setting _selected will do automatically).
+ //
+ // If we're running with e10s, then the visual selection will not
+ // be changed, which is fine, since if we weren't in the midst of a
+ // tab switch, the previously visually selected tab should still be
+ // correct, and if we are in the midst of a tab switch, then the async
+ // tab switcher will set the visually selected tab once the tab switch
+ // has completed.
+ this.mCurrentTab._selected = true;
+
+ if (wasFocused)
+ this.mCurrentTab.focus();
+
+ this.tabContainer._handleTabSelect(false);
+
+ if (aTab.pinned)
+ this.tabContainer._positionPinnedTabs();
+
+ this.tabContainer._setPositionalAttributes();
+
+ var evt = document.createEvent("UIEvents");
+ evt.initUIEvent("TabMove", true, false, window, oldPosition);
+ aTab.dispatchEvent(evt);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabForward">
+ <body>
+ <![CDATA[
+ let nextTab = this.mCurrentTab.nextSibling;
+ while (nextTab && nextTab.hidden)
+ nextTab = nextTab.nextSibling;
+
+ if (nextTab)
+ this.moveTabTo(this.mCurrentTab, nextTab._tPos);
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToStart();
+ ]]>
+ </body>
+ </method>
+
+ <!-- Adopts a tab from another browser window, and inserts it at aIndex -->
+ <method name="adoptTab">
+ <parameter name="aTab"/>
+ <parameter name="aIndex"/>
+ <parameter name="aSelectTab"/>
+ <body>
+ <![CDATA[
+ // Swap the dropped tab with a new one we create and then close
+ // it in the other window (making it seem to have moved between
+ // windows).
+ let params = { eventDetail: { adoptedTab: aTab } };
+ if (aTab.hasAttribute("usercontextid")) {
+ // new tab must have the same usercontextid as the old one
+ params.userContextId = aTab.getAttribute("usercontextid");
+ }
+ let newTab = this.addTab("about:blank", params);
+ let newBrowser = this.getBrowserForTab(newTab);
+ let newURL = aTab.linkedBrowser.currentURI.spec;
+
+ // If we're an e10s browser window, an exception will be thrown
+ // if we attempt to drag a non-remote browser in, so we need to
+ // ensure that the remoteness of the newly created browser is
+ // appropriate for the URL of the tab being dragged in.
+ this.updateBrowserRemotenessByURL(newBrowser, newURL);
+
+ // Stop the about:blank load.
+ newBrowser.stop();
+ // Make sure it has a docshell.
+ newBrowser.docShell;
+
+ let numPinned = this._numPinnedTabs;
+ if (aIndex < numPinned || (aTab.pinned && aIndex == numPinned)) {
+ this.pinTab(newTab);
+ }
+
+ this.moveTabTo(newTab, aIndex);
+
+ // We need to select the tab before calling swapBrowsersAndCloseOther
+ // so that window.content in chrome windows points to the right tab
+ // when pagehide/show events are fired. This is no longer necessary
+ // for any exiting browser code, but it may be necessary for add-on
+ // compatibility.
+ if (aSelectTab) {
+ this.selectedTab = newTab;
+ }
+
+ aTab.parentNode._finishAnimateTabMove();
+ this.swapBrowsersAndCloseOther(newTab, aTab);
+
+ if (aSelectTab) {
+ // Call updateCurrentBrowser to make sure the URL bar is up to date
+ // for our new tab after we've done swapBrowsersAndCloseOther.
+ this.updateCurrentBrowser(true);
+ }
+
+ return newTab;
+ ]]>
+ </body>
+ </method>
+
+
+ <method name="moveTabBackward">
+ <body>
+ <![CDATA[
+ let previousTab = this.mCurrentTab.previousSibling;
+ while (previousTab && previousTab.hidden)
+ previousTab = previousTab.previousSibling;
+
+ if (previousTab)
+ this.moveTabTo(this.mCurrentTab, previousTab._tPos);
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToEnd();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToStart">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos > 0)
+ this.moveTabTo(this.mCurrentTab, 0);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToEnd">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos < this.browsers.length - 1)
+ this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabOver">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var direction = window.getComputedStyle(this.parentNode, null).direction;
+ if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
+ (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
+ this.moveTabForward();
+ else
+ this.moveTabBackward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="duplicateTab">
+ <parameter name="aTab"/><!-- can be from a different window as well -->
+ <parameter name="aRestoreTabImmediately"/><!-- can defer loading of the tab contents -->
+ <body>
+ <![CDATA[
+ return SessionStore.duplicateTab(window, aTab, 0, aRestoreTabImmediately);
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ List of browsers whose docshells must be active in order for print preview
+ to work.
+ -->
+ <field name="_printPreviewBrowsers">
+ new Set()
+ </field>
+
+ <method name="activateBrowserForPrintPreview">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ this._printPreviewBrowsers.add(aBrowser);
+ if (this._switcher) {
+ this._switcher.activateBrowserForPrintPreview(aBrowser);
+ }
+ aBrowser.docShellIsActive = true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="deactivatePrintPreviewBrowsers">
+ <body>
+ <![CDATA[
+ let browsers = this._printPreviewBrowsers;
+ this._printPreviewBrowsers = new Set();
+ for (let browser of browsers) {
+ browser.docShellIsActive = this.shouldActivateDocShell(browser);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ Returns true if a given browser's docshell should be active.
+ -->
+ <method name="shouldActivateDocShell">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ if (this._switcher) {
+ return this._switcher.shouldActivateDocShell(aBrowser);
+ }
+ return (aBrowser == this.selectedBrowser &&
+ window.windowState != window.STATE_MINIMIZED) ||
+ this._printPreviewBrowsers.has(aBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ The tab switcher is responsible for asynchronously switching
+ tabs in e10s. It waits until the new tab is ready (i.e., the
+ layer tree is available) before switching to it. Then it
+ unloads the layer tree for the old tab.
+
+ The tab switcher is a state machine. For each tab, it
+ maintains state about whether the layer tree for the tab is
+ available, being loaded, being unloaded, or unavailable. It
+ also keeps track of the tab currently being displayed, the tab
+ it's trying to load, and the tab the user has asked to switch
+ to. The switcher object is created upon tab switch. It is
+ released when there are no pending tabs to load or unload.
+
+ The following general principles have guided the design:
+
+ 1. We only request one layer tree at a time. If the user
+ switches to a different tab while waiting, we don't request
+ the new layer tree until the old tab has loaded or timed out.
+
+ 2. If loading the layers for a tab times out, we show the
+ spinner and possibly request the layer tree for another tab if
+ the user has requested one.
+
+ 3. We discard layer trees on a delay. This way, if the user is
+ switching among the same tabs frequently, we don't continually
+ load the same tabs.
+
+ It's important that we always show either the spinner or a tab
+ whose layers are available. Otherwise the compositor will draw
+ an entirely black frame, which is very jarring. To ensure this
+ never happens when switching away from a tab, we assume the
+ old tab might still be drawn until a MozAfterPaint event
+ occurs. Because layout and compositing happen asynchronously,
+ we don't have any other way of knowing when the switch
+ actually takes place. Therefore, we don't unload the old tab
+ until the next MozAfterPaint event.
+ -->
+ <field name="_switcher">null</field>
+ <method name="_getSwitcher">
+ <body><![CDATA[
+ if (this._switcher) {
+ return this._switcher;
+ }
+
+ let switcher = {
+ // How long to wait for a tab's layers to load. After this
+ // time elapses, we're free to put up the spinner and start
+ // trying to load a different tab.
+ TAB_SWITCH_TIMEOUT: 400 /* ms */,
+
+ // When the user hasn't switched tabs for this long, we unload
+ // layers for all tabs that aren't in use.
+ UNLOAD_DELAY: 300 /* ms */,
+
+ // The next three tabs form the principal state variables.
+ // See the assertions in postActions for their invariants.
+
+ // Tab the user requested most recently.
+ requestedTab: this.selectedTab,
+
+ // Tab we're currently trying to load.
+ loadingTab: null,
+
+ // We show this tab in case the requestedTab hasn't loaded yet.
+ lastVisibleTab: this.selectedTab,
+
+ // Auxilliary state variables:
+
+ visibleTab: this.selectedTab, // Tab that's on screen.
+ spinnerTab: null, // Tab showing a spinner.
+ originalTab: this.selectedTab, // Tab that we started on.
+
+ tabbrowser: this, // Reference to gBrowser.
+ loadTimer: null, // TAB_SWITCH_TIMEOUT nsITimer instance.
+ unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
+
+ // Map from tabs to STATE_* (below).
+ tabState: new Map(),
+
+ // True if we're in the midst of switching tabs.
+ switchInProgress: false,
+
+ // Keep an exact list of content processes (tabParent) in which
+ // we're actively suppressing the display port. This gives a robust
+ // way to make sure we don't forget to un-suppress.
+ activeSuppressDisplayport: new Set(),
+
+ // Set of tabs that might be visible right now. We maintain
+ // this set because we can't be sure when a tab is actually
+ // drawn. A tab is added to this set when we ask to make it
+ // visible. All tabs but the most recently shown tab are
+ // removed from the set upon MozAfterPaint.
+ maybeVisibleTabs: new Set([this.selectedTab]),
+
+ STATE_UNLOADED: 0,
+ STATE_LOADING: 1,
+ STATE_LOADED: 2,
+ STATE_UNLOADING: 3,
+
+ // re-entrancy guard:
+ _processing: false,
+
+ // Wraps nsITimer. Must not use the vanilla setTimeout and
+ // clearTimeout, because they will be blocked by nsIPromptService
+ // dialogs.
+ setTimer: function(callback, timeout) {
+ let event = {
+ notify: callback
+ };
+
+ var timer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(event, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ clearTimer: function(timer) {
+ timer.cancel();
+ },
+
+ getTabState: function(tab) {
+ let state = this.tabState.get(tab);
+ if (state === undefined) {
+ return this.STATE_UNLOADED;
+ }
+ return state;
+ },
+
+ setTabStateNoAction(tab, state) {
+ if (state == this.STATE_UNLOADED) {
+ this.tabState.delete(tab);
+ } else {
+ this.tabState.set(tab, state);
+ }
+ },
+
+ setTabState: function(tab, state) {
+ this.setTabStateNoAction(tab, state);
+
+ let browser = tab.linkedBrowser;
+ let {tabParent} = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ if (state == this.STATE_LOADING) {
+ this.assert(!this.minimized);
+ browser.docShellIsActive = true;
+ if (!tabParent) {
+ this.onLayersReady(browser);
+ }
+ } else if (state == this.STATE_UNLOADING) {
+ browser.docShellIsActive = false;
+ if (!tabParent) {
+ this.onLayersCleared(browser);
+ }
+ }
+ },
+
+ get minimized() {
+ return window.windowState == window.STATE_MINIMIZED;
+ },
+
+ init: function() {
+ this.log("START");
+
+ // If we minimized the window before the switcher was activated,
+ // we might have set the preserveLayers flag for the current
+ // browser. Let's clear it.
+ this.tabbrowser.mCurrentBrowser.preserveLayers(false);
+
+ window.addEventListener("MozAfterPaint", this);
+ window.addEventListener("MozLayerTreeReady", this);
+ window.addEventListener("MozLayerTreeCleared", this);
+ window.addEventListener("TabRemotenessChange", this);
+ window.addEventListener("sizemodechange", this);
+ window.addEventListener("SwapDocShells", this, true);
+ window.addEventListener("EndSwapDocShells", this, true);
+ if (!this.minimized) {
+ this.setTabState(this.requestedTab, this.STATE_LOADED);
+ }
+ },
+
+ destroy: function() {
+ if (this.unloadTimer) {
+ this.clearTimer(this.unloadTimer);
+ this.unloadTimer = null;
+ }
+ if (this.loadTimer) {
+ this.clearTimer(this.loadTimer);
+ this.loadTimer = null;
+ }
+
+ window.removeEventListener("MozAfterPaint", this);
+ window.removeEventListener("MozLayerTreeReady", this);
+ window.removeEventListener("MozLayerTreeCleared", this);
+ window.removeEventListener("TabRemotenessChange", this);
+ window.removeEventListener("sizemodechange", this);
+ window.removeEventListener("SwapDocShells", this, true);
+ window.removeEventListener("EndSwapDocShells", this, true);
+
+ this.tabbrowser._switcher = null;
+
+ this.activeSuppressDisplayport.forEach(function(tabParent) {
+ tabParent.suppressDisplayport(false);
+ });
+ this.activeSuppressDisplayport.clear();
+ },
+
+ finish: function() {
+ this.log("FINISH");
+
+ this.assert(this.tabbrowser._switcher);
+ this.assert(this.tabbrowser._switcher === this);
+ this.assert(!this.spinnerTab);
+ this.assert(!this.loadTimer);
+ this.assert(!this.loadingTab);
+ this.assert(this.lastVisibleTab === this.requestedTab);
+ this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
+
+ this.destroy();
+
+ let toBrowser = this.requestedTab.linkedBrowser;
+ toBrowser.setAttribute("type", "content-primary");
+
+ this.tabbrowser._adjustFocusAfterTabSwitch(this.requestedTab);
+
+ let fromBrowser = this.originalTab.linkedBrowser;
+ // It's possible that the tab we're switching from closed
+ // before we were able to finalize, in which case, fromBrowser
+ // doesn't exist.
+ if (fromBrowser) {
+ fromBrowser.setAttribute("type", "content-targetable");
+ }
+
+ let event = new CustomEvent("TabSwitchDone", {
+ bubbles: true,
+ cancelable: true
+ });
+ this.tabbrowser.dispatchEvent(event);
+ },
+
+ // This function is called after all the main state changes to
+ // make sure we display the right tab.
+ updateDisplay: function() {
+ // Figure out which tab we actually want visible right now.
+ let showTab = null;
+ if (this.getTabState(this.requestedTab) != this.STATE_LOADED &&
+ this.lastVisibleTab && this.loadTimer) {
+ // If we can't show the requestedTab, and lastVisibleTab is
+ // available, show it.
+ showTab = this.lastVisibleTab;
+ } else {
+ // Show the requested tab. If it's not available, we'll show the spinner.
+ showTab = this.requestedTab;
+ }
+
+ // Show or hide the spinner as needed.
+ let needSpinner = this.getTabState(showTab) != this.STATE_LOADED && !this.minimized;
+ if (!needSpinner && this.spinnerTab) {
+ this.spinnerHidden();
+ this.tabbrowser.removeAttribute("pendingpaint");
+ this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
+ this.spinnerTab = null;
+ } else if (needSpinner && this.spinnerTab !== showTab) {
+ if (this.spinnerTab) {
+ this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
+ } else {
+ this.spinnerDisplayed();
+ }
+ this.spinnerTab = showTab;
+ this.tabbrowser.setAttribute("pendingpaint", "true");
+ this.spinnerTab.linkedBrowser.setAttribute("pendingpaint", "true");
+ }
+
+ // Switch to the tab we've decided to make visible.
+ if (this.visibleTab !== showTab) {
+ this.visibleTab = showTab;
+
+ this.maybeVisibleTabs.add(showTab);
+
+ let tabs = this.tabbrowser.mTabBox.tabs;
+ let tabPanel = this.tabbrowser.mPanelContainer;
+ let showPanel = tabs.getRelatedElement(showTab);
+ let index = Array.indexOf(tabPanel.childNodes, showPanel);
+ if (index != -1) {
+ this.log(`Switch to tab ${index} - ${this.tinfo(showTab)}`);
+ tabPanel.setAttribute("selectedIndex", index);
+ if (showTab === this.requestedTab) {
+ this.tabbrowser._adjustFocusAfterTabSwitch(showTab);
+ }
+ }
+
+ // This doesn't necessarily exist if we're a new window and haven't switched tabs yet
+ if (this.lastVisibleTab)
+ this.lastVisibleTab._visuallySelected = false;
+
+ this.visibleTab._visuallySelected = true;
+ }
+
+ this.lastVisibleTab = this.visibleTab;
+ },
+
+ assert: function(cond) {
+ if (!cond) {
+ dump("Assertion failure\n" + Error().stack);
+
+ // Don't break a user's browser if an assertion fails.
+ if (this.tabbrowser.AppConstants.DEBUG) {
+ throw new Error("Assertion failure");
+ }
+ }
+ },
+
+ // We've decided to try to load requestedTab.
+ loadRequestedTab: function() {
+ this.assert(!this.loadTimer);
+ this.assert(!this.minimized);
+
+ // loadingTab can be non-null here if we timed out loading the current tab.
+ // In that case we just overwrite it with a different tab; it's had its chance.
+ this.loadingTab = this.requestedTab;
+ this.log("Loading tab " + this.tinfo(this.loadingTab));
+
+ this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
+ this.setTabState(this.requestedTab, this.STATE_LOADING);
+ },
+
+ // This function runs before every event. It fixes up the state
+ // to account for closed tabs.
+ preActions: function() {
+ this.assert(this.tabbrowser._switcher);
+ this.assert(this.tabbrowser._switcher === this);
+
+ for (let [tab, ] of this.tabState) {
+ if (!tab.linkedBrowser) {
+ this.tabState.delete(tab);
+ }
+ }
+
+ if (this.lastVisibleTab && !this.lastVisibleTab.linkedBrowser) {
+ this.lastVisibleTab = null;
+ }
+ if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
+ this.spinnerHidden();
+ this.spinnerTab = null;
+ }
+ if (this.loadingTab && !this.loadingTab.linkedBrowser) {
+ this.loadingTab = null;
+ this.clearTimer(this.loadTimer);
+ this.loadTimer = null;
+ }
+ },
+
+ // This code runs after we've responded to an event or requested a new
+ // tab. It's expected that we've already updated all the principal
+ // state variables. This function takes care of updating any auxilliary
+ // state.
+ postActions: function() {
+ // Once we finish loading loadingTab, we null it out. So the state should
+ // always be LOADING.
+ this.assert(!this.loadingTab ||
+ this.getTabState(this.loadingTab) == this.STATE_LOADING);
+
+ // We guarantee that loadingTab is non-null iff loadTimer is non-null. So
+ // the timer is set only when we're loading something.
+ this.assert(!this.loadTimer || this.loadingTab);
+ this.assert(!this.loadingTab || this.loadTimer);
+
+ // If we're not loading anything, try loading the requested tab.
+ let requestedState = this.getTabState(this.requestedTab);
+ if (!this.loadTimer && !this.minimized &&
+ (requestedState == this.STATE_UNLOADED ||
+ requestedState == this.STATE_UNLOADING)) {
+ this.loadRequestedTab();
+ }
+
+ // See how many tabs still have work to do.
+ let numPending = 0;
+ for (let [tab, state] of this.tabState) {
+ // Skip print preview browsers since they shouldn't affect tab switching.
+ if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
+ continue;
+ }
+
+ if (state == this.STATE_LOADED && tab !== this.requestedTab) {
+ numPending++;
+ }
+ if (state == this.STATE_LOADING || state == this.STATE_UNLOADING) {
+ numPending++;
+ }
+ }
+
+ this.updateDisplay();
+
+ // It's possible for updateDisplay to trigger one of our own event
+ // handlers, which might cause finish() to already have been called.
+ // Check for that before calling finish() again.
+ if (!this.tabbrowser._switcher) {
+ return;
+ }
+
+ if (numPending == 0) {
+ this.finish();
+ }
+
+ this.logState("done");
+ },
+
+ // Fires when we're ready to unload unused tabs.
+ onUnloadTimeout: function() {
+ this.logState("onUnloadTimeout");
+ this.unloadTimer = null;
+ this.preActions();
+
+ let numPending = 0;
+
+ // Unload any tabs that can be unloaded.
+ for (let [tab, state] of this.tabState) {
+ if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
+ continue;
+ }
+
+ if (state == this.STATE_LOADED &&
+ !this.maybeVisibleTabs.has(tab) &&
+ tab !== this.lastVisibleTab &&
+ tab !== this.loadingTab &&
+ tab !== this.requestedTab)
+ {
+ this.setTabState(tab, this.STATE_UNLOADING);
+ }
+
+ if (state != this.STATE_UNLOADED && tab !== this.requestedTab) {
+ numPending++;
+ }
+ }
+
+ if (numPending) {
+ // Keep the timer going since there may be more tabs to unload.
+ this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+ }
+
+ this.postActions();
+ },
+
+ // Fires when an ongoing load has taken too long.
+ onLoadTimeout: function() {
+ this.logState("onLoadTimeout");
+ this.preActions();
+ this.loadTimer = null;
+ this.loadingTab = null;
+ this.postActions();
+ },
+
+ // Fires when the layers become available for a tab.
+ onLayersReady: function(browser) {
+ let tab = this.tabbrowser.getTabForBrowser(browser);
+ this.logState(`onLayersReady(${tab._tPos})`);
+
+ this.assert(this.getTabState(tab) == this.STATE_LOADING ||
+ this.getTabState(tab) == this.STATE_LOADED);
+ this.setTabState(tab, this.STATE_LOADED);
+
+ this.maybeFinishTabSwitch();
+
+ if (this.loadingTab === tab) {
+ this.clearTimer(this.loadTimer);
+ this.loadTimer = null;
+ this.loadingTab = null;
+ }
+ },
+
+ // Fires when we paint the screen. Any tab switches we initiated
+ // previously are done, so there's no need to keep the old layers
+ // around.
+ onPaint: function() {
+ this.maybeVisibleTabs.clear();
+ this.maybeFinishTabSwitch();
+ },
+
+ // Called when we're done clearing the layers for a tab.
+ onLayersCleared: function(browser) {
+ let tab = this.tabbrowser.getTabForBrowser(browser);
+ if (tab) {
+ this.logState(`onLayersCleared(${tab._tPos})`);
+ this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
+ this.getTabState(tab) == this.STATE_UNLOADED);
+ this.setTabState(tab, this.STATE_UNLOADED);
+ }
+ },
+
+ // Called when a tab switches from remote to non-remote. In this case
+ // a MozLayerTreeReady notification that we requested may never fire,
+ // so we need to simulate it.
+ onRemotenessChange: function(tab) {
+ this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`);
+ if (!tab.linkedBrowser.isRemoteBrowser) {
+ if (this.getTabState(tab) == this.STATE_LOADING) {
+ this.onLayersReady(tab.linkedBrowser);
+ } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
+ this.onLayersCleared(tab.linkedBrowser);
+ }
+ }
+ },
+
+ // Called when a tab has been removed, and the browser node is
+ // about to be removed from the DOM.
+ onTabRemoved: function(tab) {
+ if (this.lastVisibleTab == tab) {
+ // The browser that was being presented to the user is
+ // going to be removed during this tick of the event loop.
+ // This will cause us to show a tab spinner instead.
+ this.preActions();
+ this.lastVisibleTab = null;
+ this.postActions();
+ }
+ },
+
+ onSizeModeChange() {
+ if (this.minimized) {
+ for (let [tab, state] of this.tabState) {
+ // Skip print preview browsers since they shouldn't affect tab switching.
+ if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
+ continue;
+ }
+
+ if (state == this.STATE_LOADING || state == this.STATE_LOADED) {
+ this.setTabState(tab, this.STATE_UNLOADING);
+ }
+ }
+ if (this.loadTimer) {
+ this.clearTimer(this.loadTimer);
+ this.loadTimer = null;
+ }
+ this.loadingTab = null;
+ } else {
+ // Do nothing. We'll automatically start loading the requested tab in
+ // postActions.
+ }
+ },
+
+ onSwapDocShells(ourBrowser, otherBrowser) {
+ // This event fires before the swap. ourBrowser is from
+ // our window. We save the state of otherBrowser since ourBrowser
+ // needs to take on that state at the end of the swap.
+
+ let otherTabbrowser = otherBrowser.ownerDocument.defaultView.gBrowser;
+ let otherState;
+ if (otherTabbrowser && otherTabbrowser._switcher) {
+ let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
+ otherState = otherTabbrowser._switcher.getTabState(otherTab);
+ } else {
+ otherState = (otherBrowser.docShellIsActive
+ ? this.STATE_LOADED
+ : this.STATE_UNLOADED);
+ }
+
+ if (!this.swapMap) {
+ this.swapMap = new WeakMap();
+ }
+ this.swapMap.set(otherBrowser, otherState);
+ },
+
+ onEndSwapDocShells(ourBrowser, otherBrowser) {
+ // The swap has happened. We reset the loadingTab in
+ // case it has been swapped. We also set ourBrowser's state
+ // to whatever otherBrowser's state was before the swap.
+
+ if (this.loadTimer) {
+ // Clearing the load timer means that we will
+ // immediately display a spinner if ourBrowser isn't
+ // ready yet. Typically it will already be ready
+ // though. If it's not, we're probably in a new window,
+ // in which case we have no other tabs to display anyway.
+ this.clearTimer(this.loadTimer);
+ this.loadTimer = null;
+ }
+ this.loadingTab = null;
+
+ let otherState = this.swapMap.get(otherBrowser);
+ this.swapMap.delete(otherBrowser);
+
+ let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
+ if (ourTab) {
+ this.setTabStateNoAction(ourTab, otherState);
+ }
+ },
+
+ shouldActivateDocShell(browser) {
+ let tab = this.tabbrowser.getTabForBrowser(browser);
+ let state = this.getTabState(tab);
+ return state == this.STATE_LOADING || state == this.STATE_LOADED;
+ },
+
+ activateBrowserForPrintPreview(browser) {
+ let tab = this.tabbrowser.getTabForBrowser(browser);
+ this.setTabState(tab, this.STATE_LOADING);
+ },
+
+ // Called when the user asks to switch to a given tab.
+ requestTab: function(tab) {
+ if (tab === this.requestedTab) {
+ return;
+ }
+
+ this.logState("requestTab " + this.tinfo(tab));
+ this.startTabSwitch();
+
+ this.requestedTab = tab;
+
+ let browser = this.requestedTab.linkedBrowser;
+ let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
+ fl.tabParent.suppressDisplayport(true);
+ this.activeSuppressDisplayport.add(fl.tabParent);
+ }
+
+ this.preActions();
+
+ if (this.unloadTimer) {
+ this.clearTimer(this.unloadTimer);
+ }
+ this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+
+ this.postActions();
+ },
+
+ handleEvent: function(event, delayed = false) {
+ if (this._processing) {
+ this.setTimer(() => this.handleEvent(event, true), 0);
+ return;
+ }
+ if (delayed && this.tabbrowser._switcher != this) {
+ // if we delayed processing this event, we might be out of date, in which
+ // case we drop the delayed events
+ return;
+ }
+ this._processing = true;
+ this.preActions();
+
+ if (event.type == "MozLayerTreeReady") {
+ this.onLayersReady(event.originalTarget);
+ } if (event.type == "MozAfterPaint") {
+ this.onPaint();
+ } else if (event.type == "MozLayerTreeCleared") {
+ this.onLayersCleared(event.originalTarget);
+ } else if (event.type == "TabRemotenessChange") {
+ this.onRemotenessChange(event.target);
+ } else if (event.type == "sizemodechange") {
+ this.onSizeModeChange();
+ } else if (event.type == "SwapDocShells") {
+ this.onSwapDocShells(event.originalTarget, event.detail);
+ } else if (event.type == "EndSwapDocShells") {
+ this.onEndSwapDocShells(event.originalTarget, event.detail);
+ }
+
+ this.postActions();
+ this._processing = false;
+ },
+
+ /*
+ * Telemetry and Profiler related helpers for recording tab switch
+ * timing.
+ */
+
+ startTabSwitch: function () {
+ TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+ TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+ this.addMarker("AsyncTabSwitch:Start");
+ this.switchInProgress = true;
+ },
+
+ /**
+ * Something has occurred that might mean that we've completed
+ * the tab switch (layers are ready, paints are done, spinners
+ * are hidden). This checks to make sure all conditions are
+ * satisfied, and then records the tab switch as finished.
+ */
+ maybeFinishTabSwitch: function () {
+ if (this.switchInProgress && this.requestedTab &&
+ this.getTabState(this.requestedTab) == this.STATE_LOADED) {
+ // After this point the tab has switched from the content thread's point of view.
+ // The changes will be visible after the next refresh driver tick + composite.
+ let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+ if (time != -1) {
+ TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+ this.log("DEBUG: tab switch time = " + time);
+ this.addMarker("AsyncTabSwitch:Finish");
+ }
+ this.switchInProgress = false;
+ }
+ },
+
+ spinnerDisplayed: function () {
+ this.assert(!this.spinnerTab);
+ TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+ // We have a second, similar probe for capturing recordings of
+ // when the spinner is displayed for very long periods.
+ TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
+ this.addMarker("AsyncTabSwitch:SpinnerShown");
+ },
+
+ spinnerHidden: function () {
+ this.assert(this.spinnerTab);
+ this.log("DEBUG: spinner time = " +
+ TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
+ TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+ TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
+ this.addMarker("AsyncTabSwitch:SpinnerHidden");
+ // we do not get a onPaint after displaying the spinner
+ this.maybeFinishTabSwitch();
+ },
+
+ addMarker: function(marker) {
+ if (Services.profiler) {
+ Services.profiler.AddMarker(marker);
+ }
+ },
+
+ /*
+ * Debug related logging for switcher.
+ */
+
+ _useDumpForLogging: false,
+ _logInit: false,
+
+ logging: function () {
+ if (this._useDumpForLogging)
+ return true;
+ if (this._logInit)
+ return this._shouldLog;
+ let result = false;
+ try {
+ result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
+ } catch (ex) {
+ }
+ this._shouldLog = result;
+ this._logInit = true;
+ return this._shouldLog;
+ },
+
+ tinfo: function(tab) {
+ if (tab) {
+ return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
+ }
+ return "null";
+ },
+
+ log: function(s) {
+ if (!this.logging())
+ return;
+ if (this._useDumpForLogging) {
+ dump(s + "\n");
+ } else {
+ Services.console.logStringMessage(s);
+ }
+ },
+
+ logState: function(prefix) {
+ if (!this.logging())
+ return;
+
+ let accum = prefix + " ";
+ for (let i = 0; i < this.tabbrowser.tabs.length; i++) {
+ let tab = this.tabbrowser.tabs[i];
+ let state = this.getTabState(tab);
+
+ accum += i + ":";
+ if (tab === this.lastVisibleTab) accum += "V";
+ if (tab === this.loadingTab) accum += "L";
+ if (tab === this.requestedTab) accum += "R";
+ if (state == this.STATE_LOADED) accum += "(+)";
+ if (state == this.STATE_LOADING) accum += "(+?)";
+ if (state == this.STATE_UNLOADED) accum += "(-)";
+ if (state == this.STATE_UNLOADING) accum += "(-?)";
+ accum += " ";
+ }
+ if (this._useDumpForLogging) {
+ dump(accum + "\n");
+ } else {
+ Services.console.logStringMessage(accum);
+ }
+ },
+ };
+ this._switcher = switcher;
+ switcher.init();
+ return switcher;
+ ]]></body>
+ </method>
+
+ <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
+ MAKE SURE TO ADD IT HERE AS WELL. -->
+ <property name="canGoBack"
+ onget="return this.mCurrentBrowser.canGoBack;"
+ readonly="true"/>
+
+ <property name="canGoForward"
+ onget="return this.mCurrentBrowser.canGoForward;"
+ readonly="true"/>
+
+ <method name="goBack">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goBack();
+ ]]>
+ </body>
+ </method>
+
+ <method name="goForward">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goForward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reload">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadWithFlags">
+ <parameter name="aFlags"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reloadWithFlags(aFlags);
+ ]]>
+ </body>
+ </method>
+
+ <method name="stop">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.stop();
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURI">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURIWithFlags">
+ <parameter name="aURI"/>
+ <parameter name="aFlags"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <body>
+ <![CDATA[
+ // Note - the callee understands both:
+ // (a) loadURIWithFlags(aURI, aFlags, ...)
+ // (b) loadURIWithFlags(aURI, { flags: aFlags, ... })
+ // Forwarding it as (a) here actually supports both (a) and (b),
+ // so you can call us either way too.
+ return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
+ ]]>
+ </body>
+ </method>
+
+ <method name="goHome">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goHome();
+ ]]>
+ </body>
+ </method>
+
+ <property name="homePage">
+ <getter>
+ <![CDATA[
+ return this.mCurrentBrowser.homePage;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.mCurrentBrowser.homePage = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="gotoIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.gotoIndex(aIndex);
+ ]]>
+ </body>
+ </method>
+
+ <property name="currentURI"
+ onget="return this.mCurrentBrowser.currentURI;"
+ readonly="true"/>
+
+ <property name="finder"
+ onget="return this.mCurrentBrowser.finder"
+ readonly="true"/>
+
+ <property name="docShell"
+ onget="return this.mCurrentBrowser.docShell"
+ readonly="true"/>
+
+ <property name="webNavigation"
+ onget="return this.mCurrentBrowser.webNavigation"
+ readonly="true"/>
+
+ <property name="webBrowserFind"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webBrowserFind"/>
+
+ <property name="webProgress"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webProgress"/>
+
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this.mCurrentBrowser.contentWindow"/>
+
+ <property name="contentWindowAsCPOW"
+ readonly="true"
+ onget="return this.mCurrentBrowser.contentWindowAsCPOW"/>
+
+ <property name="sessionHistory"
+ onget="return this.mCurrentBrowser.sessionHistory;"
+ readonly="true"/>
+
+ <property name="markupDocumentViewer"
+ onget="return this.mCurrentBrowser.markupDocumentViewer;"
+ readonly="true"/>
+
+ <property name="contentViewerEdit"
+ onget="return this.mCurrentBrowser.contentViewerEdit;"
+ readonly="true"/>
+
+ <property name="contentViewerFile"
+ onget="return this.mCurrentBrowser.contentViewerFile;"
+ readonly="true"/>
+
+ <property name="contentDocument"
+ onget="return this.mCurrentBrowser.contentDocument;"
+ readonly="true"/>
+
+ <property name="contentDocumentAsCPOW"
+ onget="return this.mCurrentBrowser.contentDocumentAsCPOW;"
+ readonly="true"/>
+
+ <property name="contentTitle"
+ onget="return this.mCurrentBrowser.contentTitle;"
+ readonly="true"/>
+
+ <property name="contentPrincipal"
+ onget="return this.mCurrentBrowser.contentPrincipal;"
+ readonly="true"/>
+
+ <property name="securityUI"
+ onget="return this.mCurrentBrowser.securityUI;"
+ readonly="true"/>
+
+ <property name="fullZoom"
+ onget="return this.mCurrentBrowser.fullZoom;"
+ onset="this.mCurrentBrowser.fullZoom = val;"/>
+
+ <property name="textZoom"
+ onget="return this.mCurrentBrowser.textZoom;"
+ onset="this.mCurrentBrowser.textZoom = val;"/>
+
+ <property name="isSyntheticDocument"
+ onget="return this.mCurrentBrowser.isSyntheticDocument;"
+ readonly="true"/>
+
+ <method name="_handleKeyDownEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!aEvent.isTrusted) {
+ // Don't let untrusted events mess with tabs.
+ return;
+ }
+
+ if (aEvent.altKey)
+ return;
+
+ // Don't check if the event was already consumed because tab
+ // navigation should always work for better user experience.
+
+ if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
+ switch (aEvent.keyCode) {
+ case aEvent.DOM_VK_PAGE_UP:
+ this.moveTabBackward();
+ aEvent.preventDefault();
+ return;
+ case aEvent.DOM_VK_PAGE_DOWN:
+ this.moveTabForward();
+ aEvent.preventDefault();
+ return;
+ }
+ }
+
+ if (this.AppConstants.platform != "macosx") {
+ if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
+ aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
+ !this.mCurrentTab.pinned) {
+ this.removeCurrentTab({animate: true});
+ aEvent.preventDefault();
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_handleKeyPressEventMac">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!aEvent.isTrusted) {
+ // Don't let untrusted events mess with tabs.
+ return;
+ }
+
+ if (aEvent.altKey)
+ return;
+
+ if (this.AppConstants.platform == "macosx") {
+ if (!aEvent.metaKey)
+ return;
+
+ var offset = 1;
+ switch (aEvent.charCode) {
+ case '}'.charCodeAt(0):
+ offset = -1;
+ case '{'.charCodeAt(0):
+ if (window.getComputedStyle(this, null).direction == "ltr")
+ offset *= -1;
+ this.tabContainer.advanceSelectedTab(offset, true);
+ aEvent.preventDefault();
+ }
+ }
+ ]]></body>
+ </method>
+
+ <property name="userTypedValue"
+ onget="return this.mCurrentBrowser.userTypedValue;"
+ onset="return this.mCurrentBrowser.userTypedValue = val;"/>
+
+ <method name="createTooltip">
+ <parameter name="event"/>
+ <body><![CDATA[
+ event.stopPropagation();
+ var tab = document.tooltipNode;
+ if (tab.localName != "tab") {
+ event.preventDefault();
+ return;
+ }
+
+ let stringWithShortcut = (stringId, keyElemId) => {
+ let keyElem = document.getElementById(keyElemId);
+ let shortcut = ShortcutUtils.prettifyShortcut(keyElem);
+ return this.mStringBundle.getFormattedString(stringId, [shortcut]);
+ };
+
+ var label;
+ if (tab.mOverCloseButton) {
+ label = tab.selected ?
+ stringWithShortcut("tabs.closeSelectedTab.tooltip", "key_close") :
+ this.mStringBundle.getString("tabs.closeTab.tooltip");
+ } else if (tab._overPlayingIcon) {
+ let stringID;
+ if (tab.selected) {
+ stringID = tab.linkedBrowser.audioMuted ?
+ "tabs.unmuteAudio.tooltip" :
+ "tabs.muteAudio.tooltip";
+ label = stringWithShortcut(stringID, "key_toggleMute");
+ } else {
+ if (tab.linkedBrowser.audioBlocked) {
+ stringID = "tabs.unblockAudio.tooltip";
+ } else {
+ stringID = tab.linkedBrowser.audioMuted ?
+ "tabs.unmuteAudio.background.tooltip" :
+ "tabs.muteAudio.background.tooltip";
+ }
+
+ label = this.mStringBundle.getString(stringID);
+ }
+ } else {
+ label = tab.getAttribute("label") +
+ (this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : "");
+ }
+ event.target.setAttribute("label", label);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "keydown":
+ this._handleKeyDownEvent(aEvent);
+ break;
+ case "keypress":
+ this._handleKeyPressEventMac(aEvent);
+ break;
+ case "sizemodechange":
+ if (aEvent.target == window && !this._switcher) {
+ this.mCurrentBrowser.preserveLayers(window.windowState == window.STATE_MINIMIZED);
+ this.mCurrentBrowser.docShellIsActive = this.shouldActivateDocShell(this.mCurrentBrowser);
+ }
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ let data = aMessage.data;
+ let browser = aMessage.target;
+
+ switch (aMessage.name) {
+ case "DOMTitleChanged": {
+ let tab = this.getTabForBrowser(browser);
+ if (!tab || tab.hasAttribute("pending"))
+ return undefined;
+ let titleChanged = this.setTabTitle(tab);
+ if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
+ tab.setAttribute("titlechanged", "true");
+ break;
+ }
+ case "DOMWindowClose": {
+ if (this.tabs.length == 1) {
+ // We already did PermitUnload in the content process
+ // for this tab (the only one in the window). So we don't
+ // need to do it again for any tabs.
+ window.skipNextCanClose = true;
+ window.close();
+ return undefined;
+ }
+
+ let tab = this.getTabForBrowser(browser);
+ if (tab) {
+ // Skip running PermitUnload since it already happened in
+ // the content process.
+ this.removeTab(tab, {skipPermitUnload: true});
+ }
+ break;
+ }
+ case "contextmenu": {
+ let spellInfo = data.spellInfo;
+ if (spellInfo)
+ spellInfo.target = aMessage.target.messageManager;
+ let documentURIObject = makeURI(data.docLocation,
+ data.charSet,
+ makeURI(data.baseURI));
+ gContextMenuContentData = { isRemote: true,
+ event: aMessage.objects.event,
+ popupNode: aMessage.objects.popupNode,
+ browser: browser,
+ editFlags: data.editFlags,
+ spellInfo: spellInfo,
+ principal: data.principal,
+ customMenuItems: data.customMenuItems,
+ addonInfo: data.addonInfo,
+ documentURIObject: documentURIObject,
+ docLocation: data.docLocation,
+ charSet: data.charSet,
+ referrer: data.referrer,
+ referrerPolicy: data.referrerPolicy,
+ contentType: data.contentType,
+ contentDisposition: data.contentDisposition,
+ frameOuterWindowID: data.frameOuterWindowID,
+ selectionInfo: data.selectionInfo,
+ disableSetDesktopBackground: data.disableSetDesktopBg,
+ loginFillInfo: data.loginFillInfo,
+ parentAllowsMixedContent: data.parentAllowsMixedContent,
+ userContextId: data.userContextId,
+ };
+ let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
+ let event = gContextMenuContentData.event;
+ popup.openPopupAtScreen(event.screenX, event.screenY, true);
+ break;
+ }
+ case "DOMServiceWorkerFocusClient":
+ case "DOMWebNotificationClicked": {
+ let tab = this.getTabForBrowser(browser);
+ if (!tab)
+ return undefined;
+ this.selectedTab = tab;
+ window.focus();
+ break;
+ }
+ case "Browser:Init": {
+ let tab = this.getTabForBrowser(browser);
+ if (!tab)
+ return undefined;
+
+ this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser);
+ browser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
+ break;
+ }
+ case "Browser:WindowCreated": {
+ let tab = this.getTabForBrowser(browser);
+ if (tab && data.userContextId) {
+ ContextualIdentityService.telemetry(data.userContextId);
+ tab.setUserContextId(data.userContextId);
+ }
+
+ // We don't want to update the container icon and identifier if
+ // this is not the selected browser.
+ if (browser == gBrowser.selectedBrowser) {
+ updateUserContextUIIndicator();
+ }
+
+ break;
+ }
+ case "Findbar:Keypress": {
+ let tab = this.getTabForBrowser(browser);
+ // If the find bar for this tab is not yet alive, only initialize
+ // it if there's a possibility FindAsYouType will be used.
+ // There's no point in doing it for most random keypresses.
+ if (!this.isFindBarInitialized(tab) &&
+ data.shouldFastFind) {
+ let shouldFastFind = this._findAsYouType;
+ if (!shouldFastFind) {
+ // Please keep in sync with toolkit/content/widgets/findbar.xml
+ const FAYT_LINKS_KEY = "'";
+ const FAYT_TEXT_KEY = "/";
+ let charCode = data.fakeEvent.charCode;
+ let key = charCode ? String.fromCharCode(charCode) : null;
+ shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY;
+ }
+ if (shouldFastFind) {
+ // Make sure we return the result.
+ return this.getFindBar(tab).receiveMessage(aMessage);
+ }
+ }
+ break;
+ }
+ case "RefreshBlocker:Blocked": {
+ let event = new CustomEvent("RefreshBlocked", {
+ bubbles: true,
+ cancelable: false,
+ detail: data,
+ });
+
+ browser.dispatchEvent(event);
+
+ break;
+ }
+
+ }
+ return undefined;
+ ]]></body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ let browser;
+ switch (aTopic) {
+ case "live-resize-start":
+ browser = this.mCurrentTab.linkedBrowser;
+ let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ if (fl && fl.tabParent && !this.mActiveResizeDisplayportSuppression) {
+ fl.tabParent.suppressDisplayport(true);
+ this.mActiveResizeDisplayportSuppression = browser;
+ }
+ break;
+ case "live-resize-end":
+ browser = this.mActiveResizeDisplayportSuppression;
+ if (browser) {
+ let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ if (fl && fl.tabParent) {
+ fl.tabParent.suppressDisplayport(false);
+ this.mActiveResizeDisplayportSuppression = null;
+ }
+ }
+ break;
+ case "nsPref:changed":
+ // This is the only pref observed.
+ this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
+ this.mCurrentBrowser.permanentKey = {};
+
+ Services.obs.addObserver(this, "live-resize-start", false);
+ Services.obs.addObserver(this, "live-resize-end", false);
+
+ this.mCurrentTab = this.tabContainer.firstChild;
+ const nsIEventListenerService =
+ Components.interfaces.nsIEventListenerService;
+ let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
+ .getService(nsIEventListenerService);
+ els.addSystemEventListener(document, "keydown", this, false);
+ if (this.AppConstants.platform == "macosx") {
+ els.addSystemEventListener(document, "keypress", this, false);
+ }
+ window.addEventListener("sizemodechange", this, false);
+
+ var uniqueId = this._generateUniquePanelID();
+ this.mPanelContainer.childNodes[0].id = uniqueId;
+ this.mCurrentTab.linkedPanel = uniqueId;
+ this.mCurrentTab.permanentKey = this.mCurrentBrowser.permanentKey;
+ this.mCurrentTab._tPos = 0;
+ this.mCurrentTab._fullyOpen = true;
+ this.mCurrentTab.cachePosition = 0;
+ this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
+ this.mCurrentTab.hasBrowser = true;
+ this._tabForBrowser.set(this.mCurrentBrowser, this.mCurrentTab);
+
+ // set up the shared autoscroll popup
+ this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
+ this._autoScrollPopup.id = "autoscroller";
+ this.appendChild(this._autoScrollPopup);
+ this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+ this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
+ this.updateWindowResizers();
+
+ // Hook up the event listeners to the first browser
+ var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true, false);
+ const nsIWebProgress = Components.interfaces.nsIWebProgress;
+ const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(nsIWebProgress);
+ filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
+ this._tabListeners.set(this.mCurrentTab, tabListener);
+ this._tabFilters.set(this.mCurrentTab, filter);
+ this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
+
+ this.style.backgroundColor =
+ Services.prefs.getBoolPref("browser.display.use_system_colors") ?
+ "-moz-default-background-color" :
+ Services.prefs.getCharPref("browser.display.background_color");
+
+ let remote = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .useRemoteTabs;
+ if (remote) {
+ messageManager.addMessageListener("DOMTitleChanged", this);
+ messageManager.addMessageListener("DOMWindowClose", this);
+ messageManager.addMessageListener("contextmenu", this);
+ messageManager.addMessageListener("Browser:Init", this);
+
+ // If this window has remote tabs, switch to our tabpanels fork
+ // which does asynchronous tab switching.
+ this.mPanelContainer.classList.add("tabbrowser-tabpanels");
+ } else {
+ this._outerWindowIDBrowserMap.set(this.mCurrentBrowser.outerWindowID,
+ this.mCurrentBrowser);
+ }
+ messageManager.addMessageListener("DOMWebNotificationClicked", this);
+ messageManager.addMessageListener("DOMServiceWorkerFocusClient", this);
+ messageManager.addMessageListener("RefreshBlocker:Blocked", this);
+ messageManager.addMessageListener("Browser:WindowCreated", this);
+
+ // To correctly handle keypresses for potential FindAsYouType, while
+ // the tab's find bar is not yet initialized.
+ this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
+ Services.prefs.addObserver("accessibility.typeaheadfind", this, false);
+ messageManager.addMessageListener("Findbar:Keypress", this);
+ ]]>
+ </constructor>
+
+ <method name="_generateUniquePanelID">
+ <body><![CDATA[
+ if (!this._uniquePanelIDCounter) {
+ this._uniquePanelIDCounter = 0;
+ }
+
+ let outerID = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+
+ // We want panel IDs to be globally unique, that's why we include the
+ // window ID. We switched to a monotonic counter as Date.now() lead
+ // to random failures because of colliding IDs.
+ return "panel-" + outerID + "-" + (++this._uniquePanelIDCounter);
+ ]]></body>
+ </method>
+
+ <destructor>
+ <![CDATA[
+ Services.obs.removeObserver(this, "live-resize-start", false);
+ Services.obs.removeObserver(this, "live-resize-end", false);
+
+ for (let tab of this.tabs) {
+ let browser = tab.linkedBrowser;
+ if (browser.registeredOpenURI) {
+ this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI,
+ browser.getAttribute("usercontextid") || 0);
+ delete browser.registeredOpenURI;
+ }
+ let filter = this._tabFilters.get(tab);
+ let listener = this._tabListeners.get(tab);
+
+ browser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(listener);
+ listener.destroy();
+
+ this._tabFilters.delete(tab);
+ this._tabListeners.delete(tab);
+ }
+ const nsIEventListenerService =
+ Components.interfaces.nsIEventListenerService;
+ let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
+ .getService(nsIEventListenerService);
+ els.removeSystemEventListener(document, "keydown", this, false);
+ if (this.AppConstants.platform == "macosx") {
+ els.removeSystemEventListener(document, "keypress", this, false);
+ }
+ window.removeEventListener("sizemodechange", this, false);
+
+ if (gMultiProcessBrowser) {
+ messageManager.removeMessageListener("DOMTitleChanged", this);
+ messageManager.removeMessageListener("contextmenu", this);
+
+ if (this._switcher) {
+ this._switcher.destroy();
+ }
+ }
+
+ Services.prefs.removeObserver("accessibility.typeaheadfind", this);
+ ]]>
+ </destructor>
+
+ <!-- Deprecated stuff, implemented for backwards compatibility. -->
+ <method name="enterTabbedMode">
+ <body>
+ Services.console.logStringMessage("enterTabbedMode is an obsolete method and " +
+ "will be removed in a future release.");
+ </body>
+ </method>
+ <field name="mTabbedMode" readonly="true">true</field>
+ <method name="setStripVisibilityTo">
+ <parameter name="aShow"/>
+ <body>
+ this.tabContainer.visible = aShow;
+ </body>
+ </method>
+ <method name="getStripVisibility">
+ <body>
+ return this.tabContainer.visible;
+ </body>
+ </method>
+
+ <property name="mContextTab" readonly="true"
+ onget="return TabContextMenu.contextTab;"/>
+ <property name="mPrefs" readonly="true"
+ onget="return Services.prefs;"/>
+ <property name="mTabContainer" readonly="true"
+ onget="return this.tabContainer;"/>
+ <property name="mTabs" readonly="true"
+ onget="return this.tabs;"/>
+ <!--
+ - Compatibility hack: several extensions depend on this property to
+ - access the tab context menu or tab container, so keep that working for
+ - now. Ideally we can remove this once extensions are using
+ - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
+ -->
+ <property name="mStrip" readonly="true">
+ <getter>
+ <![CDATA[
+ return ({
+ self: this,
+ childNodes: [null, this.tabContextMenu, this.tabContainer],
+ firstChild: { nextSibling: this.tabContextMenu },
+ getElementsByAttribute: function (attr, attrValue) {
+ if (attr == "anonid" && attrValue == "tabContextMenu")
+ return [this.self.tabContextMenu];
+ return [];
+ },
+ // Also support adding event listeners (forward to the tab container)
+ addEventListener: function (a, b, c) { this.self.tabContainer.addEventListener(a, b, c); },
+ removeEventListener: function (a, b, c) { this.self.tabContainer.removeEventListener(a, b, c); }
+ });
+ ]]>
+ </getter>
+ </property>
+ <field name="_soundPlayingAttrRemovalTimer">0</field>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMWindowClose" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ if (this.tabs.length == 1) {
+ // We already did PermitUnload in nsGlobalWindow::Close
+ // for this tab. There are no other tabs we need to do
+ // PermitUnload for.
+ window.skipNextCanClose = true;
+ return;
+ }
+
+ var tab = this._getTabForContentWindow(event.target);
+ if (tab) {
+ // Skip running PermitUnload since it already happened.
+ this.removeTab(tab, {skipPermitUnload: true});
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ <handler event="DOMWillOpenModalDialog" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ let targetIsWindow = event.target instanceof Window;
+
+ // We're about to open a modal dialog, so figure out for which tab:
+ // If this is a same-process modal dialog, then we're given its DOM
+ // window as the event's target. For remote dialogs, we're given the
+ // browser, but that's in the originalTarget and not the target,
+ // because it's across the tabbrowser's XBL boundary.
+ let tabForEvent = targetIsWindow ?
+ this._getTabForContentWindow(event.target.top) :
+ this.getTabForBrowser(event.originalTarget);
+
+ // Don't need to act if the tab is already selected:
+ if (tabForEvent.selected)
+ return;
+
+ // If this is a tabprompt, we won't switch tabs, unless:
+ // - this is a beforeunload prompt
+ // - this behaviour has been disabled entirely using the pref
+ if (event.detail && event.detail.tabPrompt &&
+ !event.detail.inPermitUnload &&
+ Services.prefs.getBoolPref("browser.tabs.dontfocusfordialogs")) {
+ let docPrincipal = targetIsWindow ? event.target.document.nodePrincipal : null;
+ // At least one of these should/will be non-null:
+ let promptPrincipal = event.detail.promptPrincipal || docPrincipal ||
+ tabForEvent.linkedBrowser.contentPrincipal;
+ // For null principals, we bail immediately and don't show the checkbox:
+ if (!promptPrincipal || promptPrincipal.isNullPrincipal) {
+ tabForEvent.setAttribute("attention", "true");
+ return;
+ }
+
+ // For non-system/expanded principals, we bail and show the checkbox
+ if (promptPrincipal.URI &&
+ !Services.scriptSecurityManager.isSystemPrincipal(promptPrincipal)) {
+ let permission = Services.perms.testPermissionFromPrincipal(promptPrincipal,
+ "focus-tab-by-prompt");
+ if (permission != Services.perms.ALLOW_ACTION) {
+ // Tell the prompt box we want to show the user a checkbox:
+ let tabPrompt = this.getTabModalPromptBox(tabForEvent.linkedBrowser);
+ tabPrompt.onNextPromptShowAllowFocusCheckboxFor(promptPrincipal);
+ tabForEvent.setAttribute("attention", "true");
+ return;
+ }
+ }
+ // ... so system and expanded principals, as well as permitted "normal"
+ // URI-based principals, always get to steal focus for the tab when prompting.
+ }
+
+ // if prefs/permissions/origins so dictate, bring tab to the front:
+ this.selectedTab = tabForEvent;
+ ]]>
+ </handler>
+ <handler event="DOMTitleChanged">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ var contentWin = event.target.defaultView;
+ if (contentWin != contentWin.top)
+ return;
+
+ var tab = this._getTabForContentWindow(contentWin);
+ if (!tab || tab.hasAttribute("pending"))
+ return;
+
+ var titleChanged = this.setTabTitle(tab);
+ if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
+ tab.setAttribute("titlechanged", "true");
+ ]]>
+ </handler>
+ <handler event="oop-browser-crashed">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ let browser = event.originalTarget;
+ let icon = browser.mIconURL;
+ let tab = this.getTabForBrowser(browser);
+
+ if (this.selectedBrowser == browser) {
+ TabCrashHandler.onSelectedBrowserCrash(browser);
+ } else {
+ this.updateBrowserRemoteness(browser, false);
+ SessionStore.reviveCrashedTab(tab);
+ }
+
+ tab.removeAttribute("soundplaying");
+ this.setIcon(tab, icon, browser.contentPrincipal);
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackStarted">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ clearTimeout(tab._soundPlayingAttrRemovalTimer);
+ tab._soundPlayingAttrRemovalTimer = 0;
+
+ let modifiedAttrs = [];
+ if (tab.hasAttribute("soundplaying-scheduledremoval")) {
+ tab.removeAttribute("soundplaying-scheduledremoval");
+ modifiedAttrs.push("soundplaying-scheduledremoval");
+ }
+
+ if (!tab.hasAttribute("soundplaying")) {
+ tab.setAttribute("soundplaying", true);
+ modifiedAttrs.push("soundplaying");
+ }
+
+ this._tabAttrModified(tab, modifiedAttrs);
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackStopped">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ if (tab.hasAttribute("soundplaying")) {
+ let removalDelay = Services.prefs.getIntPref("browser.tabs.delayHidingAudioPlayingIconMS");
+
+ tab.style.setProperty("--soundplaying-removal-delay", `${removalDelay - 300}ms`);
+ tab.setAttribute("soundplaying-scheduledremoval", "true");
+ this._tabAttrModified(tab, ["soundplaying-scheduledremoval"]);
+
+ tab._soundPlayingAttrRemovalTimer = setTimeout(() => {
+ tab.removeAttribute("soundplaying-scheduledremoval");
+ tab.removeAttribute("soundplaying");
+ this._tabAttrModified(tab, ["soundplaying", "soundplaying-scheduledremoval"]);
+ }, removalDelay);
+ }
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackBlockStarted">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ if (!tab.hasAttribute("blocked")) {
+ tab.setAttribute("blocked", true);
+ this._tabAttrModified(tab, ["blocked"]);
+ }
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackBlockStopped">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ if (tab.hasAttribute("blocked")) {
+ tab.removeAttribute("blocked");
+ this._tabAttrModified(tab, ["blocked"]);
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabbox"
+ extends="chrome://global/content/bindings/tabbox.xml#tabbox">
+ <implementation>
+ <property name="tabs" readonly="true"
+ onget="return document.getBindingParent(this).tabContainer;"/>
+ </implementation>
+ </binding>
+
+ <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+ <implementation>
+ <!-- Override scrollbox.xml method, since our scrollbox's children are
+ inherited from the binding parent -->
+ <method name="_getScrollableElements">
+ <body><![CDATA[
+ return Array.filter(document.getBindingParent(this).childNodes,
+ this._canScrollToElement, this);
+ ]]></body>
+ </method>
+ <method name="_canScrollToElement">
+ <parameter name="tab"/>
+ <body><![CDATA[
+ return !tab.pinned && !tab.hidden;
+ ]]></body>
+ </method>
+ <field name="_tabMarginLeft">null</field>
+ <field name="_tabMarginRight">null</field>
+ <method name="_calcTabMargins">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (this._tabMarginLeft === null || this._tabMarginRight === null) {
+ let tabMiddle = document.getAnonymousElementByAttribute(aTab, "class", "tab-background-middle");
+ let tabMiddleStyle = window.getComputedStyle(tabMiddle, null);
+ this._tabMarginLeft = parseFloat(tabMiddleStyle.marginLeft);
+ this._tabMarginRight = parseFloat(tabMiddleStyle.marginRight);
+ }
+ ]]></body>
+ </method>
+ <method name="_adjustElementStartAndEnd">
+ <parameter name="aTab"/>
+ <parameter name="tabStart"/>
+ <parameter name="tabEnd"/>
+ <body><![CDATA[
+ this._calcTabMargins(aTab);
+ if (this._tabMarginLeft < 0) {
+ tabStart = tabStart + this._tabMarginLeft;
+ }
+ if (this._tabMarginRight < 0) {
+ tabEnd = tabEnd - this._tabMarginRight;
+ }
+ return [tabStart, tabEnd];
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="underflow" phase="capturing"><![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.removeAttribute("overflow");
+
+ if (tabs._lastTabClosedByMouse)
+ tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
+
+ for (let tab of Array.from(tabs.tabbrowser._removingTabs))
+ tabs.tabbrowser.removeTab(tab);
+
+ tabs._positionPinnedTabs();
+ ]]></handler>
+ <handler event="overflow"><![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.setAttribute("overflow", "true");
+ tabs._positionPinnedTabs();
+ tabs._handleTabSelect(false);
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabs"
+ extends="chrome://global/content/bindings/tabbox.xml#tabs">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content>
+ <xul:hbox align="end">
+ <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
+ </xul:hbox>
+ <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
+ style="min-width: 1px;"
+ class="tabbrowser-arrowscrollbox">
+<!--
+ This is a hack to circumvent bug 472020, otherwise the tabs show up on the
+ right of the newtab button.
+-->
+ <children includes="tab"/>
+<!--
+ This is to ensure anything extensions put here will go before the newtab
+ button, necessary due to the previous hack.
+-->
+ <children/>
+ <xul:toolbarbutton class="tabs-newtab-button"
+ anonid="tabs-newtab-button"
+ command="cmd_newNavigatorTab"
+ onclick="checkForMiddleClick(this, event);"
+ onmouseover="document.getBindingParent(this)._enterNewTab();"
+ onmouseout="document.getBindingParent(this)._leaveNewTab();"
+ tooltip="dynamic-shortcut-tooltip"/>
+ <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
+ style="width: 0;"/>
+ </xul:arrowscrollbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener, nsIObserver">
+ <constructor>
+ <![CDATA[
+ this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
+
+ var tab = this.firstChild;
+ tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
+ tab.setAttribute("crop", "end");
+ tab.setAttribute("onerror", "this.removeAttribute('image');");
+
+ window.addEventListener("resize", this, false);
+ window.addEventListener("load", this, false);
+
+ try {
+ this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
+ } catch (ex) {
+ this._tabAnimationLoggingEnabled = false;
+ }
+ this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
+ this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
+ Services.prefs.addObserver("privacy.userContext.enabled", this, false);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("privacy.userContext.enabled", this);
+ ]]>
+ </destructor>
+
+ <field name="tabbrowser" readonly="true">
+ document.getElementById(this.getAttribute("tabbrowser"));
+ </field>
+
+ <field name="tabbox" readonly="true">
+ this.tabbrowser.mTabBox;
+ </field>
+
+ <field name="contextMenu" readonly="true">
+ document.getElementById("tabContextMenu");
+ </field>
+
+ <field name="mTabstripWidth">0</field>
+
+ <field name="mTabstrip">
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
+ </field>
+
+ <field name="_firstTab">null</field>
+ <field name="_lastTab">null</field>
+ <field name="_afterSelectedTab">null</field>
+ <field name="_beforeHoveredTab">null</field>
+ <field name="_afterHoveredTab">null</field>
+ <field name="_hoveredTab">null</field>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ switch (aTopic) {
+ case "nsPref:changed":
+ // This is the only pref observed.
+ let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
+
+ const newTab = document.getElementById("new-tab-button");
+ const newTab2 = document.getAnonymousElementByAttribute(this, "anonid", "tabs-newtab-button")
+
+ if (containersEnabled) {
+ for (let parent of [newTab, newTab2]) {
+ if (!parent)
+ continue;
+ let popup = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menupopup");
+ if (parent.id) {
+ popup.id = "newtab-popup";
+ } else {
+ popup.setAttribute("anonid", "newtab-popup");
+ }
+ popup.className = "new-tab-popup";
+ popup.setAttribute("position", "after_end");
+ parent.appendChild(popup);
+
+ gClickAndHoldListenersOnElement.add(parent);
+ parent.setAttribute("type", "menu");
+ }
+ } else {
+ for (let parent of [newTab, newTab2]) {
+ if (!parent)
+ continue;
+ gClickAndHoldListenersOnElement.remove(parent);
+ parent.removeAttribute("type");
+ if (!parent.firstChild)
+ continue;
+ parent.firstChild.remove();
+ }
+ }
+
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <property name="_isCustomizing" readonly="true">
+ <getter>
+ let root = document.documentElement;
+ return root.getAttribute("customizing") == "true" ||
+ root.getAttribute("customize-exiting") == "true";
+ </getter>
+ </property>
+
+ <method name="_setPositionalAttributes">
+ <body><![CDATA[
+ let visibleTabs = this.tabbrowser.visibleTabs;
+
+ if (!visibleTabs.length)
+ return;
+
+ let selectedIndex = visibleTabs.indexOf(this.selectedItem);
+
+ let lastVisible = visibleTabs.length - 1;
+
+ if (this._afterSelectedTab)
+ this._afterSelectedTab.removeAttribute("afterselected-visible");
+ if (this.selectedItem.closing || selectedIndex == lastVisible) {
+ this._afterSelectedTab = null;
+ } else {
+ this._afterSelectedTab = visibleTabs[selectedIndex + 1];
+ this._afterSelectedTab.setAttribute("afterselected-visible",
+ "true");
+ }
+
+ if (this._firstTab)
+ this._firstTab.removeAttribute("first-visible-tab");
+ this._firstTab = visibleTabs[0];
+ this._firstTab.setAttribute("first-visible-tab", "true");
+ if (this._lastTab)
+ this._lastTab.removeAttribute("last-visible-tab");
+ this._lastTab = visibleTabs[lastVisible];
+ this._lastTab.setAttribute("last-visible-tab", "true");
+
+ let hoveredTab = this._hoveredTab;
+ if (hoveredTab) {
+ hoveredTab._mouseleave();
+ }
+ hoveredTab = this.querySelector("tab:hover");
+ if (hoveredTab) {
+ hoveredTab._mouseenter();
+ }
+ ]]></body>
+ </method>
+
+ <field name="_blockDblClick">false</field>
+
+ <field name="_tabDropIndicator">
+ document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
+ </field>
+
+ <field name="_dragOverDelay">350</field>
+ <field name="_dragTime">0</field>
+
+ <field name="_container" readonly="true"><![CDATA[
+ this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
+ ]]></field>
+
+ <field name="_propagatedVisibilityOnce">false</field>
+
+ <property name="visible"
+ onget="return !this._container.collapsed;">
+ <setter><![CDATA[
+ if (val == this.visible &&
+ this._propagatedVisibilityOnce)
+ return val;
+
+ this._container.collapsed = !val;
+
+ this._propagateVisibility();
+ this._propagatedVisibilityOnce = true;
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="_enterNewTab">
+ <body><![CDATA[
+ let visibleTabs = this.tabbrowser.visibleTabs;
+ let candidate = visibleTabs[visibleTabs.length - 1];
+ if (!candidate.selected) {
+ this._beforeHoveredTab = candidate;
+ candidate.setAttribute("beforehovered", "true");
+ }
+ ]]></body>
+ </method>
+
+ <method name="_leaveNewTab">
+ <body><![CDATA[
+ if (this._beforeHoveredTab) {
+ this._beforeHoveredTab.removeAttribute("beforehovered");
+ this._beforeHoveredTab = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_propagateVisibility">
+ <body><![CDATA[
+ let visible = this.visible;
+
+ document.getElementById("menu_closeWindow").hidden = !visible;
+ document.getElementById("menu_close").setAttribute("label",
+ this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
+
+ TabsInTitlebar.allowedBy("tabs-visible", visible);
+ ]]></body>
+ </method>
+
+ <method name="updateVisibility">
+ <body><![CDATA[
+ if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
+ this.visible = window.toolbar.visible;
+ else
+ this.visible = true;
+ ]]></body>
+ </method>
+
+ <method name="adjustTabstrip">
+ <body><![CDATA[
+ let numTabs = this.childNodes.length -
+ this.tabbrowser._removingTabs.length;
+ if (numTabs > 2) {
+ // This is an optimization to avoid layout flushes by calling
+ // getBoundingClientRect() when we just opened a second tab. In
+ // this case it's highly unlikely that the tab width is smaller
+ // than mTabClipWidth and the tab close button obscures too much
+ // of the tab's label. In the edge case of the window being too
+ // narrow (or if tabClipWidth has been set to a way higher value),
+ // we'll correct the 'closebuttons' attribute after the tabopen
+ // animation has finished.
+
+ let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
+ if (tab && tab.getBoundingClientRect().width <= this.mTabClipWidth) {
+ this.setAttribute("closebuttons", "activetab");
+ return;
+ }
+ }
+ this.removeAttribute("closebuttons");
+ ]]></body>
+ </method>
+
+ <method name="_handleTabSelect">
+ <parameter name="aSmoothScroll"/>
+ <body><![CDATA[
+ if (this.getAttribute("overflow") == "true")
+ this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
+ ]]></body>
+ </method>
+
+ <method name="_fillTrailingGap">
+ <body><![CDATA[
+ try {
+ // if we're at the right side (and not the logical end,
+ // which is why this works for both LTR and RTL)
+ // of the tabstrip, we need to ensure that we stay
+ // completely scrolled to the right side
+ var tabStrip = this.mTabstrip;
+ if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
+ tabStrip.scrollSize)
+ tabStrip.scrollByPixels(-1);
+ } catch (e) {}
+ ]]></body>
+ </method>
+
+ <field name="_closingTabsSpacer">
+ document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
+ </field>
+
+ <field name="_tabDefaultMaxWidth">NaN</field>
+ <field name="_lastTabClosedByMouse">false</field>
+ <field name="_hasTabTempMaxWidth">false</field>
+
+ <!-- Try to keep the active tab's close button under the mouse cursor -->
+ <method name="_lockTabSizing">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ var tabs = this.tabbrowser.visibleTabs;
+ if (!tabs.length)
+ return;
+
+ var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
+ var tabWidth = aTab.getBoundingClientRect().width;
+
+ if (!this._tabDefaultMaxWidth)
+ this._tabDefaultMaxWidth =
+ parseFloat(window.getComputedStyle(aTab).maxWidth);
+ this._lastTabClosedByMouse = true;
+
+ if (this.getAttribute("overflow") == "true") {
+ // Don't need to do anything if we're in overflow mode and aren't scrolled
+ // all the way to the right, or if we're closing the last tab.
+ if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
+ return;
+
+ // If the tab has an owner that will become the active tab, the owner will
+ // be to the left of it, so we actually want the left tab to slide over.
+ // This can't be done as easily in non-overflow mode, so we don't bother.
+ if (aTab.owner)
+ return;
+
+ this._expandSpacerBy(tabWidth);
+ } else { // non-overflow mode
+ // Locking is neither in effect nor needed, so let tabs expand normally.
+ if (isEndTab && !this._hasTabTempMaxWidth)
+ return;
+
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ // Force tabs to stay the same width, unless we're closing the last tab,
+ // which case we need to let them expand just enough so that the overall
+ // tabbar width is the same.
+ if (isEndTab) {
+ let numNormalTabs = tabs.length - numPinned;
+ tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
+ if (tabWidth > this._tabDefaultMaxWidth)
+ tabWidth = this._tabDefaultMaxWidth;
+ }
+ tabWidth += "px";
+ for (let i = numPinned; i < tabs.length; i++) {
+ let tab = tabs[i];
+ tab.style.setProperty("max-width", tabWidth, "important");
+ if (!isEndTab) { // keep tabs the same width
+ tab.style.transition = "none";
+ tab.clientTop; // flush styles to skip animation; see bug 649247
+ tab.style.transition = "";
+ }
+ }
+ this._hasTabTempMaxWidth = true;
+ this.tabbrowser.addEventListener("mousemove", this, false);
+ window.addEventListener("mouseout", this, false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_expandSpacerBy">
+ <parameter name="pixels"/>
+ <body><![CDATA[
+ let spacer = this._closingTabsSpacer;
+ spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
+ this.setAttribute("using-closing-tabs-spacer", "true");
+ this.tabbrowser.addEventListener("mousemove", this, false);
+ window.addEventListener("mouseout", this, false);
+ ]]></body>
+ </method>
+
+ <method name="_unlockTabSizing">
+ <body><![CDATA[
+ this.tabbrowser.removeEventListener("mousemove", this, false);
+ window.removeEventListener("mouseout", this, false);
+
+ if (this._hasTabTempMaxWidth) {
+ this._hasTabTempMaxWidth = false;
+ let tabs = this.tabbrowser.visibleTabs;
+ for (let i = 0; i < tabs.length; i++)
+ tabs[i].style.maxWidth = "";
+ }
+
+ if (this.hasAttribute("using-closing-tabs-spacer")) {
+ this.removeAttribute("using-closing-tabs-spacer");
+ this._closingTabsSpacer.style.width = 0;
+ }
+ ]]></body>
+ </method>
+
+ <field name="_lastNumPinned">0</field>
+ <method name="_positionPinnedTabs">
+ <body><![CDATA[
+ var numPinned = this.tabbrowser._numPinnedTabs;
+ var doPosition = this.getAttribute("overflow") == "true" &&
+ numPinned > 0;
+
+ if (doPosition) {
+ this.setAttribute("positionpinnedtabs", "true");
+
+ let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
+ let paddingStart = this.mTabstrip.scrollboxPaddingStart;
+ let width = 0;
+
+ for (let i = numPinned - 1; i >= 0; i--) {
+ let tab = this.childNodes[i];
+ width += tab.getBoundingClientRect().width;
+ tab.style.marginInlineStart = - (width + scrollButtonWidth + paddingStart) + "px";
+ }
+
+ this.style.paddingInlineStart = width + paddingStart + "px";
+
+ } else {
+ this.removeAttribute("positionpinnedtabs");
+
+ for (let i = 0; i < numPinned; i++) {
+ let tab = this.childNodes[i];
+ tab.style.marginInlineStart = "";
+ }
+
+ this.style.paddingInlineStart = "";
+ }
+
+ if (this._lastNumPinned != numPinned) {
+ this._lastNumPinned = numPinned;
+ this._handleTabSelect(false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_animateTabMove">
+ <parameter name="event"/>
+ <body><![CDATA[
+ let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+
+ if (this.getAttribute("movingtab") != "true") {
+ this.setAttribute("movingtab", "true");
+ this.selectedItem = draggedTab;
+ }
+
+ if (!("animLastScreenX" in draggedTab._dragData))
+ draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
+
+ let screenX = event.screenX;
+ if (screenX == draggedTab._dragData.animLastScreenX)
+ return;
+
+ draggedTab._dragData.animLastScreenX = screenX;
+
+ let rtl = (window.getComputedStyle(this).direction == "rtl");
+ let pinned = draggedTab.pinned;
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ let tabs = this.tabbrowser.visibleTabs
+ .slice(pinned ? 0 : numPinned,
+ pinned ? numPinned : undefined);
+ if (rtl)
+ tabs.reverse();
+ let tabWidth = draggedTab.getBoundingClientRect().width;
+
+ // Move the dragged tab based on the mouse position.
+
+ let leftTab = tabs[0];
+ let rightTab = tabs[tabs.length - 1];
+ let tabScreenX = draggedTab.boxObject.screenX;
+ let translateX = screenX - draggedTab._dragData.screenX;
+ if (!pinned)
+ translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
+ let leftBound = leftTab.boxObject.screenX - tabScreenX;
+ let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
+ (tabScreenX + tabWidth);
+ translateX = Math.max(translateX, leftBound);
+ translateX = Math.min(translateX, rightBound);
+ draggedTab.style.transform = "translateX(" + translateX + "px)";
+
+ // Determine what tab we're dragging over.
+ // * Point of reference is the center of the dragged tab. If that
+ // point touches a background tab, the dragged tab would take that
+ // tab's position when dropped.
+ // * We're doing a binary search in order to reduce the amount of
+ // tabs we need to check.
+
+ let tabCenter = tabScreenX + translateX + tabWidth / 2;
+ let newIndex = -1;
+ let oldIndex = "animDropIndex" in draggedTab._dragData ?
+ draggedTab._dragData.animDropIndex : draggedTab._tPos;
+ let low = 0;
+ let high = tabs.length - 1;
+ while (low <= high) {
+ let mid = Math.floor((low + high) / 2);
+ if (tabs[mid] == draggedTab &&
+ ++mid > high)
+ break;
+ let boxObject = tabs[mid].boxObject;
+ let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
+ if (screenX > tabCenter) {
+ high = mid - 1;
+ } else if (screenX + boxObject.width < tabCenter) {
+ low = mid + 1;
+ } else {
+ newIndex = tabs[mid]._tPos;
+ break;
+ }
+ }
+ if (newIndex >= oldIndex)
+ newIndex++;
+ if (newIndex < 0 || newIndex == oldIndex)
+ return;
+ draggedTab._dragData.animDropIndex = newIndex;
+
+ // Shift background tabs to leave a gap where the dragged tab
+ // would currently be dropped.
+
+ for (let tab of tabs) {
+ if (tab != draggedTab) {
+ let shift = getTabShift(tab, newIndex);
+ tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
+ }
+ }
+
+ function getTabShift(tab, dropIndex) {
+ if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
+ return rtl ? -tabWidth : tabWidth;
+ if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
+ return rtl ? tabWidth : -tabWidth;
+ return 0;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_finishAnimateTabMove">
+ <body><![CDATA[
+ if (this.getAttribute("movingtab") != "true")
+ return;
+
+ for (let tab of this.tabbrowser.visibleTabs)
+ tab.style.transform = "";
+
+ this.removeAttribute("movingtab");
+
+ this._handleTabSelect();
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "load":
+ this.updateVisibility();
+ TabsInTitlebar.init();
+ break;
+ case "resize":
+ if (aEvent.target != window)
+ break;
+
+ TabsInTitlebar.updateAppearance();
+
+ var width = this.mTabstrip.boxObject.width;
+ if (width != this.mTabstripWidth) {
+ this.adjustTabstrip();
+ this._fillTrailingGap();
+ this._handleTabSelect();
+ this.mTabstripWidth = width;
+ }
+
+ this.tabbrowser.updateWindowResizers();
+ break;
+ case "mouseout":
+ // If the "related target" (the node to which the pointer went) is not
+ // a child of the current document, the mouse just left the window.
+ let relatedTarget = aEvent.relatedTarget;
+ if (relatedTarget && relatedTarget.ownerDocument == document)
+ break;
+ case "mousemove":
+ if (document.getElementById("tabContextMenu").state != "open")
+ this._unlockTabSizing();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <field name="_animateElement">
+ this.mTabstrip._scrollButtonDown;
+ </field>
+
+ <method name="_notifyBackgroundTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.pinned || aTab.hidden)
+ return;
+
+ var scrollRect = this.mTabstrip.scrollClientRect;
+ var tab = aTab.getBoundingClientRect();
+ this.mTabstrip._calcTabMargins(aTab);
+
+ // DOMRect left/right properties are immutable.
+ tab = {left: tab.left, right: tab.right};
+
+ // Is the new tab already completely visible?
+ if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
+ return;
+
+ if (this.mTabstrip.smoothScroll) {
+ let selected = !this.selectedItem.pinned &&
+ this.selectedItem.getBoundingClientRect();
+ if (selected) {
+ selected = {left: selected.left, right: selected.right};
+ // Need to take in to account the width of the left/right margins on tabs.
+ selected.left = selected.left + this.mTabstrip._tabMarginLeft;
+ selected.right = selected.right - this.mTabstrip._tabMarginRight;
+ }
+
+ tab.left += this.mTabstrip._tabMarginLeft;
+ tab.right -= this.mTabstrip._tabMarginRight;
+
+ // Can we make both the new tab and the selected tab completely visible?
+ if (!selected ||
+ Math.max(tab.right - selected.left, selected.right - tab.left) <=
+ scrollRect.width) {
+ this.mTabstrip.ensureElementIsVisible(aTab);
+ return;
+ }
+
+ this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
+ selected.right - scrollRect.right :
+ selected.left - scrollRect.left);
+ }
+
+ if (!this._animateElement.hasAttribute("notifybgtab")) {
+ this._animateElement.setAttribute("notifybgtab", "true");
+ setTimeout(function (ele) {
+ ele.removeAttribute("notifybgtab");
+ }, 150, this._animateElement);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getDragTargetTab">
+ <parameter name="event"/>
+ <parameter name="isLink"/>
+ <body><![CDATA[
+ let tab = event.target.localName == "tab" ? event.target : null;
+ if (tab && isLink) {
+ let boxObject = tab.boxObject;
+ if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
+ event.screenX > boxObject.screenX + boxObject.width * .75)
+ return null;
+ }
+ return tab;
+ ]]></body>
+ </method>
+
+ <method name="_getDropIndex">
+ <parameter name="event"/>
+ <parameter name="isLink"/>
+ <body><![CDATA[
+ var tabs = this.childNodes;
+ var tab = this._getDragTargetTab(event, isLink);
+ if (window.getComputedStyle(this, null).direction == "ltr") {
+ for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
+ if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
+ return i;
+ } else {
+ for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
+ if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
+ return i;
+ }
+ return tabs.length;
+ ]]></body>
+ </method>
+
+ <method name="_getDropEffectForTabDrag">
+ <parameter name="event"/>
+ <body><![CDATA[
+ var dt = event.dataTransfer;
+ if (dt.mozItemCount == 1) {
+ var types = dt.mozTypesAt(0);
+ // tabs are always added as the first type
+ if (types[0] == TAB_DROP_TYPE) {
+ let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (sourceNode instanceof XULElement &&
+ sourceNode.localName == "tab" &&
+ sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
+ sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
+ // Do not allow transfering a private tab to a non-private window
+ // and vice versa.
+ if (PrivateBrowsingUtils.isWindowPrivate(window) !=
+ PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
+ return "none";
+
+ if (window.gMultiProcessBrowser !=
+ sourceNode.ownerDocument.defaultView.gMultiProcessBrowser)
+ return "none";
+
+ return dt.dropEffect == "copy" ? "copy" : "move";
+ }
+ }
+ }
+
+ if (browserDragAndDrop.canDropLink(event)) {
+ return "link";
+ }
+ return "none";
+ ]]></body>
+ </method>
+
+ <method name="_handleNewTab">
+ <parameter name="tab"/>
+ <body><![CDATA[
+ if (tab.parentNode != this)
+ return;
+ tab._fullyOpen = true;
+
+ this.adjustTabstrip();
+
+ if (tab.getAttribute("selected") == "true") {
+ this._fillTrailingGap();
+ this._handleTabSelect();
+ } else {
+ this._notifyBackgroundTab(tab);
+ }
+
+ // XXXmano: this is a temporary workaround for bug 345399
+ // We need to manually update the scroll buttons disabled state
+ // if a tab was inserted to the overflow area or removed from it
+ // without any scrolling and when the tabbar has already
+ // overflowed.
+ this.mTabstrip._updateScrollButtonsDisabledState();
+
+ // Preload the next about:newtab if there isn't one already.
+ this.tabbrowser._createPreloadBrowser();
+ ]]></body>
+ </method>
+
+ <method name="_canAdvanceToTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return !aTab.closing;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabTelemetryStart">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ // Animation-smoothness telemetry/logging
+ if (Services.telemetry.canRecordExtended || this._tabAnimationLoggingEnabled) {
+ if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
+ // Indicate newtab page animation where other tabs are unaffected
+ // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
+ aTab._recordingTabOpenPlain = true;
+ }
+ aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .startFrameTimeRecording();
+ }
+
+ // Overall animation duration
+ aTab._animStartTime = Date.now();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabTelemetryEnd">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab._animStartTime) {
+ return;
+ }
+
+ aTab._animStartTime = 0;
+
+ // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
+ if (!("_recordingHandle" in aTab)) {
+ return;
+ }
+
+ let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .stopFrameTimeRecording(aTab._recordingHandle);
+ delete aTab._recordingHandle;
+ let frameCount = intervals.length;
+
+ if (this._tabAnimationLoggingEnabled) {
+ let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval):\n";
+ for (let i = 0; i < frameCount; i++) {
+ msg += Math.round(intervals[i]) + "\n";
+ }
+ Services.console.logStringMessage(msg);
+ }
+
+ // For telemetry, the first frame interval is not useful since it may represent an interval
+ // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
+ if (frameCount > 1) {
+ let averageInterval = 0;
+ for (let i = 1; i < frameCount; i++) {
+ averageInterval += intervals[i];
+ }
+ averageInterval = averageInterval / (frameCount - 1);
+
+ Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval);
+
+ if (aTab._recordingTabOpenPlain) {
+ delete aTab._recordingTabOpenPlain;
+ // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be
+ // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW.
+ let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : "";
+ Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- Deprecated stuff, implemented for backwards compatibility. -->
+ <property name="mAllTabsPopup" readonly="true"
+ onget="return document.getElementById('alltabs-popup');"/>
+ </implementation>
+
+ <handlers>
+ <handler event="TabSelect" action="this._handleTabSelect();"/>
+
+ <handler event="transitionend"><![CDATA[
+ if (event.propertyName != "max-width")
+ return;
+
+ var tab = event.target;
+
+ this._handleTabTelemetryEnd(tab);
+
+ if (tab.getAttribute("fadein") == "true") {
+ if (tab._fullyOpen)
+ this.adjustTabstrip();
+ else
+ this._handleNewTab(tab);
+ } else if (tab.closing) {
+ this.tabbrowser._endRemoveTab(tab);
+ }
+ ]]></handler>
+
+ <handler event="dblclick"><![CDATA[
+ // When the tabbar has an unified appearance with the titlebar
+ // and menubar, a double-click in it should have the same behavior
+ // as double-clicking the titlebar
+ if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive)
+ return;
+
+ if (event.button != 0 ||
+ event.originalTarget.localName != "box")
+ return;
+
+ // See hack note in the tabbrowser-close-tab-button binding
+ if (!this._blockDblClick)
+ BrowserOpenTab();
+
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="click" button="0" phase="capturing"><![CDATA[
+ /* Catches extra clicks meant for the in-tab close button.
+ * Placed here to avoid leaking (a temporary handler added from the
+ * in-tab close button binding would close over the tab and leak it
+ * until the handler itself was removed). (bug 897751)
+ *
+ * The only sequence in which a second click event (i.e. dblclik)
+ * can be dispatched on an in-tab close button is when it is shown
+ * after the first click (i.e. the first click event was dispatched
+ * on the tab). This happens when we show the close button only on
+ * the active tab. (bug 352021)
+ * The only sequence in which a third click event can be dispatched
+ * on an in-tab close button is when the tab was opened with a
+ * double click on the tabbar. (bug 378344)
+ * In both cases, it is most likely that the close button area has
+ * been accidentally clicked, therefore we do not close the tab.
+ *
+ * We don't want to ignore processing of more than one click event,
+ * though, since the user might actually be repeatedly clicking to
+ * close many tabs at once.
+ */
+ let target = event.originalTarget;
+ if (target.classList.contains('tab-close-button')) {
+ // We preemptively set this to allow the closing-multiple-tabs-
+ // in-a-row case.
+ if (this._blockDblClick) {
+ target._ignoredCloseButtonClicks = true;
+ } else if (event.detail > 1 && !target._ignoredCloseButtonClicks) {
+ target._ignoredCloseButtonClicks = true;
+ event.stopPropagation();
+ return;
+ } else {
+ // Reset the "ignored click" flag
+ target._ignoredCloseButtonClicks = false;
+ }
+ }
+
+ /* Protects from close-tab-button errant doubleclick:
+ * Since we're removing the event target, if the user
+ * double-clicks the button, the dblclick event will be dispatched
+ * with the tabbar as its event target (and explicit/originalTarget),
+ * which treats that as a mouse gesture for opening a new tab.
+ * In this context, we're manually blocking the dblclick event
+ * (see tabbrowser-close-tab-button dblclick handler).
+ */
+ if (this._blockDblClick) {
+ if (!("_clickedTabBarOnce" in this)) {
+ this._clickedTabBarOnce = true;
+ return;
+ }
+ delete this._clickedTabBarOnce;
+ this._blockDblClick = false;
+ }
+ ]]></handler>
+
+ <handler event="click"><![CDATA[
+ if (event.button != 1)
+ return;
+
+ if (event.target.localName == "tab") {
+ this.tabbrowser.removeTab(event.target, {animate: true,
+ byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
+ } else if (event.originalTarget.localName == "box") {
+ // The user middleclicked an open space on the tabstrip. This could
+ // be because they intend to open a new tab, but it could also be
+ // because they just removed a tab and they now middleclicked on the
+ // resulting space while that tab is closing. In that case, we don't
+ // want to open a tab. So if we're removing one or more tabs, and
+ // the tab click is before the end of the last visible tab, we do
+ // nothing.
+ if (this.tabbrowser._removingTabs.length) {
+ let visibleTabs = this.tabbrowser.visibleTabs;
+ let ltr = (window.getComputedStyle(this, null).direction == "ltr");
+ let lastTab = visibleTabs[visibleTabs.length - 1];
+ let endOfTab = lastTab.getBoundingClientRect()[ltr ? "right" : "left"];
+ if ((ltr && event.clientX > endOfTab) ||
+ (!ltr && event.clientX < endOfTab)) {
+ BrowserOpenTab();
+ }
+ } else {
+ BrowserOpenTab();
+ }
+ } else {
+ return;
+ }
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="keydown" group="system"><![CDATA[
+ if (event.altKey || event.shiftKey)
+ return;
+
+ let wrongModifiers;
+ if (this.tabbrowser.AppConstants.platform == "macosx") {
+ wrongModifiers = !event.metaKey;
+ } else {
+ wrongModifiers = !event.ctrlKey || event.metaKey;
+ }
+
+ if (wrongModifiers)
+ return;
+
+ // Don't check if the event was already consumed because tab navigation
+ // should work always for better user experience.
+
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_UP:
+ this.tabbrowser.moveTabBackward();
+ break;
+ case KeyEvent.DOM_VK_DOWN:
+ this.tabbrowser.moveTabForward();
+ break;
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_LEFT:
+ this.tabbrowser.moveTabOver(event);
+ break;
+ case KeyEvent.DOM_VK_HOME:
+ this.tabbrowser.moveTabToStart();
+ break;
+ case KeyEvent.DOM_VK_END:
+ this.tabbrowser.moveTabToEnd();
+ break;
+ default:
+ // Consume the keydown event for the above keyboard
+ // shortcuts only.
+ return;
+ }
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ var tab = this._getDragTargetTab(event, false);
+ if (!tab || this._isCustomizing)
+ return;
+
+ let dt = event.dataTransfer;
+ dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
+ let browser = tab.linkedBrowser;
+
+ // We must not set text/x-moz-url or text/plain data here,
+ // otherwise trying to deatch the tab by dropping it on the desktop
+ // may result in an "internet shortcut"
+ dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
+
+ // Set the cursor to an arrow during tab drags.
+ dt.mozCursor = "default";
+
+ // Create a canvas to which we capture the current tab.
+ // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
+ // canvas size (in CSS pixels) to the window's backing resolution in order
+ // to get a full-resolution drag image for use on HiDPI displays.
+ let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
+ let canvas = this._dndCanvas ? this._dndCanvas
+ : document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.mozOpaque = true;
+ canvas.width = 160 * scale;
+ canvas.height = 90 * scale;
+ let toDrag;
+ let dragImageOffset = -16;
+ if (gMultiProcessBrowser) {
+ var context = canvas.getContext('2d');
+ context.fillStyle = "white";
+ context.fillRect(0, 0, canvas.width, canvas.height);
+ // Create a panel to use it in setDragImage
+ // which will tell xul to render a panel that follows
+ // the pointer while a dnd session is on.
+ if (!this._dndPanel) {
+ this._dndCanvas = canvas;
+ this._dndPanel = document.createElement("panel");
+ this._dndPanel.className = "dragfeedback-tab";
+ this._dndPanel.setAttribute("type", "drag");
+ let wrapper = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ wrapper.style.width = "160px";
+ wrapper.style.height = "90px";
+ wrapper.appendChild(canvas);
+ canvas.style.width = "100%";
+ canvas.style.height = "100%";
+ this._dndPanel.appendChild(wrapper);
+ document.documentElement.appendChild(this._dndPanel);
+ }
+ // PageThumb is async with e10s but that's fine
+ // since we can update the panel during the dnd.
+ PageThumbs.captureToCanvas(browser, canvas);
+ toDrag = this._dndPanel;
+ } else {
+ // For the non e10s case we can just use PageThumbs
+ // sync. No need for xul magic, the native dnd will
+ // be fine, so let's use the canvas for setDragImage.
+ PageThumbs.captureToCanvas(browser, canvas);
+ toDrag = canvas;
+ dragImageOffset = dragImageOffset * scale;
+ }
+ dt.setDragImage(toDrag, dragImageOffset, dragImageOffset);
+
+ // _dragData.offsetX/Y give the coordinates that the mouse should be
+ // positioned relative to the corner of the new window created upon
+ // dragend such that the mouse appears to have the same position
+ // relative to the corner of the dragged tab.
+ function clientX(ele) {
+ return ele.getBoundingClientRect().left;
+ }
+ let tabOffsetX = clientX(tab) - clientX(this);
+ tab._dragData = {
+ offsetX: event.screenX - window.screenX - tabOffsetX,
+ offsetY: event.screenY - window.screenY,
+ scrollX: this.mTabstrip.scrollPosition,
+ screenX: event.screenX
+ };
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ var effects = this._getDropEffectForTabDrag(event);
+
+ var ind = this._tabDropIndicator;
+ if (effects == "" || effects == "none") {
+ ind.collapsed = true;
+ return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+
+ var tabStrip = this.mTabstrip;
+ var ltr = (window.getComputedStyle(this, null).direction == "ltr");
+
+ // autoscroll the tab strip if we drag over the scroll
+ // buttons, even if we aren't dragging a tab, but then
+ // return to avoid drawing the drop indicator
+ var pixelsToScroll = 0;
+ if (this.getAttribute("overflow") == "true") {
+ var targetAnonid = event.originalTarget.getAttribute("anonid");
+ switch (targetAnonid) {
+ case "scrollbutton-up":
+ pixelsToScroll = tabStrip.scrollIncrement * -1;
+ break;
+ case "scrollbutton-down":
+ pixelsToScroll = tabStrip.scrollIncrement;
+ break;
+ }
+ if (pixelsToScroll)
+ tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
+ }
+
+ if (effects == "move" &&
+ this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
+ ind.collapsed = true;
+ this._animateTabMove(event);
+ return;
+ }
+
+ this._finishAnimateTabMove();
+
+ if (effects == "link") {
+ let tab = this._getDragTargetTab(event, true);
+ if (tab) {
+ if (!this._dragTime)
+ this._dragTime = Date.now();
+ if (Date.now() >= this._dragTime + this._dragOverDelay)
+ this.selectedItem = tab;
+ ind.collapsed = true;
+ return;
+ }
+ }
+
+ var rect = tabStrip.getBoundingClientRect();
+ var newMargin;
+ if (pixelsToScroll) {
+ // if we are scrolling, put the drop indicator at the edge
+ // so that it doesn't jump while scrolling
+ let scrollRect = tabStrip.scrollClientRect;
+ let minMargin = scrollRect.left - rect.left;
+ let maxMargin = Math.min(minMargin + scrollRect.width,
+ scrollRect.right);
+ if (!ltr)
+ [minMargin, maxMargin] = [this.clientWidth - maxMargin,
+ this.clientWidth - minMargin];
+ newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
+ }
+ else {
+ let newIndex = this._getDropIndex(event, effects == "link");
+ if (newIndex == this.childNodes.length) {
+ let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
+ if (ltr)
+ newMargin = tabRect.right - rect.left;
+ else
+ newMargin = rect.right - tabRect.left;
+ }
+ else {
+ let tabRect = this.childNodes[newIndex].getBoundingClientRect();
+ if (ltr)
+ newMargin = tabRect.left - rect.left;
+ else
+ newMargin = rect.right - tabRect.right;
+ }
+ }
+
+ ind.collapsed = false;
+
+ newMargin += ind.clientWidth / 2;
+ if (!ltr)
+ newMargin *= -1;
+
+ ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
+ ind.style.marginInlineStart = (-ind.clientWidth) + "px";
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ var dt = event.dataTransfer;
+ var dropEffect = dt.dropEffect;
+ var draggedTab;
+ if (dt.mozTypesAt(0)[0] == TAB_DROP_TYPE) { // tab copy or move
+ draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ // not our drop then
+ if (!draggedTab)
+ return;
+ }
+
+ this._tabDropIndicator.collapsed = true;
+ event.stopPropagation();
+ if (draggedTab && dropEffect == "copy") {
+ // copy the dropped tab (wherever it's from)
+ let newIndex = this._getDropIndex(event, false);
+ let newTab = this.tabbrowser.duplicateTab(draggedTab);
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+ if (draggedTab.parentNode != this || event.shiftKey)
+ this.selectedItem = newTab;
+ } else if (draggedTab && draggedTab.parentNode == this) {
+ this._finishAnimateTabMove();
+
+ // actually move the dragged tab
+ if ("animDropIndex" in draggedTab._dragData) {
+ let newIndex = draggedTab._dragData.animDropIndex;
+ if (newIndex > draggedTab._tPos)
+ newIndex--;
+ this.tabbrowser.moveTabTo(draggedTab, newIndex);
+ }
+ } else if (draggedTab) {
+ let newIndex = this._getDropIndex(event, false);
+ this.tabbrowser.adoptTab(draggedTab, newIndex, true);
+ } else {
+ // Pass true to disallow dropping javascript: or data: urls
+ let links;
+ try {
+ links = browserDragAndDrop.dropLinks(event, true);
+ } catch (ex) {}
+
+ if (!links || links.length === 0)
+ return;
+
+ let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+
+ if (event.shiftKey)
+ inBackground = !inBackground;
+
+ let targetTab = this._getDragTargetTab(event, true);
+ let userContextId = this.selectedItem.getAttribute("usercontextid");
+ let replace = !!targetTab;
+ let newIndex = this._getDropIndex(event, true);
+ let urls = links.map(link => link.url);
+ this.tabbrowser.loadTabs(urls, {
+ inBackground,
+ replace,
+ allowThirdPartyFixup: true,
+ targetTab,
+ newIndex,
+ userContextId,
+ });
+ }
+
+ if (draggedTab) {
+ delete draggedTab._dragData;
+ }
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ // Note: while this case is correctly handled here, this event
+ // isn't dispatched when the tab is moved within the tabstrip,
+ // see bug 460801.
+
+ this._finishAnimateTabMove();
+
+ var dt = event.dataTransfer;
+ var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
+ delete draggedTab._dragData;
+ return;
+ }
+
+ // Disable detach within the browser toolbox
+ var eX = event.screenX;
+ var eY = event.screenY;
+ var wX = window.screenX;
+ // check if the drop point is horizontally within the window
+ if (eX > wX && eX < (wX + window.outerWidth)) {
+ let bo = this.mTabstrip.boxObject;
+ // also avoid detaching if the the tab was dropped too close to
+ // the tabbar (half a tab)
+ let endScreenY = bo.screenY + 1.5 * bo.height;
+ if (eY < endScreenY && eY > window.screenY)
+ return;
+ }
+
+ // screen.availLeft et. al. only check the screen that this window is on,
+ // but we want to look at the screen the tab is being dropped onto.
+ var screen = Cc["@mozilla.org/gfx/screenmanager;1"]
+ .getService(Ci.nsIScreenManager)
+ .screenForRect(eX, eY, 1, 1);
+ var fullX = {}, fullY = {}, fullWidth = {}, fullHeight = {};
+ var availX = {}, availY = {}, availWidth = {}, availHeight = {};
+ // get full screen rect and available rect, both in desktop pix
+ screen.GetRectDisplayPix(fullX, fullY, fullWidth, fullHeight);
+ screen.GetAvailRectDisplayPix(availX, availY, availWidth, availHeight);
+
+ // scale factor to convert desktop pixels to CSS px
+ var scaleFactor =
+ screen.contentsScaleFactor / screen.defaultCSSScaleFactor;
+ // synchronize CSS-px top-left coordinates with the screen's desktop-px
+ // coordinates, to ensure uniqueness across multiple screens
+ // (compare the equivalent adjustments in nsGlobalWindow::GetScreenXY()
+ // and related methods)
+ availX.value = (availX.value - fullX.value) * scaleFactor + fullX.value;
+ availY.value = (availY.value - fullY.value) * scaleFactor + fullY.value;
+ availWidth.value *= scaleFactor;
+ availHeight.value *= scaleFactor;
+
+ // ensure new window entirely within screen
+ var winWidth = Math.min(window.outerWidth, availWidth.value);
+ var winHeight = Math.min(window.outerHeight, availHeight.value);
+ var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, availX.value),
+ availX.value + availWidth.value - winWidth);
+ var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, availY.value),
+ availY.value + availHeight.value - winHeight);
+
+ delete draggedTab._dragData;
+
+ if (this.tabbrowser.tabs.length == 1) {
+ // resize _before_ move to ensure the window fits the new screen. if
+ // the window is too large for its screen, the window manager may do
+ // automatic repositioning.
+ window.resizeTo(winWidth, winHeight);
+ window.moveTo(left, top);
+ window.focus();
+ } else {
+ let props = { screenX: left, screenY: top };
+ if (this.tabbrowser.AppConstants.platform != "win") {
+ props.outerWidth = winWidth;
+ props.outerHeight = winHeight;
+ }
+ this.tabbrowser.replaceTabWithWindow(draggedTab, props);
+ }
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragexit"><![CDATA[
+ this._dragTime = 0;
+
+ // This does not work at all (see bug 458613)
+ var target = event.relatedTarget;
+ while (target && target != this)
+ target = target.parentNode;
+ if (target)
+ return;
+
+ this._tabDropIndicator.collapsed = true;
+ event.stopPropagation();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <!-- close-tab-button binding
+ This binding relies on the structure of the tabbrowser binding.
+ Therefore it should only be used as a child of the tab or the tabs
+ element (in both cases, when they are anonymous nodes of <tabbrowser>).
+ -->
+ <binding id="tabbrowser-close-tab-button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
+ <handlers>
+ <handler event="click" button="0"><![CDATA[
+ var bindingParent = document.getBindingParent(this);
+ var tabContainer = bindingParent.parentNode;
+ tabContainer.tabbrowser.removeTab(bindingParent, {animate: true,
+ byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
+ // This enables double-click protection for the tab container
+ // (see tabbrowser-tabs 'click' handler).
+ tabContainer._blockDblClick = true;
+ ]]></handler>
+
+ <handler event="dblclick" button="0" phase="capturing">
+ // for the one-close-button case
+ event.stopPropagation();
+ </handler>
+
+ <handler event="dragstart">
+ event.stopPropagation();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tab" display="xul:hbox"
+ extends="chrome://global/content/bindings/tabbox.xml#tab">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content context="tabContextMenu">
+ <xul:stack class="tab-stack" flex="1">
+ <xul:hbox xbl:inherits="pinned,selected=visuallyselected,fadein"
+ class="tab-background">
+ <xul:hbox xbl:inherits="pinned,selected=visuallyselected"
+ class="tab-background-start"/>
+ <xul:hbox xbl:inherits="pinned,selected=visuallyselected"
+ class="tab-background-middle"/>
+ <xul:hbox xbl:inherits="pinned,selected=visuallyselected"
+ class="tab-background-end"/>
+ </xul:hbox>
+ <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
+ class="tab-content" align="center">
+ <xul:image xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
+ class="tab-throbber"
+ role="presentation"
+ layer="true" />
+ <xul:image xbl:inherits="src=image,loadingprincipal=iconLoadingPrincipal,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
+ anonid="tab-icon-image"
+ class="tab-icon-image"
+ validate="never"
+ role="presentation"/>
+ <xul:image xbl:inherits="sharing,selected=visuallyselected"
+ anonid="sharing-icon"
+ class="tab-sharing-icon-overlay"
+ role="presentation"/>
+ <xul:image xbl:inherits="crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
+ anonid="overlay-icon"
+ class="tab-icon-overlay"
+ role="presentation"/>
+ <xul:label flex="1"
+ xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected=visuallyselected,attention"
+ class="tab-text tab-label"
+ role="presentation"/>
+ <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
+ anonid="soundplaying-icon"
+ class="tab-icon-sound"
+ role="presentation"/>
+ <xul:toolbarbutton anonid="close-button"
+ xbl:inherits="fadein,pinned,selected=visuallyselected"
+ class="tab-close-button close-icon"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (!("_lastAccessed" in this)) {
+ this.updateLastAccessed();
+ }
+ ]]></constructor>
+
+ <property name="_visuallySelected">
+ <setter>
+ <![CDATA[
+ if (val)
+ this.setAttribute("visuallyselected", "true");
+ else
+ this.removeAttribute("visuallyselected");
+ this.parentNode.tabbrowser._tabAttrModified(this, ["visuallyselected"]);
+
+ this._setPositionAttributes(val);
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="_selected">
+ <setter>
+ <![CDATA[
+ // in e10s we want to only pseudo-select a tab before its rendering is done, so that
+ // the rest of the system knows that the tab is selected, but we don't want to update its
+ // visual status to selected until after we receive confirmation that its content has painted.
+ if (val)
+ this.setAttribute("selected", "true");
+ else
+ this.removeAttribute("selected");
+
+ // If we're non-e10s we should update the visual selection as well at the same time,
+ // *or* if we're e10s and the visually selected tab isn't changing, in which case the
+ // tab switcher code won't run and update anything else (like the before- and after-
+ // selected attributes).
+ if (!gMultiProcessBrowser || (val && this.hasAttribute("visuallyselected"))) {
+ this._visuallySelected = val;
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="pinned" readonly="true">
+ <getter>
+ return this.getAttribute("pinned") == "true";
+ </getter>
+ </property>
+ <property name="hidden" readonly="true">
+ <getter>
+ return this.getAttribute("hidden") == "true";
+ </getter>
+ </property>
+ <property name="muted" readonly="true">
+ <getter>
+ return this.getAttribute("muted") == "true";
+ </getter>
+ </property>
+ <property name="blocked" readonly="true">
+ <getter>
+ return this.getAttribute("blocked") == "true";
+ </getter>
+ </property>
+ <!--
+ Describes how the tab ended up in this mute state. May be any of:
+
+ - undefined: The tabs mute state has never changed.
+ - null: The mute state was last changed through the UI.
+ - Any string: The ID was changed through an extension API. The string
+ must be the ID of the extension which changed it.
+ -->
+ <field name="muteReason">undefined</field>
+
+ <property name="userContextId" readonly="true">
+ <getter>
+ return this.hasAttribute("usercontextid")
+ ? parseInt(this.getAttribute("usercontextid"))
+ : 0;
+ </getter>
+ </property>
+
+ <property name="soundPlaying" readonly="true">
+ <getter>
+ return this.getAttribute("soundplaying") == "true";
+ </getter>
+ </property>
+
+ <property name="lastAccessed">
+ <getter>
+ return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
+ </getter>
+ </property>
+ <method name="updateLastAccessed">
+ <parameter name="aDate"/>
+ <body><![CDATA[
+ this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
+ ]]></body>
+ </method>
+
+ <field name="cachePosition">Infinity</field>
+
+ <field name="mOverCloseButton">false</field>
+ <property name="_overPlayingIcon" readonly="true">
+ <getter><![CDATA[
+ let iconVisible = this.hasAttribute("soundplaying") ||
+ this.hasAttribute("muted") ||
+ this.hasAttribute("blocked");
+ let soundPlayingIcon =
+ document.getAnonymousElementByAttribute(this, "anonid", "soundplaying-icon");
+ let overlayIcon =
+ document.getAnonymousElementByAttribute(this, "anonid", "overlay-icon");
+
+ return soundPlayingIcon && soundPlayingIcon.matches(":hover") ||
+ (overlayIcon && overlayIcon.matches(":hover") && iconVisible);
+ ]]></getter>
+ </property>
+ <field name="mCorrespondingMenuitem">null</field>
+
+ <!--
+ While it would make sense to track this in a field, the field will get nuked
+ once the node is gone from the DOM, which causes us to think the tab is not
+ closed, which causes us to make wrong decisions. So we use an expando instead.
+ <field name="closing">false</field>
+ -->
+
+ <method name="_mouseenter">
+ <body><![CDATA[
+ if (this.hidden || this.closing)
+ return;
+
+ let tabContainer = this.parentNode;
+ let visibleTabs = tabContainer.tabbrowser.visibleTabs;
+ let tabIndex = visibleTabs.indexOf(this);
+ if (tabIndex == 0) {
+ tabContainer._beforeHoveredTab = null;
+ } else {
+ let candidate = visibleTabs[tabIndex - 1];
+ if (!candidate.selected) {
+ tabContainer._beforeHoveredTab = candidate;
+ candidate.setAttribute("beforehovered", "true");
+ }
+ }
+
+ if (tabIndex == visibleTabs.length - 1) {
+ tabContainer._afterHoveredTab = null;
+ } else {
+ let candidate = visibleTabs[tabIndex + 1];
+ if (!candidate.selected) {
+ tabContainer._afterHoveredTab = candidate;
+ candidate.setAttribute("afterhovered", "true");
+ }
+ }
+
+ tabContainer._hoveredTab = this;
+ ]]></body>
+ </method>
+
+ <method name="_mouseleave">
+ <body><![CDATA[
+ let tabContainer = this.parentNode;
+ if (tabContainer._beforeHoveredTab) {
+ tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
+ tabContainer._beforeHoveredTab = null;
+ }
+ if (tabContainer._afterHoveredTab) {
+ tabContainer._afterHoveredTab.removeAttribute("afterhovered");
+ tabContainer._afterHoveredTab = null;
+ }
+
+ tabContainer._hoveredTab = null;
+ ]]></body>
+ </method>
+
+ <method name="toggleMuteAudio">
+ <parameter name="aMuteReason"/>
+ <body>
+ <![CDATA[
+ let tabContainer = this.parentNode;
+ let browser = this.linkedBrowser;
+ let modifiedAttrs = [];
+ if (browser.audioBlocked) {
+ this.removeAttribute("blocked");
+ modifiedAttrs.push("blocked");
+
+ // We don't want sound icon flickering between "blocked", "none" and
+ // "sound-playing", here adding the "soundplaying" is to keep the
+ // transition smoothly.
+ if (!this.hasAttribute("soundplaying")) {
+ this.setAttribute("soundplaying", true);
+ modifiedAttrs.push("soundplaying");
+ }
+
+ browser.resumeMedia();
+ } else {
+ if (browser.audioMuted) {
+ browser.unmute();
+ this.removeAttribute("muted");
+ BrowserUITelemetry.countTabMutingEvent("unmute", aMuteReason);
+ } else {
+ browser.mute();
+ this.setAttribute("muted", "true");
+ BrowserUITelemetry.countTabMutingEvent("mute", aMuteReason);
+ }
+ this.muteReason = aMuteReason || null;
+ modifiedAttrs.push("muted");
+ }
+ tabContainer.tabbrowser._tabAttrModified(this, modifiedAttrs);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setUserContextId">
+ <parameter name="aUserContextId"/>
+ <body>
+ <![CDATA[
+ if (aUserContextId) {
+ if (this.linkedBrowser) {
+ this.linkedBrowser.setAttribute("usercontextid", aUserContextId);
+ }
+ this.setAttribute("usercontextid", aUserContextId);
+ } else {
+ if (this.linkedBrowser) {
+ this.linkedBrowser.removeAttribute("usercontextid");
+ }
+ this.removeAttribute("usercontextid");
+ }
+
+ ContextualIdentityService.setTabStyle(this);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ let anonid = event.originalTarget.getAttribute("anonid");
+ if (anonid == "close-button")
+ this.mOverCloseButton = true;
+
+ this._mouseenter();
+ ]]></handler>
+ <handler event="mouseout"><![CDATA[
+ let anonid = event.originalTarget.getAttribute("anonid");
+ if (anonid == "close-button")
+ this.mOverCloseButton = false;
+
+ this._mouseleave();
+ ]]></handler>
+ <handler event="dragstart" phase="capturing">
+ this.style.MozUserFocus = '';
+ </handler>
+ <handler event="mousedown" phase="capturing">
+ <![CDATA[
+ if (this.selected) {
+ this.style.MozUserFocus = 'ignore';
+ this.clientTop; // just using this to flush style updates
+ } else if (this.mOverCloseButton ||
+ this._overPlayingIcon) {
+ // Prevent tabbox.xml from selecting the tab.
+ event.stopPropagation();
+ }
+ ]]>
+ </handler>
+ <handler event="mouseup">
+ this.style.MozUserFocus = '';
+ </handler>
+ <handler event="click">
+ <![CDATA[
+ if (event.button != 0) {
+ return;
+ }
+
+ if (this._overPlayingIcon) {
+ this.toggleMuteAudio();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-alltabs-popup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIDOMEventListener">
+ <method name="_tabOnAttrModified">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var tab = aEvent.target;
+ if (tab.mCorrespondingMenuitem)
+ this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
+ ]]></body>
+ </method>
+
+ <method name="_tabOnTabClose">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var tab = aEvent.target;
+ if (tab.mCorrespondingMenuitem)
+ this.removeChild(tab.mCorrespondingMenuitem);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "TabAttrModified":
+ this._tabOnAttrModified(aEvent);
+ break;
+ case "TabClose":
+ this._tabOnTabClose(aEvent);
+ break;
+ case "scroll":
+ this._updateTabsVisibilityStatus();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_updateTabsVisibilityStatus">
+ <body><![CDATA[
+ var tabContainer = gBrowser.tabContainer;
+ // We don't want menu item decoration unless there is overflow.
+ if (tabContainer.getAttribute("overflow") != "true")
+ return;
+
+ var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
+ for (var i = 0; i < this.childNodes.length; i++) {
+ let curTab = this.childNodes[i].tab;
+ if (!curTab) // "Undo close tab", menuseparator, or entries put here by addons.
+ continue;
+ let curTabBO = curTab.boxObject;
+ if (curTabBO.screenX >= tabstripBO.screenX &&
+ curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
+ this.childNodes[i].setAttribute("tabIsVisible", "true");
+ else
+ this.childNodes[i].removeAttribute("tabIsVisible");
+ }
+ ]]></body>
+ </method>
+
+ <method name="_createTabMenuItem">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ var menuItem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menuitem");
+
+ menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
+
+ this._setMenuitemAttributes(menuItem, aTab);
+
+ aTab.mCorrespondingMenuitem = menuItem;
+ menuItem.tab = aTab;
+
+ this.appendChild(menuItem);
+ ]]></body>
+ </method>
+
+ <method name="_setMenuitemAttributes">
+ <parameter name="aMenuitem"/>
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ aMenuitem.setAttribute("label", aTab.label);
+ aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
+
+ if (aTab.hasAttribute("busy")) {
+ aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
+ aMenuitem.removeAttribute("image");
+ } else {
+ aMenuitem.setAttribute("image", aTab.getAttribute("image"));
+ aMenuitem.removeAttribute("busy");
+ }
+
+ if (aTab.hasAttribute("pending"))
+ aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
+ else
+ aMenuitem.removeAttribute("pending");
+
+ if (aTab.selected)
+ aMenuitem.setAttribute("selected", "true");
+ else
+ aMenuitem.removeAttribute("selected");
+
+ function addEndImage() {
+ let endImage = document.createElement("image");
+ endImage.setAttribute("class", "alltabs-endimage");
+ let endImageContainer = document.createElement("hbox");
+ endImageContainer.setAttribute("align", "center");
+ endImageContainer.setAttribute("pack", "center");
+ endImageContainer.appendChild(endImage);
+ aMenuitem.appendChild(endImageContainer);
+ return endImage;
+ }
+
+ if (aMenuitem.firstChild)
+ aMenuitem.firstChild.remove();
+ if (aTab.hasAttribute("muted"))
+ addEndImage().setAttribute("muted", "true");
+ else if (aTab.hasAttribute("soundplaying"))
+ addEndImage().setAttribute("soundplaying", "true");
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing">
+ <![CDATA[
+ if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
+ createUserContextMenu(event);
+ return;
+ }
+
+ let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
+
+ if (event.target.getAttribute("anonid") == "newtab-popup" ||
+ event.target.id == "newtab-popup") {
+ createUserContextMenu(event);
+ } else {
+ document.getElementById("alltabs-popup-separator-1").hidden = !containersEnabled;
+ let containersTab = document.getElementById("alltabs_containersTab");
+
+ containersTab.hidden = !containersEnabled;
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ containersTab.setAttribute("disabled", "true");
+ }
+
+ document.getElementById("alltabs_undoCloseTab").disabled =
+ SessionStore.getClosedTabCount(window) == 0;
+
+ var tabcontainer = gBrowser.tabContainer;
+
+ // Listen for changes in the tab bar.
+ tabcontainer.addEventListener("TabAttrModified", this, false);
+ tabcontainer.addEventListener("TabClose", this, false);
+ tabcontainer.mTabstrip.addEventListener("scroll", this, false);
+
+ let tabs = gBrowser.visibleTabs;
+ for (var i = 0; i < tabs.length; i++) {
+ if (!tabs[i].pinned)
+ this._createTabMenuItem(tabs[i]);
+ }
+ this._updateTabsVisibilityStatus();
+ }
+ ]]></handler>
+
+ <handler event="popuphidden">
+ <![CDATA[
+ if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
+ return;
+ }
+
+ // clear out the menu popup and remove the listeners
+ for (let i = this.childNodes.length - 1; i > 0; i--) {
+ let menuItem = this.childNodes[i];
+ if (menuItem.tab) {
+ menuItem.tab.mCorrespondingMenuitem = null;
+ this.removeChild(menuItem);
+ }
+ if (menuItem.hasAttribute("usercontextid")) {
+ this.removeChild(menuItem);
+ }
+ }
+ var tabcontainer = gBrowser.tabContainer;
+ tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
+ tabcontainer.removeEventListener("TabAttrModified", this, false);
+ tabcontainer.removeEventListener("TabClose", this, false);
+ ]]></handler>
+
+ <handler event="DOMMenuItemActive">
+ <![CDATA[
+ var tab = event.target.tab;
+ if (tab) {
+ let overLink = tab.linkedBrowser.currentURI.spec;
+ if (overLink == "about:blank")
+ overLink = "";
+ XULBrowserWindow.setOverLink(overLink, null);
+ }
+ ]]></handler>
+
+ <handler event="DOMMenuItemInactive">
+ <![CDATA[
+ XULBrowserWindow.setOverLink("", null);
+ ]]></handler>
+
+ <handler event="command"><![CDATA[
+ if (event.target.tab)
+ gBrowser.selectedTab = event.target.tab;
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="statuspanel" display="xul:hbox">
+ <content>
+ <xul:hbox class="statuspanel-inner">
+ <xul:label class="statuspanel-label"
+ role="status"
+ aria-live="off"
+ xbl:inherits="value=label,crop,mirror"
+ flex="1"
+ crop="end"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ window.addEventListener("resize", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ window.removeEventListener("resize", this, false);
+ MousePosTracker.removeListener(this);
+ ]]></destructor>
+
+ <property name="label">
+ <setter><![CDATA[
+ if (!this.label) {
+ this.removeAttribute("mirror");
+ this.removeAttribute("sizelimit");
+ }
+
+ this.style.minWidth = this.getAttribute("type") == "status" &&
+ this.getAttribute("previoustype") == "status"
+ ? getComputedStyle(this).width : "";
+
+ if (val) {
+ this.setAttribute("label", val);
+ this.removeAttribute("inactive");
+ this._calcMouseTargetRect();
+ MousePosTracker.addListener(this);
+ } else {
+ this.setAttribute("inactive", "true");
+ MousePosTracker.removeListener(this);
+ }
+
+ return val;
+ ]]></setter>
+ <getter>
+ return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
+ </getter>
+ </property>
+
+ <method name="getMouseTargetRect">
+ <body><![CDATA[
+ return this._mouseTargetRect;
+ ]]></body>
+ </method>
+
+ <method name="onMouseEnter">
+ <body>
+ this._mirror();
+ </body>
+ </method>
+
+ <method name="onMouseLeave">
+ <body>
+ this._mirror();
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if (!this.label)
+ return;
+
+ switch (event.type) {
+ case "resize":
+ this._calcMouseTargetRect();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_calcMouseTargetRect">
+ <body><![CDATA[
+ let container = this.parentNode;
+ let alignRight = (getComputedStyle(container).direction == "rtl");
+ let panelRect = this.getBoundingClientRect();
+ let containerRect = container.getBoundingClientRect();
+
+ this._mouseTargetRect = {
+ top: panelRect.top,
+ bottom: panelRect.bottom,
+ left: alignRight ? containerRect.right - panelRect.width : containerRect.left,
+ right: alignRight ? containerRect.right : containerRect.left + panelRect.width
+ };
+ ]]></body>
+ </method>
+
+ <method name="_mirror">
+ <body>
+ if (this.hasAttribute("mirror"))
+ this.removeAttribute("mirror");
+ else
+ this.setAttribute("mirror", "true");
+
+ if (!this.hasAttribute("sizelimit")) {
+ this.setAttribute("sizelimit", "true");
+ this._calcMouseTargetRect();
+ }
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="tabbrowser-tabpanels"
+ extends="chrome://global/content/bindings/tabbox.xml#tabpanels">
+ <implementation>
+ <field name="_selectedIndex">0</field>
+
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ return this._selectedIndex;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ if (val < 0 || val >= this.childNodes.length)
+ return val;
+
+ let toTab = this.getRelatedElement(this.childNodes[val]);
+
+ gBrowser._getSwitcher().requestTab(toTab);
+
+ var panel = this._selectedPanel;
+ var newPanel = this.childNodes[val];
+ this._selectedPanel = newPanel;
+ if (this._selectedPanel != panel) {
+ var event = document.createEvent("Events");
+ event.initEvent("select", true, true);
+ this.dispatchEvent(event);
+
+ this._selectedIndex = val;
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="tabbrowser-browser"
+ extends="chrome://global/content/bindings/browser.xml#browser">
+ <implementation>
+ <field name="tabModalPromptBox">null</field>
+
+ <!-- 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[
+ var params = arguments[1];
+ if (typeof(params) == "number") {
+ params = {
+ flags: aFlags,
+ referrerURI: aReferrerURI,
+ charset: aCharset,
+ postData: aPostData,
+ };
+ }
+ _loadURIWithFlags(this, aURI, params);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="tabbrowser-remote-browser"
+ extends="chrome://global/content/bindings/remote-browser.xml#remote-browser">
+ <implementation>
+ <field name="tabModalPromptBox">null</field>
+
+ <!-- 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[
+ var params = arguments[1];
+ if (typeof(params) == "number") {
+ params = {
+ flags: aFlags,
+ referrerURI: aReferrerURI,
+ charset: aCharset,
+ postData: aPostData,
+ };
+ }
+ _loadURIWithFlags(this, aURI, params);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/test/alerts/.eslintrc.js b/browser/base/content/test/alerts/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/alerts/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/alerts/browser.ini b/browser/base/content/test/alerts/browser.ini
new file mode 100644
index 000000000..07fcf5253
--- /dev/null
+++ b/browser/base/content/test/alerts/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ head.js
+ file_dom_notifications.html
+
+[browser_notification_close.js]
+[browser_notification_do_not_disturb.js]
+[browser_notification_open_settings.js]
+[browser_notification_remove_permission.js]
+[browser_notification_permission_migration.js]
+[browser_notification_replace.js]
+[browser_notification_tab_switching.js]
diff --git a/browser/base/content/test/alerts/browser_notification_close.js b/browser/base/content/test/alerts/browser_notification_close.js
new file mode 100644
index 000000000..bbd444212
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_close.js
@@ -0,0 +1,71 @@
+"use strict";
+
+const {PlacesTestUtils} =
+ Cu.import("resource://testing-common/PlacesTestUtils.jsm", {});
+
+let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+let oldShowFavicons;
+
+add_task(function* test_notificationClose() {
+ let pm = Services.perms;
+ let notificationURI = makeURI(notificationURL);
+ pm.add(notificationURI, "desktop-notification", pm.ALLOW_ACTION);
+
+ oldShowFavicons = Services.prefs.getBoolPref("alerts.showFavicons");
+ Services.prefs.setBoolPref("alerts.showFavicons", true);
+
+ yield PlacesTestUtils.addVisits(notificationURI);
+ let faviconURI = yield new Promise(resolve => {
+ let faviconURI = makeURI("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC");
+ PlacesUtils.favicons.setAndFetchFaviconForPage(notificationURI, faviconURI,
+ true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ (faviconURI, iconSize, iconData, mimeType) => resolve(faviconURI),
+ Services.scriptSecurityManager.getSystemPrincipal());
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* dummyTabTask(aBrowser) {
+ yield openNotification(aBrowser, "showNotification2");
+
+ info("Notification alert showing");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ yield closeNotification(aBrowser);
+ return;
+ }
+
+ let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
+ is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
+ let alertTextLabel = alertWindow.document.getElementById("alertTextLabel");
+ is(alertTextLabel.textContent, "Test body 2", "Body text of notification should be present");
+ let alertIcon = alertWindow.document.getElementById("alertIcon");
+ is(alertIcon.src, faviconURI.spec, "Icon of notification should be present");
+
+ let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
+ is(alertCloseButton.localName, "toolbarbutton", "close button found");
+ let promiseBeforeUnloadEvent =
+ BrowserTestUtils.waitForEvent(alertWindow, "beforeunload");
+ let closedTime = alertWindow.Date.now();
+ alertCloseButton.click();
+ info("Clicked on close button");
+ yield promiseBeforeUnloadEvent;
+
+ ok(true, "Alert should close when the close button is clicked");
+ let currentTime = alertWindow.Date.now();
+ // The notification will self-close at 12 seconds, so this checks
+ // that the notification closed before the timeout.
+ ok(currentTime - closedTime < 5000,
+ "Close requested at " + closedTime + ", actually closed at " + currentTime);
+ });
+});
+
+add_task(function* cleanup() {
+ Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+ if (typeof oldShowFavicons == "boolean") {
+ Services.prefs.setBoolPref("alerts.showFavicons", oldShowFavicons);
+ }
+});
diff --git a/browser/base/content/test/alerts/browser_notification_do_not_disturb.js b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js
new file mode 100644
index 000000000..92c689fd2
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_do_not_disturb.js
@@ -0,0 +1,80 @@
+"use strict";
+
+var tab;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+const ALERT_SERVICE = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+
+function test () {
+ waitForExplicitFinish();
+
+ try {
+ // Only run the test if the do-not-disturb
+ // interface has been implemented.
+ ALERT_SERVICE.manualDoNotDisturb;
+ ok(true, "Alert service implements do-not-disturb interface");
+ } catch (e) {
+ ok(true, "Alert service doesn't implement do-not-disturb interface, exiting test");
+ finish();
+ return;
+ }
+
+ let pm = Services.perms;
+ registerCleanupFunction(function() {
+ ALERT_SERVICE.manualDoNotDisturb = false;
+ pm.remove(makeURI(notificationURL), "desktop-notification");
+ gBrowser.removeTab(tab);
+ window.restore();
+ });
+
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ // Make sure that do-not-disturb is not enabled.
+ ok(!ALERT_SERVICE.manualDoNotDisturb, "Alert service should not be disabled when test starts");
+ ALERT_SERVICE.manualDoNotDisturb = false;
+
+ tab = gBrowser.addTab(notificationURL);
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", onLoad, true);
+}
+
+function onLoad() {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ openNotification(tab.linkedBrowser, "showNotification2").then(onAlertShowing);
+}
+
+function onAlertShowing() {
+ info("Notification alert showing");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ closeNotification(tab.linkedBrowser).then(finish);
+ return;
+ }
+ let doNotDisturbMenuItem = alertWindow.document.getElementById("doNotDisturbMenuItem");
+ is(doNotDisturbMenuItem.localName, "menuitem", "menuitem found");
+ alertWindow.addEventListener("beforeunload", onAlertClosing);
+ doNotDisturbMenuItem.click();
+ info("Clicked on do-not-disturb menuitem");
+}
+
+function onAlertClosing(event) {
+ event.target.removeEventListener("beforeunload", onAlertClosing);
+
+ ok(ALERT_SERVICE.manualDoNotDisturb, "Alert service should be disabled after clicking menuitem");
+
+ // The notification should not appear, but there is
+ // no way from the client-side to know that it was
+ // blocked, except for waiting some time and realizing
+ // that the "onshow" event never fired.
+ openNotification(tab.linkedBrowser, "showNotification2", 2000)
+ .then(onAlert2Showing, finish);
+}
+
+function onAlert2Showing() {
+ ok(false, "the second alert should not have been shown");
+ closeNotification(tab.linkedBrowser).then(finish);
+}
diff --git a/browser/base/content/test/alerts/browser_notification_open_settings.js b/browser/base/content/test/alerts/browser_notification_open_settings.js
new file mode 100644
index 000000000..5306fd90a
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_open_settings.js
@@ -0,0 +1,58 @@
+"use strict";
+
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+add_task(function* test_settingsOpen_observer() {
+ info("Opening a dummy tab so openPreferences=>switchToTabHavingURI doesn't use the blank tab.");
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:robots"
+ }, function* dummyTabTask(aBrowser) {
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content");
+ info("simulate a notifications-open-settings notification");
+ let uri = NetUtil.newURI("https://example.com");
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ Services.obs.notifyObservers(principal, "notifications-open-settings", null);
+ let tab = yield tabPromise;
+ ok(tab, "The notification settings tab opened");
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* test_settingsOpen_button() {
+ let pm = Services.perms;
+ info("Adding notification permission");
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ try {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* tabTask(aBrowser) {
+ info("Waiting for notification");
+ yield openNotification(aBrowser, "showNotification2");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ yield closeNotification(aBrowser);
+ return;
+ }
+
+ let closePromise = promiseWindowClosed(alertWindow);
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content");
+ let openSettingsMenuItem = alertWindow.document.getElementById("openSettingsMenuItem");
+ openSettingsMenuItem.click();
+
+ info("Waiting for notification settings tab");
+ let tab = yield tabPromise;
+ ok(tab, "The notification settings tab opened");
+
+ yield closePromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+ } finally {
+ info("Removing notification permission");
+ pm.remove(makeURI(notificationURL), "desktop-notification");
+ }
+});
diff --git a/browser/base/content/test/alerts/browser_notification_permission_migration.js b/browser/base/content/test/alerts/browser_notification_permission_migration.js
new file mode 100644
index 000000000..b015e59a7
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_permission_migration.js
@@ -0,0 +1,45 @@
+const UI_VERSION = 32;
+
+var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"]
+ .getService(Ci.nsIObserver);
+var notificationURI = makeURI("http://example.org");
+var pm = Services.perms;
+var currentUIVersion;
+
+add_task(function* setup() {
+ currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
+ Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
+ pm.add(notificationURI, "desktop-notification", pm.ALLOW_ACTION);
+});
+
+add_task(function* test_permissionMigration() {
+ if ("@mozilla.org/system-alerts-service;1" in Cc) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ return;
+ }
+
+ info("Waiting for migration notification");
+ let alertWindowPromise = promiseAlertWindow();
+ gBrowserGlue.observe(null, "browser-glue-test", "force-ui-migration");
+ let alertWindow = yield alertWindowPromise;
+
+ info("Clicking on notification");
+ let url =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "push#w_upgraded-notifications";
+ let closePromise = promiseWindowClosed(alertWindow);
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow);
+
+ info("Waiting for migration info tab");
+ let tab = yield tabPromise;
+ ok(tab, "The migration info tab opened");
+
+ yield closePromise;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* cleanup() {
+ Services.prefs.setIntPref("browser.migration.version", currentUIVersion);
+ pm.remove(notificationURI, "desktop-notification");
+});
diff --git a/browser/base/content/test/alerts/browser_notification_remove_permission.js b/browser/base/content/test/alerts/browser_notification_remove_permission.js
new file mode 100644
index 000000000..bd36faeae
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_remove_permission.js
@@ -0,0 +1,72 @@
+"use strict";
+
+var tab;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+var alertWindowClosed = false;
+var permRemoved = false;
+
+function test () {
+ waitForExplicitFinish();
+
+ let pm = Services.perms;
+ registerCleanupFunction(function() {
+ pm.remove(makeURI(notificationURL), "desktop-notification");
+ gBrowser.removeTab(tab);
+ window.restore();
+ });
+
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ tab = gBrowser.addTab(notificationURL);
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", onLoad, true);
+}
+
+function onLoad() {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ openNotification(tab.linkedBrowser, "showNotification2").then(onAlertShowing);
+}
+
+function onAlertShowing() {
+ info("Notification alert showing");
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ closeNotification(tab.linkedBrowser).then(finish);
+ return;
+ }
+ ok(Services.perms.testExactPermission(makeURI(notificationURL), "desktop-notification"),
+ "Permission should exist prior to removal");
+ let disableForOriginMenuItem = alertWindow.document.getElementById("disableForOriginMenuItem");
+ is(disableForOriginMenuItem.localName, "menuitem", "menuitem found");
+ Services.obs.addObserver(permObserver, "perm-changed", false);
+ alertWindow.addEventListener("beforeunload", onAlertClosing);
+ disableForOriginMenuItem.click();
+ info("Clicked on disable-for-origin menuitem")
+}
+
+function permObserver(subject, topic, data) {
+ if (topic != "perm-changed") {
+ return;
+ }
+
+ let permission = subject.QueryInterface(Ci.nsIPermission);
+ is(permission.type, "desktop-notification", "desktop-notification permission changed");
+ is(data, "deleted", "desktop-notification permission deleted");
+
+ Services.obs.removeObserver(permObserver, "perm-changed");
+ permRemoved = true;
+ if (alertWindowClosed) {
+ finish();
+ }
+}
+
+function onAlertClosing(event) {
+ event.target.removeEventListener("beforeunload", onAlertClosing);
+
+ alertWindowClosed = true;
+ if (permRemoved) {
+ finish();
+ }
+}
diff --git a/browser/base/content/test/alerts/browser_notification_replace.js b/browser/base/content/test/alerts/browser_notification_replace.js
new file mode 100644
index 000000000..e678dc438
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_replace.js
@@ -0,0 +1,38 @@
+"use strict";
+
+let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+add_task(function* test_notificationReplace() {
+ let pm = Services.perms;
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* dummyTabTask(aBrowser) {
+ yield ContentTask.spawn(aBrowser, {}, function* () {
+ let win = content.window.wrappedJSObject;
+ let notification = win.showNotification1();
+ let promiseCloseEvent = ContentTaskUtils.waitForEvent(notification, "close");
+
+ let showEvent = yield ContentTaskUtils.waitForEvent(notification, "show");
+ Assert.equal(showEvent.target.body, "Test body 1", "Showed tagged notification");
+
+ let newNotification = win.showNotification2();
+ let newShowEvent = yield ContentTaskUtils.waitForEvent(newNotification, "show");
+ Assert.equal(newShowEvent.target.body, "Test body 2", "Showed new notification with same tag");
+
+ let closeEvent = yield promiseCloseEvent;
+ Assert.equal(closeEvent.target.body, "Test body 1", "Closed previous tagged notification");
+
+ let promiseNewCloseEvent = ContentTaskUtils.waitForEvent(newNotification, "close");
+ newNotification.close();
+ let newCloseEvent = yield promiseNewCloseEvent;
+ Assert.equal(newCloseEvent.target.body, "Test body 2", "Closed new notification");
+ });
+ });
+});
+
+add_task(function* cleanup() {
+ Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+});
diff --git a/browser/base/content/test/alerts/browser_notification_tab_switching.js b/browser/base/content/test/alerts/browser_notification_tab_switching.js
new file mode 100644
index 000000000..7e46c0722
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_tab_switching.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+var tab;
+var notification;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+var newWindowOpenedFromTab;
+
+add_task(function* test_notificationPreventDefaultAndSwitchTabs() {
+ let pm = Services.perms;
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ let originalTab = gBrowser.selectedTab;
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: notificationURL
+ }, function* dummyTabTask(aBrowser) {
+ // Put new tab in background so it is obvious when it is re-focused.
+ yield BrowserTestUtils.switchTab(gBrowser, originalTab);
+ isnot(gBrowser.selectedBrowser, aBrowser, "Notification page loaded as a background tab");
+
+ // First, show a notification that will be have the tab-switching prevented.
+ function promiseNotificationEvent(evt) {
+ return ContentTask.spawn(aBrowser, evt, function* (evt) {
+ return yield new Promise(resolve => {
+ let notification = content.wrappedJSObject._notification;
+ notification.addEventListener(evt, function l(event) {
+ notification.removeEventListener(evt, l);
+ resolve({ defaultPrevented: event.defaultPrevented });
+ });
+ });
+ });
+ }
+ yield openNotification(aBrowser, "showNotification1");
+ info("Notification alert showing");
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ yield closeNotification(aBrowser);
+ return;
+ }
+ info("Clicking on notification");
+ let promiseClickEvent = promiseNotificationEvent("click");
+
+ // NB: This executeSoon is needed to allow the non-e10s runs of this test
+ // a chance to set the event listener on the page. Otherwise, we
+ // synchronously fire the click event before we listen for the event.
+ executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"),
+ {}, alertWindow);
+ });
+ let clickEvent = yield promiseClickEvent;
+ ok(clickEvent.defaultPrevented, "The event handler for the first notification cancels the event");
+ isnot(gBrowser.selectedBrowser, aBrowser, "Notification page still a background tab");
+ let notificationClosed = promiseNotificationEvent("close");
+ yield closeNotification(aBrowser);
+ yield notificationClosed;
+
+ // Second, show a notification that will cause the tab to get switched.
+ yield openNotification(aBrowser, "showNotification2");
+ alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ let promiseTabSelect = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabSelect");
+ EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"),
+ {},
+ alertWindow);
+ yield promiseTabSelect;
+ is(gBrowser.selectedBrowser.currentURI.spec, notificationURL,
+ "Clicking on the second notification should select its originating tab");
+ notificationClosed = promiseNotificationEvent("close");
+ yield closeNotification(aBrowser);
+ yield notificationClosed;
+ });
+});
+
+add_task(function* cleanup() {
+ Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+});
diff --git a/browser/base/content/test/alerts/file_dom_notifications.html b/browser/base/content/test/alerts/file_dom_notifications.html
new file mode 100644
index 000000000..6deede8fc
--- /dev/null
+++ b/browser/base/content/test/alerts/file_dom_notifications.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+"use strict";
+
+function showNotification1() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body 1",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ var n = new Notification("Test title", options);
+ n.addEventListener("click", function(event) {
+ event.preventDefault();
+ });
+ return n;
+}
+
+function showNotification2() {
+ var options = {
+ dir: undefined,
+ lang: undefined,
+ body: "Test body 2",
+ tag: "Test tag",
+ icon: undefined,
+ };
+ return new Notification("Test title", options);
+}
+</script>
+</head>
+<body>
+<form id="notificationForm" onsubmit="showNotification();">
+ <input type="submit" value="Show notification" id="submit"/>
+</form>
+</body>
+</html>
diff --git a/browser/base/content/test/alerts/head.js b/browser/base/content/test/alerts/head.js
new file mode 100644
index 000000000..21257de31
--- /dev/null
+++ b/browser/base/content/test/alerts/head.js
@@ -0,0 +1,71 @@
+function promiseAlertWindow() {
+ return new Promise(function(resolve) {
+ let listener = {
+ onOpenWindow(window) {
+ let alertWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ alertWindow.addEventListener("load", function onLoad() {
+ alertWindow.removeEventListener("load", onLoad);
+ let windowType = alertWindow.document.documentElement.getAttribute("windowtype");
+ if (windowType != "alert:alert") {
+ return;
+ }
+ Services.wm.removeListener(listener);
+ resolve(alertWindow);
+ });
+ },
+ };
+ Services.wm.addListener(listener);
+ });
+}
+
+/**
+ * Similar to `BrowserTestUtils.closeWindow`, but
+ * doesn't call `window.close()`.
+ */
+function promiseWindowClosed(window) {
+ return new Promise(function(resolve) {
+ Services.ww.registerNotification(function observer(subject, topic, data) {
+ if (topic == "domwindowclosed" && subject == window) {
+ Services.ww.unregisterNotification(observer);
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * These two functions work with file_dom_notifications.html to open the
+ * notification and close it.
+ *
+ * |fn| can be showNotification1 or showNotification2.
+ * if |timeout| is passed, then the promise returned from this function is
+ * rejected after the requested number of miliseconds.
+ */
+function openNotification(aBrowser, fn, timeout) {
+ return ContentTask.spawn(aBrowser, { fn, timeout }, function* ({ fn, timeout }) {
+ let win = content.wrappedJSObject;
+ let notification = win[fn]();
+ win._notification = notification;
+ yield new Promise((resolve, reject) => {
+ function listener() {
+ notification.removeEventListener("show", listener);
+ resolve();
+ }
+
+ notification.addEventListener("show", listener);
+
+ if (timeout) {
+ content.setTimeout(() => {
+ notification.removeEventListener("show", listener);
+ reject("timed out");
+ }, timeout);
+ }
+ });
+ });
+}
+
+function closeNotification(aBrowser) {
+ return ContentTask.spawn(aBrowser, null, function() {
+ content.wrappedJSObject._notification.close();
+ });
+}
diff --git a/browser/base/content/test/captivePortal/browser.ini b/browser/base/content/test/captivePortal/browser.ini
new file mode 100644
index 000000000..cfdbc5c2f
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_CaptivePortalWatcher.js]
+skip-if = os == "win" # Bug 1313894
+[browser_CaptivePortalWatcher_1.js]
+skip-if = os == "win" # Bug 1313894
+[browser_captivePortal_certErrorUI.js]
diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
new file mode 100644
index 000000000..e9c0fad6d
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
@@ -0,0 +1,119 @@
+"use strict";
+
+add_task(setupPrefsAndRecentWindowBehavior);
+
+// Each of the test cases below is run twice: once for login-success and once
+// for login-abort (aSuccess set to true and false respectively).
+let testCasesForBothSuccessAndAbort = [
+ /**
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
+ * The portal tab should be added and focused when the window is
+ * opened, and closed automatically when the success event is fired.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
+ */
+ function* test_detectedWithNoBrowserWindow_Open(aSuccess) {
+ yield portalDetected();
+ let win = yield focusWindowAndWaitForPortalUI();
+ yield freePortal(aSuccess);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * A portal is detected when multiple browser windows are open but none
+ * have focus. A brower window is focused, then the portal is freed.
+ * The portal tab should be added and focused when the window is
+ * focused, and closed automatically when the success event is fired.
+ * The captive portal notification should be shown in all windows upon
+ * detection, and closed automatically when the success event is fired.
+ */
+ function* test_detectedWithNoBrowserWindow_Focused(aSuccess) {
+ let win1 = yield openWindowAndWaitForFocus();
+ let win2 = yield openWindowAndWaitForFocus();
+ // Defocus both windows.
+ yield SimpleTest.promiseFocus(window);
+
+ yield portalDetected();
+
+ // Notification should be shown in both windows.
+ ensurePortalNotification(win1);
+ ensureNoPortalTab(win1);
+ ensurePortalNotification(win2);
+ ensureNoPortalTab(win2);
+
+ yield focusWindowAndWaitForPortalUI(false, win2);
+
+ yield freePortal(aSuccess);
+
+ ensureNoPortalNotification(win1);
+ ensureNoPortalTab(win2);
+ ensureNoPortalNotification(win2);
+
+ yield closeWindowAndWaitForXulWindowVisible(win2);
+ // No need to wait for xul-window-visible: after win2 is closed, focus
+ // is restored to the default window and win1 remains in the background.
+ yield BrowserTestUtils.closeWindow(win1);
+ },
+
+ /**
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
+ * The recheck triggered when the browser window is opened takes a
+ * long time. No portal tab should be added.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
+ */
+ function* test_detectedWithNoBrowserWindow_LongRecheck(aSuccess) {
+ yield portalDetected();
+ let win = yield focusWindowAndWaitForPortalUI(true);
+ yield freePortal(aSuccess);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * A portal is detected when there's no browser window, and the
+ * portal is freed before a browser window is opened. No portal
+ * UI should be shown when a browser window is opened.
+ */
+ function* test_detectedWithNoBrowserWindow_GoneBeforeOpen(aSuccess) {
+ yield portalDetected();
+ yield freePortal(aSuccess);
+ let win = yield openWindowAndWaitForFocus();
+ // Wait for a while to make sure no UI is shown.
+ yield new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * A portal is detected when a browser window has focus. No portal tab should
+ * be opened. A notification bar should be displayed in all browser windows.
+ */
+ function* test_detectedWithFocus(aSuccess) {
+ let win1 = yield openWindowAndWaitForFocus();
+ let win2 = yield openWindowAndWaitForFocus();
+ yield portalDetected();
+ ensureNoPortalTab(win1);
+ ensureNoPortalTab(win2);
+ ensurePortalNotification(win1);
+ ensurePortalNotification(win2);
+ yield freePortal(aSuccess);
+ ensureNoPortalNotification(win1);
+ ensureNoPortalNotification(win2);
+ yield closeWindowAndWaitForXulWindowVisible(win2);
+ yield closeWindowAndWaitForXulWindowVisible(win1);
+ },
+];
+
+for (let testcase of testCasesForBothSuccessAndAbort) {
+ add_task(testcase.bind(null, true));
+ add_task(testcase.bind(null, false));
+}
diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js
new file mode 100644
index 000000000..71b12c32a
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js
@@ -0,0 +1,91 @@
+"use strict";
+
+add_task(setupPrefsAndRecentWindowBehavior);
+
+let testcases = [
+ /**
+ * A portal is detected when there's no browser window,
+ * then a browser window is opened, and the portal is logged into
+ * and redirects to a different page. The portal tab should be added
+ * and focused when the window is opened, and left open after login
+ * since it redirected.
+ */
+ function* test_detectedWithNoBrowserWindow_Redirect() {
+ yield portalDetected();
+ let win = yield focusWindowAndWaitForPortalUI();
+ let browser = win.gBrowser.selectedTab.linkedBrowser;
+ let loadPromise =
+ BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
+ BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
+ yield loadPromise;
+ yield freePortal(true);
+ ensurePortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * Test the various expected behaviors of the "Show Login Page" button
+ * in the captive portal notification. The button should be visible for
+ * all tabs except the captive portal tab, and when clicked, should
+ * ensure a captive portal tab is open and select it.
+ */
+ function* test_showLoginPageButton() {
+ let win = yield openWindowAndWaitForFocus();
+ yield portalDetected();
+ let notification = ensurePortalNotification(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+
+ function testPortalTabSelectedAndButtonNotVisible() {
+ is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+ testShowLoginPageButtonVisibility(notification, "hidden");
+ }
+
+ let button = notification.querySelector("button.notification-button");
+ function* clickButtonAndExpectNewPortalTab() {
+ let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
+ button.click();
+ let tab = yield p;
+ is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+ return tab;
+ }
+
+ // Simulate clicking the button. The portal tab should be opened and
+ // selected and the button should hide.
+ let tab = yield clickButtonAndExpectNewPortalTab();
+ testPortalTabSelectedAndButtonNotVisible();
+
+ // Close the tab. The button should become visible.
+ yield BrowserTestUtils.removeTab(tab);
+ ensureNoPortalTab(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+
+ // When the button is clicked, a new portal tab should be opened and
+ // selected.
+ tab = yield clickButtonAndExpectNewPortalTab();
+
+ // Open another arbitrary tab. The button should become visible. When it's clicked,
+ // the portal tab should be selected.
+ let anotherTab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+ testShowLoginPageButtonVisibility(notification, "visible");
+ button.click();
+ is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+
+ // Close the portal tab and select the arbitrary tab. The button should become
+ // visible and when it's clicked, a new portal tab should be opened.
+ yield BrowserTestUtils.removeTab(tab);
+ win.gBrowser.selectedTab = anotherTab;
+ testShowLoginPageButtonVisibility(notification, "visible");
+ tab = yield clickButtonAndExpectNewPortalTab();
+
+ yield BrowserTestUtils.removeTab(anotherTab);
+ yield freePortal(true);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+];
+
+for (let testcase of testcases) {
+ add_task(testcase);
+}
diff --git a/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
new file mode 100644
index 000000000..6b97e19a3
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BAD_CERT_PAGE = "https://expired.example.com/";
+
+// This tests the alternate cert error UI when we are behind a captive portal.
+
+add_task(function* checkCaptivePortalCertErrorUI() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["captivedetect.canonicalURL", CANONICAL_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT]],
+ });
+
+ let captivePortalStatePropagated = TestUtils.topicObserved("ipc:network:captive-portal-set-state");
+
+ info("Checking that the alternate about:certerror UI is shown when we are behind a captive portal.");
+ Services.obs.notifyObservers(null, "captive-portal-login", null);
+
+ info("Waiting for captive portal state to be propagated to the content process.");
+ yield captivePortalStatePropagated;
+
+ // Open a page with a cert error.
+ let browser;
+ let certErrorLoaded;
+ let errorTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ let tab = gBrowser.addTab(BAD_CERT_PAGE);
+ gBrowser.selectedTab = tab;
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ return tab;
+ }, false);
+
+ info("Waiting for cert error page to load.")
+ yield certErrorLoaded;
+
+ let portalTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, CANONICAL_URL);
+
+ yield ContentTask.spawn(browser, null, () => {
+ let doc = content.document;
+ ok(doc.body.classList.contains("captiveportal"),
+ "Captive portal error page UI is visible.");
+
+ info("Clicking the Open Login Page button.");
+ let loginButton = doc.getElementById("openPortalLoginPageButton");
+ is(loginButton.getAttribute("autofocus"), "true", "openPortalLoginPageButton has autofocus");
+ loginButton.click();
+ });
+
+ let portalTab = yield portalTabPromise;
+ is(gBrowser.selectedTab, portalTab, "Login page should be open in a new foreground tab.");
+
+ // Make sure clicking the "Open Login Page" button again focuses the existing portal tab.
+ yield BrowserTestUtils.switchTab(gBrowser, errorTab);
+ // Passing an empty function to BrowserTestUtils.switchTab lets us wait for an arbitrary
+ // tab switch.
+ portalTabPromise = BrowserTestUtils.switchTab(gBrowser, () => {});
+ yield ContentTask.spawn(browser, null, () => {
+ info("Clicking the Open Login Page button.");
+ content.document.getElementById("openPortalLoginPageButton").click();
+ });
+
+ let portalTab2 = yield portalTabPromise;
+ is(portalTab2, portalTab, "The existing portal tab should be focused.");
+
+ let portalTabRemoved = BrowserTestUtils.removeTab(portalTab, {dontRemove: true});
+ let errorTabReloaded = waitForCertErrorLoad(browser);
+
+ Services.obs.notifyObservers(null, "captive-portal-login-success", null);
+ yield portalTabRemoved;
+
+ info("Waiting for error tab to be reloaded after the captive portal was freed.");
+ yield errorTabReloaded;
+ yield ContentTask.spawn(browser, null, () => {
+ let doc = content.document;
+ ok(!doc.body.classList.contains("captiveportal"),
+ "Captive portal error page UI is not visible.");
+ });
+
+ yield BrowserTestUtils.removeTab(errorTab);
+});
diff --git a/browser/base/content/test/captivePortal/head.js b/browser/base/content/test/captivePortal/head.js
new file mode 100644
index 000000000..e40b5a325
--- /dev/null
+++ b/browser/base/content/test/captivePortal/head.js
@@ -0,0 +1,181 @@
+Components.utils.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CaptivePortalWatcher",
+ "resource:///modules/CaptivePortalWatcher.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cps",
+ "@mozilla.org/network/captive-portal-service;1",
+ "nsICaptivePortalService");
+
+const CANONICAL_CONTENT = "success";
+const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT;
+const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected";
+const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
+
+function* setupPrefsAndRecentWindowBehavior() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["captivedetect.canonicalURL", CANONICAL_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT]],
+ });
+ // We need to test behavior when a portal is detected when there is no browser
+ // window, but we can't close the default window opened by the test harness.
+ // Instead, we deactivate CaptivePortalWatcher in the default window and
+ // exclude it from RecentWindow.getMostRecentBrowserWindow in an attempt to
+ // mask its presence.
+ window.CaptivePortalWatcher.uninit();
+ let getMostRecentBrowserWindowCopy = RecentWindow.getMostRecentBrowserWindow;
+ let defaultWindow = window;
+ RecentWindow.getMostRecentBrowserWindow = () => {
+ let win = getMostRecentBrowserWindowCopy();
+ if (win == defaultWindow) {
+ return null;
+ }
+ return win;
+ };
+
+ registerCleanupFunction(function* cleanUp() {
+ RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindowCopy;
+ window.CaptivePortalWatcher.init();
+ });
+}
+
+function* portalDetected() {
+ Services.obs.notifyObservers(null, "captive-portal-login", null);
+ yield BrowserTestUtils.waitForCondition(() => {
+ return cps.state == cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal detected.");
+}
+
+function* freePortal(aSuccess) {
+ Services.obs.notifyObservers(null,
+ "captive-portal-login-" + (aSuccess ? "success" : "abort"), null);
+ yield BrowserTestUtils.waitForCondition(() => {
+ return cps.state != cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal freed.");
+}
+
+// If a window is provided, it will be focused. Otherwise, a new window
+// will be opened and focused.
+function* focusWindowAndWaitForPortalUI(aLongRecheck, win) {
+ // CaptivePortalWatcher triggers a recheck when a window gains focus. If
+ // the time taken for the check to complete is under PORTAL_RECHECK_DELAY_MS,
+ // a tab with the login page is opened and selected. If it took longer,
+ // no tab is opened. It's not reliable to time things in an async test,
+ // so use a delay threshold of -1 to simulate a long recheck (so that any
+ // amount of time is considered excessive), and a very large threshold to
+ // simulate a short recheck.
+ Preferences.set("captivedetect.portalRecheckDelayMS", aLongRecheck ? -1 : 1000000);
+
+ if (!win) {
+ win = yield BrowserTestUtils.openNewBrowserWindow();
+ }
+ yield SimpleTest.promiseFocus(win);
+
+ // After a new window is opened, CaptivePortalWatcher asks for a recheck, and
+ // waits for it to complete. We need to manually tell it a recheck completed.
+ yield BrowserTestUtils.waitForCondition(() => {
+ return win.CaptivePortalWatcher._waitingForRecheck;
+ }, "Waiting for CaptivePortalWatcher to trigger a recheck.");
+ Services.obs.notifyObservers(null, "captive-portal-check-complete", null);
+
+ let notification = ensurePortalNotification(win);
+
+ if (aLongRecheck) {
+ ensureNoPortalTab(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+ return win;
+ }
+
+ let tab = win.gBrowser.tabs[1];
+ if (tab.linkedBrowser.currentURI.spec != CANONICAL_URL) {
+ // The tab should load the canonical URL, wait for it.
+ yield BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL);
+ }
+ is(win.gBrowser.selectedTab, tab,
+ "The captive portal tab should be open and selected in the new window.");
+ testShowLoginPageButtonVisibility(notification, "hidden");
+ return win;
+}
+
+function ensurePortalTab(win) {
+ // For the tests that call this function, it's enough to ensure there
+ // are two tabs in the window - the default tab and the portal tab.
+ is(win.gBrowser.tabs.length, 2,
+ "There should be a captive portal tab in the window.");
+}
+
+function ensurePortalNotification(win) {
+ let notificationBox =
+ win.document.getElementById("high-priority-global-notificationbox");
+ let notification = notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE)
+ isnot(notification, null,
+ "There should be a captive portal notification in the window.");
+ return notification;
+}
+
+// Helper to test whether the "Show Login Page" is visible in the captive portal
+// notification (it should be hidden when the portal tab is selected).
+function testShowLoginPageButtonVisibility(notification, visibility) {
+ let showLoginPageButton = notification.querySelector("button.notification-button");
+ // If the visibility property was never changed from default, it will be
+ // an empty string, so we pretend it's "visible" (effectively the same).
+ is(showLoginPageButton.style.visibility || "visible", visibility,
+ "The \"Show Login Page\" button should be " + visibility + ".");
+}
+
+function ensureNoPortalTab(win) {
+ is(win.gBrowser.tabs.length, 1,
+ "There should be no captive portal tab in the window.");
+}
+
+function ensureNoPortalNotification(win) {
+ let notificationBox =
+ win.document.getElementById("high-priority-global-notificationbox");
+ is(notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE), null,
+ "There should be no captive portal notification in the window.");
+}
+
+/**
+ * Some tests open a new window and close it later. When the window is closed,
+ * the original window opened by mochitest gains focus, generating a
+ * xul-window-visible notification. If the next test also opens a new window
+ * before this notification has a chance to fire, CaptivePortalWatcher picks
+ * up the first one instead of the one from the new window. To avoid this
+ * unfortunate intermittent timing issue, we wait for the notification from
+ * the original window every time we close a window that we opened.
+ */
+function waitForXulWindowVisible() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observe() {
+ Services.obs.removeObserver(observe, "xul-window-visible");
+ resolve();
+ }, "xul-window-visible", false);
+ });
+}
+
+function* closeWindowAndWaitForXulWindowVisible(win) {
+ let p = waitForXulWindowVisible();
+ yield BrowserTestUtils.closeWindow(win);
+ yield p;
+}
+
+/**
+ * BrowserTestUtils.openNewBrowserWindow() does not guarantee the newly
+ * opened window has received focus when the promise resolves, so we
+ * have to manually wait every time.
+ */
+function* openWindowAndWaitForFocus() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield SimpleTest.promiseFocus(win);
+ return win;
+}
+
+function waitForCertErrorLoad(browser) {
+ return new Promise(resolve => {
+ info("Waiting for DOMContentLoaded event");
+ browser.addEventListener("DOMContentLoaded", function load() {
+ browser.removeEventListener("DOMContentLoaded", load, false, true);
+ resolve();
+ }, false, true);
+ });
+}
diff --git a/browser/base/content/test/chrome/.eslintrc.js b/browser/base/content/test/chrome/.eslintrc.js
new file mode 100644
index 000000000..8c0f4f574
--- /dev/null
+++ b/browser/base/content/test/chrome/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/chrome/chrome.ini b/browser/base/content/test/chrome/chrome.ini
new file mode 100644
index 000000000..15035fc0c
--- /dev/null
+++ b/browser/base/content/test/chrome/chrome.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_aboutCrashed.xul]
diff --git a/browser/base/content/test/chrome/test_aboutCrashed.xul b/browser/base/content/test/chrome/test_aboutCrashed.xul
new file mode 100644
index 000000000..7a68076f1
--- /dev/null
+++ b/browser/base/content/test/chrome/test_aboutCrashed.xul
@@ -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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <iframe type="content" id="frame1"/>
+ <iframe type="content" id="frame2" onload="doTest()"/>
+ <script type="application/javascript"><![CDATA[
+ const Ci = Components.interfaces;
+ const Cu = Components.utils;
+
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource://gre/modules/Task.jsm");
+ Cu.import("resource://gre/modules/Promise.jsm");
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Load error pages do not fire "load" events, so let's use a progressListener.
+ function waitForErrorPage(frame) {
+ let errorPageDeferred = Promise.defer();
+
+ let progressListener = {
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+ frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress)
+ .removeProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION);
+
+ errorPageDeferred.resolve();
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference])
+ };
+
+ frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress)
+ .addProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION);
+
+ return errorPageDeferred.promise;
+ }
+
+ function doTest() {
+ Task.spawn(function test_aboutCrashed() {
+ let frame1 = document.getElementById("frame1");
+ let frame2 = document.getElementById("frame2");
+ let uri1 = Services.io.newURI("http://www.example.com/1", null, null);
+ let uri2 = Services.io.newURI("http://www.example.com/2", null, null);
+
+ let errorPageReady = waitForErrorPage(frame1);
+ frame1.docShell.chromeEventHandler.setAttribute("crashedPageTitle", "pageTitle");
+ frame1.docShell.displayLoadError(Components.results.NS_ERROR_CONTENT_CRASHED, uri1, null);
+
+ yield errorPageReady;
+ frame1.docShell.chromeEventHandler.removeAttribute("crashedPageTitle");
+
+ SimpleTest.is(frame1.contentDocument.documentURI,
+ "about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/1&c=UTF-8&f=regular&d=pageTitle",
+ "Correct about:tabcrashed displayed for page with title.");
+
+ errorPageReady = waitForErrorPage(frame2);
+ frame2.docShell.displayLoadError(Components.results.NS_ERROR_CONTENT_CRASHED, uri2, null);
+
+ yield errorPageReady;
+
+ SimpleTest.is(frame2.contentDocument.documentURI,
+ "about:tabcrashed?e=tabcrashed&u=http%3A//www.example.com/2&c=UTF-8&f=regular&d=%20",
+ "Correct about:tabcrashed displayed for page with no title.");
+
+ SimpleTest.finish();
+ });
+ }
+ ]]></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" />
+</window>
diff --git a/browser/base/content/test/general/.eslintrc.js b/browser/base/content/test/general/.eslintrc.js
new file mode 100644
index 000000000..11abd6140
--- /dev/null
+++ b/browser/base/content/test/general/.eslintrc.js
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js",
+ "../../../../../testing/mochitest/mochitest.eslintrc.js",
+ ]
+};
diff --git a/browser/base/content/test/general/POSTSearchEngine.xml b/browser/base/content/test/general/POSTSearchEngine.xml
new file mode 100644
index 000000000..30567d92f
--- /dev/null
+++ b/browser/base/content/test/general/POSTSearchEngine.xml
@@ -0,0 +1,6 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>POST Search</ShortName>
+ <Url type="text/html" method="POST" template="http://mochi.test:8888/browser/browser/base/content/test/general/print_postdata.sjs">
+ <Param name="searchterms" value="{searchTerms}"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/browser/base/content/test/general/aboutHome_content_script.js b/browser/base/content/test/general/aboutHome_content_script.js
new file mode 100644
index 000000000..28d0e617e
--- /dev/null
+++ b/browser/base/content/test/general/aboutHome_content_script.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+addMessageListener("AboutHome:SearchTriggered", function (msg) {
+ sendAsyncMessage("AboutHomeTest:CheckRecordedSearch", msg.data);
+});
diff --git a/browser/base/content/test/general/accounts_testRemoteCommands.html b/browser/base/content/test/general/accounts_testRemoteCommands.html
new file mode 100644
index 000000000..517317aff
--- /dev/null
+++ b/browser/base/content/test/general/accounts_testRemoteCommands.html
@@ -0,0 +1,83 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+
+<script type="text/javascript;version=1.8">
+
+function init() {
+ window.addEventListener("message", function process(e) {doTest(e)}, false);
+ // unless we relinquish the eventloop,
+ // tests will run before the chrome event handlers are ready
+ setTimeout(doTest, 0);
+}
+
+function checkStatusValue(payload, expectedValue) {
+ return payload.status == expectedValue;
+}
+
+let tests = [
+{
+ info: "Check account log in",
+ event: "login",
+ data: {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: true
+ },
+ payloadType: "message",
+ validateResponse: function(payload) {
+ return checkStatusValue(payload, "login");
+ },
+},
+];
+
+let currentTest = -1;
+function doTest(evt) {
+ if (evt) {
+ if (currentTest < 0 || !evt.data.content)
+ return; // not yet testing
+
+ let test = tests[currentTest];
+ if (evt.data.type != test.payloadType)
+ return; // skip unrequested events
+
+ let error = JSON.stringify(evt.data.content);
+ let pass = false;
+ try {
+ pass = test.validateResponse(evt.data.content)
+ } catch (e) {}
+ reportResult(test.info, pass, error);
+ }
+ // start the next test if there are any left
+ if (tests[++currentTest])
+ sendToBrowser(tests[currentTest].event, tests[currentTest].data);
+ else
+ reportFinished();
+}
+
+function reportResult(info, pass, error) {
+ let data = {type: "testResult", info: info, pass: pass, error: error};
+ let event = new CustomEvent("FirefoxAccountsTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function reportFinished(cmd) {
+ let data = {type: "testsComplete", count: tests.length};
+ let event = new CustomEvent("FirefoxAccountsTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function sendToBrowser(type, data) {
+ let event = new CustomEvent("FirefoxAccountsCommand", {detail: {command: type, data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+</script>
+ </head>
+ <body onload="init()">
+ </body>
+</html>
diff --git a/browser/base/content/test/general/alltabslistener.html b/browser/base/content/test/general/alltabslistener.html
new file mode 100644
index 000000000..166c31037
--- /dev/null
+++ b/browser/base/content/test/general/alltabslistener.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Test page for bug 463387</title>
+</head>
+<body>
+<p>Test page for bug 463387</p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/app_bug575561.html b/browser/base/content/test/general/app_bug575561.html
new file mode 100644
index 000000000..a60c7c87e
--- /dev/null
+++ b/browser/base/content/test/general/app_bug575561.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575561
+-->
+ <head>
+ <title>Test for links in app tabs</title>
+ </head>
+ <body>
+ <a href="http://example.com/browser/browser/base/content/test/general/dummy_page.html">same domain</a>
+ <a href="http://test1.example.com/browser/browser/base/content/test/general/dummy_page.html">same domain (different subdomain)</a>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html">different domain</a>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html" target="foo">different domain (with target)</a>
+ <a href="http://www.example.com/browser/browser/base/content/test/general/dummy_page.html">same domain (www prefix)</a>
+ <a href="data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>">data: URI</a>
+ <iframe src="app_subframe_bug575561.html"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/app_subframe_bug575561.html b/browser/base/content/test/general/app_subframe_bug575561.html
new file mode 100644
index 000000000..8690497ff
--- /dev/null
+++ b/browser/base/content/test/general/app_subframe_bug575561.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575561
+-->
+ <head>
+ <title>Test for links in app tab subframes</title>
+ </head>
+ <body>
+ <a href="http://example.org/browser/browser/base/content/test/general/dummy_page.html">different domain</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/audio.ogg b/browser/base/content/test/general/audio.ogg
new file mode 100644
index 000000000..477544875
--- /dev/null
+++ b/browser/base/content/test/general/audio.ogg
Binary files differ
diff --git a/browser/base/content/test/general/benignPage.html b/browser/base/content/test/general/benignPage.html
new file mode 100644
index 000000000..8e9429acd
--- /dev/null
+++ b/browser/base/content/test/general/benignPage.html
@@ -0,0 +1,12 @@
+<!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 dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <iframe src="http://not-tracking.example.com/"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini
new file mode 100644
index 000000000..96e591ffe
--- /dev/null
+++ b/browser/base/content/test/general/browser.ini
@@ -0,0 +1,494 @@
+[DEFAULT]
+support-files =
+ POSTSearchEngine.xml
+ accounts_testRemoteCommands.html
+ alltabslistener.html
+ app_bug575561.html
+ app_subframe_bug575561.html
+ aboutHome_content_script.js
+ audio.ogg
+ browser_bug479408_sample.html
+ browser_bug678392-1.html
+ browser_bug678392-2.html
+ browser_bug970746.xhtml
+ browser_fxa_oauth.html
+ browser_fxa_oauth_with_keys.html
+ browser_fxa_web_channel.html
+ browser_registerProtocolHandler_notification.html
+ browser_star_hsts.sjs
+ browser_tab_dragdrop2_frame1.xul
+ browser_web_channel.html
+ browser_web_channel_iframe.html
+ bug1262648_string_with_newlines.dtd
+ bug592338.html
+ bug792517-2.html
+ bug792517.html
+ bug792517.sjs
+ bug839103.css
+ clipboard_pastefile.html
+ contextmenu_common.js
+ ctxmenu-image.png
+ discovery.html
+ download_page.html
+ dummy_page.html
+ feed_tab.html
+ file_generic_favicon.ico
+ file_with_favicon.html
+ file_bug822367_1.html
+ file_bug822367_1.js
+ file_bug822367_2.html
+ file_bug822367_3.html
+ file_bug822367_4.html
+ file_bug822367_4.js
+ file_bug822367_4B.html
+ file_bug822367_5.html
+ file_bug822367_6.html
+ file_bug902156.js
+ file_bug902156_1.html
+ file_bug902156_2.html
+ file_bug902156_3.html
+ file_bug906190_1.html
+ file_bug906190_2.html
+ file_bug906190_3_4.html
+ file_bug906190_redirected.html
+ file_bug906190.js
+ file_bug906190.sjs
+ file_mediaPlayback.html
+ file_mixedContentFromOnunload.html
+ file_mixedContentFromOnunload_test1.html
+ file_mixedContentFromOnunload_test2.html
+ file_mixedContentFramesOnHttp.html
+ file_mixedPassiveContent.html
+ file_bug970276_popup1.html
+ file_bug970276_popup2.html
+ file_bug970276_favicon1.ico
+ file_bug970276_favicon2.ico
+ file_documentnavigation_frameset.html
+ file_double_close_tab.html
+ file_favicon_change.html
+ file_favicon_change_not_in_document.html
+ file_fullscreen-window-open.html
+ head.js
+ healthreport_pingData.js
+ healthreport_testRemoteCommands.html
+ moz.png
+ navigating_window_with_download.html
+ offlineQuotaNotification.cacheManifest
+ offlineQuotaNotification.html
+ page_style_sample.html
+ parsingTestHelpers.jsm
+ pinning_headers.sjs
+ ssl_error_reports.sjs
+ print_postdata.sjs
+ searchSuggestionEngine.sjs
+ searchSuggestionEngine.xml
+ searchSuggestionEngine2.xml
+ subtst_contextmenu.html
+ subtst_contextmenu_input.html
+ subtst_contextmenu_xul.xul
+ test-mixedcontent-securityerrors.html
+ test_bug435035.html
+ test_bug462673.html
+ test_bug628179.html
+ test_bug839103.html
+ test_bug959531.html
+ test_process_flags_chrome.html
+ title_test.svg
+ unknownContentType_file.pif
+ unknownContentType_file.pif^headers^
+ video.ogg
+ web_video.html
+ web_video1.ogv
+ web_video1.ogv^headers^
+ zoom_test.html
+ test_no_mcb_on_http_site_img.html
+ test_no_mcb_on_http_site_img.css
+ test_no_mcb_on_http_site_font.html
+ test_no_mcb_on_http_site_font.css
+ test_no_mcb_on_http_site_font2.html
+ test_no_mcb_on_http_site_font2.css
+ test_mcb_redirect.html
+ test_mcb_redirect_image.html
+ test_mcb_double_redirect_image.html
+ test_mcb_redirect.js
+ test_mcb_redirect.sjs
+ file_bug1045809_1.html
+ file_bug1045809_2.html
+ file_csp_block_all_mixedcontent.html
+ file_csp_block_all_mixedcontent.js
+ !/image/test/mochitest/blue.png
+ !/toolkit/components/passwordmgr/test/browser/form_basic.html
+ !/toolkit/components/passwordmgr/test/browser/insecure_test.html
+ !/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
+ !/toolkit/content/tests/browser/common/mockTransfer.js
+ !/toolkit/modules/tests/browser/metadata_*.html
+ !/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html
+ !/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs
+ !/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi
+ !/toolkit/mozapps/extensions/test/xpinstall/slowinstall.sjs
+
+[browser_aboutAccounts.js]
+skip-if = os == "linux" # Bug 958026
+support-files =
+ content_aboutAccounts.js
+[browser_aboutCertError.js]
+[browser_aboutNetError.js]
+[browser_aboutSupport_newtab_security_state.js]
+[browser_aboutHealthReport.js]
+skip-if = os == "linux" # Bug 924307
+[browser_aboutHome.js]
+[browser_aboutHome_wrapsCorrectly.js]
+[browser_addKeywordSearch.js]
+[browser_alltabslistener.js]
+[browser_audioTabIcon.js]
+tags = audiochannel
+[browser_backButtonFitts.js]
+skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
+[browser_beforeunload_duplicate_dialogs.js]
+[browser_blob-channelname.js]
+[browser_bookmark_popup.js]
+skip-if = (os == "linux" && debug) # mouseover not reliable on linux debug builds
+[browser_bookmark_titles.js]
+skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
+[browser_bug321000.js]
+subsuite = clipboard
+skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
+[browser_bug356571.js]
+[browser_bug380960.js]
+[browser_bug386835.js]
+[browser_bug406216.js]
+[browser_bug408415.js]
+[browser_bug409481.js]
+[browser_bug409624.js]
+[browser_bug413915.js]
+[browser_bug416661.js]
+[browser_bug417483.js]
+[browser_bug419612.js]
+[browser_bug422590.js]
+[browser_bug423833.js]
+skip-if = true # bug 428712
+[browser_bug424101.js]
+[browser_bug427559.js]
+[browser_bug431826.js]
+[browser_bug432599.js]
+[browser_bug435035.js]
+[browser_bug435325.js]
+[browser_bug441778.js]
+[browser_bug455852.js]
+[browser_bug460146.js]
+[browser_bug462289.js]
+skip-if = toolkit == "cocoa"
+[browser_bug462673.js]
+[browser_bug477014.js]
+[browser_bug479408.js]
+[browser_bug481560.js]
+[browser_bug484315.js]
+[browser_bug491431.js]
+[browser_bug495058.js]
+[browser_bug517902.js]
+skip-if = (os == 'linux' && e10s) # bug 1161699
+[browser_bug519216.js]
+[browser_bug520538.js]
+[browser_bug521216.js]
+[browser_bug533232.js]
+[browser_bug537013.js]
+subsuite = clipboard
+skip-if = e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
+[browser_bug537474.js]
+[browser_bug550565.js]
+[browser_bug553455.js]
+[browser_bug555224.js]
+[browser_bug555767.js]
+[browser_bug559991.js]
+[browser_bug561636.js]
+skip-if = true # bug 1057615
+[browser_bug563588.js]
+[browser_bug565575.js]
+[browser_bug567306.js]
+subsuite = clipboard
+[browser_bug1261299.js]
+subsuite = clipboard
+skip-if = toolkit != "cocoa" # Because of tests for supporting Service Menu of macOS, bug 1261299
+[browser_bug1297539.js]
+skip-if = toolkit != "cocoa" # Because of tests for supporting pasting from Service Menu of macOS, bug 1297539
+[browser_bug575561.js]
+[browser_bug575830.js]
+[browser_bug577121.js]
+[browser_bug578534.js]
+[browser_bug579872.js]
+[browser_bug580638.js]
+[browser_bug580956.js]
+[browser_bug581242.js]
+[browser_bug581253.js]
+[browser_bug585558.js]
+[browser_bug585785.js]
+[browser_bug585830.js]
+[browser_bug590206.js]
+[browser_bug592338.js]
+[browser_bug594131.js]
+[browser_bug595507.js]
+skip-if = true # bug 1057615
+[browser_bug596687.js]
+[browser_bug597218.js]
+[browser_bug609700.js]
+[browser_bug623893.js]
+[browser_bug624734.js]
+[browser_bug633691.js]
+[browser_bug647886.js]
+[browser_bug655584.js]
+[browser_bug664672.js]
+[browser_bug676619.js]
+skip-if = os == "mac" # mac: Intermittent failures, bug 925225
+[browser_bug678392.js]
+skip-if = os == "mac" # Bug 1102331 - does focus things on the content window which break in e10s mode (still causes orange on Mac 10.10)
+[browser_bug710878.js]
+[browser_bug719271.js]
+[browser_bug724239.js]
+[browser_bug734076.js]
+[browser_bug735471.js]
+[browser_bug749738.js]
+[browser_bug763468_perwindowpb.js]
+[browser_bug767836_perwindowpb.js]
+[browser_bug817947.js]
+[browser_bug822367.js]
+tags = mcb
+[browser_bug832435.js]
+[browser_bug839103.js]
+[browser_bug882977.js]
+[browser_bug902156.js]
+tags = mcb
+[browser_bug906190.js]
+tags = mcb
+[browser_mixedContentFromOnunload.js]
+tags = mcb
+[browser_mixedContentFramesOnHttp.js]
+tags = mcb
+[browser_bug970746.js]
+[browser_bug1015721.js]
+skip-if = os == 'win'
+[browser_bug1064280_changeUrlInPinnedTab.js]
+[browser_accesskeys.js]
+[browser_clipboard.js]
+subsuite = clipboard
+[browser_clipboard_pastefile.js]
+skip-if = true # Disabled due to the clipboard not supporting real file types yet (bug 1288773)
+[browser_contentAreaClick.js]
+skip-if = e10s # Clicks in content don't go through contentAreaClick with e10s.
+[browser_contentAltClick.js]
+[browser_contextmenu.js]
+subsuite = clipboard
+tags = fullscreen
+skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+[browser_contextmenu_input.js]
+skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
+[browser_ctrlTab.js]
+[browser_datachoices_notification.js]
+skip-if = !datareporting
+[browser_decoderDoctor.js]
+skip-if = os == "mac" # decoder doctor isn't implemented on osx
+[browser_devedition.js]
+[browser_discovery.js]
+[browser_double_close_tab.js]
+[browser_documentnavigation.js]
+[browser_duplicateIDs.js]
+[browser_drag.js]
+skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
+[browser_favicon_change.js]
+[browser_favicon_change_not_in_document.js]
+[browser_findbarClose.js]
+[browser_focusonkeydown.js]
+[browser_fullscreen-window-open.js]
+tags = fullscreen
+skip-if = os == "linux" # Linux: Intermittent failures - bug 941575.
+[browser_fxaccounts.js]
+support-files = fxa_profile_handler.sjs
+[browser_fxa_migrate.js]
+[browser_fxa_oauth.js]
+[browser_fxa_web_channel.js]
+[browser_gestureSupport.js]
+skip-if = e10s # Bug 863514 - no gesture support.
+[browser_getshortcutoruri.js]
+[browser_hide_removing.js]
+[browser_homeDrop.js]
+[browser_identity_UI.js]
+[browser_insecureLoginForms.js]
+support-files = insecure_opener.html
+[browser_invalid_uri_back_forward_manipulation.js]
+[browser_keywordBookmarklets.js]
+[browser_keywordSearch.js]
+[browser_keywordSearch_postData.js]
+[browser_lastAccessedTab.js]
+skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
+[browser_menuButtonFitts.js]
+skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
+[browser_middleMouse_noJSPaste.js]
+subsuite = clipboard
+[browser_minimize.js]
+[browser_misused_characters_in_strings.js]
+[browser_mixed_content_cert_override.js]
+[browser_mixedcontent_securityflags.js]
+tags = mcb
+[browser_modifiedclick_inherit_principal.js]
+[browser_offlineQuotaNotification.js]
+skip-if = os == "linux" && !debug # bug 1304273
+[browser_feed_discovery.js]
+support-files = feed_discovery.html
+[browser_gZipOfflineChild.js]
+support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
+[browser_overflowScroll.js]
+[browser_pageInfo.js]
+[browser_pageinfo_svg_image.js]
+support-files =
+ svg_image.html
+[browser_page_style_menu.js]
+[browser_page_style_menu_update.js]
+[browser_parsable_css.js]
+skip-if = (debug || asan) # no point in running on both opt and debug, and will likely intermittently timeout on debug
+[browser_parsable_script.js]
+skip-if = asan || (os == 'linux' && !debug && (bits == 32)) # disabled on asan because of timeouts, and bug 1172468 for the linux 32-bit pgo issue.
+[browser_permissions.js]
+support-files =
+ permissions.html
+[browser_pinnedTabs.js]
+[browser_plainTextLinks.js]
+[browser_printpreview.js]
+[browser_private_browsing_window.js]
+[browser_private_no_prompt.js]
+[browser_purgehistory_clears_sh.js]
+[browser_PageMetaData_pushstate.js]
+[browser_refreshBlocker.js]
+support-files =
+ refresh_header.sjs
+ refresh_meta.sjs
+[browser_relatedTabs.js]
+[browser_remoteTroubleshoot.js]
+support-files =
+ test_remoteTroubleshoot.html
+[browser_remoteWebNavigation_postdata.js]
+[browser_removeTabsToTheEnd.js]
+[browser_restore_isAppTab.js]
+[browser_sanitize-passwordDisabledHosts.js]
+[browser_sanitize-sitepermissions.js]
+[browser_sanitize-timespans.js]
+[browser_sanitizeDialog.js]
+[browser_save_link-perwindowpb.js]
+skip-if = e10s && debug && os == "win" # Bug 1280505
+[browser_save_private_link_perwindowpb.js]
+[browser_save_link_when_window_navigates.js]
+[browser_save_video.js]
+[browser_save_video_frame.js]
+[browser_scope.js]
+[browser_contentSearchUI.js]
+support-files =
+ contentSearchUI.html
+ contentSearchUI.js
+[browser_selectpopup.js]
+run-if = e10s
+[browser_selectTabAtIndex.js]
+[browser_ssl_error_reports.js]
+[browser_star_hsts.js]
+[browser_subframe_favicons_not_used.js]
+[browser_syncui.js]
+[browser_tab_close_dependent_window.js]
+[browser_tabDrop.js]
+[browser_tabReorder.js]
+[browser_tab_detach_restore.js]
+[browser_tab_drag_drop_perwindow.js]
+[browser_tab_dragdrop.js]
+skip-if = buildapp == 'mulet' || (e10s && (debug || os == 'linux')) # Bug 1312436
+[browser_tab_dragdrop2.js]
+[browser_tabbar_big_widgets.js]
+skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux
+ # Disabled on OS X because of bug 967917
+[browser_tabfocus.js]
+[browser_tabkeynavigation.js]
+skip-if = (os == "mac" && !e10s) # Bug 1237713 - OSX eats keypresses for some reason
+[browser_tabopen_reflows.js]
+[browser_tabs_close_beforeunload.js]
+support-files =
+ close_beforeunload_opens_second_tab.html
+ close_beforeunload.html
+[browser_tabs_isActive.js]
+[browser_tabs_owner.js]
+[browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js]
+run-if = e10s
+[browser_trackingUI_1.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+ benignPage.html
+[browser_trackingUI_2.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+ benignPage.html
+[browser_trackingUI_3.js]
+tags = trackingprotection
+[browser_trackingUI_4.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+ benignPage.html
+[browser_trackingUI_5.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+[browser_trackingUI_6.js]
+tags = trackingprotection
+support-files =
+ file_trackingUI_6.html
+ file_trackingUI_6.js
+ file_trackingUI_6.js^headers^
+[browser_trackingUI_telemetry.js]
+tags = trackingprotection
+support-files =
+ trackingPage.html
+[browser_typeAheadFind.js]
+[browser_unknownContentType_title.js]
+[browser_unloaddialogs.js]
+[browser_utilityOverlay.js]
+[browser_viewSourceInTabOnViewSource.js]
+[browser_visibleFindSelection.js]
+[browser_visibleTabs.js]
+[browser_visibleTabs_bookmarkAllPages.js]
+skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
+[browser_visibleTabs_bookmarkAllTabs.js]
+[browser_visibleTabs_contextMenu.js]
+[browser_visibleTabs_tabPreview.js]
+skip-if = (os == "win" && !debug)
+[browser_web_channel.js]
+[browser_windowopen_reflows.js]
+[browser_zbug569342.js]
+skip-if = e10s || debug # Bug 1094240 - has findbar-related failures
+[browser_registerProtocolHandler_notification.js]
+[browser_no_mcb_on_http_site.js]
+tags = mcb
+[browser_addCertException.js]
+[browser_bug1045809.js]
+tags = mcb
+[browser_e10s_switchbrowser.js]
+[browser_e10s_about_process.js]
+[browser_e10s_chrome_process.js]
+[browser_e10s_javascript.js]
+[browser_blockHPKP.js]
+tags = psm
+[browser_mcb_redirect.js]
+tags = mcb
+[browser_windowactivation.js]
+[browser_contextmenu_childprocess.js]
+[browser_bug963945.js]
+[browser_domFullscreen_fullscreenMode.js]
+tags = fullscreen
+[browser_menuButtonBadgeManager.js]
+[browser_newTabDrop.js]
+[browser_newWindowDrop.js]
+[browser_csp_block_all_mixedcontent.js]
+tags = mcb
+[browser_newwindow_focus.js]
+skip-if = (os == "linux" && !e10s) # Bug 1263254 - Perma fails on Linux without e10s for some reason.
+[browser_bug1299667.js]
diff --git a/browser/base/content/test/general/browser_PageMetaData_pushstate.js b/browser/base/content/test/general/browser_PageMetaData_pushstate.js
new file mode 100644
index 000000000..6f71c57a3
--- /dev/null
+++ b/browser/base/content/test/general/browser_PageMetaData_pushstate.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(function* () {
+ let rooturi = "https://example.com/browser/toolkit/modules/tests/browser/";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, rooturi + "metadata_simple.html");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { rooturi }, function* (args) {
+ let result = PageMetadata.getData(content.document);
+ // Result should have description.
+ Assert.equal(result.url, args.rooturi + "metadata_simple.html", "metadata url is correct");
+ Assert.equal(result.title, "Test Title", "metadata title is correct");
+ Assert.equal(result.description, "A very simple test page", "description is correct");
+
+ content.history.pushState({}, "2", "2.html");
+ result = PageMetadata.getData(content.document);
+ // Result should not have description.
+ Assert.equal(result.url, args.rooturi + "2.html", "metadata url is correct");
+ Assert.equal(result.title, "Test Title", "metadata title is correct");
+ Assert.ok(!result.description, "description is undefined");
+
+ Assert.equal(content.document.documentURI, args.rooturi + "2.html",
+ "content.document has correct url");
+ });
+
+ is(gBrowser.currentURI.spec, rooturi + "2.html", "gBrowser has correct url");
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_aboutAccounts.js b/browser/base/content/test/general/browser_aboutAccounts.js
new file mode 100644
index 000000000..fd72a1608
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutAccounts.js
@@ -0,0 +1,499 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: window.location is null");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+// Preference helpers.
+var changedPrefs = new Set();
+
+function setPref(name, value) {
+ changedPrefs.add(name);
+ Services.prefs.setCharPref(name, value);
+}
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ for (let name of changedPrefs) {
+ Services.prefs.clearUserPref(name);
+ }
+});
+
+var gTests = [
+{
+ desc: "Test the remote commands",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ setPref("identity.fxaccounts.remote.signup.uri",
+ "https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
+ let tab = yield promiseNewTabLoadEvent("about:accounts");
+ let mm = tab.linkedBrowser.messageManager;
+
+ let deferred = Promise.defer();
+
+ // We'll get a message when openPrefs() is called, which this test should
+ // arrange.
+ let promisePrefsOpened = promiseOneMessage(tab, "test:openPrefsCalled");
+ let results = 0;
+ try {
+ mm.addMessageListener("test:response", function responseHandler(msg) {
+ let data = msg.data.data;
+ if (data.type == "testResult") {
+ ok(data.pass, data.info);
+ results++;
+ } else if (data.type == "testsComplete") {
+ is(results, data.count, "Checking number of results received matches the number of tests that should have run");
+ mm.removeMessageListener("test:response", responseHandler);
+ deferred.resolve();
+ }
+ });
+ } catch (e) {
+ ok(false, "Failed to get all commands");
+ deferred.reject();
+ }
+ yield deferred.promise;
+ yield promisePrefsOpened;
+ }
+},
+{
+ desc: "Test action=signin - no user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ const expected_url = "https://example.com/?is_sign_in";
+ setPref("identity.fxaccounts.remote.signin.uri", expected_url);
+ let [tab, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin");
+ is(url, expected_url, "action=signin got the expected URL");
+ // we expect the remote iframe to be shown.
+ yield checkVisibilities(tab, {
+ stage: false, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: true,
+ networkError: false
+ });
+ }
+},
+{
+ desc: "Test action=signin - user logged in",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ // When this loads with a user logged-in, we expect the normal URL to
+ // have been ignored and the "manage" page to be shown.
+ const expected_url = "https://example.com/?is_sign_in";
+ setPref("identity.fxaccounts.remote.signin.uri", expected_url);
+ yield setSignedInUser();
+ let tab = yield promiseNewTabLoadEvent("about:accounts?action=signin");
+ // about:accounts initializes after fetching the current user from Fxa -
+ // so we also request it - by the time we get it we know it should have
+ // done its thing.
+ yield fxAccounts.getSignedInUser();
+ // we expect "manage" to be shown.
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: true,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: false
+ });
+ }
+},
+{
+ desc: "Test action=signin - captive portal",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ const signinUrl = "https://redirproxy.example.com/test";
+ setPref("identity.fxaccounts.remote.signin.uri", signinUrl);
+ let [tab, ] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin");
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: true
+ });
+ }
+},
+{
+ desc: "Test action=signin - offline",
+ teardown: () => {
+ gBrowser.removeCurrentTab();
+ BrowserOffline.toggleOfflineStatus();
+ },
+ run: function* ()
+ {
+ BrowserOffline.toggleOfflineStatus();
+ Services.cache2.clear();
+
+ const signinUrl = "https://unknowndomain.cow";
+ setPref("identity.fxaccounts.remote.signin.uri", signinUrl);
+ let [tab, ] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin");
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: true
+ });
+ }
+},
+{
+ desc: "Test action=signup - no user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ const expected_url = "https://example.com/?is_sign_up";
+ setPref("identity.fxaccounts.remote.signup.uri", expected_url);
+ let [tab, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signup");
+ is(url, expected_url, "action=signup got the expected URL");
+ // we expect the remote iframe to be shown.
+ yield checkVisibilities(tab, {
+ stage: false, // parent of 'manage' and 'intro'
+ manage: false,
+ intro: false, // this is "get started"
+ remote: true,
+ networkError: false
+ });
+ },
+},
+{
+ desc: "Test action=signup - user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* ()
+ {
+ const expected_url = "https://example.com/?is_sign_up";
+ setPref("identity.fxaccounts.remote.signup.uri", expected_url);
+ yield setSignedInUser();
+ let tab = yield promiseNewTabLoadEvent("about:accounts?action=signup");
+ yield fxAccounts.getSignedInUser();
+ // we expect "manage" to be shown.
+ yield checkVisibilities(tab, {
+ stage: true, // parent of 'manage' and 'intro'
+ manage: true,
+ intro: false, // this is "get started"
+ remote: false,
+ networkError: false
+ });
+ },
+},
+{
+ desc: "Test action=reauth",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ const expected_url = "https://example.com/?is_force_auth";
+ setPref("identity.fxaccounts.remote.force_auth.uri", expected_url);
+
+ yield setSignedInUser();
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=reauth");
+ // The current user will be appended to the url
+ let expected = expected_url + "&email=foo%40example.com";
+ is(url, expected, "action=reauth got the expected URL");
+ },
+},
+{
+ desc: "Test with migrateToDevEdition enabled (success)",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ let fxAccountsCommon = {};
+ Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
+ const pref = "identity.fxaccounts.migrateToDevEdition";
+ changedPrefs.add(pref);
+ Services.prefs.setBoolPref(pref, true);
+
+ // Create the signedInUser.json file that will be used as the source of
+ // migrated user data.
+ let signedInUser = {
+ version: 1,
+ accountData: {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ sessionToken: "dead",
+ verified: true
+ }
+ };
+ // We use a sub-dir of the real profile dir as the "pretend" profile dir
+ // for this test.
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let mockDir = profD.clone();
+ mockDir.append("about-accounts-mock-profd");
+ mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ let fxAccountsStorage = OS.Path.join(mockDir.path, fxAccountsCommon.DEFAULT_STORAGE_FILENAME);
+ yield OS.File.writeAtomic(fxAccountsStorage, JSON.stringify(signedInUser));
+ info("Wrote file " + fxAccountsStorage);
+
+ // this is a little subtle - we load about:robots so we get a non-remote
+ // tab, then we send a message which does both (a) load the URL we want and
+ // (b) mocks the default profile path used by about:accounts.
+ let tab = yield promiseNewTabLoadEvent("about:robots");
+ let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response");
+
+ let mm = tab.linkedBrowser.messageManager;
+ mm.sendAsyncMessage("test:load-with-mocked-profile-path", {
+ url: "about:accounts",
+ profilePath: mockDir.path,
+ });
+
+ let response = yield readyPromise;
+ // We are expecting the iframe to be on the "force reauth" URL
+ let expected = yield fxAccounts.promiseAccountsForceSigninURI();
+ is(response.data.url, expected);
+
+ let userData = yield fxAccounts.getSignedInUser();
+ SimpleTest.isDeeply(userData, signedInUser.accountData, "All account data were migrated");
+ // The migration pref will have been switched off by now.
+ is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value");
+
+ yield OS.File.remove(fxAccountsStorage);
+ yield OS.File.removeEmptyDir(mockDir.path);
+ },
+},
+{
+ desc: "Test with migrateToDevEdition enabled (no user to migrate)",
+ teardown: function* () {
+ gBrowser.removeCurrentTab();
+ yield signOut();
+ },
+ run: function* ()
+ {
+ const pref = "identity.fxaccounts.migrateToDevEdition";
+ changedPrefs.add(pref);
+ Services.prefs.setBoolPref(pref, true);
+
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let mockDir = profD.clone();
+ mockDir.append("about-accounts-mock-profd");
+ mockDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ // but leave it empty, so we don't think a user is logged in.
+
+ let tab = yield promiseNewTabLoadEvent("about:robots");
+ let readyPromise = promiseOneMessage(tab, "test:load-with-mocked-profile-path-response");
+
+ let mm = tab.linkedBrowser.messageManager;
+ mm.sendAsyncMessage("test:load-with-mocked-profile-path", {
+ url: "about:accounts",
+ profilePath: mockDir.path,
+ });
+
+ let response = yield readyPromise;
+ // We are expecting the iframe to be on the "signup" URL
+ let expected = yield fxAccounts.promiseAccountsSignUpURI();
+ is(response.data.url, expected);
+
+ // and expect no signed in user.
+ let userData = yield fxAccounts.getSignedInUser();
+ is(userData, null);
+ // The migration pref should have still been switched off.
+ is(Services.prefs.getBoolPref(pref), false, pref + " got the expected value");
+ yield OS.File.removeEmptyDir(mockDir.path);
+ },
+},
+{
+ desc: "Test observers about:accounts",
+ teardown: function() {
+ gBrowser.removeCurrentTab();
+ },
+ run: function* () {
+ setPref("identity.fxaccounts.remote.signup.uri", "https://example.com/");
+ yield setSignedInUser();
+ let tab = yield promiseNewTabLoadEvent("about:accounts");
+ // sign the user out - the tab should have action=signin
+ yield signOut();
+ // wait for the new load.
+ yield promiseOneMessage(tab, "test:document:load");
+ is(tab.linkedBrowser.contentDocument.location.href, "about:accounts?action=signin");
+ }
+},
+{
+ desc: "Test entrypoint query string, no action, no user logged in",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* () {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ setPref("identity.fxaccounts.remote.signup.uri", "https://example.com/");
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?entrypoint=abouthome");
+ is(url, "https://example.com/?entrypoint=abouthome", "entrypoint=abouthome got the expected URL");
+ },
+},
+{
+ desc: "Test entrypoint query string for signin",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* () {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ const expected_url = "https://example.com/?is_sign_in";
+ setPref("identity.fxaccounts.remote.signin.uri", expected_url);
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signin&entrypoint=abouthome");
+ is(url, expected_url + "&entrypoint=abouthome", "entrypoint=abouthome got the expected URL");
+ },
+},
+{
+ desc: "Test entrypoint query string for signup",
+ teardown: () => gBrowser.removeCurrentTab(),
+ run: function* () {
+ // When this loads with no user logged-in, we expect the "normal" URL
+ const sign_up_url = "https://example.com/?is_sign_up";
+ setPref("identity.fxaccounts.remote.signup.uri", sign_up_url);
+ let [, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?entrypoint=abouthome&action=signup");
+ is(url, sign_up_url + "&entrypoint=abouthome", "entrypoint=abouthome got the expected URL");
+ },
+},
+{
+ desc: "about:accounts URL params should be copied to remote URL params " +
+ "when remote URL has no URL params, except for 'action'",
+ teardown() {
+ gBrowser.removeCurrentTab();
+ },
+ run: function* () {
+ let signupURL = "https://example.com/";
+ setPref("identity.fxaccounts.remote.signup.uri", signupURL);
+ let queryStr = "email=foo%40example.com&foo=bar&baz=quux";
+ let [, url] =
+ yield promiseNewTabWithIframeLoadEvent("about:accounts?" + queryStr +
+ "&action=action");
+ is(url, signupURL + "?" + queryStr, "URL params are copied to signup URL");
+ },
+},
+{
+ desc: "about:accounts URL params should be copied to remote URL params " +
+ "when remote URL already has some URL params, except for 'action'",
+ teardown() {
+ gBrowser.removeCurrentTab();
+ },
+ run: function* () {
+ let signupURL = "https://example.com/?param";
+ setPref("identity.fxaccounts.remote.signup.uri", signupURL);
+ let queryStr = "email=foo%40example.com&foo=bar&baz=quux";
+ let [, url] =
+ yield promiseNewTabWithIframeLoadEvent("about:accounts?" + queryStr +
+ "&action=action");
+ is(url, signupURL + "&" + queryStr, "URL params are copied to signup URL");
+ },
+},
+]; // gTests
+
+function test()
+{
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info(testCase.desc);
+ try {
+ yield testCase.run();
+ } finally {
+ yield testCase.teardown();
+ }
+ }
+
+ finish();
+ });
+}
+
+function promiseOneMessage(tab, messageName) {
+ let mm = tab.linkedBrowser.messageManager;
+ let deferred = Promise.defer();
+ mm.addMessageListener(messageName, function onmessage(message) {
+ mm.removeMessageListener(messageName, onmessage);
+ deferred.resolve(message);
+ });
+ return deferred.promise;
+}
+
+function promiseNewTabLoadEvent(aUrl)
+{
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ let browser = tab.linkedBrowser;
+ let mm = browser.messageManager;
+
+ // give it an e10s-friendly content script to help with our tests.
+ mm.loadFrameScript(CHROME_BASE + "content_aboutAccounts.js", true);
+ // and wait for it to tell us about the load.
+ return promiseOneMessage(tab, "test:document:load").then(
+ () => tab
+ );
+}
+
+// Returns a promise which is resolved with the iframe's URL after a new
+// tab is created and the iframe in that tab loads.
+function promiseNewTabWithIframeLoadEvent(aUrl) {
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ let browser = tab.linkedBrowser;
+ let mm = browser.messageManager;
+
+ // give it an e10s-friendly content script to help with our tests.
+ mm.loadFrameScript(CHROME_BASE + "content_aboutAccounts.js", true);
+ // and wait for it to tell us about the iframe load.
+ mm.addMessageListener("test:iframe:load", function onFrameLoad(message) {
+ mm.removeMessageListener("test:iframe:load", onFrameLoad);
+ deferred.resolve([tab, message.data.url]);
+ });
+ return deferred.promise;
+}
+
+function checkVisibilities(tab, data) {
+ let ids = Object.keys(data);
+ let mm = tab.linkedBrowser.messageManager;
+ let deferred = Promise.defer();
+ mm.addMessageListener("test:check-visibilities-response", function onResponse(message) {
+ mm.removeMessageListener("test:check-visibilities-response", onResponse);
+ for (let id of ids) {
+ is(message.data[id], data[id], "Element '" + id + "' has correct visibility");
+ }
+ deferred.resolve();
+ });
+ mm.sendAsyncMessage("test:check-visibilities", {ids: ids});
+ return deferred.promise;
+}
+
+// watch out - these will fire observers which if you aren't careful, may
+// interfere with the tests.
+function setSignedInUser(data) {
+ if (!data) {
+ data = {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: true
+ }
+ }
+ return fxAccounts.setSignedInUser(data);
+}
+
+function signOut() {
+ // we always want a "localOnly" signout here...
+ return fxAccounts.signOut(true);
+}
diff --git a/browser/base/content/test/general/browser_aboutCertError.js b/browser/base/content/test/general/browser_aboutCertError.js
new file mode 100644
index 000000000..0e335066c
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutCertError.js
@@ -0,0 +1,409 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This is testing the aboutCertError page (Bug 1207107).
+
+const GOOD_PAGE = "https://example.com/";
+const BAD_CERT = "https://expired.example.com/";
+const UNKNOWN_ISSUER = "https://self-signed.example.com ";
+const BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443";
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+add_task(function* checkReturnToAboutHome() {
+ info("Loading a bad cert page directly and making sure 'return to previous page' goes to about:home");
+ let browser;
+ let certErrorLoaded;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
+
+ // Populate the shistory entries manually, since it happens asynchronously
+ // and the following tests will be too soon otherwise.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is one shistory entry");
+
+ info("Clicking the go back button on about:certerror");
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let returnButton = doc.getElementById("returnButton");
+ is(returnButton.getAttribute("autofocus"), "true", "returnButton has autofocus");
+ returnButton.click();
+
+ yield ContentTaskUtils.waitForEvent(this, "pageshow", true);
+ });
+
+ is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
+ is(gBrowser.currentURI.spec, "about:home", "Went back");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkReturnToPreviousPage() {
+ info("Loading a bad cert page and making sure 'return to previous page' goes back");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
+ let browser = gBrowser.selectedBrowser;
+
+ info("Loading and waiting for the cert error");
+ let certErrorLoaded = waitForCertErrorLoad(browser);
+ BrowserTestUtils.loadURI(browser, BAD_CERT);
+ yield certErrorLoaded;
+
+ is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
+
+ // Populate the shistory entries manually, since it happens asynchronously
+ // and the following tests will be too soon otherwise.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 2, "there are two shistory entries");
+
+ info("Clicking the go back button on about:certerror");
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let returnButton = doc.getElementById("returnButton");
+ returnButton.click();
+
+ yield ContentTaskUtils.waitForEvent(this, "pageshow", true);
+ });
+
+ is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
+ is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward");
+ is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkBadStsCert() {
+ info("Loading a badStsCert and making sure exception button doesn't show up");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
+ let browser = gBrowser.selectedBrowser;
+
+ info("Loading and waiting for the cert error");
+ let certErrorLoaded = waitForCertErrorLoad(browser);
+ BrowserTestUtils.loadURI(browser, BAD_STS_CERT);
+ yield certErrorLoaded;
+
+ let exceptionButtonHidden = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let exceptionButton = doc.getElementById("exceptionDialogButton");
+ return exceptionButton.hidden;
+ });
+ ok(exceptionButtonHidden, "Exception button is hidden");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+const PREF_BLOCKLIST_CLOCK_SKEW_SECONDS = "services.blocklist.clock_skew_seconds";
+
+add_task(function* checkWrongSystemTimeWarning() {
+ function* setUpPage() {
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ return yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let div = doc.getElementById("wrongSystemTimePanel");
+ let systemDateDiv = doc.getElementById("wrongSystemTime_systemDate");
+ let actualDateDiv = doc.getElementById("wrongSystemTime_actualDate");
+ let learnMoreLink = doc.getElementById("learnMoreLink");
+
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: div.textContent,
+ systemDate: systemDateDiv.textContent,
+ actualDate: actualDateDiv.textContent,
+ learnMoreLink: learnMoreLink.href
+ };
+ });
+ }
+
+ let formatter = new Intl.DateTimeFormat();
+
+ // pretend we have a positively skewed (ahead) system time
+ let serverDate = new Date("2015/10/27");
+ let serverDateFmt = formatter.format(serverDate);
+ let localDateFmt = formatter.format(new Date());
+
+ let skew = Math.floor((Date.now() - serverDate.getTime()) / 1000);
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with a skewed clock");
+ let message = yield Task.spawn(setUpPage);
+
+ isnot(message.divDisplay, "none", "Wrong time message information is visible");
+ ok(message.text.includes("because your clock appears to show the wrong time"),
+ "Correct error message found");
+ ok(message.text.includes("expired.example.com"), "URL found in error message");
+ ok(message.systemDate.includes(localDateFmt), "correct local date displayed");
+ ok(message.actualDate.includes(serverDateFmt), "correct server date displayed");
+ ok(message.learnMoreLink.includes("time-errors"), "time-errors in the Learn More URL");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // pretend we have a negatively skewed (behind) system time
+ serverDate = new Date();
+ serverDate.setYear(serverDate.getFullYear() + 1);
+ serverDateFmt = formatter.format(serverDate);
+
+ skew = Math.floor((Date.now() - serverDate.getTime()) / 1000);
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with a skewed clock");
+ message = yield Task.spawn(setUpPage);
+
+ isnot(message.divDisplay, "none", "Wrong time message information is visible");
+ ok(message.text.includes("because your clock appears to show the wrong time"),
+ "Correct error message found");
+ ok(message.text.includes("expired.example.com"), "URL found in error message");
+ ok(message.systemDate.includes(localDateFmt), "correct local date displayed");
+ ok(message.actualDate.includes(serverDateFmt), "correct server date displayed");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // pretend we only have a slightly skewed system time, four hours
+ skew = 60 * 60 * 4;
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with an only slightly skewed clock");
+ message = yield Task.spawn(setUpPage);
+
+ is(message.divDisplay, "none", "Wrong time message information is not visible");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ // now pretend we have no skewed system time
+ skew = 0;
+ yield new Promise(r => SpecialPowers.pushPrefEnv({set:
+ [[PREF_BLOCKLIST_CLOCK_SKEW_SECONDS, skew]]}, r));
+
+ info("Loading a bad cert page with no skewed clock");
+ message = yield Task.spawn(setUpPage);
+
+ is(message.divDisplay, "none", "Wrong time message information is not visible");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkAdvancedDetails() {
+ info("Loading a bad cert page and verifying the main error and advanced details section");
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ let message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let shortDescText = doc.getElementById("errorShortDescText");
+ info("Main error text: " + shortDescText.textContent);
+ ok(shortDescText.textContent.includes("expired.example.com"),
+ "Should list hostname in error message.");
+
+ let advancedButton = doc.getElementById("advancedButton");
+ advancedButton.click();
+ let el = doc.getElementById("errorCode");
+ return { textContent: el.textContent, tagName: el.tagName };
+ });
+ is(message.textContent, "SEC_ERROR_EXPIRED_CERTIFICATE",
+ "Correct error message found");
+ is(message.tagName, "a", "Error message is a link");
+
+ message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let errorCode = doc.getElementById("errorCode");
+ errorCode.click();
+ let div = doc.getElementById("certificateErrorDebugInformation");
+ let text = doc.getElementById("certificateErrorText");
+
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let serializable = docShell.failedChannel.securityInfo
+ .QueryInterface(Ci.nsITransportSecurityInfo)
+ .QueryInterface(Ci.nsISerializable);
+ let serializedSecurityInfo = serhelper.serializeToString(serializable);
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: text.textContent,
+ securityInfoAsString: serializedSecurityInfo
+ };
+ });
+ isnot(message.divDisplay, "none", "Debug information is visible");
+ ok(message.text.includes(BAD_CERT), "Correct URL found");
+ ok(message.text.includes("Certificate has expired"),
+ "Correct error message found");
+ ok(message.text.includes("HTTP Strict Transport Security: false"),
+ "Correct HSTS value found");
+ ok(message.text.includes("HTTP Public Key Pinning: false"),
+ "Correct HPKP value found");
+ let certChain = getCertChain(message.securityInfoAsString);
+ ok(message.text.includes(certChain), "Found certificate chain");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkAdvancedDetailsForHSTS() {
+ info("Loading a bad STS cert page and verifying the advanced details section");
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(BAD_STS_CERT);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ let message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let advancedButton = doc.getElementById("advancedButton");
+ advancedButton.click();
+ let ec = doc.getElementById("errorCode");
+ let cdl = doc.getElementById("cert_domain_link");
+ return {
+ ecTextContent: ec.textContent,
+ ecTagName: ec.tagName,
+ cdlTextContent: cdl.textContent,
+ cdlTagName: cdl.tagName
+ };
+ });
+
+ const badStsUri = Services.io.newURI(BAD_STS_CERT, null, null);
+ is(message.ecTextContent, "SSL_ERROR_BAD_CERT_DOMAIN",
+ "Correct error message found");
+ is(message.ecTagName, "a", "Error message is a link");
+ const url = badStsUri.prePath.slice(badStsUri.prePath.indexOf(".") + 1);
+ is(message.cdlTextContent, url,
+ "Correct cert_domain_link contents found");
+ is(message.cdlTagName, "a", "cert_domain_link is a link");
+
+ message = yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let errorCode = doc.getElementById("errorCode");
+ errorCode.click();
+ let div = doc.getElementById("certificateErrorDebugInformation");
+ let text = doc.getElementById("certificateErrorText");
+
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let serializable = docShell.failedChannel.securityInfo
+ .QueryInterface(Ci.nsITransportSecurityInfo)
+ .QueryInterface(Ci.nsISerializable);
+ let serializedSecurityInfo = serhelper.serializeToString(serializable);
+ return {
+ divDisplay: content.getComputedStyle(div).display,
+ text: text.textContent,
+ securityInfoAsString: serializedSecurityInfo
+ };
+ });
+ isnot(message.divDisplay, "none", "Debug information is visible");
+ ok(message.text.includes(badStsUri.spec), "Correct URL found");
+ ok(message.text.includes("requested domain name does not match the server\u2019s certificate"),
+ "Correct error message found");
+ ok(message.text.includes("HTTP Strict Transport Security: false"),
+ "Correct HSTS value found");
+ ok(message.text.includes("HTTP Public Key Pinning: true"),
+ "Correct HPKP value found");
+ let certChain = getCertChain(message.securityInfoAsString);
+ ok(message.text.includes(certChain), "Found certificate chain");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* checkUnknownIssuerLearnMoreLink() {
+ info("Loading a cert error for self-signed pages and checking the correct link is shown");
+ let browser;
+ let certErrorLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(UNKNOWN_ISSUER);
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = waitForCertErrorLoad(browser);
+ }, false);
+
+ info("Loading and waiting for the cert error");
+ yield certErrorLoaded;
+
+ let href = yield ContentTask.spawn(browser, null, function* () {
+ let learnMoreLink = content.document.getElementById("learnMoreLink");
+ return learnMoreLink.href;
+ });
+ ok(href.endsWith("security-error"), "security-error in the Learn More URL");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+function waitForCertErrorLoad(browser) {
+ return new Promise(resolve => {
+ info("Waiting for DOMContentLoaded event");
+ browser.addEventListener("DOMContentLoaded", function load() {
+ browser.removeEventListener("DOMContentLoaded", load, false, true);
+ resolve();
+ }, false, true);
+ });
+}
+
+function getCertChain(securityInfoAsString) {
+ let certChain = "";
+ const serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let securityInfo = serhelper.deserializeObject(securityInfoAsString);
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ let certs = securityInfo.failedCertChain.getEnumerator();
+ while (certs.hasMoreElements()) {
+ let cert = certs.getNext();
+ cert.QueryInterface(Ci.nsIX509Cert);
+ certChain += getPEMString(cert);
+ }
+ return certChain;
+}
+
+function getDERString(cert)
+{
+ var length = {};
+ var derArray = cert.getRawDER(length);
+ var derString = '';
+ for (var i = 0; i < derArray.length; i++) {
+ derString += String.fromCharCode(derArray[i]);
+ }
+ return derString;
+}
+
+function getPEMString(cert)
+{
+ var derb64 = btoa(getDERString(cert));
+ // Wrap the Base64 string into lines of 64 characters,
+ // with CRLF line breaks (as specified in RFC 1421).
+ var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
+ return "-----BEGIN CERTIFICATE-----\r\n"
+ + wrapped
+ + "\r\n-----END CERTIFICATE-----\r\n";
+}
diff --git a/browser/base/content/test/general/browser_aboutHealthReport.js b/browser/base/content/test/general/browser_aboutHealthReport.js
new file mode 100644
index 000000000..0be184fb8
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutHealthReport.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const CHROME_BASE = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+const HTTPS_BASE = "https://example.com/browser/browser/base/content/test/general/";
+
+const TELEMETRY_LOG_PREF = "toolkit.telemetry.log.level";
+const telemetryOriginalLogPref = Preferences.get(TELEMETRY_LOG_PREF, null);
+
+const originalReportUrl = Services.prefs.getCharPref("datareporting.healthreport.about.reportUrl");
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ if (telemetryOriginalLogPref) {
+ Preferences.set(TELEMETRY_LOG_PREF, telemetryOriginalLogPref);
+ } else {
+ Preferences.reset(TELEMETRY_LOG_PREF);
+ }
+
+ try {
+ Services.prefs.setCharPref("datareporting.healthreport.about.reportUrl", originalReportUrl);
+ Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true);
+ } catch (ex) {}
+});
+
+function fakeTelemetryNow(...args) {
+ let date = new Date(...args);
+ let scope = {};
+ const modules = [
+ Cu.import("resource://gre/modules/TelemetrySession.jsm", scope),
+ Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", scope),
+ Cu.import("resource://gre/modules/TelemetryController.jsm", scope),
+ ];
+
+ for (let m of modules) {
+ m.Policy.now = () => new Date(date);
+ }
+
+ return date;
+}
+
+function* setupPingArchive() {
+ let scope = {};
+ Cu.import("resource://gre/modules/TelemetryController.jsm", scope);
+ Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript(CHROME_BASE + "healthreport_pingData.js", scope);
+
+ for (let p of scope.TEST_PINGS) {
+ fakeTelemetryNow(p.date);
+ p.id = yield scope.TelemetryController.submitExternalPing(p.type, p.payload);
+ }
+}
+
+var gTests = [
+
+{
+ desc: "Test the remote commands",
+ setup: Task.async(function*()
+ {
+ Preferences.set(TELEMETRY_LOG_PREF, "Trace");
+ yield setupPingArchive();
+ Preferences.set("datareporting.healthreport.about.reportUrl",
+ HTTPS_BASE + "healthreport_testRemoteCommands.html");
+ }),
+ run: function (iframe)
+ {
+ let deferred = Promise.defer();
+ let results = 0;
+ try {
+ iframe.contentWindow.addEventListener("FirefoxHealthReportTestResponse", function evtHandler(event) {
+ let data = event.detail.data;
+ if (data.type == "testResult") {
+ ok(data.pass, data.info);
+ results++;
+ }
+ else if (data.type == "testsComplete") {
+ is(results, data.count, "Checking number of results received matches the number of tests that should have run");
+ iframe.contentWindow.removeEventListener("FirefoxHealthReportTestResponse", evtHandler, true);
+ deferred.resolve();
+ }
+ }, true);
+
+ } catch (e) {
+ ok(false, "Failed to get all commands");
+ deferred.reject();
+ }
+ return deferred.promise;
+ }
+},
+
+]; // gTests
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // xxxmpc leaving this here until we resolve bug 854038 and bug 854060
+ requestLongerTimeout(10);
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info(testCase.desc);
+ yield testCase.setup();
+
+ let iframe = yield promiseNewTabLoadEvent("about:healthreport");
+
+ yield testCase.run(iframe);
+
+ gBrowser.removeCurrentTab();
+ }
+
+ finish();
+ });
+}
+
+function promiseNewTabLoadEvent(aUrl, aEventType="load")
+{
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ tab.linkedBrowser.addEventListener(aEventType, function load(event) {
+ tab.linkedBrowser.removeEventListener(aEventType, load, true);
+ let iframe = tab.linkedBrowser.contentDocument.getElementById("remote-report");
+ iframe.addEventListener("load", function frameLoad(e) {
+ if (iframe.contentWindow.location.href == "about:blank" ||
+ e.target != iframe) {
+ return;
+ }
+ iframe.removeEventListener("load", frameLoad, false);
+ deferred.resolve(iframe);
+ }, false);
+ }, true);
+ return deferred.promise;
+}
diff --git a/browser/base/content/test/general/browser_aboutHome.js b/browser/base/content/test/general/browser_aboutHome.js
new file mode 100644
index 000000000..f0e19e852
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -0,0 +1,668 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test needs to be split up. See bug 1258717.
+requestLongerTimeout(4);
+ignoreAllUncaughtExceptions();
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHome.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/" +
+ "content/test/general/aboutHome_content_script.js";
+var gRightsVersion = Services.prefs.getIntPref("browser.rights.version");
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ Services.prefs.clearUserPref("network.cookies.cookieBehavior");
+ Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
+ Services.prefs.clearUserPref("browser.rights.override");
+ Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
+});
+
+add_task(function* () {
+ info("Check that clearing cookies does not clear storage");
+
+ yield withSnippetsMap(
+ () => {
+ Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .notifyObservers(null, "cookie-changed", "cleared");
+ },
+ function* () {
+ isnot(content.gSnippetsMap.get("snippets-last-update"), null,
+ "snippets-last-update should have a value");
+ });
+});
+
+add_task(function* () {
+ info("Check default snippets are shown");
+
+ yield withSnippetsMap(null, function* () {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element")
+ is(snippetsElt.getElementsByTagName("span").length, 1,
+ "A default snippet is present.");
+ });
+});
+
+add_task(function* () {
+ info("Check default snippets are shown if snippets are invalid xml");
+
+ yield withSnippetsMap(
+ // This must set some incorrect xhtml code.
+ snippetsMap => snippetsMap.set("snippets", "<p><b></p></b>"),
+ function* () {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ is(snippetsElt.getElementsByTagName("span").length, 1,
+ "A default snippet is present.");
+
+ content.gSnippetsMap.delete("snippets");
+ });
+});
+
+add_task(function* () {
+ info("Check that performing a search fires a search event and records to Telemetry.");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ let currEngine = Services.search.currentEngine;
+ let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+ // Make this actually work in healthreport by giving it an ID:
+ Object.defineProperty(engine.wrappedJSObject, "identifier",
+ { value: "org.mozilla.testsearchsuggestions" });
+
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.currentEngine = engine;
+ yield p;
+
+ yield ContentTask.spawn(browser, { expectedName: engine.name }, function* (args) {
+ let engineName = content.wrappedJSObject.gContentSearchController.defaultEngine.name;
+ is(engineName, args.expectedName, "Engine name in DOM should match engine we just added");
+ });
+
+ let numSearchesBefore = 0;
+ // Get the current number of recorded searches.
+ let histogramKey = engine.identifier + ".abouthome";
+ try {
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ if (histogramKey in hs) {
+ numSearchesBefore = hs[histogramKey].sum;
+ }
+ } catch (ex) {
+ // No searches performed yet, not a problem, |numSearchesBefore| is 0.
+ }
+
+ let searchStr = "a search";
+
+ let expectedURL = Services.search.currentEngine
+ .getSubmission(searchStr, null, "homepage").uri.spec;
+ let promise = waitForDocLoadAndStopIt(expectedURL, browser);
+
+ // Perform a search to increase the SEARCH_COUNT histogram.
+ yield ContentTask.spawn(browser, { searchStr }, function* (args) {
+ let doc = content.document;
+ info("Perform a search.");
+ doc.getElementById("searchText").value = args.searchStr;
+ doc.getElementById("searchSubmit").click();
+ });
+
+ yield promise;
+
+ // Make sure the SEARCH_COUNTS histogram has the right key and count.
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ Assert.ok(histogramKey in hs, "histogram with key should be recorded");
+ Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
+ "histogram sum should be incremented");
+
+ Services.search.currentEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) {}
+ });
+});
+
+add_task(function* () {
+ info("Check snippets map is cleared if cached version is old");
+
+ yield withSnippetsMap(
+ snippetsMap => {
+ snippetsMap.set("snippets", "test");
+ snippetsMap.set("snippets-cached-version", 0);
+ },
+ function* () {
+ let snippetsMap = content.gSnippetsMap;
+ ok(!snippetsMap.has("snippets"), "snippets have been properly cleared");
+ ok(!snippetsMap.has("snippets-cached-version"),
+ "cached-version has been properly cleared");
+ });
+});
+
+add_task(function* () {
+ info("Check cached snippets are shown if cached version is current");
+
+ yield withSnippetsMap(
+ snippetsMap => snippetsMap.set("snippets", "test"),
+ function* (args) {
+ let doc = content.document;
+ let snippetsMap = content.gSnippetsMap
+
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ is(snippetsElt.innerHTML, "test", "Cached snippet is present.");
+
+ is(snippetsMap.get("snippets"), "test", "snippets still cached");
+ is(snippetsMap.get("snippets-cached-version"),
+ args.expectedVersion,
+ "cached-version is correct");
+ ok(snippetsMap.has("snippets-last-update"), "last-update still exists");
+ }, { expectedVersion: AboutHomeUtils.snippetsVersion });
+});
+
+add_task(function* () {
+ info("Check if the 'Know Your Rights' default snippet is shown when " +
+ "'browser.rights.override' pref is set and that its link works");
+
+ Services.prefs.setBoolPref("browser.rights.override", false);
+
+ ok(AboutHomeUtils.showKnowYourRights, "AboutHomeUtils.showKnowYourRights should be TRUE");
+
+ yield withSnippetsMap(null, function* () {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ let linkEl = snippetsElt.querySelector("a");
+ is(linkEl.href, "about:rights", "Snippet link is present.");
+ }, null, function* () {
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:rights");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("a[href='about:rights']", {
+ button: 0
+ }, gBrowser.selectedBrowser);
+ yield loadPromise;
+ is(gBrowser.currentURI.spec, "about:rights", "about:rights should have opened.");
+ });
+
+
+ Services.prefs.clearUserPref("browser.rights.override");
+});
+
+add_task(function* () {
+ info("Check if the 'Know Your Rights' default snippet is NOT shown when " +
+ "'browser.rights.override' pref is NOT set");
+
+ Services.prefs.setBoolPref("browser.rights.override", true);
+
+ let rightsData = AboutHomeUtils.knowYourRightsData;
+ ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE");
+
+ yield withSnippetsMap(null, function*() {
+ let doc = content.document;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights",
+ "Snippet link should not point to about:rights.");
+ });
+
+ Services.prefs.clearUserPref("browser.rights.override");
+});
+
+add_task(function* () {
+ info("Check POST search engine support");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ return new Promise(resolve => {
+ let searchObserver = Task.async(function* search_observer(subject, topic, data) {
+ let currEngine = Services.search.defaultEngine;
+ let engine = subject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + data + " for " + engine.name);
+
+ if (data != "engine-added")
+ return;
+
+ if (engine.name != "POST Search")
+ return;
+
+ Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+
+ // Ready to execute the tests!
+ let needle = "Search for something awesome.";
+
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.defaultEngine = engine;
+ yield p;
+
+ let promise = BrowserTestUtils.browserLoaded(browser);
+
+ yield ContentTask.spawn(browser, { needle }, function* (args) {
+ let doc = content.document;
+ doc.getElementById("searchText").value = args.needle;
+ doc.getElementById("searchSubmit").click();
+ });
+
+ yield promise;
+
+ // When the search results load, check them for correctness.
+ yield ContentTask.spawn(browser, { needle }, function* (args) {
+ let loadedText = content.document.body.textContent;
+ ok(loadedText, "search page loaded");
+ is(loadedText, "searchterms=" + escape(args.needle.replace(/\s/g, "+")),
+ "Search text should arrive correctly");
+ });
+
+ Services.search.defaultEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) {}
+ resolve();
+ });
+ Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+ Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
+ null, null, false);
+ });
+ });
+});
+
+add_task(function* () {
+ info("Make sure that a page can't imitate about:home");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ let promise = BrowserTestUtils.browserLoaded(browser);
+ browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html");
+ yield promise;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let button = content.document.getElementById("settings");
+ ok(button, "Found settings button in test page");
+ button.click();
+ });
+
+ yield new Promise(resolve => {
+ // It may take a few turns of the event loop before the window
+ // is displayed, so we wait.
+ function check(n) {
+ let win = Services.wm.getMostRecentWindow("Browser:Preferences");
+ ok(!win, "Preferences window not showing");
+ if (win) {
+ win.close();
+ }
+
+ if (n > 0) {
+ executeSoon(() => check(n-1));
+ } else {
+ resolve();
+ }
+ }
+
+ check(5);
+ });
+ });
+});
+
+add_task(function* () {
+ // See browser_contentSearchUI.js for comprehensive content search UI tests.
+ info("Search suggestion smoke test");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ // Add a test engine that provides suggestions and switch to it.
+ let currEngine = Services.search.currentEngine;
+ let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.currentEngine = engine;
+ yield p;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // Avoid intermittent failures.
+ content.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
+
+ // Type an X in the search input.
+ let input = content.document.getElementById("searchText");
+ input.focus();
+ });
+
+ yield BrowserTestUtils.synthesizeKey("x", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // Wait for the search suggestions to become visible.
+ let table = content.document.getElementById("searchSuggestionTable");
+ let input = content.document.getElementById("searchText");
+
+ yield new Promise(resolve => {
+ let observer = new content.MutationObserver(() => {
+ if (input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ ok(!table.hidden, "Search suggestion table unhidden");
+ resolve();
+ }
+ });
+ observer.observe(input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+ });
+ });
+
+ // Empty the search input, causing the suggestions to be hidden.
+ yield BrowserTestUtils.synthesizeKey("a", { accelKey: true }, browser);
+ yield BrowserTestUtils.synthesizeKey("VK_DELETE", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let table = content.document.getElementById("searchSuggestionTable");
+ yield ContentTaskUtils.waitForCondition(() => table.hidden,
+ "Search suggestion table hidden");
+ });
+
+ Services.search.currentEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) { }
+ });
+});
+
+add_task(function* () {
+ info("Clicking suggestion list while composing");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ // Add a test engine that provides suggestions and switch to it.
+ let currEngine = Services.search.currentEngine;
+ let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+ let p = promiseContentSearchChange(browser, engine.name);
+ Services.search.currentEngine = engine;
+ yield p;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // Start composition and type "x"
+ let input = content.document.getElementById("searchText");
+ input.focus();
+ });
+
+ yield BrowserTestUtils.synthesizeComposition({
+ type: "compositionstart",
+ data: ""
+ }, browser);
+ yield BrowserTestUtils.synthesizeCompositionChange({
+ composition: {
+ string: "x",
+ clauses: [
+ { length: 1, attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 1, length: 0 }
+ }, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let searchController = content.wrappedJSObject.gContentSearchController;
+
+ // Wait for the search suggestions to become visible.
+ let table = searchController._suggestionsList;
+ let input = content.document.getElementById("searchText");
+
+ yield new Promise(resolve => {
+ let observer = new content.MutationObserver(() => {
+ if (input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ ok(!table.hidden, "Search suggestion table unhidden");
+ resolve();
+ }
+ });
+ observer.observe(input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+ });
+
+ let row = table.children[1];
+ row.setAttribute("id", "TEMPID");
+
+ // ContentSearchUIController looks at the current selectedIndex when
+ // performing a search. Synthesizing the mouse event on the suggestion
+ // doesn't actually mouseover the suggestion and trigger it to be flagged
+ // as selected, so we manually select it first.
+ searchController.selectedIndex = 1;
+ });
+
+ // Click the second suggestion.
+ let expectedURL = Services.search.currentEngine
+ .getSubmission("xbar", null, "homepage").uri.spec;
+ let loadPromise = waitForDocLoadAndStopIt(expectedURL);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#TEMPID", {
+ button: 0
+ }, browser);
+ yield loadPromise;
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let input = content.document.getElementById("searchText");
+ ok(input.value == "x", "Input value did not change");
+
+ let row = content.document.getElementById("TEMPID");
+ if (row) {
+ row.removeAttribute("id");
+ }
+ });
+
+ Services.search.currentEngine = currEngine;
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) { }
+ });
+});
+
+add_task(function* () {
+ info("Pressing any key should focus the search box in the page, and send the key to it");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ isnot(doc.getElementById("searchText"), doc.activeElement,
+ "Search input should not be the active element.");
+ });
+
+ yield BrowserTestUtils.synthesizeKey("a", {}, browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let doc = content.document;
+ let searchInput = doc.getElementById("searchText");
+ yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput,
+ "Search input should be the active element.");
+ is(searchInput.value, "a", "Search input should be 'a'.");
+ });
+ });
+});
+
+add_task(function* () {
+ info("Cmd+k should focus the search box in the toolbar when it's present");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
+
+ let doc = window.document;
+ let searchInput = doc.getElementById("searchbar").textbox.inputField;
+ isnot(searchInput, doc.activeElement, "Search bar should not be the active element.");
+
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ yield promiseWaitForCondition(() => doc.activeElement === searchInput);
+ is(searchInput, doc.activeElement, "Search bar should be the active element.");
+ });
+});
+
+add_task(function* () {
+ info("Sync button should open about:preferences#sync");
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ let oldOpenPrefs = window.openPreferences;
+ let openPrefsPromise = new Promise(resolve => {
+ window.openPreferences = function (pane, params) {
+ resolve({ pane: pane, params: params });
+ };
+ });
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#sync", {}, browser);
+
+ let result = yield openPrefsPromise;
+ window.openPreferences = oldOpenPrefs;
+
+ is(result.pane, "paneSync", "openPreferences should be called with paneSync");
+ is(result.params.urlParams.entrypoint, "abouthome",
+ "openPreferences should be called with abouthome entrypoint");
+ });
+});
+
+add_task(function* () {
+ info("Pressing Space while the Addons button is focused should activate it");
+
+ // Skip this test on Mac, because Space doesn't activate the button there.
+ if (AppConstants.platform == "macosx") {
+ return;
+ }
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+ info("Waiting for about:addons tab to open...");
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let addOnsButton = content.document.getElementById("addons");
+ addOnsButton.focus();
+ });
+ yield BrowserTestUtils.synthesizeKey(" ", {}, browser);
+
+ let tab = yield promiseTabOpened;
+ is(tab.linkedBrowser.currentURI.spec, "about:addons",
+ "Should have seen the about:addons tab");
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+/**
+ * Cleans up snippets and ensures that by default we don't try to check for
+ * remote snippets since that may cause network bustage or slowness.
+ *
+ * @param aSetupFn
+ * The setup function to be run.
+ * @param testFn
+ * the content task to run
+ * @param testArgs (optional)
+ * the parameters to pass to the content task
+ * @param parentFn (optional)
+ * the function to run in the parent after the content task has completed.
+ * @return {Promise} resolved when the snippets are ready. Gets the snippets map.
+ */
+function* withSnippetsMap(setupFn, testFn, testArgs = null, parentFn = null) {
+ let setupFnSource;
+ if (setupFn) {
+ setupFnSource = setupFn.toSource();
+ }
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ let promiseAfterLocationChange = () => {
+ return ContentTask.spawn(browser, {
+ setupFnSource,
+ version: AboutHomeUtils.snippetsVersion,
+ }, function* (args) {
+ return new Promise(resolve => {
+ let document = content.document;
+ // We're not using Promise-based listeners, because they resolve asynchronously.
+ // The snippets test setup code relies on synchronous behaviour here.
+ document.addEventListener("AboutHomeLoadSnippets", function loadSnippets() {
+ document.removeEventListener("AboutHomeLoadSnippets", loadSnippets);
+
+ let updateSnippets;
+ if (args.setupFnSource) {
+ updateSnippets = eval(`(() => (${args.setupFnSource}))()`);
+ }
+
+ content.wrappedJSObject.ensureSnippetsMapThen(snippetsMap => {
+ snippetsMap = Cu.waiveXrays(snippetsMap);
+ info("Got snippets map: " +
+ "{ last-update: " + snippetsMap.get("snippets-last-update") +
+ ", cached-version: " + snippetsMap.get("snippets-cached-version") +
+ " }");
+ // Don't try to update.
+ snippetsMap.set("snippets-last-update", Date.now());
+ snippetsMap.set("snippets-cached-version", args.version);
+ // Clear snippets.
+ snippetsMap.delete("snippets");
+
+ if (updateSnippets) {
+ updateSnippets(snippetsMap);
+ }
+
+ // Tack it to the global object
+ content.gSnippetsMap = snippetsMap;
+
+ resolve();
+ });
+ });
+ });
+ });
+ };
+
+ // We'd like to listen to the 'AboutHomeLoadSnippets' event on a fresh
+ // document as soon as technically possible, so we use webProgress.
+ let promise = new Promise(resolve => {
+ let wpl = {
+ onLocationChange() {
+ gBrowser.removeProgressListener(wpl);
+ // Phase 2: retrieving the snippets map is the next promise on our agenda.
+ promiseAfterLocationChange().then(resolve);
+ },
+ onProgressChange() {},
+ onStatusChange() {},
+ onSecurityChange() {}
+ };
+ gBrowser.addProgressListener(wpl);
+ });
+
+ // Set the URL to 'about:home' here to allow capturing the 'AboutHomeLoadSnippets'
+ // event.
+ browser.loadURI("about:home");
+ // Wait for LocationChange.
+ yield promise;
+
+ yield ContentTask.spawn(browser, testArgs, testFn);
+ if (parentFn) {
+ yield parentFn();
+ }
+ });
+}
+
+function promiseContentSearchChange(browser, newEngineName) {
+ return ContentTask.spawn(browser, { newEngineName }, function* (args) {
+ return new Promise(resolve => {
+ content.addEventListener("ContentSearchService", function listener(aEvent) {
+ if (aEvent.detail.type == "CurrentState" &&
+ content.wrappedJSObject.gContentSearchController.defaultEngine.name == args.newEngineName) {
+ content.removeEventListener("ContentSearchService", listener);
+ resolve();
+ }
+ });
+ });
+ });
+}
+
+function promiseNewEngine(basename) {
+ info("Waiting for engine to be added: " + basename);
+ return new Promise((resolve, reject) => {
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ registerCleanupFunction(() => {
+ try {
+ Services.search.removeEngine(engine);
+ } catch (ex) { /* Can't remove the engine more than once */ }
+ });
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js b/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js
new file mode 100644
index 000000000..bfe0fe9c8
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutHome_wrapsCorrectly.js
@@ -0,0 +1,28 @@
+add_task(function* () {
+ let newWindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let resizedPromise = BrowserTestUtils.waitForEvent(newWindow, "resize");
+ newWindow.resizeTo(300, 300);
+ yield resizedPromise;
+
+ yield BrowserTestUtils.openNewForegroundTab(newWindow.gBrowser, "about:home");
+
+ yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
+ Assert.equal(content.document.body.getAttribute("narrow"), "true", "narrow mode");
+ });
+
+ resizedPromise = BrowserTestUtils.waitForContentEvent(newWindow.gBrowser.selectedBrowser, "resize");
+
+
+ yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
+ content.window.resizeTo(800, 800);
+ });
+
+ yield resizedPromise;
+
+ yield ContentTask.spawn(newWindow.gBrowser.selectedBrowser, {}, function* () {
+ Assert.equal(content.document.body.hasAttribute("narrow"), false, "non-narrow mode");
+ });
+
+ yield BrowserTestUtils.closeWindow(newWindow);
+});
diff --git a/browser/base/content/test/general/browser_aboutNetError.js b/browser/base/content/test/general/browser_aboutNetError.js
new file mode 100644
index 000000000..5185cbcaa
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutNetError.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Set ourselves up for TLS error
+Services.prefs.setIntPref("security.tls.version.max", 3);
+Services.prefs.setIntPref("security.tls.version.min", 3);
+
+const LOW_TLS_VERSION = "https://tls1.example.com/";
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+add_task(function* checkReturnToPreviousPage() {
+ info("Loading a TLS page that isn't supported, ensure we have a fix button and clicking it then loads the page");
+ let browser;
+ let pageLoaded;
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ gBrowser.selectedTab = gBrowser.addTab(LOW_TLS_VERSION);
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ }, false);
+
+ info("Loading and waiting for the net error");
+ yield pageLoaded;
+
+ // NB: This code assumes that the error page and the test page load in the
+ // same process. If this test starts to fail, it could be because they load
+ // in different processes.
+ yield ContentTask.spawn(browser, LOW_TLS_VERSION, function* (LOW_TLS_VERSION_) {
+ ok(content.document.getElementById("prefResetButton").getBoundingClientRect().left >= 0,
+ "Should have a visible button");
+
+ ok(content.document.documentURI.startsWith("about:neterror"), "Should be showing error page");
+
+ let doc = content.document;
+ let prefResetButton = doc.getElementById("prefResetButton");
+ is(prefResetButton.getAttribute("autofocus"), "true", "prefResetButton has autofocus");
+ prefResetButton.click();
+
+ yield ContentTaskUtils.waitForEvent(this, "pageshow", true);
+
+ is(content.document.documentURI, LOW_TLS_VERSION_, "Should not be showing page");
+ });
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js b/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js
new file mode 100644
index 000000000..e574ba978
--- /dev/null
+++ b/browser/base/content/test/general/browser_aboutSupport_newtab_security_state.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: window.location is null");
+
+
+add_task(function* checkIdentityOfAboutSupport() {
+ let tab = gBrowser.loadOneTab("about:support", {
+ referrerURI: null,
+ inBackground: false,
+ allowThirdPartyFixup: false,
+ relatedToCurrent: false,
+ skipAnimation: true,
+ allowMixedContent: false
+ });
+
+ yield promiseTabLoaded(tab);
+ let identityBox = document.getElementById("identity-box");
+ is(identityBox.className, "chromeUI", "Should know that we're chrome.");
+ gBrowser.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/general/browser_accesskeys.js b/browser/base/content/test/general/browser_accesskeys.js
new file mode 100644
index 000000000..56fe3995f
--- /dev/null
+++ b/browser/base/content/test/general/browser_accesskeys.js
@@ -0,0 +1,82 @@
+add_task(function *() {
+ yield pushPrefs(["ui.key.contentAccess", 5], ["ui.key.chromeAccess", 5]);
+
+ const gPageURL1 = "data:text/html,<body><p>" +
+ "<button id='button' accesskey='y'>Button</button>" +
+ "<input id='checkbox' type='checkbox' accesskey='z'>Checkbox" +
+ "</p></body>";
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL1);
+ tab1.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+ Services.focus.clearFocus(window);
+
+ // Press an accesskey in the child document while the chrome is focused.
+ let focusedId = yield performAccessKey("y");
+ is(focusedId, "button", "button accesskey");
+
+ // Press an accesskey in the child document while the content document is focused.
+ focusedId = yield performAccessKey("z");
+ is(focusedId, "checkbox", "checkbox accesskey");
+
+ // Add an element with an accesskey to the chrome and press its accesskey while the chrome is focused.
+ let newButton = document.createElement("button");
+ newButton.id = "chromebutton";
+ newButton.setAttribute("accesskey", "z");
+ document.documentElement.appendChild(newButton);
+
+ Services.focus.clearFocus(window);
+
+ focusedId = yield performAccessKeyForChrome("z");
+ is(focusedId, "chromebutton", "chromebutton accesskey");
+
+ // Add a second tab and ensure that accesskey from the first tab is not used.
+ const gPageURL2 = "data:text/html,<body>" +
+ "<button id='tab2button' accesskey='y'>Button in Tab 2</button>" +
+ "</body>";
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL2);
+ tab2.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
+
+ Services.focus.clearFocus(window);
+
+ focusedId = yield performAccessKey("y");
+ is(focusedId, "tab2button", "button accesskey in tab2");
+
+ // Press the accesskey for the chrome element while the content document is focused.
+ focusedId = yield performAccessKeyForChrome("z");
+ is(focusedId, "chromebutton", "chromebutton accesskey");
+
+ newButton.parentNode.removeChild(newButton);
+
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+});
+
+function childHandleFocus() {
+ content.document.body.firstChild.addEventListener("focus", function focused(event) {
+ let focusedElement = content.document.activeElement;
+ focusedElement.blur();
+ sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id })
+ }, true);
+}
+
+function performAccessKey(key)
+{
+ return new Promise(resolve => {
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("Test:FocusFromAccessKey", function listenForFocus(msg) {
+ mm.removeMessageListener("Test:FocusFromAccessKey", listenForFocus);
+ resolve(msg.data.focus);
+ });
+
+ EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
+ });
+}
+
+// This version is used when a chrome elemnt is expected to be found for an accesskey.
+function* performAccessKeyForChrome(key, inChild)
+{
+ let waitFocusChangePromise = BrowserTestUtils.waitForEvent(document, "focus", true);
+ EventUtils.synthesizeKey(key, { altKey: true, shiftKey: true });
+ yield waitFocusChangePromise;
+ return document.activeElement.id;
+}
diff --git a/browser/base/content/test/general/browser_addCertException.js b/browser/base/content/test/general/browser_addCertException.js
new file mode 100644
index 000000000..e2cf34b47
--- /dev/null
+++ b/browser/base/content/test/general/browser_addCertException.js
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test adding a certificate exception by attempting to browse to a site with
+// a bad certificate, being redirected to the internal about:certerror page,
+// using the button contained therein to load the certificate exception
+// dialog, using that to add an exception, and finally successfully visiting
+// the site, including showing the right identity box and control center icons.
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ yield loadBadCertPage("https://expired.example.com");
+ checkControlPanelIcons();
+ let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride("expired.example.com", -1);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Check for the correct icons in the identity box and control center.
+function checkControlPanelIcons() {
+ let { gIdentityHandler } = gBrowser.ownerGlobal;
+ gIdentityHandler._identityBox.click();
+ document.getElementById("identity-popup-security-expander").click();
+
+ is_element_visible(document.getElementById("connection-icon"), "Should see connection icon");
+ let connectionIconImage = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("connection-icon"), "")
+ .getPropertyValue("list-style-image");
+ let securityViewBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-securityView"), "")
+ .getPropertyValue("background-image");
+ let securityContentBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-security-content"), "")
+ .getPropertyValue("background-image");
+ is(connectionIconImage,
+ "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using expected icon image in the identity block");
+ is(securityViewBG,
+ "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using expected icon image in the Control Center main view");
+ is(securityContentBG,
+ "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using expected icon image in the Control Center subview");
+
+ gIdentityHandler._identityPopup.hidden = true;
+}
+
diff --git a/browser/base/content/test/general/browser_addKeywordSearch.js b/browser/base/content/test/general/browser_addKeywordSearch.js
new file mode 100644
index 000000000..f38050b43
--- /dev/null
+++ b/browser/base/content/test/general/browser_addKeywordSearch.js
@@ -0,0 +1,81 @@
+var testData = [
+ { desc: "No path",
+ action: "http://example.com/",
+ param: "q",
+ },
+ { desc: "With path",
+ action: "http://example.com/new-path-here/",
+ param: "q",
+ },
+ { desc: "No action",
+ action: "",
+ param: "q",
+ },
+ { desc: "With Query String",
+ action: "http://example.com/search?oe=utf-8",
+ param: "q",
+ },
+];
+
+add_task(function*() {
+ const TEST_URL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let count = 0;
+ for (let method of ["GET", "POST"]) {
+ for (let {desc, action, param } of testData) {
+ info(`Running ${method} keyword test '${desc}'`);
+ let id = `keyword-form-${count++}`;
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenuPromise =
+ BrowserTestUtils.waitForEvent(contextMenu, "popupshown")
+ .then(() => gContextMenuContentData.popupNode);
+
+ yield ContentTask.spawn(tab.linkedBrowser,
+ { action, param, method, id }, function* (args) {
+ let doc = content.document;
+ let form = doc.createElement("form");
+ form.id = args.id;
+ form.method = args.method;
+ form.action = args.action;
+ let element = doc.createElement("input");
+ element.setAttribute("type", "text");
+ element.setAttribute("name", args.param);
+ form.appendChild(element);
+ doc.body.appendChild(form);
+ });
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter(`#${id} > input`,
+ { type : "contextmenu", button : 2 },
+ tab.linkedBrowser);
+ let target = yield contextMenuPromise;
+
+ yield new Promise(resolve => {
+ let url = action || tab.linkedBrowser.currentURI.spec;
+ let mm = tab.linkedBrowser.messageManager;
+ let onMessage = (message) => {
+ mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+ if (method == "GET") {
+ ok(message.data.spec.endsWith(`${param}=%s`),
+ `Check expected url for field named ${param} and action ${action}`);
+ } else {
+ is(message.data.spec, url,
+ `Check expected url for field named ${param} and action ${action}`);
+ is(message.data.postData, `${param}%3D%25s`,
+ `Check expected POST data for field named ${param} and action ${action}`);
+ }
+ resolve();
+ };
+ mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+ mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", null, { target });
+ });
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_alltabslistener.js b/browser/base/content/test/general/browser_alltabslistener.js
new file mode 100644
index 000000000..a56473ec9
--- /dev/null
+++ b/browser/base/content/test/general/browser_alltabslistener.js
@@ -0,0 +1,206 @@
+var Ci = Components.interfaces;
+
+const gCompleteState = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+var gFrontProgressListener = {
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("FrontProgress: " + state + " 0x" + aStateFlags.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var state = "onLocationChange";
+ info("FrontProgress: " + state + " " + aLocationURI.spec);
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("FrontProgress: " + state + " 0x" + aState.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ }
+}
+
+var gAllProgressListener = {
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("AllProgress: " + state + " 0x" + aStateFlags.toString(16));
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+
+ if ((aStateFlags & gCompleteState) == gCompleteState) {
+ ok(gAllNotificationsPos == gAllNotifications.length, "Saw the expected number of notifications");
+ ok(gFrontNotificationsPos == gFrontNotifications.length, "Saw the expected number of frontnotifications");
+ executeSoon(gNextTest);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ var state = "onLocationChange";
+ info("AllProgress: " + state + " " + aLocationURI.spec);
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ },
+
+ onStatusChange: function (aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
+ var state = "onStatusChange";
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ },
+
+ onSecurityChange: function (aBrowser, aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("AllProgress: " + state + " 0x" + aState.toString(16));
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ }
+}
+
+var gFrontNotifications, gAllNotifications, gFrontNotificationsPos, gAllNotificationsPos;
+var gBackgroundTab, gForegroundTab, gBackgroundBrowser, gForegroundBrowser, gTestBrowser;
+var gTestPage = "/browser/browser/base/content/test/general/alltabslistener.html";
+const kBasePage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+var gNextTest;
+
+function test() {
+ waitForExplicitFinish();
+
+ gBackgroundTab = gBrowser.addTab();
+ gForegroundTab = gBrowser.addTab();
+ gBackgroundBrowser = gBrowser.getBrowserForTab(gBackgroundTab);
+ gForegroundBrowser = gBrowser.getBrowserForTab(gForegroundTab);
+ gBrowser.selectedTab = gForegroundTab;
+
+ // We must wait until a page has completed loading before
+ // starting tests or we get notifications from that
+ let promises = [
+ waitForDocLoadComplete(gBackgroundBrowser),
+ waitForDocLoadComplete(gForegroundBrowser)
+ ];
+ gBackgroundBrowser.loadURI(kBasePage);
+ gForegroundBrowser.loadURI(kBasePage);
+ Promise.all(promises).then(startTest1);
+}
+
+function runTest(browser, url, next) {
+ gFrontNotificationsPos = 0;
+ gAllNotificationsPos = 0;
+ gNextTest = next;
+ gTestBrowser = browser;
+ browser.loadURI(url);
+}
+
+function startTest1() {
+ info("\nTest 1");
+ gBrowser.addProgressListener(gFrontProgressListener);
+ gBrowser.addTabsProgressListener(gAllProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2);
+}
+
+function startTest2() {
+ info("\nTest 2");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3);
+}
+
+function startTest3() {
+ info("\nTest 3");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4);
+}
+
+function startTest4() {
+ info("\nTest 4");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5);
+}
+
+function startTest5() {
+ info("\nTest 5");
+ // Switch the foreground browser
+ [gForegroundBrowser, gBackgroundBrowser] = [gBackgroundBrowser, gForegroundBrowser];
+ [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab];
+ // Avoid the onLocationChange this will fire
+ gBrowser.removeProgressListener(gFrontProgressListener);
+ gBrowser.selectedTab = gForegroundTab;
+ gBrowser.addProgressListener(gFrontProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6);
+}
+
+function startTest6() {
+ info("\nTest 6");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest);
+}
+
+function finishTest() {
+ gBrowser.removeProgressListener(gFrontProgressListener);
+ gBrowser.removeTabsProgressListener(gAllProgressListener);
+ gBrowser.removeTab(gBackgroundTab);
+ gBrowser.removeTab(gForegroundTab);
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_audioTabIcon.js b/browser/base/content/test/general/browser_audioTabIcon.js
new file mode 100644
index 000000000..4d7a7bbd8
--- /dev/null
+++ b/browser/base/content/test/general/browser_audioTabIcon.js
@@ -0,0 +1,504 @@
+const PAGE = "https://example.com/browser/browser/base/content/test/general/file_mediaPlayback.html";
+const TABATTR_REMOVAL_PREFNAME = "browser.tabs.delayHidingAudioPlayingIconMS";
+const INITIAL_TABATTR_REMOVAL_DELAY_MS = Services.prefs.getIntPref(TABATTR_REMOVAL_PREFNAME);
+
+function* wait_for_tab_playing_event(tab, expectPlaying) {
+ if (tab.soundPlaying == expectPlaying) {
+ ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
+ return true;
+ }
+ return yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => {
+ if (event.detail.changed.includes("soundplaying")) {
+ is(tab.hasAttribute("soundplaying"), expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
+ is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing");
+ return true;
+ }
+ return false;
+ });
+}
+
+function* play(tab) {
+ let browser = tab.linkedBrowser;
+ yield ContentTask.spawn(browser, {}, function* () {
+ let audio = content.document.querySelector("audio");
+ audio.play();
+ });
+
+ yield wait_for_tab_playing_event(tab, true);
+}
+
+function* pause(tab, options) {
+ ok(tab.hasAttribute("soundplaying"), "The tab should have the soundplaying attribute when pause() is called");
+
+ let extendedDelay = options && options.extendedDelay;
+ if (extendedDelay) {
+ // Use 10s to remove possibility of race condition with attr removal.
+ Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, 10000);
+ }
+
+ try {
+ let browser = tab.linkedBrowser;
+ let awaitDOMAudioPlaybackStopped =
+ BrowserTestUtils.waitForEvent(browser, "DOMAudioPlaybackStopped", "DOMAudioPlaybackStopped event should get fired after pause");
+ let awaitTabPausedAttrModified =
+ wait_for_tab_playing_event(tab, false);
+ yield ContentTask.spawn(browser, {}, function* () {
+ let audio = content.document.querySelector("audio");
+ audio.pause();
+ });
+
+ if (extendedDelay) {
+ ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after pausing");
+
+ yield awaitDOMAudioPlaybackStopped;
+ ok(tab.hasAttribute("soundplaying"), "The tab should still have the soundplaying attribute immediately after DOMAudioPlaybackStopped");
+ }
+
+ yield awaitTabPausedAttrModified;
+ ok(!tab.hasAttribute("soundplaying"), "The tab should not have the soundplaying attribute after the timeout has resolved");
+ } finally {
+ // Make sure other tests don't timeout if an exception gets thrown above.
+ // Need to use setIntPref instead of clearUserPref because prefs_general.js
+ // overrides the default value to help this and other tests run faster.
+ Services.prefs.setIntPref(TABATTR_REMOVAL_PREFNAME, INITIAL_TABATTR_REMOVAL_DELAY_MS);
+ }
+}
+
+function disable_non_test_mouse(disable) {
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.disableNonTestMouseEvents(disable);
+}
+
+function* hover_icon(icon, tooltip) {
+ disable_non_test_mouse(true);
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
+ EventUtils.synthesizeMouse(icon, 1, 1, {type: "mouseover"});
+ EventUtils.synthesizeMouse(icon, 2, 2, {type: "mousemove"});
+ EventUtils.synthesizeMouse(icon, 3, 3, {type: "mousemove"});
+ EventUtils.synthesizeMouse(icon, 4, 4, {type: "mousemove"});
+ return popupShownPromise;
+}
+
+function leave_icon(icon) {
+ EventUtils.synthesizeMouse(icon, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+
+ disable_non_test_mouse(false);
+}
+
+function* test_tooltip(icon, expectedTooltip, isActiveTab) {
+ let tooltip = document.getElementById("tabbrowser-tab-tooltip");
+
+ yield hover_icon(icon, tooltip);
+ if (isActiveTab) {
+ // The active tab should have the keybinding shortcut in the tooltip.
+ // We check this by ensuring that the strings are not equal but the expected
+ // message appears in the beginning.
+ isnot(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
+ is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected");
+ } else {
+ is(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
+ }
+ leave_icon(icon);
+}
+
+// The set of tabs which have ever had their mute state changed.
+// Used to determine whether the tab should have a muteReason value.
+let everMutedTabs = new WeakSet();
+
+function get_wait_for_mute_promise(tab, expectMuted) {
+ return BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, event => {
+ if (event.detail.changed.includes("muted")) {
+ is(tab.hasAttribute("muted"), expectMuted, "The tab should " + (expectMuted ? "" : "not ") + "be muted");
+ is(tab.muted, expectMuted, "The tab muted property " + (expectMuted ? "" : "not ") + "be true");
+
+ if (expectMuted || everMutedTabs.has(tab)) {
+ everMutedTabs.add(tab);
+ is(tab.muteReason, null, "The tab should have a null muteReason value");
+ } else {
+ is(tab.muteReason, undefined, "The tab should have an undefined muteReason value");
+ }
+ return true;
+ }
+ return false;
+ });
+}
+
+function* test_mute_tab(tab, icon, expectMuted) {
+ let mutedPromise = test_mute_keybinding(tab, expectMuted);
+
+ let activeTab = gBrowser.selectedTab;
+
+ let tooltip = document.getElementById("tabbrowser-tab-tooltip");
+
+ yield hover_icon(icon, tooltip);
+ EventUtils.synthesizeMouseAtCenter(icon, {button: 0});
+ leave_icon(icon);
+
+ is(gBrowser.selectedTab, activeTab, "Clicking on mute should not change the currently selected tab");
+
+ return mutedPromise;
+}
+
+function get_tab_state(tab) {
+ const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ return JSON.parse(ss.getTabState(tab));
+}
+
+function* test_muting_using_menu(tab, expectMuted) {
+ // Show the popup menu
+ let contextMenu = document.getElementById("tabContextMenu");
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(tab, {type: "contextmenu", button: 2});
+ yield popupShownPromise;
+
+ // Check the menu
+ let expectedLabel = expectMuted ? "Unmute Tab" : "Mute Tab";
+ let toggleMute = document.getElementById("context_toggleMuteTab");
+ is(toggleMute.label, expectedLabel, "Correct label expected");
+ is(toggleMute.accessKey, "M", "Correct accessKey expected");
+
+ is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+ ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
+
+ yield play(tab);
+
+ is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+ ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute");
+
+ yield pause(tab);
+
+ is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
+ ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
+
+ // Click on the menu and wait for the tab to be muted.
+ let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted);
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ EventUtils.synthesizeMouseAtCenter(toggleMute, {});
+ yield popupHiddenPromise;
+ yield mutedPromise;
+}
+
+function* test_playing_icon_on_tab(tab, browser, isPinned) {
+ let icon = document.getAnonymousElementByAttribute(tab, "anonid",
+ isPinned ? "overlay-icon" : "soundplaying-icon");
+ let isActiveTab = tab === gBrowser.selectedTab;
+
+ yield play(tab);
+
+ yield test_tooltip(icon, "Mute tab", isActiveTab);
+
+ ok(!("muted" in get_tab_state(tab)), "No muted attribute should be persisted");
+ ok(!("muteReason" in get_tab_state(tab)), "No muteReason property should be persisted");
+
+ yield test_mute_tab(tab, icon, true);
+
+ ok("muted" in get_tab_state(tab), "Muted attribute should be persisted");
+ ok("muteReason" in get_tab_state(tab), "muteReason property should be persisted");
+
+ yield test_tooltip(icon, "Unmute tab", isActiveTab);
+
+ yield test_mute_tab(tab, icon, false);
+
+ ok(!("muted" in get_tab_state(tab)), "No muted attribute should be persisted");
+ ok(!("muteReason" in get_tab_state(tab)), "No muteReason property should be persisted");
+
+ yield test_tooltip(icon, "Mute tab", isActiveTab);
+
+ yield test_mute_tab(tab, icon, true);
+
+ yield pause(tab);
+
+ ok(tab.hasAttribute("muted") &&
+ !tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing");
+ ok(tab.muted && !tab.soundPlaying, "Tab should still be muted but not playing");
+
+ yield test_tooltip(icon, "Unmute tab", isActiveTab);
+
+ yield test_mute_tab(tab, icon, false);
+
+ ok(!tab.hasAttribute("muted") &&
+ !tab.hasAttribute("soundplaying"), "Tab should not be be muted or playing");
+ ok(!tab.muted && !tab.soundPlaying, "Tab should not be be muted or playing");
+
+ // Make sure it's possible to mute using the context menu.
+ yield test_muting_using_menu(tab, false);
+
+ // Make sure it's possible to unmute using the context menu.
+ yield test_muting_using_menu(tab, true);
+}
+
+function* test_swapped_browser_while_playing(oldTab, newBrowser) {
+ ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab");
+ is(oldTab.muteReason, null, "Expected the correct muteReason attribute on the old tab");
+ ok(oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab");
+
+ let newTab = gBrowser.getTabForBrowser(newBrowser);
+ let AttrChangePromise = BrowserTestUtils.waitForEvent(newTab, "TabAttrModified", false, event => {
+ return event.detail.changed.includes("soundplaying") &&
+ event.detail.changed.includes("muted");
+ });
+
+ gBrowser.swapBrowsersAndCloseOther(newTab, oldTab);
+ yield AttrChangePromise;
+
+ ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
+ is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
+ ok(newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
+
+ let icon = document.getAnonymousElementByAttribute(newTab, "anonid",
+ "soundplaying-icon");
+ yield test_tooltip(icon, "Unmute tab", true);
+}
+
+function* test_swapped_browser_while_not_playing(oldTab, newBrowser) {
+ ok(oldTab.hasAttribute("muted"), "Expected the correct muted attribute on the old tab");
+ is(oldTab.muteReason, null, "Expected the correct muteReason property on the old tab");
+ ok(!oldTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the old tab");
+
+ let newTab = gBrowser.getTabForBrowser(newBrowser);
+ let AttrChangePromise = BrowserTestUtils.waitForEvent(newTab, "TabAttrModified", false, event => {
+ return event.detail.changed.includes("muted");
+ });
+
+ let AudioPlaybackPromise = new Promise(resolve => {
+ let observer = (subject, topic, data) => {
+ ok(false, "Should not see an audio-playback notification");
+ };
+ Services.obs.addObserver(observer, "audiochannel-activity-normal", false);
+ setTimeout(() => {
+ Services.obs.removeObserver(observer, "audiochannel-activity-normal");
+ resolve();
+ }, 100);
+ });
+
+ gBrowser.swapBrowsersAndCloseOther(newTab, oldTab);
+ yield AttrChangePromise;
+
+ ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
+ is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
+ ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
+
+ // Wait to see if an audio-playback event is dispatched.
+ yield AudioPlaybackPromise;
+
+ ok(newTab.hasAttribute("muted"), "Expected the correct muted attribute on the new tab");
+ is(newTab.muteReason, null, "Expected the correct muteReason property on the new tab");
+ ok(!newTab.hasAttribute("soundplaying"), "Expected the correct soundplaying attribute on the new tab");
+
+ let icon = document.getAnonymousElementByAttribute(newTab, "anonid",
+ "soundplaying-icon");
+ yield test_tooltip(icon, "Unmute tab", true);
+}
+
+function* test_browser_swapping(tab, browser) {
+ // First, test swapping with a playing but muted tab.
+ yield play(tab);
+
+ let icon = document.getAnonymousElementByAttribute(tab, "anonid",
+ "soundplaying-icon");
+ yield test_mute_tab(tab, icon, true);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, function*(newBrowser) {
+ yield test_swapped_browser_while_playing(tab, newBrowser)
+
+ // Now, test swapping with a muted but not playing tab.
+ // Note that the tab remains muted, so we only need to pause playback.
+ tab = gBrowser.getTabForBrowser(newBrowser);
+ yield pause(tab);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, secondAboutBlankBrowser => test_swapped_browser_while_not_playing(tab, secondAboutBlankBrowser));
+ });
+}
+
+function* test_click_on_pinned_tab_after_mute() {
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ gBrowser.selectedTab = originallySelectedTab;
+ isnot(tab, gBrowser.selectedTab, "Sanity check, the tab should not be selected!");
+
+ // Steps to reproduce the bug:
+ // Pin the tab.
+ gBrowser.pinTab(tab);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ // Mute the tab.
+ let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon");
+ yield test_mute_tab(tab, icon, true);
+
+ // Pause playback and wait for it to finish.
+ yield pause(tab);
+
+ // Unmute tab.
+ yield test_mute_tab(tab, icon, false);
+
+ // Now click on the tab.
+ let image = document.getAnonymousElementByAttribute(tab, "anonid", "tab-icon-image");
+ EventUtils.synthesizeMouseAtCenter(image, {button: 0});
+
+ is(tab, gBrowser.selectedTab, "Tab switch should be successful");
+
+ // Cleanup.
+ gBrowser.unpinTab(tab);
+ gBrowser.selectedTab = originallySelectedTab;
+ }
+
+ let originallySelectedTab = gBrowser.selectedTab;
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+// This test only does something useful in e10s!
+function* test_cross_process_load() {
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false,
+ event => event.detail.changed.includes("soundplaying")
+ );
+
+ // Go to a different process.
+ browser.loadURI("about:");
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield soundPlayingStoppedPromise;
+
+ ok(!tab.hasAttribute("soundplaying"), "Tab should not be playing sound any more");
+ ok(!tab.soundPlaying, "Tab should not be playing sound any more");
+ }
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+function* test_mute_keybinding() {
+ function* test_muting_using_keyboard(tab) {
+ let mutedPromise = get_wait_for_mute_promise(tab, true);
+ EventUtils.synthesizeKey("m", {ctrlKey: true});
+ yield mutedPromise;
+ mutedPromise = get_wait_for_mute_promise(tab, false);
+ EventUtils.synthesizeKey("m", {ctrlKey: true});
+ yield mutedPromise;
+ }
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // Make sure it's possible to mute before the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ // Make sure it's possible to mute after the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ // Pause playback and wait for it to finish.
+ yield pause(tab);
+
+ // Make sure things work if the tab is pinned.
+ gBrowser.pinTab(tab);
+
+ // Make sure it's possible to mute before the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ // Start playback and wait for it to finish.
+ yield play(tab);
+
+ // Make sure it's possible to mute after the tab is playing.
+ yield test_muting_using_keyboard(tab);
+
+ gBrowser.unpinTab(tab);
+ }
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+function* test_on_browser(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // Test the icon in a normal tab.
+ yield test_playing_icon_on_tab(tab, browser, false);
+
+ gBrowser.pinTab(tab);
+
+ // Test the icon in a pinned tab.
+ yield test_playing_icon_on_tab(tab, browser, true);
+
+ gBrowser.unpinTab(tab);
+
+ // Retest with another browser in the foreground tab
+ if (gBrowser.selectedBrowser.currentURI.spec == PAGE) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "data:text/html,test"
+ }, () => test_on_browser(browser));
+ } else {
+ yield test_browser_swapping(tab, browser);
+ }
+}
+
+function* test_delayed_tabattr_removal() {
+ function* taskFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield play(tab);
+
+ // Extend the delay to guarantee the soundplaying attribute
+ // is not removed from the tab when audio is stopped. Without
+ // the extended delay the attribute could be removed in the
+ // same tick and the test wouldn't catch that this broke.
+ yield pause(tab, {extendedDelay: true});
+ }
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, taskFn);
+}
+
+add_task(function*() {
+ yield new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["browser.tabs.showAudioPlayingIcon", true],
+ ]}, resolve);
+ });
+});
+
+requestLongerTimeout(2);
+add_task(function* test_page() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE
+ }, test_on_browser);
+});
+
+add_task(test_click_on_pinned_tab_after_mute);
+
+add_task(test_cross_process_load);
+
+add_task(test_mute_keybinding);
+
+add_task(test_delayed_tabattr_removal);
diff --git a/browser/base/content/test/general/browser_backButtonFitts.js b/browser/base/content/test/general/browser_backButtonFitts.js
new file mode 100644
index 000000000..0e8aeeaee
--- /dev/null
+++ b/browser/base/content/test/general/browser_backButtonFitts.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(function* () {
+ let firstLocation = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, firstLocation);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ // Push the state before maximizing the window and clicking below.
+ content.history.pushState("page2", "page2", "page2");
+
+ // While in the child process, add a listener for the popstate event here. This
+ // event will fire when the mouse click happens.
+ content.addEventListener("popstate", function onPopState() {
+ content.removeEventListener("popstate", onPopState, false);
+ sendAsyncMessage("Test:PopStateOccurred", { location: content.document.location.href });
+ }, false);
+ });
+
+ window.maximize();
+
+ // Find where the nav-bar is vertically.
+ var navBar = document.getElementById("nav-bar");
+ var boundingRect = navBar.getBoundingClientRect();
+ var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
+ var xPixel = 0; // Use the first pixel of the screen since it is maximized.
+
+ let resultLocation = yield new Promise(resolve => {
+ messageManager.addMessageListener("Test:PopStateOccurred", function statePopped(message) {
+ messageManager.removeMessageListener("Test:PopStateOccurred", statePopped);
+ resolve(message.data.location);
+ });
+
+ EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window);
+ });
+
+ is(resultLocation, firstLocation, "Clicking the first pixel should have navigated back.");
+ window.restore();
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js
new file mode 100644
index 000000000..91a4a7e9c
--- /dev/null
+++ b/browser/base/content/test/general/browser_beforeunload_duplicate_dialogs.js
@@ -0,0 +1,76 @@
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html";
+
+var expectingDialog = false;
+var wantToClose = true;
+var resolveDialogPromise;
+function onTabModalDialogLoaded(node) {
+ ok(expectingDialog, "Should be expecting this dialog.");
+ expectingDialog = false;
+ if (wantToClose) {
+ // This accepts the dialog, closing it
+ node.Dialog.ui.button0.click();
+ } else {
+ // This keeps the page open
+ node.Dialog.ui.button1.click();
+ }
+ if (resolveDialogPromise) {
+ resolveDialogPromise();
+ }
+}
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+// Listen for the dialog being created
+Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.tabs.warnOnClose");
+ Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded");
+});
+
+add_task(function* closeLastTabInWindow() {
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let firstTab = newWin.gBrowser.selectedTab;
+ yield promiseTabLoadEvent(firstTab, TEST_PAGE);
+ let windowClosedPromise = promiseWindowWillBeClosed(newWin);
+ expectingDialog = true;
+ // close tab:
+ document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
+ yield windowClosedPromise;
+ ok(!expectingDialog, "There should have been a dialog.");
+ ok(newWin.closed, "Window should be closed.");
+});
+
+add_task(function* closeWindowWithMultipleTabsIncludingOneBeforeUnload() {
+ Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let firstTab = newWin.gBrowser.selectedTab;
+ yield promiseTabLoadEvent(firstTab, TEST_PAGE);
+ yield promiseTabLoadEvent(newWin.gBrowser.addTab(), "http://example.com/");
+ let windowClosedPromise = promiseWindowWillBeClosed(newWin);
+ expectingDialog = true;
+ newWin.BrowserTryToCloseWindow();
+ yield windowClosedPromise;
+ ok(!expectingDialog, "There should have been a dialog.");
+ ok(newWin.closed, "Window should be closed.");
+ Services.prefs.clearUserPref("browser.tabs.warnOnClose");
+});
+
+add_task(function* closeWindoWithSingleTabTwice() {
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let firstTab = newWin.gBrowser.selectedTab;
+ yield promiseTabLoadEvent(firstTab, TEST_PAGE);
+ let windowClosedPromise = promiseWindowWillBeClosed(newWin);
+ expectingDialog = true;
+ wantToClose = false;
+ let firstDialogShownPromise = new Promise((resolve, reject) => { resolveDialogPromise = resolve; });
+ document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
+ yield firstDialogShownPromise;
+ info("Got initial dialog, now trying again");
+ expectingDialog = true;
+ wantToClose = true;
+ resolveDialogPromise = null;
+ document.getAnonymousElementByAttribute(firstTab, "anonid", "close-button").click();
+ yield windowClosedPromise;
+ ok(!expectingDialog, "There should have been a dialog.");
+ ok(newWin.closed, "Window should be closed.");
+});
diff --git a/browser/base/content/test/general/browser_blob-channelname.js b/browser/base/content/test/general/browser_blob-channelname.js
new file mode 100644
index 000000000..d87e4a896
--- /dev/null
+++ b/browser/base/content/test/general/browser_blob-channelname.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ var file = new File([new Blob(['test'], {type: 'text/plain'})], "test-name");
+ var url = URL.createObjectURL(file);
+ var channel = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+
+ is(channel.contentDispositionFilename, 'test-name', "filename matches");
+}
diff --git a/browser/base/content/test/general/browser_blockHPKP.js b/browser/base/content/test/general/browser_blockHPKP.js
new file mode 100644
index 000000000..c0d1233ab
--- /dev/null
+++ b/browser/base/content/test/general/browser_blockHPKP.js
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that visiting a site pinned with HPKP headers does not succeed when it
+// uses a certificate with a key not in the pinset. This should result in an
+// about:neterror page
+// Also verify that removal of the HPKP headers succeeds (via HPKP headers)
+// and that after removal the visit to the site with the previously
+// unauthorized pins succeeds.
+//
+// This test required three certs to be created in build/pgo/certs:
+// 1. A new trusted root:
+// a. certutil -S -s "Alternate trusted authority" -s "CN=Alternate Trusted Authority" -t "C,," -x -m 1 -v 120 -n "alternateTrustedAuthority" -Z SHA256 -g 2048 -2 -d .
+// b. (export) certutil -L -d . -n "alternateTrustedAuthority" -a -o alternateroot.ca
+// (files ended in .ca are added as trusted roots by the mochitest harness)
+// 2. A good pinning server cert (signed by the pgo root):
+// certutil -S -n "dynamicPinningGood" -s "CN=dynamic-pinning.example.com" -c "pgo temporary ca" -t "P,," -k rsa -g 2048 -Z SHA256 -m 8939454 -v 120 -8 "*.include-subdomains.pinning-dynamic.example.com,*.pinning-dynamic.example.com" -d .
+// 3. A certificate with a different issuer, so as to cause a key pinning violation."
+// certutil -S -n "dynamicPinningBad" -s "CN=bad.include-subdomains.pinning-dynamic.example.com" -c "alternateTrustedAuthority" -t "P,," -k rsa -g 2048 -Z SHA256 -m 893945439 -v 120 -8 "bad.include-subdomains.pinning-dynamic.example.com" -d .
+
+const gSSService = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+const gIOService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+const kPinningDomain = "include-subdomains.pinning-dynamic.example.com";
+const khpkpPinninEnablePref = "security.cert_pinning.process_headers_from_non_builtin_roots";
+const kpkpEnforcementPref = "security.cert_pinning.enforcement_level";
+const kBadPinningDomain = "bad.include-subdomains.pinning-dynamic.example.com";
+const kURLPath = "/browser/browser/base/content/test/general/pinning_headers.sjs?";
+
+function test() {
+ waitForExplicitFinish();
+ // Enable enforcing strict pinning and processing headers from
+ // non-builtin roots.
+ Services.prefs.setIntPref(kpkpEnforcementPref, 2);
+ Services.prefs.setBoolPref(khpkpPinninEnablePref, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(kpkpEnforcementPref);
+ Services.prefs.clearUserPref(khpkpPinninEnablePref);
+ let uri = gIOService.newURI("https://" + kPinningDomain, null, null);
+ gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
+ });
+ whenNewTabLoaded(window, loadPinningPage);
+}
+
+// Start by making a successful connection to a domain that will pin a site
+function loadPinningPage() {
+
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "valid").then(function() {
+ gBrowser.selectedBrowser.addEventListener("load",
+ successfulPinningPageListener,
+ true);
+ });
+}
+
+// After the site is pinned try to load with a subdomain site that should
+// fail to validate
+var successfulPinningPageListener = {
+ handleEvent: function() {
+ gBrowser.selectedBrowser.removeEventListener("load", this, true);
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() {
+ return promiseErrorPageLoaded(gBrowser.selectedBrowser);
+ }).then(errorPageLoaded);
+ }
+};
+
+// The browser should load about:neterror, when this happens, proceed
+// to load the pinning domain again, this time removing the pinning information
+function errorPageLoaded() {
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let textElement = content.document.getElementById("errorShortDescText");
+ let text = textElement.innerHTML;
+ ok(text.indexOf("MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE") > 0,
+ "Got a pinning error page");
+ }).then(function() {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kPinningDomain + kURLPath + "zeromaxagevalid").then(function() {
+ return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ }).then(pinningRemovalLoaded);
+ });
+}
+
+// After the pinning information has been removed (successful load) proceed
+// to load again with the invalid pin domain.
+function pinningRemovalLoaded() {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://" + kBadPinningDomain).then(function() {
+ return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ }).then(badPinningPageLoaded);
+}
+
+// Finally, we should successfully load
+// https://bad.include-subdomains.pinning-dynamic.example.com.
+function badPinningPageLoaded() {
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(function() {
+ ok(true, "load complete");
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_bookmark_popup.js b/browser/base/content/test/general/browser_bookmark_popup.js
new file mode 100644
index 000000000..c1ddd725e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bookmark_popup.js
@@ -0,0 +1,431 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Test opening and closing the bookmarks panel.
+ */
+
+let bookmarkPanel = document.getElementById("editBookmarkPanel");
+let bookmarkStar = document.getElementById("bookmarks-menu-button");
+let bookmarkPanelTitle = document.getElementById("editBookmarkPanelTitle");
+let editBookmarkPanelRemoveButtonRect;
+
+StarUI._closePanelQuickForTesting = true;
+
+function* test_bookmarks_popup({isNewBookmark, popupShowFn, popupEditFn,
+ shouldAutoClose, popupHideFn, isBookmarkRemoved}) {
+ yield BrowserTestUtils.withNewTab({gBrowser, url: "about:home"}, function*(browser) {
+ try {
+ if (!isNewBookmark) {
+ yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "about:home",
+ title: "Home Page"
+ });
+ }
+
+ info(`BookmarkingUI.status is ${BookmarkingUI.status}`);
+ yield BrowserTestUtils.waitForCondition(
+ () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING,
+ "BookmarkingUI should not be updating");
+
+ is(bookmarkStar.hasAttribute("starred"), !isNewBookmark,
+ "Page should only be starred prior to popupshown if editing bookmark");
+ is(bookmarkPanel.state, "closed", "Panel should be 'closed' to start test");
+ let shownPromise = promisePopupShown(bookmarkPanel);
+ yield popupShowFn(browser);
+ yield shownPromise;
+ is(bookmarkPanel.state, "open", "Panel should be 'open' after shownPromise is resolved");
+
+ editBookmarkPanelRemoveButtonRect =
+ document.getElementById("editBookmarkPanelRemoveButton").getBoundingClientRect();
+
+ if (popupEditFn) {
+ yield popupEditFn();
+ }
+ let bookmarks = [];
+ yield PlacesUtils.bookmarks.fetch({url: "about:home"}, bm => bookmarks.push(bm));
+ is(bookmarks.length, 1, "Only one bookmark should exist");
+ is(bookmarkStar.getAttribute("starred"), "true", "Page is starred");
+ is(bookmarkPanelTitle.value,
+ isNewBookmark ?
+ gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
+ gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle"),
+ "title should match isEditingBookmark state");
+
+ if (!shouldAutoClose) {
+ yield new Promise(resolve => setTimeout(resolve, 400));
+ is(bookmarkPanel.state, "open", "Panel should still be 'open' for non-autoclose");
+ }
+
+ let hiddenPromise = promisePopupHidden(bookmarkPanel);
+ if (popupHideFn) {
+ yield popupHideFn();
+ }
+ yield hiddenPromise;
+ is(bookmarkStar.hasAttribute("starred"), !isBookmarkRemoved,
+ "Page is starred after closing");
+ } finally {
+ let bookmark = yield PlacesUtils.bookmarks.fetch({url: "about:home"});
+ is(!!bookmark, !isBookmarkRemoved,
+ "bookmark should not be present if a panel action should've removed it");
+ if (bookmark) {
+ yield PlacesUtils.bookmarks.remove(bookmark);
+ }
+ }
+ });
+}
+
+add_task(function* panel_shown_for_new_bookmarks_and_autocloses() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_once_for_doubleclick_on_new_bookmark_star_and_autocloses() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ EventUtils.synthesizeMouse(bookmarkStar, 10, 10, { clickCount: 2 },
+ window);
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_once_for_slow_doubleclick_on_new_bookmark_star_and_autocloses() {
+ todo(false, "bug 1250267, may need to add some tracking state to " +
+ "browser-places.js for this.");
+ return;
+
+ /*
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ *popupShowFn() {
+ EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
+ yield new Promise(resolve => setTimeout(resolve, 300));
+ EventUtils.synthesizeMouse(bookmarkStar, 10, 10, window);
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+ */
+});
+
+add_task(function* panel_shown_for_keyboardshortcut_on_new_bookmark_star_and_autocloses() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: true,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmarks_mousemove_mouseout() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ yield new Promise(resolve => setTimeout(resolve, 400));
+ is(bookmarkPanel.state, "open", "Panel should still be open on mousemove");
+ },
+ *popupHideFn() {
+ let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
+ EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ info("Waiting for mouseout event");
+ yield mouseOutPromise;
+ info("Got mouseout event, should autoclose now");
+ },
+ shouldAutoClose: false,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_no_autoclose_close_with_ESC() {
+ yield test_bookmarks_popup({
+ isNewBookmark: false,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_editing_no_autoclose_close_with_ESC() {
+ yield test_bookmarks_popup({
+ isNewBookmark: false,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {accelKey: true}, window);
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_keypress_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ popupEditFn() {
+ EventUtils.sendChar("VK_TAB", window);
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+
+add_task(function* panel_shown_for_new_bookmark_compositionstart_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart");
+ EventUtils.synthesizeComposition({ type: "compositionstart" }, window);
+ info("Waiting for compositionstart event");
+ yield compositionStartPromise;
+ info("Got compositionstart event");
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeComposition({ type: "compositioncommitasis" });
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_compositionstart_mouseout_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ let compositionStartPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "compositionstart");
+ EventUtils.synthesizeComposition({ type: "compositionstart" }, window);
+ info("Waiting for compositionstart event");
+ yield compositionStartPromise;
+ info("Got compositionstart event");
+
+ let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
+ EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ info("Waiting for mouseout event");
+ yield mouseOutPromise;
+ info("Got mouseout event, but shouldn't run autoclose");
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ EventUtils.synthesizeComposition({ type: "compositioncommitasis" });
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* panel_shown_for_new_bookmark_compositionend_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn() {
+ bookmarkStar.click();
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ EventUtils.synthesizeComposition({ type: "compositioncommit", data: "committed text" });
+ },
+ popupHideFn() {
+ bookmarkPanel.hidePopup();
+ },
+ shouldAutoClose: false,
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* contextmenu_new_bookmark_keypress_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ *popupShowFn(browser) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu,
+ "popupshown");
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu,
+ "popuphidden");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
+ type: "contextmenu",
+ button: 2
+ }, browser);
+ yield awaitPopupShown;
+ document.getElementById("context-bookmarkpage").click();
+ contextMenu.hidePopup();
+ yield awaitPopupHidden;
+ },
+ popupEditFn() {
+ EventUtils.sendChar("VK_TAB", window);
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ bookmarkPanel.hidePopup();
+ },
+ isBookmarkRemoved: false,
+ });
+});
+
+add_task(function* bookmarks_menu_new_bookmark_remove_bookmark() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn(browser) {
+ document.getElementById("menu_bookmarkThisPage").doCommand();
+ },
+ shouldAutoClose: true,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* ctrl_d_edit_bookmark_remove_bookmark() {
+ yield test_bookmarks_popup({
+ isNewBookmark: false,
+ popupShowFn(browser) {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: true,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* enter_on_remove_bookmark_should_remove_bookmark() {
+ if (AppConstants.platform == "macosx") {
+ // "Full Keyboard Access" is disabled by default, and thus doesn't allow
+ // keyboard navigation to the "Remove Bookmarks" button by default.
+ return;
+ }
+
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn(browser) {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: true,
+ popupHideFn() {
+ while (!document.activeElement ||
+ document.activeElement.id != "editBookmarkPanelRemoveButton") {
+ EventUtils.sendChar("VK_TAB", window);
+ }
+ EventUtils.sendChar("VK_RETURN", window);
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* ctrl_d_new_bookmark_mousedown_mouseout_no_autoclose() {
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ popupShowFn(browser) {
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ *popupEditFn() {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanel, {type: "mousemove"});
+ info("Waiting for mousemove event");
+ yield mouseMovePromise;
+ info("Got mousemove event");
+
+ yield new Promise(resolve => setTimeout(resolve, 400));
+ is(bookmarkPanel.state, "open", "Panel should still be open on mousemove");
+
+ EventUtils.synthesizeMouseAtCenter(bookmarkPanelTitle, {button: 1, type: "mousedown"});
+
+ let mouseOutPromise = BrowserTestUtils.waitForEvent(bookmarkPanel, "mouseout");
+ EventUtils.synthesizeMouse(bookmarkPanel, 0, 0, {type: "mouseout"});
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mousemove"});
+ info("Waiting for mouseout event");
+ yield mouseOutPromise;
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+add_task(function* mouse_hovering_panel_should_prevent_autoclose() {
+ if (AppConstants.platform != "win") {
+ // This test requires synthesizing native mouse movement which is
+ // best supported on Windows.
+ return;
+ }
+ yield test_bookmarks_popup({
+ isNewBookmark: true,
+ *popupShowFn(browser) {
+ yield new Promise(resolve => {
+ EventUtils.synthesizeNativeMouseMove(
+ document.documentElement,
+ editBookmarkPanelRemoveButtonRect.left,
+ editBookmarkPanelRemoveButtonRect.top,
+ resolve);
+ });
+ EventUtils.synthesizeKey("D", {accelKey: true}, window);
+ },
+ shouldAutoClose: false,
+ popupHideFn() {
+ document.getElementById("editBookmarkPanelRemoveButton").click();
+ },
+ isBookmarkRemoved: true,
+ });
+});
+
+registerCleanupFunction(function() {
+ delete StarUI._closePanelQuickForTesting;
+});
diff --git a/browser/base/content/test/general/browser_bookmark_titles.js b/browser/base/content/test/general/browser_bookmark_titles.js
new file mode 100644
index 000000000..1f7082396
--- /dev/null
+++ b/browser/base/content/test/general/browser_bookmark_titles.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is tests for the default titles that new bookmarks get.
+
+var tests = [
+ // Common page.
+ ['http://example.com/browser/browser/base/content/test/general/dummy_page.html',
+ 'Dummy test page'],
+ // Data URI.
+ ['data:text/html;charset=utf-8,<title>test%20data:%20url</title>',
+ 'test data: url'],
+ // about:neterror
+ ['data:application/vnd.mozilla.xul+xml,',
+ 'data:application/vnd.mozilla.xul+xml,'],
+ // about:certerror
+ ['https://untrusted.example.com/somepage.html',
+ 'https://untrusted.example.com/somepage.html']
+];
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ let browser = gBrowser.selectedBrowser;
+ browser.stop(); // stop the about:blank load.
+
+ // Test that a bookmark of each URI gets the corresponding default title.
+ for (let i = 0; i < tests.length; ++i) {
+ let [uri, title] = tests[i];
+
+ let promiseLoaded = promisePageLoaded(browser);
+ BrowserTestUtils.loadURI(browser, uri);
+ yield promiseLoaded;
+ yield checkBookmark(uri, title);
+ }
+
+ // Network failure test: now that dummy_page.html is in history, bookmarking
+ // it should give the last known page title as the default bookmark title.
+
+ // Simulate a network outage with offline mode. (Localhost is still
+ // accessible in offline mode, so disable the test proxy as well.)
+ BrowserOffline.toggleOfflineStatus();
+ let proxy = Services.prefs.getIntPref('network.proxy.type');
+ Services.prefs.setIntPref('network.proxy.type', 0);
+ registerCleanupFunction(function () {
+ BrowserOffline.toggleOfflineStatus();
+ Services.prefs.setIntPref('network.proxy.type', proxy);
+ });
+
+ // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+ Services.cache2.clear();
+
+ let [uri, title] = tests[0];
+
+ let promiseLoaded = promisePageLoaded(browser);
+ BrowserTestUtils.loadURI(browser, uri);
+ yield promiseLoaded;
+
+ // The offline mode test is only good if the page failed to load.
+ yield ContentTask.spawn(browser, null, function() {
+ is(content.document.documentURI.substring(0, 14), 'about:neterror',
+ "Offline mode successfully simulated network outage.");
+ });
+ yield checkBookmark(uri, title);
+
+ gBrowser.removeCurrentTab();
+});
+
+// Bookmark the current page and confirm that the new bookmark has the expected
+// title. (Then delete the bookmark.)
+function* checkBookmark(uri, expected_title) {
+ is(gBrowser.selectedBrowser.currentURI.spec, uri,
+ "Trying to bookmark the expected uri");
+
+ let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.selectedBrowser.currentURI);
+ PlacesCommandHook.bookmarkCurrentPage(false);
+ yield promiseBookmark;
+
+ let id = PlacesUtils.getMostRecentBookmarkForURI(PlacesUtils._uri(uri));
+ ok(id > 0, "Found the expected bookmark");
+ let title = PlacesUtils.bookmarks.getItemTitle(id);
+ is(title, expected_title, "Bookmark got a good default title.");
+
+ PlacesUtils.bookmarks.removeItem(id);
+}
+
+// BrowserTestUtils.browserLoaded doesn't work for the about pages, so use a
+// custom page load listener.
+function promisePageLoaded(browser)
+{
+ return ContentTask.spawn(browser, null, function* () {
+ yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true,
+ (event) => {
+ return event.originalTarget === content.document &&
+ event.target.location.href !== "about:blank"
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug1015721.js b/browser/base/content/test/general/browser_bug1015721.js
new file mode 100644
index 000000000..e3e715396
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1015721.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 TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html";
+
+var gTab1, gTab2, gLevel1;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTab1 = gBrowser.addTab();
+ gTab2 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, TEST_PAGE);
+ yield FullZoomHelper.load(gTab2, TEST_PAGE);
+ }).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab1() {
+ Task.spawn(function* () {
+ is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+
+ let browser1 = gBrowser.getBrowserForTab(gTab1);
+ yield BrowserTestUtils.synthesizeMouse(null, 10, 10, {
+ wheel: true, ctrlKey: true, deltaY: -1, deltaMode: WheelEvent.DOM_DELTA_LINE
+ }, browser1);
+
+ info("Waiting for tab 1 to be zoomed");
+ yield promiseWaitForCondition(() => {
+ gLevel1 = ZoomManager.getZoomForBrowser(browser1);
+ return gLevel1 > 1;
+ });
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab2, gLevel1, "Tab 2 should have zoomed along with tab 1");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+function finishTest() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug1045809.js b/browser/base/content/test/general/browser_bug1045809.js
new file mode 100644
index 000000000..63b6b06d5
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1045809.js
@@ -0,0 +1,68 @@
+// Test that the Mixed Content Doorhanger Action to re-enable protection works
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+var origBlockActive;
+
+add_task(function* () {
+ registerCleanupFunction(function() {
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+ gBrowser.removeCurrentTab();
+ });
+
+ // Store original preferences so we can restore settings after testing
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+ // Make sure mixed content blocking is on
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ var url =
+ "https://test1.example.com/browser/browser/base/content/test/general/" +
+ "file_bug1045809_1.html";
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ // Test 1: mixed content must be blocked
+ yield promiseTabLoadEvent(tab, url);
+ yield* test1(gBrowser.getBrowserForTab(tab));
+
+ yield promiseTabLoadEvent(tab);
+ // Test 2: mixed content must NOT be blocked
+ yield* test2(gBrowser.getBrowserForTab(tab));
+
+ // Test 3: mixed content must be blocked again
+ yield promiseTabLoadEvent(tab);
+ yield* test3(gBrowser.getBrowserForTab(tab));
+});
+
+function* test1(gTestBrowser) {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer");
+ is(x, null, "Mixed Content is NOT to be found in Test1");
+ });
+
+ // Disable Mixed Content Protection for the page (and reload)
+ gIdentityHandler.disableMixedContentProtection();
+}
+
+function* test2(gTestBrowser) {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer");
+ isnot(x, null, "Mixed Content is to be found in Test2");
+ });
+
+ // Re-enable Mixed Content Protection for the page (and reload)
+ gIdentityHandler.enableMixedContentProtection();
+}
+
+function* test3(gTestBrowser) {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ var x = content.document.getElementsByTagName("iframe")[0].contentDocument.getElementById("mixedContentContainer");
+ is(x, null, "Mixed Content is NOT to be found in Test3");
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js
new file mode 100644
index 000000000..98e0e74db
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1064280_changeUrlInPinnedTab.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+ // Test that changing the URL in a pinned tab works correctly
+
+ let TEST_LINK_INITIAL = "about:";
+ let TEST_LINK_CHANGED = "about:support";
+
+ let appTab = gBrowser.addTab(TEST_LINK_INITIAL);
+ let browser = appTab.linkedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ gBrowser.pinTab(appTab);
+ is(appTab.pinned, true, "Tab was successfully pinned");
+
+ let initialTabsNo = gBrowser.tabs.length;
+
+ let goButton = document.getElementById("urlbar-go-button");
+ gBrowser.selectedTab = appTab;
+ gURLBar.focus();
+ gURLBar.value = TEST_LINK_CHANGED;
+
+ goButton.click();
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ is(appTab.linkedBrowser.currentURI.spec, TEST_LINK_CHANGED,
+ "New page loaded in the app tab");
+ is(gBrowser.tabs.length, initialTabsNo, "No additional tabs were opened");
+});
+
+registerCleanupFunction(function () {
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_bug1261299.js b/browser/base/content/test/general/browser_bug1261299.js
new file mode 100644
index 000000000..673ef2a0a
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1261299.js
@@ -0,0 +1,73 @@
+/* -*- 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/. */
+
+/**
+ * Tests for Bug 1261299
+ * Test that the service menu code path is called properly and the
+ * current selection (transferable) is cached properly on the parent process.
+ */
+
+add_task(function* test_content_and_chrome_selection()
+{
+ let testPage =
+ 'data:text/html,' +
+ '<textarea id="textarea">Write something here</textarea>';
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ let selectedText;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "Write something here", "The macOS services got the selected content text");
+
+ gURLBar.value = "test.mozilla.org";
+ yield gURLBar.focus();
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "test.mozilla.org", "The macOS services got the selected chrome text");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// Test switching active selection.
+// Each tab has a content selection and when you switch to that tab, its selection becomes
+// active aka the current selection.
+// Expect: The active selection is what is being sent to OSX service menu.
+
+add_task(function* test_active_selection_switches_properly()
+{
+ let testPage1 =
+ 'data:text/html,' +
+ '<textarea id="textarea">Write something here</textarea>';
+ let testPage2 =
+ 'data:text/html,' +
+ '<textarea id="textarea">Nothing available</textarea>';
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ let selectedText;
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1);
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2);
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeKey("KEY_ArrowRight",
+ {shiftKey: true, ctrlKey: true, code: "ArrowRight"}, gBrowser.selectedBrowser);
+
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "Write something here", "The macOS services got the selected content text");
+
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ selectedText = DOMWindowUtils.GetSelectionAsPlaintext();
+ is(selectedText, "Nothing available", "The macOS services got the selected content text");
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/base/content/test/general/browser_bug1297539.js b/browser/base/content/test/general/browser_bug1297539.js
new file mode 100644
index 000000000..d7e675437
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1297539.js
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+/**
+ * Test for Bug 1297539
+ * Test that the content event "pasteTransferable"
+ * (mozilla::EventMessage::eContentCommandPasteTransferable)
+ * is handled correctly for plain text and html in the remote case.
+ *
+ * Original test test_bug525389.html for command content event
+ * "pasteTransferable" runs only in the content process.
+ * This doesn't test the remote case.
+ *
+ */
+
+"use strict";
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function getTransferableFromClipboard(asHTML) {
+ let trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ if (asHTML) {
+ trans.addDataFlavor("text/html");
+ } else {
+ trans.addDataFlavor("text/unicode");
+ }
+ let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+ clip.getData(trans, Ci.nsIClipboard.kGlobalClipboard);
+ return trans;
+}
+
+function* cutCurrentSelection(elementQueryString, property, browser) {
+ // Cut the current selection.
+ yield BrowserTestUtils.synthesizeKey("x", {accelKey: true}, browser);
+
+ // The editor should be empty after cut.
+ yield ContentTask.spawn(browser, [elementQueryString, property],
+ function* ([contentElementQueryString, contentProperty]) {
+ let element = content.document.querySelector(contentElementQueryString);
+ is(element[contentProperty], "",
+ `${contentElementQueryString} should be empty after cut (superkey + x)`);
+ });
+}
+
+// Test that you are able to pasteTransferable for plain text
+// which is handled by TextEditor::PasteTransferable to paste into the editor.
+add_task(function* test_paste_transferable_plain_text()
+{
+ let testPage =
+ 'data:text/html,' +
+ '<textarea id="textarea">Write something here</textarea>';
+
+ yield BrowserTestUtils.withNewTab(testPage, function* (browser) {
+ // Select all the content in your editor element.
+ yield BrowserTestUtils.synthesizeMouse("#textarea", 0, 0, {}, browser);
+ yield BrowserTestUtils.synthesizeKey("a", {accelKey: true}, browser);
+
+ yield* cutCurrentSelection("#textarea", "value", browser);
+
+ let trans = getTransferableFromClipboard(false);
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let textArea = content.document.querySelector('#textarea');
+ is(textArea.value, "Write something here",
+ "Send content command pasteTransferable successful");
+ });
+ });
+});
+
+// Test that you are able to pasteTransferable for html
+// which is handled by HTMLEditor::PasteTransferable to paste into the editor.
+//
+// On Linux,
+// BrowserTestUtils.synthesizeKey("a", {accelKey: true}, browser);
+// doesn't seem to trigger for contenteditable which is why we use
+// Selection to select the contenteditable contents.
+add_task(function* test_paste_transferable_html()
+{
+ let testPage =
+ 'data:text/html,' +
+ '<div contenteditable="true"><b>Bold Text</b><i>italics</i></div>';
+
+ yield BrowserTestUtils.withNewTab(testPage, function* (browser) {
+ // Select all the content in your editor element.
+ yield BrowserTestUtils.synthesizeMouse("div", 0, 0, {}, browser);
+ yield ContentTask.spawn(browser, {}, function* () {
+ let element = content.document.querySelector("div");
+ let selection = content.window.getSelection();
+ selection.selectAllChildren(element);
+ });
+
+ yield* cutCurrentSelection("div", "textContent", browser);
+
+ let trans = getTransferableFromClipboard(true);
+ let DOMWindowUtils = EventUtils._getDOMWindowUtils(window);
+ DOMWindowUtils.sendContentCommandEvent("pasteTransferable", trans);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let textArea = content.document.querySelector('div');
+ is(textArea.innerHTML, "<b>Bold Text</b><i>italics</i>",
+ "Send content command pasteTransferable successful");
+ });
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug1299667.js b/browser/base/content/test/general/browser_bug1299667.js
new file mode 100644
index 000000000..084c8d49f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug1299667.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { addObserver, removeObserver } = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+
+function receive(topic) {
+ return new Promise((resolve, reject) => {
+ let timeout = setTimeout(() => {
+ reject(new Error("Timeout"));
+ }, 90000);
+
+ const observer = {
+ observe: subject => {
+ removeObserver(observer, topic);
+ clearTimeout(timeout);
+ resolve(subject);
+ }
+ };
+ addObserver(observer, topic, false);
+ });
+}
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.history.pushState({}, "2", "2.html");
+ });
+
+ yield receive("sessionstore-state-write-complete");
+
+ // Wait for the session data to be flushed before continuing the test
+ yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
+
+ let backButton = document.getElementById("back-button");
+ let contextMenu = document.getElementById("backForwardMenu");
+
+ info("waiting for the history menu to open");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(backButton, {type: "contextmenu", button: 2});
+ let event = yield popupShownPromise;
+
+ ok(true, "history menu opened");
+
+ // Wait for the session data to be flushed before continuing the test
+ yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
+
+ is(event.target.children.length, 2, "Two history items");
+
+ let node = event.target.firstChild;
+ is(node.getAttribute("uri"), "http://example.com/2.html", "first item uri");
+ is(node.getAttribute("index"), "1", "first item index");
+ is(node.getAttribute("historyindex"), "0", "first item historyindex");
+
+ node = event.target.lastChild;
+ is(node.getAttribute("uri"), "http://example.com/", "second item uri");
+ is(node.getAttribute("index"), "0", "second item index");
+ is(node.getAttribute("historyindex"), "-1", "second item historyindex");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ event.target.hidePopup();
+ yield popupHiddenPromise;
+ info("Hidden popup");
+
+ let onClose = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield onClose;
+ info("Tab closed");
+});
diff --git a/browser/base/content/test/general/browser_bug321000.js b/browser/base/content/test/general/browser_bug321000.js
new file mode 100644
index 000000000..b30b7101d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug321000.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const kTestString = " hello hello \n world\nworld ";
+
+var gTests = [
+
+ { desc: "Urlbar strips newlines and surrounding whitespace",
+ element: gURLBar,
+ expected: kTestString.replace(/\s*\n\s*/g, '')
+ },
+
+ { desc: "Searchbar replaces newlines with spaces",
+ element: document.getElementById('searchbar'),
+ expected: kTestString.replace(/\n/g, ' ')
+ },
+
+];
+
+// Test for bug 23485 and bug 321000.
+// Urlbar should strip newlines,
+// search bar should replace newlines with spaces.
+function test() {
+ waitForExplicitFinish();
+
+ let cbHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+ // Put a multi-line string in the clipboard.
+ // Setting the clipboard value is an async OS operation, so we need to poll
+ // the clipboard for valid data before going on.
+ waitForClipboard(kTestString, function() { cbHelper.copyString(kTestString); },
+ next_test, finish);
+}
+
+function next_test() {
+ if (gTests.length)
+ test_paste(gTests.shift());
+ else
+ finish();
+}
+
+function test_paste(aCurrentTest) {
+ var element = aCurrentTest.element;
+
+ // Register input listener.
+ var inputListener = {
+ test: aCurrentTest,
+ handleEvent: function(event) {
+ element.removeEventListener(event.type, this, false);
+
+ is(element.value, this.test.expected, this.test.desc);
+
+ // Clear the field and go to next test.
+ element.value = "";
+ setTimeout(next_test, 0);
+ }
+ }
+ element.addEventListener("input", inputListener, false);
+
+ // Focus the window.
+ window.focus();
+ gBrowser.selectedBrowser.focus();
+
+ // Focus the element and wait for focus event.
+ info("About to focus " + element.id);
+ element.addEventListener("focus", function() {
+ element.removeEventListener("focus", arguments.callee, false);
+ executeSoon(function() {
+ // Pasting is async because the Accel+V codepath ends up going through
+ // nsDocumentViewer::FireClipboardEvent.
+ info("Pasting into " + element.id);
+ EventUtils.synthesizeKey("v", { accelKey: true });
+ });
+ }, false);
+ element.focus();
+}
diff --git a/browser/base/content/test/general/browser_bug356571.js b/browser/base/content/test/general/browser_bug356571.js
new file mode 100644
index 000000000..ab689d0f8
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug356571.js
@@ -0,0 +1,93 @@
+// Bug 356571 - loadOneOrMoreURIs gives up if one of the URLs has an unknown protocol
+
+var Cr = Components.results;
+var Cm = Components.manager;
+
+// Set to true when docShell alerts for unknown protocol error
+var didFail = false;
+
+// Override Alert to avoid blocking the test due to unknown protocol error
+const kPromptServiceUUID = "{6cc9c9fe-bc0b-432b-a410-253ef8bcc699}";
+const kPromptServiceContractID = "@mozilla.org/embedcomp/prompt-service;1";
+
+// Save original prompt service factory
+const kPromptServiceFactory = Cm.getClassObject(Cc[kPromptServiceContractID],
+ Ci.nsIFactory);
+
+var fakePromptServiceFactory = {
+ createInstance: function(aOuter, aIid) {
+ if (aOuter != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return promptService.QueryInterface(aIid);
+ }
+};
+
+var promptService = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]),
+ alert: function() {
+ didFail = true;
+ }
+};
+
+/* FIXME
+Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service",
+ kPromptServiceContractID, fakePromptServiceFactory);
+*/
+
+const kCompleteState = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+const kDummyPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+const kURIs = [
+ "bad://www.mozilla.org/",
+ kDummyPage,
+ kDummyPage,
+];
+
+var gProgressListener = {
+ _runCount: 0,
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ if ((aStateFlags & kCompleteState) == kCompleteState) {
+ if (++this._runCount != kURIs.length)
+ return;
+ // Check we failed on unknown protocol (received an alert from docShell)
+ ok(didFail, "Correctly failed on unknown protocol");
+ // Check we opened all tabs
+ ok(gBrowser.tabs.length == kURIs.length, "Correctly opened all expected tabs");
+ finishTest();
+ }
+ }
+}
+
+function test() {
+ todo(false, "temp. disabled");
+ return; /* FIXME */
+ /*
+ waitForExplicitFinish();
+ // Wait for all tabs to finish loading
+ gBrowser.addTabsProgressListener(gProgressListener);
+ loadOneOrMoreURIs(kURIs.join("|"));
+ */
+}
+
+function finishTest() {
+ // Unregister the factory so we do not leak
+ Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .unregisterFactory(Components.ID(kPromptServiceUUID),
+ fakePromptServiceFactory);
+
+ // Restore the original factory
+ Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service",
+ kPromptServiceContractID, kPromptServiceFactory);
+
+ // Remove the listener
+ gBrowser.removeTabsProgressListener(gProgressListener);
+
+ // Close opened tabs
+ for (var i = gBrowser.tabs.length-1; i > 0; i--)
+ gBrowser.removeTab(gBrowser.tabs[i]);
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug380960.js b/browser/base/content/test/general/browser_bug380960.js
new file mode 100644
index 000000000..d6b64543b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug380960.js
@@ -0,0 +1,11 @@
+function test() {
+ var tab = gBrowser.addTab("about:blank", { skipAnimation: true });
+ gBrowser.removeTab(tab);
+ is(tab.parentNode, null, "tab removed immediately");
+
+ tab = gBrowser.addTab("about:blank", { skipAnimation: true });
+ gBrowser.removeTab(tab, { animate: true });
+ gBrowser.removeTab(tab);
+ is(tab.parentNode, null, "tab removed immediately when calling removeTab again after the animation was kicked off");
+}
+
diff --git a/browser/base/content/test/general/browser_bug386835.js b/browser/base/content/test/general/browser_bug386835.js
new file mode 100644
index 000000000..1c3ba99c5
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug386835.js
@@ -0,0 +1,89 @@
+var gTestPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+var gTestImage = "http://example.org/browser/browser/base/content/test/general/moz.png";
+var gTab1, gTab2, gTab3;
+var gLevel;
+const BACK = 0;
+const FORWARD = 1;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTab1 = gBrowser.addTab(gTestPage);
+ gTab2 = gBrowser.addTab();
+ gTab3 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, gTestPage);
+ yield FullZoomHelper.load(gTab2, gTestPage);
+ }).then(secondPageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function secondPageLoaded() {
+ Task.spawn(function* () {
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+ FullZoomHelper.zoomTest(gTab3, 1, "Initial zoom of tab 3 should be 1");
+
+ // Now have three tabs, two with the test page, one blank. Tab 1 is selected
+ // Zoom tab 1
+ FullZoom.enlarge();
+ gLevel = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+
+ ok(gLevel > 1, "New zoom for tab 1 should be greater than 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
+ FullZoomHelper.zoomTest(gTab3, 1, "Zooming tab 1 should not affect tab 3");
+
+ yield FullZoomHelper.load(gTab3, gTestPage);
+ }).then(thirdPageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function thirdPageLoaded() {
+ Task.spawn(function* () {
+ FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
+ FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 should still not be affected");
+ FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should have zoomed as it was loading in the background");
+
+ // Switching to tab 2 should update its zoom setting.
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
+ FullZoomHelper.zoomTest(gTab2, gLevel, "Tab 2 should be zoomed now");
+ FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should still be zoomed");
+
+ yield FullZoomHelper.load(gTab1, gTestImage);
+ }).then(imageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function imageLoaded() {
+ Task.spawn(function* () {
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when image was loaded in the background");
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should still be 1 when tab with image is selected");
+ }).then(imageZoomSwitch, FullZoomHelper.failAndContinue(finish));
+}
+
+function imageZoomSwitch() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.navigate(BACK);
+ yield FullZoomHelper.navigate(FORWARD);
+ FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should not be zoomed when an image loads");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should still not be zoomed when deselected");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+var finishTestStarted = false;
+function finishTest() {
+ Task.spawn(function* () {
+ ok(!finishTestStarted, "finishTest called more than once");
+ finishTestStarted = true;
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab3);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug406216.js b/browser/base/content/test/general/browser_bug406216.js
new file mode 100644
index 000000000..e1bd38395
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug406216.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * "TabClose" event is possibly used for closing related tabs of the current.
+ * "removeTab" method should work correctly even if the number of tabs are
+ * changed while "TabClose" event.
+ */
+
+var count = 0;
+const URIS = ["about:config",
+ "about:plugins",
+ "about:buildconfig",
+ "data:text/html,<title>OK</title>"];
+
+function test() {
+ waitForExplicitFinish();
+ URIS.forEach(addTab);
+}
+
+function addTab(aURI, aIndex) {
+ var tab = gBrowser.addTab(aURI);
+ if (aIndex == 0)
+ gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
+
+ tab.linkedBrowser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ if (++count == URIS.length)
+ executeSoon(doTabsTest);
+ }, true);
+}
+
+function doTabsTest() {
+ is(gBrowser.tabs.length, URIS.length, "Correctly opened all expected tabs");
+
+ // sample of "close related tabs" feature
+ gBrowser.tabContainer.addEventListener("TabClose", function (event) {
+ event.currentTarget.removeEventListener("TabClose", arguments.callee, true);
+ var closedTab = event.originalTarget;
+ var scheme = closedTab.linkedBrowser.currentURI.scheme;
+ Array.slice(gBrowser.tabs).forEach(function (aTab) {
+ if (aTab != closedTab && aTab.linkedBrowser.currentURI.scheme == scheme)
+ gBrowser.removeTab(aTab, {skipPermitUnload: true});
+ });
+ }, true);
+
+ gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
+ is(gBrowser.tabs.length, 1, "Related tabs are not closed unexpectedly");
+
+ gBrowser.addTab("about:blank");
+ gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug408415.js b/browser/base/content/test/general/browser_bug408415.js
new file mode 100644
index 000000000..d8f80f8be
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug408415.js
@@ -0,0 +1,45 @@
+add_task(function* test() {
+ let testPath = getRootDirectory(gTestPath);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (tabBrowser) {
+ const URI = testPath + "file_with_favicon.html";
+ const expectedIcon = testPath + "file_generic_favicon.ico";
+
+ let got_favicon = Promise.defer();
+ let listener = {
+ onLinkIconAvailable(browser, iconURI) {
+ if (got_favicon && iconURI && browser === tabBrowser) {
+ got_favicon.resolve(iconURI);
+ got_favicon = null;
+ }
+ }
+ };
+ gBrowser.addTabsProgressListener(listener);
+
+ BrowserTestUtils.loadURI(tabBrowser, URI);
+
+ let iconURI = yield got_favicon.promise;
+ is(iconURI, expectedIcon, "Correct icon before pushState.");
+
+ got_favicon = Promise.defer();
+ got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e);
+ yield ContentTask.spawn(tabBrowser, null, function() {
+ content.location.href += "#foo";
+ });
+
+ // We've navigated and shouldn't get a call to onLinkIconAvailable.
+ TestUtils.executeSoon(() => {
+ got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser)));
+ });
+ try {
+ yield got_favicon.promise;
+ } catch (e) {
+ iconURI = e;
+ }
+ is(iconURI, expectedIcon, "Correct icon after pushState.");
+
+ gBrowser.removeTabsProgressListener(listener);
+ });
+});
+
diff --git a/browser/base/content/test/general/browser_bug409481.js b/browser/base/content/test/general/browser_bug409481.js
new file mode 100644
index 000000000..395ad93d4
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug409481.js
@@ -0,0 +1,83 @@
+function test() {
+ waitForExplicitFinish();
+
+ // XXX This looks a bit odd, but is needed to avoid throwing when removing the
+ // event listeners below. See bug 310955.
+ document.getElementById("sidebar").addEventListener("load", delayedOpenUrl, true);
+ SidebarUI.show("viewWebPanelsSidebar");
+}
+
+function delayedOpenUrl() {
+ ok(true, "Ran delayedOpenUrl");
+ setTimeout(openPanelUrl, 100);
+}
+
+function openPanelUrl(event) {
+ ok(!document.getElementById("sidebar-box").hidden, "Sidebar showing");
+
+ var sidebar = document.getElementById("sidebar");
+ var root = sidebar.contentDocument.documentElement;
+ ok(root.nodeName != "parsererror", "Sidebar is well formed");
+
+ sidebar.removeEventListener("load", delayedOpenUrl, true);
+ // XXX See comment above
+ sidebar.contentDocument.addEventListener("load", delayedRunTest, true);
+ var url = 'data:text/html,<div%20id="test_bug409481">Content!</div><a id="link" href="http://www.example.com/ctest">Link</a><input id="textbox">';
+ sidebar.contentWindow.loadWebPanel(url);
+}
+
+function delayedRunTest() {
+ ok(true, "Ran delayedRunTest");
+ setTimeout(runTest, 100);
+}
+
+function runTest(event) {
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("load", delayedRunTest, true);
+
+ var browser = sidebar.contentDocument.getElementById("web-panels-browser");
+ var div = browser && browser.contentDocument.getElementById("test_bug409481");
+ ok(div && div.textContent == "Content!", "Sidebar content loaded");
+
+ var link = browser && browser.contentDocument.getElementById("link");
+ sidebar.contentDocument.addEventListener("popupshown", contextMenuOpened, false);
+
+ EventUtils.synthesizeMouseAtCenter(link, { type: "contextmenu", button: 2 }, browser.contentWindow);
+}
+
+function contextMenuOpened()
+{
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("popupshown", contextMenuOpened, false);
+
+ var copyLinkCommand = sidebar.contentDocument.getElementById("context-copylink");
+ copyLinkCommand.addEventListener("command", copyLinkCommandExecuted, false);
+ copyLinkCommand.doCommand();
+}
+
+function copyLinkCommandExecuted(event)
+{
+ event.target.removeEventListener("command", copyLinkCommandExecuted, false);
+
+ var sidebar = document.getElementById("sidebar");
+ var browser = sidebar.contentDocument.getElementById("web-panels-browser");
+ var textbox = browser && browser.contentDocument.getElementById("textbox");
+ textbox.focus();
+ document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ is(textbox.value, "http://www.example.com/ctest", "copy link command");
+
+ sidebar.contentDocument.addEventListener("popuphidden", contextMenuClosed, false);
+ event.target.parentNode.hidePopup();
+}
+
+function contextMenuClosed()
+{
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("popuphidden", contextMenuClosed, false);
+
+ SidebarUI.hide();
+
+ ok(document.getElementById("sidebar-box").hidden, "Sidebar successfully hidden");
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug409624.js b/browser/base/content/test/general/browser_bug409624.js
new file mode 100644
index 000000000..8e46ec0c2
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug409624.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/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+add_task(function* test() {
+ // This test relies on the form history being empty to start with delete
+ // all the items first.
+ yield new Promise((resolve, reject) => {
+ FormHistory.update({ op: "remove" },
+ { handleError(error) {
+ reject(error);
+ },
+ handleCompletion(reason) {
+ if (!reason) {
+ resolve();
+ } else {
+ reject();
+ }
+ },
+ });
+ });
+
+ let prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+
+ let tempScope = {};
+ Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+ let Sanitizer = tempScope.Sanitizer;
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+ let prefBranch = prefService.getBranch(s.prefDomain);
+
+ prefBranch.setBoolPref("cache", false);
+ prefBranch.setBoolPref("cookies", false);
+ prefBranch.setBoolPref("downloads", false);
+ prefBranch.setBoolPref("formdata", true);
+ prefBranch.setBoolPref("history", false);
+ prefBranch.setBoolPref("offlineApps", false);
+ prefBranch.setBoolPref("passwords", false);
+ prefBranch.setBoolPref("sessions", false);
+ prefBranch.setBoolPref("siteSettings", false);
+
+ // Sanitize now so we can test the baseline point.
+ yield s.sanitize();
+ ok(!gFindBar.hasTransactions, "pre-test baseline for sanitizer");
+
+ gFindBar.getElement("findbar-textbox").value = "m";
+ ok(gFindBar.hasTransactions, "formdata can be cleared after input");
+
+ yield s.sanitize();
+ is(gFindBar.getElement("findbar-textbox").value, "", "findBar textbox should be empty after sanitize");
+ ok(!gFindBar.hasTransactions, "No transactions after sanitize");
+});
diff --git a/browser/base/content/test/general/browser_bug413915.js b/browser/base/content/test/general/browser_bug413915.js
new file mode 100644
index 000000000..86c94c427
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug413915.js
@@ -0,0 +1,62 @@
+XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
+ "resource:///modules/Feeds.jsm");
+
+function test() {
+ var exampleUri = makeURI("http://example.com/");
+ var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+ var principal = secman.createCodebasePrincipal(exampleUri, {});
+
+ function testIsFeed(aTitle, aHref, aType, aKnown) {
+ var link = { title: aTitle, href: aHref, type: aType };
+ return Feeds.isValidFeed(link, principal, aKnown);
+ }
+
+ var href = "http://example.com/feed/";
+ var atomType = "application/atom+xml";
+ var funkyAtomType = " aPPLICAtion/Atom+XML ";
+ var rssType = "application/rss+xml";
+ var funkyRssType = " Application/RSS+XML ";
+ var rdfType = "application/rdf+xml";
+ var texmlType = "text/xml";
+ var appxmlType = "application/xml";
+ var noRss = "Foo";
+ var rss = "RSS";
+
+ // things that should be valid
+ ok(testIsFeed(noRss, href, atomType, false) == atomType,
+ "detect Atom feed");
+ ok(testIsFeed(noRss, href, funkyAtomType, false) == atomType,
+ "clean up and detect Atom feed");
+ ok(testIsFeed(noRss, href, rssType, false) == rssType,
+ "detect RSS feed");
+ ok(testIsFeed(noRss, href, funkyRssType, false) == rssType,
+ "clean up and detect RSS feed");
+
+ // things that should not be feeds
+ ok(testIsFeed(noRss, href, rdfType, false) == null,
+ "should not detect RDF non-feed");
+ ok(testIsFeed(rss, href, rdfType, false) == null,
+ "should not detect RDF feed from type and title");
+ ok(testIsFeed(noRss, href, texmlType, false) == null,
+ "should not detect text/xml non-feed");
+ ok(testIsFeed(rss, href, texmlType, false) == null,
+ "should not detect text/xml feed from type and title");
+ ok(testIsFeed(noRss, href, appxmlType, false) == null,
+ "should not detect application/xml non-feed");
+ ok(testIsFeed(rss, href, appxmlType, false) == null,
+ "should not detect application/xml feed from type and title");
+
+ // security check only, returns cleaned up type or "application/rss+xml"
+ ok(testIsFeed(noRss, href, atomType, true) == atomType,
+ "feed security check should return Atom type");
+ ok(testIsFeed(noRss, href, funkyAtomType, true) == atomType,
+ "feed security check should return cleaned up Atom type");
+ ok(testIsFeed(noRss, href, rssType, true) == rssType,
+ "feed security check should return RSS type");
+ ok(testIsFeed(noRss, href, funkyRssType, true) == rssType,
+ "feed security check should return cleaned up RSS type");
+ ok(testIsFeed(noRss, href, "", true) == rssType,
+ "feed security check without type should return RSS type");
+ ok(testIsFeed(noRss, href, "garbage", true) == "garbage",
+ "feed security check with garbage type should return garbage");
+}
diff --git a/browser/base/content/test/general/browser_bug416661.js b/browser/base/content/test/general/browser_bug416661.js
new file mode 100644
index 000000000..a37971a34
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug416661.js
@@ -0,0 +1,43 @@
+var tabElm, zoomLevel;
+function start_test_prefNotSet() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, 1, "initial zoom level should be 1");
+ FullZoom.enlarge();
+
+ // capture the zoom level to test later
+ zoomLevel = ZoomManager.zoom;
+ isnot(zoomLevel, 1, "zoom level should have changed");
+
+ yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/general/moz.png");
+ }).then(continue_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
+
+function continue_test_prefNotSet () {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, 1, "zoom level pref should not apply to an image");
+ yield FullZoom.reset();
+
+ yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/general/zoom_test.html");
+ }).then(end_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
+
+function end_test_prefNotSet() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted");
+
+ // Reset the zoom so that other tests have a fresh zoom level
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange();
+ finish();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ tabElm = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tabElm);
+ yield FullZoomHelper.load(tabElm, "http://mochi.test:8888/browser/browser/base/content/test/general/zoom_test.html");
+ }).then(start_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug417483.js b/browser/base/content/test/general/browser_bug417483.js
new file mode 100644
index 000000000..43ff7b917
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug417483.js
@@ -0,0 +1,30 @@
+add_task(function* () {
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true);
+ const htmlContent = "data:text/html, <iframe src='data:text/html,text text'></iframe>";
+ gBrowser.loadURI(htmlContent);
+ yield loadedPromise;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+ let frame = content.frames[0];
+ let sel = frame.getSelection();
+ let range = frame.document.createRange();
+ let tn = frame.document.body.childNodes[0];
+ range.setStart(tn, 4);
+ range.setEnd(tn, 5);
+ sel.addRange(range);
+ frame.focus();
+ });
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse("frame", 5, 5,
+ { type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ ok(document.getElementById("frame-sep").hidden, "'frame-sep' should be hidden if the selection contains only spaces");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+});
diff --git a/browser/base/content/test/general/browser_bug419612.js b/browser/base/content/test/general/browser_bug419612.js
new file mode 100644
index 000000000..8c34b2d39
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug419612.js
@@ -0,0 +1,32 @@
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ let testPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ let tab1 = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.load(tab1, testPage);
+
+ let tab2 = gBrowser.addTab();
+ yield FullZoomHelper.load(tab2, testPage);
+
+ FullZoom.enlarge();
+ let tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
+ let tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
+ is(tab2Zoom, tab1Zoom, "Zoom should affect background tabs");
+
+ gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", false);
+ yield FullZoom.reset();
+ gBrowser.selectedTab = tab1;
+ tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
+ tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
+ isnot(tab1Zoom, tab2Zoom, "Zoom should not affect background tabs");
+
+ if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
+ gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug422590.js b/browser/base/content/test/general/browser_bug422590.js
new file mode 100644
index 000000000..f26919cc5
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug422590.js
@@ -0,0 +1,50 @@
+function test() {
+ waitForExplicitFinish();
+ // test the main (normal) browser window
+ testCustomize(window, testChromeless);
+}
+
+function testChromeless() {
+ // test a chromeless window
+ var newWin = openDialog(getBrowserURL(), "_blank",
+ "chrome,dialog=no,location=yes,toolbar=no", "about:blank");
+ ok(newWin, "got new window");
+
+ whenDelayedStartupFinished(newWin, function () {
+ // Check that the search bar is hidden
+ var searchBar = newWin.BrowserSearch.searchBar;
+ ok(searchBar, "got search bar");
+
+ var searchBarBO = searchBar.boxObject;
+ is(searchBarBO.width, 0, "search bar hidden");
+ is(searchBarBO.height, 0, "search bar hidden");
+
+ testCustomize(newWin, function () {
+ newWin.close();
+ finish();
+ });
+ });
+}
+
+function testCustomize(aWindow, aCallback) {
+ var fileMenu = aWindow.document.getElementById("file-menu");
+ ok(fileMenu, "got file menu");
+ is(fileMenu.disabled, false, "file menu initially enabled");
+
+ openToolbarCustomizationUI(function () {
+ // Can't use the property, since the binding may have since been removed
+ // if the element is hidden (see bug 422590)
+ is(fileMenu.getAttribute("disabled"), "true",
+ "file menu is disabled during toolbar customization");
+
+ closeToolbarCustomizationUI(onClose, aWindow);
+ }, aWindow);
+
+ function onClose() {
+ is(fileMenu.getAttribute("disabled"), "false",
+ "file menu is enabled after toolbar customization");
+
+ if (aCallback)
+ aCallback();
+ }
+}
diff --git a/browser/base/content/test/general/browser_bug423833.js b/browser/base/content/test/general/browser_bug423833.js
new file mode 100644
index 000000000..d4069338b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug423833.js
@@ -0,0 +1,138 @@
+/* Tests for proper behaviour of "Show this frame" context menu options */
+
+// Two frames, one with text content, the other an error page
+var invalidPage = 'http://127.0.0.1:55555/';
+var validPage = 'http://example.com/';
+var testPage = 'data:text/html,<frameset cols="400,400"><frame src="' + validPage + '"><frame src="' + invalidPage + '"></frameset>';
+
+// Store the tab and window created in tests 2 and 3 respectively
+var test2tab;
+var test3window;
+
+// We use setInterval instead of setTimeout to avoid race conditions on error doc loads
+var intervalID;
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", test1Setup, true);
+ content.location = testPage;
+}
+
+function test1Setup() {
+ if (content.frames.length < 2 ||
+ content.frames[1].location != invalidPage)
+ // The error frame hasn't loaded yet
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("load", test1Setup, true);
+
+ var badFrame = content.frames[1];
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ // We'd like to use another load listener here, but error pages don't fire load events
+ contextMenu.showOnlyThisFrame();
+ intervalID = setInterval(testShowOnlyThisFrame, 3000);
+}
+
+function testShowOnlyThisFrame() {
+ if (content.location.href == testPage)
+ // This is a stale event from the original page loading
+ return;
+
+ // We should now have loaded the error page frame content directly
+ // in the tab, make sure the URL is right.
+ clearInterval(intervalID);
+
+ is(content.location.href, invalidPage, "Should navigate to page url, not about:neterror");
+
+ // Go back to the frames page
+ gBrowser.addEventListener("load", test2Setup, true);
+ content.location = testPage;
+}
+
+function test2Setup() {
+ if (content.frames.length < 2 ||
+ content.frames[1].location != invalidPage)
+ // The error frame hasn't loaded yet
+ return;
+
+ gBrowser.removeEventListener("load", test2Setup, true);
+
+ // Now let's do the whole thing again, but this time for "Open frame in new tab"
+ var badFrame = content.frames[1];
+
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ gBrowser.tabContainer.addEventListener("TabOpen", function (event) {
+ test2tab = event.target;
+ gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, false);
+ }, false);
+ contextMenu.openFrameInTab();
+ ok(test2tab, "openFrameInTab() opened a tab");
+
+ gBrowser.selectedTab = test2tab;
+
+ intervalID = setInterval(testOpenFrameInTab, 3000);
+}
+
+function testOpenFrameInTab() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ // Wait another cycle
+ return;
+
+ clearInterval(intervalID);
+
+ // We should now have the error page in a new, active tab.
+ is(gBrowser.contentDocument.location.href, invalidPage, "New tab should have page url, not about:neterror");
+
+ // Clear up the new tab, and punt to test 3
+ gBrowser.removeCurrentTab();
+
+ test3Setup();
+}
+
+function test3Setup() {
+ // One more time, for "Open frame in new window"
+ var badFrame = content.frames[1];
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened")
+ test3window = aSubject;
+ Services.ww.unregisterNotification(arguments.callee);
+ });
+
+ contextMenu.openFrame();
+
+ intervalID = setInterval(testOpenFrame, 3000);
+}
+
+function testOpenFrame() {
+ if (!test3window || test3window.content.location.href == "about:blank") {
+ info("testOpenFrame: Wait another cycle");
+ return;
+ }
+
+ clearInterval(intervalID);
+
+ is(test3window.content.location.href, invalidPage, "New window should have page url, not about:neterror");
+
+ test3window.close();
+ cleanup();
+}
+
+function cleanup() {
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug424101.js b/browser/base/content/test/general/browser_bug424101.js
new file mode 100644
index 000000000..8000d2ae9
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug424101.js
@@ -0,0 +1,52 @@
+/* Make sure that the context menu appears on form elements */
+
+add_task(function *() {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html,test");
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ let tests = [
+ { element: "input", type: "text" },
+ { element: "input", type: "password" },
+ { element: "input", type: "image" },
+ { element: "input", type: "button" },
+ { element: "input", type: "submit" },
+ { element: "input", type: "reset" },
+ { element: "input", type: "checkbox" },
+ { element: "input", type: "radio" },
+ { element: "button" },
+ { element: "select" },
+ { element: "option" },
+ { element: "optgroup" }
+ ];
+
+ for (let index = 0; index < tests.length; index++) {
+ let test = tests[index];
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser,
+ { element: test.element, type: test.type, index: index },
+ function* (arg) {
+ let element = content.document.createElement(arg.element);
+ element.id = "element" + arg.index;
+ if (arg.type) {
+ element.setAttribute("type", arg.type);
+ }
+ content.document.body.appendChild(element);
+ });
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#element" + index,
+ { type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ let typeAttr = test.type ? "type=" + test.type + " " : "";
+ is(gContextMenu.shouldDisplay, true,
+ "context menu behavior for <" + test.element + " " + typeAttr + "> is wrong");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug427559.js b/browser/base/content/test/general/browser_bug427559.js
new file mode 100644
index 000000000..78cecdefa
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug427559.js
@@ -0,0 +1,38 @@
+"use strict";
+
+/*
+ * Test bug 427559 to make sure focused elements that are no longer on the page
+ * will have focus transferred to the window when changing tabs back to that
+ * tab with the now-gone element.
+ */
+
+// Default focus on a button and have it kill itself on blur.
+const URL = 'data:text/html;charset=utf-8,' +
+ '<body><button onblur="this.remove()">' +
+ '<script>document.body.firstChild.focus()</script></body>';
+
+function getFocusedLocalName(browser) {
+ return ContentTask.spawn(browser, null, function* () {
+ return content.document.activeElement.localName;
+ });
+}
+
+add_task(function* () {
+ let testTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+
+ let browser = testTab.linkedBrowser;
+
+ is((yield getFocusedLocalName(browser)), "button", "button is focused");
+
+ let blankTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ yield BrowserTestUtils.switchTab(gBrowser, testTab);
+
+ // Make sure focus is given to the window because the element is now gone.
+ is((yield getFocusedLocalName(browser)), "body", "body is focused");
+
+ // Cleanup.
+ gBrowser.removeTab(blankTab);
+ gBrowser.removeCurrentTab();
+
+});
diff --git a/browser/base/content/test/general/browser_bug431826.js b/browser/base/content/test/general/browser_bug431826.js
new file mode 100644
index 000000000..592ea9cef
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug431826.js
@@ -0,0 +1,50 @@
+function remote(task) {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, task);
+}
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ let promise = remote(function () {
+ return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true, event => {
+ return content.document.documentURI != "about:blank";
+ }).then(() => 0); // don't want to send the event to the chrome process
+ });
+ gBrowser.loadURI("https://nocert.example.com/");
+ yield promise;
+
+ yield remote(() => {
+ // Confirm that we are displaying the contributed error page, not the default
+ let uri = content.document.documentURI;
+ Assert.ok(uri.startsWith("about:certerror"), "Broken page should go to about:certerror, not about:neterror");
+ });
+
+ yield remote(() => {
+ let div = content.document.getElementById("badCertAdvancedPanel");
+ // Confirm that the expert section is collapsed
+ Assert.ok(div, "Advanced content div should exist");
+ Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
+ "none", "Advanced content should not be visible by default");
+ });
+
+ // Tweak the expert mode pref
+ gPrefService.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
+
+ promise = remote(function () {
+ return ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true);
+ });
+ gBrowser.reload();
+ yield promise;
+
+ yield remote(() => {
+ let div = content.document.getElementById("badCertAdvancedPanel");
+ Assert.ok(div, "Advanced content div should exist");
+ Assert.equal(div.ownerGlobal.getComputedStyle(div).display,
+ "block", "Advanced content should be visible by default");
+ });
+
+ // Clean up
+ gBrowser.removeCurrentTab();
+ if (gPrefService.prefHasUserValue("browser.xul.error_pages.expert_bad_cert"))
+ gPrefService.clearUserPref("browser.xul.error_pages.expert_bad_cert");
+});
diff --git a/browser/base/content/test/general/browser_bug432599.js b/browser/base/content/test/general/browser_bug432599.js
new file mode 100644
index 000000000..a5f7c0b5e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug432599.js
@@ -0,0 +1,127 @@
+function invokeUsingCtrlD(phase) {
+ switch (phase) {
+ case 1:
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ break;
+ case 2:
+ case 4:
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ break;
+ case 3:
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ break;
+ }
+}
+
+function invokeUsingStarButton(phase) {
+ switch (phase) {
+ case 1:
+ EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star, {});
+ break;
+ case 2:
+ case 4:
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ break;
+ case 3:
+ EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star,
+ { clickCount: 2 });
+ break;
+ }
+}
+
+var testURL = "data:text/plain,Content";
+var bookmarkId;
+
+function add_bookmark(aURI, aTitle) {
+ return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ aTitle);
+}
+
+// test bug 432599
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+ waitForStarChange(false, initTest);
+ }, true);
+
+ content.location = testURL;
+}
+
+function initTest() {
+ // First, bookmark the page.
+ bookmarkId = add_bookmark(makeURI(testURL), "Bug 432599 Test");
+
+ checkBookmarksPanel(invokers[currentInvoker], 1);
+}
+
+function waitForStarChange(aValue, aCallback) {
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(waitForStarChange, 50, aValue, aCallback);
+ return;
+ }
+ aCallback();
+}
+
+var invokers = [invokeUsingStarButton, invokeUsingCtrlD];
+var currentInvoker = 0;
+
+var initialValue;
+var initialRemoveHidden;
+
+var popupElement = document.getElementById("editBookmarkPanel");
+var titleElement = document.getElementById("editBookmarkPanelTitle");
+var removeElement = document.getElementById("editBookmarkPanelRemoveButton");
+
+function checkBookmarksPanel(invoker, phase)
+{
+ let onPopupShown = function(aEvent) {
+ if (aEvent.originalTarget == popupElement) {
+ popupElement.removeEventListener("popupshown", arguments.callee, false);
+ checkBookmarksPanel(invoker, phase + 1);
+ }
+ };
+ let onPopupHidden = function(aEvent) {
+ if (aEvent.originalTarget == popupElement) {
+ popupElement.removeEventListener("popuphidden", arguments.callee, false);
+ if (phase < 4) {
+ checkBookmarksPanel(invoker, phase + 1);
+ } else {
+ ++currentInvoker;
+ if (currentInvoker < invokers.length) {
+ checkBookmarksPanel(invokers[currentInvoker], 1);
+ } else {
+ gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
+ PlacesUtils.bookmarks.removeItem(bookmarkId);
+ executeSoon(finish);
+ }
+ }
+ }
+ };
+
+ switch (phase) {
+ case 1:
+ case 3:
+ popupElement.addEventListener("popupshown", onPopupShown, false);
+ break;
+ case 2:
+ popupElement.addEventListener("popuphidden", onPopupHidden, false);
+ initialValue = titleElement.value;
+ initialRemoveHidden = removeElement.hidden;
+ break;
+ case 4:
+ popupElement.addEventListener("popuphidden", onPopupHidden, false);
+ is(titleElement.value, initialValue, "The bookmark panel's title should be the same");
+ is(removeElement.hidden, initialRemoveHidden, "The bookmark panel's visibility should not change");
+ break;
+ }
+ invoker(phase);
+}
diff --git a/browser/base/content/test/general/browser_bug435035.js b/browser/base/content/test/general/browser_bug435035.js
new file mode 100644
index 000000000..7570ef0d7
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug435035.js
@@ -0,0 +1,17 @@
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ is(document.getElementById("identity-box").className,
+ "unknownIdentity mixedDisplayContent",
+ "identity box has class name for mixed content");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+
+ gBrowser.loadURI(
+ "https://example.com/browser/browser/base/content/test/general/test_bug435035.html"
+ );
+}
diff --git a/browser/base/content/test/general/browser_bug435325.js b/browser/base/content/test/general/browser_bug435325.js
new file mode 100644
index 000000000..2ae15deff
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug435325.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */
+
+var proxyPrefValue;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Go offline and disable the proxy and cache, then try to load the test URL.
+ Services.io.offline = true;
+
+ // Tests always connect to localhost, and per bug 87717, localhost is now
+ // reachable in offline mode. To avoid this, disable any proxy.
+ proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+ Services.prefs.setIntPref("network.proxy.type", 0);
+
+ Services.prefs.setBoolPref("browser.cache.disk.enable", false);
+ Services.prefs.setBoolPref("browser.cache.memory.enable", false);
+
+ gBrowser.selectedTab = gBrowser.addTab("http://example.com/");
+
+ let contentScript = `
+ let listener = function () {
+ removeEventListener("DOMContentLoaded", listener);
+ sendAsyncMessage("Test:DOMContentLoaded", { uri: content.document.documentURI });
+ };
+ addEventListener("DOMContentLoaded", listener);
+ `;
+
+ function pageloaded({ data }) {
+ mm.removeMessageListener("Test:DOMContentLoaded", pageloaded);
+ checkPage(data);
+ }
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("Test:DOMContentLoaded", pageloaded);
+ mm.loadFrameScript("data:," + contentScript, true);
+}
+
+function checkPage(data) {
+ ok(Services.io.offline, "Setting Services.io.offline to true.");
+
+ is(data.uri.substring(0, 27),
+ "about:neterror?e=netOffline", "Loading the Offline mode neterror page.");
+
+ // Re-enable the proxy so example.com is resolved to localhost, rather than
+ // the actual example.com.
+ Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
+
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
+ "online.");
+ Services.obs.removeObserver(observer, "network:offline-status-changed", false);
+ finish();
+ }, "network:offline-status-changed", false);
+
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ content.document.getElementById("errorTryAgain").click();
+ });
+}
+
+registerCleanupFunction(function() {
+ Services.prefs.setBoolPref("browser.cache.disk.enable", true);
+ Services.prefs.setBoolPref("browser.cache.memory.enable", true);
+ Services.io.offline = false;
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug441778.js b/browser/base/content/test/general/browser_bug441778.js
new file mode 100644
index 000000000..fa938541f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug441778.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test the fix for bug 441778 to ensure site-specific page zoom doesn't get
+ * modified by sub-document loads of content from a different domain.
+ */
+
+function test() {
+ waitForExplicitFinish();
+
+ const TEST_PAGE_URL = 'data:text/html,<body><iframe src=""></iframe></body>';
+ const TEST_IFRAME_URL = "http://test2.example.org/";
+
+ Task.spawn(function* () {
+ // Prepare the test tab
+ let tab = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
+
+ let testBrowser = tab.linkedBrowser;
+
+ yield FullZoomHelper.load(tab, TEST_PAGE_URL);
+
+ // Change the zoom level and then save it so we can compare it to the level
+ // after loading the sub-document.
+ FullZoom.enlarge();
+ var zoomLevel = ZoomManager.zoom;
+
+ // Start the sub-document load.
+ let deferred = Promise.defer();
+ executeSoon(function () {
+ BrowserTestUtils.browserLoaded(testBrowser, true).then(url => {
+ is(url, TEST_IFRAME_URL, "got the load event for the iframe");
+ is(ZoomManager.zoom, zoomLevel, "zoom is retained after sub-document load");
+
+ FullZoomHelper.removeTabAndWaitForLocationChange().
+ then(() => deferred.resolve());
+ });
+ ContentTask.spawn(testBrowser, TEST_IFRAME_URL, url => {
+ content.document.querySelector("iframe").src = url;
+ });
+ });
+ yield deferred.promise;
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug455852.js b/browser/base/content/test/general/browser_bug455852.js
new file mode 100644
index 000000000..ce883b581
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug455852.js
@@ -0,0 +1,20 @@
+add_task(function*() {
+ is(gBrowser.tabs.length, 1, "one tab is open");
+
+ gBrowser.selectedBrowser.focus();
+ isnot(document.activeElement, gURLBar.inputField, "location bar is not focused");
+
+ var tab = gBrowser.selectedTab;
+ gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+
+ let tabClosedPromise = BrowserTestUtils.removeTab(tab, {dontRemove: true});
+ EventUtils.synthesizeKey("w", { accelKey: true });
+ yield tabClosedPromise;
+
+ is(tab.parentNode, null, "ctrl+w removes the tab");
+ is(gBrowser.tabs.length, 1, "a new tab has been opened");
+ is(document.activeElement, gURLBar.inputField, "location bar is focused for the new tab");
+
+ if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
+ gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
+});
diff --git a/browser/base/content/test/general/browser_bug460146.js b/browser/base/content/test/general/browser_bug460146.js
new file mode 100644
index 000000000..1fdf0921c
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug460146.js
@@ -0,0 +1,51 @@
+/* Check proper image url retrieval from all kinds of elements/styles */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab");
+
+ pageInfo.addEventListener("load", function () {
+ pageInfo.removeEventListener("load", arguments.callee, true);
+ pageInfo.onFinished.push(function () {
+ executeSoon(function () {
+ var imageTree = pageInfo.document.getElementById("imagetree");
+ var imageRowsNum = imageTree.view.rowCount;
+
+ ok(imageTree, "Image tree is null (media tab is broken)");
+
+ ok(imageRowsNum == 7, "Number of images listed: " +
+ imageRowsNum + ", should be 7");
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "data:text/html," +
+ "<html>" +
+ " <head>" +
+ " <title>Test for media tab</title>" +
+ " <link rel='shortcut icon' href='file:///dummy_icon.ico'>" + // Icon
+ " </head>" +
+ " <body style='background-image:url(about:logo?a);'>" + // Background
+ " <img src='file:///dummy_image.gif'>" + // Image
+ " <ul>" +
+ " <li style='list-style:url(about:logo?b);'>List Item 1</li>" + // Bullet
+ " </ul> " +
+ " <div style='-moz-border-image: url(about:logo?c) 20 20 20 20;'>test</div>" + // Border
+ " <a href='' style='cursor: url(about:logo?d),default;'>test link</a>" + // Cursor
+ " <object type='image/svg+xml' width=20 height=20 data='file:///dummy_object.svg'></object>" + // Object
+ " </body>" +
+ "</html>";
+}
diff --git a/browser/base/content/test/general/browser_bug462289.js b/browser/base/content/test/general/browser_bug462289.js
new file mode 100644
index 000000000..1ce79f07e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug462289.js
@@ -0,0 +1,81 @@
+var tab1, tab2;
+
+function focus_in_navbar()
+{
+ var parent = document.activeElement.parentNode;
+ while (parent && parent.id != "nav-bar")
+ parent = parent.parentNode;
+
+ return parent != null;
+}
+
+function test()
+{
+ waitForExplicitFinish();
+
+ tab1 = gBrowser.addTab("about:blank", {skipAnimation: true});
+ tab2 = gBrowser.addTab("about:blank", {skipAnimation: true});
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ executeSoon(step2);
+}
+
+function step2()
+{
+ is(gBrowser.selectedTab, tab1, "1st click on tab1 selects tab");
+ isnot(document.activeElement, tab1, "1st click on tab1 does not activate tab");
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ executeSoon(step3);
+}
+
+function step3()
+{
+ is(gBrowser.selectedTab, tab1, "2nd click on selected tab1 keeps tab selected");
+ isnot(document.activeElement, tab1, "2nd click on selected tab1 does not activate tab");
+
+ ok(true, "focusing URLBar then sending 1 Shift+Tab.");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
+ is(gBrowser.selectedTab, tab1, "tab key to selected tab1 keeps tab selected");
+ is(document.activeElement, tab1, "tab key to selected tab1 activates tab");
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ executeSoon(step4);
+}
+
+function step4()
+{
+ is(gBrowser.selectedTab, tab1, "3rd click on activated tab1 keeps tab selected");
+ is(document.activeElement, tab1, "3rd click on activated tab1 keeps tab activated");
+
+ gBrowser.addEventListener("TabSwitchDone", step5);
+ EventUtils.synthesizeMouseAtCenter(tab2, {});
+}
+
+function step5()
+{
+ gBrowser.removeEventListener("TabSwitchDone", step5);
+
+ // The tabbox selects a tab within a setTimeout in a bubbling mousedown event
+ // listener, and focuses the current tab if another tab previously had focus.
+ is(gBrowser.selectedTab, tab2, "click on tab2 while tab1 is activated selects tab");
+ is(document.activeElement, tab2, "click on tab2 while tab1 is activated activates tab");
+
+ info("focusing content then sending middle-button mousedown to tab2.");
+ gBrowser.selectedBrowser.focus();
+
+ EventUtils.synthesizeMouseAtCenter(tab2, {button: 1, type: "mousedown"});
+ executeSoon(step6);
+}
+
+function step6()
+{
+ is(gBrowser.selectedTab, tab2, "middle-button mousedown on selected tab2 keeps tab selected");
+ isnot(document.activeElement, tab2, "middle-button mousedown on selected tab2 does not activate tab");
+
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab1);
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug462673.js b/browser/base/content/test/general/browser_bug462673.js
new file mode 100644
index 000000000..f5b090917
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug462673.js
@@ -0,0 +1,36 @@
+add_task(function* () {
+ var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+ yield SimpleTest.promiseFocus(win);
+
+ let tab = win.gBrowser.tabContainer.firstChild;
+ yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
+
+ is(win.gBrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
+ is(win.gBrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
+ win.gBrowser.removeTab(tab);
+
+ // Closing a tab will also close its parent chrome window, but async
+ yield promiseWindowWillBeClosed(win);
+});
+
+add_task(function* () {
+ var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+ yield SimpleTest.promiseFocus(win);
+
+ let tab = win.gBrowser.tabContainer.firstChild;
+ yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html");
+
+ var newTab = win.gBrowser.addTab();
+ var newBrowser = newTab.linkedBrowser;
+ win.gBrowser.removeTab(tab);
+ ok(!win.closed, "Window stays open");
+ if (!win.closed) {
+ is(win.gBrowser.tabContainer.childElementCount, 1, "Window has one tab");
+ is(win.gBrowser.browsers.length, 1, "Window has one browser");
+ is(win.gBrowser.selectedTab, newTab, "Remaining tab is selected");
+ is(win.gBrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
+ is(win.gBrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected");
+ }
+
+ yield promiseWindowClosed(win);
+});
diff --git a/browser/base/content/test/general/browser_bug477014.js b/browser/base/content/test/general/browser_bug477014.js
new file mode 100644
index 000000000..8a0fac6d8
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug477014.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/. */
+
+// That's a gecko!
+const iconURLSpec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+var testPage="data:text/plain,test bug 477014";
+
+add_task(function*() {
+ let tabToDetach = gBrowser.addTab(testPage);
+ yield waitForDocLoadComplete(tabToDetach.linkedBrowser);
+
+ gBrowser.setIcon(tabToDetach, iconURLSpec,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ tabToDetach.setAttribute("busy", "true");
+
+ // detach and set the listener on the new window
+ let newWindow = gBrowser.replaceTabWithWindow(tabToDetach);
+ yield promiseWaitForEvent(tabToDetach.linkedBrowser, "SwapDocShells");
+
+ is(newWindow.gBrowser.selectedTab.hasAttribute("busy"), true, "Busy attribute should be correct");
+ is(newWindow.gBrowser.getIcon(), iconURLSpec, "Icon should be correct");
+
+ newWindow.close();
+});
diff --git a/browser/base/content/test/general/browser_bug479408.js b/browser/base/content/test/general/browser_bug479408.js
new file mode 100644
index 000000000..0dfa96f2e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug479408.js
@@ -0,0 +1,17 @@
+function test() {
+ waitForExplicitFinish();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(
+ "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug479408_sample.html");
+
+ gBrowser.addEventListener("DOMLinkAdded", function(aEvent) {
+ gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, true);
+
+ executeSoon(function() {
+ ok(!tab.linkedBrowser.engines,
+ "the subframe's search engine wasn't detected");
+
+ gBrowser.removeTab(tab);
+ finish();
+ });
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug479408_sample.html b/browser/base/content/test/general/browser_bug479408_sample.html
new file mode 100644
index 000000000..f83f02bb9
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug479408_sample.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>Testcase for bug 479408</title>
+
+<iframe src='data:text/html,<link%20rel="search"%20type="application/opensearchdescription+xml"%20title="Search%20bug%20479408"%20href="http://example.com/search.xml">'>
diff --git a/browser/base/content/test/general/browser_bug481560.js b/browser/base/content/test/general/browser_bug481560.js
new file mode 100644
index 000000000..bb9249e75
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug481560.js
@@ -0,0 +1,21 @@
+function test() {
+ waitForExplicitFinish();
+
+ whenNewWindowLoaded(null, function (win) {
+ waitForFocus(function () {
+ function onTabClose() {
+ ok(false, "shouldn't have gotten the TabClose event for the last tab");
+ }
+ var tab = win.gBrowser.selectedTab;
+ tab.addEventListener("TabClose", onTabClose, false);
+
+ EventUtils.synthesizeKey("w", { accelKey: true }, win);
+
+ ok(win.closed, "accel+w closed the window immediately");
+
+ tab.removeEventListener("TabClose", onTabClose, false);
+
+ finish();
+ }, win);
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug484315.js b/browser/base/content/test/general/browser_bug484315.js
new file mode 100644
index 000000000..fb23ae33a
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug484315.js
@@ -0,0 +1,23 @@
+function test() {
+ var contentWin = window.open("about:blank", "", "width=100,height=100");
+ var enumerator = Services.wm.getEnumerator("navigator:browser");
+
+ while (enumerator.hasMoreElements()) {
+ let win = enumerator.getNext();
+ if (win.content == contentWin) {
+ gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+ win.gBrowser.removeCurrentTab();
+ ok(win.closed, "popup is closed");
+
+ // clean up
+ if (!win.closed)
+ win.close();
+ if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
+ gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
+
+ return;
+ }
+ }
+
+ throw "couldn't find the content window";
+}
diff --git a/browser/base/content/test/general/browser_bug491431.js b/browser/base/content/test/general/browser_bug491431.js
new file mode 100644
index 000000000..d270e912e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug491431.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var testPage = "data:text/plain,test bug 491431 Page";
+
+function test() {
+ waitForExplicitFinish();
+
+ let newWin, tabA, tabB;
+
+ // test normal close
+ tabA = gBrowser.addTab(testPage);
+ gBrowser.tabContainer.addEventListener("TabClose", function(firstTabCloseEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
+ ok(!firstTabCloseEvent.detail.adoptedBy, "This was a normal tab close");
+
+ // test tab close by moving
+ tabB = gBrowser.addTab(testPage);
+ gBrowser.tabContainer.addEventListener("TabClose", function(secondTabCloseEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
+ executeSoon(function() {
+ ok(secondTabCloseEvent.detail.adoptedBy, "This was a tab closed by moving");
+
+ // cleanup
+ newWin.close();
+ executeSoon(finish);
+ });
+ }, true);
+ newWin = gBrowser.replaceTabWithWindow(tabB);
+ }, true);
+ gBrowser.removeTab(tabA);
+}
+
diff --git a/browser/base/content/test/general/browser_bug495058.js b/browser/base/content/test/general/browser_bug495058.js
new file mode 100644
index 000000000..a82c6c931
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug495058.js
@@ -0,0 +1,38 @@
+/**
+ * Tests that the right elements of a tab are focused when it is
+ * torn out into its own window.
+ */
+
+const URIS = [
+ "about:blank",
+ "about:sessionrestore",
+ "about:privatebrowsing",
+];
+
+add_task(function*() {
+ for (let uri of URIS) {
+ let tab = gBrowser.addTab();
+ yield BrowserTestUtils.loadURI(tab.linkedBrowser, uri);
+
+ let win = gBrowser.replaceTabWithWindow(tab);
+ yield TestUtils.topicObserved("browser-delayed-startup-finished",
+ subject => subject == win);
+ tab = win.gBrowser.selectedTab;
+
+ // BrowserTestUtils doesn't get the add-on shims, which means that
+ // MozAfterPaint won't get shimmed over if we add an event handler
+ // for it in the parent.
+ if (tab.linkedBrowser.isRemoteBrowser) {
+ yield BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "MozAfterPaint");
+ } else {
+ yield BrowserTestUtils.waitForEvent(tab.linkedBrowser, "MozAfterPaint");
+ }
+
+ Assert.equal(win.gBrowser.currentURI.spec, uri, uri + ": uri loaded in detached tab");
+ Assert.equal(win.document.activeElement, win.gBrowser.selectedBrowser, uri + ": browser is focused");
+ Assert.equal(win.gURLBar.value, "", uri + ": urlbar is empty");
+ Assert.ok(win.gURLBar.placeholder, uri + ": placeholder text is present");
+
+ yield BrowserTestUtils.closeWindow(win);
+ }
+});
diff --git a/browser/base/content/test/general/browser_bug517902.js b/browser/base/content/test/general/browser_bug517902.js
new file mode 100644
index 000000000..bc1d16f4b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug517902.js
@@ -0,0 +1,42 @@
+/* Make sure that "View Image Info" loads the correct image data */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ var doc = gBrowser.contentDocument;
+ var testImg = doc.getElementById("test-image");
+ var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab", testImg);
+
+ pageInfo.addEventListener("load", function () {
+ pageInfo.removeEventListener("load", arguments.callee, true);
+ pageInfo.onFinished.push(function () {
+ executeSoon(function () {
+ var pageInfoImg = pageInfo.document.getElementById("thepreviewimage");
+
+ is(pageInfoImg.src, testImg.src, "selected image has the correct source");
+ is(pageInfoImg.width, testImg.width, "selected image has the correct width");
+ is(pageInfoImg.height, testImg.height, "selected image has the correct height");
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "data:text/html," +
+ "<style type='text/css'>%23test-image,%23not-test-image {background-image: url('about:logo?c');}</style>" +
+ "<img src='about:logo?b' height=300 width=350 alt=2 id='not-test-image'>" +
+ "<img src='about:logo?b' height=300 width=350 alt=2>" +
+ "<img src='about:logo?a' height=200 width=250>" +
+ "<img src='about:logo?b' height=200 width=250 alt=1>" +
+ "<img src='about:logo?b' height=100 width=150 alt=2 id='test-image'>";
+}
diff --git a/browser/base/content/test/general/browser_bug519216.js b/browser/base/content/test/general/browser_bug519216.js
new file mode 100644
index 000000000..d3a517086
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug519216.js
@@ -0,0 +1,45 @@
+function test() {
+ waitForExplicitFinish();
+ gBrowser.addProgressListener(progressListener1);
+ gBrowser.addProgressListener(progressListener2);
+ gBrowser.addProgressListener(progressListener3);
+ gBrowser.loadURI("data:text/plain,bug519216");
+}
+
+var calledListener1 = false;
+var progressListener1 = {
+ onLocationChange: function onLocationChange() {
+ calledListener1 = true;
+ gBrowser.removeProgressListener(this);
+ }
+};
+
+var calledListener2 = false;
+var progressListener2 = {
+ onLocationChange: function onLocationChange() {
+ ok(calledListener1, "called progressListener1 before progressListener2");
+ calledListener2 = true;
+ gBrowser.removeProgressListener(this);
+ }
+};
+
+var progressListener3 = {
+ onLocationChange: function onLocationChange() {
+ ok(calledListener2, "called progressListener2 before progressListener3");
+ gBrowser.removeProgressListener(this);
+ gBrowser.addProgressListener(progressListener4);
+ executeSoon(function () {
+ expectListener4 = true;
+ gBrowser.reload();
+ });
+ }
+};
+
+var expectListener4 = false;
+var progressListener4 = {
+ onLocationChange: function onLocationChange() {
+ ok(expectListener4, "didn't call progressListener4 for the first location change");
+ gBrowser.removeProgressListener(this);
+ executeSoon(finish);
+ }
+};
diff --git a/browser/base/content/test/general/browser_bug520538.js b/browser/base/content/test/general/browser_bug520538.js
new file mode 100644
index 000000000..e0b64db9d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug520538.js
@@ -0,0 +1,15 @@
+function test() {
+ var tabCount = gBrowser.tabs.length;
+ gBrowser.selectedBrowser.focus();
+ browserDOMWindow.openURI(makeURI("about:blank"),
+ null,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+ is(gBrowser.tabs.length, tabCount + 1,
+ "'--new-tab about:blank' opens a new tab");
+ is(gBrowser.selectedTab, gBrowser.tabs[tabCount],
+ "'--new-tab about:blank' selects the new tab");
+ is(document.activeElement, gURLBar.inputField,
+ "'--new-tab about:blank' focuses the location bar");
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/general/browser_bug521216.js b/browser/base/content/test/general/browser_bug521216.js
new file mode 100644
index 000000000..735ae92f6
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug521216.js
@@ -0,0 +1,50 @@
+var expected = ["TabOpen", "onStateChange", "onLocationChange", "onLinkIconAvailable"];
+var actual = [];
+var tabIndex = -1;
+this.__defineGetter__("tab", () => gBrowser.tabs[tabIndex]);
+
+function test() {
+ waitForExplicitFinish();
+ tabIndex = gBrowser.tabs.length;
+ gBrowser.addTabsProgressListener(progressListener);
+ gBrowser.tabContainer.addEventListener("TabOpen", TabOpen, false);
+ gBrowser.addTab("data:text/html,<html><head><link href='about:logo' rel='shortcut icon'>");
+}
+
+function record(aName) {
+ info("got " + aName);
+ if (actual.indexOf(aName) == -1)
+ actual.push(aName);
+ if (actual.length == expected.length) {
+ is(actual.toString(), expected.toString(),
+ "got events and progress notifications in expected order");
+
+ executeSoon(function(tab) {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTabsProgressListener(progressListener);
+ gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false);
+ finish();
+ }.bind(null, tab));
+ }
+}
+
+function TabOpen(aEvent) {
+ if (aEvent.target == tab)
+ record(arguments.callee.name);
+}
+
+var progressListener = {
+ onLocationChange: function onLocationChange(aBrowser) {
+ if (aBrowser == tab.linkedBrowser)
+ record(arguments.callee.name);
+ },
+ onStateChange: function onStateChange(aBrowser) {
+ if (aBrowser == tab.linkedBrowser)
+ record(arguments.callee.name);
+ },
+ onLinkIconAvailable: function onLinkIconAvailable(aBrowser, aIconURL) {
+ if (aBrowser == tab.linkedBrowser &&
+ aIconURL == "about:logo")
+ record(arguments.callee.name);
+ }
+};
diff --git a/browser/base/content/test/general/browser_bug533232.js b/browser/base/content/test/general/browser_bug533232.js
new file mode 100644
index 000000000..6c7a0e51f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug533232.js
@@ -0,0 +1,36 @@
+function test() {
+ var tab1 = gBrowser.selectedTab;
+ var tab2 = gBrowser.addTab();
+ var childTab1;
+ var childTab2;
+
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(tab1),
+ "closing a tab next to its parent selects the parent");
+
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = tab2;
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(tab2),
+ "closing a tab next to its parent doesn't select the parent if another tab had been selected ad interim");
+
+ gBrowser.selectedTab = tab1;
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ childTab2 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(childTab2),
+ "closing a tab next to its parent selects the next tab with the same parent");
+ gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
+ is(idx(gBrowser.selectedTab), idx(tab2),
+ "closing the last tab in a set of child tabs doesn't go back to the parent");
+
+ gBrowser.removeTab(tab2, { skipPermitUnload: true });
+}
+
+function idx(tab) {
+ return Array.indexOf(gBrowser.tabs, tab);
+}
diff --git a/browser/base/content/test/general/browser_bug537013.js b/browser/base/content/test/general/browser_bug537013.js
new file mode 100644
index 000000000..5ae1586ea
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug537013.js
@@ -0,0 +1,135 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Tests for bug 537013 to ensure proper tab-sequestration of find bar. */
+
+var tabs = [];
+var texts = [
+ "This side up.",
+ "The world is coming to an end. Please log off.",
+ "Klein bottle for sale. Inquire within.",
+ "To err is human; to forgive is not company policy."
+];
+
+var Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+var HasFindClipboard = Clipboard.supportsFindClipboard();
+
+function addTabWithText(aText, aCallback) {
+ let newTab = gBrowser.addTab("data:text/html;charset=utf-8,<h1 id='h1'>" +
+ aText + "</h1>");
+ tabs.push(newTab);
+ gBrowser.selectedTab = newTab;
+}
+
+function setFindString(aString) {
+ gFindBar.open();
+ gFindBar._findField.focus();
+ gFindBar._findField.select();
+ EventUtils.sendString(aString);
+ is(gFindBar._findField.value, aString, "Set the field correctly!");
+}
+
+var newWindow;
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function () {
+ while (tabs.length) {
+ gBrowser.removeTab(tabs.pop());
+ }
+ });
+ texts.forEach(aText => addTabWithText(aText));
+
+ // Set up the first tab
+ gBrowser.selectedTab = tabs[0];
+
+ setFindString(texts[0]);
+ // Turn on highlight for testing bug 891638
+ gFindBar.toggleHighlight(true);
+
+ // Make sure the second tab is correct, then set it up
+ gBrowser.selectedTab = tabs[1];
+ gBrowser.selectedTab.addEventListener("TabFindInitialized", continueTests1);
+ // Initialize the findbar
+ gFindBar;
+}
+function continueTests1() {
+ gBrowser.selectedTab.removeEventListener("TabFindInitialized",
+ continueTests1);
+ ok(true, "'TabFindInitialized' event properly dispatched!");
+ ok(gFindBar.hidden, "Second tab doesn't show find bar!");
+ gFindBar.open();
+ is(gFindBar._findField.value, texts[0],
+ "Second tab kept old find value for new initialization!");
+ setFindString(texts[1]);
+
+ // Confirm the first tab is still correct, ensure re-hiding works as expected
+ gBrowser.selectedTab = tabs[0];
+ ok(!gFindBar.hidden, "First tab shows find bar!");
+ // When the Find Clipboard is supported, this test not relevant.
+ if (!HasFindClipboard)
+ is(gFindBar._findField.value, texts[0], "First tab persists find value!");
+ ok(gFindBar.getElement("highlight").checked,
+ "Highlight button state persists!");
+
+ // While we're here, let's test the backout of bug 253793.
+ gBrowser.reload();
+ gBrowser.addEventListener("DOMContentLoaded", continueTests2, true);
+}
+
+function continueTests2() {
+ gBrowser.removeEventListener("DOMContentLoaded", continueTests2, true);
+ ok(gFindBar.getElement("highlight").checked, "Highlight never reset!");
+ continueTests3();
+}
+
+function continueTests3() {
+ ok(gFindBar.getElement("highlight").checked, "Highlight button reset!");
+ gFindBar.close();
+ ok(gFindBar.hidden, "First tab doesn't show find bar!");
+ gBrowser.selectedTab = tabs[1];
+ ok(!gFindBar.hidden, "Second tab shows find bar!");
+ // Test for bug 892384
+ is(gFindBar._findField.getAttribute("focused"), "true",
+ "Open findbar refocused on tab change!");
+ gURLBar.focus();
+ gBrowser.selectedTab = tabs[0];
+ ok(gFindBar.hidden, "First tab doesn't show find bar!");
+
+ // Set up a third tab, no tests here
+ gBrowser.selectedTab = tabs[2];
+ setFindString(texts[2]);
+
+ // Now we jump to the second, then first, and then fourth
+ gBrowser.selectedTab = tabs[1];
+ // Test for bug 892384
+ ok(!gFindBar._findField.hasAttribute("focused"),
+ "Open findbar not refocused on tab change!");
+ gBrowser.selectedTab = tabs[0];
+ gBrowser.selectedTab = tabs[3];
+ ok(gFindBar.hidden, "Fourth tab doesn't show find bar!");
+ is(gFindBar, gBrowser.getFindBar(), "Find bar is right one!");
+ gFindBar.open();
+ // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug.
+ if (!HasFindClipboard) {
+ is(gFindBar._findField.value, texts[1],
+ "Fourth tab has second tab's find value!");
+ }
+
+ newWindow = gBrowser.replaceTabWithWindow(tabs.pop());
+ whenDelayedStartupFinished(newWindow, checkNewWindow);
+}
+
+// Test that findbar gets restored when a tab is moved to a new window.
+function checkNewWindow() {
+ ok(!newWindow.gFindBar.hidden, "New window shows find bar!");
+ // Disabled the following assertion due to intermittent failure on OSX 10.6 Debug.
+ if (!HasFindClipboard) {
+ is(newWindow.gFindBar._findField.value, texts[1],
+ "New window find bar has correct find value!");
+ }
+ ok(!newWindow.gFindBar.getElement("find-next").disabled,
+ "New window findbar has enabled buttons!");
+ newWindow.close();
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug537474.js b/browser/base/content/test/general/browser_bug537474.js
new file mode 100644
index 000000000..f1139f235
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug537474.js
@@ -0,0 +1,8 @@
+add_task(function *() {
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ browserDOMWindow.openURI(makeURI("about:"), null,
+ Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null)
+ yield browserLoadedPromise;
+ is(gBrowser.currentURI.spec, "about:", "page loads in the current content window");
+});
+
diff --git a/browser/base/content/test/general/browser_bug550565.js b/browser/base/content/test/general/browser_bug550565.js
new file mode 100644
index 000000000..b0e094e07
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug550565.js
@@ -0,0 +1,44 @@
+add_task(function* test() {
+ let testPath = getRootDirectory(gTestPath);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (tabBrowser) {
+ const URI = testPath + "file_with_favicon.html";
+ const expectedIcon = testPath + "file_generic_favicon.ico";
+
+ let got_favicon = Promise.defer();
+ let listener = {
+ onLinkIconAvailable(browser, iconURI) {
+ if (got_favicon && iconURI && browser === tabBrowser) {
+ got_favicon.resolve(iconURI);
+ got_favicon = null;
+ }
+ }
+ };
+ gBrowser.addTabsProgressListener(listener);
+
+ BrowserTestUtils.loadURI(tabBrowser, URI);
+
+ let iconURI = yield got_favicon.promise;
+ is(iconURI, expectedIcon, "Correct icon before pushState.");
+
+ got_favicon = Promise.defer();
+ got_favicon.promise.then(() => { ok(false, "shouldn't be called"); }, (e) => e);
+ yield ContentTask.spawn(tabBrowser, null, function() {
+ content.history.pushState("page2", "page2", "page2");
+ });
+
+ // We've navigated and shouldn't get a call to onLinkIconAvailable.
+ TestUtils.executeSoon(() => {
+ got_favicon.reject(gBrowser.getIcon(gBrowser.getTabForBrowser(tabBrowser)));
+ });
+ try {
+ yield got_favicon.promise;
+ } catch (e) {
+ iconURI = e;
+ }
+ is(iconURI, expectedIcon, "Correct icon after pushState.");
+
+ gBrowser.removeTabsProgressListener(listener);
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug553455.js b/browser/base/content/test/general/browser_bug553455.js
new file mode 100644
index 000000000..c29a810de
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -0,0 +1,1200 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
+const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
+const PROGRESS_NOTIFICATION = "addon-progress";
+
+const { REQUIRE_SIGNING } = Cu.import("resource://gre/modules/addons/AddonConstants.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm");
+
+var rootDir = getRootDirectory(gTestPath);
+var rootPath = rootDir.split('/');
+var chromeName = rootPath[0] + '//' + rootPath[2];
+var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
+var jar = getJar(croot);
+if (jar) {
+ var tmpdir = extractJarToTmp(jar);
+ croot = 'file://' + tmpdir.path + '/';
+}
+const CHROMEROOT = croot;
+
+var gApp = document.getElementById("bundle_brand").getString("brandShortName");
+var gVersion = Services.appinfo.version;
+
+function getObserverTopic(aNotificationId) {
+ let topic = aNotificationId;
+ if (topic == "xpinstall-disabled")
+ topic = "addon-install-disabled";
+ else if (topic == "addon-progress")
+ topic = "addon-install-started";
+ else if (topic == "addon-install-restart")
+ topic = "addon-install-complete";
+ return topic;
+}
+
+function waitForProgressNotification(aPanelOpen = false, aExpectedCount = 1) {
+ return Task.spawn(function* () {
+ let notificationId = PROGRESS_NOTIFICATION;
+ info("Waiting for " + notificationId + " notification");
+
+ let topic = getObserverTopic(notificationId);
+
+ let observerPromise = new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ // Ignore the progress notification unless that is the notification we want
+ if (notificationId != PROGRESS_NOTIFICATION &&
+ aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) {
+ return;
+ }
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }, topic, false);
+ });
+
+ let panelEventPromise;
+ if (aPanelOpen) {
+ panelEventPromise = Promise.resolve();
+ } else {
+ panelEventPromise = new Promise(resolve => {
+ PopupNotifications.panel.addEventListener("popupshowing", function eventListener() {
+ PopupNotifications.panel.removeEventListener("popupshowing", eventListener);
+ resolve();
+ });
+ });
+ }
+
+ yield observerPromise;
+ yield panelEventPromise;
+
+ info("Saw a notification");
+ ok(PopupNotifications.isPanelOpen, "Panel should be open");
+ is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
+ if (PopupNotifications.panel.childNodes.length) {
+ let nodes = Array.from(PopupNotifications.panel.childNodes);
+ let notification = nodes.find(n => n.id == notificationId + "-notification");
+ ok(notification, `Should have seen the right notification`);
+ }
+
+ return PopupNotifications.panel;
+ });
+}
+
+function waitForNotification(aId, aExpectedCount = 1) {
+ return Task.spawn(function* () {
+ info("Waiting for " + aId + " notification");
+
+ let topic = getObserverTopic(aId);
+
+ let observerPromise = new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ // Ignore the progress notification unless that is the notification we want
+ if (aId != PROGRESS_NOTIFICATION &&
+ aTopic == getObserverTopic(PROGRESS_NOTIFICATION)) {
+ return;
+ }
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }, topic, false);
+ });
+
+ let panelEventPromise = new Promise(resolve => {
+ PopupNotifications.panel.addEventListener("PanelUpdated", function eventListener(e) {
+ // Skip notifications that are not the one that we are supposed to be looking for
+ if (e.detail.indexOf(aId) == -1) {
+ return;
+ }
+ PopupNotifications.panel.removeEventListener("PanelUpdated", eventListener);
+ resolve();
+ });
+ });
+
+ yield observerPromise;
+ yield panelEventPromise;
+
+ info("Saw a notification");
+ ok(PopupNotifications.isPanelOpen, "Panel should be open");
+ is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
+ if (PopupNotifications.panel.childNodes.length) {
+ let nodes = Array.from(PopupNotifications.panel.childNodes);
+ let notification = nodes.find(n => n.id == aId + "-notification");
+ ok(notification, `Should have seen the right notification`);
+ }
+
+ return PopupNotifications.panel;
+ });
+}
+
+function waitForNotificationClose() {
+ return new Promise(resolve => {
+ info("Waiting for notification to close");
+ PopupNotifications.panel.addEventListener("popuphidden", function listener() {
+ PopupNotifications.panel.removeEventListener("popuphidden", listener, false);
+ resolve();
+ }, false);
+ });
+}
+
+function waitForInstallDialog() {
+ return Task.spawn(function* () {
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ yield waitForNotification("addon-install-confirmation");
+ return;
+ }
+
+ info("Waiting for install dialog");
+
+ let window = yield new Promise(resolve => {
+ Services.wm.addListener({
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+ resolve(aXULWindow);
+ },
+ onCloseWindow: function(aXULWindow) {
+ },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {
+ }
+ });
+ });
+ info("Install dialog opened, waiting for focus");
+
+ let domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ yield new Promise(resolve => {
+ waitForFocus(function() {
+ resolve();
+ }, domwindow);
+ });
+ info("Saw install dialog");
+ is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
+
+ // Override the countdown timer on the accept button
+ let button = domwindow.document.documentElement.getButton("accept");
+ button.disabled = false;
+
+ return;
+ });
+}
+
+function removeTab() {
+ return Promise.all([
+ waitForNotificationClose(),
+ BrowserTestUtils.removeTab(gBrowser.selectedTab)
+ ]);
+}
+
+function acceptInstallDialog() {
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ document.getElementById("addon-install-confirmation-accept").click();
+ } else {
+ let win = Services.wm.getMostRecentWindow("Addons:Install");
+ win.document.documentElement.acceptDialog();
+ }
+}
+
+function cancelInstallDialog() {
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ document.getElementById("addon-install-confirmation-cancel").click();
+ } else {
+ let win = Services.wm.getMostRecentWindow("Addons:Install");
+ win.document.documentElement.cancelDialog();
+ }
+}
+
+function waitForSingleNotification(aCallback) {
+ return Task.spawn(function* () {
+ while (PopupNotifications.panel.childNodes.length == 2) {
+ yield new Promise(resolve => executeSoon(resolve));
+
+ info("Waiting for single notification");
+ // Notification should never close while we wait
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ }
+ });
+}
+
+function setupRedirect(aSettings) {
+ var url = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs?mode=setup";
+ for (var name in aSettings) {
+ url += "&" + name + "=" + aSettings[name];
+ }
+
+ var req = new XMLHttpRequest();
+ req.open("GET", url, false);
+ req.send(null);
+}
+
+function getInstalls() {
+ return new Promise(resolve => {
+ AddonManager.getAllInstalls(installs => resolve(installs));
+ });
+}
+
+var TESTS = [
+function test_disabledInstall() {
+ return Task.spawn(function* () {
+ Services.prefs.setBoolPref("xpinstall.enabled", false);
+
+ let notificationPromise = waitForNotification("xpinstall-disabled");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Enable", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "Software installation is currently disabled. Click Enable and try again.");
+
+ let closePromise = waitForNotificationClose();
+ // Click on Enable
+ EventUtils.synthesizeMouseAtCenter(notification.button, {});
+ yield closePromise;
+
+ try {
+ ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled");
+ }
+ catch (e) {
+ ok(false, "xpinstall.enabled should be set");
+ }
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Shouldn't be any pending installs");
+ });
+},
+
+function test_blockedInstall() {
+ return Task.spawn(function* () {
+ let notificationPromise = waitForNotification("addon-install-blocked");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Allow", "Should have seen the right button");
+ is(notification.getAttribute("origin"), "example.com",
+ "Should have seen the right origin host");
+ is(notification.getAttribute("label"),
+ gApp + " prevented this site from asking you to install software on your computer.",
+ "Should have seen the right message");
+
+ let dialogPromise = waitForInstallDialog();
+ // Click on Allow
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+ // Notification should have changed to progress notification
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+ yield dialogPromise;
+
+ notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ panel = yield notificationPromise;
+
+ notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+ yield removeTab();
+ });
+},
+
+function test_whitelistedInstall() {
+ return Task.spawn(function* () {
+ let originalTab = gBrowser.selectedTab;
+ let tab;
+ gBrowser.selectedTab = originalTab;
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?"
+ + triggers).then(newTab => tab = newTab);
+ yield progressPromise;
+ yield dialogPromise;
+ yield BrowserTestUtils.waitForCondition(() => !!tab, "tab should be present");
+
+ is(gBrowser.selectedTab, tab,
+ "tab selected in response to the addon-install-confirmation notification");
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_failedDownload() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let failPromise = waitForNotification("addon-install-failed");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "missing.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ let panel = yield failPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "The add-on could not be downloaded because of a connection failure.",
+ "Should have seen the right message");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_corruptFile() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let failPromise = waitForNotification("addon-install-failed");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "corrupt.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ let panel = yield failPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "The add-on downloaded from this site could not be installed " +
+ "because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_incompatible() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let failPromise = waitForNotification("addon-install-failed");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "incompatible.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ let panel = yield failPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "XPI Test could not be installed because it is not compatible with " +
+ gApp + " " + gVersion + ".",
+ "Should have seen the right message");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_restartless() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "restartless.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-complete");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "XPI Test has been installed successfully.",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending installs");
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", result => {
+ resolve(result);
+ });
+ });
+ addon.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+
+ let closePromise = waitForNotificationClose();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ yield closePromise;
+ });
+},
+
+function test_multiple() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Unsigned XPI": "amosigned.xpi",
+ "Restartless XPI": "restartless.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "2 add-ons will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function (result) {
+ resolve(result);
+ });
+ });
+ addon.uninstall();
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_sequential() {
+ return Task.spawn(function* () {
+ // This test is only relevant if using the new doorhanger UI
+ if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Restartless XPI": "restartless.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ // Should see the right add-on
+ let container = document.getElementById("addon-install-confirmation-content");
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+
+ progressPromise = waitForProgressNotification(true, 2);
+ triggers = encodeURIComponent(JSON.stringify({
+ "Theme XPI": "theme.xpi"
+ }));
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+
+ // Should still have the right add-on in the confirmation notification
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+
+ // Wait for the install to complete, we won't see a new confirmation
+ // notification
+ yield new Promise(resolve => {
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, "addon-install-confirmation");
+ resolve();
+ }, "addon-install-confirmation", false);
+ });
+
+ // Make sure browser-addons.js executes first
+ yield new Promise(resolve => executeSoon(resolve));
+
+ // Should have dropped the progress notification
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
+ is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
+ "Should only be showing one install confirmation");
+
+ // Should still have the right add-on in the confirmation notification
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+
+ cancelInstallDialog();
+
+ ok(PopupNotifications.isPanelOpen, "Panel should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be the right number of notifications");
+ is(PopupNotifications.panel.childNodes[0].id, "addon-install-confirmation-notification",
+ "Should still have an install confirmation open");
+
+ // Should have the next add-on's confirmation dialog
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
+
+ Services.perms.remove(makeURI("http://example.com"), "install");
+ let closePromise = waitForNotificationClose();
+ cancelInstallDialog();
+ yield closePromise;
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+},
+
+function test_someUnverified() {
+ return Task.spawn(function* () {
+ // This test is only relevant if using the new doorhanger UI and allowing
+ // unsigned add-ons
+ if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+ Preferences.get("xpinstall.signatures.required", true) ||
+ REQUIRE_SIGNING) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Extension XPI": "restartless-unsigned.xpi",
+ "Theme XPI": "theme.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notification = document.getElementById("addon-install-confirmation-notification");
+ let message = notification.getAttribute("label");
+ is(message, "Caution: This site would like to install 2 add-ons in " + gApp +
+ ", some of which are unverified. Proceed at your own risk.",
+ "Should see the right message");
+
+ let container = document.getElementById("addon-install-confirmation-content");
+ is(container.childNodes.length, 2, "Should be two items listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+ is(container.childNodes[0].lastChild.getAttribute("class"),
+ "addon-install-confirmation-unsigned", "Should have the unverified marker");
+ is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
+ is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker");
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let [addon, theme] = yield new Promise(resolve => {
+ AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org",
+ "theme-xpi@tests.mozilla.org"],
+ function(addons) {
+ resolve(addons);
+ });
+ });
+ addon.uninstall();
+ // Installing a new theme tries to switch to it, switch back to the
+ // default theme.
+ theme.userDisabled = true;
+ theme.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_allUnverified() {
+ return Task.spawn(function* () {
+ // This test is only relevant if using the new doorhanger UI and allowing
+ // unsigned add-ons
+ if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
+ Preferences.get("xpinstall.signatures.required", true) ||
+ REQUIRE_SIGNING) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Extension XPI": "restartless-unsigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notification = document.getElementById("addon-install-confirmation-notification");
+ let message = notification.getAttribute("label");
+ is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
+
+ let container = document.getElementById("addon-install-confirmation-content");
+ is(container.childNodes.length, 1, "Should be one item listed");
+ is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
+ is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
+
+ let notificationPromise = waitForNotification("addon-install-complete");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(result) {
+ resolve(result);
+ });
+ });
+ addon.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_url() {
+ return Task.spawn(function* () {
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ yield removeTab();
+ });
+},
+
+function test_localFile() {
+ return Task.spawn(function* () {
+ let cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Components.interfaces.nsIChromeRegistry);
+ let path;
+ try {
+ path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec;
+ } catch (ex) {
+ path = CHROMEROOT + "corrupt.xpi";
+ }
+
+ let failPromise = new Promise(resolve => {
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, "addon-install-failed");
+ resolve();
+ }, "addon-install-failed", false);
+ });
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(path);
+ yield failPromise;
+
+ // Wait for the browser code to add the failure notification
+ yield waitForSingleNotification();
+
+ let notification = PopupNotifications.panel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+ is(notification.getAttribute("label"),
+ "This add-on could not be installed because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ yield removeTab();
+ });
+},
+
+function test_tabClose() {
+ return Task.spawn(function* () {
+ if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+ runNextTest();
+ return;
+ }
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(TESTROOT + "amosigned.xpi");
+ yield progressPromise;
+ yield dialogPromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+
+ let closePromise = waitForNotificationClose();
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield closePromise;
+
+ installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending install since the tab is closed");
+ });
+},
+
+// Add-ons should be cancelled and the install notification destroyed when
+// navigating to a new origin
+function test_tabNavigate() {
+ return Task.spawn(function* () {
+ if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+ return;
+ }
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Extension XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let closePromise = waitForNotificationClose();
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI("about:blank");
+ yield closePromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending install");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+},
+
+function test_urlBar() {
+ return Task.spawn(function* () {
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gURLBar.value = TESTROOT + "amosigned.xpi";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ yield progressPromise;
+ let installDialog = yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog(installDialog);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ yield removeTab();
+ });
+},
+
+function test_wrongHost() {
+ return Task.spawn(function* () {
+ let requestedUrl = TESTROOT2 + "enabled.html";
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl);
+ gBrowser.loadURI(TESTROOT2 + "enabled.html");
+ yield loadedPromise;
+
+ let progressPromise = waitForProgressNotification();
+ let notificationPromise = waitForNotification("addon-install-failed");
+ gBrowser.loadURI(TESTROOT + "corrupt.xpi");
+ yield progressPromise;
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.getAttribute("label"),
+ "The add-on downloaded from this site could not be installed " +
+ "because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ yield removeTab();
+ });
+},
+
+function test_reload() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Unsigned XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ function testFail() {
+ ok(false, "Reloading should not have hidden the notification");
+ }
+ PopupNotifications.panel.addEventListener("popuphiding", testFail, false);
+ let requestedUrl = TESTROOT2 + "enabled.html";
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, requestedUrl);
+ gBrowser.loadURI(TESTROOT2 + "enabled.html");
+ yield loadedPromise;
+ PopupNotifications.panel.removeEventListener("popuphiding", testFail, false);
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending install");
+ installs[0].cancel();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_theme() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "Theme XPI": "theme.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "Theme Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ let addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(result) {
+ resolve(result);
+ });
+ });
+ ok(addon.userDisabled, "Should be switching away from the default theme.");
+ // Undo the pending theme switch
+ addon.userDisabled = false;
+
+ addon = yield new Promise(resolve => {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(result) {
+ resolve(result);
+ });
+ });
+ isnot(addon, null, "Test theme will have been installed");
+ addon.uninstall();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_renotifyBlocked() {
+ return Task.spawn(function* () {
+ let notificationPromise = waitForNotification("addon-install-blocked");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let closePromise = waitForNotificationClose();
+ // hide the panel (this simulates the user dismissing it)
+ panel.hidePopup();
+ yield closePromise;
+
+ info("Timeouts after this probably mean bug 589954 regressed");
+
+ yield new Promise(resolve => executeSoon(resolve));
+
+ notificationPromise = waitForNotification("addon-install-blocked");
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield notificationPromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 2, "Should be two pending installs");
+
+ closePromise = waitForNotificationClose();
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield closePromise;
+
+ installs = yield getInstalls();
+ is(installs.length, 0, "Should have cancelled the installs");
+ });
+},
+
+function test_renotifyInstalled() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let progressPromise = waitForProgressNotification();
+ let dialogPromise = waitForInstallDialog();
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ // Wait for the complete notification
+ let notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ let panel = yield notificationPromise;
+
+ let closePromise = waitForNotificationClose();
+ // hide the panel (this simulates the user dismissing it)
+ panel.hidePopup();
+ yield closePromise;
+
+ // Install another
+ yield new Promise(resolve => executeSoon(resolve));
+
+ progressPromise = waitForProgressNotification();
+ dialogPromise = waitForInstallDialog();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ yield progressPromise;
+ yield dialogPromise;
+
+ info("Timeouts after this probably mean bug 589954 regressed");
+
+ // Wait for the complete notification
+ notificationPromise = waitForNotification("addon-install-restart");
+ acceptInstallDialog();
+ yield notificationPromise;
+
+ let installs = yield getInstalls();
+ is(installs.length, 1, "Should be one pending installs");
+ installs[0].cancel();
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield removeTab();
+ });
+},
+
+function test_cancel() {
+ return Task.spawn(function* () {
+ let pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ let notificationPromise = waitForNotification(PROGRESS_NOTIFICATION);
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "slowinstall.sjs?file=amosigned.xpi"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, TESTROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ // Close the notification
+ let anchor = document.getElementById("addons-notification-icon");
+ anchor.click();
+ // Reopen the notification
+ anchor.click();
+
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+ let button = document.getElementById("addon-progress-cancel");
+
+ // Cancel the download
+ let install = notification.notification.options.installs[0];
+ let cancelledPromise = new Promise(resolve => {
+ install.addListener({
+ onDownloadCancelled: function() {
+ install.removeListener(this);
+ resolve();
+ }
+ });
+ });
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ yield cancelledPromise;
+
+ yield new Promise(resolve => executeSoon(resolve));
+
+ ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+
+ let installs = yield getInstalls();
+ is(installs.length, 0, "Should be no pending install");
+
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+},
+
+function test_failedSecurity() {
+ return Task.spawn(function* () {
+ Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
+ setupRedirect({
+ "Location": TESTROOT + "amosigned.xpi"
+ });
+
+ let notificationPromise = waitForNotification("addon-install-blocked");
+ let triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "redirect.sjs?mode=redirect"
+ }));
+ BrowserTestUtils.openNewForegroundTab(gBrowser, SECUREROOT + "installtrigger.html?" + triggers);
+ let panel = yield notificationPromise;
+
+ let notification = panel.childNodes[0];
+ // Click on Allow
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+
+ // Notification should have changed to progress notification
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for it to fail
+ yield new Promise(resolve => {
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, "addon-install-failed");
+ resolve();
+ }, "addon-install-failed", false);
+ });
+
+ // Allow the browser code to add the failure notification and then wait
+ // for the progress notification to dismiss itself
+ yield waitForSingleNotification();
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = panel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+
+ Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
+ yield removeTab();
+ });
+}
+];
+
+var gTestStart = null;
+
+var XPInstallObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+ info("Observed " + aTopic + " for " + installInfo.installs.length + " installs");
+ installInfo.installs.forEach(function(aInstall) {
+ info("Install of " + aInstall.sourceURI.spec + " was in state " + aInstall.state);
+ });
+ }
+};
+
+add_task(function* () {
+ requestLongerTimeout(4);
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+ Services.prefs.setBoolPref("extensions.strictCompatibility", true);
+ Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false);
+ Services.prefs.setIntPref("security.dialog_enable_delay", 0);
+
+ Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false);
+
+ registerCleanupFunction(function() {
+ // Make sure no more test parts run in case we were timed out
+ TESTS = [];
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ aInstalls.forEach(function(aInstall) {
+ aInstall.cancel();
+ });
+ });
+
+ Services.prefs.clearUserPref("extensions.logging.enabled");
+ Services.prefs.clearUserPref("extensions.strictCompatibility");
+ Services.prefs.clearUserPref("extensions.install.requireSecureOrigin");
+ Services.prefs.clearUserPref("security.dialog_enable_delay");
+
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-started");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-complete");
+ });
+
+ for (let i = 0; i < TESTS.length; ++i) {
+ if (gTestStart)
+ info("Test part took " + (Date.now() - gTestStart) + "ms");
+
+ ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+
+ let installs = yield new Promise(resolve => {
+ AddonManager.getAllInstalls(function(aInstalls) {
+ resolve(aInstalls);
+ });
+ });
+
+ is(installs.length, 0, "Should be no active installs");
+ info("Running " + TESTS[i].name);
+ gTestStart = Date.now();
+ yield TESTS[i]();
+ }
+});
diff --git a/browser/base/content/test/general/browser_bug555224.js b/browser/base/content/test/general/browser_bug555224.js
new file mode 100644
index 000000000..d27bf0040
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug555224.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+const TEST_PAGE = "/browser/browser/base/content/test/general/dummy_page.html";
+var gTestTab, gBgTab, gTestZoom;
+
+function testBackgroundLoad() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom");
+
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gBgTab);
+
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTestTab);
+ finish();
+ });
+}
+
+function testInitialZoom() {
+ Task.spawn(function* () {
+ is(ZoomManager.zoom, 1, "initial zoom level should be 1");
+ FullZoom.enlarge();
+
+ gTestZoom = ZoomManager.zoom;
+ isnot(gTestZoom, 1, "zoom level should have changed");
+
+ gBgTab = gBrowser.addTab();
+ yield FullZoomHelper.load(gBgTab, "http://mochi.test:8888" + TEST_PAGE);
+ }).then(testBackgroundLoad, FullZoomHelper.failAndContinue(finish));
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTestTab = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTestTab);
+ yield FullZoomHelper.load(gTestTab, "http://example.org" + TEST_PAGE);
+ }).then(testInitialZoom, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug555767.js b/browser/base/content/test/general/browser_bug555767.js
new file mode 100644
index 000000000..bc774f7dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug555767.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/. */
+
+ add_task(function* () {
+ let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+ let tabSelected = false;
+
+ // Open the base tab
+ let baseTab = gBrowser.addTab(testURL);
+
+ // Wait for the tab to be fully loaded so matching happens correctly
+ yield promiseTabLoaded(baseTab);
+ if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
+ return;
+ baseTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let testTab = gBrowser.addTab();
+
+ // Select the testTab
+ gBrowser.selectedTab = testTab;
+
+ // Set the urlbar to include the moz-action
+ gURLBar.value = "moz-action:switchtab," + JSON.stringify({url: testURL});
+ // Focus the urlbar so we can press enter
+ gURLBar.focus();
+
+ // Functions for TabClose and TabSelect
+ function onTabClose(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
+ // Make sure we get the TabClose event for testTab
+ is(aEvent.originalTarget, testTab, "Got the TabClose event for the right tab");
+ // Confirm that we did select the tab
+ ok(tabSelected, "Confirming that the tab was selected");
+ gBrowser.removeTab(baseTab);
+ finish();
+ }
+ function onTabSelect(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
+ // Make sure we got the TabSelect event for baseTab
+ is(aEvent.originalTarget, baseTab, "Got the TabSelect event for the right tab");
+ // Confirm that the selected tab is in fact base tab
+ is(gBrowser.selectedTab, baseTab, "We've switched to the correct tab");
+ tabSelected = true;
+ }
+
+ // Add the TabClose, TabSelect event listeners before we press enter
+ gBrowser.tabContainer.addEventListener("TabClose", onTabClose, false);
+ gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false);
+
+ // Press enter!
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ });
+
diff --git a/browser/base/content/test/general/browser_bug559991.js b/browser/base/content/test/general/browser_bug559991.js
new file mode 100644
index 000000000..b1516a8b4
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug559991.js
@@ -0,0 +1,42 @@
+var tab;
+
+function test() {
+
+ // ----------
+ // Test setup
+
+ waitForExplicitFinish();
+
+ gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", true);
+ gPrefService.setBoolPref("browser.zoom.siteSpecific", true);
+
+ let uri = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+ Task.spawn(function* () {
+ tab = gBrowser.addTab();
+ yield FullZoomHelper.load(tab, uri);
+
+ // -------------------------------------------------------------------
+ // Test - Trigger a tab switch that should update the zoom level
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
+ ok(true, "applyPrefToSetting was called");
+ }).then(endTest, FullZoomHelper.failAndContinue(endTest));
+}
+
+// -------------
+// Test clean-up
+function endTest() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab);
+
+ tab = null;
+
+ if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
+ gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
+
+ if (gPrefService.prefHasUserValue("browser.zoom.siteSpecific"))
+ gPrefService.clearUserPref("browser.zoom.siteSpecific");
+
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug561636.js b/browser/base/content/test/general/browser_bug561636.js
new file mode 100644
index 000000000..69bc475c3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug561636.js
@@ -0,0 +1,370 @@
+var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+ok(gInvalidFormPopup,
+ "The browser should have a popup to show when a form is invalid");
+
+function checkPopupShow()
+{
+ ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open',
+ "[Test " + testId + "] The invalid form popup should be shown");
+}
+
+function checkPopupHide()
+{
+ ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open',
+ "[Test " + testId + "] The invalid form popup should not be shown");
+}
+
+var gObserver = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
+
+ notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+ {
+ }
+};
+
+var testId = 0;
+
+function incrementTest()
+{
+ testId++;
+ info("Starting next part of test");
+}
+
+function getDocHeader()
+{
+ return "<html><head><meta charset='utf-8'></head><body>" + getEmptyFrame();
+}
+
+function getDocFooter()
+{
+ return "</body></html>";
+}
+
+function getEmptyFrame()
+{
+ return "<iframe style='width:100px; height:30px; margin:3px; border:1px solid lightgray;' " +
+ "name='t' srcdoc=\"<html><head><meta charset='utf-8'></head><body>form target</body></html>\"></iframe>";
+}
+
+function* openNewTab(uri, background)
+{
+ let tab = gBrowser.addTab();
+ let browser = gBrowser.getBrowserForTab(tab);
+ if (!background) {
+ gBrowser.selectedTab = tab;
+ }
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(uri));
+ return browser;
+}
+
+function* clickChildElement(browser)
+{
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.document.getElementById('s').click();
+ });
+}
+
+function* blurChildElement(browser)
+{
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.document.getElementById('i').blur();
+ });
+}
+
+function* checkChildFocus(browser, message)
+{
+ yield ContentTask.spawn(browser, [message, testId], function* (args) {
+ let [msg, id] = args;
+ var focused = content.document.activeElement == content.document.getElementById('i');
+
+ var validMsg = true;
+ if (msg) {
+ validMsg = (msg == content.document.getElementById('i').validationMessage);
+ }
+
+ Assert.equal(focused, true, "Test " + id + " First invalid element should be focused");
+ Assert.equal(validMsg, true, "Test " + id + " The panel should show the message from validationMessage");
+ });
+}
+
+/**
+ * In this test, we check that no popup appears if the form is valid.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ yield clickChildElement(browser);
+
+ yield new Promise((resolve, reject) => {
+ // XXXndeakin This isn't really going to work when the content is another process
+ executeSoon(function() {
+ checkPopupHide();
+ resolve();
+ });
+ });
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the invalid element is focused and a popup appears.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the first invalid element is focused and a popup appears.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input><input id='i' required><input required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, we hide the popup by interacting with the
+ * invalid element if the element becomes valid.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+ EventUtils.synthesizeKey("a", {});
+ yield popupHiddenPromise;
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that, we don't hide the popup by interacting with the
+ * invalid element if the element is still invalid.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input type='email' id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ yield new Promise((resolve, reject) => {
+ EventUtils.synthesizeKey("a", {});
+ executeSoon(function() {
+ checkPopupShow();
+ resolve();
+ })
+ });
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that we can hide the popup by blurring the invalid
+ * element.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+ yield blurChildElement(browser);
+ yield popupHiddenPromise;
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that we can hide the popup by pressing TAB.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ yield popupHiddenPromise;
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that the popup will hide if we move to another tab.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser1 = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser1);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser1, gInvalidFormPopup.firstChild.textContent);
+
+ let popupHiddenPromise = promiseWaitForEvent(gInvalidFormPopup, "popuphidden");
+
+ let browser2 = yield openNewTab("data:text/html,<html></html>");
+ yield popupHiddenPromise;
+
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser1));
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser2));
+});
+
+/**
+ * In this test, we check that nothing happen if the invalid form is
+ * submitted in a background tab.
+ */
+add_task(function* ()
+{
+ // Observers don't propagate currently across processes. We may add support for this in the
+ // future via the addon compat layer.
+ if (gMultiProcessBrowser) {
+ return;
+ }
+
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri, true);
+ isnot(gBrowser.selectedBrowser, browser, "This tab should have been loaded in background");
+
+ let notifierPromise = new Promise((resolve, reject) => {
+ gObserver.notifyInvalidSubmit = function() {
+ executeSoon(function() {
+ checkPopupHide();
+
+ // Clean-up
+ Services.obs.removeObserver(gObserver, "invalidformsubmit");
+ gObserver.notifyInvalidSubmit = function () {};
+ resolve();
+ });
+ };
+
+ Services.obs.addObserver(gObserver, "invalidformsubmit", false);
+
+ executeSoon(function () {
+ browser.contentDocument.getElementById('s').click();
+ });
+ });
+
+ yield notifierPromise;
+
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser));
+});
+
+/**
+ * In this test, we check that the author defined error message is shown.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input x-moz-errormessage='foo' required id='i'><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ is(gInvalidFormPopup.firstChild.textContent, "foo",
+ "The panel should show the author defined error message");
+
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * In this test, we check that the message is correctly updated when it changes.
+ */
+add_task(function* ()
+{
+ incrementTest();
+ let uri = getDocHeader() + "<form target='t' action='data:text/html,'><input type='email' required id='i'><input id='s' type='submit'></form>" + getDocFooter();
+ let browser = yield openNewTab(uri);
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+ yield clickChildElement(browser);
+ yield popupShownPromise;
+
+ checkPopupShow();
+ yield checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+
+ let inputPromise = promiseWaitForEvent(gBrowser.contentDocument.getElementById('i'), "input");
+ EventUtils.synthesizeKey('f', {});
+ yield inputPromise;
+
+ // Now, the element suffers from another error, the message should have
+ // been updated.
+ yield new Promise((resolve, reject) => {
+ // XXXndeakin This isn't really going to work when the content is another process
+ executeSoon(function() {
+ checkChildFocus(browser, gInvalidFormPopup.firstChild.textContent);
+ resolve();
+ });
+ });
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug563588.js b/browser/base/content/test/general/browser_bug563588.js
new file mode 100644
index 000000000..a1774fb7e
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug563588.js
@@ -0,0 +1,30 @@
+function press(key, expectedPos) {
+ var originalSelectedTab = gBrowser.selectedTab;
+ EventUtils.synthesizeKey("VK_" + key.toUpperCase(), { accelKey: true });
+ is(gBrowser.selectedTab, originalSelectedTab,
+ "accel+" + key + " doesn't change which tab is selected");
+ is(gBrowser.tabContainer.selectedIndex, expectedPos,
+ "accel+" + key + " moves the tab to the expected position");
+ is(document.activeElement, gBrowser.selectedTab,
+ "accel+" + key + " leaves the selected tab focused");
+}
+
+function test() {
+ gBrowser.addTab();
+ gBrowser.addTab();
+ is(gBrowser.tabs.length, 3, "got three tabs");
+ is(gBrowser.tabs[0], gBrowser.selectedTab, "first tab is selected");
+
+ gBrowser.selectedTab.focus();
+ is(document.activeElement, gBrowser.selectedTab, "selected tab is focused");
+
+ press("right", 1);
+ press("down", 2);
+ press("left", 1);
+ press("up", 0);
+ press("end", 2);
+ press("home", 0);
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/general/browser_bug565575.js b/browser/base/content/test/general/browser_bug565575.js
new file mode 100644
index 000000000..3555a2e7f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug565575.js
@@ -0,0 +1,14 @@
+add_task(function* () {
+ gBrowser.selectedBrowser.focus();
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => BrowserOpenTab(), false);
+ ok(gURLBar.focused, "location bar is focused for a new tab");
+
+ yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]);
+ ok(!gURLBar.focused, "location bar isn't focused for the previously selected tab");
+
+ yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]);
+ ok(gURLBar.focused, "location bar is re-focused when selecting the new tab");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug567306.js b/browser/base/content/test/general/browser_bug567306.js
new file mode 100644
index 000000000..742ff6726
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug567306.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var {Ci: interfaces, Cc: classes} = Components;
+
+var Clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+var HasFindClipboard = Clipboard.supportsFindClipboard();
+
+add_task(function* () {
+ let newwindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let selectedBrowser = newwindow.gBrowser.selectedBrowser;
+ yield new Promise((resolve, reject) => {
+ selectedBrowser.addEventListener("pageshow", function pageshowListener() {
+ if (selectedBrowser.currentURI.spec == "about:blank")
+ return;
+
+ selectedBrowser.removeEventListener("pageshow", pageshowListener, true);
+ ok(true, "pageshow listener called: " + newwindow.content.location);
+ resolve();
+ }, true);
+ selectedBrowser.loadURI("data:text/html,<h1 id='h1'>Select Me</h1>");
+ });
+
+ yield SimpleTest.promiseFocus(newwindow);
+
+ ok(!newwindow.gFindBarInitialized, "find bar is not yet initialized");
+ let findBar = newwindow.gFindBar;
+
+ yield ContentTask.spawn(selectedBrowser, { }, function* () {
+ let elt = content.document.getElementById("h1");
+ let selection = content.getSelection();
+ let range = content.document.createRange();
+ range.setStart(elt, 0);
+ range.setEnd(elt, 1);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ });
+
+ yield findBar.onFindCommand();
+
+ // When the OS supports the Find Clipboard (OSX), the find field value is
+ // persisted across Fx sessions, thus not useful to test.
+ if (!HasFindClipboard)
+ is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
+ findBar.close();
+ yield promiseWindowClosed(newwindow);
+});
+
diff --git a/browser/base/content/test/general/browser_bug575561.js b/browser/base/content/test/general/browser_bug575561.js
new file mode 100644
index 000000000..b6d17a447
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug575561.js
@@ -0,0 +1,97 @@
+requestLongerTimeout(2);
+
+const TEST_URL = "http://example.com/browser/browser/base/content/test/general/app_bug575561.html";
+
+add_task(function*() {
+ SimpleTest.requestCompleteLog();
+
+ // Pinned: Link to the same domain should not open a new tab
+ // Tests link to http://example.com/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(0, true, false);
+ // Pinned: Link to a different subdomain should open a new tab
+ // Tests link to http://test1.example.com/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(1, true, true);
+
+ // Pinned: Link to a different domain should open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(2, true, true);
+
+ // Not Pinned: Link to a different domain should not open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(2, false, false);
+
+ // Pinned: Targetted link should open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html with target="foo"
+ yield testLink(3, true, true);
+
+ // Pinned: Link in a subframe should not open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/general/dummy_page.html in subframe
+ yield testLink(0, true, false, true);
+
+ // Pinned: Link to the same domain (with www prefix) should not open a new tab
+ // Tests link to http://www.example.com/browser/browser/base/content/test/general/dummy_page.html
+ yield testLink(4, true, false);
+
+ // Pinned: Link to a data: URI should not open a new tab
+ // Tests link to data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>
+ yield testLink(5, true, false);
+
+ // Pinned: Link to an about: URI should not open a new tab
+ // Tests link to about:logo
+ yield testLink(function(doc) {
+ let link = doc.createElement("a");
+ link.textContent = "Link to Mozilla";
+ link.href = "about:logo";
+ doc.body.appendChild(link);
+ return link;
+ }, true, false, false, "about:robots");
+});
+
+var waitForPageLoad = Task.async(function*(browser, linkLocation) {
+ yield waitForDocLoadComplete();
+
+ is(browser.contentDocument.location.href, linkLocation, "Link should not open in a new tab");
+});
+
+var waitForTabOpen = Task.async(function*() {
+ let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
+ ok(true, "Link should open a new tab");
+
+ yield waitForDocLoadComplete(event.target.linkedBrowser);
+ yield Promise.resolve();
+
+ gBrowser.removeCurrentTab();
+});
+
+var testLink = Task.async(function*(aLinkIndexOrFunction, pinTab, expectNewTab, testSubFrame, aURL = TEST_URL) {
+ let appTab = gBrowser.addTab(aURL, {skipAnimation: true});
+ if (pinTab)
+ gBrowser.pinTab(appTab);
+ gBrowser.selectedTab = appTab;
+
+ yield waitForDocLoadComplete();
+
+ let browser = appTab.linkedBrowser;
+ if (testSubFrame)
+ browser = browser.contentDocument.querySelector("iframe");
+
+ let link;
+ if (typeof aLinkIndexOrFunction == "function") {
+ link = aLinkIndexOrFunction(browser.contentDocument);
+ } else {
+ link = browser.contentDocument.querySelectorAll("a")[aLinkIndexOrFunction];
+ }
+
+ let promise;
+ if (expectNewTab)
+ promise = waitForTabOpen();
+ else
+ promise = waitForPageLoad(browser, link.href);
+
+ info("Clicking " + link.textContent);
+ link.click();
+
+ yield promise;
+
+ gBrowser.removeTab(appTab);
+});
diff --git a/browser/base/content/test/general/browser_bug575830.js b/browser/base/content/test/general/browser_bug575830.js
new file mode 100644
index 000000000..5393c08d7
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug575830.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+function test() {
+ let tab1, tab2;
+ const TEST_IMAGE = "http://example.org/browser/browser/base/content/test/general/moz.png";
+
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ tab1 = gBrowser.addTab();
+ tab2 = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.load(tab1, TEST_IMAGE);
+
+ is(ZoomManager.zoom, 1, "initial zoom level for first should be 1");
+
+ FullZoom.enlarge();
+ let zoom = ZoomManager.zoom;
+ isnot(zoom, 1, "zoom level should have changed");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
+ is(ZoomManager.zoom, 1, "initial zoom level for second tab should be 1");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ is(ZoomManager.zoom, zoom, "zoom level for first tab should not have changed");
+
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug577121.js b/browser/base/content/test/general/browser_bug577121.js
new file mode 100644
index 000000000..5ebfdc115
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug577121.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ Services.prefs.setBoolPref("browser.tabs.animate", false);
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.tabs.animate");
+ });
+
+ // Open 2 other tabs, and pin the second one. Like that, the initial tab
+ // should get closed.
+ let testTab1 = gBrowser.addTab();
+ let testTab2 = gBrowser.addTab();
+ gBrowser.pinTab(testTab2);
+
+ // Now execute "Close other Tabs" on the first manually opened tab (tab1).
+ // -> tab2 ist pinned, tab1 should remain open and the initial tab should
+ // get closed.
+ gBrowser.removeAllTabsBut(testTab1);
+
+ is(gBrowser.tabs.length, 2, "there are two remaining tabs open");
+ is(gBrowser.tabs[0], testTab2, "pinned tab2 stayed open");
+ is(gBrowser.tabs[1], testTab1, "tab1 stayed open");
+
+ // Cleanup. Close only one tab because we need an opened tab at the end of
+ // the test.
+ gBrowser.removeTab(testTab2);
+}
diff --git a/browser/base/content/test/general/browser_bug578534.js b/browser/base/content/test/general/browser_bug578534.js
new file mode 100644
index 000000000..0d61cca76
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug578534.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/. */
+
+add_task(function* test() {
+ let uriString = "http://example.com/";
+ let cookieBehavior = "network.cookie.cookieBehavior";
+ let uriObj = Services.io.newURI(uriString, null, null)
+ let cp = Components.classes["@mozilla.org/cookie/permission;1"]
+ .getService(Components.interfaces.nsICookiePermission);
+
+ yield SpecialPowers.pushPrefEnv({ set: [[ cookieBehavior, 2 ]] });
+ cp.setAccess(uriObj, cp.ACCESS_ALLOW);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: uriString }, function* (browser) {
+ yield ContentTask.spawn(browser, null, function() {
+ is(content.navigator.cookieEnabled, true,
+ "navigator.cookieEnabled should be true");
+ });
+ });
+
+ cp.setAccess(uriObj, cp.ACCESS_DEFAULT);
+});
diff --git a/browser/base/content/test/general/browser_bug579872.js b/browser/base/content/test/general/browser_bug579872.js
new file mode 100644
index 000000000..bc10ca0c8
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug579872.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/. */
+
+function test() {
+ let newTab = gBrowser.addTab();
+ waitForExplicitFinish();
+ BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart);
+
+ function mainPart() {
+ gBrowser.pinTab(newTab);
+ gBrowser.selectedTab = newTab;
+
+ openUILinkIn("javascript:var x=0;", "current");
+ is(gBrowser.tabs.length, 2, "Should open in current tab");
+
+ openUILinkIn("http://example.com/1", "current");
+ is(gBrowser.tabs.length, 2, "Should open in current tab");
+
+ openUILinkIn("http://example.org/", "current");
+ is(gBrowser.tabs.length, 3, "Should open in new tab");
+
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
+ finish();
+ }
+ newTab.linkedBrowser.loadURI("http://example.com");
+}
diff --git a/browser/base/content/test/general/browser_bug580638.js b/browser/base/content/test/general/browser_bug580638.js
new file mode 100644
index 000000000..66defafe3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug580638.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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ function testState(aPinned) {
+ function elemAttr(id, attr) {
+ return document.getElementById(id).getAttribute(attr);
+ }
+
+ if (aPinned) {
+ is(elemAttr("key_close", "disabled"), "true",
+ "key_close should be disabled when a pinned-tab is selected");
+ is(elemAttr("menu_close", "key"), "",
+ "menu_close shouldn't have a key set when a pinned is selected");
+ }
+ else {
+ is(elemAttr("key_close", "disabled"), "",
+ "key_closed shouldn't have disabled state set when a non-pinned tab is selected");
+ is(elemAttr("menu_close", "key"), "key_close",
+ "menu_close should have key_close set as its key when a non-pinned tab is selected");
+ }
+ }
+
+ let lastSelectedTab = gBrowser.selectedTab;
+ ok(!lastSelectedTab.pinned, "We should have started with a regular tab selected");
+
+ testState(false);
+
+ let pinnedTab = gBrowser.addTab("about:blank");
+ gBrowser.pinTab(pinnedTab);
+
+ // Just pinning the tab shouldn't change the key state.
+ testState(false);
+
+ // Test updating key state after selecting a tab.
+ gBrowser.selectedTab = pinnedTab;
+ testState(true);
+
+ gBrowser.selectedTab = lastSelectedTab;
+ testState(false);
+
+ gBrowser.selectedTab = pinnedTab;
+ testState(true);
+
+ // Test updating the key state after un/pinning the tab.
+ gBrowser.unpinTab(pinnedTab);
+ testState(false);
+
+ gBrowser.pinTab(pinnedTab);
+ testState(true);
+
+ // Test updating the key state after removing the tab.
+ gBrowser.removeTab(pinnedTab);
+ testState(false);
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug580956.js b/browser/base/content/test/general/browser_bug580956.js
new file mode 100644
index 000000000..b8e7bc20b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug580956.js
@@ -0,0 +1,26 @@
+function numClosedTabs() {
+ return SessionStore.getClosedTabCount(window);
+}
+
+function isUndoCloseEnabled() {
+ updateTabContextMenu();
+ return !document.getElementById("context_undoCloseTab").disabled;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", 0);
+ gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
+ is(numClosedTabs(), 0, "There should be 0 closed tabs.");
+ ok(!isUndoCloseEnabled(), "Undo Close Tab should be disabled.");
+
+ var tab = gBrowser.addTab("http://mochi.test:8888/");
+ var browser = gBrowser.getBrowserForTab(tab);
+ BrowserTestUtils.browserLoaded(browser).then(() => {
+ BrowserTestUtils.removeTab(tab).then(() => {
+ ok(isUndoCloseEnabled(), "Undo Close Tab should be enabled.");
+ finish();
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug581242.js b/browser/base/content/test/general/browser_bug581242.js
new file mode 100644
index 000000000..668c0cd41
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug581242.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // Create a new tab and load about:addons
+ let blanktab = gBrowser.addTab();
+ gBrowser.selectedTab = blanktab;
+ BrowserOpenAddonsMgr();
+
+ is(blanktab, gBrowser.selectedTab, "Current tab should be blank tab");
+ // Verify that about:addons loads
+ waitForExplicitFinish();
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ let browser = blanktab.linkedBrowser;
+ is(browser.currentURI.spec, "about:addons", "about:addons should load into blank tab.");
+ gBrowser.removeTab(blanktab);
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug581253.js b/browser/base/content/test/general/browser_bug581253.js
new file mode 100644
index 000000000..0c537c3d3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug581253.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testURL = "data:text/plain,nothing but plain text";
+var testTag = "581253_tag";
+var timerID = -1;
+
+function test() {
+ registerCleanupFunction(function() {
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ if (timerID > 0) {
+ clearTimeout(timerID);
+ }
+ });
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ tab.linkedBrowser.addEventListener("load", (function(event) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let uri = makeURI(testURL);
+ let bmTxn =
+ new PlacesCreateBookmarkTransaction(uri,
+ PlacesUtils.unfiledBookmarksFolderId,
+ -1, "", null, []);
+ PlacesUtils.transactionManager.doTransaction(bmTxn);
+
+ ok(PlacesUtils.bookmarks.isBookmarked(uri), "the test url is bookmarked");
+ waitForStarChange(true, onStarred);
+ }), true);
+
+ content.location = testURL;
+}
+
+function waitForStarChange(aValue, aCallback) {
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(waitForStarChange, 50, aValue, aCallback);
+ return;
+ }
+ aCallback();
+}
+
+function onStarred() {
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED,
+ "star button indicates that the page is bookmarked");
+
+ let uri = makeURI(testURL);
+ let tagTxn = new PlacesTagURITransaction(uri, [testTag]);
+ PlacesUtils.transactionManager.doTransaction(tagTxn);
+
+ StarUI.panel.addEventListener("popupshown", onPanelShown, false);
+ BookmarkingUI.star.click();
+}
+
+function onPanelShown(aEvent) {
+ if (aEvent.target == StarUI.panel) {
+ StarUI.panel.removeEventListener("popupshown", arguments.callee, false);
+ let tagsField = document.getElementById("editBMPanel_tagsField");
+ ok(tagsField.value == testTag, "tags field value was set");
+ tagsField.focus();
+
+ StarUI.panel.addEventListener("popuphidden", onPanelHidden, false);
+ let removeButton = document.getElementById("editBookmarkPanelRemoveButton");
+ removeButton.click();
+ }
+}
+
+function onPanelHidden(aEvent) {
+ if (aEvent.target == StarUI.panel) {
+ StarUI.panel.removeEventListener("popuphidden", arguments.callee, false);
+
+ executeSoon(function() {
+ ok(!PlacesUtils.bookmarks.isBookmarked(makeURI(testURL)),
+ "the bookmark for the test url has been removed");
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_UNSTARRED,
+ "star button indicates that the bookmark has been removed");
+ gBrowser.removeCurrentTab();
+ PlacesTestUtils.clearHistory().then(finish);
+ });
+ }
+}
diff --git a/browser/base/content/test/general/browser_bug585558.js b/browser/base/content/test/general/browser_bug585558.js
new file mode 100644
index 000000000..bae832b4d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug585558.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var tabs = [];
+
+function addTab(aURL) {
+ tabs.push(gBrowser.addTab(aURL, {skipAnimation: true}));
+}
+
+function testAttrib(elem, attrib, attribValue, msg) {
+ is(elem.hasAttribute(attrib), attribValue, msg);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add several new tabs in sequence, hiding some, to ensure that the
+ // correct attributes get set
+
+ addTab("http://mochi.test:8888/#0");
+ addTab("http://mochi.test:8888/#1");
+ addTab("http://mochi.test:8888/#2");
+ addTab("http://mochi.test:8888/#3");
+
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[4], "last-visible-tab", true,
+ "Fifth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
+ testAttrib(gBrowser.tabs[0], "afterselected-visible", false,
+ "First tab not marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ gBrowser.hideTab(gBrowser.tabs[1]);
+ executeSoon(test_hideSecond);
+}
+
+function test_hideSecond() {
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ gBrowser.showTab(gBrowser.tabs[1])
+ executeSoon(test_showSecond);
+}
+
+function test_showSecond() {
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", false,
+ "Third tab not marked as afterselected-visible!");
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ gBrowser.hideTab(gBrowser.tabs[0]);
+ executeSoon(test_hideFirst);
+}
+
+function test_hideFirst() {
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", false,
+ "Hidden first tab not marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[1], "first-visible-tab", true,
+ "Second tab marked first-visible-tab!");
+ gBrowser.showTab(gBrowser.tabs[0]);
+ executeSoon(test_showFirst);
+}
+
+function test_showFirst() {
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ gBrowser.selectedTab = gBrowser.tabs[2];
+ testAttrib(gBrowser.tabs[3], "afterselected-visible", true,
+ "Fourth tab marked afterselected-visible!");
+
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+ executeSoon(test_movedLower);
+}
+
+function test_movedLower() {
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ test_hoverOne();
+}
+
+function test_hoverOne() {
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[4], { type: "mousemove" });
+ testAttrib(gBrowser.tabs[3], "beforehovered", true, "Fourth tab marked beforehovered");
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[3], { type: "mousemove" });
+ testAttrib(gBrowser.tabs[2], "beforehovered", true, "Third tab marked beforehovered!");
+ testAttrib(gBrowser.tabs[2], "afterhovered", false, "Third tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[4], "afterhovered", true, "Fifth tab marked afterhovered!");
+ testAttrib(gBrowser.tabs[4], "beforehovered", false, "Fifth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "beforehovered", false, "First tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+ gBrowser.removeTab(tabs.pop());
+ executeSoon(test_hoverStatePersistence);
+}
+
+function test_hoverStatePersistence() {
+ // Test that the afterhovered and beforehovered attributes are still there when
+ // a tab is selected and then unselected again. See bug 856107.
+
+ function assertState() {
+ testAttrib(gBrowser.tabs[0], "beforehovered", true, "First tab still marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[2], "afterhovered", true, "Third tab still marked afterhovered!");
+ testAttrib(gBrowser.tabs[2], "beforehovered", false, "Third tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+ }
+
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[1], { type: "mousemove" });
+ assertState();
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ assertState();
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ assertState();
+ executeSoon(test_pinning);
+}
+
+function test_pinning() {
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
+ "Fourth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[3], "selected", true, "Fourth tab marked selected!");
+ testAttrib(gBrowser.tabs[3], "afterselected-visible", false,
+ "Fourth tab not marked afterselected-visible!");
+ // Causes gBrowser.tabs to change indices
+ gBrowser.pinTab(gBrowser.tabs[3]);
+ testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
+ "Fourth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ test_cleanUp();
+}
+
+function test_cleanUp() {
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug585785.js b/browser/base/content/test/general/browser_bug585785.js
new file mode 100644
index 000000000..4f9045231
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug585785.js
@@ -0,0 +1,35 @@
+var tab;
+
+function test() {
+ waitForExplicitFinish();
+
+ tab = gBrowser.addTab();
+ isnot(tab.getAttribute("fadein"), "true", "newly opened tab is yet to fade in");
+
+ // Try to remove the tab right before the opening animation's first frame
+ window.requestAnimationFrame(checkAnimationState);
+}
+
+function checkAnimationState() {
+ is(tab.getAttribute("fadein"), "true", "tab opening animation initiated");
+
+ info(window.getComputedStyle(tab).maxWidth);
+ gBrowser.removeTab(tab, { animate: true });
+ if (!tab.parentNode) {
+ ok(true, "tab removed synchronously since the opening animation hasn't moved yet");
+ finish();
+ return;
+ }
+
+ info("tab didn't close immediately, so the tab opening animation must have started moving");
+ info("waiting for the tab to close asynchronously");
+ tab.addEventListener("transitionend", function (event) {
+ if (event.propertyName == "max-width") {
+ tab.removeEventListener("transitionend", arguments.callee, false);
+ executeSoon(function () {
+ ok(!tab.parentNode, "tab removed asynchronously");
+ finish();
+ });
+ }
+ }, false);
+}
diff --git a/browser/base/content/test/general/browser_bug585830.js b/browser/base/content/test/general/browser_bug585830.js
new file mode 100644
index 000000000..6d3adf198
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug585830.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let tab1 = gBrowser.selectedTab;
+ let tab2 = gBrowser.addTab("about:blank", {skipAnimation: true});
+ gBrowser.addTab();
+ gBrowser.selectedTab = tab2;
+
+ gBrowser.removeCurrentTab({animate: true});
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, tab1, "First tab should be selected");
+ gBrowser.removeTab(tab2);
+
+ // test for "null has no properties" fix. See Bug 585830 Comment 13
+ gBrowser.removeCurrentTab({animate: true});
+ try {
+ gBrowser.tabContainer.advanceSelectedTab(-1, false);
+ } catch (err) {
+ ok(false, "Shouldn't throw");
+ }
+
+ gBrowser.removeTab(tab1);
+}
diff --git a/browser/base/content/test/general/browser_bug590206.js b/browser/base/content/test/general/browser_bug590206.js
new file mode 100644
index 000000000..f73d144e9
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug590206.js
@@ -0,0 +1,163 @@
+/*
+ * Test the identity mode UI for a variety of page types
+ */
+
+"use strict";
+
+const DUMMY = "browser/browser/base/content/test/general/dummy_page.html";
+
+function loadNewTab(url) {
+ return BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+}
+
+function getIdentityMode() {
+ return document.getElementById("identity-box").className;
+}
+
+function getConnectionState() {
+ gIdentityHandler.refreshIdentityPopup();
+ return document.getElementById("identity-popup").getAttribute("connection");
+}
+
+// This test is slow on Linux debug e10s
+requestLongerTimeout(2);
+
+add_task(function* test_webpage() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("http://example.com/" + DUMMY);
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_blank() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("about:blank");
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_chrome() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("chrome://mozapps/content/extensions/extensions.xul");
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_https() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("https://example.com/" + DUMMY);
+ is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_addons() {
+ let oldTab = gBrowser.selectedTab;
+
+ let newTab = yield loadNewTab("about:addons");
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_file() {
+ let oldTab = gBrowser.selectedTab;
+ let fileURI = getTestFilePath("");
+
+ let newTab = yield loadNewTab(fileURI);
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_resource_uri() {
+ let oldTab = gBrowser.selectedTab;
+ let dataURI = "resource://gre/modules/Services.jsm";
+
+ let newTab = yield loadNewTab(dataURI);
+
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_data_uri() {
+ let oldTab = gBrowser.selectedTab;
+ let dataURI = "data:text/html,hi"
+
+ let newTab = yield loadNewTab(dataURI);
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* test_about_uri() {
+ let oldTab = gBrowser.selectedTab;
+ let aboutURI = "about:robots"
+
+ let newTab = yield loadNewTab(aboutURI);
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = newTab;
+ is(getConnectionState(), "file", "Connection should be file");
+
+ gBrowser.removeTab(newTab);
+});
diff --git a/browser/base/content/test/general/browser_bug592338.js b/browser/base/content/test/general/browser_bug592338.js
new file mode 100644
index 000000000..ca9cc361a
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug592338.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+
+var tempScope = {};
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
+var LightweightThemeManager = tempScope.LightweightThemeManager;
+
+function wait_for_notification(aCallback) {
+ PopupNotifications.panel.addEventListener("popupshown", function() {
+ PopupNotifications.panel.removeEventListener("popupshown", arguments.callee, false);
+ aCallback(PopupNotifications.panel);
+ }, false);
+}
+
+var TESTS = [
+function test_install_http() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.org/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("http://example.org/browser/browser/base/content/test/general/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ executeSoon(function() {
+ BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser);
+
+ is(LightweightThemeManager.currentTheme, null, "Should not have installed the test theme");
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ pm.remove(makeURI("http://example.org/"), "install");
+
+ runNextTest();
+ });
+ }, false);
+},
+
+function test_install_lwtheme() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ var pm = Services.perms;
+ pm.add(makeURI("https://example.com/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("https://example.com/browser/browser/base/content/test/general/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser);
+ let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
+ waitForCondition(
+ () => notificationBox.getNotificationWithValue("lwtheme-install-notification"),
+ () => {
+ is(LightweightThemeManager.currentTheme.id, "test", "Should have installed the test theme");
+
+ LightweightThemeManager.currentTheme = null;
+ gBrowser.removeTab(gBrowser.selectedTab);
+ Services.perms.remove(makeURI("http://example.com/"), "install");
+
+ runNextTest();
+ }
+ );
+ }, false);
+},
+
+function test_lwtheme_switch_theme() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.userDisabled = false;
+ ok(aAddon.isActive, "Theme should have immediately enabled");
+ Services.prefs.setBoolPref("extensions.dss.enabled", false);
+
+ var pm = Services.perms;
+ pm.add(makeURI("https://example.com/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("https://example.com/browser/browser/base/content/test/general/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ executeSoon(function() {
+ wait_for_notification(function(aPanel) {
+ is(LightweightThemeManager.currentTheme, null, "Should not have installed the test lwtheme");
+ ok(aAddon.isActive, "Test theme should still be active");
+
+ let notification = aPanel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+
+ ok(aAddon.userDisabled, "Should be waiting to disable the test theme");
+ aAddon.userDisabled = false;
+ Services.prefs.setBoolPref("extensions.dss.enabled", true);
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ Services.perms.remove(makeURI("http://example.com"), "install");
+
+ runNextTest();
+ });
+ BrowserTestUtils.synthesizeMouse("#theme-install", 2, 2, {}, gBrowser.selectedBrowser);
+ });
+ }, false);
+ });
+}
+];
+
+function runNextTest() {
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 0, "Should be no active installs");
+
+ if (TESTS.length == 0) {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.uninstall();
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", false);
+ Services.prefs.setBoolPref("extensions.dss.enabled", false);
+
+ finish();
+ });
+ return;
+ }
+
+ info("Running " + TESTS[0].name);
+ TESTS.shift()();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+
+ AddonManager.getInstallForURL(TESTROOT + "theme.xpi", function(aInstall) {
+ aInstall.addListener({
+ onInstallEnded: function() {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ isnot(aAddon, null, "Should have installed the test theme.");
+
+ // In order to switch themes while the test is running we turn on dynamic
+ // theme switching. This means the test isn't exactly correct but should
+ // do some good
+ Services.prefs.setBoolPref("extensions.dss.enabled", true);
+
+ runNextTest();
+ });
+ }
+ });
+
+ aInstall.install();
+ }, "application/x-xpinstall");
+}
diff --git a/browser/base/content/test/general/browser_bug594131.js b/browser/base/content/test/general/browser_bug594131.js
new file mode 100644
index 000000000..ce09026ac
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug594131.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let newTab = gBrowser.addTab("http://example.com");
+ waitForExplicitFinish();
+ BrowserTestUtils.browserLoaded(newTab.linkedBrowser).then(mainPart);
+
+ function mainPart() {
+ gBrowser.pinTab(newTab);
+ gBrowser.selectedTab = newTab;
+
+ openUILinkIn("http://example.org/", "current", { inBackground: true });
+ isnot(gBrowser.selectedTab, newTab, "shouldn't load in background");
+
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
+ finish();
+ }
+}
diff --git a/browser/base/content/test/general/browser_bug595507.js b/browser/base/content/test/general/browser_bug595507.js
new file mode 100644
index 000000000..54ae42346
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug595507.js
@@ -0,0 +1,36 @@
+/**
+ * Make sure that the form validation error message shows even if the form is in an iframe.
+ */
+add_task(function* () {
+ let uri = "<iframe src=\"data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>\"</iframe>";
+
+ var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+ ok(gInvalidFormPopup,
+ "The browser should have a popup to show when a form is invalid");
+
+ let tab = gBrowser.addTab();
+ let browser = gBrowser.getBrowserForTab(tab);
+ gBrowser.selectedTab = tab;
+
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(uri));
+
+ let popupShownPromise = promiseWaitForEvent(gInvalidFormPopup, "popupshown");
+
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.document.getElementsByTagName('iframe')[0]
+ .contentDocument.getElementById('s').click();
+ });
+ yield popupShownPromise;
+
+ yield ContentTask.spawn(browser, {}, function* () {
+ let childdoc = content.document.getElementsByTagName('iframe')[0].contentDocument;
+ Assert.equal(childdoc.activeElement, childdoc.getElementById("i"),
+ "First invalid element should be focused");
+ });
+
+ ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open',
+ "The invalid form popup should be shown");
+
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_bug596687.js b/browser/base/content/test/general/browser_bug596687.js
new file mode 100644
index 000000000..5c2b4fbfe
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug596687.js
@@ -0,0 +1,25 @@
+add_task(function* test() {
+ var tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ var gotTabAttrModified = false;
+ var gotTabClose = false;
+
+ function onTabClose() {
+ gotTabClose = true;
+ tab.addEventListener("TabAttrModified", onTabAttrModified, false);
+ }
+
+ function onTabAttrModified() {
+ gotTabAttrModified = true;
+ }
+
+ tab.addEventListener("TabClose", onTabClose, false);
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ ok(gotTabClose, "should have got the TabClose event");
+ ok(!gotTabAttrModified, "shouldn't have got the TabAttrModified event after TabClose");
+
+ tab.removeEventListener("TabClose", onTabClose, false);
+ tab.removeEventListener("TabAttrModified", onTabAttrModified, false);
+});
diff --git a/browser/base/content/test/general/browser_bug597218.js b/browser/base/content/test/general/browser_bug597218.js
new file mode 100644
index 000000000..5f4ededc3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug597218.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // establish initial state
+ is(gBrowser.tabs.length, 1, "we start with one tab");
+
+ // create a tab
+ let tab = gBrowser.loadOneTab("about:blank");
+ ok(!tab.hidden, "tab starts out not hidden");
+ is(gBrowser.tabs.length, 2, "we now have two tabs");
+
+ // make sure .hidden is read-only
+ tab.hidden = true;
+ ok(!tab.hidden, "can't set .hidden directly");
+
+ // hide the tab
+ gBrowser.hideTab(tab);
+ ok(tab.hidden, "tab is hidden");
+
+ // now pin it and make sure it gets unhidden
+ gBrowser.pinTab(tab);
+ ok(tab.pinned, "tab was pinned");
+ ok(!tab.hidden, "tab was unhidden");
+
+ // try hiding it now that it's pinned; shouldn't be able to
+ gBrowser.hideTab(tab);
+ ok(!tab.hidden, "tab did not hide");
+
+ // clean up
+ gBrowser.removeTab(tab);
+ is(gBrowser.tabs.length, 1, "we finish with one tab");
+
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug609700.js b/browser/base/content/test/general/browser_bug609700.js
new file mode 100644
index 000000000..8b4f1ea91
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug609700.js
@@ -0,0 +1,20 @@
+function test() {
+ waitForExplicitFinish();
+
+ Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ Services.ww.unregisterNotification(arguments.callee);
+
+ ok(true, "duplicateTabIn opened a new window");
+
+ whenDelayedStartupFinished(aSubject, function () {
+ executeSoon(function () {
+ aSubject.close();
+ finish();
+ });
+ }, false);
+ }
+ });
+
+ duplicateTabIn(gBrowser.selectedTab, "window");
+}
diff --git a/browser/base/content/test/general/browser_bug623893.js b/browser/base/content/test/general/browser_bug623893.js
new file mode 100644
index 000000000..fa6da1b22
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug623893.js
@@ -0,0 +1,37 @@
+add_task(function* test() {
+ yield BrowserTestUtils.withNewTab("data:text/plain;charset=utf-8,1", function* (browser) {
+ BrowserTestUtils.loadURI(browser, "data:text/plain;charset=utf-8,2");
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.loadURI(browser, "data:text/plain;charset=utf-8,3");
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield duplicate(0, "maintained the original index");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ yield duplicate(-1, "went back");
+ yield duplicate(1, "went forward");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ });
+});
+
+function promiseGetIndex(browser) {
+ return ContentTask.spawn(browser, null, function() {
+ let shistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISHistory);
+ return shistory.index;
+ });
+}
+
+let duplicate = Task.async(function* (delta, msg, cb) {
+ var startIndex = yield promiseGetIndex(gBrowser.selectedBrowser);
+
+ duplicateTabIn(gBrowser.selectedTab, "tab", delta);
+
+ let tab = gBrowser.selectedTab;
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+
+ let endIndex = yield promiseGetIndex(gBrowser.selectedBrowser);
+ is(endIndex, startIndex + delta, msg);
+});
diff --git a/browser/base/content/test/general/browser_bug624734.js b/browser/base/content/test/general/browser_bug624734.js
new file mode 100644
index 000000000..d6fc7acbc
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug624734.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 624734 - Star UI has no tooltip until bookmarked page is visited
+
+function finishTest() {
+ is(BookmarkingUI.button.getAttribute("buttontooltiptext"),
+ BookmarkingUI._unstarredTooltip,
+ "Star icon should have the unstarred tooltip text");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING) {
+ waitForCondition(() => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING, finishTest, "BookmarkingUI was updating for too long");
+ } else {
+ finishTest();
+ }
+ });
+
+ tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/general/dummy_page.html");
+}
diff --git a/browser/base/content/test/general/browser_bug633691.js b/browser/base/content/test/general/browser_bug633691.js
new file mode 100644
index 000000000..28a8440ff
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug633691.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(function* test() {
+ const URL = "data:text/html,<iframe width='700' height='700'></iframe>";
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, function* (browser) {
+ yield ContentTask.spawn(browser,
+ { is_element_hidden_: is_element_hidden.toSource(),
+ is_hidden_: is_hidden.toSource() },
+ function* ({ is_element_hidden_, is_hidden_ }) {
+ let loadError =
+ ContentTaskUtils.waitForEvent(this, "AboutNetErrorLoad", false, null, true);
+ let iframe = content.document.querySelector("iframe");
+ iframe.src = "https://expired.example.com/";
+
+ yield loadError;
+
+ let is_hidden = eval(`(() => ${is_hidden_})()`);
+ let is_element_hidden = eval(`(() => ${is_element_hidden_})()`);
+ let doc = content.document.getElementsByTagName("iframe")[0].contentDocument;
+ let aP = doc.getElementById("badCertAdvancedPanel");
+ ok(aP, "Advanced content should exist");
+ void is_hidden; // Quiet eslint warnings (actual use under is_element_hidden)
+ is_element_hidden(aP, "Advanced content should not be visible by default")
+ });
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug647886.js b/browser/base/content/test/general/browser_bug647886.js
new file mode 100644
index 000000000..6c28c465c
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug647886.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.history.pushState({}, "2", "2.html");
+ });
+
+ var backButton = document.getElementById("back-button");
+ var rect = backButton.getBoundingClientRect();
+
+ info("waiting for the history menu to open");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(backButton, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(backButton, {type: "mousedown"});
+ EventUtils.synthesizeMouse(backButton, rect.width / 2, rect.height, {type: "mouseup"});
+ let event = yield popupShownPromise;
+
+ ok(true, "history menu opened");
+
+ // Wait for the session data to be flushed before continuing the test
+ yield new Promise(resolve => SessionStore.getSessionHistory(gBrowser.selectedTab, resolve));
+
+ is(event.target.children.length, 2, "Two history items");
+
+ let node = event.target.firstChild;
+ is(node.getAttribute("uri"), "http://example.com/2.html", "first item uri");
+ is(node.getAttribute("index"), "1", "first item index");
+ is(node.getAttribute("historyindex"), "0", "first item historyindex");
+
+ node = event.target.lastChild;
+ is(node.getAttribute("uri"), "http://example.com/", "second item uri");
+ is(node.getAttribute("index"), "0", "second item index");
+ is(node.getAttribute("historyindex"), "-1", "second item historyindex");
+
+ event.target.hidePopup();
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_bug655584.js b/browser/base/content/test/general/browser_bug655584.js
new file mode 100644
index 000000000..b836e3173
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug655584.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 655584 - awesomebar suggestions don't update after tab is closed
+
+add_task(function* () {
+ var tab1 = gBrowser.addTab();
+ var tab2 = gBrowser.addTab();
+
+ // When urlbar in a new tab is focused, and a tab switch occurs,
+ // the urlbar popup should be closed
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ gURLBar.focus(); // focus the urlbar in the tab we will switch to
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ gURLBar.openPopup();
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ ok(!gURLBar.popupOpen, "urlbar focused in tab to switch to, close popup");
+
+ // cleanup
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug664672.js b/browser/base/content/test/general/browser_bug664672.js
new file mode 100644
index 000000000..2064f77d0
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug664672.js
@@ -0,0 +1,19 @@
+function test() {
+ waitForExplicitFinish();
+
+ var tab = gBrowser.addTab();
+
+ tab.addEventListener("TabClose", function () {
+ tab.removeEventListener("TabClose", arguments.callee, false);
+
+ ok(tab.linkedBrowser, "linkedBrowser should still exist during the TabClose event");
+
+ executeSoon(function () {
+ ok(!tab.linkedBrowser, "linkedBrowser should be gone after the TabClose event");
+
+ finish();
+ });
+ }, false);
+
+ gBrowser.removeTab(tab);
+}
diff --git a/browser/base/content/test/general/browser_bug676619.js b/browser/base/content/test/general/browser_bug676619.js
new file mode 100644
index 000000000..6b596481d
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug676619.js
@@ -0,0 +1,124 @@
+function test () {
+ requestLongerTimeout(3);
+ waitForExplicitFinish();
+
+ var isHTTPS = false;
+
+ function loadListener() {
+ function testLocation(link, url, next) {
+ new TabOpenListener(url, function () {
+ gBrowser.removeTab(this.tab);
+ }, function () {
+ next();
+ });
+
+ ContentTask.spawn(gBrowser.selectedBrowser, link, contentLink => {
+ content.document.getElementById(contentLink).click();
+ });
+ }
+
+ function testLink(link, name, next) {
+ addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function (win) {
+ ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+ Assert.equal(content.document.getElementById("unload-flag").textContent,
+ "Okay", "beforeunload shouldn't have fired");
+ }).then(() => {
+ is(win.document.getElementById("location").value, name, "file name should match");
+ win.close();
+ next();
+ });
+ });
+
+ ContentTask.spawn(gBrowser.selectedBrowser, link, contentLink => {
+ content.document.getElementById(contentLink).click();
+ });
+ }
+
+ testLink("link1", "test.txt",
+ testLink.bind(null, "link2", "video.ogg",
+ testLink.bind(null, "link3", "just some video",
+ testLink.bind(null, "link4", "with-target.txt",
+ testLink.bind(null, "link5", "javascript.txt",
+ testLink.bind(null, "link6", "test.blob",
+ testLocation.bind(null, "link7", "http://example.com/",
+ function () {
+ if (isHTTPS) {
+ finish();
+ } else {
+ // same test again with https:
+ isHTTPS = true;
+ gBrowser.loadURI("https://example.com:443/browser/browser/base/content/test/general/download_page.html");
+ }
+ })))))));
+
+ }
+
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ loadListener();
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(loadListener);
+ });
+
+ gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/download_page.html");
+}
+
+
+function addWindowListener(aURL, aCallback) {
+ Services.wm.addListener({
+ onOpenWindow: function(aXULWindow) {
+ info("window opened, waiting for focus");
+ Services.wm.removeListener(this);
+
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ waitForFocus(function() {
+ is(domwindow.document.location.href, aURL, "should have seen the right window open");
+ aCallback(domwindow);
+ }, domwindow);
+ },
+ onCloseWindow: function(aXULWindow) { },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+ });
+}
+
+// This listens for the next opened tab and checks it is of the right url.
+// opencallback is called when the new tab is fully loaded
+// closecallback is called when the tab is closed
+function TabOpenListener(url, opencallback, closecallback) {
+ this.url = url;
+ this.opencallback = opencallback;
+ this.closecallback = closecallback;
+
+ gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+}
+
+TabOpenListener.prototype = {
+ url: null,
+ opencallback: null,
+ closecallback: null,
+ tab: null,
+ browser: null,
+
+ handleEvent: function(event) {
+ if (event.type == "TabOpen") {
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ this.tab = event.originalTarget;
+ this.browser = this.tab.linkedBrowser;
+ BrowserTestUtils.browserLoaded(this.browser, false, this.url).then(() => {
+ this.tab.addEventListener("TabClose", this, false);
+ var url = this.browser.currentURI.spec;
+ is(url, this.url, "Should have opened the correct tab");
+ this.opencallback();
+ });
+ } else if (event.type == "TabClose") {
+ if (event.originalTarget != this.tab)
+ return;
+ this.tab.removeEventListener("TabClose", this, false);
+ this.opencallback = null;
+ this.tab = null;
+ this.browser = null;
+ // Let the window close complete
+ executeSoon(this.closecallback);
+ this.closecallback = null;
+ }
+ }
+};
diff --git a/browser/base/content/test/general/browser_bug678392-1.html b/browser/base/content/test/general/browser_bug678392-1.html
new file mode 100644
index 000000000..c3b235dd0
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug678392-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <title>bug678392 - 1</title>
+ </head>
+ <body>
+bug 678392 test page 1
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/base/content/test/general/browser_bug678392-2.html b/browser/base/content/test/general/browser_bug678392-2.html
new file mode 100644
index 000000000..9b18efcf7
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug678392-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <title>bug678392 - 2</title>
+ </head>
+ <body>
+bug 678392 test page 2
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/base/content/test/general/browser_bug678392.js b/browser/base/content/test/general/browser_bug678392.js
new file mode 100644
index 000000000..6aedeefdf
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug678392.js
@@ -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/. */
+
+var HTTPROOT = "http://example.com/browser/browser/base/content/test/general/";
+
+function maxSnapshotOverride() {
+ return 5;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ BrowserOpenTab();
+ let tab = gBrowser.selectedTab;
+ registerCleanupFunction(function () { gBrowser.removeTab(tab); });
+
+ ok(gHistorySwipeAnimation, "gHistorySwipeAnimation exists.");
+
+ if (!gHistorySwipeAnimation._isSupported()) {
+ is(gHistorySwipeAnimation.active, false, "History swipe animation is not " +
+ "active when not supported by the platform.");
+ finish();
+ return;
+ }
+
+ gHistorySwipeAnimation._getMaxSnapshots = maxSnapshotOverride;
+ gHistorySwipeAnimation.init();
+
+ is(gHistorySwipeAnimation.active, true, "History swipe animation support " +
+ "was successfully initialized when supported.");
+
+ cleanupArray();
+ load(gBrowser.selectedTab, HTTPROOT + "browser_bug678392-2.html", test0);
+}
+
+function load(aTab, aUrl, aCallback) {
+ aTab.linkedBrowser.addEventListener("load", function onload(aEvent) {
+ aEvent.currentTarget.removeEventListener("load", onload, true);
+ waitForFocus(aCallback, content);
+ }, true);
+ aTab.linkedBrowser.loadURI(aUrl);
+}
+
+function cleanupArray() {
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ while (arr.length > 0) {
+ delete arr[0].browser.snapshots[arr[0].index]; // delete actual snapshot
+ arr.splice(0, 1);
+ }
+}
+
+function testArrayCleanup() {
+ // Test cleanup of array of tracked snapshots.
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ is(arr.length, 0, "Snapshots were removed correctly from the array of " +
+ "tracked snapshots.");
+}
+
+function test0() {
+ // Test growing of array of tracked snapshots.
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ ok(gHistorySwipeAnimation._trackedSnapshots, "Array for snapshot " +
+ "tracking is initialized.");
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Snapshot array " +
+ "has correct length of 1 after loading one page.");
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Snapshot array " +
+ " has correct length of 2 after loading two pages.");
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Snapshot " +
+ "array has correct length of 3 after loading three pages.");
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Snapshot " +
+ "array has correct length of 4 after loading four pages.");
+ cleanupArray();
+ testArrayCleanup();
+ test1();
+ });
+ });
+ });
+ });
+}
+
+function verifyRefRemoved(aIndex, aBrowser) {
+ let wasFound = false;
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ for (let i = 0; i < arr.length; i++) {
+ if (arr[i].index == aIndex && arr[i].browser == aBrowser)
+ wasFound = true;
+ }
+ is(wasFound, false, "The reference that was previously removed was " +
+ "still found in the array of tracked snapshots.");
+}
+
+function test1() {
+ // Test presence of snpashots in per-tab array of snapshots and removal of
+ // individual snapshots (and corresponding references in the array of
+ // tracked snapshots).
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ var historyIndex = gBrowser.webNavigation.sessionHistory.index - 1;
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ let browser = gBrowser.selectedBrowser;
+ ok(browser.snapshots, "Array of snapshots exists in browser.");
+ ok(browser.snapshots[historyIndex], "First page exists in snapshot " +
+ "array.");
+ ok(browser.snapshots[historyIndex + 1], "Second page exists in " +
+ "snapshot array.");
+ ok(browser.snapshots[historyIndex + 2], "Third page exists in " +
+ "snapshot array.");
+ ok(browser.snapshots[historyIndex + 3], "Fourth page exists in " +
+ "snapshot array.");
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length of " +
+ "array of tracked snapshots is equal to 4 after loading four " +
+ "pages.");
+
+ // Test removal of reference in the middle of the array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 1,
+ browser);
+ verifyRefRemoved(historyIndex + 1, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Length of " +
+ "array of tracked snapshots is equal to 3 after removing one" +
+ "reference from the array with length 4.");
+
+ // Test removal of reference at end of array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 3,
+ browser);
+ verifyRefRemoved(historyIndex + 3, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " +
+ "array of tracked snapshots is equal to 2 after removing two" +
+ "references from the array with length 4.");
+
+ // Test removal of reference at head of array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex,
+ browser);
+ verifyRefRemoved(historyIndex, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Length of " +
+ "array of tracked snapshots is equal to 1 after removing three" +
+ "references from the array with length 4.");
+
+ cleanupArray();
+ test2();
+ });
+ });
+ });
+ });
+}
+
+function test2() {
+ // Test growing of snapshot array across tabs.
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " +
+ "snapshot array is equal to 2 after loading two pages");
+ let prevTab = tab;
+ tab = gBrowser.addTab("about:newtab");
+ gBrowser.selectedTab = tab;
+ load(tab, HTTPROOT + "browser_bug678392-2.html" /* initial page */,
+ function() {
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length " +
+ "of snapshot array is equal to 4 after loading two pages in " +
+ "two tabs each.");
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = prevTab;
+ cleanupArray();
+ test3();
+ });
+ });
+ });
+ });
+ });
+}
+
+function test3() {
+ // Test uninit of gHistorySwipeAnimation.
+ // This test MUST be the last one to execute.
+ gHistorySwipeAnimation.uninit();
+ is(gHistorySwipeAnimation.active, false, "History swipe animation support " +
+ "was successfully uninitialized");
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_bug710878.js b/browser/base/content/test/general/browser_bug710878.js
new file mode 100644
index 000000000..dd99d67cf
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug710878.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PAGE = "data:text/html;charset=utf-8,<a href='%23xxx'><span>word1 <span> word2 </span></span><span> word3</span></a>";
+
+/**
+ * Tests that we correctly compute the text for context menu
+ * selection of some content.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu,
+ "popupshown");
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu,
+ "popuphidden");
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("a", {
+ type: "contextmenu",
+ button: 2,
+ }, browser);
+
+ yield awaitPopupShown;
+
+ is(gContextMenu.linkTextStr, "word1 word2 word3",
+ "Text under link is correctly computed.");
+
+ contextMenu.hidePopup();
+ yield awaitPopupHidden;
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug719271.js b/browser/base/content/test/general/browser_bug719271.js
new file mode 100644
index 000000000..c3bb9cd26
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug719271.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";
+
+const TEST_PAGE = "http://example.org/browser/browser/base/content/test/general/zoom_test.html";
+const TEST_VIDEO = "http://example.org/browser/browser/base/content/test/general/video.ogg";
+
+var gTab1, gTab2, gLevel1;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ gTab1 = gBrowser.addTab();
+ gTab2 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, TEST_PAGE);
+ yield FullZoomHelper.load(gTab2, TEST_VIDEO);
+ }).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab1() {
+ Task.spawn(function* () {
+ is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
+
+ // Reset zoom level if we run this test > 1 time in same browser session.
+ var level1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+ if (level1 > 1)
+ FullZoom.reduce();
+
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+
+ FullZoom.enlarge();
+ gLevel1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+
+ ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 is still unzoomed after it is selected");
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 is still zoomed");
+ }).then(zoomTab2, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab2() {
+ Task.spawn(function* () {
+ is(gBrowser.selectedTab, gTab2, "Tab 2 is selected");
+
+ FullZoom.reduce();
+ let level2 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab2));
+
+ ok(level2 < 1, "New zoom for tab 2 should be less than 1");
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Zooming tab 2 should not affect tab 1");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 should have the same zoom after it's selected");
+ }).then(testNavigation, FullZoomHelper.failAndContinue(finish));
+}
+
+function testNavigation() {
+ Task.spawn(function* () {
+ yield FullZoomHelper.load(gTab1, TEST_VIDEO);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when a video was loaded");
+ yield waitForNextTurn(); // trying to fix orange bug 806046
+ yield FullZoomHelper.navigate(FullZoomHelper.BACK);
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Zoom should be restored when a page is loaded");
+ yield waitForNextTurn(); // trying to fix orange bug 806046
+ yield FullZoomHelper.navigate(FullZoomHelper.FORWARD);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 again when navigating back to a video");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+function waitForNextTurn() {
+ let deferred = Promise.defer();
+ setTimeout(() => deferred.resolve(), 0);
+ return deferred.promise;
+}
+
+var finishTestStarted = false;
+function finishTest() {
+ Task.spawn(function* () {
+ ok(!finishTestStarted, "finishTest called more than once");
+ finishTestStarted = true;
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ yield FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/general/browser_bug724239.js b/browser/base/content/test/general/browser_bug724239.js
new file mode 100644
index 000000000..430751b91
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug724239.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* test() {
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (browser) {
+ BrowserTestUtils.loadURI(browser, "http://example.com");
+ yield BrowserTestUtils.browserLoaded(browser);
+ ok(!gBrowser.canGoBack, "about:newtab wasn't added to the session history");
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug734076.js b/browser/base/content/test/general/browser_bug734076.js
new file mode 100644
index 000000000..9de7d913f
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug734076.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* ()
+{
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, null, false);
+
+ let browser = tab.linkedBrowser;
+ browser.stop(); // stop the about:blank load
+
+ let writeDomainURL = encodeURI("data:text/html,<script>document.write(document.domain);</script>");
+
+ let tests = [
+ {
+ name: "view background image",
+ url: "http://mochi.test:8888/",
+ element: "body",
+ go: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+ let contentBody = content.document.body;
+ contentBody.style.backgroundImage = "url('" + arg.writeDomainURL + "')";
+
+ return "context-viewbgimage";
+ });
+ },
+ verify: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) {
+ Assert.ok(!content.document.body.textContent,
+ "no domain was inherited for view background image");
+ });
+ }
+ },
+ {
+ name: "view image",
+ url: "http://mochi.test:8888/",
+ element: "img",
+ go: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+ let doc = content.document;
+ let img = doc.createElement("img");
+ img.height = 100;
+ img.width = 100;
+ img.setAttribute("src", arg.writeDomainURL);
+ doc.body.insertBefore(img, doc.body.firstChild);
+
+ return "context-viewimage";
+ });
+ },
+ verify: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) {
+ Assert.ok(!content.document.body.textContent,
+ "no domain was inherited for view image");
+ });
+ }
+ },
+ {
+ name: "show only this frame",
+ url: "http://mochi.test:8888/",
+ element: "iframe",
+ go: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { writeDomainURL: writeDomainURL }, function* (arg) {
+ let doc = content.document;
+ let iframe = doc.createElement("iframe");
+ iframe.setAttribute("src", arg.writeDomainURL);
+ doc.body.insertBefore(iframe, doc.body.firstChild);
+
+ // Wait for the iframe to load.
+ return new Promise(resolve => {
+ iframe.addEventListener("load", function onload() {
+ iframe.removeEventListener("load", onload, true);
+ resolve("context-showonlythisframe");
+ }, true);
+ });
+ });
+ },
+ verify: function () {
+ return ContentTask.spawn(gBrowser.selectedBrowser, null, function* (arg) {
+ Assert.ok(!content.document.body.textContent,
+ "no domain was inherited for 'show only this frame'");
+ });
+ }
+ }
+ ];
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ for (let test of tests) {
+ let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI(test.url);
+ yield loadedPromise;
+
+ info("Run subtest " + test.name);
+ let commandToRun = yield test.go();
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse(test.element, 3, 3,
+ { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+ info("onImage: " + gContextMenu.onImage);
+ info("target: " + gContextMenu.target.tagName);
+
+ let loadedAfterCommandPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ document.getElementById(commandToRun).click();
+ yield loadedAfterCommandPromise;
+
+ yield test.verify();
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug735471.js b/browser/base/content/test/general/browser_bug735471.js
new file mode 100644
index 000000000..9afb52c4b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug735471.js
@@ -0,0 +1,23 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+function test() {
+ waitForExplicitFinish();
+ // Open a new tab.
+ whenNewTabLoaded(window, testPreferences);
+}
+
+function testPreferences() {
+ whenTabLoaded(gBrowser.selectedTab, function () {
+ is(content.location.href, "about:preferences", "Checking if the preferences tab was opened");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+
+ openPreferences();
+}
diff --git a/browser/base/content/test/general/browser_bug749738.js b/browser/base/content/test/general/browser_bug749738.js
new file mode 100644
index 000000000..7e805b799
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug749738.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";
+
+const DUMMY_PAGE = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+
+ BrowserTestUtils.loadURI(tab.linkedBrowser, DUMMY_PAGE);
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ gFindBar.onFindCommand();
+ EventUtils.sendString("Dummy");
+ gBrowser.removeTab(tab);
+
+ try {
+ gFindBar.close();
+ ok(true, "findbar.close should not throw an exception");
+ } catch (e) {
+ ok(false, "findbar.close threw exception: " + e);
+ }
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug763468_perwindowpb.js b/browser/base/content/test/general/browser_bug763468_perwindowpb.js
new file mode 100644
index 000000000..23cb14b8c
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug763468_perwindowpb.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";
+
+/* globals
+ waitForExplicitFinish, whenNewWindowLoaded, whenNewTabLoaded,
+ executeSoon, registerCleanupFunction, finish, is
+*/
+/* exported test */
+
+// This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing
+function test() {
+ // initialization
+ waitForExplicitFinish();
+
+ let windowsToClose = [];
+ let newTabURL;
+ let mode;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ whenNewTabLoaded(aWindow, function() {
+ if (aIsPrivateMode) {
+ mode = "per window private browsing";
+ newTabURL = "about:privatebrowsing";
+ } else {
+ mode = "normal";
+ newTabURL = "about:newtab";
+ }
+
+ is(aWindow.gBrowser.currentURI.spec, newTabURL,
+ "URL of NewTab should be " + newTabURL + " in " + mode + " mode");
+
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aCallback();
+ });
+ }
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function(aWin) {
+ windowsToClose.push(aWin);
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(() => aCallback(aWin));
+ });
+ }
+
+ // this function is called after calling finish() on the test.
+ registerCleanupFunction(function() {
+ windowsToClose.forEach(function(aWin) {
+ aWin.close();
+ });
+ });
+
+ // test first when not on private mode
+ testOnWindow({}, function(aWin) {
+ doTest(false, aWin, function() {
+ // then test when on private mode
+ testOnWindow({private: true}, function(aWin2) {
+ doTest(true, aWin2, function() {
+ // then test again when not on private mode
+ testOnWindow({}, function(aWin3) {
+ doTest(false, aWin3, finish);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_bug767836_perwindowpb.js b/browser/base/content/test/general/browser_bug767836_perwindowpb.js
new file mode 100644
index 000000000..7f5d15e76
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug767836_perwindowpb.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";
+/* globals waitForExplicitFinish, executeSoon, finish, whenNewWindowLoaded, ok */
+/* globals is */
+/* exported test */
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+
+ let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"]
+ .getService(Components.interfaces.nsIAboutNewTabService);
+ let newTabURL;
+ let testURL = "http://example.com/";
+ let defaultURL = aboutNewTabService.newTabURL;
+ let mode;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ openNewTab(aWindow, function() {
+ if (aIsPrivateMode) {
+ mode = "per window private browsing";
+ newTabURL = "about:privatebrowsing";
+ } else {
+ mode = "normal";
+ newTabURL = "about:newtab";
+ }
+
+ // Check the new tab opened while in normal/private mode
+ is(aWindow.gBrowser.selectedBrowser.currentURI.spec, newTabURL,
+ "URL of NewTab should be " + newTabURL + " in " + mode + " mode");
+ // Set the custom newtab url
+ aboutNewTabService.newTabURL = testURL;
+ is(aboutNewTabService.newTabURL, testURL, "Custom newtab url is set");
+
+ // Open a newtab after setting the custom newtab url
+ openNewTab(aWindow, function() {
+ is(aWindow.gBrowser.selectedBrowser.currentURI.spec, testURL,
+ "URL of NewTab should be the custom url");
+
+ // Clear the custom url.
+ aboutNewTabService.resetNewTabURL();
+ is(aboutNewTabService.newTabURL, defaultURL, "No custom newtab url is set");
+
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aWindow.close();
+ aCallback();
+ });
+ });
+ }
+
+ function testOnWindow(aIsPrivate, aCallback) {
+ whenNewWindowLoaded({private: aIsPrivate}, function(win) {
+ executeSoon(() => aCallback(win));
+ });
+ }
+
+ // check whether any custom new tab url has been configured
+ ok(!aboutNewTabService.overridden, "No custom newtab url is set");
+
+ // test normal mode
+ testOnWindow(false, function(aWindow) {
+ doTest(false, aWindow, function() {
+ // test private mode
+ testOnWindow(true, function(aWindow2) {
+ doTest(true, aWindow2, function() {
+ finish();
+ });
+ });
+ });
+ });
+}
+
+function openNewTab(aWindow, aCallback) {
+ // Open a new tab
+ aWindow.BrowserOpenTab();
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ if (browser.contentDocument.readyState === "complete") {
+ executeSoon(aCallback);
+ return;
+ }
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug817947.js b/browser/base/content/test/general/browser_bug817947.js
new file mode 100644
index 000000000..3a76e36d3
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug817947.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+const URL = "http://mochi.test:8888/browser/";
+const PREF = "browser.sessionstore.restore_on_demand";
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(PREF, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF);
+ });
+
+ preparePendingTab(function (aTab) {
+ let win = gBrowser.replaceTabWithWindow(aTab);
+
+ whenDelayedStartupFinished(win, function () {
+ let [tab] = win.gBrowser.tabs;
+
+ whenLoaded(tab.linkedBrowser, function () {
+ is(tab.linkedBrowser.currentURI.spec, URL, "correct url should be loaded");
+ ok(!tab.hasAttribute("pending"), "tab should not be pending");
+
+ win.close();
+ finish();
+ });
+ });
+ });
+}
+
+function preparePendingTab(aCallback) {
+ let tab = gBrowser.addTab(URL);
+
+ whenLoaded(tab.linkedBrowser, function () {
+ BrowserTestUtils.removeTab(tab).then(() => {
+ let [{state}] = JSON.parse(SessionStore.getClosedTabData(window));
+
+ tab = gBrowser.addTab("about:blank");
+ whenLoaded(tab.linkedBrowser, function () {
+ SessionStore.setTabState(tab, JSON.stringify(state));
+ ok(tab.hasAttribute("pending"), "tab should be pending");
+ aCallback(tab);
+ });
+ });
+ });
+}
+
+function whenLoaded(aElement, aCallback) {
+ aElement.addEventListener("load", function onLoad() {
+ aElement.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_bug822367.js b/browser/base/content/test/general/browser_bug822367.js
new file mode 100644
index 000000000..0d60c05cd
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug822367.js
@@ -0,0 +1,187 @@
+/*
+ * User Override Mixed Content Block - Tests for Bug 822367
+ */
+
+
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+// We alternate for even and odd test cases to simulate different hosts
+const gHttpTestRoot = "https://example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "https://test1.example.com/browser/browser/base/content/test/general/";
+
+var gTestBrowser = null;
+
+add_task(function* test() {
+ yield SpecialPowers.pushPrefEnv({ set: [[ PREF_DISPLAY, true ],
+ [ PREF_ACTIVE, true ]] });
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop()
+
+ // Mixed Script Test
+ var url = gHttpTestRoot + "file_bug822367_1.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+// Mixed Script Test
+add_task(function* MixedTest1A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest1B() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "hello",
+ "Waited too long for mixed script to run in Test 1");
+ });
+});
+
+// Mixed Display Test - Doorhanger should not appear
+add_task(function* MixedTest2() {
+ var url = gHttpTestRoot2 + "file_bug822367_2.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false});
+});
+
+// Mixed Script and Display Test - User Override should cause both the script and the image to load.
+add_task(function* MixedTest3() {
+ var url = gHttpTestRoot + "file_bug822367_3.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest3A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest3B() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let p1 = ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "hello",
+ "Waited too long for mixed script to run in Test 3");
+ let p2 = ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p2").innerHTML == "bye",
+ "Waited too long for mixed image to load in Test 3");
+ yield Promise.all([ p1, p2 ]);
+ });
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: true});
+});
+
+// Location change - User override on one page doesn't propogate to another page after location change.
+add_task(function* MixedTest4() {
+ var url = gHttpTestRoot2 + "file_bug822367_4.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest4A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest4B() {
+ let url = gHttpTestRoot + "file_bug822367_4B.html";
+ yield ContentTask.spawn(gTestBrowser, url, function* (wantedUrl) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.location == wantedUrl,
+ "Waited too long for mixed script to run in Test 4");
+ });
+});
+
+add_task(function* MixedTest4C() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "",
+ "Mixed script loaded in test 4 after location change!");
+ });
+});
+
+// Mixed script attempts to load in a document.open()
+add_task(function* MixedTest5() {
+ var url = gHttpTestRoot + "file_bug822367_5.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest5A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest5B() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("p1").innerHTML == "hello",
+ "Waited too long for mixed script to run in Test 5");
+ });
+});
+
+// Mixed script attempts to load in a document.open() that is within an iframe.
+add_task(function* MixedTest6() {
+ var url = gHttpTestRoot2 + "file_bug822367_6.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest6A() {
+ gTestBrowser.removeEventListener("load", MixedTest6A, true);
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+
+ yield BrowserTestUtils.waitForCondition(
+ () => gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"),
+ "Waited too long for control center to get mixed active blocked state");
+});
+
+add_task(function* MixedTest6B() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* MixedTest6C() {
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ function test() {
+ try {
+ return content.document.getElementById("f1").contentDocument.getElementById("p1").innerHTML == "hello";
+ } catch (e) {
+ return false;
+ }
+ }
+
+ yield ContentTaskUtils.waitForCondition(test, "Waited too long for mixed script to run in Test 6");
+ });
+});
+
+add_task(function* MixedTest6D() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+});
+
+add_task(function* cleanup() {
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug832435.js b/browser/base/content/test/general/browser_bug832435.js
new file mode 100644
index 000000000..6be2604cd
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug832435.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+ ok(true, "Starting up");
+
+ gBrowser.selectedBrowser.focus();
+ gURLBar.addEventListener("focus", function onFocus() {
+ gURLBar.removeEventListener("focus", onFocus);
+ ok(true, "Invoked onfocus handler");
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
+
+ // javscript: URIs are evaluated async.
+ SimpleTest.executeSoon(function() {
+ ok(true, "Evaluated without crashing");
+ finish();
+ });
+ });
+ gURLBar.inputField.value = "javascript: var foo = '11111111'; ";
+ gURLBar.focus();
+}
diff --git a/browser/base/content/test/general/browser_bug839103.js b/browser/base/content/test/general/browser_bug839103.js
new file mode 100644
index 000000000..5240c92ed
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug839103.js
@@ -0,0 +1,120 @@
+const gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+add_task(function* test() {
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (browser) {
+ yield ContentTask.spawn(browser, gTestRoot, testBody);
+ });
+});
+
+// This function runs entirely in the content process. It doesn't have access
+// any free variables in this file.
+function* testBody(testRoot) {
+ const gStyleSheet = "bug839103.css";
+
+ let loaded = ContentTaskUtils.waitForEvent(this, "load", true);
+ content.location = testRoot + "test_bug839103.html";
+
+ yield loaded;
+ function unexpectedContentEvent(event) {
+ ok(false, "Received a " + event.type + " event on content");
+ }
+
+ // We've seen the original stylesheet in the document.
+ // Now add a stylesheet on the fly and make sure we see it.
+ let doc = content.document;
+ doc.styleSheetChangeEventsEnabled = true;
+ doc.addEventListener("StyleSheetAdded", unexpectedContentEvent);
+ doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
+ doc.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent);
+ doc.defaultView.addEventListener("StyleSheetAdded", unexpectedContentEvent);
+ doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
+ doc.defaultView.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent);
+
+ let link = doc.createElement("link");
+ link.setAttribute("rel", "stylesheet");
+ link.setAttribute("type", "text/css");
+ link.setAttribute("href", testRoot + gStyleSheet);
+
+ let sheetAdded =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetAdded", true);
+ let stateChanged =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetApplicableStateChanged", true);
+ doc.body.appendChild(link);
+
+ let evt = yield sheetAdded;
+ info("received dynamic style sheet event");
+ is(evt.type, "StyleSheetAdded", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.documentSheet, "style sheet is a document sheet");
+
+ evt = yield stateChanged;
+ info("received dynamic style sheet applicable state change event");
+ is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, true, "evt.applicable has the right value");
+
+ stateChanged =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetApplicableStateChanged", true);
+ link.disabled = true;
+
+ evt = yield stateChanged;
+ is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
+ info("received dynamic style sheet applicable state change event after media=\"\" changed");
+ is(evt.target, doc, "event targets correct document");
+ is(evt.stylesheet, link.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, false, "evt.applicable has the right value");
+
+ let sheetRemoved =
+ ContentTaskUtils.waitForEvent(this, "StyleSheetRemoved", true);
+ doc.body.removeChild(link);
+
+ evt = yield sheetRemoved;
+ info("received dynamic style sheet removal");
+ is(evt.type, "StyleSheetRemoved", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.stylesheet.href.includes(gStyleSheet), "evt.stylesheet is the removed stylesheet");
+
+ let ruleAdded =
+ ContentTaskUtils.waitForEvent(this, "StyleRuleAdded", true);
+ doc.querySelector("style").sheet.insertRule("*{color:black}", 0);
+
+ evt = yield ruleAdded;
+ info("received style rule added event");
+ is(evt.type, "StyleRuleAdded", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+ is(evt.rule.cssText, "* { color: black; }", "evt.rule.cssText has expected value");
+
+ let ruleChanged =
+ ContentTaskUtils.waitForEvent(this, "StyleRuleChanged", true);
+ evt.rule.style.cssText = "color:green";
+
+ evt = yield ruleChanged;
+ ok(true, "received style rule changed event");
+ is(evt.type, "StyleRuleChanged", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+ is(evt.rule.cssText, "* { color: green; }", "evt.rule.cssText has expected value");
+
+ let ruleRemoved =
+ ContentTaskUtils.waitForEvent(this, "StyleRuleRemoved", true);
+ evt.stylesheet.deleteRule(0);
+
+ evt = yield ruleRemoved;
+ info("received style rule removed event");
+ is(evt.type, "StyleRuleRemoved", "evt.type has expected value");
+ is(evt.target, doc, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().includes("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+}
diff --git a/browser/base/content/test/general/browser_bug882977.js b/browser/base/content/test/general/browser_bug882977.js
new file mode 100644
index 000000000..ed958e06b
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug882977.js
@@ -0,0 +1,29 @@
+"use strict";
+
+/**
+ * Tests that the identity-box shows the chromeUI styling
+ * when viewing about:home in a new window.
+ */
+add_task(function*() {
+ let homepage = "about:home";
+ yield SpecialPowers.pushPrefEnv({
+ "set": [
+ ["browser.startup.homepage", homepage],
+ ["browser.startup.page", 1],
+ ]
+ });
+
+ let win = OpenBrowserWindow();
+ yield BrowserTestUtils.firstBrowserLoaded(win, false);
+
+ let browser = win.gBrowser.selectedBrowser;
+ is(browser.currentURI.spec, homepage, "Loaded the correct homepage");
+ checkIdentityMode(win);
+
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+function checkIdentityMode(win) {
+ let identityMode = win.document.getElementById("identity-box").className;
+ is(identityMode, "chromeUI", "Identity state should be chromeUI for about:home in a new window");
+}
diff --git a/browser/base/content/test/general/browser_bug902156.js b/browser/base/content/test/general/browser_bug902156.js
new file mode 100644
index 000000000..74969ead4
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug902156.js
@@ -0,0 +1,174 @@
+/*
+ * Description of the Tests for
+ * - Bug 902156: Persist "disable protection" option for Mixed Content Blocker
+ *
+ * 1. Navigate to the same domain via document.location
+ * - Load a html page which has mixed content
+ * - Control Center button to disable protection appears - we disable it
+ * - Load a new page from the same origin using document.location
+ * - Control Center button should not appear anymore!
+ *
+ * 2. Navigate to the same domain via simulateclick for a link on the page
+ * - Load a html page which has mixed content
+ * - Control Center button to disable protection appears - we disable it
+ * - Load a new page from the same origin simulating a click
+ * - Control Center button should not appear anymore!
+ *
+ * 3. Navigate to a differnet domain and show the content is still blocked
+ * - Load a different html page which has mixed content
+ * - Control Center button to disable protection should appear again because
+ * we navigated away from html page where we disabled the protection.
+ *
+ * Note, for all tests we set gHttpTestRoot to use 'https'.
+ */
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+// We alternate for even and odd test cases to simulate different hosts
+const gHttpTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/";
+
+var origBlockActive;
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+});
+
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+// ------------------------ Test 1 ------------------------------
+
+function test1A() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test1B);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ // Disable Mixed Content Protection for the page (and reload)
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+}
+
+function test1B() {
+ var expected = "Mixed Content Blocker disabled";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test1C, "Error: Waited too long for mixed script to run in Test 1B");
+}
+
+function test1C() {
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1C");
+
+ // The Script loaded after we disabled the page, now we are going to reload the
+ // page and see if our decision is persistent
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test1D);
+
+ var url = gHttpTestRoot1 + "file_bug902156_2.html";
+ gTestBrowser.loadURI(url);
+}
+
+function test1D() {
+ // The Control Center button should appear but isMixedContentBlocked should be NOT true,
+ // because our decision of disabling the mixed content blocker is persistent.
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1D");
+
+ // move on to Test 2
+ test2();
+}
+
+// ------------------------ Test 2 ------------------------------
+
+function test2() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test2A);
+ var url = gHttpTestRoot2 + "file_bug902156_2.html";
+ gTestBrowser.loadURI(url);
+}
+
+function test2A() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test2B);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ // Disable Mixed Content Protection for the page (and reload)
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+}
+
+function test2B() {
+ var expected = "Mixed Content Blocker disabled";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test2C, "Error: Waited too long for mixed script to run in Test 2B");
+}
+
+function test2C() {
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2C");
+
+ // The Script loaded after we disabled the page, now we are going to reload the
+ // page and see if our decision is persistent
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test2D);
+
+ // reload the page using the provided link in the html file
+ var mctestlink = content.document.getElementById("mctestlink");
+ mctestlink.click();
+}
+
+function test2D() {
+ // The Control Center button should appear but isMixedContentBlocked should be NOT true,
+ // because our decision of disabling the mixed content blocker is persistent.
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: false});
+
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2D");
+
+ // move on to Test 3
+ test3();
+}
+
+// ------------------------ Test 3 ------------------------------
+
+function test3() {
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test3A);
+ var url = gHttpTestRoot1 + "file_bug902156_3.html";
+ gTestBrowser.loadURI(url);
+}
+
+function test3A() {
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ // We are done with tests, clean up
+ cleanUpAfterTests();
+}
+
+// ------------------------------------------------------
+
+function test() {
+ // Performing async calls, e.g. 'onload', we have to wait till all of them finished
+ waitForExplicitFinish();
+
+ // Store original preferences so we can restore settings after testing
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ // Not really sure what this is doing
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop()
+
+ // Starting Test Number 1:
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(test1A);
+ var url = gHttpTestRoot1 + "file_bug902156_1.html";
+ gTestBrowser.loadURI(url);
+}
diff --git a/browser/base/content/test/general/browser_bug906190.js b/browser/base/content/test/general/browser_bug906190.js
new file mode 100644
index 000000000..613f50efd
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug906190.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the persistence of the "disable protection" option for Mixed Content
+ * Blocker in child tabs (bug 906190).
+ */
+
+requestLongerTimeout(2);
+
+// We use the different urls for testing same origin checks before allowing
+// mixed content on child tabs.
+const gHttpTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/";
+
+/**
+ * For all tests, we load the pages over HTTPS and test both:
+ * - |CTRL+CLICK|
+ * - |RIGHT CLICK -> OPEN LINK IN TAB|
+ */
+function* doTest(parentTabSpec, childTabSpec, testTaskFn, waitForMetaRefresh) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: parentTabSpec,
+ }, function* (browser) {
+ // As a sanity check, test that active content has been blocked as expected.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ // Disable the Mixed Content Blocker for the page, which reloads it.
+ let promiseReloaded = BrowserTestUtils.browserLoaded(browser);
+ gIdentityHandler.disableMixedContentProtection();
+ yield promiseReloaded;
+
+ // Wait for the script in the page to update the contents of the test div.
+ let testDiv = content.document.getElementById('mctestdiv');
+ yield promiseWaitForCondition(
+ () => testDiv.innerHTML == "Mixed Content Blocker disabled");
+
+ // Add the link for the child tab to the page.
+ let mainDiv = content.document.createElement("div");
+ mainDiv.innerHTML =
+ '<p><a id="linkToOpenInNewTab" href="' + childTabSpec + '">Link</a></p>';
+ content.document.body.appendChild(mainDiv);
+
+ // Execute the test in the child tabs with the two methods to open it.
+ for (let openFn of [simulateCtrlClick, simulateContextMenuOpenInTab]) {
+ let promiseTabLoaded = waitForSomeTabToLoad();
+ openFn(browser);
+ yield promiseTabLoaded;
+ gBrowser.selectTabAtIndex(2);
+
+ if (waitForMetaRefresh) {
+ yield waitForSomeTabToLoad();
+ }
+
+ yield testTaskFn();
+
+ gBrowser.removeCurrentTab();
+ }
+ });
+}
+
+function simulateCtrlClick(browser) {
+ BrowserTestUtils.synthesizeMouseAtCenter("#linkToOpenInNewTab",
+ { ctrlKey: true, metaKey: true },
+ browser);
+}
+
+function simulateContextMenuOpenInTab(browser) {
+ BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ // These are operations that must be executed synchronously with the event.
+ document.getElementById("context-openlinkintab").doCommand();
+ event.target.hidePopup();
+ return true;
+ });
+ BrowserTestUtils.synthesizeMouseAtCenter("#linkToOpenInNewTab",
+ { type: "contextmenu", button: 2 },
+ browser);
+}
+
+// Waits for a load event somewhere in the browser but ignore events coming
+// from <xul:browser>s without a tab assigned. That are most likely browsers
+// that preload the new tab page.
+function waitForSomeTabToLoad() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("load", function onLoad(event) {
+ let tab = gBrowser._getTabForContentWindow(event.target.defaultView.top);
+ if (tab) {
+ gBrowser.removeEventListener("load", onLoad, true);
+ resolve();
+ }
+ }, true);
+ });
+}
+
+/**
+ * Ensure the Mixed Content Blocker is enabled.
+ */
+add_task(function* test_initialize() {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+ "set": [["security.mixed_content.block_active_content", true]],
+ }, resolve));
+});
+
+/**
+ * 1. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a subpage from the same origin in a new tab simulating a click
+ * - Doorhanger should >> NOT << appear anymore!
+ */
+add_task(function* test_same_origin() {
+ yield doTest(gHttpTestRoot1 + "file_bug906190_1.html",
+ gHttpTestRoot1 + "file_bug906190_2.html", function* () {
+ // The doorhanger should appear but activeBlocked should be >> NOT << true,
+ // because our decision of disabling the mixed content blocker is persistent
+ // across tabs.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: true, activeBlocked: false, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker disabled", "OK: Executed mixed script");
+ });
+});
+
+/**
+ * 2. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from a different origin in a new tab simulating a click
+ * - Doorhanger >> SHOULD << appear again!
+ */
+add_task(function* test_different_origin() {
+ yield doTest(gHttpTestRoot1 + "file_bug906190_2.html",
+ gHttpTestRoot2 + "file_bug906190_2.html", function* () {
+ // The doorhanger should appear and activeBlocked should be >> TRUE <<,
+ // because our decision of disabling the mixed content blocker should only
+ // persist if pages are from the same domain.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker enabled", "OK: Blocked mixed script");
+ });
+});
+
+/**
+ * 3. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from the same origin using meta-refresh
+ * - Doorhanger should >> NOT << appear again!
+ */
+add_task(function* test_same_origin_metarefresh_same_origin() {
+ // file_bug906190_3_4.html redirects to page test1.example.com/* using meta-refresh
+ yield doTest(gHttpTestRoot1 + "file_bug906190_1.html",
+ gHttpTestRoot1 + "file_bug906190_3_4.html", function* () {
+ // The doorhanger should appear but activeBlocked should be >> NOT << true!
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: true, activeBlocked: false, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker disabled", "OK: Executed mixed script");
+ }, true);
+});
+
+/**
+ * 4. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from a different origin using meta-refresh
+ * - Doorhanger >> SHOULD << appear again!
+ */
+add_task(function* test_same_origin_metarefresh_different_origin() {
+ yield doTest(gHttpTestRoot2 + "file_bug906190_1.html",
+ gHttpTestRoot2 + "file_bug906190_3_4.html", function* () {
+ // The doorhanger should appear and activeBlocked should be >> TRUE <<.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker enabled", "OK: Blocked mixed script");
+ }, true);
+});
+
+/**
+ * 5. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from the same origin using 302 redirect
+ */
+add_task(function* test_same_origin_302redirect_same_origin() {
+ // the sjs files returns a 302 redirect- note, same origins
+ yield doTest(gHttpTestRoot1 + "file_bug906190_1.html",
+ gHttpTestRoot1 + "file_bug906190.sjs", function* () {
+ // The doorhanger should appear but activeBlocked should be >> NOT << true.
+ // Currently it is >> TRUE << - see follow up bug 914860
+ ok(!gIdentityHandler._identityBox.classList.contains("mixedActiveBlocked"),
+ "OK: Mixed Content is NOT being blocked");
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker disabled", "OK: Executed mixed script");
+ });
+});
+
+/**
+ * 6. - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin in a new tab simulating a click
+ * - Redirect to another page from a different origin using 302 redirect
+ */
+add_task(function* test_same_origin_302redirect_different_origin() {
+ // the sjs files returns a 302 redirect - note, different origins
+ yield doTest(gHttpTestRoot2 + "file_bug906190_1.html",
+ gHttpTestRoot2 + "file_bug906190.sjs", function* () {
+ // The doorhanger should appear and activeBlocked should be >> TRUE <<.
+ yield assertMixedContentBlockingState(gBrowser, {
+ activeLoaded: false, activeBlocked: true, passiveLoaded: false,
+ });
+
+ is(content.document.getElementById('mctestdiv').innerHTML,
+ "Mixed Content Blocker enabled", "OK: Blocked mixed script");
+ });
+});
+
+/**
+ * 7. - Test memory leak issue on redirection error. See Bug 1269426.
+ */
+add_task(function* test_bad_redirection() {
+ // the sjs files returns a 302 redirect - note, different origins
+ yield doTest(gHttpTestRoot2 + "file_bug906190_1.html",
+ gHttpTestRoot2 + "file_bug906190.sjs?bad-redirection=1", function* () {
+ // Nothing to do. Just see if memory leak is reported in the end.
+ ok(true, "Nothing to do");
+ });
+});
diff --git a/browser/base/content/test/general/browser_bug963945.js b/browser/base/content/test/general/browser_bug963945.js
new file mode 100644
index 000000000..4531964b0
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug963945.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/. */
+
+/*
+ * This test ensures the about:addons tab is only
+ * opened one time when in private browsing.
+ */
+
+add_task(function* test() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+ let tab = win.gBrowser.selectedTab = win.gBrowser.addTab("about:addons");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield promiseWaitForFocus(win);
+
+ EventUtils.synthesizeKey("a", { ctrlKey: true, shiftKey: true }, win);
+
+ is(win.gBrowser.tabs.length, 2, "about:addons tab was re-focused.");
+ is(win.gBrowser.currentURI.spec, "about:addons", "Addons tab was opened.");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/general/browser_bug970746.js b/browser/base/content/test/general/browser_bug970746.js
new file mode 100644
index 000000000..623623e55
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug970746.js
@@ -0,0 +1,121 @@
+/* Make sure context menu includes option to search hyperlink text on search engine */
+
+add_task(function *() {
+ const url = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug970746.xhtml";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ const ellipsis = "\u2026";
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ // Tests if the "Search <engine> for '<some terms>'" context menu item is shown for the
+ // given query string of an element. Tests to make sure label includes the proper search terms.
+ //
+ // Each test:
+ //
+ // id: The id of the element to test.
+ // isSelected: Flag to enable selecting (text highlight) the contents of the element
+ // shouldBeShown: The display state of the menu item
+ // expectedLabelContents: The menu item label should contain a portion of this string.
+ // Will only be tested if shouldBeShown is true.
+ let tests = [
+ {
+ id: "link",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a link!",
+ },
+ {
+ id: "link",
+ isSelected: false,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a link!",
+ },
+ {
+ id: "longLink",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a really lo" + ellipsis,
+ },
+ {
+ id: "longLink",
+ isSelected: false,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm a really lo" + ellipsis,
+ },
+ {
+ id: "plainText",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "Right clicking " + ellipsis,
+ },
+ {
+ id: "plainText",
+ isSelected: false,
+ shouldBeShown: false,
+ },
+ {
+ id: "mixedContent",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "I'm some text, " + ellipsis,
+ },
+ {
+ id: "mixedContent",
+ isSelected: false,
+ shouldBeShown: false,
+ },
+ {
+ id: "partialLink",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "link selection",
+ },
+ {
+ id: "partialLink",
+ isSelected: false,
+ shouldBeShown: true,
+ expectedLabelContents: "A partial link " + ellipsis,
+ },
+ {
+ id: "surrogatePair",
+ isSelected: true,
+ shouldBeShown: true,
+ expectedLabelContents: "This character\uD83D\uDD25" + ellipsis,
+ }
+ ];
+
+ for (let test of tests) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser,
+ { selectElement: test.isSelected ? test.id : null },
+ function* (arg) {
+ let selection = content.getSelection();
+ selection.removeAllRanges();
+
+ if (arg.selectElement) {
+ selection.selectAllChildren(content.document.getElementById(arg.selectElement));
+ }
+ });
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#" + test.id,
+ { type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ let menuItem = document.getElementById("context-searchselect");
+ is(menuItem.hidden, !test.shouldBeShown,
+ "search context menu item is shown for '#" + test.id + "' and selected is '" + test.isSelected + "'");
+
+ if (test.shouldBeShown) {
+ ok(menuItem.label.includes(test.expectedLabelContents),
+ "Menu item text '" + menuItem.label + "' contains the correct search terms '" + test.expectedLabelContents + "'");
+ }
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ // cleanup
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_bug970746.xhtml b/browser/base/content/test/general/browser_bug970746.xhtml
new file mode 100644
index 000000000..9d78d7147
--- /dev/null
+++ b/browser/base/content/test/general/browser_bug970746.xhtml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <a href="http://mozilla.org" id="link">I'm a link!</a>
+ <a href="http://mozilla.org" id="longLink">I'm a really long link and I should be truncated.</a>
+
+ <span id="plainText">
+ Right clicking me when I'm selected should show the menu item.
+ </span>
+ <span id="mixedContent">
+ I'm some text, and <a href="http://mozilla.org">I'm a link!</a>
+ </span>
+
+ <a href="http://mozilla.org">A partial <span id="partialLink">link selection</span></a>
+
+ <span id="surrogatePair">
+ This character🔥 shouldn't be truncated.
+ </span>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/browser_clipboard.js b/browser/base/content/test/general/browser_clipboard.js
new file mode 100644
index 000000000..33c6de52d
--- /dev/null
+++ b/browser/base/content/test/general/browser_clipboard.js
@@ -0,0 +1,174 @@
+// This test is used to check copy and paste in editable areas to ensure that non-text
+// types (html and images) are copied to and pasted from the clipboard properly.
+
+var testPage = "<body style='margin: 0'>" +
+ " <img id='img' tabindex='1' src='http://example.org/browser/browser/base/content/test/general/moz.png'>" +
+ " <div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>" +
+ "</body>";
+
+add_task(function*() {
+ let tab = gBrowser.addTab();
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ gBrowser.selectedTab = tab;
+
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(testPage));
+ yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
+
+ const modifier = (navigator.platform.indexOf("Mac") >= 0) ?
+ Components.interfaces.nsIDOMWindowUtils.MODIFIER_META :
+ Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL;
+
+ // On windows, HTML clipboard includes extra data.
+ // The values are from widget/windows/nsDataObj.cpp.
+ const htmlPrefix = (navigator.platform.indexOf("Win") >= 0) ? "<html><body>\n<!--StartFragment-->" : "";
+ const htmlPostfix = (navigator.platform.indexOf("Win") >= 0) ? "<!--EndFragment-->\n</body>\n</html>" : "";
+
+ yield ContentTask.spawn(browser, { modifier, htmlPrefix, htmlPostfix }, function* (arg) {
+ var doc = content.document;
+ var main = doc.getElementById("main");
+ main.focus();
+
+ const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ function sendKey(key) {
+ if (utils.sendKeyEvent("keydown", key, 0, arg.modifier)) {
+ utils.sendKeyEvent("keypress", key, key.charCodeAt(0), arg.modifier);
+ }
+ utils.sendKeyEvent("keyup", key, 0, arg.modifier);
+ }
+
+ // Select an area of the text.
+ let selection = doc.getSelection();
+ selection.modify("move", "left", "line");
+ selection.modify("move", "right", "character");
+ selection.modify("move", "right", "character");
+ selection.modify("move", "right", "character");
+ selection.modify("extend", "right", "word");
+ selection.modify("extend", "right", "word");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("copy", function copyEvent(event) {
+ removeEventListener("copy", copyEvent, true);
+ // The data is empty as the selection is copied during the event default phase.
+ Assert.equal(event.clipboardData.mozItemCount, 0, "Zero items on clipboard");
+ resolve();
+ }, true)
+
+ sendKey("c");
+ });
+
+ selection.modify("move", "right", "line");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("paste", function copyEvent(event) {
+ removeEventListener("paste", copyEvent, true);
+ let clipboardData = event.clipboardData;
+ Assert.equal(clipboardData.mozItemCount, 1, "One item on clipboard");
+ Assert.equal(clipboardData.types.length, 2, "Two types on clipboard");
+ Assert.equal(clipboardData.types[0], "text/html", "text/html on clipboard");
+ Assert.equal(clipboardData.types[1], "text/plain", "text/plain on clipboard");
+ Assert.equal(clipboardData.getData("text/html"), arg.htmlPrefix +
+ "t <b>Bold</b>" + arg.htmlPostfix, "text/html value");
+ Assert.equal(clipboardData.getData("text/plain"), "t Bold", "text/plain value");
+ resolve();
+ }, true)
+ sendKey("v");
+ });
+
+ Assert.equal(main.innerHTML, "Test <b>Bold</b> After Textt <b>Bold</b>", "Copy and paste html");
+
+ selection.modify("extend", "left", "word");
+ selection.modify("extend", "left", "word");
+ selection.modify("extend", "left", "character");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("cut", function copyEvent(event) {
+ removeEventListener("cut", copyEvent, true);
+ event.clipboardData.setData("text/plain", "Some text");
+ event.clipboardData.setData("text/html", "<i>Italic</i> ");
+ selection.deleteFromDocument();
+ event.preventDefault();
+ resolve();
+ }, true)
+ sendKey("x");
+ });
+
+ selection.modify("move", "left", "line");
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("paste", function copyEvent(event) {
+ removeEventListener("paste", copyEvent, true);
+ let clipboardData = event.clipboardData;
+ Assert.equal(clipboardData.mozItemCount, 1, "One item on clipboard 2");
+ Assert.equal(clipboardData.types.length, 2, "Two types on clipboard 2");
+ Assert.equal(clipboardData.types[0], "text/html", "text/html on clipboard 2");
+ Assert.equal(clipboardData.types[1], "text/plain", "text/plain on clipboard 2");
+ Assert.equal(clipboardData.getData("text/html"), arg.htmlPrefix +
+ "<i>Italic</i> " + arg.htmlPostfix, "text/html value 2");
+ Assert.equal(clipboardData.getData("text/plain"), "Some text", "text/plain value 2");
+ resolve();
+ }, true)
+ sendKey("v");
+ });
+
+ Assert.equal(main.innerHTML, "<i>Italic</i> Test <b>Bold</b> After<b></b>",
+ "Copy and paste html 2");
+ });
+
+ // Next, check that the Copy Image command works.
+
+ // The context menu needs to be opened to properly initialize for the copy
+ // image command to run.
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenuShown = promisePopupShown(contextMenu);
+ BrowserTestUtils.synthesizeMouseAtCenter("#img", { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+ yield contextMenuShown;
+
+ document.getElementById("context-copyimage-contents").doCommand();
+
+ contextMenu.hidePopup();
+ yield promisePopupHidden(contextMenu);
+
+ // Focus the content again
+ yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
+
+ yield ContentTask.spawn(browser, { modifier, htmlPrefix, htmlPostfix }, function* (arg) {
+ var doc = content.document;
+ var main = doc.getElementById("main");
+ main.focus();
+
+ yield new Promise((resolve, reject) => {
+ addEventListener("paste", function copyEvent(event) {
+ removeEventListener("paste", copyEvent, true);
+ let clipboardData = event.clipboardData;
+
+ // DataTransfer doesn't support the image types yet, so only text/html
+ // will be present.
+ if (clipboardData.getData("text/html") !== arg.htmlPrefix +
+ '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+ arg.htmlPostfix) {
+ reject('Clipboard Data did not contain an image, was ' + clipboardData.getData("text/html"));
+ }
+ resolve();
+ }, true)
+
+ const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ if (utils.sendKeyEvent("keydown", "v", 0, arg.modifier)) {
+ utils.sendKeyEvent("keypress", "v", "v".charCodeAt(0), arg.modifier);
+ }
+ utils.sendKeyEvent("keyup", "v", 0, arg.modifier);
+ });
+
+ // The new content should now include an image.
+ Assert.equal(main.innerHTML, '<i>Italic</i> <img id="img" tabindex="1" ' +
+ 'src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+ 'Test <b>Bold</b> After<b></b>', "Paste after copy image");
+ });
+
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_clipboard_pastefile.js b/browser/base/content/test/general/browser_clipboard_pastefile.js
new file mode 100644
index 000000000..fe87284f3
--- /dev/null
+++ b/browser/base/content/test/general/browser_clipboard_pastefile.js
@@ -0,0 +1,62 @@
+// This test is used to check that pasting files removes all non-file data from
+// event.clipboardData.
+
+add_task(function*() {
+ var textbox = document.createElement("textbox");
+ document.documentElement.appendChild(textbox);
+
+ textbox.focus();
+ textbox.value = "Text";
+ textbox.select();
+
+ yield new Promise((resolve, reject) => {
+ textbox.addEventListener("copy", function copyEvent(event) {
+ textbox.removeEventListener("copy", copyEvent, true);
+ event.clipboardData.setData("text/plain", "Alternate");
+ // For this test, it doesn't matter that the file isn't actually a file.
+ event.clipboardData.setData("application/x-moz-file", "Sample");
+ event.preventDefault();
+ resolve();
+ }, true)
+
+ EventUtils.synthesizeKey("c", { accelKey: true });
+ });
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "https://example.com/browser/browser/base/content/test/general/clipboard_pastefile.html");
+ let browser = tab.linkedBrowser;
+
+ yield ContentTask.spawn(browser, { }, function* (arg) {
+ content.document.getElementById("input").focus();
+ });
+
+ yield BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser);
+
+ let output = yield ContentTask.spawn(browser, { }, function* (arg) {
+ return content.document.getElementById("output").textContent;
+ });
+ is (output, "Passed", "Paste file");
+
+ textbox.focus();
+
+ yield new Promise((resolve, reject) => {
+ textbox.addEventListener("paste", function copyEvent(event) {
+ textbox.removeEventListener("paste", copyEvent, true);
+
+ let dt = event.clipboardData;
+ is(dt.types.length, 3, "number of types");
+ ok(dt.types.includes("text/plain"), "text/plain exists in types");
+ ok(dt.mozTypesAt(0).contains("text/plain"), "text/plain exists in mozTypesAt");
+ is(dt.getData("text/plain"), "Alternate", "text/plain returned in getData");
+ is(dt.mozGetDataAt("text/plain", 0), "Alternate", "text/plain returned in mozGetDataAt");
+
+ resolve();
+ }, true);
+
+ EventUtils.synthesizeKey("v", { accelKey: true });
+ });
+
+ document.documentElement.removeChild(textbox);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_contentAltClick.js b/browser/base/content/test/general/browser_contentAltClick.js
new file mode 100644
index 000000000..1a3b0fccc
--- /dev/null
+++ b/browser/base/content/test/general/browser_contentAltClick.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/. */
+
+/**
+ * Test for Bug 1109146.
+ * The tests opens a new tab and alt + clicks to download files
+ * and confirms those files are on the download list.
+ *
+ * The difference between this and the test "browser_contentAreaClick.js" is that
+ * the code path in e10s uses ContentClick.jsm instead of browser.js::contentAreaClick() util.
+ */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+function setup() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+
+ let testPage =
+ 'data:text/html,' +
+ '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' +
+ '<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' +
+ '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>';
+
+ return BrowserTestUtils.openNewForegroundTab(gBrowser, testPage);
+}
+
+function* clean_up() {
+ // Remove downloads.
+ let downloadList = yield Downloads.getList(Downloads.ALL);
+ let downloads = yield downloadList.getAll();
+ for (let download of downloads) {
+ yield downloadList.remove(download);
+ yield download.finalize(true);
+ }
+ // Remove download history.
+ yield PlacesTestUtils.clearHistory();
+
+ gPrefService.clearUserPref("browser.altClickSave");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+add_task(function* test_alt_click()
+{
+ yield setup();
+
+ let downloadList = yield Downloads.getList(Downloads.ALL);
+ let downloads = [];
+ let downloadView;
+ // When 1 download has been attempted then resolve the promise.
+ let finishedAllDownloads = new Promise( (resolve) => {
+ downloadView = {
+ onDownloadAdded: function (aDownload) {
+ downloads.push(aDownload);
+ resolve();
+ },
+ };
+ });
+ yield downloadList.addView(downloadView);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#commonlink", {altKey: true}, gBrowser.selectedBrowser);
+
+ // Wait for all downloads to be added to the download list.
+ yield finishedAllDownloads;
+ yield downloadList.removeView(downloadView);
+
+ is(downloads.length, 1, "1 downloads");
+ is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #commonlink element");
+
+ yield* clean_up();
+});
+
+add_task(function* test_alt_click_on_xlinks()
+{
+ yield setup();
+
+ let downloadList = yield Downloads.getList(Downloads.ALL);
+ let downloads = [];
+ let downloadView;
+ // When all 2 downloads have been attempted then resolve the promise.
+ let finishedAllDownloads = new Promise( (resolve) => {
+ downloadView = {
+ onDownloadAdded: function (aDownload) {
+ downloads.push(aDownload);
+ if (downloads.length == 2) {
+ resolve();
+ }
+ },
+ };
+ });
+ yield downloadList.addView(downloadView);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#mathxlink", {altKey: true}, gBrowser.selectedBrowser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#svgxlink", {altKey: true}, gBrowser.selectedBrowser);
+
+ // Wait for all downloads to be added to the download list.
+ yield finishedAllDownloads;
+ yield downloadList.removeView(downloadView);
+
+ is(downloads.length, 2, "2 downloads");
+ is(downloads[0].source.url, "http://mochi.test/moz/", "Downloaded #mathxlink element");
+ is(downloads[1].source.url, "http://mochi.test/moz/", "Downloaded #svgxlink element");
+
+ yield* clean_up();
+});
diff --git a/browser/base/content/test/general/browser_contentAreaClick.js b/browser/base/content/test/general/browser_contentAreaClick.js
new file mode 100644
index 000000000..facdfb498
--- /dev/null
+++ b/browser/base/content/test/general/browser_contentAreaClick.js
@@ -0,0 +1,307 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 549340.
+ * Test for browser.js::contentAreaClick() util.
+ *
+ * The test opens a new browser window, then replaces browser.js methods invoked
+ * by contentAreaClick with a mock function that tracks which methods have been
+ * called.
+ * Each sub-test synthesizes a mouse click event on links injected in content,
+ * the event is collected by a click handler that ensures that contentAreaClick
+ * correctly prevent default events, and follows the correct code path.
+ */
+
+var gTests = [
+
+ {
+ desc: "Simple left click",
+ setup: function() {},
+ clean: function() {},
+ event: {},
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [],
+ preventDefault: false,
+ },
+
+ {
+ desc: "Ctrl/Cmd left click",
+ setup: function() {},
+ clean: function() {},
+ event: { ctrlKey: true,
+ metaKey: true },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ // The next test was once handling feedService.forcePreview(). Now it should
+ // just be like Alt click.
+ {
+ desc: "Shift+Alt left click",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { shiftKey: true,
+ altKey: true },
+ targets: [ "commonlink", "maplink" ],
+ expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Shift+Alt left click on XLinks",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { shiftKey: true,
+ altKey: true },
+ targets: [ "mathxlink", "svgxlink"],
+ expectedInvokedMethods: [ "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Shift click",
+ setup: function() {},
+ clean: function() {},
+ event: { shiftKey: true },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Alt click",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { altKey: true },
+ targets: [ "commonlink", "maplink" ],
+ expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Alt click on XLinks",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { altKey: true },
+ targets: [ "mathxlink", "svgxlink" ],
+ expectedInvokedMethods: [ "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Panel click",
+ setup: function() {},
+ clean: function() {},
+ event: {},
+ targets: [ "panellink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "loadURI" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Simple middle click opentab",
+ setup: function() {},
+ clean: function() {},
+ event: { button: 1 },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Simple middle click openwin",
+ setup: function() {
+ gPrefService.setBoolPref("browser.tabs.opentabfor.middleclick", false);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick");
+ },
+ event: { button: 1 },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Middle mouse paste",
+ setup: function() {
+ gPrefService.setBoolPref("middlemouse.contentLoadURL", true);
+ gPrefService.setBoolPref("general.autoScroll", false);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("middlemouse.contentLoadURL");
+ gPrefService.clearUserPref("general.autoScroll");
+ },
+ event: { button: 1 },
+ targets: [ "emptylink" ],
+ expectedInvokedMethods: [ "middleMousePaste" ],
+ preventDefault: true,
+ },
+
+];
+
+// Array of method names that will be replaced in the new window.
+var gReplacedMethods = [
+ "middleMousePaste",
+ "urlSecurityCheck",
+ "loadURI",
+ "gatherTextUnder",
+ "saveURL",
+ "openLinkIn",
+ "getShortcutOrURIAndPostData",
+];
+
+// Reference to the new window.
+var gTestWin = null;
+
+// List of methods invoked by a specific call to contentAreaClick.
+var gInvokedMethods = [];
+
+// The test currently running.
+var gCurrentTest = null;
+
+function test() {
+ waitForExplicitFinish();
+
+ gTestWin = openDialog(location, "", "chrome,all,dialog=no", "about:blank");
+ whenDelayedStartupFinished(gTestWin, function () {
+ info("Browser window opened");
+ waitForFocus(function() {
+ info("Browser window focused");
+ waitForFocus(function() {
+ info("Setting up browser...");
+ setupTestBrowserWindow();
+ info("Running tests...");
+ executeSoon(runNextTest);
+ }, gTestWin.content, true);
+ }, gTestWin);
+ });
+}
+
+// Click handler used to steal click events.
+var gClickHandler = {
+ handleEvent: function (event) {
+ let linkId = event.target.id || event.target.localName;
+ is(event.type, "click",
+ gCurrentTest.desc + ":Handler received a click event on " + linkId);
+
+ let isPanelClick = linkId == "panellink";
+ gTestWin.contentAreaClick(event, isPanelClick);
+ let prevent = event.defaultPrevented;
+ is(prevent, gCurrentTest.preventDefault,
+ gCurrentTest.desc + ": event.defaultPrevented is correct (" + prevent + ")")
+
+ // Check that all required methods have been called.
+ gCurrentTest.expectedInvokedMethods.forEach(function(aExpectedMethodName) {
+ isnot(gInvokedMethods.indexOf(aExpectedMethodName), -1,
+ gCurrentTest.desc + ":" + aExpectedMethodName + " was invoked");
+ });
+
+ if (gInvokedMethods.length != gCurrentTest.expectedInvokedMethods.length) {
+ ok(false, "Wrong number of invoked methods");
+ gInvokedMethods.forEach(method => info(method + " was invoked"));
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ executeSoon(runNextTest);
+ }
+}
+
+// Wraps around the methods' replacement mock function.
+function wrapperMethod(aInvokedMethods, aMethodName) {
+ return function () {
+ aInvokedMethods.push(aMethodName);
+ // At least getShortcutOrURIAndPostData requires to return url
+ return (aMethodName == "getShortcutOrURIAndPostData") ? arguments.url : arguments[0];
+ }
+}
+
+function setupTestBrowserWindow() {
+ // Steal click events and don't propagate them.
+ gTestWin.addEventListener("click", gClickHandler, true);
+
+ // Replace methods.
+ gReplacedMethods.forEach(function (aMethodName) {
+ gTestWin["old_" + aMethodName] = gTestWin[aMethodName];
+ gTestWin[aMethodName] = wrapperMethod(gInvokedMethods, aMethodName);
+ });
+
+ // Inject links in content.
+ let doc = gTestWin.content.document;
+ let mainDiv = doc.createElement("div");
+ mainDiv.innerHTML =
+ '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' +
+ '<p><a id="panellink" href="http://mochi.test/moz/">Panel link</a></p>' +
+ '<p><a id="emptylink">Empty link</a></p>' +
+ '<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' +
+ '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>' +
+ '<p><map name="map" id="map"><area href="http://mochi.test/moz/" shape="rect" coords="0,0,128,128" /></map><img id="maplink" usemap="#map" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAABGdBTUEAALGPC%2FxhBQAAAOtJREFUeF7t0IEAAAAAgKD9qRcphAoDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGBgwIAAAT0N51AAAAAASUVORK5CYII%3D"/></p>'
+ doc.body.appendChild(mainDiv);
+}
+
+function runNextTest() {
+ if (!gCurrentTest) {
+ gCurrentTest = gTests.shift();
+ gCurrentTest.setup();
+ }
+
+ if (gCurrentTest.targets.length == 0) {
+ info(gCurrentTest.desc + ": cleaning up...")
+ gCurrentTest.clean();
+
+ if (gTests.length > 0) {
+ gCurrentTest = gTests.shift();
+ gCurrentTest.setup();
+ }
+ else {
+ finishTest();
+ return;
+ }
+ }
+
+ // Move to next target.
+ gInvokedMethods.length = 0;
+ let target = gCurrentTest.targets.shift();
+
+ info(gCurrentTest.desc + ": testing " + target);
+
+ // Fire click event.
+ let targetElt = gTestWin.content.document.getElementById(target);
+ ok(targetElt, gCurrentTest.desc + ": target is valid (" + targetElt.id + ")");
+ EventUtils.synthesizeMouseAtCenter(targetElt, gCurrentTest.event, gTestWin.content);
+}
+
+function finishTest() {
+ info("Restoring browser...");
+ gTestWin.removeEventListener("click", gClickHandler, true);
+
+ // Restore original methods.
+ gReplacedMethods.forEach(function (aMethodName) {
+ gTestWin[aMethodName] = gTestWin["old_" + aMethodName];
+ delete gTestWin["old_" + aMethodName];
+ });
+
+ gTestWin.close();
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_contentSearchUI.js b/browser/base/content/test/general/browser_contentSearchUI.js
new file mode 100644
index 000000000..003f80aff
--- /dev/null
+++ b/browser/base/content/test/general/browser_contentSearchUI.js
@@ -0,0 +1,771 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_PAGE_BASENAME = "contentSearchUI.html";
+const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js";
+const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
+
+const TEST_MSG = "ContentSearchUIControllerTest";
+
+requestLongerTimeout(2);
+
+add_task(function* emptyInput() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_BACK_SPACE");
+ checkState(state, "", [], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* blur() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("blur");
+ checkState(state, "x", [], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* upDownKeys() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ // Cycle down the suggestions starting from no selection.
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ // Cycle up starting from no selection.
+ state = yield msg("key", "VK_UP");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* rightLeftKeys() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_LEFT");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_LEFT");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_RIGHT");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_RIGHT");
+ checkState(state, "x", [], -1);
+
+ state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ // This should make the xfoo suggestion sticky. To make sure it sticks,
+ // trigger suggestions again and cycle through them by pressing Down until
+ // nothing is selected again.
+ state = yield msg("key", "VK_RIGHT");
+ checkState(state, "xfoo", [], -1);
+
+ state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 2);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* tabKey() {
+ yield setUp();
+ yield msg("key", { key: "x", waitForSuggestions: true });
+
+ let state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+
+ state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", { key: "VK_TAB", modifiers: { shiftKey: true }});
+ checkState(state, "x", [], -1);
+
+ yield setUp();
+
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+
+ for (let i = 0; i < 3; ++i) {
+ state = yield msg("key", "VK_TAB");
+ }
+ checkState(state, "x", [], -1);
+
+ yield setUp();
+
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, 0);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+
+ state = yield msg("key", "VK_UP");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", [], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* cycleSuggestions() {
+ yield setUp();
+ yield msg("key", { key: "x", waitForSuggestions: true });
+
+ let cycle = Task.async(function* (aSelectedButtonIndex) {
+ let modifiers = {
+ shiftKey: true,
+ accelKey: true,
+ };
+
+ let state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
+ });
+
+ yield cycle();
+
+ // Repeat with a one-off selected.
+ let state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 2);
+ yield cycle(0);
+
+ // Repeat with the settings button selected.
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "x", ["xfoo", "xbar"], 3);
+ yield cycle(1);
+
+ yield msg("reset");
+});
+
+add_task(function* cycleOneOffs() {
+ yield setUp();
+ yield msg("key", { key: "x", waitForSuggestions: true });
+
+ yield msg("addDuplicateOneOff");
+
+ let state = yield msg("key", "VK_DOWN");
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ let modifiers = {
+ altKey: true,
+ };
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1);
+
+ // If the settings button is selected, pressing alt+up/down should select the
+ // last/first one-off respectively (and deselect the settings button).
+ yield msg("key", "VK_TAB");
+ yield msg("key", "VK_TAB");
+ state = yield msg("key", "VK_TAB"); // Settings button selected.
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
+
+ state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("key", "VK_TAB");
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 2);
+
+ state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
+ checkState(state, "xbar", ["xfoo", "xbar"], 1, 0);
+
+ yield msg("removeLastOneOff");
+ yield msg("reset");
+});
+
+add_task(function* mouse() {
+ yield setUp();
+
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("mousemove", 0);
+ checkState(state, "x", ["xfoo", "xbar"], 0);
+
+ state = yield msg("mousemove", 1);
+ checkState(state, "x", ["xfoo", "xbar"], 1);
+
+ state = yield msg("mousemove", 2);
+ checkState(state, "x", ["xfoo", "xbar"], 1, 0);
+
+ state = yield msg("mousemove", 3);
+ checkState(state, "x", ["xfoo", "xbar"], 1, 1);
+
+ state = yield msg("mousemove", -1);
+ checkState(state, "x", ["xfoo", "xbar"], 1);
+
+ yield msg("reset");
+ yield setUp();
+
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ state = yield msg("mousemove", 0);
+ checkState(state, "x", ["xfoo", "xbar"], 0);
+
+ state = yield msg("mousemove", 2);
+ checkState(state, "x", ["xfoo", "xbar"], 0, 0);
+
+ state = yield msg("mousemove", -1);
+ checkState(state, "x", ["xfoo", "xbar"], 0);
+
+ yield msg("reset");
+});
+
+add_task(function* formHistory() {
+ yield setUp();
+
+ // Type an X and add it to form history.
+ let state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+ // Wait for Satchel to say it's been added to form history.
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function onAdd(subj, topic, data) {
+ if (data == "formhistory-add") {
+ Services.obs.removeObserver(onAdd, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+ yield Promise.all([msg("addInputValueToFormHistory"), deferred.promise]);
+
+ // Reset the input.
+ state = yield msg("reset");
+ checkState(state, "", [], -1);
+
+ // Type an X again. The form history entry should appear.
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
+ -1);
+
+ // Select the form history entry and delete it.
+ state = yield msg("key", "VK_DOWN");
+ checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
+ 0);
+
+ // Wait for Satchel.
+ deferred = Promise.defer();
+ Services.obs.addObserver(function onRemove(subj, topic, data) {
+ if (data == "formhistory-remove") {
+ Services.obs.removeObserver(onRemove, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+
+ state = yield msg("key", "VK_DELETE");
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield deferred.promise;
+
+ // Reset the input.
+ state = yield msg("reset");
+ checkState(state, "", [], -1);
+
+ // Type an X again. The form history entry should still be gone.
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield msg("reset");
+});
+
+add_task(function* cycleEngines() {
+ yield setUp();
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+
+ let promiseEngineChange = function(newEngineName) {
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function resolver(subj, topic, data) {
+ if (data != "engine-current") {
+ return;
+ }
+ SimpleTest.is(subj.name, newEngineName, "Engine cycled correctly");
+ Services.obs.removeObserver(resolver, "browser-search-engine-modified");
+ deferred.resolve();
+ }, "browser-search-engine-modified", false);
+ return deferred.promise;
+ }
+
+ let p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME);
+ yield msg("key", { key: "VK_DOWN", modifiers: { accelKey: true }});
+ yield p;
+
+ p = promiseEngineChange(TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME);
+ yield msg("key", { key: "VK_UP", modifiers: { accelKey: true }});
+ yield p;
+
+ yield msg("reset");
+});
+
+add_task(function* search() {
+ yield setUp();
+
+ let modifiers = {};
+ ["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(k => modifiers[k] = true);
+
+ // Test typing a query and pressing enter.
+ let p = msg("waitForSearch");
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ let mesg = yield p;
+ let eventData = {
+ engineName: TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME,
+ searchString: "x",
+ healthReportKey: "test",
+ searchPurpose: "test",
+ originalEvent: modifiers,
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query, then selecting a suggestion and pressing enter.
+ p = msg("waitForSearch");
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo";
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+ eventData.selection = {
+ index: 1,
+ kind: "key",
+ }
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query, then selecting a one-off button and pressing enter.
+ p = msg("waitForSearch");
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", "VK_UP");
+ yield msg("key", "VK_UP");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ delete eventData.selection;
+ eventData.searchString = "x";
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query and clicking the search engine header.
+ p = msg("waitForSearch");
+ modifiers.button = 0;
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("mousemove", -1);
+ yield msg("click", { eltIdx: -1, modifiers: modifiers });
+ mesg = yield p;
+ eventData.originalEvent = modifiers;
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query and then clicking a suggestion.
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("mousemove", 1);
+ yield msg("click", { eltIdx: 1, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo";
+ eventData.selection = {
+ index: 1,
+ kind: "mouse",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test typing a query and then clicking a one-off button.
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("mousemove", 3);
+ yield msg("click", { eltIdx: 3, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "x";
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
+ delete eventData.selection;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test selecting a suggestion, then clicking a one-off without deselecting the
+ // suggestion.
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("mousemove", 1);
+ yield msg("mousemove", 3);
+ yield msg("click", { eltIdx: 3, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo"
+ eventData.selection = {
+ index: 1,
+ kind: "mouse",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Same as above, but with the keyboard.
+ delete modifiers.button;
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ p = msg("waitForSearch");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_TAB");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ eventData.selection = {
+ index: 1,
+ kind: "key",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Test searching when using IME composition.
+ let state = yield msg("startComposition", { data: "" });
+ checkState(state, "", [], -1);
+ state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], -1);
+ yield msg("commitComposition");
+ delete modifiers.button;
+ p = msg("waitForSearch");
+ yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "x"
+ eventData.originalEvent = modifiers;
+ eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+ delete eventData.selection;
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ state = yield msg("startComposition", { data: "" });
+ checkState(state, "", [], -1);
+ state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], -1);
+
+ // Mouse over the first suggestion.
+ state = yield msg("mousemove", 0);
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], 0);
+
+ // Mouse over the second suggestion.
+ state = yield msg("mousemove", 1);
+ checkState(state, "x", [{ str: "x", type: "formHistory" },
+ { str: "xfoo", type: "formHistory" }, "xbar"], 1);
+
+ modifiers.button = 0;
+ p = msg("waitForSearch");
+ yield msg("click", { eltIdx: 1, modifiers: modifiers });
+ mesg = yield p;
+ eventData.searchString = "xfoo";
+ eventData.originalEvent = modifiers;
+ eventData.selection = {
+ index: 1,
+ kind: "mouse",
+ };
+ SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+ yield promiseTab();
+ yield setUp();
+
+ // Remove form history entries.
+ // Wait for Satchel.
+ let deferred = Promise.defer();
+ let historyCount = 2;
+ Services.obs.addObserver(function onRemove(subj, topic, data) {
+ if (data == "formhistory-remove") {
+ if (--historyCount) {
+ return;
+ }
+ Services.obs.removeObserver(onRemove, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+
+ yield msg("key", { key: "x", waitForSuggestions: true });
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DELETE");
+ yield msg("key", "VK_DOWN");
+ yield msg("key", "VK_DELETE");
+ yield deferred.promise;
+
+ yield msg("reset");
+ state = yield msg("key", { key: "x", waitForSuggestions: true });
+ checkState(state, "x", ["xfoo", "xbar"], -1);
+
+ yield promiseTab();
+ yield setUp();
+ yield msg("reset");
+});
+
+add_task(function* settings() {
+ yield setUp();
+ yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+ yield msg("key", "VK_UP");
+ let p = msg("waitForSearchSettings");
+ yield msg("key", "VK_RETURN");
+ yield p;
+
+ yield msg("reset");
+});
+
+var gDidInitialSetUp = false;
+
+function setUp(aNoEngine) {
+ return Task.spawn(function* () {
+ if (!gDidInitialSetUp) {
+ Cu.import("resource:///modules/ContentSearch.jsm");
+ let originalOnMessageSearch = ContentSearch._onMessageSearch;
+ let originalOnMessageManageEngines = ContentSearch._onMessageManageEngines;
+ ContentSearch._onMessageSearch = () => {};
+ ContentSearch._onMessageManageEngines = () => {};
+ registerCleanupFunction(() => {
+ ContentSearch._onMessageSearch = originalOnMessageSearch;
+ ContentSearch._onMessageManageEngines = originalOnMessageManageEngines;
+ });
+ yield setUpEngines();
+ yield promiseTab();
+ gDidInitialSetUp = true;
+ }
+ yield msg("focus");
+ });
+}
+
+function msg(type, data=null) {
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: type,
+ data: data,
+ });
+ let deferred = Promise.defer();
+ gMsgMan.addMessageListener(TEST_MSG, function onMsg(msgObj) {
+ if (msgObj.data.type != type) {
+ return;
+ }
+ gMsgMan.removeMessageListener(TEST_MSG, onMsg);
+ deferred.resolve(msgObj.data.data);
+ });
+ return deferred.promise;
+}
+
+function checkState(actualState, expectedInputVal, expectedSuggestions,
+ expectedSelectedIdx, expectedSelectedButtonIdx) {
+ expectedSuggestions = expectedSuggestions.map(sugg => {
+ return typeof(sugg) == "object" ? sugg : {
+ str: sugg,
+ type: "remote",
+ };
+ });
+
+ if (expectedSelectedIdx == -1 && expectedSelectedButtonIdx != undefined) {
+ expectedSelectedIdx = expectedSuggestions.length + expectedSelectedButtonIdx;
+ }
+
+ let expectedState = {
+ selectedIndex: expectedSelectedIdx,
+ numSuggestions: expectedSuggestions.length,
+ suggestionAtIndex: expectedSuggestions.map(s => s.str),
+ isFormHistorySuggestionAtIndex:
+ expectedSuggestions.map(s => s.type == "formHistory"),
+
+ tableHidden: expectedSuggestions.length == 0,
+
+ inputValue: expectedInputVal,
+ ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
+ };
+ if (expectedSelectedButtonIdx != undefined) {
+ expectedState.selectedButtonIndex = expectedSelectedButtonIdx;
+ }
+ else if (expectedSelectedIdx < expectedSuggestions.length) {
+ expectedState.selectedButtonIndex = -1;
+ }
+ else {
+ expectedState.selectedButtonIndex = expectedSelectedIdx - expectedSuggestions.length;
+ }
+
+ SimpleTest.isDeeply(actualState, expectedState, "State");
+}
+
+var gMsgMan;
+
+function* promiseTab() {
+ let deferred = Promise.defer();
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ registerCleanupFunction(() => BrowserTestUtils.removeTab(tab));
+ let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
+ tab.linkedBrowser.addEventListener("load", function onLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ gMsgMan = tab.linkedBrowser.messageManager;
+ gMsgMan.sendAsyncMessage("ContentSearch", {
+ type: "AddToWhitelist",
+ data: [pageURL],
+ });
+ promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => {
+ let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
+ gMsgMan.loadFrameScript(jsURL, false);
+ deferred.resolve(msg("init"));
+ });
+ }, true, true);
+ openUILinkIn(pageURL, "current");
+ return deferred.promise;
+}
+
+function promiseMsg(name, type, msgMan) {
+ let deferred = Promise.defer();
+ info("Waiting for " + name + " message " + type + "...");
+ msgMan.addMessageListener(name, function onMsg(msgObj) {
+ info("Received " + name + " message " + msgObj.data.type + "\n");
+ if (msgObj.data.type == type) {
+ msgMan.removeMessageListener(name, onMsg);
+ deferred.resolve(msgObj);
+ }
+ });
+ return deferred.promise;
+}
+
+function setUpEngines() {
+ return Task.spawn(function* () {
+ info("Removing default search engines");
+ let currentEngineName = Services.search.currentEngine.name;
+ let currentEngines = Services.search.getVisibleEngines();
+ info("Adding test search engines");
+ let engine1 = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ yield promiseNewSearchEngine(TEST_ENGINE_2_BASENAME);
+ Services.search.currentEngine = engine1;
+ for (let engine of currentEngines) {
+ Services.search.removeEngine(engine);
+ }
+ registerCleanupFunction(() => {
+ Services.search.restoreDefaultEngines();
+ Services.search.currentEngine = Services.search.getEngineByName(currentEngineName);
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_contextmenu.js b/browser/base/content/test/general/browser_contextmenu.js
new file mode 100644
index 000000000..3e0135848
--- /dev/null
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -0,0 +1,996 @@
+"use strict";
+
+let contextMenu;
+let LOGIN_FILL_ITEMS = [
+ "---", null,
+ "fill-login", null,
+ [
+ "fill-login-no-logins", false,
+ "---", null,
+ "fill-login-saved-passwords", true
+ ], null,
+];
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+let hasContainers = Services.prefs.getBoolPref("privacy.userContext.enabled");
+
+const example_base = "http://example.com/browser/browser/base/content/test/general/";
+const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+
+Services.scriptloader.loadSubScript(chrome_base + "contextmenu_common.js", this);
+
+// Below are test cases for XUL element
+add_task(function* test_xul_text_link_label() {
+ let url = chrome_base + "subtst_contextmenu_xul.xul";
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield test_contextmenu("#test-xul-text-link-label",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "context-searchselect", true
+ ]
+ );
+
+ // Clean up so won't affect HTML element test cases
+ lastElementSelector = null;
+ gBrowser.removeCurrentTab();
+});
+
+// Below are test cases for HTML element
+
+add_task(function* test_setup_html() {
+ let url = example_base + "subtst_contextmenu.html";
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let videoIframe = doc.querySelector("#test-video-in-iframe");
+ let video = videoIframe.contentDocument.querySelector("video");
+ let awaitPause = ContentTaskUtils.waitForEvent(video, "pause");
+ video.pause();
+ yield awaitPause;
+
+ let audioIframe = doc.querySelector("#test-audio-in-iframe");
+ // media documents always use a <video> tag.
+ let audio = audioIframe.contentDocument.querySelector("video");
+ awaitPause = ContentTaskUtils.waitForEvent(audio, "pause");
+ audio.pause();
+ yield awaitPause;
+ });
+});
+
+let plainTextItems;
+add_task(function* test_plaintext() {
+ plainTextItems = ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ];
+ yield test_contextmenu("#test-text", plainTextItems);
+});
+
+add_task(function* test_link() {
+ yield test_contextmenu("#test-link",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "context-searchselect", true
+ ]
+ );
+});
+
+add_task(function* test_mailto() {
+ yield test_contextmenu("#test-mailto",
+ ["context-copyemail", true,
+ "context-searchselect", true
+ ]
+ );
+});
+
+add_task(function* test_image() {
+ yield test_contextmenu("#test-image",
+ ["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true
+ ]
+ );
+});
+
+add_task(function* test_canvas() {
+ yield test_contextmenu("#test-canvas",
+ ["context-viewimage", true,
+ "context-saveimage", true,
+ "context-selectall", true
+ ]
+ );
+});
+
+add_task(function* test_video_ok() {
+ yield test_contextmenu("#test-video-ok",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", true,
+ "context-sendvideo", true,
+ "context-castvideo", null,
+ [], null
+ ]
+ );
+});
+
+add_task(function* test_audio_in_video() {
+ yield test_contextmenu("#test-audio-in-video",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "context-media-showcontrols", true,
+ "---", null,
+ "context-copyaudiourl", true,
+ "---", null,
+ "context-saveaudio", true,
+ "context-sendaudio", true
+ ]
+ );
+});
+
+add_task(function* test_video_bad() {
+ yield test_contextmenu("#test-video-bad",
+ ["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", false,
+ "context-media-playbackrate-100x", false,
+ "context-media-playbackrate-125x", false,
+ "context-media-playbackrate-150x", false,
+ "context-media-playbackrate-200x", false], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", false,
+ "context-sendvideo", true,
+ "context-castvideo", null,
+ [], null
+ ]
+ );
+});
+
+add_task(function* test_video_bad2() {
+ yield test_contextmenu("#test-video-bad2",
+ ["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", false,
+ "context-media-playbackrate-100x", false,
+ "context-media-playbackrate-125x", false,
+ "context-media-playbackrate-150x", false,
+ "context-media-playbackrate-200x", false], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", false,
+ "context-copyvideourl", false,
+ "---", null,
+ "context-savevideo", false,
+ "context-video-saveimage", false,
+ "context-sendvideo", false,
+ "context-castvideo", null,
+ [], null
+ ]
+ );
+});
+
+add_task(function* test_iframe() {
+ yield test_contextmenu("#test-iframe",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ]
+ );
+});
+
+add_task(function* test_video_in_iframe() {
+ yield test_contextmenu("#test-video-in-iframe",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "context-media-hidecontrols", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", true,
+ "context-sendvideo", true,
+ "context-castvideo", null,
+ [], null,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null]
+ );
+});
+
+add_task(function* test_audio_in_iframe() {
+ yield test_contextmenu("#test-audio-in-iframe",
+ ["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-125x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-loop", true,
+ "---", null,
+ "context-copyaudiourl", true,
+ "---", null,
+ "context-saveaudio", true,
+ "context-sendaudio", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null]
+ );
+});
+
+add_task(function* test_image_in_iframe() {
+ yield test_contextmenu("#test-image-in-iframe",
+ ["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null]
+ );
+});
+
+add_task(function* test_textarea() {
+ // Disabled since this is seeing spell-check-enabled
+ // instead of spell-add-dictionaries-main
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null,
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-add-dictionaries-main", true,
+ ],
+ {
+ skipFocusChange: true,
+ }
+ );
+ */
+});
+
+add_task(function* test_textarea_spellcheck() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["*chubbiness", true, // spelling suggestion
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {
+ waitForSpellCheck: true,
+ offsetX: 6,
+ offsetY: 6,
+ postCheckContextMenuFn() {
+ document.getElementById("spell-add-to-dictionary").doCommand();
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_plaintext2() {
+ yield test_contextmenu("#test-text", plainTextItems);
+});
+
+add_task(function* test_undo_add_to_dictionary() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-textarea",
+ ["spell-undo-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {
+ waitForSpellCheck: true,
+ postCheckContextMenuFn() {
+ document.getElementById("spell-undo-add-to-dictionary")
+ .doCommand();
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_contenteditable() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-contenteditable",
+ ["spell-no-suggestions", false,
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ],
+ {waitForSpellCheck: true}
+ );
+ */
+});
+
+add_task(function* test_copylinkcommand() {
+ yield test_contextmenu("#test-link", null, {
+ postCheckContextMenuFn: function*() {
+ document.commandDispatcher
+ .getControllerForCommand("cmd_copyLink")
+ .doCommand("cmd_copyLink");
+
+ // The easiest way to check the clipboard is to paste the contents
+ // into a textbox.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("test-input");
+ input.focus();
+ input.value = "";
+ });
+ document.commandDispatcher
+ .getControllerForCommand("cmd_paste")
+ .doCommand("cmd_paste");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("test-input");
+ Assert.equal(input.value, "http://mozilla.com/", "paste for command cmd_paste");
+ });
+ }
+ });
+});
+
+add_task(function* test_pagemenu() {
+ yield test_contextmenu("#test-pagemenu",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "+Plain item", {type: "", icon: "", checked: false, disabled: false},
+ "+Disabled item", {type: "", icon: "", checked: false, disabled: true},
+ "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "---", null,
+ "+Radio1", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false},
+ "+Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "generated-submenu-1", true,
+ ["+Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {postCheckContextMenuFn: function*() {
+ let item = contextMenu.getElementsByAttribute("generateditemid", "1")[0];
+ ok(item, "Got generated XUL menu item");
+ item.doCommand();
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let pagemenu = content.document.getElementById("test-pagemenu");
+ Assert.ok(!pagemenu.hasAttribute("hopeless"), "attribute got removed");
+ });
+ }
+ });
+});
+
+add_task(function* test_dom_full_screen() {
+ yield test_contextmenu("#test-dom-full-screen",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-leave-dom-fullscreen", true,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {
+ shiftkey: true,
+ *preCheckContextMenuFn() {
+ yield pushPrefs(["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"])
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ let full_screen_element = doc.getElementById("test-dom-full-screen");
+ let awaitFullScreenChange =
+ ContentTaskUtils.waitForEvent(win, "fullscreenchange");
+ full_screen_element.requestFullscreen();
+ yield awaitFullScreenChange;
+ });
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let win = content.document.defaultView;
+ let awaitFullScreenChange =
+ ContentTaskUtils.waitForEvent(win, "fullscreenchange");
+ content.document.exitFullscreen();
+ yield awaitFullScreenChange;
+ });
+ }
+ }
+ );
+});
+
+add_task(function* test_pagemenu2() {
+ yield test_contextmenu("#test-text",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {shiftkey: true}
+ );
+});
+
+add_task(function* test_select_text() {
+ yield test_contextmenu("#test-select-text",
+ ["context-copy", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-viewpartialsource-selection", true
+ ],
+ {
+ offsetX: 6,
+ offsetY: 6,
+ *preCheckContextMenuFn() {
+ yield selectText("#test-select-text");
+ }
+ }
+ );
+});
+
+add_task(function* test_select_text_link() {
+ yield test_contextmenu("#test-select-text-link",
+ ["context-openlinkincurrent", true,
+ "context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copy", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-viewpartialsource-selection", true
+ ],
+ {
+ offsetX: 6,
+ offsetY: 6,
+ *preCheckContextMenuFn() {
+ yield selectText("#test-select-text-link");
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let win = content.document.defaultView;
+ win.getSelection().removeAllRanges();
+ });
+ }
+ }
+ );
+});
+
+add_task(function* test_imagelink() {
+ yield test_contextmenu("#test-image-link",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "---", null,
+ "context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true
+ ]
+ );
+});
+
+add_task(function* test_select_input_text() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-select-input-text",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "context-searchselect", true,
+ "---", null,
+ "spell-check-enabled", true
+ ].concat(LOGIN_FILL_ITEMS),
+ {
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let element = doc.querySelector("#test-select-input-text");
+ element.select();
+ });
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_select_input_text_password() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-select-input-text-type-password",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ // spell checker is shown on input[type="password"] on this testcase
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(LOGIN_FILL_ITEMS),
+ {
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let element = doc.querySelector("#test-select-input-text-type-password");
+ element.select();
+ });
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let win = content.document.defaultView;
+ win.getSelection().removeAllRanges();
+ });
+ }
+ }
+ );
+ */
+});
+
+add_task(function* test_click_to_play_blocked_plugin() {
+ yield test_contextmenu("#test-plugin",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-ctp-play", true,
+ "context-ctp-hide", true,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ],
+ {
+ preCheckContextMenuFn: function*() {
+ pushPrefs(["plugins.click_to_play", true]);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+ },
+ postCheckContextMenuFn: function*() {
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ }
+ }
+ );
+});
+
+add_task(function* test_longdesc() {
+ yield test_contextmenu("#test-longdesc",
+ ["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true,
+ "context-viewimagedesc", true
+ ]
+ );
+});
+
+add_task(function* test_srcdoc() {
+ yield test_contextmenu("#test-srcdoc",
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "frame", null,
+ ["context-reloadframe", true,
+ "---", null,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ]
+ );
+});
+
+add_task(function* test_input_spell_false() {
+ todo(false, "spell checker tests are failing, bug 1246296");
+ return;
+
+ /*
+ yield test_contextmenu("#test-contenteditable-spellcheck-false",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ ]
+ );
+ */
+});
+
+const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+
+add_task(function* test_plaintext_sendpagetodevice() {
+ if (!gFxAccounts.sendTabToDeviceEnabled) {
+ return;
+ }
+ const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
+
+ let plainTextItemsWithSendPage =
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-sendpagetodevice", true,
+ ["*Foo", true,
+ "*Bar", true,
+ "---", null,
+ "*All Devices", true], null,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ];
+ yield test_contextmenu("#test-text", plainTextItemsWithSendPage, {
+ *onContextMenuShown() {
+ yield openMenuItemSubmenu("context-sendpagetodevice");
+ }
+ });
+
+ restoreRemoteClients(oldGetter);
+});
+
+add_task(function* test_link_sendlinktodevice() {
+ if (!gFxAccounts.sendTabToDeviceEnabled) {
+ return;
+ }
+ const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
+
+ yield test_contextmenu("#test-link",
+ ["context-openlinkintab", true,
+ ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
+ // We need a blank entry here because the containers submenu is
+ // dynamically generated with no ids.
+ ...(hasContainers ? ["", null] : []),
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ ...(hasPocket ? ["context-savelinktopocket", true] : []),
+ "context-copylink", true,
+ "context-searchselect", true,
+ "---", null,
+ "context-sendlinktodevice", true,
+ ["*Foo", true,
+ "*Bar", true,
+ "---", null,
+ "*All Devices", true], null,
+ ],
+ {
+ *onContextMenuShown() {
+ yield openMenuItemSubmenu("context-sendlinktodevice");
+ }
+ });
+
+ restoreRemoteClients(oldGetter);
+});
+
+add_task(function* test_cleanup_html() {
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * Selects the text of the element that matches the provided `selector`
+ *
+ * @param {String} selector
+ * A selector passed to querySelector to find
+ * the element that will be referenced.
+ */
+function* selectText(selector) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, selector, function*(contentSelector) {
+ info(`Selecting text of ${contentSelector}`);
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.getSelection().removeAllRanges();
+ let div = doc.createRange();
+ let element = doc.querySelector(contentSelector);
+ Assert.ok(element, "Found element to select text from");
+ div.setStartBefore(element);
+ div.setEndAfter(element);
+ win.getSelection().addRange(div);
+ });
+}
diff --git a/browser/base/content/test/general/browser_contextmenu_childprocess.js b/browser/base/content/test/general/browser_contextmenu_childprocess.js
new file mode 100644
index 000000000..3d52be9ab
--- /dev/null
+++ b/browser/base/content/test/general/browser_contextmenu_childprocess.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const gBaseURL = "https://example.com/browser/browser/base/content/test/general/";
+
+add_task(function *() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, gBaseURL + "subtst_contextmenu.html");
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+
+ // Get the point of the element with the page menu (test-pagemenu) and
+ // synthesize a right mouse click there.
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse("#test-pagemenu", 5, 5, { type : "contextmenu", button : 2 }, tab.linkedBrowser);
+ yield popupShownPromise;
+
+ checkMenu(contextMenu);
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ yield popupHiddenPromise;
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+function checkItems(menuitem, arr)
+{
+ for (let i = 0; i < arr.length; i += 2) {
+ let str = arr[i];
+ let details = arr[i + 1];
+ if (str == "---") {
+ is(menuitem.localName, "menuseparator", "menuseparator");
+ }
+ else if ("children" in details) {
+ is(menuitem.localName, "menu", "submenu");
+ is(menuitem.getAttribute("label"), str, str + " label");
+ checkItems(menuitem.firstChild.firstChild, details.children);
+ }
+ else {
+ is(menuitem.localName, "menuitem", str + " menuitem");
+
+ is(menuitem.getAttribute("label"), str, str + " label");
+ is(menuitem.getAttribute("type"), details.type, str + " type");
+ is(menuitem.getAttribute("image"), details.icon ? gBaseURL + details.icon : "", str + " icon");
+
+ if (details.checked)
+ is(menuitem.getAttribute("checked"), "true", str + " checked");
+ else
+ ok(!menuitem.hasAttribute("checked"), str + " checked");
+
+ if (details.disabled)
+ is(menuitem.getAttribute("disabled"), "true", str + " disabled");
+ else
+ ok(!menuitem.hasAttribute("disabled"), str + " disabled");
+ }
+
+ menuitem = menuitem.nextSibling;
+ }
+}
+
+function checkMenu(contextMenu)
+{
+ let items = [ "Plain item", {type: "", icon: "", checked: false, disabled: false},
+ "Disabled item", {type: "", icon: "", checked: false, disabled: true},
+ "Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "---", null,
+ "Radio1", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "Radio2", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false},
+ "Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Submenu", { children:
+ ["Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}] }
+ ];
+ checkItems(contextMenu.childNodes[2], items);
+}
diff --git a/browser/base/content/test/general/browser_contextmenu_input.js b/browser/base/content/test/general/browser_contextmenu_input.js
new file mode 100644
index 000000000..cfc7b7529
--- /dev/null
+++ b/browser/base/content/test/general/browser_contextmenu_input.js
@@ -0,0 +1,243 @@
+"use strict";
+
+let contextMenu;
+let hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+
+add_task(function* test_setup() {
+ const example_base = "http://example.com/browser/browser/base/content/test/general/";
+ const url = example_base + "subtst_contextmenu_input.html";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
+ const contextmenu_common = chrome_base + "contextmenu_common.js";
+ Services.scriptloader.loadSubScript(contextmenu_common, this);
+});
+
+add_task(function* test_text_input() {
+ yield test_contextmenu("#input_text",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", false,
+ "---", null,
+ "spell-check-enabled", true]);
+});
+
+add_task(function* test_text_input_spellcheck() {
+ yield test_contextmenu("#input_spellcheck_no_value",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", false,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null],
+ {
+ waitForSpellCheck: true,
+ // Need to dynamically add/remove the "password" type or LoginManager
+ // will think that the form inputs on the page are part of a login
+ // and will add fill-login context menu items.
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("input_spellcheck_no_value");
+ input.setAttribute("spellcheck", "true");
+ input.clientTop; // force layout flush
+ });
+ },
+ }
+ );
+});
+
+add_task(function* test_text_input_spellcheckwrong() {
+ yield test_contextmenu("#input_spellcheck_incorrect",
+ ["*prodigality", true, // spelling suggestion
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null],
+ {waitForSpellCheck: true}
+ );
+});
+
+add_task(function* test_text_input_spellcheckcorrect() {
+ yield test_contextmenu("#input_spellcheck_correct",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null],
+ {waitForSpellCheck: true}
+ );
+});
+
+add_task(function* test_text_input_disabled() {
+ yield test_contextmenu("#input_disabled",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true],
+ {skipFocusChange: true}
+ );
+});
+
+add_task(function* test_password_input() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ yield test_contextmenu("#input_password",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null,
+ "---", null,
+ "fill-login", null,
+ ["fill-login-no-logins", false,
+ "---", null,
+ "fill-login-saved-passwords", true], null],
+ {
+ skipFocusChange: true,
+ // Need to dynamically add/remove the "password" type or LoginManager
+ // will think that the form inputs on the page are part of a login
+ // and will add fill-login context menu items.
+ *preCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("input_password");
+ input.type = "password";
+ input.clientTop; // force layout flush
+ });
+ },
+ *postCheckContextMenuFn() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ let doc = content.document;
+ let input = doc.getElementById("input_password");
+ input.type = "text";
+ input.clientTop; // force layout flush
+ });
+ },
+ }
+ );
+});
+
+add_task(function* test_tel_email_url_number_input() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ for (let selector of ["#input_email", "#input_url", "#input_tel", "#input_number"]) {
+ yield test_contextmenu(selector,
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null],
+ {skipFocusChange: true}
+ );
+ }
+});
+
+add_task(function* test_date_time_color_range_month_week_datetimelocal_input() {
+ for (let selector of ["#input_date", "#input_time", "#input_color",
+ "#input_range", "#input_month", "#input_week",
+ "#input_datetime-local"]) {
+ yield test_contextmenu(selector,
+ ["context-navigation", null,
+ ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "context-bookmarkpage", true], null,
+ "---", null,
+ "context-savepage", true,
+ ...(hasPocket ? ["context-pocket", true] : []),
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", null,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true],
+ {skipFocusChange: true}
+ );
+ }
+});
+
+add_task(function* test_search_input() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ yield test_contextmenu("#input_search",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null,
+ "---", null,
+ "spell-check-enabled", true],
+ {skipFocusChange: true}
+ );
+});
+
+add_task(function* test_text_input_readonly() {
+ todo(false, "context-selectall is enabled on osx-e10s, and windows when" +
+ " it should be disabled");
+ todo(false, "spell-check should not be enabled for input[readonly]. see bug 1246296");
+ yield test_contextmenu("#input_readonly",
+ ["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", null],
+ {skipFocusChange: true}
+ );
+});
+
+add_task(function* test_cleanup() {
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js b/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js
new file mode 100644
index 000000000..00a06f53e
--- /dev/null
+++ b/browser/base/content/test/general/browser_csp_block_all_mixedcontent.js
@@ -0,0 +1,55 @@
+/*
+ * Description of the Test:
+ * We load an https page which uses a CSP including block-all-mixed-content.
+ * The page tries to load a script over http. We make sure the UI is not
+ * influenced when blocking the mixed content. In particular the page
+ * should still appear fully encrypted with a green lock.
+ */
+
+const PRE_PATH = "https://example.com/browser/browser/base/content/test/general/";
+var gTestBrowser = null;
+
+// ------------------------------------------------------
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+// ------------------------------------------------------
+function verifyUInotDegraded() {
+ // make sure that not mixed content is loaded and also not blocked
+ assertMixedContentBlockingState(
+ gTestBrowser,
+ { activeLoaded: false,
+ activeBlocked: false,
+ passiveLoaded: false
+ }
+ );
+ // clean up and finish test
+ cleanUpAfterTests();
+}
+
+// ------------------------------------------------------
+function runTests() {
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop();
+
+ // Starting the test
+ BrowserTestUtils.browserLoaded(gTestBrowser).then(verifyUInotDegraded);
+ var url = PRE_PATH + "file_csp_block_all_mixedcontent.html";
+ gTestBrowser.loadURI(url);
+}
+
+// ------------------------------------------------------
+function test() {
+ // Performing async calls, e.g. 'onload', we have to wait till all of them finished
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ { 'set': [["security.mixed_content.block_active_content", true]] },
+ function() { runTests(); }
+ );
+}
diff --git a/browser/base/content/test/general/browser_ctrlTab.js b/browser/base/content/test/general/browser_ctrlTab.js
new file mode 100644
index 000000000..d16aaeca4
--- /dev/null
+++ b/browser/base/content/test/general/browser_ctrlTab.js
@@ -0,0 +1,185 @@
+add_task(function* () {
+ gPrefService.setBoolPref("browser.ctrlTab.previews", true);
+
+ gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.addTab();
+
+ checkTabs(4);
+
+ yield ctrlTabTest([2], 1, 0);
+ yield ctrlTabTest([2, 3, 1], 2, 2);
+ yield ctrlTabTest([], 4, 2);
+
+ {
+ let selectedIndex = gBrowser.tabContainer.selectedIndex;
+ yield pressCtrlTab();
+ yield pressCtrlTab(true);
+ yield releaseCtrl();
+ is(gBrowser.tabContainer.selectedIndex, selectedIndex,
+ "Ctrl+Tab -> Ctrl+Shift+Tab keeps the selected tab");
+ }
+
+ { // test for bug 445369
+ let tabs = gBrowser.tabs.length;
+ yield pressCtrlTab();
+ yield synthesizeCtrlW();
+ is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes one tab");
+ yield releaseCtrl();
+ }
+
+ { // test for bug 667314
+ let tabs = gBrowser.tabs.length;
+ yield pressCtrlTab();
+ yield pressCtrlTab(true);
+ yield synthesizeCtrlW();
+ is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes the selected tab");
+ yield releaseCtrl();
+ }
+
+ gBrowser.addTab();
+ checkTabs(3);
+ yield ctrlTabTest([2, 1, 0], 7, 1);
+
+ { // test for bug 1292049
+ let tabToClose = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:buildconfig");
+ checkTabs(4);
+ selectTabs([0, 1, 2, 3]);
+
+ yield BrowserTestUtils.removeTab(tabToClose);
+ checkTabs(3);
+ undoCloseTab();
+ checkTabs(4);
+ is(gBrowser.tabContainer.selectedIndex, 3, "tab is selected after closing and restoring it");
+
+ yield ctrlTabTest([], 1, 2);
+ }
+
+ { // test for bug 445369
+ checkTabs(4);
+ selectTabs([1, 2, 0]);
+
+ let selectedTab = gBrowser.selectedTab;
+ let tabToRemove = gBrowser.tabs[1];
+
+ yield pressCtrlTab();
+ yield pressCtrlTab();
+ yield synthesizeCtrlW();
+ ok(!tabToRemove.parentNode,
+ "Ctrl+Tab*2 -> Ctrl+W removes the second most recently selected tab");
+
+ yield pressCtrlTab(true);
+ yield pressCtrlTab(true);
+ yield releaseCtrl();
+ ok(selectedTab.selected,
+ "Ctrl+Tab*2 -> Ctrl+W -> Ctrl+Shift+Tab*2 keeps the selected tab");
+ }
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ checkTabs(2);
+
+ yield ctrlTabTest([1], 1, 0);
+
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ checkTabs(1);
+
+ { // test for bug 445768
+ let focusedWindow = document.commandDispatcher.focusedWindow;
+ let eventConsumed = true;
+ let detectKeyEvent = function (event) {
+ eventConsumed = event.defaultPrevented;
+ };
+ document.addEventListener("keypress", detectKeyEvent, false);
+ yield pressCtrlTab();
+ document.removeEventListener("keypress", detectKeyEvent, false);
+ ok(eventConsumed, "Ctrl+Tab consumed by the tabbed browser if one tab is open");
+ is(focusedWindow, document.commandDispatcher.focusedWindow,
+ "Ctrl+Tab doesn't change focus if one tab is open");
+ }
+
+ // cleanup
+ if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
+ gPrefService.clearUserPref("browser.ctrlTab.previews");
+
+ /* private utility functions */
+
+ function* pressCtrlTab(aShiftKey) {
+ let promise;
+ if (!isOpen() && canOpen()) {
+ promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown");
+ } else {
+ promise = BrowserTestUtils.waitForEvent(document, "keyup");
+ }
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
+ return promise;
+ }
+
+ function* releaseCtrl() {
+ let promise;
+ if (isOpen()) {
+ promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popuphidden");
+ } else {
+ promise = BrowserTestUtils.waitForEvent(document, "keyup");
+ }
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
+ return promise;
+ }
+
+ function* synthesizeCtrlW() {
+ let promise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose");
+ EventUtils.synthesizeKey("w", { ctrlKey: true });
+ return promise;
+ }
+
+ function isOpen() {
+ return ctrlTab.isOpen;
+ }
+
+ function canOpen() {
+ return gPrefService.getBoolPref("browser.ctrlTab.previews") && gBrowser.tabs.length > 2;
+ }
+
+ function checkTabs(aTabs) {
+ is(gBrowser.tabs.length, aTabs, "number of open tabs should be " + aTabs);
+ }
+
+ function selectTabs(tabs) {
+ tabs.forEach(function (index) {
+ gBrowser.selectedTab = gBrowser.tabs[index];
+ });
+ }
+
+ function* ctrlTabTest(tabsToSelect, tabTimes, expectedIndex) {
+ selectTabs(tabsToSelect);
+
+ var indexStart = gBrowser.tabContainer.selectedIndex;
+ var tabCount = gBrowser.tabs.length;
+ var normalized = tabTimes % tabCount;
+ var where = normalized == 1 ? "back to the previously selected tab" :
+ normalized + " tabs back in most-recently-selected order";
+
+ for (let i = 0; i < tabTimes; i++) {
+ yield pressCtrlTab();
+
+ if (tabCount > 2)
+ is(gBrowser.tabContainer.selectedIndex, indexStart,
+ "Selected tab doesn't change while tabbing");
+ }
+
+ if (tabCount > 2) {
+ ok(isOpen(),
+ "With " + tabCount + " tabs open, Ctrl+Tab opens the preview panel");
+
+ yield releaseCtrl();
+
+ ok(!isOpen(),
+ "Releasing Ctrl closes the preview panel");
+ } else {
+ ok(!isOpen(),
+ "With " + tabCount + " tabs open, Ctrl+Tab doesn't open the preview panel");
+ }
+
+ is(gBrowser.tabContainer.selectedIndex, expectedIndex,
+ "With "+ tabCount +" tabs open and tab " + indexStart
+ + " selected, Ctrl+Tab*" + tabTimes + " goes " + where);
+ }
+});
diff --git a/browser/base/content/test/general/browser_datachoices_notification.js b/browser/base/content/test/general/browser_datachoices_notification.js
new file mode 100644
index 000000000..360728b4c
--- /dev/null
+++ b/browser/base/content/test/general/browser_datachoices_notification.js
@@ -0,0 +1,221 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Pass an empty scope object to the import to prevent "leaked window property"
+// errors in tests.
+var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+var TelemetryReportingPolicy =
+ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicy;
+
+const PREF_BRANCH = "datareporting.policy.";
+const PREF_BYPASS_NOTIFICATION = PREF_BRANCH + "dataSubmissionPolicyBypassNotification";
+const PREF_CURRENT_POLICY_VERSION = PREF_BRANCH + "currentPolicyVersion";
+const PREF_ACCEPTED_POLICY_VERSION = PREF_BRANCH + "dataSubmissionPolicyAcceptedVersion";
+const PREF_ACCEPTED_POLICY_DATE = PREF_BRANCH + "dataSubmissionPolicyNotifiedTime";
+
+const TEST_POLICY_VERSION = 37;
+
+function fakeShowPolicyTimeout(set, clear) {
+ let reportingPolicy =
+ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).Policy;
+ reportingPolicy.setShowInfobarTimeout = set;
+ reportingPolicy.clearShowInfobarTimeout = clear;
+}
+
+function sendSessionRestoredNotification() {
+ let reportingPolicyImpl =
+ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", {}).TelemetryReportingPolicyImpl;
+ reportingPolicyImpl.observe(null, "sessionstore-windows-restored", null);
+}
+
+/**
+ * Wait for a tick.
+ */
+function promiseNextTick() {
+ return new Promise(resolve => executeSoon(resolve));
+}
+
+/**
+ * Wait for a notification to be shown in a notification box.
+ * @param {Object} aNotificationBox The notification box.
+ * @return {Promise} Resolved when the notification is displayed.
+ */
+function promiseWaitForAlertActive(aNotificationBox) {
+ let deferred = PromiseUtils.defer();
+ aNotificationBox.addEventListener("AlertActive", function onActive() {
+ aNotificationBox.removeEventListener("AlertActive", onActive, true);
+ deferred.resolve();
+ });
+ return deferred.promise;
+}
+
+/**
+ * Wait for a notification to be closed.
+ * @param {Object} aNotification The notification.
+ * @return {Promise} Resolved when the notification is closed.
+ */
+function promiseWaitForNotificationClose(aNotification) {
+ let deferred = PromiseUtils.defer();
+ waitForNotificationClose(aNotification, deferred.resolve);
+ return deferred.promise;
+}
+
+function triggerInfoBar(expectedTimeoutMs) {
+ let showInfobarCallback = null;
+ let timeoutMs = null;
+ fakeShowPolicyTimeout((callback, timeout) => {
+ showInfobarCallback = callback;
+ timeoutMs = timeout;
+ }, () => {});
+ sendSessionRestoredNotification();
+ Assert.ok(!!showInfobarCallback, "Must have a timer callback.");
+ if (expectedTimeoutMs !== undefined) {
+ Assert.equal(timeoutMs, expectedTimeoutMs, "Timeout should match");
+ }
+ showInfobarCallback();
+}
+
+var checkInfobarButton = Task.async(function* (aNotification) {
+ // Check that the button on the data choices infobar does the right thing.
+ let buttons = aNotification.getElementsByTagName("button");
+ Assert.equal(buttons.length, 1, "There is 1 button in the data reporting notification.");
+ let button = buttons[0];
+
+ // Add an observer to ensure the "advanced" pane opened (but don't bother
+ // closing it - we close the entire window when done.)
+ let paneLoadedPromise = promiseTopicObserved("advanced-pane-loaded");
+
+ // Click on the button.
+ button.click();
+
+ // Wait for the preferences panel to open.
+ yield paneLoadedPromise;
+ yield promiseNextTick();
+});
+
+add_task(function* setup() {
+ const bypassNotification = Preferences.get(PREF_BYPASS_NOTIFICATION, true);
+ const currentPolicyVersion = Preferences.get(PREF_CURRENT_POLICY_VERSION, 1);
+
+ // Register a cleanup function to reset our preferences.
+ registerCleanupFunction(() => {
+ Preferences.set(PREF_BYPASS_NOTIFICATION, bypassNotification);
+ Preferences.set(PREF_CURRENT_POLICY_VERSION, currentPolicyVersion);
+
+ return closeAllNotifications();
+ });
+
+ // Don't skip the infobar visualisation.
+ Preferences.set(PREF_BYPASS_NOTIFICATION, false);
+ // Set the current policy version.
+ Preferences.set(PREF_CURRENT_POLICY_VERSION, TEST_POLICY_VERSION);
+});
+
+function clearAcceptedPolicy() {
+ // Reset the accepted policy.
+ Preferences.reset(PREF_ACCEPTED_POLICY_VERSION);
+ Preferences.reset(PREF_ACCEPTED_POLICY_DATE);
+}
+
+add_task(function* test_single_window() {
+ clearAcceptedPolicy();
+
+ // Close all the notifications, then try to trigger the data choices infobar.
+ yield closeAllNotifications();
+
+ let notificationBox = document.getElementById("global-notificationbox");
+
+ // Make sure that we have a coherent initial state.
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), 0,
+ "No version should be set on init.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0), 0,
+ "No date should be set on init.");
+ Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(),
+ "User not notified about datareporting policy.");
+
+ let alertShownPromise = promiseWaitForAlertActive(notificationBox);
+ Assert.ok(!TelemetryReportingPolicy.canUpload(),
+ "User should not be allowed to upload.");
+
+ // Wait for the infobar to be displayed.
+ triggerInfoBar(10 * 1000);
+ yield alertShownPromise;
+
+ Assert.equal(notificationBox.allNotifications.length, 1, "Notification Displayed.");
+ Assert.ok(TelemetryReportingPolicy.canUpload(), "User should be allowed to upload now.");
+
+ yield promiseNextTick();
+ let promiseClosed = promiseWaitForNotificationClose(notificationBox.currentNotification);
+ yield checkInfobarButton(notificationBox.currentNotification);
+ yield promiseClosed;
+
+ Assert.equal(notificationBox.allNotifications.length, 0, "No notifications remain.");
+
+ // Check that we are still clear to upload and that the policy data is saved.
+ Assert.ok(TelemetryReportingPolicy.canUpload());
+ Assert.equal(TelemetryReportingPolicy.testIsUserNotified(), true,
+ "User notified about datareporting policy.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), TEST_POLICY_VERSION,
+ "Version pref set.");
+ Assert.greater(parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10), -1,
+ "Date pref set.");
+});
+
+add_task(function* test_multiple_windows() {
+ clearAcceptedPolicy();
+
+ // Close all the notifications, then try to trigger the data choices infobar.
+ yield closeAllNotifications();
+
+ // Ensure we see the notification on all windows and that action on one window
+ // results in dismiss on every window.
+ let otherWindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ // Get the notification box for both windows.
+ let notificationBoxes = [
+ document.getElementById("global-notificationbox"),
+ otherWindow.document.getElementById("global-notificationbox")
+ ];
+
+ Assert.ok(notificationBoxes[1], "2nd window has a global notification box.");
+
+ // Make sure that we have a coherent initial state.
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), 0, "No version should be set on init.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_DATE, 0), 0, "No date should be set on init.");
+ Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(), "User not notified about datareporting policy.");
+
+ let showAlertPromises = [
+ promiseWaitForAlertActive(notificationBoxes[0]),
+ promiseWaitForAlertActive(notificationBoxes[1])
+ ];
+
+ Assert.ok(!TelemetryReportingPolicy.canUpload(),
+ "User should not be allowed to upload.");
+
+ // Wait for the infobars.
+ triggerInfoBar(10 * 1000);
+ yield Promise.all(showAlertPromises);
+
+ // Both notification were displayed. Close one and check that both gets closed.
+ let closeAlertPromises = [
+ promiseWaitForNotificationClose(notificationBoxes[0].currentNotification),
+ promiseWaitForNotificationClose(notificationBoxes[1].currentNotification)
+ ];
+ notificationBoxes[0].currentNotification.close();
+ yield Promise.all(closeAlertPromises);
+
+ // Close the second window we opened.
+ yield BrowserTestUtils.closeWindow(otherWindow);
+
+ // Check that we are clear to upload and that the policy data us saved.
+ Assert.ok(TelemetryReportingPolicy.canUpload(), "User should be allowed to upload now.");
+ Assert.equal(TelemetryReportingPolicy.testIsUserNotified(), true,
+ "User notified about datareporting policy.");
+ Assert.equal(Preferences.get(PREF_ACCEPTED_POLICY_VERSION, 0), TEST_POLICY_VERSION,
+ "Version pref set.");
+ Assert.greater(parseInt(Preferences.get(PREF_ACCEPTED_POLICY_DATE, null), 10), -1,
+ "Date pref set.");
+});
diff --git a/browser/base/content/test/general/browser_decoderDoctor.js b/browser/base/content/test/general/browser_decoderDoctor.js
new file mode 100644
index 000000000..a37972160
--- /dev/null
+++ b/browser/base/content/test/general/browser_decoderDoctor.js
@@ -0,0 +1,122 @@
+"use strict";
+
+function* test_decoder_doctor_notification(type, notificationMessage, options) {
+ yield BrowserTestUtils.withNewTab({ gBrowser }, function*(browser) {
+ let awaitNotificationBar =
+ BrowserTestUtils.waitForNotificationBar(gBrowser, browser, "decoder-doctor-notification");
+
+ yield ContentTask.spawn(browser, type, function*(aType) {
+ Services.obs.notifyObservers(content.window,
+ "decoder-doctor-notification",
+ JSON.stringify({type: aType,
+ isSolved: false,
+ decoderDoctorReportId: "test",
+ formats: "test"}));
+ });
+
+ let notification;
+ try {
+ notification = yield awaitNotificationBar;
+ } catch (ex) {
+ ok(false, ex);
+ return;
+ }
+ ok(notification, "Got decoder-doctor-notification notification");
+
+ is(notification.getAttribute("label"), notificationMessage,
+ "notification message should match expectation");
+ let button = notification.childNodes[0];
+ if (options && options.noLearnMoreButton) {
+ ok(!button, "There should not be a Learn More button");
+ return;
+ }
+
+ is(button.getAttribute("label"), gNavigatorBundle.getString("decoder.noCodecs.button"),
+ "notification button should be 'Learn more'");
+ is(button.getAttribute("accesskey"), gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
+ "notification button should have accesskey");
+
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ let url = baseURL + ((options && options.sumo) ||
+ "fix-video-audio-problems-firefox-windows");
+ let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ button.click();
+ let sumoTab = yield awaitNewTab;
+ yield BrowserTestUtils.removeTab(sumoTab);
+ });
+}
+
+add_task(function* test_adobe_cdm_not_found() {
+ // This is only sent on Windows.
+ if (AppConstants.platform != "win") {
+ return;
+ }
+
+ let message;
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ message = gNavigatorBundle.getFormattedString("emeNotifications.drmContentDisabled.message", [""]);
+ } else {
+ message = gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+
+ yield test_decoder_doctor_notification("adobe-cdm-not-found", message);
+});
+
+add_task(function* test_adobe_cdm_not_activated() {
+ // This is only sent on Windows.
+ if (AppConstants.platform != "win") {
+ return;
+ }
+
+ let message;
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ message = gNavigatorBundle.getString("decoder.noCodecsXP.message");
+ } else {
+ message = gNavigatorBundle.getString("decoder.noCodecs.message");
+ }
+
+ yield test_decoder_doctor_notification("adobe-cdm-not-activated", message);
+});
+
+add_task(function* test_platform_decoder_not_found() {
+ // Not sent on Windows XP.
+ if (AppConstants.isPlatformAndVersionAtMost("win", "5.9")) {
+ return;
+ }
+
+ let message;
+ let isLinux = AppConstants.platform == "linux";
+ if (isLinux) {
+ message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
+ } else {
+ message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
+ }
+
+ yield test_decoder_doctor_notification("platform-decoder-not-found",
+ message,
+ {noLearnMoreButton: isLinux});
+});
+
+add_task(function* test_cannot_initialize_pulseaudio() {
+ // This is only sent on Linux.
+ if (AppConstants.platform != "linux") {
+ return;
+ }
+
+ let message = gNavigatorBundle.getString("decoder.noPulseAudio.message");
+ yield test_decoder_doctor_notification("cannot-initialize-pulseaudio",
+ message,
+ {sumo: "fix-common-audio-and-video-issues"});
+});
+
+add_task(function* test_unsupported_libavcodec() {
+ // This is only sent on Linux.
+ if (AppConstants.platform != "linux") {
+ return;
+ }
+
+ let message = gNavigatorBundle.getString("decoder.unsupportedLibavcodec.message");
+ yield test_decoder_doctor_notification("unsupported-libavcodec",
+ message,
+ {noLearnMoreButton: true});
+});
diff --git a/browser/base/content/test/general/browser_devedition.js b/browser/base/content/test/general/browser_devedition.js
new file mode 100644
index 000000000..06ee42e7e
--- /dev/null
+++ b/browser/base/content/test/general/browser_devedition.js
@@ -0,0 +1,129 @@
+/*
+ * Testing changes for Developer Edition theme.
+ * A special stylesheet should be added to the browser.xul document
+ * when the firefox-devedition@mozilla.org lightweight theme
+ * is applied.
+ */
+
+const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes";
+const PREF_DEVTOOLS_THEME = "devtools.theme";
+const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
+
+LightweightThemeManager.clearBuiltInThemes();
+LightweightThemeManager.addBuiltInTheme(dummyLightweightTheme("firefox-devedition@mozilla.org"));
+
+registerCleanupFunction(() => {
+ // Set preferences back to their original values
+ LightweightThemeManager.currentTheme = null;
+ Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME);
+ Services.prefs.clearUserPref(PREF_LWTHEME_USED_THEMES);
+
+ LightweightThemeManager.currentTheme = null;
+ LightweightThemeManager.clearBuiltInThemes();
+});
+
+add_task(function* startTests() {
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
+
+ info ("Setting the current theme to null");
+ LightweightThemeManager.currentTheme = null;
+ ok (!DevEdition.isStyleSheetEnabled, "There is no devedition style sheet when no lw theme is applied.");
+
+ info ("Adding a lightweight theme.");
+ LightweightThemeManager.currentTheme = dummyLightweightTheme("preview0");
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been removed when a lightweight theme is applied.");
+
+ info ("Applying the devedition lightweight theme.");
+ let onAttributeAdded = waitForBrightTitlebarAttribute();
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been added when the devedition lightweight theme is applied");
+ yield onAttributeAdded;
+ is (document.documentElement.getAttribute("brighttitlebarforeground"), "true",
+ "The brighttitlebarforeground attribute is set on the window.");
+
+ info ("Unapplying all themes.");
+ LightweightThemeManager.currentTheme = null;
+ ok (!DevEdition.isStyleSheetEnabled, "There is no devedition style sheet when no lw theme is applied.");
+
+ info ("Applying the devedition lightweight theme.");
+ onAttributeAdded = waitForBrightTitlebarAttribute();
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet has been added when the devedition lightweight theme is applied");
+ yield onAttributeAdded;
+ ok (document.documentElement.hasAttribute("brighttitlebarforeground"),
+ "The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
+});
+
+add_task(function* testDevtoolsTheme() {
+ info ("Checking stylesheet and :root attributes based on devtools theme.");
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
+ is (document.documentElement.getAttribute("devtoolstheme"), "light",
+ "The documentElement has an attribute based on devtools theme.");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the light devtools theme.");
+ ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
+ "The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
+
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
+ is (document.documentElement.getAttribute("devtoolstheme"), "dark",
+ "The documentElement has an attribute based on devtools theme.");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the dark devtools theme.");
+ is (document.documentElement.getAttribute("brighttitlebarforeground"), "true",
+ "The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
+
+ Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "foobar");
+ is (document.documentElement.getAttribute("devtoolstheme"), "light",
+ "The documentElement has 'light' as a default for the devtoolstheme attribute");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is still there with the foobar devtools theme.");
+ ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
+ "The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
+});
+
+function dummyLightweightTheme(id) {
+ return {
+ id: id,
+ name: id,
+ headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
+ iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
+ textcolor: "red",
+ accentcolor: "blue"
+ };
+}
+
+add_task(function* testLightweightThemePreview() {
+ info ("Setting devedition to current and the previewing others");
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org");
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled.");
+ LightweightThemeManager.previewTheme(dummyLightweightTheme("preview0"));
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is not enabled after a lightweight theme preview.");
+ LightweightThemeManager.resetPreview();
+ LightweightThemeManager.previewTheme(dummyLightweightTheme("preview1"));
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is not enabled after a second lightweight theme preview.");
+ LightweightThemeManager.resetPreview();
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled again after resetting the preview.");
+ LightweightThemeManager.currentTheme = null;
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is gone after removing the current theme.");
+
+ info ("Previewing the devedition theme");
+ LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme("firefox-devedition@mozilla.org"));
+ ok (DevEdition.isStyleSheetEnabled, "The devedition stylesheet is enabled.");
+ LightweightThemeManager.previewTheme(dummyLightweightTheme("preview2"));
+ LightweightThemeManager.resetPreview();
+ ok (!DevEdition.isStyleSheetEnabled, "The devedition stylesheet is now disabled after resetting the preview.");
+});
+
+// Use a mutation observer to wait for the brighttitlebarforeground
+// attribute to change. Using this instead of waiting for the load
+// event on the DevEdition styleSheet.
+function waitForBrightTitlebarAttribute() {
+ return new Promise((resolve, reject) => {
+ let mutationObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "brighttitlebarforeground") {
+ mutationObserver.disconnect();
+ resolve();
+ }
+ }
+ });
+ mutationObserver.observe(document.documentElement, { attributes: true });
+ });
+}
diff --git a/browser/base/content/test/general/browser_discovery.js b/browser/base/content/test/general/browser_discovery.js
new file mode 100644
index 000000000..23d44c6a9
--- /dev/null
+++ b/browser/base/content/test/general/browser_discovery.js
@@ -0,0 +1,162 @@
+var browser;
+
+function doc() {
+ return browser.contentDocument;
+}
+
+function setHandlerFunc(aResultFunc) {
+ gBrowser.addEventListener("DOMLinkAdded", function (event) {
+ gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, false);
+ executeSoon(aResultFunc);
+ }, false);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ iconDiscovery();
+ }, true);
+ var rootDir = getRootDirectory(gTestPath);
+ content.location = rootDir + "discovery.html";
+}
+
+var iconDiscoveryTests = [
+ { text: "rel icon discovered" },
+ { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" },
+ { rel: "ICON", text: "rel is case insensitive" },
+ { rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" },
+ { href: "moz.png", text: "relative href works" },
+ { href: "notthere.png", text: "404'd icon is removed properly" },
+ { href: "data:image/x-icon,%00", type: "image/x-icon", text: "data: URIs work" },
+ { type: "image/png; charset=utf-8", text: "type may have optional parameters (RFC2046)" }
+];
+
+function runIconDiscoveryTest() {
+ var testCase = iconDiscoveryTests[0];
+ var head = doc().getElementById("linkparent");
+ var hasSrc = gBrowser.getIcon() != null;
+ if (testCase.pass)
+ ok(hasSrc, testCase.text);
+ else
+ ok(!hasSrc, testCase.text);
+
+ head.removeChild(head.getElementsByTagName('link')[0]);
+ iconDiscoveryTests.shift();
+ iconDiscovery(); // Run the next test.
+}
+
+function iconDiscovery() {
+ if (iconDiscoveryTests.length) {
+ setHandlerFunc(runIconDiscoveryTest);
+ gBrowser.setIcon(gBrowser.selectedTab, null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+
+ var testCase = iconDiscoveryTests[0];
+ var head = doc().getElementById("linkparent");
+ var link = doc().createElement("link");
+
+ var rootDir = getRootDirectory(gTestPath);
+ var rel = testCase.rel || "icon";
+ var href = testCase.href || rootDir + "moz.png";
+ var type = testCase.type || "image/png";
+ if (testCase.pass == undefined)
+ testCase.pass = true;
+
+ link.rel = rel;
+ link.href = href;
+ link.type = type;
+ head.appendChild(link);
+ } else {
+ searchDiscovery();
+ }
+}
+
+var searchDiscoveryTests = [
+ { text: "rel search discovered" },
+ { rel: "SEARCH", text: "rel is case insensitive" },
+ { rel: "-search-", pass: false, text: "rel -search- not discovered" },
+ { rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" },
+ { href: "https://not.mozilla.com", text: "HTTPS ok" },
+ { href: "ftp://not.mozilla.com", text: "FTP ok" },
+ { href: "data:text/foo,foo", pass: false, text: "data URI not permitted" },
+ { href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" },
+ { type: "APPLICATION/OPENSEARCHDESCRIPTION+XML", text: "type is case insensitve" },
+ { type: " application/opensearchdescription+xml ", text: "type may contain extra whitespace" },
+ { type: "application/opensearchdescription+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
+ { type: "aapplication/opensearchdescription+xml", pass: false, text: "type should not be loosely matched" },
+ { rel: "search search search", count: 1, text: "only one engine should be added" }
+];
+
+function runSearchDiscoveryTest() {
+ var testCase = searchDiscoveryTests[0];
+ var title = testCase.title || searchDiscoveryTests.length;
+ if (browser.engines) {
+ var hasEngine = (testCase.count) ? (browser.engines[0].title == title &&
+ browser.engines.length == testCase.count) :
+ (browser.engines[0].title == title);
+ ok(hasEngine, testCase.text);
+ browser.engines = null;
+ }
+ else
+ ok(!testCase.pass, testCase.text);
+
+ searchDiscoveryTests.shift();
+ searchDiscovery(); // Run the next test.
+}
+
+// This handler is called twice, once for each added link element.
+// Only want to check once the second link element has been added.
+var ranOnce = false;
+function runMultipleEnginesTestAndFinalize() {
+ if (!ranOnce) {
+ ranOnce = true;
+ return;
+ }
+ ok(browser.engines, "has engines");
+ is(browser.engines.length, 1, "only one engine");
+ is(browser.engines[0].uri, "http://first.mozilla.com/search.xml", "first engine wins");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function searchDiscovery() {
+ let head = doc().getElementById("linkparent");
+
+ if (searchDiscoveryTests.length) {
+ setHandlerFunc(runSearchDiscoveryTest);
+ let testCase = searchDiscoveryTests[0];
+ let link = doc().createElement("link");
+
+ let rel = testCase.rel || "search";
+ let href = testCase.href || "http://so.not.here.mozilla.com/search.xml";
+ let type = testCase.type || "application/opensearchdescription+xml";
+ let title = testCase.title || searchDiscoveryTests.length;
+ if (testCase.pass == undefined)
+ testCase.pass = true;
+
+ link.rel = rel;
+ link.href = href;
+ link.type = type;
+ link.title = title;
+ head.appendChild(link);
+ } else {
+ setHandlerFunc(runMultipleEnginesTestAndFinalize);
+ setHandlerFunc(runMultipleEnginesTestAndFinalize);
+ // Test multiple engines with the same title
+ let link = doc().createElement("link");
+ link.rel = "search";
+ link.href = "http://first.mozilla.com/search.xml";
+ link.type = "application/opensearchdescription+xml";
+ link.title = "Test Engine";
+ let link2 = link.cloneNode(false);
+ link2.href = "http://second.mozilla.com/search.xml";
+
+ head.appendChild(link);
+ head.appendChild(link2);
+ }
+}
diff --git a/browser/base/content/test/general/browser_documentnavigation.js b/browser/base/content/test/general/browser_documentnavigation.js
new file mode 100644
index 000000000..eb789d076
--- /dev/null
+++ b/browser/base/content/test/general/browser_documentnavigation.js
@@ -0,0 +1,266 @@
+/*
+ * This test checks that focus is adjusted properly in a browser when pressing F6 and Shift+F6.
+ * There are additional tests in dom/tests/mochitest/chrome/test_focus_docnav.xul which test
+ * non-browser cases.
+ */
+
+var testPage1 = "data:text/html,<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>";
+var testPage2 = "data:text/html,<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>";
+var testPage3 = "data:text/html,<html id='html3'><body id='body3' contenteditable='true'><button id='button3'>Tab 3</button></body></html>";
+
+var fm = Services.focus;
+
+function* expectFocusOnF6(backward, expectedDocument, expectedElement, onContent, desc)
+{
+ let focusChangedInChildResolver = null;
+ let focusPromise = onContent ? new Promise(resolve => focusChangedInChildResolver = resolve) :
+ BrowserTestUtils.waitForEvent(window, "focus", true);
+
+ function focusChangedListener(msg) {
+ let expected = expectedDocument;
+ if (!expectedElement.startsWith("html")) {
+ expected += "," + expectedElement;
+ }
+
+ is(msg.data.details, expected, desc + " child focus matches");
+ focusChangedInChildResolver();
+ }
+
+ if (onContent) {
+ messageManager.addMessageListener("BrowserTest:FocusChanged", focusChangedListener);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { expectedElementId: expectedElement }, function* (arg) {
+ let contentExpectedElement = content.document.getElementById(arg.expectedElementId);
+ if (!contentExpectedElement) {
+ // Element not found, so look in the child frames.
+ for (let f = 0; f < content.frames.length; f++) {
+ if (content.frames[f].document.getElementById(arg.expectedElementId)) {
+ contentExpectedElement = content.frames[f].document;
+ break;
+ }
+ }
+ }
+ else if (contentExpectedElement.localName == "html") {
+ contentExpectedElement = contentExpectedElement.ownerDocument;
+ }
+
+ if (!contentExpectedElement) {
+ sendSyncMessage("BrowserTest:FocusChanged",
+ { details : "expected element " + arg.expectedElementId + " not found" });
+ return;
+ }
+
+ contentExpectedElement.addEventListener("focus", function focusReceived() {
+ contentExpectedElement.removeEventListener("focus", focusReceived, true);
+
+ const contentFM = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ let details = contentFM.focusedWindow.document.documentElement.id;
+ if (contentFM.focusedElement) {
+ details += "," + contentFM.focusedElement.id;
+ }
+
+ sendSyncMessage("BrowserTest:FocusChanged", { details : details });
+ }, true);
+ });
+ }
+
+ EventUtils.synthesizeKey("VK_F6", { shiftKey: backward });
+ yield focusPromise;
+
+ if (typeof expectedElement == "string") {
+ expectedElement = fm.focusedWindow.document.getElementById(expectedElement);
+ }
+
+ if (gMultiProcessBrowser && onContent) {
+ expectedDocument = "main-window";
+ expectedElement = gBrowser.selectedBrowser;
+ }
+
+ is(fm.focusedWindow.document.documentElement.id, expectedDocument, desc + " document matches");
+ is(fm.focusedElement, expectedElement, desc + " element matches");
+
+ if (onContent) {
+ messageManager.removeMessageListener("BrowserTest:FocusChanged", focusChangedListener);
+ }
+}
+
+// Load a page and navigate between it and the chrome window.
+add_task(function* ()
+{
+ let page1Promise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.selectedBrowser.loadURI(testPage1);
+ yield page1Promise;
+
+ // When the urlbar is focused, pressing F6 should focus the root of the content page.
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "basic focus content page");
+
+ // When the content is focused, pressing F6 should focus the urlbar.
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "basic focus content page urlbar");
+
+ // When a button in content is focused, pressing F6 should focus the urlbar.
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "basic focus content page with button focused");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
+ return content.document.getElementById("button1").focus();
+ });
+
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "basic focus content page with button focused urlbar");
+
+ // The document root should be focused, not the button
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "basic focus again content page with button focused");
+
+ // Check to ensure that the root element is focused
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () {
+ Assert.ok(content.document.activeElement == content.document.documentElement,
+ "basic focus again content page with button focused child root is focused");
+ });
+});
+
+// Open a second tab. Document focus should skip the background tab.
+add_task(function* ()
+{
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2);
+
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "basic focus content page and second tab urlbar");
+ yield* expectFocusOnF6(false, "html2", "html2",
+ true, "basic focus content page with second tab");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Shift+F6 should navigate backwards. There's only one document here so the effect
+// is the same.
+add_task(function* ()
+{
+ gURLBar.focus();
+ yield* expectFocusOnF6(true, "html1", "html1",
+ true, "back focus content page");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus content page urlbar");
+});
+
+// Open the sidebar and navigate between the sidebar, content and top-level window
+add_task(function* ()
+{
+ let sidebar = document.getElementById("sidebar");
+
+ let loadPromise = BrowserTestUtils.waitForEvent(sidebar, "load", true);
+ SidebarUI.toggle('viewBookmarksSidebar');
+ yield loadPromise;
+
+
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "bookmarksPanel",
+ sidebar.contentDocument.getElementById("search-box").inputField,
+ false, "focus with sidebar open sidebar");
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "focus with sidebar open content");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus with sidebar urlbar");
+
+ // Now go backwards
+ yield* expectFocusOnF6(true, "html1", "html1",
+ true, "back focus with sidebar open content");
+ yield* expectFocusOnF6(true, "bookmarksPanel",
+ sidebar.contentDocument.getElementById("search-box").inputField,
+ false, "back focus with sidebar open sidebar");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus with sidebar urlbar");
+
+ SidebarUI.toggle('viewBookmarksSidebar');
+});
+
+// Navigate when the downloads panel is open
+add_task(function* ()
+{
+ yield pushPrefs(["accessibility.tabfocus", 7]);
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown", true);
+ EventUtils.synthesizeMouseAtCenter(document.getElementById("downloads-button"), { });
+ yield popupShownPromise;
+
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "main-window", document.getElementById("downloadsHistory"),
+ false, "focus with downloads panel open panel");
+ yield* expectFocusOnF6(false, "html1", "html1",
+ true, "focus with downloads panel open");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus downloads panel open urlbar");
+
+ // Now go backwards
+ yield* expectFocusOnF6(true, "html1", "html1",
+ true, "back focus with downloads panel open");
+ yield* expectFocusOnF6(true, "main-window", document.getElementById("downloadsHistory"),
+ false, "back focus with downloads panel open");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus downloads panel open urlbar");
+
+ let downloadsPopup = document.getElementById("downloadsPanel");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(downloadsPopup, "popuphidden", true);
+ downloadsPopup.hidePopup();
+ yield popupHiddenPromise;
+});
+
+// Navigation with a contenteditable body
+add_task(function* ()
+{
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3);
+
+ // The body should be focused when it is editable, not the root.
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "html3", "body3",
+ true, "focus with contenteditable body");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus with contenteditable body urlbar");
+
+ // Now go backwards
+
+ yield* expectFocusOnF6(false, "html3", "body3",
+ true, "back focus with contenteditable body");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "back focus with contenteditable body urlbar");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Navigation with a frameset loaded
+add_task(function* ()
+{
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/base/content/test/general/file_documentnavigation_frameset.html");
+
+ gURLBar.focus();
+ yield* expectFocusOnF6(false, "htmlframe1", "htmlframe1",
+ true, "focus on frameset frame 0");
+ yield* expectFocusOnF6(false, "htmlframe2", "htmlframe2",
+ true, "focus on frameset frame 1");
+ yield* expectFocusOnF6(false, "htmlframe3", "htmlframe3",
+ true, "focus on frameset frame 2");
+ yield* expectFocusOnF6(false, "htmlframe4", "htmlframe4",
+ true, "focus on frameset frame 3");
+ yield* expectFocusOnF6(false, "main-window", gURLBar.inputField,
+ false, "focus on frameset frame urlbar");
+
+ yield* expectFocusOnF6(true, "htmlframe4", "htmlframe4",
+ true, "back focus on frameset frame 3");
+ yield* expectFocusOnF6(true, "htmlframe3", "htmlframe3",
+ true, "back focus on frameset frame 2");
+ yield* expectFocusOnF6(true, "htmlframe2", "htmlframe2",
+ true, "back focus on frameset frame 1");
+ yield* expectFocusOnF6(true, "htmlframe1", "htmlframe1",
+ true, "back focus on frameset frame 0");
+ yield* expectFocusOnF6(true, "main-window", gURLBar.inputField,
+ false, "back focus on frameset frame urlbar");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// XXXndeakin add tests for browsers inside of panels
diff --git a/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
new file mode 100644
index 000000000..054fb3cc0
--- /dev/null
+++ b/browser/base/content/test/general/browser_domFullscreen_fullscreenMode.js
@@ -0,0 +1,221 @@
+"use strict";
+
+var gMessageManager;
+
+function frameScript() {
+ addMessageListener("Test:RequestFullscreen", () => {
+ content.document.body.requestFullscreen();
+ });
+ addMessageListener("Test:ExitFullscreen", () => {
+ content.document.exitFullscreen();
+ });
+ addMessageListener("Test:QueryFullscreenState", () => {
+ sendAsyncMessage("Test:FullscreenState", {
+ inDOMFullscreen: !!content.document.fullscreenElement,
+ inFullscreen: content.fullScreen
+ });
+ });
+ content.document.addEventListener("fullscreenchange", () => {
+ sendAsyncMessage("Test:FullscreenChanged", {
+ inDOMFullscreen: !!content.document.fullscreenElement,
+ inFullscreen: content.fullScreen
+ });
+ });
+ function waitUntilActive() {
+ let doc = content.document;
+ if (doc.docShell.isActive && doc.hasFocus()) {
+ sendAsyncMessage("Test:Activated");
+ } else {
+ setTimeout(waitUntilActive, 10);
+ }
+ }
+ waitUntilActive();
+}
+
+function listenOneMessage(aMsg, aListener) {
+ function listener({ data }) {
+ gMessageManager.removeMessageListener(aMsg, listener);
+ aListener(data);
+ }
+ gMessageManager.addMessageListener(aMsg, listener);
+}
+
+function listenOneEvent(aEvent, aListener) {
+ function listener(evt) {
+ removeEventListener(aEvent, listener);
+ aListener(evt);
+ }
+ addEventListener(aEvent, listener);
+}
+
+function queryFullscreenState() {
+ return new Promise(resolve => {
+ listenOneMessage("Test:FullscreenState", resolve);
+ gMessageManager.sendAsyncMessage("Test:QueryFullscreenState");
+ });
+}
+
+function captureUnexpectedFullscreenChange() {
+ ok(false, "catched an unexpected fullscreen change");
+}
+
+const FS_CHANGE_DOM = 1 << 0;
+const FS_CHANGE_SIZE = 1 << 1;
+const FS_CHANGE_BOTH = FS_CHANGE_DOM | FS_CHANGE_SIZE;
+
+function waitForFullscreenChanges(aFlags) {
+ return new Promise(resolve => {
+ let fullscreenData = null;
+ let sizemodeChanged = false;
+ function tryResolve() {
+ if ((!(aFlags & FS_CHANGE_DOM) || fullscreenData) &&
+ (!(aFlags & FS_CHANGE_SIZE) || sizemodeChanged)) {
+ if (!fullscreenData) {
+ queryFullscreenState().then(resolve);
+ } else {
+ resolve(fullscreenData);
+ }
+ }
+ }
+ if (aFlags & FS_CHANGE_SIZE) {
+ listenOneEvent("sizemodechange", () => {
+ sizemodeChanged = true;
+ tryResolve();
+ });
+ }
+ if (aFlags & FS_CHANGE_DOM) {
+ gMessageManager.removeMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ listenOneMessage("Test:FullscreenChanged", data => {
+ gMessageManager.addMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+ fullscreenData = data;
+ tryResolve();
+ });
+ }
+ });
+}
+
+var gTests = [
+ {
+ desc: "document method",
+ affectsFullscreenMode: false,
+ exitFunc: () => {
+ gMessageManager.sendAsyncMessage("Test:ExitFullscreen");
+ }
+ },
+ {
+ desc: "escape key",
+ affectsFullscreenMode: false,
+ exitFunc: () => {
+ executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
+ }
+ },
+ {
+ desc: "F11 key",
+ affectsFullscreenMode: true,
+ exitFunc: function () {
+ executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
+ }
+ }
+];
+
+function checkState(expectedStates, contentStates) {
+ is(contentStates.inDOMFullscreen, expectedStates.inDOMFullscreen,
+ "The DOM fullscreen state of the content should match");
+ // TODO window.fullScreen is not updated as soon as the fullscreen
+ // state flips in child process, hence checking it could cause
+ // anonying intermittent failure. As we just want to confirm the
+ // fullscreen state of the browser window, we can just check the
+ // that on the chrome window below.
+ // is(contentStates.inFullscreen, expectedStates.inFullscreen,
+ // "The fullscreen state of the content should match");
+ is(!!document.fullscreenElement, expectedStates.inDOMFullscreen,
+ "The DOM fullscreen state of the chrome should match");
+ is(window.fullScreen, expectedStates.inFullscreen,
+ "The fullscreen state of the chrome should match");
+}
+
+const kPage = "http://example.org/browser/browser/" +
+ "base/content/test/general/dummy_page.html";
+
+add_task(function* () {
+ yield pushPrefs(
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"]);
+
+ let tab = gBrowser.addTab(kPage);
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ yield waitForDocLoadComplete();
+
+ registerCleanupFunction(() => {
+ if (browser.contentWindow.fullScreen) {
+ BrowserFullScreen();
+ }
+ gBrowser.removeTab(tab);
+ });
+
+ gMessageManager = browser.messageManager;
+ gMessageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")();", false);
+ gMessageManager.addMessageListener(
+ "Test:FullscreenChanged", captureUnexpectedFullscreenChange);
+
+ // Wait for the document being activated, so that
+ // fullscreen request won't be denied.
+ yield new Promise(resolve => listenOneMessage("Test:Activated", resolve));
+
+ for (let test of gTests) {
+ let contentStates;
+ info("Testing exit DOM fullscreen via " + test.desc);
+
+ contentStates = yield queryFullscreenState();
+ checkState({inDOMFullscreen: false, inFullscreen: false}, contentStates);
+
+ /* DOM fullscreen without fullscreen mode */
+
+ info("> Enter DOM fullscreen");
+ gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_BOTH);
+ checkState({inDOMFullscreen: true, inFullscreen: true}, contentStates);
+
+ info("> Exit DOM fullscreen");
+ test.exitFunc();
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_BOTH);
+ checkState({inDOMFullscreen: false, inFullscreen: false}, contentStates);
+
+ /* DOM fullscreen with fullscreen mode */
+
+ info("> Enter fullscreen mode");
+ // Need to be asynchronous because sizemodechange event could be
+ // dispatched synchronously, which would cause the event listener
+ // miss that event and wait infinitely.
+ executeSoon(() => BrowserFullScreen());
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_SIZE);
+ checkState({inDOMFullscreen: false, inFullscreen: true}, contentStates);
+
+ info("> Enter DOM fullscreen in fullscreen mode");
+ gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
+ contentStates = yield waitForFullscreenChanges(FS_CHANGE_DOM);
+ checkState({inDOMFullscreen: true, inFullscreen: true}, contentStates);
+
+ info("> Exit DOM fullscreen in fullscreen mode");
+ test.exitFunc();
+ contentStates = yield waitForFullscreenChanges(
+ test.affectsFullscreenMode ? FS_CHANGE_BOTH : FS_CHANGE_DOM);
+ checkState({
+ inDOMFullscreen: false,
+ inFullscreen: !test.affectsFullscreenMode
+ }, contentStates);
+
+ /* Cleanup */
+
+ // Exit fullscreen mode if we are still in
+ if (window.fullScreen) {
+ info("> Cleanup");
+ executeSoon(() => BrowserFullScreen());
+ yield waitForFullscreenChanges(FS_CHANGE_SIZE);
+ }
+ }
+});
diff --git a/browser/base/content/test/general/browser_double_close_tab.js b/browser/base/content/test/general/browser_double_close_tab.js
new file mode 100644
index 000000000..29242c3f9
--- /dev/null
+++ b/browser/base/content/test/general/browser_double_close_tab.js
@@ -0,0 +1,80 @@
+"use strict";
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/base/content/test/general/file_double_close_tab.html";
+var testTab;
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+function waitForDialog(callback) {
+ function onTabModalDialogLoaded(node) {
+ Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded");
+ callback(node);
+ }
+
+ // Listen for the dialog being created
+ Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false);
+}
+
+function waitForDialogDestroyed(node, callback) {
+ // Now listen for the dialog going away again...
+ let observer = new MutationObserver(function(muts) {
+ if (!node.parentNode) {
+ ok(true, "Dialog is gone");
+ done();
+ }
+ });
+ observer.observe(node.parentNode, {childList: true});
+ let failureTimeout = setTimeout(function() {
+ ok(false, "Dialog should have been destroyed");
+ done();
+ }, 10000);
+
+ function done() {
+ clearTimeout(failureTimeout);
+ observer.disconnect();
+ observer = null;
+ callback();
+ }
+}
+
+add_task(function*() {
+ testTab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(testTab, TEST_PAGE);
+ // XXXgijs the reason this has nesting and callbacks rather than promises is
+ // that DOM promises resolve on the next tick. So they're scheduled
+ // in an event queue. So when we spin a new event queue for a modal dialog...
+ // everything gets messed up and the promise's .then callbacks never get
+ // called, despite resolve() being called just fine.
+ yield new Promise(resolveOuter => {
+ waitForDialog(dialogNode => {
+ waitForDialogDestroyed(dialogNode, () => {
+ let doCompletion = () => setTimeout(resolveOuter, 0);
+ info("Now checking if dialog is destroyed");
+ ok(!dialogNode.parentNode, "onbeforeunload dialog should be gone.");
+ if (dialogNode.parentNode) {
+ // Failed to remove onbeforeunload dialog, so do it ourselves:
+ let leaveBtn = dialogNode.ui.button0;
+ waitForDialogDestroyed(dialogNode, doCompletion);
+ EventUtils.synthesizeMouseAtCenter(leaveBtn, {});
+ return;
+ }
+ doCompletion();
+ });
+ // Click again:
+ document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click();
+ });
+ // Click once:
+ document.getAnonymousElementByAttribute(testTab, "anonid", "close-button").click();
+ });
+ yield promiseWaitForCondition(() => !testTab.parentNode);
+ ok(!testTab.parentNode, "Tab should be closed completely");
+});
+
+registerCleanupFunction(function() {
+ if (testTab.parentNode) {
+ // Remove the handler, or closing this tab will prove tricky:
+ try {
+ testTab.linkedBrowser.contentWindow.onbeforeunload = null;
+ } catch (ex) {}
+ gBrowser.removeTab(testTab);
+ }
+});
diff --git a/browser/base/content/test/general/browser_drag.js b/browser/base/content/test/general/browser_drag.js
new file mode 100644
index 000000000..64ad19bde
--- /dev/null
+++ b/browser/base/content/test/general/browser_drag.js
@@ -0,0 +1,45 @@
+function test()
+{
+ waitForExplicitFinish();
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // ---- Test dragging the proxy icon ---
+ var value = content.location.href;
+ var urlString = value + "\n" + content.document.title;
+ var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+ var expected = [ [
+ { type : "text/x-moz-url",
+ data : urlString },
+ { type : "text/uri-list",
+ data : value },
+ { type : "text/plain",
+ data : value },
+ { type : "text/html",
+ data : htmlString }
+ ] ];
+ // set the valid attribute so dropping is allowed
+ var oldstate = gURLBar.getAttribute("pageproxystate");
+ gURLBar.setAttribute("pageproxystate", "valid");
+ var dt = EventUtils.synthesizeDragStart(document.getElementById("identity-box"), expected);
+ is(dt, null, "drag on proxy icon");
+ gURLBar.setAttribute("pageproxystate", oldstate);
+ // Now, the identity information panel is opened by the proxy icon click.
+ // We need to close it for next tests.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
+
+ // now test dragging onto a tab
+ var tab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ var browser = gBrowser.getBrowserForTab(tab);
+
+ browser.addEventListener("load", function () {
+ is(browser.contentWindow.location, "http://mochi.test:8888/", "drop on tab");
+ gBrowser.removeTab(tab);
+ finish();
+ }, true);
+
+ EventUtils.synthesizeDrop(tab, tab, [[{type: "text/uri-list", data: "http://mochi.test:8888/"}]], "copy", window);
+}
diff --git a/browser/base/content/test/general/browser_duplicateIDs.js b/browser/base/content/test/general/browser_duplicateIDs.js
new file mode 100644
index 000000000..38fc17820
--- /dev/null
+++ b/browser/base/content/test/general/browser_duplicateIDs.js
@@ -0,0 +1,8 @@
+function test() {
+ var ids = {};
+ Array.forEach(document.querySelectorAll("[id]"), function (node) {
+ var id = node.id;
+ ok(!(id in ids), id + " should be unique");
+ ids[id] = null;
+ });
+}
diff --git a/browser/base/content/test/general/browser_e10s_about_process.js b/browser/base/content/test/general/browser_e10s_about_process.js
new file mode 100644
index 000000000..2b4816754
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_about_process.js
@@ -0,0 +1,114 @@
+const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+const CHROME = {
+ id: "cb34538a-d9da-40f3-b61a-069f0b2cb9fb",
+ path: "test-chrome",
+ flags: 0,
+}
+const CANREMOTE = {
+ id: "2480d3e1-9ce4-4b84-8ae3-910b9a95cbb3",
+ path: "test-allowremote",
+ flags: Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD,
+}
+const MUSTREMOTE = {
+ id: "f849cee5-e13e-44d2-981d-0fb3884aaead",
+ path: "test-mustremote",
+ flags: Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD,
+}
+
+const TEST_MODULES = [
+ CHROME,
+ CANREMOTE,
+ MUSTREMOTE
+]
+
+function AboutModule() {
+}
+
+AboutModule.prototype = {
+ newChannel: function(aURI, aLoadInfo) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ getURIFlags: function(aURI) {
+ for (let module of TEST_MODULES) {
+ if (aURI.path.startsWith(module.path)) {
+ return module.flags;
+ }
+ }
+
+ ok(false, "Called getURIFlags for an unknown page " + aURI.spec);
+ return 0;
+ },
+
+ getIndexedDBOriginPostfix: function(aURI) {
+ return null;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule])
+};
+
+var AboutModuleFactory = {
+ createInstance: function(aOuter, aIID) {
+ if (aOuter)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return new AboutModule().QueryInterface(aIID);
+ },
+
+ lockFactory: function(aLock) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+add_task(function* init() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ for (let module of TEST_MODULES) {
+ registrar.registerFactory(Components.ID(module.id), "",
+ "@mozilla.org/network/protocol/about;1?what=" + module.path,
+ AboutModuleFactory);
+ }
+});
+
+registerCleanupFunction(() => {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ for (let module of TEST_MODULES) {
+ registrar.unregisterFactory(Components.ID(module.id), AboutModuleFactory);
+ }
+});
+
+function test_url(url, chromeResult, contentResult) {
+ is(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS),
+ chromeResult, "Check URL in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS),
+ contentResult, "Check URL in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CHROME_PROCESS),
+ chromeResult, "Check URL with ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CONTENT_PROCESS),
+ contentResult, "Check URL with ref in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CHROME_PROCESS),
+ chromeResult, "Check URL with query in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CONTENT_PROCESS),
+ contentResult, "Check URL with query in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CHROME_PROCESS),
+ chromeResult, "Check URL with query and ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CONTENT_PROCESS),
+ contentResult, "Check URL with query and ref in content process.");
+}
+
+add_task(function* test_chrome() {
+ test_url("about:" + CHROME.path, true, false);
+});
+
+add_task(function* test_any() {
+ test_url("about:" + CANREMOTE.path, true, true);
+});
+
+add_task(function* test_remote() {
+ test_url("about:" + MUSTREMOTE.path, false, true);
+});
diff --git a/browser/base/content/test/general/browser_e10s_chrome_process.js b/browser/base/content/test/general/browser_e10s_chrome_process.js
new file mode 100644
index 000000000..0726447ce
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_chrome_process.js
@@ -0,0 +1,150 @@
+// Returns a function suitable for add_task which loads startURL, runs
+// transitionTask and waits for endURL to load, checking that the URLs were
+// loaded in the correct process.
+function makeTest(name, startURL, startProcessIsRemote, endURL, endProcessIsRemote, transitionTask) {
+ return function*() {
+ info("Running test " + name + ", " + transitionTask.name);
+ let browser = gBrowser.selectedBrowser;
+
+ // In non-e10s nothing should be remote
+ if (!gMultiProcessBrowser) {
+ startProcessIsRemote = false;
+ endProcessIsRemote = false;
+ }
+
+ // Load the initial URL and make sure we are in the right initial process
+ info("Loading initial URL");
+ browser.loadURI(startURL);
+ yield waitForDocLoadComplete();
+
+ is(browser.currentURI.spec, startURL, "Shouldn't have been redirected");
+ is(browser.isRemoteBrowser, startProcessIsRemote, "Should be displayed in the right process");
+
+ let docLoadedPromise = waitForDocLoadComplete();
+ let asyncTask = Task.async(transitionTask);
+ let expectSyncChange = yield asyncTask(browser, endURL);
+ if (expectSyncChange) {
+ is(browser.isRemoteBrowser, endProcessIsRemote, "Should have switched to the right process synchronously");
+ }
+ yield docLoadedPromise;
+
+ is(browser.currentURI.spec, endURL, "Should have made it to the final URL");
+ is(browser.isRemoteBrowser, endProcessIsRemote, "Should be displayed in the right process");
+ }
+}
+
+const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+const PATH = (getRootDirectory(gTestPath) + "test_process_flags_chrome.html").replace("chrome://mochitests", "");
+
+const CHROME = "chrome://mochitests" + PATH;
+const CANREMOTE = "chrome://mochitests-any" + PATH;
+const MUSTREMOTE = "chrome://mochitests-content" + PATH;
+
+add_task(function* init() {
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+});
+
+registerCleanupFunction(() => {
+ gBrowser.removeCurrentTab();
+});
+
+function test_url(url, chromeResult, contentResult) {
+ is(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS),
+ chromeResult, "Check URL in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS),
+ contentResult, "Check URL in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CHROME_PROCESS),
+ chromeResult, "Check URL with ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "#foo", CONTENT_PROCESS),
+ contentResult, "Check URL with ref in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CHROME_PROCESS),
+ chromeResult, "Check URL with query in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo", CONTENT_PROCESS),
+ contentResult, "Check URL with query in content process.");
+
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CHROME_PROCESS),
+ chromeResult, "Check URL with query and ref in chrome process.");
+ is(E10SUtils.canLoadURIInProcess(url + "?foo#bar", CONTENT_PROCESS),
+ contentResult, "Check URL with query and ref in content process.");
+}
+
+add_task(function* test_chrome() {
+ test_url(CHROME, true, false);
+});
+
+add_task(function* test_any() {
+ test_url(CANREMOTE, true, true);
+});
+
+add_task(function* test_remote() {
+ test_url(MUSTREMOTE, false, true);
+});
+
+// The set of page transitions
+var TESTS = [
+ [
+ "chrome -> chrome",
+ CHROME, false,
+ CHROME, false,
+ ],
+ [
+ "chrome -> canremote",
+ CHROME, false,
+ CANREMOTE, false,
+ ],
+ [
+ "chrome -> mustremote",
+ CHROME, false,
+ MUSTREMOTE, true,
+ ],
+ [
+ "remote -> chrome",
+ MUSTREMOTE, true,
+ CHROME, false,
+ ],
+ [
+ "remote -> canremote",
+ MUSTREMOTE, true,
+ CANREMOTE, true,
+ ],
+ [
+ "remote -> mustremote",
+ MUSTREMOTE, true,
+ MUSTREMOTE, true,
+ ],
+];
+
+// The different ways to transition from one page to another
+var TRANSITIONS = [
+// Loads the new page by calling browser.loadURI directly
+function* loadURI(browser, uri) {
+ info("Calling browser.loadURI");
+ yield BrowserTestUtils.loadURI(browser, uri);
+ return true;
+},
+
+// Loads the new page by finding a link with the right href in the document and
+// clicking it
+function* clickLink(browser, uri) {
+ info("Clicking link");
+
+ function frame_script(frameUri) {
+ let link = content.document.querySelector("a[href='" + frameUri + "']");
+ link.click();
+ }
+
+ browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")(" + JSON.stringify(uri) + ");", false);
+
+ return false;
+},
+];
+
+// Creates a set of test tasks, one for each combination of TESTS and TRANSITIONS.
+for (let test of TESTS) {
+ for (let transition of TRANSITIONS) {
+ add_task(makeTest(...test, transition));
+ }
+}
diff --git a/browser/base/content/test/general/browser_e10s_javascript.js b/browser/base/content/test/general/browser_e10s_javascript.js
new file mode 100644
index 000000000..90e847b09
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_javascript.js
@@ -0,0 +1,11 @@
+const CHROME_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+const CONTENT_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+add_task(function*() {
+ let url = "javascript:dosomething()";
+
+ ok(E10SUtils.canLoadURIInProcess(url, CHROME_PROCESS),
+ "Check URL in chrome process.");
+ ok(E10SUtils.canLoadURIInProcess(url, CONTENT_PROCESS),
+ "Check URL in content process.");
+});
diff --git a/browser/base/content/test/general/browser_e10s_switchbrowser.js b/browser/base/content/test/general/browser_e10s_switchbrowser.js
new file mode 100644
index 000000000..e6134f749
--- /dev/null
+++ b/browser/base/content/test/general/browser_e10s_switchbrowser.js
@@ -0,0 +1,261 @@
+requestLongerTimeout(2);
+
+const DUMMY_PATH = "browser/browser/base/content/test/general/dummy_page.html";
+
+const gExpectedHistory = {
+ index: -1,
+ entries: []
+};
+
+function get_remote_history(browser) {
+ function frame_script() {
+ let webNav = docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ let sessionHistory = webNav.sessionHistory;
+ let result = {
+ index: sessionHistory.index,
+ entries: []
+ };
+
+ for (let i = 0; i < sessionHistory.count; i++) {
+ let entry = sessionHistory.getEntryAtIndex(i, false);
+ result.entries.push({
+ uri: entry.URI.spec,
+ title: entry.title
+ });
+ }
+
+ sendAsyncMessage("Test:History", result);
+ }
+
+ return new Promise(resolve => {
+ browser.messageManager.addMessageListener("Test:History", function listener({data}) {
+ browser.messageManager.removeMessageListener("Test:History", listener);
+ resolve(data);
+ });
+
+ browser.messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+ });
+}
+
+var check_history = Task.async(function*() {
+ let sessionHistory = yield get_remote_history(gBrowser.selectedBrowser);
+
+ let count = sessionHistory.entries.length;
+ is(count, gExpectedHistory.entries.length, "Should have the right number of history entries");
+ is(sessionHistory.index, gExpectedHistory.index, "Should have the right history index");
+
+ for (let i = 0; i < count; i++) {
+ let entry = sessionHistory.entries[i];
+ is(entry.uri, gExpectedHistory.entries[i].uri, "Should have the right URI");
+ is(entry.title, gExpectedHistory.entries[i].title, "Should have the right title");
+ }
+});
+
+function clear_history() {
+ gExpectedHistory.index = -1;
+ gExpectedHistory.entries = [];
+}
+
+// Waits for a load and updates the known history
+var waitForLoad = Task.async(function*(uri) {
+ info("Loading " + uri);
+ // Longwinded but this ensures we don't just shortcut to LoadInNewProcess
+ gBrowser.selectedBrowser.webNavigation.loadURI(uri, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+
+ yield waitForDocLoadComplete();
+ gExpectedHistory.index++;
+ gExpectedHistory.entries.push({
+ uri: gBrowser.currentURI.spec,
+ title: gBrowser.contentTitle
+ });
+});
+
+// Waits for a load and updates the known history
+var waitForLoadWithFlags = Task.async(function*(uri, flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE) {
+ info("Loading " + uri + " flags = " + flags);
+ gBrowser.selectedBrowser.loadURIWithFlags(uri, flags, null, null, null);
+
+ yield waitForDocLoadComplete();
+ if (!(flags & Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY)) {
+
+ if (flags & Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY) {
+ gExpectedHistory.entries.pop();
+ }
+ else {
+ gExpectedHistory.index++;
+ }
+
+ gExpectedHistory.entries.push({
+ uri: gBrowser.currentURI.spec,
+ title: gBrowser.contentTitle
+ });
+ }
+});
+
+var back = Task.async(function*() {
+ info("Going back");
+ gBrowser.goBack();
+ yield waitForDocLoadComplete();
+ gExpectedHistory.index--;
+});
+
+var forward = Task.async(function*() {
+ info("Going forward");
+ gBrowser.goForward();
+ yield waitForDocLoadComplete();
+ gExpectedHistory.index++;
+});
+
+// Tests that navigating from a page that should be in the remote process and
+// a page that should be in the main process works and retains history
+add_task(function* test_navigation() {
+ let expectedRemote = gMultiProcessBrowser;
+
+ info("1");
+ // Create a tab and load a remote page in it
+ gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ let {permanentKey} = gBrowser.selectedBrowser;
+ yield waitForLoad("http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("2");
+ // Load another page
+ yield waitForLoad("http://example.com/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("3");
+ // Load a non-remote page
+ yield waitForLoad("about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("4");
+ // Load a remote page
+ yield waitForLoad("http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("5");
+ yield back();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("6");
+ yield back();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("7");
+ yield forward();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("8");
+ yield forward();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("9");
+ yield back();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("10");
+ // Load a new remote page, this should replace the last history entry
+ gExpectedHistory.entries.splice(gExpectedHistory.entries.length - 1, 1);
+ yield waitForLoad("http://example.com/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+ yield check_history();
+
+ info("11");
+ gBrowser.removeCurrentTab();
+ clear_history();
+});
+
+// Tests that calling gBrowser.loadURI or browser.loadURI to load a page in a
+// different process updates the browser synchronously
+add_task(function* test_synchronous() {
+ let expectedRemote = gMultiProcessBrowser;
+
+ info("1");
+ // Create a tab and load a remote page in it
+ gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ let {permanentKey} = gBrowser.selectedBrowser;
+ yield waitForLoad("http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("2");
+ // Load another page
+ info("Loading about:robots");
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ yield waitForDocLoadComplete();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("3");
+ // Load the remote page again
+ info("Loading http://example.org/" + DUMMY_PATH);
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "http://example.org/" + DUMMY_PATH);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ yield waitForDocLoadComplete();
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ is(gBrowser.selectedBrowser.permanentKey, permanentKey, "browser.permanentKey is still the same");
+
+ info("4");
+ gBrowser.removeCurrentTab();
+ clear_history();
+});
+
+// Tests that load flags are correctly passed through to the child process with
+// normal loads
+add_task(function* test_loadflags() {
+ let expectedRemote = gMultiProcessBrowser;
+
+ info("1");
+ // Create a tab and load a remote page in it
+ gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ yield waitForLoadWithFlags("about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ yield check_history();
+
+ info("2");
+ // Load a page in the remote process with some custom flags
+ yield waitForLoadWithFlags("http://example.com/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ yield check_history();
+
+ info("3");
+ // Load a non-remote page
+ yield waitForLoadWithFlags("about:robots");
+ is(gBrowser.selectedBrowser.isRemoteBrowser, false, "Remote attribute should be correct");
+ yield check_history();
+
+ info("4");
+ // Load another remote page
+ yield waitForLoadWithFlags("http://example.org/" + DUMMY_PATH, Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY);
+ is(gBrowser.selectedBrowser.isRemoteBrowser, expectedRemote, "Remote attribute should be correct");
+ yield check_history();
+
+ is(gExpectedHistory.entries.length, 2, "Should end with the right number of history entries");
+
+ info("5");
+ gBrowser.removeCurrentTab();
+ clear_history();
+});
diff --git a/browser/base/content/test/general/browser_favicon_change.js b/browser/base/content/test/general/browser_favicon_change.js
new file mode 100644
index 000000000..f6b0a2a42
--- /dev/null
+++ b/browser/base/content/test/general/browser_favicon_change.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change.html"
+
+add_task(function*() {
+ let extraTab = gBrowser.selectedTab = gBrowser.addTab();
+ extraTab.linkedBrowser.loadURI(TEST_URL);
+ let tabLoaded = BrowserTestUtils.browserLoaded(extraTab.linkedBrowser);
+ let expectedFavicon = "http://example.org/one-icon";
+ let haveChanged = new Promise.defer();
+ let observer = new MutationObserver(function(mutations) {
+ for (let mut of mutations) {
+ if (mut.attributeName != "image") {
+ continue;
+ }
+ let imageVal = extraTab.getAttribute("image").replace(/#.*$/, "");
+ if (!imageVal) {
+ // The value gets removed because it doesn't load.
+ continue;
+ }
+ is(imageVal, expectedFavicon, "Favicon image should correspond to expected image.");
+ haveChanged.resolve();
+ }
+ });
+ observer.observe(extraTab, {attributes: true});
+ yield tabLoaded;
+ yield haveChanged.promise;
+ haveChanged = new Promise.defer();
+ expectedFavicon = "http://example.org/other-icon";
+ ContentTask.spawn(extraTab.linkedBrowser, null, function() {
+ let ev = new content.CustomEvent("PleaseChangeFavicon", {});
+ content.dispatchEvent(ev);
+ });
+ yield haveChanged.promise;
+ observer.disconnect();
+ gBrowser.removeTab(extraTab);
+});
+
diff --git a/browser/base/content/test/general/browser_favicon_change_not_in_document.js b/browser/base/content/test/general/browser_favicon_change_not_in_document.js
new file mode 100644
index 000000000..d14a1da32
--- /dev/null
+++ b/browser/base/content/test/general/browser_favicon_change_not_in_document.js
@@ -0,0 +1,34 @@
+"use strict";
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change_not_in_document.html"
+
+add_task(function*() {
+ let extraTab = gBrowser.selectedTab = gBrowser.addTab();
+ let tabLoaded = promiseTabLoaded(extraTab);
+ extraTab.linkedBrowser.loadURI(TEST_URL);
+ let expectedFavicon = "http://example.org/one-icon";
+ let haveChanged = new Promise.defer();
+ let observer = new MutationObserver(function(mutations) {
+ for (let mut of mutations) {
+ if (mut.attributeName != "image") {
+ continue;
+ }
+ let imageVal = extraTab.getAttribute("image").replace(/#.*$/, "");
+ if (!imageVal) {
+ // The value gets removed because it doesn't load.
+ continue;
+ }
+ is(imageVal, expectedFavicon, "Favicon image should correspond to expected image.");
+ haveChanged.resolve();
+ }
+ });
+ observer.observe(extraTab, {attributes: true});
+ yield tabLoaded;
+ expectedFavicon = "http://example.org/yet-another-icon";
+ haveChanged = new Promise.defer();
+ yield haveChanged.promise;
+ observer.disconnect();
+ gBrowser.removeTab(extraTab);
+});
+
+
diff --git a/browser/base/content/test/general/browser_feed_discovery.js b/browser/base/content/test/general/browser_feed_discovery.js
new file mode 100644
index 000000000..73dcef755
--- /dev/null
+++ b/browser/base/content/test/general/browser_feed_discovery.js
@@ -0,0 +1,33 @@
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/feed_discovery.html"
+
+/** Test for Bug 377611 **/
+
+add_task(function* () {
+ // Open a new tab.
+ gBrowser.selectedTab = gBrowser.addTab(URL);
+ registerCleanupFunction(() => gBrowser.removeCurrentTab());
+
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ let discovered = browser.feeds;
+ ok(discovered.length > 0, "some feeds should be discovered");
+
+ let feeds = {};
+ for (let aFeed of discovered) {
+ feeds[aFeed.href] = true;
+ }
+
+ yield ContentTask.spawn(browser, feeds, function* (contentFeeds) {
+ for (let aLink of content.document.getElementsByTagName("link")) {
+ // ignore real stylesheets, and anything without an href property
+ if (aLink.type != "text/css" && aLink.href) {
+ if (/bogus/i.test(aLink.title)) {
+ ok(!contentFeeds[aLink.href], "don't discover " + aLink.href);
+ } else {
+ ok(contentFeeds[aLink.href], "should discover " + aLink.href);
+ }
+ }
+ }
+ });
+})
diff --git a/browser/base/content/test/general/browser_findbarClose.js b/browser/base/content/test/general/browser_findbarClose.js
new file mode 100644
index 000000000..53503073c
--- /dev/null
+++ b/browser/base/content/test/general/browser_findbarClose.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests find bar auto-close behavior
+
+var newTab;
+
+add_task(function* findbar_test() {
+ waitForExplicitFinish();
+ newTab = gBrowser.addTab("about:blank");
+
+ let promise = ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+ yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", false);
+ });
+ newTab.linkedBrowser.loadURI("http://example.com/browser/" +
+ "browser/base/content/test/general/test_bug628179.html");
+ yield promise;
+
+ gFindBar.open();
+
+ yield new ContentTask.spawn(newTab.linkedBrowser, null, function* () {
+ let iframe = content.document.getElementById("iframe");
+ let awaitLoad = ContentTaskUtils.waitForEvent(iframe, "load", false);
+ iframe.src = "http://example.org/";
+ yield awaitLoad;
+ });
+
+ ok(!gFindBar.hidden, "the Find bar isn't hidden after the location of a " +
+ "subdocument changes");
+
+ gFindBar.close();
+ gBrowser.removeTab(newTab);
+ finish();
+});
+
diff --git a/browser/base/content/test/general/browser_focusonkeydown.js b/browser/base/content/test/general/browser_focusonkeydown.js
new file mode 100644
index 000000000..5b3337203
--- /dev/null
+++ b/browser/base/content/test/general/browser_focusonkeydown.js
@@ -0,0 +1,26 @@
+add_task(function *()
+{
+ let keyUps = 0;
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html,<body>");
+
+ gURLBar.focus();
+
+ window.addEventListener("keyup", function countKeyUps(event) {
+ window.removeEventListener("keyup", countKeyUps, true);
+ if (event.originalTarget == gURLBar.inputField) {
+ keyUps++;
+ }
+ }, true);
+
+ gURLBar.addEventListener("keydown", function redirectFocus(event) {
+ gURLBar.removeEventListener("keydown", redirectFocus, true);
+ gBrowser.selectedBrowser.focus();
+ }, true);
+
+ EventUtils.synthesizeKey("v", { });
+
+ is(keyUps, 1, "Key up fired at url bar");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_fullscreen-window-open.js b/browser/base/content/test/general/browser_fullscreen-window-open.js
new file mode 100644
index 000000000..2624b754a
--- /dev/null
+++ b/browser/base/content/test/general/browser_fullscreen-window-open.js
@@ -0,0 +1,347 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+const PREF_DISABLE_OPEN_NEW_WINDOW = "browser.link.open_newwindow.disabled_in_fullscreen";
+const isOSX = (Services.appinfo.OS === "Darwin");
+
+const TEST_FILE = "file_fullscreen-window-open.html";
+const gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+ "http://127.0.0.1:8888/");
+
+function test () {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true);
+
+ let newTab = gBrowser.addTab(gHttpTestRoot + TEST_FILE);
+ gBrowser.selectedTab = newTab;
+
+ whenTabLoaded(newTab, function () {
+ // Enter browser fullscreen mode.
+ BrowserFullScreen();
+
+ runNextTest();
+ });
+}
+
+registerCleanupFunction(function() {
+ // Exit browser fullscreen mode.
+ BrowserFullScreen();
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.clearUserPref(PREF_DISABLE_OPEN_NEW_WINDOW);
+});
+
+var gTests = [
+ test_open,
+ test_open_with_size,
+ test_open_with_pos,
+ test_open_with_outerSize,
+ test_open_with_innerSize,
+ test_open_with_dialog,
+ test_open_when_open_new_window_by_pref,
+ test_open_with_pref_to_disable_in_fullscreen,
+ test_open_from_chrome,
+];
+
+function runNextTest () {
+ let testCase = gTests.shift();
+ if (testCase) {
+ executeSoon(testCase);
+ }
+ else {
+ finish();
+ }
+}
+
+
+// Test for window.open() with no feature.
+function test_open() {
+ waitForTabOpen({
+ message: {
+ title: "test_open",
+ param: "",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with width/height.
+function test_open_with_size() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_size",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with top/left.
+function test_open_with_pos() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_pos",
+ param: "top=200,left=200",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with outerWidth/Height.
+function test_open_with_outerSize() {
+ let [outerWidth, outerHeight] = [window.outerWidth, window.outerHeight];
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_outerSize",
+ param: "outerWidth=200,outerHeight=200",
+ },
+ successFn: function () {
+ is(window.outerWidth, outerWidth, "Don't change window.outerWidth.");
+ is(window.outerHeight, outerHeight, "Don't change window.outerHeight.");
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with innerWidth/Height.
+function test_open_with_innerSize() {
+ let [innerWidth, innerHeight] = [window.innerWidth, window.innerHeight];
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_innerSize",
+ param: "innerWidth=200,innerHeight=200",
+ },
+ successFn: function () {
+ is(window.innerWidth, innerWidth, "Don't change window.innerWidth.");
+ is(window.innerHeight, innerHeight, "Don't change window.innerHeight.");
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with dialog.
+function test_open_with_dialog() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_dialog",
+ param: "dialog=yes",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open()
+// when "browser.link.open_newwindow" is nsIBrowserDOMWindow.OPEN_NEWWINDOW
+function test_open_when_open_new_window_by_pref() {
+ const PREF_NAME = "browser.link.open_newwindow";
+ Services.prefs.setIntPref(PREF_NAME, Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW);
+ is(Services.prefs.getIntPref(PREF_NAME), Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW,
+ PREF_NAME + " is nsIBrowserDOMWindow.OPEN_NEWWINDOW at this time");
+
+ waitForTabOpen({
+ message: {
+ title: "test_open_when_open_new_window_by_pref",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {
+ Services.prefs.clearUserPref(PREF_NAME);
+ },
+ });
+}
+
+// Test for the pref, "browser.link.open_newwindow.disabled_in_fullscreen"
+function test_open_with_pref_to_disable_in_fullscreen() {
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, false);
+
+ waitForWindowOpen({
+ message: {
+ title: "test_open_with_pref_disabled_in_fullscreen",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true);
+ },
+ });
+}
+
+
+// Test for window.open() called from chrome context.
+function test_open_from_chrome() {
+ waitForWindowOpenFromChrome({
+ message: {
+ title: "test_open_from_chrome",
+ param: "",
+ },
+ finalizeFn: function () {}
+ });
+}
+
+function waitForTabOpen(aOptions) {
+ let message = aOptions.message;
+
+ if (!message.title) {
+ ok(false, "Can't get message.title.");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onTabOpen = function onTabOpen(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
+
+ let tab = aEvent.target;
+ whenTabLoaded(tab, function () {
+ is(tab.linkedBrowser.contentTitle, message.title,
+ "Opened Tab is expected: " + message.title);
+
+ if (aOptions.successFn) {
+ aOptions.successFn();
+ }
+
+ gBrowser.removeTab(tab);
+ finalize();
+ });
+ }
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
+
+ let finalize = function () {
+ aOptions.finalizeFn();
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ const URI = "data:text/html;charset=utf-8,<!DOCTYPE html><html><head><title>"+
+ message.title +
+ "<%2Ftitle><%2Fhead><body><%2Fbody><%2Fhtml>";
+
+ executeWindowOpenInContent({
+ uri: URI,
+ title: message.title,
+ option: message.param,
+ });
+}
+
+
+function waitForWindowOpen(aOptions) {
+ let message = aOptions.message;
+ let url = aOptions.url || "about:blank";
+
+ if (!message.title) {
+ ok(false, "Can't get message.title");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onFinalize = function () {
+ aOptions.finalizeFn();
+
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ let listener = new WindowListener(message.title, getBrowserURL(), {
+ onSuccess: aOptions.successFn,
+ onFinalize: onFinalize,
+ });
+ Services.wm.addListener(listener);
+
+ executeWindowOpenInContent({
+ uri: url,
+ title: message.title,
+ option: message.param,
+ });
+}
+
+function executeWindowOpenInContent(aParam) {
+ ContentTask.spawn(gBrowser.selectedBrowser, JSON.stringify(aParam), function* (dataTestParam) {
+ let testElm = content.document.getElementById("test");
+ testElm.setAttribute("data-test-param", dataTestParam);
+ testElm.click();
+ });
+}
+
+function waitForWindowOpenFromChrome(aOptions) {
+ let message = aOptions.message;
+ let url = aOptions.url || "about:blank";
+
+ if (!message.title) {
+ ok(false, "Can't get message.title");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onFinalize = function () {
+ aOptions.finalizeFn();
+
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ let listener = new WindowListener(message.title, getBrowserURL(), {
+ onSuccess: aOptions.successFn,
+ onFinalize: onFinalize,
+ });
+ Services.wm.addListener(listener);
+
+ window.open(url, message.title, message.option);
+}
+
+function WindowListener(aTitle, aUrl, aCallBackObj) {
+ this.test_title = aTitle;
+ this.test_url = aUrl;
+ this.callback_onSuccess = aCallBackObj.onSuccess;
+ this.callBack_onFinalize = aCallBackObj.onFinalize;
+}
+WindowListener.prototype = {
+
+ test_title: null,
+ test_url: null,
+ callback_onSuccess: null,
+ callBack_onFinalize: null,
+
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+
+ let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ let onLoad = aEvent => {
+ is(domwindow.document.location.href, this.test_url,
+ "Opened Window is expected: "+ this.test_title);
+ if (this.callback_onSuccess) {
+ this.callback_onSuccess();
+ }
+
+ domwindow.removeEventListener("load", onLoad, true);
+
+ // wait for trasition to fullscreen on OSX Lion later
+ if (isOSX) {
+ setTimeout(function() {
+ domwindow.close();
+ executeSoon(this.callBack_onFinalize);
+ }.bind(this), 3000);
+ }
+ else {
+ domwindow.close();
+ executeSoon(this.callBack_onFinalize);
+ }
+ };
+ domwindow.addEventListener("load", onLoad, true);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {},
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediatorListener,
+ Ci.nsISupports]),
+};
diff --git a/browser/base/content/test/general/browser_fxa_migrate.js b/browser/base/content/test/general/browser_fxa_migrate.js
new file mode 100644
index 000000000..2faf9fb10
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_migrate.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const STATE_CHANGED_TOPIC = "fxa-migration:state-changed";
+const NOTIFICATION_TITLE = "fxa-migration";
+
+var imports = {};
+Cu.import("resource://services-sync/FxaMigrator.jsm", imports);
+
+add_task(function* test() {
+ // Fake the state where we saw an EOL notification.
+ Services.obs.notifyObservers(null, STATE_CHANGED_TOPIC, null);
+
+ let notificationBox = document.getElementById("global-notificationbox");
+ Assert.ok(notificationBox.allNotifications.some(n => {
+ return n.getAttribute("value") == NOTIFICATION_TITLE;
+ }), "Disconnect notification should be present");
+});
diff --git a/browser/base/content/test/general/browser_fxa_oauth.html b/browser/base/content/test/general/browser_fxa_oauth.html
new file mode 100644
index 000000000..b31e7ceb4
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_oauth.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>fxa_oauth_test</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ // Note: This intentionally sends an object instead of a string, to ensure both work
+ // (see browser_fxa_oauth_with_keys.html for the other test)
+ detail: {
+ id: "oauth_client_id",
+ message: {
+ command: "oauth_complete",
+ data: {
+ state: "state",
+ code: "code1",
+ closeWindow: "signin",
+ },
+ },
+ },
+ });
+
+ window.dispatchEvent(event);
+ };
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_fxa_oauth.js b/browser/base/content/test/general/browser_fxa_oauth.js
new file mode 100644
index 000000000..1f688bfa8
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_oauth.js
@@ -0,0 +1,327 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.docShell is null");
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthClient",
+ "resource://gre/modules/FxAccountsOAuthClient.jsm");
+
+const HTTP_PATH = "http://example.com";
+const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_fxa_oauth.html";
+const HTTP_ENDPOINT_WITH_KEYS = "/browser/browser/base/content/test/general/browser_fxa_oauth_with_keys.html";
+
+var gTests = [
+ {
+ desc: "FxA OAuth - should open a new tab, complete OAuth flow",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+ let properURL = "http://example.com/browser/browser/base/content/test/general/browser_fxa_oauth.html";
+ let queryStrings = [
+ "action=signin",
+ "client_id=client_id",
+ "scope=",
+ "state=state",
+ "webChannelId=oauth_client_id",
+ ];
+ queryStrings.sort();
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+ Assert.ok(gBrowser.currentURI.spec.split("?")[0], properURL, "Check URL without params");
+ let actualURL = new URL(gBrowser.currentURI.spec);
+ let actualQueryStrings = actualURL.search.substring(1).split("&");
+ actualQueryStrings.sort();
+ Assert.equal(actualQueryStrings.length, queryStrings.length, "Check number of params");
+
+ for (let i = 0; i < queryStrings.length; i++) {
+ Assert.equal(actualQueryStrings[i], queryStrings[i], "Check parameter " + i);
+ }
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ },
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = function(tokenData) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should open a new tab, complete OAuth flow when forcing auth",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+ let properURL = "http://example.com/browser/browser/base/content/test/general/browser_fxa_oauth.html";
+ let queryStrings = [
+ "action=force_auth",
+ "client_id=client_id",
+ "scope=",
+ "state=state",
+ "webChannelId=oauth_client_id",
+ "email=test%40invalid.com",
+ ];
+ queryStrings.sort();
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+ Assert.ok(gBrowser.currentURI.spec.split("?")[0], properURL, "Check URL without params");
+
+ let actualURL = new URL(gBrowser.currentURI.spec);
+ let actualQueryStrings = actualURL.search.substring(1).split("&");
+ actualQueryStrings.sort();
+ Assert.equal(actualQueryStrings.length, queryStrings.length, "Check number of params");
+
+ for (let i = 0; i < queryStrings.length; i++) {
+ Assert.equal(actualQueryStrings[i], queryStrings[i], "Check parameter " + i);
+ }
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ action: "force_auth",
+ email: "test@invalid.com"
+ },
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = function(tokenData) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should receive an error when there's a state mismatch",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should have passed in the expected non-matching state value.
+ let queryString = gBrowser.currentURI.spec.split("?")[1];
+ Assert.ok(queryString.indexOf('state=different-state') >= 0);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "different-state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ },
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = reject;
+
+ client.onError = function(err) {
+ Assert.ok(tabOpened);
+ Assert.equal(err.message, "OAuth flow failed. State doesn't match");
+ resolve();
+ };
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should be able to request keys during OAuth flow",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should have asked for keys.
+ let queryString = gBrowser.currentURI.spec.split('?')[1];
+ Assert.ok(queryString.indexOf('keys=true') >= 0);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ keys: true,
+ },
+ authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS
+ });
+
+ client.onComplete = function(tokenData, keys) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ Assert.deepEqual(keys.kAr, {k: "kAr"});
+ Assert.deepEqual(keys.kBr, {k: "kBr"});
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should not receive keys if not explicitly requested",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should not have asked for keys.
+ let queryString = gBrowser.currentURI.spec.split('?')[1];
+ Assert.ok(queryString.indexOf('keys=true') == -1);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH
+ },
+ // This endpoint will cause the completion message to contain keys.
+ authorizationEndpoint: HTTP_ENDPOINT_WITH_KEYS
+ });
+
+ client.onComplete = function(tokenData, keys) {
+ Assert.ok(tabOpened);
+ Assert.equal(tokenData.code, "code1");
+ Assert.equal(tokenData.state, "state");
+ Assert.strictEqual(keys, undefined);
+ resolve();
+ };
+
+ client.onError = reject;
+
+ client.launchWebFlow();
+ });
+ }
+ },
+ {
+ desc: "FxA OAuth - should receive an error if keys could not be obtained",
+ run: function () {
+ return new Promise(function(resolve, reject) {
+ let tabOpened = false;
+
+ waitForTab(function (tab) {
+ Assert.ok("Tab successfully opened");
+
+ // It should have asked for keys.
+ let queryString = gBrowser.currentURI.spec.split('?')[1];
+ Assert.ok(queryString.indexOf('keys=true') >= 0);
+
+ tabOpened = true;
+ });
+
+ let client = new FxAccountsOAuthClient({
+ parameters: {
+ state: "state",
+ client_id: "client_id",
+ oauth_uri: HTTP_PATH,
+ content_uri: HTTP_PATH,
+ keys: true,
+ },
+ // This endpoint will cause the completion message not to contain keys.
+ authorizationEndpoint: HTTP_ENDPOINT
+ });
+
+ client.onComplete = reject;
+
+ client.onError = function(err) {
+ Assert.ok(tabOpened);
+ Assert.equal(err.message, "OAuth flow failed. Keys were not returned");
+ resolve();
+ };
+
+ client.launchWebFlow();
+ });
+ }
+ }
+]; // gTests
+
+function waitForTab(aCallback) {
+ let container = gBrowser.tabContainer;
+ container.addEventListener("TabOpen", function tabOpener(event) {
+ container.removeEventListener("TabOpen", tabOpener, false);
+ gBrowser.addEventListener("load", function listener() {
+ gBrowser.removeEventListener("load", listener, true);
+ let tab = event.target;
+ aCallback(tab);
+ }, true);
+ }, false);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " http://example.com";
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ try {
+ for (let testCase of gTests) {
+ info("Running: " + testCase.desc);
+ yield testCase.run();
+ }
+ } finally {
+ Services.prefs.clearUserPref(webchannelWhitelistPref);
+ }
+ }).then(finish, ex => {
+ Assert.ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_fxa_oauth_with_keys.html b/browser/base/content/test/general/browser_fxa_oauth_with_keys.html
new file mode 100644
index 000000000..2c28f7088
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_oauth_with_keys.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>fxa_oauth_test</title>
+</head>
+<body>
+<script>
+ window.onload = function() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ // Note: This intentionally sends a string instead of an object, to ensure both work
+ // (see browser_fxa_oauth.html for the other test)
+ detail: JSON.stringify({
+ id: "oauth_client_id",
+ message: {
+ command: "oauth_complete",
+ data: {
+ state: "state",
+ code: "code1",
+ closeWindow: "signin",
+ // Keys normally contain more information, but this is enough
+ // to keep Loop's tests happy.
+ keys: { kAr: { k: 'kAr' }, kBr: { k: 'kBr' }},
+ },
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ };
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_fxa_web_channel.html b/browser/base/content/test/general/browser_fxa_web_channel.html
new file mode 100644
index 000000000..be5631ff1
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_web_channel.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>fxa_web_channel_test</title>
+</head>
+<body>
+<script>
+ var webChannelId = "account_updates_test";
+
+ window.onload = function() {
+ var testName = window.location.search.replace(/^\?/, "");
+
+ switch (testName) {
+ case "profile_change":
+ test_profile_change();
+ break;
+ case "login":
+ test_login();
+ break;
+ case "can_link_account":
+ test_can_link_account();
+ break;
+ case "logout":
+ test_logout();
+ break;
+ case "delete":
+ test_delete();
+ break;
+ }
+ };
+
+ function test_profile_change() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "profile:change",
+ data: {
+ uid: "abc123",
+ },
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_login() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:login",
+ data: {
+ authAt: Date.now(),
+ email: "testuser@testuser.com",
+ keyFetchToken: 'key_fetch_token',
+ sessionToken: 'session_token',
+ uid: 'uid',
+ unwrapBKey: 'unwrap_b_key',
+ verified: true,
+ },
+ messageId: 1,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_can_link_account() {
+ window.addEventListener("WebChannelMessageToContent", function (e) {
+ // echo any responses from the browser back to the tests on the
+ // fxaccounts_webchannel_response_echo WebChannel. The tests are
+ // listening for events and do the appropriate checks.
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: 'fxaccounts_webchannel_response_echo',
+ message: e.detail.message,
+ })
+ });
+
+ window.dispatchEvent(event);
+ }, true);
+
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:can_link_account",
+ data: {
+ email: "testuser@testuser.com",
+ },
+ messageId: 2,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_logout() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:logout",
+ data: {
+ uid: 'uid'
+ },
+ messageId: 3,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_delete() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: webChannelId,
+ message: {
+ command: "fxaccounts:delete",
+ data: {
+ uid: 'uid'
+ },
+ messageId: 4,
+ },
+ }),
+ });
+
+ window.dispatchEvent(event);
+ }
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_fxa_web_channel.js b/browser/base/content/test/general/browser_fxa_web_channel.js
new file mode 100644
index 000000000..eb0167ffb
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_web_channel.js
@@ -0,0 +1,210 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
+ return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
+ "resource://gre/modules/WebChannel.jsm");
+
+// FxAccountsWebChannel isn't explicitly exported by FxAccountsWebChannel.jsm
+// but we can get it here via a backstage pass.
+var {FxAccountsWebChannel} = Components.utils.import("resource://gre/modules/FxAccountsWebChannel.jsm", {});
+
+const TEST_HTTP_PATH = "http://example.com";
+const TEST_BASE_URL = TEST_HTTP_PATH + "/browser/browser/base/content/test/general/browser_fxa_web_channel.html";
+const TEST_CHANNEL_ID = "account_updates_test";
+
+var gTests = [
+ {
+ desc: "FxA Web Channel - should receive message about profile changes",
+ run: function* () {
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ });
+ let promiseObserver = new Promise((resolve, reject) => {
+ makeObserver(FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
+ Assert.equal(data, "abc123");
+ client.tearDown();
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?profile_change"
+ }, function* () {
+ yield promiseObserver;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - login messages should notify the fxAccounts object",
+ run: function* () {
+
+ let promiseLogin = new Promise((resolve, reject) => {
+ let login = (accountData) => {
+ Assert.equal(typeof accountData.authAt, 'number');
+ Assert.equal(accountData.email, 'testuser@testuser.com');
+ Assert.equal(accountData.keyFetchToken, 'key_fetch_token');
+ Assert.equal(accountData.sessionToken, 'session_token');
+ Assert.equal(accountData.uid, 'uid');
+ Assert.equal(accountData.unwrapBKey, 'unwrap_b_key');
+ Assert.equal(accountData.verified, true);
+
+ client.tearDown();
+ resolve();
+ };
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ login: login
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?login"
+ }, function* () {
+ yield promiseLogin;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - can_link_account messages should respond",
+ run: function* () {
+ let properUrl = TEST_BASE_URL + "?can_link_account";
+
+ let promiseEcho = new Promise((resolve, reject) => {
+
+ let webChannelOrigin = Services.io.newURI(properUrl, null, null);
+ // responses sent to content are echoed back over the
+ // `fxaccounts_webchannel_response_echo` channel. Ensure the
+ // fxaccounts:can_link_account message is responded to.
+ let echoWebChannel = new WebChannel('fxaccounts_webchannel_response_echo', webChannelOrigin);
+ echoWebChannel.listen((webChannelId, message, target) => {
+ Assert.equal(message.command, 'fxaccounts:can_link_account');
+ Assert.equal(message.messageId, 2);
+ Assert.equal(message.data.ok, true);
+
+ client.tearDown();
+ echoWebChannel.stopListening();
+
+ resolve();
+ });
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ shouldAllowRelink(acctName) {
+ return acctName === 'testuser@testuser.com';
+ }
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: properUrl
+ }, function* () {
+ yield promiseEcho;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - logout messages should notify the fxAccounts object",
+ run: function* () {
+ let promiseLogout = new Promise((resolve, reject) => {
+ let logout = (uid) => {
+ Assert.equal(uid, 'uid');
+
+ client.tearDown();
+ resolve();
+ };
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ logout: logout
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?logout"
+ }, function* () {
+ yield promiseLogout;
+ });
+ }
+ },
+ {
+ desc: "fxa web channel - delete messages should notify the fxAccounts object",
+ run: function* () {
+ let promiseDelete = new Promise((resolve, reject) => {
+ let logout = (uid) => {
+ Assert.equal(uid, 'uid');
+
+ client.tearDown();
+ resolve();
+ };
+
+ let client = new FxAccountsWebChannel({
+ content_uri: TEST_HTTP_PATH,
+ channel_id: TEST_CHANNEL_ID,
+ helpers: {
+ logout: logout
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: TEST_BASE_URL + "?delete"
+ }, function* () {
+ yield promiseDelete;
+ });
+ }
+ }
+]; // gTests
+
+function makeObserver(aObserveTopic, aObserveFunc) {
+ let callback = function (aSubject, aTopic, aData) {
+ if (aTopic == aObserveTopic) {
+ removeMe();
+ aObserveFunc(aSubject, aTopic, aData);
+ }
+ };
+
+ function removeMe() {
+ Services.obs.removeObserver(callback, aObserveTopic);
+ }
+
+ Services.obs.addObserver(callback, aObserveTopic, false);
+ return removeMe;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info("Running: " + testCase.desc);
+ yield testCase.run();
+ }
+ }).then(finish, ex => {
+ Assert.ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_fxaccounts.js b/browser/base/content/test/general/browser_fxaccounts.js
new file mode 100644
index 000000000..0f68286dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxaccounts.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
+var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+var {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
+var FxAccountsCommon = {};
+Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
+
+const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/";
+
+// instrument gFxAccounts to send observer notifications when it's done
+// what it does.
+(function() {
+ let unstubs = {}; // The original functions we stub out.
+
+ // The stub functions.
+ let stubs = {
+ updateAppMenuItem: function() {
+ return unstubs['updateAppMenuItem'].call(gFxAccounts).then(() => {
+ Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateAppMenuItem", null);
+ });
+ },
+ // Opening preferences is trickier than it should be as leaks are reported
+ // due to the promises it fires off at load time and there's no clear way to
+ // know when they are done.
+ // So just ensure openPreferences is called rather than whether it opens.
+ openPreferences: function() {
+ Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences", null);
+ }
+ };
+
+ for (let name in stubs) {
+ unstubs[name] = gFxAccounts[name];
+ gFxAccounts[name] = stubs[name];
+ }
+ // and undo our damage at the end.
+ registerCleanupFunction(() => {
+ for (let name in unstubs) {
+ gFxAccounts[name] = unstubs[name];
+ }
+ stubs = unstubs = null;
+ });
+})();
+
+// Other setup/cleanup
+var newTab;
+
+Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri",
+ TEST_ROOT + "accounts_testRemoteCommands.html");
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
+ Services.prefs.clearUserPref("identity.fxaccounts.remote.profile.uri");
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function* initialize() {
+ // Set a new tab with something other than about:blank, so it doesn't get reused.
+ // We must wait for it to load or the promiseTabOpen() call in the next test
+ // gets confused.
+ newTab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla", {animate: false});
+ yield promiseTabLoaded(newTab);
+});
+
+// The elements we care about.
+var panelUILabel = document.getElementById("PanelUI-fxa-label");
+var panelUIStatus = document.getElementById("PanelUI-fxa-status");
+var panelUIFooter = document.getElementById("PanelUI-footer-fxa");
+
+// The tests
+add_task(function* test_nouser() {
+ let user = yield fxAccounts.getSignedInUser();
+ Assert.strictEqual(user, null, "start with no user signed in");
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+ Services.obs.notifyObservers(null, this.FxAccountsCommon.ONLOGOUT_NOTIFICATION, null);
+ yield promiseUpdateDone;
+
+ // Check the world - the FxA footer area is visible as it is offering a signin.
+ Assert.ok(isFooterVisible())
+
+ Assert.equal(panelUILabel.getAttribute("label"), panelUIStatus.getAttribute("defaultlabel"));
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"), panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.ok(!panelUIFooter.hasAttribute("fxastatus"), "no fxsstatus when signed out");
+ Assert.ok(!panelUIFooter.hasAttribute("fxaprofileimage"), "no fxaprofileimage when signed out");
+
+ let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+ panelUIStatus.click();
+ yield promisePreferencesOpened;
+});
+
+/*
+XXX - Bug 1191162 - need a better hawk mock story or this will leak in debug builds.
+
+add_task(function* test_unverifiedUser() {
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+ yield setSignedInUser(false); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ // Check the world.
+ Assert.ok(isFooterVisible())
+
+ Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+ let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+ panelUIStatus.click();
+ yield promisePreferencesOpened
+ yield signOut();
+});
+*/
+
+add_task(function* test_verifiedUserEmptyProfile() {
+ // We see 2 updateAppMenuItem() calls - one for the signedInUser and one after
+ // we first fetch the profile. We want them both to fire or we aren't testing
+ // the state we think we are testing.
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
+ gFxAccounts._cachedProfile = null;
+ configureProfileURL({}); // successful but empty profile.
+ yield setSignedInUser(true); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ // Check the world.
+ Assert.ok(isFooterVisible())
+ Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+
+ let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
+ panelUIStatus.click();
+ yield promisePreferencesOpened;
+ yield signOut();
+});
+
+add_task(function* test_verifiedUserDisplayName() {
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 2);
+ gFxAccounts._cachedProfile = null;
+ configureProfileURL({ displayName: "Test User Display Name" });
+ yield setSignedInUser(true); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ Assert.ok(isFooterVisible())
+ Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+ yield signOut();
+});
+
+add_task(function* test_verifiedUserProfileFailure() {
+ // profile failure means only one observer fires.
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem", 1);
+ gFxAccounts._cachedProfile = null;
+ configureProfileURL(null, 500);
+ yield setSignedInUser(true); // this will fire the observer that does the update.
+ yield promiseUpdateDone;
+
+ Assert.ok(isFooterVisible())
+ Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
+ Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
+ panelUIStatus.getAttribute("signedinTooltiptext"));
+ Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
+ yield signOut();
+});
+
+// Helpers.
+function isFooterVisible() {
+ let style = window.getComputedStyle(panelUIFooter);
+ return style.getPropertyValue("display") == "flex";
+}
+
+function configureProfileURL(profile, responseStatus = 200) {
+ let responseBody = profile ? JSON.stringify(profile) : "";
+ let url = TEST_ROOT + "fxa_profile_handler.sjs?" +
+ "responseStatus=" + responseStatus +
+ "responseBody=" + responseBody +
+ // This is a bit cheeky - the FxA code will just append "/profile"
+ // to the preference value. We arrange for this to be seen by our
+ // .sjs as part of the query string.
+ "&path=";
+
+ Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", url);
+}
+
+function promiseObserver(topic, count = 1) {
+ return new Promise(resolve => {
+ let obs = (aSubject, aTopic, aData) => {
+ if (--count == 0) {
+ Services.obs.removeObserver(obs, aTopic);
+ resolve(aSubject);
+ }
+ }
+ Services.obs.addObserver(obs, topic, false);
+ });
+}
+
+// Stolen from browser_aboutHome.js
+function promiseWaitForEvent(node, type, capturing) {
+ return new Promise((resolve) => {
+ node.addEventListener(type, function listener(event) {
+ node.removeEventListener(type, listener, capturing);
+ resolve(event);
+ }, capturing);
+ });
+}
+
+var promiseTabOpen = Task.async(function*(urlBase) {
+ info("Waiting for tab to open...");
+ let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
+ let tab = event.target;
+ yield promiseTabLoadEvent(tab);
+ ok(tab.linkedBrowser.currentURI.spec.startsWith(urlBase),
+ "Got " + tab.linkedBrowser.currentURI.spec + ", expecting " + urlBase);
+ let whenUnloaded = promiseTabUnloaded(tab);
+ gBrowser.removeTab(tab);
+ yield whenUnloaded;
+});
+
+function promiseTabUnloaded(tab)
+{
+ return new Promise(resolve => {
+ info("Wait for tab to unload");
+ function handle(event) {
+ tab.linkedBrowser.removeEventListener("unload", handle, true);
+ info("Got unload event");
+ resolve(event);
+ }
+ tab.linkedBrowser.addEventListener("unload", handle, true, true);
+ });
+}
+
+// FxAccounts helpers.
+function setSignedInUser(verified) {
+ let data = {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: verified,
+
+ oauthTokens: {
+ // a token for the profile server.
+ profile: "key value",
+ }
+ }
+ return fxAccounts.setSignedInUser(data);
+}
+
+var signOut = Task.async(function* () {
+ // This test needs to make sure that any updates for the logout have
+ // completed before starting the next test, or we see the observer
+ // notifications get out of sync.
+ let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateAppMenuItem");
+ // we always want a "localOnly" signout here...
+ yield fxAccounts.signOut(true);
+ yield promiseUpdateDone;
+});
diff --git a/browser/base/content/test/general/browser_gZipOfflineChild.js b/browser/base/content/test/general/browser_gZipOfflineChild.js
new file mode 100644
index 000000000..09691bed8
--- /dev/null
+++ b/browser/base/content/test/general/browser_gZipOfflineChild.js
@@ -0,0 +1,80 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/test_offline_gzip.html"
+
+registerCleanupFunction(function() {
+ // Clean up after ourself
+ let uri = Services.io.newURI(URL, null, null);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ Services.perms.removeFromPrincipal(principal, "offline-app");
+ Services.prefs.clearUserPref("offline-apps.allow_by_default");
+});
+
+var cacheCount = 0;
+var intervalID = 0;
+
+//
+// Handle "message" events which are posted from the iframe upon
+// offline cache events.
+//
+function handleMessageEvents(event) {
+ cacheCount++;
+ switch (cacheCount) {
+ case 1:
+ // This is the initial caching off offline data.
+ is(event.data, "oncache", "Child was successfully cached.");
+ // Reload the frame; this will generate an error message
+ // in the case of bug 501422.
+ event.source.location.reload();
+ // Use setInterval to repeatedly call a function which
+ // checks that one of two things has occurred: either
+ // the offline cache is udpated (which means our iframe
+ // successfully reloaded), or the string "error" appears
+ // in the iframe, as in the case of bug 501422.
+ intervalID = setInterval(function() {
+ // Sometimes document.body may not exist, and trying to access
+ // it will throw an exception, so handle this case.
+ try {
+ var bodyInnerHTML = event.source.document.body.innerHTML;
+ }
+ catch (e) {
+ bodyInnerHTML = "";
+ }
+ if (cacheCount == 2 || bodyInnerHTML.includes("error")) {
+ clearInterval(intervalID);
+ is(cacheCount, 2, "frame not reloaded successfully");
+ if (cacheCount != 2) {
+ finish();
+ }
+ }
+ }, 100);
+ break;
+ case 2:
+ is(event.data, "onupdate", "Child was successfully updated.");
+ clearInterval(intervalID);
+ finish();
+ break;
+ default:
+ // how'd we get here?
+ ok(false, "cacheCount not 1 or 2");
+ }
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("offline-apps.allow_by_default", true);
+
+ // Open a new tab.
+ gBrowser.selectedTab = gBrowser.addTab(URL);
+ registerCleanupFunction(() => gBrowser.removeCurrentTab());
+
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ let window = gBrowser.selectedBrowser.contentWindow;
+
+ window.addEventListener("message", handleMessageEvents, false);
+ });
+}
diff --git a/browser/base/content/test/general/browser_gestureSupport.js b/browser/base/content/test/general/browser_gestureSupport.js
new file mode 100644
index 000000000..b31cad31d
--- /dev/null
+++ b/browser/base/content/test/general/browser_gestureSupport.js
@@ -0,0 +1,670 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Simple gestures tests
+//
+// These tests require the ability to disable the fact that the
+// Firefox chrome intentionally prevents "simple gesture" events from
+// reaching web content.
+
+var test_utils;
+var test_commandset;
+var test_prefBranch = "browser.gesture.";
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // Disable the default gestures support during the test
+ gGestureSupport.init(false);
+
+ test_utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ // Run the tests of "simple gesture" events generally
+ test_EnsureConstantsAreDisjoint();
+ test_TestEventListeners();
+ test_TestEventCreation();
+
+ // Reenable the default gestures support. The remaining tests target
+ // the Firefox gesture functionality.
+ gGestureSupport.init(true);
+
+ // Test Firefox's gestures support.
+ test_commandset = document.getElementById("mainCommandSet");
+ test_swipeGestures();
+ test_latchedGesture("pinch", "out", "in", "MozMagnifyGesture");
+ test_thresholdGesture("pinch", "out", "in", "MozMagnifyGesture");
+ test_rotateGestures();
+}
+
+var test_eventCount = 0;
+var test_expectedType;
+var test_expectedDirection;
+var test_expectedDelta;
+var test_expectedModifiers;
+var test_expectedClickCount;
+var test_imageTab;
+
+function test_gestureListener(evt)
+{
+ is(evt.type, test_expectedType,
+ "evt.type (" + evt.type + ") does not match expected value");
+ is(evt.target, test_utils.elementFromPoint(20, 20, false, false),
+ "evt.target (" + evt.target + ") does not match expected value");
+ is(evt.clientX, 20,
+ "evt.clientX (" + evt.clientX + ") does not match expected value");
+ is(evt.clientY, 20,
+ "evt.clientY (" + evt.clientY + ") does not match expected value");
+ isnot(evt.screenX, 0,
+ "evt.screenX (" + evt.screenX + ") does not match expected value");
+ isnot(evt.screenY, 0,
+ "evt.screenY (" + evt.screenY + ") does not match expected value");
+
+ is(evt.direction, test_expectedDirection,
+ "evt.direction (" + evt.direction + ") does not match expected value");
+ is(evt.delta, test_expectedDelta,
+ "evt.delta (" + evt.delta + ") does not match expected value");
+
+ is(evt.shiftKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.SHIFT_MASK) != 0,
+ "evt.shiftKey did not match expected value");
+ is(evt.ctrlKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.CONTROL_MASK) != 0,
+ "evt.ctrlKey did not match expected value");
+ is(evt.altKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.ALT_MASK) != 0,
+ "evt.altKey did not match expected value");
+ is(evt.metaKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.META_MASK) != 0,
+ "evt.metaKey did not match expected value");
+
+ if (evt.type == "MozTapGesture") {
+ is(evt.clickCount, test_expectedClickCount, "evt.clickCount does not match");
+ }
+
+ test_eventCount++;
+}
+
+function test_helper1(type, direction, delta, modifiers)
+{
+ // Setup the expected values
+ test_expectedType = type;
+ test_expectedDirection = direction;
+ test_expectedDelta = delta;
+ test_expectedModifiers = modifiers;
+
+ let expectedEventCount = test_eventCount + 1;
+
+ document.addEventListener(type, test_gestureListener, true);
+ test_utils.sendSimpleGestureEvent(type, 20, 20, direction, delta, modifiers);
+ document.removeEventListener(type, test_gestureListener, true);
+
+ is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
+}
+
+function test_clicks(type, clicks)
+{
+ // Setup the expected values
+ test_expectedType = type;
+ test_expectedDirection = 0;
+ test_expectedDelta = 0;
+ test_expectedModifiers = 0;
+ test_expectedClickCount = clicks;
+
+ let expectedEventCount = test_eventCount + 1;
+
+ document.addEventListener(type, test_gestureListener, true);
+ test_utils.sendSimpleGestureEvent(type, 20, 20, 0, 0, 0, clicks);
+ document.removeEventListener(type, test_gestureListener, true);
+
+ is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
+}
+
+function test_TestEventListeners()
+{
+ let e = test_helper1; // easier to type this name
+
+ // Swipe gesture animation events
+ e("MozSwipeGestureStart", 0, -0.7, 0);
+ e("MozSwipeGestureUpdate", 0, -0.4, 0);
+ e("MozSwipeGestureEnd", 0, 0, 0);
+ e("MozSwipeGestureStart", 0, 0.6, 0);
+ e("MozSwipeGestureUpdate", 0, 0.3, 0);
+ e("MozSwipeGestureEnd", 0, 1, 0);
+
+ // Swipe gesture event
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_UP, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_DOWN, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+
+ // magnify gesture events
+ e("MozMagnifyGestureStart", 0, 50.0, 0);
+ e("MozMagnifyGestureUpdate", 0, -25.0, 0);
+ e("MozMagnifyGestureUpdate", 0, 5.0, 0);
+ e("MozMagnifyGesture", 0, 30.0, 0);
+
+ // rotate gesture events
+ e("MozRotateGestureStart", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);
+ e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE, -13.0, 0);
+ e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_CLOCKWISE, 13.0, 0);
+ e("MozRotateGesture", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);
+
+ // Tap and presstap gesture events
+ test_clicks("MozTapGesture", 1);
+ test_clicks("MozTapGesture", 2);
+ test_clicks("MozTapGesture", 3);
+ test_clicks("MozPressTapGesture", 1);
+
+ // simple delivery test for edgeui gestures
+ e("MozEdgeUIStarted", 0, 0, 0);
+ e("MozEdgeUICanceled", 0, 0, 0);
+ e("MozEdgeUICompleted", 0, 0, 0);
+
+ // event.shiftKey
+ let modifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.metaKey
+ modifier = Components.interfaces.nsIDOMEvent.META_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.altKey
+ modifier = Components.interfaces.nsIDOMEvent.ALT_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.ctrlKey
+ modifier = Components.interfaces.nsIDOMEvent.CONTROL_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+}
+
+function test_eventDispatchListener(evt)
+{
+ test_eventCount++;
+ evt.stopPropagation();
+}
+
+function test_helper2(type, direction, delta, altKey, ctrlKey, shiftKey, metaKey)
+{
+ let event = null;
+ let successful;
+
+ try {
+ event = document.createEvent("SimpleGestureEvent");
+ successful = true;
+ }
+ catch (ex) {
+ successful = false;
+ }
+ ok(successful, "Unable to create SimpleGestureEvent");
+
+ try {
+ event.initSimpleGestureEvent(type, true, true, window, 1,
+ 10, 10, 10, 10,
+ ctrlKey, altKey, shiftKey, metaKey,
+ 1, window,
+ 0, direction, delta, 0);
+ successful = true;
+ }
+ catch (ex) {
+ successful = false;
+ }
+ ok(successful, "event.initSimpleGestureEvent should not fail");
+
+ // Make sure the event fields match the expected values
+ is(event.type, type, "Mismatch on evt.type");
+ is(event.direction, direction, "Mismatch on evt.direction");
+ is(event.delta, delta, "Mismatch on evt.delta");
+ is(event.altKey, altKey, "Mismatch on evt.altKey");
+ is(event.ctrlKey, ctrlKey, "Mismatch on evt.ctrlKey");
+ is(event.shiftKey, shiftKey, "Mismatch on evt.shiftKey");
+ is(event.metaKey, metaKey, "Mismatch on evt.metaKey");
+ is(event.view, window, "Mismatch on evt.view");
+ is(event.detail, 1, "Mismatch on evt.detail");
+ is(event.clientX, 10, "Mismatch on evt.clientX");
+ is(event.clientY, 10, "Mismatch on evt.clientY");
+ is(event.screenX, 10, "Mismatch on evt.screenX");
+ is(event.screenY, 10, "Mismatch on evt.screenY");
+ is(event.button, 1, "Mismatch on evt.button");
+ is(event.relatedTarget, window, "Mismatch on evt.relatedTarget");
+
+ // Test event dispatch
+ let expectedEventCount = test_eventCount + 1;
+ document.addEventListener(type, test_eventDispatchListener, true);
+ document.dispatchEvent(event);
+ document.removeEventListener(type, test_eventDispatchListener, true);
+ is(expectedEventCount, test_eventCount, "Dispatched event was never received by listener");
+}
+
+function test_TestEventCreation()
+{
+ // Event creation
+ test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_RIGHT, 20.0,
+ true, false, true, false);
+ test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_LEFT, -20.0,
+ false, true, false, true);
+}
+
+function test_EnsureConstantsAreDisjoint()
+{
+ let up = SimpleGestureEvent.DIRECTION_UP;
+ let down = SimpleGestureEvent.DIRECTION_DOWN;
+ let left = SimpleGestureEvent.DIRECTION_LEFT;
+ let right = SimpleGestureEvent.DIRECTION_RIGHT;
+
+ let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;
+ let cclockwise = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;
+
+ ok(up ^ down, "DIRECTION_UP and DIRECTION_DOWN are not bitwise disjoint");
+ ok(up ^ left, "DIRECTION_UP and DIRECTION_LEFT are not bitwise disjoint");
+ ok(up ^ right, "DIRECTION_UP and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(down ^ left, "DIRECTION_DOWN and DIRECTION_LEFT are not bitwise disjoint");
+ ok(down ^ right, "DIRECTION_DOWN and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(left ^ right, "DIRECTION_LEFT and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(clockwise ^ cclockwise, "ROTATION_CLOCKWISE and ROTATION_COUNTERCLOCKWISE are not bitwise disjoint");
+}
+
+// Helper for test of latched event processing. Emits the actual
+// gesture events to test whether the commands associated with the
+// gesture will only trigger once for each direction of movement.
+function test_emitLatchedEvents(eventPrefix, initialDelta, cmd)
+{
+ let cumulativeDelta = 0;
+ let isIncreasing = initialDelta > 0;
+
+ let expect = {};
+ // Reset the call counters and initialize expected values
+ for (let dir in cmd)
+ cmd[dir].callCount = expect[dir] = 0;
+
+ let check = (aDir, aMsg) => ok(cmd[aDir].callCount == expect[aDir], aMsg);
+ let checkBoth = function(aNum, aInc, aDec) {
+ let prefix = "Step " + aNum + ": ";
+ check("inc", prefix + aInc);
+ check("dec", prefix + aDec);
+ };
+
+ // Send the "Start" event.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, initialDelta, 0);
+ cumulativeDelta += initialDelta;
+ if (isIncreasing) {
+ expect.inc++;
+ checkBoth(1, "Increasing command was not triggered", "Decreasing command was triggered");
+ } else {
+ expect.dec++;
+ checkBoth(1, "Increasing command was triggered", "Decreasing command was not triggered");
+ }
+
+ // Send random values in the same direction and ensure neither
+ // command triggers.
+ for (let i = 0; i < 5; i++) {
+ let delta = Math.random() * (isIncreasing ? 100 : -100);
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
+ cumulativeDelta += delta;
+ checkBoth(2, "Increasing command was triggered", "Decreasing command was triggered");
+ }
+
+ // Now go back in the opposite direction.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
+ - initialDelta, 0);
+ cumulativeDelta += - initialDelta;
+ if (isIncreasing) {
+ expect.dec++;
+ checkBoth(3, "Increasing command was triggered", "Decreasing command was not triggered");
+ } else {
+ expect.inc++;
+ checkBoth(3, "Increasing command was not triggered", "Decreasing command was triggered");
+ }
+
+ // Send random values in the opposite direction and ensure neither
+ // command triggers.
+ for (let i = 0; i < 5; i++) {
+ let delta = Math.random() * (isIncreasing ? -100 : 100);
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
+ cumulativeDelta += delta;
+ checkBoth(4, "Increasing command was triggered", "Decreasing command was triggered");
+ }
+
+ // Go back to the original direction. The original command should trigger.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
+ initialDelta, 0);
+ cumulativeDelta += initialDelta;
+ if (isIncreasing) {
+ expect.inc++;
+ checkBoth(5, "Increasing command was not triggered", "Decreasing command was triggered");
+ } else {
+ expect.dec++;
+ checkBoth(5, "Increasing command was triggered", "Decreasing command was not triggered");
+ }
+
+ // Send the wrap-up event. No commands should be triggered.
+ test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, cumulativeDelta, 0);
+ checkBoth(6, "Increasing command was triggered", "Decreasing command was triggered");
+}
+
+function test_addCommand(prefName, id)
+{
+ let cmd = test_commandset.appendChild(document.createElement("command"));
+ cmd.setAttribute("id", id);
+ cmd.setAttribute("oncommand", "this.callCount++;");
+
+ cmd.origPrefName = prefName;
+ cmd.origPrefValue = gPrefService.getCharPref(prefName);
+ gPrefService.setCharPref(prefName, id);
+
+ return cmd;
+}
+
+function test_removeCommand(cmd)
+{
+ gPrefService.setCharPref(cmd.origPrefName, cmd.origPrefValue);
+ test_commandset.removeChild(cmd);
+}
+
+// Test whether latched events are only called once per direction of motion.
+function test_latchedGesture(gesture, inc, dec, eventPrefix)
+{
+ let branch = test_prefBranch + gesture + ".";
+
+ // Put the gesture into latched mode.
+ let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
+ gPrefService.setBoolPref(branch + "latched", true);
+
+ // Install the test commands for increasing and decreasing motion.
+ let cmd = {
+ inc: test_addCommand(branch + inc, "test:incMotion"),
+ dec: test_addCommand(branch + dec, "test:decMotion"),
+ };
+
+ // Test the gestures in each direction.
+ test_emitLatchedEvents(eventPrefix, 500, cmd);
+ test_emitLatchedEvents(eventPrefix, -500, cmd);
+
+ // Restore the gesture to its original configuration.
+ gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
+ for (let dir in cmd)
+ test_removeCommand(cmd[dir]);
+}
+
+// Test whether non-latched events are triggered upon sufficient motion.
+function test_thresholdGesture(gesture, inc, dec, eventPrefix)
+{
+ let branch = test_prefBranch + gesture + ".";
+
+ // Disable latched mode for this gesture.
+ let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
+ gPrefService.setBoolPref(branch + "latched", false);
+
+ // Set the triggering threshold value to 50.
+ let oldThresholdValue = gPrefService.getIntPref(branch + "threshold");
+ gPrefService.setIntPref(branch + "threshold", 50);
+
+ // Install the test commands for increasing and decreasing motion.
+ let cmdInc = test_addCommand(branch + inc, "test:incMotion");
+ let cmdDec = test_addCommand(branch + dec, "test:decMotion");
+
+ // Send the start event but stop short of triggering threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, 49.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Now trigger the threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, 1, 0);
+ ok(cmdInc.callCount == 1, "Increasing command was not triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // The tracking counter should go to zero. Go back the other way and
+ // stop short of triggering the threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -49.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Now cross the threshold and trigger the decreasing command.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -1.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 1, "Decreasing command was not triggered");
+
+ // Send the wrap-up event. No commands should trigger.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, -0.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Restore the gesture to its original configuration.
+ gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
+ gPrefService.setIntPref(branch + "threshold", oldThresholdValue);
+ test_removeCommand(cmdInc);
+ test_removeCommand(cmdDec);
+}
+
+function test_swipeGestures()
+{
+ // easier to type names for the direction constants
+ let up = SimpleGestureEvent.DIRECTION_UP;
+ let down = SimpleGestureEvent.DIRECTION_DOWN;
+ let left = SimpleGestureEvent.DIRECTION_LEFT;
+ let right = SimpleGestureEvent.DIRECTION_RIGHT;
+
+ let branch = test_prefBranch + "swipe.";
+
+ // Install the test commands for the swipe gestures.
+ let cmdUp = test_addCommand(branch + "up", "test:swipeUp");
+ let cmdDown = test_addCommand(branch + "down", "test:swipeDown");
+ let cmdLeft = test_addCommand(branch + "left", "test:swipeLeft");
+ let cmdRight = test_addCommand(branch + "right", "test:swipeRight");
+
+ function resetCounts() {
+ cmdUp.callCount = 0;
+ cmdDown.callCount = 0;
+ cmdLeft.callCount = 0;
+ cmdRight.callCount = 0;
+ }
+
+ // UP
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, up, 0, 0);
+ ok(cmdUp.callCount == 1, "Step 1: Up command was not triggered");
+ ok(cmdDown.callCount == 0, "Step 1: Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 1: Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 1: Right command was triggered");
+
+ // DOWN
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, down, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 2: Up command was triggered");
+ ok(cmdDown.callCount == 1, "Step 2: Down command was not triggered");
+ ok(cmdLeft.callCount == 0, "Step 2: Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 2: Right command was triggered");
+
+ // LEFT
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, left, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 3: Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 3: Down command was triggered");
+ ok(cmdLeft.callCount == 1, "Step 3: Left command was not triggered");
+ ok(cmdRight.callCount == 0, "Step 3: Right command was triggered");
+
+ // RIGHT
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, right, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 4: Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 4: Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 4: Left command was triggered");
+ ok(cmdRight.callCount == 1, "Step 4: Right command was not triggered");
+
+ // Make sure combinations do not trigger events.
+ let combos = [ up | left, up | right, down | left, down | right];
+ for (let i = 0; i < combos.length; i++) {
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, combos[i], 0, 0);
+ ok(cmdUp.callCount == 0, "Step 5-"+i+": Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 5-"+i+": Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 5-"+i+": Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 5-"+i+": Right command was triggered");
+ }
+
+ // Remove the test commands.
+ test_removeCommand(cmdUp);
+ test_removeCommand(cmdDown);
+ test_removeCommand(cmdLeft);
+ test_removeCommand(cmdRight);
+}
+
+
+function test_rotateHelperGetImageRotation(aImageElement)
+{
+ // Get the true image rotation from the transform matrix, bounded
+ // to 0 <= result < 360
+ let transformValue = content.window.getComputedStyle(aImageElement, null)
+ .transform;
+ if (transformValue == "none")
+ return 0;
+
+ transformValue = transformValue.split("(")[1]
+ .split(")")[0]
+ .split(",");
+ var rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
+ (180 / Math.PI));
+ return (rotation < 0 ? rotation + 360 : rotation);
+}
+
+function test_rotateHelperOneGesture(aImageElement, aCurrentRotation,
+ aDirection, aAmount, aStop)
+{
+ if (aAmount <= 0 || aAmount > 90) // Bound to 0 < aAmount <= 90
+ return;
+
+ // easier to type names for the direction constants
+ let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;
+
+ let delta = aAmount * (aDirection == clockwise ? 1 : -1);
+
+ // Kill transition time on image so test isn't wrong and doesn't take 10 seconds
+ aImageElement.style.transitionDuration = "0s";
+
+ // Start the gesture, perform an update, and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, aDirection, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, delta, 0);
+ aImageElement.clientTop;
+
+ // If stop, check intermediate
+ if (aStop) {
+ // Send near-zero-delta to stop, and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, .001, 0);
+ aImageElement.clientTop;
+
+ let stopExpectedRotation = (aCurrentRotation + delta) % 360;
+ if (stopExpectedRotation < 0)
+ stopExpectedRotation += 360;
+
+ is(stopExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
+ "Image rotation at gesture stop/hold: expected=" + stopExpectedRotation +
+ ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
+ ", init=" + aCurrentRotation +
+ ", amt=" + aAmount +
+ ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
+ }
+ // End it and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, aDirection, 0, 0);
+ aImageElement.clientTop;
+
+ let finalExpectedRotation;
+
+ if (aAmount < 45 && aStop) {
+ // Rotate a bit, then stop. Expect no change at end of gesture.
+ finalExpectedRotation = aCurrentRotation;
+ }
+ else {
+ // Either not stopping (expect 90 degree change in aDirection), OR
+ // stopping but after 45, (expect 90 degree change in aDirection)
+ finalExpectedRotation = (aCurrentRotation +
+ (aDirection == clockwise ? 1 : -1) * 90) % 360;
+ if (finalExpectedRotation < 0)
+ finalExpectedRotation += 360;
+ }
+
+ is(finalExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
+ "Image rotation gesture end: expected=" + finalExpectedRotation +
+ ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
+ ", init=" + aCurrentRotation +
+ ", amt=" + aAmount +
+ ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
+}
+
+function test_rotateGesturesOnTab()
+{
+ gBrowser.selectedBrowser.removeEventListener("load", test_rotateGesturesOnTab, true);
+
+ if (!(content.document instanceof ImageDocument)) {
+ ok(false, "Image document failed to open for rotation testing");
+ gBrowser.removeTab(test_imageTab);
+ finish();
+ return;
+ }
+
+ // easier to type names for the direction constants
+ let cl = SimpleGestureEvent.ROTATION_CLOCKWISE;
+ let ccl = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;
+
+ let imgElem = content.document.body &&
+ content.document.body.firstElementChild;
+
+ if (!imgElem) {
+ ok(false, "Could not get image element on ImageDocument for rotation!");
+ gBrowser.removeTab(test_imageTab);
+ finish();
+ return;
+ }
+
+ // Quick function to normalize rotation to 0 <= r < 360
+ var normRot = function(rotation) {
+ rotation = rotation % 360;
+ if (rotation < 0)
+ rotation += 360;
+ return rotation;
+ }
+
+ for (var initRot = 0; initRot < 360; initRot += 90) {
+ // Test each case: at each 90 degree snap; cl/ccl;
+ // amount more or less than 45; stop and hold or don't (32 total tests)
+ // The amount added to the initRot is where it is expected to be
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), cl, 55, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), cl, 55, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), ccl, 55, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), ccl, 55, false);
+
+ // Manually rotate it 90 degrees clockwise to prepare for next iteration,
+ // and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, cl, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, 90, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, cl, 0, 0);
+ imgElem.clientTop;
+ }
+
+ gBrowser.removeTab(test_imageTab);
+ test_imageTab = null;
+ finish();
+}
+
+function test_rotateGestures()
+{
+ test_imageTab = gBrowser.addTab("chrome://branding/content/about-logo.png");
+ gBrowser.selectedTab = test_imageTab;
+
+ gBrowser.selectedBrowser.addEventListener("load", test_rotateGesturesOnTab, true);
+}
diff --git a/browser/base/content/test/general/browser_getshortcutoruri.js b/browser/base/content/test/general/browser_getshortcutoruri.js
new file mode 100644
index 000000000..9ebf8e9ca
--- /dev/null
+++ b/browser/base/content/test/general/browser_getshortcutoruri.js
@@ -0,0 +1,143 @@
+function getPostDataString(aIS) {
+ if (!aIS)
+ return null;
+
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(aIS);
+ var dataLines = sis.read(aIS.available()).split("\n");
+
+ // only want the last line
+ return dataLines[dataLines.length-1];
+}
+
+function keywordResult(aURL, aPostData, aIsUnsafe) {
+ this.url = aURL;
+ this.postData = aPostData;
+ this.isUnsafe = aIsUnsafe;
+}
+
+function keyWordData() {}
+keyWordData.prototype = {
+ init: function(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.keyword = aKeyWord;
+ this.uri = makeURI(aURL);
+ this.postData = aPostData;
+ this.searchWord = aSearchWord;
+
+ this.method = (this.postData ? "POST" : "GET");
+ }
+}
+
+function bmKeywordData(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.init(aKeyWord, aURL, aPostData, aSearchWord);
+}
+bmKeywordData.prototype = new keyWordData();
+
+function searchKeywordData(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.init(aKeyWord, aURL, aPostData, aSearchWord);
+}
+searchKeywordData.prototype = new keyWordData();
+
+var testData = [
+ [new bmKeywordData("bmget", "http://bmget/search=%s", null, "foo"),
+ new keywordResult("http://bmget/search=foo", null)],
+
+ [new bmKeywordData("bmpost", "http://bmpost/", "search=%s", "foo2"),
+ new keywordResult("http://bmpost/", "search=foo2")],
+
+ [new bmKeywordData("bmpostget", "http://bmpostget/search1=%s", "search2=%s", "foo3"),
+ new keywordResult("http://bmpostget/search1=foo3", "search2=foo3")],
+
+ [new bmKeywordData("bmget-nosearch", "http://bmget-nosearch/", null, ""),
+ new keywordResult("http://bmget-nosearch/", null)],
+
+ [new searchKeywordData("searchget", "http://searchget/?search={searchTerms}", null, "foo4"),
+ new keywordResult("http://searchget/?search=foo4", null, true)],
+
+ [new searchKeywordData("searchpost", "http://searchpost/", "search={searchTerms}", "foo5"),
+ new keywordResult("http://searchpost/", "search=foo5", true)],
+
+ [new searchKeywordData("searchpostget", "http://searchpostget/?search1={searchTerms}", "search2={searchTerms}", "foo6"),
+ new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6", true)],
+
+ // Bookmark keywords that don't take parameters should not be activated if a
+ // parameter is passed (bug 420328).
+ [new bmKeywordData("bmget-noparam", "http://bmget-noparam/", null, "foo7"),
+ new keywordResult(null, null, true)],
+ [new bmKeywordData("bmpost-noparam", "http://bmpost-noparam/", "not_a=param", "foo8"),
+ new keywordResult(null, null, true)],
+
+ // Test escaping (%s = escaped, %S = raw)
+ // UTF-8 default
+ [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "fo"),
+ new keywordResult("http://bmget/?esc=fo%C3%A9&raw=fo", null)],
+ // Explicitly-defined ISO-8859-1
+ [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "fo"),
+ new keywordResult("http://bmget/?esc=fo%E9&raw=fo", null)],
+
+ // Bug 359809: Test escaping +, /, and @
+ // UTF-8 default
+ [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "+/@"),
+ new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
+ // Explicitly-defined ISO-8859-1
+ [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "+/@"),
+ new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
+
+ // Test using a non-bmKeywordData object, to test the behavior of
+ // getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for
+ // bmKeywordData objects)
+ [{keyword: "http://gavinsharp.com"},
+ new keywordResult(null, null, true)]
+];
+
+add_task(function* test_getshortcutoruri() {
+ yield setupKeywords();
+
+ for (let item of testData) {
+ let [data, result] = item;
+
+ let query = data.keyword;
+ if (data.searchWord)
+ query += " " + data.searchWord;
+ let returnedData = yield getShortcutOrURIAndPostData(query);
+ // null result.url means we should expect the same query we sent in
+ let expected = result.url || query;
+ is(returnedData.url, expected, "got correct URL for " + data.keyword);
+ is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword);
+ is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
+ }
+
+ yield cleanupKeywords();
+});
+
+var folder = null;
+var gAddedEngines = [];
+
+function* setupKeywords() {
+ folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "keyword-test" });
+ for (let item of testData) {
+ let data = item[0];
+ if (data instanceof bmKeywordData) {
+ yield PlacesUtils.bookmarks.insert({ url: data.uri, parentGuid: folder.guid });
+ yield PlacesUtils.keywords.insert({ keyword: data.keyword, url: data.uri.spec, postData: data.postData });
+ }
+
+ if (data instanceof searchKeywordData) {
+ Services.search.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec);
+ let addedEngine = Services.search.getEngineByName(data.keyword);
+ if (data.postData) {
+ let [paramName, paramValue] = data.postData.split("=");
+ addedEngine.addParam(paramName, paramValue, null);
+ }
+ gAddedEngines.push(addedEngine);
+ }
+ }
+}
+
+function* cleanupKeywords() {
+ PlacesUtils.bookmarks.remove(folder);
+ gAddedEngines.map(Services.search.removeEngine);
+}
diff --git a/browser/base/content/test/general/browser_hide_removing.js b/browser/base/content/test/general/browser_hide_removing.js
new file mode 100644
index 000000000..be62e2d89
--- /dev/null
+++ b/browser/base/content/test/general/browser_hide_removing.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/. */
+
+// Bug 587922: tabs don't get removed if they're hidden
+
+function test() {
+ waitForExplicitFinish();
+
+ // Add a tab that will get removed and hidden
+ let testTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ is(gBrowser.visibleTabs.length, 2, "just added a tab, so 2 tabs");
+ gBrowser.selectedTab = testTab;
+
+ let numVisBeforeHide, numVisAfterHide;
+ gBrowser.tabContainer.addEventListener("TabSelect", function() {
+ gBrowser.tabContainer.removeEventListener("TabSelect", arguments.callee, false);
+
+ // While the next tab is being selected, hide the removing tab
+ numVisBeforeHide = gBrowser.visibleTabs.length;
+ gBrowser.hideTab(testTab);
+ numVisAfterHide = gBrowser.visibleTabs.length;
+ }, false);
+ gBrowser.removeTab(testTab, {animate: true});
+
+ // Make sure the tab gets removed at the end of the animation by polling
+ (function checkRemoved() {
+ return setTimeout(function() {
+ if (gBrowser.tabs.length != 1) {
+ checkRemoved();
+ return;
+ }
+
+ is(numVisBeforeHide, 1, "animated remove has in 1 tab left");
+ is(numVisAfterHide, 1, "hiding a removing tab is also has 1 tab");
+ finish();
+ }, 50);
+ })();
+}
diff --git a/browser/base/content/test/general/browser_homeDrop.js b/browser/base/content/test/general/browser_homeDrop.js
new file mode 100644
index 000000000..6e87963d5
--- /dev/null
+++ b/browser/base/content/test/general/browser_homeDrop.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function*() {
+ let HOMEPAGE_PREF = "browser.startup.homepage";
+
+ let homepageStr = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ homepageStr.data = "about:mozilla";
+ yield pushPrefs([HOMEPAGE_PREF, homepageStr, Ci.nsISupportsString]);
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // Since synthesizeDrop triggers the srcElement, need to use another button.
+ let dragSrcElement = document.getElementById("downloads-button");
+ ok(dragSrcElement, "Downloads button exists");
+ let homeButton = document.getElementById("home-button");
+ ok(homeButton, "home button present");
+
+ function* drop(dragData, homepage) {
+ let setHomepageDialogPromise = BrowserTestUtils.domWindowOpened();
+
+ EventUtils.synthesizeDrop(dragSrcElement, homeButton, dragData, "copy", window);
+
+ let setHomepageDialog = yield setHomepageDialogPromise;
+ ok(true, "dialog appeared in response to home button drop");
+ yield BrowserTestUtils.waitForEvent(setHomepageDialog, "load", false);
+
+ let setHomepagePromise = new Promise(function(resolve) {
+ let observer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+ observe: function(subject, topic, data) {
+ is(topic, "nsPref:changed", "observed correct topic");
+ is(data, HOMEPAGE_PREF, "observed correct data");
+ let modified = Services.prefs.getComplexValue(HOMEPAGE_PREF,
+ Ci.nsISupportsString);
+ is(modified.data, homepage, "homepage is set correctly");
+ Services.prefs.removeObserver(HOMEPAGE_PREF, observer);
+
+ Services.prefs.setComplexValue(HOMEPAGE_PREF,
+ Ci.nsISupportsString, homepageStr);
+
+ resolve();
+ }
+ };
+ Services.prefs.addObserver(HOMEPAGE_PREF, observer, false);
+ });
+
+ setHomepageDialog.document.documentElement.acceptDialog();
+
+ yield setHomepagePromise;
+ }
+
+ function dropInvalidURI() {
+ return new Promise(resolve => {
+ let consoleListener = {
+ observe: function (m) {
+ if (m.message.includes("NS_ERROR_DOM_BAD_URI")) {
+ ok(true, "drop was blocked");
+ resolve();
+ }
+ }
+ };
+ Services.console.registerListener(consoleListener);
+ registerCleanupFunction(function () {
+ Services.console.unregisterListener(consoleListener);
+ });
+
+ executeSoon(function () {
+ info("Attempting second drop, of a javascript: URI");
+ // The drop handler throws an exception when dragging URIs that inherit
+ // principal, e.g. javascript:
+ expectUncaughtException();
+ EventUtils.synthesizeDrop(dragSrcElement, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window);
+ });
+ });
+ }
+
+ yield* drop([[{type: "text/plain",
+ data: "http://mochi.test:8888/"}]],
+ "http://mochi.test:8888/");
+ yield* drop([[{type: "text/plain",
+ data: "http://mochi.test:8888/\nhttp://mochi.test:8888/b\nhttp://mochi.test:8888/c"}]],
+ "http://mochi.test:8888/|http://mochi.test:8888/b|http://mochi.test:8888/c");
+ yield dropInvalidURI();
+});
+
diff --git a/browser/base/content/test/general/browser_identity_UI.js b/browser/base/content/test/general/browser_identity_UI.js
new file mode 100644
index 000000000..5aacb2e79
--- /dev/null
+++ b/browser/base/content/test/general/browser_identity_UI.js
@@ -0,0 +1,146 @@
+/* Tests for correct behaviour of getEffectiveHost on identity handler */
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+
+ ok(gIdentityHandler, "gIdentityHandler should exist");
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true).then(() => {
+ gBrowser.selectedBrowser.addEventListener("load", checkResult, true);
+ nextTest();
+ });
+}
+
+// Greek IDN for 'example.test'.
+var idnDomain = "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE";
+var tests = [
+ {
+ name: "normal domain",
+ location: "http://test1.example.org/",
+ effectiveHost: "test1.example.org"
+ },
+ {
+ name: "view-source",
+ location: "view-source:http://example.com/",
+ effectiveHost: null
+ },
+ {
+ name: "normal HTTPS",
+ location: "https://example.com/",
+ effectiveHost: "example.com",
+ isHTTPS: true
+ },
+ {
+ name: "IDN subdomain",
+ location: "http://sub1." + idnDomain + "/",
+ effectiveHost: "sub1." + idnDomain
+ },
+ {
+ name: "subdomain with port",
+ location: "http://sub1.test1.example.org:8000/",
+ effectiveHost: "sub1.test1.example.org"
+ },
+ {
+ name: "subdomain HTTPS",
+ location: "https://test1.example.com/",
+ effectiveHost: "test1.example.com",
+ isHTTPS: true
+ },
+ {
+ name: "view-source HTTPS",
+ location: "view-source:https://example.com/",
+ effectiveHost: null,
+ isHTTPS: true
+ },
+ {
+ name: "IP address",
+ location: "http://127.0.0.1:8888/",
+ effectiveHost: "127.0.0.1"
+ },
+]
+
+var gCurrentTest, gCurrentTestIndex = -1, gTestDesc, gPopupHidden;
+// Go through the tests in both directions, to add additional coverage for
+// transitions between different states.
+var gForward = true;
+var gCheckETLD = false;
+function nextTest() {
+ if (!gCheckETLD) {
+ if (gForward)
+ gCurrentTestIndex++;
+ else
+ gCurrentTestIndex--;
+
+ if (gCurrentTestIndex == tests.length) {
+ // Went too far, reverse
+ gCurrentTestIndex--;
+ gForward = false;
+ }
+
+ if (gCurrentTestIndex == -1) {
+ gBrowser.selectedBrowser.removeEventListener("load", checkResult, true);
+ gBrowser.removeCurrentTab();
+ finish();
+ return;
+ }
+
+ gCurrentTest = tests[gCurrentTestIndex];
+ gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + ")";
+ if (!gForward)
+ gTestDesc += " (second time)";
+ if (gCurrentTest.isHTTPS) {
+ gCheckETLD = true;
+ }
+
+ // Navigate to the next page, which will cause checkResult to fire.
+ let spec = gBrowser.selectedBrowser.currentURI.spec;
+ if (spec == "about:blank" || spec == gCurrentTest.location) {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
+ } else {
+ // Open the Control Center and make sure it closes after nav (Bug 1207542).
+ let popupShown = promisePopupShown(gIdentityHandler._identityPopup);
+ gPopupHidden = promisePopupHidden(gIdentityHandler._identityPopup);
+ gIdentityHandler._identityBox.click();
+ info("Waiting for the Control Center to be shown");
+ popupShown.then(() => {
+ is_element_visible(gIdentityHandler._identityPopup, "Control Center is visible");
+ // Show the subview, which is an easy way in automation to reproduce
+ // Bug 1207542, where the CC wouldn't close on navigation.
+ gBrowser.ownerDocument.querySelector("#identity-popup-security-expander").click();
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
+ });
+ }
+ } else {
+ gCheckETLD = false;
+ gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + " without eTLD in identity icon label)";
+ if (!gForward)
+ gTestDesc += " (second time)";
+ gBrowser.selectedBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY);
+ }
+}
+
+function checkResult() {
+ // Sanity check other values, and the value of gIdentityHandler.getEffectiveHost()
+ is(gIdentityHandler._uri.spec, gCurrentTest.location, "location matches for test " + gTestDesc);
+ // getEffectiveHost can't be called for all modes
+ if (gCurrentTest.effectiveHost === null) {
+ let identityBox = document.getElementById("identity-box");
+ ok(identityBox.className == "unknownIdentity" ||
+ identityBox.className == "chromeUI", "mode matched");
+ } else {
+ is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
+ }
+
+ if (gPopupHidden) {
+ info("Waiting for the Control Center to hide");
+ gPopupHidden.then(() => {
+ gPopupHidden = null;
+ is_element_hidden(gIdentityHandler._identityPopup, "control center is hidden");
+ executeSoon(nextTest);
+ });
+ } else {
+ executeSoon(nextTest);
+ }
+}
diff --git a/browser/base/content/test/general/browser_insecureLoginForms.js b/browser/base/content/test/general/browser_insecureLoginForms.js
new file mode 100644
index 000000000..72db7dbe6
--- /dev/null
+++ b/browser/base/content/test/general/browser_insecureLoginForms.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Load directly from the browser-chrome support files of login tests.
+const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
+
+/**
+ * Waits for the given number of occurrences of InsecureLoginFormsStateChange
+ * on the given browser element.
+ */
+function waitForInsecureLoginFormsStateChange(browser, count) {
+ return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
+ false, () => --count == 0);
+}
+
+/**
+ * Checks the insecure login forms logic for the identity block.
+ */
+add_task(function* test_simple() {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+ "set": [["security.insecure_password.ui.enabled", true]],
+ }, resolve));
+
+ for (let [origin, expectWarning] of [
+ ["http://example.com", true],
+ ["http://127.0.0.1", false],
+ ["https://example.com", false],
+ ]) {
+ let testUrlPath = origin + TEST_URL_PATH;
+ let tab = gBrowser.addTab(testUrlPath + "form_basic.html");
+ let browser = tab.linkedBrowser;
+ yield Promise.all([
+ BrowserTestUtils.switchTab(gBrowser, tab),
+ BrowserTestUtils.browserLoaded(browser),
+ // One event is triggered by pageshow and one by DOMFormHasPassword.
+ waitForInsecureLoginFormsStateChange(browser, 2),
+ ]);
+
+ let { gIdentityHandler } = gBrowser.ownerGlobal;
+ gIdentityHandler._identityBox.click();
+ document.getElementById("identity-popup-security-expander").click();
+
+ if (expectWarning) {
+ is_element_visible(document.getElementById("connection-icon"));
+ let connectionIconImage = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("connection-icon"), "")
+ .getPropertyValue("list-style-image");
+ let securityViewBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-securityView"), "")
+ .getPropertyValue("background-image");
+ let securityContentBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-security-content"), "")
+ .getPropertyValue("background-image");
+ is(connectionIconImage,
+ "url(\"chrome://browser/skin/connection-mixed-active-loaded.svg#icon\")",
+ "Using expected icon image in the identity block");
+ is(securityViewBG,
+ "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "Using expected icon image in the Control Center main view");
+ is(securityContentBG,
+ "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "Using expected icon image in the Control Center subview");
+ is(Array.filter(document.querySelectorAll("[observes=identity-popup-insecure-login-forms-learn-more]"),
+ element => !is_hidden(element)).length, 1,
+ "The 'Learn more' link should be visible once.");
+ }
+
+ // Messages should be visible when the scheme is HTTP, and invisible when
+ // the scheme is HTTPS.
+ is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
+ element => !is_hidden(element)),
+ expectWarning,
+ "The relevant messages should be visible or hidden.");
+
+ gIdentityHandler._identityPopup.hidden = true;
+ gBrowser.removeTab(tab);
+ }
+});
+
+/**
+ * Checks that the insecure login forms logic does not regress mixed content
+ * blocking messages when mixed active content is loaded.
+ */
+add_task(function* test_mixedcontent() {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({
+ "set": [["security.mixed_content.block_active_content", false]],
+ }, resolve));
+
+ // Load the page with the subframe in a new tab.
+ let testUrlPath = "://example.com" + TEST_URL_PATH;
+ let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
+ let browser = tab.linkedBrowser;
+ yield Promise.all([
+ BrowserTestUtils.switchTab(gBrowser, tab),
+ BrowserTestUtils.browserLoaded(browser),
+ // Two events are triggered by pageshow and one by DOMFormHasPassword.
+ waitForInsecureLoginFormsStateChange(browser, 3),
+ ]);
+
+ assertMixedContentBlockingState(browser, { activeLoaded: true,
+ activeBlocked: false,
+ passiveLoaded: false });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Checks that insecure window.opener does not trigger a warning.
+ */
+add_task(function* test_ignoring_window_opener() {
+ let newTabURL = "https://example.com" + TEST_URL_PATH + "form_basic.html";
+ let path = getRootDirectory(gTestPath)
+ .replace("chrome://mochitests/content", "http://example.com");
+ let url = path + "insecure_opener.html";
+
+ yield BrowserTestUtils.withNewTab(url, function*(browser) {
+ // Clicking the link will spawn a new tab.
+ let loaded = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL);
+ yield ContentTask.spawn(browser, {}, function() {
+ content.document.getElementById("link").click();
+ });
+ let tab = yield loaded;
+ browser = tab.linkedBrowser;
+ yield waitForInsecureLoginFormsStateChange(browser, 2);
+
+ // Open the identity popup.
+ let { gIdentityHandler } = gBrowser.ownerGlobal;
+ gIdentityHandler._identityBox.click();
+ document.getElementById("identity-popup-security-expander").click();
+
+ ok(is_visible(document.getElementById("connection-icon")),
+ "Connection icon is visible");
+
+ // Assert that the identity indicators are still "secure".
+ let connectionIconImage = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("connection-icon"))
+ .getPropertyValue("list-style-image");
+ let securityViewBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-securityView"))
+ .getPropertyValue("background-image");
+ let securityContentBG = gBrowser.ownerGlobal
+ .getComputedStyle(document.getElementById("identity-popup-security-content"))
+ .getPropertyValue("background-image");
+ is(connectionIconImage,
+ "url(\"chrome://browser/skin/connection-secure.svg\")",
+ "Using expected icon image in the identity block");
+ is(securityViewBG,
+ "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "Using expected icon image in the Control Center main view");
+ is(securityContentBG,
+ "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "Using expected icon image in the Control Center subview");
+
+ ok(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
+ element => is_hidden(element)),
+ "All messages should be hidden.");
+
+ gIdentityHandler._identityPopup.hidden = true;
+
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js
new file mode 100644
index 000000000..8e69e781b
--- /dev/null
+++ b/browser/base/content/test/general/browser_invalid_uri_back_forward_manipulation.js
@@ -0,0 +1,39 @@
+"use strict";
+
+
+/**
+ * Verify that loading an invalid URI does not clobber a previously-loaded page's history
+ * entry, but that the invalid URI gets its own history entry instead. We're checking this
+ * using nsIWebNavigation's canGoBack, as well as actually going back and then checking
+ * canGoForward.
+ */
+add_task(function* checkBackFromInvalidURI() {
+ yield pushPrefs(["keyword.enabled", false]);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots", true);
+ gURLBar.value = "::2600";
+ gURLBar.focus();
+
+ let promiseErrorPageLoaded = new Promise(resolve => {
+ tab.linkedBrowser.addEventListener("DOMContentLoaded", function onLoad() {
+ tab.linkedBrowser.removeEventListener("DOMContentLoaded", onLoad, false, true);
+ resolve();
+ }, false, true);
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield promiseErrorPageLoaded;
+
+ ok(gBrowser.webNavigation.canGoBack, "Should be able to go back");
+ if (gBrowser.webNavigation.canGoBack) {
+ // Can't use DOMContentLoaded here because the page is bfcached. Can't use pageshow for
+ // the error page because it doesn't seem to fire for those.
+ let promiseOtherPageLoaded = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "pageshow", false,
+ // Be paranoid we *are* actually seeing this other page load, not some kind of race
+ // for if/when we do start firing pageshow for the error page...
+ function(e) { return gBrowser.currentURI.spec == "about:robots" }
+ );
+ gBrowser.goBack();
+ yield promiseOtherPageLoaded;
+ ok(gBrowser.webNavigation.canGoForward, "Should be able to go forward from previous page.");
+ }
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_keywordBookmarklets.js b/browser/base/content/test/general/browser_keywordBookmarklets.js
new file mode 100644
index 000000000..5e94733fe
--- /dev/null
+++ b/browser/base/content/test/general/browser_keywordBookmarklets.js
@@ -0,0 +1,54 @@
+"use strict"
+
+add_task(function* test_keyword_bookmarklet() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "bookmarklet",
+ url: "javascript:'1';" });
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction (function* () {
+ gBrowser.removeTab(tab);
+ yield PlacesUtils.bookmarks.remove(bm);
+ });
+ yield promisePageShow();
+ let originalPrincipal = gBrowser.contentPrincipal;
+
+ function getPrincipalURI() {
+ return ContentTask.spawn(tab.linkedBrowser, null, function() {
+ return content.document.nodePrincipal.URI.spec;
+ });
+ }
+
+ let originalPrincipalURI = yield getPrincipalURI();
+
+ yield PlacesUtils.keywords.insert({ keyword: "bm", url: "javascript:'1';" })
+
+ // Enter bookmarklet keyword in the URL bar
+ gURLBar.value = "bm";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ yield promisePageShow();
+
+ let newPrincipalURI = yield getPrincipalURI();
+ is(newPrincipalURI, originalPrincipalURI, "content has the same principal");
+
+ // In e10s, null principals don't round-trip so the same null principal sent
+ // from the child will be a new null principal. Verify that this is the
+ // case.
+ if (tab.linkedBrowser.isRemoteBrowser) {
+ ok(originalPrincipal.isNullPrincipal && gBrowser.contentPrincipal.isNullPrincipal,
+ "both principals should be null principals in the parent");
+ } else {
+ ok(gBrowser.contentPrincipal.equals(originalPrincipal),
+ "javascript bookmarklet should inherit principal");
+ }
+});
+
+function* promisePageShow() {
+ return new Promise(resolve => {
+ gBrowser.selectedBrowser.addEventListener("pageshow", function listen() {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", listen);
+ resolve();
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_keywordSearch.js b/browser/base/content/test/general/browser_keywordSearch.js
new file mode 100644
index 000000000..cf8bd0c0e
--- /dev/null
+++ b/browser/base/content/test/general/browser_keywordSearch.js
@@ -0,0 +1,88 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ **/
+
+var gTests = [
+ {
+ name: "normal search (search service)",
+ testText: "test search",
+ searchURL: Services.search.defaultEngine.getSubmission("test search", null, "keyword").uri.spec
+ },
+ {
+ name: "?-prefixed search (search service)",
+ testText: "? foo ",
+ searchURL: Services.search.defaultEngine.getSubmission("foo", null, "keyword").uri.spec
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let windowObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ ok(false, "Alert window opened");
+ let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+ win.addEventListener("load", function() {
+ win.removeEventListener("load", arguments.callee, false);
+ win.close();
+ }, false);
+ executeSoon(finish);
+ }
+ }
+ };
+
+ Services.ww.registerNotification(windowObserver);
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onLocationChange(webProgress, req, flags, status) {
+ // Only care about document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart))
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+
+ executeSoon(nextTest);
+ }
+ };
+ gBrowser.addProgressListener(listener);
+
+ registerCleanupFunction(function () {
+ Services.ww.unregisterNotification(windowObserver);
+
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ });
+
+ nextTest();
+}
+
+var gCurrTest;
+function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ doTest();
+ } else {
+ finish();
+ }
+}
+
+function doTest() {
+ info("Running test: " + gCurrTest.name);
+
+ // Simulate a user entering search terms
+ gURLBar.value = gCurrTest.testText;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
diff --git a/browser/base/content/test/general/browser_keywordSearch_postData.js b/browser/base/content/test/general/browser_keywordSearch_postData.js
new file mode 100644
index 000000000..3f700fa58
--- /dev/null
+++ b/browser/base/content/test/general/browser_keywordSearch_postData.js
@@ -0,0 +1,94 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ **/
+
+var gTests = [
+ {
+ name: "normal search (search service)",
+ testText: "test search",
+ expectText: "test+search"
+ },
+ {
+ name: "?-prefixed search (search service)",
+ testText: "? foo ",
+ expectText: "foo"
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let searchObserver = function search_observer(aSubject, aTopic, aData) {
+ let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + aData + " for " + engine.name);
+
+ if (aData != "engine-added")
+ return;
+
+ if (engine.name != "POST Search")
+ return;
+
+ Services.search.defaultEngine = engine;
+
+ registerCleanupFunction(function () {
+ Services.search.removeEngine(engine);
+ });
+
+ // ready to execute the tests!
+ executeSoon(nextTest);
+ };
+
+ Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+
+ Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+ });
+
+ Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
+ null, null, false);
+}
+
+var gCurrTest;
+function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ doTest();
+ } else {
+ finish();
+ }
+}
+
+function doTest() {
+ info("Running test: " + gCurrTest.name);
+
+ waitForLoad(function () {
+ let loadedText = gBrowser.contentDocument.body.textContent;
+ ok(loadedText, "search page loaded");
+ let needle = "searchterms=" + gCurrTest.expectText;
+ is(loadedText, needle, "The query POST data should be returned in the response");
+ nextTest();
+ });
+
+ // Simulate a user entering search terms
+ gURLBar.value = gCurrTest.testText;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
+
+
+function waitForLoad(cb) {
+ let browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function listener() {
+ if (browser.currentURI.spec == "about:blank")
+ return;
+ info("Page loaded: " + browser.currentURI.spec);
+ browser.removeEventListener("load", listener, true);
+
+ cb();
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_lastAccessedTab.js b/browser/base/content/test/general/browser_lastAccessedTab.js
new file mode 100644
index 000000000..57bd330ae
--- /dev/null
+++ b/browser/base/content/test/general/browser_lastAccessedTab.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// gBrowser.selectedTab.lastAccessed and Date.now() called from this test can't
+// run concurrently, and therefore don't always match exactly.
+const CURRENT_TIME_TOLERANCE_MS = 15;
+
+function isCurrent(tab, msg) {
+ const DIFF = Math.abs(Date.now() - tab.lastAccessed);
+ ok(DIFF <= CURRENT_TIME_TOLERANCE_MS, msg + " (difference: " + DIFF + ")");
+}
+
+function nextStep(fn) {
+ setTimeout(fn, CURRENT_TIME_TOLERANCE_MS + 10);
+}
+
+var originalTab;
+var newTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ originalTab = gBrowser.selectedTab;
+ nextStep(step2);
+}
+
+function step2() {
+ isCurrent(originalTab, "selected tab has the current timestamp");
+ newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ nextStep(step3);
+}
+
+function step3() {
+ ok(newTab.lastAccessed < Date.now(), "new tab hasn't been selected so far");
+ gBrowser.selectedTab = newTab;
+ isCurrent(newTab, "new tab has the current timestamp after being selected");
+ nextStep(step4);
+}
+
+function step4() {
+ ok(originalTab.lastAccessed < Date.now(),
+ "original tab has old timestamp after being deselected");
+ isCurrent(newTab, "new tab has the current timestamp since it's still selected");
+
+ gBrowser.removeTab(newTab);
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_mcb_redirect.js b/browser/base/content/test/general/browser_mcb_redirect.js
new file mode 100644
index 000000000..41b4e9468
--- /dev/null
+++ b/browser/base/content/test/general/browser_mcb_redirect.js
@@ -0,0 +1,314 @@
+/*
+ * Description of the Tests for
+ * - Bug 418354 - Call Mixed content blocking on redirects
+ *
+ * Single redirect script tests
+ * 1. Load a script over https inside an https page
+ * - the server responds with a 302 redirect to a >> HTTP << script
+ * - the doorhanger should appear!
+ *
+ * 2. Load a script over https inside an http page
+ * - the server responds with a 302 redirect to a >> HTTP << script
+ * - the doorhanger should not appear!
+ *
+ * Single redirect image tests
+ * 3. Load an image over https inside an https page
+ * - the server responds with a 302 redirect to a >> HTTP << image
+ * - the image should not load
+ *
+ * 4. Load an image over https inside an http page
+ * - the server responds with a 302 redirect to a >> HTTP << image
+ * - the image should load and get cached
+ *
+ * Single redirect cached image tests
+ * 5. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an http page
+ * - the server would have responded with a 302 redirect to a >> HTTP <<
+ * image, but instead we try to use the cached image.
+ * - the image should load
+ *
+ * 6. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an https page
+ * - the server would have responded with a 302 redirect to a >> HTTP <<
+ * image, but instead we try to use the cached image.
+ * - the image should not load
+ *
+ * Double redirect image test
+ * 7. Load an image over https inside an http page
+ * - the server responds with a 302 redirect to a >> HTTP << server
+ * - the HTTP server responds with a 302 redirect to a >> HTTPS << image
+ * - the image should load and get cached
+ *
+ * Double redirect cached image tests
+ * 8. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an http page
+ * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS,
+ * but instead we try to use the cached image.
+ * - the image should load
+ *
+ * 9. Using offline mode to ensure we hit the cache, load a cached image over
+ * https inside an https page
+ * - the image would have gone through two redirects: HTTPS->HTTP->HTTPS,
+ * but instead we try to use the cached image.
+ * - the image should not load
+ */
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+const gHttpsTestRoot = "https://example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/";
+
+var origBlockActive;
+var origBlockDisplay;
+var gTestBrowser = null;
+
+// ------------------------ Helper Functions ---------------------
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+ Services.prefs.setBoolPref(PREF_DISPLAY, origBlockDisplay);
+
+ // Make sure we are online again
+ Services.io.offline = false;
+});
+
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function waitForCondition(condition, nextTest, errorMsg, okMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ if (condition()) {
+ ok(true, okMsg)
+ moveOn();
+ }
+ tries++;
+ }, 500);
+ var moveOn = function() {
+ clearInterval(interval); nextTest();
+ };
+}
+
+// ------------------------ Test 1 ------------------------------
+
+function test1() {
+ gTestBrowser.addEventListener("load", checkUIForTest1, true);
+ var url = gHttpsTestRoot + "test_mcb_redirect.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkUIForTest1() {
+ gTestBrowser.removeEventListener("load", checkUIForTest1, true);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+
+ var expected = "script blocked";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test2, "Error: Waited too long for status in Test 1!",
+ "OK: Expected result in innerHTML for Test1!");
+}
+
+// ------------------------ Test 2 ------------------------------
+
+function test2() {
+ gTestBrowser.addEventListener("load", checkUIForTest2, true);
+ var url = gHttpTestRoot + "test_mcb_redirect.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkUIForTest2() {
+ gTestBrowser.removeEventListener("load", checkUIForTest2, true);
+
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false});
+
+ var expected = "script executed";
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test3, "Error: Waited too long for status in Test 2!",
+ "OK: Expected result in innerHTML for Test2!");
+}
+
+// ------------------------ Test 3 ------------------------------
+// HTTPS page loading insecure image
+function test3() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest3, true);
+ var url = gHttpsTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest3() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest3, true);
+
+ var expected = "image blocked"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test4, "Error: Waited too long for status in Test 3!",
+ "OK: Expected result in innerHTML for Test3!");
+}
+
+// ------------------------ Test 4 ------------------------------
+// HTTP page loading insecure image
+function test4() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest4, true);
+ var url = gHttpTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest4() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest4, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test5, "Error: Waited too long for status in Test 4!",
+ "OK: Expected result in innerHTML for Test4!");
+}
+
+// ------------------------ Test 5 ------------------------------
+// HTTP page laoding insecure cached image
+// Assuming test 4 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test5() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest5, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest5() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest5, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test6, "Error: Waited too long for status in Test 5!",
+ "OK: Expected result in innerHTML for Test5!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ Test 6 ------------------------------
+// HTTPS page loading insecure cached image
+// Assuming test 4 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test6() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest6, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpsTestRoot + "test_mcb_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest6() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest6, true);
+
+ var expected = "image blocked"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test7, "Error: Waited too long for status in Test 6!",
+ "OK: Expected result in innerHTML for Test6!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ Test 7 ------------------------------
+// HTTP page loading insecure image that went through a double redirect
+function test7() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest7, true);
+ var url = gHttpTestRoot + "test_mcb_double_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest7() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest7, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test8, "Error: Waited too long for status in Test 7!",
+ "OK: Expected result in innerHTML for Test7!");
+}
+
+// ------------------------ Test 8 ------------------------------
+// HTTP page loading insecure cached image that went through a double redirect
+// Assuming test 7 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test8() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest8, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpTestRoot + "test_mcb_double_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest8() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest8, true);
+
+ var expected = "image loaded"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ test9, "Error: Waited too long for status in Test 8!",
+ "OK: Expected result in innerHTML for Test8!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ Test 9 ------------------------------
+// HTTPS page loading insecure cached image that went through a double redirect
+// Assuming test 7 succeeded, the image has already been loaded once
+// and hence should be cached per the sjs cache-control header
+// Going into offline mode to ensure we are loading from the cache.
+function test9() {
+ gTestBrowser.addEventListener("load", checkLoadEventForTest9, true);
+ // Go into offline mode
+ Services.io.offline = true;
+ var url = gHttpsTestRoot + "test_mcb_double_redirect_image.html"
+ gTestBrowser.contentWindow.location = url;
+}
+
+function checkLoadEventForTest9() {
+ gTestBrowser.removeEventListener("load", checkLoadEventForTest9, true);
+
+ var expected = "image blocked"
+ waitForCondition(
+ () => content.document.getElementById('mctestdiv').innerHTML == expected,
+ cleanUpAfterTests, "Error: Waited too long for status in Test 9!",
+ "OK: Expected result in innerHTML for Test9!");
+ // Go back online
+ Services.io.offline = false;
+}
+
+// ------------------------ SETUP ------------------------------
+
+function test() {
+ // Performing async calls, e.g. 'onload', we have to wait till all of them finished
+ waitForExplicitFinish();
+
+ // Store original preferences so we can restore settings after testing
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+ origBlockDisplay = Services.prefs.getBoolPref(PREF_DISPLAY);
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+ Services.prefs.setBoolPref(PREF_DISPLAY, true);
+
+ pushPrefs(["dom.ipc.processCount", 1]).then(() => {
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop();
+
+ executeSoon(test1);
+ });
+}
diff --git a/browser/base/content/test/general/browser_menuButtonBadgeManager.js b/browser/base/content/test/general/browser_menuButtonBadgeManager.js
new file mode 100644
index 000000000..9afe39ab7
--- /dev/null
+++ b/browser/base/content/test/general/browser_menuButtonBadgeManager.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var menuButton = document.getElementById("PanelUI-menu-button");
+
+add_task(function* testButtonActivities() {
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
+ is(menuButton.hasAttribute("badge"), false, "Should not have the badge attribute set");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
+ is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
+ is(menuButton.getAttribute("badge-status"), "update-succeeded", "Should have update-succeeded badge status (update > fxa)");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-failed");
+ is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-severe");
+ is(menuButton.getAttribute("badge-status"), "download-severe", "Should have download-severe badge status");
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-warning");
+ is(menuButton.getAttribute("badge-status"), "download-warning", "Should have download-warning badge status");
+
+ gMenuButtonBadgeManager.addBadge("unknownbadge", "attr");
+ is(menuButton.getAttribute("badge-status"), "download-warning", "Should not have changed badge status");
+
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+ is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
+
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE);
+ is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
+
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
+
+ yield PanelUI.show();
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)");
+ PanelUI.hide();
+
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
+ gMenuButtonBadgeManager.clearBadges();
+ is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (clearBadges called)");
+});
diff --git a/browser/base/content/test/general/browser_menuButtonFitts.js b/browser/base/content/test/general/browser_menuButtonFitts.js
new file mode 100644
index 000000000..e2541b925
--- /dev/null
+++ b/browser/base/content/test/general/browser_menuButtonFitts.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function test () {
+ waitForExplicitFinish();
+ window.maximize();
+
+ // Find where the nav-bar is vertically.
+ var navBar = document.getElementById("nav-bar");
+ var boundingRect = navBar.getBoundingClientRect();
+ var yPixel = boundingRect.top + Math.floor(boundingRect.height / 2);
+ var xPixel = boundingRect.width - 1; // Use the last pixel of the screen since it is maximized.
+
+ function onPopupHidden() {
+ PanelUI.panel.removeEventListener("popuphidden", onPopupHidden);
+ window.restore();
+ finish();
+ }
+ function onPopupShown() {
+ PanelUI.panel.removeEventListener("popupshown", onPopupShown);
+ ok(true, "Clicking at the far edge of the window opened the menu popup.");
+ PanelUI.panel.addEventListener("popuphidden", onPopupHidden);
+ PanelUI.hide();
+ }
+ registerCleanupFunction(function() {
+ PanelUI.panel.removeEventListener("popupshown", onPopupShown);
+ PanelUI.panel.removeEventListener("popuphidden", onPopupHidden);
+ });
+ PanelUI.panel.addEventListener("popupshown", onPopupShown);
+ EventUtils.synthesizeMouseAtPoint(xPixel, yPixel, {}, window);
+}
diff --git a/browser/base/content/test/general/browser_middleMouse_noJSPaste.js b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js
new file mode 100644
index 000000000..fa0c26f78
--- /dev/null
+++ b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const middleMousePastePref = "middlemouse.contentLoadURL";
+const autoScrollPref = "general.autoScroll";
+
+add_task(function* () {
+ yield pushPrefs([middleMousePastePref, true], [autoScrollPref, false]);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ let url = "javascript:http://www.example.com/";
+ yield new Promise((resolve, reject) => {
+ SimpleTest.waitForClipboard(url, () => {
+ Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Components.interfaces.nsIClipboardHelper)
+ .copyString(url);
+ }, resolve, () => {
+ ok(false, "Clipboard copy failed");
+ reject();
+ });
+ });
+
+ let middlePagePromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Middle click on the content area
+ info("Middle clicking");
+ yield BrowserTestUtils.synthesizeMouse(null, 10, 10, {button: 1}, gBrowser.selectedBrowser);
+ yield middlePagePromise;
+
+ is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_minimize.js b/browser/base/content/test/general/browser_minimize.js
new file mode 100644
index 000000000..1d761c0da
--- /dev/null
+++ b/browser/base/content/test/general/browser_minimize.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function *() {
+ registerCleanupFunction(function() {
+ window.restore();
+ });
+ function waitForActive() { return gBrowser.selectedTab.linkedBrowser.docShellIsActive; }
+ function waitForInactive() { return !gBrowser.selectedTab.linkedBrowser.docShellIsActive; }
+ yield promiseWaitForCondition(waitForActive);
+ is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, true, "Docshell should be active");
+ window.minimize();
+ yield promiseWaitForCondition(waitForInactive);
+ is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, false, "Docshell should be Inactive");
+ window.restore();
+ yield promiseWaitForCondition(waitForActive);
+ is(gBrowser.selectedTab.linkedBrowser.docShellIsActive, true, "Docshell should be active again");
+});
diff --git a/browser/base/content/test/general/browser_misused_characters_in_strings.js b/browser/base/content/test/general/browser_misused_characters_in_strings.js
new file mode 100644
index 000000000..fe8022662
--- /dev/null
+++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js
@@ -0,0 +1,244 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' issues to remain, while we
+ * detect newly occurring issues in shipping files. It is a list of objects
+ * specifying conditions under which an error should be ignored.
+ *
+ * As each issue is found in the whitelist, it is removed from the list. At
+ * the end of the test, there is an assertion that all items have been
+ * removed from the whitelist, thus ensuring there are no stale entries. */
+let gWhitelist = [{
+ file: "search.properties",
+ key: "searchForSomethingWith",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "certerror.introPara",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "weakCryptoAdvanced.longDesc",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "weakCryptoAdvanced.override",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "inadequateSecurityError.longDesc",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "certerror.wrongSystemTime",
+ type: "single-quote"
+ }, {
+ file: "phishing-afterload-warning-message.dtd",
+ key: "safeb.blocked.malwarePage.shortDesc",
+ type: "single-quote"
+ }, {
+ file: "phishing-afterload-warning-message.dtd",
+ key: "safeb.blocked.unwantedPage.shortDesc",
+ type: "single-quote"
+ }, {
+ file: "phishing-afterload-warning-message.dtd",
+ key: "safeb.blocked.phishingPage.shortDesc2",
+ type: "single-quote"
+ }, {
+ file: "mathfont.properties",
+ key: "operator.\\u002E\\u002E\\u002E.postfix",
+ type: "ellipsis"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapRectBoundsError",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapCircleWrongNumberOfCoords",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapCircleNegativeRadius",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapPolyWrongNumberOfCoords",
+ type: "double-quote"
+ }, {
+ file: "layout_errors.properties",
+ key: "ImageMapPolyOddNumberOfCoords",
+ type: "double-quote"
+ }, {
+ file: "xbl.properties",
+ key: "CommandNotInChrome",
+ type: "double-quote"
+ }, {
+ file: "dom.properties",
+ key: "PatternAttributeCompileFailure",
+ type: "single-quote"
+ }, {
+ file: "pipnss.properties",
+ key: "certErrorMismatchSingle2",
+ type: "double-quote"
+ }, {
+ file: "pipnss.properties",
+ key: "certErrorCodePrefix2",
+ type: "double-quote"
+ }, {
+ file: "aboutSupport.dtd",
+ key: "aboutSupport.pageSubtitle",
+ type: "single-quote"
+ }, {
+ file: "aboutSupport.dtd",
+ key: "aboutSupport.userJSDescription",
+ type: "single-quote"
+ }, {
+ file: "netError.dtd",
+ key: "inadequateSecurityError.longDesc",
+ type: "single-quote"
+ }, {
+ file: "netErrorApp.dtd",
+ key: "securityOverride.warningContent",
+ type: "single-quote"
+ }, {
+ file: "pocket.properties",
+ key: "tos",
+ type: "double-quote"
+ }, {
+ file: "pocket.properties",
+ key: "tos",
+ type: "apostrophe"
+ }, {
+ file: "aboutNetworking.dtd",
+ key: "aboutNetworking.logTutorial",
+ type: "single-quote"
+ }
+];
+
+var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
+var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in gWhitelist.
+ *
+ * @param filepath The URI spec of the locale file
+ * @param key The key of the entity that is being checked
+ * @param type The type of error that has been found
+ * @return true if the error should be ignored, false otherwise.
+ */
+function ignoredError(filepath, key, type) {
+ for (let index in gWhitelist) {
+ let whitelistItem = gWhitelist[index];
+ if (filepath.endsWith(whitelistItem.file) &&
+ key == whitelistItem.key &&
+ type == whitelistItem.type) {
+ gWhitelist.splice(index, 1);
+ return true;
+ }
+ }
+ return false;
+}
+
+function fetchFile(uri) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (this.readyState != this.DONE) {
+ return;
+ }
+ try {
+ resolve(this.responseText);
+ } catch (ex) {
+ ok(false, `Script error reading ${uri}: ${ex}`);
+ resolve("");
+ }
+ };
+ xhr.onerror = error => {
+ ok(false, `XHR error reading ${uri}: ${error}`);
+ resolve("");
+ };
+ xhr.send(null);
+ });
+}
+
+function testForError(filepath, key, str, pattern, type, helpText) {
+ if (str.match(pattern) &&
+ !ignoredError(filepath, key, type)) {
+ ok(false, `${filepath} with key=${key} has a misused ${type}. ${helpText}`);
+ }
+}
+
+function testForErrors(filepath, key, str) {
+ testForError(filepath, key, str, /\w'\w/, "apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo's.");
+ testForError(filepath, key, str, /\w\u2018\w/, "incorrect-apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo\u2018s.");
+ testForError(filepath, key, str, /'.+'/, "single-quote", "Single-quoted strings should use Unicode \u2018foo\u2019 instead of 'foo'.");
+ testForError(filepath, key, str, /"/, "double-quote", "Double-quoted strings should use Unicode \u201cfoo\u201d instead of \"foo\".");
+ testForError(filepath, key, str, /\.\.\./, "ellipsis", "Strings with an ellipsis should use the Unicode \u2026 character instead of three periods.");
+}
+
+function* getAllTheFiles(extension) {
+ let appDirGreD = Services.dirsvc.get("GreD", Ci.nsIFile);
+ let appDirXCurProcD = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+ if (appDirGreD.contains(appDirXCurProcD)) {
+ return yield generateURIsFromDirTree(appDirGreD, [extension]);
+ }
+ if (appDirXCurProcD.contains(appDirGreD)) {
+ return yield generateURIsFromDirTree(appDirXCurProcD, [extension]);
+ }
+ let urisGreD = yield generateURIsFromDirTree(appDirGreD, [extension]);
+ let urisXCurProcD = yield generateURIsFromDirTree(appDirXCurProcD, [extension]);
+ return Array.from(new Set(urisGreD.concat(appDirXCurProcD)));
+}
+
+add_task(function* checkAllTheProperties() {
+ // This asynchronously produces a list of URLs (sadly, mostly sync on our
+ // test infrastructure because it runs against jarfiles there, and
+ // our zipreader APIs are all sync)
+ let uris = yield getAllTheFiles(".properties");
+ ok(uris.length, `Found ${uris.length} .properties files to scan for misused characters`);
+
+ for (let uri of uris) {
+ let bundle = new StringBundle(uri.spec);
+ let entities = bundle.getAll();
+ for (let entity of entities) {
+ testForErrors(uri.spec, entity.key, entity.value);
+ }
+ }
+});
+
+var checkDTD = Task.async(function* (aURISpec) {
+ let rawContents = yield fetchFile(aURISpec);
+ // The regular expression below is adapted from:
+ // https://hg.mozilla.org/mozilla-central/file/68c0b7d6f16ce5bb023e08050102b5f2fe4aacd8/python/compare-locales/compare_locales/parser.py#l233
+ let entities = rawContents.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/g);
+ if (!entities) {
+ // Some files, such as requestAutocomplete.dtd, have no entities defined.
+ return;
+ }
+ for (let entity of entities) {
+ let [, key, str] = entity.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/);
+ // The matched string includes the enclosing quotation marks,
+ // we need to slice them off.
+ str = str.slice(1, -1);
+ testForErrors(aURISpec, key, str);
+ }
+});
+
+add_task(function* checkAllTheDTDs() {
+ let uris = yield getAllTheFiles(".dtd");
+ ok(uris.length, `Found ${uris.length} .dtd files to scan for misused characters`);
+ for (let uri of uris) {
+ yield checkDTD(uri.spec);
+ }
+
+ // This support DTD file supplies a string with a newline to make sure
+ // the regex in checkDTD works correctly for that case.
+ let dtdLocation = gTestPath.replace(/\/[^\/]*$/i, "/bug1262648_string_with_newlines.dtd");
+ yield checkDTD(dtdLocation);
+});
+
+add_task(function* ensureWhiteListIsEmpty() {
+ is(gWhitelist.length, 0, "No remaining whitelist entries exist");
+});
diff --git a/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js
new file mode 100644
index 000000000..ac19efd05
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixedContentFramesOnHttp.js
@@ -0,0 +1,34 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Test for Bug 1182551 -
+ *
+ * This test has a top level HTTP page with an HTTPS iframe. The HTTPS iframe
+ * includes an HTTP image. We check that the top level security state is
+ * STATE_IS_INSECURE. The mixed content from the iframe shouldn't "upgrade"
+ * the HTTP top level page to broken HTTPS.
+ */
+
+const gHttpTestUrl = "http://example.com/browser/browser/base/content/test/general/file_mixedContentFramesOnHttp.html";
+
+var gTestBrowser = null;
+
+add_task(function *() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["security.mixed_content.block_active_content", true],
+ ["security.mixed_content.block_display_content", false]
+ ]
+ }, resolve);
+ });
+ let url = gHttpTestUrl
+ yield BrowserTestUtils.withNewTab({gBrowser, url}, function*() {
+ gTestBrowser = gBrowser.selectedBrowser;
+ // check security state is insecure
+ isSecurityState("insecure");
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: true});
+ });
+});
+
diff --git a/browser/base/content/test/general/browser_mixedContentFromOnunload.js b/browser/base/content/test/general/browser_mixedContentFromOnunload.js
new file mode 100644
index 000000000..9b39776f4
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixedContentFromOnunload.js
@@ -0,0 +1,49 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Tests for Bug 947079 - Fix bug in nsSecureBrowserUIImpl that sets the wrong
+ * security state on a page because of a subresource load that is not on the
+ * same page.
+ */
+
+// We use different domains for each test and for navigation within each test
+const gHttpTestRoot1 = "http://example.com/browser/browser/base/content/test/general/";
+const gHttpsTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/general/";
+const gHttpTestRoot2 = "http://example.net/browser/browser/base/content/test/general/";
+const gHttpsTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/general/";
+
+var gTestBrowser = null;
+add_task(function *() {
+ let url = gHttpTestRoot1 + "file_mixedContentFromOnunload.html";
+ yield BrowserTestUtils.withNewTab({gBrowser, url}, function*() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["security.mixed_content.block_active_content", true],
+ ["security.mixed_content.block_display_content", false]
+ ]
+ }, resolve);
+ });
+ gTestBrowser = gBrowser.selectedBrowser;
+ // Navigation from an http page to a https page with no mixed content
+ // The http page loads an http image on unload
+ url = gHttpsTestRoot1 + "file_mixedContentFromOnunload_test1.html";
+ yield BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+ // check security state. Since current url is https and doesn't have any
+ // mixed content resources, we expect it to be secure.
+ isSecurityState("secure");
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: false});
+ // Navigation from an http page to a https page that has mixed display content
+ // The https page loads an http image on unload
+ url = gHttpTestRoot2 + "file_mixedContentFromOnunload.html";
+ yield BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+ url = gHttpsTestRoot2 + "file_mixedContentFromOnunload_test2.html";
+ yield BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+ isSecurityState("broken");
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: false, passiveLoaded: true});
+ });
+});
diff --git a/browser/base/content/test/general/browser_mixed_content_cert_override.js b/browser/base/content/test/general/browser_mixed_content_cert_override.js
new file mode 100644
index 000000000..037fce5d2
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixed_content_cert_override.js
@@ -0,0 +1,54 @@
+/*
+ * Bug 1253771 - check mixed content blocking in combination with overriden certificates
+ */
+
+"use strict";
+
+const MIXED_CONTENT_URL = "https://self-signed.example.com/browser/browser/base/content/test/general/test-mixedcontent-securityerrors.html";
+
+function getConnectionState() {
+ return document.getElementById("identity-popup").getAttribute("connection");
+}
+
+function getPopupContentVerifier() {
+ return document.getElementById("identity-popup-content-verifier");
+}
+
+function getConnectionIcon() {
+ return window.getComputedStyle(document.getElementById("connection-icon")).listStyleImage;
+}
+
+function checkIdentityPopup(icon) {
+ gIdentityHandler.refreshIdentityPopup();
+ is(getConnectionIcon(), `url("chrome://browser/skin/${icon}")`);
+ is(getConnectionState(), "secure-cert-user-overridden");
+ isnot(getPopupContentVerifier().style.display, "none", "Overridden certificate warning is shown");
+ ok(getPopupContentVerifier().textContent.includes("security exception"), "Text shows overridden certificate warning.");
+}
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // check that a warning is shown when loading a page with mixed content and an overridden certificate
+ yield loadBadCertPage(MIXED_CONTENT_URL);
+ checkIdentityPopup("connection-mixed-passive-loaded.svg#icon");
+
+ // check that the crossed out icon is shown when disabling mixed content protection
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ checkIdentityPopup("connection-mixed-active-loaded.svg#icon");
+
+ // check that a warning is shown even without mixed content
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "https://self-signed.example.com");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ checkIdentityPopup("connection-mixed-passive-loaded.svg#icon");
+
+ // remove cert exception
+ let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride("self-signed.example.com", -1);
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
diff --git a/browser/base/content/test/general/browser_mixedcontent_securityflags.js b/browser/base/content/test/general/browser_mixedcontent_securityflags.js
new file mode 100644
index 000000000..1c2614b86
--- /dev/null
+++ b/browser/base/content/test/general/browser_mixedcontent_securityflags.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The test loads a web page with mixed active and mixed display content and
+// makes sure that the mixed content flags on the docshell are set correctly.
+// * Using default about:config prefs (mixed active blocked, mixed display
+// loaded) we load the page and check the flags.
+// * We change the about:config prefs (mixed active blocked, mixed display
+// blocked), reload the page, and check the flags again.
+// * We override protection so all mixed content can load and check the
+// flags again.
+
+const TEST_URI = "https://example.com/browser/browser/base/content/test/general/test-mixedcontent-securityerrors.html";
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.clearUserPref(PREF_DISPLAY);
+ Services.prefs.clearUserPref(PREF_ACTIVE);
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* blockMixedActiveContentTest() {
+ // Turn on mixed active blocking and mixed display loading and load the page.
+ Services.prefs.setBoolPref(PREF_DISPLAY, false);
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
+ gTestBrowser = gBrowser.getBrowserForTab(tab);
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ is(docShell.hasMixedDisplayContentBlocked, false, "hasMixedDisplayContentBlocked flag has been set");
+ is(docShell.hasMixedActiveContentBlocked, true, "hasMixedActiveContentBlocked flag has been set");
+ is(docShell.hasMixedDisplayContentLoaded, true, "hasMixedDisplayContentLoaded flag has been set");
+ is(docShell.hasMixedActiveContentLoaded, false, "hasMixedActiveContentLoaded flag has been set");
+ });
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: true});
+
+ // Turn on mixed active and mixed display blocking and reload the page.
+ Services.prefs.setBoolPref(PREF_DISPLAY, true);
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ gBrowser.reload();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ is(docShell.hasMixedDisplayContentBlocked, true, "hasMixedDisplayContentBlocked flag has been set");
+ is(docShell.hasMixedActiveContentBlocked, true, "hasMixedActiveContentBlocked flag has been set");
+ is(docShell.hasMixedDisplayContentLoaded, false, "hasMixedDisplayContentLoaded flag has been set");
+ is(docShell.hasMixedActiveContentLoaded, false, "hasMixedActiveContentLoaded flag has been set");
+ });
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: false, activeBlocked: true, passiveLoaded: false});
+});
+
+add_task(function* overrideMCB() {
+ // Disable mixed content blocking (reloads page) and retest
+ let {gIdentityHandler} = gTestBrowser.ownerGlobal;
+ gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ is(docShell.hasMixedDisplayContentLoaded, true, "hasMixedDisplayContentLoaded flag has not been set");
+ is(docShell.hasMixedActiveContentLoaded, true, "hasMixedActiveContentLoaded flag has not been set");
+ is(docShell.hasMixedDisplayContentBlocked, false, "second hasMixedDisplayContentBlocked flag has been set");
+ is(docShell.hasMixedActiveContentBlocked, false, "second hasMixedActiveContentBlocked flag has been set");
+ });
+ assertMixedContentBlockingState(gTestBrowser, {activeLoaded: true, activeBlocked: false, passiveLoaded: true});
+});
diff --git a/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js
new file mode 100644
index 000000000..3b5a5a149
--- /dev/null
+++ b/browser/base/content/test/general/browser_modifiedclick_inherit_principal.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const kURL =
+ "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+ "data:text/html,<a href=''>Middle-click me</a>";
+
+/*
+ * Check that when manually opening content JS links in new tabs/windows,
+ * we use the correct principal, and we don't clear the URL bar.
+ */
+add_task(function* () {
+ yield BrowserTestUtils.withNewTab(kURL, function* (browser) {
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ yield ContentTask.spawn(browser, null, function* () {
+ let a = content.document.createElement("a");
+ a.href = "javascript:document.write('spoof'); void(0);";
+ a.textContent = "Some link";
+ content.document.body.appendChild(a);
+ });
+ info("Added element");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("a", {button: 1}, browser);
+ let newTab = yield newTabPromise;
+ is(newTab.linkedBrowser.contentPrincipal.origin, "http://example.com",
+ "Principal should be for example.com");
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ info(gURLBar.value);
+ isnot(gURLBar.value, "", "URL bar should not be empty.");
+ yield BrowserTestUtils.removeTab(newTab);
+ });
+});
diff --git a/browser/base/content/test/general/browser_newTabDrop.js b/browser/base/content/test/general/browser_newTabDrop.js
new file mode 100644
index 000000000..03c90df3f
--- /dev/null
+++ b/browser/base/content/test/general/browser_newTabDrop.js
@@ -0,0 +1,99 @@
+registerCleanupFunction(function* cleanup() {
+ while (gBrowser.tabs.length > 1) {
+ yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
+ }
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+});
+
+let originalEngine;
+add_task(function* test_setup() {
+ // Stop search-engine loads from hitting the network
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+});
+
+// New Tab Button opens any link.
+add_task(function*() { yield dropText("mochi.test/first", 1); });
+add_task(function*() { yield dropText("javascript:'bad'", 1); });
+add_task(function*() { yield dropText("jAvascript:'bad'", 1); });
+add_task(function*() { yield dropText("mochi.test/second", 1); });
+add_task(function*() { yield dropText("data:text/html,bad", 1); });
+add_task(function*() { yield dropText("mochi.test/third", 1); });
+
+// Single text/plain item, with multiple links.
+add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); });
+add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 2); });
+add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 2); });
+
+// Multiple text/plain items, with single and multiple links.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/5"}],
+ [{type: "text/plain",
+ data: "mochi.test/6\nmochi.test/7"}]], 3);
+});
+
+// Single text/x-moz-url item, with multiple links.
+// "text/x-moz-url" has titles in even-numbered lines.
+add_task(function*() {
+ yield drop([[{type: "text/x-moz-url",
+ data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2);
+});
+
+// Single item with multiple types.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/10"},
+ {type: "text/x-moz-url",
+ data: "mochi.test/11\nTITLE11"}]], 1);
+});
+
+function dropText(text, expectedTabOpenCount=0) {
+ return drop([[{type: "text/plain", data: text}]], expectedTabOpenCount);
+}
+
+function* drop(dragData, expectedTabOpenCount=0) {
+ let dragDataString = JSON.stringify(dragData);
+ info(`Starting test for datagData:${dragDataString}; expectedTabOpenCount:${expectedTabOpenCount}`);
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // Since synthesizeDrop triggers the srcElement, need to use another button.
+ let dragSrcElement = document.getElementById("downloads-button");
+ ok(dragSrcElement, "Downloads button exists");
+ let newTabButton = document.getElementById("new-tab-button");
+ ok(newTabButton, "New Tab button exists");
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(newTabButton, "drop");
+ let actualTabOpenCount = 0;
+ let openedTabs = [];
+ let checkCount = function(event) {
+ openedTabs.push(event.target);
+ actualTabOpenCount++;
+ return actualTabOpenCount == expectedTabOpenCount;
+ };
+ let awaitTabOpen = expectedTabOpenCount && BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen", false, checkCount);
+
+ EventUtils.synthesizeDrop(dragSrcElement, newTabButton, dragData, "link", window);
+
+ let tabsOpened = false;
+ if (awaitTabOpen) {
+ yield awaitTabOpen;
+ info("Got TabOpen event");
+ tabsOpened = true;
+ for (let tab of openedTabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ }
+ is(tabsOpened, !!expectedTabOpenCount, `Tabs for ${dragDataString} should only open if any of dropped items are valid`);
+
+ yield awaitDrop;
+ ok(true, "Got drop event");
+}
diff --git a/browser/base/content/test/general/browser_newWindowDrop.js b/browser/base/content/test/general/browser_newWindowDrop.js
new file mode 100644
index 000000000..f404d4eed
--- /dev/null
+++ b/browser/base/content/test/general/browser_newWindowDrop.js
@@ -0,0 +1,120 @@
+registerCleanupFunction(function* cleanup() {
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+});
+
+let originalEngine;
+add_task(function* test_setup() {
+ // Opening multiple windows on debug build takes too long time.
+ requestLongerTimeout(10);
+
+ // Stop search-engine loads from hitting the network
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ // Move New Window button to nav bar, to make it possible to drag and drop.
+ let {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+ let origPlacement = CustomizableUI.getPlacementOfWidget("new-window-button");
+ if (!origPlacement || origPlacement.area != CustomizableUI.AREA_NAVBAR) {
+ CustomizableUI.addWidgetToArea("new-window-button",
+ CustomizableUI.AREA_NAVBAR,
+ 0);
+ CustomizableUI.ensureWidgetPlacedInWindow("new-window-button", window);
+ registerCleanupFunction(function () {
+ CustomizableUI.reset();
+ });
+ }
+});
+
+// New Window Button opens any link.
+add_task(function*() { yield dropText("mochi.test/first", 1); });
+add_task(function*() { yield dropText("javascript:'bad'", 1); });
+add_task(function*() { yield dropText("jAvascript:'bad'", 1); });
+add_task(function*() { yield dropText("mochi.test/second", 1); });
+add_task(function*() { yield dropText("data:text/html,bad", 1); });
+add_task(function*() { yield dropText("mochi.test/third", 1); });
+
+// Single text/plain item, with multiple links.
+add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); });
+add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 2); });
+add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 2); });
+
+// Multiple text/plain items, with single and multiple links.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/5"}],
+ [{type: "text/plain",
+ data: "mochi.test/6\nmochi.test/7"}]], 3);
+});
+
+// Single text/x-moz-url item, with multiple links.
+// "text/x-moz-url" has titles in even-numbered lines.
+add_task(function*() {
+ yield drop([[{type: "text/x-moz-url",
+ data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2);
+});
+
+// Single item with multiple types.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/10"},
+ {type: "text/x-moz-url",
+ data: "mochi.test/11\nTITLE11"}]], 1);
+});
+
+function dropText(text, expectedWindowOpenCount=0) {
+ return drop([[{type: "text/plain", data: text}]], expectedWindowOpenCount);
+}
+
+function* drop(dragData, expectedWindowOpenCount=0) {
+ let dragDataString = JSON.stringify(dragData);
+ info(`Starting test for datagData:${dragDataString}; expectedWindowOpenCount:${expectedWindowOpenCount}`);
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ // Since synthesizeDrop triggers the srcElement, need to use another button.
+ let dragSrcElement = document.getElementById("downloads-button");
+ ok(dragSrcElement, "Downloads button exists");
+ let newWindowButton = document.getElementById("new-window-button");
+ ok(newWindowButton, "New Window button exists");
+
+ let tmp = {};
+ Cu.import("resource://testing-common/TestUtils.jsm", tmp);
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(newWindowButton, "drop");
+ let actualWindowOpenCount = 0;
+ let openedWindows = [];
+ let checkCount = function(window) {
+ // Add observer as soon as domWindow is opened to avoid missing the topic.
+ let awaitStartup = tmp.TestUtils.topicObserved("browser-delayed-startup-finished",
+ subject => subject == window);
+ openedWindows.push([window, awaitStartup]);
+ actualWindowOpenCount++;
+ return actualWindowOpenCount == expectedWindowOpenCount;
+ };
+ let awaitWindowOpen = expectedWindowOpenCount && BrowserTestUtils.domWindowOpened(null, checkCount);
+
+ EventUtils.synthesizeDrop(dragSrcElement, newWindowButton, dragData, "link", window);
+
+ let windowsOpened = false;
+ if (awaitWindowOpen) {
+ yield awaitWindowOpen;
+ info("Got Window opened");
+ windowsOpened = true;
+ for (let [window, awaitStartup] of openedWindows.reverse()) {
+ // Wait for startup before closing, to properly close the browser window.
+ yield awaitStartup;
+ yield BrowserTestUtils.closeWindow(window);
+ }
+ }
+ is(windowsOpened, !!expectedWindowOpenCount, `Windows for ${dragDataString} should only open if any of dropped items are valid`);
+
+ yield awaitDrop;
+ ok(true, "Got drop event");
+}
diff --git a/browser/base/content/test/general/browser_newwindow_focus.js b/browser/base/content/test/general/browser_newwindow_focus.js
new file mode 100644
index 000000000..7880db0bd
--- /dev/null
+++ b/browser/base/content/test/general/browser_newwindow_focus.js
@@ -0,0 +1,96 @@
+"use strict";
+
+/**
+ * These tests are for the auto-focus behaviour on the initial browser
+ * when a window is opened from content.
+ */
+
+const PAGE = `data:text/html,<a id="target" href="%23" onclick="window.open('http://www.example.com', '_blank', 'width=100,height=100');">Click me</a>`;
+
+/**
+ * Returns a Promise that resolves when a new window has
+ * opened, and the "load" event has fired in that window.
+ * We can't use BrowserTestUtils.domWindowOpened directly,
+ * because by the time the "then" on the Promise runs,
+ * DOMContentLoaded and load may have already run in the new
+ * window. However, we want to be very explicit about what
+ * events we're waiting for, and not rely on a quirk of our
+ * Promises infrastructure.
+ */
+function promiseNewWindow() {
+ return new Promise((resolve) => {
+ let observer = (subject, topic, data) => {
+ if (topic == "domwindowopened") {
+ Services.ww.unregisterNotification(observer);
+ let win = subject.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ resolve(win);
+ });
+ }
+ };
+
+ Services.ww.registerNotification(observer);
+ });
+}
+
+/**
+ * Test that when a new window is opened from content, focus moves
+ * to the initial browser in that window once the window has finished
+ * painting.
+ */
+add_task(function* test_focus_browser() {
+ yield BrowserTestUtils.withNewTab({
+ url: PAGE,
+ gBrowser,
+ }, function*(browser) {
+ let newWinPromise = promiseNewWindow();
+ let delayedStartupPromise = BrowserTestUtils.waitForNewWindow();
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser);
+ let newWin = yield newWinPromise;
+ yield BrowserTestUtils.contentPainted(newWin.gBrowser.selectedBrowser);
+ yield delayedStartupPromise;
+
+ let focusedElement =
+ Services.focus.getFocusedElementForWindow(newWin, false, {});
+
+ Assert.equal(focusedElement, newWin.gBrowser.selectedBrowser,
+ "Initial browser should be focused");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+ });
+});
+
+/**
+ * Test that when a new window is opened from content and focus
+ * shifts in that window before the content has a chance to paint
+ * that we _don't_ steal focus once content has painted.
+ */
+add_task(function* test_no_steal_focus() {
+ yield BrowserTestUtils.withNewTab({
+ url: PAGE,
+ gBrowser,
+ }, function*(browser) {
+ let newWinPromise = promiseNewWindow();
+ let delayedStartupPromise = BrowserTestUtils.waitForNewWindow();
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#target", {}, browser);
+ let newWin = yield newWinPromise;
+
+ // Because we're switching focus, we shouldn't steal it once
+ // content paints.
+ newWin.gURLBar.focus();
+
+ yield BrowserTestUtils.contentPainted(newWin.gBrowser.selectedBrowser);
+ yield delayedStartupPromise;
+
+ let focusedElement =
+ Services.focus.getFocusedElementForWindow(newWin, false, {});
+
+ Assert.equal(focusedElement, newWin.gURLBar.inputField,
+ "URLBar should be focused");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+ });
+});
diff --git a/browser/base/content/test/general/browser_no_mcb_on_http_site.js b/browser/base/content/test/general/browser_no_mcb_on_http_site.js
new file mode 100644
index 000000000..45fd67379
--- /dev/null
+++ b/browser/base/content/test/general/browser_no_mcb_on_http_site.js
@@ -0,0 +1,106 @@
+/*
+ * Description of the Tests for
+ * - Bug 909920 - Mixed content warning should not show on a HTTP site
+ *
+ * Description of the tests:
+ * Test 1:
+ * 1) Load an http page
+ * 2) The page includes a css file using https
+ * 3) The css file loads an |IMAGE| << over http
+ *
+ * Test 2:
+ * 1) Load an http page
+ * 2) The page includes a css file using https
+ * 3) The css file loads a |FONT| over http
+ *
+ * Test 3:
+ * 1) Load an http page
+ * 2) The page includes a css file using https
+ * 3) The css file imports (@import) another css file using http
+ * 3) The imported css file loads a |FONT| over http
+*
+ * Since the top-domain is >> NOT << served using https, the MCB
+ * should >> NOT << trigger a warning.
+ */
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+
+const gHttpTestRoot = "http://example.com/browser/browser/base/content/test/general/";
+
+var gTestBrowser = null;
+
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+}
+
+add_task(function* init() {
+ yield SpecialPowers.pushPrefEnv({ set: [[ PREF_ACTIVE, true ],
+ [ PREF_DISPLAY, true ]] });
+ let url = gHttpTestRoot + "test_no_mcb_on_http_site_img.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url)
+ gTestBrowser = tab.linkedBrowser;
+});
+
+// ------------- TEST 1 -----------------------------------------
+
+add_task(function* test1() {
+ let expected = "Verifying MCB does not trigger warning/error for an http page ";
+ expected += "with https css that includes http image";
+
+ yield ContentTask.spawn(gTestBrowser, expected, function* (condition) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("testDiv").innerHTML == condition,
+ "Waited too long for status in Test 1!");
+ });
+
+ // Explicit OKs needed because the harness requires at least one call to ok.
+ ok(true, "test 1 passed");
+
+ // set up test 2
+ let url = gHttpTestRoot + "test_no_mcb_on_http_site_font.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+// ------------- TEST 2 -----------------------------------------
+
+add_task(function* test2() {
+ let expected = "Verifying MCB does not trigger warning/error for an http page ";
+ expected += "with https css that includes http font";
+
+ yield ContentTask.spawn(gTestBrowser, expected, function* (condition) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("testDiv").innerHTML == condition,
+ "Waited too long for status in Test 2!");
+ });
+
+ ok(true, "test 2 passed");
+
+ // set up test 3
+ let url = gHttpTestRoot + "test_no_mcb_on_http_site_font2.html";
+ BrowserTestUtils.loadURI(gTestBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+// ------------- TEST 3 -----------------------------------------
+
+add_task(function* test3() {
+ let expected = "Verifying MCB does not trigger warning/error for an http page "
+ expected += "with https css that imports another http css which includes http font";
+
+ yield ContentTask.spawn(gTestBrowser, expected, function* (condition) {
+ yield ContentTaskUtils.waitForCondition(
+ () => content.document.getElementById("testDiv").innerHTML == condition,
+ "Waited too long for status in Test 3!");
+ });
+
+ ok(true, "test3 passed");
+});
+
+// ------------------------------------------------------
+
+add_task(function* cleanup() {
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/general/browser_offlineQuotaNotification.js b/browser/base/content/test/general/browser_offlineQuotaNotification.js
new file mode 100644
index 000000000..e56bfe9a8
--- /dev/null
+++ b/browser/base/content/test/general/browser_offlineQuotaNotification.js
@@ -0,0 +1,95 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test offline quota warnings - must be run as a mochitest-browser test or
+// else the test runner gets in the way of notifications due to bug 857897.
+
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/offlineQuotaNotification.html";
+
+registerCleanupFunction(function() {
+ // Clean up after ourself
+ let uri = Services.io.newURI(URL, null, null);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ Services.perms.removeFromPrincipal(principal, "offline-app");
+ Services.prefs.clearUserPref("offline-apps.quota.warn");
+ Services.prefs.clearUserPref("offline-apps.allow_by_default");
+ let {OfflineAppCacheHelper} = Components.utils.import("resource:///modules/offlineAppCache.jsm", {});
+ OfflineAppCacheHelper.clear();
+});
+
+// Same as the other one, but for in-content preferences
+function checkInContentPreferences(win) {
+ let doc = win.document;
+ let sel = doc.getElementById("categories").selectedItems[0].id;
+ let tab = doc.getElementById("advancedPrefs").selectedTab.id;
+ is(gBrowser.currentURI.spec, "about:preferences#advanced", "about:preferences loaded");
+ is(sel, "category-advanced", "Advanced pane was selected");
+ is(tab, "networkTab", "Network tab is selected");
+ // all good, we are done.
+ win.close();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("offline-apps.allow_by_default", false);
+
+ // Open a new tab.
+ gBrowser.selectedTab = gBrowser.addTab(URL);
+ registerCleanupFunction(() => gBrowser.removeCurrentTab());
+
+
+ Promise.all([
+ // Wait for a notification that asks whether to allow offline storage.
+ promiseNotification(),
+ // Wait for the tab to load.
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser),
+ ]).then(() => {
+ info("Loaded page, adding onCached handler");
+ // Need a promise to keep track of when we've added our handler.
+ let mm = gBrowser.selectedBrowser.messageManager;
+ let onCachedAttached = BrowserTestUtils.waitForMessage(mm, "Test:OnCachedAttached");
+ let gotCached = ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ return new Promise(resolve => {
+ content.window.applicationCache.oncached = function() {
+ setTimeout(resolve, 0);
+ };
+ sendAsyncMessage("Test:OnCachedAttached");
+ });
+ });
+ gotCached.then(function() {
+ // We got cached - now we should have provoked the quota warning.
+ let notification = PopupNotifications.getNotification('offline-app-usage');
+ ok(notification, "have offline-app-usage notification");
+ // select the default action - this should cause the preferences
+ // tab to open - which we track via an "Initialized" event.
+ PopupNotifications.panel.firstElementChild.button.click();
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function PrefInit() {
+ newTabBrowser.removeEventListener("Initialized", PrefInit, true);
+ executeSoon(function() {
+ checkInContentPreferences(newTabBrowser.contentWindow);
+ })
+ }, true);
+ });
+ onCachedAttached.then(function() {
+ Services.prefs.setIntPref("offline-apps.quota.warn", 1);
+
+ // Click the notification panel's "Allow" button. This should kick
+ // off updates which will call our oncached handler above.
+ PopupNotifications.panel.firstElementChild.button.click();
+ });
+ });
+}
+
+function promiseNotification() {
+ return new Promise(resolve => {
+ PopupNotifications.panel.addEventListener("popupshown", function onShown() {
+ PopupNotifications.panel.removeEventListener("popupshown", onShown);
+ resolve();
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_overflowScroll.js b/browser/base/content/test/general/browser_overflowScroll.js
new file mode 100644
index 000000000..56932fae2
--- /dev/null
+++ b/browser/base/content/test/general/browser_overflowScroll.js
@@ -0,0 +1,91 @@
+var tabstrip = gBrowser.tabContainer.mTabstrip;
+var scrollbox = tabstrip._scrollbox;
+var originalSmoothScroll = tabstrip.smoothScroll;
+var tabs = gBrowser.tabs;
+
+var rect = ele => ele.getBoundingClientRect();
+var width = ele => rect(ele).width;
+var left = ele => rect(ele).left;
+var right = ele => rect(ele).right;
+var isLeft = (ele, msg) => is(left(ele) + tabstrip._tabMarginLeft, left(scrollbox), msg);
+var isRight = (ele, msg) => is(right(ele) - tabstrip._tabMarginRight, right(scrollbox), msg);
+var elementFromPoint = x => tabstrip._elementFromPoint(x);
+var nextLeftElement = () => elementFromPoint(left(scrollbox) - 1);
+var nextRightElement = () => elementFromPoint(right(scrollbox) + 1);
+var firstScrollable = () => tabs[gBrowser._numPinnedTabs];
+
+function test() {
+ requestLongerTimeout(2);
+ waitForExplicitFinish();
+
+ // If the previous (or more) test finished with cleaning up the tabs,
+ // there may be some pending animations. That can cause a failure of
+ // this tests, so, we should test this in another stack.
+ setTimeout(doTest, 0);
+}
+
+function doTest() {
+ tabstrip.smoothScroll = false;
+
+ var tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
+ var tabCountForOverflow = Math.ceil(width(tabstrip) / tabMinWidth * 3);
+ while (tabs.length < tabCountForOverflow)
+ gBrowser.addTab("about:blank", {skipAnimation: true});
+ gBrowser.pinTab(tabs[0]);
+
+ tabstrip.addEventListener("overflow", runOverflowTests, false);
+}
+
+function runOverflowTests(aEvent) {
+ if (aEvent.detail != 1)
+ return;
+
+ tabstrip.removeEventListener("overflow", runOverflowTests, false);
+
+ var upButton = tabstrip._scrollButtonUp;
+ var downButton = tabstrip._scrollButtonDown;
+ var element;
+
+ gBrowser.selectedTab = firstScrollable();
+ ok(left(scrollbox) <= left(firstScrollable()), "Selecting the first tab scrolls it into view " +
+ "(" + left(scrollbox) + " <= " + left(firstScrollable()) + ")");
+
+ element = nextRightElement();
+ EventUtils.synthesizeMouseAtCenter(downButton, {});
+ isRight(element, "Scrolled one tab to the right with a single click");
+
+ gBrowser.selectedTab = tabs[tabs.length - 1];
+ ok(right(gBrowser.selectedTab) <= right(scrollbox), "Selecting the last tab scrolls it into view " +
+ "(" + right(gBrowser.selectedTab) + " <= " + right(scrollbox) + ")");
+
+ element = nextLeftElement();
+ EventUtils.synthesizeMouse(upButton, 1, 1, {});
+ isLeft(element, "Scrolled one tab to the left with a single click");
+
+ let elementPoint = left(scrollbox) - width(scrollbox);
+ element = elementFromPoint(elementPoint);
+ if (elementPoint == right(element)) {
+ element = element.nextSibling;
+ }
+ EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 2});
+ isLeft(element, "Scrolled one page of tabs with a double click");
+
+ EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 3});
+ var firstScrollableLeft = left(firstScrollable());
+ ok(left(scrollbox) <= firstScrollableLeft, "Scrolled to the start with a triple click " +
+ "(" + left(scrollbox) + " <= " + firstScrollableLeft + ")");
+
+ for (var i = 2; i; i--)
+ EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE });
+ is(left(firstScrollable()), firstScrollableLeft, "Remained at the start with the mouse wheel");
+
+ element = nextRightElement();
+ EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE});
+ isRight(element, "Scrolled one tab to the right with the mouse wheel");
+
+ while (tabs.length > 1)
+ gBrowser.removeTab(tabs[0]);
+
+ tabstrip.smoothScroll = originalSmoothScroll;
+ finish();
+}
diff --git a/browser/base/content/test/general/browser_pageInfo.js b/browser/base/content/test/general/browser_pageInfo.js
new file mode 100644
index 000000000..90fe2e17f
--- /dev/null
+++ b/browser/base/content/test/general/browser_pageInfo.js
@@ -0,0 +1,38 @@
+function test() {
+ waitForExplicitFinish();
+
+ var pageInfo;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
+
+ Services.obs.addObserver(observer, "page-info-dialog-loaded", false);
+ pageInfo = BrowserPageInfo();
+ }, true);
+ content.location =
+ "https://example.com/browser/browser/base/content/test/general/feed_tab.html";
+
+ function observer(win, topic, data) {
+ Services.obs.removeObserver(observer, "page-info-dialog-loaded");
+ pageInfo.onFinished.push(handlePageInfo);
+ }
+
+ function handlePageInfo() {
+ ok(pageInfo.document.getElementById("feedTab"), "Feed tab");
+ let feedListbox = pageInfo.document.getElementById("feedListbox");
+ ok(feedListbox, "Feed list");
+
+ var feedRowsNum = feedListbox.getRowCount();
+ is(feedRowsNum, 3, "Number of feeds listed");
+
+ for (var i = 0; i < feedRowsNum; i++) {
+ let feedItem = feedListbox.getItemAtIndex(i);
+ is(feedItem.getAttribute("name"), i + 1, "Feed name");
+ }
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+}
diff --git a/browser/base/content/test/general/browser_page_style_menu.js b/browser/base/content/test/general/browser_page_style_menu.js
new file mode 100644
index 000000000..cb080d52a
--- /dev/null
+++ b/browser/base/content/test/general/browser_page_style_menu.js
@@ -0,0 +1,97 @@
+"use strict";
+
+/**
+ * Stylesheets are updated for a browser after the pageshow event.
+ * This helper function returns a Promise that waits for that pageshow
+ * event, and then resolves on the next tick to ensure that gPageStyleMenu
+ * has had a chance to update the stylesheets.
+ *
+ * @param browser
+ * The <xul:browser> to wait for.
+ * @return Promise
+ */
+function promiseStylesheetsUpdated(browser) {
+ return ContentTask.spawn(browser, { PAGE }, function*(args) {
+ return new Promise((resolve) => {
+ addEventListener("pageshow", function onPageShow(e) {
+ if (e.target.location == args.PAGE) {
+ removeEventListener("pageshow", onPageShow);
+ content.setTimeout(resolve, 0);
+ }
+ });
+ })
+ });
+}
+
+const PAGE = "http://example.com/browser/browser/base/content/test/general/page_style_sample.html";
+
+/*
+ * Test that the right stylesheets do (and others don't) show up
+ * in the page style menu.
+ */
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false);
+ let browser = tab.linkedBrowser;
+ yield BrowserTestUtils.loadURI(browser, PAGE);
+ yield promiseStylesheetsUpdated(browser);
+
+ let menupopup = document.getElementById("pageStyleMenu").menupopup;
+ gPageStyleMenu.fillPopup(menupopup);
+
+ var items = [];
+ var current = menupopup.getElementsByTagName("menuseparator")[0];
+ while (current.nextSibling) {
+ current = current.nextSibling;
+ items.push(current);
+ }
+
+ items = items.map(el => ({
+ label: el.getAttribute("label"),
+ checked: el.getAttribute("checked") == "true",
+ }));
+
+ let validLinks = yield ContentTask.spawn(gBrowser.selectedBrowser, items, function(contentItems) {
+ let contentValidLinks = 0;
+ Array.forEach(content.document.querySelectorAll("link, style"), function (el) {
+ var title = el.getAttribute("title");
+ var rel = el.getAttribute("rel");
+ var media = el.getAttribute("media");
+ var idstring = el.nodeName + " " + (title ? title : "without title and") +
+ " with rel=\"" + rel + "\"" +
+ (media ? " and media=\"" + media + "\"" : "");
+
+ var item = contentItems.filter(aItem => aItem.label == title);
+ var found = item.length == 1;
+ var checked = found && item[0].checked;
+
+ switch (el.getAttribute("data-state")) {
+ case "0":
+ ok(!found, idstring + " should not show up in page style menu");
+ break;
+ case "0-todo":
+ contentValidLinks++;
+ todo(!found, idstring + " should not show up in page style menu");
+ ok(!checked, idstring + " should not be selected");
+ break;
+ case "1":
+ contentValidLinks++;
+ ok(found, idstring + " should show up in page style menu");
+ ok(!checked, idstring + " should not be selected");
+ break;
+ case "2":
+ contentValidLinks++;
+ ok(found, idstring + " should show up in page style menu");
+ ok(checked, idstring + " should be selected");
+ break;
+ default:
+ throw "data-state attribute is missing or has invalid value";
+ }
+ });
+ return contentValidLinks;
+ });
+
+ ok(items.length, "At least one item in the menu");
+ is(items.length, validLinks, "all valid links found");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_page_style_menu_update.js b/browser/base/content/test/general/browser_page_style_menu_update.js
new file mode 100644
index 000000000..a0c741e48
--- /dev/null
+++ b/browser/base/content/test/general/browser_page_style_menu_update.js
@@ -0,0 +1,67 @@
+"use strict";
+
+const PAGE = "http://example.com/browser/browser/base/content/test/general/page_style_sample.html";
+
+/**
+ * Stylesheets are updated for a browser after the pageshow event.
+ * This helper function returns a Promise that waits for that pageshow
+ * event, and then resolves on the next tick to ensure that gPageStyleMenu
+ * has had a chance to update the stylesheets.
+ *
+ * @param browser
+ * The <xul:browser> to wait for.
+ * @return Promise
+ */
+function promiseStylesheetsUpdated(browser) {
+ return ContentTask.spawn(browser, { PAGE }, function*(args) {
+ return new Promise((resolve) => {
+ addEventListener("pageshow", function onPageShow(e) {
+ if (e.target.location == args.PAGE) {
+ removeEventListener("pageshow", onPageShow);
+ content.setTimeout(resolve, 0);
+ }
+ });
+ })
+ });
+}
+
+/**
+ * Tests that the Page Style menu shows the currently
+ * selected Page Style after a new one has been selected.
+ */
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", false);
+ let browser = tab.linkedBrowser;
+
+ yield BrowserTestUtils.loadURI(browser, PAGE);
+ yield promiseStylesheetsUpdated(browser);
+
+ let menupopup = document.getElementById("pageStyleMenu").menupopup;
+ gPageStyleMenu.fillPopup(menupopup);
+
+ // page_style_sample.html should default us to selecting the stylesheet
+ // with the title "6" first.
+ let selected = menupopup.querySelector("menuitem[checked='true']");
+ is(selected.getAttribute("label"), "6", "Should have '6' stylesheet selected by default");
+
+ // Now select stylesheet "1"
+ let target = menupopup.querySelector("menuitem[label='1']");
+ target.click();
+
+ // Now we need to wait for the content process to send its stylesheet
+ // update for the selected tab to the parent. Because messages are
+ // guaranteed to be sent in order, we'll make sure we do the check
+ // after the parent has been updated by yielding until the child
+ // has finished running a ContentTask for us.
+ yield ContentTask.spawn(browser, {}, function*() {
+ dump('\nJust wasting some time.\n');
+ });
+
+ gPageStyleMenu.fillPopup(menupopup);
+ // gPageStyleMenu empties out the menu between opens, so we need
+ // to get a new reference to the selected menuitem
+ selected = menupopup.querySelector("menuitem[checked='true']");
+ is(selected.getAttribute("label"), "1", "Should now have stylesheet 1 selected");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_pageinfo_svg_image.js b/browser/base/content/test/general/browser_pageinfo_svg_image.js
new file mode 100644
index 000000000..02514d79f
--- /dev/null
+++ b/browser/base/content/test/general/browser_pageinfo_svg_image.js
@@ -0,0 +1,38 @@
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
+ var pageInfo = BrowserPageInfo(gBrowser.selectedBrowser.currentURI.spec,
+ "mediaTab");
+
+ pageInfo.addEventListener("load", function loadListener2() {
+ pageInfo.removeEventListener("load", loadListener2, true);
+ pageInfo.onFinished.push(function() {
+ executeSoon(function() {
+ var imageTree = pageInfo.document.getElementById("imagetree");
+ var imageRowsNum = imageTree.view.rowCount;
+
+ ok(imageTree, "Image tree is null (media tab is broken)");
+
+ is(imageRowsNum, 1, "should have one image");
+
+ // Only bother running this if we've got the right number of rows.
+ if (imageRowsNum == 1) {
+ is(imageTree.view.getCellText(0, imageTree.columns[0]),
+ "https://example.com/browser/browser/base/content/test/general/title_test.svg",
+ "The URL should be the svg image.");
+ }
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "https://example.com/browser/browser/base/content/test/general/svg_image.html";
+}
diff --git a/browser/base/content/test/general/browser_parsable_css.js b/browser/base/content/test/general/browser_parsable_css.js
new file mode 100644
index 000000000..72954d2e5
--- /dev/null
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -0,0 +1,376 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' CSS issues to remain, while we
+ * detect newly occurring issues in shipping CSS. It is a list of objects
+ * specifying conditions under which an error should be ignored.
+ *
+ * Every property of the objects in it needs to consist of a regular expression
+ * matching the offending error. If an object has multiple regex criteria, they
+ * ALL need to match an error in order for that error not to cause a test
+ * failure. */
+let whitelist = [
+ // CodeMirror is imported as-is, see bug 1004423.
+ {sourceName: /codemirror\.css$/i,
+ isFromDevTools: true},
+ // The debugger uses cross-browser CSS.
+ {sourceName: /devtools\/client\/debugger\/new\/styles.css/i,
+ isFromDevTools: true},
+ // PDFjs is futureproofing its pseudoselectors, and those rules are dropped.
+ {sourceName: /web\/viewer\.css$/i,
+ errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i,
+ isFromDevTools: false},
+ // Tracked in bug 1004428.
+ {sourceName: /aboutaccounts\/(main|normalize)\.css$/i,
+ isFromDevTools: false},
+ // Highlighter CSS uses a UA-only pseudo-class, see bug 985597.
+ {sourceName: /highlighters\.css$/i,
+ errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i,
+ isFromDevTools: true},
+ // Responsive Design Mode CSS uses a UA-only pseudo-class, see Bug 1241714.
+ {sourceName: /responsive-ua\.css$/i,
+ errorMessage: /Unknown pseudo-class.*moz-dropdown-list/i,
+ isFromDevTools: true},
+
+ {sourceName: /\b(contenteditable|EditorOverride|svg|forms|html|mathml|ua)\.css$/i,
+ errorMessage: /Unknown pseudo-class.*-moz-/i,
+ isFromDevTools: false},
+ {sourceName: /\b(html|mathml|ua)\.css$/i,
+ errorMessage: /Unknown property.*-moz-/i,
+ isFromDevTools: false},
+ // Reserved to UA sheets unless layout.css.overflow-clip-box.enabled flipped to true.
+ {sourceName: /res\/forms\.css$/i,
+ errorMessage: /Unknown property.*overflow-clip-box/i,
+ isFromDevTools: false},
+ {sourceName: /res\/(ua|html)\.css$/i,
+ errorMessage: /Unknown pseudo-class .*\bfullscreen\b/i,
+ isFromDevTools: false},
+ {sourceName: /skin\/timepicker\.css$/i,
+ errorMessage: /Error in parsing.*mask/i,
+ isFromDevTools: false},
+];
+
+// Platform can be "linux", "macosx" or "win". If omitted, the exception applies to all platforms.
+let allowedImageReferences = [
+ // Bug 1302691
+ {file: "chrome://devtools/skin/images/dock-bottom-minimize@2x.png",
+ from: "chrome://devtools/skin/toolbox.css",
+ isFromDevTools: true},
+ {file: "chrome://devtools/skin/images/dock-bottom-maximize@2x.png",
+ from: "chrome://devtools/skin/toolbox.css",
+ isFromDevTools: true},
+];
+
+var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
+var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
+
+// Add suffix to stylesheets' URI so that we always load them here and
+// have them parsed. Add a random number so that even if we run this
+// test multiple times, it would be unlikely to affect each other.
+const kPathSuffix = "?always-parse-css-" + Math.random();
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in whitelist
+ *
+ * @param aErrorObject the error to check
+ * @return true if the error should be ignored, false otherwise.
+ */
+function ignoredError(aErrorObject) {
+ for (let whitelistItem of whitelist) {
+ let matches = true;
+ for (let prop of ["sourceName", "errorMessage"]) {
+ if (whitelistItem.hasOwnProperty(prop) &&
+ !whitelistItem[prop].test(aErrorObject[prop] || "")) {
+ matches = false;
+ break;
+ }
+ }
+ if (matches) {
+ whitelistItem.used = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+function once(target, name) {
+ return new Promise((resolve, reject) => {
+ let cb = () => {
+ target.removeEventListener(name, cb);
+ resolve();
+ };
+ target.addEventListener(name, cb);
+ });
+}
+
+function fetchFile(uri) {
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.responseType = "text";
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (this.readyState != this.DONE) {
+ return;
+ }
+ try {
+ resolve(this.responseText);
+ } catch (ex) {
+ ok(false, `Script error reading ${uri}: ${ex}`);
+ resolve("");
+ }
+ };
+ xhr.onerror = error => {
+ ok(false, `XHR error reading ${uri}: ${error}`);
+ resolve("");
+ };
+ xhr.send(null);
+ });
+}
+
+var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIChromeRegistry);
+var gChromeMap = new Map();
+
+function getBaseUriForChromeUri(chromeUri) {
+ let chromeFile = chromeUri + "gobbledygooknonexistentfile.reallynothere";
+ let uri = Services.io.newURI(chromeFile, null, null);
+ let fileUri = gChromeReg.convertChromeURL(uri);
+ return fileUri.resolve(".");
+}
+
+function parseManifest(manifestUri) {
+ return fetchFile(manifestUri.spec).then(data => {
+ for (let line of data.split('\n')) {
+ let [type, ...argv] = line.split(/\s+/);
+ let component;
+ if (type == "content" || type == "skin") {
+ [component] = argv;
+ } else {
+ // skip unrelated lines
+ continue;
+ }
+ let chromeUri = `chrome://${component}/${type}/`;
+ gChromeMap.set(getBaseUriForChromeUri(chromeUri), chromeUri);
+ }
+ });
+}
+
+function convertToChromeUri(fileUri) {
+ let baseUri = fileUri.spec;
+ let path = "";
+ while (true) {
+ let slashPos = baseUri.lastIndexOf("/", baseUri.length - 2);
+ if (slashPos < 0) {
+ info(`File not accessible from chrome protocol: ${fileUri.path}`);
+ return fileUri;
+ }
+ path = baseUri.slice(slashPos + 1) + path;
+ baseUri = baseUri.slice(0, slashPos + 1);
+ if (gChromeMap.has(baseUri)) {
+ let chromeBaseUri = gChromeMap.get(baseUri);
+ let chromeUri = `${chromeBaseUri}${path}`;
+ return Services.io.newURI(chromeUri, null, null);
+ }
+ }
+}
+
+function messageIsCSSError(msg) {
+ // Only care about CSS errors generated by our iframe:
+ if ((msg instanceof Ci.nsIScriptError) &&
+ msg.category.includes("CSS") &&
+ msg.sourceName.endsWith(kPathSuffix)) {
+ let sourceName = msg.sourceName.slice(0, -kPathSuffix.length);
+ let msgInfo = { sourceName, errorMessage: msg.errorMessage };
+ // Check if this error is whitelisted in whitelist
+ if (!ignoredError(msgInfo)) {
+ ok(false, `Got error message for ${sourceName}: ${msg.errorMessage}`);
+ return true;
+ }
+ info(`Ignored error for ${sourceName} because of filter.`);
+ }
+ return false;
+}
+
+let imageURIsToReferencesMap = new Map();
+
+function processCSSRules(sheet) {
+ for (let rule of sheet.cssRules) {
+ if (rule instanceof CSSMediaRule) {
+ processCSSRules(rule);
+ continue;
+ }
+ if (!(rule instanceof CSSStyleRule))
+ continue;
+
+ // Extract urls from the css text.
+ // Note: CSSStyleRule.cssText always has double quotes around URLs even
+ // when the original CSS file didn't.
+ let urls = rule.cssText.match(/url\("[^"]*"\)/g);
+ if (!urls)
+ continue;
+
+ for (let url of urls) {
+ // Remove the url(" prefix and the ") suffix.
+ url = url.replace(/url\("(.*)"\)/, "$1");
+ if (url.startsWith("data:"))
+ continue;
+
+ // Make the url absolute and remove the ref.
+ let baseURI = Services.io.newURI(rule.parentStyleSheet.href, null, null);
+ url = Services.io.newURI(url, null, baseURI).specIgnoringRef;
+
+ // Store the image url along with the css file referencing it.
+ let baseUrl = baseURI.spec.split("?always-parse-css")[0];
+ if (!imageURIsToReferencesMap.has(url)) {
+ imageURIsToReferencesMap.set(url, new Set([baseUrl]));
+ } else {
+ imageURIsToReferencesMap.get(url).add(baseUrl);
+ }
+ }
+ }
+}
+
+function chromeFileExists(aURI)
+{
+ let available = 0;
+ try {
+ let channel = NetUtil.newChannel({uri: aURI, loadUsingSystemPrincipal: true});
+ let stream = channel.open();
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sstream.init(stream);
+ available = sstream.available();
+ sstream.close();
+ } catch (e) {
+ if (e.result != Components.results.NS_ERROR_FILE_NOT_FOUND) {
+ dump("Checking " + aURI + ": " + e + "\n");
+ Cu.reportError(e);
+ }
+ }
+ return available > 0;
+}
+
+add_task(function* checkAllTheCSS() {
+ let appDir = Services.dirsvc.get("GreD", Ci.nsIFile);
+ // This asynchronously produces a list of URLs (sadly, mostly sync on our
+ // test infrastructure because it runs against jarfiles there, and
+ // our zipreader APIs are all sync)
+ let uris = yield generateURIsFromDirTree(appDir, [".css", ".manifest"]);
+
+ // Create a clean iframe to load all the files into. This needs to live at a
+ // chrome URI so that it's allowed to load and parse any styles.
+ let testFile = getRootDirectory(gTestPath) + "dummy_page.html";
+ let windowless = Services.appShell.createWindowlessBrowser();
+ let iframe = windowless.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
+ windowless.document.documentElement.appendChild(iframe);
+ let iframeLoaded = once(iframe, 'load');
+ iframe.contentWindow.location = testFile;
+ yield iframeLoaded;
+ let doc = iframe.contentWindow.document;
+
+ // Parse and remove all manifests from the list.
+ // NOTE that this must be done before filtering out devtools paths
+ // so that all chrome paths can be recorded.
+ let manifestPromises = [];
+ uris = uris.filter(uri => {
+ if (uri.path.endsWith(".manifest")) {
+ manifestPromises.push(parseManifest(uri));
+ return false;
+ }
+ return true;
+ });
+ // Wait for all manifest to be parsed
+ yield Promise.all(manifestPromises);
+
+ // We build a list of promises that get resolved when their respective
+ // files have loaded and produced no errors.
+ let allPromises = [];
+
+ // filter out either the devtools paths or the non-devtools paths:
+ let isDevtools = SimpleTest.harnessParameters.subsuite == "devtools";
+ let devtoolsPathBits = ["webide", "devtools"];
+ uris = uris.filter(uri => isDevtools == devtoolsPathBits.some(path => uri.spec.includes(path)));
+
+ for (let uri of uris) {
+ let linkEl = doc.createElement("link");
+ linkEl.setAttribute("rel", "stylesheet");
+ let promiseForThisSpec = Promise.defer();
+ let onLoad = (e) => {
+ processCSSRules(linkEl.sheet);
+ promiseForThisSpec.resolve();
+ linkEl.removeEventListener("load", onLoad);
+ linkEl.removeEventListener("error", onError);
+ };
+ let onError = (e) => {
+ ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!");
+ promiseForThisSpec.resolve();
+ linkEl.removeEventListener("load", onLoad);
+ linkEl.removeEventListener("error", onError);
+ };
+ linkEl.addEventListener("load", onLoad);
+ linkEl.addEventListener("error", onError);
+ linkEl.setAttribute("type", "text/css");
+ let chromeUri = convertToChromeUri(uri);
+ linkEl.setAttribute("href", chromeUri.spec + kPathSuffix);
+ allPromises.push(promiseForThisSpec.promise);
+ doc.head.appendChild(linkEl);
+ }
+
+ // Wait for all the files to have actually loaded:
+ yield Promise.all(allPromises);
+
+ // Check if all the files referenced from CSS actually exist.
+ for (let [image, references] of imageURIsToReferencesMap) {
+ if (!chromeFileExists(image)) {
+ for (let ref of references) {
+ let ignored = false;
+ for (let item of allowedImageReferences) {
+ if (image.endsWith(item.file) && ref.endsWith(item.from) &&
+ isDevtools == item.isFromDevTools &&
+ (!item.platforms || item.platforms.includes(AppConstants.platform))) {
+ item.used = true;
+ ignored = true;
+ break;
+ }
+ }
+ if (!ignored)
+ ok(false, "missing " + image + " referenced from " + ref);
+ }
+ }
+ }
+
+ let messages = Services.console.getMessageArray();
+ // Count errors (the test output will list actual issues for us, as well
+ // as the ok(false) in messageIsCSSError.
+ let errors = messages.filter(messageIsCSSError);
+ is(errors.length, 0, "All the styles (" + allPromises.length + ") loaded without errors.");
+
+ // Confirm that all whitelist rules have been used.
+ for (let item of whitelist) {
+ if (!item.used && isDevtools == item.isFromDevTools) {
+ ok(false, "Unused whitelist item. " +
+ (item.sourceName ? " sourceName: " + item.sourceName : "") +
+ (item.errorMessage ? " errorMessage: " + item.errorMessage : ""));
+ }
+ }
+
+ // Confirm that all file whitelist rules have been used.
+ for (let item of allowedImageReferences) {
+ if (!item.used && isDevtools == item.isFromDevTools &&
+ (!item.platforms || item.platforms.includes(AppConstants.platform))) {
+ ok(false, "Unused file whitelist item. " +
+ " file: " + item.file +
+ " from: " + item.from);
+ }
+ }
+
+ // Clean up to avoid leaks:
+ iframe.remove();
+ doc.head.innerHTML = '';
+ doc = null;
+ iframe = null;
+ windowless.close();
+ windowless = null;
+ imageURIsToReferencesMap = null;
+});
diff --git a/browser/base/content/test/general/browser_parsable_script.js b/browser/base/content/test/general/browser_parsable_script.js
new file mode 100644
index 000000000..50333dd65
--- /dev/null
+++ b/browser/base/content/test/general/browser_parsable_script.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' JS issues to remain, while we
+ * detect newly occurring issues in shipping JS. It is a list of regexes
+ * matching files which have errors:
+ */
+const kWhitelist = new Set([
+ /defaults\/profile\/prefs.js$/,
+ /browser\/content\/browser\/places\/controller.js$/,
+]);
+
+
+var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
+var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
+
+// Normally we would use reflect.jsm to get Reflect.parse. However, if
+// we do that, then all the AST data is allocated in reflect.jsm's
+// zone. That exposes a bug in our GC. The GC collects reflect.jsm's
+// zone but not the zone in which our test code lives (since no new
+// data is being allocated in it). The cross-compartment wrappers in
+// our zone that point to the AST data never get collected, and so the
+// AST data itself is never collected. We need to GC both zones at
+// once to fix the problem.
+const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance();
+init();
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in kWhitelist
+ *
+ * @param uri the uri to check against the whitelist
+ * @return true if the uri should be skipped, false otherwise.
+ */
+function uriIsWhiteListed(uri) {
+ for (let whitelistItem of kWhitelist) {
+ if (whitelistItem.test(uri.spec)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function parsePromise(uri) {
+ let promise = new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", uri, true);
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ let scriptText = this.responseText;
+ try {
+ info("Checking " + uri);
+ Reflect.parse(scriptText);
+ resolve(true);
+ } catch (ex) {
+ let errorMsg = "Script error reading " + uri + ": " + ex;
+ ok(false, errorMsg);
+ resolve(false);
+ }
+ }
+ };
+ xhr.onerror = (error) => {
+ ok(false, "XHR error reading " + uri + ": " + error);
+ resolve(false);
+ };
+ xhr.overrideMimeType("application/javascript");
+ xhr.send(null);
+ });
+ return promise;
+}
+
+add_task(function* checkAllTheJS() {
+ // In debug builds, even on a fast machine, collecting the file list may take
+ // more than 30 seconds, and parsing all files may take four more minutes.
+ // For this reason, this test must be explictly requested in debug builds by
+ // using the "--setpref parse=<filter>" argument to mach. You can specify:
+ // - A case-sensitive substring of the file name to test (slow).
+ // - A single absolute URI printed out by a previous run (fast).
+ // - An empty string to run the test on all files (slowest).
+ let parseRequested = Services.prefs.prefHasUserValue("parse");
+ let parseValue = parseRequested && Services.prefs.getCharPref("parse");
+ if (SpecialPowers.isDebugBuild) {
+ if (!parseRequested) {
+ ok(true, "Test disabled on debug build. To run, execute: ./mach" +
+ " mochitest-browser --setpref parse=<case_sensitive_filter>" +
+ " browser/base/content/test/general/browser_parsable_script.js");
+ return;
+ }
+ // Request a 15 minutes timeout (30 seconds * 30) for debug builds.
+ requestLongerTimeout(30);
+ }
+
+ let uris;
+ // If an absolute URI is specified on the command line, use it immediately.
+ if (parseValue && parseValue.includes(":")) {
+ uris = [NetUtil.newURI(parseValue)];
+ } else {
+ let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+ // This asynchronously produces a list of URLs (sadly, mostly sync on our
+ // test infrastructure because it runs against jarfiles there, and
+ // our zipreader APIs are all sync)
+ let startTimeMs = Date.now();
+ info("Collecting URIs");
+ uris = yield generateURIsFromDirTree(appDir, [".js", ".jsm"]);
+ info("Collected URIs in " + (Date.now() - startTimeMs) + "ms");
+
+ // Apply the filter specified on the command line, if any.
+ if (parseValue) {
+ uris = uris.filter(uri => {
+ if (uri.spec.includes(parseValue)) {
+ return true;
+ }
+ info("Not checking filtered out " + uri.spec);
+ return false;
+ });
+ }
+ }
+
+ // We create an array of promises so we can parallelize all our parsing
+ // and file loading activity:
+ let allPromises = [];
+ for (let uri of uris) {
+ if (uriIsWhiteListed(uri)) {
+ info("Not checking whitelisted " + uri.spec);
+ continue;
+ }
+ allPromises.push(parsePromise(uri.spec));
+ }
+
+ let promiseResults = yield Promise.all(allPromises);
+ is(promiseResults.filter((x) => !x).length, 0, "There should be 0 parsing errors");
+});
diff --git a/browser/base/content/test/general/browser_permissions.js b/browser/base/content/test/general/browser_permissions.js
new file mode 100644
index 000000000..721a669d2
--- /dev/null
+++ b/browser/base/content/test/general/browser_permissions.js
@@ -0,0 +1,202 @@
+/*
+ * Test the Permissions section in the Control Center.
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PERMISSIONS_PAGE = "http://example.com/browser/browser/base/content/test/general/permissions.html";
+var {SitePermissions} = Cu.import("resource:///modules/SitePermissions.jsm", {});
+
+registerCleanupFunction(function() {
+ SitePermissions.remove(gBrowser.currentURI, "cookie");
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+ SitePermissions.remove(gBrowser.currentURI, "microphone");
+
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function* openIdentityPopup() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+ gIdentityHandler._identityBox.click();
+ return promise;
+}
+
+function* closeIdentityPopup() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
+ gIdentityHandler._identityPopup.hidePopup();
+ return promise;
+}
+
+add_task(function* testMainViewVisible() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ let permissionsList = document.getElementById("identity-popup-permission-list");
+ let emptyLabel = permissionsList.nextSibling.nextSibling;
+
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyLabel), "List of permissions is empty");
+
+ yield closeIdentityPopup();
+
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
+
+ yield openIdentityPopup();
+
+ ok(is_hidden(emptyLabel), "List of permissions is not empty");
+
+ let labelText = SitePermissions.getPermissionLabel("camera");
+ let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
+ is(labels.length, 1, "One permission visible in main view");
+ is(labels[0].textContent, labelText, "Correct value");
+
+ let img = permissionsList.querySelector("image.identity-popup-permission-icon");
+ ok(img, "There is an image for the permissions");
+ ok(img.classList.contains("camera-icon"), "proper class is in image class");
+
+ yield closeIdentityPopup();
+
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyLabel), "List of permissions is empty");
+
+ yield closeIdentityPopup();
+});
+
+add_task(function* testIdentityIcon() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
+
+ ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box signals granted permissions");
+
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+
+ ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box doesn't signal granted permissions");
+
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
+
+ ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box doesn't signal granted permissions");
+
+ SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.SESSION);
+
+ ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
+ "identity-box signals granted permissions");
+
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+ SitePermissions.remove(gBrowser.currentURI, "cookie");
+});
+
+add_task(function* testCancelPermission() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ let permissionsList = document.getElementById("identity-popup-permission-list");
+ let emptyLabel = permissionsList.nextSibling.nextSibling;
+
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
+
+ yield openIdentityPopup();
+
+ ok(is_hidden(emptyLabel), "List of permissions is not empty");
+
+ let cancelButtons = permissionsList
+ .querySelectorAll(".identity-popup-permission-remove-button");
+
+ cancelButtons[0].click();
+ let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
+ is(labels.length, 1, "One permission should be removed");
+ cancelButtons[1].click();
+ labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
+ is(labels.length, 0, "One permission should be removed");
+
+ yield closeIdentityPopup();
+});
+
+add_task(function* testPermissionHints() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ let permissionsList = document.getElementById("identity-popup-permission-list");
+ let emptyHint = document.getElementById("identity-popup-permission-empty-hint");
+ let reloadHint = document.getElementById("identity-popup-permission-reload-hint");
+
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyHint), "Empty hint is visible");
+ ok(is_hidden(reloadHint), "Reload hint is hidden");
+
+ yield closeIdentityPopup();
+
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
+
+ yield openIdentityPopup();
+
+ ok(is_hidden(emptyHint), "Empty hint is hidden");
+ ok(is_hidden(reloadHint), "Reload hint is hidden");
+
+ let cancelButtons = permissionsList
+ .querySelectorAll(".identity-popup-permission-remove-button");
+ SitePermissions.remove(gBrowser.currentURI, "camera");
+
+ cancelButtons[0].click();
+ ok(is_hidden(emptyHint), "Empty hint is hidden");
+ ok(!is_hidden(reloadHint), "Reload hint is visible");
+
+ cancelButtons[1].click();
+ ok(is_hidden(emptyHint), "Empty hint is hidden");
+ ok(!is_hidden(reloadHint), "Reload hint is visible");
+
+ yield closeIdentityPopup();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+ yield openIdentityPopup();
+
+ ok(!is_hidden(emptyHint), "Empty hint is visible after reloading");
+ ok(is_hidden(reloadHint), "Reload hint is hidden after reloading");
+
+ yield closeIdentityPopup();
+});
+
+add_task(function* testPermissionIcons() {
+ let {gIdentityHandler} = gBrowser.ownerGlobal;
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
+
+ SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
+ SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.BLOCK);
+ SitePermissions.set(gBrowser.currentURI, "microphone", SitePermissions.SESSION);
+
+ let geoIcon = gIdentityHandler._identityBox
+ .querySelector(".blocked-permission-icon[data-permission-id='geo']");
+ ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
+
+ let cameraIcon = gIdentityHandler._identityBox
+ .querySelector(".blocked-permission-icon[data-permission-id='camera']");
+ ok(!cameraIcon.hasAttribute("showing"),
+ "allowed permission icon is not shown");
+
+ let microphoneIcon = gIdentityHandler._identityBox
+ .querySelector(".blocked-permission-icon[data-permission-id='microphone']");
+ ok(!microphoneIcon.hasAttribute("showing"),
+ "allowed permission icon is not shown");
+
+ SitePermissions.remove(gBrowser.currentURI, "geo");
+
+ ok(!geoIcon.hasAttribute("showing"),
+ "blocked permission icon is not shown after reset");
+});
diff --git a/browser/base/content/test/general/browser_pinnedTabs.js b/browser/base/content/test/general/browser_pinnedTabs.js
new file mode 100644
index 000000000..e0ddb5072
--- /dev/null
+++ b/browser/base/content/test/general/browser_pinnedTabs.js
@@ -0,0 +1,75 @@
+var tabs;
+
+function index(tab) {
+ return Array.indexOf(gBrowser.tabs, tab);
+}
+
+function indexTest(tab, expectedIndex, msg) {
+ var diag = "tab " + tab + " should be at index " + expectedIndex;
+ if (msg)
+ msg = msg + " (" + diag + ")";
+ else
+ msg = diag;
+ is(index(tabs[tab]), expectedIndex, msg);
+}
+
+function PinUnpinHandler(tab, eventName) {
+ this.eventCount = 0;
+ var self = this;
+ tab.addEventListener(eventName, function() {
+ tab.removeEventListener(eventName, arguments.callee, true);
+
+ self.eventCount++;
+ }, true);
+ gBrowser.tabContainer.addEventListener(eventName, function(e) {
+ gBrowser.tabContainer.removeEventListener(eventName, arguments.callee, true);
+
+ if (e.originalTarget == tab) {
+ self.eventCount++;
+ }
+ }, true);
+}
+
+function test() {
+ tabs = [gBrowser.selectedTab, gBrowser.addTab(), gBrowser.addTab(), gBrowser.addTab()];
+ indexTest(0, 0);
+ indexTest(1, 1);
+ indexTest(2, 2);
+ indexTest(3, 3);
+
+ var eh = new PinUnpinHandler(tabs[3], "TabPinned");
+ gBrowser.pinTab(tabs[3]);
+ is(eh.eventCount, 2, "TabPinned event should be fired");
+ indexTest(0, 1);
+ indexTest(1, 2);
+ indexTest(2, 3);
+ indexTest(3, 0);
+
+ eh = new PinUnpinHandler(tabs[1], "TabPinned");
+ gBrowser.pinTab(tabs[1]);
+ is(eh.eventCount, 2, "TabPinned event should be fired");
+ indexTest(0, 2);
+ indexTest(1, 1);
+ indexTest(2, 3);
+ indexTest(3, 0);
+
+ gBrowser.moveTabTo(tabs[3], 3);
+ indexTest(3, 1, "shouldn't be able to mix a pinned tab into normal tabs");
+
+ gBrowser.moveTabTo(tabs[2], 0);
+ indexTest(2, 2, "shouldn't be able to mix a normal tab into pinned tabs");
+
+ eh = new PinUnpinHandler(tabs[1], "TabUnpinned");
+ gBrowser.unpinTab(tabs[1]);
+ is(eh.eventCount, 2, "TabUnpinned event should be fired");
+ indexTest(1, 1, "unpinning a tab should move a tab to the start of normal tabs");
+
+ eh = new PinUnpinHandler(tabs[3], "TabUnpinned");
+ gBrowser.unpinTab(tabs[3]);
+ is(eh.eventCount, 2, "TabUnpinned event should be fired");
+ indexTest(3, 0, "unpinning a tab should move a tab to the start of normal tabs");
+
+ gBrowser.removeTab(tabs[1]);
+ gBrowser.removeTab(tabs[2]);
+ gBrowser.removeTab(tabs[3]);
+}
diff --git a/browser/base/content/test/general/browser_plainTextLinks.js b/browser/base/content/test/general/browser_plainTextLinks.js
new file mode 100644
index 000000000..7a304fce0
--- /dev/null
+++ b/browser/base/content/test/general/browser_plainTextLinks.js
@@ -0,0 +1,146 @@
+function testExpected(expected, msg) {
+ is(document.getElementById("context-openlinkincurrent").hidden, expected, msg);
+}
+
+function testLinkExpected(expected, msg) {
+ is(gContextMenu.linkURL, expected, msg);
+}
+
+add_task(function *() {
+ const url = "data:text/html;charset=UTF-8,Test For Non-Hyperlinked url selection";
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield SimpleTest.promiseFocus(gBrowser.selectedBrowser.contentWindowAsCPOW);
+
+ // Initial setup of the content area.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+ let doc = content.document;
+ let range = doc.createRange();
+ let selection = content.getSelection();
+
+ let mainDiv = doc.createElement("div");
+ let div = doc.createElement("div");
+ let div2 = doc.createElement("div");
+ let span1 = doc.createElement("span");
+ let span2 = doc.createElement("span");
+ let span3 = doc.createElement("span");
+ let span4 = doc.createElement("span");
+ let p1 = doc.createElement("p");
+ let p2 = doc.createElement("p");
+ span1.textContent = "http://index.";
+ span2.textContent = "example.com example.com";
+ span3.textContent = " - Test";
+ span4.innerHTML = "<a href='http://www.example.com'>http://www.example.com/example</a>";
+ p1.textContent = "mailto:test.com ftp.example.com";
+ p2.textContent = "example.com -";
+ div.appendChild(span1);
+ div.appendChild(span2);
+ div.appendChild(span3);
+ div.appendChild(span4);
+ div.appendChild(p1);
+ div.appendChild(p2);
+ let p3 = doc.createElement("p");
+ p3.textContent = "main.example.com";
+ div2.appendChild(p3);
+ mainDiv.appendChild(div);
+ mainDiv.appendChild(div2);
+ doc.body.appendChild(mainDiv);
+
+ function setSelection(el1, el2, index1, index2) {
+ while (el1.nodeType != el1.TEXT_NODE)
+ el1 = el1.firstChild;
+ while (el2.nodeType != el1.TEXT_NODE)
+ el2 = el2.firstChild;
+
+ selection.removeAllRanges();
+ range.setStart(el1, index1);
+ range.setEnd(el2, index2);
+ selection.addRange(range);
+
+ return range;
+ }
+
+ // Each of these tests creates a selection and returns a range within it.
+ content.tests = [
+ () => setSelection(span1.firstChild, span2.firstChild, 0, 11),
+ () => setSelection(span1.firstChild, span2.firstChild, 7, 11),
+ () => setSelection(span1.firstChild, span2.firstChild, 8, 11),
+ () => setSelection(span2.firstChild, span2.firstChild, 0, 11),
+ () => setSelection(span2.firstChild, span2.firstChild, 11, 23),
+ () => setSelection(span2.firstChild, span2.firstChild, 0, 10),
+ () => setSelection(span2.firstChild, span3.firstChild, 12, 7),
+ () => setSelection(span2.firstChild, span2.firstChild, 12, 19),
+ () => setSelection(p1.firstChild, p1.firstChild, 0, 15),
+ () => setSelection(p1.firstChild, p1.firstChild, 16, 31),
+ () => setSelection(p2.firstChild, p2.firstChild, 0, 14),
+ () => {
+ selection.selectAllChildren(div2);
+ return selection.getRangeAt(0);
+ },
+ () => {
+ selection.selectAllChildren(span4);
+ return selection.getRangeAt(0);
+ },
+ () => {
+ mainDiv.innerHTML = "(open-suse.ru)";
+ return setSelection(mainDiv, mainDiv, 1, 13);
+ },
+ () => setSelection(mainDiv, mainDiv, 1, 14)
+ ];
+ });
+
+ let checks = [
+ () => testExpected(false, "The link context menu should show for http://www.example.com"),
+ () => testExpected(false, "The link context menu should show for www.example.com"),
+ () => testExpected(true, "The link context menu should not show for ww.example.com"),
+ () => {
+ testExpected(false, "The link context menu should show for example.com");
+ testLinkExpected("http://example.com/", "url for example.com selection should not prepend www");
+ },
+ () => testExpected(false, "The link context menu should show for example.com"),
+ () => testExpected(true, "Link options should not show for selection that's not at a word boundary"),
+ () => testExpected(true, "Link options should not show for selection that has whitespace"),
+ () => testExpected(true, "Link options should not show unless a url is selected"),
+ () => testExpected(true, "Link options should not show for mailto: links"),
+ () => {
+ testExpected(false, "Link options should show for ftp.example.com");
+ testLinkExpected("http://ftp.example.com/", "ftp.example.com should be preceeded with http://");
+ },
+ () => testExpected(false, "Link options should show for www.example.com "),
+ () => testExpected(false, "Link options should show for triple-click selections"),
+ () => testLinkExpected("http://www.example.com/", "Linkified text should open the correct link"),
+ () => {
+ testExpected(false, "Link options should show for open-suse.ru");
+ testLinkExpected("http://open-suse.ru/", "Linkified text should open the correct link");
+ },
+ () => testExpected(true, "Link options should not show for 'open-suse.ru)'")
+ ];
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+
+ for (let testid = 0; testid < checks.length; testid++) {
+ let menuPosition = yield ContentTask.spawn(gBrowser.selectedBrowser, { testid: testid }, function* (arg) {
+ let range = content.tests[arg.testid]();
+
+ // Get the range of the selection and determine its coordinates. These
+ // coordinates will be returned to the parent process and the context menu
+ // will be opened at that location.
+ let rangeRect = range.getBoundingClientRect();
+ return [rangeRect.x + 3, rangeRect.y + 3];
+ });
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtPoint(menuPosition[0], menuPosition[1],
+ { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ checks[testid]();
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+ }
+
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_printpreview.js b/browser/base/content/test/general/browser_printpreview.js
new file mode 100644
index 000000000..c38fc18be
--- /dev/null
+++ b/browser/base/content/test/general/browser_printpreview.js
@@ -0,0 +1,74 @@
+let ourTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", true).then(function(tab) {
+ ourTab = tab;
+ ok(!gInPrintPreviewMode,
+ "Should NOT be in print preview mode at starting this tests");
+ // Skip access key test on platforms which don't support access key.
+ if (!/Win|Linux/.test(navigator.platform)) {
+ openPrintPreview(testClosePrintPreviewWithEscKey);
+ } else {
+ openPrintPreview(testClosePrintPreviewWithAccessKey);
+ }
+ });
+}
+
+function tidyUp() {
+ BrowserTestUtils.removeTab(ourTab).then(finish);
+}
+
+function testClosePrintPreviewWithAccessKey() {
+ EventUtils.synthesizeKey("c", { altKey: true });
+ checkPrintPreviewClosed(function (aSucceeded) {
+ ok(aSucceeded,
+ "print preview mode should be finished by access key");
+ openPrintPreview(testClosePrintPreviewWithEscKey);
+ });
+}
+
+function testClosePrintPreviewWithEscKey() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ checkPrintPreviewClosed(function (aSucceeded) {
+ ok(aSucceeded,
+ "print preview mode should be finished by Esc key press");
+ openPrintPreview(testClosePrintPreviewWithClosingWindowShortcutKey);
+ });
+}
+
+function testClosePrintPreviewWithClosingWindowShortcutKey() {
+ EventUtils.synthesizeKey("w", { accelKey: true });
+ checkPrintPreviewClosed(function (aSucceeded) {
+ ok(aSucceeded,
+ "print preview mode should be finished by closing window shortcut key");
+ tidyUp();
+ });
+}
+
+function openPrintPreview(aCallback) {
+ document.getElementById("cmd_printPreview").doCommand();
+ executeSoon(function () {
+ if (gInPrintPreviewMode) {
+ executeSoon(aCallback);
+ return;
+ }
+ executeSoon(arguments.callee);
+ });
+}
+
+function checkPrintPreviewClosed(aCallback) {
+ let count = 0;
+ executeSoon(function () {
+ if (!gInPrintPreviewMode) {
+ executeSoon(function () { aCallback(count < 1000); });
+ return;
+ }
+ if (++count == 1000) {
+ // The test might fail.
+ PrintUtils.exitPrintPreview();
+ }
+ executeSoon(arguments.callee);
+ });
+}
diff --git a/browser/base/content/test/general/browser_private_browsing_window.js b/browser/base/content/test/general/browser_private_browsing_window.js
new file mode 100644
index 000000000..607a34060
--- /dev/null
+++ b/browser/base/content/test/general/browser_private_browsing_window.js
@@ -0,0 +1,65 @@
+// Make sure that we can open private browsing windows
+
+function test() {
+ waitForExplicitFinish();
+ var nonPrivateWin = OpenBrowserWindow();
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow() should open a normal window");
+ nonPrivateWin.close();
+
+ var privateWin = OpenBrowserWindow({private: true});
+ ok(PrivateBrowsingUtils.isWindowPrivate(privateWin), "OpenBrowserWindow({private: true}) should open a private window");
+
+ nonPrivateWin = OpenBrowserWindow({private: false});
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow({private: false}) should open a normal window");
+ nonPrivateWin.close();
+
+ whenDelayedStartupFinished(privateWin, function() {
+ nonPrivateWin = privateWin.OpenBrowserWindow({private: false});
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "privateWin.OpenBrowserWindow({private: false}) should open a normal window");
+
+ nonPrivateWin.close();
+
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accesskey: true },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accesskey: false },
+ ].forEach(function(menu) {
+ let newWindow = privateWin.document.getElementById(menu.normal);
+ let newPrivateWindow = privateWin.document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ ok(!newPrivateWindow.hidden, "New Private Window menu item should be hidden");
+ isnot(newWindow.label, newPrivateWindow.label, "New Window's label shouldn't be overwritten");
+ if (menu.accesskey) {
+ isnot(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey shouldn't be overwritten");
+ }
+ isnot(newWindow.command, newPrivateWindow.command, "New Window's command shouldn't be overwritten");
+ }
+ });
+
+ privateWin.close();
+
+ Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true);
+ privateWin = OpenBrowserWindow({private: true});
+ whenDelayedStartupFinished(privateWin, function() {
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accessKey: true },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accessKey: false },
+ ].forEach(function(menu) {
+ let newWindow = privateWin.document.getElementById(menu.normal);
+ let newPrivateWindow = privateWin.document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ ok(newPrivateWindow.hidden, "New Private Window menu item should be hidden");
+ is(newWindow.label, newPrivateWindow.label, "New Window's label should be overwritten");
+ if (menu.accesskey) {
+ is(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey should be overwritten");
+ }
+ is(newWindow.command, newPrivateWindow.command, "New Window's command should be overwritten");
+ }
+ });
+
+ privateWin.close();
+ Services.prefs.clearUserPref("browser.privatebrowsing.autostart");
+ finish();
+ });
+ });
+}
+
diff --git a/browser/base/content/test/general/browser_private_no_prompt.js b/browser/base/content/test/general/browser_private_no_prompt.js
new file mode 100644
index 000000000..c6c580f80
--- /dev/null
+++ b/browser/base/content/test/general/browser_private_no_prompt.js
@@ -0,0 +1,12 @@
+function test() {
+ waitForExplicitFinish();
+ var privateWin = OpenBrowserWindow({private: true});
+
+ whenDelayedStartupFinished(privateWin, function () {
+ privateWin.BrowserOpenTab();
+ privateWin.BrowserTryToCloseWindow();
+ ok(true, "didn't prompt");
+
+ executeSoon(finish);
+ });
+}
diff --git a/browser/base/content/test/general/browser_purgehistory_clears_sh.js b/browser/base/content/test/general/browser_purgehistory_clears_sh.js
new file mode 100644
index 000000000..1a1e6554d
--- /dev/null
+++ b/browser/base/content/test/general/browser_purgehistory_clears_sh.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const url = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+add_task(function* purgeHistoryTest() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url,
+ }, function* purgeHistoryTestInner(browser) {
+ let backButton = browser.ownerDocument.getElementById("Browser:Back");
+ let forwardButton = browser.ownerDocument.getElementById("Browser:Forward");
+
+ ok(!browser.webNavigation.canGoBack,
+ "Initial value for webNavigation.canGoBack");
+ ok(!browser.webNavigation.canGoForward,
+ "Initial value for webNavigation.canGoBack");
+ ok(backButton.hasAttribute("disabled"), "Back button is disabled");
+ ok(forwardButton.hasAttribute("disabled"), "Forward button is disabled");
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let startHistory = content.history.length;
+ content.history.pushState({}, "");
+ content.history.pushState({}, "");
+ content.history.back();
+ let newHistory = content.history.length;
+ Assert.equal(startHistory, 1, "Initial SHistory size");
+ Assert.equal(newHistory, 3, "New SHistory size");
+ });
+
+ ok(browser.webNavigation.canGoBack, true,
+ "New value for webNavigation.canGoBack");
+ ok(browser.webNavigation.canGoForward, true,
+ "New value for webNavigation.canGoForward");
+ ok(!backButton.hasAttribute("disabled"), "Back button was enabled");
+ ok(!forwardButton.hasAttribute("disabled"), "Forward button was enabled");
+
+
+ let tmp = {};
+ Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+ let {Sanitizer} = tmp;
+ let sanitizer = new Sanitizer();
+
+ yield sanitizer.sanitize(["history"]);
+
+ yield ContentTask.spawn(browser, null, function*() {
+ Assert.equal(content.history.length, 1, "SHistory correctly cleared");
+ });
+
+ ok(!browser.webNavigation.canGoBack,
+ "webNavigation.canGoBack correctly cleared");
+ ok(!browser.webNavigation.canGoForward,
+ "webNavigation.canGoForward correctly cleared");
+ ok(backButton.hasAttribute("disabled"), "Back button was disabled");
+ ok(forwardButton.hasAttribute("disabled"), "Forward button was disabled");
+ });
+});
diff --git a/browser/base/content/test/general/browser_refreshBlocker.js b/browser/base/content/test/general/browser_refreshBlocker.js
new file mode 100644
index 000000000..ee274f2c2
--- /dev/null
+++ b/browser/base/content/test/general/browser_refreshBlocker.js
@@ -0,0 +1,135 @@
+"use strict";
+
+const META_PAGE = "http://example.org/browser/browser/base/content/test/general/refresh_meta.sjs"
+const HEADER_PAGE = "http://example.org/browser/browser/base/content/test/general/refresh_header.sjs"
+const TARGET_PAGE = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+const PREF = "accessibility.blockautorefresh";
+
+/**
+ * Goes into the content, and simulates a meta-refresh header at a very
+ * low level, and checks to see if it was blocked. This will always cancel
+ * the refresh, regardless of whether or not the refresh was blocked.
+ *
+ * @param browser (<xul:browser>)
+ * The browser to test for refreshing.
+ * @param expectRefresh (bool)
+ * Whether or not we expect the refresh attempt to succeed.
+ * @returns Promise
+ */
+function* attemptFakeRefresh(browser, expectRefresh) {
+ yield ContentTask.spawn(browser, expectRefresh, function*(contentExpectRefresh) {
+ let URI = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
+ let refresher = docShell.QueryInterface(Ci.nsIRefreshURI);
+ refresher.refreshURI(URI, 0, false, true);
+
+ Assert.equal(refresher.refreshPending, contentExpectRefresh,
+ "Got the right refreshPending state");
+
+ if (refresher.refreshPending) {
+ // Cancel the pending refresh
+ refresher.cancelRefreshURITimers();
+ }
+
+ // The RefreshBlocker will wait until onLocationChange has
+ // been fired before it will show any notifications (see bug
+ // 1246291), so we cause this to occur manually here.
+ content.location = URI.spec + "#foo";
+ });
+}
+
+/**
+ * Tests that we can enable the blocking pref and block a refresh
+ * from occurring while showing a notification bar. Also tests that
+ * when we disable the pref, that refreshes can go through again.
+ */
+add_task(function* test_can_enable_and_block() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: TARGET_PAGE,
+ }, function*(browser) {
+ // By default, we should be able to reload the page.
+ yield attemptFakeRefresh(browser, true);
+
+ yield pushPrefs(["accessibility.blockautorefresh", true]);
+
+ let notificationPromise =
+ BrowserTestUtils.waitForNotificationBar(gBrowser, browser,
+ "refresh-blocked");
+
+ yield attemptFakeRefresh(browser, false);
+
+ yield notificationPromise;
+
+ yield pushPrefs(["accessibility.blockautorefresh", false]);
+
+ // Page reloads should go through again.
+ yield attemptFakeRefresh(browser, true);
+ });
+});
+
+/**
+ * Attempts a "real" refresh by opening a tab, and then sending it to
+ * an SJS page that will attempt to cause a refresh. This will also pass
+ * a delay amount to the SJS page. The refresh should be blocked, and
+ * the notification should be shown. Once shown, the "Allow" button will
+ * be clicked, and the refresh will go through. Finally, the helper will
+ * close the tab and resolve the Promise.
+ *
+ * @param refreshPage (string)
+ * The SJS page to use. Use META_PAGE for the <meta> tag refresh
+ * case. Use HEADER_PAGE for the HTTP header case.
+ * @param delay (int)
+ * The amount, in ms, for the page to wait before attempting the
+ * refresh.
+ *
+ * @returns Promise
+ */
+function* testRealRefresh(refreshPage, delay) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, function*(browser) {
+ yield pushPrefs(["accessibility.blockautorefresh", true]);
+
+ browser.loadURI(refreshPage + "?p=" + TARGET_PAGE + "&d=" + delay);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // Once browserLoaded resolves, all nsIWebProgressListener callbacks
+ // should have fired, so the notification should be visible.
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ let notification = notificationBox.currentNotification;
+
+ ok(notification, "Notification should be visible");
+ is(notification.value, "refresh-blocked",
+ "Should be showing the right notification");
+
+ // Then click the button to allow the refresh.
+ let buttons = notification.querySelectorAll(".notification-button");
+ is(buttons.length, 1, "Should have one button.");
+
+ // Prepare a Promise that should resolve when the refresh goes through
+ let refreshPromise = BrowserTestUtils.browserLoaded(browser);
+ buttons[0].click();
+
+ yield refreshPromise;
+ });
+}
+
+/**
+ * Tests the meta-tag case for both short and longer delay times.
+ */
+add_task(function* test_can_allow_refresh() {
+ yield testRealRefresh(META_PAGE, 0);
+ yield testRealRefresh(META_PAGE, 100);
+ yield testRealRefresh(META_PAGE, 500);
+});
+
+/**
+ * Tests that when a HTTP header case for both short and longer
+ * delay times.
+ */
+add_task(function* test_can_block_refresh_from_header() {
+ yield testRealRefresh(HEADER_PAGE, 0);
+ yield testRealRefresh(HEADER_PAGE, 100);
+ yield testRealRefresh(HEADER_PAGE, 500);
+});
diff --git a/browser/base/content/test/general/browser_registerProtocolHandler_notification.html b/browser/base/content/test/general/browser_registerProtocolHandler_notification.html
new file mode 100644
index 000000000..241b03b95
--- /dev/null
+++ b/browser/base/content/test/general/browser_registerProtocolHandler_notification.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Protocol registrar page</title>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/browser_registerProtocolHandler_notification.js b/browser/base/content/test/general/browser_registerProtocolHandler_notification.js
new file mode 100644
index 000000000..b30ece0f6
--- /dev/null
+++ b/browser/base/content/test/general/browser_registerProtocolHandler_notification.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/. */
+
+function test() {
+ waitForExplicitFinish();
+ let notificationValue = "Protocol Registration: testprotocol";
+ let testURI = "http://example.com/browser/" +
+ "browser/base/content/test/general/browser_registerProtocolHandler_notification.html";
+
+ waitForCondition(function() {
+ // Do not start until the notification is up
+ let notificationBox = window.gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue(notificationValue);
+ return notification;
+ },
+ function() {
+
+ let notificationBox = window.gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue(notificationValue);
+ ok(notification, "Notification box should be displayed");
+ if (notification == null) {
+ finish();
+ return;
+ }
+ is(notification.type, "info", "We expect this notification to have the type of 'info'.");
+ isnot(notification.image, null, "We expect this notification to have an icon.");
+
+ let buttons = notification.getElementsByClassName("notification-button-default");
+ is(buttons.length, 1, "We expect see one default button.");
+
+ buttons = notification.getElementsByClassName("notification-button");
+ is(buttons.length, 1, "We expect see one button.");
+
+ let button = buttons[0];
+ isnot(button.label, null, "We expect the add button to have a label.");
+ todo_isnot(button.accesskey, null, "We expect the add button to have a accesskey.");
+
+ finish();
+ }, "Still can not get notification after retry 100 times.", 100);
+
+ window.gBrowser.selectedBrowser.loadURI(testURI);
+}
diff --git a/browser/base/content/test/general/browser_relatedTabs.js b/browser/base/content/test/general/browser_relatedTabs.js
new file mode 100644
index 000000000..97cf51d84
--- /dev/null
+++ b/browser/base/content/test/general/browser_relatedTabs.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/. */
+
+add_task(function*() {
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add several new tabs in sequence, interrupted by selecting a
+ // different tab, moving a tab around and closing a tab,
+ // returning a list of opened tabs for verifying the expected order.
+ // The new tab behaviour is documented in bug 465673
+ let tabs = [];
+ function addTab(aURL, aReferrer) {
+ let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer});
+ tabs.push(tab);
+ return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ }
+
+ yield addTab("http://mochi.test:8888/#0");
+ gBrowser.selectedTab = tabs[0];
+ yield addTab("http://mochi.test:8888/#1");
+ yield addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
+ yield addTab("http://mochi.test:8888/#3", gBrowser.currentURI);
+ gBrowser.selectedTab = tabs[tabs.length - 1];
+ gBrowser.selectedTab = tabs[0];
+ yield addTab("http://mochi.test:8888/#4", gBrowser.currentURI);
+ gBrowser.selectedTab = tabs[3];
+ yield addTab("http://mochi.test:8888/#5", gBrowser.currentURI);
+ gBrowser.removeTab(tabs.pop());
+ yield addTab("about:blank", gBrowser.currentURI);
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+ yield addTab("http://mochi.test:8888/#6", gBrowser.currentURI);
+ yield addTab();
+ yield addTab("http://mochi.test:8888/#7");
+
+ function testPosition(tabNum, expectedPosition, msg) {
+ is(Array.indexOf(gBrowser.tabs, tabs[tabNum]), expectedPosition, msg);
+ }
+
+ testPosition(0, 3, "tab without referrer was opened to the far right");
+ testPosition(1, 7, "tab without referrer was opened to the far right");
+ testPosition(2, 5, "tab with referrer opened immediately to the right");
+ testPosition(3, 1, "next tab with referrer opened further to the right");
+ testPosition(4, 4, "tab selection changed, tab opens immediately to the right");
+ testPosition(5, 6, "blank tab with referrer opens to the right of 3rd original tab where removed tab was");
+ testPosition(6, 2, "tab has moved, new tab opens immediately to the right");
+ testPosition(7, 8, "blank tab without referrer opens at the end");
+ testPosition(8, 9, "tab without referrer opens at the end");
+
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+});
diff --git a/browser/base/content/test/general/browser_remoteTroubleshoot.js b/browser/base/content/test/general/browser_remoteTroubleshoot.js
new file mode 100644
index 000000000..5c939dbd0
--- /dev/null
+++ b/browser/base/content/test/general/browser_remoteTroubleshoot.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/. */
+
+var {WebChannel} = Cu.import("resource://gre/modules/WebChannel.jsm", {});
+
+const TEST_URL_TAIL = "example.com/browser/browser/base/content/test/general/test_remoteTroubleshoot.html"
+const TEST_URI_GOOD = Services.io.newURI("https://" + TEST_URL_TAIL, null, null);
+const TEST_URI_BAD = Services.io.newURI("http://" + TEST_URL_TAIL, null, null);
+const TEST_URI_GOOD_OBJECT = Services.io.newURI("https://" + TEST_URL_TAIL + "?object", null, null);
+
+// Creates a one-shot web-channel for the test data to be sent back from the test page.
+function promiseChannelResponse(channelID, originOrPermission) {
+ return new Promise((resolve, reject) => {
+ let channel = new WebChannel(channelID, originOrPermission);
+ channel.listen((id, data, target) => {
+ channel.stopListening();
+ resolve(data);
+ });
+ });
+}
+
+// Loads the specified URI in a new tab and waits for it to send us data on our
+// test web-channel and resolves with that data.
+function promiseNewChannelResponse(uri) {
+ let channelPromise = promiseChannelResponse("test-remote-troubleshooting-backchannel",
+ uri);
+ let tab = gBrowser.loadOneTab(uri.spec, { inBackground: false });
+ return promiseTabLoaded(tab).then(
+ () => channelPromise
+ ).then(data => {
+ gBrowser.removeTab(tab);
+ return data;
+ });
+}
+
+add_task(function*() {
+ // We haven't set a permission yet - so even the "good" URI should fail.
+ let got = yield promiseNewChannelResponse(TEST_URI_GOOD);
+ // Should have no data.
+ Assert.ok(got.message === undefined, "should have failed to get any data");
+
+ // Add a permission manager entry for our URI.
+ Services.perms.add(TEST_URI_GOOD,
+ "remote-troubleshooting",
+ Services.perms.ALLOW_ACTION);
+ registerCleanupFunction(() => {
+ Services.perms.remove(TEST_URI_GOOD, "remote-troubleshooting");
+ });
+
+ // Try again - now we are expecting a response with the actual data.
+ got = yield promiseNewChannelResponse(TEST_URI_GOOD);
+
+ // Check some keys we expect to always get.
+ Assert.ok(got.message.extensions, "should have extensions");
+ Assert.ok(got.message.graphics, "should have graphics");
+
+ // Check we have channel and build ID info:
+ Assert.equal(got.message.application.buildID, Services.appinfo.appBuildID,
+ "should have correct build ID");
+
+ let updateChannel = null;
+ try {
+ updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
+ } catch (ex) {}
+ if (!updateChannel) {
+ Assert.ok(!('updateChannel' in got.message.application),
+ "should not have update channel where not available.");
+ } else {
+ Assert.equal(got.message.application.updateChannel, updateChannel,
+ "should have correct update channel.");
+ }
+
+
+ // And check some keys we know we decline to return.
+ Assert.ok(!got.message.modifiedPreferences, "should not have a modifiedPreferences key");
+ Assert.ok(!got.message.crashes, "should not have crash info");
+
+ // Now a http:// URI - should get nothing even with the permission setup.
+ got = yield promiseNewChannelResponse(TEST_URI_BAD);
+ Assert.ok(got.message === undefined, "should have failed to get any data");
+
+ // Check that the page can send an object as well if it's in the whitelist
+ let webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " https://example.com";
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(webchannelWhitelistPref);
+ });
+ got = yield promiseNewChannelResponse(TEST_URI_GOOD_OBJECT);
+ Assert.ok(got.message, "should have gotten some data back");
+});
diff --git a/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.js
new file mode 100644
index 000000000..451323f50
--- /dev/null
+++ b/browser/base/content/test/general/browser_remoteWebNavigation_postdata.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/. */
+
+Cu.import("resource://gre/modules/BrowserUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+function makeInputStream(aString) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.data = aString;
+ return stream; // XPConnect will QI this to nsIInputStream for us.
+}
+
+add_task(function* test_remoteWebNavigation_postdata() {
+ let obj = {};
+ Cu.import("resource://testing-common/httpd.js", obj);
+ Cu.import("resource://services-common/utils.js", obj);
+
+ let server = new obj.HttpServer();
+ server.start(-1);
+
+ let loadDeferred = Promise.defer();
+
+ server.registerPathHandler("/test", (request, response) => {
+ let body = obj.CommonUtils.readBytesFromInputStream(request.bodyInputStream);
+ is(body, "success", "request body is correct");
+ is(request.method, "POST", "request was a post");
+ response.write("Received from POST: " + body);
+ loadDeferred.resolve();
+ });
+
+ let i = server.identity;
+ let path = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort + "/test";
+
+ let postdata =
+ "Content-Length: 7\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "\r\n" +
+ "success";
+
+ openUILinkIn(path, "tab", null, makeInputStream(postdata));
+
+ yield loadDeferred.promise;
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ let serverStoppedDeferred = Promise.defer();
+ server.stop(function() { serverStoppedDeferred.resolve(); });
+ yield serverStoppedDeferred.promise;
+});
diff --git a/browser/base/content/test/general/browser_removeTabsToTheEnd.js b/browser/base/content/test/general/browser_removeTabsToTheEnd.js
new file mode 100644
index 000000000..351085d74
--- /dev/null
+++ b/browser/base/content/test/general/browser_removeTabsToTheEnd.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // Add two new tabs after the original tab. Pin the first one.
+ let originalTab = gBrowser.selectedTab;
+ let newTab1 = gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.pinTab(newTab1);
+
+ // Check that there is only one closable tab from originalTab to the end
+ is(gBrowser.getTabsToTheEndFrom(originalTab).length, 1,
+ "One unpinned tab to the right");
+
+ // Remove tabs to the end
+ gBrowser.removeTabsToTheEndFrom(originalTab);
+ is(gBrowser.tabs.length, 2, "Length is 2");
+ is(gBrowser.tabs[1], originalTab, "Starting tab is not removed");
+ is(gBrowser.tabs[0], newTab1, "Pinned tab is not removed");
+
+ // Remove pinned tab
+ gBrowser.removeTab(newTab1);
+}
diff --git a/browser/base/content/test/general/browser_restore_isAppTab.js b/browser/base/content/test/general/browser_restore_isAppTab.js
new file mode 100644
index 000000000..e20974d80
--- /dev/null
+++ b/browser/base/content/test/general/browser_restore_isAppTab.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+const DUMMY = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+function getMinidumpDirectory() {
+ let dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ dir.append("minidumps");
+ return dir;
+}
+
+// This observer is needed so we can clean up all evidence of the crash so
+// the testrunner thinks things are peachy.
+var CrashObserver = {
+ observe: function(subject, topic, data) {
+ is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+ ok(subject instanceof Ci.nsIPropertyBag2,
+ 'Subject implements nsIPropertyBag2.');
+ // we might see this called as the process terminates due to previous tests.
+ // We are only looking for "abnormal" exits...
+ if (!subject.hasKey("abnormal")) {
+ info("This is a normal termination and isn't the one we are looking for...");
+ return;
+ }
+
+ let dumpID;
+ if ('nsICrashReporter' in Ci) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ ok(dumpID, "dumpID is present and not an empty string");
+ }
+
+ if (dumpID) {
+ let minidumpDirectory = getMinidumpDirectory();
+ let file = minidumpDirectory.clone();
+ file.append(dumpID + '.dmp');
+ file.remove(true);
+ file = minidumpDirectory.clone();
+ file.append(dumpID + '.extra');
+ file.remove(true);
+ }
+ }
+}
+Services.obs.addObserver(CrashObserver, 'ipc:content-shutdown', false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(CrashObserver, 'ipc:content-shutdown');
+});
+
+function frameScript() {
+ addMessageListener("Test:GetIsAppTab", function() {
+ sendAsyncMessage("Test:IsAppTab", { isAppTab: docShell.isAppTab });
+ });
+
+ addMessageListener("Test:Crash", function() {
+ privateNoteIntentionalCrash();
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+ });
+}
+
+function loadFrameScript(browser) {
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+}
+
+function isBrowserAppTab(browser) {
+ return new Promise(resolve => {
+ function listener({ data }) {
+ browser.messageManager.removeMessageListener("Test:IsAppTab", listener);
+ resolve(data.isAppTab);
+ }
+ // It looks like same-process messages may be reordered by the message
+ // manager, so we need to wait one tick before sending the message.
+ executeSoon(function () {
+ browser.messageManager.addMessageListener("Test:IsAppTab", listener);
+ browser.messageManager.sendAsyncMessage("Test:GetIsAppTab");
+ });
+ });
+}
+
+// Restarts the child process by crashing it then reloading the tab
+var restart = Task.async(function*(browser) {
+ // If the tab isn't remote this would crash the main process so skip it
+ if (!browser.isRemoteBrowser)
+ return;
+
+ // Make sure the main process has all of the current tab state before crashing
+ yield TabStateFlusher.flush(browser);
+
+ browser.messageManager.sendAsyncMessage("Test:Crash");
+ yield promiseWaitForEvent(browser, "AboutTabCrashedLoad", false, true);
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ SessionStore.reviveCrashedTab(tab);
+
+ yield promiseTabLoaded(tab);
+});
+
+add_task(function* navigate() {
+ let tab = gBrowser.addTab("about:robots");
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ let isAppTab = yield isBrowserAppTab(browser);
+ ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+ gBrowser.pinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.loadURI(DUMMY);
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.unpinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+ gBrowser.pinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.loadURI("about:robots");
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* crash() {
+ if (!gMultiProcessBrowser || !("nsICrashReporter" in Ci))
+ return;
+
+ let tab = gBrowser.addTab(DUMMY);
+ let browser = tab.linkedBrowser;
+ gBrowser.selectedTab = tab;
+ yield waitForDocLoadComplete();
+ loadFrameScript(browser);
+ let isAppTab = yield isBrowserAppTab(browser);
+ ok(!isAppTab, "Docshell shouldn't think it is an app tab");
+
+ gBrowser.pinTab(tab);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ yield restart(browser);
+ loadFrameScript(browser);
+ isAppTab = yield isBrowserAppTab(browser);
+ ok(isAppTab, "Docshell should think it is an app tab");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
new file mode 100644
index 000000000..4f4f5c398
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
@@ -0,0 +1,39 @@
+// Bug 474792 - Clear "Never remember passwords for this site" when
+// clearing site-specific settings in Clear Recent History dialog
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+add_task(function*() {
+ var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
+
+ // Add a disabled host
+ pwmgr.setLoginSavingEnabled("http://example.com", false);
+ // Sanity check
+ is(pwmgr.getLoginSavingEnabled("http://example.com"), false,
+ "example.com should be disabled for password saving since we haven't cleared that yet.");
+
+ // Set up the sanitizer to just clear siteSettings
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", true);
+
+ // Clear it
+ yield s.sanitize();
+
+ // Make sure it's gone
+ is(pwmgr.getLoginSavingEnabled("http://example.com"), true,
+ "example.com should be enabled for password saving again now that we've cleared.");
+});
diff --git a/browser/base/content/test/general/browser_sanitize-sitepermissions.js b/browser/base/content/test/general/browser_sanitize-sitepermissions.js
new file mode 100644
index 000000000..1b43d62fc
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitize-sitepermissions.js
@@ -0,0 +1,52 @@
+// Bug 380852 - Delete permission manager entries in Clear Recent History
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+function countPermissions() {
+ let result = 0;
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ result++;
+ enumerator.getNext();
+ }
+ return result;
+}
+
+add_task(function* test() {
+ // sanitize before we start so we have a good baseline.
+ // Set up the sanitizer to just clear siteSettings
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", true);
+ s.sanitize();
+
+ // Count how many permissions we start with - some are defaults that
+ // will not be sanitized.
+ let numAtStart = countPermissions();
+
+ // Add a permission entry
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com"), "testing", pm.ALLOW_ACTION);
+
+ // Sanity check
+ ok(pm.enumerator.hasMoreElements(), "Permission manager should have elements, since we just added one");
+
+ // Clear it
+ yield s.sanitize();
+
+ // Make sure it's gone
+ is(numAtStart, countPermissions(), "Permission manager should have the same count it started with");
+});
diff --git a/browser/base/content/test/general/browser_sanitize-timespans.js b/browser/base/content/test/general/browser_sanitize-timespans.js
new file mode 100644
index 000000000..3712c5e1c
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitize-timespans.js
@@ -0,0 +1,733 @@
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+requestLongerTimeout(2);
+
+// Bug 453440 - Test the timespan-based logic of the sanitizer code
+var now_mSec = Date.now();
+var now_uSec = now_mSec * 1000;
+
+const kMsecPerMin = 60 * 1000;
+const kUsecPerMin = 60 * 1000000;
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+var FormHistory = (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
+var Downloads = (Components.utils.import("resource://gre/modules/Downloads.jsm", {})).Downloads;
+
+function promiseFormHistoryRemoved() {
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function onfh() {
+ Services.obs.removeObserver(onfh, "satchel-storage-changed", false);
+ deferred.resolve();
+ }, "satchel-storage-changed", false);
+ return deferred.promise;
+}
+
+function promiseDownloadRemoved(list) {
+ let deferred = Promise.defer();
+
+ let view = {
+ onDownloadRemoved: function(download) {
+ list.removeView(view);
+ deferred.resolve();
+ }
+ };
+
+ list.addView(view);
+
+ return deferred.promise;
+}
+
+add_task(function* test() {
+ yield setupDownloads();
+ yield setupFormHistory();
+ yield setupHistory();
+ yield onHistoryReady();
+});
+
+function countEntries(name, message, check) {
+ let deferred = Promise.defer();
+
+ var obj = {};
+ if (name !== null)
+ obj.fieldname = name;
+
+ let count;
+ FormHistory.count(obj, { handleResult: result => count = result,
+ handleError: function (error) {
+ deferred.reject(error)
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) {
+ if (!reason) {
+ check(count, message);
+ deferred.resolve();
+ }
+ },
+ });
+
+ return deferred.promise;
+}
+
+function* onHistoryReady() {
+ var hoursSinceMidnight = new Date().getHours();
+ var minutesSinceMidnight = hoursSinceMidnight * 60 + new Date().getMinutes();
+
+ // Should test cookies here, but nsICookieManager/nsICookieService
+ // doesn't let us fake creation times. bug 463127
+
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", true);
+ itemPrefs.setBoolPref("downloads", true);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", true);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", false);
+
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ let downloadPromise = promiseDownloadRemoved(publicList);
+ let formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 10 minutes ago
+ s.range = [now_uSec - 10*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://10minutes.com"))),
+ "Pretend visit to 10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+ "Pretend visit to 1hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 10) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ let checkZero = function(num, message) { is(num, 0, message); }
+ let checkOne = function(num, message) { is(num, 1, message); }
+
+ yield countEntries("10minutes", "10minutes form entry should be deleted", checkZero);
+ yield countEntries("1hour", "1hour form entry should still exist", checkOne);
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 10)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-10-minutes")), "10 minute download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+
+ if (minutesSinceMidnight > 10)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 1 hour
+ Sanitizer.prefs.setIntPref("timeSpan", 1);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+ "Pretend visit to 1hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 1) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("1hour", "1hour form entry should be deleted", checkZero);
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 1)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-1-hour")), "<1 hour download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+
+ if (hoursSinceMidnight > 1)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 1 hour 10 minutes
+ s.range = [now_uSec - 70*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 70) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should be deleted", checkZero);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 70)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "1 hour 10 minute old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ if (minutesSinceMidnight > 70)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 2 hours
+ Sanitizer.prefs.setIntPref("timeSpan", 2);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 2) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("2hour", "2hour form entry should be deleted", checkZero);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 2)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-2-hour")), "<2 hour old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ if (hoursSinceMidnight > 2)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 2 hours 10 minutes
+ s.range = [now_uSec - 130*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 130) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should be deleted", checkZero);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 130)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "2 hour 10 minute old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ if (minutesSinceMidnight > 130)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 4 hours
+ Sanitizer.prefs.setIntPref("timeSpan", 3);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 4) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("4hour", "4hour form entry should be deleted", checkZero);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 4)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-4-hour")), "<4 hour old download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should still be present");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ if (hoursSinceMidnight > 4)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 4 hours 10 minutes
+ s.range = [now_uSec - 250*60*1000000, now_uSec];
+ yield s.sanitize();
+ s.range = null;
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should now be deleted");
+ if (minutesSinceMidnight > 250) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should be deleted", checkZero);
+ if (minutesSinceMidnight > 250)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!(yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "4 hour 10 minute download should now be deleted");
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+ if (minutesSinceMidnight > 250)
+ ok((yield downloadExists(publicList, "fakefile-today")), "'Today' download should still be present");
+
+ // The 'Today' download might have been already deleted, in which case we
+ // should not wait for a download removal notification.
+ if (minutesSinceMidnight > 250) {
+ downloadPromise = promiseDownloadRemoved(publicList);
+ } else {
+ downloadPromise = Promise.resolve();
+ }
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear Today
+ Sanitizer.prefs.setIntPref("timeSpan", 4);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ // Be careful. If we add our objectss just before midnight, and sanitize
+ // runs immediately after, they won't be expired. This is expected, but
+ // we should not test in that case. We cannot just test for opposite
+ // condition because we could cross midnight just one moment after we
+ // cache our time, then we would have an even worse random failure.
+ var today = isToday(new Date(now_mSec));
+ if (today) {
+ ok(!(yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should now be deleted");
+
+ yield countEntries("today", "today form entry should be deleted", checkZero);
+ ok(!(yield downloadExists(publicList, "fakefile-today")), "'Today' download should now be deleted");
+ }
+
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+ ok((yield downloadExists(publicList, "fakefile-old")), "Year old download should still be present");
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Choose everything
+ Sanitizer.prefs.setIntPref("timeSpan", 0);
+ yield s.sanitize();
+
+ yield formHistoryPromise;
+ yield downloadPromise;
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should now be deleted");
+
+ yield countEntries("b4today", "b4today form entry should be deleted", checkZero);
+
+ ok(!(yield downloadExists(publicList, "fakefile-old")), "Year old download should now be deleted");
+}
+
+function setupHistory() {
+ let deferred = Promise.defer();
+
+ let places = [];
+
+ function addPlace(aURI, aTitle, aVisitDate) {
+ places.push({
+ uri: aURI,
+ title: aTitle,
+ visits: [{
+ visitDate: aVisitDate,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ }]
+ });
+ }
+
+ addPlace(makeURI("http://10minutes.com/"), "10 minutes ago", now_uSec - 10 * kUsecPerMin);
+ addPlace(makeURI("http://1hour.com/"), "Less than 1 hour ago", now_uSec - 45 * kUsecPerMin);
+ addPlace(makeURI("http://1hour10minutes.com/"), "1 hour 10 minutes ago", now_uSec - 70 * kUsecPerMin);
+ addPlace(makeURI("http://2hour.com/"), "Less than 2 hours ago", now_uSec - 90 * kUsecPerMin);
+ addPlace(makeURI("http://2hour10minutes.com/"), "2 hours 10 minutes ago", now_uSec - 130 * kUsecPerMin);
+ addPlace(makeURI("http://4hour.com/"), "Less than 4 hours ago", now_uSec - 180 * kUsecPerMin);
+ addPlace(makeURI("http://4hour10minutes.com/"), "4 hours 10 minutesago", now_uSec - 250 * kUsecPerMin);
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+ addPlace(makeURI("http://today.com/"), "Today", today.getTime() * 1000);
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ addPlace(makeURI("http://before-today.com/"), "Before Today", lastYear.getTime() * 1000);
+ PlacesUtils.asyncHistory.updatePlaces(places, {
+ handleError: () => ok(false, "Unexpected error in adding visit."),
+ handleResult: () => { },
+ handleCompletion: () => deferred.resolve()
+ });
+
+ return deferred.promise;
+}
+
+function* setupFormHistory() {
+
+ function searchEntries(terms, params) {
+ let deferred = Promise.defer();
+
+ let results = [];
+ FormHistory.search(terms, params, { handleResult: result => results.push(result),
+ handleError: function (error) {
+ deferred.reject(error);
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) { deferred.resolve(results); }
+ });
+ return deferred.promise;
+ }
+
+ function update(changes)
+ {
+ let deferred = Promise.defer();
+ FormHistory.update(changes, { handleError: function (error) {
+ deferred.reject(error);
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) { deferred.resolve(); }
+ });
+ return deferred.promise;
+ }
+
+ // Make sure we've got a clean DB to start with, then add the entries we'll be testing.
+ yield update(
+ [{
+ op: "remove"
+ },
+ {
+ op : "add",
+ fieldname : "10minutes",
+ value : "10m"
+ }, {
+ op : "add",
+ fieldname : "1hour",
+ value : "1h"
+ }, {
+ op : "add",
+ fieldname : "1hour10minutes",
+ value : "1h10m"
+ }, {
+ op : "add",
+ fieldname : "2hour",
+ value : "2h"
+ }, {
+ op : "add",
+ fieldname : "2hour10minutes",
+ value : "2h10m"
+ }, {
+ op : "add",
+ fieldname : "4hour",
+ value : "4h"
+ }, {
+ op : "add",
+ fieldname : "4hour10minutes",
+ value : "4h10m"
+ }, {
+ op : "add",
+ fieldname : "today",
+ value : "1d"
+ }, {
+ op : "add",
+ fieldname : "b4today",
+ value : "1y"
+ }]);
+
+ // Artifically age the entries to the proper vintage.
+ let timestamp = now_uSec - 10 * kUsecPerMin;
+ let results = yield searchEntries(["guid"], { fieldname: "10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 45 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "1hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 70 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "1hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 90 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "2hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 130 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "2hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 180 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "4hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 250 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "4hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+ timestamp = today.getTime() * 1000;
+ results = yield searchEntries(["guid"], { fieldname: "today" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ timestamp = lastYear.getTime() * 1000;
+ results = yield searchEntries(["guid"], { fieldname: "b4today" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ var checks = 0;
+ let checkOne = function(num, message) { is(num, 1, message); checks++; }
+
+ // Sanity check.
+ yield countEntries("10minutes", "Checking for 10minutes form history entry creation", checkOne);
+ yield countEntries("1hour", "Checking for 1hour form history entry creation", checkOne);
+ yield countEntries("1hour10minutes", "Checking for 1hour10minutes form history entry creation", checkOne);
+ yield countEntries("2hour", "Checking for 2hour form history entry creation", checkOne);
+ yield countEntries("2hour10minutes", "Checking for 2hour10minutes form history entry creation", checkOne);
+ yield countEntries("4hour", "Checking for 4hour form history entry creation", checkOne);
+ yield countEntries("4hour10minutes", "Checking for 4hour10minutes form history entry creation", checkOne);
+ yield countEntries("today", "Checking for today form history entry creation", checkOne);
+ yield countEntries("b4today", "Checking for b4today form history entry creation", checkOne);
+ is(checks, 9, "9 checks made");
+}
+
+function* setupDownloads() {
+
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+
+ let download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 10 * kMsecPerMin), // 10 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-1-hour"
+ });
+ download.startTime = new Date(now_mSec - 45 * kMsecPerMin), // 45 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-1-hour-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 70 * kMsecPerMin), // 70 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-2-hour"
+ });
+ download.startTime = new Date(now_mSec - 90 * kMsecPerMin), // 90 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-2-hour-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 130 * kMsecPerMin), // 130 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-4-hour"
+ });
+ download.startTime = new Date(now_mSec - 180 * kMsecPerMin), // 180 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-4-hour-10-minutes"
+ });
+ download.startTime = new Date(now_mSec - 250 * kMsecPerMin), // 250 minutes ago
+ download.canceled = true;
+ yield publicList.add(download);
+
+ // Add "today" download
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-today"
+ });
+ download.startTime = today, // 12:00:01 AM this morning
+ download.canceled = true;
+ yield publicList.add(download);
+
+ // Add "before today" download
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+
+ download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-old"
+ });
+ download.startTime = lastYear,
+ download.canceled = true;
+ yield publicList.add(download);
+
+ // Confirm everything worked
+ let downloads = yield publicList.getAll();
+ is(downloads.length, 9, "9 Pretend downloads added");
+
+ ok((yield downloadExists(publicList, "fakefile-old")), "Pretend download for everything case should exist");
+ ok((yield downloadExists(publicList, "fakefile-10-minutes")), "Pretend download for 10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-1-hour")), "Pretend download for 1-hour case should exist");
+ ok((yield downloadExists(publicList, "fakefile-1-hour-10-minutes")), "Pretend download for 1-hour-10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-2-hour")), "Pretend download for 2-hour case should exist");
+ ok((yield downloadExists(publicList, "fakefile-2-hour-10-minutes")), "Pretend download for 2-hour-10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-4-hour")), "Pretend download for 4-hour case should exist");
+ ok((yield downloadExists(publicList, "fakefile-4-hour-10-minutes")), "Pretend download for 4-hour-10-minutes case should exist");
+ ok((yield downloadExists(publicList, "fakefile-today")), "Pretend download for Today case should exist");
+}
+
+/**
+ * Checks to see if the downloads with the specified id exists.
+ *
+ * @param aID
+ * The ids of the downloads to check.
+ */
+let downloadExists = Task.async(function* (list, path) {
+ let listArray = yield list.getAll();
+ return listArray.some(i => i.target.path == path);
+});
+
+function isToday(aDate) {
+ return aDate.getDate() == new Date().getDate();
+}
diff --git a/browser/base/content/test/general/browser_sanitizeDialog.js b/browser/base/content/test/general/browser_sanitizeDialog.js
new file mode 100644
index 000000000..50546be45
--- /dev/null
+++ b/browser/base/content/test/general/browser_sanitizeDialog.js
@@ -0,0 +1,1027 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
+ * See bug 480169.
+ *
+ * The purpose of this test is not to fully flex the sanitize timespan code;
+ * browser/base/content/test/general/browser_sanitize-timespans.js does that. This
+ * test checks the UI of the dialog and makes sure it's correctly connected to
+ * the sanitize timespan code.
+ *
+ * Some of this code, especially the history creation parts, was taken from
+ * browser/base/content/test/general/browser_sanitize-timespans.js.
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Timer",
+ "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var tempScope = {};
+Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+const kMsecPerMin = 60 * 1000;
+const kUsecPerMin = 60 * 1000000;
+
+add_task(function* init() {
+ requestLongerTimeout(3);
+ yield blankSlate();
+ registerCleanupFunction(function* () {
+ yield blankSlate();
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ });
+});
+
+/**
+ * Initializes the dialog to its default state.
+ */
+add_task(function* default_state() {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Select "Last Hour"
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ // Hide details
+ if (!this.getItemList().collapsed)
+ this.toggleDetails();
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Cancels the dialog, makes sure history not cleared.
+ */
+add_task(function* test_cancel() {
+ // Add history (within the past hour)
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+ yield PlacesTestUtils.addVisits(places);
+
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", false);
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.cancelDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseHistoryClearedState(uris, false);
+ yield blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the combined history-downloads checkbox clears both history
+ * visits and downloads when checked; the dialog respects simple timespan.
+ */
+add_task(function* test_history_downloads_checked() {
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ yield addDownloadWithMinutesAgo(downloadIDs, i);
+ }
+ // Add downloads (over an hour ago).
+ let olderDownloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
+ }
+
+ // Add history (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+ // Add history (over an hour ago).
+ let olderURIs = [];
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)});
+ olderURIs.push(pURI);
+ }
+ let promiseSanitized = promiseSanitizationComplete();
+
+ yield PlacesTestUtils.addVisits(places);
+
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected");
+ boolPrefIs("cpd.history", true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked");
+ boolPrefIs("cpd.downloads", true,
+ "downloads pref should be true after accepting dialog with " +
+ "history checkbox checked");
+
+ yield promiseSanitized;
+
+ // History visits and downloads within one hour should be cleared.
+ yield promiseHistoryClearedState(uris, true);
+ yield ensureDownloadsClearedState(downloadIDs, true);
+
+ // Visits and downloads > 1 hour should still exist.
+ yield promiseHistoryClearedState(olderURIs, false);
+ yield ensureDownloadsClearedState(olderDownloadIDs, false);
+
+ // OK, done, cleanup after ourselves.
+ yield blankSlate();
+ yield promiseHistoryClearedState(olderURIs, true);
+ yield ensureDownloadsClearedState(olderDownloadIDs, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the combined history-downloads checkbox removes neither
+ * history visits nor downloads when not checked.
+ */
+add_task(function* test_history_downloads_unchecked() {
+ // Add form entries
+ let formEntries = [];
+
+ for (let i = 0; i < 5; i++) {
+ formEntries.push((yield promiseAddFormEntryWithMinutesAgo(i)));
+ }
+
+
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ yield addDownloadWithMinutesAgo(downloadIDs, i);
+ }
+
+ // Add history, downloads, form entries (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+
+ yield PlacesTestUtils.addVisits(places);
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan");
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+
+ // Remove only form entries, leave history (including downloads).
+ this.checkPrefCheckbox("history", false);
+ this.checkPrefCheckbox("formdata", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected");
+ boolPrefIs("cpd.history", false,
+ "history pref should be false after accepting dialog with " +
+ "history checkbox unchecked");
+ boolPrefIs("cpd.downloads", false,
+ "downloads pref should be false after accepting dialog with " +
+ "history checkbox unchecked");
+
+ // Of the three only form entries should be cleared.
+ yield promiseHistoryClearedState(uris, false);
+ yield ensureDownloadsClearedState(downloadIDs, false);
+
+ for (let entry of formEntries) {
+ let exists = yield formNameExists(entry);
+ is(exists, false, "form entry " + entry + " should no longer exist");
+ }
+
+ // OK, done, cleanup after ourselves.
+ yield blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ yield ensureDownloadsClearedState(downloadIDs, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the "Everything" duration option works.
+ */
+add_task(function* test_everything() {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+ uris.push(pURI);
+ });
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ yield PlacesTestUtils.addVisits(places);
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan");
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected");
+
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * Ensures that the "Everything" warning is visible on dialog open after
+ * the previous test.
+ */
+add_task(function* test_everything_warning() {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+ uris.push(pURI);
+ });
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ yield PlacesTestUtils.addVisits(places);
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), true,
+ "Warning panel should be visible after previously accepting dialog " +
+ "with clearing everything");
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected");
+
+ yield promiseSanitized;
+
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+/**
+ * The next three tests checks that when a certain history item cannot be
+ * cleared then the checkbox should be both disabled and unchecked.
+ * In addition, we ensure that this behavior does not modify the preferences.
+ */
+add_task(function* test_cannot_clear_history() {
+ // Add form entries
+ let formEntries = [ (yield promiseAddFormEntryWithMinutesAgo(10)) ];
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ // Add history.
+ let pURI = makeURI("http://" + 10 + "-minutes-ago.com/");
+ yield PlacesTestUtils.addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)});
+ let uris = [ pURI ];
+
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ // Check that the relevant checkboxes are enabled
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.formdata']");
+ ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " +
+ "clear formdata should be enabled.");
+
+ cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.history']");
+ ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
+ "clear history should be enabled.");
+
+ this.checkAllCheckboxes();
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+
+ yield promiseHistoryClearedState(uris, true);
+
+ let exists = yield formNameExists(formEntries[0]);
+ is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+add_task(function* test_no_formdata_history_to_clear() {
+ let promiseSanitized = promiseSanitizationComplete();
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ boolPrefIs("cpd.history", true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked");
+ boolPrefIs("cpd.formdata", true,
+ "formdata pref should be true after accepting dialog with " +
+ "formdata checkbox checked");
+
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.history']");
+ ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
+ "There is no history, but history checkbox should always be enabled " +
+ "and will be checked from previous preference.");
+
+ this.acceptDialog();
+ }
+ wh.open();
+ yield wh.promiseClosed;
+ yield promiseSanitized;
+});
+
+add_task(function* test_form_entries() {
+ let formEntry = (yield promiseAddFormEntryWithMinutesAgo(10));
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ boolPrefIs("cpd.formdata", true,
+ "formdata pref should persist previous value after accepting " +
+ "dialog where you could not clear formdata.");
+
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.formdata']");
+
+ info("There exists formEntries so the checkbox should be in sync with the pref.");
+ is(cb.length, 1, "There is only one checkbox for form data");
+ ok(!cb[0].disabled, "The checkbox is enabled");
+ ok(cb[0].checked, "The checkbox is checked");
+
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+ let exists = yield formNameExists(formEntry);
+ is(exists, false, "form entry " + formEntry + " should no longer exist");
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+
+/**
+ * Ensure that toggling details persists
+ * across dialog openings.
+ */
+add_task(function* test_toggling_details_persists() {
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Check all items and select "Everything"
+ this.checkAllCheckboxes();
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should remain closed because all items are checked.
+ this.checkDetails(false);
+
+ // Uncheck history.
+ this.checkPrefCheckbox("history", false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Modify the Site Preferences item state (bug 527820)
+ this.checkAllCheckboxes();
+ this.checkPrefCheckbox("siteSettings", false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.cancelDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Select another duration
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.acceptDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should not be open because "Last Hour" is selected
+ this.checkDetails(false);
+
+ this.cancelDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+ {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should have remained closed
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+ this.cancelDialog();
+ };
+ wh.open();
+ yield wh.promiseClosed;
+ }
+});
+
+// Test for offline cache deletion
+add_task(function* test_offline_cache() {
+ // Prepare stuff, we will work with www.example.com
+ var URL = "http://www.example.com";
+ var URI = makeURI(URL);
+ var principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI);
+
+ // Give www.example.com privileges to store offline data
+ Services.perms.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+ Services.perms.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+
+ // Store something to the offline cache
+ var appcacheserv = Cc["@mozilla.org/network/application-cache-service;1"]
+ .getService(Ci.nsIApplicationCacheService);
+ var appcachegroupid = appcacheserv.buildGroupIDForInfo(makeURI(URL + "/manifest"), LoadContextInfo.default);
+ var appcache = appcacheserv.createApplicationCache(appcachegroupid);
+ var storage = Services.cache2.appCacheStorage(LoadContextInfo.default, appcache);
+
+ // Open the dialog
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ // Show details
+ this.toggleDetails();
+ // Clear only offlineApps
+ this.uncheckAllCheckboxes();
+ this.checkPrefCheckbox("offlineApps", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function () {
+ // Check if the cache has been deleted
+ var size = -1;
+ var visitor = {
+ onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory)
+ {
+ size = aConsumption;
+ }
+ };
+ storage.asyncVisitStorage(visitor, false);
+ // Offline cache visit happens synchronously, since it's forwarded to the old code
+ is(size, 0, "offline application cache entries evicted");
+ };
+
+ var cacheListener = {
+ onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
+ onCacheEntryAvailable: function (entry, isnew, unused, status) {
+ is(status, Cr.NS_OK);
+ var stream = entry.openOutputStream(0);
+ var content = "content";
+ stream.write(content, content.length);
+ stream.close();
+ entry.close();
+ wh.open();
+ }
+ };
+
+ storage.asyncOpenURI(makeURI(URL), "", Ci.nsICacheStorage.OPEN_TRUNCATE, cacheListener);
+ yield wh.promiseClosed;
+});
+
+// Test for offline apps permission deletion
+add_task(function* test_offline_apps_permissions() {
+ // Prepare stuff, we will work with www.example.com
+ var URL = "http://www.example.com";
+ var URI = makeURI(URL);
+ var principal = Services.scriptSecurityManager.createCodebasePrincipal(URI, {});
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ // Open the dialog
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ // Show details
+ this.toggleDetails();
+ // Clear only offlineApps
+ this.uncheckAllCheckboxes();
+ this.checkPrefCheckbox("siteSettings", true);
+ this.acceptDialog();
+ };
+ wh.onunload = function* () {
+ yield promiseSanitized;
+
+ // Check all has been deleted (privileges, data, cache)
+ is(Services.perms.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed");
+ };
+ wh.open();
+ yield wh.promiseClosed;
+});
+
+var now_mSec = Date.now();
+var now_uSec = now_mSec * 1000;
+
+/**
+ * This wraps the dialog and provides some convenience methods for interacting
+ * with it.
+ *
+ * @param aWin
+ * The dialog's nsIDOMWindow
+ */
+function WindowHelper(aWin) {
+ this.win = aWin;
+ this.promiseClosed = new Promise(resolve => { this._resolveClosed = resolve });
+}
+
+WindowHelper.prototype = {
+ /**
+ * "Presses" the dialog's OK button.
+ */
+ acceptDialog: function () {
+ is(this.win.document.documentElement.getButton("accept").disabled, false,
+ "Dialog's OK button should not be disabled");
+ this.win.document.documentElement.acceptDialog();
+ },
+
+ /**
+ * "Presses" the dialog's Cancel button.
+ */
+ cancelDialog: function () {
+ this.win.document.documentElement.cancelDialog();
+ },
+
+ /**
+ * Ensures that the details progressive disclosure button and the item list
+ * hidden by it match up. Also makes sure the height of the dialog is
+ * sufficient for the item list and warning panel.
+ *
+ * @param aShouldBeShown
+ * True if you expect the details to be shown and false if hidden
+ */
+ checkDetails: function (aShouldBeShown) {
+ let button = this.getDetailsButton();
+ let list = this.getItemList();
+ let hidden = list.hidden || list.collapsed;
+ is(hidden, !aShouldBeShown,
+ "Details should be " + (aShouldBeShown ? "shown" : "hidden") +
+ " but were actually " + (hidden ? "hidden" : "shown"));
+ let dir = hidden ? "down" : "up";
+ is(button.className, "expander-" + dir,
+ "Details button should be " + dir + " because item list is " +
+ (hidden ? "" : "not ") + "hidden");
+ let height = 0;
+ if (!hidden) {
+ ok(list.boxObject.height > 30, "listbox has sufficient size")
+ height += list.boxObject.height;
+ }
+ if (this.isWarningPanelVisible())
+ height += this.getWarningPanel().boxObject.height;
+ ok(height < this.win.innerHeight,
+ "Window should be tall enough to fit warning panel and item list");
+ },
+
+ /**
+ * (Un)checks a history scope checkbox (browser & download history,
+ * form history, etc.).
+ *
+ * @param aPrefName
+ * The final portion of the checkbox's privacy.cpd.* preference name
+ * @param aCheckState
+ * True if the checkbox should be checked, false otherwise
+ */
+ checkPrefCheckbox: function (aPrefName, aCheckState) {
+ var pref = "privacy.cpd." + aPrefName;
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='" + pref + "']");
+ is(cb.length, 1, "found checkbox for " + pref + " preference");
+ if (cb[0].checked != aCheckState)
+ cb[0].click();
+ },
+
+ /**
+ * Makes sure all the checkboxes are checked.
+ */
+ _checkAllCheckboxesCustom: function (check) {
+ var cb = this.win.document.querySelectorAll("#itemList > [preference]");
+ ok(cb.length > 1, "found checkboxes for preferences");
+ for (var i = 0; i < cb.length; ++i) {
+ var pref = this.win.document.getElementById(cb[i].getAttribute("preference"));
+ if (!!pref.value ^ check)
+ cb[i].click();
+ }
+ },
+
+ checkAllCheckboxes: function () {
+ this._checkAllCheckboxesCustom(true);
+ },
+
+ uncheckAllCheckboxes: function () {
+ this._checkAllCheckboxesCustom(false);
+ },
+
+ /**
+ * @return The details progressive disclosure button
+ */
+ getDetailsButton: function () {
+ return this.win.document.getElementById("detailsExpander");
+ },
+
+ /**
+ * @return The dialog's duration dropdown
+ */
+ getDurationDropdown: function () {
+ return this.win.document.getElementById("sanitizeDurationChoice");
+ },
+
+ /**
+ * @return The item list hidden by the details progressive disclosure button
+ */
+ getItemList: function () {
+ return this.win.document.getElementById("itemList");
+ },
+
+ /**
+ * @return The clear-everything warning box
+ */
+ getWarningPanel: function () {
+ return this.win.document.getElementById("sanitizeEverythingWarningBox");
+ },
+
+ /**
+ * @return True if the "Everything" warning panel is visible (as opposed to
+ * the tree)
+ */
+ isWarningPanelVisible: function () {
+ return !this.getWarningPanel().hidden;
+ },
+
+ /**
+ * Opens the clear recent history dialog. Before calling this, set
+ * this.onload to a function to execute onload. It should close the dialog
+ * when done so that the tests may continue. Set this.onunload to a function
+ * to execute onunload. this.onunload is optional. If it returns true, the
+ * caller is expected to call waitForAsyncUpdates at some point; if false is
+ * returned, waitForAsyncUpdates is called automatically.
+ */
+ open: function () {
+ let wh = this;
+
+ function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened")
+ return;
+
+ Services.ww.unregisterNotification(windowObserver);
+
+ var loaded = false;
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+
+ win.addEventListener("load", function onload(event) {
+ win.removeEventListener("load", onload, false);
+
+ if (win.name !== "SanitizeDialog")
+ return;
+
+ wh.win = win;
+ loaded = true;
+ executeSoon(() => wh.onload());
+ }, false);
+
+ win.addEventListener("unload", function onunload(event) {
+ if (win.name !== "SanitizeDialog") {
+ win.removeEventListener("unload", onunload, false);
+ return;
+ }
+
+ // Why is unload fired before load?
+ if (!loaded)
+ return;
+
+ win.removeEventListener("unload", onunload, false);
+ wh.win = win;
+
+ // Some exceptions that reach here don't reach the test harness, but
+ // ok()/is() do...
+ Task.spawn(function* () {
+ if (wh.onunload) {
+ yield wh.onunload();
+ }
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ wh._resolveClosed();
+ });
+ }, false);
+ }
+ Services.ww.registerNotification(windowObserver);
+ Services.ww.openWindow(null,
+ "chrome://browser/content/sanitize.xul",
+ "SanitizeDialog",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+ },
+
+ /**
+ * Selects a duration in the duration dropdown.
+ *
+ * @param aDurVal
+ * One of the Sanitizer.TIMESPAN_* values
+ */
+ selectDuration: function (aDurVal) {
+ this.getDurationDropdown().value = aDurVal;
+ if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ is(this.isWarningPanelVisible(), true,
+ "Warning panel should be visible for TIMESPAN_EVERYTHING");
+ }
+ else {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
+ }
+ },
+
+ /**
+ * Toggles the details progressive disclosure button.
+ */
+ toggleDetails: function () {
+ this.getDetailsButton().click();
+ }
+};
+
+function promiseSanitizationComplete() {
+ return promiseTopicObserved("sanitizer-sanitization-complete");
+}
+
+/**
+ * Adds a download to history.
+ *
+ * @param aMinutesAgo
+ * The download will be downloaded this many minutes ago
+ */
+function* addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+
+ let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
+ let download = yield Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: name
+ });
+ download.startTime = new Date(now_mSec - (aMinutesAgo * kMsecPerMin));
+ download.canceled = true;
+ publicList.add(download);
+
+ ok((yield downloadExists(name)),
+ "Sanity check: download " + name +
+ " should exist after creating it");
+
+ aExpectedPathList.push(name);
+}
+
+/**
+ * Adds a form entry to history.
+ *
+ * @param aMinutesAgo
+ * The entry will be added this many minutes ago
+ */
+function promiseAddFormEntryWithMinutesAgo(aMinutesAgo) {
+ let name = aMinutesAgo + "-minutes-ago";
+
+ // Artifically age the entry to the proper vintage.
+ let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin);
+
+ return new Promise((resolve, reject) =>
+ FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp },
+ { handleError: function (error) {
+ reject();
+ throw new Error("Error occurred updating form history: " + error);
+ },
+ handleCompletion: function (reason) {
+ resolve(name);
+ }
+ })
+ )
+}
+
+/**
+ * Checks if a form entry exists.
+ */
+function formNameExists(name)
+{
+ return new Promise((resolve, reject) => {
+ let count = 0;
+ FormHistory.count({ fieldname: name },
+ { handleResult: result => count = result,
+ handleError: function (error) {
+ reject(error);
+ throw new Error("Error occurred searching form history: " + error);
+ },
+ handleCompletion: function (reason) {
+ if (!reason) {
+ resolve(count);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Removes all history visits, downloads, and form entries.
+ */
+function* blankSlate() {
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ let downloads = yield publicList.getAll();
+ for (let download of downloads) {
+ yield publicList.remove(download);
+ yield download.finalize(true);
+ }
+
+ yield new Promise((resolve, reject) => {
+ FormHistory.update({op: "remove"}, {
+ handleCompletion(reason) {
+ if (!reason) {
+ resolve();
+ }
+ },
+ handleError(error) {
+ reject(error);
+ throw new Error("Error occurred updating form history: " + error);
+ }
+ });
+ });
+
+ yield PlacesTestUtils.clearHistory();
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(gPrefService.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Checks to see if the download with the specified path exists.
+ *
+ * @param aPath
+ * The path of the download to check
+ * @return True if the download exists, false otherwise
+ */
+function* downloadExists(aPath) {
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ let listArray = yield publicList.getAll();
+ return listArray.some(i => i.target.path == aPath);
+}
+
+/**
+ * Ensures that the specified downloads are either cleared or not.
+ *
+ * @param aDownloadIDs
+ * Array of download database IDs
+ * @param aShouldBeCleared
+ * True if each download should be cleared, false otherwise
+ */
+function* ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ for (let id of aDownloadIDs) {
+ is((yield downloadExists(id)), !aShouldBeCleared,
+ "download " + id + " should " + niceStr + " exist");
+ }
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function intPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Creates a visit time.
+ *
+ * @param aMinutesAgo
+ * The visit will be visited this many minutes ago
+ */
+function visitTimeForMinutesAgo(aMinutesAgo) {
+ return now_uSec - aMinutesAgo * kUsecPerMin;
+}
diff --git a/browser/base/content/test/general/browser_save_link-perwindowpb.js b/browser/base/content/test/general/browser_save_link-perwindowpb.js
new file mode 100644
index 000000000..5c99ba32a
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link-perwindowpb.js
@@ -0,0 +1,199 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+// Trigger a save of a link in public mode, then trigger an identical save
+// in private mode and ensure that the second request is differentiated from
+// the first by checking that cookies set by the first response are not sent
+// during the second request.
+function triggerSave(aWindow, aCallback) {
+ info("started triggerSave");
+ var fileName;
+ let testBrowser = aWindow.gBrowser.selectedBrowser;
+ // This page sets a cookie if and only if a cookie does not exist yet
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517-2.html";
+ testBrowser.loadURI(testURI);
+ BrowserTestUtils.browserLoaded(testBrowser, false, testURI)
+ .then(() => {
+ waitForFocus(function () {
+ info("register to handle popupshown");
+ aWindow.document.addEventListener("popupshown", contextMenuOpened, false);
+
+ BrowserTestUtils.synthesizeMouseAtCenter("#fff", {type: "contextmenu", button: 2}, testBrowser);
+ info("right clicked!");
+ }, aWindow);
+ });
+
+ function contextMenuOpened(event) {
+ info("contextMenuOpened");
+ aWindow.document.removeEventListener("popupshown", contextMenuOpened);
+
+ // Create the folder the link will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ info("showCallback");
+ fileName = fp.defaultString;
+ info("fileName: " + fileName);
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ info("done showCallback");
+ };
+
+ mockTransferCallback = function(downloadSuccess) {
+ info("mockTransferCallback");
+ onTransferComplete(aWindow, downloadSuccess, destDir);
+ destDir.remove(true);
+ ok(!destDir.exists(), "Destination dir should be removed");
+ ok(!destFile.exists(), "Destination file should be removed");
+ mockTransferCallback = null;
+ info("done mockTransferCallback");
+ }
+
+ // Select "Save Link As" option from context menu
+ var saveLinkCommand = aWindow.document.getElementById("context-savelink");
+ info("saveLinkCommand: " + saveLinkCommand);
+ saveLinkCommand.doCommand();
+
+ event.target.hidePopup();
+ info("popup hidden");
+ }
+
+ function onTransferComplete(aWindow2, downloadSuccess, destDir) {
+ ok(downloadSuccess, "Link should have been downloaded successfully");
+ aWindow2.close();
+
+ executeSoon(() => aCallback());
+ }
+}
+
+function test() {
+ info("Start the test");
+ waitForExplicitFinish();
+
+ var gNumSet = 0;
+ function testOnWindow(options, callback) {
+ info("testOnWindow(" + options + ")");
+ var win = OpenBrowserWindow(options);
+ info("got " + win);
+ whenDelayedStartupFinished(win, () => callback(win));
+ }
+
+ function whenDelayedStartupFinished(aWindow, aCallback) {
+ info("whenDelayedStartupFinished");
+ Services.obs.addObserver(function obs(aSubject, aTopic) {
+ info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(obs, aTopic);
+ executeSoon(aCallback);
+ info("whenDelayedStartupFinished found our window");
+ }
+ }, "browser-delayed-startup-finished", false);
+ }
+
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ info("Running the cleanup code");
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ info("Finished running the cleanup code");
+ });
+
+ function observer(subject, topic, state) {
+ info("observer called with " + topic);
+ if (topic == "http-on-modify-request") {
+ onModifyRequest(subject);
+ } else if (topic == "http-on-examine-response") {
+ onExamineResponse(subject);
+ }
+ }
+
+ function onExamineResponse(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ info("onExamineResponse with " + channel.URI.spec);
+ if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") {
+ info("returning");
+ return;
+ }
+ try {
+ let cookies = channel.getResponseHeader("set-cookie");
+ // From browser/base/content/test/general/bug792715.sjs, we receive a Set-Cookie
+ // header with foopy=1 when there are no cookies for that domain.
+ is(cookies, "foopy=1", "Cookie should be foopy=1");
+ gNumSet += 1;
+ info("gNumSet = " + gNumSet);
+ } catch (ex) {
+ if (ex.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ info("onExamineResponse caught NOTAVAIL" + ex);
+ } else {
+ info("ionExamineResponse caught " + ex);
+ }
+ }
+ }
+
+ function onModifyRequest(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ info("onModifyRequest with " + channel.URI.spec);
+ if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") {
+ return;
+ }
+ try {
+ let cookies = channel.getRequestHeader("cookie");
+ info("cookies: " + cookies);
+ // From browser/base/content/test/general/bug792715.sjs, we should never send a
+ // cookie because we are making only 2 requests: one in public mode, and
+ // one in private mode.
+ throw "We should never send a cookie in this test";
+ } catch (ex) {
+ if (ex.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ info("onModifyRequest caught NOTAVAIL" + ex);
+ } else {
+ info("ionModifyRequest caught " + ex);
+ }
+ }
+ }
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+
+ testOnWindow(undefined, function(win) {
+ // The first save from a regular window sets a cookie.
+ triggerSave(win, function() {
+ is(gNumSet, 1, "1 cookie should be set");
+
+ // The second save from a private window also sets a cookie.
+ testOnWindow({private: true}, function(win2) {
+ triggerSave(win2, function() {
+ is(gNumSet, 2, "2 cookies should be set");
+ finish();
+ });
+ });
+ });
+ });
+}
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists()) {
+ info("create testsavedir!");
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ info("return from createTempSaveDir: " + saveDir.path);
+ return saveDir;
+}
diff --git a/browser/base/content/test/general/browser_save_link_when_window_navigates.js b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
new file mode 100644
index 000000000..2fd10b00e
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
+const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir";
+const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists()) {
+ info("create testsavedir!");
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ info("return from createTempSaveDir: " + saveDir.path);
+ return saveDir;
+}
+
+function triggerSave(aWindow, aCallback) {
+ info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off"));
+ var fileName;
+ let testBrowser = aWindow.gBrowser.selectedBrowser;
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html";
+ windowObserver.setCallback(onUCTDialog);
+ testBrowser.loadURI(testURI);
+
+ // Create the folder the link will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ info("showCallback");
+ fileName = fp.defaultString;
+ info("fileName: " + fileName);
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ info("done showCallback");
+ };
+
+ mockTransferCallback = function(downloadSuccess) {
+ info("mockTransferCallback");
+ onTransferComplete(aWindow, downloadSuccess, destDir);
+ destDir.remove(true);
+ ok(!destDir.exists(), "Destination dir should be removed");
+ ok(!destFile.exists(), "Destination file should be removed");
+ mockTransferCallback = null;
+ info("done mockTransferCallback");
+ }
+
+ function onUCTDialog(dialog) {
+ function doLoad() {
+ content.document.querySelector('iframe').remove();
+ }
+ testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false);
+ executeSoon(continueDownloading);
+ }
+
+ function continueDownloading() {
+ let windows = Services.wm.getEnumerator("");
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (win.location && win.location.href == UCT_URI) {
+ win.document.documentElement._fireButtonEvent("accept");
+ win.close();
+ return;
+ }
+ }
+ ok(false, "No Unknown Content Type dialog yet?");
+ }
+
+ function onTransferComplete(aWindow2, downloadSuccess) {
+ ok(downloadSuccess, "Link should have been downloaded successfully");
+ aWindow2.close();
+
+ executeSoon(aCallback);
+ }
+}
+
+
+var windowObserver = {
+ setCallback: function(aCallback) {
+ if (this._callback) {
+ ok(false, "Should only be dealing with one callback at a time.");
+ }
+ this._callback = aCallback;
+ },
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened") {
+ return;
+ }
+
+ let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+
+ win.addEventListener("load", function onLoad(event) {
+ win.removeEventListener("load", onLoad, false);
+
+ if (win.location == UCT_URI) {
+ SimpleTest.executeSoon(function() {
+ if (windowObserver._callback) {
+ windowObserver._callback(win);
+ delete windowObserver._callback;
+ } else {
+ ok(false, "Unexpected UCT dialog!");
+ }
+ });
+ }
+ }, false);
+ }
+};
+
+Services.ww.registerNotification(windowObserver);
+
+function test() {
+ waitForExplicitFinish();
+
+ function testOnWindow(options, callback) {
+ info("testOnWindow(" + options + ")");
+ var win = OpenBrowserWindow(options);
+ info("got " + win);
+ whenDelayedStartupFinished(win, () => callback(win));
+ }
+
+ function whenDelayedStartupFinished(aWindow, aCallback) {
+ info("whenDelayedStartupFinished");
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ info("whenDelayedStartupFinished found our window");
+ }
+ }, "browser-delayed-startup-finished", false);
+ }
+
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ info("Running the cleanup code");
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ Services.ww.unregisterNotification(windowObserver);
+ Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF);
+ Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
+ info("Finished running the cleanup code");
+ });
+
+ Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false);
+ testOnWindow(undefined, function(win) {
+ let windowGonePromise = promiseWindowWillBeClosed(win);
+ Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true);
+ triggerSave(win, function() {
+ windowGonePromise.then(function() {
+ Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false);
+ testOnWindow(undefined, function(win2) {
+ triggerSave(win2, finish);
+ });
+ });
+ });
+ });
+}
+
diff --git a/browser/base/content/test/general/browser_save_private_link_perwindowpb.js b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
new file mode 100644
index 000000000..e7ed5fa34
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_private_link_perwindowpb.js
@@ -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/. */
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+
+function promiseNoCacheEntry(filename) {
+ return new Promise((resolve, reject) => {
+ Visitor.prototype = {
+ onCacheStorageInfo: function(num, consumption)
+ {
+ info("disk storage contains " + num + " entries");
+ },
+ onCacheEntryInfo: function(uri)
+ {
+ let urispec = uri.asciiSpec;
+ info(urispec);
+ is(urispec.includes(filename), false, "web content present in disk cache");
+ },
+ onCacheEntryVisitCompleted: function()
+ {
+ resolve();
+ }
+ };
+ function Visitor() {}
+
+ let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
+ let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+ storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
+ });
+}
+
+function promiseImageDownloaded() {
+ return new Promise((resolve, reject) => {
+ let fileName;
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+
+ function onTransferComplete(downloadSuccess) {
+ ok(downloadSuccess, "Image file should have been downloaded successfully " + fileName);
+
+ // Give the request a chance to finish and create a cache entry
+ resolve(fileName);
+ }
+
+ // Create the folder the image will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ fileName = fp.defaultString;
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ mockTransferCallback = onTransferComplete;
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferCallback = null;
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ });
+}
+
+add_task(function* () {
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.html";
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let tab = yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, testURI);
+
+ let contextMenu = privateWindow.document.getElementById("contentAreaContextMenu");
+ let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ let popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#img", {
+ type: "contextmenu",
+ button: 2
+ }, tab.linkedBrowser);
+ yield popupShown;
+
+ let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ cache.clear();
+
+ let imageDownloaded = promiseImageDownloaded();
+ // Select "Save Image As" option from context menu
+ privateWindow.document.getElementById("context-saveimage").doCommand();
+
+ contextMenu.hidePopup();
+ yield popupHidden;
+
+ // wait for image download
+ let fileName = yield imageDownloaded;
+ yield promiseNoCacheEntry(fileName);
+
+ yield BrowserTestUtils.closeWindow(privateWindow);
+});
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
diff --git a/browser/base/content/test/general/browser_save_video.js b/browser/base/content/test/general/browser_save_video.js
new file mode 100644
index 000000000..e81286b7a
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_video.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+/**
+ * TestCase for bug 564387
+ * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387>
+ */
+add_task(function* () {
+ var fileName;
+
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html");
+ yield loadPromise;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#video1",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser);
+ info("context menu click on video1");
+
+ yield popupShownPromise;
+
+ info("context menu opened on video1");
+
+ // Create the folder the video will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ fileName = fp.defaultString;
+ destFile.append(fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ let transferCompletePromise = new Promise((resolve) => {
+ function onTransferComplete(downloadSuccess) {
+ ok(downloadSuccess, "Video file should have been downloaded successfully");
+
+ is(fileName, "web-video1-expectedName.ogv",
+ "Video file name is correctly retrieved from Content-Disposition http header");
+ resolve();
+ }
+
+ mockTransferCallback = onTransferComplete;
+ mockTransferRegisterer.register();
+ });
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ // Select "Save Video As" option from context menu
+ var saveVideoCommand = document.getElementById("context-savevideo");
+ saveVideoCommand.doCommand();
+ info("context-savevideo command executed");
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ yield popupHiddenPromise;
+
+ yield transferCompletePromise;
+});
+
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
diff --git a/browser/base/content/test/general/browser_save_video_frame.js b/browser/base/content/test/general/browser_save_video_frame.js
new file mode 100644
index 000000000..e9b8a0475
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_video_frame.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const VIDEO_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html";
+
+/**
+ * mockTransfer.js provides a utility that lets us mock out
+ * the "Save File" dialog.
+ */
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+/**
+ * Creates and returns an nsIFile for a new temporary save
+ * directory.
+ *
+ * @return nsIFile
+ */
+function createTemporarySaveDirectory() {
+ let saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+/**
+ * MockTransfer exposes a "mockTransferCallback" global which
+ * allows us to define a callback to be called once the mock file
+ * selector has selected where to save the file.
+ */
+function waitForTransferComplete() {
+ return new Promise((resolve) => {
+ mockTransferCallback = () => {
+ ok(true, "Transfer completed");
+ resolve();
+ }
+ });
+}
+
+/**
+ * Given some browser, loads a framescript that right-clicks
+ * on the video1 element to spawn a contextmenu.
+ */
+function rightClickVideo(browser) {
+ let frame_script = () => {
+ const Ci = Components.interfaces;
+ let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let document = content.document;
+ let video = document.getElementById("video1");
+ let rect = video.getBoundingClientRect();
+
+ /* Synthesize a click in the center of the video. */
+ let left = rect.left + (rect.width / 2);
+ let top = rect.top + (rect.height / 2);
+
+ utils.sendMouseEvent("contextmenu", left, top,
+ 2, /* aButton */
+ 1, /* aClickCount */
+ 0 /* aModifiers */);
+ };
+ let mm = browser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+}
+
+/**
+ * Loads a page with a <video> element, right-clicks it and chooses
+ * to save a frame screenshot to the disk. Completes once we've
+ * verified that the frame has been saved to disk.
+ */
+add_task(function*() {
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+
+ // Create the folder the video will be saved into.
+ let destDir = createTemporarySaveDirectory();
+ let destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ destFile.append(fp.defaultString);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ mockTransferRegisterer.register();
+
+ // Make sure that we clean these things up when we're done.
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+ info("Loading video tab");
+ yield promiseTabLoadEvent(tab, VIDEO_URL);
+ info("Video tab loaded.");
+
+ let context = document.getElementById("contentAreaContextMenu");
+ let popupPromise = promisePopupShown(context);
+
+ info("Synthesizing right-click on video element");
+ rightClickVideo(browser);
+ info("Waiting for popup to fire popupshown.");
+ yield popupPromise;
+ info("Popup fired popupshown");
+
+ let saveSnapshotCommand = document.getElementById("context-video-saveimage");
+ let promiseTransfer = waitForTransferComplete()
+ info("Firing save snapshot command");
+ saveSnapshotCommand.doCommand();
+ context.hidePopup();
+ info("Waiting for transfer completion");
+ yield promiseTransfer;
+ info("Transfer complete");
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_scope.js b/browser/base/content/test/general/browser_scope.js
new file mode 100644
index 000000000..f8141e5f6
--- /dev/null
+++ b/browser/base/content/test/general/browser_scope.js
@@ -0,0 +1,10 @@
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.docShell is null");
+
+function test() {
+ ok(!!gBrowser, "gBrowser exists");
+ is(gBrowser, getBrowser(), "both ways of getting tabbrowser work");
+}
diff --git a/browser/base/content/test/general/browser_selectTabAtIndex.js b/browser/base/content/test/general/browser_selectTabAtIndex.js
new file mode 100644
index 000000000..b6578aec0
--- /dev/null
+++ b/browser/base/content/test/general/browser_selectTabAtIndex.js
@@ -0,0 +1,81 @@
+"use strict";
+
+function test() {
+ const isLinux = navigator.platform.indexOf("Linux") == 0;
+
+ function assertTab(expectedTab) {
+ is(gBrowser.tabContainer.selectedIndex, expectedTab,
+ `tab index ${expectedTab} should be selected`);
+ }
+
+ function sendAccelKey(key) {
+ // Make sure the keystroke goes to chrome.
+ document.activeElement.blur();
+ EventUtils.synthesizeKey(key.toString(), { altKey: isLinux, accelKey: !isLinux });
+ }
+
+ function createTabs(count) {
+ for (let n = 0; n < count; n++)
+ gBrowser.addTab();
+ }
+
+ function testKey(key, expectedTab) {
+ sendAccelKey(key);
+ assertTab(expectedTab);
+ }
+
+ function testIndex(index, expectedTab) {
+ gBrowser.selectTabAtIndex(index);
+ assertTab(expectedTab);
+ }
+
+ // Create fewer tabs than our 9 number keys.
+ is(gBrowser.tabs.length, 1, "should have 1 tab");
+ createTabs(4);
+ is(gBrowser.tabs.length, 5, "should have 5 tabs");
+
+ // Test keyboard shortcuts. Order tests so that no two test cases have the
+ // same expected tab in a row. This ensures that tab selection actually
+ // changed the selected tab.
+ testKey(8, 4);
+ testKey(1, 0);
+ testKey(2, 1);
+ testKey(4, 3);
+ testKey(9, 4);
+
+ // Test index selection.
+ testIndex(0, 0);
+ testIndex(4, 4);
+ testIndex(-5, 0);
+ testIndex(5, 4);
+ testIndex(-4, 1);
+ testIndex(1, 1);
+ testIndex(-1, 4);
+ testIndex(9, 4);
+
+ // Create more tabs than our 9 number keys.
+ createTabs(10);
+ is(gBrowser.tabs.length, 15, "should have 15 tabs");
+
+ // Test keyboard shortcuts.
+ testKey(2, 1);
+ testKey(1, 0);
+ testKey(4, 3);
+ testKey(8, 7);
+ testKey(9, 14);
+
+ // Test index selection.
+ testIndex(-15, 0);
+ testIndex(14, 14);
+ testIndex(-14, 1);
+ testIndex(15, 14);
+ testIndex(-1, 14);
+ testIndex(0, 0);
+ testIndex(1, 1);
+ testIndex(9, 9);
+
+ // Clean up tabs.
+ for (let n = 15; n > 1; n--)
+ gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
+ is(gBrowser.tabs.length, 1, "should have 1 tab");
+}
diff --git a/browser/base/content/test/general/browser_selectpopup.js b/browser/base/content/test/general/browser_selectpopup.js
new file mode 100644
index 000000000..d34254d1c
--- /dev/null
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -0,0 +1,563 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks that a <select> with an <optgroup> opens and can be navigated
+// in a child process. This is different than single-process as a <menulist> is used
+// to implement the dropdown list.
+
+requestLongerTimeout(2);
+
+const XHTML_DTD = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
+
+const PAGECONTENT =
+ "<html xmlns='http://www.w3.org/1999/xhtml'>" +
+ "<body onload='gChangeEvents = 0;gInputEvents = 0; document.body.firstChild.focus()'><select oninput='gInputEvents++' onchange='gChangeEvents++'>" +
+ " <optgroup label='First Group'>" +
+ " <option value='One'>One</option>" +
+ " <option value='Two'>Two</option>" +
+ " </optgroup>" +
+ " <option value='Three'>Three</option>" +
+ " <optgroup label='Second Group' disabled='true'>" +
+ " <option value='Four'>Four</option>" +
+ " <option value='Five'>Five</option>" +
+ " </optgroup>" +
+ " <option value='Six' disabled='true'>Six</option>" +
+ " <optgroup label='Third Group'>" +
+ " <option value='Seven'> Seven </option>" +
+ " <option value='Eight'>&nbsp;&nbsp;Eight&nbsp;&nbsp;</option>" +
+ " </optgroup></select><input />Text" +
+ "</body></html>";
+
+const PAGECONTENT_SMALL =
+ "<html>" +
+ "<body><select id='one'>" +
+ " <option value='One'>One</option>" +
+ " <option value='Two'>Two</option>" +
+ "</select><select id='two'>" +
+ " <option value='Three'>Three</option>" +
+ " <option value='Four'>Four</option>" +
+ "</select><select id='three'>" +
+ " <option value='Five'>Five</option>" +
+ " <option value='Six'>Six</option>" +
+ "</select></body></html>";
+
+const PAGECONTENT_SOMEHIDDEN =
+ "<html><head><style>.hidden { display: none; }</style></head>" +
+ "<body><select id='one'>" +
+ " <option value='One' style='display: none;'>OneHidden</option>" +
+ " <option value='Two' class='hidden'>TwoHidden</option>" +
+ " <option value='Three'>ThreeVisible</option>" +
+ " <option value='Four'style='display: table;'>FourVisible</option>" +
+ " <option value='Five'>FiveVisible</option>" +
+ " <optgroup label='GroupHidden' class='hidden'>" +
+ " <option value='Four'>Six.OneHidden</option>" +
+ " <option value='Five' style='display: block;'>Six.TwoHidden</option>" +
+ " </optgroup>" +
+ " <option value='Six' class='hidden' style='display: block;'>SevenVisible</option>" +
+ "</select></body></html>";
+
+const PAGECONTENT_TRANSLATED =
+ "<html><body>" +
+ "<div id='div'>" +
+ "<iframe id='frame' width='320' height='295' style='border: none;'" +
+ " src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" +
+ "</iframe>" +
+ "</div></body></html>";
+
+function openSelectPopup(selectPopup, withMouse, selector = "select", win = window)
+{
+ let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+
+ if (withMouse) {
+ return Promise.all([popupShownPromise,
+ BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser)]);
+ }
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }, win);
+ return popupShownPromise;
+}
+
+function hideSelectPopup(selectPopup, mode = "enter", win = window)
+{
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+
+ if (mode == "escape") {
+ EventUtils.synthesizeKey("KEY_Escape", { code: "Escape" }, win);
+ }
+ else if (mode == "enter") {
+ EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" }, win);
+ }
+ else if (mode == "click") {
+ EventUtils.synthesizeMouseAtCenter(selectPopup.lastChild, { }, win);
+ }
+
+ return popupHiddenPromise;
+}
+
+function getInputEvents()
+{
+ return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ return content.wrappedJSObject.gInputEvents;
+ });
+}
+
+function getChangeEvents()
+{
+ return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ return content.wrappedJSObject.gChangeEvents;
+ });
+}
+
+function* doSelectTests(contentType, dtd)
+{
+ const pageUrl = "data:" + contentType + "," + escape(dtd + "\n" + PAGECONTENT);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ yield openSelectPopup(selectPopup);
+
+ let isWindows = navigator.platform.indexOf("Win") >= 0;
+
+ is(menulist.selectedIndex, 1, "Initial selection");
+ is(selectPopup.firstChild.localName, "menucaption", "optgroup is caption");
+ is(selectPopup.firstChild.getAttribute("label"), "First Group", "optgroup label");
+ is(selectPopup.childNodes[1].localName, "menuitem", "option is menuitem");
+ is(selectPopup.childNodes[1].getAttribute("label"), "One", "option label");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(2), "Select item 2");
+ is(menulist.selectedIndex, isWindows ? 2 : 1, "Select item 2 selectedIndex");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3");
+ is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+
+ // On Windows, one can navigate on disabled menuitems
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(9),
+ "Skip optgroup header and disabled items select item 7");
+ is(menulist.selectedIndex, isWindows ? 9 : 1, "Select or skip disabled item selectedIndex");
+
+ for (let i = 0; i < 10; i++) {
+ is(menulist.getItemAtIndex(i).disabled, i >= 4 && i <= 7, "item " + i + " disabled")
+ }
+
+ EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
+ is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again");
+ is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
+
+ is((yield getInputEvents()), 0, "Before closed - number of input events");
+ is((yield getChangeEvents()), 0, "Before closed - number of change events");
+
+ EventUtils.synthesizeKey("a", { accelKey: true });
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { isWindows }, function(args) {
+ Assert.equal(String(content.getSelection()), args.isWindows ? "Text" : "",
+ "Select all while popup is open");
+ });
+
+ // Backspace should not go back
+ let handleKeyPress = function(event) {
+ ok(false, "Should not get keypress event");
+ }
+ window.addEventListener("keypress", handleKeyPress);
+ EventUtils.synthesizeKey("VK_BACK_SPACE", { });
+ window.removeEventListener("keypress", handleKeyPress);
+
+ yield hideSelectPopup(selectPopup);
+
+ is(menulist.selectedIndex, 3, "Item 3 still selected");
+ is((yield getInputEvents()), 1, "After closed - number of input events");
+ is((yield getChangeEvents()), 1, "After closed - number of change events");
+
+ // Opening and closing the popup without changing the value should not fire a change event.
+ yield openSelectPopup(selectPopup, true);
+ yield hideSelectPopup(selectPopup, "escape");
+ is((yield getInputEvents()), 1, "Open and close with no change - number of input events");
+ is((yield getChangeEvents()), 1, "Open and close with no change - number of change events");
+ EventUtils.synthesizeKey("VK_TAB", { });
+ EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+ is((yield getInputEvents()), 1, "Tab away from select with no change - number of input events");
+ is((yield getChangeEvents()), 1, "Tab away from select with no change - number of change events");
+
+ yield openSelectPopup(selectPopup, true);
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ yield hideSelectPopup(selectPopup, "escape");
+ is((yield getInputEvents()), isWindows ? 2 : 1, "Open and close with change - number of input events");
+ is((yield getChangeEvents()), isWindows ? 2 : 1, "Open and close with change - number of change events");
+ EventUtils.synthesizeKey("VK_TAB", { });
+ EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+ is((yield getInputEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of input events");
+ is((yield getChangeEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of change events");
+
+ is(selectPopup.lastChild.previousSibling.label, "Seven", "Spaces collapsed");
+ is(selectPopup.lastChild.label, "\xA0\xA0Eight\xA0\xA0", "Non-breaking spaces not collapsed");
+
+ yield BrowserTestUtils.removeTab(tab);
+}
+
+add_task(function*() {
+ yield doSelectTests("text/html", "");
+});
+
+add_task(function*() {
+ yield doSelectTests("application/xhtml+xml", XHTML_DTD);
+});
+
+// This test opens a select popup and removes the content node of a popup while
+// The popup should close if its node is removed.
+add_task(function*() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ // First, try it when a different <select> element than the one that is open is removed
+ yield openSelectPopup(selectPopup, true, "#one");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ content.document.body.removeChild(content.document.getElementById("two"));
+ });
+
+ // Wait a bit just to make sure the popup won't close.
+ yield new Promise(resolve => setTimeout(resolve, 1000));
+
+ is(selectPopup.state, "open", "Different popup did not affect open popup");
+
+ yield hideSelectPopup(selectPopup);
+
+ // Next, try it when the same <select> element than the one that is open is removed
+ yield openSelectPopup(selectPopup, true, "#three");
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+ content.document.body.removeChild(content.document.getElementById("three"));
+ });
+ yield popupHiddenPromise;
+
+ ok(true, "Popup hidden when select is removed");
+
+ // Finally, try it when the tab is closed while the select popup is open.
+ yield openSelectPopup(selectPopup, true, "#one");
+
+ popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+ yield BrowserTestUtils.removeTab(tab);
+ yield popupHiddenPromise;
+
+ ok(true, "Popup hidden when tab is closed");
+});
+
+// This test opens a select popup that is isn't a frame and has some translations applied.
+add_task(function*() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_TRANSLATED);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ // First, get the position of the select popup when no translations have been applied.
+ yield openSelectPopup(selectPopup, false);
+
+ let rect = selectPopup.getBoundingClientRect();
+ let expectedX = rect.left;
+ let expectedY = rect.top;
+
+ yield hideSelectPopup(selectPopup);
+
+ // Iterate through a set of steps which each add more translation to the select's expected position.
+ let steps = [
+ [ "div", "transform: translateX(7px) translateY(13px);", 7, 13 ],
+ [ "frame", "border-top: 5px solid green; border-left: 10px solid red; border-right: 35px solid blue;", 10, 5 ],
+ [ "frame", "border: none; padding-left: 6px; padding-right: 12px; padding-top: 2px;", -4, -3 ],
+ [ "select", "margin: 9px; transform: translateY(-3px);", 9, 6 ],
+ ];
+
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
+ let step = steps[stepIndex];
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, step, function*(contentStep) {
+ return new Promise(resolve => {
+ let changedWin = content;
+
+ let elem;
+ if (contentStep[0] == "select") {
+ changedWin = content.document.getElementById("frame").contentWindow;
+ elem = changedWin.document.getElementById("select");
+ }
+ else {
+ elem = content.document.getElementById(contentStep[0]);
+ }
+
+ changedWin.addEventListener("MozAfterPaint", function onPaint() {
+ changedWin.removeEventListener("MozAfterPaint", onPaint);
+ resolve();
+ });
+
+ elem.style = contentStep[1];
+ elem.getBoundingClientRect();
+ });
+ });
+
+ yield openSelectPopup(selectPopup, false);
+
+ expectedX += step[2];
+ expectedY += step[3];
+
+ let popupRect = selectPopup.getBoundingClientRect();
+ is(popupRect.left, expectedX, "step " + (stepIndex + 1) + " x");
+ is(popupRect.top, expectedY, "step " + (stepIndex + 1) + " y");
+
+ yield hideSelectPopup(selectPopup);
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// Test that we get the right events when a select popup is changed.
+add_task(function* test_event_order() {
+ const URL = "data:text/html," + escape(PAGECONTENT_SMALL);
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: URL,
+ }, function*(browser) {
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
+
+ // According to https://html.spec.whatwg.org/#the-select-element,
+ // we want to fire input, change, and then click events on the
+ // <select> (in that order) when it has changed.
+ let expectedEnter = [
+ {
+ type: "input",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ {
+ type: "change",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ ];
+
+ let expectedClick = [
+ {
+ type: "mousedown",
+ cancelable: true,
+ targetIsOption: true,
+ },
+ {
+ type: "mouseup",
+ cancelable: true,
+ targetIsOption: true,
+ },
+ {
+ type: "input",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ {
+ type: "change",
+ cancelable: false,
+ targetIsOption: false,
+ },
+ {
+ type: "click",
+ cancelable: true,
+ targetIsOption: true,
+ },
+ ];
+
+ for (let mode of ["enter", "click"]) {
+ let expected = mode == "enter" ? expectedEnter : expectedClick;
+ yield openSelectPopup(selectPopup, true, mode == "enter" ? "#one" : "#two");
+
+ let eventsPromise = ContentTask.spawn(browser, [mode, expected], function*([contentMode, contentExpected]) {
+ return new Promise((resolve) => {
+ function onEvent(event) {
+ select.removeEventListener(event.type, onEvent);
+ Assert.ok(contentExpected.length, "Unexpected event " + event.type);
+ let expectation = contentExpected.shift();
+ Assert.equal(event.type, expectation.type,
+ "Expected the right event order");
+ Assert.ok(event.bubbles, "All of these events should bubble");
+ Assert.equal(event.cancelable, expectation.cancelable,
+ "Cancellation property should match");
+ Assert.equal(event.target.localName,
+ expectation.targetIsOption ? "option" : "select",
+ "Target matches");
+ if (!contentExpected.length) {
+ resolve();
+ }
+ }
+
+ let select = content.document.getElementById(contentMode == "enter" ? "one" : "two");
+ for (let event of ["input", "change", "mousedown", "mouseup", "click"]) {
+ select.addEventListener(event, onEvent);
+ }
+ });
+ });
+
+ EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+ yield hideSelectPopup(selectPopup, mode);
+ yield eventsPromise;
+ }
+ });
+});
+
+function* performLargePopupTests(win)
+{
+ let browser = win.gBrowser.selectedBrowser;
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let doc = content.document;
+ let select = doc.getElementById("one");
+ for (var i = 0; i < 180; i++) {
+ select.add(new content.Option("Test" + i));
+ }
+
+ select.options[60].selected = true;
+ select.focus();
+ });
+
+ let selectPopup = win.document.getElementById("ContentSelectDropdown").menupopup;
+ let browserRect = browser.getBoundingClientRect();
+
+ let positions = [
+ "margin-top: 300px;",
+ "position: fixed; bottom: 100px;",
+ "width: 100%; height: 9999px;"
+ ];
+
+ let position;
+ while (true) {
+ yield openSelectPopup(selectPopup, false, "select", win);
+
+ let rect = selectPopup.getBoundingClientRect();
+ ok(rect.top >= browserRect.top, "Popup top position in within browser area");
+ ok(rect.bottom <= browserRect.bottom, "Popup bottom position in within browser area");
+
+ // Don't check the scroll position for the last step as the popup will be cut off.
+ if (positions.length > 0) {
+ let cs = win.getComputedStyle(selectPopup);
+ let bpBottom = parseFloat(cs.paddingBottom) + parseFloat(cs.borderBottomWidth);
+
+ is(selectPopup.childNodes[60].getBoundingClientRect().bottom,
+ selectPopup.getBoundingClientRect().bottom - bpBottom,
+ "Popup scroll at correct position " + bpBottom);
+ }
+
+ yield hideSelectPopup(selectPopup, "enter", win);
+
+ position = positions.shift();
+ if (!position) {
+ break;
+ }
+
+ let contentPainted = BrowserTestUtils.contentPainted(browser);
+ yield ContentTask.spawn(browser, position, function*(contentPosition) {
+ let select = content.document.getElementById("one");
+ select.setAttribute("style", contentPosition);
+ select.getBoundingClientRect();
+ });
+ yield contentPainted;
+ }
+}
+
+// This test checks select elements with a large number of options to ensure that
+// the popup appears within the browser area.
+add_task(function* test_large_popup() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ yield* performLargePopupTests(window);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// This test checks the same as the previous test but in a new smaller window.
+add_task(function* test_large_popup_in_small_window() {
+ let newwin = yield BrowserTestUtils.openNewBrowserWindow({ width: 400, height: 400 });
+
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(newwin.gBrowser.selectedBrowser);
+ yield BrowserTestUtils.loadURI(newwin.gBrowser.selectedBrowser, pageUrl);
+ yield browserLoadedPromise;
+
+ newwin.gBrowser.selectedBrowser.focus();
+
+ yield* performLargePopupTests(newwin);
+
+ yield BrowserTestUtils.closeWindow(newwin);
+});
+
+// This test checks that a mousemove event is fired correctly at the menu and
+// not at the browser, ensuring that any mouse capture has been cleared.
+add_task(function* test_mousemove_correcttarget() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SMALL);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ yield new Promise(resolve => {
+ window.addEventListener("mousemove", function checkForMouseMove(event) {
+ window.removeEventListener("mousemove", checkForMouseMove, true);
+ is(event.target.localName.indexOf("menu"), 0, "mouse over menu");
+ resolve();
+ }, true);
+
+ EventUtils.synthesizeMouseAtCenter(selectPopup.firstChild, { type: "mousemove" });
+ });
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mouseup" }, gBrowser.selectedBrowser);
+
+ yield hideSelectPopup(selectPopup);
+
+ // The popup should be closed when fullscreen mode is entered or exited.
+ for (let steps = 0; steps < 2; steps++) {
+ yield openSelectPopup(selectPopup, true);
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+ let sizeModeChanged = BrowserTestUtils.waitForEvent(window, "sizemodechange");
+ BrowserFullScreen();
+ yield sizeModeChanged;
+ yield popupHiddenPromise;
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+// This test checks when a <select> element has some options with altered display values.
+add_task(function* test_somehidden() {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_SOMEHIDDEN);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+
+ // The exact number is not needed; just ensure the height is larger than 4 items to accomodate any popup borders.
+ ok(selectPopup.getBoundingClientRect().height >= selectPopup.lastChild.getBoundingClientRect().height * 4, "Height contains at least 4 items");
+ ok(selectPopup.getBoundingClientRect().height < selectPopup.lastChild.getBoundingClientRect().height * 5, "Height doesn't contain 5 items");
+
+ // The label contains the substring 'Visible' for items that are visible.
+ // Otherwise, it is expected to be display: none.
+ is(selectPopup.parentNode.itemCount, 9, "Correct number of items");
+ let child = selectPopup.firstChild;
+ let idx = 1;
+ while (child) {
+ is(getComputedStyle(child).display, child.label.indexOf("Visible") > 0 ? "-moz-box" : "none",
+ "Item " + (idx++) + " is visible");
+ child = child.nextSibling;
+ }
+
+ yield hideSelectPopup(selectPopup, "escape");
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/general/browser_ssl_error_reports.js b/browser/base/content/test/general/browser_ssl_error_reports.js
new file mode 100644
index 000000000..b1b1c8b84
--- /dev/null
+++ b/browser/base/content/test/general/browser_ssl_error_reports.js
@@ -0,0 +1,174 @@
+"use strict";
+
+const URL_REPORTS = "https://example.com/browser/browser/base/content/test/general/ssl_error_reports.sjs?";
+const URL_BAD_CHAIN = "https://badchain.include-subdomains.pinning.example.com/";
+const URL_NO_CERT = "https://fail-handshake.example.com/";
+const URL_BAD_CERT = "https://expired.example.com/";
+const URL_BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443/";
+const ROOT = getRootDirectory(gTestPath);
+const PREF_REPORT_ENABLED = "security.ssl.errorReporting.enabled";
+const PREF_REPORT_AUTOMATIC = "security.ssl.errorReporting.automatic";
+const PREF_REPORT_URL = "security.ssl.errorReporting.url";
+
+SimpleTest.requestCompleteLog();
+
+Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
+
+function cleanup() {
+ Services.prefs.clearUserPref(PREF_REPORT_ENABLED);
+ Services.prefs.clearUserPref(PREF_REPORT_AUTOMATIC);
+ Services.prefs.clearUserPref(PREF_REPORT_URL);
+}
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
+ cleanup();
+});
+
+add_task(function* test_send_report_neterror() {
+ yield testSendReportAutomatically(URL_BAD_CHAIN, "succeed", "neterror");
+ yield testSendReportAutomatically(URL_NO_CERT, "nocert", "neterror");
+ yield testSetAutomatic(URL_NO_CERT, "nocert", "neterror");
+});
+
+
+add_task(function* test_send_report_certerror() {
+ yield testSendReportAutomatically(URL_BAD_CERT, "badcert", "certerror");
+ yield testSetAutomatic(URL_BAD_CERT, "badcert", "certerror");
+});
+
+add_task(function* test_send_disabled() {
+ Services.prefs.setBoolPref(PREF_REPORT_ENABLED, false);
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
+ Services.prefs.setCharPref(PREF_REPORT_URL, "https://example.com/invalid");
+
+ // Check with enabled=false but automatic=true.
+ yield testSendReportDisabled(URL_NO_CERT, "neterror");
+ yield testSendReportDisabled(URL_BAD_CERT, "certerror");
+
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false);
+
+ // Check again with both prefs false.
+ yield testSendReportDisabled(URL_NO_CERT, "neterror");
+ yield testSendReportDisabled(URL_BAD_CERT, "certerror");
+ cleanup();
+});
+
+function* testSendReportAutomatically(testURL, suffix, errorURISuffix) {
+ Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
+ Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
+
+ // Add a tab and wait until it's loaded.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ let browser = tab.linkedBrowser;
+
+ // Load the page and wait for the error report submission.
+ let promiseStatus = createReportResponseStatusPromise(URL_REPORTS + suffix);
+ browser.loadURI(testURL);
+ yield promiseErrorPageLoaded(browser);
+
+ ok(!isErrorStatus(yield promiseStatus),
+ "SSL error report submitted successfully");
+
+ // Check that we loaded the right error page.
+ yield checkErrorPage(browser, errorURISuffix);
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+ cleanup();
+}
+
+function* testSetAutomatic(testURL, suffix, errorURISuffix) {
+ Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false);
+ Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
+
+ // Add a tab and wait until it's loaded.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ let browser = tab.linkedBrowser;
+
+ // Load the page.
+ browser.loadURI(testURL);
+ yield promiseErrorPageLoaded(browser);
+
+ // Check that we loaded the right error page.
+ yield checkErrorPage(browser, errorURISuffix);
+
+ let statusPromise = createReportResponseStatusPromise(URL_REPORTS + suffix);
+
+ // Click the checkbox, enable automatic error reports.
+ yield ContentTask.spawn(browser, null, function* () {
+ content.document.getElementById("automaticallyReportInFuture").click();
+ });
+
+ // Wait for the error report submission.
+ yield statusPromise;
+
+ let isAutomaticReportingEnabled = () =>
+ Services.prefs.getBoolPref(PREF_REPORT_AUTOMATIC);
+
+ // Check that the pref was flipped.
+ ok(isAutomaticReportingEnabled(), "automatic SSL report submission enabled");
+
+ // Disable automatic error reports.
+ yield ContentTask.spawn(browser, null, function* () {
+ content.document.getElementById("automaticallyReportInFuture").click();
+ });
+
+ // Check that the pref was flipped.
+ ok(!isAutomaticReportingEnabled(), "automatic SSL report submission disabled");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+ cleanup();
+}
+
+function* testSendReportDisabled(testURL, errorURISuffix) {
+ // Add a tab and wait until it's loaded.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ let browser = tab.linkedBrowser;
+
+ // Load the page.
+ browser.loadURI(testURL);
+ yield promiseErrorPageLoaded(browser);
+
+ // Check that we loaded the right error page.
+ yield checkErrorPage(browser, errorURISuffix);
+
+ // Check that the error reporting section is hidden.
+ yield ContentTask.spawn(browser, null, function* () {
+ let section = content.document.getElementById("certificateErrorReporting");
+ Assert.equal(content.getComputedStyle(section).display, "none",
+ "error reporting section should be hidden");
+ });
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+}
+
+function isErrorStatus(status) {
+ return status < 200 || status >= 300;
+}
+
+// use the observer service to see when a report is sent
+function createReportResponseStatusPromise(expectedURI) {
+ return new Promise(resolve => {
+ let observer = (subject, topic, data) => {
+ subject.QueryInterface(Ci.nsIHttpChannel);
+ let requestURI = subject.URI.spec;
+ if (requestURI == expectedURI) {
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ resolve(subject.responseStatus);
+ }
+ };
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+ });
+}
+
+function checkErrorPage(browser, suffix) {
+ return ContentTask.spawn(browser, { suffix }, function* (args) {
+ let uri = content.document.documentURI;
+ Assert.ok(uri.startsWith(`about:${args.suffix}`), "correct error page loaded");
+ });
+}
diff --git a/browser/base/content/test/general/browser_star_hsts.js b/browser/base/content/test/general/browser_star_hsts.js
new file mode 100644
index 000000000..c52e563bc
--- /dev/null
+++ b/browser/base/content/test/general/browser_star_hsts.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var secureURL = "https://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs";
+var unsecureURL = "http://example.com/browser/browser/base/content/test/general/browser_star_hsts.sjs";
+
+add_task(function* test_star_redirect() {
+ registerCleanupFunction(function() {
+ // Ensure to remove example.com from the HSTS list.
+ let sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+ sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS,
+ NetUtil.newURI("http://example.com/"), 0);
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ gBrowser.removeCurrentTab();
+ });
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ // This will add the page to the HSTS cache.
+ yield promiseTabLoadEvent(tab, secureURL, secureURL);
+ // This should transparently be redirected to the secure page.
+ yield promiseTabLoadEvent(tab, unsecureURL, secureURL);
+
+ yield promiseStarState(BookmarkingUI.STATUS_UNSTARRED);
+
+ let promiseBookmark = promiseOnBookmarkItemAdded(gBrowser.currentURI);
+ BookmarkingUI.star.click();
+ // This resolves on the next tick, so the star should have already been
+ // updated at that point.
+ yield promiseBookmark;
+
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED, "The star is starred");
+});
+
+/**
+ * Waits for the star to reflect the expected state.
+ */
+function promiseStarState(aValue) {
+ let deferred = Promise.defer();
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ (function checkState() {
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(checkState, 1000);
+ } else {
+ deferred.resolve();
+ }
+ })();
+ return deferred.promise;
+}
+
+/**
+ * Starts a load in an existing tab and waits for it to finish (via some event).
+ *
+ * @param aTab
+ * The tab to load into.
+ * @param aUrl
+ * The url to load.
+ * @param [optional] aFinalURL
+ * The url to wait for, same as aURL if not defined.
+ * @return {Promise} resolved when the event is handled.
+ */
+function promiseTabLoadEvent(aTab, aURL, aFinalURL)
+{
+ if (!aFinalURL)
+ aFinalURL = aURL;
+ let deferred = Promise.defer();
+ info("Wait for load tab event");
+ aTab.linkedBrowser.addEventListener("load", function load(event) {
+ if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank" ||
+ event.target.location.href != aFinalURL) {
+ info("skipping spurious load event");
+ return;
+ }
+ aTab.linkedBrowser.removeEventListener("load", load, true);
+ info("Tab load event received");
+ deferred.resolve();
+ }, true, true);
+ aTab.linkedBrowser.loadURI(aURL);
+ return deferred.promise;
+}
diff --git a/browser/base/content/test/general/browser_star_hsts.sjs b/browser/base/content/test/general/browser_star_hsts.sjs
new file mode 100644
index 000000000..10c7aae12
--- /dev/null
+++ b/browser/base/content/test/general/browser_star_hsts.sjs
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function handleRequest(request, response)
+{
+ let page = "<!DOCTYPE html><html><body><p>HSTS page</p></body></html>";
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ response.setHeader("Strict-Transport-Security", "max-age=60");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Length", page.length + "", false);
+ response.write(page);
+}
diff --git a/browser/base/content/test/general/browser_subframe_favicons_not_used.js b/browser/base/content/test/general/browser_subframe_favicons_not_used.js
new file mode 100644
index 000000000..7efe78d9b
--- /dev/null
+++ b/browser/base/content/test/general/browser_subframe_favicons_not_used.js
@@ -0,0 +1,20 @@
+/* Make sure <link rel="..."> isn't respected in sub-frames. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let testPath = getRootDirectory(gTestPath);
+
+ let tab = gBrowser.addTab(testPath + "file_bug970276_popup1.html");
+
+ tab.linkedBrowser.addEventListener("load", function() {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let expectedIcon = testPath + "file_bug970276_favicon1.ico";
+ is(gBrowser.getIcon(tab), expectedIcon, "Correct icon.");
+
+ gBrowser.removeTab(tab);
+
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/general/browser_syncui.js b/browser/base/content/test/general/browser_syncui.js
new file mode 100644
index 000000000..daf0fa497
--- /dev/null
+++ b/browser/base/content/test/general/browser_syncui.js
@@ -0,0 +1,205 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
+var {Weave} = Cu.import("resource://services-sync/main.js", {});
+
+var stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://weave/locale/services/sync.properties");
+
+// ensure test output sees log messages.
+Log.repository.getLogger("browserwindow.syncui").addAppender(new Log.DumpAppender());
+
+// Send the specified sync-related notification and return a promise that
+// resolves once gSyncUI._promiseUpateUI is complete and the UI is ready to check.
+function notifyAndPromiseUIUpdated(topic) {
+ return new Promise(resolve => {
+ // Instrument gSyncUI so we know when the update is complete.
+ let oldPromiseUpdateUI = gSyncUI._promiseUpdateUI.bind(gSyncUI);
+ gSyncUI._promiseUpdateUI = function() {
+ return oldPromiseUpdateUI().then(() => {
+ // Restore our override.
+ gSyncUI._promiseUpdateUI = oldPromiseUpdateUI;
+ // Resolve the promise so the caller knows the update is done.
+ resolve();
+ });
+ };
+ // Now send the notification.
+ Services.obs.notifyObservers(null, topic, null);
+ });
+}
+
+// Sync manages 3 broadcasters so the menus correctly reflect the Sync state.
+// Only one of these 3 should ever be visible - pass the ID of the broadcaster
+// you expect to be visible and it will check it's the only one that is.
+function checkBroadcasterVisible(broadcasterId) {
+ let all = ["sync-reauth-state", "sync-setup-state", "sync-syncnow-state"];
+ Assert.ok(all.indexOf(broadcasterId) >= 0, "valid id");
+ for (let check of all) {
+ let eltHidden = document.getElementById(check).hidden;
+ Assert.equal(eltHidden, check == broadcasterId ? false : true, check);
+ }
+}
+
+function promiseObserver(topic) {
+ return new Promise(resolve => {
+ let obs = (aSubject, aTopic, aData) => {
+ Services.obs.removeObserver(obs, aTopic);
+ resolve(aSubject);
+ }
+ Services.obs.addObserver(obs, topic, false);
+ });
+}
+
+function checkButtonTooltips(stringPrefix) {
+ for (let butId of ["PanelUI-remotetabs-syncnow", "PanelUI-fxa-icon"]) {
+ let text = document.getElementById(butId).getAttribute("tooltiptext");
+ let desc = `Text is "${text}", expecting it to start with "${stringPrefix}"`
+ Assert.ok(text.startsWith(stringPrefix), desc);
+ }
+}
+
+add_task(function* prepare() {
+ // add the Sync button to the toolbar so we can get it!
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_NAVBAR);
+ registerCleanupFunction(() => {
+ CustomizableUI.removeWidgetFromArea("sync-button");
+ });
+
+ let xps = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ yield xps.whenLoaded();
+ // Put Sync and the UI into a known state.
+ Weave.Status.login = Weave.LOGIN_FAILED_NO_USERNAME;
+ yield notifyAndPromiseUIUpdated("weave:service:login:error");
+
+ checkBroadcasterVisible("sync-setup-state");
+ checkButtonTooltips("Sign In To Sync");
+ // mock out the "_needsSetup()" function so we don't short-circuit.
+ let oldNeedsSetup = window.gSyncUI._needsSetup;
+ window.gSyncUI._needsSetup = () => Promise.resolve(false);
+ registerCleanupFunction(() => {
+ window.gSyncUI._needsSetup = oldNeedsSetup;
+ // and an observer to set the state back to what it should be now we've
+ // restored the stub.
+ Services.obs.notifyObservers(null, "weave:service:login:finish", null);
+ });
+ // and a notification to have the state change away from "needs setup"
+ yield notifyAndPromiseUIUpdated("weave:service:login:finish");
+ checkBroadcasterVisible("sync-syncnow-state");
+ // open the sync-button panel so we can check elements in that.
+ document.getElementById("sync-button").click();
+});
+
+add_task(function* testSyncNeedsVerification() {
+ // mock out the "_needsVerification()" function
+ let oldNeedsVerification = window.gSyncUI._needsVerification;
+ window.gSyncUI._needsVerification = () => true;
+ try {
+ // a notification for the state change
+ yield notifyAndPromiseUIUpdated("weave:service:login:finish");
+ checkButtonTooltips("Verify");
+ } finally {
+ window.gSyncUI._needsVerification = oldNeedsVerification;
+ }
+});
+
+
+add_task(function* testSyncLoginError() {
+ checkBroadcasterVisible("sync-syncnow-state");
+
+ // Pretend we are in a "login failed" error state
+ Weave.Status.sync = Weave.LOGIN_FAILED;
+ Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED;
+ yield notifyAndPromiseUIUpdated("weave:ui:sync:error");
+
+ // But the menu *should* reflect the login error.
+ checkBroadcasterVisible("sync-reauth-state");
+ // The tooltips for the buttons should also reflect it.
+ checkButtonTooltips("Reconnect");
+
+ // Now pretend we just had a successful login - the error notification should go away.
+ Weave.Status.sync = Weave.STATUS_OK;
+ Weave.Status.login = Weave.LOGIN_SUCCEEDED;
+ yield notifyAndPromiseUIUpdated("weave:service:login:start");
+ yield notifyAndPromiseUIUpdated("weave:service:login:finish");
+ // The menus should be back to "all good"
+ checkBroadcasterVisible("sync-syncnow-state");
+});
+
+function checkButtonsStatus(shouldBeActive) {
+ for (let eid of [
+ "sync-status", // the broadcaster itself.
+ "sync-button", // the main sync button which observes the broadcaster
+ "PanelUI-fxa-icon", // the sync icon in the fxa footer that observes it.
+ ]) {
+ let elt = document.getElementById(eid);
+ if (shouldBeActive) {
+ Assert.equal(elt.getAttribute("syncstatus"), "active", `${eid} should be active`);
+ } else {
+ Assert.ok(!elt.hasAttribute("syncstatus"), `${eid} should have no status attr`);
+ }
+ }
+}
+
+function* testButtonActions(startNotification, endNotification, expectActive = true) {
+ checkButtonsStatus(false);
+ // pretend a sync is starting.
+ yield notifyAndPromiseUIUpdated(startNotification);
+ checkButtonsStatus(expectActive);
+ // and has stopped
+ yield notifyAndPromiseUIUpdated(endNotification);
+ checkButtonsStatus(false);
+}
+
+function *doTestButtonActivities() {
+ // logins do not "activate" the spinner/button as they may block and make
+ // the UI look like Sync is never completing.
+ yield testButtonActions("weave:service:login:start", "weave:service:login:finish", false);
+ yield testButtonActions("weave:service:login:start", "weave:service:login:error", false);
+
+ // But notifications for Sync itself should activate it.
+ yield testButtonActions("weave:service:sync:start", "weave:service:sync:finish");
+ yield testButtonActions("weave:service:sync:start", "weave:service:sync:error");
+
+ // and ensure the counters correctly handle multiple in-flight syncs
+ yield notifyAndPromiseUIUpdated("weave:service:sync:start");
+ checkButtonsStatus(true);
+ // sync stops.
+ yield notifyAndPromiseUIUpdated("weave:service:sync:finish");
+ // Button should not be active.
+ checkButtonsStatus(false);
+}
+
+add_task(function* testButtonActivitiesInNavBar() {
+ // check the button's functionality while the button is in the NavBar - which
+ // it already is.
+ yield doTestButtonActivities();
+});
+
+add_task(function* testFormatLastSyncDateNow() {
+ let now = new Date();
+ let nowString = gSyncUI.formatLastSyncDate(now);
+ Assert.equal(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: 'long', hour: 'numeric', minute: 'numeric'}));
+});
+
+add_task(function* testFormatLastSyncDateMonthAgo() {
+ let monthAgo = new Date();
+ monthAgo.setMonth(monthAgo.getMonth() - 1);
+ let monthAgoString = gSyncUI.formatLastSyncDate(monthAgo);
+ Assert.equal(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: 'long', day: 'numeric'}));
+});
+
+add_task(function* testButtonActivitiesInPanel() {
+ // check the button's functionality while the button is in the panel - it's
+ // currently in the NavBar - move it to the panel and open it.
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ yield PanelUI.show();
+ try {
+ yield doTestButtonActivities();
+ } finally {
+ PanelUI.hide();
+ }
+});
diff --git a/browser/base/content/test/general/browser_tabDrop.js b/browser/base/content/test/general/browser_tabDrop.js
new file mode 100644
index 000000000..fd743e6dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabDrop.js
@@ -0,0 +1,103 @@
+registerCleanupFunction(function* cleanup() {
+ while (gBrowser.tabs.length > 1) {
+ yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]);
+ }
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+});
+
+let originalEngine;
+add_task(function* test_setup() {
+ // Stop search-engine loads from hitting the network
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+});
+
+add_task(function*() { yield dropText("mochi.test/first", 1); });
+add_task(function*() { yield dropText("javascript:'bad'"); });
+add_task(function*() { yield dropText("jAvascript:'bad'"); });
+add_task(function*() { yield dropText("search this", 1); });
+add_task(function*() { yield dropText("mochi.test/second", 1); });
+add_task(function*() { yield dropText("data:text/html,bad"); });
+add_task(function*() { yield dropText("mochi.test/third", 1); });
+
+// Single text/plain item, with multiple links.
+add_task(function*() { yield dropText("mochi.test/1\nmochi.test/2", 2); });
+add_task(function*() { yield dropText("javascript:'bad1'\nmochi.test/3", 0); });
+add_task(function*() { yield dropText("mochi.test/4\ndata:text/html,bad1", 0); });
+
+// Multiple text/plain items, with single and multiple links.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/5"}],
+ [{type: "text/plain",
+ data: "mochi.test/6\nmochi.test/7"}]], 3);
+});
+
+// Single text/x-moz-url item, with multiple links.
+// "text/x-moz-url" has titles in even-numbered lines.
+add_task(function*() {
+ yield drop([[{type: "text/x-moz-url",
+ data: "mochi.test/8\nTITLE8\nmochi.test/9\nTITLE9"}]], 2);
+});
+
+// Single item with multiple types.
+add_task(function*() {
+ yield drop([[{type: "text/plain",
+ data: "mochi.test/10"},
+ {type: "text/x-moz-url",
+ data: "mochi.test/11\nTITLE11"}]], 1);
+});
+
+function dropText(text, expectedTabOpenCount=0) {
+ return drop([[{type: "text/plain", data: text}]], expectedTabOpenCount);
+}
+
+function* drop(dragData, expectedTabOpenCount=0) {
+ let dragDataString = JSON.stringify(dragData);
+ info(`Starting test for datagData:${dragDataString}; expectedTabOpenCount:${expectedTabOpenCount}`);
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
+ let actualTabOpenCount = 0;
+ let openedTabs = [];
+ let checkCount = function(event) {
+ openedTabs.push(event.target);
+ actualTabOpenCount++;
+ return actualTabOpenCount == expectedTabOpenCount;
+ };
+ let awaitTabOpen = expectedTabOpenCount && BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen", false, checkCount);
+ // A drop type of "link" onto an existing tab would normally trigger a
+ // load in that same tab, but tabbrowser code in _getDragTargetTab treats
+ // drops on the outer edges of a tab differently (loading a new tab
+ // instead). Make events created by synthesizeDrop have all of their
+ // coordinates set to 0 (screenX/screenY), so they're treated as drops
+ // on the outer edge of the tab, thus they open new tabs.
+ var event = {
+ clientX: 0,
+ clientY: 0,
+ screenX: 0,
+ screenY: 0,
+ };
+ EventUtils.synthesizeDrop(gBrowser.selectedTab, gBrowser.selectedTab, dragData, "link", window, undefined, event);
+ let tabsOpened = false;
+ if (awaitTabOpen) {
+ yield awaitTabOpen;
+ info("Got TabOpen event");
+ tabsOpened = true;
+ for (let tab of openedTabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ }
+ is(tabsOpened, !!expectedTabOpenCount, `Tabs for ${dragDataString} should only open if any of dropped items are valid`);
+
+ yield awaitDrop;
+ ok(true, "Got drop event");
+}
diff --git a/browser/base/content/test/general/browser_tabReorder.js b/browser/base/content/test/general/browser_tabReorder.js
new file mode 100644
index 000000000..9e0503e95
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabReorder.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ let initialTabsLength = gBrowser.tabs.length;
+
+ let newTab1 = gBrowser.selectedTab = gBrowser.addTab("about:robots", {skipAnimation: true});
+ let newTab2 = gBrowser.selectedTab = gBrowser.addTab("about:about", {skipAnimation: true});
+ let newTab3 = gBrowser.selectedTab = gBrowser.addTab("about:config", {skipAnimation: true});
+ registerCleanupFunction(function () {
+ while (gBrowser.tabs.length > initialTabsLength) {
+ gBrowser.removeTab(gBrowser.tabs[initialTabsLength]);
+ }
+ });
+
+ is(gBrowser.tabs.length, initialTabsLength + 3, "new tabs are opened");
+ is(gBrowser.tabs[initialTabsLength], newTab1, "newTab1 position is correct");
+ is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
+ is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ function dragAndDrop(tab1, tab2, copy) {
+ let rect = tab2.getBoundingClientRect();
+ let event = {
+ ctrlKey: copy,
+ altKey: copy,
+ clientX: rect.left + rect.width / 2 + 10,
+ clientY: rect.top + rect.height / 2,
+ };
+
+ EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
+ }
+
+ dragAndDrop(newTab1, newTab2, false);
+ is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there");
+ is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
+ is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
+ is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
+
+ dragAndDrop(newTab2, newTab1, true);
+ is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated");
+ is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 stays same place");
+ is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 stays same place");
+ is(gBrowser.tabs[initialTabsLength + 3], newTab3, "a new tab is inserted before newTab3");
+}
diff --git a/browser/base/content/test/general/browser_tab_close_dependent_window.js b/browser/base/content/test/general/browser_tab_close_dependent_window.js
new file mode 100644
index 000000000..ab8a960ac
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_close_dependent_window.js
@@ -0,0 +1,24 @@
+"use strict";
+
+add_task(function* closing_tab_with_dependents_should_close_window() {
+ info("Opening window");
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+
+ info("Opening tab with data URI");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, `data:text/html,<html%20onclick="W=window.open()"><body%20onbeforeunload="W.close()">`);
+ info("Closing original tab in this window.");
+ yield BrowserTestUtils.removeTab(win.gBrowser.tabs[0]);
+ info("Clicking into the window");
+ let depTabOpened = BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer, "TabOpen");
+ yield BrowserTestUtils.synthesizeMouse("html", 0, 0, {}, tab.linkedBrowser);
+
+ let openedTab = (yield depTabOpened).target;
+ info("Got opened tab");
+
+ let windowClosedPromise = BrowserTestUtils.windowClosed(win);
+ yield BrowserTestUtils.removeTab(tab);
+ is(Cu.isDeadWrapper(openedTab) || openedTab.linkedBrowser == null, true, "Opened tab should also have closed");
+ info("If we timeout now, the window failed to close - that shouldn't happen!");
+ yield windowClosedPromise;
+});
+
diff --git a/browser/base/content/test/general/browser_tab_detach_restore.js b/browser/base/content/test/general/browser_tab_detach_restore.js
new file mode 100644
index 000000000..d482edc26
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_detach_restore.js
@@ -0,0 +1,34 @@
+"use strict";
+
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+add_task(function*() {
+ let uri = "http://example.com/browser/browser/base/content/test/general/dummy_page.html";
+
+ // Clear out the closed windows set to start
+ while (SessionStore.getClosedWindowCount() > 0)
+ SessionStore.forgetClosedWindow(0);
+
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.loadURI(uri);
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+
+ let key = tab.linkedBrowser.permanentKey;
+ let win = gBrowser.replaceTabWithWindow(tab);
+ yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+
+ is(win.gBrowser.selectedBrowser.permanentKey, key, "Should have properly copied the permanentKey");
+ yield BrowserTestUtils.closeWindow(win);
+
+ is(SessionStore.getClosedWindowCount(), 1, "Should have restore data for the closed window");
+
+ win = SessionStore.undoCloseWindow(0);
+ yield BrowserTestUtils.waitForEvent(win, "load");
+ yield BrowserTestUtils.waitForEvent(win.gBrowser.tabs[0], "SSTabRestored");
+
+ is(win.gBrowser.tabs.length, 1, "Should have restored one tab");
+ is(win.gBrowser.selectedBrowser.currentURI.spec, uri, "Should have restored the right page");
+
+ yield promiseWindowClosed(win);
+});
diff --git a/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
new file mode 100644
index 000000000..a8fc34083
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(2);
+
+const EVENTUTILS_URL = "chrome://mochikit/content/tests/SimpleTest/EventUtils.js";
+var EventUtils = {};
+
+Services.scriptloader.loadSubScript(EVENTUTILS_URL, EventUtils);
+
+/**
+ * Tests that tabs from Private Browsing windows cannot be dragged
+ * into non-private windows, and vice-versa.
+ */
+add_task(function* test_dragging_private_windows() {
+ let normalWin = yield BrowserTestUtils.openNewBrowserWindow();
+ let privateWin =
+ yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+ let normalTab =
+ yield BrowserTestUtils.openNewForegroundTab(normalWin.gBrowser);
+ let privateTab =
+ yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser);
+
+ let effect = EventUtils.synthesizeDrop(normalTab, privateTab,
+ [[{type: TAB_DROP_TYPE, data: normalTab}]],
+ null, normalWin, privateWin);
+ is(effect, "none", "Should not be able to drag a normal tab to a private window");
+
+ effect = EventUtils.synthesizeDrop(privateTab, normalTab,
+ [[{type: TAB_DROP_TYPE, data: privateTab}]],
+ null, privateWin, normalWin);
+ is(effect, "none", "Should not be able to drag a private tab to a normal window");
+
+ normalWin.gBrowser.swapBrowsersAndCloseOther(normalTab, privateTab);
+ is(normalWin.gBrowser.tabs.length, 2,
+ "Prevent moving a normal tab to a private tabbrowser");
+ is(privateWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a normal tab in a private tabbrowser");
+
+ privateWin.gBrowser.swapBrowsersAndCloseOther(privateTab, normalTab);
+ is(privateWin.gBrowser.tabs.length, 2,
+ "Prevent moving a private tab to a normal tabbrowser");
+ is(normalWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a private tab in a normal tabbrowser");
+
+ yield BrowserTestUtils.closeWindow(normalWin);
+ yield BrowserTestUtils.closeWindow(privateWin);
+});
+
+/**
+ * Tests that tabs from e10s windows cannot be dragged into non-e10s
+ * windows, and vice-versa.
+ */
+add_task(function* test_dragging_e10s_windows() {
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ let remoteWin = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ let nonRemoteWin = yield BrowserTestUtils.openNewBrowserWindow({remote: false});
+
+ let remoteTab =
+ yield BrowserTestUtils.openNewForegroundTab(remoteWin.gBrowser);
+ let nonRemoteTab =
+ yield BrowserTestUtils.openNewForegroundTab(nonRemoteWin.gBrowser);
+
+ let effect = EventUtils.synthesizeDrop(remoteTab, nonRemoteTab,
+ [[{type: TAB_DROP_TYPE, data: remoteTab}]],
+ null, remoteWin, nonRemoteWin);
+ is(effect, "none", "Should not be able to drag a remote tab to a non-e10s window");
+
+ effect = EventUtils.synthesizeDrop(nonRemoteTab, remoteTab,
+ [[{type: TAB_DROP_TYPE, data: nonRemoteTab}]],
+ null, nonRemoteWin, remoteWin);
+ is(effect, "none", "Should not be able to drag a non-remote tab to an e10s window");
+
+ remoteWin.gBrowser.swapBrowsersAndCloseOther(remoteTab, nonRemoteTab);
+ is(remoteWin.gBrowser.tabs.length, 2,
+ "Prevent moving a normal tab to a private tabbrowser");
+ is(nonRemoteWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a normal tab in a private tabbrowser");
+
+ nonRemoteWin.gBrowser.swapBrowsersAndCloseOther(nonRemoteTab, remoteTab);
+ is(nonRemoteWin.gBrowser.tabs.length, 2,
+ "Prevent moving a private tab to a normal tabbrowser");
+ is(remoteWin.gBrowser.tabs.length, 2,
+ "Prevent accepting a private tab in a normal tabbrowser");
+
+ yield BrowserTestUtils.closeWindow(remoteWin);
+ yield BrowserTestUtils.closeWindow(nonRemoteWin);
+});
+
+/**
+ * Tests that remoteness-blacklisted tabs from e10s windows can
+ * be dragged between e10s windows.
+ */
+add_task(function* test_dragging_blacklisted() {
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ let remoteWin1 = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ remoteWin1.gBrowser.myID = "remoteWin1";
+ let remoteWin2 = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ remoteWin2.gBrowser.myID = "remoteWin2";
+
+ // Anything under chrome://mochitests/content/ will be blacklisted, and
+ // open in the parent process.
+ const BLACKLISTED_URL = getRootDirectory(gTestPath) +
+ "browser_tab_drag_drop_perwindow.js";
+ let blacklistedTab =
+ yield BrowserTestUtils.openNewForegroundTab(remoteWin1.gBrowser,
+ BLACKLISTED_URL);
+
+ ok(blacklistedTab.linkedBrowser, "Newly created tab should have a browser.");
+
+ ok(!blacklistedTab.linkedBrowser.isRemoteBrowser,
+ `Expected a non-remote browser for URL: ${BLACKLISTED_URL}`);
+
+ let otherTab =
+ yield BrowserTestUtils.openNewForegroundTab(remoteWin2.gBrowser);
+
+ let effect = EventUtils.synthesizeDrop(blacklistedTab, otherTab,
+ [[{type: TAB_DROP_TYPE, data: blacklistedTab}]],
+ null, remoteWin1, remoteWin2);
+ is(effect, "move", "Should be able to drag the blacklisted tab.");
+
+ // The synthesized drop should also do the work of swapping the
+ // browsers, so no need to call swapBrowsersAndCloseOther manually.
+
+ is(remoteWin1.gBrowser.tabs.length, 1,
+ "Should have moved the blacklisted tab out of this window.");
+ is(remoteWin2.gBrowser.tabs.length, 3,
+ "Should have inserted the blacklisted tab into the other window.");
+
+ // The currently selected tab in the second window should be the
+ // one we just dragged in.
+ let draggedBrowser = remoteWin2.gBrowser.selectedBrowser;
+ ok(!draggedBrowser.isRemoteBrowser,
+ "The browser we just dragged in should not be remote.");
+
+ is(draggedBrowser.currentURI.spec, BLACKLISTED_URL,
+ `Expected the URL of the dragged in tab to be ${BLACKLISTED_URL}`);
+
+ yield BrowserTestUtils.closeWindow(remoteWin1);
+ yield BrowserTestUtils.closeWindow(remoteWin2);
+});
+
+
+/**
+ * Tests that tabs dragged between windows dispatch TabOpen and TabClose
+ * events with the appropriate adoption details.
+ */
+add_task(function* test_dragging_adoption_events() {
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser);
+
+ let awaitCloseEvent = BrowserTestUtils.waitForEvent(tab1, "TabClose");
+ let awaitOpenEvent = BrowserTestUtils.waitForEvent(win2, "TabOpen");
+
+ let effect = EventUtils.synthesizeDrop(tab1, tab2,
+ [[{type: TAB_DROP_TYPE, data: tab1}]],
+ null, win1, win2);
+ is(effect, "move", "Tab should be moved from win1 to win2.");
+
+ let closeEvent = yield awaitCloseEvent;
+ let openEvent = yield awaitOpenEvent;
+
+ is(openEvent.detail.adoptedTab, tab1, "New tab adopted old tab");
+ is(closeEvent.detail.adoptedBy, openEvent.target, "Old tab adopted by new tab");
+
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+});
+
+
+/**
+ * Tests that per-site zoom settings remain active after a tab is
+ * dragged between windows.
+ */
+add_task(function* test_dragging_zoom_handling() {
+ const ZOOM_FACTOR = 1.62;
+
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(win2.gBrowser,
+ "http://example.com/");
+
+ win2.FullZoom.setZoom(ZOOM_FACTOR);
+ FullZoomHelper.zoomTest(tab2, ZOOM_FACTOR,
+ "Original tab should have correct zoom factor");
+
+ let effect = EventUtils.synthesizeDrop(tab2, tab1,
+ [[{type: TAB_DROP_TYPE, data: tab2}]],
+ null, win2, win1);
+ is(effect, "move", "Tab should be moved from win2 to win1.");
+
+ // Delay slightly to make sure we've finished executing any promise
+ // chains in the zoom code.
+ yield new Promise(resolve => setTimeout(resolve, 0));
+
+ FullZoomHelper.zoomTest(win1.gBrowser.selectedTab, ZOOM_FACTOR,
+ "Dragged tab should have correct zoom factor");
+
+ win1.FullZoom.reset();
+
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+});
diff --git a/browser/base/content/test/general/browser_tab_dragdrop.js b/browser/base/content/test/general/browser_tab_dragdrop.js
new file mode 100644
index 000000000..cfe996e1e
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_dragdrop.js
@@ -0,0 +1,186 @@
+function swapTabsAndCloseOther(a, b) {
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
+}
+
+var getClicks = function(tab) {
+ return ContentTask.spawn(tab.linkedBrowser, {}, function() {
+ return content.wrappedJSObject.clicks;
+ });
+}
+
+var clickTest = Task.async(function*(tab) {
+ let clicks = yield getClicks(tab);
+
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function() {
+ let target = content.document.body;
+ let rect = target.getBoundingClientRect();
+ let left = (rect.left + rect.right) / 2;
+ let top = (rect.top + rect.bottom) / 2;
+
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let newClicks = yield getClicks(tab);
+ is(newClicks, clicks + 1, "adding 1 more click on BODY");
+});
+
+function loadURI(tab, url) {
+ tab.linkedBrowser.loadURI(url);
+ return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+}
+
+// Creates a framescript which caches the current object value from the plugin
+// in the page. checkObjectValue below verifies that the framescript is still
+// active for the browser and that the cached value matches that from the plugin
+// in the page which tells us the plugin hasn't been reinitialized.
+function* cacheObjectValue(browser) {
+ yield ContentTask.spawn(browser, null, function*() {
+ let plugin = content.document.wrappedJSObject.body.firstChild;
+ info(`plugin is ${plugin}`);
+ let win = content.document.defaultView;
+ info(`win is ${win}`);
+ win.objectValue = plugin.getObjectValue();
+ info(`got objectValue: ${win.objectValue}`);
+ win.checkObjectValueListener = () => {
+ let result;
+ let exception;
+ try {
+ result = plugin.checkObjectValue(win.objectValue);
+ } catch (e) {
+ exception = e.toString();
+ }
+ info(`sending plugin.checkObjectValue(objectValue): ${result}`);
+ sendAsyncMessage("Test:CheckObjectValueResult", {
+ result,
+ exception
+ });
+ };
+
+ addMessageListener("Test:CheckObjectValue", win.checkObjectValueListener);
+ });
+}
+
+// Note, can't run this via registerCleanupFunction because it needs the
+// browser to still be alive and have a messageManager.
+function* cleanupObjectValue(browser) {
+ info("entered cleanupObjectValue")
+ yield ContentTask.spawn(browser, null, function*() {
+ info("in cleanup function");
+ let win = content.document.defaultView;
+ info(`about to delete objectValue: ${win.objectValue}`);
+ delete win.objectValue;
+ removeMessageListener("Test:CheckObjectValue", win.checkObjectValueListener);
+ info(`about to delete checkObjectValueListener: ${win.checkObjectValueListener}`);
+ delete win.checkObjectValueListener;
+ info(`deleted objectValue (${win.objectValue}) and checkObjectValueListener (${win.checkObjectValueListener})`);
+ });
+ info("exiting cleanupObjectValue")
+}
+
+// See the notes for cacheObjectValue above.
+function checkObjectValue(browser) {
+ let mm = browser.messageManager;
+
+ return new Promise((resolve, reject) => {
+ let listener = ({ data }) => {
+ mm.removeMessageListener("Test:CheckObjectValueResult", listener);
+ if (data.result === null) {
+ ok(false, "checkObjectValue threw an exception: " + data.exception);
+ reject(data.exception);
+ } else {
+ resolve(data.result);
+ }
+ };
+
+ mm.addMessageListener("Test:CheckObjectValueResult", listener);
+ mm.sendAsyncMessage("Test:CheckObjectValue");
+ });
+}
+
+add_task(function*() {
+ let embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ // create a few tabs
+ let tabs = [
+ gBrowser.tabs[0],
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true})
+ ];
+
+ // Initially 0 1 2 3 4
+ yield loadURI(tabs[1], "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
+ yield loadURI(tabs[2], "data:text/plain;charset=utf-8,tab2");
+ yield loadURI(tabs[3], "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
+ yield loadURI(tabs[4], "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
+ yield BrowserTestUtils.switchTab(gBrowser, tabs[3]);
+
+ swapTabsAndCloseOther(2, 3); // now: 0 1 2 4
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ is(gBrowser.tabs[2], tabs[3], "tab3");
+ is(gBrowser.tabs[3], tabs[4], "tab4");
+ delete tabs[2];
+
+ info("about to cacheObjectValue")
+ yield cacheObjectValue(tabs[4].linkedBrowser);
+ info("just finished cacheObjectValue")
+
+ swapTabsAndCloseOther(3, 2); // now: 0 1 4
+ is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 2,
+ "The third tab should be selected");
+ delete tabs[4];
+
+
+ ok((yield checkObjectValue(gBrowser.tabs[2].linkedBrowser)), "same plugin instance");
+
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ is(gBrowser.tabs[2], tabs[3], "tab4");
+
+ let clicks = yield getClicks(gBrowser.tabs[2]);
+ is(clicks, 0, "no click on BODY so far");
+ yield clickTest(gBrowser.tabs[2]);
+
+ swapTabsAndCloseOther(2, 1); // now: 0 4
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ delete tabs[3];
+
+ ok((yield checkObjectValue(gBrowser.tabs[1].linkedBrowser)), "same plugin instance");
+ yield cleanupObjectValue(gBrowser.tabs[1].linkedBrowser);
+
+ yield clickTest(gBrowser.tabs[1]);
+
+ // Load a new document (about:blank) in tab4, then detach that tab into a new window.
+ // In the new window, navigate back to the original document and click on its <body>,
+ // verify that its onclick was called.
+ is(Array.prototype.indexOf.call(gBrowser.tabs, gBrowser.selectedTab), 1,
+ "The second tab should be selected");
+ is(gBrowser.tabs[1], tabs[1],
+ "The second tab in gBrowser.tabs should be equal to the second tab in our array");
+ is(gBrowser.selectedTab, tabs[1],
+ "The second tab in our array is the selected tab");
+ yield loadURI(tabs[1], "about:blank");
+ let key = tabs[1].linkedBrowser.permanentKey;
+
+ let win = gBrowser.replaceTabWithWindow(tabs[1]);
+ yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+ delete tabs[1];
+
+ // Verify that the original window now only has the initial tab left in it.
+ is(gBrowser.tabs[0], tabs[0], "tab0");
+ is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:blank", "tab0 uri");
+
+ let tab = win.gBrowser.tabs[0];
+ is(tab.linkedBrowser.permanentKey, key, "Should have kept the key");
+
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "pageshow");
+ win.gBrowser.goBack();
+ yield awaitPageShow;
+
+ yield clickTest(tab);
+ promiseWindowClosed(win);
+});
diff --git a/browser/base/content/test/general/browser_tab_dragdrop2.js b/browser/base/content/test/general/browser_tab_dragdrop2.js
new file mode 100644
index 000000000..2ab622d8b
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_dragdrop2.js
@@ -0,0 +1,57 @@
+"use strict";
+
+const ROOT = getRootDirectory(gTestPath);
+const URI = ROOT + "browser_tab_dragdrop2_frame1.xul";
+
+// Load the test page (which runs some child popup tests) in a new window.
+// After the tests were run, tear off the tab into a new window and run popup
+// tests a second time. We don't care about tests results, exceptions and
+// crashes will be caught.
+add_task(function* () {
+ // Open a new window.
+ let args = "chrome,all,dialog=no";
+ let win = window.openDialog(getBrowserURL(), "_blank", args, URI);
+
+ // Wait until the tests were run.
+ yield promiseTestsDone(win);
+ ok(true, "tests succeeded");
+
+ // Create a second tab so that we can move the original one out.
+ win.gBrowser.addTab("about:blank", {skipAnimation: true});
+
+ // Tear off the original tab.
+ let browser = win.gBrowser.selectedBrowser;
+ let tabClosed = promiseWaitForEvent(browser, "pagehide", true);
+ let win2 = win.gBrowser.replaceTabWithWindow(win.gBrowser.tabs[0]);
+
+ // Add a 'TestsDone' event listener to ensure that the docShells is properly
+ // swapped to the new window instead of the page being loaded again. If this
+ // works fine we should *NOT* see a TestsDone event.
+ let onTestsDone = () => ok(false, "shouldn't run tests when tearing off");
+ win2.addEventListener("TestsDone", onTestsDone);
+
+ // Wait until the original tab is gone and the new window is ready.
+ yield Promise.all([tabClosed, promiseDelayedStartupFinished(win2)]);
+
+ // Remove the 'TestsDone' event listener as now
+ // we're kicking off a new test run manually.
+ win2.removeEventListener("TestsDone", onTestsDone);
+
+ // Run tests once again.
+ let promise = promiseTestsDone(win2);
+ win2.content.test_panels();
+ yield promise;
+ ok(true, "tests succeeded a second time");
+
+ // Cleanup.
+ yield promiseWindowClosed(win2);
+ yield promiseWindowClosed(win);
+});
+
+function promiseTestsDone(win) {
+ return promiseWaitForEvent(win, "TestsDone");
+}
+
+function promiseDelayedStartupFinished(win) {
+ return new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+}
diff --git a/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul
new file mode 100644
index 000000000..d11709942
--- /dev/null
+++ b/browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul
@@ -0,0 +1,169 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ XUL Widget Test for panels
+ -->
+<window title="Titlebar" width="200" height="200"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+<tree id="tree" seltype="single" width="100" height="100">
+ <treecols>
+ <treecol flex="1"/>
+ <treecol flex="1"/>
+ </treecols>
+ <treechildren id="treechildren">
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ </treechildren>
+</tree>
+
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var currentTest = null;
+
+var i, waitSteps;
+var my_debug = false;
+function test_panels()
+{
+ i = waitSteps = 0;
+ checkTreeCoords();
+
+ addEventListener("popupshown", popupShown, false);
+ addEventListener("popuphidden", nextTest, false);
+ return nextTest();
+}
+
+function nextTest()
+{
+ ok(true,"popuphidden " + i)
+ if (i == tests.length) {
+ let details = {bubbles: true, cancelable: false};
+ document.dispatchEvent(new CustomEvent("TestsDone", details));
+ return i;
+ }
+
+ currentTest = tests[i];
+ var panel = createPanel(currentTest.attrs);
+ SimpleTest.waitForFocus(() => currentTest.test(panel));
+ return i;
+}
+
+function popupShown(event)
+{
+ var panel = event.target;
+ if (waitSteps > 0 && navigator.platform.indexOf("Linux") >= 0 &&
+ panel.boxObject.screenY == 210) {
+ waitSteps--;
+ setTimeout(popupShown, 10, event);
+ return;
+ }
+ ++i;
+
+ currentTest.result(currentTest.testname + " ", panel);
+ panel.hidePopup();
+}
+
+function createPanel(attrs)
+{
+ var panel = document.createElement("panel");
+ for (var a in attrs) {
+ panel.setAttribute(a, attrs[a]);
+ }
+
+ var button = document.createElement("button");
+ panel.appendChild(button);
+ button.label = "OK";
+ button.width = 120;
+ button.height = 40;
+ button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
+ panel.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
+ return document.documentElement.appendChild(panel);
+}
+
+function checkTreeCoords()
+{
+ var tree = $("tree");
+ var treechildren = $("treechildren");
+ tree.currentIndex = 0;
+ tree.treeBoxObject.scrollToRow(0);
+ synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { });
+
+ tree.treeBoxObject.scrollToRow(2);
+ synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { });
+}
+
+var tests = [
+ {
+ testname: "normal panel",
+ attrs: { },
+ test: function(panel) {
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+ }
+ },
+ {
+ // only noautohide panels support titlebars, so one shouldn't be shown here
+ testname: "autohide panel with titlebar",
+ attrs: { titlebar: "normal" },
+ test: function(panel) {
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+ }
+ },
+ {
+ testname: "noautohide panel with titlebar",
+ attrs: { noautohide: true, titlebar: "normal" },
+ test: function(panel) {
+ waitSteps = 25;
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+
+ var gotMouseEvent = false;
+ function mouseMoved(event)
+ {
+ gotMouseEvent = true;
+ }
+
+ panel.addEventListener("mousemove", mouseMoved, true);
+ synthesizeMouse(panel, 10, 10, { type: "mousemove" });
+ panel.removeEventListener("mousemove", mouseMoved, true);
+
+ var tree = $("tree");
+ tree.currentIndex = 0;
+ panel.appendChild(tree);
+ checkTreeCoords();
+ }
+ }
+];
+
+SimpleTest.waitForFocus(test_panels);
+
+]]>
+</script>
+
+</window>
diff --git a/browser/base/content/test/general/browser_tabbar_big_widgets.js b/browser/base/content/test/general/browser_tabbar_big_widgets.js
new file mode 100644
index 000000000..7a4c45138
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabbar_big_widgets.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const kButtonId = "test-tabbar-size-with-large-buttons";
+
+function test() {
+ registerCleanupFunction(cleanup);
+ let titlebar = document.getElementById("titlebar");
+ let originalHeight = titlebar.getBoundingClientRect().height;
+ let button = document.createElement("toolbarbutton");
+ button.id = kButtonId;
+ button.setAttribute("style", "min-height: 100px");
+ gNavToolbox.palette.appendChild(button);
+ CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_TABSTRIP);
+ let currentHeight = titlebar.getBoundingClientRect().height;
+ ok(currentHeight > originalHeight, "Titlebar should have grown");
+ CustomizableUI.removeWidgetFromArea(kButtonId);
+ currentHeight = titlebar.getBoundingClientRect().height;
+ is(currentHeight, originalHeight, "Titlebar should have gone back to its original size.");
+}
+
+function cleanup() {
+ let btn = document.getElementById(kButtonId);
+ if (btn) {
+ btn.remove();
+ }
+}
+
diff --git a/browser/base/content/test/general/browser_tabfocus.js b/browser/base/content/test/general/browser_tabfocus.js
new file mode 100644
index 000000000..4042421e8
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabfocus.js
@@ -0,0 +1,565 @@
+/*
+ * This test checks that focus is adjusted properly when switching tabs.
+ */
+
+var testPage1 = "<html id='html1'><body id='body1'><button id='button1'>Tab 1</button></body></html>";
+var testPage2 = "<html id='html2'><body id='body2'><button id='button2'>Tab 2</button></body></html>";
+var testPage3 = "<html id='html3'><body id='body3'><button id='button3'>Tab 3</button></body></html>";
+
+const fm = Services.focus;
+
+function EventStore() {
+ this["main-window"] = [];
+ this["window1"] = [];
+ this["window2"] = [];
+}
+
+EventStore.prototype = {
+ "push": function (event) {
+ if (event.indexOf("1") > -1) {
+ this["window1"].push(event);
+ } else if (event.indexOf("2") > -1) {
+ this["window2"].push(event);
+ } else {
+ this["main-window"].push(event);
+ }
+ }
+}
+
+var tab1 = null;
+var tab2 = null;
+var browser1 = null;
+var browser2 = null;
+var _lastfocus;
+var _lastfocuswindow = null;
+var actualEvents = new EventStore();
+var expectedEvents = new EventStore();
+var currentTestName = "";
+var _expectedElement = null;
+var _expectedWindow = null;
+
+var currentPromiseResolver = null;
+
+function* getFocusedElementForBrowser(browser, dontCheckExtraFocus = false)
+{
+ if (gMultiProcessBrowser) {
+ return new Promise((resolve, reject) => {
+ messageManager.addMessageListener("Browser:GetCurrentFocus", function getCurrentFocus(message) {
+ messageManager.removeMessageListener("Browser:GetCurrentFocus", getCurrentFocus);
+ resolve(message.data.details);
+ });
+
+ // The dontCheckExtraFocus flag is used to indicate not to check some
+ // additional focus related properties. This is needed as both URLs are
+ // loaded using the same child process and share focus managers.
+ browser.messageManager.sendAsyncMessage("Browser:GetFocusedElement",
+ { dontCheckExtraFocus : dontCheckExtraFocus });
+ });
+ }
+ var focusedWindow = {};
+ var node = fm.getFocusedElementForWindow(browser.contentWindow, false, focusedWindow);
+ return "Focus is " + (node ? node.id : "<none>");
+}
+
+function focusInChild()
+{
+ var contentFM = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+
+ function getWindowDocId(target)
+ {
+ return (String(target.location).indexOf("1") >= 0) ? "window1" : "window2";
+ }
+
+ function eventListener(event) {
+ var id;
+ if (event.target instanceof Components.interfaces.nsIDOMWindow)
+ id = getWindowDocId(event.originalTarget) + "-window";
+ else if (event.target instanceof Components.interfaces.nsIDOMDocument)
+ id = getWindowDocId(event.originalTarget) + "-document";
+ else
+ id = event.originalTarget.id;
+ sendSyncMessage("Browser:FocusChanged", { details : event.type + ": " + id });
+ }
+
+ addEventListener("focus", eventListener, true);
+ addEventListener("blur", eventListener, true);
+
+ addMessageListener("Browser:ChangeFocus", function changeFocus(message) {
+ content.document.getElementById(message.data.id)[message.data.type]();
+ });
+
+ addMessageListener("Browser:GetFocusedElement", function getFocusedElement(message) {
+ var focusedWindow = {};
+ var node = contentFM.getFocusedElementForWindow(content, false, focusedWindow);
+ var details = "Focus is " + (node ? node.id : "<none>");
+
+ /* Check focus manager properties. Add an error onto the string if they are
+ not what is expected which will cause matching to fail in the parent process. */
+ let doc = content.document;
+ if (!message.data.dontCheckExtraFocus) {
+ if (contentFM.focusedElement != node) {
+ details += "<ERROR: focusedElement doesn't match>";
+ }
+ if (contentFM.focusedWindow && contentFM.focusedWindow != content) {
+ details += "<ERROR: focusedWindow doesn't match>";
+ }
+ if ((contentFM.focusedWindow == content) != doc.hasFocus()) {
+ details += "<ERROR: child hasFocus() is not correct>";
+ }
+ if ((contentFM.focusedElement && doc.activeElement != contentFM.focusedElement) ||
+ (!contentFM.focusedElement && doc.activeElement != doc.body)) {
+ details += "<ERROR: child activeElement is not correct>";
+ }
+ }
+
+ sendSyncMessage("Browser:GetCurrentFocus", { details : details });
+ });
+}
+
+function focusElementInChild(elementid, type)
+{
+ let browser = (elementid.indexOf("1") >= 0) ? browser1 : browser2;
+ if (gMultiProcessBrowser) {
+ browser.messageManager.sendAsyncMessage("Browser:ChangeFocus",
+ { id: elementid, type: type });
+ }
+ else {
+ browser.contentDocument.getElementById(elementid)[type]();
+ }
+}
+
+add_task(function*() {
+ tab1 = gBrowser.addTab();
+ browser1 = gBrowser.getBrowserForTab(tab1);
+
+ tab2 = gBrowser.addTab();
+ browser2 = gBrowser.getBrowserForTab(tab2);
+
+ yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage1));
+ yield promiseTabLoadEvent(tab2, "data:text/html," + escape(testPage2));
+
+ var childFocusScript = "data:,(" + focusInChild.toString() + ")();";
+ browser1.messageManager.loadFrameScript(childFocusScript, true);
+ browser2.messageManager.loadFrameScript(childFocusScript, true);
+
+ gURLBar.focus();
+ yield SimpleTest.promiseFocus();
+
+ if (gMultiProcessBrowser) {
+ messageManager.addMessageListener("Browser:FocusChanged", message => {
+ actualEvents.push(message.data.details);
+ compareFocusResults();
+ });
+ }
+
+ _lastfocus = "urlbar";
+ _lastfocuswindow = "main-window";
+
+ window.addEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.addEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ // make sure that the focus initially starts out blank
+ var focusedWindow = {};
+
+ let focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is <none>", "initial focus in tab 1");
+
+ focused = yield getFocusedElementForBrowser(browser2);
+ is(focused, "Focus is <none>", "initial focus in tab 2");
+
+ is(document.activeElement, gURLBar.inputField, "focus after loading two tabs");
+
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", null, true,
+ "after tab change, focus in new tab");
+
+ focused = yield getFocusedElementForBrowser(browser2);
+ is(focused, "Focus is <none>", "focusedElement after tab change, focus in new tab");
+
+ // switching tabs when nothing in the new tab is focused
+ // should focus the browser
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true,
+ "after tab change, focus in original tab");
+
+ focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is <none>", "focusedElement after tab change, focus in original tab");
+
+ // focusing a button in the current tab should focus it
+ yield expectFocusShift(() => focusElementInChild("button1", "focus"),
+ "window1", "button1", true,
+ "after button focused");
+
+ focused = yield getFocusedElementForBrowser(browser1);
+ is(focused, "Focus is button1", "focusedElement in first browser after button focused");
+
+ // focusing a button in a background tab should not change the actual
+ // focus, but should set the focus that would be in that background tab to
+ // that button.
+ yield expectFocusShift(() => focusElementInChild("button2", "focus"),
+ "window1", "button1", false,
+ "after button focus in unfocused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is button1", "focusedElement in first browser after button focus in unfocused tab");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement in second browser after button focus in unfocused tab");
+
+ // switching tabs should now make the button in the other tab focused
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true,
+ "after tab change with button focused");
+
+ // blurring an element in a background tab should not change the active
+ // focus, but should clear the focus in that tab.
+ yield expectFocusShift(() => focusElementInChild("button1", "blur"),
+ "window2", "button2", false,
+ "focusedWindow after blur in unfocused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, true);
+ is(focused, "Focus is <none>", "focusedElement in first browser after focus in unfocused tab");
+ focused = yield getFocusedElementForBrowser(browser2, false);
+ is(focused, "Focus is button2", "focusedElement in second browser after focus in unfocused tab");
+
+ // When focus is in the tab bar, it should be retained there
+ yield expectFocusShift(() => gBrowser.selectedTab.focus(),
+ "main-window", "tab2", true,
+ "focusing tab element");
+ yield* expectFocusShiftAfterTabSwitch(tab1, "main-window", "tab1", true,
+ "tab change when selected tab element was focused");
+
+ let switchWaiter;
+ if (gMultiProcessBrowser) {
+ switchWaiter = new Promise((resolve, reject) => {
+ gBrowser.addEventListener("TabSwitchDone", function listener() {
+ gBrowser.removeEventListener("TabSwitchDone", listener);
+ executeSoon(resolve);
+ });
+ });
+ }
+
+ yield* expectFocusShiftAfterTabSwitch(tab2, "main-window", "tab2", true,
+ "another tab change when selected tab element was focused");
+
+ // When this a remote browser, wait for the paint on the second browser so that
+ // any post tab-switching stuff has time to complete before blurring the tab.
+ // Otherwise, the _adjustFocusAfterTabSwitch in tabbrowser gets confused and
+ // isn't sure what tab is really focused.
+ if (gMultiProcessBrowser) {
+ yield switchWaiter;
+ }
+
+ yield expectFocusShift(() => gBrowser.selectedTab.blur(),
+ "main-window", null, true,
+ "blurring tab element");
+
+ // focusing the url field should switch active focus away from the browser but
+ // not clear what would be the focus in the browser
+ focusElementInChild("button1", "focus");
+
+ yield expectFocusShift(() => gURLBar.focus(),
+ "main-window", "urlbar", true,
+ "focusedWindow after url field focused");
+ focused = yield getFocusedElementForBrowser(browser1, true);
+ is(focused, "Focus is button1", "focusedElement after url field focused, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement after url field focused, second browser");
+
+ yield expectFocusShift(() => gURLBar.blur(),
+ "main-window", null, true,
+ "blurring url field");
+
+ // when a chrome element is focused, switching tabs to a tab with a button
+ // with the current focus should focus the button
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", "button1", true,
+ "after tab change, focus in url field, button focused in new tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is button1", "after switch tab, focus in unfocused tab, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "after switch tab, focus in unfocused tab, second browser");
+
+ // blurring an element in the current tab should clear the active focus
+ yield expectFocusShift(() => focusElementInChild("button1", "blur"),
+ "window1", null, true,
+ "after blur in focused tab");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is <none>", "focusedWindow after blur in focused tab, child");
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in focused tab, parent");
+
+ // blurring an non-focused url field should have no effect
+ yield expectFocusShift(() => gURLBar.blur(),
+ "window1", null, false,
+ "after blur in unfocused url field");
+
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), browser1, "focusedElement after blur in unfocused url field");
+
+ // switch focus to a tab with a currently focused element
+ yield* expectFocusShiftAfterTabSwitch(tab2, "window2", "button2", true,
+ "after switch from unfocused to focused tab");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "focusedElement after switch from unfocused to focused tab");
+
+ // clearing focus on the chrome window should switch the focus to the
+ // chrome window
+ yield expectFocusShift(() => fm.clearFocus(window),
+ "main-window", null, true,
+ "after switch to chrome with no focused element");
+
+ focusedWindow = {};
+ is(fm.getFocusedElementForWindow(window, false, focusedWindow), null, "focusedElement after switch to chrome with no focused element");
+
+ // switch focus to another tab when neither have an active focus
+ yield* expectFocusShiftAfterTabSwitch(tab1, "window1", null, true,
+ "focusedWindow after tab switch from no focus to no focus");
+
+ focused = yield getFocusedElementForBrowser(browser1, false);
+ is(focused, "Focus is <none>", "after tab switch from no focus to no focus, first browser");
+ focused = yield getFocusedElementForBrowser(browser2, true);
+ is(focused, "Focus is button2", "after tab switch from no focus to no focus, second browser");
+
+ // next, check whether navigating forward, focusing the urlbar and then
+ // navigating back maintains the focus in the urlbar.
+ yield expectFocusShift(() => focusElementInChild("button1", "focus"),
+ "window1", "button1", true,
+ "focus button");
+
+ yield promiseTabLoadEvent(tab1, "data:text/html," + escape(testPage3));
+
+ // now go back again
+ gURLBar.focus();
+
+ yield new Promise((resolve, reject) => {
+ window.addEventListener("pageshow", function navigationOccured(event) {
+ window.removeEventListener("pageshow", navigationOccured, true);
+ resolve();
+ }, true);
+ document.getElementById('Browser:Back').doCommand();
+ });
+
+ is(window.document.activeElement, gURLBar.inputField, "urlbar still focused after navigating back");
+
+ // Document navigation with F6 does not yet work in mutli-process browsers.
+ if (!gMultiProcessBrowser) {
+ gURLBar.focus();
+ actualEvents = new EventStore();
+ _lastfocus = "urlbar";
+ _lastfocuswindow = "main-window";
+
+ yield expectFocusShift(() => EventUtils.synthesizeKey("VK_F6", { }),
+ "window1", "html1",
+ true, "switch document forward with f6");
+
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6");
+
+ browser1.style.MozUserFocus = "ignore";
+ browser1.clientWidth;
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6 when browser non-focusable");
+
+ browser1.style.MozUserFocus = "normal";
+ browser1.clientWidth;
+ }
+
+ window.removeEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.removeEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+
+ finish();
+});
+
+function _browser_tabfocus_test_eventOccured(event)
+{
+ function getWindowDocId(target)
+ {
+ if (target == browser1.contentWindow || target == browser1.contentDocument) {
+ return "window1";
+ }
+ if (target == browser2.contentWindow || target == browser2.contentDocument) {
+ return "window2";
+ }
+ return "main-window";
+ }
+
+ var id;
+
+ // Some focus events from the child bubble up? Ignore them.
+ if (Cu.isCrossProcessWrapper(event.originalTarget))
+ return;
+
+ if (event.target instanceof Window)
+ id = getWindowDocId(event.originalTarget) + "-window";
+ else if (event.target instanceof Document)
+ id = getWindowDocId(event.originalTarget) + "-document";
+ else if (event.target.id == "urlbar" && event.originalTarget.localName == "input")
+ id = "urlbar";
+ else if (event.originalTarget.localName == "browser")
+ id = (event.originalTarget == browser1) ? "browser1" : "browser2";
+ else if (event.originalTarget.localName == "tab")
+ id = (event.originalTarget == tab1) ? "tab1" : "tab2";
+ else
+ id = event.originalTarget.id;
+
+ actualEvents.push(event.type + ": " + id);
+ compareFocusResults();
+}
+
+function getId(element)
+{
+ if (!element) {
+ return null;
+ }
+
+ if (element.localName == "browser") {
+ return element == browser1 ? "browser1" : "browser2";
+ }
+
+ if (element.localName == "tab") {
+ return element == tab1 ? "tab1" : "tab2";
+ }
+
+ return (element.localName == "input") ? "urlbar" : element.id;
+}
+
+function compareFocusResults()
+{
+ if (!currentPromiseResolver)
+ return;
+
+ let winIds = ["main-window", "window1", "window2"];
+
+ for (let winId of winIds) {
+ if (actualEvents[winId].length < expectedEvents[winId].length)
+ return;
+ }
+
+ for (let winId of winIds) {
+ for (let e = 0; e < expectedEvents.length; e++) {
+ is(actualEvents[winId][e], expectedEvents[winId][e], currentTestName + " events [event " + e + "]");
+ }
+ actualEvents[winId] = [];
+ }
+
+ // Use executeSoon as this will be called during a focus/blur event handler
+ executeSoon(() => {
+ let matchWindow = window;
+ if (gMultiProcessBrowser) {
+ is(_expectedWindow, "main-window", "main-window is always expected");
+ }
+ else if (_expectedWindow != "main-window") {
+ matchWindow = (_expectedWindow == "window1" ? browser1.contentWindow : browser2.contentWindow);
+ }
+
+ var focusedElement = fm.focusedElement;
+ is(getId(focusedElement), _expectedElement, currentTestName + " focusedElement");
+ is(fm.focusedWindow, matchWindow, currentTestName + " focusedWindow");
+ var focusedWindow = {};
+ is(getId(fm.getFocusedElementForWindow(matchWindow, false, focusedWindow)),
+ _expectedElement, currentTestName + " getFocusedElementForWindow");
+ is(focusedWindow.value, matchWindow, currentTestName + " getFocusedElementForWindow frame");
+ is(matchWindow.document.hasFocus(), true, currentTestName + " hasFocus");
+ var expectedActive = _expectedElement;
+ if (!expectedActive) {
+ expectedActive = matchWindow.document instanceof XULDocument ?
+ "main-window" : getId(matchWindow.document.body);
+ }
+ is(getId(matchWindow.document.activeElement), expectedActive, currentTestName + " activeElement");
+
+ currentPromiseResolver();
+ currentPromiseResolver = null;
+ });
+}
+
+function* expectFocusShiftAfterTabSwitch(tab, expectedWindow, expectedElement, focusChanged, testid)
+{
+ let tabSwitchPromise = null;
+ yield expectFocusShift(() => { tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, tab) },
+ expectedWindow, expectedElement, focusChanged, testid)
+ yield tabSwitchPromise;
+}
+
+function* expectFocusShift(callback, expectedWindow, expectedElement, focusChanged, testid)
+{
+ currentPromiseResolver = null;
+ currentTestName = testid;
+
+ expectedEvents = new EventStore();
+
+ if (focusChanged) {
+ _expectedElement = expectedElement;
+ _expectedWindow = expectedWindow;
+
+ // When the content is in a child process, the expected element in the chrome window
+ // will always be the urlbar or a browser element.
+ if (gMultiProcessBrowser) {
+ if (_expectedWindow == "window1") {
+ _expectedElement = "browser1";
+ }
+ else if (_expectedWindow == "window2") {
+ _expectedElement = "browser2";
+ }
+ _expectedWindow = "main-window";
+ }
+
+ if (gMultiProcessBrowser && _lastfocuswindow != "main-window" &&
+ _lastfocuswindow != expectedWindow) {
+ let browserid = _lastfocuswindow == "window1" ? "browser1" : "browser2";
+ expectedEvents.push("blur: " + browserid);
+ }
+
+ var newElementIsFocused = (expectedElement && !expectedElement.startsWith("html"));
+ if (newElementIsFocused && gMultiProcessBrowser &&
+ _lastfocuswindow != "main-window" &&
+ expectedWindow == "main-window") {
+ // When switching from a child to a chrome element, the focus on the element will arrive first.
+ expectedEvents.push("focus: " + expectedElement);
+ newElementIsFocused = false;
+ }
+
+ if (_lastfocus && _lastfocus != _expectedElement)
+ expectedEvents.push("blur: " + _lastfocus);
+
+ if (_lastfocuswindow &&
+ _lastfocuswindow != expectedWindow) {
+
+ if (!gMultiProcessBrowser || _lastfocuswindow != "main-window") {
+ expectedEvents.push("blur: " + _lastfocuswindow + "-document");
+ expectedEvents.push("blur: " + _lastfocuswindow + "-window");
+ }
+ }
+
+ if (expectedWindow && _lastfocuswindow != expectedWindow) {
+ if (gMultiProcessBrowser && expectedWindow != "main-window") {
+ let browserid = expectedWindow == "window1" ? "browser1" : "browser2";
+ expectedEvents.push("focus: " + browserid);
+ }
+
+ if (!gMultiProcessBrowser || expectedWindow != "main-window") {
+ expectedEvents.push("focus: " + expectedWindow + "-document");
+ expectedEvents.push("focus: " + expectedWindow + "-window");
+ }
+ }
+
+ if (newElementIsFocused) {
+ expectedEvents.push("focus: " + expectedElement);
+ }
+
+ _lastfocus = expectedElement;
+ _lastfocuswindow = expectedWindow;
+ }
+
+ return new Promise((resolve, reject) => {
+ currentPromiseResolver = resolve;
+ callback();
+
+ // No events are expected, so resolve the promise immediately.
+ if (expectedEvents["main-window"].length + expectedEvents["window1"].length + expectedEvents["window2"].length == 0) {
+ currentPromiseResolver();
+ currentPromiseResolver = null;
+ }
+ });
+}
diff --git a/browser/base/content/test/general/browser_tabkeynavigation.js b/browser/base/content/test/general/browser_tabkeynavigation.js
new file mode 100644
index 000000000..d8e51f4b9
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabkeynavigation.js
@@ -0,0 +1,156 @@
+/*
+ * This test checks that keyboard navigation for tabs isn't blocked by content
+ */
+add_task(function* test() {
+
+ let testPage1 = "data:text/html,<html id='tab1'><body><button id='button1'>Tab 1</button></body></html>";
+ let testPage2 = "data:text/html,<html id='tab2'><body><button id='button2'>Tab 2</button><script>function preventDefault(event) { event.preventDefault(); event.stopImmediatePropagation(); } window.addEventListener('keydown', preventDefault, true); window.addEventListener('keypress', preventDefault, true);</script></body></html>";
+ let testPage3 = "data:text/html,<html id='tab3'><body><button id='button3'>Tab 3</button></body></html>";
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage1);
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage2);
+ let tab3 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testPage3);
+
+ // Kill the animation for simpler test.
+ Services.prefs.setBoolPref("browser.tabs.animate", false);
+
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+Tab on Tab1");
+
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+Tab on Tab2");
+
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+Shift+Tab on Tab3");
+
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+Shift+Tab on Tab2");
+
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+PageDown on Tab1");
+
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+PageDown on Tab2");
+
+ EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+PageUp on Tab3");
+
+ EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+PageUp on Tab2");
+
+ if (gBrowser.mTabBox._handleMetaAltArrows) {
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ let ltr = window.getComputedStyle(gBrowser.mTabBox, "").direction == "ltr";
+ let advanceKey = ltr ? "VK_RIGHT" : "VK_LEFT";
+ let reverseKey = ltr ? "VK_LEFT" : "VK_RIGHT";
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+ EventUtils.synthesizeKey(advanceKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + advanceKey + " on Tab1");
+
+ EventUtils.synthesizeKey(advanceKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+" + advanceKey + " on Tab2");
+
+ EventUtils.synthesizeKey(reverseKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + reverseKey + " on Tab3");
+
+ EventUtils.synthesizeKey(reverseKey, { altKey: true, metaKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+" + reverseKey + " on Tab2");
+ }
+
+ gBrowser.selectedTab = tab2;
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated");
+ is(gBrowser.tabContainer.selectedIndex, 2,
+ "Tab2 index should be 2");
+
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated after Ctrl+Shift+PageDown");
+ is(gBrowser.tabContainer.selectedIndex, 3,
+ "Tab2 index should be 1 after Ctrl+Shift+PageDown");
+
+ EventUtils.synthesizeKey("VK_PAGE_UP", { ctrlKey: true, shiftKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated after Ctrl+Shift+PageUp");
+ is(gBrowser.tabContainer.selectedIndex, 2,
+ "Tab2 index should be 2 after Ctrl+Shift+PageUp");
+
+ if (navigator.platform.indexOf("Mac") == 0) {
+ gBrowser.selectedTab = tab1;
+ browser1.focus();
+
+ // XXX Currently, Command + "{" and "}" don't work if keydown event is
+ // consumed because following keypress event isn't fired.
+
+ let ltr = window.getComputedStyle(gBrowser.mTabBox, "").direction == "ltr";
+ let advanceKey = ltr ? "}" : "{";
+ let reverseKey = ltr ? "{" : "}";
+
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated");
+
+ EventUtils.synthesizeKey(advanceKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + advanceKey + " on Tab1");
+
+ EventUtils.synthesizeKey(advanceKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab3,
+ "Tab3 should be activated by pressing Ctrl+" + advanceKey + " on Tab2");
+
+ EventUtils.synthesizeKey(reverseKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab2,
+ "Tab2 should be activated by pressing Ctrl+" + reverseKey + " on Tab3");
+
+ EventUtils.synthesizeKey(reverseKey, { metaKey: true });
+ is(gBrowser.selectedTab, tab1,
+ "Tab1 should be activated by pressing Ctrl+" + reverseKey + " on Tab2");
+ } else {
+ gBrowser.selectedTab = tab2;
+ EventUtils.synthesizeKey("VK_F4", { type: "keydown", ctrlKey: true });
+
+ isnot(gBrowser.selectedTab, tab2,
+ "Tab2 should be closed by pressing Ctrl+F4 on Tab2");
+ is(gBrowser.tabs.length, 3,
+ "The count of tabs should be 3 since tab2 should be closed");
+
+ // NOTE: keypress event shouldn't be fired since the keydown event should
+ // be consumed by tab2.
+ EventUtils.synthesizeKey("VK_F4", { type: "keyup", ctrlKey: true });
+ is(gBrowser.tabs.length, 3,
+ "The count of tabs should be 3 since renaming key events shouldn't close other tabs");
+ }
+
+ gBrowser.selectedTab = tab3;
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+
+ Services.prefs.clearUserPref("browser.tabs.animate");
+});
diff --git a/browser/base/content/test/general/browser_tabopen_reflows.js b/browser/base/content/test/general/browser_tabopen_reflows.js
new file mode 100644
index 000000000..8e04cf12e
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabopen_reflows.js
@@ -0,0 +1,157 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyGetter(this, "docShell", () => {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+});
+
+const EXPECTED_REFLOWS = [
+ // tabbrowser.adjustTabstrip() call after tabopen animation has finished
+ "adjustTabstrip@chrome://browser/content/tabbrowser.xml|" +
+ "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+ "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
+
+ // switching focus in updateCurrentBrowser() causes reflows
+ "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml|" +
+ "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
+ "onselect@chrome://browser/content/browser.xul|",
+
+ // switching focus in openLinkIn() causes reflows
+ "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "BrowserOpenTab@chrome://browser/content/browser.js|",
+
+ // accessing element.scrollPosition in _fillTrailingGap() flushes layout
+ "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
+ "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
+ "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+ "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
+
+ // SessionStore.getWindowDimensions()
+ "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|",
+
+ // selection change notification may cause querying the focused editor content
+ // by IME and that will cause reflow.
+ "select@chrome://global/content/bindings/textbox.xml|" +
+ "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
+ "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "BrowserOpenTab@chrome://browser/content/browser.js|",
+
+];
+
+const PREF_PRELOAD = "browser.newtab.preload";
+const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when opening new tabs.
+ */
+add_task(function*() {
+ let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
+ let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils;
+ let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
+
+ // resolves promise when directory links are downloaded and written to disk
+ function watchLinksChangeOnce() {
+ let deferred = Promise.defer();
+ let observer = {
+ onManyLinksChanged: () => {
+ DirectoryLinksProvider.removeObserver(observer);
+ NewTabUtils.links.populateCache(() => {
+ NewTabUtils.allPages.update();
+ deferred.resolve();
+ }, true);
+ }
+ };
+ observer.onDownloadFail = observer.onManyLinksChanged;
+ DirectoryLinksProvider.addObserver(observer);
+ return deferred.promise;
+ }
+
+ let gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(PREF_PRELOAD);
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
+ return watchLinksChangeOnce();
+ });
+
+ Services.prefs.setBoolPref(PREF_PRELOAD, false);
+ // set directory source to dummy/empty links
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, 'data:application/json,{"test":1}');
+
+ // run tests when directory source change completes
+ yield watchLinksChangeOnce();
+
+ // Perform a click in the top left of content to ensure the mouse isn't
+ // hovering over any of the tiles
+ let target = gBrowser.selectedBrowser;
+ let rect = target.getBoundingClientRect();
+ let left = rect.left + 1;
+ let top = rect.top + 1;
+
+ let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+
+ // Add a reflow observer and open a new tab.
+ docShell.addWeakReflowObserver(observer);
+ BrowserOpenTab();
+
+ // Wait until the tabopen animation has finished.
+ yield waitForTransitionEnd();
+
+ // Remove reflow observer and clean up.
+ docShell.removeWeakReflowObserver(observer);
+ gBrowser.removeCurrentTab();
+});
+
+var observer = {
+ reflow: function (start, end) {
+ // Gather information about the current code path.
+ let path = (new Error().stack).split("\n").slice(1).map(line => {
+ return line.replace(/:\d+:\d+$/, "");
+ }).join("|");
+ let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
+
+ // Stack trace is empty. Reflow was triggered by native code.
+ if (path === "") {
+ return;
+ }
+
+ // Check if this is an expected reflow.
+ for (let stack of EXPECTED_REFLOWS) {
+ if (path.startsWith(stack)) {
+ ok(true, "expected uninterruptible reflow '" + stack + "'");
+ return;
+ }
+ }
+
+ ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
+ },
+
+ reflowInterruptible: function (start, end) {
+ // We're not interested in interruptible reflows.
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+function waitForTransitionEnd() {
+ return new Promise(resolve => {
+ let tab = gBrowser.selectedTab;
+ tab.addEventListener("transitionend", function onEnd(event) {
+ if (event.propertyName === "max-width") {
+ tab.removeEventListener("transitionend", onEnd);
+ resolve();
+ }
+ });
+ });
+}
diff --git a/browser/base/content/test/general/browser_tabs_close_beforeunload.js b/browser/base/content/test/general/browser_tabs_close_beforeunload.js
new file mode 100644
index 000000000..b867efd72
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabs_close_beforeunload.js
@@ -0,0 +1,49 @@
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+const FIRST_TAB = getRootDirectory(gTestPath) + "close_beforeunload_opens_second_tab.html";
+const SECOND_TAB = getRootDirectory(gTestPath) + "close_beforeunload.html";
+
+add_task(function*() {
+ info("Opening first tab");
+ let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, FIRST_TAB);
+ let secondTabLoadedPromise;
+ let secondTab;
+ let tabOpened = new Promise(resolve => {
+ info("Adding tabopen listener");
+ gBrowser.tabContainer.addEventListener("TabOpen", function tabOpenListener(e) {
+ info("Got tabopen, removing listener and waiting for load");
+ gBrowser.tabContainer.removeEventListener("TabOpen", tabOpenListener, false, false);
+ secondTab = e.target;
+ secondTabLoadedPromise = BrowserTestUtils.browserLoaded(secondTab.linkedBrowser, false, SECOND_TAB);
+ resolve();
+ }, false, false);
+ });
+ info("Opening second tab using a click");
+ yield ContentTask.spawn(firstTab.linkedBrowser, "", function*() {
+ content.document.getElementsByTagName("a")[0].click();
+ });
+ info("Waiting for the second tab to be opened");
+ yield tabOpened;
+ info("Waiting for the load in that tab to finish");
+ yield secondTabLoadedPromise;
+
+ let closeBtn = document.getAnonymousElementByAttribute(secondTab, "anonid", "close-button");
+ let closePromise = BrowserTestUtils.removeTab(secondTab, {dontRemove: true});
+ info("closing second tab (which will self-close in beforeunload)");
+ closeBtn.click();
+ ok(secondTab.closing, "Second tab should be marked as closing synchronously.");
+ yield closePromise;
+ ok(secondTab.closing, "Second tab should still be marked as closing");
+ ok(!secondTab.linkedBrowser, "Second tab's browser should be dead");
+ ok(!firstTab.closing, "First tab should not be closing");
+ ok(firstTab.linkedBrowser, "First tab's browser should be alive");
+ info("closing first tab");
+ yield BrowserTestUtils.removeTab(firstTab);
+
+ ok(firstTab.closing, "First tab should be marked as closing");
+ ok(!firstTab.linkedBrowser, "First tab's browser should be dead");
+});
diff --git a/browser/base/content/test/general/browser_tabs_isActive.js b/browser/base/content/test/general/browser_tabs_isActive.js
new file mode 100644
index 000000000..0725757e7
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabs_isActive.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for the docshell active state of local and remote browsers.
+
+const kTestPage = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
+ gBrowser.removeEventListener("TabSwitchDone", onSwitch);
+ executeSoon(resolve);
+ });
+ });
+}
+
+function getParentTabState(aTab) {
+ return aTab.linkedBrowser.docShellIsActive;
+}
+
+function getChildTabState(aTab) {
+ return ContentTask.spawn(aTab.linkedBrowser, {}, function* () {
+ return docShell.isActive;
+ });
+}
+
+function checkState(parentSide, childSide, value, message) {
+ is(parentSide, value, message + " (parent side)");
+ is(childSide, value, message + " (child side)");
+}
+
+function waitForMs(aMs) {
+ return new Promise((resolve) => {
+ setTimeout(done, aMs);
+ function done() {
+ resolve(true);
+ }
+ });
+}
+
+add_task(function *() {
+ let url = kTestPage;
+ let originalTab = gBrowser.selectedTab; // test tab
+ let newTab = gBrowser.addTab(url, {skipAnimation: true});
+ let parentSide, childSide;
+
+ // new tab added but not selected checks
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active initially");
+
+ // select the newly added tab and wait for TabSwitchDone event
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ ok(newTab.linkedBrowser.isRemoteBrowser, "for testing we need a remote tab");
+ }
+
+ // check active state of both tabs
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is not active while unselected");
+
+ // switch back to the original test tab and wait for TabSwitchDone event
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = originalTab;
+ yield tabSwitchedPromise;
+
+ // check active state of both tabs
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active again after switch back");
+
+ // switch to the new tab and wait for TabSwitchDone event
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ // check active state of both tabs
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is active again after switch back");
+
+ gBrowser.removeTab(newTab);
+});
+
+add_task(function *() {
+ let url = "about:about";
+ let originalTab = gBrowser.selectedTab; // test tab
+ let newTab = gBrowser.addTab(url, {skipAnimation: true});
+ let parentSide, childSide;
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active initially");
+
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ ok(!newTab.linkedBrowser.isRemoteBrowser, "for testing we need a local tab");
+ }
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is active after selection");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is not active while unselected");
+
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = originalTab;
+ yield tabSwitchedPromise;
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, false, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, true, "original tab is active again after switch back");
+
+ tabSwitchedPromise = promiseNewTabSwitched();
+ gBrowser.selectedTab = newTab;
+ yield tabSwitchedPromise;
+
+ parentSide = getParentTabState(newTab);
+ childSide = yield getChildTabState(newTab);
+ checkState(parentSide, childSide, true, "newly added " + url + " tab is not active after switch back");
+ parentSide = getParentTabState(originalTab);
+ childSide = yield getChildTabState(originalTab);
+ checkState(parentSide, childSide, false, "original tab is active again after switch back");
+
+ gBrowser.removeTab(newTab);
+});
diff --git a/browser/base/content/test/general/browser_tabs_owner.js b/browser/base/content/test/general/browser_tabs_owner.js
new file mode 100644
index 000000000..300d783ba
--- /dev/null
+++ b/browser/base/content/test/general/browser_tabs_owner.js
@@ -0,0 +1,44 @@
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: gBrowser._finalizeTabSwitch is not a function");
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: gBrowser._finalizeTabSwitch is not a function");
+
+function test() {
+ gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.addTab();
+
+ var tabs = gBrowser.tabs;
+ var owner;
+
+ is(tabs.length, 4, "4 tabs are open");
+
+ owner = gBrowser.selectedTab = tabs[2];
+ BrowserOpenTab();
+ is(gBrowser.selectedTab, tabs[4], "newly opened tab is selected");
+ gBrowser.removeCurrentTab();
+ is(gBrowser.selectedTab, owner, "owner is selected");
+
+ owner = gBrowser.selectedTab;
+ BrowserOpenTab();
+ gBrowser.selectedTab = tabs[1];
+ gBrowser.selectedTab = tabs[4];
+ gBrowser.removeCurrentTab();
+ isnot(gBrowser.selectedTab, owner, "selecting a different tab clears the owner relation");
+
+ owner = gBrowser.selectedTab;
+ BrowserOpenTab();
+ gBrowser.moveTabTo(gBrowser.selectedTab, 0);
+ gBrowser.removeCurrentTab();
+ is(gBrowser.selectedTab, owner, "owner relatitionship persists when tab is moved");
+
+ while (tabs.length > 1)
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js b/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js
new file mode 100644
index 000000000..f90f047d3
--- /dev/null
+++ b/browser/base/content/test/general/browser_testOpenNewRemoteTabsFromNonRemoteBrowsers.js
@@ -0,0 +1,126 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const OPEN_LOCATION_PREF = "browser.link.open_newwindow";
+const NON_REMOTE_PAGE = "about:welcomeback";
+
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+requestLongerTimeout(2);
+
+function frame_script() {
+ content.document.body.innerHTML = `
+ <a href="about:home" target="_blank" id="testAnchor">Open a window</a>
+ `;
+
+ let element = content.document.getElementById("testAnchor");
+ element.click();
+}
+
+/**
+ * Takes some browser in some window, and forces that browser
+ * to become non-remote, and then navigates it to a page that
+ * we're not supposed to be displaying remotely. Returns a
+ * Promise that resolves when the browser is no longer remote.
+ */
+function prepareNonRemoteBrowser(aWindow, browser) {
+ browser.loadURI(NON_REMOTE_PAGE);
+ return BrowserTestUtils.browserLoaded(browser);
+}
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(OPEN_LOCATION_PREF);
+});
+
+/**
+ * Test that if we open a new tab from a link in a non-remote
+ * browser in an e10s window, that the new tab will load properly.
+ */
+add_task(function* test_new_tab() {
+ let normalWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true,
+ });
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true,
+ private: true,
+ });
+
+ for (let testWindow of [normalWindow, privateWindow]) {
+ yield promiseWaitForFocus(testWindow);
+ let testBrowser = testWindow.gBrowser.selectedBrowser;
+ info("Preparing non-remote browser");
+ yield prepareNonRemoteBrowser(testWindow, testBrowser);
+ info("Non-remote browser prepared - sending frame script");
+
+ // Get our framescript ready
+ let mm = testBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+
+ let tabOpenEvent = yield waitForNewTabEvent(testWindow.gBrowser);
+ let newTab = tabOpenEvent.target;
+
+ yield promiseTabLoadEvent(newTab);
+
+ // Our framescript opens to about:home which means that the
+ // tab should eventually become remote.
+ ok(newTab.linkedBrowser.isRemoteBrowser,
+ "The opened browser never became remote.");
+
+ testWindow.gBrowser.removeTab(newTab);
+ }
+
+ normalWindow.close();
+ privateWindow.close();
+});
+
+/**
+ * Test that if we open a new window from a link in a non-remote
+ * browser in an e10s window, that the new window is not an e10s
+ * window. Also tests with a private browsing window.
+ */
+add_task(function* test_new_window() {
+ let normalWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true
+ }, true);
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({
+ remote: true,
+ private: true,
+ }, true);
+
+ // Fiddle with the prefs so that we open target="_blank" links
+ // in new windows instead of new tabs.
+ Services.prefs.setIntPref(OPEN_LOCATION_PREF,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW);
+
+ for (let testWindow of [normalWindow, privateWindow]) {
+ yield promiseWaitForFocus(testWindow);
+ let testBrowser = testWindow.gBrowser.selectedBrowser;
+ yield prepareNonRemoteBrowser(testWindow, testBrowser);
+
+ // Get our framescript ready
+ let mm = testBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+
+ // Click on the link in the browser, and wait for the new window.
+ let {subject: newWindow} =
+ yield promiseTopicObserved("browser-delayed-startup-finished");
+
+ is(PrivateBrowsingUtils.isWindowPrivate(testWindow),
+ PrivateBrowsingUtils.isWindowPrivate(newWindow),
+ "Private browsing state of new window does not match the original!");
+
+ let newTab = newWindow.gBrowser.selectedTab;
+
+ yield promiseTabLoadEvent(newTab);
+
+ // Our framescript opens to about:home which means that the
+ // tab should eventually become remote.
+ ok(newTab.linkedBrowser.isRemoteBrowser,
+ "The opened browser never became remote.");
+ newWindow.close();
+ }
+
+ normalWindow.close();
+ privateWindow.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_1.js b/browser/base/content/test/general/browser_trackingUI_1.js
new file mode 100644
index 000000000..937d607af
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_1.js
@@ -0,0 +1,170 @@
+/*
+ * Test that the Tracking Protection section is visible in the Control Center
+ * and has the correct state for the cases when:
+ * 1) A page with no tracking elements is loaded.
+ * 2) A page with tracking elements is loaded and they are blocked.
+ * 3) A page with tracking elements is loaded and they are not blocked.
+ * See also Bugs 1175327, 1043801, 1178985
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var tabbrowser = null;
+
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = tabbrowser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function hidden(sel) {
+ let win = tabbrowser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ let display = win.getComputedStyle(el).getPropertyValue("display", null);
+ let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null);
+ return display === "none" || opacity === "0";
+}
+
+function clickButton(sel) {
+ let win = tabbrowser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ el.doCommand();
+}
+
+function testBenignPage() {
+ info("Non-tracking content must not be blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ ok(!TrackingProtection.content.hasAttribute("state"), "content: no state");
+ ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
+ ok(!TrackingProtection.icon.hasAttribute("tooltiptext"), "icon: no tooltip");
+
+ ok(hidden("#tracking-protection-icon"), "icon is hidden");
+ ok(hidden("#tracking-action-block"), "blockButton is hidden");
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+
+ // Make sure that the no tracking elements message appears
+ ok(!hidden("#tracking-not-detected"), "labelNoTracking is visible");
+ ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
+ ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+}
+
+function testTrackingPage(window) {
+ info("Tracking content must be blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
+ 'content: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
+ 'icon: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("tooltiptext"),
+ gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(hidden("#tracking-action-block"), "blockButton is hidden");
+
+
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+ ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
+ } else {
+ ok(!hidden("#tracking-action-unblock"), "unblockButton is visible");
+ ok(hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is hidden");
+ }
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
+ ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
+}
+
+function testTrackingPageUnblocked() {
+ info("Tracking content must be white-listed and not blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
+ 'content: state="loaded-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
+ 'icon: state="loaded-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("tooltiptext"),
+ gNavigatorBundle.getString("trackingProtection.icon.disabledTooltip"), "correct tooltip");
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(!hidden("#tracking-action-block"), "blockButton is visible");
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(!hidden("#tracking-loaded"), "labelTrackingLoaded is visible");
+ ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+}
+
+function* testTrackingProtectionForTab(tab) {
+ info("Load a test page not containing tracking elements");
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ testBenignPage();
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-unblock");
+ yield tabReloadPromise;
+ testTrackingPageUnblocked();
+
+ info("Re-enable TP for the page (which reloads the page)");
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-block");
+ yield tabReloadPromise;
+ testTrackingPage(tab.ownerGlobal);
+}
+
+add_task(function* testNormalBrowsing() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ tabbrowser = gBrowser;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PREF),
+ "TP.enabled is based on the original pref value");
+
+ Services.prefs.setBoolPref(PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionForTab(tab);
+
+ Services.prefs.setBoolPref(PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ tabbrowser = privateWin.gBrowser;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PB_PREF),
+ "TP.enabled is based on the pb pref value");
+
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionForTab(tab);
+
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_2.js b/browser/base/content/test/general/browser_trackingUI_2.js
new file mode 100644
index 000000000..96ccb6c2e
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_2.js
@@ -0,0 +1,96 @@
+/*
+ * Test that the Tracking Protection section is never visible in the
+ * Control Center when the feature is off.
+ * See also Bugs 1175327, 1043801, 1178985.
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var tabbrowser = null;
+
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = tabbrowser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function hidden(el) {
+ let win = el.ownerGlobal;
+ let display = win.getComputedStyle(el).getPropertyValue("display", null);
+ let opacity = win.getComputedStyle(el).getPropertyValue("opacity", null);
+
+ return display === "none" || opacity === "0";
+}
+
+add_task(function* testNormalBrowsing() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ tabbrowser = gBrowser;
+ let {gIdentityHandler} = tabbrowser.ownerGlobal;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PREF),
+ "TP.enabled is based on the original pref value");
+
+ Services.prefs.setBoolPref(PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ Services.prefs.setBoolPref(PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ gIdentityHandler._identityBox.click();
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+
+ info("Load a test page not containing tracking elements");
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ gIdentityHandler._identityBox.click();
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ tabbrowser = privateWin.gBrowser;
+ let {gIdentityHandler} = tabbrowser.ownerGlobal;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+ is(TrackingProtection.enabled, Services.prefs.getBoolPref(PB_PREF),
+ "TP.enabled is based on the pb pref value");
+
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled after setting the pref");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ gIdentityHandler._identityBox.click();
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+
+ info("Load a test page not containing tracking elements");
+ gIdentityHandler._identityBox.click();
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ ok(hidden(TrackingProtection.container), "The container is hidden");
+ gIdentityHandler._identityPopup.hidden = true;
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_3.js b/browser/base/content/test/general/browser_trackingUI_3.js
new file mode 100644
index 000000000..63f8a13bc
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_3.js
@@ -0,0 +1,52 @@
+/*
+ * Test that the Tracking Protection is correctly enabled / disabled
+ * in both normal and private windows given all possible states of the prefs:
+ * privacy.trackingprotection.enabled
+ * privacy.trackingprotection.pbmode.enabled
+ * See also Bug 1178985.
+ */
+
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+});
+
+add_task(function* testNormalBrowsing() {
+ let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ Services.prefs.setBoolPref(PREF, true);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)");
+
+ Services.prefs.setBoolPref(PREF, false);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=true)");
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ let TrackingProtection = privateWin.gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ Services.prefs.setBoolPref(PREF, true);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=true,PB=true)");
+
+ Services.prefs.setBoolPref(PREF, false);
+ Services.prefs.setBoolPref(PB_PREF, false);
+ ok(!TrackingProtection.enabled, "TP is disabled (ENABLED=false,PB=false)");
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled (ENABLED=false,PB=true)");
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_4.js b/browser/base/content/test/general/browser_trackingUI_4.js
new file mode 100644
index 000000000..93a06913e
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_4.js
@@ -0,0 +1,109 @@
+/*
+ * Test that the Tracking Protection icon is properly animated in the identity
+ * block when loading tabs and switching between tabs.
+ * See also Bug 1175858.
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var tabbrowser = null;
+
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = tabbrowser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(PREF);
+ Services.prefs.clearUserPref(PB_PREF);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function waitForSecurityChange(numChanges = 1) {
+ return new Promise(resolve => {
+ let n = 0;
+ let listener = {
+ onSecurityChange: function() {
+ n = n + 1;
+ info ("Received onSecurityChange event " + n + " of " + numChanges);
+ if (n >= numChanges) {
+ tabbrowser.removeProgressListener(listener);
+ resolve();
+ }
+ }
+ };
+ tabbrowser.addProgressListener(listener);
+ });
+}
+
+function* testTrackingProtectionAnimation() {
+ info("Load a test page not containing tracking elements");
+ let benignTab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE);
+
+ ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
+ ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
+
+ info("Load a test page containing tracking elements");
+ let trackingTab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser, TRACKING_PAGE);
+
+ ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
+ ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
+
+ info("Switch from tracking -> benign tab");
+ let securityChanged = waitForSecurityChange();
+ tabbrowser.selectedTab = benignTab;
+ yield securityChanged;
+
+ ok(!TrackingProtection.icon.hasAttribute("state"), "icon: no state");
+ ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate");
+
+ info("Switch from benign -> tracking tab");
+ securityChanged = waitForSecurityChange();
+ tabbrowser.selectedTab = trackingTab;
+ yield securityChanged;
+
+ ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
+ ok(!TrackingProtection.icon.hasAttribute("animate"), "icon: no animate");
+
+ info("Reload tracking tab");
+ securityChanged = waitForSecurityChange(2);
+ tabbrowser.reload();
+ yield securityChanged;
+
+ ok(TrackingProtection.icon.hasAttribute("state"), "icon: state");
+ ok(TrackingProtection.icon.hasAttribute("animate"), "icon: animate");
+}
+
+add_task(function* testNormalBrowsing() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ tabbrowser = gBrowser;
+
+ TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ Services.prefs.setBoolPref(PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionAnimation();
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ tabbrowser = privateWin.gBrowser;
+
+ TrackingProtection = tabbrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the private window");
+
+ Services.prefs.setBoolPref(PB_PREF, true);
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ yield testTrackingProtectionAnimation();
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_5.js b/browser/base/content/test/general/browser_trackingUI_5.js
new file mode 100644
index 000000000..23164a5b2
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_5.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that sites added to the Tracking Protection whitelist in private
+// browsing mode don't persist once the private browsing window closes.
+
+const PB_PREF = "privacy.trackingprotection.pbmode.enabled";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+var TrackingProtection = null;
+var browser = null;
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ TrackingProtection = browser = null;
+ UrlClassifierTestUtils.cleanupTestTrackers();
+});
+
+function hidden(sel) {
+ let win = browser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ let display = win.getComputedStyle(el).getPropertyValue("display", null);
+ return display === "none";
+}
+
+function identityPopupState() {
+ let win = browser.ownerGlobal;
+ return win.document.getElementById("identity-popup").state;
+}
+
+function clickButton(sel) {
+ let win = browser.ownerGlobal;
+ let el = win.document.querySelector(sel);
+ el.doCommand();
+}
+
+function testTrackingPage(window) {
+ info("Tracking content must be blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
+ 'content: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
+ 'icon: state="blocked-tracking-content"');
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(hidden("#tracking-action-block"), "blockButton is hidden");
+
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+ ok(!hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is visible");
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(hidden("#tracking-loaded"), "labelTrackingLoaded is hidden");
+ ok(!hidden("#tracking-blocked"), "labelTrackingBlocked is visible");
+}
+
+function testTrackingPageUnblocked() {
+ info("Tracking content must be white-listed and not blocked");
+ ok(!TrackingProtection.container.hidden, "The container is visible");
+ is(TrackingProtection.content.getAttribute("state"), "loaded-tracking-content",
+ 'content: state="loaded-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "loaded-tracking-content",
+ 'icon: state="loaded-tracking-content"');
+
+ ok(!hidden("#tracking-protection-icon"), "icon is visible");
+ ok(!hidden("#tracking-action-block"), "blockButton is visible");
+ ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
+
+ // Make sure that the blocked tracking elements message appears
+ ok(hidden("#tracking-not-detected"), "labelNoTracking is hidden");
+ ok(!hidden("#tracking-loaded"), "labelTrackingLoaded is visible");
+ ok(hidden("#tracking-blocked"), "labelTrackingBlocked is hidden");
+}
+
+add_task(function* testExceptionAddition() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ browser = privateWin.gBrowser;
+ let tab = browser.selectedTab = browser.addTab();
+
+ TrackingProtection = browser.ownerGlobal.TrackingProtection;
+ yield pushPrefs([PB_PREF, true]);
+
+ ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-unblock");
+ is(identityPopupState(), "closed", "foobar");
+
+ yield tabReloadPromise;
+ testTrackingPageUnblocked();
+
+ info("Test that the exception is remembered across tabs in the same private window");
+ tab = browser.selectedTab = browser.addTab();
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ testTrackingPageUnblocked();
+
+ yield promiseWindowClosed(privateWin);
+});
+
+add_task(function* testExceptionPersistence() {
+ info("Open another private browsing window");
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ browser = privateWin.gBrowser;
+ let tab = browser.selectedTab = browser.addTab();
+
+ TrackingProtection = browser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection.enabled, "TP is still enabled");
+
+ info("Load a test page containing tracking elements");
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+
+ testTrackingPage(tab.ownerGlobal);
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ clickButton("#tracking-action-unblock");
+ is(identityPopupState(), "closed", "foobar");
+
+ yield tabReloadPromise;
+ testTrackingPageUnblocked();
+
+ privateWin.close();
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_6.js b/browser/base/content/test/general/browser_trackingUI_6.js
new file mode 100644
index 000000000..be91bc4a0
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_6.js
@@ -0,0 +1,46 @@
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_trackingUI_6.html";
+
+function waitForSecurityChange(numChanges = 1) {
+ return new Promise(resolve => {
+ let n = 0;
+ let listener = {
+ onSecurityChange: function() {
+ n = n + 1;
+ info ("Received onSecurityChange event " + n + " of " + numChanges);
+ if (n >= numChanges) {
+ gBrowser.removeProgressListener(listener);
+ resolve();
+ }
+ }
+ };
+ gBrowser.addProgressListener(listener);
+ });
+}
+
+add_task(function* test_fetch() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({ set: [['privacy.trackingprotection.enabled', true]] },
+ resolve);
+ });
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, function* (newTabBrowser) {
+ let securityChange = waitForSecurityChange();
+ yield ContentTask.spawn(newTabBrowser, null, function* () {
+ yield content.wrappedJSObject.test_fetch()
+ .then(response => Assert.ok(false, "should have denied the request"))
+ .catch(e => Assert.ok(true, `Caught exception: ${e}`));
+ });
+ yield securityChange;
+
+ var TrackingProtection = newTabBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "got TP object");
+ ok(TrackingProtection.enabled, "TP is enabled");
+
+ is(TrackingProtection.content.getAttribute("state"), "blocked-tracking-content",
+ 'content: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("state"), "blocked-tracking-content",
+ 'icon: state="blocked-tracking-content"');
+ is(TrackingProtection.icon.getAttribute("tooltiptext"),
+ gNavigatorBundle.getString("trackingProtection.icon.activeTooltip"), "correct tooltip");
+ });
+});
diff --git a/browser/base/content/test/general/browser_trackingUI_telemetry.js b/browser/base/content/test/general/browser_trackingUI_telemetry.js
new file mode 100644
index 000000000..d9fce18d4
--- /dev/null
+++ b/browser/base/content/test/general/browser_trackingUI_telemetry.js
@@ -0,0 +1,145 @@
+/*
+ * Test telemetry for Tracking Protection
+ */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF = "privacy.trackingprotection.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+const {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+/**
+ * Enable local telemetry recording for the duration of the tests.
+ */
+var oldCanRecord = Services.telemetry.canRecordExtended;
+Services.telemetry.canRecordExtended = true;
+Services.prefs.setBoolPref(PREF, false);
+Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").clear();
+registerCleanupFunction(function () {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ Services.prefs.clearUserPref(PREF);
+});
+
+function getShieldHistogram() {
+ return Services.telemetry.getHistogramById("TRACKING_PROTECTION_SHIELD");
+}
+
+function getEnabledHistogram() {
+ return Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED");
+}
+
+function getEventsHistogram() {
+ return Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS");
+}
+
+function getShieldCounts() {
+ return getShieldHistogram().snapshot().counts;
+}
+
+function getEnabledCounts() {
+ return getEnabledHistogram().snapshot().counts;
+}
+
+function getEventCounts() {
+ return getEventsHistogram().snapshot().counts;
+}
+
+add_task(function* setup() {
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ let TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+ ok(!TrackingProtection.enabled, "TP is not enabled");
+
+ // Open a window with TP disabled to make sure 'enabled' is logged correctly.
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ yield promiseWindowClosed(newWin);
+
+ is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
+ is(getEnabledCounts()[1], 0, "TP was not enabled on start up");
+
+ // Enable TP so the next browser to open will log 'enabled'
+ Services.prefs.setBoolPref(PREF, true);
+});
+
+
+add_task(function* testNewWindow() {
+ let newWin = yield promiseOpenAndLoadWindow({}, true);
+ let tab = newWin.gBrowser.selectedTab = newWin.gBrowser.addTab();
+ let TrackingProtection = newWin.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ is(getEnabledCounts()[0], 1, "TP was disabled once on start up");
+ is(getEnabledCounts()[1], 1, "TP was enabled once on start up");
+
+ // Reset these to make counting easier
+ getEventsHistogram().clear();
+ getShieldHistogram().clear();
+
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ is(getEventCounts()[0], 1, "Total page loads");
+ is(getEventCounts()[1], 0, "Disable actions");
+ is(getEventCounts()[2], 0, "Enable actions");
+ is(getShieldCounts()[0], 1, "Page loads without tracking");
+
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ // Note that right now the events and shield histogram is not measuring what
+ // you might think. Since onSecurityChange fires twice for a tracking page,
+ // the total page loads count is double counting, and the shield count
+ // (which is meant to measure times when the shield wasn't shown) fires even
+ // when tracking elements exist on the page.
+ todo_is(getEventCounts()[0], 2, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+ is(getEventCounts()[1], 0, "Disable actions");
+ is(getEventCounts()[2], 0, "Enable actions");
+ todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+ info("Disable TP for the page (which reloads the page)");
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ newWin.document.querySelector("#tracking-action-unblock").doCommand();
+ yield tabReloadPromise;
+ todo_is(getEventCounts()[0], 3, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+ is(getEventCounts()[1], 1, "Disable actions");
+ is(getEventCounts()[2], 0, "Enable actions");
+ todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+ info("Re-enable TP for the page (which reloads the page)");
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ newWin.document.querySelector("#tracking-action-block").doCommand();
+ yield tabReloadPromise;
+ todo_is(getEventCounts()[0], 4, "FIXME: TOTAL PAGE LOADS IS DOUBLE COUNTING");
+ is(getEventCounts()[1], 1, "Disable actions");
+ is(getEventCounts()[2], 1, "Enable actions");
+ todo_is(getShieldCounts()[0], 1, "FIXME: TOTAL PAGE LOADS WITHOUT TRACKING IS DOUBLE COUNTING");
+
+ yield promiseWindowClosed(newWin);
+
+ // Reset these to make counting easier for the next test
+ getEventsHistogram().clear();
+ getShieldHistogram().clear();
+ getEnabledHistogram().clear();
+});
+
+add_task(function* testPrivateBrowsing() {
+ let privateWin = yield promiseOpenAndLoadWindow({private: true}, true);
+ let tab = privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab();
+ let TrackingProtection = privateWin.TrackingProtection;
+ ok(TrackingProtection, "TP is attached to the browser window");
+
+ // Do a bunch of actions and make sure that no telemetry data is gathered
+ yield promiseTabLoadEvent(tab, BENIGN_PAGE);
+ yield promiseTabLoadEvent(tab, TRACKING_PAGE);
+ let tabReloadPromise = promiseTabLoadEvent(tab);
+ privateWin.document.querySelector("#tracking-action-unblock").doCommand();
+ yield tabReloadPromise;
+ tabReloadPromise = promiseTabLoadEvent(tab);
+ privateWin.document.querySelector("#tracking-action-block").doCommand();
+ yield tabReloadPromise;
+
+ // Sum up all the counts to make sure that nothing got logged
+ is(getEnabledCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode");
+ is(getEventCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode");
+ is(getShieldCounts().reduce((p, c) => p+c), 0, "Telemetry logging off in PB mode");
+
+ yield promiseWindowClosed(privateWin);
+});
diff --git a/browser/base/content/test/general/browser_typeAheadFind.js b/browser/base/content/test/general/browser_typeAheadFind.js
new file mode 100644
index 000000000..1d550944a
--- /dev/null
+++ b/browser/base/content/test/general/browser_typeAheadFind.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/. */
+
+add_task(function *() {
+ let testWindow = yield BrowserTestUtils.openNewBrowserWindow();
+
+ testWindow.gBrowser.loadURI("data:text/html,<h1>A Page</h1>");
+ yield BrowserTestUtils.browserLoaded(testWindow.gBrowser.selectedBrowser);
+
+ yield SimpleTest.promiseFocus(testWindow.gBrowser.selectedBrowser);
+
+ ok(!testWindow.gFindBarInitialized, "find bar is not initialized");
+
+ let findBarOpenPromise = promiseWaitForEvent(testWindow.gBrowser, "findbaropen");
+ EventUtils.synthesizeKey("/", {}, testWindow);
+ yield findBarOpenPromise;
+
+ ok(testWindow.gFindBarInitialized, "find bar is now initialized");
+
+ yield BrowserTestUtils.closeWindow(testWindow);
+});
diff --git a/browser/base/content/test/general/browser_unknownContentType_title.js b/browser/base/content/test/general/browser_unknownContentType_title.js
new file mode 100644
index 000000000..269406bdb
--- /dev/null
+++ b/browser/base/content/test/general/browser_unknownContentType_title.js
@@ -0,0 +1,33 @@
+const url = "data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3Ctitle%3ETest%20Page%3C%2Ftitle%3E%3C%2Fhead%3E%3C%2Fhtml%3E";
+const unknown_url = "http://example.com/browser/browser/base/content/test/general/unknownContentType_file.pif";
+
+function waitForNewWindow() {
+ return new Promise(resolve => {
+ let listener = (win) => {
+ Services.obs.removeObserver(listener, "toplevel-window-ready");
+ win.addEventListener("load", () => {
+ resolve(win);
+ });
+ };
+
+ Services.obs.addObserver(listener, "toplevel-window-ready", false)
+ });
+}
+
+add_task(function*() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ let browser = tab.linkedBrowser;
+ yield promiseTabLoaded(gBrowser.selectedTab);
+
+ is(gBrowser.contentTitle, "Test Page", "Should have the right title.")
+
+ browser.loadURI(unknown_url);
+ let win = yield waitForNewWindow();
+ is(win.location, "chrome://mozapps/content/downloads/unknownContentType.xul",
+ "Should have seen the unknown content dialog.");
+ is(gBrowser.contentTitle, "Test Page", "Should still have the right title.")
+
+ win.close();
+ yield promiseWaitForFocus(window);
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/general/browser_unloaddialogs.js b/browser/base/content/test/general/browser_unloaddialogs.js
new file mode 100644
index 000000000..bf3790b95
--- /dev/null
+++ b/browser/base/content/test/general/browser_unloaddialogs.js
@@ -0,0 +1,41 @@
+var testUrls =
+ [
+ "data:text/html,<script>" +
+ "function handle(evt) {" +
+ "evt.target.removeEventListener(evt.type, handle, true);" +
+ "try { alert('This should NOT appear'); } catch(e) { }" +
+ "}" +
+ "window.addEventListener('pagehide', handle, true);" +
+ "window.addEventListener('beforeunload', handle, true);" +
+ "window.addEventListener('unload', handle, true);" +
+ "</script><body>Testing alert during pagehide/beforeunload/unload</body>",
+ "data:text/html,<script>" +
+ "function handle(evt) {" +
+ "evt.target.removeEventListener(evt.type, handle, true);" +
+ "try { prompt('This should NOT appear'); } catch(e) { }" +
+ "}" +
+ "window.addEventListener('pagehide', handle, true);" +
+ "window.addEventListener('beforeunload', handle, true);" +
+ "window.addEventListener('unload', handle, true);" +
+ "</script><body>Testing prompt during pagehide/beforeunload/unload</body>",
+ "data:text/html,<script>" +
+ "function handle(evt) {" +
+ "evt.target.removeEventListener(evt.type, handle, true);" +
+ "try { confirm('This should NOT appear'); } catch(e) { }" +
+ "}" +
+ "window.addEventListener('pagehide', handle, true);" +
+ "window.addEventListener('beforeunload', handle, true);" +
+ "window.addEventListener('unload', handle, true);" +
+ "</script><body>Testing confirm during pagehide/beforeunload/unload</body>",
+ ];
+
+add_task(function*() {
+ for (let url of testUrls) {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ ok(true, "Loaded page " + url);
+ // Wait one turn of the event loop before closing, so everything settles.
+ yield new Promise(resolve => setTimeout(resolve, 0));
+ yield BrowserTestUtils.removeTab(tab);
+ ok(true, "Closed page " + url + " without timeout");
+ }
+});
diff --git a/browser/base/content/test/general/browser_utilityOverlay.js b/browser/base/content/test/general/browser_utilityOverlay.js
new file mode 100644
index 000000000..34adc00d9
--- /dev/null
+++ b/browser/base/content/test/general/browser_utilityOverlay.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 gTests = [
+ test_eventMatchesKey,
+ test_getTopWin,
+ test_getBoolPref,
+ test_openNewTabWith,
+ test_openUILink
+];
+
+function test () {
+ waitForExplicitFinish();
+ executeSoon(runNextTest);
+}
+
+function runNextTest() {
+ if (gTests.length) {
+ let testFun = gTests.shift();
+ info("Running " + testFun.name);
+ testFun()
+ }
+ else {
+ finish();
+ }
+}
+
+function test_eventMatchesKey() {
+ let eventMatchResult;
+ let key;
+ let checkEvent = function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ eventMatchResult = eventMatchesKey(e, key);
+ }
+ document.addEventListener("keypress", checkEvent);
+
+ try {
+ key = document.createElement("key");
+ let keyset = document.getElementById("mainKeyset");
+ key.setAttribute("key", "t");
+ key.setAttribute("modifiers", "accel");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("t", {accelKey: true});
+ is(eventMatchResult, true, "eventMatchesKey: one modifier");
+ keyset.removeChild(key);
+
+ key = document.createElement("key");
+ key.setAttribute("key", "g");
+ key.setAttribute("modifiers", "accel,shift");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("g", {accelKey: true, shiftKey: true});
+ is(eventMatchResult, true, "eventMatchesKey: combination modifiers");
+ keyset.removeChild(key);
+
+ key = document.createElement("key");
+ key.setAttribute("key", "w");
+ key.setAttribute("modifiers", "accel");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("f", {accelKey: true});
+ is(eventMatchResult, false, "eventMatchesKey: mismatch keys");
+ keyset.removeChild(key);
+
+ key = document.createElement("key");
+ key.setAttribute("keycode", "VK_DELETE");
+ keyset.appendChild(key);
+ EventUtils.synthesizeKey("VK_DELETE", {accelKey: true});
+ is(eventMatchResult, false, "eventMatchesKey: mismatch modifiers");
+ keyset.removeChild(key);
+ } finally {
+ // Make sure to remove the event listener so future tests don't
+ // fail when they simulate key presses.
+ document.removeEventListener("keypress", checkEvent);
+ }
+
+ runNextTest();
+}
+
+function test_getTopWin() {
+ is(getTopWin(), window, "got top window");
+ runNextTest();
+}
+
+
+function test_getBoolPref() {
+ is(getBoolPref("browser.search.openintab", false), false, "getBoolPref");
+ is(getBoolPref("this.pref.doesnt.exist", true), true, "getBoolPref fallback");
+ is(getBoolPref("this.pref.doesnt.exist", false), false, "getBoolPref fallback #2");
+ runNextTest();
+}
+
+function test_openNewTabWith() {
+ openNewTabWith("http://example.com/");
+ let tab = gBrowser.selectedTab = gBrowser.tabs[1];
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ is(tab.linkedBrowser.currentURI.spec, "http://example.com/", "example.com loaded");
+ gBrowser.removeCurrentTab();
+ runNextTest();
+ });
+}
+
+function test_openUILink() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ is(tab.linkedBrowser.currentURI.spec, "http://example.org/", "example.org loaded");
+ gBrowser.removeCurrentTab();
+ runNextTest();
+ });
+
+ openUILink("http://example.org/"); // defaults to "current"
+}
diff --git a/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js b/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js
new file mode 100644
index 000000000..c8f3cdc96
--- /dev/null
+++ b/browser/base/content/test/general/browser_viewSourceInTabOnViewSource.js
@@ -0,0 +1,55 @@
+function wait_while_tab_is_busy() {
+ return new Promise(resolve => {
+ let progressListener = {
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ gBrowser.removeProgressListener(this);
+ setTimeout(resolve, 0);
+ }
+ }
+ };
+ gBrowser.addProgressListener(progressListener);
+ });
+}
+
+// This function waits for the tab to stop being busy instead of waiting for it
+// to load, since the canViewSource change happens at that time.
+var with_new_tab_opened = Task.async(function* (options, taskFn) {
+ let busyPromise = wait_while_tab_is_busy();
+ let tab = yield BrowserTestUtils.openNewForegroundTab(options.gBrowser, options.url, false);
+ yield busyPromise;
+ yield taskFn(tab.linkedBrowser);
+ gBrowser.removeTab(tab);
+});
+
+add_task(function*() {
+ yield new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["view_source.tab", true],
+ ]}, resolve);
+ });
+});
+
+add_task(function* test_regular_page() {
+ function* test_expect_view_source_enabled(browser) {
+ ok(!XULBrowserWindow.canViewSource.hasAttribute("disabled"),
+ "View Source should be enabled");
+ }
+
+ yield with_new_tab_opened({
+ gBrowser,
+ url: "http://example.com",
+ }, test_expect_view_source_enabled);
+});
+
+add_task(function* test_view_source_page() {
+ function* test_expect_view_source_disabled(browser) {
+ ok(XULBrowserWindow.canViewSource.hasAttribute("disabled"),
+ "View Source should be disabled");
+ }
+
+ yield with_new_tab_opened({
+ gBrowser,
+ url: "view-source:http://example.com",
+ }, test_expect_view_source_disabled);
+});
diff --git a/browser/base/content/test/general/browser_visibleFindSelection.js b/browser/base/content/test/general/browser_visibleFindSelection.js
new file mode 100644
index 000000000..630490644
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleFindSelection.js
@@ -0,0 +1,52 @@
+add_task(function*() {
+ const childContent = "<div style='position: absolute; left: 2200px; background: green; width: 200px; height: 200px;'>" +
+ "div</div><div style='position: absolute; left: 0px; background: red; width: 200px; height: 200px;'>" +
+ "<span id='s'>div</span></div>";
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ yield promiseTabLoadEvent(tab, "data:text/html," + escape(childContent));
+ yield SimpleTest.promiseFocus(gBrowser.selectedBrowser.contentWindowAsCPOW);
+
+ let findBarOpenPromise = promiseWaitForEvent(gBrowser, "findbaropen");
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ yield findBarOpenPromise;
+
+ ok(gFindBarInitialized, "find bar is now initialized");
+
+ // Finds the div in the green box.
+ let scrollPromise = promiseWaitForEvent(gBrowser, "scroll");
+ EventUtils.synthesizeKey("d", {});
+ EventUtils.synthesizeKey("i", {});
+ EventUtils.synthesizeKey("v", {});
+ yield scrollPromise;
+
+ // Wait for one paint to ensure we've processed the previous key events and scrolling.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ return new Promise(
+ resolve => {
+ content.requestAnimationFrame(() => {
+ setTimeout(resolve, 0);
+ });
+ }
+ );
+ });
+
+ // Finds the div in the red box.
+ scrollPromise = promiseWaitForEvent(gBrowser, "scroll");
+ EventUtils.synthesizeKey("g", { accelKey: true });
+ yield scrollPromise;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ Assert.ok(content.document.getElementById("s").getBoundingClientRect().left >= 0,
+ "scroll should include find result");
+ });
+
+ // clear the find bar
+ EventUtils.synthesizeKey("a", { accelKey: true });
+ EventUtils.synthesizeKey("VK_DELETE", { });
+
+ gFindBar.close();
+ gBrowser.removeCurrentTab();
+});
+
diff --git a/browser/base/content/test/general/browser_visibleTabs.js b/browser/base/content/test/general/browser_visibleTabs.js
new file mode 100644
index 000000000..e9130bc18
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(function* () {
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+
+ // Add a tab that will get pinned
+ let pinned = gBrowser.addTab();
+ gBrowser.pinTab(pinned);
+
+ let testTab = gBrowser.addTab();
+
+ let visible = gBrowser.visibleTabs;
+ is(visible.length, 3, "3 tabs should be open");
+ is(visible[0], pinned, "the pinned tab is first");
+ is(visible[1], origTab, "original tab is next");
+ is(visible[2], testTab, "last created tab is last");
+
+ // Only show the test tab (but also get pinned and selected)
+ is(gBrowser.selectedTab, origTab, "sanity check that we're on the original tab");
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.visibleTabs.length, 3, "all 3 tabs are still visible");
+
+ // Select the test tab and only show that (and pinned)
+ gBrowser.selectedTab = testTab;
+ gBrowser.showOnlyTheseTabs([testTab]);
+
+ visible = gBrowser.visibleTabs;
+ is(visible.length, 2, "2 tabs should be visible including the pinned");
+ is(visible[0], pinned, "first is pinned");
+ is(visible[1], testTab, "next is the test tab");
+ is(gBrowser.tabs.length, 3, "3 tabs should still be open");
+
+ gBrowser.selectTabAtIndex(1);
+ is(gBrowser.selectedTab, testTab, "second tab is the test tab");
+ gBrowser.selectTabAtIndex(0);
+ is(gBrowser.selectedTab, pinned, "first tab is pinned");
+ gBrowser.selectTabAtIndex(2);
+ is(gBrowser.selectedTab, testTab, "no third tab, so no change");
+ gBrowser.selectTabAtIndex(0);
+ is(gBrowser.selectedTab, pinned, "switch back to the pinned");
+ gBrowser.selectTabAtIndex(2);
+ is(gBrowser.selectedTab, testTab, "no third tab, so select last tab");
+ gBrowser.selectTabAtIndex(-2);
+ is(gBrowser.selectedTab, pinned, "pinned tab is second from left (when orig tab is hidden)");
+ gBrowser.selectTabAtIndex(-1);
+ is(gBrowser.selectedTab, testTab, "last tab is the test tab");
+
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, pinned, "wrapped around the end to pinned");
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, testTab, "next to test tab");
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, pinned, "next to pinned again");
+
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, testTab, "going backwards to last tab");
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, pinned, "next to pinned");
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, testTab, "next to test tab again");
+
+ // Try showing all tabs
+ gBrowser.showOnlyTheseTabs(Array.slice(gBrowser.tabs));
+ is(gBrowser.visibleTabs.length, 3, "all 3 tabs are visible again");
+
+ // Select the pinned tab and show the testTab to make sure selection updates
+ gBrowser.selectedTab = pinned;
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.tabs[1], origTab, "make sure origTab is in the middle");
+ is(origTab.hidden, true, "make sure it's hidden");
+ gBrowser.removeTab(pinned);
+ is(gBrowser.selectedTab, testTab, "making sure origTab was skipped");
+ is(gBrowser.visibleTabs.length, 1, "only testTab is there");
+
+ // Only show one of the non-pinned tabs (but testTab is selected)
+ gBrowser.showOnlyTheseTabs([origTab]);
+ is(gBrowser.visibleTabs.length, 2, "got 2 tabs");
+
+ // Now really only show one of the tabs
+ gBrowser.showOnlyTheseTabs([testTab]);
+ visible = gBrowser.visibleTabs;
+ is(visible.length, 1, "only the original tab is visible");
+ is(visible[0], testTab, "it's the original tab");
+ is(gBrowser.tabs.length, 2, "still have 2 open tabs");
+
+ // Close the last visible tab and make sure we still get a visible tab
+ gBrowser.removeTab(testTab);
+ is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
+ is(gBrowser.tabs.length, 1, "sanity check that it matches");
+ is(gBrowser.selectedTab, origTab, "got the orig tab");
+ is(origTab.hidden, false, "and it's not hidden -- visible!");
+});
diff --git a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
new file mode 100644
index 000000000..827f86c05
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let tabOne = gBrowser.addTab("about:blank");
+ let tabTwo = gBrowser.addTab("http://mochi.test:8888/");
+ gBrowser.selectedTab = tabTwo;
+
+ let browser = gBrowser.getBrowserForTab(tabTwo);
+ let onLoad = function() {
+ browser.removeEventListener("load", onLoad, true);
+
+ gBrowser.showOnlyTheseTabs([tabTwo]);
+
+ is(gBrowser.visibleTabs.length, 1, "Only one tab is visible");
+
+ let uris = PlacesCommandHook.uniqueCurrentPages;
+ is(uris.length, 1, "Only one uri is returned");
+
+ is(uris[0].uri.spec, tabTwo.linkedBrowser.currentURI.spec, "It's the correct URI");
+
+ gBrowser.removeTab(tabOne);
+ gBrowser.removeTab(tabTwo);
+ Array.forEach(gBrowser.tabs, function(tab) {
+ gBrowser.showTab(tab);
+ });
+
+ finish();
+ }
+ browser.addEventListener("load", onLoad, true);
+}
diff --git a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
new file mode 100644
index 000000000..0a0ea87bd
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+ is(gBrowser.visibleTabs.length, 1, "1 tab should be open");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled");
+
+ // Add a tab
+ let testTab1 = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "2 tabs should be open");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled since there are two tabs with the same address");
+
+ let testTab2 = gBrowser.addTab("about:mozilla");
+ is(gBrowser.visibleTabs.length, 3, "3 tabs should be open");
+ // Wait for tab load, the code checks for currentURI.
+ testTab2.linkedBrowser.addEventListener("load", function () {
+ testTab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ is(Disabled(), false, "Bookmark All Tabs should be enabled since there are two tabs with different addresses");
+
+ // Hide the original tab
+ gBrowser.selectedTab = testTab2;
+ gBrowser.showOnlyTheseTabs([testTab2]);
+ is(gBrowser.visibleTabs.length, 1, "1 tab should be visible");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled as there is only one visible tab");
+
+ // Add a tab that will get pinned
+ let pinned = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "2 tabs should be visible now");
+ is(Disabled(), false, "Bookmark All Tabs should be available as there are two visible tabs");
+ gBrowser.pinTab(pinned);
+ is(Hidden(), false, "Bookmark All Tabs should be visible on a normal tab");
+ is(Disabled(), true, "Bookmark All Tabs should not be available since one tab is pinned");
+ gBrowser.selectedTab = pinned;
+ is(Hidden(), true, "Bookmark All Tabs should be hidden on a pinned tab");
+
+ // Show all tabs
+ let allTabs = Array.from(gBrowser.tabs);
+ gBrowser.showOnlyTheseTabs(allTabs);
+
+ // reset the environment
+ gBrowser.removeTab(testTab2);
+ gBrowser.removeTab(testTab1);
+ gBrowser.removeTab(pinned);
+ is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
+ is(gBrowser.tabs.length, 1, "sanity check that it matches");
+ is(Disabled(), true, "Bookmark All Tabs should be hidden");
+ is(gBrowser.selectedTab, origTab, "got the orig tab");
+ is(origTab.hidden, false, "and it's not hidden -- visible!");
+ finish();
+ }, true);
+}
+
+function Disabled() {
+ updateTabContextMenu();
+ return document.getElementById("Browser:BookmarkAllTabs").getAttribute("disabled") == "true";
+}
+
+function Hidden() {
+ updateTabContextMenu();
+ return document.getElementById("context_bookmarkAllTabs").hidden;
+}
diff --git a/browser/base/content/test/general/browser_visibleTabs_contextMenu.js b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
new file mode 100644
index 000000000..4fdab3d8a
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+
+add_task(function* test() {
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+ is(gBrowser.visibleTabs.length, 1, "there is one visible tab");
+ let testTab = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "there are now two visible tabs");
+
+ // Check the context menu with two tabs
+ updateTabContextMenu(origTab);
+ is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled");
+ is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled");
+
+
+ if (gFxAccounts.sendTabToDeviceEnabled) {
+ // Check the send tab to device menu item
+ const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
+ yield updateTabContextMenu(origTab, function* () {
+ yield openMenuItemSubmenu("context_sendTabToDevice");
+ });
+ is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
+ let targets = document.getElementById("context_sendTabToDevicePopupMenu").childNodes;
+ is(targets[0].getAttribute("label"), "Foo", "Foo target is present");
+ is(targets[1].getAttribute("label"), "Bar", "Bar target is present");
+ is(targets[3].getAttribute("label"), "All Devices", "All Devices target is present");
+ restoreRemoteClients(oldGetter);
+ }
+
+ // Hide the original tab.
+ gBrowser.selectedTab = testTab;
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.visibleTabs.length, 1, "now there is only one visible tab");
+
+ // Check the context menu with one tab.
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled when more than one tab exists");
+ is(document.getElementById("context_reloadAllTabs").disabled, true, "Reload All Tabs is disabled");
+
+ // Add a tab that will get pinned
+ // So now there's one pinned tab, one visible unpinned tab, and one hidden tab
+ let pinned = gBrowser.addTab();
+ gBrowser.pinTab(pinned);
+ is(gBrowser.visibleTabs.length, 2, "now there are two visible tabs");
+
+ // Check the context menu on the unpinned visible tab
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeOtherTabs").disabled, true, "Close Other Tabs is disabled");
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled");
+
+ // Show all tabs
+ let allTabs = Array.from(gBrowser.tabs);
+ gBrowser.showOnlyTheseTabs(allTabs);
+
+ // Check the context menu now
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeOtherTabs").disabled, false, "Close Other Tabs is enabled");
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled");
+
+ // Check the context menu of the original tab
+ // Close Tabs To The End should now be enabled
+ updateTabContextMenu(origTab);
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, false, "Close Tabs To The End is enabled");
+
+ gBrowser.removeTab(testTab);
+ gBrowser.removeTab(pinned);
+});
+
diff --git a/browser/base/content/test/general/browser_visibleTabs_tabPreview.js b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
new file mode 100644
index 000000000..7ce4b143f
--- /dev/null
+++ b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* test() {
+ gPrefService.setBoolPref("browser.ctrlTab.previews", true);
+
+ let [origTab] = gBrowser.visibleTabs;
+ let tabOne = gBrowser.addTab();
+ let tabTwo = gBrowser.addTab();
+
+ // test the ctrlTab.tabList
+ pressCtrlTab();
+ ok(ctrlTab.tabList.length, 3, "Show 3 tabs in tab preview");
+ releaseCtrl();
+
+ gBrowser.showOnlyTheseTabs([origTab]);
+ pressCtrlTab();
+ ok(ctrlTab.tabList.length, 1, "Show 1 tab in tab preview");
+ ok(!ctrlTab.isOpen, "With 1 tab open, Ctrl+Tab doesn't open the preview panel");
+
+ gBrowser.showOnlyTheseTabs([origTab, tabOne, tabTwo]);
+ pressCtrlTab();
+ ok(ctrlTab.isOpen, "With 3 tabs open, Ctrl+Tab does open the preview panel");
+ releaseCtrl();
+
+ // cleanup
+ gBrowser.removeTab(tabOne);
+ gBrowser.removeTab(tabTwo);
+
+ if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
+ gPrefService.clearUserPref("browser.ctrlTab.previews");
+});
+
+function pressCtrlTab(aShiftKey) {
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
+}
+
+function releaseCtrl() {
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
+}
diff --git a/browser/base/content/test/general/browser_web_channel.html b/browser/base/content/test/general/browser_web_channel.html
new file mode 100644
index 000000000..f117ccca2
--- /dev/null
+++ b/browser/base/content/test/general/browser_web_channel.html
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>web_channel_test</title>
+</head>
+<body>
+<script>
+ var IFRAME_SRC_ROOT = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
+
+ window.onload = function() {
+ var testName = window.location.search.replace(/^\?/, "");
+
+ switch (testName) {
+ case "generic":
+ test_generic();
+ break;
+ case "twoway":
+ test_twoWay();
+ break;
+ case "multichannel":
+ test_multichannel();
+ break;
+ case "iframe":
+ test_iframe();
+ break;
+ case "iframe_pre_redirect":
+ test_iframe_pre_redirect();
+ break;
+ case "unsolicited":
+ test_unsolicited();
+ break;
+ case "bubbles":
+ test_bubbles();
+ break;
+ case "object":
+ test_object();
+ break;
+ default:
+ throw new Error(`INVALID TEST NAME ${testName}`);
+ }
+ };
+
+ function test_generic() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "generic",
+ message: {
+ something: {
+ nested: "hello",
+ },
+ }
+ })
+ });
+
+ window.dispatchEvent(event);
+ }
+
+ function test_twoWay() {
+ var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "one",
+ },
+ })
+ });
+
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "two",
+ detail: e.detail.message,
+ },
+ }),
+ });
+
+ if (!e.detail.message.error) {
+ window.dispatchEvent(secondMessage);
+ }
+ }, true);
+
+ window.dispatchEvent(firstMessage);
+ }
+
+ function test_multichannel() {
+ var event1 = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "wrongchannel",
+ message: {},
+ })
+ });
+
+ var event2 = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "multichannel",
+ message: {},
+ })
+ });
+
+ window.dispatchEvent(event1);
+ window.dispatchEvent(event2);
+ }
+
+ function test_iframe() {
+ // Note that this message is the response to the message sent
+ // by the iframe! This is bad, as this page is *not* trusted.
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ // the test parent will fail if the echo message is received.
+ echoEventToChannel(e, "echo");
+ });
+
+ // only attach the iframe for the iframe test to avoid
+ // interfering with other tests.
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe");
+ document.body.appendChild(iframe);
+ }
+
+ function test_iframe_pre_redirect() {
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("src", IFRAME_SRC_ROOT + "?iframe_pre_redirect");
+ document.body.appendChild(iframe);
+ }
+
+ function test_unsolicited() {
+ // echo any unsolicted events back to chrome.
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ echoEventToChannel(e, "echo");
+ }, true);
+ }
+
+ function test_bubbles() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "not_a_window",
+ message: {
+ command: "start"
+ }
+ })
+ });
+
+ var nonWindowTarget = document.getElementById("not_a_window");
+
+ nonWindowTarget.addEventListener("WebChannelMessageToContent", function(e) {
+ echoEventToChannel(e, "not_a_window");
+ }, true);
+
+
+ nonWindowTarget.dispatchEvent(event);
+ }
+
+ function test_object() {
+ let objectMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "objects",
+ message: { type: "object" }
+ }
+ });
+
+ let stringMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "objects",
+ message: { type: "string" }
+ })
+ });
+ // Test fails if objectMessage is received, we send stringMessage to know
+ // when we should stop listening for objectMessage
+ window.dispatchEvent(objectMessage);
+ window.dispatchEvent(stringMessage);
+ }
+
+ function echoEventToChannel(e, channelId) {
+ var echoedEvent = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: channelId,
+ message: e.detail.message,
+ })
+ });
+
+ e.target.dispatchEvent(echoedEvent);
+ }
+</script>
+
+<div id="not_a_window"></div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_web_channel.js b/browser/base/content/test/general/browser_web_channel.js
new file mode 100644
index 000000000..abc1c6fef
--- /dev/null
+++ b/browser/base/content/test/general/browser_web_channel.js
@@ -0,0 +1,436 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
+ "resource://gre/modules/WebChannel.jsm");
+
+const HTTP_PATH = "http://example.com";
+const HTTP_ENDPOINT = "/browser/browser/base/content/test/general/browser_web_channel.html";
+const HTTP_MISMATCH_PATH = "http://example.org";
+const HTTP_IFRAME_PATH = "http://mochi.test:8888";
+const HTTP_REDIRECTED_IFRAME_PATH = "http://example.org";
+
+requestLongerTimeout(2); // timeouts in debug builds.
+
+// Keep this synced with /mobile/android/tests/browser/robocop/testWebChannel.js
+// as much as possible. (We only have that since we can't run browser chrome
+// tests on Android. Yet?)
+var gTests = [
+ {
+ desc: "WebChannel generic message",
+ run: function* () {
+ return new Promise(function(resolve, reject) {
+ let tab;
+ let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null));
+ channel.listen(function (id, message, target) {
+ is(id, "generic");
+ is(message.something.nested, "hello");
+ channel.stopListening();
+ gBrowser.removeTab(tab);
+ resolve();
+ });
+
+ tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?generic");
+ });
+ }
+ },
+ {
+ desc: "WebChannel generic message in a private window.",
+ run: function* () {
+ let promiseTestDone = new Promise(function(resolve, reject) {
+ let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH, null, null));
+ channel.listen(function(id, message, target) {
+ is(id, "generic");
+ is(message.something.nested, "hello");
+ channel.stopListening();
+ resolve();
+ });
+ });
+
+ const url = HTTP_PATH + HTTP_ENDPOINT + "?generic";
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, url);
+ yield promiseTestDone;
+ yield BrowserTestUtils.closeWindow(privateWindow);
+ }
+ },
+ {
+ desc: "WebChannel two way communication",
+ run: function* () {
+ return new Promise(function(resolve, reject) {
+ let tab;
+ let channel = new WebChannel("twoway", Services.io.newURI(HTTP_PATH, null, null));
+
+ channel.listen(function (id, message, sender) {
+ is(id, "twoway", "bad id");
+ ok(message.command, "command not ok");
+
+ if (message.command === "one") {
+ channel.send({ data: { nested: true } }, sender);
+ }
+
+ if (message.command === "two") {
+ is(message.detail.data.nested, true);
+ channel.stopListening();
+ gBrowser.removeTab(tab);
+ resolve();
+ }
+ });
+
+ tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?twoway");
+ });
+ }
+ },
+ {
+ desc: "WebChannel two way communication in an iframe",
+ run: function* () {
+ let parentChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+ let iframeChannel = new WebChannel("twoway", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
+ let promiseTestDone = new Promise(function (resolve, reject) {
+ parentChannel.listen(function (id, message, sender) {
+ reject(new Error("WebChannel message incorrectly sent to parent"));
+ });
+
+ iframeChannel.listen(function (id, message, sender) {
+ is(id, "twoway", "bad id (2)");
+ ok(message.command, "command not ok (2)");
+
+ if (message.command === "one") {
+ iframeChannel.send({ data: { nested: true } }, sender);
+ }
+
+ if (message.command === "two") {
+ is(message.detail.data.nested, true);
+ resolve();
+ }
+ });
+ });
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?iframe"
+ }, function* () {
+ yield promiseTestDone;
+ parentChannel.stopListening();
+ iframeChannel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel response to a redirected iframe",
+ run: function* () {
+ /**
+ * This test checks that WebChannel responses are only sent
+ * to an iframe if the iframe has not redirected to another origin.
+ * Test flow:
+ * 1. create a page, embed an iframe on origin A.
+ * 2. the iframe sends a message `redirecting`, then redirects to
+ * origin B.
+ * 3. the iframe at origin B is set up to echo any messages back to the
+ * test parent.
+ * 4. the test parent receives the `redirecting` message from origin A.
+ * the test parent creates a new channel with origin B.
+ * 5. when origin B is ready, it sends a `loaded` message to the test
+ * parent, letting the test parent know origin B is ready to echo
+ * messages.
+ * 5. the test parent tries to send a response to origin A. If the
+ * WebChannel does not perform a valid origin check, the response
+ * will be received by origin B. If the WebChannel does perform
+ * a valid origin check, the response will not be sent.
+ * 6. the test parent sends a `done` message to origin B, which origin
+ * B echoes back. If the response to origin A is not echoed but
+ * the message to origin B is, then hooray, the test passes.
+ */
+
+ let preRedirectChannel = new WebChannel("pre_redirect", Services.io.newURI(HTTP_IFRAME_PATH, null, null));
+ let postRedirectChannel = new WebChannel("post_redirect", Services.io.newURI(HTTP_REDIRECTED_IFRAME_PATH, null, null));
+
+ let promiseTestDone = new Promise(function (resolve, reject) {
+ preRedirectChannel.listen(function (id, message, preRedirectSender) {
+ if (message.command === "redirecting") {
+
+ postRedirectChannel.listen(function (aId, aMessage, aPostRedirectSender) {
+ is(aId, "post_redirect");
+ isnot(aMessage.command, "no_response_expected");
+
+ if (aMessage.command === "loaded") {
+ // The message should not be received on the preRedirectChannel
+ // because the target window has redirected.
+ preRedirectChannel.send({ command: "no_response_expected" }, preRedirectSender);
+ postRedirectChannel.send({ command: "done" }, aPostRedirectSender);
+ } else if (aMessage.command === "done") {
+ resolve();
+ } else {
+ reject(new Error(`Unexpected command ${aMessage.command}`));
+ }
+ });
+ } else {
+ reject(new Error(`Unexpected command ${message.command}`));
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?iframe_pre_redirect"
+ }, function* () {
+ yield promiseTestDone;
+ preRedirectChannel.stopListening();
+ postRedirectChannel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel multichannel",
+ run: function* () {
+ return new Promise(function(resolve, reject) {
+ let tab;
+ let channel = new WebChannel("multichannel", Services.io.newURI(HTTP_PATH, null, null));
+
+ channel.listen(function (id, message, sender) {
+ is(id, "multichannel");
+ gBrowser.removeTab(tab);
+ resolve();
+ });
+
+ tab = gBrowser.addTab(HTTP_PATH + HTTP_ENDPOINT + "?multichannel");
+ });
+ }
+ },
+ {
+ desc: "WebChannel unsolicited send, using system principal",
+ run: function* () {
+ let channel = new WebChannel("echo", Services.io.newURI(HTTP_PATH, null, null));
+
+ // an unsolicted message is sent from Chrome->Content which is then
+ // echoed back. If the echo is received here, then the content
+ // received the message.
+ let messagePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ is(id, "echo");
+ is(message.command, "unsolicited");
+
+ resolve()
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+ }, function* (targetBrowser) {
+ channel.send({ command: "unsolicited" }, {
+ browser: targetBrowser,
+ principal: Services.scriptSecurityManager.getSystemPrincipal()
+ });
+ yield messagePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel unsolicited send, using target origin's principal",
+ run: function* () {
+ let targetURI = Services.io.newURI(HTTP_PATH, null, null);
+ let channel = new WebChannel("echo", targetURI);
+
+ // an unsolicted message is sent from Chrome->Content which is then
+ // echoed back. If the echo is received here, then the content
+ // received the message.
+ let messagePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ is(id, "echo");
+ is(message.command, "unsolicited");
+
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+ }, function* (targetBrowser) {
+
+ channel.send({ command: "unsolicited" }, {
+ browser: targetBrowser,
+ principal: Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI)
+ });
+
+ yield messagePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel unsolicited send with principal mismatch",
+ run: function* () {
+ let targetURI = Services.io.newURI(HTTP_PATH, null, null);
+ let channel = new WebChannel("echo", targetURI);
+
+ // two unsolicited messages are sent from Chrome->Content. The first,
+ // `unsolicited_no_response_expected` is sent to the wrong principal
+ // and should not be echoed back. The second, `done`, is sent to the
+ // correct principal and should be echoed back.
+ let messagePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ is(id, "echo");
+
+ if (message.command === "done") {
+ resolve();
+ } else {
+ reject(new Error(`Unexpected command ${message.command}`));
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited"
+ }, function* (targetBrowser) {
+
+ let mismatchURI = Services.io.newURI(HTTP_MISMATCH_PATH, null, null);
+ let mismatchPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(mismatchURI);
+
+ // send a message to the wrong principal. It should not be delivered
+ // to content, and should not be echoed back.
+ channel.send({ command: "unsolicited_no_response_expected" }, {
+ browser: targetBrowser,
+ principal: mismatchPrincipal
+ });
+
+ let targetPrincipal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(targetURI);
+
+ // send the `done` message to the correct principal. It
+ // should be echoed back.
+ channel.send({ command: "done" }, {
+ browser: targetBrowser,
+ principal: targetPrincipal
+ });
+
+ yield messagePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel non-window target",
+ run: function* () {
+ /**
+ * This test ensures messages can be received from and responses
+ * sent to non-window elements.
+ *
+ * First wait for the non-window element to send a "start" message.
+ * Then send the non-window element a "done" message.
+ * The non-window element will echo the "done" message back, if it
+ * receives the message.
+ * Listen for the response. If received, good to go!
+ */
+ let channel = new WebChannel("not_a_window", Services.io.newURI(HTTP_PATH, null, null));
+
+ let testDonePromise = new Promise(function (resolve, reject) {
+ channel.listen(function (id, message, sender) {
+ if (message.command === "start") {
+ channel.send({ command: "done" }, sender);
+ } else if (message.command === "done") {
+ resolve();
+ } else {
+ reject(new Error(`Unexpected command ${message.command}`));
+ }
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?bubbles"
+ }, function* () {
+ yield testDonePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel disallows non-string message from non-whitelisted origin",
+ run: function* () {
+ /**
+ * This test ensures that non-string messages can't be sent via WebChannels.
+ * We create a page (on a non-whitelisted origin) which should send us two
+ * messages immediately. The first message has an object for it's detail,
+ * and the second has a string. We check that we only get the second
+ * message.
+ */
+ let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null));
+ let testDonePromise = new Promise((resolve, reject) => {
+ channel.listen((id, message, sender) => {
+ is(id, "objects");
+ is(message.type, "string");
+ resolve();
+ });
+ });
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?object"
+ }, function* () {
+ yield testDonePromise;
+ channel.stopListening();
+ });
+ }
+ },
+ {
+ desc: "WebChannel allows both string and non-string message from whitelisted origin",
+ run: function* () {
+ /**
+ * Same process as above, but we whitelist the origin before loading the page,
+ * and expect to get *both* messages back (each exactly once).
+ */
+ let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH, null, null));
+
+ let testDonePromise = new Promise((resolve, reject) => {
+ let sawObject = false;
+ let sawString = false;
+ channel.listen((id, message, sender) => {
+ is(id, "objects");
+ if (message.type === "object") {
+ ok(!sawObject);
+ sawObject = true;
+ } else if (message.type === "string") {
+ ok(!sawString);
+ sawString = true;
+ } else {
+ reject(new Error(`Unknown message type: ${message.type}`))
+ }
+ if (sawObject && sawString) {
+ resolve();
+ }
+ });
+ });
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " " + HTTP_PATH;
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: HTTP_PATH + HTTP_ENDPOINT + "?object"
+ }, function* () {
+ yield testDonePromise;
+ Services.prefs.setCharPref(webchannelWhitelistPref, origWhitelist);
+ channel.stopListening();
+ });
+ }
+ }
+]; // gTests
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ for (let testCase of gTests) {
+ info("Running: " + testCase.desc);
+ yield testCase.run();
+ }
+ }).then(finish, ex => {
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
diff --git a/browser/base/content/test/general/browser_web_channel_iframe.html b/browser/base/content/test/general/browser_web_channel_iframe.html
new file mode 100644
index 000000000..7900e7530
--- /dev/null
+++ b/browser/base/content/test/general/browser_web_channel_iframe.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>web_channel_test (iframe)</title>
+</head>
+<body>
+<script>
+ var REDIRECTED_IFRAME_SRC_ROOT = "http://example.org/browser/browser/base/content/test/general/browser_web_channel_iframe.html";
+
+ window.onload = function() {
+ var testName = window.location.search.replace(/^\?/, "");
+ switch (testName) {
+ case "iframe":
+ test_iframe();
+ break;
+ case "iframe_pre_redirect":
+ test_iframe_pre_redirect();
+ break;
+ case "iframe_post_redirect":
+ test_iframe_post_redirect();
+ break;
+ default:
+ throw new Error(`INVALID TEST NAME ${testName}`);
+ }
+ };
+
+ function test_iframe() {
+ var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "one",
+ },
+ })
+ });
+
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ var secondMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "twoway",
+ message: {
+ command: "two",
+ detail: e.detail.message,
+ },
+ }),
+ });
+
+ if (!e.detail.message.error) {
+ window.dispatchEvent(secondMessage);
+ }
+ }, true);
+
+ window.dispatchEvent(firstMessage);
+ }
+
+
+ function test_iframe_pre_redirect() {
+ var firstMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "pre_redirect",
+ message: {
+ command: "redirecting",
+ },
+ }),
+ });
+ window.dispatchEvent(firstMessage);
+ document.location = REDIRECTED_IFRAME_SRC_ROOT + "?iframe_post_redirect";
+ }
+
+ function test_iframe_post_redirect() {
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ var echoMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "post_redirect",
+ message: e.detail.message,
+ }),
+ });
+
+ window.dispatchEvent(echoMessage);
+ }, true);
+
+ // Let the test parent know the page has loaded and is ready to echo events
+ var loadedMessage = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "post_redirect",
+ message: {
+ command: "loaded",
+ },
+ }),
+ });
+ window.dispatchEvent(loadedMessage);
+ }
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/browser_windowactivation.js b/browser/base/content/test/general/browser_windowactivation.js
new file mode 100644
index 000000000..ae4ba75dc
--- /dev/null
+++ b/browser/base/content/test/general/browser_windowactivation.js
@@ -0,0 +1,183 @@
+/*
+ * This test checks that window activation state is set properly with multiple tabs.
+ */
+
+var testPage = "data:text/html,<body><style>:-moz-window-inactive { background-color: red; }</style><div id='area'></div></body>";
+
+var colorChangeNotifications = 0;
+var otherWindow;
+
+var browser1, browser2;
+
+function test() {
+ waitForExplicitFinish();
+ waitForFocus(reallyRunTests);
+}
+
+function reallyRunTests() {
+
+ let tab1 = gBrowser.addTab();
+ let tab2 = gBrowser.addTab();
+ browser1 = gBrowser.getBrowserForTab(tab1);
+ browser2 = gBrowser.getBrowserForTab(tab2);
+
+ gURLBar.focus();
+
+ var loadCount = 0;
+ function check()
+ {
+ // wait for both tabs to load
+ if (++loadCount != 2) {
+ return;
+ }
+
+ browser1.removeEventListener("load", check, true);
+ browser2.removeEventListener("load", check, true);
+
+ sendGetBackgroundRequest(true);
+ }
+
+ // The test performs four checks, using -moz-window-inactive on two child tabs.
+ // First, the initial state should be transparent. The second check is done
+ // while another window is focused. The third check is done after that window
+ // is closed and the main window focused again. The fourth check is done after
+ // switching to the second tab.
+ window.messageManager.addMessageListener("Test:BackgroundColorChanged", function(message) {
+ colorChangeNotifications++;
+
+ switch (colorChangeNotifications) {
+ case 1:
+ is(message.data.color, "transparent", "first window initial");
+ break;
+ case 2:
+ is(message.data.color, "transparent", "second window initial");
+ runOtherWindowTests();
+ break;
+ case 3:
+ is(message.data.color, "rgb(255, 0, 0)", "first window lowered");
+ break;
+ case 4:
+ is(message.data.color, "rgb(255, 0, 0)", "second window lowered");
+ sendGetBackgroundRequest(true);
+ otherWindow.close();
+ break;
+ case 5:
+ is(message.data.color, "transparent", "first window raised");
+ break;
+ case 6:
+ is(message.data.color, "transparent", "second window raised");
+ gBrowser.selectedTab = tab2;
+ break;
+ case 7:
+ is(message.data.color, "transparent", "first window after tab switch");
+ break;
+ case 8:
+ is(message.data.color, "transparent", "second window after tab switch");
+ finishTest();
+ break;
+ case 9:
+ ok(false, "too many color change notifications");
+ break;
+ }
+ });
+
+ window.messageManager.addMessageListener("Test:FocusReceived", function(message) {
+ // No color change should occur after a tab switch.
+ if (colorChangeNotifications == 6) {
+ sendGetBackgroundRequest(false);
+ }
+ });
+
+ window.messageManager.addMessageListener("Test:ActivateEvent", function(message) {
+ ok(message.data.ok, "Test:ActivateEvent");
+ });
+
+ window.messageManager.addMessageListener("Test:DeactivateEvent", function(message) {
+ ok(message.data.ok, "Test:DeactivateEvent");
+ });
+
+ browser1.addEventListener("load", check, true);
+ browser2.addEventListener("load", check, true);
+ browser1.contentWindow.location = testPage;
+ browser2.contentWindow.location = testPage;
+
+ browser1.messageManager.loadFrameScript("data:,(" + childFunction.toString() + ")();", true);
+ browser2.messageManager.loadFrameScript("data:,(" + childFunction.toString() + ")();", true);
+
+ gBrowser.selectedTab = tab1;
+}
+
+function sendGetBackgroundRequest(ifChanged)
+{
+ browser1.messageManager.sendAsyncMessage("Test:GetBackgroundColor", { ifChanged: ifChanged });
+ browser2.messageManager.sendAsyncMessage("Test:GetBackgroundColor", { ifChanged: ifChanged });
+}
+
+function runOtherWindowTests() {
+ otherWindow = window.open("data:text/html,<body>Hi</body>", "", "chrome");
+ waitForFocus(function () {
+ sendGetBackgroundRequest(true);
+ }, otherWindow);
+}
+
+function finishTest()
+{
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ otherWindow = null;
+ finish();
+}
+
+function childFunction()
+{
+ let oldColor = null;
+
+ let expectingResponse = false;
+ let ifChanged = true;
+
+ addMessageListener("Test:GetBackgroundColor", function(message) {
+ expectingResponse = true;
+ ifChanged = message.data.ifChanged;
+ });
+
+ content.addEventListener("focus", function () {
+ sendAsyncMessage("Test:FocusReceived", { });
+ }, false);
+
+ var windowGotActivate = false;
+ var windowGotDeactivate = false;
+ addEventListener("activate", function() {
+ sendAsyncMessage("Test:ActivateEvent", { ok: !windowGotActivate });
+ windowGotActivate = false;
+ });
+
+ addEventListener("deactivate", function() {
+ sendAsyncMessage("Test:DeactivateEvent", { ok: !windowGotDeactivate });
+ windowGotDeactivate = false;
+ });
+ content.addEventListener("activate", function() {
+ windowGotActivate = true;
+ });
+
+ content.addEventListener("deactivate", function() {
+ windowGotDeactivate = true;
+ });
+
+ content.setInterval(function () {
+ if (!expectingResponse) {
+ return;
+ }
+
+ let area = content.document.getElementById("area");
+ if (!area) {
+ return; /* hasn't loaded yet */
+ }
+
+ let color = content.getComputedStyle(area, "").backgroundColor;
+ if (oldColor != color || !ifChanged) {
+ expectingResponse = false;
+ oldColor = color;
+ sendAsyncMessage("Test:BackgroundColorChanged", { color: color });
+ }
+ }, 20);
+}
diff --git a/browser/base/content/test/general/browser_windowopen_reflows.js b/browser/base/content/test/general/browser_windowopen_reflows.js
new file mode 100644
index 000000000..7dac8aad6
--- /dev/null
+++ b/browser/base/content/test/general/browser_windowopen_reflows.js
@@ -0,0 +1,117 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const EXPECTED_REFLOWS = [
+ // handleEvent flushes layout to get the tabstrip width after a resize.
+ "handleEvent@chrome://browser/content/tabbrowser.xml|",
+
+ // Loading a tab causes a reflow.
+ "loadTabs@chrome://browser/content/tabbrowser.xml|" +
+ "loadOneOrMoreURIs@chrome://browser/content/browser.js|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
+
+ // Selecting the address bar causes a reflow.
+ "select@chrome://global/content/bindings/textbox.xml|" +
+ "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
+
+ // Focusing the content area causes a reflow.
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|",
+
+ // Sometimes sessionstore collects data during this test, which causes a sync reflow
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=892154 will fix this)
+ "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm",
+];
+
+if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") {
+ // TabsInTitlebar._update causes a reflow on OS X and Windows trying to do calculations
+ // since layout info is already dirty. This doesn't seem to happen before
+ // MozAfterPaint on Linux.
+ EXPECTED_REFLOWS.push("TabsInTitlebar._update/rect@chrome://browser/content/browser-tabsintitlebar.js|" +
+ "TabsInTitlebar._update@chrome://browser/content/browser-tabsintitlebar.js|" +
+ "updateAppearance@chrome://browser/content/browser-tabsintitlebar.js|" +
+ "handleEvent@chrome://browser/content/tabbrowser.xml|");
+}
+
+if (Services.appinfo.OS == "Darwin") {
+ // _onOverflow causes a reflow getting widths.
+ EXPECTED_REFLOWS.push("OverflowableToolbar.prototype._onOverflow@resource:///modules/CustomizableUI.jsm|" +
+ "OverflowableToolbar.prototype.init@resource:///modules/CustomizableUI.jsm|" +
+ "OverflowableToolbar.prototype.observe@resource:///modules/CustomizableUI.jsm|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|");
+ // Same as above since in packaged builds there are no function names and the resource URI includes "app"
+ EXPECTED_REFLOWS.push("@resource://app/modules/CustomizableUI.jsm|" +
+ "@resource://app/modules/CustomizableUI.jsm|" +
+ "@resource://app/modules/CustomizableUI.jsm|" +
+ "gBrowserInit._delayedStartup@chrome://browser/content/browser.js|");
+}
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when opening new windows.
+ */
+function test() {
+ waitForExplicitFinish();
+
+ // Add a reflow observer and open a new window
+ let win = OpenBrowserWindow();
+ let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ docShell.addWeakReflowObserver(observer);
+
+ // Wait until the mozafterpaint event occurs.
+ waitForMozAfterPaint(win, function paintListener() {
+ // Remove reflow observer and clean up.
+ docShell.removeWeakReflowObserver(observer);
+ win.close();
+
+ finish();
+ });
+}
+
+var observer = {
+ reflow: function (start, end) {
+ // Gather information about the current code path.
+ let stack = new Error().stack;
+ let path = stack.split("\n").slice(1).map(line => {
+ return line.replace(/:\d+:\d+$/, "");
+ }).join("|");
+ let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
+
+ // Stack trace is empty. Reflow was triggered by native code.
+ if (path === "") {
+ return;
+ }
+
+ // Check if this is an expected reflow.
+ for (let expectedStack of EXPECTED_REFLOWS) {
+ if (path.startsWith(expectedStack) ||
+ // Accept an empty function name for gBrowserInit._delayedStartup or TabsInTitlebar._update to workaround bug 906578.
+ path.startsWith(expectedStack.replace(/(^|\|)(gBrowserInit\._delayedStartup|TabsInTitlebar\._update)@/, "$1@"))) {
+ ok(true, "expected uninterruptible reflow '" + expectedStack + "'");
+ return;
+ }
+ }
+
+ ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
+ },
+
+ reflowInterruptible: function (start, end) {
+ // We're not interested in interruptible reflows.
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+function waitForMozAfterPaint(win, callback) {
+ win.addEventListener("MozAfterPaint", function onEnd(event) {
+ if (event.target != win)
+ return;
+ win.removeEventListener("MozAfterPaint", onEnd);
+ executeSoon(callback);
+ });
+}
diff --git a/browser/base/content/test/general/browser_zbug569342.js b/browser/base/content/test/general/browser_zbug569342.js
new file mode 100644
index 000000000..2dac5acde
--- /dev/null
+++ b/browser/base/content/test/general/browser_zbug569342.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gTab = null;
+
+function load(url, cb) {
+ gTab = gBrowser.addTab(url);
+ gBrowser.addEventListener("load", function (event) {
+ if (event.target.location != url)
+ return;
+
+ gBrowser.removeEventListener("load", arguments.callee, true);
+ // Trigger onLocationChange by switching tabs.
+ gBrowser.selectedTab = gTab;
+ cb();
+ }, true);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(gFindBar.hidden, "Find bar should not be visible by default");
+
+ // Open the Find bar before we navigate to pages that shouldn't have it.
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(!gFindBar.hidden, "Find bar should be visible");
+
+ nextTest();
+}
+
+var urls = [
+ "about:config",
+ "about:addons",
+];
+
+function nextTest() {
+ let url = urls.shift();
+ if (url) {
+ testFindDisabled(url, nextTest);
+ } else {
+ // Make sure the find bar is re-enabled after disabled page is closed.
+ testFindEnabled("about:blank", function () {
+ EventUtils.synthesizeKey("VK_ESCAPE", { });
+ ok(gFindBar.hidden, "Find bar should now be hidden");
+ finish();
+ });
+ }
+}
+
+function testFindDisabled(url, cb) {
+ load(url, function() {
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ EventUtils.synthesizeKey("/", {}, gTab.linkedBrowser.contentWindow);
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ ok(document.getElementById("cmd_find").getAttribute("disabled"),
+ "Find command should be disabled");
+
+ gBrowser.removeTab(gTab);
+ cb();
+ });
+}
+
+function testFindEnabled(url, cb) {
+ load(url, function() {
+ ok(!document.getElementById("cmd_find").getAttribute("disabled"),
+ "Find command should not be disabled");
+
+ // Open Find bar and then close it.
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(!gFindBar.hidden, "Find bar should be visible again");
+ EventUtils.synthesizeKey("VK_ESCAPE", { });
+ ok(gFindBar.hidden, "Find bar should now be hidden");
+
+ gBrowser.removeTab(gTab);
+ cb();
+ });
+}
diff --git a/browser/base/content/test/general/bug1262648_string_with_newlines.dtd b/browser/base/content/test/general/bug1262648_string_with_newlines.dtd
new file mode 100644
index 000000000..308072c4e
--- /dev/null
+++ b/browser/base/content/test/general/bug1262648_string_with_newlines.dtd
@@ -0,0 +1,3 @@
+<!ENTITY foo.bar "This string
+contains
+newlines!"> \ No newline at end of file
diff --git a/browser/base/content/test/general/bug364677-data.xml b/browser/base/content/test/general/bug364677-data.xml
new file mode 100644
index 000000000..b48915c05
--- /dev/null
+++ b/browser/base/content/test/general/bug364677-data.xml
@@ -0,0 +1,5 @@
+<rss version="2.0">
+ <channel>
+ <title>t</title>
+ </channel>
+</rss>
diff --git a/browser/base/content/test/general/bug364677-data.xml^headers^ b/browser/base/content/test/general/bug364677-data.xml^headers^
new file mode 100644
index 000000000..f203c6368
--- /dev/null
+++ b/browser/base/content/test/general/bug364677-data.xml^headers^
@@ -0,0 +1 @@
+Content-Type: text/xml
diff --git a/browser/base/content/test/general/bug395533-data.txt b/browser/base/content/test/general/bug395533-data.txt
new file mode 100644
index 000000000..e0ed39850
--- /dev/null
+++ b/browser/base/content/test/general/bug395533-data.txt
@@ -0,0 +1,6 @@
+<rss version="2.0">
+ <channel>
+ <link>http://example.org/</link>
+ <title>t</title>
+ </channel>
+</rss>
diff --git a/browser/base/content/test/general/bug592338.html b/browser/base/content/test/general/bug592338.html
new file mode 100644
index 000000000..159b21a76
--- /dev/null
+++ b/browser/base/content/test/general/bug592338.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<script type="text/javascript">
+var theme = {
+ id: "test",
+ name: "Test Background",
+ headerURL: "http://example.com/firefox/personas/01/header.jpg",
+ footerURL: "http://example.com/firefox/personas/01/footer.jpg",
+ textcolor: "#fff",
+ accentcolor: "#6b6b6b"
+};
+
+function setTheme(node) {
+ node.setAttribute("data-browsertheme", JSON.stringify(theme));
+ var event = document.createEvent("Events");
+ event.initEvent("InstallBrowserTheme", true, false);
+ node.dispatchEvent(event);
+}
+</script>
+</head>
+<body>
+<a id="theme-install" href="#" onclick="setTheme(this)">Install</a>
+</body>
+</html>
diff --git a/browser/base/content/test/general/bug792517-2.html b/browser/base/content/test/general/bug792517-2.html
new file mode 100644
index 000000000..bfc24d817
--- /dev/null
+++ b/browser/base/content/test/general/bug792517-2.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<a href="bug792517.sjs" id="fff">this is a link</a>
+</body>
+</html>
diff --git a/browser/base/content/test/general/bug792517.html b/browser/base/content/test/general/bug792517.html
new file mode 100644
index 000000000..e7c040bf1
--- /dev/null
+++ b/browser/base/content/test/general/bug792517.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<img src="moz.png" id="img">
+</body>
+</html>
diff --git a/browser/base/content/test/general/bug792517.sjs b/browser/base/content/test/general/bug792517.sjs
new file mode 100644
index 000000000..91e5aa23f
--- /dev/null
+++ b/browser/base/content/test/general/bug792517.sjs
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ if (aRequest.hasHeader('Cookie')) {
+ aResponse.write("cookie-present");
+ } else {
+ aResponse.setHeader("Set-Cookie", "foopy=1");
+ aResponse.write("cookie-not-present");
+ }
+}
diff --git a/browser/base/content/test/general/bug839103.css b/browser/base/content/test/general/bug839103.css
new file mode 100644
index 000000000..611907d3d
--- /dev/null
+++ b/browser/base/content/test/general/bug839103.css
@@ -0,0 +1 @@
+* {}
diff --git a/browser/base/content/test/general/clipboard_pastefile.html b/browser/base/content/test/general/clipboard_pastefile.html
new file mode 100644
index 000000000..fcbf60ed2
--- /dev/null
+++ b/browser/base/content/test/general/clipboard_pastefile.html
@@ -0,0 +1,37 @@
+<html><body>
+<script>
+function checkPaste(event)
+{
+ let output = document.getElementById("output");
+ output.textContent = checkPasteHelper(event);
+}
+
+function checkPasteHelper(event)
+{
+ let dt = event.clipboardData;
+ if (dt.types.length != 2)
+ return "Wrong number of types; got " + dt.types.length;
+
+ for (let type of dt.types) {
+ if (type != "Files" && type != "application/x-moz-file")
+ return "Invalid type for types; got" + type;
+ }
+
+ for (let type of dt.mozTypesAt(0)) {
+ if (type != "Files" && type != "application/x-moz-file")
+ return "Invalid type for mozTypesAt; got" + type;
+ }
+
+ if (dt.getData("text/plain"))
+ return "text/plain found with getData";
+ if (dt.mozGetDataAt("text/plain", 0))
+ return "text/plain found with mozGetDataAt";
+
+ return "Passed";
+}
+</script>
+
+<input id="input" onpaste="checkPaste(event)">
+<div id="output"></div>
+
+</body></html>
diff --git a/browser/base/content/test/general/close_beforeunload.html b/browser/base/content/test/general/close_beforeunload.html
new file mode 100644
index 000000000..4b62002cc
--- /dev/null
+++ b/browser/base/content/test/general/close_beforeunload.html
@@ -0,0 +1,8 @@
+<body>
+ <p>I will close myself if you close me.</p>
+ <script>
+ window.onbeforeunload = function() {
+ window.close();
+ };
+ </script>
+</body>
diff --git a/browser/base/content/test/general/close_beforeunload_opens_second_tab.html b/browser/base/content/test/general/close_beforeunload_opens_second_tab.html
new file mode 100644
index 000000000..243307a0e
--- /dev/null
+++ b/browser/base/content/test/general/close_beforeunload_opens_second_tab.html
@@ -0,0 +1,3 @@
+<body>
+ <a href="#" onclick="window.open('close_beforeunload.html', '_blank')">Open second tab</a>
+</body>
diff --git a/browser/base/content/test/general/contentSearchUI.html b/browser/base/content/test/general/contentSearchUI.html
new file mode 100644
index 000000000..3750ac2b0
--- /dev/null
+++ b/browser/base/content/test/general/contentSearchUI.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<html>
+<head>
+<meta charset="utf-8">
+<script type="application/javascript;version=1.8"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js">
+</script>
+<script type="application/javascript;version=1.8"
+ src="chrome://browser/content/contentSearchUI.js">
+</script>
+<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css"/>
+</head>
+<body>
+
+<div id="container" style="position: relative;"><input type="text" value=""/></div>
+
+</body>
+</html>
diff --git a/browser/base/content/test/general/contentSearchUI.js b/browser/base/content/test/general/contentSearchUI.js
new file mode 100644
index 000000000..0e46230a2
--- /dev/null
+++ b/browser/base/content/test/general/contentSearchUI.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+(function () {
+
+const TEST_MSG = "ContentSearchUIControllerTest";
+const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
+var gController;
+
+addMessageListener(TEST_MSG, msg => {
+ messageHandlers[msg.data.type](msg.data.data);
+});
+
+var messageHandlers = {
+
+ init: function() {
+ Services.search.currentEngine = Services.search.getEngineByName(ENGINE_NAME);
+ let input = content.document.querySelector("input");
+ gController =
+ new content.ContentSearchUIController(input, input.parentNode, "test", "test");
+ content.addEventListener("ContentSearchService", function listener(aEvent) {
+ if (aEvent.detail.type == "State" &&
+ gController.defaultEngine.name == ENGINE_NAME) {
+ content.removeEventListener("ContentSearchService", listener);
+ ack("init");
+ }
+ });
+ gController.remoteTimeout = 5000;
+ },
+
+ key: function (arg) {
+ let keyName = typeof(arg) == "string" ? arg : arg.key;
+ content.synthesizeKey(keyName, arg.modifiers || {});
+ let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
+ wait(ack.bind(null, "key"));
+ },
+
+ startComposition: function (arg) {
+ content.synthesizeComposition({ type: "compositionstart", data: "" });
+ ack("startComposition");
+ },
+
+ changeComposition: function (arg) {
+ let data = typeof(arg) == "string" ? arg : arg.data;
+ content.synthesizeCompositionChange({
+ composition: {
+ string: data,
+ clauses: [
+ { length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: data.length, length: 0 }
+ });
+ let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
+ wait(ack.bind(null, "changeComposition"));
+ },
+
+ commitComposition: function () {
+ content.synthesizeComposition({ type: "compositioncommitasis" });
+ ack("commitComposition");
+ },
+
+ focus: function () {
+ gController.input.focus();
+ ack("focus");
+ },
+
+ blur: function () {
+ gController.input.blur();
+ ack("blur");
+ },
+
+ waitForSearch: function () {
+ waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData));
+ },
+
+ waitForSearchSettings: function () {
+ waitForContentSearchEvent("ManageEngines",
+ aData => ack("waitForSearchSettings", aData));
+ },
+
+ mousemove: function (itemIndex) {
+ let row;
+ if (itemIndex == -1) {
+ row = gController._table.firstChild;
+ }
+ else {
+ let allElts = [...gController._suggestionsList.children,
+ ...gController._oneOffButtons,
+ content.document.getElementById("contentSearchSettingsButton")];
+ row = allElts[itemIndex];
+ }
+ let event = {
+ type: "mousemove",
+ clickcount: 0,
+ }
+ row.addEventListener("mousemove", function handler() {
+ row.removeEventListener("mousemove", handler);
+ ack("mousemove");
+ });
+ content.synthesizeMouseAtCenter(row, event);
+ },
+
+ click: function (arg) {
+ let eltIdx = typeof(arg) == "object" ? arg.eltIdx : arg;
+ let row;
+ if (eltIdx == -1) {
+ row = gController._table.firstChild;
+ }
+ else {
+ let allElts = [...gController._suggestionsList.children,
+ ...gController._oneOffButtons,
+ content.document.getElementById("contentSearchSettingsButton")];
+ row = allElts[eltIdx];
+ }
+ let event = arg.modifiers || {};
+ // synthesizeMouseAtCenter defaults to sending a mousedown followed by a
+ // mouseup if the event type is not specified.
+ content.synthesizeMouseAtCenter(row, event);
+ ack("click");
+ },
+
+ addInputValueToFormHistory: function () {
+ gController.addInputValueToFormHistory();
+ ack("addInputValueToFormHistory");
+ },
+
+ addDuplicateOneOff: function () {
+ let btn = gController._oneOffButtons[gController._oneOffButtons.length - 1];
+ let newBtn = btn.cloneNode(true);
+ btn.parentNode.appendChild(newBtn);
+ gController._oneOffButtons.push(newBtn);
+ ack("addDuplicateOneOff");
+ },
+
+ removeLastOneOff: function () {
+ gController._oneOffButtons.pop().remove();
+ ack("removeLastOneOff");
+ },
+
+ reset: function () {
+ // Reset both the input and suggestions by select all + delete. If there was
+ // no text entered, this won't have any effect, so also escape to ensure the
+ // suggestions table is closed.
+ gController.input.focus();
+ content.synthesizeKey("a", { accelKey: true });
+ content.synthesizeKey("VK_DELETE", {});
+ content.synthesizeKey("VK_ESCAPE", {});
+ ack("reset");
+ },
+};
+
+function ack(aType, aData) {
+ sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() });
+}
+
+function waitForSuggestions(cb) {
+ let observer = new content.MutationObserver(() => {
+ if (gController.input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ cb();
+ }
+ });
+ observer.observe(gController.input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+}
+
+function waitForContentSearchEvent(messageType, cb) {
+ let mm = content.SpecialPowers.Cc["@mozilla.org/globalmessagemanager;1"].
+ getService(content.SpecialPowers.Ci.nsIMessageListenerManager);
+ mm.addMessageListener("ContentSearch", function listener(aMsg) {
+ if (aMsg.data.type != messageType) {
+ return;
+ }
+ mm.removeMessageListener("ContentSearch", listener);
+ cb(aMsg.data.data);
+ });
+}
+
+function currentState() {
+ let state = {
+ selectedIndex: gController.selectedIndex,
+ selectedButtonIndex: gController.selectedButtonIndex,
+ numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions,
+ suggestionAtIndex: [],
+ isFormHistorySuggestionAtIndex: [],
+
+ tableHidden: gController._table.hidden,
+
+ inputValue: gController.input.value,
+ ariaExpanded: gController.input.getAttribute("aria-expanded"),
+ };
+
+ if (state.numSuggestions) {
+ for (let i = 0; i < gController.numSuggestions; i++) {
+ state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
+ state.isFormHistorySuggestionAtIndex.push(
+ gController.isFormHistorySuggestionAtIndex(i));
+ }
+ }
+
+ return state;
+}
+
+})();
diff --git a/browser/base/content/test/general/content_aboutAccounts.js b/browser/base/content/test/general/content_aboutAccounts.js
new file mode 100644
index 000000000..12ac04934
--- /dev/null
+++ b/browser/base/content/test/general/content_aboutAccounts.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/. */
+
+// This file is loaded as a "content script" for browser_aboutAccounts tests
+"use strict";
+
+var {interfaces: Ci, utils: Cu} = Components;
+
+addEventListener("load", function load(event) {
+ if (event.target != content.document) {
+ return;
+ }
+// content.document.removeEventListener("load", load, true);
+ sendAsyncMessage("test:document:load");
+ // Opening Sync prefs in tests is a pain as leaks are reported due to the
+ // in-flight promises. For now we just mock the openPrefs() function and have
+ // it send a message back to the test so we know it was called.
+ content.openPrefs = function() {
+ sendAsyncMessage("test:openPrefsCalled");
+ }
+}, true);
+
+addEventListener("DOMContentLoaded", function domContentLoaded(event) {
+ removeEventListener("DOMContentLoaded", domContentLoaded, true);
+ let iframe = content.document.getElementById("remote");
+ if (!iframe) {
+ // at least one test initially loads about:blank - in that case, we are done.
+ return;
+ }
+ // We use DOMContentLoaded here as that fires for our iframe even when we've
+ // arranged for the URL in the iframe to cause an error.
+ addEventListener("DOMContentLoaded", function iframeLoaded(dclEvent) {
+ if (iframe.contentWindow.location.href == "about:blank" ||
+ dclEvent.target != iframe.contentDocument) {
+ return;
+ }
+ removeEventListener("DOMContentLoaded", iframeLoaded, true);
+ sendAsyncMessage("test:iframe:load", {url: iframe.contentDocument.location.href});
+ // And an event listener for the test responses, which we send to the test
+ // via a message.
+ iframe.contentWindow.addEventListener("FirefoxAccountsTestResponse", function (fxAccountsEvent) {
+ sendAsyncMessage("test:response", {data: fxAccountsEvent.detail.data});
+ }, true);
+ }, true);
+}, true);
+
+// Return the visibility state of a list of ids.
+addMessageListener("test:check-visibilities", function (message) {
+ let result = {};
+ for (let id of message.data.ids) {
+ let elt = content.document.getElementById(id);
+ if (elt) {
+ let displayStyle = content.window.getComputedStyle(elt).display;
+ if (displayStyle == 'none') {
+ result[id] = false;
+ } else if (displayStyle == 'block') {
+ result[id] = true;
+ } else {
+ result[id] = "strange: " + displayStyle; // tests should fail!
+ }
+ } else {
+ result[id] = "doesn't exist: " + id;
+ }
+ }
+ sendAsyncMessage("test:check-visibilities-response", result);
+});
+
+addMessageListener("test:load-with-mocked-profile-path", function (message) {
+ addEventListener("DOMContentLoaded", function domContentLoaded(event) {
+ removeEventListener("DOMContentLoaded", domContentLoaded, true);
+ content.getDefaultProfilePath = () => message.data.profilePath;
+ // now wait for the iframe to load.
+ let iframe = content.document.getElementById("remote");
+ iframe.addEventListener("load", function iframeLoaded(loadEvent) {
+ if (iframe.contentWindow.location.href == "about:blank" ||
+ loadEvent.target != iframe) {
+ return;
+ }
+ iframe.removeEventListener("load", iframeLoaded, true);
+ sendAsyncMessage("test:load-with-mocked-profile-path-response",
+ {url: iframe.contentDocument.location.href});
+ }, true);
+ });
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(message.data.url, webNav.LOAD_FLAGS_NONE, null, null, null);
+}, true);
diff --git a/browser/base/content/test/general/contextmenu_common.js b/browser/base/content/test/general/contextmenu_common.js
new file mode 100644
index 000000000..1a0fa931a
--- /dev/null
+++ b/browser/base/content/test/general/contextmenu_common.js
@@ -0,0 +1,324 @@
+var lastElement;
+
+function openContextMenuFor(element, shiftkey, waitForSpellCheck) {
+ // Context menu should be closed before we open it again.
+ is(SpecialPowers.wrap(contextMenu).state, "closed", "checking if popup is closed");
+
+ if (lastElement)
+ lastElement.blur();
+ element.focus();
+
+ // Some elements need time to focus and spellcheck before any tests are
+ // run on them.
+ function actuallyOpenContextMenuFor() {
+ lastElement = element;
+ var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
+ synthesizeMouse(element, 2, 2, eventDetails, element.ownerGlobal);
+ }
+
+ if (waitForSpellCheck) {
+ var { onSpellCheck } = SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", {});
+ onSpellCheck(element, actuallyOpenContextMenuFor);
+ }
+ else {
+ actuallyOpenContextMenuFor();
+ }
+}
+
+function closeContextMenu() {
+ contextMenu.hidePopup();
+}
+
+function getVisibleMenuItems(aMenu, aData) {
+ var items = [];
+ var accessKeys = {};
+ for (var i = 0; i < aMenu.childNodes.length; i++) {
+ var item = aMenu.childNodes[i];
+ if (item.hidden)
+ continue;
+
+ var key = item.accessKey;
+ if (key)
+ key = key.toLowerCase();
+
+ var isPageMenuItem = item.hasAttribute("generateditemid");
+
+ if (item.nodeName == "menuitem") {
+ var isGenerated = item.className == "spell-suggestion"
+ || item.className == "sendtab-target";
+ if (isGenerated) {
+ is(item.id, "", "child menuitem #" + i + " is generated");
+ } else if (isPageMenuItem) {
+ is(item.id, "", "child menuitem #" + i + " is a generated page menu item");
+ } else {
+ ok(item.id, "child menuitem #" + i + " has an ID");
+ }
+ var label = item.getAttribute("label");
+ ok(label.length, "menuitem " + item.id + " has a label");
+ if (isGenerated) {
+ is(key, "", "Generated items shouldn't have an access key");
+ items.push("*" + label);
+ } else if (isPageMenuItem) {
+ items.push("+" + label);
+ } else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
+ item.id != "spell-no-suggestions" &&
+ item.id != "spell-add-dictionaries-main" &&
+ item.id != "context-savelinktopocket" &&
+ item.id != "fill-login-saved-passwords" &&
+ item.id != "fill-login-no-logins") {
+ ok(key, "menuitem " + item.id + " has an access key");
+ if (accessKeys[key])
+ ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ if (!isGenerated && !isPageMenuItem) {
+ items.push(item.id);
+ }
+ if (isPageMenuItem) {
+ var p = {};
+ p.type = item.getAttribute("type");
+ p.icon = item.getAttribute("image");
+ p.checked = item.hasAttribute("checked");
+ p.disabled = item.hasAttribute("disabled");
+ items.push(p);
+ } else {
+ items.push(!item.disabled);
+ }
+ } else if (item.nodeName == "menuseparator") {
+ ok(true, "--- seperator id is " + item.id);
+ items.push("---");
+ items.push(null);
+ } else if (item.nodeName == "menu") {
+ if (isPageMenuItem) {
+ item.id = "generated-submenu-" + aData.generatedSubmenuId++;
+ }
+ ok(item.id, "child menu #" + i + " has an ID");
+ if (!isPageMenuItem) {
+ ok(key, "menu has an access key");
+ if (accessKeys[key])
+ ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ items.push(item.id);
+ items.push(!item.disabled);
+ // Add a dummy item so that the indexes in checkMenu are the same
+ // for expectedItems and actualItems.
+ items.push([]);
+ items.push(null);
+ } else if (item.nodeName == "menugroup") {
+ ok(item.id, "child menugroup #" + i + " has an ID");
+ items.push(item.id);
+ items.push(!item.disabled);
+ var menugroupChildren = [];
+ for (var child of item.children) {
+ if (child.hidden)
+ continue;
+
+ menugroupChildren.push([child.id, !child.disabled]);
+ }
+ items.push(menugroupChildren);
+ items.push(null);
+ } else {
+ ok(false, "child #" + i + " of menu ID " + aMenu.id +
+ " has an unknown type (" + item.nodeName + ")");
+ }
+ }
+ return items;
+}
+
+function checkContextMenu(expectedItems) {
+ is(contextMenu.state, "open", "checking if popup is open");
+ var data = { generatedSubmenuId: 1 };
+ checkMenu(contextMenu, expectedItems, data);
+}
+
+function checkMenuItem(actualItem, actualEnabled, expectedItem, expectedEnabled, index) {
+ is(actualItem, expectedItem,
+ "checking item #" + index/2 + " (" + expectedItem + ") name");
+
+ if (typeof expectedEnabled == "object" && expectedEnabled != null ||
+ typeof actualEnabled == "object" && actualEnabled != null) {
+
+ ok(!(actualEnabled == null), "actualEnabled is not null");
+ ok(!(expectedEnabled == null), "expectedEnabled is not null");
+ is(typeof actualEnabled, typeof expectedEnabled, "checking types");
+
+ if (typeof actualEnabled != typeof expectedEnabled ||
+ actualEnabled == null || expectedEnabled == null)
+ return;
+
+ is(actualEnabled.type, expectedEnabled.type,
+ "checking item #" + index/2 + " (" + expectedItem + ") type attr value");
+ var icon = actualEnabled.icon;
+ if (icon) {
+ var tmp = "";
+ var j = icon.length - 1;
+ while (j && icon[j] != "/") {
+ tmp = icon[j--] + tmp;
+ }
+ icon = tmp;
+ }
+ is(icon, expectedEnabled.icon,
+ "checking item #" + index/2 + " (" + expectedItem + ") icon attr value");
+ is(actualEnabled.checked, expectedEnabled.checked,
+ "checking item #" + index/2 + " (" + expectedItem + ") has checked attr");
+ is(actualEnabled.disabled, expectedEnabled.disabled,
+ "checking item #" + index/2 + " (" + expectedItem + ") has disabled attr");
+ } else if (expectedEnabled != null)
+ is(actualEnabled, expectedEnabled,
+ "checking item #" + index/2 + " (" + expectedItem + ") enabled state");
+}
+
+/*
+ * checkMenu - checks to see if the specified <menupopup> contains the
+ * expected items and state.
+ * expectedItems is a array of (1) item IDs and (2) a boolean specifying if
+ * the item is enabled or not (or null to ignore it). Submenus can be checked
+ * by providing a nested array entry after the expected <menu> ID.
+ * For example: ["blah", true, // item enabled
+ * "submenu", null, // submenu
+ * ["sub1", true, // submenu contents
+ * "sub2", false], null, // submenu contents
+ * "lol", false] // item disabled
+ *
+ */
+function checkMenu(menu, expectedItems, data) {
+ var actualItems = getVisibleMenuItems(menu, data);
+ // ok(false, "Items are: " + actualItems);
+ for (var i = 0; i < expectedItems.length; i+=2) {
+ var actualItem = actualItems[i];
+ var actualEnabled = actualItems[i + 1];
+ var expectedItem = expectedItems[i];
+ var expectedEnabled = expectedItems[i + 1];
+ if (expectedItem instanceof Array) {
+ ok(true, "Checking submenu/menugroup...");
+ var previousId = expectedItems[i - 2]; // The last item was the menu ID.
+ var previousItem = menu.getElementsByAttribute("id", previousId)[0];
+ ok(previousItem, (previousItem ? previousItem.nodeName : "item") + " with previous id (" + previousId + ") found");
+ if (previousItem && previousItem.nodeName == "menu") {
+ ok(previousItem, "got a submenu element of id='" + previousId + "'");
+ is(previousItem.nodeName, "menu", "submenu element of id='" + previousId +
+ "' has expected nodeName");
+ checkMenu(previousItem.menupopup, expectedItem, data, i);
+ } else if (previousItem && previousItem.nodeName == "menugroup") {
+ ok(expectedItem.length, "menugroup must not be empty");
+ for (var j = 0; j < expectedItem.length / 2; j++) {
+ checkMenuItem(actualItems[i][j][0], actualItems[i][j][1], expectedItem[j*2], expectedItem[j*2+1], i+j*2);
+ }
+ i += j;
+ } else {
+ ok(false, "previous item is not a menu or menugroup");
+ }
+ } else {
+ checkMenuItem(actualItem, actualEnabled, expectedItem, expectedEnabled, i);
+ }
+ }
+ // Could find unexpected extra items at the end...
+ is(actualItems.length, expectedItems.length, "checking expected number of menu entries");
+}
+
+let lastElementSelector = null;
+/**
+ * Right-clicks on the element that matches `selector` and checks the
+ * context menu that appears against the `menuItems` array.
+ *
+ * @param {String} selector
+ * A selector passed to querySelector to find
+ * the element that will be referenced.
+ * @param {Array} menuItems
+ * An array of menuitem ids and their associated enabled state. A state
+ * of null means that it will be ignored. Ids of '---' are used for
+ * menuseparators.
+ * @param {Object} options, optional
+ * skipFocusChange: don't move focus to the element before test, useful
+ * if you want to delay spell-check initialization
+ * offsetX: horizontal mouse offset from the top-left corner of
+ * the element, optional
+ * offsetY: vertical mouse offset from the top-left corner of the
+ * element, optional
+ * centered: if true, mouse position is centered in element, defaults
+ * to true if offsetX and offsetY are not provided
+ * waitForSpellCheck: wait until spellcheck is initialized before
+ * starting test
+ * preCheckContextMenuFn: callback to run before opening menu
+ * onContextMenuShown: callback to run when the context menu is shown
+ * postCheckContextMenuFn: callback to run after opening menu
+ * @return {Promise} resolved after the test finishes
+ */
+function* test_contextmenu(selector, menuItems, options={}) {
+ contextMenu = document.getElementById("contentAreaContextMenu");
+ is(contextMenu.state, "closed", "checking if popup is closed");
+
+ // Default to centered if no positioning is defined.
+ if (!options.offsetX && !options.offsetY) {
+ options.centered = true;
+ }
+
+ if (!options.skipFocusChange) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser,
+ [lastElementSelector, selector],
+ function*([contentLastElementSelector, contentSelector]) {
+ if (contentLastElementSelector) {
+ let contentLastElement = content.document.querySelector(contentLastElementSelector);
+ contentLastElement.blur();
+ }
+ let element = content.document.querySelector(contentSelector);
+ element.focus();
+ });
+ lastElementSelector = selector;
+ info(`Moved focus to ${selector}`);
+ }
+
+ if (options.preCheckContextMenuFn) {
+ yield options.preCheckContextMenuFn();
+ info("Completed preCheckContextMenuFn");
+ }
+
+ if (options.waitForSpellCheck) {
+ info("Waiting for spell check");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, selector, function*(contentSelector) {
+ let {onSpellCheck} = Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", {});
+ let element = content.document.querySelector(contentSelector);
+ yield new Promise(resolve => onSpellCheck(element, resolve));
+ info("Spell check running");
+ });
+ }
+
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouse(selector, options.offsetX || 0, options.offsetY || 0, {
+ type: "contextmenu",
+ button: 2,
+ shiftkey: options.shiftkey,
+ centered: options.centered
+ },
+ gBrowser.selectedBrowser);
+ yield awaitPopupShown;
+ info("Popup Shown");
+
+ if (options.onContextMenuShown) {
+ yield options.onContextMenuShown();
+ info("Completed onContextMenuShown");
+ }
+
+ if (menuItems) {
+ if (Services.prefs.getBoolPref("devtools.inspector.enabled")) {
+ let inspectItems = ["---", null,
+ "context-inspect", true];
+ menuItems = menuItems.concat(inspectItems);
+ }
+
+ checkContextMenu(menuItems);
+ }
+
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+
+ if (options.postCheckContextMenuFn) {
+ yield options.postCheckContextMenuFn();
+ info("Completed postCheckContextMenuFn");
+ }
+
+ contextMenu.hidePopup();
+ yield awaitPopupHidden;
+}
diff --git a/browser/base/content/test/general/ctxmenu-image.png b/browser/base/content/test/general/ctxmenu-image.png
new file mode 100644
index 000000000..4c3be5084
--- /dev/null
+++ b/browser/base/content/test/general/ctxmenu-image.png
Binary files differ
diff --git a/browser/base/content/test/general/discovery.html b/browser/base/content/test/general/discovery.html
new file mode 100644
index 000000000..1679e6545
--- /dev/null
+++ b/browser/base/content/test/general/discovery.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+ <head id="linkparent">
+ <title>Autodiscovery Test</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/download_page.html b/browser/base/content/test/general/download_page.html
new file mode 100644
index 000000000..4f9154033
--- /dev/null
+++ b/browser/base/content/test/general/download_page.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=676619
+-->
+ <head>
+ <title>Test for the download attribute</title>
+
+ </head>
+ <body>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=676619">Bug 676619</a>
+ <br/>
+ <ul>
+ <li><a href="data:text/plain,Hey What are you looking for?"
+ download="test.txt" id="link1">Download "test.txt"</a></li>
+ <li><a href="video.ogg"
+ download id="link2">Download "video.ogg"</a></li>
+ <li><a href="video.ogg"
+ download="just some video" id="link3">Download "just some video"</a></li>
+ <li><a href="data:text/plain,test"
+ download="with-target.txt" id="link4">Download "with-target.txt"</a></li>
+ <li><a href="javascript:(1+2)+''"
+ download="javascript.txt" id="link5">Download "javascript.txt"</a></li>
+ </ul>
+ <script>
+ var li = document.createElement('li');
+ var a = document.createElement('a');
+
+ a.href = window.URL.createObjectURL(new Blob(["just text"])) ;
+ a.download = "test.blob";
+ a.id = "link6";
+ a.textContent = 'Download "test.blob"';
+
+ li.appendChild(a);
+ document.getElementsByTagName('ul')[0].appendChild(li);
+
+ window.addEventListener("beforeunload", function (evt) {
+ document.getElementById("unload-flag").textContent = "Fail";
+ });
+ </script>
+ <ul>
+ <li><a href="http://example.com/"
+ download="example.com" id="link7" target="_blank">Download "example.com"</a></li>
+ <ul>
+ <div id="unload-flag">Okay</div>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/dummy_page.html b/browser/base/content/test/general/dummy_page.html
new file mode 100644
index 000000000..1a87e2840
--- /dev/null
+++ b/browser/base/content/test/general/dummy_page.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/feed_discovery.html b/browser/base/content/test/general/feed_discovery.html
new file mode 100644
index 000000000..baecba19b
--- /dev/null
+++ b/browser/base/content/test/general/feed_discovery.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377611
+-->
+ <head>
+ <title>Test for feed discovery</title>
+ <meta charset="utf-8">
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ <!-- rel is a space-separated list -->
+ <link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" />
+ <link rel="foo alternate" type="application/atom+xml" title="5" href="/5.atom" />
+ <link rel="alternate foo" type="application/atom+xml" title="6" href="/6.atom" />
+ <link rel="foo alternate foo" type="application/atom+xml" title="7" href="/7.atom" />
+ <link rel="meat feed cake" title="8" href="/8.atom" />
+
+ <!-- rel is case-insensitive -->
+ <link rel="ALTERNate" type="application/atom+xml" title="9" href="/9.atom" />
+ <link rel="fEEd" title="10" href="/10.atom" />
+
+ <!-- type can have leading and trailing whitespace -->
+ <link rel="alternate" type=" application/atom+xml " title="11" href="/11.atom" />
+
+ <!-- type is case-insensitive -->
+ <link rel="alternate" type="aPPliCAtion/ATom+xML" title="12" href="/12.atom" />
+
+ <!-- "feed stylesheet" is a feed, though "alternate stylesheet" isn't -->
+ <link rel="feed stylesheet" title="13" href="/13.atom" />
+
+ <!-- hyphens or letters around rel not allowed -->
+ <link rel="disabled-alternate" type="application/atom+xml" title="Bogus1" href="/Bogus1" />
+ <link rel="alternates" type="application/atom+xml" title="Bogus2" href="/Bogus2" />
+ <link rel=" alternate-like" type="application/atom+xml" title="Bogus3" href="/Bogus3" />
+
+ <!-- don't tolerate text/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="text/xml" title="Bogus4 scissorsshaped" href="/Bogus4" />
+
+ <!-- don't tolerate application/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/xml" title="Bogus5 scissorsshaped" href="/Bogus5" />
+
+ <!-- don't tolerate application/rdf+xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus6 scissorsshaped" href="/Bogus6" />
+
+ <!-- don't tolerate random types -->
+ <link rel="alternate" type="text/plain" title="Bogus7 rss" href="/Bogus7" />
+
+ <!-- don't find Atom by title -->
+ <link rel="foopy" type="application/atom+xml" title="Bogus8 Atom and RSS" href="/Bogus8" />
+
+ <!-- don't find application/rss+xml by title -->
+ <link rel="goats" type="application/rss+xml" title="Bogus9 RSS and Atom" href="/Bogus9" />
+
+ <!-- don't find application/rdf+xml by title -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus10 RSS and Atom" href="/Bogus10" />
+
+ <!-- don't find application/xml by title -->
+ <link rel="alternate" type="application/xml" title="Bogus11 RSS and Atom" href="/Bogus11" />
+
+ <!-- don't find text/xml by title -->
+ <link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" />
+
+ <!-- alternate and stylesheet isn't a feed -->
+ <link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" />
+ </head>
+ <body>
+ </body>
+</html>
+
diff --git a/browser/base/content/test/general/feed_tab.html b/browser/base/content/test/general/feed_tab.html
new file mode 100644
index 000000000..50903f48b
--- /dev/null
+++ b/browser/base/content/test/general/feed_tab.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=458579
+-->
+ <head>
+ <title>Test for page info feeds tab</title>
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_bug1045809_1.html b/browser/base/content/test/general/file_bug1045809_1.html
new file mode 100644
index 000000000..9baf2d45d
--- /dev/null
+++ b/browser/base/content/test/general/file_bug1045809_1.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ </head>
+ <body>
+ <iframe src="http://test1.example.com/browser/browser/base/content/test/general/file_bug1045809_2.html"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_bug1045809_2.html b/browser/base/content/test/general/file_bug1045809_2.html
new file mode 100644
index 000000000..67a297dbc
--- /dev/null
+++ b/browser/base/content/test/general/file_bug1045809_2.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ </head>
+ <body>
+ <div id="mixedContentContainer">Mixed Content is here</div>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_1.html b/browser/base/content/test/general/file_bug822367_1.html
new file mode 100644
index 000000000..62f42d226
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 1 for Mixed Content Blocker User Override - Mixed Script
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_1.js b/browser/base/content/test/general/file_bug822367_1.js
new file mode 100644
index 000000000..175de363b
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_1.js
@@ -0,0 +1 @@
+document.getElementById('p1').innerHTML="hello";
diff --git a/browser/base/content/test/general/file_bug822367_2.html b/browser/base/content/test/general/file_bug822367_2.html
new file mode 100644
index 000000000..fe56ee213
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 2 for Mixed Content Blocker User Override - Mixed Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 822367 - Mixed Display</title>
+</head>
+<body>
+ <div id="testContent">
+ <img src="http://example.com/tests/image/test/mochitest/blue.png">
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_3.html b/browser/base/content/test/general/file_bug822367_3.html
new file mode 100644
index 000000000..c1ff2c000
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_3.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 3 for Mixed Content Blocker User Override - Mixed Script and Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 822367</title>
+ <script>
+ function foo() {
+ var x = document.createElement('p');
+ x.setAttribute("id", "p2");
+ x.innerHTML = "bye";
+ document.getElementById("testContent").appendChild(x);
+ }
+ </script>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ <img src="http://example.com/tests/image/test/mochitest/blue.png" onload="foo()">
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_4.html b/browser/base/content/test/general/file_bug822367_4.html
new file mode 100644
index 000000000..9a073143f
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_4.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 4 for Mixed Content Blocker User Override - Mixed Script and Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 4 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_4.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_4.js b/browser/base/content/test/general/file_bug822367_4.js
new file mode 100644
index 000000000..301db89c7
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_4.js
@@ -0,0 +1 @@
+document.location = "https://example.com/browser/browser/base/content/test/general/file_bug822367_4B.html";
diff --git a/browser/base/content/test/general/file_bug822367_4B.html b/browser/base/content/test/general/file_bug822367_4B.html
new file mode 100644
index 000000000..76ea2b623
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_4B.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 4B for Mixed Content Blocker User Override - Location Changed
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 4B Location Change for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_5.html b/browser/base/content/test/general/file_bug822367_5.html
new file mode 100644
index 000000000..3c9a9317e
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_5.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 5 for Mixed Content Blocker User Override - Mixed Script in document.open()
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 5 for Bug 822367</title>
+ <script>
+ function createDoc()
+ {
+ var doc=document.open("text/html", "replace");
+ doc.write('<!DOCTYPE html><html><body><p id="p1">This is some content</p><script src="http://example.com/browser/browser/base/content/test/general/file_bug822367_1.js">\<\/script\>\<\/body>\<\/html>');
+ doc.close();
+ }
+ </script>
+</head>
+<body>
+ <div id="testContent">
+ <img src="https://example.com/tests/image/test/mochitest/blue.png" onload="createDoc()">
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug822367_6.html b/browser/base/content/test/general/file_bug822367_6.html
new file mode 100644
index 000000000..baa5674c2
--- /dev/null
+++ b/browser/base/content/test/general/file_bug822367_6.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 6 for Mixed Content Blocker User Override - Mixed Script in document.open() within an iframe
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 6 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <iframe name="f1" id="f1" src="https://example.com/browser/browser/base/content/test/general/file_bug822367_5.html"></iframe>
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug902156.js b/browser/base/content/test/general/file_bug902156.js
new file mode 100644
index 000000000..f943dd628
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156.js
@@ -0,0 +1,5 @@
+/*
+ * Once the mixed content blocker is disabled for the page, this scripts loads
+ * and updates the text inside the div container.
+ */
+document.getElementById("mctestdiv").innerHTML = "Mixed Content Blocker disabled";
diff --git a/browser/base/content/test/general/file_bug902156_1.html b/browser/base/content/test/general/file_bug902156_1.html
new file mode 100644
index 000000000..e3625de99
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156_1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug902156_2.html b/browser/base/content/test/general/file_bug902156_2.html
new file mode 100644
index 000000000..25aff3349
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156_2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 2 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <a href="https://test2.example.com/browser/browser/base/content/test/general/file_bug902156_1.html"
+ id="mctestlink" target="_top">Go to http site</a>
+ <script src="http://test2.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug902156_3.html b/browser/base/content/test/general/file_bug902156_3.html
new file mode 100644
index 000000000..65805adff
--- /dev/null
+++ b/browser/base/content/test/general/file_bug902156_3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190.js b/browser/base/content/test/general/file_bug906190.js
new file mode 100644
index 000000000..f943dd628
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190.js
@@ -0,0 +1,5 @@
+/*
+ * Once the mixed content blocker is disabled for the page, this scripts loads
+ * and updates the text inside the div container.
+ */
+document.getElementById("mctestdiv").innerHTML = "Mixed Content Blocker disabled";
diff --git a/browser/base/content/test/general/file_bug906190.sjs b/browser/base/content/test/general/file_bug906190.sjs
new file mode 100644
index 000000000..bff126874
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+ var page = "<!DOCTYPE html><html><body>bug 906190</body></html>";
+ var path = "https://test1.example.com/browser/browser/base/content/test/general/";
+ var url;
+
+ if (request.queryString.includes('bad-redirection=1')) {
+ url = path + "this_page_does_not_exist.html";
+ } else {
+ url = path + "file_bug906190_redirected.html";
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "302", "Found");
+ response.setHeader("Location", url, false);
+ response.write(page);
+}
diff --git a/browser/base/content/test/general/file_bug906190_1.html b/browser/base/content/test/general/file_bug906190_1.html
new file mode 100644
index 000000000..cbb3cac26
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 906190</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190_2.html b/browser/base/content/test/general/file_bug906190_2.html
new file mode 100644
index 000000000..70c7c61cf
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 2 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 906190</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test2.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190_3_4.html b/browser/base/content/test/general/file_bug906190_3_4.html
new file mode 100644
index 000000000..aea6648a9
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_3_4.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3 and 4 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="refresh" content="0; url=https://test1.example.com/browser/browser/base/content/test/general/file_bug906190_redirected.html">
+ <title>Test 3 and 4 for Bug 906190</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug906190_redirected.html b/browser/base/content/test/general/file_bug906190_redirected.html
new file mode 100644
index 000000000..cc324bd25
--- /dev/null
+++ b/browser/base/content/test/general/file_bug906190_redirected.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Redirected Page of Test 3 to 6 for Bug 906190 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=906190
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Redirected Page for Bug 906190</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/general/file_bug906190.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug970276_favicon1.ico b/browser/base/content/test/general/file_bug970276_favicon1.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_favicon1.ico
Binary files differ
diff --git a/browser/base/content/test/general/file_bug970276_favicon2.ico b/browser/base/content/test/general/file_bug970276_favicon2.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_favicon2.ico
Binary files differ
diff --git a/browser/base/content/test/general/file_bug970276_popup1.html b/browser/base/content/test/general/file_bug970276_popup1.html
new file mode 100644
index 000000000..5ce7dab87
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_popup1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for bug 970276.</title>
+
+ <!--Set a favicon; that's the whole point of this file.-->
+ <link rel="icon" href="file_bug970276_favicon1.ico">
+</head>
+<body>
+ Test file for bug 970276.
+
+ <iframe src="file_bug970276_popup2.html">
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_bug970276_popup2.html b/browser/base/content/test/general/file_bug970276_popup2.html
new file mode 100644
index 000000000..0b9e5294e
--- /dev/null
+++ b/browser/base/content/test/general/file_bug970276_popup2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for bug 970276.</title>
+
+ <!--Set a favicon; that's the whole point of this file.-->
+ <link rel="icon" href="file_bug970276_favicon2.ico">
+</head>
+<body>
+ Test inner file for bug 970276.
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_csp_block_all_mixedcontent.html b/browser/base/content/test/general/file_csp_block_all_mixedcontent.html
new file mode 100644
index 000000000..93a7f13d9
--- /dev/null
+++ b/browser/base/content/test/general/file_csp_block_all_mixedcontent.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html><head><meta charset="utf-8">
+<title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>
+</head>
+<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
+<body>
+<script src="http://example.com/browser/browser/base/content/test/general/file_csp_block_all_mixedcontent.js"/>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_csp_block_all_mixedcontent.js b/browser/base/content/test/general/file_csp_block_all_mixedcontent.js
new file mode 100644
index 000000000..dc6d6a64e
--- /dev/null
+++ b/browser/base/content/test/general/file_csp_block_all_mixedcontent.js
@@ -0,0 +1,3 @@
+// empty script file just used for testing Bug 1122236.
+// Making sure the UI is not degraded when blocking
+// mixed content using the CSP directive: block-all-mixed-content.
diff --git a/browser/base/content/test/general/file_documentnavigation_frameset.html b/browser/base/content/test/general/file_documentnavigation_frameset.html
new file mode 100644
index 000000000..beb01addf
--- /dev/null
+++ b/browser/base/content/test/general/file_documentnavigation_frameset.html
@@ -0,0 +1,12 @@
+<html id="outer">
+
+<frameset rows="30%, 70%">
+ <frame src="data:text/html,&lt;html id='htmlframe1' &gt;&lt;body id='framebody1'&gt;&lt;input id='i1'&gt;&lt;body&gt;&lt;/html&gt;">
+ <frameset cols="30%, 33%, 34%">
+ <frame src="data:text/html,&lt;html id='htmlframe2'&gt;&lt;body id='framebody2'&gt;&lt;input id='i2'&gt;&lt;body&gt;&lt;/html&gt;">
+ <frame src="data:text/html,&lt;html id='htmlframe3'&gt;&lt;body id='framebody3'&gt;&lt;input id='i3'&gt;&lt;body&gt;&lt;/html&gt;">
+ <frame src="data:text/html,&lt;html id='htmlframe4'&gt;&lt;body id='framebody4'&gt;&lt;input id='i4'&gt;&lt;body&gt;&lt;/html&gt;">
+ </frameset>
+</frameset>
+
+</html>
diff --git a/browser/base/content/test/general/file_double_close_tab.html b/browser/base/content/test/general/file_double_close_tab.html
new file mode 100644
index 000000000..0bead5efc
--- /dev/null
+++ b/browser/base/content/test/general/file_double_close_tab.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test page that blocks beforeunload. Used in tests for bug 1050638 and bug 305085</title>
+ </head>
+ <body>
+ This page will block beforeunload. It should still be user-closable at all times.
+ <script>
+ window.onbeforeunload = function() {
+ return "stop";
+ };
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_favicon_change.html b/browser/base/content/test/general/file_favicon_change.html
new file mode 100644
index 000000000..18ac6526b
--- /dev/null
+++ b/browser/base/content/test/general/file_favicon_change.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html><head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i">
+</head>
+<body>
+ <script>
+ window.addEventListener("PleaseChangeFavicon", function() {
+ var ico = document.getElementById("i");
+ ico.setAttribute("href", "http://example.org/other-icon");
+ });
+ </script>
+</body></html>
diff --git a/browser/base/content/test/general/file_favicon_change_not_in_document.html b/browser/base/content/test/general/file_favicon_change_not_in_document.html
new file mode 100644
index 000000000..deebb07dc
--- /dev/null
+++ b/browser/base/content/test/general/file_favicon_change_not_in_document.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html><head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i">
+</head>
+<body onload="onload()">
+ <script>
+ function onload() {
+ var ico = document.createElement("link");
+ ico.setAttribute("rel", "icon");
+ ico.setAttribute("type", "image/ico");
+ ico.setAttribute("href", "http://example.org/other-icon");
+ setTimeout(function() {
+ ico.setAttribute("href", "http://example.org/yet-another-icon");
+ document.getElementById("i").remove();
+ document.head.appendChild(ico);
+ }, 1000);
+ }
+ </script>
+</body></html>
+
diff --git a/browser/base/content/test/general/file_fullscreen-window-open.html b/browser/base/content/test/general/file_fullscreen-window-open.html
new file mode 100644
index 000000000..1584f4c98
--- /dev/null
+++ b/browser/base/content/test/general/file_fullscreen-window-open.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for window.open() when browser is in fullscreen</title>
+ </head>
+ <body>
+ <script>
+ window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, true);
+
+ document.getElementById("test").addEventListener("click", onClick, true);
+ }, true);
+
+ function onClick(aEvent) {
+ aEvent.preventDefault();
+
+ var dataStr = aEvent.target.getAttribute("data-test-param");
+ var data = JSON.parse(dataStr);
+ window.open(data.uri, data.title, data.option);
+ }
+ </script>
+ <a id="test" href="" data-test-param="">Test</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/file_generic_favicon.ico b/browser/base/content/test/general/file_generic_favicon.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/browser/base/content/test/general/file_generic_favicon.ico
Binary files differ
diff --git a/browser/base/content/test/general/file_mediaPlayback.html b/browser/base/content/test/general/file_mediaPlayback.html
new file mode 100644
index 000000000..a6979287e
--- /dev/null
+++ b/browser/base/content/test/general/file_mediaPlayback.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<audio src="audio.ogg" controls loop>
diff --git a/browser/base/content/test/general/file_mixedContentFramesOnHttp.html b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html
new file mode 100644
index 000000000..3bd16aea5
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFramesOnHttp.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1182551</title>
+</head>
+<body>
+ <p>Test for Bug 1182551. This is an HTTP top level page. We include an HTTPS iframe that loads mixed passive content.</p>
+ <iframe src="https://example.org/browser/browser/base/content/test/general/file_mixedPassiveContent.html"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload.html b/browser/base/content/test/general/file_mixedContentFromOnunload.html
new file mode 100644
index 000000000..fb28a2889
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFromOnunload.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=947079
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 947079</title>
+</head>
+<body>
+ <p>Test for Bug 947079</p>
+ <script>
+ window.addEventListener('unload', function() {
+ new Image().src = 'http://mochi.test:8888/tests/image/test/mochitest/blue.png';
+ });
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html b/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html
new file mode 100644
index 000000000..1d027b036
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFromOnunload_test1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 1 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079
+Page with no insecure subresources
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 947079</title>
+</head>
+<body>
+ <p>There are no insecure resource loads on this page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html b/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html
new file mode 100644
index 000000000..4813337cc
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedContentFromOnunload_test2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 2 for https://bugzilla.mozilla.org/show_bug.cgi?id=947079
+Page with an insecure image load
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 947079</title>
+</head>
+<body>
+ <p>Page with http image load</p>
+ <img src="http://test2.example.com/tests/image/test/mochitest/blue.png">
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_mixedPassiveContent.html b/browser/base/content/test/general/file_mixedPassiveContent.html
new file mode 100644
index 000000000..a60ac94e8
--- /dev/null
+++ b/browser/base/content/test/general/file_mixedPassiveContent.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test for https://bugzilla.mozilla.org/show_bug.cgi?id=1182551
+-->
+<head>
+ <meta charset="utf-8">
+ <title>HTTPS page with HTTP image</title>
+</head>
+<body>
+ <img src="http://mochi.test:8888/tests/image/test/mochitest/blue.png">
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_trackingUI_6.html b/browser/base/content/test/general/file_trackingUI_6.html
new file mode 100644
index 000000000..52e1ae63f
--- /dev/null
+++ b/browser/base/content/test/general/file_trackingUI_6.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Testing the shield from fetch and XHR</title>
+</head>
+<body>
+ <p>Hello there!</p>
+ <script type="application/javascript; version=1.8">
+ function test_fetch() {
+ let url = "http://trackertest.org/browser/browser/base/content/test/general/file_trackingUI_6.js";
+ return fetch(url);
+ }
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/file_trackingUI_6.js b/browser/base/content/test/general/file_trackingUI_6.js
new file mode 100644
index 000000000..f7ac687cf
--- /dev/null
+++ b/browser/base/content/test/general/file_trackingUI_6.js
@@ -0,0 +1,2 @@
+/* Some code goes here! */
+void 0;
diff --git a/browser/base/content/test/general/file_trackingUI_6.js^headers^ b/browser/base/content/test/general/file_trackingUI_6.js^headers^
new file mode 100644
index 000000000..cb762eff8
--- /dev/null
+++ b/browser/base/content/test/general/file_trackingUI_6.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/browser/base/content/test/general/file_with_favicon.html b/browser/base/content/test/general/file_with_favicon.html
new file mode 100644
index 000000000..0702b4aab
--- /dev/null
+++ b/browser/base/content/test/general/file_with_favicon.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for bugs with favicons</title>
+
+ <!--Set a favicon; that's the whole point of this file.-->
+ <link rel="icon" href="file_generic_favicon.ico">
+</head>
+<body>
+ Test file for bugs with favicons
+</body>
+</html>
diff --git a/browser/base/content/test/general/fxa_profile_handler.sjs b/browser/base/content/test/general/fxa_profile_handler.sjs
new file mode 100644
index 000000000..7160b76d0
--- /dev/null
+++ b/browser/base/content/test/general/fxa_profile_handler.sjs
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This is basically an echo server!
+// We just grab responseStatus and responseBody query params!
+
+function reallyHandleRequest(request, response) {
+ var query = "?" + request.queryString;
+
+ var responseStatus = 200;
+ var match = /responseStatus=([^&]*)/.exec(query);
+ if (match) {
+ responseStatus = parseInt(match[1]);
+ }
+
+ var responseBody = "";
+ match = /responseBody=([^&]*)/.exec(query);
+ if (match) {
+ responseBody = decodeURIComponent(match[1]);
+ }
+
+ response.setStatusLine("1.0", responseStatus, "OK");
+ response.write(responseBody);
+}
+
+function handleRequest(request, response)
+{
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 500, "NotOK");
+ response.write("Error handling request: " + e);
+ }
+}
diff --git a/browser/base/content/test/general/gZipOfflineChild.cacheManifest b/browser/base/content/test/general/gZipOfflineChild.cacheManifest
new file mode 100644
index 000000000..ae0545d12
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+gZipOfflineChild.html
diff --git a/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/gZipOfflineChild.html b/browser/base/content/test/general/gZipOfflineChild.html
new file mode 100644
index 000000000..ea2caa125
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.html
Binary files differ
diff --git a/browser/base/content/test/general/gZipOfflineChild.html^headers^ b/browser/base/content/test/general/gZipOfflineChild.html^headers^
new file mode 100644
index 000000000..4204d8601
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild.html^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Content-Encoding: gzip
diff --git a/browser/base/content/test/general/gZipOfflineChild_uncompressed.html b/browser/base/content/test/general/gZipOfflineChild_uncompressed.html
new file mode 100644
index 000000000..4ab8f8d5e
--- /dev/null
+++ b/browser/base/content/test/general/gZipOfflineChild_uncompressed.html
@@ -0,0 +1,21 @@
+<html manifest="gZipOfflineChild.cacheManifest">
+<head>
+ <!-- This file is gzipped to create gZipOfflineChild.html -->
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success, "*");
+}
+
+applicationCache.oncached = function() { finish("oncache"); }
+applicationCache.onnoupdate = function() { finish("onupdate"); }
+applicationCache.onerror = function() { finish("onerror"); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/head.js b/browser/base/content/test/general/head.js
new file mode 100644
index 000000000..6c28615fe
--- /dev/null
+++ b/browser/base/content/test/general/head.js
@@ -0,0 +1,1069 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler",
+ "resource:///modules/ContentCrashHandlers.jsm");
+
+/**
+ * Wait for a <notification> to be closed then call the specified callback.
+ */
+function waitForNotificationClose(notification, cb) {
+ let parent = notification.parentNode;
+
+ let observer = new MutationObserver(function onMutatations(mutations) {
+ for (let mutation of mutations) {
+ for (let i = 0; i < mutation.removedNodes.length; i++) {
+ let node = mutation.removedNodes.item(i);
+ if (node != notification) {
+ continue;
+ }
+ observer.disconnect();
+ cb();
+ }
+ }
+ });
+ observer.observe(parent, {childList: true});
+}
+
+function closeAllNotifications () {
+ let notificationBox = document.getElementById("global-notificationbox");
+
+ if (!notificationBox || !notificationBox.currentNotification) {
+ return Promise.resolve();
+ }
+
+ let deferred = Promise.defer();
+ for (let notification of notificationBox.allNotifications) {
+ waitForNotificationClose(notification, function () {
+ if (notificationBox.allNotifications.length === 0) {
+ deferred.resolve();
+ }
+ });
+ notification.close();
+ }
+
+ return deferred.promise;
+}
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ }
+ }, "browser-delayed-startup-finished", false);
+}
+
+function updateTabContextMenu(tab, onOpened) {
+ let menu = document.getElementById("tabContextMenu");
+ if (!tab)
+ tab = gBrowser.selectedTab;
+ var evt = new Event("");
+ tab.dispatchEvent(evt);
+ menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
+ is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
+ const onFinished = () => menu.hidePopup();
+ if (onOpened) {
+ return Task.spawn(function*() {
+ yield onOpened();
+ onFinished();
+ });
+ }
+ onFinished();
+ return Promise.resolve();
+}
+
+function openToolbarCustomizationUI(aCallback, aBrowserWin) {
+ if (!aBrowserWin)
+ aBrowserWin = window;
+
+ aBrowserWin.gCustomizeMode.enter();
+
+ aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded);
+ executeSoon(function() {
+ aCallback(aBrowserWin)
+ });
+ });
+}
+
+function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
+ aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded);
+ executeSoon(aCallback);
+ });
+
+ aBrowserWin.gCustomizeMode.exit();
+}
+
+function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
+ retryTimes = typeof retryTimes !== 'undefined' ? retryTimes : 30;
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= retryTimes) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+function promiseWaitForCondition(aConditionFn) {
+ let deferred = Promise.defer();
+ waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
+ return deferred.promise;
+}
+
+function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) {
+ return new Promise((resolve) => {
+ function listener(event) {
+ info("Saw " + eventName);
+ object.removeEventListener(eventName, listener, capturing, chrome);
+ resolve(event);
+ }
+
+ info("Waiting for " + eventName);
+ object.addEventListener(eventName, listener, capturing, chrome);
+ });
+}
+
+/**
+ * Allows setting focus on a window, and waiting for that window to achieve
+ * focus.
+ *
+ * @param aWindow
+ * The window to focus and wait for.
+ *
+ * @return {Promise}
+ * @resolves When the window is focused.
+ * @rejects Never.
+ */
+function promiseWaitForFocus(aWindow) {
+ return new Promise((resolve) => {
+ waitForFocus(resolve, aWindow);
+ });
+}
+
+function getTestPlugin(aName) {
+ var pluginName = aName || "Test Plug-in";
+ var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+
+ // Find the test plugin
+ for (var i = 0; i < tags.length; i++) {
+ if (tags[i].name == pluginName)
+ return tags[i];
+ }
+ ok(false, "Unable to find plugin");
+ return null;
+}
+
+// call this to set the test plugin(s) initially expected enabled state.
+// it will automatically be reset to it's previous value after the test
+// ends
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var plugin = getTestPlugin(pluginName);
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = newEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ getTestPlugin(pluginName).enabledState = oldEnabledState;
+ });
+}
+
+function pushPrefs(...aPrefs) {
+ let deferred = Promise.defer();
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
+ return deferred.promise;
+}
+
+function updateBlocklist(aCallback) {
+ var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ var observer = function() {
+ Services.obs.removeObserver(observer, "blocklist-updated");
+ SimpleTest.executeSoon(aCallback);
+ };
+ Services.obs.addObserver(observer, "blocklist-updated", false);
+ blocklistNotifier.notify(null);
+}
+
+var _originalTestBlocklistURL = null;
+function setAndUpdateBlocklist(aURL, aCallback) {
+ if (!_originalTestBlocklistURL)
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ updateBlocklist(aCallback);
+}
+
+function resetBlocklist() {
+ Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
+}
+
+function whenNewWindowLoaded(aOptions, aCallback) {
+ let win = OpenBrowserWindow(aOptions);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ aCallback(win);
+ }, false);
+}
+
+function promiseWindowWillBeClosed(win) {
+ return new Promise((resolve, reject) => {
+ Services.obs.addObserver(function observe(subject, topic) {
+ if (subject == win) {
+ Services.obs.removeObserver(observe, topic);
+ resolve();
+ }
+ }, "domwindowclosed", false);
+ });
+}
+
+function promiseWindowClosed(win) {
+ let promise = promiseWindowWillBeClosed(win);
+ win.close();
+ return promise;
+}
+
+function promiseOpenAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {
+ let deferred = Promise.defer();
+ let win = OpenBrowserWindow(aOptions);
+ if (aWaitForDelayedStartup) {
+ Services.obs.addObserver(function onDS(aSubject, aTopic, aData) {
+ if (aSubject != win) {
+ return;
+ }
+ Services.obs.removeObserver(onDS, "browser-delayed-startup-finished");
+ deferred.resolve(win);
+ }, "browser-delayed-startup-finished", false);
+
+ } else {
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ deferred.resolve(win);
+ });
+ }
+ return deferred.promise;
+}
+
+/**
+ * Waits for all pending async statements on the default connection, before
+ * proceeding with aCallback.
+ *
+ * @param aCallback
+ * Function to be called when done.
+ * @param aScope
+ * Scope for the callback.
+ * @param aArguments
+ * Arguments array for the callback.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ * a write lock. Since all statements on the same connection are
+ * serialized, the end of this write operation means that all writes are
+ * complete. Note that WAL makes so that writers don't block readers, but
+ * this is a problem only across different connections.
+ */
+function waitForAsyncUpdates(aCallback, aScope, aArguments) {
+ let scope = aScope || this;
+ let args = aArguments || [];
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
+ begin.executeAsync();
+ begin.finalize();
+
+ let commit = db.createAsyncStatement("COMMIT");
+ commit.executeAsync({
+ handleResult: function() {},
+ handleError: function() {},
+ handleCompletion: function(aReason) {
+ aCallback.apply(scope, args);
+ }
+ });
+ commit.finalize();
+}
+
+/**
+ * Asynchronously check a url is visited.
+
+ * @param aURI The URI.
+ * @param aExpectedValue The expected value.
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI, aExpectedValue) {
+ let deferred = Promise.defer();
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(unused, aIsVisited) {
+ deferred.resolve(aIsVisited);
+ });
+
+ return deferred.promise;
+}
+
+function whenNewTabLoaded(aWindow, aCallback) {
+ aWindow.BrowserOpenTab();
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ if (browser.contentDocument.readyState === "complete") {
+ aCallback();
+ return;
+ }
+
+ whenTabLoaded(aWindow.gBrowser.selectedTab, aCallback);
+}
+
+function whenTabLoaded(aTab, aCallback) {
+ promiseTabLoadEvent(aTab).then(aCallback);
+}
+
+function promiseTabLoaded(aTab) {
+ let deferred = Promise.defer();
+ whenTabLoaded(aTab, deferred.resolve);
+ return deferred.promise;
+}
+
+/**
+ * Ensures that the specified URIs are either cleared or not.
+ *
+ * @param aURIs
+ * Array of page URIs
+ * @param aShouldBeCleared
+ * True if each visit to the URI should be cleared, false otherwise
+ */
+function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
+ let deferred = Promise.defer();
+ let callbackCount = 0;
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ function callbackDone() {
+ if (++callbackCount == aURIs.length)
+ deferred.resolve();
+ }
+ aURIs.forEach(function (aURI) {
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(uri, isVisited) {
+ is(isVisited, !aShouldBeCleared,
+ "history visit " + uri.spec + " should " + niceStr + " exist");
+ callbackDone();
+ });
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Waits for the next top-level document load in the current browser. The URI
+ * of the document is compared against aExpectedURL. The load is then stopped
+ * before it actually starts.
+ *
+ * @param aExpectedURL
+ * The URL of the document that is expected to load.
+ * @param aStopFromProgressListener
+ * Whether to cancel the load directly from the progress listener. Defaults to true.
+ * If you're using this method to avoid hitting the network, you want the default (true).
+ * However, the browser UI will behave differently for loads stopped directly from
+ * the progress listener (effectively in the middle of a call to loadURI) and so there
+ * are cases where you may want to avoid stopping the load directly from within the
+ * progress listener callback.
+ * @return promise
+ */
+function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser, aStopFromProgressListener=true) {
+ function content_script(contentStopFromProgressListener) {
+ let { interfaces: Ci, utils: Cu } = Components;
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+ let wp = docShell.QueryInterface(Ci.nsIWebProgress);
+
+ function stopContent(now, uri) {
+ if (now) {
+ /* Hammer time. */
+ content.stop();
+
+ /* Let the parent know we're done. */
+ sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri });
+ } else {
+ setTimeout(stopContent.bind(null, true, uri), 0);
+ }
+ }
+
+ let progressListener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n");
+
+ if (webProgress.isTopLevel &&
+ flags & Ci.nsIWebProgressListener.STATE_START) {
+ wp.removeProgressListener(progressListener);
+
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`);
+
+ stopContent(contentStopFromProgressListener, chan.originalURI.spec);
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
+ };
+ wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW);
+
+ /**
+ * As |this| is undefined and we can't extend |docShell|, adding an unload
+ * event handler is the easiest way to ensure the weakly referenced
+ * progress listener is kept alive as long as necessary.
+ */
+ addEventListener("unload", function () {
+ try {
+ wp.removeProgressListener(progressListener);
+ } catch (e) { /* Will most likely fail. */ }
+ });
+ }
+
+ return new Promise((resolve, reject) => {
+ function complete({ data }) {
+ is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded");
+ mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ resolve();
+ }
+
+ let mm = aBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true);
+ mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
+ });
+}
+
+/**
+ * Waits for the next load to complete in any browser or the given browser.
+ * If a <tabbrowser> is given it waits for a load in any of its browsers.
+ *
+ * @return promise
+ */
+function waitForDocLoadComplete(aBrowser=gBrowser) {
+ return new Promise(resolve => {
+ let listener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
+ Ci.nsIWebProgressListener.STATE_STOP;
+ info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
+
+ // When a load needs to be retargetted to a new process it is cancelled
+ // with NS_BINDING_ABORTED so ignore that case
+ if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
+ aBrowser.removeProgressListener(this);
+ waitForDocLoadComplete.listeners.delete(this);
+
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ info("Browser loaded " + chan.originalURI.spec);
+ resolve();
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference])
+ };
+ aBrowser.addProgressListener(listener);
+ waitForDocLoadComplete.listeners.add(listener);
+ info("Waiting for browser load");
+ });
+}
+
+// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
+// they're not GC'ed before we saw the page load.
+waitForDocLoadComplete.listeners = new Set();
+registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
+
+var FullZoomHelper = {
+
+ selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
+ if (!tab)
+ throw new Error("tab must be given.");
+ if (gBrowser.selectedTab == tab)
+ return Promise.resolve();
+
+ return Promise.all([BrowserTestUtils.switchTab(gBrowser, tab),
+ this.waitForLocationChange()]);
+ },
+
+ removeTabAndWaitForLocationChange: function removeTabAndWaitForLocationChange(tab) {
+ tab = tab || gBrowser.selectedTab;
+ let selected = gBrowser.selectedTab == tab;
+ gBrowser.removeTab(tab);
+ if (selected)
+ return this.waitForLocationChange();
+ return Promise.resolve();
+ },
+
+ waitForLocationChange: function waitForLocationChange() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function obs(subj, topic, data) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }, "browser-fullZoom:location-change", false);
+ });
+ },
+
+ load: function load(tab, url) {
+ return new Promise(resolve => {
+ let didLoad = false;
+ let didZoom = false;
+
+ promiseTabLoadEvent(tab).then(event => {
+ didLoad = true;
+ if (didZoom)
+ resolve();
+ }, true);
+
+ this.waitForLocationChange().then(function () {
+ didZoom = true;
+ if (didLoad)
+ resolve();
+ });
+
+ tab.linkedBrowser.loadURI(url);
+ });
+ },
+
+ zoomTest: function zoomTest(tab, val, msg) {
+ is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
+ },
+
+ enlarge: function enlarge() {
+ return new Promise(resolve => FullZoom.enlarge(resolve));
+ },
+
+ reduce: function reduce() {
+ return new Promise(resolve => FullZoom.reduce(resolve));
+ },
+
+ reset: function reset() {
+ return FullZoom.reset();
+ },
+
+ BACK: 0,
+ FORWARD: 1,
+ navigate: function navigate(direction) {
+ return new Promise(resolve => {
+ let didPs = false;
+ let didZoom = false;
+
+ gBrowser.addEventListener("pageshow", function listener(event) {
+ gBrowser.removeEventListener("pageshow", listener, true);
+ didPs = true;
+ if (didZoom)
+ resolve();
+ }, true);
+
+ if (direction == this.BACK)
+ gBrowser.goBack();
+ else if (direction == this.FORWARD)
+ gBrowser.goForward();
+
+ this.waitForLocationChange().then(function () {
+ didZoom = true;
+ if (didPs)
+ resolve();
+ });
+ });
+ },
+
+ failAndContinue: function failAndContinue(func) {
+ return function (err) {
+ ok(false, err);
+ func();
+ };
+ },
+};
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url)
+{
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url)
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+
+ return loaded;
+}
+
+/**
+ * Returns a Promise that resolves once a new tab has been opened in
+ * a xul:tabbrowser.
+ *
+ * @param aTabBrowser
+ * The xul:tabbrowser to monitor for a new tab.
+ * @return {Promise}
+ * Resolved when the new tab has been opened.
+ * @resolves to the TabOpen event that was fired.
+ * @rejects Never.
+ */
+function waitForNewTabEvent(aTabBrowser) {
+ return promiseWaitForEvent(aTabBrowser.tabContainer, "TabOpen");
+}
+
+/**
+ * Test the state of the identity box and control center to make
+ * sure they are correctly showing the expected mixed content states.
+ *
+ * @note The checks are done synchronously, but new code should wait on the
+ * returned Promise object to ensure the identity panel has closed.
+ * Bug 1221114 is filed to fix the existing code.
+ *
+ * @param tabbrowser
+ * @param Object states
+ * MUST include the following properties:
+ * {
+ * activeLoaded: true|false,
+ * activeBlocked: true|false,
+ * passiveLoaded: true|false,
+ * }
+ *
+ * @return {Promise}
+ * @resolves When the operation has finished and the identity panel has closed.
+ */
+function assertMixedContentBlockingState(tabbrowser, states = {}) {
+ if (!tabbrowser || !("activeLoaded" in states) ||
+ !("activeBlocked" in states) || !("passiveLoaded" in states)) {
+ throw new Error("assertMixedContentBlockingState requires a browser and a states object");
+ }
+
+ let {passiveLoaded, activeLoaded, activeBlocked} = states;
+ let {gIdentityHandler} = tabbrowser.ownerGlobal;
+ let doc = tabbrowser.ownerDocument;
+ let identityBox = gIdentityHandler._identityBox;
+ let classList = identityBox.classList;
+ let connectionIcon = doc.getElementById("connection-icon");
+ let connectionIconImage = tabbrowser.ownerGlobal.getComputedStyle(connectionIcon).
+ getPropertyValue("list-style-image");
+
+ let stateSecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
+ let stateBroken = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
+ let stateInsecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_INSECURE;
+ let stateActiveBlocked = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
+ let stateActiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
+ let statePassiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
+
+ is(activeBlocked, !!stateActiveBlocked, "Expected state for activeBlocked matches UI state");
+ is(activeLoaded, !!stateActiveLoaded, "Expected state for activeLoaded matches UI state");
+ is(passiveLoaded, !!statePassiveLoaded, "Expected state for passiveLoaded matches UI state");
+
+ if (stateInsecure) {
+ // HTTP request, there should be no MCB classes for the identity box and the non secure icon
+ // should always be visible regardless of MCB state.
+ ok(classList.contains("unknownIdentity"), "unknownIdentity on HTTP page");
+ is_element_hidden(connectionIcon);
+
+ ok(!classList.contains("mixedActiveContent"), "No MCB icon on HTTP page");
+ ok(!classList.contains("mixedActiveBlocked"), "No MCB icon on HTTP page");
+ ok(!classList.contains("mixedDisplayContent"), "No MCB icon on HTTP page");
+ ok(!classList.contains("mixedDisplayContentLoadedActiveBlocked"), "No MCB icon on HTTP page");
+ } else {
+ // Make sure the identity box UI has the correct mixedcontent states and icons
+ is(classList.contains("mixedActiveContent"), activeLoaded,
+ "identityBox has expected class for activeLoaded");
+ is(classList.contains("mixedActiveBlocked"), activeBlocked && !passiveLoaded,
+ "identityBox has expected class for activeBlocked && !passiveLoaded");
+ is(classList.contains("mixedDisplayContent"), passiveLoaded && !(activeLoaded || activeBlocked),
+ "identityBox has expected class for passiveLoaded && !(activeLoaded || activeBlocked)");
+ is(classList.contains("mixedDisplayContentLoadedActiveBlocked"), passiveLoaded && activeBlocked,
+ "identityBox has expected class for passiveLoaded && activeBlocked");
+
+ is_element_visible(connectionIcon);
+ if (activeLoaded) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-active-loaded.svg#icon\")",
+ "Using active loaded icon");
+ }
+ if (activeBlocked && !passiveLoaded) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-secure.svg\")",
+ "Using active blocked icon");
+ }
+ if (passiveLoaded && !(activeLoaded || activeBlocked)) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using passive loaded icon");
+ }
+ if (passiveLoaded && activeBlocked) {
+ is(connectionIconImage, "url(\"chrome://browser/skin/connection-mixed-passive-loaded.svg#icon\")",
+ "Using active blocked and passive loaded icon");
+ }
+ }
+
+ // Make sure the identity popup has the correct mixedcontent states
+ gIdentityHandler._identityBox.click();
+ let popupAttr = doc.getElementById("identity-popup").getAttribute("mixedcontent");
+ let bodyAttr = doc.getElementById("identity-popup-securityView-body").getAttribute("mixedcontent");
+
+ is(popupAttr.includes("active-loaded"), activeLoaded,
+ "identity-popup has expected attr for activeLoaded");
+ is(bodyAttr.includes("active-loaded"), activeLoaded,
+ "securityView-body has expected attr for activeLoaded");
+
+ is(popupAttr.includes("active-blocked"), activeBlocked,
+ "identity-popup has expected attr for activeBlocked");
+ is(bodyAttr.includes("active-blocked"), activeBlocked,
+ "securityView-body has expected attr for activeBlocked");
+
+ is(popupAttr.includes("passive-loaded"), passiveLoaded,
+ "identity-popup has expected attr for passiveLoaded");
+ is(bodyAttr.includes("passive-loaded"), passiveLoaded,
+ "securityView-body has expected attr for passiveLoaded");
+
+ // Make sure the correct icon is visible in the Control Center.
+ // This logic is controlled with CSS, so this helps prevent regressions there.
+ let securityView = doc.getElementById("identity-popup-securityView");
+ let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView).
+ getPropertyValue("background-image");
+ let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView).
+ getPropertyValue("background-image");
+
+ if (stateInsecure) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
+ "CC using 'not secure' icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
+ "CC using 'not secure' icon");
+ }
+
+ if (stateSecure) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "CC using secure icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-secure\")",
+ "CC using secure icon");
+ }
+
+ if (stateBroken) {
+ if (activeLoaded) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "CC using active loaded icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
+ "CC using active loaded icon");
+ } else if (activeBlocked || passiveLoaded) {
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ } else {
+ // There is a case here with weak ciphers, but no bc tests are handling this yet.
+ is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/connection.svg#connection-degraded\")",
+ "CC using degraded icon");
+ }
+ }
+
+ if (activeLoaded || activeBlocked || passiveLoaded) {
+ doc.getElementById("identity-popup-security-expander").click();
+ is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
+ element => !is_hidden(element)).length, 1,
+ "The 'Learn more' link should be visible once.");
+ }
+
+ gIdentityHandler._identityPopup.hidden = true;
+
+ // Wait for the panel to be closed before continuing. The promisePopupHidden
+ // function cannot be used because it's unreliable unless promisePopupShown is
+ // also called before closing the panel. This cannot be done until all callers
+ // are made asynchronous (bug 1221114).
+ return new Promise(resolve => executeSoon(resolve));
+}
+
+function is_hidden(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+ if (style.display == "-moz-popup")
+ return ["hiding", "closed"].indexOf(element.state) != -1;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_hidden(element.parentNode);
+
+ return false;
+}
+
+function is_visible(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return false;
+ if (style.visibility != "visible")
+ return false;
+ if (style.display == "-moz-popup" && element.state != "open")
+ return false;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_visible(element.parentNode);
+
+ return true;
+}
+
+function is_element_visible(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_visible(element), msg || "Element should be visible");
+}
+
+function is_element_hidden(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_hidden(element), msg || "Element should be hidden");
+}
+
+function promisePopupEvent(popup, eventSuffix) {
+ let endState = {shown: "open", hidden: "closed"}[eventSuffix];
+
+ if (popup.state == endState)
+ return Promise.resolve();
+
+ let eventType = "popup" + eventSuffix;
+ let deferred = Promise.defer();
+ popup.addEventListener(eventType, function onPopupShown(event) {
+ popup.removeEventListener(eventType, onPopupShown);
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function promisePopupShown(popup) {
+ return promisePopupEvent(popup, "shown");
+}
+
+function promisePopupHidden(popup) {
+ return promisePopupEvent(popup, "hidden");
+}
+
+function promiseNotificationShown(notification) {
+ let win = notification.browser.ownerGlobal;
+ if (win.PopupNotifications.panel.state == "open") {
+ return Promise.resolve();
+ }
+ let panelPromise = promisePopupShown(win.PopupNotifications.panel);
+ notification.reshow();
+ return panelPromise;
+}
+
+/**
+ * Allows waiting for an observer notification once.
+ *
+ * @param aTopic
+ * Notification topic to observe.
+ *
+ * @return {Promise}
+ * @resolves An object with subject and data properties from the observed
+ * notification.
+ * @rejects Never.
+ */
+function promiseTopicObserved(aTopic)
+{
+ return new Promise((resolve) => {
+ Services.obs.addObserver(
+ function PTO_observe(aSubject, aTopic2, aData) {
+ Services.obs.removeObserver(PTO_observe, aTopic2);
+ resolve({subject: aSubject, data: aData});
+ }, aTopic, false);
+ });
+}
+
+function promiseNewSearchEngine(basename) {
+ return new Promise((resolve, reject) => {
+ info("Waiting for engine to be added: " + basename);
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ registerCleanupFunction(() => Services.search.removeEngine(engine));
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ Assert.ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+}
+
+// Compares the security state of the page with what is expected
+function isSecurityState(expectedState) {
+ let ui = gTestBrowser.securityUI;
+ if (!ui) {
+ ok(false, "No security UI to get the security state");
+ return;
+ }
+
+ const wpl = Components.interfaces.nsIWebProgressListener;
+
+ // determine the security state
+ let isSecure = ui.state & wpl.STATE_IS_SECURE;
+ let isBroken = ui.state & wpl.STATE_IS_BROKEN;
+ let isInsecure = ui.state & wpl.STATE_IS_INSECURE;
+
+ let actualState;
+ if (isSecure && !(isBroken || isInsecure)) {
+ actualState = "secure";
+ } else if (isBroken && !(isSecure || isInsecure)) {
+ actualState = "broken";
+ } else if (isInsecure && !(isSecure || isBroken)) {
+ actualState = "insecure";
+ } else {
+ actualState = "unknown";
+ }
+
+ is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + ".");
+}
+
+/**
+ * Resolves when a bookmark with the given uri is added.
+ */
+function promiseOnBookmarkItemAdded(aExpectedURI) {
+ return new Promise((resolve, reject) => {
+ let bookmarksObserver = {
+ onItemAdded: function (aItemId, aFolderId, aIndex, aItemType, aURI) {
+ info("Added a bookmark to " + aURI.spec);
+ PlacesUtils.bookmarks.removeObserver(bookmarksObserver);
+ if (aURI.equals(aExpectedURI)) {
+ resolve();
+ }
+ else {
+ reject(new Error("Added an unexpected bookmark"));
+ }
+ },
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onItemRemoved: function () {},
+ onItemChanged: function () {},
+ onItemVisited: function () {},
+ onItemMoved: function () {},
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver,
+ ])
+ };
+ info("Waiting for a bookmark to be added");
+ PlacesUtils.bookmarks.addObserver(bookmarksObserver, false);
+ });
+}
+
+function promiseErrorPageLoaded(browser) {
+ return new Promise(resolve => {
+ browser.addEventListener("DOMContentLoaded", function onLoad() {
+ browser.removeEventListener("DOMContentLoaded", onLoad, false, true);
+ resolve();
+ }, false, true);
+ });
+}
+
+function* loadBadCertPage(url) {
+ const EXCEPTION_DIALOG_URI = "chrome://pippki/content/exceptionDialog.xul";
+ let exceptionDialogResolved = new Promise(function(resolve) {
+ // When the certificate exception dialog has opened, click the button to add
+ // an exception.
+ let certExceptionDialogObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "cert-exception-ui-ready") {
+ Services.obs.removeObserver(this, "cert-exception-ui-ready");
+ let certExceptionDialog = getCertExceptionDialog(EXCEPTION_DIALOG_URI);
+ ok(certExceptionDialog, "found exception dialog");
+ executeSoon(function() {
+ certExceptionDialog.documentElement.getButton("extra1").click();
+ resolve();
+ });
+ }
+ }
+ };
+
+ Services.obs.addObserver(certExceptionDialogObserver,
+ "cert-exception-ui-ready", false);
+ });
+
+ let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+ yield BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
+ yield loaded;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ content.document.getElementById("exceptionDialogButton").click();
+ });
+ yield exceptionDialogResolved;
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+}
+
+// Utility function to get a handle on the certificate exception dialog.
+// Modified from toolkit/components/passwordmgr/test/prompt_common.js
+function getCertExceptionDialog(aLocation) {
+ let enumerator = Services.wm.getXULWindowEnumerator(null);
+
+ while (enumerator.hasMoreElements()) {
+ let win = enumerator.getNext();
+ let windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+
+ let containedDocShells = windowDocShell.getDocShellEnumerator(
+ Ci.nsIDocShellTreeItem.typeChrome,
+ Ci.nsIDocShell.ENUMERATE_FORWARDS);
+ while (containedDocShells.hasMoreElements()) {
+ // Get the corresponding document for this docshell
+ let childDocShell = containedDocShells.getNext();
+ let childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
+ .contentViewer
+ .DOMDocument;
+
+ if (childDoc.location.href == aLocation) {
+ return childDoc;
+ }
+ }
+ }
+ return undefined;
+}
+
+function setupRemoteClientsFixture(fixture) {
+ let oldRemoteClientsGetter =
+ Object.getOwnPropertyDescriptor(gFxAccounts, "remoteClients").get;
+
+ Object.defineProperty(gFxAccounts, "remoteClients", {
+ get: function() { return fixture; }
+ });
+ return oldRemoteClientsGetter;
+}
+
+function restoreRemoteClients(getter) {
+ Object.defineProperty(gFxAccounts, "remoteClients", {
+ get: getter
+ });
+}
+
+function* openMenuItemSubmenu(id) {
+ let menuPopup = document.getElementById(id).menupopup;
+ let menuPopupPromise = BrowserTestUtils.waitForEvent(menuPopup, "popupshown");
+ menuPopup.showPopup();
+ yield menuPopupPromise;
+}
diff --git a/browser/base/content/test/general/head_plain.js b/browser/base/content/test/general/head_plain.js
new file mode 100644
index 000000000..3796c7d2b
--- /dev/null
+++ b/browser/base/content/test/general/head_plain.js
@@ -0,0 +1,27 @@
+
+function getTestPlugin(pluginName) {
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ var name = pluginName || "Test Plug-in";
+ for (var tag of tags) {
+ if (tag.name == name) {
+ return tag;
+ }
+ }
+
+ ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+ return null;
+}
+
+// call this to set the test plugin(s) initially expected enabled state.
+// it will automatically be reset to it's previous value after the test
+// ends
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ var plugin = getTestPlugin(pluginName);
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = newEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ getTestPlugin(pluginName).enabledState = oldEnabledState;
+ });
+}
diff --git a/browser/base/content/test/general/healthreport_pingData.js b/browser/base/content/test/general/healthreport_pingData.js
new file mode 100644
index 000000000..1737baba1
--- /dev/null
+++ b/browser/base/content/test/general/healthreport_pingData.js
@@ -0,0 +1,17 @@
+var TEST_PINGS = [
+ {
+ type: "test-telemetryArchive-1",
+ payload: { foo: "bar" },
+ date: new Date(2010, 1, 1, 10, 0, 0),
+ },
+ {
+ type: "test-telemetryArchive-2",
+ payload: { x: { y: "z"} },
+ date: new Date(2010, 1, 1, 11, 0, 0),
+ },
+ {
+ type: "test-telemetryArchive-3",
+ payload: { moo: "meh" },
+ date: new Date(2010, 1, 1, 12, 0, 0),
+ },
+];
diff --git a/browser/base/content/test/general/healthreport_testRemoteCommands.html b/browser/base/content/test/general/healthreport_testRemoteCommands.html
new file mode 100644
index 000000000..7978914f2
--- /dev/null
+++ b/browser/base/content/test/general/healthreport_testRemoteCommands.html
@@ -0,0 +1,243 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+<script type="application/javascript;version=1.7"
+ src="healthreport_pingData.js">
+</script>
+<script type="application/javascript;version=1.7">
+
+function init() {
+ window.addEventListener("message", doTest, false);
+ doTest();
+}
+
+function checkSubmissionValue(payload, expectedValue) {
+ return payload.enabled == expectedValue;
+}
+
+function isArray(arg) {
+ return Object.prototype.toString.call(arg) === '[object Array]';
+}
+
+function writeDiagnostic(text) {
+ let node = document.createTextNode(text);
+ let br = document.createElement("br");
+ document.body.appendChild(node);
+ document.body.appendChild(br);
+}
+
+function validateCurrentTelemetryEnvironment(data) {
+ // Simple check for now: check that the received object has the expected
+ // top-level properties.
+ const expectedKeys = ["profile", "settings", "system", "build", "partner", "addons"];
+ return expectedKeys.every(key => (key in data));
+}
+
+function validateCurrentTelemetryPingData(ping) {
+ // Simple check for now: check that the received object has the expected
+ // top-level properties and that the type and reason match.
+ const expectedKeys = ["environment", "clientId", "payload", "application",
+ "version", "type", "id"];
+ return expectedKeys.every(key => (key in ping)) &&
+ (ping.type == "main") &&
+ ("info" in ping.payload) &&
+ ("reason" in ping.payload.info) &&
+ (ping.payload.info.reason == "gather-subsession-payload");
+}
+
+function validateTelemetryPingList(list) {
+ if (!isArray(list)) {
+ console.log("Telemetry ping list is not an array.");
+ return false;
+ }
+
+ // Telemetry may generate other pings (e.g. "deletion" pings), so filter those
+ // out.
+ const TEST_TYPES_REGEX = /^test-telemetryArchive/;
+ list = list.filter(p => TEST_TYPES_REGEX.test(p.type));
+
+ if (list.length != TEST_PINGS.length) {
+ console.log("Telemetry ping length is not correct.");
+ return false;
+ }
+
+ let valid = true;
+ for (let i=0; i<list.length; ++i) {
+ let received = list[i];
+ let expected = TEST_PINGS[i];
+ if (received.type != expected.type ||
+ received.timestampCreated != expected.date.getTime()) {
+ writeDiagnostic("Telemetry ping " + i + " does not match.");
+ writeDiagnostic("Expected: " + JSON.stringify(expected));
+ writeDiagnostic("Received: " + JSON.stringify(received));
+ valid = false;
+ } else {
+ writeDiagnostic("Telemetry ping " + i + " matches.");
+ }
+ }
+
+ return true;
+}
+
+function validateTelemetryPingData(expected, received) {
+ const receivedDate = new Date(received.creationDate);
+ if (received.id != expected.id ||
+ received.type != expected.type ||
+ receivedDate.getTime() != expected.date.getTime()) {
+ writeDiagnostic("Telemetry ping data for " + expected.id + " doesn't match.");
+ writeDiagnostic("Expected: " + JSON.stringify(expected));
+ writeDiagnostic("Received: " + JSON.stringify(received));
+ return false;
+ }
+
+ writeDiagnostic("Telemetry ping data for " + expected.id + " matched.");
+ return true;
+}
+
+var tests = [
+{
+ info: "Checking initial value is enabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying disabling works",
+ event: "DisableDataSubmission",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, false);
+ },
+},
+{
+ info: "Verifying we're still disabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, false);
+ },
+},
+{
+ info: "Verifying that we can get the current ping data while submission is disabled",
+ event: "RequestCurrentPingData",
+ payloadType: "telemetry-current-ping-data",
+ validateResponse: function(payload) {
+ return validateCurrentTelemetryPingData(payload);
+ },
+},
+{
+ info: "Verifying enabling works",
+ event: "EnableDataSubmission",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying we're still re-enabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying that we can get the current Telemetry environment data",
+ event: "RequestCurrentEnvironment",
+ payloadType: "telemetry-current-environment-data",
+ validateResponse: function(payload) {
+ return validateCurrentTelemetryEnvironment(payload);
+ },
+},
+{
+ info: "Verifying that we can get the current Telemetry ping data",
+ event: "RequestCurrentPingData",
+ payloadType: "telemetry-current-ping-data",
+ validateResponse: function(payload) {
+ return validateCurrentTelemetryPingData(payload);
+ },
+},
+{
+ info: "Verifying that we get the proper Telemetry ping list",
+ event: "RequestTelemetryPingList",
+ payloadType: "telemetry-ping-list",
+ validateResponse: function(payload) {
+ // Validate the ping list
+ if (!validateTelemetryPingList(payload)) {
+ return false;
+ }
+
+ // Now that we received the ping ids, set up additional test tasks
+ // that check loading the individual pings.
+ for (let i=0; i<TEST_PINGS.length; ++i) {
+ TEST_PINGS[i].id = payload[i].id;
+ tests.push({
+ info: "Verifying that we can get the proper Telemetry ping data #" + (i + 1),
+ event: "RequestTelemetryPingData",
+ eventData: { id: TEST_PINGS[i].id },
+ payloadType: "telemetry-ping-data",
+ validateResponse: function(payload) {
+ return validateTelemetryPingData(TEST_PINGS[i], payload.pingData);
+ },
+ });
+ }
+
+ return true;
+ },
+},
+];
+
+var currentTest = -1;
+function doTest(evt) {
+ if (evt) {
+ if (currentTest < 0 || !evt.data.content)
+ return; // not yet testing
+
+ var test = tests[currentTest];
+ if (evt.data.type != test.payloadType)
+ return; // skip unrequested events
+
+ var error = JSON.stringify(evt.data.content);
+ var pass = false;
+ try {
+ pass = test.validateResponse(evt.data.content)
+ } catch (e) {}
+ reportResult(test.info, pass, error);
+ }
+ // start the next test if there are any left
+ if (tests[++currentTest])
+ sendToBrowser(tests[currentTest].event, tests[currentTest].eventData);
+ else
+ reportFinished();
+}
+
+function reportResult(info, pass, error) {
+ var data = {type: "testResult", info: info, pass: pass, error: error};
+ var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function reportFinished(cmd) {
+ var data = {type: "testsComplete", count: tests.length};
+ var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+function sendToBrowser(type, eventData) {
+ eventData = eventData || {};
+ let detail = {command: type};
+ for (let key of Object.keys(eventData)) {
+ detail[key] = eventData[key];
+ }
+
+ var event = new CustomEvent("RemoteHealthReportCommand", {detail: detail, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+</script>
+ </head>
+ <body onload="init()">
+ </body>
+</html>
diff --git a/browser/base/content/test/general/insecure_opener.html b/browser/base/content/test/general/insecure_opener.html
new file mode 100644
index 000000000..26ed014f6
--- /dev/null
+++ b/browser/base/content/test/general/insecure_opener.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <a id="link" target="_blank" href="https://example.com/browser/toolkit/components/passwordmgr/test/browser/form_basic.html">Click me, I'm "secure".</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/mochitest.ini b/browser/base/content/test/general/mochitest.ini
new file mode 100644
index 000000000..a07a01b87
--- /dev/null
+++ b/browser/base/content/test/general/mochitest.ini
@@ -0,0 +1,27 @@
+[DEFAULT]
+support-files =
+ audio.ogg
+ bug364677-data.xml
+ bug364677-data.xml^headers^
+ bug395533-data.txt
+ contextmenu_common.js
+ ctxmenu-image.png
+ head_plain.js
+ offlineByDefault.js
+ offlineChild.cacheManifest
+ offlineChild.cacheManifest^headers^
+ offlineChild.html
+ offlineChild2.cacheManifest
+ offlineChild2.cacheManifest^headers^
+ offlineChild2.html
+ offlineEvent.cacheManifest
+ offlineEvent.cacheManifest^headers^
+ offlineEvent.html
+ subtst_contextmenu.html
+ video.ogg
+ !/image/test/mochitest/blue.png
+
+[test_bug364677.html]
+[test_bug395533.html]
+[test_offlineNotification.html]
+skip-if = e10s # Bug 1257785
diff --git a/browser/base/content/test/general/moz.png b/browser/base/content/test/general/moz.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/base/content/test/general/moz.png
Binary files differ
diff --git a/browser/base/content/test/general/navigating_window_with_download.html b/browser/base/content/test/general/navigating_window_with_download.html
new file mode 100644
index 000000000..6b0918941
--- /dev/null
+++ b/browser/base/content/test/general/navigating_window_with_download.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <head><title>This window will navigate while you're downloading something</title></head>
+ <body>
+ <iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/offlineByDefault.js b/browser/base/content/test/general/offlineByDefault.js
new file mode 100644
index 000000000..72f7e52a0
--- /dev/null
+++ b/browser/base/content/test/general/offlineByDefault.js
@@ -0,0 +1,17 @@
+var offlineByDefault = {
+ defaultValue: false,
+ prefBranch: SpecialPowers.Cc["@mozilla.org/preferences-service;1"].getService(SpecialPowers.Ci.nsIPrefBranch),
+ set: function(allow) {
+ try {
+ this.defaultValue = this.prefBranch.getBoolPref("offline-apps.allow_by_default");
+ } catch (e) {
+ this.defaultValue = false
+ }
+ this.prefBranch.setBoolPref("offline-apps.allow_by_default", allow);
+ },
+ reset: function() {
+ this.prefBranch.setBoolPref("offline-apps.allow_by_default", this.defaultValue);
+ }
+}
+
+offlineByDefault.set(false);
diff --git a/browser/base/content/test/general/offlineChild.cacheManifest b/browser/base/content/test/general/offlineChild.cacheManifest
new file mode 100644
index 000000000..091fe7194
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild.html
diff --git a/browser/base/content/test/general/offlineChild.cacheManifest^headers^ b/browser/base/content/test/general/offlineChild.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/offlineChild.html b/browser/base/content/test/general/offlineChild.html
new file mode 100644
index 000000000..43f225b3b
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild.html
@@ -0,0 +1,20 @@
+<html manifest="offlineChild.cacheManifest">
+<head>
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success ? "success" : "failure", "*");
+}
+
+applicationCache.oncached = function() { finish(true); }
+applicationCache.onnoupdate = function() { finish(true); }
+applicationCache.onerror = function() { finish(false); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/offlineChild2.cacheManifest b/browser/base/content/test/general/offlineChild2.cacheManifest
new file mode 100644
index 000000000..19efe54fe
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild2.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild2.html
diff --git a/browser/base/content/test/general/offlineChild2.cacheManifest^headers^ b/browser/base/content/test/general/offlineChild2.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild2.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/offlineChild2.html b/browser/base/content/test/general/offlineChild2.html
new file mode 100644
index 000000000..ac762e759
--- /dev/null
+++ b/browser/base/content/test/general/offlineChild2.html
@@ -0,0 +1,20 @@
+<html manifest="offlineChild2.cacheManifest">
+<head>
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success ? "success" : "failure", "*");
+}
+
+applicationCache.oncached = function() { finish(true); }
+applicationCache.onnoupdate = function() { finish(true); }
+applicationCache.onerror = function() { finish(false); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/offlineEvent.cacheManifest b/browser/base/content/test/general/offlineEvent.cacheManifest
new file mode 100644
index 000000000..091fe7194
--- /dev/null
+++ b/browser/base/content/test/general/offlineEvent.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild.html
diff --git a/browser/base/content/test/general/offlineEvent.cacheManifest^headers^ b/browser/base/content/test/general/offlineEvent.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/general/offlineEvent.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/general/offlineEvent.html b/browser/base/content/test/general/offlineEvent.html
new file mode 100644
index 000000000..f6e2494e2
--- /dev/null
+++ b/browser/base/content/test/general/offlineEvent.html
@@ -0,0 +1,9 @@
+<html manifest="offlineEvent.cacheManifest">
+<head>
+<title></title>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/general/offlineQuotaNotification.cacheManifest b/browser/base/content/test/general/offlineQuotaNotification.cacheManifest
new file mode 100644
index 000000000..2e210abd2
--- /dev/null
+++ b/browser/base/content/test/general/offlineQuotaNotification.cacheManifest
@@ -0,0 +1,7 @@
+CACHE MANIFEST
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# store a "large" file so an "over quota warning" will be issued - any file
+# larger than 1kb and in '_BROWSER_FILES' should be right...
+title_test.svg
diff --git a/browser/base/content/test/general/offlineQuotaNotification.html b/browser/base/content/test/general/offlineQuotaNotification.html
new file mode 100644
index 000000000..b1b91bf9e
--- /dev/null
+++ b/browser/base/content/test/general/offlineQuotaNotification.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html manifest="offlineQuotaNotification.cacheManifest">
+<head>
+ <meta charset="utf-8">
+ <title>Test offline app quota notification</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+</html>
diff --git a/browser/base/content/test/general/page_style_sample.html b/browser/base/content/test/general/page_style_sample.html
new file mode 100644
index 000000000..54cbaa9e6
--- /dev/null
+++ b/browser/base/content/test/general/page_style_sample.html
@@ -0,0 +1,41 @@
+<html>
+ <head>
+ <title>Test for page style menu</title>
+ <!-- data-state values:
+ 0: should not appear in the page style menu
+ 0-todo: should not appear in the page style menu, but does
+ 1: should appear in the page style menu
+ 2: should appear in the page style menu as the selected stylesheet -->
+ <link data-state="1" href="404.css" title="1" rel="alternate stylesheet">
+ <link data-state="0" title="2" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" title="" rel="alternate stylesheet">
+ <link data-state="1" href="404.css" title="3" rel="stylesheet alternate">
+ <link data-state="1" href="404.css" title="4" rel=" alternate stylesheet ">
+ <link data-state="1" href="404.css" title="5" rel="alternate stylesheet">
+ <link data-state="2" href="404.css" title="6" rel="stylesheet">
+ <link data-state="1" href="404.css" title="7" rel="foo stylesheet">
+ <link data-state="0" href="404.css" title="8" rel="alternate">
+ <link data-state="1" href="404.css" title="9" rel="alternate STYLEsheet">
+ <link data-state="1" href="404.css" title="10" rel="alternate stylesheet" media="">
+ <link data-state="1" href="404.css" title="11" rel="alternate stylesheet" media="all">
+ <link data-state="1" href="404.css" title="12" rel="alternate stylesheet" media="ALL ">
+ <link data-state="1" href="404.css" title="13" rel="alternate stylesheet" media="screen">
+ <link data-state="1" href="404.css" title="14" rel="alternate stylesheet" media=" Screen">
+ <link data-state="0" href="404.css" title="15" rel="alternate stylesheet" media="screen foo">
+ <link data-state="0" href="404.css" title="16" rel="alternate stylesheet" media="all screen">
+ <link data-state="0" href="404.css" title="17" rel="alternate stylesheet" media="foo bar">
+ <link data-state="1" href="404.css" title="18" rel="alternate stylesheet" media="all,screen">
+ <link data-state="1" href="404.css" title="19" rel="alternate stylesheet" media="all, screen">
+ <link data-state="0" href="404.css" title="20" rel="alternate stylesheet" media="all screen">
+ <link data-state="0" href="404.css" title="21" rel="alternate stylesheet" media="foo">
+ <link data-state="0" href="404.css" title="22" rel="alternate stylesheet" media="allscreen">
+ <link data-state="0" href="404.css" title="23" rel="alternate stylesheet" media="_all">
+ <link data-state="0" href="404.css" title="24" rel="alternate stylesheet" media="not screen">
+ <link data-state="1" href="404.css" title="25" rel="alternate stylesheet" media="only screen">
+ <link data-state="1" href="404.css" title="26" rel="alternate stylesheet" media="screen and (min-device-width: 1px)">
+ <link data-state="0" href="404.css" title="27" rel="alternate stylesheet" media="screen and (max-device-width: 1px)">
+ <style data-state="1" title="28">/* some more styles */</style>
+ </head>
+ <body></body>
+</html>
diff --git a/browser/base/content/test/general/parsingTestHelpers.jsm b/browser/base/content/test/general/parsingTestHelpers.jsm
new file mode 100644
index 000000000..69c764483
--- /dev/null
+++ b/browser/base/content/test/general/parsingTestHelpers.jsm
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["generateURIsFromDirTree"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+/* Shorthand constructors to construct an nsI(Local)File and zip reader: */
+const LocalFile = new Components.Constructor("@mozilla.org/file/local;1", Ci.nsIFile, "initWithPath");
+const ZipReader = new Components.Constructor("@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open");
+
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+
+/**
+ * Returns a promise that is resolved with a list of files that have one of the
+ * extensions passed, represented by their nsIURI objects, which exist inside
+ * the directory passed.
+ *
+ * @param dir the directory which to scan for files (nsIFile)
+ * @param extensions the extensions of files we're interested in (Array).
+ */
+function generateURIsFromDirTree(dir, extensions) {
+ if (!Array.isArray(extensions)) {
+ extensions = [extensions];
+ }
+ let dirQueue = [dir.path];
+ return Task.spawn(function*() {
+ let rv = [];
+ while (dirQueue.length) {
+ let nextDir = dirQueue.shift();
+ let {subdirs, files} = yield iterateOverPath(nextDir, extensions);
+ dirQueue.push(...subdirs);
+ rv.push(...files);
+ }
+ return rv;
+ });
+}
+
+/**
+ * Uses OS.File.DirectoryIterator to asynchronously iterate over a directory.
+ * It returns a promise that is resolved with an object with two properties:
+ * - files: an array of nsIURIs corresponding to files that match the extensions passed
+ * - subdirs: an array of paths for subdirectories we need to recurse into
+ * (handled by generateURIsFromDirTree above)
+ *
+ * @param path the path to check (string)
+ * @param extensions the file extensions we're interested in.
+ */
+function iterateOverPath(path, extensions) {
+ let iterator = new OS.File.DirectoryIterator(path);
+ let parentDir = new LocalFile(path);
+ let subdirs = [];
+ let files = [];
+
+ let pathEntryIterator = (entry) => {
+ if (entry.isDir) {
+ subdirs.push(entry.path);
+ } else if (extensions.some((extension) => entry.name.endsWith(extension))) {
+ let file = parentDir.clone();
+ file.append(entry.name);
+ // the build system might leave dead symlinks hanging around, which are
+ // returned as part of the directory iterator, but don't actually exist:
+ if (file.exists()) {
+ let uriSpec = getURLForFile(file);
+ files.push(Services.io.newURI(uriSpec, null, null));
+ }
+ } else if (entry.name.endsWith(".ja") || entry.name.endsWith(".jar") ||
+ entry.name.endsWith(".zip") || entry.name.endsWith(".xpi")) {
+ let file = parentDir.clone();
+ file.append(entry.name);
+ for (let extension of extensions) {
+ let jarEntryIterator = generateEntriesFromJarFile(file, extension);
+ files.push(...jarEntryIterator);
+ }
+ }
+ };
+
+ return new Promise((resolve, reject) => {
+ Task.spawn(function* () {
+ try {
+ // Iterate through the directory
+ yield iterator.forEach(pathEntryIterator);
+ resolve({files: files, subdirs: subdirs});
+ } catch (ex) {
+ reject(ex);
+ } finally {
+ iterator.close();
+ }
+ });
+ });
+}
+
+/* Helper function to generate a URI spec (NB: not an nsIURI yet!)
+ * given an nsIFile object */
+function getURLForFile(file) {
+ let fileHandler = Services.io.getProtocolHandler("file");
+ fileHandler = fileHandler.QueryInterface(Ci.nsIFileProtocolHandler);
+ return fileHandler.getURLSpecFromActualFile(file);
+}
+
+/**
+ * A generator that generates nsIURIs for particular files found in jar files
+ * like omni.ja.
+ *
+ * @param jarFile an nsIFile object for the jar file that needs checking.
+ * @param extension the extension we're interested in.
+ */
+function* generateEntriesFromJarFile(jarFile, extension) {
+ let zr = new ZipReader(jarFile);
+ let entryEnumerator = zr.findEntries("*" + extension + "$");
+
+ const kURIStart = getURLForFile(jarFile);
+ while (entryEnumerator.hasMore()) {
+ let entry = entryEnumerator.getNext();
+ // Ignore the JS cache which is stored in omni.ja
+ if (entry.startsWith("jsloader") || entry.startsWith("jssubloader")) {
+ continue;
+ }
+ let entryURISpec = "jar:" + kURIStart + "!/" + entry;
+ yield Services.io.newURI(entryURISpec, null, null);
+ }
+ zr.close();
+}
+
+
diff --git a/browser/base/content/test/general/permissions.html b/browser/base/content/test/general/permissions.html
new file mode 100644
index 000000000..46436a006
--- /dev/null
+++ b/browser/base/content/test/general/permissions.html
@@ -0,0 +1,14 @@
+<!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 dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <!-- This page could eventually request permissions from content
+ and make sure that chrome responds appropriately -->
+ <button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/pinning_headers.sjs b/browser/base/content/test/general/pinning_headers.sjs
new file mode 100644
index 000000000..51496183a
--- /dev/null
+++ b/browser/base/content/test/general/pinning_headers.sjs
@@ -0,0 +1,23 @@
+const INVALIDPIN1 = "pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\";";
+const INVALIDPIN2 = "pin-sha256=\"AAAAAAAAAAAAAAAAAAAAAAAAAj0e1Md7GkYYkVoZWmM=\";";
+const VALIDPIN = "pin-sha256=\"hXweb81C3HnmM2Ai1dnUzFba40UJMhuu8qZmvN/6WWc=\";";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ switch (request.queryString) {
+ case "zeromaxagevalid":
+ response.setHeader("Public-Key-Pins", "max-age=0;" + VALIDPIN +
+ INVALIDPIN2 + "includeSubdomains");
+ break;
+ case "valid":
+ default:
+ response.setHeader("Public-Key-Pins", "max-age=50000;" + VALIDPIN +
+ INVALIDPIN2 + "includeSubdomains");
+ }
+
+ response.write("Hello world!" + request.host);
+}
diff --git a/browser/base/content/test/general/print_postdata.sjs b/browser/base/content/test/general/print_postdata.sjs
new file mode 100644
index 000000000..4175a2480
--- /dev/null
+++ b/browser/base/content/test/general/print_postdata.sjs
@@ -0,0 +1,22 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/browser/base/content/test/general/refresh_header.sjs b/browser/base/content/test/general/refresh_header.sjs
new file mode 100644
index 000000000..327372f9b
--- /dev/null
+++ b/browser/base/content/test/general/refresh_header.sjs
@@ -0,0 +1,24 @@
+/**
+ * Will cause an auto-refresh to the URL provided in the query string
+ * after some delay using the refresh HTTP header.
+ *
+ * Expects the query string to be in the format:
+ *
+ * ?p=[URL of the page to redirect to]&d=[delay]
+ *
+ * Example:
+ *
+ * ?p=http%3A%2F%2Fexample.org%2Fbrowser%2Fbrowser%2Fbase%2Fcontent%2Ftest%2Fgeneral%2Frefresh_meta.sjs&d=200
+ */
+function handleRequest(request, response) {
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let page = query.get("p");
+ let delay = query.get("d");
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "200", "Found");
+ response.setHeader("refresh", `${delay}; url=${page}`);
+ response.write("OK");
+} \ No newline at end of file
diff --git a/browser/base/content/test/general/refresh_meta.sjs b/browser/base/content/test/general/refresh_meta.sjs
new file mode 100644
index 000000000..648fac1a3
--- /dev/null
+++ b/browser/base/content/test/general/refresh_meta.sjs
@@ -0,0 +1,36 @@
+/**
+ * Will cause an auto-refresh to the URL provided in the query string
+ * after some delay using a <meta> tag.
+ *
+ * Expects the query string to be in the format:
+ *
+ * ?p=[URL of the page to redirect to]&d=[delay]
+ *
+ * Example:
+ *
+ * ?p=http%3A%2F%2Fexample.org%2Fbrowser%2Fbrowser%2Fbase%2Fcontent%2Ftest%2Fgeneral%2Frefresh_meta.sjs&d=200
+ */
+function handleRequest(request, response) {
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let page = query.get("p");
+ let delay = query.get("d");
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <META http-equiv='refresh' content='${delay}; url=${page}'>
+ <title>Gonna refresh you, folks.</title>
+ </head>
+ <body>
+ <h1>Wait for it...</h1>
+ </body>
+ </html>`;
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "200", "Found");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(html);
+} \ No newline at end of file
diff --git a/browser/base/content/test/general/searchSuggestionEngine.sjs b/browser/base/content/test/general/searchSuggestionEngine.sjs
new file mode 100644
index 000000000..1978b4f66
--- /dev/null
+++ b/browser/base/content/test/general/searchSuggestionEngine.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/base/content/test/general/searchSuggestionEngine.xml b/browser/base/content/test/general/searchSuggestionEngine.xml
new file mode 100644
index 000000000..3d1f294f5
--- /dev/null
+++ b/browser/base/content/test/general/searchSuggestionEngine.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/general/searchSuggestionEngine2.xml b/browser/base/content/test/general/searchSuggestionEngine2.xml
new file mode 100644
index 000000000..05644649a
--- /dev/null
+++ b/browser/base/content/test/general/searchSuggestionEngine2.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine2.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine2&amp;terms={searchTerms}" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/general/ssl_error_reports.sjs b/browser/base/content/test/general/ssl_error_reports.sjs
new file mode 100644
index 000000000..e2e5bafc0
--- /dev/null
+++ b/browser/base/content/test/general/ssl_error_reports.sjs
@@ -0,0 +1,91 @@
+const EXPECTED_CHAIN = [
+ "MIIDCjCCAfKgAwIBAgIENUiGYDANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQxMDAxMjExNDE5WhcNMjQxMDAxMjExNDE5WjAxMS8wLQYDVQQDEyZpbmNsdWRlLXN1YmRvbWFpbnMucGlubmluZy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALxYrge8C4eVfTb6/lJ4k/+/4J6wlnWpp5Szxy1MHhsLB+LJh/HRHqkO/tsigT204kTeU3dxuAfQHz0g+Td8dr6KICLLNVFUPw+XjhBV4AtxV8wcprs6EmdBhJgAjkFB4M76BL7/Ow0NfH012WNESn8TTbsp3isgkmrXjTZhWR33vIL1eDNimykp/Os/+JO+x9KVfdCtDCrPwO9Yusial5JiaW7qemRtVuUDL87NSJ7xokPEOSc9luv/fBamZ3rgqf3K6epqg+0o3nNCCcNFnfLW52G0t69+dIjr39WISHnqqZj3Sb7JPU6OmxTd13ByoLkoM3ZUQ2Lpas+RJvQyGXkCAwEAAaM1MDMwMQYDVR0RBCowKIImaW5jbHVkZS1zdWJkb21haW5zLnBpbm5pbmcuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAAmzXfeoOS59FkNABRonFPRyFl7BoGpVJENUteFfTa2pdAhGYdo19Y4uILTTj+vtDAa5yryb5Uvd+YuJnExosbMMkzCrmZ9+VJCJdqUTb+idwk9/sgPl2gtGeRmefB0hXSUFHc/p1CDufSpYOmj9NCUZD2JEsybgJQNulkfAsVnS3lzDcxAwcO+RC/1uJDSiUtcBpWS4FW58liuDYE7PD67kLJHZPVUV2WCMuIl4VM2tKPtvShz1JkZ5UytOLs6jPfviNAk/ftXczaE2/RJgM2MnDX9nGzOxG6ONcVNCljL8avhFBCosutE6i5LYSZR6V14YY/xOn15WDSuWdnIsJCo=",
+ "MIIC2jCCAcKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQwOTI1MjEyMTU0WhcNMjQwOTI1MjEyMTU0WjAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBT+BwAhO52IWgSIdZZifU9LHOs3IR/+8DCC0WP5d/OuyKlZ6Rqd0tsd3i7durhQyjHSbLf2lJStcnFjcVEbEnNI76RuvlN8xLLn5eV+2Ayr4cZYKztudwRmw+DV/iYAiMSy0hs7m3ssfX7qpoi1aNRjUanwU0VTCPQhF1bEKAC2du+C5Z8e92zN5t87w7bYr7lt+m8197XliXEu+0s9RgnGwGaZ296BIRz6NOoJYTa43n06LU1I1+Z4d6lPdzUFrSR0GBaMhUSurUBtOin3yWiMhg1VHX/KwqGc4als5GyCVXy8HGrA/0zQPOhetxrlhEVAdK/xBt7CZvByj1Rcc7AgMBAAGjEzARMA8GA1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAJq/hogSRqzPWTwX4wTn/DVSNdWwFLv53qep9YrSMJ8ZsfbfK9Es4VP4dBLRQAVMJ0Z5mW1I6d/n0KayTanuUBvemYdxPi/qQNSs8UJcllqdhqWzmzAg6a0LxrMnEeKzPBPD6q8PwQ7tYP+B4sBN9tnnsnyPgti9ZiNZn5FwXZliHXseQ7FE9/SqHlLw5LXW3YtKjuti6RmuV6fq3j+D4oeC5vb1mKgIyoTqGN6ze57v8RHi+pQ8Q+kmoUn/L3Z2YmFe4SKN/4WoyXr8TdejpThGOCGCAd3565s5gOx5QfSQX11P8NZKO8hcN0tme3VzmGpHK0Z/6MTmdpNaTwQ6odk="
+ ];
+
+const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = -16384;
+
+function parseReport(request) {
+ // read the report from the request
+ let inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
+ inputStream.init(request.bodyInputStream, 0x01, 0004, 0);
+
+ let body = "";
+ if (inputStream) {
+ while (inputStream.available()) {
+ body = body + inputStream.read(inputStream.available());
+ }
+ }
+ // parse the report
+ return JSON.parse(body);
+}
+
+function handleRequest(request, response) {
+ let report = {};
+ let certChain = [];
+
+ switch (request.queryString) {
+ case "succeed":
+ report = parseReport(request);
+ certChain = report.failedCertChain;
+
+ // ensure the cert chain is what we expect
+ for (idx in certChain) {
+ if (certChain[idx] !== EXPECTED_CHAIN[idx]) {
+ // if the chain differs, send an error response to cause test
+ // failure
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected chain</html>");
+ return;
+ }
+ }
+
+ if (report.errorCode !== MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE) {
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected error code</html>");
+ return;
+ }
+
+ // if all is as expected, send the 201 the client expects
+ response.setStatusLine("1.1", 201, "Created");
+ response.write("<html>OK</html>");
+ break;
+ case "nocert":
+ report = parseReport(request);
+ certChain = report.failedCertChain;
+
+ if (certChain && certChain.length > 0) {
+ // We're not expecting a chain; if there is one, send an error
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected chain</html>");
+ return;
+ }
+
+ // if all is as expected, send the 201 the client expects
+ response.setStatusLine("1.1", 201, "Created");
+ response.write("<html>OK</html>");
+ break;
+ case "badcert":
+ report = parseReport(request);
+ certChain = report.failedCertChain;
+
+ if (!certChain || certChain.length != 2) {
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>The report contained an unexpected chain</html>");
+ return;
+ }
+
+ // if all is as expected, send the 201 the client expects
+ response.setStatusLine("1.1", 201, "Created");
+ response.write("<html>OK</html>");
+ break;
+ case "error":
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>server error</html>");
+ break;
+ default:
+ response.setStatusLine("1.1", 500, "Server error");
+ response.write("<html>succeed, nocert or error expected (got " + request.queryString + ")</html>");
+ break;
+ }
+}
diff --git a/browser/base/content/test/general/subtst_contextmenu.html b/browser/base/content/test/general/subtst_contextmenu.html
new file mode 100644
index 000000000..1768f399f
--- /dev/null
+++ b/browser/base/content/test/general/subtst_contextmenu.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+Browser context menu subtest.
+
+<div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<a id="test-link" href="http://mozilla.com">Click the monkey!</a>
+<a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
+<input id="test-input"><br>
+<img id="test-image" src="ctxmenu-image.png">
+<canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
+<video controls id="test-video-ok" src="video.ogg" width="100" height="100" style="background-color: green"></video>
+<video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
+<video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
+<video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
+ <source src="bogus.duh" type="video/durrrr;">
+</video>
+<iframe id="test-iframe" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-audio-in-iframe" src="audio.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe>
+<textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
+<div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
+<div id="test-contenteditable-spellcheck-false" contenteditable="true" spellcheck="false">test</div> <!-- No Check Spelling menu item -->
+<div id="test-dom-full-screen">DOM full screen FTW</div>
+<div contextmenu="myMenu">
+ <p id="test-pagemenu" hopeless="true">I've got a context menu!</p>
+ <menu id="myMenu" type="context">
+ <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem>
+ <menuitem label="Disabled item" disabled></menuitem>
+ <menuitem> Item w/ textContent</menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox" checked></menuitem>
+ </menu>
+ <menu>
+ <menuitem type="radio" label="Radio1" checked></menuitem>
+ <menuitem type="radio" label="Radio2"></menuitem>
+ <menuitem type="radio" label="Radio3"></menuitem>
+ </menu>
+ <menu>
+ <menuitem label="Item w/ icon" icon="favicon.ico"></menuitem>
+ <menuitem label="Item w/ bad icon" icon="data://www.mozilla.org/favicon.ico"></menuitem>
+ </menu>
+ <menu label="Submenu">
+ <menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox"></menuitem>
+ </menu>
+ </menu>
+ <menu hidden>
+ <menuitem label="Bogus item"></menuitem>
+ </menu>
+ <menu>
+ </menu>
+ <menuitem label="Hidden item" hidden></menuitem>
+ <menuitem></menuitem>
+ </menu>
+</div>
+<div id="test-select-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<div id="test-select-text-link">http://mozilla.com</div>
+<a id="test-image-link" href="#"><img src="ctxmenu-image.png"></a>
+<input id="test-select-input-text" type="text" value="input">
+<input id="test-select-input-text-type-password" type="password" value="password">
+<embed id="test-plugin" style="width: 200px; height: 200px;" type="application/x-test"></embed>
+<img id="test-longdesc" src="ctxmenu-image.png" longdesc="http://www.mozilla.org"></embed>
+<iframe id="test-srcdoc" width="98" height="98" srcdoc="Hello World" style="border: 1px solid black"></iframe>
+</body>
+</html>
diff --git a/browser/base/content/test/general/subtst_contextmenu_input.html b/browser/base/content/test/general/subtst_contextmenu_input.html
new file mode 100644
index 000000000..c5be977ea
--- /dev/null
+++ b/browser/base/content/test/general/subtst_contextmenu_input.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+ Browser context menu subtest.
+ <input id="input_text">
+ <input id="input_spellcheck_no_value">
+ <input id="input_spellcheck_incorrect" spellcheck="true" value="prodkjfgigrty">
+ <input id="input_spellcheck_correct" spellcheck="true" value="foo">
+ <input id="input_disabled" disabled="true">
+ <input id="input_password">
+ <input id="input_email" type="email">
+ <input id="input_tel" type="tel">
+ <input id="input_url" type="url">
+ <input id="input_number" type="number">
+ <input id="input_date" type="date">
+ <input id="input_time" type="time">
+ <input id="input_color" type="color">
+ <input id="input_range" type="range">
+ <input id="input_search" type="search">
+ <input id="input_datetime" type="datetime">
+ <input id="input_month" type="month">
+ <input id="input_week" type="week">
+ <input id="input_datetime-local" type="datetime-local">
+ <input id="input_readonly" readonly="true">
+</body>
+</html>
diff --git a/browser/base/content/test/general/subtst_contextmenu_xul.xul b/browser/base/content/test/general/subtst_contextmenu_xul.xul
new file mode 100644
index 000000000..5a2ab42e8
--- /dev/null
+++ b/browser/base/content/test/general/subtst_contextmenu_xul.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+ <label id="test-xul-text-link-label" class="text-link" value="XUL text-link label" href="https://www.mozilla.com"/>
+</page>
diff --git a/browser/base/content/test/general/svg_image.html b/browser/base/content/test/general/svg_image.html
new file mode 100644
index 000000000..7ab17c33a
--- /dev/null
+++ b/browser/base/content/test/general/svg_image.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test for page info svg images</title>
+ </head>
+ <body>
+ <svg width="20" height="20">
+ <image xlink:href="title_test.svg" width="20" height="20">
+ </svg>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/test-mixedcontent-securityerrors.html b/browser/base/content/test/general/test-mixedcontent-securityerrors.html
new file mode 100644
index 000000000..cb8cfdaaf
--- /dev/null
+++ b/browser/base/content/test/general/test-mixedcontent-securityerrors.html
@@ -0,0 +1,21 @@
+<!--
+ Bug 875456 - Log mixed content messages from the Mixed Content Blocker to the
+ Security Pane in the Web Console
+-->
+
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <title>Mixed Content test - http on https</title>
+ <script src="testscript.js"></script>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <iframe src="http://example.com"></iframe>
+ <img src="http://example.com/tests/image/test/mochitest/blue.png"></img>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/test_bug364677.html b/browser/base/content/test/general/test_bug364677.html
new file mode 100644
index 000000000..67b9729d1
--- /dev/null
+++ b/browser/base/content/test/general/test_bug364677.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364677
+-->
+<head>
+ <title>Test for Bug 364677</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364677">Mozilla Bug 364677</a>
+<p id="display"><iframe id="testFrame" src="bug364677-data.xml"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 364677 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "feedHandler",
+ "Feed served as text/xml without a channel/link should have been sniffed");
+});
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/base/content/test/general/test_bug395533.html b/browser/base/content/test/general/test_bug395533.html
new file mode 100644
index 000000000..ad6209047
--- /dev/null
+++ b/browser/base/content/test/general/test_bug395533.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=395533
+-->
+<head>
+ <title>Test for Bug 395533</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=395533">Mozilla Bug 395533</a>
+<p id="display"><iframe id="testFrame" src="bug395533-data.txt"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 395533 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Need privs because the feed seems to have an about:feeds principal or some
+ // such. It's not same-origin with us in any case.
+ is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "",
+ "Text got sniffed as a feed?");
+});
+addLoadEvent(SimpleTest.finish);
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/base/content/test/general/test_bug435035.html b/browser/base/content/test/general/test_bug435035.html
new file mode 100644
index 000000000..a6624db15
--- /dev/null
+++ b/browser/base/content/test/general/test_bug435035.html
@@ -0,0 +1 @@
+<img src="http://example.com/browser/browser/base/content/test/general/moz.png">
diff --git a/browser/base/content/test/general/test_bug462673.html b/browser/base/content/test/general/test_bug462673.html
new file mode 100644
index 000000000..d864990e4
--- /dev/null
+++ b/browser/base/content/test/general/test_bug462673.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+var w;
+function openIt() {
+ w = window.open("", "window2");
+}
+function closeIt() {
+ if (w) {
+ w.close();
+ w = null;
+ }
+}
+</script>
+</head>
+<body onload="openIt();" onunload="closeIt();">
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_bug628179.html b/browser/base/content/test/general/test_bug628179.html
new file mode 100644
index 000000000..d35e17a7c
--- /dev/null
+++ b/browser/base/content/test/general/test_bug628179.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for closing the Find bar in subdocuments</title>
+ </head>
+ <body>
+ <iframe id=iframe src="http://example.com/" width=320 height=240></iframe>
+ </body>
+</html>
+
diff --git a/browser/base/content/test/general/test_bug839103.html b/browser/base/content/test/general/test_bug839103.html
new file mode 100644
index 000000000..3639d4bda
--- /dev/null
+++ b/browser/base/content/test/general/test_bug839103.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Document for Bug 839103</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style></style>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_bug959531.html b/browser/base/content/test/general/test_bug959531.html
new file mode 100644
index 000000000..e749b198a
--- /dev/null
+++ b/browser/base/content/test/general/test_bug959531.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for content page with settings button</title>
+ </head>
+ <body>
+ <button name="settings" id="settings">Settings</button>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/test_mcb_double_redirect_image.html b/browser/base/content/test/general/test_mcb_double_redirect_image.html
new file mode 100644
index 000000000..1b54774ec
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_double_redirect_image.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 7-9 for Bug 1082837 - See file browser_mcb_redirect.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1082837
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1082837</title>
+ <script>
+ function image_loaded() {
+ document.getElementById("mctestdiv").innerHTML = "image loaded";
+ }
+ function image_blocked() {
+ document.getElementById("mctestdiv").innerHTML = "image blocked";
+ }
+ </script>
+</head>
+<body>
+ <div id="mctestdiv"></div>
+ <img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_http_sjs" onload="image_loaded()" onerror="image_blocked()" ></image>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_mcb_redirect.html b/browser/base/content/test/general/test_mcb_redirect.html
new file mode 100644
index 000000000..88af791a3
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 418354 - See file browser_mcb_redirect.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=418354
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Bug 418354</title>
+</head>
+<body>
+ <div id="mctestdiv">script blocked</div>
+ <script src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?script" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_mcb_redirect.js b/browser/base/content/test/general/test_mcb_redirect.js
new file mode 100644
index 000000000..48538c940
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect.js
@@ -0,0 +1,5 @@
+/*
+ * Once the mixed content blocker is disabled for the page, this scripts loads
+ * and updates the text inside the div container.
+ */
+document.getElementById("mctestdiv").innerHTML = "script executed";
diff --git a/browser/base/content/test/general/test_mcb_redirect.sjs b/browser/base/content/test/general/test_mcb_redirect.sjs
new file mode 100644
index 000000000..9a1811dfa
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect.sjs
@@ -0,0 +1,22 @@
+function handleRequest(request, response) {
+ var page = "<!DOCTYPE html><html><body>bug 418354 and bug 1082837</body></html>";
+
+ if (request.queryString === "script") {
+ var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.js";
+ response.setHeader("Cache-Control", "no-cache", false);
+ } else if (request.queryString === "image_http") {
+ var redirect = "http://example.com/tests/image/test/mochitest/blue.png";
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ } else if (request.queryString === "image_redirect_http_sjs") {
+ var redirect = "http://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_redirect_https";
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ } else if (request.queryString === "image_redirect_https") {
+ var redirect = "https://example.com/tests/image/test/mochitest/blue.png";
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ }
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "302", "Found");
+ response.setHeader("Location", redirect, false);
+ response.write(page);
+}
diff --git a/browser/base/content/test/general/test_mcb_redirect_image.html b/browser/base/content/test/general/test_mcb_redirect_image.html
new file mode 100644
index 000000000..c70cd8987
--- /dev/null
+++ b/browser/base/content/test/general/test_mcb_redirect_image.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3-6 for Bug 1082837 - See file browser_mcb_redirect.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1082837
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1082837</title>
+ <script>
+ function image_loaded() {
+ document.getElementById("mctestdiv").innerHTML = "image loaded";
+ }
+ function image_blocked() {
+ document.getElementById("mctestdiv").innerHTML = "image blocked";
+ }
+ </script>
+</head>
+<body>
+ <div id="mctestdiv"></div>
+ <img src="https://example.com/browser/browser/base/content/test/general/test_mcb_redirect.sjs?image_http" onload="image_loaded()" onerror="image_blocked()" ></image>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font.css b/browser/base/content/test/general/test_no_mcb_on_http_site_font.css
new file mode 100644
index 000000000..68a6954cc
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font.css
@@ -0,0 +1,10 @@
+@font-face {
+ font-family: testFont;
+ src: url(http://example.com/browser/devtools/client/fontinspector/test/browser_font.woff);
+}
+body {
+ font-family: Arial;
+}
+div {
+ font-family: testFont;
+}
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font.html b/browser/base/content/test/general/test_no_mcb_on_http_site_font.html
new file mode 100644
index 000000000..28a9cb2c0
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 2 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=909920
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 909920</title>
+ <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font.css" />
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="text/javascript">
+ function checkLoadStates() {
+ var ui = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .securityUI;
+
+ var loadedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+ is(loadedMixedActive, false, "OK: Should not load mixed active content!");
+
+ var blockedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+ is(blockedMixedActive, false, "OK: Should not block mixed active content!");
+
+ var loadedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+ is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
+
+ var blockedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+ is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
+
+ var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http font";
+ document.getElementById("testDiv").innerHTML = newValue;
+ }
+</script>
+</head>
+<body onload="checkLoadStates()">
+ <div class="testDiv" id="testDiv">
+ Testing MCB does not trigger warning/error for an http page with https css that includes http font
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css
new file mode 100644
index 000000000..f73b573b4
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css
@@ -0,0 +1 @@
+@import url(http://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font.css);
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html
new file mode 100644
index 000000000..2b3164902
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=909920
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 909920</title>
+ <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_font2.css" />
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="text/javascript">
+ function checkLoadStates() {
+ var ui = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .securityUI;
+
+ var loadedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+ is(loadedMixedActive, false, "OK: Should not load mixed active content!");
+
+ var blockedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+ is(blockedMixedActive, false, "OK: Should not block mixed active content!");
+
+ var loadedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+ is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
+
+ var blockedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+ is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
+
+ var newValue = "Verifying MCB does not trigger warning/error for an http page ";
+ newValue += "with https css that imports another http css which includes http font";
+ document.getElementById("testDiv").innerHTML = newValue;
+ }
+</script>
+</head>
+<body onload="checkLoadStates()">
+ <div class="testDiv" id="testDiv">
+ Testing MCB does not trigger warning/error for an http page with https css that imports another http css which includes http font
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_img.css b/browser/base/content/test/general/test_no_mcb_on_http_site_img.css
new file mode 100644
index 000000000..d045e21ba
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_img.css
@@ -0,0 +1,3 @@
+#testDiv {
+ background: url(http://example.com/tests/image/test/mochitest/blue.png)
+}
diff --git a/browser/base/content/test/general/test_no_mcb_on_http_site_img.html b/browser/base/content/test/general/test_no_mcb_on_http_site_img.html
new file mode 100644
index 000000000..741573260
--- /dev/null
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_img.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 909920 - See file browser_no_mcb_on_http_site.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=909920
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 909920</title>
+ <link rel="stylesheet" type="text/css" href="https://example.com/browser/browser/base/content/test/general/test_no_mcb_on_http_site_img.css" />
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<script type="text/javascript">
+ function checkLoadStates() {
+ var ui = SpecialPowers.wrap(window)
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .QueryInterface(SpecialPowers.Ci.nsIDocShell)
+ .securityUI;
+
+ var loadedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+ is(loadedMixedActive, false, "OK: Should not load mixed active content!");
+
+ var blockedMixedActive = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+ is(blockedMixedActive, false, "OK: Should not block mixed active content!");
+
+ var loadedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+ is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
+
+ var blockedMixedDisplay = ui &&
+ !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+ is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
+
+ var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http image";
+ document.getElementById("testDiv").innerHTML = newValue;
+ }
+</script>
+</head>
+<body onload="checkLoadStates()">
+ <div class="testDiv" id="testDiv">
+ Testing MCB does not trigger warning/error for an http page with https css that includes http image
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_offlineNotification.html b/browser/base/content/test/general/test_offlineNotification.html
new file mode 100644
index 000000000..4f78184b4
--- /dev/null
+++ b/browser/base/content/test/general/test_offlineNotification.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462856
+-->
+<head>
+ <title>Test offline app notification</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="offlineByDefault.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+<!-- Load the test frame twice from the same domain,
+ to make sure we get notifications for both -->
+<iframe name="testFrame" src="offlineChild.html"></iframe>
+<iframe name="testFrame2" src="offlineChild2.html"></iframe>
+<!-- Load from another domain to make sure we get a second allow/deny
+ notification -->
+<iframe name="testFrame3" src="http://example.com/tests/browser/base/content/test/general/offlineChild.html"></iframe>
+
+<iframe id="eventsTestFrame" src="offlineEvent.html"></iframe>
+
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+const Cc = SpecialPowers.Cc;
+
+var numFinished = 0;
+
+window.addEventListener("message", function(event) {
+ is(event.data, "success", "Child was successfully cached.");
+
+ if (++numFinished == 3) {
+ // Clean up after ourself
+ var pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(SpecialPowers.Ci.nsIPermissionManager);
+ var ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var uri1 = ioService.newURI(frames.testFrame.location, null, null);
+ var uri2 = ioService.newURI(frames.testFrame3.location, null, null);
+
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
+ var principal1 = ssm.createCodebasePrincipal(uri1, {});
+ var principal2 = ssm.createCodebasePrincipal(uri2, {});
+
+ pm.removeFromPrincipal(principal1, "offline-app");
+ pm.removeFromPrincipal(principal2, "offline-app");
+
+ offlineByDefault.reset();
+
+ SimpleTest.finish();
+ }
+ }, false);
+
+var count = 0;
+var expectedEvent = "";
+function eventHandler(evt) {
+ ++count;
+ is(evt.type, expectedEvent, "Wrong event!");
+}
+
+function testEventHandling() {
+ var events = [ "checking",
+ "error",
+ "noupdate",
+ "downloading",
+ "progress",
+ "updateready",
+ "cached",
+ "obsolete"];
+ var w = document.getElementById("eventsTestFrame").contentWindow;
+ var e;
+ for (var i = 0; i < events.length; ++i) {
+ count = 0;
+ expectedEvent = events[i];
+ e = w.document.createEvent("event");
+ e.initEvent(expectedEvent, true, true);
+ w.applicationCache["on" + expectedEvent] = eventHandler;
+ w.applicationCache.addEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 2, "Wrong number events!");
+ w.applicationCache["on" + expectedEvent] = null;
+ w.applicationCache.removeEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 2, "Wrong number events!");
+ }
+
+ // Test some random event.
+ count = 0;
+ expectedEvent = "foo";
+ e = w.document.createEvent("event");
+ e.initEvent(expectedEvent, true, true);
+ w.applicationCache.addEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 1, "Wrong number events!");
+ w.applicationCache.removeEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 1, "Wrong number events!");
+}
+
+function loaded() {
+ testEventHandling();
+
+ // Click the notification panel's "Allow" button. This should kick
+ // off updates, which will eventually lead to getting messages from
+ // the children.
+ var wm = SpecialPowers.Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(SpecialPowers.Ci.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ var panel = win.PopupNotifications.panel;
+ is(panel.childElementCount, 2, "2 notifications being displayed");
+ panel.firstElementChild.button.click();
+
+ // should have dismissed one of the notifications.
+ is(panel.childElementCount, 1, "1 notification now being displayed");
+ panel.firstElementChild.button.click();
+}
+
+SimpleTest.waitForFocus(loaded);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_offline_gzip.html b/browser/base/content/test/general/test_offline_gzip.html
new file mode 100644
index 000000000..a18d6604e
--- /dev/null
+++ b/browser/base/content/test/general/test_offline_gzip.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=501422
+
+When content which was transported over the network with
+Content-Type: gzip is added to the offline
+cache, it can be fetched from the cache successfully.
+-->
+<head>
+ <title>Test gzipped offline resources</title>
+ <meta charset="utf-8">
+</head>
+<body>
+<p id="display">
+<iframe name="testFrame" src="gZipOfflineChild.html"></iframe>
+
+<div id="content" style="display: none">
+</div>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_process_flags_chrome.html b/browser/base/content/test/general/test_process_flags_chrome.html
new file mode 100644
index 000000000..adcbf0340
--- /dev/null
+++ b/browser/base/content/test/general/test_process_flags_chrome.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+<p>chrome: test page</p>
+<p><a href="chrome://mochitests/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">chrome</a></p>
+<p><a href="chrome://mochitests-any/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">canremote</a></p>
+<p><a href="chrome://mochitests-content/content/browser/browser/base/content/test/general/test_process_flags_chrome.html">mustremote</a></p>
+</body>
+</html>
diff --git a/browser/base/content/test/general/test_remoteTroubleshoot.html b/browser/base/content/test/general/test_remoteTroubleshoot.html
new file mode 100644
index 000000000..7ba1c5268
--- /dev/null
+++ b/browser/base/content/test/general/test_remoteTroubleshoot.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+// This test is run multiple times, once with only strings allowed through the
+// WebChannel, and once with objects allowed. This function allows us to handle
+// both cases without too much pain.
+function makeDetails(object) {
+ if (window.location.search.indexOf("object") >= 0) {
+ return object;
+ }
+ return JSON.stringify(object)
+}
+// Add a listener for responses to our remote requests.
+window.addEventListener("WebChannelMessageToContent", function (event) {
+ if (event.detail.id == "remote-troubleshooting") {
+ // Send what we got back to the test.
+ var backEvent = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: makeDetails({
+ id: "test-remote-troubleshooting-backchannel",
+ message: {
+ message: event.detail.message,
+ },
+ }),
+ });
+ window.dispatchEvent(backEvent);
+ // and stick it in our DOM just for good measure/diagnostics.
+ document.getElementById("troubleshooting").textContent =
+ JSON.stringify(event.detail.message, null, 2);
+ }
+});
+
+// Make a request for the troubleshooting data as we load.
+window.onload = function() {
+ var event = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: makeDetails({
+ id: "remote-troubleshooting",
+ message: {
+ command: "request",
+ },
+ }),
+ });
+ window.dispatchEvent(event);
+}
+</script>
+
+<body>
+ <pre id="troubleshooting"/>
+</body>
+
+</html>
diff --git a/browser/base/content/test/general/title_test.svg b/browser/base/content/test/general/title_test.svg
new file mode 100644
index 000000000..7638fd5cc
--- /dev/null
+++ b/browser/base/content/test/general/title_test.svg
@@ -0,0 +1,59 @@
+<svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title>This is a root SVG element's title</title>
+ <foreignObject>
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <svg xmlns="http://www.w3.org/2000/svg" id="svg1">
+ <title>This is a non-root SVG element title</title>
+ </svg>
+ </body>
+ </html>
+ </foreignObject>
+ <text id="text1" x="10px" y="32px" font-size="24px">
+ This contains only &lt;title&gt;
+ <title>
+
+
+ This is a title
+
+ </title>
+ </text>
+ <text id="text2" x="10px" y="96px" font-size="24px">
+ This contains only &lt;desc&gt;
+ <desc>This is a desc</desc>
+ </text>
+ <text id="text3" x="10px" y="128px" font-size="24px" title="ignored for SVG">
+ This contains nothing.
+ </text>
+ <a id="link1" xlink:href="#">
+ This link contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ <text id="text4" x="10px" y="192px" font-size="24px">
+ </text>
+ </a>
+ <a id="link2" xlink:href="#">
+ <text x="10px" y="192px" font-size="24px">
+ This text contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ </text>
+ </a>
+ <a id="link3" xlink:href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="224px" font-size="24px">
+ This link contains &lt;title&gt; &amp; xlink:title attr.
+ <title>This is a title</title>
+ </text>
+ </a>
+ <a id="link4" xlink:href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="256px" font-size="24px">
+ This link contains xlink:title attr.
+ </text>
+ </a>
+ <text id="text5" x="10px" y="160px" font-size="24px"
+ xlink:title="This is an xlink:title attribute but it isn't on a link" >
+ This contains nothing.
+ </text>
+</svg>
diff --git a/browser/base/content/test/general/trackingPage.html b/browser/base/content/test/general/trackingPage.html
new file mode 100644
index 000000000..17f0e459e
--- /dev/null
+++ b/browser/base/content/test/general/trackingPage.html
@@ -0,0 +1,12 @@
+<!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 dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ </head>
+ <body>
+ <iframe src="http://tracking.example.com/"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/unknownContentType_file.pif b/browser/base/content/test/general/unknownContentType_file.pif
new file mode 100644
index 000000000..9353d1312
--- /dev/null
+++ b/browser/base/content/test/general/unknownContentType_file.pif
@@ -0,0 +1 @@
+Dummy content for unknownContentType_dialog_layout_data.pif
diff --git a/browser/base/content/test/general/unknownContentType_file.pif^headers^ b/browser/base/content/test/general/unknownContentType_file.pif^headers^
new file mode 100644
index 000000000..09b22facc
--- /dev/null
+++ b/browser/base/content/test/general/unknownContentType_file.pif^headers^
@@ -0,0 +1 @@
+Content-Type: application/octet-stream
diff --git a/browser/base/content/test/general/video.ogg b/browser/base/content/test/general/video.ogg
new file mode 100644
index 000000000..ac7ece351
--- /dev/null
+++ b/browser/base/content/test/general/video.ogg
Binary files differ
diff --git a/browser/base/content/test/general/web_video.html b/browser/base/content/test/general/web_video.html
new file mode 100644
index 000000000..467fb0ce1
--- /dev/null
+++ b/browser/base/content/test/general/web_video.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>Document with Web Video</title>
+ </head>
+ <body>
+ This document has some web video in it.
+ <br>
+ <video src="web_video1.ogv" id="video1"> </video>
+ </body>
+</html>
diff --git a/browser/base/content/test/general/web_video1.ogv b/browser/base/content/test/general/web_video1.ogv
new file mode 100644
index 000000000..093158432
--- /dev/null
+++ b/browser/base/content/test/general/web_video1.ogv
Binary files differ
diff --git a/browser/base/content/test/general/web_video1.ogv^headers^ b/browser/base/content/test/general/web_video1.ogv^headers^
new file mode 100644
index 000000000..4511e9255
--- /dev/null
+++ b/browser/base/content/test/general/web_video1.ogv^headers^
@@ -0,0 +1,3 @@
+Content-Disposition: filename="web-video1-expectedName.ogv"
+Content-Type: video/ogg
+
diff --git a/browser/base/content/test/general/zoom_test.html b/browser/base/content/test/general/zoom_test.html
new file mode 100644
index 000000000..bf80490ca
--- /dev/null
+++ b/browser/base/content/test/general/zoom_test.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416661
+-->
+ <head>
+ <title>Test for zoom setting</title>
+
+ </head>
+ <body>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=416661">Bug 416661</a>
+ <p>Site specific zoom settings should not apply to image documents.</p>
+ </body>
+</html>
diff --git a/browser/base/content/test/newtab/.eslintrc.js b/browser/base/content/test/newtab/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/newtab/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/newtab/browser.ini b/browser/base/content/test/newtab/browser.ini
new file mode 100644
index 000000000..2d14d208d
--- /dev/null
+++ b/browser/base/content/test/newtab/browser.ini
@@ -0,0 +1,55 @@
+[DEFAULT]
+skip-if = (os == 'linux') # Bug 1243103, Bug 1243398, etc.
+support-files =
+ head.js
+
+[browser_newtab_1188015.js]
+[browser_newtab_background_captures.js]
+[browser_newtab_block.js]
+[browser_newtab_bug721442.js]
+[browser_newtab_bug722273.js]
+skip-if = (os == "mac" && debug) # temporary skip-if due to increase in intermittent failures on Mac debug - bug 1119906
+[browser_newtab_bug723102.js]
+[browser_newtab_bug723121.js]
+[browser_newtab_bug725996.js]
+[browser_newtab_bug734043.js]
+[browser_newtab_bug735987.js]
+[browser_newtab_bug752841.js]
+[browser_newtab_bug765628.js]
+[browser_newtab_bug876313.js]
+[browser_newtab_bug991111.js]
+[browser_newtab_bug991210.js]
+[browser_newtab_bug998387.js]
+[browser_newtab_bug1145428.js]
+[browser_newtab_bug1178586.js]
+[browser_newtab_bug1194895.js]
+[browser_newtab_bug1271075.js]
+[browser_newtab_disable.js]
+[browser_newtab_drag_drop.js]
+[browser_newtab_drag_drop_ext.js]
+# temporary until determine why more intermittent on VM
+subsuite = clipboard
+[browser_newtab_drop_preview.js]
+[browser_newtab_enhanced.js]
+[browser_newtab_focus.js]
+[browser_newtab_perwindow_private_browsing.js]
+[browser_newtab_reportLinkAction.js]
+[browser_newtab_reflow_load.js]
+support-files =
+ content-reflows.js
+[browser_newtab_search.js]
+support-files =
+ searchEngineNoLogo.xml
+ searchEngineFavicon.xml
+ searchEngine1xLogo.xml
+ searchEngine2xLogo.xml
+ searchEngine1x2xLogo.xml
+ ../general/searchSuggestionEngine.xml
+ ../general/searchSuggestionEngine.sjs
+[browser_newtab_sponsored_icon_click.js]
+skip-if = true # Bug 1314619
+[browser_newtab_undo.js]
+# temporary until determine why more intermittent on VM
+subsuite = clipboard
+[browser_newtab_unpin.js]
+[browser_newtab_update.js]
diff --git a/browser/base/content/test/newtab/browser_newtab_1188015.js b/browser/base/content/test/newtab/browser_newtab_1188015.js
new file mode 100644
index 000000000..f19aae1b9
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_1188015.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "directory": [{
+ url: "http://example1.com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title1",
+ type: "affiliate",
+ titleBgColor: "green"
+ }]
+});
+
+add_task(function* () {
+ yield pushPrefs(["browser.newtab.preload", false]);
+
+ // Make the page have a directory link
+ yield setLinks([]);
+ yield* addNewTabPageTab();
+
+ let color = yield performOnCell(0, cell => {
+ return cell.node.querySelector(".newtab-title").style.backgroundColor;
+ });
+
+ is(color, "green", "title bg color is green");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_background_captures.js b/browser/base/content/test/newtab/browser_newtab_background_captures.js
new file mode 100644
index 000000000..5e838196e
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_background_captures.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verifies that hidden, pre-loaded newtabs don't allow background captures, and
+ * when unhidden, do allow background captures.
+ */
+
+const CAPTURE_PREF = "browser.pagethumbnails.capturing_disabled";
+
+add_task(function* () {
+ let imports = {};
+ Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
+
+ // Disable captures.
+ yield pushPrefs([CAPTURE_PREF, false]);
+
+ // Make sure the thumbnail doesn't exist yet.
+ let url = "http://example.com/";
+ let path = imports.PageThumbsStorage.getFilePathForURL(url);
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(path);
+ try {
+ file.remove(false);
+ }
+ catch (err) {}
+
+ // Add a top site.
+ yield setLinks("-1");
+
+ // We need a handle to a hidden, pre-loaded newtab so we can verify that it
+ // doesn't allow background captures. Ensure we have a preloaded browser.
+ gBrowser._createPreloadBrowser();
+
+ // Wait for the preloaded browser to load.
+ if (gBrowser._preloadedBrowser.contentDocument.readyState != "complete") {
+ yield BrowserTestUtils.waitForEvent(gBrowser._preloadedBrowser, "load", true);
+ }
+
+ // We're now ready to use the preloaded browser.
+ BrowserOpenTab();
+ let tab = gBrowser.selectedTab;
+
+ let thumbnailCreatedPromise = new Promise(resolve => {
+ // Showing the preloaded tab should trigger thumbnail capture.
+ Services.obs.addObserver(function onCreate(subj, topic, data) {
+ if (data != url)
+ return;
+ Services.obs.removeObserver(onCreate, "page-thumbnail:create");
+ ok(true, "thumbnail created after preloaded tab was shown");
+
+ resolve();
+ }, "page-thumbnail:create", false);
+ });
+
+ // Enable captures.
+ yield pushPrefs([CAPTURE_PREF, false]);
+
+ yield thumbnailCreatedPromise;
+
+ // Test finished!
+ gBrowser.removeTab(tab);
+ file.remove(false);
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_block.js b/browser/base/content/test/newtab/browser_newtab_block.js
new file mode 100644
index 000000000..70656462a
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+/*
+ * These tests make sure that blocking/removing sites from the grid works
+ * as expected. Pinned tabs should not be moved. Gaps will be re-filled
+ * if more sites are available.
+ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "suggested": [{
+ url: "http://suggested.com/",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ title: "title",
+ type: "affiliate",
+ adgroup_name: "test",
+ frecent_sites: ["example0.com"]
+ }]
+});
+
+add_task(function* () {
+ let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+ DirectoryLinksProvider.getFrecentSitesName = () => "";
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = (site) => false;
+
+ // we remove sites and expect the gaps to be filled as long as there still
+ // are some sites available
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield blockCell(4);
+ yield* checkGrid("0,1,2,3,5,6,7,8,9");
+
+ yield blockCell(4);
+ yield* checkGrid("0,1,2,3,6,7,8,9,");
+
+ yield blockCell(4);
+ yield* checkGrid("0,1,2,3,7,8,9,,");
+
+ // we removed a pinned site
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ yield blockCell(1);
+ yield* checkGrid("0,2,3,4,5,6,7,8,");
+
+ // we remove the last site on the grid (which is pinned) and expect the gap
+ // to be re-filled and the new site to be unpinned
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield blockCell(8);
+ yield* checkGrid("0,1,2,3,4,5,6,7,9");
+
+ // we remove the first site on the grid with the last one pinned. all cells
+ // but the last one should shift to the left and a new site fades in
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield blockCell(0);
+ yield* checkGrid("1,2,3,4,5,6,7,9,8p");
+
+ // Test that blocking the targeted site also removes its associated suggested tile
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced on
+ yield* addNewTabPageTab();
+
+ yield* checkGrid("http://suggested.com/,0,1,2,3,4,5,6,7,8,9");
+
+ yield blockCell(1);
+ yield* addNewTabPageTab();
+ yield* checkGrid("1,2,3,4,5,6,7,8,9");
+ DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1145428.js b/browser/base/content/test/newtab/browser_newtab_bug1145428.js
new file mode 100644
index 000000000..72fe70212
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1145428.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that pinning suggested tile results in:
+ * - making suggested tile a history tile and replacing enhancedImageURI with imageURI
+ * - upond end of campaign, replaces landing url with baseDomain and switches
+ * background image to thumbnail
+ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "suggested": [{
+ url: "http://example.com/landing/page.html",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title",
+ type: "affiliate",
+ adgroup_name: "example",
+ frecent_sites: ["example0.com"],
+ }]
+});
+
+add_task(function* () {
+ let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+ DirectoryLinksProvider.getFrecentSitesName = () => "";
+
+ function getData(cellNum) {
+ return performOnCell(cellNum, cell => {
+ if (!cell.site)
+ return null;
+ let siteNode = cell.site.node;
+ return {
+ type: siteNode.getAttribute("type"),
+ thumbnail: siteNode.querySelector(".newtab-thumbnail.thumbnail").style.backgroundImage,
+ enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+ title: siteNode.querySelector(".newtab-title").textContent,
+ suggested: siteNode.getAttribute("suggested"),
+ url: siteNode.querySelector(".newtab-link").getAttribute("href"),
+ };
+ });
+ }
+
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ // load another newtab since the first may not get suggested tile
+ yield* addNewTabPageTab();
+ yield* checkGrid("http://example.com/landing/page.html,0,1,2,3,4,5,6,7,8,9");
+ // evaluate suggested tile
+ let tileData = yield getData(0);
+ is(tileData.type, "affiliate", "unpinned type");
+ is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
+ is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
+ is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists");
+ is(tileData.url, "http://example.com/landing/page.html", "unpinned landing page");
+
+ // suggested tile should not be pinned
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), false, "suggested tile is not pinned");
+
+ // pin suggested tile
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+
+ // tile should be pinned and turned into history tile
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/landing/page.html"}), true, "suggested tile is pinned");
+ tileData = yield getData(0);
+ is(tileData.type, "history", "pinned type");
+ is(tileData.suggested, null, "no suggested attribute");
+ is(tileData.url, "http://example.com/landing/page.html", "original landing page");
+
+ // set pinned tile endTime into past and reload the page
+ NewTabUtils.pinnedLinks._links[0].endTime = Date.now() - 1000;
+ yield* addNewTabPageTab();
+
+ // check that url is reset to base domain and thumbnail points to moz-page-thumb service
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/"}), true, "baseDomain url is pinned");
+ tileData = yield getData(0);
+ is(tileData.type, "history", "type is history");
+ is(tileData.title, "example.com", "title changed to baseDomain");
+ is(tileData.thumbnail.indexOf("moz-page-thumb") != -1, true, "thumbnail contains moz-page-thumb");
+ is(tileData.enhanced, "", "no enhanced image");
+ is(tileData.url, "http://example.com/", "url points to baseDomian");
+
+ DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1178586.js b/browser/base/content/test/newtab/browser_newtab_bug1178586.js
new file mode 100644
index 000000000..84d5cb577
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1178586.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that pinned suggested tile turns into history tile
+ * and remains a history tile after a user clicks on it
+ */
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "suggested": [{
+ url: "http://example.com/hardlanding/page.html",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title",
+ type: "affiliate",
+ adgroup_name: "example",
+ frecent_sites: ["example0.com"],
+ }]
+});
+
+add_task(function* () {
+ let origGetFrecentSitesName = DirectoryLinksProvider.getFrecentSitesName;
+ DirectoryLinksProvider.getFrecentSitesName = () => "";
+
+ function getData(cellNum) {
+ return performOnCell(cellNum, cell => {
+ if (!cell.site)
+ return null;
+ let siteNode = cell.site.node;
+ return {
+ type: siteNode.getAttribute("type"),
+ thumbnail: siteNode.querySelector(".newtab-thumbnail.thumbnail").style.backgroundImage,
+ enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+ title: siteNode.querySelector(".newtab-title").textContent,
+ suggested: siteNode.getAttribute("suggested"),
+ url: siteNode.querySelector(".newtab-link").getAttribute("href"),
+ };
+ });
+ }
+
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ // load another newtab since the first may not get suggested tile
+ yield* addNewTabPageTab();
+ yield* checkGrid("http://example.com/hardlanding/page.html,0,1,2,3,4,5,6,7,8,9");
+ // evaluate suggested tile
+ let tileData = yield getData(0);
+ is(tileData.type, "affiliate", "unpinned type");
+ is(tileData.thumbnail, "url(\"data:image/png;base64,helloWORLD3\")", "unpinned thumbnail");
+ is(tileData.enhanced, "url(\"data:image/png;base64,helloWORLD2\")", "unpinned enhanced");
+ is(tileData.suggested, "true", "has suggested set", "unpinned suggested exists");
+ is(tileData.url, "http://example.com/hardlanding/page.html", "unpinned landing page");
+
+ // suggested tile should not be pinned
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), false, "suggested tile is not pinned");
+
+ // pin suggested tile
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site > .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+
+ // tile should be pinned and turned into history tile
+ is(NewTabUtils.pinnedLinks.isPinned({url: "http://example.com/hardlanding/page.html"}), true, "suggested tile is pinned");
+ tileData = yield getData(0);
+ is(tileData.type, "history", "pinned type");
+ is(tileData.suggested, null, "no suggested attribute");
+ is(tileData.url, "http://example.com/hardlanding/page.html", "original landing page");
+
+ // click the pinned tile
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
+ // add new page twice to avoid using cached version
+ yield* addNewTabPageTab();
+ yield* addNewTabPageTab();
+
+ // check that type and suggested did not change
+ tileData = yield getData(0);
+ is(tileData.type, "history", "pinned type");
+ is(tileData.suggested, null, "no suggested attribute");
+
+ DirectoryLinksProvider.getFrecentSitesName = origGetFrecentSitesName;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1194895.js b/browser/base/content/test/newtab/browser_newtab_bug1194895.js
new file mode 100644
index 000000000..c08b23185
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1194895.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PRELOAD_PREF = "browser.newtab.preload";
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
+
+function populateDirectoryTiles() {
+ let directoryTiles = [];
+ let i = 0;
+ while (i++ < 14) {
+ directoryTiles.push({
+ directoryId: i,
+ url: "http://example" + i + ".com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD",
+ title: "dirtitle" + i,
+ type: "affiliate"
+ });
+ }
+ return directoryTiles;
+}
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "directory": populateDirectoryTiles()
+});
+
+
+add_task(function* () {
+ requestLongerTimeout(4);
+ let origEnhanced = NewTabUtils.allPages.enhanced;
+ let origCompareLinks = NewTabUtils.links.compareLinks;
+ registerCleanupFunction(() => {
+ NewTabUtils.allPages.enhanced = origEnhanced;
+ NewTabUtils.links.compareLinks = origCompareLinks;
+ });
+
+ // turn off preload to ensure grid updates on every setLinks
+ yield pushPrefs([PRELOAD_PREF, false]);
+ // set newtab to have three columns only
+ yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
+ yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
+
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+
+ // Testing history tiles
+
+ // two rows of tiles should always fit on any screen
+ yield setLinks("0,1,2,3,4,5");
+ yield* addNewTabPageTab();
+
+ // should do not see scrollbar since tiles fit into visible space
+ yield* checkGrid("0,1,2,3,4,5");
+ let scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar");
+
+ // add enough tiles to cause extra two rows and observe scrollbar
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8,9");
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "document has scrollbar");
+
+ // pin the last tile to make it stay at the bottom of the newtab
+ yield pinCell(9);
+ // block first 6 tiles, which should not remove the scroll bar
+ // since the last tile is pinned in the nineth position
+ for (let i = 0; i < 6; i++) {
+ yield blockCell(0);
+ }
+ yield* addNewTabPageTab();
+ yield* checkGrid("6,7,8,,,,,,,9p");
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "document has scrollbar when tile is pinned to the last row");
+
+ // unpin the site: this will move tile up and make scrollbar disappear
+ yield unpinCell(9);
+ yield* addNewTabPageTab();
+ yield* checkGrid("6,7,8,9");
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar when bottom row tile is unpinned");
+
+ // reset everything to clean slate
+ NewTabUtils.restore();
+
+ // Testing directory tiles
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced on
+
+ // setup page with no history tiles to test directory only display
+ yield setLinks([]);
+ yield* addNewTabPageTab();
+ ok(!scrolling, "no scrollbar for directory tiles");
+
+ // introduce one history tile - it should occupy the last
+ // available slot at the bottom of newtab and cause scrollbar
+ yield setLinks("41");
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "adding low frecency history site causes scrollbar");
+
+ // set PREF_NEWTAB_ROWS to 4, that should clip off the history tile
+ // and remove scroll bar
+ yield pushPrefs([PREF_NEWTAB_ROWS, 4]);
+ yield* addNewTabPageTab();
+
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar if history tiles falls past max rows");
+
+ // restore max rows and watch scrollbar re-appear
+ yield pushPrefs([PREF_NEWTAB_ROWS, 5]);
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "scrollbar is back when max rows allow for bottom history tile");
+
+ // block that history tile, and watch scrollbar disappear
+ yield blockCell(14);
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar after bottom history tiles is blocked");
+
+ // Test well-populated user history - newtab has highly-frecent history sites
+ // redefine compareLinks to always choose history tiles first
+ NewTabUtils.links.compareLinks = function (aLink1, aLink2) {
+ if (aLink1.type == aLink2.type) {
+ return aLink2.frecency - aLink1.frecency ||
+ aLink2.lastVisitDate - aLink1.lastVisitDate;
+ }
+ if (aLink2.type == "history") {
+ return 1;
+ }
+ return -1;
+ };
+
+ // add a row of history tiles, directory tiles will be clipped off, hence no scrollbar
+ yield setLinks("31,32,33");
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(!scrolling, "no scrollbar when directory tiles follow history tiles");
+
+ // fill first four rows with history tiles and observer scrollbar
+ yield setLinks("30,31,32,33,34,35,36,37,38,39");
+ yield* addNewTabPageTab();
+ scrolling = yield hasScrollbar();
+ ok(scrolling, "scrollbar appears when history tiles need extra row");
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug1271075.js b/browser/base/content/test/newtab/browser_newtab_bug1271075.js
new file mode 100644
index 000000000..723b48fc6
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug1271075.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add a few tabs.
+ let tabs = [];
+ function addTab(aURL, aReferrer) {
+ let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer});
+ tabs.push(tab);
+ return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ }
+
+ yield addTab("http://mochi.test:8888/#0");
+ yield addTab("http://mochi.test:8888/#1");
+ yield addTab("http://mochi.test:8888/#2");
+ yield addTab("http://mochi.test:8888/#3");
+
+ // Create a new tab page with a "www.example.com" tile and move it to the 2nd tab position.
+ yield setLinks("-1");
+ yield* addNewTabPageTab();
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+
+ // Send a middle-click and confirm that the clicked site opens immediately next to the new tab page.
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell",
+ {button: 1}, gBrowser.selectedBrowser);
+
+ yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserAtIndex(2));
+ is(gBrowser.getBrowserAtIndex(2).currentURI.spec, "http://example.com/",
+ "Middle click opens site in a new tab immediately to the right.");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug721442.js b/browser/base/content/test/newtab/browser_newtab_bug721442.js
new file mode 100644
index 000000000..99bd8d930
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug721442.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks([
+ {url: "http://example7.com/", title: ""},
+ {url: "http://example8.com/", title: "title"},
+ {url: "http://example9.com/", title: "http://example9.com/"}
+ ]);
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("7p,8p,9p,0,1,2,3,4,5");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ function checkTooltip(aIndex, aExpected, aMessage) {
+ let cell = content.gGrid.cells[aIndex];
+
+ let link = cell.node.querySelector(".newtab-link");
+ Assert.equal(link.getAttribute("title"), aExpected, aMessage);
+ }
+
+ checkTooltip(0, "http://example7.com/", "1st tooltip is correct");
+ checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct");
+ checkTooltip(2, "http://example9.com/", "3rd tooltip is correct");
+ });
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug722273.js b/browser/base/content/test/newtab/browser_newtab_bug722273.js
new file mode 100644
index 000000000..5cbfcd3ff
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const NOW = Date.now() * 1000;
+const URL = "http://fake-site.com/";
+
+var tmp = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+var {Sanitizer} = tmp;
+
+add_task(function* () {
+ yield promiseSanitizeHistory();
+ yield promiseAddFakeVisits();
+ yield* addNewTabPageTab();
+
+ let cellUrl = yield performOnCell(0, cell => { return cell.site.url; });
+ is(cellUrl, URL, "first site is our fake site");
+
+ let updatedPromise = whenPagesUpdated();
+ yield promiseSanitizeHistory();
+ yield updatedPromise;
+
+ let isGone = yield performOnCell(0, cell => { return cell.site == null; });
+ ok(isGone, "fake site is gone");
+});
+
+function promiseAddFakeVisits() {
+ let visits = [];
+ for (let i = 59; i > 0; i--) {
+ visits.push({
+ visitDate: NOW - i * 60 * 1000000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ });
+ }
+ let place = {
+ uri: makeURI(URL),
+ title: "fake site",
+ visits: visits
+ };
+ return new Promise((resolve, reject) => {
+ PlacesUtils.asyncHistory.updatePlaces(place, {
+ handleError: () => reject(new Error("Couldn't add visit")),
+ handleResult: function () {},
+ handleCompletion: function () {
+ NewTabUtils.links.populateCache(function () {
+ NewTabUtils.allPages.update();
+ resolve();
+ }, true);
+ }
+ });
+ });
+}
+
+function promiseSanitizeHistory() {
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let prefs = gPrefService.getBranch(s.prefDomain);
+ prefs.setBoolPref("history", true);
+ prefs.setBoolPref("downloads", false);
+ prefs.setBoolPref("cache", false);
+ prefs.setBoolPref("cookies", false);
+ prefs.setBoolPref("formdata", false);
+ prefs.setBoolPref("offlineApps", false);
+ prefs.setBoolPref("passwords", false);
+ prefs.setBoolPref("sessions", false);
+ prefs.setBoolPref("siteSettings", false);
+
+ return s.sanitize();
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug723102.js b/browser/base/content/test/newtab/browser_newtab_bug723102.js
new file mode 100644
index 000000000..02282dc97
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug723102.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // create a new tab page and hide it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ let firstTab = gBrowser.selectedTab;
+
+ yield* addNewTabPageTab();
+ yield BrowserTestUtils.removeTab(firstTab);
+
+ ok(NewTabUtils.allPages.enabled, "page is enabled");
+ NewTabUtils.allPages.enabled = false;
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ Assert.ok(content.gGrid.node.hasAttribute("page-disabled"), "page is disabled");
+ });
+
+ NewTabUtils.allPages.enabled = true;
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug723121.js b/browser/base/content/test/newtab/browser_newtab_bug723121.js
new file mode 100644
index 000000000..82f45ebd5
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+ let grid = content.gGrid;
+ let cell = grid.cells[0];
+ let site = cell.site.node;
+ let link = site.querySelector(".newtab-link");
+
+ function checkGridLocked(aLocked, aMessage) {
+ Assert.equal(grid.node.hasAttribute("locked"), aLocked, aMessage);
+ }
+
+ function sendDragEvent(aEventType, aTarget) {
+ let dataTransfer = new content.DataTransfer(aEventType, false);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent(aEventType, true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+ aTarget.dispatchEvent(event);
+ }
+
+ checkGridLocked(false, "grid is unlocked");
+
+ sendDragEvent("dragstart", link);
+ checkGridLocked(true, "grid is now locked");
+
+ sendDragEvent("dragend", link);
+ checkGridLocked(false, "grid isn't locked anymore");
+
+ sendDragEvent("dragstart", cell.node);
+ checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+
+ sendDragEvent("dragstart", site);
+ checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+ });
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug725996.js b/browser/base/content/test/newtab/browser_newtab_bug725996.js
new file mode 100644
index 000000000..e0de809c8
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug725996.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ function doDrop(data) {
+ return ContentTask.spawn(gBrowser.selectedBrowser, { data: data }, function*(args) {
+ let dataTransfer = new content.DataTransfer("dragstart", false);
+ dataTransfer.mozSetDataAt("text/x-moz-url", args.data, 0);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[0].node;
+ target.dispatchEvent(event);
+ });
+ }
+
+ yield doDrop("http://example99.com/\nblank");
+ is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
+ "first cell is pinned and contains the dropped site");
+
+ yield whenPagesUpdated();
+ yield* checkGrid("99p,0,1,2,3,4,5,6,7");
+
+ yield doDrop("");
+ is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
+ "first cell is still pinned with the site we dropped before");
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug734043.js b/browser/base/content/test/newtab/browser_newtab_bug734043.js
new file mode 100644
index 000000000..02f765274
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.addEventListener("error", function () {
+ sendAsyncMessage("test:newtab-error", {});
+ });
+ });
+
+ let receivedError = false;
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("test:newtab-error", function onResponse(message) {
+ mm.removeMessageListener("test:newtab-error", onResponse);
+ ok(false, "Error event happened");
+ receivedError = true;
+ });
+
+ let pagesUpdatedPromise = whenPagesUpdated();
+
+ for (let i = 0; i < 3; i++) {
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block", {}, gBrowser.selectedBrowser);
+ }
+
+ yield pagesUpdatedPromise;
+
+ ok(!receivedError, "we got here without any errors");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug735987.js b/browser/base/content/test/newtab/browser_newtab_bug735987.js
new file mode 100644
index 000000000..2ae541c70
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug735987.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield* simulateExternalDrop(1);
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield blockCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield* simulateExternalDrop(1);
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ // Simulate a restart and force the next about:newtab
+ // instance to read its data from the storage again.
+ NewTabUtils.blockedLinks.resetCache();
+
+ // Update all open pages, e.g. preloaded ones.
+ NewTabUtils.allPages.update();
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield blockCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug752841.js b/browser/base/content/test/newtab/browser_newtab_bug752841.js
new file mode 100644
index 000000000..e3faad13f
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug752841.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+
+function getCellsCount()
+{
+ return ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ return content.gGrid.cells.length;
+ });
+}
+
+add_task(function* () {
+ let testValues = [
+ {row: 0, column: 0},
+ {row: -1, column: -1},
+ {row: -1, column: 0},
+ {row: 0, column: -1},
+ {row: 2, column: 4},
+ {row: 2, column: 5},
+ ];
+
+ // Expected length of grid
+ let expectedValues = [1, 1, 1, 1, 8, 10];
+
+ // Values before setting new pref values (15 is the default value -> 5 x 3)
+ let previousValues = [15, 1, 1, 1, 1, 8];
+
+ yield* addNewTabPageTab();
+ let existingTab = gBrowser.selectedTab;
+
+ for (let i = 0; i < expectedValues.length; i++) {
+ let existingTabGridLength = yield getCellsCount();
+ is(existingTabGridLength, previousValues[i],
+ "Grid length of existing page before update is correctly.");
+
+ yield pushPrefs([PREF_NEWTAB_ROWS, testValues[i].row]);
+ yield pushPrefs([PREF_NEWTAB_COLUMNS, testValues[i].column]);
+
+ existingTabGridLength = yield getCellsCount();
+ is(existingTabGridLength, expectedValues[i],
+ "Existing page grid is updated correctly.");
+
+ yield* addNewTabPageTab();
+ let newTab = gBrowser.selectedTab;
+ let newTabGridLength = yield getCellsCount();
+ is(newTabGridLength, expectedValues[i],
+ "New page grid is updated correctly.");
+
+ yield BrowserTestUtils.removeTab(newTab);
+ }
+
+ gBrowser.removeTab(existingTab);
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_bug765628.js b/browser/base/content/test/newtab/browser_newtab_bug765628.js
new file mode 100644
index 000000000..25afd32a9
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug765628.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+ const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
+ const GOOD_DRAG_DATA = "http://example99.com/\nsite 99";
+
+ function sendDropEvent(aCellIndex, aDragData) {
+ let dataTransfer = new content.DataTransfer("dragstart", false);
+ dataTransfer.mozSetDataAt("text/x-moz-url", aDragData, 0);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[aCellIndex].node;
+ target.dispatchEvent(event);
+ }
+
+ sendDropEvent(0, BAD_DRAG_DATA);
+ sendDropEvent(1, GOOD_DRAG_DATA);
+ });
+
+ yield whenPagesUpdated();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug876313.js b/browser/base/content/test/newtab/browser_newtab_bug876313.js
new file mode 100644
index 000000000..1c0b0f501
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug876313.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * This test makes sure that the changes made by unpinning
+ * a site are actually written to NewTabUtils' storage.
+ */
+add_task(function* () {
+ // Second cell is pinned with page #99.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",99");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ // Unpin the second cell's site.
+ yield unpinCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // Clear the pinned cache to force NewTabUtils to read the pref again.
+ NewTabUtils.pinnedLinks.resetCache();
+ NewTabUtils.allPages.update();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug991111.js b/browser/base/content/test/newtab/browser_newtab_bug991111.js
new file mode 100644
index 000000000..37aa8213b
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug991111.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // set max rows to 1, to avoid scroll events by clicking middle button
+ yield pushPrefs(["browser.newtabpage.rows", 1]);
+ yield setLinks("-1");
+ yield* addNewTabPageTab();
+ // we need a second newtab to honor max rows
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
+ let {site} = content.wrappedJSObject.gGrid.cells[args.index];
+
+ let origOnClick = site.onClick;
+ site.onClick = e => {
+ origOnClick.call(site, e);
+ sendAsyncMessage("test:clicked-on-cell", {});
+ };
+ });
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ let messagePromise = new Promise(resolve => {
+ mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
+ mm.removeMessageListener("test:clicked-on-cell", onResponse);
+ resolve();
+ });
+ });
+
+ // Send a middle-click and make sure it happened
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell",
+ {button: 1}, gBrowser.selectedBrowser);
+ yield messagePromise;
+ ok(true, "middle click triggered click listener");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug991210.js b/browser/base/content/test/newtab/browser_newtab_bug991210.js
new file mode 100644
index 000000000..367c49f5c
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug991210.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // turn off preload to ensure that a newtab page loads
+ yield pushPrefs(["browser.newtab.preload", false]);
+
+ // add a test provider that waits for load
+ let afterLoadProvider = {
+ getLinks: function(callback) {
+ this.callback = callback;
+ },
+ addObserver: function() {},
+ };
+ NewTabUtils.links.addProvider(afterLoadProvider);
+
+ // wait until about:newtab loads before calling provider callback
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab");
+
+ afterLoadProvider.callback([]);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let {_cellHeight, _cellWidth, node} = content.gGrid;
+ Assert.notEqual(_cellHeight, null, "grid has a computed cell height");
+ Assert.notEqual(_cellWidth, null, "grid has a computed cell width");
+ let {height, maxHeight, maxWidth} = node.style;
+ Assert.notEqual(height, "", "grid has a computed grid height");
+ Assert.notEqual(maxHeight, "", "grid has a computed grid max-height");
+ Assert.notEqual(maxWidth, "", "grid has a computed grid max-width");
+ });
+
+ // restore original state
+ NewTabUtils.links.removeProvider(afterLoadProvider);
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_bug998387.js b/browser/base/content/test/newtab/browser_newtab_bug998387.js
new file mode 100644
index 000000000..30424c2e5
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug998387.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ // set max rows to 1, to avoid scroll events by clicking middle button
+ yield pushPrefs(["browser.newtabpage.rows", 1]);
+ yield setLinks("0");
+ yield* addNewTabPageTab();
+ // we need a second newtab to honor max rows
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {index: 0}, function* (args) {
+ let {site} = content.wrappedJSObject.gGrid.cells[args.index];
+
+ let origOnClick = site.onClick;
+ site.onClick = e => {
+ origOnClick.call(site, e);
+ sendAsyncMessage("test:clicked-on-cell", {});
+ };
+ });
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ let messagePromise = new Promise(resolve => {
+ mm.addMessageListener("test:clicked-on-cell", function onResponse(message) {
+ mm.removeMessageListener("test:clicked-on-cell", onResponse);
+ resolve();
+ });
+ });
+
+ // Send a middle-click and make sure it happened
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-control-block",
+ {button: 1}, gBrowser.selectedBrowser);
+
+ yield messagePromise;
+ ok(true, "middle click triggered click listener");
+
+ // Make sure the cell didn't actually get blocked
+ yield* checkGrid("0");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_disable.js b/browser/base/content/test/newtab/browser_newtab_disable.js
new file mode 100644
index 000000000..58b9a18af
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_disable.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that the 'New Tab Page' feature can be disabled if the
+ * decides not to use it.
+ */
+add_task(function* () {
+ // create a new tab page and hide it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ let firstTab = yield* addNewTabPageTab();
+
+ function isGridDisabled(browser = gBrowser.selectedBrowser)
+ {
+ return ContentTask.spawn(browser, {}, function*() {
+ return content.gGrid.node.hasAttribute("page-disabled");
+ });
+ }
+
+ let isDisabled = yield isGridDisabled();
+ ok(!isDisabled, "page is not disabled");
+
+ NewTabUtils.allPages.enabled = false;
+
+ isDisabled = yield isGridDisabled();
+ ok(isDisabled, "page is disabled");
+
+ // create a second new tab page and make sure it's disabled. enable it
+ // again and check if the former page gets enabled as well.
+ yield* addNewTabPageTab();
+ isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
+ ok(isDisabled, "page is disabled");
+
+ // check that no sites have been rendered
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+ Assert.equal(content.document.querySelectorAll(".site").length, 0,
+ "no sites have been rendered");
+ });
+
+ NewTabUtils.allPages.enabled = true;
+
+ isDisabled = yield isGridDisabled();
+ ok(!isDisabled, "page is not disabled");
+
+ isDisabled = yield isGridDisabled(firstTab.linkedBrowser);
+ ok(!isDisabled, "old page is not disabled");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop.js b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
new file mode 100644
index 000000000..da9d89de7
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that dragging and dropping sites works as expected.
+ * Sites contained in the grid need to shift around to indicate the result
+ * of the drag-and-drop operation. If the grid is full and we're dragging
+ * a new site into it another one gets pushed out.
+ */
+add_task(function* () {
+ requestLongerTimeout(2);
+ yield* addNewTabPageTab();
+
+ // test a simple drag-and-drop scenario
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield doDragEvent(0, 1);
+ yield* checkGrid("1,0p,2,3,4,5,6,7,8");
+
+ // drag a cell to its current cell and make sure it's not pinned afterwards
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield doDragEvent(0, 0);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // ensure that pinned pages aren't moved if that's not necessary
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1,2");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1p,2p,3,4,5,6,7,8");
+
+ yield doDragEvent(0, 3);
+ yield* checkGrid("3,1p,2p,0p,4,5,6,7,8");
+
+ // pinned sites should always be moved around as blocks. if a pinned site is
+ // moved around, neighboring pinned are affected as well
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2,3,4,5,6,7,8");
+
+ yield doDragEvent(2, 0);
+ yield* checkGrid("2p,0p,1p,3,4,5,6,7,8");
+
+ // pinned sites should not be pushed out of the grid (unless there are only
+ // pinned ones left on the grid)
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ yield doDragEvent(2, 5);
+ yield* checkGrid("0,1,3,4,5,2p,6,7p,8p");
+
+ // make sure that pinned sites are re-positioned correctly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,5");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+ yield doDragEvent(0, 4);
+ yield* checkGrid("3,1p,2p,4,0p,5p,6,7,8");
+});
+
+function doDragEvent(sourceIndex, dropIndex) {
+ return ContentTask.spawn(gBrowser.selectedBrowser,
+ { sourceIndex: sourceIndex, dropIndex: dropIndex }, function*(args) {
+ let dataTransfer = new content.DataTransfer("dragstart", false);
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("dragstart", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[args.sourceIndex].site.node;
+ target.dispatchEvent(event);
+
+ event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ target = content.gGrid.cells[args.dropIndex].node;
+ target.dispatchEvent(event);
+ });
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
new file mode 100644
index 000000000..4e7b062cb
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+
+/*
+ * These tests make sure that dragging and dropping sites works as expected.
+ * Sites contained in the grid need to shift around to indicate the result
+ * of the drag-and-drop operation. If the grid is full and we're dragging
+ * a new site into it another one gets pushed out.
+ * This is a continuation of browser_newtab_drag_drop.js
+ * to decrease test run time, focusing on external sites.
+ */
+ add_task(function* () {
+ yield* addNewTabPageTab();
+
+ // drag a new site onto the very first cell
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ yield* simulateExternalDrop(0);
+ yield* checkGrid("99p,0,1,2,3,4,5,7p,8p");
+
+ // drag a new site onto the grid and make sure that pinned cells don't get
+ // pushed out
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ // force the grid to be small enough that a pinned cell could be pushed out
+ yield pushPrefs([PREF_NEWTAB_COLUMNS, 3]);
+ yield* simulateExternalDrop(5);
+ yield* checkGrid("0,1,2,3,4,99p,5,7p,8p");
+
+ // drag a new site beneath a pinned cell and make sure the pinned cell is
+ // not moved
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield* simulateExternalDrop(5);
+ yield* checkGrid("0,1,2,3,4,99p,5,6,8p");
+
+ // drag a new site onto a block of pinned sites and make sure they're shifted
+ // around accordingly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,,,,");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2p");
+
+ yield* simulateExternalDrop(1);
+ yield* checkGrid("0p,99p,1p,2p,3,4,5,6,7");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_drop_preview.js b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
new file mode 100644
index 000000000..f9e37f629
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests ensure that the drop preview correctly arranges sites when
+ * dragging them around.
+ */
+add_task(function* () {
+ yield* addNewTabPageTab();
+
+ // the first three sites are pinned - make sure they're re-arranged correctly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,5");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+ let foundSites = yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
+ let cells = content.gGrid.cells;
+ content.gDrag._draggedSite = cells[0].site;
+ let sites = content.gDropPreview.rearrange(cells[4]);
+ content.gDrag._draggedSite = null;
+
+ sites = sites.slice(0, 9);
+ return sites.map(function (aSite) {
+ if (!aSite)
+ return "";
+
+ let pinned = aSite.isPinned();
+ if (pinned != aSite.node.hasAttribute("pinned")) {
+ Assert.ok(false, "invalid state (site.isPinned() != site[pinned])");
+ }
+
+ return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
+ });
+ });
+
+ let expectedSites = "3,1p,2p,4,0p,5p,6,7,8"
+ is(foundSites, expectedSites, "grid status = " + expectedSites);
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_enhanced.js b/browser/base/content/test/newtab/browser_newtab_enhanced.js
new file mode 100644
index 000000000..5ac07ce55
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -0,0 +1,228 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+const PRELOAD_PREF = "browser.newtab.preload";
+
+var suggestedLink = {
+ url: "http://example1.com/2",
+ imageURI: "data:image/png;base64,helloWORLD3",
+ title: "title2",
+ type: "affiliate",
+ adgroup_name: "Technology",
+ frecent_sites: ["classroom.google.com", "codeacademy.org", "codecademy.com", "codeschool.com", "codeyear.com", "elearning.ut.ac.id", "how-to-build-websites.com", "htmlcodetutorial.com", "htmldog.com", "htmlplayground.com", "learn.jquery.com", "quackit.com", "roseindia.net", "teamtreehouse.com", "tizag.com", "tutorialspoint.com", "udacity.com", "w3schools.com", "webdevelopersnotes.com"]
+};
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "enhanced": [{
+ url: "http://example.com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD",
+ title: "title",
+ type: "organic",
+ }],
+ "directory": [{
+ url: "http://example1.com/",
+ enhancedImageURI: "data:image/png;base64,helloWORLD2",
+ title: "title1",
+ type: "organic"
+ }],
+ "suggested": [suggestedLink]
+});
+
+add_task(function* () {
+ let origEnhanced = NewTabUtils.allPages.enhanced;
+ registerCleanupFunction(() => {
+ NewTabUtils.allPages.enhanced = origEnhanced;
+ });
+
+ yield pushPrefs([PRELOAD_PREF, false]);
+
+ function getData(cellNum) {
+ return performOnCell(cellNum, cell => {
+ if (!cell.site)
+ return null;
+ let siteNode = cell.site.node;
+ return {
+ type: siteNode.getAttribute("type"),
+ enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+ title: siteNode.querySelector(".newtab-title").textContent,
+ suggested: siteNode.querySelector(".newtab-suggested").innerHTML
+ };
+ });
+ }
+
+ // Make the page have a directory link, enhanced link, and history link
+ yield setLinks("-1");
+
+ // Test with enhanced = false
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("classic");
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+ let {type, enhanced, title, suggested} = yield getData(0);
+ isnot(type, "enhanced", "history link is not enhanced");
+ is(enhanced, "", "history link has no enhanced image");
+ is(title, "example.com");
+ is(suggested, "", "There is no suggested explanation");
+
+ let data = yield getData(1);
+ is(data, null, "there is only one link and it's a history link");
+
+ // Test with enhanced = true
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced on
+ ({type, enhanced, title, suggested} = yield getData(0));
+ is(type, "organic", "directory link is organic");
+ isnot(enhanced, "", "directory link has enhanced image");
+ is(title, "title1");
+ is(suggested, "", "There is no suggested explanation");
+
+ ({type, enhanced, title, suggested} = yield getData(1));
+ is(type, "enhanced", "history link is enhanced");
+ isnot(enhanced, "", "history link has enhanced image");
+ is(title, "title");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(2);
+ is(data, null, "there are only 2 links, directory and enhanced history");
+
+ // Test with a pinned link
+ setPinnedLinks("-1");
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ is(type, "enhanced", "pinned history link is enhanced");
+ isnot(enhanced, "", "pinned history link has enhanced image");
+ is(title, "title");
+ is(suggested, "", "There is no suggested explanation");
+
+ ({type, enhanced, title, suggested} = yield getData(1));
+ is(type, "organic", "directory link is organic");
+ isnot(enhanced, "", "directory link has enhanced image");
+ is(title, "title1");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(2);
+ is(data, null, "directory link pushed out by pinned history link");
+
+ // Test pinned link with enhanced = false
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced"); // Toggle enhanced off
+ ({type, enhanced, title, suggested} = yield getData(0));
+ isnot(type, "enhanced", "history link is not enhanced");
+ is(enhanced, "", "history link has no enhanced image");
+ is(title, "example.com");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(1);
+ is(data, null, "directory link still pushed out by pinned history link");
+
+ yield unpinCell(0);
+
+
+
+ // Test that a suggested tile is not enhanced by a directory tile
+ NewTabUtils.isTopPlacesSite = () => true;
+ yield setLinks("-1,2,3,4,5,6,7,8");
+
+ // Test with enhanced = false
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ isnot(type, "enhanced", "history link is not enhanced");
+ is(enhanced, "", "history link has no enhanced image");
+ is(title, "example.com");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(7);
+ isnot(data, null, "there are 8 history links");
+ data = yield getData(8);
+ is(data, null, "there are 8 history links");
+
+
+ // Test with enhanced = true
+ yield* addNewTabPageTab();
+ yield customizeNewTabPage("enhanced");
+
+ // Suggested link was not enhanced by directory link with same domain
+ ({type, enhanced, title, suggested} = yield getData(0));
+ is(type, "affiliate", "suggested link is affiliate");
+ is(enhanced, "", "suggested link has no enhanced image");
+ is(title, "title2");
+ ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
+
+ // Enhanced history link shows up second
+ ({type, enhanced, title, suggested} = yield getData(1));
+ is(type, "enhanced", "pinned history link is enhanced");
+ isnot(enhanced, "", "pinned history link has enhanced image");
+ is(title, "title");
+ is(suggested, "", "There is no suggested explanation");
+
+ data = yield getData(9);
+ is(data, null, "there is a suggested link followed by an enhanced history link and the remaining history links");
+
+
+
+ // Test no override category/adgroup name.
+ let linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + JSON.stringify({"suggested": [suggestedLink]})]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> Technology </strong> visitors") > -1, "Suggested for 'Technology'");
+
+
+ // Test server provided explanation string.
+ suggestedLink.explanation = "Suggested for %1$S enthusiasts who visit sites like %2$S";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> Technology </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'Technology' enthusiasts");
+
+
+ // Test server provided explanation string with category override.
+ suggestedLink.adgroup_name = "webdev education";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> webdev education </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'webdev education' enthusiasts");
+
+
+
+ // Test with xml entities in category name
+ suggestedLink.url = "http://example1.com/3";
+ suggestedLink.adgroup_name = ">angles< & \"quotes\'";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Suggested for <strong> &gt;angles&lt; &amp; \"quotes\' </strong> enthusiasts who visit sites like <strong> classroom.google.com </strong>") > -1, "Suggested for 'xml entities' enthusiasts");
+
+
+ // Test with xml entities in explanation.
+ suggestedLink.explanation = "Testing junk explanation &<>\"'";
+ linksChangedPromise = watchLinksChangeOnce();
+ yield pushPrefs([PREF_NEWTAB_DIRECTORYSOURCE,
+ "data:application/json," + encodeURIComponent(JSON.stringify({"suggested": [suggestedLink]}))]);
+ yield linksChangedPromise;
+
+ yield* addNewTabPageTab();
+ ({type, enhanced, title, suggested} = yield getData(0));
+ Cu.reportError("SUGGEST " + suggested);
+ ok(suggested.indexOf("Testing junk explanation &amp;&lt;&gt;\"'") > -1, "Junk test");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_focus.js b/browser/base/content/test/newtab/browser_newtab_focus.js
new file mode 100644
index 000000000..ae0dd8d29
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that focusing the 'New Tab Page' works as expected.
+ */
+add_task(function* () {
+ yield pushPrefs(["accessibility.tabfocus", 7]);
+
+ // Focus count in new tab page.
+ // 30 = 9 * 3 + 3 = 9 sites, each with link, pin and remove buttons; search
+ // bar; search button; and toggle button. Additionaly there may or may not be
+ // a scroll bar caused by fix to 1180387, which will eat an extra focus
+ let FOCUS_COUNT = 30;
+
+ // Create a new tab page.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield* addNewTabPageTab();
+ gURLBar.focus();
+
+ // Count the focus with the enabled page.
+ countFocus(FOCUS_COUNT);
+
+ // Disable page and count the focus with the disabled page.
+ NewTabUtils.allPages.enabled = false;
+
+ countFocus(4);
+
+ NewTabUtils.allPages.enabled = true;
+});
+
+/**
+ * Focus the urlbar and count how many focus stops to return again to the urlbar.
+ */
+function countFocus(aExpectedCount) {
+ let focusCount = 0;
+ do {
+ EventUtils.synthesizeKey("VK_TAB", {});
+ if (document.activeElement == gBrowser.selectedBrowser) {
+ focusCount++;
+ }
+ } while (document.activeElement != gURLBar.inputField);
+
+ ok(focusCount == aExpectedCount || focusCount == (aExpectedCount + 1),
+ "Validate focus count in the new tab page.");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
new file mode 100644
index 000000000..b330bec13
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests ensure that all changes made to the new tab page in private
+ * browsing mode are discarded after switching back to normal mode again.
+ * The private browsing mode should start with the current grid shown in normal
+ * mode.
+ */
+
+add_task(function* () {
+ // prepare the grid
+ yield testOnWindow(undefined);
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+
+ yield* addNewTabPageTab();
+ yield pinCell(0);
+ yield* checkGrid("0p,1,2,3,4,5,6,7,8");
+
+ // open private window
+ yield testOnWindow({private: true});
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0p,1,2,3,4,5,6,7,8");
+
+ // modify the grid while we're in pb mode
+ yield blockCell(1);
+ yield* checkGrid("0p,2,3,4,5,6,7,8");
+
+ yield unpinCell(0);
+ yield* checkGrid("0,2,3,4,5,6,7,8");
+
+ // open normal window
+ yield testOnWindow(undefined);
+
+ // check that the grid is the same as before entering pb mode
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,2,3,4,5,6,7,8")
+});
+
+var windowsToClose = [];
+function* testOnWindow(options) {
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ var win = OpenBrowserWindow(options);
+ windowsToClose.push(win);
+ gWindow = win;
+ yield newWindowPromise;
+}
+
+registerCleanupFunction(function () {
+ gWindow = window;
+ windowsToClose.forEach(function(win) {
+ win.close();
+ });
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_reflow_load.js b/browser/base/content/test/newtab/browser_newtab_reflow_load.js
new file mode 100644
index 000000000..b8a24595e
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_reflow_load.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FRAME_SCRIPT = getRootDirectory(gTestPath) + "content-reflows.js";
+const ADDITIONAL_WAIT_MS = 2000;
+
+/*
+ * Ensure that loading about:newtab doesn't cause uninterruptible reflows.
+ */
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ return gBrowser.selectedTab = gBrowser.addTab("about:blank", {animate: false});
+ }, false);
+
+ let browser = gBrowser.selectedBrowser;
+ let mm = browser.messageManager;
+ mm.loadFrameScript(FRAME_SCRIPT, true);
+ mm.addMessageListener("newtab-reflow", ({data: stack}) => {
+ ok(false, `unexpected uninterruptible reflow ${stack}`);
+ });
+
+ let browserLoadedPromise = BrowserTestUtils.waitForEvent(browser, "load", true);
+ browser.loadURI("about:newtab");
+ yield browserLoadedPromise;
+
+ // Wait some more to catch sync reflows after the page has loaded.
+ yield new Promise(resolve => {
+ setTimeout(resolve, ADDITIONAL_WAIT_MS);
+ });
+
+ // Clean up.
+ gBrowser.removeCurrentTab({animate: false});
+
+ ok(true, "Each test requires at least one pass, fail or todo so here is a pass.");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
new file mode 100644
index 000000000..24e1be369
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PRELOAD_PREF = "browser.newtab.preload";
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+ "directory": [{
+ url: "http://example.com/organic",
+ type: "organic"
+ }, {
+ url: "http://localhost/sponsored",
+ type: "sponsored"
+ }]
+});
+
+add_task(function* () {
+ yield pushPrefs([PRELOAD_PREF, false]);
+
+ let originalReportSitesAction = DirectoryLinksProvider.reportSitesAction;
+ registerCleanupFunction(() => {
+ DirectoryLinksProvider.reportSitesAction = originalReportSitesAction;
+ });
+
+ let expected = {};
+
+ function expectReportSitesAction() {
+ return new Promise(resolve => {
+ DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) {
+ let {link} = sites[siteIndex];
+ is(link.type, expected.type, "got expected type");
+ is(action, expected.action, "got expected action");
+ is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned");
+ resolve();
+ }
+ });
+ }
+
+ // Test that the last visible site (index 1) is reported
+ let reportSitesPromise = expectReportSitesAction();
+ expected.type = "sponsored";
+ expected.action = "view";
+ expected.pinned = false;
+ yield* addNewTabPageTab();
+ yield reportSitesPromise;
+
+ // Click the pin button on the link in the 1th tile spot
+ expected.action = "pin";
+ // tiles become "history" when pinned
+ expected.type = "history";
+ expected.pinned = true;
+ let pagesUpdatedPromise = whenPagesUpdated();
+ reportSitesPromise = expectReportSitesAction();
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield pagesUpdatedPromise;
+ yield reportSitesPromise;
+
+ // Unpin that link
+ expected.action = "unpin";
+ expected.pinned = false;
+ pagesUpdatedPromise = whenPagesUpdated();
+ reportSitesPromise = expectReportSitesAction();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-cell + .newtab-cell .newtab-control-pin", {}, gBrowser.selectedBrowser);
+ yield pagesUpdatedPromise;
+ yield reportSitesPromise;
+
+ // Block the site in the 0th tile spot
+ expected.type = "organic";
+ expected.action = "block";
+ expected.pinned = false;
+ pagesUpdatedPromise = whenPagesUpdated();
+ reportSitesPromise = expectReportSitesAction();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site .newtab-control-block", {}, gBrowser.selectedBrowser);
+ yield pagesUpdatedPromise;
+ yield reportSitesPromise;
+
+ // Click the 1th link now in the 0th tile spot
+ expected.type = "history";
+ expected.action = "click";
+ reportSitesPromise = expectReportSitesAction();
+ yield BrowserTestUtils.synthesizeMouseAtCenter(".newtab-site", {}, gBrowser.selectedBrowser);
+ yield reportSitesPromise;
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_search.js b/browser/base/content/test/newtab/browser_newtab_search.js
new file mode 100644
index 000000000..19ed4ba74
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -0,0 +1,247 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// See browser/components/search/test/browser_*_behavior.js for tests of actual
+// searches.
+
+Cu.import("resource://gre/modules/Task.jsm");
+
+const ENGINE_NO_LOGO = {
+ name: "searchEngineNoLogo.xml",
+ numLogos: 0,
+};
+
+const ENGINE_FAVICON = {
+ name: "searchEngineFavicon.xml",
+ logoPrefix1x: "data:image/png;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsA",
+ numLogos: 1,
+};
+ENGINE_FAVICON.logoPrefix2x = ENGINE_FAVICON.logoPrefix1x;
+
+const ENGINE_1X_LOGO = {
+ name: "searchEngine1xLogo.xml",
+ logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADEw",
+ numLogos: 1,
+};
+ENGINE_1X_LOGO.logoPrefix2x = ENGINE_1X_LOGO.logoPrefix1x;
+
+const ENGINE_2X_LOGO = {
+ name: "searchEngine2xLogo.xml",
+ logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMU",
+ numLogos: 1,
+};
+ENGINE_2X_LOGO.logoPrefix1x = ENGINE_2X_LOGO.logoPrefix2x;
+
+const ENGINE_1X_2X_LOGO = {
+ name: "searchEngine1x2xLogo.xml",
+ logoPrefix1x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADIG",
+ logoPrefix2x: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMo",
+ numLogos: 2,
+};
+
+const ENGINE_SUGGESTIONS = {
+ name: "searchSuggestionEngine.xml",
+ numLogos: 0,
+};
+
+// The test has an expected search event queue and a search event listener.
+// Search events that are expected to happen are added to the queue, and the
+// listener consumes the queue and ensures that each event it receives is at
+// the head of the queue.
+let gExpectedSearchEventQueue = [];
+let gExpectedSearchEventResolver = null;
+
+let gNewEngines = [];
+
+add_task(function* () {
+ let oldCurrentEngine = Services.search.currentEngine;
+
+ yield* addNewTabPageTab();
+
+ // The tab is removed at the end of the test, so there's no need to remove
+ // this listener at the end of the test.
+ info("Adding search event listener");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ const SERVICE_EVENT_NAME = "ContentSearchService";
+ content.addEventListener(SERVICE_EVENT_NAME, function (event) {
+ sendAsyncMessage("test:search-event", { eventType: event.detail.type });
+ });
+ });
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("test:search-event", function (message) {
+ let eventType = message.data.eventType;
+ if (!gExpectedSearchEventResolver) {
+ ok(false, "Got search event " + eventType + " with no promise assigned");
+ }
+
+ let expectedEventType = gExpectedSearchEventQueue.shift();
+ is(eventType, expectedEventType, "Got expected search event " + expectedEventType);
+ if (!gExpectedSearchEventQueue.length) {
+ gExpectedSearchEventResolver();
+ gExpectedSearchEventResolver = null;
+ }
+ });
+
+ // Add the engine without any logos and switch to it.
+ let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO);
+ let searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = noLogoEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_NO_LOGO);
+
+ // Add the engine with favicon and switch to it.
+ let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = faviconEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_FAVICON);
+
+ // Add the engine with a 1x-DPI logo and switch to it.
+ let logo1xEngine = yield promiseNewSearchEngine(ENGINE_1X_LOGO);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = logo1xEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_1X_LOGO);
+
+ // Add the engine with a 2x-DPI logo and switch to it.
+ let logo2xEngine = yield promiseNewSearchEngine(ENGINE_2X_LOGO);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = logo2xEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_2X_LOGO);
+
+ // Add the engine with 1x- and 2x-DPI logos and switch to it.
+ let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = logo1x2xEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_1X_2X_LOGO);
+
+ // Add the engine that provides search suggestions and switch to it.
+ let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = suggestionEngine;
+ yield searchEventsPromise;
+ yield* checkCurrentEngine(ENGINE_SUGGESTIONS);
+
+ // Avoid intermittent failures.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ content.gSearch._contentSearchController.remoteTimeout = 5000;
+ });
+
+ // Type an X in the search input. This is only a smoke test. See
+ // browser_searchSuggestionUI.js for comprehensive content search suggestion
+ // UI tests.
+ let suggestionsOpenPromise = new Promise(resolve => {
+ mm.addMessageListener("test:newtab-suggestions-open", function onResponse(message) {
+ mm.removeMessageListener("test:newtab-suggestions-open", onResponse);
+ resolve();
+ });
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let table = content.document.getElementById("searchSuggestionTable");
+
+ let input = content.document.getElementById("newtab-search-text");
+ input.focus();
+
+ info("Waiting for suggestions table to open");
+ let observer = new content.MutationObserver(() => {
+ if (input.getAttribute("aria-expanded") == "true") {
+ observer.disconnect();
+ Assert.ok(!table.hidden, "Search suggestion table unhidden");
+ sendAsyncMessage("test:newtab-suggestions-open", {});
+ }
+ });
+ observer.observe(input, {
+ attributes: true,
+ attributeFilter: ["aria-expanded"],
+ });
+ });
+
+ let suggestionsPromise = promiseSearchEvents(["Suggestions"]);
+
+ EventUtils.synthesizeKey("x", {});
+
+ // Wait for the search suggestions to become visible and for the Suggestions
+ // message.
+ yield suggestionsOpenPromise;
+ yield suggestionsPromise;
+
+ // Empty the search input, causing the suggestions to be hidden.
+ EventUtils.synthesizeKey("a", { accelKey: true });
+ EventUtils.synthesizeKey("VK_DELETE", {});
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ Assert.ok(content.document.getElementById("searchSuggestionTable").hidden,
+ "Search suggestion table hidden");
+ });
+
+ // Done. Revert the current engine and remove the new engines.
+ searchEventsPromise = promiseSearchEvents(["CurrentEngine"]);
+ Services.search.currentEngine = oldCurrentEngine;
+ yield searchEventsPromise;
+
+ let events = Array(gNewEngines.length).fill("CurrentState", 0, gNewEngines.length);
+ searchEventsPromise = promiseSearchEvents(events);
+
+ for (let engine of gNewEngines) {
+ Services.search.removeEngine(engine);
+ }
+ yield searchEventsPromise;
+});
+
+function promiseSearchEvents(events) {
+ info("Expecting search events: " + events);
+ return new Promise(resolve => {
+ gExpectedSearchEventQueue.push(...events);
+ gExpectedSearchEventResolver = resolve;
+ });
+}
+
+function promiseNewSearchEngine({name: basename, numLogos}) {
+ info("Waiting for engine to be added: " + basename);
+
+ // Wait for the search events triggered by adding the new engine.
+ // engine-added engine-loaded
+ let expectedSearchEvents = ["CurrentState", "CurrentState"];
+ // engine-changed for each of the logos
+ for (let i = 0; i < numLogos; i++) {
+ expectedSearchEvents.push("CurrentState");
+ }
+ let eventPromise = promiseSearchEvents(expectedSearchEvents);
+
+ // Wait for addEngine().
+ let addEnginePromise = new Promise((resolve, reject) => {
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ gNewEngines.push(engine);
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+
+ return Promise.all([addEnginePromise, eventPromise]).then(([newEngine, _]) => {
+ return newEngine;
+ });
+}
+
+function* checkCurrentEngine(engineInfo)
+{
+ let engine = Services.search.currentEngine;
+ ok(engine.name.includes(engineInfo.name),
+ "Sanity check: current engine: engine.name=" + engine.name +
+ " basename=" + engineInfo.name);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { name: engine.name }, function* (args) {
+ Assert.equal(content.gSearch._contentSearchController.defaultEngine.name,
+ args.name, "currentEngineName: " + args.name);
+ });
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
new file mode 100644
index 000000000..f6bb85d47
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* () {
+ yield setLinks("0");
+ yield* addNewTabPageTab();
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ var EventUtils = {};
+ EventUtils.window = {};
+ EventUtils.parent = EventUtils.window;
+ EventUtils._EU_Ci = Components.interfaces;
+
+ Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ let cell = content.gGrid.cells[0];
+
+ let site = cell.node.querySelector(".newtab-site");
+ site.setAttribute("type", "sponsored");
+
+ // test explain text appearing upon a click
+ let sponsoredButton = site.querySelector(".newtab-sponsored");
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ let explain = site.querySelector(".sponsored-explain");
+ Assert.notEqual(explain, null, "Sponsored explanation shown");
+ Assert.ok(explain.querySelector("input").classList.contains("newtab-control-block"),
+ "sponsored tiles show blocked image");
+ Assert.ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
+
+ // test dismissing sponsored explain
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ Assert.equal(site.querySelector(".sponsored-explain"), null,
+ "Sponsored explanation no longer shown");
+ Assert.ok(!sponsoredButton.hasAttribute("active"),
+ "Sponsored button does not have active attribute");
+
+ // test with enhanced tile
+ site.setAttribute("type", "enhanced");
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ explain = site.querySelector(".sponsored-explain");
+ Assert.notEqual(explain, null, "Sponsored explanation shown");
+ Assert.ok(explain.querySelector("input").classList.contains("newtab-customize"),
+ "enhanced tiles show customize image");
+ Assert.ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
+
+ // test dismissing enhanced explain
+ EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, content);
+ Assert.equal(site.querySelector(".sponsored-explain"), null,
+ "Sponsored explanation no longer shown");
+ Assert.ok(!sponsoredButton.hasAttribute("active"),
+ "Sponsored button does not have active attribute");
+ });
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_undo.js b/browser/base/content/test/newtab/browser_newtab_undo.js
new file mode 100644
index 000000000..ba094cb26
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_undo.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that the undo dialog works as expected.
+ */
+add_task(function* () {
+ // remove unpinned sites and undo it
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("5");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("5p,0,1,2,3,4,6,7,8");
+
+ yield blockCell(4);
+ yield blockCell(4);
+ yield* checkGrid("5p,0,1,2,6,7,8");
+
+ yield* undo();
+ yield* checkGrid("5p,0,1,2,4,6,7,8");
+
+ // now remove a pinned site and undo it
+ yield blockCell(0);
+ yield* checkGrid("0,1,2,4,6,7,8");
+
+ yield* undo();
+ yield* checkGrid("5p,0,1,2,4,6,7,8");
+
+ // remove a site and restore all
+ yield blockCell(1);
+ yield* checkGrid("5p,1,2,4,6,7,8");
+
+ yield* undoAll();
+ yield* checkGrid("5p,0,1,2,3,4,6,7,8");
+});
+
+function* undo() {
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-button", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+}
+
+function* undoAll() {
+ let updatedPromise = whenPagesUpdated();
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#newtab-undo-restore-button", {}, gBrowser.selectedBrowser);
+ yield updatedPromise;
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_unpin.js b/browser/base/content/test/newtab/browser_newtab_unpin.js
new file mode 100644
index 000000000..14751465f
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_unpin.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that when a site gets unpinned it is either moved to
+ * its actual place in the grid or removed in case it's not on the grid anymore.
+ */
+add_task(function* () {
+ // we have a pinned link that didn't change its position since it was pinned.
+ // nothing should happen when we unpin it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ yield unpinCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // we have a pinned link that is not anymore in the list of the most-visited
+ // links. this should disappear, the remaining links adjust their positions
+ // and a new link will appear at the end of the grid.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",99");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield unpinCell(1);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // we have a pinned link that changed its position since it was pinned. it
+ // should be moved to its new position after being unpinned.
+ yield setLinks("0,1,2,3,4,5,6,7");
+ setPinnedLinks(",1,,,,,,,0");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("2,1p,3,4,5,6,7,,0p");
+
+ yield unpinCell(1);
+ yield* checkGrid("1,2,3,4,5,6,7,,0p");
+
+ yield unpinCell(8);
+ yield* checkGrid("0,1,2,3,4,5,6,7,");
+
+ // we have pinned link that changed its position since it was pinned. the
+ // link will disappear from the grid because it's now a much lower priority
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("9");
+
+ yield* addNewTabPageTab();
+ yield* checkGrid("9p,0,1,2,3,4,5,6,7");
+
+ yield unpinCell(0);
+ yield* checkGrid("0,1,2,3,4,5,6,7,8");
+});
diff --git a/browser/base/content/test/newtab/browser_newtab_update.js b/browser/base/content/test/newtab/browser_newtab_update.js
new file mode 100644
index 000000000..6cf089dfd
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_update.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Checks that newtab is updated as its links change.
+ */
+add_task(function* () {
+ // First, start with an empty page. setLinks will trigger a hidden page
+ // update because it calls clearHistory. We need to wait for that update to
+ // happen so that the next time we wait for a page update below, we catch the
+ // right update and not the one triggered by setLinks.
+ let updatedPromise = whenPagesUpdated();
+ let setLinksPromise = setLinks([]);
+ yield Promise.all([updatedPromise, setLinksPromise]);
+
+ // Strategy: Add some visits, open a new page, check the grid, repeat.
+ yield fillHistoryAndWaitForPageUpdate([1]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("1,,,,,,,,");
+
+ yield fillHistoryAndWaitForPageUpdate([2]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("2,1,,,,,,,");
+
+ yield fillHistoryAndWaitForPageUpdate([1]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("1,2,,,,,,,");
+
+ yield fillHistoryAndWaitForPageUpdate([2, 3, 4]);
+ yield* addNewTabPageTab();
+ yield* checkGrid("2,1,3,4,,,,,");
+
+ // Make sure these added links have the right type
+ let type = yield performOnCell(1, cell => { return cell.site.link.type });
+ is(type, "history", "added link is history");
+});
+
+function fillHistoryAndWaitForPageUpdate(links) {
+ let updatedPromise = whenPagesUpdated;
+ let fillHistoryPromise = fillHistory(links.map(link));
+ return Promise.all([updatedPromise, fillHistoryPromise]);
+}
+
+function link(id) {
+ return { url: "http://example" + id + ".com/", title: "site#" + id };
+}
diff --git a/browser/base/content/test/newtab/content-reflows.js b/browser/base/content/test/newtab/content-reflows.js
new file mode 100644
index 000000000..f1a53782e
--- /dev/null
+++ b/browser/base/content/test/newtab/content-reflows.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/. */
+
+(function () {
+ "use strict";
+
+ const Ci = Components.interfaces;
+
+ docShell.addWeakReflowObserver({
+ reflow() {
+ // Gather information about the current code path.
+ let path = (new Error().stack).split("\n").slice(1).join("\n");
+ if (path) {
+ sendSyncMessage("newtab-reflow", path);
+ }
+ },
+
+ reflowInterruptible() {
+ // We're not interested in interruptible reflows.
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+ Ci.nsISupportsWeakReference])
+ });
+})();
diff --git a/browser/base/content/test/newtab/head.js b/browser/base/content/test/newtab/head.js
new file mode 100644
index 000000000..d702103a0
--- /dev/null
+++ b/browser/base/content/test/newtab/head.js
@@ -0,0 +1,552 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
+const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
+
+Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
+
+var tmp = {};
+Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
+Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
+Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp);
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+var {NewTabUtils, Sanitizer, DirectoryLinksProvider, PlacesTestUtils} = tmp;
+
+var gWindow = window;
+
+// Default to dummy/empty directory links
+var gDirectorySource = 'data:application/json,{"test":1}';
+var gOrigDirectorySource;
+
+// The tests assume all 3 rows and all 3 columns of sites are shown, but the
+// window may be too small to actually show everything. Resize it if necessary.
+var requiredSize = {};
+requiredSize.innerHeight =
+ 40 + 32 + // undo container + bottom margin
+ 44 + 32 + // search bar + bottom margin
+ (3 * (180 + 32)) + // 3 rows * (tile height + title and bottom margin)
+ 100; // breathing room
+requiredSize.innerWidth =
+ (3 * (290 + 20)) + // 3 cols * (tile width + side margins)
+ 100; // breathing room
+
+var oldSize = {};
+Object.keys(requiredSize).forEach(prop => {
+ info([prop, gBrowser.contentWindow[prop], requiredSize[prop]]);
+ if (gBrowser.contentWindow[prop] < requiredSize[prop]) {
+ oldSize[prop] = gBrowser.contentWindow[prop];
+ info("Changing browser " + prop + " from " + oldSize[prop] + " to " +
+ requiredSize[prop]);
+ gBrowser.contentWindow[prop] = requiredSize[prop];
+ }
+});
+
+var screenHeight = {};
+var screenWidth = {};
+Cc["@mozilla.org/gfx/screenmanager;1"].
+ getService(Ci.nsIScreenManager).
+ primaryScreen.
+ GetAvailRectDisplayPix({}, {}, screenWidth, screenHeight);
+screenHeight = screenHeight.value;
+screenWidth = screenWidth.value;
+
+if (screenHeight < gBrowser.contentWindow.outerHeight) {
+ info("Warning: Browser outer height is now " +
+ gBrowser.contentWindow.outerHeight + ", which is larger than the " +
+ "available screen height, " + screenHeight +
+ ". That may cause problems.");
+}
+
+if (screenWidth < gBrowser.contentWindow.outerWidth) {
+ info("Warning: Browser outer width is now " +
+ gBrowser.contentWindow.outerWidth + ", which is larger than the " +
+ "available screen width, " + screenWidth +
+ ". That may cause problems.");
+}
+
+registerCleanupFunction(function () {
+ while (gWindow.gBrowser.tabs.length > 1)
+ gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
+
+ Object.keys(oldSize).forEach(prop => {
+ if (oldSize[prop]) {
+ gBrowser.contentWindow[prop] = oldSize[prop];
+ }
+ });
+
+ // Stop any update timers to prevent unexpected updates in later tests
+ let timer = NewTabUtils.allPages._scheduleUpdateTimeout;
+ if (timer) {
+ clearTimeout(timer);
+ delete NewTabUtils.allPages._scheduleUpdateTimeout;
+ }
+
+ Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
+
+ return watchLinksChangeOnce();
+});
+
+function pushPrefs(...aPrefs) {
+ return new Promise(resolve =>
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve));
+}
+
+/**
+ * Resolves promise when directory links are downloaded and written to disk
+ */
+function watchLinksChangeOnce() {
+ return new Promise(resolve => {
+ let observer = {
+ onManyLinksChanged: () => {
+ DirectoryLinksProvider.removeObserver(observer);
+ resolve();
+ }
+ };
+ observer.onDownloadFail = observer.onManyLinksChanged;
+ DirectoryLinksProvider.addObserver(observer);
+ });
+}
+
+add_task(function* setup() {
+ registerCleanupFunction(function() {
+ return new Promise(resolve => {
+ function cleanupAndFinish() {
+ PlacesTestUtils.clearHistory().then(() => {
+ whenPagesUpdated().then(resolve);
+ NewTabUtils.restore();
+ });
+ }
+
+ let callbacks = NewTabUtils.links._populateCallbacks;
+ let numCallbacks = callbacks.length;
+
+ if (numCallbacks)
+ callbacks.splice(0, numCallbacks, cleanupAndFinish);
+ else
+ cleanupAndFinish();
+ });
+ });
+
+ let promiseReady = Task.spawn(function*() {
+ yield watchLinksChangeOnce();
+ yield whenPagesUpdated();
+ });
+
+ // Save the original directory source (which is set globally for tests)
+ gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
+ Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
+ yield promiseReady;
+});
+
+/** Perform an action on a cell within the newtab page.
+ * @param aIndex index of cell
+ * @param aFn function to call in child process or tab.
+ * @returns result of calling the function.
+ */
+function performOnCell(aIndex, aFn) {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
+ { index: aIndex, fn: aFn.toString() }, function* (args) {
+ let cell = content.gGrid.cells[args.index];
+ return eval(args.fn)(cell);
+ });
+}
+
+/**
+ * Allows to provide a list of links that is used to construct the grid.
+ * @param aLinksPattern the pattern (see below)
+ *
+ * Example: setLinks("-1,0,1,2,3")
+ * Result: [{url: "http://example.com/", title: "site#-1"},
+ * {url: "http://example0.com/", title: "site#0"},
+ * {url: "http://example1.com/", title: "site#1"},
+ * {url: "http://example2.com/", title: "site#2"},
+ * {url: "http://example3.com/", title: "site#3"}]
+ */
+function setLinks(aLinks) {
+ return new Promise(resolve => {
+ let links = aLinks;
+
+ if (typeof links == "string") {
+ links = aLinks.split(/\s*,\s*/).map(function (id) {
+ return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
+ title: "site#" + id};
+ });
+ }
+
+ // Call populateCache() once to make sure that all link fetching that is
+ // currently in progress has ended. We clear the history, fill it with the
+ // given entries and call populateCache() now again to make sure the cache
+ // has the desired contents.
+ NewTabUtils.links.populateCache(function () {
+ PlacesTestUtils.clearHistory().then(() => {
+ fillHistory(links).then(() => {
+ NewTabUtils.links.populateCache(function () {
+ NewTabUtils.allPages.update();
+ resolve();
+ }, true);
+ });
+ });
+ });
+ });
+}
+
+function fillHistory(aLinks) {
+ return new Promise(resolve => {
+ let numLinks = aLinks.length;
+ if (!numLinks) {
+ executeSoon(resolve);
+ return;
+ }
+
+ let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
+
+ // Important: To avoid test failures due to clock jitter on Windows XP, call
+ // Date.now() once here, not each time through the loop.
+ let now = Date.now() * 1000;
+
+ for (let i = 0; i < aLinks.length; i++) {
+ let link = aLinks[i];
+ let place = {
+ uri: makeURI(link.url),
+ title: link.title,
+ // Links are secondarily sorted by visit date descending, so decrease the
+ // visit date as we progress through the array so that links appear in the
+ // grid in the order they're present in the array.
+ visits: [{visitDate: now - i, transitionType: transitionLink}]
+ };
+
+ PlacesUtils.asyncHistory.updatePlaces(place, {
+ handleError: () => ok(false, "couldn't add visit to history"),
+ handleResult: function () {},
+ handleCompletion: function () {
+ if (--numLinks == 0) {
+ resolve();
+ }
+ }
+ });
+ }
+ });
+}
+
+/**
+ * Allows to specify the list of pinned links (that have a fixed position in
+ * the grid.
+ * @param aLinksPattern the pattern (see below)
+ *
+ * Example: setPinnedLinks("3,,1")
+ * Result: 'http://example3.com/' is pinned in the first cell. 'http://example1.com/' is
+ * pinned in the third cell.
+ */
+function setPinnedLinks(aLinks) {
+ let links = aLinks;
+
+ if (typeof links == "string") {
+ links = aLinks.split(/\s*,\s*/).map(function (id) {
+ if (id)
+ return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
+ title: "site#" + id,
+ type: "history"};
+ return undefined;
+ });
+ }
+
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(links);
+ Services.prefs.setComplexValue("browser.newtabpage.pinned",
+ Ci.nsISupportsString, string);
+
+ NewTabUtils.pinnedLinks.resetCache();
+ NewTabUtils.allPages.update();
+}
+
+/**
+ * Restore the grid state.
+ */
+function restore() {
+ return new Promise(resolve => {
+ whenPagesUpdated().then(resolve);
+ NewTabUtils.restore();
+ });
+}
+
+/**
+ * Wait until a given condition becomes true.
+ */
+function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
+ return new Promise((resolve, reject) => {
+ let tries = 0;
+
+ function tryNow() {
+ tries++;
+
+ if (aConditionFn()) {
+ resolve();
+ } else if (tries < aMaxTries) {
+ tryAgain();
+ } else {
+ reject("Condition timed out: " + aConditionFn.toSource());
+ }
+ }
+
+ function tryAgain() {
+ setTimeout(tryNow, aCheckInterval);
+ }
+
+ tryAgain();
+ });
+}
+
+/**
+ * Creates a new tab containing 'about:newtab'.
+ */
+function* addNewTabPageTab() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gWindow.gBrowser, "about:newtab", false);
+ let browser = tab.linkedBrowser;
+
+ // Wait for the document to become visible in case it was preloaded.
+ yield waitForCondition(() => !browser.contentDocument.hidden)
+
+ yield new Promise(resolve => {
+ if (NewTabUtils.allPages.enabled) {
+ // Continue when the link cache has been populated.
+ NewTabUtils.links.populateCache(function () {
+ whenSearchInitDone().then(resolve);
+ });
+ } else {
+ resolve();
+ }
+ });
+
+ return tab;
+}
+
+/**
+ * Compares the current grid arrangement with the given pattern.
+ * @param the pattern (see below)
+ *
+ * Example: checkGrid("3p,2,,1p")
+ * Result: We expect the first cell to contain the pinned site 'http://example3.com/'.
+ * The second cell contains 'http://example2.com/'. The third cell is empty.
+ * The fourth cell contains the pinned site 'http://example4.com/'.
+ */
+function* checkGrid(pattern) {
+ let length = pattern.split(",").length;
+
+ yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
+ { length, pattern }, function* (args) {
+ let grid = content.wrappedJSObject.gGrid;
+
+ let sites = grid.sites.slice(0, args.length);
+ let foundPattern = sites.map(function (aSite) {
+ if (!aSite)
+ return "";
+
+ let pinned = aSite.isPinned();
+ let hasPinnedAttr = aSite.node.hasAttribute("pinned");
+
+ if (pinned != hasPinnedAttr)
+ ok(false, "invalid state (site.isPinned() != site[pinned])");
+
+ return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
+ });
+
+ Assert.equal(foundPattern, args.pattern, "grid status = " + args.pattern);
+ });
+}
+
+/**
+ * Blocks a site from the grid.
+ * @param aIndex The cell index.
+ */
+function blockCell(aIndex) {
+ return new Promise(resolve => {
+ whenPagesUpdated().then(resolve);
+ performOnCell(aIndex, cell => {
+ return cell.site.block();
+ });
+ });
+}
+
+/**
+ * Pins a site on a given position.
+ * @param aIndex The cell index.
+ * @param aPinIndex The index the defines where the site should be pinned.
+ */
+function pinCell(aIndex) {
+ performOnCell(aIndex, cell => {
+ cell.site.pin();
+ });
+}
+
+/**
+ * Unpins the given cell's site.
+ * @param aIndex The cell index.
+ */
+function unpinCell(aIndex) {
+ return new Promise(resolve => {
+ whenPagesUpdated().then(resolve);
+ performOnCell(aIndex, cell => {
+ cell.site.unpin();
+ });
+ });
+}
+
+/**
+ * Simulates a drag and drop operation. Instead of rearranging a site that is
+ * is already contained in the newtab grid, this is used to simulate dragging
+ * an external link onto the grid e.g. the text from the URL bar.
+ * @param aDestIndex The cell index of the drop target.
+ */
+function* simulateExternalDrop(aDestIndex) {
+ let pagesUpdatedPromise = whenPagesUpdated();
+
+ yield ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aDestIndex, function*(dropIndex) {
+ return new Promise(resolve => {
+ const url = "data:text/html;charset=utf-8," +
+ "<a id='link' href='http://example99.com/'>link</a>";
+
+ let doc = content.document;
+ let iframe = doc.createElement("iframe");
+
+ function iframeLoaded() {
+ let dataTransfer = new iframe.contentWindow.DataTransfer("dragstart", false);
+ dataTransfer.mozSetDataAt("text/x-moz-url", "http://example99.com/", 0);
+
+ let event = content.document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ let target = content.gGrid.cells[dropIndex].node;
+ target.dispatchEvent(event);
+
+ iframe.remove();
+
+ resolve();
+ }
+
+ iframe.addEventListener("load", function onLoad() {
+ iframe.removeEventListener("load", onLoad);
+ content.setTimeout(iframeLoaded, 0);
+ });
+
+ iframe.setAttribute("src", url);
+ iframe.style.width = "50px";
+ iframe.style.height = "50px";
+ iframe.style.position = "absolute";
+ iframe.style.zIndex = 50;
+
+ // the frame has to be attached to a visible element
+ let margin = doc.getElementById("newtab-search-container");
+ margin.appendChild(iframe);
+ });
+ });
+
+ yield pagesUpdatedPromise;
+}
+
+/**
+ * Resumes testing when all pages have been updated.
+ */
+function whenPagesUpdated() {
+ return new Promise(resolve => {
+ let page = {
+ observe: _ => _,
+
+ update() {
+ NewTabUtils.allPages.unregister(this);
+ executeSoon(resolve);
+ }
+ };
+
+ NewTabUtils.allPages.register(page);
+ registerCleanupFunction(function () {
+ NewTabUtils.allPages.unregister(page);
+ });
+ });
+}
+
+/**
+ * Waits for the response to the page's initial search state request.
+ */
+function whenSearchInitDone() {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function*() {
+ return new Promise(resolve => {
+ if (content.gSearch) {
+ let searchController = content.gSearch._contentSearchController;
+ if (searchController.defaultEngine) {
+ resolve();
+ return;
+ }
+ }
+
+ let eventName = "ContentSearchService";
+ content.addEventListener(eventName, function onEvent(event) {
+ if (event.detail.type == "State") {
+ content.removeEventListener(eventName, onEvent);
+ let resolver = function() {
+ // Wait for the search controller to receive the event, then resolve.
+ if (content.gSearch._contentSearchController.defaultEngine) {
+ resolve();
+ return;
+ }
+ }
+ content.setTimeout(resolver, 0);
+ }
+ });
+ });
+ });
+}
+
+/**
+ * Changes the newtab customization option and waits for the panel to open and close
+ *
+ * @param {string} aTheme
+ * Can be any of("blank"|"classic"|"enhanced")
+ */
+function customizeNewTabPage(aTheme) {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, aTheme, function*(aTheme) {
+
+ let document = content.document;
+ let panel = document.getElementById("newtab-customize-panel");
+ let customizeButton = document.getElementById("newtab-customize-button");
+
+ function panelOpened(opened) {
+ return new Promise( (resolve) => {
+ let options = {attributes: true, oldValue: true};
+ let observer = new content.MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ document.getElementById("newtab-customize-" + aTheme).click();
+ observer.disconnect();
+ if (opened == panel.hasAttribute("open")) {
+ resolve();
+ }
+ });
+ });
+ observer.observe(panel, options);
+ });
+ }
+
+ let opened = panelOpened(true);
+ customizeButton.click();
+ yield opened;
+
+ let closed = panelOpened(false);
+ customizeButton.click();
+ yield closed;
+ });
+}
+
+/**
+ * Reports presence of a scrollbar
+ */
+function hasScrollbar() {
+ return ContentTask.spawn(gWindow.gBrowser.selectedBrowser, {}, function* () {
+ let docElement = content.document.documentElement;
+ return docElement.scrollHeight > docElement.clientHeight;
+ });
+}
diff --git a/browser/base/content/test/newtab/searchEngine1x2xLogo.xml b/browser/base/content/test/newtab/searchEngine1x2xLogo.xml
new file mode 100644
index 000000000..c8b6749b3
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngine1x2xLogo.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngine1x2xLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1x2xlogo" rel="searchform"/>
+<!-- #00FF00 -->
+<Image width="65" height="26">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADIGr0+8uwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVRYw+3PAREAAAQEsKd/Z3I4W4NVJtd14uDg4ODg4ODg4ODg4ODg4ODg8Pqw7M0BM+n9I0oAAAAASUVORK5CYII=</Image>
+<!-- #00FFFF -->
+<Image width="130" height="52">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMoaoKANQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAYklEQVR42u3RQQ0AAAjEMMC/58MECZ9OwtqVlL4bCzAIAwZhwCAMGIQBgzBgEAYMwoBBGDAIAwZhwCAMGIQBgzBgEAYMwoBBGIQBgzBgEAYMwoBBGDAIAwZhwCAMGIQBg+5ar7sCZiri9VUAAAAASUVORK5CYII=</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngine1xLogo.xml b/browser/base/content/test/newtab/searchEngine1xLogo.xml
new file mode 100644
index 000000000..19ac03f48
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngine1xLogo.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngine1xLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1xlogo" rel="searchform"/>
+<!-- #FF0000 -->
+<Image width="65" height="26">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAIAAABn3KYmAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADEwS9h64QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVRYw+3PAREAAAQEsKd/Z3I4W4PV5LyOg4ODg4ODg4ODg4ODg4ODg4PD78MC7cwBM02qquMAAAAASUVORK5CYII=</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngine2xLogo.xml b/browser/base/content/test/newtab/searchEngine2xLogo.xml
new file mode 100644
index 000000000..941bf040d
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngine2xLogo.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngine2xLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/2xlogo" rel="searchform"/>
+<!-- #0000FF -->
+<Image width="130" height="52">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAIAAADJ8nfCAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gkTADMURe38sgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAYklEQVR42u3RQQ0AAAjEsAP/nsEECZ9OwlrJRN+1BRiEAYMwYBAGDMKAQRgwCAMGYcAgDBiEAYMwYBAGDMKAQRgwCAMGYcAgDMKAQRgwCAMGYcAgDBiEAYMwYBAGDMKAQXctkIQBZ/1YP3YAAAAASUVORK5CYII=</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngineFavicon.xml b/browser/base/content/test/newtab/searchEngineFavicon.xml
new file mode 100644
index 000000000..6f2a970f5
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineFavicon.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngineFavicon.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/1xlogo" rel="searchform"/>
+<Image width="16" height="16">data:application/ico;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAA/wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAABAAAAAgAAAAAQAgAAAAAAAABAAAEwsAABMLAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+</SearchPlugin>
diff --git a/browser/base/content/test/newtab/searchEngineNoLogo.xml b/browser/base/content/test/newtab/searchEngineNoLogo.xml
new file mode 100644
index 000000000..bbff6cf8f
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineNoLogo.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngineNoLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/nologo" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/plugins/.eslintrc.js b/browser/base/content/test/plugins/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/plugins/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/plugins/blockNoPlugins.xml b/browser/base/content/test/plugins/blockNoPlugins.xml
new file mode 100644
index 000000000..e4e191b37
--- /dev/null
+++ b/browser/base/content/test/plugins/blockNoPlugins.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310001">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginHard.xml b/browser/base/content/test/plugins/blockPluginHard.xml
new file mode 100644
index 000000000..24eb5bc6f
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginHard.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginInfoURL.xml b/browser/base/content/test/plugins/blockPluginInfoURL.xml
new file mode 100644
index 000000000..c16808896
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginInfoURL.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="2"></versionRange>
+ <infoURL>http://test.url.com/</infoURL>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml b/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml
new file mode 100644
index 000000000..bf8545afe
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginVulnerableNoUpdate.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml b/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml
new file mode 100644
index 000000000..5545162b1
--- /dev/null
+++ b/browser/base/content/test/plugins/blockPluginVulnerableUpdatable.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/plugins/blocklist_proxy.js b/browser/base/content/test/plugins/blocklist_proxy.js
new file mode 100644
index 000000000..1a4ed4726
--- /dev/null
+++ b/browser/base/content/test/plugins/blocklist_proxy.js
@@ -0,0 +1,78 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cm = Components.manager;
+
+const kBlocklistServiceUUID = "{66354bc9-7ed1-4692-ae1d-8da97d6b205e}";
+const kBlocklistServiceContractID = "@mozilla.org/extensions/blocklist;1";
+const kBlocklistServiceFactory = Cm.getClassObject(Cc[kBlocklistServiceContractID], Ci.nsIFactory);
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+/*
+ * A lightweight blocklist proxy for the testing purposes.
+ */
+var BlocklistProxy = {
+ _uuid: null,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsIBlocklistService,
+ Ci.nsITimerCallback]),
+
+ init: function() {
+ if (!this._uuid) {
+ this._uuid =
+ Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator)
+ .generateUUID();
+ Cm.nsIComponentRegistrar.registerFactory(this._uuid, "",
+ "@mozilla.org/extensions/blocklist;1",
+ this);
+ }
+ },
+
+ uninit: function() {
+ if (this._uuid) {
+ Cm.nsIComponentRegistrar.unregisterFactory(this._uuid, this);
+ Cm.nsIComponentRegistrar.registerFactory(Components.ID(kBlocklistServiceUUID),
+ "Blocklist Service",
+ "@mozilla.org/extensions/blocklist;1",
+ kBlocklistServiceFactory);
+ this._uuid = null;
+ }
+ },
+
+ notify: function (aTimer) {
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ },
+
+ isAddonBlocklisted: function (aAddon, aAppVersion, aToolkitVersion) {
+ return false;
+ },
+
+ getAddonBlocklistState: function (aAddon, aAppVersion, aToolkitVersion) {
+ return 0; // STATE_NOT_BLOCKED
+ },
+
+ getPluginBlocklistState: function (aPluginTag, aAppVersion, aToolkitVersion) {
+ return 0; // STATE_NOT_BLOCKED
+ },
+
+ getAddonBlocklistURL: function (aAddon, aAppVersion, aToolkitVersion) {
+ return "";
+ },
+
+ getPluginBlocklistURL: function (aPluginTag) {
+ return "";
+ },
+
+ getPluginInfoURL: function (aPluginTag) {
+ return "";
+ },
+}
+
+BlocklistProxy.init();
+addEventListener("unload", () => {
+ BlocklistProxy.uninit();
+});
diff --git a/browser/base/content/test/plugins/browser.ini b/browser/base/content/test/plugins/browser.ini
new file mode 100644
index 000000000..cfc1f769c
--- /dev/null
+++ b/browser/base/content/test/plugins/browser.ini
@@ -0,0 +1,78 @@
+[DEFAULT]
+support-files =
+ blocklist_proxy.js
+ blockNoPlugins.xml
+ blockPluginHard.xml
+ blockPluginInfoURL.xml
+ blockPluginVulnerableNoUpdate.xml
+ blockPluginVulnerableUpdatable.xml
+ browser_clearplugindata.html
+ browser_clearplugindata_noage.html
+ head.js
+ plugin_add_dynamically.html
+ plugin_alternate_content.html
+ plugin_big.html
+ plugin_both.html
+ plugin_both2.html
+ plugin_bug744745.html
+ plugin_bug749455.html
+ plugin_bug787619.html
+ plugin_bug797677.html
+ plugin_bug820497.html
+ plugin_clickToPlayAllow.html
+ plugin_clickToPlayDeny.html
+ plugin_data_url.html
+ plugin_hidden_to_visible.html
+ plugin_iframe.html
+ plugin_outsideScrollArea.html
+ plugin_overlayed.html
+ plugin_positioned.html
+ plugin_small.html
+ plugin_small_2.html
+ plugin_syncRemoved.html
+ plugin_test.html
+ plugin_test2.html
+ plugin_test3.html
+ plugin_two_types.html
+ plugin_unknown.html
+ plugin_crashCommentAndURL.html
+ plugin_zoom.html
+
+[browser_bug743421.js]
+[browser_bug744745.js]
+[browser_bug787619.js]
+[browser_bug797677.js]
+[browser_bug812562.js]
+[browser_bug818118.js]
+[browser_bug820497.js]
+[browser_clearplugindata.js]
+[browser_CTP_context_menu.js]
+skip-if = toolkit == "gtk2" || toolkit == "gtk3" # fails intermittently on Linux (bug 909342)
+[browser_CTP_crashreporting.js]
+skip-if = !crashreporter
+[browser_CTP_data_urls.js]
+[browser_CTP_drag_drop.js]
+[browser_CTP_hide_overlay.js]
+[browser_CTP_iframe.js]
+[browser_CTP_multi_allow.js]
+[browser_CTP_nonplugins.js]
+[browser_CTP_notificationBar.js]
+[browser_CTP_outsideScrollArea.js]
+[browser_CTP_remove_navigate.js]
+[browser_CTP_resize.js]
+[browser_CTP_zoom.js]
+[browser_blocking.js]
+[browser_plugins_added_dynamically.js]
+[browser_pluginnotification.js]
+[browser_plugin_reloading.js]
+[browser_blocklist_content.js]
+skip-if = !e10s
+[browser_globalplugin_crashinfobar.js]
+skip-if = !crashreporter
+[browser_pluginCrashCommentAndURL.js]
+skip-if = !crashreporter
+[browser_pageInfo_plugins.js]
+[browser_pluginCrashReportNonDeterminism.js]
+skip-if = !crashreporter || os == 'linux' # Bug 1152811
+[browser_private_clicktoplay.js]
+
diff --git a/browser/base/content/test/plugins/browser_CTP_context_menu.js b/browser/base/content/test/plugins/browser_CTP_context_menu.js
new file mode 100644
index 000000000..03f3e18ef
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_context_menu.js
@@ -0,0 +1,69 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+// Test that the activate action in content menus for CTP plugins works
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ let bindingPromise = waitForEvent(gBrowser.selectedBrowser, "PluginBindingAttached", null, true, true);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+ yield bindingPromise;
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(!pluginInfo.activated, "plugin should not be activated");
+
+ // Display a context menu on the test plugin so we can test
+ // activation menu options.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("contextmenu", left, top, 2, 1, 0);
+ });
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Should have a click-to-play notification");
+ ok(popupNotification.dismissed, "notification should be dismissed");
+
+ // fixes a occasional test timeout on win7 opt
+ yield promiseForCondition(() => document.getElementById("context-ctp-play"));
+
+ let actMenuItem = document.getElementById("context-ctp-play");
+ ok(actMenuItem, "Should have a context menu entry for activating the plugin");
+
+ // Activate the plugin via the context menu
+ EventUtils.synthesizeMouseAtCenter(actMenuItem, {});
+
+ yield promiseForCondition(() => !PopupNotifications.panel.dismissed && PopupNotifications.panel.firstChild);
+
+ // Activate the plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should not be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_crashreporting.js b/browser/base/content/test/plugins/browser_CTP_crashreporting.js
new file mode 100644
index 000000000..bb52d5704
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_crashreporting.js
@@ -0,0 +1,233 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+const PLUGIN_PAGE = gTestRoot + "plugin_big.html";
+const PLUGIN_SMALL_PAGE = gTestRoot + "plugin_small.html";
+
+/**
+ * Takes an nsIPropertyBag and converts it into a JavaScript Object. It
+ * will also convert any nsIPropertyBag's within the nsIPropertyBag
+ * recursively.
+ *
+ * @param aBag
+ * The nsIPropertyBag to convert.
+ * @return Object
+ * Keyed on the names of the nsIProperty's within the nsIPropertyBag,
+ * and mapping to their values.
+ */
+function convertPropertyBag(aBag) {
+ let result = {};
+ let enumerator = aBag.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let { name, value } = enumerator.getNext().QueryInterface(Ci.nsIProperty);
+ if (value instanceof Ci.nsIPropertyBag) {
+ value = convertPropertyBag(value);
+ }
+ result[name] = value;
+ }
+ return result;
+}
+
+add_task(function* setup() {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
+ // crash reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverURL = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ registerCleanupFunction(function cleanUp() {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverURL);
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ window.focus();
+ });
+});
+
+/**
+ * Test that plugin crash submissions still work properly after
+ * click-to-play activation.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PLUGIN_PAGE,
+ }, function* (browser) {
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(browser);
+
+ let pluginInfo = yield promiseForPluginInfo("test", browser);
+ ok(!pluginInfo.activated, "Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
+ yield promiseForNotificationShown(notification, browser);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // Prepare a crash report topic observer that only returns when
+ // the crash report has been successfully sent.
+ let crashReportChecker = (subject, data) => {
+ return (data == "success");
+ };
+ let crashReportPromise = TestUtils.topicObserved("crash-report-status",
+ crashReportChecker);
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let plugin = content.document.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+
+ yield ContentTaskUtils.waitForCondition(() => {
+ return plugin.activated;
+ }, "Waited too long for plugin to activate.");
+
+ try {
+ Components.utils.waiveXrays(plugin).crash();
+ } catch (e) {
+ }
+
+ let doc = plugin.ownerDocument;
+
+ let getUI = (anonid) => {
+ return doc.getAnonymousElementByAttribute(plugin, "anonid", anonid);
+ };
+
+ // Now wait until the plugin crash report UI shows itself, which is
+ // asynchronous.
+ let statusDiv;
+
+ yield ContentTaskUtils.waitForCondition(() => {
+ statusDiv = getUI("submitStatus");
+ return statusDiv.getAttribute("status") == "please";
+ }, "Waited too long for plugin to show crash report UI");
+
+ // Make sure the UI matches our expectations...
+ let style = content.getComputedStyle(getUI("pleaseSubmit"));
+ if (style.display != "block") {
+ throw new Error(`Submission UI visibility is not correct. ` +
+ `Expected block style, got ${style.display}.`);
+ }
+
+ // Fill the crash report in with some test values that we'll test for in
+ // the parent.
+ getUI("submitComment").value = "a test comment";
+ let optIn = getUI("submitURLOptIn");
+ if (!optIn.checked) {
+ throw new Error("URL opt-in should default to true.");
+ }
+
+ // Submit the report.
+ optIn.click();
+ getUI("submitButton").click();
+
+ // And wait for the parent to say that the crash report was submitted
+ // successfully.
+ yield ContentTaskUtils.waitForCondition(() => {
+ return statusDiv.getAttribute("status") == "success";
+ }, "Timed out waiting for plugin binding to be in success state");
+ });
+
+ let [subject, ] = yield crashReportPromise;
+
+ ok(subject instanceof Ci.nsIPropertyBag,
+ "The crash report subject should be an nsIPropertyBag.");
+
+ let crashData = convertPropertyBag(subject);
+ ok(crashData.serverCrashID, "Should have a serverCrashID set.");
+
+ // Remove the submitted report file after ensuring it exists.
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(crashData.serverCrashID + ".txt");
+ ok(file.exists(), "Submitted report file should exist");
+ file.remove(false);
+
+ ok(crashData.extra, "Extra data should exist");
+ is(crashData.extra.PluginUserComment, "a test comment",
+ "Comment in extra data should match comment in textbox");
+
+ is(crashData.extra.PluginContentURL, undefined,
+ "URL should be absent from extra data when opt-in not checked");
+ });
+});
+
+/**
+ * Test that plugin crash submissions still work properly after
+ * click-to-play with the notification bar.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PLUGIN_SMALL_PAGE,
+ }, function* (browser) {
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(browser);
+
+ let pluginInfo = yield promiseForPluginInfo("test", browser);
+ ok(pluginInfo.activated, "Plugin should be activated from previous test");
+
+ // Prepare a crash report topic observer that only returns when
+ // the crash report has been successfully sent.
+ let crashReportChecker = (subject, data) => {
+ return (data == "success");
+ };
+ let crashReportPromise = TestUtils.topicObserved("crash-report-status",
+ crashReportChecker);
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let plugin = content.document.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+
+ yield ContentTaskUtils.waitForCondition(() => {
+ return plugin.activated;
+ }, "Waited too long for plugin to activate.");
+
+ try {
+ Components.utils.waiveXrays(plugin).crash();
+ } catch (e) {}
+ });
+
+ // Wait for the notification bar to be displayed.
+ let notification = yield waitForNotificationBar("plugin-crashed", browser);
+
+ // Then click the button to submit the crash report.
+ let buttons = notification.querySelectorAll(".notification-button");
+ is(buttons.length, 2, "Should have two buttons.");
+
+ // The "Submit Crash Report" button should be the second one.
+ let submitButton = buttons[1];
+ submitButton.click();
+
+ let [subject, ] = yield crashReportPromise;
+
+ ok(subject instanceof Ci.nsIPropertyBag,
+ "The crash report subject should be an nsIPropertyBag.");
+
+ let crashData = convertPropertyBag(subject);
+ ok(crashData.serverCrashID, "Should have a serverCrashID set.");
+
+ // Remove the submitted report file after ensuring it exists.
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(crashData.serverCrashID + ".txt");
+ ok(file.exists(), "Submitted report file should exist");
+ file.remove(false);
+
+ is(crashData.extra.PluginContentURL, undefined,
+ "URL should be absent from extra data when opt-in not checked");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_data_urls.js b/browser/base/content/test/plugins/browser_CTP_data_urls.js
new file mode 100644
index 000000000..0f4747b1e
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_data_urls.js
@@ -0,0 +1,255 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
+
+// Test that the click-to-play doorhanger still works when navigating to data URLs
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1a, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
+
+ let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ // navigate forward to a page with 'test' in it
+ content.document.getElementById("data-link-1").click();
+ });
+ yield loadPromise;
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1b, Should have a click-to-play notification");
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 1b, plugin should not be activated");
+
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // Simulate clicking the "Allow Always" button.
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 1b, plugin should be activated");
+});
+
+// Test that the click-to-play notification doesn't break when navigating
+// to data URLs with multiple plugins.
+add_task(function* () {
+ // We click activated above
+ clearAllPluginPermissions();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 2a, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 2a, plugin should not be activated");
+
+ let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ // navigate forward to a page with 'test1' & 'test2' in it
+ content.document.getElementById("data-link-2").click();
+ });
+ yield loadPromise;
+
+ // Work around for delayed PluginBindingAttached
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test1").clientTop;
+ content.document.getElementById("test2").clientTop;
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test1");
+ ok(!pluginInfo.activated, "Test 2a, test1 should not be activated");
+ pluginInfo = yield promiseForPluginInfo("test2");
+ ok(!pluginInfo.activated, "Test 2a, test2 should not be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 2b, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ // Simulate choosing "Allow now" for the test plugin
+ is(notification.options.pluginData.size, 2, "Test 2b, Should have two types of plugin in the notification");
+
+ let centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 2b, found center action for the Test plugin");
+
+ let centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 2b, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 2b, found center item for the Test plugin");
+
+ // "click" the button to activate the Test plugin
+ centerItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test1");
+ ok(pluginInfo.activated, "Test 2b, plugin should be activated");
+});
+
+add_task(function* () {
+ // We click activated above
+ clearAllPluginPermissions();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_data_url.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+});
+
+// Test that when navigating to a data url, the plugin permission is inherited
+add_task(function* () {
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 3a, Should have a click-to-play notification");
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 3a, plugin should not be activated");
+
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // Simulate clicking the "Allow Always" button.
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 3a, plugin should be activated");
+
+ let loadPromise = promiseTabLoadEvent(gBrowser.selectedTab);
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ // navigate forward to a page with 'test' in it
+ content.document.getElementById("data-link-1").click();
+ });
+ yield loadPromise;
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 3b, plugin should be activated");
+
+ clearAllPluginPermissions();
+});
+
+// Test that the click-to-play doorhanger still works
+// when directly navigating to data URLs.
+// Fails, bug XXX. Plugins plus a data url don't fire a load event.
+/*
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab,
+ "data:text/html,Hi!<embed id='test' style='width:200px; height:200px' type='application/x-test'/>");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 4a, Should have a click-to-play notification");
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 4a, plugin should not be activated");
+
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // Simulate clicking the "Allow Always" button.
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 4a, plugin should be activated");
+});
+*/
diff --git a/browser/base/content/test/plugins/browser_CTP_drag_drop.js b/browser/base/content/test/plugins/browser_CTP_drag_drop.js
new file mode 100644
index 000000000..7c9858e27
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_drag_drop.js
@@ -0,0 +1,96 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gNewWindow = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gNewWindow.close();
+ gNewWindow = null;
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+add_task(function* () {
+ gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+
+ // XXX technically can't load fire before we get this call???
+ yield waitForEvent(gNewWindow, "load", null, true);
+
+ yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
+ ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, gNewWindow.gBrowser.selectedTab);
+
+ yield promisePopupNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab again");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+});
+
+add_task(function* () {
+ yield promisePopupNotification("click-to-play-plugins");
+
+ gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+
+ yield promiseWaitForFocus(gNewWindow);
+
+ yield promisePopupNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+});
+
+add_task(function* () {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
+ ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
+
+ let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser);
+ ok(!pluginInfo.activated, "plugin should not be activated");
+
+ yield ContentTask.spawn(gNewWindow.gBrowser.selectedBrowser, {}, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed && gNewWindow.PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+});
+
+add_task(function* () {
+ // Click the activate button on doorhanger to make sure it works
+ gNewWindow.PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let pluginInfo = yield promiseForPluginInfo("test", gNewWindow.gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_hide_overlay.js b/browser/base/content/test/plugins/browser_CTP_hide_overlay.js
new file mode 100644
index 000000000..5fab7f6ed
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_hide_overlay.js
@@ -0,0 +1,88 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ // Tests that the overlay can be hidden for plugins using the close icon.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon")
+ let bounds = closeIcon.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+
+ Assert.ok(!overlay.classList.contains("visible"), "overlay should be hidden.");
+ });
+});
+
+// Test that the overlay cannot be interacted with after the user closes the overlay
+add_task(function* () {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon")
+ let closeIconBounds = closeIcon.getBoundingClientRect();
+ let overlayBounds = overlay.getBoundingClientRect();
+ let overlayLeft = (overlayBounds.left + overlayBounds.right) / 2;
+ let overlayTop = (overlayBounds.left + overlayBounds.right) / 2 ;
+ let closeIconLeft = (closeIconBounds.left + closeIconBounds.right) / 2;
+ let closeIconTop = (closeIconBounds.top + closeIconBounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ // Simulate clicking on the close icon.
+ utils.sendMouseEvent("mousedown", closeIconLeft, closeIconTop, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", closeIconLeft, closeIconTop, 0, 1, 0, false, 0, 0);
+
+ // Simulate clicking on the overlay.
+ utils.sendMouseEvent("mousedown", overlayLeft, overlayTop, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", overlayLeft, overlayTop, 0, 1, 0, false, 0, 0);
+
+ Assert.ok(overlay.hasAttribute("dismissed") && !overlay.classList.contains("visible"),
+ "Overlay should be hidden");
+ });
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+
+ ok(notification.dismissed, "No notification should be shown");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_iframe.js b/browser/base/content/test/plugins/browser_CTP_iframe.js
new file mode 100644
index 000000000..58565559f
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_iframe.js
@@ -0,0 +1,48 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_iframe.html");
+
+ // Tests that the overlays are visible and actionable if the plugin is in an iframe.
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ let frame = content.document.getElementById("frame");
+ let doc = frame.contentDocument;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(plugin && overlay.classList.contains("visible"),
+ "Test 1, Plugin overlay should exist, not be hidden");
+
+ let closeIcon = doc.getAnonymousElementByAttribute(plugin, "anonid", "closeIcon");
+ let bounds = closeIcon.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = doc.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ Assert.ok(!overlay.classList.contains("visible"),
+ "Test 1, Plugin overlay should exist, be hidden");
+ });
+});
+
diff --git a/browser/base/content/test/plugins/browser_CTP_multi_allow.js b/browser/base/content/test/plugins/browser_CTP_multi_allow.js
new file mode 100644
index 000000000..7bc6aaabf
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_multi_allow.js
@@ -0,0 +1,99 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ // Test that the click-to-play doorhanger for multiple plugins shows the correct
+ // state when re-opening without reloads or navigation.
+
+ let pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(!pluginInfo.activated, "plugin should be activated");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(notification, "Test 1a, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 2,
+ "Test 1a, Should have two types of plugin in the notification");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ is(PopupNotifications.panel.firstChild.childNodes.length, 2, "have child nodes");
+
+ let pluginItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 1a, all plugins should start out blocked");
+ if (item.action.pluginName == "Test") {
+ pluginItem = item;
+ }
+ }
+
+ // Choose "Allow now" for the test plugin
+ pluginItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test", gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(notification, "Test 1b, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ pluginItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action.pluginName == "Test") {
+ is(item.value, "allownow", "Test 1b, Test plugin should now be set to 'Allow now'");
+ } else {
+ is(item.value, "block", "Test 1b, Second Test plugin should still be blocked");
+ pluginItem = item;
+ }
+ }
+
+ // Choose "Allow and remember" for the Second Test plugin
+ pluginItem.value = "allowalways";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("secondtestA", gBrowser.selectedBrowser);
+ ok(pluginInfo.activated, "plugin should be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(notification, "Test 1c, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action.pluginName == "Test") {
+ is(item.value, "allownow", "Test 1c, Test plugin should be set to 'Allow now'");
+ } else {
+ is(item.value, "allowalways", "Test 1c, Second Test plugin should be set to 'Allow always'");
+ }
+ }
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_nonplugins.js b/browser/base/content/test/plugins/browser_CTP_nonplugins.js
new file mode 100644
index 000000000..cdef44d9d
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_nonplugins.js
@@ -0,0 +1,58 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_DISABLED, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+
+ // Test that the click-to-play notification is not shown for non-plugin object elements
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+ let pluginRemovedPromise = waitForEvent(gBrowser.selectedBrowser, "PluginRemoved", null, true, true);
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("secondtestA");
+ plugin.parentNode.removeChild(plugin);
+ plugin = content.document.getElementById("secondtestB");
+ plugin.parentNode.removeChild(plugin);
+
+ let image = content.document.createElement("object");
+ image.type = "image/png";
+ image.data = "moz.png";
+ content.document.body.appendChild(image);
+ });
+ yield pluginRemovedPromise;
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 2, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.parentNode.removeChild(plugin);
+ });
+
+ popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ ok(popupNotification, "Test 3, Should still have a click-to-play notification");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_notificationBar.js b/browser/base/content/test/plugins/browser_CTP_notificationBar.js
new file mode 100644
index 000000000..3c7bd911c
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_notificationBar.js
@@ -0,0 +1,151 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ // Expecting a notification bar for hidden plugins
+ yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+});
+
+add_task(function* () {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+ yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null);
+});
+
+add_task(function* () {
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Expecting a plugin notification bar when plugins are overlaid.
+ yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlayed.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 3b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 1a, plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 3b, overlay should be hidden.");
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_positioned.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Expecting a plugin notification bar when plugins are overlaid offscreen.
+ yield promisePopupNotification("click-to-play-plugins");
+ yield promiseForNotificationBar("plugin-hidden", gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 4b, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 4b, overlay should be hidden.");
+ });
+});
+
+// Test that the notification bar is getting dismissed when directly activating plugins
+// via the doorhanger.
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_small.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Expecting a plugin notification bar when plugins are overlaid offscreen.
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.equal(plugin.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 6, Plugin should be click-to-play");
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 6, Should have a click-to-play notification");
+
+ // simulate "always allow"
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+ yield promiseForCondition(() => notificationBox.getNotificationWithValue("plugin-hidden") === null);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 7, plugin should be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
new file mode 100644
index 000000000..ccb4d11d7
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js
@@ -0,0 +1,120 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
+});
+
+// Test that the click-to-play overlay is not hidden for elements
+// partially or fully outside the viewport.
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let p = doc.createElement('embed');
+
+ p.setAttribute('id', 'test');
+ p.setAttribute('type', 'application/x-test');
+ p.style.left = "0";
+ p.style.bottom = "200px";
+
+ doc.getElementById('container').appendChild(p);
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ let doc = content.document;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 2, overlay should be visible.");
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let p = doc.createElement('embed');
+
+ p.setAttribute('id', 'test');
+ p.setAttribute('type', 'application/x-test');
+ p.style.left = "0";
+ p.style.bottom = "-410px";
+
+ doc.getElementById('container').appendChild(p);
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let doc = content.document;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 3, overlay should be visible.");
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_outsideScrollArea.html");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let p = doc.createElement('embed');
+
+ p.setAttribute('id', 'test');
+ p.setAttribute('type', 'application/x-test');
+ p.style.left = "-600px";
+ p.style.bottom = "0";
+
+ doc.getElementById('container').appendChild(p);
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let doc = content.document;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 4, overlay should be hidden.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_remove_navigate.js b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
new file mode 100644
index 000000000..8ee1c5b5a
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_remove_navigate.js
@@ -0,0 +1,79 @@
+const gTestRoot = getRootDirectory(gTestPath);
+const gHttpTestRoot = gTestRoot.replace("chrome://mochitests/content/",
+ "http://127.0.0.1:8888/");
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page, that we don't show the hidden plugin
+ * notification bar on the new page.
+ */
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ // Load up a page with a plugin...
+ let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html");
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+ yield notificationPromise;
+
+ // Trigger the PluginRemoved event to be fired, and then immediately
+ // browse to a new page.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.remove();
+ });
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "about:mozilla");
+
+ // There should be no hidden plugin notification bar at about:mozilla.
+ let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
+ is(notificationBox.getNotificationWithValue("plugin-hidden"), null,
+ "Expected no notification box");
+});
+
+/**
+ * Tests that if a plugin is removed just as we transition to
+ * a different page with a plugin, that we show the right notification
+ * for the new page.
+ */
+add_task(function* () {
+ // Load up a page with a plugin...
+ let notificationPromise = waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small.html");
+ yield promiseUpdatePluginBindings(gBrowser.selectedBrowser);
+ yield notificationPromise;
+
+ // Trigger the PluginRemoved event to be fired, and then immediately
+ // browse to a new page.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.remove();
+ });
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gHttpTestRoot + "plugin_small_2.html");
+ let notification = yield waitForNotificationBar("plugin-hidden", gBrowser.selectedBrowser);
+ ok(notification, "There should be a notification shown for the new page.");
+ // Ensure that the notification is showing information about
+ // the x-second-test plugin.
+ let label = notification.label;
+ ok(label.includes("Second Test"), "Should mention the second plugin");
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_resize.js b/browser/base/content/test/plugins/browser_CTP_resize.js
new file mode 100644
index 000000000..9b2a2cd82
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_resize.js
@@ -0,0 +1,130 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
+
+ yield promiseTabLoadEvent(newTab, gTestRoot + "plugin_small.html"); // 10x10 plugin
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+// Test that the overlay is hidden for "small" plugin elements and is shown
+// once they are resized to a size that can hold the overlay
+add_task(function* () {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 2, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 2, overlay should be hidden.");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.width = "300px";
+ });
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 3, overlay should be hidden.");
+ });
+});
+
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.height = "300px";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test").clientTop;
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 4, overlay should be visible.");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.width = "10px";
+ plugin.style.height = "10px";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test").clientTop;
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!(overlay && overlay.classList.contains("visible")),
+ "Test 5, overlay should be hidden.");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ plugin.style.height = "300px";
+ plugin.style.width = "300px";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ content.document.getElementById("test").clientTop;
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 6, overlay should be visible.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_CTP_zoom.js b/browser/base/content/test/plugins/browser_CTP_zoom.js
new file mode 100644
index 000000000..8b353232d
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_CTP_zoom.js
@@ -0,0 +1,62 @@
+"use strict";
+
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ FullZoom.reset(); // must be called before closing the tab we zoomed!
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 1, Should not have a click-to-play notification");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+// Enlarges the zoom level 4 times and tests that the overlay is
+// visible after each enlargement.
+add_task(function* () {
+ for (let count = 0; count < 4; count++) {
+
+ FullZoom.enlarge();
+
+ // Reload the page
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_zoom.html");
+ yield promiseUpdatePluginBindings(gTestBrowser);
+ yield ContentTask.spawn(gTestBrowser, { count }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Overlay should be visible for zoom change count " + args.count);
+ });
+ }
+});
+
+
diff --git a/browser/base/content/test/plugins/browser_blocking.js b/browser/base/content/test/plugins/browser_blocking.js
new file mode 100644
index 000000000..334ed9f2e
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_blocking.js
@@ -0,0 +1,349 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+function updateAllTestPlugins(aState) {
+ setTestPluginEnabledState(aState, "Test Plug-in");
+ setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED);
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ // Prime the content process
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+
+ // Make sure the blocklist service(s) are running
+ Components.classes["@mozilla.org/extensions/blocklist;1"]
+ .getService(Components.interfaces.nsIBlocklistService);
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+});
+
+add_task(function* () {
+ // enable hard blocklisting for the next test
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+ ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default");
+
+ yield promiseForNotificationShown(notification);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED");
+
+ is(notification.options.pluginData.size, 1, "Test 5: Only the blocked plugin should be present in the notification");
+ ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible.");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests a vulnerable, updatable plugin
+
+add_task(function* () {
+ // enable hard blocklisting of test
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+ "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!pluginInfo.activated, "Test 18a, Plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 18a, Plugin overlay should exist, not be hidden");
+
+ let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ Assert.ok(updateLink.style.visibility != "hidden",
+ "Test 18a, Plugin should have an update link");
+ });
+
+ let promise = waitForEvent(gBrowser.tabContainer, "TabOpen", null, true);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ let bounds = updateLink.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ promise = waitForEvent(gBrowser.tabContainer, "TabClose", null, true);
+ gBrowser.removeCurrentTab();
+ yield promise;
+});
+
+add_task(function* () {
+ // clicking the update link should not activate the plugin
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+ "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!pluginInfo.activated, "Test 18b, Plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 18b, Plugin overlay should exist, not be hidden");
+ });
+});
+
+// Tests a vulnerable plugin with no update
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableNoUpdate.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 18c, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE,
+ "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
+ ok(!pluginInfo.activated, "Test 18c, Plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(overlay && overlay.classList.contains("visible"),
+ "Test 18c, Plugin overlay should exist, not be hidden");
+
+ let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ Assert.ok(updateLink && updateLink.style.display != "block",
+ "Test 18c, Plugin should not have an update link");
+ });
+
+ // check that click "Always allow" works with blocked plugins
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE,
+ "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
+ ok(pluginInfo.activated, "Test 18c, Plugin should be activated");
+ let enabledState = getTestPluginEnabledState();
+ ok(enabledState, "Test 18c, Plugin enabled state should be STATE_CLICKTOPLAY");
+});
+
+// continue testing "Always allow", make sure it sticks.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 18d, Waited too long for plugin to activate");
+
+ clearAllPluginPermissions();
+});
+
+// clicking the in-content overlay of a vulnerable plugin should bring
+// up the notification and not directly activate the plugin
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 18f, Should have a click-to-play notification");
+ ok(notification.dismissed, "Test 18f, notification should start dismissed");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18f, Waited too long for plugin to activate");
+
+ var oldEventCallback = notification.options.eventCallback;
+ let promise = promiseForCondition(() => oldEventCallback == null);
+ notification.options.eventCallback = function() {
+ if (oldEventCallback) {
+ oldEventCallback();
+ }
+ oldEventCallback = null;
+ };
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ ok(notification, "Test 18g, Should have a click-to-play notification");
+ ok(!notification.dismissed, "Test 18g, notification should be open");
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+});
+
+// Test that "always allow"-ing a plugin will not allow it when it becomes
+// blocklisted.
+add_task(function* () {
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 24a, Should have a click-to-play notification");
+
+ // Plugin should start as CTP
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 24a, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ ok(!pluginInfo.activated, "Test 24a, Plugin should not be active.");
+
+ // simulate "always allow"
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 24a, Plugin should be active.");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+});
+
+// the plugin is now blocklisted, so it should not automatically load
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 24b, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE,
+ "Test 24b, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!pluginInfo.activated, "Test 24b, Plugin should not be active.");
+
+ // simulate "always allow"
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 24b, Plugin should be active.");
+
+ clearAllPluginPermissions();
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Plugin sync removal test. Note this test produces a notification drop down since
+// the plugin we add has zero dims.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html");
+
+ // Maybe there some better trick here, we need to wait for the page load, then
+ // wait for the js to execute in the page.
+ yield waitForMs(500);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+ ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
+ ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+});
+
+// Tests a page with a blocked plugin in it and make sure the infoURL property
+// the blocklist file gets used.
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+
+ // Since the plugin notification is dismissed by default, reshow it.
+ yield promiseForNotificationShown(notification);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED,
+ "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(!objLoadingContent.activated, "Plugin should not be activated.");
+ });
+
+ const testUrl = "http://test.url.com/";
+
+ let firstPanelChild = PopupNotifications.panel.firstChild;
+ let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid",
+ "click-to-play-plugins-notification-link");
+ is(infoLink.href, testUrl,
+ "Test 26, the notification URL needs to match the infoURL from the blocklist file.");
+});
+
diff --git a/browser/base/content/test/plugins/browser_blocklist_content.js b/browser/base/content/test/plugins/browser_blocklist_content.js
new file mode 100644
index 000000000..bf4e159bc
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_blocklist_content.js
@@ -0,0 +1,104 @@
+var gTestBrowser = null;
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gChromeRoot = getRootDirectory(gTestPath);
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ Assert.ok(test.activated, "task 1a: test plugin should be activated!");
+ });
+});
+
+// Load a fresh page, load a new plugin blocklist, then load the same page again.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ ok(!test.activated, "task 2a: test plugin shouldn't activate!");
+ });
+});
+
+// Unload the block list and lets do this again, only this time lets
+// hack around in the content blocklist service maliciously.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ // Hack the planet! Load our blocklist shim, so we can mess with blocklist
+ // return results in the content process. Active until we close our tab.
+ let mm = gTestBrowser.messageManager;
+ info("test 3a: loading " + gChromeRoot + "blocklist_proxy.js" + "\n");
+ mm.loadFrameScript(gChromeRoot + "blocklist_proxy.js", true);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ Assert.ok(test.activated, "task 3a: test plugin should be activated!");
+ });
+});
+
+// Load a fresh page, load a new plugin blocklist, then load the same page again.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>GO!</html>");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginHard.xml", gTestBrowser);
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let test = content.document.getElementById("test");
+ Assert.ok(!test.activated, "task 4a: test plugin shouldn't activate!");
+ });
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
diff --git a/browser/base/content/test/plugins/browser_bug743421.js b/browser/base/content/test/plugins/browser_bug743421.js
new file mode 100644
index 000000000..966e7b012
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug743421.js
@@ -0,0 +1,119 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+add_task(function* () {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests that navigation within the page and the window.history API doesn't break click-to-play state.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 1a, Should not have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[0];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+ });
+
+ // Click the activate button on doorhanger to make sure it works
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[0];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1b, Plugin should be activated");
+ });
+});
+
+add_task(function* () {
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 1c, Should still have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ let plugin = content.document.getElementsByTagName("embed")[1];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated,
+ "Test 1c, Newly inserted plugin in activated page should be activated");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[1];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1d, Plugin should be activated");
+
+ let promise = ContentTaskUtils.waitForEvent(content, "hashchange");
+ content.location += "#anchorNavigation";
+ yield promise;
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ let plugin = content.document.getElementsByTagName("embed")[2];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1e, Plugin should be activated");
+ });
+});
+
+add_task(function* () {
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementsByTagName("embed")[2];
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1f, Plugin should be activated");
+
+ content.history.replaceState({}, "", "replacedState");
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin());
+ plugin = content.document.getElementsByTagName("embed")[3];
+ objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ Assert.ok(objLoadingContent.activated, "Test 1g, Plugin should be activated");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug744745.js b/browser/base/content/test/plugins/browser_bug744745.js
new file mode 100644
index 000000000..c9f552a4e
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug744745.js
@@ -0,0 +1,50 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gNumPluginBindingsAttached = 0;
+
+function pluginBindingAttached() {
+ gNumPluginBindingsAttached++;
+ if (gNumPluginBindingsAttached != 1) {
+ ok(false, "if we've gotten here, something is quite wrong");
+ }
+}
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+
+ let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug744745.html");
+
+ yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; });
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("test");
+ if (!plugin) {
+ Assert.ok(false, "plugin element not available.");
+ return;
+ }
+ // We can't use MochiKit's routine
+ let style = content.getComputedStyle(plugin);
+ Assert.ok(("opacity" in style) && style.opacity == 1, "plugin style properly configured.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug787619.js b/browser/base/content/test/plugins/browser_bug787619.js
new file mode 100644
index 000000000..bfd52258c
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug787619.js
@@ -0,0 +1,65 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gWrapperClickCount = 0;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ let testRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testRoot + "plugin_bug787619.html");
+
+ // Due to layout being async, "PluginBindAttached" may trigger later.
+ // This forces a layout flush, thus triggering it, and schedules the
+ // test so it is definitely executed afterwards.
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // check plugin state
+ let pluginInfo = yield promiseForPluginInfo("plugin");
+ ok(!pluginInfo.activated, "1a plugin should not be activated");
+
+ // click the overlay to prompt
+ let promise = promisePopupNotification("click-to-play-plugins");
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ let plugin = content.document.getElementById("plugin");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+ yield promise;
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("plugin");
+ ok(!pluginInfo.activated, "1b plugin should not be activated");
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ // check plugin state
+ pluginInfo = yield promiseForPluginInfo("plugin");
+ ok(pluginInfo.activated, "plugin should be activated");
+
+ is(gWrapperClickCount, 0, 'wrapper should not have received any clicks');
+});
diff --git a/browser/base/content/test/plugins/browser_bug797677.js b/browser/base/content/test/plugins/browser_bug797677.js
new file mode 100644
index 000000000..1ae9f5047
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug797677.js
@@ -0,0 +1,43 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gConsoleErrors = 0;
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ consoleService.unregisterListener(errorListener);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ let consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+ let errorListener = {
+ observe: function(aMessage) {
+ if (aMessage.message.includes("NS_ERROR_FAILURE"))
+ gConsoleErrors++;
+ }
+ };
+ consoleService.registerListener(errorListener);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug797677.html");
+
+ let pluginInfo = yield promiseForPluginInfo("plugin");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "plugin should not have been found.");
+
+ // simple cpows
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ let plugin = content.document.getElementById("plugin");
+ ok(plugin, "plugin should be in the page");
+ });
+ is(gConsoleErrors, 0, "should have no console errors");
+});
diff --git a/browser/base/content/test/plugins/browser_bug812562.js b/browser/base/content/test/plugins/browser_bug812562.js
new file mode 100644
index 000000000..be7b00b22
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug812562.js
@@ -0,0 +1,80 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+});
+
+// Tests that the going back will reshow the notification for click-to-play
+// blocklisted plugins
+add_task(function* () {
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginVulnerableUpdatable.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "test part 1: Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ Assert.ok(!!content.document.getElementById("test"),
+ "test part 1: plugin should not be activated");
+ });
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+});
+
+add_task(function* () {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "test part 2: Should not have a click-to-play notification");
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ Assert.ok(!content.document.getElementById("test"),
+ "test part 2: plugin should not be activated");
+ });
+
+ let obsPromise = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
+ let overlayPromise = promisePopupNotification("click-to-play-plugins");
+ gTestBrowser.goBack();
+ yield obsPromise;
+ yield overlayPromise;
+});
+
+add_task(function* () {
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "test part 3: Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "plugin should be marked as VULNERABLE");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ Assert.ok(!!content.document.getElementById("test"),
+ "test part 3: plugin should not be activated");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug818118.js b/browser/base/content/test/plugins/browser_bug818118.js
new file mode 100644
index 000000000..9dd6e22e7
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug818118.js
@@ -0,0 +1,40 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_both.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin should be click to play");
+ ok(!pluginInfo.activated, "plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, () => {
+ let unknown = content.document.getElementById("unknown");
+ ok(unknown, "should have unknown plugin in page");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_bug820497.js b/browser/base/content/test/plugins/browser_bug820497.js
new file mode 100644
index 000000000..b2e0f5268
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_bug820497.js
@@ -0,0 +1,71 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gNumPluginBindingsAttached = 0;
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+
+ gTestBrowser.addEventListener("PluginBindingAttached", function () { gNumPluginBindingsAttached++ }, true, true);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug820497.html");
+
+ yield promiseForCondition(function () { return gNumPluginBindingsAttached == 1; });
+
+ yield ContentTask.spawn(gTestBrowser, null, () => {
+ // Note we add the second plugin in the code farther down, so there's
+ // no way we got here with anything but one plugin loaded.
+ let doc = content.document;
+ let testplugin = doc.getElementById("test");
+ ok(testplugin, "should have test plugin");
+ let secondtestplugin = doc.getElementById("secondtest");
+ ok(!secondtestplugin, "should not yet have second test plugin");
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 1, "should be 1 type of plugin in the popup notification");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ XPCNativeWrapper.unwrap(content).addSecondPlugin();
+ });
+
+ yield promiseForCondition(function () { return gNumPluginBindingsAttached == 2; });
+
+ yield ContentTask.spawn(gTestBrowser, null, () => {
+ let doc = content.document;
+ let testplugin = doc.getElementById("test");
+ ok(testplugin, "should have test plugin");
+ let secondtestplugin = doc.getElementById("secondtest");
+ ok(secondtestplugin, "should have second test plugin");
+ });
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ ok(notification, "should have popup notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 2, "aited too long for 2 types of plugins in popup notification");
+});
diff --git a/browser/base/content/test/plugins/browser_clearplugindata.html b/browser/base/content/test/plugins/browser_clearplugindata.html
new file mode 100644
index 000000000..243350ba4
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_clearplugindata.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <title>Plugin Clear Site Data sanitize test</title>
+
+ <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+ <script type="application/javascript">
+ function testSteps()
+ {
+ // Make sure clearing by timerange is supported.
+ var p = document.getElementById("plugin1");
+ p.setSitesWithDataCapabilities(true);
+
+ p.setSitesWithData(
+ "foo.com:0:5," +
+ "bar.com:0:100," +
+ "baz.com:1:5," +
+ "qux.com:1:100"
+ );
+ }
+ </script>
+ </head>
+
+ <body onload="testSteps();"></body>
+
+</html>
diff --git a/browser/base/content/test/plugins/browser_clearplugindata.js b/browser/base/content/test/plugins/browser_clearplugindata.js
new file mode 100644
index 000000000..69d474fed
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_clearplugindata.js
@@ -0,0 +1,127 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+// Test clearing plugin data using sanitize.js.
+const testURL1 = gTestRoot + "browser_clearplugindata.html";
+const testURL2 = gTestRoot + "browser_clearplugindata_noage.html";
+
+var tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+var Sanitizer = tempScope.Sanitizer;
+
+const pluginHostIface = Ci.nsIPluginHost;
+var pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+pluginHost.QueryInterface(pluginHostIface);
+
+var pluginTag = getTestPlugin();
+var sanitizer = null;
+
+function stored(needles) {
+ let something = pluginHost.siteHasData(this.pluginTag, null);
+ if (!needles)
+ return something;
+
+ if (!something)
+ return false;
+
+ for (let i = 0; i < needles.length; ++i) {
+ if (!pluginHost.siteHasData(this.pluginTag, needles[i]))
+ return false;
+ }
+ return true;
+}
+
+add_task(function* () {
+ registerCleanupFunction(function () {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ if (gTestBrowser) {
+ gBrowser.removeCurrentTab();
+ }
+ window.focus();
+ gTestBrowser = null;
+ });
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+
+ sanitizer = new Sanitizer();
+ sanitizer.ignoreTimespan = false;
+ sanitizer.prefDomain = "privacy.cpd.";
+ let itemPrefs = gPrefService.getBranch(sanitizer.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", true); // plugin data
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", false);
+});
+
+add_task(function* () {
+ // Load page to set data for the plugin.
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testURL1);
+
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
+ "Data stored for sites");
+
+ // Clear 20 seconds ago
+ let now_uSec = Date.now() * 1000;
+ sanitizer.range = [now_uSec - 20*1000000, now_uSec];
+ yield sanitizer.sanitize();
+
+ ok(stored(["bar.com", "qux.com"]), "Data stored for sites");
+ ok(!stored(["foo.com"]), "Data cleared for foo.com");
+ ok(!stored(["baz.com"]), "Data cleared for baz.com");
+
+ // Clear everything
+ sanitizer.range = null;
+ yield sanitizer.sanitize();
+
+ ok(!stored(null), "All data cleared");
+
+ gBrowser.removeCurrentTab();
+ gTestBrowser = null;
+});
+
+add_task(function* () {
+ // Load page to set data for the plugin.
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, testURL2);
+
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
+ "Data stored for sites");
+
+ // Attempt to clear 20 seconds ago. The plugin will throw
+ // NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, which should result in us
+ // clearing all data regardless of age.
+ let now_uSec = Date.now() * 1000;
+ sanitizer.range = [now_uSec - 20*1000000, now_uSec];
+ yield sanitizer.sanitize();
+
+ ok(!stored(null), "All data cleared");
+
+ gBrowser.removeCurrentTab();
+ gTestBrowser = null;
+});
+
diff --git a/browser/base/content/test/plugins/browser_clearplugindata_noage.html b/browser/base/content/test/plugins/browser_clearplugindata_noage.html
new file mode 100644
index 000000000..820979541
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_clearplugindata_noage.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <title>Plugin Clear Site Data sanitize test without age</title>
+
+ <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+ <script type="application/javascript">
+ function testSteps()
+ {
+ // Make sure clearing by timerange is disabled.
+ var p = document.getElementById("plugin1");
+ p.setSitesWithDataCapabilities(false);
+
+ p.setSitesWithData(
+ "foo.com:0:5," +
+ "bar.com:0:100," +
+ "baz.com:1:5," +
+ "qux.com:1:100"
+ );
+ }
+ </script>
+ </head>
+
+ <body onload="testSteps();"></body>
+
+</html>
diff --git a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
new file mode 100644
index 000000000..bdca32e70
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
@@ -0,0 +1,34 @@
+/**
+ * Test that the notification bar for crashed GMPs works.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, function* (browser) {
+ yield ContentTask.spawn(browser, null, function* () {
+ const GMP_CRASH_EVENT = {
+ pluginID: 1,
+ pluginName: "GlobalTestPlugin",
+ submittedCrashReport: false,
+ bubbles: true,
+ cancelable: true,
+ gmpPlugin: true,
+ };
+
+ let crashEvent = new content.PluginCrashedEvent("PluginCrashed",
+ GMP_CRASH_EVENT);
+ content.dispatchEvent(crashEvent);
+ });
+
+ let notification = yield waitForNotificationBar("plugin-crashed", browser);
+
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ ok(notification, "Infobar was shown.");
+ is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM,
+ "Correct priority.");
+ is(notification.getAttribute("label"),
+ "The GlobalTestPlugin plugin has crashed.",
+ "Correct message.");
+ });
+});
diff --git a/browser/base/content/test/plugins/browser_pageInfo_plugins.js b/browser/base/content/test/plugins/browser_pageInfo_plugins.js
new file mode 100644
index 000000000..0d941e0fa
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pageInfo_plugins.js
@@ -0,0 +1,191 @@
+var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPageInfo = null;
+var gNextTest = null;
+var gTestBrowser = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+ .getService(Components.interfaces.nsIPluginHost);
+var gPermissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+var gTestPermissionString = gPluginHost.getPermissionStringForType("application/x-test");
+var gSecondTestPermissionString = gPluginHost.getPermissionStringForType("application/x-second-test");
+
+function doOnPageLoad(url, continuation) {
+ gNextTest = continuation;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.contentWindow.location = url;
+}
+
+function pageLoad() {
+ gTestBrowser.removeEventListener("load", pageLoad);
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function doOnOpenPageInfo(continuation) {
+ Services.obs.addObserver(pageInfoObserve, "page-info-dialog-loaded", false);
+ gNextTest = continuation;
+ // An explanation: it looks like the test harness complains about leaked
+ // windows if we don't keep a reference to every window we've opened.
+ // So, don't reuse pointers to opened Page Info windows - simply append
+ // to this list.
+ gPageInfo = BrowserPageInfo(null, "permTab");
+}
+
+function pageInfoObserve(win, topic, data) {
+ Services.obs.removeObserver(pageInfoObserve, "page-info-dialog-loaded");
+ gPageInfo.onFinished.push(() => executeSoon(gNextTest));
+}
+
+function finishTest() {
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString);
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString);
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ gBrowser.removeCurrentTab();
+
+ gPageInfo = null;
+ gNextTest = null;
+ gTestBrowser = null;
+ gPluginHost = null;
+ gPermissionManager = null;
+
+ executeSoon(finish);
+}
+
+function test() {
+ waitForExplicitFinish();
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString);
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString);
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart1a);
+}
+
+// The first test plugin is CtP and the second test plugin is enabled.
+function testPart1a() {
+ let test = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "part 1a: Test plugin should not be activated");
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA");
+ objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "part 1a: Second Test plugin should be activated");
+
+ doOnOpenPageInfo(testPart1b);
+}
+
+function testPart1b() {
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0");
+
+ is(testRadioGroup.selectedItem, testRadioDefault, "part 1b: Test radio group should be set to 'Default'");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ testRadioGroup.selectedItem = testRadioAllow;
+ testRadioAllow.doCommand();
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 1b: Second Test radio group should be set to 'Default'");
+ let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3");
+ secondtestRadioGroup.selectedItem = secondtestRadioAsk;
+ secondtestRadioAsk.doCommand();
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart2);
+}
+
+// Now, the Test plugin should be allowed, and the Test2 plugin should be CtP
+function testPart2() {
+ let test = gTestBrowser.contentDocument.getElementById("test").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(test.activated, "part 2: Test plugin should be activated");
+
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!secondtest.activated, "part 2: Second Test plugin should not be activated");
+ is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "part 2: Second test plugin should be click-to-play.");
+
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 2: Test radio group should be set to 'Allow'");
+ let testRadioBlock = gPageInfo.document.getElementById(gTestPermissionString + "#2");
+ testRadioGroup.selectedItem = testRadioBlock;
+ testRadioBlock.doCommand();
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAsk, "part 2: Second Test radio group should be set to 'Always Ask'");
+ let secondtestRadioBlock = gPageInfo.document.getElementById(gSecondTestPermissionString + "#2");
+ secondtestRadioGroup.selectedItem = secondtestRadioBlock;
+ secondtestRadioBlock.doCommand();
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart3);
+}
+
+// Now, all the things should be blocked
+function testPart3() {
+ let test = gTestBrowser.contentDocument.getElementById("test").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!test.activated, "part 3: Test plugin should not be activated");
+ is(test.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED,
+ "part 3: Test plugin should be marked as PLUGIN_DISABLED");
+
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+
+ ok(!secondtest.activated, "part 3: Second Test plugin should not be activated");
+ is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED,
+ "part 3: Second test plugin should be marked as PLUGIN_DISABLED");
+
+ // reset permissions
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gTestPermissionString);
+ gPermissionManager.remove(makeURI("http://127.0.0.1:8888/"), gSecondTestPermissionString);
+ // check that changing the permissions affects the radio state in the
+ // open Page Info window
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0");
+ is(testRadioGroup.selectedItem, testRadioDefault, "part 3: Test radio group should be set to 'Default'");
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 3: Second Test radio group should be set to 'Default'");
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart4a);
+}
+
+// Now test that setting permission directly (as from the popup notification)
+// immediately influences Page Info.
+function testPart4a() {
+ // simulate "allow" from the doorhanger
+ gPermissionManager.add(gTestBrowser.currentURI, gTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION);
+ gPermissionManager.add(gTestBrowser.currentURI, gSecondTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ // check (again) that changing the permissions affects the radio state in the
+ // open Page Info window
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 4a: Test radio group should be set to 'Allow'");
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4a: Second Test radio group should be set to 'Always Allow'");
+
+ // now close Page Info and see that it opens with the right settings
+ gPageInfo.close();
+ doOnOpenPageInfo(testPart4b);
+}
+
+// check that "always allow" resulted in the radio buttons getting set to allow
+function testPart4b() {
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 4b: Test radio group should be set to 'Allow'");
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4b: Second Test radio group should be set to 'Allow'");
+
+ Services.prefs.setBoolPref("plugins.click_to_play", false);
+ gPageInfo.close();
+ finishTest();
+}
diff --git a/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js
new file mode 100644
index 000000000..ab4743f6f
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pluginCrashCommentAndURL.js
@@ -0,0 +1,207 @@
+Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var config = {};
+
+add_task(function* () {
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
+ // crash reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Components.classes["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverUrl = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ // Crash immediately
+ Services.prefs.setIntPref("dom.ipc.plugins.timeoutSecs", 0);
+
+ registerCleanupFunction(Task.async(function*() {
+ Services.prefs.clearUserPref("dom.ipc.plugins.timeoutSecs");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverUrl);
+ env = null;
+ config = null;
+ gTestBrowser = null;
+ gBrowser.removeCurrentTab();
+ window.focus();
+ }));
+});
+
+add_task(function* () {
+ config = {
+ shouldSubmissionUIBeVisible: true,
+ comment: "",
+ urlOptIn: false
+ };
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ let pluginCrashed = promisePluginCrashed();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Wait for the plugin to crash
+ yield pluginCrashed;
+
+ let crashReportStatus = TestUtils.topicObserved("crash-report-status", onSubmitStatus);
+
+ // Test that the crash submission UI is actually visible and submit the crash report.
+ yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) {
+ let doc = content.document;
+ let plugin = doc.getElementById("plugin");
+ let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit");
+ let submitButton = doc.getAnonymousElementByAttribute(plugin, "anonid", "submitButton");
+ // Test that we don't send the URL when urlOptIn is false.
+ doc.getAnonymousElementByAttribute(plugin, "anonid", "submitURLOptIn").checked = aConfig.urlOptIn;
+ submitButton.click();
+ Assert.equal(content.getComputedStyle(pleaseSubmit).display == "block",
+ aConfig.shouldSubmissionUIBeVisible, "The crash UI should be visible");
+ });
+
+ yield crashReportStatus;
+});
+
+add_task(function* () {
+ config = {
+ shouldSubmissionUIBeVisible: true,
+ comment: "a test comment",
+ urlOptIn: true
+ };
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ let pluginCrashed = promisePluginCrashed();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Wait for the plugin to crash
+ yield pluginCrashed;
+
+ let crashReportStatus = TestUtils.topicObserved("crash-report-status", onSubmitStatus);
+
+ // Test that the crash submission UI is actually visible and submit the crash report.
+ yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) {
+ let doc = content.document;
+ let plugin = doc.getElementById("plugin");
+ let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit");
+ let submitButton = doc.getAnonymousElementByAttribute(plugin, "anonid", "submitButton");
+ // Test that we send the URL when urlOptIn is true.
+ doc.getAnonymousElementByAttribute(plugin, "anonid", "submitURLOptIn").checked = aConfig.urlOptIn;
+ doc.getAnonymousElementByAttribute(plugin, "anonid", "submitComment").value = aConfig.comment;
+ submitButton.click();
+ Assert.equal(content.getComputedStyle(pleaseSubmit).display == "block",
+ aConfig.shouldSubmissionUIBeVisible, "The crash UI should be visible");
+ });
+
+ yield crashReportStatus;
+});
+
+add_task(function* () {
+ config = {
+ shouldSubmissionUIBeVisible: false,
+ comment: "",
+ urlOptIn: true
+ };
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ let pluginCrashed = promisePluginCrashed();
+
+ // Make sure that the plugin container is too small to display the crash submission UI
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_crashCommentAndURL.html?" +
+ encodeURIComponent(JSON.stringify({width: 300, height: 300})));
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ // Wait for the plugin to crash
+ yield pluginCrashed;
+
+ // Test that the crash submission UI is not visible and do not submit a crash report.
+ yield ContentTask.spawn(gTestBrowser, config, function* (aConfig) {
+ let doc = content.document;
+ let plugin = doc.getElementById("plugin");
+ let pleaseSubmit = doc.getAnonymousElementByAttribute(plugin, "anonid", "pleaseSubmit");
+ Assert.equal(!!pleaseSubmit && content.getComputedStyle(pleaseSubmit).display == "block",
+ aConfig.shouldSubmissionUIBeVisible, "Plugin crash UI should not be visible");
+ });
+});
+
+function promisePluginCrashed() {
+ return new ContentTask.spawn(gTestBrowser, {}, function* () {
+ yield new Promise((resolve) => {
+ addEventListener("PluginCrashReporterDisplayed", function onPluginCrashed() {
+ removeEventListener("PluginCrashReporterDisplayed", onPluginCrashed);
+ resolve();
+ });
+ });
+ })
+}
+
+function onSubmitStatus(aSubject, aData) {
+ // Wait for success or failed, doesn't matter which.
+ if (aData != "success" && aData != "failed")
+ return false;
+
+ let propBag = aSubject.QueryInterface(Ci.nsIPropertyBag);
+ if (aData == "success") {
+ let remoteID = getPropertyBagValue(propBag, "serverCrashID");
+ ok(!!remoteID, "serverCrashID should be set");
+
+ // Remove the submitted report file.
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(remoteID + ".txt");
+ ok(file.exists(), "Submitted report file should exist");
+ file.remove(false);
+ }
+
+ let extra = getPropertyBagValue(propBag, "extra");
+ ok(extra instanceof Ci.nsIPropertyBag, "Extra data should be property bag");
+
+ let val = getPropertyBagValue(extra, "PluginUserComment");
+ if (config.comment)
+ is(val, config.comment,
+ "Comment in extra data should match comment in textbox");
+ else
+ ok(val === undefined,
+ "Comment should be absent from extra data when textbox is empty");
+
+ val = getPropertyBagValue(extra, "PluginContentURL");
+ if (config.urlOptIn)
+ is(val, gBrowser.currentURI.spec,
+ "URL in extra data should match browser URL when opt-in checked");
+ else
+ ok(val === undefined,
+ "URL should be absent from extra data when opt-in not checked");
+
+ return true;
+}
+
+function getPropertyBagValue(bag, key) {
+ try {
+ var val = bag.getProperty(key);
+ }
+ catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+ return val;
+}
diff --git a/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js
new file mode 100644
index 000000000..42ef57314
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pluginCrashReportNonDeterminism.js
@@ -0,0 +1,254 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * With e10s, plugins must run in their own process. This means we have
+ * three processes at a minimum when we're running a plugin:
+ *
+ * 1) The main browser, or "chrome" process
+ * 2) The content process hosting the plugin instance
+ * 3) The plugin process
+ *
+ * If the plugin process crashes, we cannot be sure if the chrome process
+ * will hear about it first, or the content process will hear about it
+ * first. Because of how IPC works, that's really up to the operating system,
+ * and we assume any guarantees about it, so we have to account for both
+ * possibilities.
+ *
+ * This test exercises the browser's reaction to both possibilities.
+ */
+
+const CRASH_URL = "http://example.com/browser/browser/base/content/test/plugins/plugin_crashCommentAndURL.html";
+const CRASHED_MESSAGE = "BrowserPlugins:NPAPIPluginProcessCrashed";
+
+/**
+ * In order for our test to work, we need to be able to put a plugin
+ * in a very specific state. Specifically, we need it to match the
+ * :-moz-handler-crashed pseudoselector. The only way I can find to
+ * do that is by actually crashing the plugin. So we wait for the
+ * plugin to crash and show the "please" state (since that will
+ * only show if both the message from the parent has been received
+ * AND the PluginCrashed event has fired).
+ *
+ * Once in that state, we try to rewind the clock a little bit - we clear
+ * out the crashData cache in the PluginContent with a message, and we also
+ * override the pluginFallbackState of the <object> to fool PluginContent
+ * into believing that the plugin is in a particular state.
+ *
+ * @param browser
+ * The browser that has loaded the CRASH_URL that we need to
+ * prepare to be in the special state.
+ * @param pluginFallbackState
+ * The value we should override the <object>'s pluginFallbackState
+ * with.
+ * @return Promise
+ * The Promise resolves when the plugin has officially been put into
+ * the crash reporter state, and then "rewound" to have the "status"
+ * attribute of the statusDiv removed. The resolved Promise returns
+ * the run ID for the crashed plugin. It rejects if we never get into
+ * the crash reporter state.
+ */
+function preparePlugin(browser, pluginFallbackState) {
+ return ContentTask.spawn(browser, pluginFallbackState, function* (pluginFallbackState) {
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // CRASH_URL will load a plugin that crashes immediately. We
+ // wait until the plugin has finished being put into the crash
+ // state.
+ let statusDiv;
+ yield ContentTaskUtils.waitForCondition(() => {
+ statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+ return statusDiv && statusDiv.getAttribute("status") == "please";
+ }, "Timed out waiting for plugin to be in crash report state");
+
+ // "Rewind", by wiping out the status attribute...
+ statusDiv.removeAttribute("status");
+ // Somehow, I'm able to get away with overriding the getter for
+ // this XPCOM object. Probably because I've got chrome privledges.
+ Object.defineProperty(plugin, "pluginFallbackType", {
+ get: function() {
+ return pluginFallbackState;
+ }
+ });
+ return plugin.runID;
+ }).then((runID) => {
+ browser.messageManager.sendAsyncMessage("BrowserPlugins:Test:ClearCrashData");
+ return runID;
+ });
+}
+
+add_task(function* setup() {
+ // Bypass click-to-play
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED);
+
+ // Clear out any minidumps we create from plugins - we really don't care
+ // about them.
+ let crashObserver = (subject, topic, data) => {
+ if (topic != "plugin-crashed") {
+ return;
+ }
+
+ let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
+ let minidumpID = propBag.getPropertyAsAString("pluginDumpID");
+
+ let minidumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ minidumpDir.append("minidumps");
+
+ let pluginDumpFile = minidumpDir.clone();
+ pluginDumpFile.append(minidumpID + ".dmp");
+
+ let extraFile = minidumpDir.clone();
+ extraFile.append(minidumpID + ".extra");
+
+ ok(pluginDumpFile.exists(), "Found minidump");
+ ok(extraFile.exists(), "Found extra file");
+
+ pluginDumpFile.remove(false);
+ extraFile.remove(false);
+ };
+
+ Services.obs.addObserver(crashObserver, "plugin-crashed", false);
+ // plugins.testmode will make BrowserPlugins:Test:ClearCrashData work.
+ Services.prefs.setBoolPref("plugins.testmode", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("plugins.testmode");
+ Services.obs.removeObserver(crashObserver, "plugin-crashed");
+ });
+});
+
+/**
+ * In this case, the chrome process hears about the crash first.
+ */
+add_task(function* testChromeHearsPluginCrashFirst() {
+ // Open a remote window so that we can run this test even if e10s is not
+ // enabled by default.
+ let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ let browser = win.gBrowser.selectedBrowser;
+
+ browser.loadURI(CRASH_URL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // In this case, we want the <object> to match the -moz-handler-crashed
+ // pseudoselector, but we want it to seem still active, because the
+ // content process is not yet supposed to know that the plugin has
+ // crashed.
+ let runID = yield preparePlugin(browser,
+ Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE);
+
+ // Send the message down to PluginContent.jsm saying that the plugin has
+ // crashed, and that we have a crash report.
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage(CRASHED_MESSAGE,
+ { pluginName: "", runID, state: "please" });
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // At this point, the content process should have heard the
+ // plugin crash message from the parent, and we are OK to emit
+ // the PluginCrashed event.
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+
+ if (statusDiv.getAttribute("status") == "please") {
+ Assert.ok(false, "Did not expect plugin to be in crash report mode yet.");
+ return;
+ }
+
+ // Now we need the plugin to seem crashed to PluginContent.jsm, without
+ // actually crashing the plugin again. We hack around this by overriding
+ // the pluginFallbackType again.
+ Object.defineProperty(plugin, "pluginFallbackType", {
+ get: function() {
+ return Ci.nsIObjectLoadingContent.PLUGIN_CRASHED;
+ },
+ });
+
+ let event = new content.PluginCrashedEvent("PluginCrashed", {
+ pluginName: "",
+ pluginDumpID: "",
+ browserDumpID: "",
+ submittedCrashReport: false,
+ bubbles: true,
+ cancelable: true,
+ });
+
+ plugin.dispatchEvent(event);
+ Assert.equal(statusDiv.getAttribute("status"), "please",
+ "Should have been showing crash report UI");
+ });
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * In this case, the content process hears about the crash first.
+ */
+add_task(function* testContentHearsCrashFirst() {
+ // Open a remote window so that we can run this test even if e10s is not
+ // enabled by default.
+ let win = yield BrowserTestUtils.openNewBrowserWindow({remote: true});
+ let browser = win.gBrowser.selectedBrowser;
+
+ browser.loadURI(CRASH_URL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // In this case, we want the <object> to match the -moz-handler-crashed
+ // pseudoselector, and we want the plugin to seem crashed, since the
+ // content process in this case has heard about the crash first.
+ let runID = yield preparePlugin(browser,
+ Ci.nsIObjectLoadingContent.PLUGIN_CRASHED);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // At this point, the content process has not yet heard from the
+ // parent about the crash report. Let's ensure that by making sure
+ // we're not showing the plugin crash report UI.
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+
+ if (statusDiv.getAttribute("status") == "please") {
+ Assert.ok(false, "Did not expect plugin to be in crash report mode yet.");
+ }
+
+ let event = new content.PluginCrashedEvent("PluginCrashed", {
+ pluginName: "",
+ pluginDumpID: "",
+ browserDumpID: "",
+ submittedCrashReport: false,
+ bubbles: true,
+ cancelable: true,
+ });
+
+ plugin.dispatchEvent(event);
+
+ Assert.notEqual(statusDiv.getAttribute("status"), "please",
+ "Should not yet be showing crash report UI");
+ });
+
+ // Now send the message down to PluginContent.jsm that the plugin has
+ // crashed...
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage(CRASHED_MESSAGE,
+ { pluginName: "", runID, state: "please"});
+
+ yield ContentTask.spawn(browser, null, function* () {
+ // At this point, the content process will have heard the message
+ // from the parent and reacted to it. We should be showing the plugin
+ // crash report UI now.
+ let plugin = content.document.getElementById("plugin");
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let statusDiv = plugin.ownerDocument
+ .getAnonymousElementByAttribute(plugin, "anonid",
+ "submitStatus");
+
+ Assert.equal(statusDiv.getAttribute("status"), "please",
+ "Should have been showing crash report UI");
+ });
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/plugins/browser_plugin_reloading.js b/browser/base/content/test/plugins/browser_plugin_reloading.js
new file mode 100644
index 000000000..7327d4cf9
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_plugin_reloading.js
@@ -0,0 +1,85 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+function updateAllTestPlugins(aState) {
+ setTestPluginEnabledState(aState, "Test Plug-in");
+ setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gTestBrowser = null;
+ gBrowser.removeCurrentTab();
+ window.focus();
+ }));
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests that a click-to-play plugin retains its activated state upon reloading
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 1, Should have a click-to-play notification");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 2, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+ // run the plugin
+ yield promisePlayObject("test");
+
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 3, plugin should have started");
+ ok(pluginInfo.activated, "Test 4, plugin node should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let npobj1 = Components.utils.waiveXrays(plugin).getObjectValue();
+ plugin.src = plugin.src;
+ let pluginsDiffer = false;
+ try {
+ Components.utils.waiveXrays(plugin).checkObjectValue(npobj1);
+ } catch (e) {
+ pluginsDiffer = true;
+ }
+
+ Assert.ok(pluginsDiffer, "Test 5, plugins differ.");
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 6, Plugin should have retained activated state.");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 7, plugin should have started");
+});
diff --git a/browser/base/content/test/plugins/browser_pluginnotification.js b/browser/base/content/test/plugins/browser_pluginnotification.js
new file mode 100644
index 000000000..bf32f37a4
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_pluginnotification.js
@@ -0,0 +1,626 @@
+var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gTestBrowser = null;
+
+function updateAllTestPlugins(aState) {
+ setTestPluginEnabledState(aState, "Test Plug-in");
+ setTestPluginEnabledState(aState, "Second Test Plug-in");
+}
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gTestBrowser = null;
+ gBrowser.removeCurrentTab();
+ window.focus();
+ }));
+});
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ // Prime the blocklist service, the remote service doesn't launch on startup.
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html></html>");
+ let exmsg = yield promiseInitContentBlocklistSvc(gBrowser.selectedBrowser);
+ ok(!exmsg, "exception: " + exmsg);
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+});
+
+// Tests a page with an unknown plugin in it.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_unknown.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("unknown");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED,
+ "Test 1a, plugin fallback type should be PLUGIN_UNSUPPORTED");
+});
+
+// Test that the doorhanger is shown when the user clicks on the overlay
+// after having previously blocked the plugin.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Plugin should not be activated");
+
+ // Simulate clicking the "Allow Now" button.
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._secondaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Plugin should be activated");
+
+ // Simulate clicking the "Block" button.
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Plugin should not be activated");
+
+ // Simulate clicking the overlay
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ ok(!notification.dismissed, "A plugin notification should be shown.");
+
+ clearAllPluginPermissions();
+});
+
+// Tests that going back will reshow the notification for click-to-play plugins
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+
+ // make sure the notification is gone
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 11b, Should not have a click-to-play notification");
+
+ gTestBrowser.webNavigation.goBack();
+
+ yield promisePopupNotification("click-to-play-plugins");
+});
+
+// Tests that the "Allow Always" permission works for click-to-play plugins
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 12a, Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+
+ yield promiseForNotificationShown(notification);
+
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 12a, Plugin should be activated");
+});
+
+// Test that the "Always" permission, when set for just the Test plugin,
+// does not also allow the Second Test plugin.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let test = content.document.getElementById("test");
+ let secondtestA = content.document.getElementById("secondtestA");
+ let secondtestB = content.document.getElementById("secondtestB");
+ Assert.ok(test.activated && !secondtestA.activated && !secondtestB.activated,
+ "Content plugins are set up");
+ });
+
+ clearAllPluginPermissions();
+});
+
+// Tests that the plugin's "activated" property is true for working plugins
+// with click-to-play disabled.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_ENABLED);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test2.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test1");
+ ok(pluginInfo.activated, "Test 14, Plugin should be activated");
+});
+
+// Tests that the overlay is shown instead of alternate content when
+// plugins are click to play.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_alternate_content.html");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!!mainBox, "Test 15, Plugin overlay should exist");
+ });
+});
+
+// Tests that mContentType is used for click-to-play plugins, and not the
+// inspected type.
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_bug749455.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 17, Should have a click-to-play notification");
+});
+
+// Tests that clicking the icon of the overlay activates the doorhanger
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+ "Test 19a, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let icon = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
+ let bounds = icon.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ yield promiseForCondition(condition);
+});
+
+// Tests that clicking the text of the overlay activates the plugin
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+ "Test 19c, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let text = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay");
+ let bounds = text.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ yield promiseForCondition(condition);
+});
+
+// Tests that clicking the box of the overlay activates the doorhanger
+// (just to be thorough)
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 18g, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed,
+ "Test 19e, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", 50, 50, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", 50, 50, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 19e, Plugin should not be activated");
+
+ clearAllPluginPermissions();
+});
+
+// Tests that a plugin in a div that goes from style="display: none" to
+// "display: block" can be clicked to activate.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_hidden_to_visible.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 20a, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ Assert.ok(!!overlay, "Test 20a, Plugin overlay should exist");
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let overlayRect = mainBox.getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 20a, plugin should have an overlay with 0px width and height");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 20b, plugin should not be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let div = doc.getElementById("container");
+ Assert.equal(div.style.display, "none",
+ "Test 20b, container div should be display: none");
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let div = doc.getElementById("container");
+ div.style.display = "block";
+ });
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let mainBox = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
+ let overlayRect = mainBox.getBoundingClientRect();
+ Assert.ok(overlayRect.width == 200 && overlayRect.height == 200,
+ "Test 20c, plugin should have overlay dims of 200px");
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(!pluginInfo.activated, "Test 20b, plugin should not be activated");
+
+ ok(notification.dismissed, "Test 20c, Doorhanger should start out dismissed");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let bounds = plugin.getBoundingClientRect();
+ let left = (bounds.left + bounds.right) / 2;
+ let top = (bounds.top + bounds.bottom) / 2;
+ let utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ });
+
+ let condition = () => !notification.dismissed && !!PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 20c, plugin should be activated");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 20c, plugin should have overlay dims of 0px");
+ });
+
+ clearAllPluginPermissions();
+});
+
+// Test having multiple different types of plugin on one page
+add_task(function* () {
+ // contains three plugins, application/x-test, application/x-second-test x 2
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_two_types.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21a, Should have a click-to-play notification");
+
+ // confirm all three are blocked by ctp at this point
+ let ids = ["test", "secondtestA", "secondtestB"];
+ for (let id of ids) {
+ yield ContentTask.spawn(gTestBrowser, { id }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById(args.id);
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 200 && overlayRect.height == 200,
+ "Test 21a, plugin " + args.id + " should have click-to-play overlay with dims");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo(id);
+ ok(!pluginInfo.activated, "Test 21a, Plugin with id=" + id + " should not be activated");
+ }
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21a, Should have a click-to-play notification");
+
+ // we have to actually show the panel to get the bindings to instantiate
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 2, "Test 21a, Should have two types of plugin in the notification");
+
+ let centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 21b, found center action for the Test plugin");
+
+ let centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 21b, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 21b, found center item for the Test plugin");
+
+ // Select the allow now option in the select drop down for Test Plugin
+ centerItem.value = "allownow";
+
+ // "click" the button to activate the Test plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ ok(pluginInfo.activated, "Test 21b, plugin should be activated");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21b, Should have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ ok(notification.options.pluginData.size == 2, "Test 21c, Should have one type of plugin in the notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 21c, plugin should have overlay dims of 0px");
+ });
+
+ ids = ["secondtestA", "secondtestB"];
+ for (let id of ids) {
+ yield ContentTask.spawn(gTestBrowser, { id }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById(args.id);
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 200 && overlayRect.height == 200,
+ "Test 21c, plugin " + args.id + " should have click-to-play overlay with zero dims");
+ });
+
+
+ let pluginInfo = yield promiseForPluginInfo(id);
+ ok(!pluginInfo.activated, "Test 21c, Plugin with id=" + id + " should not be activated");
+ }
+
+ centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Second Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 21d, found center action for the Second Test plugin");
+
+ centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action == centerAction) {
+ is(item.value, "block", "Test 21d, test plugin 2 should start blocked");
+ centerItem = item;
+ break;
+ }
+ else {
+ is(item.value, "allownow", "Test 21d, test plugin should be enabled");
+ }
+ }
+ ok(centerItem, "Test 21d, found center item for the Second Test plugin");
+
+ // Select the allow now option in the select drop down for Second Test Plguins
+ centerItem.value = "allownow";
+
+ // "click" the button to activate the Second Test plugins
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21d, Should have a click-to-play notification");
+
+ ids = ["test", "secondtestA", "secondtestB"];
+ for (let id of ids) {
+ yield ContentTask.spawn(gTestBrowser, { id }, function* (args) {
+ let doc = content.document;
+ let plugin = doc.getElementById(args.id);
+ let overlayRect = doc.getAnonymousElementByAttribute(plugin, "anonid", "main").getBoundingClientRect();
+ Assert.ok(overlayRect.width == 0 && overlayRect.height == 0,
+ "Test 21d, plugin " + args.id + " should have click-to-play overlay with zero dims");
+ });
+
+ let pluginInfo = yield promiseForPluginInfo(id);
+ ok(pluginInfo.activated, "Test 21d, Plugin with id=" + id + " should not be activated");
+ }
+});
+
+// Tests that a click-to-play plugin resets its activated state when changing types
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 22, Should have a click-to-play notification");
+
+ // Plugin should start as CTP
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.type = null;
+ // We currently don't properly change state just on type change,
+ // so rebind the plugin to tree. bug 767631
+ plugin.parentNode.appendChild(plugin);
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL");
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let doc = content.document;
+ let plugin = doc.getElementById("test");
+ plugin.type = "application/x-test";
+ plugin.parentNode.appendChild(plugin);
+ });
+
+ pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be TYPE_NULL");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ ok(!pluginInfo.activated, "Test 23, plugin node should not be activated");
+});
+
+// Plugin sync removal test. Note this test produces a notification drop down since
+// the plugin we add has zero dims.
+add_task(function* () {
+ updateAllTestPlugins(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_syncRemoved.html");
+
+ // Maybe there some better trick here, we need to wait for the page load, then
+ // wait for the js to execute in the page.
+ yield waitForMs(500);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+ ok(notification, "Test 25: There should be a plugin notification even if the plugin was immediately removed");
+ ok(notification.dismissed, "Test 25: The notification should be dismissed by default");
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html,<html>hi</html>");
+});
+
+// Tests a page with a blocked plugin in it and make sure the infoURL property
+// the blocklist file gets used.
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockPluginInfoURL.xml", gTestBrowser);
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_test.html");
+
+ // Work around for delayed PluginBindingAttached
+ yield promiseUpdatePluginBindings(gTestBrowser);
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+
+ // Since the plugin notification is dismissed by default, reshow it.
+ yield promiseForNotificationShown(notification);
+
+ let pluginInfo = yield promiseForPluginInfo("test");
+ is(pluginInfo.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED,
+ "Test 26, plugin fallback type should be PLUGIN_BLOCKLISTED");
+ ok(!pluginInfo.activated, "Plugin should be activated.");
+
+ const testUrl = "http://test.url.com/";
+
+ let firstPanelChild = PopupNotifications.panel.firstChild;
+ let infoLink = document.getAnonymousElementByAttribute(firstPanelChild, "anonid",
+ "click-to-play-plugins-notification-link");
+ is(infoLink.href, testUrl,
+ "Test 26, the notification URL needs to match the infoURL from the blocklist file.");
+});
diff --git a/browser/base/content/test/plugins/browser_plugins_added_dynamically.js b/browser/base/content/test/plugins/browser_plugins_added_dynamically.js
new file mode 100644
index 000000000..22077a54d
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_plugins_added_dynamically.js
@@ -0,0 +1,137 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://mochi.test:8888/");
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+var gTestBrowser = null;
+
+add_task(function* () {
+ registerCleanupFunction(Task.async(function*() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+ yield asyncSetAndUpdateBlocklist(gTestRoot + "blockNoPlugins.xml", gTestBrowser);
+ resetBlocklist();
+ gBrowser.removeCurrentTab();
+ window.focus();
+ gTestBrowser = null;
+ }));
+});
+
+// "Activate" of a given type -> plugins of that type dynamically added should
+// automatically play.
+add_task(function* () {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
+});
+
+add_task(function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 1a, Should not have a click-to-play notification");
+
+ // Add a plugin of type test
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test"));
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 1a, Should not have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ let centerAction = null;
+ for (let action of notification.options.pluginData.values()) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 2, found center action for the Test plugin");
+
+ let centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 3, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 4, found center item for the Test plugin");
+
+ // Select the allow now option in the select drop down for Test Plugin
+ centerItem.value = "allownow";
+
+ // "click" the button to activate the Test plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(pluginInfo.activated, "Test 5, plugin should be activated");
+
+ // Add another plugin of type test
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-test"));
+ });
+
+ pluginInfo = yield promiseForPluginInfo("plugintwo");
+ ok(pluginInfo.activated, "Test 6, plugins should be activated");
+});
+
+// "Activate" of a given type -> plugins of other types dynamically added
+// should not automatically play.
+add_task(function* () {
+ clearAllPluginPermissions();
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_add_dynamically.html");
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!notification, "Test 7, Should not have a click-to-play notification");
+
+ // Add a plugin of type test
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("pluginone", "application/x-test"));
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 8, Should not have a click-to-play notification");
+
+ yield promiseForNotificationShown(notification);
+
+ is(notification.options.pluginData.size, 1, "Should be one plugin action");
+
+ let pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(!pluginInfo.activated, "Test 8, test plugin should be activated");
+
+ let condition = () => !notification.dismissed &&
+ PopupNotifications.panel.firstChild;
+ yield promiseForCondition(condition);
+
+ // "click" the button to activate the Test plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(pluginInfo.activated, "Test 9, test plugin should be activated");
+
+ yield ContentTask.spawn(gTestBrowser, {}, function* () {
+ new XPCNativeWrapper(XPCNativeWrapper.unwrap(content).addPlugin("plugintwo", "application/x-second-test"));
+ });
+
+ yield promisePopupNotification("click-to-play-plugins");
+
+ pluginInfo = yield promiseForPluginInfo("pluginone");
+ ok(pluginInfo.activated, "Test 10, plugins should be activated");
+ pluginInfo = yield promiseForPluginInfo("plugintwo");
+ ok(!pluginInfo.activated, "Test 11, plugins should be activated");
+});
diff --git a/browser/base/content/test/plugins/browser_private_clicktoplay.js b/browser/base/content/test/plugins/browser_private_clicktoplay.js
new file mode 100644
index 000000000..785b1bb31
--- /dev/null
+++ b/browser/base/content/test/plugins/browser_private_clicktoplay.js
@@ -0,0 +1,216 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gPrivateWindow = null;
+var gPrivateBrowser = null;
+
+function finishTest() {
+ clearAllPluginPermissions();
+ gBrowser.removeCurrentTab();
+ if (gPrivateWindow) {
+ gPrivateWindow.close();
+ }
+ window.focus();
+}
+
+let createPrivateWindow = Task.async(function* createPrivateWindow(url) {
+ gPrivateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ ok(!!gPrivateWindow, "should have created a private window.");
+ gPrivateBrowser = gPrivateWindow.getBrowser().selectedBrowser;
+
+ BrowserTestUtils.loadURI(gPrivateBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gPrivateBrowser);
+});
+
+add_task(function* test() {
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ let promise = BrowserTestUtils.browserLoaded(gTestBrowser);
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ yield promise;
+});
+
+add_task(function* test1a() {
+ yield createPrivateWindow(gHttpTestRoot + "plugin_test.html");
+});
+
+add_task(function* test1b() {
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 1b, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gPrivateBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+ });
+
+ // Check the button status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+
+ yield promiseShown;
+ let button1 = gPrivateWindow.PopupNotifications.panel.firstChild._primaryButton;
+ let button2 = gPrivateWindow.PopupNotifications.panel.firstChild._secondaryButton;
+ is(button1.getAttribute("action"), "_singleActivateNow", "Test 1b, Blocked plugin in private window should have a activate now button");
+ ok(button2.hidden, "Test 1b, Blocked plugin in a private window should not have a secondary button")
+
+ gPrivateWindow.close();
+ BrowserTestUtils.loadURI(gTestBrowser, gHttpTestRoot + "plugin_test.html");
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* test2a() {
+ // enable test plugin on this site
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 2a, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 2a, Plugin should not be activated");
+ });
+
+ // Simulate clicking the "Allow Now" button.
+ let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+
+ PopupNotifications.panel.firstChild._secondaryButton.click();
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let condition = () => objLoadingContent.activated;
+ yield ContentTaskUtils.waitForCondition(condition, "Test 2a, Waited too long for plugin to activate");
+ });
+});
+
+add_task(function* test2c() {
+ let topicObserved = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
+ yield createPrivateWindow(gHttpTestRoot + "plugin_test.html");
+ yield topicObserved;
+
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 2c, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gPrivateBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 2c, Plugin should be activated");
+ });
+
+ // Check the button status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer;
+ ok(buttonContainer.hidden, "Test 2c, Activated plugin in a private window should not have visible buttons");
+
+ clearAllPluginPermissions();
+ gPrivateWindow.close();
+
+ BrowserTestUtils.loadURI(gTestBrowser, gHttpTestRoot + "plugin_test.html");
+ yield BrowserTestUtils.browserLoaded(gTestBrowser);
+});
+
+add_task(function* test3a() {
+ // enable test plugin on this site
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 3a, Should have a click-to-play notification");
+
+ yield ContentTask.spawn(gTestBrowser, null, function() {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 3a, Plugin should not be activated");
+ });
+
+ // Simulate clicking the "Allow Always" button.
+ let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ PopupNotifications.panel.firstChild._secondaryButton.click();
+
+ yield ContentTask.spawn(gTestBrowser, null, function* () {
+ let plugin = content.document.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let condition = () => objLoadingContent.activated;
+ yield ContentTaskUtils.waitForCondition(condition, "Test 3a, Waited too long for plugin to activate");
+ });
+});
+
+add_task(function* test3c() {
+ let topicObserved = TestUtils.topicObserved("PopupNotifications-updateNotShowing");
+ yield createPrivateWindow(gHttpTestRoot + "plugin_test.html");
+ yield topicObserved;
+
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 3c, Should have a click-to-play notification");
+
+ // Check the button status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ let buttonContainer = gPrivateWindow.PopupNotifications.panel.firstChild._buttonContainer;
+ ok(buttonContainer.hidden, "Test 3c, Activated plugin in a private window should not have visible buttons");
+
+ BrowserTestUtils.loadURI(gPrivateBrowser, gHttpTestRoot + "plugin_two_types.html");
+ yield BrowserTestUtils.browserLoaded(gPrivateBrowser);
+});
+
+add_task(function* test3d() {
+ let popupNotification = gPrivateWindow.PopupNotifications.getNotification("click-to-play-plugins", gPrivateBrowser);
+ ok(popupNotification, "Test 3d, Should have a click-to-play notification");
+
+ // Check the list item status
+ let promiseShown = BrowserTestUtils.waitForEvent(gPrivateWindow.PopupNotifications.panel,
+ "Shown");
+ popupNotification.reshow();
+ yield promiseShown;
+ let doc = gPrivateWindow.document;
+ for (let item of gPrivateWindow.PopupNotifications.panel.firstChild.childNodes) {
+ let allowalways = doc.getAnonymousElementByAttribute(item, "anonid", "allowalways");
+ ok(allowalways, "Test 3d, should have list item for allow always");
+ let allownow = doc.getAnonymousElementByAttribute(item, "anonid", "allownow");
+ ok(allownow, "Test 3d, should have list item for allow now");
+ let block = doc.getAnonymousElementByAttribute(item, "anonid", "block");
+ ok(block, "Test 3d, should have list item for block");
+
+ if (item.action.pluginName === "Test") {
+ is(item.value, "allowalways", "Test 3d, Plugin should bet set to 'allow always'");
+ ok(!allowalways.hidden, "Test 3d, Plugin set to 'always allow' should have a visible 'always allow' action.");
+ ok(allownow.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'allow now' action.");
+ ok(block.hidden, "Test 3d, Plugin set to 'always allow' should have an invisible 'block' action.");
+ } else if (item.action.pluginName === "Second Test") {
+ is(item.value, "block", "Test 3d, Second plugin should bet set to 'block'");
+ ok(allowalways.hidden, "Test 3d, Plugin set to 'block' should have a visible 'always allow' action.");
+ ok(!allownow.hidden, "Test 3d, Plugin set to 'block' should have a visible 'allow now' action.");
+ ok(!block.hidden, "Test 3d, Plugin set to 'block' should have a visible 'block' action.");
+ } else {
+ ok(false, "Test 3d, Unexpected plugin '"+item.action.pluginName+"'");
+ }
+ }
+
+ finishTest();
+});
diff --git a/browser/base/content/test/plugins/head.js b/browser/base/content/test/plugins/head.js
new file mode 100644
index 000000000..4995c4dc6
--- /dev/null
+++ b/browser/base/content/test/plugins/head.js
@@ -0,0 +1,396 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+
+// The blocklist shim running in the content process does not initialize at
+// start up, so it's not active until we load content that needs to do a
+// check. This helper bypasses the delay to get the svc up and running
+// immediately. Note, call this after remote content has loaded.
+function promiseInitContentBlocklistSvc(aBrowser)
+{
+ return ContentTask.spawn(aBrowser, {}, function* () {
+ try {
+ Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsIBlocklistService);
+ } catch (ex) {
+ return ex.message;
+ }
+ return null;
+ });
+}
+
+/**
+ * Waits a specified number of miliseconds.
+ *
+ * Usage:
+ * let wait = yield waitForMs(2000);
+ * ok(wait, "2 seconds should now have elapsed");
+ *
+ * @param aMs the number of miliseconds to wait for
+ * @returns a Promise that resolves to true after the time has elapsed
+ */
+function waitForMs(aMs) {
+ return new Promise((resolve) => {
+ setTimeout(done, aMs);
+ function done() {
+ resolve(true);
+ }
+ });
+}
+
+function waitForEvent(subject, eventName, checkFn, useCapture, useUntrusted) {
+ return new Promise((resolve, reject) => {
+ subject.addEventListener(eventName, function listener(event) {
+ try {
+ if (checkFn && !checkFn(event)) {
+ return;
+ }
+ subject.removeEventListener(eventName, listener, useCapture);
+ resolve(event);
+ } catch (ex) {
+ try {
+ subject.removeEventListener(eventName, listener, useCapture);
+ } catch (ex2) {
+ // Maybe the provided object does not support removeEventListener.
+ }
+ reject(ex);
+ }
+ }, useCapture, useUntrusted);
+ });
+}
+
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url) {
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url)
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+
+ return loaded;
+}
+
+function waitForCondition(condition, nextTest, errorMsg, aTries, aWait) {
+ let tries = 0;
+ let maxTries = aTries || 100; // 100 tries
+ let maxWait = aWait || 100; // 100 msec x 100 tries = ten seconds
+ let interval = setInterval(function() {
+ if (tries >= maxTries) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ let conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, maxWait);
+ let moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+// Waits for a conditional function defined by the caller to return true.
+function promiseForCondition(aConditionFn, aMessage, aTries, aWait) {
+ return new Promise((resolve) => {
+ waitForCondition(aConditionFn, resolve,
+ (aMessage || "Condition didn't pass."),
+ aTries, aWait);
+ });
+}
+
+// Returns the chrome side nsIPluginTag for this plugin
+function getTestPlugin(aName) {
+ let pluginName = aName || "Test Plug-in";
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let tags = ph.getPluginTags();
+
+ // Find the test plugin
+ for (let i = 0; i < tags.length; i++) {
+ if (tags[i].name == pluginName)
+ return tags[i];
+ }
+ ok(false, "Unable to find plugin");
+ return null;
+}
+
+// Set the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ let name = pluginName || "Test Plug-in";
+ let plugin = getTestPlugin(name);
+ plugin.enabledState = newEnabledState;
+}
+
+// Get the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
+function getTestPluginEnabledState(pluginName) {
+ let name = pluginName || "Test Plug-in";
+ let plugin = getTestPlugin(name);
+ return plugin.enabledState;
+}
+
+// Returns a promise for nsIObjectLoadingContent props data.
+function promiseForPluginInfo(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ throw new Error("no plugin found");
+ return {
+ pluginFallbackType: plugin.pluginFallbackType,
+ activated: plugin.activated,
+ hasRunningPlugin: plugin.hasRunningPlugin,
+ displayedType: plugin.displayedType,
+ };
+ });
+}
+
+// Return a promise and call the plugin's nsIObjectLoadingContent
+// playPlugin() method.
+function promisePlayObject(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ objLoadingContent.playPlugin();
+ });
+}
+
+function promiseCrashObject(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ Components.utils.waiveXrays(plugin).crash();
+ });
+}
+
+// Return a promise and call the plugin's getObjectValue() method.
+function promiseObjectValueResult(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ return Components.utils.waiveXrays(plugin).getObjectValue();
+ });
+}
+
+// Return a promise and reload the target plugin in the page
+function promiseReloadPlugin(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return ContentTask.spawn(browser, aId, function* (aId) {
+ let plugin = content.document.getElementById(aId);
+ plugin.src = plugin.src;
+ });
+}
+
+// after a test is done using the plugin doorhanger, we should just clear
+// any permissions that may have crept in
+function clearAllPluginPermissions() {
+ let perms = Services.perms.enumerator;
+ while (perms.hasMoreElements()) {
+ let perm = perms.getNext();
+ if (perm.type.startsWith('plugin')) {
+ info("removing permission:" + perm.principal.origin + " " + perm.type + "\n");
+ Services.perms.removePermission(perm);
+ }
+ }
+}
+
+function updateBlocklist(aCallback) {
+ let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ let observer = function() {
+ Services.obs.removeObserver(observer, "blocklist-updated");
+ SimpleTest.executeSoon(aCallback);
+ };
+ Services.obs.addObserver(observer, "blocklist-updated", false);
+ blocklistNotifier.notify(null);
+}
+
+var _originalTestBlocklistURL = null;
+function setAndUpdateBlocklist(aURL, aCallback) {
+ if (!_originalTestBlocklistURL) {
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ }
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ updateBlocklist(aCallback);
+}
+
+// A generator that insures a new blocklist is loaded (in both
+// processes if applicable).
+function* asyncSetAndUpdateBlocklist(aURL, aBrowser) {
+ info("*** loading new blocklist: " + aURL);
+ let doTestRemote = aBrowser ? aBrowser.isRemoteBrowser : false;
+ if (!_originalTestBlocklistURL) {
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ }
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ let localPromise = TestUtils.topicObserved("blocklist-updated");
+ let remotePromise;
+ if (doTestRemote) {
+ remotePromise = TestUtils.topicObserved("content-blocklist-updated");
+ }
+ let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ blocklistNotifier.notify(null);
+ info("*** waiting on local load");
+ yield localPromise;
+ if (doTestRemote) {
+ info("*** waiting on remote load");
+ yield remotePromise;
+ }
+ info("*** blocklist loaded.");
+}
+
+// Reset back to the blocklist we had at the start of the test run.
+function resetBlocklist() {
+ Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
+}
+
+// Insure there's a popup notification present. This test does not indicate
+// open state. aBrowser can be undefined.
+function promisePopupNotification(aName, aBrowser) {
+ return new Promise((resolve) => {
+ waitForCondition(() => PopupNotifications.getNotification(aName, aBrowser),
+ () => {
+ ok(!!PopupNotifications.getNotification(aName, aBrowser),
+ aName + " notification appeared");
+
+ resolve();
+ }, "timeout waiting for popup notification " + aName);
+ });
+}
+
+/**
+ * Allows setting focus on a window, and waiting for that window to achieve
+ * focus.
+ *
+ * @param aWindow
+ * The window to focus and wait for.
+ *
+ * @return {Promise}
+ * @resolves When the window is focused.
+ * @rejects Never.
+ */
+function promiseWaitForFocus(aWindow) {
+ return new Promise((resolve) => {
+ waitForFocus(resolve, aWindow);
+ });
+}
+
+/**
+ * Returns a Promise that resolves when a notification bar
+ * for a browser is shown. Alternatively, for old-style callers,
+ * can automatically call a callback before it resolves.
+ *
+ * @param notificationID
+ * The ID of the notification to look for.
+ * @param browser
+ * The browser to check for the notification bar.
+ * @param callback (optional)
+ * A function to be called just before the Promise resolves.
+ *
+ * @return Promise
+ */
+function waitForNotificationBar(notificationID, browser, callback) {
+ return new Promise((resolve, reject) => {
+ let notification;
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ waitForCondition(
+ () => (notification = notificationBox.getNotificationWithValue(notificationID)),
+ () => {
+ ok(notification, `Successfully got the ${notificationID} notification bar`);
+ if (callback) {
+ callback(notification);
+ }
+ resolve(notification);
+ },
+ `Waited too long for the ${notificationID} notification bar`
+ );
+ });
+}
+
+function promiseForNotificationBar(notificationID, browser) {
+ return new Promise((resolve) => {
+ waitForNotificationBar(notificationID, browser, resolve);
+ });
+}
+
+/**
+ * Reshow a notification and call a callback when it is reshown.
+ * @param notification
+ * The notification to reshow
+ * @param callback
+ * A function to be called when the notification has been reshown
+ */
+function waitForNotificationShown(notification, callback) {
+ if (PopupNotifications.panel.state == "open") {
+ executeSoon(callback);
+ return;
+ }
+ PopupNotifications.panel.addEventListener("popupshown", function onShown(e) {
+ PopupNotifications.panel.removeEventListener("popupshown", onShown);
+ callback();
+ }, false);
+ notification.reshow();
+}
+
+function promiseForNotificationShown(notification) {
+ return new Promise((resolve) => {
+ waitForNotificationShown(notification, resolve);
+ });
+}
+
+/**
+ * Due to layout being async, "PluginBindAttached" may trigger later. This
+ * returns a Promise that resolves once we've forced a layout flush, which
+ * triggers the PluginBindAttached event to fire. This trick only works if
+ * there is some sort of plugin in the page.
+ * @param browser
+ * The browser to force plugin bindings in.
+ * @return Promise
+ */
+function promiseUpdatePluginBindings(browser) {
+ return ContentTask.spawn(browser, {}, function* () {
+ let doc = content.document;
+ let elems = doc.getElementsByTagName('embed');
+ if (!elems || elems.length < 1) {
+ elems = doc.getElementsByTagName('object');
+ }
+ if (elems && elems.length > 0) {
+ elems[0].clientTop;
+ }
+ });
+}
diff --git a/browser/base/content/test/plugins/plugin_add_dynamically.html b/browser/base/content/test/plugins/plugin_add_dynamically.html
new file mode 100644
index 000000000..863d36e09
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_add_dynamically.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script>
+function addPlugin(aId, aType="application/x-test") {
+ var embed = document.createElement("embed");
+ embed.setAttribute("id", aId);
+ embed.style.width = "200px";
+ embed.style.height = "200px";
+ embed.setAttribute("type", aType);
+ return document.body.appendChild(embed);
+}
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_alternate_content.html b/browser/base/content/test/plugins/plugin_alternate_content.html
new file mode 100644
index 000000000..f8acc833c
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_alternate_content.html
@@ -0,0 +1,9 @@
+<!-- bug 739575 -->
+<html>
+<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+</head>
+<body>
+<object id="test" type="application/x-test" style="height: 200px; width:200px">
+<p><a href="about:blank">you should not see this link when plugins are click-to-play</a></p>
+</object>
+</body></html>
diff --git a/browser/base/content/test/plugins/plugin_big.html b/browser/base/content/test/plugins/plugin_big.html
new file mode 100644
index 000000000..d11506176
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_big.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 500px; height: 500px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_both.html b/browser/base/content/test/plugins/plugin_both.html
new file mode 100644
index 000000000..2335366dc
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_both.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+<embed id="test" style="width: 100px; height: 100px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_both2.html b/browser/base/content/test/plugins/plugin_both2.html
new file mode 100644
index 000000000..ba605d6e8
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_both2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 100px; height: 100px" type="application/x-test">
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug744745.html b/browser/base/content/test/plugins/plugin_bug744745.html
new file mode 100644
index 000000000..d0691c9c0
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug744745.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<style>
+.x {
+ opacity: 0 !important;
+}
+</style>
+<object id="test" class="x" type="application/x-test" width=200 height=200></object>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug749455.html b/browser/base/content/test/plugins/plugin_bug749455.html
new file mode 100644
index 000000000..831dc82f7
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug749455.html
@@ -0,0 +1,8 @@
+<!-- bug 749455 -->
+<html>
+<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+</head>
+<body>
+<embed src="plugin_bug749455.html" type="application/x-test" width="100px" height="100px"></embed>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug787619.html b/browser/base/content/test/plugins/plugin_bug787619.html
new file mode 100644
index 000000000..cb91116f0
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug787619.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+ <a id="wrapper">
+ <embed id="plugin" style="width: 200px; height: 200px" type="application/x-test">
+ </a>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug797677.html b/browser/base/content/test/plugins/plugin_bug797677.html
new file mode 100644
index 000000000..1545f3647
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug797677.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body><embed id="plugin" type="9000"></embed></body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_bug820497.html b/browser/base/content/test/plugins/plugin_bug820497.html
new file mode 100644
index 000000000..4884e9dbe
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_bug820497.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<object id="test" type="application/x-test" width=200 height=200></object>
+<script>
+ function addSecondPlugin() {
+ var object = document.createElement("object");
+ object.type = "application/x-second-test";
+ object.width = 200;
+ object.height = 200;
+ object.id = "secondtest";
+ document.body.appendChild(object);
+ }
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_clickToPlayAllow.html b/browser/base/content/test/plugins/plugin_clickToPlayAllow.html
new file mode 100644
index 000000000..3f5df1984
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_clickToPlayAllow.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_clickToPlayDeny.html b/browser/base/content/test/plugins/plugin_clickToPlayDeny.html
new file mode 100644
index 000000000..3f5df1984
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_clickToPlayDeny.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_crashCommentAndURL.html b/browser/base/content/test/plugins/plugin_crashCommentAndURL.html
new file mode 100644
index 000000000..711a19ed3
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_crashCommentAndURL.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+ function crash() {
+ var plugin = document.getElementById("plugin");
+ var argStr = decodeURIComponent(window.location.search.substr(1));
+ if (argStr) {
+ var args = JSON.parse(argStr);
+ for (var key in args)
+ plugin.setAttribute(key, args[key]);
+ }
+ try {
+ plugin.crash();
+ }
+ catch (err) {}
+ }
+ </script>
+ </head>
+ <body onload="crash();">
+ <embed id="plugin" type="application/x-test"
+ width="400" height="400"
+ drawmode="solid" color="FF00FFFF">
+ </embed>
+ </body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_data_url.html b/browser/base/content/test/plugins/plugin_data_url.html
new file mode 100644
index 000000000..77e101144
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_data_url.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+ <a id="data-link-1" href='data:text/html,<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>'>
+ data: with one plugin
+ </a><br />
+ <a id="data-link-2" href='data:text/html,<embed id="test1" style="width: 200px; height: 200px" type="application/x-test"/><embed id="test2" style="width: 200px; height: 200px" type="application/x-second-test"/>'>
+ data: with two plugins
+ </a><br />
+ <object id="test" style="width: 200px; height: 200px" type="application/x-test"></object>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_hidden_to_visible.html b/browser/base/content/test/plugins/plugin_hidden_to_visible.html
new file mode 100644
index 000000000..eeacc1874
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_hidden_to_visible.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+ <div id="container" style="display: none">
+ <object id="test" type="application/x-test" style="width: 200px; height: 200px;"></object>
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_iframe.html b/browser/base/content/test/plugins/plugin_iframe.html
new file mode 100644
index 000000000..239c9a771
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_iframe.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<iframe id="frame" with="400" height="400" src="plugin_test.html">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_outsideScrollArea.html b/browser/base/content/test/plugins/plugin_outsideScrollArea.html
new file mode 100644
index 000000000..c6ef50d5d
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_outsideScrollArea.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style type="text/css">
+#container {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ background: blue;
+}
+
+#test {
+ width: 400px;
+ height: 400px;
+ position: absolute;
+}
+</style>
+</head>
+<body>
+ <div id="container"></div>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_overlayed.html b/browser/base/content/test/plugins/plugin_overlayed.html
new file mode 100644
index 000000000..11c127093
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_overlayed.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ .absthing {
+ width: 400px;
+ height: 400px;
+ position: absolute;
+ left: 20px;
+ top: 20px;
+ }
+ #d1 {
+ z-index: 1;
+ }
+ #d2 {
+ z-index: 2;
+ background-color: rgba(0,0,255,0.5);
+ border: 1px solid red;
+ }
+ </style>
+<body>
+ <div class="absthing" id="d1">
+ <embed id="test" type="application/x-test">
+ </div>
+ <div class="absthing" id="d2">
+ <p>This is overlaying
+ </div>
diff --git a/browser/base/content/test/plugins/plugin_positioned.html b/browser/base/content/test/plugins/plugin_positioned.html
new file mode 100644
index 000000000..1bad7ee46
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_positioned.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ #test {
+ position: absolute;
+ left: -1000px;
+ top: -1000px;
+ }
+ </style>
+<body>
+ <embed id="test" type="application/x-test">
diff --git a/browser/base/content/test/plugins/plugin_small.html b/browser/base/content/test/plugins/plugin_small.html
new file mode 100644
index 000000000..f37ee28c7
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_small.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 10px; height: 10px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_small_2.html b/browser/base/content/test/plugins/plugin_small_2.html
new file mode 100644
index 000000000..ebc5ffe84
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_small_2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 10px; height: 10px" type="application/x-second-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_syncRemoved.html b/browser/base/content/test/plugins/plugin_syncRemoved.html
new file mode 100644
index 000000000..d97787056
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_syncRemoved.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<body>
+<script type="text/javascript">
+ // create an embed, insert it in the doc and immediately remove it
+ var embed = document.createElement('embed');
+ embed.setAttribute("id", "test");
+ embed.setAttribute("type", "application/x-test");
+ embed.setAttribute("style", "width: 0px; height: 0px;");
+ document.body.appendChild(embed);
+ window.getComputedStyle(embed, null).top;
+ document.body.remove(embed);
+</script>
diff --git a/browser/base/content/test/plugins/plugin_test.html b/browser/base/content/test/plugins/plugin_test.html
new file mode 100644
index 000000000..d4b5b6ca7
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_test2.html b/browser/base/content/test/plugins/plugin_test2.html
new file mode 100644
index 000000000..95614c930
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_test2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test1" style="width: 200px; height: 200px" type="application/x-test">
+<embed id="test2" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_test3.html b/browser/base/content/test/plugins/plugin_test3.html
new file mode 100644
index 000000000..215c02326
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_test3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 0px; height: 0px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_two_types.html b/browser/base/content/test/plugins/plugin_two_types.html
new file mode 100644
index 000000000..2359d2ec1
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_two_types.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>
+<embed id="secondtestA" style="width: 200px; height: 200px" type="application/x-second-test"/>
+<embed id="secondtestB" style="width: 200px; height: 200px" type="application/x-second-test"/>
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_unknown.html b/browser/base/content/test/plugins/plugin_unknown.html
new file mode 100644
index 000000000..578f455cc
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_unknown.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+</body>
+</html>
diff --git a/browser/base/content/test/plugins/plugin_zoom.html b/browser/base/content/test/plugins/plugin_zoom.html
new file mode 100644
index 000000000..f9e598658
--- /dev/null
+++ b/browser/base/content/test/plugins/plugin_zoom.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<!-- The odd width and height are here to trigger bug 972237. -->
+<embed id="test" style="width: 99.789%; height: 99.123%" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/popupNotifications/.eslintrc.js b/browser/base/content/test/popupNotifications/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/popupNotifications/browser.ini b/browser/base/content/test/popupNotifications/browser.ini
new file mode 100644
index 000000000..83bb7c517
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_displayURI.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_2.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_3.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_4.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_checkbox.js]
+skip-if = (os == "linux" && (debug || asan))
+[browser_reshow_in_background.js]
+skip-if = (os == "linux" && (debug || asan))
diff --git a/browser/base/content/test/popupNotifications/browser_displayURI.js b/browser/base/content/test/popupNotifications/browser_displayURI.js
new file mode 100644
index 000000000..48222be19
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_displayURI.js
@@ -0,0 +1,28 @@
+/*
+ * Make sure that the origin is shown for ContentPermissionPrompt
+ * consumers e.g. geolocation.
+*/
+
+add_task(function* test_displayURI() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "https://test1.example.com/",
+ }, function*(browser) {
+ let popupShownPromise = new Promise((resolve, reject) => {
+ onPopupEvent("popupshown", function() {
+ resolve(this);
+ });
+ });
+ yield ContentTask.spawn(browser, null, function*() {
+ content.navigator.geolocation.getCurrentPosition(function (pos) {
+ // Do nothing
+ });
+ });
+ let panel = yield popupShownPromise;
+ let notification = panel.children[0];
+ let body = document.getAnonymousElementByAttribute(notification,
+ "class",
+ "popup-notification-body");
+ ok(body.innerHTML.includes("example.com"), "Check that at least the eTLD+1 is present in the markup");
+ });
+});
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification.js b/browser/base/content/test/popupNotifications/browser_popupNotification.js
new file mode 100644
index 000000000..6be3e4205
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification.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/. */
+
+// These are shared between test #4 to #5
+var wrongBrowserNotificationObject = new BasicNotification("wrongBrowser");
+var wrongBrowserNotification;
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ { id: "Test#1",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.mainActionClicked, "mainAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ { id: "Test#2",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ { id: "Test#3",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // test opening a notification for a background browser
+ // Note: test 4 to 6 share a tab.
+ { id: "Test#4",
+ run: function* () {
+ let tab = gBrowser.addTab("about:blank");
+ isnot(gBrowser.selectedTab, tab, "new tab isn't selected");
+ wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(tab);
+ let promiseTopic = promiseTopicObserved("PopupNotifications-backgroundShow");
+ wrongBrowserNotification = showNotification(wrongBrowserNotificationObject);
+ yield promiseTopic;
+ is(PopupNotifications.isPanelOpen, false, "panel isn't open");
+ ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked");
+ ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked");
+ ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called");
+ goNext();
+ }
+ },
+ // now select that browser and test to see that the notification appeared
+ { id: "Test#5",
+ run: function () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1];
+ },
+ onShown: function (popup) {
+ checkPopup(popup, wrongBrowserNotificationObject);
+ is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie");
+
+ // switch back to the old browser
+ gBrowser.selectedTab = this.oldSelectedTab;
+ },
+ onHidden: function (popup) {
+ // actually remove the notification to prevent it from reappearing
+ ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch");
+ wrongBrowserNotification.remove();
+ ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered");
+ wrongBrowserNotification = null;
+ }
+ },
+ // test that the removed notification isn't shown on browser re-select
+ { id: "Test#6",
+ run: function* () {
+ let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing");
+ gBrowser.selectedTab = gBrowser.tabs[gBrowser.tabs.length - 1];
+ yield promiseTopic;
+ is(PopupNotifications.isPanelOpen, false, "panel isn't open");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ goNext();
+ }
+ },
+ // Test that two notifications with the same ID result in a single displayed
+ // notification.
+ { id: "Test#7",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ // Show the same notification twice
+ this.notification1 = showNotification(this.notifyObj);
+ this.notification2 = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ this.notification2.remove();
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that two notifications with different IDs are displayed
+ { id: "Test#8",
+ run: function () {
+ this.testNotif1 = new BasicNotification(this.id);
+ this.testNotif1.message += " 1";
+ showNotification(this.testNotif1);
+ this.testNotif2 = new BasicNotification(this.id);
+ this.testNotif2.message += " 2";
+ this.testNotif2.id += "-2";
+ showNotification(this.testNotif2);
+ },
+ onShown: function (popup) {
+ is(popup.childNodes.length, 2, "two notifications are shown");
+ // Trigger the main command for the first notification, and the secondary
+ // for the second. Need to do mainCommand first since the secondaryCommand
+ // triggering is async.
+ triggerMainCommand(popup);
+ is(popup.childNodes.length, 1, "only one notification left");
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.testNotif1.mainActionClicked, "main action #1 was clicked");
+ ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked");
+ ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called");
+
+ ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked");
+ ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked");
+ ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called");
+ }
+ },
+ // Test notification without mainAction
+ { id: "Test#9",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.mainAction = null;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ this.notification.remove();
+ }
+ },
+ // Test two notifications with different anchors
+ { id: "Test#10",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.firstNotification = showNotification(this.notifyObj);
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "-2";
+ this.notifyObj2.anchorID = "addons-notification-icon";
+ // Second showNotification() overrides the first
+ this.secondNotification = showNotification(this.notifyObj2);
+ },
+ onShown: function (popup) {
+ // This also checks that only one element is shown.
+ checkPopup(popup, this.notifyObj2);
+ is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor shouldn't be visible");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ // Remove the notifications
+ this.firstNotification.remove();
+ this.secondNotification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_2.js b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
new file mode 100644
index 000000000..d77098895
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_2.js
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ // Test optional params
+ { id: "Test#1",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.secondaryActions = undefined;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that icons appear
+ { id: "Test#2",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.id = "geolocation";
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ let icon = document.getElementById("geo-notification-icon");
+ isnot(icon.boxObject.width, 0,
+ "geo anchor should be visible after dismissal");
+ this.notification.remove();
+ is(icon.boxObject.width, 0,
+ "geo anchor should not be visible after removal");
+ }
+ },
+
+ // Test that persistence allows the notification to persist across reloads
+ { id: "Test#3",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ persistence: 2
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/")
+ // Next load will remove the notification
+ this.complete = true;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after 3 page loads");
+ ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that a timeout allows the notification to persist across reloads
+ { id: "Test#4",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ // Set a timeout of 10 minutes that should never be hit
+ this.notifyObj.addOptions({
+ timeout: Date.now() + 600000
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ // Next load will hide the notification
+ this.notification.options.timeout = Date.now() - 1;
+ this.complete = true;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after the timeout was passed");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that setting persistWhileVisible allows a visible notification to
+ // persist across location changes
+ { id: "Test#5",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ persistWhileVisible: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ // Notification should persist across location changes
+ this.complete = true;
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after it was dismissed");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+
+ // Test that nested icon nodes correctly activate popups
+ { id: "Test#6",
+ run: function() {
+ // Add a temporary box as the anchor with a button
+ this.box = document.createElement("box");
+ PopupNotifications.iconBox.appendChild(this.box);
+
+ let button = document.createElement("button");
+ button.setAttribute("label", "Please click me!");
+ this.box.appendChild(button);
+
+ // The notification should open up on the box
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.anchorID = this.box.id = "nested-box";
+ this.notifyObj.addOptions({dismissed: true});
+ this.notification = showNotification(this.notifyObj);
+
+ // This test places a normal button in the notification area, which has
+ // standard GTK styling and dimensions. Due to the clip-path, this button
+ // gets clipped off, which makes it necessary to synthesize the mouse click
+ // a little bit downward. To be safe, I adjusted the x-offset with the same
+ // amount.
+ EventUtils.synthesizeMouse(button, 4, 4, {});
+ },
+ onShown: function(popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification.remove();
+ this.box.parentNode.removeChild(this.box);
+ }
+ },
+ // Test that popupnotifications without popups have anchor icons shown
+ { id: "Test#7",
+ run: function* () {
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.anchorID = "geo-notification-icon";
+ notifyObj.addOptions({neverShow: true});
+ let promiseTopic = promiseTopicObserved("PopupNotifications-updateNotShowing");
+ showNotification(notifyObj);
+ yield promiseTopic;
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+ goNext();
+ }
+ },
+ // Test notification "Not Now" menu item
+ { id: "Test#8",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 1);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test notification close button
+ { id: "Test#9",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ EventUtils.synthesizeMouseAtCenter(notification.closebutton, {});
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test notification when chrome is hidden
+ { id: "Test#10",
+ run: function () {
+ window.locationbar.visible = false;
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ window.locationbar.visible = true;
+ }
+ },
+ // Test that dismissed popupnotifications can be opened on about:blank
+ // (where the rest of the identity block is disabled)
+ { id: "Test#11",
+ run: function() {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notifyObj.addOptions({dismissed: true});
+ this.notification = showNotification(this.notifyObj);
+
+ EventUtils.synthesizeMouse(document.getElementById("geo-notification-icon"), 0, 0, {});
+ },
+ onShown: function(popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_3.js b/browser/base/content/test/popupNotifications/browser_popupNotification_3.js
new file mode 100644
index 000000000..33ec3f714
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_3.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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ // Test notification is removed when dismissed if removeOnDismissal is true
+ { id: "Test#1",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ removeOnDismissal: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test multiple notification icons are shown
+ { id: "Test#2",
+ run: function () {
+ this.notifyObj1 = new BasicNotification(this.id);
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notification1 = showNotification(this.notifyObj1);
+
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notification2 = showNotification(this.notifyObj2);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj2);
+
+ // check notifyObj1 anchor icon is showing
+ isnot(document.getElementById("default-notification-icon").boxObject.width, 0,
+ "default anchor should be visible");
+ // check notifyObj2 anchor icon is showing
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ this.notification1.remove();
+ ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered");
+
+ this.notification2.remove();
+ ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that multiple notification icons are removed when switching tabs
+ { id: "Test#3",
+ run: function () {
+ // show the notification on old tab.
+ this.notifyObjOld = new BasicNotification(this.id);
+ this.notifyObjOld.anchorID = "default-notification-icon";
+ this.notificationOld = showNotification(this.notifyObjOld);
+
+ // switch tab
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // show the notification on new tab.
+ this.notifyObjNew = new BasicNotification(this.id);
+ this.notifyObjNew.anchorID = "geo-notification-icon";
+ this.notificationNew = showNotification(this.notifyObjNew);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObjNew);
+
+ // check notifyObjOld anchor icon is removed
+ is(document.getElementById("default-notification-icon").boxObject.width, 0,
+ "default anchor shouldn't be visible");
+ // check notifyObjNew anchor icon is showing
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ this.notificationNew.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ gBrowser.selectedTab = this.oldSelectedTab;
+ this.notificationOld.remove();
+ }
+ },
+ // test security delay - too early
+ { id: "Test#4",
+ run: function () {
+ // Set the security delay to 100s
+ PopupNotifications.buttonDelay = 100000;
+
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+
+ // Wait to see if the main command worked
+ executeSoon(function delayedDismissal() {
+ dismissNotification(popup);
+ });
+
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked because it was too soon");
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+ }
+ },
+ // test security delay - after delay
+ { id: "Test#5",
+ run: function () {
+ // Set the security delay to 10ms
+ PopupNotifications.buttonDelay = 10;
+
+ this.notifyObj = new BasicNotification(this.id);
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+
+ // Wait until after the delay to trigger the main action
+ setTimeout(function delayedDismissal() {
+ triggerMainCommand(popup);
+ }, 500);
+
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.mainActionClicked, "mainAction was clicked after the delay");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback was not triggered");
+ PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
+ }
+ },
+ // reload removes notification
+ { id: "Test#6",
+ run: function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(true, "Notification removed in background tab after reloading");
+ goNext();
+ }
+ };
+ showNotification(notifyObj);
+ executeSoon(function () {
+ gBrowser.selectedBrowser.reload();
+ });
+ }
+ },
+ // location change in background tab removes notification
+ { id: "Test#7",
+ run: function* () {
+ let oldSelectedTab = gBrowser.selectedTab;
+ let newTab = gBrowser.addTab("about:blank");
+ gBrowser.selectedTab = newTab;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ gBrowser.selectedTab = oldSelectedTab;
+ let browser = gBrowser.getBrowserForTab(newTab);
+
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.browser = browser;
+ notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(true, "Notification removed in background tab after reloading");
+ executeSoon(function () {
+ gBrowser.removeTab(newTab);
+ goNext();
+ });
+ }
+ };
+ showNotification(notifyObj);
+ executeSoon(function () {
+ browser.reload();
+ });
+ }
+ },
+ // Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab
+ { id: "Test#8",
+ run: function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ let originalTab = gBrowser.selectedTab;
+ let bgTab = gBrowser.addTab("about:blank");
+ gBrowser.selectedTab = bgTab;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ let anchor = document.createElement("box");
+ anchor.id = "test26-anchor";
+ anchor.className = "notification-anchor-icon";
+ PopupNotifications.iconBox.appendChild(anchor);
+
+ gBrowser.selectedTab = originalTab;
+
+ let fgNotifyObj = new BasicNotification(this.id);
+ fgNotifyObj.anchorID = anchor.id;
+ fgNotifyObj.options.dismissed = true;
+ let fgNotification = showNotification(fgNotifyObj);
+
+ let bgNotifyObj = new BasicNotification(this.id);
+ bgNotifyObj.anchorID = anchor.id;
+ bgNotifyObj.browser = gBrowser.getBrowserForTab(bgTab);
+ // show the notification in the background tab ...
+ let bgNotification = showNotification(bgNotifyObj);
+ // ... and re-show it
+ bgNotification = showNotification(bgNotifyObj);
+
+ ok(fgNotification.id, "notification has id");
+ is(fgNotification.id, bgNotification.id, "notification ids are the same");
+ is(anchor.getAttribute("showing"), "true", "anchor still showing");
+
+ fgNotification.remove();
+ gBrowser.removeTab(bgTab);
+ goNext();
+ }
+ },
+ // location change in an embedded frame should not remove a notification
+ { id: "Test#9",
+ run: function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "data:text/html;charset=utf8,<iframe%20id='iframe'%20src='http://example.com/'>");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(false, "Notification removed from browser when subframe navigated");
+ }
+ };
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ let self = this;
+ let progressListener = {
+ onLocationChange: function onLocationChange() {
+ gBrowser.removeProgressListener(progressListener);
+
+ executeSoon(() => {
+ let notification = PopupNotifications.getNotification(self.notifyObj.id,
+ self.notifyObj.browser);
+ ok(notification != null, "Notification remained when subframe navigated");
+ self.notifyObj.options.eventCallback = undefined;
+
+ notification.remove();
+ });
+ },
+ };
+
+ info("Adding progress listener and performing navigation");
+ gBrowser.addProgressListener(progressListener);
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+ content.document.getElementById("iframe")
+ .setAttribute("src", "http://example.org/");
+ });
+ },
+ onHidden: function () {}
+ },
+ // Popup Notifications should catch exceptions from callbacks
+ { id: "Test#10",
+ run: function () {
+ this.testNotif1 = new BasicNotification(this.id);
+ this.testNotif1.message += " 1";
+ this.notification1 = showNotification(this.testNotif1);
+ this.testNotif1.options.eventCallback = function (eventName) {
+ info("notifyObj1.options.eventCallback: " + eventName);
+ if (eventName == "dismissed") {
+ throw new Error("Oops 1!");
+ }
+ };
+
+ this.testNotif2 = new BasicNotification(this.id);
+ this.testNotif2.message += " 2";
+ this.testNotif2.id += "-2";
+ this.testNotif2.options.eventCallback = function (eventName) {
+ info("notifyObj2.options.eventCallback: " + eventName);
+ if (eventName == "dismissed") {
+ throw new Error("Oops 2!");
+ }
+ };
+ this.notification2 = showNotification(this.testNotif2);
+ },
+ onShown: function (popup) {
+ is(popup.childNodes.length, 2, "two notifications are shown");
+ dismissNotification(popup);
+ },
+ onHidden: function () {
+ this.notification1.remove();
+ this.notification2.remove();
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_4.js b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
new file mode 100644
index 000000000..750ad82fd
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
@@ -0,0 +1,294 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var tests = [
+ // Popup Notifications main actions should catch exceptions from callbacks
+ { id: "Test#1",
+ run: function () {
+ this.testNotif = new ErrorNotification();
+ showNotification(this.testNotif);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.testNotif);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.testNotif.mainActionClicked, "main action has been triggered");
+ }
+ },
+ // Popup Notifications secondary actions should catch exceptions from callbacks
+ { id: "Test#2",
+ run: function () {
+ this.testNotif = new ErrorNotification();
+ showNotification(this.testNotif);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.testNotif);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.testNotif.secondaryActionClicked, "secondary action has been triggered");
+ }
+ },
+ // Existing popup notification shouldn't disappear when adding a dismissed notification
+ { id: "Test#3",
+ run: function () {
+ this.notifyObj1 = new BasicNotification(this.id);
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notification1 = showNotification(this.notifyObj1);
+ },
+ onShown: function (popup) {
+ // Now show a dismissed notification, and check that it doesn't clobber
+ // the showing one.
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notifyObj2.options.dismissed = true;
+ this.notification2 = showNotification(this.notifyObj2);
+
+ checkPopup(popup, this.notifyObj1);
+
+ // check that both anchor icons are showing
+ is(document.getElementById("default-notification-icon").getAttribute("showing"), "true",
+ "notification1 anchor should be visible");
+ is(document.getElementById("geo-notification-icon").getAttribute("showing"), "true",
+ "notification2 anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification1.remove();
+ this.notification2.remove();
+ }
+ },
+ // Showing should be able to modify the popup data
+ { id: "Test#4",
+ run: function() {
+ this.notifyObj = new BasicNotification(this.id);
+ let normalCallback = this.notifyObj.options.eventCallback;
+ this.notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "showing") {
+ this.mainAction.label = "Alternate Label";
+ }
+ normalCallback.call(this, eventName);
+ };
+ showNotification(this.notifyObj);
+ },
+ onShown: function(popup) {
+ // checkPopup checks for the matching label. Note that this assumes that
+ // this.notifyObj.mainAction is the same as notification.mainAction,
+ // which could be a problem if we ever decided to deep-copy.
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function() { }
+ },
+ // Moving a tab to a new window should remove non-swappable notifications.
+ { id: "Test#5",
+ run: function() {
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ let notifyObj = new BasicNotification(this.id);
+ showNotification(notifyObj);
+ let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ whenDelayedStartupFinished(win, function() {
+ let anchor = win.document.getElementById("default-notification-icon");
+ win.PopupNotifications._reshowNotifications(anchor);
+ ok(win.PopupNotifications.panel.childNodes.length == 0,
+ "no notification displayed in new window");
+ ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
+ ok(notifyObj.removedCallbackTriggered, "the removed callback was triggered");
+ win.close();
+ goNext();
+ });
+ }
+ },
+ // Moving a tab to a new window should preserve swappable notifications.
+ { id: "Test#6",
+ run: function* () {
+ let originalBrowser = gBrowser.selectedBrowser;
+ let originalWindow = window;
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ let notifyObj = new BasicNotification(this.id);
+ let originalCallback = notifyObj.options.eventCallback;
+ notifyObj.options.eventCallback = function (eventName) {
+ originalCallback(eventName);
+ return eventName == "swapping";
+ };
+
+ let notification = showNotification(notifyObj);
+ let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ yield whenDelayedStartupFinished(win);
+
+ yield new Promise(resolve => {
+ let originalCallback = notification.options.eventCallback;
+ notification.options.eventCallback = function (eventName) {
+ originalCallback(eventName);
+ if (eventName == "shown") {
+ resolve();
+ }
+ };
+ info("Showing the notification again");
+ notification.reshow();
+ });
+
+ checkPopup(win.PopupNotifications.panel, notifyObj);
+ ok(notifyObj.swappingCallbackTriggered, "the swapping callback was triggered");
+ yield BrowserTestUtils.closeWindow(win);
+
+ // These are the same checks that PopupNotifications.jsm makes before it
+ // allows a notification to open. Do not go to the next test until we are
+ // sure that its attempt to display a notification will not fail.
+ yield BrowserTestUtils.waitForCondition(() => originalBrowser.docShellIsActive,
+ "The browser should be active");
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ yield BrowserTestUtils.waitForCondition(() => fm.activeWindow == originalWindow,
+ "The window should be active")
+
+ goNext();
+ }
+ },
+ // the hideNotNow option
+ { id: "Test#7",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.hideNotNow = true;
+ this.notifyObj.mainAction.dismiss = true;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ // checkPopup verifies that the Not Now item is hidden, and that no separator is added.
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ this.notification.remove();
+ }
+ },
+ // the main action callback can keep the notification.
+ { id: "Test#8",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.mainAction.dismiss = true;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+ ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
+ this.notification.remove();
+ }
+ },
+ // a secondary action callback can keep the notification.
+ { id: "Test#9",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.secondaryActions[0].dismiss = true;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+ ok(!this.notifyObj.removedCallbackTriggered, "removed callback wasn't triggered");
+ this.notification.remove();
+ }
+ },
+ // returning true in the showing callback should dismiss the notification.
+ { id: "Test#10",
+ run: function() {
+ let notifyObj = new BasicNotification(this.id);
+ let originalCallback = notifyObj.options.eventCallback;
+ notifyObj.options.eventCallback = function (eventName) {
+ originalCallback(eventName);
+ return eventName == "showing";
+ };
+
+ let notification = showNotification(notifyObj);
+ ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered");
+ ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered");
+ notification.remove();
+ goNext();
+ }
+ },
+ // panel updates should fire the showing and shown callbacks again.
+ { id: "Test#11",
+ run: function() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+
+ this.notifyObj.showingCallbackTriggered = false;
+ this.notifyObj.shownCallbackTriggered = false;
+
+ // Force an update of the panel. This is typically called
+ // automatically when receiving 'activate' or 'TabSelect' events,
+ // but from a setTimeout, which is inconvenient for the test.
+ PopupNotifications._update();
+
+ checkPopup(popup, this.notifyObj);
+
+ this.notification.remove();
+ },
+ onHidden: function() { }
+ },
+ // A first dismissed notification shouldn't stop _update from showing a second notification
+ { id: "Test#12",
+ run: function () {
+ this.notifyObj1 = new BasicNotification(this.id);
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notifyObj1.options.dismissed = true;
+ this.notification1 = showNotification(this.notifyObj1);
+
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notifyObj2.options.dismissed = true;
+ this.notification2 = showNotification(this.notifyObj2);
+
+ this.notification2.dismissed = false;
+ PopupNotifications._update();
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj2);
+ this.notification1.remove();
+ this.notification2.remove();
+ },
+ onHidden: function(popup) { }
+ },
+ // The anchor icon should be shown for notifications in background windows.
+ { id: "Test#13",
+ run: function() {
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.options.dismissed = true;
+ let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank"));
+ whenDelayedStartupFinished(win, function() {
+ showNotification(notifyObj);
+ let anchor = document.getElementById("default-notification-icon");
+ is(anchor.getAttribute("showing"), "true", "the anchor is shown");
+ win.close();
+ goNext();
+ });
+ }
+ }
+];
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
new file mode 100644
index 000000000..bcc51fcd7
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_checkbox.js
@@ -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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+function checkCheckbox(checkbox, label, checked=false, hidden=false) {
+ is(checkbox.label, label, "Checkbox should have the correct label");
+ is(checkbox.hidden, hidden, "Checkbox should be shown");
+ is(checkbox.checked, checked, "Checkbox should be checked by default");
+}
+
+function checkMainAction(notification, disabled=false) {
+ let mainAction = notification.button;
+ let warningLabel = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-warning");
+ is(warningLabel.hidden, !disabled, "Warning label should be shown");
+ is(mainAction.disabled, disabled, "MainAction should be disabled");
+}
+
+function promiseElementVisible(element) {
+ // HTMLElement.offsetParent is null when the element is not visisble
+ // (or if the element has |position: fixed|). See:
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
+ return BrowserTestUtils.waitForCondition(() => element.offsetParent !== null,
+ "Waiting for element to be visible");
+}
+
+var gNotification;
+
+var tests = [
+ // Test that passing the checkbox field shows the checkbox.
+ { id: "show_checkbox",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ checkCheckbox(notification.checkbox, "This is a checkbox");
+ triggerMainCommand(popup);
+ },
+ onHidden: function () { }
+ },
+
+ // Test checkbox being checked by default
+ { id: "checkbox_checked",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "Check this",
+ checked: true,
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ checkCheckbox(notification.checkbox, "Check this", true);
+ triggerMainCommand(popup);
+ },
+ onHidden: function () { }
+ },
+
+ // Test checkbox passing the checkbox state on mainAction
+ { id: "checkbox_passCheckboxChecked_mainAction",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.mainAction.callback = ({checkboxChecked}) => this.mainActionChecked = checkboxChecked;
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox");
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", true);
+ triggerMainCommand(popup);
+ },
+ onHidden: function () {
+ is(this.mainActionChecked, true, "mainAction callback is passed the correct checkbox value");
+ }
+ },
+
+ // Test checkbox passing the checkbox state on secondaryAction
+ { id: "checkbox_passCheckboxChecked_secondaryAction",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.secondaryActions = [{
+ label: "Test Secondary",
+ accessKey: "T",
+ callback: ({checkboxChecked}) => this.secondaryActionChecked = checkboxChecked,
+ }];
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox");
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", true);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function () {
+ is(this.secondaryActionChecked, true, "secondaryAction callback is passed the correct checkbox value");
+ }
+ },
+
+ // Test checkbox preserving its state through re-opening the doorhanger
+ { id: "checkbox_reopen",
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ checkedState: {
+ disableMainAction: true,
+ warningLabel: "Testing disable",
+ },
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox");
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ dismissNotification(popup);
+ },
+ onHidden: function* (popup) {
+ let icon = document.getElementById("default-notification-icon");
+ let shown = waitForNotificationPanel();
+ EventUtils.synthesizeMouseAtCenter(icon, {});
+ yield shown;
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ checkCheckbox(checkbox, "This is a checkbox", true);
+ checkMainAction(notification, true);
+ gNotification.remove();
+ }
+ },
+];
+
+// Test checkbox disabling the main action in different combinations
+["checkedState", "uncheckedState"].forEach(function (state) {
+ [true, false].forEach(function (checked) {
+ tests.push(
+ { id: `checkbox_disableMainAction_${state}_${checked ? 'checked' : 'unchecked'}`,
+ run: function () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.checkbox = {
+ label: "This is a checkbox",
+ checked: checked,
+ [state]: {
+ disableMainAction: true,
+ warningLabel: "Testing disable",
+ },
+ };
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ let checkbox = notification.checkbox;
+ let disabled = (state === "checkedState" && checked) ||
+ (state === "uncheckedState" && !checked);
+
+ checkCheckbox(checkbox, "This is a checkbox", checked);
+ checkMainAction(notification, disabled);
+ yield promiseElementVisible(checkbox);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", !checked);
+ checkMainAction(notification, !disabled);
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ checkCheckbox(checkbox, "This is a checkbox", checked);
+ checkMainAction(notification, disabled);
+
+ // Unblock the main command if it's currently disabled.
+ if (disabled) {
+ EventUtils.synthesizeMouseAtCenter(checkbox, {});
+ }
+ triggerMainCommand(popup);
+ },
+ onHidden: function () { }
+ }
+ );
+ });
+});
+
diff --git a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
new file mode 100644
index 000000000..0f5b57ced
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+}
+
+var tests = [
+ // Test that for persistent notifications,
+ // the secondary action is triggered by pressing the escape key.
+ { id: "Test#1",
+ run() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.persistent = true;
+ showNotification(this.notifyObj);
+ },
+ onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ },
+ onHidden(popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked");
+ ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that for non-persistent notifications, the escape key dismisses the notification.
+ { id: "Test#2",
+ *run() {
+ yield waitForWindowReadyForPopupNotifications(window);
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ },
+ onHidden(popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked");
+ ok(!this.notifyObj.secondaryActionClicked, "secondaryAction was not clicked");
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ ok(!this.notifyObj.removedCallbackTriggered, "removed callback was not triggered");
+ this.notification.remove();
+ }
+ },
+ // Test that the space key on an anchor element focuses an active notification
+ { id: "Test#3",
+ *run() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notifyObj.addOptions({
+ persistent: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ *onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ let anchor = document.getElementById(this.notifyObj.anchorID);
+ anchor.focus();
+ is(document.activeElement, anchor);
+ EventUtils.synthesizeKey(" ", {});
+ is(document.activeElement, popup.childNodes[0].button);
+ this.notification.remove();
+ },
+ onHidden(popup) { }
+ },
+];
diff --git a/browser/base/content/test/popupNotifications/browser_reshow_in_background.js b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js
new file mode 100644
index 000000000..6f415f62e
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_reshow_in_background.js
@@ -0,0 +1,52 @@
+"use strict";
+
+/**
+ * Tests that when PopupNotifications for background tabs are reshown, they
+ * don't show up in the foreground tab, but only in the background tab that
+ * they belong to.
+ */
+add_task(function* test_background_notifications_dont_reshow_in_foreground() {
+ // Our initial tab will be A. Let's open two more tabs B and C, but keep
+ // A selected. Then, we'll trigger a PopupNotification in C, and then make
+ // it reshow.
+ let tabB = gBrowser.addTab("about:blank");
+ let tabC = gBrowser.addTab("about:blank");
+
+ let seenEvents = [];
+
+ let options = {
+ dismissed: false,
+ eventCallback(popupEvent) {
+ seenEvents.push(popupEvent);
+ },
+ };
+
+ let notification =
+ PopupNotifications.show(tabC.linkedBrowser, "test-notification",
+ "", "plugins-notification-icon",
+ null, null, options);
+ Assert.deepEqual(seenEvents, [], "Should have seen no events yet.");
+
+ yield BrowserTestUtils.switchTab(gBrowser, tabB);
+ Assert.deepEqual(seenEvents, [], "Should have seen no events yet.");
+
+ notification.reshow();
+ Assert.deepEqual(seenEvents, [], "Should have seen no events yet.");
+
+ let panelShown =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ yield BrowserTestUtils.switchTab(gBrowser, tabC);
+ yield panelShown;
+
+ Assert.equal(seenEvents.length, 2, "Should have seen two events.");
+ Assert.equal(seenEvents[0], "showing", "Should have said popup was showing.");
+ Assert.equal(seenEvents[1], "shown", "Should have said popup was shown.");
+
+ let panelHidden =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ PopupNotifications.remove(notification);
+ yield panelHidden;
+
+ yield BrowserTestUtils.removeTab(tabB);
+ yield BrowserTestUtils.removeTab(tabC);
+});
diff --git a/browser/base/content/test/popupNotifications/head.js b/browser/base/content/test/popupNotifications/head.js
new file mode 100644
index 000000000..4a803d6af
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/head.js
@@ -0,0 +1,303 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ return new Promise(resolve => {
+ info("Waiting for delayed startup to finish");
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ if (aCallback) {
+ executeSoon(aCallback);
+ }
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+}
+
+/**
+ * Allows waiting for an observer notification once.
+ *
+ * @param topic
+ * Notification topic to observe.
+ *
+ * @return {Promise}
+ * @resolves The array [subject, data] from the observed notification.
+ * @rejects Never.
+ */
+function promiseTopicObserved(topic)
+{
+ let deferred = Promise.defer();
+ info("Waiting for observer topic " + topic);
+ Services.obs.addObserver(function PTO_observe(subject, topic, data) {
+ Services.obs.removeObserver(PTO_observe, topic);
+ deferred.resolve([subject, data]);
+ }, topic, false);
+ return deferred.promise;
+}
+
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url)
+{
+ let browser = tab.linkedBrowser;
+
+ if (url) {
+ browser.loadURI(url);
+ }
+
+ return BrowserTestUtils.browserLoaded(browser, false, url);
+}
+
+const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay");
+
+function setup() {
+ // Disable transitions as they slow the test down and we want to click the
+ // mouse buttons in a predictable location.
+
+ registerCleanupFunction(() => {
+ PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
+ });
+}
+
+function goNext() {
+ executeSoon(() => executeSoon(Task.async(runNextTest)));
+}
+
+function* runNextTest() {
+ if (tests.length == 0) {
+ executeSoon(finish);
+ return;
+ }
+
+ let nextTest = tests.shift();
+ if (nextTest.onShown) {
+ let shownState = false;
+ onPopupEvent("popupshowing", function () {
+ info("[" + nextTest.id + "] popup showing");
+ });
+ onPopupEvent("popupshown", function () {
+ shownState = true;
+ info("[" + nextTest.id + "] popup shown");
+ Task.spawn(() => nextTest.onShown(this))
+ .then(undefined, ex => Assert.ok(false, "onShown failed: " + ex));
+ });
+ onPopupEvent("popuphidden", function () {
+ info("[" + nextTest.id + "] popup hidden");
+ Task.spawn(() => nextTest.onHidden(this))
+ .then(() => goNext(), ex => Assert.ok(false, "onHidden failed: " + ex));
+ }, () => shownState);
+ info("[" + nextTest.id + "] added listeners; panel is open: " + PopupNotifications.isPanelOpen);
+ }
+
+ info("[" + nextTest.id + "] running test");
+ yield nextTest.run();
+}
+
+function showNotification(notifyObj) {
+ info("Showing notification " + notifyObj.id);
+ return PopupNotifications.show(notifyObj.browser,
+ notifyObj.id,
+ notifyObj.message,
+ notifyObj.anchorID,
+ notifyObj.mainAction,
+ notifyObj.secondaryActions,
+ notifyObj.options);
+}
+
+function dismissNotification(popup) {
+ info("Dismissing notification " + popup.childNodes[0].id);
+ executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
+}
+
+function BasicNotification(testId) {
+ this.browser = gBrowser.selectedBrowser;
+ this.id = "test-notification-" + testId;
+ this.message = "This is popup notification for " + testId;
+ this.anchorID = null;
+ this.mainAction = {
+ label: "Main Action",
+ accessKey: "M",
+ callback: () => this.mainActionClicked = true
+ };
+ this.secondaryActions = [
+ {
+ label: "Secondary Action",
+ accessKey: "S",
+ callback: () => this.secondaryActionClicked = true
+ }
+ ];
+ this.options = {
+ eventCallback: eventName => {
+ switch (eventName) {
+ case "dismissed":
+ this.dismissalCallbackTriggered = true;
+ break;
+ case "showing":
+ this.showingCallbackTriggered = true;
+ break;
+ case "shown":
+ this.shownCallbackTriggered = true;
+ break;
+ case "removed":
+ this.removedCallbackTriggered = true;
+ break;
+ case "swapping":
+ this.swappingCallbackTriggered = true;
+ break;
+ }
+ }
+ };
+}
+
+BasicNotification.prototype.addOptions = function(options) {
+ for (let [name, value] of Object.entries(options))
+ this.options[name] = value;
+};
+
+function ErrorNotification() {
+ this.mainAction.callback = () => {
+ this.mainActionClicked = true;
+ throw new Error("Oops!");
+ };
+ this.secondaryActions[0].callback = () => {
+ this.secondaryActionClicked = true;
+ throw new Error("Oops!");
+ };
+}
+
+ErrorNotification.prototype = new BasicNotification();
+ErrorNotification.prototype.constructor = ErrorNotification;
+
+function checkPopup(popup, notifyObj) {
+ info("Checking notification " + notifyObj.id);
+
+ ok(notifyObj.showingCallbackTriggered, "showing callback was triggered");
+ ok(notifyObj.shownCallbackTriggered, "shown callback was triggered");
+
+ let notifications = popup.childNodes;
+ is(notifications.length, 1, "one notification displayed");
+ let notification = notifications[0];
+ if (!notification)
+ return;
+ let icon = document.getAnonymousElementByAttribute(notification, "class",
+ "popup-notification-icon");
+ if (notifyObj.id == "geolocation") {
+ isnot(icon.boxObject.width, 0, "icon for geo displayed");
+ ok(popup.anchorNode.classList.contains("notification-anchor-icon"),
+ "notification anchored to icon");
+ }
+ is(notification.getAttribute("label"), notifyObj.message, "message matches");
+ is(notification.id, notifyObj.id + "-notification", "id matches");
+ if (notifyObj.mainAction) {
+ is(notification.getAttribute("buttonlabel"), notifyObj.mainAction.label,
+ "main action label matches");
+ is(notification.getAttribute("buttonaccesskey"),
+ notifyObj.mainAction.accessKey, "main action accesskey matches");
+ }
+ let actualSecondaryActions =
+ Array.filter(notification.childNodes, child => child.nodeName == "menuitem");
+ let secondaryActions = notifyObj.secondaryActions || [];
+ let actualSecondaryActionsCount = actualSecondaryActions.length;
+ if (notifyObj.options.hideNotNow) {
+ is(notification.getAttribute("hidenotnow"), "true", "'Not Now' item hidden");
+ if (secondaryActions.length)
+ is(notification.lastChild.tagName, "menuitem", "no menuseparator");
+ }
+ else if (secondaryActions.length) {
+ is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
+ }
+ is(actualSecondaryActionsCount, secondaryActions.length,
+ actualSecondaryActions.length + " secondary actions");
+ secondaryActions.forEach(function (a, i) {
+ is(actualSecondaryActions[i].getAttribute("label"), a.label,
+ "label for secondary action " + i + " matches");
+ is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey,
+ "accessKey for secondary action " + i + " matches");
+ });
+}
+
+XPCOMUtils.defineLazyGetter(this, "gActiveListeners", () => {
+ let listeners = new Map();
+ registerCleanupFunction(() => {
+ for (let [listener, eventName] of listeners) {
+ PopupNotifications.panel.removeEventListener(eventName, listener, false);
+ }
+ });
+ return listeners;
+});
+
+function onPopupEvent(eventName, callback, condition) {
+ let listener = event => {
+ if (event.target != PopupNotifications.panel ||
+ (condition && !condition()))
+ return;
+ PopupNotifications.panel.removeEventListener(eventName, listener, false);
+ gActiveListeners.delete(listener);
+ executeSoon(() => callback.call(PopupNotifications.panel));
+ }
+ gActiveListeners.set(listener, eventName);
+ PopupNotifications.panel.addEventListener(eventName, listener, false);
+}
+
+function waitForNotificationPanel() {
+ return new Promise(resolve => {
+ onPopupEvent("popupshown", function() {
+ resolve(this);
+ });
+ });
+}
+
+function triggerMainCommand(popup) {
+ let notifications = popup.childNodes;
+ ok(notifications.length > 0, "at least one notification displayed");
+ let notification = notifications[0];
+ info("Triggering main command for notification " + notification.id);
+ // 20, 10 so that the inner button is hit
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+}
+
+function triggerSecondaryCommand(popup, index) {
+ let notifications = popup.childNodes;
+ ok(notifications.length > 0, "at least one notification displayed");
+ let notification = notifications[0];
+ info("Triggering secondary command for notification " + notification.id);
+ // Cancel the arrow panel slide-in transition (bug 767133) such that
+ // it won't interfere with us interacting with the dropdown.
+ document.getAnonymousNodes(popup)[0].style.transition = "none";
+
+ notification.button.focus();
+
+ popup.addEventListener("popupshown", function handle() {
+ popup.removeEventListener("popupshown", handle, false);
+ info("Command popup open for notification " + notification.id);
+ // Press down until the desired command is selected
+ for (let i = 0; i <= index; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ }
+ // Activate
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ // One down event to open the popup
+ info("Open the popup to trigger secondary command for notification " + notification.id);
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.includes("Mac") });
+}
diff --git a/browser/base/content/test/popups/browser.ini b/browser/base/content/test/popups/browser.ini
new file mode 100644
index 000000000..46a32783b
--- /dev/null
+++ b/browser/base/content/test/popups/browser.ini
@@ -0,0 +1,4 @@
+[browser_popupUI.js]
+[browser_popup_blocker.js]
+support-files = popup_blocker.html
+skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures
diff --git a/browser/base/content/test/popups/browser_popupUI.js b/browser/base/content/test/popups/browser_popupUI.js
new file mode 100644
index 000000000..7c6805f60
--- /dev/null
+++ b/browser/base/content/test/popups/browser_popupUI.js
@@ -0,0 +1,37 @@
+function test() {
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({ set: [[ "dom.disable_open_during_load", false ]] });
+
+ let popupOpened = BrowserTestUtils.waitForNewWindow(true, "about:blank");
+ BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "data:text/html,<html><script>popup=open('about:blank','','width=300,height=200')</script>"
+ );
+ popupOpened.then((win) => testPopupUI(win));
+}
+
+function testPopupUI(win) {
+ var doc = win.document;
+
+ ok(win.gURLBar, "location bar exists in the popup");
+ isnot(win.gURLBar.clientWidth, 0, "location bar is visible in the popup");
+ ok(win.gURLBar.readOnly, "location bar is read-only in the popup");
+ isnot(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true",
+ "'open location' command is not disabled in the popup");
+
+ let historyButton = doc.getAnonymousElementByAttribute(win.gURLBar, "anonid",
+ "historydropmarker");
+ is(historyButton.clientWidth, 0, "history dropdown button is hidden in the popup");
+
+ EventUtils.synthesizeKey("t", { accelKey: true }, win);
+ is(win.gBrowser.browsers.length, 1, "Accel+T doesn't open a new tab in the popup");
+ is(gBrowser.browsers.length, 3, "Accel+T opened a new tab in the parent window");
+ gBrowser.removeCurrentTab();
+
+ EventUtils.synthesizeKey("w", { accelKey: true }, win);
+ ok(win.closed, "Accel+W closes the popup");
+
+ if (!win.closed)
+ win.close();
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/popups/browser_popup_blocker.js b/browser/base/content/test/popups/browser_popup_blocker.js
new file mode 100644
index 000000000..8cadfea57
--- /dev/null
+++ b/browser/base/content/test/popups/browser_popup_blocker.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const baseURL = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+
+function clearAllPermissionsByPrefix(aPrefix) {
+ let perms = Services.perms.enumerator;
+ while (perms.hasMoreElements()) {
+ let perm = perms.getNext();
+ if (perm.type.startsWith(aPrefix)) {
+ Services.perms.removePermission(perm);
+ }
+ }
+}
+
+add_task(function* test_opening_blocked_popups() {
+ // Enable the popup blocker.
+ yield SpecialPowers.pushPrefEnv({set: [["dom.disable_open_during_load", true]]});
+
+ // Open the test page.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, baseURL + "popup_blocker.html");
+
+ // Wait for the popup-blocked notification.
+ let notification;
+ yield BrowserTestUtils.waitForCondition(() =>
+ notification = gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
+
+ // Show the menu.
+ let popupShown = BrowserTestUtils.waitForEvent(window, "popupshown");
+ let popupFilled = BrowserTestUtils.waitForMessage(gBrowser.selectedBrowser.messageManager,
+ "PopupBlocking:ReplyGetBlockedPopupList");
+ notification.querySelector("button").doCommand();
+ let popup_event = yield popupShown;
+ let menu = popup_event.target;
+ is(menu.id, "blockedPopupOptions", "Blocked popup menu shown");
+
+ yield popupFilled;
+ // The menu is filled on the same message that we waited for, so let's ensure that it
+ // had a chance of running before this test code.
+ yield new Promise(resolve => executeSoon(resolve));
+
+ // Check the menu contents.
+ let sep = menu.querySelector("menuseparator");
+ let popupCount = 0;
+ for (let i = sep.nextElementSibling; i; i = i.nextElementSibling) {
+ popupCount++;
+ }
+ is(popupCount, 2, "Two popups were blocked");
+
+ // Pressing "allow" should open all blocked popups.
+ let popupTabs = [];
+ function onTabOpen(event) {
+ popupTabs.push(event.target);
+ }
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen);
+
+ // Press the button.
+ let allow = menu.querySelector("[observes='blockedPopupAllowSite']");
+ allow.doCommand();
+ yield BrowserTestUtils.waitForCondition(() =>
+ popupTabs.length == 2 &&
+ popupTabs.every(aTab => aTab.linkedBrowser.currentURI.spec != "about:blank"));
+
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen);
+
+ is(popupTabs[0].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,a", "Popup a");
+ is(popupTabs[1].linkedBrowser.currentURI.spec, "data:text/plain;charset=utf-8,b", "Popup b");
+
+ // Clean up.
+ gBrowser.removeTab(tab);
+ for (let popup of popupTabs) {
+ gBrowser.removeTab(popup);
+ }
+ clearAllPermissionsByPrefix("popup");
+ // Ensure the menu closes.
+ menu.hidePopup();
+});
+
+add_task(function* check_icon_hides() {
+ // Enable the popup blocker.
+ yield SpecialPowers.pushPrefEnv({set: [["dom.disable_open_during_load", true]]});
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, baseURL + "popup_blocker.html");
+
+ let button = document.getElementById("page-report-button");
+ yield BrowserTestUtils.waitForCondition(() =>
+ gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked"));
+ ok(!button.hidden, "Button should be visible");
+
+ let otherPageLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ openLinkIn(baseURL, "current", {});
+ yield otherPageLoaded;
+ ok(button.hidden, "Button should have hidden again after another page loaded.");
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/popups/popup_blocker.html b/browser/base/content/test/popups/popup_blocker.html
new file mode 100644
index 000000000..6e2b7db15
--- /dev/null
+++ b/browser/base/content/test/popups/popup_blocker.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>Page creating two popups</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ window.open("data:text/plain;charset=utf-8,a", "a");
+ window.open("data:text/plain;charset=utf-8,b", "b");
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/referrer/.eslintrc.js b/browser/base/content/test/referrer/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/referrer/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/referrer/browser.ini b/browser/base/content/test/referrer/browser.ini
new file mode 100644
index 000000000..13b712850
--- /dev/null
+++ b/browser/base/content/test/referrer/browser.ini
@@ -0,0 +1,24 @@
+[DEFAULT]
+support-files =
+ file_referrer_policyserver.sjs
+ file_referrer_policyserver_attr.sjs
+ file_referrer_testserver.sjs
+ head.js
+
+[browser_referrer_middle_click.js]
+[browser_referrer_middle_click_in_container.js]
+[browser_referrer_open_link_in_private.js]
+skip-if = os == 'linux' # Bug 1145199
+[browser_referrer_open_link_in_tab.js]
+skip-if = os == 'linux' # Bug 1144816
+[browser_referrer_open_link_in_window.js]
+skip-if = os == 'linux' # Bug 1145199
+[browser_referrer_open_link_in_window_in_container.js]
+skip-if = os == 'linux' # Bug 1145199
+[browser_referrer_simple_click.js]
+[browser_referrer_open_link_in_container_tab.js]
+skip-if = os == 'linux' # Bug 1144816
+[browser_referrer_open_link_in_container_tab2.js]
+skip-if = os == 'linux' # Bug 1144816
+[browser_referrer_open_link_in_container_tab3.js]
+skip-if = os == 'linux' # Bug 1144816
diff --git a/browser/base/content/test/referrer/browser_referrer_middle_click.js b/browser/base/content/test/referrer/browser_referrer_middle_click.js
new file mode 100644
index 000000000..e6e01c6a3
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_middle_click.js
@@ -0,0 +1,20 @@
+// Tests referrer on middle-click navigation.
+// Middle-clicks on the link, which opens it in a new tab.
+
+function startMiddleClickTestCase(aTestNumber) {
+ info("browser_referrer_middle_click: " +
+ getReferrerTestDescription(aTestNumber));
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => {
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startMiddleClickTestCase);
+ });
+ });
+
+ clickTheLink(gTestWindow, "testlink", {button: 1});
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startMiddleClickTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js b/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js
new file mode 100644
index 000000000..e89b891f3
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_middle_click_in_container.js
@@ -0,0 +1,27 @@
+// Tests referrer on middle-click navigation.
+// Middle-clicks on the link, which opens it in a new tab, same container.
+
+function startMiddleClickTestCase(aTestNumber) {
+ info("browser_referrer_middle_click: " +
+ getReferrerTestDescription(aTestNumber));
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ BrowserTestUtils.switchTab(gTestWindow.gBrowser, aNewTab).then(() => {
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startMiddleClickTestCase,
+ { userContextId: 3 });
+ });
+ });
+
+ clickTheLink(gTestWindow, "testlink", {button: 1});
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startMiddleClickTestCase, { userContextId: 3 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js
new file mode 100644
index 000000000..deaf90fb9
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab.js
@@ -0,0 +1,59 @@
+// Tests referrer on context menu navigation - open link in new container tab.
+// Selects "open link in new container tab" from the context menu.
+
+function getReferrerTest(aTestNumber) {
+ let test = _referrerTests[aTestNumber];
+ if (test) {
+ // We want all the referrer tests to fail!
+ test.result = "";
+ }
+
+ return test;
+}
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_container_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase);
+ });
+
+ let menu = gTestWindow.document.getElementById("context-openlinkinusercontext-menu");
+
+ let menupopup = menu.menupopup;
+ menu.addEventListener("popupshown", function onPopupShown() {
+ menu.removeEventListener("popupshown", onPopupShown);
+
+ is(menupopup.nodeType, Node.ELEMENT_NODE, "We have a menupopup.");
+ ok(menupopup.firstChild, "We have a first container entry.");
+
+ let firstContext = menupopup.firstChild;
+ is(firstContext.nodeType, Node.ELEMENT_NODE, "We have a first container entry.");
+ ok(firstContext.hasAttribute("data-usercontextid"), "We have a usercontextid value.");
+
+ aContextMenu.addEventListener("popuphidden", function onPopupHidden() {
+ aContextMenu.removeEventListener("popuphidden", onPopupHidden);
+ firstContext.doCommand();
+ });
+
+ aContextMenu.hidePopup();
+ });
+
+ menupopup.showPopup();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase);
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js
new file mode 100644
index 000000000..77a5645c6
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab2.js
@@ -0,0 +1,31 @@
+// Tests referrer on context menu navigation - open link in new container tab.
+// Selects "open link in new container tab" from the context menu.
+
+// The test runs from a container ID 1.
+// Output: we have the correct referrer policy applied.
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_container_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase, { userContextId: 1 });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkincontainertab");
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase, { userContextId: 1 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js
new file mode 100644
index 000000000..c0a73d828
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_container_tab3.js
@@ -0,0 +1,63 @@
+// Tests referrer on context menu navigation - open link in new container tab.
+// Selects "open link in new container tab" from the context menu.
+
+// The test runs from a container ID 2.
+// Output: we have no referrer.
+
+function getReferrerTest(aTestNumber) {
+ let test = _referrerTests[aTestNumber];
+ if (test) {
+ // We want all the referrer tests to fail!
+ test.result = "";
+ }
+
+ return test;
+}
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_container_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase, { userContextId: 2 });
+ });
+
+ let menu = gTestWindow.document.getElementById("context-openlinkinusercontext-menu");
+
+ let menupopup = menu.menupopup;
+ menu.addEventListener("popupshown", function onPopupShown() {
+ menu.removeEventListener("popupshown", onPopupShown);
+
+ is(menupopup.nodeType, Node.ELEMENT_NODE, "We have a menupopup.");
+ ok(menupopup.firstChild, "We have a first container entry.");
+
+ let firstContext = menupopup.firstChild;
+ is(firstContext.nodeType, Node.ELEMENT_NODE, "We have a first container entry.");
+ ok(firstContext.hasAttribute("data-usercontextid"), "We have a usercontextid value.");
+ is("0", firstContext.getAttribute("data-usercontextid"), "We have the right usercontextid value.");
+
+ aContextMenu.addEventListener("popuphidden", function onPopupHidden() {
+ aContextMenu.removeEventListener("popuphidden", onPopupHidden);
+ firstContext.doCommand();
+ });
+
+ aContextMenu.hidePopup();
+ });
+
+ menupopup.showPopup();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase, { userContextId: 2 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
new file mode 100644
index 000000000..8f12e3824
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_private.js
@@ -0,0 +1,22 @@
+// Tests referrer on context menu navigation - open link in new private window.
+// Selects "open link in new private window" from the context menu.
+
+function startNewPrivateWindowTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_private: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ newWindowOpened().then(function(aNewWindow) {
+ BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+ startNewPrivateWindowTestCase);
+ });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkprivate");
+ });
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewPrivateWindowTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js
new file mode 100644
index 000000000..03119cb57
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_tab.js
@@ -0,0 +1,21 @@
+// Tests referrer on context menu navigation - open link in new tab.
+// Selects "open link in new tab" from the context menu.
+
+function startNewTabTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_tab: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ someTabLoaded(gTestWindow).then(function(aNewTab) {
+ gTestWindow.gBrowser.selectedTab = aNewTab;
+ checkReferrerAndStartNextTest(aTestNumber, null, aNewTab,
+ startNewTabTestCase);
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlinkintab");
+ });
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewTabTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
new file mode 100644
index 000000000..81e7b2648
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window.js
@@ -0,0 +1,22 @@
+// Tests referrer on context menu navigation - open link in new window.
+// Selects "open link in new window" from the context menu.
+
+function startNewWindowTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_window: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ newWindowOpened().then(function(aNewWindow) {
+ BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+ startNewWindowTestCase);
+ });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink");
+ });
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewWindowTestCase);
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js b/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js
new file mode 100644
index 000000000..d5ce87952
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_open_link_in_window_in_container.js
@@ -0,0 +1,32 @@
+// Tests referrer on context menu navigation - open link in new window.
+// Selects "open link in new window" from the context menu.
+
+// This test runs from a container tab. The new tab/window will be loaded in
+// the same container.
+
+function startNewWindowTestCase(aTestNumber) {
+ info("browser_referrer_open_link_in_window: " +
+ getReferrerTestDescription(aTestNumber));
+ contextMenuOpened(gTestWindow, "testlink").then(function(aContextMenu) {
+ newWindowOpened().then(function(aNewWindow) {
+ BrowserTestUtils.firstBrowserLoaded(aNewWindow, false).then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, aNewWindow, null,
+ startNewWindowTestCase,
+ { userContextId: 1 });
+ });
+ });
+
+ doContextMenuCommand(gTestWindow, aContextMenu, "context-openlink");
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv(
+ {set: [["privacy.userContext.enabled", true]]},
+ function() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startNewWindowTestCase, { userContextId: 1 });
+ });
+}
diff --git a/browser/base/content/test/referrer/browser_referrer_simple_click.js b/browser/base/content/test/referrer/browser_referrer_simple_click.js
new file mode 100644
index 000000000..7f3784e64
--- /dev/null
+++ b/browser/base/content/test/referrer/browser_referrer_simple_click.js
@@ -0,0 +1,20 @@
+// Tests referrer on simple click navigation.
+// Clicks on the link, which opens it in the same tab.
+
+function startSimpleClickTestCase(aTestNumber) {
+ info("browser_referrer_simple_click: " +
+ getReferrerTestDescription(aTestNumber));
+ BrowserTestUtils.browserLoaded(gTestWindow.gBrowser.selectedBrowser, false,
+ (url) => url.endsWith("file_referrer_testserver.sjs"))
+ .then(function() {
+ checkReferrerAndStartNextTest(aTestNumber, null, null,
+ startSimpleClickTestCase);
+ });
+
+ clickTheLink(gTestWindow, "testlink", {});
+}
+
+function test() {
+ requestLongerTimeout(10); // slowwww shutdown on e10s
+ startReferrerTest(startSimpleClickTestCase);
+}
diff --git a/browser/base/content/test/referrer/file_referrer_policyserver.sjs b/browser/base/content/test/referrer/file_referrer_policyserver.sjs
new file mode 100644
index 000000000..e07965675
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_policyserver.sjs
@@ -0,0 +1,37 @@
+/**
+ * Renders a link with the provided referrer policy.
+ * Used in browser_referrer_*.js, bug 1113431.
+ * Arguments: ?scheme=http://&policy=origin&rel=noreferrer
+ */
+function handleRequest(request, response)
+{
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let scheme = query.get("scheme");
+ let policy = query.get("policy");
+ let rel = query.get("rel");
+
+ let linkUrl = scheme +
+ "test1.example.com/browser/browser/base/content/test/referrer/" +
+ "file_referrer_testserver.sjs";
+ let metaReferrerTag =
+ policy ? `<meta name='referrer' content='${policy}'>` : "";
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ ${metaReferrerTag}
+ <title>Test referrer</title>
+ </head>
+ <body>
+ <a id='testlink' href='${linkUrl}' ${rel ? ` rel='${rel}'` : ""}>
+ referrer test link</a>
+ </body>
+ </html>`;
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(html);
+}
diff --git a/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs b/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs
new file mode 100644
index 000000000..25a58188a
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_policyserver_attr.sjs
@@ -0,0 +1,36 @@
+/**
+ * Renders a link with the provided referrer policy.
+ * Used in browser_referrer_*.js, bug 1113431.
+ * Arguments: ?scheme=http://&policy=origin&rel=noreferrer
+ */
+function handleRequest(request, response)
+{
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let scheme = query.get("scheme");
+ let policy = query.get("policy");
+ let rel = query.get("rel");
+
+ let linkUrl = scheme +
+ "test1.example.com/browser/browser/base/content/test/referrer/" +
+ "file_referrer_testserver.sjs";
+ let referrerPolicy =
+ policy ? `referrerpolicy="${policy}"` : "";
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Test referrer</title>
+ </head>
+ <body>
+ <a id='testlink' href='${linkUrl}' ${referrerPolicy} ${rel ? ` rel='${rel}'` : ""}>
+ referrer test link</a>
+ </body>
+ </html>`;
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(html);
+}
diff --git a/browser/base/content/test/referrer/file_referrer_testserver.sjs b/browser/base/content/test/referrer/file_referrer_testserver.sjs
new file mode 100644
index 000000000..0cfc53b2c
--- /dev/null
+++ b/browser/base/content/test/referrer/file_referrer_testserver.sjs
@@ -0,0 +1,31 @@
+/**
+ * Renders the HTTP Referer header up to the second path slash.
+ * Used in browser_referrer_*.js, bug 1113431.
+ */
+function handleRequest(request, response)
+{
+ let referrer = "";
+ try {
+ referrer = request.getHeader("referer");
+ } catch (e) {
+ referrer = "";
+ }
+
+ // Strip it past the first path slash. Makes tests easier to read.
+ referrer = referrer.split("/").slice(0, 4).join("/");
+
+ let html = `<!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Test referrer</title>
+ </head>
+ <body>
+ <div id='testdiv'>${referrer}</div>
+ </body>
+ </html>`;
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(html);
+}
diff --git a/browser/base/content/test/referrer/head.js b/browser/base/content/test/referrer/head.js
new file mode 100644
index 000000000..1a5d5b051
--- /dev/null
+++ b/browser/base/content/test/referrer/head.js
@@ -0,0 +1,265 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
+ "resource://testing-common/BrowserTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentTask",
+ "resource://testing-common/ContentTask.jsm");
+
+const REFERRER_URL_BASE = "/browser/browser/base/content/test/referrer/";
+const REFERRER_POLICYSERVER_URL =
+ "test1.example.com" + REFERRER_URL_BASE + "file_referrer_policyserver.sjs";
+const REFERRER_POLICYSERVER_URL_ATTRIBUTE =
+ "test1.example.com" + REFERRER_URL_BASE + "file_referrer_policyserver_attr.sjs";
+
+SpecialPowers.pushPrefEnv({"set": [['network.http.enablePerElementReferrer', true]]});
+
+var gTestWindow = null;
+var rounds = 0;
+
+// We test that the UI code propagates three pieces of state - the referrer URI
+// itself, the referrer policy, and the triggering principal. After that, we
+// trust nsIWebNavigation to do the right thing with the info it's given, which
+// is covered more exhaustively by dom/base/test/test_bug704320.html (which is
+// a faster content-only test). So, here, we limit ourselves to cases that
+// would break when the UI code drops either of these pieces; we don't try to
+// duplicate the entire cross-product test in bug 704320 - that would be slow,
+// especially when we're opening a new window for each case.
+var _referrerTests = [
+ // 1. Normal cases - no referrer policy, no special attributes.
+ // We expect a full referrer normally, and no referrer on downgrade.
+ {
+ fromScheme: "http://",
+ toScheme: "http://",
+ result: "http://test1.example.com/browser" // full referrer
+ },
+ {
+ fromScheme: "https://",
+ toScheme: "http://",
+ result: "" // no referrer when downgrade
+ },
+ // 2. Origin referrer policy - we expect an origin referrer,
+ // even on downgrade. But rel=noreferrer trumps this.
+ {
+ fromScheme: "https://",
+ toScheme: "http://",
+ policy: "origin",
+ result: "https://test1.example.com/" // origin, even on downgrade
+ },
+ {
+ fromScheme: "https://",
+ toScheme: "http://",
+ policy: "origin",
+ rel: "noreferrer",
+ result: "" // rel=noreferrer trumps meta-referrer
+ },
+ // 3. XXX: using no-referrer here until we support all attribute values (bug 1178337)
+ // Origin-when-cross-origin policy - this depends on the triggering
+ // principal. We expect full referrer for same-origin requests,
+ // and origin referrer for cross-origin requests.
+ {
+ fromScheme: "https://",
+ toScheme: "https://",
+ policy: "no-referrer",
+ result: "" // same origin https://test1.example.com/browser
+ },
+ {
+ fromScheme: "http://",
+ toScheme: "https://",
+ policy: "no-referrer",
+ result: "" // cross origin http://test1.example.com
+ },
+];
+
+/**
+ * Returns the test object for a given test number.
+ * @param aTestNumber The test number - 0, 1, 2, ...
+ * @return The test object, or undefined if the number is out of range.
+ */
+function getReferrerTest(aTestNumber) {
+ return _referrerTests[aTestNumber];
+}
+
+/**
+ * Returns a brief summary of the test, for logging.
+ * @param aTestNumber The test number - 0, 1, 2...
+ * @return The test description.
+ */
+function getReferrerTestDescription(aTestNumber) {
+ let test = getReferrerTest(aTestNumber);
+ return "policy=[" + test.policy + "] " +
+ "rel=[" + test.rel + "] " +
+ test.fromScheme + " -> " + test.toScheme;
+}
+
+/**
+ * Clicks the link.
+ * @param aWindow The window to click the link in.
+ * @param aLinkId The id of the link element.
+ * @param aOptions The options for synthesizeMouseAtCenter.
+ */
+function clickTheLink(aWindow, aLinkId, aOptions) {
+ return BrowserTestUtils.synthesizeMouseAtCenter(
+ "#" + aLinkId, aOptions, aWindow.gBrowser.selectedBrowser);
+}
+
+/**
+ * Extracts the referrer result from the target window.
+ * @param aWindow The window where the referrer target has loaded.
+ * @return {Promise}
+ * @resolves When extacted, with the text of the (trimmed) referrer.
+ */
+function referrerResultExtracted(aWindow) {
+ return ContentTask.spawn(aWindow.gBrowser.selectedBrowser, {}, function() {
+ return content.document.getElementById("testdiv").textContent;
+ });
+}
+
+/**
+ * Waits for browser delayed startup to finish.
+ * @param aWindow The window to wait for.
+ * @return {Promise}
+ * @resolves When the window is loaded.
+ */
+function delayedStartupFinished(aWindow) {
+ return new Promise(function(resolve) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+}
+
+/**
+ * Waits for some (any) tab to load. The caller triggers the load.
+ * @param aWindow The window where to wait for a tab to load.
+ * @return {Promise}
+ * @resolves With the tab once it's loaded.
+ */
+function someTabLoaded(aWindow) {
+ return BrowserTestUtils.waitForNewTab(gTestWindow.gBrowser).then((tab) => {
+ return BrowserTestUtils.browserStopped(tab.linkedBrowser).then(() => tab);
+ });
+}
+
+/**
+ * Waits for a new window to open and load. The caller triggers the open.
+ * @return {Promise}
+ * @resolves With the new window once it's open and loaded.
+ */
+function newWindowOpened() {
+ return TestUtils.topicObserved("browser-delayed-startup-finished")
+ .then(([win]) => win);
+}
+
+/**
+ * Opens the context menu.
+ * @param aWindow The window to open the context menu in.
+ * @param aLinkId The id of the link to open the context menu on.
+ * @return {Promise}
+ * @resolves With the menu popup when the context menu is open.
+ */
+function contextMenuOpened(aWindow, aLinkId) {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(aWindow.document,
+ "popupshown");
+ // Simulate right-click.
+ clickTheLink(aWindow, aLinkId, { type: "contextmenu", button: 2 });
+ return popupShownPromise.then(e => e.target);
+}
+
+/**
+ * Performs a context menu command.
+ * @param aWindow The window with the already open context menu.
+ * @param aMenu The menu popup to hide.
+ * @param aItemId The id of the menu item to activate.
+ */
+function doContextMenuCommand(aWindow, aMenu, aItemId) {
+ let command = aWindow.document.getElementById(aItemId);
+ command.doCommand();
+ aMenu.hidePopup();
+}
+
+/**
+ * Loads a single test case, i.e., a source url into gTestWindow.
+ * @param aTestNumber The test case number - 0, 1, 2...
+ * @return {Promise}
+ * @resolves When the source url for this test case is loaded.
+ */
+function referrerTestCaseLoaded(aTestNumber, aParams) {
+ let test = getReferrerTest(aTestNumber);
+ let server = rounds == 0 ? REFERRER_POLICYSERVER_URL :
+ REFERRER_POLICYSERVER_URL_ATTRIBUTE;
+ let url = test.fromScheme + server +
+ "?scheme=" + escape(test.toScheme) +
+ "&policy=" + escape(test.policy || "") +
+ "&rel=" + escape(test.rel || "");
+ let browser = gTestWindow.gBrowser;
+ return BrowserTestUtils.openNewForegroundTab(browser, () => {
+ browser.selectedTab = browser.addTab(url, aParams);
+ }, false, true);
+}
+
+/**
+ * Checks the result of the referrer test, and moves on to the next test.
+ * @param aTestNumber The test number - 0, 1, 2, ...
+ * @param aNewWindow The new window where the referrer target opened, or null.
+ * @param aNewTab The new tab where the referrer target opened, or null.
+ * @param aStartTestCase The callback to start the next test, aTestNumber + 1.
+ */
+function checkReferrerAndStartNextTest(aTestNumber, aNewWindow, aNewTab,
+ aStartTestCase, aParams = {}) {
+ referrerResultExtracted(aNewWindow || gTestWindow).then(function(result) {
+ // Compare the actual result against the expected one.
+ let test = getReferrerTest(aTestNumber);
+ let desc = getReferrerTestDescription(aTestNumber);
+ is(result, test.result, desc);
+
+ // Clean up - close new tab / window, and then the source tab.
+ aNewTab && (aNewWindow || gTestWindow).gBrowser.removeTab(aNewTab);
+ aNewWindow && aNewWindow.close();
+ is(gTestWindow.gBrowser.tabs.length, 2, "two tabs open");
+ gTestWindow.gBrowser.removeTab(gTestWindow.gBrowser.tabs[1]);
+
+ // Move on to the next test. Or finish if we're done.
+ var nextTestNumber = aTestNumber + 1;
+ if (getReferrerTest(nextTestNumber)) {
+ referrerTestCaseLoaded(nextTestNumber, aParams).then(function() {
+ aStartTestCase(nextTestNumber);
+ });
+ } else if (rounds == 0) {
+ nextTestNumber = 0;
+ rounds = 1;
+ referrerTestCaseLoaded(nextTestNumber, aParams).then(function() {
+ aStartTestCase(nextTestNumber);
+ });
+ } else {
+ finish();
+ }
+ });
+}
+
+/**
+ * Fires up the complete referrer test.
+ * @param aStartTestCase The callback to start a single test case, called with
+ * the test number - 0, 1, 2... Needs to trigger the navigation from the source
+ * page, and call checkReferrerAndStartNextTest() when the target is loaded.
+ */
+function startReferrerTest(aStartTestCase, params = {}) {
+ waitForExplicitFinish();
+
+ // Open the window where we'll load the source URLs.
+ gTestWindow = openDialog(location, "", "chrome,all,dialog=no", "about:blank");
+ registerCleanupFunction(function() {
+ gTestWindow && gTestWindow.close();
+ });
+
+ // Load and start the first test.
+ delayedStartupFinished(gTestWindow).then(function() {
+ referrerTestCaseLoaded(0, params).then(function() {
+ aStartTestCase(0);
+ });
+ });
+}
diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini
new file mode 100644
index 000000000..6ad3668fd
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_identityBlock_focus.js]
+skip-if = os == 'mac' # Bug 1334418 (try only)
+support-files = ../general/permissions.html
+[browser_identityPopup_focus.js]
diff --git a/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js
new file mode 100644
index 000000000..e1e4e537a
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_identityBlock_focus.js
@@ -0,0 +1,62 @@
+/* Tests that the identity block can be reached via keyboard
+ * shortcuts and that it has the correct tab order.
+ */
+
+const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+const PERMISSIONS_PAGE = TEST_PATH + "permissions.html";
+
+function synthesizeKeyAndWaitForFocus(element, keyCode, options) {
+ let focused = BrowserTestUtils.waitForEvent(element, "focus");
+ EventUtils.synthesizeKey(keyCode, options);
+ return focused;
+}
+
+// Checks that the identity block is the next element after the urlbar
+// to be focused if there are no active notification anchors.
+add_task(function* testWithoutNotifications() {
+ yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+ yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true})
+ is(document.activeElement, gURLBar.inputField, "urlbar should be focused");
+ yield synthesizeKeyAndWaitForFocus(gIdentityHandler._identityBox, "VK_TAB", {shiftKey: true})
+ is(document.activeElement, gIdentityHandler._identityBox,
+ "identity block should be focused");
+ });
+});
+
+// Checks that when there is a notification anchor, it will receive
+// focus before the identity block.
+add_task(function* testWithoutNotifications() {
+
+ yield BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, function*(browser) {
+ let popupshown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ // Request a permission;
+ BrowserTestUtils.synthesizeMouseAtCenter("#geo", {}, browser);
+ yield popupshown;
+
+ yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true})
+ is(document.activeElement, gURLBar.inputField, "urlbar should be focused");
+ let geoIcon = document.getElementById("geo-notification-icon");
+ yield synthesizeKeyAndWaitForFocus(geoIcon, "VK_TAB", {shiftKey: true})
+ is(document.activeElement, geoIcon, "notification anchor should be focused");
+ yield synthesizeKeyAndWaitForFocus(gIdentityHandler._identityBox, "VK_TAB", {shiftKey: true})
+ is(document.activeElement, gIdentityHandler._identityBox,
+ "identity block should be focused");
+ });
+});
+
+// Checks that with invalid pageproxystate the identity block is ignored.
+add_task(function* testInvalidPageProxyState() {
+ yield BrowserTestUtils.withNewTab("about:blank", function*(browser) {
+ // Loading about:blank will automatically focus the urlbar, which, however, can
+ // race with the test code. So we only send the shortcut if the urlbar isn't focused yet.
+ if (document.activeElement != gURLBar.inputField) {
+ yield synthesizeKeyAndWaitForFocus(gURLBar, "l", {accelKey: true})
+ }
+ is(document.activeElement, gURLBar.inputField, "urlbar should be focused");
+ yield synthesizeKeyAndWaitForFocus(gBrowser.getTabForBrowser(browser), "VK_TAB", {shiftKey: true})
+ isnot(document.activeElement, gIdentityHandler._identityBox,
+ "identity block should not be focused");
+ // Restore focus to the url bar.
+ gURLBar.focus();
+ });
+});
diff --git a/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
new file mode 100644
index 000000000..eea06f079
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
@@ -0,0 +1,27 @@
+/* Tests the focus behavior of the identity popup. */
+
+// Access the identity popup via mouseclick. Focus should not be moved inside.
+add_task(function* testIdentityPopupFocusClick() {
+ yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+ let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(gIdentityHandler._identityBox, {});
+ yield shown;
+ isnot(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
+ });
+});
+
+// Access the identity popup via keyboard. Focus should be moved inside.
+add_task(function* testIdentityPopupFocusKeyboard() {
+ yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+ yield BrowserTestUtils.withNewTab("https://example.com", function*() {
+ let focused = BrowserTestUtils.waitForEvent(gIdentityHandler._identityBox, "focus");
+ gIdentityHandler._identityBox.focus();
+ yield focused;
+ let shown = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+ EventUtils.synthesizeKey(" ", {});
+ yield shown;
+ is(Services.focus.focusedElement, document.getElementById("identity-popup-security-expander"));
+ });
+});
+
diff --git a/browser/base/content/test/siteIdentity/head.js b/browser/base/content/test/siteIdentity/head.js
new file mode 100644
index 000000000..12a0547ee
--- /dev/null
+++ b/browser/base/content/test/siteIdentity/head.js
@@ -0,0 +1,6 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
diff --git a/browser/base/content/test/social/.eslintrc.js b/browser/base/content/test/social/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/social/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/social/blocklist.xml b/browser/base/content/test/social/blocklist.xml
new file mode 100644
index 000000000..2e3665c36
--- /dev/null
+++ b/browser/base/content/test/social/blocklist.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+ <emItems>
+ <emItem blockID="s1" id="test1.example.com@services.mozilla.org"></emItem>
+ </emItems>
+</blocklist>
diff --git a/browser/base/content/test/social/browser.ini b/browser/base/content/test/social/browser.ini
new file mode 100644
index 000000000..91f931602
--- /dev/null
+++ b/browser/base/content/test/social/browser.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files =
+ blocklist.xml
+ head.js
+ opengraph/og_invalid_url.html
+ opengraph/opengraph.html
+ opengraph/shortlink_linkrel.html
+ opengraph/shorturl_link.html
+ opengraph/shorturl_linkrel.html
+ microformats.html
+ share.html
+ share_activate.html
+ social_activate.html
+ social_activate_basic.html
+ social_activate_iframe.html
+ social_postActivation.html
+ !/browser/base/content/test/plugins/blockNoPlugins.xml
+
+[browser_aboutHome_activation.js]
+[browser_addons.js]
+[browser_blocklist.js]
+[browser_share.js]
+[browser_social_activation.js]
diff --git a/browser/base/content/test/social/browser_aboutHome_activation.js b/browser/base/content/test/social/browser_aboutHome_activation.js
new file mode 100644
index 000000000..37cca53d2
--- /dev/null
+++ b/browser/base/content/test/social/browser_aboutHome_activation.js
@@ -0,0 +1,229 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHome.jsm");
+
+var snippet =
+' <script>' +
+' var manifest = {' +
+' "name": "Demo Social Service",' +
+' "origin": "https://example.com",' +
+' "iconURL": "chrome://branding/content/icon16.png",' +
+' "icon32URL": "chrome://branding/content/icon32.png",' +
+' "icon64URL": "chrome://branding/content/icon64.png",' +
+' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' +
+' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' +
+' };' +
+' function activateProvider(node) {' +
+' node.setAttribute("data-service", JSON.stringify(manifest));' +
+' var event = new CustomEvent("ActivateSocialFeature");' +
+' node.dispatchEvent(event);' +
+' }' +
+' </script>' +
+' <div id="activationSnippet" onclick="activateProvider(this)">' +
+' <img src="chrome://branding/content/icon32.png"></img>' +
+' </div>';
+
+// enable one-click activation
+var snippet2 =
+' <script>' +
+' var manifest = {' +
+' "name": "Demo Social Service",' +
+' "origin": "https://example.com",' +
+' "iconURL": "chrome://branding/content/icon16.png",' +
+' "icon32URL": "chrome://branding/content/icon32.png",' +
+' "icon64URL": "chrome://branding/content/icon64.png",' +
+' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' +
+' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' +
+' "oneclick": true' +
+' };' +
+' function activateProvider(node) {' +
+' node.setAttribute("data-service", JSON.stringify(manifest));' +
+' var event = new CustomEvent("ActivateSocialFeature");' +
+' node.dispatchEvent(event);' +
+' }' +
+' </script>' +
+' <div id="activationSnippet" onclick="activateProvider(this)">' +
+' <img src="chrome://branding/content/icon32.png"></img>' +
+' </div>';
+
+var gTests = [
+
+{
+ desc: "Test activation with enable panel",
+ snippet: snippet,
+ panel: true
+},
+
+{
+ desc: "Test activation bypassing enable panel",
+ snippet: snippet2,
+ panel: false
+}
+];
+
+function test()
+{
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+ ignoreAllUncaughtExceptions();
+ PopupNotifications.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ PopupNotifications.panel.removeAttribute("animate");
+ });
+
+ Task.spawn(function* () {
+ for (let test of gTests) {
+ info(test.desc);
+
+ // Create a tab to run the test.
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // Add an event handler to modify the snippets map once it's ready.
+ let snippetsPromise = promiseSetupSnippetsMap(tab, test.snippet);
+
+ // Start loading about:home and wait for it to complete, snippets should be loaded
+ yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsCompleted");
+
+ yield snippetsPromise;
+
+ // ensure our activation snippet is indeed available
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function*(arg) {
+ ok(!!content.document.getElementById("snippets"), "Found snippets element");
+ ok(!!content.document.getElementById("activationSnippet"), "The snippet is present.");
+ });
+
+ yield new Promise(resolve => {
+ activateProvider(tab, test.panel).then(() => {
+ checkSocialUI();
+ SocialService.uninstallProvider("https://example.com", function () {
+ info("provider uninstalled");
+ resolve();
+ });
+ });
+ });
+
+ // activation opened a post-activation info tab, close it.
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ }).then(finish, ex => {
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
+
+/**
+ * Starts a load in an existing tab and waits for it to finish (via some event).
+ *
+ * @param aTab
+ * The tab to load into.
+ * @param aUrl
+ * The url to load.
+ * @param aEvent
+ * The load event type to wait for. Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ */
+function promiseTabLoadEvent(aTab, aURL, aEventType="load")
+{
+ return new Promise(resolve => {
+ info("Wait tab event: " + aEventType);
+ aTab.linkedBrowser.addEventListener(aEventType, function load(event) {
+ if (event.originalTarget != aTab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ aTab.linkedBrowser.removeEventListener(aEventType, load, true);
+ info("Tab event received: " + aEventType);
+ resolve();
+ }, true, true);
+ aTab.linkedBrowser.loadURI(aURL);
+ });
+}
+
+/**
+ * Cleans up snippets and ensures that by default we don't try to check for
+ * remote snippets since that may cause network bustage or slowness.
+ *
+ * @param aTab
+ * The tab containing about:home.
+ * @param aSetupFn
+ * The setup function to be run.
+ * @return {Promise} resolved when the snippets are ready. Gets the snippets map.
+ */
+function promiseSetupSnippetsMap(aTab, aSnippet)
+{
+ info("Waiting for snippets map");
+
+ return ContentTask.spawn(aTab.linkedBrowser,
+ {snippetsVersion: AboutHomeUtils.snippetsVersion,
+ snippet: aSnippet},
+ function*(arg) {
+ return new Promise(resolve => {
+ addEventListener("AboutHomeLoadSnippets", function load(event) {
+ removeEventListener("AboutHomeLoadSnippets", load, true);
+
+ let cw = content.window.wrappedJSObject;
+
+ // The snippets should already be ready by this point. Here we're
+ // just obtaining a reference to the snippets map.
+ cw.ensureSnippetsMapThen(function (aSnippetsMap) {
+ aSnippetsMap = Cu.waiveXrays(aSnippetsMap);
+ console.log("Got snippets map: " +
+ "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
+ ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
+ " }");
+ // Don't try to update.
+ aSnippetsMap.set("snippets-last-update", Date.now());
+ aSnippetsMap.set("snippets-cached-version", arg.snippetsVersion);
+ // Clear snippets.
+ aSnippetsMap.delete("snippets");
+ aSnippetsMap.set("snippets", arg.snippet);
+ resolve();
+ });
+ }, true, true);
+ });
+ });
+}
+
+
+function sendActivationEvent(tab) {
+ // hack Social.lastEventReceived so we don't hit the "too many events" check.
+ Social.lastEventReceived = 0;
+ let doc = tab.linkedBrowser.contentDocument;
+ // if our test has a frame, use it
+ if (doc.defaultView.frames[0])
+ doc = doc.defaultView.frames[0].document;
+ let button = doc.getElementById("activationSnippet");
+ BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser);
+}
+
+function activateProvider(tab, expectPanel, aCallback) {
+ return new Promise(resolve => {
+ if (expectPanel) {
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
+ let panel = document.getElementById("servicesInstall-notification");
+ panel.button.click();
+ });
+ }
+ waitForProviderLoad().then(() => {
+ checkSocialUI();
+ resolve();
+ });
+ sendActivationEvent(tab);
+ });
+}
+
+function waitForProviderLoad(cb) {
+ return Promise.all([
+ promiseObserverNotified("social:provider-enabled"),
+ ensureFrameLoaded(gBrowser, "https://example.com/browser/browser/base/content/test/social/social_postActivation.html"),
+ ]);
+}
diff --git a/browser/base/content/test/social/browser_addons.js b/browser/base/content/test/social/browser_addons.js
new file mode 100644
index 000000000..5a75d1d67
--- /dev/null
+++ b/browser/base/content/test/social/browser_addons.js
@@ -0,0 +1,217 @@
+var AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager;
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+var manifest = {
+ name: "provider 1",
+ origin: "https://example.com",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
+};
+var manifest2 = { // used for testing install
+ name: "provider 2",
+ origin: "https://test1.example.com",
+ shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
+ version: "1.0"
+};
+var manifestUpgrade = { // used for testing install
+ name: "provider 3",
+ origin: "https://test2.example.com",
+ shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png",
+ version: "1.0"
+};
+
+function test() {
+ waitForExplicitFinish();
+ PopupNotifications.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ PopupNotifications.panel.removeAttribute("animate");
+ });
+
+ let prefname = getManifestPrefname(manifest);
+ // ensure that manifest2 is NOT showing as builtin
+ is(SocialService.getOriginActivationType(manifest.origin), "foreign", "manifest is foreign");
+ is(SocialService.getOriginActivationType(manifest2.origin), "foreign", "manifest2 is foreign");
+
+ Services.prefs.setBoolPref("social.remote-install.enabled", true);
+ runSocialTests(tests, undefined, undefined, function () {
+ Services.prefs.clearUserPref("social.remote-install.enabled");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ // just in case the tests failed, clear these here as well
+ Services.prefs.clearUserPref("social.directories");
+ finish();
+ });
+}
+
+function installListener(next, aManifest) {
+ let expectEvent = "onInstalling";
+ let prefname = getManifestPrefname(aManifest);
+ // wait for the actual removal to call next
+ SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
+ if (topic == "provider-disabled") {
+ SocialService.unregisterProviderListener(providerListener);
+ is(origin, aManifest.origin, "provider disabled");
+ executeSoon(next);
+ }
+ });
+
+ return {
+ onInstalling: function(addon) {
+ is(expectEvent, "onInstalling", "install started");
+ is(addon.manifest.origin, aManifest.origin, "provider about to be installed");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ expectEvent = "onInstalled";
+ },
+ onInstalled: function(addon) {
+ is(addon.manifest.origin, aManifest.origin, "provider installed");
+ ok(addon.installDate.getTime() > 0, "addon has installDate");
+ ok(addon.updateDate.getTime() > 0, "addon has updateDate");
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ expectEvent = "onUninstalling";
+ },
+ onUninstalling: function(addon) {
+ is(expectEvent, "onUninstalling", "uninstall started");
+ is(addon.manifest.origin, aManifest.origin, "provider about to be uninstalled");
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ expectEvent = "onUninstalled";
+ },
+ onUninstalled: function(addon) {
+ is(expectEvent, "onUninstalled", "provider has been uninstalled");
+ is(addon.manifest.origin, aManifest.origin, "provider uninstalled");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ AddonManager.removeAddonListener(this);
+ }
+ };
+}
+
+var tests = {
+ testHTTPInstallFailure: function(next) {
+ let installFrom = "http://example.com";
+ is(SocialService.getOriginActivationType(installFrom), "foreign", "testing foriegn install");
+ let data = {
+ origin: installFrom,
+ url: installFrom+"/activate",
+ manifest: manifest,
+ window: window
+ }
+ Social.installProvider(data, function(addonManifest) {
+ ok(!addonManifest, "unable to install provider over http");
+ next();
+ });
+ },
+ testAddonEnableToggle: function(next) {
+ let expectEvent;
+ let prefname = getManifestPrefname(manifest);
+ let listener = {
+ onEnabled: function(addon) {
+ is(expectEvent, "onEnabled", "provider onEnabled");
+ ok(!addon.userDisabled, "provider enabled");
+ executeSoon(function() {
+ expectEvent = "onDisabling";
+ addon.userDisabled = true;
+ });
+ },
+ onEnabling: function(addon) {
+ is(expectEvent, "onEnabling", "provider onEnabling");
+ expectEvent = "onEnabled";
+ },
+ onDisabled: function(addon) {
+ is(expectEvent, "onDisabled", "provider onDisabled");
+ ok(addon.userDisabled, "provider disabled");
+ AddonManager.removeAddonListener(listener);
+ // clear the provider user-level pref
+ Services.prefs.clearUserPref(prefname);
+ executeSoon(next);
+ },
+ onDisabling: function(addon) {
+ is(expectEvent, "onDisabling", "provider onDisabling");
+ expectEvent = "onDisabled";
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ // we're only testing enable disable, so we quickly set the user-level pref
+ // for this provider and test enable/disable toggling
+ setManifestPref(prefname, manifest);
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ AddonManager.getAddonsByTypes(["service"], function(addons) {
+ for (let addon of addons) {
+ if (addon.userDisabled) {
+ expectEvent = "onEnabling";
+ addon.userDisabled = false;
+ // only test with one addon
+ return;
+ }
+ }
+ ok(false, "no addons toggled");
+ next();
+ });
+ },
+ testProviderEnableToggle: function(next) {
+ // enable and disabel a provider from the SocialService interface, check
+ // that the addon manager is updated
+
+ let expectEvent;
+ let prefname = getManifestPrefname(manifest);
+
+ let listener = {
+ onEnabled: function(addon) {
+ is(expectEvent, "onEnabled", "provider onEnabled");
+ is(addon.manifest.origin, manifest.origin, "provider enabled");
+ ok(!addon.userDisabled, "provider !userDisabled");
+ },
+ onEnabling: function(addon) {
+ is(expectEvent, "onEnabling", "provider onEnabling");
+ is(addon.manifest.origin, manifest.origin, "provider about to be enabled");
+ expectEvent = "onEnabled";
+ },
+ onDisabled: function(addon) {
+ is(expectEvent, "onDisabled", "provider onDisabled");
+ is(addon.manifest.origin, manifest.origin, "provider disabled");
+ ok(addon.userDisabled, "provider userDisabled");
+ },
+ onDisabling: function(addon) {
+ is(expectEvent, "onDisabling", "provider onDisabling");
+ is(addon.manifest.origin, manifest.origin, "provider about to be disabled");
+ expectEvent = "onDisabled";
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ expectEvent = "onEnabling";
+ setManifestPref(prefname, manifest);
+ SocialService.enableProvider(manifest.origin, function(provider) {
+ expectEvent = "onDisabling";
+ SocialService.disableProvider(provider.origin, function() {
+ AddonManager.removeAddonListener(listener);
+ Services.prefs.clearUserPref(prefname);
+ next();
+ });
+ });
+ },
+ testDirectoryInstall: function(next) {
+ AddonManager.addAddonListener(installListener(next, manifest2));
+
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
+ let panel = document.getElementById("servicesInstall-notification");
+ info("servicesInstall-notification panel opened");
+ panel.button.click();
+ });
+
+ Services.prefs.setCharPref("social.directories", manifest2.origin);
+ is(SocialService.getOriginActivationType(manifest2.origin), "directory", "testing directory install");
+ let data = {
+ origin: manifest2.origin,
+ url: manifest2.origin + "/directory",
+ manifest: manifest2,
+ window: window
+ }
+ Social.installProvider(data, function(addonManifest) {
+ Services.prefs.clearUserPref("social.directories");
+ SocialService.enableProvider(addonManifest.origin, function(provider) {
+ Social.uninstallProvider(addonManifest.origin);
+ });
+ });
+ }
+}
diff --git a/browser/base/content/test/social/browser_blocklist.js b/browser/base/content/test/social/browser_blocklist.js
new file mode 100644
index 000000000..b67d5efb3
--- /dev/null
+++ b/browser/base/content/test/social/browser_blocklist.js
@@ -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/. */
+
+// a place for miscellaneous social tests
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
+var blocklistURL = "http://example.com/browser/browser/base/content/test/social/blocklist.xml";
+
+var manifest = { // normal provider
+ name: "provider ok",
+ origin: "https://example.com",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
+};
+var manifest_bad = { // normal provider
+ name: "provider blocked",
+ origin: "https://test1.example.com",
+ shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html",
+ iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png"
+};
+
+// blocklist testing
+function updateBlocklist() {
+ var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ let promise = promiseObserverNotified("blocklist-updated");
+ blocklistNotifier.notify(null);
+ return promise;
+}
+
+var _originalTestBlocklistURL = null;
+function setAndUpdateBlocklist(aURL) {
+ if (!_originalTestBlocklistURL)
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ return updateBlocklist();
+}
+
+function resetBlocklist() {
+ // XXX - this has "forked" from the head.js helpers in our parent directory :(
+ // But let's reuse their blockNoPlugins.xml. Later, we should arrange to
+ // use their head.js helpers directly
+ let noBlockedURL = "http://example.com/browser/browser/base/content/test/plugins/blockNoPlugins.xml";
+ return new Promise(resolve => {
+ setAndUpdateBlocklist(noBlockedURL).then(() => {
+ Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
+ resolve();
+ });
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+ // turn on logging for nsBlocklistService.js
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("extensions.logging.enabled");
+ });
+
+ runSocialTests(tests, undefined, undefined, function () {
+ resetBlocklist().then(finish); // restore to original pref
+ });
+}
+
+var tests = {
+ testSimpleBlocklist: function(next) {
+ // this really just tests adding and clearing our blocklist for later tests
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ ok(Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocking 'blocked'");
+ ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest)), "not blocking 'good'");
+ resetBlocklist().then(() => {
+ ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocklist cleared");
+ next();
+ });
+ });
+ },
+ testAddingNonBlockedProvider: function(next) {
+ function finishTest(isgood) {
+ ok(isgood, "adding non-blocked provider ok");
+ Services.prefs.clearUserPref("social.manifest.good");
+ resetBlocklist().then(next);
+ }
+ setManifestPref("social.manifest.good", manifest);
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ try {
+ SocialService.addProvider(manifest, function(provider) {
+ try {
+ SocialService.disableProvider(provider.origin, function() {
+ ok(true, "added and removed provider");
+ finishTest(true);
+ });
+ } catch (e) {
+ ok(false, "SocialService.disableProvider threw exception: " + e);
+ finishTest(false);
+ }
+ });
+ } catch (e) {
+ ok(false, "SocialService.addProvider threw exception: " + e);
+ finishTest(false);
+ }
+ });
+ },
+ testAddingBlockedProvider: function(next) {
+ function finishTest(good) {
+ ok(good, "Unable to add blocklisted provider");
+ Services.prefs.clearUserPref("social.manifest.blocked");
+ resetBlocklist().then(next);
+ }
+ setManifestPref("social.manifest.blocked", manifest_bad);
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ try {
+ SocialService.addProvider(manifest_bad, function(provider) {
+ SocialService.disableProvider(provider.origin, function() {
+ ok(false, "SocialService.addProvider should throw blocklist exception");
+ finishTest(false);
+ });
+ });
+ } catch (e) {
+ ok(true, "SocialService.addProvider should throw blocklist exception: " + e);
+ finishTest(true);
+ }
+ });
+ },
+ testInstallingBlockedProvider: function(next) {
+ function finishTest(good) {
+ ok(good, "Unable to install blocklisted provider");
+ resetBlocklist().then(next);
+ }
+ let activationURL = manifest_bad.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ setAndUpdateBlocklist(blocklistURL).then(() => {
+ try {
+ // expecting an exception when attempting to install a hard blocked
+ // provider
+ let data = {
+ origin: manifest_bad.origin,
+ url: activationURL,
+ manifest: manifest_bad,
+ window: window
+ }
+ Social.installProvider(data, function(addonManifest) {
+ finishTest(false);
+ });
+ } catch (e) {
+ finishTest(true);
+ }
+ });
+ },
+ testBlockingExistingProvider: function(next) {
+ let listener = {
+ _window: null,
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+ this._window = aXULWindow;
+ let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+
+ domwindow.addEventListener("load", function _load() {
+ domwindow.removeEventListener("load", _load, false);
+
+ domwindow.addEventListener("unload", function _unload() {
+ domwindow.removeEventListener("unload", _unload, false);
+ info("blocklist window was closed");
+ Services.wm.removeListener(listener);
+ next();
+ }, false);
+
+ is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused");
+ // wait until after load to cancel so the dialog has initalized. we
+ // don't want to accept here since that restarts the browser.
+ executeSoon(() => {
+ let cancelButton = domwindow.document.documentElement.getButton("cancel");
+ info("***** hit the cancel button\n");
+ cancelButton.doCommand();
+ });
+ }, false);
+ },
+ onCloseWindow: function(aXULWindow) { },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+ };
+
+ Services.wm.addListener(listener);
+
+ setManifestPref("social.manifest.blocked", manifest_bad);
+ try {
+ SocialService.addProvider(manifest_bad, function(provider) {
+ // the act of blocking should cause a 'provider-disabled' notification
+ // from SocialService.
+ SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
+ if (topic != "provider-disabled")
+ return;
+ SocialService.unregisterProviderListener(providerListener);
+ is(origin, provider.origin, "provider disabled");
+ SocialService.getProvider(provider.origin, function(p) {
+ ok(p == null, "blocklisted provider disabled");
+ Services.prefs.clearUserPref("social.manifest.blocked");
+ resetBlocklist();
+ });
+ });
+ // no callback - the act of updating should cause the listener above
+ // to fire.
+ setAndUpdateBlocklist(blocklistURL);
+ });
+ } catch (e) {
+ ok(false, "unable to add provider " + e);
+ next();
+ }
+ }
+}
diff --git a/browser/base/content/test/social/browser_share.js b/browser/base/content/test/social/browser_share.js
new file mode 100644
index 000000000..19dca519b
--- /dev/null
+++ b/browser/base/content/test/social/browser_share.js
@@ -0,0 +1,396 @@
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+var baseURL = "https://example.com/browser/browser/base/content/test/social/";
+
+var manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
+};
+var activationPage = "https://example.com/browser/browser/base/content/test/social/share_activate.html";
+
+function sendActivationEvent(subframe) {
+ // hack Social.lastEventReceived so we don't hit the "too many events" check.
+ Social.lastEventReceived = 0;
+ let doc = subframe.contentDocument;
+ // if our test has a frame, use it
+ let button = doc.getElementById("activation");
+ ok(!!button, "got the activation button");
+ EventUtils.synthesizeMouseAtCenter(button, {}, doc.defaultView);
+}
+
+function test() {
+ waitForExplicitFinish();
+ Services.prefs.setCharPref("social.shareDirectory", activationPage);
+
+ let frameScript = "data:,(" + function frame_script() {
+ addEventListener("OpenGraphData", function (aEvent) {
+ sendAsyncMessage("sharedata", aEvent.detail);
+ }, true, true);
+ /* bug 1042991, ensure history is available by calling history.back on close */
+ addMessageListener("closeself", function(e) {
+ content.history.back();
+ content.close();
+ }, true);
+ /* if text is entered into field, onbeforeunload will cause a modal dialog
+ unless dialogs have been disabled for the iframe. */
+ content.onbeforeunload = function(e) {
+ return 'FAIL.';
+ };
+ }.toString() + ")();";
+ let mm = getGroupMessageManager("social");
+ mm.loadFrameScript(frameScript, true);
+
+ // Animation on the panel can cause intermittent failures such as bug 1115131.
+ SocialShare.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ SocialShare.panel.removeAttribute("animate");
+ mm.removeDelayedFrameScript(frameScript);
+ Services.prefs.clearUserPref("social.directories");
+ Services.prefs.clearUserPref("social.shareDirectory");
+ Services.prefs.clearUserPref("social.share.activationPanelEnabled");
+ });
+ runSocialTests(tests, undefined, function(next) {
+ let shareButton = SocialShare.shareButton;
+ if (shareButton) {
+ CustomizableUI.removeWidgetFromArea("social-share-button", CustomizableUI.AREA_NAVBAR)
+ shareButton.remove();
+ }
+ next();
+ });
+}
+
+var corpus = [
+ {
+ url: baseURL+"opengraph/opengraph.html",
+ options: {
+ // og:title
+ title: ">This is my title<",
+ // og:description
+ description: "A test corpus file for open graph tags we care about",
+ // medium: this.getPageMedium(),
+ // source: this.getSourceURL(),
+ // og:url
+ url: "https://www.mozilla.org/",
+ // shortUrl: this.getShortURL(),
+ // og:image
+ previews:["https://www.mozilla.org/favicon.png"],
+ // og:site_name
+ siteName: ">My simple test page<"
+ }
+ },
+ {
+ // tests that og:url doesn't override the page url if it is bad
+ url: baseURL+"opengraph/og_invalid_url.html",
+ options: {
+ description: "A test corpus file for open graph tags passing a bad url",
+ url: baseURL+"opengraph/og_invalid_url.html",
+ previews: [],
+ siteName: "Evil chrome delivering website"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shorturl_link.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shorturl_linkrel.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shortlink_linkrel.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ }
+];
+
+function hasoptions(testOptions, options) {
+ for (let option in testOptions) {
+ let data = testOptions[option];
+ info("data: "+JSON.stringify(data));
+ let message_data = options[option];
+ info("message_data: "+JSON.stringify(message_data));
+ if (Array.isArray(data)) {
+ // the message may have more array elements than we are testing for, this
+ // is ok since some of those are hard to test. So we just test that
+ // anything in our test data IS in the message.
+ ok(Array.every(data, function(item) { return message_data.indexOf(item) >= 0 }), "option "+option);
+ } else {
+ is(message_data, data, "option "+option);
+ }
+ }
+}
+
+var tests = {
+ testShareDisabledOnActivation: function(next) {
+ // starting on about:blank page, share should be visible but disabled when
+ // adding provider
+ is(gBrowser.currentURI.spec, "about:blank");
+
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ SocialService.addProvider(manifest, function(provider) {
+ is(SocialUI.enabled, true, "SocialUI is enabled");
+ checkSocialUI();
+ // share should not be enabled since we only have about:blank page
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ is(shareButton.getAttribute("disabled"), "true", "share button attribute is disabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+ SocialService.disableProvider(manifest.origin, next);
+ });
+ },
+ testShareEnabledOnActivation: function(next) {
+ // starting from *some* page, share should be visible and enabled when
+ // activating provider
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ let testData = corpus[0];
+ BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => {
+ SocialService.addProvider(manifest, function(provider) {
+ is(SocialUI.enabled, true, "SocialUI is enabled");
+ checkSocialUI();
+ // share should not be enabled since we only have about:blank page
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+ BrowserTestUtils.removeTab(tab).then(next);
+ });
+ });
+ },
+ testSharePage: function(next) {
+ let testTab;
+ let testIndex = 0;
+ let testData = corpus[testIndex++];
+
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ let mm = getGroupMessageManager("social");
+ mm.addMessageListener("sharedata", function handler(msg) {
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ hasoptions(testData.options, JSON.parse(msg.data));
+ testData = corpus[testIndex++];
+ BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, "share panel closed").then(() => {
+ if (testData) {
+ runOneTest();
+ } else {
+ mm.removeMessageListener("sharedata", handler);
+ SocialService.disableProvider(manifest.origin, next);
+ }
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+ });
+
+ function runOneTest() {
+ BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => {
+ testTab = tab;
+
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ SocialShare.sharePage(manifest.origin);
+ });
+ }
+ executeSoon(runOneTest);
+ },
+ testShareMicroformats: function(next) {
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ SocialService.addProvider(manifest, function(provider) {
+ let target, testTab;
+
+ let expecting = JSON.stringify({
+ "url": "https://example.com/browser/browser/base/content/test/social/microformats.html",
+ "title": "Raspberry Pi Page",
+ "previews": ["https://example.com/someimage.jpg"],
+ "microformats": {
+ "items": [{
+ "type": ["h-product"],
+ "properties": {
+ "name": ["Raspberry Pi"],
+ "photo": ["https://example.com/someimage.jpg"],
+ "description": [{
+ "value": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.",
+ "html": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming."
+ }
+ ],
+ "url": ["https://example.com/"],
+ "price": ["29.95"],
+ "review": [{
+ "value": "4.5 out of 5",
+ "type": ["h-review"],
+ "properties": {
+ "rating": ["4.5"]
+ }
+ }
+ ],
+ "category": ["Computer", "Education"]
+ }
+ }
+ ],
+ "rels": {
+ "tag": ["https://example.com/wiki/computer", "https://example.com/wiki/education"]
+ },
+ "rel-urls": {
+ "https://example.com/wiki/computer": {
+ "text": "Computer",
+ "rels": ["tag"]
+ },
+ "https://example.com/wiki/education": {
+ "text": "Education",
+ "rels": ["tag"]
+ }
+ }
+ }
+ });
+
+ let mm = getGroupMessageManager("social");
+ mm.addMessageListener("sharedata", function handler(msg) {
+ is(msg.data, expecting, "microformats data ok");
+ BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; },
+ "share panel closed").then(() => {
+ mm.removeMessageListener("sharedata", handler);
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ SocialService.disableProvider(manifest.origin, next);
+ });
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+
+ let url = "https://example.com/browser/browser/base/content/test/social/microformats.html"
+ BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => {
+ testTab = tab;
+
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ let doc = tab.linkedBrowser.contentDocument;
+ target = doc.getElementById("simple-hcard");
+ SocialShare.sharePage(manifest.origin, null, target);
+ });
+ });
+ },
+ testSharePanelActivation: function(next) {
+ let testTab;
+ // cleared in the cleanup function
+ Services.prefs.setCharPref("social.directories", "https://example.com");
+ Services.prefs.setBoolPref("social.share.activationPanelEnabled", true);
+ // make the iframe so we can wait on the load
+ SocialShare._createFrame();
+ let iframe = SocialShare.iframe;
+
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+
+ ensureFrameLoaded(iframe).then(() => {
+ let subframe = iframe.contentDocument.getElementById("activation-frame");
+ ensureFrameLoaded(subframe, activationPage).then(() => {
+ is(subframe.contentDocument.location.href, activationPage, "activation page loaded");
+ promiseObserverNotified("social:provider-enabled").then(() => {
+ let mm = getGroupMessageManager("social");
+ mm.addMessageListener("sharedata", function handler(msg) {
+ ok(true, "share completed");
+
+ BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; },
+ "share panel closed").then(() => {
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ mm.removeMessageListener("sharedata", handler);
+ SocialService.uninstallProvider(manifest.origin, next);
+ });
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+ });
+ sendActivationEvent(subframe);
+ });
+ });
+ BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => {
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ testTab = tab;
+ SocialShare.sharePage();
+ });
+ },
+ testSharePanelDialog: function(next) {
+ let testTab;
+ // initialize the button into the navbar
+ CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
+ // ensure correct state
+ SocialUI.onCustomizeEnd(window);
+ SocialShare._createFrame();
+
+ SocialService.addProvider(manifest, () => {
+ BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => {
+ ensureFrameLoaded(SocialShare.iframe).then(() => {
+ // send keys to the input field. An unexpected failure will happen
+ // if the onbeforeunload handler is fired.
+ EventUtils.sendKey("f");
+ EventUtils.sendKey("a");
+ EventUtils.sendKey("i");
+ EventUtils.sendKey("l");
+
+ SocialShare.panel.addEventListener("popuphidden", function hidden(evt) {
+ SocialShare.panel.removeEventListener("popuphidden", hidden);
+ let topwin = Services.wm.getMostRecentWindow(null);
+ is(topwin, window, "no dialog is open");
+
+ BrowserTestUtils.removeTab(testTab).then(() => {
+ SocialService.disableProvider(manifest.origin, next);
+ });
+ });
+ SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {});
+ });
+
+ let shareButton = SocialShare.shareButton;
+ // verify the attribute for proper css
+ ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
+ // button should be visible
+ is(shareButton.hidden, false, "share button is visible");
+
+ testTab = tab;
+ SocialShare.sharePage();
+ });
+ });
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_activation.js b/browser/base/content/test/social/browser_social_activation.js
new file mode 100644
index 000000000..2af0d8021
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_activation.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/. */
+
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: Assert is null");
+
+
+var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+var tabsToRemove = [];
+
+function removeProvider(provider) {
+ return new Promise(resolve => {
+ // a full install sets the manifest into a pref, addProvider alone doesn't,
+ // make sure we uninstall if the manifest was added.
+ if (provider.manifest) {
+ SocialService.uninstallProvider(provider.origin, resolve);
+ } else {
+ SocialService.disableProvider(provider.origin, resolve);
+ }
+ });
+}
+
+function postTestCleanup(callback) {
+ Task.spawn(function* () {
+ // any tabs opened by the test.
+ for (let tab of tabsToRemove) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ tabsToRemove = [];
+ // all the providers may have been added.
+ while (Social.providers.length > 0) {
+ yield removeProvider(Social.providers[0]);
+ }
+ }).then(callback);
+}
+
+function newTab(url) {
+ return new Promise(resolve => {
+ BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => {
+ tabsToRemove.push(tab);
+ resolve(tab);
+ });
+ });
+}
+
+function sendActivationEvent(tab, callback, nullManifest) {
+ // hack Social.lastEventReceived so we don't hit the "too many events" check.
+ Social.lastEventReceived = 0;
+ BrowserTestUtils.synthesizeMouseAtCenter("#activation", {}, tab.linkedBrowser);
+ executeSoon(callback);
+}
+
+function activateProvider(domain, callback, nullManifest) {
+ let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_basic.html"
+ newTab(activationURL).then(tab => {
+ sendActivationEvent(tab, callback, nullManifest);
+ });
+}
+
+function activateIFrameProvider(domain, callback) {
+ let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_iframe.html"
+ newTab(activationURL).then(tab => {
+ sendActivationEvent(tab, callback, false);
+ });
+}
+
+function waitForProviderLoad(origin) {
+ return Promise.all([
+ ensureFrameLoaded(gBrowser, origin + "/browser/browser/base/content/test/social/social_postActivation.html"),
+ ]);
+}
+
+function getAddonItemInList(aId, aList) {
+ var item = aList.firstChild;
+ while (item) {
+ if ("mAddon" in item && item.mAddon.id == aId) {
+ aList.ensureElementIsVisible(item);
+ return item;
+ }
+ item = item.nextSibling;
+ }
+ return null;
+}
+
+function clickAddonRemoveButton(tab, aCallback) {
+ AddonManager.getAddonsByTypes(["service"], function(aAddons) {
+ let addon = aAddons[0];
+
+ let doc = tab.linkedBrowser.contentDocument;
+ let list = doc.getElementById("addon-list");
+
+ let item = getAddonItemInList(addon.id, list);
+ let button = item._removeBtn;
+ isnot(button, null, "Should have a remove button");
+ ok(!button.disabled, "Button should not be disabled");
+
+ // uninstall happens after about:addons tab is closed, so we wait on
+ // disabled
+ promiseObserverNotified("social:provider-disabled").then(() => {
+ is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
+ executeSoon(function() { aCallback(addon); });
+ });
+
+ BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser);
+ });
+}
+
+function activateOneProvider(manifest, finishActivation, aCallback) {
+ info("activating provider "+manifest.name);
+ let panel = document.getElementById("servicesInstall-notification");
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
+ ok(!panel.hidden, "servicesInstall-notification panel opened");
+ if (finishActivation)
+ panel.button.click();
+ else
+ panel.closebutton.click();
+ });
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden").then(() => {
+ ok(panel.hidden, "servicesInstall-notification panel hidden");
+ if (!finishActivation) {
+ ok(panel.hidden, "activation panel is not showing");
+ executeSoon(aCallback);
+ } else {
+ waitForProviderLoad(manifest.origin).then(() => {
+ checkSocialUI();
+ executeSoon(aCallback);
+ });
+ }
+ });
+
+ // the test will continue as the popup events fire...
+ activateProvider(manifest.origin, function() {
+ info("waiting on activation panel to open/close...");
+ });
+}
+
+var gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];
+var gProviders = [
+ {
+ name: "provider 1",
+ origin: "https://example.com",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html?provider1",
+ iconURL: "chrome://branding/content/icon48.png"
+ },
+ {
+ name: "provider 2",
+ origin: "https://test1.example.com",
+ shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html?provider2",
+ iconURL: "chrome://branding/content/icon64.png"
+ },
+ {
+ name: "provider 3",
+ origin: "https://test2.example.com",
+ shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html?provider2",
+ iconURL: "chrome://branding/content/about-logo.png"
+ }
+];
+
+
+function test() {
+ PopupNotifications.panel.setAttribute("animate", "false");
+ registerCleanupFunction(function () {
+ PopupNotifications.panel.removeAttribute("animate");
+ });
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, () => {
+ runSocialTests(tests, undefined, postTestCleanup);
+ });
+}
+
+var tests = {
+ testActivationWrongOrigin: function(next) {
+ // At this stage none of our providers exist, so we expect failure.
+ Services.prefs.setBoolPref("social.remote-install.enabled", false);
+ activateProvider(gTestDomains[0], function() {
+ is(SocialUI.enabled, false, "SocialUI is not enabled");
+ let panel = document.getElementById("servicesInstall-notification");
+ ok(panel.hidden, "activation panel still hidden");
+ checkSocialUI();
+ Services.prefs.clearUserPref("social.remote-install.enabled");
+ next();
+ });
+ },
+
+ testIFrameActivation: function(next) {
+ activateIFrameProvider(gTestDomains[0], function() {
+ is(SocialUI.enabled, false, "SocialUI is not enabled");
+ let panel = document.getElementById("servicesInstall-notification");
+ ok(panel.hidden, "activation panel still hidden");
+ checkSocialUI();
+ next();
+ });
+ },
+
+ testActivationFirstProvider: function(next) {
+ // first up we add a manifest entry for a single provider.
+ activateOneProvider(gProviders[0], false, function() {
+ // we deactivated leaving no providers left, so Social is disabled.
+ checkSocialUI();
+ next();
+ });
+ },
+
+ testActivationMultipleProvider: function(next) {
+ // The trick with this test is to make sure that Social.providers[1] is
+ // the current provider when doing the undo - this makes sure that the
+ // Social code doesn't fallback to Social.providers[0], which it will
+ // do in some cases (but those cases do not include what this test does)
+ // first enable the 2 providers
+ SocialService.addProvider(gProviders[0], function() {
+ SocialService.addProvider(gProviders[1], function() {
+ checkSocialUI();
+ // activate the last provider.
+ activateOneProvider(gProviders[2], false, function() {
+ // we deactivated - the first provider should be enabled.
+ checkSocialUI();
+ next();
+ });
+ });
+ });
+ },
+
+ testAddonManagerDoubleInstall: function(next) {
+ // Create a new tab and load about:addons
+ let addonsTab = gBrowser.addTab();
+ gBrowser.selectedTab = addonsTab;
+ BrowserOpenAddonsMgr('addons://list/service');
+ gBrowser.selectedBrowser.addEventListener("load", function tabLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", tabLoad, true);
+ is(addonsTab.linkedBrowser.currentURI.spec, "about:addons", "about:addons should load into blank tab.");
+
+ activateOneProvider(gProviders[0], true, function() {
+ info("first activation completed");
+ is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => {
+ is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_activate_basic.html", "activation page selected");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => {
+ tabsToRemove.pop();
+ // uninstall the provider
+ clickAddonRemoveButton(addonsTab, function(addon) {
+ checkSocialUI();
+ activateOneProvider(gProviders[0], true, function() {
+ info("second activation completed");
+ is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => {
+
+ // after closing the addons tab, verify provider is still installed
+ AddonManager.getAddonsByTypes(["service"], function(aAddons) {
+ is(aAddons.length, 1, "there can be only one");
+
+ let doc = addonsTab.linkedBrowser.contentDocument;
+ let list = doc.getElementById("addon-list");
+ is(list.childNodes.length, 1, "only one addon is displayed");
+
+ BrowserTestUtils.removeTab(addonsTab).then(next);
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ }, true);
+ }
+}
diff --git a/browser/base/content/test/social/head.js b/browser/base/content/test/social/head.js
new file mode 100644
index 000000000..ea175c97a
--- /dev/null
+++ b/browser/base/content/test/social/head.js
@@ -0,0 +1,273 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+
+function promiseObserverNotified(aTopic) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) {
+ dump("notification promised "+aTopic);
+ Services.obs.removeObserver(onNotification, aTopic);
+ TestUtils.executeSoon(() => resolve({subject: aSubject, data: aData}));
+ }, aTopic, false);
+ });
+}
+
+// Check that a specified (string) URL hasn't been "remembered" (ie, is not
+// in history, will not appear in about:newtab or auto-complete, etc.)
+function promiseSocialUrlNotRemembered(url) {
+ return new Promise(resolve => {
+ let uri = Services.io.newURI(url, null, null);
+ PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) {
+ ok(!aIsVisited, "social URL " + url + " should not be in global history");
+ resolve();
+ });
+ });
+}
+
+var gURLsNotRemembered = [];
+
+
+function checkProviderPrefsEmpty(isError) {
+ let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
+ let prefs = MANIFEST_PREFS.getChildList("", []);
+ let c = 0;
+ for (let pref of prefs) {
+ if (MANIFEST_PREFS.prefHasUserValue(pref)) {
+ info("provider [" + pref + "] manifest left installed from previous test");
+ c++;
+ }
+ }
+ is(c, 0, "all provider prefs uninstalled from previous test");
+ is(Social.providers.length, 0, "all providers uninstalled from previous test " + Social.providers.length);
+}
+
+function defaultFinishChecks() {
+ checkProviderPrefsEmpty(true);
+ finish();
+}
+
+function runSocialTestWithProvider(manifest, callback, finishcallback) {
+
+ let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+
+ let manifests = Array.isArray(manifest) ? manifest : [manifest];
+
+ // Check that none of the provider's content ends up in history.
+ function* finishCleanUp() {
+ for (let i = 0; i < manifests.length; i++) {
+ let m = manifests[i];
+ for (let what of ['iconURL', 'shareURL']) {
+ if (m[what]) {
+ yield promiseSocialUrlNotRemembered(m[what]);
+ }
+ }
+ }
+ for (let i = 0; i < gURLsNotRemembered.length; i++) {
+ yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
+ }
+ gURLsNotRemembered = [];
+ }
+
+ info("runSocialTestWithProvider: " + manifests.toSource());
+
+ let finishCount = 0;
+ function finishIfDone(callFinish) {
+ finishCount++;
+ if (finishCount == manifests.length)
+ Task.spawn(finishCleanUp).then(finishcallback || defaultFinishChecks);
+ }
+ function removeAddedProviders(cleanup) {
+ manifests.forEach(function (m) {
+ // If we're "cleaning up", don't call finish when done.
+ let callback = cleanup ? function () {} : finishIfDone;
+ // Similarly, if we're cleaning up, catch exceptions from removeProvider
+ let removeProvider = SocialService.disableProvider.bind(SocialService);
+ if (cleanup) {
+ removeProvider = function (origin, cb) {
+ try {
+ SocialService.disableProvider(origin, cb);
+ } catch (ex) {
+ // Ignore "provider doesn't exist" errors.
+ if (ex.message.indexOf("SocialService.disableProvider: no provider with origin") == 0)
+ return;
+ info("Failed to clean up provider " + origin + ": " + ex);
+ }
+ }
+ }
+ removeProvider(m.origin, callback);
+ });
+ }
+ function finishSocialTest(cleanup) {
+ removeAddedProviders(cleanup);
+ }
+
+ let providersAdded = 0;
+
+ manifests.forEach(function (m) {
+ SocialService.addProvider(m, function(provider) {
+
+ providersAdded++;
+ info("runSocialTestWithProvider: provider added");
+
+ // we want to set the first specified provider as the UI's provider
+ if (provider.origin == manifests[0].origin) {
+ firstProvider = provider;
+ }
+
+ // If we've added all the providers we need, call the callback to start
+ // the tests (and give it a callback it can call to finish them)
+ if (providersAdded == manifests.length) {
+ registerCleanupFunction(function () {
+ finishSocialTest(true);
+ });
+ BrowserTestUtils.waitForCondition(() => provider.enabled,
+ "providers added and enabled").then(() => {
+ info("provider has been enabled");
+ callback(finishSocialTest);
+ });
+ }
+ });
+ });
+}
+
+function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
+ let testIter = (function*() {
+ for (let name in tests) {
+ if (tests.hasOwnProperty(name)) {
+ yield [name, tests[name]];
+ }
+ }
+ })();
+ let providersAtStart = Social.providers.length;
+ info("runSocialTests: start test run with " + providersAtStart + " providers");
+ window.focus();
+
+
+ if (cbPreTest === undefined) {
+ cbPreTest = function(cb) { cb() };
+ }
+ if (cbPostTest === undefined) {
+ cbPostTest = function(cb) { cb() };
+ }
+
+ function runNextTest() {
+ let result = testIter.next();
+ if (result.done) {
+ // out of items:
+ (cbFinish || defaultFinishChecks)();
+ is(providersAtStart, Social.providers.length,
+ "runSocialTests: finish test run with " + Social.providers.length + " providers");
+ return;
+ }
+ let [name, func] = result.value;
+ // We run on a timeout to help keep the debug messages sane.
+ executeSoon(function() {
+ function cleanupAndRunNextTest() {
+ info("sub-test " + name + " complete");
+ cbPostTest(runNextTest);
+ }
+ cbPreTest(function() {
+ info("pre-test: starting with " + Social.providers.length + " providers");
+ info("sub-test " + name + " starting");
+ try {
+ func.call(tests, cleanupAndRunNextTest);
+ } catch (ex) {
+ ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack);
+ cleanupAndRunNextTest();
+ }
+ })
+ });
+ }
+ runNextTest();
+}
+
+// A fairly large hammer which checks all aspects of the SocialUI for
+// internal consistency.
+function checkSocialUI(win) {
+ let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+ // if we have enabled providers, we should also have instances of those
+ // providers
+ if (SocialService.hasEnabledProviders) {
+ ok(Social.providers.length > 0, "providers are enabled");
+ } else {
+ is(Social.providers.length, 0, "providers are not enabled");
+ }
+}
+
+function setManifestPref(name, manifest) {
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ Services.prefs.setComplexValue(name, Ci.nsISupportsString, string);
+}
+
+function getManifestPrefname(aManifest) {
+ // is same as the generated name in SocialServiceInternal.getManifestPrefname
+ let originUri = Services.io.newURI(aManifest.origin, null, null);
+ return "social.manifest." + originUri.hostPort.replace('.', '-');
+}
+
+function ensureFrameLoaded(frame, uri) {
+ return new Promise(resolve => {
+ if (frame.contentDocument && frame.contentDocument.readyState == "complete" &&
+ (!uri || frame.contentDocument.location.href == uri)) {
+ resolve();
+ } else {
+ frame.addEventListener("load", function handler() {
+ if (uri && frame.contentDocument.location.href != uri)
+ return;
+ frame.removeEventListener("load", handler, true);
+ resolve()
+ }, true);
+ }
+ });
+}
+
+// Support for going on and offline.
+// (via browser/base/content/test/browser_bookmark_titles.js)
+var origProxyType = Services.prefs.getIntPref('network.proxy.type');
+
+function toggleOfflineStatus(goOffline) {
+ // Bug 968887 fix. when going on/offline, wait for notification before continuing
+ return new Promise(resolve => {
+ if (!goOffline) {
+ Services.prefs.setIntPref('network.proxy.type', origProxyType);
+ }
+ if (goOffline != Services.io.offline) {
+ info("initial offline state " + Services.io.offline);
+ let expect = !Services.io.offline;
+ Services.obs.addObserver(function offlineChange(subject, topic, data) {
+ Services.obs.removeObserver(offlineChange, "network:offline-status-changed");
+ info("offline state changed to " + Services.io.offline);
+ is(expect, Services.io.offline, "network:offline-status-changed successful toggle");
+ resolve();
+ }, "network:offline-status-changed", false);
+ BrowserOffline.toggleOfflineStatus();
+ } else {
+ resolve();
+ }
+ if (goOffline) {
+ Services.prefs.setIntPref('network.proxy.type', 0);
+ // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+ Services.cache2.clear();
+ }
+ });
+}
+
+function goOffline() {
+ // Simulate a network outage with offline mode. (Localhost is still
+ // accessible in offline mode, so disable the test proxy as well.)
+ return toggleOfflineStatus(true);
+}
+
+function goOnline(callback) {
+ return toggleOfflineStatus(false);
+}
diff --git a/browser/base/content/test/social/microformats.html b/browser/base/content/test/social/microformats.html
new file mode 100644
index 000000000..1a0e4436b
--- /dev/null
+++ b/browser/base/content/test/social/microformats.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <head><title>Raspberry Pi Page</title></head>
+ <div class="hproduct">
+ <h2 class="fn">Raspberry Pi</h2>
+ <img class="photo" src="https://example.com/someimage.jpg" />
+ <p class="description">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>
+ <a class="url" href="https://example.com/">More info about the Raspberry Pi</a>
+ <p class="price">29.95</p>
+ <p class="review hreview"><span id="test-review" class="rating">4.5</span> out of 5</p>
+ <p>Categories:
+ <a rel="tag" href="https://example.com/wiki/computer" class="category">Computer</a>,
+ <a rel="tag" href="https://example.com/wiki/education" class="category">Education</a>
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/moz.png b/browser/base/content/test/social/moz.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/base/content/test/social/moz.png
Binary files differ
diff --git a/browser/base/content/test/social/opengraph/og_invalid_url.html b/browser/base/content/test/social/opengraph/og_invalid_url.html
new file mode 100644
index 000000000..ad1dae2be
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/og_invalid_url.html
@@ -0,0 +1,11 @@
+<html xmlns:og="http://ogp.me/ns#">
+<head>
+ <meta property="og:url" content="chrome://browser/content/aboutDialog.xul"/>
+ <meta property="og:site_name" content="Evil chrome delivering website"/>
+ <meta property="og:description"
+ content="A test corpus file for open graph tags passing a bad url"/>
+</head>
+<body>
+ Open Graph Test Page
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/opengraph.html b/browser/base/content/test/social/opengraph/opengraph.html
new file mode 100644
index 000000000..50b7703b8
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/opengraph.html
@@ -0,0 +1,13 @@
+<html xmlns:og="http://ogp.me/ns#">
+<head>
+ <meta property="og:title" content="&gt;This is my title&lt;"/>
+ <meta property="og:url" content="https://www.mozilla.org"/>
+ <meta property="og:image" content="https://www.mozilla.org/favicon.png"/>
+ <meta property="og:site_name" content="&#62;My simple test page&#60;"/>
+ <meta property="og:description"
+ content="A test corpus file for open graph tags we care about"/>
+</head>
+<body>
+ Open Graph Test Page
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shortlink_linkrel.html b/browser/base/content/test/social/opengraph/shortlink_linkrel.html
new file mode 100644
index 000000000..54c40c376
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shortlink_linkrel.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link rel="shortlink" href="http://imshort/p/abcde" />
+</head>
+<body>
+ link[rel='shortlink']
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shorturl_link.html b/browser/base/content/test/social/opengraph/shorturl_link.html
new file mode 100644
index 000000000..667122cea
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shorturl_link.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link id="shorturl" rev="canonical" type="text/html" href="http://imshort/p/abcde" />
+</head>
+<body>
+ link id="shorturl"
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shorturl_linkrel.html b/browser/base/content/test/social/opengraph/shorturl_linkrel.html
new file mode 100644
index 000000000..36533528e
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shorturl_linkrel.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+ <title>Test Image</title>
+
+ <meta name="description" content="Iron man in a tutu" />
+ <meta name="title" content="Test Image" />
+
+ <meta name="medium" content="image" />
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link id="shorturl" href="http://imshort/p/abcde" />
+
+ <meta property="og:title" content="TestImage" />
+ <meta property="og:type" content="photos:photo" />
+ <meta property="og:url" content="http://www.example.com/photos/56789/" />
+ <meta property="og:site_name" content="My Photo Site" />
+ <meta property="og:description" content="Iron man in a tutu" />
+ <meta property="og:image" content="http://example.com/1234/56789.jpg" />
+ <meta property="og:image:width" content="480" />
+ <meta property="og:image:height" content="640" />
+</head>
+<body>
+ link[rel='shorturl']
+</body>
+</html>
diff --git a/browser/base/content/test/social/share.html b/browser/base/content/test/social/share.html
new file mode 100644
index 000000000..55cba9844
--- /dev/null
+++ b/browser/base/content/test/social/share.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body onload="document.getElementById('testclose').focus()">
+ <p>This is a test social share window.</p>
+ <input id="testclose"/>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/share_activate.html b/browser/base/content/test/social/share_activate.html
new file mode 100644
index 000000000..69707e705
--- /dev/null
+++ b/browser/base/content/test/social/share_activate.html
@@ -0,0 +1,35 @@
+<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/. -->
+<head>
+ <title>Activation test</title>
+</head>
+<script>
+
+var data = {
+ // currently required
+ "name": "Demo Social Service",
+ // browser_share.js serves this page from "https://example.com"
+ "origin": "https://example.com",
+ "iconURL": "chrome://branding/content/icon16.png",
+ "icon32URL": "chrome://branding/content/favicon32.png",
+ "icon64URL": "chrome://branding/content/icon64.png",
+ "shareURL": "/browser/browser/base/content/test/social/share.html"
+}
+
+function activate(node) {
+ node.setAttribute("data-service", JSON.stringify(data));
+ var event = new CustomEvent("ActivateSocialFeature");
+ node.dispatchEvent(event);
+}
+
+</script>
+<body>
+
+nothing to see here
+
+<button id="activation" onclick="activate(this, true)">Activate the share provider</button>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_activate.html b/browser/base/content/test/social/social_activate.html
new file mode 100644
index 000000000..78da597a1
--- /dev/null
+++ b/browser/base/content/test/social/social_activate.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Activation test</title>
+</head>
+<script>
+// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun
+var data = {
+ // currently required
+ "name": "Demo Social Service",
+ "iconURL": "chrome://branding/content/icon16.png",
+ "icon32URL": "chrome://branding/content/favicon32.png",
+ "icon64URL": "chrome://branding/content/icon64.png",
+
+ // at least one of these must be defined
+ "shareURL": "/browser/browser/base/content/test/social/social_share.html",
+ "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html",
+
+ // should be available for display purposes
+ "description": "A short paragraph about this provider",
+ "author": "Shane Caraveo, Mozilla",
+
+ // optional
+ "version": "1.0"
+}
+
+function activate(node) {
+ node.setAttribute("data-service", JSON.stringify(data));
+ var event = new CustomEvent("ActivateSocialFeature");
+ node.dispatchEvent(event);
+}
+
+</script>
+<body>
+
+nothing to see here
+
+<button id="activation" onclick="activate(this)">Activate The Demo Provider</button>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_activate_basic.html b/browser/base/content/test/social/social_activate_basic.html
new file mode 100644
index 000000000..78da597a1
--- /dev/null
+++ b/browser/base/content/test/social/social_activate_basic.html
@@ -0,0 +1,41 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Activation test</title>
+</head>
+<script>
+// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun
+var data = {
+ // currently required
+ "name": "Demo Social Service",
+ "iconURL": "chrome://branding/content/icon16.png",
+ "icon32URL": "chrome://branding/content/favicon32.png",
+ "icon64URL": "chrome://branding/content/icon64.png",
+
+ // at least one of these must be defined
+ "shareURL": "/browser/browser/base/content/test/social/social_share.html",
+ "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html",
+
+ // should be available for display purposes
+ "description": "A short paragraph about this provider",
+ "author": "Shane Caraveo, Mozilla",
+
+ // optional
+ "version": "1.0"
+}
+
+function activate(node) {
+ node.setAttribute("data-service", JSON.stringify(data));
+ var event = new CustomEvent("ActivateSocialFeature");
+ node.dispatchEvent(event);
+}
+
+</script>
+<body>
+
+nothing to see here
+
+<button id="activation" onclick="activate(this)">Activate The Demo Provider</button>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_activate_iframe.html b/browser/base/content/test/social/social_activate_iframe.html
new file mode 100644
index 000000000..bde884c9d
--- /dev/null
+++ b/browser/base/content/test/social/social_activate_iframe.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <title>Activation iframe test</title>
+</head>
+
+<body>
+
+<iframe src="social_activate_basic.html"/>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_crash_content_helper.js b/browser/base/content/test/social/social_crash_content_helper.js
new file mode 100644
index 000000000..4698b6957
--- /dev/null
+++ b/browser/base/content/test/social/social_crash_content_helper.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Cu = Components.utils;
+
+// Ideally we would use CrashTestUtils.jsm, but that's only available for
+// xpcshell tests - so we just copy a ctypes crasher from it.
+Cu.import("resource://gre/modules/ctypes.jsm");
+var crash = function() { // this will crash when called.
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+};
+
+
+var TestHelper = {
+ init: function() {
+ addMessageListener("social-test:crash", this);
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "social-test:crash":
+ privateNoteIntentionalCrash();
+ crash();
+ break;
+ }
+ },
+}
+
+TestHelper.init();
diff --git a/browser/base/content/test/social/social_postActivation.html b/browser/base/content/test/social/social_postActivation.html
new file mode 100644
index 000000000..e0a6acfdf
--- /dev/null
+++ b/browser/base/content/test/social/social_postActivation.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Post-Activation test</title>
+</head>
+
+<body>
+
+Post Activation landing page
+
+</body>
+</html>
diff --git a/browser/base/content/test/tabPrompts/.eslintrc.js b/browser/base/content/test/tabPrompts/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/tabPrompts/browser.ini b/browser/base/content/test/tabPrompts/browser.ini
new file mode 100644
index 000000000..9b94f14c5
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser.ini
@@ -0,0 +1,4 @@
+[browser_closeTabSpecificPanels.js]
+[browser_multiplePrompts.js]
+[browser_openPromptInBackgroundTab.js]
+support-files = openPromptOffTimeout.html
diff --git a/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
new file mode 100644
index 000000000..30c15a56f
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
@@ -0,0 +1,41 @@
+"use strict";
+
+/*
+ * This test creates multiple panels, one that has been tagged as specific to its tab's content
+ * and one that isn't. When a tab loses focus, panel specific to that tab should close.
+ * The non-specific panel should remain open.
+ *
+ */
+
+add_task(function*() {
+ let tab1 = gBrowser.addTab("http://mochi.test:8888/#0");
+ let tab2 = gBrowser.addTab("http://mochi.test:8888/#1");
+ let specificPanel = document.createElement("panel");
+ specificPanel.setAttribute("tabspecific", "true");
+ let generalPanel = document.createElement("panel");
+ let anchor = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+ anchor.appendChild(specificPanel);
+ anchor.appendChild(generalPanel);
+ is(specificPanel.state, "closed", "specificPanel starts as closed");
+ is(generalPanel.state, "closed", "generalPanel starts as closed");
+
+ let specificPanelPromise = BrowserTestUtils.waitForEvent(specificPanel, "popupshown");
+ specificPanel.openPopupAtScreen(210, 210);
+ yield specificPanelPromise;
+ is(specificPanel.state, "open", "specificPanel has been opened");
+
+ let generalPanelPromise = BrowserTestUtils.waitForEvent(generalPanel, "popupshown");
+ generalPanel.openPopupAtScreen(510, 510);
+ yield generalPanelPromise;
+ is(generalPanel.state, "open", "generalPanel has been opened");
+
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(specificPanel.state, "closed", "specificPanel panel is closed after its tab loses focus");
+ is(generalPanel.state, "open", "generalPanel is still open after tab switch");
+
+ specificPanel.remove();
+ generalPanel.remove();
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_multiplePrompts.js b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js
new file mode 100644
index 000000000..c548429ea
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js
@@ -0,0 +1,72 @@
+"use strict";
+
+/*
+ * This test triggers multiple alerts on one single tab, because it"s possible
+ * for web content to do so. The behavior is described in bug 1266353.
+ *
+ * We assert the presentation of the multiple alerts, ensuring we show only
+ * the oldest one.
+ */
+add_task(function*() {
+ const PROMPTCOUNT = 5;
+
+ let contentScript = function() {
+ var i = 5; // contentScript has no access to PROMPTCOUNT.
+ window.addEventListener("message", function() {
+ i--;
+ if (i) {
+ window.postMessage("ping", "*");
+ }
+ alert("Alert countdown #" + i);
+ });
+ window.postMessage("ping", "*");
+ };
+ let url = "data:text/html,<script>(" + encodeURIComponent(contentScript.toSource()) + ")();</script>"
+
+ let promptsOpenedPromise = new Promise(function(resolve) {
+ let unopenedPromptCount = PROMPTCOUNT;
+ Services.obs.addObserver(function observer() {
+ unopenedPromptCount--;
+ if (!unopenedPromptCount) {
+ Services.obs.removeObserver(observer, "tabmodal-dialog-loaded");
+ info("Prompts opened.");
+ resolve();
+ }
+ }, "tabmodal-dialog-loaded", false);
+ });
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
+ info("Tab loaded");
+
+ yield promptsOpenedPromise;
+
+ let promptsCount = PROMPTCOUNT;
+ while (promptsCount--) {
+ let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(prompts.length, promptsCount + 1, "There should be " + (promptsCount + 1) + " prompt(s).");
+ // The oldest should be the first.
+ let i = 0;
+ for (let prompt of prompts) {
+ is(prompt.Dialog.args.text, "Alert countdown #" + i, "The #" + i + " alert should be labelled as such.");
+ if (i !== promptsCount) {
+ is(prompt.hidden, true, "This prompt should be hidden.");
+ i++;
+ continue;
+ }
+
+ is(prompt.hidden, false, "The last prompt should not be hidden.");
+ prompt.onButtonClick(0);
+
+ // The click is handled async; wait for an event loop turn for that to
+ // happen.
+ yield new Promise(function(resolve) {
+ Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ }
+ }
+
+ let prompts = tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(prompts.length, 0, "Prompts should all be dismissed.");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
new file mode 100644
index 000000000..d244d157a
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
@@ -0,0 +1,66 @@
+"use strict";
+
+const ROOT = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://example.com/");
+let pageWithAlert = ROOT + "openPromptOffTimeout.html";
+
+registerCleanupFunction(function() {
+ Services.perms.removeAll(makeURI(pageWithAlert));
+});
+
+/*
+ * This test opens a tab that alerts when it is hidden. We then switch away
+ * from the tab, and check that by default the tab is not automatically
+ * re-selected. We also check that a checkbox appears in the alert that allows
+ * the user to enable this automatically re-selecting. We then check that
+ * checking the checkbox does actually enable that behaviour.
+ */
+add_task(function*() {
+ yield SpecialPowers.pushPrefEnv({"set": [["browser.tabs.dontfocusfordialogs", true]]});
+ let firstTab = gBrowser.selectedTab;
+ // load page that opens prompt when page is hidden
+ let openedTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageWithAlert, true);
+ let openedTabGotAttentionPromise = BrowserTestUtils.waitForAttribute("attention", openedTab, "true");
+ // switch away from that tab again - this triggers the alert.
+ yield BrowserTestUtils.switchTab(gBrowser, firstTab);
+ // ... but that's async on e10s...
+ yield openedTabGotAttentionPromise;
+ // check for attention attribute
+ is(openedTab.getAttribute("attention"), "true", "Tab with alert should have 'attention' attribute.");
+ ok(!openedTab.selected, "Tab with alert should not be selected");
+
+ // switch tab back, and check the checkbox is displayed:
+ yield BrowserTestUtils.switchTab(gBrowser, openedTab);
+ // check the prompt is there, and the extra row is present
+ let prompts = openedTab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(prompts.length, 1, "There should be 1 prompt");
+ let ourPrompt = prompts[0];
+ let row = ourPrompt.querySelector("row");
+ ok(row, "Should have found the row with our checkbox");
+ let checkbox = row.querySelector("checkbox[label*='example.com']");
+ ok(checkbox, "The checkbox should be there");
+ ok(!checkbox.checked, "Checkbox shouldn't be checked");
+ // tick box and accept dialog
+ checkbox.checked = true;
+ ourPrompt.onButtonClick(0);
+ // Wait for that click to actually be handled completely.
+ yield new Promise(function(resolve) {
+ Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ // check permission is set
+ let ps = Services.perms;
+ is(ps.ALLOW_ACTION, ps.testPermission(makeURI(pageWithAlert), "focus-tab-by-prompt"),
+ "Tab switching should now be allowed");
+
+ let openedTabSelectedPromise = BrowserTestUtils.waitForAttribute("selected", openedTab, "true");
+ // switch to other tab again
+ yield BrowserTestUtils.switchTab(gBrowser, firstTab);
+
+ // This is sync in non-e10s, but in e10s we need to wait for this, so yield anyway.
+ // Note that the switchTab promise doesn't actually guarantee anything about *which*
+ // tab ends up as selected when its event fires, so using that here wouldn't work.
+ yield openedTabSelectedPromise;
+ // should be switched back
+ ok(openedTab.selected, "Ta-dah, the other tab should now be selected again!");
+
+ yield BrowserTestUtils.removeTab(openedTab);
+});
diff --git a/browser/base/content/test/tabPrompts/openPromptOffTimeout.html b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html
new file mode 100644
index 000000000..e865c7872
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html
@@ -0,0 +1,10 @@
+<body>
+This page opens an alert box when the page is hidden.
+<script>
+document.addEventListener("visibilitychange", () => {
+ if (document.hidden) {
+ alert("You hid my page!");
+ }
+}, false);
+</script>
+</body>
diff --git a/browser/base/content/test/tabcrashed/browser.ini b/browser/base/content/test/tabcrashed/browser.ini
new file mode 100644
index 000000000..051b40d9f
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ head.js
+[browser_shown.js]
+skip-if = !e10s || !crashreporter
+[browser_clearEmail.js]
+skip-if = !e10s || !crashreporter
+[browser_showForm.js]
+skip-if = !e10s || !crashreporter
+[browser_withoutDump.js]
+skip-if = !e10s
+[browser_autoSubmitRequest.js]
+skip-if = !e10s || !crashreporter
diff --git a/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js
new file mode 100644
index 000000000..778331814
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js
@@ -0,0 +1,152 @@
+"use strict";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const AUTOSUBMIT_PREF = "browser.crashReports.unsubmittedCheck.autoSubmit2";
+
+const {TabStateFlusher} =
+ Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+// On debug builds, crashing tabs results in much thinking, which
+// slows down the test and results in intermittent test timeouts,
+// so we'll pump up the expected timeout for this test.
+requestLongerTimeout(2);
+
+/**
+ * Tests that if the user is not configured to autosubmit
+ * backlogged crash reports, that we offer to do that, and
+ * that the user can accept that offer.
+ */
+add_task(function* test_show_form() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [[AUTOSUBMIT_PREF, false]],
+ })
+
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ // Make sure we've flushed the browser messages so that
+ // we can restore it.
+ yield TabStateFlusher.flush(browser);
+
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let doc = browser.contentDocument;
+
+ // Ensure the request is visible. We can safely reach into
+ // the content since about:tabcrashed is an in-process URL.
+ let requestAutoSubmit = doc.getElementById("requestAutoSubmit");
+ Assert.ok(!requestAutoSubmit.hidden,
+ "Request for autosubmission is visible.");
+
+ // Since the pref is set to false, the checkbox should be
+ // unchecked.
+ let autoSubmit = doc.getElementById("autoSubmit");
+ Assert.ok(!autoSubmit.checked,
+ "Checkbox for autosubmission is not checked.")
+
+ // Check the checkbox, and then restore the tab.
+ autoSubmit.checked = true;
+ let restoreButton = doc.getElementById("restoreTab");
+ restoreButton.click();
+
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+
+ // The autosubmission pref should now be set.
+ Assert.ok(Services.prefs.getBoolPref(AUTOSUBMIT_PREF),
+ "Autosubmission pref should have been set.");
+ });
+});
+
+/**
+ * Tests that if the user is autosubmitting backlogged crash reports
+ * that we don't make the offer again.
+ */
+add_task(function* test_show_form() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [[AUTOSUBMIT_PREF, true]],
+ })
+
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ yield TabStateFlusher.flush(browser);
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let doc = browser.contentDocument;
+
+ // Ensure the request is NOT visible. We can safely reach into
+ // the content since about:tabcrashed is an in-process URL.
+ let requestAutoSubmit = doc.getElementById("requestAutoSubmit");
+ Assert.ok(requestAutoSubmit.hidden,
+ "Request for autosubmission is not visible.");
+
+ // Restore the tab.
+ let restoreButton = doc.getElementById("restoreTab");
+ restoreButton.click();
+
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+
+ // The autosubmission pref should still be set to true.
+ Assert.ok(Services.prefs.getBoolPref(AUTOSUBMIT_PREF),
+ "Autosubmission pref should have been set.");
+ });
+});
+
+/**
+ * Tests that we properly set the autoSubmit preference if the user is
+ * presented with a tabcrashed page without a crash report.
+ */
+add_task(function* test_no_offer() {
+ // We should default to sending the report.
+ Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport"));
+
+ yield SpecialPowers.pushPrefEnv({
+ set: [[AUTOSUBMIT_PREF, false]],
+ });
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ yield TabStateFlusher.flush(browser);
+
+ // Make it so that it seems like no dump is available for the next crash.
+ prepareNoDump();
+
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ // eslint-disable-next-line mozilla/no-cpows-in-tests
+ let doc = browser.contentDocument;
+
+ // Ensure the request to autosubmit is invisible, since there's no report.
+ let requestRect = doc.getElementById("requestAutoSubmit")
+ .getBoundingClientRect();
+ Assert.equal(0, requestRect.height,
+ "Request for autosubmission has no height");
+ Assert.equal(0, requestRect.width,
+ "Request for autosubmission has no width");
+
+ // Since the pref is set to false, the checkbox should be
+ // unchecked.
+ let autoSubmit = doc.getElementById("autoSubmit");
+ Assert.ok(!autoSubmit.checked,
+ "Checkbox for autosubmission is not checked.");
+
+ let restoreButton = doc.getElementById("restoreTab");
+ restoreButton.click();
+
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+
+ // The autosubmission pref should now be set.
+ Assert.ok(!Services.prefs.getBoolPref(AUTOSUBMIT_PREF),
+ "Autosubmission pref should not have changed.");
+ });
+
+ // We should not have changed the default value for sending the report.
+ Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport"));
+});
diff --git a/browser/base/content/test/tabcrashed/browser_clearEmail.js b/browser/base/content/test/tabcrashed/browser_clearEmail.js
new file mode 100644
index 000000000..9ec04944f
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_clearEmail.js
@@ -0,0 +1,85 @@
+"use strict";
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const EMAIL = "foo@privacy.com";
+
+/**
+ * Sets up the browser to send crash reports to the local crash report
+ * testing server.
+ */
+add_task(function* setup() {
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash
+ // reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverUrl = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ // By default, requesting the email address of the user is disabled.
+ // For the purposes of this test, we turn it back on.
+ yield SpecialPowers.pushPrefEnv({
+ set: [["browser.tabs.crashReporting.requestEmail", true]],
+ });
+
+ registerCleanupFunction(function() {
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverUrl);
+ });
+});
+
+/**
+ * Test that if we have an email address stored in prefs, and we decide
+ * not to submit the email address in the next crash report, that we
+ * clear the email address.
+ */
+add_task(function* test_clear_email() {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let prefs = TabCrashHandler.prefs;
+ let originalSendReport = prefs.getBoolPref("sendReport");
+ let originalEmailMe = prefs.getBoolPref("emailMe");
+ let originalIncludeURL = prefs.getBoolPref("includeURL");
+ let originalEmail = prefs.getCharPref("email");
+
+ // Pretend that we stored an email address from the previous
+ // crash
+ prefs.setCharPref("email", EMAIL);
+ prefs.setBoolPref("emailMe", true);
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+ let doc = browser.contentDocument;
+
+ // Since about:tabcrashed will run in the parent process, we can safely
+ // manipulate its DOM nodes directly
+ let emailMe = doc.getElementById("emailMe");
+ emailMe.checked = false;
+
+ let crashReport = promiseCrashReport({
+ Email: "",
+ });
+
+ let restoreTab = browser.contentDocument.getElementById("restoreTab");
+ restoreTab.click();
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+ yield crashReport;
+
+ is(prefs.getCharPref("email"), "", "No email address should be stored");
+
+ // Submitting the crash report may have set some prefs regarding how to
+ // send tab crash reports. Let's reset them for the next test.
+ prefs.setBoolPref("sendReport", originalSendReport);
+ prefs.setBoolPref("emailMe", originalEmailMe);
+ prefs.setBoolPref("includeURL", originalIncludeURL);
+ prefs.setCharPref("email", originalEmail);
+ });
+});
+
diff --git a/browser/base/content/test/tabcrashed/browser_showForm.js b/browser/base/content/test/tabcrashed/browser_showForm.js
new file mode 100644
index 000000000..780af93fb
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_showForm.js
@@ -0,0 +1,40 @@
+"use strict";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+// On debug builds, crashing tabs results in much thinking, which
+// slows down the test and results in intermittent test timeouts,
+// so we'll pump up the expected timeout for this test.
+requestLongerTimeout(2);
+
+/**
+ * Tests that we show the about:tabcrashed additional details form
+ * if the "submit a crash report" checkbox was checked by default.
+ */
+add_task(function* test_show_form() {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ // Flip the pref so that the checkbox should be checked
+ // by default.
+ let pref = TabCrashHandler.prefs.root + "sendReport";
+ yield SpecialPowers.pushPrefEnv({
+ set: [[pref, true]]
+ });
+
+ // Now crash the browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let doc = browser.contentDocument;
+
+ // Ensure the checkbox is checked. We can safely reach into
+ // the content since about:tabcrashed is an in-process URL.
+ let checkbox = doc.getElementById("sendReport");
+ ok(checkbox.checked, "Send report checkbox is checked.");
+
+ // Ensure the options form is displayed.
+ let options = doc.getElementById("options");
+ ok(!options.hidden, "Showing the crash report options form.");
+ });
+});
diff --git a/browser/base/content/test/tabcrashed/browser_shown.js b/browser/base/content/test/tabcrashed/browser_shown.js
new file mode 100644
index 000000000..d09d9438f
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_shown.js
@@ -0,0 +1,203 @@
+"use strict";
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const COMMENTS = "Here's my test comment!";
+const EMAIL = "foo@privacy.com";
+
+/**
+ * Sets up the browser to send crash reports to the local crash report
+ * testing server.
+ */
+add_task(function* setup() {
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash
+ // reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverUrl = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ // On debug builds, crashing tabs results in much thinking, which
+ // slows down the test and results in intermittent test timeouts,
+ // so we'll pump up the expected timeout for this test.
+ requestLongerTimeout(2);
+
+ registerCleanupFunction(function() {
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverUrl);
+ });
+});
+
+/**
+ * This function returns a Promise that resolves once the following
+ * actions have taken place:
+ *
+ * 1) A new tab is opened up at PAGE
+ * 2) The tab is crashed
+ * 3) The about:tabcrashed page's fields are set in accordance with
+ * fieldValues
+ * 4) The tab is restored
+ * 5) A crash report is received from the testing server
+ * 6) Any tab crash prefs that were overwritten are reset
+ *
+ * @param fieldValues
+ * An Object describing how to set the about:tabcrashed
+ * fields. The following properties are accepted:
+ *
+ * comments (String)
+ * The comments to put in the comment textarea
+ * email (String)
+ * The email address to put in the email address input
+ * emailMe (bool)
+ * The checked value of the "Email me" checkbox
+ * includeURL (bool)
+ * The checked value of the "Include URL" checkbox
+ *
+ * If any of these fields are missing, the defaults from
+ * the user preferences are used.
+ * @param expectedExtra
+ * An Object describing the expected values that the submitted
+ * crash report's extra data should contain.
+ * @returns Promise
+ */
+function crashTabTestHelper(fieldValues, expectedExtra) {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let prefs = TabCrashHandler.prefs;
+ let originalSendReport = prefs.getBoolPref("sendReport");
+ let originalEmailMe = prefs.getBoolPref("emailMe");
+ let originalIncludeURL = prefs.getBoolPref("includeURL");
+ let originalEmail = prefs.getCharPref("email");
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+ let doc = browser.contentDocument;
+
+ // Since about:tabcrashed will run in the parent process, we can safely
+ // manipulate its DOM nodes directly
+ let comments = doc.getElementById("comments");
+ let email = doc.getElementById("email");
+ let emailMe = doc.getElementById("emailMe");
+ let includeURL = doc.getElementById("includeURL");
+
+ if (fieldValues.hasOwnProperty("comments")) {
+ comments.value = fieldValues.comments;
+ }
+
+ if (fieldValues.hasOwnProperty("email")) {
+ email.value = fieldValues.email;
+ }
+
+ if (fieldValues.hasOwnProperty("emailMe")) {
+ emailMe.checked = fieldValues.emailMe;
+ }
+
+ if (fieldValues.hasOwnProperty("includeURL")) {
+ includeURL.checked = fieldValues.includeURL;
+ }
+
+ let crashReport = promiseCrashReport(expectedExtra);
+ let restoreTab = browser.contentDocument.getElementById("restoreTab");
+ restoreTab.click();
+ yield BrowserTestUtils.waitForEvent(tab, "SSTabRestored");
+ yield crashReport;
+
+ // Submitting the crash report may have set some prefs regarding how to
+ // send tab crash reports. Let's reset them for the next test.
+ prefs.setBoolPref("sendReport", originalSendReport);
+ prefs.setBoolPref("emailMe", originalEmailMe);
+ prefs.setBoolPref("includeURL", originalIncludeURL);
+ prefs.setCharPref("email", originalEmail);
+ });
+}
+
+/**
+ * Tests what we send with the crash report by default. By default, we do not
+ * send any comments, the URL of the crashing page, or the email address of
+ * the user.
+ */
+add_task(function* test_default() {
+ yield crashTabTestHelper({}, {
+ "Comments": null,
+ "URL": "",
+ "Email": null,
+ });
+});
+
+/**
+ * Test just sending a comment.
+ */
+add_task(function* test_just_a_comment() {
+ yield crashTabTestHelper({
+ comments: COMMENTS,
+ }, {
+ "Comments": COMMENTS,
+ "URL": "",
+ "Email": null,
+ });
+});
+
+/**
+ * Test that we don't send email if emailMe is unchecked
+ */
+add_task(function* test_no_email() {
+ yield crashTabTestHelper({
+ email: EMAIL,
+ emailMe: false,
+ }, {
+ "Comments": null,
+ "URL": "",
+ "Email": null,
+ });
+});
+
+/**
+ * Test that we can send an email address if emailMe is checked
+ */
+add_task(function* test_yes_email() {
+ yield crashTabTestHelper({
+ email: EMAIL,
+ emailMe: true,
+ }, {
+ "Comments": null,
+ "URL": "",
+ "Email": EMAIL,
+ });
+});
+
+/**
+ * Test that we will send the URL of the page if includeURL is checked.
+ */
+add_task(function* test_send_URL() {
+ yield crashTabTestHelper({
+ includeURL: true,
+ }, {
+ "Comments": null,
+ "URL": PAGE,
+ "Email": null,
+ });
+});
+
+/**
+ * Test that we can send comments, the email address, and the URL
+ */
+add_task(function* test_send_all() {
+ yield crashTabTestHelper({
+ includeURL: true,
+ emailMe: true,
+ email: EMAIL,
+ comments: COMMENTS,
+ }, {
+ "Comments": COMMENTS,
+ "URL": PAGE,
+ "Email": EMAIL,
+ });
+});
+
diff --git a/browser/base/content/test/tabcrashed/browser_withoutDump.js b/browser/base/content/test/tabcrashed/browser_withoutDump.js
new file mode 100644
index 000000000..62557f443
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_withoutDump.js
@@ -0,0 +1,36 @@
+"use strict";
+
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+add_task(function* setup() {
+ prepareNoDump();
+});
+
+/**
+ * Tests tab crash page when a dump is not available.
+ */
+add_task(function* test_without_dump() {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let tabRemovedPromise = BrowserTestUtils.removeTab(tab, { dontRemove: true });
+
+ yield ContentTask.spawn(browser, null, function*() {
+ let doc = content.document;
+ Assert.ok(!doc.documentElement.classList.contains("crashDumpAvailable"),
+ "doesn't have crash dump");
+
+ let options = doc.getElementById("options");
+ Assert.ok(options, "has crash report options");
+ Assert.ok(options.hidden, "crash report options are hidden");
+
+ doc.getElementById("closeTab").click();
+ });
+
+ yield tabRemovedPromise;
+ });
+});
diff --git a/browser/base/content/test/tabcrashed/head.js b/browser/base/content/test/tabcrashed/head.js
new file mode 100644
index 000000000..6eee08f13
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/head.js
@@ -0,0 +1,110 @@
+/**
+ * Returns a Promise that resolves once a crash report has
+ * been submitted. This function will also test the crash
+ * reports extra data to see if it matches expectedExtra.
+ *
+ * @param expectedExtra (object)
+ * An Object whose key-value pairs will be compared
+ * against the key-value pairs in the extra data of the
+ * crash report. A test failure will occur if there is
+ * a mismatch.
+ *
+ * If the value of the key-value pair is "null", this will
+ * be interpreted as "this key should not be included in the
+ * extra data", and will cause a test failure if it is detected
+ * in the crash report.
+ *
+ * Note that this will ignore any keys that are not included
+ * in expectedExtra. It's possible that the crash report
+ * will contain other extra information that is not
+ * compared against.
+ * @returns Promise
+ */
+function promiseCrashReport(expectedExtra={}) {
+ return Task.spawn(function*() {
+ info("Starting wait on crash-report-status");
+ let [subject, ] =
+ yield TestUtils.topicObserved("crash-report-status", (unused, data) => {
+ return data == "success";
+ });
+ info("Topic observed!");
+
+ if (!(subject instanceof Ci.nsIPropertyBag2)) {
+ throw new Error("Subject was not a Ci.nsIPropertyBag2");
+ }
+
+ let remoteID = getPropertyBagValue(subject, "serverCrashID");
+ if (!remoteID) {
+ throw new Error("Report should have a server ID");
+ }
+
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(Services.crashmanager._submittedDumpsDir);
+ file.append(remoteID + ".txt");
+ if (!file.exists()) {
+ throw new Error("Report should have been received by the server");
+ }
+
+ file.remove(false);
+
+ let extra = getPropertyBagValue(subject, "extra");
+ if (!(extra instanceof Ci.nsIPropertyBag2)) {
+ throw new Error("extra was not a Ci.nsIPropertyBag2");
+ }
+
+ info("Iterating crash report extra keys");
+ let enumerator = extra.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let key = enumerator.getNext().QueryInterface(Ci.nsIProperty).name;
+ let value = extra.getPropertyAsAString(key);
+ if (key in expectedExtra) {
+ if (expectedExtra[key] == null) {
+ ok(false, `Got unexpected key ${key} with value ${value}`);
+ } else {
+ is(value, expectedExtra[key],
+ `Crash report had the right extra value for ${key}`);
+ }
+ }
+ }
+ });
+}
+
+
+/**
+ * For an nsIPropertyBag, returns the value for a given
+ * key.
+ *
+ * @param bag
+ * The nsIPropertyBag to retrieve the value from
+ * @param key
+ * The key that we want to get the value for from the
+ * bag
+ * @returns The value corresponding to the key from the bag,
+ * or null if the value could not be retrieved (for
+ * example, if no value is set at that key).
+*/
+function getPropertyBagValue(bag, key) {
+ try {
+ let val = bag.getProperty(key);
+ return val;
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Monkey patches TabCrashHandler.getDumpID to return null in order to test
+ * about:tabcrashed when a dump is not available.
+ */
+function prepareNoDump() {
+ let originalGetDumpID = TabCrashHandler.getDumpID;
+ TabCrashHandler.getDumpID = function(browser) { return null; };
+ registerCleanupFunction(() => {
+ TabCrashHandler.getDumpID = originalGetDumpID;
+ });
+}
diff --git a/browser/base/content/test/tabs/.eslintrc.js b/browser/base/content/test/tabs/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/tabs/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/tabs/browser.ini b/browser/base/content/test/tabs/browser.ini
new file mode 100644
index 000000000..7771e0a6e
--- /dev/null
+++ b/browser/base/content/test/tabs/browser.ini
@@ -0,0 +1,4 @@
+[browser_tabSpinnerProbe.js]
+skip-if = !e10s # Tab spinner is e10s only.
+[browser_tabSwitchPrintPreview.js]
+skip-if = os == 'mac'
diff --git a/browser/base/content/test/tabs/browser_tabSpinnerProbe.js b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
new file mode 100644
index 000000000..c3569c2b1
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_tabSpinnerProbe.js
@@ -0,0 +1,93 @@
+"use strict";
+
+/**
+ * Tests the FX_TAB_SWITCH_SPINNER_VISIBLE_MS and
+ * FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS telemetry probes
+ */
+let gMinHangTime = 500; // ms
+let gMaxHangTime = 5 * 1000; // ms
+
+/**
+ * Make a data URI for a generic webpage with a script that hangs for a given
+ * amount of time.
+ * @param {?Number} aHangMs Number of milliseconds that the hang should last.
+ * Defaults to 0.
+ * @return {String} The data URI generated.
+ */
+function makeDataURI(aHangMs = 0) {
+ return `data:text/html,
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tab Spinner Test</title>
+ <script>
+ function hang() {
+ let hangDuration = ${aHangMs};
+ if (hangDuration > 0) {
+ let startTime = window.performance.now();
+ while(window.performance.now() - startTime < hangDuration) {}
+ }
+ }
+ </script>
+ </head>
+ <body>
+ <h1 id='header'>Tab Spinner Test</h1>
+ </body>
+ </html>`;
+}
+
+/**
+ * Returns the sum of all values in an array.
+ * @param {Array} aArray An array of integers
+ * @return {Number} The sum of the integers in the array
+ */
+function sum(aArray) {
+ return aArray.reduce(function(previousValue, currentValue) {
+ return previousValue + currentValue;
+ });
+}
+
+/**
+ * A generator intended to be run as a Task. It tests one of the tab spinner
+ * telemetry probes.
+ * @param {String} aProbe The probe to test. Should be one of:
+ * - FX_TAB_SWITCH_SPINNER_VISIBLE_MS
+ * - FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS
+ */
+function* testProbe(aProbe) {
+ info(`Testing probe: ${aProbe}`);
+ let histogram = Services.telemetry.getHistogramById(aProbe);
+ let buckets = histogram.snapshot().ranges.filter(function(value) {
+ return (value > gMinHangTime && value < gMaxHangTime);
+ });
+ let delayTime = buckets[0]; // Pick a bucket arbitrarily
+
+ // The tab spinner does not show up instantly. We need to hang for a little
+ // bit of extra time to account for the tab spinner delay.
+ delayTime += gBrowser.selectedTab.linkedBrowser.getTabBrowser()._getSwitcher().TAB_SWITCH_TIMEOUT;
+ let dataURI1 = makeDataURI(delayTime);
+ let dataURI2 = makeDataURI();
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI1);
+ histogram.clear();
+ // Queue a hang in the content process when the
+ // event loop breathes next.
+ ContentTask.spawn(tab1.linkedBrowser, null, function*() {
+ content.wrappedJSObject.hang();
+ });
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI2);
+ let snapshot = histogram.snapshot();
+ yield BrowserTestUtils.removeTab(tab2);
+ yield BrowserTestUtils.removeTab(tab1);
+ ok(sum(snapshot.counts) > 0,
+ `Spinner probe should now have a value in some bucket`);
+}
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_MS"));
+add_task(testProbe.bind(null, "FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS"));
diff --git a/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js b/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js
new file mode 100644
index 000000000..4ec36a7cc
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_tabSwitchPrintPreview.js
@@ -0,0 +1,29 @@
+const kURL1 = "data:text/html,Should I stay or should I go?";
+const kURL2 = "data:text/html,I shouldn't be here!";
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+/**
+ * Verify that if we open a new tab and try to make it the selected tab while
+ * print preview is up, that doesn't happen.
+ */
+add_task(function* () {
+ yield BrowserTestUtils.withNewTab(kURL1, function* (browser) {
+ let tab = gBrowser.addTab(kURL2);
+ document.getElementById("cmd_printPreview").doCommand();
+ gBrowser.selectedTab = tab;
+ yield BrowserTestUtils.waitForCondition(() => gInPrintPreviewMode, "should be in print preview mode");
+ isnot(gBrowser.selectedTab, tab, "Selected tab should not be the tab we added");
+ is(gBrowser.selectedTab, PrintPreviewListener._printPreviewTab, "Selected tab should be the print preview tab");
+ gBrowser.selectedTab = tab;
+ isnot(gBrowser.selectedTab, tab, "Selected tab should still not be the tab we added");
+ is(gBrowser.selectedTab, PrintPreviewListener._printPreviewTab, "Selected tab should still be the print preview tab");
+ PrintUtils.exitPrintPreview();
+ yield BrowserTestUtils.waitForCondition(() => !gInPrintPreviewMode, "should be in print preview mode");
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/browser/base/content/test/urlbar/.eslintrc.js b/browser/base/content/test/urlbar/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/urlbar/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/urlbar/authenticate.sjs b/browser/base/content/test/urlbar/authenticate.sjs
new file mode 100644
index 000000000..58da655cf
--- /dev/null
+++ b/browser/base/content/test/urlbar/authenticate.sjs
@@ -0,0 +1,220 @@
+function handleRequest(request, response)
+{
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 200, "AlmostOK");
+ response.write("Error handling request: " + e);
+ }
+}
+
+
+function reallyHandleRequest(request, response) {
+ var match;
+ var requestAuth = true, requestProxyAuth = true;
+
+ // Allow the caller to drive how authentication is processed via the query.
+ // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
+ // The extra ? allows the user/pass/realm checks to succeed if the name is
+ // at the beginning of the query string.
+ var query = "?" + request.queryString;
+
+ var expected_user = "", expected_pass = "", realm = "mochitest";
+ var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy";
+ var huge = false, plugin = false, anonymous = false;
+ var authHeaderCount = 1;
+ // user=xxx
+ match = /[^_]user=([^&]*)/.exec(query);
+ if (match)
+ expected_user = match[1];
+
+ // pass=xxx
+ match = /[^_]pass=([^&]*)/.exec(query);
+ if (match)
+ expected_pass = match[1];
+
+ // realm=xxx
+ match = /[^_]realm=([^&]*)/.exec(query);
+ if (match)
+ realm = match[1];
+
+ // proxy_user=xxx
+ match = /proxy_user=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_user = match[1];
+
+ // proxy_pass=xxx
+ match = /proxy_pass=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_pass = match[1];
+
+ // proxy_realm=xxx
+ match = /proxy_realm=([^&]*)/.exec(query);
+ if (match)
+ proxy_realm = match[1];
+
+ // huge=1
+ match = /huge=1/.exec(query);
+ if (match)
+ huge = true;
+
+ // plugin=1
+ match = /plugin=1/.exec(query);
+ if (match)
+ plugin = true;
+
+ // multiple=1
+ match = /multiple=([^&]*)/.exec(query);
+ if (match)
+ authHeaderCount = match[1]+0;
+
+ // anonymous=1
+ match = /anonymous=1/.exec(query);
+ if (match)
+ anonymous = true;
+
+ // Look for an authentication header, if any, in the request.
+ //
+ // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+ //
+ // This test only supports Basic auth. The value sent by the client is
+ // "username:password", obscured with base64 encoding.
+
+ var actual_user = "", actual_pass = "", authHeader, authPresent = false;
+ if (request.hasHeader("Authorization")) {
+ authPresent = true;
+ authHeader = request.getHeader("Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ actual_user = match[1];
+ actual_pass = match[2];
+ }
+
+ var proxy_actual_user = "", proxy_actual_pass = "";
+ if (request.hasHeader("Proxy-Authorization")) {
+ authHeader = request.getHeader("Proxy-Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ proxy_actual_user = match[1];
+ proxy_actual_pass = match[2];
+ }
+
+ // Don't request authentication if the credentials we got were what we
+ // expected.
+ if (expected_user == actual_user &&
+ expected_pass == actual_pass) {
+ requestAuth = false;
+ }
+ if (proxy_expected_user == proxy_actual_user &&
+ proxy_expected_pass == proxy_actual_pass) {
+ requestProxyAuth = false;
+ }
+
+ if (anonymous) {
+ if (authPresent) {
+ response.setStatusLine("1.0", 400, "Unexpected authorization header found");
+ } else {
+ response.setStatusLine("1.0", 200, "Authorization header not found");
+ }
+ } else {
+ if (requestProxyAuth) {
+ response.setStatusLine("1.0", 407, "Proxy authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
+ } else if (requestAuth) {
+ response.setStatusLine("1.0", 401, "Authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
+ } else {
+ response.setStatusLine("1.0", 200, "OK");
+ }
+ }
+
+ response.setHeader("Content-Type", "application/xhtml+xml", false);
+ response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
+ response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
+ response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
+ response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n");
+
+ if (huge) {
+ response.write("<div style='display: none'>");
+ for (i = 0; i < 100000; i++) {
+ response.write("123456789\n");
+ }
+ response.write("</div>");
+ response.write("<span id='footnote'>This is a footnote after the huge content fill</span>");
+ }
+
+ if (plugin) {
+ response.write("<embed id='embedtest' style='width: 400px; height: 100px;' " +
+ "type='application/x-test'></embed>\n");
+ }
+
+ response.write("</html>");
+}
+
+
+// base64 decoder
+//
+// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa()
+// doesn't seem to exist. :-(
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
+ 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+ -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+ 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+];
+const base64Pad = '=';
+
+function base64ToString(data) {
+
+ var result = '';
+ var leftbits = 0; // number of bits decoded, but yet to be appended
+ var leftdata = 0; // bits decoded, but yet to be appended
+
+ // Convert one by one.
+ for (var i = 0; i < data.length; i++) {
+ var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+ var padding = (data[i] == base64Pad);
+ // Skip illegal characters and whitespace
+ if (c == -1) continue;
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding)
+ result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits)
+ throw Components.Exception('Corrupted base64 string');
+
+ return result;
+}
diff --git a/browser/base/content/test/urlbar/browser.ini b/browser/base/content/test/urlbar/browser.ini
new file mode 100644
index 000000000..39bc086c9
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -0,0 +1,101 @@
+[DEFAULT]
+support-files =
+ dummy_page.html
+ head.js
+
+[browser_URLBarSetURI.js]
+skip-if = (os == "linux" || os == "mac") && debug # bug 970052, bug 970053
+[browser_action_keyword.js]
+skip-if = os == "linux" # Bug 1188154
+support-files =
+ print_postdata.sjs
+[browser_action_keyword_override.js]
+[browser_action_searchengine.js]
+[browser_action_searchengine_alias.js]
+[browser_autocomplete_a11y_label.js]
+[browser_autocomplete_autoselect.js]
+[browser_autocomplete_cursor.js]
+[browser_autocomplete_edit_completed.js]
+[browser_autocomplete_enter_race.js]
+[browser_autocomplete_no_title.js]
+[browser_autocomplete_tag_star_visibility.js]
+[browser_bug1104165-switchtab-decodeuri.js]
+[browser_bug1003461-switchtab-override.js]
+[browser_bug1024133-switchtab-override-keynav.js]
+[browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
+[browser_bug1070778.js]
+[browser_bug1225194-remotetab.js]
+[browser_bug304198.js]
+[browser_bug556061.js]
+subsuite = clipboard
+[browser_bug562649.js]
+[browser_bug623155.js]
+support-files =
+ redirect_bug623155.sjs
+[browser_bug783614.js]
+[browser_canonizeURL.js]
+[browser_dragdropURL.js]
+[browser_locationBarCommand.js]
+[browser_locationBarExternalLoad.js]
+[browser_moz_action_link.js]
+[browser_removeUnsafeProtocolsFromURLBarPaste.js]
+subsuite = clipboard
+[browser_search_favicon.js]
+[browser_tabMatchesInAwesomebar.js]
+support-files =
+ moz.png
+[browser_tabMatchesInAwesomebar_perwindowpb.js]
+skip-if = os == 'linux' # Bug 1104755
+[browser_urlbarAboutHomeLoading.js]
+[browser_urlbarAutoFillTrimURLs.js]
+[browser_urlbarCopying.js]
+subsuite = clipboard
+support-files =
+ authenticate.sjs
+[browser_urlbarDecode.js]
+[browser_urlbarDelete.js]
+[browser_urlbarEnter.js]
+[browser_urlbarEnterAfterMouseOver.js]
+skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
+[browser_urlbarFocusedCmdK.js]
+[browser_urlbarHashChangeProxyState.js]
+[browser_urlbarKeepStateAcrossTabSwitches.js]
+[browser_urlbarOneOffs.js]
+[browser_urlbarPrivateBrowsingWindowChange.js]
+[browser_urlbarRaceWithTabs.js]
+[browser_urlbarRevert.js]
+[browser_urlbarSearchSingleWordNotification.js]
+[browser_urlbarSearchSuggestions.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_urlbarSearchSuggestionsNotification.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_urlbarSearchTelemetry.js]
+support-files =
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+[browser_urlbarStop.js]
+[browser_urlbarTrimURLs.js]
+subsuite = clipboard
+[browser_urlbarUpdateForDomainCompletion.js]
+[browser_urlbar_autoFill_backspaced.js]
+[browser_urlbar_blanking.js]
+support-files =
+ file_blank_but_not_blank.html
+[browser_urlbar_locationchange_urlbar_edit_dos.js]
+support-files =
+ file_urlbar_edit_dos.html
+[browser_urlbar_searchsettings.js]
+[browser_urlbar_stop_pending.js]
+support-files =
+ slow-page.sjs
+[browser_urlbar_remoteness_switch.js]
+run-if = e10s
+[browser_urlHighlight.js]
+[browser_wyciwyg_urlbarCopying.js]
+subsuite = clipboard
+support-files =
+ test_wyciwyg_copying.html
diff --git a/browser/base/content/test/urlbar/browser_URLBarSetURI.js b/browser/base/content/test/urlbar/browser_URLBarSetURI.js
new file mode 100644
index 000000000..ac8352f1a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_URLBarSetURI.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/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // avoid prompting about phishing
+ Services.prefs.setIntPref(phishyUserPassPref, 32);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(phishyUserPassPref);
+ });
+
+ nextTest();
+}
+
+const phishyUserPassPref = "network.http.phishy-userpass-length";
+
+function nextTest() {
+ let test = tests.shift();
+ if (test) {
+ test(function () {
+ executeSoon(nextTest);
+ });
+ } else {
+ executeSoon(finish);
+ }
+}
+
+var tests = [
+ function revert(next) {
+ loadTabInWindow(window, function (tab) {
+ gURLBar.handleRevert();
+ is(gURLBar.textValue, "example.com", "URL bar had user/pass stripped after reverting");
+ gBrowser.removeTab(tab);
+ next();
+ });
+ },
+ function customize(next) {
+ // Need to wait for delayedStartup for the customization part of the test,
+ // since that's where BrowserToolboxCustomizeDone is set.
+ BrowserTestUtils.openNewBrowserWindow().then(function(win) {
+ loadTabInWindow(win, function () {
+ openToolbarCustomizationUI(function () {
+ closeToolbarCustomizationUI(function () {
+ is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped after customize");
+ win.close();
+ next();
+ }, win);
+ }, win);
+ });
+ });
+ },
+ function pageloaderror(next) {
+ loadTabInWindow(window, function (tab) {
+ // Load a new URL and then immediately stop it, to simulate a page load
+ // error.
+ tab.linkedBrowser.loadURI("http://test1.example.com");
+ tab.linkedBrowser.stop();
+ is(gURLBar.textValue, "example.com", "URL bar had user/pass stripped after load error");
+ gBrowser.removeTab(tab);
+ next();
+ });
+ }
+];
+
+function loadTabInWindow(win, callback) {
+ info("Loading tab");
+ let url = "http://user:pass@example.com/";
+ let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(url);
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url).then(() => {
+ info("Tab loaded");
+ is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped initially");
+ callback(tab);
+ }, true);
+}
+
+function openToolbarCustomizationUI(aCallback, aBrowserWin) {
+ if (!aBrowserWin)
+ aBrowserWin = window;
+
+ aBrowserWin.gCustomizeMode.enter();
+
+ aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded);
+ executeSoon(function() {
+ aCallback(aBrowserWin)
+ });
+ });
+}
+
+function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
+ aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded);
+ executeSoon(aCallback);
+ });
+
+ aBrowserWin.gCustomizeMode.exit();
+}
+
diff --git a/browser/base/content/test/urlbar/browser_action_keyword.js b/browser/base/content/test/urlbar/browser_action_keyword.js
new file mode 100644
index 000000000..854a7b82f
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_keyword.js
@@ -0,0 +1,119 @@
+function* promise_first_result(inputText) {
+ yield promiseAutocompleteResultPopup(inputText);
+
+ let firstResult = gURLBar.popup.richlistbox.firstChild;
+ return firstResult;
+}
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/print_postdata.sjs";
+
+add_task(function* setup() {
+ yield PlacesUtils.keywords.insert({ keyword: "get",
+ url: TEST_URL + "?q=%s" });
+ yield PlacesUtils.keywords.insert({ keyword: "post",
+ url: TEST_URL,
+ postData: "q=%s" });
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.keywords.remove("get");
+ yield PlacesUtils.keywords.remove("post");
+ while (gBrowser.tabs.length > 1) {
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+ });
+});
+
+add_task(function* get_keyword() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ let result = yield promise_first_result("get something");
+ isnot(result, null, "Expect a keyword result");
+
+ let types = new Set(result.getAttribute("type").split(/\s+/));
+ Assert.ok(types.has("keyword"));
+ is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute");
+ is(result.getAttribute("title"), "mochi.test:8888", "Expect correct title");
+
+ // We need to make a real URI out of this to ensure it's normalised for
+ // comparison.
+ let uri = NetUtil.newURI(result.getAttribute("url"));
+ is(uri.spec, PlacesUtils.mozActionURI("keyword",
+ { url: TEST_URL + "?q=something",
+ input: "get something"}),
+ "Expect correct url");
+
+ let titleHbox = result._titleText.parentNode.parentNode;
+ ok(titleHbox.classList.contains("ac-title"), "Title hbox element sanity check");
+ is_element_visible(titleHbox, "Title element should be visible");
+ is(result._titleText.textContent, "mochi.test:8888: something",
+ "Node should contain the name of the bookmark and query");
+
+ let urlHbox = result._urlText.parentNode.parentNode;
+ ok(urlHbox.classList.contains("ac-url"), "URL hbox element sanity check");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+
+ let actionHbox = result._actionText.parentNode.parentNode;
+ ok(actionHbox.classList.contains("ac-action"), "Action hbox element sanity check");
+ is_element_visible(actionHbox, "Action element should be visible");
+ is(result._actionText.textContent, "", "Action text should be empty");
+
+ // Click on the result
+ info("Normal click on result");
+ let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeMouseAtCenter(result, {});
+ yield tabPromise;
+ is(tab.linkedBrowser.currentURI.spec, TEST_URL + "?q=something",
+ "Tab should have loaded from clicking on result");
+
+ // Middle-click on the result
+ info("Middle-click on result");
+ result = yield promise_first_result("get somethingmore");
+ isnot(result, null, "Expect a keyword result");
+ // We need to make a real URI out of this to ensure it's normalised for
+ // comparison.
+ uri = NetUtil.newURI(result.getAttribute("url"));
+ is(uri.spec, PlacesUtils.mozActionURI("keyword",
+ { url: TEST_URL + "?q=somethingmore",
+ input: "get somethingmore" }),
+ "Expect correct url");
+
+ tabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ EventUtils.synthesizeMouseAtCenter(result, {button: 1});
+ let tabOpenEvent = yield tabPromise;
+ let newTab = tabOpenEvent.target;
+ yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
+ is(newTab.linkedBrowser.currentURI.spec,
+ TEST_URL + "?q=somethingmore",
+ "Tab should have loaded from middle-clicking on result");
+});
+
+
+add_task(function* post_keyword() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ let result = yield promise_first_result("post something");
+ isnot(result, null, "Expect a keyword result");
+
+ let types = new Set(result.getAttribute("type").split(/\s+/));
+ Assert.ok(types.has("keyword"));
+ is(result.getAttribute("actiontype"), "keyword", "Expect correct `actiontype` attribute");
+ is(result.getAttribute("title"), "mochi.test:8888", "Expect correct title");
+
+ is(result.getAttribute("url"),
+ PlacesUtils.mozActionURI("keyword", { url: TEST_URL,
+ input: "post something",
+ "postData": "q=something" }),
+ "Expect correct url");
+
+ // Click on the result
+ info("Normal click on result");
+ let tabPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.synthesizeMouseAtCenter(result, {});
+ yield tabPromise;
+ is(tab.linkedBrowser.currentURI.spec, TEST_URL,
+ "Tab should have loaded from clicking on result");
+
+ let postData = yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
+ return content.document.body.textContent;
+ });
+ is(postData, "q=something", "post data was submitted correctly");
+});
diff --git a/browser/base/content/test/urlbar/browser_action_keyword_override.js b/browser/base/content/test/urlbar/browser_action_keyword_override.js
new file mode 100644
index 000000000..f5a865678
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_keyword_override.js
@@ -0,0 +1,40 @@
+add_task(function*() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test" });
+ yield PlacesUtils.keywords.insert({ keyword: "keyword",
+ url: "http://example.com/?q=%s" })
+
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ });
+
+ yield promiseAutocompleteResultPopup("keyword search");
+ let result = gURLBar.popup.richlistbox.children[0];
+
+ info("Before override");
+ let titleHbox = result._titleText.parentNode.parentNode;
+ ok(titleHbox.classList.contains("ac-title"), "Title hbox element sanity check");
+ is_element_visible(titleHbox, "Title element should be visible");
+
+ let urlHbox = result._urlText.parentNode.parentNode;
+ ok(urlHbox.classList.contains("ac-url"), "URL hbox element sanity check");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+
+ let actionHbox = result._actionText.parentNode.parentNode;
+ ok(actionHbox.classList.contains("ac-action"), "Action hbox element sanity check");
+ is_element_visible(actionHbox, "Action element should be visible");
+ is(result._actionText.textContent, "", "Action text should be empty");
+
+ info("During override");
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" });
+ is_element_visible(titleHbox, "Title element should be visible");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+ is_element_visible(actionHbox, "Action element should be visible");
+ is(result._actionText.textContent, "", "Action text should be empty");
+
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+});
diff --git a/browser/base/content/test/urlbar/browser_action_searchengine.js b/browser/base/content/test/urlbar/browser_action_searchengine.js
new file mode 100644
index 000000000..d2115abba
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_searchengine.js
@@ -0,0 +1,36 @@
+add_task(function* () {
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ registerCleanupFunction(() => {
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+
+ try {
+ gBrowser.removeTab(tab);
+ } catch (ex) { /* tab may have already been closed in case of failure */ }
+
+ return PlacesTestUtils.clearHistory();
+ });
+
+ yield promiseAutocompleteResultPopup("open a search");
+ let result = gURLBar.popup.richlistbox.firstChild;
+
+ isnot(result, null, "Should have a result");
+ is(result.getAttribute("url"),
+ `moz-action:searchengine,{"engineName":"MozSearch","input":"open%20a%20search","searchQuery":"open%20a%20search"}`,
+ "Result should be a moz-action: for the correct search engine");
+ is(result.hasAttribute("image"), false, "Result shouldn't have an image attribute");
+
+ let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ result.click();
+ yield tabPromise;
+
+ is(gBrowser.selectedBrowser.currentURI.spec, "http://example.com/?q=open+a+search", "Correct URL should be loaded");
+});
diff --git a/browser/base/content/test/urlbar/browser_action_searchengine_alias.js b/browser/base/content/test/urlbar/browser_action_searchengine_alias.js
new file mode 100644
index 000000000..1967d178a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_action_searchengine_alias.js
@@ -0,0 +1,35 @@
+add_task(function* () {
+ let iconURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC";
+ Services.search.addEngineWithDetails("MozSearch", iconURI, "moz", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ registerCleanupFunction(() => {
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ Services.search.removeEngine(engine);
+
+ try {
+ gBrowser.removeTab(tab);
+ } catch (ex) { /* tab may have already been closed in case of failure */ }
+
+ return PlacesTestUtils.clearHistory();
+ });
+
+ yield promiseAutocompleteResultPopup("moz open a search");
+
+ let result = gURLBar.popup.richlistbox.children[0];
+ ok(result.hasAttribute("image"), "Result should have an image attribute");
+ ok(result.getAttribute("image") === engine.iconURI.spec,
+ "Image attribute should have the search engine's icon");
+
+ let tabPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", { });
+ yield tabPromise;
+
+ is(gBrowser.selectedBrowser.currentURI.spec, "http://example.com/?q=open+a+search");
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
new file mode 100644
index 000000000..a27f9672e
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+add_task(function* switchToTab() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:about");
+
+ yield promiseAutocompleteResultPopup("% about");
+
+ ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results");
+ let result = gURLBar.popup.richlistbox.children[1];
+ is(result.getAttribute("type"), "switchtab", "Expect right type attribute");
+ is(result.label, "about:about about:about Tab", "Result a11y label should be: <title> <url> Tab");
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* searchSuggestions() {
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ registerCleanupFunction(function () {
+ Services.search.currentEngine = oldCurrentEngine;
+ Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ });
+
+ yield promiseAutocompleteResultPopup("foo");
+ // Don't assume that the search doesn't match history or bookmarks left around
+ // by earlier tests.
+ Assert.ok(gURLBar.popup.richlistbox.children.length >= 3,
+ "Should get at least heuristic result + two search suggestions");
+ // The first expected search is the search term itself since the heuristic
+ // result will come before the search suggestions.
+ let expectedSearches = [
+ "foo",
+ "foofoo",
+ "foobar",
+ ];
+ for (let child of gURLBar.popup.richlistbox.children) {
+ if (child.getAttribute("type").split(/\s+/).indexOf("searchengine") >= 0) {
+ Assert.ok(expectedSearches.length > 0);
+ let suggestion = expectedSearches.shift();
+ Assert.equal(child.label, suggestion + " browser_searchSuggestionEngine searchSuggestionEngine.xml Search",
+ "Result label should be: <search term> <engine name> Search");
+ }
+ }
+ Assert.ok(expectedSearches.length == 0);
+ gURLBar.closePopup();
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js b/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js
new file mode 100644
index 000000000..e4e0daa8e
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_autoselect.js
@@ -0,0 +1,92 @@
+const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
+
+function repeat(limit, func) {
+ for (let i = 0; i < limit; i++) {
+ func(i);
+ }
+}
+
+function is_selected(index) {
+ is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
+
+ // This is true because although both the listbox and the one-offs can have
+ // selections, the test doesn't check that.
+ is(gURLBar.popup.oneOffSearchButtons.selectedButton, null,
+ "A result is selected, so the one-offs should not have a selection");
+}
+
+function is_selected_one_off(index) {
+ is(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, index,
+ "Expected one-off button should be selected");
+
+ // This is true because although both the listbox and the one-offs can have
+ // selections, the test doesn't check that.
+ is(gURLBar.popup.richlistbox.selectedIndex, -1,
+ "A one-off is selected, so the listbox should not have a selection");
+}
+
+add_task(function*() {
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
+ registerCleanupFunction(function* () {
+ yield PlacesTestUtils.clearHistory();
+ Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
+ });
+
+ let visits = [];
+ repeat(maxResults, i => {
+ visits.push({
+ uri: makeURI("http://example.com/autocomplete/?" + i),
+ });
+ });
+ yield PlacesTestUtils.addVisits(visits);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ yield promiseAutocompleteResultPopup("example.com/autocomplete");
+
+ let popup = gURLBar.popup;
+ let results = popup.richlistbox.children;
+ is(results.length, maxResults,
+ "Should get maxResults=" + maxResults + " results");
+ is_selected(0);
+
+ info("Key Down to select the next item");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is_selected(1);
+
+ info("Key Down maxResults-1 times should select the first one-off");
+ repeat(maxResults - 1, () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected_one_off(0);
+
+ info("Key Down numButtons-1 should select the last one-off");
+ let numButtons =
+ gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
+ repeat(numButtons - 1, () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected_one_off(numButtons - 1);
+
+ info("Key Down twice more should select the second result");
+ repeat(2, () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected(1);
+
+ info("Key Down maxResults + numButtons times should wrap around");
+ repeat(maxResults + numButtons,
+ () => EventUtils.synthesizeKey("VK_DOWN", {}));
+ is_selected(1);
+
+ info("Key Up maxResults + numButtons times should wrap around the other way");
+ repeat(maxResults + numButtons, () => EventUtils.synthesizeKey("VK_UP", {}));
+ is_selected(1);
+
+ info("Page Up will go up the list, but not wrap");
+ EventUtils.synthesizeKey("VK_PAGE_UP", {})
+ is_selected(0);
+
+ info("Page Up again will wrap around to the end of the list");
+ EventUtils.synthesizeKey("VK_PAGE_UP", {})
+ is_selected(maxResults - 1);
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_cursor.js b/browser/base/content/test/urlbar/browser_autocomplete_cursor.js
new file mode 100644
index 000000000..9cc2c6eac
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_cursor.js
@@ -0,0 +1,17 @@
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ yield promiseAutocompleteResultPopup("www.mozilla.org");
+
+ gURLBar.selectTextRange(4, 4);
+
+ is(gURLBar.popup.state, "open", "Popup should be open");
+ is(gURLBar.popup.richlistbox.selectedIndex, 0, "Should have selected something");
+
+ EventUtils.synthesizeKey("VK_RIGHT", {});
+ yield promisePopupHidden(gURLBar.popup);
+
+ is(gURLBar.selectionStart, 5, "Should have moved the cursor");
+ is(gURLBar.selectionEnd, 5, "And not selected anything");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js b/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js
new file mode 100644
index 000000000..19db1a368
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js
@@ -0,0 +1,48 @@
+add_task(function*() {
+ yield PlacesTestUtils.clearHistory();
+
+ yield PlacesTestUtils.addVisits([
+ { uri: makeURI("http://example.com/foo") },
+ { uri: makeURI("http://example.com/foo/bar") },
+ ]);
+
+ registerCleanupFunction(function* () {
+ yield PlacesTestUtils.clearHistory();
+ });
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ gURLBar.focus();
+
+ yield promiseAutocompleteResultPopup("http://example.com");
+
+ let popup = gURLBar.popup;
+ let list = popup.richlistbox;
+ let initialIndex = list.selectedIndex;
+
+ info("Key Down to select the next item.");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ let nextIndex = initialIndex + 1;
+ let nextValue = gURLBar.controller.getFinalCompleteValueAt(nextIndex);
+ is(list.selectedIndex, nextIndex, "The next item is selected.");
+ is(gURLBar.value, nextValue, "The selected URL is completed.");
+
+ info("Press backspace");
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ yield promiseSearchComplete();
+
+ let editedValue = gURLBar.textValue;
+ is(list.selectedIndex, initialIndex, "The initial index is selected again.");
+ isnot(editedValue, nextValue, "The URL has changed.");
+
+ let docLoad = waitForDocLoadAndStopIt("http://" + editedValue);
+
+ info("Press return to load edited URL.");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield Promise.all([
+ promisePopupHidden(gURLBar.popup),
+ docLoad,
+ ]);
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js b/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js
new file mode 100644
index 000000000..4e3c8943c
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_enter_race.js
@@ -0,0 +1,122 @@
+// The order of these tests matters!
+
+add_task(function* setup () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test" });
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ yield BrowserTestUtils.removeTab(tab);
+ });
+ yield PlacesUtils.keywords.insert({ keyword: "keyword",
+ url: "http://example.com/?q=%s" });
+ // Needs at least one success.
+ ok(true, "Setup complete");
+});
+
+add_task(function* test_keyword() {
+ yield promiseAutocompleteResultPopup("keyword bear");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("d", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/?q=beard");
+});
+
+add_task(function* test_sametext() {
+ yield promiseAutocompleteResultPopup("example.com", window, true);
+
+ // Simulate re-entering the same text searched the last time. This may happen
+ // through a copy paste, but clipboard handling is not much reliable, so just
+ // fire an input event.
+ info("synthesize input event");
+ let event = document.createEvent("Events");
+ event.initEvent("input", true, true);
+ gURLBar.dispatchEvent(event);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/");
+});
+
+add_task(function* test_after_empty_search() {
+ yield promiseAutocompleteResultPopup("");
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.synthesizeKey("x", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/");
+});
+
+add_task(function* test_disabled_ac() {
+ // Disable autocomplete.
+ let suggestHistory = Preferences.get("browser.urlbar.suggest.history");
+ Preferences.set("browser.urlbar.suggest.history", false);
+ let suggestBookmarks = Preferences.get("browser.urlbar.suggest.bookmark");
+ Preferences.set("browser.urlbar.suggest.bookmark", false);
+ let suggestOpenPages = Preferences.get("browser.urlbar.suggest.openpage");
+ Preferences.set("browser.urlbar.suggest.openpages", false);
+
+ Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ let engine = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ function* cleanup() {
+ Preferences.set("browser.urlbar.suggest.history", suggestHistory);
+ Preferences.set("browser.urlbar.suggest.bookmark", suggestBookmarks);
+ Preferences.set("browser.urlbar.suggest.openpage", suggestOpenPages);
+
+ Services.search.currentEngine = originalEngine;
+ let engine = Services.search.getEngineByName("MozSearch");
+ if (engine) {
+ Services.search.removeEngine(engine);
+ }
+ }
+ registerCleanupFunction(cleanup);
+
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.synthesizeKey("x", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/?q=ex");
+ yield cleanup();
+});
+
+add_task(function* test_delay() {
+ const TIMEOUT = 10000;
+ // Set a large delay.
+ let delay = Preferences.get("browser.urlbar.delay");
+ Preferences.set("browser.urlbar.delay", TIMEOUT);
+
+ registerCleanupFunction(function* () {
+ Preferences.set("browser.urlbar.delay", delay);
+ });
+
+ // This is needed to clear the current value, otherwise autocomplete may think
+ // the user removed text from the end.
+ let start = Date.now();
+ yield promiseAutocompleteResultPopup("");
+ Assert.ok((Date.now() - start) < TIMEOUT);
+
+ start = Date.now();
+ gURLBar.closePopup();
+ gURLBar.focus();
+ gURLBar.value = "e";
+ EventUtils.synthesizeKey("x", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ info("wait for the page to load");
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedTab.linkedBrowser,
+ false, "http://example.com/");
+ Assert.ok((Date.now() - start) < TIMEOUT);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_no_title.js b/browser/base/content/test/urlbar/browser_autocomplete_no_title.js
new file mode 100644
index 000000000..8d608550b
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_no_title.js
@@ -0,0 +1,15 @@
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ let uri = NetUtil.newURI("http://bug1060642.example.com/beards/are/pretty/great");
+ yield PlacesTestUtils.addVisits([{uri: uri, title: ""}]);
+
+ yield promiseAutocompleteResultPopup("bug1060642");
+ ok(gURLBar.popup.richlistbox.children.length > 1, "Should get at least 2 results");
+ let result = gURLBar.popup.richlistbox.children[1];
+ is(result._titleText.textContent, "bug1060642.example.com", "Result title should be as expected");
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js b/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js
new file mode 100644
index 000000000..8a69b4b44
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js
@@ -0,0 +1,102 @@
+add_task(function*() {
+ registerCleanupFunction(() => {
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ });
+
+ function* addTagItem(tagName) {
+ let uri = NetUtil.newURI(`http://example.com/this/is/tagged/${tagName}`);
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ `test ${tagName}`);
+ PlacesUtils.tagging.tagURI(uri, [tagName]);
+ yield PlacesTestUtils.addVisits([{uri: uri, title: `Test page with tag ${tagName}`}]);
+ }
+
+ // We use different tags for each part of the test, as otherwise the
+ // autocomplete code tries to be smart by using the previously cached element
+ // without updating it (since all parameters it knows about are the same).
+
+ let testcases = [{
+ description: "Test with suggest.bookmark=true",
+ tagName: "tagtest1",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "tagtest1",
+ expected: {
+ type: "bookmark",
+ typeImageVisible: true,
+ },
+ }, {
+ description: "Test with suggest.bookmark=false",
+ tagName: "tagtest2",
+ prefs: {
+ "suggest.bookmark": false,
+ },
+ input: "tagtest2",
+ expected: {
+ type: "tag",
+ typeImageVisible: false,
+ },
+ }, {
+ description: "Test with suggest.bookmark=true (again)",
+ tagName: "tagtest3",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "tagtest3",
+ expected: {
+ type: "bookmark",
+ typeImageVisible: true,
+ },
+ }, {
+ description: "Test with bookmark restriction token",
+ tagName: "tagtest4",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "* tagtest4",
+ expected: {
+ type: "bookmark",
+ typeImageVisible: true,
+ },
+ }, {
+ description: "Test with history restriction token",
+ tagName: "tagtest5",
+ prefs: {
+ "suggest.bookmark": true,
+ },
+ input: "^ tagtest5",
+ expected: {
+ type: "tag",
+ typeImageVisible: false,
+ },
+ }];
+
+ for (let testcase of testcases) {
+ info(`Test case: ${testcase.description}`);
+
+ yield addTagItem(testcase.tagName);
+ for (let prefName of Object.keys(testcase.prefs)) {
+ Services.prefs.setBoolPref(`browser.urlbar.${prefName}`, testcase.prefs[prefName]);
+ }
+
+ yield promiseAutocompleteResultPopup(testcase.input);
+ let result = gURLBar.popup.richlistbox.children[1];
+ ok(result && !result.collasped, "Should have result");
+
+ is(result.getAttribute("type"), testcase.expected.type, "Result should have expected type");
+
+ let typeIconStyle = window.getComputedStyle(result._typeIcon);
+ let imageURL = typeIconStyle.listStyleImage;
+ if (testcase.expected.typeImageVisible) {
+ ok(/^url\(.+\)$/.test(imageURL), "Type image should be visible");
+ } else {
+ is(imageURL, "none", "Type image should be hidden");
+ }
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ }
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js b/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js
new file mode 100644
index 000000000..89f604491
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.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/. */
+
+add_task(function* test_switchtab_override() {
+ let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+ info("Opening first tab");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
+
+ info("Opening and selecting second tab");
+ let secondTab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(() => {
+ try {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(secondTab);
+ } catch (ex) { /* tabs may have already been closed in case of failure */ }
+ });
+
+ info("Wait for autocomplete")
+ let deferred = Promise.defer();
+ let onSearchComplete = gURLBar.onSearchComplete;
+ registerCleanupFunction(() => {
+ gURLBar.onSearchComplete = onSearchComplete;
+ });
+ gURLBar.onSearchComplete = function () {
+ ok(gURLBar.popupOpen, "The autocomplete popup is correctly open");
+ onSearchComplete.apply(gURLBar);
+ deferred.resolve();
+ }
+
+ gURLBar.focus();
+ gURLBar.value = "dummy_pag";
+ EventUtils.synthesizeKey("e", {});
+ yield deferred.promise;
+
+ info("Select second autocomplete popup entry");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found");
+
+ info("Override switch-to-tab");
+ deferred = Promise.defer();
+ // In case of failure this would switch tab.
+ let onTabSelect = event => {
+ deferred.reject(new Error("Should have overridden switch to tab"));
+ };
+ gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false);
+ registerCleanupFunction(() => {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
+ });
+ // Otherwise it would load the page.
+ BrowserTestUtils.browserLoaded(secondTab.linkedBrowser).then(deferred.resolve);
+
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" });
+ EventUtils.synthesizeKey("VK_RETURN", { });
+ info(`gURLBar.value = ${gURLBar.value}`);
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+ yield deferred.promise;
+
+ yield PlacesTestUtils.clearHistory();
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js b/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js
new file mode 100644
index 000000000..2d97ea07b
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.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/. */
+
+add_task(function* test_switchtab_override_keynav() {
+ let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+ info("Opening first tab");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
+
+ info("Opening and selecting second tab");
+ let secondTab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(() => {
+ try {
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(secondTab);
+ } catch (ex) { /* tabs may have already been closed in case of failure */ }
+ return PlacesTestUtils.clearHistory();
+ });
+
+ gURLBar.focus();
+ gURLBar.value = "dummy_pag";
+ EventUtils.synthesizeKey("e", {});
+ yield promiseSearchComplete();
+
+ info("Select second autocomplete popup entry");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(/moz-action:switchtab/.test(gURLBar.value), "switch to tab entry found");
+
+ info("Shift+left on switch-to-tab entry");
+
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keydown" });
+ EventUtils.synthesizeKey("VK_LEFT", { shiftKey: true });
+ EventUtils.synthesizeKey("VK_SHIFT", { type: "keyup" });
+
+ ok(!/moz-action:switchtab/.test(gURLBar.inputField.value), "switch to tab should be hidden");
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js b/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
new file mode 100644
index 000000000..9e779ade1
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(function* test_ignoreFragment() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home#1");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ let numTabsAtStart = gBrowser.tabs.length;
+
+ switchTab("about:home#1", true);
+ switchTab("about:mozilla", true);
+
+ let hashChangePromise = ContentTask.spawn(tabRefAboutHome.linkedBrowser, null, function* () {
+ yield ContentTaskUtils.waitForEvent(this, "hashchange", false);
+ });
+ switchTab("about:home#2", true, { ignoreFragment: "whenComparingAndReplace" });
+ is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to");
+ yield hashChangePromise;
+ is(gBrowser.currentURI.ref, "2", "The ref should be updated to the new ref");
+ switchTab("about:mozilla", true);
+ switchTab("about:home#3", true, { ignoreFragment: "whenComparing" });
+ is(tabRefAboutHome, gBrowser.selectedTab, "The same about:home tab should be switched to");
+ is(gBrowser.currentURI.ref, "2", "The ref should be unchanged since the fragment is only ignored when comparing");
+ switchTab("about:mozilla", true);
+ switchTab("about:home#1", false);
+ isnot(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should not be initial about:blank tab");
+ is(gBrowser.tabs.length, numTabsAtStart + 1, "Should have one new tab opened");
+ switchTab("about:mozilla", true);
+ switchTab("about:home", true, {ignoreFragment: "whenComparingAndReplace"});
+ yield BrowserTestUtils.waitForCondition(function() {
+ return tabRefAboutHome.linkedBrowser.currentURI.spec == "about:home";
+ });
+ is(tabRefAboutHome.linkedBrowser.currentURI.spec, "about:home", "about:home shouldn't have hash");
+ switchTab("about:about", false, { ignoreFragment: "whenComparingAndReplace" });
+ cleanupTestTabs();
+});
+
+add_task(function* test_ignoreQueryString() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ switchTab("about:home?hello=firefox", true);
+ switchTab("about:home?hello=firefoxos", false);
+ // Remove the last opened tab to test ignoreQueryString option.
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos", true, { ignoreQueryString: true });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ is(gBrowser.currentURI.spec, "about:home?hello=firefox", "The spec should NOT be updated to the new query string");
+ cleanupTestTabs();
+});
+
+add_task(function* test_replaceQueryString() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ switchTab("about:home", false);
+ switchTab("about:home?hello=firefox", true);
+ switchTab("about:home?hello=firefoxos", false);
+ // Remove the last opened tab to test replaceQueryString option.
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos", true, { replaceQueryString: true });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ // Wait for the tab to load the new URI spec.
+ yield BrowserTestUtils.browserLoaded(tabRefAboutHome.linkedBrowser);
+ is(gBrowser.currentURI.spec, "about:home?hello=firefoxos", "The spec should be updated to the new spec");
+ cleanupTestTabs();
+});
+
+add_task(function* test_replaceQueryStringAndFragment() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox#aaa");
+ let tabRefAboutMozilla =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla?hello=firefoxos#aaa");
+
+ switchTab("about:home", false);
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefox#aaa", true);
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ switchTab("about:mozilla?hello=firefox#bbb", true, { replaceQueryString: true, ignoreFragment: "whenComparingAndReplace" });
+ is(tabRefAboutMozilla, gBrowser.selectedTab, "Selected tab should be the initial about:mozilla tab");
+ switchTab("about:home?hello=firefoxos#bbb", true, { ignoreQueryString: true, ignoreFragment: "whenComparingAndReplace" });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ cleanupTestTabs();
+});
+
+add_task(function* test_ignoreQueryStringIgnoresFragment() {
+ let tabRefAboutHome =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home?hello=firefox#aaa");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla?hello=firefoxos#aaa");
+
+ switchTab("about:home?hello=firefox#bbb", false, { ignoreQueryString: true });
+ gBrowser.removeCurrentTab();
+ switchTab("about:home?hello=firefoxos#aaa", true, { ignoreQueryString: true });
+ is(tabRefAboutHome, gBrowser.selectedTab, "Selected tab should be the initial about:home tab");
+ cleanupTestTabs();
+});
+
+// Begin helpers
+
+function cleanupTestTabs() {
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeCurrentTab();
+}
+
+function switchTab(aURI, aShouldFindExistingTab, aOpenParams = {}) {
+ // Build the description before switchToTabHavingURI deletes the object properties.
+ let msg = `Should switch to existing ${aURI} tab if one existed, ` +
+ `${(aOpenParams.ignoreFragment ? "ignoring" : "including")} fragment portion, `;
+ if (aOpenParams.replaceQueryString) {
+ msg += "replacing";
+ } else if (aOpenParams.ignoreQueryString) {
+ msg += "ignoring";
+ } else {
+ msg += "including";
+ }
+ msg += " query string.";
+ let tabFound = switchToTabHavingURI(aURI, true, aOpenParams);
+ is(tabFound, aShouldFindExistingTab, msg);
+}
+
+registerCleanupFunction(cleanupTestTabs);
diff --git a/browser/base/content/test/urlbar/browser_bug1070778.js b/browser/base/content/test/urlbar/browser_bug1070778.js
new file mode 100644
index 000000000..ab88d04d8
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1070778.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function is_selected(index) {
+ is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
+}
+
+add_task(function*() {
+ let bookmarks = [];
+ bookmarks.push((yield PlacesUtils.bookmarks
+ .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/?q=%s",
+ title: "test" })));
+ yield PlacesUtils.keywords.insert({ keyword: "keyword",
+ url: "http://example.com/?q=%s" });
+
+ // This item only needed so we can select the keyword item, select something
+ // else, then select the keyword item again.
+ bookmarks.push((yield PlacesUtils.bookmarks
+ .insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/keyword",
+ title: "keyword abc" })));
+
+ registerCleanupFunction(function* () {
+ for (let bm of bookmarks) {
+ yield PlacesUtils.bookmarks.remove(bm);
+ }
+ });
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+ yield promiseAutocompleteResultPopup("keyword a");
+
+ // First item should already be selected
+ is_selected(0);
+ // Select next one (important!)
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is_selected(1);
+ // Re-select keyword item
+ EventUtils.synthesizeKey("VK_UP", {});
+ is_selected(0);
+
+ EventUtils.synthesizeKey("b", {});
+ yield promiseSearchComplete();
+
+ is(gURLBar.textValue, "keyword ab", "urlbar should have expected input");
+
+ let result = gURLBar.popup.richlistbox.firstChild;
+ isnot(result, null, "Should have first item");
+ let uri = NetUtil.newURI(result.getAttribute("url"));
+ is(uri.spec, PlacesUtils.mozActionURI("keyword", {url: "http://example.com/?q=ab", input: "keyword ab"}), "Expect correct url");
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promisePopupHidden(gURLBar.popup);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js b/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js
new file mode 100644
index 000000000..d165d7304
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js
@@ -0,0 +1,29 @@
+add_task(function* test_switchtab_decodeuri() {
+ info("Opening first tab");
+ const TEST_URL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#test%7C1";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Opening and selecting second tab");
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ info("Wait for autocomplete")
+ yield promiseAutocompleteResultPopup("dummy_page");
+
+ info("Select autocomplete popup entry");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(gURLBar.value.startsWith("moz-action:switchtab"), "switch to tab entry found");
+
+ info("switch-to-tab");
+ yield new Promise((resolve, reject) => {
+ // In case of success it should switch tab.
+ gBrowser.tabContainer.addEventListener("TabSelect", function select() {
+ gBrowser.tabContainer.removeEventListener("TabSelect", select, false);
+ is(gBrowser.selectedTab, tab, "Should have switched to the right tab");
+ resolve();
+ }, false);
+ EventUtils.synthesizeKey("VK_RETURN", { });
+ });
+
+ gBrowser.removeCurrentTab();
+ yield PlacesTestUtils.clearHistory();
+});
diff --git a/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js b/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js
new file mode 100644
index 000000000..3b4a44e76
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js
@@ -0,0 +1,16 @@
+add_task(function* test_remotetab_opens() {
+ const url = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+ yield BrowserTestUtils.withNewTab({url: "about:robots", gBrowser}, function* () {
+ // Set the urlbar to include the moz-action
+ gURLBar.value = "moz-action:remotetab," + JSON.stringify({ url });
+ // Focus the urlbar so we can press enter
+ gURLBar.focus();
+
+ // The URL is going to open in the current tab as it is currently about:blank
+ let promiseTabLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield promiseTabLoaded;
+
+ Assert.equal(gBrowser.selectedTab.linkedBrowser.currentURI.spec, url, "correct URL loaded");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_bug304198.js b/browser/base/content/test/urlbar/browser_bug304198.js
new file mode 100644
index 000000000..dc8d39fae
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug304198.js
@@ -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/. */
+
+add_task(function* () {
+ let charsToDelete, deletedURLTab, fullURLTab, partialURLTab, testPartialURL, testURL;
+
+ charsToDelete = 5;
+ deletedURLTab = gBrowser.addTab();
+ fullURLTab = gBrowser.addTab();
+ partialURLTab = gBrowser.addTab();
+ testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+ let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, testURL);
+ let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, testURL);
+ let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, testURL);
+ deletedURLTab.linkedBrowser.loadURI(testURL);
+ fullURLTab.linkedBrowser.loadURI(testURL);
+ partialURLTab.linkedBrowser.loadURI(testURL);
+ yield Promise.all([loaded1, loaded2, loaded3]);
+
+ testURL = gURLBar.trimValue(testURL);
+ testPartialURL = testURL.substr(0, (testURL.length - charsToDelete));
+
+ function cleanUp() {
+ gBrowser.removeTab(fullURLTab);
+ gBrowser.removeTab(partialURLTab);
+ gBrowser.removeTab(deletedURLTab);
+ }
+
+ function* cycleTabs() {
+ yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
+
+ yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
+ is(gURLBar.textValue, testPartialURL, 'gURLBar.textValue should be testPartialURL after switching back to partialURLTab');
+ yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
+ is(gURLBar.textValue, '', 'gURLBar.textValue should be "" after switching back to deletedURLTab');
+
+ yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
+ }
+
+ function urlbarBackspace() {
+ return new Promise((resolve, reject) => {
+ gBrowser.selectedBrowser.focus();
+ gURLBar.addEventListener("input", function () {
+ gURLBar.removeEventListener("input", arguments.callee, false);
+ resolve();
+ }, false);
+ gURLBar.focus();
+ if (gURLBar.selectionStart == gURLBar.selectionEnd) {
+ gURLBar.selectionStart = gURLBar.selectionEnd = gURLBar.textValue.length;
+ }
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ });
+ }
+
+ function* prepareDeletedURLTab() {
+ yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to deletedURLTab');
+
+ // simulate the user removing the whole url from the location bar
+ gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", true);
+
+ yield urlbarBackspace();
+ is(gURLBar.textValue, "", 'gURLBar.textValue should be "" (just set)');
+ if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
+ gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+ }
+ }
+
+ function* prepareFullURLTab() {
+ yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to fullURLTab');
+ }
+
+ function* preparePartialURLTab() {
+ yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
+ is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to partialURLTab');
+
+ // simulate the user removing part of the url from the location bar
+ gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", false);
+
+ let deleted = 0;
+ while (deleted < charsToDelete) {
+ yield urlbarBackspace(arguments.callee);
+ deleted++;
+ }
+
+ is(gURLBar.textValue, testPartialURL, "gURLBar.textValue should be testPartialURL (just set)");
+ if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
+ gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+ }
+ }
+
+ // prepare the three tabs required by this test
+
+ // First tab
+ yield* prepareFullURLTab();
+ yield* preparePartialURLTab();
+ yield* prepareDeletedURLTab();
+
+ // now cycle the tabs and make sure everything looks good
+ yield* cycleTabs();
+ cleanUp();
+});
+
+
diff --git a/browser/base/content/test/urlbar/browser_bug556061.js b/browser/base/content/test/urlbar/browser_bug556061.js
new file mode 100644
index 000000000..4c6ac5bf5
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug556061.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+var testActionURL = "moz-action:switchtab," + JSON.stringify({url: testURL});
+testURL = gURLBar.trimValue(testURL);
+var testTab;
+
+function runNextTest() {
+ if (tests.length) {
+ let t = tests.shift();
+ waitForClipboard(t.expected, t.setup, function() {
+ t.success();
+ runNextTest();
+ }, cleanup);
+ }
+ else {
+ cleanup();
+ }
+}
+
+function cleanup() {
+ gBrowser.removeTab(testTab);
+ finish();
+}
+
+var tests = [
+ {
+ expected: testURL,
+ setup: function() {
+ gURLBar.value = testActionURL;
+ gURLBar.valueIsTyped = true;
+ is(gURLBar.value, testActionURL, "gURLBar starts with the correct real value");
+ is(gURLBar.textValue, testURL, "gURLBar starts with the correct display value");
+
+ // Focus the urlbar so we can select it all & copy
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ },
+ success: function() {
+ is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying");
+ }
+ },
+ {
+ expected: testURL.substring(0, 10),
+ setup: function() {
+ // Set selectionStart/End manually and make sure it matches the substring
+ gURLBar.selectionStart = 0;
+ gURLBar.selectionEnd = 10;
+ goDoCommand("cmd_copy");
+ },
+ success: function() {
+ is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying");
+ }
+ },
+ {
+ expected: testURL,
+ setup: function() {
+ // Setup for cut test...
+ // Select all
+ gURLBar.select();
+ goDoCommand("cmd_cut");
+ },
+ success: function() {
+ is(gURLBar.value, "", "gURLBar.value is now empty");
+ }
+ },
+ {
+ expected: testURL.substring(testURL.length - 10, testURL.length),
+ setup: function() {
+ // Reset urlbar value
+ gURLBar.value = testActionURL;
+ gURLBar.valueIsTyped = true;
+ // Sanity check that we have the right value
+ is(gURLBar.value, testActionURL, "gURLBar starts with the correct real value");
+ is(gURLBar.textValue, testURL, "gURLBar starts with the correct display value");
+
+ // Now just select part of the value & cut that.
+ gURLBar.selectionStart = testURL.length - 10;
+ gURLBar.selectionEnd = testURL.length;
+ goDoCommand("cmd_cut");
+ },
+ success: function() {
+ is(gURLBar.value, testURL.substring(0, testURL.length - 10), "gURLBar.value has the correct value");
+ }
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+ testTab = gBrowser.addTab();
+ gBrowser.selectedTab = testTab;
+
+ // Kick off the testing
+ runNextTest();
+}
diff --git a/browser/base/content/test/urlbar/browser_bug562649.js b/browser/base/content/test/urlbar/browser_bug562649.js
new file mode 100644
index 000000000..f56e430ee
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug562649.js
@@ -0,0 +1,24 @@
+function test() {
+ const URI = "data:text/plain,bug562649";
+ browserDOMWindow.openURI(makeURI(URI),
+ null,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI");
+ is(gURLBar.value, URI, "location bar value matches test URI");
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.removeCurrentTab({ skipPermitUnload: true });
+ is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs");
+ is(gURLBar.value, URI, "location bar value matches test URI after switching tabs");
+
+ waitForExplicitFinish();
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(() => {
+ is(gBrowser.userTypedValue, null, "userTypedValue is null as the page has loaded");
+ is(gURLBar.value, URI, "location bar value matches test URI as the page has loaded");
+
+ gBrowser.removeCurrentTab({ skipPermitUnload: true });
+ finish();
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_bug623155.js b/browser/base/content/test/urlbar/browser_bug623155.js
new file mode 100644
index 000000000..dd6ff8c85
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug623155.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const REDIRECT_FROM = "https://example.com/browser/browser/base/content/test/urlbar/" +
+ "redirect_bug623155.sjs";
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function isRedirectedURISpec(aURISpec) {
+ return isRedirectedURI(Services.io.newURI(aURISpec, null, null));
+}
+
+function isRedirectedURI(aURI) {
+ // Compare only their before-hash portion.
+ return Services.io.newURI(REDIRECT_TO, null, null)
+ .equalsExceptRef(aURI);
+}
+
+/*
+ Test.
+
+1. Load
+https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#BG
+ in a background tab.
+
+2. The redirected URI is <https://www.bank1.com/#BG>, which displayes a cert
+ error page.
+
+3. Switch the tab to foreground.
+
+4. Check the URLbar's value, expecting <https://www.bank1.com/#BG>
+
+5. Load
+https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#FG
+ in the foreground tab.
+
+6. The redirected URI is <https://www.bank1.com/#FG>. And this is also
+ a cert-error page.
+
+7. Check the URLbar's value, expecting <https://www.bank1.com/#FG>
+
+8. End.
+
+ */
+
+var gNewTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Load a URI in the background.
+ gNewTab = gBrowser.addTab(REDIRECT_FROM + "#BG");
+ gBrowser.getBrowserForTab(gNewTab)
+ .webProgress
+ .addProgressListener(gWebProgressListener,
+ Components.interfaces.nsIWebProgress
+ .NOTIFY_LOCATION);
+}
+
+var gWebProgressListener = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ // ---------------------------------------------------------------------------
+ // NOTIFY_LOCATION mode should work fine without these methods.
+ //
+ // onStateChange: function() {},
+ // onStatusChange: function() {},
+ // onProgressChange: function() {},
+ // onSecurityChange: function() {},
+ // ----------------------------------------------------------------------------
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (!aRequest) {
+ // This is bug 673752, or maybe initial "about:blank".
+ return;
+ }
+
+ ok(gNewTab, "There is a new tab.");
+ ok(isRedirectedURI(aLocation),
+ "onLocationChange catches only redirected URI.");
+
+ if (aLocation.ref == "BG") {
+ // This is background tab's request.
+ isnot(gNewTab, gBrowser.selectedTab, "This is a background tab.");
+ } else if (aLocation.ref == "FG") {
+ // This is foreground tab's request.
+ is(gNewTab, gBrowser.selectedTab, "This is a foreground tab.");
+ }
+ else {
+ // We shonuld not reach here.
+ ok(false, "This URI hash is not expected:" + aLocation.ref);
+ }
+
+ let isSelectedTab = gNewTab.selected;
+ setTimeout(delayed, 0, isSelectedTab);
+ }
+};
+
+function delayed(aIsSelectedTab) {
+ // Switch tab and confirm URL bar.
+ if (!aIsSelectedTab) {
+ gBrowser.selectedTab = gNewTab;
+ }
+
+ let currentURI = gBrowser.selectedBrowser.currentURI.spec;
+ ok(isRedirectedURISpec(currentURI),
+ "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab);
+ is(gURLBar.value, currentURI,
+ "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab);
+
+ if (!aIsSelectedTab) {
+ // If this was a background request, go on a foreground request.
+ gBrowser.selectedBrowser.loadURI(REDIRECT_FROM + "#FG");
+ }
+ else {
+ // Othrewise, nothing to do remains.
+ finish();
+ }
+}
+
+/* Cleanup */
+registerCleanupFunction(function() {
+ if (gNewTab) {
+ gBrowser.getBrowserForTab(gNewTab)
+ .webProgress
+ .removeProgressListener(gWebProgressListener);
+
+ gBrowser.removeTab(gNewTab);
+ }
+ gNewTab = null;
+});
diff --git a/browser/base/content/test/urlbar/browser_bug783614.js b/browser/base/content/test/urlbar/browser_bug783614.js
new file mode 100644
index 000000000..ebc62e8fa
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_bug783614.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ gURLBar.focus();
+ gURLBar.inputField.value = "https://example.com/";
+ gURLBar.selectionStart = 4;
+ gURLBar.selectionEnd = 5;
+ goDoCommand("cmd_cut");
+ is(gURLBar.inputField.value, "http://example.com/", "location bar value after cutting 's' from https");
+ gURLBar.handleRevert();
+}
diff --git a/browser/base/content/test/urlbar/browser_canonizeURL.js b/browser/base/content/test/urlbar/browser_canonizeURL.js
new file mode 100644
index 000000000..59ab54ca0
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_canonizeURL.js
@@ -0,0 +1,42 @@
+add_task(function*() {
+ let testcases = [
+ ["example", "http://www.example.net/", { shiftKey: true }],
+ // Check that a direct load is not overwritten by a previous canonization.
+ ["http://example.com/test/", "http://example.com/test/", {}],
+ ["ex-ample", "http://www.ex-ample.net/", { shiftKey: true }],
+ [" example ", "http://www.example.net/", { shiftKey: true }],
+ [" example/foo ", "http://www.example.net/foo", { shiftKey: true }],
+ [" example/foo bar ", "http://www.example.net/foo%20bar", { shiftKey: true }],
+ ["example.net", "http://example.net/", { shiftKey: true }],
+ ["http://example", "http://example/", { shiftKey: true }],
+ ["example:8080", "http://example:8080/", { shiftKey: true }],
+ ["ex-ample.foo", "http://ex-ample.foo/", { shiftKey: true }],
+ ["example.foo/bar ", "http://example.foo/bar", { shiftKey: true }],
+ ["1.1.1.1", "http://1.1.1.1/", { shiftKey: true }],
+ ["ftp://example", "ftp://example/", { shiftKey: true }],
+ ["ftp.example.bar", "http://ftp.example.bar/", { shiftKey: true }],
+ ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec, { shiftKey: true }],
+ ];
+
+ // Disable autoFill for this test, since it could mess up the results.
+ let autoFill = Preferences.get("browser.urlbar.autoFill");
+ Preferences.set("browser.urlbar.autoFill", false);
+ registerCleanupFunction(() => {
+ Preferences.set("browser.urlbar.autoFill", autoFill);
+ });
+
+ for (let [inputValue, expectedURL, options] of testcases) {
+ let promiseLoad = waitForDocLoadAndStopIt(expectedURL);
+ gURLBar.focus();
+ if (Object.keys(options).length > 0) {
+ gURLBar.selectionStart = gURLBar.selectionEnd =
+ gURLBar.inputField.value.length;
+ gURLBar.inputField.value = inputValue.slice(0, -1);
+ EventUtils.synthesizeKey(inputValue.slice(-1), {});
+ } else {
+ gURLBar.textValue = inputValue;
+ }
+ EventUtils.synthesizeKey("VK_RETURN", options);
+ yield promiseLoad;
+ }
+});
diff --git a/browser/base/content/test/urlbar/browser_dragdropURL.js b/browser/base/content/test/urlbar/browser_dragdropURL.js
new file mode 100644
index 000000000..ec2906700
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_dragdropURL.js
@@ -0,0 +1,15 @@
+"use strict";
+
+const TEST_URL = "data:text/html,a test page";
+const DRAG_URL = "http://www.example.com/";
+
+add_task(function* checkURLBarUpdateForDrag() {
+ yield BrowserTestUtils.withNewTab(TEST_URL, function* (browser) {
+ // Have to use something other than the URL bar as a source, so picking the
+ // downloads button somewhat arbitrarily:
+ EventUtils.synthesizeDrop(document.getElementById("downloads-button"), gURLBar,
+ [[{type: "text/plain", data: DRAG_URL}]], "copy", window);
+ is(gURLBar.value, TEST_URL, "URL bar value should not have changed");
+ is(gBrowser.selectedBrowser.userTypedValue, null, "Stored URL bar value should not have changed");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_locationBarCommand.js b/browser/base/content/test/urlbar/browser_locationBarCommand.js
new file mode 100644
index 000000000..935bdf758
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_locationBarCommand.js
@@ -0,0 +1,218 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_VALUE = "example.com";
+const START_VALUE = "example.org";
+
+add_task(function* setup() {
+ Services.prefs.setBoolPref("browser.altClickSave", true);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.altClickSave");
+ });
+});
+
+add_task(function* alt_left_click_test() {
+ info("Running test: Alt left click");
+
+ // Monkey patch saveURL() to avoid dealing with file save code paths.
+ let oldSaveURL = saveURL;
+ let saveURLPromise = new Promise(resolve => {
+ saveURL = () => {
+ // Restore old saveURL() value.
+ saveURL = oldSaveURL;
+ resolve();
+ };
+ });
+
+ triggerCommand(true, {altKey: true});
+
+ yield saveURLPromise;
+ ok(true, "SaveURL was called");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+});
+
+add_task(function* shift_left_click_test() {
+ info("Running test: Shift left click");
+
+ let newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ triggerCommand(true, {shiftKey: true});
+ let win = yield newWindowPromise;
+
+ // Wait for the initial browser to load.
+ let browser = win.gBrowser.selectedBrowser;
+ let destinationURL = "http://" + TEST_VALUE + "/";
+ yield Promise.all([
+ BrowserTestUtils.browserLoaded(browser),
+ BrowserTestUtils.waitForLocationChange(win.gBrowser, destinationURL)
+ ]);
+
+ info("URL should be loaded in a new window");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused");
+ is(win.gURLBar.textValue, TEST_VALUE, "New URL is loaded in new window");
+
+ // Cleanup.
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+add_task(function* right_click_test() {
+ info("Running test: Right click on go button");
+
+ // Add a new tab.
+ yield* promiseOpenNewTab();
+
+ triggerCommand(true, {button: 2});
+
+ // Right click should do nothing (context menu will be shown).
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* shift_accel_left_click_test() {
+ info("Running test: Shift+Ctrl/Cmd left click on go button");
+
+ // Add a new tab.
+ let tab = yield* promiseOpenNewTab();
+
+ let loadStartedPromise = promiseLoadStarted();
+ triggerCommand(true, {accelKey: true, shiftKey: true});
+ yield loadStartedPromise;
+
+ // Check the load occurred in a new background tab.
+ info("URL should be loaded in a new background tab");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ ok(!gURLBar.focused, "Urlbar is no longer focused after urlbar command");
+ is(gBrowser.selectedTab, tab, "Focus did not change to the new tab");
+
+ // Select the new background tab
+ gBrowser.selectedTab = gBrowser.selectedTab.nextSibling;
+ is(gURLBar.value, TEST_VALUE, "New URL is loaded in new tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* load_in_current_tab_test() {
+ let tests = [
+ {desc: "Simple return keypress"},
+ {desc: "Left click on go button", click: true},
+ {desc: "Ctrl/Cmd+Return keypress", event: {accelKey: true}},
+ {desc: "Alt+Return keypress in a blank tab", event: {altKey: true}}
+ ];
+
+ for (let test of tests) {
+ info(`Running test: ${test.desc}`);
+
+ // Add a new tab.
+ let tab = yield* promiseOpenNewTab();
+
+ // Trigger a load and check it occurs in the current tab.
+ let loadStartedPromise = promiseLoadStarted();
+ triggerCommand(test.click || false, test.event || {});
+ yield loadStartedPromise;
+
+ info("URL should be loaded in the current tab");
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+ yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused");
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ }
+});
+
+add_task(function* load_in_new_tab_test() {
+ let tests = [
+ {desc: "Ctrl/Cmd left click on go button", click: true, event: {accelKey: true}},
+ {desc: "Alt+Return keypress in a dirty tab", event: {altKey: true}, url: START_VALUE}
+ ];
+
+ for (let test of tests) {
+ info(`Running test: ${test.desc}`);
+
+ // Add a new tab.
+ let tab = yield* promiseOpenNewTab(test.url || "about:blank");
+
+ // Trigger a load and check it occurs in the current tab.
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ triggerCommand(test.click || false, test.event || {});
+ yield tabSwitchedPromise;
+
+ // Check the load occurred in a new tab.
+ info("URL should be loaded in a new focused tab");
+ is(gURLBar.inputField.value, TEST_VALUE, "Urlbar still has the value we entered");
+ yield promiseCheckChildNoFocusedElement(gBrowser.selectedBrowser);
+ is(document.activeElement, gBrowser.selectedBrowser, "Content window should be focused");
+ isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
+
+ // Cleanup.
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function triggerCommand(shouldClick, event) {
+ gURLBar.value = TEST_VALUE;
+ gURLBar.focus();
+
+ if (shouldClick) {
+ is(gURLBar.getAttribute("pageproxystate"), "invalid",
+ "page proxy state must be invalid for go button to be visible");
+
+ let goButton = document.getElementById("urlbar-go-button");
+ EventUtils.synthesizeMouseAtCenter(goButton, event);
+ } else {
+ EventUtils.synthesizeKey("VK_RETURN", event);
+ }
+}
+
+function promiseLoadStarted() {
+ return new Promise(resolve => {
+ gBrowser.addTabsProgressListener({
+ onStateChange(browser, webProgress, req, flags, status) {
+ if (flags & Ci.nsIWebProgressListener.STATE_START) {
+ gBrowser.removeTabsProgressListener(this);
+ resolve();
+ }
+ }
+ });
+ });
+}
+
+function* promiseOpenNewTab(url = "about:blank") {
+ let tab = gBrowser.addTab(url);
+ let tabSwitchPromise = promiseNewTabSwitched(tab);
+ gBrowser.selectedTab = tab;
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ yield tabSwitchPromise;
+ return tab;
+}
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
+ gBrowser.removeEventListener("TabSwitchDone", onSwitch);
+ executeSoon(resolve);
+ });
+ });
+}
+
+function promiseCheckChildNoFocusedElement(browser)
+{
+ if (!gMultiProcessBrowser) {
+ Assert.equal(Services.focus.focusedElement, null, "There should be no focused element");
+ return null;
+ }
+
+ return ContentTask.spawn(browser, { }, function* () {
+ const fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ Assert.equal(fm.focusedElement, null, "There should be no focused element");
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js b/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js
new file mode 100644
index 000000000..31fc84768
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const url = "data:text/html,<body>hi";
+
+add_task(function*() {
+ yield* testURL(url, urlEnter);
+ yield* testURL(url, urlClick);
+});
+
+function urlEnter(url) {
+ gURLBar.value = url;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
+
+function urlClick(url) {
+ gURLBar.value = url;
+ gURLBar.focus();
+ let goButton = document.getElementById("urlbar-go-button");
+ EventUtils.synthesizeMouseAtCenter(goButton, {});
+}
+
+function promiseNewTabSwitched() {
+ return new Promise(resolve => {
+ gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
+ gBrowser.removeEventListener("TabSwitchDone", onSwitch);
+ executeSoon(resolve);
+ });
+ });
+}
+
+function* testURL(url, loadFunc, endFunc) {
+ let tabSwitchedPromise = promiseNewTabSwitched();
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ let browser = gBrowser.selectedBrowser;
+
+ let pageshowPromise = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+
+ yield tabSwitchedPromise;
+ yield pageshowPromise;
+
+ let pagePrincipal = gBrowser.contentPrincipal;
+ loadFunc(url);
+
+ yield BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+
+ yield ContentTask.spawn(browser, { isRemote: gMultiProcessBrowser },
+ function* (arg) {
+ const fm = Components.classes["@mozilla.org/focus-manager;1"].
+ getService(Components.interfaces.nsIFocusManager);
+ Assert.equal(fm.focusedElement, null, "focusedElement not null");
+
+ if (arg.isRemote) {
+ Assert.equal(fm.activeWindow, content, "activeWindow not correct");
+ }
+ });
+
+ is(document.activeElement, browser, "content window should be focused");
+
+ ok(!gBrowser.contentPrincipal.equals(pagePrincipal),
+ "load of " + url + " by " + loadFunc.name + " should produce a page with a different principal");
+
+ gBrowser.removeTab(tab);
+}
diff --git a/browser/base/content/test/urlbar/browser_moz_action_link.js b/browser/base/content/test/urlbar/browser_moz_action_link.js
new file mode 100644
index 000000000..ed2d36ee5
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_moz_action_link.js
@@ -0,0 +1,31 @@
+"use strict";
+
+const kURIs = [
+ "moz-action:foo,",
+ "moz-action:foo",
+];
+
+add_task(function*() {
+ for (let uri of kURIs) {
+ let dataURI = `data:text/html,<a id=a href="${uri}" target=_blank>Link</a>`;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataURI);
+
+ let tabSwitchPromise = BrowserTestUtils.switchTab(gBrowser, function() {});
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ content.document.getElementById("a").click();
+ });
+ yield tabSwitchPromise;
+ isnot(gBrowser.selectedTab, tab, "Switched to new tab!");
+ is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank");
+ let newTab = gBrowser.selectedTab;
+ yield BrowserTestUtils.switchTab(gBrowser, tab);
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gBrowser.selectedTab, newTab, "Switched to new tab again!");
+ is(gURLBar.value, "about:blank", "URL bar should be displaying about:blank after tab switch");
+ // Finally, check that directly setting it produces the right results, too:
+ URLBarSetURI(makeURI(uri));
+ is(gURLBar.value, "about:blank", "URL bar should still be displaying about:blank");
+ yield BrowserTestUtils.removeTab(newTab);
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js b/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js
new file mode 100644
index 000000000..e9ba8d989
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js
@@ -0,0 +1,49 @@
+function test() {
+ waitForExplicitFinish();
+ testNext();
+}
+
+var pairs = [
+ ["javascript:", ""],
+ ["javascript:1+1", "1+1"],
+ ["javascript:document.domain", "document.domain"],
+ ["data:text/html,<body>hi</body>", "data:text/html,<body>hi</body>"],
+ // Nested things get confusing because some things don't parse as URIs:
+ ["javascript:javascript:alert('hi!')", "alert('hi!')"],
+ ["data:data:text/html,<body>hi</body>", "data:data:text/html,<body>hi</body>"],
+ ["javascript:data:javascript:alert('hi!')", "data:javascript:alert('hi!')"],
+ ["javascript:data:text/html,javascript:alert('hi!')", "data:text/html,javascript:alert('hi!')"],
+ ["data:data:text/html,javascript:alert('hi!')", "data:data:text/html,javascript:alert('hi!')"],
+];
+
+var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+
+function paste(input, cb) {
+ waitForClipboard(input, function() {
+ clipboardHelper.copyString(input);
+ }, function() {
+ document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ cb();
+ }, function() {
+ ok(false, "Failed to copy string '" + input + "' to clipboard");
+ cb();
+ });
+}
+
+function testNext() {
+ gURLBar.value = '';
+ if (!pairs.length) {
+ finish();
+ return;
+ }
+
+ let [inputValue, expectedURL] = pairs.shift();
+
+ gURLBar.focus();
+ paste(inputValue, function() {
+ is(gURLBar.textValue, expectedURL, "entering '" + inputValue + "' strips relevant bits.");
+
+ setTimeout(testNext, 0);
+ });
+}
+
diff --git a/browser/base/content/test/urlbar/browser_search_favicon.js b/browser/base/content/test/urlbar/browser_search_favicon.js
new file mode 100644
index 000000000..a8e6dbbcd
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_search_favicon.js
@@ -0,0 +1,52 @@
+var gOriginalEngine;
+var gEngine;
+var gRestyleSearchesPref = "browser.urlbar.restyleSearches";
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(gRestyleSearchesPref);
+ Services.search.currentEngine = gOriginalEngine;
+ Services.search.removeEngine(gEngine);
+ return PlacesTestUtils.clearHistory();
+});
+
+add_task(function*() {
+ Services.prefs.setBoolPref(gRestyleSearchesPref, true);
+});
+
+add_task(function*() {
+
+ Services.search.addEngineWithDetails("SearchEngine", "", "", "",
+ "GET", "http://s.example.com/search");
+ gEngine = Services.search.getEngineByName("SearchEngine");
+ gEngine.addParam("q", "{searchTerms}", null);
+ gOriginalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = gEngine;
+
+ let uri = NetUtil.newURI("http://s.example.com/search?q=foo&client=1");
+ yield PlacesTestUtils.addVisits({ uri: uri, title: "Foo - SearchEngine Search" });
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+
+ // The first autocomplete result has the action searchengine, while
+ // the second result is the "search favicon" element.
+ yield promiseAutocompleteResultPopup("foo");
+ let result = gURLBar.popup.richlistbox.children[1];
+
+ isnot(result, null, "Expect a search result");
+ is(result.getAttribute("type"), "searchengine", "Expect correct `type` attribute");
+
+ let titleHbox = result._titleText.parentNode.parentNode;
+ ok(titleHbox.classList.contains("ac-title"), "Title hbox sanity check");
+ is_element_visible(titleHbox, "Title element should be visible");
+
+ let urlHbox = result._urlText.parentNode.parentNode;
+ ok(urlHbox.classList.contains("ac-url"), "URL hbox sanity check");
+ is_element_hidden(urlHbox, "URL element should be hidden");
+
+ let actionHbox = result._actionText.parentNode.parentNode;
+ ok(actionHbox.classList.contains("ac-action"), "Action hbox sanity check");
+ is_element_hidden(actionHbox, "Action element should be hidden because it is not selected");
+ is(result._actionText.textContent, "Search with SearchEngine", "Action text should be as expected");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js
new file mode 100644
index 000000000..d207092d4
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js
@@ -0,0 +1,216 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(2);
+
+const TEST_URL_BASES = [
+ "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#tabmatch",
+ "http://example.org/browser/browser/base/content/test/urlbar/moz.png#tabmatch"
+];
+
+var gController = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+
+var gTabCounter = 0;
+
+add_task(function* step_1() {
+ info("Running step 1");
+ let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+ let promises = [];
+ for (let i = 0; i < maxResults - 1; i++) {
+ let tab = gBrowser.addTab();
+ promises.push(loadTab(tab, TEST_URL_BASES[0] + (++gTabCounter)));
+ }
+
+ yield Promise.all(promises);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_2() {
+ info("Running step 2");
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+
+ let promises = [];
+ for (let i = 1; i < gBrowser.tabs.length; i++)
+ promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[1] + (++gTabCounter)));
+
+ yield Promise.all(promises);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_3() {
+ info("Running step 3");
+ let promises = [];
+ for (let i = 1; i < gBrowser.tabs.length; i++)
+ promises.push(loadTab(gBrowser.tabs[i], TEST_URL_BASES[0] + gTabCounter));
+
+ yield Promise.all(promises);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_4() {
+ info("Running step 4 - ensure we don't register subframes as open pages");
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.loadURI('data:text/html,<body><iframe src=""></iframe></body>');
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
+ let iframe_loaded = ContentTaskUtils.waitForEvent(content.document, "load", true);
+ content.document.querySelector("iframe").src = "http://test2.example.org/";
+ yield iframe_loaded;
+ });
+
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_5() {
+ info("Running step 5 - remove tab immediately");
+ let tab = gBrowser.addTab("about:logo");
+ yield BrowserTestUtils.removeTab(tab);
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_6() {
+ info("Running step 6 - check swapBrowsersAndCloseOther preserves registered switch-to-tab result");
+ let tabToKeep = gBrowser.addTab();
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.loadURI("about:mozilla");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ gBrowser.updateBrowserRemoteness(tabToKeep.linkedBrowser, tab.linkedBrowser.isRemoteBrowser);
+ gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab);
+
+ yield ensure_opentabs_match_db()
+
+ yield BrowserTestUtils.removeTab(tabToKeep);
+
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* step_7() {
+ info("Running step 7 - close all tabs");
+
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+
+ gBrowser.addTab("about:blank", {skipAnimation: true});
+ while (gBrowser.tabs.length > 1) {
+ info("Removing tab: " + gBrowser.tabs[0].linkedBrowser.currentURI.spec);
+ gBrowser.selectTabAtIndex(0);
+ gBrowser.removeCurrentTab();
+ }
+
+ yield ensure_opentabs_match_db();
+});
+
+add_task(function* cleanup() {
+ info("Cleaning up");
+
+ yield PlacesTestUtils.clearHistory();
+});
+
+function loadTab(tab, url) {
+ // Because adding visits is async, we will not be notified immediately.
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let visited = new Promise(resolve => {
+ Services.obs.addObserver(
+ function observer(aSubject, aTopic, aData) {
+ if (url != aSubject.QueryInterface(Ci.nsIURI).spec)
+ return;
+ Services.obs.removeObserver(observer, aTopic);
+ resolve();
+ },
+ "uri-visit-saved",
+ false
+ );
+ });
+
+ info("Loading page: " + url);
+ tab.linkedBrowser.loadURI(url);
+ return Promise.all([ loaded, visited ]);
+}
+
+function ensure_opentabs_match_db() {
+ var tabs = {};
+
+ var winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let browserWin = winEnum.getNext();
+ // skip closed-but-not-destroyed windows
+ if (browserWin.closed)
+ continue;
+
+ for (let i = 0; i < browserWin.gBrowser.tabContainer.childElementCount; i++) {
+ let browser = browserWin.gBrowser.getBrowserAtIndex(i);
+ let url = browser.currentURI.spec;
+ if (browserWin.isBlankPageURL(url))
+ continue;
+ if (!(url in tabs))
+ tabs[url] = 1;
+ else
+ tabs[url]++;
+ }
+ }
+
+ return new Promise(resolve => {
+ checkAutocompleteResults(tabs, resolve);
+ });
+}
+
+function checkAutocompleteResults(aExpected, aCallback)
+{
+ gController.input = {
+ timeout: 10,
+ textValue: "",
+ searches: ["unifiedcomplete"],
+ searchParam: "enable-actions",
+ popupOpen: false,
+ minResultsForPopup: 0,
+ invalidate: function() {},
+ disableAutoComplete: false,
+ completeDefaultIndex: false,
+ get popup() { return this; },
+ onSearchBegin: function() {},
+ onSearchComplete: function ()
+ {
+ info("Found " + gController.matchCount + " matches.");
+ // Check to see the expected uris and titles match up (in any order)
+ for (let i = 0; i < gController.matchCount; i++) {
+ if (gController.getStyleAt(i).includes("heuristic")) {
+ info("Skip heuristic match");
+ continue;
+ }
+ let action = gURLBar.popup.input._parseActionUrl(gController.getValueAt(i));
+ let uri = action.params.url;
+
+ info("Search for '" + uri + "' in open tabs.");
+ let expected = uri in aExpected;
+ ok(expected, uri + " was found in autocomplete, was " + (expected ? "" : "not ") + "expected");
+ // Remove the found entry from expected results.
+ delete aExpected[uri];
+ }
+
+ // Make sure there is no reported open page that is not open.
+ for (let entry in aExpected) {
+ ok(false, "'" + entry + "' should be found in autocomplete");
+ }
+
+ executeSoon(aCallback);
+ },
+ setSelectedIndex: function() {},
+ get searchCount() { return this.searches.length; },
+ getSearchAt: function(aIndex) { return this.searches[aIndex]; },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteInput,
+ Ci.nsIAutoCompletePopup,
+ ])
+ };
+
+ info("Searching open pages.");
+ gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage"));
+}
diff --git a/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js
new file mode 100644
index 000000000..08a18b38a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js
@@ -0,0 +1,84 @@
+let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+add_task(function*() {
+ let normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield runTest(normalWindow, privateWindow, false);
+ yield BrowserTestUtils.closeWindow(normalWindow);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+
+ normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
+ privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield runTest(privateWindow, normalWindow, false);
+ yield BrowserTestUtils.closeWindow(normalWindow);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+
+ privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield runTest(privateWindow, privateWindow, false);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+
+ normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
+ yield runTest(normalWindow, normalWindow, true);
+ yield BrowserTestUtils.closeWindow(normalWindow);
+});
+
+function* runTest(aSourceWindow, aDestWindow, aExpectSwitch, aCallback) {
+ yield BrowserTestUtils.openNewForegroundTab(aSourceWindow.gBrowser, testURL);
+ let testTab = yield BrowserTestUtils.openNewForegroundTab(aDestWindow.gBrowser);
+
+ info("waiting for focus on the window");
+ yield SimpleTest.promiseFocus(aDestWindow);
+ info("got focus on the window");
+
+ // Select the testTab
+ aDestWindow.gBrowser.selectedTab = testTab;
+
+ // Ensure that this tab has no history entries
+ let sessionHistoryCount = yield new Promise(resolve => {
+ SessionStore.getSessionHistory(gBrowser.selectedTab, function(sessionHistory) {
+ resolve(sessionHistory.entries.length);
+ });
+ });
+
+ ok(sessionHistoryCount < 2,
+ `The test tab has 1 or fewer history entries. sessionHistoryCount=${sessionHistoryCount}`);
+ // Ensure that this tab is on about:blank
+ is(testTab.linkedBrowser.currentURI.spec, "about:blank",
+ "The test tab is on about:blank");
+ // Ensure that this tab's document has no child nodes
+ yield ContentTask.spawn(testTab.linkedBrowser, null, function*() {
+ ok(!content.document.body.hasChildNodes(),
+ "The test tab has no child nodes");
+ });
+ ok(!testTab.hasAttribute("busy"),
+ "The test tab doesn't have the busy attribute");
+
+ // Wait for the Awesomebar popup to appear.
+ yield promiseAutocompleteResultPopup(testURL, aDestWindow);
+
+ info(`awesomebar popup appeared. aExpectSwitch: ${aExpectSwitch}`);
+ // Make sure the last match is selected.
+ let {controller, popup} = aDestWindow.gURLBar;
+ while (popup.selectedIndex < controller.matchCount - 1) {
+ info("handling key navigation for DOM_VK_DOWN key");
+ controller.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
+ }
+
+ let awaitTabSwitch;
+ if (aExpectSwitch) {
+ awaitTabSwitch = BrowserTestUtils.removeTab(testTab, {dontRemove: true})
+ }
+
+ // Execute the selected action.
+ controller.handleEnter(true);
+ info("sent Enter command to the controller");
+
+ if (aExpectSwitch) {
+ // If we expect a tab switch then the current tab
+ // will be closed and we switch to the other tab.
+ yield awaitTabSwitch;
+ } else {
+ // If we don't expect a tab switch then wait for the tab to load.
+ yield BrowserTestUtils.browserLoaded(testTab.linkedBrowser);
+ }
+}
diff --git a/browser/base/content/test/urlbar/browser_urlHighlight.js b/browser/base/content/test/urlbar/browser_urlHighlight.js
new file mode 100644
index 000000000..ba1537d91
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlHighlight.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function testVal(aExpected) {
+ gURLBar.value = aExpected.replace(/[<>]/g, "");
+
+ let selectionController = gURLBar.editor.selectionController;
+ let selection = selectionController.getSelection(selectionController.SELECTION_URLSECONDARY);
+ let value = gURLBar.editor.rootElement.textContent;
+ let result = "";
+ for (let i = 0; i < selection.rangeCount; i++) {
+ let range = selection.getRangeAt(i).toString();
+ let pos = value.indexOf(range);
+ result += value.substring(0, pos) + "<" + range + ">";
+ value = value.substring(pos + range.length);
+ }
+ result += value;
+ is(result, aExpected,
+ "Correct part of the urlbar contents is highlighted");
+}
+
+function test() {
+ const prefname = "browser.urlbar.formatting.enabled";
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(prefname);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(prefname, true);
+
+ gURLBar.focus();
+
+ testVal("https://mozilla.org");
+
+ gBrowser.selectedBrowser.focus();
+
+ testVal("<https://>mozilla.org");
+ testVal("<https://>mözilla.org");
+ testVal("<https://>mozilla.imaginatory");
+
+ testVal("<https://www.>mozilla.org");
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<www.>mozilla.org");
+ testVal("<sub.>mozilla.org");
+ testVal("<sub1.sub2.sub3.>mozilla.org");
+ testVal("<mozilla.com.>mozilla.com");
+ testVal("<https://mozilla.com:mozilla.com@>mozilla.com");
+ testVal("<mozilla.com:mozilla.com@>mozilla.com");
+
+ testVal("<ftp.>mozilla.org");
+ testVal("<ftp://ftp.>mozilla.org");
+
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@>mozilla.org");
+ testVal("<user:pass@sub1.sub2.sub3.>mozilla.org");
+ testVal("<user:pass@>mozilla.org");
+
+ testVal("<https://>mozilla.org< >");
+ testVal("mozilla.org< >");
+
+ testVal("<https://>mozilla.org</file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+ testVal("foo.bar<?q=test>");
+ testVal("foo.bar<#mozilla.org>");
+ testVal("foo.bar<?somewhere.mozilla.org>");
+ testVal("foo.bar<?@mozilla.org>");
+ testVal("foo.bar<#x@mozilla.org>");
+ testVal("foo.bar<#@x@mozilla.org>");
+ testVal("foo.bar<?x@mozilla.org>");
+ testVal("foo.bar<?@x@mozilla.org>");
+ testVal("<foo.bar@x@>mozilla.org");
+ testVal("<foo.bar@:baz@>mozilla.org");
+ testVal("<foo.bar:@baz@>mozilla.org");
+ testVal("<foo.bar@:ba:z@>mozilla.org");
+ testVal("<foo.:bar:@baz@>mozilla.org");
+
+ testVal("<https://sub.>mozilla.org<:666/file.ext>");
+ testVal("<sub.>mozilla.org<:666/file.ext>");
+ testVal("localhost<:666/file.ext>");
+
+ let IPs = ["192.168.1.1",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[1:2:3:4:5:6:7::]",
+ "[::1:2:3:4:5:6:7]",
+ "[1:2:a:B:c:D:e:F]",
+ "[1::8]",
+ "[1:2::8]",
+ "[fe80::222:19ff:fe11:8c76]",
+ "[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]",
+ "[::192.168.1.1]",
+ "[1::0.0.0.0]",
+ "[1:2::255.255.255.255]",
+ "[1:2:3::255.255.255.255]",
+ "[1:2:3:4::255.255.255.255]",
+ "[1:2:3:4:5::255.255.255.255]",
+ "[1:2:3:4:5:6:255.255.255.255]"];
+ IPs.forEach(function (IP) {
+ testVal(IP);
+ testVal(IP + "</file.ext>");
+ testVal(IP + "<:666/file.ext>");
+ testVal("<https://>" + IP);
+ testVal("<https://>" + IP + "</file.ext>");
+ testVal("<https://user:pass@>" + IP + "<:666/file.ext>");
+ testVal("<user:pass@>" + IP + "<:666/file.ext>");
+ });
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+ testVal("foo9://mozilla.org/");
+ testVal("foo+://mozilla.org/");
+ testVal("foo.://mozilla.org/");
+ testVal("foo-://mozilla.org/");
+
+ Services.prefs.setBoolPref(prefname, false);
+
+ testVal("https://mozilla.org");
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js
new file mode 100644
index 000000000..792826eb1
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarAboutHomeLoading.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+/**
+ * Test what happens if loading a URL that should clear the
+ * location bar after a parent process URL.
+ */
+add_task(function* clearURLBarAfterParentProcessURL() {
+ let tab = yield new Promise(resolve => {
+ gBrowser.selectedTab = gBrowser.addTab("about:preferences");
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function onInit() {
+ newTabBrowser.removeEventListener("Initialized", onInit, true);
+ resolve(gBrowser.selectedTab);
+ }, true);
+ });
+ document.getElementById("home-button").click();
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Same as above, but open the tab without passing the URL immediately
+ * which changes behaviour in tabbrowser.xml.
+ */
+add_task(function* clearURLBarAfterParentProcessURLInExistingTab() {
+ let tab = yield new Promise(resolve => {
+ gBrowser.selectedTab = gBrowser.addTab();
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function onInit() {
+ newTabBrowser.removeEventListener("Initialized", onInit, true);
+ resolve(gBrowser.selectedTab);
+ }, true);
+ newTabBrowser.loadURI("about:preferences");
+ });
+ document.getElementById("home-button").click();
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "The browser should have no recorded userTypedValue");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Load about:home directly from an about:newtab page. Because it is an
+ * 'initial' page, we need to treat this specially if the user actually
+ * loads a page like this from the URL bar.
+ */
+add_task(function* clearURLBarAfterManuallyLoadingAboutHome() {
+ let promiseTabOpenedAndSwitchedTo = BrowserTestUtils.switchTab(gBrowser, () => {});
+ // This opens about:newtab:
+ BrowserOpenTab();
+ let tab = yield promiseTabOpenedAndSwitchedTo;
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+
+ gURLBar.value = "about:home";
+ gURLBar.select();
+ let aboutHomeLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:home");
+ EventUtils.sendKey("return");
+ yield aboutHomeLoaded;
+
+ is(gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Ensure we don't show 'about:home' in the URL bar temporarily in new tabs
+ * while we're switching remoteness (when the URL we're loading and the
+ * default content principal are different).
+ */
+add_task(function* dontTemporarilyShowAboutHome() {
+ yield SpecialPowers.pushPrefEnv({set: [["browser.startup.page", 1]]});
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ let win = OpenBrowserWindow();
+ yield windowOpenedPromise;
+ let promiseTabSwitch = BrowserTestUtils.switchTab(win.gBrowser, () => {});
+ win.BrowserOpenTab();
+ yield promiseTabSwitch;
+ yield TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+ yield BrowserTestUtils.closeWindow(win);
+ ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
+
+ windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ win = SessionStore.undoCloseWindow(0);
+ yield windowOpenedPromise;
+ let wpl = {
+ onLocationChange(wpl, request, location, flags) {
+ is(win.gURLBar.value, "", "URL bar value should stay empty.");
+ },
+ };
+ win.gBrowser.addProgressListener(wpl);
+ let otherTab = win.gBrowser.selectedTab.previousSibling;
+ let tabLoaded = BrowserTestUtils.browserLoaded(otherTab.linkedBrowser, false, "about:home");
+ yield BrowserTestUtils.switchTab(win.gBrowser, otherTab);
+ yield tabLoaded;
+ win.gBrowser.removeProgressListener(wpl);
+ is(win.gURLBar.value, "", "URL bar value should be empty.");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js b/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js
new file mode 100644
index 000000000..8101c101d
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js
@@ -0,0 +1,49 @@
+// This test ensures that autoFilled values are not trimmed, unless the user
+// selects from the autocomplete popup.
+
+add_task(function* setup() {
+ const PREF_TRIMURL = "browser.urlbar.trimURLs";
+ const PREF_AUTOFILL = "browser.urlbar.autoFill";
+
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(PREF_TRIMURL);
+ Services.prefs.clearUserPref(PREF_AUTOFILL);
+ yield PlacesTestUtils.clearHistory();
+ gURLBar.handleRevert();
+ });
+ Services.prefs.setBoolPref(PREF_TRIMURL, true);
+ Services.prefs.setBoolPref(PREF_AUTOFILL, true);
+
+ // Adding a tab would hit switch-to-tab, so it's safer to just add a visit.
+ yield PlacesTestUtils.addVisits({
+ uri: "http://www.autofilltrimurl.com/whatever",
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ });
+});
+
+function* promiseSearch(searchtext) {
+ gURLBar.focus();
+ gURLBar.inputField.value = searchtext.substr(0, searchtext.length -1);
+ EventUtils.synthesizeKey(searchtext.substr(-1, 1), {});
+ yield promiseSearchComplete();
+}
+
+add_task(function* () {
+ yield promiseSearch("http://");
+ is(gURLBar.inputField.value, "http://", "Autofilled value is as expected");
+});
+
+add_task(function* () {
+ yield promiseSearch("http://au");
+ is(gURLBar.inputField.value, "http://autofilltrimurl.com/", "Autofilled value is as expected");
+});
+
+add_task(function* () {
+ yield promiseSearch("http://www.autofilltrimurl.com");
+ is(gURLBar.inputField.value, "http://www.autofilltrimurl.com/", "Autofilled value is as expected");
+
+ // Now ensure selecting from the popup correctly trims.
+ is(gURLBar.controller.matchCount, 2, "Found the expected number of matches");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(gURLBar.inputField.value, "www.autofilltrimurl.com/whatever", "trim was applied correctly");
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarCopying.js b/browser/base/content/test/urlbar/browser_urlbarCopying.js
new file mode 100644
index 000000000..8d5562b61
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarCopying.js
@@ -0,0 +1,232 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const trimPref = "browser.urlbar.trimURLs";
+const phishyUserPassPref = "network.http.phishy-userpass-length";
+
+function toUnicode(input) {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+
+ return converter.ConvertToUnicode(input);
+}
+
+function test() {
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ Services.prefs.clearUserPref(trimPref);
+ Services.prefs.clearUserPref(phishyUserPassPref);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(trimPref, true);
+ Services.prefs.setIntPref(phishyUserPassPref, 32); // avoid prompting about phishing
+
+ waitForExplicitFinish();
+
+ nextTest();
+}
+
+var tests = [
+ // pageproxystate="invalid"
+ {
+ setURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "example.com"
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e"
+ },
+
+ // pageproxystate="valid" from this point on (due to the load)
+ {
+ loadURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "http://example.com/"
+ },
+ {
+ copyVal: "<example.co>m",
+ copyExpected: "example.co"
+ },
+ {
+ copyVal: "e<x>ample.com",
+ copyExpected: "x"
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e"
+ },
+
+ {
+ loadURL: "http://example.com/foo",
+ expectedURL: "example.com/foo",
+ copyExpected: "http://example.com/foo"
+ },
+ {
+ copyVal: "<example.com>/foo",
+ copyExpected: "http://example.com"
+ },
+ {
+ copyVal: "<example>.com/foo",
+ copyExpected: "example"
+ },
+
+ // Test that userPass is stripped out
+ {
+ loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass",
+ expectedURL: "mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass",
+ copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass"
+ },
+
+ // Test escaping
+ {
+ loadURL: "http://example.com/()%28%29%C3%A9",
+ expectedURL: "example.com/()()\xe9",
+ copyExpected: "http://example.com/()%28%29%C3%A9"
+ },
+ {
+ copyVal: "<example.com/(>)()\xe9",
+ copyExpected: "http://example.com/("
+ },
+ {
+ copyVal: "e<xample.com/(>)()\xe9",
+ copyExpected: "xample.com/("
+ },
+
+ {
+ loadURL: "http://example.com/%C3%A9%C3%A9",
+ expectedURL: "example.com/\xe9\xe9",
+ copyExpected: "http://example.com/%C3%A9%C3%A9"
+ },
+ {
+ copyVal: "e<xample.com/\xe9>\xe9",
+ copyExpected: "xample.com/\xe9"
+ },
+ {
+ copyVal: "<example.com/\xe9>\xe9",
+ copyExpected: "http://example.com/\xe9"
+ },
+
+ {
+ loadURL: "http://example.com/?%C3%B7%C3%B7",
+ expectedURL: "example.com/?\xf7\xf7",
+ copyExpected: "http://example.com/?%C3%B7%C3%B7"
+ },
+ {
+ copyVal: "e<xample.com/?\xf7>\xf7",
+ copyExpected: "xample.com/?\xf7"
+ },
+ {
+ copyVal: "<example.com/?\xf7>\xf7",
+ copyExpected: "http://example.com/?\xf7"
+ },
+ {
+ loadURL: "http://example.com/a%20test",
+ expectedURL: "example.com/a test",
+ copyExpected: "http://example.com/a%20test"
+ },
+ {
+ loadURL: "http://example.com/a%E3%80%80test",
+ expectedURL: toUnicode("example.com/a test"),
+ copyExpected: "http://example.com/a%E3%80%80test"
+ },
+ {
+ loadURL: "http://example.com/a%20%C2%A0test",
+ expectedURL: "example.com/a%20%C2%A0test",
+ copyExpected: "http://example.com/a%20%C2%A0test"
+ },
+ {
+ loadURL: "http://example.com/%20%20%20",
+ expectedURL: "example.com/%20%20%20",
+ copyExpected: "http://example.com/%20%20%20"
+ },
+ {
+ loadURL: "http://example.com/%E3%80%80%E3%80%80",
+ expectedURL: "example.com/%E3%80%80%E3%80%80",
+ copyExpected: "http://example.com/%E3%80%80%E3%80%80"
+ },
+
+ // data: and javsacript: URIs shouldn't be encoded
+ {
+ loadURL: "javascript:('%C3%A9%20%25%50')",
+ expectedURL: "javascript:('%C3%A9 %25P')",
+ copyExpected: "javascript:('%C3%A9 %25P')"
+ },
+ {
+ copyVal: "<javascript:(>'%C3%A9 %25P')",
+ copyExpected: "javascript:("
+ },
+
+ {
+ loadURL: "data:text/html,(%C3%A9%20%25%50)",
+ expectedURL: "data:text/html,(%C3%A9 %25P)",
+ copyExpected: "data:text/html,(%C3%A9 %25P)",
+ },
+ {
+ copyVal: "<data:text/html,(>%C3%A9 %25P)",
+ copyExpected: "data:text/html,("
+ },
+ {
+ copyVal: "<data:text/html,(%C3%A9 %25P>)",
+ copyExpected: "data:text/html,(%C3%A9 %25P",
+ }
+];
+
+function nextTest() {
+ let test = tests.shift();
+ if (tests.length == 0)
+ runTest(test, finish);
+ else
+ runTest(test, nextTest);
+}
+
+function runTest(test, cb) {
+ function doCheck() {
+ if (test.setURL || test.loadURL) {
+ gURLBar.valueIsTyped = !!test.setURL;
+ is(gURLBar.textValue, test.expectedURL, "url bar value set");
+ }
+
+ testCopy(test.copyVal, test.copyExpected, cb);
+ }
+
+ if (test.loadURL) {
+ loadURL(test.loadURL, doCheck);
+ } else {
+ if (test.setURL)
+ gURLBar.value = test.setURL;
+ doCheck();
+ }
+}
+
+function testCopy(copyVal, targetValue, cb) {
+ info("Expecting copy of: " + targetValue);
+ waitForClipboard(targetValue, function () {
+ gURLBar.focus();
+ if (copyVal) {
+ let startBracket = copyVal.indexOf("<");
+ let endBracket = copyVal.indexOf(">");
+ if (startBracket == -1 || endBracket == -1 ||
+ startBracket > endBracket ||
+ copyVal.replace("<", "").replace(">", "") != gURLBar.textValue) {
+ ok(false, "invalid copyVal: " + copyVal);
+ }
+ gURLBar.selectionStart = startBracket;
+ gURLBar.selectionEnd = endBracket - 1;
+ } else {
+ gURLBar.select();
+ }
+
+ goDoCommand("cmd_copy");
+ }, cb, cb);
+}
+
+function loadURL(aURL, aCB) {
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, aURL);
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, aURL).then(aCB);
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarDecode.js b/browser/base/content/test/urlbar/browser_urlbarDecode.js
new file mode 100644
index 000000000..6a2c421ef
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarDecode.js
@@ -0,0 +1,97 @@
+"use strict";
+
+// This test makes sure (1) you can't break the urlbar by typing particular JSON
+// or JS fragments into it, (2) urlbar.textValue shows URLs unescaped, and (3)
+// the urlbar also shows the URLs embedded in action URIs unescaped. See bug
+// 1233672.
+
+add_task(function* injectJSON() {
+ let inputStrs = [
+ 'http://example.com/ ", "url": "bar',
+ 'http://example.com/\\',
+ 'http://example.com/"',
+ 'http://example.com/","url":"evil.com',
+ 'http://mozilla.org/\\u0020',
+ 'http://www.mozilla.org/","url":1e6,"some-key":"foo',
+ 'http://www.mozilla.org/","url":null,"some-key":"foo',
+ 'http://www.mozilla.org/","url":["foo","bar"],"some-key":"foo',
+ ];
+ for (let inputStr of inputStrs) {
+ yield checkInput(inputStr);
+ }
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+add_task(function losslessDecode() {
+ let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let url = "http://" + urlNoScheme;
+ gURLBar.textValue = url;
+ // Since this is directly setting textValue, it is expected to be trimmed.
+ Assert.equal(gURLBar.inputField.value, urlNoScheme,
+ "The string displayed in the textbox should not be escaped");
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+add_task(function* actionURILosslessDecode() {
+ let urlNoScheme = "example.com/\u30a2\u30a4\u30a6\u30a8\u30aa";
+ let url = "http://" + urlNoScheme;
+ yield promiseAutocompleteResultPopup(url);
+
+ // At this point the heuristic result is selected but the urlbar's value is
+ // simply `url`. Key down and back around until the heuristic result is
+ // selected again, and at that point the urlbar's value should be a visiturl
+ // moz-action.
+
+ do {
+ gURLBar.controller.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
+ } while (gURLBar.popup.selectedIndex != 0);
+
+ let [, type, ] = gURLBar.value.match(/^moz-action:([^,]+),(.*)$/);
+ Assert.equal(type, "visiturl",
+ "visiturl action URI should be in the urlbar");
+
+ Assert.equal(gURLBar.inputField.value, urlNoScheme,
+ "The string displayed in the textbox should not be escaped");
+
+ gURLBar.value = "";
+ gURLBar.handleRevert();
+ gURLBar.blur();
+});
+
+function* checkInput(inputStr) {
+ yield promiseAutocompleteResultPopup(inputStr);
+
+ let item = gURLBar.popup.richlistbox.firstChild;
+ Assert.ok(item, "Should have a result");
+
+ // visiturl matches have their param.urls fixed up.
+ let fixupInfo = Services.uriFixup.getFixupURIInfo(inputStr,
+ Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+ );
+ let expectedVisitURL = fixupInfo.fixedURI.spec;
+
+ let type = "visiturl";
+ let params = {
+ url: expectedVisitURL,
+ input: inputStr,
+ };
+ for (let key in params) {
+ params[key] = encodeURIComponent(params[key]);
+ }
+ let expectedURL = "moz-action:" + type + "," + JSON.stringify(params);
+ Assert.equal(item.getAttribute("url"), expectedURL, "url");
+
+ Assert.equal(item.getAttribute("title"), inputStr.replace("\\", "/"), "title");
+ Assert.equal(item.getAttribute("text"), inputStr, "text");
+
+ let itemType = item.getAttribute("type");
+ Assert.equal(itemType, "visiturl");
+
+ Assert.equal(item._titleText.textContent, inputStr.replace("\\", "/"), "Visible title");
+ Assert.equal(item._actionText.textContent, "Visit", "Visible action");
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarDelete.js b/browser/base/content/test/urlbar/browser_urlbarDelete.js
new file mode 100644
index 000000000..d4eb6c856
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarDelete.js
@@ -0,0 +1,39 @@
+add_task(function*() {
+ let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://bug1105244.example.com/",
+ title: "test" });
+
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ });
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, testDelete);
+});
+
+function sendHome() {
+ // unclear why VK_HOME doesn't work on Mac, but it doesn't...
+ if (Services.appinfo.OS == "Darwin") {
+ EventUtils.synthesizeKey("VK_LEFT", { altKey: true });
+ } else {
+ EventUtils.synthesizeKey("VK_HOME", {});
+ }
+}
+
+function sendDelete() {
+ EventUtils.synthesizeKey("VK_DELETE", {});
+}
+
+function* testDelete() {
+ yield promiseAutocompleteResultPopup("bug1105244");
+
+ // move to the start.
+ sendHome();
+ // delete the first few chars - each delete should operate on the input field.
+ sendDelete();
+ Assert.equal(gURLBar.inputField.value, "ug1105244");
+
+ yield promisePopupShown(gURLBar.popup);
+
+ sendDelete();
+ Assert.equal(gURLBar.inputField.value, "g1105244");
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarEnter.js b/browser/base/content/test/urlbar/browser_urlbarEnter.js
new file mode 100644
index 000000000..32cbaf2be
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarEnter.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_VALUE = "example.com/\xF7?\xF7";
+const START_VALUE = "example.com/%C3%B7?%C3%B7";
+
+add_task(function* () {
+ info("Simple return keypress");
+ let tab = gBrowser.selectedTab = gBrowser.addTab(START_VALUE);
+
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ // Check url bar and selected tab.
+ is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress");
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ // Cleanup.
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(function* () {
+ info("Alt+Return keypress");
+ // due to bug 691608, we must wait for the load event, else isTabEmpty() will
+ // return true on e10s for this tab, so it will be reused even with altKey.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE);
+
+ let tabOpenPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
+
+ // wait for the new tab to appear.
+ yield tabOpenPromise;
+
+ // Check url bar and selected tab.
+ is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress");
+ isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
+
+ // Cleanup.
+ yield BrowserTestUtils.removeTab(tab);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js b/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js
new file mode 100644
index 000000000..22e336f91
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js
@@ -0,0 +1,69 @@
+function repeat(limit, func) {
+ for (let i = 0; i < limit; i++) {
+ func(i);
+ }
+}
+
+function* promiseAutoComplete(inputText) {
+ gURLBar.focus();
+ gURLBar.value = inputText.slice(0, -1);
+ EventUtils.synthesizeKey(inputText.slice(-1), {});
+ yield promiseSearchComplete();
+}
+
+function is_selected(index) {
+ is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
+}
+
+let gMaxResults;
+
+add_task(function*() {
+ registerCleanupFunction(function* () {
+ yield PlacesTestUtils.clearHistory();
+ });
+
+ yield PlacesTestUtils.clearHistory();
+
+ gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ let visits = [];
+ repeat(gMaxResults, i => {
+ visits.push({
+ uri: makeURI("http://example.com/autocomplete/?" + i),
+ });
+ });
+ yield PlacesTestUtils.addVisits(visits);
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseAutoComplete("http://example.com/autocomplete/");
+
+ let popup = gURLBar.popup;
+ let results = popup.richlistbox.children;
+ is(results.length, gMaxResults,
+ "Should get gMaxResults=" + gMaxResults + " results");
+
+ let initiallySelected = gURLBar.popup.richlistbox.selectedIndex;
+
+ info("Key Down to select the next item");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is_selected(initiallySelected + 1);
+ let expectedURL = gURLBar.controller.getFinalCompleteValueAt(initiallySelected + 1);
+
+ is(gURLBar.value, gURLBar.controller.getValueAt(initiallySelected + 1),
+ "Value in the URL bar should be updated by keyboard selection");
+
+ // Verify that what we're about to do changes the selectedIndex:
+ isnot(initiallySelected + 1, 3, "Shouldn't be changing the selectedIndex to the same index we keyboard-selected.");
+
+ // Would love to use a synthetic mousemove event here, but that doesn't seem to do anything.
+ // EventUtils.synthesizeMouseAtCenter(results[3], {type: "mousemove"});
+ gURLBar.popup.richlistbox.selectedIndex = 3;
+ is_selected(3);
+
+ let autocompletePopupHidden = promisePopupHidden(gURLBar.popup);
+ let openedExpectedPage = waitForDocLoadAndStopIt(expectedURL);
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield Promise.all([autocompletePopupHidden, openedExpectedPage]);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js
new file mode 100644
index 000000000..8c9e2c9f2
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarFocusedCmdK.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function*() {
+ // Remove the search bar from toolbar
+ CustomizableUI.removeWidgetFromArea("search-container");
+
+ // Test that Ctrl/Cmd + K will focus the url bar
+ let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus");
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ yield focusPromise;
+ Assert.equal(document.activeElement, gURLBar.inputField, "URL Bar should be focused");
+
+ // Reset changes made to toolbar
+ CustomizableUI.reset();
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js b/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js
new file mode 100644
index 000000000..152106dad
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarHashChangeProxyState.js
@@ -0,0 +1,111 @@
+"use strict";
+
+/**
+ * Check that navigating through both the URL bar and using in-page hash- or ref-
+ * based links and back or forward navigation updates the URL bar and identity block correctly.
+ */
+add_task(function* () {
+ let baseURL = "https://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+ let url = baseURL + "#foo";
+ yield BrowserTestUtils.withNewTab({ gBrowser, url }, function*(browser) {
+ let identityBox = document.getElementById("identity-box");
+ let expectedURL = url;
+
+ let verifyURLBarState = testType => {
+ is(gURLBar.textValue, expectedURL, "URL bar visible value should be correct " + testType);
+ is(gURLBar.value, expectedURL, "URL bar value should be correct " + testType);
+ ok(identityBox.classList.contains("verifiedDomain"), "Identity box should know we're doing SSL " + testType);
+ is(gURLBar.getAttribute("pageproxystate"), "valid", "URL bar is in valid page proxy state");
+ };
+
+ verifyURLBarState("at the beginning");
+
+ let locationChangePromise;
+ let resolveLocationChangePromise;
+ let expectURL = url => {
+ expectedURL = url;
+ locationChangePromise = new Promise(r => resolveLocationChangePromise = r);
+ };
+ let wpl = {
+ onLocationChange(wpl, request, location, flags) {
+ is(location.spec, expectedURL, "Got the expected URL");
+ resolveLocationChangePromise();
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+
+ expectURL(baseURL + "#foo");
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ yield locationChangePromise;
+ verifyURLBarState("after hitting enter on the same URL a second time");
+
+ expectURL(baseURL + "#bar");
+ gURLBar.value = expectedURL;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ yield locationChangePromise;
+ verifyURLBarState("after a URL bar hash navigation");
+
+ expectURL(baseURL + "#foo");
+ yield ContentTask.spawn(browser, null, function() {
+ let a = content.document.createElement("a");
+ a.href = "#foo";
+ a.textContent = "Foo Link";
+ content.document.body.appendChild(a);
+ a.click();
+ });
+
+ yield locationChangePromise;
+ verifyURLBarState("after a page link hash navigation");
+
+ expectURL(baseURL + "#bar");
+ gBrowser.goBack();
+
+ yield locationChangePromise;
+ verifyURLBarState("after going back");
+
+ expectURL(baseURL + "#foo");
+ gBrowser.goForward();
+
+ yield locationChangePromise;
+ verifyURLBarState("after going forward");
+
+ expectURL(baseURL + "#foo");
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ yield locationChangePromise;
+ verifyURLBarState("after hitting enter on the same URL");
+
+ gBrowser.removeProgressListener(wpl);
+ });
+});
+
+/**
+ * Check that initial secure loads that swap remoteness
+ * get the correct page icon when finished.
+ */
+add_task(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+ // NB: CPOW usage because new tab pages can be preloaded, in which case no
+ // load events fire.
+ yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
+ let url = "https://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#foo";
+ gURLBar.value = url;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ is(gURLBar.textValue, url, "URL bar visible value should be correct when the page loads from about:newtab");
+ is(gURLBar.value, url, "URL bar value should be correct when the page loads from about:newtab");
+ let identityBox = document.getElementById("identity-box");
+ ok(identityBox.classList.contains("verifiedDomain"),
+ "Identity box should know we're doing SSL when the page loads from about:newtab");
+ is(gURLBar.getAttribute("pageproxystate"), "valid",
+ "URL bar is in valid page proxy state when SSL page with hash loads from about:newtab");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js b/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js
new file mode 100644
index 000000000..9c8996059
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarKeepStateAcrossTabSwitches.js
@@ -0,0 +1,49 @@
+"use strict";
+
+/**
+ * Verify user typed text remains in the URL bar when tab switching, even when
+ * loads fail.
+ */
+add_task(function* () {
+ let input = "i-definitely-dont-exist.example.com";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+ // NB: CPOW usage because new tab pages can be preloaded, in which case no
+ // load events fire.
+ yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ gURLBar.value = input;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ yield errorPageLoaded;
+ is(gURLBar.textValue, input, "Text is still in URL bar");
+ yield BrowserTestUtils.switchTab(gBrowser, tab.previousSibling);
+ yield BrowserTestUtils.switchTab(gBrowser, tab);
+ is(gURLBar.textValue, input, "Text is still in URL bar after tab switch");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Invalid URIs fail differently (that is, immediately, in the loadURI call)
+ * if keyword searches are turned off. Test that this works, too.
+ */
+add_task(function* () {
+ let input = "To be or not to be-that is the question";
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({set: [["keyword.enabled", false]]}, resolve));
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+ // NB: CPOW usage because new tab pages can be preloaded, in which case no
+ // load events fire.
+ yield BrowserTestUtils.waitForCondition(() => !tab.linkedBrowser.contentDocument.hidden)
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ gURLBar.value = input;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ yield errorPageLoaded;
+ is(gURLBar.textValue, input, "Text is still in URL bar");
+ is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser");
+ yield BrowserTestUtils.switchTab(gBrowser, tab.previousSibling);
+ yield BrowserTestUtils.switchTab(gBrowser, tab);
+ is(gURLBar.textValue, input, "Text is still in URL bar after tab switch");
+ is(tab.linkedBrowser.userTypedValue, input, "Text still stored on browser");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbarOneOffs.js b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
new file mode 100644
index 000000000..1f58b8edd
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarOneOffs.js
@@ -0,0 +1,232 @@
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+let gMaxResults;
+
+add_task(function* init() {
+ Services.prefs.setBoolPref("browser.urlbar.oneOffSearches", true);
+ gMaxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+
+ // Add a search suggestion engine and move it to the front so that it appears
+ // as the first one-off.
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ Services.search.moveEngine(engine, 0);
+
+ registerCleanupFunction(function* () {
+ yield hidePopup();
+ yield PlacesTestUtils.clearHistory();
+ });
+
+ yield PlacesTestUtils.clearHistory();
+
+ let visits = [];
+ for (let i = 0; i < gMaxResults; i++) {
+ visits.push({
+ uri: makeURI("http://example.com/browser_urlbarOneOffs.js/?" + i),
+ // TYPED so that the visit shows up when the urlbar's drop-down arrow is
+ // pressed.
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ });
+ }
+ yield PlacesTestUtils.addVisits(visits);
+});
+
+// Keys up and down through the history panel, i.e., the panel that's shown when
+// there's no text in the textbox.
+add_task(function* history() {
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ yield promisePopupShown(gURLBar.popup);
+
+ assertState(-1, -1, "");
+
+ // Key down through each result.
+ for (let i = 0; i < gMaxResults; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(i, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key down through each one-off.
+ let numButtons =
+ gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
+ for (let i = 0; i < numButtons; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(-1, i, "");
+ }
+
+ // Key down once more. Nothing should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(-1, -1, "");
+
+ // Once more. The first result should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(0, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - 1));
+
+ // Now key up. Nothing should be selected again.
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, -1, "");
+
+ // Key up through each one-off.
+ for (let i = numButtons - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, i, "");
+ }
+
+ // Key up through each result.
+ for (let i = gMaxResults - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(i, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key up once more. Nothing should be selected.
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, -1, "");
+
+ yield hidePopup();
+});
+
+// Keys up and down through the non-history panel, i.e., the panel that's shown
+// when you type something in the textbox.
+add_task(function* typedValue() {
+ // Use a typed value that returns the visits added above but that doesn't
+ // trigger autofill since that would complicate the test.
+ let typedValue = "browser_urlbarOneOffs";
+ yield promiseAutocompleteResultPopup(typedValue, window, true);
+
+ assertState(0, -1, typedValue);
+
+ // Key down through each result. The first result is already selected, which
+ // is why gMaxResults - 1 is the correct number of times to do this.
+ for (let i = 0; i < gMaxResults - 1; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ // i starts at zero so that the textValue passed to assertState is correct.
+ // But that means that i + 1 is the expected selected index, since initially
+ // (when this loop starts) the first result is selected.
+ assertState(i + 1, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key down through each one-off.
+ let numButtons =
+ gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true).length;
+ for (let i = 0; i < numButtons; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(-1, i, typedValue);
+ }
+
+ // Key down once more. The selection should wrap around to the first result.
+ EventUtils.synthesizeKey("VK_DOWN", {})
+ assertState(0, -1, typedValue);
+
+ // Now key up. The selection should wrap back around to the one-offs. Key
+ // up through all the one-offs.
+ for (let i = numButtons - 1; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(-1, i, typedValue);
+ }
+
+ // Key up through each non-heuristic result.
+ for (let i = gMaxResults - 2; i >= 0; i--) {
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(i + 1, -1,
+ "example.com/browser_urlbarOneOffs.js/?" + (gMaxResults - i - 1));
+ }
+
+ // Key up once more. The heuristic result should be selected.
+ EventUtils.synthesizeKey("VK_UP", {})
+ assertState(0, -1, typedValue);
+
+ yield hidePopup();
+});
+
+// Checks that "Search with Current Search Engine" items are updated to "Search
+// with One-Off Engine" when a one-off is selected.
+add_task(function* searchWith() {
+ let typedValue = "foo";
+ yield promiseAutocompleteResultPopup(typedValue);
+
+ assertState(0, -1, typedValue);
+
+ let item = gURLBar.popup.richlistbox.firstChild;
+ Assert.equal(item._actionText.textContent,
+ "Search with " + Services.search.currentEngine.name,
+ "Sanity check: first result's action text");
+
+ // Alt+Down to the first one-off. Now the first result and the first one-off
+ // should both be selected.
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
+ assertState(0, 0, typedValue);
+
+ let engineName = gURLBar.popup.oneOffSearchButtons.selectedButton.engine.name;
+ Assert.notEqual(engineName, Services.search.currentEngine.name,
+ "Sanity check: First one-off engine should not be " +
+ "the current engine");
+ Assert.equal(item._actionText.textContent,
+ "Search with " + engineName,
+ "First result's action text should be updated");
+
+ yield hidePopup();
+});
+
+// Clicks a one-off.
+add_task(function* oneOffClick() {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+ yield promiseAutocompleteResultPopup(typedValue);
+
+ assertState(0, -1, typedValue);
+
+ let oneOffs = gURLBar.popup.oneOffSearchButtons.getSelectableButtons(true);
+ let resultsPromise =
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+ "http://mochi.test:8888/");
+ EventUtils.synthesizeMouseAtCenter(oneOffs[0], {});
+ yield resultsPromise;
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
+
+// Presses the Return key when a one-off is selected.
+add_task(function* oneOffReturn() {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ // We are explicitly using something that looks like a url, to make the test
+ // stricter. Even if it looks like a url, we should search.
+ let typedValue = "foo.bar";
+ yield promiseAutocompleteResultPopup(typedValue, window, true);
+
+ assertState(0, -1, typedValue);
+
+ // Alt+Down to select the first one-off.
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true })
+ assertState(0, 0, typedValue);
+
+ let resultsPromise =
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false,
+ "http://mochi.test:8888/");
+ EventUtils.synthesizeKey("VK_RETURN", {})
+ yield resultsPromise;
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
+
+
+function assertState(result, oneOff, textValue = undefined) {
+ Assert.equal(gURLBar.popup.selectedIndex, result,
+ "Expected result should be selected");
+ Assert.equal(gURLBar.popup.oneOffSearchButtons.selectedButtonIndex, oneOff,
+ "Expected one-off should be selected");
+ if (textValue !== undefined) {
+ Assert.equal(gURLBar.textValue, textValue, "Expected textValue");
+ }
+}
+
+function* hidePopup() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promisePopupHidden(gURLBar.popup);
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js b/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js
new file mode 100644
index 000000000..5db0f0ea6
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarPrivateBrowsingWindowChange.js
@@ -0,0 +1,41 @@
+"use strict";
+
+/**
+ * Test that when opening a private browsing window and typing in it before about:privatebrowsing
+ * loads, we don't clear the URL bar.
+ */
+add_task(function*() {
+ let urlbarTestValue = "Mary had a little lamb";
+ let win = OpenBrowserWindow({private: true});
+ yield BrowserTestUtils.waitForEvent(win, "load");
+ let urlbar = win.document.getElementById("urlbar");
+ urlbar.value = urlbarTestValue;
+ // Need this so the autocomplete controller attaches:
+ let focusEv = new FocusEvent("focus", {});
+ urlbar.dispatchEvent(focusEv);
+ // And so we know input happened:
+ let inputEv = new InputEvent("input", {data: "", view: win, bubbles: true});
+ urlbar.onInput(inputEv);
+ // Check it worked:
+ is(urlbar.value, urlbarTestValue, "URL bar value should be there");
+ is(win.gBrowser.selectedBrowser.userTypedValue, urlbarTestValue, "browser object should know the url bar value");
+
+ let continueTest;
+ let continuePromise = new Promise(resolve => continueTest = resolve);
+ let wpl = {
+ onLocationChange(aWebProgress, aRequest, aLocation) {
+ if (aLocation && aLocation.spec == "about:privatebrowsing") {
+ continueTest();
+ }
+ },
+ };
+ win.gBrowser.addProgressListener(wpl);
+
+ yield continuePromise;
+ is(urlbar.value, urlbarTestValue,
+ "URL bar value should be the same once about:privatebrowsing has loaded");
+ is(win.gBrowser.selectedBrowser.userTypedValue, urlbarTestValue,
+ "browser object should still know url bar value once about:privatebrowsing has loaded");
+ win.gBrowser.removeProgressListener(wpl);
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js b/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js
new file mode 100644
index 000000000..d66514c5a
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarRaceWithTabs.js
@@ -0,0 +1,57 @@
+const kURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
+
+function* addBookmark(bookmark) {
+ if (bookmark.keyword) {
+ yield PlacesUtils.keywords.insert({
+ keyword: bookmark.keyword,
+ url: bookmark.url,
+ });
+ }
+
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: bookmark.url,
+ title: bookmark.title,
+ });
+
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ if (bookmark.keyword) {
+ yield PlacesUtils.keywords.remove(bookmark.keyword);
+ }
+ });
+}
+
+/**
+ * Check that if the user hits enter and ctrl-t at the same time, we open the URL in the right tab.
+ */
+add_task(function* hitEnterLoadInRightTab() {
+ info("Opening new tab");
+ let oldTabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ BrowserOpenTab();
+ let oldTab = (yield oldTabCreatedPromise).target;
+ let oldTabLoadedPromise = BrowserTestUtils.browserLoaded(oldTab.linkedBrowser, false, kURL);
+ oldTabLoadedPromise.then(() => info("Old tab loaded"));
+ let newTabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+
+ info("Creating bookmark and keyword");
+ yield addBookmark({title: "Test for keyword bookmark and URL", url: kURL, keyword: "urlbarkeyword"});
+ info("Filling URL bar, sending <return> and opening a tab");
+ gURLBar.value = "urlbarkeyword";
+ gURLBar.select();
+ EventUtils.sendKey("return");
+ BrowserOpenTab();
+ info("Waiting for new tab");
+ let newTab = (yield newTabCreatedPromise).target;
+ info("Created new tab; waiting for either tab to load");
+ let newTabLoadedPromise = BrowserTestUtils.browserLoaded(newTab.linkedBrowser, false, kURL);
+ newTabLoadedPromise.then(() => info("New tab loaded"));
+ yield Promise.race([newTabLoadedPromise, oldTabLoadedPromise]);
+ is(newTab.linkedBrowser.currentURI.spec, "about:newtab", "New tab still has about:newtab");
+ is(oldTab.linkedBrowser.currentURI.spec, kURL, "Old tab loaded URL");
+ info("Closing new tab");
+ yield BrowserTestUtils.removeTab(newTab);
+ info("Closing old tab");
+ yield BrowserTestUtils.removeTab(oldTab);
+ info("Finished");
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarRevert.js b/browser/base/content/test/urlbar/browser_urlbarRevert.js
new file mode 100644
index 000000000..0ce3c8fac
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarRevert.js
@@ -0,0 +1,37 @@
+var tab = null;
+
+function test() {
+ waitForExplicitFinish();
+
+ let pageLoaded = {
+ onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ gBrowser.removeProgressListener(this);
+ executeSoon(checkURLBarRevert);
+ }
+ }
+ }
+
+ gBrowser.addProgressListener(pageLoaded);
+ tab = gBrowser.addTab("http://example.com");
+ gBrowser.selectedTab = tab;
+}
+
+function checkURLBarRevert() {
+ let originalValue = gURLBar.value;
+
+ gBrowser.userTypedValue = "foobar";
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ gBrowser.selectedTab = tab;
+ is(gURLBar.value, "foobar", "location bar displays typed value");
+
+ gURLBar.focus();
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+
+ is(gURLBar.value, originalValue, "ESC reverted the location bar value");
+
+ gBrowser.removeTab(tab);
+ finish();
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js
new file mode 100644
index 000000000..ee0342055
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js
@@ -0,0 +1,198 @@
+"use strict";
+
+var notificationObserver;
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.fixup.domainwhitelist.localhost");
+ if (notificationObserver) {
+ notificationObserver.disconnect();
+ }
+});
+
+function promiseNotification(aBrowser, value, expected, input) {
+ let deferred = Promise.defer();
+ let notificationBox = aBrowser.getNotificationBox(aBrowser.selectedBrowser);
+ if (expected) {
+ info("Waiting for " + value + " notification");
+ let checkForNotification = function() {
+ if (notificationBox.getNotificationWithValue(value)) {
+ info("Saw the notification");
+ notificationObserver.disconnect();
+ notificationObserver = null;
+ deferred.resolve();
+ }
+ }
+ if (notificationObserver) {
+ notificationObserver.disconnect();
+ }
+ notificationObserver = new MutationObserver(checkForNotification);
+ notificationObserver.observe(notificationBox, {childList: true});
+ } else {
+ setTimeout(() => {
+ is(notificationBox.getNotificationWithValue(value), null,
+ `We are expecting to not get a notification for ${input}`);
+ deferred.resolve();
+ }, 1000);
+ }
+ return deferred.promise;
+}
+
+function* runURLBarSearchTest({valueToOpen, expectSearch, expectNotification, aWindow=window}) {
+ aWindow.gURLBar.value = valueToOpen;
+ let expectedURI;
+ if (!expectSearch) {
+ expectedURI = "http://" + valueToOpen + "/";
+ } else {
+ yield new Promise(resolve => {
+ Services.search.init(resolve);
+ });
+ expectedURI = Services.search.defaultEngine.getSubmission(valueToOpen, null, "keyword").uri.spec;
+ }
+ aWindow.gURLBar.focus();
+ let docLoadPromise = waitForDocLoadAndStopIt(expectedURI, aWindow.gBrowser.selectedBrowser);
+ EventUtils.synthesizeKey("VK_RETURN", {}, aWindow);
+
+ yield Promise.all([
+ docLoadPromise,
+ promiseNotification(aWindow.gBrowser, "keyword-uri-fixup", expectNotification, valueToOpen)
+ ]);
+}
+
+add_task(function* test_navigate_full_domain() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "www.mozilla.org",
+ expectSearch: false,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_decimal_ip() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "1234",
+ expectSearch: true,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_decimal_ip_with_path() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "1234/12",
+ expectSearch: true,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_large_number() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "123456789012345",
+ expectSearch: true,
+ expectNotification: false
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_small_hex_number() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "0x1f00ffff",
+ expectSearch: true,
+ expectNotification: false
+ });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_navigate_large_hex_number() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "0x7f0000017f000001",
+ expectSearch: true,
+ expectNotification: false
+ });
+ gBrowser.removeTab(tab);
+});
+
+function get_test_function_for_localhost_with_hostname(hostName, isPrivate) {
+ return function* test_navigate_single_host() {
+ const pref = "browser.fixup.domainwhitelist.localhost";
+ let win;
+ if (isPrivate) {
+ let promiseWin = BrowserTestUtils.waitForNewWindow();
+ win = OpenBrowserWindow({private: true});
+ yield promiseWin;
+ let deferredOpenFocus = Promise.defer();
+ waitForFocus(deferredOpenFocus.resolve, win);
+ yield deferredOpenFocus.promise;
+ } else {
+ win = window;
+ }
+ let browser = win.gBrowser;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(browser);
+
+ Services.prefs.setBoolPref(pref, false);
+ yield* runURLBarSearchTest({
+ valueToOpen: hostName,
+ expectSearch: true,
+ expectNotification: true,
+ aWindow: win,
+ });
+
+ let notificationBox = browser.getNotificationBox(tab.linkedBrowser);
+ let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
+ let docLoadPromise = waitForDocLoadAndStopIt("http://" + hostName + "/", tab.linkedBrowser);
+ notification.querySelector(".notification-button-default").click();
+
+ // check pref value
+ let prefValue = Services.prefs.getBoolPref(pref);
+ is(prefValue, !isPrivate, "Pref should have the correct state.");
+
+ yield docLoadPromise;
+ browser.removeTab(tab);
+
+ // Now try again with the pref set.
+ tab = browser.selectedTab = browser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ // In a private window, the notification should appear again.
+ yield* runURLBarSearchTest({
+ valueToOpen: hostName,
+ expectSearch: isPrivate,
+ expectNotification: isPrivate,
+ aWindow: win,
+ });
+ browser.removeTab(tab);
+ if (isPrivate) {
+ info("Waiting for private window to close");
+ yield BrowserTestUtils.closeWindow(win);
+ let deferredFocus = Promise.defer();
+ info("Waiting for focus");
+ waitForFocus(deferredFocus.resolve, window);
+ yield deferredFocus.promise;
+ }
+ }
+}
+
+add_task(get_test_function_for_localhost_with_hostname("localhost"));
+add_task(get_test_function_for_localhost_with_hostname("localhost."));
+add_task(get_test_function_for_localhost_with_hostname("localhost", true));
+
+add_task(function* test_navigate_invalid_url() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield* runURLBarSearchTest({
+ valueToOpen: "mozilla is awesome",
+ expectSearch: true,
+ expectNotification: false,
+ });
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
new file mode 100644
index 000000000..5146ba98c
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
@@ -0,0 +1,66 @@
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ Services.search.currentEngine = oldCurrentEngine;
+
+ // Clicking suggestions causes visits to search results pages, so clear that
+ // history now.
+ yield PlacesTestUtils.clearHistory();
+
+ // Make sure the popup is closed for the next test.
+ gURLBar.blur();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ });
+});
+
+add_task(function* clickSuggestion() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ let [idx, suggestion, engineName] = yield promiseFirstSuggestion();
+ Assert.equal(engineName,
+ "browser_searchSuggestionEngine%20searchSuggestionEngine.xml",
+ "Expected suggestion engine");
+ let item = gURLBar.popup.richlistbox.getItemAtIndex(idx);
+
+ let uri = Services.search.currentEngine.getSubmission(suggestion).uri;
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser,
+ false, uri.spec);
+ item.click();
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+function getFirstSuggestion() {
+ let controller = gURLBar.popup.input.controller;
+ let matchCount = controller.matchCount;
+ for (let i = 0; i < matchCount; i++) {
+ let url = controller.getValueAt(i);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (mozActionMatch) {
+ let [, type, paramStr] = mozActionMatch;
+ let params = JSON.parse(paramStr);
+ if (type == "searchengine" && "searchSuggestion" in params) {
+ return [i, params.searchSuggestion, params.engineName];
+ }
+ }
+ }
+ return [-1, null, null];
+}
+
+function* promiseFirstSuggestion() {
+ let tuple = [-1, null, null];
+ yield BrowserTestUtils.waitForCondition(() => {
+ tuple = getFirstSuggestion();
+ return tuple[0] >= 0;
+ });
+ return tuple;
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js
new file mode 100644
index 000000000..94ae8a3ff
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js
@@ -0,0 +1,254 @@
+const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ registerCleanupFunction(function* () {
+ Services.search.currentEngine = oldCurrentEngine;
+ Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+
+ // Disable the notification for future tests so it doesn't interfere with
+ // them. clearUserPref() won't work because by default the pref is false.
+ yield setUserMadeChoicePref(true);
+
+ // Make sure the popup is closed for the next test.
+ gURLBar.blur();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ });
+});
+
+add_task(function* focus() {
+ // Focusing the urlbar used to open the popup in order to show the
+ // notification, but it doesn't anymore. Make sure it does not.
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+});
+
+add_task(function* dismissWithoutResults() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ let popupPromise = promisePopupShown(gURLBar.popup);
+ gURLBar.openPopup();
+ yield popupPromise;
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(true);
+ Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
+ let disableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+ );
+ let transitionPromise = promiseTransition();
+ disableButton.click();
+ yield transitionPromise;
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(false);
+});
+
+add_task(function* dismissWithResults() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(true);
+ Assert.ok(gURLBar.popup._matchCount > 0, "popup should have results");
+ let disableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+ );
+ let transitionPromise = promiseTransition();
+ disableButton.click();
+ yield transitionPromise;
+ Assert.ok(gURLBar.popup.popupOpen, "popup should remain open");
+ gURLBar.blur();
+ gURLBar.focus();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(false);
+});
+
+add_task(function* disable() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
+ assertVisible(true);
+ let disableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-disable"
+ );
+ let transitionPromise = promiseTransition();
+ disableButton.click();
+ yield transitionPromise;
+ gURLBar.blur();
+ yield promiseAutocompleteResultPopup("foo");
+ Assert.ok(!suggestionsPresent());
+});
+
+add_task(function* enable() {
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+ yield setUserMadeChoicePref(false);
+ gURLBar.blur();
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ assertVisible(true);
+ Assert.ok(!suggestionsPresent());
+ let enableButton = document.getAnonymousElementByAttribute(
+ gURLBar.popup, "anonid", "search-suggestions-notification-enable"
+ );
+ let searchPromise = BrowserTestUtils.waitForCondition(suggestionsPresent,
+ "waiting for suggestions");
+ enableButton.click();
+ yield searchPromise;
+ // Clicking Yes should trigger a new search so that suggestions appear
+ // immediately.
+ Assert.ok(suggestionsPresent());
+ gURLBar.blur();
+ gURLBar.focus();
+ // Suggestions should still be present in a new search of course.
+ yield promiseAutocompleteResultPopup("bar");
+ Assert.ok(suggestionsPresent());
+});
+
+add_task(function* privateWindow() {
+ // Since suggestions are disabled in private windows, the notification should
+ // not appear even when suggestions are otherwise enabled.
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ win.gURLBar.blur();
+ win.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo", win);
+ assertVisible(false, win);
+ win.gURLBar.blur();
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+add_task(function* multipleWindows() {
+ // Opening multiple windows, using their urlbars, and then dismissing the
+ // notification in one should dismiss the notification in all.
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+ yield setUserMadeChoicePref(false);
+
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win1");
+ assertVisible(true);
+
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+ win2.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win2", win2);
+ assertVisible(true, win2);
+
+ let win3 = yield BrowserTestUtils.openNewBrowserWindow();
+ win3.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win3", win3);
+ assertVisible(true, win3);
+
+ let enableButton = win3.document.getAnonymousElementByAttribute(
+ win3.gURLBar.popup, "anonid", "search-suggestions-notification-enable"
+ );
+ let transitionPromise = promiseTransition(win3);
+ enableButton.click();
+ yield transitionPromise;
+ assertVisible(false, win3);
+
+ win2.gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win2done", win2);
+ assertVisible(false, win2);
+
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("win1done");
+ assertVisible(false);
+
+ yield BrowserTestUtils.closeWindow(win2);
+ yield BrowserTestUtils.closeWindow(win3);
+});
+
+add_task(function* enableOutsideNotification() {
+ // Setting the suggest.searches pref outside the notification (e.g., by
+ // ticking the checkbox in the preferences window) should hide it.
+ Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+ yield setUserMadeChoicePref(false);
+
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("foo");
+ assertVisible(false);
+});
+
+/**
+ * Setting the choice pref triggers a pref observer in the urlbar, which hides
+ * the notification if it's present. This function returns a promise that's
+ * resolved once the observer fires.
+ *
+ * @param userMadeChoice A boolean, the pref's new value.
+ * @return A Promise that's resolved when the observer fires -- or, if the pref
+ * is currently the given value, that's resolved immediately.
+ */
+function setUserMadeChoicePref(userMadeChoice) {
+ return new Promise(resolve => {
+ let currentUserMadeChoice = Services.prefs.getBoolPref(CHOICE_PREF);
+ if (currentUserMadeChoice != userMadeChoice) {
+ Services.prefs.addObserver(CHOICE_PREF, function obs(subj, topic, data) {
+ Services.prefs.removeObserver(CHOICE_PREF, obs);
+ resolve();
+ }, false);
+ }
+ Services.prefs.setBoolPref(CHOICE_PREF, userMadeChoice);
+ if (currentUserMadeChoice == userMadeChoice) {
+ resolve();
+ }
+ });
+}
+
+function suggestionsPresent() {
+ let controller = gURLBar.popup.input.controller;
+ let matchCount = controller.matchCount;
+ for (let i = 0; i < matchCount; i++) {
+ let url = controller.getValueAt(i);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (mozActionMatch) {
+ let [, type, paramStr] = mozActionMatch;
+ let params = JSON.parse(paramStr);
+ if (type == "searchengine" && "searchSuggestion" in params) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function assertVisible(visible, win=window) {
+ let style =
+ win.getComputedStyle(win.gURLBar.popup.searchSuggestionsNotification);
+ Assert.equal(style.visibility, visible ? "visible" : "collapse");
+}
+
+function promiseTransition(win=window) {
+ return new Promise(resolve => {
+ win.gURLBar.popup.addEventListener("transitionend", function onEnd() {
+ win.gURLBar.popup.removeEventListener("transitionend", onEnd, true);
+ // The urlbar needs to handle the transitionend first, but that happens
+ // naturally since promises are resolved at the end of the current tick.
+ resolve();
+ }, true);
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
new file mode 100644
index 000000000..8c28401ea
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
@@ -0,0 +1,216 @@
+"use strict";
+
+Cu.import("resource:///modules/BrowserUITelemetry.jsm");
+
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ Services.search.currentEngine = oldCurrentEngine;
+
+ // Clicking urlbar results causes visits to their associated pages, so clear
+ // that history now.
+ yield PlacesTestUtils.clearHistory();
+
+ // Make sure the popup is closed for the next test.
+ gURLBar.blur();
+ Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+ });
+
+ // Move the mouse away from the urlbar one-offs so that a one-off engine is
+ // not inadvertently selected.
+ yield new Promise(resolve => {
+ EventUtils.synthesizeNativeMouseMove(window.document.documentElement, 0, 0,
+ resolve);
+ });
+});
+
+add_task(function* heuristicResultMouse() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("heuristicResult");
+ let action = getActionAtIndex(0);
+ Assert.ok(!!action, "there should be an action at index 0");
+ Assert.equal(action.type, "searchengine", "type should be searchengine");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.popup.richlistbox.getItemAtIndex(0).click();
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* heuristicResultKeyboard() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("heuristicResult");
+ let action = getActionAtIndex(0);
+ Assert.ok(!!action, "there should be an action at index 0");
+ Assert.equal(action.type, "searchengine", "type should be searchengine");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ EventUtils.sendKey("return");
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* searchSuggestionMouse() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("searchSuggestion");
+ let idx = getFirstSuggestionIndex();
+ Assert.ok(idx >= 0, "there should be a first suggestion");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.popup.richlistbox.getItemAtIndex(idx).click();
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(function* searchSuggestionKeyboard() {
+ yield compareCounts(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gURLBar.focus();
+ yield promiseAutocompleteResultPopup("searchSuggestion");
+ let idx = getFirstSuggestionIndex();
+ Assert.ok(idx >= 0, "there should be a first suggestion");
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ while (idx--) {
+ EventUtils.sendKey("down");
+ }
+ EventUtils.sendKey("return");
+ yield loadPromise;
+ yield BrowserTestUtils.removeTab(tab);
+ });
+});
+
+/**
+ * This does three things: gets current telemetry/FHR counts, calls
+ * clickCallback, gets telemetry/FHR counts again to compare them to the old
+ * counts.
+ *
+ * @param clickCallback Use this to open the urlbar popup and choose and click a
+ * result.
+ */
+function* compareCounts(clickCallback) {
+ // Search events triggered by clicks (not the Return key in the urlbar) are
+ // recorded in three places:
+ // * BrowserUITelemetry
+ // * Telemetry histogram named "SEARCH_COUNTS"
+ // * FHR
+
+ let engine = Services.search.currentEngine;
+ let engineID = "org.mozilla.testsearchsuggestions";
+
+ // First, get the current counts.
+
+ // BrowserUITelemetry
+ let uiTelemCount = 0;
+ let bucket = BrowserUITelemetry.currentBucket;
+ let events = BrowserUITelemetry.getToolbarMeasures().countableEvents;
+ if (events[bucket] &&
+ events[bucket].search &&
+ events[bucket].search.urlbar) {
+ uiTelemCount = events[bucket].search.urlbar;
+ }
+
+ // telemetry histogram SEARCH_COUNTS
+ let histogramCount = 0;
+ let histogramKey = engineID + ".urlbar";
+ let histogram;
+ try {
+ histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+ } catch (ex) {
+ // No searches performed yet, not a problem.
+ }
+ if (histogram) {
+ let snapshot = histogram.snapshot();
+ if (histogramKey in snapshot) {
+ histogramCount = snapshot[histogramKey].sum;
+ }
+ }
+
+ // FHR -- first make sure the engine has an identifier so that FHR is happy.
+ Object.defineProperty(engine.wrappedJSObject, "identifier",
+ { value: engineID });
+
+ gURLBar.focus();
+ yield clickCallback();
+
+ // Now get the new counts and compare them to the old.
+
+ // BrowserUITelemetry
+ events = BrowserUITelemetry.getToolbarMeasures().countableEvents;
+ Assert.ok(bucket in events, "bucket should be recorded");
+ events = events[bucket];
+ Assert.ok("search" in events, "search should be recorded");
+ events = events.search;
+ Assert.ok("urlbar" in events, "urlbar should be recorded");
+ Assert.equal(events.urlbar, uiTelemCount + 1,
+ "clicked suggestion should be recorded");
+
+ // telemetry histogram SEARCH_COUNTS
+ histogram = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+ let snapshot = histogram.snapshot();
+ Assert.ok(histogramKey in snapshot, "histogram with key should be recorded");
+ Assert.equal(snapshot[histogramKey].sum, histogramCount + 1,
+ "histogram sum should be incremented");
+}
+
+/**
+ * Returns the "action" object at the given index in the urlbar results:
+ * { type, params: {}}
+ *
+ * @param index The index in the urlbar results.
+ * @return An action object, or null if index >= number of results.
+ */
+function getActionAtIndex(index) {
+ let controller = gURLBar.popup.input.controller;
+ if (controller.matchCount <= index) {
+ return null;
+ }
+ let url = controller.getValueAt(index);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (!mozActionMatch) {
+ let msg = "result at index " + index + " is not a moz-action: " + url;
+ Assert.ok(false, msg);
+ throw new Error(msg);
+ }
+ let [, type, paramStr] = mozActionMatch;
+ return {
+ type: type,
+ params: JSON.parse(paramStr),
+ };
+}
+
+/**
+ * Returns the index of the first search suggestion in the urlbar results.
+ *
+ * @return An index, or -1 if there are no search suggestions.
+ */
+function getFirstSuggestionIndex() {
+ let controller = gURLBar.popup.input.controller;
+ let matchCount = controller.matchCount;
+ for (let i = 0; i < matchCount; i++) {
+ let url = controller.getValueAt(i);
+ let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+ if (mozActionMatch) {
+ let [, type, paramStr] = mozActionMatch;
+ let params = JSON.parse(paramStr);
+ if (type == "searchengine" && "searchSuggestion" in params) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarStop.js b/browser/base/content/test/urlbar/browser_urlbarStop.js
new file mode 100644
index 000000000..8cf9d8017
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarStop.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const goodURL = "http://mochi.test:8888/";
+const badURL = "http://mochi.test:8888/whatever.html";
+
+add_task(function* () {
+ gBrowser.selectedTab = gBrowser.addTab(goodURL);
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ is(gURLBar.textValue, gURLBar.trimValue(goodURL), "location bar reflects loaded page");
+
+ yield typeAndSubmitAndStop(badURL);
+ is(gURLBar.textValue, gURLBar.trimValue(goodURL), "location bar reflects loaded page after stop()");
+ gBrowser.removeCurrentTab();
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ is(gURLBar.textValue, "", "location bar is empty");
+
+ yield typeAndSubmitAndStop(badURL);
+ is(gURLBar.textValue, gURLBar.trimValue(badURL), "location bar reflects stopped page in an empty tab");
+ gBrowser.removeCurrentTab();
+});
+
+function* typeAndSubmitAndStop(url) {
+ yield promiseAutocompleteResultPopup(url, window, true);
+ is(gURLBar.textValue, gURLBar.trimValue(url), "location bar reflects loading page");
+
+ let promise = waitForDocLoadAndStopIt(url, gBrowser.selectedBrowser, false);
+ gURLBar.handleCommand();
+ yield promise;
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js b/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js
new file mode 100644
index 000000000..913e99a8e
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarTrimURLs.js
@@ -0,0 +1,98 @@
+add_task(function* () {
+ const PREF_TRIMURLS = "browser.urlbar.trimURLs";
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ registerCleanupFunction(function* () {
+ yield BrowserTestUtils.removeTab(tab);
+ Services.prefs.clearUserPref(PREF_TRIMURLS);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, true);
+
+ testVal("http://mozilla.org/", "mozilla.org");
+ testVal("https://mozilla.org/", "https://mozilla.org");
+ testVal("http://mözilla.org/", "mözilla.org");
+ testVal("http://mozilla.imaginatory/", "mozilla.imaginatory");
+ testVal("http://www.mozilla.org/", "www.mozilla.org");
+ testVal("http://sub.mozilla.org/", "sub.mozilla.org");
+ testVal("http://sub1.sub2.sub3.mozilla.org/", "sub1.sub2.sub3.mozilla.org");
+ testVal("http://mozilla.org/file.ext", "mozilla.org/file.ext");
+ testVal("http://mozilla.org/sub/", "mozilla.org/sub/");
+
+ testVal("http://ftp.mozilla.org/", "ftp.mozilla.org");
+ testVal("http://ftp1.mozilla.org/", "ftp1.mozilla.org");
+ testVal("http://ftp42.mozilla.org/", "ftp42.mozilla.org");
+ testVal("http://ftpx.mozilla.org/", "ftpx.mozilla.org");
+ testVal("ftp://ftp.mozilla.org/", "ftp://ftp.mozilla.org");
+ testVal("ftp://ftp1.mozilla.org/", "ftp://ftp1.mozilla.org");
+ testVal("ftp://ftp42.mozilla.org/", "ftp://ftp42.mozilla.org");
+ testVal("ftp://ftpx.mozilla.org/", "ftp://ftpx.mozilla.org");
+
+ testVal("https://user:pass@mozilla.org/", "https://user:pass@mozilla.org");
+ testVal("https://user@mozilla.org/", "https://user@mozilla.org");
+ testVal("http://user:pass@mozilla.org/", "user:pass@mozilla.org");
+ testVal("http://user@mozilla.org/", "user@mozilla.org");
+ testVal("http://sub.mozilla.org:666/", "sub.mozilla.org:666");
+
+ testVal("https://[fe80::222:19ff:fe11:8c76]/file.ext");
+ testVal("http://[fe80::222:19ff:fe11:8c76]/", "[fe80::222:19ff:fe11:8c76]");
+ testVal("https://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
+ testVal("http://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext", "user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+
+ // Behaviour for hosts with no dots depends on the whitelist:
+ let fixupWhitelistPref = "browser.fixup.domainwhitelist.localhost";
+ Services.prefs.setBoolPref(fixupWhitelistPref, false);
+ testVal("http://localhost");
+ Services.prefs.setBoolPref(fixupWhitelistPref, true);
+ testVal("http://localhost", "localhost");
+ Services.prefs.clearUserPref(fixupWhitelistPref);
+
+ testVal("http:// invalid url");
+
+ testVal("http://someotherhostwithnodots");
+ testVal("http://localhost/ foo bar baz");
+ testVal("http://localhost.localdomain/ foo bar baz", "localhost.localdomain/ foo bar baz");
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, false);
+
+ testVal("http://mozilla.org/");
+
+ Services.prefs.setBoolPref(PREF_TRIMURLS, true);
+
+ let promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser,
+ false, "http://example.com/");
+ gBrowser.loadURI("http://example.com/");
+ yield promiseLoaded;
+
+ yield testCopy("example.com", "http://example.com/")
+
+ SetPageProxyState("invalid");
+ gURLBar.valueIsTyped = true;
+ yield testCopy("example.com", "example.com");
+});
+
+function testVal(originalValue, targetValue) {
+ gURLBar.value = originalValue;
+ gURLBar.valueIsTyped = false;
+ is(gURLBar.textValue, targetValue || originalValue, "url bar value set");
+}
+
+function testCopy(originalValue, targetValue) {
+ return new Promise((resolve, reject) => {
+ waitForClipboard(targetValue, function () {
+ is(gURLBar.textValue, originalValue, "url bar copy value set");
+
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ }, resolve, reject);
+ });
+}
diff --git a/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js b/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js
new file mode 100644
index 000000000..c3cdf507f
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbarUpdateForDomainCompletion.js
@@ -0,0 +1,17 @@
+"use strict";
+
+/**
+ * Disable keyword.enabled (so no keyword search), and check that when you type in
+ * "example" and hit enter, the browser loads and the URL bar is updated accordingly.
+ */
+add_task(function* () {
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({set: [["keyword.enabled", false]]}, resolve));
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ gURLBar.value = "example";
+ gURLBar.select();
+ let loadPromise = BrowserTestUtils.browserLoaded(browser, false, url => url == "http://www.example.com/");
+ EventUtils.sendKey("return");
+ yield loadPromise;
+ is(gURLBar.textValue, "www.example.com");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js b/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js
new file mode 100644
index 000000000..7fefd3f77
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js
@@ -0,0 +1,146 @@
+/* This test ensures that backspacing autoFilled values still allows to
+ * confirm the remaining value.
+ */
+
+function* test_autocomplete(data) {
+ let {desc, typed, autofilled, modified, keys, action, onAutoFill} = data;
+ info(desc);
+
+ yield promiseAutocompleteResultPopup(typed);
+ is(gURLBar.textValue, autofilled, "autofilled value is as expected");
+ if (onAutoFill)
+ onAutoFill()
+
+ keys.forEach(key => EventUtils.synthesizeKey(key, {}));
+
+ is(gURLBar.textValue, modified, "backspaced value is as expected");
+
+ yield promiseSearchComplete();
+
+ ok(gURLBar.popup.richlistbox.children.length > 0, "Should get at least 1 result");
+ let result = gURLBar.popup.richlistbox.children[0];
+ let type = result.getAttribute("type");
+ let types = type.split(/\s+/);
+ ok(types.indexOf(action) >= 0, `The type attribute "${type}" includes the expected action "${action}"`);
+
+ gURLBar.popup.hidePopup();
+ yield promisePopupHidden(gURLBar.popup);
+ gURLBar.blur();
+}
+
+add_task(function* () {
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref("browser.urlbar.autoFill");
+ gURLBar.handleRevert();
+ yield PlacesTestUtils.clearHistory();
+ });
+ Services.prefs.setBoolPref("browser.urlbar.autoFill", true);
+
+ // Add a typed visit, so it will be autofilled.
+ yield PlacesTestUtils.addVisits({
+ uri: NetUtil.newURI("http://example.com/"),
+ transition: Ci.nsINavHistoryService.TRANSITION_TYPED
+ });
+
+ yield test_autocomplete({ desc: "DELETE the autofilled part should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exam",
+ keys: ["VK_DELETE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "DELETE the final slash should visit",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["VK_DELETE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "BACK_SPACE the autofilled part should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exam",
+ keys: ["VK_BACK_SPACE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "BACK_SPACE the final slash should visit",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.com",
+ keys: ["VK_BACK_SPACE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "DELETE the autofilled part, then BACK_SPACE, should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exa",
+ keys: ["VK_DELETE", "VK_BACK_SPACE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "DELETE the final slash, then BACK_SPACE, should search",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.co",
+ keys: ["VK_DELETE", "VK_BACK_SPACE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "BACK_SPACE the autofilled part, then BACK_SPACE, should search",
+ typed: "exam",
+ autofilled: "example.com/",
+ modified: "exa",
+ keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"],
+ action: "searchengine"
+ });
+ yield test_autocomplete({ desc: "BACK_SPACE the final slash, then BACK_SPACE, should search",
+ typed: "example.com",
+ autofilled: "example.com/",
+ modified: "example.co",
+ keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"],
+ action: "visiturl"
+ });
+
+ yield test_autocomplete({ desc: "BACK_SPACE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["VK_BACK_SPACE"],
+ action: "searchengine",
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ gURLBar.selectionStart = 1;
+ gURLBar.selectionEnd = 12;
+ }
+ });
+ yield test_autocomplete({ desc: "DELETE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["VK_DELETE"],
+ action: "searchengine",
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ gURLBar.selectionStart = 1;
+ gURLBar.selectionEnd = 12;
+ }
+ });
+ yield test_autocomplete({ desc: "double BACK_SPACE after blur should search",
+ typed: "ex",
+ autofilled: "example.com/",
+ modified: "e",
+ keys: ["VK_BACK_SPACE", "VK_BACK_SPACE"],
+ action: "searchengine",
+ onAutoFill: () => {
+ gURLBar.blur();
+ gURLBar.focus();
+ gURLBar.selectionStart = 2;
+ gURLBar.selectionEnd = 12;
+ }
+ });
+
+ yield PlacesTestUtils.clearHistory();
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_blanking.js b/browser/base/content/test/urlbar/browser_urlbar_blanking.js
new file mode 100644
index 000000000..13660edab
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_blanking.js
@@ -0,0 +1,35 @@
+"use strict";
+
+add_task(function*() {
+ for (let page of gInitialPages) {
+ if (page == "about:newtab") {
+ // New tab preloading makes this a pain to test, so skip
+ continue;
+ }
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, page);
+ ok(!gURLBar.value, "The URL bar should be empty if we load a plain " + page + " page.");
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(function*() {
+ const URI = "http://www.example.com/browser/browser/base/content/test/urlbar/file_blank_but_not_blank.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URI);
+ is(gURLBar.value, URI, "The URL bar should match the URI");
+ let browserLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ ContentTask.spawn(tab.linkedBrowser, null, function() {
+ content.document.querySelector('a').click();
+ });
+ yield browserLoaded;
+ ok(gURLBar.value.startsWith("javascript"), "The URL bar should have the JS URI");
+ // When reloading, the javascript: uri we're using will throw an exception.
+ // That's deliberate, so we need to tell mochitest to ignore it:
+ SimpleTest.expectUncaughtException(true);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ // This is sync, so by the time we return we should have changed the URL bar.
+ content.location.reload();
+ });
+ ok(!!gURLBar.value, "URL bar should not be blank.");
+ yield BrowserTestUtils.removeTab(tab);
+ SimpleTest.expectUncaughtException(false);
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js b/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js
new file mode 100644
index 000000000..63ed58a62
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_locationchange_urlbar_edit_dos.js
@@ -0,0 +1,41 @@
+"use strict";
+
+function* checkURLBarValueStays(browser) {
+ gURLBar.select();
+ EventUtils.synthesizeKey("a", {});
+ is(gURLBar.value, "a", "URL bar value should match after sending a key");
+ yield new Promise(resolve => {
+ let listener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ ok(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT,
+ "Should only get a same document location change");
+ gBrowser.selectedBrowser.removeProgressListener(filter);
+ filter = null;
+ resolve();
+ },
+ };
+ let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
+ gBrowser.selectedBrowser.addProgressListener(filter);
+ });
+ is(gURLBar.value, "a", "URL bar should not have been changed by location changes.");
+}
+
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com/browser/browser/base/content/test/urlbar/file_urlbar_edit_dos.html"
+ }, function*(browser) {
+ yield ContentTask.spawn(browser, "", function() {
+ content.wrappedJSObject.dos_hash();
+ });
+ yield checkURLBarValueStays(browser);
+ yield ContentTask.spawn(browser, "", function() {
+ content.clearTimeout(content.wrappedJSObject.dos_timeout);
+ content.wrappedJSObject.dos_pushState();
+ });
+ yield checkURLBarValueStays(browser);
+ });
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js b/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js
new file mode 100644
index 000000000..9a1df0505
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js
@@ -0,0 +1,39 @@
+"use strict";
+
+/**
+ * Verify that when loading and going back/forward through history between URLs
+ * loaded in the content process, and URLs loaded in the parent process, we
+ * don't set the URL for the tab to about:blank inbetween the loads.
+ */
+add_task(function*() {
+ let url = "http://www.example.com/foo.html";
+ yield BrowserTestUtils.withNewTab({gBrowser, url}, function*(browser) {
+ let wpl = {
+ onLocationChange(wpl, request, location, flags) {
+ if (location.schemeIs("about")) {
+ is(location.spec, "about:config", "Only about: location change should be for about:preferences");
+ } else {
+ is(location.spec, url, "Only non-about: location change should be for the http URL we're dealing with.");
+ }
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+
+ let didLoad = BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+ return loadedURL == "about:config";
+ });
+ yield BrowserTestUtils.loadURI(browser, "about:config");
+ yield didLoad;
+
+ gBrowser.goBack();
+ yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+ return url == loadedURL;
+ });
+ gBrowser.goForward();
+ yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+ return loadedURL == "about:config";
+ });
+ gBrowser.removeProgressListener(wpl);
+ });
+});
+
diff --git a/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js b/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js
new file mode 100644
index 000000000..04b1c508b
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_searchsettings.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function*() {
+ let button = document.getElementById("urlbar-search-settings");
+ if (!button) {
+ ok("Skipping test");
+ return;
+ }
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* () {
+ let popupopened = BrowserTestUtils.waitForEvent(gURLBar.popup, "popupshown");
+
+ gURLBar.focus();
+ EventUtils.synthesizeKey("a", {});
+ yield popupopened;
+
+ // Since the current tab is blank the preferences pane will load there
+ let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ let popupclosed = BrowserTestUtils.waitForEvent(gURLBar.popup, "popuphidden");
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ yield loaded;
+ yield popupclosed;
+
+ is(gBrowser.selectedBrowser.currentURI.spec, "about:preferences#search",
+ "Should have loaded the right page");
+ });
+});
diff --git a/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js
new file mode 100644
index 000000000..6b6a10ea3
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_stop_pending.js
@@ -0,0 +1,138 @@
+"use strict";
+
+const SLOW_PAGE = "http://www.example.com/browser/browser/base/content/test/urlbar/slow-page.sjs";
+const SLOW_PAGE2 = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/slow-page.sjs?faster";
+
+/**
+ * Check that if we:
+ * 1) have a loaded page
+ * 2) load a separate URL
+ * 3) before the URL for step 2 has finished loading, load a third URL
+ * we don't revert to the URL from (1).
+ */
+add_task(function*() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com", true, true);
+
+ let expectedURLBarChange = SLOW_PAGE;
+ let sawChange = false;
+ let handler = e => {
+ sawChange = true;
+ is(gURLBar.value, expectedURLBarChange, "Should not change URL bar value!");
+ };
+
+ let obs = new MutationObserver(handler);
+
+ obs.observe(gURLBar, {attributes: true});
+ gURLBar.value = SLOW_PAGE;
+ gURLBar.handleCommand();
+
+ // If this ever starts going intermittent, we've broken this.
+ yield new Promise(resolve => setTimeout(resolve, 200));
+ expectedURLBarChange = SLOW_PAGE2;
+ let pageLoadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ gURLBar.value = expectedURLBarChange;
+ gURLBar.handleCommand();
+ is(gURLBar.value, expectedURLBarChange, "Should not have changed URL bar value synchronously.");
+ yield pageLoadPromise;
+ ok(sawChange, "The URL bar change handler should have been called by the time the page was loaded");
+ obs.disconnect();
+ obs = null;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we switch to that tab and stop the request
+ *
+ * The URL bar continues to contain the URL of the page we wanted to visit.
+ */
+add_task(function*() {
+ let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+ socket.init(-1, true, -1);
+ const PORT = socket.port;
+ registerCleanupFunction(() => { socket.close(); });
+
+ const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+ const BASE_PAGE = TEST_PATH + "dummy_page.html";
+ const SLOW_HOST = `https://localhost:${PORT}/`;
+ info("Using URLs: " + SLOW_HOST);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+ info("opened tab");
+ yield ContentTask.spawn(tab.linkedBrowser, SLOW_HOST, URL => {
+ let link = content.document.createElement("a");
+ link.href = URL;
+ link.textContent = "click me to open a slow page";
+ link.id = "clickme"
+ content.document.body.appendChild(link);
+ });
+ info("added link");
+ let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ // Middle click the link:
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser);
+ // get new tab, switch to it
+ let newTab = (yield newTabPromise).target;
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gURLBar.value, SLOW_HOST, "Should have slow page in URL bar");
+ let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+ BrowserStop();
+ yield browserStoppedPromise;
+
+ is(gURLBar.value, SLOW_HOST, "Should still have slow page in URL bar after stop");
+ yield BrowserTestUtils.removeTab(newTab);
+ yield BrowserTestUtils.removeTab(tab);
+});
+/**
+ * Check that if we:
+ * 1) middle-click a link to a separate page whose server doesn't respond
+ * 2) we alter the URL on that page to some other server that doesn't respond
+ * 3) we stop the request
+ *
+ * The URL bar continues to contain the second URL.
+ */
+add_task(function*() {
+ let socket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+ socket.init(-1, true, -1);
+ const PORT1 = socket.port;
+ let socket2 = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+ socket2.init(-1, true, -1);
+ const PORT2 = socket2.port;
+ registerCleanupFunction(() => { socket.close(); socket2.close(); });
+
+ const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com");
+ const BASE_PAGE = TEST_PATH + "dummy_page.html";
+ const SLOW_HOST1 = `https://localhost:${PORT1}/`;
+ const SLOW_HOST2 = `https://localhost:${PORT2}/`;
+ info("Using URLs: " + SLOW_HOST1 + " and " + SLOW_HOST2);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, BASE_PAGE);
+ info("opened tab");
+ yield ContentTask.spawn(tab.linkedBrowser, SLOW_HOST1, URL => {
+ let link = content.document.createElement("a");
+ link.href = URL;
+ link.textContent = "click me to open a slow page";
+ link.id = "clickme"
+ content.document.body.appendChild(link);
+ });
+ info("added link");
+ let newTabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ // Middle click the link:
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { button: 1 }, tab.linkedBrowser);
+ // get new tab, switch to it
+ let newTab = (yield newTabPromise).target;
+ yield BrowserTestUtils.switchTab(gBrowser, newTab);
+ is(gURLBar.value, SLOW_HOST1, "Should have slow page in URL bar");
+ let browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+ gURLBar.value = SLOW_HOST2;
+ gURLBar.handleCommand();
+ yield browserStoppedPromise;
+
+ is(gURLBar.value, SLOW_HOST2, "Should have second slow page in URL bar");
+ browserStoppedPromise = BrowserTestUtils.browserStopped(newTab.linkedBrowser);
+ BrowserStop();
+ yield browserStoppedPromise;
+
+ is(gURLBar.value, SLOW_HOST2, "Should still have second slow page in URL bar after stop");
+ yield BrowserTestUtils.removeTab(newTab);
+ yield BrowserTestUtils.removeTab(tab);
+});
+
diff --git a/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js b/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js
new file mode 100644
index 000000000..54b174aa8
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function testURLBarCopy(targetValue) {
+ return new Promise((resolve, reject) => {
+ info("Expecting copy of: " + targetValue);
+ waitForClipboard(targetValue, function () {
+ gURLBar.focus();
+ gURLBar.select();
+
+ goDoCommand("cmd_copy");
+ }, resolve, () => {
+ ok(false, "Clipboard copy failed");
+ reject();
+ });
+ });
+}
+
+add_task(function* () {
+ const url = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/test_wyciwyg_copying.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#btn", {}, tab.linkedBrowser);
+ let currentURL = gBrowser.currentURI.spec;
+ ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
+
+ yield testURLBarCopy(url);
+
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/urlbar/dummy_page.html b/browser/base/content/test/urlbar/dummy_page.html
new file mode 100644
index 000000000..1a87e2840
--- /dev/null
+++ b/browser/base/content/test/urlbar/dummy_page.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/urlbar/file_blank_but_not_blank.html b/browser/base/content/test/urlbar/file_blank_but_not_blank.html
new file mode 100644
index 000000000..1f5fea8dc
--- /dev/null
+++ b/browser/base/content/test/urlbar/file_blank_but_not_blank.html
@@ -0,0 +1,2 @@
+<script>var q = "1";</script>
+<a href="javascript:q">Click me</a>
diff --git a/browser/base/content/test/urlbar/file_urlbar_edit_dos.html b/browser/base/content/test/urlbar/file_urlbar_edit_dos.html
new file mode 100644
index 000000000..5a6e7d109
--- /dev/null
+++ b/browser/base/content/test/urlbar/file_urlbar_edit_dos.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Try editing the URL bar</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<script>
+var dos_timeout = null;
+function dos_hash() {
+ dos_timeout = setTimeout(function() {
+ location.hash = "#";
+ }, 50);
+}
+
+function dos_pushState() {
+ dos_timeout = setTimeout(function() {
+ history.pushState({}, "Some title", "");
+ }, 50);
+}
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/urlbar/head.js b/browser/base/content/test/urlbar/head.js
new file mode 100644
index 000000000..427dba080
--- /dev/null
+++ b/browser/base/content/test/urlbar/head.js
@@ -0,0 +1,205 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+
+/**
+ * Waits for the next top-level document load in the current browser. The URI
+ * of the document is compared against aExpectedURL. The load is then stopped
+ * before it actually starts.
+ *
+ * @param aExpectedURL
+ * The URL of the document that is expected to load.
+ * @param aStopFromProgressListener
+ * Whether to cancel the load directly from the progress listener. Defaults to true.
+ * If you're using this method to avoid hitting the network, you want the default (true).
+ * However, the browser UI will behave differently for loads stopped directly from
+ * the progress listener (effectively in the middle of a call to loadURI) and so there
+ * are cases where you may want to avoid stopping the load directly from within the
+ * progress listener callback.
+ * @return promise
+ */
+function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser, aStopFromProgressListener=true) {
+ function content_script(aStopFromProgressListener) {
+ let { interfaces: Ci, utils: Cu } = Components;
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+ let wp = docShell.QueryInterface(Ci.nsIWebProgress);
+
+ function stopContent(now, uri) {
+ if (now) {
+ /* Hammer time. */
+ content.stop();
+
+ /* Let the parent know we're done. */
+ sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri });
+ } else {
+ setTimeout(stopContent.bind(null, true, uri), 0);
+ }
+ }
+
+ let progressListener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n");
+
+ if (webProgress.isTopLevel &&
+ flags & Ci.nsIWebProgressListener.STATE_START) {
+ wp.removeProgressListener(progressListener);
+
+ let chan = req.QueryInterface(Ci.nsIChannel);
+ dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`);
+
+ stopContent(aStopFromProgressListener, chan.originalURI.spec);
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
+ };
+ wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW);
+
+ /**
+ * As |this| is undefined and we can't extend |docShell|, adding an unload
+ * event handler is the easiest way to ensure the weakly referenced
+ * progress listener is kept alive as long as necessary.
+ */
+ addEventListener("unload", function () {
+ try {
+ wp.removeProgressListener(progressListener);
+ } catch (e) { /* Will most likely fail. */ }
+ });
+ }
+
+ return new Promise((resolve, reject) => {
+ function complete({ data }) {
+ is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded");
+ mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ resolve();
+ }
+
+ let mm = aBrowser.messageManager;
+ mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true);
+ mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
+ info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
+ });
+}
+
+function is_hidden(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+ if (style.display == "-moz-popup")
+ return ["hiding", "closed"].indexOf(element.state) != -1;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_hidden(element.parentNode);
+
+ return false;
+}
+
+function is_visible(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return false;
+ if (style.visibility != "visible")
+ return false;
+ if (style.display == "-moz-popup" && element.state != "open")
+ return false;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_visible(element.parentNode);
+
+ return true;
+}
+
+function is_element_visible(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_visible(element), msg || "Element should be visible");
+}
+
+function is_element_hidden(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_hidden(element), msg || "Element should be hidden");
+}
+
+function promisePopupEvent(popup, eventSuffix) {
+ let endState = {shown: "open", hidden: "closed"}[eventSuffix];
+
+ if (popup.state == endState)
+ return Promise.resolve();
+
+ let eventType = "popup" + eventSuffix;
+ let deferred = Promise.defer();
+ popup.addEventListener(eventType, function onPopupShown(event) {
+ popup.removeEventListener(eventType, onPopupShown);
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function promisePopupShown(popup) {
+ return promisePopupEvent(popup, "shown");
+}
+
+function promisePopupHidden(popup) {
+ return promisePopupEvent(popup, "hidden");
+}
+
+function promiseSearchComplete(win = window) {
+ return promisePopupShown(win.gURLBar.popup).then(() => {
+ function searchIsComplete() {
+ return win.gURLBar.controller.searchStatus >=
+ Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+ }
+
+ // Wait until there are at least two matches.
+ return BrowserTestUtils.waitForCondition(searchIsComplete, "waiting urlbar search to complete");
+ });
+}
+
+function promiseAutocompleteResultPopup(inputText,
+ win = window,
+ fireInputEvent = false) {
+ waitForFocus(() => {
+ win.gURLBar.focus();
+ win.gURLBar.value = inputText;
+ if (fireInputEvent) {
+ // This is necessary to get the urlbar to set gBrowser.userTypedValue.
+ let event = document.createEvent("Events");
+ event.initEvent("input", true, true);
+ win.gURLBar.dispatchEvent(event);
+ }
+ win.gURLBar.controller.startSearch(inputText);
+ }, win);
+
+ return promiseSearchComplete(win);
+}
+
+function promiseNewSearchEngine(basename) {
+ return new Promise((resolve, reject) => {
+ info("Waiting for engine to be added: " + basename);
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ registerCleanupFunction(() => Services.search.removeEngine(engine));
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ Assert.ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ },
+ });
+ });
+}
+
diff --git a/browser/base/content/test/urlbar/moz.png b/browser/base/content/test/urlbar/moz.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/base/content/test/urlbar/moz.png
Binary files differ
diff --git a/browser/base/content/test/urlbar/print_postdata.sjs b/browser/base/content/test/urlbar/print_postdata.sjs
new file mode 100644
index 000000000..4175a2480
--- /dev/null
+++ b/browser/base/content/test/urlbar/print_postdata.sjs
@@ -0,0 +1,22 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/browser/base/content/test/urlbar/redirect_bug623155.sjs b/browser/base/content/test/urlbar/redirect_bug623155.sjs
new file mode 100644
index 000000000..64c6f143b
--- /dev/null
+++ b/browser/base/content/test/urlbar/redirect_bug623155.sjs
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function handleRequest(aRequest, aResponse) {
+ // Set HTTP Status
+ aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently");
+
+ // Set redirect URI, mirroring the hash value.
+ let hash = (/\#.+/.test(aRequest.path))?
+ "#" + aRequest.path.split("#")[1]:
+ "";
+ aResponse.setHeader("Location", REDIRECT_TO + hash);
+}
diff --git a/browser/base/content/test/urlbar/searchSuggestionEngine.sjs b/browser/base/content/test/urlbar/searchSuggestionEngine.sjs
new file mode 100644
index 000000000..1978b4f66
--- /dev/null
+++ b/browser/base/content/test/urlbar/searchSuggestionEngine.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/base/content/test/urlbar/searchSuggestionEngine.xml b/browser/base/content/test/urlbar/searchSuggestionEngine.xml
new file mode 100644
index 000000000..a5659792e
--- /dev/null
+++ b/browser/base/content/test/urlbar/searchSuggestionEngine.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/urlbar/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/base/content/test/urlbar/slow-page.sjs b/browser/base/content/test/urlbar/slow-page.sjs
new file mode 100644
index 000000000..f428d66e4
--- /dev/null
+++ b/browser/base/content/test/urlbar/slow-page.sjs
@@ -0,0 +1,22 @@
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let timer;
+
+const DELAY_MS = 5000;
+function handleRequest(request, response) {
+ if (request.queryString.endsWith("faster")) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>Not so slow!</body>");
+ return;
+ }
+ response.processAsync();
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(() => {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<body>This was the slow load. You should never see this.</body>");
+ response.finish();
+ }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/browser/base/content/test/urlbar/test_wyciwyg_copying.html b/browser/base/content/test/urlbar/test_wyciwyg_copying.html
new file mode 100644
index 000000000..3a8c3a150
--- /dev/null
+++ b/browser/base/content/test/urlbar/test_wyciwyg_copying.html
@@ -0,0 +1,13 @@
+<html>
+<body>
+<script>
+ function go() {
+ var w = window.open();
+ w.document.open();
+ w.document.write("<html><body>test document</body></html>");
+ w.document.close();
+ }
+</script>
+<button id="btn" onclick="go();">test</button>
+</body>
+</html>
diff --git a/browser/base/content/test/webrtc/.eslintrc.js b/browser/base/content/test/webrtc/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/base/content/test/webrtc/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/base/content/test/webrtc/browser.ini b/browser/base/content/test/webrtc/browser.ini
new file mode 100644
index 000000000..8830989ad
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ get_user_media.html
+ get_user_media_content_script.js
+ head.js
+
+[browser_devices_get_user_media.js]
+skip-if = (os == "linux" && debug) # linux: bug 976544
+[browser_devices_get_user_media_anim.js]
+[browser_devices_get_user_media_in_frame.js]
+[browser_devices_get_user_media_tear_off_tab.js]
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media.js b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
new file mode 100644
index 000000000..3681a810b
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -0,0 +1,554 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(2);
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+const permissionError = "error: NotAllowedError: The request is not allowed " +
+ "by the user agent or the platform in the current context.";
+
+var gTests = [
+
+{
+ desc: "getUserMedia audio+video",
+ run: function* checkAudioVideo() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareDevices-notification-icon", "anchored to device icon");
+ checkDeviceSelectors(true, true);
+ let iconclass =
+ PopupNotifications.panel.firstChild.getAttribute("iconclass");
+ ok(iconclass.includes("camera-icon"), "panel using devices icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({audio: true, video: true});
+ yield closeStream();
+ }
+},
+
+{
+ desc: "getUserMedia audio only",
+ run: function* checkAudioOnly() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareMicrophone-notification-icon", "anchored to mic icon");
+ checkDeviceSelectors(true);
+ let iconclass =
+ PopupNotifications.panel.firstChild.getAttribute("iconclass");
+ ok(iconclass.includes("microphone-icon"), "panel using microphone icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Microphone",
+ "expected microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({audio: true});
+ yield closeStream();
+ }
+},
+
+{
+ desc: "getUserMedia video only",
+ run: function* checkVideoOnly() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(false, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareDevices-notification-icon", "anchored to device icon");
+ checkDeviceSelectors(false, true);
+ let iconclass =
+ PopupNotifications.panel.firstChild.getAttribute("iconclass");
+ ok(iconclass.includes("camera-icon"), "panel using devices icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Camera", "expected camera to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true});
+ yield closeStream();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video, user clicks \"Don't Share\"",
+ run: function* checkDontShare() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ yield promiseMessage(permissionError, () => {
+ activateSecondaryAction(kActionDeny);
+ });
+
+ yield expectObserverCalled("getUserMedia:response:deny");
+ yield expectObserverCalled("recording-window-ended");
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: stop sharing",
+ run: function* checkStopSharing() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ yield stopSharing();
+
+ // the stream is already closed, but this will do some cleanup anyway
+ yield closeStream(true);
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the page removes all gUM UI",
+ run: function* checkReloading() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("reloading the web page");
+ promise = promiseObserverCalled("recording-device-events");
+ content.location.reload();
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia prompt: Always/Never Share",
+ run: function* checkRememberCheckbox() {
+ let elt = id => document.getElementById(id);
+
+ function* checkPerm(aRequestAudio, aRequestVideo,
+ aExpectedAudioPerm, aExpectedVideoPerm, aNever) {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(elt("webRTC-selectMicrophone").hidden, !aRequestAudio,
+ "microphone selector expected to be " + (aRequestAudio ? "visible" : "hidden"));
+
+ is(elt("webRTC-selectCamera").hidden, !aRequestVideo,
+ "camera selector expected to be " + (aRequestVideo ? "visible" : "hidden"));
+
+ let expectedMessage = aNever ? permissionError : "ok";
+ yield promiseMessage(expectedMessage, () => {
+ activateSecondaryAction(aNever ? kActionNever : kActionAlways);
+ });
+ let expected = [];
+ if (expectedMessage == "ok") {
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ if (aRequestVideo)
+ expected.push("Camera");
+ if (aRequestAudio)
+ expected.push("Microphone");
+ expected = expected.join("And");
+ }
+ else {
+ yield expectObserverCalled("getUserMedia:response:deny");
+ yield expectObserverCalled("recording-window-ended");
+ expected = "none";
+ }
+ is((yield getMediaCaptureState()), expected,
+ "expected " + expected + " to be shared");
+
+ function checkDevicePermissions(aDevice, aExpected) {
+ let Perms = Services.perms;
+ let uri = gBrowser.selectedBrowser.documentURI;
+ let devicePerms = Perms.testExactPermission(uri, aDevice);
+ if (aExpected === undefined)
+ is(devicePerms, Perms.UNKNOWN_ACTION, "no " + aDevice + " persistent permissions");
+ else {
+ is(devicePerms, aExpected ? Perms.ALLOW_ACTION : Perms.DENY_ACTION,
+ aDevice + " persistently " + (aExpected ? "allowed" : "denied"));
+ }
+ Perms.remove(uri, aDevice);
+ }
+ checkDevicePermissions("microphone", aExpectedAudioPerm);
+ checkDevicePermissions("camera", aExpectedVideoPerm);
+
+ if (expectedMessage == "ok")
+ yield closeStream();
+ }
+
+ // 3 cases where the user accepts the device prompt.
+ info("audio+video, user grants, expect both perms set to allow");
+ yield checkPerm(true, true, true, true);
+ info("audio only, user grants, check audio perm set to allow, video perm not set");
+ yield checkPerm(true, false, true, undefined);
+ info("video only, user grants, check video perm set to allow, audio perm not set");
+ yield checkPerm(false, true, undefined, true);
+
+ // 3 cases where the user rejects the device request by using 'Never Share'.
+ info("audio only, user denies, expect audio perm set to deny, video not set");
+ yield checkPerm(true, false, false, undefined, true);
+ info("video only, user denies, expect video perm set to deny, audio perm not set");
+ yield checkPerm(false, true, undefined, false, true);
+ info("audio+video, user denies, expect both perms set to deny");
+ yield checkPerm(true, true, false, false, true);
+ }
+},
+
+{
+ desc: "getUserMedia without prompt: use persistent permissions",
+ run: function* checkUsePersistentPermissions() {
+ function* usePerm(aAllowAudio, aAllowVideo, aRequestAudio, aRequestVideo,
+ aExpectStream) {
+ let Perms = Services.perms;
+ let uri = gBrowser.selectedBrowser.documentURI;
+
+ if (aAllowAudio !== undefined) {
+ Perms.add(uri, "microphone", aAllowAudio ? Perms.ALLOW_ACTION
+ : Perms.DENY_ACTION);
+ }
+ if (aAllowVideo !== undefined) {
+ Perms.add(uri, "camera", aAllowVideo ? Perms.ALLOW_ACTION
+ : Perms.DENY_ACTION);
+ }
+
+ if (aExpectStream === undefined) {
+ // Check that we get a prompt.
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ // Deny the request to cleanup...
+ yield promiseMessage(permissionError, () => {
+ activateSecondaryAction(kActionDeny);
+ });
+ yield expectObserverCalled("getUserMedia:response:deny");
+ yield expectObserverCalled("recording-window-ended");
+ }
+ else {
+ let expectedMessage = aExpectStream ? "ok" : permissionError;
+ let promise = promiseMessage(expectedMessage);
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+
+ if (expectedMessage == "ok") {
+ yield expectObserverCalled("getUserMedia:request");
+ yield promiseNoPopupNotification("webRTC-shareDevices");
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+
+ // Check what's actually shared.
+ let expected = [];
+ if (aAllowVideo && aRequestVideo)
+ expected.push("Camera");
+ if (aAllowAudio && aRequestAudio)
+ expected.push("Microphone");
+ expected = expected.join("And");
+ is((yield getMediaCaptureState()), expected,
+ "expected " + expected + " to be shared");
+
+ yield closeStream();
+ }
+ else {
+ yield expectObserverCalled("recording-window-ended");
+ }
+ }
+
+ Perms.remove(uri, "camera");
+ Perms.remove(uri, "microphone");
+ }
+
+ // Set both permissions identically
+ info("allow audio+video, request audio+video, expect ok (audio+video)");
+ yield usePerm(true, true, true, true, true);
+ info("deny audio+video, request audio+video, expect denied");
+ yield usePerm(false, false, true, true, false);
+
+ // Allow audio, deny video.
+ info("allow audio, deny video, request audio+video, expect denied");
+ yield usePerm(true, false, true, true, false);
+ info("allow audio, deny video, request audio, expect ok (audio)");
+ yield usePerm(true, false, true, false, true);
+ info("allow audio, deny video, request video, expect denied");
+ yield usePerm(true, false, false, true, false);
+
+ // Deny audio, allow video.
+ info("deny audio, allow video, request audio+video, expect denied");
+ yield usePerm(false, true, true, true, false);
+ info("deny audio, allow video, request audio, expect denied");
+ yield usePerm(false, true, true, false, false);
+ info("deny audio, allow video, request video, expect ok (video)");
+ yield usePerm(false, true, false, true, true);
+
+ // Allow audio, video not set.
+ info("allow audio, request audio+video, expect prompt");
+ yield usePerm(true, undefined, true, true, undefined);
+ info("allow audio, request audio, expect ok (audio)");
+ yield usePerm(true, undefined, true, false, true);
+ info("allow audio, request video, expect prompt");
+ yield usePerm(true, undefined, false, true, undefined);
+
+ // Deny audio, video not set.
+ info("deny audio, request audio+video, expect denied");
+ yield usePerm(false, undefined, true, true, false);
+ info("deny audio, request audio, expect denied");
+ yield usePerm(false, undefined, true, false, false);
+ info("deny audio, request video, expect prompt");
+ yield usePerm(false, undefined, false, true, undefined);
+
+ // Allow video, audio not set.
+ info("allow video, request audio+video, expect prompt");
+ yield usePerm(undefined, true, true, true, undefined);
+ info("allow video, request audio, expect prompt");
+ yield usePerm(undefined, true, true, false, undefined);
+ info("allow video, request video, expect ok (video)");
+ yield usePerm(undefined, true, false, true, true);
+
+ // Deny video, audio not set.
+ info("deny video, request audio+video, expect denied");
+ yield usePerm(undefined, false, true, true, false);
+ info("deny video, request audio, expect prompt");
+ yield usePerm(undefined, false, true, false, undefined);
+ info("deny video, request video, expect denied");
+ yield usePerm(undefined, false, false, true, false);
+ }
+},
+
+{
+ desc: "Stop Sharing removes persistent permissions",
+ run: function* checkStopSharingRemovesPersistentPermissions() {
+ function* stopAndCheckPerm(aRequestAudio, aRequestVideo) {
+ let Perms = Services.perms;
+ let uri = gBrowser.selectedBrowser.documentURI;
+
+ // Initially set both permissions to 'allow'.
+ Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
+ Perms.add(uri, "camera", Perms.ALLOW_ACTION);
+
+ let indicator = promiseIndicatorWindow();
+ // Start sharing what's been requested.
+ let promise = promiseMessage("ok");
+ yield promiseRequestDevice(aRequestAudio, aRequestVideo);
+ yield promise;
+
+ yield expectObserverCalled("getUserMedia:request");
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ yield indicator;
+ yield checkSharingUI({video: aRequestVideo, audio: aRequestAudio});
+
+ yield stopSharing(aRequestVideo ? "camera" : "microphone");
+
+ // Check that permissions have been removed as expected.
+ let audioPerm = Perms.testExactPermission(uri, "microphone");
+ if (aRequestAudio)
+ is(audioPerm, Perms.UNKNOWN_ACTION, "microphone permissions removed");
+ else
+ is(audioPerm, Perms.ALLOW_ACTION, "microphone permissions untouched");
+
+ let videoPerm = Perms.testExactPermission(uri, "camera");
+ if (aRequestVideo)
+ is(videoPerm, Perms.UNKNOWN_ACTION, "camera permissions removed");
+ else
+ is(videoPerm, Perms.ALLOW_ACTION, "camera permissions untouched");
+
+ // Cleanup.
+ yield closeStream(true);
+
+ Perms.remove(uri, "camera");
+ Perms.remove(uri, "microphone");
+ }
+
+ info("request audio+video, stop sharing resets both");
+ yield stopAndCheckPerm(true, true);
+ info("request audio, stop sharing resets audio only");
+ yield stopAndCheckPerm(true, false);
+ info("request video, stop sharing resets video only");
+ yield stopAndCheckPerm(false, true);
+ }
+},
+
+{
+ desc: "test showControlCenter",
+ run: function* checkShowControlCenter() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(false, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(false, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Camera", "expected camera to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true});
+
+ ok(gIdentityHandler._identityPopup.hidden, "control center should be hidden");
+ if ("nsISystemStatusBar" in Ci) {
+ let activeStreams = webrtcUI.getActiveStreams(true, false, false);
+ webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices");
+ }
+ else {
+ let win =
+ Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator");
+ let elt = win.document.getElementById("audioVideoButton");
+ EventUtils.synthesizeMouseAtCenter(elt, {}, win);
+ yield promiseWaitForCondition(() => !gIdentityHandler._identityPopup.hidden);
+ }
+ ok(!gIdentityHandler._identityPopup.hidden, "control center should be open");
+
+ gIdentityHandler._identityPopup.hidden = true;
+ yield expectNoObserverCalled();
+
+ yield closeStream();
+ }
+},
+
+{
+ desc: "'Always Allow' ignored and not shown on http pages",
+ run: function* checkNoAlwaysOnHttp() {
+ // Load an http page instead of the https version.
+ let browser = gBrowser.selectedBrowser;
+ browser.loadURI(browser.documentURI.spec.replace("https://", "http://"));
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // Initially set both permissions to 'allow'.
+ let Perms = Services.perms;
+ let uri = browser.documentURI;
+ Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
+ Perms.add(uri, "camera", Perms.ALLOW_ACTION);
+
+ // Request devices and expect a prompt despite the saved 'Allow' permission,
+ // because the connection isn't secure.
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ // Ensure that the 'Always Allow' action isn't shown.
+ let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label");
+ ok(!!alwaysLabel, "found the 'Always Allow' localized label");
+ let labels = [];
+ let notification = PopupNotifications.panel.firstChild;
+ for (let node of notification.childNodes) {
+ if (node.localName == "menuitem")
+ labels.push(node.getAttribute("label"));
+ }
+ is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown");
+
+ // Cleanup.
+ yield closeStream(true);
+ Perms.remove(uri, "camera");
+ Perms.remove(uri, "microphone");
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+ ok(gIdentityHandler._identityPopup.hidden,
+ "should start the test with the control center hidden");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+
+ // Cleanup before the next test
+ yield expectNoObserverCalled();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ content.location = rootDir + "get_user_media.html";
+}
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js
new file mode 100644
index 000000000..f407061a7
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_anim.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+var gTests = [
+
+{
+ desc: "device sharing animation on background tabs",
+ run: function* checkAudioVideo() {
+ function* getStreamAndCheckBackgroundAnim(aAudio, aVideo, aSharing) {
+ // Get a stream
+ let popupPromise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(aAudio, aVideo);
+ yield popupPromise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ let expected = [];
+ if (aVideo)
+ expected.push("Camera");
+ if (aAudio)
+ expected.push("Microphone");
+ is((yield getMediaCaptureState()), expected.join("And"),
+ "expected stream to be shared");
+
+ // Check the attribute on the tab, and check there's no visible
+ // sharing icon on the tab
+ let tab = gBrowser.selectedTab;
+ is(tab.getAttribute("sharing"), aSharing,
+ "the tab has the attribute to show the " + aSharing + " icon");
+ let icon =
+ document.getAnonymousElementByAttribute(tab, "anonid", "sharing-icon");
+ is(window.getComputedStyle(icon).display, "none",
+ "the animated sharing icon of the tab is hidden");
+
+ // After selecting a new tab, check the attribute is still there,
+ // and the icon is now visible.
+ yield BrowserTestUtils.switchTab(gBrowser, gBrowser.addTab());
+ is(gBrowser.selectedTab.getAttribute("sharing"), "",
+ "the new tab doesn't have the 'sharing' attribute");
+ is(tab.getAttribute("sharing"), aSharing,
+ "the tab still has the 'sharing' attribute");
+ isnot(window.getComputedStyle(icon).display, "none",
+ "the animated sharing icon of the tab is now visible");
+
+ // Ensure the icon disappears when selecting the tab.
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ ok(tab.selected, "the tab with ongoing sharing is selected again");
+ is(window.getComputedStyle(icon).display, "none",
+ "the animated sharing icon is gone after selecting the tab again");
+
+ // And finally verify the attribute is removed when closing the stream.
+ yield closeStream();
+
+ // TODO(Bug 1304997): Fix the race in closeStream() and remove this
+ // promiseWaitForCondition().
+ yield promiseWaitForCondition(() => !tab.getAttribute("sharing"));
+ is(tab.getAttribute("sharing"), "",
+ "the tab no longer has the 'sharing' attribute after closing the stream");
+ }
+
+ yield getStreamAndCheckBackgroundAnim(true, true, "camera");
+ yield getStreamAndCheckBackgroundAnim(false, true, "camera");
+ yield getStreamAndCheckBackgroundAnim(true, false, "microphone");
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ content.location = rootDir + "get_user_media.html";
+}
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js
new file mode 100644
index 000000000..01a544aae
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_in_frame.js
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+function promiseReloadFrame(aFrameId) {
+ return ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) {
+ content.wrappedJSObject.document.getElementById(aFrameId).contentWindow.location.reload();
+ });
+}
+
+var gTests = [
+
+{
+ desc: "getUserMedia audio+video",
+ run: function* checkAudioVideo() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+
+ is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
+ "webRTC-shareDevices-notification-icon", "anchored to device icon");
+ checkDeviceSelectors(true, true);
+ is(PopupNotifications.panel.firstChild.getAttribute("popupid"),
+ "webRTC-shareDevices", "panel using devices icon");
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({audio: true, video: true});
+ yield closeStream(false, "frame1");
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: stop sharing",
+ run: function* checkStopSharing() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ activateSecondaryAction(kActionAlways);
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ let Perms = Services.perms;
+ let uri = Services.io.newURI("https://example.com/", null, null);
+ is(Perms.testExactPermission(uri, "microphone"), Perms.ALLOW_ACTION,
+ "microphone persistently allowed");
+ is(Perms.testExactPermission(uri, "camera"), Perms.ALLOW_ACTION,
+ "camera persistently allowed");
+
+ yield stopSharing();
+
+ // The persistent permissions for the frame should have been removed.
+ is(Perms.testExactPermission(uri, "microphone"), Perms.UNKNOWN_ACTION,
+ "microphone not persistently allowed");
+ is(Perms.testExactPermission(uri, "camera"), Perms.UNKNOWN_ACTION,
+ "camera not persistently allowed");
+
+ // the stream is already closed, but this will do some cleanup anyway
+ yield closeStream(true, "frame1");
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the frame removes all sharing UI",
+ run: function* checkReloading() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("reloading the frame");
+ promise = promiseObserverCalled("recording-device-events");
+ yield promiseReloadFrame("frame1");
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the frame removes prompts",
+ run: function* checkReloadingRemovesPrompts() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ info("reloading the frame");
+ promise = promiseObserverCalled("recording-window-ended");
+ yield promiseReloadFrame("frame1");
+ yield promise;
+ yield promiseNoPopupNotification("webRTC-shareDevices");
+
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading a frame updates the sharing UI",
+ run: function* checkUpdateWhenReloading() {
+ // We'll share only the mic in the first frame, then share both in the
+ // second frame, then reload the second frame. After each step, we'll check
+ // the UI is in the correct state.
+
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, false, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, false);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "Microphone", "microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: false, audio: true});
+ yield expectNoObserverCalled();
+
+ promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame2");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield checkSharingUI({video: true, audio: true});
+ yield expectNoObserverCalled();
+
+ info("reloading the second frame");
+ promise = promiseObserverCalled("recording-device-events");
+ yield promiseReloadFrame("frame2");
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield checkSharingUI({video: false, audio: true});
+ yield expectNoObserverCalled();
+
+ yield closeStream(false, "frame1");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+},
+
+{
+ desc: "getUserMedia audio+video: reloading the top level page removes all sharing UI",
+ run: function* checkReloading() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true, "frame1");
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("reloading the web page");
+ promise = promiseObserverCalled("recording-device-events");
+ content.location.reload();
+ yield promise;
+
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+
+ // Cleanup before the next test
+ yield expectNoObserverCalled();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ let url = rootDir + "get_user_media.html";
+ content.location = 'data:text/html,<iframe id="frame1" src="' + url + '"></iframe><iframe id="frame2" src="' + url + '"></iframe>'
+}
diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
new file mode 100644
index 000000000..b19065371
--- /dev/null
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
@@ -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/. */
+
+registerCleanupFunction(function() {
+ gBrowser.removeCurrentTab();
+});
+
+var gTests = [
+
+{
+ desc: "getUserMedia: tearing-off a tab keeps sharing indicators",
+ run: function* checkTearingOff() {
+ let promise = promisePopupNotificationShown("webRTC-shareDevices");
+ yield promiseRequestDevice(true, true);
+ yield promise;
+ yield expectObserverCalled("getUserMedia:request");
+ checkDeviceSelectors(true, true);
+
+ let indicator = promiseIndicatorWindow();
+ yield promiseMessage("ok", () => {
+ PopupNotifications.panel.firstChild.button.click();
+ });
+ yield expectObserverCalled("getUserMedia:response:allow");
+ yield expectObserverCalled("recording-device-events");
+ is((yield getMediaCaptureState()), "CameraAndMicrophone",
+ "expected camera and microphone to be shared");
+
+ yield indicator;
+ yield checkSharingUI({video: true, audio: true});
+
+ info("tearing off the tab");
+ let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ yield whenDelayedStartupFinished(win);
+ yield checkSharingUI({audio: true, video: true}, win);
+
+ // Clicking the global sharing indicator should open the control center in
+ // the second window.
+ ok(win.gIdentityHandler._identityPopup.hidden, "control center should be hidden");
+ let activeStreams = webrtcUI.getActiveStreams(true, false, false);
+ webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices");
+ ok(!win.gIdentityHandler._identityPopup.hidden,
+ "control center should be open in the second window");
+ ok(gIdentityHandler._identityPopup.hidden,
+ "control center should be hidden in the first window");
+ win.gIdentityHandler._identityPopup.hidden = true;
+
+ // Closing the new window should remove all sharing indicators.
+ // We need to load the content script in the first window so that we can
+ // catch the notifications fired globally when closing the second window.
+ gBrowser.selectedBrowser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ let promises = [promiseObserverCalled("recording-device-events"),
+ promiseObserverCalled("recording-window-ended")];
+ yield BrowserTestUtils.closeWindow(win);
+ yield Promise.all(promises);
+
+ yield expectNoObserverCalled();
+ yield checkNotSharing();
+ }
+}
+
+];
+
+function test() {
+ waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, runTest);
+}
+
+function runTest() {
+ // An empty tab where we can load the content script without leaving it
+ // behind at the end of the test.
+ gBrowser.addTab();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
+
+ browser.addEventListener("load", function onload() {
+ browser.removeEventListener("load", onload, true);
+
+ is(PopupNotifications._currentNotifications.length, 0,
+ "should start the test without any prior popup notification");
+ ok(gIdentityHandler._identityPopup.hidden,
+ "should start the test with the control center hidden");
+
+ Task.spawn(function* () {
+ yield SpecialPowers.pushPrefEnv({"set": [[PREF_PERMISSION_FAKE, true]]});
+
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+
+ // Cleanup before the next test
+ yield expectNoObserverCalled();
+ }
+ }).then(finish, ex => {
+ Cu.reportError(ex);
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ rootDir = rootDir.replace("chrome://mochitests/content/",
+ "https://example.com/");
+ content.location = rootDir + "get_user_media.html";
+}
diff --git a/browser/base/content/test/webrtc/get_user_media.html b/browser/base/content/test/webrtc/get_user_media.html
new file mode 100644
index 000000000..16303c62d
--- /dev/null
+++ b/browser/base/content/test/webrtc/get_user_media.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="UTF-8"></head>
+<body>
+<div id="message"></div>
+<script>
+// Specifies whether we are using fake streams to run this automation
+var useFakeStreams = true;
+try {
+ var audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev");
+ var videoDevice = SpecialPowers.getCharPref("media.video_loopback_dev");
+ dump("TEST DEVICES: Using media devices:\n");
+ dump("audio: " + audioDevice + "\nvideo: " + videoDevice + "\n");
+ useFakeStreams = false;
+} catch (e) {
+ dump("TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n");
+ useFakeStreams = true;
+}
+
+function message(m) {
+ document.getElementById("message").innerHTML = m;
+ window.parent.postMessage(m, "*");
+}
+
+var gStream;
+
+function requestDevice(aAudio, aVideo, aShare) {
+ var opts = {video: aVideo, audio: aAudio};
+ if (aShare) {
+ opts.video = {
+ mozMediaSource: aShare,
+ mediaSource: aShare
+ }
+ } else if (useFakeStreams) {
+ opts.fake = true;
+ }
+
+ window.navigator.mediaDevices.getUserMedia(opts)
+ .then(stream => {
+ gStream = stream;
+ message("ok");
+ }, err => message("error: " + err));
+}
+message("pending");
+
+function closeStream() {
+ if (!gStream)
+ return;
+ gStream.getTracks().forEach(t => t.stop());
+ gStream = null;
+ message("closed");
+}
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/webrtc/get_user_media_content_script.js b/browser/base/content/test/webrtc/get_user_media_content_script.js
new file mode 100644
index 000000000..71b68d826
--- /dev/null
+++ b/browser/base/content/test/webrtc/get_user_media_content_script.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
+ "@mozilla.org/mediaManagerService;1",
+ "nsIMediaManagerService");
+
+const kObservedTopics = [
+ "getUserMedia:response:allow",
+ "getUserMedia:revoke",
+ "getUserMedia:response:deny",
+ "getUserMedia:request",
+ "recording-device-events",
+ "recording-window-ended"
+];
+
+var gObservedTopics = {};
+function observer(aSubject, aTopic, aData) {
+ if (!(aTopic in gObservedTopics))
+ gObservedTopics[aTopic] = 1;
+ else
+ ++gObservedTopics[aTopic];
+}
+
+kObservedTopics.forEach(topic => {
+ Services.obs.addObserver(observer, topic, false);
+});
+
+addMessageListener("Test:ExpectObserverCalled", ({data}) => {
+ sendAsyncMessage("Test:ExpectObserverCalled:Reply",
+ {count: gObservedTopics[data]});
+ if (data in gObservedTopics)
+ --gObservedTopics[data];
+});
+
+addMessageListener("Test:ExpectNoObserverCalled", data => {
+ sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics);
+ gObservedTopics = {};
+});
+
+function _getMediaCaptureState() {
+ let hasVideo = {};
+ let hasAudio = {};
+ let hasScreenShare = {};
+ let hasWindowShare = {};
+ MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio,
+ hasScreenShare, hasWindowShare);
+ if (hasVideo.value && hasAudio.value)
+ return "CameraAndMicrophone";
+ if (hasVideo.value)
+ return "Camera";
+ if (hasAudio.value)
+ return "Microphone";
+ if (hasScreenShare.value)
+ return "Screen";
+ if (hasWindowShare.value)
+ return "Window";
+ return "none";
+}
+
+addMessageListener("Test:GetMediaCaptureState", data => {
+ sendAsyncMessage("Test:MediaCaptureState", _getMediaCaptureState());
+});
+
+addMessageListener("Test:WaitForObserverCall", ({data}) => {
+ let topic = data;
+ Services.obs.addObserver(function observer() {
+ sendAsyncMessage("Test:ObserverCalled", topic);
+ Services.obs.removeObserver(observer, topic);
+
+ if (kObservedTopics.indexOf(topic) != -1) {
+ if (!(topic in gObservedTopics))
+ gObservedTopics[topic] = -1;
+ else
+ --gObservedTopics[topic];
+ }
+ }, topic, false);
+});
+
+addMessageListener("Test:WaitForMessage", () => {
+ content.addEventListener("message", ({data}) => {
+ sendAsyncMessage("Test:MessageReceived", data);
+ }, {once: true});
+});
diff --git a/browser/base/content/test/webrtc/head.js b/browser/base/content/test/webrtc/head.js
new file mode 100644
index 000000000..70b183773
--- /dev/null
+++ b/browser/base/content/test/webrtc/head.js
@@ -0,0 +1,453 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
+const CONTENT_SCRIPT_HELPER = getRootDirectory(gTestPath) + "get_user_media_content_script.js";
+
+function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
+ retryTimes = typeof retryTimes !== 'undefined' ? retryTimes : 30;
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= retryTimes) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+function promiseWaitForCondition(aConditionFn) {
+ let deferred = Promise.defer();
+ waitForCondition(aConditionFn, deferred.resolve, "Condition didn't pass.");
+ return deferred.promise;
+}
+
+/**
+ * Waits for a window with the given URL to exist.
+ *
+ * @param url
+ * The url of the window.
+ * @return {Promise} resolved when the window exists.
+ * @resolves to the window
+ */
+function promiseWindow(url) {
+ info("expecting a " + url + " window");
+ return new Promise(resolve => {
+ Services.obs.addObserver(function obs(win) {
+ win.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function loadHandler() {
+ win.removeEventListener("load", loadHandler);
+
+ if (win.location.href !== url) {
+ info("ignoring a window with this url: " + win.location.href);
+ return;
+ }
+
+ Services.obs.removeObserver(obs, "domwindowopened");
+ resolve(win);
+ });
+ }, "domwindowopened", false);
+ });
+}
+
+function whenDelayedStartupFinished(aWindow) {
+ return new Promise(resolve => {
+ info("Waiting for delayed startup to finish");
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+}
+
+function promiseIndicatorWindow() {
+ // We don't show the indicator window on Mac.
+ if ("nsISystemStatusBar" in Ci)
+ return Promise.resolve();
+
+ return promiseWindow("chrome://browser/content/webrtcIndicator.xul");
+}
+
+function* assertWebRTCIndicatorStatus(expected) {
+ let ui = Cu.import("resource:///modules/webrtcUI.jsm", {}).webrtcUI;
+ let expectedState = expected ? "visible" : "hidden";
+ let msg = "WebRTC indicator " + expectedState;
+ if (!expected && ui.showGlobalIndicator) {
+ // It seems the global indicator is not always removed synchronously
+ // in some cases.
+ info("waiting for the global indicator to be hidden");
+ yield promiseWaitForCondition(() => !ui.showGlobalIndicator);
+ }
+ is(ui.showGlobalIndicator, !!expected, msg);
+
+ let expectVideo = false, expectAudio = false, expectScreen = false;
+ if (expected) {
+ if (expected.video)
+ expectVideo = true;
+ if (expected.audio)
+ expectAudio = true;
+ if (expected.screen)
+ expectScreen = true;
+ }
+ is(ui.showCameraIndicator, expectVideo, "camera global indicator as expected");
+ is(ui.showMicrophoneIndicator, expectAudio, "microphone global indicator as expected");
+ is(ui.showScreenSharingIndicator, expectScreen, "screen global indicator as expected");
+
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ let menu = win.document.getElementById("tabSharingMenu");
+ is(menu && !menu.hidden, !!expected, "WebRTC menu should be " + expectedState);
+ }
+
+ if (!("nsISystemStatusBar" in Ci)) {
+ if (!expected) {
+ let win = Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator");
+ if (win) {
+ yield new Promise((resolve, reject) => {
+ win.addEventListener("unload", function listener(e) {
+ if (e.target == win.document) {
+ win.removeEventListener("unload", listener);
+ resolve();
+ }
+ }, false);
+ });
+ }
+ }
+ let indicator = Services.wm.getEnumerator("Browser:WebRTCGlobalIndicator");
+ let hasWindow = indicator.hasMoreElements();
+ is(hasWindow, !!expected, "popup " + msg);
+ if (hasWindow) {
+ let document = indicator.getNext().document;
+ let docElt = document.documentElement;
+
+ if (document.readyState != "complete") {
+ info("Waiting for the sharing indicator's document to load");
+ let deferred = Promise.defer();
+ document.addEventListener("readystatechange",
+ function onReadyStateChange() {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", onReadyStateChange);
+ deferred.resolve();
+ });
+ yield deferred.promise;
+ }
+
+ for (let item of ["video", "audio", "screen"]) {
+ let expectedValue = (expected && expected[item]) ? "true" : "";
+ is(docElt.getAttribute("sharing" + item), expectedValue,
+ item + " global indicator attribute as expected");
+ }
+
+ ok(!indicator.hasMoreElements(), "only one global indicator window");
+ }
+ }
+}
+
+function promisePopupEvent(popup, eventSuffix) {
+ let endState = {shown: "open", hidden: "closed"}[eventSuffix];
+
+ if (popup.state == endState)
+ return Promise.resolve();
+
+ let eventType = "popup" + eventSuffix;
+ let deferred = Promise.defer();
+ popup.addEventListener(eventType, function onPopupShown(event) {
+ popup.removeEventListener(eventType, onPopupShown);
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function promiseNotificationShown(notification) {
+ let win = notification.browser.ownerGlobal;
+ if (win.PopupNotifications.panel.state == "open") {
+ return Promise.resolve();
+ }
+ let panelPromise = promisePopupEvent(win.PopupNotifications.panel, "shown");
+ notification.reshow();
+ return panelPromise;
+}
+
+function _mm() {
+ return gBrowser.selectedBrowser.messageManager;
+}
+
+function promiseObserverCalled(aTopic) {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:ObserverCalled", function listener({data}) {
+ if (data == aTopic) {
+ ok(true, "got " + aTopic + " notification");
+ mm.removeMessageListener("Test:ObserverCalled", listener);
+ resolve();
+ }
+ });
+ mm.sendAsyncMessage("Test:WaitForObserverCall", aTopic);
+ });
+}
+
+function expectObserverCalled(aTopic) {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:ExpectObserverCalled:Reply",
+ function listener({data}) {
+ is(data.count, 1, "expected notification " + aTopic);
+ mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
+ resolve();
+ });
+ mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
+ });
+}
+
+function expectNoObserverCalled() {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
+ function listener({data}) {
+ mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
+ for (let topic in data) {
+ if (data[topic])
+ is(data[topic], 0, topic + " notification unexpected");
+ }
+ resolve();
+ });
+ mm.sendAsyncMessage("Test:ExpectNoObserverCalled");
+ });
+}
+
+function promiseMessage(aMessage, aAction) {
+ let promise = new Promise((resolve, reject) => {
+ let mm = _mm();
+ mm.addMessageListener("Test:MessageReceived", function listener({data}) {
+ is(data, aMessage, "received " + aMessage);
+ if (data == aMessage)
+ resolve();
+ else
+ reject();
+ mm.removeMessageListener("Test:MessageReceived", listener);
+ });
+ mm.sendAsyncMessage("Test:WaitForMessage");
+ });
+
+ if (aAction)
+ aAction();
+
+ return promise;
+}
+
+function promisePopupNotificationShown(aName, aAction) {
+ let deferred = Promise.defer();
+
+ PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() {
+ PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown);
+
+ ok(!!PopupNotifications.getNotification(aName), aName + " notification shown");
+ ok(PopupNotifications.isPanelOpen, "notification panel open");
+ ok(!!PopupNotifications.panel.firstChild, "notification panel populated");
+
+ deferred.resolve();
+ });
+
+ if (aAction)
+ aAction();
+
+ return deferred.promise;
+}
+
+function promisePopupNotification(aName) {
+ let deferred = Promise.defer();
+
+ waitForCondition(() => PopupNotifications.getNotification(aName),
+ () => {
+ ok(!!PopupNotifications.getNotification(aName),
+ aName + " notification appeared");
+
+ deferred.resolve();
+ }, "timeout waiting for popup notification " + aName);
+
+ return deferred.promise;
+}
+
+function promiseNoPopupNotification(aName) {
+ let deferred = Promise.defer();
+
+ waitForCondition(() => !PopupNotifications.getNotification(aName),
+ () => {
+ ok(!PopupNotifications.getNotification(aName),
+ aName + " notification removed");
+ deferred.resolve();
+ }, "timeout waiting for popup notification " + aName + " to disappear");
+
+ return deferred.promise;
+}
+
+const kActionAlways = 1;
+const kActionDeny = 2;
+const kActionNever = 3;
+
+function activateSecondaryAction(aAction) {
+ let notification = PopupNotifications.panel.firstChild;
+ notification.button.focus();
+ let popup = notification.menupopup;
+ popup.addEventListener("popupshown", function () {
+ popup.removeEventListener("popupshown", arguments.callee, false);
+
+ // Press 'down' as many time as needed to select the requested action.
+ while (aAction--)
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ // Activate
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }, false);
+
+ // One down event to open the popup
+ EventUtils.synthesizeKey("VK_DOWN",
+ { altKey: !navigator.platform.includes("Mac") });
+}
+
+function getMediaCaptureState() {
+ return new Promise(resolve => {
+ let mm = _mm();
+ mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
+ resolve(data);
+ });
+ mm.sendAsyncMessage("Test:GetMediaCaptureState");
+ });
+}
+
+function* stopSharing(aType = "camera") {
+ let promiseRecordingEvent = promiseObserverCalled("recording-device-events");
+ gIdentityHandler._identityBox.click();
+ let permissions = document.getElementById("identity-popup-permission-list");
+ let cancelButton =
+ permissions.querySelector(".identity-popup-permission-icon." + aType + "-icon ~ " +
+ ".identity-popup-permission-remove-button");
+ cancelButton.click();
+ gIdentityHandler._identityPopup.hidden = true;
+ yield promiseRecordingEvent;
+ yield expectObserverCalled("getUserMedia:revoke");
+ yield expectObserverCalled("recording-window-ended");
+ yield expectNoObserverCalled();
+ yield* checkNotSharing();
+}
+
+function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId, aType) {
+ info("requesting devices");
+ return ContentTask.spawn(gBrowser.selectedBrowser,
+ {aRequestAudio, aRequestVideo, aFrameId, aType},
+ function*(args) {
+ let global = content.wrappedJSObject;
+ if (args.aFrameId)
+ global = global.document.getElementById(args.aFrameId).contentWindow;
+ global.requestDevice(args.aRequestAudio, args.aRequestVideo, args.aType);
+ });
+}
+
+function* closeStream(aAlreadyClosed, aFrameId) {
+ yield expectNoObserverCalled();
+
+ let promises;
+ if (!aAlreadyClosed) {
+ promises = [promiseObserverCalled("recording-device-events"),
+ promiseObserverCalled("recording-window-ended")];
+ }
+
+ info("closing the stream");
+ yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) {
+ let global = content.wrappedJSObject;
+ if (aFrameId)
+ global = global.document.getElementById(aFrameId).contentWindow;
+ global.closeStream();
+ });
+
+ if (promises)
+ yield Promise.all(promises);
+
+ yield* assertWebRTCIndicatorStatus(null);
+}
+
+function checkDeviceSelectors(aAudio, aVideo) {
+ let micSelector = document.getElementById("webRTC-selectMicrophone");
+ if (aAudio)
+ ok(!micSelector.hidden, "microphone selector visible");
+ else
+ ok(micSelector.hidden, "microphone selector hidden");
+
+ let cameraSelector = document.getElementById("webRTC-selectCamera");
+ if (aVideo)
+ ok(!cameraSelector.hidden, "camera selector visible");
+ else
+ ok(cameraSelector.hidden, "camera selector hidden");
+}
+
+function* checkSharingUI(aExpected, aWin = window) {
+ let doc = aWin.document;
+ // First check the icon above the control center (i) icon.
+ let identityBox = doc.getElementById("identity-box");
+ ok(identityBox.hasAttribute("sharing"), "sharing attribute is set");
+ let sharing = identityBox.getAttribute("sharing");
+ if (aExpected.video)
+ is(sharing, "camera", "showing camera icon on the control center icon");
+ else if (aExpected.audio)
+ is(sharing, "microphone", "showing mic icon on the control center icon");
+
+ // Then check the sharing indicators inside the control center panel.
+ identityBox.click();
+ let permissions = doc.getElementById("identity-popup-permission-list");
+ for (let id of ["microphone", "camera", "screen"]) {
+ let convertId = id => {
+ if (id == "camera")
+ return "video";
+ if (id == "microphone")
+ return "audio";
+ return id;
+ };
+ let expected = aExpected[convertId(id)];
+ is(!!aWin.gIdentityHandler._sharingState[id], !!expected,
+ "sharing state for " + id + " as expected");
+ let icon = permissions.querySelectorAll(
+ ".identity-popup-permission-icon." + id + "-icon");
+ if (expected) {
+ is(icon.length, 1, "should show " + id + " icon in control center panel");
+ ok(icon[0].classList.contains("in-use"), "icon should have the in-use class");
+ } else if (!icon.length) {
+ ok(true, "should not show " + id + " icon in the control center panel");
+ } else {
+ // This will happen if there are persistent permissions set.
+ ok(!icon[0].classList.contains("in-use"),
+ "if shown, the " + id + " icon should not have the in-use class");
+ is(icon.length, 1, "should not show more than 1 " + id + " icon");
+ }
+ }
+ aWin.gIdentityHandler._identityPopup.hidden = true;
+
+ // Check the global indicators.
+ yield* assertWebRTCIndicatorStatus(aExpected);
+}
+
+function* checkNotSharing() {
+ is((yield getMediaCaptureState()), "none", "expected nothing to be shared");
+
+ ok(!document.getElementById("identity-box").hasAttribute("sharing"),
+ "no sharing indicator on the control center icon");
+
+ yield* assertWebRTCIndicatorStatus(null);
+}
diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml
new file mode 100644
index 000000000..84ed693ff
--- /dev/null
+++ b/browser/base/content/urlbarBindings.xml
@@ -0,0 +1,2740 @@
+<?xml version="1.0"?>
+
+<!--
+-*- Mode: HTML -*-
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+
+<!DOCTYPE bindings [
+<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
+%notificationDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+]>
+
+<bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+
+ <content sizetopopup="pref">
+ <xul:hbox anonid="textbox-container"
+ class="autocomplete-textbox-container urlbar-textbox-container"
+ flex="1" xbl:inherits="focused">
+ <children includes="image|deck|stack|box">
+ <xul:image class="autocomplete-icon" allowevents="true"/>
+ </children>
+ <xul:hbox anonid="textbox-input-box"
+ class="textbox-input-box urlbar-input-box"
+ flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
+ <children/>
+ <html:input anonid="input"
+ class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
+ allowevents="true"
+ inputmode="url"
+ xbl:inherits="tooltiptext=inputtooltiptext,value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
+ </xul:hbox>
+ <xul:dropmarker anonid="historydropmarker"
+ class="autocomplete-history-dropmarker urlbar-history-dropmarker"
+ tooltiptext="&urlbar.openHistoryPopup.tooltip;"
+ allowevents="true"
+ xbl:inherits="open,enablehistory,parentfocused=focused"/>
+ <children includes="hbox"/>
+ </xul:hbox>
+ <xul:popupset anonid="popupset"
+ class="autocomplete-result-popupset"/>
+ <children includes="toolbarbutton"/>
+ </content>
+
+ <implementation implements="nsIObserver, nsIDOMEventListener">
+ <field name="AppConstants" readonly="true">
+ (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
+ </field>
+
+ <field name="ExtensionSearchHandler" readonly="true">
+ (Components.utils.import("resource://gre/modules/ExtensionSearchHandler.jsm", {})).ExtensionSearchHandler;
+ </field>
+
+ <constructor><![CDATA[
+ this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch("browser.urlbar.");
+
+ this._prefs.addObserver("", this, false);
+ this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
+ this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
+ this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
+ this.timeout = this._prefs.getIntPref("delay");
+ this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
+ this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
+ this._cacheUserMadeSearchSuggestionsChoice();
+ this.inputField.controllers.insertControllerAt(0, this._copyCutController);
+ this.inputField.addEventListener("paste", this, false);
+ this.inputField.addEventListener("mousedown", this, false);
+ this.inputField.addEventListener("mousemove", this, false);
+ this.inputField.addEventListener("mouseout", this, false);
+ this.inputField.addEventListener("overflow", this, false);
+ this.inputField.addEventListener("underflow", this, false);
+
+ var textBox = document.getAnonymousElementByAttribute(this,
+ "anonid", "textbox-input-box");
+ var cxmenu = document.getAnonymousElementByAttribute(textBox,
+ "anonid", "input-box-contextmenu");
+ var pasteAndGo;
+ cxmenu.addEventListener("popupshowing", function() {
+ if (!pasteAndGo)
+ return;
+ var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+ var enabled = controller.isCommandEnabled("cmd_paste");
+ if (enabled)
+ pasteAndGo.removeAttribute("disabled");
+ else
+ pasteAndGo.setAttribute("disabled", "true");
+ }, false);
+
+ var insertLocation = cxmenu.firstChild;
+ while (insertLocation.nextSibling &&
+ insertLocation.getAttribute("cmd") != "cmd_paste")
+ insertLocation = insertLocation.nextSibling;
+ if (insertLocation) {
+ pasteAndGo = document.createElement("menuitem");
+ let label = Services.strings.createBundle("chrome://browser/locale/browser.properties").
+ GetStringFromName("pasteAndGo.label");
+ pasteAndGo.setAttribute("label", label);
+ pasteAndGo.setAttribute("anonid", "paste-and-go");
+ pasteAndGo.setAttribute("oncommand",
+ "gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
+ cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
+ }
+
+ this._enableOrDisableOneOffSearches();
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._prefs.removeObserver("", this);
+ this._prefs = null;
+ this.inputField.controllers.removeController(this._copyCutController);
+ this.inputField.removeEventListener("paste", this, false);
+ this.inputField.removeEventListener("mousedown", this, false);
+ this.inputField.removeEventListener("mousemove", this, false);
+ this.inputField.removeEventListener("mouseout", this, false);
+ this.inputField.removeEventListener("overflow", this, false);
+ this.inputField.removeEventListener("underflow", this, false);
+ ]]></destructor>
+
+ <field name="_value">""</field>
+ <field name="gotResultForCurrentQuery">false</field>
+
+ <!--
+ This is set around HandleHenter so it can be used in handleCommand.
+ It is also used to track whether we must handle a delayed handleEnter,
+ by checking if it has been cleared.
+ -->
+ <field name="handleEnterInstance">null</field>
+
+ <!--
+ For performance reasons we want to limit the size of the text runs we
+ build and show to the user.
+ -->
+ <field name="textRunsMaxLen">255</field>
+
+ <!--
+ onBeforeValueGet is called by the base-binding's .value getter.
+ It can return an object with a "value" property, to override the
+ return value of the getter.
+ -->
+ <method name="onBeforeValueGet">
+ <body><![CDATA[
+ return { value: this._value };
+ ]]></body>
+ </method>
+
+ <!--
+ onBeforeValueSet is called by the base-binding's .value setter.
+ It should return the value that the setter should use.
+ -->
+ <method name="onBeforeValueSet">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this._value = aValue;
+ var returnValue = aValue;
+ var action = this._parseActionUrl(aValue);
+
+ if (action) {
+ switch (action.type) {
+ case "switchtab": // Fall through.
+ case "remotetab": // Fall through.
+ case "visiturl": {
+ returnValue = action.params.displayUrl;
+ break;
+ }
+ case "keyword": // Fall through.
+ case "searchengine": {
+ returnValue = action.params.input;
+ break;
+ }
+ case "extension": {
+ returnValue = action.params.content;
+ break;
+ }
+ }
+ } else {
+ let originalUrl = ReaderMode.getOriginalUrl(aValue);
+ if (originalUrl) {
+ returnValue = originalUrl;
+ }
+ }
+
+ // Set the actiontype only if the user is not overriding actions.
+ if (action && this._pressedNoActionKeys.size == 0) {
+ this.setAttribute("actiontype", action.type);
+ } else {
+ this.removeAttribute("actiontype");
+ }
+ return returnValue;
+ ]]></body>
+ </method>
+
+ <method name="onKeyPress">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_LEFT:
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_HOME:
+ // Reset the selected index so that nsAutoCompleteController
+ // simply closes the popup without trying to fill anything.
+ this.popup.selectedIndex = -1;
+ break;
+ }
+ if (this.popup.popupOpen &&
+ !this.popup.disableKeyNavigation &&
+ this.popup.handleKeyPress(aEvent)) {
+ return true;
+ }
+ return this.handleKeyPress(aEvent);
+ ]]></body>
+ </method>
+
+ <field name="_mayTrimURLs">true</field>
+ <method name="trimValue">
+ <parameter name="aURL"/>
+ <body><![CDATA[
+ // This method must not modify the given URL such that calling
+ // nsIURIFixup::createFixupURI with the result will produce a different URI.
+ return this._mayTrimURLs ? trimURL(aURL) : aURL;
+ ]]></body>
+ </method>
+
+ <field name="_formattingEnabled">true</field>
+ <method name="formatValue">
+ <body><![CDATA[
+ if (!this._formattingEnabled || !this.editor)
+ return;
+
+ let controller = this.editor.selectionController;
+ let strikeOut = controller.getSelection(controller.SELECTION_URLSTRIKEOUT);
+ strikeOut.removeAllRanges();
+
+ let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+ selection.removeAllRanges();
+
+ if (this.focused)
+ return;
+
+ let textNode = this.editor.rootElement.firstChild;
+ let value = textNode.textContent;
+ if (!value)
+ return;
+
+ // Get the URL from the fixup service:
+ let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ let uriInfo;
+ try {
+ uriInfo = Services.uriFixup.getFixupURIInfo(value, flags);
+ } catch (ex) {}
+ // Ignore if we couldn't make a URI out of this, the URI resulted in a search,
+ // or the URI has a non-http(s)/ftp protocol.
+ if (!uriInfo ||
+ !uriInfo.fixedURI ||
+ uriInfo.keywordProviderName ||
+ ["http", "https", "ftp"].indexOf(uriInfo.fixedURI.scheme) == -1) {
+ return;
+ }
+
+ // If we trimmed off the http scheme, ensure we stick it back on before
+ // trying to figure out what domain we're accessing, so we don't get
+ // confused by user:pass@host http URLs. We later use
+ // trimmedLength to ensure we don't count the length of a trimmed protocol
+ // when determining which parts of the URL to highlight as "preDomain".
+ let trimmedLength = 0;
+ if (uriInfo.fixedURI.scheme == "http" && !value.startsWith("http://")) {
+ value = "http://" + value;
+ trimmedLength = "http://".length;
+ }
+
+ let matchedURL = value.match(/^((?:[a-z]+:\/\/)(?:[^\/#?]+@)?)(\S+?)(?::\d+)?\s*(?:[\/#?]|$)/);
+ if (!matchedURL)
+ return;
+
+ // Strike out the "https" part if mixed active content is loaded.
+ if (this.getAttribute("pageproxystate") == "valid" &&
+ value.startsWith("https:") &&
+ gBrowser.securityUI.state &
+ Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
+ let range = document.createRange();
+ range.setStart(textNode, 0);
+ range.setEnd(textNode, 5);
+ strikeOut.addRange(range);
+ }
+
+ let [, preDomain, domain] = matchedURL;
+ let baseDomain = domain;
+ let subDomain = "";
+ try {
+ baseDomain = Services.eTLD.getBaseDomainFromHost(uriInfo.fixedURI.host);
+ if (!domain.endsWith(baseDomain)) {
+ // getBaseDomainFromHost converts its resultant to ACE.
+ let IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ baseDomain = IDNService.convertACEtoUTF8(baseDomain);
+ }
+ } catch (e) {}
+ if (baseDomain != domain) {
+ subDomain = domain.slice(0, -baseDomain.length);
+ }
+
+ let rangeLength = preDomain.length + subDomain.length - trimmedLength;
+ if (rangeLength) {
+ let range = document.createRange();
+ range.setStart(textNode, 0);
+ range.setEnd(textNode, rangeLength);
+ selection.addRange(range);
+ }
+
+ let startRest = preDomain.length + domain.length - trimmedLength;
+ if (startRest < value.length - trimmedLength) {
+ let range = document.createRange();
+ range.setStart(textNode, startRest);
+ range.setEnd(textNode, value.length - trimmedLength);
+ selection.addRange(range);
+ }
+ ]]></body>
+ </method>
+
+ <method name="handleRevert">
+ <body><![CDATA[
+ var isScrolling = this.popupOpen;
+
+ gBrowser.userTypedValue = null;
+
+ // don't revert to last valid url unless page is NOT loading
+ // and user is NOT key-scrolling through autocomplete list
+ if (!XULBrowserWindow.isBusy && !isScrolling) {
+ URLBarSetURI();
+
+ // If the value isn't empty and the urlbar has focus, select the value.
+ if (this.value && this.hasAttribute("focused"))
+ this.select();
+ }
+
+ // tell widget to revert to last typed text only if the user
+ // was scrolling when they hit escape
+ return !isScrolling;
+ ]]></body>
+ </method>
+
+ <!--
+ This is ultimately called by the autocomplete controller as the result
+ of handleEnter when the Return key is pressed in the textbox. Since
+ onPopupClick also calls handleEnter, this is also called as a result in
+ that case.
+
+ @param event
+ The event that triggered the command.
+ @param openUILinkWhere
+ Optional. The "where" to pass to openUILinkIn. This method
+ computes the appropriate "where" given the event, but you can
+ use this to override it.
+ @param openUILinkParams
+ Optional. The parameters to pass to openUILinkIn. As with
+ "where", this method computes the appropriate parameters, but
+ any parameters you supply here will override those.
+ -->
+ <method name="handleCommand">
+ <parameter name="event"/>
+ <parameter name="openUILinkWhere"/>
+ <parameter name="openUILinkParams"/>
+ <body><![CDATA[
+ let isMouseEvent = event instanceof MouseEvent;
+ if (isMouseEvent && event.button == 2) {
+ // Do nothing for right clicks.
+ return;
+ }
+
+ // Determine whether to use the selected one-off search button. In
+ // one-off search buttons parlance, "selected" means that the button
+ // has been navigated to via the keyboard. So we want to use it if
+ // the triggering event is not a mouse click -- i.e., it's a Return
+ // key -- or if the one-off was mouse-clicked.
+ let selectedOneOff = this.popup.oneOffSearchButtons.selectedButton;
+ if (selectedOneOff &&
+ isMouseEvent &&
+ event.originalTarget != selectedOneOff) {
+ selectedOneOff = null;
+ }
+
+ // Do the command of the selected one-off if it's not an engine.
+ if (selectedOneOff && !selectedOneOff.engine) {
+ selectedOneOff.doCommand();
+ return;
+ }
+
+ let where = openUILinkWhere;
+ if (!where) {
+ if (isMouseEvent) {
+ where = whereToOpenLink(event, false, false);
+ } else {
+ // If the current tab is empty, ignore Alt+Enter (reuse this tab)
+ let altEnter = !isMouseEvent &&
+ event &&
+ event.altKey &&
+ !isTabEmpty(gBrowser.selectedTab);
+ where = altEnter ? "tab" : "current";
+ }
+ }
+
+ let url = this.value;
+ if (!url) {
+ return;
+ }
+
+ let mayInheritPrincipal = false;
+ let postData = null;
+ let browser = gBrowser.selectedBrowser;
+ let action = this._parseActionUrl(url);
+
+ if (selectedOneOff && selectedOneOff.engine) {
+ // If there's a selected one-off button then load a search using
+ // the one-off's engine.
+ [url, postData] =
+ this._parseAndRecordSearchEngineLoad(selectedOneOff.engine,
+ this.oneOffSearchQuery,
+ event, where,
+ openUILinkParams);
+ } else if (action) {
+ switch (action.type) {
+ case "visiturl":
+ // Unifiedcomplete uses fixupURI to tell if something is a visit
+ // or a search, and passes out the fixedURI as the url param.
+ // By using that uri we would end up passing a different string
+ // to the docshell that may run a different not-found heuristic.
+ // For example, "mozilla/run" would be fixed by unifiedcomplete
+ // to "http://mozilla/run". The docshell, once it can't resolve
+ // mozilla, would note the string has a scheme, and try to load
+ // http://mozilla.com/run instead of searching "mozilla/run".
+ // So, if we have the original input at hand, we pass it through
+ // and let the docshell handle it.
+ if (action.params.input) {
+ url = action.params.input;
+ break;
+ }
+ url = action.params.url;
+ break;
+ case "remotetab":
+ url = action.params.url;
+ break;
+ case "keyword":
+ if (action.params.postData) {
+ postData = getPostDataStream(action.params.postData);
+ }
+ mayInheritPrincipal = true;
+ url = action.params.url;
+ break;
+ case "switchtab":
+ url = action.params.url;
+ if (this.hasAttribute("actiontype")) {
+ this.handleRevert();
+ let prevTab = gBrowser.selectedTab;
+ if (switchToTabHavingURI(url) && isTabEmpty(prevTab)) {
+ gBrowser.removeTab(prevTab);
+ }
+ return;
+ }
+ break;
+ case "searchengine":
+ if (selectedOneOff && selectedOneOff.engine) {
+ // Replace the engine with the selected one-off engine.
+ action.params.engineName = selectedOneOff.engine.name;
+ }
+ const actionDetails = {
+ isSuggestion: !!action.params.searchSuggestion,
+ isAlias: !!action.params.alias
+ };
+ [url, postData] = this._parseAndRecordSearchEngineLoad(
+ action.params.engineName,
+ action.params.searchSuggestion || action.params.searchQuery,
+ event,
+ where,
+ openUILinkParams,
+ actionDetails
+ );
+ break;
+ case "extension":
+ this.handleRevert();
+ // Give the extension control of handling the command.
+ let searchString = action.params.content;
+ let keyword = action.params.keyword;
+ this.ExtensionSearchHandler.handleInputEntered(keyword, searchString, where);
+ return;
+ }
+ } else {
+ // This is a fallback for add-ons and old testing code that directly
+ // set value and try to confirm it. UnifiedComplete should always
+ // resolve to a valid url.
+ try {
+ new URL(url);
+ } catch (ex) {
+ let lastLocationChange = browser.lastLocationChange;
+ getShortcutOrURIAndPostData(url).then(data => {
+ if (where != "current" ||
+ browser.lastLocationChange == lastLocationChange) {
+ this._loadURL(data.url, browser, data.postData, where,
+ openUILinkParams, data.mayInheritPrincipal);
+ }
+ });
+ return;
+ }
+ }
+
+ this._loadURL(url, browser, postData, where, openUILinkParams,
+ mayInheritPrincipal);
+ ]]></body>
+ </method>
+
+ <property name="oneOffSearchQuery">
+ <getter><![CDATA[
+ // this.textValue may be an autofilled string. Search only with the
+ // portion that the user typed, if any, by preferring the autocomplete
+ // controller's searchString (including handleEnterInstance.searchString).
+ return (this.handleEnterInstance && this.handleEnterInstance.searchString) ||
+ this.mController.searchString ||
+ this.textValue;
+ ]]></getter>
+ </property>
+
+ <method name="_loadURL">
+ <parameter name="url"/>
+ <parameter name="browser"/>
+ <parameter name="postData"/>
+ <parameter name="openUILinkWhere"/>
+ <parameter name="openUILinkParams"/>
+ <parameter name="mayInheritPrincipal"/>
+ <body><![CDATA[
+ this.value = url;
+ browser.userTypedValue = url;
+ if (gInitialPages.includes(url)) {
+ browser.initialPageLoadedFromURLBar = url;
+ }
+ try {
+ addToUrlbarHistory(url);
+ } catch (ex) {
+ // Things may go wrong when adding url to session history,
+ // but don't let that interfere with the loading of the url.
+ Cu.reportError(ex);
+ }
+
+ let params = {
+ postData,
+ allowThirdPartyFixup: true,
+ currentBrowser: browser,
+ };
+ if (openUILinkWhere == "current") {
+ params.indicateErrorPageLoad = true;
+ params.allowPinnedTabHostChange = true;
+ params.disallowInheritPrincipal = !mayInheritPrincipal;
+ params.allowPopups = url.startsWith("javascript:");
+ } else {
+ params.initiatingDoc = document;
+ }
+
+ if (openUILinkParams) {
+ for (let key in openUILinkParams) {
+ params[key] = openUILinkParams[key];
+ }
+ }
+
+ // Focus the content area before triggering loads, since if the load
+ // occurs in a new tab, we want focus to be restored to the content
+ // area when the current tab is re-selected.
+ browser.focus();
+
+ if (openUILinkWhere != "current") {
+ this.handleRevert();
+ }
+
+ try {
+ openUILinkIn(url, openUILinkWhere, params);
+ } catch (ex) {
+ // This load can throw an exception in certain cases, which means
+ // we'll want to replace the URL with the loaded URL:
+ if (ex.result != Cr.NS_ERROR_LOAD_SHOWED_ERRORPAGE) {
+ this.handleRevert();
+ }
+ }
+
+ if (openUILinkWhere == "current") {
+ // Ensure the start of the URL is visible for usability reasons.
+ this.selectionStart = this.selectionEnd = 0;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_parseAndRecordSearchEngineLoad">
+ <parameter name="engineOrEngineName"/>
+ <parameter name="query"/>
+ <parameter name="event"/>
+ <parameter name="openUILinkWhere"/>
+ <parameter name="openUILinkParams"/>
+ <parameter name="searchActionDetails"/>
+ <body><![CDATA[
+ let engine =
+ typeof(engineOrEngineName) == "string" ?
+ Services.search.getEngineByName(engineOrEngineName) :
+ engineOrEngineName;
+ let isOneOff = this.popup.oneOffSearchButtons
+ .maybeRecordTelemetry(event, openUILinkWhere, openUILinkParams);
+ // Infer the type of the event which triggered the search.
+ let eventType = "unknown";
+ if (event instanceof KeyboardEvent) {
+ eventType = "key";
+ } else if (event instanceof MouseEvent) {
+ eventType = "mouse";
+ }
+ // Augment the search action details object.
+ let details = searchActionDetails || {};
+ details.isOneOff = isOneOff;
+ details.type = eventType;
+
+ BrowserSearch.recordSearchInTelemetry(engine, "urlbar", details);
+ let submission = engine.getSubmission(query, null, "keyword");
+ return [submission.uri.spec, submission.postData];
+ ]]></body>
+ </method>
+
+ <method name="maybeCanonizeURL">
+ <parameter name="aTriggeringEvent"/>
+ <parameter name="aUrl"/>
+ <body><![CDATA[
+ // Only add the suffix when the URL bar value isn't already "URL-like",
+ // and only if we get a keyboard event, to match user expectations.
+ if (!/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(aUrl) ||
+ !(aTriggeringEvent instanceof KeyEvent)) {
+ return;
+ }
+
+ let url = aUrl;
+ let accel = this.AppConstants.platform == "macosx" ?
+ aTriggeringEvent.metaKey :
+ aTriggeringEvent.ctrlKey;
+ let shift = aTriggeringEvent.shiftKey;
+ let suffix = "";
+
+ switch (true) {
+ case (accel && shift):
+ suffix = ".org/";
+ break;
+ case (shift):
+ suffix = ".net/";
+ break;
+ case (accel):
+ try {
+ suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
+ if (suffix.charAt(suffix.length - 1) != "/")
+ suffix += "/";
+ } catch (e) {
+ suffix = ".com/";
+ }
+ break;
+ }
+
+ if (!suffix)
+ return;
+
+ // trim leading/trailing spaces (bug 233205)
+ url = url.trim();
+
+ // Tack www. and suffix on. If user has appended directories, insert
+ // suffix before them (bug 279035). Be careful not to get two slashes.
+ let firstSlash = url.indexOf("/");
+ if (firstSlash >= 0) {
+ url = url.substring(0, firstSlash) + suffix +
+ url.substring(firstSlash + 1);
+ } else {
+ url = url + suffix;
+ }
+
+ this.popup.overrideValue = "http://www." + url;
+ ]]></body>
+ </method>
+
+ <field name="_contentIsCropped">false</field>
+
+ <method name="_initURLTooltip">
+ <body><![CDATA[
+ if (this.focused || !this._contentIsCropped)
+ return;
+ this.inputField.setAttribute("tooltiptext", this.value);
+ ]]></body>
+ </method>
+
+ <method name="_hideURLTooltip">
+ <body><![CDATA[
+ this.inputField.removeAttribute("tooltiptext");
+ ]]></body>
+ </method>
+
+ <method name="onDragOver">
+ <parameter name="aEvent"/>
+ <body>
+ var types = aEvent.dataTransfer.types;
+ if (types.includes("application/x-moz-file") ||
+ types.includes("text/x-moz-url") ||
+ types.includes("text/uri-list") ||
+ types.includes("text/unicode"))
+ aEvent.preventDefault();
+ </body>
+ </method>
+
+ <method name="onDrop">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let links = browserDragAndDrop.dropLinks(aEvent);
+
+ // The URL bar automatically handles inputs with newline characters,
+ // so we can get away with treating text/x-moz-url flavours as text/plain.
+ if (links.length > 0 && links[0].url) {
+ let url = links[0].url;
+ aEvent.preventDefault();
+ this.value = url;
+ SetPageProxyState("invalid");
+ this.focus();
+ try {
+ urlSecurityCheck(url,
+ gBrowser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ } catch (ex) {
+ return;
+ }
+ this.handleCommand();
+ // Force not showing the dropped URI immediately.
+ gBrowser.userTypedValue = null;
+ URLBarSetURI();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getSelectedValueForClipboard">
+ <body><![CDATA[
+ // Grab the actual input field's value, not our value, which could include moz-action:
+ var inputVal = this.inputField.value;
+ var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
+
+ // If the selection doesn't start at the beginning or doesn't span the full domain or
+ // the URL bar is modified or there is no text at all, nothing else to do here.
+ if (this.selectionStart > 0 || this.valueIsTyped || selectedVal == "")
+ return selectedVal;
+ // The selection doesn't span the full domain if it doesn't contain a slash and is
+ // followed by some character other than a slash.
+ if (!selectedVal.includes("/")) {
+ let remainder = inputVal.replace(selectedVal, "");
+ if (remainder != "" && remainder[0] != "/")
+ return selectedVal;
+ }
+
+ let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
+
+ let uri;
+ if (this.getAttribute("pageproxystate") == "valid") {
+ uri = gBrowser.currentURI;
+ } else {
+ // We're dealing with an autocompleted value, create a new URI from that.
+ try {
+ uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
+ } catch (e) {}
+ if (!uri)
+ return selectedVal;
+ }
+
+ // Avoid copying 'about:reader?url=', and always provide the original URI:
+ let readerOriginalURL = ReaderMode.getOriginalUrl(uri.spec);
+ if (readerOriginalURL) {
+ uri = uriFixup.createFixupURI(readerOriginalURL, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
+ }
+
+ // Only copy exposable URIs
+ try {
+ uri = uriFixup.createExposableURI(uri);
+ } catch (ex) {}
+
+ // If the entire URL is selected, just use the actual loaded URI.
+ if (inputVal == selectedVal) {
+ // ... but only if isn't a javascript: or data: URI, since those
+ // are hard to read when encoded
+ if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
+ selectedVal = uri.spec;
+ }
+
+ return selectedVal;
+ }
+
+ // Just the beginning of the URL is selected, check for a trimmed
+ // value
+ let spec = uri.spec;
+ let trimmedSpec = this.trimValue(spec);
+ if (spec != trimmedSpec) {
+ // Prepend the portion that trimValue removed from the beginning.
+ // This assumes trimValue will only truncate the URL at
+ // the beginning or end (or both).
+ let trimmedSegments = spec.split(trimmedSpec);
+ selectedVal = trimmedSegments[0] + selectedVal;
+ }
+
+ return selectedVal;
+ ]]></body>
+ </method>
+
+ <field name="_copyCutController"><![CDATA[
+ ({
+ urlbar: this,
+ doCommand: function(aCommand) {
+ var urlbar = this.urlbar;
+ var val = urlbar._getSelectedValueForClipboard();
+ if (!val)
+ return;
+
+ if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
+ let start = urlbar.selectionStart;
+ let end = urlbar.selectionEnd;
+ urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
+ urlbar.inputField.value.substring(end);
+ urlbar.selectionStart = urlbar.selectionEnd = start;
+
+ let event = document.createEvent("UIEvents");
+ event.initUIEvent("input", true, false, window, 0);
+ urlbar.dispatchEvent(event);
+
+ SetPageProxyState("invalid");
+ }
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(val);
+ },
+ supportsCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_copy":
+ case "cmd_cut":
+ return true;
+ }
+ return false;
+ },
+ isCommandEnabled: function(aCommand) {
+ return this.supportsCommand(aCommand) &&
+ (aCommand != "cmd_cut" || !this.urlbar.readOnly) &&
+ this.urlbar.selectionStart < this.urlbar.selectionEnd;
+ },
+ onEvent: function(aEventName) {}
+ })
+ ]]></field>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aTopic == "nsPref:changed") {
+ switch (aData) {
+ case "clickSelectsAll":
+ case "doubleClickSelectsAll":
+ this[aData] = this._prefs.getBoolPref(aData);
+ break;
+ case "autoFill":
+ this.completeDefaultIndex = this._prefs.getBoolPref(aData);
+ break;
+ case "delay":
+ this.timeout = this._prefs.getIntPref(aData);
+ break;
+ case "formatting.enabled":
+ this._formattingEnabled = this._prefs.getBoolPref(aData);
+ break;
+ case "suggest.searches":
+ case "userMadeSearchSuggestionsChoice":
+ // Mirror the value for future use, see the comment in the
+ // binding's constructor.
+ this._prefs.setBoolPref("searchSuggestionsChoice",
+ this._prefs.getBoolPref("suggest.searches"));
+
+ this._cacheUserMadeSearchSuggestionsChoice();
+ if (this._userMadeSearchSuggestionsChoice) {
+ this.popup.searchSuggestionsNotificationWasDismissed(
+ this._prefs.getBoolPref("suggest.searches")
+ );
+ }
+ break;
+ case "trimURLs":
+ this._mayTrimURLs = this._prefs.getBoolPref(aData);
+ break;
+ case "oneOffSearches":
+ this._enableOrDisableOneOffSearches();
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_enableOrDisableOneOffSearches">
+ <body><![CDATA[
+ let enable = this._prefs.getBoolPref("oneOffSearches");
+ this.popup.enableOneOffSearches(enable);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "paste":
+ let originalPasteData = aEvent.clipboardData.getData("text/plain");
+ if (!originalPasteData) {
+ return;
+ }
+
+ let oldValue = this.inputField.value;
+ let oldStart = oldValue.substring(0, this.inputField.selectionStart);
+ // If there is already non-whitespace content in the URL bar
+ // preceding the pasted content, it's not necessary to check
+ // protocols used by the pasted content:
+ if (oldStart.trim()) {
+ return;
+ }
+ let oldEnd = oldValue.substring(this.inputField.selectionEnd);
+
+ let pasteData = stripUnsafeProtocolOnPaste(originalPasteData);
+ if (originalPasteData != pasteData) {
+ // Unfortunately we're not allowed to set the bits being pasted
+ // so cancel this event:
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+
+ this.inputField.value = oldStart + pasteData + oldEnd;
+ // Fix up cursor/selection:
+ let newCursorPos = oldStart.length + pasteData.length;
+ this.inputField.selectionStart = newCursorPos;
+ this.inputField.selectionEnd = newCursorPos;
+ }
+ break;
+ case "mousedown":
+ if (this.doubleClickSelectsAll &&
+ aEvent.button == 0 && aEvent.detail == 2) {
+ this.editor.selectAll();
+ aEvent.preventDefault();
+ }
+ break;
+ case "mousemove":
+ this._initURLTooltip();
+ break;
+ case "mouseout":
+ this._hideURLTooltip();
+ break;
+ case "overflow":
+ this._contentIsCropped = true;
+ break;
+ case "underflow":
+ this._contentIsCropped = false;
+ this._hideURLTooltip();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ onBeforeTextValueSet is called by the base-binding's .textValue getter.
+ It should return the value that the getter should use.
+ -->
+ <method name="onBeforeTextValueGet">
+ <body><![CDATA[
+ return { value: this.inputField.value };
+ ]]></body>
+ </method>
+
+ <!--
+ onBeforeTextValueSet is called by the base-binding's .textValue setter.
+ It should return the value that the setter should use.
+ -->
+ <method name="onBeforeTextValueSet">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ let val = aValue;
+ let uri;
+ try {
+ uri = makeURI(val);
+ } catch (ex) {}
+
+ if (uri) {
+ // Do not touch moz-action URIs at all. They depend on being
+ // properly encoded and decoded and will break if decoded
+ // unexpectedly.
+ if (!this._parseActionUrl(val)) {
+ val = losslessDecodeURI(uri);
+ }
+ }
+
+ return val;
+ ]]></body>
+ </method>
+
+ <method name="_parseActionUrl">
+ <parameter name="aUrl"/>
+ <body><![CDATA[
+ const MOZ_ACTION_REGEX = /^moz-action:([^,]+),(.*)$/;
+ if (!MOZ_ACTION_REGEX.test(aUrl))
+ 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_REGEX);
+
+ let action = {
+ type: type,
+ };
+
+ action.params = JSON.parse(params);
+ for (let key in action.params) {
+ action.params[key] = decodeURIComponent(action.params[key]);
+ }
+
+ if ("url" in action.params) {
+ let uri;
+ try {
+ uri = makeURI(action.params.url);
+ action.params.displayUrl = losslessDecodeURI(uri);
+ } catch (e) {
+ action.params.displayUrl = action.params.url;
+ }
+ }
+
+ return action;
+ ]]></body>
+ </method>
+
+ <property name="_noActionKeys" readonly="true">
+ <getter><![CDATA[
+ if (!this.__noActionKeys) {
+ this.__noActionKeys = new Set([
+ KeyEvent.DOM_VK_ALT,
+ KeyEvent.DOM_VK_SHIFT,
+ ]);
+ let modifier = this.AppConstants.platform == "macosx" ?
+ KeyEvent.DOM_VK_META :
+ KeyEvent.DOM_VK_CONTROL;
+ this.__noActionKeys.add(modifier);
+ }
+ return this.__noActionKeys;
+ ]]></getter>
+ </property>
+
+ <field name="_pressedNoActionKeys"><![CDATA[
+ new Set()
+ ]]></field>
+
+ <method name="_clearNoActions">
+ <parameter name="aURL"/>
+ <body><![CDATA[
+ this._pressedNoActionKeys.clear();
+ this.popup.removeAttribute("noactions");
+ let action = this._parseActionUrl(this._value);
+ if (action)
+ this.setAttribute("actiontype", action.type);
+ ]]></body>
+ </method>
+
+ <method name="onInput">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!this.mIgnoreInput && this.mController.input == this) {
+ this._value = this.inputField.value;
+ gBrowser.userTypedValue = this.value;
+ this.valueIsTyped = true;
+ // Only wait for a result when we are sure to get one. In some
+ // cases, like when pasting the same exact text, we may not fire
+ // a new search and we won't get a result.
+ if (this.mController.handleText()) {
+ this.gotResultForCurrentQuery = false;
+ }
+ }
+ this.resetActionType();
+ ]]></body>
+ </method>
+
+ <method name="handleEnter">
+ <parameter name="event"/>
+ <body><![CDATA[
+ // We need to ensure we're using a selected autocomplete result.
+ // A result should automatically be selected by default,
+ // however autocomplete is async and therefore we may not
+ // have a result set relating to the current input yet. If that
+ // happens, we need to mark that when the first result does get added,
+ // it needs to be handled as if enter was pressed with that first
+ // result selected.
+ // If anything other than the default (first) result is selected, then
+ // it must have been manually selected by the human. We let this
+ // explicit choice be used, even if it may be related to a previous
+ // input.
+ // However, if the default result is automatically selected, we
+ // ensure that it corresponds to the current input.
+
+ // Store the current search string so it can be used in
+ // handleCommand, which will be called as a result of
+ // mController.handleEnter().
+ // Note this is also used to detect if we should perform a delayed
+ // handleEnter, in such a case it won't have been cleared.
+ this.handleEnterInstance = {
+ searchString: this.mController.searchString,
+ event: event
+ };
+
+ if (this.popup.selectedIndex != 0 || this.gotResultForCurrentQuery) {
+ this.maybeCanonizeURL(event, this.value);
+ let rv = this.mController.handleEnter(false, event);
+ this.handleEnterInstance = null;
+ this.popup.overrideValue = null;
+ return rv;
+ }
+
+ return true;
+ ]]></body>
+ </method>
+
+ <method name="handleDelete">
+ <body><![CDATA[
+ // If the heuristic result is selected, then the autocomplete
+ // controller's handleDelete implementation will remove it, which is
+ // not what we want. So in that case, call handleText so it acts as
+ // a backspace on the text value instead of removing the result.
+ if (this.popup.selectedIndex == 0 &&
+ this.popup._isFirstResultHeuristic) {
+ this.mController.handleText();
+ return false;
+ }
+ return this.mController.handleDelete();
+ ]]></body>
+ </method>
+
+ <field name="_userMadeSearchSuggestionsChoice"><![CDATA[
+ false
+ ]]></field>
+
+ <method name="_cacheUserMadeSearchSuggestionsChoice">
+ <body><![CDATA[
+ this._userMadeSearchSuggestionsChoice =
+ this._prefs.getBoolPref("userMadeSearchSuggestionsChoice") ||
+ this._prefs.getBoolPref("suggest.searches");
+ ]]></body>
+ </method>
+
+ <property name="shouldShowSearchSuggestionsNotification" readonly="true">
+ <getter><![CDATA[
+ return !this._userMadeSearchSuggestionsChoice &&
+ !this.inPrivateContext &&
+ // When _urlbarFocused is true, tabbrowser would close the
+ // popup if it's opened here, so don't show the notification.
+ !gBrowser.selectedBrowser._urlbarFocused &&
+ Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
+ this._prefs.getIntPref("daysBeforeHidingSuggestionsPrompt");
+ ]]></getter>
+ </property>
+
+ </implementation>
+
+ <handlers>
+ <handler event="keydown"><![CDATA[
+ if (this._noActionKeys.has(event.keyCode) &&
+ this.popup.selectedIndex >= 0 &&
+ !this._pressedNoActionKeys.has(event.keyCode)) {
+ if (this._pressedNoActionKeys.size == 0) {
+ this.popup.setAttribute("noactions", "true");
+ this.removeAttribute("actiontype");
+ }
+ this._pressedNoActionKeys.add(event.keyCode);
+ }
+ ]]></handler>
+
+ <handler event="keyup"><![CDATA[
+ if (this._noActionKeys.has(event.keyCode) &&
+ this._pressedNoActionKeys.has(event.keyCode)) {
+ this._pressedNoActionKeys.delete(event.keyCode);
+ if (this._pressedNoActionKeys.size == 0)
+ this._clearNoActions();
+ }
+ ]]></handler>
+
+ <handler event="focus"><![CDATA[
+ if (event.originalTarget == this.inputField) {
+ this._hideURLTooltip();
+ this.formatValue();
+ }
+ ]]></handler>
+
+ <handler event="blur"><![CDATA[
+ if (event.originalTarget == this.inputField) {
+ this._clearNoActions();
+ this.formatValue();
+ }
+ if (ExtensionSearchHandler.hasActiveInputSession()) {
+ ExtensionSearchHandler.handleInputCancelled();
+ }
+ ]]></handler>
+
+ <handler event="dragstart" phase="capturing"><![CDATA[
+ // Drag only if the gesture starts from the input field.
+ if (this.inputField != event.originalTarget &&
+ !(this.inputField.compareDocumentPosition(event.originalTarget) &
+ Node.DOCUMENT_POSITION_CONTAINED_BY))
+ return;
+
+ // Drag only if the entire value is selected and it's a valid URI.
+ var isFullSelection = this.selectionStart == 0 &&
+ this.selectionEnd == this.textLength;
+ if (!isFullSelection ||
+ this.getAttribute("pageproxystate") != "valid")
+ return;
+
+ var urlString = gBrowser.selectedBrowser.currentURI.spec;
+ var title = gBrowser.selectedBrowser.contentTitle || urlString;
+ var htmlString = "<a href=\"" + urlString + "\">" + urlString + "</a>";
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", urlString + "\n" + title);
+ dt.setData("text/unicode", urlString);
+ dt.setData("text/html", htmlString);
+
+ dt.effectAllowed = "copyLink";
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
+ <handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
+ <handler event="select"><![CDATA[
+ if (!Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(Ci.nsIClipboard)
+ .supportsSelectionClipboard())
+ return;
+
+ if (!window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .isHandlingUserInput)
+ return;
+
+ var val = this._getSelectedValueForClipboard();
+ if (!val)
+ return;
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyStringToClipboard(val, Ci.nsIClipboard.kSelectionClipboard);
+ ]]></handler>
+ </handlers>
+
+ </binding>
+
+ <!-- Note: this binding is applied to the autocomplete popup used in web page content and extended in search.xml for the searchbar. -->
+ <binding id="browser-autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup">
+ <implementation>
+ <field name="AppConstants" readonly="true">
+ (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
+ </field>
+
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
+ // this method is defined on the base binding
+ this._openAutocompletePopup(aInput, aElement);
+ ]]></body>
+ </method>
+
+ <method name="onPopupClick">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ // Ignore all right-clicks
+ if (aEvent.button == 2)
+ return;
+
+ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+
+ var searchBar = BrowserSearch.searchBar;
+ var popupForSearchBar = searchBar && searchBar.textbox == this.mInput;
+ if (popupForSearchBar) {
+ searchBar.telemetrySearchDetails = {
+ index: controller.selection.currentIndex,
+ kind: "mouse"
+ };
+ }
+
+ // Check for unmodified left-click, and use default behavior
+ if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
+ !aEvent.altKey && !aEvent.metaKey) {
+ controller.handleEnter(true, aEvent);
+ return;
+ }
+
+ // Check for middle-click or modified clicks on the search bar
+ if (popupForSearchBar) {
+ // Handle search bar popup clicks
+ var search = controller.getValueAt(this.selectedIndex);
+
+ // open the search results according to the clicking subtlety
+ var where = whereToOpenLink(aEvent, false, true);
+ let params = {};
+
+ // But open ctrl/cmd clicks on autocomplete items in a new background tab.
+ let modifier = this.AppConstants.platform == "macosx" ?
+ aEvent.metaKey :
+ aEvent.ctrlKey;
+ if (where == "tab" && (aEvent instanceof MouseEvent) &&
+ (aEvent.button == 1 || modifier))
+ params.inBackground = true;
+
+ // leave the popup open for background tab loads
+ if (!(where == "tab" && params.inBackground)) {
+ // close the autocomplete popup and revert the entered search term
+ this.closePopup();
+ controller.handleEscape();
+ }
+
+ searchBar.doSearch(search, where, null, params);
+ if (where == "tab" && params.inBackground)
+ searchBar.focus();
+ else
+ searchBar.value = search;
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
+
+ <resources>
+ <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
+ <stylesheet src="chrome://browser/skin/searchbar.css"/>
+ </resources>
+
+ <content ignorekeys="true" level="top" consumeoutsideclicks="never"
+ aria-owns="richlistbox">
+ <xul:hbox anonid="search-suggestions-notification"
+ align="center"
+ role="alert"
+ aria-describedby="search-suggestions-notification-text">
+ <xul:description flex="1">
+ &urlbar.searchSuggestionsNotification.question;
+ <!-- Several things here are to make the label accessibile via an
+ accesskey so that a11y doesn't suck: the accesskey, using an
+ onclick handler instead of an href attribute, the control
+ attribute, and having the control attribute refer to a valid ID
+ that is the label itself. -->
+ <xul:label id="search-suggestions-notification-learn-more"
+ class="text-link"
+ role="link"
+ value="&urlbar.searchSuggestionsNotification.learnMore;"
+ accesskey="&urlbar.searchSuggestionsNotification.learnMore.accesskey;"
+ onclick="document.getBindingParent(this).openSearchSuggestionsNotificationLearnMoreURL();"
+ control="search-suggestions-notification-learn-more"/>
+ </xul:description>
+ <xul:button anonid="search-suggestions-notification-disable"
+ label="&urlbar.searchSuggestionsNotification.disable;"
+ accesskey="&urlbar.searchSuggestionsNotification.disable.accesskey;"
+ onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(false);"/>
+ <xul:button anonid="search-suggestions-notification-enable"
+ label="&urlbar.searchSuggestionsNotification.enable;"
+ accesskey="&urlbar.searchSuggestionsNotification.enable.accesskey;"
+ onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(true);"/>
+ </xul:hbox>
+ <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
+ flex="1"/>
+ <xul:hbox anonid="footer">
+ <children/>
+ <xul:vbox anonid="one-off-search-buttons"
+ class="search-one-offs"
+ compact="true"
+ includecurrentengine="true"
+ disabletab="true"
+ flex="1"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <field name="_maxResults">0</field>
+
+ <field name="_bundle" readonly="true">
+ Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle("chrome://browser/locale/places/places.properties");
+ </field>
+
+ <field name="searchSuggestionsNotification" readonly="true">
+ document.getAnonymousElementByAttribute(
+ this, "anonid", "search-suggestions-notification"
+ );
+ </field>
+
+ <field name="footer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "footer");
+ </field>
+
+ <field name="oneOffSearchButtons" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "one-off-search-buttons");
+ </field>
+
+ <field name="_oneOffSearchesEnabled">false</field>
+
+ <field name="_overrideValue">null</field>
+ <property name="overrideValue"
+ onget="return this._overrideValue;"
+ onset="this._overrideValue = val; return val;"/>
+
+ <method name="onPopupClick">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.button == 2) {
+ // Ignore right-clicks.
+ return;
+ }
+ // Otherwise "call super" -- do what autocomplete-base-popup does.
+ let controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+ controller.handleEnter(true, aEvent);
+ ]]></body>
+ </method>
+
+ <method name="enableOneOffSearches">
+ <parameter name="enable"/>
+ <body><![CDATA[
+ this._oneOffSearchesEnabled = enable;
+ if (enable) {
+ this.oneOffSearchButtons.telemetryOrigin = "urlbar";
+ this.oneOffSearchButtons.style.display = "-moz-box";
+ this.oneOffSearchButtons.popup = this;
+ this.oneOffSearchButtons.textbox = this.input;
+ } else {
+ this.oneOffSearchButtons.telemetryOrigin = null;
+ this.oneOffSearchButtons.style.display = "none";
+ this.oneOffSearchButtons.popup = null;
+ this.oneOffSearchButtons.textbox = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="openSearchSuggestionsNotificationLearnMoreURL">
+ <body><![CDATA[
+ let url = Services.urlFormatter.formatURL(
+ Services.prefs.getCharPref("app.support.baseURL") + "suggestions"
+ );
+ openUILinkIn(url, "tab");
+ ]]></body>
+ </method>
+
+ <method name="dismissSearchSuggestionsNotification">
+ <parameter name="enableSuggestions"/>
+ <body><![CDATA[
+ // Make sure the urlbar is focused. It won't be, for example, if the
+ // user used an accesskey to make an opt-in choice. mIgnoreFocus
+ // prevents the text from being selected.
+ this.input.mIgnoreFocus = true;
+ this.input.focus();
+ this.input.mIgnoreFocus = false;
+
+ Services.prefs.setBoolPref(
+ "browser.urlbar.suggest.searches", enableSuggestions
+ );
+ Services.prefs.setBoolPref(
+ "browser.urlbar.userMadeSearchSuggestionsChoice", true
+ );
+ // The input's pref observer will now hide the notification.
+ ]]></body>
+ </method>
+
+ <!-- Override this so that navigating between items results in an item
+ always being selected. -->
+ <method name="getNextIndex">
+ <parameter name="reverse"/>
+ <parameter name="amount"/>
+ <parameter name="index"/>
+ <parameter name="maxRow"/>
+ <body><![CDATA[
+ if (maxRow < 0)
+ return -1;
+
+ let newIndex = index + (reverse ? -1 : 1) * amount;
+
+ // We only want to wrap if navigation is in any direction by one item,
+ // otherwise we clamp to one end of the list.
+ // ie, hitting page-down will only cause is to wrap if we're already
+ // at one end of the list.
+
+ // Allow the selection to be removed if the first result is not a
+ // heuristic result.
+ if (!this._isFirstResultHeuristic) {
+ if (reverse && index == -1 || newIndex > maxRow && index != maxRow)
+ newIndex = maxRow;
+ else if (!reverse && index == -1 || newIndex < 0 && index != 0)
+ newIndex = 0;
+
+ if (newIndex < 0 && index == 0 || newIndex > maxRow && index == maxRow)
+ newIndex = -1;
+
+ return newIndex;
+ }
+
+ // Otherwise do not allow the selection to be removed.
+ if (newIndex < 0) {
+ newIndex = index > 0 ? 0 : maxRow;
+ } else if (newIndex > maxRow) {
+ newIndex = index < maxRow ? maxRow : 0;
+ }
+ return newIndex;
+ ]]></body>
+ </method>
+
+ <property name="_isFirstResultHeuristic" readonly="true">
+ <getter>
+ <![CDATA[
+ // The popup usually has a special "heuristic" first result (added
+ // by UnifiedComplete.js) that is automatically selected when the
+ // popup opens.
+ return this.input.mController.matchCount > 0 &&
+ this.input.mController
+ .getStyleAt(0)
+ .split(/\s+/).indexOf("heuristic") > 0;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="maxResults" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._maxResults) {
+ var prefService =
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ this._maxResults = prefService.getIntPref("browser.urlbar.maxRichResults");
+ }
+ return this._maxResults;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
+ let showNotification = aInput.shouldShowSearchSuggestionsNotification;
+ if (showNotification) {
+ let prefs = aInput._prefs;
+ let now = new Date();
+ let date = now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate();
+ let previousDate = prefs.getIntPref("lastSuggestionsPromptDate");
+ if (previousDate < date) {
+ let remainingDays =
+ prefs.getIntPref("daysBeforeHidingSuggestionsPrompt") - 1;
+ prefs.setIntPref("daysBeforeHidingSuggestionsPrompt",
+ remainingDays);
+ prefs.setIntPref("lastSuggestionsPromptDate", date);
+ if (!remainingDays)
+ showNotification = false;
+ }
+ }
+
+ if (showNotification) {
+ this._showSearchSuggestionsNotification();
+ } else if (this.classList.contains("showSearchSuggestionsNotification")) {
+ this._hideSearchSuggestionsNotification();
+ }
+
+ this._openAutocompletePopup(aInput, aElement);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body><![CDATA[
+ if (this.mPopupOpen) {
+ return;
+ }
+
+ this.mInput = aInput;
+ aInput.controller.setInitiallySelectedIndex(this._isFirstResultHeuristic ? 0 : -1);
+ this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView);
+ this._invalidate();
+
+ var rect = window.document.documentElement.getBoundingClientRect();
+ var width = rect.right - rect.left;
+ this.setAttribute("width", width);
+
+ // 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;
+
+ // Make the popup's starting margin negative so that the leading edge
+ // of the popup aligns with the window border.
+ let elementRect = aElement.getBoundingClientRect();
+ if (popupDirection == "rtl") {
+ let offset = elementRect.right - rect.right
+ this.style.marginRight = offset + "px";
+ } else {
+ let offset = rect.left - elementRect.left;
+ this.style.marginLeft = offset + "px";
+ }
+
+ // Keep the popup items' site icons aligned with the urlbar's identity
+ // icon if it's not too far from the edge of the window. If there are
+ // at most two toolbar buttons between the window edge and the urlbar,
+ // then consider that as "not too far." The forward button's
+ // visibility may have changed since the last time the popup was
+ // opened, so this needs to happen now. Do it *before* the popup
+ // opens because otherwise the items will visibly shift.
+ let nodes = [...document.getElementById("nav-bar-customization-target").childNodes];
+ let urlbarPosition = nodes.findIndex(n => n.id == "urlbar-container");
+ let alignSiteIcons = urlbarPosition <= 2 &&
+ nodes.slice(0, urlbarPosition)
+ .every(n => n.localName == "toolbarbutton");
+ if (alignSiteIcons) {
+ let identityRect =
+ document.getElementById("identity-icon").getBoundingClientRect();
+ this.siteIconStart = popupDirection == "rtl" ? identityRect.right
+ : identityRect.left;
+ }
+ else {
+ // Reset the alignment so that the site icons are positioned
+ // according to whatever's in the CSS.
+ this.siteIconStart = undefined;
+ }
+
+ // Position the popup below the navbar. To get the y-coordinate,
+ // which is an offset from the bottom of the input, subtract the
+ // bottom of the navbar from the buttom of the input.
+ let yOffset =
+ document.getElementById("nav-bar").getBoundingClientRect().bottom -
+ aInput.getBoundingClientRect().bottom;
+ this.openPopup(aElement, "after_start", 0, yOffset, false, false);
+ ]]></body>
+ </method>
+
+ <method name="_updateFooterVisibility">
+ <body>
+ <![CDATA[
+ this.footer.collapsed = this._matchCount == 0;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_showSearchSuggestionsNotification">
+ <body>
+ <![CDATA[
+ // With the notification shown, the listbox's height can sometimes be
+ // too small when it's flexed, as it normally is. Also, it can start
+ // out slightly scrolled down. Both problems appear together, most
+ // often when the popup is very narrow and the notification's text
+ // must wrap. Work around them by removing the flex.
+ //
+ // But without flexing the listbox, the listbox's height animation
+ // sometimes fails to complete, leaving the popup too tall. Work
+ // around that problem by disabling the listbox animation.
+ this.richlistbox.flex = 0;
+ this.setAttribute("dontanimate", "true");
+
+ this.classList.add("showSearchSuggestionsNotification");
+ this._updateFooterVisibility();
+
+ // This event allows accessibility APIs to see the notification.
+ if (!this.popupOpen) {
+ let event = document.createEvent("Events");
+ event.initEvent("AlertActive", true, true);
+ this.searchSuggestionsNotification.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="searchSuggestionsNotificationWasDismissed">
+ <parameter name="enableSuggestions"/>
+ <body>
+ <![CDATA[
+ if (!this.popupOpen) {
+ this._hideSearchSuggestionsNotification();
+ return;
+ }
+ this._hideSearchSuggestionsNotificationWithAnimation().then(() => {
+ if (enableSuggestions && this.input.textValue) {
+ // Start a new search so that suggestions appear immediately.
+ this.input.controller.startSearch(this.input.textValue);
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="_hideSearchSuggestionsNotification">
+ <body>
+ <![CDATA[
+ this.classList.remove("showSearchSuggestionsNotification");
+ this.richlistbox.flex = 1;
+ this.removeAttribute("dontanimate");
+ if (this._matchCount) {
+ // Update popup height.
+ this._invalidate();
+ } else {
+ this.closePopup();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_hideSearchSuggestionsNotificationWithAnimation">
+ <body>
+ <![CDATA[
+ return new Promise(resolve => {
+ let notificationHeight = this.searchSuggestionsNotification
+ .getBoundingClientRect()
+ .height;
+ this.searchSuggestionsNotification.style.marginTop =
+ "-" + notificationHeight + "px";
+
+ let popupHeightPx =
+ (this.getBoundingClientRect().height - notificationHeight) + "px";
+ this.style.height = popupHeightPx;
+
+ let onTransitionEnd = () => {
+ this.removeEventListener("transitionend", onTransitionEnd, true);
+ this.searchSuggestionsNotification.style.marginTop = "0px";
+ this.style.removeProperty("height");
+ this._hideSearchSuggestionsNotification();
+ resolve();
+ };
+ this.addEventListener("transitionend", onTransitionEnd, true);
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="_selectedOneOffChanged">
+ <body><![CDATA[
+ // Update all searchengine result items to use the newly selected
+ // engine.
+ for (let item of this.richlistbox.childNodes) {
+ if (item.collapsed) {
+ break;
+ }
+ let url = item.getAttribute("url");
+ if (url) {
+ let action = item._parseActionUrl(url);
+ if (action && action.type == "searchengine") {
+ item._adjustAcItem();
+ }
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- This handles keypress changes to the selection among the one-off
+ search buttons and between the one-offs and the listbox. It returns
+ true if the keypress was consumed and false if not. -->
+ <method name="handleKeyPress">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ this.oneOffSearchButtons.handleKeyPress(aEvent, this._matchCount,
+ !this._isFirstResultHeuristic,
+ gBrowser.userTypedValue);
+ return aEvent.defaultPrevented;
+ ]]></body>
+ </method>
+
+ <!-- This is called when a one-off is clicked and when "search in new tab"
+ is selected from a one-off context menu. -->
+ <method name="handleOneOffSearch">
+ <parameter name="event"/>
+ <parameter name="engine"/>
+ <parameter name="where"/>
+ <parameter name="params"/>
+ <body><![CDATA[
+ this.input.handleCommand(event, where, params);
+ ]]></body>
+ </method>
+
+ <!-- Result listitems call this to determine which search engine they
+ should show in their labels and include in their url attributes. -->
+ <property name="overrideSearchEngineName" readonly="true">
+ <getter><![CDATA[
+ let button = this.oneOffSearchButtons.selectedButton;
+ return button && button.engine && button.engine.name;
+ ]]></getter>
+ </property>
+
+ <method name="createResultLabel">
+ <parameter name="item"/>
+ <parameter name="proposedLabel"/>
+ <body>
+ <![CDATA[
+ let parts = [proposedLabel];
+
+ let action = this.mInput._parseActionUrl(item.getAttribute("url"));
+ if (action) {
+ switch (action.type) {
+ case "searchengine":
+ parts = [
+ action.params.searchSuggestion || action.params.searchQuery,
+ action.params.engineName,
+ ];
+ break;
+ case "switchtab":
+ case "remotetab":
+ parts = [
+ item.getAttribute("title"),
+ item.getAttribute("displayurl"),
+ ];
+ break;
+ }
+ }
+
+ let types = item.getAttribute("type").split(/\s+/);
+ let type = types.find(type => type != "action" && type != "heuristic");
+ try {
+ // Some types intentionally do not map to strings, which is not
+ // an error.
+ parts.push(this._bundle.GetStringFromName(type + "ResultLabel"));
+ } catch (e) {}
+
+ return parts.filter(str => str).join(" ");
+ ]]>
+ </body>
+ </method>
+
+ <method name="onResultsAdded">
+ <body>
+ <![CDATA[
+ // If nothing is selected yet, select the first result if it is a
+ // pre-selected "heuristic" result. (See UnifiedComplete.js.)
+ if (this.selectedIndex == -1 && this._isFirstResultHeuristic) {
+ // Don't fire DOMMenuItemActive so that screen readers still see
+ // the input as being focused.
+ this.richlistbox.suppressMenuItemEvent = true;
+ this.input.controller.setInitiallySelectedIndex(0);
+ this.richlistbox.suppressMenuItemEvent = false;
+ }
+
+ this.input.gotResultForCurrentQuery = true;
+
+ // Check if we should perform a delayed handleEnter.
+ if (this.input.handleEnterInstance) {
+ let instance = this.input.handleEnterInstance;
+ this.input.handleEnterInstance = null;
+ // Don't handle this immediately or we could cause a recursive
+ // loop where the controller sets popupOpen and re-enters here.
+ setTimeout(() => {
+ // Safety check: handle only if the search string didn't change.
+ let { event, searchString } = instance;
+ if (this.input.mController.searchString == searchString) {
+ this.input.maybeCanonizeURL(event, searchString);
+ this.input.mController.handleEnter(false, event);
+ this.overrideValue = null;
+ }
+ }, 0);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_onSearchBegin">
+ <body><![CDATA[
+ // Set the selected index to 0 (heuristic) until a result comes back
+ // and we can evaluate it better.
+ //
+ // This is required to properly manage delayed handleEnter:
+ // 1. if a search starts we set selectedIndex to 0 here, and it will
+ // be updated by onResultsAdded. Since selectedIndex is 0,
+ // handleEnter will delay the action if a result didn't arrive yet.
+ // 2. if a search doesn't start (for example if autocomplete is
+ // disabled), this won't be called, and the selectedIndex will be
+ // the default -1 value. Then handleEnter will know it should not
+ // delay the action, cause a result wont't ever arrive.
+ this.input.controller.setInitiallySelectedIndex(0);
+ ]]></body>
+ </method>
+
+ </implementation>
+ <handlers>
+
+ <handler event="SelectedOneOffButtonChanged"><![CDATA[
+ this._selectedOneOffChanged();
+ ]]></handler>
+
+ <handler event="mousedown"><![CDATA[
+ // Required to make the xul:label.text-link elements in the search
+ // suggestions notification work correctly when clicked on Linux.
+ // This is copied from the mousedown handler in
+ // browser-search-autocomplete-result-popup, which apparently had a
+ // similar problem.
+ event.preventDefault();
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <implementation>
+ <constructor><![CDATA[
+ if (!this.notification)
+ return;
+
+ this.notification.options.installs.forEach(function(aInstall) {
+ aInstall.addListener(this);
+ }, this);
+
+ // Calling updateProgress can sometimes cause this notification to be
+ // removed in the middle of refreshing the notification panel which
+ // makes the panel get refreshed again. Just initialise to the
+ // undetermined state and then schedule a proper check at the next
+ // opportunity
+ this.setProgress(0, -1);
+ this._updateProgressTimeout = setTimeout(this.updateProgress.bind(this), 0);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this.destroy();
+ ]]></destructor>
+
+ <field name="progressmeter" readonly="true">
+ document.getElementById("addon-progress-notification-progressmeter");
+ </field>
+ <field name="progresstext" readonly="true">
+ document.getElementById("addon-progress-notification-progresstext");
+ </field>
+ <property name="DownloadUtils" readonly="true">
+ <getter><![CDATA[
+ let module = {};
+ Components.utils.import("resource://gre/modules/DownloadUtils.jsm", module);
+ Object.defineProperty(this, "DownloadUtils", {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: module.DownloadUtils
+ });
+ return module.DownloadUtils;
+ ]]></getter>
+ </property>
+
+ <method name="destroy">
+ <body><![CDATA[
+ if (!this.notification)
+ return;
+
+ this.notification.options.installs.forEach(function(aInstall) {
+ aInstall.removeListener(this);
+ }, this);
+ clearTimeout(this._updateProgressTimeout);
+ ]]></body>
+ </method>
+
+ <method name="setProgress">
+ <parameter name="aProgress"/>
+ <parameter name="aMaxProgress"/>
+ <body><![CDATA[
+ if (aMaxProgress == -1) {
+ this.progressmeter.setAttribute("mode", "undetermined");
+ }
+ else {
+ this.progressmeter.setAttribute("mode", "determined");
+ this.progressmeter.setAttribute("value", (aProgress * 100) / aMaxProgress);
+ }
+
+ let now = Date.now();
+
+ if (!this.notification.lastUpdate) {
+ this.notification.lastUpdate = now;
+ this.notification.lastProgress = aProgress;
+ return;
+ }
+
+ let delta = now - this.notification.lastUpdate;
+ if ((delta < 400) && (aProgress < aMaxProgress))
+ return;
+
+ delta /= 1000;
+
+ // This code is taken from nsDownloadManager.cpp
+ let speed = (aProgress - this.notification.lastProgress) / delta;
+ if (this.notification.speed)
+ speed = speed * 0.9 + this.notification.speed * 0.1;
+
+ this.notification.lastUpdate = now;
+ this.notification.lastProgress = aProgress;
+ this.notification.speed = speed;
+
+ let status = null;
+ [status, this.notification.last] = this.DownloadUtils.getDownloadStatus(aProgress, aMaxProgress, speed, this.notification.last);
+ this.progresstext.setAttribute("value", status);
+ this.progresstext.setAttribute("tooltiptext", status);
+ ]]></body>
+ </method>
+
+ <method name="cancel">
+ <body><![CDATA[
+ let installs = this.notification.options.installs;
+ installs.forEach(function(aInstall) {
+ try {
+ aInstall.cancel();
+ }
+ catch (e) {
+ // Cancel will throw if the download has already failed
+ }
+ }, this);
+
+ PopupNotifications.remove(this.notification);
+ ]]></body>
+ </method>
+
+ <method name="updateProgress">
+ <body><![CDATA[
+ if (!this.notification)
+ return;
+
+ let downloadingCount = 0;
+ let progress = 0;
+ let maxProgress = 0;
+
+ this.notification.options.installs.forEach(function(aInstall) {
+ if (aInstall.maxProgress == -1)
+ maxProgress = -1;
+ progress += aInstall.progress;
+ if (maxProgress >= 0)
+ maxProgress += aInstall.maxProgress;
+ if (aInstall.state < AddonManager.STATE_DOWNLOADED)
+ downloadingCount++;
+ });
+
+ if (downloadingCount == 0) {
+ this.destroy();
+ if (Preferences.get("xpinstall.customConfirmationUI", false)) {
+ this.progressmeter.setAttribute("mode", "undetermined");
+ let status = gNavigatorBundle.getString("addonDownloadVerifying");
+ this.progresstext.setAttribute("value", status);
+ this.progresstext.setAttribute("tooltiptext", status);
+ } else {
+ PopupNotifications.remove(this.notification);
+ }
+ }
+ else {
+ this.setProgress(progress, maxProgress);
+ }
+ ]]></body>
+ </method>
+
+ <method name="onDownloadProgress">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadCancelled">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadEnded">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="plugin-popupnotification-center-item">
+ <content align="center">
+ <xul:vbox pack="center" anonid="itemBox" class="itemBox">
+ <xul:description anonid="center-item-label" class="center-item-label" />
+ <xul:hbox flex="1" pack="start" align="center" anonid="center-item-warning">
+ <xul:image anonid="center-item-warning-icon" class="center-item-warning-icon"/>
+ <xul:label anonid="center-item-warning-label"/>
+ <xul:label anonid="center-item-link" value="&checkForUpdates;" class="text-link"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox pack="center">
+ <xul:menulist class="center-item-menulist"
+ anonid="center-item-menulist">
+ <xul:menupopup>
+ <xul:menuitem anonid="allownow" value="allownow"
+ label="&pluginActivateNow.label;" />
+ <xul:menuitem anonid="allowalways" value="allowalways"
+ label="&pluginActivateAlways.label;" />
+ <xul:menuitem anonid="block" value="block"
+ label="&pluginBlockNow.label;" />
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <constructor><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-label").value = this.action.pluginName;
+
+ let curState = "block";
+ if (this.action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
+ if (this.action.pluginPermissionType == Ci.nsIPermissionManager.EXPIRE_SESSION) {
+ curState = "allownow";
+ }
+ else {
+ curState = "allowalways";
+ }
+ }
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").value = curState;
+
+ let warningString = "";
+ let linkString = "";
+
+ let link = document.getAnonymousElementByAttribute(this, "anonid", "center-item-link");
+
+ let url;
+ let linkHandler;
+
+ if (this.action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
+ warningString = gNavigatorBundle.getString("pluginActivateDisabled.label");
+ linkString = gNavigatorBundle.getString("pluginActivateDisabled.manage");
+ linkHandler = function(event) {
+ event.preventDefault();
+ gPluginHandler.managePlugins();
+ };
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-icon").hidden = true;
+ }
+ else {
+ url = this.action.detailsLink;
+
+ switch (this.action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning").hidden = true;
+ break;
+ case Ci.nsIBlocklistService.STATE_BLOCKED:
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
+ warningString = gNavigatorBundle.getString("pluginActivateBlocked.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.learnMore");
+ break;
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ warningString = gNavigatorBundle.getString("pluginActivateOutdated.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.updateLabel");
+ break;
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ warningString = gNavigatorBundle.getString("pluginActivateVulnerable.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.riskLabel");
+ break;
+ }
+ }
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-label").value = warningString;
+
+ let chromeWin = window.QueryInterface(Ci.nsIDOMChromeWindow);
+ let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWin);
+
+ if (isWindowPrivate) {
+ // TODO: temporary compromise of hiding some privacy leaks, remove once bug 892487 is fixed
+ let allowalways = document.getAnonymousElementByAttribute(this, "anonid", "allowalways");
+ let block = document.getAnonymousElementByAttribute(this, "anonid", "block");
+ let allownow = document.getAnonymousElementByAttribute(this, "anonid", "allownow");
+
+ allowalways.hidden = curState !== "allowalways";
+ block.hidden = curState !== "block";
+ allownow.hidden = curState === "allowalways";
+ }
+
+ if (url || linkHandler) {
+ link.value = linkString;
+ if (url) {
+ link.href = url;
+ }
+ if (linkHandler) {
+ link.addEventListener("click", linkHandler, false);
+ }
+ }
+ else {
+ link.hidden = true;
+ }
+ ]]></constructor>
+ <property name="value">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid",
+ "center-item-menulist").value;
+ </getter>
+ <setter><!-- This should be used only in automated tests -->
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "center-item-menulist").value = val;
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="click-to-play-plugins-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <content align="start" style="width: &pluginNotification.width;;">
+ <xul:vbox flex="1" align="stretch" class="popup-notification-main-box"
+ xbl:inherits="popupid">
+ <xul:hbox class="click-to-play-plugins-notification-description-box" flex="1" align="start">
+ <xul:description class="click-to-play-plugins-outer-description" flex="1">
+ <html:span anonid="click-to-play-plugins-notification-description" />
+ <xul:label class="text-link click-to-play-plugins-notification-link" anonid="click-to-play-plugins-notification-link" />
+ </xul:description>
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton popup-notification-closebutton tabbable close-icon"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:hbox>
+ <xul:grid anonid="click-to-play-plugins-notification-center-box"
+ class="click-to-play-plugins-notification-center-box">
+ <xul:columns>
+ <xul:column flex="1"/>
+ <xul:column/>
+ </xul:columns>
+ <xul:rows>
+ <children includes="row"/>
+ <xul:hbox pack="start" anonid="plugin-notification-showbox">
+ <xul:button label="&pluginNotification.showAll.label;"
+ accesskey="&pluginNotification.showAll.accesskey;"
+ class="plugin-notification-showbutton"
+ oncommand="document.getBindingParent(this)._setState(2)"/>
+ </xul:hbox>
+ </xul:rows>
+ </xul:grid>
+ <xul:hbox anonid="button-container"
+ class="click-to-play-plugins-notification-button-container"
+ pack="center" align="center">
+ <xul:button anonid="primarybutton"
+ class="click-to-play-popup-button"
+ oncommand="document.getBindingParent(this)._onButton(this)"
+ flex="1"/>
+ <xul:button anonid="secondarybutton"
+ class="click-to-play-popup-button"
+ oncommand="document.getBindingParent(this)._onButton(this);"
+ flex="1"/>
+ </xul:hbox>
+ <xul:box hidden="true">
+ <children/>
+ </xul:box>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <field name="_states">
+ ({SINGLE: 0, MULTI_COLLAPSED: 1, MULTI_EXPANDED: 2})
+ </field>
+ <field name="_primaryButton">
+ document.getAnonymousElementByAttribute(this, "anonid", "primarybutton");
+ </field>
+ <field name="_secondaryButton">
+ document.getAnonymousElementByAttribute(this, "anonid", "secondarybutton")
+ </field>
+ <field name="_buttonContainer">
+ document.getAnonymousElementByAttribute(this, "anonid", "button-container")
+ </field>
+ <field name="_brandShortName">
+ document.getElementById("bundle_brand").getString("brandShortName")
+ </field>
+ <field name="_items">[]</field>
+ <constructor><![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let sortedActions = [];
+ for (let action of this.notification.options.pluginData.values()) {
+ sortedActions.push(action);
+ }
+ sortedActions.sort((a, b) => a.pluginName.localeCompare(b.pluginName));
+
+ for (let action of sortedActions) {
+ let item = document.createElementNS(XUL_NS, "row");
+ item.setAttribute("class", "plugin-popupnotification-centeritem");
+ item.action = action;
+ this.appendChild(item);
+ this._items.push(item);
+ }
+ switch (this._items.length) {
+ case 0:
+ PopupNotifications._dismiss();
+ break;
+ case 1:
+ this._setState(this._states.SINGLE);
+ break;
+ default:
+ if (this.notification.options.primaryPlugin) {
+ this._setState(this._states.MULTI_COLLAPSED);
+ } else {
+ this._setState(this._states.MULTI_EXPANDED);
+ }
+ }
+ ]]></constructor>
+ <method name="_setState">
+ <parameter name="state" />
+ <body><![CDATA[
+ var grid = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-center-box");
+
+ if (this._states.SINGLE == state) {
+ grid.hidden = true;
+ this._setupSingleState();
+ return;
+ }
+
+ let prePath = this.notification.options.principal.URI.prePath;
+ this._setupDescription("pluginActivateMultiple.message", null, prePath);
+
+ var showBox = document.getAnonymousElementByAttribute(this, "anonid", "plugin-notification-showbox");
+
+ var dialogStrings = Services.strings.createBundle("chrome://global/locale/dialog.properties");
+ this._primaryButton.label = dialogStrings.GetStringFromName("button-accept");
+ this._primaryButton.setAttribute("default", "true");
+
+ this._secondaryButton.label = dialogStrings.GetStringFromName("button-cancel");
+ this._primaryButton.setAttribute("action", "_multiAccept");
+ this._secondaryButton.setAttribute("action", "_cancel");
+
+ grid.hidden = false;
+
+ if (this._states.MULTI_COLLAPSED == state) {
+ for (let child of this.childNodes) {
+ if (child.tagName != "row") {
+ continue;
+ }
+ child.hidden = this.notification.options.primaryPlugin !=
+ child.action.permissionString;
+ }
+ showBox.hidden = false;
+ }
+ else {
+ for (let child of this.childNodes) {
+ if (child.tagName != "row") {
+ continue;
+ }
+ child.hidden = false;
+ }
+ showBox.hidden = true;
+ }
+ this._setupLink(null);
+ ]]></body>
+ </method>
+ <method name="_setupSingleState">
+ <body><![CDATA[
+ var action = this._items[0].action;
+ var prePath = action.pluginPermissionPrePath;
+ let chromeWin = window.QueryInterface(Ci.nsIDOMChromeWindow);
+ let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWin);
+
+ let label, linkLabel, button1, button2;
+
+ if (action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
+ button1 = {
+ label: "pluginBlockNow.label",
+ accesskey: "pluginBlockNow.accesskey",
+ action: "_singleBlock"
+ };
+ button2 = {
+ label: "pluginContinue.label",
+ accesskey: "pluginContinue.accesskey",
+ action: "_singleContinue",
+ default: true
+ };
+ switch (action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ label = "pluginEnabled.message";
+ linkLabel = "pluginActivate.learnMore";
+ break;
+
+ case Ci.nsIBlocklistService.STATE_BLOCKED:
+ Cu.reportError(Error("Cannot happen!"));
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ label = "pluginEnabledOutdated.message";
+ linkLabel = "pluginActivate.updateLabel";
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ label = "pluginEnabledVulnerable.message";
+ linkLabel = "pluginActivate.riskLabel"
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected blocklist state"));
+ }
+
+ // TODO: temporary compromise, remove this once bug 892487 is fixed
+ if (isWindowPrivate) {
+ this._buttonContainer.hidden = true;
+ }
+ }
+ else if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
+ let linkElement =
+ document.getAnonymousElementByAttribute(
+ this, "anonid", "click-to-play-plugins-notification-link");
+ linkElement.textContent = gNavigatorBundle.getString("pluginActivateDisabled.manage");
+ linkElement.setAttribute("onclick", "gPluginHandler.managePlugins()");
+
+ let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ descElement.textContent = gNavigatorBundle.getFormattedString(
+ "pluginActivateDisabled.message", [action.pluginName, this._brandShortName]) + " ";
+ this._buttonContainer.hidden = true;
+ return;
+ }
+ else if (action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ descElement.textContent = gNavigatorBundle.getFormattedString(
+ "pluginActivateBlocked.message", [action.pluginName, this._brandShortName]) + " ";
+ this._setupLink("pluginActivate.learnMore", action.detailsLink);
+ this._buttonContainer.hidden = true;
+ return;
+ }
+ else {
+ button1 = {
+ label: "pluginActivateNow.label",
+ accesskey: "pluginActivateNow.accesskey",
+ action: "_singleActivateNow"
+ };
+ button2 = {
+ label: "pluginActivateAlways.label",
+ accesskey: "pluginActivateAlways.accesskey",
+ action: "_singleActivateAlways"
+ };
+ switch (action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ label = "pluginActivateNew.message";
+ linkLabel = "pluginActivate.learnMore";
+ button2.default = true;
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ label = "pluginActivateOutdated.message";
+ linkLabel = "pluginActivate.updateLabel";
+ button1.default = true;
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ label = "pluginActivateVulnerable.message";
+ linkLabel = "pluginActivate.riskLabel"
+ button1.default = true;
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected blocklist state"));
+ }
+
+ // TODO: temporary compromise, remove this once bug 892487 is fixed
+ if (isWindowPrivate) {
+ button1.default = true;
+ this._secondaryButton.hidden = true;
+ }
+ }
+ this._setupDescription(label, action.pluginName, prePath);
+ this._setupLink(linkLabel, action.detailsLink);
+
+ this._primaryButton.label = gNavigatorBundle.getString(button1.label);
+ this._primaryButton.accessKey = gNavigatorBundle.getString(button1.accesskey);
+ this._primaryButton.setAttribute("action", button1.action);
+
+ this._secondaryButton.label = gNavigatorBundle.getString(button2.label);
+ this._secondaryButton.accessKey = gNavigatorBundle.getString(button2.accesskey);
+ this._secondaryButton.setAttribute("action", button2.action);
+ if (button1.default) {
+ this._primaryButton.setAttribute("default", "true");
+ }
+ else if (button2.default) {
+ this._secondaryButton.setAttribute("default", "true");
+ }
+ ]]></body>
+ </method>
+ <method name="_setupDescription">
+ <parameter name="baseString" />
+ <parameter name="pluginName" /> <!-- null for the multiple-plugin case -->
+ <parameter name="prePath" />
+ <body><![CDATA[
+ var span = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ while (span.lastChild) {
+ span.removeChild(span.lastChild);
+ }
+
+ var args = ["__prepath__", this._brandShortName];
+ if (pluginName) {
+ args.unshift(pluginName);
+ }
+ var bases = gNavigatorBundle.getFormattedString(baseString, args).
+ split("__prepath__", 2);
+
+ span.appendChild(document.createTextNode(bases[0]));
+ var prePathSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "em");
+ prePathSpan.appendChild(document.createTextNode(prePath));
+ span.appendChild(prePathSpan);
+ span.appendChild(document.createTextNode(bases[1] + " "));
+ ]]></body>
+ </method>
+ <method name="_setupLink">
+ <parameter name="linkString"/>
+ <parameter name="linkUrl" />
+ <body><![CDATA[
+ var link = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-link");
+ if (!linkString || !linkUrl) {
+ link.hidden = true;
+ return;
+ }
+
+ link.hidden = false;
+ link.textContent = gNavigatorBundle.getString(linkString);
+ link.href = linkUrl;
+ ]]></body>
+ </method>
+ <method name="_onButton">
+ <parameter name="aButton" />
+ <body><![CDATA[
+ let methodName = aButton.getAttribute("action");
+ this[methodName]();
+ ]]></body>
+ </method>
+ <method name="_singleActivateNow">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this._items[0].action,
+ "allownow");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleBlock">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this._items[0].action,
+ "block");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleActivateAlways">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this._items[0].action,
+ "allowalways");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleContinue">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this._items[0].action,
+ "continue");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_multiAccept">
+ <body><![CDATA[
+ for (let item of this._items) {
+ let action = item.action;
+ if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED ||
+ action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ continue;
+ }
+ gPluginHandler._updatePluginPermission(this.notification,
+ item.action, item.value);
+ }
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_cancel">
+ <body><![CDATA[
+ PopupNotifications._dismiss();
+ ]]></body>
+ </method>
+ <method name="_accept">
+ <parameter name="aEvent" />
+ <body><![CDATA[
+ if (aEvent.defaultPrevented)
+ return;
+ aEvent.preventDefault();
+ if (this._primaryButton.getAttribute("default") == "true") {
+ this._primaryButton.click();
+ }
+ else if (this._secondaryButton.getAttribute("default") == "true") {
+ this._secondaryButton.click();
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ <handlers>
+ <!-- The _accept method checks for .defaultPrevented so that if focus is in a button,
+ enter activates the button and not this default action -->
+ <handler event="keypress" keycode="VK_RETURN" group="system" action="this._accept(event);"/>
+ </handlers>
+ </binding>
+
+ <binding id="splitmenu">
+ <content>
+ <xul:hbox anonid="menuitem" flex="1"
+ class="splitmenu-menuitem"
+ xbl:inherits="iconic,label,disabled,onclick=oncommand,_moz-menuactive=active"/>
+ <xul:menu anonid="menu" class="splitmenu-menu"
+ xbl:inherits="disabled,_moz-menuactive=active"
+ oncommand="event.stopPropagation();">
+ <children includes="menupopup"/>
+ </xul:menu>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._parentMenupopup.addEventListener("DOMMenuItemActive", this, false);
+ this._parentMenupopup.addEventListener("popuphidden", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._parentMenupopup.removeEventListener("DOMMenuItemActive", this, false);
+ this._parentMenupopup.removeEventListener("popuphidden", this, false);
+ ]]></destructor>
+
+ <field name="menuitem" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "menuitem");
+ </field>
+ <field name="menu" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "menu");
+ </field>
+
+ <field name="_menuDelay">600</field>
+
+ <field name="_parentMenupopup"><![CDATA[
+ this._getParentMenupopup(this);
+ ]]></field>
+
+ <method name="_getParentMenupopup">
+ <parameter name="aNode"/>
+ <body><![CDATA[
+ let node = aNode.parentNode;
+ while (node) {
+ if (node.localName == "menupopup")
+ break;
+ node = node.parentNode;
+ }
+ return node;
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ switch (event.type) {
+ case "DOMMenuItemActive":
+ if (this.getAttribute("active") == "true" &&
+ event.target != this &&
+ this._getParentMenupopup(event.target) == this._parentMenupopup)
+ this.removeAttribute("active");
+ break;
+ case "popuphidden":
+ if (event.target == this._parentMenupopup)
+ this.removeAttribute("active");
+ break;
+ }
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ if (this.getAttribute("active") != "true") {
+ this.setAttribute("active", "true");
+
+ let event = document.createEvent("Events");
+ event.initEvent("DOMMenuItemActive", true, false);
+ this.dispatchEvent(event);
+
+ if (this.getAttribute("disabled") != "true") {
+ let self = this;
+ setTimeout(function () {
+ if (self.getAttribute("active") == "true")
+ self.menu.open = true;
+ }, this._menuDelay);
+ }
+ }
+ ]]></handler>
+
+ <handler event="popupshowing"><![CDATA[
+ if (event.target == this.firstChild &&
+ this._parentMenupopup._currentPopup)
+ this._parentMenupopup._currentPopup.hidePopup();
+ ]]></handler>
+
+ <handler event="click" phase="capturing"><![CDATA[
+ if (this.getAttribute("disabled") == "true") {
+ // Prevent the command from being carried out
+ event.stopPropagation();
+ return;
+ }
+
+ let node = event.originalTarget;
+ while (true) {
+ if (node == this.menuitem)
+ break;
+ if (node == this)
+ return;
+ node = node.parentNode;
+ }
+
+ this._parentMenupopup.hidePopup();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="menuitem-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem">
+ <implementation>
+ <constructor><![CDATA[
+ this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
+ // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
+ // 592424 is fixed
+ document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+ <binding id="menuitem-iconic-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem-iconic">
+ <implementation>
+ <constructor><![CDATA[
+ this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
+ // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
+ // 592424 is fixed
+ document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
+ ]]></constructor>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/browser/base/content/usercontext.svg b/browser/base/content/usercontext.svg
new file mode 100644
index 000000000..705f80bfd
--- /dev/null
+++ b/browser/base/content/usercontext.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="32" height="32" viewBox="0 0 32 32">
+ <style>
+ path, circle {
+ fill: menutext;
+ }
+ path:not(:target),
+ circle:not(:target) {
+ display: none;
+ }
+ </style>
+ <path id="dollar" d="M17.3857868,14.0527919 C14.2304569,13.0862944 13.4913706,12.4609137 13.4913706,11.0964467 C13.4913706,9.61827411 14.7137056,8.85076142 16.4192893,8.85076142 C17.9827411,8.85076142 19.3187817,9.33401015 20.5979695,10.4994924 L22.4456853,8.42436548 C21.1664975,7.20203046 19.3187819,6.26535905 17,6.00952148 L17,2 L15,2 L15,6.00952148 C12.3827412,6.43591742 9.76751269,8.53807107 9.76751269,11.3238579 C9.76751269,14.1664975 11.4730964,15.786802 15.4812183,17.0091371 C18.4375635,17.9187817 19.2335025,18.6294416 19.2335025,20.2213198 C19.2335025,22.0690355 17.7553299,23.035533 15.7370558,23.035533 C13.7756345,23.035533 12.2406091,22.3248731 10.9329949,21.1025381 L9,23.2345178 C10.4213198,24.6274112 12.8659899,25.8324934 15,26.0030518 L15,30 L17,30 L17,26.0030518 C20.7116753,25.4060974 22.9857868,22.893401 22.9857868,20.022335 C22.9857868,16.4690355 20.7116751,15.1045685 17.3857868,14.0527919 Z"/>
+ <path id="briefcase" fill-rule="evenodd" d="M22,9.99887085 L21.635468,10 L29.0034652,10 C29.5538362,10 30,10.4449463 30,10.9933977 L30,27.0066023 C30,27.5552407 29.5601869,28 29.0034652,28 L2.99653482,28 C2.44616384,28 2,27.5550537 2,27.0066023 L2,10.9933977 C2,10.4447593 2.43981314,10 2.99653482,10 L8,10 L8,7.99922997 C8,5.79051625 10.0426627,4 12.5635454,4 L19.4364546,4 C21.9568311,4 24,5.79246765 24,7.99922997 L24,9.99267578 L22,9.99887085 L22,10 L10,10 L10,7.99922997 C10,6.89421235 11.0713286,6 12.3917227,6 L19.6082773,6 C20.9273761,6 22,6.89552665 22,7.99922997 L22,9.99887085 Z"/>
+ <path id="fingerprint" d="M7.17741905,12 C7.10965537,12 7.041327,11.9953181 6.97243393,11.985018 C6.33263187,11.8918489 5.90515601,11.3862071 6.01809547,10.8552833 C7.41798011,4.26321358 12.2613889,2.57493207 15.0238882,2.15590491 C19.6448063,1.45690206 24.3408291,3.21541158 25.8344535,5.29743816 C26.1664955,5.76047488 25.9835336,6.35881757 25.4244832,6.63364321 C24.8654329,6.9098734 24.1437497,6.75583996 23.8122724,6.29327142 C22.8923805,5.01043967 19.1749781,3.51130562 15.4479759,4.07406612 C12.8080159,4.474834 9.43056132,6.03623689 8.33561323,11.1942506 C8.23453242,11.666651 7.73816348,12 7.17741905,12 Z M16.63127,26 C16.1452186,26 15.6509104,25.9658335 15.147795,25.8938767 C10.637921,25.257137 6.71207921,21.8114952 6.01575422,17.8807924 C5.91171832,17.2932317 6.33391695,16.7382846 6.95813239,16.6404441 C7.58454965,16.5343208 8.17298555,16.9406954 8.27757192,17.5272206 C8.80876054,20.5255916 11.9766264,23.26409 15.4885263,23.7610576 C17.3975027,24.02766 20.959494,23.8221432 23.3220449,19.3789425 C24.4625867,17.2331815 23.0049831,11.881462 19.9521622,9.34692739 C18.2380468,7.92384005 16.4573263,7.76905536 14.6628445,8.89499751 C13.26469,9.77142052 11.8070864,12.2857658 11.8665355,14.6287608 C11.9127737,16.4835887 12.8386382,17.9325598 14.6171568,18.9363308 C15.2210054,19.2764429 16.9411759,19.4933486 17.9424527,18.8296898 C18.7257495,18.3104622 18.9591422,17.2761485 18.6365758,15.7583267 C18.3822659,14.5650869 17.2219077,12.4452096 16.6664991,12.3711821 C16.6692513,12.3722175 16.4666841,12.4312324 16.1276041,12.9095636 C15.8545786,13.2936782 15.58981,14.7297074 15.9476054,15.3581643 C16.0142104,15.4761941 16.0725586,15.5465978 16.3202632,15.5465978 C16.9532859,15.5465978 17.46686,16.0290705 17.46686,16.6249139 C17.46686,17.2207573 16.9543868,17.7042653 16.3213641,17.7042653 C15.2644914,17.7042653 14.4140391,17.2336992 13.9268868,16.3774655 C13.1083609,14.9388479 13.5536787,12.6548678 14.2202791,11.7137354 C15.2540327,10.2564816 16.3631986,10.1151564 17.1123672,10.2564816 C19.7066595,10.7389543 20.8763754,15.2908666 20.8857331,15.3359043 C21.5303153,18.3648181 20.3594985,19.8665919 19.264094,20.593407 C17.4151172,21.8192603 14.6920186,21.493643 13.4380832,20.7859819 C10.3280151,19.0310652 9.62013053,16.497566 9.5744428,14.6805283 C9.49022326,11.3643051 11.4779146,8.30018945 13.391845,7.10021984 C16.0417332,5.43848454 18.9877658,5.66781436 21.4714167,7.72919442 C25.1176276,10.7565552 27.0871539,17.1229168 25.3746898,20.3433702 C23.4326862,23.9950465 20.2983981,26 16.63127,26 Z M16.0845157,30 C14.9348455,30 13.9050564,29.8557557 13.0394288,29.6610017 C10.2114238,29.0257442 7.58700058,27.4599412 6.18892823,25.5735955 C5.84440518,25.1078371 5.98426642,24.4803503 6.50105099,24.1700066 C7.01675554,23.8596629 7.71552172,23.986423 8.06112477,24.4507244 C9.89498097,26.9252176 15.9397944,29.9781448 22.2508301,26.1937972 C22.7676147,25.8844249 23.4658409,26.0087566 23.8109039,26.474515 C24.155427,26.9397877 24.0161057,27.5672745 23.4993212,27.8776182 C20.7987573,29.4963593 18.2315746,30 16.0845157,30 Z"/>
+ <path id="cart" fill-rule="evenodd" d="M20.8195396,14 L15.1804604,14 L15.1804604,14 L15.8471271,18 L20.1528729,18 L20.8195396,14 Z M22.8471271,14 L27.6125741,14 L27.6125741,14 L26.2792408,18 L22.1804604,18 L22.8471271,14 Z M21.1528729,12 L14.8471271,12 L14.8471271,12 L14.1804604,8 L21.8195396,8 L21.1528729,12 Z M23.1804604,12 L28.2792408,12 L28.2792408,12 L29.6125741,8 L23.8471271,8 L23.1804604,12 Z M13.1528729,14 L8.47703296,14 L10.077033,18 L10.077033,18 L13.8195396,18 L13.1528729,14 Z M12.8195396,12 L7.67703296,12 L6.07703296,8 L12.1528729,8 L12.8195396,12 L12.8195396,12 Z M31.7207592,8 L32,8 L32,6 L31,6 L5.27703296,6 L5.27703296,6 L4,2.8074176 L4,2 L3,2 L1,2 L0,2 L0,4 L1,4 L2.32296704,4 L9.78931928,22.6658806 L9.78931928,22.6658806 C8.71085924,23.3823847 8,24.6081773 8,26 C8,28.209139 9.790861,30 12,30 C14.209139,30 16,28.209139 16,26 C16,25.2714257 15.8052114,24.5883467 15.4648712,24 L22.5351288,24 C22.1947886,24.5883467 22,25.2714257 22,26 C22,28.209139 23.790861,30 26,30 C28.209139,30 30,28.209139 30,26 C30,23.790861 28.209139,22 26,22 L11.677033,22 L10.877033,20 L27,20 L28,20 L28,19.1622777 L31.7207592,8 L31.7207592,8 Z M26,28 C27.1045695,28 28,27.1045695 28,26 C28,24.8954305 27.1045695,24 26,24 C24.8954305,24 24,24.8954305 24,26 C24,27.1045695 24.8954305,28 26,28 Z M12,28 C13.1045695,28 14,27.1045695 14,26 C14,24.8954305 13.1045695,24 12,24 C10.8954305,24 10,24.8954305 10,26 C10,27.1045695 10.8954305,28 12,28 Z"/>
+ <circle id="circle" r="16" cx="16" cy="16" fill-rule="evenodd" />
+</svg>
+
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
new file mode 100644
index 000000000..7da54e064
--- /dev/null
+++ b/browser/base/content/utilityOverlay.js
@@ -0,0 +1,924 @@
+/* -*- 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/. */
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Components.utils.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
+ "resource:///modules/ShellService.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+this.__defineGetter__("BROWSER_NEW_TAB_URL", () => {
+ if (PrivateBrowsingUtils.isWindowPrivate(window) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing &&
+ !aboutNewTabService.overridden) {
+ return "about:privatebrowsing";
+ }
+ return aboutNewTabService.newTabURL;
+});
+
+var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
+
+var gBidiUI = false;
+
+/**
+ * Determines whether the given url is considered a special URL for new tabs.
+ */
+function isBlankPageURL(aURL) {
+ return aURL == "about:blank" || aURL == BROWSER_NEW_TAB_URL;
+}
+
+function getBrowserURL()
+{
+ return "chrome://browser/content/browser.xul";
+}
+
+function getTopWin(skipPopups) {
+ // If this is called in a browser window, use that window regardless of
+ // whether it's the frontmost window, since commands can be executed in
+ // background windows (bug 626148).
+ if (top.document.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ (!skipPopups || top.toolbar.visible))
+ return top;
+
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ return RecentWindow.getMostRecentBrowserWindow({private: isPrivate,
+ allowPopups: !skipPopups});
+}
+
+function openTopWin(url) {
+ /* deprecated */
+ openUILinkIn(url, "current");
+}
+
+function getBoolPref(prefname, def)
+{
+ try {
+ return Services.prefs.getBoolPref(prefname);
+ }
+ catch (er) {
+ return def;
+ }
+}
+
+/* openUILink handles clicks on UI elements that cause URLs to load.
+ *
+ * As the third argument, you may pass an object with the same properties as
+ * accepted by openUILinkIn, plus "ignoreButton" and "ignoreAlt".
+ */
+function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup,
+ aPostData, aReferrerURI) {
+ let params;
+
+ if (aIgnoreButton && typeof aIgnoreButton == "object") {
+ params = aIgnoreButton;
+
+ // don't forward "ignoreButton" and "ignoreAlt" to openUILinkIn
+ aIgnoreButton = params.ignoreButton;
+ aIgnoreAlt = params.ignoreAlt;
+ delete params.ignoreButton;
+ delete params.ignoreAlt;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI,
+ referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ initiatingDoc: event ? event.target.ownerDocument : null,
+ };
+ }
+
+ let where = whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
+ openUILinkIn(url, where, params);
+}
+
+
+/* whereToOpenLink() looks at an event to decide where to open a link.
+ *
+ * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter).
+ *
+ * On Windows, the modifiers are:
+ * Ctrl new tab, selected
+ * Shift new window
+ * Ctrl+Shift new tab, in background
+ * Alt save
+ *
+ * Middle-clicking is the same as Ctrl+clicking (it opens a new tab).
+ *
+ * Exceptions:
+ * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff.
+ * (Currently, the Alt isn't sent here at all for menu items, but that will change in bug 126189.)
+ * - Alt is hard to use in context menus, because pressing Alt closes the menu.
+ * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable".
+ * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click.
+ */
+function whereToOpenLink( e, ignoreButton, ignoreAlt )
+{
+ // This method must treat a null event like a left click without modifier keys (i.e.
+ // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 })
+ // for compatibility purposes.
+ if (!e)
+ return "current";
+
+ var shift = e.shiftKey;
+ var ctrl = e.ctrlKey;
+ var meta = e.metaKey;
+ var alt = e.altKey && !ignoreAlt;
+
+ // ignoreButton allows "middle-click paste" to use function without always opening in a new window.
+ var middle = !ignoreButton && e.button == 1;
+ var middleUsesTabs = getBoolPref("browser.tabs.opentabfor.middleclick", true);
+
+ // Don't do anything special with right-mouse clicks. They're probably clicks on context menu items.
+
+ var metaKey = AppConstants.platform == "macosx" ? meta : ctrl;
+ if (metaKey || (middle && middleUsesTabs))
+ return shift ? "tabshifted" : "tab";
+
+ if (alt && getBoolPref("browser.altClickSave", false))
+ return "save";
+
+ if (shift || (middle && !middleUsesTabs))
+ return "window";
+
+ return "current";
+}
+
+/* openUILinkIn opens a URL in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "current" current tab (if there aren't any browser windows, then in a new window instead)
+ * "tab" new tab (if there aren't any browser windows, then in a new window instead)
+ * "tabshifted" same as "tab" but in background if default is to select new tabs, and vice versa
+ * "window" new window
+ * "save" save to disk (with no filename hint!)
+ *
+ * aAllowThirdPartyFixup controls whether third party services such as Google's
+ * I Feel Lucky are allowed to interpret this URL. This parameter may be
+ * undefined, which is treated as false.
+ *
+ * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
+ * these properties:
+ * allowThirdPartyFixup (boolean)
+ * postData (nsIInputStream)
+ * referrerURI (nsIURI)
+ * relatedToCurrent (boolean)
+ * skipTabAnimation (boolean)
+ * allowPinnedTabHostChange (boolean)
+ * allowPopups (boolean)
+ * userContextId (unsigned int)
+ */
+function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
+ var params;
+
+ if (arguments.length == 3 && typeof arguments[2] == "object") {
+ params = aAllowThirdPartyFixup;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI,
+ referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ };
+ }
+
+ params.fromChrome = true;
+
+ openLinkIn(url, where, params);
+}
+
+function openLinkIn(url, where, params) {
+ if (!where || !url)
+ return;
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var aFromChrome = params.fromChrome;
+ var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ var aPostData = params.postData;
+ var aCharset = params.charset;
+ var aReferrerURI = params.referrerURI;
+ var aReferrerPolicy = ('referrerPolicy' in params ?
+ params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ var aRelatedToCurrent = params.relatedToCurrent;
+ var aAllowMixedContent = params.allowMixedContent;
+ var aInBackground = params.inBackground;
+ var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
+ var aInitiatingDoc = params.initiatingDoc;
+ var aIsPrivate = params.private;
+ var aSkipTabAnimation = params.skipTabAnimation;
+ var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
+ var aNoReferrer = params.noReferrer;
+ var aAllowPopups = !!params.allowPopups;
+ var aUserContextId = params.userContextId;
+ var aIndicateErrorPageLoad = params.indicateErrorPageLoad;
+ var aPrincipal = params.originPrincipal;
+ var aForceAboutBlankViewerInCurrent =
+ params.forceAboutBlankViewerInCurrent;
+
+ // Establish a window in which we're running this code.
+ var w = getTopWin();
+
+ if ((where == "tab" || where == "tabshifted") &&
+ w && !w.toolbar.visible) {
+ w = getTopWin(true);
+ aRelatedToCurrent = false;
+ }
+
+ // Can only do this after we're sure of what |w| will be the rest of this function.
+ // Note that if |w| is null we might have no current browser (we'll open a new window).
+ var aCurrentBrowser = params.currentBrowser || (w && w.gBrowser.selectedBrowser);
+
+ if (where == "save") {
+ // TODO(1073187): propagate referrerPolicy.
+
+ // ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc
+ if ("isContentWindowPrivate" in params) {
+ saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, null, params.isContentWindowPrivate);
+ }
+ else {
+ if (!aInitiatingDoc) {
+ Components.utils.reportError("openUILink/openLinkIn was called with " +
+ "where == 'save' but without initiatingDoc. See bug 814264.");
+ return;
+ }
+ saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, aInitiatingDoc);
+ }
+ return;
+ }
+
+ if (!w || where == "window") {
+ // This propagates to window.arguments.
+ var sa = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+
+ var wuri = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ wuri.data = url;
+
+ let charset = null;
+ if (aCharset) {
+ charset = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ charset.data = "charset=" + aCharset;
+ }
+
+ var allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
+
+ var referrerURISupports = null;
+ if (aReferrerURI && !aNoReferrer) {
+ referrerURISupports = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ referrerURISupports.data = aReferrerURI.spec;
+ }
+
+ var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ referrerPolicySupports.data = aReferrerPolicy;
+
+ var userContextIdSupports = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ userContextIdSupports.data = aUserContextId;
+
+ sa.appendElement(wuri, /* weak =*/ false);
+ sa.appendElement(charset, /* weak =*/ false);
+ sa.appendElement(referrerURISupports, /* weak =*/ false);
+ sa.appendElement(aPostData, /* weak =*/ false);
+ sa.appendElement(allowThirdPartyFixupSupports, /* weak =*/ false);
+ sa.appendElement(referrerPolicySupports, /* weak =*/ false);
+ sa.appendElement(userContextIdSupports, /* weak =*/ false);
+ sa.appendElement(aPrincipal, /* weak =*/ false);
+
+ let features = "chrome,dialog=no,all";
+ if (aIsPrivate) {
+ features += ",private";
+ }
+
+ Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
+ return;
+ }
+
+ let loadInBackground = where == "current" ? false : aInBackground;
+ if (loadInBackground == null) {
+ loadInBackground = aFromChrome ?
+ false :
+ getBoolPref("browser.tabs.loadInBackground");
+ }
+
+ let uriObj;
+ if (where == "current") {
+ try {
+ uriObj = Services.io.newURI(url, null, null);
+ } catch (e) {}
+ }
+
+ // We avoid using |w| here because in the 'popup window' case,
+ // if we pass a currentBrowser param |w.gBrowser| might not be the
+ // tabbrowser that contains |aCurrentBrowser|. We really only care
+ // about the tab linked to |aCurrentBrowser|.
+ let tab = aCurrentBrowser.getTabBrowser().getTabForBrowser(aCurrentBrowser);
+ if (where == "current" && tab.pinned &&
+ !aAllowPinnedTabHostChange) {
+ try {
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+ if (!uriObj || (!uriObj.schemeIs("javascript") &&
+ aCurrentBrowser.currentURI.host != uriObj.host)) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ } catch (err) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ }
+
+ // Raise the target window before loading the URI, since loading it may
+ // result in a new frontmost window (e.g. "javascript:window.open('');").
+ w.focus();
+
+ let browserUsedForLoad = null;
+ switch (where) {
+ case "current":
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+
+ // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL isn't supported for javascript URIs,
+ // i.e. it causes them not to load at all. Callers should strip
+ // "javascript:" from pasted strings to protect users from malicious URIs
+ // (see stripUnsafeProtocolOnPaste).
+ if (aDisallowInheritPrincipal && !(uriObj && uriObj.schemeIs("javascript"))) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
+ }
+
+ if (aAllowPopups) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
+ }
+ if (aIndicateErrorPageLoad) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
+ }
+
+ let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+ if (aForceAboutBlankViewerInCurrent &&
+ (!uriObj ||
+ (Services.io.getProtocolFlags(uriObj.scheme) & URI_INHERITS_SECURITY_CONTEXT))) {
+ // Unless we know for sure we're not inheriting principals,
+ // force the about:blank viewer to have the right principal:
+ aCurrentBrowser.createAboutBlankContentViewer(aPrincipal);
+ }
+
+ aCurrentBrowser.loadURIWithFlags(url, {
+ flags: flags,
+ referrerURI: aNoReferrer ? null : aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ postData: aPostData,
+ userContextId: aUserContextId
+ });
+ browserUsedForLoad = aCurrentBrowser;
+ break;
+ case "tabshifted":
+ loadInBackground = !loadInBackground;
+ // fall through
+ case "tab":
+ let tabUsedForLoad = w.gBrowser.loadOneTab(url, {
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: loadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ relatedToCurrent: aRelatedToCurrent,
+ skipAnimation: aSkipTabAnimation,
+ allowMixedContent: aAllowMixedContent,
+ noReferrer: aNoReferrer,
+ userContextId: aUserContextId,
+ originPrincipal: aPrincipal,
+ });
+ browserUsedForLoad = tabUsedForLoad.linkedBrowser;
+ break;
+ }
+
+ // Focus the content, but only if the browser used for the load is selected.
+ if (browserUsedForLoad &&
+ browserUsedForLoad == browserUsedForLoad.getTabBrowser().selectedBrowser) {
+ browserUsedForLoad.focus();
+ }
+
+ if (!loadInBackground && w.isBlankPageURL(url)) {
+ w.focusAndSelectUrlBar();
+ }
+}
+
+// Used as an onclick handler for UI elements with link-like behavior.
+// e.g. onclick="checkForMiddleClick(this, event);"
+function checkForMiddleClick(node, event) {
+ // We should be using the disabled property here instead of the attribute,
+ // but some elements that this function is used with don't support it (e.g.
+ // menuitem).
+ if (node.getAttribute("disabled") == "true")
+ return; // Do nothing
+
+ if (event.button == 1) {
+ /* Execute the node's oncommand or command.
+ *
+ * XXX: we should use node.oncommand(event) once bug 246720 is fixed.
+ */
+ var target = node.hasAttribute("oncommand") ? node :
+ node.ownerDocument.getElementById(node.getAttribute("command"));
+ var fn = new Function("event", target.getAttribute("oncommand"));
+ fn.call(target, event);
+
+ // If the middle-click was on part of a menu, close the menu.
+ // (Menus close automatically with left-click but not with middle-click.)
+ closeMenus(event.target);
+ }
+}
+
+// Populate a menu with user-context menu items. This method should be called
+// by onpopupshowing passing the event as first argument.
+function createUserContextMenu(event, isContextMenu = false, excludeUserContextId = 0) {
+ while (event.target.hasChildNodes()) {
+ event.target.removeChild(event.target.firstChild);
+ }
+
+ let bundle = document.getElementById("bundle_browser");
+ let docfrag = document.createDocumentFragment();
+
+ // If we are excluding a userContextId, we want to add a 'no-container' item.
+ if (excludeUserContextId) {
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("data-usercontextid", "0");
+ menuitem.setAttribute("label", bundle.getString("userContextNone.label"));
+ menuitem.setAttribute("accesskey", bundle.getString("userContextNone.accesskey"));
+
+ // We don't set an oncommand/command attribute because if we have
+ // to exclude a userContextId we are generating the contextMenu and
+ // isContextMenu will be true.
+
+ docfrag.appendChild(menuitem);
+
+ let menuseparator = document.createElement("menuseparator");
+ docfrag.appendChild(menuseparator);
+ }
+
+ ContextualIdentityService.getIdentities().forEach(identity => {
+ if (identity.userContextId == excludeUserContextId) {
+ return;
+ }
+
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("data-usercontextid", identity.userContextId);
+ menuitem.setAttribute("label", ContextualIdentityService.getUserContextLabel(identity.userContextId));
+
+ if (identity.accessKey) {
+ menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+ }
+
+ menuitem.classList.add("menuitem-iconic");
+ menuitem.setAttribute("data-identity-color", identity.color);
+
+ if (!isContextMenu) {
+ menuitem.setAttribute("command", "Browser:NewUserContextTab");
+ }
+
+ menuitem.setAttribute("data-identity-icon", identity.icon);
+
+ docfrag.appendChild(menuitem);
+ });
+
+ if (!isContextMenu) {
+ docfrag.appendChild(document.createElement("menuseparator"));
+
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label",
+ bundle.getString("userContext.aboutPage.label"));
+ menuitem.setAttribute("accesskey",
+ bundle.getString("userContext.aboutPage.accesskey"));
+ menuitem.setAttribute("command", "Browser:OpenAboutContainers");
+ docfrag.appendChild(menuitem);
+ }
+
+ event.target.appendChild(docfrag);
+ return true;
+}
+
+// Closes all popups that are ancestors of the node.
+function closeMenus(node)
+{
+ if ("tagName" in node) {
+ if (node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ && (node.tagName == "menupopup" || node.tagName == "popup"))
+ node.hidePopup();
+
+ closeMenus(node.parentNode);
+ }
+}
+
+/** This function takes in a key element and compares it to the keys pressed during an event.
+ *
+ * @param aEvent
+ * The KeyboardEvent event you want to compare against your key.
+ *
+ * @param aKey
+ * The <key> element checked to see if it was called in aEvent.
+ * For example, aKey can be a variable set to document.getElementById("key_close")
+ * to check if the close command key was pressed in aEvent.
+*/
+function eventMatchesKey(aEvent, aKey)
+{
+ let keyPressed = aKey.getAttribute("key").toLowerCase();
+ let keyModifiers = aKey.getAttribute("modifiers");
+ let modifiers = ["Alt", "Control", "Meta", "Shift"];
+
+ if (aEvent.key != keyPressed) {
+ return false;
+ }
+ let eventModifiers = modifiers.filter(modifier => aEvent.getModifierState(modifier));
+ // Check if aEvent has a modifier and aKey doesn't
+ if (eventModifiers.length > 0 && keyModifiers.length == 0) {
+ return false;
+ }
+ // Check whether aKey's modifiers match aEvent's modifiers
+ if (keyModifiers) {
+ keyModifiers = keyModifiers.split(/[\s,]+/);
+ // Capitalize first letter of aKey's modifers to compare to aEvent's modifier
+ keyModifiers.forEach(function(modifier, index) {
+ if (modifier == "accel") {
+ keyModifiers[index] = AppConstants.platform == "macosx" ? "Meta" : "Control";
+ } else {
+ keyModifiers[index] = modifier[0].toUpperCase() + modifier.slice(1);
+ }
+ });
+ return modifiers.every(modifier => keyModifiers.includes(modifier) == aEvent.getModifierState(modifier));
+ }
+ return true;
+}
+
+// Gather all descendent text under given document node.
+function gatherTextUnder ( root )
+{
+ var text = "";
+ var node = root.firstChild;
+ var depth = 1;
+ while ( node && depth > 0 ) {
+ // See if this node is text.
+ if ( node.nodeType == Node.TEXT_NODE ) {
+ // Add this text to our collection.
+ text += " " + node.data;
+ } else if ( node instanceof HTMLImageElement) {
+ // If it has an "alt" attribute, add that.
+ var altText = node.getAttribute( "alt" );
+ if ( altText && altText != "" ) {
+ text += " " + altText;
+ }
+ }
+ // Find next node to test.
+ // First, see if this node has children.
+ if ( node.hasChildNodes() ) {
+ // Go to first child.
+ node = node.firstChild;
+ depth++;
+ } else {
+ // No children, try next sibling (or parent next sibling).
+ while ( depth > 0 && !node.nextSibling ) {
+ node = node.parentNode;
+ depth--;
+ }
+ if ( node.nextSibling ) {
+ node = node.nextSibling;
+ }
+ }
+ }
+ // Strip leading and tailing whitespace.
+ text = text.trim();
+ // Compress remaining whitespace.
+ text = text.replace( /\s+/g, " " );
+ return text;
+}
+
+// This function exists for legacy reasons.
+function getShellService()
+{
+ return ShellService;
+}
+
+function isBidiEnabled() {
+ // first check the pref.
+ if (getBoolPref("bidi.browser.ui", false))
+ return true;
+
+ // then check intl.uidirection.<locale>
+ var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Components.interfaces.nsIXULChromeRegistry);
+ if (chromeReg.isLocaleRTL("global"))
+ return true;
+
+ // now see if the system locale is an RTL one.
+ var rv = false;
+
+ try {
+ var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
+ .getService(Components.interfaces.nsILocaleService);
+ var systemLocale = localeService.getSystemLocale().getCategory("NSILOCALE_CTYPE").substr(0, 3);
+
+ switch (systemLocale) {
+ case "ar-":
+ case "he-":
+ case "fa-":
+ case "ug-":
+ case "ur-":
+ case "syr":
+ rv = true;
+ Services.prefs.setBoolPref("bidi.browser.ui", true);
+ }
+ } catch (e) {}
+
+ return rv;
+}
+
+function openAboutDialog() {
+ var enumerator = Services.wm.getEnumerator("Browser:About");
+ while (enumerator.hasMoreElements()) {
+ // Only open one about window (Bug 599573)
+ let win = enumerator.getNext();
+ if (win.closed) {
+ continue;
+ }
+ win.focus();
+ return;
+ }
+
+ var features = "chrome,";
+ if (AppConstants.platform == "win") {
+ features += "centerscreen,dependent";
+ } else if (AppConstants.platform == "macosx") {
+ features += "resizable=no,minimizable=no";
+ } else {
+ features += "centerscreen,dependent,dialog=no";
+ }
+
+ window.openDialog("chrome://browser/content/aboutDialog.xul", "", features);
+}
+
+function openPreferences(paneID, extraArgs)
+{
+ function switchToAdvancedSubPane(doc) {
+ if (extraArgs && extraArgs["advancedTab"]) {
+ let advancedPaneTabs = doc.getElementById("advancedPrefs");
+ advancedPaneTabs.selectedTab = doc.getElementById(extraArgs["advancedTab"]);
+ }
+ }
+
+ // This function is duplicated from preferences.js.
+ function internalPrefCategoryNameToFriendlyName(aName) {
+ return (aName || "").replace(/^pane./, function(toReplace) { return toReplace[4].toLowerCase(); });
+ }
+
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let friendlyCategoryName = internalPrefCategoryNameToFriendlyName(paneID);
+ let params;
+ if (extraArgs && extraArgs["urlParams"]) {
+ params = new URLSearchParams();
+ let urlParams = extraArgs["urlParams"];
+ for (let name in urlParams) {
+ if (urlParams[name] !== undefined) {
+ params.set(name, urlParams[name]);
+ }
+ }
+ }
+ let preferencesURL = "about:preferences" + (params ? "?" + params : "") +
+ (friendlyCategoryName ? "#" + friendlyCategoryName : "");
+ let newLoad = true;
+ let browser = null;
+ if (!win) {
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ let windowArguments = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ let supportsStringPrefURL = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ supportsStringPrefURL.data = preferencesURL;
+ windowArguments.appendElement(supportsStringPrefURL, /* weak =*/ false);
+
+ win = Services.ww.openWindow(null, Services.prefs.getCharPref("browser.chromeURL"),
+ "_blank", "chrome,dialog=no,all", windowArguments);
+ } else {
+ let shouldReplaceFragment = friendlyCategoryName ? "whenComparingAndReplace" : "whenComparing";
+ newLoad = !win.switchToTabHavingURI(preferencesURL, true, { ignoreFragment: shouldReplaceFragment, replaceQueryString: true });
+ browser = win.gBrowser.selectedBrowser;
+ }
+
+ if (newLoad) {
+ Services.obs.addObserver(function advancedPaneLoadedObs(prefWin, topic, data) {
+ if (!browser) {
+ browser = win.gBrowser.selectedBrowser;
+ }
+ if (prefWin != browser.contentWindow) {
+ return;
+ }
+ Services.obs.removeObserver(advancedPaneLoadedObs, "advanced-pane-loaded");
+ switchToAdvancedSubPane(browser.contentDocument);
+ }, "advanced-pane-loaded", false);
+ } else {
+ if (paneID) {
+ browser.contentWindow.gotoPref(paneID);
+ }
+ switchToAdvancedSubPane(browser.contentDocument);
+ }
+}
+
+function openAdvancedPreferences(tabID)
+{
+ openPreferences("paneAdvanced", { "advancedTab" : tabID });
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openTroubleshootingPage()
+{
+ openUILinkIn("about:support", "tab");
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openHealthReport()
+{
+ openUILinkIn("about:healthreport", "tab");
+}
+
+/**
+ * Opens the feedback page for this version of the application.
+ */
+function openFeedbackPage()
+{
+ var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter)
+ .formatURLPref("app.feedback.baseURL");
+ openUILinkIn(url, "tab");
+}
+
+function openTourPage()
+{
+ let scope = {}
+ Components.utils.import("resource:///modules/UITour.jsm", scope);
+ openUILinkIn(scope.UITour.url, "tab");
+}
+
+function buildHelpMenu()
+{
+ // Enable/disable the "Report Web Forgery" menu item.
+ if (typeof gSafeBrowsing != "undefined") {
+ gSafeBrowsing.setReportPhishingMenu();
+ }
+}
+
+function isElementVisible(aElement)
+{
+ if (!aElement)
+ return false;
+
+ // If aElement or a direct or indirect parent is hidden or collapsed,
+ // height, width or both will be 0.
+ var bo = aElement.boxObject;
+ return (bo.height > 0 && bo.width > 0);
+}
+
+function makeURLAbsolute(aBase, aUrl)
+{
+ // Note: makeURI() will throw if aUri is not a valid URI
+ return makeURI(aUrl, null, makeURI(aBase)).spec;
+}
+
+/**
+ * openNewTabWith: opens a new tab with the given URL.
+ *
+ * @param aURL
+ * The URL to open (as a string).
+ * @param aDocument
+ * Note this parameter is now ignored. There is no security check & no
+ * referrer header derived from aDocument (null case).
+ * @param aPostData
+ * Form POST data, or null.
+ * @param aEvent
+ * The triggering event (for the purpose of determining whether to open
+ * in the background), or null.
+ * @param aAllowThirdPartyFixup
+ * If true, then we allow the URL text to be sent to third party services
+ * (e.g., Google's I Feel Lucky) for interpretation. This parameter may
+ * be undefined in which case it is treated as false.
+ * @param [optional] aReferrer
+ * This will be used as the referrer. There will be no security check.
+ * @param [optional] aReferrerPolicy
+ * Referrer policy - Ci.nsIHttpChannel.REFERRER_POLICY_*.
+ */
+function openNewTabWith(aURL, aDocument, aPostData, aEvent,
+ aAllowThirdPartyFixup, aReferrer, aReferrerPolicy) {
+
+ // As in openNewWindowWith(), we want to pass the charset of the
+ // current document over to a new tab.
+ let originCharset = null;
+ if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = gBrowser.selectedBrowser.characterSet;
+
+ openLinkIn(aURL, aEvent && aEvent.shiftKey ? "tabshifted" : "tab",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aReferrer,
+ referrerPolicy: aReferrerPolicy,
+ });
+}
+
+/**
+ * @param aDocument
+ * Note this parameter is ignored. See openNewTabWith()
+ */
+function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup,
+ aReferrer, aReferrerPolicy) {
+ // Extract the current charset menu setting from the current document and
+ // use it to initialize the new browser window...
+ let originCharset = null;
+ if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = gBrowser.selectedBrowser.characterSet;
+
+ openLinkIn(aURL, "window",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aReferrer,
+ referrerPolicy: aReferrerPolicy,
+ });
+}
+
+function getHelpLinkURL(aHelpTopic) {
+ var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter)
+ .formatURLPref("app.support.baseURL");
+ return url + aHelpTopic;
+}
+
+// aCalledFromModal is optional
+function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
+ var url = getHelpLinkURL(aHelpTopic);
+ var where = aWhere;
+ if (!aWhere)
+ where = aCalledFromModal ? "window" : "tab";
+
+ openUILinkIn(url, where);
+}
+
+function openPrefsHelp() {
+ // non-instant apply prefwindows are usually modal, so we can't open in the topmost window,
+ // since its probably behind the window.
+ var instantApply = getBoolPref("browser.preferences.instantApply");
+
+ var helpTopic = document.getElementsByTagName("prefwindow")[0].currentPane.helpTopic;
+ openHelpLink(helpTopic, !instantApply);
+}
+
+function trimURL(aURL) {
+ // This function must not modify the given URL such that calling
+ // nsIURIFixup::createFixupURI with the result will produce a different URI.
+
+ // remove single trailing slash for http/https/ftp URLs
+ let url = aURL.replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1");
+
+ // remove http://
+ if (!url.startsWith("http://")) {
+ return url;
+ }
+ let urlWithoutProtocol = url.substring(7);
+
+ let flags = Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP |
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ let fixedUpURL, expectedURLSpec;
+ try {
+ fixedUpURL = Services.uriFixup.createFixupURI(urlWithoutProtocol, flags);
+ expectedURLSpec = makeURI(aURL).spec;
+ } catch (ex) {
+ return url;
+ }
+ if (fixedUpURL.spec == expectedURLSpec) {
+ return urlWithoutProtocol;
+ }
+ return url;
+}
diff --git a/browser/base/content/viewSourceOverlay.xul b/browser/base/content/viewSourceOverlay.xul
new file mode 100644
index 000000000..8b40ddfd2
--- /dev/null
+++ b/browser/base/content/viewSourceOverlay.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+
+<overlay id="viewSourceOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="viewSource">
+ <commandset id="baseMenuCommandSet"/>
+ <keyset id="baseMenuKeyset"/>
+ <stringbundleset id="stringbundleset"/>
+</window>
+
+<menubar id="viewSource-main-menubar">
+#ifdef XP_MACOSX
+ <menu id="windowMenu"/>
+ <menupopup id="menu_ToolsPopup"/>
+#endif
+ <menu id="helpMenu"/>
+</menubar>
+
+</overlay>
diff --git a/browser/base/content/web-panels.js b/browser/base/content/web-panels.js
new file mode 100644
index 000000000..3a64b92a0
--- /dev/null
+++ b/browser/base/content/web-panels.js
@@ -0,0 +1,104 @@
+/* -*- 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 NS_ERROR_MODULE_NETWORK = 2152398848;
+const NS_NET_STATUS_READ_FROM = NS_ERROR_MODULE_NETWORK + 8;
+const NS_NET_STATUS_WROTE_TO = NS_ERROR_MODULE_NETWORK + 9;
+
+function getPanelBrowser()
+{
+ return document.getElementById("web-panels-browser");
+}
+
+var panelProgressListener = {
+ onProgressChange : function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (!aRequest)
+ return;
+
+ // ignore local/resource:/chrome: files
+ if (aStatus == NS_NET_STATUS_READ_FROM || aStatus == NS_NET_STATUS_WROTE_TO)
+ return;
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ window.parent.document.getElementById('sidebar-throbber').setAttribute("loading", "true");
+ }
+ else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ window.parent.document.getElementById('sidebar-throbber').removeAttribute("loading");
+ }
+ }
+ ,
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags) {
+ UpdateBackForwardCommands(getPanelBrowser().webNavigation);
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) {
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState) {
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ }
+};
+
+var gLoadFired = false;
+function loadWebPanel(aURI) {
+ var panelBrowser = getPanelBrowser();
+ if (gLoadFired) {
+ panelBrowser.webNavigation
+ .loadURI(aURI, nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, null, null);
+ }
+ panelBrowser.setAttribute("cachedurl", aURI);
+}
+
+function load()
+{
+ var panelBrowser = getPanelBrowser();
+ panelBrowser.webProgress.addProgressListener(panelProgressListener,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ panelBrowser.messageManager.loadFrameScript("chrome://browser/content/content.js", true);
+ var cachedurl = panelBrowser.getAttribute("cachedurl")
+ if (cachedurl) {
+ panelBrowser.webNavigation
+ .loadURI(cachedurl, nsIWebNavigation.LOAD_FLAGS_NONE, null,
+ null, null);
+ }
+
+ gLoadFired = true;
+}
+
+function unload()
+{
+ getPanelBrowser().webProgress.removeProgressListener(panelProgressListener);
+}
+
+function PanelBrowserStop()
+{
+ getPanelBrowser().webNavigation.stop(nsIWebNavigation.STOP_ALL)
+}
+
+function PanelBrowserReload()
+{
+ getPanelBrowser().webNavigation
+ .sessionHistory
+ .QueryInterface(nsIWebNavigation)
+ .reload(nsIWebNavigation.LOAD_FLAGS_NONE);
+}
diff --git a/browser/base/content/web-panels.xul b/browser/base/content/web-panels.xul
new file mode 100644
index 000000000..4693d878b
--- /dev/null
+++ b/browser/base/content/web-panels.xul
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE page [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd">
+%textcontextDTD;
+]>
+
+<page id="webpanels-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="load()" onunload="unload()">
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/browser.js"/>
+ <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
+ <script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
+ <script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
+ <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
+ <script type="application/javascript" src="chrome://browser/content/web-panels.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
+ </stringbundleset>
+
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="isFrameImage"/>
+ </broadcasterset>
+
+ <commandset id="mainCommandset">
+ <command id="Browser:Back"
+ oncommand="getPanelBrowser().webNavigation.goBack();"
+ disabled="true"/>
+ <command id="Browser:Forward"
+ oncommand="getPanelBrowser().webNavigation.goForward();"
+ disabled="true"/>
+ <command id="Browser:Stop" oncommand="PanelBrowserStop();"/>
+ <command id="Browser:Reload" oncommand="PanelBrowserReload();"/>
+ </commandset>
+
+ <popupset id="mainPopupSet">
+ <tooltip id="aHTMLTooltip" page="true"/>
+ <menupopup id="contentAreaContextMenu" pagemenu="start"
+ onpopupshowing="if (event.target != this)
+ return true;
+ gContextMenu = new nsContextMenu(this, event.shiftKey);
+ if (gContextMenu.shouldDisplay)
+ document.popupNode = this.triggerNode;
+ return gContextMenu.shouldDisplay;"
+ onpopuphiding="if (event.target != this)
+ return;
+ gContextMenu.hiding();
+ gContextMenu = null;">
+#include browser-context.inc
+ </menupopup>
+ </popupset>
+
+ <commandset id="editMenuCommands"/>
+ <browser id="web-panels-browser" persist="cachedurl" type="content" flex="1"
+ context="contentAreaContextMenu" tooltip="aHTMLTooltip"
+ onclick="window.parent.contentAreaClick(event, true);"/>
+</page>
diff --git a/browser/base/content/webrtcIndicator.js b/browser/base/content/webrtcIndicator.js
new file mode 100644
index 000000000..301607031
--- /dev/null
+++ b/browser/base/content/webrtcIndicator.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/. */
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/webrtcUI.jsm");
+
+const BUNDLE_URL = "chrome://browser/locale/webrtcIndicator.properties";
+var gStringBundle;
+
+function init(event) {
+ gStringBundle = Services.strings.createBundle(BUNDLE_URL);
+
+ let brand = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ let brandShortName = brand.GetStringFromName("brandShortName");
+ document.title =
+ gStringBundle.formatStringFromName("webrtcIndicator.windowtitle",
+ [brandShortName], 1);
+
+ for (let id of ["audioVideoButton", "screenSharePopup"]) {
+ let popup = document.getElementById(id);
+ popup.addEventListener("popupshowing", onPopupMenuShowing);
+ popup.addEventListener("popuphiding", onPopupMenuHiding);
+ popup.addEventListener("command", onPopupMenuCommand);
+ }
+
+ let fxButton = document.getElementById("firefoxButton");
+ fxButton.addEventListener("click", onFirefoxButtonClick);
+ fxButton.addEventListener("mousedown", PositionHandler);
+
+ updateIndicatorState();
+
+ // Alert accessibility implementations stuff just changed. We only need to do
+ // this initially, because changes after this will automatically fire alert
+ // events if things change materially.
+ let ev = new CustomEvent("AlertActive", {bubbles: true, cancelable: true});
+ document.documentElement.dispatchEvent(ev);
+}
+
+function updateIndicatorState() {
+ updateWindowAttr("sharingvideo", webrtcUI.showCameraIndicator);
+ updateWindowAttr("sharingaudio", webrtcUI.showMicrophoneIndicator);
+ updateWindowAttr("sharingscreen", webrtcUI.showScreenSharingIndicator);
+
+ // Camera and microphone button tooltip.
+ let shareTypes = [];
+ if (webrtcUI.showCameraIndicator)
+ shareTypes.push("Camera");
+ if (webrtcUI.showMicrophoneIndicator)
+ shareTypes.push("Microphone");
+
+ let audioVideoButton = document.getElementById("audioVideoButton");
+ if (shareTypes.length) {
+ let stringId = "webrtcIndicator.sharing" + shareTypes.join("And") + ".tooltip";
+ audioVideoButton.setAttribute("tooltiptext",
+ gStringBundle.GetStringFromName(stringId));
+ }
+ else {
+ audioVideoButton.removeAttribute("tooltiptext");
+ }
+
+ // Screen sharing button tooltip.
+ let screenShareButton = document.getElementById("screenShareButton");
+ if (webrtcUI.showScreenSharingIndicator) {
+ let stringId = "webrtcIndicator.sharing" +
+ webrtcUI.showScreenSharingIndicator + ".tooltip";
+ screenShareButton.setAttribute("tooltiptext",
+ gStringBundle.GetStringFromName(stringId));
+ }
+ else {
+ screenShareButton.removeAttribute("tooltiptext");
+ }
+
+ // Resize and ensure the window position is correct
+ // (sizeToContent messes with our position).
+ window.sizeToContent();
+ PositionHandler.adjustPosition();
+}
+
+function updateWindowAttr(attr, value) {
+ let docEl = document.documentElement;
+ if (value)
+ docEl.setAttribute(attr, "true");
+ else
+ docEl.removeAttribute(attr);
+}
+
+function onPopupMenuShowing(event) {
+ let popup = event.target;
+ let type = popup.getAttribute("type");
+
+ let activeStreams;
+ if (type == "Devices")
+ activeStreams = webrtcUI.getActiveStreams(true, true, false);
+ else
+ activeStreams = webrtcUI.getActiveStreams(false, false, true);
+
+ if (activeStreams.length == 1) {
+ webrtcUI.showSharingDoorhanger(activeStreams[0], type);
+ event.preventDefault();
+ return;
+ }
+
+ for (let stream of activeStreams) {
+ let item = document.createElement("menuitem");
+ item.setAttribute("label", stream.browser.contentTitle || stream.uri);
+ item.setAttribute("tooltiptext", stream.uri);
+ item.stream = stream;
+ popup.appendChild(item);
+ }
+}
+
+function onPopupMenuHiding(event) {
+ let popup = event.target;
+ while (popup.firstChild)
+ popup.firstChild.remove();
+}
+
+function onPopupMenuCommand(event) {
+ let item = event.target;
+ webrtcUI.showSharingDoorhanger(item.stream,
+ item.parentNode.getAttribute("type"));
+}
+
+function onFirefoxButtonClick(event) {
+ event.target.blur();
+ let activeStreams = webrtcUI.getActiveStreams(true, true, true);
+ activeStreams[0].browser.ownerGlobal.focus();
+}
+
+var PositionHandler = {
+ positionCustomized: false,
+ threshold: 10,
+ adjustPosition: function() {
+ if (!this.positionCustomized) {
+ // Center the window horizontally on the screen (not the available area).
+ // Until we have moved the window to y=0, 'screen.width' may give a value
+ // for a secondary screen, so use values from the screen manager instead.
+ let primaryScreen = Cc["@mozilla.org/gfx/screenmanager;1"]
+ .getService(Ci.nsIScreenManager)
+ .primaryScreen;
+ let widthDevPix = {};
+ primaryScreen.GetRect({}, {}, widthDevPix, {});
+ let availTopDevPix = {};
+ primaryScreen.GetAvailRect({}, availTopDevPix, {}, {});
+ let scaleFactor = primaryScreen.defaultCSSScaleFactor;
+ let widthCss = widthDevPix.value / scaleFactor;
+ window.moveTo((widthCss - document.documentElement.clientWidth) / 2,
+ availTopDevPix.value / scaleFactor);
+ } else {
+ // This will ensure we're at y=0.
+ this.setXPosition(window.screenX);
+ }
+ },
+ setXPosition: function(desiredX) {
+ // Ensure the indicator isn't moved outside the available area of the screen.
+ desiredX = Math.max(desiredX, screen.availLeft);
+ let maxX =
+ screen.availLeft + screen.availWidth - document.documentElement.clientWidth;
+ window.moveTo(Math.min(desiredX, maxX), screen.availTop);
+ },
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "mousedown":
+ if (aEvent.button != 0 || aEvent.defaultPrevented)
+ return;
+
+ this._startMouseX = aEvent.screenX;
+ this._startWindowX = window.screenX;
+ this._deltaX = this._startMouseX - this._startWindowX;
+
+ window.addEventListener("mousemove", this);
+ window.addEventListener("mouseup", this);
+ break;
+
+ case "mousemove":
+ let moveOffset = Math.abs(aEvent.screenX - this._startMouseX);
+ if (this._dragFullyStarted || moveOffset > this.threshold) {
+ this.setXPosition(aEvent.screenX - this._deltaX);
+ this._dragFullyStarted = true;
+ }
+ break;
+
+ case "mouseup":
+ this._dragFullyStarted = false;
+ window.removeEventListener("mousemove", this);
+ window.removeEventListener("mouseup", this);
+ this.positionCustomized =
+ Math.abs(this._startWindowX - window.screenX) >= this.threshold;
+ break;
+ }
+ }
+};
diff --git a/browser/base/content/webrtcIndicator.xul b/browser/base/content/webrtcIndicator.xul
new file mode 100644
index 000000000..9208dc814
--- /dev/null
+++ b/browser/base/content/webrtcIndicator.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://browser/skin/webRTC-indicator.css" type="text/css"?>
+
+<!DOCTYPE window>
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="webrtcIndicator"
+ role="alert"
+ windowtype="Browser:WebRTCGlobalIndicator"
+ onload="init(event);"
+#ifdef XP_MACOSX
+ inwindowmenu="false"
+#endif
+ sizemode="normal"
+ hidechrome="true"
+ orient="horizontal"
+ >
+ <script type="application/javascript" src="chrome://browser/content/webrtcIndicator.js"/>
+
+ <button id="firefoxButton"/>
+ <button id="audioVideoButton" type="menu">
+ <menupopup id="audioVideoPopup" type="Devices"/>
+ </button>
+ <separator id="shareSeparator"/>
+ <button id="screenShareButton" type="menu">
+ <menupopup id="screenSharePopup" type="Screen"/>
+ </button>
+</window>
diff --git a/browser/base/content/win6BrowserOverlay.xul b/browser/base/content/win6BrowserOverlay.xul
new file mode 100644
index 000000000..a69e3f6bd
--- /dev/null
+++ b/browser/base/content/win6BrowserOverlay.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+
+<!-- -*- Mode: HTML -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="win6-browser-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <toolbar id="toolbar-menubar"
+ autohide="true"/>
+</overlay>
diff --git a/browser/base/jar.mn b/browser/base/jar.mn
new file mode 100644
index 000000000..4dcd47c95
--- /dev/null
+++ b/browser/base/jar.mn
@@ -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/.
+browser.jar:
+% content browser %content/browser/ contentaccessible=yes
+#ifdef XP_MACOSX
+% overlay chrome://mozapps/content/downloads/downloads.xul chrome://browser/content/downloadManagerOverlay.xul
+% overlay chrome://mozapps/content/update/updates.xul chrome://browser/content/softwareUpdateOverlay.xul
+#endif
+#ifdef XP_WIN
+% overlay chrome://browser/content/browser.xul chrome://browser/content/win6BrowserOverlay.xul os=WINNT osversion>=6
+#endif
+% overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
+% overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
+
+ content/browser/aboutDialog-appUpdater.js (content/aboutDialog-appUpdater.js)
+* content/browser/aboutDialog.xul (content/aboutDialog.xul)
+ content/browser/aboutDialog.js (content/aboutDialog.js)
+ content/browser/aboutDialog.css (content/aboutDialog.css)
+ content/browser/aboutRobots.xhtml (content/aboutRobots.xhtml)
+* content/browser/abouthome/aboutHome.xhtml (content/abouthome/aboutHome.xhtml)
+ content/browser/abouthome/aboutHome.js (content/abouthome/aboutHome.js)
+* content/browser/abouthome/aboutHome.css (content/abouthome/aboutHome.css)
+ content/browser/abouthome/snippet1.png (content/abouthome/snippet1.png)
+ content/browser/abouthome/snippet2.png (content/abouthome/snippet2.png)
+ content/browser/abouthome/downloads.png (content/abouthome/downloads.png)
+ content/browser/abouthome/bookmarks.png (content/abouthome/bookmarks.png)
+ content/browser/abouthome/history.png (content/abouthome/history.png)
+ content/browser/abouthome/addons.png (content/abouthome/addons.png)
+ content/browser/abouthome/sync.png (content/abouthome/sync.png)
+ content/browser/abouthome/settings.png (content/abouthome/settings.png)
+ content/browser/abouthome/restore.png (content/abouthome/restore.png)
+ content/browser/abouthome/restore-large.png (content/abouthome/restore-large.png)
+ content/browser/abouthome/mozilla.png (content/abouthome/mozilla.png)
+ content/browser/abouthome/snippet1@2x.png (content/abouthome/snippet1@2x.png)
+ content/browser/abouthome/snippet2@2x.png (content/abouthome/snippet2@2x.png)
+ content/browser/abouthome/downloads@2x.png (content/abouthome/downloads@2x.png)
+ content/browser/abouthome/bookmarks@2x.png (content/abouthome/bookmarks@2x.png)
+ content/browser/abouthome/history@2x.png (content/abouthome/history@2x.png)
+ content/browser/abouthome/addons@2x.png (content/abouthome/addons@2x.png)
+ content/browser/abouthome/sync@2x.png (content/abouthome/sync@2x.png)
+ content/browser/abouthome/settings@2x.png (content/abouthome/settings@2x.png)
+ content/browser/abouthome/restore@2x.png (content/abouthome/restore@2x.png)
+ content/browser/abouthome/restore-large@2x.png (content/abouthome/restore-large@2x.png)
+ content/browser/abouthome/mozilla@2x.png (content/abouthome/mozilla@2x.png)
+
+ content/browser/aboutNetError.xhtml (content/aboutNetError.xhtml)
+
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ content/browser/abouthealthreport/abouthealth.xhtml (content/abouthealthreport/abouthealth.xhtml)
+ content/browser/abouthealthreport/abouthealth.js (content/abouthealthreport/abouthealth.js)
+ content/browser/abouthealthreport/abouthealth.css (content/abouthealthreport/abouthealth.css)
+#endif
+ content/browser/aboutaccounts/aboutaccounts.xhtml (content/aboutaccounts/aboutaccounts.xhtml)
+ content/browser/aboutaccounts/aboutaccounts.js (content/aboutaccounts/aboutaccounts.js)
+ content/browser/aboutaccounts/aboutaccounts.css (content/aboutaccounts/aboutaccounts.css)
+ content/browser/aboutaccounts/main.css (content/aboutaccounts/main.css)
+ content/browser/aboutaccounts/normalize.css (content/aboutaccounts/normalize.css)
+ content/browser/aboutaccounts/images/fox.png (content/aboutaccounts/images/fox.png)
+ content/browser/aboutaccounts/images/graphic_sync_intro.png (content/aboutaccounts/images/graphic_sync_intro.png)
+ content/browser/aboutaccounts/images/graphic_sync_intro@2x.png (content/aboutaccounts/images/graphic_sync_intro@2x.png)
+
+
+ content/browser/aboutRobots-icon.png (content/aboutRobots-icon.png)
+ content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png)
+ content/browser/aboutSocialError.xhtml (content/aboutSocialError.xhtml)
+ content/browser/aboutProviderDirectory.xhtml (content/aboutProviderDirectory.xhtml)
+ content/browser/aboutTabCrashed.css (content/aboutTabCrashed.css)
+ content/browser/aboutTabCrashed.js (content/aboutTabCrashed.js)
+ content/browser/aboutTabCrashed.xhtml (content/aboutTabCrashed.xhtml)
+* content/browser/browser.css (content/browser.css)
+ content/browser/browser.js (content/browser.js)
+* content/browser/browser.xul (content/browser.xul)
+ content/browser/browser-addons.js (content/browser-addons.js)
+ content/browser/browser-captivePortal.js (content/browser-captivePortal.js)
+ content/browser/browser-ctrlTab.js (content/browser-ctrlTab.js)
+ content/browser/browser-customization.js (content/browser-customization.js)
+ content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
+ content/browser/browser-devedition.js (content/browser-devedition.js)
+ content/browser/browser-feeds.js (content/browser-feeds.js)
+ content/browser/browser-fullScreenAndPointerLock.js (content/browser-fullScreenAndPointerLock.js)
+ content/browser/browser-fullZoom.js (content/browser-fullZoom.js)
+ content/browser/browser-fxaccounts.js (content/browser-fxaccounts.js)
+ content/browser/browser-gestureSupport.js (content/browser-gestureSupport.js)
+ content/browser/browser-media.js (content/browser-media.js)
+ content/browser/browser-places.js (content/browser-places.js)
+ content/browser/browser-plugins.js (content/browser-plugins.js)
+ content/browser/browser-refreshblocker.js (content/browser-refreshblocker.js)
+ content/browser/browser-safebrowsing.js (content/browser-safebrowsing.js)
+ content/browser/browser-sidebar.js (content/browser-sidebar.js)
+ content/browser/browser-social.js (content/browser-social.js)
+ content/browser/browser-syncui.js (content/browser-syncui.js)
+* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
+#ifdef CAN_DRAW_IN_TITLEBAR
+ content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar.js)
+#else
+ content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar-stub.js)
+#endif
+ content/browser/browser-thumbnails.js (content/browser-thumbnails.js)
+ content/browser/browser-trackingprotection.js (content/browser-trackingprotection.js)
+ content/browser/tab-content.js (content/tab-content.js)
+ content/browser/content.js (content/content.js)
+ content/browser/social-content.js (content/social-content.js)
+ content/browser/defaultthemes/1.footer.jpg (content/defaultthemes/1.footer.jpg)
+ content/browser/defaultthemes/1.header.jpg (content/defaultthemes/1.header.jpg)
+ content/browser/defaultthemes/1.icon.jpg (content/defaultthemes/1.icon.jpg)
+ content/browser/defaultthemes/1.preview.jpg (content/defaultthemes/1.preview.jpg)
+ content/browser/defaultthemes/2.footer.jpg (content/defaultthemes/2.footer.jpg)
+ content/browser/defaultthemes/2.header.jpg (content/defaultthemes/2.header.jpg)
+ content/browser/defaultthemes/2.icon.jpg (content/defaultthemes/2.icon.jpg)
+ content/browser/defaultthemes/2.preview.jpg (content/defaultthemes/2.preview.jpg)
+ content/browser/defaultthemes/3.footer.png (content/defaultthemes/3.footer.png)
+ content/browser/defaultthemes/3.header.png (content/defaultthemes/3.header.png)
+ content/browser/defaultthemes/3.icon.png (content/defaultthemes/3.icon.png)
+ content/browser/defaultthemes/3.preview.png (content/defaultthemes/3.preview.png)
+ content/browser/defaultthemes/4.footer.png (content/defaultthemes/4.footer.png)
+ content/browser/defaultthemes/4.header.png (content/defaultthemes/4.header.png)
+ content/browser/defaultthemes/4.icon.png (content/defaultthemes/4.icon.png)
+ content/browser/defaultthemes/4.preview.png (content/defaultthemes/4.preview.png)
+ content/browser/defaultthemes/5.footer.png (content/defaultthemes/5.footer.png)
+ content/browser/defaultthemes/5.header.png (content/defaultthemes/5.header.png)
+ content/browser/defaultthemes/5.icon.jpg (content/defaultthemes/5.icon.jpg)
+ content/browser/defaultthemes/5.preview.jpg (content/defaultthemes/5.preview.jpg)
+ content/browser/defaultthemes/devedition.header.png (content/defaultthemes/devedition.header.png)
+ content/browser/defaultthemes/devedition.icon.png (content/defaultthemes/devedition.icon.png)
+ content/browser/gcli_sec_bad.svg (content/gcli_sec_bad.svg)
+ content/browser/gcli_sec_good.svg (content/gcli_sec_good.svg)
+ content/browser/gcli_sec_moderate.svg (content/gcli_sec_moderate.svg)
+ content/browser/newtab/newTab.xhtml (content/newtab/newTab.xhtml)
+* content/browser/newtab/newTab.js (content/newtab/newTab.js)
+ content/browser/newtab/newTab.css (content/newtab/newTab.css)
+ content/browser/newtab/newTab.inadjacent.json (content/newtab/newTab.inadjacent.json)
+ content/browser/newtab/alternativeDefaultSites.json (content/newtab/alternativeDefaultSites.json)
+* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
+ content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js)
+ content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css)
+ content/browser/pageinfo/pageInfo.xml (content/pageinfo/pageInfo.xml)
+ content/browser/pageinfo/feeds.js (content/pageinfo/feeds.js)
+ content/browser/pageinfo/feeds.xml (content/pageinfo/feeds.xml)
+ content/browser/pageinfo/permissions.js (content/pageinfo/permissions.js)
+ content/browser/pageinfo/security.js (content/pageinfo/security.js)
+ content/browser/sync/aboutSyncTabs.xul (content/sync/aboutSyncTabs.xul)
+ content/browser/sync/aboutSyncTabs.js (content/sync/aboutSyncTabs.js)
+ content/browser/sync/aboutSyncTabs.css (content/sync/aboutSyncTabs.css)
+ content/browser/sync/aboutSyncTabs-bindings.xml (content/sync/aboutSyncTabs-bindings.xml)
+ content/browser/sync/setup.xul (content/sync/setup.xul)
+ content/browser/sync/addDevice.js (content/sync/addDevice.js)
+ content/browser/sync/addDevice.xul (content/sync/addDevice.xul)
+ content/browser/sync/setup.js (content/sync/setup.js)
+ content/browser/sync/genericChange.xul (content/sync/genericChange.xul)
+ content/browser/sync/genericChange.js (content/sync/genericChange.js)
+ content/browser/sync/key.xhtml (content/sync/key.xhtml)
+ content/browser/sync/utils.js (content/sync/utils.js)
+* content/browser/sync/customize.xul (content/sync/customize.xul)
+ content/browser/sync/customize.js (content/sync/customize.js)
+ content/browser/sync/customize.css (content/sync/customize.css)
+ content/browser/safeMode.css (content/safeMode.css)
+ content/browser/safeMode.js (content/safeMode.js)
+ content/browser/safeMode.xul (content/safeMode.xul)
+ content/browser/sanitize.js (content/sanitize.js)
+* content/browser/sanitize.xul (content/sanitize.xul)
+* content/browser/sanitizeDialog.js (content/sanitizeDialog.js)
+ content/browser/sanitizeDialog.css (content/sanitizeDialog.css)
+ content/browser/contentSearchUI.js (content/contentSearchUI.js)
+ content/browser/contentSearchUI.css (content/contentSearchUI.css)
+ content/browser/tabbrowser.css (content/tabbrowser.css)
+ content/browser/tabbrowser.xml (content/tabbrowser.xml)
+ content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
+ content/browser/utilityOverlay.js (content/utilityOverlay.js)
+ content/browser/usercontext.svg (content/usercontext.svg)
+ content/browser/web-panels.js (content/web-panels.js)
+* content/browser/web-panels.xul (content/web-panels.xul)
+* content/browser/baseMenuOverlay.xul (content/baseMenuOverlay.xul)
+* content/browser/nsContextMenu.js (content/nsContextMenu.js)
+# XXX: We should exclude this one as well (bug 71895)
+* content/browser/hiddenWindow.xul (content/hiddenWindow.xul)
+#ifdef XP_MACOSX
+* content/browser/macBrowserOverlay.xul (content/macBrowserOverlay.xul)
+* content/browser/downloadManagerOverlay.xul (content/downloadManagerOverlay.xul)
+* content/browser/softwareUpdateOverlay.xul (content/softwareUpdateOverlay.xul)
+#endif
+* content/browser/viewSourceOverlay.xul (content/viewSourceOverlay.xul)
+#ifndef XP_MACOSX
+* content/browser/webrtcIndicator.xul (content/webrtcIndicator.xul)
+ content/browser/webrtcIndicator.js (content/webrtcIndicator.js)
+#endif
+#ifdef XP_WIN
+ content/browser/win6BrowserOverlay.xul (content/win6BrowserOverlay.xul)
+#endif
+# the following files are browser-specific overrides
+* content/browser/license.html (/toolkit/content/license.html)
+% override chrome://global/content/license.html chrome://browser/content/license.html
+ content/browser/report-phishing-overlay.xul (content/report-phishing-overlay.xul)
+ content/browser/blockedSite.xhtml (content/blockedSite.xhtml)
+% overlay chrome://browser/content/browser.xul chrome://browser/content/report-phishing-overlay.xul
+
+% override chrome://global/content/netError.xhtml chrome://browser/content/aboutNetError.xhtml
diff --git a/browser/base/moz.build b/browser/base/moz.build
new file mode 100644
index 000000000..466349992
--- /dev/null
+++ b/browser/base/moz.build
@@ -0,0 +1,50 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SPHINX_TREES['sslerrorreport'] = 'content/docs/sslerrorreport'
+
+MOCHITEST_MANIFESTS += [
+ 'content/test/general/mochitest.ini',
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ 'content/test/chrome/chrome.ini',
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ 'content/test/alerts/browser.ini',
+ 'content/test/captivePortal/browser.ini',
+ 'content/test/general/browser.ini',
+ 'content/test/newtab/browser.ini',
+ 'content/test/plugins/browser.ini',
+ 'content/test/popupNotifications/browser.ini',
+ 'content/test/popups/browser.ini',
+ 'content/test/referrer/browser.ini',
+ 'content/test/siteIdentity/browser.ini',
+ 'content/test/social/browser.ini',
+ 'content/test/tabcrashed/browser.ini',
+ 'content/test/tabPrompts/browser.ini',
+ 'content/test/tabs/browser.ini',
+ 'content/test/urlbar/browser.ini',
+ 'content/test/webrtc/browser.ini',
+]
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
+
+DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
+ DEFINES['HAVE_SHELL_SERVICE'] = 1
+ DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
+ DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
+ DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/branding/aurora/VisualElements_150.png b/browser/branding/aurora/VisualElements_150.png
new file mode 100644
index 000000000..92370ee7a
--- /dev/null
+++ b/browser/branding/aurora/VisualElements_150.png
Binary files differ
diff --git a/browser/branding/aurora/VisualElements_70.png b/browser/branding/aurora/VisualElements_70.png
new file mode 100644
index 000000000..aa206c51e
--- /dev/null
+++ b/browser/branding/aurora/VisualElements_70.png
Binary files differ
diff --git a/browser/branding/aurora/appname.bmp b/browser/branding/aurora/appname.bmp
new file mode 100644
index 000000000..904794ddb
--- /dev/null
+++ b/browser/branding/aurora/appname.bmp
Binary files differ
diff --git a/browser/branding/aurora/background.png b/browser/branding/aurora/background.png
new file mode 100644
index 000000000..704d1c2eb
--- /dev/null
+++ b/browser/branding/aurora/background.png
Binary files differ
diff --git a/browser/branding/aurora/bgintro.bmp b/browser/branding/aurora/bgintro.bmp
new file mode 100644
index 000000000..02ef9b336
--- /dev/null
+++ b/browser/branding/aurora/bgintro.bmp
Binary files differ
diff --git a/browser/branding/aurora/branding.nsi b/browser/branding/aurora/branding.nsi
new file mode 100644
index 000000000..bb42794d1
--- /dev/null
+++ b/browser/branding/aurora/branding.nsi
@@ -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/.
+
+# NSIS branding defines for Aurora builds.
+# The official release build branding.nsi is located in other-license/branding/firefox/
+# The unofficial build branding.nsi is located in browser/branding/unofficial/
+
+# BrandFullNameInternal is used for some registry and file system values
+# instead of BrandFullName and typically should not be modified.
+!define BrandFullNameInternal "Firefox Developer Edition"
+!define BrandShortName "Firefox Developer Edition"
+!define CompanyName "mozilla.org"
+!define URLInfoAbout "https://www.mozilla.org"
+!define HelpLink "https://support.mozilla.org"
+
+!define URLStubDownload "http://download.mozilla.org/?os=win&lang=${AB_CD}&product=firefox-aurora-latest"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=aurora&installer_lang=${AB_CD}"
+!define URLSystemRequirements "https://www.mozilla.org/firefox/system-requirements/"
+!define Channel "aurora"
+
+# The installer's certificate name and issuer expected by the stub installer
+!define CertNameDownload "Mozilla Corporation"
+!define CertIssuerDownload "DigiCert SHA2 Assured ID Code Signing CA"
+
+# Dialog units are used so the UI displays correctly with the system's DPI
+# settings.
+# The dialog units for the bitmap's dimensions should match exactly with the
+# bitmap's width and height in pixels.
+!define APPNAME_BMP_WIDTH_DU 108u
+!define APPNAME_BMP_HEIGHT_DU 48u
+!define INTRO_BLURB_WIDTH_DU "232u"
+!define INTRO_BLURB_EDGE_DU "196u"
+!define INTRO_BLURB_LTR_TOP_DU "16u"
+!define INTRO_BLURB_RTL_TOP_DU "11u"
+
+# UI Colors that can be customized for each channel
+!define FOOTER_CONTROL_TEXT_COLOR_NORMAL 0x000000
+!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x999999
+!define FOOTER_BKGRD_COLOR 0xFFFFFF
+!define INTRO_BLURB_TEXT_COLOR 0xFFFFFF
+!define INSTALL_BLURB_TEXT_COLOR 0xFFFFFF
+!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0xFFFFFF
+!define COMMON_TEXT_COLOR_NORMAL 0xFFFFFF
+!define COMMON_TEXT_COLOR_FADED 0xA1AAB3
+!define COMMON_BKGRD_COLOR 0x0F1B26
+
+# Enable DeveloperEdition-specific behavior
+!define DEV_EDITION
diff --git a/browser/branding/aurora/clock.bmp b/browser/branding/aurora/clock.bmp
new file mode 100644
index 000000000..c74398edb
--- /dev/null
+++ b/browser/branding/aurora/clock.bmp
Binary files differ
diff --git a/browser/branding/aurora/configure.sh b/browser/branding/aurora/configure.sh
new file mode 100644
index 000000000..36feb0828
--- /dev/null
+++ b/browser/branding/aurora/configure.sh
@@ -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/.
+
+MOZ_APP_DISPLAYNAME=FirefoxDeveloperEdition
+MOZ_APP_REMOTINGNAME=firefox-dev
+MOZ_DEV_EDITION=1
diff --git a/browser/branding/aurora/content/about-background.png b/browser/branding/aurora/content/about-background.png
new file mode 100644
index 000000000..1fd5c9c88
--- /dev/null
+++ b/browser/branding/aurora/content/about-background.png
Binary files differ
diff --git a/browser/branding/aurora/content/about-logo.png b/browser/branding/aurora/content/about-logo.png
new file mode 100644
index 000000000..e3b7e1c8b
--- /dev/null
+++ b/browser/branding/aurora/content/about-logo.png
Binary files differ
diff --git a/browser/branding/aurora/content/about-logo@2x.png b/browser/branding/aurora/content/about-logo@2x.png
new file mode 100644
index 000000000..08dd73dd6
--- /dev/null
+++ b/browser/branding/aurora/content/about-logo@2x.png
Binary files differ
diff --git a/browser/branding/aurora/content/about-wordmark.svg b/browser/branding/aurora/content/about-wordmark.svg
new file mode 100644
index 000000000..b1b435b7a
--- /dev/null
+++ b/browser/branding/aurora/content/about-wordmark.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 132 62" width="132" height="62">
+ <path fill="#fff" d="M5.3,45.2H2.7L0.1,57.3h3.2c1.8,0,3-0.4,4.2-1.5c1.4-1.2,2.6-4.1,2.6-6.2c0-1.7-0.4-2.8-1.2-3.5C8,45.4,7.1,45.2,5.3,45.2z
+ M6.6,54.9c-0.7,0.9-1.9,1.3-3.5,1.3H1.8l2.1-9.8h1.5c1.3,0,2,0.2,2.6,1.1c0.4,0.5,0.5,1.3,0.5,2.4C8.5,51.2,7.9,53.4,6.6,54.9z
+ M15.3,48.3c-1.1,0-2,0.4-2.9,1.2c-1.3,1.2-2,3.1-2,4.8c0,2,1.3,3.2,2.9,3.2c1.8,0,2.7-0.4,3.6-1.3l-0.6-0.8c-0.8,0.7-1.5,1-2.6,1
+ c-0.9,0-1.8-0.7-1.8-1.9c0-0.3,0-0.7,0.1-1.1c0.4,0,0.8,0,1.1,0c1.9,0,3.1-0.3,3.8-1c0.5-0.5,0.8-1.1,0.8-1.8
+ C17.8,49.2,17,48.3,15.3,48.3z M16,51.7c-0.5,0.4-1.2,0.7-2.8,0.7c-0.3,0-0.7,0-0.9,0c0.5-1.8,1.6-3,2.8-3c1,0,1.4,0.5,1.4,1.2
+ C16.4,51,16.2,51.4,16,51.7z M25,48.5l-3.3,5.9c-0.4,0.6-0.5,1-0.7,1.5h0c0-0.4-0.1-0.9-0.1-1.5l-0.8-6.1l-1.4,0.3l1.3,8.8h1.4
+ l5.2-8.9L25,48.5z M30.7,48.3c-1.1,0-2,0.4-2.9,1.2c-1.3,1.2-2,3.1-2,4.8c0,2,1.3,3.2,2.9,3.2c1.8,0,2.7-0.4,3.6-1.3l-0.6-0.8
+ c-0.8,0.7-1.5,1-2.6,1c-0.9,0-1.8-0.7-1.8-1.9c0-0.3,0-0.7,0.1-1.1c0.4,0,0.8,0,1.1,0c1.9,0,3.1-0.3,3.8-1c0.5-0.5,0.8-1.1,0.8-1.8
+ C33.3,49.2,32.4,48.3,30.7,48.3z M31.4,51.7c-0.5,0.4-1.2,0.7-2.8,0.7c-0.3,0-0.7,0-0.9,0c0.5-1.8,1.6-3,2.8-3c1,0,1.4,0.5,1.4,1.2
+ C31.9,51,31.7,51.4,31.4,51.7z M37.2,47.5c0.2-1.1,0.4-2.5,0.1-3.2l-1.5,0.6c0.2,0.6,0.2,1.5-0.1,2.7L34,55.7
+ c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.7,0.4,1,1.2,1c0.4,0,0.7,0,1.1-0.2l-0.1-0.9c-0.1,0-0.2,0-0.3,0c-0.2,0-0.4-0.1-0.4-0.4
+ c0-0.2,0-0.4,0.1-0.6L37.2,47.5z M42.3,48.2c-2.8,0-4.9,2.6-4.9,6.1c0,2,1.1,3.2,3,3.2c2.9,0,4.9-2.7,4.9-6.1
+ C45.3,49.5,44.4,48.2,42.3,48.2z M40.7,56.4c-1.1,0-1.7-0.6-1.7-1.9c0-2.9,1.2-5.1,3.2-5.1c0.9,0,1.7,0.5,1.7,1.9
+ C43.8,54,42.7,56.4,40.7,56.4z M52.1,48.3c-1,0-2.3,0.5-3.2,1.8c0.2-0.9,0.1-1.4-0.1-1.9l-1.3,0.6c0.2,0.6,0.2,1-0.1,2.2l-2.2,10
+ l1.4-0.3l0.8-3.6c0.6,0.3,1.2,0.4,2.1,0.4c1.2,0,2.4-0.6,3.3-1.6c0.9-1.1,1.5-3.2,1.5-4.8C54.4,49.4,53.7,48.3,52.1,48.3z
+ M51.8,55.2c-0.4,0.7-1.4,1.2-2.2,1.2c-0.7,0-1.4-0.2-1.8-0.5l0.9-4.3c0.9-1.3,2-1.9,2.9-1.9c0.9,0,1.3,0.5,1.3,1.7
+ C52.9,52.5,52.4,54.3,51.8,55.2z M60.1,48.3c-1.1,0-2,0.4-2.9,1.2c-1.3,1.2-2,3.1-2,4.8c0,2,1.3,3.2,2.9,3.2c1.8,0,2.7-0.4,3.6-1.3
+ l-0.6-0.8c-0.8,0.7-1.5,1-2.6,1c-0.9,0-1.8-0.7-1.8-1.9c0-0.3,0-0.7,0.1-1.1c0.4,0,0.8,0,1.1,0c1.9,0,3.1-0.3,3.8-1
+ c0.5-0.5,0.8-1.1,0.8-1.8C62.6,49.2,61.8,48.3,60.1,48.3z M60.8,51.7c-0.5,0.4-1.2,0.7-2.8,0.7c-0.3,0-0.7,0-0.9,0
+ c0.5-1.8,1.6-3,2.8-3c1,0,1.4,0.5,1.4,1.2C61.2,51,61,51.4,60.8,51.7z M65.8,50.3c0.2-1,0.2-1.5-0.1-2.1l-1.3,0.6
+ c0.2,0.6,0.2,1.2-0.1,2.5l-1.3,6h1.4l1.2-5.5c0.9-1.3,1.8-2,2.6-2c0.3,0,0.4,0,0.6,0.1l0.6-1.5c-0.2-0.1-0.3-0.1-0.7-0.1
+ C67.8,48.3,66.6,49.1,65.8,50.3z M78.1,51.6l0.3-1.3h-4.2l0.8-4h5l0.5-1.2h-6.7l-2.6,12.1H78l0.3-1.3h-5.4l0.9-4.4H78.1z M86.6,54.5
+ l2.1-9.8l-1.4-0.2l-0.9,4.3c-0.4-0.2-1-0.3-2-0.3c-1,0-2.2,0.4-3.1,1.3c-1.3,1.3-2,3.1-2,4.9c0,1.9,0.8,3,2.5,3
+ c1.3,0,2.3-0.5,3.1-1.6c-0.1,1,0.2,1.4,0.7,1.9l1.2-0.9C86.4,56.3,86.2,55.9,86.6,54.5z M85.2,54.2c-0.9,1.5-2,2.1-3,2.1
+ c-0.9,0-1.3-0.6-1.3-1.7c0-1.3,0.6-3.2,1.4-4.1c0.6-0.6,1.4-1,2.1-1c0.8,0,1.2,0.1,1.7,0.4L85.2,54.2z M88.2,57.3h1.5l1.9-9
+ l-1.5,0.2L88.2,57.3z M91.4,44.7c-0.6,0-1.1,0.5-1.1,1.1c0,0.6,0.5,1.1,1.1,1.1s1.1-0.5,1.1-1.1S92,44.7,91.4,44.7z M93.7,55.7
+ c0-0.2,0-0.6,0.1-1l1.1-5.2h1.9l0.6-1h-2.2c0.2-0.8,0.5-1.9,0.7-2.5l-1.5,0.3c-0.2,0.7-0.4,1.5-0.6,2.2h-1.2l-0.2,1h1.2l-1.1,5.3
+ c-0.1,0.5-0.1,1-0.1,1.3c0,0.9,0.5,1.4,1.6,1.4c0.6,0,1.1-0.1,1.6-0.4v-0.9c-0.3,0.1-0.5,0.2-0.9,0.2C94,56.5,93.7,56.3,93.7,55.7z
+ M99.8,44.7c-0.6,0-1.1,0.5-1.1,1.1c0,0.6,0.5,1.1,1.1,1.1s1.1-0.5,1.1-1.1S100.4,44.7,99.8,44.7z M96.6,57.3h1.5l1.9-9l-1.5,0.2
+ L96.6,57.3z M105.3,48.2c-2.8,0-4.9,2.6-4.9,6.1c0,2,1.1,3.2,3,3.2c2.9,0,4.9-2.7,4.9-6.1C108.3,49.5,107.3,48.2,105.3,48.2z
+ M103.6,56.4c-1.1,0-1.7-0.6-1.7-1.9c0-2.9,1.2-5.1,3.1-5.1c0.9,0,1.7,0.5,1.7,1.9C106.8,54,105.7,56.4,103.6,56.4z M115.3,48.3
+ c-1.1,0-2.5,0.6-3.4,1.9c0.2-0.9,0.1-1.4-0.1-1.9l-1.3,0.6c0.2,0.7,0.2,1-0.1,2.2l-1.4,6.3h1.4l1.2-5.8c1.2-1.5,2.4-2.1,3.1-2.1
+ c0.6,0,0.9,0.3,0.9,0.9c0,0.2,0,0.5-0.1,1l-1.3,6h1.4l1.4-6.7c0-0.2,0.1-0.4,0.1-0.6C117.1,49,116.4,48.3,115.3,48.3z M130.9,12.5
+ c-0.1-0.2-0.2-0.4-0.4-0.6c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.1-0.7-0.1c-0.2,0-0.5,0-0.7,0.1c-0.2,0.1-0.4,0.2-0.6,0.4
+ c-0.2,0.2-0.3,0.4-0.4,0.6c-0.1,0.2-0.1,0.5-0.1,0.7c0,0.3,0,0.5,0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.6c0.2,0.2,0.4,0.3,0.6,0.4
+ c0.2,0.1,0.5,0.1,0.7,0.1c0.2,0,0.5,0,0.7-0.1c0.2-0.1,0.4-0.2,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.6c0.1-0.2,0.1-0.5,0.1-0.7
+ C131,13,131,12.7,130.9,12.5z M130.5,13.8c-0.1,0.2-0.2,0.3-0.3,0.5c-0.1,0.1-0.3,0.2-0.5,0.3c-0.2,0.1-0.4,0.1-0.6,0.1
+ c-0.2,0-0.4,0-0.6-0.1c-0.2-0.1-0.3-0.2-0.5-0.3c-0.1-0.1-0.2-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.2,0-0.4,0.1-0.6
+ c0.1-0.2,0.2-0.3,0.3-0.5c0.1-0.1,0.3-0.2,0.5-0.3c0.2-0.1,0.4-0.1,0.6-0.1c0.2,0,0.4,0,0.6,0.1c0.2,0.1,0.3,0.2,0.5,0.3
+ c0.1,0.1,0.2,0.3,0.3,0.5c0.1,0.2,0.1,0.4,0.1,0.6C130.6,13.5,130.6,13.6,130.5,13.8z M129.6,13.6c0,0-0.1-0.1-0.1-0.1
+ c0,0-0.1-0.1-0.1-0.1c0,0,0,0-0.1-0.1c0.2,0,0.3-0.1,0.4-0.2c0.1-0.1,0.1-0.2,0.1-0.4c0-0.1,0-0.2,0-0.2c0-0.1-0.1-0.1-0.1-0.2
+ c-0.1-0.1-0.1-0.1-0.2-0.1c-0.1,0-0.2,0-0.3,0h-0.6v2h0.3v-0.9c0,0,0.1,0,0.1,0c0,0,0,0,0.1,0c0.1,0.1,0.1,0.1,0.2,0.2
+ c0.1,0.1,0.1,0.2,0.2,0.3l0.2,0.3h0.4l-0.3-0.5C129.7,13.7,129.6,13.7,129.6,13.6z M129.1,13.1h-0.2v-0.6h0.2c0.2,0,0.3,0,0.3,0.1
+ c0.1,0.1,0.1,0.1,0.1,0.2c0,0.1,0,0.2-0.1,0.2c0,0-0.1,0.1-0.1,0.1C129.3,13.1,129.2,13.1,129.1,13.1z M0,36.8h5.8V20.9h9.7v-4.7
+ H5.8V6.9h11.9l0.7-4.7H0V36.8z M24.3,7.7C26.4,7.7,28,6,28,4c0-2.1-1.7-3.7-3.6-3.7c-2.1,0-3.7,1.7-3.7,3.7
+ C20.7,6,22.3,7.7,24.3,7.7z M21.5,36.8h5.6V10.9l-5.6,1V36.8z M32.4,36.8H38V19.7c0.5-2.1,2.5-3.7,4.8-3.7c0.6,0,1,0.2,1.6,0.4
+ l1.7-5.1c-0.7-0.3-1.2-0.4-2-0.4c-2.5,0-4.5,1.3-6.5,4.1c0-1.4-0.4-2.9-1-4l-5.1,1.3c0.6,1.6,0.9,3.6,0.9,6.8V36.8z M56.9,37.4
+ c3.4,0,6.5-1.1,9.2-3.4l-2.2-3.4c-1.9,1.7-4,2.5-6.3,2.5c-5,0-6.3-3.7-6.3-7.2v-0.4h15.2v-1.2c0-5.9-1.1-9-3.3-11
+ c-2.2-2-4.5-2.6-7-2.6c-3.2,0-5.7,1.1-7.8,3.4c-2.2,2.5-3.2,5.4-3.2,9.9C45.2,32.3,49.8,37.4,56.9,37.4z M56.2,15
+ c2.8,0,4.6,2.4,4.6,6.6h-9.4C51.4,17.5,53.1,15,56.2,15z M76.1,36.8V15.3h5.2l1.4-3.8h-6.6V7.6c0-2.3,1.2-3.6,3.1-3.6
+ C80.2,4,81,4.4,82.2,5l1.8-3.5c-1.8-1-3.6-1.5-5.7-1.5c-4.5,0-7.7,2.5-7.7,7.7c0,2.4,0.2,3.8,0.2,3.8h-2.5v3.8h2.4v21.5H76.1z
+ M93.1,37.4c6.9,0,11.3-5.1,11.3-13.2c0-8-4.1-13.4-11.4-13.4c-6.7,0-11.1,5.2-11.1,13.3C81.9,32.3,86.2,37.4,93.1,37.4z M93.1,15
+ c3.2,0,5.2,2.1,5.2,9.3c0,6.4-1.8,9-5.1,9c-3.3,0-5.2-2.2-5.2-9.5C88.1,17.7,89.6,15,93.1,15z M126.1,11.5h-6.4
+ c-0.8,1.1-3.3,6.1-4,7.8c-1.2-2.3-3.5-6.3-4.6-8.2l-6,1.2l7.3,10.9L103,36.8h7c1-1.4,4.6-7.6,5.5-9.5c0.5,0.9,4.6,8,5.5,9.5h6.9
+ L118.6,23L126.1,11.5z"/>
+</svg>
diff --git a/browser/branding/aurora/content/about.png b/browser/branding/aurora/content/about.png
new file mode 100644
index 000000000..c7d54ef1c
--- /dev/null
+++ b/browser/branding/aurora/content/about.png
Binary files differ
diff --git a/browser/branding/aurora/content/aboutDialog.css b/browser/branding/aurora/content/aboutDialog.css
new file mode 100644
index 000000000..209c53244
--- /dev/null
+++ b/browser/branding/aurora/content/aboutDialog.css
@@ -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/. */
+
+#aboutDialogContainer {
+ background-image: url("chrome://branding/content/about-background.png");
+ background-repeat: no-repeat;
+ background-color: rgb(26,58,99);
+ color: #fff;
+}
+
+.text-link {
+ color: #fff !important;
+ text-decoration: underline;
+}
+
+.text-link:-moz-focusring {
+ border-color: #fff;
+}
+
+#rightBox {
+ /* this margin prevents text from overlapping the planet image */
+ margin-left: 280px;
+ margin-right: 20px;
+}
+
+#bottomBox {
+ padding: 15px 10px 15px;
+ background-color: rgba(0,0,0,.7);
+}
+
+#version {
+ margin-top: 30px;
+}
diff --git a/browser/branding/aurora/content/icon48.png b/browser/branding/aurora/content/icon48.png
new file mode 100644
index 000000000..85e3c0d4b
--- /dev/null
+++ b/browser/branding/aurora/content/icon48.png
Binary files differ
diff --git a/browser/branding/aurora/content/icon64.png b/browser/branding/aurora/content/icon64.png
new file mode 100644
index 000000000..4b90768d2
--- /dev/null
+++ b/browser/branding/aurora/content/icon64.png
Binary files differ
diff --git a/browser/branding/aurora/content/identity-icons-brand.svg b/browser/branding/aurora/content/identity-icons-brand.svg
new file mode 100644
index 000000000..b284275bf
--- /dev/null
+++ b/browser/branding/aurora/content/identity-icons-brand.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+ <path fill="#0c99d5" d="M26.797,6.125 C26.719,6.050 26.674,6.013 26.674,6.013 C26.715,6.045 26.756,6.082 26.797,6.125 M28.924,21.127 C28.947,20.948 28.949,20.780 28.929,20.626 C28.501,21.359 27.844,22.025 27.228,22.905 C27.980,22.421 28.624,21.866 28.924,21.127 ZM30.097,13.924 C30.082,13.747 30.062,13.570 30.036,13.394 C29.974,12.946 29.875,12.504 29.728,12.076 C29.735,12.102 29.741,12.129 29.747,12.155 C29.737,12.124 29.731,12.107 29.731,12.107 C29.731,12.107 29.614,12.472 29.435,13.070 C29.423,14.290 29.290,15.417 29.094,16.268 C29.419,15.962 29.657,15.599 29.820,15.196 C29.748,15.461 29.649,15.713 29.520,15.947 C29.361,16.217 29.182,16.436 29.009,16.610 C28.821,16.797 28.641,16.932 28.500,17.024 C28.543,16.905 28.585,16.773 28.625,16.631 C28.626,16.629 28.626,16.628 28.627,16.626 C28.637,16.590 28.647,16.552 28.657,16.513 C28.761,16.176 28.854,15.828 28.934,15.474 C29.049,14.961 29.137,14.433 29.192,13.898 C29.300,12.857 29.284,11.787 29.104,10.744 C29.015,10.227 28.886,9.717 28.712,9.220 C28.480,8.559 28.197,8.001 27.916,7.544 C27.895,7.508 27.874,7.472 27.853,7.436 C27.760,7.280 27.665,7.132 27.570,6.994 C27.258,6.543 26.939,6.200 26.674,6.013 C26.715,6.045 26.756,6.082 26.797,6.125 C26.719,6.050 26.674,6.013 26.674,6.013 C26.694,6.035 26.712,6.058 26.732,6.081 C26.682,6.033 26.653,6.008 26.653,6.008 C26.653,6.008 26.682,6.170 26.733,6.456 C25.969,5.218 24.807,4.635 24.807,4.635 C24.807,4.635 24.902,4.945 25.050,5.391 C25.741,5.949 26.345,6.571 26.869,7.227 C26.871,7.238 26.873,7.250 26.875,7.261 C26.905,7.435 26.938,7.624 26.973,7.826 C26.880,7.700 26.782,7.575 26.683,7.451 C25.589,5.958 24.211,4.706 22.611,3.779 C22.625,3.783 22.600,3.776 22.613,3.780 C20.672,2.647 18.429,2.000 16.039,2.000 C12.495,2.000 9.273,3.423 6.880,5.744 C6.814,5.809 7.271,6.270 7.404,6.311 C8.081,6.106 8.838,6.051 9.423,6.120 C9.705,5.897 9.663,5.963 9.961,5.769 L9.964,5.772 C11.767,4.606 13.854,3.982 16.035,3.982 C18.042,3.982 19.969,4.510 21.669,5.503 C22.118,5.635 22.683,5.830 23.182,6.091 C22.663,5.187 22.132,4.560 21.761,4.182 C23.311,5.364 24.150,6.425 24.701,7.396 C24.746,7.475 24.789,7.553 24.830,7.631 C24.928,7.816 25.017,7.998 25.099,8.177 C24.570,7.618 23.753,7.042 23.039,6.772 C22.981,6.750 22.924,6.730 22.868,6.712 C22.708,6.662 22.555,6.628 22.414,6.617 C23.550,7.488 25.407,9.978 25.432,13.744 C25.432,13.765 25.432,13.786 25.432,13.807 C25.432,14.010 25.428,14.216 25.417,14.427 C25.207,13.971 24.877,13.340 24.562,12.869 C24.483,12.751 24.405,12.644 24.330,12.551 C24.265,12.471 24.202,12.400 24.143,12.346 C24.479,15.517 24.324,16.750 24.116,17.651 C24.096,17.734 24.077,17.815 24.057,17.894 C24.015,18.058 23.973,18.216 23.934,18.378 C23.920,18.257 23.899,18.145 23.874,18.040 C23.816,17.791 23.735,17.585 23.666,17.424 C23.643,17.372 23.621,17.322 23.603,17.280 C23.603,17.280 23.581,17.854 23.325,18.780 C23.199,19.234 23.018,19.771 22.754,20.367 C22.294,21.408 21.823,21.903 21.501,22.062 C21.397,22.113 21.309,22.129 21.242,22.117 C21.167,22.112 21.126,22.079 21.127,22.076 C21.135,22.001 21.143,21.926 21.146,21.854 C21.150,21.754 21.145,21.662 21.119,21.593 C21.119,21.593 20.862,21.684 20.697,21.924 C20.630,22.020 20.544,22.115 20.431,22.203 C20.411,22.219 20.614,21.936 20.599,21.949 C20.499,22.033 20.392,22.130 20.285,22.245 C20.170,22.368 20.059,22.494 19.954,22.609 C19.699,22.887 19.483,23.095 19.352,23.001 C19.437,22.975 19.513,22.906 19.572,22.818 C19.635,22.726 19.680,22.613 19.699,22.500 C19.544,22.612 19.152,22.914 18.272,23.049 C18.109,23.074 17.707,23.146 17.127,23.121 C16.424,23.090 15.460,22.916 14.345,22.341 C14.578,22.313 14.903,22.241 15.196,22.312 C15.275,22.331 15.352,22.360 15.424,22.405 C15.392,22.369 15.355,22.338 15.315,22.310 C14.933,22.037 14.212,22.084 13.681,21.911 C13.170,21.744 12.503,21.005 12.119,20.631 C12.263,20.667 12.407,20.696 12.551,20.721 C12.652,20.738 12.752,20.753 12.852,20.765 C13.008,20.784 13.164,20.798 13.319,20.805 C14.486,20.856 15.595,20.569 16.313,20.063 C17.285,19.377 17.861,18.876 18.378,18.994 C18.428,19.006 18.476,19.010 18.522,19.010 C18.543,19.010 18.563,19.009 18.583,19.007 C18.867,18.975 19.053,18.730 19.002,18.441 C18.983,18.332 18.931,18.217 18.836,18.104 C18.561,17.778 18.016,17.375 17.274,17.265 C16.935,17.215 16.556,17.226 16.142,17.333 C15.385,17.528 14.711,18.047 13.824,18.051 C13.526,18.053 13.204,17.996 12.846,17.850 C12.759,17.815 12.671,17.774 12.580,17.728 C12.489,17.681 12.877,17.783 12.781,17.725 C12.508,17.621 12.011,17.386 11.888,17.297 C11.868,17.282 12.094,17.339 12.070,17.324 C10.721,16.501 10.809,15.842 10.809,15.435 C10.809,15.270 10.858,15.077 10.953,14.899 C11.046,14.723 11.183,14.563 11.362,14.461 C11.475,14.502 11.562,14.541 11.616,14.567 C11.655,14.586 11.677,14.598 11.677,14.598 C11.677,14.598 11.664,14.576 11.644,14.546 C11.613,14.499 11.565,14.428 11.530,14.386 C11.544,14.381 11.557,14.377 11.571,14.373 C11.665,14.406 11.829,14.468 11.985,14.532 C12.092,14.576 12.195,14.621 12.268,14.659 C12.514,14.786 12.596,14.916 12.596,14.916 C12.596,14.916 12.654,14.879 12.594,14.749 C12.583,14.726 12.560,14.683 12.519,14.630 C12.465,14.561 12.378,14.473 12.239,14.386 C12.244,14.386 12.248,14.385 12.251,14.385 C12.379,14.437 12.514,14.503 12.663,14.590 C12.670,14.555 12.679,14.520 12.687,14.484 C12.688,14.479 12.690,14.475 12.691,14.470 C12.693,14.462 12.695,14.454 12.697,14.446 C12.704,14.416 12.711,14.385 12.718,14.354 C12.730,14.301 12.740,14.245 12.748,14.185 C12.764,14.058 12.768,13.913 12.740,13.731 C12.695,13.446 12.701,13.373 12.632,13.269 C12.573,13.181 12.648,13.142 12.740,13.221 C12.718,13.151 12.687,13.081 12.650,13.010 C12.650,13.009 12.651,13.009 12.651,13.007 C12.659,12.968 12.693,12.919 12.745,12.864 C12.758,12.849 12.774,12.834 12.790,12.818 C12.805,12.803 12.821,12.789 12.839,12.773 C13.359,12.313 14.782,11.539 14.908,11.443 C15.118,11.283 15.332,11.035 15.466,10.750 C15.508,10.672 15.544,10.578 15.571,10.468 C15.606,10.323 15.625,10.149 15.614,9.937 C15.606,9.764 15.537,9.634 14.917,9.568 C14.584,9.533 14.091,9.516 13.362,9.521 C13.335,9.521 13.309,9.521 13.282,9.521 C12.690,9.526 12.305,9.171 12.073,8.833 C12.025,8.759 11.984,8.689 11.946,8.626 C11.895,8.532 11.861,8.448 11.834,8.381 C11.917,8.070 12.028,7.772 12.165,7.489 C12.456,6.890 12.871,6.355 13.419,5.893 C13.468,5.850 13.226,5.921 13.272,5.877 C13.327,5.824 13.669,5.655 13.733,5.618 C13.772,5.595 13.692,5.556 13.557,5.528 C13.549,5.527 13.541,5.525 13.532,5.524 C13.380,5.496 13.167,5.485 12.972,5.527 C12.581,5.610 12.505,5.658 12.303,5.765 C12.385,5.678 12.650,5.540 12.585,5.554 C12.161,5.652 11.661,5.940 11.235,6.251 C11.231,6.211 11.235,6.179 11.243,6.116 C11.042,6.223 10.557,6.609 10.433,6.903 C10.433,6.839 10.433,6.807 10.425,6.736 C10.299,6.856 10.177,6.996 10.065,7.151 C10.055,7.165 10.044,7.178 10.034,7.192 C10.033,7.195 10.031,7.197 10.029,7.199 C9.691,7.112 9.367,7.055 9.056,7.023 C8.305,6.944 7.631,7.012 7.032,7.178 C6.951,7.201 6.871,7.224 6.793,7.250 C6.579,7.089 6.235,6.843 5.692,5.978 C5.659,5.926 5.656,6.097 5.626,6.042 C5.468,5.748 5.327,5.300 5.258,4.892 C5.234,4.750 5.218,4.613 5.214,4.489 C5.214,4.489 5.050,4.588 4.873,4.889 C4.806,5.003 4.737,5.146 4.675,5.324 C4.662,5.361 4.649,5.399 4.637,5.439 C4.596,5.570 4.568,5.648 4.539,5.720 C4.530,5.742 4.556,5.482 4.546,5.502 C4.530,5.537 4.502,5.579 4.472,5.627 C4.431,5.692 4.385,5.769 4.356,5.851 C4.349,5.870 4.343,5.889 4.338,5.909 C4.308,6.034 4.259,6.110 4.239,6.266 C4.238,6.270 4.237,6.273 4.235,6.276 C4.234,6.261 4.233,6.230 4.231,6.200 C4.229,6.152 4.225,6.105 4.218,6.123 C4.118,6.397 4.024,6.712 3.948,7.067 C3.838,7.628 3.726,8.395 3.793,9.368 C3.792,9.403 3.795,9.438 3.797,9.472 C3.800,9.514 3.803,9.555 3.802,9.594 C3.461,10.078 3.239,10.494 3.153,10.699 C3.066,10.873 2.979,11.068 2.893,11.284 C2.564,12.102 2.241,13.234 1.969,14.813 C1.969,14.813 2.200,14.061 2.661,13.210 C2.321,14.282 2.055,15.950 2.211,18.452 C2.215,18.397 2.248,18.101 2.322,17.660 C2.360,17.435 2.408,17.173 2.470,16.885 C2.473,16.950 2.477,17.015 2.482,17.081 C2.497,17.315 2.519,17.556 2.548,17.803 C2.565,17.949 2.585,18.097 2.607,18.248 C2.814,19.617 3.265,21.166 4.197,22.811 C5.154,24.502 7.676,28.430 14.005,29.900 C13.826,29.847 13.665,29.780 13.524,29.710 C13.117,29.508 12.879,29.280 12.879,29.280 C12.879,29.280 13.080,29.346 13.407,29.439 C14.081,29.630 15.290,29.931 16.388,29.990 C16.586,30.000 16.781,30.004 16.968,29.996 C16.428,29.900 16.320,29.631 16.320,29.631 C16.320,29.631 21.233,29.917 23.785,27.837 C23.835,27.796 23.885,27.754 23.934,27.711 C23.938,27.709 23.941,27.708 23.945,27.706 C24.327,27.379 24.606,27.021 24.755,26.675 C24.636,26.734 24.518,26.789 24.403,26.841 C24.025,27.251 23.564,27.586 23.055,27.860 C22.590,27.996 22.118,28.072 21.749,28.108 C21.581,28.124 21.434,28.132 21.319,28.133 C21.594,27.872 21.957,27.681 22.387,27.495 C23.024,27.219 23.811,26.955 24.683,26.496 C24.685,26.495 24.687,26.494 24.689,26.493 C24.741,26.466 24.793,26.437 24.845,26.409 C25.598,25.996 26.410,25.432 27.244,24.585 C28.038,23.779 28.427,23.083 28.643,22.448 C28.703,22.270 28.750,22.097 28.788,21.928 C28.852,21.645 28.893,21.372 28.934,21.104 C28.934,21.103 28.934,21.101 28.934,21.100 C28.934,21.102 28.933,21.103 28.933,21.105 C28.926,21.144 28.918,21.183 28.910,21.221 C28.671,22.267 27.797,22.972 26.794,23.585 C26.608,23.698 26.417,23.808 26.226,23.917 C26.339,23.696 26.459,23.491 26.582,23.294 C26.586,23.289 26.589,23.284 26.592,23.279 C26.590,23.284 26.588,23.288 26.586,23.293 C26.573,23.319 26.561,23.344 26.550,23.367 C26.567,23.339 26.585,23.311 26.603,23.283 C26.798,22.973 27.012,22.669 27.232,22.372 C27.760,21.689 28.278,21.118 28.621,20.490 C28.672,20.397 28.726,20.292 28.782,20.177 C28.803,20.134 28.825,20.090 28.846,20.043 C29.220,19.292 29.607,18.267 29.857,17.120 C29.969,16.606 30.053,16.067 30.097,15.517 C30.138,14.992 30.142,14.457 30.097,13.924 Z"/>
+</svg>
diff --git a/browser/branding/aurora/content/jar.mn b/browser/branding/aurora/content/jar.mn
new file mode 100644
index 000000000..140359a19
--- /dev/null
+++ b/browser/branding/aurora/content/jar.mn
@@ -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/.
+
+browser.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png
+ content/branding/about-background.png
+ content/branding/about-logo.png
+ content/branding/about-logo@2x.png
+ content/branding/about-wordmark.svg
+ content/branding/icon48.png
+ content/branding/icon64.png
+ content/branding/icon16.png (../default16.png)
+ content/branding/icon32.png (../default32.png)
+ content/branding/icon128.png (../mozicon128.png)
+ content/branding/identity-icons-brand.svg
+ content/branding/silhouette-40.svg
+ content/branding/aboutDialog.css
diff --git a/browser/branding/aurora/content/moz.build b/browser/branding/aurora/content/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/branding/aurora/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/aurora/content/silhouette-40.svg b/browser/branding/aurora/content/silhouette-40.svg
new file mode 100644
index 000000000..5a41a1c3f
--- /dev/null
+++ b/browser/branding/aurora/content/silhouette-40.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-45 31 40 40">
+ <path fill="#ccc" d="M-14.1,54.7c0.7-1.4,1.7-4.4,0.8-6.9c0,0,0,0,0,0.1l0,0c0,0-0.2,0.5-0.4,1.3c0-0.1,0-0.2,0-0.3
+ c0.1-0.9,0-1.9-0.1-2.9c-0.3-1.5-1.4-2.8-2-3.2c0,0,0.1,0,0.1,0.1c-0.1-0.1-0.1-0.1-0.1-0.1s0,0.1,0.1,0.4c-0.7-1.1-1.6-1.5-1.6-1.5
+ s0,0.2,0.1,0.5c-2-1.9-4.7-3-7.6-3c-3,0-5.7,1.2-7.8,3.1c0.1,0.1,0.2,0.3,0.4,0.5c0,0,0.8-0.1,1.7-0.1c1.7-1.2,3.6-1.8,5.7-1.8
+ c2.6,0,5.1,1.1,7,3c-0.2-0.1-0.1,0,0,0.1c-0.6-0.4-1.2-0.8-1.7-0.8c1,0.8,2.6,2.7,2.4,6.2c-0.3-0.6-0.6-1-0.9-1.3
+ c0.4,3.5,0,4.2-0.2,5.1c0-0.4-0.2-0.7-0.3-0.9c0,0,0,1.1-0.7,2.6c-0.5,1.2-1.1,1.5-1.3,1.5c-0.2,0-0.1-0.2-0.1-0.4
+ c0,0-0.4,0.2-0.7,0.6c-0.3,0.4-0.6,0.8-0.8,0.6c0.1-0.1,0.2-0.3,0.3-0.4c-0.1,0.1-0.5,0.4-1.2,0.5c-0.3,0-1.6,0.3-3.3-0.6
+ c0.3,0,0.6-0.1,0.9,0.1c-0.3-0.3-1-0.3-1.5-0.4c-0.5-0.4-1.1-1-1.4-1.4c1.3,0.3,2.8,0.1,3.6-0.5s1.3-1,1.8-0.9
+ c0.4,0.1,0.7-0.4,0.4-0.8c-0.3-0.4-1.2-1-2.3-0.7c-0.8,0.2-1.8,1.1-3.3,0.2c-1.3-0.8-1.3-1.4-1.3-1.8c0-0.3,0.2-0.7,0.5-0.8
+ c0.2,0.1,0.3,0.1,0.3,0.1s-0.1-0.1-0.1-0.2l0,0c0.1,0,0.4,0.2,0.6,0.2c0.2,0.1,0.3,0.2,0.3,0.2s0,0,0-0.1c0,0-0.1-0.2-0.3-0.3l0,0
+ c0.1,0,0.2,0.1,0.4,0.2c0-0.2,0.1-0.4,0.1-0.7c0-0.2,0-0.3-0.1-0.4c-0.1-0.1,0-0.1,0.1,0c0-0.1,0-0.1-0.1-0.2l0,0c0,0,0,0,0-0.1
+ c0.2-0.3,1.8-1.2,1.9-1.3c0.2-0.1,0.3-0.3,0.4-0.5c0.2-0.1,0.3-0.5,0.3-0.8c0-0.1-0.2-0.3-0.4-0.3c-0.1,0-0.4-0.1-0.6,0l0,0
+ c-0.3,0-0.7,0-1.2,0s-0.8-0.3-1-0.6c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2c0.2-0.8,0.7-1.5,1.4-2.1c0,0-0.2,0-0.1,0
+ c0,0,0.3-0.2,0.4-0.2c0.1,0-0.3-0.1-0.6-0.1c-0.5,0.2-0.6,0.2-0.8,0.3c0.1-0.1,0.3-0.2,0.2-0.2c-0.3,0.1-0.7,0.4-1.1,0.6v-0.1
+ c-0.2,0.1-0.6,0.4-0.7,0.7c0-0.1,0-0.1,0-0.1c-0.1,0-0.2,0.2-0.3,0.3l0,0c-1.1-0.3-2-0.2-2.8,0c-0.2-0.1-0.6-0.5-0.9-1
+ c0,0,0,0.1-0.1,0.1c-0.1-0.4-0.3-0.9-0.3-1.3v-0.1c0,0-0.1,0.1-0.3,0.3c-0.1,0.2-0.2,0.3-0.2,0.5c0,0.1-0.1,0.2-0.1,0.2v-0.2
+ c0,0.1-0.1,0.2-0.2,0.3c0,0.2,0,0.3-0.1,0.4l0,0c0,0,0-0.2,0-0.1c-0.1,0.2-0.2,0.5-0.2,0.8c-0.1,0.3-0.1,0.5-0.1,0.8s0,0.7,0,1.2
+ c0,0.1,0,0.1,0,0.2c-0.3,0.4-0.5,0.7-0.6,0.9c-0.4,0.7-0.7,1.8-1,3.5c0,0,0.2-0.6,0.6-1.3l0,0c-0.3,0.9-0.5,2.3-0.4,4.4
+ c0-0.1,0.1-0.6,0.2-1.3c0.1,1.4,0.5,3.1,1.5,5c0.8,1.4,1.7,2.4,2.7,3.2c0.2,0.2,0.4,0.3,0.6,0.5c1.3,1,3.3,2.1,5,2.4
+ c-0.6-0.2-1-0.5-1-0.5s2,0.7,3.5,0.6c-0.5-0.1-0.6-0.3-0.6-0.3s4.2,0.2,6.4-1.5c0.5-0.4,0.8-0.8,0.9-1.2c0.6-0.4,1.3-0.8,2-1.6
+ c1.2-1.2,1.3-2.1,1.4-3v0.1C-14,55.2-14,54.9-14.1,54.7z"/>
+</svg>
diff --git a/browser/branding/aurora/default16.png b/browser/branding/aurora/default16.png
new file mode 100644
index 000000000..3b2baaa8d
--- /dev/null
+++ b/browser/branding/aurora/default16.png
Binary files differ
diff --git a/browser/branding/aurora/default32.png b/browser/branding/aurora/default32.png
new file mode 100644
index 000000000..04cfba796
--- /dev/null
+++ b/browser/branding/aurora/default32.png
Binary files differ
diff --git a/browser/branding/aurora/default48.png b/browser/branding/aurora/default48.png
new file mode 100644
index 000000000..85e3c0d4b
--- /dev/null
+++ b/browser/branding/aurora/default48.png
Binary files differ
diff --git a/browser/branding/aurora/disk.icns b/browser/branding/aurora/disk.icns
new file mode 100644
index 000000000..d9e0e6b61
--- /dev/null
+++ b/browser/branding/aurora/disk.icns
Binary files differ
diff --git a/browser/branding/aurora/document.icns b/browser/branding/aurora/document.icns
new file mode 100644
index 000000000..7eedd34ee
--- /dev/null
+++ b/browser/branding/aurora/document.icns
Binary files differ
diff --git a/browser/branding/aurora/document.ico b/browser/branding/aurora/document.ico
new file mode 100644
index 000000000..2402ac57f
--- /dev/null
+++ b/browser/branding/aurora/document.ico
Binary files differ
diff --git a/browser/branding/aurora/dsstore b/browser/branding/aurora/dsstore
new file mode 100644
index 000000000..4a5fa3f54
--- /dev/null
+++ b/browser/branding/aurora/dsstore
Binary files differ
diff --git a/browser/branding/aurora/firefox.VisualElementsManifest.xml b/browser/branding/aurora/firefox.VisualElementsManifest.xml
new file mode 100644
index 000000000..7654e0ab7
--- /dev/null
+++ b/browser/branding/aurora/firefox.VisualElementsManifest.xml
@@ -0,0 +1,8 @@
+<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
+ <VisualElements
+ ShowNameOnSquare150x150Logo='on'
+ Square150x150Logo='browser\VisualElements\VisualElements_150.png'
+ Square70x70Logo='browser\VisualElements\VisualElements_70.png'
+ ForegroundText='light'
+ BackgroundColor='#14171a'/>
+</Application>
diff --git a/browser/branding/aurora/firefox.icns b/browser/branding/aurora/firefox.icns
new file mode 100644
index 000000000..662165bb3
--- /dev/null
+++ b/browser/branding/aurora/firefox.icns
Binary files differ
diff --git a/browser/branding/aurora/firefox.ico b/browser/branding/aurora/firefox.ico
new file mode 100644
index 000000000..4f1856b99
--- /dev/null
+++ b/browser/branding/aurora/firefox.ico
Binary files differ
diff --git a/browser/branding/aurora/locales/browserconfig.properties b/browser/branding/aurora/locales/browserconfig.properties
new file mode 100644
index 000000000..06cefece3
--- /dev/null
+++ b/browser/branding/aurora/locales/browserconfig.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/.
+
+# Do NOT localize or otherwise change these values
+browser.startup.homepage=about:home
diff --git a/browser/branding/aurora/locales/en-US/brand.dtd b/browser/branding/aurora/locales/en-US/brand.dtd
new file mode 100644
index 000000000..9598f79b0
--- /dev/null
+++ b/browser/branding/aurora/locales/en-US/brand.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 brandShorterName "Firefox">
+<!ENTITY brandShortName "Firefox Developer Edition">
+<!ENTITY brandFullName "Firefox Developer Edition">
+<!ENTITY vendorShortName "Mozilla">
+<!ENTITY trademarkInfo.part1 " ">
diff --git a/browser/branding/aurora/locales/en-US/brand.properties b/browser/branding/aurora/locales/en-US/brand.properties
new file mode 100644
index 000000000..e49d0ae86
--- /dev/null
+++ b/browser/branding/aurora/locales/en-US/brand.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/.
+
+brandShorterName=Firefox
+brandShortName=Firefox Developer Edition
+brandFullName=Firefox Developer Edition
+vendorShortName=Mozilla
+
+syncBrandShortName=Sync
diff --git a/browser/branding/aurora/locales/jar.mn b/browser/branding/aurora/locales/jar.mn
new file mode 100644
index 000000000..24880ad7d
--- /dev/null
+++ b/browser/branding/aurora/locales/jar.mn
@@ -0,0 +1,12 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Aurora branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
+ locale/branding/browserconfig.properties
diff --git a/browser/branding/aurora/locales/moz.build b/browser/branding/aurora/locales/moz.build
new file mode 100644
index 000000000..8bad13124
--- /dev/null
+++ b/browser/branding/aurora/locales/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_DISTRIBUTION_ID_UNQUOTED'] = CONFIG['MOZ_DISTRIBUTION_ID']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/aurora/moz.build b/browser/branding/aurora/moz.build
new file mode 100644
index 000000000..9045cee11
--- /dev/null
+++ b/browser/branding/aurora/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['content', 'locales']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+include('../branding-common.mozbuild')
+FirefoxBranding()
diff --git a/browser/branding/aurora/mozicon128.png b/browser/branding/aurora/mozicon128.png
new file mode 100644
index 000000000..2822bf449
--- /dev/null
+++ b/browser/branding/aurora/mozicon128.png
Binary files differ
diff --git a/browser/branding/aurora/newtab.ico b/browser/branding/aurora/newtab.ico
new file mode 100644
index 000000000..a9b37c08c
--- /dev/null
+++ b/browser/branding/aurora/newtab.ico
Binary files differ
diff --git a/browser/branding/aurora/newwindow.ico b/browser/branding/aurora/newwindow.ico
new file mode 100644
index 000000000..553720771
--- /dev/null
+++ b/browser/branding/aurora/newwindow.ico
Binary files differ
diff --git a/browser/branding/aurora/particles.bmp b/browser/branding/aurora/particles.bmp
new file mode 100644
index 000000000..ab74ce047
--- /dev/null
+++ b/browser/branding/aurora/particles.bmp
Binary files differ
diff --git a/browser/branding/aurora/pbmode.ico b/browser/branding/aurora/pbmode.ico
new file mode 100644
index 000000000..47677c13f
--- /dev/null
+++ b/browser/branding/aurora/pbmode.ico
Binary files differ
diff --git a/browser/branding/aurora/pencil-rtl.bmp b/browser/branding/aurora/pencil-rtl.bmp
new file mode 100644
index 000000000..e50d92db7
--- /dev/null
+++ b/browser/branding/aurora/pencil-rtl.bmp
Binary files differ
diff --git a/browser/branding/aurora/pencil.bmp b/browser/branding/aurora/pencil.bmp
new file mode 100644
index 000000000..252c10f41
--- /dev/null
+++ b/browser/branding/aurora/pencil.bmp
Binary files differ
diff --git a/browser/branding/aurora/pref/firefox-branding.js b/browser/branding/aurora/pref/firefox-branding.js
new file mode 100644
index 000000000..9f005b857
--- /dev/null
+++ b/browser/branding/aurora/pref/firefox-branding.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/. */
+
+pref("startup.homepage_override_url", "");
+pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
+pref("startup.homepage_welcome_url.additional", "");
+// The time interval between checks for a new version (in seconds)
+pref("app.update.interval", 28800); // 8 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
+// Give the user x seconds to react before showing the big UI. default=192 hours
+pref("app.update.promptWaitTime", 691200);
+// URL user can browse to manually if for some reason all update installation
+// attempts fail.
+pref("app.update.url.manual", "https://www.mozilla.org/firefox/aurora/");
+// A default value for the "More information about this update" link
+// supplied in the "An update is available" page of the update wizard.
+pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
+
+// The number of days a binary is permitted to be old
+// without checking for an update. This assumes that
+// app.update.checkInstallTime is true.
+pref("app.update.checkInstallTime.days", 2);
+
+// Give the user x seconds to reboot before showing a badge on the hamburger
+// button. default=4 days
+pref("app.update.badgeWaitTime", 345600);
+
+// Number of usages of the web console or scratchpad.
+// If this is less than 5, then pasting code into the web console or scratchpad is disabled
+pref("devtools.selfxss.count", 5);
diff --git a/browser/branding/aurora/wizHeader.bmp b/browser/branding/aurora/wizHeader.bmp
new file mode 100644
index 000000000..32aefb96e
--- /dev/null
+++ b/browser/branding/aurora/wizHeader.bmp
Binary files differ
diff --git a/browser/branding/aurora/wizHeaderRTL.bmp b/browser/branding/aurora/wizHeaderRTL.bmp
new file mode 100644
index 000000000..5f0ccb1c4
--- /dev/null
+++ b/browser/branding/aurora/wizHeaderRTL.bmp
Binary files differ
diff --git a/browser/branding/aurora/wizWatermark.bmp b/browser/branding/aurora/wizWatermark.bmp
new file mode 100644
index 000000000..56c6c3103
--- /dev/null
+++ b/browser/branding/aurora/wizWatermark.bmp
Binary files differ
diff --git a/browser/branding/branding-common.mozbuild b/browser/branding/branding-common.mozbuild
new file mode 100644
index 000000000..f74724f4a
--- /dev/null
+++ b/browser/branding/branding-common.mozbuild
@@ -0,0 +1,58 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+@template
+def FirefoxBranding():
+ JS_PREFERENCE_FILES += [
+ 'pref/firefox-branding.js',
+ ]
+
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ FINAL_TARGET_FILES['..'] += [
+ 'firefox.VisualElementsManifest.xml',
+ ]
+ FINAL_TARGET_FILES.VisualElements += [
+ 'VisualElements_150.png',
+ 'VisualElements_70.png',
+ ]
+ BRANDING_FILES += [
+ 'appname.bmp',
+ 'bgintro.bmp',
+ 'branding.nsi',
+ 'clock.bmp',
+ 'document.ico',
+ 'firefox.ico',
+ 'newtab.ico',
+ 'newwindow.ico',
+ 'particles.bmp',
+ 'pbmode.ico',
+ 'pencil-rtl.bmp',
+ 'pencil.bmp',
+ 'wizHeader.bmp',
+ 'wizHeaderRTL.bmp',
+ 'wizWatermark.bmp',
+ ]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ BRANDING_FILES += [
+ 'background.png',
+ 'disk.icns',
+ 'document.icns',
+ 'dsstore',
+ 'firefox.icns',
+ ]
+ elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ BRANDING_FILES += [
+ 'default16.png',
+ 'default32.png',
+ 'default48.png',
+ 'mozicon128.png',
+ ]
+ FINAL_TARGET_FILES.icons += ['mozicon128.png']
+ FINAL_TARGET_FILES.chrome.icons.default += [
+ 'default16.png',
+ 'default32.png',
+ 'default48.png',
+ ]
diff --git a/browser/branding/nightly/VisualElements_150.png b/browser/branding/nightly/VisualElements_150.png
new file mode 100644
index 000000000..461961e8d
--- /dev/null
+++ b/browser/branding/nightly/VisualElements_150.png
Binary files differ
diff --git a/browser/branding/nightly/VisualElements_70.png b/browser/branding/nightly/VisualElements_70.png
new file mode 100644
index 000000000..aad81f40d
--- /dev/null
+++ b/browser/branding/nightly/VisualElements_70.png
Binary files differ
diff --git a/browser/branding/nightly/appname.bmp b/browser/branding/nightly/appname.bmp
new file mode 100644
index 000000000..fc1b6343c
--- /dev/null
+++ b/browser/branding/nightly/appname.bmp
Binary files differ
diff --git a/browser/branding/nightly/background.png b/browser/branding/nightly/background.png
new file mode 100644
index 000000000..db5576a33
--- /dev/null
+++ b/browser/branding/nightly/background.png
Binary files differ
diff --git a/browser/branding/nightly/bgintro.bmp b/browser/branding/nightly/bgintro.bmp
new file mode 100644
index 000000000..777ab2e84
--- /dev/null
+++ b/browser/branding/nightly/bgintro.bmp
Binary files differ
diff --git a/browser/branding/nightly/branding.nsi b/browser/branding/nightly/branding.nsi
new file mode 100644
index 000000000..fa13b32c6
--- /dev/null
+++ b/browser/branding/nightly/branding.nsi
@@ -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/.
+
+# NSIS branding defines for nightly builds.
+# The official release build branding.nsi is located in other-license/branding/firefox/
+# The unofficial build branding.nsi is located in browser/branding/unofficial/
+
+# BrandFullNameInternal is used for some registry and file system values
+# instead of BrandFullName and typically should not be modified.
+!define BrandFullNameInternal "Nightly"
+!define CompanyName "mozilla.org"
+!define URLInfoAbout "https://www.mozilla.org"
+!define HelpLink "https://support.mozilla.org"
+
+!define URLStubDownload "http://download.mozilla.org/?os=win&lang=${AB_CD}&product=firefox-nightly-latest"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=nightly&installer_lang=${AB_CD}"
+!define URLSystemRequirements "https://www.mozilla.org/firefox/system-requirements/"
+!define Channel "nightly"
+
+# The installer's certificate name and issuer expected by the stub installer
+!define CertNameDownload "Mozilla Corporation"
+!define CertIssuerDownload "DigiCert SHA2 Assured ID Code Signing CA"
+
+# Dialog units are used so the UI displays correctly with the system's DPI
+# settings.
+# The dialog units for the bitmap's dimensions should match exactly with the
+# bitmap's width and height in pixels.
+!define APPNAME_BMP_WIDTH_DU 159u
+!define APPNAME_BMP_HEIGHT_DU 28u
+!define INTRO_BLURB_WIDTH_DU "230u"
+!define INTRO_BLURB_EDGE_DU "198u"
+!define INTRO_BLURB_LTR_TOP_DU "16u"
+!define INTRO_BLURB_RTL_TOP_DU "11u"
+
+# UI Colors that can be customized for each channel
+!define FOOTER_CONTROL_TEXT_COLOR_NORMAL 0x000000
+!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x999999
+!define FOOTER_BKGRD_COLOR 0xFFFFFF
+!define INTRO_BLURB_TEXT_COLOR 0xFFFFFF
+!define INSTALL_BLURB_TEXT_COLOR 0xFFFFFF
+!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0xFFFFFF
+!define COMMON_TEXT_COLOR_NORMAL 0xFFFFFF
+!define COMMON_TEXT_COLOR_FADED 0xA1AAB3
+!define COMMON_BKGRD_COLOR 0x0F1B26
diff --git a/browser/branding/nightly/clock.bmp b/browser/branding/nightly/clock.bmp
new file mode 100644
index 000000000..c74398edb
--- /dev/null
+++ b/browser/branding/nightly/clock.bmp
Binary files differ
diff --git a/browser/branding/nightly/configure.sh b/browser/branding/nightly/configure.sh
new file mode 100644
index 000000000..edd3bd3e8
--- /dev/null
+++ b/browser/branding/nightly/configure.sh
@@ -0,0 +1,5 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOZ_APP_DISPLAYNAME=Nightly
diff --git a/browser/branding/nightly/content/about-background.png b/browser/branding/nightly/content/about-background.png
new file mode 100644
index 000000000..b572ab237
--- /dev/null
+++ b/browser/branding/nightly/content/about-background.png
Binary files differ
diff --git a/browser/branding/nightly/content/about-logo.png b/browser/branding/nightly/content/about-logo.png
new file mode 100644
index 000000000..cd985a92f
--- /dev/null
+++ b/browser/branding/nightly/content/about-logo.png
Binary files differ
diff --git a/browser/branding/nightly/content/about-logo@2x.png b/browser/branding/nightly/content/about-logo@2x.png
new file mode 100644
index 000000000..fb12dfbd7
--- /dev/null
+++ b/browser/branding/nightly/content/about-logo@2x.png
Binary files differ
diff --git a/browser/branding/nightly/content/about-wordmark.svg b/browser/branding/nightly/content/about-wordmark.svg
new file mode 100644
index 000000000..6f71130b4
--- /dev/null
+++ b/browser/branding/nightly/content/about-wordmark.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="270px" height="48px" viewBox="0 0 270 48">
+ <path fill="#fff" d="M75.5,11.8V7.9c0-2.2,1.2-3.5,3.1-3.5c1,0,1.8,0.3,3,0.9l1.8-3.5c-1.7-1-3.5-1.4-5.7-1.4
+ C73.2,0.3,70,2.8,70,8c0,2.3,0.2,3.7,0.2,3.7h-2.5v3.8H70V37h5.4V15.6h5.1l1.4-3.8H75.5z M92.3,11.2c-6.7,0-11,5.2-11,13.3
+ c0,8.1,4.3,13.2,11.1,13.2c6.8,0,11.2-5,11.2-13.2C103.6,16.5,99.5,11.2,92.3,11.2z M92.5,33.6c-3.3,0-5.1-2.1-5.1-9.5
+ c0-6.1,1.5-8.8,5-8.8c3.2,0,5.2,2.1,5.2,9.3C97.6,30.9,95.8,33.6,92.5,33.6z M43.7,11.1c-2.5,0-4.4,1.3-6.4,4c0-1.4-0.3-2.8-0.9-4
+ l-5,1.3c0.6,1.6,0.9,3.6,0.9,6.8V37h5.5V19.9c0.5-2,2.4-3.7,4.7-3.7c0.6,0,1,0.1,1.6,0.4l1.7-5.1C45,11.2,44.5,11.1,43.7,11.1z
+ M0,37h5.7V21.2h9.6v-4.6H5.7V7.2h11.8l0.7-4.7H0V37z M21.4,37h5.5V11.2l-5.5,1V37z M24.2,0.7c-2,0-3.6,1.6-3.6,3.7
+ c0,2,1.5,3.6,3.5,3.6c2,0,3.7-1.6,3.7-3.6C27.8,2.3,26.2,0.7,24.2,0.7z M125.2,11.8h-6.4c-0.7,1.1-3.3,6.1-4,7.7
+ c-1.2-2.3-3.4-6.3-4.6-8.2l-5.9,1.2l7.3,10.8L102.2,37h6.9c0.9-1.4,4.5-7.5,5.5-9.4c0.5,0.9,4.6,8,5.5,9.4h6.9l-9.2-13.8L125.2,11.8
+ z M62.7,13.8c-2.1-1.9-4.4-2.6-6.9-2.6c-3.2,0-5.7,1-7.7,3.4C45.9,17.1,45,20,45,24.5c0,8.1,4.5,13.2,11.6,13.2
+ c3.4,0,6.4-1.1,9.1-3.3L63.4,31c-1.9,1.6-3.9,2.5-6.3,2.5c-4.9,0-6.2-3.7-6.2-7.2v-0.4H66v-1.2C66,18.9,64.9,15.8,62.7,13.8z
+ M51,21.8c0-4.1,1.7-6.5,4.8-6.5c2.8,0,4.5,2.4,4.5,6.5H51z M198.5,14.3l-2.4-2.4c-1.2,0.8-2.2,1.1-3.5,1.1c-3,0-3.8-1.4-7.6-1.4
+ c-5.4,0-9.2,3.4-9.2,8.4c0,3.3,2.2,6.1,5.6,7.2c-3.4,1-4.5,2.2-4.5,4.3c0,2.2,1.8,3.6,4.7,3.6h3.8c2.5,0,3.9,0.2,4.9,0.9
+ c0.9,0.6,1.4,1.6,1.4,3c0,3.1-2.2,4.4-6,4.4c-2,0-3.8-0.5-5.1-1.2c-0.9-0.6-1.5-1.6-1.5-2.9c0-0.8,0.3-1.7,0.7-2.2l-4.1,0.4
+ c-0.3,1-0.5,1.7-0.5,2.6c0,3.5,3,6.4,10.8,6.4c6.1,0,9.9-2.5,9.9-7.9c0-2.1-0.8-3.9-2.7-5.3c-1.5-1.1-3.1-1.4-6-1.4h-4
+ c-1.3,0-2-0.5-2-1.2c0-0.8,1.1-1.7,4.5-2.9c1.8,0,3.4-0.3,4.7-1.1c2.3-1.4,3.7-4.1,3.7-6.8c0-1.6-0.5-3-1.5-4.3
+ c0.4,0.2,1.1,0.3,1.7,0.3C195.8,15.8,196.9,15.4,198.5,14.3z M185,24.8c-3.1,0-4.8-1.7-4.8-4.8c0-3.5,1.6-5.1,4.7-5.1
+ c3.3,0,4.6,1.5,4.6,4.9C189.5,23.1,188,24.8,185,24.8z M168.6,1.3c-1.7,0-3,1.4-3,3.1c0,1.7,1.4,3,3,3c1.7,0,3.1-1.3,3.1-3
+ C171.6,2.7,170.3,1.3,168.6,1.3z M245.7,34.5c-1.1,0-1.4-0.6-1.4-2.5V6.5c0-3.8-0.6-5.9-0.6-5.9l-3.9,0.8c0,0,0.6,1.9,0.6,5.1v26.4
+ c0,1.8,0.4,2.8,1.2,3.5c0.7,0.7,1.7,1,2.9,1c1,0,1.5-0.1,2.5-0.5l-0.8-2.5C246.2,34.4,245.8,34.5,245.7,34.5z M212.7,11.6
+ c-3.2,0-6.1,1.8-8.3,3.9c0,0,0.2-1.8,0.2-3.4V6.3c0-3.8-0.7-5.9-0.7-5.9L200,1.1c0,0,0.7,1.9,0.7,5.1V37h3.9V19.3
+ c2.1-2.7,4.9-4.2,7.2-4.2c1.3,0,2.3,0.4,2.9,1c0.7,0.7,0.9,1.8,0.9,3.7V37h3.8V19.1c0-1.8-0.1-2.6-0.4-3.6
+ C218.4,13.2,215.7,11.6,212.7,11.6z M265.4,12.1l-4.9,16.4c-0.6,2-1.6,5.2-1.6,5.2s-0.7-3.9-1.5-6.2l-5.1-16.2l-3.9,1.3l5.4,15.6
+ c0.8,2.5,2.2,7.4,2.5,9l1.6-0.3c-1.3,5.1-2.5,6.7-5.7,7.6l1.2,2.7c4.4-1,6.4-4.3,8-9.3l8.6-25.8H265.4z M234.9,15l1.2-2.9h-6.2
+ c0-3.3,0.5-7.2,0.5-7.2l-4.1,0.9c0,0-0.4,3.9-0.4,6.3h-3.2V15h3.2v17.1c0,2.5,0.7,4.1,2.4,5c0.9,0.4,1.9,0.7,3.3,0.7
+ c1.8,0,3.1-0.4,4.4-1l-0.6-2.5c-0.7,0.3-1.3,0.5-2.4,0.5c-2.4,0-3.2-0.9-3.2-3.7V15H234.9z M166.5,37h4.1V11.5l-4.1,0.6V37z
+ M156.8,21.3c0,5,0.4,10.5,0.4,10.5s-1.4-3.8-3.2-7.2L142.7,2.7h-4.8V37h4.2l-0.2-19.9c0-4.5-0.4-9.3-0.4-9.3s1.7,4.1,3.9,8.2l11,21
+ h4.3V2.7h-4L156.8,21.3z M128.3,12.9c-0.3-0.1-0.7-0.1-1-0.1v2.3h0.3v-1c0.3,0,0.7,1,0.7,1s0.2,0,0.4,0c-0.2-0.3-0.3-0.7-0.6-1
+ C128.8,14.1,128.9,13.1,128.3,12.9z M127.6,13.8v-0.7c0,0,0.7,0,0.7,0.3C128.3,13.9,127.8,13.9,127.6,13.8z M128,12
+ c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S129.1,12,128,12z M128,15.5c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5s1.5,0.7,1.5,1.5
+ S128.8,15.5,128,15.5z"/>
+</svg>
diff --git a/browser/branding/nightly/content/about.png b/browser/branding/nightly/content/about.png
new file mode 100644
index 000000000..5917b1e11
--- /dev/null
+++ b/browser/branding/nightly/content/about.png
Binary files differ
diff --git a/browser/branding/nightly/content/aboutDialog.css b/browser/branding/nightly/content/aboutDialog.css
new file mode 100644
index 000000000..f0df8b1aa
--- /dev/null
+++ b/browser/branding/nightly/content/aboutDialog.css
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#aboutDialogContainer {
+ background-image: url("chrome://branding/content/about-background.png");
+ background-repeat: no-repeat;
+ background-color: rgb(10,17,37);
+ color: #fff;
+}
+
+.text-link {
+ color: #fff !important;
+ text-decoration: underline;
+}
+
+.text-link:-moz-focusring {
+ border-color: #fff;
+}
+
+#rightBox {
+ /* this margin prevents text from overlapping the planet image */
+ margin-left: 280px;
+ margin-right: 20px;
+}
+
+#bottomBox {
+ background-color: rgba(0,0,0,.7);
+}
diff --git a/browser/branding/nightly/content/icon48.png b/browser/branding/nightly/content/icon48.png
new file mode 100644
index 000000000..88a307340
--- /dev/null
+++ b/browser/branding/nightly/content/icon48.png
Binary files differ
diff --git a/browser/branding/nightly/content/icon64.png b/browser/branding/nightly/content/icon64.png
new file mode 100644
index 000000000..2eab258cb
--- /dev/null
+++ b/browser/branding/nightly/content/icon64.png
Binary files differ
diff --git a/browser/branding/nightly/content/identity-icons-brand.svg b/browser/branding/nightly/content/identity-icons-brand.svg
new file mode 100644
index 000000000..6c33113e5
--- /dev/null
+++ b/browser/branding/nightly/content/identity-icons-brand.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+ <path fill="#144787" d="M15.953,30.000 C8.221,30.000 1.953,23.732 1.953,16.000 C1.953,8.268 8.221,2.000 15.953,2.000 C23.685,2.000 29.953,8.268 29.953,16.000 C29.953,23.732 23.685,30.000 15.953,30.000 ZM16.000,4.000 C9.373,4.000 4.000,9.373 4.000,16.000 C4.000,22.627 9.373,28.000 16.000,28.000 C22.627,28.000 28.000,22.627 28.000,16.000 C28.000,9.373 22.627,4.000 16.000,4.000 ZM27.085,16.311 C27.142,16.652 27.085,17.189 26.942,17.483 C26.885,17.958 26.784,18.470 26.561,18.931 C26.407,19.254 26.189,19.798 25.772,19.846 C25.646,19.858 25.319,20.214 25.283,20.155 C25.208,20.028 25.155,19.869 24.999,19.809 C24.873,19.751 24.990,19.698 24.895,19.643 C24.828,19.607 24.816,19.527 24.803,19.452 C24.756,19.459 24.710,19.469 24.668,19.492 C24.580,19.543 24.528,19.636 24.445,19.693 C24.391,19.671 24.300,19.631 24.307,19.561 C24.235,19.627 24.158,19.762 24.088,19.660 C24.030,19.576 24.071,19.459 24.068,19.367 C24.064,19.275 23.978,19.139 23.883,19.254 C23.816,19.334 23.769,19.345 23.665,19.365 C23.570,19.385 23.496,19.449 23.395,19.450 C23.156,19.456 23.176,19.563 23.117,19.744 C23.063,19.902 22.845,19.920 22.750,20.050 C22.700,20.117 22.549,20.431 22.421,20.318 C22.319,20.227 22.581,19.988 22.581,19.868 C22.581,19.767 22.498,19.709 22.475,19.618 C22.457,19.552 22.479,19.498 22.403,19.463 C22.448,19.376 22.500,19.239 22.455,19.142 C22.398,19.015 22.189,19.130 22.139,19.206 C22.088,19.277 21.964,19.523 21.848,19.374 C21.819,19.341 21.840,19.299 21.785,19.301 C21.747,19.301 21.720,19.325 21.702,19.354 C21.621,19.328 21.652,19.254 21.682,19.199 C21.770,19.040 21.781,18.864 21.900,18.718 C22.026,18.561 22.220,18.468 22.333,18.301 C22.372,18.242 22.441,18.131 22.385,18.064 C22.364,18.038 22.326,18.027 22.313,17.995 C22.297,17.956 22.315,17.913 22.310,17.874 C22.268,17.891 22.222,17.909 22.175,17.894 C22.169,17.851 22.175,17.805 22.164,17.761 C22.108,17.783 22.049,17.851 21.982,17.836 C21.919,17.823 21.927,17.867 21.857,17.865 C21.916,17.772 21.952,17.667 22.015,17.575 C22.051,17.521 22.101,17.473 22.130,17.413 C22.198,17.271 22.058,17.158 22.081,17.020 C22.106,16.863 22.281,16.825 22.417,16.839 C22.554,16.854 22.707,16.980 22.845,16.930 C22.975,16.887 23.014,16.706 22.977,16.590 C22.933,16.460 22.768,16.424 22.779,16.265 C22.784,16.172 22.831,16.089 22.813,15.996 C22.799,15.921 22.761,15.854 22.743,15.779 C22.700,15.598 22.867,15.549 22.923,15.405 C22.951,15.329 22.964,15.138 23.092,15.223 C23.205,15.300 23.158,15.476 23.266,15.564 C23.392,15.670 23.563,15.600 23.690,15.535 C23.796,15.480 23.958,15.425 24.012,15.314 C24.088,15.163 23.947,14.988 24.138,14.892 C24.217,14.853 24.415,14.744 24.505,14.779 C24.587,14.810 24.616,14.902 24.675,14.961 C24.699,14.882 24.731,14.810 24.781,14.748 C24.889,14.613 25.040,14.518 25.035,14.330 C25.082,14.104 25.010,14.119 24.970,13.940 C24.961,13.858 24.888,13.292 25.017,13.315 C25.310,13.366 25.065,12.676 25.022,12.563 C25.008,12.523 24.942,12.470 24.895,12.448 C24.767,12.330 24.692,12.528 24.611,12.454 C24.512,12.332 24.530,12.133 24.515,11.982 C24.490,11.818 24.397,11.698 24.393,11.534 C24.391,11.455 23.924,10.932 24.021,10.856 C24.079,10.823 24.816,10.879 24.776,10.823 C24.654,10.644 24.704,10.469 24.461,10.371 C24.307,10.307 24.204,10.138 24.055,10.065 C23.983,10.030 23.679,9.806 23.937,9.790 C24.106,9.779 23.902,9.526 23.827,9.502 C23.735,9.473 23.742,9.752 23.654,9.509 C23.635,9.469 23.273,9.012 23.264,9.056 C23.252,9.127 23.343,9.225 23.333,9.307 C23.306,9.533 22.982,9.108 22.993,9.125 C22.933,9.059 22.694,8.882 22.666,8.802 C22.671,8.819 22.774,8.629 22.775,8.629 C22.833,8.553 22.774,8.467 22.707,8.398 C22.624,8.314 22.486,8.305 22.473,8.177 C22.471,8.157 22.374,7.979 22.459,7.982 C22.529,7.988 22.754,8.172 22.836,8.210 C23.061,8.314 22.996,8.383 23.128,8.505 C23.313,8.638 23.606,8.799 23.768,8.997 C23.793,9.043 24.122,9.380 24.134,9.252 C24.138,9.209 24.019,8.872 23.971,8.855 C23.971,8.855 23.755,8.580 23.750,8.556 C23.751,8.565 23.408,8.154 23.493,8.168 C23.624,8.192 24.093,8.542 24.064,8.677 C24.044,8.766 24.221,8.830 24.248,8.912 C24.255,8.935 24.567,9.221 24.611,9.241 C24.658,9.263 24.819,9.415 24.855,9.460 C24.920,9.506 24.972,9.462 24.994,9.578 C25.003,9.631 25.087,9.819 25.114,9.855 C25.193,9.961 25.247,10.125 25.294,10.249 C25.360,10.431 25.319,10.633 25.323,10.821 C25.342,10.874 25.233,10.938 25.240,11.005 C25.253,11.111 25.254,11.235 25.269,11.329 C25.283,11.426 25.466,11.679 25.436,11.759 C25.366,11.907 25.416,11.891 25.486,12.027 C25.533,12.120 25.436,12.159 25.479,12.277 C25.296,12.295 25.477,12.379 25.360,12.421 C25.254,12.435 25.222,12.414 25.132,12.483 C25.100,12.506 25.506,13.100 25.580,12.526 C25.600,12.372 25.765,12.220 25.921,12.157 C26.006,12.120 26.087,12.281 26.110,12.120 C26.115,12.075 26.029,11.929 26.071,11.909 C26.155,11.878 26.299,12.539 26.365,12.588 C26.529,12.703 26.642,12.893 26.664,13.093 C26.687,13.325 27.108,13.685 27.021,13.898 C26.960,14.050 26.984,14.494 27.000,14.662 C27.027,14.841 27.085,14.984 27.086,15.176 C27.086,15.280 27.016,15.531 27.063,15.619 C27.169,15.815 27.050,16.103 27.085,16.311 ZM22.142,23.184 C22.112,23.125 22.171,23.051 22.238,23.053 C22.254,23.009 22.333,22.943 22.382,22.954 C22.416,22.963 22.419,23.000 22.453,22.969 C22.486,22.940 22.480,22.892 22.522,22.869 C22.633,22.801 22.687,22.918 22.624,23.003 C22.576,23.067 22.455,23.115 22.380,23.094 C22.284,23.069 22.231,23.151 22.142,23.184 ZM23.207,22.324 C23.239,22.315 23.270,22.260 23.306,22.269 C23.406,22.296 23.298,22.486 23.288,22.537 C23.275,22.612 23.284,22.763 23.169,22.745 L23.178,22.726 C23.171,22.725 23.158,22.726 23.151,22.728 C23.149,22.736 23.142,22.743 23.142,22.745 C23.147,22.701 23.158,22.644 23.137,22.603 C23.111,22.555 23.056,22.544 23.013,22.521 C22.969,22.499 22.959,22.482 22.986,22.442 C23.007,22.409 23.041,22.351 23.077,22.331 C23.117,22.311 23.164,22.338 23.207,22.324 ZM22.843,22.551 C22.923,22.537 22.969,22.582 23.023,22.635 C23.084,22.697 23.040,22.772 22.962,22.794 C22.887,22.818 22.856,22.754 22.786,22.754 C22.784,22.741 22.788,22.725 22.782,22.715 L22.784,22.717 C22.770,22.644 22.750,22.570 22.843,22.551 ZM21.519,24.762 C21.567,24.727 21.846,24.663 21.821,24.594 C21.794,24.527 21.833,24.497 21.894,24.472 C21.932,24.456 21.966,24.405 22.006,24.399 C22.036,24.472 22.031,24.559 22.135,24.559 C22.214,24.559 22.293,24.523 22.355,24.476 C22.432,24.421 22.439,24.330 22.536,24.295 C22.621,24.266 22.687,24.230 22.764,24.186 C22.833,24.148 22.894,24.066 22.975,24.056 C23.014,24.053 23.111,24.047 23.138,24.086 C23.178,24.144 23.007,24.248 22.971,24.277 C22.933,24.308 22.826,24.395 22.905,24.445 C22.964,24.479 23.050,24.417 23.102,24.395 C23.176,24.363 23.259,24.339 23.313,24.275 C23.363,24.217 23.378,24.138 23.448,24.097 C23.536,24.044 23.588,23.991 23.640,23.902 C23.674,23.845 23.670,23.783 23.719,23.732 C23.768,23.678 23.748,23.617 23.778,23.557 C23.830,23.464 23.868,23.572 23.893,23.610 C23.953,23.590 23.971,23.508 24.025,23.473 C24.059,23.452 24.122,23.435 24.138,23.393 C24.152,23.359 24.143,23.326 24.174,23.297 C24.231,23.242 24.357,23.260 24.429,23.226 C24.472,23.206 24.526,23.098 24.578,23.111 C24.487,23.399 24.310,23.603 24.102,23.820 C23.895,24.033 23.706,24.266 23.458,24.435 C23.205,24.609 22.957,24.793 22.680,24.931 C22.405,25.070 22.198,25.292 21.945,25.461 C21.384,25.837 20.791,26.207 20.145,26.425 C19.822,26.536 19.494,26.666 19.162,26.751 C19.054,26.779 18.944,26.802 18.835,26.828 C18.811,26.833 18.676,26.883 18.658,26.872 C18.648,26.848 18.444,26.892 18.414,26.908 C18.301,26.966 18.196,26.992 18.071,26.992 C17.968,26.992 17.857,26.963 17.765,27.012 C17.733,27.030 17.578,27.065 17.628,26.974 C17.655,26.924 17.821,26.954 17.862,26.954 C17.963,26.952 18.056,26.912 18.150,26.879 C18.270,26.839 18.383,26.793 18.504,26.755 C18.570,26.735 18.631,26.724 18.687,26.688 C18.784,26.624 18.892,26.637 18.993,26.598 C19.061,26.573 19.104,26.473 19.201,26.498 C19.250,26.511 19.275,26.533 19.329,26.533 C19.399,26.531 19.370,26.513 19.385,26.462 C19.403,26.392 19.458,26.381 19.487,26.438 C19.525,26.511 19.644,26.394 19.694,26.381 C19.771,26.363 19.759,26.287 19.866,26.290 C19.962,26.296 20.025,26.216 20.116,26.197 C20.163,26.186 20.285,26.190 20.310,26.145 C20.187,26.148 20.064,26.166 19.947,26.214 C19.825,26.263 19.710,26.334 19.579,26.360 C19.457,26.381 19.329,26.369 19.207,26.396 C19.090,26.422 18.986,26.482 18.874,26.520 C18.777,26.555 18.653,26.580 18.556,26.531 C18.448,26.473 18.551,26.391 18.624,26.372 C18.720,26.350 18.836,26.365 18.919,26.305 C18.986,26.258 19.005,26.172 19.025,26.099 C18.982,26.103 18.525,26.137 18.667,26.010 C18.732,25.950 18.835,25.935 18.919,25.917 C19.013,25.897 19.102,25.851 19.201,25.855 C19.324,25.860 19.401,25.950 19.520,25.868 C19.590,25.820 19.647,25.747 19.728,25.716 C19.807,25.684 19.904,25.746 19.976,25.693 C20.039,25.645 20.052,25.567 20.138,25.545 C20.224,25.523 20.384,25.545 20.399,25.418 C20.408,25.352 20.267,25.281 20.230,25.230 C20.183,25.164 20.122,25.079 20.066,25.020 C20.028,24.982 19.915,24.969 19.920,24.904 C19.929,24.802 20.086,24.807 20.156,24.791 C20.266,24.763 20.348,24.703 20.467,24.729 C20.569,24.749 20.647,24.763 20.738,24.703 C20.819,24.650 20.891,24.596 20.986,24.569 C20.943,24.654 20.934,24.754 20.879,24.835 C20.830,24.904 20.749,24.947 20.695,25.015 C20.542,25.206 20.843,25.316 20.879,25.172 C20.897,25.095 20.857,25.039 20.952,24.999 C21.010,24.975 21.102,24.977 21.110,24.907 C21.123,24.820 21.227,24.762 21.312,24.751 C21.400,24.738 21.497,24.663 21.583,24.671 C21.562,24.691 21.508,24.727 21.519,24.762 ZM19.358,6.878 C19.266,7.018 19.200,7.122 19.066,7.232 C18.993,7.294 19.013,7.534 18.871,7.436 C18.822,7.401 18.820,7.388 18.748,7.396 C18.700,7.401 18.649,7.447 18.639,7.487 C18.612,7.492 18.581,7.587 18.547,7.525 C18.527,7.534 18.419,7.572 18.400,7.563 C18.371,7.551 18.347,7.478 18.319,7.454 C18.245,7.394 18.373,7.308 18.310,7.246 C18.263,7.199 18.205,7.193 18.151,7.226 C18.065,7.283 18.029,7.228 17.946,7.244 C17.858,7.261 17.905,7.195 17.891,7.131 C17.853,7.113 17.808,7.117 17.770,7.128 C17.720,7.142 17.736,7.152 17.731,7.173 C17.713,7.157 17.630,7.152 17.618,7.150 C17.610,7.117 17.702,7.062 17.707,7.011 C17.707,6.997 17.689,6.860 17.684,6.855 C17.616,6.805 17.722,6.776 17.761,6.811 C17.806,6.849 17.844,6.778 17.902,6.802 C17.907,6.774 17.799,6.742 17.776,6.738 C17.715,6.729 17.653,6.800 17.589,6.807 C17.567,6.809 17.475,6.842 17.463,6.813 C17.452,6.791 17.468,6.758 17.475,6.738 C17.443,6.711 17.407,6.694 17.364,6.691 C17.299,6.685 17.233,6.705 17.173,6.672 C17.098,6.629 17.057,6.599 16.969,6.585 C16.906,6.574 16.852,6.537 16.843,6.474 C16.834,6.423 16.808,6.293 16.821,6.242 C16.893,6.228 16.846,6.304 16.904,6.308 C16.963,6.312 17.008,6.330 17.067,6.337 C17.118,6.343 17.191,6.377 17.242,6.361 C17.324,6.335 17.348,6.204 17.395,6.193 C17.393,6.175 17.382,6.160 17.366,6.149 C17.404,6.140 17.441,6.128 17.472,6.106 C17.445,6.091 17.382,6.133 17.391,6.075 C17.398,6.033 17.423,5.996 17.429,5.954 C17.438,5.869 17.231,5.867 17.181,5.887 C17.139,5.902 17.102,5.931 17.076,5.967 C17.037,6.025 16.994,6.000 16.925,5.991 C16.938,5.900 16.871,5.883 16.818,5.825 C16.742,5.745 16.690,5.636 16.699,5.528 C16.706,5.439 16.629,5.364 16.690,5.278 C16.785,5.145 17.116,5.151 17.272,5.206 C17.623,5.224 17.968,5.258 18.310,5.355 C18.696,5.464 19.038,5.663 19.417,5.781 C19.597,5.836 19.649,5.934 19.590,6.104 C19.539,6.250 19.669,6.293 19.784,6.333 C19.886,6.372 20.003,6.441 19.955,6.568 C19.913,6.678 19.759,6.701 19.658,6.718 C19.530,6.738 19.430,6.765 19.358,6.878 ZM16.767,16.426 C16.812,16.438 16.868,16.393 16.922,16.404 C16.956,16.411 16.963,16.433 16.990,16.444 C17.021,16.460 17.021,16.460 17.051,16.453 C17.073,16.438 17.096,16.433 17.127,16.449 C17.147,16.462 17.152,16.484 17.179,16.493 C17.218,16.510 17.247,16.493 17.289,16.491 C17.350,16.500 17.377,16.542 17.420,16.482 C17.447,16.426 17.459,16.373 17.526,16.373 C17.578,16.377 17.618,16.400 17.578,16.442 C17.553,16.473 17.535,16.502 17.538,16.551 C17.549,16.590 17.582,16.590 17.610,16.566 C17.643,16.533 17.653,16.473 17.709,16.466 C17.759,16.462 17.804,16.508 17.857,16.511 C17.882,16.513 17.905,16.508 17.932,16.517 C17.961,16.526 17.972,16.539 17.991,16.551 C18.040,16.571 18.076,16.553 18.115,16.577 C18.189,16.632 18.220,16.715 18.277,16.774 C18.311,16.807 18.364,16.817 18.391,16.852 C18.403,16.874 18.409,16.889 18.419,16.903 C18.439,16.914 18.459,16.925 18.473,16.947 C18.500,16.981 18.480,17.011 18.475,17.052 C18.468,17.087 18.484,17.116 18.502,17.153 C18.531,17.195 18.579,17.322 18.489,17.328 C18.466,17.335 18.432,17.335 18.414,17.331 C18.382,17.331 18.400,17.335 18.378,17.315 C18.346,17.291 18.313,17.266 18.283,17.242 C18.238,17.202 18.240,17.145 18.211,17.094 C18.195,17.065 18.162,17.054 18.124,17.038 C18.103,17.020 18.090,17.020 18.060,17.001 C18.033,16.992 18.008,16.998 17.975,16.998 C17.923,16.996 17.849,16.932 17.821,16.890 C17.804,16.861 17.801,16.847 17.767,16.847 C17.742,16.845 17.715,16.867 17.688,16.867 C17.621,16.867 17.571,16.830 17.499,16.848 C17.436,16.865 17.384,16.852 17.314,16.845 C17.240,16.848 17.161,16.836 17.105,16.785 C17.073,16.759 17.055,16.723 17.015,16.699 C16.979,16.692 16.938,16.694 16.911,16.684 C16.848,16.668 16.769,16.655 16.711,16.628 C16.665,16.608 16.636,16.566 16.595,16.535 C16.546,16.506 16.474,16.491 16.424,16.462 C16.379,16.449 16.264,16.422 16.318,16.375 C16.345,16.351 16.406,16.329 16.440,16.327 C16.501,16.336 16.505,16.386 16.546,16.415 C16.578,16.440 16.625,16.429 16.659,16.397 C16.679,16.375 16.674,16.358 16.704,16.377 C16.731,16.386 16.747,16.415 16.767,16.426 ZM16.489,5.335 C16.436,5.355 16.381,5.339 16.325,5.351 C16.325,5.348 16.323,5.342 16.323,5.339 C16.285,5.328 16.242,5.295 16.257,5.253 C16.303,5.233 16.589,5.189 16.598,5.257 C16.604,5.293 16.515,5.324 16.489,5.335 ZM15.832,5.765 C15.861,5.889 15.724,6.033 15.696,6.158 C15.671,6.273 15.550,6.434 15.455,6.505 C15.354,6.581 15.158,6.740 15.027,6.700 C14.950,6.678 14.871,6.658 14.791,6.638 C14.711,6.618 14.644,6.548 14.572,6.530 C14.617,6.417 14.687,6.558 14.741,6.477 C14.747,6.501 14.799,6.523 14.820,6.499 C14.844,6.477 14.822,6.415 14.822,6.388 C14.820,6.319 14.939,6.235 15.002,6.228 C15.047,6.224 15.099,6.226 15.137,6.200 C15.185,6.166 15.223,6.202 15.286,6.186 C15.331,6.177 15.503,6.078 15.480,6.018 C15.392,6.027 15.354,6.018 15.277,6.082 C15.293,6.020 15.419,5.951 15.399,5.894 C15.338,5.896 15.336,5.980 15.279,5.989 C15.223,5.998 15.214,5.892 15.182,5.865 C15.043,5.750 14.982,6.140 14.854,6.107 C14.797,6.093 14.815,6.013 14.732,6.029 C14.630,6.049 14.603,6.122 14.596,6.215 C14.592,6.268 14.500,6.330 14.459,6.379 C14.394,6.457 14.311,6.441 14.236,6.388 C14.171,6.344 14.180,6.242 14.101,6.215 C14.026,6.188 13.929,6.210 13.860,6.250 C13.778,6.297 13.736,6.394 13.657,6.437 C13.583,6.474 13.461,6.461 13.384,6.465 C13.339,6.466 13.136,6.492 13.157,6.397 C13.172,6.330 13.215,6.275 13.138,6.228 C13.098,6.202 13.019,6.153 13.062,6.097 C13.098,6.051 13.175,6.053 13.197,5.996 C13.127,5.958 13.031,5.947 13.035,5.849 C12.924,5.829 12.884,5.927 12.814,5.980 C12.717,6.051 12.773,5.914 12.792,5.882 C12.818,5.838 12.866,5.774 12.915,5.758 C12.965,5.739 12.969,5.690 13.003,5.656 L12.990,5.667 C12.951,5.628 12.933,5.550 12.951,5.499 C12.972,5.437 13.048,5.448 13.100,5.441 C13.256,5.422 13.431,5.288 13.592,5.340 C13.745,5.391 13.848,5.300 14.002,5.306 C14.080,5.308 14.169,5.293 14.247,5.286 C14.329,5.277 14.394,5.227 14.480,5.227 C14.542,5.227 14.680,5.238 14.631,5.328 C14.606,5.377 14.576,5.462 14.673,5.455 C14.800,5.446 14.887,5.264 15.005,5.237 C15.086,5.216 15.045,5.306 15.020,5.335 C15.011,5.344 14.915,5.492 14.993,5.461 C15.011,5.452 15.025,5.439 15.036,5.422 C15.063,5.380 15.111,5.377 15.160,5.364 C15.253,5.339 15.342,5.309 15.431,5.275 C15.604,5.207 15.669,5.282 15.818,5.349 C15.863,5.371 16.160,5.346 16.131,5.441 C16.111,5.504 15.976,5.534 15.920,5.552 C15.789,5.594 15.807,5.648 15.832,5.765 ZM12.967,8.084 C12.936,8.126 12.792,8.137 12.760,8.097 C12.764,8.090 12.767,8.077 12.774,8.072 L12.742,8.086 C12.694,8.134 12.623,8.154 12.557,8.145 C12.559,8.110 12.541,8.057 12.555,8.026 C12.571,7.993 12.614,7.984 12.638,7.957 C12.679,7.908 12.704,7.835 12.753,7.793 C12.807,7.747 12.907,7.731 12.927,7.815 C12.949,7.895 12.868,7.928 12.853,7.991 C12.909,7.988 13.008,8.032 12.967,8.084 ZM12.708,6.525 C12.735,6.534 12.771,6.548 12.803,6.552 C12.783,6.576 12.776,6.609 12.830,6.594 C12.803,6.652 12.746,6.714 12.683,6.731 C12.611,6.749 12.528,6.732 12.474,6.793 C12.386,6.893 12.501,7.060 12.631,7.008 C12.643,7.057 12.607,7.068 12.647,7.084 C12.530,7.226 12.440,7.387 12.298,7.166 C12.165,6.960 11.940,7.117 11.804,7.241 C11.654,7.376 11.638,7.600 11.856,7.676 C11.994,7.725 11.973,7.742 11.870,7.831 C11.917,7.886 11.775,7.937 11.759,8.004 C11.796,7.964 11.874,7.970 11.886,7.906 C11.951,7.917 11.911,7.837 11.960,7.848 C12.082,7.873 11.928,8.001 11.906,8.026 C11.949,8.055 11.998,8.032 12.045,8.032 C12.086,8.032 12.125,8.059 12.165,8.052 C12.221,8.044 12.194,7.999 12.230,7.982 C12.294,7.955 12.253,8.061 12.244,8.073 C12.205,8.130 12.176,8.248 12.109,8.261 C12.048,8.272 12.014,8.283 12.025,8.360 C12.034,8.432 12.057,8.425 12.009,8.502 C11.940,8.606 12.055,8.642 12.071,8.726 C12.088,8.815 12.061,8.791 12.140,8.830 C12.206,8.862 12.273,8.857 12.343,8.868 C12.426,8.879 12.566,8.782 12.537,8.689 C12.523,8.644 12.516,8.620 12.550,8.584 C12.573,8.558 12.640,8.502 12.674,8.544 C12.641,8.498 12.701,8.465 12.708,8.420 C12.715,8.365 12.814,8.374 12.778,8.303 C12.758,8.268 12.638,8.230 12.650,8.210 C12.728,8.201 12.810,8.190 12.884,8.221 C13.003,8.272 13.046,8.471 13.202,8.398 C13.366,8.321 13.483,8.163 13.529,7.995 C13.565,7.860 13.454,7.829 13.350,7.791 C13.287,7.767 13.278,7.727 13.249,7.669 C13.217,7.603 13.342,7.536 13.245,7.521 C13.125,7.501 13.084,7.376 13.209,7.325 C13.267,7.303 13.328,7.317 13.386,7.301 C13.438,7.286 13.458,7.144 13.506,7.228 C13.510,7.166 13.666,7.232 13.715,7.228 C13.783,7.223 13.729,7.164 13.790,7.172 C13.830,7.179 13.855,7.217 13.894,7.221 C13.948,7.226 13.947,7.181 13.984,7.162 C14.035,7.141 14.069,7.210 14.110,7.219 C14.155,7.228 14.202,7.261 14.249,7.239 C14.283,7.223 14.342,7.197 14.349,7.261 C14.362,7.348 14.405,7.403 14.414,7.478 C14.423,7.552 14.601,7.554 14.489,7.653 C14.425,7.709 14.358,7.815 14.263,7.786 C14.204,7.769 14.216,7.798 14.162,7.831 C14.114,7.860 14.107,7.913 14.063,7.917 C13.925,7.930 14.083,8.044 14.078,8.110 C14.072,8.166 14.112,8.194 14.114,8.245 C14.117,8.301 14.071,8.361 14.063,8.418 C14.051,8.503 14.216,8.647 14.263,8.718 C14.344,8.841 14.425,8.955 14.560,9.026 C14.644,9.070 14.684,9.165 14.763,9.216 C14.802,9.241 14.862,9.225 14.863,9.274 C14.863,9.320 14.858,9.371 14.874,9.416 C14.894,9.478 15.023,9.487 15.079,9.487 C15.088,9.588 15.235,9.480 15.279,9.469 C15.180,9.584 15.104,9.693 14.986,9.793 C14.930,9.839 14.865,9.899 14.790,9.910 C14.725,9.921 14.673,9.883 14.619,9.939 C14.576,9.983 14.585,10.052 14.543,10.100 C14.504,10.145 14.421,10.152 14.371,10.191 C14.347,10.207 14.112,10.371 14.128,10.251 C14.133,10.209 14.160,10.176 14.169,10.136 C14.182,10.080 14.117,10.076 14.092,10.049 C14.049,10.003 14.020,10.038 14.008,9.957 C14.000,9.912 13.986,9.861 13.945,9.832 C13.876,9.784 13.898,9.786 13.866,9.708 C13.840,9.646 13.781,9.606 13.758,9.546 C13.700,9.404 13.716,9.256 13.616,9.127 C13.612,9.178 13.652,9.220 13.635,9.260 C13.619,9.303 13.632,9.373 13.657,9.411 C13.569,9.404 13.513,9.376 13.450,9.316 C13.422,9.291 13.328,9.200 13.289,9.254 C13.337,9.249 13.441,9.394 13.504,9.425 C13.648,9.497 13.648,9.548 13.587,9.695 C13.646,9.706 13.736,9.655 13.776,9.724 C13.812,9.786 13.756,9.854 13.864,9.852 C13.848,9.996 13.706,10.089 13.698,10.234 C13.693,10.316 13.725,10.386 13.698,10.469 C13.670,10.557 13.600,10.633 13.506,10.659 C13.276,10.721 13.175,10.484 13.094,10.331 C13.060,10.267 12.974,10.209 12.898,10.245 C12.868,10.260 12.855,10.293 12.825,10.307 C12.767,10.336 12.706,10.322 12.652,10.362 C12.587,10.409 12.551,10.495 12.469,10.521 C12.418,10.537 12.350,10.521 12.307,10.553 C12.280,10.573 12.273,10.608 12.271,10.639 C12.235,10.646 12.208,10.670 12.185,10.697 C12.136,10.754 12.041,10.790 12.003,10.847 C11.951,10.923 12.066,10.992 12.057,11.069 C12.052,11.115 11.976,11.231 11.940,11.244 C11.777,11.300 11.894,11.463 11.937,11.554 C11.946,11.572 11.953,11.597 11.929,11.610 C11.890,11.634 11.904,11.650 11.929,11.698 C11.971,11.776 12.037,11.836 12.106,11.893 C12.163,11.938 12.289,11.965 12.305,12.031 C12.334,12.149 12.251,12.244 12.185,12.335 C12.133,12.406 12.091,12.488 12.055,12.568 C12.037,12.607 12.037,12.707 12.001,12.731 C11.852,12.829 11.879,12.435 11.707,12.526 C11.600,12.583 11.572,12.751 11.439,12.767 C11.370,12.776 11.343,12.705 11.334,12.652 C11.297,12.652 11.253,12.649 11.219,12.630 C11.140,12.592 11.192,12.567 11.210,12.512 C11.237,12.425 11.327,12.435 11.392,12.390 C11.475,12.332 11.396,12.281 11.397,12.211 C11.336,12.204 11.286,12.111 11.252,12.069 C11.183,11.989 11.117,11.987 11.018,11.985 C10.975,11.985 10.941,11.956 10.905,11.936 C10.892,11.985 10.712,11.934 10.651,11.965 C10.596,11.995 10.518,12.162 10.603,12.180 C10.572,12.250 10.491,12.328 10.502,12.405 C10.506,12.430 10.551,12.415 10.515,12.454 C10.486,12.483 10.480,12.526 10.473,12.565 C10.443,12.740 10.491,12.900 10.538,13.064 C10.459,13.084 10.383,13.122 10.310,13.162 C10.367,13.091 10.302,12.973 10.204,13.004 C10.204,13.039 10.191,13.071 10.177,13.100 C10.119,13.113 10.080,13.111 10.053,13.062 C10.015,12.993 10.056,13.013 10.087,12.960 C10.150,12.851 10.011,12.709 9.956,12.836 C9.952,12.725 9.761,12.548 9.729,12.762 C9.720,12.829 9.758,12.958 9.628,12.915 C9.571,12.896 9.510,12.771 9.499,12.716 C9.490,12.820 9.603,12.873 9.585,12.960 C9.576,13.009 9.481,13.090 9.441,13.126 C9.508,13.124 9.580,13.120 9.645,13.130 C9.643,13.179 9.601,13.206 9.601,13.268 C9.589,13.270 9.576,13.274 9.565,13.275 C9.610,13.332 9.574,13.414 9.558,13.476 C9.537,13.472 9.502,13.485 9.481,13.483 C9.477,13.518 9.463,13.538 9.434,13.529 C9.438,13.540 9.436,13.549 9.431,13.558 C9.420,13.567 9.407,13.567 9.396,13.560 C9.396,13.543 9.386,13.518 9.387,13.501 C9.334,13.552 9.305,13.470 9.245,13.481 C9.236,13.567 9.244,13.651 9.125,13.625 C9.078,13.616 8.979,13.507 8.934,13.520 C8.915,13.525 8.902,13.552 8.893,13.583 C8.871,13.574 8.846,13.569 8.818,13.567 C8.728,13.560 8.663,13.642 8.555,13.629 C8.435,13.614 8.418,13.629 8.320,13.698 C8.233,13.760 8.172,13.685 8.086,13.669 C8.028,13.658 7.971,13.656 7.915,13.640 C7.892,13.633 7.863,13.620 7.849,13.600 C7.822,13.560 7.726,13.569 7.676,13.563 C7.466,13.545 7.257,13.549 7.070,13.658 C6.928,13.742 6.752,13.875 6.723,14.046 C6.676,14.327 7.052,14.356 7.218,14.476 C7.431,14.629 6.987,14.759 6.881,14.797 C6.756,14.841 6.632,14.901 6.536,14.995 C6.461,15.068 6.412,15.243 6.277,15.185 C6.087,15.101 5.939,14.870 5.832,14.704 C5.745,14.664 5.666,14.383 5.655,14.294 C5.648,14.239 5.549,14.061 5.627,14.046 C5.643,14.001 5.601,13.953 5.583,13.917 C5.549,13.846 5.571,13.762 5.540,13.687 C5.508,13.611 5.630,13.479 5.614,13.385 C5.607,13.345 5.574,13.326 5.609,13.286 C5.652,13.233 5.659,13.195 5.670,13.130 C5.690,13.022 5.817,13.031 5.846,12.936 C5.855,12.909 5.857,12.856 5.893,12.847 C5.965,12.829 5.884,12.953 5.921,12.982 C5.923,12.884 5.956,12.789 5.956,12.687 C5.956,12.567 5.977,12.448 5.995,12.330 C6.008,12.239 6.024,12.146 6.065,12.064 C6.094,12.011 6.099,11.832 5.990,11.918 C5.986,11.721 5.988,11.524 6.024,11.331 C6.065,11.107 6.164,10.910 6.267,10.710 C6.466,10.324 6.626,9.908 6.869,9.546 C6.987,9.369 7.126,9.207 7.246,9.030 C7.309,8.934 7.378,8.841 7.426,8.737 C7.471,8.640 7.548,8.585 7.620,8.507 C7.750,8.369 7.854,8.237 8.009,8.119 C8.075,8.068 8.149,7.973 8.242,7.995 C8.345,8.017 8.375,7.937 8.449,7.889 C8.447,7.953 8.460,8.001 8.451,8.066 C8.508,8.004 8.546,7.917 8.598,7.849 C8.672,7.758 8.756,7.676 8.857,7.612 C8.895,7.587 8.920,7.547 8.960,7.523 C8.990,7.507 9.026,7.509 9.057,7.492 C9.093,7.470 9.316,7.357 9.283,7.319 C9.157,7.168 8.710,7.729 8.578,7.704 C8.573,7.633 8.758,7.538 8.809,7.503 C8.956,7.405 9.096,7.277 9.236,7.168 C9.384,7.053 9.533,6.931 9.691,6.829 C9.765,6.783 9.846,6.751 9.914,6.698 C9.963,6.661 10.017,6.589 10.089,6.598 C10.081,6.747 10.202,6.612 10.259,6.598 C10.400,6.563 10.547,6.463 10.678,6.404 C10.761,6.366 11.435,5.923 11.466,6.046 C11.529,6.053 11.561,5.965 11.640,5.978 C11.743,5.993 11.807,5.947 11.890,5.894 C12.027,5.805 12.172,5.716 12.312,5.632 C12.366,5.601 12.415,5.594 12.406,5.672 C12.400,5.719 12.303,5.739 12.269,5.759 C12.206,5.798 12.160,5.854 12.122,5.916 C12.064,6.007 11.985,6.080 11.920,6.168 C11.985,6.182 12.016,6.128 12.071,6.109 C12.140,6.089 12.154,6.142 12.224,6.098 C12.257,6.080 12.289,6.104 12.316,6.115 C12.356,6.131 12.365,6.106 12.397,6.093 C12.444,6.075 12.499,6.169 12.562,6.171 C12.758,6.171 12.357,6.397 12.553,6.426 C12.636,6.437 12.631,6.497 12.708,6.525 ZM12.925,6.519 C12.893,6.521 12.868,6.543 12.836,6.550 C12.827,6.552 12.814,6.552 12.803,6.552 C12.810,6.543 12.819,6.536 12.827,6.532 C12.857,6.519 12.893,6.517 12.925,6.519 ZM7.191,20.631 C7.273,20.622 7.280,20.739 7.342,20.770 C7.342,20.762 7.343,20.759 7.345,20.753 C7.390,20.788 7.412,20.824 7.421,20.882 C7.430,20.954 7.448,20.992 7.473,21.057 C7.491,21.103 7.494,21.205 7.426,21.221 C7.365,21.234 7.349,21.156 7.307,21.128 C7.200,21.057 7.047,21.183 7.002,20.999 C6.984,20.928 6.993,20.881 6.943,20.819 C6.903,20.770 6.860,20.739 6.896,20.671 C6.950,20.571 7.122,20.522 7.191,20.631 ZM7.360,20.726 C7.356,20.737 7.351,20.744 7.345,20.753 C7.340,20.748 7.334,20.744 7.329,20.740 L7.360,20.726 ZM7.298,20.467 C7.237,20.411 7.275,20.221 7.370,20.301 C7.410,20.332 7.426,20.425 7.405,20.462 C7.378,20.507 7.322,20.527 7.298,20.467 ZM7.521,14.913 C7.548,14.895 7.548,14.870 7.565,14.848 C7.588,14.815 7.606,14.817 7.636,14.830 C7.647,14.835 7.660,14.848 7.676,14.842 C7.699,14.833 7.678,14.824 7.694,14.819 C7.751,14.766 7.748,14.850 7.746,14.893 C7.748,14.943 7.768,14.928 7.795,14.952 C7.818,14.970 7.798,14.984 7.805,15.008 C7.814,15.030 7.843,15.037 7.861,15.065 C7.881,15.090 7.870,15.112 7.856,15.141 C7.805,15.218 7.705,15.178 7.640,15.141 C7.611,15.134 7.574,15.105 7.550,15.088 C7.527,15.072 7.496,15.057 7.473,15.041 C7.444,15.034 7.403,15.014 7.410,14.986 C7.412,14.968 7.458,14.935 7.475,14.930 L7.521,14.913 ZM16.778,18.530 C16.859,18.576 16.931,18.607 17.024,18.572 C17.107,18.539 17.164,18.488 17.260,18.497 C17.373,18.507 17.386,18.572 17.425,18.658 C17.486,18.789 17.698,18.758 17.670,18.940 C17.520,19.022 17.664,19.144 17.704,19.248 C17.724,19.301 17.720,19.359 17.722,19.416 C17.722,19.458 17.738,19.529 17.704,19.561 C17.673,19.591 17.603,19.591 17.564,19.600 C17.510,19.614 17.458,19.642 17.402,19.649 C17.305,19.664 17.229,19.600 17.141,19.667 C17.107,19.693 17.082,19.725 17.042,19.746 C16.997,19.767 16.942,19.775 16.893,19.786 C16.846,19.797 16.791,19.815 16.744,19.795 C16.706,19.778 16.688,19.736 16.652,19.716 C16.564,19.665 16.427,19.756 16.341,19.784 C16.273,19.806 16.147,19.875 16.075,19.840 C15.971,19.789 16.116,19.654 16.145,19.600 C16.190,19.510 16.170,19.410 16.192,19.315 C16.154,19.312 16.149,19.283 16.118,19.270 C16.107,19.264 16.080,19.266 16.068,19.263 C16.053,19.261 16.039,19.252 16.025,19.248 C16.019,19.292 15.949,19.334 15.951,19.272 C15.931,19.266 15.915,19.261 15.897,19.255 C15.899,19.219 15.965,19.099 16.010,19.100 L15.998,19.090 C16.111,18.986 16.235,18.865 16.391,18.833 C16.463,18.816 16.551,18.791 16.602,18.732 C16.659,18.667 16.589,18.596 16.607,18.523 C16.627,18.439 16.729,18.503 16.778,18.530 ZM17.698,17.537 C17.704,17.564 17.756,17.581 17.754,17.605 C17.754,17.616 17.691,17.621 17.680,17.621 C17.646,17.632 17.610,17.632 17.582,17.648 C17.560,17.659 17.542,17.676 17.519,17.687 C17.432,17.725 17.497,17.597 17.497,17.552 C17.502,17.512 17.465,17.406 17.504,17.366 L17.499,17.339 C17.499,17.315 17.488,17.293 17.506,17.266 C17.511,17.247 17.546,17.237 17.564,17.220 C17.587,17.209 17.610,17.176 17.632,17.198 C17.644,17.209 17.650,17.227 17.650,17.238 C17.666,17.255 17.684,17.273 17.695,17.295 C17.700,17.322 17.700,17.357 17.688,17.379 C17.682,17.419 17.693,17.430 17.698,17.468 C17.698,17.492 17.688,17.513 17.698,17.537 ZM18.757,18.490 C18.745,18.519 18.682,18.516 18.649,18.510 C18.631,18.510 18.613,18.510 18.595,18.494 C18.576,18.476 18.590,18.446 18.561,18.452 C18.536,18.448 18.522,18.477 18.493,18.481 C18.482,18.485 18.461,18.474 18.446,18.485 C18.376,18.497 18.414,18.514 18.407,18.563 C18.405,18.607 18.355,18.636 18.369,18.678 C18.378,18.718 18.407,18.767 18.425,18.802 C18.486,18.955 18.277,18.829 18.232,18.791 C18.186,18.760 18.141,18.740 18.133,18.683 C18.117,18.640 18.121,18.598 18.115,18.552 C18.107,18.441 18.141,18.333 18.151,18.224 C18.153,18.164 18.139,18.120 18.130,18.064 C18.115,18.022 18.076,17.982 18.119,17.949 C18.146,17.891 18.130,17.861 18.115,17.801 C18.099,17.759 18.092,17.738 18.087,17.692 C18.096,17.581 18.015,17.508 17.988,17.397 C17.977,17.348 18.000,17.298 18.060,17.326 L18.103,17.346 C18.119,17.371 18.115,17.395 18.141,17.399 C18.177,17.415 18.263,17.349 18.286,17.397 C18.293,17.417 18.272,17.424 18.279,17.446 C18.288,17.468 18.310,17.477 18.324,17.484 C18.367,17.504 18.405,17.523 18.416,17.572 C18.416,17.590 18.409,17.603 18.425,17.628 C18.443,17.646 18.461,17.645 18.479,17.645 C18.531,17.652 18.588,17.643 18.610,17.688 C18.648,17.741 18.569,17.739 18.565,17.783 C18.561,17.825 18.603,17.852 18.624,17.882 C18.648,17.909 18.680,17.951 18.689,17.989 C18.700,18.040 18.664,18.058 18.666,18.111 C18.658,18.160 18.736,18.188 18.714,18.250 C18.700,18.295 18.658,18.346 18.676,18.399 C18.696,18.435 18.772,18.445 18.757,18.490 ZM7.507,20.618 C7.588,20.596 7.665,20.660 7.744,20.642 C7.827,20.624 7.852,20.527 7.859,20.458 C7.892,20.498 7.935,20.489 7.964,20.527 C7.994,20.573 8.061,20.580 8.106,20.602 C8.125,20.545 8.077,20.452 8.079,20.391 C8.079,20.356 8.045,20.288 8.055,20.263 C8.079,20.205 8.188,20.155 8.244,20.137 C8.311,20.115 8.390,20.165 8.449,20.190 C8.508,20.216 8.573,20.201 8.634,20.188 C8.665,20.181 8.697,20.177 8.729,20.176 C8.737,20.148 8.751,20.123 8.773,20.103 C8.819,20.055 8.873,20.075 8.924,20.101 C9.021,20.146 9.100,19.953 9.118,19.873 C9.141,19.771 9.152,19.580 9.123,19.478 C9.091,19.359 9.062,19.325 9.172,19.266 C9.195,19.255 9.202,19.228 9.213,19.203 C9.121,19.037 9.073,18.856 9.166,18.701 C9.242,18.574 9.409,18.461 9.359,18.322 C9.443,18.330 9.529,18.337 9.616,18.344 C9.751,18.169 9.950,18.047 10.166,18.011 C10.292,17.871 10.493,17.721 10.644,17.834 C10.693,17.871 10.723,17.929 10.779,17.956 C10.833,17.982 10.896,17.971 10.955,17.969 C11.164,17.964 11.370,18.100 11.448,18.297 C11.397,18.359 11.349,18.421 11.298,18.483 C11.360,18.711 11.712,18.793 11.849,18.601 C11.940,18.476 11.946,18.270 12.089,18.219 C12.161,18.193 12.244,18.219 12.323,18.246 C12.325,18.244 12.325,18.244 12.327,18.242 C12.329,18.242 12.329,18.248 12.329,18.248 C12.395,18.270 12.462,18.292 12.516,18.290 C12.530,18.266 12.532,18.257 12.548,18.230 C12.546,18.231 12.564,18.255 12.568,18.264 C12.586,18.255 12.605,18.257 12.618,18.237 C12.672,18.162 12.627,18.051 12.661,17.965 C12.694,17.885 12.782,17.887 12.857,17.920 C12.909,17.814 12.960,17.712 13.028,17.566 C13.062,17.492 13.033,17.869 12.981,18.242 C12.987,18.246 12.983,18.255 12.990,18.259 C13.031,18.271 13.073,18.237 13.102,18.202 C13.170,18.118 13.220,18.016 13.307,17.949 C13.364,17.903 13.440,17.896 13.508,17.913 C13.538,17.865 13.571,17.809 13.587,17.805 C13.682,17.783 13.756,17.745 13.853,17.781 C13.936,17.814 14.004,17.880 14.090,17.902 C14.148,17.916 14.256,17.914 14.286,17.976 C14.310,18.027 14.351,18.027 14.412,18.044 C14.437,18.051 14.489,18.053 14.491,18.089 C14.488,18.109 14.477,18.113 14.457,18.100 C14.464,18.155 14.534,18.153 14.574,18.169 C14.648,18.200 14.709,18.250 14.772,18.295 C14.894,18.384 14.978,18.514 15.070,18.632 C15.093,18.663 15.196,18.783 15.153,18.825 C15.126,18.853 15.068,18.847 15.034,18.864 C14.978,18.889 14.898,18.920 14.849,18.955 C14.804,18.986 14.784,19.039 14.739,19.066 C14.617,19.139 14.518,18.955 14.452,18.900 C14.362,18.825 14.400,18.691 14.328,18.630 C14.213,18.530 14.056,18.485 13.921,18.419 C13.848,18.383 13.767,18.355 13.704,18.301 C13.661,18.262 13.619,18.219 13.560,18.206 C13.560,18.206 13.558,18.208 13.558,18.210 C13.528,18.322 13.670,18.374 13.742,18.426 C13.830,18.490 13.851,18.587 13.894,18.678 C13.945,18.783 14.027,18.871 14.103,18.958 C14.137,18.995 14.182,19.022 14.205,19.068 C14.227,19.111 14.207,19.161 14.238,19.201 C14.090,19.259 13.963,18.947 13.878,18.876 C13.844,18.845 13.774,18.809 13.727,18.827 C13.700,18.880 13.653,18.949 13.691,18.991 C13.722,19.024 13.718,19.075 13.734,19.115 C13.752,19.161 13.790,19.210 13.815,19.252 C13.846,19.299 13.900,19.319 13.930,19.367 C13.950,19.399 13.959,19.436 13.975,19.469 C14.002,19.525 14.080,19.622 14.153,19.580 C14.234,19.534 14.204,19.168 14.322,19.332 C14.383,19.418 14.403,19.857 14.581,19.762 C14.685,19.707 14.662,19.643 14.791,19.694 C14.853,19.716 14.926,19.605 14.953,19.713 C14.977,19.797 14.995,19.778 15.068,19.818 C15.126,19.849 15.122,19.926 15.122,19.981 C15.122,20.048 15.126,20.128 15.209,20.143 C15.282,20.154 15.264,20.239 15.275,20.290 C15.324,20.513 15.636,20.316 15.680,20.474 C15.690,20.516 15.696,20.558 15.742,20.576 C15.769,20.587 15.805,20.589 15.822,20.615 C15.872,20.684 15.867,20.724 15.971,20.748 C16.100,20.779 16.305,20.742 16.402,20.647 C16.445,20.607 16.462,20.511 16.517,20.489 C16.582,20.462 16.658,20.569 16.672,20.615 C16.701,20.697 16.704,20.766 16.782,20.821 C16.859,20.873 16.929,20.924 16.970,21.010 C17.069,21.214 16.835,21.365 16.915,21.558 C16.933,21.602 16.965,21.640 16.999,21.671 C16.992,21.691 16.976,21.733 16.997,21.752 C17.049,21.797 17.112,21.695 17.148,21.757 C17.247,21.936 17.013,22.016 16.902,22.085 C16.780,22.162 16.834,22.357 16.848,22.471 C16.943,22.466 17.021,22.378 17.121,22.377 C17.267,22.377 17.152,22.579 17.100,22.623 C16.986,22.715 16.789,22.739 16.654,22.675 C16.613,22.657 16.535,22.604 16.507,22.670 C16.478,22.734 16.417,22.792 16.462,22.865 C16.537,22.992 16.747,22.945 16.780,23.118 C16.801,23.237 16.622,23.313 16.749,23.391 C16.848,23.452 16.888,23.532 16.951,23.621 C17.028,23.732 17.170,23.521 17.220,23.656 C17.197,23.574 17.307,23.599 17.351,23.590 C17.414,23.579 17.463,23.523 17.481,23.464 C17.515,23.357 17.380,23.306 17.310,23.257 C17.251,23.215 17.262,23.186 17.310,23.140 C17.278,23.058 17.195,23.023 17.147,22.954 C17.085,22.865 17.127,22.721 17.217,22.664 C17.359,22.573 17.493,22.787 17.627,22.812 C17.778,22.841 17.946,22.805 18.071,22.914 C18.184,23.011 18.241,23.149 18.382,23.220 C18.443,23.251 18.506,23.268 18.518,23.344 C18.531,23.430 18.619,23.393 18.676,23.450 C18.714,23.486 18.718,23.543 18.777,23.557 C18.676,23.681 18.563,23.796 18.435,23.894 C18.329,23.976 18.211,24.051 18.144,24.169 C18.008,24.412 18.117,24.716 17.950,24.949 C17.808,25.150 17.592,25.250 17.520,25.498 C17.454,25.725 17.395,25.931 17.161,26.044 C16.893,26.172 16.582,26.210 16.289,26.239 C16.012,26.265 15.724,26.268 15.485,26.429 C15.262,26.578 15.043,26.706 14.764,26.706 C14.698,26.706 14.633,26.702 14.567,26.697 C14.511,26.739 14.475,26.799 14.421,26.842 C14.313,26.930 14.187,26.821 14.085,26.782 C14.101,26.815 14.105,26.848 14.096,26.883 C14.044,26.862 13.988,26.912 13.939,26.873 C13.896,26.842 13.871,26.790 13.813,26.779 C13.679,26.755 13.596,26.819 13.465,26.733 C13.274,26.609 13.078,26.627 12.855,26.598 C12.625,26.567 12.397,26.516 12.172,26.451 C11.769,26.336 11.334,26.207 10.968,26.001 C10.797,25.906 10.621,25.778 10.560,25.587 C10.534,25.512 10.556,25.416 10.529,25.345 C10.511,25.299 10.315,25.279 10.267,25.263 C10.162,25.228 9.925,25.244 9.972,25.088 C10.004,24.977 10.148,25.017 10.234,25.009 C10.403,24.995 10.387,24.822 10.285,24.732 C10.193,24.650 9.992,24.616 10.000,24.472 C10.011,24.326 10.107,24.022 9.911,23.964 C9.763,23.920 9.621,23.900 9.569,23.750 C9.542,23.670 9.472,23.616 9.441,23.532 C9.409,23.450 9.316,23.371 9.235,23.339 C9.179,23.315 9.024,23.342 9.003,23.278 C8.987,23.229 8.970,23.165 8.972,23.115 C8.983,22.940 9.218,23.211 9.285,23.211 C9.423,23.207 9.547,23.036 9.452,22.920 C9.494,22.900 9.569,22.909 9.589,22.858 C9.542,22.848 9.445,22.892 9.416,22.834 C9.387,22.777 9.449,22.699 9.400,22.648 C9.357,22.601 9.280,22.657 9.226,22.652 C9.161,22.646 9.265,22.577 9.262,22.553 C9.186,22.551 9.123,22.617 9.058,22.644 C8.999,22.672 8.936,22.688 8.877,22.715 C8.825,22.679 8.897,22.626 8.922,22.601 C8.978,22.548 8.943,22.490 8.922,22.431 C9.051,22.440 9.055,22.347 8.979,22.269 C8.880,22.169 8.925,22.096 8.880,21.981 C8.828,21.848 8.746,21.919 8.699,22.001 C8.611,22.152 8.525,21.963 8.483,22.003 C8.444,21.888 8.356,21.828 8.258,21.761 C8.170,21.702 8.158,21.597 8.097,21.518 C8.023,21.425 7.897,21.391 7.836,21.285 C7.780,21.189 7.755,21.076 7.680,20.990 C7.635,20.939 7.295,20.673 7.507,20.618 ZM8.787,22.989 C8.819,22.987 8.868,23.022 8.857,23.053 L8.843,23.067 C8.857,23.073 8.868,23.078 8.871,23.094 C8.819,23.100 8.827,23.167 8.807,23.202 C8.724,23.202 8.685,23.116 8.721,23.053 C8.731,23.031 8.758,22.991 8.787,22.989 ZM24.510,20.174 C24.490,20.126 24.542,20.112 24.566,20.075 C24.594,20.033 24.580,19.982 24.647,19.984 C24.675,19.986 24.679,19.993 24.704,19.984 C24.729,19.977 24.758,19.955 24.781,19.942 C24.796,20.002 24.814,20.039 24.843,20.090 C24.871,20.139 24.852,20.181 24.819,20.223 C24.796,20.250 24.773,20.281 24.737,20.292 C24.697,20.307 24.638,20.312 24.634,20.258 C24.631,20.258 24.625,20.258 24.621,20.256 C24.623,20.252 24.625,20.250 24.627,20.247 L24.618,20.258 C24.584,20.241 24.524,20.210 24.510,20.174 ZM24.501,21.961 C24.438,22.069 24.310,22.100 24.213,22.160 C24.127,22.216 24.075,22.304 23.990,22.360 C23.857,22.449 23.538,22.719 23.383,22.666 C23.368,22.568 23.539,22.466 23.609,22.418 C23.663,22.382 23.687,22.336 23.726,22.295 C23.785,22.236 23.856,22.205 23.897,22.121 C23.951,22.012 23.849,21.817 23.942,21.746 C24.014,21.693 24.048,21.761 24.122,21.739 C24.167,21.724 24.194,21.691 24.217,21.666 C24.287,21.562 24.233,21.373 24.104,21.345 C24.138,21.240 24.345,21.061 24.471,21.103 C24.460,21.148 24.426,21.207 24.449,21.263 C24.544,21.258 24.639,21.123 24.621,21.032 C24.612,20.992 24.566,20.934 24.526,20.915 C24.434,20.879 24.433,20.941 24.377,20.961 C24.260,21.005 24.127,20.992 24.003,21.048 C23.884,21.099 23.811,21.147 23.670,21.123 C23.660,21.107 23.660,21.090 23.670,21.076 C23.701,21.079 23.748,21.061 23.778,21.066 C23.785,20.997 23.917,20.955 23.978,20.924 C24.003,20.910 24.052,20.886 24.071,20.875 C24.096,20.859 24.138,20.821 24.161,20.808 C24.251,20.751 24.409,20.573 24.524,20.569 C24.560,20.667 24.616,20.740 24.733,20.722 C24.789,20.711 24.810,20.718 24.837,20.637 C24.855,20.584 24.859,20.533 24.821,20.487 C25.037,20.498 24.897,20.321 25.028,20.232 C25.096,20.288 25.143,20.212 25.217,20.234 C25.220,20.250 25.224,20.265 25.227,20.281 C25.247,20.272 25.287,20.278 25.308,20.259 C25.398,20.421 25.186,20.764 25.105,20.908 C25.053,20.999 24.979,21.074 24.942,21.170 C24.898,21.281 24.920,21.413 24.826,21.496 C24.751,21.562 24.690,21.588 24.623,21.668 C24.587,21.710 24.550,21.728 24.530,21.783 C24.508,21.845 24.537,21.899 24.501,21.961 Z"/>
+</svg>
diff --git a/browser/branding/nightly/content/jar.mn b/browser/branding/nightly/content/jar.mn
new file mode 100644
index 000000000..140359a19
--- /dev/null
+++ b/browser/branding/nightly/content/jar.mn
@@ -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/.
+
+browser.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png
+ content/branding/about-background.png
+ content/branding/about-logo.png
+ content/branding/about-logo@2x.png
+ content/branding/about-wordmark.svg
+ content/branding/icon48.png
+ content/branding/icon64.png
+ content/branding/icon16.png (../default16.png)
+ content/branding/icon32.png (../default32.png)
+ content/branding/icon128.png (../mozicon128.png)
+ content/branding/identity-icons-brand.svg
+ content/branding/silhouette-40.svg
+ content/branding/aboutDialog.css
diff --git a/browser/branding/nightly/content/moz.build b/browser/branding/nightly/content/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/branding/nightly/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/nightly/content/silhouette-40.svg b/browser/branding/nightly/content/silhouette-40.svg
new file mode 100644
index 000000000..bef723b59
--- /dev/null
+++ b/browser/branding/nightly/content/silhouette-40.svg
@@ -0,0 +1,1360 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-45 31 40 40">
+ <path fill="#ccc" d="M-25,62.991c-6.622,0-11.991-5.369-11.991-11.991S-31.622,39.009-25,39.009S-13.009,44.378-13.009,51
+ S-18.378,62.991-25,62.991z M-34.439,48.549c-0.002,0.007-0.004,0.013-0.006,0.02c0.002-0.004,0.006-0.006,0.007-0.01
+ C-34.437,48.555-34.438,48.552-34.439,48.549z M-34.259,47.956c-0.001-0.006-0.003-0.005-0.002-0.012l0,0
+ c-0.001,0.004-0.003,0.008-0.004,0.012l0.008,0.006C-34.258,47.96-34.258,47.958-34.259,47.956z M-34.245,48.067
+ c-0.02,0.006-0.04,0.012-0.06,0.019c-0.026,0.084-0.054,0.168-0.078,0.254c0.004-0.002,0.007-0.004,0.01-0.008
+ c0.007-0.006,0.013-0.014,0.019-0.021c0.008-0.007,0.009-0.014,0.011-0.023c0.003-0.009,0.003-0.01,0.008-0.017
+ c0.01-0.012,0.006-0.037,0.005-0.051c0-0.011-0.007-0.015,0-0.024c0.005-0.007,0.012-0.014,0.017-0.019
+ c0.011-0.01,0.026-0.015,0.039-0.022c0.007-0.003,0.013-0.008,0.017-0.014c0.003-0.005,0.004-0.011,0.007-0.017
+ c0.007-0.014,0.008-0.027,0.008-0.044C-34.242,48.074-34.243,48.071-34.245,48.067z M-16.736,45.824
+ c0.001,0.012,0.002,0.024,0.007,0.036c0.006,0.017,0.012,0.034,0.011,0.054c0,0.017,0.007,0.032,0.011,0.049
+ c0.004,0.024,0.013,0.043,0.024,0.064c0.004,0.009,0.009,0.017,0.013,0.026c0.008,0.012,0.014,0.025,0.021,0.038
+ c0.009,0.017,0.014,0.034,0.022,0.051c0.004,0.008,0.006,0.016,0.008,0.024c0.002,0.005,0.003,0.013,0.007,0.017
+ c-0.02-0.021-0.027-0.053-0.042-0.077c-0.013-0.021-0.029-0.04-0.041-0.061c-0.009-0.016-0.021-0.028-0.023-0.045
+ c-0.002-0.015-0.003-0.029-0.007-0.042c-0.003-0.007-0.004-0.012-0.009-0.019c-0.004-0.007-0.011-0.011-0.015-0.018
+ c-0.01-0.015-0.018-0.034-0.025-0.051c-0.01-0.022-0.015-0.05-0.037-0.064c0.004,0.004,0.008,0.014,0.009,0.02
+ c0.002,0.007,0.005,0.015,0.005,0.022c0,0.013-0.007,0.013-0.014,0.004c-0.005-0.006-0.007-0.015-0.01-0.023
+ c-0.001-0.003-0.005-0.009-0.005-0.012c-0.001-0.004,0-0.007-0.001-0.01c-0.001-0.006-0.006-0.013-0.009-0.019
+ c-0.004-0.01-0.007-0.018-0.014-0.027c-0.006-0.008-0.012-0.014-0.016-0.025c-0.005-0.014-0.007-0.028-0.017-0.04
+ s-0.018-0.001-0.028,0.005c-0.006,0.004-0.014,0.005-0.022,0.008c-0.008,0.002-0.015,0.005-0.023,0.007
+ c-0.006,0.002-0.012,0.003-0.018,0.005c-0.003,0.001-0.004,0.002-0.008,0.003c-0.004,0-0.008-0.001-0.012,0
+ c-0.01,0.001-0.018,0.005-0.029,0.003c-0.003-0.001-0.005-0.003-0.008-0.003c-0.004-0.001-0.008-0.001-0.012,0
+ c-0.02,0.001-0.016,0.025-0.012,0.039c0.004,0.017,0.013,0.032,0.021,0.047c0.001,0.004,0.004,0.008,0.006,0.012
+ c0.001,0.004-0.001,0.007,0,0.011c0.002,0.009,0.006,0.017,0.004,0.027c-0.001,0.009-0.003,0.012-0.01,0.016
+ c-0.008,0.004-0.01,0.004-0.006,0.014c0.003,0.006,0.008,0.013,0.009,0.019c0.001,0.007,0.001,0.015,0.003,0.022
+ c0.004,0.017,0.012,0.034,0.02,0.049c0.005,0.008,0.014,0.031-0.004,0.019c-0.012-0.007-0.019-0.022-0.026-0.033
+ c-0.004-0.007-0.009-0.011-0.016-0.015c-0.006-0.003-0.013-0.005-0.019-0.009c-0.011-0.007-0.011-0.014-0.011-0.026
+ c0-0.007,0-0.015-0.008-0.019c-0.006-0.003-0.015-0.003-0.023-0.006c-0.005-0.002-0.011-0.004-0.015-0.007
+ c0.009,0.008,0.012,0.018,0.015,0.028c0.007,0.02,0.016,0.038,0.027,0.056c0.009,0.016,0.02,0.022,0.034,0.032
+ c0.011,0.008,0.022,0.024,0.029,0.035c0.008,0.01,0.015,0.019,0.024,0.028c0.009,0.009,0.019,0.015,0.025,0.027
+ c0.008,0.014,0.01,0.027,0.013,0.042c0.004,0.013,0.003,0.027,0.003,0.041c0,0.009,0.001,0.018-0.001,0.026
+ c0,0.003-0.002,0.005-0.002,0.008c-0.001,0.004,0,0.008,0,0.012c0,0.013,0,0.027,0,0.041c0,0.027,0.021,0.046,0.029,0.069
+ c0.008,0.023,0.006,0.053,0.005,0.079c-0.001,0.004-0.002,0.006-0.003,0.011c-0.002,0.005,0,0.009-0.001,0.014
+ c-0.001,0.005-0.003,0.008-0.004,0.012c0,0.003,0,0.006,0,0.008c0,0.004-0.003,0.004-0.004,0.007c-0.001,0.003,0,0.008,0,0.011
+ c-0.001,0.004-0.003,0.002-0.003,0.008c0.002,0.003,0.003,0.005,0.003,0.008c0.005,0.013,0.015,0.032,0.027,0.041
+ c0.005,0.003,0.012,0.004,0.019,0.004s0.013,0.005,0.018,0.012c0,0.002,0.001,0.004,0.001,0.006
+ c0.003,0.002,0.005,0.005,0.006,0.008c0.002,0.004,0.003,0.011,0.004,0.015c0.004,0.017,0.01,0.029,0.019,0.042
+ c0.011,0.015,0.021,0.018,0.039,0.019c0.016,0.001,0.029,0.009,0.042,0.02c0.006,0.005,0.011,0.013,0.018,0.018
+ c0.005,0.003,0.013,0.004,0.018,0.007c0.009,0.004,0.018,0.006,0.028,0.009c0.008,0.003,0.017,0.005,0.026,0.007
+ c0.007,0.001,0.015-0.001,0.023,0.001c0.007,0.002,0.014,0.006,0.022,0.006c0.005,0.001,0.018-0.002,0.022,0.001
+ c0.006,0.004,0.02,0.036,0.03,0.029c0.003-0.002,0.001-0.019,0.001-0.022c0-0.009,0.004-0.018,0.004-0.027
+ c0-0.005-0.001-0.01,0.001-0.015c0.001-0.004,0.003-0.006,0.003-0.011c-0.001-0.007-0.005-0.017-0.008-0.023
+ c-0.012-0.023,0.022-0.001,0.018-0.021c0-0.003-0.006-0.009-0.008-0.012c-0.002-0.005-0.003-0.001-0.002-0.009
+ c0.002-0.009,0.003-0.016,0.003-0.026c0-0.009,0-0.017-0.003-0.026c-0.003-0.007-0.009-0.013-0.011-0.02
+ c-0.004-0.011,0.006-0.019,0-0.029c-0.003-0.007-0.008-0.01-0.011-0.015c-0.004-0.006-0.004-0.016-0.005-0.023
+ c0-0.008-0.005-0.014-0.004-0.021c0.003,0.003,0.005,0.007,0.008,0.01c0.002,0.001,0.004,0.003,0.007,0.004
+ c0,0.002,0.001,0.004,0.001,0.006c0.003,0.005,0.011,0.019,0.019,0.016c0.011-0.002-0.009-0.032-0.013-0.037
+ c-0.008-0.01-0.017-0.018-0.023-0.03c-0.006-0.014-0.009-0.028-0.013-0.042c0.007,0.002,0.015,0.019,0.019,0.026
+ c0.005,0.007,0.012,0.012,0.017,0.02c0.009,0.013,0.017,0.029,0.023,0.044c0.002,0.006,0.001,0.013,0.005,0.019
+ c0.004,0.005,0.009,0.011,0.014,0.015c0.006,0.006,0.015,0.012,0.019,0.02c0.003,0.006,0.004,0.015,0.004,0.022
+ c0,0.005,0,0.01,0,0.015c0.001,0.004,0.002,0.005,0.003,0.008c0.005,0.018,0.017,0.031,0.025,0.049
+ c0.004,0.009,0.006,0.02,0.009,0.03c0.003,0.008,0.006,0.014,0.01,0.021c0.007,0.016,0.017,0.032,0.026,0.047
+ c0.005,0.008,0.007,0.017,0.01,0.026c0.003,0.008,0.008,0.017,0.008,0.025c-0.006-0.007-0.016-0.008-0.022-0.014
+ c-0.004-0.004-0.007-0.017-0.011-0.018c-0.007,0.022,0.013,0.046,0.022,0.064c0.006,0.013,0.012,0.028,0.017,0.041
+ c0.004,0.008,0.007,0.016,0.01,0.023c0.003,0.008,0.01,0.013,0.014,0.019c0.01,0.013,0.019,0.033,0.019,0.049
+ c0,0.015,0,0.03,0,0.045c0,0.015,0,0.033-0.008,0.045c-0.005,0.006-0.01,0.015-0.018,0.016c-0.006,0.001-0.017,0.001-0.023,0
+ s-0.009-0.004-0.015-0.004c-0.005,0-0.007,0.003-0.012,0.003c-0.006,0.001-0.01-0.003-0.018-0.003c-0.015,0-0.031,0-0.043,0.01
+ c-0.006,0.006-0.009,0.014-0.018,0.013c-0.004,0-0.004-0.003-0.007-0.004c-0.003-0.001-0.007,0-0.011-0.001
+ c-0.006-0.001-0.012-0.005-0.02-0.002c-0.008,0.003-0.01,0.016-0.016,0.021c-0.006,0.006-0.02,0.011-0.028,0.008
+ c-0.003-0.001-0.005-0.005-0.008-0.006c-0.004-0.002-0.008-0.001-0.012-0.001s-0.01-0.001-0.015,0
+ c-0.007,0.001-0.01,0.005-0.016,0.008c-0.009,0.004-0.016,0.003-0.026,0.003c-0.017,0-0.037,0.003-0.053-0.006
+ c-0.013-0.008-0.018-0.02-0.025-0.032c-0.007-0.015-0.009-0.032-0.023-0.042c-0.015-0.01-0.028-0.006-0.032,0.012
+ c-0.005-0.001-0.017-0.015-0.021-0.019c-0.005-0.009-0.007-0.02-0.008-0.03c0-0.007-0.003-0.012-0.004-0.02
+ c-0.001-0.009,0.003-0.013,0.004-0.021c0.002-0.012-0.006-0.022-0.004-0.035c0.001-0.008,0.004-0.013,0.004-0.022
+ c0-0.008-0.001-0.012-0.005-0.018c-0.006-0.012-0.012-0.023-0.017-0.035c-0.005-0.015-0.011-0.026-0.024-0.037
+ c-0.008-0.008-0.016-0.017-0.026-0.023c-0.007-0.005-0.014-0.008-0.02-0.015c-0.007-0.008-0.005-0.01-0.003-0.019
+ c0.002-0.011-0.008-0.014-0.012-0.022c-0.003-0.007,0-0.008,0.001-0.016c0.003-0.018-0.018-0.021-0.03-0.027
+ c-0.01-0.004-0.024-0.005-0.033-0.012c-0.006-0.004-0.01-0.011-0.016-0.014c0.001,0-0.009-0.003-0.009-0.003
+ c-0.003-0.001-0.006-0.001-0.01-0.002c-0.008-0.002-0.015-0.007-0.023-0.009c-0.008-0.003-0.014-0.004-0.022-0.005
+ c-0.006-0.001-0.01-0.005-0.015-0.007c-0.005-0.001-0.01-0.002-0.015-0.003c-0.008-0.003-0.015-0.007-0.023-0.009
+ c-0.008-0.002-0.015-0.005-0.023-0.007c-0.005-0.001-0.01,0-0.015,0c-0.005-0.001-0.008-0.002-0.012-0.003
+ c-0.021-0.005-0.042-0.003-0.063-0.005h-0.001c-0.006,0.002-0.01,0.003-0.016,0.004c-0.005,0-0.013-0.002-0.018,0
+ c-0.006,0.001-0.007,0.004-0.015,0.004c-0.005,0-0.01-0.001-0.015,0c-0.01,0-0.021,0.006-0.03,0.011
+ c-0.008,0.004-0.016,0.007-0.023,0.013c-0.004,0.004-0.01,0.011-0.011,0.017c-0.001,0.005,0,0.01-0.001,0.015
+ c-0.002,0.009-0.005,0.018-0.007,0.027c-0.001,0.005-0.002,0.018-0.008,0.022c-0.008,0.005-0.011-0.008-0.019,0
+ c-0.005,0.006-0.007,0.016-0.007,0.023c0.001,0.01,0.006,0.016,0.003,0.026c-0.002,0.008-0.001,0.015-0.004,0.023
+ c-0.002,0.008-0.004,0.014-0.002,0.023c0.002,0.007,0.002,0.016,0.006,0.023c0.005,0.009,0.015,0.018,0.022,0.027
+ c0.006,0.008,0.011,0.016,0.016,0.025c0.004,0.008,0.007,0.012,0.002,0.02c-0.004,0.007-0.013,0.008-0.02,0.01
+ c-0.009,0.002-0.014-0.001-0.022,0.005c-0.005,0.005-0.008,0.01-0.016,0.011c-0.007,0.001-0.013-0.001-0.019-0.004
+ c-0.004-0.003-0.012-0.01-0.016-0.011c-0.006-0.001-0.008,0.003-0.014,0.004c-0.004,0-0.008-0.001-0.011-0.001
+ c-0.01,0.001-0.017,0.008-0.023,0.016c-0.006,0.006-0.009,0.01-0.007,0.018c0.002,0.008,0.006,0.014,0.01,0.02
+ c0,0.001,0,0.001,0.001,0.002c0.01,0.006,0.019,0.013,0.028,0.02c0.026,0.02,0.046,0.051,0.068,0.076
+ c0.01,0.012,0.019,0.026,0.029,0.039c0.014,0.017,0.033,0.025,0.049,0.039c0.009,0.008,0.018,0.018,0.027,0.025
+ c0.011,0.008,0.028,0.01,0.039,0.018c0.012,0.009,0.02,0.013,0.036,0.018c0.013,0.004,0.025,0.011,0.036,0.019
+ c0.008,0.006,0.023,0.021,0.026,0.031c0.002,0.007,0.002,0.012,0.005,0.019c0.005,0.009,0.014,0.017,0.024,0.023
+ c0.015,0.009,0.027,0.016,0.036,0.033c0.006,0.014,0.01,0.029,0.012,0.045c0.012,0.002,0.02,0.009,0.032,0.006
+ c0.008-0.003,0.014-0.01,0.02-0.015c0.019-0.015,0.036-0.024,0.061-0.017c0.008,0.002,0.024,0.009,0.031,0.014
+ c0.009,0.007,0.011,0.02,0.017,0.03c0.004,0.009,0.007,0.019,0.013,0.028c0.009,0.013,0.021,0.024,0.033,0.035
+ c0.017,0.016,0.038,0.036,0.045,0.06c0.006,0.024-0.011,0.048-0.004,0.073c0.004,0.012,0.012,0.024,0.016,0.035
+ c0.005,0.01,0.007,0.019,0.012,0.029c0.005,0.011,0.005,0.021,0.007,0.033c0.002,0.009,0.011,0.019,0.011,0.03
+ c0.026,0,0.022-0.06,0.022-0.076c0-0.015,0-0.025-0.004-0.039s-0.009-0.019-0.017-0.03c-0.011-0.014-0.017-0.037-0.023-0.052
+ c-0.005-0.011-0.008-0.023-0.012-0.034c-0.005-0.012-0.011-0.022-0.017-0.034c-0.008-0.015-0.028-0.048-0.015-0.064
+ c0.016-0.019,0.042,0.004,0.053,0.017c0.012,0.015,0.024,0.024,0.034,0.041c0.007,0.014,0.014,0.026,0.02,0.04
+ c0.011,0.027,0.016,0.053,0.016,0.081c0,0.012,0.001,0.024,0,0.035c-0.001,0.015-0.006,0.021,0,0.036
+ c0.011,0.03,0.026,0.059,0.036,0.09c0.007,0.022,0.012,0.041,0.016,0.064c0.002,0.013,0.008,0.02,0.004,0.035
+ c-0.003,0.013-0.008,0.027-0.01,0.04c-0.001,0.006,0.001,0.012,0,0.017c-0.001,0.007-0.005,0.011-0.006,0.018
+ c-0.001,0.006,0.001,0.012,0.001,0.018c-0.001,0.008-0.005,0.014-0.007,0.022c-0.005,0.025-0.012,0.05-0.018,0.075
+ c-0.012,0.051-0.038,0.1-0.056,0.15c-0.006,0.014-0.006,0.024-0.006,0.039c0,0.013-0.004,0.031-0.001,0.042
+ c0.003,0.01,0.011,0.018,0.013,0.029c0.002,0.015,0.004,0.026,0.011,0.04c0.002,0.004,0.003,0.007,0.005,0.011
+ c0.002,0.002,0.004,0.006,0.006,0.009c0.101,0.09,0.202,0.179,0.302,0.269c0.157-0.083,0.314-0.165,0.471-0.247
+ c0.084,0.037,0.169,0.074,0.253,0.111c-0.052,0.135-0.104,0.271-0.156,0.406c0.008,0.013,0.01,0.03,0.014,0.044
+ c0.005,0.019,0.007,0.039,0.012,0.058c0.001,0.007,0.007,0.016,0.008,0.023c0.003,0.009,0.002,0.018,0.002,0.027
+ c0,0.01,0,0.019,0,0.028c0,0.008-0.005,0.017-0.006,0.024v-0.008l-0.004-0.001c-0.003-0.008,0.001-0.017-0.001-0.026
+ c-0.002-0.01-0.006-0.016-0.006-0.027c0.001-0.017,0.004-0.039,0.001-0.055c-0.004-0.013-0.012-0.026-0.016-0.039
+ c-0.002-0.009-0.005-0.018-0.008-0.026c-0.001-0.004-0.001-0.005-0.001-0.007c-0.008,0.021-0.017,0.042-0.025,0.063
+ c0.003,0.005,0.004,0.01,0.01,0.016c0.013,0.014,0.017,0.03,0.022,0.048c0.003,0.012,0.003,0.031,0,0.043
+ c-0.002,0.009-0.005,0.013-0.004,0.022c0,0.006,0.001,0.016,0,0.022c-0.003,0.01-0.019,0.028-0.029,0.031
+ c-0.012,0.004-0.026-0.005-0.033-0.015c-0.007-0.01-0.011-0.023-0.016-0.034c-0.004,0.01-0.008,0.021-0.012,0.031
+ c0.004,0.007,0.01,0.012,0.012,0.019c0.004,0.011,0.001,0.027,0.001,0.038s0.002,0.023,0.001,0.033
+ c-0.004,0.022-0.017,0.044-0.024,0.065c-0.007,0.024-0.013,0.045-0.022,0.068c-0.008,0.023-0.011,0.046-0.022,0.068
+ c-0.008,0.02-0.013,0.046-0.02,0.066c-0.006,0.018-0.012,0.039-0.02,0.055c-0.006,0.014-0.013,0.017-0.012,0.032
+ c0,0.011,0,0.022,0,0.032c0,0.022-0.005,0.041-0.011,0.061c-0.006,0.018-0.015,0.036-0.022,0.054
+ c-0.004,0.008-0.004,0.018-0.007,0.027c-0.001,0.006-0.004,0.009-0.004,0.016c0,0.005,0.002,0.012,0,0.017
+ c-0.003,0.012-0.01,0.024-0.011,0.038c-0.001,0.013-0.005,0.025-0.005,0.038c-0.001,0.011,0,0.022,0,0.032
+ c0,0.013,0.005,0.021,0.005,0.033c0.001,0.014-0.003,0.026-0.005,0.038c-0.001,0.004-0.004,0.01-0.004,0.012
+ c-0.001,0.006,0,0.011-0.001,0.016c-0.001,0.005-0.005,0.004-0.005,0.01c0,0.004,0.004,0.012,0.005,0.017
+ c0,0.013-0.004,0.02-0.006,0.032c-0.003,0.014,0,0.031,0,0.045s-0.007,0.024-0.01,0.037c-0.002,0.011-0.004,0.022-0.007,0.033
+ c-0.001,0.005,0.001,0.011,0.001,0.016c-0.001,0.005-0.004,0.008-0.005,0.012c-0.001,0.006-0.003,0.015-0.001,0.021
+ c0.001,0.005,0.005,0.005,0.006,0.01c0.002,0.01,0,0.023,0,0.033c0,0.014-0.004,0.025-0.006,0.039c-0.002,0.01,0,0.021,0,0.032
+ c0,0.009,0.002,0.013,0.004,0.022c0.001,0.004,0,0.013,0.002,0.016c0.006,0.008,0.023,0.001,0.031-0.001
+ c0.009-0.003,0.016-0.011,0.027-0.009c0.007,0.001,0.014,0.009,0.019,0.014c0.017,0.017,0.026,0.025,0.026,0.05
+ c0,0.011,0,0.022,0,0.033c0,0.016,0,0.017,0.016,0.016c0.014,0,0.021-0.001,0.028,0.011c0.004,0.009,0.005,0.019,0.016,0.022
+ c0.008,0.003,0.021,0.001,0.026-0.007c0.006-0.009-0.004-0.021,0.005-0.029c0.008,0.019,0,0.048-0.004,0.067
+ c-0.002,0.008-0.002,0.015-0.006,0.024c0,0-0.003,0.011-0.004,0.012c-0.002,0.01-0.001,0.021-0.006,0.031
+ c-0.005,0.01-0.011,0.019-0.015,0.028c-0.006,0.012-0.015,0.018-0.024,0.028c-0.005,0.006-0.014,0.017-0.016,0.025
+ c-0.003,0.01,0.004,0.02,0.005,0.029c0.003,0.014,0.005,0.019,0.002,0.034c-0.002,0.012-0.001,0.023-0.001,0.035
+ c0,0.015-0.006,0.025-0.011,0.039c-0.008,0.022-0.017,0.042-0.022,0.065c-0.002,0.009,0,0.016-0.004,0.026
+ c-0.002,0.006-0.005,0.012-0.008,0.018c-0.005,0.009-0.011,0.017-0.015,0.027c-0.008,0.018-0.009,0.038-0.02,0.056
+ c-0.003,0.004-0.009,0.009-0.013,0.013c-0.003,0.004-0.004,0.007-0.006,0.011c-0.009,0.012-0.017,0.027-0.026,0.04
+ c-0.008,0.01-0.021,0.02-0.032,0.028c-0.01,0.008-0.019,0.018-0.028,0.026c-0.009,0.007-0.017,0.018-0.027,0.023
+ c-0.012,0.006-0.018-0.001-0.024-0.012c-0.009-0.016-0.01-0.02-0.026-0.007c-0.006,0.005-0.013,0.01-0.017,0.017
+ c-0.005,0.007-0.005,0.015-0.008,0.023c-0.003,0.004-0.006,0.005-0.007,0.01c-0.003,0.01,0.001,0.023,0,0.033
+ c-0.001,0.011-0.005,0.021-0.006,0.032c0,0.015-0.004,0.027-0.005,0.041c-0.003,0.029-0.015,0.056-0.016,0.085
+ c-0.001,0.011-0.006,0.02-0.006,0.032c0,0.01,0.003,0.024,0.001,0.033c-0.003,0.01-0.008,0.014-0.017,0.018
+ c-0.002,0.001-0.015,0.004-0.016,0.005c-0.003,0.007,0.003,0.006,0.004,0.011c0.001,0.005,0,0.01,0.001,0.015
+ c0.002,0.011,0.009,0.021,0.006,0.033c-0.002,0.005-0.005,0.005-0.006,0.01s0,0.012,0,0.017c0,0.014,0,0.02-0.007,0.032
+ c-0.006,0.012-0.013,0.02-0.015,0.033c-0.001,0.012-0.004,0.027-0.01,0.037c-0.008,0.013-0.019,0.022-0.028,0.033
+ c-0.007,0.008-0.014,0.019-0.02,0.028c-0.007,0.011-0.016,0.017-0.023,0.027c-0.007,0.01-0.013,0.017-0.017,0.028
+ c-0.004,0.011-0.007,0.022-0.012,0.032c-0.012,0.024-0.023,0.054-0.03,0.081c-0.006,0.024-0.014,0.048-0.028,0.069
+ c-0.004,0.005-0.008,0.011-0.012,0.016c0,0.009,0,0.017,0.002,0.025c0.005,0.018,0.009,0.046,0.004,0.065
+ c-0.001,0.007-0.004,0.009-0.006,0.016c0,0.005,0.001,0.011,0.001,0.017c0,0.01,0.001,0.024-0.007,0.033
+ c-0.017,0.017-0.037-0.004-0.049-0.015c-0.017-0.016-0.034-0.033-0.048-0.051c-0.004-0.006-0.01-0.011-0.015-0.017
+ c-0.007-0.008-0.013-0.022-0.021-0.028c-0.015-0.011-0.04-0.001-0.046,0.018c-0.001,0.004,0,0.011-0.001,0.016
+ c-0.001,0.008-0.005,0.013-0.004,0.022c0.001,0.012,0.006,0.02,0.006,0.033c-0.001,0.013,0.002,0.024,0.005,0.037
+ c0.002,0.007,0.008,0.016,0.01,0.024c0.004,0.024,0.011,0.045,0.023,0.069c0.021,0.044,0.03,0.086,0.047,0.131
+ c0.004,0.01,0.011,0.021,0.013,0.032c0.001,0.011,0,0.023,0,0.033c0,0.022-0.006,0.045-0.015,0.065
+ c-0.017,0.039-0.036,0.073-0.073,0.097c-0.01,0.007-0.023,0.013-0.031,0.023c-0.008,0.011-0.012,0.024-0.023,0.033
+ c-0.018,0.015-0.028,0.027-0.038,0.049c-0.004,0.009-0.011,0.019-0.018,0.027c-0.004,0.005-0.01,0.008-0.014,0.012
+ c-0.002,0.003-0.002,0.008-0.005,0.011c-0.005,0.007-0.012,0.013-0.017,0.02c-0.007,0.01-0.011,0.023-0.018,0.032
+ c-0.007,0.009-0.019,0.016-0.021,0.028c-0.001,0.006,0.001,0.011-0.001,0.017c-0.002,0.006-0.004,0.009-0.004,0.016
+ c-0.001,0.011,0.001,0.018-0.011,0.022c-0.009,0.003-0.02,0.006-0.029,0.001c-0.007-0.005-0.011-0.015-0.014-0.023
+ c-0.001-0.002-0.002-0.004-0.003-0.005c-0.013-0.002-0.022-0.011-0.03-0.021c-0.011-0.013-0.027-0.035-0.031-0.051
+ c-0.002-0.005,0-0.01-0.003-0.014c-0.001-0.003-0.006-0.005-0.008-0.008c-0.005-0.006-0.005-0.014-0.007-0.021
+ c-0.007-0.019-0.005-0.04-0.01-0.059c-0.003-0.016-0.01-0.029-0.012-0.044c-0.003-0.021,0.005-0.039,0.006-0.06
+ c0-0.009-0.004-0.022-0.009-0.033c-0.02-0.01-0.041-0.018-0.062-0.021c-0.024-0.003-0.037-0.011-0.053,0.012
+ c-0.006,0.008-0.007,0.018-0.014,0.026c-0.006,0.006-0.014,0.011-0.02,0.017c-0.009,0.008-0.013,0.018-0.021,0.028
+ c-0.005,0.007-0.012,0.014-0.016,0.021c-0.006,0.012-0.006,0.024-0.007,0.037c0,0.011-0.003,0.021-0.005,0.032
+ c-0.002,0.019-0.01,0.034-0.011,0.053c-0.001,0.018-0.001,0.039-0.005,0.057c-0.006,0.026-0.028,0.05-0.041,0.073
+ c-0.008,0.014-0.014,0.033-0.03,0.041c-0.003,0.001-0.009,0.002-0.012,0.004c-0.001,0-0.002,0.001-0.003,0.001
+ c-0.175,0.457-0.35,0.913-0.525,1.37c0,0.001,0.001,0,0.001,0.002c0.002,0.013-0.004,0.021-0.006,0.032
+ c-0.002,0.011,0.003,0.023,0.001,0.033c-0.003,0.012-0.015,0.025-0.022,0.034c-0.007,0.007-0.013,0.013-0.02,0.02
+ c-0.047,0.122-0.094,0.245-0.141,0.367c0.116,0.027,0.232,0.055,0.348,0.082c0.005-0.004,0.01-0.008,0.015-0.012
+ c0.01-0.006,0.022-0.01,0.032-0.016c0.007-0.005,0.012-0.011,0.017-0.017c0.004-0.003,0.006-0.005,0.008-0.006
+ c0.001-0.002,0.002-0.004,0.003-0.006c0.006-0.009,0.012-0.014,0.02-0.022c0.012-0.013,0.027-0.024,0.04-0.036
+ c0.012-0.011,0.022-0.022,0.033-0.033c0.012-0.012,0.026-0.021,0.038-0.033c0.01-0.01,0.025-0.02,0.037-0.028
+ c0.007-0.005,0.013-0.009,0.017-0.016c0.001-0.001,0.001-0.001,0.002-0.002c0.008-0.012,0.017-0.023,0.025-0.035
+ c0.002-0.004,0.004-0.008,0.006-0.012c0.001-0.004,0.006-0.005,0.008-0.008c0.048-0.065,0.095-0.131,0.142-0.196
+ c0.009-0.017,0.022-0.036,0.027-0.053c0.004-0.012,0.004-0.021,0.012-0.032c0.009-0.011,0.021-0.022,0.032-0.033
+ c0.014-0.014,0.027-0.028,0.037-0.045c0.004-0.006,0.01-0.012,0.014-0.018c0.007-0.011,0.013-0.023,0.021-0.033
+ c0.004-0.007,0.016-0.03,0.027-0.029c0.003,0,0.001,0.004,0.002,0.005c0.131-0.182,0.262-0.363,0.393-0.544
+ c0.001-0.004-0.001-0.009,0.001-0.012c0.005-0.011,0.014-0.017,0.023-0.027c0.002-0.003,0.006-0.009,0.009-0.011
+ c0.001-0.001,0.003-0.001,0.004-0.002c0.019-0.026,0.038-0.053,0.057-0.079c0.002-0.005,0.006-0.009,0.006-0.015
+ c0.001-0.005-0.001-0.011,0-0.017c0.001-0.006,0.005-0.01,0.006-0.016c0.001-0.005-0.001-0.011-0.001-0.017
+ c0.001-0.003,0.005-0.01,0.005-0.011c0-0.008-0.001-0.008-0.003-0.016c-0.005-0.017,0.001-0.031,0.009-0.046
+ c0.007-0.013,0.022-0.048,0.043-0.034c0.007,0.005,0.005,0.015,0.013,0.019c0.008,0.005,0.021,0.001,0.027-0.002
+ c0.007-0.004,0.012-0.01,0.018-0.015c0.026-0.036,0.053-0.073,0.079-0.109c-0.007-0.001-0.015-0.002-0.022-0.003
+ c-0.01-0.002-0.022,0-0.032,0c-0.02,0-0.056,0.005-0.05-0.027l0.007-0.003c-0.001,0-0.002-0.001-0.002-0.001
+ c-0.006-0.003-0.014-0.006-0.016-0.012c-0.002-0.007,0.004-0.016,0.006-0.022c0.004-0.01,0.004-0.018,0.007-0.028
+ c0.009-0.032,0.037-0.059,0.042-0.092c0.001-0.007-0.001-0.014,0.001-0.022c0.002-0.007,0.004-0.014,0.006-0.022
+ c0.003-0.015,0.01-0.035,0.019-0.049c0.009-0.014,0.021-0.027,0.034-0.038c0.012-0.009,0.027-0.02,0.028-0.037
+ c0.007-0.002,0.013,0.001,0.02-0.002c0.004-0.001,0.013-0.007,0.018-0.01c0.008-0.004,0.017-0.021,0.025-0.021
+ c0.002,0.01,0.005,0.018,0.007,0.028c0.002,0.016,0.007,0.033,0.01,0.049c0.001,0.011,0.003,0.026,0.001,0.037
+ c-0.001,0.006-0.005,0.01-0.006,0.016c-0.001,0.007,0.001,0.015,0,0.022c-0.002,0.021-0.009,0.046-0.018,0.065
+ c-0.009,0.022-0.014,0.048-0.02,0.071c-0.003,0.014-0.009,0.025-0.014,0.038c0.102-0.037,0.204-0.075,0.305-0.112
+ c0.052,0.073,0.104,0.145,0.156,0.218c-0.134,0.131-0.269,0.263-0.404,0.394c0.001,0.002,0,0.004,0,0.006
+ c0.004,0.023,0.023,0.036,0.023,0.06c-0.001,0.013,0.003,0.031,0,0.044c-0.003,0.009-0.01,0.018-0.013,0.028
+ c-0.003,0.014-0.01,0.028-0.013,0.042c-0.003,0.012-0.002,0.026-0.002,0.038c0,0.026,0,0.05-0.005,0.075
+ c-0.003,0.014-0.006,0.025-0.006,0.04c0.001,0.014-0.002,0.024-0.005,0.038c-0.003,0.012-0.005,0.025-0.009,0.037
+ c-0.004,0.011-0.01,0.022-0.012,0.033c-0.001,0.005,0,0.011-0.002,0.016c-0.003,0.007-0.012,0.015-0.017,0.022
+ c-0.006,0.011-0.012,0.022-0.019,0.033c-0.007,0.012-0.008,0.027-0.016,0.039c-0.006,0.009-0.011,0.017-0.016,0.027
+ c-0.006,0.011-0.01,0.021-0.014,0.032c-0.004,0.013-0.011,0.018-0.019,0.027c-0.009,0.012-0.018,0.023-0.028,0.033
+ c-0.012,0.012-0.024,0.019-0.033,0.033c-0.011,0.017-0.019,0.033-0.026,0.05c-0.002,0.005-0.006,0.01-0.007,0.015
+ c-0.001,0.005,0.002,0.01-0.001,0.015c-0.003,0.008-0.014,0.014-0.02,0.019c-0.016,0.012-0.039,0.021-0.051,0.037
+ c-0.007,0.009-0.014,0.017-0.02,0.027c-0.005,0.008-0.009,0.016-0.017,0.022c-0.007,0.005-0.018,0.007-0.027,0.009
+ c-0.006,0.002-0.01,0.001-0.016,0.002c-0.006,0-0.008,0.003-0.012,0.004c-0.023,0.005-0.046-0.007-0.06,0.016
+ c-0.01,0.015-0.021,0.031-0.03,0.046c-0.017,0.029-0.048,0.047-0.071,0.071c-0.017,0.019-0.027,0.039-0.05,0.052
+ c-0.005,0.003-0.008,0.002-0.013,0.005c-0.002,0.002-0.005,0.006-0.008,0.008c-0.006,0.007-0.012,0.012-0.016,0.021
+ c-0.014,0.027-0.025,0.057-0.035,0.086c-0.005,0.013-0.022,0.029-0.032,0.039c-0.01,0.011-0.021,0.019-0.028,0.033
+ c-0.005,0.009-0.008,0.019-0.014,0.028c-0.012,0.021-0.023,0.042-0.035,0.063c-0.011,0.018-0.016,0.042-0.026,0.061
+ c-0.009,0.019-0.022,0.037-0.033,0.054c-0.02,0.033-0.029,0.072-0.047,0.105c-0.011,0.02-0.021,0.036-0.035,0.053
+ c-0.017,0.02-0.014,0.044-0.028,0.064c-0.012,0.018-0.026,0.033-0.035,0.052c-0.009,0.018-0.014,0.035-0.025,0.052
+ c-0.011,0.019-0.03,0.032-0.041,0.051c-0.01,0.017-0.02,0.035-0.028,0.053c-0.009,0.021-0.024,0.036-0.037,0.055
+ c-0.005,0.008-0.011,0.013-0.016,0.021c-0.007,0.012-0.016,0.017-0.025,0.026c-0.009,0.01-0.015,0.021-0.027,0.028
+ c-0.007,0.004-0.013,0.005-0.019,0.013c-0.006,0.008-0.008,0.018-0.013,0.025c-0.005,0.006-0.012,0.011-0.016,0.017
+ c-0.009,0.01-0.016,0.022-0.026,0.029c-0.009,0.006-0.018,0.01-0.026,0.016c-0.02,0.016-0.025,0.002-0.041-0.011
+ c-0.01-0.007-0.022-0.013-0.033-0.019c-0.012-0.005-0.025-0.009-0.037-0.014c-0.01-0.005-0.015-0.011-0.027-0.011
+ c-0.013-0.001-0.024,0.002-0.036,0.005c-0.008,0.002-0.019,0.005-0.03,0.006c-0.017,0.01-0.028,0.021-0.043,0.033
+ c-0.005,0.003-0.01,0.007-0.016,0.009c-0.013,0.004-0.026,0.007-0.039,0.012c-0.008,0.004-0.018,0.006-0.027,0.011
+ c-0.008,0.004-0.015,0.01-0.022,0.015c-0.019,0.011-0.041,0.017-0.058,0.03c-0.015,0.011-0.028,0.021-0.044,0.03
+ c-0.016,0.008-0.033,0.014-0.05,0.022c-0.013,0.007-0.022,0.018-0.034,0.027c-0.005,0.004-0.006,0.001-0.011,0.006
+ c-0.003,0.003-0.006,0.009-0.009,0.013c-0.009,0.01-0.013,0.022-0.022,0.034c-0.017,0.024-0.036,0.05-0.058,0.068
+ c-0.018,0.014-0.028,0.031-0.038,0.05c-0.012,0.024-0.033,0.039-0.056,0.052c-0.019,0.01-0.042,0.025-0.053,0.045
+ c-0.007,0.01-0.018,0.023-0.022,0.034c-0.004,0.009-0.004,0.021-0.008,0.031c-0.008,0.022-0.027,0.034-0.044,0.047
+ c-0.021,0.018-0.039,0.04-0.057,0.062c-0.014,0.017-0.036,0.028-0.055,0.037c-0.004-0.008,0.01-0.019,0.014-0.026
+ c0.006-0.011,0.017-0.026,0.012-0.038c-0.009,0.002-0.019,0.011-0.027,0.016c-0.01,0.006-0.016,0.007-0.027,0.011
+ c-0.014,0.003-0.027,0.017-0.036,0.028c-0.015,0.016-0.027,0.027-0.045,0.038c-0.016,0.01-0.029,0.023-0.045,0.03
+ c-0.014,0.007-0.023,0.018-0.037,0.025c-0.018,0.008-0.032,0.021-0.049,0.03c-0.021,0.01-0.039,0.026-0.06,0.035
+ c-0.016,0.007-0.032,0.013-0.048,0.022c-0.017,0.01-0.032,0.02-0.05,0.03c-0.007,0.004-0.011,0.004-0.018,0.011
+ c-0.004,0.004-0.008,0.01-0.013,0.013c-0.01,0.008-0.022,0.016-0.033,0.022c-0.007,0.004-0.015,0.008-0.023,0.011
+ c-0.006,0.003-0.011,0.002-0.017,0.004c-0.003,0-0.005,0.002-0.006,0.004c-0.023,0.001-0.002-0.026,0.005-0.037
+ c0.005-0.007,0.011-0.012,0.013-0.021c0.001-0.009-0.004-0.019,0.001-0.027c0.002-0.005,0.01-0.008,0.013-0.013
+ c0.005-0.005,0.01-0.013,0.013-0.02c0.009-0.018,0.016-0.036,0.033-0.05c0.01-0.008,0.024-0.01,0.034-0.019
+ c0.007-0.008,0.011-0.019,0.02-0.025c0.007-0.006,0.016-0.008,0.023-0.013c0.008-0.007,0.012-0.021,0.014-0.029
+ c0.002-0.008-0.001-0.015,0.002-0.022c0.002-0.006,0.01-0.012,0.014-0.017c0.008-0.009,0.015-0.018,0.022-0.027
+ c0.016-0.019,0.03-0.037,0.04-0.059c0.01-0.02,0.021-0.037,0.033-0.055c0.007-0.009,0.014-0.018,0.021-0.027
+ c0.005-0.008,0.017-0.02,0.02-0.028c0.005-0.012,0.002-0.03,0.002-0.043c-0.001-0.019-0.005-0.024-0.022-0.028
+ c-0.013-0.004-0.017-0.008-0.026-0.017c-0.005-0.004-0.009-0.003-0.013-0.004c-0.344,0.335-0.687,0.671-1.031,1.006
+ c-0.061-0.042-0.122-0.085-0.183-0.128c-0.002,0.003-0.005,0.006-0.006,0.009c-0.013,0.031-0.044,0.08-0.08,0.087
+ c-0.008,0.002-0.009,0.001-0.017,0.006c-0.005,0.003-0.01,0.008-0.016,0.01c-0.009,0.002-0.025,0.003-0.033-0.003
+ c-0.003-0.003-0.005-0.006-0.005-0.01c-0.002,0.003-0.003,0.005-0.004,0.007c-0.007,0.014-0.02,0.029-0.023,0.045
+ c-0.004,0.02,0.003,0.034-0.007,0.053c-0.007,0.012-0.012,0.026-0.019,0.039c-0.004,0.008-0.005,0.013-0.007,0.022
+ c-0.002,0.01-0.004,0.012-0.011,0.02c-0.009,0.012-0.018,0.023-0.032,0.031c-0.015,0.008-0.036,0.005-0.049-0.002l0.003-0.007
+ c-0.002,0.001-0.005,0.002-0.008,0.001c-0.009-0.004-0.006-0.017-0.005-0.024c0.002-0.01,0.006-0.025,0.011-0.034
+ c0.006-0.011,0.016-0.017,0.022-0.027c0.002-0.004,0.007-0.01,0.008-0.014c0.003-0.008,0-0.017,0.003-0.026
+ c0.002-0.011,0.005-0.01,0-0.021c-0.004-0.01-0.01-0.018-0.014-0.027c-0.007-0.017,0.005-0.023,0.007-0.039
+ c0.001-0.01-0.001-0.012,0.005-0.022c0.005-0.007,0.009-0.012,0.013-0.02c0.005-0.014,0.007-0.026,0.025-0.029
+ c0.002,0,0.006,0,0.01,0c0-0.018,0.007-0.037,0.017-0.049c0.013-0.015,0.03-0.028,0.034-0.048c0.001-0.011-0.001-0.023-0.001-0.033
+ c0-0.005,0.002-0.009,0.003-0.014c-0.001-0.002-0.002-0.004-0.004-0.006c-0.344-0.241-0.689-0.482-1.033-0.722
+ c0.006-0.039,0.013-0.079,0.02-0.118c-0.014,0-0.028,0-0.041,0c-0.009,0-0.022,0.003-0.027-0.006
+ c-0.005-0.01,0.009-0.026,0.014-0.033c0.006-0.009,0.008-0.017,0.013-0.027c0.004-0.007,0.01-0.013,0.014-0.021
+ c0.006-0.01,0.01-0.02,0.014-0.031c0.002-0.005,0.004-0.006,0.004-0.012c0.001-0.008,0-0.014,0.001-0.021
+ c0.002-0.008,0.009-0.018,0.014-0.025c0.008-0.009,0.009-0.014,0.012-0.025c0.005-0.018,0.014-0.041,0.023-0.059
+ c0.001-0.001,0.003-0.002,0.004-0.003c0.012-0.068,0.023-0.137,0.035-0.205c-0.176-0.03-0.352-0.06-0.528-0.09
+ c0.067-0.179,0.135-0.359,0.203-0.538c-0.089-0.116-0.178-0.231-0.267-0.346c0.037-0.043,0.074-0.086,0.111-0.128
+ c-0.005-0.007-0.01-0.013-0.017-0.018c-0.204,0.031-0.408,0.062-0.612,0.094c-0.004-0.042,0.013,0.467-0.012-0.127
+ c-0.024-0.594,0.79-0.564,1.185-0.846c-0.005-0.014-0.01-0.028-0.014-0.042c-0.008-0.025-0.013-0.053-0.025-0.077
+ c-0.011-0.024-0.02-0.049-0.036-0.07c-0.014-0.017-0.036-0.032-0.039-0.054c-0.003-0.02,0.009-0.039,0.015-0.056
+ c0.008-0.018,0.028-0.026,0.045-0.035c0.009-0.005,0.019-0.007,0.027-0.014c0.009-0.008,0.01-0.015,0.015-0.026
+ c0.007-0.013,0.018-0.02,0.011-0.037c-0.001-0.003-0.006-0.008-0.008-0.012c-0.003-0.005-0.004-0.011-0.007-0.016
+ c-0.011-0.019-0.012-0.04-0.032-0.055c-0.02-0.014-0.04-0.028-0.06-0.042c-0.02-0.012-0.033-0.031-0.055-0.039
+ c-0.022-0.008-0.042-0.035-0.043-0.06c-0.001-0.011,0-0.022,0-0.033c0-0.014,0.003-0.024,0.005-0.038
+ c0.002-0.01-0.002-0.022,0.001-0.032c0.003-0.009,0.013-0.017,0.019-0.023c0.014-0.013,0.024-0.027,0.034-0.04
+ c-0.145-0.072-0.29-0.143-0.435-0.215c-0.005,0.005-0.011,0.01-0.012,0.016c-0.006-0.001-0.015-0.021-0.02-0.028
+ c-0.002-0.003-0.005-0.005-0.008-0.007c-0.101-0.05-0.203-0.101-0.304-0.151c-0.032-0.038-0.064-0.077-0.097-0.116
+ c-0.011-0.005-0.018-0.016-0.032-0.019c-0.013-0.003-0.019,0-0.029-0.01c-0.011-0.012-0.013-0.024-0.031-0.028
+ c-0.008-0.002-0.009,0.001-0.016-0.005c-0.006-0.004-0.013-0.012-0.018-0.017c-0.009-0.007-0.016-0.01-0.026-0.016
+ c-0.009-0.005-0.011-0.011-0.018-0.02c-0.003-0.005-0.006-0.008-0.01-0.011c-0.006-0.004-0.016-0.004-0.021-0.008
+ c-0.009-0.007-0.011-0.021-0.02-0.028c-0.007-0.004-0.014-0.005-0.019-0.013c-0.004-0.005-0.005-0.014-0.01-0.018
+ c-0.006-0.006-0.01-0.003-0.016-0.006c-0.014-0.006-0.011-0.023-0.02-0.033c-0.01-0.01-0.018-0.006-0.029-0.011
+ c-0.011-0.003-0.025-0.022-0.032-0.031c-0.015-0.022-0.04-0.033-0.055-0.055c-0.01-0.012-0.017-0.021-0.029-0.032
+ c-0.015-0.014-0.02-0.03-0.031-0.047c-0.007-0.01-0.017-0.018-0.021-0.03c-0.004-0.014,0.004-0.024,0.005-0.038
+ c0.001-0.025,0.01-0.045-0.002-0.069c-0.005-0.011,0.002-0.018,0.005-0.026c-0.059-0.071-0.118-0.142-0.177-0.214
+ c0.011-0.004,0.023-0.008,0.034-0.012l0.051-0.034c-0.007-0.015-0.015-0.032-0.006-0.049c0.007-0.011,0.017-0.026,0.025-0.037
+ c0.009-0.01,0.024-0.015,0.033-0.025c0.025-0.026,0.023-0.063,0.024-0.098c-0.252-0.022-0.504-0.045-0.755-0.067
+ c0.009-0.109,0.019-0.218,0.029-0.327c-0.002-0.001-0.004-0.003-0.006-0.005c-0.009-0.01-0.017-0.015-0.023-0.027
+ c-0.007-0.017-0.001-0.038,0.018-0.045c0.006-0.003,0.012,0.001,0.018,0.002c0.004-0.051,0.009-0.102,0.013-0.154
+ c-0.005-0.003-0.01-0.006-0.015-0.011c-0.009-0.01-0.015-0.019-0.027-0.027c-0.011-0.006-0.017-0.01-0.023-0.021
+ c-0.006-0.01-0.006-0.017-0.006-0.028c0-0.007-0.001-0.015,0-0.021c0.002-0.008,0.01-0.014,0.011-0.022
+ c0.001-0.009-0.009-0.02-0.016-0.026c-0.003-0.002-0.013-0.01-0.016-0.011c-0.005-0.002-0.012,0.001-0.016-0.002
+ c-0.021-0.015,0.031-0.046-0.012-0.054c-0.007-0.001-0.01,0.003-0.015-0.004c-0.002-0.003-0.004-0.018-0.005-0.021
+ c-0.003-0.015-0.001-0.031-0.001-0.046c0-0.016,0.002-0.034,0-0.049c-0.001-0.009-0.005-0.016-0.006-0.025
+ c-0.001-0.01,0-0.02,0-0.03c0-0.031,0.013-0.066,0.004-0.097c-0.006-0.025-0.003-0.05,0.003-0.076
+ c0.006-0.022,0.01-0.042,0.015-0.065c0.007-0.028,0.009-0.058,0.027-0.081c0.011-0.014,0.02-0.037,0.038-0.039
+ c0.017-0.001,0.018,0,0.026-0.016c0.008-0.015,0.007-0.027,0.007-0.044c0-0.022-0.001-0.044,0.004-0.065
+ c0.003-0.011,0.003-0.022,0.006-0.032c0.003-0.012,0.001-0.026,0.001-0.038c0-0.022-0.005-0.043-0.006-0.065
+ c0-0.013,0-0.026,0-0.039V49.35c0-0.013-0.001-0.028,0.002-0.041c0.001-0.005,0.003-0.007,0.004-0.013c0.001-0.005,0-0.011,0-0.017
+ c0-0.01-0.001-0.021,0-0.032c0-0.012,0.006-0.024,0.009-0.034c0.004-0.01,0.007-0.022,0.014-0.03
+ c0.007-0.009,0.015-0.007,0.026-0.007c0.009,0,0.024-0.001,0.031-0.007c0.012-0.009,0.006-0.019,0.003-0.031
+ c-0.003-0.011,0-0.022-0.001-0.033c-0.001-0.012-0.003-0.013,0.01-0.02c0.022-0.011,0.042-0.019,0.066-0.024
+ c0.012-0.002,0.022,0.002,0.032,0.005c0.011,0.003,0.022,0.001,0.033,0.001c0.011,0,0.022-0.001,0.033,0
+ c0.012,0.002,0.019,0.006,0.032,0.006c0.011,0,0.022,0,0.033,0c0.01-0.001,0.018,0.002,0.027,0.004
+ c0.007-0.002,0.014-0.004,0.022-0.005c0.012,0,0.026-0.001,0.038,0c0.011,0.002,0.016,0.006,0.027,0.006c0.011,0,0.022,0,0.033,0
+ c0.01,0,0.023,0.002,0.032,0c0.007-0.001,0.011-0.005,0.017-0.006c0.007-0.001,0.015,0.001,0.022,0.001
+ c0.019,0,0.046-0.005,0.065-0.001c0.005,0.001,0.006,0.005,0.01,0.006c0.007,0.002,0.018,0.002,0.026,0.004
+ c0.015,0.004,0.022,0.012,0.035,0.022c0.016,0.013,0.027,0.036,0.043,0.046c0.008,0.006,0.018,0.009,0.027,0.014
+ c0.014,0.007,0.024,0.01,0.038,0.013c0.008,0.002,0.008,0.006,0.016,0.003c0.002,0,0.01-0.008,0.012-0.009
+ c0.007-0.006,0.014-0.011,0.02-0.018c0.006-0.006,0.011-0.011,0.018-0.015c0.01-0.007,0.02-0.005,0.032-0.007
+ c0.009-0.001,0.019-0.007,0.028-0.008c0.007-0.001,0.014,0,0.02-0.001c0.012-0.002,0.022-0.009,0.034-0.006
+ c0.025,0.005,0.044,0.035,0.065,0.048c0.011,0.006,0.022,0.013,0.033,0.018c0.01,0.004,0.02,0.005,0.029,0.009
+ c0.009,0.004,0.016,0.008,0.024,0.014c0.015,0.01,0.032,0.011,0.049,0.016c0.023,0.006,0.055,0.022,0.071,0.038
+ c0.004,0.004,0.012,0.015,0.013,0.02c0,0.006-0.005,0.012-0.006,0.017c-0.002,0.007-0.005,0.014-0.006,0.021
+ c-0.004,0.019,0.02,0.048,0.033,0.059c0.013,0.012,0.028,0.026,0.043,0.034c0.002,0.001,0.01,0.003,0.013,0.004
+ c0.004,0.002,0.007,0.004,0.01,0.005c0.012,0.006,0.018,0.009,0.016,0.024c-0.003,0.013-0.009,0.014-0.017,0.021
+ c-0.007,0.007-0.012,0.017-0.016,0.028c-0.014,0.038-0.022,0.074-0.022,0.115c0,0.014,0.003,0.034,0,0.048
+ c-0.001,0.004-0.002,0.008-0.003,0.012c0.022,0.011,0.043,0.022,0.065,0.033c0.094-0.079,0.189-0.158,0.283-0.238
+ c-0.269-0.352-0.538-0.704-0.807-1.056c-0.004,0.003-0.008,0.006-0.014,0.006c-0.005,0.001-0.007,0.002-0.012,0.003
+ c-0.004,0.001-0.01,0.001-0.014,0c-0.022-0.005-0.047-0.009-0.067-0.019c-0.027-0.013-0.052-0.029-0.075-0.047
+ c-0.025-0.02-0.045-0.05-0.05-0.081c-0.003-0.019,0.003-0.037-0.006-0.054c-0.007-0.012-0.014-0.022-0.019-0.035
+ c-0.006-0.017,0.002-0.037,0.01-0.052c0.004-0.008,0.009-0.016,0.009-0.026c-0.001-0.004-0.002-0.007-0.002-0.011
+ c-0.062-0.08-0.124-0.161-0.186-0.242c0.389-1.261,1.079-1.084,1.742-0.555c0.001,0,0.002,0.001,0.003,0.001
+ c0.005,0,0.012-0.002,0.017-0.001c0.005,0.002,0.006,0.006,0.011,0.007c0.01,0.002,0.024,0,0.035,0s0.024-0.002,0.034-0.001
+ c0.013,0.002,0.021,0.006,0.035,0.006s0.027,0.001,0.041,0.006c0.009,0.003,0.019,0.01,0.028,0.012
+ c0.015,0.003,0.021-0.007,0.023,0.011c0.002,0.013,0.001,0.028-0.004,0.041c-0.008,0.019-0.013,0.035-0.013,0.057
+ c0,0.012-0.004,0.018-0.006,0.029c0,0.001,0,0.002,0,0.004c0.057,0.051,0.114,0.106,0.17,0.161c0.032,0.011,0.064,0.026,0.1,0.018
+ c0.005-0.001,0.005-0.005,0.012-0.005c0.007,0.001,0.013,0.005,0.018,0.008c0.012,0.006,0.015,0.01,0.029,0.01
+ c0.011,0,0.023,0,0.034,0c0.006,0,0.012-0.001,0.018,0c0.008,0.001,0.01,0.004,0.017,0.006c0.012,0.004,0.022,0.007,0.035,0.013
+ c0.01,0.005,0.019,0.01,0.028,0.016c0.02,0.011,0.043,0.018,0.064,0.028c0.035,0.018,0.058,0.054,0.086,0.08
+ c0.003,0.002,0.008,0.007,0.01,0.008c0.003,0.002,0.01,0.004,0.012,0.006c0.007,0.006,0.013,0.021,0.018,0.029
+ c0.005,0.01,0.008,0.019,0.007,0.031c-0.002,0.02-0.011,0.047-0.019,0.065c-0.004,0.008-0.013,0.015-0.018,0.022
+ c-0.004,0.007-0.007,0.016-0.011,0.023c-0.005,0.008-0.012,0.015-0.018,0.023c-0.005,0.008-0.009,0.018-0.014,0.026
+ c-0.002,0.004-0.005,0.007-0.007,0.01c0.042,0.045,0.084,0.091,0.124,0.136c0.006-0.006,0.013-0.013,0.02-0.016
+ c0.008-0.004,0.026-0.013,0.035-0.011c0.007,0.002,0.013,0.012,0.019,0.017c0.013,0.013,0.025,0.025,0.034,0.041
+ c0.008,0.013,0.01,0.027,0.018,0.04c0.004,0.007,0.007,0.015,0.011,0.024c0.012,0.026,0.032,0.046,0.049,0.069
+ c0.014,0.018,0.011,0.04,0.016,0.061c0.295,0.327,0.516,0.558,0.595,0.478c0.083-0.331,0.166-0.663,0.248-0.995
+ c0.021-0.028,0.042-0.057,0.063-0.085c-0.002-0.004-0.004-0.008-0.004-0.012c-0.001-0.012-0.002-0.029,0.001-0.041
+ c0.002-0.007,0.007-0.014,0.009-0.022c0.004-0.012,0.006-0.013,0.013-0.024c0.013-0.017,0.023-0.034,0.039-0.05
+ c0.015-0.016,0.035-0.03,0.057-0.04c0.002-0.005,0-0.01,0.002-0.02c0.001-0.006,0.005-0.01,0.006-0.017
+ c0.001-0.005-0.002-0.012,0-0.017c0.006-0.02,0.037-0.028,0.052-0.039c0.01-0.007,0.018-0.016,0.029-0.023
+ c0.01-0.007,0.02-0.012,0.031-0.018c0.038-0.053,0.077-0.106,0.116-0.159c0.014-0.002,0.029-0.003,0.044-0.004
+ c0.003-0.008,0.004-0.017,0.009-0.024c0.005-0.008,0.014-0.013,0.02-0.02c0.018-0.019,0.024-0.044,0.041-0.064
+ c0.005-0.006,0.013-0.011,0.017-0.018c0.006-0.01,0.008-0.024,0.011-0.035c0.003-0.009,0.009-0.019,0.011-0.028
+ c0.003-0.013-0.003-0.028,0.001-0.041c0.006-0.022,0.032-0.043,0.055-0.039c0.005,0.001,0.018,0.009,0.024,0.012
+ c0.012,0.005,0.024,0.01,0.035,0.017c0.038,0.023,0.05,0.063,0.071,0.101c0.009,0.018,0.023,0.034,0.031,0.053
+ c0.009,0.019,0.015,0.035,0.031,0.051c0,0,0,0.001,0.001,0.001c0.067-0.006,0.133-0.012,0.2-0.018c0-0.006,0.001-0.011,0.001-0.016
+ c-0.001-0.007-0.005-0.011-0.007-0.018c-0.001-0.005,0-0.013-0.001-0.018c-0.003-0.016-0.01-0.03-0.016-0.045
+ c-0.009-0.026-0.027-0.049-0.036-0.075c-0.009-0.026-0.021-0.058-0.016-0.087c0.004-0.018,0.009-0.014,0.023-0.017
+ c0.015-0.002,0.026-0.006,0.041-0.006c0.011,0,0.024,0.002,0.034,0.001c0.006-0.001,0.012-0.005,0.017-0.006
+ c0.014-0.005,0.022-0.004,0.036-0.002c0.014,0.002,0.029,0.002,0.044,0.002c0.016-0.001,0.028,0.003,0.043,0.006
+ c0.012,0.001,0.027-0.001,0.039-0.001c0.015,0,0.026,0.006,0.041,0.006c0.023,0,0.046-0.001,0.069,0.005
+ c0.012,0.003,0.024,0.006,0.036,0.007c0.018,0.001,0.034,0.009,0.052,0.012c0.022,0.002,0.048,0,0.067-0.007
+ c0.01-0.003,0.015-0.003,0.022-0.012c0.003-0.005,0.005-0.009,0.005-0.013c-0.02-0.016-0.041-0.03-0.057-0.05
+ c-0.016-0.022-0.029-0.044-0.04-0.069c-0.003-0.008-0.003-0.015-0.006-0.023c-0.001-0.005-0.004-0.009-0.006-0.015
+ c-0.002-0.01-0.004-0.02-0.004-0.031c0-0.019,0.003-0.043-0.003-0.06c-0.007-0.019-0.018-0.027-0.036-0.016
+ c-0.007,0.005-0.009,0.005-0.018,0.005c-0.008,0-0.015-0.001-0.023-0.004c-0.01-0.004-0.013-0.017-0.018-0.026
+ c-0.005-0.009-0.01-0.019-0.016-0.027c-0.011-0.014-0.031-0.005-0.044-0.015c-0.013-0.011-0.018-0.03-0.026-0.045
+ c-0.004-0.007-0.009-0.016-0.013-0.023c-0.003-0.004-0.008-0.007-0.011-0.011c-0.004-0.005-0.005-0.01-0.008-0.015
+ c-0.002-0.003-0.004-0.005-0.006-0.007c-0.009-0.003-0.018-0.008-0.028-0.013c-0.029-0.016-0.059-0.038-0.082-0.062
+ c-0.013-0.014-0.029-0.025-0.042-0.039c-0.008-0.008-0.016-0.018-0.023-0.027c-0.012-0.016-0.02-0.035-0.03-0.052
+ c-0.005-0.008-0.014-0.015-0.019-0.024c-0.004-0.007-0.007-0.012-0.011-0.019c-0.008-0.011-0.016-0.024-0.02-0.037
+ c-0.004-0.012-0.011-0.025-0.011-0.038c0-0.007,0-0.012,0.008-0.015c0.009-0.003,0.025,0.011,0.031,0.018
+ c0.006,0.007,0.008,0.017,0.014,0.024s0.013,0.012,0.018,0.02c0.005,0.007,0.008,0.012,0.016,0.017
+ c0.006,0.003,0.013,0.004,0.018,0.009c0.007,0.006,0.015,0.016,0.02,0.023c0.005,0.007,0.009,0.015,0.016,0.007
+ c0.005-0.005,0.006-0.015,0.011-0.019c0.004-0.004,0.013-0.009,0.019-0.008c0.006,0,0.01,0.008,0.015,0.011
+ c0.004,0.001,0.019,0.002,0.022,0.001c0.011-0.005-0.01-0.025-0.014-0.032c-0.008-0.015-0.014-0.032-0.021-0.048
+ c-0.005-0.014-0.005-0.029-0.01-0.041c-0.006-0.017-0.008-0.034-0.016-0.049c-0.004-0.008-0.007-0.016-0.012-0.023
+ c-0.004-0.008-0.011-0.013-0.017-0.019c-0.009-0.009-0.018-0.018-0.027-0.027c-0.009-0.008-0.02-0.014-0.03-0.022
+ c-0.014-0.011-0.025-0.024-0.038-0.035c-0.004-0.004-0.008-0.006-0.011-0.011c-0.002-0.001-0.004-0.005-0.005-0.007
+ c-0.004-0.007-0.007-0.011-0.013-0.016c-0.003-0.003-0.012-0.009-0.008-0.014c0.002-0.003,0.016,0.004,0.021,0.003
+ c-0.005-0.004-0.012-0.007-0.017-0.011c-0.008-0.006-0.014-0.013-0.021-0.02c-0.006-0.006-0.014-0.012-0.021-0.019
+ c-0.003-0.003-0.004-0.006-0.008-0.009c-0.003-0.002-0.005-0.003-0.008-0.004c-0.004-0.003-0.008-0.009-0.011-0.013
+ c-0.005-0.006-0.008-0.012-0.012-0.019c-0.008-0.013-0.017-0.027-0.03-0.036c-0.013-0.01-0.026-0.02-0.035-0.035
+ c-0.006-0.012-0.009-0.026-0.014-0.038c-0.007-0.014-0.027-0.015-0.038-0.025c-0.008-0.008-0.013-0.02-0.022-0.028
+ c-0.017-0.015-0.038-0.031-0.06-0.037c-0.009-0.003-0.013-0.003-0.019-0.012c-0.003-0.005-0.004-0.015-0.012-0.018
+ c-0.012-0.005-0.023,0.017-0.034,0c-0.003-0.006-0.004-0.014-0.007-0.02c-0.003-0.004-0.008-0.01-0.012-0.014
+ c-0.011-0.009-0.025-0.017-0.037-0.023c-0.021-0.011-0.047-0.007-0.067-0.016c-0.014-0.006-0.033-0.01-0.047-0.015
+ c-0.014-0.004-0.02-0.01-0.031-0.021c-0.01-0.011-0.025-0.019-0.034-0.031c-0.01-0.015-0.017-0.028-0.033-0.038
+ c-0.015-0.01-0.031-0.02-0.046-0.03c-0.004-0.002-0.005,0-0.008-0.004c-0.003-0.003-0.003-0.008-0.006-0.012
+ c-0.004-0.007-0.011-0.012-0.019-0.015c-0.011-0.003-0.02-0.009-0.027-0.018c-0.004-0.008-0.01-0.012-0.015-0.019
+ c-0.01-0.015-0.016-0.031-0.026-0.046c-0.009-0.012-0.018-0.022-0.028-0.033c-0.005-0.005-0.011-0.009-0.018-0.011
+ c-0.008-0.004-0.016-0.008-0.024-0.012c-0.01-0.005-0.021-0.008-0.032-0.016c-0.008-0.006-0.015-0.012-0.023-0.018
+ c-0.015-0.01-0.029-0.021-0.041-0.034c-0.009-0.01-0.015-0.017-0.027-0.023c-0.007-0.003-0.015-0.003-0.023-0.005
+ c-0.011-0.002-0.021-0.006-0.031-0.01c-0.01,0.004-0.02,0.008-0.031,0.009c-0.01,0.002-0.015,0.001-0.024,0.008
+ c-0.01,0.008-0.019,0.022-0.022,0.035c-0.004,0.019,0.006,0.031,0.012,0.048c0.013,0.034,0.016,0.069-0.027,0.074
+ c0,0.01-0.009,0.017-0.011,0.025c-0.002,0.01,0.001,0.022,0,0.032c-0.001,0.022-0.028,0.033-0.047,0.037
+ c-0.007,0.002-0.016,0.001-0.023,0c-0.007,0-0.011-0.003-0.016-0.004c-0.004-0.001-0.01,0-0.014-0.001
+ c-0.005-0.001-0.006-0.005-0.01-0.006c-0.005-0.001-0.01,0.001-0.015-0.001c-0.002,0-0.008-0.004-0.011-0.005
+ c-0.009-0.004-0.017-0.01-0.026-0.016c-0.009-0.005-0.018-0.011-0.028-0.016c-0.005-0.002-0.009-0.005-0.014-0.009
+ c-0.003-0.002-0.008-0.004-0.011-0.006c-0.016-0.012-0.025-0.032-0.036-0.048c-0.018-0.024-0.04-0.013-0.065-0.009
+ c-0.005,0.001-0.01-0.001-0.016,0c-0.005,0.001-0.009,0.004-0.015,0.006c-0.008,0.001-0.017-0.002-0.025-0.001
+ c-0.01,0.001-0.018,0.005-0.027,0.006c-0.018,0.001-0.036-0.003-0.053,0.001c-0.017,0.003-0.033,0.007-0.05,0.009
+ c-0.006,0.001-0.012,0-0.018,0.001c-0.004,0.001-0.006,0.004-0.012,0.005c-0.005,0-0.011-0.002-0.016-0.001
+ c-0.005,0.001-0.005,0.005-0.01,0.006c-0.007,0.001-0.014-0.002-0.021-0.001c-0.006,0.002-0.01,0.005-0.016,0.006
+ c-0.011,0.002-0.02,0.003-0.032,0.006c-0.012,0.004-0.018,0.004-0.031,0.003c-0.019-0.001-0.032,0.006-0.048,0.016
+ c-0.022,0.014-0.04,0.037-0.068,0.038c-0.011,0-0.016-0.004-0.026-0.005c-0.012-0.002-0.021,0-0.032,0.004
+ c-0.014,0.004-0.028,0.007-0.042,0.01c-0.012,0.003-0.025,0.006-0.037,0.007c-0.028,0.002-0.055,0.009-0.082,0.016
+ c-0.022,0.005-0.044,0.01-0.065,0.014c-0.015,0.003-0.029,0.009-0.043,0.012c-0.024,0.005-0.054,0-0.078,0
+ c-0.021,0-0.04,0-0.054-0.015c-0.01-0.009-0.02-0.026-0.024-0.039c-0.002-0.006-0.002-0.01-0.005-0.016
+ c-0.002-0.003-0.006-0.006-0.008-0.009c-0.007-0.009-0.011-0.021-0.016-0.032c-0.011-0.022-0.012-0.039,0.007-0.058
+ c0.009-0.009,0.017-0.014,0.022-0.026c0.004-0.008,0.006-0.017,0.01-0.025c0.009-0.017,0.018-0.033,0.03-0.05
+ c0.019-0.03-0.027-0.017-0.041-0.014c-0.021,0.004-0.037-0.007-0.046-0.023c-0.009-0.015-0.019-0.029-0.032-0.042
+ c-0.007-0.007-0.014-0.01-0.021-0.016c-0.007-0.005-0.011-0.013-0.018-0.018c-0.019-0.013-0.038-0.025-0.057-0.038
+ c-0.01-0.007-0.016-0.016-0.025-0.023c-0.008-0.006-0.016-0.012-0.024-0.018c-0.013-0.01-0.024-0.021-0.036-0.033
+ c-0.017-0.015-0.036-0.023-0.056-0.033c-0.018-0.009-0.035-0.024-0.054-0.03c-0.024-0.008-0.047-0.018-0.068-0.033
+ c-0.005-0.003-0.01-0.006-0.015-0.01c-0.009,0.006-0.018,0.012-0.026,0.017c-0.013,0.007-0.019,0.007-0.033,0.01
+ c-0.012,0.002-0.019,0.005-0.031,0.005c-0.018,0-0.036,0.002-0.053,0.001c-0.021-0.002-0.038-0.008-0.059-0.005
+ c-0.024,0.004-0.049,0.009-0.073,0.01c-0.023,0-0.046,0-0.068,0c-0.013,0-0.025-0.002-0.038-0.001
+ c-0.014,0.001-0.026,0.006-0.04,0.006c-0.029,0-0.057-0.015-0.086-0.016c-0.01,0-0.021,0-0.031,0c-0.017,0-0.03,0.004-0.046,0.005
+ c-0.02,0.002-0.04,0.009-0.06,0.011c-0.011,0.002-0.024,0-0.036,0h-0.069c-0.01,0-0.021,0-0.031,0c-0.011,0-0.016-0.004-0.027-0.006
+ c-0.012-0.001-0.014,0.001-0.021-0.01c-0.006-0.011-0.003-0.02-0.014-0.028c-0.012-0.009-0.029-0.013-0.043-0.018
+ c-0.019-0.007-0.039-0.012-0.058-0.018c-0.007-0.003-0.015-0.007-0.021-0.008c-0.009-0.003-0.018,0-0.027-0.001
+ c-0.009-0.002-0.017-0.005-0.026-0.006c-0.012-0.001-0.025-0.003-0.036-0.006c-0.014-0.004-0.029-0.009-0.043-0.01
+ c-0.007,0-0.015,0.002-0.021,0.001c-0.014-0.003-0.027-0.011-0.042-0.011c-0.007-0.001-0.014,0.001-0.022,0
+ c-0.008-0.001-0.012-0.004-0.02-0.006c-0.017-0.002-0.032-0.002-0.048-0.009c-0.018-0.007-0.033-0.016-0.053-0.017
+ c-0.021-0.002-0.046,0.005-0.067,0c-0.007-0.001-0.014-0.004-0.021-0.006c-0.005-0.002-0.011-0.003-0.017-0.005
+ c-0.006-0.002-0.01-0.007-0.015-0.01c-0.007-0.003-0.014-0.007-0.021-0.01c-0.026-0.013-0.051-0.024-0.075-0.041
+ c-0.011-0.008-0.021-0.019-0.031-0.029c-0.006-0.006-0.028-0.023-0.016-0.03l0.011-0.011c-0.01-0.002-0.023-0.029-0.017-0.036
+ c0.004-0.004,0.022-0.006,0.027-0.006c0.009-0.001,0.018,0,0.027,0c0.016,0,0.036,0.003,0.052,0
+ c0.008-0.001,0.016-0.008,0.022-0.009c0.009-0.003,0.017-0.001,0.025-0.001c0.022-0.001,0.038-0.017,0.059-0.016
+ c0.022,0,0.032-0.006,0.046-0.023c0.006-0.007,0.011-0.016,0.015-0.025c0.004-0.007,0.011-0.014,0.013-0.021
+ c0.002-0.008-0.002-0.018-0.001-0.026c0.002-0.01,0.006-0.016,0.005-0.026c0-0.004,0-0.008,0.001-0.011
+ c-0.001-0.005-0.004-0.008-0.005-0.012c-0.002-0.01,0-0.016-0.006-0.026c-0.006-0.012-0.014-0.023-0.02-0.036
+ c-0.003-0.008-0.006-0.014-0.01-0.021c-0.009-0.013-0.016-0.027-0.024-0.041c-0.006-0.011-0.017-0.025-0.02-0.038
+ c-0.008-0.032,0.022-0.019,0.041-0.015c0.009,0.001,0.018,0.004,0.028,0.005c0.003,0,0.007,0,0.01,0
+ c0.006,0,0.008,0.003,0.012,0.004c0.032,0.008,0.064,0.018,0.093,0.032c0.006-0.022-0.001-0.041-0.019-0.054
+ c-0.009-0.006-0.019-0.01-0.028-0.015c-0.011-0.007-0.019-0.017-0.026-0.027c-0.014-0.021-0.021-0.047-0.021-0.072
+ c0-0.012-0.004-0.02-0.006-0.031c-0.002-0.014,0.006-0.025,0.006-0.038c-0.001-0.015-0.009-0.033-0.006-0.047
+ c0.002-0.009,0.01-0.016,0.011-0.026c0.001-0.005,0-0.011,0-0.016c0-0.007,0.003-0.009,0.004-0.016c0.001-0.003,0-0.013,0.002-0.015
+ c0.004-0.003,0.015,0,0.02,0c0.015-0.002,0.011-0.008,0.011-0.022V43.87c0-0.023-0.001-0.043,0.026-0.042
+ c0.012,0,0.02,0.001,0.032,0.004c0.011,0.003,0.025,0.001,0.037,0.001s0.024-0.001,0.037,0c0.015,0.001,0.028,0.007,0.043,0.011
+ c0.023,0.006,0.05,0.011,0.071,0.022c0.007,0.004,0.02,0.005,0.023,0.014c0.004,0.013-0.004,0.027-0.006,0.038
+ c-0.002,0.012,0.002,0.024-0.001,0.036c-0.003,0.01-0.004,0.019-0.008,0.028c-0.009,0.019-0.021,0.038-0.002,0.057
+ c0.01,0.009,0.025,0.01,0.036,0.016c0.011,0.006,0.022,0.009,0.033,0.014c0.008,0.003,0.015,0.006,0.021,0.011
+ c0.004,0.003,0.007,0.005,0.011,0.01s0.007,0.011,0.011,0.016c0.011,0.012,0.044,0.036,0.059,0.017
+ c0.01-0.013-0.027-0.049-0.038-0.058c-0.008-0.006-0.014-0.015-0.022-0.02c-0.006-0.005-0.009-0.006-0.015-0.012
+ c-0.006-0.008-0.011-0.015-0.011-0.026c0-0.012,0.006-0.017,0.012-0.026c0.002-0.004,0.007-0.012,0.008-0.016
+ c0.001-0.005,0.004-0.013,0.005-0.017c0.005-0.027-0.005-0.037-0.02-0.057c-0.008-0.01-0.013-0.021-0.021-0.031
+ c-0.005-0.008-0.01-0.012-0.015-0.021c-0.004-0.007-0.006-0.015-0.011-0.022c-0.008-0.01-0.014-0.023-0.022-0.033
+ c-0.01-0.011-0.024-0.021-0.037-0.03c-0.024-0.015-0.04-0.036-0.059-0.056c-0.012-0.015-0.021-0.031-0.034-0.045
+ c-0.007-0.008-0.015-0.019-0.012-0.031c0.003-0.014,0.024-0.024,0.037-0.026c0.008-0.001,0.015,0.001,0.023,0
+ c0.007-0.001,0.013-0.004,0.02-0.005c0.013-0.003,0.028,0.002,0.041,0.004c0.01,0.002,0.022,0,0.032,0.002
+ c0.013,0.004,0.023,0.003,0.037,0.004c0.024,0.002,0.033,0.015,0.049,0.031c0.015,0.014,0.032,0.025,0.051,0.033
+ c0.022,0.008,0.041,0.001,0.063,0.005c0.009,0.001,0.02,0.008,0.028,0.009c0.011,0.003,0.023,0.001,0.036,0.001
+ c0.01,0.001,0.018,0.005,0.026,0.012c0.009,0.009,0.008,0.013,0.011,0.024c0.003,0.007,0.01,0.016,0.014,0.023
+ c0.006,0.01,0.006,0.02,0.013,0.031c0.009,0.015,0.021,0.028,0.03,0.042c0.006,0.009,0.011,0.021,0.017,0.03
+ c0.009,0.013,0.022,0.022,0.032,0.033c0.014,0.016,0.03,0.03,0.046,0.042c0.019,0.013,0.041,0.018,0.058,0.032
+ c0.012,0.009,0.025,0.016,0.037,0.025c0.019,0.014,0.024,0.039,0.042,0.055c0.008,0.007,0.018,0.013,0.025,0.021
+ c0.006,0.008,0.014,0.018,0.023,0.024c0.014,0.009,0.027,0.003,0.042,0.006c0.009,0.002,0.02,0.009,0.03,0.012
+ c0.019,0.008,0.034,0.026,0.054,0.035c0.022,0.01,0.049,0.017,0.073,0.017c0.012-0.001,0.02,0.004,0.032,0.005
+ c0.017,0.001,0.032-0.005,0.049-0.006c0.01,0,0.025-0.001,0.035,0c0.012,0.002,0.019,0.006,0.032,0.006
+ c0.012,0,0.02,0.003,0.031,0.005c0.005,0.002,0.011-0.001,0.016,0c0.009,0.001,0.009,0.002,0.016,0.007
+ c0.007,0.004,0.014,0.01,0.021,0.014c0.008,0.005,0.017,0.007,0.026,0.011c0.011,0.004,0.021,0.003,0.032,0.006
+ c0.013,0.004,0.024,0.012,0.032,0.021c0.003-0.001,0.007-0.003,0.01-0.005c0.024-0.012,0.043-0.023,0.054-0.049
+ c0.009-0.019,0.003-0.044-0.007-0.063c-0.009-0.018-0.038-0.035-0.036-0.058c0.003-0.021,0.03-0.027,0.031-0.047
+ c0.001-0.009,0.002-0.023,0-0.032s-0.011-0.023-0.015-0.031c-0.005-0.008-0.011-0.012-0.015-0.02
+ c-0.004-0.006-0.005-0.014-0.008-0.02c-0.005-0.01-0.009-0.019-0.013-0.029c-0.004-0.008-0.009-0.019-0.011-0.027
+ c-0.001-0.005,0-0.01-0.002-0.015c-0.003-0.006-0.011-0.01-0.016-0.014c-0.017-0.014-0.03-0.026-0.042-0.044
+ c-0.006-0.009-0.013-0.017-0.019-0.026c-0.007-0.011-0.016-0.015-0.028-0.021c-0.019-0.011-0.035-0.021-0.056-0.028
+ c-0.003-0.001-0.009-0.003-0.012-0.004c-0.001,0-0.011-0.004-0.012-0.004c-0.011-0.002-0.023-0.001-0.035-0.001
+ c-0.01,0-0.023,0.003-0.032,0c-0.011-0.002-0.013-0.016-0.016-0.026c-0.005-0.021-0.002-0.048-0.022-0.062
+ c-0.018-0.014-0.038-0.02-0.057-0.032c-0.017-0.01-0.03-0.028-0.047-0.039c-0.032-0.022-0.095-0.042-0.095-0.088
+ c0-0.008,0.003-0.014,0.004-0.022c0.002-0.006,0-0.013,0.001-0.019c0.002-0.011,0.007-0.022,0.01-0.033
+ c0.002-0.008,0.002-0.013,0.006-0.021s0.01-0.018,0.015-0.025c0.009-0.016,0.02-0.029,0.032-0.041
+ c-0.009-0.008-0.018-0.015-0.027-0.023c-0.015-0.014-0.039-0.026-0.058-0.034c-0.014-0.006-0.026-0.014-0.041-0.021
+ c-0.017-0.007-0.046-0.021-0.052-0.04c-0.003-0.008-0.005-0.019-0.006-0.027c0-0.012,0.003-0.021-0.004-0.031
+ c-0.006-0.008-0.016-0.014-0.021-0.023c-0.002-0.004-0.002-0.01-0.005-0.015c-0.002-0.004-0.005-0.005-0.007-0.009
+ c-0.004-0.015-0.002-0.044,0-0.059c0.003-0.016,0.011-0.03,0.011-0.047c0-0.021-0.001-0.036-0.009-0.054
+ c-0.009-0.018-0.015-0.038-0.023-0.056c-0.011-0.024-0.029-0.045-0.047-0.064c-0.03-0.032-0.062-0.064-0.088-0.1
+ c-0.012-0.016-0.026-0.032-0.037-0.048c-0.011-0.017-0.019-0.038-0.033-0.052c-0.006-0.006-0.014-0.007-0.02-0.011
+ c-0.006-0.005-0.013-0.014-0.018-0.02c-0.007-0.01-0.024-0.035-0.019-0.047c0.007,0.003,0.018,0.008,0.026,0.005
+ c0.011-0.005,0.003-0.014,0-0.021c-0.002-0.004-0.003-0.009-0.004-0.012c-0.002-0.005-0.005-0.011-0.008-0.015
+ c-0.006-0.01-0.013-0.023-0.021-0.031c-0.013-0.014-0.025-0.027-0.034-0.043c-0.003-0.004-0.013-0.015-0.013-0.021
+ c0.001-0.009,0.015-0.012,0.022-0.016c0.011-0.006,0.03-0.02,0.025-0.035c-0.002-0.006-0.011-0.016-0.016-0.021
+ c-0.008-0.008-0.023-0.008-0.029-0.018c-0.004-0.007-0.004-0.018-0.007-0.025c-0.004-0.012-0.005-0.02-0.005-0.032
+ c0-0.01-0.002-0.019,0.004-0.028s0.014-0.011,0.022-0.015c0.014-0.007,0.036-0.02,0.02-0.035c-0.012-0.012-0.031-0.015-0.046-0.022
+ c-0.019-0.009-0.042-0.015-0.063-0.021c-0.014-0.004-0.029-0.004-0.042-0.007c-0.013-0.002-0.027-0.01-0.038-0.013
+ c-0.019-0.005-0.04,0.001-0.058-0.005c-0.009-0.003-0.018-0.007-0.026-0.011c-0.013-0.005-0.023-0.005-0.036-0.007
+ c-0.012-0.003-0.025-0.008-0.037-0.011c-0.01-0.003-0.021-0.007-0.032-0.01c-0.01-0.002-0.02-0.005-0.031-0.005
+ c-0.013,0.001-0.024-0.004-0.037-0.005c-0.007,0-0.025-0.003-0.031-0.006c-0.011-0.008-0.006-0.014,0.005-0.015
+ c0.01-0.001,0.02,0.002,0.03-0.001c0.007-0.002,0.013-0.01,0.022-0.011c-0.001-0.005-0.019-0.015-0.025-0.018
+ c-0.011-0.007-0.02-0.007-0.032-0.007c-0.011,0-0.022,0-0.032,0c-0.005,0-0.011,0.001-0.016,0c-0.008,0-0.013-0.004-0.021-0.005
+ c-0.011-0.002-0.021-0.003-0.032-0.005c-0.005-0.002-0.009-0.004-0.015-0.006c-0.009-0.002-0.02,0-0.03-0.003
+ c-0.014-0.005-0.024-0.006-0.039-0.007c-0.013-0.001-0.023-0.006-0.037-0.005c-0.011,0-0.02-0.004-0.031-0.006
+ c-0.012-0.001-0.025-0.003-0.036-0.006c-0.009-0.003-0.017-0.008-0.027-0.009c-0.007-0.001-0.01-0.003-0.016-0.005
+ c-0.008-0.001-0.014,0-0.021-0.001c-0.011-0.001-0.016-0.005-0.027-0.005s-0.021,0.001-0.03-0.007c-0.01-0.008-0.006-0.013,0-0.023
+ c0.006-0.01,0.009-0.016,0.009-0.028c0-0.022,0.001-0.042-0.004-0.063c-0.001-0.006,0-0.01-0.001-0.016s-0.004-0.01-0.005-0.016
+ c-0.002-0.009,0.002-0.02,0.011-0.025c0.005-0.003,0.01,0,0.015-0.001c0.006-0.001,0.01-0.005,0.016-0.006
+ c0.011-0.002,0.02,0,0.031-0.005c0.011-0.004,0.023-0.006,0.033-0.011c0.012-0.006,0.032-0.01,0.041-0.019
+ c0.02-0.018-0.012-0.029-0.022-0.042c-0.019-0.022-0.022-0.037-0.016-0.065c0.002-0.01,0.009-0.02,0.006-0.031
+ c-0.003-0.007-0.007-0.014-0.011-0.021c-0.716-0.167-1.463-0.256-2.23-0.256c-1.483,0-2.888,0.331-4.147,0.923
+ c-0.001,0.002-0.003,0.003-0.004,0.004c-0.011,0.007-0.019,0.017-0.021,0.029c-0.001,0.003-0.001,0.007-0.001,0.011
+ c0.689-0.188,1.378-0.376,2.067-0.564c0.028,0.121,0.055,0.242,0.083,0.363c-0.41,0.599-0.395,1.249-1.229,1.798
+ c-0.743,0.488-0.665,0.181-0.862,0.139c0,0,0,0,0,0.001c0.002,0.009,0.006,0.021,0.003,0.031c-0.002,0.006-0.008,0.015-0.012,0.021
+ c-0.004,0.007-0.01,0.015-0.014,0.022c-0.003,0.006-0.006,0.009-0.01,0.014c-0.001,0.001-0.047,0.052-0.11,0.123
+ c-0.002,0.008-0.005,0.017-0.007,0.025c0.052-0.057,0.09-0.098,0.089-0.095c-0.008,0.019-0.015,0.038-0.025,0.057
+ c-0.011,0.021-0.027,0.038-0.044,0.055c-0.011,0.01-0.022,0.021-0.032,0.031c-0.161,0.768,0.008,1.628-0.821,2.21
+ c-0.58,0.408-0.637,0.248-0.658,0.049c-0.037,0.163-0.024,0.083-0.004-0.031c-0.01-0.112-0.018-0.228-0.106-0.255
+ c-0.007-0.017-0.014-0.034-0.021-0.051c-0.001,0-0.002,0-0.003,0c-0.004,0-0.007,0.003-0.011,0.004
+ c-0.006,0.001-0.012-0.001-0.018,0c-0.009,0.003-0.017,0.003-0.025,0.006c-0.008,0.002-0.014,0.005-0.022,0.009
+ c-0.017,0.008-0.028-0.011-0.028-0.026c0-0.007,0-0.015,0-0.022c0-0.009,0.002-0.011,0.007-0.018
+ c0.003-0.005,0.005-0.011,0.004-0.018c-0.001-0.003-0.004-0.003-0.004-0.006c-0.001-0.005,0.002-0.009-0.003-0.012
+ c0.004-0.006,0.006-0.014,0.009-0.021c0.004-0.007,0.005-0.019,0.011-0.025c-0.016,0.001-0.03,0.01-0.043,0.018
+ c-0.008,0.005-0.015,0.009-0.024,0.012c-0.01,0.002-0.018,0.007-0.028,0.01c-0.023,0.009-0.044,0.022-0.066,0.032
+ c-0.016,0.006-0.03,0.011-0.046,0.018c-0.018,0.01-0.036,0.019-0.053,0.029c-0.016,0.008-0.028,0.024-0.047,0.024
+ c-0.012,0-0.02-0.003-0.031-0.008c-0.01-0.004-0.019-0.006-0.028-0.013c0,0,0.001,0,0.002,0h0.007
+ c-0.006-0.001-0.014-0.007-0.018-0.011c-0.006-0.006-0.002-0.014,0.001-0.021c0.01-0.023,0.03-0.038,0.045-0.057
+ c0.005-0.006,0.009-0.013,0.014-0.019c0.005-0.008,0.012-0.011,0.018-0.018c0.004-0.004,0.008-0.008,0.012-0.013
+ c0.006-0.006,0.014-0.007,0.02-0.012c0.014-0.011,0.025-0.022,0.036-0.035c0.008-0.01,0.017-0.017,0.025-0.026
+ c0.005-0.007,0.013-0.011,0.018-0.017c0.003-0.005,0.007-0.01,0.009-0.014c0.002-0.005,0.001-0.008,0.003-0.012
+ c0-0.003,0.003-0.003,0.003-0.006c0.001-0.004-0.002-0.007,0.001-0.011c0,0,0.006-0.002,0.007-0.003
+ c0.005-0.003,0.012-0.008,0.016-0.012c0.008-0.009,0.014-0.021,0.019-0.032c0.003-0.006,0.004-0.013,0.006-0.018
+ c0.004-0.008,0.01-0.014,0.013-0.021c0.006-0.015,0.013-0.03,0.026-0.041c-0.101-0.249-0.203-0.497-0.304-0.746
+ c0,0-0.001,0-0.001-0.001c-0.014-0.013-0.029-0.014-0.046-0.004c-0.017,0.012-0.022-0.004-0.038-0.007
+ c-0.008-0.001-0.014,0-0.021,0.004c-0.002,0.002-0.004,0.004-0.006,0.005c-0.002,0.001-0.007,0.003-0.009,0.004
+ c-0.006,0.006-0.007,0.015-0.015,0.02c-0.009,0.006-0.01,0.002-0.017-0.002c-0.016-0.008-0.031,0.007-0.042,0.017
+ c-0.009,0.008-0.018,0.016-0.03,0.016c-0.01,0.001-0.012,0-0.019,0.005c-0.004,0.003-0.012,0.011-0.017,0.012
+ c-0.003,0-0.019-0.005-0.021-0.008c-0.008,0.003-0.016,0.007-0.024,0.01c-0.003,0.001-0.008,0.005-0.011,0.005
+ c-0.006,0.001-0.007-0.003-0.012-0.003c-0.005,0-0.014,0.008-0.019,0.01c-0.005,0.003-0.011,0.006-0.017,0.008
+ s-0.021,0.003-0.024,0.008c-0.001-0.01,0.005-0.015,0.009-0.023c0.005-0.009,0.007-0.019,0.012-0.028
+ c0.005-0.009,0.008-0.019,0.012-0.028c0.002-0.006,0.005-0.013,0.007-0.019c0.002-0.005,0.005-0.009,0.008-0.013
+ c0.001-0.004,0-0.008,0.001-0.012c0.003-0.011,0.011-0.02,0.014-0.031c0.003-0.01,0.007-0.019,0.009-0.028
+ c0.001-0.005,0.003-0.012,0.004-0.017c0.001-0.003,0.004-0.004,0.004-0.007c0.001-0.008-0.005-0.009-0.003-0.018
+ c0.001-0.006,0.008-0.012,0.007-0.018c-0.008-0.003-0.011,0.009-0.019,0.007c-0.003-0.008,0.006-0.022,0.008-0.03
+ c0.002-0.008,0.007-0.016,0.01-0.024c-0.011-0.005-0.014,0.007-0.023,0.007c-0.005,0.001-0.013-0.007-0.012-0.012
+ c0-0.003,0.004-0.006,0.005-0.008c0.005-0.008,0.014-0.015,0.018-0.024c0.004-0.009,0.006-0.019,0.01-0.028
+ c0.003-0.006,0.008-0.01,0.01-0.016c0.002-0.004,0.002-0.008,0.004-0.012c0.004-0.007,0.011-0.015,0.017-0.021
+ c0.012-0.011,0.026-0.024,0.04-0.034c0.01-0.008,0.017-0.012,0.021-0.025c0.003-0.009,0.004-0.019,0.007-0.028
+ c0.005-0.017,0.019-0.032,0.028-0.047c0-0.001,0.001-0.002,0.001-0.002c-0.01-0.024-0.019-0.048-0.029-0.071
+ c-0.012,0.003-0.024,0.007-0.036,0.009c-0.033,0.003-0.06,0.027-0.089,0.043c-0.021,0.011-0.04,0.022-0.058,0.038
+ c-0.009,0.007-0.016,0.016-0.025,0.022c-0.009,0.006-0.016,0.013-0.024,0.02c-0.007,0.005-0.012,0.013-0.019,0.021
+ c-0.009,0.01-0.018,0.019-0.029,0.027c-0.005,0.004-0.01,0.01-0.015,0.013c-0.008,0.005-0.018,0.01-0.025,0.016
+ c-0.004,0.004-0.002,0.006-0.004,0.011c-0.002,0.006-0.01,0.012-0.014,0.016c-0.012,0.014-0.026,0.022-0.041,0.032
+ c-0.006,0.004-0.017,0.009-0.021,0.015c-0.002,0.002-0.002,0.006-0.004,0.008c-0.003,0.003-0.007,0.004-0.01,0.006
+ c-0.011,0.008-0.015,0.022-0.025,0.031c-0.006,0.005-0.012,0.009-0.016,0.016c0.001-0.002-0.003,0.01-0.003,0.009
+ c-0.001,0.003,0,0.007-0.001,0.011c-0.002,0.012-0.013,0.03-0.023,0.037c-0.006,0.005-0.01,0.005-0.014,0.014
+ c-0.004,0.009-0.002,0.019-0.01,0.028c-0.008,0.01-0.016,0.019-0.025,0.029c-0.004,0.006-0.008,0.013-0.015,0.015
+ c-0.002-0.004,0-0.008,0.002-0.011c-0.007,0.003-0.007,0.012-0.015,0.017c-0.006,0.003-0.015,0.006-0.02,0.011
+ c-0.004,0.005-0.007,0.017,0,0.02c0.009,0.003,0.012-0.009,0.02-0.001c0.002,0.003-0.001,0.007,0.003,0.01
+ c0.005,0.003,0.003,0.001,0.009-0.001c0.006-0.001,0.011-0.008,0.016-0.009c0.008-0.001,0.011,0.008,0.016,0.011
+ c0.004,0.004,0.009,0.006,0.013,0.008c0.005,0.003,0.004,0.002,0.007,0.005c0.001,0.001,0.004,0.005,0.005,0.007
+ c0.003,0.004,0.007,0.007,0.01,0.01c0.005,0.006,0.006,0.009,0.013,0.014c0.003,0.002,0.01,0.007,0.012,0.009
+ c0.006,0.007,0.003,0.008,0,0.016c-0.001,0.003-0.001,0.006-0.003,0.009c-0.001,0.001-0.003,0.003-0.004,0.004
+ c-0.002,0.002-0.002,0.006-0.004,0.008c-0.001,0.001-0.003,0.003-0.004,0.004c-0.002,0.003-0.006,0.007-0.005,0.011
+ c0,0.002,0.001,0.004,0.003,0.005c0.003,0.014,0.009,0.025,0.009,0.039c0,0.015,0,0.028-0.015,0.037
+ c-0.006,0.004-0.013,0.002-0.02,0.004c-0.004,0.001-0.008,0.006-0.013,0.007c-0.008,0.002-0.012-0.001-0.02,0.004
+ c-0.007,0.004-0.011,0.004-0.02,0.004c-0.008,0-0.013,0-0.02-0.004c-0.007-0.004-0.008-0.005-0.016,0
+ c-0.012,0.007-0.022,0.015-0.035,0.021c-0.013,0.005-0.024,0.009-0.037,0.015c-0.004,0.002-0.008,0.004-0.012,0.007
+ c-0.008,0.004-0.01,0.007-0.015,0.013c-0.006,0.006-0.015,0.009-0.021,0.015c-0.006,0.007-0.009,0.016-0.015,0.022
+ c-0.012,0.01-0.029,0.006-0.039,0.016c-0.011,0.01-0.018,0.023-0.03,0.033c-0.005,0.005-0.01,0.006-0.015,0.009
+ c-0.005,0.004-0.008,0.007-0.013,0.012c-0.009,0.01-0.015,0.009-0.027,0.013c-0.006,0.002-0.01,0.008-0.015,0.012
+ c-0.012,0.012-0.02,0.025-0.03,0.038c-0.01,0.013-0.019,0.024-0.028,0.037c-0.004,0.007-0.006,0.011-0.007,0.019
+ c-0.001,0.003-0.003,0.005-0.004,0.009c0,0.004,0.001,0.008,0,0.012c-0.001,0.006-0.004,0.01-0.004,0.016
+ c0,0.004,0.001,0.007-0.001,0.011c-0.003,0.006-0.009,0.007-0.015,0.01c-0.016,0.007-0.024,0.01-0.032,0.027
+ c-0.003,0.008-0.007,0.016-0.012,0.024c-0.005,0.007-0.01,0.009-0.017,0.015c-0.006,0.005-0.007,0.011-0.011,0.017
+ c-0.004,0.004-0.01,0.008-0.014,0.013c-0.008,0.008-0.011,0.019-0.014,0.03c0.157,0.008,0.315,0.015,0.472,0.023
+ c0.297,0.375,0.594,0.75,0.891,1.125c-0.013,0.092-0.025,0.183-0.038,0.275c-0.505,0.161-1.213-0.164-1.514,0.484
+ c-0.301,0.649-0.023,0.006-0.022,0.016c0,0.005,0.006,0.009,0.007,0.013c0.002,0.005,0.001,0.01,0.001,0.015
+ c0,0.013-0.003,0.029,0.005,0.039c0.007,0.008,0.013,0.016,0.019,0.025c0.007,0.01,0.613,0.002,0.019,0.032
+ c-0.594,0.03,0.013,0.021,0.002,0.036c-0.005,0.007-0.012,0.012-0.016,0.02c-0.004,0.006-0.004,0.013-0.006,0.02
+ c-0.004,0.016-0.008,0.03-0.007,0.048c0.001,0.015,0.019,0.04-0.004,0.047c-0.008,0.002-0.016-0.001-0.024,0.003
+ c-0.007,0.004-0.012,0.008-0.02,0.01c-0.008,0.001-0.007-0.001-0.012,0.003c-0.001,0.001-0.003,0.006-0.005,0.008
+ c-0.003,0.004-0.008,0.007-0.012,0.012c-0.008,0.008-0.016,0.019-0.026,0.024c-0.01,0.006-0.019,0.002-0.029,0.004
+ c-0.007,0.002-0.01,0.005-0.02,0.004c-0.017-0.001-0.025-0.013-0.031-0.027c-0.008-0.018-0.012-0.033-0.025-0.047
+ c-0.004-0.004-0.009-0.009-0.01-0.014c-0.002-0.008-0.001-0.013-0.005-0.019c-0.008-0.013-0.02-0.019-0.032-0.027
+ c-0.013-0.009-0.025-0.019-0.04-0.024c-0.01-0.004-0.019-0.007-0.028-0.012s-0.015-0.004-0.024-0.006
+ c-0.015-0.004-0.036-0.006-0.052-0.003c-0.025,0.003-0.051,0.012-0.075,0.019c-0.012,0.003-0.037,0-0.037-0.015
+ c-0.01-0.001-0.016-0.004-0.028-0.004c-0.01,0-0.017-0.004-0.027-0.004c-0.011,0-0.018-0.004-0.028-0.004c-0.002,0-0.004,0-0.006,0
+ c-0.039,0.012-0.077,0.025-0.116,0.037c-0.003,0.006-0.005,0.014-0.009,0.02c-0.004,0.007-0.011,0.016-0.018,0.022
+ c-0.009,0.007-0.021,0.006-0.031,0.009c-0.008,0.003-0.006,0.005-0.008,0.012c-0.004,0.01-0.013,0.02-0.019,0.028
+ c-0.007,0.009-0.012,0.018-0.018,0.028c-0.002,0.003-0.004,0.007-0.006,0.011l0,0c0.004,0.006,0.007,0.014,0.006,0.02
+ c0,0.007-0.003,0.016-0.006,0.021c-0.004,0.008-0.01,0.012-0.016,0.019c-0.005,0.007-0.006,0.011-0.013,0.016
+ c-0.004,0.003-0.008,0.008-0.012,0.011c-0.005,0.003-0.012,0.004-0.015,0.01c-0.003,0.007,0.002,0.016-0.001,0.023
+ c-0.002,0.005-0.009,0.007-0.011,0.012c-0.002,0.005,0.001,0.011,0,0.016c-0.002,0.007-0.005,0.014-0.008,0.02
+ c-0.004,0.007-0.005,0.011-0.004,0.02c0,0.017,0.003,0.036-0.005,0.052c-0.006,0.014-0.01,0.029-0.015,0.044
+ c-0.006,0.015-0.013,0.028-0.017,0.043c-0.001,0.003-0.003,0.004-0.003,0.008c-0.001,0.004,0,0.009,0,0.013
+ c-0.001,0.003-0.003,0.006-0.004,0.008c0,0.004,0,0.008,0,0.011c-0.002,0.009-0.004,0.015-0.004,0.024v0.028
+ c0,0.02-0.003,0.036-0.009,0.055c-0.006,0.017-0.008,0.035-0.011,0.053c-0.002,0.008-0.006,0.015-0.004,0.024
+ c0.001,0.009,0.005,0.013,0.003,0.023c-0.001,0.002-0.003,0.006-0.003,0.009c-0.001,0.003,0,0.014-0.005,0.015
+ c-0.004,0.002-0.007-0.003-0.009-0.006c-0.002-0.006,0.001-0.006,0.002-0.013l-0.008,0.008c0.001-0.006-0.002-0.005-0.003-0.009
+ c-0.001-0.004-0.001-0.007-0.001-0.011c-0.001-0.007-0.004-0.015-0.007-0.021c-0.005-0.01-0.009-0.012-0.009-0.023
+ s-0.003-0.012-0.009-0.02c-0.005-0.007-0.008-0.011-0.006-0.019c0-0.003,0.008-0.02,0.002-0.024c-0.008-0.005-0.01,0.01-0.018,0.007
+ c-0.008-0.004-0.005-0.017-0.005-0.024c0-0.008,0-0.016,0-0.024c0-0.009,0.004-0.014,0.004-0.024c0-0.009,0.001-0.011,0.005-0.019
+ c0.004-0.007,0.003-0.015,0.003-0.025c0-0.008-0.005-0.012-0.004-0.019c0-0.009,0.007-0.016,0.008-0.024
+ c0.001-0.009-0.002-0.02,0-0.029c0.001-0.003,0.004-0.004,0.004-0.007c0.001-0.005-0.002-0.007-0.003-0.011
+ c0.003-0.002,0.005-0.011,0.006-0.014c0.001-0.006,0.002-0.013,0-0.019c-0.013,0.006-0.031,0.009-0.04,0.023
+ c-0.006,0.007-0.009,0.015-0.011,0.025c-0.002,0.011-0.01,0.02-0.012,0.032c-0.002,0.009-0.001,0.017-0.005,0.027
+ c-0.004,0.009-0.013,0.013-0.019,0.02c-0.005,0.006-0.003,0.01-0.005,0.017c-0.001,0.004-0.005,0.007-0.008,0.01
+ c-0.006,0.007-0.011,0.012-0.014,0.02c0.009,0.005-0.003,0.022-0.009,0.026c-0.005,0.004-0.009,0.005-0.012,0.011
+ c-0.002,0.005-0.002,0.011-0.005,0.016c-0.007,0.012-0.014,0.024-0.019,0.037c-0.005,0.014-0.007,0.029-0.013,0.043
+ c-0.003,0.008,0.002,0.008,0.002,0.016c0,0.011-0.011,0.008-0.016,0.013c-0.008,0.007-0.007,0.017-0.016,0.023
+ c-0.01,0.005-0.026,0.003-0.032-0.008c-0.003-0.005,0-0.015-0.001-0.02c0-0.009-0.002-0.015-0.005-0.023
+ c-0.002-0.006-0.002-0.011-0.003-0.017c-0.002-0.007-0.007-0.013-0.008-0.02c-0.001-0.008,0-0.012-0.004-0.02
+ c-0.003-0.004-0.01-0.012-0.011-0.017c-0.001-0.012,0.008-0.022,0.008-0.035c-0.001-0.014-0.008-0.024-0.011-0.037
+ c-0.003-0.01-0.002-0.025,0.006-0.032c0.008-0.006,0.026-0.005,0.028-0.018c0.001-0.007-0.006-0.015-0.009-0.02
+ c-0.003-0.008-0.001-0.013-0.002-0.021c-0.001-0.007-0.007-0.007,0.001-0.015c0.004-0.005,0.007-0.004,0.014-0.005
+ c0.002-0.008,0.001-0.012-0.005-0.018c-0.001-0.001-0.006-0.005-0.006-0.006c0-0.004,0.004-0.003,0.005-0.006
+ c0.001-0.005,0.002-0.006,0.003-0.012c0-0.004-0.002-0.01-0.001-0.013c0.002-0.007,0.009-0.013,0.013-0.019
+ c-0.005,0.004-0.011,0.013-0.014,0.019c-0.006,0.01-0.011,0.019-0.014,0.031c-0.001,0.01,0.005,0.03-0.008,0.033
+ c-0.009,0.002-0.019-0.002-0.028,0c0.007,0.007-0.007,0.017-0.012,0.024c-0.005,0.008-0.009,0.017-0.015,0.025
+ c-0.008,0.011-0.023,0.021-0.03,0.033c-0.004,0.007-0.006,0.026-0.018,0.026c0,0.014-0.035,0.019-0.044,0.027
+ c-0.008,0.007-0.011,0.016-0.021,0.02c-0.005,0.003-0.019,0.006-0.016,0.014c-0.009,0.007-0.013,0.022-0.019,0.032
+ c-0.01,0.018-0.017,0.041-0.03,0.057c-0.002,0.004-0.005,0.005-0.007,0.009c-0.002,0.003-0.003,0.007-0.005,0.011
+ c-0.008,0.013-0.02,0.016-0.034,0.021c-0.012,0.005-0.026,0.002-0.038,0.007c-0.009,0.004-0.012,0.01-0.018,0.017
+ c-0.004,0.004-0.005,0.005-0.013,0.004c-0.005-0.001-0.008-0.001-0.008-0.007c-0.198,0.776-0.304,1.59-0.304,2.428
+ c0,5.385,4.365,9.75,9.75,9.75s9.75-4.365,9.75-9.75C-15.25,49.098-15.794,47.324-16.736,45.824z M-17.781,55.174
+ c0.006,0.002,0.007,0.007,0.004,0.011c-0.003,0.005-0.005,0.009-0.007,0.014c-0.008,0.015-0.022,0.028-0.018,0.048
+ c0.002,0.015,0.013-0.002,0.018-0.007c0.008-0.008,0.014-0.017,0.023-0.024c0.008-0.006,0.018-0.008,0.027-0.015
+ c0.007-0.005,0.014-0.01,0.02-0.016c0.007-0.005,0.012-0.011,0.019-0.016c0.007-0.005,0.014-0.01,0.021-0.016
+ c0.005-0.005,0.009-0.01,0.013-0.016c0.004-0.004,0.008-0.006,0.012-0.011c0.007-0.009,0.014-0.018,0.026-0.02
+ c0.008-0.002,0.016-0.002,0.023-0.003c0.016-0.003,0.031-0.01,0.044-0.02c0.004-0.003,0.008-0.008,0.013-0.011
+ c0.006-0.003,0.011-0.005,0.016-0.009c0.006-0.004,0.013-0.009,0.02-0.011c0.004-0.001,0.01-0.002,0.014-0.004
+ c0.005-0.002,0.008-0.008,0.011-0.013c0.009-0.014,0.027-0.02,0.041-0.029c0.009-0.006,0.017-0.015,0.023-0.024
+ c0.006-0.008,0.011-0.017,0.019-0.023c0.017-0.015,0.034-0.03,0.054-0.042c0.007-0.004,0.015-0.011,0.02-0.019
+ c0.004-0.008,0.006-0.017,0.009-0.025c0.007-0.016,0.017-0.033,0.029-0.045c0.016-0.016,0.036-0.027,0.054-0.04
+ c0.005-0.005,0.011-0.008,0.017-0.012c0.005-0.004,0.008-0.009,0.012-0.013c0.01-0.01,0.013-0.024,0.02-0.036
+ c0.006-0.01,0.012-0.02,0.02-0.029c0.005-0.005,0.01-0.012,0.014-0.018c-0.017,0.004-0.027,0.013-0.036,0.026
+ c-0.002,0.002-0.005,0.005-0.007,0.007c-0.003,0.003-0.004,0.007-0.007,0.011c-0.006,0.008-0.015,0.013-0.022,0.02
+ c-0.009,0.009-0.016,0.019-0.024,0.028s-0.016,0.019-0.024,0.029c-0.004,0.004-0.007,0.009-0.012,0.013
+ c-0.004,0.004-0.009,0.007-0.013,0.011c-0.007,0.009-0.017,0.018-0.027,0.023c-0.003,0.002-0.011,0.005-0.015,0.003
+ c-0.003-0.003,0-0.006,0.001-0.009c0.004-0.01,0.008-0.018,0.015-0.025c0.007-0.006,0.013-0.015,0.019-0.022
+ c0.005-0.007,0.012-0.012,0.018-0.018c0.013-0.016,0.031-0.028,0.045-0.044c0.007-0.007,0.013-0.016,0.02-0.024
+ c0.006-0.008,0.013-0.016,0.019-0.024c0.005-0.006,0.009-0.013,0.014-0.02l-0.003,0.007c0.005-0.002,0.012-0.013,0.009-0.018
+ c-0.003,0.001-0.005,0.002-0.009,0.002c-0.004,0-0.005,0.001-0.009,0.004c-0.015,0.011-0.028,0.028-0.045,0.039
+ c-0.009,0.006-0.019,0.012-0.027,0.019c-0.007,0.005-0.012,0.012-0.019,0.018c-0.013,0.01-0.024,0.021-0.036,0.033
+ c-0.005,0.005-0.01,0.01-0.016,0.014c-0.005,0.003-0.008,0.002-0.014,0.002c-0.008,0.001-0.018,0.012-0.023,0.018
+ c-0.01,0.013-0.024,0.023-0.036,0.034c-0.002,0.003-0.007,0.006-0.008,0.01c-0.001,0.004-0.001,0.002,0.002,0.004
+ c0.003,0.004,0.005,0.003,0.004,0.01c0,0.003-0.003,0.013-0.007,0.013c-0.003,0.001-0.004-0.003-0.006-0.005
+ c-0.002-0.001-0.005-0.001-0.007-0.002c-0.002-0.002-0.002-0.004-0.005-0.004s-0.006,0.005-0.007,0.007
+ c-0.004,0.004-0.01,0.006-0.016,0.009c-0.004,0.003-0.013,0.004-0.013,0.011c0,0.005,0.006,0.006,0.005,0.011
+ c-0.001,0.005-0.006,0.005-0.009,0.007c-0.006,0.004-0.011,0.01-0.016,0.014c-0.016,0.015-0.028,0.032-0.043,0.047
+ c-0.006,0.007-0.013,0.012-0.019,0.019c-0.004,0.005-0.008,0.011-0.014,0.015c-0.008,0.007-0.016,0.014-0.027,0.017
+ c-0.007,0.003-0.014,0.005-0.021,0.008c-0.007,0.004-0.011,0.008-0.017,0.013c-0.007,0.007-0.016,0.012-0.023,0.017
+ c-0.016,0.013-0.032,0.027-0.049,0.038c-0.005,0.004-0.008,0.004-0.013,0.007c-0.011,0.005-0.02,0.014-0.03,0.021
+ c-0.015,0.011-0.032,0.02-0.045,0.033c-0.004,0.004-0.013,0.015-0.011,0.021c0.001,0.004,0.006,0.006,0.009,0.005
+ c0.004-0.001,0.009-0.007,0.012-0.01c0.001-0.001,0.006-0.006,0.008-0.003C-17.778,55.171-17.779,55.173-17.781,55.174z
+ M-17.109,54.626c0,0.004-0.001,0.011,0.003,0.014c0.002,0.001,0.01,0,0.013-0.001c0.009-0.002,0.019-0.008,0.025-0.015
+ c0.004-0.005,0.005-0.013,0.009-0.019c0.005-0.007,0.009-0.015,0.013-0.022c0.007-0.011,0.013-0.022,0.02-0.033
+ c0.01-0.015,0.022-0.029,0.03-0.044c0.018-0.032,0.031-0.067,0.051-0.097c0.019-0.029,0.04-0.056,0.059-0.085
+ c0.009-0.015,0.022-0.029,0.03-0.045c0.004-0.01,0.02-0.035,0.011-0.046c-0.01-0.013-0.031,0.024-0.034,0.029
+ c-0.014,0.017-0.026,0.034-0.039,0.051c-0.005,0.005-0.007,0.012-0.012,0.018c-0.002,0.004-0.003,0.007-0.005,0.011
+ c-0.001,0.003-0.004,0.005-0.005,0.008c-0.002,0.003-0.003,0.005-0.005,0.008c-0.003,0.003-0.005,0.006-0.007,0.009
+ c-0.005,0.007-0.01,0.014-0.014,0.021c-0.006,0.01-0.013,0.019-0.018,0.029c-0.005,0.008-0.011,0.015-0.016,0.023
+ c-0.006,0.01-0.015,0.019-0.021,0.029c-0.004,0.01-0.009,0.018-0.011,0.028c-0.008,0.028-0.03,0.05-0.046,0.073
+ c-0.007,0.011-0.017,0.02-0.024,0.031h-0.001c0.001-0.001,0.001-0.003,0.001-0.004l-0.005,0.011
+ c-0.001,0.001-0.001,0.002-0.002,0.002c0,0.001,0,0.002,0,0.003l0.002-0.005c0.001-0.002,0.003-0.004,0.004-0.007
+ C-17.104,54.61-17.108,54.618-17.109,54.626z M-17.08,54.48c0.01-0.013,0.024-0.024,0.032-0.038c0.01-0.017,0.017-0.035,0.025-0.052
+ c0.005-0.01,0.009-0.019,0.012-0.029c0.003-0.009,0.005-0.018,0.009-0.026c0.007-0.014,0.014-0.031,0.026-0.043
+ c0.004-0.004,0.007-0.006,0.01-0.01c0.003-0.005,0.005-0.011,0.009-0.016c0.004-0.006,0.01-0.012,0.014-0.017
+ c0.006-0.008,0.013-0.015,0.018-0.022c0.007-0.009,0.014-0.018,0.021-0.026c0.004-0.007,0.012-0.02,0.011-0.028
+ c-0.002,0.002-0.003,0.003-0.006,0.005c-0.006,0.004-0.016,0.004-0.022,0.009c-0.003,0.003-0.005,0.006-0.008,0.008
+ c-0.007,0.004-0.013,0.011-0.019,0.016c-0.007,0.007-0.012,0.015-0.019,0.021c-0.003,0.003-0.007,0.007-0.01,0.01
+ c-0.008,0.009-0.014,0.019-0.021,0.028c-0.009,0.012-0.02,0.022-0.029,0.034c-0.008,0.008-0.015,0.015-0.023,0.023
+ c-0.007,0.007-0.013,0.016-0.021,0.022c-0.008,0.006-0.015,0.014-0.022,0.021c-0.006,0.007-0.011,0.013-0.018,0.019
+ c-0.006,0.006-0.013,0.012-0.019,0.019c-0.004,0.005-0.009,0.008-0.013,0.014c-0.005,0.006-0.01,0.012-0.016,0.017
+ c-0.007,0.006-0.014,0.011-0.021,0.017c-0.005,0.004-0.008,0.009-0.014,0.012c-0.008,0.004-0.022,0.008-0.026,0.017
+ c-0.003,0.009-0.005,0.017-0.01,0.025c-0.006,0.011-0.011,0.021-0.019,0.031c-0.008,0.01-0.018,0.02-0.026,0.03
+ c-0.009,0.011-0.018,0.022-0.026,0.034c-0.003,0.005-0.005,0.01-0.008,0.014c-0.002,0.003-0.005,0.005-0.006,0.007
+ c-0.002,0.003-0.002,0.007-0.005,0.01c-0.002,0.001-0.005,0.002-0.007,0.004c-0.001,0.001-0.002,0.004-0.004,0.005
+ c-0.005,0.004-0.009,0.008-0.013,0.013c-0.003,0.004-0.007,0.007-0.011,0.011c-0.002,0.004-0.004,0.008-0.008,0.011
+ c-0.005,0.004-0.012,0.004-0.017,0.007c-0.013,0.01-0.02,0.026-0.034,0.035c-0.011,0.008-0.013,0.02-0.023,0.03
+ c-0.006,0.006-0.012,0.01-0.018,0.016c-0.003,0.003-0.012,0.01-0.013,0.015c-0.001,0.005,0.003,0.007,0.003,0.011h-0.001h0.001
+ c0.001,0.001,0.003,0,0.004-0.002c0.002-0.001,0.005-0.003,0.007-0.005c0.005-0.004,0.008-0.005,0.013-0.006
+ c0.003-0.001,0.007-0.003,0.009-0.005c0.005-0.004,0.009-0.007,0.014-0.009c0.022-0.012,0.04-0.031,0.057-0.048
+ c0.014-0.014,0.03-0.026,0.045-0.038c0.021-0.015,0.041-0.033,0.064-0.046c0.023-0.014,0.042-0.035,0.063-0.051
+ c0.02-0.017,0.04-0.034,0.059-0.052C-17.113,54.515-17.094,54.499-17.08,54.48z M-20.326,56.962c0.008,0.008,0.02,0.009,0.031,0.011
+ c0.004,0.001,0.008,0.001,0.011,0.002c0.001-0.007,0.004-0.009,0.006-0.016c0.006-0.022-0.02-0.036-0.037-0.032
+ c-0.004,0.001-0.013,0.003-0.018,0.007c0.001,0.005-0.001,0.012,0.001,0.018c0,0,0.001,0,0.001,0.001h0.031
+ C-20.309,56.958-20.317,56.961-20.326,56.962z M-16.279,52.758c-0.003,0.011-0.009,0.027-0.014,0.037
+ c-0.01,0.02-0.024,0.034-0.038,0.05c-0.01,0.011-0.027,0.034-0.042,0.028c0.006-0.009,0.007-0.018,0.007-0.029
+ c0.001-0.019,0.003-0.036,0.006-0.056c0.002-0.009-0.001-0.018,0-0.027c0-0.009,0.002-0.013,0.004-0.02
+ c0.002-0.008,0-0.015,0.001-0.022c0.001-0.009,0.004-0.014,0.006-0.022c0.005-0.022,0.006-0.045,0.017-0.065
+ c0.004-0.008,0.012-0.024,0.021-0.027c0.015-0.006,0.025,0.015,0.027,0.027c0.006,0.026,0.002,0.055,0.01,0.081
+ c0.003,0.011-0.003,0.017-0.005,0.028C-16.28,52.746-16.278,52.752-16.279,52.758z M-16.376,52.871
+ c0.001,0.001,0.002,0.002,0.003,0.002c-0.001,0.001-0.002,0.002-0.003,0.004V52.871z M-16.881,54.798
+ c-0.002,0.003-0.004,0.007-0.006,0.011c-0.003,0.005-0.008,0.007-0.01,0.011c-0.014,0.019-0.025,0.036-0.041,0.052
+ c-0.009,0.009-0.024,0.02-0.026,0.034v-0.021c0.012-0.01,0.016-0.025,0.021-0.038c0.003-0.007,0.007-0.01,0.01-0.016
+ c0.007-0.012,0.007-0.022,0.017-0.032c0.012-0.013,0.017-0.026,0.025-0.043c0.006-0.014,0.018-0.025,0.024-0.039
+ c0.003-0.007,0-0.01,0.006-0.015c0.011,0.016,0.008,0.045-0.001,0.062C-16.868,54.775-16.875,54.786-16.881,54.798z M-16.936,54.754
+ c-0.019,0.042-0.028,0.087-0.055,0.125c-0.011,0.015-0.021,0.027-0.029,0.044c-0.004,0.009-0.008,0.017-0.011,0.026
+ c-0.003,0.009-0.005,0.019-0.008,0.028c-0.007,0.022-0.015,0.04-0.029,0.059c-0.006,0.009-0.024,0.044-0.037,0.044
+ c0-0.001,0-0.001,0-0.002l-0.012,0.008c0.003-0.021,0.022-0.043,0.032-0.061c0.007-0.013,0.012-0.03,0.017-0.044
+ c0.009-0.02,0.02-0.037,0.029-0.057c0.013-0.027,0.033-0.048,0.042-0.077c0.004-0.011,0.005-0.022,0.01-0.033
+ c0.004-0.011,0.012-0.021,0.016-0.033c0.004-0.01,0.005-0.021,0.008-0.031c0.004-0.012,0.011-0.022,0.015-0.034
+ c0.006-0.017,0.008-0.036,0.015-0.055c0.007-0.017,0.015-0.031,0.025-0.047c0.006-0.011,0.027-0.04,0.031-0.012
+ c0.004,0.026-0.017,0.048-0.026,0.071C-16.914,54.7-16.923,54.727-16.936,54.754z M-17.041,54.825
+ c-0.007,0.028-0.023,0.052-0.033,0.079c-0.007,0.017-0.012,0.036-0.021,0.053c-0.009,0.019-0.023,0.036-0.032,0.056
+ c-0.01,0.018-0.017,0.038-0.028,0.055c-0.009,0.016-0.018,0.03-0.025,0.047c-0.013,0.028-0.033,0.053-0.046,0.08
+ c-0.007,0.018-0.015,0.036-0.022,0.055c-0.008,0.019-0.028,0.035-0.036,0.055c-0.004,0.01-0.006,0.017-0.013,0.025
+ c-0.005,0.004-0.019,0.022-0.021,0.007V55.32c0.001-0.053,0.038-0.097,0.064-0.141c0.006-0.01,0.009-0.019,0.013-0.028
+ c0.005-0.008,0.011-0.013,0.017-0.021c0.006-0.009,0.011-0.018,0.015-0.028c0.005-0.014,0.006-0.027,0.013-0.04
+ c0.005-0.01,0.015-0.017,0.02-0.026c0.004-0.008,0.004-0.013,0.006-0.021c0.004-0.01,0.011-0.023,0.016-0.032
+ c0.007-0.012,0.016-0.021,0.023-0.033c0.008-0.015,0.015-0.03,0.026-0.043c0.015-0.021,0.022-0.041,0.028-0.066
+ c0.002-0.009,0.007-0.018,0.011-0.027c0.004-0.011,0.003-0.026,0.013-0.034c0.009-0.007,0.023-0.007,0.023,0.007
+ c0,0.001-0.004,0.014-0.005,0.016C-17.037,54.81-17.039,54.818-17.041,54.825z M-17.292,55.364c0.006-0.01,0.009-0.02,0.016-0.029
+ c0.007-0.009,0.015-0.016,0.022-0.026c0.01-0.015,0.027-0.024,0.038-0.039c0.007-0.009,0.012-0.022,0.019-0.032
+ c0.005-0.006,0.011-0.01,0.015-0.016c0.005-0.005,0.012-0.012,0.015-0.017c0.01-0.014,0.015-0.03,0.023-0.044
+ c0.012-0.022,0.032-0.037,0.044-0.059c0.014-0.026,0.025-0.05,0.041-0.075c0.008-0.012,0.018-0.021,0.026-0.033
+ c0.007-0.013,0.013-0.036,0.024-0.045c0.012-0.009,0.013-0.001,0.012,0.012c0,0.016-0.007,0.029-0.015,0.043
+ c-0.014,0.027-0.03,0.055-0.043,0.082c-0.007,0.013-0.011,0.026-0.018,0.038c-0.004,0.007-0.008,0.016-0.012,0.023
+ c-0.005,0.006-0.01,0.007-0.014,0.015c-0.013,0.025-0.025,0.045-0.045,0.066c-0.012,0.013-0.019,0.027-0.028,0.042
+ c-0.009,0.015-0.022,0.026-0.032,0.039c-0.015,0.02-0.027,0.041-0.043,0.06c-0.014,0.016-0.026,0.031-0.038,0.049
+ c-0.011,0.016-0.024,0.029-0.032,0.045c-0.008,0.018-0.017,0.036-0.029,0.052c-0.01,0.012-0.021,0.022-0.03,0.035
+ c-0.014,0.017-0.03,0.034-0.041,0.053c-0.008,0.012-0.013,0.025-0.019,0.038c-0.006,0.012-0.014,0.02-0.02,0.032
+ c-0.003,0.007-0.007,0.014-0.01,0.021c-0.004,0.007-0.006,0.017-0.01,0.025v-0.001l-0.011-0.011
+ c0.002-0.001,0.003-0.001,0.005-0.002c0-0.007,0.005-0.018,0.007-0.024c0.005-0.015,0.021-0.028,0.028-0.042
+ c0.011-0.022,0.019-0.044,0.033-0.063c0.004-0.007,0.009-0.015,0.014-0.021c0.006-0.006,0.013-0.01,0.018-0.017
+ c0.013-0.018,0.018-0.042,0.03-0.06c0.013-0.02,0.028-0.036,0.035-0.059C-17.31,55.4-17.303,55.382-17.292,55.364z M-17.472,55.592
+ c-0.009,0.016-0.017,0.031-0.024,0.048c-0.006,0.014-0.015,0.021-0.022,0.034c-0.009,0.016-0.013,0.034-0.019,0.052
+ c-0.006,0.018-0.018,0.034-0.027,0.051c-0.006,0.012-0.015,0.022-0.022,0.033c-0.005,0.007-0.008,0.015-0.014,0.022
+ c-0.004,0.006-0.009,0.01-0.013,0.016c-0.019,0.025-0.037,0.041-0.064,0.055c-0.014,0.006-0.028,0.01-0.043,0.014
+ c-0.024,0.006-0.039,0.02-0.059,0.031c-0.013,0.006-0.026,0.012-0.039,0.018c-0.013,0.006-0.026,0.011-0.039,0.014
+ c-0.009,0.003-0.018,0.008-0.027,0.01c-0.007,0.001-0.015-0.002-0.022-0.001s-0.015,0.005-0.022,0.007
+ C-17.942,56.001-17.955,56-17.971,56l-0.011-0.016c-0.006,0.008-0.024,0.007-0.032,0.006c-0.008-0.002-0.013-0.005-0.021-0.006
+ s-0.014,0.001-0.022-0.001c-0.01-0.002-0.024-0.006-0.03-0.015c-0.004-0.006-0.009-0.026-0.008-0.033
+ c0.001-0.016,0.021-0.039,0.033-0.049c0.008-0.007,0.021-0.009,0.031-0.015c0.011-0.006,0.02-0.013,0.029-0.022
+ c0.006-0.006,0.012-0.011,0.02-0.016c0.004-0.002,0.012-0.008,0.017-0.007c0.005,0,0.005,0.005,0.01,0.006
+ c0.011,0.003,0.024-0.004,0.033-0.01c0.009-0.006,0.016-0.009,0.026-0.013c0.016-0.007,0.036-0.021,0.056-0.016
+ c0.01,0.003,0.015,0.006,0.027,0.006c0.014,0,0.024,0.006,0.038,0.005c0.012,0,0.02,0.004,0.031,0.007
+ c0.007,0.002,0.003,0.004,0.012,0.003c0.006,0,0.01-0.004,0.017-0.004c0.011-0.001,0.019,0.001,0.028-0.004
+ c0.01-0.005,0.018-0.011,0.028-0.017c0.016-0.009,0.032-0.022,0.047-0.035c0.016-0.013,0.026-0.034,0.042-0.049
+ c0.016-0.014,0.025-0.028,0.039-0.044c0.012-0.014,0.019-0.03,0.029-0.047c0.011-0.017,0.013-0.04,0.025-0.056
+ c0.006-0.009,0.036-0.043,0.044-0.042c0.006,0.013-0.008,0.028-0.014,0.038C-17.455,55.567-17.465,55.579-17.472,55.592z
+ M-18.013,56.088c0.011-0.001,0.025,0.002,0.037,0c0.015-0.003,0.03-0.015,0.043-0.022c0.007-0.004,0.011-0.009,0.018-0.002
+ c0.01,0.01,0.001,0.019-0.007,0.026c-0.014,0.012-0.029,0.023-0.043,0.031c-0.008,0.005-0.042,0.033-0.044,0.015l0.002-0.005
+ c-0.016,0.001-0.034-0.006-0.051-0.005c-0.013,0-0.047-0.01-0.044-0.028c0.004-0.018,0.04-0.025,0.054-0.02
+ C-18.034,56.082-18.029,56.088-18.013,56.088z M-18.048,56.206c0.02-0.008,0.045-0.013,0.066-0.01
+ c0.013,0.002,0.012,0.002,0.007,0.012c-0.005,0.01-0.01,0.018-0.018,0.025c-0.015,0.013-0.033,0.012-0.049,0.022
+ c-0.006,0.003-0.009,0.01-0.015,0.014c-0.007,0.004-0.013,0.003-0.022,0.005c-0.001,0.002-0.003,0.004-0.006,0.004
+ c-0.006,0.002-0.011,0-0.017,0.001c-0.003,0.001-0.01,0.002-0.015,0.003l0.004-0.004c-0.015,0.002-0.05-0.02-0.049-0.038
+ c0-0.011,0.014-0.02,0.023-0.024c0.008-0.002,0.016-0.003,0.025-0.004c0.01,0,0.007,0.004,0.014,0.005
+ C-18.08,56.221-18.066,56.213-18.048,56.206z M-18.259,56.168c-0.008,0.003-0.015,0.01-0.022,0.012
+ c-0.009,0.003-0.026,0.002-0.033,0.011l0.006-0.011c-0.005-0.023,0.023-0.051,0.038-0.066c0.006-0.005,0.015-0.011,0.021-0.016
+ c0.011-0.007,0.013-0.016,0.022-0.025c0.007-0.006,0.014-0.007,0.021-0.013c0.006-0.004,0.011-0.012,0.016-0.018
+ c0.006-0.006,0.024-0.03,0.034-0.026c0.023,0.009-0.045,0.111-0.059,0.126C-18.228,56.156-18.242,56.16-18.259,56.168z
+ M-18.196,55.957c-0.007,0.017-0.013,0.039-0.024,0.055c-0.011,0.015-0.027,0.018-0.041,0.03c-0.009,0.009-0.018,0.016-0.027,0.025
+ c-0.011,0.011-0.017,0.025-0.031,0.015l0.005-0.011c-0.001,0-0.003,0-0.005-0.001c0.001-0.01,0.009-0.018,0.015-0.026
+ c0.012-0.018,0.024-0.036,0.038-0.053c0.004-0.004,0.008-0.009,0.013-0.013c0.003-0.004,0.003-0.01,0.006-0.015
+ c0.005-0.007,0.014-0.01,0.019-0.017c0.014-0.018,0.014-0.036,0.035-0.049c0.005-0.003,0.024-0.02,0.031-0.016
+ c0.008,0.007-0.007,0.022-0.009,0.027C-18.178,55.925-18.188,55.94-18.196,55.957z M-18.255,56.79c0.002,0.007,0.003,0.018,0,0.025
+ c-0.003,0.008-0.013,0.011-0.009,0.022c0.02-0.006,0.027-0.032,0.039-0.046c0.005-0.008,0.014-0.012,0.02-0.019
+ c0.012-0.013,0.016-0.033,0.026-0.048c0.011-0.019,0.029-0.036,0.049-0.045c0.008-0.003,0.02-0.005,0.027-0.011
+ c0.004-0.002,0.007-0.01,0.012-0.01c0.008,0.001,0.007,0.012,0.006,0.018c-0.003,0.01-0.008,0.021-0.011,0.032
+ c-0.004,0.009-0.01,0.022-0.011,0.033c-0.004,0.024-0.011,0.044-0.028,0.063c-0.01,0.011-0.021,0.023-0.028,0.036
+ c-0.008,0.014-0.012,0.025-0.027,0.035c-0.012,0.008-0.049,0.035-0.063,0.028c0.001-0.012,0.027-0.021,0.036-0.028
+ c0.005-0.004,0.01-0.01,0.013-0.015c0.004-0.004,0.014-0.015,0.015-0.02c-0.012-0.006-0.026,0.005-0.036,0.011
+ c-0.013,0.007-0.025,0.014-0.036,0.024c-0.01,0.01-0.022,0.019-0.032,0.029c-0.005,0.004-0.01,0.007-0.014,0.012
+ c-0.006,0.007-0.006,0.018-0.012,0.025c-0.009,0.011-0.02,0.018-0.022,0.034c-0.001,0.006,0.002,0.014-0.001,0.02
+ c-0.003,0.005-0.011,0.009-0.015,0.012c-0.014,0.01-0.025,0.018-0.035,0.032c-0.007,0.01-0.014,0.022-0.025,0.03
+ c-0.018,0.012-0.046,0.026-0.053,0.048c-0.003,0.007-0.002,0.011-0.006,0.017c-0.003,0.005-0.009,0.01-0.012,0.015
+ c-0.008,0.011-0.014,0.024-0.021,0.034c-0.017,0.021-0.044,0.036-0.066,0.051c-0.009,0.006-0.016,0.015-0.023,0.022
+ c-0.005,0.004-0.011,0.007-0.016,0.011c0.007-0.009,0.008-0.021,0.012-0.031c0.004-0.011,0.006-0.022,0.011-0.033
+ c0.007-0.016,0.013-0.034,0.022-0.049c0.005-0.009,0.003-0.012,0.005-0.022c0.001-0.006,0.006-0.012,0.009-0.018
+ c0.005-0.009,0.01-0.017,0.017-0.026c0.009-0.015,0.023-0.024,0.034-0.037c0.007-0.01,0.015-0.021,0.023-0.032
+ c0.008-0.011,0.02-0.015,0.031-0.023c0.013-0.009,0.022-0.021,0.031-0.034c0.003-0.004,0.005-0.005,0.007-0.009
+ c0.003-0.006,0.003-0.016,0.007-0.021c0.006-0.007,0.015-0.009,0.022-0.016c0.008-0.009,0.01-0.017,0.016-0.027
+ c0.007-0.012,0.02-0.018,0.027-0.029c0.01-0.012,0.017-0.018,0.03-0.028C-18.291,56.817-18.276,56.799-18.255,56.79z
+ M-18.624,57.274c0.001,0,0.002-0.001,0.003-0.002c-0.001,0.002-0.003,0.004-0.003,0.007V57.274z M-18.13,56.418
+ c-0.021,0.014-0.052,0.017-0.071,0.035l-0.009,0.01c-0.009-0.012-0.014-0.014-0.005-0.027c0.012-0.016,0.028-0.03,0.043-0.044
+ c0.021-0.021,0.044-0.035,0.07-0.049c0.011-0.007,0.019-0.016,0.03-0.022c0.012-0.006,0.025-0.008,0.036-0.016
+ c0.01-0.008,0.022-0.019,0.031-0.028c0.006-0.007,0.033-0.055,0.04-0.037c0.004,0.01-0.011,0.029-0.016,0.038
+ c-0.008,0.017-0.015,0.03-0.028,0.044c-0.018,0.017-0.037,0.034-0.056,0.048c-0.01,0.008-0.019,0.018-0.03,0.024
+ C-18.107,56.401-18.118,56.41-18.13,56.418z M-17.835,56.051c0.012-0.008,0.022-0.019,0.032-0.027
+ c0.013-0.01,0.026-0.019,0.039-0.028c0.009-0.006,0.017-0.012,0.025-0.019c0.013-0.009,0.026-0.019,0.036-0.031
+ c0.011-0.012,0.021-0.031,0.037-0.037c0.022-0.01,0.017,0.015,0.009,0.026c-0.01,0.016-0.02,0.034-0.033,0.048
+ c-0.011,0.013-0.022,0.022-0.035,0.032c-0.013,0.011-0.022,0.022-0.034,0.034c-0.012,0.013-0.027,0.02-0.039,0.032
+ c-0.009,0.008-0.017,0.017-0.026,0.024c-0.01,0.009-0.021,0.015-0.029,0.026c-0.008,0.012-0.016,0.027-0.031,0.033l-0.005-0.006
+ c-0.012,0.003-0.032-0.006-0.034-0.018c-0.005-0.027,0.026-0.05,0.044-0.063C-17.864,56.066-17.85,56.06-17.835,56.051z
+ M-17.847,56.169c0.005-0.008,0.011-0.015,0.018-0.021c0.013-0.011,0.024-0.022,0.036-0.033c0.011-0.01,0.025-0.019,0.035-0.031
+ c0,0.017-0.013,0.035-0.023,0.048c-0.012,0.017-0.017,0.035-0.032,0.049c-0.006,0.005-0.01,0.011-0.015,0.017
+ c-0.007,0.008-0.01,0.006-0.018,0.009c-0.019,0.008-0.021,0.013-0.022,0.033l-0.011-0.011l-0.001,0.003
+ c-0.008-0.006,0.006-0.031,0.012-0.038C-17.861,56.185-17.853,56.179-17.847,56.169z M-17.809,56.229
+ c0.012-0.01,0.023-0.022,0.034-0.033c0.009-0.008,0.011-0.017,0.018-0.026c0.008-0.012,0.022-0.021,0.031-0.034
+ c0.003-0.004,0.01-0.017,0.016-0.016c0.012,0,0.004,0.02,0.002,0.026c-0.004,0.011-0.008,0.026-0.015,0.036
+ c-0.007,0.01-0.017,0.016-0.025,0.025c-0.013,0.017-0.026,0.032-0.042,0.045c-0.02,0.017-0.038,0.036-0.061,0.048l0.005-0.017
+ C-17.873,56.28-17.814,56.233-17.809,56.229z M-17.71,56.066l-0.005,0.011c-0.021-0.023-0.007-0.047,0.011-0.064
+ c0.015-0.016,0.032-0.03,0.048-0.045c0.018-0.016,0.036-0.032,0.052-0.049c0.011-0.011,0.02-0.022,0.03-0.033
+ c0.019-0.02,0.03-0.045,0.048-0.065c0.03-0.036,0.067-0.076,0.087-0.12c0.004-0.009,0.009-0.018,0.013-0.027
+ c0.008-0.016,0.017-0.033,0.026-0.048c0.004-0.006,0.038-0.051,0.048-0.044c0.014,0.009-0.017,0.057-0.022,0.064
+ c-0.009,0.016-0.022,0.029-0.031,0.044c-0.008,0.012-0.018,0.021-0.026,0.033s-0.015,0.023-0.024,0.035
+ c-0.012,0.015-0.024,0.029-0.033,0.047c-0.013,0.026-0.033,0.046-0.049,0.07c-0.029,0.044-0.072,0.079-0.098,0.124
+ c-0.007,0.014-0.019,0.031-0.031,0.04c-0.014,0.009-0.027,0.027-0.043,0.032L-17.71,56.066z M-19.005,57.415l-0.022,0.006
+ c0.005-0.004,0.007-0.013,0.01-0.018c0.003-0.007,0.007-0.014,0.012-0.02c0.007-0.009,0.026-0.027,0.038-0.028
+ c0.017-0.001,0.021,0.016,0.013,0.028c-0.005,0.006-0.014,0.012-0.02,0.017C-18.982,57.408-18.993,57.418-19.005,57.415z
+ M-19.109,57.361h-0.01l-0.005-0.001c0.006-0.014,0.039-0.065,0.06-0.053c0.002,0.002,0.009,0.015,0.009,0.017
+ c0.001,0.01-0.004,0.012-0.01,0.021C-19.076,57.36-19.093,57.381-19.109,57.361z M-19.194,57.357
+ c-0.007,0.003-0.029,0.011-0.023-0.007h-0.017c0.015-0.005,0.022-0.022,0.032-0.034c0.004-0.005,0.01-0.013,0.016-0.016
+ c0.007-0.004,0.015-0.003,0.022-0.008c0.001,0.009,0.012,0.012,0.013,0.02c0,0.007-0.017,0.024-0.021,0.029
+ C-19.179,57.348-19.185,57.353-19.194,57.357z M-19.499,57.514c-0.001,0.002-0.001,0.003-0.001,0.005l-0.006-0.006
+ c-0.001,0.001-0.002,0.001-0.003,0.001c0.019-0.023,0.036-0.05,0.063-0.065c0.014-0.007,0.025-0.011,0.039-0.022
+ c0.01-0.008,0.016-0.022,0.031-0.016c-0.002,0.025-0.035,0.052-0.053,0.065C-19.45,57.491-19.474,57.511-19.499,57.514z
+ M-19.589,57.621c-0.005,0.009-0.012,0.015-0.019,0.023c-0.011,0.012-0.013,0.008-0.027,0.012c-0.008,0.002-0.014,0.009-0.023,0.01
+ c-0.005,0-0.013-0.001-0.019-0.001c0.003-0.001,0.005-0.003,0.008-0.005l-0.016,0.006c-0.007-0.001-0.011-0.008-0.016-0.013
+ c0.002-0.008,0.009-0.011,0.016-0.016c0.011-0.008,0.021-0.018,0.032-0.027c0.008-0.005,0.011-0.013,0.018-0.019
+ s0.017-0.012,0.026-0.016c0.017-0.01,0.027-0.022,0.038-0.036c0.004-0.006,0.013-0.017,0.02-0.022
+ c0.011-0.007,0.016-0.005,0.018,0.007c0.003,0.022-0.012,0.035-0.026,0.051C-19.571,57.589-19.579,57.605-19.589,57.621z
+ M-19.772,57.757c-0.03,0.027-0.066,0.047-0.098,0.071c-0.016,0.011-0.027,0.023-0.038,0.039c-0.011,0.015-0.024,0.018-0.039,0.026
+ c-0.014,0.007-0.024,0.027-0.039,0.033c-0.006,0.002-0.013,0.001-0.019,0.002c-0.002,0.001-0.004,0.001-0.006,0.002
+ c-0.004-0.005,0.008-0.02,0.011-0.025c0.013-0.018,0.034-0.026,0.047-0.043c0.01-0.014,0.022-0.026,0.034-0.038
+ c0.01-0.012,0.017-0.023,0.025-0.035c0.009-0.012,0.027-0.022,0.039-0.031c0.018-0.013,0.03-0.031,0.049-0.045
+ c0.013-0.01,0.024-0.016,0.039-0.021c0.011-0.004,0.011-0.001,0.022,0.001c0.009,0.002,0.014-0.002,0.021-0.005
+ c0.004,0.008-0.012,0.03-0.017,0.037C-19.75,57.736-19.761,57.747-19.772,57.757z M-20.016,57.932c0.002,0,0.004-0.001,0.005-0.002
+ c0.001,0.001,0.002,0.002,0.005,0.002H-20.016z M-20.023,57.932h0.007c-0.004,0.003-0.008,0.005-0.01,0.007
+ C-20.024,57.937-20.023,57.935-20.023,57.932z M-19.994,57.988c-0.005,0.009-0.017,0.017-0.023,0.025
+ c-0.008,0.009-0.016,0.018-0.022,0.028c-0.007,0.011-0.016,0.017-0.027,0.024c-0.01,0.005-0.017,0.014-0.027,0.02
+ c-0.019,0.011-0.04,0.015-0.056,0.031c-0.006,0.006-0.015,0.018-0.022,0.022c-0.004,0.003-0.011,0.003-0.015,0
+ c-0.002,0-0.003-0.001-0.003-0.002c0.001-0.003,0.003-0.005,0.004-0.007c0.009-0.014,0.019-0.026,0.032-0.036
+ c0.011-0.009,0.023-0.02,0.032-0.031c0.012-0.015,0.015-0.027,0.032-0.037c0.011-0.007,0.017-0.011,0.023-0.022
+ c0.008-0.014,0.013-0.007,0.027-0.012c0.01-0.003,0.017-0.013,0.027-0.015C-19.999,57.972-19.986,57.974-19.994,57.988z
+ M-20.192,58.127c0,0.003,0.001,0.006,0.003,0.009c-0.004,0.007-0.008,0.014-0.013,0.019L-20.192,58.127z M-20.079,57.926
+ c-0.013,0.008-0.028,0.019-0.038,0.032c-0.006,0.009-0.006,0.02-0.01,0.029c-0.003,0.006-0.01,0.011-0.014,0.016
+ c-0.004,0.005-0.006,0.012-0.011,0.017c-0.008,0.009-0.019,0.018-0.028,0.025c-0.007,0.005-0.027,0.013-0.027,0.022
+ c0.003,0.002,0.005,0.002,0.008,0.001c-0.006,0.006-0.01,0.015-0.015,0.022c-0.011,0.014-0.026,0.026-0.038,0.037
+ c-0.018,0.015-0.049,0.026-0.071,0.035c-0.023,0.01-0.045,0.02-0.063,0.038c-0.011,0.012-0.022,0.026-0.035,0.036
+ c-0.016,0.012-0.033,0.022-0.048,0.034c-0.005,0.004-0.011,0.01-0.017,0.015c-0.007,0.005-0.022,0.01-0.026,0.019
+ c-0.008,0.022,0.037-0.003,0.043-0.007s0.02-0.012,0.022,0c0.004,0.014-0.016,0.017-0.023,0.02c-0.009,0.004-0.017,0.01-0.026,0.014
+ c-0.01,0.004-0.02,0.009-0.029,0.014c-0.008,0.004-0.017,0.008-0.026,0.01c-0.005,0.002-0.01,0.003-0.016,0.006
+ c-0.006,0.002-0.017,0.005-0.022,0.008c-0.005,0.003-0.005,0.007-0.01,0.01c-0.004,0.002-0.011,0.004-0.016,0.007
+ c-0.007,0.003-0.015,0.006-0.022,0.009c-0.021,0.009-0.042,0.023-0.054,0.043c-0.008,0.015-0.009,0.033-0.028,0.033
+ c-0.006,0-0.012-0.001-0.017,0c-0.006,0.001-0.007,0.003-0.012,0.004c-0.009,0.003-0.017,0.004-0.026,0.007
+ c-0.004,0.002-0.007,0.002-0.012,0.004c-0.005,0.002-0.01,0.006-0.015,0.007c-0.012,0.003-0.021-0.002-0.033,0.004
+ c-0.01,0.004-0.02,0.009-0.028,0.016c-0.019,0.015-0.047,0.017-0.064,0.034c-0.009,0.01-0.016,0.021-0.027,0.028
+ c-0.009,0.005-0.019,0.006-0.027,0.012c-0.012,0.006-0.021,0.013-0.033,0.019c-0.013,0.006-0.019,0.01-0.033,0.013
+ c-0.011,0.003-0.022,0.006-0.033,0.01c-0.009,0.004-0.017,0.004-0.027,0.005c-0.009,0.002-0.018,0.007-0.027,0.011
+ c-0.01,0.005-0.021,0.007-0.031,0.012c-0.032,0.016-0.07,0.025-0.098,0.05c-0.01,0.008-0.022,0.016-0.034,0.021
+ c-0.006,0.003-0.011,0.002-0.017,0.004c-0.004,0.002-0.005,0.005-0.01,0.006s-0.012-0.001-0.017,0c-0.006,0-0.008,0.003-0.012,0.004
+ c-0.012,0.003-0.015,0.004-0.024,0.013c-0.008,0.009-0.013,0.009-0.023,0.014c-0.02,0.008-0.039,0.019-0.058,0.029
+ c-0.024,0.012-0.049,0.024-0.073,0.032c-0.018,0.006-0.032,0.014-0.048,0.024c-0.02,0.012-0.049,0.018-0.072,0.024
+ c-0.011,0.003-0.022-0.001-0.032,0.001c-0.012,0.002-0.022,0.012-0.033,0.016c-0.012,0.003-0.067,0.017-0.048-0.015
+ c0.004-0.007,0.016-0.01,0.022-0.016c0.008-0.006,0.013-0.015,0.019-0.023c0.012-0.015,0.022-0.03,0.033-0.045
+ c0.008-0.01,0.009-0.025,0.018-0.036c0.003,0,0.006-0.001,0.009-0.001c0.002-0.004,0.004-0.007,0.007-0.009
+ c0.004-0.004,0.01-0.011,0.016-0.011c0.006-0.001,0.013,0.006,0.022,0.004c0.009-0.001,0.014-0.006,0.02-0.012
+ c0.011-0.011,0.017-0.026,0.029-0.037c0.014-0.014,0.032-0.019,0.049-0.028c0.016-0.009,0.026-0.025,0.042-0.033
+ c0.013-0.007,0.028-0.012,0.039-0.021c0.014-0.012,0.016-0.025,0.025-0.039c-0.011-0.008-0.015-0.008-0.029-0.011
+ c-0.011-0.002-0.025-0.012-0.039-0.01c-0.006,0.002-0.011,0.005-0.017,0.006c-0.006,0.002-0.015,0-0.021,0
+ c-0.014,0-0.03-0.002-0.043,0.001c-0.011,0.003-0.023,0.013-0.034,0.02c-0.013,0.009-0.028,0.011-0.043,0.016
+ c-0.027,0.008-0.046,0.02-0.07,0.035c-0.009,0.006-0.019,0.013-0.029,0.019c-0.013,0.008-0.024,0.013-0.037,0.019
+ c-0.013,0.007-0.026,0.012-0.038,0.02c-0.013,0.008-0.029,0.008-0.044,0.013c-0.012,0.004-0.022,0.014-0.034,0.019
+ c-0.012,0.007-0.027,0.01-0.041,0.013c-0.004,0.002-0.006,0.004-0.012,0.005c-0.007,0.001-0.015,0-0.022,0
+ c-0.014,0-0.022,0.008-0.037,0.004c-0.002-0.011,0.008-0.014,0.016-0.019c0.008-0.005,0.013-0.013,0.021-0.018
+ c0.015-0.008,0.029-0.019,0.044-0.027c0.007-0.004,0.014-0.006,0.022-0.009c0.012-0.006,0.016-0.017,0.025-0.025
+ c0.014-0.013,0.026-0.017,0.034-0.035c-0.011-0.015-0.046,0.004-0.059,0.01c-0.022,0.011-0.044,0.019-0.065,0.03
+ c-0.024,0.013-0.048,0.017-0.071,0.03c-0.011,0.007-0.016,0.008-0.027,0.011c-0.007,0.002-0.01,0.004-0.017,0.004
+ c-0.007,0.001-0.014,0-0.022,0.002c-0.021,0.005-0.042,0.014-0.06,0.024c-0.007,0.005-0.018,0.007-0.027,0.011
+ c-0.019,0.008-0.036,0.022-0.054,0.033c-0.008,0.005-0.018,0.009-0.027,0.013c-0.01,0.005-0.019,0.01-0.027,0.015
+ c-0.008,0.004-0.015,0.008-0.022,0.013c-0.011,0.006-0.019,0.012-0.029,0.019c-0.004,0.003-0.01,0.003-0.015,0.006
+ c-0.005,0.003-0.011,0.009-0.016,0.012c-0.013,0.009-0.021,0.022-0.033,0.032c-0.007,0.006-0.015,0.017-0.023,0.021
+ c-0.004,0.002-0.009,0-0.014,0.002c-0.004,0.001-0.012,0.008-0.016,0.011c-0.013,0.009-0.025,0.022-0.039,0.03
+ c-0.009,0.004-0.018,0.008-0.026,0.013c-0.004,0.003-0.008,0.007-0.012,0.01c-0.009,0.006-0.008,0.007-0.013,0.015
+ c-0.012,0.018-0.013,0.04-0.019,0.061c-0.003,0.011,0,0.019-0.005,0.028c-0.005,0.011-0.014,0.02-0.022,0.028
+ c-0.012,0.011-0.023,0.024-0.035,0.035c-0.016,0.014-0.035,0.024-0.049,0.038c-0.006,0.006-0.007,0.011-0.011,0.017
+ c-0.005,0.006-0.013,0.009-0.019,0.013c-0.019,0.013-0.034,0.03-0.053,0.042c-0.017,0.011-0.029,0.023-0.042,0.037
+ c-0.013,0.013-0.028,0.019-0.043,0.029c-0.017,0.01-0.035,0.02-0.054,0.027c-0.01,0.004-0.017,0.003-0.027,0.005
+ c-0.012,0.002-0.021,0.016-0.033,0.021c-0.006,0.003-0.015,0.004-0.02,0.008c-0.008,0.006-0.016,0.013-0.024,0.019
+ c-0.015,0.011-0.032,0.024-0.049,0.033c-0.016,0.009-0.036,0.019-0.053,0.03c-0.014,0.009-0.028,0.02-0.044,0.027
+ c-0.015,0.006-0.029,0.005-0.044,0.013c-0.01,0.006-0.019,0.012-0.027,0.018c-0.017,0.012-0.036,0.023-0.055,0.033
+ c-0.027,0.015-0.052,0.037-0.081,0.05c-0.016,0.007-0.032,0.011-0.048,0.016c-0.014,0.005-0.027,0.017-0.043,0.016
+ c0.002-0.002,0.004-0.003,0.004-0.006l-0.011-0.006c-0.001,0.012-0.013,0.005-0.011-0.005c0.001-0.007,0.01-0.013,0.015-0.017
+ c0.013-0.014,0.03-0.021,0.045-0.033c0.008-0.007,0.018-0.014,0.026-0.021c0.007-0.006,0.014-0.008,0.023-0.012
+ c0.025-0.011,0.037-0.038,0.06-0.053c0.01-0.007,0.014-0.005,0.02-0.016c0.005-0.009,0.014-0.02,0.017-0.029
+ c0.006-0.018-0.006-0.036-0.021-0.046c-0.016-0.013-0.038-0.013-0.033-0.04c0.002-0.011,0.009-0.021,0.011-0.032
+ c0.003-0.011,0.009-0.032,0.005-0.042c-0.021,0.007-0.03,0.041-0.037,0.059c-0.013,0.034-0.034,0.067-0.066,0.086
+ c-0.019,0.012-0.038,0.023-0.06,0.032c-0.012,0.004-0.012,0.005-0.017,0.017c-0.004,0.01-0.001,0.03-0.009,0.033
+ c-0.008,0.003-0.025,0-0.033,0h-0.033c-0.01,0-0.023,0.002-0.033,0s-0.022-0.009-0.032-0.012c-0.015-0.005-0.029-0.013-0.044-0.016
+ c-0.01-0.001-0.022,0.001-0.033,0.001c-0.01,0-0.022-0.002-0.032,0.001c-0.007,0.001-0.009,0.005-0.017,0.004
+ c-0.008,0-0.009-0.004-0.016-0.006c-0.01-0.002-0.022,0.002-0.033,0.001c-0.017-0.003-0.007-0.008-0.01-0.022
+ c-0.001-0.003-0.004-0.01-0.006-0.011c-0.006-0.007-0.002-0.002-0.011-0.005c-0.011-0.005-0.02-0.007-0.033-0.002
+ c-0.009,0.004-0.018,0.007-0.027,0.012c-0.009,0.005-0.017,0.013-0.026,0.018c-0.005,0.003-0.012,0.003-0.016,0.005
+ c-0.003,0.002-0.005,0.006-0.008,0.009c-0.011,0.008-0.023,0.012-0.037,0.012c-0.016,0-0.049,0.006-0.064-0.001
+ c-0.021-0.011-0.008-0.04-0.012-0.059c-0.001-0.006-0.004-0.01-0.006-0.016c-0.001-0.005,0.001-0.011,0.001-0.017
+ c-0.001-0.01-0.006-0.015-0.006-0.027c0-0.021-0.011-0.041-0.016-0.059c-0.008-0.025-0.017-0.048-0.038-0.066
+ c-0.014-0.011-0.031-0.034-0.049-0.038c-0.005-0.002-0.011,0.001-0.016-0.001c-0.006-0.002-0.012-0.006-0.017-0.009
+ c-0.009-0.004-0.019-0.007-0.028-0.01c-0.011-0.004-0.02-0.007-0.032-0.007c-0.013,0-0.026,0.002-0.038-0.004
+ c-0.036-0.017-0.003-0.039,0.012-0.056c0.017-0.018,0.031-0.037,0.048-0.053c0.019-0.019,0.043-0.023,0.066-0.034
+ c0.019-0.01,0.032-0.027,0.05-0.037c0.019-0.011,0.036-0.025,0.051-0.041c0.008-0.008,0.016-0.017,0.022-0.026
+ c0.003-0.003,0.01-0.012,0.012-0.016c0.002-0.007,0-0.014,0.002-0.022c0.002-0.009,0.008-0.018,0.01-0.027
+ c0.002-0.012-0.002-0.016,0.006-0.027c0.014-0.019,0.043-0.032,0.064-0.042c0.022-0.011,0.041-0.028,0.051-0.051
+ c0.002-0.006,0.002-0.01,0.003-0.016c0.002-0.005,0.005-0.006,0.006-0.011c0.003-0.013-0.002-0.02,0.006-0.032
+ c0.011-0.014,0.024-0.019,0.038-0.029c0.01-0.008,0.019-0.016,0.028-0.025c0.008-0.006,0.013-0.012,0.022-0.017
+ c0.015-0.009,0.03-0.009,0.046-0.015c0.021-0.008,0.041-0.016,0.061-0.023c0.02-0.008,0.038-0.016,0.054-0.029
+ c0.01-0.007,0.02-0.013,0.029-0.02c0.01-0.008,0.02-0.016,0.031-0.021c0.017-0.007,0.055-0.009,0.055-0.034
+ c0-0.024-0.032-0.026-0.049-0.023c-0.023,0.005-0.048-0.003-0.071,0.003c-0.011,0.002-0.02,0.006-0.033,0.004
+ c-0.011-0.001-0.02-0.008-0.032-0.006c-0.006,0.001-0.011,0.005-0.016,0.007c-0.007,0.003-0.011,0.003-0.017,0.004
+ c-0.005,0.001-0.006,0.007-0.011,0.005c-0.003-0.001-0.008-0.009-0.01-0.012c0.003-0.005,0.008-0.015,0.009-0.021
+ c0.002-0.011-0.001-0.015-0.005-0.025c-0.005-0.01-0.002-0.017-0.004-0.028c-0.003-0.014-0.012-0.024-0.016-0.037
+ c-0.005-0.022,0.003-0.04,0.006-0.06c0.001-0.007,0.005-0.003,0.003-0.012c-0.001-0.007-0.006-0.007-0.01-0.012
+ c-0.009-0.011-0.014-0.022-0.018-0.036c0-0.002-0.003-0.009-0.004-0.012c-0.001-0.006-0.001-0.007-0.003-0.012
+ c-0.006-0.011-0.012-0.014-0.003-0.026c0.008-0.01,0.018-0.012,0.027-0.018c0.013-0.008,0.024-0.021,0.036-0.031
+ c0.015-0.015,0.034-0.027,0.048-0.043c0.02-0.024,0.042-0.05,0.068-0.066c0.018-0.011,0.034-0.02,0.051-0.031
+ c0.019-0.014,0.025-0.041,0.044-0.055c0.009-0.006,0.024-0.004,0.032-0.01c0.006-0.005,0.01-0.013,0.015-0.019
+ c0.018-0.022,0.038-0.043,0.057-0.063c0.007-0.008,0.014-0.016,0.02-0.024c0.009-0.012,0.021-0.021,0.029-0.032
+ c0.012-0.016,0.02-0.036,0.025-0.056c0.002-0.013,0.002-0.023,0.006-0.037c0.004-0.009,0.004-0.012,0.012-0.02
+ c0.006-0.005,0.011-0.011,0.016-0.017c0.016-0.017,0.037-0.028,0.052-0.044c0.01-0.01,0.02-0.018,0.03-0.028
+ c0.023-0.024,0.05-0.041,0.074-0.062c0.01-0.008,0.019-0.015,0.029-0.023c0.004-0.003,0.006-0.008,0.01-0.011
+ c0.007-0.005,0.015-0.007,0.022-0.012c0.024-0.018,0.048-0.035,0.071-0.053c0.019-0.015,0.037-0.027,0.06-0.036
+ c0.011-0.004,0.02-0.002,0.031-0.005c0.014-0.004,0.014-0.015,0.023,0.002c0.01,0.018,0.024,0.036,0.045,0.042
+ c0.01,0.003,0.02,0.002,0.031,0.006c0.014,0.006,0.024,0.006,0.038,0.006s0.025-0.005,0.039-0.006c0.005,0,0.011,0.001,0.016,0.001
+ c0.008-0.002,0.011-0.01,0.021-0.005c-0.003,0.009-0.015,0.01-0.022,0.016c-0.008,0.009-0.014,0.021-0.022,0.031
+ c-0.01,0.011-0.016,0.022-0.028,0.032c-0.013,0.011-0.027,0.013-0.026,0.034c0.001,0.01,0.004,0.021,0.013,0.026
+ c0.004,0.003,0.024,0.007,0.029,0.007c-0.007,0.004-0.012,0.012-0.008,0.021c0.006,0.01,0.023,0.006,0.033,0.006
+ c0.02,0,0.036-0.007,0.053-0.015c0.008-0.004,0.01-0.002,0.018-0.008c0.009-0.007,0.015-0.01,0.025-0.013
+ c0.018-0.006,0.036-0.014,0.054-0.017c0.016-0.003,0.034-0.001,0.05-0.001c0.014,0,0.031-0.004,0.044-0.001
+ c0.01,0.003,0.021,0.005,0.029,0.006c0.009-0.007,0.013-0.004,0.021,0.001c0.012,0.008,0.006,0.015,0.015,0.022
+ c0.016,0.014,0.034,0.001,0.045-0.011c0.008-0.008,0.016-0.017,0.026-0.023c0.016-0.01,0.036-0.003,0.054-0.009
+ c0.02-0.007,0.026-0.027,0.049-0.029c0.011-0.001,0.017,0.004,0.027,0.006c0.028,0.006,0.059-0.005,0.087-0.01
+ c0.024-0.004,0.049-0.008,0.072-0.019c0.018-0.009,0.034-0.016,0.054-0.022c0.022-0.007,0.025-0.001,0.041,0.012
+ c-0.007,0.011-0.02,0.004-0.03,0.006c-0.015,0.002-0.03,0.01-0.043,0.018c-0.011,0.007-0.021,0.009-0.034,0.013
+ c-0.014,0.005-0.027,0.012-0.041,0.018c0.011,0,0.024,0.002,0.035,0c0.018-0.003,0.038-0.01,0.056-0.015
+ c0.01-0.003,0.02-0.005,0.031-0.008c0.004-0.001,0.006-0.004,0.012-0.004c0.008-0.002,0.014-0.002,0.022-0.005
+ c0.012-0.003,0.025,0.001,0.038-0.002c0.013-0.003,0.024-0.004,0.038-0.004c0.011,0,0.022,0.001,0.033-0.001
+ c0.013-0.003,0.025-0.005,0.038-0.005c0.007,0,0.022,0.004,0.025-0.005c-0.003-0.011-0.016-0.013-0.025-0.011
+ c-0.006,0.001-0.011,0.006-0.017,0.006c-0.004,0-0.007-0.005-0.01-0.006c-0.008-0.003-0.021-0.002-0.016-0.015
+ c0.002-0.006,0.018-0.013,0.023-0.016c0.007-0.003,0.023-0.013,0.031-0.013c0.005,0,0.005,0.005,0.01,0.006s0.012,0,0.017,0
+ c0.015,0,0.033-0.002,0.047-0.007c0.007-0.002,0.012-0.006,0.017-0.008c0.009-0.004,0.019,0,0.028-0.002
+ c-0.001-0.007-0.012-0.014-0.017-0.019c0.023,0.018,0.06-0.013,0.072-0.029c0.009-0.014,0.017-0.019,0.032-0.026
+ c0.014-0.007,0.026-0.009,0.04-0.017c0.003-0.002,0.011-0.006,0.014-0.007c0.004,0.001,0.008,0.001,0.012,0.001
+ c0.003,0,0.005-0.002,0.006-0.005c0.007-0.001,0.014,0.001,0.021,0c0.012-0.003,0.008-0.008,0.011-0.017
+ c0.005-0.027,0.031-0.045,0.047-0.065c0.019-0.024,0.044-0.046,0.074-0.054c0.006-0.001,0.013-0.001,0.02-0.001
+ c0.008,0,0.012-0.003,0.018-0.004c0.006-0.002,0.013-0.001,0.019-0.002c0.006-0.002,0.011-0.009,0.017-0.01
+ c0.007-0.002,0.014,0.001,0.021-0.001c0.004-0.002,0.014-0.009,0.017-0.011c0.006-0.006,0.012-0.027,0.022-0.027
+ c0.006,0,0.008,0.007,0.012,0.01c0.004,0.003,0.011,0.005,0.016,0.007c0.011,0.006,0.024,0.01,0.037,0.012
+ c0,0.008,0.006,0.013,0.006,0.02c0.002,0.011,0.001,0.018,0.006,0.028c0,0.003,0.001,0.005,0.004,0.007
+ c0.001,0.004-0.001,0.01,0.002,0.014c0.003,0.004,0.017,0.011,0.022,0.016c0.006,0.005,0.014,0.006,0.019,0.013
+ c0.001,0.003,0.002,0.006,0.003,0.009c0.002,0.003,0.006,0.004,0.008,0.007c0.005,0.006,0.004,0.015,0.007,0.021
+ c0.005,0.013,0.011,0.011,0.025,0.011c-0.001,0.013,0.039,0.002,0.046,0c0.009-0.004,0.018-0.004,0.027-0.011
+ c0.012-0.008,0.019-0.022,0.033-0.029c0.02-0.01,0.044-0.002,0.065,0.002s0.03-0.022,0.053-0.015
+ c0.007,0.002,0.015,0.007,0.018,0.014c0.002,0.007-0.001,0.016,0.001,0.023c0.002,0.006,0.004,0.009,0.004,0.016
+ c0,0.023-0.016,0.038-0.005,0.06c0.005,0.012,0.005,0.014-0.002,0.027c-0.006,0.011-0.009,0.014-0.009,0.027
+ s0.003,0.021,0.005,0.033c0.002,0.006,0.002,0.023-0.003,0.028c0.013,0.001,0.026-0.023,0.029-0.034
+ c0.005-0.016,0.013-0.037,0.012-0.054c0-0.017,0.007-0.03,0.018-0.042c0.004-0.005,0.01-0.009,0.014-0.014
+ c0-0.003,0.001-0.005,0.002-0.008c0.002-0.002,0.012-0.004,0.015-0.005c0.007-0.003,0.01-0.006,0.015-0.009
+ c0.008-0.004,0.014-0.003,0.023-0.003c0.012-0.001,0.02-0.001,0.028,0.01c0.003,0.005,0.01,0.016,0.011,0.022
+ c0.002,0.009-0.005,0.018-0.006,0.027c-0.001,0.009,0,0.018,0,0.028c0,0.021-0.005,0.047-0.015,0.066
+ c-0.008,0.015-0.015,0.026-0.019,0.042c-0.002,0.011-0.006,0.016-0.003,0.027c0.003,0.01,0.003,0.018,0.004,0.028
+ c0.001,0.007,0.004,0.009,0.006,0.016c0.002,0.008,0,0.019,0,0.027c0.001,0.022,0.011,0.034,0.027,0.048
+ c0.013,0.011,0.026,0.022,0.038,0.033c0.007,0.005,0.011,0.01,0.016,0.017c0.006,0.008,0.008,0.014,0.012,0.022
+ c0.008,0.012,0.025,0.021,0.031,0.034c0.005,0.01-0.003,0.012-0.005,0.02c-0.002,0.008-0.001,0.013-0.004,0.021
+ c0.006,0.004,0.017,0.021,0.016,0.029c-0.001,0.006-0.007,0.006-0.012,0.01c-0.004,0.004-0.007,0.011-0.01,0.017
+ c-0.012,0.019-0.024,0.043-0.045,0.053c-0.005,0.002-0.011,0.001-0.016,0.005c-0.005,0.004-0.002,0.006-0.006,0.011
+ c-0.007,0.01-0.01,0.015-0.01,0.028c0.001,0.012,0.007,0.018,0.011,0.027c0.006,0.012,0.005,0.024,0.01,0.035
+ c0.008,0.017,0.024,0.032,0.019,0.053c-0.002,0.01-0.013,0.02-0.006,0.031c0.007,0.01,0.022,0.008,0.032,0.006
+ c0.012-0.003,0.022-0.01,0.032-0.017c0.011-0.008,0.021-0.01,0.033-0.015c0.025-0.009,0.051-0.011,0.076-0.017
+ c0.025-0.006,0.047-0.015,0.071-0.022c0.026-0.008,0.044-0.026,0.064-0.044c0.006-0.005,0.019-0.011,0.022-0.016
+ c0.004-0.005,0.002-0.025,0-0.03c-0.003-0.01-0.014-0.017-0.016-0.029c-0.001-0.013,0.012-0.018,0.022-0.023
+ c0.013-0.006,0.018-0.002,0.029,0c0.009,0.002,0.022,0.003,0.032,0.002c0.009-0.001,0.014-0.006,0.023-0.01
+ c0.011-0.006,0.018-0.012,0.02,0.004c0.002,0.015-0.002,0.021-0.01,0.033c-0.006,0.009-0.006,0.018-0.013,0.026
+ c-0.016,0.018-0.025,0.025-0.015,0.05c0.003,0.009,0.003,0.03,0.01,0.033c0.014,0.006,0.035-0.004,0.045-0.012
+ c0.017-0.015,0.024-0.052,0.048-0.059c0.005-0.002,0.011,0.001,0.017,0c0.008-0.001,0.011-0.006,0.017-0.01
+ c0.005-0.002,0.011-0.004,0.015-0.006c0.005-0.003,0.008-0.007,0.012-0.01c0.006-0.004,0.014-0.003,0.02-0.008
+ c0.004-0.003,0.007-0.007,0.01-0.011c0.008-0.008,0.019-0.013,0.027-0.02c-0.006,0.01-0.004,0.021-0.01,0.032
+ C-20.058,57.907-20.066,57.918-20.079,57.926z M-20.496,56.942c-0.016,0.005-0.024,0.014-0.022,0.033l-0.011-0.017
+ c-0.018,0.024-0.04-0.035-0.048-0.043c-0.006-0.005-0.01-0.004-0.012-0.011c-0.002-0.007,0.001-0.013,0.004-0.018
+ c0.008-0.011,0.022-0.015,0.034-0.019c0.03-0.009,0.049-0.033,0.077-0.045c0.011-0.005,0.025-0.008,0.037-0.011
+ c0.006,0.001,0.01,0.001,0.015-0.001c0.007-0.003,0.014-0.006,0.021-0.009c0.01-0.002,0.019-0.004,0.029-0.008
+ c0.012-0.004,0.023-0.004,0.034,0.007c0.014,0.014,0.011,0.028,0.005,0.044c-0.01,0.026-0.034,0.038-0.056,0.053
+ c-0.008,0.005-0.016,0.013-0.025,0.018c-0.013,0.006-0.03,0.01-0.044,0.015C-20.471,56.934-20.484,56.938-20.496,56.942z
+ M-20.562,57.008c0.01,0.008,0.021,0.015,0.033,0.021c0.015,0.007,0.025,0.007,0.034,0.022c0.015,0.022,0.011,0.039,0,0.061
+ c-0.003,0.006-0.004,0.013-0.008,0.019c-0.005,0.009-0.014,0.015-0.02,0.023c-0.008,0.014-0.006,0.029-0.018,0.042
+ c-0.016,0.018-0.038,0.034-0.064,0.028c-0.019-0.005-0.028-0.016-0.042-0.028c-0.007-0.005-0.016-0.01-0.022-0.016
+ c-0.002-0.002-0.004-0.004-0.005-0.006c-0.005-0.002-0.009-0.001-0.013-0.003c-0.008-0.004-0.035-0.013-0.038-0.024v0.013
+ c-0.005-0.017-0.059-0.055-0.053-0.07c0.002-0.002,0.004-0.003,0.006-0.005c0.002-0.004,0.003-0.008,0.005-0.011
+ c0.005-0.008,0.008-0.014,0.011-0.023c0.003-0.009,0.006-0.017,0.004-0.027c-0.002-0.012-0.008-0.019-0.004-0.032
+ c0.007-0.023,0.026-0.02,0.045-0.024c0.011-0.002,0.018-0.005,0.03-0.004c0.012,0,0.021-0.007,0.033-0.006
+ c0.015,0.001,0.025,0.015,0.037,0.023c0.009,0.006,0.018,0.007,0.027,0.011C-20.577,56.996-20.569,57.003-20.562,57.008z
+ M-20.741,57.17c0.003,0.011,0.002,0.033-0.001,0.043c-0.021,0.002-0.04,0.018-0.063,0.011l-0.001-0.005l-0.006-0.005
+ c0.001,0.004,0.001,0.007-0.001,0.01c-0.007-0.001-0.018-0.015-0.026-0.019c-0.008-0.005-0.013-0.007-0.022-0.007
+ c-0.003,0-0.007,0-0.011,0c-0.005,0-0.008,0.003-0.012,0.004c-0.007,0.001-0.013,0.001-0.02,0.001c-0.012,0-0.012-0.001-0.021-0.007
+ c-0.011-0.007-0.022-0.014-0.033-0.021c0-0.023,0.06-0.031,0.075-0.032c0.005,0,0.012-0.001,0.017,0
+ c0.007,0.002,0.008,0.007,0.016,0.006c0.004,0,0.007-0.004,0.011-0.006c0.005-0.002,0.011-0.003,0.016-0.005
+ c0.013-0.006,0.024-0.012,0.037-0.01c0.01,0.002,0.019,0.009,0.028,0.015C-20.748,57.15-20.743,57.159-20.741,57.17z
+ M-24.165,56.011c-0.325-0.11-1.03,0.279-0.976-0.33c0.055-0.609,0.057-0.298,0.086-0.447c0.107-0.033,0.215-0.065,0.322-0.098
+ c-0.002-0.002-0.005-0.003-0.007-0.007c-0.002-0.006,0-0.011-0.001-0.018c-0.001-0.007-0.003-0.013-0.003-0.021
+ c0-0.011,0.004-0.022,0.005-0.033c0.001-0.006-0.001-0.009-0.003-0.013c-0.001-0.006,0.001-0.013,0.002-0.018
+ c0.002-0.005,0.003-0.011,0.004-0.016c0-0.005-0.001-0.01,0-0.015c0-0.006,0.005-0.01,0.007-0.015
+ c0.002-0.007,0.002-0.012,0.003-0.019c0.001-0.009,0.008-0.014,0.012-0.023c0.003-0.004,0.004-0.012,0.008-0.015
+ c0.003-0.003,0.005-0.002,0.008-0.003c0.004-0.002,0.007-0.007,0.01-0.01c0.003-0.003,0.004-0.008,0.007-0.011
+ c0.004-0.003,0.008-0.004,0.012-0.006c0.01-0.006,0.015-0.019,0.025-0.023c0.013-0.006,0.025-0.015,0.036-0.024
+ c0.006-0.005,0.011-0.009,0.018-0.013c0.005-0.002,0.006-0.002,0.01-0.002c0.006-0.001,0.01-0.005,0.015-0.008
+ c0.003-0.001,0.007-0.003,0.01-0.005c0.003-0.001,0.006-0.004,0.009-0.005s0.005,0,0.008,0c0.004-0.001,0.007-0.002,0.01-0.003
+ c0.005-0.001,0.007,0,0.012-0.002c0.002-0.002,0.005-0.005,0.008-0.006c0.002-0.002,0.003-0.004,0.005-0.005
+ c0.005-0.003,0.006-0.002,0.011-0.002c0.005-0.001,0.013-0.005,0.018-0.008c0.007-0.004,0.014-0.009,0.02-0.013
+ c0.005-0.003,0.01-0.006,0.016-0.008c0.005-0.002,0.012-0.002,0.018-0.005c0.005-0.004,0.012-0.007,0.018-0.011
+ c0.005-0.003,0.008-0.006,0.015-0.007c0.012-0.001,0.028-0.002,0.039,0.003c0.006,0.004,0.009,0.006,0.012,0.013
+ c0.002,0.004,0.004,0.006,0.006,0.01c0.002,0.003,0.001,0.005,0.002,0.008c0.001,0.002,0.002,0.002,0.003,0.005
+ c0.001,0.005-0.001,0.012-0.001,0.018c0.001,0.005-0.002,0.008-0.002,0.013c0,0.006,0.005,0.008,0.01,0.009
+ c0.007,0.002,0.011,0.005,0.014,0.011c0.001,0.003,0.001,0.005,0.002,0.008c0,0.003,0.004,0.007,0.004,0.01
+ c0.001,0.004-0.002,0.008-0.002,0.013c0,0.005,0,0.008,0.003,0.013c0.005,0.01,0.013,0.018,0.022,0.026
+ c0.003,0.002,0.002,0.004,0.006,0.005c0.004,0,0.006-0.001,0.008-0.003c0.007-0.006,0.011-0.018,0.012-0.026
+ c0.001-0.006-0.004-0.014-0.007-0.019c-0.004-0.008-0.009-0.016-0.014-0.024c-0.005-0.008-0.013-0.016-0.014-0.026
+ c-0.001-0.01,0.008-0.019,0.01-0.028c0.001-0.007-0.001-0.012,0.008-0.013c0.005-0.001,0.01,0.001,0.015-0.001
+ c0.005-0.001,0.009-0.004,0.013-0.006c0.004-0.003,0.007-0.007,0.011-0.011c0.003,0.003,0.008,0.004,0.012,0.005
+ c0.005,0.002,0.01,0.005,0.016,0.006c0.008,0.002,0.015,0,0.023-0.003c0.007-0.002,0.01-0.005,0.018-0.005
+ c0.017,0,0.033,0,0.049-0.008c0.013-0.007,0.026-0.008,0.04-0.008c0.007,0,0.012-0.003,0.018-0.005
+ c0.006-0.001,0.01-0.001,0.016-0.003c0.005-0.002,0.009-0.005,0.015-0.005c0.007,0,0.014,0,0.021,0c0.005,0,0.012,0.002,0.018,0
+ c0.004-0.001,0.009-0.004,0.013-0.004c0.008-0.002,0.024,0,0.03,0.007c0.001,0.002,0,0.005,0,0.008c0,0.002,0.002,0.003,0.002,0.005
+ c0.001,0.005,0.001,0.01,0.001,0.015s0.001,0.01-0.001,0.015c0,0.003-0.002,0.006-0.002,0.008c-0.002,0.004-0.001,0.007-0.002,0.011
+ c-0.001,0.004-0.006,0.006-0.006,0.012c0,0.006,0.004,0.011,0.008,0.014c0.01,0.007,0.021,0.008,0.031,0.012
+ c0.009,0.003,0.021,0.004,0.031,0.003c0.021-0.002,0.017-0.023,0.03-0.032c0.008-0.006,0.019-0.007,0.027-0.012
+ c0.004-0.003,0.009-0.007,0.012-0.011c0.004-0.006,0.005-0.01,0.012-0.009c0.001,0.001,0.002,0.002,0.004,0.002
+ c0.003,0,0.003-0.001,0.005-0.002c0.005-0.002,0.009-0.005,0.013-0.008c0.004-0.004,0.005-0.009,0.01-0.013
+ c0.005-0.004,0.007-0.002,0.013-0.003c0.011-0.002,0.019-0.01,0.031-0.01c0.003,0,0.01,0.001,0.007-0.005
+ c-0.001-0.001-0.006-0.004-0.007-0.005c-0.003-0.003-0.005-0.005-0.008-0.006c0.001-0.006,0.008-0.004,0.012-0.004
+ c0.008-0.002,0.014-0.003,0.021-0.003c0.012,0,0.027,0.002,0.039,0c0.011-0.002,0.02-0.014,0.025-0.023
+ c0.003-0.005,0.004-0.009,0.009-0.013s0.011-0.005,0.017-0.005c0.01,0,0.018,0.004,0.028,0.005c0.005,0,0.009,0.002,0.014,0.004
+ c0.006,0.004,0.01,0.003,0.017,0.003c0.005,0.001,0.003,0,0.005,0.003c0.001,0.001,0.003,0.006,0.004,0.008
+ c0.003,0.006,0.006,0.012,0.007,0.018c0.002,0.008,0.001,0.016,0.002,0.024c0.001,0.007,0.003,0.012,0.003,0.02
+ c0,0.004-0.001,0.01,0,0.014c0,0.001,0.001,0.001,0.001,0.002c0.099-0.03-0.143,0.256,0.297-0.09s0.336,0.817-0.066,1.025
+ C-23.693,55.914-23.874,55.909-24.165,56.011z M-24.181,56.017c0.005-0.002,0.011-0.004,0.016-0.006
+ c0.017,0.006,0.034,0.012,0.051,0.018L-24.181,56.017z M-24.607,55.943l0.426,0.074c-0.05,0.017-0.099,0.035-0.148,0.052
+ C-24.422,56.027-24.515,55.985-24.607,55.943z M-33.449,52.473c0.015,0.018,0.025,0.032,0.022,0.057
+ c-0.003,0.021-0.001,0.046-0.026,0.05c-0.001-0.002-0.003-0.003-0.006-0.003h-0.005c-0.002-0.02-0.023-0.04-0.026-0.063
+ c-0.002-0.014,0.012-0.061,0.033-0.037C-33.455,52.475-33.452,52.475-33.449,52.473z M-27.922,53.12L-27.922,53.12L-27.922,53.12
+ L-27.922,53.12z M-25.706,56.522c-0.279,0.349-0.558,0.698-0.837,1.047c-0.001,0.012,0.003,0.025-0.001,0.036
+ c-0.004,0.013-0.015,0.02-0.02,0.032c-0.01,0.022-0.003,0.045-0.005,0.068c-0.003,0.021-0.017,0.037-0.022,0.058
+ c-0.007,0.022-0.001,0.045-0.004,0.068c-0.002,0.011-0.009,0.02-0.011,0.031c-0.002,0.012,0.001,0.025,0,0.037
+ c-0.002,0.019-0.012,0.037-0.015,0.058c-0.002,0.022,0.005,0.042,0.01,0.063c0.006,0.027,0.006,0.093,0.041,0.1
+ c0.004,0.025,0.002,0.056-0.031,0.057c-0.012,0.001-0.021-0.008-0.035-0.004c-0.014,0.003-0.024,0.019-0.038,0.02
+ c-0.024,0.002-0.036-0.029-0.058-0.027c-0.003,0-0.002,0.003-0.003,0.003c-0.019,0.067-0.038,0.134-0.057,0.201
+ c0.006,0.01,0.013,0.019,0.014,0.026c0.004,0.015-0.002,0.032-0.017,0.044c-0.005,0.005-0.012-0.001-0.017,0.002
+ c-0.092,0.323-0.183,0.646-0.274,0.969c0.091,0.148,0.182,0.296,0.274,0.444c-0.291-0.117-0.581-0.234-0.872-0.352
+ c0.122,0.03,0.231,0.045,0.318,0.038l-1.097-0.379c0,0.006-0.007,0.011-0.004,0.022c0,0.001,0.001,0.003,0.002,0.004
+ c0.26,0.105,0.521,0.21,0.781,0.315c-0.216-0.052-0.483-0.162-0.78-0.312c0.002,0.006,0.004,0.014,0.007,0.019
+ c0.008,0.01,0.009,0.005,0.02,0.011c0.018,0.01,0.024,0.026,0.038,0.04c0.028,0.03,0.071,0.05,0.105,0.068
+ c0.035,0.019,0.074,0.04,0.111,0.053c0.02,0.006,0.043,0.012,0.062,0.016c0.016,0.004,0.037,0.006,0.052,0.012
+ c0.021,0.009,0.039,0.025,0.059,0.034c0.018,0.009,0.029,0.022,0.046,0.032c0.018,0.01,0.04,0.019,0.059,0.026
+ c0.026,0.011,0.024,0.011,0.039,0.037c0.014,0.024,0.041,0.018,0.065,0.027c0.024,0.01,0.041,0.034,0.058,0.053
+ c0.022,0.026,0.06,0.041,0.094,0.052c0.018,0.006,0.036,0.008,0.053,0.015c0.019,0.007,0.037,0.02,0.052,0.033
+ c0.016,0.015,0.027,0.03,0.047,0.041c0.019,0.011,0.033,0.017,0.048,0.032c0.011,0.01,0.026,0.027,0.039,0.033
+ c0.016,0.006,0.037,0.009,0.054,0.014c0.022,0.006,0.045,0.001,0.067,0.006c0.008,0.002,0.019,0.007,0.028,0.009
+ c0.011,0.003,0.02,0.004,0.031,0.006c0.022,0.003,0.041,0.007,0.063,0.01c0.014,0.002,0.04,0.013,0.036,0.031
+ c-0.022-0.001-0.039,0.007-0.062,0.002c-0.019-0.004-0.036-0.005-0.057-0.008c-0.016-0.001-0.037-0.005-0.053-0.009
+ c-0.021-0.004-0.043-0.002-0.063-0.006c-0.019-0.005-0.034-0.016-0.053-0.02c-0.025-0.006-0.051-0.002-0.076-0.012
+ c-0.017-0.007-0.033-0.021-0.049-0.025c-0.018-0.004-0.04,0.002-0.058,0c-0.042-0.003-0.08-0.034-0.121-0.047
+ c-0.019-0.006-0.035-0.012-0.053-0.012c-0.02,0.001-0.037,0.004-0.053-0.008c-0.006-0.004-0.01-0.011-0.015-0.016
+ c-0.019-0.019-0.029-0.02-0.055-0.023c-0.019-0.002-0.045,0-0.064-0.005c-0.016-0.005-0.034-0.015-0.05-0.021
+ c-0.011-0.003-0.024-0.013-0.034-0.015c-0.008-0.002-0.019,0.004-0.026-0.001c-0.026-0.015-0.012-0.051-0.048-0.057
+ c-0.031-0.004-0.031-0.006-0.051-0.031c-0.025-0.03-0.056-0.057-0.091-0.074c-0.024-0.012-0.049-0.022-0.073-0.035
+ c-0.009-0.004-0.016-0.013-0.026-0.016c-0.011-0.002-0.022,0.002-0.032-0.002c-0.013-0.004-0.022-0.012-0.035-0.016
+ c-0.008-0.001-0.017-0.004-0.024-0.007c-0.01-0.005-0.014-0.01-0.025-0.012c-0.011-0.003-0.019-0.003-0.031-0.007
+ c-0.046-0.017-0.088-0.037-0.136-0.047c-0.032-0.006-0.06-0.024-0.09-0.034c-0.019-0.007-0.03-0.006-0.047-0.018
+ c-0.019-0.014-0.036-0.026-0.052-0.041c-0.017-0.017-0.039-0.023-0.058-0.037s-0.029-0.038-0.046-0.056
+ c-0.019-0.021-0.044-0.041-0.069-0.054c-0.029-0.016-0.058-0.041-0.089-0.051c-0.031-0.01-0.052-0.018-0.079-0.033
+ c-0.031-0.017-0.065-0.026-0.096-0.043c-0.013-0.008-0.027-0.013-0.04-0.021c-0.013-0.008-0.018-0.016-0.028-0.025
+ c-0.019-0.018-0.041-0.033-0.06-0.052c-0.019-0.019-0.043-0.015-0.065-0.031c-0.013-0.009-0.016-0.029-0.027-0.037
+ c-0.005-0.004-0.023-0.004-0.03-0.006c-0.02-0.006-0.035-0.01-0.041-0.031c-0.006-0.021-0.007-0.043-0.011-0.063
+ c-0.004-0.019,0-0.032,0.005-0.053c0.006-0.029,0.005-0.038-0.01-0.062c-0.012-0.021-0.01-0.029-0.038-0.032
+ c-0.028-0.003-0.03,0.016-0.053,0.025c-0.018,0.008-0.051,0.003-0.057-0.014c-0.004-0.012,0.005-0.023-0.003-0.033
+ c-0.006-0.008-0.019-0.011-0.027-0.016c-0.013-0.009-0.021-0.035-0.037-0.036c-0.002-0.013-0.002-0.021-0.005-0.03
+ c-0.021-0.006-0.049-0.03-0.066-0.043c-0.044-0.033-0.081-0.077-0.119-0.115c-0.017-0.017-0.032-0.033-0.048-0.052
+ c-0.012-0.015-0.028-0.02-0.042-0.032c-0.034-0.028-0.047-0.063-0.068-0.1c-0.012-0.021-0.04-0.029-0.05-0.051
+ c-0.001-0.002,0-0.004-0.001-0.007c-0.446-0.335-0.894-0.711-1.316-1.103c0.004,0.013,0.005,0.026,0.012,0.039
+ c0.005,0.008,0.01,0.012,0.014,0.021c0.007,0.017,0.009,0.036,0.016,0.053c0.018,0.046,0.042,0.088,0.053,0.136
+ c0.007,0.026,0.015,0.047,0.029,0.069c0.009,0.012,0.033,0.059,0.028,0.067l-0.011-0.005c-0.024-0.008-0.045-0.037-0.067-0.052
+ c-0.018-0.011-0.025-0.06-0.037-0.085c-0.013-0.027-0.034-0.048-0.05-0.074c-0.014-0.021-0.023-0.052-0.055-0.052
+ c0.003-0.024-0.014-0.031-0.027-0.047c-0.012-0.016-0.023-0.031-0.036-0.046c-0.027-0.029-0.05-0.068-0.065-0.104
+ c-0.004-0.01,0-0.02-0.003-0.032c-0.003-0.01-0.011-0.017-0.016-0.027c-0.007-0.014-0.009-0.032-0.017-0.047
+ c-0.006-0.011-0.021-0.019-0.026-0.031c-0.004-0.013-0.001-0.019-0.008-0.032c-0.002-0.003-0.004-0.006-0.006-0.008
+ c-1.279-1.251-2.211-2.634-1.778-3.512c0.001-0.001,0.002-0.001,0.004-0.002c-0.003-0.011-0.007-0.023-0.011-0.037
+ c-0.017-0.067,0.002-0.076,0.068-0.074c0.017,0.021,0.042,0.044,0.046,0.072c0.086-0.033,0.173-0.067,0.259-0.1
+ c-0.061-0.188-0.123-0.376-0.184-0.564c-0.002-0.002-0.005-0.002-0.009-0.006c0.002,0.002-0.034-0.046-0.033-0.041
+ c-0.001-0.001-0.002-0.002-0.003-0.002c0.006-0.01,0.011-0.023,0.018-0.035c-0.015-0.046-0.03-0.092-0.045-0.138
+ c-0.008-0.005-0.018-0.008-0.042-0.015c-0.043-0.01-0.053-0.017-0.072-0.058c-0.048-0.106-0.079-0.23-0.112-0.343
+ c0.001,0.002,0.002,0.003,0.005,0.002c0.008-0.042-0.03-0.102-0.042-0.14c-0.015-0.053-0.027-0.105-0.042-0.158
+ c-0.019-0.062-0.091-0.233-0.022-0.289c-0.041-0.125-0.082-0.25-0.123-0.375c0.346-0.283,0.692-0.566,1.038-0.85
+ c-0.001-0.001-0.002-0.002-0.003-0.003c-0.003-0.005-0.003-0.008-0.004-0.013c-0.001-0.003-0.003-0.004-0.003-0.008
+ c0-0.003,0-0.006,0-0.008c0-0.003-0.002-0.006-0.003-0.008c-0.001-0.006,0.003-0.011,0.003-0.016c0-0.007-0.001-0.008-0.003-0.013
+ c-0.003-0.009-0.009-0.015-0.013-0.023c-0.004-0.008-0.004-0.019-0.008-0.029c-0.003-0.006-0.006-0.011-0.011-0.016
+ c-0.005-0.006-0.007-0.014-0.011-0.019c0.003-0.004,0.01-0.003,0.011-0.008c0-0.007-0.017-0.004-0.021-0.004
+ c-0.018,0-0.034,0.005-0.052,0.004c-0.014-0.001-0.026-0.008-0.039-0.011c-0.016-0.004-0.03,0.009-0.045,0.011
+ c-0.006,0.001-0.013-0.002-0.02,0.001c-0.008,0.003-0.009,0.008-0.019,0.007c-0.014-0.001-0.023-0.008-0.036,0
+ c-0.006,0.003-0.011,0.008-0.017,0.012c-0.01,0.006-0.021,0.008-0.031,0.013c-0.017,0.007-0.035,0.009-0.052,0.014
+ c-0.034,0.008-0.062,0.022-0.093,0.037c-0.014,0.007-0.032,0.009-0.048,0.014c-0.016,0.006-0.031,0.016-0.047,0.023
+ c-0.012,0.005-0.026,0.012-0.039,0.015c-0.004,0-0.008,0-0.012-0.001c-0.005,0.001-0.007,0.002-0.012,0.003
+ c-0.007,0.002-0.014,0.005-0.02,0.006c-0.013,0.002-0.029-0.005-0.04,0.002c-0.006,0.004-0.008,0.01-0.016,0.013
+ c-0.007,0.001-0.017,0.002-0.023,0c0-0.007-0.005-0.008-0.008-0.012c-0.004-0.004-0.004-0.006-0.005-0.011
+ c-0.002-0.009-0.005-0.017-0.004-0.026c-0.001-0.007-0.002-0.015,0-0.022c0.002-0.016,0.015-0.028,0.023-0.041
+ c0.007-0.012,0.006-0.026,0.014-0.038c0.005-0.009,0.006-0.017,0.01-0.026c0.003-0.005,0.01-0.01,0.012-0.015
+ c0.002-0.004,0-0.008,0.002-0.011c0.006-0.012,0.019-0.021,0.027-0.033c0.004-0.007,0.005-0.016,0.008-0.024
+ c0.002-0.006,0.004-0.013,0.004-0.02v-0.02c0-0.003,0.001-0.009,0-0.012c-0.001-0.003-0.004-0.004-0.005-0.008
+ c-0.003-0.014,0.007-0.032-0.004-0.044c-0.006-0.005-0.012-0.005-0.018-0.008c-0.008-0.004-0.014-0.01-0.021-0.015
+ c-0.014-0.007-0.023-0.005-0.036,0.003c-0.006,0.003-0.01,0.007-0.012-0.003c-0.002-0.007,0-0.017,0-0.024
+ c0-0.009-0.005-0.013-0.004-0.021c0-0.005,0.006-0.01,0.009-0.014c0.006-0.01,0.012-0.02,0.023-0.026
+ c0.011-0.007,0.019-0.015,0.025-0.027c0.003-0.007,0.006-0.014,0.01-0.02c0.001-0.003,0.003-0.006,0.005-0.01
+ c0.001-0.002,0.001-0.005,0.002-0.008c0.001-0.006,0.004-0.01,0.006-0.016c0.001-0.003,0.002-0.004,0.003-0.007
+ c0.001-0.006,0-0.013,0.008-0.014l0,0c0.005-0.008,0.011-0.015,0.016-0.023c0.001-0.002,0.002-0.004,0.004-0.006
+ c0.003-0.006,0.006-0.009,0.01-0.013c0.005-0.006,0.009-0.012,0.013-0.018c0.004-0.007,0.005-0.016,0.009-0.023
+ c0.004-0.008,0.012-0.014,0.015-0.024c0.006-0.02-0.003-0.035-0.023-0.023c-0.016,0.009-0.031,0.021-0.048,0.03
+ c-0.025,0.012-0.013-0.02-0.004-0.03c0.013-0.015,0.03-0.023,0.042-0.039c0.012-0.014,0.015-0.035,0.029-0.047
+ c0.012-0.01,0.033-0.022,0.047-0.024c0.021-0.004,0.035-0.013,0.053-0.023c0.01-0.006,0.027-0.012,0.038-0.015
+ c0.006-0.001,0.01,0,0.015-0.001c0.005,0,0.007-0.003,0.01-0.003c0.011-0.002,0.014-0.002,0.023-0.007
+ c0.01-0.006,0.019-0.012,0.029-0.017c0.018-0.012,0.04-0.021,0.052-0.04c0.005-0.006,0.007-0.016,0.014-0.02
+ c0.007-0.005,0.016-0.006,0.023-0.009c0.018-0.009,0.024-0.022,0.035-0.037c0.003-0.005,0.01-0.016,0.015-0.019
+ c0.013-0.005,0.015,0.009,0.013,0.018c-0.003,0.009-0.013,0.02-0.018,0.029c-0.006,0.01-0.01,0.019-0.015,0.029
+ c-0.006,0.015-0.011,0.028-0.02,0.043c-0.006,0.009-0.01,0.019-0.017,0.029c-0.005,0.007-0.01,0.011-0.014,0.019
+ c-0.003,0.006-0.011,0.017-0.011,0.024c-0.001,0.01,0.007,0.012,0.004,0.022c-0.002,0.008-0.01,0.017-0.018,0.02
+ c-0.005,0.001-0.009,0-0.015,0.001c-0.004,0-0.007,0.003-0.01,0.003c-0.008,0.002-0.017-0.001-0.022,0.007
+ c-0.003,0.005,0.001,0.018,0.005,0.022c0.003,0.003,0.008,0.003,0.012,0.007c0.003,0.003,0.005,0.004,0.006,0.008
+ c0.001,0.004-0.001,0.01,0,0.015c0.001,0.006,0.001,0.005,0.003,0.01c0.004,0.008,0.009,0.014,0.013,0.022
+ c0.002,0.005,0.003,0.004,0.003,0.011c0,0.006-0.003,0.006-0.004,0.01c-0.001,0.006-0.001,0.012,0,0.018
+ c0.005,0.002,0.01,0.003,0.016,0.003c0.009,0.001,0.019-0.002,0.028,0c0.008,0.002,0.01,0.006,0.02,0.003
+ c0.008-0.002,0.015-0.002,0.024-0.003c0.003,0,0.009-0.003,0.012-0.003c0.004,0,0.003,0.003,0.007,0.003
+ c0.008,0.002,0.014,0.001,0.021,0.007c0.01,0.009,0.001,0.033-0.001,0.045c-0.005,0.018-0.001,0.035,0.007,0.052
+ c0.013,0.026,0.064-0.008,0.072-0.025c0.002-0.004,0.001-0.005,0.005-0.008c0.003-0.003,0.008-0.001,0.011-0.004
+ c0.007-0.005,0.011-0.015,0.009-0.023c-0.001-0.009-0.022-0.045-0.001-0.045c0.001-0.005,0.004-0.009,0.006-0.014
+ c0.003-0.005,0.003-0.01,0.004-0.016c0.001-0.005,0.007-0.023,0.014-0.024c0.004,0.007,0.014-0.001,0.017-0.004
+ c0.005-0.005,0.007-0.019,0.016-0.017c0.009,0.002,0.011,0.017,0.013,0.023c0.001,0.006,0.004,0.006,0.002,0.013
+ c-0.001,0.006-0.002,0.011-0.004,0.016c-0.003,0.012-0.003,0.023-0.003,0.036c0,0.011,0.001,0.02-0.003,0.029
+ c-0.008,0.017-0.007,0.035,0.003,0.051c0.009,0.014,0.024,0.027,0.037,0.039c0.008,0.007,0.016,0.005,0.026,0.005
+ c0.016,0,0.034,0,0.048-0.009c0.015-0.01,0.028-0.021,0.044-0.028c0.019-0.008,0.049-0.011,0.066-0.03
+ c0.008,0.003,0.009,0.003,0.018,0c0.009-0.003,0.016-0.005,0.023-0.01c0.006-0.003,0.011-0.01,0.017-0.011
+ c0.004-0.001,0.009,0.001,0.012,0c0.005-0.001,0.007-0.003,0.012-0.004c0.008-0.001,0.016,0.001,0.023-0.001
+ c0.009-0.002,0.019-0.006,0.029-0.007c0.007,0,0.015-0.001,0.02,0.004c0.008,0.007,0.001,0.014,0.004,0.024
+ c0.004,0.015,0.022,0.018,0.013,0.036c-0.011,0.021-0.032,0.027-0.049,0.044c-0.014,0.013-0.026,0.028-0.039,0.041
+ c-0.011,0.011-0.025,0.017-0.036,0.028c-0.008,0.007-0.02,0.016-0.025,0.026c-0.003,0.008-0.003,0.013-0.009,0.019
+ c-0.006,0.007-0.013,0.009-0.019,0.017c-0.007,0.012-0.017,0.02-0.027,0.029c-0.008,0.007-0.014,0.013-0.021,0.02
+ c-0.004,0.005-0.009,0.007-0.013,0.011c-0.002,0.004-0.004,0.009-0.006,0.012c-0.009,0.012-0.02,0.02-0.031,0.029
+ c-0.005,0.004-0.011,0.009-0.014,0.014c-0.005,0.006-0.007,0.014-0.011,0.021c-0.005,0.007-0.002,0.012-0.004,0.02
+ c-0.002,0.007-0.009,0.013-0.011,0.02c-0.005,0.015-0.001,0.036-0.001,0.052c0,0.008-0.003,0.012-0.004,0.02
+ c-0.002,0.006,0.001,0.013,0,0.02c-0.001,0.006-0.003,0.009-0.004,0.015c-0.001,0.007,0,0.014,0,0.021
+ c-0.001,0.012-0.006,0.027-0.009,0.039c-0.001,0.005-0.001,0.009-0.002,0.013c0.08-0.066,0.159-0.131,0.239-0.196
+ c0.048,0.003,0.096,0.005,0.144,0.008c0.001-0.004,0-0.007,0.002-0.01c0.002-0.004,0.005-0.01,0.009-0.013
+ c0.004-0.004,0.01-0.003,0.014-0.009c0.008-0.011,0.004-0.027,0.005-0.04c0-0.018,0.014-0.018,0.028-0.025
+ c0.011-0.006,0.02-0.012,0.031-0.02c0.012-0.008,0.024-0.003,0.036-0.012c0.006-0.004,0.011-0.009,0.017-0.014
+ c0.005-0.004,0.008-0.004,0.015-0.006c0.009-0.002,0.009-0.003,0.016,0.001c0.006,0.004,0.008,0.004,0.007,0.011
+ c0,0.003-0.005,0.008-0.006,0.01c-0.002,0.006-0.005,0.013-0.005,0.019c0,0.012,0.003,0.027-0.003,0.037
+ c-0.006,0.011-0.021,0.016-0.029,0.026c-0.007,0.008-0.01,0.018-0.019,0.026c-0.002,0.002-0.006,0.005-0.008,0.008
+ c-0.002,0.003-0.002,0.011-0.005,0.014c-0.001,0.001-0.003,0.001-0.005,0.002c0.022,0.001,0.043,0.002,0.064,0.003
+ c0.002-0.006,0.006-0.013,0.008-0.018c0.008-0.024,0.023-0.032,0.045-0.045c0.022-0.012,0.043-0.01,0.067-0.021
+ c0.013-0.006,0.024-0.014,0.037-0.019c0.016-0.007,0.039-0.005,0.054-0.011c0.048-0.02,0.066-0.083,0.096-0.12
+ c0.018-0.021,0.03-0.027,0.055-0.019c0.012,0.004,0.021,0.013,0.032,0.016c0.021,0.007,0.044,0.005,0.064,0.011
+ c0.026,0.009,0.043,0.024,0.059,0.046c0.009,0.012,0.013,0.026,0.021,0.038c0.006,0.009,0.02,0.018,0.025,0.026
+ c0.01,0.017,0.007,0.045,0.005,0.066c-0.005,0.043,0.01,0.019,0.035,0.044c0.015,0.014,0.004,0.026-0.002,0.038
+ c0.293,0.016,0.586,0.031,0.879,0.047c0-0.008-0.001-0.017,0.001-0.024c0.004-0.014,0.016-0.024,0.019-0.037
+ c-0.001,0.006,0.001,0.011,0.001,0.016c0.02-0.01,0.041-0.019,0.062-0.026c0.02-0.008,0.043-0.037,0.059-0.037
+ c0.012,0,0.047,0.023,0.06,0.03c0.035,0.017,0.058,0.028,0.098,0.028l0,0c0.32-0.095,0.639-0.19,0.958-0.285
+ c0.003-0.004,0.006-0.008,0.01-0.012c0.019-0.019,0.042-0.016,0.061-0.031c0.016-0.013,0.016-0.038,0.023-0.056
+ c0.016-0.04,0.063-0.047,0.1-0.04c0.003,0.006,0.007,0.008,0.013,0.009c0.006-0.001,0.011-0.004,0.013-0.009
+ c0.01-0.003,0.014-0.004,0.022-0.01c0.009-0.007,0.027-0.023,0.029-0.034c0.005-0.022-0.051-0.075-0.003-0.083
+ c0.018,0.021,0.04,0.04,0.055,0.062c0.016,0.021,0.032,0.044,0.05,0.065c0.005,0.006,0.01,0.015,0.017,0.024
+ c0.129-0.039,0.258-0.077,0.387-0.116c0.024,0.045,0.047,0.09,0.07,0.135c-0.204,0.679-0.937,1.674-0.611,2.037
+ c0.325,0.363,0.981,0.295,1.472,0.442c0.069,0.151,0.138,0.302,0.207,0.454c-0.111,0.243-0.223,0.487-0.334,0.73
+ c0.051,0.247,0.101,0.495,0.152,0.742c0.02,0.015,0.036,0.036,0.042,0.045c0.021,0.033,0.016,0.069,0.028,0.103
+ c0.007,0.016,0.016,0.033,0.02,0.049c0.003,0.016-0.008,0.032-0.008,0.046c-0.001,0.012,0.019,0.024,0.027,0.035
+ c0.012,0.015,0.016,0.03,0.019,0.045c0.009,0.037,0.011,0.074,0,0.111c-0.006,0.022,0.009,0.04,0.008,0.062
+ c0,0.02-0.001,0.04,0,0.059c0.001,0.01,0.007,0.019,0.006,0.028c-0.001,0.009-0.011,0.018-0.018,0.027
+ c0.045,0.223,0.091,0.445,0.136,0.668c0.483,0.384,0.967,0.768,1.45,1.152c-0.011,0.005-0.021,0.011-0.032,0.016
+ c0.032,0.006,0.083,0.008,0.097,0.036c-0.026,0.011-0.052,0.025-0.078,0.032c-0.021,0.005-0.039,0.021-0.059,0.025
+ c-0.022,0.006-0.046,0.005-0.067,0.013c-0.002,0.001-0.004,0.002-0.006,0.003c0.041,0.054,0.082,0.109,0.124,0.164
+ c0.047-0.014,0.094-0.027,0.141-0.04c-0.014-0.016-0.025-0.039-0.028-0.05c-0.002-0.012-0.006-0.038,0-0.049
+ c0.007-0.012,0.024-0.012,0.033-0.024c0.02-0.028-0.005-0.064,0.023-0.084c0.028-0.02,0.058,0.011,0.085,0.021
+ c0.031,0.012,0.067,0.008,0.065,0.05c-0.003,0.038-0.052,0.063-0.079,0.084c-0.008,0.006-0.024,0.015-0.027,0.025
+ c-0.001,0.002,0.001,0.004,0.001,0.006C-25.854,56.564-25.78,56.543-25.706,56.522z M-29.093,49.403
+ c-0.006,0.016-0.011,0.036-0.032,0.033c-0.008-0.002-0.017-0.013-0.026-0.025l0.016,0.024c-0.018,0.002-0.027-0.025-0.031-0.046
+ l0.005,0.008c-0.001-0.003-0.003-0.006-0.005-0.008c-0.001-0.007-0.001-0.013-0.001-0.018c0-0.021,0.011-0.037,0.018-0.055
+ c0.006-0.015,0.008-0.046,0.024-0.051c0.019-0.006,0.034,0.02,0.043,0.032c0.016,0.026,0.002,0.027-0.001,0.053
+ C-29.085,49.369-29.086,49.386-29.093,49.403z M-29.177,49.371c0.003,0.004,0.007,0.01,0.011,0.018l0,0L-29.177,49.371z
+ M-30.756,48.278c-0.003,0.006-0.009,0.011-0.011,0.018c0,0.002-0.001,0.005-0.001,0.007c-0.002,0.002-0.004,0.005-0.006,0.007
+ c-0.002,0.006-0.002,0.012-0.005,0.018c-0.004,0.01-0.003,0.021-0.006,0.031c-0.006,0.016-0.011,0.032-0.019,0.047
+ c-0.008,0.019-0.025,0.034-0.038,0.051c-0.009,0.011-0.017,0.022-0.026,0.033c-0.006,0.008-0.012,0.016-0.017,0.024
+ c-0.01,0.017-0.014,0.037-0.026,0.053c-0.007,0.01-0.017,0.019-0.024,0.029c-0.009,0.012-0.016,0.023-0.025,0.034
+ c-0.006,0.006-0.01,0.014-0.017,0.02c-0.006,0.006-0.014,0.011-0.019,0.018c-0.007,0.011-0.016,0.019-0.029,0.022
+ c-0.01,0.001-0.022-0.003-0.032,0c-0.004,0.001-0.007,0.006-0.01,0.008c-0.006,0.003-0.009,0.002-0.016,0.002l-0.01-0.007
+ c-0.003-0.003-0.007-0.004-0.011-0.006c-0.008-0.007-0.012-0.018-0.018-0.026c-0.006-0.007-0.007-0.014-0.012-0.021
+ c-0.004-0.006-0.009-0.009-0.013-0.016c-0.004-0.006-0.009-0.012-0.015-0.018c-0.006-0.005-0.012-0.008-0.017-0.013
+ c-0.006-0.005-0.008-0.015-0.01-0.022c-0.002-0.007-0.005-0.013-0.005-0.021c0.001-0.008-0.003-0.014-0.003-0.022
+ c-0.001-0.008,0.001-0.017,0-0.025c-0.003-0.032-0.026-0.059-0.033-0.089c-0.004-0.014-0.005-0.027,0.008-0.037
+ c0.005-0.004,0.012-0.006,0.018-0.009c0.008-0.005,0.017-0.01,0.025-0.015c0.006-0.003,0.012-0.008,0.018-0.012
+ c0.007-0.006,0.011-0.014,0.018-0.02c0.005-0.004,0.01-0.006,0.014-0.011c0.004-0.004,0.005-0.009,0.009-0.013
+ c0.008-0.011,0.022-0.021,0.033-0.029c0.01-0.008,0.018-0.016,0.029-0.022c0.013-0.008,0.023-0.02,0.036-0.029
+ c0.008-0.006,0.014-0.012,0.022-0.018c0.007-0.004,0.013-0.007,0.02-0.011c0.012-0.008,0.026-0.011,0.038-0.017
+ c0.005-0.002,0.01-0.003,0.014-0.007c0.008-0.005,0.017-0.009,0.024-0.015c0.005-0.004,0.01-0.008,0.014-0.01
+ c0.007-0.005,0.014-0.01,0.022-0.014c0.006-0.003,0.011-0.005,0.018-0.004c0.009,0.003,0.016,0.006,0.024,0.009
+ c0.011,0.005,0.02,0.009,0.03,0.015c0.004,0.002,0.007,0.005,0.011,0.007c0.008,0.004,0.008,0.004,0.01,0.012
+ c0.005,0.013,0.002,0.026,0.01,0.039c0.006,0.01,0.026,0.011,0.03,0.021c0.003,0.01-0.003,0.024-0.008,0.032
+ S-30.752,48.27-30.756,48.278z M-30.84,48.052c0,0-0.007,0.007-0.007,0.008c-0.004,0.004-0.006,0.005-0.01,0.008
+ c-0.008,0.005-0.014,0.014-0.022,0.019c-0.01,0.008-0.023,0.016-0.035,0.019c-0.012,0.003-0.023,0.006-0.033,0.014
+ c-0.011,0.007-0.021,0.016-0.032,0.023c-0.003,0.002-0.015,0.009-0.019,0.009c-0.006,0.001-0.014-0.005-0.02-0.01l0.002-0.003
+ c-0.004-0.005-0.005-0.009-0.007-0.015c-0.002-0.007,0-0.014-0.002-0.022c-0.001-0.005-0.006-0.007-0.004-0.014
+ c0.001-0.006,0.006-0.01,0.01-0.014c0.006-0.006,0.01-0.018,0.014-0.026c0.008-0.016,0.019-0.02,0.033-0.029
+ c0.008-0.004,0.011-0.002,0.018,0.001c0.009,0.002,0.011-0.002,0.017-0.008c0.009-0.008,0.019-0.015,0.029-0.024
+ c0.012-0.01,0.022-0.022,0.037-0.029c0.004-0.002,0.009-0.005,0.014-0.007c0.004-0.002,0.012-0.004,0.017-0.002
+ c0.027,0.01,0,0.046,0.001,0.063C-30.839,48.024-30.836,48.042-30.84,48.052z M-31.022,48.138c0.001,0.001,0.003,0.003,0.004,0.004
+ l0,0L-31.022,48.138z M-31.215,48.707c0.002,0.008,0.007,0.015,0.011,0.022c0.005,0.007,0.008,0.014,0.01,0.021
+ c0.003,0.007,0.006,0.014,0.003,0.022c-0.004,0.011-0.009,0.008-0.017,0.01c-0.013,0.003-0.024,0.015-0.035,0.021
+ c-0.006,0.003-0.013,0.005-0.019,0.008c-0.004,0.003-0.008,0.005-0.013,0.007c-0.005,0.002-0.01,0.006-0.015,0.007
+ c-0.005,0.001-0.009,0-0.014,0c-0.009,0.002-0.018,0.004-0.027,0.007c-0.023,0.006-0.046,0.007-0.07,0.013
+ c-0.015,0.004-0.03,0.005-0.046,0.008c-0.013,0.003-0.024,0.007-0.036,0.011c-0.038,0.011-0.072,0.036-0.104,0.057
+ c-0.018,0.012-0.034,0.018-0.054,0.013c-0.01-0.003-0.02,0.001-0.029,0.006c-0.014,0.007-0.025,0.015-0.04,0.021
+ c-0.012,0.004-0.029,0.007-0.042,0.008c-0.006,0-0.012-0.001-0.018-0.001c-0.01,0.001-0.02,0.004-0.029,0.004
+ c-0.009,0.001-0.016-0.001-0.024,0.001c-0.008,0.001-0.014,0.003-0.022,0.003c-0.017,0-0.033,0.002-0.05,0.003
+ c-0.007,0-0.012,0.003-0.018,0.004c-0.009,0.001-0.019,0-0.028,0c-0.01,0-0.017,0.001-0.026,0.003c-0.01,0.001-0.019-0.001-0.028,0
+ c-0.007,0.001-0.014,0.004-0.021,0.004c-0.011,0.001-0.022,0-0.033,0c-0.02,0-0.04,0-0.061,0c-0.023,0-0.053-0.005-0.071-0.021
+ c-0.009-0.009-0.017-0.016-0.025-0.025c-0.004-0.003-0.007-0.007-0.011-0.011c-0.005-0.002-0.012-0.003-0.017-0.005
+ c-0.006-0.002-0.013-0.006-0.018-0.01c-0.01-0.008-0.01-0.018-0.015-0.028c-0.003-0.007-0.007-0.015-0.011-0.022
+ c-0.006-0.01-0.002-0.019,0.001-0.031c0.001-0.004,0.002-0.008,0.004-0.012c-0.004-0.014-0.005-0.028-0.008-0.042
+ c-0.002-0.015,0.011-0.033,0.017-0.047c0.002-0.007,0.006-0.014,0.004-0.022c-0.004-0.009-0.01-0.017-0.011-0.027
+ c-0.001-0.013-0.006-0.024,0.001-0.037c0.002-0.005,0.004-0.009,0.008-0.013c0.005-0.006,0.012-0.01,0.017-0.016
+ c0.005-0.006,0.008-0.009,0.018-0.007c0.003,0.001,0.003,0.003,0.006,0.004c0.004,0.001,0.007,0,0.011,0.001
+ c0.008,0.002,0.013,0.004,0.022,0.002c0.003-0.001,0.007-0.003,0.011-0.004c0.005-0.002,0.008-0.001,0.014-0.002
+ c0.004-0.001,0.006-0.004,0.011-0.004c0.003,0.001,0.007,0.003,0.01,0.003c0.004,0.001,0.008,0,0.011,0
+ c0.004,0.001,0.006,0.003,0.008,0.003c0.007,0.002,0.014,0.001,0.021,0.001c0.009,0,0.016,0.006,0.025,0.007
+ c0.003,0.001,0.007-0.001,0.011,0c0.003,0.001,0.003,0.003,0.006,0.004c0.007,0.001,0.016,0,0.022,0c0.017,0,0.03,0.001,0.042-0.012
+ c0.009-0.009,0.012-0.023,0.022-0.032c0.003-0.004,0.008-0.007,0.011-0.011c0.006-0.006,0.011-0.007,0.018-0.011
+ c0.015-0.007,0.022-0.023,0.03-0.037c0.007-0.013,0.017-0.024,0.024-0.038c0.005-0.012,0.009-0.026,0.014-0.038
+ c0.002-0.006,0.009-0.013,0.01-0.019c0.001-0.003,0-0.007,0.001-0.01c0-0.003,0.003-0.004,0.004-0.007
+ c0-0.005-0.002-0.007-0.003-0.011c-0.002-0.006-0.002-0.012-0.004-0.018c-0.006-0.023-0.004-0.043,0.003-0.064
+ c0.005-0.016,0.011-0.03,0.011-0.047c-0.001-0.016,0-0.035,0.021-0.035c0.017,0,0.021,0.014,0.033,0.024
+ c0.012,0.009,0.027,0.002,0.038-0.003c0.014-0.006,0.027-0.017,0.037-0.028c0.006-0.007,0.014-0.015,0.017-0.023
+ c0.007-0.013,0.012-0.028,0.018-0.042c0.009-0.018,0.018-0.037,0.029-0.054c0.005-0.009,0.011-0.015,0.013-0.024
+ c0.002-0.01-0.002-0.015-0.007-0.022c-0.007-0.012-0.009-0.029-0.006-0.043c0.001-0.008,0.003-0.013,0.003-0.022
+ c0-0.007,0-0.014,0-0.021c0.001-0.02,0.007-0.042,0.014-0.061c0.007-0.019,0.005-0.04,0.009-0.06
+ c0.003-0.019,0.013-0.039,0.013-0.058c0-0.013,0.003-0.03-0.001-0.043c-0.001-0.006-0.005-0.012-0.006-0.018
+ c-0.002-0.007-0.002-0.014-0.003-0.021c-0.001-0.004-0.004-0.004-0.004-0.007c-0.001-0.003,0-0.008,0-0.011
+ c0-0.007-0.001-0.015,0.001-0.021c0.004-0.016,0.005-0.03,0.014-0.043c0.008-0.012,0.019-0.019,0.025-0.032
+ c0.004-0.008,0.02-0.028,0.031-0.026c0.005,0.001,0.011,0.008,0.014,0.012c0.006,0.008,0.004,0.012,0.001,0.02
+ c-0.004,0.017,0.001,0.031,0.01,0.044c0.008,0.014,0.011,0.027,0.016,0.042c0.005,0.017,0.015,0.016,0.028,0.021
+ c0.018,0.007,0.022,0.024,0.028,0.04c0.002,0.006,0.006,0.013,0.007,0.018c0.001,0.003-0.001,0.008,0,0.011
+ c0.001,0.003,0.003,0.008,0.004,0.01c0.004,0.013,0.003,0.026,0.003,0.04c0,0.006,0.002,0.015,0,0.022
+ c-0.001,0.004-0.008,0.016-0.003,0.02c0.01,0.009,0.018-0.03,0.033-0.014c0.009,0.01,0.012,0.031,0.009,0.043
+ c-0.001,0.005-0.003,0.007-0.003,0.011c0,0.004,0.001,0.008,0,0.011c-0.001,0.005-0.005,0.013-0.007,0.018
+ c-0.003,0.006-0.007,0.009-0.011,0.014c-0.003,0.006-0.006,0.012-0.007,0.018c-0.001,0.008,0.001,0.015-0.002,0.022
+ c-0.006,0.014-0.015,0.012-0.026,0.018c-0.008,0.005-0.01,0.012-0.011,0.021c-0.001,0.005-0.001,0.01,0.001,0.014
+ c0.001,0.006,0.001,0.013,0.003,0.018c0.005,0.016,0.017,0.029,0.023,0.043c0.004,0.007,0.008,0.014,0.016,0.017
+ c0.009,0.004,0.016-0.001,0.021-0.006c0.01-0.011,0.017-0.029,0.022-0.043c0.007-0.016,0-0.034,0.017-0.044
+ c0.014-0.007,0.018,0.01,0.025,0.019c0.004,0.005,0.012,0.009,0.018,0.011c0.009,0.001,0.018-0.006,0.025-0.003
+ c0.004,0.001,0.007,0.008,0.01,0.011c0.005,0.003,0.01,0.004,0.015,0.007c0.007,0.005,0.014,0.012,0.019,0.019
+ c0.009,0.012,0.01,0.02,0.01,0.034v0.06c0,0.019,0.003,0.04-0.004,0.058c-0.002,0.007-0.003,0.013-0.003,0.021
+ s-0.003,0.014-0.004,0.021c0,0.004,0.001,0.008,0.001,0.011c-0.001,0.004-0.004,0.007-0.005,0.011c0,0.005,0.001,0.01,0,0.015
+ c-0.001,0.008-0.004,0.017-0.007,0.025c-0.006,0.013-0.012,0.027-0.014,0.042c0,0.008-0.003,0.014-0.004,0.022
+ c-0.003,0.017-0.003,0.03,0,0.048c0.002,0.01,0,0.02,0.001,0.03c0,0.008,0.003,0.016,0.004,0.024c0.002,0.01,0.003,0.02,0.005,0.03
+ c0.003,0.01,0.008,0.019,0.011,0.029c0.005,0.014,0.006,0.029,0.013,0.042c0.002,0.004,0.005,0.011,0.007,0.014
+ c0.004,0.006,0.007,0.007,0.012,0.011c0.009,0.008,0.019,0.022,0.024,0.033C-31.218,48.693-31.217,48.7-31.215,48.707z
+ M-31.576,47.405c-0.012,0.017-0.033,0.042-0.054,0.043c-0.01,0-0.018,0.003-0.029,0.003c-0.009,0.001-0.025-0.005-0.025-0.017
+ l0.011,0.001c-0.008-0.003-0.017-0.012-0.022-0.017c-0.018-0.015-0.019-0.026-0.015-0.048c0.003-0.013,0.006-0.027,0.015-0.037
+ c0.008-0.007,0.016-0.011,0.022-0.021c0.005-0.007,0.005-0.023,0.014-0.025c0.01-0.003,0.02-0.001,0.028-0.008
+ c0.007-0.005,0.009-0.012,0.019-0.01c0.01,0.002,0.018,0.01,0.028,0.011c0.012,0,0.014-0.001,0.019,0.011
+ c0.004,0.009,0.008,0.022,0.007,0.032c-0.002,0.016-0.006,0.031-0.008,0.046C-31.567,47.381-31.57,47.395-31.576,47.405z
+ M-32.403,49.724C-32.403,49.723-32.403,49.723-32.403,49.724L-32.403,49.724L-32.403,49.724z M-32.319,48.806
+ c-0.002,0.006,0,0.006-0.004,0.012c-0.002,0.003-0.005,0.006-0.007,0.009c-0.004,0.006-0.006,0.013-0.008,0.019
+ c-0.003,0.007-0.003,0.013-0.006,0.02c0,0.001-0.007,0.011-0.008,0.011c-0.005,0.003-0.01-0.002-0.015-0.003l-0.008-0.004
+ c-0.004-0.014-0.016-0.027-0.021-0.04c-0.003-0.008-0.005-0.014-0.011-0.021c-0.002-0.002-0.005-0.007-0.007-0.008
+ c-0.003-0.002-0.007-0.001-0.009-0.003c-0.006-0.003-0.01-0.011-0.015-0.017c-0.006-0.008-0.004-0.009-0.002-0.019
+ c0.003-0.011-0.005-0.009-0.01-0.016c-0.004-0.007,0.001-0.017,0.003-0.024c0.005-0.013,0.006-0.029,0.012-0.044
+ c0.005-0.012,0.012-0.025,0.019-0.036c0.006-0.009,0.002-0.015,0.005-0.024c0.002-0.006,0.009-0.008,0.011-0.015
+ c0.002-0.007,0.005-0.018,0.004-0.024c-0.001-0.009-0.008-0.021,0.005-0.024c0.007-0.003,0.014,0,0.019,0.005
+ c0.006,0.006,0.004,0.01,0.006,0.019c0.003,0.015,0.012,0.028,0.016,0.043c0.001,0.004,0.002,0.004,0.003,0.009
+ c0,0.007,0.002,0.01,0.003,0.016c0.002,0.008,0.003,0.015,0.006,0.024c0.002,0.008,0.006,0.012,0.011,0.019
+ c0.005,0.008,0.007,0.017,0.011,0.025c0.006,0.014,0.009,0.028,0.009,0.043c0,0.01,0.001,0.019-0.003,0.028
+ C-32.313,48.793-32.317,48.8-32.319,48.806z M-32.951,49.238c-0.004,0.004-0.007,0.009-0.011,0.013
+ c-0.005,0.007-0.015,0.019-0.017,0.027s0.001,0.009-0.005,0.015c-0.003,0.004-0.006,0.007-0.01,0.01
+ c-0.011,0.011-0.017,0.014-0.017,0.031c0,0.014-0.003,0.026-0.01,0.037c-0.004,0.005-0.011,0.011-0.018,0.011
+ c-0.008,0.001-0.011-0.006-0.015-0.012c0.008-0.018-0.01-0.041-0.02-0.056c-0.011-0.018-0.03-0.034-0.033-0.056
+ c-0.001-0.014,0.009-0.022,0.015-0.033c0.008-0.012,0.016-0.023,0.025-0.035c0.007-0.01,0.014-0.02,0.024-0.028
+ c0.004-0.002,0.009-0.004,0.012-0.008c0.002-0.005,0.001-0.014,0.005-0.019c0.002,0,0.004-0.001,0.006-0.001
+ c0.003-0.002,0.004-0.005,0.006-0.007c0.007-0.005,0.01-0.005,0.019-0.005c0.012,0.001,0.031-0.004,0.039,0.005
+ c0.009,0.01,0.013,0.018,0.013,0.031c0,0.007,0,0.014,0,0.02c0,0.01,0.005,0.015,0.009,0.023c0.003,0.007,0.003,0.01-0.001,0.018
+ C-32.939,49.226-32.945,49.232-32.951,49.238z M-33.22,48.753c-0.008,0.025-0.021,0.047-0.041,0.062
+ c-0.017,0.014-0.035,0.032-0.048,0.05c-0.013,0.02-0.032,0.039-0.042,0.061c-0.004,0.007-0.007,0.013-0.01,0.02
+ c-0.002,0.004-0.005,0.008-0.007,0.012c-0.003,0.003-0.003,0.007-0.005,0.011c-0.008,0.013-0.022,0.025-0.031,0.037
+ c-0.005,0.006-0.009,0.013-0.013,0.02c-0.003,0.004-0.008,0.007-0.011,0.011c-0.006,0.008-0.006,0.014-0.013,0.022
+ c-0.008,0.009-0.018,0.015-0.027,0.023c-0.007,0.007-0.012,0.014-0.019,0.021c-0.005,0.004-0.013,0.01-0.02,0.005
+ c-0.007-0.004-0.004-0.011-0.006-0.018c-0.001,0-0.002,0-0.003-0.001l-0.004-0.004c0-0.009,0.003-0.015,0.004-0.023
+ c0.001-0.005,0-0.011,0-0.015c0-0.007,0.002-0.011,0.003-0.016c0.004-0.02,0.008-0.039,0.014-0.057
+ c0.005-0.016,0.005-0.032,0.011-0.048c0.003-0.008,0.004-0.017,0.009-0.023c0.007-0.008,0.012-0.01,0.016-0.02
+ c0.001-0.004,0.002-0.004,0.003-0.009c0-0.004-0.001-0.007,0.001-0.011c0.002-0.004,0.008-0.008,0.012-0.012
+ c0.007-0.008,0.011-0.019,0.015-0.029c0.003-0.009,0.008-0.012,0.013-0.02c0.005-0.007,0.007-0.016,0.01-0.025
+ c0.004-0.012,0.008-0.013,0.016-0.023c0.009-0.012,0.012-0.027,0.021-0.04c0.009-0.012,0.017-0.022,0.025-0.035
+ c0.005-0.009,0.008-0.016,0.015-0.024c0.005-0.008,0.007-0.017,0.012-0.024c0.006-0.008,0.013-0.013,0.021-0.019
+ c0.001-0.002,0.003-0.005,0.005-0.006c0.003-0.002,0.006-0.002,0.008-0.004c0.01-0.007,0.013-0.029,0.029-0.023
+ c0.013,0.004,0.016,0.023,0.019,0.033c0.003,0.007,0.005,0.013,0.01,0.018c0.003,0.004,0.007,0.006,0.01,0.01
+ c0.005,0.004,0.003,0.005,0.005,0.011c0.005,0.014,0.017,0.031,0.019,0.047c0.002,0.012-0.004,0.014-0.011,0.022
+ C-33.213,48.73-33.216,48.742-33.22,48.753z M-33.383,48.53c-0.013,0.006-0.025,0.013-0.038,0.017
+ c-0.014,0.005-0.02,0.014-0.029,0.024c-0.008,0.008-0.016,0.012-0.026,0.016c-0.007,0.003-0.01,0.004-0.017-0.001
+ c-0.005-0.002-0.011-0.006-0.012-0.011l-0.008-0.009c-0.008-0.007-0.014-0.015-0.02-0.024c-0.003-0.004-0.01-0.011-0.011-0.016
+ c-0.004-0.011-0.001-0.028,0.002-0.039c0.003-0.009,0.001-0.021,0.009-0.027c0.004-0.004,0.01-0.005,0.015-0.01
+ c0.003-0.004,0.005-0.008,0.008-0.012c0.002-0.004,0.007-0.007,0.01-0.01c0.005-0.006,0.008-0.012,0.015-0.016
+ c0.006-0.005,0.008-0.004,0.016-0.005c0.007-0.001,0.012-0.004,0.02-0.004s0.012,0.003,0.02,0.004
+ c0.012,0.002,0.025-0.003,0.036,0.003c0.006,0.003,0.014,0.008,0.019,0.013s0.006,0.013,0.012,0.017
+ c0.007,0.004,0.014,0,0.019,0.004c0.004,0.002,0.006,0.008,0.008,0.012c0.007,0.013,0.012,0.016,0.005,0.031
+ c-0.006,0.013-0.011,0.017-0.023,0.024C-33.364,48.516-33.372,48.525-33.383,48.53z M-33.559,49.088
+ c-0.003,0.01-0.003,0.011-0.01,0.017c-0.006,0.006-0.017,0.007-0.021,0.015c-0.003,0.005,0,0.013-0.001,0.021
+ c-0.001,0.013,0.004,0.016-0.01,0.02l-0.005-0.007c-0.014,0.002-0.02-0.026-0.017-0.036c0.004-0.01,0.006-0.017,0.007-0.027
+ c0-0.009-0.002-0.017,0-0.025c0.002-0.015,0.003-0.029,0.011-0.042c0.003-0.006,0.008-0.011,0.013-0.016
+ c0.009-0.01,0.013-0.012,0.025-0.005c0.007,0.003,0.011,0.005,0.014,0.012c0.004,0.008,0.006,0.018,0.005,0.028
+ c-0.001,0.007,0.001,0.014,0,0.021C-33.55,49.072-33.557,49.079-33.559,49.088z M-33.537,48.952
+ c-0.006,0.009-0.019,0.019-0.031,0.016l0.004-0.008c-0.003,0-0.009,0.002-0.012,0.001c-0.006-0.002-0.005-0.007-0.009-0.012
+ c-0.006-0.008-0.011-0.012-0.011-0.024c-0.001-0.009,0.003-0.016,0.004-0.025c0.003-0.02,0-0.043,0-0.063
+ c0-0.012,0.002-0.018,0.005-0.029c0.003-0.012,0.002-0.021,0.011-0.03c0.006-0.006,0.013-0.01,0.015-0.018
+ c0.001-0.005,0-0.01,0.001-0.015c0.001-0.006,0.004-0.01,0.005-0.015c0.008-0.001,0.023-0.005,0.03-0.01
+ c0.01-0.007,0.011-0.016,0.014-0.026c0.002-0.008,0.01-0.013,0.016-0.02c0.008-0.009,0.007-0.022,0.014-0.033
+ c0.012-0.021,0.045-0.029,0.058-0.005c0.006,0.012,0.001,0.021-0.002,0.033c-0.001,0.005,0.001,0.012,0.001,0.017
+ c-0.001,0.012-0.007,0.022-0.009,0.034c0,0.007,0,0.014-0.004,0.021c-0.004,0.006-0.01,0.011-0.014,0.017
+ c-0.007,0.009-0.016,0.017-0.02,0.028c-0.01,0.025-0.031,0.045-0.04,0.071c-0.005,0.011-0.007,0.023-0.01,0.035
+ c-0.001,0.004-0.003,0.006-0.003,0.013s-0.003,0.009-0.004,0.015c-0.002,0.005,0.001,0.011,0,0.017S-33.534,48.947-33.537,48.952z
+ M-33.541,48.343c-0.002,0.008-0.003,0.013-0.003,0.021c0,0.01,0,0.011-0.005,0.019c-0.007,0.012-0.019,0.023-0.023,0.036
+ c-0.002,0.007-0.003,0.013-0.007,0.02c-0.003,0.006-0.009,0.014-0.009,0.02s0.003,0.01,0.004,0.016
+ c0.001,0.007-0.002,0.012-0.003,0.017c-0.002,0.012-0.002,0.025-0.009,0.035c-0.002,0.004-0.005,0.008-0.009,0.011
+ c-0.003,0.002-0.014,0.007-0.018,0.008c0-0.006-0.006-0.009-0.005-0.015l-0.008-0.004c0-0.008,0.001-0.016,0.001-0.023
+ c-0.001-0.013-0.008-0.025-0.009-0.037c0-0.005,0.002-0.011,0.001-0.016c-0.001-0.004-0.004-0.008-0.005-0.012
+ c-0.001-0.006,0-0.006-0.003-0.012c-0.003-0.004-0.008-0.009-0.012-0.013c-0.008-0.01-0.015-0.022-0.025-0.03
+ c-0.007-0.004-0.017-0.005-0.023-0.009c-0.005-0.005-0.012-0.016-0.013-0.024c0-0.008,0.005-0.015,0.005-0.024
+ c-0.001-0.008,0.002-0.016,0.003-0.024c0.004-0.016,0.011-0.034,0.017-0.051c0.003-0.005,0.005-0.015,0.008-0.02
+ c0.004-0.005,0.009-0.009,0.012-0.016c0.003-0.009,0.007-0.019,0.008-0.028c0-0.009-0.002-0.019,0-0.028
+ c0.007,0,0.01-0.003,0.015-0.004c0.006-0.002,0.014,0.001,0.02,0c0.013-0.002,0.021-0.011,0.031-0.017c0.009-0.006,0.011,0,0.02,0
+ c0.008,0,0.013-0.003,0.021-0.003c0.014,0,0.027,0.001,0.04,0.007c0.01,0.005,0.02,0.014,0.029,0.02
+ c0.007,0.005,0.012,0.016,0.016,0.024c0.005,0.011,0.009,0.018,0.006,0.032c-0.004,0.022-0.019,0.029-0.031,0.045
+ C-33.523,48.289-33.533,48.314-33.541,48.343z M-32.86,55.655c0.007,0.016,0.017,0.03,0.024,0.047
+ c0.017,0.042,0.038,0.08,0.055,0.121c0.017,0.04,0.045,0.07,0.072,0.105c0.02,0.024,0.06,0.071,0.052,0.104
+ c-0.017-0.006-0.026-0.023-0.041-0.031l-0.006,0.005c-0.033-0.027-0.062-0.063-0.067-0.105c-0.005-0.03-0.024-0.065-0.039-0.094
+ c-0.009-0.018-0.031-0.033-0.035-0.052c-0.002-0.008,0.001-0.017-0.001-0.025c-0.004-0.012-0.016-0.028-0.021-0.04
+ c-0.008-0.018-0.015-0.031-0.02-0.051c-0.003-0.012-0.021-0.054,0.006-0.037C-32.866,55.611-32.865,55.641-32.86,55.655z
+ M-31.614,57.091c0.006,0.006,0.009,0.014,0.014,0.021c0.002,0.003,0.004,0.007,0.007,0.011c0.001,0.001,0.003,0.002,0.004,0.004
+ c0.002,0.003,0.005,0.004,0.007,0.006c0.002,0.002,0.004,0.005,0.006,0.008c0.003,0.004,0.005,0.008,0.008,0.013
+ c0.003,0.005,0.006,0.011,0.009,0.017c0.001,0.004,0.004,0.007,0.008,0.01c0.003,0.002,0.004,0.005,0.006,0.009
+ c0.002,0.003,0.003,0.008,0.006,0.011c0.004,0.004,0.007,0.009,0.008,0.014c0.002,0.005,0.003,0.009,0.005,0.014
+ c0.001,0.002,0.002,0.004,0.004,0.006c0.002,0.002,0.005,0.003,0.007,0.006c0.002,0.003,0.005,0.005,0.007,0.008
+ c0.001,0.002,0.002,0.004,0.004,0.006c0.001,0.003,0.004,0.006,0.007,0.009c0.001,0.001,0.003,0.002,0.004,0.004
+ c0.001,0.001,0.002,0.002,0.002,0.003c0.003,0.005,0.007,0.01,0.009,0.015c0.003,0.009,0.007,0.017,0.011,0.025
+ c0.001,0.003,0.002,0.007,0.004,0.01c0.001,0.002,0.005,0.007,0.004,0.009c-0.001,0-0.004-0.004-0.004-0.005
+ c-0.002-0.002-0.003-0.003-0.005-0.005c-0.004-0.003-0.008-0.007-0.013-0.01c-0.003-0.002-0.007-0.005-0.009-0.008
+ c-0.001-0.002-0.003-0.003-0.004-0.005c-0.001-0.002-0.003-0.004-0.004-0.007c-0.001-0.002-0.002-0.004-0.004-0.006
+ c-0.001-0.001-0.003-0.002-0.004-0.003c-0.003-0.004-0.003-0.009-0.005-0.013c-0.002-0.003-0.004-0.005-0.007-0.007
+ c-0.003-0.004-0.005-0.008-0.007-0.012c-0.002-0.003-0.003-0.007-0.005-0.01c-0.002-0.005-0.004-0.01-0.007-0.014
+ c-0.005-0.007-0.011-0.014-0.016-0.021c-0.002-0.004-0.006-0.007-0.008-0.011c-0.003-0.004-0.006-0.008-0.008-0.012
+ c-0.005-0.007-0.009-0.015-0.014-0.022c-0.003-0.004-0.007-0.008-0.01-0.012c-0.003-0.003-0.005-0.008-0.006-0.012
+ c-0.002-0.004-0.003-0.008-0.006-0.012c-0.001-0.001-0.002-0.002-0.002-0.004c-0.001,0-0.001-0.001-0.001-0.001s0,0,0-0.001
+ c-0.001-0.001-0.001-0.002-0.002-0.004c0-0.002-0.001-0.002-0.001-0.003s0-0.002,0-0.003s-0.001-0.001-0.001-0.002
+ c0-0.001,0-0.002-0.001-0.003c-0.001-0.004-0.003-0.007-0.004-0.01c-0.001-0.002-0.001-0.004,0.001-0.004
+ C-31.618,57.088-31.615,57.09-31.614,57.091z M-31.125,57.71c0.016,0.015,0.034,0.023,0.052,0.036
+ c0.023,0.015,0.041,0.038,0.063,0.054c0.033,0.024,0.065,0.05,0.1,0.072c0.018,0.011,0.033,0.026,0.051,0.039
+ c0.014,0.01,0.034,0.018,0.042,0.034c-0.014,0.003-0.039-0.014-0.053-0.019c-0.014-0.004-0.044-0.007-0.051-0.022l0,0
+ c-0.026-0.02-0.048-0.046-0.072-0.069c-0.023-0.022-0.049-0.034-0.069-0.058c-0.027-0.032-0.054-0.036-0.09-0.055
+ c-0.018-0.009-0.059-0.045-0.062-0.068c0.023-0.02,0.031,0.013,0.043,0.024C-31.158,57.69-31.14,57.698-31.125,57.71z
+ M-22.303,59.124c0.005-0.006,0.012-0.01,0.017-0.016c0.004-0.007,0.003-0.01,0.006-0.016c0.004-0.01,0.012-0.017,0.017-0.027
+ c0.001-0.002,0.002-0.01,0.003-0.012c0.003-0.005,0.008-0.009,0.013-0.015c0.009-0.01,0.011-0.012,0.026-0.012
+ c0.01,0.001,0.018-0.002,0.027-0.005c0.01-0.004,0.022-0.01,0.032-0.011c0.005-0.001,0.012,0,0.017,0
+ c0.009,0,0.013-0.002,0.022-0.004c0.012-0.003,0.021-0.004,0.033-0.011c0.007-0.004,0.013-0.008,0.021-0.012
+ c0.01-0.005,0.018-0.01,0.026-0.018c0.003-0.003,0.005-0.006,0.008-0.008c0.005-0.004,0.009-0.005,0.014-0.008
+ c0.014-0.011,0.017-0.026,0.027-0.039c0.011-0.016,0.034-0.026,0.049-0.037c0.006-0.005,0.01-0.012,0.017-0.017
+ c0.01-0.006,0.022-0.011,0.033-0.015c0.019-0.007,0.035-0.021,0.055-0.026c0.019-0.004,0.046-0.006,0.064-0.019
+ c0.015-0.01,0.029-0.025,0.046-0.035c0.005-0.003,0.019-0.011,0.025-0.006c0.008,0.008-0.004,0.018-0.009,0.022
+ c-0.012,0.012-0.026,0.02-0.038,0.033c-0.013,0.014-0.027,0.028-0.041,0.041c-0.005,0.004-0.012,0.01-0.016,0.016
+ c-0.006,0.009-0.005,0.02-0.009,0.029c-0.012,0.023-0.036,0.037-0.055,0.053c-0.007,0.006-0.012,0.011-0.018,0.017
+ c-0.011,0.012-0.017,0.025-0.027,0.039c-0.014,0.021-0.051,0.038-0.075,0.042c-0.012,0.002-0.025,0-0.037,0.002
+ c-0.009,0.002-0.019,0.008-0.029,0.011c-0.011,0.003-0.028,0.009-0.036,0.016c-0.005,0.005-0.007,0.011-0.012,0.016
+ c-0.005,0.004-0.011,0.007-0.016,0.01c-0.009,0.006-0.018,0.015-0.027,0.021c-0.017,0.01-0.037,0.015-0.054,0.023
+ c-0.013,0.006-0.021,0.004-0.034,0.005c-0.011,0.002-0.021,0.007-0.032,0.01c-0.011,0.003-0.027,0.003-0.038,0.001
+ c-0.01-0.001-0.016-0.005-0.025-0.005l0.003-0.006C-22.321,59.142-22.312,59.134-22.303,59.124z M-22.336,59.162
+ c-0.004,0.001-0.005-0.001-0.005-0.004c0.003-0.001,0.006-0.001,0.008-0.001L-22.336,59.162z M-21.881,59.028
+ c-0.01-0.011,0.016-0.024,0.024-0.029c0.007-0.004,0.016-0.006,0.023-0.009c0.005-0.002,0.008-0.003,0.011-0.006
+ c0.005-0.002,0.007-0.009,0.011-0.011c0.006-0.004,0.015-0.002,0.022-0.005c0.007-0.004,0.014-0.009,0.02-0.013
+ c0.009-0.005,0.047-0.031,0.044-0.005c0,0.005-0.007,0.018-0.01,0.022c-0.003,0.006-0.008,0.011-0.012,0.016
+ c-0.008,0.011-0.02,0.027-0.034,0.032c-0.008,0.002-0.016,0-0.024,0.002c-0.009,0.002-0.014,0.008-0.023,0.01
+ c-0.011,0.002-0.023,0-0.033,0c-0.013-0.001-0.021,0.004-0.033,0.005l0.011-0.005C-21.883,59.03-21.882,59.029-21.881,59.028z
+ M-23.012,48.049c0.012-0.009,0.023-0.017,0.03-0.03c0.003-0.007,0.004-0.011,0.011-0.014c0.006-0.002,0.012-0.003,0.018-0.003
+ c0.025-0.001,0.044,0.01,0.051,0.036c0.005,0.018,0.007,0.037,0.01,0.056c0.002,0.017,0.009,0.033,0.012,0.05
+ c0.002,0.014,0.004,0.027,0.001,0.041c-0.003,0.013-0.006,0.019-0.018,0.024c-0.024,0.011-0.052,0.005-0.076,0
+ c-0.008-0.002-0.016-0.005-0.022-0.01c-0.007-0.005-0.012-0.013-0.021-0.014h0.013c-0.003,0-0.005-0.003-0.008-0.005
+ c-0.006-0.006-0.01-0.008-0.014-0.015c-0.005-0.008-0.012-0.014-0.015-0.023c-0.003-0.006-0.005-0.014-0.006-0.021
+ c-0.001-0.014-0.002-0.033,0.006-0.046C-23.032,48.063-23.023,48.056-23.012,48.049z M-22.19,43.243
+ c0.013,0.002,0.025,0.012,0.037,0.018c0.011,0.005,0.021,0,0.032,0.003c0.005,0.001,0.005,0.005,0.01,0.006
+ c0.007,0.002,0.014-0.002,0.021-0.001c0.01,0.002,0.018,0.007,0.026,0.012c0.007,0.004,0.012,0.01,0.019,0.014
+ c0.008,0.005,0.017,0.005,0.024,0.013c0.007,0.007,0.016,0.02,0.005,0.025c-0.002,0.001-0.013,0.003-0.016,0.004
+ c-0.008,0.002-0.014,0.004-0.021,0.008c-0.01,0.005-0.016,0.01-0.026,0.014c-0.005,0.002-0.005,0.005-0.011,0.005
+ c-0.005,0-0.014-0.005-0.021-0.005c-0.023,0-0.044-0.011-0.066-0.01c-0.002-0.002-0.004-0.005-0.006-0.007
+ c-0.01-0.01-0.019-0.021-0.027-0.032c-0.006-0.007-0.011-0.016-0.017-0.024c-0.011-0.018,0.002-0.022,0.015-0.033
+ C-22.204,43.246-22.201,43.241-22.19,43.243z M-22.195,43.354c0.006-0.003,0.012-0.004,0.018-0.005
+ c0.002,0.002,0.005,0.004,0.007,0.005H-22.195z M-21.865,43.386c0.003-0.011,0-0.022,0.001-0.032
+ c0.001-0.003,0.005-0.01,0.005-0.012c0.001-0.008-0.003-0.011-0.005-0.02c-0.002-0.015-0.002-0.047,0.016-0.053
+ c0.009-0.002,0.024,0.001,0.033,0.001c0.017,0,0.031,0,0.047-0.006c0.02-0.007,0.035-0.012,0.057-0.006
+ c0.024,0.006,0.042-0.011,0.063,0.003c0.015,0.009,0.031,0.023,0.039,0.039c0.009,0.019,0.009,0.042,0.009,0.065
+ c0,0.019-0.004,0.044,0.004,0.063c0.009,0.02,0.024,0.035,0.038,0.052c0.015,0.018,0.027,0.036,0.038,0.057
+ c0.004,0.008,0.008,0.013,0.009,0.022c0.003,0.017,0.001,0.021-0.015,0.021c-0.02,0-0.037-0.013-0.053-0.025
+ c-0.023-0.016-0.058-0.029-0.086-0.036c-0.024-0.007-0.046-0.017-0.071-0.022c-0.015-0.003-0.025,0.004-0.038,0.003
+ c-0.01,0-0.021-0.008-0.031-0.009c-0.011-0.001-0.021-0.002-0.031-0.006c-0.003-0.001-0.009-0.007-0.012-0.009
+ c-0.004-0.002-0.011-0.005-0.014-0.007c-0.007-0.007-0.005-0.017-0.011-0.023l0.004,0.003
+ C-21.886,43.435-21.868,43.401-21.865,43.386z"/>
+</svg>
diff --git a/browser/branding/nightly/default16.png b/browser/branding/nightly/default16.png
new file mode 100644
index 000000000..ace90e53c
--- /dev/null
+++ b/browser/branding/nightly/default16.png
Binary files differ
diff --git a/browser/branding/nightly/default32.png b/browser/branding/nightly/default32.png
new file mode 100644
index 000000000..b62d0200f
--- /dev/null
+++ b/browser/branding/nightly/default32.png
Binary files differ
diff --git a/browser/branding/nightly/default48.png b/browser/branding/nightly/default48.png
new file mode 100644
index 000000000..88a307340
--- /dev/null
+++ b/browser/branding/nightly/default48.png
Binary files differ
diff --git a/browser/branding/nightly/disk.icns b/browser/branding/nightly/disk.icns
new file mode 100644
index 000000000..c49b7b878
--- /dev/null
+++ b/browser/branding/nightly/disk.icns
Binary files differ
diff --git a/browser/branding/nightly/document.icns b/browser/branding/nightly/document.icns
new file mode 100644
index 000000000..98b461d75
--- /dev/null
+++ b/browser/branding/nightly/document.icns
Binary files differ
diff --git a/browser/branding/nightly/document.ico b/browser/branding/nightly/document.ico
new file mode 100644
index 000000000..796181004
--- /dev/null
+++ b/browser/branding/nightly/document.ico
Binary files differ
diff --git a/browser/branding/nightly/dsstore b/browser/branding/nightly/dsstore
new file mode 100644
index 000000000..657101d6e
--- /dev/null
+++ b/browser/branding/nightly/dsstore
Binary files differ
diff --git a/browser/branding/nightly/firefox.VisualElementsManifest.xml b/browser/branding/nightly/firefox.VisualElementsManifest.xml
new file mode 100644
index 000000000..7654e0ab7
--- /dev/null
+++ b/browser/branding/nightly/firefox.VisualElementsManifest.xml
@@ -0,0 +1,8 @@
+<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
+ <VisualElements
+ ShowNameOnSquare150x150Logo='on'
+ Square150x150Logo='browser\VisualElements\VisualElements_150.png'
+ Square70x70Logo='browser\VisualElements\VisualElements_70.png'
+ ForegroundText='light'
+ BackgroundColor='#14171a'/>
+</Application>
diff --git a/browser/branding/nightly/firefox.icns b/browser/branding/nightly/firefox.icns
new file mode 100644
index 000000000..858cb2a19
--- /dev/null
+++ b/browser/branding/nightly/firefox.icns
Binary files differ
diff --git a/browser/branding/nightly/firefox.ico b/browser/branding/nightly/firefox.ico
new file mode 100644
index 000000000..12bebb1c2
--- /dev/null
+++ b/browser/branding/nightly/firefox.ico
Binary files differ
diff --git a/browser/branding/nightly/locales/browserconfig.properties b/browser/branding/nightly/locales/browserconfig.properties
new file mode 100644
index 000000000..06cefece3
--- /dev/null
+++ b/browser/branding/nightly/locales/browserconfig.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/.
+
+# Do NOT localize or otherwise change these values
+browser.startup.homepage=about:home
diff --git a/browser/branding/nightly/locales/en-US/brand.dtd b/browser/branding/nightly/locales/en-US/brand.dtd
new file mode 100644
index 000000000..cf4596ae0
--- /dev/null
+++ b/browser/branding/nightly/locales/en-US/brand.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 brandShorterName "Nightly">
+<!ENTITY brandShortName "Nightly">
+<!ENTITY brandFullName "Nightly">
+<!ENTITY vendorShortName "Mozilla">
+<!ENTITY trademarkInfo.part1 " ">
diff --git a/browser/branding/nightly/locales/en-US/brand.properties b/browser/branding/nightly/locales/en-US/brand.properties
new file mode 100644
index 000000000..8cd2c2ec9
--- /dev/null
+++ b/browser/branding/nightly/locales/en-US/brand.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/.
+
+brandShorterName=Nightly
+brandShortName=Nightly
+brandFullName=Nightly
+vendorShortName=Mozilla
+
+syncBrandShortName=Sync
diff --git a/browser/branding/nightly/locales/jar.mn b/browser/branding/nightly/locales/jar.mn
new file mode 100644
index 000000000..63af8f6f9
--- /dev/null
+++ b/browser/branding/nightly/locales/jar.mn
@@ -0,0 +1,12 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Nightly branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
+ locale/branding/browserconfig.properties
diff --git a/browser/branding/nightly/locales/moz.build b/browser/branding/nightly/locales/moz.build
new file mode 100644
index 000000000..8bad13124
--- /dev/null
+++ b/browser/branding/nightly/locales/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_DISTRIBUTION_ID_UNQUOTED'] = CONFIG['MOZ_DISTRIBUTION_ID']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/nightly/moz.build b/browser/branding/nightly/moz.build
new file mode 100644
index 000000000..9045cee11
--- /dev/null
+++ b/browser/branding/nightly/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['content', 'locales']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+include('../branding-common.mozbuild')
+FirefoxBranding()
diff --git a/browser/branding/nightly/mozicon128.png b/browser/branding/nightly/mozicon128.png
new file mode 100644
index 000000000..4222e6814
--- /dev/null
+++ b/browser/branding/nightly/mozicon128.png
Binary files differ
diff --git a/browser/branding/nightly/newtab.ico b/browser/branding/nightly/newtab.ico
new file mode 100644
index 000000000..a9b37c08c
--- /dev/null
+++ b/browser/branding/nightly/newtab.ico
Binary files differ
diff --git a/browser/branding/nightly/newwindow.ico b/browser/branding/nightly/newwindow.ico
new file mode 100644
index 000000000..553720771
--- /dev/null
+++ b/browser/branding/nightly/newwindow.ico
Binary files differ
diff --git a/browser/branding/nightly/particles.bmp b/browser/branding/nightly/particles.bmp
new file mode 100644
index 000000000..ab74ce047
--- /dev/null
+++ b/browser/branding/nightly/particles.bmp
Binary files differ
diff --git a/browser/branding/nightly/pbmode.ico b/browser/branding/nightly/pbmode.ico
new file mode 100644
index 000000000..47677c13f
--- /dev/null
+++ b/browser/branding/nightly/pbmode.ico
Binary files differ
diff --git a/browser/branding/nightly/pencil-rtl.bmp b/browser/branding/nightly/pencil-rtl.bmp
new file mode 100644
index 000000000..e50d92db7
--- /dev/null
+++ b/browser/branding/nightly/pencil-rtl.bmp
Binary files differ
diff --git a/browser/branding/nightly/pencil.bmp b/browser/branding/nightly/pencil.bmp
new file mode 100644
index 000000000..252c10f41
--- /dev/null
+++ b/browser/branding/nightly/pencil.bmp
Binary files differ
diff --git a/browser/branding/nightly/pref/firefox-branding.js b/browser/branding/nightly/pref/firefox-branding.js
new file mode 100644
index 000000000..359d6bb6e
--- /dev/null
+++ b/browser/branding/nightly/pref/firefox-branding.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/. */
+
+pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%");
+pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/");
+pref("startup.homepage_welcome_url.additional", "");
+// The time interval between checks for a new version (in seconds)
+pref("app.update.interval", 7200); // 2 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
+// Give the user x seconds to react before showing the big UI. default=12 hours
+pref("app.update.promptWaitTime", 43200);
+// URL user can browse to manually if for some reason all update installation
+// attempts fail.
+pref("app.update.url.manual", "https://nightly.mozilla.org");
+// A default value for the "More information about this update" link
+// supplied in the "An update is available" page of the update wizard.
+pref("app.update.url.details", "https://nightly.mozilla.org");
+
+// The number of days a binary is permitted to be old
+// without checking for an update. This assumes that
+// app.update.checkInstallTime is true.
+pref("app.update.checkInstallTime.days", 2);
+
+// Give the user x seconds to reboot before showing a badge on the hamburger
+// button. default=immediately
+pref("app.update.badgeWaitTime", 0);
+
+// Number of usages of the web console or scratchpad.
+// If this is less than 5, then pasting code into the web console or scratchpad is disabled
+pref("devtools.selfxss.count", 5);
diff --git a/browser/branding/nightly/wizHeader.bmp b/browser/branding/nightly/wizHeader.bmp
new file mode 100644
index 000000000..ac96070a8
--- /dev/null
+++ b/browser/branding/nightly/wizHeader.bmp
Binary files differ
diff --git a/browser/branding/nightly/wizHeaderRTL.bmp b/browser/branding/nightly/wizHeaderRTL.bmp
new file mode 100644
index 000000000..710af4a8e
--- /dev/null
+++ b/browser/branding/nightly/wizHeaderRTL.bmp
Binary files differ
diff --git a/browser/branding/nightly/wizWatermark.bmp b/browser/branding/nightly/wizWatermark.bmp
new file mode 100644
index 000000000..86d929cc1
--- /dev/null
+++ b/browser/branding/nightly/wizWatermark.bmp
Binary files differ
diff --git a/browser/branding/official/LICENSE b/browser/branding/official/LICENSE
new file mode 100644
index 000000000..32d55b9c7
--- /dev/null
+++ b/browser/branding/official/LICENSE
@@ -0,0 +1,10 @@
+These files are under the MPL 2, as below. However, please note that you
+are not granted any trademark rights or licenses to the trademarks of the
+Mozilla Foundation or any party, including without limitation the
+Firefox name or logo.
+
+For more information, see: http://www.mozilla.org/foundation/licensing.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/.
diff --git a/browser/branding/official/VisualElements_150.png b/browser/branding/official/VisualElements_150.png
new file mode 100644
index 000000000..f46b957c2
--- /dev/null
+++ b/browser/branding/official/VisualElements_150.png
Binary files differ
diff --git a/browser/branding/official/VisualElements_70.png b/browser/branding/official/VisualElements_70.png
new file mode 100644
index 000000000..b45a3400f
--- /dev/null
+++ b/browser/branding/official/VisualElements_70.png
Binary files differ
diff --git a/browser/branding/official/appname.bmp b/browser/branding/official/appname.bmp
new file mode 100644
index 000000000..1c2f44cf3
--- /dev/null
+++ b/browser/branding/official/appname.bmp
Binary files differ
diff --git a/browser/branding/official/background.png b/browser/branding/official/background.png
new file mode 100644
index 000000000..d594986c1
--- /dev/null
+++ b/browser/branding/official/background.png
Binary files differ
diff --git a/browser/branding/official/bgintro.bmp b/browser/branding/official/bgintro.bmp
new file mode 100644
index 000000000..9c2fc80cf
--- /dev/null
+++ b/browser/branding/official/bgintro.bmp
Binary files differ
diff --git a/browser/branding/official/branding.nsi b/browser/branding/official/branding.nsi
new file mode 100644
index 000000000..5960f3107
--- /dev/null
+++ b/browser/branding/official/branding.nsi
@@ -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/.
+
+# NSIS branding defines for official release builds.
+# The nightly build branding.nsi is located in browser/installer/windows/nsis/
+# The unofficial build branding.nsi is located in browser/branding/unofficial/
+
+# BrandFullNameInternal is used for some registry and file system values
+# instead of BrandFullName and typically should not be modified.
+!define BrandFullNameInternal "Mozilla Firefox"
+!define CompanyName "Mozilla Corporation"
+!define URLInfoAbout "https://www.mozilla.org"
+!define URLUpdateInfo "https://www.mozilla.org/firefox/${AppVersion}/releasenotes"
+!define HelpLink "https://support.mozilla.org"
+
+; The OFFICIAL define is a workaround to support different urls for Release and
+; Beta since they share the same branding when building with other branches that
+; set the update channel to beta.
+!define OFFICIAL
+!define URLStubDownload "http://download.mozilla.org/?os=win&lang=${AB_CD}&product=firefox-latest"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=release&installer_lang=${AB_CD}"
+!define URLSystemRequirements "https://www.mozilla.org/firefox/system-requirements/"
+!define Channel "release"
+
+# The installer's certificate name and issuer expected by the stub installer
+!define CertNameDownload "Mozilla Corporation"
+!define CertIssuerDownload "DigiCert SHA2 Assured ID Code Signing CA"
+
+# Dialog units are used so the UI displays correctly with the system's DPI
+# settings.
+# The dialog units for the bitmap's dimensions should match exactly with the
+# bitmap's width and height in pixels.
+!define APPNAME_BMP_WIDTH_DU "134u"
+!define APPNAME_BMP_HEIGHT_DU "36u"
+!define INTRO_BLURB_WIDTH_DU "258u"
+!define INTRO_BLURB_EDGE_DU "170u"
+!define INTRO_BLURB_LTR_TOP_DU "20u"
+!define INTRO_BLURB_RTL_TOP_DU "12u"
+
+# UI Colors that can be customized for each channel
+!define FOOTER_CONTROL_TEXT_COLOR_NORMAL 0x000000
+!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x666666
+!define FOOTER_BKGRD_COLOR 0xFFFFFF
+!define INTRO_BLURB_TEXT_COLOR 0x666666
+!define INSTALL_BLURB_TEXT_COLOR 0x666666
+!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0x666666
+!define COMMON_TEXT_COLOR_NORMAL 0x000000
+!define COMMON_TEXT_COLOR_FADED 0x666666
+!define COMMON_BKGRD_COLOR 0xF0F0F0
diff --git a/browser/branding/official/clock.bmp b/browser/branding/official/clock.bmp
new file mode 100644
index 000000000..7da034d3b
--- /dev/null
+++ b/browser/branding/official/clock.bmp
Binary files differ
diff --git a/browser/branding/official/configure.sh b/browser/branding/official/configure.sh
new file mode 100644
index 000000000..92ef14e03
--- /dev/null
+++ b/browser/branding/official/configure.sh
@@ -0,0 +1,5 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOZ_APP_DISPLAYNAME=Firefox
diff --git a/browser/branding/official/content/about-logo.png b/browser/branding/official/content/about-logo.png
new file mode 100644
index 000000000..c7c5ac2b8
--- /dev/null
+++ b/browser/branding/official/content/about-logo.png
Binary files differ
diff --git a/browser/branding/official/content/about-logo@2x.png b/browser/branding/official/content/about-logo@2x.png
new file mode 100644
index 000000000..624ad150f
--- /dev/null
+++ b/browser/branding/official/content/about-logo@2x.png
Binary files differ
diff --git a/browser/branding/official/content/about-wordmark.png b/browser/branding/official/content/about-wordmark.png
new file mode 100644
index 000000000..c9651b520
--- /dev/null
+++ b/browser/branding/official/content/about-wordmark.png
Binary files differ
diff --git a/browser/branding/official/content/about.png b/browser/branding/official/content/about.png
new file mode 100644
index 000000000..7d78057dc
--- /dev/null
+++ b/browser/branding/official/content/about.png
Binary files differ
diff --git a/browser/branding/official/content/aboutDialog.css b/browser/branding/official/content/aboutDialog.css
new file mode 100644
index 000000000..46ba1b771
--- /dev/null
+++ b/browser/branding/official/content/aboutDialog.css
@@ -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/. */
+
+#clientBox {
+ background-color: #F7F7F7;
+ color: #222222;
+}
+
+#leftBox {
+ background-image: url("chrome://branding/content/about-logo.png");
+ background-repeat: no-repeat;
+ background-size: 192px auto;
+ /* min-width and min-height create room for the logo */
+ min-width: 210px;
+ min-height: 210px;
+ margin-top: 20px;
+ margin-inline-start: 30px;
+}
+
+
+@media (min-resolution: 2dppx) {
+ #leftBox {
+ background-image: url("chrome://branding/content/about-logo@2x.png");
+ }
+}
+
+#rightBox {
+ margin-left: 30px;
+ margin-right: 30px;
+}
+
+#updateDeck > hbox > label:not([class="text-link"]) {
+ color: #909090;
+}
+
+#trademark {
+ font-size: xx-small;
+ text-align: center;
+ color: #999999;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
diff --git a/browser/branding/official/content/icon48.png b/browser/branding/official/content/icon48.png
new file mode 100644
index 000000000..10de89172
--- /dev/null
+++ b/browser/branding/official/content/icon48.png
Binary files differ
diff --git a/browser/branding/official/content/icon64.png b/browser/branding/official/content/icon64.png
new file mode 100644
index 000000000..ac94fb47d
--- /dev/null
+++ b/browser/branding/official/content/icon64.png
Binary files differ
diff --git a/browser/branding/official/content/identity-icons-brand.svg b/browser/branding/official/content/identity-icons-brand.svg
new file mode 100644
index 000000000..380e4e597
--- /dev/null
+++ b/browser/branding/official/content/identity-icons-brand.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+ <path fill="#ff9500" d="M26.797,6.125 C26.719,6.050 26.674,6.013 26.674,6.013 C26.715,6.045 26.756,6.082 26.797,6.125 M28.924,21.127 C28.947,20.948 28.949,20.780 28.929,20.626 C28.501,21.359 27.844,22.025 27.228,22.905 C27.980,22.421 28.624,21.866 28.924,21.127 ZM30.097,13.924 C30.082,13.747 30.062,13.570 30.036,13.394 C29.974,12.946 29.875,12.504 29.728,12.076 C29.735,12.102 29.741,12.129 29.747,12.155 C29.737,12.124 29.731,12.107 29.731,12.107 C29.731,12.107 29.614,12.472 29.435,13.070 C29.423,14.290 29.290,15.417 29.094,16.268 C29.419,15.962 29.657,15.599 29.820,15.196 C29.748,15.461 29.649,15.713 29.520,15.947 C29.361,16.217 29.182,16.436 29.009,16.610 C28.821,16.797 28.641,16.932 28.500,17.024 C28.543,16.905 28.585,16.773 28.625,16.631 C28.626,16.629 28.626,16.628 28.627,16.626 C28.637,16.590 28.647,16.552 28.657,16.513 C28.761,16.176 28.854,15.828 28.934,15.474 C29.049,14.961 29.137,14.433 29.192,13.898 C29.300,12.857 29.284,11.787 29.104,10.744 C29.015,10.227 28.886,9.717 28.712,9.220 C28.480,8.559 28.197,8.001 27.916,7.544 C27.895,7.508 27.874,7.472 27.853,7.436 C27.760,7.280 27.665,7.132 27.570,6.994 C27.258,6.543 26.939,6.200 26.674,6.013 C26.715,6.045 26.756,6.082 26.797,6.125 C26.719,6.050 26.674,6.013 26.674,6.013 C26.694,6.035 26.712,6.058 26.732,6.081 C26.682,6.033 26.653,6.008 26.653,6.008 C26.653,6.008 26.682,6.170 26.733,6.456 C25.969,5.218 24.807,4.635 24.807,4.635 C24.807,4.635 24.902,4.945 25.050,5.391 C25.741,5.949 26.345,6.571 26.869,7.227 C26.871,7.238 26.873,7.250 26.875,7.261 C26.905,7.435 26.938,7.624 26.973,7.826 C26.880,7.700 26.782,7.575 26.683,7.451 C25.589,5.958 24.211,4.706 22.611,3.779 C22.625,3.783 22.600,3.776 22.613,3.780 C20.672,2.647 18.429,2.000 16.039,2.000 C12.495,2.000 9.273,3.423 6.880,5.744 C6.814,5.809 7.271,6.270 7.404,6.311 C8.081,6.106 8.838,6.051 9.423,6.120 C9.705,5.897 9.663,5.963 9.961,5.769 L9.964,5.772 C11.767,4.606 13.854,3.982 16.035,3.982 C18.042,3.982 19.969,4.510 21.669,5.503 C22.118,5.635 22.683,5.830 23.182,6.091 C22.663,5.187 22.132,4.560 21.761,4.182 C23.311,5.364 24.150,6.425 24.701,7.396 C24.746,7.475 24.789,7.553 24.830,7.631 C24.928,7.816 25.017,7.998 25.099,8.177 C24.570,7.618 23.753,7.042 23.039,6.772 C22.981,6.750 22.924,6.730 22.868,6.712 C22.708,6.662 22.555,6.628 22.414,6.617 C23.550,7.488 25.407,9.978 25.432,13.744 C25.432,13.765 25.432,13.786 25.432,13.807 C25.432,14.010 25.428,14.216 25.417,14.427 C25.207,13.971 24.877,13.340 24.562,12.869 C24.483,12.751 24.405,12.644 24.330,12.551 C24.265,12.471 24.202,12.400 24.143,12.346 C24.479,15.517 24.324,16.750 24.116,17.651 C24.096,17.734 24.077,17.815 24.057,17.894 C24.015,18.058 23.973,18.216 23.934,18.378 C23.920,18.257 23.899,18.145 23.874,18.040 C23.816,17.791 23.735,17.585 23.666,17.424 C23.643,17.372 23.621,17.322 23.603,17.280 C23.603,17.280 23.581,17.854 23.325,18.780 C23.199,19.234 23.018,19.771 22.754,20.367 C22.294,21.408 21.823,21.903 21.501,22.062 C21.397,22.113 21.309,22.129 21.242,22.117 C21.167,22.112 21.126,22.079 21.127,22.076 C21.135,22.001 21.143,21.926 21.146,21.854 C21.150,21.754 21.145,21.662 21.119,21.593 C21.119,21.593 20.862,21.684 20.697,21.924 C20.630,22.020 20.544,22.115 20.431,22.203 C20.411,22.219 20.614,21.936 20.599,21.949 C20.499,22.033 20.392,22.130 20.285,22.245 C20.170,22.368 20.059,22.494 19.954,22.609 C19.699,22.887 19.483,23.095 19.352,23.001 C19.437,22.975 19.513,22.906 19.572,22.818 C19.635,22.726 19.680,22.613 19.699,22.500 C19.544,22.612 19.152,22.914 18.272,23.049 C18.109,23.074 17.707,23.146 17.127,23.121 C16.424,23.090 15.460,22.916 14.345,22.341 C14.578,22.313 14.903,22.241 15.196,22.312 C15.275,22.331 15.352,22.360 15.424,22.405 C15.392,22.369 15.355,22.338 15.315,22.310 C14.933,22.037 14.212,22.084 13.681,21.911 C13.170,21.744 12.503,21.005 12.119,20.631 C12.263,20.667 12.407,20.696 12.551,20.721 C12.652,20.738 12.752,20.753 12.852,20.765 C13.008,20.784 13.164,20.798 13.319,20.805 C14.486,20.856 15.595,20.569 16.313,20.063 C17.285,19.377 17.861,18.876 18.378,18.994 C18.428,19.006 18.476,19.010 18.522,19.010 C18.543,19.010 18.563,19.009 18.583,19.007 C18.867,18.975 19.053,18.730 19.002,18.441 C18.983,18.332 18.931,18.217 18.836,18.104 C18.561,17.778 18.016,17.375 17.274,17.265 C16.935,17.215 16.556,17.226 16.142,17.333 C15.385,17.528 14.711,18.047 13.824,18.051 C13.526,18.053 13.204,17.996 12.846,17.850 C12.759,17.815 12.671,17.774 12.580,17.728 C12.489,17.681 12.877,17.783 12.781,17.725 C12.508,17.621 12.011,17.386 11.888,17.297 C11.868,17.282 12.094,17.339 12.070,17.324 C10.721,16.501 10.809,15.842 10.809,15.435 C10.809,15.270 10.858,15.077 10.953,14.899 C11.046,14.723 11.183,14.563 11.362,14.461 C11.475,14.502 11.562,14.541 11.616,14.567 C11.655,14.586 11.677,14.598 11.677,14.598 C11.677,14.598 11.664,14.576 11.644,14.546 C11.613,14.499 11.565,14.428 11.530,14.386 C11.544,14.381 11.557,14.377 11.571,14.373 C11.665,14.406 11.829,14.468 11.985,14.532 C12.092,14.576 12.195,14.621 12.268,14.659 C12.514,14.786 12.596,14.916 12.596,14.916 C12.596,14.916 12.654,14.879 12.594,14.749 C12.583,14.726 12.560,14.683 12.519,14.630 C12.465,14.561 12.378,14.473 12.239,14.386 C12.244,14.386 12.248,14.385 12.251,14.385 C12.379,14.437 12.514,14.503 12.663,14.590 C12.670,14.555 12.679,14.520 12.687,14.484 C12.688,14.479 12.690,14.475 12.691,14.470 C12.693,14.462 12.695,14.454 12.697,14.446 C12.704,14.416 12.711,14.385 12.718,14.354 C12.730,14.301 12.740,14.245 12.748,14.185 C12.764,14.058 12.768,13.913 12.740,13.731 C12.695,13.446 12.701,13.373 12.632,13.269 C12.573,13.181 12.648,13.142 12.740,13.221 C12.718,13.151 12.687,13.081 12.650,13.010 C12.650,13.009 12.651,13.009 12.651,13.007 C12.659,12.968 12.693,12.919 12.745,12.864 C12.758,12.849 12.774,12.834 12.790,12.818 C12.805,12.803 12.821,12.789 12.839,12.773 C13.359,12.313 14.782,11.539 14.908,11.443 C15.118,11.283 15.332,11.035 15.466,10.750 C15.508,10.672 15.544,10.578 15.571,10.468 C15.606,10.323 15.625,10.149 15.614,9.937 C15.606,9.764 15.537,9.634 14.917,9.568 C14.584,9.533 14.091,9.516 13.362,9.521 C13.335,9.521 13.309,9.521 13.282,9.521 C12.690,9.526 12.305,9.171 12.073,8.833 C12.025,8.759 11.984,8.689 11.946,8.626 C11.895,8.532 11.861,8.448 11.834,8.381 C11.917,8.070 12.028,7.772 12.165,7.489 C12.456,6.890 12.871,6.355 13.419,5.893 C13.468,5.850 13.226,5.921 13.272,5.877 C13.327,5.824 13.669,5.655 13.733,5.618 C13.772,5.595 13.692,5.556 13.557,5.528 C13.549,5.527 13.541,5.525 13.532,5.524 C13.380,5.496 13.167,5.485 12.972,5.527 C12.581,5.610 12.505,5.658 12.303,5.765 C12.385,5.678 12.650,5.540 12.585,5.554 C12.161,5.652 11.661,5.940 11.235,6.251 C11.231,6.211 11.235,6.179 11.243,6.116 C11.042,6.223 10.557,6.609 10.433,6.903 C10.433,6.839 10.433,6.807 10.425,6.736 C10.299,6.856 10.177,6.996 10.065,7.151 C10.055,7.165 10.044,7.178 10.034,7.192 C10.033,7.195 10.031,7.197 10.029,7.199 C9.691,7.112 9.367,7.055 9.056,7.023 C8.305,6.944 7.631,7.012 7.032,7.178 C6.951,7.201 6.871,7.224 6.793,7.250 C6.579,7.089 6.235,6.843 5.692,5.978 C5.659,5.926 5.656,6.097 5.626,6.042 C5.468,5.748 5.327,5.300 5.258,4.892 C5.234,4.750 5.218,4.613 5.214,4.489 C5.214,4.489 5.050,4.588 4.873,4.889 C4.806,5.003 4.737,5.146 4.675,5.324 C4.662,5.361 4.649,5.399 4.637,5.439 C4.596,5.570 4.568,5.648 4.539,5.720 C4.530,5.742 4.556,5.482 4.546,5.502 C4.530,5.537 4.502,5.579 4.472,5.627 C4.431,5.692 4.385,5.769 4.356,5.851 C4.349,5.870 4.343,5.889 4.338,5.909 C4.308,6.034 4.259,6.110 4.239,6.266 C4.238,6.270 4.237,6.273 4.235,6.276 C4.234,6.261 4.233,6.230 4.231,6.200 C4.229,6.152 4.225,6.105 4.218,6.123 C4.118,6.397 4.024,6.712 3.948,7.067 C3.838,7.628 3.726,8.395 3.793,9.368 C3.792,9.403 3.795,9.438 3.797,9.472 C3.800,9.514 3.803,9.555 3.802,9.594 C3.461,10.078 3.239,10.494 3.153,10.699 C3.066,10.873 2.979,11.068 2.893,11.284 C2.564,12.102 2.241,13.234 1.969,14.813 C1.969,14.813 2.200,14.061 2.661,13.210 C2.321,14.282 2.055,15.950 2.211,18.452 C2.215,18.397 2.248,18.101 2.322,17.660 C2.360,17.435 2.408,17.173 2.470,16.885 C2.473,16.950 2.477,17.015 2.482,17.081 C2.497,17.315 2.519,17.556 2.548,17.803 C2.565,17.949 2.585,18.097 2.607,18.248 C2.814,19.617 3.265,21.166 4.197,22.811 C5.154,24.502 7.676,28.430 14.005,29.900 C13.826,29.847 13.665,29.780 13.524,29.710 C13.117,29.508 12.879,29.280 12.879,29.280 C12.879,29.280 13.080,29.346 13.407,29.439 C14.081,29.630 15.290,29.931 16.388,29.990 C16.586,30.000 16.781,30.004 16.968,29.996 C16.428,29.900 16.320,29.631 16.320,29.631 C16.320,29.631 21.233,29.917 23.785,27.837 C23.835,27.796 23.885,27.754 23.934,27.711 C23.938,27.709 23.941,27.708 23.945,27.706 C24.327,27.379 24.606,27.021 24.755,26.675 C24.636,26.734 24.518,26.789 24.403,26.841 C24.025,27.251 23.564,27.586 23.055,27.860 C22.590,27.996 22.118,28.072 21.749,28.108 C21.581,28.124 21.434,28.132 21.319,28.133 C21.594,27.872 21.957,27.681 22.387,27.495 C23.024,27.219 23.811,26.955 24.683,26.496 C24.685,26.495 24.687,26.494 24.689,26.493 C24.741,26.466 24.793,26.437 24.845,26.409 C25.598,25.996 26.410,25.432 27.244,24.585 C28.038,23.779 28.427,23.083 28.643,22.448 C28.703,22.270 28.750,22.097 28.788,21.928 C28.852,21.645 28.893,21.372 28.934,21.104 C28.934,21.103 28.934,21.101 28.934,21.100 C28.934,21.102 28.933,21.103 28.933,21.105 C28.926,21.144 28.918,21.183 28.910,21.221 C28.671,22.267 27.797,22.972 26.794,23.585 C26.608,23.698 26.417,23.808 26.226,23.917 C26.339,23.696 26.459,23.491 26.582,23.294 C26.586,23.289 26.589,23.284 26.592,23.279 C26.590,23.284 26.588,23.288 26.586,23.293 C26.573,23.319 26.561,23.344 26.550,23.367 C26.567,23.339 26.585,23.311 26.603,23.283 C26.798,22.973 27.012,22.669 27.232,22.372 C27.760,21.689 28.278,21.118 28.621,20.490 C28.672,20.397 28.726,20.292 28.782,20.177 C28.803,20.134 28.825,20.090 28.846,20.043 C29.220,19.292 29.607,18.267 29.857,17.120 C29.969,16.606 30.053,16.067 30.097,15.517 C30.138,14.992 30.142,14.457 30.097,13.924 Z"/>
+</svg>
diff --git a/browser/branding/official/content/jar.mn b/browser/branding/official/content/jar.mn
new file mode 100644
index 000000000..4b8c5fc9d
--- /dev/null
+++ b/browser/branding/official/content/jar.mn
@@ -0,0 +1,18 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png
+ content/branding/about-logo.png
+ content/branding/about-logo@2x.png
+ content/branding/about-wordmark.png
+ content/branding/icon48.png
+ content/branding/icon64.png
+ content/branding/icon16.png (../default16.png)
+ content/branding/icon32.png (../default32.png)
+ content/branding/icon128.png (../mozicon128.png)
+ content/branding/identity-icons-brand.svg
+ content/branding/silhouette-40.svg
+ content/branding/aboutDialog.css
diff --git a/browser/branding/official/content/moz.build b/browser/branding/official/content/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/branding/official/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/official/content/silhouette-40.svg b/browser/branding/official/content/silhouette-40.svg
new file mode 100644
index 000000000..5a41a1c3f
--- /dev/null
+++ b/browser/branding/official/content/silhouette-40.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-45 31 40 40">
+ <path fill="#ccc" d="M-14.1,54.7c0.7-1.4,1.7-4.4,0.8-6.9c0,0,0,0,0,0.1l0,0c0,0-0.2,0.5-0.4,1.3c0-0.1,0-0.2,0-0.3
+ c0.1-0.9,0-1.9-0.1-2.9c-0.3-1.5-1.4-2.8-2-3.2c0,0,0.1,0,0.1,0.1c-0.1-0.1-0.1-0.1-0.1-0.1s0,0.1,0.1,0.4c-0.7-1.1-1.6-1.5-1.6-1.5
+ s0,0.2,0.1,0.5c-2-1.9-4.7-3-7.6-3c-3,0-5.7,1.2-7.8,3.1c0.1,0.1,0.2,0.3,0.4,0.5c0,0,0.8-0.1,1.7-0.1c1.7-1.2,3.6-1.8,5.7-1.8
+ c2.6,0,5.1,1.1,7,3c-0.2-0.1-0.1,0,0,0.1c-0.6-0.4-1.2-0.8-1.7-0.8c1,0.8,2.6,2.7,2.4,6.2c-0.3-0.6-0.6-1-0.9-1.3
+ c0.4,3.5,0,4.2-0.2,5.1c0-0.4-0.2-0.7-0.3-0.9c0,0,0,1.1-0.7,2.6c-0.5,1.2-1.1,1.5-1.3,1.5c-0.2,0-0.1-0.2-0.1-0.4
+ c0,0-0.4,0.2-0.7,0.6c-0.3,0.4-0.6,0.8-0.8,0.6c0.1-0.1,0.2-0.3,0.3-0.4c-0.1,0.1-0.5,0.4-1.2,0.5c-0.3,0-1.6,0.3-3.3-0.6
+ c0.3,0,0.6-0.1,0.9,0.1c-0.3-0.3-1-0.3-1.5-0.4c-0.5-0.4-1.1-1-1.4-1.4c1.3,0.3,2.8,0.1,3.6-0.5s1.3-1,1.8-0.9
+ c0.4,0.1,0.7-0.4,0.4-0.8c-0.3-0.4-1.2-1-2.3-0.7c-0.8,0.2-1.8,1.1-3.3,0.2c-1.3-0.8-1.3-1.4-1.3-1.8c0-0.3,0.2-0.7,0.5-0.8
+ c0.2,0.1,0.3,0.1,0.3,0.1s-0.1-0.1-0.1-0.2l0,0c0.1,0,0.4,0.2,0.6,0.2c0.2,0.1,0.3,0.2,0.3,0.2s0,0,0-0.1c0,0-0.1-0.2-0.3-0.3l0,0
+ c0.1,0,0.2,0.1,0.4,0.2c0-0.2,0.1-0.4,0.1-0.7c0-0.2,0-0.3-0.1-0.4c-0.1-0.1,0-0.1,0.1,0c0-0.1,0-0.1-0.1-0.2l0,0c0,0,0,0,0-0.1
+ c0.2-0.3,1.8-1.2,1.9-1.3c0.2-0.1,0.3-0.3,0.4-0.5c0.2-0.1,0.3-0.5,0.3-0.8c0-0.1-0.2-0.3-0.4-0.3c-0.1,0-0.4-0.1-0.6,0l0,0
+ c-0.3,0-0.7,0-1.2,0s-0.8-0.3-1-0.6c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.2-0.1-0.2c0.2-0.8,0.7-1.5,1.4-2.1c0,0-0.2,0-0.1,0
+ c0,0,0.3-0.2,0.4-0.2c0.1,0-0.3-0.1-0.6-0.1c-0.5,0.2-0.6,0.2-0.8,0.3c0.1-0.1,0.3-0.2,0.2-0.2c-0.3,0.1-0.7,0.4-1.1,0.6v-0.1
+ c-0.2,0.1-0.6,0.4-0.7,0.7c0-0.1,0-0.1,0-0.1c-0.1,0-0.2,0.2-0.3,0.3l0,0c-1.1-0.3-2-0.2-2.8,0c-0.2-0.1-0.6-0.5-0.9-1
+ c0,0,0,0.1-0.1,0.1c-0.1-0.4-0.3-0.9-0.3-1.3v-0.1c0,0-0.1,0.1-0.3,0.3c-0.1,0.2-0.2,0.3-0.2,0.5c0,0.1-0.1,0.2-0.1,0.2v-0.2
+ c0,0.1-0.1,0.2-0.2,0.3c0,0.2,0,0.3-0.1,0.4l0,0c0,0,0-0.2,0-0.1c-0.1,0.2-0.2,0.5-0.2,0.8c-0.1,0.3-0.1,0.5-0.1,0.8s0,0.7,0,1.2
+ c0,0.1,0,0.1,0,0.2c-0.3,0.4-0.5,0.7-0.6,0.9c-0.4,0.7-0.7,1.8-1,3.5c0,0,0.2-0.6,0.6-1.3l0,0c-0.3,0.9-0.5,2.3-0.4,4.4
+ c0-0.1,0.1-0.6,0.2-1.3c0.1,1.4,0.5,3.1,1.5,5c0.8,1.4,1.7,2.4,2.7,3.2c0.2,0.2,0.4,0.3,0.6,0.5c1.3,1,3.3,2.1,5,2.4
+ c-0.6-0.2-1-0.5-1-0.5s2,0.7,3.5,0.6c-0.5-0.1-0.6-0.3-0.6-0.3s4.2,0.2,6.4-1.5c0.5-0.4,0.8-0.8,0.9-1.2c0.6-0.4,1.3-0.8,2-1.6
+ c1.2-1.2,1.3-2.1,1.4-3v0.1C-14,55.2-14,54.9-14.1,54.7z"/>
+</svg>
diff --git a/browser/branding/official/default16.png b/browser/branding/official/default16.png
new file mode 100644
index 000000000..33ebba13b
--- /dev/null
+++ b/browser/branding/official/default16.png
Binary files differ
diff --git a/browser/branding/official/default22.png b/browser/branding/official/default22.png
new file mode 100644
index 000000000..2390f0924
--- /dev/null
+++ b/browser/branding/official/default22.png
Binary files differ
diff --git a/browser/branding/official/default24.png b/browser/branding/official/default24.png
new file mode 100644
index 000000000..737466b5a
--- /dev/null
+++ b/browser/branding/official/default24.png
Binary files differ
diff --git a/browser/branding/official/default256.png b/browser/branding/official/default256.png
new file mode 100644
index 000000000..eef767dab
--- /dev/null
+++ b/browser/branding/official/default256.png
Binary files differ
diff --git a/browser/branding/official/default32.png b/browser/branding/official/default32.png
new file mode 100644
index 000000000..e9d5bbb4b
--- /dev/null
+++ b/browser/branding/official/default32.png
Binary files differ
diff --git a/browser/branding/official/default48.png b/browser/branding/official/default48.png
new file mode 100644
index 000000000..10de89172
--- /dev/null
+++ b/browser/branding/official/default48.png
Binary files differ
diff --git a/browser/branding/official/disk.icns b/browser/branding/official/disk.icns
new file mode 100644
index 000000000..82fdccff8
--- /dev/null
+++ b/browser/branding/official/disk.icns
Binary files differ
diff --git a/browser/branding/official/document.icns b/browser/branding/official/document.icns
new file mode 100644
index 000000000..5f03305d7
--- /dev/null
+++ b/browser/branding/official/document.icns
Binary files differ
diff --git a/browser/branding/official/document.ico b/browser/branding/official/document.ico
new file mode 100644
index 000000000..0957f61e3
--- /dev/null
+++ b/browser/branding/official/document.ico
Binary files differ
diff --git a/browser/branding/official/dsstore b/browser/branding/official/dsstore
new file mode 100644
index 000000000..8ea703674
--- /dev/null
+++ b/browser/branding/official/dsstore
Binary files differ
diff --git a/browser/branding/official/firefox.VisualElementsManifest.xml b/browser/branding/official/firefox.VisualElementsManifest.xml
new file mode 100644
index 000000000..d675d573f
--- /dev/null
+++ b/browser/branding/official/firefox.VisualElementsManifest.xml
@@ -0,0 +1,8 @@
+<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
+ <VisualElements
+ ShowNameOnSquare150x150Logo='on'
+ Square150x150Logo='browser\VisualElements\VisualElements_150.png'
+ Square70x70Logo='browser\VisualElements\VisualElements_70.png'
+ ForegroundText='light'
+ BackgroundColor='#0996f8'/>
+</Application>
diff --git a/browser/branding/official/firefox.icns b/browser/branding/official/firefox.icns
new file mode 100644
index 000000000..4d2ad5a04
--- /dev/null
+++ b/browser/branding/official/firefox.icns
Binary files differ
diff --git a/browser/branding/official/firefox.ico b/browser/branding/official/firefox.ico
new file mode 100644
index 000000000..85ffedfe6
--- /dev/null
+++ b/browser/branding/official/firefox.ico
Binary files differ
diff --git a/browser/branding/official/locales/browserconfig.properties b/browser/branding/official/locales/browserconfig.properties
new file mode 100644
index 000000000..06cefece3
--- /dev/null
+++ b/browser/branding/official/locales/browserconfig.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/.
+
+# Do NOT localize or otherwise change these values
+browser.startup.homepage=about:home
diff --git a/browser/branding/official/locales/en-US/brand.dtd b/browser/branding/official/locales/en-US/brand.dtd
new file mode 100644
index 000000000..bb912cf05
--- /dev/null
+++ b/browser/branding/official/locales/en-US/brand.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 brandShorterName "Firefox">
+<!ENTITY brandShortName "Firefox">
+<!ENTITY brandFullName "Mozilla Firefox">
+<!ENTITY vendorShortName "Mozilla">
+<!ENTITY trademarkInfo.part1 "Firefox and the Firefox logos are trademarks of the Mozilla Foundation.">
diff --git a/browser/branding/official/locales/en-US/brand.properties b/browser/branding/official/locales/en-US/brand.properties
new file mode 100644
index 000000000..b7b267fb3
--- /dev/null
+++ b/browser/branding/official/locales/en-US/brand.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/.
+
+brandShorterName=Firefox
+brandShortName=Firefox
+brandFullName=Mozilla Firefox
+vendorShortName=Mozilla
+
+homePageSingleStartMain=Firefox Start, a fast home page with built-in search
+homePageImport=Import your home page from %S
+
+homePageMigrationPageTitle=Home Page Selection
+homePageMigrationDescription=Please select the home page you wish to use:
+
+syncBrandShortName=Sync
diff --git a/browser/branding/official/locales/jar.mn b/browser/branding/official/locales/jar.mn
new file mode 100644
index 000000000..9fdfe578b
--- /dev/null
+++ b/browser/branding/official/locales/jar.mn
@@ -0,0 +1,11 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+ locale/branding/brand.dtd (%brand.dtd)
+ locale/branding/brand.properties (%brand.properties)
+ locale/branding/browserconfig.properties
diff --git a/browser/branding/official/locales/moz.build b/browser/branding/official/locales/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/branding/official/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/official/moz.build b/browser/branding/official/moz.build
new file mode 100644
index 000000000..9045cee11
--- /dev/null
+++ b/browser/branding/official/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['content', 'locales']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+include('../branding-common.mozbuild')
+FirefoxBranding()
diff --git a/browser/branding/official/mozicon128.png b/browser/branding/official/mozicon128.png
new file mode 100644
index 000000000..946c31e68
--- /dev/null
+++ b/browser/branding/official/mozicon128.png
Binary files differ
diff --git a/browser/branding/official/newtab.ico b/browser/branding/official/newtab.ico
new file mode 100644
index 000000000..a9b37c08c
--- /dev/null
+++ b/browser/branding/official/newtab.ico
Binary files differ
diff --git a/browser/branding/official/newwindow.ico b/browser/branding/official/newwindow.ico
new file mode 100644
index 000000000..553720771
--- /dev/null
+++ b/browser/branding/official/newwindow.ico
Binary files differ
diff --git a/browser/branding/official/particles.bmp b/browser/branding/official/particles.bmp
new file mode 100644
index 000000000..d523606c2
--- /dev/null
+++ b/browser/branding/official/particles.bmp
Binary files differ
diff --git a/browser/branding/official/pbmode.ico b/browser/branding/official/pbmode.ico
new file mode 100644
index 000000000..47677c13f
--- /dev/null
+++ b/browser/branding/official/pbmode.ico
Binary files differ
diff --git a/browser/branding/official/pencil-rtl.bmp b/browser/branding/official/pencil-rtl.bmp
new file mode 100644
index 000000000..67d2fe5d2
--- /dev/null
+++ b/browser/branding/official/pencil-rtl.bmp
Binary files differ
diff --git a/browser/branding/official/pencil.bmp b/browser/branding/official/pencil.bmp
new file mode 100644
index 000000000..7dd55741f
--- /dev/null
+++ b/browser/branding/official/pencil.bmp
Binary files differ
diff --git a/browser/branding/official/pref/firefox-branding.js b/browser/branding/official/pref/firefox-branding.js
new file mode 100644
index 000000000..52aaa4f50
--- /dev/null
+++ b/browser/branding/official/pref/firefox-branding.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/. */
+
+pref("startup.homepage_override_url", "");
+pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
+pref("startup.homepage_welcome_url.additional", "");
+// Interval: Time between checks for a new version (in seconds)
+pref("app.update.interval", 43200); // 12 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
+// Give the user x seconds to react before showing the big UI. default=192 hours
+pref("app.update.promptWaitTime", 691200);
+// URL user can browse to manually if for some reason all update installation
+// attempts fail.
+pref("app.update.url.manual", "https://www.mozilla.org/firefox/");
+// A default value for the "More information about this update" link
+// supplied in the "An update is available" page of the update wizard.
+pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
+
+// The number of days a binary is permitted to be old
+// without checking for an update. This assumes that
+// app.update.checkInstallTime is true.
+pref("app.update.checkInstallTime.days", 63);
+
+// Give the user x seconds to reboot before showing a badge on the hamburger
+// button. default=immediately
+pref("app.update.badgeWaitTime", 0);
+
+// Number of usages of the web console or scratchpad.
+// If this is less than 5, then pasting code into the web console or scratchpad is disabled
+pref("devtools.selfxss.count", 0);
diff --git a/browser/branding/official/wizHeader.bmp b/browser/branding/official/wizHeader.bmp
new file mode 100644
index 000000000..51bec2211
--- /dev/null
+++ b/browser/branding/official/wizHeader.bmp
Binary files differ
diff --git a/browser/branding/official/wizHeaderRTL.bmp b/browser/branding/official/wizHeaderRTL.bmp
new file mode 100644
index 000000000..7610fbfd0
--- /dev/null
+++ b/browser/branding/official/wizHeaderRTL.bmp
Binary files differ
diff --git a/browser/branding/official/wizWatermark.bmp b/browser/branding/official/wizWatermark.bmp
new file mode 100644
index 000000000..5326bba93
--- /dev/null
+++ b/browser/branding/official/wizWatermark.bmp
Binary files differ
diff --git a/browser/branding/unofficial/VisualElements_150.png b/browser/branding/unofficial/VisualElements_150.png
new file mode 100644
index 000000000..461961e8d
--- /dev/null
+++ b/browser/branding/unofficial/VisualElements_150.png
Binary files differ
diff --git a/browser/branding/unofficial/VisualElements_70.png b/browser/branding/unofficial/VisualElements_70.png
new file mode 100644
index 000000000..aad81f40d
--- /dev/null
+++ b/browser/branding/unofficial/VisualElements_70.png
Binary files differ
diff --git a/browser/branding/unofficial/appname.bmp b/browser/branding/unofficial/appname.bmp
new file mode 100644
index 000000000..1ee08c944
--- /dev/null
+++ b/browser/branding/unofficial/appname.bmp
Binary files differ
diff --git a/browser/branding/unofficial/background.png b/browser/branding/unofficial/background.png
new file mode 100644
index 000000000..3fa4a0ec7
--- /dev/null
+++ b/browser/branding/unofficial/background.png
Binary files differ
diff --git a/browser/branding/unofficial/bgintro.bmp b/browser/branding/unofficial/bgintro.bmp
new file mode 100644
index 000000000..9f2a0a6e0
--- /dev/null
+++ b/browser/branding/unofficial/bgintro.bmp
Binary files differ
diff --git a/browser/branding/unofficial/branding.nsi b/browser/branding/unofficial/branding.nsi
new file mode 100644
index 000000000..34214453f
--- /dev/null
+++ b/browser/branding/unofficial/branding.nsi
@@ -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/.
+
+# NSIS branding defines for unofficial builds.
+# The official release build branding.nsi is located in other-license/branding/firefox/
+# The nightly build branding.nsi is located in browser/installer/windows/nsis/
+
+# BrandFullNameInternal is used for some registry and file system values
+# instead of BrandFullName and typically should not be modified.
+!define BrandFullNameInternal "Mozilla Developer Preview"
+!define CompanyName "mozilla.org"
+!define URLInfoAbout "https://www.mozilla.org"
+!define HelpLink "https://support.mozilla.org"
+
+!define URLStubDownload "http://download.mozilla.org/?os=win&lang=${AB_CD}&product=firefox-latest"
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=release&installer_lang=${AB_CD}"
+!define URLSystemRequirements "https://www.mozilla.org/firefox/system-requirements/"
+!define Channel "unofficial"
+
+# The installer's certificate name and issuer expected by the stub installer
+!define CertNameDownload "Mozilla Corporation"
+!define CertIssuerDownload "DigiCert SHA2 Assured ID Code Signing CA"
+
+# Dialog units are used so the UI displays correctly with the system's DPI
+# settings.
+# The dialog units for the bitmap's dimensions should match exactly with the
+# bitmap's width and height in pixels.
+!define APPNAME_BMP_WIDTH_DU 159u
+!define APPNAME_BMP_HEIGHT_DU 50u
+!define INTRO_BLURB_WIDTH_DU "230u"
+!define INTRO_BLURB_EDGE_DU "198u"
+!define INTRO_BLURB_LTR_TOP_DU "16u"
+!define INTRO_BLURB_RTL_TOP_DU "11u"
+
+# UI Colors that can be customized for each channel
+!define FOOTER_CONTROL_TEXT_COLOR_NORMAL 0x000000
+!define FOOTER_CONTROL_TEXT_COLOR_FADED 0x999999
+!define FOOTER_BKGRD_COLOR 0xFFFFFF
+!define INTRO_BLURB_TEXT_COLOR 0xFFFFFF
+!define INSTALL_BLURB_TEXT_COLOR 0xFFFFFF
+!define INSTALL_PROGRESS_TEXT_COLOR_NORMAL 0xFFFFFF
+!define COMMON_TEXT_COLOR_NORMAL 0xFFFFFF
+!define COMMON_TEXT_COLOR_FADED 0xA1AAB3
+!define COMMON_BKGRD_COLOR 0x0F1B26
diff --git a/browser/branding/unofficial/clock.bmp b/browser/branding/unofficial/clock.bmp
new file mode 100644
index 000000000..c74398edb
--- /dev/null
+++ b/browser/branding/unofficial/clock.bmp
Binary files differ
diff --git a/browser/branding/unofficial/configure.sh b/browser/branding/unofficial/configure.sh
new file mode 100644
index 000000000..edd3bd3e8
--- /dev/null
+++ b/browser/branding/unofficial/configure.sh
@@ -0,0 +1,5 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOZ_APP_DISPLAYNAME=Nightly
diff --git a/browser/branding/unofficial/content/about-background.png b/browser/branding/unofficial/content/about-background.png
new file mode 100644
index 000000000..70eb8dafd
--- /dev/null
+++ b/browser/branding/unofficial/content/about-background.png
Binary files differ
diff --git a/browser/branding/unofficial/content/about-logo.png b/browser/branding/unofficial/content/about-logo.png
new file mode 100644
index 000000000..4c7214ba3
--- /dev/null
+++ b/browser/branding/unofficial/content/about-logo.png
Binary files differ
diff --git a/browser/branding/unofficial/content/about-logo@2x.png b/browser/branding/unofficial/content/about-logo@2x.png
new file mode 100644
index 000000000..3526eda54
--- /dev/null
+++ b/browser/branding/unofficial/content/about-logo@2x.png
Binary files differ
diff --git a/browser/branding/unofficial/content/about-wordmark.svg b/browser/branding/unofficial/content/about-wordmark.svg
new file mode 100644
index 000000000..60b278d03
--- /dev/null
+++ b/browser/branding/unofficial/content/about-wordmark.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="132px" height="48px" viewBox="0 0 132 48">
+ <path fill="#fff" d="M60.6,14.3l-2.4-2.4C57,12.7,56,13,54.7,13c-3,0-3.8-1.4-7.6-1.4c-5.4,0-9.2,3.4-9.2,8.4
+ c0,3.3,2.2,6.1,5.6,7.2c-3.4,1-4.5,2.2-4.5,4.3c0,2.2,1.8,3.6,4.7,3.6h3.8c2.5,0,3.9,0.2,4.9,0.9c0.9,0.6,1.4,1.6,1.4,3
+ c0,3.1-2.2,4.4-6,4.4c-2,0-3.8-0.5-5.1-1.2c-0.9-0.6-1.5-1.6-1.5-2.9c0-0.8,0.3-1.7,0.7-2.2l-4.1,0.4c-0.3,1-0.5,1.7-0.5,2.6
+ c0,3.5,3,6.4,10.8,6.4c6.1,0,9.9-2.5,9.9-7.9c0-2.1-0.8-3.9-2.7-5.3c-1.5-1.1-3.1-1.4-6-1.4h-4c-1.3,0-2-0.5-2-1.2
+ c0-0.8,1.1-1.7,4.5-2.9c1.8,0,3.4-0.3,4.7-1.1c2.3-1.4,3.7-4.1,3.7-6.8c0-1.6-0.5-3-1.5-4.3c0.4,0.2,1.1,0.3,1.7,0.3
+ C57.9,15.8,59,15.4,60.6,14.3z M47.1,24.8c-3.1,0-4.8-1.7-4.8-4.8c0-3.5,1.6-5.1,4.7-5.1c3.3,0,4.6,1.5,4.6,4.9
+ C51.6,23.1,50.1,24.8,47.1,24.8z M30.7,1.3c-1.7,0-3,1.4-3,3.1s1.4,3,3,3c1.7,0,3.1-1.3,3.1-3C33.7,2.7,32.4,1.3,30.7,1.3z
+ M107.7,34.5c-1.1,0-1.4-0.6-1.4-2.5V6.5c0-3.8-0.6-5.9-0.6-5.9l-3.9,0.8c0,0,0.6,1.9,0.6,5.1v26.4c0,1.8,0.4,2.8,1.2,3.5
+ c0.7,0.7,1.7,1,2.9,1c1,0,1.5-0.1,2.5-0.5l-0.8-2.5C108.2,34.4,107.8,34.5,107.7,34.5z M74.7,11.6c-3.2,0-6.1,1.8-8.3,3.9
+ c0,0,0.2-1.8,0.2-3.4V6.3c0-3.8-0.7-5.9-0.7-5.9l-3.9,0.7c0,0,0.7,1.9,0.7,5.1V37h3.9V19.3c2.1-2.7,4.9-4.2,7.2-4.2
+ c1.3,0,2.3,0.4,2.9,1c0.7,0.7,0.9,1.8,0.9,3.7V37h3.8V19.1c0-1.8-0.1-2.6-0.4-3.6C80.4,13.2,77.7,11.6,74.7,11.6z M127.4,12.1
+ l-4.9,16.4c-0.6,2-1.6,5.2-1.6,5.2s-0.7-3.9-1.5-6.2l-5.1-16.2l-3.9,1.3l5.4,15.6c0.8,2.5,2.2,7.4,2.5,9l1.6-0.3
+ c-1.3,5.1-2.5,6.7-5.7,7.6l1.2,2.7c4.4-1,6.4-4.3,8-9.3l8.6-25.8H127.4z M96.9,15l1.2-2.9h-6.2c0-3.3,0.5-7.2,0.5-7.2l-4.1,0.9
+ c0,0-0.4,3.9-0.4,6.3h-3.2V15h3.2v17.1c0,2.5,0.7,4.1,2.4,5c0.9,0.4,1.9,0.7,3.3,0.7c1.8,0,3.1-0.4,4.4-1l-0.6-2.5
+ c-0.7,0.3-1.3,0.5-2.4,0.5c-2.4,0-3.2-0.9-3.2-3.7V15H96.9z M28.6,37h4.1V11.5l-4.1,0.6V37z M18.9,21.3c0,5,0.4,10.5,0.4,10.5
+ s-1.4-3.8-3.2-7.2L4.8,2.7H0V37h4.2L4,17.1c0-4.5-0.4-9.3-0.4-9.3s1.7,4.1,3.9,8.2l11,21h4.3V2.7h-4L18.9,21.3z"/>
+</svg>
diff --git a/browser/branding/unofficial/content/about.png b/browser/branding/unofficial/content/about.png
new file mode 100644
index 000000000..231449344
--- /dev/null
+++ b/browser/branding/unofficial/content/about.png
Binary files differ
diff --git a/browser/branding/unofficial/content/aboutDialog.css b/browser/branding/unofficial/content/aboutDialog.css
new file mode 100644
index 000000000..be7fdcab6
--- /dev/null
+++ b/browser/branding/unofficial/content/aboutDialog.css
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#aboutDialogContainer {
+ background-image: url("chrome://branding/content/about-background.png");
+ background-repeat: no-repeat;
+ background-color: rgb(10,17,37);
+ color: #fff;
+}
+
+.text-link {
+ color: #fff !important;
+ text-decoration: underline;
+}
+
+#rightBox {
+ /* this margin prevents text from overlapping the planet image */
+ margin-left: 280px;
+ margin-right: 20px;
+}
+
+#bottomBox {
+ background-color: rgba(0,0,0,.7);
+}
diff --git a/browser/branding/unofficial/content/icon48.png b/browser/branding/unofficial/content/icon48.png
new file mode 100644
index 000000000..5fc7861e5
--- /dev/null
+++ b/browser/branding/unofficial/content/icon48.png
Binary files differ
diff --git a/browser/branding/unofficial/content/icon64.png b/browser/branding/unofficial/content/icon64.png
new file mode 100644
index 000000000..83f7016bc
--- /dev/null
+++ b/browser/branding/unofficial/content/icon64.png
Binary files differ
diff --git a/browser/branding/unofficial/content/identity-icons-brand.svg b/browser/branding/unofficial/content/identity-icons-brand.svg
new file mode 100644
index 000000000..6c33113e5
--- /dev/null
+++ b/browser/branding/unofficial/content/identity-icons-brand.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+ <path fill="#144787" d="M15.953,30.000 C8.221,30.000 1.953,23.732 1.953,16.000 C1.953,8.268 8.221,2.000 15.953,2.000 C23.685,2.000 29.953,8.268 29.953,16.000 C29.953,23.732 23.685,30.000 15.953,30.000 ZM16.000,4.000 C9.373,4.000 4.000,9.373 4.000,16.000 C4.000,22.627 9.373,28.000 16.000,28.000 C22.627,28.000 28.000,22.627 28.000,16.000 C28.000,9.373 22.627,4.000 16.000,4.000 ZM27.085,16.311 C27.142,16.652 27.085,17.189 26.942,17.483 C26.885,17.958 26.784,18.470 26.561,18.931 C26.407,19.254 26.189,19.798 25.772,19.846 C25.646,19.858 25.319,20.214 25.283,20.155 C25.208,20.028 25.155,19.869 24.999,19.809 C24.873,19.751 24.990,19.698 24.895,19.643 C24.828,19.607 24.816,19.527 24.803,19.452 C24.756,19.459 24.710,19.469 24.668,19.492 C24.580,19.543 24.528,19.636 24.445,19.693 C24.391,19.671 24.300,19.631 24.307,19.561 C24.235,19.627 24.158,19.762 24.088,19.660 C24.030,19.576 24.071,19.459 24.068,19.367 C24.064,19.275 23.978,19.139 23.883,19.254 C23.816,19.334 23.769,19.345 23.665,19.365 C23.570,19.385 23.496,19.449 23.395,19.450 C23.156,19.456 23.176,19.563 23.117,19.744 C23.063,19.902 22.845,19.920 22.750,20.050 C22.700,20.117 22.549,20.431 22.421,20.318 C22.319,20.227 22.581,19.988 22.581,19.868 C22.581,19.767 22.498,19.709 22.475,19.618 C22.457,19.552 22.479,19.498 22.403,19.463 C22.448,19.376 22.500,19.239 22.455,19.142 C22.398,19.015 22.189,19.130 22.139,19.206 C22.088,19.277 21.964,19.523 21.848,19.374 C21.819,19.341 21.840,19.299 21.785,19.301 C21.747,19.301 21.720,19.325 21.702,19.354 C21.621,19.328 21.652,19.254 21.682,19.199 C21.770,19.040 21.781,18.864 21.900,18.718 C22.026,18.561 22.220,18.468 22.333,18.301 C22.372,18.242 22.441,18.131 22.385,18.064 C22.364,18.038 22.326,18.027 22.313,17.995 C22.297,17.956 22.315,17.913 22.310,17.874 C22.268,17.891 22.222,17.909 22.175,17.894 C22.169,17.851 22.175,17.805 22.164,17.761 C22.108,17.783 22.049,17.851 21.982,17.836 C21.919,17.823 21.927,17.867 21.857,17.865 C21.916,17.772 21.952,17.667 22.015,17.575 C22.051,17.521 22.101,17.473 22.130,17.413 C22.198,17.271 22.058,17.158 22.081,17.020 C22.106,16.863 22.281,16.825 22.417,16.839 C22.554,16.854 22.707,16.980 22.845,16.930 C22.975,16.887 23.014,16.706 22.977,16.590 C22.933,16.460 22.768,16.424 22.779,16.265 C22.784,16.172 22.831,16.089 22.813,15.996 C22.799,15.921 22.761,15.854 22.743,15.779 C22.700,15.598 22.867,15.549 22.923,15.405 C22.951,15.329 22.964,15.138 23.092,15.223 C23.205,15.300 23.158,15.476 23.266,15.564 C23.392,15.670 23.563,15.600 23.690,15.535 C23.796,15.480 23.958,15.425 24.012,15.314 C24.088,15.163 23.947,14.988 24.138,14.892 C24.217,14.853 24.415,14.744 24.505,14.779 C24.587,14.810 24.616,14.902 24.675,14.961 C24.699,14.882 24.731,14.810 24.781,14.748 C24.889,14.613 25.040,14.518 25.035,14.330 C25.082,14.104 25.010,14.119 24.970,13.940 C24.961,13.858 24.888,13.292 25.017,13.315 C25.310,13.366 25.065,12.676 25.022,12.563 C25.008,12.523 24.942,12.470 24.895,12.448 C24.767,12.330 24.692,12.528 24.611,12.454 C24.512,12.332 24.530,12.133 24.515,11.982 C24.490,11.818 24.397,11.698 24.393,11.534 C24.391,11.455 23.924,10.932 24.021,10.856 C24.079,10.823 24.816,10.879 24.776,10.823 C24.654,10.644 24.704,10.469 24.461,10.371 C24.307,10.307 24.204,10.138 24.055,10.065 C23.983,10.030 23.679,9.806 23.937,9.790 C24.106,9.779 23.902,9.526 23.827,9.502 C23.735,9.473 23.742,9.752 23.654,9.509 C23.635,9.469 23.273,9.012 23.264,9.056 C23.252,9.127 23.343,9.225 23.333,9.307 C23.306,9.533 22.982,9.108 22.993,9.125 C22.933,9.059 22.694,8.882 22.666,8.802 C22.671,8.819 22.774,8.629 22.775,8.629 C22.833,8.553 22.774,8.467 22.707,8.398 C22.624,8.314 22.486,8.305 22.473,8.177 C22.471,8.157 22.374,7.979 22.459,7.982 C22.529,7.988 22.754,8.172 22.836,8.210 C23.061,8.314 22.996,8.383 23.128,8.505 C23.313,8.638 23.606,8.799 23.768,8.997 C23.793,9.043 24.122,9.380 24.134,9.252 C24.138,9.209 24.019,8.872 23.971,8.855 C23.971,8.855 23.755,8.580 23.750,8.556 C23.751,8.565 23.408,8.154 23.493,8.168 C23.624,8.192 24.093,8.542 24.064,8.677 C24.044,8.766 24.221,8.830 24.248,8.912 C24.255,8.935 24.567,9.221 24.611,9.241 C24.658,9.263 24.819,9.415 24.855,9.460 C24.920,9.506 24.972,9.462 24.994,9.578 C25.003,9.631 25.087,9.819 25.114,9.855 C25.193,9.961 25.247,10.125 25.294,10.249 C25.360,10.431 25.319,10.633 25.323,10.821 C25.342,10.874 25.233,10.938 25.240,11.005 C25.253,11.111 25.254,11.235 25.269,11.329 C25.283,11.426 25.466,11.679 25.436,11.759 C25.366,11.907 25.416,11.891 25.486,12.027 C25.533,12.120 25.436,12.159 25.479,12.277 C25.296,12.295 25.477,12.379 25.360,12.421 C25.254,12.435 25.222,12.414 25.132,12.483 C25.100,12.506 25.506,13.100 25.580,12.526 C25.600,12.372 25.765,12.220 25.921,12.157 C26.006,12.120 26.087,12.281 26.110,12.120 C26.115,12.075 26.029,11.929 26.071,11.909 C26.155,11.878 26.299,12.539 26.365,12.588 C26.529,12.703 26.642,12.893 26.664,13.093 C26.687,13.325 27.108,13.685 27.021,13.898 C26.960,14.050 26.984,14.494 27.000,14.662 C27.027,14.841 27.085,14.984 27.086,15.176 C27.086,15.280 27.016,15.531 27.063,15.619 C27.169,15.815 27.050,16.103 27.085,16.311 ZM22.142,23.184 C22.112,23.125 22.171,23.051 22.238,23.053 C22.254,23.009 22.333,22.943 22.382,22.954 C22.416,22.963 22.419,23.000 22.453,22.969 C22.486,22.940 22.480,22.892 22.522,22.869 C22.633,22.801 22.687,22.918 22.624,23.003 C22.576,23.067 22.455,23.115 22.380,23.094 C22.284,23.069 22.231,23.151 22.142,23.184 ZM23.207,22.324 C23.239,22.315 23.270,22.260 23.306,22.269 C23.406,22.296 23.298,22.486 23.288,22.537 C23.275,22.612 23.284,22.763 23.169,22.745 L23.178,22.726 C23.171,22.725 23.158,22.726 23.151,22.728 C23.149,22.736 23.142,22.743 23.142,22.745 C23.147,22.701 23.158,22.644 23.137,22.603 C23.111,22.555 23.056,22.544 23.013,22.521 C22.969,22.499 22.959,22.482 22.986,22.442 C23.007,22.409 23.041,22.351 23.077,22.331 C23.117,22.311 23.164,22.338 23.207,22.324 ZM22.843,22.551 C22.923,22.537 22.969,22.582 23.023,22.635 C23.084,22.697 23.040,22.772 22.962,22.794 C22.887,22.818 22.856,22.754 22.786,22.754 C22.784,22.741 22.788,22.725 22.782,22.715 L22.784,22.717 C22.770,22.644 22.750,22.570 22.843,22.551 ZM21.519,24.762 C21.567,24.727 21.846,24.663 21.821,24.594 C21.794,24.527 21.833,24.497 21.894,24.472 C21.932,24.456 21.966,24.405 22.006,24.399 C22.036,24.472 22.031,24.559 22.135,24.559 C22.214,24.559 22.293,24.523 22.355,24.476 C22.432,24.421 22.439,24.330 22.536,24.295 C22.621,24.266 22.687,24.230 22.764,24.186 C22.833,24.148 22.894,24.066 22.975,24.056 C23.014,24.053 23.111,24.047 23.138,24.086 C23.178,24.144 23.007,24.248 22.971,24.277 C22.933,24.308 22.826,24.395 22.905,24.445 C22.964,24.479 23.050,24.417 23.102,24.395 C23.176,24.363 23.259,24.339 23.313,24.275 C23.363,24.217 23.378,24.138 23.448,24.097 C23.536,24.044 23.588,23.991 23.640,23.902 C23.674,23.845 23.670,23.783 23.719,23.732 C23.768,23.678 23.748,23.617 23.778,23.557 C23.830,23.464 23.868,23.572 23.893,23.610 C23.953,23.590 23.971,23.508 24.025,23.473 C24.059,23.452 24.122,23.435 24.138,23.393 C24.152,23.359 24.143,23.326 24.174,23.297 C24.231,23.242 24.357,23.260 24.429,23.226 C24.472,23.206 24.526,23.098 24.578,23.111 C24.487,23.399 24.310,23.603 24.102,23.820 C23.895,24.033 23.706,24.266 23.458,24.435 C23.205,24.609 22.957,24.793 22.680,24.931 C22.405,25.070 22.198,25.292 21.945,25.461 C21.384,25.837 20.791,26.207 20.145,26.425 C19.822,26.536 19.494,26.666 19.162,26.751 C19.054,26.779 18.944,26.802 18.835,26.828 C18.811,26.833 18.676,26.883 18.658,26.872 C18.648,26.848 18.444,26.892 18.414,26.908 C18.301,26.966 18.196,26.992 18.071,26.992 C17.968,26.992 17.857,26.963 17.765,27.012 C17.733,27.030 17.578,27.065 17.628,26.974 C17.655,26.924 17.821,26.954 17.862,26.954 C17.963,26.952 18.056,26.912 18.150,26.879 C18.270,26.839 18.383,26.793 18.504,26.755 C18.570,26.735 18.631,26.724 18.687,26.688 C18.784,26.624 18.892,26.637 18.993,26.598 C19.061,26.573 19.104,26.473 19.201,26.498 C19.250,26.511 19.275,26.533 19.329,26.533 C19.399,26.531 19.370,26.513 19.385,26.462 C19.403,26.392 19.458,26.381 19.487,26.438 C19.525,26.511 19.644,26.394 19.694,26.381 C19.771,26.363 19.759,26.287 19.866,26.290 C19.962,26.296 20.025,26.216 20.116,26.197 C20.163,26.186 20.285,26.190 20.310,26.145 C20.187,26.148 20.064,26.166 19.947,26.214 C19.825,26.263 19.710,26.334 19.579,26.360 C19.457,26.381 19.329,26.369 19.207,26.396 C19.090,26.422 18.986,26.482 18.874,26.520 C18.777,26.555 18.653,26.580 18.556,26.531 C18.448,26.473 18.551,26.391 18.624,26.372 C18.720,26.350 18.836,26.365 18.919,26.305 C18.986,26.258 19.005,26.172 19.025,26.099 C18.982,26.103 18.525,26.137 18.667,26.010 C18.732,25.950 18.835,25.935 18.919,25.917 C19.013,25.897 19.102,25.851 19.201,25.855 C19.324,25.860 19.401,25.950 19.520,25.868 C19.590,25.820 19.647,25.747 19.728,25.716 C19.807,25.684 19.904,25.746 19.976,25.693 C20.039,25.645 20.052,25.567 20.138,25.545 C20.224,25.523 20.384,25.545 20.399,25.418 C20.408,25.352 20.267,25.281 20.230,25.230 C20.183,25.164 20.122,25.079 20.066,25.020 C20.028,24.982 19.915,24.969 19.920,24.904 C19.929,24.802 20.086,24.807 20.156,24.791 C20.266,24.763 20.348,24.703 20.467,24.729 C20.569,24.749 20.647,24.763 20.738,24.703 C20.819,24.650 20.891,24.596 20.986,24.569 C20.943,24.654 20.934,24.754 20.879,24.835 C20.830,24.904 20.749,24.947 20.695,25.015 C20.542,25.206 20.843,25.316 20.879,25.172 C20.897,25.095 20.857,25.039 20.952,24.999 C21.010,24.975 21.102,24.977 21.110,24.907 C21.123,24.820 21.227,24.762 21.312,24.751 C21.400,24.738 21.497,24.663 21.583,24.671 C21.562,24.691 21.508,24.727 21.519,24.762 ZM19.358,6.878 C19.266,7.018 19.200,7.122 19.066,7.232 C18.993,7.294 19.013,7.534 18.871,7.436 C18.822,7.401 18.820,7.388 18.748,7.396 C18.700,7.401 18.649,7.447 18.639,7.487 C18.612,7.492 18.581,7.587 18.547,7.525 C18.527,7.534 18.419,7.572 18.400,7.563 C18.371,7.551 18.347,7.478 18.319,7.454 C18.245,7.394 18.373,7.308 18.310,7.246 C18.263,7.199 18.205,7.193 18.151,7.226 C18.065,7.283 18.029,7.228 17.946,7.244 C17.858,7.261 17.905,7.195 17.891,7.131 C17.853,7.113 17.808,7.117 17.770,7.128 C17.720,7.142 17.736,7.152 17.731,7.173 C17.713,7.157 17.630,7.152 17.618,7.150 C17.610,7.117 17.702,7.062 17.707,7.011 C17.707,6.997 17.689,6.860 17.684,6.855 C17.616,6.805 17.722,6.776 17.761,6.811 C17.806,6.849 17.844,6.778 17.902,6.802 C17.907,6.774 17.799,6.742 17.776,6.738 C17.715,6.729 17.653,6.800 17.589,6.807 C17.567,6.809 17.475,6.842 17.463,6.813 C17.452,6.791 17.468,6.758 17.475,6.738 C17.443,6.711 17.407,6.694 17.364,6.691 C17.299,6.685 17.233,6.705 17.173,6.672 C17.098,6.629 17.057,6.599 16.969,6.585 C16.906,6.574 16.852,6.537 16.843,6.474 C16.834,6.423 16.808,6.293 16.821,6.242 C16.893,6.228 16.846,6.304 16.904,6.308 C16.963,6.312 17.008,6.330 17.067,6.337 C17.118,6.343 17.191,6.377 17.242,6.361 C17.324,6.335 17.348,6.204 17.395,6.193 C17.393,6.175 17.382,6.160 17.366,6.149 C17.404,6.140 17.441,6.128 17.472,6.106 C17.445,6.091 17.382,6.133 17.391,6.075 C17.398,6.033 17.423,5.996 17.429,5.954 C17.438,5.869 17.231,5.867 17.181,5.887 C17.139,5.902 17.102,5.931 17.076,5.967 C17.037,6.025 16.994,6.000 16.925,5.991 C16.938,5.900 16.871,5.883 16.818,5.825 C16.742,5.745 16.690,5.636 16.699,5.528 C16.706,5.439 16.629,5.364 16.690,5.278 C16.785,5.145 17.116,5.151 17.272,5.206 C17.623,5.224 17.968,5.258 18.310,5.355 C18.696,5.464 19.038,5.663 19.417,5.781 C19.597,5.836 19.649,5.934 19.590,6.104 C19.539,6.250 19.669,6.293 19.784,6.333 C19.886,6.372 20.003,6.441 19.955,6.568 C19.913,6.678 19.759,6.701 19.658,6.718 C19.530,6.738 19.430,6.765 19.358,6.878 ZM16.767,16.426 C16.812,16.438 16.868,16.393 16.922,16.404 C16.956,16.411 16.963,16.433 16.990,16.444 C17.021,16.460 17.021,16.460 17.051,16.453 C17.073,16.438 17.096,16.433 17.127,16.449 C17.147,16.462 17.152,16.484 17.179,16.493 C17.218,16.510 17.247,16.493 17.289,16.491 C17.350,16.500 17.377,16.542 17.420,16.482 C17.447,16.426 17.459,16.373 17.526,16.373 C17.578,16.377 17.618,16.400 17.578,16.442 C17.553,16.473 17.535,16.502 17.538,16.551 C17.549,16.590 17.582,16.590 17.610,16.566 C17.643,16.533 17.653,16.473 17.709,16.466 C17.759,16.462 17.804,16.508 17.857,16.511 C17.882,16.513 17.905,16.508 17.932,16.517 C17.961,16.526 17.972,16.539 17.991,16.551 C18.040,16.571 18.076,16.553 18.115,16.577 C18.189,16.632 18.220,16.715 18.277,16.774 C18.311,16.807 18.364,16.817 18.391,16.852 C18.403,16.874 18.409,16.889 18.419,16.903 C18.439,16.914 18.459,16.925 18.473,16.947 C18.500,16.981 18.480,17.011 18.475,17.052 C18.468,17.087 18.484,17.116 18.502,17.153 C18.531,17.195 18.579,17.322 18.489,17.328 C18.466,17.335 18.432,17.335 18.414,17.331 C18.382,17.331 18.400,17.335 18.378,17.315 C18.346,17.291 18.313,17.266 18.283,17.242 C18.238,17.202 18.240,17.145 18.211,17.094 C18.195,17.065 18.162,17.054 18.124,17.038 C18.103,17.020 18.090,17.020 18.060,17.001 C18.033,16.992 18.008,16.998 17.975,16.998 C17.923,16.996 17.849,16.932 17.821,16.890 C17.804,16.861 17.801,16.847 17.767,16.847 C17.742,16.845 17.715,16.867 17.688,16.867 C17.621,16.867 17.571,16.830 17.499,16.848 C17.436,16.865 17.384,16.852 17.314,16.845 C17.240,16.848 17.161,16.836 17.105,16.785 C17.073,16.759 17.055,16.723 17.015,16.699 C16.979,16.692 16.938,16.694 16.911,16.684 C16.848,16.668 16.769,16.655 16.711,16.628 C16.665,16.608 16.636,16.566 16.595,16.535 C16.546,16.506 16.474,16.491 16.424,16.462 C16.379,16.449 16.264,16.422 16.318,16.375 C16.345,16.351 16.406,16.329 16.440,16.327 C16.501,16.336 16.505,16.386 16.546,16.415 C16.578,16.440 16.625,16.429 16.659,16.397 C16.679,16.375 16.674,16.358 16.704,16.377 C16.731,16.386 16.747,16.415 16.767,16.426 ZM16.489,5.335 C16.436,5.355 16.381,5.339 16.325,5.351 C16.325,5.348 16.323,5.342 16.323,5.339 C16.285,5.328 16.242,5.295 16.257,5.253 C16.303,5.233 16.589,5.189 16.598,5.257 C16.604,5.293 16.515,5.324 16.489,5.335 ZM15.832,5.765 C15.861,5.889 15.724,6.033 15.696,6.158 C15.671,6.273 15.550,6.434 15.455,6.505 C15.354,6.581 15.158,6.740 15.027,6.700 C14.950,6.678 14.871,6.658 14.791,6.638 C14.711,6.618 14.644,6.548 14.572,6.530 C14.617,6.417 14.687,6.558 14.741,6.477 C14.747,6.501 14.799,6.523 14.820,6.499 C14.844,6.477 14.822,6.415 14.822,6.388 C14.820,6.319 14.939,6.235 15.002,6.228 C15.047,6.224 15.099,6.226 15.137,6.200 C15.185,6.166 15.223,6.202 15.286,6.186 C15.331,6.177 15.503,6.078 15.480,6.018 C15.392,6.027 15.354,6.018 15.277,6.082 C15.293,6.020 15.419,5.951 15.399,5.894 C15.338,5.896 15.336,5.980 15.279,5.989 C15.223,5.998 15.214,5.892 15.182,5.865 C15.043,5.750 14.982,6.140 14.854,6.107 C14.797,6.093 14.815,6.013 14.732,6.029 C14.630,6.049 14.603,6.122 14.596,6.215 C14.592,6.268 14.500,6.330 14.459,6.379 C14.394,6.457 14.311,6.441 14.236,6.388 C14.171,6.344 14.180,6.242 14.101,6.215 C14.026,6.188 13.929,6.210 13.860,6.250 C13.778,6.297 13.736,6.394 13.657,6.437 C13.583,6.474 13.461,6.461 13.384,6.465 C13.339,6.466 13.136,6.492 13.157,6.397 C13.172,6.330 13.215,6.275 13.138,6.228 C13.098,6.202 13.019,6.153 13.062,6.097 C13.098,6.051 13.175,6.053 13.197,5.996 C13.127,5.958 13.031,5.947 13.035,5.849 C12.924,5.829 12.884,5.927 12.814,5.980 C12.717,6.051 12.773,5.914 12.792,5.882 C12.818,5.838 12.866,5.774 12.915,5.758 C12.965,5.739 12.969,5.690 13.003,5.656 L12.990,5.667 C12.951,5.628 12.933,5.550 12.951,5.499 C12.972,5.437 13.048,5.448 13.100,5.441 C13.256,5.422 13.431,5.288 13.592,5.340 C13.745,5.391 13.848,5.300 14.002,5.306 C14.080,5.308 14.169,5.293 14.247,5.286 C14.329,5.277 14.394,5.227 14.480,5.227 C14.542,5.227 14.680,5.238 14.631,5.328 C14.606,5.377 14.576,5.462 14.673,5.455 C14.800,5.446 14.887,5.264 15.005,5.237 C15.086,5.216 15.045,5.306 15.020,5.335 C15.011,5.344 14.915,5.492 14.993,5.461 C15.011,5.452 15.025,5.439 15.036,5.422 C15.063,5.380 15.111,5.377 15.160,5.364 C15.253,5.339 15.342,5.309 15.431,5.275 C15.604,5.207 15.669,5.282 15.818,5.349 C15.863,5.371 16.160,5.346 16.131,5.441 C16.111,5.504 15.976,5.534 15.920,5.552 C15.789,5.594 15.807,5.648 15.832,5.765 ZM12.967,8.084 C12.936,8.126 12.792,8.137 12.760,8.097 C12.764,8.090 12.767,8.077 12.774,8.072 L12.742,8.086 C12.694,8.134 12.623,8.154 12.557,8.145 C12.559,8.110 12.541,8.057 12.555,8.026 C12.571,7.993 12.614,7.984 12.638,7.957 C12.679,7.908 12.704,7.835 12.753,7.793 C12.807,7.747 12.907,7.731 12.927,7.815 C12.949,7.895 12.868,7.928 12.853,7.991 C12.909,7.988 13.008,8.032 12.967,8.084 ZM12.708,6.525 C12.735,6.534 12.771,6.548 12.803,6.552 C12.783,6.576 12.776,6.609 12.830,6.594 C12.803,6.652 12.746,6.714 12.683,6.731 C12.611,6.749 12.528,6.732 12.474,6.793 C12.386,6.893 12.501,7.060 12.631,7.008 C12.643,7.057 12.607,7.068 12.647,7.084 C12.530,7.226 12.440,7.387 12.298,7.166 C12.165,6.960 11.940,7.117 11.804,7.241 C11.654,7.376 11.638,7.600 11.856,7.676 C11.994,7.725 11.973,7.742 11.870,7.831 C11.917,7.886 11.775,7.937 11.759,8.004 C11.796,7.964 11.874,7.970 11.886,7.906 C11.951,7.917 11.911,7.837 11.960,7.848 C12.082,7.873 11.928,8.001 11.906,8.026 C11.949,8.055 11.998,8.032 12.045,8.032 C12.086,8.032 12.125,8.059 12.165,8.052 C12.221,8.044 12.194,7.999 12.230,7.982 C12.294,7.955 12.253,8.061 12.244,8.073 C12.205,8.130 12.176,8.248 12.109,8.261 C12.048,8.272 12.014,8.283 12.025,8.360 C12.034,8.432 12.057,8.425 12.009,8.502 C11.940,8.606 12.055,8.642 12.071,8.726 C12.088,8.815 12.061,8.791 12.140,8.830 C12.206,8.862 12.273,8.857 12.343,8.868 C12.426,8.879 12.566,8.782 12.537,8.689 C12.523,8.644 12.516,8.620 12.550,8.584 C12.573,8.558 12.640,8.502 12.674,8.544 C12.641,8.498 12.701,8.465 12.708,8.420 C12.715,8.365 12.814,8.374 12.778,8.303 C12.758,8.268 12.638,8.230 12.650,8.210 C12.728,8.201 12.810,8.190 12.884,8.221 C13.003,8.272 13.046,8.471 13.202,8.398 C13.366,8.321 13.483,8.163 13.529,7.995 C13.565,7.860 13.454,7.829 13.350,7.791 C13.287,7.767 13.278,7.727 13.249,7.669 C13.217,7.603 13.342,7.536 13.245,7.521 C13.125,7.501 13.084,7.376 13.209,7.325 C13.267,7.303 13.328,7.317 13.386,7.301 C13.438,7.286 13.458,7.144 13.506,7.228 C13.510,7.166 13.666,7.232 13.715,7.228 C13.783,7.223 13.729,7.164 13.790,7.172 C13.830,7.179 13.855,7.217 13.894,7.221 C13.948,7.226 13.947,7.181 13.984,7.162 C14.035,7.141 14.069,7.210 14.110,7.219 C14.155,7.228 14.202,7.261 14.249,7.239 C14.283,7.223 14.342,7.197 14.349,7.261 C14.362,7.348 14.405,7.403 14.414,7.478 C14.423,7.552 14.601,7.554 14.489,7.653 C14.425,7.709 14.358,7.815 14.263,7.786 C14.204,7.769 14.216,7.798 14.162,7.831 C14.114,7.860 14.107,7.913 14.063,7.917 C13.925,7.930 14.083,8.044 14.078,8.110 C14.072,8.166 14.112,8.194 14.114,8.245 C14.117,8.301 14.071,8.361 14.063,8.418 C14.051,8.503 14.216,8.647 14.263,8.718 C14.344,8.841 14.425,8.955 14.560,9.026 C14.644,9.070 14.684,9.165 14.763,9.216 C14.802,9.241 14.862,9.225 14.863,9.274 C14.863,9.320 14.858,9.371 14.874,9.416 C14.894,9.478 15.023,9.487 15.079,9.487 C15.088,9.588 15.235,9.480 15.279,9.469 C15.180,9.584 15.104,9.693 14.986,9.793 C14.930,9.839 14.865,9.899 14.790,9.910 C14.725,9.921 14.673,9.883 14.619,9.939 C14.576,9.983 14.585,10.052 14.543,10.100 C14.504,10.145 14.421,10.152 14.371,10.191 C14.347,10.207 14.112,10.371 14.128,10.251 C14.133,10.209 14.160,10.176 14.169,10.136 C14.182,10.080 14.117,10.076 14.092,10.049 C14.049,10.003 14.020,10.038 14.008,9.957 C14.000,9.912 13.986,9.861 13.945,9.832 C13.876,9.784 13.898,9.786 13.866,9.708 C13.840,9.646 13.781,9.606 13.758,9.546 C13.700,9.404 13.716,9.256 13.616,9.127 C13.612,9.178 13.652,9.220 13.635,9.260 C13.619,9.303 13.632,9.373 13.657,9.411 C13.569,9.404 13.513,9.376 13.450,9.316 C13.422,9.291 13.328,9.200 13.289,9.254 C13.337,9.249 13.441,9.394 13.504,9.425 C13.648,9.497 13.648,9.548 13.587,9.695 C13.646,9.706 13.736,9.655 13.776,9.724 C13.812,9.786 13.756,9.854 13.864,9.852 C13.848,9.996 13.706,10.089 13.698,10.234 C13.693,10.316 13.725,10.386 13.698,10.469 C13.670,10.557 13.600,10.633 13.506,10.659 C13.276,10.721 13.175,10.484 13.094,10.331 C13.060,10.267 12.974,10.209 12.898,10.245 C12.868,10.260 12.855,10.293 12.825,10.307 C12.767,10.336 12.706,10.322 12.652,10.362 C12.587,10.409 12.551,10.495 12.469,10.521 C12.418,10.537 12.350,10.521 12.307,10.553 C12.280,10.573 12.273,10.608 12.271,10.639 C12.235,10.646 12.208,10.670 12.185,10.697 C12.136,10.754 12.041,10.790 12.003,10.847 C11.951,10.923 12.066,10.992 12.057,11.069 C12.052,11.115 11.976,11.231 11.940,11.244 C11.777,11.300 11.894,11.463 11.937,11.554 C11.946,11.572 11.953,11.597 11.929,11.610 C11.890,11.634 11.904,11.650 11.929,11.698 C11.971,11.776 12.037,11.836 12.106,11.893 C12.163,11.938 12.289,11.965 12.305,12.031 C12.334,12.149 12.251,12.244 12.185,12.335 C12.133,12.406 12.091,12.488 12.055,12.568 C12.037,12.607 12.037,12.707 12.001,12.731 C11.852,12.829 11.879,12.435 11.707,12.526 C11.600,12.583 11.572,12.751 11.439,12.767 C11.370,12.776 11.343,12.705 11.334,12.652 C11.297,12.652 11.253,12.649 11.219,12.630 C11.140,12.592 11.192,12.567 11.210,12.512 C11.237,12.425 11.327,12.435 11.392,12.390 C11.475,12.332 11.396,12.281 11.397,12.211 C11.336,12.204 11.286,12.111 11.252,12.069 C11.183,11.989 11.117,11.987 11.018,11.985 C10.975,11.985 10.941,11.956 10.905,11.936 C10.892,11.985 10.712,11.934 10.651,11.965 C10.596,11.995 10.518,12.162 10.603,12.180 C10.572,12.250 10.491,12.328 10.502,12.405 C10.506,12.430 10.551,12.415 10.515,12.454 C10.486,12.483 10.480,12.526 10.473,12.565 C10.443,12.740 10.491,12.900 10.538,13.064 C10.459,13.084 10.383,13.122 10.310,13.162 C10.367,13.091 10.302,12.973 10.204,13.004 C10.204,13.039 10.191,13.071 10.177,13.100 C10.119,13.113 10.080,13.111 10.053,13.062 C10.015,12.993 10.056,13.013 10.087,12.960 C10.150,12.851 10.011,12.709 9.956,12.836 C9.952,12.725 9.761,12.548 9.729,12.762 C9.720,12.829 9.758,12.958 9.628,12.915 C9.571,12.896 9.510,12.771 9.499,12.716 C9.490,12.820 9.603,12.873 9.585,12.960 C9.576,13.009 9.481,13.090 9.441,13.126 C9.508,13.124 9.580,13.120 9.645,13.130 C9.643,13.179 9.601,13.206 9.601,13.268 C9.589,13.270 9.576,13.274 9.565,13.275 C9.610,13.332 9.574,13.414 9.558,13.476 C9.537,13.472 9.502,13.485 9.481,13.483 C9.477,13.518 9.463,13.538 9.434,13.529 C9.438,13.540 9.436,13.549 9.431,13.558 C9.420,13.567 9.407,13.567 9.396,13.560 C9.396,13.543 9.386,13.518 9.387,13.501 C9.334,13.552 9.305,13.470 9.245,13.481 C9.236,13.567 9.244,13.651 9.125,13.625 C9.078,13.616 8.979,13.507 8.934,13.520 C8.915,13.525 8.902,13.552 8.893,13.583 C8.871,13.574 8.846,13.569 8.818,13.567 C8.728,13.560 8.663,13.642 8.555,13.629 C8.435,13.614 8.418,13.629 8.320,13.698 C8.233,13.760 8.172,13.685 8.086,13.669 C8.028,13.658 7.971,13.656 7.915,13.640 C7.892,13.633 7.863,13.620 7.849,13.600 C7.822,13.560 7.726,13.569 7.676,13.563 C7.466,13.545 7.257,13.549 7.070,13.658 C6.928,13.742 6.752,13.875 6.723,14.046 C6.676,14.327 7.052,14.356 7.218,14.476 C7.431,14.629 6.987,14.759 6.881,14.797 C6.756,14.841 6.632,14.901 6.536,14.995 C6.461,15.068 6.412,15.243 6.277,15.185 C6.087,15.101 5.939,14.870 5.832,14.704 C5.745,14.664 5.666,14.383 5.655,14.294 C5.648,14.239 5.549,14.061 5.627,14.046 C5.643,14.001 5.601,13.953 5.583,13.917 C5.549,13.846 5.571,13.762 5.540,13.687 C5.508,13.611 5.630,13.479 5.614,13.385 C5.607,13.345 5.574,13.326 5.609,13.286 C5.652,13.233 5.659,13.195 5.670,13.130 C5.690,13.022 5.817,13.031 5.846,12.936 C5.855,12.909 5.857,12.856 5.893,12.847 C5.965,12.829 5.884,12.953 5.921,12.982 C5.923,12.884 5.956,12.789 5.956,12.687 C5.956,12.567 5.977,12.448 5.995,12.330 C6.008,12.239 6.024,12.146 6.065,12.064 C6.094,12.011 6.099,11.832 5.990,11.918 C5.986,11.721 5.988,11.524 6.024,11.331 C6.065,11.107 6.164,10.910 6.267,10.710 C6.466,10.324 6.626,9.908 6.869,9.546 C6.987,9.369 7.126,9.207 7.246,9.030 C7.309,8.934 7.378,8.841 7.426,8.737 C7.471,8.640 7.548,8.585 7.620,8.507 C7.750,8.369 7.854,8.237 8.009,8.119 C8.075,8.068 8.149,7.973 8.242,7.995 C8.345,8.017 8.375,7.937 8.449,7.889 C8.447,7.953 8.460,8.001 8.451,8.066 C8.508,8.004 8.546,7.917 8.598,7.849 C8.672,7.758 8.756,7.676 8.857,7.612 C8.895,7.587 8.920,7.547 8.960,7.523 C8.990,7.507 9.026,7.509 9.057,7.492 C9.093,7.470 9.316,7.357 9.283,7.319 C9.157,7.168 8.710,7.729 8.578,7.704 C8.573,7.633 8.758,7.538 8.809,7.503 C8.956,7.405 9.096,7.277 9.236,7.168 C9.384,7.053 9.533,6.931 9.691,6.829 C9.765,6.783 9.846,6.751 9.914,6.698 C9.963,6.661 10.017,6.589 10.089,6.598 C10.081,6.747 10.202,6.612 10.259,6.598 C10.400,6.563 10.547,6.463 10.678,6.404 C10.761,6.366 11.435,5.923 11.466,6.046 C11.529,6.053 11.561,5.965 11.640,5.978 C11.743,5.993 11.807,5.947 11.890,5.894 C12.027,5.805 12.172,5.716 12.312,5.632 C12.366,5.601 12.415,5.594 12.406,5.672 C12.400,5.719 12.303,5.739 12.269,5.759 C12.206,5.798 12.160,5.854 12.122,5.916 C12.064,6.007 11.985,6.080 11.920,6.168 C11.985,6.182 12.016,6.128 12.071,6.109 C12.140,6.089 12.154,6.142 12.224,6.098 C12.257,6.080 12.289,6.104 12.316,6.115 C12.356,6.131 12.365,6.106 12.397,6.093 C12.444,6.075 12.499,6.169 12.562,6.171 C12.758,6.171 12.357,6.397 12.553,6.426 C12.636,6.437 12.631,6.497 12.708,6.525 ZM12.925,6.519 C12.893,6.521 12.868,6.543 12.836,6.550 C12.827,6.552 12.814,6.552 12.803,6.552 C12.810,6.543 12.819,6.536 12.827,6.532 C12.857,6.519 12.893,6.517 12.925,6.519 ZM7.191,20.631 C7.273,20.622 7.280,20.739 7.342,20.770 C7.342,20.762 7.343,20.759 7.345,20.753 C7.390,20.788 7.412,20.824 7.421,20.882 C7.430,20.954 7.448,20.992 7.473,21.057 C7.491,21.103 7.494,21.205 7.426,21.221 C7.365,21.234 7.349,21.156 7.307,21.128 C7.200,21.057 7.047,21.183 7.002,20.999 C6.984,20.928 6.993,20.881 6.943,20.819 C6.903,20.770 6.860,20.739 6.896,20.671 C6.950,20.571 7.122,20.522 7.191,20.631 ZM7.360,20.726 C7.356,20.737 7.351,20.744 7.345,20.753 C7.340,20.748 7.334,20.744 7.329,20.740 L7.360,20.726 ZM7.298,20.467 C7.237,20.411 7.275,20.221 7.370,20.301 C7.410,20.332 7.426,20.425 7.405,20.462 C7.378,20.507 7.322,20.527 7.298,20.467 ZM7.521,14.913 C7.548,14.895 7.548,14.870 7.565,14.848 C7.588,14.815 7.606,14.817 7.636,14.830 C7.647,14.835 7.660,14.848 7.676,14.842 C7.699,14.833 7.678,14.824 7.694,14.819 C7.751,14.766 7.748,14.850 7.746,14.893 C7.748,14.943 7.768,14.928 7.795,14.952 C7.818,14.970 7.798,14.984 7.805,15.008 C7.814,15.030 7.843,15.037 7.861,15.065 C7.881,15.090 7.870,15.112 7.856,15.141 C7.805,15.218 7.705,15.178 7.640,15.141 C7.611,15.134 7.574,15.105 7.550,15.088 C7.527,15.072 7.496,15.057 7.473,15.041 C7.444,15.034 7.403,15.014 7.410,14.986 C7.412,14.968 7.458,14.935 7.475,14.930 L7.521,14.913 ZM16.778,18.530 C16.859,18.576 16.931,18.607 17.024,18.572 C17.107,18.539 17.164,18.488 17.260,18.497 C17.373,18.507 17.386,18.572 17.425,18.658 C17.486,18.789 17.698,18.758 17.670,18.940 C17.520,19.022 17.664,19.144 17.704,19.248 C17.724,19.301 17.720,19.359 17.722,19.416 C17.722,19.458 17.738,19.529 17.704,19.561 C17.673,19.591 17.603,19.591 17.564,19.600 C17.510,19.614 17.458,19.642 17.402,19.649 C17.305,19.664 17.229,19.600 17.141,19.667 C17.107,19.693 17.082,19.725 17.042,19.746 C16.997,19.767 16.942,19.775 16.893,19.786 C16.846,19.797 16.791,19.815 16.744,19.795 C16.706,19.778 16.688,19.736 16.652,19.716 C16.564,19.665 16.427,19.756 16.341,19.784 C16.273,19.806 16.147,19.875 16.075,19.840 C15.971,19.789 16.116,19.654 16.145,19.600 C16.190,19.510 16.170,19.410 16.192,19.315 C16.154,19.312 16.149,19.283 16.118,19.270 C16.107,19.264 16.080,19.266 16.068,19.263 C16.053,19.261 16.039,19.252 16.025,19.248 C16.019,19.292 15.949,19.334 15.951,19.272 C15.931,19.266 15.915,19.261 15.897,19.255 C15.899,19.219 15.965,19.099 16.010,19.100 L15.998,19.090 C16.111,18.986 16.235,18.865 16.391,18.833 C16.463,18.816 16.551,18.791 16.602,18.732 C16.659,18.667 16.589,18.596 16.607,18.523 C16.627,18.439 16.729,18.503 16.778,18.530 ZM17.698,17.537 C17.704,17.564 17.756,17.581 17.754,17.605 C17.754,17.616 17.691,17.621 17.680,17.621 C17.646,17.632 17.610,17.632 17.582,17.648 C17.560,17.659 17.542,17.676 17.519,17.687 C17.432,17.725 17.497,17.597 17.497,17.552 C17.502,17.512 17.465,17.406 17.504,17.366 L17.499,17.339 C17.499,17.315 17.488,17.293 17.506,17.266 C17.511,17.247 17.546,17.237 17.564,17.220 C17.587,17.209 17.610,17.176 17.632,17.198 C17.644,17.209 17.650,17.227 17.650,17.238 C17.666,17.255 17.684,17.273 17.695,17.295 C17.700,17.322 17.700,17.357 17.688,17.379 C17.682,17.419 17.693,17.430 17.698,17.468 C17.698,17.492 17.688,17.513 17.698,17.537 ZM18.757,18.490 C18.745,18.519 18.682,18.516 18.649,18.510 C18.631,18.510 18.613,18.510 18.595,18.494 C18.576,18.476 18.590,18.446 18.561,18.452 C18.536,18.448 18.522,18.477 18.493,18.481 C18.482,18.485 18.461,18.474 18.446,18.485 C18.376,18.497 18.414,18.514 18.407,18.563 C18.405,18.607 18.355,18.636 18.369,18.678 C18.378,18.718 18.407,18.767 18.425,18.802 C18.486,18.955 18.277,18.829 18.232,18.791 C18.186,18.760 18.141,18.740 18.133,18.683 C18.117,18.640 18.121,18.598 18.115,18.552 C18.107,18.441 18.141,18.333 18.151,18.224 C18.153,18.164 18.139,18.120 18.130,18.064 C18.115,18.022 18.076,17.982 18.119,17.949 C18.146,17.891 18.130,17.861 18.115,17.801 C18.099,17.759 18.092,17.738 18.087,17.692 C18.096,17.581 18.015,17.508 17.988,17.397 C17.977,17.348 18.000,17.298 18.060,17.326 L18.103,17.346 C18.119,17.371 18.115,17.395 18.141,17.399 C18.177,17.415 18.263,17.349 18.286,17.397 C18.293,17.417 18.272,17.424 18.279,17.446 C18.288,17.468 18.310,17.477 18.324,17.484 C18.367,17.504 18.405,17.523 18.416,17.572 C18.416,17.590 18.409,17.603 18.425,17.628 C18.443,17.646 18.461,17.645 18.479,17.645 C18.531,17.652 18.588,17.643 18.610,17.688 C18.648,17.741 18.569,17.739 18.565,17.783 C18.561,17.825 18.603,17.852 18.624,17.882 C18.648,17.909 18.680,17.951 18.689,17.989 C18.700,18.040 18.664,18.058 18.666,18.111 C18.658,18.160 18.736,18.188 18.714,18.250 C18.700,18.295 18.658,18.346 18.676,18.399 C18.696,18.435 18.772,18.445 18.757,18.490 ZM7.507,20.618 C7.588,20.596 7.665,20.660 7.744,20.642 C7.827,20.624 7.852,20.527 7.859,20.458 C7.892,20.498 7.935,20.489 7.964,20.527 C7.994,20.573 8.061,20.580 8.106,20.602 C8.125,20.545 8.077,20.452 8.079,20.391 C8.079,20.356 8.045,20.288 8.055,20.263 C8.079,20.205 8.188,20.155 8.244,20.137 C8.311,20.115 8.390,20.165 8.449,20.190 C8.508,20.216 8.573,20.201 8.634,20.188 C8.665,20.181 8.697,20.177 8.729,20.176 C8.737,20.148 8.751,20.123 8.773,20.103 C8.819,20.055 8.873,20.075 8.924,20.101 C9.021,20.146 9.100,19.953 9.118,19.873 C9.141,19.771 9.152,19.580 9.123,19.478 C9.091,19.359 9.062,19.325 9.172,19.266 C9.195,19.255 9.202,19.228 9.213,19.203 C9.121,19.037 9.073,18.856 9.166,18.701 C9.242,18.574 9.409,18.461 9.359,18.322 C9.443,18.330 9.529,18.337 9.616,18.344 C9.751,18.169 9.950,18.047 10.166,18.011 C10.292,17.871 10.493,17.721 10.644,17.834 C10.693,17.871 10.723,17.929 10.779,17.956 C10.833,17.982 10.896,17.971 10.955,17.969 C11.164,17.964 11.370,18.100 11.448,18.297 C11.397,18.359 11.349,18.421 11.298,18.483 C11.360,18.711 11.712,18.793 11.849,18.601 C11.940,18.476 11.946,18.270 12.089,18.219 C12.161,18.193 12.244,18.219 12.323,18.246 C12.325,18.244 12.325,18.244 12.327,18.242 C12.329,18.242 12.329,18.248 12.329,18.248 C12.395,18.270 12.462,18.292 12.516,18.290 C12.530,18.266 12.532,18.257 12.548,18.230 C12.546,18.231 12.564,18.255 12.568,18.264 C12.586,18.255 12.605,18.257 12.618,18.237 C12.672,18.162 12.627,18.051 12.661,17.965 C12.694,17.885 12.782,17.887 12.857,17.920 C12.909,17.814 12.960,17.712 13.028,17.566 C13.062,17.492 13.033,17.869 12.981,18.242 C12.987,18.246 12.983,18.255 12.990,18.259 C13.031,18.271 13.073,18.237 13.102,18.202 C13.170,18.118 13.220,18.016 13.307,17.949 C13.364,17.903 13.440,17.896 13.508,17.913 C13.538,17.865 13.571,17.809 13.587,17.805 C13.682,17.783 13.756,17.745 13.853,17.781 C13.936,17.814 14.004,17.880 14.090,17.902 C14.148,17.916 14.256,17.914 14.286,17.976 C14.310,18.027 14.351,18.027 14.412,18.044 C14.437,18.051 14.489,18.053 14.491,18.089 C14.488,18.109 14.477,18.113 14.457,18.100 C14.464,18.155 14.534,18.153 14.574,18.169 C14.648,18.200 14.709,18.250 14.772,18.295 C14.894,18.384 14.978,18.514 15.070,18.632 C15.093,18.663 15.196,18.783 15.153,18.825 C15.126,18.853 15.068,18.847 15.034,18.864 C14.978,18.889 14.898,18.920 14.849,18.955 C14.804,18.986 14.784,19.039 14.739,19.066 C14.617,19.139 14.518,18.955 14.452,18.900 C14.362,18.825 14.400,18.691 14.328,18.630 C14.213,18.530 14.056,18.485 13.921,18.419 C13.848,18.383 13.767,18.355 13.704,18.301 C13.661,18.262 13.619,18.219 13.560,18.206 C13.560,18.206 13.558,18.208 13.558,18.210 C13.528,18.322 13.670,18.374 13.742,18.426 C13.830,18.490 13.851,18.587 13.894,18.678 C13.945,18.783 14.027,18.871 14.103,18.958 C14.137,18.995 14.182,19.022 14.205,19.068 C14.227,19.111 14.207,19.161 14.238,19.201 C14.090,19.259 13.963,18.947 13.878,18.876 C13.844,18.845 13.774,18.809 13.727,18.827 C13.700,18.880 13.653,18.949 13.691,18.991 C13.722,19.024 13.718,19.075 13.734,19.115 C13.752,19.161 13.790,19.210 13.815,19.252 C13.846,19.299 13.900,19.319 13.930,19.367 C13.950,19.399 13.959,19.436 13.975,19.469 C14.002,19.525 14.080,19.622 14.153,19.580 C14.234,19.534 14.204,19.168 14.322,19.332 C14.383,19.418 14.403,19.857 14.581,19.762 C14.685,19.707 14.662,19.643 14.791,19.694 C14.853,19.716 14.926,19.605 14.953,19.713 C14.977,19.797 14.995,19.778 15.068,19.818 C15.126,19.849 15.122,19.926 15.122,19.981 C15.122,20.048 15.126,20.128 15.209,20.143 C15.282,20.154 15.264,20.239 15.275,20.290 C15.324,20.513 15.636,20.316 15.680,20.474 C15.690,20.516 15.696,20.558 15.742,20.576 C15.769,20.587 15.805,20.589 15.822,20.615 C15.872,20.684 15.867,20.724 15.971,20.748 C16.100,20.779 16.305,20.742 16.402,20.647 C16.445,20.607 16.462,20.511 16.517,20.489 C16.582,20.462 16.658,20.569 16.672,20.615 C16.701,20.697 16.704,20.766 16.782,20.821 C16.859,20.873 16.929,20.924 16.970,21.010 C17.069,21.214 16.835,21.365 16.915,21.558 C16.933,21.602 16.965,21.640 16.999,21.671 C16.992,21.691 16.976,21.733 16.997,21.752 C17.049,21.797 17.112,21.695 17.148,21.757 C17.247,21.936 17.013,22.016 16.902,22.085 C16.780,22.162 16.834,22.357 16.848,22.471 C16.943,22.466 17.021,22.378 17.121,22.377 C17.267,22.377 17.152,22.579 17.100,22.623 C16.986,22.715 16.789,22.739 16.654,22.675 C16.613,22.657 16.535,22.604 16.507,22.670 C16.478,22.734 16.417,22.792 16.462,22.865 C16.537,22.992 16.747,22.945 16.780,23.118 C16.801,23.237 16.622,23.313 16.749,23.391 C16.848,23.452 16.888,23.532 16.951,23.621 C17.028,23.732 17.170,23.521 17.220,23.656 C17.197,23.574 17.307,23.599 17.351,23.590 C17.414,23.579 17.463,23.523 17.481,23.464 C17.515,23.357 17.380,23.306 17.310,23.257 C17.251,23.215 17.262,23.186 17.310,23.140 C17.278,23.058 17.195,23.023 17.147,22.954 C17.085,22.865 17.127,22.721 17.217,22.664 C17.359,22.573 17.493,22.787 17.627,22.812 C17.778,22.841 17.946,22.805 18.071,22.914 C18.184,23.011 18.241,23.149 18.382,23.220 C18.443,23.251 18.506,23.268 18.518,23.344 C18.531,23.430 18.619,23.393 18.676,23.450 C18.714,23.486 18.718,23.543 18.777,23.557 C18.676,23.681 18.563,23.796 18.435,23.894 C18.329,23.976 18.211,24.051 18.144,24.169 C18.008,24.412 18.117,24.716 17.950,24.949 C17.808,25.150 17.592,25.250 17.520,25.498 C17.454,25.725 17.395,25.931 17.161,26.044 C16.893,26.172 16.582,26.210 16.289,26.239 C16.012,26.265 15.724,26.268 15.485,26.429 C15.262,26.578 15.043,26.706 14.764,26.706 C14.698,26.706 14.633,26.702 14.567,26.697 C14.511,26.739 14.475,26.799 14.421,26.842 C14.313,26.930 14.187,26.821 14.085,26.782 C14.101,26.815 14.105,26.848 14.096,26.883 C14.044,26.862 13.988,26.912 13.939,26.873 C13.896,26.842 13.871,26.790 13.813,26.779 C13.679,26.755 13.596,26.819 13.465,26.733 C13.274,26.609 13.078,26.627 12.855,26.598 C12.625,26.567 12.397,26.516 12.172,26.451 C11.769,26.336 11.334,26.207 10.968,26.001 C10.797,25.906 10.621,25.778 10.560,25.587 C10.534,25.512 10.556,25.416 10.529,25.345 C10.511,25.299 10.315,25.279 10.267,25.263 C10.162,25.228 9.925,25.244 9.972,25.088 C10.004,24.977 10.148,25.017 10.234,25.009 C10.403,24.995 10.387,24.822 10.285,24.732 C10.193,24.650 9.992,24.616 10.000,24.472 C10.011,24.326 10.107,24.022 9.911,23.964 C9.763,23.920 9.621,23.900 9.569,23.750 C9.542,23.670 9.472,23.616 9.441,23.532 C9.409,23.450 9.316,23.371 9.235,23.339 C9.179,23.315 9.024,23.342 9.003,23.278 C8.987,23.229 8.970,23.165 8.972,23.115 C8.983,22.940 9.218,23.211 9.285,23.211 C9.423,23.207 9.547,23.036 9.452,22.920 C9.494,22.900 9.569,22.909 9.589,22.858 C9.542,22.848 9.445,22.892 9.416,22.834 C9.387,22.777 9.449,22.699 9.400,22.648 C9.357,22.601 9.280,22.657 9.226,22.652 C9.161,22.646 9.265,22.577 9.262,22.553 C9.186,22.551 9.123,22.617 9.058,22.644 C8.999,22.672 8.936,22.688 8.877,22.715 C8.825,22.679 8.897,22.626 8.922,22.601 C8.978,22.548 8.943,22.490 8.922,22.431 C9.051,22.440 9.055,22.347 8.979,22.269 C8.880,22.169 8.925,22.096 8.880,21.981 C8.828,21.848 8.746,21.919 8.699,22.001 C8.611,22.152 8.525,21.963 8.483,22.003 C8.444,21.888 8.356,21.828 8.258,21.761 C8.170,21.702 8.158,21.597 8.097,21.518 C8.023,21.425 7.897,21.391 7.836,21.285 C7.780,21.189 7.755,21.076 7.680,20.990 C7.635,20.939 7.295,20.673 7.507,20.618 ZM8.787,22.989 C8.819,22.987 8.868,23.022 8.857,23.053 L8.843,23.067 C8.857,23.073 8.868,23.078 8.871,23.094 C8.819,23.100 8.827,23.167 8.807,23.202 C8.724,23.202 8.685,23.116 8.721,23.053 C8.731,23.031 8.758,22.991 8.787,22.989 ZM24.510,20.174 C24.490,20.126 24.542,20.112 24.566,20.075 C24.594,20.033 24.580,19.982 24.647,19.984 C24.675,19.986 24.679,19.993 24.704,19.984 C24.729,19.977 24.758,19.955 24.781,19.942 C24.796,20.002 24.814,20.039 24.843,20.090 C24.871,20.139 24.852,20.181 24.819,20.223 C24.796,20.250 24.773,20.281 24.737,20.292 C24.697,20.307 24.638,20.312 24.634,20.258 C24.631,20.258 24.625,20.258 24.621,20.256 C24.623,20.252 24.625,20.250 24.627,20.247 L24.618,20.258 C24.584,20.241 24.524,20.210 24.510,20.174 ZM24.501,21.961 C24.438,22.069 24.310,22.100 24.213,22.160 C24.127,22.216 24.075,22.304 23.990,22.360 C23.857,22.449 23.538,22.719 23.383,22.666 C23.368,22.568 23.539,22.466 23.609,22.418 C23.663,22.382 23.687,22.336 23.726,22.295 C23.785,22.236 23.856,22.205 23.897,22.121 C23.951,22.012 23.849,21.817 23.942,21.746 C24.014,21.693 24.048,21.761 24.122,21.739 C24.167,21.724 24.194,21.691 24.217,21.666 C24.287,21.562 24.233,21.373 24.104,21.345 C24.138,21.240 24.345,21.061 24.471,21.103 C24.460,21.148 24.426,21.207 24.449,21.263 C24.544,21.258 24.639,21.123 24.621,21.032 C24.612,20.992 24.566,20.934 24.526,20.915 C24.434,20.879 24.433,20.941 24.377,20.961 C24.260,21.005 24.127,20.992 24.003,21.048 C23.884,21.099 23.811,21.147 23.670,21.123 C23.660,21.107 23.660,21.090 23.670,21.076 C23.701,21.079 23.748,21.061 23.778,21.066 C23.785,20.997 23.917,20.955 23.978,20.924 C24.003,20.910 24.052,20.886 24.071,20.875 C24.096,20.859 24.138,20.821 24.161,20.808 C24.251,20.751 24.409,20.573 24.524,20.569 C24.560,20.667 24.616,20.740 24.733,20.722 C24.789,20.711 24.810,20.718 24.837,20.637 C24.855,20.584 24.859,20.533 24.821,20.487 C25.037,20.498 24.897,20.321 25.028,20.232 C25.096,20.288 25.143,20.212 25.217,20.234 C25.220,20.250 25.224,20.265 25.227,20.281 C25.247,20.272 25.287,20.278 25.308,20.259 C25.398,20.421 25.186,20.764 25.105,20.908 C25.053,20.999 24.979,21.074 24.942,21.170 C24.898,21.281 24.920,21.413 24.826,21.496 C24.751,21.562 24.690,21.588 24.623,21.668 C24.587,21.710 24.550,21.728 24.530,21.783 C24.508,21.845 24.537,21.899 24.501,21.961 Z"/>
+</svg>
diff --git a/browser/branding/unofficial/content/jar.mn b/browser/branding/unofficial/content/jar.mn
new file mode 100644
index 000000000..140359a19
--- /dev/null
+++ b/browser/branding/unofficial/content/jar.mn
@@ -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/.
+
+browser.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png
+ content/branding/about-background.png
+ content/branding/about-logo.png
+ content/branding/about-logo@2x.png
+ content/branding/about-wordmark.svg
+ content/branding/icon48.png
+ content/branding/icon64.png
+ content/branding/icon16.png (../default16.png)
+ content/branding/icon32.png (../default32.png)
+ content/branding/icon128.png (../mozicon128.png)
+ content/branding/identity-icons-brand.svg
+ content/branding/silhouette-40.svg
+ content/branding/aboutDialog.css
diff --git a/browser/branding/unofficial/content/moz.build b/browser/branding/unofficial/content/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/branding/unofficial/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/unofficial/content/silhouette-40.svg b/browser/branding/unofficial/content/silhouette-40.svg
new file mode 100644
index 000000000..bef723b59
--- /dev/null
+++ b/browser/branding/unofficial/content/silhouette-40.svg
@@ -0,0 +1,1360 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="-45 31 40 40">
+ <path fill="#ccc" d="M-25,62.991c-6.622,0-11.991-5.369-11.991-11.991S-31.622,39.009-25,39.009S-13.009,44.378-13.009,51
+ S-18.378,62.991-25,62.991z M-34.439,48.549c-0.002,0.007-0.004,0.013-0.006,0.02c0.002-0.004,0.006-0.006,0.007-0.01
+ C-34.437,48.555-34.438,48.552-34.439,48.549z M-34.259,47.956c-0.001-0.006-0.003-0.005-0.002-0.012l0,0
+ c-0.001,0.004-0.003,0.008-0.004,0.012l0.008,0.006C-34.258,47.96-34.258,47.958-34.259,47.956z M-34.245,48.067
+ c-0.02,0.006-0.04,0.012-0.06,0.019c-0.026,0.084-0.054,0.168-0.078,0.254c0.004-0.002,0.007-0.004,0.01-0.008
+ c0.007-0.006,0.013-0.014,0.019-0.021c0.008-0.007,0.009-0.014,0.011-0.023c0.003-0.009,0.003-0.01,0.008-0.017
+ c0.01-0.012,0.006-0.037,0.005-0.051c0-0.011-0.007-0.015,0-0.024c0.005-0.007,0.012-0.014,0.017-0.019
+ c0.011-0.01,0.026-0.015,0.039-0.022c0.007-0.003,0.013-0.008,0.017-0.014c0.003-0.005,0.004-0.011,0.007-0.017
+ c0.007-0.014,0.008-0.027,0.008-0.044C-34.242,48.074-34.243,48.071-34.245,48.067z M-16.736,45.824
+ c0.001,0.012,0.002,0.024,0.007,0.036c0.006,0.017,0.012,0.034,0.011,0.054c0,0.017,0.007,0.032,0.011,0.049
+ c0.004,0.024,0.013,0.043,0.024,0.064c0.004,0.009,0.009,0.017,0.013,0.026c0.008,0.012,0.014,0.025,0.021,0.038
+ c0.009,0.017,0.014,0.034,0.022,0.051c0.004,0.008,0.006,0.016,0.008,0.024c0.002,0.005,0.003,0.013,0.007,0.017
+ c-0.02-0.021-0.027-0.053-0.042-0.077c-0.013-0.021-0.029-0.04-0.041-0.061c-0.009-0.016-0.021-0.028-0.023-0.045
+ c-0.002-0.015-0.003-0.029-0.007-0.042c-0.003-0.007-0.004-0.012-0.009-0.019c-0.004-0.007-0.011-0.011-0.015-0.018
+ c-0.01-0.015-0.018-0.034-0.025-0.051c-0.01-0.022-0.015-0.05-0.037-0.064c0.004,0.004,0.008,0.014,0.009,0.02
+ c0.002,0.007,0.005,0.015,0.005,0.022c0,0.013-0.007,0.013-0.014,0.004c-0.005-0.006-0.007-0.015-0.01-0.023
+ c-0.001-0.003-0.005-0.009-0.005-0.012c-0.001-0.004,0-0.007-0.001-0.01c-0.001-0.006-0.006-0.013-0.009-0.019
+ c-0.004-0.01-0.007-0.018-0.014-0.027c-0.006-0.008-0.012-0.014-0.016-0.025c-0.005-0.014-0.007-0.028-0.017-0.04
+ s-0.018-0.001-0.028,0.005c-0.006,0.004-0.014,0.005-0.022,0.008c-0.008,0.002-0.015,0.005-0.023,0.007
+ c-0.006,0.002-0.012,0.003-0.018,0.005c-0.003,0.001-0.004,0.002-0.008,0.003c-0.004,0-0.008-0.001-0.012,0
+ c-0.01,0.001-0.018,0.005-0.029,0.003c-0.003-0.001-0.005-0.003-0.008-0.003c-0.004-0.001-0.008-0.001-0.012,0
+ c-0.02,0.001-0.016,0.025-0.012,0.039c0.004,0.017,0.013,0.032,0.021,0.047c0.001,0.004,0.004,0.008,0.006,0.012
+ c0.001,0.004-0.001,0.007,0,0.011c0.002,0.009,0.006,0.017,0.004,0.027c-0.001,0.009-0.003,0.012-0.01,0.016
+ c-0.008,0.004-0.01,0.004-0.006,0.014c0.003,0.006,0.008,0.013,0.009,0.019c0.001,0.007,0.001,0.015,0.003,0.022
+ c0.004,0.017,0.012,0.034,0.02,0.049c0.005,0.008,0.014,0.031-0.004,0.019c-0.012-0.007-0.019-0.022-0.026-0.033
+ c-0.004-0.007-0.009-0.011-0.016-0.015c-0.006-0.003-0.013-0.005-0.019-0.009c-0.011-0.007-0.011-0.014-0.011-0.026
+ c0-0.007,0-0.015-0.008-0.019c-0.006-0.003-0.015-0.003-0.023-0.006c-0.005-0.002-0.011-0.004-0.015-0.007
+ c0.009,0.008,0.012,0.018,0.015,0.028c0.007,0.02,0.016,0.038,0.027,0.056c0.009,0.016,0.02,0.022,0.034,0.032
+ c0.011,0.008,0.022,0.024,0.029,0.035c0.008,0.01,0.015,0.019,0.024,0.028c0.009,0.009,0.019,0.015,0.025,0.027
+ c0.008,0.014,0.01,0.027,0.013,0.042c0.004,0.013,0.003,0.027,0.003,0.041c0,0.009,0.001,0.018-0.001,0.026
+ c0,0.003-0.002,0.005-0.002,0.008c-0.001,0.004,0,0.008,0,0.012c0,0.013,0,0.027,0,0.041c0,0.027,0.021,0.046,0.029,0.069
+ c0.008,0.023,0.006,0.053,0.005,0.079c-0.001,0.004-0.002,0.006-0.003,0.011c-0.002,0.005,0,0.009-0.001,0.014
+ c-0.001,0.005-0.003,0.008-0.004,0.012c0,0.003,0,0.006,0,0.008c0,0.004-0.003,0.004-0.004,0.007c-0.001,0.003,0,0.008,0,0.011
+ c-0.001,0.004-0.003,0.002-0.003,0.008c0.002,0.003,0.003,0.005,0.003,0.008c0.005,0.013,0.015,0.032,0.027,0.041
+ c0.005,0.003,0.012,0.004,0.019,0.004s0.013,0.005,0.018,0.012c0,0.002,0.001,0.004,0.001,0.006
+ c0.003,0.002,0.005,0.005,0.006,0.008c0.002,0.004,0.003,0.011,0.004,0.015c0.004,0.017,0.01,0.029,0.019,0.042
+ c0.011,0.015,0.021,0.018,0.039,0.019c0.016,0.001,0.029,0.009,0.042,0.02c0.006,0.005,0.011,0.013,0.018,0.018
+ c0.005,0.003,0.013,0.004,0.018,0.007c0.009,0.004,0.018,0.006,0.028,0.009c0.008,0.003,0.017,0.005,0.026,0.007
+ c0.007,0.001,0.015-0.001,0.023,0.001c0.007,0.002,0.014,0.006,0.022,0.006c0.005,0.001,0.018-0.002,0.022,0.001
+ c0.006,0.004,0.02,0.036,0.03,0.029c0.003-0.002,0.001-0.019,0.001-0.022c0-0.009,0.004-0.018,0.004-0.027
+ c0-0.005-0.001-0.01,0.001-0.015c0.001-0.004,0.003-0.006,0.003-0.011c-0.001-0.007-0.005-0.017-0.008-0.023
+ c-0.012-0.023,0.022-0.001,0.018-0.021c0-0.003-0.006-0.009-0.008-0.012c-0.002-0.005-0.003-0.001-0.002-0.009
+ c0.002-0.009,0.003-0.016,0.003-0.026c0-0.009,0-0.017-0.003-0.026c-0.003-0.007-0.009-0.013-0.011-0.02
+ c-0.004-0.011,0.006-0.019,0-0.029c-0.003-0.007-0.008-0.01-0.011-0.015c-0.004-0.006-0.004-0.016-0.005-0.023
+ c0-0.008-0.005-0.014-0.004-0.021c0.003,0.003,0.005,0.007,0.008,0.01c0.002,0.001,0.004,0.003,0.007,0.004
+ c0,0.002,0.001,0.004,0.001,0.006c0.003,0.005,0.011,0.019,0.019,0.016c0.011-0.002-0.009-0.032-0.013-0.037
+ c-0.008-0.01-0.017-0.018-0.023-0.03c-0.006-0.014-0.009-0.028-0.013-0.042c0.007,0.002,0.015,0.019,0.019,0.026
+ c0.005,0.007,0.012,0.012,0.017,0.02c0.009,0.013,0.017,0.029,0.023,0.044c0.002,0.006,0.001,0.013,0.005,0.019
+ c0.004,0.005,0.009,0.011,0.014,0.015c0.006,0.006,0.015,0.012,0.019,0.02c0.003,0.006,0.004,0.015,0.004,0.022
+ c0,0.005,0,0.01,0,0.015c0.001,0.004,0.002,0.005,0.003,0.008c0.005,0.018,0.017,0.031,0.025,0.049
+ c0.004,0.009,0.006,0.02,0.009,0.03c0.003,0.008,0.006,0.014,0.01,0.021c0.007,0.016,0.017,0.032,0.026,0.047
+ c0.005,0.008,0.007,0.017,0.01,0.026c0.003,0.008,0.008,0.017,0.008,0.025c-0.006-0.007-0.016-0.008-0.022-0.014
+ c-0.004-0.004-0.007-0.017-0.011-0.018c-0.007,0.022,0.013,0.046,0.022,0.064c0.006,0.013,0.012,0.028,0.017,0.041
+ c0.004,0.008,0.007,0.016,0.01,0.023c0.003,0.008,0.01,0.013,0.014,0.019c0.01,0.013,0.019,0.033,0.019,0.049
+ c0,0.015,0,0.03,0,0.045c0,0.015,0,0.033-0.008,0.045c-0.005,0.006-0.01,0.015-0.018,0.016c-0.006,0.001-0.017,0.001-0.023,0
+ s-0.009-0.004-0.015-0.004c-0.005,0-0.007,0.003-0.012,0.003c-0.006,0.001-0.01-0.003-0.018-0.003c-0.015,0-0.031,0-0.043,0.01
+ c-0.006,0.006-0.009,0.014-0.018,0.013c-0.004,0-0.004-0.003-0.007-0.004c-0.003-0.001-0.007,0-0.011-0.001
+ c-0.006-0.001-0.012-0.005-0.02-0.002c-0.008,0.003-0.01,0.016-0.016,0.021c-0.006,0.006-0.02,0.011-0.028,0.008
+ c-0.003-0.001-0.005-0.005-0.008-0.006c-0.004-0.002-0.008-0.001-0.012-0.001s-0.01-0.001-0.015,0
+ c-0.007,0.001-0.01,0.005-0.016,0.008c-0.009,0.004-0.016,0.003-0.026,0.003c-0.017,0-0.037,0.003-0.053-0.006
+ c-0.013-0.008-0.018-0.02-0.025-0.032c-0.007-0.015-0.009-0.032-0.023-0.042c-0.015-0.01-0.028-0.006-0.032,0.012
+ c-0.005-0.001-0.017-0.015-0.021-0.019c-0.005-0.009-0.007-0.02-0.008-0.03c0-0.007-0.003-0.012-0.004-0.02
+ c-0.001-0.009,0.003-0.013,0.004-0.021c0.002-0.012-0.006-0.022-0.004-0.035c0.001-0.008,0.004-0.013,0.004-0.022
+ c0-0.008-0.001-0.012-0.005-0.018c-0.006-0.012-0.012-0.023-0.017-0.035c-0.005-0.015-0.011-0.026-0.024-0.037
+ c-0.008-0.008-0.016-0.017-0.026-0.023c-0.007-0.005-0.014-0.008-0.02-0.015c-0.007-0.008-0.005-0.01-0.003-0.019
+ c0.002-0.011-0.008-0.014-0.012-0.022c-0.003-0.007,0-0.008,0.001-0.016c0.003-0.018-0.018-0.021-0.03-0.027
+ c-0.01-0.004-0.024-0.005-0.033-0.012c-0.006-0.004-0.01-0.011-0.016-0.014c0.001,0-0.009-0.003-0.009-0.003
+ c-0.003-0.001-0.006-0.001-0.01-0.002c-0.008-0.002-0.015-0.007-0.023-0.009c-0.008-0.003-0.014-0.004-0.022-0.005
+ c-0.006-0.001-0.01-0.005-0.015-0.007c-0.005-0.001-0.01-0.002-0.015-0.003c-0.008-0.003-0.015-0.007-0.023-0.009
+ c-0.008-0.002-0.015-0.005-0.023-0.007c-0.005-0.001-0.01,0-0.015,0c-0.005-0.001-0.008-0.002-0.012-0.003
+ c-0.021-0.005-0.042-0.003-0.063-0.005h-0.001c-0.006,0.002-0.01,0.003-0.016,0.004c-0.005,0-0.013-0.002-0.018,0
+ c-0.006,0.001-0.007,0.004-0.015,0.004c-0.005,0-0.01-0.001-0.015,0c-0.01,0-0.021,0.006-0.03,0.011
+ c-0.008,0.004-0.016,0.007-0.023,0.013c-0.004,0.004-0.01,0.011-0.011,0.017c-0.001,0.005,0,0.01-0.001,0.015
+ c-0.002,0.009-0.005,0.018-0.007,0.027c-0.001,0.005-0.002,0.018-0.008,0.022c-0.008,0.005-0.011-0.008-0.019,0
+ c-0.005,0.006-0.007,0.016-0.007,0.023c0.001,0.01,0.006,0.016,0.003,0.026c-0.002,0.008-0.001,0.015-0.004,0.023
+ c-0.002,0.008-0.004,0.014-0.002,0.023c0.002,0.007,0.002,0.016,0.006,0.023c0.005,0.009,0.015,0.018,0.022,0.027
+ c0.006,0.008,0.011,0.016,0.016,0.025c0.004,0.008,0.007,0.012,0.002,0.02c-0.004,0.007-0.013,0.008-0.02,0.01
+ c-0.009,0.002-0.014-0.001-0.022,0.005c-0.005,0.005-0.008,0.01-0.016,0.011c-0.007,0.001-0.013-0.001-0.019-0.004
+ c-0.004-0.003-0.012-0.01-0.016-0.011c-0.006-0.001-0.008,0.003-0.014,0.004c-0.004,0-0.008-0.001-0.011-0.001
+ c-0.01,0.001-0.017,0.008-0.023,0.016c-0.006,0.006-0.009,0.01-0.007,0.018c0.002,0.008,0.006,0.014,0.01,0.02
+ c0,0.001,0,0.001,0.001,0.002c0.01,0.006,0.019,0.013,0.028,0.02c0.026,0.02,0.046,0.051,0.068,0.076
+ c0.01,0.012,0.019,0.026,0.029,0.039c0.014,0.017,0.033,0.025,0.049,0.039c0.009,0.008,0.018,0.018,0.027,0.025
+ c0.011,0.008,0.028,0.01,0.039,0.018c0.012,0.009,0.02,0.013,0.036,0.018c0.013,0.004,0.025,0.011,0.036,0.019
+ c0.008,0.006,0.023,0.021,0.026,0.031c0.002,0.007,0.002,0.012,0.005,0.019c0.005,0.009,0.014,0.017,0.024,0.023
+ c0.015,0.009,0.027,0.016,0.036,0.033c0.006,0.014,0.01,0.029,0.012,0.045c0.012,0.002,0.02,0.009,0.032,0.006
+ c0.008-0.003,0.014-0.01,0.02-0.015c0.019-0.015,0.036-0.024,0.061-0.017c0.008,0.002,0.024,0.009,0.031,0.014
+ c0.009,0.007,0.011,0.02,0.017,0.03c0.004,0.009,0.007,0.019,0.013,0.028c0.009,0.013,0.021,0.024,0.033,0.035
+ c0.017,0.016,0.038,0.036,0.045,0.06c0.006,0.024-0.011,0.048-0.004,0.073c0.004,0.012,0.012,0.024,0.016,0.035
+ c0.005,0.01,0.007,0.019,0.012,0.029c0.005,0.011,0.005,0.021,0.007,0.033c0.002,0.009,0.011,0.019,0.011,0.03
+ c0.026,0,0.022-0.06,0.022-0.076c0-0.015,0-0.025-0.004-0.039s-0.009-0.019-0.017-0.03c-0.011-0.014-0.017-0.037-0.023-0.052
+ c-0.005-0.011-0.008-0.023-0.012-0.034c-0.005-0.012-0.011-0.022-0.017-0.034c-0.008-0.015-0.028-0.048-0.015-0.064
+ c0.016-0.019,0.042,0.004,0.053,0.017c0.012,0.015,0.024,0.024,0.034,0.041c0.007,0.014,0.014,0.026,0.02,0.04
+ c0.011,0.027,0.016,0.053,0.016,0.081c0,0.012,0.001,0.024,0,0.035c-0.001,0.015-0.006,0.021,0,0.036
+ c0.011,0.03,0.026,0.059,0.036,0.09c0.007,0.022,0.012,0.041,0.016,0.064c0.002,0.013,0.008,0.02,0.004,0.035
+ c-0.003,0.013-0.008,0.027-0.01,0.04c-0.001,0.006,0.001,0.012,0,0.017c-0.001,0.007-0.005,0.011-0.006,0.018
+ c-0.001,0.006,0.001,0.012,0.001,0.018c-0.001,0.008-0.005,0.014-0.007,0.022c-0.005,0.025-0.012,0.05-0.018,0.075
+ c-0.012,0.051-0.038,0.1-0.056,0.15c-0.006,0.014-0.006,0.024-0.006,0.039c0,0.013-0.004,0.031-0.001,0.042
+ c0.003,0.01,0.011,0.018,0.013,0.029c0.002,0.015,0.004,0.026,0.011,0.04c0.002,0.004,0.003,0.007,0.005,0.011
+ c0.002,0.002,0.004,0.006,0.006,0.009c0.101,0.09,0.202,0.179,0.302,0.269c0.157-0.083,0.314-0.165,0.471-0.247
+ c0.084,0.037,0.169,0.074,0.253,0.111c-0.052,0.135-0.104,0.271-0.156,0.406c0.008,0.013,0.01,0.03,0.014,0.044
+ c0.005,0.019,0.007,0.039,0.012,0.058c0.001,0.007,0.007,0.016,0.008,0.023c0.003,0.009,0.002,0.018,0.002,0.027
+ c0,0.01,0,0.019,0,0.028c0,0.008-0.005,0.017-0.006,0.024v-0.008l-0.004-0.001c-0.003-0.008,0.001-0.017-0.001-0.026
+ c-0.002-0.01-0.006-0.016-0.006-0.027c0.001-0.017,0.004-0.039,0.001-0.055c-0.004-0.013-0.012-0.026-0.016-0.039
+ c-0.002-0.009-0.005-0.018-0.008-0.026c-0.001-0.004-0.001-0.005-0.001-0.007c-0.008,0.021-0.017,0.042-0.025,0.063
+ c0.003,0.005,0.004,0.01,0.01,0.016c0.013,0.014,0.017,0.03,0.022,0.048c0.003,0.012,0.003,0.031,0,0.043
+ c-0.002,0.009-0.005,0.013-0.004,0.022c0,0.006,0.001,0.016,0,0.022c-0.003,0.01-0.019,0.028-0.029,0.031
+ c-0.012,0.004-0.026-0.005-0.033-0.015c-0.007-0.01-0.011-0.023-0.016-0.034c-0.004,0.01-0.008,0.021-0.012,0.031
+ c0.004,0.007,0.01,0.012,0.012,0.019c0.004,0.011,0.001,0.027,0.001,0.038s0.002,0.023,0.001,0.033
+ c-0.004,0.022-0.017,0.044-0.024,0.065c-0.007,0.024-0.013,0.045-0.022,0.068c-0.008,0.023-0.011,0.046-0.022,0.068
+ c-0.008,0.02-0.013,0.046-0.02,0.066c-0.006,0.018-0.012,0.039-0.02,0.055c-0.006,0.014-0.013,0.017-0.012,0.032
+ c0,0.011,0,0.022,0,0.032c0,0.022-0.005,0.041-0.011,0.061c-0.006,0.018-0.015,0.036-0.022,0.054
+ c-0.004,0.008-0.004,0.018-0.007,0.027c-0.001,0.006-0.004,0.009-0.004,0.016c0,0.005,0.002,0.012,0,0.017
+ c-0.003,0.012-0.01,0.024-0.011,0.038c-0.001,0.013-0.005,0.025-0.005,0.038c-0.001,0.011,0,0.022,0,0.032
+ c0,0.013,0.005,0.021,0.005,0.033c0.001,0.014-0.003,0.026-0.005,0.038c-0.001,0.004-0.004,0.01-0.004,0.012
+ c-0.001,0.006,0,0.011-0.001,0.016c-0.001,0.005-0.005,0.004-0.005,0.01c0,0.004,0.004,0.012,0.005,0.017
+ c0,0.013-0.004,0.02-0.006,0.032c-0.003,0.014,0,0.031,0,0.045s-0.007,0.024-0.01,0.037c-0.002,0.011-0.004,0.022-0.007,0.033
+ c-0.001,0.005,0.001,0.011,0.001,0.016c-0.001,0.005-0.004,0.008-0.005,0.012c-0.001,0.006-0.003,0.015-0.001,0.021
+ c0.001,0.005,0.005,0.005,0.006,0.01c0.002,0.01,0,0.023,0,0.033c0,0.014-0.004,0.025-0.006,0.039c-0.002,0.01,0,0.021,0,0.032
+ c0,0.009,0.002,0.013,0.004,0.022c0.001,0.004,0,0.013,0.002,0.016c0.006,0.008,0.023,0.001,0.031-0.001
+ c0.009-0.003,0.016-0.011,0.027-0.009c0.007,0.001,0.014,0.009,0.019,0.014c0.017,0.017,0.026,0.025,0.026,0.05
+ c0,0.011,0,0.022,0,0.033c0,0.016,0,0.017,0.016,0.016c0.014,0,0.021-0.001,0.028,0.011c0.004,0.009,0.005,0.019,0.016,0.022
+ c0.008,0.003,0.021,0.001,0.026-0.007c0.006-0.009-0.004-0.021,0.005-0.029c0.008,0.019,0,0.048-0.004,0.067
+ c-0.002,0.008-0.002,0.015-0.006,0.024c0,0-0.003,0.011-0.004,0.012c-0.002,0.01-0.001,0.021-0.006,0.031
+ c-0.005,0.01-0.011,0.019-0.015,0.028c-0.006,0.012-0.015,0.018-0.024,0.028c-0.005,0.006-0.014,0.017-0.016,0.025
+ c-0.003,0.01,0.004,0.02,0.005,0.029c0.003,0.014,0.005,0.019,0.002,0.034c-0.002,0.012-0.001,0.023-0.001,0.035
+ c0,0.015-0.006,0.025-0.011,0.039c-0.008,0.022-0.017,0.042-0.022,0.065c-0.002,0.009,0,0.016-0.004,0.026
+ c-0.002,0.006-0.005,0.012-0.008,0.018c-0.005,0.009-0.011,0.017-0.015,0.027c-0.008,0.018-0.009,0.038-0.02,0.056
+ c-0.003,0.004-0.009,0.009-0.013,0.013c-0.003,0.004-0.004,0.007-0.006,0.011c-0.009,0.012-0.017,0.027-0.026,0.04
+ c-0.008,0.01-0.021,0.02-0.032,0.028c-0.01,0.008-0.019,0.018-0.028,0.026c-0.009,0.007-0.017,0.018-0.027,0.023
+ c-0.012,0.006-0.018-0.001-0.024-0.012c-0.009-0.016-0.01-0.02-0.026-0.007c-0.006,0.005-0.013,0.01-0.017,0.017
+ c-0.005,0.007-0.005,0.015-0.008,0.023c-0.003,0.004-0.006,0.005-0.007,0.01c-0.003,0.01,0.001,0.023,0,0.033
+ c-0.001,0.011-0.005,0.021-0.006,0.032c0,0.015-0.004,0.027-0.005,0.041c-0.003,0.029-0.015,0.056-0.016,0.085
+ c-0.001,0.011-0.006,0.02-0.006,0.032c0,0.01,0.003,0.024,0.001,0.033c-0.003,0.01-0.008,0.014-0.017,0.018
+ c-0.002,0.001-0.015,0.004-0.016,0.005c-0.003,0.007,0.003,0.006,0.004,0.011c0.001,0.005,0,0.01,0.001,0.015
+ c0.002,0.011,0.009,0.021,0.006,0.033c-0.002,0.005-0.005,0.005-0.006,0.01s0,0.012,0,0.017c0,0.014,0,0.02-0.007,0.032
+ c-0.006,0.012-0.013,0.02-0.015,0.033c-0.001,0.012-0.004,0.027-0.01,0.037c-0.008,0.013-0.019,0.022-0.028,0.033
+ c-0.007,0.008-0.014,0.019-0.02,0.028c-0.007,0.011-0.016,0.017-0.023,0.027c-0.007,0.01-0.013,0.017-0.017,0.028
+ c-0.004,0.011-0.007,0.022-0.012,0.032c-0.012,0.024-0.023,0.054-0.03,0.081c-0.006,0.024-0.014,0.048-0.028,0.069
+ c-0.004,0.005-0.008,0.011-0.012,0.016c0,0.009,0,0.017,0.002,0.025c0.005,0.018,0.009,0.046,0.004,0.065
+ c-0.001,0.007-0.004,0.009-0.006,0.016c0,0.005,0.001,0.011,0.001,0.017c0,0.01,0.001,0.024-0.007,0.033
+ c-0.017,0.017-0.037-0.004-0.049-0.015c-0.017-0.016-0.034-0.033-0.048-0.051c-0.004-0.006-0.01-0.011-0.015-0.017
+ c-0.007-0.008-0.013-0.022-0.021-0.028c-0.015-0.011-0.04-0.001-0.046,0.018c-0.001,0.004,0,0.011-0.001,0.016
+ c-0.001,0.008-0.005,0.013-0.004,0.022c0.001,0.012,0.006,0.02,0.006,0.033c-0.001,0.013,0.002,0.024,0.005,0.037
+ c0.002,0.007,0.008,0.016,0.01,0.024c0.004,0.024,0.011,0.045,0.023,0.069c0.021,0.044,0.03,0.086,0.047,0.131
+ c0.004,0.01,0.011,0.021,0.013,0.032c0.001,0.011,0,0.023,0,0.033c0,0.022-0.006,0.045-0.015,0.065
+ c-0.017,0.039-0.036,0.073-0.073,0.097c-0.01,0.007-0.023,0.013-0.031,0.023c-0.008,0.011-0.012,0.024-0.023,0.033
+ c-0.018,0.015-0.028,0.027-0.038,0.049c-0.004,0.009-0.011,0.019-0.018,0.027c-0.004,0.005-0.01,0.008-0.014,0.012
+ c-0.002,0.003-0.002,0.008-0.005,0.011c-0.005,0.007-0.012,0.013-0.017,0.02c-0.007,0.01-0.011,0.023-0.018,0.032
+ c-0.007,0.009-0.019,0.016-0.021,0.028c-0.001,0.006,0.001,0.011-0.001,0.017c-0.002,0.006-0.004,0.009-0.004,0.016
+ c-0.001,0.011,0.001,0.018-0.011,0.022c-0.009,0.003-0.02,0.006-0.029,0.001c-0.007-0.005-0.011-0.015-0.014-0.023
+ c-0.001-0.002-0.002-0.004-0.003-0.005c-0.013-0.002-0.022-0.011-0.03-0.021c-0.011-0.013-0.027-0.035-0.031-0.051
+ c-0.002-0.005,0-0.01-0.003-0.014c-0.001-0.003-0.006-0.005-0.008-0.008c-0.005-0.006-0.005-0.014-0.007-0.021
+ c-0.007-0.019-0.005-0.04-0.01-0.059c-0.003-0.016-0.01-0.029-0.012-0.044c-0.003-0.021,0.005-0.039,0.006-0.06
+ c0-0.009-0.004-0.022-0.009-0.033c-0.02-0.01-0.041-0.018-0.062-0.021c-0.024-0.003-0.037-0.011-0.053,0.012
+ c-0.006,0.008-0.007,0.018-0.014,0.026c-0.006,0.006-0.014,0.011-0.02,0.017c-0.009,0.008-0.013,0.018-0.021,0.028
+ c-0.005,0.007-0.012,0.014-0.016,0.021c-0.006,0.012-0.006,0.024-0.007,0.037c0,0.011-0.003,0.021-0.005,0.032
+ c-0.002,0.019-0.01,0.034-0.011,0.053c-0.001,0.018-0.001,0.039-0.005,0.057c-0.006,0.026-0.028,0.05-0.041,0.073
+ c-0.008,0.014-0.014,0.033-0.03,0.041c-0.003,0.001-0.009,0.002-0.012,0.004c-0.001,0-0.002,0.001-0.003,0.001
+ c-0.175,0.457-0.35,0.913-0.525,1.37c0,0.001,0.001,0,0.001,0.002c0.002,0.013-0.004,0.021-0.006,0.032
+ c-0.002,0.011,0.003,0.023,0.001,0.033c-0.003,0.012-0.015,0.025-0.022,0.034c-0.007,0.007-0.013,0.013-0.02,0.02
+ c-0.047,0.122-0.094,0.245-0.141,0.367c0.116,0.027,0.232,0.055,0.348,0.082c0.005-0.004,0.01-0.008,0.015-0.012
+ c0.01-0.006,0.022-0.01,0.032-0.016c0.007-0.005,0.012-0.011,0.017-0.017c0.004-0.003,0.006-0.005,0.008-0.006
+ c0.001-0.002,0.002-0.004,0.003-0.006c0.006-0.009,0.012-0.014,0.02-0.022c0.012-0.013,0.027-0.024,0.04-0.036
+ c0.012-0.011,0.022-0.022,0.033-0.033c0.012-0.012,0.026-0.021,0.038-0.033c0.01-0.01,0.025-0.02,0.037-0.028
+ c0.007-0.005,0.013-0.009,0.017-0.016c0.001-0.001,0.001-0.001,0.002-0.002c0.008-0.012,0.017-0.023,0.025-0.035
+ c0.002-0.004,0.004-0.008,0.006-0.012c0.001-0.004,0.006-0.005,0.008-0.008c0.048-0.065,0.095-0.131,0.142-0.196
+ c0.009-0.017,0.022-0.036,0.027-0.053c0.004-0.012,0.004-0.021,0.012-0.032c0.009-0.011,0.021-0.022,0.032-0.033
+ c0.014-0.014,0.027-0.028,0.037-0.045c0.004-0.006,0.01-0.012,0.014-0.018c0.007-0.011,0.013-0.023,0.021-0.033
+ c0.004-0.007,0.016-0.03,0.027-0.029c0.003,0,0.001,0.004,0.002,0.005c0.131-0.182,0.262-0.363,0.393-0.544
+ c0.001-0.004-0.001-0.009,0.001-0.012c0.005-0.011,0.014-0.017,0.023-0.027c0.002-0.003,0.006-0.009,0.009-0.011
+ c0.001-0.001,0.003-0.001,0.004-0.002c0.019-0.026,0.038-0.053,0.057-0.079c0.002-0.005,0.006-0.009,0.006-0.015
+ c0.001-0.005-0.001-0.011,0-0.017c0.001-0.006,0.005-0.01,0.006-0.016c0.001-0.005-0.001-0.011-0.001-0.017
+ c0.001-0.003,0.005-0.01,0.005-0.011c0-0.008-0.001-0.008-0.003-0.016c-0.005-0.017,0.001-0.031,0.009-0.046
+ c0.007-0.013,0.022-0.048,0.043-0.034c0.007,0.005,0.005,0.015,0.013,0.019c0.008,0.005,0.021,0.001,0.027-0.002
+ c0.007-0.004,0.012-0.01,0.018-0.015c0.026-0.036,0.053-0.073,0.079-0.109c-0.007-0.001-0.015-0.002-0.022-0.003
+ c-0.01-0.002-0.022,0-0.032,0c-0.02,0-0.056,0.005-0.05-0.027l0.007-0.003c-0.001,0-0.002-0.001-0.002-0.001
+ c-0.006-0.003-0.014-0.006-0.016-0.012c-0.002-0.007,0.004-0.016,0.006-0.022c0.004-0.01,0.004-0.018,0.007-0.028
+ c0.009-0.032,0.037-0.059,0.042-0.092c0.001-0.007-0.001-0.014,0.001-0.022c0.002-0.007,0.004-0.014,0.006-0.022
+ c0.003-0.015,0.01-0.035,0.019-0.049c0.009-0.014,0.021-0.027,0.034-0.038c0.012-0.009,0.027-0.02,0.028-0.037
+ c0.007-0.002,0.013,0.001,0.02-0.002c0.004-0.001,0.013-0.007,0.018-0.01c0.008-0.004,0.017-0.021,0.025-0.021
+ c0.002,0.01,0.005,0.018,0.007,0.028c0.002,0.016,0.007,0.033,0.01,0.049c0.001,0.011,0.003,0.026,0.001,0.037
+ c-0.001,0.006-0.005,0.01-0.006,0.016c-0.001,0.007,0.001,0.015,0,0.022c-0.002,0.021-0.009,0.046-0.018,0.065
+ c-0.009,0.022-0.014,0.048-0.02,0.071c-0.003,0.014-0.009,0.025-0.014,0.038c0.102-0.037,0.204-0.075,0.305-0.112
+ c0.052,0.073,0.104,0.145,0.156,0.218c-0.134,0.131-0.269,0.263-0.404,0.394c0.001,0.002,0,0.004,0,0.006
+ c0.004,0.023,0.023,0.036,0.023,0.06c-0.001,0.013,0.003,0.031,0,0.044c-0.003,0.009-0.01,0.018-0.013,0.028
+ c-0.003,0.014-0.01,0.028-0.013,0.042c-0.003,0.012-0.002,0.026-0.002,0.038c0,0.026,0,0.05-0.005,0.075
+ c-0.003,0.014-0.006,0.025-0.006,0.04c0.001,0.014-0.002,0.024-0.005,0.038c-0.003,0.012-0.005,0.025-0.009,0.037
+ c-0.004,0.011-0.01,0.022-0.012,0.033c-0.001,0.005,0,0.011-0.002,0.016c-0.003,0.007-0.012,0.015-0.017,0.022
+ c-0.006,0.011-0.012,0.022-0.019,0.033c-0.007,0.012-0.008,0.027-0.016,0.039c-0.006,0.009-0.011,0.017-0.016,0.027
+ c-0.006,0.011-0.01,0.021-0.014,0.032c-0.004,0.013-0.011,0.018-0.019,0.027c-0.009,0.012-0.018,0.023-0.028,0.033
+ c-0.012,0.012-0.024,0.019-0.033,0.033c-0.011,0.017-0.019,0.033-0.026,0.05c-0.002,0.005-0.006,0.01-0.007,0.015
+ c-0.001,0.005,0.002,0.01-0.001,0.015c-0.003,0.008-0.014,0.014-0.02,0.019c-0.016,0.012-0.039,0.021-0.051,0.037
+ c-0.007,0.009-0.014,0.017-0.02,0.027c-0.005,0.008-0.009,0.016-0.017,0.022c-0.007,0.005-0.018,0.007-0.027,0.009
+ c-0.006,0.002-0.01,0.001-0.016,0.002c-0.006,0-0.008,0.003-0.012,0.004c-0.023,0.005-0.046-0.007-0.06,0.016
+ c-0.01,0.015-0.021,0.031-0.03,0.046c-0.017,0.029-0.048,0.047-0.071,0.071c-0.017,0.019-0.027,0.039-0.05,0.052
+ c-0.005,0.003-0.008,0.002-0.013,0.005c-0.002,0.002-0.005,0.006-0.008,0.008c-0.006,0.007-0.012,0.012-0.016,0.021
+ c-0.014,0.027-0.025,0.057-0.035,0.086c-0.005,0.013-0.022,0.029-0.032,0.039c-0.01,0.011-0.021,0.019-0.028,0.033
+ c-0.005,0.009-0.008,0.019-0.014,0.028c-0.012,0.021-0.023,0.042-0.035,0.063c-0.011,0.018-0.016,0.042-0.026,0.061
+ c-0.009,0.019-0.022,0.037-0.033,0.054c-0.02,0.033-0.029,0.072-0.047,0.105c-0.011,0.02-0.021,0.036-0.035,0.053
+ c-0.017,0.02-0.014,0.044-0.028,0.064c-0.012,0.018-0.026,0.033-0.035,0.052c-0.009,0.018-0.014,0.035-0.025,0.052
+ c-0.011,0.019-0.03,0.032-0.041,0.051c-0.01,0.017-0.02,0.035-0.028,0.053c-0.009,0.021-0.024,0.036-0.037,0.055
+ c-0.005,0.008-0.011,0.013-0.016,0.021c-0.007,0.012-0.016,0.017-0.025,0.026c-0.009,0.01-0.015,0.021-0.027,0.028
+ c-0.007,0.004-0.013,0.005-0.019,0.013c-0.006,0.008-0.008,0.018-0.013,0.025c-0.005,0.006-0.012,0.011-0.016,0.017
+ c-0.009,0.01-0.016,0.022-0.026,0.029c-0.009,0.006-0.018,0.01-0.026,0.016c-0.02,0.016-0.025,0.002-0.041-0.011
+ c-0.01-0.007-0.022-0.013-0.033-0.019c-0.012-0.005-0.025-0.009-0.037-0.014c-0.01-0.005-0.015-0.011-0.027-0.011
+ c-0.013-0.001-0.024,0.002-0.036,0.005c-0.008,0.002-0.019,0.005-0.03,0.006c-0.017,0.01-0.028,0.021-0.043,0.033
+ c-0.005,0.003-0.01,0.007-0.016,0.009c-0.013,0.004-0.026,0.007-0.039,0.012c-0.008,0.004-0.018,0.006-0.027,0.011
+ c-0.008,0.004-0.015,0.01-0.022,0.015c-0.019,0.011-0.041,0.017-0.058,0.03c-0.015,0.011-0.028,0.021-0.044,0.03
+ c-0.016,0.008-0.033,0.014-0.05,0.022c-0.013,0.007-0.022,0.018-0.034,0.027c-0.005,0.004-0.006,0.001-0.011,0.006
+ c-0.003,0.003-0.006,0.009-0.009,0.013c-0.009,0.01-0.013,0.022-0.022,0.034c-0.017,0.024-0.036,0.05-0.058,0.068
+ c-0.018,0.014-0.028,0.031-0.038,0.05c-0.012,0.024-0.033,0.039-0.056,0.052c-0.019,0.01-0.042,0.025-0.053,0.045
+ c-0.007,0.01-0.018,0.023-0.022,0.034c-0.004,0.009-0.004,0.021-0.008,0.031c-0.008,0.022-0.027,0.034-0.044,0.047
+ c-0.021,0.018-0.039,0.04-0.057,0.062c-0.014,0.017-0.036,0.028-0.055,0.037c-0.004-0.008,0.01-0.019,0.014-0.026
+ c0.006-0.011,0.017-0.026,0.012-0.038c-0.009,0.002-0.019,0.011-0.027,0.016c-0.01,0.006-0.016,0.007-0.027,0.011
+ c-0.014,0.003-0.027,0.017-0.036,0.028c-0.015,0.016-0.027,0.027-0.045,0.038c-0.016,0.01-0.029,0.023-0.045,0.03
+ c-0.014,0.007-0.023,0.018-0.037,0.025c-0.018,0.008-0.032,0.021-0.049,0.03c-0.021,0.01-0.039,0.026-0.06,0.035
+ c-0.016,0.007-0.032,0.013-0.048,0.022c-0.017,0.01-0.032,0.02-0.05,0.03c-0.007,0.004-0.011,0.004-0.018,0.011
+ c-0.004,0.004-0.008,0.01-0.013,0.013c-0.01,0.008-0.022,0.016-0.033,0.022c-0.007,0.004-0.015,0.008-0.023,0.011
+ c-0.006,0.003-0.011,0.002-0.017,0.004c-0.003,0-0.005,0.002-0.006,0.004c-0.023,0.001-0.002-0.026,0.005-0.037
+ c0.005-0.007,0.011-0.012,0.013-0.021c0.001-0.009-0.004-0.019,0.001-0.027c0.002-0.005,0.01-0.008,0.013-0.013
+ c0.005-0.005,0.01-0.013,0.013-0.02c0.009-0.018,0.016-0.036,0.033-0.05c0.01-0.008,0.024-0.01,0.034-0.019
+ c0.007-0.008,0.011-0.019,0.02-0.025c0.007-0.006,0.016-0.008,0.023-0.013c0.008-0.007,0.012-0.021,0.014-0.029
+ c0.002-0.008-0.001-0.015,0.002-0.022c0.002-0.006,0.01-0.012,0.014-0.017c0.008-0.009,0.015-0.018,0.022-0.027
+ c0.016-0.019,0.03-0.037,0.04-0.059c0.01-0.02,0.021-0.037,0.033-0.055c0.007-0.009,0.014-0.018,0.021-0.027
+ c0.005-0.008,0.017-0.02,0.02-0.028c0.005-0.012,0.002-0.03,0.002-0.043c-0.001-0.019-0.005-0.024-0.022-0.028
+ c-0.013-0.004-0.017-0.008-0.026-0.017c-0.005-0.004-0.009-0.003-0.013-0.004c-0.344,0.335-0.687,0.671-1.031,1.006
+ c-0.061-0.042-0.122-0.085-0.183-0.128c-0.002,0.003-0.005,0.006-0.006,0.009c-0.013,0.031-0.044,0.08-0.08,0.087
+ c-0.008,0.002-0.009,0.001-0.017,0.006c-0.005,0.003-0.01,0.008-0.016,0.01c-0.009,0.002-0.025,0.003-0.033-0.003
+ c-0.003-0.003-0.005-0.006-0.005-0.01c-0.002,0.003-0.003,0.005-0.004,0.007c-0.007,0.014-0.02,0.029-0.023,0.045
+ c-0.004,0.02,0.003,0.034-0.007,0.053c-0.007,0.012-0.012,0.026-0.019,0.039c-0.004,0.008-0.005,0.013-0.007,0.022
+ c-0.002,0.01-0.004,0.012-0.011,0.02c-0.009,0.012-0.018,0.023-0.032,0.031c-0.015,0.008-0.036,0.005-0.049-0.002l0.003-0.007
+ c-0.002,0.001-0.005,0.002-0.008,0.001c-0.009-0.004-0.006-0.017-0.005-0.024c0.002-0.01,0.006-0.025,0.011-0.034
+ c0.006-0.011,0.016-0.017,0.022-0.027c0.002-0.004,0.007-0.01,0.008-0.014c0.003-0.008,0-0.017,0.003-0.026
+ c0.002-0.011,0.005-0.01,0-0.021c-0.004-0.01-0.01-0.018-0.014-0.027c-0.007-0.017,0.005-0.023,0.007-0.039
+ c0.001-0.01-0.001-0.012,0.005-0.022c0.005-0.007,0.009-0.012,0.013-0.02c0.005-0.014,0.007-0.026,0.025-0.029
+ c0.002,0,0.006,0,0.01,0c0-0.018,0.007-0.037,0.017-0.049c0.013-0.015,0.03-0.028,0.034-0.048c0.001-0.011-0.001-0.023-0.001-0.033
+ c0-0.005,0.002-0.009,0.003-0.014c-0.001-0.002-0.002-0.004-0.004-0.006c-0.344-0.241-0.689-0.482-1.033-0.722
+ c0.006-0.039,0.013-0.079,0.02-0.118c-0.014,0-0.028,0-0.041,0c-0.009,0-0.022,0.003-0.027-0.006
+ c-0.005-0.01,0.009-0.026,0.014-0.033c0.006-0.009,0.008-0.017,0.013-0.027c0.004-0.007,0.01-0.013,0.014-0.021
+ c0.006-0.01,0.01-0.02,0.014-0.031c0.002-0.005,0.004-0.006,0.004-0.012c0.001-0.008,0-0.014,0.001-0.021
+ c0.002-0.008,0.009-0.018,0.014-0.025c0.008-0.009,0.009-0.014,0.012-0.025c0.005-0.018,0.014-0.041,0.023-0.059
+ c0.001-0.001,0.003-0.002,0.004-0.003c0.012-0.068,0.023-0.137,0.035-0.205c-0.176-0.03-0.352-0.06-0.528-0.09
+ c0.067-0.179,0.135-0.359,0.203-0.538c-0.089-0.116-0.178-0.231-0.267-0.346c0.037-0.043,0.074-0.086,0.111-0.128
+ c-0.005-0.007-0.01-0.013-0.017-0.018c-0.204,0.031-0.408,0.062-0.612,0.094c-0.004-0.042,0.013,0.467-0.012-0.127
+ c-0.024-0.594,0.79-0.564,1.185-0.846c-0.005-0.014-0.01-0.028-0.014-0.042c-0.008-0.025-0.013-0.053-0.025-0.077
+ c-0.011-0.024-0.02-0.049-0.036-0.07c-0.014-0.017-0.036-0.032-0.039-0.054c-0.003-0.02,0.009-0.039,0.015-0.056
+ c0.008-0.018,0.028-0.026,0.045-0.035c0.009-0.005,0.019-0.007,0.027-0.014c0.009-0.008,0.01-0.015,0.015-0.026
+ c0.007-0.013,0.018-0.02,0.011-0.037c-0.001-0.003-0.006-0.008-0.008-0.012c-0.003-0.005-0.004-0.011-0.007-0.016
+ c-0.011-0.019-0.012-0.04-0.032-0.055c-0.02-0.014-0.04-0.028-0.06-0.042c-0.02-0.012-0.033-0.031-0.055-0.039
+ c-0.022-0.008-0.042-0.035-0.043-0.06c-0.001-0.011,0-0.022,0-0.033c0-0.014,0.003-0.024,0.005-0.038
+ c0.002-0.01-0.002-0.022,0.001-0.032c0.003-0.009,0.013-0.017,0.019-0.023c0.014-0.013,0.024-0.027,0.034-0.04
+ c-0.145-0.072-0.29-0.143-0.435-0.215c-0.005,0.005-0.011,0.01-0.012,0.016c-0.006-0.001-0.015-0.021-0.02-0.028
+ c-0.002-0.003-0.005-0.005-0.008-0.007c-0.101-0.05-0.203-0.101-0.304-0.151c-0.032-0.038-0.064-0.077-0.097-0.116
+ c-0.011-0.005-0.018-0.016-0.032-0.019c-0.013-0.003-0.019,0-0.029-0.01c-0.011-0.012-0.013-0.024-0.031-0.028
+ c-0.008-0.002-0.009,0.001-0.016-0.005c-0.006-0.004-0.013-0.012-0.018-0.017c-0.009-0.007-0.016-0.01-0.026-0.016
+ c-0.009-0.005-0.011-0.011-0.018-0.02c-0.003-0.005-0.006-0.008-0.01-0.011c-0.006-0.004-0.016-0.004-0.021-0.008
+ c-0.009-0.007-0.011-0.021-0.02-0.028c-0.007-0.004-0.014-0.005-0.019-0.013c-0.004-0.005-0.005-0.014-0.01-0.018
+ c-0.006-0.006-0.01-0.003-0.016-0.006c-0.014-0.006-0.011-0.023-0.02-0.033c-0.01-0.01-0.018-0.006-0.029-0.011
+ c-0.011-0.003-0.025-0.022-0.032-0.031c-0.015-0.022-0.04-0.033-0.055-0.055c-0.01-0.012-0.017-0.021-0.029-0.032
+ c-0.015-0.014-0.02-0.03-0.031-0.047c-0.007-0.01-0.017-0.018-0.021-0.03c-0.004-0.014,0.004-0.024,0.005-0.038
+ c0.001-0.025,0.01-0.045-0.002-0.069c-0.005-0.011,0.002-0.018,0.005-0.026c-0.059-0.071-0.118-0.142-0.177-0.214
+ c0.011-0.004,0.023-0.008,0.034-0.012l0.051-0.034c-0.007-0.015-0.015-0.032-0.006-0.049c0.007-0.011,0.017-0.026,0.025-0.037
+ c0.009-0.01,0.024-0.015,0.033-0.025c0.025-0.026,0.023-0.063,0.024-0.098c-0.252-0.022-0.504-0.045-0.755-0.067
+ c0.009-0.109,0.019-0.218,0.029-0.327c-0.002-0.001-0.004-0.003-0.006-0.005c-0.009-0.01-0.017-0.015-0.023-0.027
+ c-0.007-0.017-0.001-0.038,0.018-0.045c0.006-0.003,0.012,0.001,0.018,0.002c0.004-0.051,0.009-0.102,0.013-0.154
+ c-0.005-0.003-0.01-0.006-0.015-0.011c-0.009-0.01-0.015-0.019-0.027-0.027c-0.011-0.006-0.017-0.01-0.023-0.021
+ c-0.006-0.01-0.006-0.017-0.006-0.028c0-0.007-0.001-0.015,0-0.021c0.002-0.008,0.01-0.014,0.011-0.022
+ c0.001-0.009-0.009-0.02-0.016-0.026c-0.003-0.002-0.013-0.01-0.016-0.011c-0.005-0.002-0.012,0.001-0.016-0.002
+ c-0.021-0.015,0.031-0.046-0.012-0.054c-0.007-0.001-0.01,0.003-0.015-0.004c-0.002-0.003-0.004-0.018-0.005-0.021
+ c-0.003-0.015-0.001-0.031-0.001-0.046c0-0.016,0.002-0.034,0-0.049c-0.001-0.009-0.005-0.016-0.006-0.025
+ c-0.001-0.01,0-0.02,0-0.03c0-0.031,0.013-0.066,0.004-0.097c-0.006-0.025-0.003-0.05,0.003-0.076
+ c0.006-0.022,0.01-0.042,0.015-0.065c0.007-0.028,0.009-0.058,0.027-0.081c0.011-0.014,0.02-0.037,0.038-0.039
+ c0.017-0.001,0.018,0,0.026-0.016c0.008-0.015,0.007-0.027,0.007-0.044c0-0.022-0.001-0.044,0.004-0.065
+ c0.003-0.011,0.003-0.022,0.006-0.032c0.003-0.012,0.001-0.026,0.001-0.038c0-0.022-0.005-0.043-0.006-0.065
+ c0-0.013,0-0.026,0-0.039V49.35c0-0.013-0.001-0.028,0.002-0.041c0.001-0.005,0.003-0.007,0.004-0.013c0.001-0.005,0-0.011,0-0.017
+ c0-0.01-0.001-0.021,0-0.032c0-0.012,0.006-0.024,0.009-0.034c0.004-0.01,0.007-0.022,0.014-0.03
+ c0.007-0.009,0.015-0.007,0.026-0.007c0.009,0,0.024-0.001,0.031-0.007c0.012-0.009,0.006-0.019,0.003-0.031
+ c-0.003-0.011,0-0.022-0.001-0.033c-0.001-0.012-0.003-0.013,0.01-0.02c0.022-0.011,0.042-0.019,0.066-0.024
+ c0.012-0.002,0.022,0.002,0.032,0.005c0.011,0.003,0.022,0.001,0.033,0.001c0.011,0,0.022-0.001,0.033,0
+ c0.012,0.002,0.019,0.006,0.032,0.006c0.011,0,0.022,0,0.033,0c0.01-0.001,0.018,0.002,0.027,0.004
+ c0.007-0.002,0.014-0.004,0.022-0.005c0.012,0,0.026-0.001,0.038,0c0.011,0.002,0.016,0.006,0.027,0.006c0.011,0,0.022,0,0.033,0
+ c0.01,0,0.023,0.002,0.032,0c0.007-0.001,0.011-0.005,0.017-0.006c0.007-0.001,0.015,0.001,0.022,0.001
+ c0.019,0,0.046-0.005,0.065-0.001c0.005,0.001,0.006,0.005,0.01,0.006c0.007,0.002,0.018,0.002,0.026,0.004
+ c0.015,0.004,0.022,0.012,0.035,0.022c0.016,0.013,0.027,0.036,0.043,0.046c0.008,0.006,0.018,0.009,0.027,0.014
+ c0.014,0.007,0.024,0.01,0.038,0.013c0.008,0.002,0.008,0.006,0.016,0.003c0.002,0,0.01-0.008,0.012-0.009
+ c0.007-0.006,0.014-0.011,0.02-0.018c0.006-0.006,0.011-0.011,0.018-0.015c0.01-0.007,0.02-0.005,0.032-0.007
+ c0.009-0.001,0.019-0.007,0.028-0.008c0.007-0.001,0.014,0,0.02-0.001c0.012-0.002,0.022-0.009,0.034-0.006
+ c0.025,0.005,0.044,0.035,0.065,0.048c0.011,0.006,0.022,0.013,0.033,0.018c0.01,0.004,0.02,0.005,0.029,0.009
+ c0.009,0.004,0.016,0.008,0.024,0.014c0.015,0.01,0.032,0.011,0.049,0.016c0.023,0.006,0.055,0.022,0.071,0.038
+ c0.004,0.004,0.012,0.015,0.013,0.02c0,0.006-0.005,0.012-0.006,0.017c-0.002,0.007-0.005,0.014-0.006,0.021
+ c-0.004,0.019,0.02,0.048,0.033,0.059c0.013,0.012,0.028,0.026,0.043,0.034c0.002,0.001,0.01,0.003,0.013,0.004
+ c0.004,0.002,0.007,0.004,0.01,0.005c0.012,0.006,0.018,0.009,0.016,0.024c-0.003,0.013-0.009,0.014-0.017,0.021
+ c-0.007,0.007-0.012,0.017-0.016,0.028c-0.014,0.038-0.022,0.074-0.022,0.115c0,0.014,0.003,0.034,0,0.048
+ c-0.001,0.004-0.002,0.008-0.003,0.012c0.022,0.011,0.043,0.022,0.065,0.033c0.094-0.079,0.189-0.158,0.283-0.238
+ c-0.269-0.352-0.538-0.704-0.807-1.056c-0.004,0.003-0.008,0.006-0.014,0.006c-0.005,0.001-0.007,0.002-0.012,0.003
+ c-0.004,0.001-0.01,0.001-0.014,0c-0.022-0.005-0.047-0.009-0.067-0.019c-0.027-0.013-0.052-0.029-0.075-0.047
+ c-0.025-0.02-0.045-0.05-0.05-0.081c-0.003-0.019,0.003-0.037-0.006-0.054c-0.007-0.012-0.014-0.022-0.019-0.035
+ c-0.006-0.017,0.002-0.037,0.01-0.052c0.004-0.008,0.009-0.016,0.009-0.026c-0.001-0.004-0.002-0.007-0.002-0.011
+ c-0.062-0.08-0.124-0.161-0.186-0.242c0.389-1.261,1.079-1.084,1.742-0.555c0.001,0,0.002,0.001,0.003,0.001
+ c0.005,0,0.012-0.002,0.017-0.001c0.005,0.002,0.006,0.006,0.011,0.007c0.01,0.002,0.024,0,0.035,0s0.024-0.002,0.034-0.001
+ c0.013,0.002,0.021,0.006,0.035,0.006s0.027,0.001,0.041,0.006c0.009,0.003,0.019,0.01,0.028,0.012
+ c0.015,0.003,0.021-0.007,0.023,0.011c0.002,0.013,0.001,0.028-0.004,0.041c-0.008,0.019-0.013,0.035-0.013,0.057
+ c0,0.012-0.004,0.018-0.006,0.029c0,0.001,0,0.002,0,0.004c0.057,0.051,0.114,0.106,0.17,0.161c0.032,0.011,0.064,0.026,0.1,0.018
+ c0.005-0.001,0.005-0.005,0.012-0.005c0.007,0.001,0.013,0.005,0.018,0.008c0.012,0.006,0.015,0.01,0.029,0.01
+ c0.011,0,0.023,0,0.034,0c0.006,0,0.012-0.001,0.018,0c0.008,0.001,0.01,0.004,0.017,0.006c0.012,0.004,0.022,0.007,0.035,0.013
+ c0.01,0.005,0.019,0.01,0.028,0.016c0.02,0.011,0.043,0.018,0.064,0.028c0.035,0.018,0.058,0.054,0.086,0.08
+ c0.003,0.002,0.008,0.007,0.01,0.008c0.003,0.002,0.01,0.004,0.012,0.006c0.007,0.006,0.013,0.021,0.018,0.029
+ c0.005,0.01,0.008,0.019,0.007,0.031c-0.002,0.02-0.011,0.047-0.019,0.065c-0.004,0.008-0.013,0.015-0.018,0.022
+ c-0.004,0.007-0.007,0.016-0.011,0.023c-0.005,0.008-0.012,0.015-0.018,0.023c-0.005,0.008-0.009,0.018-0.014,0.026
+ c-0.002,0.004-0.005,0.007-0.007,0.01c0.042,0.045,0.084,0.091,0.124,0.136c0.006-0.006,0.013-0.013,0.02-0.016
+ c0.008-0.004,0.026-0.013,0.035-0.011c0.007,0.002,0.013,0.012,0.019,0.017c0.013,0.013,0.025,0.025,0.034,0.041
+ c0.008,0.013,0.01,0.027,0.018,0.04c0.004,0.007,0.007,0.015,0.011,0.024c0.012,0.026,0.032,0.046,0.049,0.069
+ c0.014,0.018,0.011,0.04,0.016,0.061c0.295,0.327,0.516,0.558,0.595,0.478c0.083-0.331,0.166-0.663,0.248-0.995
+ c0.021-0.028,0.042-0.057,0.063-0.085c-0.002-0.004-0.004-0.008-0.004-0.012c-0.001-0.012-0.002-0.029,0.001-0.041
+ c0.002-0.007,0.007-0.014,0.009-0.022c0.004-0.012,0.006-0.013,0.013-0.024c0.013-0.017,0.023-0.034,0.039-0.05
+ c0.015-0.016,0.035-0.03,0.057-0.04c0.002-0.005,0-0.01,0.002-0.02c0.001-0.006,0.005-0.01,0.006-0.017
+ c0.001-0.005-0.002-0.012,0-0.017c0.006-0.02,0.037-0.028,0.052-0.039c0.01-0.007,0.018-0.016,0.029-0.023
+ c0.01-0.007,0.02-0.012,0.031-0.018c0.038-0.053,0.077-0.106,0.116-0.159c0.014-0.002,0.029-0.003,0.044-0.004
+ c0.003-0.008,0.004-0.017,0.009-0.024c0.005-0.008,0.014-0.013,0.02-0.02c0.018-0.019,0.024-0.044,0.041-0.064
+ c0.005-0.006,0.013-0.011,0.017-0.018c0.006-0.01,0.008-0.024,0.011-0.035c0.003-0.009,0.009-0.019,0.011-0.028
+ c0.003-0.013-0.003-0.028,0.001-0.041c0.006-0.022,0.032-0.043,0.055-0.039c0.005,0.001,0.018,0.009,0.024,0.012
+ c0.012,0.005,0.024,0.01,0.035,0.017c0.038,0.023,0.05,0.063,0.071,0.101c0.009,0.018,0.023,0.034,0.031,0.053
+ c0.009,0.019,0.015,0.035,0.031,0.051c0,0,0,0.001,0.001,0.001c0.067-0.006,0.133-0.012,0.2-0.018c0-0.006,0.001-0.011,0.001-0.016
+ c-0.001-0.007-0.005-0.011-0.007-0.018c-0.001-0.005,0-0.013-0.001-0.018c-0.003-0.016-0.01-0.03-0.016-0.045
+ c-0.009-0.026-0.027-0.049-0.036-0.075c-0.009-0.026-0.021-0.058-0.016-0.087c0.004-0.018,0.009-0.014,0.023-0.017
+ c0.015-0.002,0.026-0.006,0.041-0.006c0.011,0,0.024,0.002,0.034,0.001c0.006-0.001,0.012-0.005,0.017-0.006
+ c0.014-0.005,0.022-0.004,0.036-0.002c0.014,0.002,0.029,0.002,0.044,0.002c0.016-0.001,0.028,0.003,0.043,0.006
+ c0.012,0.001,0.027-0.001,0.039-0.001c0.015,0,0.026,0.006,0.041,0.006c0.023,0,0.046-0.001,0.069,0.005
+ c0.012,0.003,0.024,0.006,0.036,0.007c0.018,0.001,0.034,0.009,0.052,0.012c0.022,0.002,0.048,0,0.067-0.007
+ c0.01-0.003,0.015-0.003,0.022-0.012c0.003-0.005,0.005-0.009,0.005-0.013c-0.02-0.016-0.041-0.03-0.057-0.05
+ c-0.016-0.022-0.029-0.044-0.04-0.069c-0.003-0.008-0.003-0.015-0.006-0.023c-0.001-0.005-0.004-0.009-0.006-0.015
+ c-0.002-0.01-0.004-0.02-0.004-0.031c0-0.019,0.003-0.043-0.003-0.06c-0.007-0.019-0.018-0.027-0.036-0.016
+ c-0.007,0.005-0.009,0.005-0.018,0.005c-0.008,0-0.015-0.001-0.023-0.004c-0.01-0.004-0.013-0.017-0.018-0.026
+ c-0.005-0.009-0.01-0.019-0.016-0.027c-0.011-0.014-0.031-0.005-0.044-0.015c-0.013-0.011-0.018-0.03-0.026-0.045
+ c-0.004-0.007-0.009-0.016-0.013-0.023c-0.003-0.004-0.008-0.007-0.011-0.011c-0.004-0.005-0.005-0.01-0.008-0.015
+ c-0.002-0.003-0.004-0.005-0.006-0.007c-0.009-0.003-0.018-0.008-0.028-0.013c-0.029-0.016-0.059-0.038-0.082-0.062
+ c-0.013-0.014-0.029-0.025-0.042-0.039c-0.008-0.008-0.016-0.018-0.023-0.027c-0.012-0.016-0.02-0.035-0.03-0.052
+ c-0.005-0.008-0.014-0.015-0.019-0.024c-0.004-0.007-0.007-0.012-0.011-0.019c-0.008-0.011-0.016-0.024-0.02-0.037
+ c-0.004-0.012-0.011-0.025-0.011-0.038c0-0.007,0-0.012,0.008-0.015c0.009-0.003,0.025,0.011,0.031,0.018
+ c0.006,0.007,0.008,0.017,0.014,0.024s0.013,0.012,0.018,0.02c0.005,0.007,0.008,0.012,0.016,0.017
+ c0.006,0.003,0.013,0.004,0.018,0.009c0.007,0.006,0.015,0.016,0.02,0.023c0.005,0.007,0.009,0.015,0.016,0.007
+ c0.005-0.005,0.006-0.015,0.011-0.019c0.004-0.004,0.013-0.009,0.019-0.008c0.006,0,0.01,0.008,0.015,0.011
+ c0.004,0.001,0.019,0.002,0.022,0.001c0.011-0.005-0.01-0.025-0.014-0.032c-0.008-0.015-0.014-0.032-0.021-0.048
+ c-0.005-0.014-0.005-0.029-0.01-0.041c-0.006-0.017-0.008-0.034-0.016-0.049c-0.004-0.008-0.007-0.016-0.012-0.023
+ c-0.004-0.008-0.011-0.013-0.017-0.019c-0.009-0.009-0.018-0.018-0.027-0.027c-0.009-0.008-0.02-0.014-0.03-0.022
+ c-0.014-0.011-0.025-0.024-0.038-0.035c-0.004-0.004-0.008-0.006-0.011-0.011c-0.002-0.001-0.004-0.005-0.005-0.007
+ c-0.004-0.007-0.007-0.011-0.013-0.016c-0.003-0.003-0.012-0.009-0.008-0.014c0.002-0.003,0.016,0.004,0.021,0.003
+ c-0.005-0.004-0.012-0.007-0.017-0.011c-0.008-0.006-0.014-0.013-0.021-0.02c-0.006-0.006-0.014-0.012-0.021-0.019
+ c-0.003-0.003-0.004-0.006-0.008-0.009c-0.003-0.002-0.005-0.003-0.008-0.004c-0.004-0.003-0.008-0.009-0.011-0.013
+ c-0.005-0.006-0.008-0.012-0.012-0.019c-0.008-0.013-0.017-0.027-0.03-0.036c-0.013-0.01-0.026-0.02-0.035-0.035
+ c-0.006-0.012-0.009-0.026-0.014-0.038c-0.007-0.014-0.027-0.015-0.038-0.025c-0.008-0.008-0.013-0.02-0.022-0.028
+ c-0.017-0.015-0.038-0.031-0.06-0.037c-0.009-0.003-0.013-0.003-0.019-0.012c-0.003-0.005-0.004-0.015-0.012-0.018
+ c-0.012-0.005-0.023,0.017-0.034,0c-0.003-0.006-0.004-0.014-0.007-0.02c-0.003-0.004-0.008-0.01-0.012-0.014
+ c-0.011-0.009-0.025-0.017-0.037-0.023c-0.021-0.011-0.047-0.007-0.067-0.016c-0.014-0.006-0.033-0.01-0.047-0.015
+ c-0.014-0.004-0.02-0.01-0.031-0.021c-0.01-0.011-0.025-0.019-0.034-0.031c-0.01-0.015-0.017-0.028-0.033-0.038
+ c-0.015-0.01-0.031-0.02-0.046-0.03c-0.004-0.002-0.005,0-0.008-0.004c-0.003-0.003-0.003-0.008-0.006-0.012
+ c-0.004-0.007-0.011-0.012-0.019-0.015c-0.011-0.003-0.02-0.009-0.027-0.018c-0.004-0.008-0.01-0.012-0.015-0.019
+ c-0.01-0.015-0.016-0.031-0.026-0.046c-0.009-0.012-0.018-0.022-0.028-0.033c-0.005-0.005-0.011-0.009-0.018-0.011
+ c-0.008-0.004-0.016-0.008-0.024-0.012c-0.01-0.005-0.021-0.008-0.032-0.016c-0.008-0.006-0.015-0.012-0.023-0.018
+ c-0.015-0.01-0.029-0.021-0.041-0.034c-0.009-0.01-0.015-0.017-0.027-0.023c-0.007-0.003-0.015-0.003-0.023-0.005
+ c-0.011-0.002-0.021-0.006-0.031-0.01c-0.01,0.004-0.02,0.008-0.031,0.009c-0.01,0.002-0.015,0.001-0.024,0.008
+ c-0.01,0.008-0.019,0.022-0.022,0.035c-0.004,0.019,0.006,0.031,0.012,0.048c0.013,0.034,0.016,0.069-0.027,0.074
+ c0,0.01-0.009,0.017-0.011,0.025c-0.002,0.01,0.001,0.022,0,0.032c-0.001,0.022-0.028,0.033-0.047,0.037
+ c-0.007,0.002-0.016,0.001-0.023,0c-0.007,0-0.011-0.003-0.016-0.004c-0.004-0.001-0.01,0-0.014-0.001
+ c-0.005-0.001-0.006-0.005-0.01-0.006c-0.005-0.001-0.01,0.001-0.015-0.001c-0.002,0-0.008-0.004-0.011-0.005
+ c-0.009-0.004-0.017-0.01-0.026-0.016c-0.009-0.005-0.018-0.011-0.028-0.016c-0.005-0.002-0.009-0.005-0.014-0.009
+ c-0.003-0.002-0.008-0.004-0.011-0.006c-0.016-0.012-0.025-0.032-0.036-0.048c-0.018-0.024-0.04-0.013-0.065-0.009
+ c-0.005,0.001-0.01-0.001-0.016,0c-0.005,0.001-0.009,0.004-0.015,0.006c-0.008,0.001-0.017-0.002-0.025-0.001
+ c-0.01,0.001-0.018,0.005-0.027,0.006c-0.018,0.001-0.036-0.003-0.053,0.001c-0.017,0.003-0.033,0.007-0.05,0.009
+ c-0.006,0.001-0.012,0-0.018,0.001c-0.004,0.001-0.006,0.004-0.012,0.005c-0.005,0-0.011-0.002-0.016-0.001
+ c-0.005,0.001-0.005,0.005-0.01,0.006c-0.007,0.001-0.014-0.002-0.021-0.001c-0.006,0.002-0.01,0.005-0.016,0.006
+ c-0.011,0.002-0.02,0.003-0.032,0.006c-0.012,0.004-0.018,0.004-0.031,0.003c-0.019-0.001-0.032,0.006-0.048,0.016
+ c-0.022,0.014-0.04,0.037-0.068,0.038c-0.011,0-0.016-0.004-0.026-0.005c-0.012-0.002-0.021,0-0.032,0.004
+ c-0.014,0.004-0.028,0.007-0.042,0.01c-0.012,0.003-0.025,0.006-0.037,0.007c-0.028,0.002-0.055,0.009-0.082,0.016
+ c-0.022,0.005-0.044,0.01-0.065,0.014c-0.015,0.003-0.029,0.009-0.043,0.012c-0.024,0.005-0.054,0-0.078,0
+ c-0.021,0-0.04,0-0.054-0.015c-0.01-0.009-0.02-0.026-0.024-0.039c-0.002-0.006-0.002-0.01-0.005-0.016
+ c-0.002-0.003-0.006-0.006-0.008-0.009c-0.007-0.009-0.011-0.021-0.016-0.032c-0.011-0.022-0.012-0.039,0.007-0.058
+ c0.009-0.009,0.017-0.014,0.022-0.026c0.004-0.008,0.006-0.017,0.01-0.025c0.009-0.017,0.018-0.033,0.03-0.05
+ c0.019-0.03-0.027-0.017-0.041-0.014c-0.021,0.004-0.037-0.007-0.046-0.023c-0.009-0.015-0.019-0.029-0.032-0.042
+ c-0.007-0.007-0.014-0.01-0.021-0.016c-0.007-0.005-0.011-0.013-0.018-0.018c-0.019-0.013-0.038-0.025-0.057-0.038
+ c-0.01-0.007-0.016-0.016-0.025-0.023c-0.008-0.006-0.016-0.012-0.024-0.018c-0.013-0.01-0.024-0.021-0.036-0.033
+ c-0.017-0.015-0.036-0.023-0.056-0.033c-0.018-0.009-0.035-0.024-0.054-0.03c-0.024-0.008-0.047-0.018-0.068-0.033
+ c-0.005-0.003-0.01-0.006-0.015-0.01c-0.009,0.006-0.018,0.012-0.026,0.017c-0.013,0.007-0.019,0.007-0.033,0.01
+ c-0.012,0.002-0.019,0.005-0.031,0.005c-0.018,0-0.036,0.002-0.053,0.001c-0.021-0.002-0.038-0.008-0.059-0.005
+ c-0.024,0.004-0.049,0.009-0.073,0.01c-0.023,0-0.046,0-0.068,0c-0.013,0-0.025-0.002-0.038-0.001
+ c-0.014,0.001-0.026,0.006-0.04,0.006c-0.029,0-0.057-0.015-0.086-0.016c-0.01,0-0.021,0-0.031,0c-0.017,0-0.03,0.004-0.046,0.005
+ c-0.02,0.002-0.04,0.009-0.06,0.011c-0.011,0.002-0.024,0-0.036,0h-0.069c-0.01,0-0.021,0-0.031,0c-0.011,0-0.016-0.004-0.027-0.006
+ c-0.012-0.001-0.014,0.001-0.021-0.01c-0.006-0.011-0.003-0.02-0.014-0.028c-0.012-0.009-0.029-0.013-0.043-0.018
+ c-0.019-0.007-0.039-0.012-0.058-0.018c-0.007-0.003-0.015-0.007-0.021-0.008c-0.009-0.003-0.018,0-0.027-0.001
+ c-0.009-0.002-0.017-0.005-0.026-0.006c-0.012-0.001-0.025-0.003-0.036-0.006c-0.014-0.004-0.029-0.009-0.043-0.01
+ c-0.007,0-0.015,0.002-0.021,0.001c-0.014-0.003-0.027-0.011-0.042-0.011c-0.007-0.001-0.014,0.001-0.022,0
+ c-0.008-0.001-0.012-0.004-0.02-0.006c-0.017-0.002-0.032-0.002-0.048-0.009c-0.018-0.007-0.033-0.016-0.053-0.017
+ c-0.021-0.002-0.046,0.005-0.067,0c-0.007-0.001-0.014-0.004-0.021-0.006c-0.005-0.002-0.011-0.003-0.017-0.005
+ c-0.006-0.002-0.01-0.007-0.015-0.01c-0.007-0.003-0.014-0.007-0.021-0.01c-0.026-0.013-0.051-0.024-0.075-0.041
+ c-0.011-0.008-0.021-0.019-0.031-0.029c-0.006-0.006-0.028-0.023-0.016-0.03l0.011-0.011c-0.01-0.002-0.023-0.029-0.017-0.036
+ c0.004-0.004,0.022-0.006,0.027-0.006c0.009-0.001,0.018,0,0.027,0c0.016,0,0.036,0.003,0.052,0
+ c0.008-0.001,0.016-0.008,0.022-0.009c0.009-0.003,0.017-0.001,0.025-0.001c0.022-0.001,0.038-0.017,0.059-0.016
+ c0.022,0,0.032-0.006,0.046-0.023c0.006-0.007,0.011-0.016,0.015-0.025c0.004-0.007,0.011-0.014,0.013-0.021
+ c0.002-0.008-0.002-0.018-0.001-0.026c0.002-0.01,0.006-0.016,0.005-0.026c0-0.004,0-0.008,0.001-0.011
+ c-0.001-0.005-0.004-0.008-0.005-0.012c-0.002-0.01,0-0.016-0.006-0.026c-0.006-0.012-0.014-0.023-0.02-0.036
+ c-0.003-0.008-0.006-0.014-0.01-0.021c-0.009-0.013-0.016-0.027-0.024-0.041c-0.006-0.011-0.017-0.025-0.02-0.038
+ c-0.008-0.032,0.022-0.019,0.041-0.015c0.009,0.001,0.018,0.004,0.028,0.005c0.003,0,0.007,0,0.01,0
+ c0.006,0,0.008,0.003,0.012,0.004c0.032,0.008,0.064,0.018,0.093,0.032c0.006-0.022-0.001-0.041-0.019-0.054
+ c-0.009-0.006-0.019-0.01-0.028-0.015c-0.011-0.007-0.019-0.017-0.026-0.027c-0.014-0.021-0.021-0.047-0.021-0.072
+ c0-0.012-0.004-0.02-0.006-0.031c-0.002-0.014,0.006-0.025,0.006-0.038c-0.001-0.015-0.009-0.033-0.006-0.047
+ c0.002-0.009,0.01-0.016,0.011-0.026c0.001-0.005,0-0.011,0-0.016c0-0.007,0.003-0.009,0.004-0.016c0.001-0.003,0-0.013,0.002-0.015
+ c0.004-0.003,0.015,0,0.02,0c0.015-0.002,0.011-0.008,0.011-0.022V43.87c0-0.023-0.001-0.043,0.026-0.042
+ c0.012,0,0.02,0.001,0.032,0.004c0.011,0.003,0.025,0.001,0.037,0.001s0.024-0.001,0.037,0c0.015,0.001,0.028,0.007,0.043,0.011
+ c0.023,0.006,0.05,0.011,0.071,0.022c0.007,0.004,0.02,0.005,0.023,0.014c0.004,0.013-0.004,0.027-0.006,0.038
+ c-0.002,0.012,0.002,0.024-0.001,0.036c-0.003,0.01-0.004,0.019-0.008,0.028c-0.009,0.019-0.021,0.038-0.002,0.057
+ c0.01,0.009,0.025,0.01,0.036,0.016c0.011,0.006,0.022,0.009,0.033,0.014c0.008,0.003,0.015,0.006,0.021,0.011
+ c0.004,0.003,0.007,0.005,0.011,0.01s0.007,0.011,0.011,0.016c0.011,0.012,0.044,0.036,0.059,0.017
+ c0.01-0.013-0.027-0.049-0.038-0.058c-0.008-0.006-0.014-0.015-0.022-0.02c-0.006-0.005-0.009-0.006-0.015-0.012
+ c-0.006-0.008-0.011-0.015-0.011-0.026c0-0.012,0.006-0.017,0.012-0.026c0.002-0.004,0.007-0.012,0.008-0.016
+ c0.001-0.005,0.004-0.013,0.005-0.017c0.005-0.027-0.005-0.037-0.02-0.057c-0.008-0.01-0.013-0.021-0.021-0.031
+ c-0.005-0.008-0.01-0.012-0.015-0.021c-0.004-0.007-0.006-0.015-0.011-0.022c-0.008-0.01-0.014-0.023-0.022-0.033
+ c-0.01-0.011-0.024-0.021-0.037-0.03c-0.024-0.015-0.04-0.036-0.059-0.056c-0.012-0.015-0.021-0.031-0.034-0.045
+ c-0.007-0.008-0.015-0.019-0.012-0.031c0.003-0.014,0.024-0.024,0.037-0.026c0.008-0.001,0.015,0.001,0.023,0
+ c0.007-0.001,0.013-0.004,0.02-0.005c0.013-0.003,0.028,0.002,0.041,0.004c0.01,0.002,0.022,0,0.032,0.002
+ c0.013,0.004,0.023,0.003,0.037,0.004c0.024,0.002,0.033,0.015,0.049,0.031c0.015,0.014,0.032,0.025,0.051,0.033
+ c0.022,0.008,0.041,0.001,0.063,0.005c0.009,0.001,0.02,0.008,0.028,0.009c0.011,0.003,0.023,0.001,0.036,0.001
+ c0.01,0.001,0.018,0.005,0.026,0.012c0.009,0.009,0.008,0.013,0.011,0.024c0.003,0.007,0.01,0.016,0.014,0.023
+ c0.006,0.01,0.006,0.02,0.013,0.031c0.009,0.015,0.021,0.028,0.03,0.042c0.006,0.009,0.011,0.021,0.017,0.03
+ c0.009,0.013,0.022,0.022,0.032,0.033c0.014,0.016,0.03,0.03,0.046,0.042c0.019,0.013,0.041,0.018,0.058,0.032
+ c0.012,0.009,0.025,0.016,0.037,0.025c0.019,0.014,0.024,0.039,0.042,0.055c0.008,0.007,0.018,0.013,0.025,0.021
+ c0.006,0.008,0.014,0.018,0.023,0.024c0.014,0.009,0.027,0.003,0.042,0.006c0.009,0.002,0.02,0.009,0.03,0.012
+ c0.019,0.008,0.034,0.026,0.054,0.035c0.022,0.01,0.049,0.017,0.073,0.017c0.012-0.001,0.02,0.004,0.032,0.005
+ c0.017,0.001,0.032-0.005,0.049-0.006c0.01,0,0.025-0.001,0.035,0c0.012,0.002,0.019,0.006,0.032,0.006
+ c0.012,0,0.02,0.003,0.031,0.005c0.005,0.002,0.011-0.001,0.016,0c0.009,0.001,0.009,0.002,0.016,0.007
+ c0.007,0.004,0.014,0.01,0.021,0.014c0.008,0.005,0.017,0.007,0.026,0.011c0.011,0.004,0.021,0.003,0.032,0.006
+ c0.013,0.004,0.024,0.012,0.032,0.021c0.003-0.001,0.007-0.003,0.01-0.005c0.024-0.012,0.043-0.023,0.054-0.049
+ c0.009-0.019,0.003-0.044-0.007-0.063c-0.009-0.018-0.038-0.035-0.036-0.058c0.003-0.021,0.03-0.027,0.031-0.047
+ c0.001-0.009,0.002-0.023,0-0.032s-0.011-0.023-0.015-0.031c-0.005-0.008-0.011-0.012-0.015-0.02
+ c-0.004-0.006-0.005-0.014-0.008-0.02c-0.005-0.01-0.009-0.019-0.013-0.029c-0.004-0.008-0.009-0.019-0.011-0.027
+ c-0.001-0.005,0-0.01-0.002-0.015c-0.003-0.006-0.011-0.01-0.016-0.014c-0.017-0.014-0.03-0.026-0.042-0.044
+ c-0.006-0.009-0.013-0.017-0.019-0.026c-0.007-0.011-0.016-0.015-0.028-0.021c-0.019-0.011-0.035-0.021-0.056-0.028
+ c-0.003-0.001-0.009-0.003-0.012-0.004c-0.001,0-0.011-0.004-0.012-0.004c-0.011-0.002-0.023-0.001-0.035-0.001
+ c-0.01,0-0.023,0.003-0.032,0c-0.011-0.002-0.013-0.016-0.016-0.026c-0.005-0.021-0.002-0.048-0.022-0.062
+ c-0.018-0.014-0.038-0.02-0.057-0.032c-0.017-0.01-0.03-0.028-0.047-0.039c-0.032-0.022-0.095-0.042-0.095-0.088
+ c0-0.008,0.003-0.014,0.004-0.022c0.002-0.006,0-0.013,0.001-0.019c0.002-0.011,0.007-0.022,0.01-0.033
+ c0.002-0.008,0.002-0.013,0.006-0.021s0.01-0.018,0.015-0.025c0.009-0.016,0.02-0.029,0.032-0.041
+ c-0.009-0.008-0.018-0.015-0.027-0.023c-0.015-0.014-0.039-0.026-0.058-0.034c-0.014-0.006-0.026-0.014-0.041-0.021
+ c-0.017-0.007-0.046-0.021-0.052-0.04c-0.003-0.008-0.005-0.019-0.006-0.027c0-0.012,0.003-0.021-0.004-0.031
+ c-0.006-0.008-0.016-0.014-0.021-0.023c-0.002-0.004-0.002-0.01-0.005-0.015c-0.002-0.004-0.005-0.005-0.007-0.009
+ c-0.004-0.015-0.002-0.044,0-0.059c0.003-0.016,0.011-0.03,0.011-0.047c0-0.021-0.001-0.036-0.009-0.054
+ c-0.009-0.018-0.015-0.038-0.023-0.056c-0.011-0.024-0.029-0.045-0.047-0.064c-0.03-0.032-0.062-0.064-0.088-0.1
+ c-0.012-0.016-0.026-0.032-0.037-0.048c-0.011-0.017-0.019-0.038-0.033-0.052c-0.006-0.006-0.014-0.007-0.02-0.011
+ c-0.006-0.005-0.013-0.014-0.018-0.02c-0.007-0.01-0.024-0.035-0.019-0.047c0.007,0.003,0.018,0.008,0.026,0.005
+ c0.011-0.005,0.003-0.014,0-0.021c-0.002-0.004-0.003-0.009-0.004-0.012c-0.002-0.005-0.005-0.011-0.008-0.015
+ c-0.006-0.01-0.013-0.023-0.021-0.031c-0.013-0.014-0.025-0.027-0.034-0.043c-0.003-0.004-0.013-0.015-0.013-0.021
+ c0.001-0.009,0.015-0.012,0.022-0.016c0.011-0.006,0.03-0.02,0.025-0.035c-0.002-0.006-0.011-0.016-0.016-0.021
+ c-0.008-0.008-0.023-0.008-0.029-0.018c-0.004-0.007-0.004-0.018-0.007-0.025c-0.004-0.012-0.005-0.02-0.005-0.032
+ c0-0.01-0.002-0.019,0.004-0.028s0.014-0.011,0.022-0.015c0.014-0.007,0.036-0.02,0.02-0.035c-0.012-0.012-0.031-0.015-0.046-0.022
+ c-0.019-0.009-0.042-0.015-0.063-0.021c-0.014-0.004-0.029-0.004-0.042-0.007c-0.013-0.002-0.027-0.01-0.038-0.013
+ c-0.019-0.005-0.04,0.001-0.058-0.005c-0.009-0.003-0.018-0.007-0.026-0.011c-0.013-0.005-0.023-0.005-0.036-0.007
+ c-0.012-0.003-0.025-0.008-0.037-0.011c-0.01-0.003-0.021-0.007-0.032-0.01c-0.01-0.002-0.02-0.005-0.031-0.005
+ c-0.013,0.001-0.024-0.004-0.037-0.005c-0.007,0-0.025-0.003-0.031-0.006c-0.011-0.008-0.006-0.014,0.005-0.015
+ c0.01-0.001,0.02,0.002,0.03-0.001c0.007-0.002,0.013-0.01,0.022-0.011c-0.001-0.005-0.019-0.015-0.025-0.018
+ c-0.011-0.007-0.02-0.007-0.032-0.007c-0.011,0-0.022,0-0.032,0c-0.005,0-0.011,0.001-0.016,0c-0.008,0-0.013-0.004-0.021-0.005
+ c-0.011-0.002-0.021-0.003-0.032-0.005c-0.005-0.002-0.009-0.004-0.015-0.006c-0.009-0.002-0.02,0-0.03-0.003
+ c-0.014-0.005-0.024-0.006-0.039-0.007c-0.013-0.001-0.023-0.006-0.037-0.005c-0.011,0-0.02-0.004-0.031-0.006
+ c-0.012-0.001-0.025-0.003-0.036-0.006c-0.009-0.003-0.017-0.008-0.027-0.009c-0.007-0.001-0.01-0.003-0.016-0.005
+ c-0.008-0.001-0.014,0-0.021-0.001c-0.011-0.001-0.016-0.005-0.027-0.005s-0.021,0.001-0.03-0.007c-0.01-0.008-0.006-0.013,0-0.023
+ c0.006-0.01,0.009-0.016,0.009-0.028c0-0.022,0.001-0.042-0.004-0.063c-0.001-0.006,0-0.01-0.001-0.016s-0.004-0.01-0.005-0.016
+ c-0.002-0.009,0.002-0.02,0.011-0.025c0.005-0.003,0.01,0,0.015-0.001c0.006-0.001,0.01-0.005,0.016-0.006
+ c0.011-0.002,0.02,0,0.031-0.005c0.011-0.004,0.023-0.006,0.033-0.011c0.012-0.006,0.032-0.01,0.041-0.019
+ c0.02-0.018-0.012-0.029-0.022-0.042c-0.019-0.022-0.022-0.037-0.016-0.065c0.002-0.01,0.009-0.02,0.006-0.031
+ c-0.003-0.007-0.007-0.014-0.011-0.021c-0.716-0.167-1.463-0.256-2.23-0.256c-1.483,0-2.888,0.331-4.147,0.923
+ c-0.001,0.002-0.003,0.003-0.004,0.004c-0.011,0.007-0.019,0.017-0.021,0.029c-0.001,0.003-0.001,0.007-0.001,0.011
+ c0.689-0.188,1.378-0.376,2.067-0.564c0.028,0.121,0.055,0.242,0.083,0.363c-0.41,0.599-0.395,1.249-1.229,1.798
+ c-0.743,0.488-0.665,0.181-0.862,0.139c0,0,0,0,0,0.001c0.002,0.009,0.006,0.021,0.003,0.031c-0.002,0.006-0.008,0.015-0.012,0.021
+ c-0.004,0.007-0.01,0.015-0.014,0.022c-0.003,0.006-0.006,0.009-0.01,0.014c-0.001,0.001-0.047,0.052-0.11,0.123
+ c-0.002,0.008-0.005,0.017-0.007,0.025c0.052-0.057,0.09-0.098,0.089-0.095c-0.008,0.019-0.015,0.038-0.025,0.057
+ c-0.011,0.021-0.027,0.038-0.044,0.055c-0.011,0.01-0.022,0.021-0.032,0.031c-0.161,0.768,0.008,1.628-0.821,2.21
+ c-0.58,0.408-0.637,0.248-0.658,0.049c-0.037,0.163-0.024,0.083-0.004-0.031c-0.01-0.112-0.018-0.228-0.106-0.255
+ c-0.007-0.017-0.014-0.034-0.021-0.051c-0.001,0-0.002,0-0.003,0c-0.004,0-0.007,0.003-0.011,0.004
+ c-0.006,0.001-0.012-0.001-0.018,0c-0.009,0.003-0.017,0.003-0.025,0.006c-0.008,0.002-0.014,0.005-0.022,0.009
+ c-0.017,0.008-0.028-0.011-0.028-0.026c0-0.007,0-0.015,0-0.022c0-0.009,0.002-0.011,0.007-0.018
+ c0.003-0.005,0.005-0.011,0.004-0.018c-0.001-0.003-0.004-0.003-0.004-0.006c-0.001-0.005,0.002-0.009-0.003-0.012
+ c0.004-0.006,0.006-0.014,0.009-0.021c0.004-0.007,0.005-0.019,0.011-0.025c-0.016,0.001-0.03,0.01-0.043,0.018
+ c-0.008,0.005-0.015,0.009-0.024,0.012c-0.01,0.002-0.018,0.007-0.028,0.01c-0.023,0.009-0.044,0.022-0.066,0.032
+ c-0.016,0.006-0.03,0.011-0.046,0.018c-0.018,0.01-0.036,0.019-0.053,0.029c-0.016,0.008-0.028,0.024-0.047,0.024
+ c-0.012,0-0.02-0.003-0.031-0.008c-0.01-0.004-0.019-0.006-0.028-0.013c0,0,0.001,0,0.002,0h0.007
+ c-0.006-0.001-0.014-0.007-0.018-0.011c-0.006-0.006-0.002-0.014,0.001-0.021c0.01-0.023,0.03-0.038,0.045-0.057
+ c0.005-0.006,0.009-0.013,0.014-0.019c0.005-0.008,0.012-0.011,0.018-0.018c0.004-0.004,0.008-0.008,0.012-0.013
+ c0.006-0.006,0.014-0.007,0.02-0.012c0.014-0.011,0.025-0.022,0.036-0.035c0.008-0.01,0.017-0.017,0.025-0.026
+ c0.005-0.007,0.013-0.011,0.018-0.017c0.003-0.005,0.007-0.01,0.009-0.014c0.002-0.005,0.001-0.008,0.003-0.012
+ c0-0.003,0.003-0.003,0.003-0.006c0.001-0.004-0.002-0.007,0.001-0.011c0,0,0.006-0.002,0.007-0.003
+ c0.005-0.003,0.012-0.008,0.016-0.012c0.008-0.009,0.014-0.021,0.019-0.032c0.003-0.006,0.004-0.013,0.006-0.018
+ c0.004-0.008,0.01-0.014,0.013-0.021c0.006-0.015,0.013-0.03,0.026-0.041c-0.101-0.249-0.203-0.497-0.304-0.746
+ c0,0-0.001,0-0.001-0.001c-0.014-0.013-0.029-0.014-0.046-0.004c-0.017,0.012-0.022-0.004-0.038-0.007
+ c-0.008-0.001-0.014,0-0.021,0.004c-0.002,0.002-0.004,0.004-0.006,0.005c-0.002,0.001-0.007,0.003-0.009,0.004
+ c-0.006,0.006-0.007,0.015-0.015,0.02c-0.009,0.006-0.01,0.002-0.017-0.002c-0.016-0.008-0.031,0.007-0.042,0.017
+ c-0.009,0.008-0.018,0.016-0.03,0.016c-0.01,0.001-0.012,0-0.019,0.005c-0.004,0.003-0.012,0.011-0.017,0.012
+ c-0.003,0-0.019-0.005-0.021-0.008c-0.008,0.003-0.016,0.007-0.024,0.01c-0.003,0.001-0.008,0.005-0.011,0.005
+ c-0.006,0.001-0.007-0.003-0.012-0.003c-0.005,0-0.014,0.008-0.019,0.01c-0.005,0.003-0.011,0.006-0.017,0.008
+ s-0.021,0.003-0.024,0.008c-0.001-0.01,0.005-0.015,0.009-0.023c0.005-0.009,0.007-0.019,0.012-0.028
+ c0.005-0.009,0.008-0.019,0.012-0.028c0.002-0.006,0.005-0.013,0.007-0.019c0.002-0.005,0.005-0.009,0.008-0.013
+ c0.001-0.004,0-0.008,0.001-0.012c0.003-0.011,0.011-0.02,0.014-0.031c0.003-0.01,0.007-0.019,0.009-0.028
+ c0.001-0.005,0.003-0.012,0.004-0.017c0.001-0.003,0.004-0.004,0.004-0.007c0.001-0.008-0.005-0.009-0.003-0.018
+ c0.001-0.006,0.008-0.012,0.007-0.018c-0.008-0.003-0.011,0.009-0.019,0.007c-0.003-0.008,0.006-0.022,0.008-0.03
+ c0.002-0.008,0.007-0.016,0.01-0.024c-0.011-0.005-0.014,0.007-0.023,0.007c-0.005,0.001-0.013-0.007-0.012-0.012
+ c0-0.003,0.004-0.006,0.005-0.008c0.005-0.008,0.014-0.015,0.018-0.024c0.004-0.009,0.006-0.019,0.01-0.028
+ c0.003-0.006,0.008-0.01,0.01-0.016c0.002-0.004,0.002-0.008,0.004-0.012c0.004-0.007,0.011-0.015,0.017-0.021
+ c0.012-0.011,0.026-0.024,0.04-0.034c0.01-0.008,0.017-0.012,0.021-0.025c0.003-0.009,0.004-0.019,0.007-0.028
+ c0.005-0.017,0.019-0.032,0.028-0.047c0-0.001,0.001-0.002,0.001-0.002c-0.01-0.024-0.019-0.048-0.029-0.071
+ c-0.012,0.003-0.024,0.007-0.036,0.009c-0.033,0.003-0.06,0.027-0.089,0.043c-0.021,0.011-0.04,0.022-0.058,0.038
+ c-0.009,0.007-0.016,0.016-0.025,0.022c-0.009,0.006-0.016,0.013-0.024,0.02c-0.007,0.005-0.012,0.013-0.019,0.021
+ c-0.009,0.01-0.018,0.019-0.029,0.027c-0.005,0.004-0.01,0.01-0.015,0.013c-0.008,0.005-0.018,0.01-0.025,0.016
+ c-0.004,0.004-0.002,0.006-0.004,0.011c-0.002,0.006-0.01,0.012-0.014,0.016c-0.012,0.014-0.026,0.022-0.041,0.032
+ c-0.006,0.004-0.017,0.009-0.021,0.015c-0.002,0.002-0.002,0.006-0.004,0.008c-0.003,0.003-0.007,0.004-0.01,0.006
+ c-0.011,0.008-0.015,0.022-0.025,0.031c-0.006,0.005-0.012,0.009-0.016,0.016c0.001-0.002-0.003,0.01-0.003,0.009
+ c-0.001,0.003,0,0.007-0.001,0.011c-0.002,0.012-0.013,0.03-0.023,0.037c-0.006,0.005-0.01,0.005-0.014,0.014
+ c-0.004,0.009-0.002,0.019-0.01,0.028c-0.008,0.01-0.016,0.019-0.025,0.029c-0.004,0.006-0.008,0.013-0.015,0.015
+ c-0.002-0.004,0-0.008,0.002-0.011c-0.007,0.003-0.007,0.012-0.015,0.017c-0.006,0.003-0.015,0.006-0.02,0.011
+ c-0.004,0.005-0.007,0.017,0,0.02c0.009,0.003,0.012-0.009,0.02-0.001c0.002,0.003-0.001,0.007,0.003,0.01
+ c0.005,0.003,0.003,0.001,0.009-0.001c0.006-0.001,0.011-0.008,0.016-0.009c0.008-0.001,0.011,0.008,0.016,0.011
+ c0.004,0.004,0.009,0.006,0.013,0.008c0.005,0.003,0.004,0.002,0.007,0.005c0.001,0.001,0.004,0.005,0.005,0.007
+ c0.003,0.004,0.007,0.007,0.01,0.01c0.005,0.006,0.006,0.009,0.013,0.014c0.003,0.002,0.01,0.007,0.012,0.009
+ c0.006,0.007,0.003,0.008,0,0.016c-0.001,0.003-0.001,0.006-0.003,0.009c-0.001,0.001-0.003,0.003-0.004,0.004
+ c-0.002,0.002-0.002,0.006-0.004,0.008c-0.001,0.001-0.003,0.003-0.004,0.004c-0.002,0.003-0.006,0.007-0.005,0.011
+ c0,0.002,0.001,0.004,0.003,0.005c0.003,0.014,0.009,0.025,0.009,0.039c0,0.015,0,0.028-0.015,0.037
+ c-0.006,0.004-0.013,0.002-0.02,0.004c-0.004,0.001-0.008,0.006-0.013,0.007c-0.008,0.002-0.012-0.001-0.02,0.004
+ c-0.007,0.004-0.011,0.004-0.02,0.004c-0.008,0-0.013,0-0.02-0.004c-0.007-0.004-0.008-0.005-0.016,0
+ c-0.012,0.007-0.022,0.015-0.035,0.021c-0.013,0.005-0.024,0.009-0.037,0.015c-0.004,0.002-0.008,0.004-0.012,0.007
+ c-0.008,0.004-0.01,0.007-0.015,0.013c-0.006,0.006-0.015,0.009-0.021,0.015c-0.006,0.007-0.009,0.016-0.015,0.022
+ c-0.012,0.01-0.029,0.006-0.039,0.016c-0.011,0.01-0.018,0.023-0.03,0.033c-0.005,0.005-0.01,0.006-0.015,0.009
+ c-0.005,0.004-0.008,0.007-0.013,0.012c-0.009,0.01-0.015,0.009-0.027,0.013c-0.006,0.002-0.01,0.008-0.015,0.012
+ c-0.012,0.012-0.02,0.025-0.03,0.038c-0.01,0.013-0.019,0.024-0.028,0.037c-0.004,0.007-0.006,0.011-0.007,0.019
+ c-0.001,0.003-0.003,0.005-0.004,0.009c0,0.004,0.001,0.008,0,0.012c-0.001,0.006-0.004,0.01-0.004,0.016
+ c0,0.004,0.001,0.007-0.001,0.011c-0.003,0.006-0.009,0.007-0.015,0.01c-0.016,0.007-0.024,0.01-0.032,0.027
+ c-0.003,0.008-0.007,0.016-0.012,0.024c-0.005,0.007-0.01,0.009-0.017,0.015c-0.006,0.005-0.007,0.011-0.011,0.017
+ c-0.004,0.004-0.01,0.008-0.014,0.013c-0.008,0.008-0.011,0.019-0.014,0.03c0.157,0.008,0.315,0.015,0.472,0.023
+ c0.297,0.375,0.594,0.75,0.891,1.125c-0.013,0.092-0.025,0.183-0.038,0.275c-0.505,0.161-1.213-0.164-1.514,0.484
+ c-0.301,0.649-0.023,0.006-0.022,0.016c0,0.005,0.006,0.009,0.007,0.013c0.002,0.005,0.001,0.01,0.001,0.015
+ c0,0.013-0.003,0.029,0.005,0.039c0.007,0.008,0.013,0.016,0.019,0.025c0.007,0.01,0.613,0.002,0.019,0.032
+ c-0.594,0.03,0.013,0.021,0.002,0.036c-0.005,0.007-0.012,0.012-0.016,0.02c-0.004,0.006-0.004,0.013-0.006,0.02
+ c-0.004,0.016-0.008,0.03-0.007,0.048c0.001,0.015,0.019,0.04-0.004,0.047c-0.008,0.002-0.016-0.001-0.024,0.003
+ c-0.007,0.004-0.012,0.008-0.02,0.01c-0.008,0.001-0.007-0.001-0.012,0.003c-0.001,0.001-0.003,0.006-0.005,0.008
+ c-0.003,0.004-0.008,0.007-0.012,0.012c-0.008,0.008-0.016,0.019-0.026,0.024c-0.01,0.006-0.019,0.002-0.029,0.004
+ c-0.007,0.002-0.01,0.005-0.02,0.004c-0.017-0.001-0.025-0.013-0.031-0.027c-0.008-0.018-0.012-0.033-0.025-0.047
+ c-0.004-0.004-0.009-0.009-0.01-0.014c-0.002-0.008-0.001-0.013-0.005-0.019c-0.008-0.013-0.02-0.019-0.032-0.027
+ c-0.013-0.009-0.025-0.019-0.04-0.024c-0.01-0.004-0.019-0.007-0.028-0.012s-0.015-0.004-0.024-0.006
+ c-0.015-0.004-0.036-0.006-0.052-0.003c-0.025,0.003-0.051,0.012-0.075,0.019c-0.012,0.003-0.037,0-0.037-0.015
+ c-0.01-0.001-0.016-0.004-0.028-0.004c-0.01,0-0.017-0.004-0.027-0.004c-0.011,0-0.018-0.004-0.028-0.004c-0.002,0-0.004,0-0.006,0
+ c-0.039,0.012-0.077,0.025-0.116,0.037c-0.003,0.006-0.005,0.014-0.009,0.02c-0.004,0.007-0.011,0.016-0.018,0.022
+ c-0.009,0.007-0.021,0.006-0.031,0.009c-0.008,0.003-0.006,0.005-0.008,0.012c-0.004,0.01-0.013,0.02-0.019,0.028
+ c-0.007,0.009-0.012,0.018-0.018,0.028c-0.002,0.003-0.004,0.007-0.006,0.011l0,0c0.004,0.006,0.007,0.014,0.006,0.02
+ c0,0.007-0.003,0.016-0.006,0.021c-0.004,0.008-0.01,0.012-0.016,0.019c-0.005,0.007-0.006,0.011-0.013,0.016
+ c-0.004,0.003-0.008,0.008-0.012,0.011c-0.005,0.003-0.012,0.004-0.015,0.01c-0.003,0.007,0.002,0.016-0.001,0.023
+ c-0.002,0.005-0.009,0.007-0.011,0.012c-0.002,0.005,0.001,0.011,0,0.016c-0.002,0.007-0.005,0.014-0.008,0.02
+ c-0.004,0.007-0.005,0.011-0.004,0.02c0,0.017,0.003,0.036-0.005,0.052c-0.006,0.014-0.01,0.029-0.015,0.044
+ c-0.006,0.015-0.013,0.028-0.017,0.043c-0.001,0.003-0.003,0.004-0.003,0.008c-0.001,0.004,0,0.009,0,0.013
+ c-0.001,0.003-0.003,0.006-0.004,0.008c0,0.004,0,0.008,0,0.011c-0.002,0.009-0.004,0.015-0.004,0.024v0.028
+ c0,0.02-0.003,0.036-0.009,0.055c-0.006,0.017-0.008,0.035-0.011,0.053c-0.002,0.008-0.006,0.015-0.004,0.024
+ c0.001,0.009,0.005,0.013,0.003,0.023c-0.001,0.002-0.003,0.006-0.003,0.009c-0.001,0.003,0,0.014-0.005,0.015
+ c-0.004,0.002-0.007-0.003-0.009-0.006c-0.002-0.006,0.001-0.006,0.002-0.013l-0.008,0.008c0.001-0.006-0.002-0.005-0.003-0.009
+ c-0.001-0.004-0.001-0.007-0.001-0.011c-0.001-0.007-0.004-0.015-0.007-0.021c-0.005-0.01-0.009-0.012-0.009-0.023
+ s-0.003-0.012-0.009-0.02c-0.005-0.007-0.008-0.011-0.006-0.019c0-0.003,0.008-0.02,0.002-0.024c-0.008-0.005-0.01,0.01-0.018,0.007
+ c-0.008-0.004-0.005-0.017-0.005-0.024c0-0.008,0-0.016,0-0.024c0-0.009,0.004-0.014,0.004-0.024c0-0.009,0.001-0.011,0.005-0.019
+ c0.004-0.007,0.003-0.015,0.003-0.025c0-0.008-0.005-0.012-0.004-0.019c0-0.009,0.007-0.016,0.008-0.024
+ c0.001-0.009-0.002-0.02,0-0.029c0.001-0.003,0.004-0.004,0.004-0.007c0.001-0.005-0.002-0.007-0.003-0.011
+ c0.003-0.002,0.005-0.011,0.006-0.014c0.001-0.006,0.002-0.013,0-0.019c-0.013,0.006-0.031,0.009-0.04,0.023
+ c-0.006,0.007-0.009,0.015-0.011,0.025c-0.002,0.011-0.01,0.02-0.012,0.032c-0.002,0.009-0.001,0.017-0.005,0.027
+ c-0.004,0.009-0.013,0.013-0.019,0.02c-0.005,0.006-0.003,0.01-0.005,0.017c-0.001,0.004-0.005,0.007-0.008,0.01
+ c-0.006,0.007-0.011,0.012-0.014,0.02c0.009,0.005-0.003,0.022-0.009,0.026c-0.005,0.004-0.009,0.005-0.012,0.011
+ c-0.002,0.005-0.002,0.011-0.005,0.016c-0.007,0.012-0.014,0.024-0.019,0.037c-0.005,0.014-0.007,0.029-0.013,0.043
+ c-0.003,0.008,0.002,0.008,0.002,0.016c0,0.011-0.011,0.008-0.016,0.013c-0.008,0.007-0.007,0.017-0.016,0.023
+ c-0.01,0.005-0.026,0.003-0.032-0.008c-0.003-0.005,0-0.015-0.001-0.02c0-0.009-0.002-0.015-0.005-0.023
+ c-0.002-0.006-0.002-0.011-0.003-0.017c-0.002-0.007-0.007-0.013-0.008-0.02c-0.001-0.008,0-0.012-0.004-0.02
+ c-0.003-0.004-0.01-0.012-0.011-0.017c-0.001-0.012,0.008-0.022,0.008-0.035c-0.001-0.014-0.008-0.024-0.011-0.037
+ c-0.003-0.01-0.002-0.025,0.006-0.032c0.008-0.006,0.026-0.005,0.028-0.018c0.001-0.007-0.006-0.015-0.009-0.02
+ c-0.003-0.008-0.001-0.013-0.002-0.021c-0.001-0.007-0.007-0.007,0.001-0.015c0.004-0.005,0.007-0.004,0.014-0.005
+ c0.002-0.008,0.001-0.012-0.005-0.018c-0.001-0.001-0.006-0.005-0.006-0.006c0-0.004,0.004-0.003,0.005-0.006
+ c0.001-0.005,0.002-0.006,0.003-0.012c0-0.004-0.002-0.01-0.001-0.013c0.002-0.007,0.009-0.013,0.013-0.019
+ c-0.005,0.004-0.011,0.013-0.014,0.019c-0.006,0.01-0.011,0.019-0.014,0.031c-0.001,0.01,0.005,0.03-0.008,0.033
+ c-0.009,0.002-0.019-0.002-0.028,0c0.007,0.007-0.007,0.017-0.012,0.024c-0.005,0.008-0.009,0.017-0.015,0.025
+ c-0.008,0.011-0.023,0.021-0.03,0.033c-0.004,0.007-0.006,0.026-0.018,0.026c0,0.014-0.035,0.019-0.044,0.027
+ c-0.008,0.007-0.011,0.016-0.021,0.02c-0.005,0.003-0.019,0.006-0.016,0.014c-0.009,0.007-0.013,0.022-0.019,0.032
+ c-0.01,0.018-0.017,0.041-0.03,0.057c-0.002,0.004-0.005,0.005-0.007,0.009c-0.002,0.003-0.003,0.007-0.005,0.011
+ c-0.008,0.013-0.02,0.016-0.034,0.021c-0.012,0.005-0.026,0.002-0.038,0.007c-0.009,0.004-0.012,0.01-0.018,0.017
+ c-0.004,0.004-0.005,0.005-0.013,0.004c-0.005-0.001-0.008-0.001-0.008-0.007c-0.198,0.776-0.304,1.59-0.304,2.428
+ c0,5.385,4.365,9.75,9.75,9.75s9.75-4.365,9.75-9.75C-15.25,49.098-15.794,47.324-16.736,45.824z M-17.781,55.174
+ c0.006,0.002,0.007,0.007,0.004,0.011c-0.003,0.005-0.005,0.009-0.007,0.014c-0.008,0.015-0.022,0.028-0.018,0.048
+ c0.002,0.015,0.013-0.002,0.018-0.007c0.008-0.008,0.014-0.017,0.023-0.024c0.008-0.006,0.018-0.008,0.027-0.015
+ c0.007-0.005,0.014-0.01,0.02-0.016c0.007-0.005,0.012-0.011,0.019-0.016c0.007-0.005,0.014-0.01,0.021-0.016
+ c0.005-0.005,0.009-0.01,0.013-0.016c0.004-0.004,0.008-0.006,0.012-0.011c0.007-0.009,0.014-0.018,0.026-0.02
+ c0.008-0.002,0.016-0.002,0.023-0.003c0.016-0.003,0.031-0.01,0.044-0.02c0.004-0.003,0.008-0.008,0.013-0.011
+ c0.006-0.003,0.011-0.005,0.016-0.009c0.006-0.004,0.013-0.009,0.02-0.011c0.004-0.001,0.01-0.002,0.014-0.004
+ c0.005-0.002,0.008-0.008,0.011-0.013c0.009-0.014,0.027-0.02,0.041-0.029c0.009-0.006,0.017-0.015,0.023-0.024
+ c0.006-0.008,0.011-0.017,0.019-0.023c0.017-0.015,0.034-0.03,0.054-0.042c0.007-0.004,0.015-0.011,0.02-0.019
+ c0.004-0.008,0.006-0.017,0.009-0.025c0.007-0.016,0.017-0.033,0.029-0.045c0.016-0.016,0.036-0.027,0.054-0.04
+ c0.005-0.005,0.011-0.008,0.017-0.012c0.005-0.004,0.008-0.009,0.012-0.013c0.01-0.01,0.013-0.024,0.02-0.036
+ c0.006-0.01,0.012-0.02,0.02-0.029c0.005-0.005,0.01-0.012,0.014-0.018c-0.017,0.004-0.027,0.013-0.036,0.026
+ c-0.002,0.002-0.005,0.005-0.007,0.007c-0.003,0.003-0.004,0.007-0.007,0.011c-0.006,0.008-0.015,0.013-0.022,0.02
+ c-0.009,0.009-0.016,0.019-0.024,0.028s-0.016,0.019-0.024,0.029c-0.004,0.004-0.007,0.009-0.012,0.013
+ c-0.004,0.004-0.009,0.007-0.013,0.011c-0.007,0.009-0.017,0.018-0.027,0.023c-0.003,0.002-0.011,0.005-0.015,0.003
+ c-0.003-0.003,0-0.006,0.001-0.009c0.004-0.01,0.008-0.018,0.015-0.025c0.007-0.006,0.013-0.015,0.019-0.022
+ c0.005-0.007,0.012-0.012,0.018-0.018c0.013-0.016,0.031-0.028,0.045-0.044c0.007-0.007,0.013-0.016,0.02-0.024
+ c0.006-0.008,0.013-0.016,0.019-0.024c0.005-0.006,0.009-0.013,0.014-0.02l-0.003,0.007c0.005-0.002,0.012-0.013,0.009-0.018
+ c-0.003,0.001-0.005,0.002-0.009,0.002c-0.004,0-0.005,0.001-0.009,0.004c-0.015,0.011-0.028,0.028-0.045,0.039
+ c-0.009,0.006-0.019,0.012-0.027,0.019c-0.007,0.005-0.012,0.012-0.019,0.018c-0.013,0.01-0.024,0.021-0.036,0.033
+ c-0.005,0.005-0.01,0.01-0.016,0.014c-0.005,0.003-0.008,0.002-0.014,0.002c-0.008,0.001-0.018,0.012-0.023,0.018
+ c-0.01,0.013-0.024,0.023-0.036,0.034c-0.002,0.003-0.007,0.006-0.008,0.01c-0.001,0.004-0.001,0.002,0.002,0.004
+ c0.003,0.004,0.005,0.003,0.004,0.01c0,0.003-0.003,0.013-0.007,0.013c-0.003,0.001-0.004-0.003-0.006-0.005
+ c-0.002-0.001-0.005-0.001-0.007-0.002c-0.002-0.002-0.002-0.004-0.005-0.004s-0.006,0.005-0.007,0.007
+ c-0.004,0.004-0.01,0.006-0.016,0.009c-0.004,0.003-0.013,0.004-0.013,0.011c0,0.005,0.006,0.006,0.005,0.011
+ c-0.001,0.005-0.006,0.005-0.009,0.007c-0.006,0.004-0.011,0.01-0.016,0.014c-0.016,0.015-0.028,0.032-0.043,0.047
+ c-0.006,0.007-0.013,0.012-0.019,0.019c-0.004,0.005-0.008,0.011-0.014,0.015c-0.008,0.007-0.016,0.014-0.027,0.017
+ c-0.007,0.003-0.014,0.005-0.021,0.008c-0.007,0.004-0.011,0.008-0.017,0.013c-0.007,0.007-0.016,0.012-0.023,0.017
+ c-0.016,0.013-0.032,0.027-0.049,0.038c-0.005,0.004-0.008,0.004-0.013,0.007c-0.011,0.005-0.02,0.014-0.03,0.021
+ c-0.015,0.011-0.032,0.02-0.045,0.033c-0.004,0.004-0.013,0.015-0.011,0.021c0.001,0.004,0.006,0.006,0.009,0.005
+ c0.004-0.001,0.009-0.007,0.012-0.01c0.001-0.001,0.006-0.006,0.008-0.003C-17.778,55.171-17.779,55.173-17.781,55.174z
+ M-17.109,54.626c0,0.004-0.001,0.011,0.003,0.014c0.002,0.001,0.01,0,0.013-0.001c0.009-0.002,0.019-0.008,0.025-0.015
+ c0.004-0.005,0.005-0.013,0.009-0.019c0.005-0.007,0.009-0.015,0.013-0.022c0.007-0.011,0.013-0.022,0.02-0.033
+ c0.01-0.015,0.022-0.029,0.03-0.044c0.018-0.032,0.031-0.067,0.051-0.097c0.019-0.029,0.04-0.056,0.059-0.085
+ c0.009-0.015,0.022-0.029,0.03-0.045c0.004-0.01,0.02-0.035,0.011-0.046c-0.01-0.013-0.031,0.024-0.034,0.029
+ c-0.014,0.017-0.026,0.034-0.039,0.051c-0.005,0.005-0.007,0.012-0.012,0.018c-0.002,0.004-0.003,0.007-0.005,0.011
+ c-0.001,0.003-0.004,0.005-0.005,0.008c-0.002,0.003-0.003,0.005-0.005,0.008c-0.003,0.003-0.005,0.006-0.007,0.009
+ c-0.005,0.007-0.01,0.014-0.014,0.021c-0.006,0.01-0.013,0.019-0.018,0.029c-0.005,0.008-0.011,0.015-0.016,0.023
+ c-0.006,0.01-0.015,0.019-0.021,0.029c-0.004,0.01-0.009,0.018-0.011,0.028c-0.008,0.028-0.03,0.05-0.046,0.073
+ c-0.007,0.011-0.017,0.02-0.024,0.031h-0.001c0.001-0.001,0.001-0.003,0.001-0.004l-0.005,0.011
+ c-0.001,0.001-0.001,0.002-0.002,0.002c0,0.001,0,0.002,0,0.003l0.002-0.005c0.001-0.002,0.003-0.004,0.004-0.007
+ C-17.104,54.61-17.108,54.618-17.109,54.626z M-17.08,54.48c0.01-0.013,0.024-0.024,0.032-0.038c0.01-0.017,0.017-0.035,0.025-0.052
+ c0.005-0.01,0.009-0.019,0.012-0.029c0.003-0.009,0.005-0.018,0.009-0.026c0.007-0.014,0.014-0.031,0.026-0.043
+ c0.004-0.004,0.007-0.006,0.01-0.01c0.003-0.005,0.005-0.011,0.009-0.016c0.004-0.006,0.01-0.012,0.014-0.017
+ c0.006-0.008,0.013-0.015,0.018-0.022c0.007-0.009,0.014-0.018,0.021-0.026c0.004-0.007,0.012-0.02,0.011-0.028
+ c-0.002,0.002-0.003,0.003-0.006,0.005c-0.006,0.004-0.016,0.004-0.022,0.009c-0.003,0.003-0.005,0.006-0.008,0.008
+ c-0.007,0.004-0.013,0.011-0.019,0.016c-0.007,0.007-0.012,0.015-0.019,0.021c-0.003,0.003-0.007,0.007-0.01,0.01
+ c-0.008,0.009-0.014,0.019-0.021,0.028c-0.009,0.012-0.02,0.022-0.029,0.034c-0.008,0.008-0.015,0.015-0.023,0.023
+ c-0.007,0.007-0.013,0.016-0.021,0.022c-0.008,0.006-0.015,0.014-0.022,0.021c-0.006,0.007-0.011,0.013-0.018,0.019
+ c-0.006,0.006-0.013,0.012-0.019,0.019c-0.004,0.005-0.009,0.008-0.013,0.014c-0.005,0.006-0.01,0.012-0.016,0.017
+ c-0.007,0.006-0.014,0.011-0.021,0.017c-0.005,0.004-0.008,0.009-0.014,0.012c-0.008,0.004-0.022,0.008-0.026,0.017
+ c-0.003,0.009-0.005,0.017-0.01,0.025c-0.006,0.011-0.011,0.021-0.019,0.031c-0.008,0.01-0.018,0.02-0.026,0.03
+ c-0.009,0.011-0.018,0.022-0.026,0.034c-0.003,0.005-0.005,0.01-0.008,0.014c-0.002,0.003-0.005,0.005-0.006,0.007
+ c-0.002,0.003-0.002,0.007-0.005,0.01c-0.002,0.001-0.005,0.002-0.007,0.004c-0.001,0.001-0.002,0.004-0.004,0.005
+ c-0.005,0.004-0.009,0.008-0.013,0.013c-0.003,0.004-0.007,0.007-0.011,0.011c-0.002,0.004-0.004,0.008-0.008,0.011
+ c-0.005,0.004-0.012,0.004-0.017,0.007c-0.013,0.01-0.02,0.026-0.034,0.035c-0.011,0.008-0.013,0.02-0.023,0.03
+ c-0.006,0.006-0.012,0.01-0.018,0.016c-0.003,0.003-0.012,0.01-0.013,0.015c-0.001,0.005,0.003,0.007,0.003,0.011h-0.001h0.001
+ c0.001,0.001,0.003,0,0.004-0.002c0.002-0.001,0.005-0.003,0.007-0.005c0.005-0.004,0.008-0.005,0.013-0.006
+ c0.003-0.001,0.007-0.003,0.009-0.005c0.005-0.004,0.009-0.007,0.014-0.009c0.022-0.012,0.04-0.031,0.057-0.048
+ c0.014-0.014,0.03-0.026,0.045-0.038c0.021-0.015,0.041-0.033,0.064-0.046c0.023-0.014,0.042-0.035,0.063-0.051
+ c0.02-0.017,0.04-0.034,0.059-0.052C-17.113,54.515-17.094,54.499-17.08,54.48z M-20.326,56.962c0.008,0.008,0.02,0.009,0.031,0.011
+ c0.004,0.001,0.008,0.001,0.011,0.002c0.001-0.007,0.004-0.009,0.006-0.016c0.006-0.022-0.02-0.036-0.037-0.032
+ c-0.004,0.001-0.013,0.003-0.018,0.007c0.001,0.005-0.001,0.012,0.001,0.018c0,0,0.001,0,0.001,0.001h0.031
+ C-20.309,56.958-20.317,56.961-20.326,56.962z M-16.279,52.758c-0.003,0.011-0.009,0.027-0.014,0.037
+ c-0.01,0.02-0.024,0.034-0.038,0.05c-0.01,0.011-0.027,0.034-0.042,0.028c0.006-0.009,0.007-0.018,0.007-0.029
+ c0.001-0.019,0.003-0.036,0.006-0.056c0.002-0.009-0.001-0.018,0-0.027c0-0.009,0.002-0.013,0.004-0.02
+ c0.002-0.008,0-0.015,0.001-0.022c0.001-0.009,0.004-0.014,0.006-0.022c0.005-0.022,0.006-0.045,0.017-0.065
+ c0.004-0.008,0.012-0.024,0.021-0.027c0.015-0.006,0.025,0.015,0.027,0.027c0.006,0.026,0.002,0.055,0.01,0.081
+ c0.003,0.011-0.003,0.017-0.005,0.028C-16.28,52.746-16.278,52.752-16.279,52.758z M-16.376,52.871
+ c0.001,0.001,0.002,0.002,0.003,0.002c-0.001,0.001-0.002,0.002-0.003,0.004V52.871z M-16.881,54.798
+ c-0.002,0.003-0.004,0.007-0.006,0.011c-0.003,0.005-0.008,0.007-0.01,0.011c-0.014,0.019-0.025,0.036-0.041,0.052
+ c-0.009,0.009-0.024,0.02-0.026,0.034v-0.021c0.012-0.01,0.016-0.025,0.021-0.038c0.003-0.007,0.007-0.01,0.01-0.016
+ c0.007-0.012,0.007-0.022,0.017-0.032c0.012-0.013,0.017-0.026,0.025-0.043c0.006-0.014,0.018-0.025,0.024-0.039
+ c0.003-0.007,0-0.01,0.006-0.015c0.011,0.016,0.008,0.045-0.001,0.062C-16.868,54.775-16.875,54.786-16.881,54.798z M-16.936,54.754
+ c-0.019,0.042-0.028,0.087-0.055,0.125c-0.011,0.015-0.021,0.027-0.029,0.044c-0.004,0.009-0.008,0.017-0.011,0.026
+ c-0.003,0.009-0.005,0.019-0.008,0.028c-0.007,0.022-0.015,0.04-0.029,0.059c-0.006,0.009-0.024,0.044-0.037,0.044
+ c0-0.001,0-0.001,0-0.002l-0.012,0.008c0.003-0.021,0.022-0.043,0.032-0.061c0.007-0.013,0.012-0.03,0.017-0.044
+ c0.009-0.02,0.02-0.037,0.029-0.057c0.013-0.027,0.033-0.048,0.042-0.077c0.004-0.011,0.005-0.022,0.01-0.033
+ c0.004-0.011,0.012-0.021,0.016-0.033c0.004-0.01,0.005-0.021,0.008-0.031c0.004-0.012,0.011-0.022,0.015-0.034
+ c0.006-0.017,0.008-0.036,0.015-0.055c0.007-0.017,0.015-0.031,0.025-0.047c0.006-0.011,0.027-0.04,0.031-0.012
+ c0.004,0.026-0.017,0.048-0.026,0.071C-16.914,54.7-16.923,54.727-16.936,54.754z M-17.041,54.825
+ c-0.007,0.028-0.023,0.052-0.033,0.079c-0.007,0.017-0.012,0.036-0.021,0.053c-0.009,0.019-0.023,0.036-0.032,0.056
+ c-0.01,0.018-0.017,0.038-0.028,0.055c-0.009,0.016-0.018,0.03-0.025,0.047c-0.013,0.028-0.033,0.053-0.046,0.08
+ c-0.007,0.018-0.015,0.036-0.022,0.055c-0.008,0.019-0.028,0.035-0.036,0.055c-0.004,0.01-0.006,0.017-0.013,0.025
+ c-0.005,0.004-0.019,0.022-0.021,0.007V55.32c0.001-0.053,0.038-0.097,0.064-0.141c0.006-0.01,0.009-0.019,0.013-0.028
+ c0.005-0.008,0.011-0.013,0.017-0.021c0.006-0.009,0.011-0.018,0.015-0.028c0.005-0.014,0.006-0.027,0.013-0.04
+ c0.005-0.01,0.015-0.017,0.02-0.026c0.004-0.008,0.004-0.013,0.006-0.021c0.004-0.01,0.011-0.023,0.016-0.032
+ c0.007-0.012,0.016-0.021,0.023-0.033c0.008-0.015,0.015-0.03,0.026-0.043c0.015-0.021,0.022-0.041,0.028-0.066
+ c0.002-0.009,0.007-0.018,0.011-0.027c0.004-0.011,0.003-0.026,0.013-0.034c0.009-0.007,0.023-0.007,0.023,0.007
+ c0,0.001-0.004,0.014-0.005,0.016C-17.037,54.81-17.039,54.818-17.041,54.825z M-17.292,55.364c0.006-0.01,0.009-0.02,0.016-0.029
+ c0.007-0.009,0.015-0.016,0.022-0.026c0.01-0.015,0.027-0.024,0.038-0.039c0.007-0.009,0.012-0.022,0.019-0.032
+ c0.005-0.006,0.011-0.01,0.015-0.016c0.005-0.005,0.012-0.012,0.015-0.017c0.01-0.014,0.015-0.03,0.023-0.044
+ c0.012-0.022,0.032-0.037,0.044-0.059c0.014-0.026,0.025-0.05,0.041-0.075c0.008-0.012,0.018-0.021,0.026-0.033
+ c0.007-0.013,0.013-0.036,0.024-0.045c0.012-0.009,0.013-0.001,0.012,0.012c0,0.016-0.007,0.029-0.015,0.043
+ c-0.014,0.027-0.03,0.055-0.043,0.082c-0.007,0.013-0.011,0.026-0.018,0.038c-0.004,0.007-0.008,0.016-0.012,0.023
+ c-0.005,0.006-0.01,0.007-0.014,0.015c-0.013,0.025-0.025,0.045-0.045,0.066c-0.012,0.013-0.019,0.027-0.028,0.042
+ c-0.009,0.015-0.022,0.026-0.032,0.039c-0.015,0.02-0.027,0.041-0.043,0.06c-0.014,0.016-0.026,0.031-0.038,0.049
+ c-0.011,0.016-0.024,0.029-0.032,0.045c-0.008,0.018-0.017,0.036-0.029,0.052c-0.01,0.012-0.021,0.022-0.03,0.035
+ c-0.014,0.017-0.03,0.034-0.041,0.053c-0.008,0.012-0.013,0.025-0.019,0.038c-0.006,0.012-0.014,0.02-0.02,0.032
+ c-0.003,0.007-0.007,0.014-0.01,0.021c-0.004,0.007-0.006,0.017-0.01,0.025v-0.001l-0.011-0.011
+ c0.002-0.001,0.003-0.001,0.005-0.002c0-0.007,0.005-0.018,0.007-0.024c0.005-0.015,0.021-0.028,0.028-0.042
+ c0.011-0.022,0.019-0.044,0.033-0.063c0.004-0.007,0.009-0.015,0.014-0.021c0.006-0.006,0.013-0.01,0.018-0.017
+ c0.013-0.018,0.018-0.042,0.03-0.06c0.013-0.02,0.028-0.036,0.035-0.059C-17.31,55.4-17.303,55.382-17.292,55.364z M-17.472,55.592
+ c-0.009,0.016-0.017,0.031-0.024,0.048c-0.006,0.014-0.015,0.021-0.022,0.034c-0.009,0.016-0.013,0.034-0.019,0.052
+ c-0.006,0.018-0.018,0.034-0.027,0.051c-0.006,0.012-0.015,0.022-0.022,0.033c-0.005,0.007-0.008,0.015-0.014,0.022
+ c-0.004,0.006-0.009,0.01-0.013,0.016c-0.019,0.025-0.037,0.041-0.064,0.055c-0.014,0.006-0.028,0.01-0.043,0.014
+ c-0.024,0.006-0.039,0.02-0.059,0.031c-0.013,0.006-0.026,0.012-0.039,0.018c-0.013,0.006-0.026,0.011-0.039,0.014
+ c-0.009,0.003-0.018,0.008-0.027,0.01c-0.007,0.001-0.015-0.002-0.022-0.001s-0.015,0.005-0.022,0.007
+ C-17.942,56.001-17.955,56-17.971,56l-0.011-0.016c-0.006,0.008-0.024,0.007-0.032,0.006c-0.008-0.002-0.013-0.005-0.021-0.006
+ s-0.014,0.001-0.022-0.001c-0.01-0.002-0.024-0.006-0.03-0.015c-0.004-0.006-0.009-0.026-0.008-0.033
+ c0.001-0.016,0.021-0.039,0.033-0.049c0.008-0.007,0.021-0.009,0.031-0.015c0.011-0.006,0.02-0.013,0.029-0.022
+ c0.006-0.006,0.012-0.011,0.02-0.016c0.004-0.002,0.012-0.008,0.017-0.007c0.005,0,0.005,0.005,0.01,0.006
+ c0.011,0.003,0.024-0.004,0.033-0.01c0.009-0.006,0.016-0.009,0.026-0.013c0.016-0.007,0.036-0.021,0.056-0.016
+ c0.01,0.003,0.015,0.006,0.027,0.006c0.014,0,0.024,0.006,0.038,0.005c0.012,0,0.02,0.004,0.031,0.007
+ c0.007,0.002,0.003,0.004,0.012,0.003c0.006,0,0.01-0.004,0.017-0.004c0.011-0.001,0.019,0.001,0.028-0.004
+ c0.01-0.005,0.018-0.011,0.028-0.017c0.016-0.009,0.032-0.022,0.047-0.035c0.016-0.013,0.026-0.034,0.042-0.049
+ c0.016-0.014,0.025-0.028,0.039-0.044c0.012-0.014,0.019-0.03,0.029-0.047c0.011-0.017,0.013-0.04,0.025-0.056
+ c0.006-0.009,0.036-0.043,0.044-0.042c0.006,0.013-0.008,0.028-0.014,0.038C-17.455,55.567-17.465,55.579-17.472,55.592z
+ M-18.013,56.088c0.011-0.001,0.025,0.002,0.037,0c0.015-0.003,0.03-0.015,0.043-0.022c0.007-0.004,0.011-0.009,0.018-0.002
+ c0.01,0.01,0.001,0.019-0.007,0.026c-0.014,0.012-0.029,0.023-0.043,0.031c-0.008,0.005-0.042,0.033-0.044,0.015l0.002-0.005
+ c-0.016,0.001-0.034-0.006-0.051-0.005c-0.013,0-0.047-0.01-0.044-0.028c0.004-0.018,0.04-0.025,0.054-0.02
+ C-18.034,56.082-18.029,56.088-18.013,56.088z M-18.048,56.206c0.02-0.008,0.045-0.013,0.066-0.01
+ c0.013,0.002,0.012,0.002,0.007,0.012c-0.005,0.01-0.01,0.018-0.018,0.025c-0.015,0.013-0.033,0.012-0.049,0.022
+ c-0.006,0.003-0.009,0.01-0.015,0.014c-0.007,0.004-0.013,0.003-0.022,0.005c-0.001,0.002-0.003,0.004-0.006,0.004
+ c-0.006,0.002-0.011,0-0.017,0.001c-0.003,0.001-0.01,0.002-0.015,0.003l0.004-0.004c-0.015,0.002-0.05-0.02-0.049-0.038
+ c0-0.011,0.014-0.02,0.023-0.024c0.008-0.002,0.016-0.003,0.025-0.004c0.01,0,0.007,0.004,0.014,0.005
+ C-18.08,56.221-18.066,56.213-18.048,56.206z M-18.259,56.168c-0.008,0.003-0.015,0.01-0.022,0.012
+ c-0.009,0.003-0.026,0.002-0.033,0.011l0.006-0.011c-0.005-0.023,0.023-0.051,0.038-0.066c0.006-0.005,0.015-0.011,0.021-0.016
+ c0.011-0.007,0.013-0.016,0.022-0.025c0.007-0.006,0.014-0.007,0.021-0.013c0.006-0.004,0.011-0.012,0.016-0.018
+ c0.006-0.006,0.024-0.03,0.034-0.026c0.023,0.009-0.045,0.111-0.059,0.126C-18.228,56.156-18.242,56.16-18.259,56.168z
+ M-18.196,55.957c-0.007,0.017-0.013,0.039-0.024,0.055c-0.011,0.015-0.027,0.018-0.041,0.03c-0.009,0.009-0.018,0.016-0.027,0.025
+ c-0.011,0.011-0.017,0.025-0.031,0.015l0.005-0.011c-0.001,0-0.003,0-0.005-0.001c0.001-0.01,0.009-0.018,0.015-0.026
+ c0.012-0.018,0.024-0.036,0.038-0.053c0.004-0.004,0.008-0.009,0.013-0.013c0.003-0.004,0.003-0.01,0.006-0.015
+ c0.005-0.007,0.014-0.01,0.019-0.017c0.014-0.018,0.014-0.036,0.035-0.049c0.005-0.003,0.024-0.02,0.031-0.016
+ c0.008,0.007-0.007,0.022-0.009,0.027C-18.178,55.925-18.188,55.94-18.196,55.957z M-18.255,56.79c0.002,0.007,0.003,0.018,0,0.025
+ c-0.003,0.008-0.013,0.011-0.009,0.022c0.02-0.006,0.027-0.032,0.039-0.046c0.005-0.008,0.014-0.012,0.02-0.019
+ c0.012-0.013,0.016-0.033,0.026-0.048c0.011-0.019,0.029-0.036,0.049-0.045c0.008-0.003,0.02-0.005,0.027-0.011
+ c0.004-0.002,0.007-0.01,0.012-0.01c0.008,0.001,0.007,0.012,0.006,0.018c-0.003,0.01-0.008,0.021-0.011,0.032
+ c-0.004,0.009-0.01,0.022-0.011,0.033c-0.004,0.024-0.011,0.044-0.028,0.063c-0.01,0.011-0.021,0.023-0.028,0.036
+ c-0.008,0.014-0.012,0.025-0.027,0.035c-0.012,0.008-0.049,0.035-0.063,0.028c0.001-0.012,0.027-0.021,0.036-0.028
+ c0.005-0.004,0.01-0.01,0.013-0.015c0.004-0.004,0.014-0.015,0.015-0.02c-0.012-0.006-0.026,0.005-0.036,0.011
+ c-0.013,0.007-0.025,0.014-0.036,0.024c-0.01,0.01-0.022,0.019-0.032,0.029c-0.005,0.004-0.01,0.007-0.014,0.012
+ c-0.006,0.007-0.006,0.018-0.012,0.025c-0.009,0.011-0.02,0.018-0.022,0.034c-0.001,0.006,0.002,0.014-0.001,0.02
+ c-0.003,0.005-0.011,0.009-0.015,0.012c-0.014,0.01-0.025,0.018-0.035,0.032c-0.007,0.01-0.014,0.022-0.025,0.03
+ c-0.018,0.012-0.046,0.026-0.053,0.048c-0.003,0.007-0.002,0.011-0.006,0.017c-0.003,0.005-0.009,0.01-0.012,0.015
+ c-0.008,0.011-0.014,0.024-0.021,0.034c-0.017,0.021-0.044,0.036-0.066,0.051c-0.009,0.006-0.016,0.015-0.023,0.022
+ c-0.005,0.004-0.011,0.007-0.016,0.011c0.007-0.009,0.008-0.021,0.012-0.031c0.004-0.011,0.006-0.022,0.011-0.033
+ c0.007-0.016,0.013-0.034,0.022-0.049c0.005-0.009,0.003-0.012,0.005-0.022c0.001-0.006,0.006-0.012,0.009-0.018
+ c0.005-0.009,0.01-0.017,0.017-0.026c0.009-0.015,0.023-0.024,0.034-0.037c0.007-0.01,0.015-0.021,0.023-0.032
+ c0.008-0.011,0.02-0.015,0.031-0.023c0.013-0.009,0.022-0.021,0.031-0.034c0.003-0.004,0.005-0.005,0.007-0.009
+ c0.003-0.006,0.003-0.016,0.007-0.021c0.006-0.007,0.015-0.009,0.022-0.016c0.008-0.009,0.01-0.017,0.016-0.027
+ c0.007-0.012,0.02-0.018,0.027-0.029c0.01-0.012,0.017-0.018,0.03-0.028C-18.291,56.817-18.276,56.799-18.255,56.79z
+ M-18.624,57.274c0.001,0,0.002-0.001,0.003-0.002c-0.001,0.002-0.003,0.004-0.003,0.007V57.274z M-18.13,56.418
+ c-0.021,0.014-0.052,0.017-0.071,0.035l-0.009,0.01c-0.009-0.012-0.014-0.014-0.005-0.027c0.012-0.016,0.028-0.03,0.043-0.044
+ c0.021-0.021,0.044-0.035,0.07-0.049c0.011-0.007,0.019-0.016,0.03-0.022c0.012-0.006,0.025-0.008,0.036-0.016
+ c0.01-0.008,0.022-0.019,0.031-0.028c0.006-0.007,0.033-0.055,0.04-0.037c0.004,0.01-0.011,0.029-0.016,0.038
+ c-0.008,0.017-0.015,0.03-0.028,0.044c-0.018,0.017-0.037,0.034-0.056,0.048c-0.01,0.008-0.019,0.018-0.03,0.024
+ C-18.107,56.401-18.118,56.41-18.13,56.418z M-17.835,56.051c0.012-0.008,0.022-0.019,0.032-0.027
+ c0.013-0.01,0.026-0.019,0.039-0.028c0.009-0.006,0.017-0.012,0.025-0.019c0.013-0.009,0.026-0.019,0.036-0.031
+ c0.011-0.012,0.021-0.031,0.037-0.037c0.022-0.01,0.017,0.015,0.009,0.026c-0.01,0.016-0.02,0.034-0.033,0.048
+ c-0.011,0.013-0.022,0.022-0.035,0.032c-0.013,0.011-0.022,0.022-0.034,0.034c-0.012,0.013-0.027,0.02-0.039,0.032
+ c-0.009,0.008-0.017,0.017-0.026,0.024c-0.01,0.009-0.021,0.015-0.029,0.026c-0.008,0.012-0.016,0.027-0.031,0.033l-0.005-0.006
+ c-0.012,0.003-0.032-0.006-0.034-0.018c-0.005-0.027,0.026-0.05,0.044-0.063C-17.864,56.066-17.85,56.06-17.835,56.051z
+ M-17.847,56.169c0.005-0.008,0.011-0.015,0.018-0.021c0.013-0.011,0.024-0.022,0.036-0.033c0.011-0.01,0.025-0.019,0.035-0.031
+ c0,0.017-0.013,0.035-0.023,0.048c-0.012,0.017-0.017,0.035-0.032,0.049c-0.006,0.005-0.01,0.011-0.015,0.017
+ c-0.007,0.008-0.01,0.006-0.018,0.009c-0.019,0.008-0.021,0.013-0.022,0.033l-0.011-0.011l-0.001,0.003
+ c-0.008-0.006,0.006-0.031,0.012-0.038C-17.861,56.185-17.853,56.179-17.847,56.169z M-17.809,56.229
+ c0.012-0.01,0.023-0.022,0.034-0.033c0.009-0.008,0.011-0.017,0.018-0.026c0.008-0.012,0.022-0.021,0.031-0.034
+ c0.003-0.004,0.01-0.017,0.016-0.016c0.012,0,0.004,0.02,0.002,0.026c-0.004,0.011-0.008,0.026-0.015,0.036
+ c-0.007,0.01-0.017,0.016-0.025,0.025c-0.013,0.017-0.026,0.032-0.042,0.045c-0.02,0.017-0.038,0.036-0.061,0.048l0.005-0.017
+ C-17.873,56.28-17.814,56.233-17.809,56.229z M-17.71,56.066l-0.005,0.011c-0.021-0.023-0.007-0.047,0.011-0.064
+ c0.015-0.016,0.032-0.03,0.048-0.045c0.018-0.016,0.036-0.032,0.052-0.049c0.011-0.011,0.02-0.022,0.03-0.033
+ c0.019-0.02,0.03-0.045,0.048-0.065c0.03-0.036,0.067-0.076,0.087-0.12c0.004-0.009,0.009-0.018,0.013-0.027
+ c0.008-0.016,0.017-0.033,0.026-0.048c0.004-0.006,0.038-0.051,0.048-0.044c0.014,0.009-0.017,0.057-0.022,0.064
+ c-0.009,0.016-0.022,0.029-0.031,0.044c-0.008,0.012-0.018,0.021-0.026,0.033s-0.015,0.023-0.024,0.035
+ c-0.012,0.015-0.024,0.029-0.033,0.047c-0.013,0.026-0.033,0.046-0.049,0.07c-0.029,0.044-0.072,0.079-0.098,0.124
+ c-0.007,0.014-0.019,0.031-0.031,0.04c-0.014,0.009-0.027,0.027-0.043,0.032L-17.71,56.066z M-19.005,57.415l-0.022,0.006
+ c0.005-0.004,0.007-0.013,0.01-0.018c0.003-0.007,0.007-0.014,0.012-0.02c0.007-0.009,0.026-0.027,0.038-0.028
+ c0.017-0.001,0.021,0.016,0.013,0.028c-0.005,0.006-0.014,0.012-0.02,0.017C-18.982,57.408-18.993,57.418-19.005,57.415z
+ M-19.109,57.361h-0.01l-0.005-0.001c0.006-0.014,0.039-0.065,0.06-0.053c0.002,0.002,0.009,0.015,0.009,0.017
+ c0.001,0.01-0.004,0.012-0.01,0.021C-19.076,57.36-19.093,57.381-19.109,57.361z M-19.194,57.357
+ c-0.007,0.003-0.029,0.011-0.023-0.007h-0.017c0.015-0.005,0.022-0.022,0.032-0.034c0.004-0.005,0.01-0.013,0.016-0.016
+ c0.007-0.004,0.015-0.003,0.022-0.008c0.001,0.009,0.012,0.012,0.013,0.02c0,0.007-0.017,0.024-0.021,0.029
+ C-19.179,57.348-19.185,57.353-19.194,57.357z M-19.499,57.514c-0.001,0.002-0.001,0.003-0.001,0.005l-0.006-0.006
+ c-0.001,0.001-0.002,0.001-0.003,0.001c0.019-0.023,0.036-0.05,0.063-0.065c0.014-0.007,0.025-0.011,0.039-0.022
+ c0.01-0.008,0.016-0.022,0.031-0.016c-0.002,0.025-0.035,0.052-0.053,0.065C-19.45,57.491-19.474,57.511-19.499,57.514z
+ M-19.589,57.621c-0.005,0.009-0.012,0.015-0.019,0.023c-0.011,0.012-0.013,0.008-0.027,0.012c-0.008,0.002-0.014,0.009-0.023,0.01
+ c-0.005,0-0.013-0.001-0.019-0.001c0.003-0.001,0.005-0.003,0.008-0.005l-0.016,0.006c-0.007-0.001-0.011-0.008-0.016-0.013
+ c0.002-0.008,0.009-0.011,0.016-0.016c0.011-0.008,0.021-0.018,0.032-0.027c0.008-0.005,0.011-0.013,0.018-0.019
+ s0.017-0.012,0.026-0.016c0.017-0.01,0.027-0.022,0.038-0.036c0.004-0.006,0.013-0.017,0.02-0.022
+ c0.011-0.007,0.016-0.005,0.018,0.007c0.003,0.022-0.012,0.035-0.026,0.051C-19.571,57.589-19.579,57.605-19.589,57.621z
+ M-19.772,57.757c-0.03,0.027-0.066,0.047-0.098,0.071c-0.016,0.011-0.027,0.023-0.038,0.039c-0.011,0.015-0.024,0.018-0.039,0.026
+ c-0.014,0.007-0.024,0.027-0.039,0.033c-0.006,0.002-0.013,0.001-0.019,0.002c-0.002,0.001-0.004,0.001-0.006,0.002
+ c-0.004-0.005,0.008-0.02,0.011-0.025c0.013-0.018,0.034-0.026,0.047-0.043c0.01-0.014,0.022-0.026,0.034-0.038
+ c0.01-0.012,0.017-0.023,0.025-0.035c0.009-0.012,0.027-0.022,0.039-0.031c0.018-0.013,0.03-0.031,0.049-0.045
+ c0.013-0.01,0.024-0.016,0.039-0.021c0.011-0.004,0.011-0.001,0.022,0.001c0.009,0.002,0.014-0.002,0.021-0.005
+ c0.004,0.008-0.012,0.03-0.017,0.037C-19.75,57.736-19.761,57.747-19.772,57.757z M-20.016,57.932c0.002,0,0.004-0.001,0.005-0.002
+ c0.001,0.001,0.002,0.002,0.005,0.002H-20.016z M-20.023,57.932h0.007c-0.004,0.003-0.008,0.005-0.01,0.007
+ C-20.024,57.937-20.023,57.935-20.023,57.932z M-19.994,57.988c-0.005,0.009-0.017,0.017-0.023,0.025
+ c-0.008,0.009-0.016,0.018-0.022,0.028c-0.007,0.011-0.016,0.017-0.027,0.024c-0.01,0.005-0.017,0.014-0.027,0.02
+ c-0.019,0.011-0.04,0.015-0.056,0.031c-0.006,0.006-0.015,0.018-0.022,0.022c-0.004,0.003-0.011,0.003-0.015,0
+ c-0.002,0-0.003-0.001-0.003-0.002c0.001-0.003,0.003-0.005,0.004-0.007c0.009-0.014,0.019-0.026,0.032-0.036
+ c0.011-0.009,0.023-0.02,0.032-0.031c0.012-0.015,0.015-0.027,0.032-0.037c0.011-0.007,0.017-0.011,0.023-0.022
+ c0.008-0.014,0.013-0.007,0.027-0.012c0.01-0.003,0.017-0.013,0.027-0.015C-19.999,57.972-19.986,57.974-19.994,57.988z
+ M-20.192,58.127c0,0.003,0.001,0.006,0.003,0.009c-0.004,0.007-0.008,0.014-0.013,0.019L-20.192,58.127z M-20.079,57.926
+ c-0.013,0.008-0.028,0.019-0.038,0.032c-0.006,0.009-0.006,0.02-0.01,0.029c-0.003,0.006-0.01,0.011-0.014,0.016
+ c-0.004,0.005-0.006,0.012-0.011,0.017c-0.008,0.009-0.019,0.018-0.028,0.025c-0.007,0.005-0.027,0.013-0.027,0.022
+ c0.003,0.002,0.005,0.002,0.008,0.001c-0.006,0.006-0.01,0.015-0.015,0.022c-0.011,0.014-0.026,0.026-0.038,0.037
+ c-0.018,0.015-0.049,0.026-0.071,0.035c-0.023,0.01-0.045,0.02-0.063,0.038c-0.011,0.012-0.022,0.026-0.035,0.036
+ c-0.016,0.012-0.033,0.022-0.048,0.034c-0.005,0.004-0.011,0.01-0.017,0.015c-0.007,0.005-0.022,0.01-0.026,0.019
+ c-0.008,0.022,0.037-0.003,0.043-0.007s0.02-0.012,0.022,0c0.004,0.014-0.016,0.017-0.023,0.02c-0.009,0.004-0.017,0.01-0.026,0.014
+ c-0.01,0.004-0.02,0.009-0.029,0.014c-0.008,0.004-0.017,0.008-0.026,0.01c-0.005,0.002-0.01,0.003-0.016,0.006
+ c-0.006,0.002-0.017,0.005-0.022,0.008c-0.005,0.003-0.005,0.007-0.01,0.01c-0.004,0.002-0.011,0.004-0.016,0.007
+ c-0.007,0.003-0.015,0.006-0.022,0.009c-0.021,0.009-0.042,0.023-0.054,0.043c-0.008,0.015-0.009,0.033-0.028,0.033
+ c-0.006,0-0.012-0.001-0.017,0c-0.006,0.001-0.007,0.003-0.012,0.004c-0.009,0.003-0.017,0.004-0.026,0.007
+ c-0.004,0.002-0.007,0.002-0.012,0.004c-0.005,0.002-0.01,0.006-0.015,0.007c-0.012,0.003-0.021-0.002-0.033,0.004
+ c-0.01,0.004-0.02,0.009-0.028,0.016c-0.019,0.015-0.047,0.017-0.064,0.034c-0.009,0.01-0.016,0.021-0.027,0.028
+ c-0.009,0.005-0.019,0.006-0.027,0.012c-0.012,0.006-0.021,0.013-0.033,0.019c-0.013,0.006-0.019,0.01-0.033,0.013
+ c-0.011,0.003-0.022,0.006-0.033,0.01c-0.009,0.004-0.017,0.004-0.027,0.005c-0.009,0.002-0.018,0.007-0.027,0.011
+ c-0.01,0.005-0.021,0.007-0.031,0.012c-0.032,0.016-0.07,0.025-0.098,0.05c-0.01,0.008-0.022,0.016-0.034,0.021
+ c-0.006,0.003-0.011,0.002-0.017,0.004c-0.004,0.002-0.005,0.005-0.01,0.006s-0.012-0.001-0.017,0c-0.006,0-0.008,0.003-0.012,0.004
+ c-0.012,0.003-0.015,0.004-0.024,0.013c-0.008,0.009-0.013,0.009-0.023,0.014c-0.02,0.008-0.039,0.019-0.058,0.029
+ c-0.024,0.012-0.049,0.024-0.073,0.032c-0.018,0.006-0.032,0.014-0.048,0.024c-0.02,0.012-0.049,0.018-0.072,0.024
+ c-0.011,0.003-0.022-0.001-0.032,0.001c-0.012,0.002-0.022,0.012-0.033,0.016c-0.012,0.003-0.067,0.017-0.048-0.015
+ c0.004-0.007,0.016-0.01,0.022-0.016c0.008-0.006,0.013-0.015,0.019-0.023c0.012-0.015,0.022-0.03,0.033-0.045
+ c0.008-0.01,0.009-0.025,0.018-0.036c0.003,0,0.006-0.001,0.009-0.001c0.002-0.004,0.004-0.007,0.007-0.009
+ c0.004-0.004,0.01-0.011,0.016-0.011c0.006-0.001,0.013,0.006,0.022,0.004c0.009-0.001,0.014-0.006,0.02-0.012
+ c0.011-0.011,0.017-0.026,0.029-0.037c0.014-0.014,0.032-0.019,0.049-0.028c0.016-0.009,0.026-0.025,0.042-0.033
+ c0.013-0.007,0.028-0.012,0.039-0.021c0.014-0.012,0.016-0.025,0.025-0.039c-0.011-0.008-0.015-0.008-0.029-0.011
+ c-0.011-0.002-0.025-0.012-0.039-0.01c-0.006,0.002-0.011,0.005-0.017,0.006c-0.006,0.002-0.015,0-0.021,0
+ c-0.014,0-0.03-0.002-0.043,0.001c-0.011,0.003-0.023,0.013-0.034,0.02c-0.013,0.009-0.028,0.011-0.043,0.016
+ c-0.027,0.008-0.046,0.02-0.07,0.035c-0.009,0.006-0.019,0.013-0.029,0.019c-0.013,0.008-0.024,0.013-0.037,0.019
+ c-0.013,0.007-0.026,0.012-0.038,0.02c-0.013,0.008-0.029,0.008-0.044,0.013c-0.012,0.004-0.022,0.014-0.034,0.019
+ c-0.012,0.007-0.027,0.01-0.041,0.013c-0.004,0.002-0.006,0.004-0.012,0.005c-0.007,0.001-0.015,0-0.022,0
+ c-0.014,0-0.022,0.008-0.037,0.004c-0.002-0.011,0.008-0.014,0.016-0.019c0.008-0.005,0.013-0.013,0.021-0.018
+ c0.015-0.008,0.029-0.019,0.044-0.027c0.007-0.004,0.014-0.006,0.022-0.009c0.012-0.006,0.016-0.017,0.025-0.025
+ c0.014-0.013,0.026-0.017,0.034-0.035c-0.011-0.015-0.046,0.004-0.059,0.01c-0.022,0.011-0.044,0.019-0.065,0.03
+ c-0.024,0.013-0.048,0.017-0.071,0.03c-0.011,0.007-0.016,0.008-0.027,0.011c-0.007,0.002-0.01,0.004-0.017,0.004
+ c-0.007,0.001-0.014,0-0.022,0.002c-0.021,0.005-0.042,0.014-0.06,0.024c-0.007,0.005-0.018,0.007-0.027,0.011
+ c-0.019,0.008-0.036,0.022-0.054,0.033c-0.008,0.005-0.018,0.009-0.027,0.013c-0.01,0.005-0.019,0.01-0.027,0.015
+ c-0.008,0.004-0.015,0.008-0.022,0.013c-0.011,0.006-0.019,0.012-0.029,0.019c-0.004,0.003-0.01,0.003-0.015,0.006
+ c-0.005,0.003-0.011,0.009-0.016,0.012c-0.013,0.009-0.021,0.022-0.033,0.032c-0.007,0.006-0.015,0.017-0.023,0.021
+ c-0.004,0.002-0.009,0-0.014,0.002c-0.004,0.001-0.012,0.008-0.016,0.011c-0.013,0.009-0.025,0.022-0.039,0.03
+ c-0.009,0.004-0.018,0.008-0.026,0.013c-0.004,0.003-0.008,0.007-0.012,0.01c-0.009,0.006-0.008,0.007-0.013,0.015
+ c-0.012,0.018-0.013,0.04-0.019,0.061c-0.003,0.011,0,0.019-0.005,0.028c-0.005,0.011-0.014,0.02-0.022,0.028
+ c-0.012,0.011-0.023,0.024-0.035,0.035c-0.016,0.014-0.035,0.024-0.049,0.038c-0.006,0.006-0.007,0.011-0.011,0.017
+ c-0.005,0.006-0.013,0.009-0.019,0.013c-0.019,0.013-0.034,0.03-0.053,0.042c-0.017,0.011-0.029,0.023-0.042,0.037
+ c-0.013,0.013-0.028,0.019-0.043,0.029c-0.017,0.01-0.035,0.02-0.054,0.027c-0.01,0.004-0.017,0.003-0.027,0.005
+ c-0.012,0.002-0.021,0.016-0.033,0.021c-0.006,0.003-0.015,0.004-0.02,0.008c-0.008,0.006-0.016,0.013-0.024,0.019
+ c-0.015,0.011-0.032,0.024-0.049,0.033c-0.016,0.009-0.036,0.019-0.053,0.03c-0.014,0.009-0.028,0.02-0.044,0.027
+ c-0.015,0.006-0.029,0.005-0.044,0.013c-0.01,0.006-0.019,0.012-0.027,0.018c-0.017,0.012-0.036,0.023-0.055,0.033
+ c-0.027,0.015-0.052,0.037-0.081,0.05c-0.016,0.007-0.032,0.011-0.048,0.016c-0.014,0.005-0.027,0.017-0.043,0.016
+ c0.002-0.002,0.004-0.003,0.004-0.006l-0.011-0.006c-0.001,0.012-0.013,0.005-0.011-0.005c0.001-0.007,0.01-0.013,0.015-0.017
+ c0.013-0.014,0.03-0.021,0.045-0.033c0.008-0.007,0.018-0.014,0.026-0.021c0.007-0.006,0.014-0.008,0.023-0.012
+ c0.025-0.011,0.037-0.038,0.06-0.053c0.01-0.007,0.014-0.005,0.02-0.016c0.005-0.009,0.014-0.02,0.017-0.029
+ c0.006-0.018-0.006-0.036-0.021-0.046c-0.016-0.013-0.038-0.013-0.033-0.04c0.002-0.011,0.009-0.021,0.011-0.032
+ c0.003-0.011,0.009-0.032,0.005-0.042c-0.021,0.007-0.03,0.041-0.037,0.059c-0.013,0.034-0.034,0.067-0.066,0.086
+ c-0.019,0.012-0.038,0.023-0.06,0.032c-0.012,0.004-0.012,0.005-0.017,0.017c-0.004,0.01-0.001,0.03-0.009,0.033
+ c-0.008,0.003-0.025,0-0.033,0h-0.033c-0.01,0-0.023,0.002-0.033,0s-0.022-0.009-0.032-0.012c-0.015-0.005-0.029-0.013-0.044-0.016
+ c-0.01-0.001-0.022,0.001-0.033,0.001c-0.01,0-0.022-0.002-0.032,0.001c-0.007,0.001-0.009,0.005-0.017,0.004
+ c-0.008,0-0.009-0.004-0.016-0.006c-0.01-0.002-0.022,0.002-0.033,0.001c-0.017-0.003-0.007-0.008-0.01-0.022
+ c-0.001-0.003-0.004-0.01-0.006-0.011c-0.006-0.007-0.002-0.002-0.011-0.005c-0.011-0.005-0.02-0.007-0.033-0.002
+ c-0.009,0.004-0.018,0.007-0.027,0.012c-0.009,0.005-0.017,0.013-0.026,0.018c-0.005,0.003-0.012,0.003-0.016,0.005
+ c-0.003,0.002-0.005,0.006-0.008,0.009c-0.011,0.008-0.023,0.012-0.037,0.012c-0.016,0-0.049,0.006-0.064-0.001
+ c-0.021-0.011-0.008-0.04-0.012-0.059c-0.001-0.006-0.004-0.01-0.006-0.016c-0.001-0.005,0.001-0.011,0.001-0.017
+ c-0.001-0.01-0.006-0.015-0.006-0.027c0-0.021-0.011-0.041-0.016-0.059c-0.008-0.025-0.017-0.048-0.038-0.066
+ c-0.014-0.011-0.031-0.034-0.049-0.038c-0.005-0.002-0.011,0.001-0.016-0.001c-0.006-0.002-0.012-0.006-0.017-0.009
+ c-0.009-0.004-0.019-0.007-0.028-0.01c-0.011-0.004-0.02-0.007-0.032-0.007c-0.013,0-0.026,0.002-0.038-0.004
+ c-0.036-0.017-0.003-0.039,0.012-0.056c0.017-0.018,0.031-0.037,0.048-0.053c0.019-0.019,0.043-0.023,0.066-0.034
+ c0.019-0.01,0.032-0.027,0.05-0.037c0.019-0.011,0.036-0.025,0.051-0.041c0.008-0.008,0.016-0.017,0.022-0.026
+ c0.003-0.003,0.01-0.012,0.012-0.016c0.002-0.007,0-0.014,0.002-0.022c0.002-0.009,0.008-0.018,0.01-0.027
+ c0.002-0.012-0.002-0.016,0.006-0.027c0.014-0.019,0.043-0.032,0.064-0.042c0.022-0.011,0.041-0.028,0.051-0.051
+ c0.002-0.006,0.002-0.01,0.003-0.016c0.002-0.005,0.005-0.006,0.006-0.011c0.003-0.013-0.002-0.02,0.006-0.032
+ c0.011-0.014,0.024-0.019,0.038-0.029c0.01-0.008,0.019-0.016,0.028-0.025c0.008-0.006,0.013-0.012,0.022-0.017
+ c0.015-0.009,0.03-0.009,0.046-0.015c0.021-0.008,0.041-0.016,0.061-0.023c0.02-0.008,0.038-0.016,0.054-0.029
+ c0.01-0.007,0.02-0.013,0.029-0.02c0.01-0.008,0.02-0.016,0.031-0.021c0.017-0.007,0.055-0.009,0.055-0.034
+ c0-0.024-0.032-0.026-0.049-0.023c-0.023,0.005-0.048-0.003-0.071,0.003c-0.011,0.002-0.02,0.006-0.033,0.004
+ c-0.011-0.001-0.02-0.008-0.032-0.006c-0.006,0.001-0.011,0.005-0.016,0.007c-0.007,0.003-0.011,0.003-0.017,0.004
+ c-0.005,0.001-0.006,0.007-0.011,0.005c-0.003-0.001-0.008-0.009-0.01-0.012c0.003-0.005,0.008-0.015,0.009-0.021
+ c0.002-0.011-0.001-0.015-0.005-0.025c-0.005-0.01-0.002-0.017-0.004-0.028c-0.003-0.014-0.012-0.024-0.016-0.037
+ c-0.005-0.022,0.003-0.04,0.006-0.06c0.001-0.007,0.005-0.003,0.003-0.012c-0.001-0.007-0.006-0.007-0.01-0.012
+ c-0.009-0.011-0.014-0.022-0.018-0.036c0-0.002-0.003-0.009-0.004-0.012c-0.001-0.006-0.001-0.007-0.003-0.012
+ c-0.006-0.011-0.012-0.014-0.003-0.026c0.008-0.01,0.018-0.012,0.027-0.018c0.013-0.008,0.024-0.021,0.036-0.031
+ c0.015-0.015,0.034-0.027,0.048-0.043c0.02-0.024,0.042-0.05,0.068-0.066c0.018-0.011,0.034-0.02,0.051-0.031
+ c0.019-0.014,0.025-0.041,0.044-0.055c0.009-0.006,0.024-0.004,0.032-0.01c0.006-0.005,0.01-0.013,0.015-0.019
+ c0.018-0.022,0.038-0.043,0.057-0.063c0.007-0.008,0.014-0.016,0.02-0.024c0.009-0.012,0.021-0.021,0.029-0.032
+ c0.012-0.016,0.02-0.036,0.025-0.056c0.002-0.013,0.002-0.023,0.006-0.037c0.004-0.009,0.004-0.012,0.012-0.02
+ c0.006-0.005,0.011-0.011,0.016-0.017c0.016-0.017,0.037-0.028,0.052-0.044c0.01-0.01,0.02-0.018,0.03-0.028
+ c0.023-0.024,0.05-0.041,0.074-0.062c0.01-0.008,0.019-0.015,0.029-0.023c0.004-0.003,0.006-0.008,0.01-0.011
+ c0.007-0.005,0.015-0.007,0.022-0.012c0.024-0.018,0.048-0.035,0.071-0.053c0.019-0.015,0.037-0.027,0.06-0.036
+ c0.011-0.004,0.02-0.002,0.031-0.005c0.014-0.004,0.014-0.015,0.023,0.002c0.01,0.018,0.024,0.036,0.045,0.042
+ c0.01,0.003,0.02,0.002,0.031,0.006c0.014,0.006,0.024,0.006,0.038,0.006s0.025-0.005,0.039-0.006c0.005,0,0.011,0.001,0.016,0.001
+ c0.008-0.002,0.011-0.01,0.021-0.005c-0.003,0.009-0.015,0.01-0.022,0.016c-0.008,0.009-0.014,0.021-0.022,0.031
+ c-0.01,0.011-0.016,0.022-0.028,0.032c-0.013,0.011-0.027,0.013-0.026,0.034c0.001,0.01,0.004,0.021,0.013,0.026
+ c0.004,0.003,0.024,0.007,0.029,0.007c-0.007,0.004-0.012,0.012-0.008,0.021c0.006,0.01,0.023,0.006,0.033,0.006
+ c0.02,0,0.036-0.007,0.053-0.015c0.008-0.004,0.01-0.002,0.018-0.008c0.009-0.007,0.015-0.01,0.025-0.013
+ c0.018-0.006,0.036-0.014,0.054-0.017c0.016-0.003,0.034-0.001,0.05-0.001c0.014,0,0.031-0.004,0.044-0.001
+ c0.01,0.003,0.021,0.005,0.029,0.006c0.009-0.007,0.013-0.004,0.021,0.001c0.012,0.008,0.006,0.015,0.015,0.022
+ c0.016,0.014,0.034,0.001,0.045-0.011c0.008-0.008,0.016-0.017,0.026-0.023c0.016-0.01,0.036-0.003,0.054-0.009
+ c0.02-0.007,0.026-0.027,0.049-0.029c0.011-0.001,0.017,0.004,0.027,0.006c0.028,0.006,0.059-0.005,0.087-0.01
+ c0.024-0.004,0.049-0.008,0.072-0.019c0.018-0.009,0.034-0.016,0.054-0.022c0.022-0.007,0.025-0.001,0.041,0.012
+ c-0.007,0.011-0.02,0.004-0.03,0.006c-0.015,0.002-0.03,0.01-0.043,0.018c-0.011,0.007-0.021,0.009-0.034,0.013
+ c-0.014,0.005-0.027,0.012-0.041,0.018c0.011,0,0.024,0.002,0.035,0c0.018-0.003,0.038-0.01,0.056-0.015
+ c0.01-0.003,0.02-0.005,0.031-0.008c0.004-0.001,0.006-0.004,0.012-0.004c0.008-0.002,0.014-0.002,0.022-0.005
+ c0.012-0.003,0.025,0.001,0.038-0.002c0.013-0.003,0.024-0.004,0.038-0.004c0.011,0,0.022,0.001,0.033-0.001
+ c0.013-0.003,0.025-0.005,0.038-0.005c0.007,0,0.022,0.004,0.025-0.005c-0.003-0.011-0.016-0.013-0.025-0.011
+ c-0.006,0.001-0.011,0.006-0.017,0.006c-0.004,0-0.007-0.005-0.01-0.006c-0.008-0.003-0.021-0.002-0.016-0.015
+ c0.002-0.006,0.018-0.013,0.023-0.016c0.007-0.003,0.023-0.013,0.031-0.013c0.005,0,0.005,0.005,0.01,0.006s0.012,0,0.017,0
+ c0.015,0,0.033-0.002,0.047-0.007c0.007-0.002,0.012-0.006,0.017-0.008c0.009-0.004,0.019,0,0.028-0.002
+ c-0.001-0.007-0.012-0.014-0.017-0.019c0.023,0.018,0.06-0.013,0.072-0.029c0.009-0.014,0.017-0.019,0.032-0.026
+ c0.014-0.007,0.026-0.009,0.04-0.017c0.003-0.002,0.011-0.006,0.014-0.007c0.004,0.001,0.008,0.001,0.012,0.001
+ c0.003,0,0.005-0.002,0.006-0.005c0.007-0.001,0.014,0.001,0.021,0c0.012-0.003,0.008-0.008,0.011-0.017
+ c0.005-0.027,0.031-0.045,0.047-0.065c0.019-0.024,0.044-0.046,0.074-0.054c0.006-0.001,0.013-0.001,0.02-0.001
+ c0.008,0,0.012-0.003,0.018-0.004c0.006-0.002,0.013-0.001,0.019-0.002c0.006-0.002,0.011-0.009,0.017-0.01
+ c0.007-0.002,0.014,0.001,0.021-0.001c0.004-0.002,0.014-0.009,0.017-0.011c0.006-0.006,0.012-0.027,0.022-0.027
+ c0.006,0,0.008,0.007,0.012,0.01c0.004,0.003,0.011,0.005,0.016,0.007c0.011,0.006,0.024,0.01,0.037,0.012
+ c0,0.008,0.006,0.013,0.006,0.02c0.002,0.011,0.001,0.018,0.006,0.028c0,0.003,0.001,0.005,0.004,0.007
+ c0.001,0.004-0.001,0.01,0.002,0.014c0.003,0.004,0.017,0.011,0.022,0.016c0.006,0.005,0.014,0.006,0.019,0.013
+ c0.001,0.003,0.002,0.006,0.003,0.009c0.002,0.003,0.006,0.004,0.008,0.007c0.005,0.006,0.004,0.015,0.007,0.021
+ c0.005,0.013,0.011,0.011,0.025,0.011c-0.001,0.013,0.039,0.002,0.046,0c0.009-0.004,0.018-0.004,0.027-0.011
+ c0.012-0.008,0.019-0.022,0.033-0.029c0.02-0.01,0.044-0.002,0.065,0.002s0.03-0.022,0.053-0.015
+ c0.007,0.002,0.015,0.007,0.018,0.014c0.002,0.007-0.001,0.016,0.001,0.023c0.002,0.006,0.004,0.009,0.004,0.016
+ c0,0.023-0.016,0.038-0.005,0.06c0.005,0.012,0.005,0.014-0.002,0.027c-0.006,0.011-0.009,0.014-0.009,0.027
+ s0.003,0.021,0.005,0.033c0.002,0.006,0.002,0.023-0.003,0.028c0.013,0.001,0.026-0.023,0.029-0.034
+ c0.005-0.016,0.013-0.037,0.012-0.054c0-0.017,0.007-0.03,0.018-0.042c0.004-0.005,0.01-0.009,0.014-0.014
+ c0-0.003,0.001-0.005,0.002-0.008c0.002-0.002,0.012-0.004,0.015-0.005c0.007-0.003,0.01-0.006,0.015-0.009
+ c0.008-0.004,0.014-0.003,0.023-0.003c0.012-0.001,0.02-0.001,0.028,0.01c0.003,0.005,0.01,0.016,0.011,0.022
+ c0.002,0.009-0.005,0.018-0.006,0.027c-0.001,0.009,0,0.018,0,0.028c0,0.021-0.005,0.047-0.015,0.066
+ c-0.008,0.015-0.015,0.026-0.019,0.042c-0.002,0.011-0.006,0.016-0.003,0.027c0.003,0.01,0.003,0.018,0.004,0.028
+ c0.001,0.007,0.004,0.009,0.006,0.016c0.002,0.008,0,0.019,0,0.027c0.001,0.022,0.011,0.034,0.027,0.048
+ c0.013,0.011,0.026,0.022,0.038,0.033c0.007,0.005,0.011,0.01,0.016,0.017c0.006,0.008,0.008,0.014,0.012,0.022
+ c0.008,0.012,0.025,0.021,0.031,0.034c0.005,0.01-0.003,0.012-0.005,0.02c-0.002,0.008-0.001,0.013-0.004,0.021
+ c0.006,0.004,0.017,0.021,0.016,0.029c-0.001,0.006-0.007,0.006-0.012,0.01c-0.004,0.004-0.007,0.011-0.01,0.017
+ c-0.012,0.019-0.024,0.043-0.045,0.053c-0.005,0.002-0.011,0.001-0.016,0.005c-0.005,0.004-0.002,0.006-0.006,0.011
+ c-0.007,0.01-0.01,0.015-0.01,0.028c0.001,0.012,0.007,0.018,0.011,0.027c0.006,0.012,0.005,0.024,0.01,0.035
+ c0.008,0.017,0.024,0.032,0.019,0.053c-0.002,0.01-0.013,0.02-0.006,0.031c0.007,0.01,0.022,0.008,0.032,0.006
+ c0.012-0.003,0.022-0.01,0.032-0.017c0.011-0.008,0.021-0.01,0.033-0.015c0.025-0.009,0.051-0.011,0.076-0.017
+ c0.025-0.006,0.047-0.015,0.071-0.022c0.026-0.008,0.044-0.026,0.064-0.044c0.006-0.005,0.019-0.011,0.022-0.016
+ c0.004-0.005,0.002-0.025,0-0.03c-0.003-0.01-0.014-0.017-0.016-0.029c-0.001-0.013,0.012-0.018,0.022-0.023
+ c0.013-0.006,0.018-0.002,0.029,0c0.009,0.002,0.022,0.003,0.032,0.002c0.009-0.001,0.014-0.006,0.023-0.01
+ c0.011-0.006,0.018-0.012,0.02,0.004c0.002,0.015-0.002,0.021-0.01,0.033c-0.006,0.009-0.006,0.018-0.013,0.026
+ c-0.016,0.018-0.025,0.025-0.015,0.05c0.003,0.009,0.003,0.03,0.01,0.033c0.014,0.006,0.035-0.004,0.045-0.012
+ c0.017-0.015,0.024-0.052,0.048-0.059c0.005-0.002,0.011,0.001,0.017,0c0.008-0.001,0.011-0.006,0.017-0.01
+ c0.005-0.002,0.011-0.004,0.015-0.006c0.005-0.003,0.008-0.007,0.012-0.01c0.006-0.004,0.014-0.003,0.02-0.008
+ c0.004-0.003,0.007-0.007,0.01-0.011c0.008-0.008,0.019-0.013,0.027-0.02c-0.006,0.01-0.004,0.021-0.01,0.032
+ C-20.058,57.907-20.066,57.918-20.079,57.926z M-20.496,56.942c-0.016,0.005-0.024,0.014-0.022,0.033l-0.011-0.017
+ c-0.018,0.024-0.04-0.035-0.048-0.043c-0.006-0.005-0.01-0.004-0.012-0.011c-0.002-0.007,0.001-0.013,0.004-0.018
+ c0.008-0.011,0.022-0.015,0.034-0.019c0.03-0.009,0.049-0.033,0.077-0.045c0.011-0.005,0.025-0.008,0.037-0.011
+ c0.006,0.001,0.01,0.001,0.015-0.001c0.007-0.003,0.014-0.006,0.021-0.009c0.01-0.002,0.019-0.004,0.029-0.008
+ c0.012-0.004,0.023-0.004,0.034,0.007c0.014,0.014,0.011,0.028,0.005,0.044c-0.01,0.026-0.034,0.038-0.056,0.053
+ c-0.008,0.005-0.016,0.013-0.025,0.018c-0.013,0.006-0.03,0.01-0.044,0.015C-20.471,56.934-20.484,56.938-20.496,56.942z
+ M-20.562,57.008c0.01,0.008,0.021,0.015,0.033,0.021c0.015,0.007,0.025,0.007,0.034,0.022c0.015,0.022,0.011,0.039,0,0.061
+ c-0.003,0.006-0.004,0.013-0.008,0.019c-0.005,0.009-0.014,0.015-0.02,0.023c-0.008,0.014-0.006,0.029-0.018,0.042
+ c-0.016,0.018-0.038,0.034-0.064,0.028c-0.019-0.005-0.028-0.016-0.042-0.028c-0.007-0.005-0.016-0.01-0.022-0.016
+ c-0.002-0.002-0.004-0.004-0.005-0.006c-0.005-0.002-0.009-0.001-0.013-0.003c-0.008-0.004-0.035-0.013-0.038-0.024v0.013
+ c-0.005-0.017-0.059-0.055-0.053-0.07c0.002-0.002,0.004-0.003,0.006-0.005c0.002-0.004,0.003-0.008,0.005-0.011
+ c0.005-0.008,0.008-0.014,0.011-0.023c0.003-0.009,0.006-0.017,0.004-0.027c-0.002-0.012-0.008-0.019-0.004-0.032
+ c0.007-0.023,0.026-0.02,0.045-0.024c0.011-0.002,0.018-0.005,0.03-0.004c0.012,0,0.021-0.007,0.033-0.006
+ c0.015,0.001,0.025,0.015,0.037,0.023c0.009,0.006,0.018,0.007,0.027,0.011C-20.577,56.996-20.569,57.003-20.562,57.008z
+ M-20.741,57.17c0.003,0.011,0.002,0.033-0.001,0.043c-0.021,0.002-0.04,0.018-0.063,0.011l-0.001-0.005l-0.006-0.005
+ c0.001,0.004,0.001,0.007-0.001,0.01c-0.007-0.001-0.018-0.015-0.026-0.019c-0.008-0.005-0.013-0.007-0.022-0.007
+ c-0.003,0-0.007,0-0.011,0c-0.005,0-0.008,0.003-0.012,0.004c-0.007,0.001-0.013,0.001-0.02,0.001c-0.012,0-0.012-0.001-0.021-0.007
+ c-0.011-0.007-0.022-0.014-0.033-0.021c0-0.023,0.06-0.031,0.075-0.032c0.005,0,0.012-0.001,0.017,0
+ c0.007,0.002,0.008,0.007,0.016,0.006c0.004,0,0.007-0.004,0.011-0.006c0.005-0.002,0.011-0.003,0.016-0.005
+ c0.013-0.006,0.024-0.012,0.037-0.01c0.01,0.002,0.019,0.009,0.028,0.015C-20.748,57.15-20.743,57.159-20.741,57.17z
+ M-24.165,56.011c-0.325-0.11-1.03,0.279-0.976-0.33c0.055-0.609,0.057-0.298,0.086-0.447c0.107-0.033,0.215-0.065,0.322-0.098
+ c-0.002-0.002-0.005-0.003-0.007-0.007c-0.002-0.006,0-0.011-0.001-0.018c-0.001-0.007-0.003-0.013-0.003-0.021
+ c0-0.011,0.004-0.022,0.005-0.033c0.001-0.006-0.001-0.009-0.003-0.013c-0.001-0.006,0.001-0.013,0.002-0.018
+ c0.002-0.005,0.003-0.011,0.004-0.016c0-0.005-0.001-0.01,0-0.015c0-0.006,0.005-0.01,0.007-0.015
+ c0.002-0.007,0.002-0.012,0.003-0.019c0.001-0.009,0.008-0.014,0.012-0.023c0.003-0.004,0.004-0.012,0.008-0.015
+ c0.003-0.003,0.005-0.002,0.008-0.003c0.004-0.002,0.007-0.007,0.01-0.01c0.003-0.003,0.004-0.008,0.007-0.011
+ c0.004-0.003,0.008-0.004,0.012-0.006c0.01-0.006,0.015-0.019,0.025-0.023c0.013-0.006,0.025-0.015,0.036-0.024
+ c0.006-0.005,0.011-0.009,0.018-0.013c0.005-0.002,0.006-0.002,0.01-0.002c0.006-0.001,0.01-0.005,0.015-0.008
+ c0.003-0.001,0.007-0.003,0.01-0.005c0.003-0.001,0.006-0.004,0.009-0.005s0.005,0,0.008,0c0.004-0.001,0.007-0.002,0.01-0.003
+ c0.005-0.001,0.007,0,0.012-0.002c0.002-0.002,0.005-0.005,0.008-0.006c0.002-0.002,0.003-0.004,0.005-0.005
+ c0.005-0.003,0.006-0.002,0.011-0.002c0.005-0.001,0.013-0.005,0.018-0.008c0.007-0.004,0.014-0.009,0.02-0.013
+ c0.005-0.003,0.01-0.006,0.016-0.008c0.005-0.002,0.012-0.002,0.018-0.005c0.005-0.004,0.012-0.007,0.018-0.011
+ c0.005-0.003,0.008-0.006,0.015-0.007c0.012-0.001,0.028-0.002,0.039,0.003c0.006,0.004,0.009,0.006,0.012,0.013
+ c0.002,0.004,0.004,0.006,0.006,0.01c0.002,0.003,0.001,0.005,0.002,0.008c0.001,0.002,0.002,0.002,0.003,0.005
+ c0.001,0.005-0.001,0.012-0.001,0.018c0.001,0.005-0.002,0.008-0.002,0.013c0,0.006,0.005,0.008,0.01,0.009
+ c0.007,0.002,0.011,0.005,0.014,0.011c0.001,0.003,0.001,0.005,0.002,0.008c0,0.003,0.004,0.007,0.004,0.01
+ c0.001,0.004-0.002,0.008-0.002,0.013c0,0.005,0,0.008,0.003,0.013c0.005,0.01,0.013,0.018,0.022,0.026
+ c0.003,0.002,0.002,0.004,0.006,0.005c0.004,0,0.006-0.001,0.008-0.003c0.007-0.006,0.011-0.018,0.012-0.026
+ c0.001-0.006-0.004-0.014-0.007-0.019c-0.004-0.008-0.009-0.016-0.014-0.024c-0.005-0.008-0.013-0.016-0.014-0.026
+ c-0.001-0.01,0.008-0.019,0.01-0.028c0.001-0.007-0.001-0.012,0.008-0.013c0.005-0.001,0.01,0.001,0.015-0.001
+ c0.005-0.001,0.009-0.004,0.013-0.006c0.004-0.003,0.007-0.007,0.011-0.011c0.003,0.003,0.008,0.004,0.012,0.005
+ c0.005,0.002,0.01,0.005,0.016,0.006c0.008,0.002,0.015,0,0.023-0.003c0.007-0.002,0.01-0.005,0.018-0.005
+ c0.017,0,0.033,0,0.049-0.008c0.013-0.007,0.026-0.008,0.04-0.008c0.007,0,0.012-0.003,0.018-0.005
+ c0.006-0.001,0.01-0.001,0.016-0.003c0.005-0.002,0.009-0.005,0.015-0.005c0.007,0,0.014,0,0.021,0c0.005,0,0.012,0.002,0.018,0
+ c0.004-0.001,0.009-0.004,0.013-0.004c0.008-0.002,0.024,0,0.03,0.007c0.001,0.002,0,0.005,0,0.008c0,0.002,0.002,0.003,0.002,0.005
+ c0.001,0.005,0.001,0.01,0.001,0.015s0.001,0.01-0.001,0.015c0,0.003-0.002,0.006-0.002,0.008c-0.002,0.004-0.001,0.007-0.002,0.011
+ c-0.001,0.004-0.006,0.006-0.006,0.012c0,0.006,0.004,0.011,0.008,0.014c0.01,0.007,0.021,0.008,0.031,0.012
+ c0.009,0.003,0.021,0.004,0.031,0.003c0.021-0.002,0.017-0.023,0.03-0.032c0.008-0.006,0.019-0.007,0.027-0.012
+ c0.004-0.003,0.009-0.007,0.012-0.011c0.004-0.006,0.005-0.01,0.012-0.009c0.001,0.001,0.002,0.002,0.004,0.002
+ c0.003,0,0.003-0.001,0.005-0.002c0.005-0.002,0.009-0.005,0.013-0.008c0.004-0.004,0.005-0.009,0.01-0.013
+ c0.005-0.004,0.007-0.002,0.013-0.003c0.011-0.002,0.019-0.01,0.031-0.01c0.003,0,0.01,0.001,0.007-0.005
+ c-0.001-0.001-0.006-0.004-0.007-0.005c-0.003-0.003-0.005-0.005-0.008-0.006c0.001-0.006,0.008-0.004,0.012-0.004
+ c0.008-0.002,0.014-0.003,0.021-0.003c0.012,0,0.027,0.002,0.039,0c0.011-0.002,0.02-0.014,0.025-0.023
+ c0.003-0.005,0.004-0.009,0.009-0.013s0.011-0.005,0.017-0.005c0.01,0,0.018,0.004,0.028,0.005c0.005,0,0.009,0.002,0.014,0.004
+ c0.006,0.004,0.01,0.003,0.017,0.003c0.005,0.001,0.003,0,0.005,0.003c0.001,0.001,0.003,0.006,0.004,0.008
+ c0.003,0.006,0.006,0.012,0.007,0.018c0.002,0.008,0.001,0.016,0.002,0.024c0.001,0.007,0.003,0.012,0.003,0.02
+ c0,0.004-0.001,0.01,0,0.014c0,0.001,0.001,0.001,0.001,0.002c0.099-0.03-0.143,0.256,0.297-0.09s0.336,0.817-0.066,1.025
+ C-23.693,55.914-23.874,55.909-24.165,56.011z M-24.181,56.017c0.005-0.002,0.011-0.004,0.016-0.006
+ c0.017,0.006,0.034,0.012,0.051,0.018L-24.181,56.017z M-24.607,55.943l0.426,0.074c-0.05,0.017-0.099,0.035-0.148,0.052
+ C-24.422,56.027-24.515,55.985-24.607,55.943z M-33.449,52.473c0.015,0.018,0.025,0.032,0.022,0.057
+ c-0.003,0.021-0.001,0.046-0.026,0.05c-0.001-0.002-0.003-0.003-0.006-0.003h-0.005c-0.002-0.02-0.023-0.04-0.026-0.063
+ c-0.002-0.014,0.012-0.061,0.033-0.037C-33.455,52.475-33.452,52.475-33.449,52.473z M-27.922,53.12L-27.922,53.12L-27.922,53.12
+ L-27.922,53.12z M-25.706,56.522c-0.279,0.349-0.558,0.698-0.837,1.047c-0.001,0.012,0.003,0.025-0.001,0.036
+ c-0.004,0.013-0.015,0.02-0.02,0.032c-0.01,0.022-0.003,0.045-0.005,0.068c-0.003,0.021-0.017,0.037-0.022,0.058
+ c-0.007,0.022-0.001,0.045-0.004,0.068c-0.002,0.011-0.009,0.02-0.011,0.031c-0.002,0.012,0.001,0.025,0,0.037
+ c-0.002,0.019-0.012,0.037-0.015,0.058c-0.002,0.022,0.005,0.042,0.01,0.063c0.006,0.027,0.006,0.093,0.041,0.1
+ c0.004,0.025,0.002,0.056-0.031,0.057c-0.012,0.001-0.021-0.008-0.035-0.004c-0.014,0.003-0.024,0.019-0.038,0.02
+ c-0.024,0.002-0.036-0.029-0.058-0.027c-0.003,0-0.002,0.003-0.003,0.003c-0.019,0.067-0.038,0.134-0.057,0.201
+ c0.006,0.01,0.013,0.019,0.014,0.026c0.004,0.015-0.002,0.032-0.017,0.044c-0.005,0.005-0.012-0.001-0.017,0.002
+ c-0.092,0.323-0.183,0.646-0.274,0.969c0.091,0.148,0.182,0.296,0.274,0.444c-0.291-0.117-0.581-0.234-0.872-0.352
+ c0.122,0.03,0.231,0.045,0.318,0.038l-1.097-0.379c0,0.006-0.007,0.011-0.004,0.022c0,0.001,0.001,0.003,0.002,0.004
+ c0.26,0.105,0.521,0.21,0.781,0.315c-0.216-0.052-0.483-0.162-0.78-0.312c0.002,0.006,0.004,0.014,0.007,0.019
+ c0.008,0.01,0.009,0.005,0.02,0.011c0.018,0.01,0.024,0.026,0.038,0.04c0.028,0.03,0.071,0.05,0.105,0.068
+ c0.035,0.019,0.074,0.04,0.111,0.053c0.02,0.006,0.043,0.012,0.062,0.016c0.016,0.004,0.037,0.006,0.052,0.012
+ c0.021,0.009,0.039,0.025,0.059,0.034c0.018,0.009,0.029,0.022,0.046,0.032c0.018,0.01,0.04,0.019,0.059,0.026
+ c0.026,0.011,0.024,0.011,0.039,0.037c0.014,0.024,0.041,0.018,0.065,0.027c0.024,0.01,0.041,0.034,0.058,0.053
+ c0.022,0.026,0.06,0.041,0.094,0.052c0.018,0.006,0.036,0.008,0.053,0.015c0.019,0.007,0.037,0.02,0.052,0.033
+ c0.016,0.015,0.027,0.03,0.047,0.041c0.019,0.011,0.033,0.017,0.048,0.032c0.011,0.01,0.026,0.027,0.039,0.033
+ c0.016,0.006,0.037,0.009,0.054,0.014c0.022,0.006,0.045,0.001,0.067,0.006c0.008,0.002,0.019,0.007,0.028,0.009
+ c0.011,0.003,0.02,0.004,0.031,0.006c0.022,0.003,0.041,0.007,0.063,0.01c0.014,0.002,0.04,0.013,0.036,0.031
+ c-0.022-0.001-0.039,0.007-0.062,0.002c-0.019-0.004-0.036-0.005-0.057-0.008c-0.016-0.001-0.037-0.005-0.053-0.009
+ c-0.021-0.004-0.043-0.002-0.063-0.006c-0.019-0.005-0.034-0.016-0.053-0.02c-0.025-0.006-0.051-0.002-0.076-0.012
+ c-0.017-0.007-0.033-0.021-0.049-0.025c-0.018-0.004-0.04,0.002-0.058,0c-0.042-0.003-0.08-0.034-0.121-0.047
+ c-0.019-0.006-0.035-0.012-0.053-0.012c-0.02,0.001-0.037,0.004-0.053-0.008c-0.006-0.004-0.01-0.011-0.015-0.016
+ c-0.019-0.019-0.029-0.02-0.055-0.023c-0.019-0.002-0.045,0-0.064-0.005c-0.016-0.005-0.034-0.015-0.05-0.021
+ c-0.011-0.003-0.024-0.013-0.034-0.015c-0.008-0.002-0.019,0.004-0.026-0.001c-0.026-0.015-0.012-0.051-0.048-0.057
+ c-0.031-0.004-0.031-0.006-0.051-0.031c-0.025-0.03-0.056-0.057-0.091-0.074c-0.024-0.012-0.049-0.022-0.073-0.035
+ c-0.009-0.004-0.016-0.013-0.026-0.016c-0.011-0.002-0.022,0.002-0.032-0.002c-0.013-0.004-0.022-0.012-0.035-0.016
+ c-0.008-0.001-0.017-0.004-0.024-0.007c-0.01-0.005-0.014-0.01-0.025-0.012c-0.011-0.003-0.019-0.003-0.031-0.007
+ c-0.046-0.017-0.088-0.037-0.136-0.047c-0.032-0.006-0.06-0.024-0.09-0.034c-0.019-0.007-0.03-0.006-0.047-0.018
+ c-0.019-0.014-0.036-0.026-0.052-0.041c-0.017-0.017-0.039-0.023-0.058-0.037s-0.029-0.038-0.046-0.056
+ c-0.019-0.021-0.044-0.041-0.069-0.054c-0.029-0.016-0.058-0.041-0.089-0.051c-0.031-0.01-0.052-0.018-0.079-0.033
+ c-0.031-0.017-0.065-0.026-0.096-0.043c-0.013-0.008-0.027-0.013-0.04-0.021c-0.013-0.008-0.018-0.016-0.028-0.025
+ c-0.019-0.018-0.041-0.033-0.06-0.052c-0.019-0.019-0.043-0.015-0.065-0.031c-0.013-0.009-0.016-0.029-0.027-0.037
+ c-0.005-0.004-0.023-0.004-0.03-0.006c-0.02-0.006-0.035-0.01-0.041-0.031c-0.006-0.021-0.007-0.043-0.011-0.063
+ c-0.004-0.019,0-0.032,0.005-0.053c0.006-0.029,0.005-0.038-0.01-0.062c-0.012-0.021-0.01-0.029-0.038-0.032
+ c-0.028-0.003-0.03,0.016-0.053,0.025c-0.018,0.008-0.051,0.003-0.057-0.014c-0.004-0.012,0.005-0.023-0.003-0.033
+ c-0.006-0.008-0.019-0.011-0.027-0.016c-0.013-0.009-0.021-0.035-0.037-0.036c-0.002-0.013-0.002-0.021-0.005-0.03
+ c-0.021-0.006-0.049-0.03-0.066-0.043c-0.044-0.033-0.081-0.077-0.119-0.115c-0.017-0.017-0.032-0.033-0.048-0.052
+ c-0.012-0.015-0.028-0.02-0.042-0.032c-0.034-0.028-0.047-0.063-0.068-0.1c-0.012-0.021-0.04-0.029-0.05-0.051
+ c-0.001-0.002,0-0.004-0.001-0.007c-0.446-0.335-0.894-0.711-1.316-1.103c0.004,0.013,0.005,0.026,0.012,0.039
+ c0.005,0.008,0.01,0.012,0.014,0.021c0.007,0.017,0.009,0.036,0.016,0.053c0.018,0.046,0.042,0.088,0.053,0.136
+ c0.007,0.026,0.015,0.047,0.029,0.069c0.009,0.012,0.033,0.059,0.028,0.067l-0.011-0.005c-0.024-0.008-0.045-0.037-0.067-0.052
+ c-0.018-0.011-0.025-0.06-0.037-0.085c-0.013-0.027-0.034-0.048-0.05-0.074c-0.014-0.021-0.023-0.052-0.055-0.052
+ c0.003-0.024-0.014-0.031-0.027-0.047c-0.012-0.016-0.023-0.031-0.036-0.046c-0.027-0.029-0.05-0.068-0.065-0.104
+ c-0.004-0.01,0-0.02-0.003-0.032c-0.003-0.01-0.011-0.017-0.016-0.027c-0.007-0.014-0.009-0.032-0.017-0.047
+ c-0.006-0.011-0.021-0.019-0.026-0.031c-0.004-0.013-0.001-0.019-0.008-0.032c-0.002-0.003-0.004-0.006-0.006-0.008
+ c-1.279-1.251-2.211-2.634-1.778-3.512c0.001-0.001,0.002-0.001,0.004-0.002c-0.003-0.011-0.007-0.023-0.011-0.037
+ c-0.017-0.067,0.002-0.076,0.068-0.074c0.017,0.021,0.042,0.044,0.046,0.072c0.086-0.033,0.173-0.067,0.259-0.1
+ c-0.061-0.188-0.123-0.376-0.184-0.564c-0.002-0.002-0.005-0.002-0.009-0.006c0.002,0.002-0.034-0.046-0.033-0.041
+ c-0.001-0.001-0.002-0.002-0.003-0.002c0.006-0.01,0.011-0.023,0.018-0.035c-0.015-0.046-0.03-0.092-0.045-0.138
+ c-0.008-0.005-0.018-0.008-0.042-0.015c-0.043-0.01-0.053-0.017-0.072-0.058c-0.048-0.106-0.079-0.23-0.112-0.343
+ c0.001,0.002,0.002,0.003,0.005,0.002c0.008-0.042-0.03-0.102-0.042-0.14c-0.015-0.053-0.027-0.105-0.042-0.158
+ c-0.019-0.062-0.091-0.233-0.022-0.289c-0.041-0.125-0.082-0.25-0.123-0.375c0.346-0.283,0.692-0.566,1.038-0.85
+ c-0.001-0.001-0.002-0.002-0.003-0.003c-0.003-0.005-0.003-0.008-0.004-0.013c-0.001-0.003-0.003-0.004-0.003-0.008
+ c0-0.003,0-0.006,0-0.008c0-0.003-0.002-0.006-0.003-0.008c-0.001-0.006,0.003-0.011,0.003-0.016c0-0.007-0.001-0.008-0.003-0.013
+ c-0.003-0.009-0.009-0.015-0.013-0.023c-0.004-0.008-0.004-0.019-0.008-0.029c-0.003-0.006-0.006-0.011-0.011-0.016
+ c-0.005-0.006-0.007-0.014-0.011-0.019c0.003-0.004,0.01-0.003,0.011-0.008c0-0.007-0.017-0.004-0.021-0.004
+ c-0.018,0-0.034,0.005-0.052,0.004c-0.014-0.001-0.026-0.008-0.039-0.011c-0.016-0.004-0.03,0.009-0.045,0.011
+ c-0.006,0.001-0.013-0.002-0.02,0.001c-0.008,0.003-0.009,0.008-0.019,0.007c-0.014-0.001-0.023-0.008-0.036,0
+ c-0.006,0.003-0.011,0.008-0.017,0.012c-0.01,0.006-0.021,0.008-0.031,0.013c-0.017,0.007-0.035,0.009-0.052,0.014
+ c-0.034,0.008-0.062,0.022-0.093,0.037c-0.014,0.007-0.032,0.009-0.048,0.014c-0.016,0.006-0.031,0.016-0.047,0.023
+ c-0.012,0.005-0.026,0.012-0.039,0.015c-0.004,0-0.008,0-0.012-0.001c-0.005,0.001-0.007,0.002-0.012,0.003
+ c-0.007,0.002-0.014,0.005-0.02,0.006c-0.013,0.002-0.029-0.005-0.04,0.002c-0.006,0.004-0.008,0.01-0.016,0.013
+ c-0.007,0.001-0.017,0.002-0.023,0c0-0.007-0.005-0.008-0.008-0.012c-0.004-0.004-0.004-0.006-0.005-0.011
+ c-0.002-0.009-0.005-0.017-0.004-0.026c-0.001-0.007-0.002-0.015,0-0.022c0.002-0.016,0.015-0.028,0.023-0.041
+ c0.007-0.012,0.006-0.026,0.014-0.038c0.005-0.009,0.006-0.017,0.01-0.026c0.003-0.005,0.01-0.01,0.012-0.015
+ c0.002-0.004,0-0.008,0.002-0.011c0.006-0.012,0.019-0.021,0.027-0.033c0.004-0.007,0.005-0.016,0.008-0.024
+ c0.002-0.006,0.004-0.013,0.004-0.02v-0.02c0-0.003,0.001-0.009,0-0.012c-0.001-0.003-0.004-0.004-0.005-0.008
+ c-0.003-0.014,0.007-0.032-0.004-0.044c-0.006-0.005-0.012-0.005-0.018-0.008c-0.008-0.004-0.014-0.01-0.021-0.015
+ c-0.014-0.007-0.023-0.005-0.036,0.003c-0.006,0.003-0.01,0.007-0.012-0.003c-0.002-0.007,0-0.017,0-0.024
+ c0-0.009-0.005-0.013-0.004-0.021c0-0.005,0.006-0.01,0.009-0.014c0.006-0.01,0.012-0.02,0.023-0.026
+ c0.011-0.007,0.019-0.015,0.025-0.027c0.003-0.007,0.006-0.014,0.01-0.02c0.001-0.003,0.003-0.006,0.005-0.01
+ c0.001-0.002,0.001-0.005,0.002-0.008c0.001-0.006,0.004-0.01,0.006-0.016c0.001-0.003,0.002-0.004,0.003-0.007
+ c0.001-0.006,0-0.013,0.008-0.014l0,0c0.005-0.008,0.011-0.015,0.016-0.023c0.001-0.002,0.002-0.004,0.004-0.006
+ c0.003-0.006,0.006-0.009,0.01-0.013c0.005-0.006,0.009-0.012,0.013-0.018c0.004-0.007,0.005-0.016,0.009-0.023
+ c0.004-0.008,0.012-0.014,0.015-0.024c0.006-0.02-0.003-0.035-0.023-0.023c-0.016,0.009-0.031,0.021-0.048,0.03
+ c-0.025,0.012-0.013-0.02-0.004-0.03c0.013-0.015,0.03-0.023,0.042-0.039c0.012-0.014,0.015-0.035,0.029-0.047
+ c0.012-0.01,0.033-0.022,0.047-0.024c0.021-0.004,0.035-0.013,0.053-0.023c0.01-0.006,0.027-0.012,0.038-0.015
+ c0.006-0.001,0.01,0,0.015-0.001c0.005,0,0.007-0.003,0.01-0.003c0.011-0.002,0.014-0.002,0.023-0.007
+ c0.01-0.006,0.019-0.012,0.029-0.017c0.018-0.012,0.04-0.021,0.052-0.04c0.005-0.006,0.007-0.016,0.014-0.02
+ c0.007-0.005,0.016-0.006,0.023-0.009c0.018-0.009,0.024-0.022,0.035-0.037c0.003-0.005,0.01-0.016,0.015-0.019
+ c0.013-0.005,0.015,0.009,0.013,0.018c-0.003,0.009-0.013,0.02-0.018,0.029c-0.006,0.01-0.01,0.019-0.015,0.029
+ c-0.006,0.015-0.011,0.028-0.02,0.043c-0.006,0.009-0.01,0.019-0.017,0.029c-0.005,0.007-0.01,0.011-0.014,0.019
+ c-0.003,0.006-0.011,0.017-0.011,0.024c-0.001,0.01,0.007,0.012,0.004,0.022c-0.002,0.008-0.01,0.017-0.018,0.02
+ c-0.005,0.001-0.009,0-0.015,0.001c-0.004,0-0.007,0.003-0.01,0.003c-0.008,0.002-0.017-0.001-0.022,0.007
+ c-0.003,0.005,0.001,0.018,0.005,0.022c0.003,0.003,0.008,0.003,0.012,0.007c0.003,0.003,0.005,0.004,0.006,0.008
+ c0.001,0.004-0.001,0.01,0,0.015c0.001,0.006,0.001,0.005,0.003,0.01c0.004,0.008,0.009,0.014,0.013,0.022
+ c0.002,0.005,0.003,0.004,0.003,0.011c0,0.006-0.003,0.006-0.004,0.01c-0.001,0.006-0.001,0.012,0,0.018
+ c0.005,0.002,0.01,0.003,0.016,0.003c0.009,0.001,0.019-0.002,0.028,0c0.008,0.002,0.01,0.006,0.02,0.003
+ c0.008-0.002,0.015-0.002,0.024-0.003c0.003,0,0.009-0.003,0.012-0.003c0.004,0,0.003,0.003,0.007,0.003
+ c0.008,0.002,0.014,0.001,0.021,0.007c0.01,0.009,0.001,0.033-0.001,0.045c-0.005,0.018-0.001,0.035,0.007,0.052
+ c0.013,0.026,0.064-0.008,0.072-0.025c0.002-0.004,0.001-0.005,0.005-0.008c0.003-0.003,0.008-0.001,0.011-0.004
+ c0.007-0.005,0.011-0.015,0.009-0.023c-0.001-0.009-0.022-0.045-0.001-0.045c0.001-0.005,0.004-0.009,0.006-0.014
+ c0.003-0.005,0.003-0.01,0.004-0.016c0.001-0.005,0.007-0.023,0.014-0.024c0.004,0.007,0.014-0.001,0.017-0.004
+ c0.005-0.005,0.007-0.019,0.016-0.017c0.009,0.002,0.011,0.017,0.013,0.023c0.001,0.006,0.004,0.006,0.002,0.013
+ c-0.001,0.006-0.002,0.011-0.004,0.016c-0.003,0.012-0.003,0.023-0.003,0.036c0,0.011,0.001,0.02-0.003,0.029
+ c-0.008,0.017-0.007,0.035,0.003,0.051c0.009,0.014,0.024,0.027,0.037,0.039c0.008,0.007,0.016,0.005,0.026,0.005
+ c0.016,0,0.034,0,0.048-0.009c0.015-0.01,0.028-0.021,0.044-0.028c0.019-0.008,0.049-0.011,0.066-0.03
+ c0.008,0.003,0.009,0.003,0.018,0c0.009-0.003,0.016-0.005,0.023-0.01c0.006-0.003,0.011-0.01,0.017-0.011
+ c0.004-0.001,0.009,0.001,0.012,0c0.005-0.001,0.007-0.003,0.012-0.004c0.008-0.001,0.016,0.001,0.023-0.001
+ c0.009-0.002,0.019-0.006,0.029-0.007c0.007,0,0.015-0.001,0.02,0.004c0.008,0.007,0.001,0.014,0.004,0.024
+ c0.004,0.015,0.022,0.018,0.013,0.036c-0.011,0.021-0.032,0.027-0.049,0.044c-0.014,0.013-0.026,0.028-0.039,0.041
+ c-0.011,0.011-0.025,0.017-0.036,0.028c-0.008,0.007-0.02,0.016-0.025,0.026c-0.003,0.008-0.003,0.013-0.009,0.019
+ c-0.006,0.007-0.013,0.009-0.019,0.017c-0.007,0.012-0.017,0.02-0.027,0.029c-0.008,0.007-0.014,0.013-0.021,0.02
+ c-0.004,0.005-0.009,0.007-0.013,0.011c-0.002,0.004-0.004,0.009-0.006,0.012c-0.009,0.012-0.02,0.02-0.031,0.029
+ c-0.005,0.004-0.011,0.009-0.014,0.014c-0.005,0.006-0.007,0.014-0.011,0.021c-0.005,0.007-0.002,0.012-0.004,0.02
+ c-0.002,0.007-0.009,0.013-0.011,0.02c-0.005,0.015-0.001,0.036-0.001,0.052c0,0.008-0.003,0.012-0.004,0.02
+ c-0.002,0.006,0.001,0.013,0,0.02c-0.001,0.006-0.003,0.009-0.004,0.015c-0.001,0.007,0,0.014,0,0.021
+ c-0.001,0.012-0.006,0.027-0.009,0.039c-0.001,0.005-0.001,0.009-0.002,0.013c0.08-0.066,0.159-0.131,0.239-0.196
+ c0.048,0.003,0.096,0.005,0.144,0.008c0.001-0.004,0-0.007,0.002-0.01c0.002-0.004,0.005-0.01,0.009-0.013
+ c0.004-0.004,0.01-0.003,0.014-0.009c0.008-0.011,0.004-0.027,0.005-0.04c0-0.018,0.014-0.018,0.028-0.025
+ c0.011-0.006,0.02-0.012,0.031-0.02c0.012-0.008,0.024-0.003,0.036-0.012c0.006-0.004,0.011-0.009,0.017-0.014
+ c0.005-0.004,0.008-0.004,0.015-0.006c0.009-0.002,0.009-0.003,0.016,0.001c0.006,0.004,0.008,0.004,0.007,0.011
+ c0,0.003-0.005,0.008-0.006,0.01c-0.002,0.006-0.005,0.013-0.005,0.019c0,0.012,0.003,0.027-0.003,0.037
+ c-0.006,0.011-0.021,0.016-0.029,0.026c-0.007,0.008-0.01,0.018-0.019,0.026c-0.002,0.002-0.006,0.005-0.008,0.008
+ c-0.002,0.003-0.002,0.011-0.005,0.014c-0.001,0.001-0.003,0.001-0.005,0.002c0.022,0.001,0.043,0.002,0.064,0.003
+ c0.002-0.006,0.006-0.013,0.008-0.018c0.008-0.024,0.023-0.032,0.045-0.045c0.022-0.012,0.043-0.01,0.067-0.021
+ c0.013-0.006,0.024-0.014,0.037-0.019c0.016-0.007,0.039-0.005,0.054-0.011c0.048-0.02,0.066-0.083,0.096-0.12
+ c0.018-0.021,0.03-0.027,0.055-0.019c0.012,0.004,0.021,0.013,0.032,0.016c0.021,0.007,0.044,0.005,0.064,0.011
+ c0.026,0.009,0.043,0.024,0.059,0.046c0.009,0.012,0.013,0.026,0.021,0.038c0.006,0.009,0.02,0.018,0.025,0.026
+ c0.01,0.017,0.007,0.045,0.005,0.066c-0.005,0.043,0.01,0.019,0.035,0.044c0.015,0.014,0.004,0.026-0.002,0.038
+ c0.293,0.016,0.586,0.031,0.879,0.047c0-0.008-0.001-0.017,0.001-0.024c0.004-0.014,0.016-0.024,0.019-0.037
+ c-0.001,0.006,0.001,0.011,0.001,0.016c0.02-0.01,0.041-0.019,0.062-0.026c0.02-0.008,0.043-0.037,0.059-0.037
+ c0.012,0,0.047,0.023,0.06,0.03c0.035,0.017,0.058,0.028,0.098,0.028l0,0c0.32-0.095,0.639-0.19,0.958-0.285
+ c0.003-0.004,0.006-0.008,0.01-0.012c0.019-0.019,0.042-0.016,0.061-0.031c0.016-0.013,0.016-0.038,0.023-0.056
+ c0.016-0.04,0.063-0.047,0.1-0.04c0.003,0.006,0.007,0.008,0.013,0.009c0.006-0.001,0.011-0.004,0.013-0.009
+ c0.01-0.003,0.014-0.004,0.022-0.01c0.009-0.007,0.027-0.023,0.029-0.034c0.005-0.022-0.051-0.075-0.003-0.083
+ c0.018,0.021,0.04,0.04,0.055,0.062c0.016,0.021,0.032,0.044,0.05,0.065c0.005,0.006,0.01,0.015,0.017,0.024
+ c0.129-0.039,0.258-0.077,0.387-0.116c0.024,0.045,0.047,0.09,0.07,0.135c-0.204,0.679-0.937,1.674-0.611,2.037
+ c0.325,0.363,0.981,0.295,1.472,0.442c0.069,0.151,0.138,0.302,0.207,0.454c-0.111,0.243-0.223,0.487-0.334,0.73
+ c0.051,0.247,0.101,0.495,0.152,0.742c0.02,0.015,0.036,0.036,0.042,0.045c0.021,0.033,0.016,0.069,0.028,0.103
+ c0.007,0.016,0.016,0.033,0.02,0.049c0.003,0.016-0.008,0.032-0.008,0.046c-0.001,0.012,0.019,0.024,0.027,0.035
+ c0.012,0.015,0.016,0.03,0.019,0.045c0.009,0.037,0.011,0.074,0,0.111c-0.006,0.022,0.009,0.04,0.008,0.062
+ c0,0.02-0.001,0.04,0,0.059c0.001,0.01,0.007,0.019,0.006,0.028c-0.001,0.009-0.011,0.018-0.018,0.027
+ c0.045,0.223,0.091,0.445,0.136,0.668c0.483,0.384,0.967,0.768,1.45,1.152c-0.011,0.005-0.021,0.011-0.032,0.016
+ c0.032,0.006,0.083,0.008,0.097,0.036c-0.026,0.011-0.052,0.025-0.078,0.032c-0.021,0.005-0.039,0.021-0.059,0.025
+ c-0.022,0.006-0.046,0.005-0.067,0.013c-0.002,0.001-0.004,0.002-0.006,0.003c0.041,0.054,0.082,0.109,0.124,0.164
+ c0.047-0.014,0.094-0.027,0.141-0.04c-0.014-0.016-0.025-0.039-0.028-0.05c-0.002-0.012-0.006-0.038,0-0.049
+ c0.007-0.012,0.024-0.012,0.033-0.024c0.02-0.028-0.005-0.064,0.023-0.084c0.028-0.02,0.058,0.011,0.085,0.021
+ c0.031,0.012,0.067,0.008,0.065,0.05c-0.003,0.038-0.052,0.063-0.079,0.084c-0.008,0.006-0.024,0.015-0.027,0.025
+ c-0.001,0.002,0.001,0.004,0.001,0.006C-25.854,56.564-25.78,56.543-25.706,56.522z M-29.093,49.403
+ c-0.006,0.016-0.011,0.036-0.032,0.033c-0.008-0.002-0.017-0.013-0.026-0.025l0.016,0.024c-0.018,0.002-0.027-0.025-0.031-0.046
+ l0.005,0.008c-0.001-0.003-0.003-0.006-0.005-0.008c-0.001-0.007-0.001-0.013-0.001-0.018c0-0.021,0.011-0.037,0.018-0.055
+ c0.006-0.015,0.008-0.046,0.024-0.051c0.019-0.006,0.034,0.02,0.043,0.032c0.016,0.026,0.002,0.027-0.001,0.053
+ C-29.085,49.369-29.086,49.386-29.093,49.403z M-29.177,49.371c0.003,0.004,0.007,0.01,0.011,0.018l0,0L-29.177,49.371z
+ M-30.756,48.278c-0.003,0.006-0.009,0.011-0.011,0.018c0,0.002-0.001,0.005-0.001,0.007c-0.002,0.002-0.004,0.005-0.006,0.007
+ c-0.002,0.006-0.002,0.012-0.005,0.018c-0.004,0.01-0.003,0.021-0.006,0.031c-0.006,0.016-0.011,0.032-0.019,0.047
+ c-0.008,0.019-0.025,0.034-0.038,0.051c-0.009,0.011-0.017,0.022-0.026,0.033c-0.006,0.008-0.012,0.016-0.017,0.024
+ c-0.01,0.017-0.014,0.037-0.026,0.053c-0.007,0.01-0.017,0.019-0.024,0.029c-0.009,0.012-0.016,0.023-0.025,0.034
+ c-0.006,0.006-0.01,0.014-0.017,0.02c-0.006,0.006-0.014,0.011-0.019,0.018c-0.007,0.011-0.016,0.019-0.029,0.022
+ c-0.01,0.001-0.022-0.003-0.032,0c-0.004,0.001-0.007,0.006-0.01,0.008c-0.006,0.003-0.009,0.002-0.016,0.002l-0.01-0.007
+ c-0.003-0.003-0.007-0.004-0.011-0.006c-0.008-0.007-0.012-0.018-0.018-0.026c-0.006-0.007-0.007-0.014-0.012-0.021
+ c-0.004-0.006-0.009-0.009-0.013-0.016c-0.004-0.006-0.009-0.012-0.015-0.018c-0.006-0.005-0.012-0.008-0.017-0.013
+ c-0.006-0.005-0.008-0.015-0.01-0.022c-0.002-0.007-0.005-0.013-0.005-0.021c0.001-0.008-0.003-0.014-0.003-0.022
+ c-0.001-0.008,0.001-0.017,0-0.025c-0.003-0.032-0.026-0.059-0.033-0.089c-0.004-0.014-0.005-0.027,0.008-0.037
+ c0.005-0.004,0.012-0.006,0.018-0.009c0.008-0.005,0.017-0.01,0.025-0.015c0.006-0.003,0.012-0.008,0.018-0.012
+ c0.007-0.006,0.011-0.014,0.018-0.02c0.005-0.004,0.01-0.006,0.014-0.011c0.004-0.004,0.005-0.009,0.009-0.013
+ c0.008-0.011,0.022-0.021,0.033-0.029c0.01-0.008,0.018-0.016,0.029-0.022c0.013-0.008,0.023-0.02,0.036-0.029
+ c0.008-0.006,0.014-0.012,0.022-0.018c0.007-0.004,0.013-0.007,0.02-0.011c0.012-0.008,0.026-0.011,0.038-0.017
+ c0.005-0.002,0.01-0.003,0.014-0.007c0.008-0.005,0.017-0.009,0.024-0.015c0.005-0.004,0.01-0.008,0.014-0.01
+ c0.007-0.005,0.014-0.01,0.022-0.014c0.006-0.003,0.011-0.005,0.018-0.004c0.009,0.003,0.016,0.006,0.024,0.009
+ c0.011,0.005,0.02,0.009,0.03,0.015c0.004,0.002,0.007,0.005,0.011,0.007c0.008,0.004,0.008,0.004,0.01,0.012
+ c0.005,0.013,0.002,0.026,0.01,0.039c0.006,0.01,0.026,0.011,0.03,0.021c0.003,0.01-0.003,0.024-0.008,0.032
+ S-30.752,48.27-30.756,48.278z M-30.84,48.052c0,0-0.007,0.007-0.007,0.008c-0.004,0.004-0.006,0.005-0.01,0.008
+ c-0.008,0.005-0.014,0.014-0.022,0.019c-0.01,0.008-0.023,0.016-0.035,0.019c-0.012,0.003-0.023,0.006-0.033,0.014
+ c-0.011,0.007-0.021,0.016-0.032,0.023c-0.003,0.002-0.015,0.009-0.019,0.009c-0.006,0.001-0.014-0.005-0.02-0.01l0.002-0.003
+ c-0.004-0.005-0.005-0.009-0.007-0.015c-0.002-0.007,0-0.014-0.002-0.022c-0.001-0.005-0.006-0.007-0.004-0.014
+ c0.001-0.006,0.006-0.01,0.01-0.014c0.006-0.006,0.01-0.018,0.014-0.026c0.008-0.016,0.019-0.02,0.033-0.029
+ c0.008-0.004,0.011-0.002,0.018,0.001c0.009,0.002,0.011-0.002,0.017-0.008c0.009-0.008,0.019-0.015,0.029-0.024
+ c0.012-0.01,0.022-0.022,0.037-0.029c0.004-0.002,0.009-0.005,0.014-0.007c0.004-0.002,0.012-0.004,0.017-0.002
+ c0.027,0.01,0,0.046,0.001,0.063C-30.839,48.024-30.836,48.042-30.84,48.052z M-31.022,48.138c0.001,0.001,0.003,0.003,0.004,0.004
+ l0,0L-31.022,48.138z M-31.215,48.707c0.002,0.008,0.007,0.015,0.011,0.022c0.005,0.007,0.008,0.014,0.01,0.021
+ c0.003,0.007,0.006,0.014,0.003,0.022c-0.004,0.011-0.009,0.008-0.017,0.01c-0.013,0.003-0.024,0.015-0.035,0.021
+ c-0.006,0.003-0.013,0.005-0.019,0.008c-0.004,0.003-0.008,0.005-0.013,0.007c-0.005,0.002-0.01,0.006-0.015,0.007
+ c-0.005,0.001-0.009,0-0.014,0c-0.009,0.002-0.018,0.004-0.027,0.007c-0.023,0.006-0.046,0.007-0.07,0.013
+ c-0.015,0.004-0.03,0.005-0.046,0.008c-0.013,0.003-0.024,0.007-0.036,0.011c-0.038,0.011-0.072,0.036-0.104,0.057
+ c-0.018,0.012-0.034,0.018-0.054,0.013c-0.01-0.003-0.02,0.001-0.029,0.006c-0.014,0.007-0.025,0.015-0.04,0.021
+ c-0.012,0.004-0.029,0.007-0.042,0.008c-0.006,0-0.012-0.001-0.018-0.001c-0.01,0.001-0.02,0.004-0.029,0.004
+ c-0.009,0.001-0.016-0.001-0.024,0.001c-0.008,0.001-0.014,0.003-0.022,0.003c-0.017,0-0.033,0.002-0.05,0.003
+ c-0.007,0-0.012,0.003-0.018,0.004c-0.009,0.001-0.019,0-0.028,0c-0.01,0-0.017,0.001-0.026,0.003c-0.01,0.001-0.019-0.001-0.028,0
+ c-0.007,0.001-0.014,0.004-0.021,0.004c-0.011,0.001-0.022,0-0.033,0c-0.02,0-0.04,0-0.061,0c-0.023,0-0.053-0.005-0.071-0.021
+ c-0.009-0.009-0.017-0.016-0.025-0.025c-0.004-0.003-0.007-0.007-0.011-0.011c-0.005-0.002-0.012-0.003-0.017-0.005
+ c-0.006-0.002-0.013-0.006-0.018-0.01c-0.01-0.008-0.01-0.018-0.015-0.028c-0.003-0.007-0.007-0.015-0.011-0.022
+ c-0.006-0.01-0.002-0.019,0.001-0.031c0.001-0.004,0.002-0.008,0.004-0.012c-0.004-0.014-0.005-0.028-0.008-0.042
+ c-0.002-0.015,0.011-0.033,0.017-0.047c0.002-0.007,0.006-0.014,0.004-0.022c-0.004-0.009-0.01-0.017-0.011-0.027
+ c-0.001-0.013-0.006-0.024,0.001-0.037c0.002-0.005,0.004-0.009,0.008-0.013c0.005-0.006,0.012-0.01,0.017-0.016
+ c0.005-0.006,0.008-0.009,0.018-0.007c0.003,0.001,0.003,0.003,0.006,0.004c0.004,0.001,0.007,0,0.011,0.001
+ c0.008,0.002,0.013,0.004,0.022,0.002c0.003-0.001,0.007-0.003,0.011-0.004c0.005-0.002,0.008-0.001,0.014-0.002
+ c0.004-0.001,0.006-0.004,0.011-0.004c0.003,0.001,0.007,0.003,0.01,0.003c0.004,0.001,0.008,0,0.011,0
+ c0.004,0.001,0.006,0.003,0.008,0.003c0.007,0.002,0.014,0.001,0.021,0.001c0.009,0,0.016,0.006,0.025,0.007
+ c0.003,0.001,0.007-0.001,0.011,0c0.003,0.001,0.003,0.003,0.006,0.004c0.007,0.001,0.016,0,0.022,0c0.017,0,0.03,0.001,0.042-0.012
+ c0.009-0.009,0.012-0.023,0.022-0.032c0.003-0.004,0.008-0.007,0.011-0.011c0.006-0.006,0.011-0.007,0.018-0.011
+ c0.015-0.007,0.022-0.023,0.03-0.037c0.007-0.013,0.017-0.024,0.024-0.038c0.005-0.012,0.009-0.026,0.014-0.038
+ c0.002-0.006,0.009-0.013,0.01-0.019c0.001-0.003,0-0.007,0.001-0.01c0-0.003,0.003-0.004,0.004-0.007
+ c0-0.005-0.002-0.007-0.003-0.011c-0.002-0.006-0.002-0.012-0.004-0.018c-0.006-0.023-0.004-0.043,0.003-0.064
+ c0.005-0.016,0.011-0.03,0.011-0.047c-0.001-0.016,0-0.035,0.021-0.035c0.017,0,0.021,0.014,0.033,0.024
+ c0.012,0.009,0.027,0.002,0.038-0.003c0.014-0.006,0.027-0.017,0.037-0.028c0.006-0.007,0.014-0.015,0.017-0.023
+ c0.007-0.013,0.012-0.028,0.018-0.042c0.009-0.018,0.018-0.037,0.029-0.054c0.005-0.009,0.011-0.015,0.013-0.024
+ c0.002-0.01-0.002-0.015-0.007-0.022c-0.007-0.012-0.009-0.029-0.006-0.043c0.001-0.008,0.003-0.013,0.003-0.022
+ c0-0.007,0-0.014,0-0.021c0.001-0.02,0.007-0.042,0.014-0.061c0.007-0.019,0.005-0.04,0.009-0.06
+ c0.003-0.019,0.013-0.039,0.013-0.058c0-0.013,0.003-0.03-0.001-0.043c-0.001-0.006-0.005-0.012-0.006-0.018
+ c-0.002-0.007-0.002-0.014-0.003-0.021c-0.001-0.004-0.004-0.004-0.004-0.007c-0.001-0.003,0-0.008,0-0.011
+ c0-0.007-0.001-0.015,0.001-0.021c0.004-0.016,0.005-0.03,0.014-0.043c0.008-0.012,0.019-0.019,0.025-0.032
+ c0.004-0.008,0.02-0.028,0.031-0.026c0.005,0.001,0.011,0.008,0.014,0.012c0.006,0.008,0.004,0.012,0.001,0.02
+ c-0.004,0.017,0.001,0.031,0.01,0.044c0.008,0.014,0.011,0.027,0.016,0.042c0.005,0.017,0.015,0.016,0.028,0.021
+ c0.018,0.007,0.022,0.024,0.028,0.04c0.002,0.006,0.006,0.013,0.007,0.018c0.001,0.003-0.001,0.008,0,0.011
+ c0.001,0.003,0.003,0.008,0.004,0.01c0.004,0.013,0.003,0.026,0.003,0.04c0,0.006,0.002,0.015,0,0.022
+ c-0.001,0.004-0.008,0.016-0.003,0.02c0.01,0.009,0.018-0.03,0.033-0.014c0.009,0.01,0.012,0.031,0.009,0.043
+ c-0.001,0.005-0.003,0.007-0.003,0.011c0,0.004,0.001,0.008,0,0.011c-0.001,0.005-0.005,0.013-0.007,0.018
+ c-0.003,0.006-0.007,0.009-0.011,0.014c-0.003,0.006-0.006,0.012-0.007,0.018c-0.001,0.008,0.001,0.015-0.002,0.022
+ c-0.006,0.014-0.015,0.012-0.026,0.018c-0.008,0.005-0.01,0.012-0.011,0.021c-0.001,0.005-0.001,0.01,0.001,0.014
+ c0.001,0.006,0.001,0.013,0.003,0.018c0.005,0.016,0.017,0.029,0.023,0.043c0.004,0.007,0.008,0.014,0.016,0.017
+ c0.009,0.004,0.016-0.001,0.021-0.006c0.01-0.011,0.017-0.029,0.022-0.043c0.007-0.016,0-0.034,0.017-0.044
+ c0.014-0.007,0.018,0.01,0.025,0.019c0.004,0.005,0.012,0.009,0.018,0.011c0.009,0.001,0.018-0.006,0.025-0.003
+ c0.004,0.001,0.007,0.008,0.01,0.011c0.005,0.003,0.01,0.004,0.015,0.007c0.007,0.005,0.014,0.012,0.019,0.019
+ c0.009,0.012,0.01,0.02,0.01,0.034v0.06c0,0.019,0.003,0.04-0.004,0.058c-0.002,0.007-0.003,0.013-0.003,0.021
+ s-0.003,0.014-0.004,0.021c0,0.004,0.001,0.008,0.001,0.011c-0.001,0.004-0.004,0.007-0.005,0.011c0,0.005,0.001,0.01,0,0.015
+ c-0.001,0.008-0.004,0.017-0.007,0.025c-0.006,0.013-0.012,0.027-0.014,0.042c0,0.008-0.003,0.014-0.004,0.022
+ c-0.003,0.017-0.003,0.03,0,0.048c0.002,0.01,0,0.02,0.001,0.03c0,0.008,0.003,0.016,0.004,0.024c0.002,0.01,0.003,0.02,0.005,0.03
+ c0.003,0.01,0.008,0.019,0.011,0.029c0.005,0.014,0.006,0.029,0.013,0.042c0.002,0.004,0.005,0.011,0.007,0.014
+ c0.004,0.006,0.007,0.007,0.012,0.011c0.009,0.008,0.019,0.022,0.024,0.033C-31.218,48.693-31.217,48.7-31.215,48.707z
+ M-31.576,47.405c-0.012,0.017-0.033,0.042-0.054,0.043c-0.01,0-0.018,0.003-0.029,0.003c-0.009,0.001-0.025-0.005-0.025-0.017
+ l0.011,0.001c-0.008-0.003-0.017-0.012-0.022-0.017c-0.018-0.015-0.019-0.026-0.015-0.048c0.003-0.013,0.006-0.027,0.015-0.037
+ c0.008-0.007,0.016-0.011,0.022-0.021c0.005-0.007,0.005-0.023,0.014-0.025c0.01-0.003,0.02-0.001,0.028-0.008
+ c0.007-0.005,0.009-0.012,0.019-0.01c0.01,0.002,0.018,0.01,0.028,0.011c0.012,0,0.014-0.001,0.019,0.011
+ c0.004,0.009,0.008,0.022,0.007,0.032c-0.002,0.016-0.006,0.031-0.008,0.046C-31.567,47.381-31.57,47.395-31.576,47.405z
+ M-32.403,49.724C-32.403,49.723-32.403,49.723-32.403,49.724L-32.403,49.724L-32.403,49.724z M-32.319,48.806
+ c-0.002,0.006,0,0.006-0.004,0.012c-0.002,0.003-0.005,0.006-0.007,0.009c-0.004,0.006-0.006,0.013-0.008,0.019
+ c-0.003,0.007-0.003,0.013-0.006,0.02c0,0.001-0.007,0.011-0.008,0.011c-0.005,0.003-0.01-0.002-0.015-0.003l-0.008-0.004
+ c-0.004-0.014-0.016-0.027-0.021-0.04c-0.003-0.008-0.005-0.014-0.011-0.021c-0.002-0.002-0.005-0.007-0.007-0.008
+ c-0.003-0.002-0.007-0.001-0.009-0.003c-0.006-0.003-0.01-0.011-0.015-0.017c-0.006-0.008-0.004-0.009-0.002-0.019
+ c0.003-0.011-0.005-0.009-0.01-0.016c-0.004-0.007,0.001-0.017,0.003-0.024c0.005-0.013,0.006-0.029,0.012-0.044
+ c0.005-0.012,0.012-0.025,0.019-0.036c0.006-0.009,0.002-0.015,0.005-0.024c0.002-0.006,0.009-0.008,0.011-0.015
+ c0.002-0.007,0.005-0.018,0.004-0.024c-0.001-0.009-0.008-0.021,0.005-0.024c0.007-0.003,0.014,0,0.019,0.005
+ c0.006,0.006,0.004,0.01,0.006,0.019c0.003,0.015,0.012,0.028,0.016,0.043c0.001,0.004,0.002,0.004,0.003,0.009
+ c0,0.007,0.002,0.01,0.003,0.016c0.002,0.008,0.003,0.015,0.006,0.024c0.002,0.008,0.006,0.012,0.011,0.019
+ c0.005,0.008,0.007,0.017,0.011,0.025c0.006,0.014,0.009,0.028,0.009,0.043c0,0.01,0.001,0.019-0.003,0.028
+ C-32.313,48.793-32.317,48.8-32.319,48.806z M-32.951,49.238c-0.004,0.004-0.007,0.009-0.011,0.013
+ c-0.005,0.007-0.015,0.019-0.017,0.027s0.001,0.009-0.005,0.015c-0.003,0.004-0.006,0.007-0.01,0.01
+ c-0.011,0.011-0.017,0.014-0.017,0.031c0,0.014-0.003,0.026-0.01,0.037c-0.004,0.005-0.011,0.011-0.018,0.011
+ c-0.008,0.001-0.011-0.006-0.015-0.012c0.008-0.018-0.01-0.041-0.02-0.056c-0.011-0.018-0.03-0.034-0.033-0.056
+ c-0.001-0.014,0.009-0.022,0.015-0.033c0.008-0.012,0.016-0.023,0.025-0.035c0.007-0.01,0.014-0.02,0.024-0.028
+ c0.004-0.002,0.009-0.004,0.012-0.008c0.002-0.005,0.001-0.014,0.005-0.019c0.002,0,0.004-0.001,0.006-0.001
+ c0.003-0.002,0.004-0.005,0.006-0.007c0.007-0.005,0.01-0.005,0.019-0.005c0.012,0.001,0.031-0.004,0.039,0.005
+ c0.009,0.01,0.013,0.018,0.013,0.031c0,0.007,0,0.014,0,0.02c0,0.01,0.005,0.015,0.009,0.023c0.003,0.007,0.003,0.01-0.001,0.018
+ C-32.939,49.226-32.945,49.232-32.951,49.238z M-33.22,48.753c-0.008,0.025-0.021,0.047-0.041,0.062
+ c-0.017,0.014-0.035,0.032-0.048,0.05c-0.013,0.02-0.032,0.039-0.042,0.061c-0.004,0.007-0.007,0.013-0.01,0.02
+ c-0.002,0.004-0.005,0.008-0.007,0.012c-0.003,0.003-0.003,0.007-0.005,0.011c-0.008,0.013-0.022,0.025-0.031,0.037
+ c-0.005,0.006-0.009,0.013-0.013,0.02c-0.003,0.004-0.008,0.007-0.011,0.011c-0.006,0.008-0.006,0.014-0.013,0.022
+ c-0.008,0.009-0.018,0.015-0.027,0.023c-0.007,0.007-0.012,0.014-0.019,0.021c-0.005,0.004-0.013,0.01-0.02,0.005
+ c-0.007-0.004-0.004-0.011-0.006-0.018c-0.001,0-0.002,0-0.003-0.001l-0.004-0.004c0-0.009,0.003-0.015,0.004-0.023
+ c0.001-0.005,0-0.011,0-0.015c0-0.007,0.002-0.011,0.003-0.016c0.004-0.02,0.008-0.039,0.014-0.057
+ c0.005-0.016,0.005-0.032,0.011-0.048c0.003-0.008,0.004-0.017,0.009-0.023c0.007-0.008,0.012-0.01,0.016-0.02
+ c0.001-0.004,0.002-0.004,0.003-0.009c0-0.004-0.001-0.007,0.001-0.011c0.002-0.004,0.008-0.008,0.012-0.012
+ c0.007-0.008,0.011-0.019,0.015-0.029c0.003-0.009,0.008-0.012,0.013-0.02c0.005-0.007,0.007-0.016,0.01-0.025
+ c0.004-0.012,0.008-0.013,0.016-0.023c0.009-0.012,0.012-0.027,0.021-0.04c0.009-0.012,0.017-0.022,0.025-0.035
+ c0.005-0.009,0.008-0.016,0.015-0.024c0.005-0.008,0.007-0.017,0.012-0.024c0.006-0.008,0.013-0.013,0.021-0.019
+ c0.001-0.002,0.003-0.005,0.005-0.006c0.003-0.002,0.006-0.002,0.008-0.004c0.01-0.007,0.013-0.029,0.029-0.023
+ c0.013,0.004,0.016,0.023,0.019,0.033c0.003,0.007,0.005,0.013,0.01,0.018c0.003,0.004,0.007,0.006,0.01,0.01
+ c0.005,0.004,0.003,0.005,0.005,0.011c0.005,0.014,0.017,0.031,0.019,0.047c0.002,0.012-0.004,0.014-0.011,0.022
+ C-33.213,48.73-33.216,48.742-33.22,48.753z M-33.383,48.53c-0.013,0.006-0.025,0.013-0.038,0.017
+ c-0.014,0.005-0.02,0.014-0.029,0.024c-0.008,0.008-0.016,0.012-0.026,0.016c-0.007,0.003-0.01,0.004-0.017-0.001
+ c-0.005-0.002-0.011-0.006-0.012-0.011l-0.008-0.009c-0.008-0.007-0.014-0.015-0.02-0.024c-0.003-0.004-0.01-0.011-0.011-0.016
+ c-0.004-0.011-0.001-0.028,0.002-0.039c0.003-0.009,0.001-0.021,0.009-0.027c0.004-0.004,0.01-0.005,0.015-0.01
+ c0.003-0.004,0.005-0.008,0.008-0.012c0.002-0.004,0.007-0.007,0.01-0.01c0.005-0.006,0.008-0.012,0.015-0.016
+ c0.006-0.005,0.008-0.004,0.016-0.005c0.007-0.001,0.012-0.004,0.02-0.004s0.012,0.003,0.02,0.004
+ c0.012,0.002,0.025-0.003,0.036,0.003c0.006,0.003,0.014,0.008,0.019,0.013s0.006,0.013,0.012,0.017
+ c0.007,0.004,0.014,0,0.019,0.004c0.004,0.002,0.006,0.008,0.008,0.012c0.007,0.013,0.012,0.016,0.005,0.031
+ c-0.006,0.013-0.011,0.017-0.023,0.024C-33.364,48.516-33.372,48.525-33.383,48.53z M-33.559,49.088
+ c-0.003,0.01-0.003,0.011-0.01,0.017c-0.006,0.006-0.017,0.007-0.021,0.015c-0.003,0.005,0,0.013-0.001,0.021
+ c-0.001,0.013,0.004,0.016-0.01,0.02l-0.005-0.007c-0.014,0.002-0.02-0.026-0.017-0.036c0.004-0.01,0.006-0.017,0.007-0.027
+ c0-0.009-0.002-0.017,0-0.025c0.002-0.015,0.003-0.029,0.011-0.042c0.003-0.006,0.008-0.011,0.013-0.016
+ c0.009-0.01,0.013-0.012,0.025-0.005c0.007,0.003,0.011,0.005,0.014,0.012c0.004,0.008,0.006,0.018,0.005,0.028
+ c-0.001,0.007,0.001,0.014,0,0.021C-33.55,49.072-33.557,49.079-33.559,49.088z M-33.537,48.952
+ c-0.006,0.009-0.019,0.019-0.031,0.016l0.004-0.008c-0.003,0-0.009,0.002-0.012,0.001c-0.006-0.002-0.005-0.007-0.009-0.012
+ c-0.006-0.008-0.011-0.012-0.011-0.024c-0.001-0.009,0.003-0.016,0.004-0.025c0.003-0.02,0-0.043,0-0.063
+ c0-0.012,0.002-0.018,0.005-0.029c0.003-0.012,0.002-0.021,0.011-0.03c0.006-0.006,0.013-0.01,0.015-0.018
+ c0.001-0.005,0-0.01,0.001-0.015c0.001-0.006,0.004-0.01,0.005-0.015c0.008-0.001,0.023-0.005,0.03-0.01
+ c0.01-0.007,0.011-0.016,0.014-0.026c0.002-0.008,0.01-0.013,0.016-0.02c0.008-0.009,0.007-0.022,0.014-0.033
+ c0.012-0.021,0.045-0.029,0.058-0.005c0.006,0.012,0.001,0.021-0.002,0.033c-0.001,0.005,0.001,0.012,0.001,0.017
+ c-0.001,0.012-0.007,0.022-0.009,0.034c0,0.007,0,0.014-0.004,0.021c-0.004,0.006-0.01,0.011-0.014,0.017
+ c-0.007,0.009-0.016,0.017-0.02,0.028c-0.01,0.025-0.031,0.045-0.04,0.071c-0.005,0.011-0.007,0.023-0.01,0.035
+ c-0.001,0.004-0.003,0.006-0.003,0.013s-0.003,0.009-0.004,0.015c-0.002,0.005,0.001,0.011,0,0.017S-33.534,48.947-33.537,48.952z
+ M-33.541,48.343c-0.002,0.008-0.003,0.013-0.003,0.021c0,0.01,0,0.011-0.005,0.019c-0.007,0.012-0.019,0.023-0.023,0.036
+ c-0.002,0.007-0.003,0.013-0.007,0.02c-0.003,0.006-0.009,0.014-0.009,0.02s0.003,0.01,0.004,0.016
+ c0.001,0.007-0.002,0.012-0.003,0.017c-0.002,0.012-0.002,0.025-0.009,0.035c-0.002,0.004-0.005,0.008-0.009,0.011
+ c-0.003,0.002-0.014,0.007-0.018,0.008c0-0.006-0.006-0.009-0.005-0.015l-0.008-0.004c0-0.008,0.001-0.016,0.001-0.023
+ c-0.001-0.013-0.008-0.025-0.009-0.037c0-0.005,0.002-0.011,0.001-0.016c-0.001-0.004-0.004-0.008-0.005-0.012
+ c-0.001-0.006,0-0.006-0.003-0.012c-0.003-0.004-0.008-0.009-0.012-0.013c-0.008-0.01-0.015-0.022-0.025-0.03
+ c-0.007-0.004-0.017-0.005-0.023-0.009c-0.005-0.005-0.012-0.016-0.013-0.024c0-0.008,0.005-0.015,0.005-0.024
+ c-0.001-0.008,0.002-0.016,0.003-0.024c0.004-0.016,0.011-0.034,0.017-0.051c0.003-0.005,0.005-0.015,0.008-0.02
+ c0.004-0.005,0.009-0.009,0.012-0.016c0.003-0.009,0.007-0.019,0.008-0.028c0-0.009-0.002-0.019,0-0.028
+ c0.007,0,0.01-0.003,0.015-0.004c0.006-0.002,0.014,0.001,0.02,0c0.013-0.002,0.021-0.011,0.031-0.017c0.009-0.006,0.011,0,0.02,0
+ c0.008,0,0.013-0.003,0.021-0.003c0.014,0,0.027,0.001,0.04,0.007c0.01,0.005,0.02,0.014,0.029,0.02
+ c0.007,0.005,0.012,0.016,0.016,0.024c0.005,0.011,0.009,0.018,0.006,0.032c-0.004,0.022-0.019,0.029-0.031,0.045
+ C-33.523,48.289-33.533,48.314-33.541,48.343z M-32.86,55.655c0.007,0.016,0.017,0.03,0.024,0.047
+ c0.017,0.042,0.038,0.08,0.055,0.121c0.017,0.04,0.045,0.07,0.072,0.105c0.02,0.024,0.06,0.071,0.052,0.104
+ c-0.017-0.006-0.026-0.023-0.041-0.031l-0.006,0.005c-0.033-0.027-0.062-0.063-0.067-0.105c-0.005-0.03-0.024-0.065-0.039-0.094
+ c-0.009-0.018-0.031-0.033-0.035-0.052c-0.002-0.008,0.001-0.017-0.001-0.025c-0.004-0.012-0.016-0.028-0.021-0.04
+ c-0.008-0.018-0.015-0.031-0.02-0.051c-0.003-0.012-0.021-0.054,0.006-0.037C-32.866,55.611-32.865,55.641-32.86,55.655z
+ M-31.614,57.091c0.006,0.006,0.009,0.014,0.014,0.021c0.002,0.003,0.004,0.007,0.007,0.011c0.001,0.001,0.003,0.002,0.004,0.004
+ c0.002,0.003,0.005,0.004,0.007,0.006c0.002,0.002,0.004,0.005,0.006,0.008c0.003,0.004,0.005,0.008,0.008,0.013
+ c0.003,0.005,0.006,0.011,0.009,0.017c0.001,0.004,0.004,0.007,0.008,0.01c0.003,0.002,0.004,0.005,0.006,0.009
+ c0.002,0.003,0.003,0.008,0.006,0.011c0.004,0.004,0.007,0.009,0.008,0.014c0.002,0.005,0.003,0.009,0.005,0.014
+ c0.001,0.002,0.002,0.004,0.004,0.006c0.002,0.002,0.005,0.003,0.007,0.006c0.002,0.003,0.005,0.005,0.007,0.008
+ c0.001,0.002,0.002,0.004,0.004,0.006c0.001,0.003,0.004,0.006,0.007,0.009c0.001,0.001,0.003,0.002,0.004,0.004
+ c0.001,0.001,0.002,0.002,0.002,0.003c0.003,0.005,0.007,0.01,0.009,0.015c0.003,0.009,0.007,0.017,0.011,0.025
+ c0.001,0.003,0.002,0.007,0.004,0.01c0.001,0.002,0.005,0.007,0.004,0.009c-0.001,0-0.004-0.004-0.004-0.005
+ c-0.002-0.002-0.003-0.003-0.005-0.005c-0.004-0.003-0.008-0.007-0.013-0.01c-0.003-0.002-0.007-0.005-0.009-0.008
+ c-0.001-0.002-0.003-0.003-0.004-0.005c-0.001-0.002-0.003-0.004-0.004-0.007c-0.001-0.002-0.002-0.004-0.004-0.006
+ c-0.001-0.001-0.003-0.002-0.004-0.003c-0.003-0.004-0.003-0.009-0.005-0.013c-0.002-0.003-0.004-0.005-0.007-0.007
+ c-0.003-0.004-0.005-0.008-0.007-0.012c-0.002-0.003-0.003-0.007-0.005-0.01c-0.002-0.005-0.004-0.01-0.007-0.014
+ c-0.005-0.007-0.011-0.014-0.016-0.021c-0.002-0.004-0.006-0.007-0.008-0.011c-0.003-0.004-0.006-0.008-0.008-0.012
+ c-0.005-0.007-0.009-0.015-0.014-0.022c-0.003-0.004-0.007-0.008-0.01-0.012c-0.003-0.003-0.005-0.008-0.006-0.012
+ c-0.002-0.004-0.003-0.008-0.006-0.012c-0.001-0.001-0.002-0.002-0.002-0.004c-0.001,0-0.001-0.001-0.001-0.001s0,0,0-0.001
+ c-0.001-0.001-0.001-0.002-0.002-0.004c0-0.002-0.001-0.002-0.001-0.003s0-0.002,0-0.003s-0.001-0.001-0.001-0.002
+ c0-0.001,0-0.002-0.001-0.003c-0.001-0.004-0.003-0.007-0.004-0.01c-0.001-0.002-0.001-0.004,0.001-0.004
+ C-31.618,57.088-31.615,57.09-31.614,57.091z M-31.125,57.71c0.016,0.015,0.034,0.023,0.052,0.036
+ c0.023,0.015,0.041,0.038,0.063,0.054c0.033,0.024,0.065,0.05,0.1,0.072c0.018,0.011,0.033,0.026,0.051,0.039
+ c0.014,0.01,0.034,0.018,0.042,0.034c-0.014,0.003-0.039-0.014-0.053-0.019c-0.014-0.004-0.044-0.007-0.051-0.022l0,0
+ c-0.026-0.02-0.048-0.046-0.072-0.069c-0.023-0.022-0.049-0.034-0.069-0.058c-0.027-0.032-0.054-0.036-0.09-0.055
+ c-0.018-0.009-0.059-0.045-0.062-0.068c0.023-0.02,0.031,0.013,0.043,0.024C-31.158,57.69-31.14,57.698-31.125,57.71z
+ M-22.303,59.124c0.005-0.006,0.012-0.01,0.017-0.016c0.004-0.007,0.003-0.01,0.006-0.016c0.004-0.01,0.012-0.017,0.017-0.027
+ c0.001-0.002,0.002-0.01,0.003-0.012c0.003-0.005,0.008-0.009,0.013-0.015c0.009-0.01,0.011-0.012,0.026-0.012
+ c0.01,0.001,0.018-0.002,0.027-0.005c0.01-0.004,0.022-0.01,0.032-0.011c0.005-0.001,0.012,0,0.017,0
+ c0.009,0,0.013-0.002,0.022-0.004c0.012-0.003,0.021-0.004,0.033-0.011c0.007-0.004,0.013-0.008,0.021-0.012
+ c0.01-0.005,0.018-0.01,0.026-0.018c0.003-0.003,0.005-0.006,0.008-0.008c0.005-0.004,0.009-0.005,0.014-0.008
+ c0.014-0.011,0.017-0.026,0.027-0.039c0.011-0.016,0.034-0.026,0.049-0.037c0.006-0.005,0.01-0.012,0.017-0.017
+ c0.01-0.006,0.022-0.011,0.033-0.015c0.019-0.007,0.035-0.021,0.055-0.026c0.019-0.004,0.046-0.006,0.064-0.019
+ c0.015-0.01,0.029-0.025,0.046-0.035c0.005-0.003,0.019-0.011,0.025-0.006c0.008,0.008-0.004,0.018-0.009,0.022
+ c-0.012,0.012-0.026,0.02-0.038,0.033c-0.013,0.014-0.027,0.028-0.041,0.041c-0.005,0.004-0.012,0.01-0.016,0.016
+ c-0.006,0.009-0.005,0.02-0.009,0.029c-0.012,0.023-0.036,0.037-0.055,0.053c-0.007,0.006-0.012,0.011-0.018,0.017
+ c-0.011,0.012-0.017,0.025-0.027,0.039c-0.014,0.021-0.051,0.038-0.075,0.042c-0.012,0.002-0.025,0-0.037,0.002
+ c-0.009,0.002-0.019,0.008-0.029,0.011c-0.011,0.003-0.028,0.009-0.036,0.016c-0.005,0.005-0.007,0.011-0.012,0.016
+ c-0.005,0.004-0.011,0.007-0.016,0.01c-0.009,0.006-0.018,0.015-0.027,0.021c-0.017,0.01-0.037,0.015-0.054,0.023
+ c-0.013,0.006-0.021,0.004-0.034,0.005c-0.011,0.002-0.021,0.007-0.032,0.01c-0.011,0.003-0.027,0.003-0.038,0.001
+ c-0.01-0.001-0.016-0.005-0.025-0.005l0.003-0.006C-22.321,59.142-22.312,59.134-22.303,59.124z M-22.336,59.162
+ c-0.004,0.001-0.005-0.001-0.005-0.004c0.003-0.001,0.006-0.001,0.008-0.001L-22.336,59.162z M-21.881,59.028
+ c-0.01-0.011,0.016-0.024,0.024-0.029c0.007-0.004,0.016-0.006,0.023-0.009c0.005-0.002,0.008-0.003,0.011-0.006
+ c0.005-0.002,0.007-0.009,0.011-0.011c0.006-0.004,0.015-0.002,0.022-0.005c0.007-0.004,0.014-0.009,0.02-0.013
+ c0.009-0.005,0.047-0.031,0.044-0.005c0,0.005-0.007,0.018-0.01,0.022c-0.003,0.006-0.008,0.011-0.012,0.016
+ c-0.008,0.011-0.02,0.027-0.034,0.032c-0.008,0.002-0.016,0-0.024,0.002c-0.009,0.002-0.014,0.008-0.023,0.01
+ c-0.011,0.002-0.023,0-0.033,0c-0.013-0.001-0.021,0.004-0.033,0.005l0.011-0.005C-21.883,59.03-21.882,59.029-21.881,59.028z
+ M-23.012,48.049c0.012-0.009,0.023-0.017,0.03-0.03c0.003-0.007,0.004-0.011,0.011-0.014c0.006-0.002,0.012-0.003,0.018-0.003
+ c0.025-0.001,0.044,0.01,0.051,0.036c0.005,0.018,0.007,0.037,0.01,0.056c0.002,0.017,0.009,0.033,0.012,0.05
+ c0.002,0.014,0.004,0.027,0.001,0.041c-0.003,0.013-0.006,0.019-0.018,0.024c-0.024,0.011-0.052,0.005-0.076,0
+ c-0.008-0.002-0.016-0.005-0.022-0.01c-0.007-0.005-0.012-0.013-0.021-0.014h0.013c-0.003,0-0.005-0.003-0.008-0.005
+ c-0.006-0.006-0.01-0.008-0.014-0.015c-0.005-0.008-0.012-0.014-0.015-0.023c-0.003-0.006-0.005-0.014-0.006-0.021
+ c-0.001-0.014-0.002-0.033,0.006-0.046C-23.032,48.063-23.023,48.056-23.012,48.049z M-22.19,43.243
+ c0.013,0.002,0.025,0.012,0.037,0.018c0.011,0.005,0.021,0,0.032,0.003c0.005,0.001,0.005,0.005,0.01,0.006
+ c0.007,0.002,0.014-0.002,0.021-0.001c0.01,0.002,0.018,0.007,0.026,0.012c0.007,0.004,0.012,0.01,0.019,0.014
+ c0.008,0.005,0.017,0.005,0.024,0.013c0.007,0.007,0.016,0.02,0.005,0.025c-0.002,0.001-0.013,0.003-0.016,0.004
+ c-0.008,0.002-0.014,0.004-0.021,0.008c-0.01,0.005-0.016,0.01-0.026,0.014c-0.005,0.002-0.005,0.005-0.011,0.005
+ c-0.005,0-0.014-0.005-0.021-0.005c-0.023,0-0.044-0.011-0.066-0.01c-0.002-0.002-0.004-0.005-0.006-0.007
+ c-0.01-0.01-0.019-0.021-0.027-0.032c-0.006-0.007-0.011-0.016-0.017-0.024c-0.011-0.018,0.002-0.022,0.015-0.033
+ C-22.204,43.246-22.201,43.241-22.19,43.243z M-22.195,43.354c0.006-0.003,0.012-0.004,0.018-0.005
+ c0.002,0.002,0.005,0.004,0.007,0.005H-22.195z M-21.865,43.386c0.003-0.011,0-0.022,0.001-0.032
+ c0.001-0.003,0.005-0.01,0.005-0.012c0.001-0.008-0.003-0.011-0.005-0.02c-0.002-0.015-0.002-0.047,0.016-0.053
+ c0.009-0.002,0.024,0.001,0.033,0.001c0.017,0,0.031,0,0.047-0.006c0.02-0.007,0.035-0.012,0.057-0.006
+ c0.024,0.006,0.042-0.011,0.063,0.003c0.015,0.009,0.031,0.023,0.039,0.039c0.009,0.019,0.009,0.042,0.009,0.065
+ c0,0.019-0.004,0.044,0.004,0.063c0.009,0.02,0.024,0.035,0.038,0.052c0.015,0.018,0.027,0.036,0.038,0.057
+ c0.004,0.008,0.008,0.013,0.009,0.022c0.003,0.017,0.001,0.021-0.015,0.021c-0.02,0-0.037-0.013-0.053-0.025
+ c-0.023-0.016-0.058-0.029-0.086-0.036c-0.024-0.007-0.046-0.017-0.071-0.022c-0.015-0.003-0.025,0.004-0.038,0.003
+ c-0.01,0-0.021-0.008-0.031-0.009c-0.011-0.001-0.021-0.002-0.031-0.006c-0.003-0.001-0.009-0.007-0.012-0.009
+ c-0.004-0.002-0.011-0.005-0.014-0.007c-0.007-0.007-0.005-0.017-0.011-0.023l0.004,0.003
+ C-21.886,43.435-21.868,43.401-21.865,43.386z"/>
+</svg>
diff --git a/browser/branding/unofficial/default16.png b/browser/branding/unofficial/default16.png
new file mode 100644
index 000000000..d285a90b4
--- /dev/null
+++ b/browser/branding/unofficial/default16.png
Binary files differ
diff --git a/browser/branding/unofficial/default32.png b/browser/branding/unofficial/default32.png
new file mode 100644
index 000000000..95adf2497
--- /dev/null
+++ b/browser/branding/unofficial/default32.png
Binary files differ
diff --git a/browser/branding/unofficial/default48.png b/browser/branding/unofficial/default48.png
new file mode 100644
index 000000000..d38185f54
--- /dev/null
+++ b/browser/branding/unofficial/default48.png
Binary files differ
diff --git a/browser/branding/unofficial/disk.icns b/browser/branding/unofficial/disk.icns
new file mode 100644
index 000000000..718f1a3d3
--- /dev/null
+++ b/browser/branding/unofficial/disk.icns
Binary files differ
diff --git a/browser/branding/unofficial/document.icns b/browser/branding/unofficial/document.icns
new file mode 100644
index 000000000..ff419f2f7
--- /dev/null
+++ b/browser/branding/unofficial/document.icns
Binary files differ
diff --git a/browser/branding/unofficial/document.ico b/browser/branding/unofficial/document.ico
new file mode 100644
index 000000000..584a0a1bf
--- /dev/null
+++ b/browser/branding/unofficial/document.ico
Binary files differ
diff --git a/browser/branding/unofficial/dsstore b/browser/branding/unofficial/dsstore
new file mode 100644
index 000000000..2d11482a7
--- /dev/null
+++ b/browser/branding/unofficial/dsstore
Binary files differ
diff --git a/browser/branding/unofficial/firefox.VisualElementsManifest.xml b/browser/branding/unofficial/firefox.VisualElementsManifest.xml
new file mode 100644
index 000000000..7654e0ab7
--- /dev/null
+++ b/browser/branding/unofficial/firefox.VisualElementsManifest.xml
@@ -0,0 +1,8 @@
+<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
+ <VisualElements
+ ShowNameOnSquare150x150Logo='on'
+ Square150x150Logo='browser\VisualElements\VisualElements_150.png'
+ Square70x70Logo='browser\VisualElements\VisualElements_70.png'
+ ForegroundText='light'
+ BackgroundColor='#14171a'/>
+</Application>
diff --git a/browser/branding/unofficial/firefox.icns b/browser/branding/unofficial/firefox.icns
new file mode 100644
index 000000000..0c6941acf
--- /dev/null
+++ b/browser/branding/unofficial/firefox.icns
Binary files differ
diff --git a/browser/branding/unofficial/firefox.ico b/browser/branding/unofficial/firefox.ico
new file mode 100644
index 000000000..5217a6c0b
--- /dev/null
+++ b/browser/branding/unofficial/firefox.ico
Binary files differ
diff --git a/browser/branding/unofficial/locales/browserconfig.properties b/browser/branding/unofficial/locales/browserconfig.properties
new file mode 100644
index 000000000..06cefece3
--- /dev/null
+++ b/browser/branding/unofficial/locales/browserconfig.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/.
+
+# Do NOT localize or otherwise change these values
+browser.startup.homepage=about:home
diff --git a/browser/branding/unofficial/locales/en-US/brand.dtd b/browser/branding/unofficial/locales/en-US/brand.dtd
new file mode 100644
index 000000000..cf4596ae0
--- /dev/null
+++ b/browser/branding/unofficial/locales/en-US/brand.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 brandShorterName "Nightly">
+<!ENTITY brandShortName "Nightly">
+<!ENTITY brandFullName "Nightly">
+<!ENTITY vendorShortName "Mozilla">
+<!ENTITY trademarkInfo.part1 " ">
diff --git a/browser/branding/unofficial/locales/en-US/brand.properties b/browser/branding/unofficial/locales/en-US/brand.properties
new file mode 100644
index 000000000..8cd2c2ec9
--- /dev/null
+++ b/browser/branding/unofficial/locales/en-US/brand.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/.
+
+brandShorterName=Nightly
+brandShortName=Nightly
+brandFullName=Nightly
+vendorShortName=Mozilla
+
+syncBrandShortName=Sync
diff --git a/browser/branding/unofficial/locales/jar.mn b/browser/branding/unofficial/locales/jar.mn
new file mode 100644
index 000000000..8515d5cc0
--- /dev/null
+++ b/browser/branding/unofficial/locales/jar.mn
@@ -0,0 +1,12 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Unofficial branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
+ locale/branding/browserconfig.properties
diff --git a/browser/branding/unofficial/locales/moz.build b/browser/branding/unofficial/locales/moz.build
new file mode 100644
index 000000000..8bad13124
--- /dev/null
+++ b/browser/branding/unofficial/locales/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_DISTRIBUTION_ID_UNQUOTED'] = CONFIG['MOZ_DISTRIBUTION_ID']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/branding/unofficial/moz.build b/browser/branding/unofficial/moz.build
new file mode 100644
index 000000000..9045cee11
--- /dev/null
+++ b/browser/branding/unofficial/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['content', 'locales']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+include('../branding-common.mozbuild')
+FirefoxBranding()
diff --git a/browser/branding/unofficial/mozicon128.png b/browser/branding/unofficial/mozicon128.png
new file mode 100644
index 000000000..471cf4645
--- /dev/null
+++ b/browser/branding/unofficial/mozicon128.png
Binary files differ
diff --git a/browser/branding/unofficial/newtab.ico b/browser/branding/unofficial/newtab.ico
new file mode 100644
index 000000000..a9b37c08c
--- /dev/null
+++ b/browser/branding/unofficial/newtab.ico
Binary files differ
diff --git a/browser/branding/unofficial/newwindow.ico b/browser/branding/unofficial/newwindow.ico
new file mode 100644
index 000000000..553720771
--- /dev/null
+++ b/browser/branding/unofficial/newwindow.ico
Binary files differ
diff --git a/browser/branding/unofficial/particles.bmp b/browser/branding/unofficial/particles.bmp
new file mode 100644
index 000000000..ab74ce047
--- /dev/null
+++ b/browser/branding/unofficial/particles.bmp
Binary files differ
diff --git a/browser/branding/unofficial/pbmode.ico b/browser/branding/unofficial/pbmode.ico
new file mode 100644
index 000000000..47677c13f
--- /dev/null
+++ b/browser/branding/unofficial/pbmode.ico
Binary files differ
diff --git a/browser/branding/unofficial/pencil-rtl.bmp b/browser/branding/unofficial/pencil-rtl.bmp
new file mode 100644
index 000000000..e50d92db7
--- /dev/null
+++ b/browser/branding/unofficial/pencil-rtl.bmp
Binary files differ
diff --git a/browser/branding/unofficial/pencil.bmp b/browser/branding/unofficial/pencil.bmp
new file mode 100644
index 000000000..252c10f41
--- /dev/null
+++ b/browser/branding/unofficial/pencil.bmp
Binary files differ
diff --git a/browser/branding/unofficial/pref/firefox-branding.js b/browser/branding/unofficial/pref/firefox-branding.js
new file mode 100644
index 000000000..b20a3a309
--- /dev/null
+++ b/browser/branding/unofficial/pref/firefox-branding.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/. */
+
+pref("startup.homepage_override_url", "");
+pref("startup.homepage_welcome_url", "");
+pref("startup.homepage_welcome_url.additional", "");
+// The time interval between checks for a new version (in seconds)
+pref("app.update.interval", 86400); // 24 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+pref("app.update.download.backgroundInterval", 60);
+// Give the user x seconds to react before showing the big UI. default=24 hours
+pref("app.update.promptWaitTime", 86400);
+// URL user can browse to manually if for some reason all update installation
+// attempts fail.
+pref("app.update.url.manual", "https://nightly.mozilla.org");
+// A default value for the "More information about this update" link
+// supplied in the "An update is available" page of the update wizard.
+pref("app.update.url.details", "https://nightly.mozilla.org");
+
+// The number of days a binary is permitted to be old
+// without checking for an update. This assumes that
+// app.update.checkInstallTime is true.
+pref("app.update.checkInstallTime.days", 2);
+
+// Give the user x seconds to reboot before showing a badge on the hamburger
+// button. default=immediately
+pref("app.update.badgeWaitTime", 0);
+
+// Number of usages of the web console or scratchpad.
+// If this is less than 5, then pasting code into the web console or scratchpad is disabled
+pref("devtools.selfxss.count", 0);
diff --git a/browser/branding/unofficial/wizHeader.bmp b/browser/branding/unofficial/wizHeader.bmp
new file mode 100644
index 000000000..f67b45239
--- /dev/null
+++ b/browser/branding/unofficial/wizHeader.bmp
Binary files differ
diff --git a/browser/branding/unofficial/wizHeaderRTL.bmp b/browser/branding/unofficial/wizHeaderRTL.bmp
new file mode 100644
index 000000000..7c57e461b
--- /dev/null
+++ b/browser/branding/unofficial/wizHeaderRTL.bmp
Binary files differ
diff --git a/browser/branding/unofficial/wizWatermark.bmp b/browser/branding/unofficial/wizWatermark.bmp
new file mode 100644
index 000000000..05f0e7f9b
--- /dev/null
+++ b/browser/branding/unofficial/wizWatermark.bmp
Binary files differ
diff --git a/browser/build.mk b/browser/build.mk
new file mode 100644
index 000000000..1bcba59d0
--- /dev/null
+++ b/browser/build.mk
@@ -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/.
+
+installer:
+ @$(MAKE) -C browser/installer installer
+
+package:
+ @$(MAKE) -C browser/installer
+
+package-compare:
+ @$(MAKE) -C browser/installer package-compare
+
+stage-package:
+ @$(MAKE) -C browser/installer stage-package
+
+sdk:
+ @$(MAKE) -C browser/installer make-sdk
+
+install::
+ @$(MAKE) -C browser/installer install
+
+clean::
+ @$(MAKE) -C browser/installer clean
+
+distclean::
+ @$(MAKE) -C browser/installer distclean
+
+source-package::
+ @$(MAKE) -C browser/installer source-package
+
+upload::
+ @$(MAKE) -C browser/installer upload
+
+source-upload::
+ @$(MAKE) -C browser/installer source-upload
+
+hg-bundle::
+ @$(MAKE) -C browser/installer hg-bundle
+
+l10n-check::
+ @$(MAKE) -C browser/locales l10n-check
+
+ifdef ENABLE_TESTS
+# Implemented in testing/testsuite-targets.mk
+
+mochitest-browser-chrome:
+ $(RUN_MOCHITEST) --flavor=browser
+ $(CHECK_TEST_ERROR)
+
+mochitest:: mochitest-browser-chrome
+
+.PHONY: mochitest-browser-chrome
+
+endif
diff --git a/browser/components/BrowserComponents.manifest b/browser/components/BrowserComponents.manifest
new file mode 100644
index 000000000..dbfc3d2ec
--- /dev/null
+++ b/browser/components/BrowserComponents.manifest
@@ -0,0 +1,44 @@
+# nsBrowserContentHandler.js
+component {5d0ce354-df01-421a-83fb-7ead0990c24e} nsBrowserContentHandler.js application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/browser/clh;1 {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+component {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} nsBrowserContentHandler.js application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/browser/final-clh;1 {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=text/html {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.mozilla.xul+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/svg+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=text/rdf {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=text/xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=application/xhtml+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=text/css {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=text/plain {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/gif {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/jpeg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/jpg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/png {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/bmp {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/x-icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=image/vnd.microsoft.icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+contract @mozilla.org/uriloader/content-handler;1?type=application/http-index-format {5d0ce354-df01-421a-83fb-7ead0990c24e} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+category command-line-handler m-browser @mozilla.org/browser/clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+category command-line-handler x-default @mozilla.org/browser/final-clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+category command-line-validator b-browser @mozilla.org/browser/clh;1 application={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+
+# nsBrowserGlue.js
+
+# This component must restrict its registration for the app-startup category
+# to the specific list of apps that use it so it doesn't get loaded in xpcshell.
+# Thus we restrict it to these apps:
+#
+# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
+# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
+# graphene: {d1bfe7d9-c01e-4237-998b-7b5f960a4314}
+
+component {eab9012e-5f74-4cbc-b2b5-a590235513cc} nsBrowserGlue.js
+contract @mozilla.org/browser/browserglue;1 {eab9012e-5f74-4cbc-b2b5-a590235513cc}
+category app-startup nsBrowserGlue service,@mozilla.org/browser/browserglue;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} application={d1bfe7d9-c01e-4237-998b-7b5f960a4314}
+component {d8903bf6-68d5-4e97-bcd1-e4d3012f721a} nsBrowserGlue.js
+#ifndef MOZ_MULET
+contract @mozilla.org/content-permission/prompt;1 {d8903bf6-68d5-4e97-bcd1-e4d3012f721a}
+#endif
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp
new file mode 100644
index 000000000..a09932d95
--- /dev/null
+++ b/browser/components/about/AboutRedirector.cpp
@@ -0,0 +1,235 @@
+/* -*- 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/. */
+
+// See also: docshell/base/nsAboutRedirector.cpp
+
+#include "AboutRedirector.h"
+#include "nsNetUtil.h"
+#include "nsIAboutNewTabService.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIProtocolHandler.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace browser {
+
+NS_IMPL_ISUPPORTS(AboutRedirector, nsIAboutModule)
+
+struct RedirEntry {
+ const char* id;
+ const char* url;
+ uint32_t flags;
+};
+
+/*
+ Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome
+ privileges. This is potentially dangerous. Please use
+ URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below
+ unless your about: page really needs chrome privileges. Security review is
+ required before adding new map entries without
+ URI_SAFE_FOR_UNTRUSTED_CONTENT.
+*/
+static RedirEntry kRedirMap[] = {
+ { "blocked", "chrome://browser/content/blockedSite.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+ { "certerror", "chrome://browser/content/aboutNetError.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+ { "socialerror", "chrome://browser/content/aboutSocialError.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+ { "providerdirectory", "chrome://browser/content/aboutProviderDirectory.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+ { "tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+ { "feeds", "chrome://browser/content/feeds/subscribe.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+ { "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml",
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "rights",
+ "chrome://global/content/aboutRights.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::MAKE_LINKABLE |
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "robots", "chrome://browser/content/aboutRobots.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "searchreset", "chrome://browser/content/search/searchReset.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+ { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
+ nsIAboutModule::ALLOW_SCRIPT },
+ // Linkable because of indexeddb use (bug 1228118)
+ { "home", "chrome://browser/content/abouthome/aboutHome.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::MAKE_LINKABLE |
+ nsIAboutModule::ENABLE_INDEXED_DB },
+ // the newtab's actual URL will be determined when the channel is created
+ { "newtab", "about:blank",
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "preferences", "chrome://browser/content/preferences/in-content/preferences.xul",
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "downloads", "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
+ nsIAboutModule::ALLOW_SCRIPT },
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ { "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT },
+#endif
+ { "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT },
+ { "reader", "chrome://global/content/reader/aboutReader.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+};
+static const int kRedirTotal = ArrayLength(kRedirMap);
+
+static nsAutoCString
+GetAboutModuleName(nsIURI *aURI)
+{
+ nsAutoCString path;
+ aURI->GetPath(path);
+
+ int32_t f = path.FindChar('#');
+ if (f >= 0)
+ path.SetLength(f);
+
+ f = path.FindChar('?');
+ if (f >= 0)
+ path.SetLength(f);
+
+ ToLowerCase(path);
+ return path;
+}
+
+NS_IMETHODIMP
+AboutRedirector::NewChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ASSERTION(result, "must not be null");
+
+ nsAutoCString path = GetAboutModuleName(aURI);
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (int i = 0; i < kRedirTotal; i++) {
+ if (!strcmp(path.get(), kRedirMap[i].id)) {
+ nsAutoCString url;
+
+ if (path.EqualsLiteral("newtab")) {
+ // let the aboutNewTabService decide where to redirect
+ nsCOMPtr<nsIAboutNewTabService> aboutNewTabService =
+ do_GetService("@mozilla.org/browser/aboutnewtab-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aboutNewTabService->GetDefaultURL(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if about:newtab points to an external resource we have to make sure
+ // the content is signed and trusted
+ bool remoteEnabled = false;
+ rv = aboutNewTabService->GetRemoteEnabled(&remoteEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (remoteEnabled) {
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+ aLoadInfo->SetVerifySignedContent(true);
+ }
+ }
+ // fall back to the specified url in the map
+ if (url.IsEmpty()) {
+ url.AssignASCII(kRedirMap[i].url);
+ }
+
+ nsCOMPtr<nsIChannel> tempChannel;
+ nsCOMPtr<nsIURI> tempURI;
+ rv = NS_NewURI(getter_AddRefs(tempURI), url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If tempURI links to an external URI (i.e. something other than
+ // chrome:// or resource://) then set the LOAD_REPLACE flag on the
+ // channel which forces the channel owner to reflect the displayed
+ // URL rather then being the systemPrincipal.
+ bool isUIResource = false;
+ rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &isUIResource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsLoadFlags loadFlags = isUIResource
+ ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
+ : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
+ tempURI,
+ aLoadInfo,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ tempChannel->SetOriginalURI(aURI);
+
+ NS_ADDREF(*result = tempChannel);
+ return rv;
+ }
+ }
+
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+NS_IMETHODIMP
+AboutRedirector::GetURIFlags(nsIURI *aURI, uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString name = GetAboutModuleName(aURI);
+
+ for (int i = 0; i < kRedirTotal; i++) {
+ if (name.Equals(kRedirMap[i].id)) {
+ *result = kRedirMap[i].flags;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+nsresult
+AboutRedirector::Create(nsISupports *aOuter, REFNSIID aIID, void **result)
+{
+ AboutRedirector* about = new AboutRedirector();
+ if (about == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(about);
+ nsresult rv = about->QueryInterface(aIID, result);
+ NS_RELEASE(about);
+ return rv;
+}
+
+} // namespace browser
+} // namespace mozilla
diff --git a/browser/components/about/AboutRedirector.h b/browser/components/about/AboutRedirector.h
new file mode 100644
index 000000000..8feeb7491
--- /dev/null
+++ b/browser/components/about/AboutRedirector.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AboutRedirector_h__
+#define AboutRedirector_h__
+
+#include "nsIAboutModule.h"
+
+namespace mozilla {
+namespace browser {
+
+class AboutRedirector : public nsIAboutModule
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIABOUTMODULE
+
+ AboutRedirector() {}
+
+ static nsresult
+ Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
+
+protected:
+ virtual ~AboutRedirector() {}
+};
+
+} // namespace browser
+} // namespace mozilla
+
+#endif // AboutRedirector_h__
diff --git a/browser/components/about/moz.build b/browser/components/about/moz.build
new file mode 100644
index 000000000..f1559c2b9
--- /dev/null
+++ b/browser/components/about/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.browser += [
+ 'AboutRedirector.h',
+]
+
+SOURCES += [
+ 'AboutRedirector.cpp',
+]
+
+FINAL_LIBRARY = 'browsercomps'
+
+LOCAL_INCLUDES += [
+ '../build',
+]
diff --git a/browser/components/build/Makefile.in b/browser/components/build/Makefile.in
new file mode 100644
index 000000000..2387227ab
--- /dev/null
+++ b/browser/components/build/Makefile.in
@@ -0,0 +1,8 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/rules.mk
+
+# Ensure that we don't embed a manifest referencing the CRT.
+EMBED_MANIFEST_AT =
diff --git a/browser/components/build/moz.build b/browser/components/build/moz.build
new file mode 100644
index 000000000..8c99b74dd
--- /dev/null
+++ b/browser/components/build/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'nsBrowserCompsCID.h',
+]
+
+SOURCES += [
+ 'nsModule.cpp',
+]
+
+Library('browsercomps')
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '../about',
+ '../dirprovider',
+ '../feeds',
+ '../migration',
+ '../shell',
+]
diff --git a/browser/components/build/nsBrowserCompsCID.h b/browser/components/build/nsBrowserCompsCID.h
new file mode 100644
index 000000000..e325e43d4
--- /dev/null
+++ b/browser/components/build/nsBrowserCompsCID.h
@@ -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/. */
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef XP_WIN
+#define NS_WINIEHISTORYENUMERATOR_CID \
+{ 0x93480624, 0x806e, 0x4756, { 0xb7, 0xcb, 0x0f, 0xb7, 0xdd, 0x74, 0x6a, 0x8f } }
+
+#define NS_IEHISTORYENUMERATOR_CONTRACTID \
+ "@mozilla.org/profile/migrator/iehistoryenumerator;1"
+#endif
+
+#define NS_SHELLSERVICE_CID \
+{ 0x63c7b9f4, 0xcc8, 0x43f8, { 0xb6, 0x66, 0xa, 0x66, 0x16, 0x55, 0xcb, 0x73 } }
+
+#define NS_SHELLSERVICE_CONTRACTID \
+ "@mozilla.org/browser/shell-service;1"
+
+#define NS_RDF_FORWARDPROXY_INFER_DATASOURCE_CID \
+{ 0x7a024bcf, 0xedd5, 0x4d9a, { 0x86, 0x14, 0xd4, 0x4b, 0xe1, 0xda, 0xda, 0xd3 } }
+
+#define NS_FEEDSNIFFER_CID \
+{ 0x6893e69, 0x71d8, 0x4b23, { 0x81, 0xeb, 0x80, 0x31, 0x4d, 0xaf, 0x3e, 0x66 } }
+
+#define NS_FEEDSNIFFER_CONTRACTID \
+ "@mozilla.org/browser/feeds/sniffer;1"
+
+#define NS_ABOUTFEEDS_CID \
+{ 0x12ff56ec, 0x58be, 0x402c, { 0xb0, 0x57, 0x1, 0xf9, 0x61, 0xde, 0x96, 0x9b } }
+
+// 136e2c4d-c5a4-477c-b131-d93d7d704f64
+#define NS_PRIVATE_BROWSING_SERVICE_WRAPPER_CID \
+{ 0x136e2c4d, 0xc5a4, 0x477c, { 0xb1, 0x31, 0xd9, 0x3d, 0x7d, 0x70, 0x4f, 0x64 } }
+
+// 7e4bb6ad-2fc4-4dc6-89ef-23e8e5ccf980
+#define NS_BROWSER_ABOUT_REDIRECTOR_CID \
+{ 0x7e4bb6ad, 0x2fc4, 0x4dc6, { 0x89, 0xef, 0x23, 0xe8, 0xe5, 0xcc, 0xf9, 0x80 } }
+
+// {6DEB193C-F87D-4078-BC78-5E64655B4D62}
+#define NS_BROWSERDIRECTORYPROVIDER_CID \
+{ 0x6deb193c, 0xf87d, 0x4078, { 0xbc, 0x78, 0x5e, 0x64, 0x65, 0x5b, 0x4d, 0x62 } }
diff --git a/browser/components/build/nsModule.cpp b/browser/components/build/nsModule.cpp
new file mode 100644
index 000000000..f85d8812c
--- /dev/null
+++ b/browser/components/build/nsModule.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.h"
+
+#include "nsBrowserCompsCID.h"
+#include "DirectoryProvider.h"
+
+#if defined(XP_WIN)
+#include "nsWindowsShellService.h"
+#elif defined(XP_MACOSX)
+#include "nsMacShellService.h"
+#elif defined(MOZ_WIDGET_GTK)
+#include "nsGNOMEShellService.h"
+#endif
+
+#if defined(XP_WIN)
+#include "nsIEHistoryEnumerator.h"
+#endif
+
+#include "rdf.h"
+#include "nsFeedSniffer.h"
+#include "AboutRedirector.h"
+#include "nsIAboutModule.h"
+
+#include "nsNetCID.h"
+
+using namespace mozilla::browser;
+
+/////////////////////////////////////////////////////////////////////////////
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(DirectoryProvider)
+#if defined(XP_WIN)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindowsShellService)
+#elif defined(XP_MACOSX)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacShellService)
+#elif defined(MOZ_WIDGET_GTK)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
+#endif
+
+#if defined(XP_WIN)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsIEHistoryEnumerator)
+#endif
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFeedSniffer)
+
+NS_DEFINE_NAMED_CID(NS_BROWSERDIRECTORYPROVIDER_CID);
+#if defined(XP_WIN)
+NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
+#elif defined(MOZ_WIDGET_GTK)
+NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_FEEDSNIFFER_CID);
+NS_DEFINE_NAMED_CID(NS_BROWSER_ABOUT_REDIRECTOR_CID);
+#if defined(XP_WIN)
+NS_DEFINE_NAMED_CID(NS_WINIEHISTORYENUMERATOR_CID);
+#elif defined(XP_MACOSX)
+NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
+#endif
+
+static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
+ { &kNS_BROWSERDIRECTORYPROVIDER_CID, false, nullptr, DirectoryProviderConstructor },
+#if defined(XP_WIN)
+ { &kNS_SHELLSERVICE_CID, false, nullptr, nsWindowsShellServiceConstructor },
+#elif defined(MOZ_WIDGET_GTK)
+ { &kNS_SHELLSERVICE_CID, false, nullptr, nsGNOMEShellServiceConstructor },
+#endif
+ { &kNS_FEEDSNIFFER_CID, false, nullptr, nsFeedSnifferConstructor },
+ { &kNS_BROWSER_ABOUT_REDIRECTOR_CID, false, nullptr, AboutRedirector::Create },
+#if defined(XP_WIN)
+ { &kNS_WINIEHISTORYENUMERATOR_CID, false, nullptr, nsIEHistoryEnumeratorConstructor },
+#elif defined(XP_MACOSX)
+ { &kNS_SHELLSERVICE_CID, false, nullptr, nsMacShellServiceConstructor },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
+ { NS_BROWSERDIRECTORYPROVIDER_CONTRACTID, &kNS_BROWSERDIRECTORYPROVIDER_CID },
+#if defined(XP_WIN)
+ { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
+#elif defined(MOZ_WIDGET_GTK)
+ { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
+#endif
+ { NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "blocked", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "certerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "socialerror", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "providerdirectory", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "tabcrashed", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "searchreset", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "welcomeback", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-tabs", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "accounts", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+#endif
+ { NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+#if defined(XP_WIN)
+ { NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
+#elif defined(XP_MACOSX)
+ { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kBrowserCategories[] = {
+ { XPCOM_DIRECTORY_PROVIDER_CATEGORY, "browser-directory-provider", NS_BROWSERDIRECTORYPROVIDER_CONTRACTID },
+ { NS_CONTENT_SNIFFER_CATEGORY, "Feed Sniffer", NS_FEEDSNIFFER_CONTRACTID },
+ { nullptr }
+};
+
+static const mozilla::Module kBrowserModule = {
+ mozilla::Module::kVersion,
+ kBrowserCIDs,
+ kBrowserContracts,
+ kBrowserCategories
+};
+
+NSMODULE_DEFN(nsBrowserCompsModule) = &kBrowserModule;
diff --git a/browser/components/contextualidentity/content/usercontext.css b/browser/components/contextualidentity/content/usercontext.css
new file mode 100644
index 000000000..728275d9f
--- /dev/null
+++ b/browser/components/contextualidentity/content/usercontext.css
@@ -0,0 +1,91 @@
+[data-identity-color="blue"] {
+ --identity-tab-color: #0996f8;
+ --identity-icon-color: #00a7e0;
+}
+
+[data-identity-color="turquoise"] {
+ --identity-tab-color: #01bdad;
+ --identity-icon-color: #01bdad;
+}
+
+[data-identity-color="green"] {
+ --identity-tab-color: #57bd35;
+ --identity-icon-color: #7dc14c;
+}
+
+[data-identity-color="yellow"] {
+ --identity-tab-color: #ffcb00;
+ --identity-icon-color: #ffcb00;
+}
+
+[data-identity-color="orange"] {
+ --identity-tab-color: #ff9216;
+ --identity-icon-color: #ff9216;
+}
+
+[data-identity-color="red"] {
+ --identity-tab-color: #d92215;
+ --identity-icon-color: #d92215;
+}
+
+[data-identity-color="pink"] {
+ --identity-tab-color: #ea385e;
+ --identity-icon-color: #ee5195;
+}
+
+[data-identity-color="purple"] {
+ --identity-tab-color: #7a2f7a;
+ --identity-icon-color: #7a2f7a;
+}
+
+[data-identity-icon="fingerprint"] {
+ --identity-icon: url("chrome://browser/content/usercontext.svg#fingerprint");
+}
+
+[data-identity-icon="briefcase"] {
+ --identity-icon: url("chrome://browser/content/usercontext.svg#briefcase");
+}
+
+[data-identity-icon="dollar"] {
+ --identity-icon: url("chrome://browser/content/usercontext.svg#dollar");
+}
+
+[data-identity-icon="cart"] {
+ --identity-icon: url("chrome://browser/content/usercontext.svg#cart");
+}
+
+[data-identity-icon="circle"] {
+ --identity-icon: url("chrome://browser/content/usercontext.svg#circle");
+}
+
+#userContext-indicator {
+ height: 16px;
+ width: 16px;
+}
+
+#userContext-label {
+ margin-inline-end: 3px;
+ color: var(--identity-tab-color);
+}
+
+#userContext-icons {
+ -moz-box-align: center;
+}
+
+.tabbrowser-tab[usercontextid] {
+ background-image: linear-gradient(to right, transparent 20%, var(--identity-tab-color) 30%, var(--identity-tab-color) 70%, transparent 80%);
+ background-size: auto 2px;
+ background-repeat: no-repeat;
+}
+
+.userContext-icon,
+.menuitem-iconic[data-usercontextid] > .menu-iconic-left > .menu-iconic-icon,
+.subviewbutton[usercontextid] > .toolbarbutton-icon,
+#userContext-indicator {
+ background-image: var(--identity-icon);
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: var(--identity-icon-color);
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center center;
+}
diff --git a/browser/components/contextualidentity/jar.mn b/browser/components/contextualidentity/jar.mn
new file mode 100644
index 000000000..848245949
--- /dev/null
+++ b/browser/components/contextualidentity/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/usercontext/usercontext.css (content/usercontext.css)
diff --git a/browser/components/contextualidentity/moz.build b/browser/components/contextualidentity/moz.build
new file mode 100644
index 000000000..62d333db8
--- /dev/null
+++ b/browser/components/contextualidentity/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser/browser.ini',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Contextual Identity')
diff --git a/browser/components/contextualidentity/test/browser/.eslintrc.js b/browser/components/contextualidentity/test/browser/.eslintrc.js
new file mode 100644
index 000000000..e25a6863e
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/.eslintrc.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ],
+
+ "rules": {
+ "no-undef": "error"
+ }
+};
diff --git a/browser/components/contextualidentity/test/browser/browser.ini b/browser/components/contextualidentity/test/browser/browser.ini
new file mode 100644
index 000000000..55083f8d2
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser.ini
@@ -0,0 +1,30 @@
+[DEFAULT]
+support-files =
+ empty_file.html
+ file_reflect_cookie_into_title.html
+ favicon-normal32.png
+ file_set_storages.html
+ serviceworker.html
+ worker.js
+
+[browser_aboutURLs.js]
+[browser_eme.js]
+[browser_favicon.js]
+[browser_forgetaboutsite.js]
+[browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js]
+[browser_forgetAPI_EME_forgetThisSite.js]
+[browser_forgetAPI_quota_clearStoragesForPrincipal.js]
+[browser_newtabButton.js]
+[browser_usercontext.js]
+[browser_usercontextid_tabdrop.js]
+skip-if = os == "mac" || os == "win" # Intermittent failure - bug 1268276
+[browser_windowName.js]
+tags = openwindow
+[browser_windowOpen.js]
+tags = openwindow
+[browser_serviceworkers.js]
+[browser_broadcastchannel.js]
+[browser_blobUrl.js]
+[browser_middleClick.js]
+[browser_imageCache.js]
+[browser_count_and_remove.js]
diff --git a/browser/components/contextualidentity/test/browser/browser_aboutURLs.js b/browser/components/contextualidentity/test/browser/browser_aboutURLs.js
new file mode 100644
index 000000000..586bca37f
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_aboutURLs.js
@@ -0,0 +1,49 @@
+"use strict";
+
+// For some about: URLs, they will take more time to load and cause timeout.
+// See Bug 1270998.
+requestLongerTimeout(2);
+
+add_task(function* () {
+ let aboutURLs = [];
+
+ // List of about: URLs that will initiate network requests.
+ let networkURLs = [
+ "credits",
+ "telemetry" // about:telemetry will fetch Telemetry asynchrounously and takes
+ // longer, we skip this for now.
+ ];
+
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ for (let cid in Cc) {
+ let result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/);
+ if (!result) {
+ continue;
+ }
+
+ let aboutType = result[1];
+ let contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
+ try {
+ let am = Cc[contract].getService(Ci.nsIAboutModule);
+ let uri = ios.newURI("about:"+aboutType, null, null);
+ let flags = am.getURIFlags(uri);
+ if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT) &&
+ networkURLs.indexOf(aboutType) == -1) {
+ aboutURLs.push(aboutType);
+ }
+ } catch (e) {
+ // getService might have thrown if the component doesn't actually
+ // implement nsIAboutModule
+ }
+ }
+
+ for (let url of aboutURLs) {
+ info("Loading about:" + url);
+ let tab = gBrowser.addTab("about:"+url, {userContextId: 1});
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ ok(true, "Done loading about:" + url);
+
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_blobUrl.js b/browser/components/contextualidentity/test/browser/browser_blobUrl.js
new file mode 100644
index 000000000..8a441311e
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_blobUrl.js
@@ -0,0 +1,78 @@
+"use strict";
+
+// Here we want to test that blob URLs are not available cross containers.
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ + "contextualidentity/test/browser/empty_file.html";
+
+add_task(function* setup() {
+ yield new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]}, resolve);
+ });
+});
+
+
+add_task(function* test() {
+ info("Creating a tab with UCI = 1...");
+ let tab1 = gBrowser.addTab(BASE_URI, {userContextId: 1});
+ is(tab1.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
+
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+ yield BrowserTestUtils.browserLoaded(browser1);
+
+ let blobURL;
+
+ info("Creating a blob URL...");
+ yield ContentTask.spawn(browser1, null, function() {
+ return Promise.resolve(content.window.URL.createObjectURL(new content.window.Blob([123])));
+ }).then(newURL => { blobURL = newURL });
+
+ info("Blob URL: " + blobURL);
+
+ info("Creating a tab with UCI = 2...");
+ let tab2 = gBrowser.addTab(BASE_URI, {userContextId: 2});
+ is(tab2.getAttribute('usercontextid'), 2, "New tab has UCI equal 2");
+
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+ yield BrowserTestUtils.browserLoaded(browser2);
+
+ yield ContentTask.spawn(browser2, blobURL, function(url) {
+ return new Promise(resolve => {
+ var xhr = new content.window.XMLHttpRequest();
+ xhr.onerror = function() { resolve("SendErrored"); }
+ xhr.onload = function() { resolve("SendLoaded"); }
+ xhr.open("GET", url);
+ xhr.send();
+ });
+ }).then(status => {
+ is(status, "SendErrored", "Using a blob URI from one user context id in another should not work");
+ });
+
+ info("Creating a tab with UCI = 1...");
+ let tab3 = gBrowser.addTab(BASE_URI, {userContextId: 1});
+ is(tab3.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
+
+ let browser3 = gBrowser.getBrowserForTab(tab3);
+ yield BrowserTestUtils.browserLoaded(browser3);
+
+ yield ContentTask.spawn(browser3, blobURL, function(url) {
+ return new Promise(resolve => {
+ var xhr = new content.window.XMLHttpRequest();
+ xhr.open("GET", url);
+ try {
+ xhr.send();
+ resolve("SendSucceeded");
+ } catch (e) {
+ resolve("SendThrew");
+ }
+ });
+ }).then(status => {
+ is(status, "SendSucceeded", "Using a blob URI within a single user context id should work");
+ });
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+ yield BrowserTestUtils.removeTab(tab3);
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js b/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js
new file mode 100644
index 000000000..a821ce96b
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js
@@ -0,0 +1,80 @@
+let { classes: Cc, interfaces: Ci } = Components;
+
+const BASE_ORIGIN = "http://example.com";
+const URI = BASE_ORIGIN +
+ "/browser/browser/components/contextualidentity/test/browser/empty_file.html";
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function* openTabInUserContext(uri, userContextId) {
+ // open the tab in the correct userContextId
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]}, resolve);
+ });
+});
+
+add_task(function* test() {
+ let receiver = yield* openTabInUserContext(URI, 2);
+
+ let channelName = "contextualidentity-broadcastchannel";
+
+ // reflect the received message on title
+ yield ContentTask.spawn(receiver.browser, channelName,
+ function (name) {
+ content.window.testPromise = new content.window.Promise(resolve => {
+ content.window.bc = new content.window.BroadcastChannel(name);
+ content.window.bc.onmessage = function (e) {
+ content.document.title += e.data;
+ resolve();
+ }
+ });
+ }
+ );
+
+ let sender1 = yield* openTabInUserContext(URI, 1);
+ let sender2 = yield* openTabInUserContext(URI, 2);
+ sender1.message = "Message from user context #1";
+ sender2.message = "Message from user context #2";
+
+ // send a message from a tab in different user context first
+ // then send a message from a tab in the same user context
+ for (let sender of [sender1, sender2]) {
+ yield ContentTask.spawn(
+ sender.browser,
+ { name: channelName, message: sender.message },
+ function (opts) {
+ let bc = new content.window.BroadcastChannel(opts.name);
+ bc.postMessage(opts.message);
+ });
+ }
+
+ // Since sender1 sends before sender2, if the title is exactly
+ // sender2's message, sender1's message must've been blocked
+ yield ContentTask.spawn(receiver.browser, sender2.message,
+ function* (message) {
+ yield content.window.testPromise.then(function() {
+ is(content.document.title, message,
+ "should only receive messages from the same user context");
+ });
+ }
+ );
+
+ gBrowser.removeTab(sender1.tab);
+ gBrowser.removeTab(sender2.tab);
+ gBrowser.removeTab(receiver.tab);
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_count_and_remove.js b/browser/components/contextualidentity/test/browser/browser_count_and_remove.js
new file mode 100644
index 000000000..23b7e948a
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_count_and_remove.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
+
+function openTabInUserContext(userContextId) {
+ let tab = gBrowser.addTab("about:blank", {userContextId});
+ gBrowser.selectedTab = tab;
+}
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]});
+});
+
+add_task(function* test() {
+ is(ContextualIdentityService.countContainerTabs(), 0, "0 container tabs by default.");
+
+ openTabInUserContext(1);
+ is(ContextualIdentityService.countContainerTabs(), 1, "1 container tab created");
+
+ openTabInUserContext(1);
+ is(ContextualIdentityService.countContainerTabs(), 2, "2 container tab created");
+
+ openTabInUserContext(2);
+ is(ContextualIdentityService.countContainerTabs(), 3, "3 container tab created");
+
+ ContextualIdentityService.closeAllContainerTabs();
+ is(ContextualIdentityService.countContainerTabs(), 0, "0 container tab at the end.");
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_eme.js b/browser/components/contextualidentity/test/browser/browser_eme.js
new file mode 100644
index 000000000..557648d60
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_eme.js
@@ -0,0 +1,186 @@
+/*
+ * Bug 1283325 - A test case to test the EME is originAttributes aware or not.
+ */
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+const TEST_HOST = "example.com";
+const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
+
+const TESTKEY = {
+ initDataType: 'keyids',
+ initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"], "type":"persistent-license"}',
+ kid: "LwVHf8JLtPrv2GUXFW2v_A",
+ key: "97b9ddc459c8d5ff23c1f2754c95abe8",
+ sessionType: 'persistent-license',
+};
+
+const USER_ID_DEFAULT = 0;
+const USER_ID_PERSONAL = 1;
+
+function* openTabInUserContext(uri, userContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerDocument.defaultView.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+function HexToBase64(hex)
+{
+ var bin = "";
+ for (var i = 0; i < hex.length; i += 2) {
+ bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+ }
+ return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
+}
+
+function Base64ToHex(str)
+{
+ var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
+ var res = "";
+ for (var i = 0; i < bin.length; i++) {
+ res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
+ }
+ return res;
+}
+
+function ByteArrayToHex(array) {
+ let bin = String.fromCharCode.apply(null, new Uint8Array(array));
+ let res = "";
+
+ for (let i = 0; i < bin.length; i++) {
+ res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
+ }
+
+ return res;
+}
+
+function generateKeyObject(aKid, aKey) {
+ let keyObj = {
+ kty: 'oct',
+ kid: aKid,
+ k: HexToBase64(aKey),
+ };
+
+ return new TextEncoder().encode(JSON.stringify({
+ keys: [keyObj]
+ }));
+}
+
+function generateKeyInfo(aData) {
+ let keyInfo = {
+ initDataType: aData.initDataType,
+ initData: new TextEncoder().encode(aData.initData),
+ sessionType: aData.sessionType,
+ keyObj: generateKeyObject(aData.kid, aData.key),
+ };
+
+ return keyInfo;
+}
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ [ "privacy.userContext.enabled", true ],
+ [ "media.mediasource.enabled", true ],
+ [ "media.eme.apiVisible", true ],
+ [ "media.mediasource.webm.enabled", true ],
+ [ "media.clearkey.persistent-license.enabled", true ],
+ ]}, resolve);
+ });
+});
+
+add_task(function* test() {
+ // Open a tab with the default container.
+ let defaultContainer = yield openTabInUserContext(TEST_URL + "empty_file.html", USER_ID_DEFAULT);
+
+ // Generate the key info for the default container.
+ let keyInfo = generateKeyInfo(TESTKEY);
+
+ // Update the media key for the default container.
+ let result = yield ContentTask.spawn(defaultContainer.browser, keyInfo, function* (aKeyInfo) {
+ let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
+ [{
+ initDataTypes: [aKeyInfo.initDataType],
+ videoCapabilities: [{contentType: 'video/webm'}],
+ sessionTypes: ['persistent-license'],
+ persistentState: 'required',
+ }]);
+ let mediaKeys = yield access.createMediaKeys();
+ let session = mediaKeys.createSession(aKeyInfo.sessionType);
+ let res = {};
+
+ // Insert the media key.
+ yield new Promise(resolve => {
+ session.addEventListener("message", function(event) {
+ session.update(aKeyInfo.keyObj).then(
+ () => { resolve(); }
+ ).catch(
+ () => {
+ ok(false, "Update the media key fail.");
+ resolve();
+ }
+ );
+ });
+
+ session.generateRequest(aKeyInfo.initDataType, aKeyInfo.initData);
+ });
+
+ let map = session.keyStatuses;
+
+ is(map.size, 1, "One media key has been added.");
+
+ if (map.size === 1) {
+ res.keyId = map.keys().next().value;
+ res.sessionId = session.sessionId;
+ }
+
+ // Close the session.
+ session.close();
+ yield session.closed;
+
+ return res;
+ });
+
+ // Check the media key ID.
+ is(ByteArrayToHex(result.keyId), Base64ToHex(TESTKEY.kid), "The key Id of the default container is correct.");
+
+ // Store the sessionId for the further checking.
+ keyInfo.sessionId = result.sessionId;
+
+ // Open a tab with personal container.
+ let personalContainer = yield openTabInUserContext(TEST_URL + "empty_file.html", USER_ID_PERSONAL);
+
+ yield ContentTask.spawn(personalContainer.browser, keyInfo, function* (aKeyInfo) {
+ let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
+ [{
+ initDataTypes: [aKeyInfo.initDataType],
+ videoCapabilities: [{contentType: 'video/webm'}],
+ sessionTypes: ['persistent-license'],
+ persistentState: 'required',
+ }]);
+ let mediaKeys = yield access.createMediaKeys();
+ let session = mediaKeys.createSession(aKeyInfo.sessionType);
+
+ // First, load the session to check that mediakeys do not share with
+ // default container.
+ yield session.load(aKeyInfo.sessionId);
+
+ let map = session.keyStatuses;
+
+ // Check that there is no media key here.
+ is(map.size, 0, "No media key should be here for the personal container.");
+ });
+
+ // Close default container tab.
+ yield BrowserTestUtils.removeTab(defaultContainer.tab);
+
+ // Close personal container tab.
+ yield BrowserTestUtils.removeTab(personalContainer.tab);
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_favicon.js b/browser/components/contextualidentity/test/browser/browser_favicon.js
new file mode 100644
index 000000000..a0a7eb208
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_favicon.js
@@ -0,0 +1,140 @@
+/*
+ * Bug 1270678 - A test case to test does the favicon obey originAttributes.
+ */
+let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+const USER_CONTEXTS = [
+ "default",
+ "personal",
+ "work",
+];
+
+let gHttpServer = null;
+let gUserContextId;
+let gFaviconData;
+
+function getIconFile() {
+ new Promise(resolve => {
+ NetUtil.asyncFetch({
+ uri: "http://www.example.com/browser/browser/components/contextualidentity/test/browser/favicon-normal32.png",
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON
+ }, function(inputStream, status) {
+ let size = inputStream.available();
+ gFaviconData = NetUtil.readInputStreamToString(inputStream, size);
+ resolve();
+ });
+ });
+}
+
+function* openTabInUserContext(uri, userContextId) {
+ // open the tab in the correct userContextId
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+function loadIndexHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = `
+ <!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test</title>
+ </head>
+ <body>
+ Favicon!!
+ </body>
+ </html>`;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function loadFaviconHandler(metadata, response) {
+ let expectedCookie = "userContext=" + USER_CONTEXTS[gUserContextId];
+
+ if (metadata.hasHeader("Cookie")) {
+ is(metadata.getHeader("Cookie"), expectedCookie, "The cookie has matched with the expected cookie.");
+ } else {
+ ok(false, "The request should have a cookie.");
+ }
+
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "image/png", false);
+ response.bodyOutputStream.write(gFaviconData, gFaviconData.length);
+}
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]}, resolve);
+ });
+
+ // Create a http server for the image cache test.
+ if (!gHttpServer) {
+ gHttpServer = new HttpServer();
+ gHttpServer.registerPathHandler('/', loadIndexHandler);
+ gHttpServer.registerPathHandler('/favicon.png', loadFaviconHandler);
+ gHttpServer.start(-1);
+ }
+});
+
+registerCleanupFunction(() => {
+ gHttpServer.stop(() => {
+ gHttpServer = null;
+ });
+});
+
+add_task(function* test() {
+ waitForExplicitFinish();
+
+ // First, get the icon data.
+ yield getIconFile();
+
+ let serverPort = gHttpServer.identity.primaryPort;
+ let testURL = "http://localhost:" + serverPort + "/";
+ let testFaviconURL = "http://localhost:" + serverPort + "/favicon.png";
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ gUserContextId = userContextId;
+
+ // Load the page in 3 different contexts and set a cookie
+ // which should only be visible in that context.
+
+ // Open our tab in the given user context.
+ let tabInfo = yield* openTabInUserContext(testURL, userContextId);
+
+ // Write a cookie according to the userContext.
+ yield ContentTask.spawn(tabInfo.browser, { userContext: USER_CONTEXTS[userContextId] }, function (arg) {
+ content.document.cookie = "userContext=" + arg.userContext;
+ });
+
+ let pageURI = NetUtil.newURI(testURL);
+ let favIconURI = NetUtil.newURI(testFaviconURL);
+
+ yield new Promise(resolve => {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
+ true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, {
+ onComplete() {
+ resolve();
+ },
+ },
+ tabInfo.browser.contentPrincipal);
+ });
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+ }
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js b/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js
new file mode 100644
index 000000000..1a97448c0
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js
@@ -0,0 +1,219 @@
+/*
+ * Bug 1278037 - A Test case for checking whether forgetting APIs are working for the media key.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+const TEST_HOST = "example.com";
+const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
+
+const USER_CONTEXTS = [
+ "default",
+ "personal",
+];
+
+const TEST_EME_KEY = {
+ initDataType: 'keyids',
+ initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"], "type":"persistent-license"}',
+ kid: "LwVHf8JLtPrv2GUXFW2v_A",
+ key: "97b9ddc459c8d5ff23c1f2754c95abe8",
+ sessionType: 'persistent-license',
+};
+
+//
+// Support functions.
+//
+
+function* openTabInUserContext(uri, userContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+function HexToBase64(hex) {
+ var bin = "";
+ for (var i = 0; i < hex.length; i += 2) {
+ bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
+ }
+ return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
+}
+
+function Base64ToHex(str) {
+ var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
+ var res = "";
+ for (var i = 0; i < bin.length; i++) {
+ res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
+ }
+ return res;
+}
+
+function ByteArrayToHex(array) {
+ let bin = String.fromCharCode.apply(null, new Uint8Array(array));
+ let res = "";
+
+ for (let i = 0; i < bin.length; i++) {
+ res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
+ }
+
+ return res;
+}
+
+function generateKeyObject(aKid, aKey) {
+ let keyObj = {
+ kty: 'oct',
+ kid: aKid,
+ k: HexToBase64(aKey),
+ };
+
+ return new TextEncoder().encode(JSON.stringify({
+ keys: [keyObj]
+ }));
+}
+
+function generateKeyInfo(aData) {
+ let keyInfo = {
+ initDataType: aData.initDataType,
+ initData: new TextEncoder().encode(aData.initData),
+ sessionType: aData.sessionType,
+ keyObj: generateKeyObject(aData.kid, aData.key),
+ };
+
+ return keyInfo;
+}
+
+// Setup a EME key for the given browser, and return the sessionId.
+function* setupEMEKey(browser) {
+ // Generate the key info.
+ let keyInfo = generateKeyInfo(TEST_EME_KEY);
+
+ // Setup the EME key.
+ let result = yield ContentTask.spawn(browser, keyInfo, function* (aKeyInfo) {
+ let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
+ [{
+ initDataTypes: [aKeyInfo.initDataType],
+ videoCapabilities: [{contentType: 'video/webm'}],
+ sessionTypes: ['persistent-license'],
+ persistentState: 'required',
+ }]);
+ let mediaKeys = yield access.createMediaKeys();
+ let session = mediaKeys.createSession(aKeyInfo.sessionType);
+ let res = {};
+
+ // Insert the EME key.
+ yield new Promise(resolve => {
+ session.addEventListener("message", function(event) {
+ session.update(aKeyInfo.keyObj).then(
+ () => { resolve(); }
+ ).catch(
+ () => {
+ ok(false, "Update the EME key fail.");
+ resolve();
+ }
+ );
+ });
+
+ session.generateRequest(aKeyInfo.initDataType, aKeyInfo.initData);
+ });
+
+ let map = session.keyStatuses;
+
+ is(map.size, 1, "One EME key has been added.");
+
+ if (map.size === 1) {
+ res.keyId = map.keys().next().value;
+ res.sessionId = session.sessionId;
+ }
+
+ // Close the session.
+ session.close();
+ yield session.closed;
+
+ return res;
+ });
+
+ // Check the EME key ID.
+ is(ByteArrayToHex(result.keyId), Base64ToHex(TEST_EME_KEY.kid), "The key Id is correct.");
+ return result.sessionId;
+}
+
+// Check whether the EME key has been cleared.
+function* checkEMEKey(browser, emeSessionId) {
+ // Generate the key info.
+ let keyInfo = generateKeyInfo(TEST_EME_KEY);
+ keyInfo.sessionId = emeSessionId;
+
+ yield ContentTask.spawn(browser, keyInfo, function* (aKeyInfo) {
+ let access = yield content.navigator.requestMediaKeySystemAccess('org.w3.clearkey',
+ [{
+ initDataTypes: [aKeyInfo.initDataType],
+ videoCapabilities: [{contentType: 'video/webm'}],
+ sessionTypes: ['persistent-license'],
+ persistentState: 'required',
+ }]);
+ let mediaKeys = yield access.createMediaKeys();
+ let session = mediaKeys.createSession(aKeyInfo.sessionType);
+
+ // First, load the session with the sessionId.
+ yield session.load(aKeyInfo.sessionId);
+
+ let map = session.keyStatuses;
+
+ // Check that there is no media key here.
+ is(map.size, 0, "No media key should be here after forgetThisSite() was called.");
+ });
+}
+
+//
+// Test functions.
+//
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ [ "privacy.userContext.enabled", true ],
+ [ "media.mediasource.enabled", true ],
+ [ "media.eme.apiVisible", true ],
+ [ "media.mediasource.webm.enabled", true ],
+ [ "media.clearkey.persistent-license.enabled", true ],
+ ]});
+});
+
+add_task(function* test_EME_forgetThisSite() {
+ let tabs = [];
+ let emeSessionIds = [];
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Open our tab in the given user context.
+ tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
+
+ // Setup EME Key.
+ emeSessionIds[userContextId] = yield setupEMEKey(tabs[userContextId].browser);
+
+ // Close this tab.
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+
+ // Clear all EME data for a given domain with originAttributes pattern.
+ let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
+ getService(Ci.mozIGeckoMediaPluginChromeService);
+ mps.forgetThisSite(TEST_HOST, JSON.stringify({}));
+
+ // Open tabs again to check EME keys have been cleared.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Open our tab in the given user context.
+ tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
+
+ // Check whether EME Key has been cleared.
+ yield checkEMEKey(tabs[userContextId].browser, emeSessionIds[userContextId]);
+
+ // Close this tab.
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js b/browser/components/contextualidentity/test/browser/browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js
new file mode 100644
index 000000000..1d9024d25
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_forgetAPI_cookie_getCookiesWithOriginAttributes.js
@@ -0,0 +1,86 @@
+/*
+ * Bug 1278037 - A Test case for checking whether forgetting APIs are working for cookies.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+const TEST_HOST = "example.com";
+const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
+
+const USER_CONTEXTS = [
+ "default",
+ "personal",
+];
+
+//
+// Support functions.
+//
+
+function* openTabInUserContext(uri, userContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+function getCookiesForOA(host, userContextId) {
+ return Services.cookies.getCookiesFromHost(host, {userContextId});
+}
+
+//
+// Test functions.
+//
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ [ "privacy.userContext.enabled", true ],
+ ]});
+});
+
+add_task(function* test_cookie_getCookiesWithOriginAttributes() {
+ let tabs = [];
+ let cookieName = "userContextId";
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Load the page in 2 different contexts and set a cookie
+ // which should only be visible in that context.
+ let value = USER_CONTEXTS[userContextId];
+
+ // Open our tab in the given user context.
+ tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "file_reflect_cookie_into_title.html?" + value, userContextId);
+
+ // Close this tab.
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+
+ // Check that cookies have been set properly.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ let enumerator = getCookiesForOA(TEST_HOST, userContextId);
+ ok(enumerator.hasMoreElements(), "Cookies available");
+
+ let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ is(foundCookie["name"], cookieName, "Check cookie name");
+ is(foundCookie["value"], USER_CONTEXTS[userContextId], "Check cookie value");
+ }
+
+ // Using getCookiesWithOriginAttributes() to get all cookies for a certain
+ // domain by using the originAttributes pattern, and clear all these cookies.
+ let enumerator = Services.cookies.getCookiesWithOriginAttributes(JSON.stringify({}), TEST_HOST);
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ Services.cookies.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+ }
+
+ // Check that whether cookies has been cleared.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ let enumerator = getCookiesForOA(TEST_HOST, userContextId);
+ ok(!enumerator.hasMoreElements(), "No Cookie should be here");
+ }
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_forgetAPI_quota_clearStoragesForPrincipal.js b/browser/components/contextualidentity/test/browser/browser_forgetAPI_quota_clearStoragesForPrincipal.js
new file mode 100644
index 000000000..6a4b37c55
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_forgetAPI_quota_clearStoragesForPrincipal.js
@@ -0,0 +1,147 @@
+/*
+ * Bug 1278037 - A Test case for checking whether forgetting APIs are working for the quota manager.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+const TEST_HOST = "example.com";
+const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
+
+const USER_CONTEXTS = [
+ "default",
+ "personal",
+];
+
+//
+// Support functions.
+//
+
+function* openTabInUserContext(uri, userContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+// Setup an entry for the indexedDB.
+function* setupIndexedDB(browser) {
+ yield ContentTask.spawn(browser, { input: "TestForgetAPIs" }, function* (arg) {
+ let request = content.indexedDB.open("idb", 1);
+
+ request.onerror = function() {
+ throw new Error("error opening db connection");
+ };
+
+ request.onupgradeneeded = event => {
+ let db = event.target.result;
+ let store = db.createObjectStore("obj", { keyPath: "id" });
+ store.createIndex("userContext", "userContext", { unique: false });
+ };
+
+ let db = yield new Promise(resolve => {
+ request.onsuccess = event => {
+ resolve(event.target.result);
+ };
+ });
+
+ // Add an entry into the indexedDB.
+ let transaction = db.transaction(["obj"], "readwrite");
+ let store = transaction.objectStore("obj");
+ store.add({id: 1, userContext: arg.input});
+
+ yield new Promise(resolve => {
+ transaction.oncomplete = () => {
+ resolve();
+ };
+ });
+
+ // Check the indexedDB has been set properly.
+ transaction = db.transaction(["obj"], "readonly");
+ store = transaction.objectStore("obj");
+ let getRequest = store.get(1);
+ yield new Promise(resolve => {
+ getRequest.onsuccess = () => {
+ let res = getRequest.result;
+ is(res.userContext, arg.input, "Check the indexedDB value");
+ resolve();
+ };
+ });
+ });
+}
+
+// Check whether the indexedDB has been cleared.
+function* checkIndexedDB(browser) {
+ yield ContentTask.spawn(browser, null, function* () {
+ let request = content.indexedDB.open("idb", 1);
+
+ let db = yield new Promise(done => {
+ request.onsuccess = event => {
+ done(event.target.result);
+ };
+ });
+
+ try {
+ db.transaction(["obj"], "readonly");
+ ok(false, "The indexedDB should not exist");
+ } catch (e) {
+ is(e.name, "NotFoundError", "The indexedDB does not exist as expected");
+ }
+ });
+}
+
+//
+// Test functions.
+//
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ [ "privacy.userContext.enabled", true ],
+ ]});
+});
+
+add_task(function* test_quota_clearStoragesForPrincipal() {
+ let tabs = [];
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Open our tab in the given user context.
+ tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
+
+ // Setup an entry for the indexedDB.
+ yield setupIndexedDB(tabs[userContextId].browser);
+
+ // Close this tab.
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+
+ // Using quota manager to clear all indexed DB for a given domain.
+ let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
+ getService(Ci.nsIQuotaManagerService);
+
+ let caUtils = {};
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js",
+ caUtils);
+ let httpURI = caUtils.makeURI("http://" + TEST_HOST);
+ let httpPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(httpURI, {});
+ qms.clearStoragesForPrincipal(httpPrincipal, null, true);
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Open our tab in the given user context.
+ tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "empty_file.html", userContextId);
+
+ // Check whether indexed DB has been cleared.
+ yield checkIndexedDB(tabs[userContextId].browser);
+
+ // Close this tab.
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js
new file mode 100644
index 000000000..9efc86e0c
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_forgetaboutsite.js
@@ -0,0 +1,352 @@
+/*
+ * Bug 1238183 - Test cases for forgetAboutSite with userContextId.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+let LoadContextInfo = Cc["@mozilla.org/load-context-info-factory;1"]
+ .getService(Ci.nsILoadContextInfoFactory);
+let css = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+
+const USER_CONTEXTS = [
+ "default",
+ "personal",
+];
+const TEST_HOST = "example.com";
+const TEST_URL = "http://" + TEST_HOST + "/browser/browser/components/contextualidentity/test/browser/";
+const COOKIE_NAME = "userContextId";
+
+// Counter for image load hits.
+let gHits = 0;
+
+let gHttpServer = null;
+
+function imageHandler(metadata, response) {
+ // A 1x1 PNG image.
+ // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+ const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
+ gHits++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ response.write(IMAGE);
+}
+
+function loadImagePageHandler(metadata, response) {
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "Ok");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = "<!DOCTYPE HTML>\
+ <html>\
+ <head>\
+ <meta charset='utf-8'>\
+ <title>Load Image</title>\
+ </head>\
+ <body>\
+ <img src='image.png'>\
+ </body>\
+ </html>";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function* openTabInUserContext(uri, userContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+function getCookiesForOA(host, userContextId) {
+ return Services.cookies.getCookiesFromHost(host, {userContextId});
+}
+
+function createURI(uri)
+{
+ let ioServ = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ return ioServ.newURI(uri, null, null);
+}
+
+function getCacheStorage(where, lci, appcache)
+{
+ if (!lci) lci = LoadContextInfo.default;
+ switch (where) {
+ case "disk": return css.diskCacheStorage(lci, false);
+ case "memory": return css.memoryCacheStorage(lci);
+ case "appcache": return css.appCacheStorage(lci, appcache);
+ case "pin": return css.pinningCacheStorage(lci);
+ }
+ return null;
+}
+
+function OpenCacheEntry(key, where, flags, lci)
+{
+ return new Promise(resolve => {
+ key = createURI(key);
+ function CacheListener() { }
+ CacheListener.prototype = {
+ _appCache: null,
+
+ QueryInterface: function (iid) {
+ if (iid.equals(Components.interfaces.nsICacheEntryOpenCallback) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ onCacheEntryCheck: function(entry, appCache) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function (entry, isnew, appCache, status) {
+ resolve();
+ },
+
+ run: function () {
+ let storage = getCacheStorage(where, lci, this._appCache);
+ storage.asyncOpenURI(key, "", flags, this);
+ }
+ };
+
+ (new CacheListener()).run();
+ });
+}
+
+//
+// Test functions.
+//
+
+// Cookies
+function* test_cookie_cleared() {
+ let tabs = [];
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Load the page in 2 different contexts and set a cookie
+ // which should only be visible in that context.
+ let value = USER_CONTEXTS[userContextId];
+
+ // Open our tab in the given user context.
+ tabs[userContextId] = yield* openTabInUserContext(TEST_URL+ "file_reflect_cookie_into_title.html?" + value, userContextId);
+
+ // Close this tab.
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+ // Check that cookies have been set properly.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ let enumerator = getCookiesForOA(TEST_HOST, userContextId);
+ ok(enumerator.hasMoreElements(), "Cookies available");
+
+ let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ Assert.equal(foundCookie["name"], COOKIE_NAME, "Check cookie name");
+ Assert.equal(foundCookie["value"], USER_CONTEXTS[userContextId], "Check cookie value");
+ }
+
+ // Forget the site.
+ ForgetAboutSite.removeDataFromDomain(TEST_HOST);
+
+ // Check that whether cookies has been cleared or not.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ let enumerator = getCookiesForOA(TEST_HOST, userContextId);
+ ok(!enumerator.hasMoreElements(), "No Cookie should be here");
+ }
+}
+
+// Cache
+function* test_cache_cleared() {
+ // First, add some caches.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ yield OpenCacheEntry("http://" + TEST_HOST + "/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ LoadContextInfo.custom(false, {userContextId}));
+
+ yield OpenCacheEntry("http://" + TEST_HOST + "/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ LoadContextInfo.custom(false, {userContextId}));
+ }
+
+
+ // Check that caches have been set correctly.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ let mem = getCacheStorage("memory", LoadContextInfo.custom(false, {userContextId}));
+ let disk = getCacheStorage("disk", LoadContextInfo.custom(false, {userContextId}));
+
+ Assert.ok(mem.exists(createURI("http://" + TEST_HOST + "/"), ""), "The memory cache has been set correctly");
+ Assert.ok(disk.exists(createURI("http://" + TEST_HOST + "/"), ""), "The disk cache has been set correctly");
+ }
+
+ // Forget the site.
+ ForgetAboutSite.removeDataFromDomain(TEST_HOST);
+
+ // Check that do caches be removed or not?
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ let mem = getCacheStorage("memory", LoadContextInfo.custom(false, {userContextId}));
+ let disk = getCacheStorage("disk", LoadContextInfo.custom(false, {userContextId}));
+
+ Assert.ok(!mem.exists(createURI("http://" + TEST_HOST + "/"), ""), "The memory cache is cleared");
+ Assert.ok(!disk.exists(createURI("http://" + TEST_HOST + "/"), ""), "The disk cache is cleared");
+ }
+}
+
+// Image Cache
+function* test_image_cache_cleared() {
+ let tabs = [];
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Open our tab in the given user context to cache image.
+ tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
+ userContextId);
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+
+ let expectedHits = USER_CONTEXTS.length;
+
+ // Check that image cache works with the userContextId.
+ is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times.");
+
+ // Reset the cache count.
+ gHits = 0;
+
+ // Forget the site.
+ ForgetAboutSite.removeDataFromDomain("localhost:" + gHttpServer.identity.primaryPort + "/");
+
+ // Load again.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Open our tab in the given user context to cache image.
+ tabs[userContextId] = yield* openTabInUserContext('http://localhost:' + gHttpServer.identity.primaryPort + '/loadImage.html',
+ userContextId);
+ yield BrowserTestUtils.removeTab(tabs[userContextId].tab);
+ }
+
+ // Check that image cache was cleared and the server gets another two hits.
+ is(gHits, expectedHits, "The image should be loaded" + expectedHits + "times.");
+}
+
+// Offline Storage
+function* test_storage_cleared() {
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Load the page in 2 different contexts and set the local storage
+ // which should only be visible in that context.
+ let value = USER_CONTEXTS[userContextId];
+
+ // Open our tab in the given user context.
+ let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html?" + value, userContextId);
+
+ // Check that the storages has been set correctly.
+ yield ContentTask.spawn(tabInfo.browser, { userContext: USER_CONTEXTS[userContextId] }, function* (arg) {
+ // Check that the local storage has been set correctly.
+ Assert.equal(content.localStorage.getItem("userContext"), arg.userContext, "Check the local storage value");
+
+ // Check that the session storage has been set correctly.
+ Assert.equal(content.sessionStorage.getItem("userContext"), arg.userContext, "Check the session storage value");
+
+ // Check that the indexedDB has been set correctly.
+ let request = content.indexedDB.open("idb", 1);
+
+ let db = yield new Promise(done => {
+ request.onsuccess = event => {
+ done(event.target.result);
+ };
+ });
+
+ let transaction = db.transaction(["obj"], "readonly");
+ let store = transaction.objectStore("obj");
+ let storeRequest = store.get(1);
+
+ yield new Promise(done => {
+ storeRequest.onsuccess = event => {
+ let res = storeRequest.result;
+ Assert.equal(res.userContext, arg.userContext, "Check the indexedDB value");
+ done();
+ };
+ });
+ });
+
+ // Close this tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+ }
+
+ // Forget the site.
+ ForgetAboutSite.removeDataFromDomain(TEST_HOST);
+
+ // Open the tab again without setting the localStorage and check that the
+ // local storage has been cleared or not.
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Open our tab in the given user context without setting local storage.
+ let tabInfo = yield* openTabInUserContext(TEST_URL+ "file_set_storages.html", userContextId);
+
+ // Check that do storages be cleared or not.
+ yield ContentTask.spawn(tabInfo.browser, null, function* () {
+ // Check that does the local storage be cleared or not.
+ Assert.ok(!content.localStorage.getItem("userContext"), "The local storage has been cleared");
+
+ // Check that does the session storage be cleared or not.
+ Assert.ok(!content.sessionStorage.getItem("userContext"), "The session storage has been cleared");
+
+ // Check that does the indexedDB be cleared or not.
+ let request = content.indexedDB.open("idb", 1);
+
+ let db = yield new Promise(done => {
+ request.onsuccess = event => {
+ done(event.target.result);
+ };
+ });
+ try {
+ db.transaction(["obj"], "readonly");
+ Assert.ok(false, "The indexedDB should not exist");
+ } catch (e) {
+ Assert.equal(e.name, "NotFoundError", "The indexedDB does not exist as expected");
+ }
+ });
+
+ // Close the tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+ }
+}
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]}, resolve);
+ });
+
+ // Create a http server for the image cache test.
+ if (!gHttpServer) {
+ gHttpServer = new HttpServer();
+ gHttpServer.registerPathHandler('/image.png', imageHandler);
+ gHttpServer.registerPathHandler('/loadImage.html', loadImagePageHandler);
+ gHttpServer.start(-1);
+ }
+});
+
+let tests = [
+ test_cookie_cleared,
+ test_cache_cleared,
+ test_image_cache_cleared,
+ test_storage_cleared,
+];
+
+add_task(function* test() {
+ for (let i = 0; i < tests.length; i++)
+ add_task(tests[i]);
+});
+
+registerCleanupFunction(() => {
+ gHttpServer.stop(() => {
+ gHttpServer = null;
+ });
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_imageCache.js b/browser/components/contextualidentity/test/browser/browser_imageCache.js
new file mode 100644
index 000000000..df36d44c1
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_imageCache.js
@@ -0,0 +1,59 @@
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+const NUM_USER_CONTEXTS = 3;
+
+let gHits = 0;
+
+let server = new HttpServer();
+server.registerPathHandler('/image.png', imageHandler);
+server.registerPathHandler('/file.html', fileHandler);
+server.start(-1);
+
+let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
+let IMAGE_URI = BASE_URI + '/image.png';
+let FILE_URI = BASE_URI + '/file.html';
+
+function imageHandler(metadata, response) {
+ gHits++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function fileHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = `<html><body><image src=${IMAGE_URI}></body></html>`;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [["privacy.userContext.enabled", true]]});
+});
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function* openTabInUserContext(uri, userContextId) {
+ // open the tab in the correct userContextId
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerDocument.defaultView.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return tab;
+}
+
+add_task(function* test() {
+ for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
+ let tab = yield* openTabInUserContext(FILE_URI, userContextId);
+ gBrowser.removeTab(tab);
+ }
+ is(gHits, NUM_USER_CONTEXTS, "should get an image request for each user contexts");
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_middleClick.js b/browser/components/contextualidentity/test/browser/browser_middleClick.js
new file mode 100644
index 000000000..f3bed2b53
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_middleClick.js
@@ -0,0 +1,41 @@
+"use strict";
+
+const BASE_ORIGIN = "http://example.com";
+const URI = BASE_ORIGIN +
+ "/browser/browser/components/contextualidentity/test/browser/empty_file.html";
+
+add_task(function* () {
+ info("Opening a new container tab...");
+
+ let tab = gBrowser.addTab(URI, { userContextId: 1 });
+ gBrowser.selectedTab = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ info("Create a HTMLAnchorElement...");
+ yield ContentTask.spawn(browser, URI,
+ function(URI) {
+ let anchor = content.document.createElement("a");
+ anchor.setAttribute('id', 'clickMe');
+ anchor.setAttribute("href", URI);
+ anchor.appendChild(content.document.createTextNode("click me!"));
+ content.document.body.appendChild(anchor);
+ }
+ );
+
+ info("Synthesize a mouse click and wait for a new tab...");
+ let newTab = yield new Promise((resolve, reject) => {
+ gBrowser.tabContainer.addEventListener("TabOpen", function onTabOpen(openEvent) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen);
+ resolve(openEvent.target);
+ })
+
+ BrowserTestUtils.synthesizeMouseAtCenter("#clickMe", { button: 1 }, browser);
+ });
+
+ is(newTab.getAttribute("usercontextid"), 1, "Correct UserContextId?");
+
+ yield BrowserTestUtils.removeTab(tab);
+ yield BrowserTestUtils.removeTab(newTab);
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_newtabButton.js b/browser/components/contextualidentity/test/browser/browser_newtabButton.js
new file mode 100644
index 000000000..228e6f971
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_newtabButton.js
@@ -0,0 +1,35 @@
+"use strict";
+
+// Testing that when the user opens the add tab menu and clicks menu items
+// the correct context id is opened
+
+add_task(function* test() {
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]});
+
+ let newTab = document.getElementById('tabbrowser-tabs');
+ let newTabButton = document.getAnonymousElementByAttribute(newTab, "anonid", "tabs-newtab-button");
+ ok(newTabButton, "New tab button exists");
+ ok(!newTabButton.hidden, "New tab button is visible");
+ yield BrowserTestUtils.waitForCondition(() => !!document.getAnonymousElementByAttribute(newTab, "anonid", "newtab-popup"), "Wait for popup to exist");
+ let popup = document.getAnonymousElementByAttribute(newTab, "anonid", "newtab-popup");
+
+ for (let i = 1; i <= 4; i++) {
+ let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(newTabButton, {type: "mousedown"});
+
+ yield popupShownPromise;
+ let contextIdItem = popup.querySelector(`menuitem[data-usercontextid="${i}"]`);
+
+ ok(contextIdItem, `User context id ${i} exists`);
+
+ let waitForTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ EventUtils.synthesizeMouseAtCenter(contextIdItem, {});
+
+ let tab = yield waitForTabPromise;
+
+ is(tab.getAttribute('usercontextid'), i, `New tab has UCI equal ${i}`);
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_serviceworkers.js b/browser/components/contextualidentity/test/browser/browser_serviceworkers.js
new file mode 100644
index 000000000..b074b91ac
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_serviceworkers.js
@@ -0,0 +1,108 @@
+let { classes: Cc, interfaces: Ci } = Components;
+
+let swm = Cc["@mozilla.org/serviceworkers/manager;1"].
+ getService(Ci.nsIServiceWorkerManager);
+
+const BASE_ORIGIN = "https://example.com";
+const URI = BASE_ORIGIN +
+ "/browser/browser/components/contextualidentity/test/browser/serviceworker.html";
+const NUM_USER_CONTEXTS = 3;
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function openTabInUserContext(uri, userContextId) {
+ // open the tab in the correct userContextId
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ return tab;
+}
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.ipc.processCount", 1]
+ ]}, resolve);
+ });
+});
+
+let infos = [];
+
+add_task(function* test() {
+ // Open the same URI in multiple user contexts, and make sure we have a
+ // separate service worker in each of the contexts
+ for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
+ // Open a tab in given user contexts
+ let tab = openTabInUserContext(URI, userContextId);
+
+ // wait for tab load
+ yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
+
+ // remove the tab
+ gBrowser.removeTab(tab);
+ }
+
+ if (!allRegistered()) {
+ yield promiseAllRegistered();
+ }
+ ok(true, "all service workers are registered");
+
+ // Unregistered all service workers added in this test
+ for (let info of infos) {
+ yield promiseUnregister(info);
+ }
+});
+
+function allRegistered() {
+ let results = [];
+ let registrations = swm.getAllRegistrations();
+ for (let i = 0; i < registrations.length; i++) {
+ let info = registrations.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+ let principal = info.principal;
+ if (principal.originNoSuffix === BASE_ORIGIN) {
+ results[principal.userContextId] = true;
+ infos[principal.userContextId] = info;
+ }
+ }
+ for (let userContextId = 0; userContextId < NUM_USER_CONTEXTS; userContextId++) {
+ if (!results[userContextId]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function promiseAllRegistered() {
+ return new Promise(function(resolve) {
+ let listener = {
+ onRegister: function() {
+ if (allRegistered()) {
+ swm.removeListener(listener);
+ resolve();
+ }
+ }
+ }
+ swm.addListener(listener);
+ });
+}
+
+function promiseUnregister(info) {
+ return new Promise(function(resolve) {
+ swm.unregister(info.principal, {
+ unregisterSucceeded: function(aState) {
+ ok(aState, "ServiceWorkerRegistration exists");
+ resolve();
+ },
+ unregisterFailed: function(aState) {
+ ok(false, "unregister should succeed");
+ }
+ }, info.scope);
+ });
+}
diff --git a/browser/components/contextualidentity/test/browser/browser_usercontext.js b/browser/components/contextualidentity/test/browser/browser_usercontext.js
new file mode 100644
index 000000000..e0e785d3f
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_usercontext.js
@@ -0,0 +1,86 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+const USER_CONTEXTS = [
+ "default",
+ "personal",
+ "work",
+];
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ + "contextualidentity/test/browser/file_reflect_cookie_into_title.html";
+
+
+// opens `uri' in a new tab with the provided userContextId and focuses it.
+// returns the newly opened tab
+function openTabInUserContext(uri, userContextId) {
+ // open the tab in the correct userContextId
+ let tab = gBrowser.addTab(uri, {userContextId});
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ return tab;
+}
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true],
+ ["dom.ipc.processCount", 1]
+ ]}, resolve);
+ });
+});
+
+add_task(function* test() {
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // load the page in 3 different contexts and set a cookie
+ // which should only be visible in that context
+ let cookie = USER_CONTEXTS[userContextId];
+
+ // open our tab in the given user context
+ let tab = openTabInUserContext(BASE_URI+"?"+cookie, userContextId);
+
+ // wait for tab load
+ yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
+
+ // remove the tab
+ gBrowser.removeTab(tab);
+ }
+
+ {
+ // Set a cookie in a different context so we can detect if that affects
+ // cross-context properly. If we don't do that, we get an UNEXPECTED-PASS
+ // for the localStorage case for the last tab we set.
+ let tab = openTabInUserContext(BASE_URI+"?foo", 9999);
+ yield BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
+ gBrowser.removeTab(tab);
+ }
+
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Load the page without setting the cookie this time
+ let expectedContext = USER_CONTEXTS[userContextId];
+
+ let tab = openTabInUserContext(BASE_URI, userContextId);
+
+ // wait for load
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // get the title
+ let title = browser.contentDocument.title.trim().split("|");
+
+ // check each item in the title and validate it meets expectatations
+ for (let part of title) {
+ let [storageMethodName, value] = part.split("=");
+ is(value, expectedContext,
+ "the title reflects the expected contextual identity of " +
+ expectedContext + " for method " + storageMethodName + ": " + value);
+ }
+
+ gBrowser.removeTab(tab);
+ }
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_usercontextid_tabdrop.js b/browser/components/contextualidentity/test/browser/browser_usercontextid_tabdrop.js
new file mode 100644
index 000000000..6a8fbc591
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_usercontextid_tabdrop.js
@@ -0,0 +1,134 @@
+"use strict";
+
+let EventUtils = {};
+Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+/**
+ * Dragging an URL to a tab without userContextId set.
+ */
+add_task(function* () {
+ let tab = gBrowser.addTab("http://example.com/");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://test1.example.com/");
+
+ // A drop type of "link" onto an existing tab would normally trigger a
+ // load in that same tab, but tabbrowser code in _getDragTargetTab treats
+ // drops on the outer edges of a tab differently (loading a new tab
+ // instead). Make events created by synthesizeDrop have all of their
+ // coordinates set to 0 (screenX/screenY), so they're treated as drops
+ // on the outer edge of the tab, thus they open new tabs.
+ let event = {
+ clientX: 0,
+ clientY: 0,
+ screenX: 0,
+ screenY: 0,
+ };
+ EventUtils.synthesizeDrop(tab, tab, [[{type: "text/plain", data: "http://test1.example.com/"}]], "link", window, undefined, event);
+
+ yield awaitDrop;
+
+ let tab2 = yield newTabPromise;
+ Assert.ok(!tab2.hasAttribute("usercontextid"), "Tab shouldn't have usercontextid attribute");
+
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+
+ yield ContentTask.spawn(tab2.linkedBrowser, {}, function* () {
+ Assert.equal(content.document.documentURI, "http://test1.example.com/");
+ Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, 0);
+
+ // referrer is empty when urls are dragged to new or existing tabs.
+ // If this changes in the future, it would be okay to send the referrer
+ // in this case because we are creating a new tab with the default
+ // usercontextid as the original tab.
+ Assert.equal(content.document.referrer, "", "referrer should be empty");
+ });
+
+ yield BrowserTestUtils.removeTab(tab);
+ yield BrowserTestUtils.removeTab(tab2);
+});
+
+/**
+ * When dragging an URL to a new tab, the new tab should have the same
+ * userContextId as the original tab.
+ */
+add_task(function* () {
+ let tab = gBrowser.addTab("http://example.com/", {userContextId: 1});
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://test1.example.com/");
+
+ // A drop type of "link" onto an existing tab would normally trigger a
+ // load in that same tab, but tabbrowser code in _getDragTargetTab treats
+ // drops on the outer edges of a tab differently (loading a new tab
+ // instead). Make events created by synthesizeDrop have all of their
+ // coordinates set to 0 (screenX/screenY), so they're treated as drops
+ // on the outer edge of the tab, thus they open new tabs.
+ let event = {
+ clientX: 0,
+ clientY: 0,
+ screenX: 0,
+ screenY: 0,
+ };
+ EventUtils.synthesizeDrop(tab, tab, [[{type: "text/plain", data: "http://test1.example.com/"}]], "link", window, undefined, event);
+
+ yield awaitDrop;
+
+ let tab2 = yield newTabPromise;
+ Assert.equal(tab2.getAttribute("usercontextid"), 1);
+
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+
+ yield ContentTask.spawn(tab2.linkedBrowser, {}, function* () {
+ Assert.equal(content.document.documentURI, "http://test1.example.com/");
+ Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, 1);
+
+ // referrer is empty when urls are dragged to new or existing tabs.
+ // If this changes in the future, it would be okay to send the referrer
+ // in this case because we are creating a new tab with the same
+ // usercontextid as the original tab.
+ Assert.equal(content.document.referrer, "", "referrer should be empty");
+ });
+
+ yield BrowserTestUtils.removeTab(tab);
+ yield BrowserTestUtils.removeTab(tab2);
+});
+
+/**
+ * When dragging a URL from one tab or link on a tab to an existing tab, the
+ * existing tab should not change its userContextId.
+ * Ex: if you drag a link from tab 1 with userContext 1 to tab 2 with
+ * userContext 2, the link will open in tab 2 with userContext 2.
+ */
+add_task(function* () {
+ let tab = gBrowser.addTab("http://example.com/", {userContextId: 1});
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let tab2 = gBrowser.addTab("http://example.org/", {userContextId: 2});
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+
+ let awaitDrop = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "drop");
+
+ EventUtils.synthesizeDrop(tab, tab2, [[{type: "text/plain", data: "http://test1.example.com/"}]], "link", window);
+
+ yield awaitDrop;
+ Assert.equal(tab2.getAttribute("usercontextid"), 2);
+
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+
+ yield ContentTask.spawn(tab2.linkedBrowser, {}, function* () {
+ Assert.equal(content.document.documentURI, "http://test1.example.com/");
+ Assert.equal(content.document.nodePrincipal.originAttributes.userContextId, 2);
+
+ // referrer is empty when urls are dragged to new or existing tabs.
+ // If this changes in the future, we should ensure that we are not sending
+ // a referrer for this case! When opening links across user contexts, we
+ // don't want the referrer to follow the user from one context to another.
+ Assert.equal(content.document.referrer, "", "referrer should be empty");
+ });
+
+ yield BrowserTestUtils.removeTab(tab);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_windowName.js b/browser/components/contextualidentity/test/browser/browser_windowName.js
new file mode 100644
index 000000000..555c421ce
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_windowName.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+const USER_CONTEXTS = [
+ "default",
+ "personal",
+ "work",
+];
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ + "contextualidentity/test/browser/empty_file.html";
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true],
+ ["browser.link.open_newwindow", 3],
+ ]}, resolve);
+ });
+});
+
+add_task(function* test() {
+ info("Creating first tab...");
+ let tab1 = gBrowser.addTab(BASE_URI + '?old', {userContextId: 1});
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+ yield BrowserTestUtils.browserLoaded(browser1);
+ yield ContentTask.spawn(browser1, null, function(opts) {
+ content.window.name = 'tab-1';
+ });
+
+ info("Creating second tab...");
+ let tab2 = gBrowser.addTab(BASE_URI + '?old', {userContextId: 2});
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+ yield BrowserTestUtils.browserLoaded(browser2);
+ yield ContentTask.spawn(browser2, null, function(opts) {
+ content.window.name = 'tab-2';
+ });
+
+ // Let's try to open a window from tab1 with a name 'tab-2'.
+ info("Opening a window from the first tab...");
+ yield ContentTask.spawn(browser1, { url: BASE_URI + '?new' }, function* (opts) {
+ yield (new content.window.wrappedJSObject.Promise(resolve => {
+ let w = content.window.wrappedJSObject.open(opts.url, 'tab-2');
+ w.onload = function() { resolve(); }
+ }));
+ });
+
+ is(browser1.contentTitle, '?old', "Tab1 title must be 'old'");
+ is(browser1.contentPrincipal.userContextId, 1, "Tab1 UCI must be 1");
+
+ is(browser2.contentTitle, '?old', "Tab2 title must be 'old'");
+ is(browser2.contentPrincipal.userContextId, 2, "Tab2 UCI must be 2");
+
+ let found = false;
+ for (let i = 0; i < gBrowser.tabContainer.childNodes.length; ++i) {
+ let tab = gBrowser.tabContainer.childNodes[i];
+ let browser = gBrowser.getBrowserForTab(tab);
+ if (browser.contentTitle == '?new') {
+ is(browser.contentPrincipal.userContextId, 1, "Tab3 UCI must be 1");
+ isnot(browser, browser1, "Tab3 is not browser 1");
+ isnot(browser, browser2, "Tab3 is not browser 2");
+ gBrowser.removeTab(tab);
+ found = true;
+ break;
+ }
+ }
+
+ ok(found, "We have tab3");
+
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+});
diff --git a/browser/components/contextualidentity/test/browser/browser_windowOpen.js b/browser/components/contextualidentity/test/browser/browser_windowOpen.js
new file mode 100644
index 000000000..00c6e0aa0
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/browser_windowOpen.js
@@ -0,0 +1,41 @@
+"use strict";
+
+// Here we want to test that a new opened window shows the same UI of the
+// parent one if this has been loaded from a particular container.
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ + "contextualidentity/test/browser/empty_file.html";
+
+add_task(function* setup() {
+ yield new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true],
+ ["browser.link.open_newwindow", 2],
+ ]}, resolve);
+ });
+});
+
+
+add_task(function* test() {
+ info("Creating a tab with UCI = 1...");
+ let tab = gBrowser.addTab(BASE_URI, {userContextId: 1});
+ is(tab.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ info("Opening a new window from this tab...");
+ ContentTask.spawn(browser, BASE_URI, function(url) {
+ content.window.newWindow = content.window.open(url, "_blank");
+ });
+
+ let newWin = yield BrowserTestUtils.waitForNewWindow();
+ let newTab = newWin.gBrowser.selectedTab;
+
+ yield BrowserTestUtils.browserLoaded(newTab.linkedBrowser);
+ is(newTab.getAttribute('usercontextid'), 1, "New tab has UCI equal 1");
+
+ info("Closing the new window and tab...");
+ yield BrowserTestUtils.closeWindow(newWin);
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/contextualidentity/test/browser/empty_file.html b/browser/components/contextualidentity/test/browser/empty_file.html
new file mode 100644
index 000000000..c6d11dcd5
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/empty_file.html
@@ -0,0 +1,5 @@
+<html><body>
+<script>
+document.title = window.location.search;
+</script>
+</body></html>
diff --git a/browser/components/contextualidentity/test/browser/favicon-normal32.png b/browser/components/contextualidentity/test/browser/favicon-normal32.png
new file mode 100644
index 000000000..5535363c9
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/favicon-normal32.png
Binary files differ
diff --git a/browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html b/browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html
new file mode 100644
index 000000000..b04f3fd5c
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/file_reflect_cookie_into_title.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>title not set</title>
+ <script>
+ // if we have a query string, use it to set the cookie and localStorage
+ if (window.location.search.length > 0) {
+ let context_name = window.location.search.substr(1);
+ document.cookie = "userContextId=" + context_name;
+ localStorage.setItem("userContext", context_name);
+ }
+
+ // get the cookie
+ let [name, val] = document.cookie.split("=");
+
+ // set the title to reflect the cookie and local storage values we find
+ document.title = "cookie=" + val + "|"
+ + "local=" + localStorage.getItem("userContext");
+ </script>
+ </head>
+ <body></body>
+</html>
+
diff --git a/browser/components/contextualidentity/test/browser/file_set_storages.html b/browser/components/contextualidentity/test/browser/file_set_storages.html
new file mode 100644
index 000000000..c6adcbdde
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/file_set_storages.html
@@ -0,0 +1,41 @@
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>Bug 1238183</title>
+ </head>
+ <body>
+ <script type="application/javascript;version=1.7">
+ "use strict";
+
+ // if we have a query string, use it to set storages
+ if (window.location.search.length > 0) {
+ let context_name = window.location.search.substr(1);
+ localStorage.setItem("userContext", context_name);
+ sessionStorage.setItem("userContext", context_name);
+
+ let request = indexedDB.open("idb", 1);
+
+ request.onerror = function() {
+ throw new Error("error opening db connection");
+ };
+
+ request.onupgradeneeded = event => {
+ let db = event.target.result;
+ let store = db.createObjectStore("obj", { keyPath: "id" });
+ store.createIndex("userContext", "userContext", { unique: false });
+ };
+
+ request.onsuccess = event => {
+ let db = request.result;
+ let transaction = db.transaction(["obj"], "readwrite");
+ let store = transaction.objectStore("obj");
+ store.add({id: 1, userContext: context_name});
+
+ transaction.oncomplete = () => {
+ db.close();
+ };
+ };
+ }
+ </script>
+ </body>
+</html>
diff --git a/browser/components/contextualidentity/test/browser/serviceworker.html b/browser/components/contextualidentity/test/browser/serviceworker.html
new file mode 100644
index 000000000..11edd001a
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/serviceworker.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <script>
+ navigator.serviceWorker.register("worker.js");
+ </script>
+ </head>
+ <body>
+ This is a test page.
+ </body>
+<html>
diff --git a/browser/components/contextualidentity/test/browser/worker.js b/browser/components/contextualidentity/test/browser/worker.js
new file mode 100644
index 000000000..2aba167d1
--- /dev/null
+++ b/browser/components/contextualidentity/test/browser/worker.js
@@ -0,0 +1 @@
+// empty worker, always succeed!
diff --git a/browser/components/controlcenter/content/panel.inc.xul b/browser/components/controlcenter/content/panel.inc.xul
new file mode 100644
index 000000000..361b3c945
--- /dev/null
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -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/. -->
+
+<panel id="identity-popup"
+ type="arrow"
+ hidden="true"
+ role="alertdialog"
+ onpopupshown="gIdentityHandler.onPopupShown(event);"
+ onpopuphidden="gIdentityHandler.onPopupHidden(event);"
+ orient="vertical">
+
+ <broadcasterset>
+ <broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
+ <broadcaster id="identity-popup-insecure-login-forms-learn-more" class="text-link plain" value="&identity.learnMore;"/>
+ </broadcasterset>
+
+ <panelmultiview id="identity-popup-multiView"
+ mainViewId="identity-popup-mainView">
+ <panelview id="identity-popup-mainView" flex="1">
+
+ <!-- Security Section -->
+ <hbox id="identity-popup-security" class="identity-popup-section">
+ <vbox id="identity-popup-security-content" flex="1">
+ <label class="plain">
+ <label class="identity-popup-headline host"></label>
+ <label class="identity-popup-headline hostless" crop="end"/>
+ </label>
+ <description class="identity-popup-connection-not-secure"
+ value="&identity.connectionNotSecure;"
+ when-connection="not-secure secure-cert-user-overridden"/>
+ <description class="identity-popup-connection-secure"
+ value="&identity.connectionSecure;"
+ when-connection="secure secure-ev"/>
+ <description value="&identity.connectionInternal;"
+ when-connection="chrome"/>
+ <description value="&identity.connectionFile;"
+ when-connection="file"/>
+
+ <vbox id="identity-popup-security-descriptions">
+ <description class="identity-popup-warning-gray"
+ when-mixedcontent="active-blocked">&identity.activeBlocked;</description>
+ <description class="identity-popup-warning-yellow"
+ when-mixedcontent="passive-loaded">&identity.passiveLoaded;</description>
+ <description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
+ <description class="identity-popup-warning-yellow"
+ when-ciphers="weak">&identity.weakEncryption;</description>
+ <description when-loginforms="insecure">&identity.insecureLoginForms2;</description>
+ </vbox>
+ </vbox>
+ <button id="identity-popup-security-expander"
+ class="identity-popup-expander"
+ when-connection="not-secure secure secure-ev secure-cert-user-overridden"
+ oncommand="gIdentityHandler.toggleSubView('security', this)"/>
+ </hbox>
+
+ <!-- Tracking Protection Section -->
+ <hbox id="tracking-protection-container"
+ class="identity-popup-section"
+ when-connection="not-secure secure secure-ev secure-cert-user-overridden file">
+ <vbox id="tracking-protection-content" flex="1">
+ <description class="identity-popup-headline"
+ crop="end"
+ value="&trackingProtection.title;" />
+
+ <label id="tracking-blocked"
+ crop="end">&trackingProtection.detectedBlocked3;</label>
+ <label id="tracking-loaded"
+ crop="end">&trackingProtection.detectedNotBlocked3;</label>
+ <label id="tracking-not-detected"
+ crop="end">&trackingProtection.notDetected3;</label>
+
+ <button id="tracking-action-unblock"
+ label="&trackingProtection.unblock.label;"
+ accesskey="&trackingProtection.unblock.accesskey;"
+ oncommand="TrackingProtection.disableForCurrentPage();" />
+ <button id="tracking-action-unblock-private"
+ label="&trackingProtection.unblockPrivate.label;"
+ accesskey="&trackingProtection.unblockPrivate.accesskey;"
+ oncommand="TrackingProtection.disableForCurrentPage();" />
+ <button id="tracking-action-block"
+ label="&trackingProtection.block2.label;"
+ accesskey="&trackingProtection.block2.accesskey;"
+ oncommand="TrackingProtection.enableForCurrentPage();" />
+ </vbox>
+ </hbox>
+
+ <!-- Permissions Section -->
+ <hbox class="identity-popup-section">
+ <vbox id="identity-popup-permissions-content" flex="1">
+ <label id="identity-popup-permissions-headline"
+ class="identity-popup-headline"
+ value="&identity.permissions;"/>
+ <vbox id="identity-popup-permission-list"/>
+ <description id="identity-popup-permission-reload-hint">&identity.permissionsReloadHint;</description>
+ <description id="identity-popup-permission-empty-hint">&identity.permissionsEmpty;</description>
+ </vbox>
+ </hbox>
+ </panelview>
+
+ <!-- Security SubView -->
+ <panelview id="identity-popup-securityView" flex="1">
+ <vbox id="identity-popup-securityView-header">
+ <label class="plain">
+ <label class="identity-popup-headline host"></label>
+ <label class="identity-popup-headline hostless" crop="end"/>
+ </label>
+ <description class="identity-popup-connection-not-secure"
+ value="&identity.connectionNotSecure;"
+ when-connection="not-secure secure-cert-user-overridden"/>
+ <description class="identity-popup-connection-secure"
+ value="&identity.connectionSecure;"
+ when-connection="secure secure-ev"/>
+ </vbox>
+
+ <vbox id="identity-popup-securityView-body" flex="1">
+ <!-- (EV) Certificate Information -->
+ <description id="identity-popup-content-verified-by"
+ when-connection="secure-ev">&identity.connectionVerified2;</description>
+ <description id="identity-popup-content-owner"
+ when-connection="secure-ev"
+ class="header"/>
+ <description id="identity-popup-content-supplemental"
+ when-connection="secure-ev"/>
+ <description id="identity-popup-content-verifier"
+ when-connection="secure secure-ev secure-cert-user-overridden"/>
+
+ <!-- Remove Certificate Exception -->
+ <button when-connection="secure-cert-user-overridden"
+ label="&identity.removeCertException.label;"
+ accesskey="&identity.removeCertException.accesskey;"
+ oncommand="gIdentityHandler.removeCertException()"/>
+
+ <!-- Connection is Not Secure -->
+ <description when-connection="not-secure"
+ and-when-loginforms="secure">&identity.description.insecure;</description>
+
+ <!-- Insecure login forms -->
+ <description when-loginforms="insecure">&identity.description.insecureLoginForms; <label observes="identity-popup-insecure-login-forms-learn-more"/></description>
+
+ <!-- Weak Cipher -->
+ <description when-ciphers="weak">&identity.description.weakCipher;</description>
+ <description class="identity-popup-warning-yellow"
+ when-ciphers="weak">&identity.description.weakCipher2;</description>
+
+ <!-- Active Mixed Content Blocked -->
+ <description class="identity-popup-warning-gray"
+ when-mixedcontent="active-blocked">&identity.description.activeBlocked; <label observes="identity-popup-mcb-learn-more"/></description>
+
+ <!-- Passive Mixed Content Loaded -->
+ <description when-mixedcontent="passive-loaded">&identity.description.passiveLoaded;</description>
+ <description class="identity-popup-warning-yellow"
+ when-mixedcontent="passive-loaded">&identity.description.passiveLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
+
+ <!-- Passive Mixed Content Loaded, Active Mixed Content Blocked -->
+ <description when-mixedcontent="passive-loaded active-blocked">&identity.description.passiveLoaded;</description>
+ <description when-mixedcontent="passive-loaded active-blocked"
+ class="identity-popup-warning-yellow">&identity.description.passiveLoaded3; <label observes="identity-popup-mcb-learn-more"/></description>
+
+ <!-- Active Mixed Content Blocking Disabled -->
+ <description when-mixedcontent="active-loaded"
+ and-when-loginforms="secure">&identity.description.activeLoaded;</description>
+ <description when-mixedcontent="active-loaded"
+ and-when-loginforms="secure">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
+ <!-- Show only the first message when there are insecure login forms,
+ and make sure the Learn More link is included. -->
+ <description when-mixedcontent="active-loaded"
+ and-when-loginforms="insecure">&identity.description.activeLoaded; <label observes="identity-popup-mcb-learn-more"/></description>
+
+ <!-- Buttons to enable/disable mixed content blocking. -->
+ <button when-mixedcontent="active-blocked"
+ label="&identity.disableMixedContentBlocking.label;"
+ accesskey="&identity.disableMixedContentBlocking.accesskey;"
+ oncommand="gIdentityHandler.disableMixedContentProtection()"/>
+ <button when-mixedcontent="active-loaded"
+ label="&identity.enableMixedContentBlocking.label;"
+ accesskey="&identity.enableMixedContentBlocking.accesskey;"
+ oncommand="gIdentityHandler.enableMixedContentProtection()"/>
+ </vbox>
+
+ <vbox id="identity-popup-securityView-footer">
+ <!-- More Security Information -->
+ <button label="&identity.moreInfoLinkText2;"
+ oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
+ </vbox>
+
+ </panelview>
+ </panelmultiview>
+</panel>
diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm
new file mode 100644
index 000000000..86ff2708b
--- /dev/null
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -0,0 +1,4420 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["CustomizableUI"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PanelWideWidgetTracker",
+ "resource:///modules/PanelWideWidgetTracker.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets",
+ "resource:///modules/CustomizableWidgets.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+ "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
+ const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
+ return Services.strings.createBundle(kUrl);
+});
+XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
+ "resource://gre/modules/ShortcutUtils.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gELS",
+ "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
+ "resource://gre/modules/LightweightThemeManager.jsm");
+
+const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const kSpecialWidgetPfx = "customizableui-special-";
+
+const kPrefCustomizationState = "browser.uiCustomization.state";
+const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
+const kPrefCustomizationDebug = "browser.uiCustomization.debug";
+const kPrefDrawInTitlebar = "browser.tabs.drawInTitlebar";
+const kPrefWebIDEInNavbar = "devtools.webide.widget.inNavbarByDefault";
+
+const kExpectedWindowURL = "chrome://browser/content/browser.xul";
+
+/**
+ * The keys are the handlers that are fired when the event type (the value)
+ * is fired on the subview. A widget that provides a subview has the option
+ * of providing onViewShowing and onViewHiding event handlers.
+ */
+const kSubviewEvents = [
+ "ViewShowing",
+ "ViewHiding"
+];
+
+/**
+ * The current version. We can use this to auto-add new default widgets as necessary.
+ * (would be const but isn't because of testing purposes)
+ */
+var kVersion = 6;
+
+/**
+ * Buttons removed from built-ins by version they were removed. kVersion must be
+ * bumped any time a new id is added to this. Use the button id as key, and
+ * version the button is removed in as the value. e.g. "pocket-button": 5
+ */
+var ObsoleteBuiltinButtons = {
+ "pocket-button": 6
+};
+
+/**
+ * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed
+ * on their IDs.
+ */
+var gPalette = new Map();
+
+/**
+ * gAreas maps area IDs to Sets of properties about those areas. An area is a
+ * place where a widget can be put.
+ */
+var gAreas = new Map();
+
+/**
+ * gPlacements maps area IDs to Arrays of widget IDs, indicating that the widgets
+ * are placed within that area (either directly in the area node, or in the
+ * customizationTarget of the node).
+ */
+var gPlacements = new Map();
+
+/**
+ * gFuturePlacements represent placements that will happen for areas that have
+ * not yet loaded (due to lazy-loading). This can occur when add-ons register
+ * widgets.
+ */
+var gFuturePlacements = new Map();
+
+// XXXunf Temporary. Need a nice way to abstract functions to build widgets
+// of these types.
+var gSupportedWidgetTypes = new Set(["button", "view", "custom"]);
+
+/**
+ * gPanelsForWindow is a list of known panels in a window which we may need to close
+ * should command events fire which target them.
+ */
+var gPanelsForWindow = new WeakMap();
+
+/**
+ * gSeenWidgets remembers which widgets the user has seen for the first time
+ * before. This way, if a new widget is created, and the user has not seen it
+ * before, it can be put in its default location. Otherwise, it remains in the
+ * palette.
+ */
+var gSeenWidgets = new Set();
+
+/**
+ * gDirtyAreaCache is a set of area IDs for areas where items have been added,
+ * moved or removed at least once. This set is persisted, and is used to
+ * optimize building of toolbars in the default case where no toolbars should
+ * be "dirty".
+ */
+var gDirtyAreaCache = new Set();
+
+/**
+ * gPendingBuildAreas is a map from area IDs to map from build nodes to their
+ * existing children at the time of node registration, that are waiting
+ * for the area to be registered
+ */
+var gPendingBuildAreas = new Map();
+
+var gSavedState = null;
+var gRestoring = false;
+var gDirty = false;
+var gInBatchStack = 0;
+var gResetting = false;
+var gUndoResetting = false;
+
+/**
+ * gBuildAreas maps area IDs to actual area nodes within browser windows.
+ */
+var gBuildAreas = new Map();
+
+/**
+ * gBuildWindows is a map of windows that have registered build areas, mapped
+ * to a Set of known toolboxes in that window.
+ */
+var gBuildWindows = new Map();
+
+var gNewElementCount = 0;
+var gGroupWrapperCache = new Map();
+var gSingleWrapperCache = new WeakMap();
+var gListeners = new Set();
+
+var gUIStateBeforeReset = {
+ uiCustomizationState: null,
+ drawInTitlebar: null,
+ currentTheme: null,
+};
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let scope = {};
+ Cu.import("resource://gre/modules/Console.jsm", scope);
+ let debug;
+ try {
+ debug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
+ } catch (ex) {}
+ let consoleOptions = {
+ maxLogLevel: debug ? "all" : "log",
+ prefix: "CustomizableUI",
+ };
+ return new scope.ConsoleAPI(consoleOptions);
+});
+
+var CustomizableUIInternal = {
+ initialize: function() {
+ log.debug("Initializing");
+
+ this.addListener(this);
+ this._defineBuiltInWidgets();
+ this.loadSavedState();
+ this._introduceNewBuiltinWidgets();
+ this._markObsoleteBuiltinButtonsSeen();
+
+ /**
+ * Please be advised that adding items to the panel by default could
+ * cause CART talos test regressions. This might happen when the
+ * number of items in the panel causes the area to become "scrollable"
+ * during the last phases of the transition. See bug 1230671 for an
+ * example of this. Be sure that what you're adding really needs to go
+ * into the panel by default, and if it does, consider swapping
+ * something out for it.
+ */
+ let panelPlacements = [
+ "edit-controls",
+ "zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "sync-button",
+ ];
+
+ if (!AppConstants.MOZ_DEV_EDITION) {
+ panelPlacements.splice(-1, 0, "developer-button");
+ }
+
+ if (AppConstants.E10S_TESTING_ONLY) {
+ if (gPalette.has("e10s-button")) {
+ let newWindowIndex = panelPlacements.indexOf("new-window-button");
+ if (newWindowIndex > -1) {
+ panelPlacements.splice(newWindowIndex + 1, 0, "e10s-button");
+ }
+ }
+ }
+
+ let showCharacterEncoding = Services.prefs.getComplexValue(
+ "browser.menu.showCharacterEncoding",
+ Ci.nsIPrefLocalizedString
+ ).data;
+ if (showCharacterEncoding == "true") {
+ panelPlacements.push("characterencoding-button");
+ }
+
+ this.registerArea(CustomizableUI.AREA_PANEL, {
+ anchor: "PanelUI-menu-button",
+ type: CustomizableUI.TYPE_MENU_PANEL,
+ defaultPlacements: panelPlacements
+ }, true);
+ PanelWideWidgetTracker.init();
+
+ let navbarPlacements = [
+ "urlbar-container",
+ "search-container",
+ "bookmarks-menu-button",
+ "downloads-button",
+ "home-button",
+ ];
+
+ if (AppConstants.MOZ_DEV_EDITION) {
+ navbarPlacements.splice(2, 0, "developer-button");
+ }
+
+ if (Services.prefs.getBoolPref(kPrefWebIDEInNavbar)) {
+ navbarPlacements.push("webide-button");
+ }
+
+ // Place this last, when createWidget is called for pocket, it will
+ // append to the toolbar.
+ if (Services.prefs.getPrefType("extensions.pocket.enabled") != Services.prefs.PREF_INVALID &&
+ Services.prefs.getBoolPref("extensions.pocket.enabled")) {
+ navbarPlacements.push("pocket-button");
+ }
+
+ this.registerArea(CustomizableUI.AREA_NAVBAR, {
+ legacy: true,
+ type: CustomizableUI.TYPE_TOOLBAR,
+ overflowable: true,
+ defaultPlacements: navbarPlacements,
+ defaultCollapsed: false,
+ }, true);
+
+ if (AppConstants.platform != "macosx") {
+ this.registerArea(CustomizableUI.AREA_MENUBAR, {
+ legacy: true,
+ type: CustomizableUI.TYPE_TOOLBAR,
+ defaultPlacements: [
+ "menubar-items",
+ ],
+ get defaultCollapsed() {
+ if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
+ if (AppConstants.platform == "linux") {
+ return true;
+ }
+ // This is duplicated logic from /browser/base/jar.mn
+ // for win6BrowserOverlay.xul.
+ return AppConstants.isPlatformAndVersionAtLeast("win", 6);
+ }
+ return false;
+ }
+ }, true);
+ }
+
+ this.registerArea(CustomizableUI.AREA_TABSTRIP, {
+ legacy: true,
+ type: CustomizableUI.TYPE_TOOLBAR,
+ defaultPlacements: [
+ "tabbrowser-tabs",
+ "new-tab-button",
+ "alltabs-button",
+ ],
+ defaultCollapsed: null,
+ }, true);
+ this.registerArea(CustomizableUI.AREA_BOOKMARKS, {
+ legacy: true,
+ type: CustomizableUI.TYPE_TOOLBAR,
+ defaultPlacements: [
+ "personal-bookmarks",
+ ],
+ defaultCollapsed: true,
+ }, true);
+
+ this.registerArea(CustomizableUI.AREA_ADDONBAR, {
+ type: CustomizableUI.TYPE_TOOLBAR,
+ legacy: true,
+ defaultPlacements: ["addonbar-closebutton", "status-bar"],
+ defaultCollapsed: false,
+ }, true);
+ },
+
+ get _builtinToolbars() {
+ let toolbars = new Set([
+ CustomizableUI.AREA_NAVBAR,
+ CustomizableUI.AREA_BOOKMARKS,
+ CustomizableUI.AREA_TABSTRIP,
+ CustomizableUI.AREA_ADDONBAR,
+ ]);
+ if (AppConstants.platform != "macosx") {
+ toolbars.add(CustomizableUI.AREA_MENUBAR);
+ }
+ return toolbars;
+ },
+
+ _defineBuiltInWidgets: function() {
+ for (let widgetDefinition of CustomizableWidgets) {
+ this.createBuiltinWidget(widgetDefinition);
+ }
+ },
+
+ _introduceNewBuiltinWidgets: function() {
+ // We should still enter even if gSavedState.currentVersion >= kVersion
+ // because the per-widget pref facility is independent of versioning.
+ if (!gSavedState) {
+ // Flip all the prefs so we don't try to re-introduce later:
+ for (let [, widget] of gPalette) {
+ if (widget.defaultArea && widget._introducedInVersion === "pref") {
+ let prefId = "browser.toolbarbuttons.introduced." + widget.id;
+ Services.prefs.setBoolPref(prefId, true);
+ }
+ }
+ return;
+ }
+
+ let currentVersion = gSavedState.currentVersion;
+ for (let [id, widget] of gPalette) {
+ if (widget.defaultArea) {
+ let shouldAdd = false;
+ let shouldSetPref = false;
+ let prefId = "browser.toolbarbuttons.introduced." + widget.id;
+ if (widget._introducedInVersion === "pref") {
+ try {
+ shouldAdd = !Services.prefs.getBoolPref(prefId);
+ } catch (ex) {
+ // Pref doesn't exist:
+ shouldAdd = true;
+ }
+ shouldSetPref = shouldAdd;
+ } else if (widget._introducedInVersion > currentVersion) {
+ shouldAdd = true;
+ }
+
+ if (shouldAdd) {
+ let futurePlacements = gFuturePlacements.get(widget.defaultArea);
+ if (futurePlacements) {
+ futurePlacements.add(id);
+ } else {
+ gFuturePlacements.set(widget.defaultArea, new Set([id]));
+ }
+ if (shouldSetPref) {
+ Services.prefs.setBoolPref(prefId, true);
+ }
+ }
+ }
+ }
+
+ if (currentVersion < 2) {
+ // Nuke the old 'loop-call-button' out of orbit.
+ CustomizableUI.removeWidgetFromArea("loop-call-button");
+ }
+
+ if (currentVersion < 4) {
+ CustomizableUI.removeWidgetFromArea("loop-button-throttled");
+ }
+ },
+
+ /**
+ * _markObsoleteBuiltinButtonsSeen
+ * when upgrading, ensure obsoleted buttons are in seen state.
+ */
+ _markObsoleteBuiltinButtonsSeen: function() {
+ if (!gSavedState)
+ return;
+ let currentVersion = gSavedState.currentVersion;
+ if (currentVersion >= kVersion)
+ return;
+ // we're upgrading, update state if necessary
+ for (let id in ObsoleteBuiltinButtons) {
+ let version = ObsoleteBuiltinButtons[id]
+ if (version == kVersion) {
+ gSeenWidgets.add(id);
+ gDirty = true;
+ }
+ }
+ },
+
+ _placeNewDefaultWidgetsInArea: function(aArea) {
+ let futurePlacedWidgets = gFuturePlacements.get(aArea);
+ let savedPlacements = gSavedState && gSavedState.placements && gSavedState.placements[aArea];
+ let defaultPlacements = gAreas.get(aArea).get("defaultPlacements");
+ if (!savedPlacements || !savedPlacements.length || !futurePlacedWidgets || !defaultPlacements ||
+ !defaultPlacements.length) {
+ return;
+ }
+ let defaultWidgetIndex = -1;
+
+ for (let widgetId of futurePlacedWidgets) {
+ let widget = gPalette.get(widgetId);
+ if (!widget || widget.source !== CustomizableUI.SOURCE_BUILTIN ||
+ !widget.defaultArea || !widget._introducedInVersion ||
+ savedPlacements.indexOf(widget.id) !== -1) {
+ continue;
+ }
+ defaultWidgetIndex = defaultPlacements.indexOf(widget.id);
+ if (defaultWidgetIndex === -1) {
+ continue;
+ }
+ // Now we know that this widget should be here by default, was newly introduced,
+ // and we have a saved state to insert into, and a default state to work off of.
+ // Try introducing after widgets that come before it in the default placements:
+ for (let i = defaultWidgetIndex; i >= 0; i--) {
+ // Special case: if the defaults list this widget as coming first, insert at the beginning:
+ if (i === 0 && i === defaultWidgetIndex) {
+ savedPlacements.splice(0, 0, widget.id);
+ // Before you ask, yes, deleting things inside a let x of y loop where y is a Set is
+ // safe, and we won't skip any items.
+ futurePlacedWidgets.delete(widget.id);
+ gDirty = true;
+ break;
+ }
+ // Otherwise, if we're somewhere other than the beginning, check if the previous
+ // widget is in the saved placements.
+ if (i) {
+ let previousWidget = defaultPlacements[i - 1];
+ let previousWidgetIndex = savedPlacements.indexOf(previousWidget);
+ if (previousWidgetIndex != -1) {
+ savedPlacements.splice(previousWidgetIndex + 1, 0, widget.id);
+ futurePlacedWidgets.delete(widget.id);
+ gDirty = true;
+ break;
+ }
+ }
+ }
+ // The loop above either inserts the item or doesn't - either way, we can get away
+ // with doing nothing else now; if the item remains in gFuturePlacements, we'll
+ // add it at the end in restoreStateForArea.
+ }
+ this.saveState();
+ },
+
+ wrapWidget: function(aWidgetId) {
+ if (gGroupWrapperCache.has(aWidgetId)) {
+ return gGroupWrapperCache.get(aWidgetId);
+ }
+
+ let provider = this.getWidgetProvider(aWidgetId);
+ if (!provider) {
+ return null;
+ }
+
+ if (provider == CustomizableUI.PROVIDER_API) {
+ let widget = gPalette.get(aWidgetId);
+ if (!widget.wrapper) {
+ widget.wrapper = new WidgetGroupWrapper(widget);
+ gGroupWrapperCache.set(aWidgetId, widget.wrapper);
+ }
+ return widget.wrapper;
+ }
+
+ // PROVIDER_SPECIAL gets treated the same as PROVIDER_XUL.
+ let wrapper = new XULWidgetGroupWrapper(aWidgetId);
+ gGroupWrapperCache.set(aWidgetId, wrapper);
+ return wrapper;
+ },
+
+ registerArea: function(aName, aProperties, aInternalCaller) {
+ if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
+ throw new Error("Invalid area name");
+ }
+
+ let areaIsKnown = gAreas.has(aName);
+ let props = areaIsKnown ? gAreas.get(aName) : new Map();
+ const kImmutableProperties = new Set(["type", "legacy", "overflowable"]);
+ for (let key in aProperties) {
+ if (areaIsKnown && kImmutableProperties.has(key) &&
+ props.get(key) != aProperties[key]) {
+ throw new Error("An area cannot change the property for '" + key + "'");
+ }
+ // XXXgijs for special items, we need to make sure they have an appropriate ID
+ // so we aren't perpetually in a non-default state:
+ if (key == "defaultPlacements" && Array.isArray(aProperties[key])) {
+ props.set(key, aProperties[key].map(x => this.isSpecialWidget(x) ? this.ensureSpecialWidgetId(x) : x ));
+ } else {
+ props.set(key, aProperties[key]);
+ }
+ }
+ // Default to a toolbar:
+ if (!props.has("type")) {
+ props.set("type", CustomizableUI.TYPE_TOOLBAR);
+ }
+ if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
+ // Check aProperties instead of props because this check is only interested
+ // in the passed arguments, not the state of a potentially pre-existing area.
+ if (!aInternalCaller && aProperties["defaultCollapsed"]) {
+ throw new Error("defaultCollapsed is only allowed for default toolbars.")
+ }
+ if (!props.has("defaultCollapsed")) {
+ props.set("defaultCollapsed", true);
+ }
+ } else if (props.has("defaultCollapsed")) {
+ throw new Error("defaultCollapsed only applies for TYPE_TOOLBAR areas.");
+ }
+ // Sanity check type:
+ let allTypes = [CustomizableUI.TYPE_TOOLBAR, CustomizableUI.TYPE_MENU_PANEL];
+ if (allTypes.indexOf(props.get("type")) == -1) {
+ throw new Error("Invalid area type " + props.get("type"));
+ }
+
+ // And to no placements:
+ if (!props.has("defaultPlacements")) {
+ props.set("defaultPlacements", []);
+ }
+ // Sanity check default placements array:
+ if (!Array.isArray(props.get("defaultPlacements"))) {
+ throw new Error("Should provide an array of default placements");
+ }
+
+ if (!areaIsKnown) {
+ gAreas.set(aName, props);
+
+ // Reconcile new default widgets. Have to do this before we start restoring things.
+ this._placeNewDefaultWidgetsInArea(aName);
+
+ if (props.get("legacy") && !gPlacements.has(aName)) {
+ // Guarantee this area exists in gFuturePlacements, to avoid checking it in
+ // various places elsewhere.
+ if (!gFuturePlacements.has(aName)) {
+ gFuturePlacements.set(aName, new Set());
+ }
+ } else {
+ this.restoreStateForArea(aName);
+ }
+
+ // If we have pending build area nodes, register all of them
+ if (gPendingBuildAreas.has(aName)) {
+ let pendingNodes = gPendingBuildAreas.get(aName);
+ for (let [pendingNode, existingChildren] of pendingNodes) {
+ this.registerToolbarNode(pendingNode, existingChildren);
+ }
+ gPendingBuildAreas.delete(aName);
+ }
+ }
+ },
+
+ unregisterArea: function(aName, aDestroyPlacements) {
+ if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
+ throw new Error("Invalid area name");
+ }
+ if (!gAreas.has(aName) && !gPlacements.has(aName)) {
+ throw new Error("Area not registered");
+ }
+
+ // Move all the widgets out
+ this.beginBatchUpdate();
+ try {
+ let placements = gPlacements.get(aName);
+ if (placements) {
+ // Need to clone this array so removeWidgetFromArea doesn't modify it
+ placements = [...placements];
+ placements.forEach(this.removeWidgetFromArea, this);
+ }
+
+ // Delete all remaining traces.
+ gAreas.delete(aName);
+ // Only destroy placements when necessary:
+ if (aDestroyPlacements) {
+ gPlacements.delete(aName);
+ } else {
+ // Otherwise we need to re-set them, as removeFromArea will have emptied
+ // them out:
+ gPlacements.set(aName, placements);
+ }
+ gFuturePlacements.delete(aName);
+ let existingAreaNodes = gBuildAreas.get(aName);
+ if (existingAreaNodes) {
+ for (let areaNode of existingAreaNodes) {
+ this.notifyListeners("onAreaNodeUnregistered", aName, areaNode.customizationTarget,
+ CustomizableUI.REASON_AREA_UNREGISTERED);
+ }
+ }
+ gBuildAreas.delete(aName);
+ } finally {
+ this.endBatchUpdate(true);
+ }
+ },
+
+ registerToolbarNode: function(aToolbar, aExistingChildren) {
+ let area = aToolbar.id;
+ if (gBuildAreas.has(area) && gBuildAreas.get(area).has(aToolbar)) {
+ return;
+ }
+ let areaProperties = gAreas.get(area);
+
+ // If this area is not registered, try to do it automatically:
+ if (!areaProperties) {
+ // If there's no defaultset attribute and this isn't a legacy extra toolbar,
+ // we assume that we should wait for registerArea to be called:
+ if (!aToolbar.hasAttribute("defaultset") &&
+ !aToolbar.hasAttribute("customindex")) {
+ if (!gPendingBuildAreas.has(area)) {
+ gPendingBuildAreas.set(area, new Map());
+ }
+ let pendingNodes = gPendingBuildAreas.get(area);
+ pendingNodes.set(aToolbar, aExistingChildren);
+ return;
+ }
+ let props = {type: CustomizableUI.TYPE_TOOLBAR, legacy: true};
+ let defaultsetAttribute = aToolbar.getAttribute("defaultset") || "";
+ props.defaultPlacements = defaultsetAttribute.split(',').filter(s => s);
+ this.registerArea(area, props);
+ areaProperties = gAreas.get(area);
+ }
+
+ this.beginBatchUpdate();
+ try {
+ let placements = gPlacements.get(area);
+ if (!placements && areaProperties.has("legacy")) {
+ let legacyState = aToolbar.getAttribute("currentset");
+ if (legacyState) {
+ legacyState = legacyState.split(",").filter(s => s);
+ }
+
+ // Manually restore the state here, so the legacy state can be converted.
+ this.restoreStateForArea(area, legacyState);
+ placements = gPlacements.get(area);
+ }
+
+ // Check that the current children and the current placements match. If
+ // not, mark it as dirty:
+ if (aExistingChildren.length != placements.length ||
+ aExistingChildren.every((id, i) => id == placements[i])) {
+ gDirtyAreaCache.add(area);
+ }
+
+ if (areaProperties.has("overflowable")) {
+ aToolbar.overflowable = new OverflowableToolbar(aToolbar);
+ }
+
+ this.registerBuildArea(area, aToolbar);
+
+ // We only build the toolbar if it's been marked as "dirty". Dirty means
+ // one of the following things:
+ // 1) Items have been added, moved or removed from this toolbar before.
+ // 2) The number of children of the toolbar does not match the length of
+ // the placements array for that area.
+ //
+ // This notion of being "dirty" is stored in a cache which is persisted
+ // in the saved state.
+ if (gDirtyAreaCache.has(area)) {
+ this.buildArea(area, placements, aToolbar);
+ }
+ this.notifyListeners("onAreaNodeRegistered", area, aToolbar.customizationTarget);
+ aToolbar.setAttribute("currentset", placements.join(","));
+ } finally {
+ this.endBatchUpdate();
+ }
+ },
+
+ buildArea: function(aArea, aPlacements, aAreaNode) {
+ let document = aAreaNode.ownerDocument;
+ let window = document.defaultView;
+ let inPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(window);
+ let container = aAreaNode.customizationTarget;
+ let areaIsPanel = gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL;
+
+ if (!container) {
+ throw new Error("Expected area " + aArea
+ + " to have a customizationTarget attribute.");
+ }
+
+ // Restore nav-bar visibility since it may have been hidden
+ // through a migration path (bug 938980) or an add-on.
+ if (aArea == CustomizableUI.AREA_NAVBAR) {
+ aAreaNode.collapsed = false;
+ }
+
+ this.beginBatchUpdate();
+
+ try {
+ let currentNode = container.firstChild;
+ let placementsToRemove = new Set();
+ for (let id of aPlacements) {
+ while (currentNode && currentNode.getAttribute("skipintoolbarset") == "true") {
+ currentNode = currentNode.nextSibling;
+ }
+
+ if (currentNode && currentNode.id == id) {
+ currentNode = currentNode.nextSibling;
+ continue;
+ }
+
+ if (this.isSpecialWidget(id) && areaIsPanel) {
+ placementsToRemove.add(id);
+ continue;
+ }
+
+ let [provider, node] = this.getWidgetNode(id, window);
+ if (!node) {
+ log.debug("Unknown widget: " + id);
+ continue;
+ }
+
+ let widget = null;
+ // If the placements have items in them which are (now) no longer removable,
+ // we shouldn't be moving them:
+ if (provider == CustomizableUI.PROVIDER_API) {
+ widget = gPalette.get(id);
+ if (!widget.removable && aArea != widget.defaultArea) {
+ placementsToRemove.add(id);
+ continue;
+ }
+ } else if (provider == CustomizableUI.PROVIDER_XUL &&
+ node.parentNode != container && !this.isWidgetRemovable(node)) {
+ placementsToRemove.add(id);
+ continue;
+ } // Special widgets are always removable, so no need to check them
+
+ if (inPrivateWindow && widget && !widget.showInPrivateBrowsing) {
+ continue;
+ }
+
+ this.ensureButtonContextMenu(node, aAreaNode);
+ if (node.localName == "toolbarbutton") {
+ if (areaIsPanel) {
+ node.setAttribute("wrap", "true");
+ } else {
+ node.removeAttribute("wrap");
+ }
+ }
+
+ // This needs updating in case we're resetting / undoing a reset.
+ if (widget) {
+ widget.currentArea = aArea;
+ }
+ this.insertWidgetBefore(node, currentNode, container, aArea);
+ if (gResetting) {
+ this.notifyListeners("onWidgetReset", node, container);
+ } else if (gUndoResetting) {
+ this.notifyListeners("onWidgetUndoMove", node, container);
+ }
+ }
+
+ if (currentNode) {
+ let palette = aAreaNode.toolbox ? aAreaNode.toolbox.palette : null;
+ let limit = currentNode.previousSibling;
+ let node = container.lastChild;
+ while (node && node != limit) {
+ let previousSibling = node.previousSibling;
+ // Nodes opt-in to removability. If they're removable, and we haven't
+ // seen them in the placements array, then we toss them into the palette
+ // if one exists. If no palette exists, we just remove the node. If the
+ // node is not removable, we leave it where it is. However, we can only
+ // safely touch elements that have an ID - both because we depend on
+ // IDs, and because such elements are not intended to be widgets
+ // (eg, titlebar-placeholder elements).
+ if (node.id && node.getAttribute("skipintoolbarset") != "true") {
+ if (this.isWidgetRemovable(node)) {
+ if (palette && !this.isSpecialWidget(node.id)) {
+ palette.appendChild(node);
+ this.removeLocationAttributes(node);
+ } else {
+ container.removeChild(node);
+ }
+ } else {
+ node.setAttribute("removable", false);
+ log.debug("Adding non-removable widget to placements of " + aArea + ": " +
+ node.id);
+ gPlacements.get(aArea).push(node.id);
+ gDirty = true;
+ }
+ }
+ node = previousSibling;
+ }
+ }
+
+ // If there are placements in here which aren't removable from their original area,
+ // we remove them from this area's placement array. They will (have) be(en) added
+ // to their original area's placements array in the block above this one.
+ if (placementsToRemove.size) {
+ let placementAry = gPlacements.get(aArea);
+ for (let id of placementsToRemove) {
+ let index = placementAry.indexOf(id);
+ placementAry.splice(index, 1);
+ }
+ }
+
+ if (gResetting) {
+ this.notifyListeners("onAreaReset", aArea, container);
+ }
+ } finally {
+ this.endBatchUpdate();
+ }
+ },
+
+ addPanelCloseListeners: function(aPanel) {
+ gELS.addSystemEventListener(aPanel, "click", this, false);
+ gELS.addSystemEventListener(aPanel, "keypress", this, false);
+ let win = aPanel.ownerGlobal;
+ if (!gPanelsForWindow.has(win)) {
+ gPanelsForWindow.set(win, new Set());
+ }
+ gPanelsForWindow.get(win).add(this._getPanelForNode(aPanel));
+ },
+
+ removePanelCloseListeners: function(aPanel) {
+ gELS.removeSystemEventListener(aPanel, "click", this, false);
+ gELS.removeSystemEventListener(aPanel, "keypress", this, false);
+ let win = aPanel.ownerGlobal;
+ let panels = gPanelsForWindow.get(win);
+ if (panels) {
+ panels.delete(this._getPanelForNode(aPanel));
+ }
+ },
+
+ ensureButtonContextMenu: function(aNode, aAreaNode) {
+ const kPanelItemContextMenu = "customizationPanelItemContextMenu";
+
+ let currentContextMenu = aNode.getAttribute("context") ||
+ aNode.getAttribute("contextmenu");
+ let place = CustomizableUI.getPlaceForItem(aAreaNode);
+ let contextMenuForPlace = place == "panel" ?
+ kPanelItemContextMenu :
+ null;
+ if (contextMenuForPlace && !currentContextMenu) {
+ aNode.setAttribute("context", contextMenuForPlace);
+ } else if (currentContextMenu == kPanelItemContextMenu &&
+ contextMenuForPlace != kPanelItemContextMenu) {
+ aNode.removeAttribute("context");
+ aNode.removeAttribute("contextmenu");
+ }
+ },
+
+ getWidgetProvider: function(aWidgetId) {
+ if (this.isSpecialWidget(aWidgetId)) {
+ return CustomizableUI.PROVIDER_SPECIAL;
+ }
+ if (gPalette.has(aWidgetId)) {
+ return CustomizableUI.PROVIDER_API;
+ }
+ // If this was an API widget that was destroyed, return null:
+ if (gSeenWidgets.has(aWidgetId)) {
+ return null;
+ }
+
+ // We fall back to the XUL provider, but we don't know for sure (at this
+ // point) whether it exists there either. So the API is technically lying.
+ // Ideally, it would be able to return an error value (or throw an
+ // exception) if it really didn't exist. Our code calling this function
+ // handles that fine, but this is a public API.
+ return CustomizableUI.PROVIDER_XUL;
+ },
+
+ getWidgetNode: function(aWidgetId, aWindow) {
+ let document = aWindow.document;
+
+ if (this.isSpecialWidget(aWidgetId)) {
+ let widgetNode = document.getElementById(aWidgetId) ||
+ this.createSpecialWidget(aWidgetId, document);
+ return [ CustomizableUI.PROVIDER_SPECIAL, widgetNode];
+ }
+
+ let widget = gPalette.get(aWidgetId);
+ if (widget) {
+ // If we have an instance of this widget already, just use that.
+ if (widget.instances.has(document)) {
+ log.debug("An instance of widget " + aWidgetId + " already exists in this "
+ + "document. Reusing.");
+ return [ CustomizableUI.PROVIDER_API,
+ widget.instances.get(document) ];
+ }
+
+ return [ CustomizableUI.PROVIDER_API,
+ this.buildWidget(document, widget) ];
+ }
+
+ log.debug("Searching for " + aWidgetId + " in toolbox.");
+ let node = this.findWidgetInWindow(aWidgetId, aWindow);
+ if (node) {
+ return [ CustomizableUI.PROVIDER_XUL, node ];
+ }
+
+ log.debug("No node for " + aWidgetId + " found.");
+ return [null, null];
+ },
+
+ registerMenuPanel: function(aPanelContents) {
+ if (gBuildAreas.has(CustomizableUI.AREA_PANEL) &&
+ gBuildAreas.get(CustomizableUI.AREA_PANEL).has(aPanelContents)) {
+ return;
+ }
+
+ let document = aPanelContents.ownerDocument;
+
+ aPanelContents.toolbox = document.getElementById("navigator-toolbox");
+ aPanelContents.customizationTarget = aPanelContents;
+
+ this.addPanelCloseListeners(this._getPanelForNode(aPanelContents));
+
+ let placements = gPlacements.get(CustomizableUI.AREA_PANEL);
+ this.buildArea(CustomizableUI.AREA_PANEL, placements, aPanelContents);
+ this.notifyListeners("onAreaNodeRegistered", CustomizableUI.AREA_PANEL, aPanelContents);
+
+ for (let child of aPanelContents.children) {
+ if (child.localName != "toolbarbutton") {
+ if (child.localName == "toolbaritem") {
+ this.ensureButtonContextMenu(child, aPanelContents);
+ }
+ continue;
+ }
+ this.ensureButtonContextMenu(child, aPanelContents);
+ child.setAttribute("wrap", "true");
+ }
+
+ this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanelContents);
+ },
+
+ onWidgetAdded: function(aWidgetId, aArea, aPosition) {
+ this.insertNode(aWidgetId, aArea, aPosition, true);
+
+ if (!gResetting) {
+ this._clearPreviousUIState();
+ }
+ },
+
+ onWidgetRemoved: function(aWidgetId, aArea) {
+ let areaNodes = gBuildAreas.get(aArea);
+ if (!areaNodes) {
+ return;
+ }
+
+ let area = gAreas.get(aArea);
+ let isToolbar = area.get("type") == CustomizableUI.TYPE_TOOLBAR;
+ let isOverflowable = isToolbar && area.get("overflowable");
+ let showInPrivateBrowsing = gPalette.has(aWidgetId)
+ ? gPalette.get(aWidgetId).showInPrivateBrowsing
+ : true;
+
+ for (let areaNode of areaNodes) {
+ let window = areaNode.ownerGlobal;
+ if (!showInPrivateBrowsing &&
+ PrivateBrowsingUtils.isWindowPrivate(window)) {
+ continue;
+ }
+
+ let container = areaNode.customizationTarget;
+ let widgetNode = window.document.getElementById(aWidgetId);
+ if (widgetNode && isOverflowable) {
+ container = areaNode.overflowable.getContainerFor(widgetNode);
+ }
+
+ if (!widgetNode || !container.contains(widgetNode)) {
+ log.info("Widget " + aWidgetId + " not found, unable to remove from " + aArea);
+ continue;
+ }
+
+ this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null, container, true);
+
+ // We remove location attributes here to make sure they're gone too when a
+ // widget is removed from a toolbar to the palette. See bug 930950.
+ this.removeLocationAttributes(widgetNode);
+ // We also need to remove the panel context menu if it's there:
+ this.ensureButtonContextMenu(widgetNode);
+ widgetNode.removeAttribute("wrap");
+ if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
+ container.removeChild(widgetNode);
+ } else {
+ areaNode.toolbox.palette.appendChild(widgetNode);
+ }
+ this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
+
+ if (isToolbar) {
+ areaNode.setAttribute("currentset", gPlacements.get(aArea).join(','));
+ }
+
+ let windowCache = gSingleWrapperCache.get(window);
+ if (windowCache) {
+ windowCache.delete(aWidgetId);
+ }
+ }
+ if (!gResetting) {
+ this._clearPreviousUIState();
+ }
+ },
+
+ onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
+ this.insertNode(aWidgetId, aArea, aNewPosition);
+ if (!gResetting) {
+ this._clearPreviousUIState();
+ }
+ },
+
+ onCustomizeEnd: function(aWindow) {
+ this._clearPreviousUIState();
+ },
+
+ registerBuildArea: function(aArea, aNode) {
+ // We ensure that the window is registered to have its customization data
+ // cleaned up when unloading.
+ let window = aNode.ownerGlobal;
+ if (window.closed) {
+ return;
+ }
+ this.registerBuildWindow(window);
+
+ // Also register this build area's toolbox.
+ if (aNode.toolbox) {
+ gBuildWindows.get(window).add(aNode.toolbox);
+ }
+
+ if (!gBuildAreas.has(aArea)) {
+ gBuildAreas.set(aArea, new Set());
+ }
+
+ gBuildAreas.get(aArea).add(aNode);
+
+ // Give a class to all customize targets to be used for styling in Customize Mode
+ let customizableNode = this.getCustomizeTargetForArea(aArea, window);
+ customizableNode.classList.add("customization-target");
+ },
+
+ registerBuildWindow: function(aWindow) {
+ if (!gBuildWindows.has(aWindow)) {
+ gBuildWindows.set(aWindow, new Set());
+
+ aWindow.addEventListener("unload", this);
+ aWindow.addEventListener("command", this, true);
+
+ this.notifyListeners("onWindowOpened", aWindow);
+ }
+ },
+
+ unregisterBuildWindow: function(aWindow) {
+ aWindow.removeEventListener("unload", this);
+ aWindow.removeEventListener("command", this, true);
+ gPanelsForWindow.delete(aWindow);
+ gBuildWindows.delete(aWindow);
+ gSingleWrapperCache.delete(aWindow);
+ let document = aWindow.document;
+
+ for (let [areaId, areaNodes] of gBuildAreas) {
+ let areaProperties = gAreas.get(areaId);
+ for (let node of areaNodes) {
+ if (node.ownerDocument == document) {
+ this.notifyListeners("onAreaNodeUnregistered", areaId, node.customizationTarget,
+ CustomizableUI.REASON_WINDOW_CLOSED);
+ if (areaProperties.has("overflowable")) {
+ node.overflowable.uninit();
+ node.overflowable = null;
+ }
+ areaNodes.delete(node);
+ }
+ }
+ }
+
+ for (let [, widget] of gPalette) {
+ widget.instances.delete(document);
+ this.notifyListeners("onWidgetInstanceRemoved", widget.id, document);
+ }
+
+ for (let [, areaMap] of gPendingBuildAreas) {
+ let toDelete = [];
+ for (let [areaNode, ] of areaMap) {
+ if (areaNode.ownerDocument == document) {
+ toDelete.push(areaNode);
+ }
+ }
+ for (let areaNode of toDelete) {
+ areaMap.delete(areaNode);
+ }
+ }
+
+ this.notifyListeners("onWindowClosed", aWindow);
+ },
+
+ setLocationAttributes: function(aNode, aArea) {
+ let props = gAreas.get(aArea);
+ if (!props) {
+ throw new Error("Expected area " + aArea + " to have a properties Map " +
+ "associated with it.");
+ }
+
+ aNode.setAttribute("cui-areatype", props.get("type") || "");
+ let anchor = props.get("anchor");
+ if (anchor) {
+ aNode.setAttribute("cui-anchorid", anchor);
+ } else {
+ aNode.removeAttribute("cui-anchorid");
+ }
+ },
+
+ removeLocationAttributes: function(aNode) {
+ aNode.removeAttribute("cui-areatype");
+ aNode.removeAttribute("cui-anchorid");
+ },
+
+ insertNode: function(aWidgetId, aArea, aPosition, isNew) {
+ let areaNodes = gBuildAreas.get(aArea);
+ if (!areaNodes) {
+ return;
+ }
+
+ let placements = gPlacements.get(aArea);
+ if (!placements) {
+ log.error("Could not find any placements for " + aArea +
+ " when moving a widget.");
+ return;
+ }
+
+ // Go through each of the nodes associated with this area and move the
+ // widget to the requested location.
+ for (let areaNode of areaNodes) {
+ this.insertNodeInWindow(aWidgetId, areaNode, isNew);
+ }
+ },
+
+ insertNodeInWindow: function(aWidgetId, aAreaNode, isNew) {
+ let window = aAreaNode.ownerGlobal;
+ let showInPrivateBrowsing = gPalette.has(aWidgetId)
+ ? gPalette.get(aWidgetId).showInPrivateBrowsing
+ : true;
+
+ if (!showInPrivateBrowsing && PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+
+ let [, widgetNode] = this.getWidgetNode(aWidgetId, window);
+ if (!widgetNode) {
+ log.error("Widget '" + aWidgetId + "' not found, unable to move");
+ return;
+ }
+
+ let areaId = aAreaNode.id;
+ if (isNew) {
+ this.ensureButtonContextMenu(widgetNode, aAreaNode);
+ if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) {
+ widgetNode.setAttribute("wrap", "true");
+ }
+ }
+
+ let [insertionContainer, nextNode] = this.findInsertionPoints(widgetNode, aAreaNode);
+ this.insertWidgetBefore(widgetNode, nextNode, insertionContainer, areaId);
+
+ if (gAreas.get(areaId).get("type") == CustomizableUI.TYPE_TOOLBAR) {
+ aAreaNode.setAttribute("currentset", gPlacements.get(areaId).join(','));
+ }
+ },
+
+ findInsertionPoints: function(aNode, aAreaNode) {
+ let areaId = aAreaNode.id;
+ let props = gAreas.get(areaId);
+
+ // For overflowable toolbars, rely on them (because the work is more complicated):
+ if (props.get("type") == CustomizableUI.TYPE_TOOLBAR && props.get("overflowable")) {
+ return aAreaNode.overflowable.findOverflowedInsertionPoints(aNode);
+ }
+
+ let container = aAreaNode.customizationTarget;
+ let placements = gPlacements.get(areaId);
+ let nodeIndex = placements.indexOf(aNode.id);
+
+ while (++nodeIndex < placements.length) {
+ let nextNodeId = placements[nodeIndex];
+ let nextNode = container.getElementsByAttribute("id", nextNodeId).item(0);
+
+ if (nextNode) {
+ return [container, nextNode];
+ }
+ }
+
+ return [container, null];
+ },
+
+ insertWidgetBefore: function(aNode, aNextNode, aContainer, aArea) {
+ this.notifyListeners("onWidgetBeforeDOMChange", aNode, aNextNode, aContainer);
+ this.setLocationAttributes(aNode, aArea);
+ aContainer.insertBefore(aNode, aNextNode);
+ this.notifyListeners("onWidgetAfterDOMChange", aNode, aNextNode, aContainer);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "command":
+ if (!this._originalEventInPanel(aEvent)) {
+ break;
+ }
+ aEvent = aEvent.sourceEvent;
+ // Fall through
+ case "click":
+ case "keypress":
+ this.maybeAutoHidePanel(aEvent);
+ break;
+ case "unload":
+ this.unregisterBuildWindow(aEvent.currentTarget);
+ break;
+ }
+ },
+
+ _originalEventInPanel: function(aEvent) {
+ let e = aEvent.sourceEvent;
+ if (!e) {
+ return false;
+ }
+ let node = this._getPanelForNode(e.target);
+ if (!node) {
+ return false;
+ }
+ let win = e.view;
+ let panels = gPanelsForWindow.get(win);
+ return !!panels && panels.has(node);
+ },
+
+ isSpecialWidget: function(aId) {
+ return (aId.startsWith(kSpecialWidgetPfx) ||
+ aId.startsWith("separator") ||
+ aId.startsWith("spring") ||
+ aId.startsWith("spacer"));
+ },
+
+ ensureSpecialWidgetId: function(aId) {
+ let nodeType = aId.match(/spring|spacer|separator/)[0];
+ // If the ID we were passed isn't a generated one, generate one now:
+ if (nodeType == aId) {
+ // Ids are differentiated through a unique count suffix.
+ return kSpecialWidgetPfx + aId + (++gNewElementCount);
+ }
+ return aId;
+ },
+
+ createSpecialWidget: function(aId, aDocument) {
+ let nodeName = "toolbar" + aId.match(/spring|spacer|separator/)[0];
+ let node = aDocument.createElementNS(kNSXUL, nodeName);
+ node.id = this.ensureSpecialWidgetId(aId);
+ if (nodeName == "toolbarspring") {
+ node.flex = 1;
+ }
+ return node;
+ },
+
+ /* Find a XUL-provided widget in a window. Don't try to use this
+ * for an API-provided widget or a special widget.
+ */
+ findWidgetInWindow: function(aId, aWindow) {
+ if (!gBuildWindows.has(aWindow)) {
+ throw new Error("Build window not registered");
+ }
+
+ if (!aId) {
+ log.error("findWidgetInWindow was passed an empty string.");
+ return null;
+ }
+
+ let document = aWindow.document;
+
+ // look for a node with the same id, as the node may be
+ // in a different toolbar.
+ let node = document.getElementById(aId);
+ if (node) {
+ let parent = node.parentNode;
+ while (parent && !(parent.customizationTarget ||
+ parent == aWindow.gNavToolbox.palette)) {
+ parent = parent.parentNode;
+ }
+
+ if (parent) {
+ let nodeInArea = node.parentNode.localName == "toolbarpaletteitem" ?
+ node.parentNode : node;
+ // Check if we're in a customization target, or in the palette:
+ if ((parent.customizationTarget == nodeInArea.parentNode &&
+ gBuildWindows.get(aWindow).has(parent.toolbox)) ||
+ aWindow.gNavToolbox.palette == nodeInArea.parentNode) {
+ // Normalize the removable attribute. For backwards compat, if
+ // the widget is not located in a toolbox palette then absence
+ // of the "removable" attribute means it is not removable.
+ if (!node.hasAttribute("removable")) {
+ // If we first see this in customization mode, it may be in the
+ // customization palette instead of the toolbox palette.
+ node.setAttribute("removable", !parent.customizationTarget);
+ }
+ return node;
+ }
+ }
+ }
+
+ let toolboxes = gBuildWindows.get(aWindow);
+ for (let toolbox of toolboxes) {
+ if (toolbox.palette) {
+ // Attempt to locate a node with a matching ID within
+ // the palette.
+ let node = toolbox.palette.getElementsByAttribute("id", aId)[0];
+ if (node) {
+ // Normalize the removable attribute. For backwards compat, this
+ // is optional if the widget is located in the toolbox palette,
+ // and defaults to *true*, unlike if it was located elsewhere.
+ if (!node.hasAttribute("removable")) {
+ node.setAttribute("removable", true);
+ }
+ return node;
+ }
+ }
+ }
+ return null;
+ },
+
+ buildWidget: function(aDocument, aWidget) {
+ if (aDocument.documentURI != kExpectedWindowURL) {
+ throw new Error("buildWidget was called for a non-browser window!");
+ }
+ if (typeof aWidget == "string") {
+ aWidget = gPalette.get(aWidget);
+ }
+ if (!aWidget) {
+ throw new Error("buildWidget was passed a non-widget to build.");
+ }
+
+ log.debug("Building " + aWidget.id + " of type " + aWidget.type);
+
+ let node;
+ if (aWidget.type == "custom") {
+ if (aWidget.onBuild) {
+ node = aWidget.onBuild(aDocument);
+ }
+ if (!node || !(node instanceof aDocument.defaultView.XULElement))
+ log.error("Custom widget with id " + aWidget.id + " does not return a valid node");
+ }
+ else {
+ if (aWidget.onBeforeCreated) {
+ aWidget.onBeforeCreated(aDocument);
+ }
+ node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+
+ node.setAttribute("id", aWidget.id);
+ node.setAttribute("widget-id", aWidget.id);
+ node.setAttribute("widget-type", aWidget.type);
+ if (aWidget.disabled) {
+ node.setAttribute("disabled", true);
+ }
+ node.setAttribute("removable", aWidget.removable);
+ node.setAttribute("overflows", aWidget.overflows);
+ if (aWidget.tabSpecific) {
+ node.setAttribute("tabspecific", aWidget.tabSpecific);
+ }
+ node.setAttribute("label", this.getLocalizedProperty(aWidget, "label"));
+ let additionalTooltipArguments = [];
+ if (aWidget.shortcutId) {
+ let keyEl = aDocument.getElementById(aWidget.shortcutId);
+ if (keyEl) {
+ additionalTooltipArguments.push(ShortcutUtils.prettifyShortcut(keyEl));
+ } else {
+ log.error("Key element with id '" + aWidget.shortcutId + "' for widget '" + aWidget.id +
+ "' not found!");
+ }
+ }
+
+ let tooltip = this.getLocalizedProperty(aWidget, "tooltiptext", additionalTooltipArguments);
+ if (tooltip) {
+ node.setAttribute("tooltiptext", tooltip);
+ }
+ node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
+
+ let commandHandler = this.handleWidgetCommand.bind(this, aWidget, node);
+ node.addEventListener("command", commandHandler, false);
+ let clickHandler = this.handleWidgetClick.bind(this, aWidget, node);
+ node.addEventListener("click", clickHandler, false);
+
+ // If the widget has a view, and has view showing / hiding listeners,
+ // hook those up to this widget.
+ if (aWidget.type == "view") {
+ log.debug("Widget " + aWidget.id + " has a view. Auto-registering event handlers.");
+ let viewNode = aDocument.getElementById(aWidget.viewId);
+
+ if (viewNode) {
+ // PanelUI relies on the .PanelUI-subView class to be able to show only
+ // one sub-view at a time.
+ viewNode.classList.add("PanelUI-subView");
+
+ for (let eventName of kSubviewEvents) {
+ let handler = "on" + eventName;
+ if (typeof aWidget[handler] == "function") {
+ viewNode.addEventListener(eventName, aWidget[handler], false);
+ }
+ }
+
+ log.debug("Widget " + aWidget.id + " showing and hiding event handlers set.");
+ } else {
+ log.error("Could not find the view node with id: " + aWidget.viewId +
+ ", for widget: " + aWidget.id + ".");
+ }
+ }
+
+ if (aWidget.onCreated) {
+ aWidget.onCreated(node);
+ }
+ }
+
+ aWidget.instances.set(aDocument, node);
+ return node;
+ },
+
+ getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
+ const kReqStringProps = ["label"];
+
+ if (typeof aWidget == "string") {
+ aWidget = gPalette.get(aWidget);
+ }
+ if (!aWidget) {
+ throw new Error("getLocalizedProperty was passed a non-widget to work with.");
+ }
+ let def, name;
+ // Let widgets pass their own string identifiers or strings, so that
+ // we can use strings which aren't the default (in case string ids change)
+ // and so that non-builtin-widgets can also provide labels, tooltips, etc.
+ if (aWidget[aProp] != null) {
+ name = aWidget[aProp];
+ // By using this as the default, if a widget provides a full string rather
+ // than a string ID for localization, we will fall back to that string
+ // and return that.
+ def = aDef || name;
+ } else {
+ name = aWidget.id + "." + aProp;
+ def = aDef || "";
+ }
+ try {
+ if (Array.isArray(aFormatArgs) && aFormatArgs.length) {
+ return gWidgetsBundle.formatStringFromName(name, aFormatArgs,
+ aFormatArgs.length) || def;
+ }
+ return gWidgetsBundle.GetStringFromName(name) || def;
+ } catch (ex) {
+ // If an empty string was explicitly passed, treat it as an actual
+ // value rather than a missing property.
+ if (!def && (name != "" || kReqStringProps.includes(aProp))) {
+ log.error("Could not localize property '" + name + "'.");
+ }
+ }
+ return def;
+ },
+
+ addShortcut: function(aShortcutNode, aTargetNode) {
+ if (!aTargetNode)
+ aTargetNode = aShortcutNode;
+ let document = aShortcutNode.ownerDocument;
+
+ // Detect if we've already been here before.
+ if (!aTargetNode || aTargetNode.hasAttribute("shortcut"))
+ return;
+
+ let shortcutId = aShortcutNode.getAttribute("key");
+ let shortcut;
+ if (shortcutId) {
+ shortcut = document.getElementById(shortcutId);
+ } else {
+ let commandId = aShortcutNode.getAttribute("command");
+ if (commandId)
+ shortcut = ShortcutUtils.findShortcut(document.getElementById(commandId));
+ }
+ if (!shortcut) {
+ return;
+ }
+
+ aTargetNode.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(shortcut));
+ },
+
+ handleWidgetCommand: function(aWidget, aNode, aEvent) {
+ log.debug("handleWidgetCommand");
+
+ if (aWidget.type == "button") {
+ if (aWidget.onCommand) {
+ try {
+ aWidget.onCommand.call(null, aEvent);
+ } catch (e) {
+ log.error(e);
+ }
+ } else {
+ // XXXunf Need to think this through more, and formalize.
+ Services.obs.notifyObservers(aNode,
+ "customizedui-widget-command",
+ aWidget.id);
+ }
+ } else if (aWidget.type == "view") {
+ let ownerWindow = aNode.ownerGlobal;
+ let area = this.getPlacementOfWidget(aNode.id).area;
+ let anchor = aNode;
+ if (area != CustomizableUI.AREA_PANEL) {
+ let wrapper = this.wrapWidget(aWidget.id).forWindow(ownerWindow);
+ if (wrapper && wrapper.anchor) {
+ this.hidePanelForNode(aNode);
+ anchor = wrapper.anchor;
+ }
+ }
+ ownerWindow.PanelUI.showSubView(aWidget.viewId, anchor, area);
+ }
+ },
+
+ handleWidgetClick: function(aWidget, aNode, aEvent) {
+ log.debug("handleWidgetClick");
+ if (aWidget.onClick) {
+ try {
+ aWidget.onClick.call(null, aEvent);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ } else {
+ // XXXunf Need to think this through more, and formalize.
+ Services.obs.notifyObservers(aNode, "customizedui-widget-click", aWidget.id);
+ }
+ },
+
+ _getPanelForNode: function(aNode) {
+ let panel = aNode;
+ while (panel && panel.localName != "panel")
+ panel = panel.parentNode;
+ return panel;
+ },
+
+ /*
+ * If people put things in the panel which need more than single-click interaction,
+ * we don't want to close it. Right now we check for text inputs and menu buttons.
+ * We also check for being outside of any toolbaritem/toolbarbutton, ie on a blank
+ * part of the menu.
+ */
+ _isOnInteractiveElement: function(aEvent) {
+ function getMenuPopupForDescendant(aNode) {
+ let lastPopup = null;
+ while (aNode && aNode.parentNode &&
+ aNode.parentNode.localName.startsWith("menu")) {
+ lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup;
+ aNode = aNode.parentNode;
+ }
+ return lastPopup;
+ }
+
+ let target = aEvent.originalTarget;
+ let panel = this._getPanelForNode(aEvent.currentTarget);
+ // This can happen in e.g. customize mode. If there's no panel,
+ // there's clearly nothing for us to close; pretend we're interactive.
+ if (!panel) {
+ return true;
+ }
+ // We keep track of:
+ // whether we're in an input container (text field)
+ let inInput = false;
+ // whether we're in a popup/context menu
+ let inMenu = false;
+ // whether we're in a toolbarbutton/toolbaritem
+ let inItem = false;
+ // whether the current menuitem has a valid closemenu attribute
+ let menuitemCloseMenu = "auto";
+ // whether the toolbarbutton/item has a valid closemenu attribute.
+ let closemenu = "auto";
+
+ // While keeping track of that, we go from the original target back up,
+ // to the panel if we have to. We bail as soon as we find an input,
+ // a toolbarbutton/item, or the panel:
+ while (true && target) {
+ // Skip out of iframes etc:
+ if (target.nodeType == target.DOCUMENT_NODE) {
+ if (!target.defaultView) {
+ // Err, we're done.
+ break;
+ }
+ // Cue some voodoo
+ target = target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ if (!target) {
+ break;
+ }
+ }
+ let tagName = target.localName;
+ inInput = tagName == "input" || tagName == "textbox";
+ inItem = tagName == "toolbaritem" || tagName == "toolbarbutton";
+ let isMenuItem = tagName == "menuitem";
+ inMenu = inMenu || isMenuItem;
+ if (inItem && target.hasAttribute("closemenu")) {
+ let closemenuVal = target.getAttribute("closemenu");
+ closemenu = (closemenuVal == "single" || closemenuVal == "none") ?
+ closemenuVal : "auto";
+ }
+
+ if (isMenuItem && target.hasAttribute("closemenu")) {
+ let closemenuVal = target.getAttribute("closemenu");
+ menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
+ closemenuVal : "auto";
+ }
+ // Break out of the loop immediately for disabled items, as we need to
+ // keep the menu open in that case.
+ if (target.getAttribute("disabled") == "true") {
+ return true;
+ }
+
+ // This isn't in the loop condition because we want to break before
+ // changing |target| if any of these conditions are true
+ if (inInput || inItem || target == panel) {
+ break;
+ }
+ // We need specific code for popups: the item on which they were invoked
+ // isn't necessarily in their parentNode chain:
+ if (isMenuItem) {
+ let topmostMenuPopup = getMenuPopupForDescendant(target);
+ target = (topmostMenuPopup && topmostMenuPopup.triggerNode) ||
+ target.parentNode;
+ } else {
+ target = target.parentNode;
+ }
+ }
+
+ // If the user clicked a menu item...
+ if (inMenu) {
+ // We care if we're in an input also,
+ // or if the user specified closemenu!="auto":
+ if (inInput || menuitemCloseMenu != "auto") {
+ return true;
+ }
+ // Otherwise, we're probably fine to close the panel
+ return false;
+ }
+ // If we're not in a menu, and we *are* in a type="menu" toolbarbutton,
+ // we'll now interact with the menu
+ if (inItem && target.getAttribute("type") == "menu") {
+ return true;
+ }
+ // If we're not in a menu, and we *are* in a type="menu-button" toolbarbutton,
+ // it depends whether we're in the dropmarker or the 'real' button:
+ if (inItem && target.getAttribute("type") == "menu-button") {
+ // 'real' button (which has a single action):
+ if (target.getAttribute("anonid") == "button") {
+ return closemenu != "none";
+ }
+ // otherwise, this is the outer button, and the user will now
+ // interact with the menu:
+ return true;
+ }
+ return inInput || !inItem;
+ },
+
+ hidePanelForNode: function(aNode) {
+ let panel = this._getPanelForNode(aNode);
+ if (panel) {
+ panel.hidePopup();
+ }
+ },
+
+ maybeAutoHidePanel: function(aEvent) {
+ if (aEvent.type == "keypress") {
+ if (aEvent.keyCode != aEvent.DOM_VK_RETURN) {
+ return;
+ }
+ // If the user hit enter/return, we don't check preventDefault - it makes sense
+ // that this was prevented, but we probably still want to close the panel.
+ // If consumers don't want this to happen, they should specify the closemenu
+ // attribute.
+
+ } else if (aEvent.type != "command") { // mouse events:
+ if (aEvent.defaultPrevented || aEvent.button != 0) {
+ return;
+ }
+ let isInteractive = this._isOnInteractiveElement(aEvent);
+ log.debug("maybeAutoHidePanel: interactive ? " + isInteractive);
+ if (isInteractive) {
+ return;
+ }
+ }
+
+ // We can't use event.target because we might have passed a panelview
+ // anonymous content boundary as well, and so target points to the
+ // panelmultiview in that case. Unfortunately, this means we get
+ // anonymous child nodes instead of the real ones, so looking for the
+ // 'stoooop, don't close me' attributes is more involved.
+ let target = aEvent.originalTarget;
+ let closemenu = "auto";
+ let widgetType = "button";
+ while (target.parentNode && target.localName != "panel") {
+ closemenu = target.getAttribute("closemenu");
+ widgetType = target.getAttribute("widget-type");
+ if (closemenu == "none" || closemenu == "single" ||
+ widgetType == "view") {
+ break;
+ }
+ target = target.parentNode;
+ }
+ if (closemenu == "none" || widgetType == "view") {
+ return;
+ }
+
+ if (closemenu == "single") {
+ let panel = this._getPanelForNode(target);
+ let multiview = panel.querySelector("panelmultiview");
+ if (multiview.showingSubView) {
+ multiview.showMainView();
+ return;
+ }
+ }
+
+ // If we get here, we can actually hide the popup:
+ this.hidePanelForNode(aEvent.target);
+ },
+
+ getUnusedWidgets: function(aWindowPalette) {
+ let window = aWindowPalette.ownerGlobal;
+ let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ // We use a Set because there can be overlap between the widgets in
+ // gPalette and the items in the palette, especially after the first
+ // customization, since programmatically generated widgets will remain
+ // in the toolbox palette.
+ let widgets = new Set();
+
+ // It's possible that some widgets have been defined programmatically and
+ // have not been overlayed into the palette. We can find those inside
+ // gPalette.
+ for (let [id, widget] of gPalette) {
+ if (!widget.currentArea) {
+ if (widget.showInPrivateBrowsing || !isWindowPrivate) {
+ widgets.add(id);
+ }
+ }
+ }
+
+ log.debug("Iterating the actual nodes of the window palette");
+ for (let node of aWindowPalette.children) {
+ log.debug("In palette children: " + node.id);
+ if (node.id && !this.getPlacementOfWidget(node.id)) {
+ widgets.add(node.id);
+ }
+ }
+
+ return [...widgets];
+ },
+
+ getPlacementOfWidget: function(aWidgetId, aOnlyRegistered, aDeadAreas) {
+ if (aOnlyRegistered && !this.widgetExists(aWidgetId)) {
+ return null;
+ }
+
+ for (let [area, placements] of gPlacements) {
+ if (!gAreas.has(area) && !aDeadAreas) {
+ continue;
+ }
+ let index = placements.indexOf(aWidgetId);
+ if (index != -1) {
+ return { area: area, position: index };
+ }
+ }
+
+ return null;
+ },
+
+ widgetExists: function(aWidgetId) {
+ if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
+ return true;
+ }
+
+ // Destroyed API widgets are in gSeenWidgets, but not in gPalette:
+ if (gSeenWidgets.has(aWidgetId)) {
+ return false;
+ }
+
+ // We're assuming XUL widgets always exist, as it's much harder to check,
+ // and checking would be much more error prone.
+ return true;
+ },
+
+ addWidgetToArea: function(aWidgetId, aArea, aPosition, aInitialAdd) {
+ if (!gAreas.has(aArea)) {
+ throw new Error("Unknown customization area: " + aArea);
+ }
+
+ // Hack: don't want special widgets in the panel (need to check here as well
+ // as in canWidgetMoveToArea because the menu panel is lazy):
+ if (gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL &&
+ this.isSpecialWidget(aWidgetId)) {
+ return;
+ }
+
+ // If this is a lazy area that hasn't been restored yet, we can't yet modify
+ // it - would would at least like to add to it. So we keep track of it in
+ // gFuturePlacements, and use that to add it when restoring the area. We
+ // throw away aPosition though, as that can only be bogus if the area hasn't
+ // yet been restorted (caller can't possibly know where its putting the
+ // widget in relation to other widgets).
+ if (this.isAreaLazy(aArea)) {
+ gFuturePlacements.get(aArea).add(aWidgetId);
+ return;
+ }
+
+ if (this.isSpecialWidget(aWidgetId)) {
+ aWidgetId = this.ensureSpecialWidgetId(aWidgetId);
+ }
+
+ let oldPlacement = this.getPlacementOfWidget(aWidgetId, false, true);
+ if (oldPlacement && oldPlacement.area == aArea) {
+ this.moveWidgetWithinArea(aWidgetId, aPosition);
+ return;
+ }
+
+ // Do nothing if the widget is not allowed to move to the target area.
+ if (!this.canWidgetMoveToArea(aWidgetId, aArea)) {
+ return;
+ }
+
+ if (oldPlacement) {
+ this.removeWidgetFromArea(aWidgetId);
+ }
+
+ if (!gPlacements.has(aArea)) {
+ gPlacements.set(aArea, [aWidgetId]);
+ aPosition = 0;
+ } else {
+ let placements = gPlacements.get(aArea);
+ if (typeof aPosition != "number") {
+ aPosition = placements.length;
+ }
+ if (aPosition < 0) {
+ aPosition = 0;
+ }
+ placements.splice(aPosition, 0, aWidgetId);
+ }
+
+ let widget = gPalette.get(aWidgetId);
+ if (widget) {
+ widget.currentArea = aArea;
+ widget.currentPosition = aPosition;
+ }
+
+ // We initially set placements with addWidgetToArea, so in that case
+ // we don't consider the area "dirtied".
+ if (!aInitialAdd) {
+ gDirtyAreaCache.add(aArea);
+ }
+
+ gDirty = true;
+ this.saveState();
+
+ this.notifyListeners("onWidgetAdded", aWidgetId, aArea, aPosition);
+ },
+
+ removeWidgetFromArea: function(aWidgetId) {
+ let oldPlacement = this.getPlacementOfWidget(aWidgetId, false, true);
+ if (!oldPlacement) {
+ return;
+ }
+
+ if (!this.isWidgetRemovable(aWidgetId)) {
+ return;
+ }
+
+ let placements = gPlacements.get(oldPlacement.area);
+ let position = placements.indexOf(aWidgetId);
+ if (position != -1) {
+ placements.splice(position, 1);
+ }
+
+ let widget = gPalette.get(aWidgetId);
+ if (widget) {
+ widget.currentArea = null;
+ widget.currentPosition = null;
+ }
+
+ gDirty = true;
+ this.saveState();
+ gDirtyAreaCache.add(oldPlacement.area);
+
+ this.notifyListeners("onWidgetRemoved", aWidgetId, oldPlacement.area);
+ },
+
+ moveWidgetWithinArea: function(aWidgetId, aPosition) {
+ let oldPlacement = this.getPlacementOfWidget(aWidgetId);
+ if (!oldPlacement) {
+ return;
+ }
+
+ let placements = gPlacements.get(oldPlacement.area);
+ if (typeof aPosition != "number") {
+ aPosition = placements.length;
+ } else if (aPosition < 0) {
+ aPosition = 0;
+ } else if (aPosition > placements.length) {
+ aPosition = placements.length;
+ }
+
+ let widget = gPalette.get(aWidgetId);
+ if (widget) {
+ widget.currentPosition = aPosition;
+ widget.currentArea = oldPlacement.area;
+ }
+
+ if (aPosition == oldPlacement.position) {
+ return;
+ }
+
+ placements.splice(oldPlacement.position, 1);
+ // If we just removed the item from *before* where it is now added,
+ // we need to compensate the position offset for that:
+ if (oldPlacement.position < aPosition) {
+ aPosition--;
+ }
+ placements.splice(aPosition, 0, aWidgetId);
+
+ gDirty = true;
+ gDirtyAreaCache.add(oldPlacement.area);
+
+ this.saveState();
+
+ this.notifyListeners("onWidgetMoved", aWidgetId, oldPlacement.area,
+ oldPlacement.position, aPosition);
+ },
+
+ // Note that this does not populate gPlacements, which is done lazily so that
+ // the legacy state can be migrated, which is only available once a browser
+ // window is openned.
+ // The panel area is an exception here, since it has no legacy state and is
+ // built lazily - and therefore wouldn't otherwise result in restoring its
+ // state immediately when a browser window opens, which is important for
+ // other consumers of this API.
+ loadSavedState: function() {
+ let state = null;
+ try {
+ state = Services.prefs.getCharPref(kPrefCustomizationState);
+ } catch (e) {
+ log.debug("No saved state found");
+ // This will fail if nothing has been customized, so silently fall back to
+ // the defaults.
+ }
+
+ if (!state) {
+ return;
+ }
+ try {
+ gSavedState = JSON.parse(state);
+ if (typeof gSavedState != "object" || gSavedState === null) {
+ throw "Invalid saved state";
+ }
+ } catch (e) {
+ Services.prefs.clearUserPref(kPrefCustomizationState);
+ gSavedState = {};
+ log.debug("Error loading saved UI customization state, falling back to defaults.");
+ }
+
+ if (!("placements" in gSavedState)) {
+ gSavedState.placements = {};
+ }
+
+ if (!("currentVersion" in gSavedState)) {
+ gSavedState.currentVersion = 0;
+ }
+
+ gSeenWidgets = new Set(gSavedState.seen || []);
+ gDirtyAreaCache = new Set(gSavedState.dirtyAreaCache || []);
+ gNewElementCount = gSavedState.newElementCount || 0;
+ },
+
+ restoreStateForArea: function(aArea, aLegacyState) {
+ let placementsPreexisted = gPlacements.has(aArea);
+
+ this.beginBatchUpdate();
+ try {
+ gRestoring = true;
+
+ let restored = false;
+ if (placementsPreexisted) {
+ log.debug("Restoring " + aArea + " from pre-existing placements");
+ for (let [position, id] of gPlacements.get(aArea).entries()) {
+ this.moveWidgetWithinArea(id, position);
+ }
+ gDirty = false;
+ restored = true;
+ } else {
+ gPlacements.set(aArea, []);
+ }
+
+ if (!restored && gSavedState && aArea in gSavedState.placements) {
+ log.debug("Restoring " + aArea + " from saved state");
+ let placements = gSavedState.placements[aArea];
+ for (let id of placements)
+ this.addWidgetToArea(id, aArea);
+ gDirty = false;
+ restored = true;
+ }
+
+ if (!restored && aLegacyState) {
+ log.debug("Restoring " + aArea + " from legacy state");
+ for (let id of aLegacyState)
+ this.addWidgetToArea(id, aArea);
+ // Don't override dirty state, to ensure legacy state is saved here and
+ // therefore only used once.
+ restored = true;
+ }
+
+ if (!restored) {
+ log.debug("Restoring " + aArea + " from default state");
+ let defaults = gAreas.get(aArea).get("defaultPlacements");
+ if (defaults) {
+ for (let id of defaults)
+ this.addWidgetToArea(id, aArea, null, true);
+ }
+ gDirty = false;
+ }
+
+ // Finally, add widgets to the area that were added before the it was able
+ // to be restored. This can occur when add-ons register widgets for a
+ // lazily-restored area before it's been restored.
+ if (gFuturePlacements.has(aArea)) {
+ for (let id of gFuturePlacements.get(aArea))
+ this.addWidgetToArea(id, aArea);
+ gFuturePlacements.delete(aArea);
+ }
+
+ log.debug("Placements for " + aArea + ":\n\t" + gPlacements.get(aArea).join("\n\t"));
+
+ gRestoring = false;
+ } finally {
+ this.endBatchUpdate();
+ }
+ },
+
+ saveState: function() {
+ if (gInBatchStack || !gDirty) {
+ return;
+ }
+ // Clone because we want to modify this map:
+ let state = { placements: new Map(gPlacements),
+ seen: gSeenWidgets,
+ dirtyAreaCache: gDirtyAreaCache,
+ currentVersion: kVersion,
+ newElementCount: gNewElementCount };
+
+ // Merge in previously saved areas if not present in gPlacements.
+ // This way, state is still persisted for e.g. temporarily disabled
+ // add-ons - see bug 989338.
+ if (gSavedState && gSavedState.placements) {
+ for (let area of Object.keys(gSavedState.placements)) {
+ if (!state.placements.has(area)) {
+ let placements = gSavedState.placements[area];
+ state.placements.set(area, placements);
+ }
+ }
+ }
+
+ log.debug("Saving state.");
+ let serialized = JSON.stringify(state, this.serializerHelper);
+ log.debug("State saved as: " + serialized);
+ Services.prefs.setCharPref(kPrefCustomizationState, serialized);
+ gDirty = false;
+ },
+
+ serializerHelper: function(aKey, aValue) {
+ if (typeof aValue == "object" && aValue.constructor.name == "Map") {
+ let result = {};
+ for (let [mapKey, mapValue] of aValue)
+ result[mapKey] = mapValue;
+ return result;
+ }
+
+ if (typeof aValue == "object" && aValue.constructor.name == "Set") {
+ return [...aValue];
+ }
+
+ return aValue;
+ },
+
+ beginBatchUpdate: function() {
+ gInBatchStack++;
+ },
+
+ endBatchUpdate: function(aForceDirty) {
+ gInBatchStack--;
+ if (aForceDirty === true) {
+ gDirty = true;
+ }
+ if (gInBatchStack == 0) {
+ this.saveState();
+ } else if (gInBatchStack < 0) {
+ throw new Error("The batch editing stack should never reach a negative number.");
+ }
+ },
+
+ addListener: function(aListener) {
+ gListeners.add(aListener);
+ },
+
+ removeListener: function(aListener) {
+ if (aListener == this) {
+ return;
+ }
+
+ gListeners.delete(aListener);
+ },
+
+ notifyListeners: function(aEvent, ...aArgs) {
+ if (gRestoring) {
+ return;
+ }
+
+ for (let listener of gListeners) {
+ try {
+ if (typeof listener[aEvent] == "function") {
+ listener[aEvent].apply(listener, aArgs);
+ }
+ } catch (e) {
+ log.error(e + " -- " + e.fileName + ":" + e.lineNumber);
+ }
+ }
+ },
+
+ _dispatchToolboxEventToWindow: function(aEventType, aDetails, aWindow) {
+ let evt = new aWindow.CustomEvent(aEventType, {
+ bubbles: true,
+ cancelable: true,
+ detail: aDetails
+ });
+ aWindow.gNavToolbox.dispatchEvent(evt);
+ },
+
+ dispatchToolboxEvent: function(aEventType, aDetails={}, aWindow=null) {
+ if (aWindow) {
+ this._dispatchToolboxEventToWindow(aEventType, aDetails, aWindow);
+ return;
+ }
+ for (let [win, ] of gBuildWindows) {
+ this._dispatchToolboxEventToWindow(aEventType, aDetails, win);
+ }
+ },
+
+ createWidget: function(aProperties) {
+ let widget = this.normalizeWidget(aProperties, CustomizableUI.SOURCE_EXTERNAL);
+ // XXXunf This should probably throw.
+ if (!widget) {
+ log.error("unable to normalize widget");
+ return undefined;
+ }
+
+ gPalette.set(widget.id, widget);
+
+ // Clear our caches:
+ gGroupWrapperCache.delete(widget.id);
+ for (let [win, ] of gBuildWindows) {
+ let cache = gSingleWrapperCache.get(win);
+ if (cache) {
+ cache.delete(widget.id);
+ }
+ }
+
+ this.notifyListeners("onWidgetCreated", widget.id);
+
+ if (widget.defaultArea) {
+ let addToDefaultPlacements = false;
+ let area = gAreas.get(widget.defaultArea);
+ if (!CustomizableUI.isBuiltinToolbar(widget.defaultArea) &&
+ widget.defaultArea != CustomizableUI.AREA_PANEL) {
+ addToDefaultPlacements = true;
+ }
+
+ if (addToDefaultPlacements) {
+ if (area.has("defaultPlacements")) {
+ area.get("defaultPlacements").push(widget.id);
+ } else {
+ area.set("defaultPlacements", [widget.id]);
+ }
+ }
+ }
+
+ // Look through previously saved state to see if we're restoring a widget.
+ let seenAreas = new Set();
+ let widgetMightNeedAutoAdding = true;
+ for (let [area, ] of gPlacements) {
+ seenAreas.add(area);
+ let areaIsRegistered = gAreas.has(area);
+ let index = gPlacements.get(area).indexOf(widget.id);
+ if (index != -1) {
+ widgetMightNeedAutoAdding = false;
+ if (areaIsRegistered) {
+ widget.currentArea = area;
+ widget.currentPosition = index;
+ }
+ break;
+ }
+ }
+
+ // Also look at saved state data directly in areas that haven't yet been
+ // restored. Can't rely on this for restored areas, as they may have
+ // changed.
+ if (widgetMightNeedAutoAdding && gSavedState) {
+ for (let area of Object.keys(gSavedState.placements)) {
+ if (seenAreas.has(area)) {
+ continue;
+ }
+
+ let areaIsRegistered = gAreas.has(area);
+ let index = gSavedState.placements[area].indexOf(widget.id);
+ if (index != -1) {
+ widgetMightNeedAutoAdding = false;
+ if (areaIsRegistered) {
+ widget.currentArea = area;
+ widget.currentPosition = index;
+ }
+ break;
+ }
+ }
+ }
+
+ // If we're restoring the widget to it's old placement, fire off the
+ // onWidgetAdded event - our own handler will take care of adding it to
+ // any build areas.
+ this.beginBatchUpdate();
+ try {
+ if (widget.currentArea) {
+ this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
+ widget.currentPosition);
+ } else if (widgetMightNeedAutoAdding) {
+ let autoAdd = true;
+ try {
+ autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
+ } catch (e) {}
+
+ // If the widget doesn't have an existing placement, and it hasn't been
+ // seen before, then add it to its default area so it can be used.
+ // If the widget is not removable, we *have* to add it to its default
+ // area here.
+ let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
+ if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
+ if (widget.defaultArea) {
+ if (this.isAreaLazy(widget.defaultArea)) {
+ gFuturePlacements.get(widget.defaultArea).add(widget.id);
+ } else {
+ this.addWidgetToArea(widget.id, widget.defaultArea);
+ }
+ }
+ }
+ }
+ } finally {
+ // Ensure we always have this widget in gSeenWidgets, and save
+ // state in case this needs to be done here.
+ gSeenWidgets.add(widget.id);
+ this.endBatchUpdate(true);
+ }
+
+ this.notifyListeners("onWidgetAfterCreation", widget.id, widget.currentArea);
+ return widget.id;
+ },
+
+ createBuiltinWidget: function(aData) {
+ // This should only ever be called on startup, before any windows are
+ // opened - so we know there's no build areas to handle. Also, builtin
+ // widgets are expected to be (mostly) static, so shouldn't affect the
+ // current placement settings.
+
+ // This allows a widget to be both built-in by default but also able to be
+ // destroyed and removed from the area based on criteria that may not be
+ // available when the widget is created -- for example, because some other
+ // feature in the browser supersedes the widget.
+ let conditionalDestroyPromise = aData.conditionalDestroyPromise || null;
+ delete aData.conditionalDestroyPromise;
+
+ let widget = this.normalizeWidget(aData, CustomizableUI.SOURCE_BUILTIN);
+ if (!widget) {
+ log.error("Error creating builtin widget: " + aData.id);
+ return;
+ }
+
+ log.debug("Creating built-in widget with id: " + widget.id);
+ gPalette.set(widget.id, widget);
+
+ if (conditionalDestroyPromise) {
+ conditionalDestroyPromise.then(shouldDestroy => {
+ if (shouldDestroy) {
+ this.destroyWidget(widget.id);
+ this.removeWidgetFromArea(widget.id);
+ }
+ }, err => {
+ Cu.reportError(err);
+ });
+ }
+ },
+
+ // Returns true if the area will eventually lazily restore (but hasn't yet).
+ isAreaLazy: function(aArea) {
+ if (gPlacements.has(aArea)) {
+ return false;
+ }
+ return gAreas.get(aArea).has("legacy");
+ },
+
+ // XXXunf Log some warnings here, when the data provided isn't up to scratch.
+ normalizeWidget: function(aData, aSource) {
+ let widget = {
+ implementation: aData,
+ source: aSource || CustomizableUI.SOURCE_EXTERNAL,
+ instances: new Map(),
+ currentArea: null,
+ removable: true,
+ overflows: true,
+ defaultArea: null,
+ shortcutId: null,
+ tabSpecific: false,
+ tooltiptext: null,
+ showInPrivateBrowsing: true,
+ _introducedInVersion: -1,
+ };
+
+ if (typeof aData.id != "string" || !/^[a-z0-9-_]{1,}$/i.test(aData.id)) {
+ log.error("Given an illegal id in normalizeWidget: " + aData.id);
+ return null;
+ }
+
+ delete widget.implementation.currentArea;
+ widget.implementation.__defineGetter__("currentArea", () => widget.currentArea);
+
+ const kReqStringProps = ["id"];
+ for (let prop of kReqStringProps) {
+ if (typeof aData[prop] != "string") {
+ log.error("Missing required property '" + prop + "' in normalizeWidget: "
+ + aData.id);
+ return null;
+ }
+ widget[prop] = aData[prop];
+ }
+
+ const kOptStringProps = ["label", "tooltiptext", "shortcutId"];
+ for (let prop of kOptStringProps) {
+ if (typeof aData[prop] == "string") {
+ widget[prop] = aData[prop];
+ }
+ }
+
+ const kOptBoolProps = ["removable", "showInPrivateBrowsing", "overflows", "tabSpecific"];
+ for (let prop of kOptBoolProps) {
+ if (typeof aData[prop] == "boolean") {
+ widget[prop] = aData[prop];
+ }
+ }
+
+ // When we normalize builtin widgets, areas have not yet been registered:
+ if (aData.defaultArea &&
+ (aSource == CustomizableUI.SOURCE_BUILTIN || gAreas.has(aData.defaultArea))) {
+ widget.defaultArea = aData.defaultArea;
+ } else if (!widget.removable) {
+ log.error("Widget '" + widget.id + "' is not removable but does not specify " +
+ "a valid defaultArea. That's not possible; it must specify a " +
+ "valid defaultArea as well.");
+ return null;
+ }
+
+ if ("type" in aData && gSupportedWidgetTypes.has(aData.type)) {
+ widget.type = aData.type;
+ } else {
+ widget.type = "button";
+ }
+
+ widget.disabled = aData.disabled === true;
+
+ if (aSource == CustomizableUI.SOURCE_BUILTIN) {
+ widget._introducedInVersion = aData.introducedInVersion || 0;
+ }
+
+ this.wrapWidgetEventHandler("onBeforeCreated", widget);
+ this.wrapWidgetEventHandler("onClick", widget);
+ this.wrapWidgetEventHandler("onCreated", widget);
+ this.wrapWidgetEventHandler("onDestroyed", widget);
+
+ if (widget.type == "button") {
+ widget.onCommand = typeof aData.onCommand == "function" ?
+ aData.onCommand :
+ null;
+ } else if (widget.type == "view") {
+ if (typeof aData.viewId != "string") {
+ log.error("Expected a string for widget " + widget.id + " viewId, but got "
+ + aData.viewId);
+ return null;
+ }
+ widget.viewId = aData.viewId;
+
+ this.wrapWidgetEventHandler("onViewShowing", widget);
+ this.wrapWidgetEventHandler("onViewHiding", widget);
+ } else if (widget.type == "custom") {
+ this.wrapWidgetEventHandler("onBuild", widget);
+ }
+
+ if (gPalette.has(widget.id)) {
+ return null;
+ }
+
+ return widget;
+ },
+
+ wrapWidgetEventHandler: function(aEventName, aWidget) {
+ if (typeof aWidget.implementation[aEventName] != "function") {
+ aWidget[aEventName] = null;
+ return;
+ }
+ aWidget[aEventName] = function(...aArgs) {
+ // Wrap inside a try...catch to properly log errors, until bug 862627 is
+ // fixed, which in turn might help bug 503244.
+ try {
+ // Don't copy the function to the normalized widget object, instead
+ // keep it on the original object provided to the API so that
+ // additional methods can be implemented and used by the event
+ // handlers.
+ return aWidget.implementation[aEventName].apply(aWidget.implementation,
+ aArgs);
+ } catch (e) {
+ Cu.reportError(e);
+ return undefined;
+ }
+ };
+ },
+
+ destroyWidget: function(aWidgetId) {
+ let widget = gPalette.get(aWidgetId);
+ if (!widget) {
+ gGroupWrapperCache.delete(aWidgetId);
+ for (let [window, ] of gBuildWindows) {
+ let windowCache = gSingleWrapperCache.get(window);
+ if (windowCache) {
+ windowCache.delete(aWidgetId);
+ }
+ }
+ return;
+ }
+
+ // Remove it from the default placements of an area if it was added there:
+ if (widget.defaultArea) {
+ let area = gAreas.get(widget.defaultArea);
+ if (area) {
+ let defaultPlacements = area.get("defaultPlacements");
+ // We can assume this is present because if a widget has a defaultArea,
+ // we automatically create a defaultPlacements array for that area.
+ let widgetIndex = defaultPlacements.indexOf(aWidgetId);
+ if (widgetIndex != -1) {
+ defaultPlacements.splice(widgetIndex, 1);
+ }
+ }
+ }
+
+ // This will not remove the widget from gPlacements - we want to keep the
+ // setting so the widget gets put back in it's old position if/when it
+ // returns.
+ for (let [window, ] of gBuildWindows) {
+ let windowCache = gSingleWrapperCache.get(window);
+ if (windowCache) {
+ windowCache.delete(aWidgetId);
+ }
+ let widgetNode = window.document.getElementById(aWidgetId) ||
+ window.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
+ if (widgetNode) {
+ let container = widgetNode.parentNode
+ this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null,
+ container, true);
+ widgetNode.remove();
+ this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null,
+ container, true);
+ }
+ if (widget.type == "view") {
+ let viewNode = window.document.getElementById(widget.viewId);
+ if (viewNode) {
+ for (let eventName of kSubviewEvents) {
+ let handler = "on" + eventName;
+ if (typeof widget[handler] == "function") {
+ viewNode.removeEventListener(eventName, widget[handler], false);
+ }
+ }
+ }
+ }
+ if (widgetNode && widget.onDestroyed) {
+ widget.onDestroyed(window.document);
+ }
+ }
+
+ gPalette.delete(aWidgetId);
+ gGroupWrapperCache.delete(aWidgetId);
+
+ this.notifyListeners("onWidgetDestroyed", aWidgetId);
+ },
+
+ getCustomizeTargetForArea: function(aArea, aWindow) {
+ let buildAreaNodes = gBuildAreas.get(aArea);
+ if (!buildAreaNodes) {
+ return null;
+ }
+
+ for (let node of buildAreaNodes) {
+ if (node.ownerGlobal == aWindow) {
+ return node.customizationTarget ? node.customizationTarget : node;
+ }
+ }
+
+ return null;
+ },
+
+ reset: function() {
+ gResetting = true;
+ this._resetUIState();
+
+ // Rebuild each registered area (across windows) to reflect the state that
+ // was reset above.
+ this._rebuildRegisteredAreas();
+
+ for (let [widgetId, widget] of gPalette) {
+ if (widget.source == CustomizableUI.SOURCE_EXTERNAL) {
+ gSeenWidgets.add(widgetId);
+ }
+ }
+ if (gSeenWidgets.size) {
+ gDirty = true;
+ }
+
+ gResetting = false;
+ },
+
+ _resetUIState: function() {
+ try {
+ gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
+ gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
+ gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme;
+ } catch (e) { }
+
+ this._resetExtraToolbars();
+
+ Services.prefs.clearUserPref(kPrefCustomizationState);
+ Services.prefs.clearUserPref(kPrefDrawInTitlebar);
+ LightweightThemeManager.currentTheme = null;
+ log.debug("State reset");
+
+ // Reset placements to make restoring default placements possible.
+ gPlacements = new Map();
+ gDirtyAreaCache = new Set();
+ gSeenWidgets = new Set();
+ // Clear the saved state to ensure that defaults will be used.
+ gSavedState = null;
+ // Restore the state for each area to its defaults
+ for (let [areaId, ] of gAreas) {
+ this.restoreStateForArea(areaId);
+ }
+ },
+
+ _resetExtraToolbars: function(aFilter = null) {
+ let firstWindow = true; // Only need to unregister and persist once
+ for (let [win, ] of gBuildWindows) {
+ let toolbox = win.gNavToolbox;
+ for (let child of toolbox.children) {
+ let matchesFilter = !aFilter || aFilter == child.id;
+ if (child.hasAttribute("customindex") && matchesFilter) {
+ let toolbarId = "toolbar" + child.getAttribute("customindex");
+ toolbox.toolbarset.removeAttribute(toolbarId);
+ if (firstWindow) {
+ win.document.persist(toolbox.toolbarset.id, toolbarId);
+ // We have to unregister it properly to ensure we don't kill
+ // XUL widgets which might be in here
+ this.unregisterArea(child.id, true);
+ }
+ child.remove();
+ }
+ }
+ firstWindow = false;
+ }
+ },
+
+ _rebuildRegisteredAreas: function() {
+ for (let [areaId, areaNodes] of gBuildAreas) {
+ let placements = gPlacements.get(areaId);
+ let isFirstChangedToolbar = true;
+ for (let areaNode of areaNodes) {
+ this.buildArea(areaId, placements, areaNode);
+
+ let area = gAreas.get(areaId);
+ if (area.get("type") == CustomizableUI.TYPE_TOOLBAR) {
+ let defaultCollapsed = area.get("defaultCollapsed");
+ let win = areaNode.ownerGlobal;
+ if (defaultCollapsed !== null) {
+ win.setToolbarVisibility(areaNode, !defaultCollapsed, isFirstChangedToolbar);
+ }
+ }
+ isFirstChangedToolbar = false;
+ }
+ }
+ },
+
+ /**
+ * Undoes a previous reset, restoring the state of the UI to the state prior to the reset.
+ */
+ undoReset: function() {
+ if (gUIStateBeforeReset.uiCustomizationState == null ||
+ gUIStateBeforeReset.drawInTitlebar == null) {
+ return;
+ }
+ gUndoResetting = true;
+
+ let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
+ let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
+ let currentTheme = gUIStateBeforeReset.currentTheme;
+
+ // Need to clear the previous state before setting the prefs
+ // because pref observers may check if there is a previous UI state.
+ this._clearPreviousUIState();
+
+ Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
+ Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
+ LightweightThemeManager.currentTheme = currentTheme;
+ this.loadSavedState();
+ // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
+ // and we don't need to do anything else here:
+ if (gSavedState) {
+ for (let areaId of Object.keys(gSavedState.placements)) {
+ let placements = gSavedState.placements[areaId];
+ gPlacements.set(areaId, placements);
+ }
+ this._rebuildRegisteredAreas();
+ }
+
+ gUndoResetting = false;
+ },
+
+ _clearPreviousUIState: function() {
+ Object.getOwnPropertyNames(gUIStateBeforeReset).forEach((prop) => {
+ gUIStateBeforeReset[prop] = null;
+ });
+ },
+
+ removeExtraToolbar: function(aToolbarId) {
+ this._resetExtraToolbars(aToolbarId);
+ },
+
+ /**
+ * @param {String|Node} aWidget - widget ID or a widget node (preferred for performance).
+ * @return {Boolean} whether the widget is removable
+ */
+ isWidgetRemovable: function(aWidget) {
+ let widgetId;
+ let widgetNode;
+ if (typeof aWidget == "string") {
+ widgetId = aWidget;
+ } else {
+ widgetId = aWidget.id;
+ widgetNode = aWidget;
+ }
+ let provider = this.getWidgetProvider(widgetId);
+
+ if (provider == CustomizableUI.PROVIDER_API) {
+ return gPalette.get(widgetId).removable;
+ }
+
+ if (provider == CustomizableUI.PROVIDER_XUL) {
+ if (gBuildWindows.size == 0) {
+ // We don't have any build windows to look at, so just assume for now
+ // that its removable.
+ return true;
+ }
+
+ if (!widgetNode) {
+ // Pick any of the build windows to look at.
+ let [window, ] = [...gBuildWindows][0];
+ [, widgetNode] = this.getWidgetNode(widgetId, window);
+ }
+ // If we don't have a node, we assume it's removable. This can happen because
+ // getWidgetProvider returns PROVIDER_XUL by default, but this will also happen
+ // for API-provided widgets which have been destroyed.
+ if (!widgetNode) {
+ return true;
+ }
+ return widgetNode.getAttribute("removable") == "true";
+ }
+
+ // Otherwise this is either a special widget, which is always removable, or
+ // an API widget which has already been removed from gPalette. Returning true
+ // here allows us to then remove its ID from any placements where it might
+ // still occur.
+ return true;
+ },
+
+ canWidgetMoveToArea: function(aWidgetId, aArea) {
+ let placement = this.getPlacementOfWidget(aWidgetId);
+ if (placement && placement.area != aArea) {
+ // Special widgets can't move to the menu panel.
+ if (this.isSpecialWidget(aWidgetId) && gAreas.has(aArea) &&
+ gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL) {
+ return false;
+ }
+ // For everything else, just return whether the widget is removable.
+ return this.isWidgetRemovable(aWidgetId);
+ }
+
+ return true;
+ },
+
+ ensureWidgetPlacedInWindow: function(aWidgetId, aWindow) {
+ let placement = this.getPlacementOfWidget(aWidgetId);
+ if (!placement) {
+ return false;
+ }
+ let areaNodes = gBuildAreas.get(placement.area);
+ if (!areaNodes) {
+ return false;
+ }
+ let container = [...areaNodes].filter((n) => n.ownerGlobal == aWindow);
+ if (!container.length) {
+ return false;
+ }
+ let existingNode = container[0].getElementsByAttribute("id", aWidgetId)[0];
+ if (existingNode) {
+ return true;
+ }
+
+ this.insertNodeInWindow(aWidgetId, container[0], true);
+ return true;
+ },
+
+ get inDefaultState() {
+ for (let [areaId, props] of gAreas) {
+ let defaultPlacements = props.get("defaultPlacements");
+ // Areas without default placements (like legacy ones?) get skipped
+ if (!defaultPlacements) {
+ continue;
+ }
+
+ let currentPlacements = gPlacements.get(areaId);
+ // We're excluding all of the placement IDs for items that do not exist,
+ // and items that have removable="false",
+ // because we don't want to consider them when determining if we're
+ // in the default state. This way, if an add-on introduces a widget
+ // and is then uninstalled, the leftover placement doesn't cause us to
+ // automatically assume that the buttons are not in the default state.
+ let buildAreaNodes = gBuildAreas.get(areaId);
+ if (buildAreaNodes && buildAreaNodes.size) {
+ let container = [...buildAreaNodes][0];
+ let removableOrDefault = (itemNodeOrItem) => {
+ let item = (itemNodeOrItem && itemNodeOrItem.id) || itemNodeOrItem;
+ let isRemovable = this.isWidgetRemovable(itemNodeOrItem);
+ let isInDefault = defaultPlacements.indexOf(item) != -1;
+ return isRemovable || isInDefault;
+ };
+ // Toolbars have a currentSet property which also deals correctly with overflown
+ // widgets (if any) - use that instead:
+ if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
+ let currentSet = container.currentSet;
+ currentPlacements = currentSet ? currentSet.split(',') : [];
+ currentPlacements = currentPlacements.filter(removableOrDefault);
+ } else {
+ // Clone the array so we don't modify the actual placements...
+ currentPlacements = [...currentPlacements];
+ currentPlacements = currentPlacements.filter((item) => {
+ let itemNode = container.getElementsByAttribute("id", item)[0];
+ return itemNode && removableOrDefault(itemNode || item);
+ });
+ }
+
+ if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
+ let attribute = container.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
+ let collapsed = container.getAttribute(attribute) == "true";
+ let defaultCollapsed = props.get("defaultCollapsed");
+ if (defaultCollapsed !== null && collapsed != defaultCollapsed) {
+ log.debug("Found " + areaId + " had non-default toolbar visibility (expected " + defaultCollapsed + ", was " + collapsed + ")");
+ return false;
+ }
+ }
+ }
+ log.debug("Checking default state for " + areaId + ":\n" + currentPlacements.join(",") +
+ "\nvs.\n" + defaultPlacements.join(","));
+
+ if (currentPlacements.length != defaultPlacements.length) {
+ return false;
+ }
+
+ for (let i = 0; i < currentPlacements.length; ++i) {
+ if (currentPlacements[i] != defaultPlacements[i]) {
+ log.debug("Found " + currentPlacements[i] + " in " + areaId + " where " +
+ defaultPlacements[i] + " was expected!");
+ return false;
+ }
+ }
+ }
+
+ if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
+ log.debug(kPrefDrawInTitlebar + " pref is non-default");
+ return false;
+ }
+
+ if (LightweightThemeManager.currentTheme) {
+ log.debug(LightweightThemeManager.currentTheme + " theme is non-default");
+ return false;
+ }
+
+ return true;
+ },
+
+ setToolbarVisibility: function(aToolbarId, aIsVisible) {
+ // We only persist the attribute the first time.
+ let isFirstChangedToolbar = true;
+ for (let window of CustomizableUI.windows) {
+ let toolbar = window.document.getElementById(aToolbarId);
+ if (toolbar) {
+ window.setToolbarVisibility(toolbar, aIsVisible, isFirstChangedToolbar);
+ isFirstChangedToolbar = false;
+ }
+ }
+ },
+};
+Object.freeze(CustomizableUIInternal);
+
+this.CustomizableUI = {
+ /**
+ * Constant reference to the ID of the menu panel.
+ */
+ AREA_PANEL: "PanelUI-contents",
+ /**
+ * Constant reference to the ID of the navigation toolbar.
+ */
+ AREA_NAVBAR: "nav-bar",
+ /**
+ * Constant reference to the ID of the menubar's toolbar.
+ */
+ AREA_MENUBAR: "toolbar-menubar",
+ /**
+ * Constant reference to the ID of the tabstrip toolbar.
+ */
+ AREA_TABSTRIP: "TabsToolbar",
+ /**
+ * Constant reference to the ID of the bookmarks toolbar.
+ */
+ AREA_BOOKMARKS: "PersonalToolbar",
+ /**
+ * Constant reference to the ID of the addon-bar toolbar shim.
+ * Do not use, this will be removed as soon as reasonably possible.
+ * @deprecated
+ */
+ AREA_ADDONBAR: "addon-bar",
+ /**
+ * Constant indicating the area is a menu panel.
+ */
+ TYPE_MENU_PANEL: "menu-panel",
+ /**
+ * Constant indicating the area is a toolbar.
+ */
+ TYPE_TOOLBAR: "toolbar",
+
+ /**
+ * Constant indicating a XUL-type provider.
+ */
+ PROVIDER_XUL: "xul",
+ /**
+ * Constant indicating an API-type provider.
+ */
+ PROVIDER_API: "api",
+ /**
+ * Constant indicating dynamic (special) widgets: spring, spacer, and separator.
+ */
+ PROVIDER_SPECIAL: "special",
+
+ /**
+ * Constant indicating the widget is built-in
+ */
+ SOURCE_BUILTIN: "builtin",
+ /**
+ * Constant indicating the widget is externally provided
+ * (e.g. by add-ons or other items not part of the builtin widget set).
+ */
+ SOURCE_EXTERNAL: "external",
+
+ /**
+ * The class used to distinguish items that span the entire menu panel.
+ */
+ WIDE_PANEL_CLASS: "panel-wide-item",
+ /**
+ * The (constant) number of columns in the menu panel.
+ */
+ PANEL_COLUMN_COUNT: 3,
+
+ /**
+ * Constant indicating the reason the event was fired was a window closing
+ */
+ REASON_WINDOW_CLOSED: "window-closed",
+ /**
+ * Constant indicating the reason the event was fired was an area being
+ * unregistered separately from window closing mechanics.
+ */
+ REASON_AREA_UNREGISTERED: "area-unregistered",
+
+
+ /**
+ * An iteratable property of windows managed by CustomizableUI.
+ * Note that this can *only* be used as an iterator. ie:
+ * for (let window of CustomizableUI.windows) { ... }
+ */
+ windows: {
+ *[Symbol.iterator]() {
+ for (let [window, ] of gBuildWindows)
+ yield window;
+ }
+ },
+
+ /**
+ * Add a listener object that will get fired for various events regarding
+ * customization.
+ *
+ * @param aListener the listener object to add
+ *
+ * Not all event handler methods need to be defined.
+ * CustomizableUI will catch exceptions. Events are dispatched
+ * synchronously on the UI thread, so if you can delay any/some of your
+ * processing, that is advisable. The following event handlers are supported:
+ * - onWidgetAdded(aWidgetId, aArea, aPosition)
+ * Fired when a widget is added to an area. aWidgetId is the widget that
+ * was added, aArea the area it was added to, and aPosition the position
+ * in which it was added.
+ * - onWidgetMoved(aWidgetId, aArea, aOldPosition, aNewPosition)
+ * Fired when a widget is moved within its area. aWidgetId is the widget
+ * that was moved, aArea the area it was moved in, aOldPosition its old
+ * position, and aNewPosition its new position.
+ * - onWidgetRemoved(aWidgetId, aArea)
+ * Fired when a widget is removed from its area. aWidgetId is the widget
+ * that was removed, aArea the area it was removed from.
+ *
+ * - onWidgetBeforeDOMChange(aNode, aNextNode, aContainer, aIsRemoval)
+ * Fired *before* a widget's DOM node is acted upon by CustomizableUI
+ * (to add, move or remove it). aNode is the DOM node changed, aNextNode
+ * the DOM node (if any) before which a widget will be inserted,
+ * aContainer the *actual* DOM container (could be an overflow panel in
+ * case of an overflowable toolbar), and aWasRemoval is true iff the
+ * action about to happen is the removal of the DOM node.
+ * - onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval)
+ * Like onWidgetBeforeDOMChange, but fired after the change to the DOM
+ * node of the widget.
+ *
+ * - onWidgetReset(aNode, aContainer)
+ * Fired after a reset to default placements moves a widget's node to a
+ * different location. aNode is the widget's node, aContainer is the
+ * area it was moved into (NB: it might already have been there and been
+ * moved to a different position!)
+ * - onWidgetUndoMove(aNode, aContainer)
+ * Fired after undoing a reset to default placements moves a widget's
+ * node to a different location. aNode is the widget's node, aContainer
+ * is the area it was moved into (NB: it might already have been there
+ * and been moved to a different position!)
+ * - onAreaReset(aArea, aContainer)
+ * Fired after a reset to default placements is complete on an area's
+ * DOM node. Note that this is fired for each DOM node. aArea is the area
+ * that was reset, aContainer the DOM node that was reset.
+ *
+ * - onWidgetCreated(aWidgetId)
+ * Fired when a widget with id aWidgetId has been created, but before it
+ * is added to any placements or any DOM nodes have been constructed.
+ * Only fired for API-based widgets.
+ * - onWidgetAfterCreation(aWidgetId, aArea)
+ * Fired after a widget with id aWidgetId has been created, and has been
+ * added to either its default area or the area in which it was placed
+ * previously. If the widget has no default area and/or it has never
+ * been placed anywhere, aArea may be null. Only fired for API-based
+ * widgets.
+ * - onWidgetDestroyed(aWidgetId)
+ * Fired when widgets are destroyed. aWidgetId is the widget that is
+ * being destroyed. Only fired for API-based widgets.
+ * - onWidgetInstanceRemoved(aWidgetId, aDocument)
+ * Fired when a window is unloaded and a widget's instance is destroyed
+ * because of this. Only fired for API-based widgets.
+ *
+ * - onWidgetDrag(aWidgetId, aArea)
+ * Fired both when and after customize mode drag handling system tries
+ * to determine the width and height of widget aWidgetId when dragged to a
+ * different area. aArea will be the area the item is dragged to, or
+ * undefined after the measurements have been done and the node has been
+ * moved back to its 'regular' area.
+ *
+ * - onCustomizeStart(aWindow)
+ * Fired when opening customize mode in aWindow.
+ * - onCustomizeEnd(aWindow)
+ * Fired when exiting customize mode in aWindow.
+ *
+ * - onWidgetOverflow(aNode, aContainer)
+ * Fired when a widget's DOM node is overflowing its container, a toolbar,
+ * and will be displayed in the overflow panel.
+ * - onWidgetUnderflow(aNode, aContainer)
+ * Fired when a widget's DOM node is *not* overflowing its container, a
+ * toolbar, anymore.
+ * - onWindowOpened(aWindow)
+ * Fired when a window has been opened that is managed by CustomizableUI,
+ * once all of the prerequisite setup has been done.
+ * - onWindowClosed(aWindow)
+ * Fired when a window that has been managed by CustomizableUI has been
+ * closed.
+ * - onAreaNodeRegistered(aArea, aContainer)
+ * Fired after an area node is first built when it is registered. This
+ * is often when the window has opened, but in the case of add-ons,
+ * could fire when the node has just been registered with CustomizableUI
+ * after an add-on update or disable/enable sequence.
+ * - onAreaNodeUnregistered(aArea, aContainer, aReason)
+ * Fired when an area node is explicitly unregistered by an API caller,
+ * or by a window closing. The aReason parameter indicates which of
+ * these is the case.
+ */
+ addListener: function(aListener) {
+ CustomizableUIInternal.addListener(aListener);
+ },
+ /**
+ * Remove a listener added with addListener
+ * @param aListener the listener object to remove
+ */
+ removeListener: function(aListener) {
+ CustomizableUIInternal.removeListener(aListener);
+ },
+
+ /**
+ * Register a customizable area with CustomizableUI.
+ * @param aName the name of the area to register. Can only contain
+ * alphanumeric characters, dashes (-) and underscores (_).
+ * @param aProps the properties of the area. The following properties are
+ * recognized:
+ * - type: the type of area. Either TYPE_TOOLBAR (default) or
+ * TYPE_MENU_PANEL;
+ * - anchor: for a menu panel or overflowable toolbar, the
+ * anchoring node for the panel.
+ * - legacy: set to true if you want customizableui to
+ * automatically migrate the currentset attribute
+ * - overflowable: set to true if your toolbar is overflowable.
+ * This requires an anchor, and only has an
+ * effect for toolbars.
+ * - defaultPlacements: an array of widget IDs making up the
+ * default contents of the area
+ * - defaultCollapsed: (INTERNAL ONLY) applies if the type is TYPE_TOOLBAR, specifies
+ * if toolbar is collapsed by default (default to true).
+ * Specify null to ensure that reset/inDefaultArea don't care
+ * about a toolbar's collapsed state
+ */
+ registerArea: function(aName, aProperties) {
+ CustomizableUIInternal.registerArea(aName, aProperties);
+ },
+ /**
+ * Register a concrete node for a registered area. This method is automatically
+ * called from any toolbar in the main browser window that has its
+ * "customizable" attribute set to true. There should normally be no need to
+ * call it yourself.
+ *
+ * Note that ideally, you should register your toolbar using registerArea
+ * before any of the toolbars have their XBL bindings constructed (which
+ * will happen when they're added to the DOM and are not hidden). If you
+ * don't, and your toolbar has a defaultset attribute, CustomizableUI will
+ * register it automatically. If your toolbar does not have a defaultset
+ * attribute, the node will be saved for processing when you call
+ * registerArea. Note that CustomizableUI won't restore state in the area,
+ * allow the user to customize it in customize mode, or otherwise deal
+ * with it, until the area has been registered.
+ */
+ registerToolbarNode: function(aToolbar, aExistingChildren) {
+ CustomizableUIInternal.registerToolbarNode(aToolbar, aExistingChildren);
+ },
+ /**
+ * Register the menu panel node. This method should not be called by anyone
+ * apart from the built-in PanelUI.
+ * @param aPanel the panel DOM node being registered.
+ */
+ registerMenuPanel: function(aPanel) {
+ CustomizableUIInternal.registerMenuPanel(aPanel);
+ },
+ /**
+ * Unregister a customizable area. The inverse of registerArea.
+ *
+ * Unregistering an area will remove all the (removable) widgets in the
+ * area, which will return to the panel, and destroy all other traces
+ * of the area within CustomizableUI. Note that this means the *contents*
+ * of the area's DOM nodes will be moved to the panel or removed, but
+ * the area's DOM nodes *themselves* will stay.
+ *
+ * Furthermore, by default the placements of the area will be kept in the
+ * saved state (!) and restored if you re-register the area at a later
+ * point. This is useful for e.g. add-ons that get disabled and then
+ * re-enabled (e.g. when they update).
+ *
+ * You can override this last behaviour (and destroy the placements
+ * information in the saved state) by passing true for aDestroyPlacements.
+ *
+ * @param aName the name of the area to unregister
+ * @param aDestroyPlacements whether to destroy the placements information
+ * for the area, too.
+ */
+ unregisterArea: function(aName, aDestroyPlacements) {
+ CustomizableUIInternal.unregisterArea(aName, aDestroyPlacements);
+ },
+ /**
+ * Add a widget to an area.
+ * If the area to which you try to add is not known to CustomizableUI,
+ * this will throw.
+ * If the area to which you try to add has not yet been restored from its
+ * legacy state, this will postpone the addition.
+ * If the area to which you try to add is the same as the area in which
+ * the widget is currently placed, this will do the same as
+ * moveWidgetWithinArea.
+ * If the widget cannot be removed from its original location, this will
+ * no-op.
+ *
+ * This will fire an onWidgetAdded notification,
+ * and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification
+ * for each window CustomizableUI knows about.
+ *
+ * @param aWidgetId the ID of the widget to add
+ * @param aArea the ID of the area to add the widget to
+ * @param aPosition the position at which to add the widget. If you do not
+ * pass a position, the widget will be added to the end
+ * of the area.
+ */
+ addWidgetToArea: function(aWidgetId, aArea, aPosition) {
+ CustomizableUIInternal.addWidgetToArea(aWidgetId, aArea, aPosition);
+ },
+ /**
+ * Remove a widget from its area. If the widget cannot be removed from its
+ * area, or is not in any area, this will no-op. Otherwise, this will fire an
+ * onWidgetRemoved notification, and an onWidgetBeforeDOMChange and
+ * onWidgetAfterDOMChange notification for each window CustomizableUI knows
+ * about.
+ *
+ * @param aWidgetId the ID of the widget to remove
+ */
+ removeWidgetFromArea: function(aWidgetId) {
+ CustomizableUIInternal.removeWidgetFromArea(aWidgetId);
+ },
+ /**
+ * Move a widget within an area.
+ * If the widget is not in any area, this will no-op.
+ * If the widget is already at the indicated position, this will no-op.
+ *
+ * Otherwise, this will move the widget and fire an onWidgetMoved notification,
+ * and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification for
+ * each window CustomizableUI knows about.
+ *
+ * @param aWidgetId the ID of the widget to move
+ * @param aPosition the position to move the widget to.
+ * Negative values or values greater than the number of
+ * widgets will be interpreted to mean moving the widget to
+ * respectively the first or last position.
+ */
+ moveWidgetWithinArea: function(aWidgetId, aPosition) {
+ CustomizableUIInternal.moveWidgetWithinArea(aWidgetId, aPosition);
+ },
+ /**
+ * Ensure a XUL-based widget created in a window after areas were
+ * initialized moves to its correct position.
+ * This is roughly equivalent to manually looking up the position and using
+ * insertItem in the old API, but a lot less work for consumers.
+ * Always prefer this over using toolbar.insertItem (which might no-op
+ * because it delegates to addWidgetToArea) or, worse, moving items in the
+ * DOM yourself.
+ *
+ * @param aWidgetId the ID of the widget that was just created
+ * @param aWindow the window in which you want to ensure it was added.
+ *
+ * NB: why is this API per-window, you wonder? Because if you need this,
+ * presumably you yourself need to create the widget in all the windows
+ * and need to loop through them anyway.
+ */
+ ensureWidgetPlacedInWindow: function(aWidgetId, aWindow) {
+ return CustomizableUIInternal.ensureWidgetPlacedInWindow(aWidgetId, aWindow);
+ },
+ /**
+ * Start a batch update of items.
+ * During a batch update, the customization state is not saved to the user's
+ * preferences file, in order to reduce (possibly sync) IO.
+ * Calls to begin/endBatchUpdate may be nested.
+ *
+ * Callers should ensure that NO MATTER WHAT they call endBatchUpdate once
+ * for each call to beginBatchUpdate, even if there are exceptions in the
+ * code in the batch update. Otherwise, for the duration of the
+ * Firefox session, customization state is never saved. Typically, you
+ * would do this using a try...finally block.
+ */
+ beginBatchUpdate: function() {
+ CustomizableUIInternal.beginBatchUpdate();
+ },
+ /**
+ * End a batch update. See the documentation for beginBatchUpdate above.
+ *
+ * State is not saved if we believe it is identical to the last known
+ * saved state. State is only ever saved when all batch updates have
+ * finished (ie there has been 1 endBatchUpdate call for each
+ * beginBatchUpdate call). If any of the endBatchUpdate calls pass
+ * aForceDirty=true, we will flush to the prefs file.
+ *
+ * @param aForceDirty force CustomizableUI to flush to the prefs file when
+ * all batch updates have finished.
+ */
+ endBatchUpdate: function(aForceDirty) {
+ CustomizableUIInternal.endBatchUpdate(aForceDirty);
+ },
+ /**
+ * Create a widget.
+ *
+ * To create a widget, you should pass an object with its desired
+ * properties. The following properties are supported:
+ *
+ * - id: the ID of the widget (required).
+ * - type: a string indicating the type of widget. Possible types
+ * are:
+ * 'button' - for simple button widgets (the default)
+ * 'view' - for buttons that open a panel or subview,
+ * depending on where they are placed.
+ * 'custom' - for fine-grained control over the creation
+ * of the widget.
+ * - viewId: Only useful for views (and required there): the id of the
+ * <panelview> that should be shown when clicking the widget.
+ * - onBuild(aDoc): Only useful for custom widgets (and required there); a
+ * function that will be invoked with the document in which
+ * to build a widget. Should return the DOM node that has
+ * been constructed.
+ * - onBeforeCreated(aDoc): Attached to all non-custom widgets; a function
+ * that will be invoked before the widget gets a DOM node
+ * constructed, passing the document in which that will happen.
+ * This is useful especially for 'view' type widgets that need
+ * to construct their views on the fly (e.g. from bootstrapped
+ * add-ons)
+ * - onCreated(aNode): Attached to all widgets; a function that will be invoked
+ * whenever the widget has a DOM node constructed, passing the
+ * constructed node as an argument.
+ * - onDestroyed(aDoc): Attached to all non-custom widgets; a function that
+ * will be invoked after the widget has a DOM node destroyed,
+ * passing the document from which it was removed. This is
+ * useful especially for 'view' type widgets that need to
+ * cleanup after views that were constructed on the fly.
+ * - onCommand(aEvt): Only useful for button widgets; a function that will be
+ * invoked when the user activates the button.
+ * - onClick(aEvt): Attached to all widgets; a function that will be invoked
+ * when the user clicks the widget.
+ * - onViewShowing(aEvt): Only useful for views; a function that will be
+ * invoked when a user shows your view. If any event
+ * handler calls aEvt.preventDefault(), the view will
+ * not be shown.
+ *
+ * The event's `detail` property is an object with an
+ * `addBlocker` method. Handlers which need to
+ * perform asynchronous operations before the view is
+ * shown may pass this method a Promise, which will
+ * prevent the view from showing until it resolves.
+ * Additionally, if the promise resolves to the exact
+ * value `false`, the view will not be shown.
+ * - onViewHiding(aEvt): Only useful for views; a function that will be
+ * invoked when a user hides your view.
+ * - tooltiptext: string to use for the tooltip of the widget
+ * - label: string to use for the label of the widget
+ * - removable: whether the widget is removable (optional, default: true)
+ * NB: if you specify false here, you must provide a
+ * defaultArea, too.
+ * - overflows: whether widget can overflow when in an overflowable
+ * toolbar (optional, default: true)
+ * - defaultArea: default area to add the widget to
+ * (optional, default: none; required if non-removable)
+ * - shortcutId: id of an element that has a shortcut for this widget
+ * (optional, default: null). This is only used to display
+ * the shortcut as part of the tooltip for builtin widgets
+ * (which have strings inside
+ * customizableWidgets.properties). If you're in an add-on,
+ * you should not set this property.
+ * - showInPrivateBrowsing: whether to show the widget in private browsing
+ * mode (optional, default: true)
+ *
+ * @param aProperties the specifications for the widget.
+ * @return a wrapper around the created widget (see getWidget)
+ */
+ createWidget: function(aProperties) {
+ return CustomizableUIInternal.wrapWidget(
+ CustomizableUIInternal.createWidget(aProperties)
+ );
+ },
+ /**
+ * Destroy a widget
+ *
+ * If the widget is part of the default placements in an area, this will
+ * remove it from there. It will also remove any DOM instances. However,
+ * it will keep the widget in the placements for whatever area it was
+ * in at the time. You can remove it from there yourself by calling
+ * CustomizableUI.removeWidgetFromArea(aWidgetId).
+ *
+ * @param aWidgetId the ID of the widget to destroy
+ */
+ destroyWidget: function(aWidgetId) {
+ CustomizableUIInternal.destroyWidget(aWidgetId);
+ },
+ /**
+ * Get a wrapper object with information about the widget.
+ * The object provides the following properties
+ * (all read-only unless otherwise indicated):
+ *
+ * - id: the widget's ID;
+ * - type: the type of widget (button, view, custom). For
+ * XUL-provided widgets, this is always 'custom';
+ * - provider: the provider type of the widget, id est one of
+ * PROVIDER_API or PROVIDER_XUL;
+ * - forWindow(w): a method to obtain a single window wrapper for a widget,
+ * in the window w passed as the only argument;
+ * - instances: an array of all instances (single window wrappers)
+ * of the widget. This array is NOT live;
+ * - areaType: the type of the widget's current area
+ * - isGroup: true; will be false for wrappers around single widget nodes;
+ * - source: for API-provided widgets, whether they are built-in to
+ * Firefox or add-on-provided;
+ * - disabled: for API-provided widgets, whether the widget is currently
+ * disabled. NB: this property is writable, and will toggle
+ * all the widgets' nodes' disabled states;
+ * - label: for API-provied widgets, the label of the widget;
+ * - tooltiptext: for API-provided widgets, the tooltip of the widget;
+ * - showInPrivateBrowsing: for API-provided widgets, whether the widget is
+ * visible in private browsing;
+ *
+ * Single window wrappers obtained through forWindow(someWindow) or from the
+ * instances array have the following properties
+ * (all read-only unless otherwise indicated):
+ *
+ * - id: the widget's ID;
+ * - type: the type of widget (button, view, custom). For
+ * XUL-provided widgets, this is always 'custom';
+ * - provider: the provider type of the widget, id est one of
+ * PROVIDER_API or PROVIDER_XUL;
+ * - node: reference to the corresponding DOM node;
+ * - anchor: the anchor on which to anchor panels opened from this
+ * node. This will point to the overflow chevron on
+ * overflowable toolbars if and only if your widget node
+ * is overflowed, to the anchor for the panel menu
+ * if your widget is inside the panel menu, and to the
+ * node itself in all other cases;
+ * - overflowed: boolean indicating whether the node is currently in the
+ * overflow panel of the toolbar;
+ * - isGroup: false; will be true for the group widget;
+ * - label: for API-provided widgets, convenience getter for the
+ * label attribute of the DOM node;
+ * - tooltiptext: for API-provided widgets, convenience getter for the
+ * tooltiptext attribute of the DOM node;
+ * - disabled: for API-provided widgets, convenience getter *and setter*
+ * for the disabled state of this single widget. Note that
+ * you may prefer to use the group wrapper's getter/setter
+ * instead.
+ *
+ * @param aWidgetId the ID of the widget whose information you need
+ * @return a wrapper around the widget as described above, or null if the
+ * widget is known not to exist (anymore). NB: non-null return
+ * is no guarantee the widget exists because we cannot know in
+ * advance if a XUL widget exists or not.
+ */
+ getWidget: function(aWidgetId) {
+ return CustomizableUIInternal.wrapWidget(aWidgetId);
+ },
+ /**
+ * Get an array of widget wrappers (see getWidget) for all the widgets
+ * which are currently not in any area (so which are in the palette).
+ *
+ * @param aWindowPalette the palette (and by extension, the window) in which
+ * CustomizableUI should look. This matters because of
+ * course XUL-provided widgets could be available in
+ * some windows but not others, and likewise
+ * API-provided widgets might not exist in a private
+ * window (because of the showInPrivateBrowsing
+ * property).
+ *
+ * @return an array of widget wrappers (see getWidget)
+ */
+ getUnusedWidgets: function(aWindowPalette) {
+ return CustomizableUIInternal.getUnusedWidgets(aWindowPalette).map(
+ CustomizableUIInternal.wrapWidget,
+ CustomizableUIInternal
+ );
+ },
+ /**
+ * Get an array of all the widget IDs placed in an area. This is roughly
+ * equivalent to fetching the currentset attribute and splitting by commas
+ * in the legacy APIs. Modifying the array will not affect CustomizableUI.
+ *
+ * @param aArea the ID of the area whose placements you want to obtain.
+ * @return an array containing the widget IDs that are in the area.
+ *
+ * NB: will throw if called too early (before placements have been fetched)
+ * or if the area is not currently known to CustomizableUI.
+ */
+ getWidgetIdsInArea: function(aArea) {
+ if (!gAreas.has(aArea)) {
+ throw new Error("Unknown customization area: " + aArea);
+ }
+ if (!gPlacements.has(aArea)) {
+ throw new Error("Area not yet restored");
+ }
+
+ // We need to clone this, as we don't want to let consumers muck with placements
+ return [...gPlacements.get(aArea)];
+ },
+ /**
+ * Get an array of widget wrappers for all the widgets in an area. This is
+ * the same as calling getWidgetIdsInArea and .map() ing the result through
+ * CustomizableUI.getWidget. Careful: this means that if there are IDs in there
+ * which don't have corresponding DOM nodes (like in the old-style currentset
+ * attribute), there might be nulls in this array, or items for which
+ * wrapper.forWindow(win) will return null.
+ *
+ * @param aArea the ID of the area whose widgets you want to obtain.
+ * @return an array of widget wrappers and/or null values for the widget IDs
+ * placed in an area.
+ *
+ * NB: will throw if called too early (before placements have been fetched)
+ * or if the area is not currently known to CustomizableUI.
+ */
+ getWidgetsInArea: function(aArea) {
+ return this.getWidgetIdsInArea(aArea).map(
+ CustomizableUIInternal.wrapWidget,
+ CustomizableUIInternal
+ );
+ },
+ /**
+ * Obtain an array of all the area IDs known to CustomizableUI.
+ * This array is created for you, so is modifiable without CustomizableUI
+ * being affected.
+ */
+ get areas() {
+ return [...gAreas.keys()];
+ },
+ /**
+ * Check what kind of area (toolbar or menu panel) an area is. This is
+ * useful if you have a widget that needs to behave differently depending
+ * on its location. Note that widget wrappers have a convenience getter
+ * property (areaType) for this purpose.
+ *
+ * @param aArea the ID of the area whose type you want to know
+ * @return TYPE_TOOLBAR or TYPE_MENU_PANEL depending on the area, null if
+ * the area is unknown.
+ */
+ getAreaType: function(aArea) {
+ let area = gAreas.get(aArea);
+ return area ? area.get("type") : null;
+ },
+ /**
+ * Check if a toolbar is collapsed by default.
+ *
+ * @param aArea the ID of the area whose default-collapsed state you want to know.
+ * @return `true` or `false` depending on the area, null if the area is unknown,
+ * or its collapsed state cannot normally be controlled by the user
+ */
+ isToolbarDefaultCollapsed: function(aArea) {
+ let area = gAreas.get(aArea);
+ return area ? area.get("defaultCollapsed") : null;
+ },
+ /**
+ * Obtain the DOM node that is the customize target for an area in a
+ * specific window.
+ *
+ * Areas can have a customization target that does not correspond to the
+ * node itself. In particular, toolbars that have a customizationtarget
+ * attribute set will have their customization target set to that node.
+ * This means widgets will end up in the customization target, not in the
+ * DOM node with the ID that corresponds to the area ID. This is useful
+ * because it lets you have fixed content in a toolbar (e.g. the panel
+ * menu item in the navbar) and have all the customizable widgets use
+ * the customization target.
+ *
+ * Using this API yourself is discouraged; you should generally not need
+ * to be asking for the DOM container node used for a particular area.
+ * In particular, if you're wanting to check it in relation to a widget's
+ * node, your DOM node might not be a direct child of the customize target
+ * in a window if, for instance, the window is in customization mode, or if
+ * this is an overflowable toolbar and the widget has been overflowed.
+ *
+ * @param aArea the ID of the area whose customize target you want to have
+ * @param aWindow the window where you want to fetch the DOM node.
+ * @return the customize target DOM node for aArea in aWindow
+ */
+ getCustomizeTargetForArea: function(aArea, aWindow) {
+ return CustomizableUIInternal.getCustomizeTargetForArea(aArea, aWindow);
+ },
+ /**
+ * Reset the customization state back to its default.
+ *
+ * This is the nuclear option. You should never call this except if the user
+ * explicitly requests it. Firefox does this when the user clicks the
+ * "Restore Defaults" button in customize mode.
+ */
+ reset: function() {
+ CustomizableUIInternal.reset();
+ },
+
+ /**
+ * Undo the previous reset, can only be called immediately after a reset.
+ * @return a promise that will be resolved when the operation is complete.
+ */
+ undoReset: function() {
+ CustomizableUIInternal.undoReset();
+ },
+
+ /**
+ * Remove a custom toolbar added in a previous version of Firefox or using
+ * an add-on. NB: only works on the customizable toolbars generated by
+ * the toolbox itself. Intended for use from CustomizeMode, not by
+ * other consumers.
+ * @param aToolbarId the ID of the toolbar to remove
+ */
+ removeExtraToolbar: function(aToolbarId) {
+ CustomizableUIInternal.removeExtraToolbar(aToolbarId);
+ },
+
+ /**
+ * Can the last Restore Defaults operation be undone.
+ *
+ * @return A boolean stating whether an undo of the
+ * Restore Defaults can be performed.
+ */
+ get canUndoReset() {
+ return gUIStateBeforeReset.uiCustomizationState != null ||
+ gUIStateBeforeReset.drawInTitlebar != null ||
+ gUIStateBeforeReset.currentTheme != null;
+ },
+
+ /**
+ * Get the placement of a widget. This is by far the best way to obtain
+ * information about what the state of your widget is. The internals of
+ * this call are cheap (no DOM necessary) and you will know where the user
+ * has put your widget.
+ *
+ * @param aWidgetId the ID of the widget whose placement you want to know
+ * @return
+ * {
+ * area: "somearea", // The ID of the area where the widget is placed
+ * position: 42 // the index in the placements array corresponding to
+ * // your widget.
+ * }
+ *
+ * OR
+ *
+ * null // if the widget is not placed anywhere (ie in the palette)
+ */
+ getPlacementOfWidget: function(aWidgetId, aOnlyRegistered=true, aDeadAreas=false) {
+ return CustomizableUIInternal.getPlacementOfWidget(aWidgetId, aOnlyRegistered, aDeadAreas);
+ },
+ /**
+ * Check if a widget can be removed from the area it's in.
+ *
+ * Note that if you're wanting to move the widget somewhere, you should
+ * generally be checking canWidgetMoveToArea, because that will return
+ * true if the widget is already in the area where you want to move it (!).
+ *
+ * NB: oh, also, this method might lie if the widget in question is a
+ * XUL-provided widget and there are no windows open, because it
+ * can obviously not check anything in this case. It will return
+ * true. You will be able to move the widget elsewhere. However,
+ * once the user reopens a window, the widget will move back to its
+ * 'proper' area automagically.
+ *
+ * @param aWidgetId a widget ID or DOM node to check
+ * @return true if the widget can be removed from its area,
+ * false otherwise.
+ */
+ isWidgetRemovable: function(aWidgetId) {
+ return CustomizableUIInternal.isWidgetRemovable(aWidgetId);
+ },
+ /**
+ * Check if a widget can be moved to a particular area. Like
+ * isWidgetRemovable but better, because it'll return true if the widget
+ * is already in the right area.
+ *
+ * @param aWidgetId the widget ID or DOM node you want to move somewhere
+ * @param aArea the area ID you want to move it to.
+ * @return true if this is possible, false if it is not. The same caveats as
+ * for isWidgetRemovable apply, however, if no windows are open.
+ */
+ canWidgetMoveToArea: function(aWidgetId, aArea) {
+ return CustomizableUIInternal.canWidgetMoveToArea(aWidgetId, aArea);
+ },
+ /**
+ * Whether we're in a default state. Note that non-removable non-default
+ * widgets and non-existing widgets are not taken into account in determining
+ * whether we're in the default state.
+ *
+ * NB: this is a property with a getter. The getter is NOT cheap, because
+ * it does smart things with non-removable non-default items, non-existent
+ * items, and so forth. Please don't call unless necessary.
+ */
+ get inDefaultState() {
+ return CustomizableUIInternal.inDefaultState;
+ },
+
+ /**
+ * Set a toolbar's visibility state in all windows.
+ * @param aToolbarId the toolbar whose visibility should be adjusted
+ * @param aIsVisible whether the toolbar should be visible
+ */
+ setToolbarVisibility: function(aToolbarId, aIsVisible) {
+ CustomizableUIInternal.setToolbarVisibility(aToolbarId, aIsVisible);
+ },
+
+ /**
+ * Get a localized property off a (widget?) object.
+ *
+ * NB: this is unlikely to be useful unless you're in Firefox code, because
+ * this code uses the builtin widget stringbundle, and can't be told
+ * to use add-on-provided strings. It's mainly here as convenience for
+ * custom builtin widgets that build their own DOM but use the same
+ * stringbundle as the other builtin widgets.
+ *
+ * @param aWidget the object whose property we should use to fetch a
+ * localizable string;
+ * @param aProp the property on the object to use for the fetching;
+ * @param aFormatArgs (optional) any extra arguments to use for a formatted
+ * string;
+ * @param aDef (optional) the default to return if we don't find the
+ * string in the stringbundle;
+ *
+ * @return the localized string, or aDef if the string isn't in the bundle.
+ * If no default is provided,
+ * if aProp exists on aWidget, we'll return that,
+ * otherwise we'll return the empty string
+ *
+ */
+ getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
+ return CustomizableUIInternal.getLocalizedProperty(aWidget, aProp,
+ aFormatArgs, aDef);
+ },
+ /**
+ * Utility function to detect, find and set a keyboard shortcut for a menuitem
+ * or (toolbar)button.
+ *
+ * @param aShortcutNode the XUL node where the shortcut will be derived from;
+ * @param aTargetNode (optional) the XUL node on which the `shortcut`
+ * attribute will be set. If NULL, the shortcut will be
+ * set on aShortcutNode;
+ */
+ addShortcut: function(aShortcutNode, aTargetNode) {
+ return CustomizableUIInternal.addShortcut(aShortcutNode, aTargetNode);
+ },
+ /**
+ * Given a node, walk up to the first panel in its ancestor chain, and
+ * close it.
+ *
+ * @param aNode a node whose panel should be closed;
+ */
+ hidePanelForNode: function(aNode) {
+ CustomizableUIInternal.hidePanelForNode(aNode);
+ },
+ /**
+ * Check if a widget is a "special" widget: a spring, spacer or separator.
+ *
+ * @param aWidgetId the widget ID to check.
+ * @return true if the widget is 'special', false otherwise.
+ */
+ isSpecialWidget: function(aWidgetId) {
+ return CustomizableUIInternal.isSpecialWidget(aWidgetId);
+ },
+ /**
+ * Add listeners to a panel that will close it. For use from the menu panel
+ * and overflowable toolbar implementations, unlikely to be useful for
+ * consumers.
+ *
+ * @param aPanel the panel to which listeners should be attached.
+ */
+ addPanelCloseListeners: function(aPanel) {
+ CustomizableUIInternal.addPanelCloseListeners(aPanel);
+ },
+ /**
+ * Remove close listeners that have been added to a panel with
+ * addPanelCloseListeners. For use from the menu panel and overflowable
+ * toolbar implementations, unlikely to be useful for consumers.
+ *
+ * @param aPanel the panel from which listeners should be removed.
+ */
+ removePanelCloseListeners: function(aPanel) {
+ CustomizableUIInternal.removePanelCloseListeners(aPanel);
+ },
+ /**
+ * Notify listeners a widget is about to be dragged to an area. For use from
+ * Customize Mode only, do not use otherwise.
+ *
+ * @param aWidgetId the ID of the widget that is being dragged to an area.
+ * @param aArea the ID of the area to which the widget is being dragged.
+ */
+ onWidgetDrag: function(aWidgetId, aArea) {
+ CustomizableUIInternal.notifyListeners("onWidgetDrag", aWidgetId, aArea);
+ },
+ /**
+ * Notify listeners that a window is entering customize mode. For use from
+ * Customize Mode only, do not use otherwise.
+ * @param aWindow the window entering customize mode
+ */
+ notifyStartCustomizing: function(aWindow) {
+ CustomizableUIInternal.notifyListeners("onCustomizeStart", aWindow);
+ },
+ /**
+ * Notify listeners that a window is exiting customize mode. For use from
+ * Customize Mode only, do not use otherwise.
+ * @param aWindow the window exiting customize mode
+ */
+ notifyEndCustomizing: function(aWindow) {
+ CustomizableUIInternal.notifyListeners("onCustomizeEnd", aWindow);
+ },
+
+ /**
+ * Notify toolbox(es) of a particular event. If you don't pass aWindow,
+ * all toolboxes will be notified. For use from Customize Mode only,
+ * do not use otherwise.
+ * @param aEvent the name of the event to send.
+ * @param aDetails optional, the details of the event.
+ * @param aWindow optional, the window in which to send the event.
+ */
+ dispatchToolboxEvent: function(aEvent, aDetails={}, aWindow=null) {
+ CustomizableUIInternal.dispatchToolboxEvent(aEvent, aDetails, aWindow);
+ },
+
+ /**
+ * Check whether an area is overflowable.
+ *
+ * @param aAreaId the ID of an area to check for overflowable-ness
+ * @return true if the area is overflowable, false otherwise.
+ */
+ isAreaOverflowable: function(aAreaId) {
+ let area = gAreas.get(aAreaId);
+ return area ? area.get("type") == this.TYPE_TOOLBAR && area.get("overflowable")
+ : false;
+ },
+ /**
+ * Obtain a string indicating the place of an element. This is intended
+ * for use from customize mode; You should generally use getPlacementOfWidget
+ * instead, which is cheaper because it does not use the DOM.
+ *
+ * @param aElement the DOM node whose place we need to check
+ * @return "toolbar" if the node is in a toolbar, "panel" if it is in the
+ * menu panel, "palette" if it is in the (visible!) customization
+ * palette, undefined otherwise.
+ */
+ getPlaceForItem: function(aElement) {
+ let place;
+ let node = aElement;
+ while (node && !place) {
+ if (node.localName == "toolbar")
+ place = "toolbar";
+ else if (node.id == CustomizableUI.AREA_PANEL)
+ place = "panel";
+ else if (node.id == "customization-palette")
+ place = "palette";
+
+ node = node.parentNode;
+ }
+ return place;
+ },
+
+ /**
+ * Check if a toolbar is builtin or not.
+ * @param aToolbarId the ID of the toolbar you want to check
+ */
+ isBuiltinToolbar: function(aToolbarId) {
+ return CustomizableUIInternal._builtinToolbars.has(aToolbarId);
+ },
+};
+Object.freeze(this.CustomizableUI);
+Object.freeze(this.CustomizableUI.windows);
+
+/**
+ * All external consumers of widgets are really interacting with these wrappers
+ * which provide a common interface.
+ */
+
+/**
+ * WidgetGroupWrapper is the common interface for interacting with an entire
+ * widget group - AKA, all instances of a widget across a series of windows.
+ * This particular wrapper is only used for widgets created via the provider
+ * API.
+ */
+function WidgetGroupWrapper(aWidget) {
+ this.isGroup = true;
+
+ const kBareProps = ["id", "source", "type", "disabled", "label", "tooltiptext",
+ "showInPrivateBrowsing", "viewId"];
+ for (let prop of kBareProps) {
+ let propertyName = prop;
+ this.__defineGetter__(propertyName, () => aWidget[propertyName]);
+ }
+
+ this.__defineGetter__("provider", () => CustomizableUI.PROVIDER_API);
+
+ this.__defineSetter__("disabled", function(aValue) {
+ aValue = !!aValue;
+ aWidget.disabled = aValue;
+ for (let [, instance] of aWidget.instances) {
+ instance.disabled = aValue;
+ }
+ });
+
+ this.forWindow = function WidgetGroupWrapper_forWindow(aWindow) {
+ let wrapperMap;
+ if (!gSingleWrapperCache.has(aWindow)) {
+ wrapperMap = new Map();
+ gSingleWrapperCache.set(aWindow, wrapperMap);
+ } else {
+ wrapperMap = gSingleWrapperCache.get(aWindow);
+ }
+ if (wrapperMap.has(aWidget.id)) {
+ return wrapperMap.get(aWidget.id);
+ }
+
+ let instance = aWidget.instances.get(aWindow.document);
+ if (!instance &&
+ (aWidget.showInPrivateBrowsing || !PrivateBrowsingUtils.isWindowPrivate(aWindow))) {
+ instance = CustomizableUIInternal.buildWidget(aWindow.document,
+ aWidget);
+ }
+
+ let wrapper = new WidgetSingleWrapper(aWidget, instance);
+ wrapperMap.set(aWidget.id, wrapper);
+ return wrapper;
+ };
+
+ this.__defineGetter__("instances", function() {
+ // Can't use gBuildWindows here because some areas load lazily:
+ let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
+ if (!placement) {
+ return [];
+ }
+ let area = placement.area;
+ let buildAreas = gBuildAreas.get(area);
+ if (!buildAreas) {
+ return [];
+ }
+ return Array.from(buildAreas, (node) => this.forWindow(node.ownerGlobal));
+ });
+
+ this.__defineGetter__("areaType", function() {
+ let areaProps = gAreas.get(aWidget.currentArea);
+ return areaProps && areaProps.get("type");
+ });
+
+ Object.freeze(this);
+}
+
+/**
+ * A WidgetSingleWrapper is a wrapper around a single instance of a widget in
+ * a particular window.
+ */
+function WidgetSingleWrapper(aWidget, aNode) {
+ this.isGroup = false;
+
+ this.node = aNode;
+ this.provider = CustomizableUI.PROVIDER_API;
+
+ const kGlobalProps = ["id", "type"];
+ for (let prop of kGlobalProps) {
+ this[prop] = aWidget[prop];
+ }
+
+ const kNodeProps = ["label", "tooltiptext"];
+ for (let prop of kNodeProps) {
+ let propertyName = prop;
+ // Look at the node for these, instead of the widget data, to ensure the
+ // wrapper always reflects this live instance.
+ this.__defineGetter__(propertyName,
+ () => aNode.getAttribute(propertyName));
+ }
+
+ this.__defineGetter__("disabled", () => aNode.disabled);
+ this.__defineSetter__("disabled", function(aValue) {
+ aNode.disabled = !!aValue;
+ });
+
+ this.__defineGetter__("anchor", function() {
+ let anchorId;
+ // First check for an anchor for the area:
+ let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
+ if (placement) {
+ anchorId = gAreas.get(placement.area).get("anchor");
+ }
+ if (!anchorId) {
+ anchorId = aNode.getAttribute("cui-anchorid");
+ }
+
+ return anchorId ? aNode.ownerDocument.getElementById(anchorId)
+ : aNode;
+ });
+
+ this.__defineGetter__("overflowed", function() {
+ return aNode.getAttribute("overflowedItem") == "true";
+ });
+
+ Object.freeze(this);
+}
+
+/**
+ * XULWidgetGroupWrapper is the common interface for interacting with an entire
+ * widget group - AKA, all instances of a widget across a series of windows.
+ * This particular wrapper is only used for widgets created via the old-school
+ * XUL method (overlays, or programmatically injecting toolbaritems, or other
+ * such things).
+ */
+// XXXunf Going to need to hook this up to some events to keep it all live.
+function XULWidgetGroupWrapper(aWidgetId) {
+ this.isGroup = true;
+ this.id = aWidgetId;
+ this.type = "custom";
+ this.provider = CustomizableUI.PROVIDER_XUL;
+
+ this.forWindow = function XULWidgetGroupWrapper_forWindow(aWindow) {
+ let wrapperMap;
+ if (!gSingleWrapperCache.has(aWindow)) {
+ wrapperMap = new Map();
+ gSingleWrapperCache.set(aWindow, wrapperMap);
+ } else {
+ wrapperMap = gSingleWrapperCache.get(aWindow);
+ }
+ if (wrapperMap.has(aWidgetId)) {
+ return wrapperMap.get(aWidgetId);
+ }
+
+ let instance = aWindow.document.getElementById(aWidgetId);
+ if (!instance) {
+ // Toolbar palettes aren't part of the document, so elements in there
+ // won't be found via document.getElementById().
+ instance = aWindow.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
+ }
+
+ let wrapper = new XULWidgetSingleWrapper(aWidgetId, instance, aWindow.document);
+ wrapperMap.set(aWidgetId, wrapper);
+ return wrapper;
+ };
+
+ this.__defineGetter__("areaType", function() {
+ let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId);
+ if (!placement) {
+ return null;
+ }
+
+ let areaProps = gAreas.get(placement.area);
+ return areaProps && areaProps.get("type");
+ });
+
+ this.__defineGetter__("instances", function() {
+ return Array.from(gBuildWindows, (wins) => this.forWindow(wins[0]));
+ });
+
+ Object.freeze(this);
+}
+
+/**
+ * A XULWidgetSingleWrapper is a wrapper around a single instance of a XUL
+ * widget in a particular window.
+ */
+function XULWidgetSingleWrapper(aWidgetId, aNode, aDocument) {
+ this.isGroup = false;
+
+ this.id = aWidgetId;
+ this.type = "custom";
+ this.provider = CustomizableUI.PROVIDER_XUL;
+
+ let weakDoc = Cu.getWeakReference(aDocument);
+ // If we keep a strong ref, the weak ref will never die, so null it out:
+ aDocument = null;
+
+ this.__defineGetter__("node", function() {
+ // If we've set this to null (further down), we're sure there's nothing to
+ // be gotten here, so bail out early:
+ if (!weakDoc) {
+ return null;
+ }
+ if (aNode) {
+ // Return the last known node if it's still in the DOM...
+ if (aNode.ownerDocument.contains(aNode)) {
+ return aNode;
+ }
+ // ... or the toolbox
+ let toolbox = aNode.ownerGlobal.gNavToolbox;
+ if (toolbox && toolbox.palette && aNode.parentNode == toolbox.palette) {
+ return aNode;
+ }
+ // If it isn't, clear the cached value and fall through to the "slow" case:
+ aNode = null;
+ }
+
+ let doc = weakDoc.get();
+ if (doc) {
+ // Store locally so we can cache the result:
+ aNode = CustomizableUIInternal.findWidgetInWindow(aWidgetId, doc.defaultView);
+ return aNode;
+ }
+ // The weakref to the document is dead, we're done here forever more:
+ weakDoc = null;
+ return null;
+ });
+
+ this.__defineGetter__("anchor", function() {
+ let anchorId;
+ // First check for an anchor for the area:
+ let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId);
+ if (placement) {
+ anchorId = gAreas.get(placement.area).get("anchor");
+ }
+
+ let node = this.node;
+ if (!anchorId && node) {
+ anchorId = node.getAttribute("cui-anchorid");
+ }
+
+ return (anchorId && node) ? node.ownerDocument.getElementById(anchorId) : node;
+ });
+
+ this.__defineGetter__("overflowed", function() {
+ let node = this.node;
+ if (!node) {
+ return false;
+ }
+ return node.getAttribute("overflowedItem") == "true";
+ });
+
+ Object.freeze(this);
+}
+
+const LAZY_RESIZE_INTERVAL_MS = 200;
+const OVERFLOW_PANEL_HIDE_DELAY_MS = 500;
+
+function OverflowableToolbar(aToolbarNode) {
+ this._toolbar = aToolbarNode;
+ this._collapsed = new Map();
+ this._enabled = true;
+
+ this._toolbar.setAttribute("overflowable", "true");
+ let doc = this._toolbar.ownerDocument;
+ this._target = this._toolbar.customizationTarget;
+ this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
+ this._list.toolbox = this._toolbar.toolbox;
+ this._list.customizationTarget = this._list;
+
+ let window = this._toolbar.ownerGlobal;
+ if (window.gBrowserInit.delayedStartupFinished) {
+ this.init();
+ } else {
+ Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
+ }
+}
+
+OverflowableToolbar.prototype = {
+ initialized: false,
+ _forceOnOverflow: false,
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "browser-delayed-startup-finished" &&
+ aSubject == this._toolbar.ownerGlobal) {
+ Services.obs.removeObserver(this, "browser-delayed-startup-finished");
+ this.init();
+ }
+ },
+
+ init: function() {
+ let doc = this._toolbar.ownerDocument;
+ let window = doc.defaultView;
+ window.addEventListener("resize", this);
+ window.gNavToolbox.addEventListener("customizationstarting", this);
+ window.gNavToolbox.addEventListener("aftercustomization", this);
+
+ let chevronId = this._toolbar.getAttribute("overflowbutton");
+ this._chevron = doc.getElementById(chevronId);
+ this._chevron.addEventListener("command", this);
+ this._chevron.addEventListener("dragover", this);
+ this._chevron.addEventListener("dragend", this);
+
+ let panelId = this._toolbar.getAttribute("overflowpanel");
+ this._panel = doc.getElementById(panelId);
+ this._panel.addEventListener("popuphiding", this);
+ CustomizableUIInternal.addPanelCloseListeners(this._panel);
+
+ CustomizableUI.addListener(this);
+
+ // The 'overflow' event may have been fired before init was called.
+ if (this._toolbar.overflowedDuringConstruction) {
+ this.onOverflow(this._toolbar.overflowedDuringConstruction);
+ this._toolbar.overflowedDuringConstruction = null;
+ }
+
+ this.initialized = true;
+ },
+
+ uninit: function() {
+ this._toolbar.removeEventListener("overflow", this._toolbar);
+ this._toolbar.removeEventListener("underflow", this._toolbar);
+ this._toolbar.removeAttribute("overflowable");
+
+ if (!this.initialized) {
+ Services.obs.removeObserver(this, "browser-delayed-startup-finished");
+ return;
+ }
+
+ this._disable();
+
+ let window = this._toolbar.ownerGlobal;
+ window.removeEventListener("resize", this);
+ window.gNavToolbox.removeEventListener("customizationstarting", this);
+ window.gNavToolbox.removeEventListener("aftercustomization", this);
+ this._chevron.removeEventListener("command", this);
+ this._chevron.removeEventListener("dragover", this);
+ this._chevron.removeEventListener("dragend", this);
+ this._panel.removeEventListener("popuphiding", this);
+ CustomizableUI.removeListener(this);
+ CustomizableUIInternal.removePanelCloseListeners(this._panel);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "aftercustomization":
+ this._enable();
+ break;
+ case "command":
+ if (aEvent.target == this._chevron) {
+ this._onClickChevron(aEvent);
+ } else {
+ this._panel.hidePopup();
+ }
+ break;
+ case "customizationstarting":
+ this._disable();
+ break;
+ case "dragover":
+ this._showWithTimeout();
+ break;
+ case "dragend":
+ this._panel.hidePopup();
+ break;
+ case "popuphiding":
+ this._onPanelHiding(aEvent);
+ break;
+ case "resize":
+ this._onResize(aEvent);
+ }
+ },
+
+ show: function() {
+ if (this._panel.state == "open") {
+ return Promise.resolve();
+ }
+ return new Promise(resolve => {
+ let doc = this._panel.ownerDocument;
+ this._panel.hidden = false;
+ let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
+ gELS.addSystemEventListener(contextMenu, 'command', this, true);
+ let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon");
+ this._panel.openPopup(anchor || this._chevron);
+ this._chevron.open = true;
+
+ let overflowableToolbarInstance = this;
+ this._panel.addEventListener("popupshown", function onPopupShown(aEvent) {
+ this.removeEventListener("popupshown", onPopupShown);
+ this.addEventListener("dragover", overflowableToolbarInstance);
+ this.addEventListener("dragend", overflowableToolbarInstance);
+ resolve();
+ });
+ });
+ },
+
+ _onClickChevron: function(aEvent) {
+ if (this._chevron.open) {
+ this._panel.hidePopup();
+ this._chevron.open = false;
+ } else {
+ this.show();
+ }
+ },
+
+ _onPanelHiding: function(aEvent) {
+ this._chevron.open = false;
+ this._panel.removeEventListener("dragover", this);
+ this._panel.removeEventListener("dragend", this);
+ let doc = aEvent.target.ownerDocument;
+ let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
+ gELS.removeSystemEventListener(contextMenu, 'command', this, true);
+ },
+
+ onOverflow: function(aEvent) {
+ // The rangeParent check is here because of bug 1111986 and ensuring that
+ // overflow events from the bookmarks toolbar items or similar things that
+ // manage their own overflow don't trigger an overflow on the entire toolbar
+ if (!this._enabled ||
+ (aEvent && aEvent.target != this._toolbar.customizationTarget) ||
+ (aEvent && aEvent.rangeParent))
+ return;
+
+ let child = this._target.lastChild;
+
+ while (child && this._target.scrollLeftMin != this._target.scrollLeftMax) {
+ let prevChild = child.previousSibling;
+
+ if (child.getAttribute("overflows") != "false") {
+ this._collapsed.set(child.id, this._target.clientWidth);
+ child.setAttribute("overflowedItem", true);
+ child.setAttribute("cui-anchorid", this._chevron.id);
+ CustomizableUIInternal.notifyListeners("onWidgetOverflow", child, this._target);
+
+ this._list.insertBefore(child, this._list.firstChild);
+ if (!this._toolbar.hasAttribute("overflowing")) {
+ CustomizableUI.addListener(this);
+ }
+ this._toolbar.setAttribute("overflowing", "true");
+ }
+ child = prevChild;
+ }
+
+ let win = this._target.ownerGlobal;
+ win.UpdateUrlbarSearchSplitterState();
+ },
+
+ _onResize: function(aEvent) {
+ if (!this._lazyResizeHandler) {
+ this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this),
+ LAZY_RESIZE_INTERVAL_MS);
+ }
+ this._lazyResizeHandler.arm();
+ },
+
+ _moveItemsBackToTheirOrigin: function(shouldMoveAllItems) {
+ let placements = gPlacements.get(this._toolbar.id);
+ while (this._list.firstChild) {
+ let child = this._list.firstChild;
+ let minSize = this._collapsed.get(child.id);
+
+ if (!shouldMoveAllItems &&
+ minSize &&
+ this._target.clientWidth <= minSize) {
+ return;
+ }
+
+ this._collapsed.delete(child.id);
+ let beforeNodeIndex = placements.indexOf(child.id) + 1;
+ // If this is a skipintoolbarset item, meaning it doesn't occur in the placements list,
+ // we're inserting it at the end. This will mean first-in, first-out (more or less)
+ // leading to as little change in order as possible.
+ if (beforeNodeIndex == 0) {
+ beforeNodeIndex = placements.length;
+ }
+ let inserted = false;
+ for (; beforeNodeIndex < placements.length; beforeNodeIndex++) {
+ let beforeNode = this._target.getElementsByAttribute("id", placements[beforeNodeIndex])[0];
+ if (beforeNode) {
+ this._target.insertBefore(child, beforeNode);
+ inserted = true;
+ break;
+ }
+ }
+ if (!inserted) {
+ this._target.appendChild(child);
+ }
+ child.removeAttribute("cui-anchorid");
+ child.removeAttribute("overflowedItem");
+ CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target);
+ }
+
+ let win = this._target.ownerGlobal;
+ win.UpdateUrlbarSearchSplitterState();
+
+ if (!this._collapsed.size) {
+ this._toolbar.removeAttribute("overflowing");
+ CustomizableUI.removeListener(this);
+ }
+ },
+
+ _onLazyResize: function() {
+ if (!this._enabled)
+ return;
+
+ if (this._target.scrollLeftMin != this._target.scrollLeftMax) {
+ this.onOverflow();
+ } else {
+ this._moveItemsBackToTheirOrigin();
+ }
+ },
+
+ _disable: function() {
+ this._enabled = false;
+ this._moveItemsBackToTheirOrigin(true);
+ if (this._lazyResizeHandler) {
+ this._lazyResizeHandler.disarm();
+ }
+ },
+
+ _enable: function() {
+ this._enabled = true;
+ this.onOverflow();
+ },
+
+ onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer) {
+ if (aContainer != this._target && aContainer != this._list) {
+ return;
+ }
+ // When we (re)move an item, update all the items that come after it in the list
+ // with the minsize *of the item before the to-be-removed node*. This way, we
+ // ensure that we try to move items back as soon as that's possible.
+ if (aNode.parentNode == this._list) {
+ let updatedMinSize;
+ if (aNode.previousSibling) {
+ updatedMinSize = this._collapsed.get(aNode.previousSibling.id);
+ } else {
+ // Force (these) items to try to flow back into the bar:
+ updatedMinSize = 1;
+ }
+ let nextItem = aNode.nextSibling;
+ while (nextItem) {
+ this._collapsed.set(nextItem.id, updatedMinSize);
+ nextItem = nextItem.nextSibling;
+ }
+ }
+ },
+
+ onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
+ if (aContainer != this._target && aContainer != this._list) {
+ return;
+ }
+
+ let nowInBar = aNode.parentNode == aContainer;
+ let nowOverflowed = aNode.parentNode == this._list;
+ let wasOverflowed = this._collapsed.has(aNode.id);
+
+ // If this wasn't overflowed before...
+ if (!wasOverflowed) {
+ // ... but it is now, then we added to the overflow panel. Exciting stuff:
+ if (nowOverflowed) {
+ // NB: we're guaranteed that it has a previousSibling, because if it didn't,
+ // we would have added it to the toolbar instead. See getOverflowedNextNode.
+ let prevId = aNode.previousSibling.id;
+ let minSize = this._collapsed.get(prevId);
+ this._collapsed.set(aNode.id, minSize);
+ aNode.setAttribute("cui-anchorid", this._chevron.id);
+ aNode.setAttribute("overflowedItem", true);
+ CustomizableUIInternal.notifyListeners("onWidgetOverflow", aNode, this._target);
+ }
+ // If it is not overflowed and not in the toolbar, and was not overflowed
+ // either, it moved out of the toolbar. That means there's now space in there!
+ // Let's try to move stuff back:
+ else if (!nowInBar) {
+ this._moveItemsBackToTheirOrigin(true);
+ }
+ // If it's in the toolbar now, then we don't care. An overflow event may
+ // fire afterwards; that's ok!
+ }
+ // If it used to be overflowed...
+ else if (!nowOverflowed) {
+ // ... and isn't anymore, let's remove our bookkeeping:
+ this._collapsed.delete(aNode.id);
+ aNode.removeAttribute("cui-anchorid");
+ aNode.removeAttribute("overflowedItem");
+ CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target);
+
+ if (!this._collapsed.size) {
+ this._toolbar.removeAttribute("overflowing");
+ CustomizableUI.removeListener(this);
+ }
+ } else if (aNode.previousSibling) {
+ // but if it still is, it must have changed places. Bookkeep:
+ let prevId = aNode.previousSibling.id;
+ let minSize = this._collapsed.get(prevId);
+ this._collapsed.set(aNode.id, minSize);
+ } else {
+ // If it's now the first item in the overflow list,
+ // maybe we can return it:
+ this._moveItemsBackToTheirOrigin();
+ }
+ },
+
+ findOverflowedInsertionPoints: function(aNode) {
+ let newNodeCanOverflow = aNode.getAttribute("overflows") != "false";
+ let areaId = this._toolbar.id;
+ let placements = gPlacements.get(areaId);
+ let nodeIndex = placements.indexOf(aNode.id);
+ let nodeBeforeNewNodeIsOverflown = false;
+
+ let loopIndex = -1;
+ while (++loopIndex < placements.length) {
+ let nextNodeId = placements[loopIndex];
+ if (loopIndex > nodeIndex) {
+ if (newNodeCanOverflow && this._collapsed.has(nextNodeId)) {
+ let nextNode = this._list.getElementsByAttribute("id", nextNodeId).item(0);
+ if (nextNode) {
+ return [this._list, nextNode];
+ }
+ }
+ if (!nodeBeforeNewNodeIsOverflown || !newNodeCanOverflow) {
+ let nextNode = this._target.getElementsByAttribute("id", nextNodeId).item(0);
+ if (nextNode) {
+ return [this._target, nextNode];
+ }
+ }
+ } else if (loopIndex < nodeIndex && this._collapsed.has(nextNodeId)) {
+ nodeBeforeNewNodeIsOverflown = true;
+ }
+ }
+
+ let containerForAppending = (this._collapsed.size && newNodeCanOverflow) ?
+ this._list : this._target;
+ return [containerForAppending, null];
+ },
+
+ getContainerFor: function(aNode) {
+ if (aNode.getAttribute("overflowedItem") == "true") {
+ return this._list;
+ }
+ return this._target;
+ },
+
+ _hideTimeoutId: null,
+ _showWithTimeout: function() {
+ this.show().then(function () {
+ let window = this._toolbar.ownerGlobal;
+ if (this._hideTimeoutId) {
+ window.clearTimeout(this._hideTimeoutId);
+ }
+ this._hideTimeoutId = window.setTimeout(() => {
+ if (!this._panel.firstChild.matches(":hover")) {
+ this._panel.hidePopup();
+ }
+ }, OVERFLOW_PANEL_HIDE_DELAY_MS);
+ }.bind(this));
+ },
+};
+
+CustomizableUIInternal.initialize();
diff --git a/browser/components/customizableui/CustomizableWidgets.jsm b/browser/components/customizableui/CustomizableWidgets.jsm
new file mode 100644
index 000000000..907e2e0f7
--- /dev/null
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -0,0 +1,1281 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["CustomizableWidgets"];
+
+Cu.import("resource:///modules/CustomizableUI.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
+ "resource:///modules/BrowserUITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
+ "resource:///modules/PlacesUIUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
+ "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
+ "resource://gre/modules/ShortcutUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
+ "resource://gre/modules/CharsetMenu.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SyncedTabs",
+ "resource://services-sync/SyncedTabs.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
+ "resource://gre/modules/ContextualIdentityService.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() {
+ const kCharsetBundle = "chrome://global/locale/charsetMenu.properties";
+ return Services.strings.createBundle(kCharsetBundle);
+});
+XPCOMUtils.defineLazyGetter(this, "BrandBundle", function() {
+ const kBrandBundle = "chrome://branding/locale/brand.properties";
+ return Services.strings.createBundle(kBrandBundle);
+});
+
+const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const kPrefCustomizationDebug = "browser.uiCustomization.debug";
+const kWidePanelItemClass = "panel-wide-item";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let scope = {};
+ Cu.import("resource://gre/modules/Console.jsm", scope);
+ let debug;
+ try {
+ debug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
+ } catch (ex) {}
+ let consoleOptions = {
+ maxLogLevel: debug ? "all" : "log",
+ prefix: "CustomizableWidgets",
+ };
+ return new scope.ConsoleAPI(consoleOptions);
+});
+
+
+
+function setAttributes(aNode, aAttrs) {
+ let doc = aNode.ownerDocument;
+ for (let [name, value] of Object.entries(aAttrs)) {
+ if (!value) {
+ if (aNode.hasAttribute(name))
+ aNode.removeAttribute(name);
+ } else {
+ if (name == "shortcutId") {
+ continue;
+ }
+ if (name == "label" || name == "tooltiptext") {
+ let stringId = (typeof value == "string") ? value : name;
+ let additionalArgs = [];
+ if (aAttrs.shortcutId) {
+ let shortcut = doc.getElementById(aAttrs.shortcutId);
+ if (shortcut) {
+ additionalArgs.push(ShortcutUtils.prettifyShortcut(shortcut));
+ }
+ }
+ value = CustomizableUI.getLocalizedProperty({id: aAttrs.id}, stringId, additionalArgs);
+ }
+ aNode.setAttribute(name, value);
+ }
+ }
+}
+
+function updateCombinedWidgetStyle(aNode, aArea, aModifyCloseMenu) {
+ let inPanel = (aArea == CustomizableUI.AREA_PANEL);
+ let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1 toolbarbutton-combined";
+ let attrs = {class: cls};
+ if (aModifyCloseMenu) {
+ attrs.closemenu = inPanel ? "none" : null;
+ }
+ for (let i = 0, l = aNode.childNodes.length; i < l; ++i) {
+ if (aNode.childNodes[i].localName == "separator")
+ continue;
+ setAttributes(aNode.childNodes[i], attrs);
+ }
+}
+
+function fillSubviewFromMenuItems(aMenuItems, aSubview) {
+ let attrs = ["oncommand", "onclick", "label", "key", "disabled",
+ "command", "observes", "hidden", "class", "origin",
+ "image", "checked"];
+
+ let doc = aSubview.ownerDocument;
+ let fragment = doc.createDocumentFragment();
+ for (let menuChild of aMenuItems) {
+ if (menuChild.hidden)
+ continue;
+
+ let subviewItem;
+ if (menuChild.localName == "menuseparator") {
+ // Don't insert duplicate or leading separators. This can happen if there are
+ // menus (which we don't copy) above the separator.
+ if (!fragment.lastChild || fragment.lastChild.localName == "menuseparator") {
+ continue;
+ }
+ subviewItem = doc.createElementNS(kNSXUL, "menuseparator");
+ } else if (menuChild.localName == "menuitem") {
+ subviewItem = doc.createElementNS(kNSXUL, "toolbarbutton");
+ CustomizableUI.addShortcut(menuChild, subviewItem);
+
+ let item = menuChild;
+ if (!item.hasAttribute("onclick")) {
+ subviewItem.addEventListener("click", event => {
+ let newEvent = new doc.defaultView.MouseEvent(event.type, event);
+ item.dispatchEvent(newEvent);
+ });
+ }
+
+ if (!item.hasAttribute("oncommand")) {
+ subviewItem.addEventListener("command", event => {
+ let newEvent = doc.createEvent("XULCommandEvent");
+ newEvent.initCommandEvent(
+ event.type, event.bubbles, event.cancelable, event.view,
+ event.detail, event.ctrlKey, event.altKey, event.shiftKey,
+ event.metaKey, event.sourceEvent);
+ item.dispatchEvent(newEvent);
+ });
+ }
+ } else {
+ continue;
+ }
+ for (let attr of attrs) {
+ let attrVal = menuChild.getAttribute(attr);
+ if (attrVal)
+ subviewItem.setAttribute(attr, attrVal);
+ }
+ // We do this after so the .subviewbutton class doesn't get overriden.
+ if (menuChild.localName == "menuitem") {
+ subviewItem.classList.add("subviewbutton");
+ }
+ fragment.appendChild(subviewItem);
+ }
+ aSubview.appendChild(fragment);
+}
+
+function clearSubview(aSubview) {
+ let parent = aSubview.parentNode;
+ // We'll take the container out of the document before cleaning it out
+ // to avoid reflowing each time we remove something.
+ parent.removeChild(aSubview);
+
+ while (aSubview.firstChild) {
+ aSubview.firstChild.remove();
+ }
+
+ parent.appendChild(aSubview);
+}
+
+const CustomizableWidgets = [
+ {
+ id: "history-panelmenu",
+ type: "view",
+ viewId: "PanelUI-history",
+ shortcutId: "key_gotoHistory",
+ tooltiptext: "history-panelmenu.tooltiptext2",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onViewShowing: function(aEvent) {
+ // Populate our list of history
+ const kMaxResults = 15;
+ let doc = aEvent.target.ownerDocument;
+ let win = doc.defaultView;
+
+ let options = PlacesUtils.history.getNewQueryOptions();
+ options.excludeQueries = true;
+ options.queryType = options.QUERY_TYPE_HISTORY;
+ options.sortingMode = options.SORT_BY_DATE_DESCENDING;
+ options.maxResults = kMaxResults;
+ let query = PlacesUtils.history.getNewQuery();
+
+ let items = doc.getElementById("PanelUI-historyItems");
+ // Clear previous history items.
+ while (items.firstChild) {
+ items.firstChild.remove();
+ }
+
+ // Get all statically placed buttons to supply them with keyboard shortcuts.
+ let staticButtons = items.parentNode.getElementsByTagNameNS(kNSXUL, "toolbarbutton");
+ for (let i = 0, l = staticButtons.length; i < l; ++i)
+ CustomizableUI.addShortcut(staticButtons[i]);
+
+ PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .asyncExecuteLegacyQueries([query], 1, options, {
+ handleResult: function (aResultSet) {
+ let onItemCommand = function (aEvent) {
+ // Only handle the click event for middle clicks, we're using the command
+ // event otherwise.
+ if (aEvent.type == "click" && aEvent.button != 1) {
+ return;
+ }
+ let item = aEvent.target;
+ win.openUILink(item.getAttribute("targetURI"), aEvent);
+ CustomizableUI.hidePanelForNode(item);
+ };
+ let fragment = doc.createDocumentFragment();
+ let row;
+ while ((row = aResultSet.getNextRow())) {
+ let uri = row.getResultByIndex(1);
+ let title = row.getResultByIndex(2);
+ let icon = row.getResultByIndex(6);
+
+ let item = doc.createElementNS(kNSXUL, "toolbarbutton");
+ item.setAttribute("label", title || uri);
+ item.setAttribute("targetURI", uri);
+ item.setAttribute("class", "subviewbutton");
+ item.addEventListener("command", onItemCommand);
+ item.addEventListener("click", onItemCommand);
+ if (icon) {
+ let iconURL = "moz-anno:favicon:" + icon;
+ item.setAttribute("image", iconURL);
+ }
+ fragment.appendChild(item);
+ }
+ items.appendChild(fragment);
+ },
+ handleError: function (aError) {
+ log.debug("History view tried to show but had an error: " + aError);
+ },
+ handleCompletion: function (aReason) {
+ log.debug("History view is being shown!");
+ },
+ });
+
+ let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
+ while (recentlyClosedTabs.firstChild) {
+ recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild);
+ }
+
+ let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
+ while (recentlyClosedWindows.firstChild) {
+ recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild);
+ }
+
+ let utils = RecentlyClosedTabsAndWindowsMenuUtils;
+ let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true,
+ "menuRestoreAllTabsSubview.label");
+ let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator");
+ let elementCount = tabsFragment.childElementCount;
+ separator.hidden = !elementCount;
+ while (--elementCount >= 0) {
+ tabsFragment.children[elementCount].classList.add("subviewbutton", "cui-withicon");
+ }
+ recentlyClosedTabs.appendChild(tabsFragment);
+
+ let windowsFragment = utils.getWindowsFragment(doc.defaultView, "toolbarbutton", true,
+ "menuRestoreAllWindowsSubview.label");
+ separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator");
+ elementCount = windowsFragment.childElementCount;
+ separator.hidden = !elementCount;
+ while (--elementCount >= 0) {
+ windowsFragment.children[elementCount].classList.add("subviewbutton", "cui-withicon");
+ }
+ recentlyClosedWindows.appendChild(windowsFragment);
+ },
+ onCreated: function(aNode) {
+ // Middle clicking recently closed items won't close the panel - cope:
+ let onRecentlyClosedClick = function(aEvent) {
+ if (aEvent.button == 1) {
+ CustomizableUI.hidePanelForNode(this);
+ }
+ };
+ let doc = aNode.ownerDocument;
+ let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs");
+ let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows");
+ recentlyClosedTabs.addEventListener("click", onRecentlyClosedClick);
+ recentlyClosedWindows.addEventListener("click", onRecentlyClosedClick);
+ },
+ onViewHiding: function(aEvent) {
+ log.debug("History view is being hidden!");
+ }
+ }, {
+ id: "sync-button",
+ label: "remotetabs-panelmenu.label",
+ tooltiptext: "remotetabs-panelmenu.tooltiptext2",
+ type: "view",
+ viewId: "PanelUI-remotetabs",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ deckIndices: {
+ DECKINDEX_TABS: 0,
+ DECKINDEX_TABSDISABLED: 1,
+ DECKINDEX_FETCHING: 2,
+ DECKINDEX_NOCLIENTS: 3,
+ },
+ onCreated(aNode) {
+ // Add an observer to the button so we get the animation during sync.
+ // (Note the observer sets many attributes, including label and
+ // tooltiptext, but we only want the 'syncstatus' attribute for the
+ // animation)
+ let doc = aNode.ownerDocument;
+ let obnode = doc.createElementNS(kNSXUL, "observes");
+ obnode.setAttribute("element", "sync-status");
+ obnode.setAttribute("attribute", "syncstatus");
+ aNode.appendChild(obnode);
+
+ // A somewhat complicated dance to format the mobilepromo label.
+ let bundle = doc.getElementById("bundle_browser");
+ let formatArgs = ["android", "ios"].map(os => {
+ let link = doc.createElement("label");
+ link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
+ link.setAttribute("mobile-promo-os", os);
+ link.className = "text-link remotetabs-promo-link";
+ return link.outerHTML;
+ });
+ let promoParentElt = doc.getElementById("PanelUI-remotetabs-mobile-promo");
+ // Put it all together...
+ let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
+ promoParentElt.innerHTML = contents;
+ // We manually manage the "click" event to open the promo links because
+ // allowing the "text-link" widget handle it has 2 problems: (1) it only
+ // supports button 0 and (2) it's tricky to intercept when it does the
+ // open and auto-close the panel. (1) can probably be fixed, but (2) is
+ // trickier without hard-coding here the knowledge of exactly what buttons
+ // it does support.
+ // So we allow left and middle clicks to open the link in a new tab and
+ // close the panel; not setting a "href" attribute prevents the text-link
+ // widget handling it, and we build the final URL in the click handler to
+ // make testing easier (ie, so tests can change the pref after the links
+ // were created and have the new pref value used.)
+ promoParentElt.addEventListener("click", e => {
+ let os = e.target.getAttribute("mobile-promo-os");
+ if (!os || e.button > 1) {
+ return;
+ }
+ let link = Services.prefs.getCharPref(`identity.mobilepromo.${os}`) + "synced-tabs";
+ doc.defaultView.openUILinkIn(link, "tab");
+ CustomizableUI.hidePanelForNode(e.target);
+ });
+ },
+ onViewShowing(aEvent) {
+ let doc = aEvent.target.ownerDocument;
+ this._tabsList = doc.getElementById("PanelUI-remotetabs-tabslist");
+ Services.obs.addObserver(this, SyncedTabs.TOPIC_TABS_CHANGED, false);
+
+ if (SyncedTabs.isConfiguredToSyncTabs) {
+ if (SyncedTabs.hasSyncedThisSession) {
+ this.setDeckIndex(this.deckIndices.DECKINDEX_TABS);
+ } else {
+ // Sync hasn't synced tabs yet, so show the "fetching" panel.
+ this.setDeckIndex(this.deckIndices.DECKINDEX_FETCHING);
+ }
+ // force a background sync.
+ SyncedTabs.syncTabs().catch(ex => {
+ Cu.reportError(ex);
+ });
+ // show the current list - it will be updated by our observer.
+ this._showTabs();
+ } else {
+ // not configured to sync tabs, so no point updating the list.
+ this.setDeckIndex(this.deckIndices.DECKINDEX_TABSDISABLED);
+ }
+ },
+ onViewHiding() {
+ Services.obs.removeObserver(this, SyncedTabs.TOPIC_TABS_CHANGED);
+ this._tabsList = null;
+ },
+ _tabsList: null,
+ observe(subject, topic, data) {
+ switch (topic) {
+ case SyncedTabs.TOPIC_TABS_CHANGED:
+ this._showTabs();
+ break;
+ default:
+ break;
+ }
+ },
+ setDeckIndex(index) {
+ let deck = this._tabsList.ownerDocument.getElementById("PanelUI-remotetabs-deck");
+ // We call setAttribute instead of relying on the XBL property setter due
+ // to things going wrong when we try and set the index before the XBL
+ // binding has been created - see bug 1241851 for the gory details.
+ deck.setAttribute("selectedIndex", index);
+ },
+
+ _showTabsPromise: Promise.resolve(),
+ // Update the tab list after any existing in-flight updates are complete.
+ _showTabs() {
+ this._showTabsPromise = this._showTabsPromise.then(() => {
+ return this.__showTabs();
+ });
+ },
+ // Return a new promise to update the tab list.
+ __showTabs() {
+ let doc = this._tabsList.ownerDocument;
+ return SyncedTabs.getTabClients().then(clients => {
+ // The view may have been hidden while the promise was resolving.
+ if (!this._tabsList) {
+ return;
+ }
+ if (clients.length === 0 && !SyncedTabs.hasSyncedThisSession) {
+ // the "fetching tabs" deck is being shown - let's leave it there.
+ // When that first sync completes we'll be notified and update.
+ return;
+ }
+
+ if (clients.length === 0) {
+ this.setDeckIndex(this.deckIndices.DECKINDEX_NOCLIENTS);
+ return;
+ }
+
+ this.setDeckIndex(this.deckIndices.DECKINDEX_TABS);
+ this._clearTabList();
+ SyncedTabs.sortTabClientsByLastUsed(clients, 50 /* maxTabs */);
+ let fragment = doc.createDocumentFragment();
+
+ for (let client of clients) {
+ // add a menu separator for all clients other than the first.
+ if (fragment.lastChild) {
+ let separator = doc.createElementNS(kNSXUL, "menuseparator");
+ fragment.appendChild(separator);
+ }
+ this._appendClient(client, fragment);
+ }
+ this._tabsList.appendChild(fragment);
+ }).catch(err => {
+ Cu.reportError(err);
+ }).then(() => {
+ // an observer for tests.
+ Services.obs.notifyObservers(null, "synced-tabs-menu:test:tabs-updated", null);
+ });
+ },
+ _clearTabList () {
+ let list = this._tabsList;
+ while (list.lastChild) {
+ list.lastChild.remove();
+ }
+ },
+ _showNoClientMessage() {
+ this._appendMessageLabel("notabslabel");
+ },
+ _appendMessageLabel(messageAttr, appendTo = null) {
+ if (!appendTo) {
+ appendTo = this._tabsList;
+ }
+ let message = this._tabsList.getAttribute(messageAttr);
+ let doc = this._tabsList.ownerDocument;
+ let messageLabel = doc.createElementNS(kNSXUL, "label");
+ messageLabel.textContent = message;
+ appendTo.appendChild(messageLabel);
+ return messageLabel;
+ },
+ _appendClient: function (client, attachFragment) {
+ let doc = attachFragment.ownerDocument;
+ // Create the element for the remote client.
+ let clientItem = doc.createElementNS(kNSXUL, "label");
+ clientItem.setAttribute("itemtype", "client");
+ let window = doc.defaultView;
+ clientItem.setAttribute("tooltiptext",
+ window.gSyncUI.formatLastSyncDate(new Date(client.lastModified)));
+ clientItem.textContent = client.name;
+
+ attachFragment.appendChild(clientItem);
+
+ if (client.tabs.length == 0) {
+ let label = this._appendMessageLabel("notabsforclientlabel", attachFragment);
+ label.setAttribute("class", "PanelUI-remotetabs-notabsforclient-label");
+ } else {
+ for (let tab of client.tabs) {
+ let tabEnt = this._createTabElement(doc, tab);
+ attachFragment.appendChild(tabEnt);
+ }
+ }
+ },
+ _createTabElement(doc, tabInfo) {
+ let item = doc.createElementNS(kNSXUL, "toolbarbutton");
+ let tooltipText = (tabInfo.title ? tabInfo.title + "\n" : "") + tabInfo.url;
+ item.setAttribute("itemtype", "tab");
+ item.setAttribute("class", "subviewbutton");
+ item.setAttribute("targetURI", tabInfo.url);
+ item.setAttribute("label", tabInfo.title != "" ? tabInfo.title : tabInfo.url);
+ item.setAttribute("image", tabInfo.icon);
+ item.setAttribute("tooltiptext", tooltipText);
+ // We need to use "click" instead of "command" here so openUILink
+ // respects different buttons (eg, to open in a new tab).
+ item.addEventListener("click", e => {
+ doc.defaultView.openUILink(tabInfo.url, e);
+ CustomizableUI.hidePanelForNode(item);
+ BrowserUITelemetry.countSyncedTabEvent("open", "toolbarbutton-subview");
+ });
+ return item;
+ },
+ }, {
+ id: "privatebrowsing-button",
+ shortcutId: "key_privatebrowsing",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onCommand: function(e) {
+ let win = e.target.ownerGlobal;
+ win.OpenBrowserWindow({private: true});
+ }
+ }, {
+ id: "save-page-button",
+ shortcutId: "key_savePage",
+ tooltiptext: "save-page-button.tooltiptext3",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onCommand: function(aEvent) {
+ let win = aEvent.target.ownerGlobal;
+ win.saveBrowser(win.gBrowser.selectedBrowser);
+ }
+ }, {
+ id: "find-button",
+ shortcutId: "key_find",
+ tooltiptext: "find-button.tooltiptext3",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onCommand: function(aEvent) {
+ let win = aEvent.target.ownerGlobal;
+ if (win.gFindBar) {
+ win.gFindBar.onFindCommand();
+ }
+ }
+ }, {
+ id: "open-file-button",
+ shortcutId: "openFileKb",
+ tooltiptext: "open-file-button.tooltiptext3",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onCommand: function(aEvent) {
+ let win = aEvent.target.ownerGlobal;
+ win.BrowserOpenFileWindow();
+ }
+ }, {
+ id: "sidebar-button",
+ type: "view",
+ viewId: "PanelUI-sidebar",
+ tooltiptext: "sidebar-button.tooltiptext2",
+ onViewShowing: function(aEvent) {
+ // Populate the subview with whatever menuitems are in the
+ // sidebar menu. We skip menu elements, because the menu panel has no way
+ // of dealing with those right now.
+ let doc = aEvent.target.ownerDocument;
+ let menu = doc.getElementById("viewSidebarMenu");
+
+ // First clear any existing menuitems then populate. Add it to the
+ // standard menu first, then copy all sidebar options to the panel.
+ let sidebarItems = doc.getElementById("PanelUI-sidebarItems");
+ clearSubview(sidebarItems);
+ fillSubviewFromMenuItems([...menu.children], sidebarItems);
+ }
+ }, {
+ id: "social-share-button",
+ // custom build our button so we can attach to the share command
+ type: "custom",
+ onBuild: function(aDocument) {
+ let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+ node.setAttribute("id", this.id);
+ node.classList.add("toolbarbutton-1");
+ node.classList.add("chromeclass-toolbar-additional");
+ node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+ node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+ node.setAttribute("removable", "true");
+ node.setAttribute("observes", "Social:PageShareable");
+ node.setAttribute("command", "Social:SharePage");
+
+ let listener = {
+ onWidgetAdded: (aWidgetId) => {
+ if (aWidgetId != this.id)
+ return;
+
+ Services.obs.notifyObservers(null, "social:" + this.id + "-added", null);
+ },
+
+ onWidgetRemoved: aWidgetId => {
+ if (aWidgetId != this.id)
+ return;
+
+ Services.obs.notifyObservers(null, "social:" + this.id + "-removed", null);
+ },
+
+ onWidgetInstanceRemoved: (aWidgetId, aDoc) => {
+ if (aWidgetId != this.id || aDoc != aDocument)
+ return;
+
+ CustomizableUI.removeListener(listener);
+ }
+ };
+ CustomizableUI.addListener(listener);
+
+ return node;
+ }
+ }, {
+ id: "add-ons-button",
+ shortcutId: "key_openAddons",
+ tooltiptext: "add-ons-button.tooltiptext3",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onCommand: function(aEvent) {
+ let win = aEvent.target.ownerGlobal;
+ win.BrowserOpenAddonsMgr();
+ }
+ }, {
+ id: "zoom-controls",
+ type: "custom",
+ tooltiptext: "zoom-controls.tooltiptext2",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onBuild: function(aDocument) {
+ const kPanelId = "PanelUI-popup";
+ let areaType = CustomizableUI.getAreaType(this.currentArea);
+ let inPanel = areaType == CustomizableUI.TYPE_MENU_PANEL;
+ let inToolbar = areaType == CustomizableUI.TYPE_TOOLBAR;
+
+ let buttons = [{
+ id: "zoom-out-button",
+ command: "cmd_fullZoomReduce",
+ label: true,
+ tooltiptext: "tooltiptext2",
+ shortcutId: "key_fullZoomReduce",
+ }, {
+ id: "zoom-reset-button",
+ command: "cmd_fullZoomReset",
+ tooltiptext: "tooltiptext2",
+ shortcutId: "key_fullZoomReset",
+ }, {
+ id: "zoom-in-button",
+ command: "cmd_fullZoomEnlarge",
+ label: true,
+ tooltiptext: "tooltiptext2",
+ shortcutId: "key_fullZoomEnlarge",
+ }];
+
+ let node = aDocument.createElementNS(kNSXUL, "toolbaritem");
+ node.setAttribute("id", "zoom-controls");
+ node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+ node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+ // Set this as an attribute in addition to the property to make sure we can style correctly.
+ node.setAttribute("removable", "true");
+ node.classList.add("chromeclass-toolbar-additional");
+ node.classList.add("toolbaritem-combined-buttons");
+ node.classList.add(kWidePanelItemClass);
+
+ buttons.forEach(function(aButton, aIndex) {
+ if (aIndex != 0)
+ node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
+ let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+ setAttributes(btnNode, aButton);
+ node.appendChild(btnNode);
+ });
+
+ // The middle node is the 'Reset Zoom' button.
+ let zoomResetButton = node.childNodes[2];
+ let window = aDocument.defaultView;
+ function updateZoomResetButton() {
+ let updateDisplay = true;
+ // Label should always show 100% in customize mode, so don't update:
+ if (aDocument.documentElement.hasAttribute("customizing")) {
+ updateDisplay = false;
+ }
+ // XXXgijs in some tests we get called very early, and there's no docShell on the
+ // tabbrowser. This breaks the zoom toolkit code (see bug 897410). Don't let that happen:
+ let zoomFactor = 100;
+ try {
+ zoomFactor = Math.round(window.ZoomManager.zoom * 100);
+ } catch (e) {}
+ zoomResetButton.setAttribute("label", CustomizableUI.getLocalizedProperty(
+ buttons[1], "label", [updateDisplay ? zoomFactor : 100]
+ ));
+ }
+
+ // Register ourselves with the service so we know when the zoom prefs change.
+ Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomChange", false);
+ Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomReset", false);
+ Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:location-change", false);
+
+ if (inPanel) {
+ let panel = aDocument.getElementById(kPanelId);
+ panel.addEventListener("popupshowing", updateZoomResetButton);
+ } else {
+ if (inToolbar) {
+ let container = window.gBrowser.tabContainer;
+ container.addEventListener("TabSelect", updateZoomResetButton);
+ }
+ updateZoomResetButton();
+ }
+ updateCombinedWidgetStyle(node, this.currentArea, true);
+
+ let listener = {
+ onWidgetAdded: function(aWidgetId, aArea, aPosition) {
+ if (aWidgetId != this.id)
+ return;
+
+ updateCombinedWidgetStyle(node, aArea, true);
+ updateZoomResetButton();
+
+ let areaType = CustomizableUI.getAreaType(aArea);
+ if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ let panel = aDocument.getElementById(kPanelId);
+ panel.addEventListener("popupshowing", updateZoomResetButton);
+ } else if (areaType == CustomizableUI.TYPE_TOOLBAR) {
+ let container = window.gBrowser.tabContainer;
+ container.addEventListener("TabSelect", updateZoomResetButton);
+ }
+ }.bind(this),
+
+ onWidgetRemoved: function(aWidgetId, aPrevArea) {
+ if (aWidgetId != this.id)
+ return;
+
+ let areaType = CustomizableUI.getAreaType(aPrevArea);
+ if (areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ let panel = aDocument.getElementById(kPanelId);
+ panel.removeEventListener("popupshowing", updateZoomResetButton);
+ } else if (areaType == CustomizableUI.TYPE_TOOLBAR) {
+ let container = window.gBrowser.tabContainer;
+ container.removeEventListener("TabSelect", updateZoomResetButton);
+ }
+
+ // When a widget is demoted to the palette ('removed'), it's visual
+ // style should change.
+ updateCombinedWidgetStyle(node, null, true);
+ updateZoomResetButton();
+ }.bind(this),
+
+ onWidgetReset: function(aWidgetNode) {
+ if (aWidgetNode != node)
+ return;
+ updateCombinedWidgetStyle(node, this.currentArea, true);
+ updateZoomResetButton();
+ }.bind(this),
+
+ onWidgetUndoMove: function(aWidgetNode) {
+ if (aWidgetNode != node)
+ return;
+ updateCombinedWidgetStyle(node, this.currentArea, true);
+ updateZoomResetButton();
+ }.bind(this),
+
+ onWidgetMoved: function(aWidgetId, aArea) {
+ if (aWidgetId != this.id)
+ return;
+ updateCombinedWidgetStyle(node, aArea, true);
+ updateZoomResetButton();
+ }.bind(this),
+
+ onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
+ if (aWidgetId != this.id || aDoc != aDocument)
+ return;
+
+ CustomizableUI.removeListener(listener);
+ Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomChange");
+ Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomReset");
+ Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:location-change");
+ let panel = aDoc.getElementById(kPanelId);
+ panel.removeEventListener("popupshowing", updateZoomResetButton);
+ let container = aDoc.defaultView.gBrowser.tabContainer;
+ container.removeEventListener("TabSelect", updateZoomResetButton);
+ }.bind(this),
+
+ onCustomizeStart: function(aWindow) {
+ if (aWindow.document == aDocument) {
+ updateZoomResetButton();
+ }
+ },
+
+ onCustomizeEnd: function(aWindow) {
+ if (aWindow.document == aDocument) {
+ updateZoomResetButton();
+ }
+ },
+
+ onWidgetDrag: function(aWidgetId, aArea) {
+ if (aWidgetId != this.id)
+ return;
+ aArea = aArea || this.currentArea;
+ updateCombinedWidgetStyle(node, aArea, true);
+ }.bind(this)
+ };
+ CustomizableUI.addListener(listener);
+
+ return node;
+ }
+ }, {
+ id: "edit-controls",
+ type: "custom",
+ tooltiptext: "edit-controls.tooltiptext2",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onBuild: function(aDocument) {
+ let buttons = [{
+ id: "cut-button",
+ command: "cmd_cut",
+ label: true,
+ tooltiptext: "tooltiptext2",
+ shortcutId: "key_cut",
+ }, {
+ id: "copy-button",
+ command: "cmd_copy",
+ label: true,
+ tooltiptext: "tooltiptext2",
+ shortcutId: "key_copy",
+ }, {
+ id: "paste-button",
+ command: "cmd_paste",
+ label: true,
+ tooltiptext: "tooltiptext2",
+ shortcutId: "key_paste",
+ }];
+
+ let node = aDocument.createElementNS(kNSXUL, "toolbaritem");
+ node.setAttribute("id", "edit-controls");
+ node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+ node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+ // Set this as an attribute in addition to the property to make sure we can style correctly.
+ node.setAttribute("removable", "true");
+ node.classList.add("chromeclass-toolbar-additional");
+ node.classList.add("toolbaritem-combined-buttons");
+ node.classList.add(kWidePanelItemClass);
+
+ buttons.forEach(function(aButton, aIndex) {
+ if (aIndex != 0)
+ node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
+ let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+ setAttributes(btnNode, aButton);
+ node.appendChild(btnNode);
+ });
+
+ updateCombinedWidgetStyle(node, this.currentArea);
+
+ let listener = {
+ onWidgetAdded: function(aWidgetId, aArea, aPosition) {
+ if (aWidgetId != this.id)
+ return;
+ updateCombinedWidgetStyle(node, aArea);
+ }.bind(this),
+
+ onWidgetRemoved: function(aWidgetId, aPrevArea) {
+ if (aWidgetId != this.id)
+ return;
+ // When a widget is demoted to the palette ('removed'), it's visual
+ // style should change.
+ updateCombinedWidgetStyle(node);
+ }.bind(this),
+
+ onWidgetReset: function(aWidgetNode) {
+ if (aWidgetNode != node)
+ return;
+ updateCombinedWidgetStyle(node, this.currentArea);
+ }.bind(this),
+
+ onWidgetUndoMove: function(aWidgetNode) {
+ if (aWidgetNode != node)
+ return;
+ updateCombinedWidgetStyle(node, this.currentArea);
+ }.bind(this),
+
+ onWidgetMoved: function(aWidgetId, aArea) {
+ if (aWidgetId != this.id)
+ return;
+ updateCombinedWidgetStyle(node, aArea);
+ }.bind(this),
+
+ onWidgetInstanceRemoved: function(aWidgetId, aDoc) {
+ if (aWidgetId != this.id || aDoc != aDocument)
+ return;
+ CustomizableUI.removeListener(listener);
+ }.bind(this),
+
+ onWidgetDrag: function(aWidgetId, aArea) {
+ if (aWidgetId != this.id)
+ return;
+ aArea = aArea || this.currentArea;
+ updateCombinedWidgetStyle(node, aArea);
+ }.bind(this)
+ };
+ CustomizableUI.addListener(listener);
+
+ return node;
+ }
+ },
+ {
+ id: "feed-button",
+ type: "view",
+ viewId: "PanelUI-feeds",
+ tooltiptext: "feed-button.tooltiptext2",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onClick: function(aEvent) {
+ let win = aEvent.target.ownerGlobal;
+ let feeds = win.gBrowser.selectedBrowser.feeds;
+
+ // Here, we only care about the case where we have exactly 1 feed and the
+ // user clicked...
+ let isClick = (aEvent.button == 0 || aEvent.button == 1);
+ if (feeds && feeds.length == 1 && isClick) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ win.FeedHandler.subscribeToFeed(feeds[0].href, aEvent);
+ CustomizableUI.hidePanelForNode(aEvent.target);
+ }
+ },
+ onViewShowing: function(aEvent) {
+ let doc = aEvent.target.ownerDocument;
+ let container = doc.getElementById("PanelUI-feeds");
+ let gotView = doc.defaultView.FeedHandler.buildFeedList(container, true);
+
+ // For no feeds or only a single one, don't show the panel.
+ if (!gotView) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ return;
+ }
+ },
+ onCreated: function(node) {
+ let win = node.ownerGlobal;
+ let selectedBrowser = win.gBrowser.selectedBrowser;
+ let feeds = selectedBrowser && selectedBrowser.feeds;
+ if (!feeds || !feeds.length) {
+ node.setAttribute("disabled", "true");
+ }
+ }
+ }, {
+ id: "characterencoding-button",
+ label: "characterencoding-button2.label",
+ type: "view",
+ viewId: "PanelUI-characterEncodingView",
+ tooltiptext: "characterencoding-button2.tooltiptext",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ maybeDisableMenu: function(aDocument) {
+ let window = aDocument.defaultView;
+ return !(window.gBrowser &&
+ window.gBrowser.selectedBrowser.mayEnableCharacterEncodingMenu);
+ },
+ populateList: function(aDocument, aContainerId, aSection) {
+ let containerElem = aDocument.getElementById(aContainerId);
+
+ containerElem.addEventListener("command", this.onCommand, false);
+
+ let list = this.charsetInfo[aSection];
+
+ for (let item of list) {
+ let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+ elem.setAttribute("label", item.label);
+ elem.setAttribute("type", "checkbox");
+ elem.section = aSection;
+ elem.value = item.value;
+ elem.setAttribute("class", "subviewbutton");
+ containerElem.appendChild(elem);
+ }
+ },
+ updateCurrentCharset: function(aDocument) {
+ let currentCharset = aDocument.defaultView.gBrowser.selectedBrowser.characterSet;
+ currentCharset = CharsetMenu.foldCharset(currentCharset);
+
+ let pinnedContainer = aDocument.getElementById("PanelUI-characterEncodingView-pinned");
+ let charsetContainer = aDocument.getElementById("PanelUI-characterEncodingView-charsets");
+ let elements = [...(pinnedContainer.childNodes), ...(charsetContainer.childNodes)];
+
+ this._updateElements(elements, currentCharset);
+ },
+ updateCurrentDetector: function(aDocument) {
+ let detectorContainer = aDocument.getElementById("PanelUI-characterEncodingView-autodetect");
+ let currentDetector;
+ try {
+ currentDetector = Services.prefs.getComplexValue(
+ "intl.charset.detector", Ci.nsIPrefLocalizedString).data;
+ } catch (e) {}
+
+ this._updateElements(detectorContainer.childNodes, currentDetector);
+ },
+ _updateElements: function(aElements, aCurrentItem) {
+ if (!aElements.length) {
+ return;
+ }
+ let disabled = this.maybeDisableMenu(aElements[0].ownerDocument);
+ for (let elem of aElements) {
+ if (disabled) {
+ elem.setAttribute("disabled", "true");
+ } else {
+ elem.removeAttribute("disabled");
+ }
+ if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
+ elem.setAttribute("checked", "true");
+ } else {
+ elem.removeAttribute("checked");
+ }
+ }
+ },
+ onViewShowing: function(aEvent) {
+ let document = aEvent.target.ownerDocument;
+
+ let autoDetectLabelId = "PanelUI-characterEncodingView-autodetect-label";
+ let autoDetectLabel = document.getElementById(autoDetectLabelId);
+ if (!autoDetectLabel.hasAttribute("value")) {
+ let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
+ autoDetectLabel.setAttribute("value", label);
+ this.populateList(document,
+ "PanelUI-characterEncodingView-pinned",
+ "pinnedCharsets");
+ this.populateList(document,
+ "PanelUI-characterEncodingView-charsets",
+ "otherCharsets");
+ this.populateList(document,
+ "PanelUI-characterEncodingView-autodetect",
+ "detectors");
+ }
+ this.updateCurrentDetector(document);
+ this.updateCurrentCharset(document);
+ },
+ onCommand: function(aEvent) {
+ let node = aEvent.target;
+ if (!node.hasAttribute || !node.section) {
+ return;
+ }
+
+ let window = node.ownerGlobal;
+ let section = node.section;
+ let value = node.value;
+
+ // The behavior as implemented here is directly based off of the
+ // `MultiplexHandler()` method in browser.js.
+ if (section != "detectors") {
+ window.BrowserSetForcedCharacterSet(value);
+ } else {
+ // Set the detector pref.
+ try {
+ let str = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ str.data = value;
+ Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
+ } catch (e) {
+ Cu.reportError("Failed to set the intl.charset.detector preference.");
+ }
+ // Prepare a browser page reload with a changed charset.
+ window.BrowserCharsetReload();
+ }
+ },
+ onCreated: function(aNode) {
+ const kPanelId = "PanelUI-popup";
+ let document = aNode.ownerDocument;
+
+ let updateButton = () => {
+ if (this.maybeDisableMenu(document))
+ aNode.setAttribute("disabled", "true");
+ else
+ aNode.removeAttribute("disabled");
+ };
+
+ if (this.currentArea == CustomizableUI.AREA_PANEL) {
+ let panel = document.getElementById(kPanelId);
+ panel.addEventListener("popupshowing", updateButton);
+ }
+
+ let listener = {
+ onWidgetAdded: (aWidgetId, aArea) => {
+ if (aWidgetId != this.id)
+ return;
+ if (aArea == CustomizableUI.AREA_PANEL) {
+ let panel = document.getElementById(kPanelId);
+ panel.addEventListener("popupshowing", updateButton);
+ }
+ },
+ onWidgetRemoved: (aWidgetId, aPrevArea) => {
+ if (aWidgetId != this.id)
+ return;
+ aNode.removeAttribute("disabled");
+ if (aPrevArea == CustomizableUI.AREA_PANEL) {
+ let panel = document.getElementById(kPanelId);
+ panel.removeEventListener("popupshowing", updateButton);
+ }
+ },
+ onWidgetInstanceRemoved: (aWidgetId, aDoc) => {
+ if (aWidgetId != this.id || aDoc != document)
+ return;
+
+ CustomizableUI.removeListener(listener);
+ let panel = aDoc.getElementById(kPanelId);
+ panel.removeEventListener("popupshowing", updateButton);
+ }
+ };
+ CustomizableUI.addListener(listener);
+ if (!this.charsetInfo) {
+ this.charsetInfo = CharsetMenu.getData();
+ }
+ }
+ }, {
+ id: "email-link-button",
+ tooltiptext: "email-link-button.tooltiptext3",
+ onCommand: function(aEvent) {
+ let win = aEvent.view;
+ win.MailIntegration.sendLinkForBrowser(win.gBrowser.selectedBrowser)
+ }
+ }, {
+ id: "containers-panelmenu",
+ type: "view",
+ viewId: "PanelUI-containers",
+ hasObserver: false,
+ onCreated: function(aNode) {
+ let doc = aNode.ownerDocument;
+ let win = doc.defaultView;
+ let items = doc.getElementById("PanelUI-containersItems");
+
+ let onItemCommand = function (aEvent) {
+ let item = aEvent.target;
+ if (item.hasAttribute("usercontextid")) {
+ let userContextId = parseInt(item.getAttribute("usercontextid"));
+ win.openUILinkIn(win.BROWSER_NEW_TAB_URL, "tab", {userContextId});
+ }
+ };
+ items.addEventListener("command", onItemCommand);
+
+ if (PrivateBrowsingUtils.isWindowPrivate(win)) {
+ aNode.setAttribute("disabled", "true");
+ }
+
+ this.updateVisibility(aNode);
+
+ if (!this.hasObserver) {
+ Services.prefs.addObserver("privacy.userContext.enabled", this, true);
+ this.hasObserver = true;
+ }
+ },
+ onViewShowing: function(aEvent) {
+ let doc = aEvent.target.ownerDocument;
+
+ let items = doc.getElementById("PanelUI-containersItems");
+
+ while (items.firstChild) {
+ items.firstChild.remove();
+ }
+
+ let fragment = doc.createDocumentFragment();
+ let bundle = doc.getElementById("bundle_browser");
+
+ ContextualIdentityService.getIdentities().forEach(identity => {
+ let label = ContextualIdentityService.getUserContextLabel(identity.userContextId);
+
+ let item = doc.createElementNS(kNSXUL, "toolbarbutton");
+ item.setAttribute("label", label);
+ item.setAttribute("usercontextid", identity.userContextId);
+ item.setAttribute("class", "subviewbutton");
+ item.setAttribute("data-identity-color", identity.color);
+ item.setAttribute("data-identity-icon", identity.icon);
+
+ fragment.appendChild(item);
+ });
+
+ fragment.appendChild(doc.createElementNS(kNSXUL, "menuseparator"));
+
+ let item = doc.createElementNS(kNSXUL, "toolbarbutton");
+ item.setAttribute("label", bundle.getString("userContext.aboutPage.label"));
+ item.setAttribute("command", "Browser:OpenAboutContainers");
+ item.setAttribute("class", "subviewbutton");
+ fragment.appendChild(item);
+
+ items.appendChild(fragment);
+ },
+
+ updateVisibility(aNode) {
+ aNode.hidden = !Services.prefs.getBoolPref("privacy.userContext.enabled");
+ },
+
+ observe(aSubject, aTopic, aData) {
+ let {instances} = CustomizableUI.getWidget("containers-panelmenu");
+ for (let {node} of instances) {
+ if (node) {
+ this.updateVisibility(node);
+ }
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver
+ ]),
+ }];
+
+let preferencesButton = {
+ id: "preferences-button",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onCommand: function(aEvent) {
+ let win = aEvent.target.ownerGlobal;
+ win.openPreferences();
+ }
+};
+if (AppConstants.platform == "win") {
+ preferencesButton.label = "preferences-button.labelWin";
+ preferencesButton.tooltiptext = "preferences-button.tooltipWin2";
+} else if (AppConstants.platform == "macosx") {
+ preferencesButton.tooltiptext = "preferences-button.tooltiptext.withshortcut";
+ preferencesButton.shortcutId = "key_preferencesCmdMac";
+} else {
+ preferencesButton.tooltiptext = "preferences-button.tooltiptext2";
+}
+CustomizableWidgets.push(preferencesButton);
+
+if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
+ CustomizableWidgets.push({
+ id: "panic-button",
+ type: "view",
+ viewId: "PanelUI-panicView",
+ _sanitizer: null,
+ _ensureSanitizer: function() {
+ if (!this.sanitizer) {
+ let scope = {};
+ Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js",
+ scope);
+ this._Sanitizer = scope.Sanitizer;
+ this._sanitizer = new scope.Sanitizer();
+ this._sanitizer.ignoreTimespan = false;
+ }
+ },
+ _getSanitizeRange: function(aDocument) {
+ let group = aDocument.getElementById("PanelUI-panic-timeSpan");
+ return this._Sanitizer.getClearRange(+group.value);
+ },
+ forgetButtonCalled: function(aEvent) {
+ let doc = aEvent.target.ownerDocument;
+ this._ensureSanitizer();
+ this._sanitizer.range = this._getSanitizeRange(doc);
+ let group = doc.getElementById("PanelUI-panic-timeSpan");
+ BrowserUITelemetry.countPanicEvent(group.selectedItem.id);
+ group.selectedItem = doc.getElementById("PanelUI-panic-5min");
+ let itemsToClear = [
+ "cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"
+ ];
+ let newWindowPrivateState = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView) ?
+ "private" : "non-private";
+ this._sanitizer.items.openWindows.privateStateForNewWindow = newWindowPrivateState;
+ let promise = this._sanitizer.sanitize(itemsToClear);
+ promise.then(function() {
+ let otherWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (otherWindow.closed) {
+ Cu.reportError("Got a closed window!");
+ }
+ if (otherWindow.PanicButtonNotifier) {
+ otherWindow.PanicButtonNotifier.notify();
+ } else {
+ otherWindow.PanicButtonNotifierShouldNotify = true;
+ }
+ });
+ },
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "command":
+ this.forgetButtonCalled(aEvent);
+ break;
+ }
+ },
+ onViewShowing: function(aEvent) {
+ let forgetButton = aEvent.target.querySelector("#PanelUI-panic-view-button");
+ forgetButton.addEventListener("command", this);
+ },
+ onViewHiding: function(aEvent) {
+ let forgetButton = aEvent.target.querySelector("#PanelUI-panic-view-button");
+ forgetButton.removeEventListener("command", this);
+ },
+ });
+}
+
+if (AppConstants.E10S_TESTING_ONLY) {
+ if (Services.appinfo.browserTabsRemoteAutostart) {
+ CustomizableWidgets.push({
+ id: "e10s-button",
+ defaultArea: CustomizableUI.AREA_PANEL,
+ onBuild: function(aDocument) {
+ let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+ node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+ node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+ },
+ onCommand: function(aEvent) {
+ let win = aEvent.view;
+ win.OpenBrowserWindow({remote: false});
+ },
+ });
+ }
+}
diff --git a/browser/components/customizableui/CustomizeMode.jsm b/browser/components/customizableui/CustomizeMode.jsm
new file mode 100644
index 000000000..49868cdbd
--- /dev/null
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -0,0 +1,2341 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["CustomizeMode"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const kPrefCustomizationDebug = "browser.uiCustomization.debug";
+const kPrefCustomizationAnimation = "browser.uiCustomization.disableAnimation";
+const kPaletteId = "customization-palette";
+const kDragDataTypePrefix = "text/toolbarwrapper-id/";
+const kPlaceholderClass = "panel-customization-placeholder";
+const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
+const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
+const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
+const kMaxTransitionDurationMs = 2000;
+
+const kPanelItemContextMenu = "customizationPanelItemContextMenu";
+const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/CustomizableUI.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
+ "resource:///modules/DragPositionManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
+ "resource:///modules/BrowserUITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
+ "resource://gre/modules/LightweightThemeManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+
+let gDebug;
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let scope = {};
+ Cu.import("resource://gre/modules/Console.jsm", scope);
+ try {
+ gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug);
+ } catch (ex) {}
+ let consoleOptions = {
+ maxLogLevel: gDebug ? "all" : "log",
+ prefix: "CustomizeMode",
+ };
+ return new scope.ConsoleAPI(consoleOptions);
+});
+
+var gDisableAnimation = null;
+
+var gDraggingInToolbars;
+
+var gTab;
+
+function closeGlobalTab() {
+ let win = gTab.ownerGlobal;
+ if (win.gBrowser.browsers.length == 1) {
+ win.BrowserOpenTab();
+ }
+ win.gBrowser.removeTab(gTab);
+ gTab = null;
+}
+
+function unregisterGlobalTab() {
+ gTab.removeEventListener("TabClose", unregisterGlobalTab);
+ gTab.ownerGlobal.removeEventListener("unload", unregisterGlobalTab);
+ gTab.removeAttribute("customizemode");
+ gTab = null;
+}
+
+function CustomizeMode(aWindow) {
+ if (gDisableAnimation === null) {
+ gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL &&
+ Services.prefs.getBoolPref(kPrefCustomizationAnimation);
+ }
+ this.window = aWindow;
+ this.document = aWindow.document;
+ this.browser = aWindow.gBrowser;
+ this.areas = new Set();
+
+ // There are two palettes - there's the palette that can be overlayed with
+ // toolbar items in browser.xul. This is invisible, and never seen by the
+ // user. Then there's the visible palette, which gets populated and displayed
+ // to the user when in customizing mode.
+ this.visiblePalette = this.document.getElementById(kPaletteId);
+ this.paletteEmptyNotice = this.document.getElementById("customization-empty");
+ this.tipPanel = this.document.getElementById("customization-tipPanel");
+ if (Services.prefs.getCharPref("general.skins.selectedSkin") != "classic/1.0") {
+ let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
+ lwthemeButton.setAttribute("hidden", "true");
+ }
+ if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
+ this._updateTitlebarButton();
+ Services.prefs.addObserver(kDrawInTitlebarPref, this, false);
+ }
+ this.window.addEventListener("unload", this);
+}
+
+CustomizeMode.prototype = {
+ _changed: false,
+ _transitioning: false,
+ window: null,
+ document: null,
+ // areas is used to cache the customizable areas when in customization mode.
+ areas: null,
+ // When in customizing mode, we swap out the reference to the invisible
+ // palette in gNavToolbox.palette for our visiblePalette. This way, for the
+ // customizing browser window, when widgets are removed from customizable
+ // areas and added to the palette, they're added to the visible palette.
+ // _stowedPalette is a reference to the old invisible palette so we can
+ // restore gNavToolbox.palette to its original state after exiting
+ // customization mode.
+ _stowedPalette: null,
+ _dragOverItem: null,
+ _customizing: false,
+ _skipSourceNodeCheck: null,
+ _mainViewContext: null,
+
+ get panelUIContents() {
+ return this.document.getElementById("PanelUI-contents");
+ },
+
+ get _handler() {
+ return this.window.CustomizationHandler;
+ },
+
+ uninit: function() {
+ if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
+ Services.prefs.removeObserver(kDrawInTitlebarPref, this);
+ }
+ },
+
+ toggle: function() {
+ if (this._handler.isEnteringCustomizeMode || this._handler.isExitingCustomizeMode) {
+ this._wantToBeInCustomizeMode = !this._wantToBeInCustomizeMode;
+ return;
+ }
+ if (this._customizing) {
+ this.exit();
+ } else {
+ this.enter();
+ }
+ },
+
+ _updateLWThemeButtonIcon: function() {
+ let lwthemeButton = this.document.getElementById("customization-lwtheme-button");
+ let lwthemeIcon = this.document.getAnonymousElementByAttribute(lwthemeButton,
+ "class", "button-icon");
+ lwthemeIcon.style.backgroundImage = LightweightThemeManager.currentTheme ?
+ "url(" + LightweightThemeManager.currentTheme.iconURL + ")" : "";
+ },
+
+ setTab: function(aTab) {
+ if (gTab == aTab) {
+ return;
+ }
+
+ if (gTab) {
+ closeGlobalTab();
+ }
+
+ gTab = aTab;
+
+ gTab.setAttribute("customizemode", "true");
+ SessionStore.persistTabAttribute("customizemode");
+
+ gTab.linkedBrowser.stop();
+
+ let win = gTab.ownerGlobal;
+
+ win.gBrowser.setTabTitle(gTab);
+ win.gBrowser.setIcon(gTab,
+ "chrome://browser/skin/customizableui/customizeFavicon.ico");
+
+ gTab.addEventListener("TabClose", unregisterGlobalTab);
+ win.addEventListener("unload", unregisterGlobalTab);
+
+ if (gTab.selected) {
+ win.gCustomizeMode.enter();
+ }
+ },
+
+ enter: function() {
+ this._wantToBeInCustomizeMode = true;
+
+ if (this._customizing || this._handler.isEnteringCustomizeMode) {
+ return;
+ }
+
+ // Exiting; want to re-enter once we've done that.
+ if (this._handler.isExitingCustomizeMode) {
+ log.debug("Attempted to enter while we're in the middle of exiting. " +
+ "We'll exit after we've entered");
+ return;
+ }
+
+ if (!gTab) {
+ this.setTab(this.browser.loadOneTab("about:blank",
+ { inBackground: false,
+ forceNotRemote: true,
+ skipAnimation: true }));
+ return;
+ }
+ if (!gTab.selected) {
+ // This will force another .enter() to be called via the
+ // onlocationchange handler of the tabbrowser, so we return early.
+ gTab.ownerGlobal.gBrowser.selectedTab = gTab;
+ return;
+ }
+ gTab.ownerGlobal.focus();
+ if (gTab.ownerDocument != this.document) {
+ return;
+ }
+
+ let window = this.window;
+ let document = this.document;
+
+ this._handler.isEnteringCustomizeMode = true;
+
+ // Always disable the reset button at the start of customize mode, it'll be re-enabled
+ // if necessary when we finish entering:
+ let resetButton = this.document.getElementById("customization-reset-button");
+ resetButton.setAttribute("disabled", "true");
+
+ Task.spawn(function*() {
+ // We shouldn't start customize mode until after browser-delayed-startup has finished:
+ if (!this.window.gBrowserInit.delayedStartupFinished) {
+ yield new Promise(resolve => {
+ let delayedStartupObserver = aSubject => {
+ if (aSubject == this.window) {
+ Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
+ resolve();
+ }
+ };
+
+ Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
+ });
+ }
+
+ let toolbarVisibilityBtn = document.getElementById(kToolbarVisibilityBtn);
+ let togglableToolbars = window.getTogglableToolbars();
+ if (togglableToolbars.length == 0) {
+ toolbarVisibilityBtn.setAttribute("hidden", "true");
+ } else {
+ toolbarVisibilityBtn.removeAttribute("hidden");
+ }
+
+ this.updateLWTStyling();
+
+ CustomizableUI.dispatchToolboxEvent("beforecustomization", {}, window);
+ CustomizableUI.notifyStartCustomizing(this.window);
+
+ // Add a keypress listener to the document so that we can quickly exit
+ // customization mode when pressing ESC.
+ document.addEventListener("keypress", this);
+
+ // Same goes for the menu button - if we're customizing, a click on the
+ // menu button means a quick exit from customization mode.
+ window.PanelUI.hide();
+ window.PanelUI.menuButton.addEventListener("command", this);
+ window.PanelUI.menuButton.open = true;
+ window.PanelUI.beginBatchUpdate();
+
+ // The menu panel is lazy, and registers itself when the popup shows. We
+ // need to force the menu panel to register itself, or else customization
+ // is really not going to work. We pass "true" to ensureReady to
+ // indicate that we're handling calling startBatchUpdate and
+ // endBatchUpdate.
+ if (!window.PanelUI.isReady) {
+ yield window.PanelUI.ensureReady(true);
+ }
+
+ // Hide the palette before starting the transition for increased perf.
+ this.visiblePalette.hidden = true;
+ this.visiblePalette.removeAttribute("showing");
+
+ // Disable the button-text fade-out mask
+ // during the transition for increased perf.
+ let panelContents = window.PanelUI.contents;
+ panelContents.setAttribute("customize-transitioning", "true");
+
+ // Move the mainView in the panel to the holder so that we can see it
+ // while customizing.
+ let mainView = window.PanelUI.mainView;
+ let panelHolder = document.getElementById("customization-panelHolder");
+ panelHolder.appendChild(mainView);
+
+ let customizeButton = document.getElementById("PanelUI-customize");
+ customizeButton.setAttribute("enterLabel", customizeButton.getAttribute("label"));
+ customizeButton.setAttribute("label", customizeButton.getAttribute("exitLabel"));
+ customizeButton.setAttribute("enterTooltiptext", customizeButton.getAttribute("tooltiptext"));
+ customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("exitTooltiptext"));
+
+ this._transitioning = true;
+
+ let customizer = document.getElementById("customization-container");
+ customizer.parentNode.selectedPanel = customizer;
+ customizer.hidden = false;
+
+ this._wrapToolbarItemSync(CustomizableUI.AREA_TABSTRIP);
+
+ let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true]):not([collapsed=true])");
+ for (let toolbar of customizableToolbars)
+ toolbar.setAttribute("customizing", true);
+
+ yield this._doTransition(true);
+
+ Services.obs.addObserver(this, "lightweight-theme-window-updated", false);
+
+ // Let everybody in this window know that we're about to customize.
+ CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window);
+
+ this._mainViewContext = mainView.getAttribute("context");
+ if (this._mainViewContext) {
+ mainView.removeAttribute("context");
+ }
+
+ this._showPanelCustomizationPlaceholders();
+
+ yield this._wrapToolbarItems();
+ this.populatePalette();
+
+ this._addDragHandlers(this.visiblePalette);
+
+ window.gNavToolbox.addEventListener("toolbarvisibilitychange", this);
+
+ document.getElementById("PanelUI-help").setAttribute("disabled", true);
+ document.getElementById("PanelUI-quit").setAttribute("disabled", true);
+
+ this._updateResetButton();
+ this._updateUndoResetButton();
+
+ this._skipSourceNodeCheck = Services.prefs.getPrefType(kSkipSourceNodePref) == Ci.nsIPrefBranch.PREF_BOOL &&
+ Services.prefs.getBoolPref(kSkipSourceNodePref);
+
+ CustomizableUI.addListener(this);
+ window.PanelUI.endBatchUpdate();
+ this._customizing = true;
+ this._transitioning = false;
+
+ // Show the palette now that the transition has finished.
+ this.visiblePalette.hidden = false;
+ window.setTimeout(() => {
+ // Force layout reflow to ensure the animation runs,
+ // and make it async so it doesn't affect the timing.
+ this.visiblePalette.clientTop;
+ this.visiblePalette.setAttribute("showing", "true");
+ }, 0);
+ this._updateEmptyPaletteNotice();
+
+ this._updateLWThemeButtonIcon();
+ this.maybeShowTip(panelHolder);
+
+ this._handler.isEnteringCustomizeMode = false;
+ panelContents.removeAttribute("customize-transitioning");
+
+ CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
+ this._enableOutlinesTimeout = window.setTimeout(() => {
+ this.document.getElementById("nav-bar").setAttribute("showoutline", "true");
+ this.panelUIContents.setAttribute("showoutline", "true");
+ delete this._enableOutlinesTimeout;
+ }, 0);
+
+ if (!this._wantToBeInCustomizeMode) {
+ this.exit();
+ }
+ }.bind(this)).then(null, function(e) {
+ log.error("Error entering customize mode", e);
+ // We should ensure this has been called, and calling it again doesn't hurt:
+ window.PanelUI.endBatchUpdate();
+ this._handler.isEnteringCustomizeMode = false;
+ // Exit customize mode to ensure proper clean-up when entering failed.
+ this.exit();
+ }.bind(this));
+ },
+
+ exit: function() {
+ this._wantToBeInCustomizeMode = false;
+
+ if (!this._customizing || this._handler.isExitingCustomizeMode) {
+ return;
+ }
+
+ // Entering; want to exit once we've done that.
+ if (this._handler.isEnteringCustomizeMode) {
+ log.debug("Attempted to exit while we're in the middle of entering. " +
+ "We'll exit after we've entered");
+ return;
+ }
+
+ if (this.resetting) {
+ log.debug("Attempted to exit while we're resetting. " +
+ "We'll exit after resetting has finished.");
+ return;
+ }
+
+ this.hideTip();
+
+ this._handler.isExitingCustomizeMode = true;
+
+ if (this._enableOutlinesTimeout) {
+ this.window.clearTimeout(this._enableOutlinesTimeout);
+ } else {
+ this.document.getElementById("nav-bar").removeAttribute("showoutline");
+ this.panelUIContents.removeAttribute("showoutline");
+ }
+
+ this._removeExtraToolbarsIfEmpty();
+
+ CustomizableUI.removeListener(this);
+
+ this.document.removeEventListener("keypress", this);
+ this.window.PanelUI.menuButton.removeEventListener("command", this);
+ this.window.PanelUI.menuButton.open = false;
+
+ this.window.PanelUI.beginBatchUpdate();
+
+ this._removePanelCustomizationPlaceholders();
+
+ let window = this.window;
+ let document = this.document;
+
+ // Hide the palette before starting the transition for increased perf.
+ this.visiblePalette.hidden = true;
+ this.visiblePalette.removeAttribute("showing");
+ this.paletteEmptyNotice.hidden = true;
+
+ // Disable the button-text fade-out mask
+ // during the transition for increased perf.
+ let panelContents = window.PanelUI.contents;
+ panelContents.setAttribute("customize-transitioning", "true");
+
+ // Disable the reset and undo reset buttons while transitioning:
+ let resetButton = this.document.getElementById("customization-reset-button");
+ let undoResetButton = this.document.getElementById("customization-undo-reset-button");
+ undoResetButton.hidden = resetButton.disabled = true;
+
+ this._transitioning = true;
+
+ Task.spawn(function*() {
+ yield this.depopulatePalette();
+
+ yield this._doTransition(false);
+ this.removeLWTStyling();
+
+ Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
+
+ if (this.browser.selectedTab == gTab) {
+ if (gTab.linkedBrowser.currentURI.spec == "about:blank") {
+ closeGlobalTab();
+ } else {
+ unregisterGlobalTab();
+ }
+ }
+ let browser = document.getElementById("browser");
+ browser.parentNode.selectedPanel = browser;
+ let customizer = document.getElementById("customization-container");
+ customizer.hidden = true;
+
+ window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this);
+
+ DragPositionManager.stop();
+ this._removeDragHandlers(this.visiblePalette);
+
+ yield this._unwrapToolbarItems();
+
+ if (this._changed) {
+ // XXXmconley: At first, it seems strange to also persist the old way with
+ // currentset - but this might actually be useful for switching
+ // to old builds. We might want to keep this around for a little
+ // bit.
+ this.persistCurrentSets();
+ }
+
+ // And drop all area references.
+ this.areas.clear();
+
+ // Let everybody in this window know that we're starting to
+ // exit customization mode.
+ CustomizableUI.dispatchToolboxEvent("customizationending", {}, window);
+
+ window.PanelUI.setMainView(window.PanelUI.mainView);
+ window.PanelUI.menuButton.disabled = false;
+
+ let customizeButton = document.getElementById("PanelUI-customize");
+ customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label"));
+ customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel"));
+ customizeButton.setAttribute("exitTooltiptext", customizeButton.getAttribute("tooltiptext"));
+ customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("enterTooltiptext"));
+
+ // We have to use setAttribute/removeAttribute here instead of the
+ // property because the XBL property will be set later, and right
+ // now we'd be setting an expando, which breaks the XBL property.
+ document.getElementById("PanelUI-help").removeAttribute("disabled");
+ document.getElementById("PanelUI-quit").removeAttribute("disabled");
+
+ panelContents.removeAttribute("customize-transitioning");
+
+ // We need to set this._customizing to false before removing the tab
+ // or the TabSelect event handler will think that we are exiting
+ // customization mode for a second time.
+ this._customizing = false;
+
+ let mainView = window.PanelUI.mainView;
+ if (this._mainViewContext) {
+ mainView.setAttribute("context", this._mainViewContext);
+ }
+
+ let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])");
+ for (let toolbar of customizableToolbars)
+ toolbar.removeAttribute("customizing");
+
+ this.window.PanelUI.endBatchUpdate();
+ delete this._lastLightweightTheme;
+ this._changed = false;
+ this._transitioning = false;
+ this._handler.isExitingCustomizeMode = false;
+ CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
+ CustomizableUI.notifyEndCustomizing(window);
+
+ if (this._wantToBeInCustomizeMode) {
+ this.enter();
+ }
+ }.bind(this)).then(null, function(e) {
+ log.error("Error exiting customize mode", e);
+ // We should ensure this has been called, and calling it again doesn't hurt:
+ window.PanelUI.endBatchUpdate();
+ this._handler.isExitingCustomizeMode = false;
+ }.bind(this));
+ },
+
+ /**
+ * The customize mode transition has 4 phases when entering:
+ * 1) Pre-customization mode
+ * This is the starting phase of the browser.
+ * 2) LWT swapping
+ * This is where we swap some of the lightweight theme styles in order
+ * to make them work in customize mode. We set/unset a customization-
+ * lwtheme attribute iff we're using a lightweight theme.
+ * 3) customize-entering
+ * This phase is a transition, optimized for smoothness.
+ * 4) customize-entered
+ * After the transition completes, this phase draws all of the
+ * expensive detail that isn't necessary during the second phase.
+ *
+ * Exiting customization mode has a similar set of phases, but in reverse
+ * order - customize-entered, customize-exiting, remove LWT swapping,
+ * pre-customization mode.
+ *
+ * When in the customize-entering, customize-entered, or customize-exiting
+ * phases, there is a "customizing" attribute set on the main-window to simplify
+ * excluding certain styles while in any phase of customize mode.
+ */
+ _doTransition: function(aEntering) {
+ let deck = this.document.getElementById("content-deck");
+ let customizeTransitionEndPromise = new Promise(resolve => {
+ let customizeTransitionEnd = (aEvent) => {
+ if (aEvent != "timedout" &&
+ (aEvent.originalTarget != deck || aEvent.propertyName != "margin-left")) {
+ return;
+ }
+ this.window.clearTimeout(catchAllTimeout);
+ // We request an animation frame to do the final stage of the transition
+ // to improve perceived performance. (bug 962677)
+ this.window.requestAnimationFrame(() => {
+ deck.removeEventListener("transitionend", customizeTransitionEnd);
+
+ if (!aEntering) {
+ this.document.documentElement.removeAttribute("customize-exiting");
+ this.document.documentElement.removeAttribute("customizing");
+ } else {
+ this.document.documentElement.setAttribute("customize-entered", true);
+ this.document.documentElement.removeAttribute("customize-entering");
+ }
+ CustomizableUI.dispatchToolboxEvent("customization-transitionend", aEntering, this.window);
+
+ resolve();
+ });
+ };
+ deck.addEventListener("transitionend", customizeTransitionEnd);
+ let catchAll = () => customizeTransitionEnd("timedout");
+ let catchAllTimeout = this.window.setTimeout(catchAll, kMaxTransitionDurationMs);
+ });
+
+ if (gDisableAnimation) {
+ this.document.getElementById("tab-view-deck").setAttribute("fastcustomizeanimation", true);
+ }
+
+ if (aEntering) {
+ this.document.documentElement.setAttribute("customizing", true);
+ this.document.documentElement.setAttribute("customize-entering", true);
+ } else {
+ this.document.documentElement.setAttribute("customize-exiting", true);
+ this.document.documentElement.removeAttribute("customize-entered");
+ }
+
+ return customizeTransitionEndPromise;
+ },
+
+ updateLWTStyling: function(aData) {
+ let docElement = this.document.documentElement;
+ if (!aData) {
+ let lwt = docElement._lightweightTheme;
+ aData = lwt.getData();
+ }
+ let headerURL = aData && aData.headerURL;
+ if (!headerURL) {
+ this.removeLWTStyling();
+ return;
+ }
+
+ let deck = this.document.getElementById("tab-view-deck");
+ let headerImageRef = this._getHeaderImageRef(aData);
+ docElement.setAttribute("customization-lwtheme", "true");
+
+ let toolboxRect = this.window.gNavToolbox.getBoundingClientRect();
+ let height = toolboxRect.bottom;
+
+ if (AppConstants.platform == "macosx") {
+ let drawingInTitlebar = !docElement.hasAttribute("drawtitle");
+ let titlebar = this.document.getElementById("titlebar");
+ if (drawingInTitlebar) {
+ titlebar.style.backgroundImage = headerImageRef;
+ } else {
+ titlebar.style.removeProperty("background-image");
+ }
+ }
+
+ let limitedBG = "-moz-image-rect(" + headerImageRef + ", 0, 100%, " +
+ height + ", 0)";
+
+ let ridgeStart = height - 1;
+ let ridgeCenter = (ridgeStart + 1) + "px";
+ let ridgeEnd = (ridgeStart + 2) + "px";
+ ridgeStart = ridgeStart + "px";
+
+ let ridge = "linear-gradient(to bottom, " +
+ "transparent " + ridgeStart +
+ ", rgba(0,0,0,0.25) " + ridgeStart +
+ ", rgba(0,0,0,0.25) " + ridgeCenter +
+ ", rgba(255,255,255,0.5) " + ridgeCenter +
+ ", rgba(255,255,255,0.5) " + ridgeEnd + ", " +
+ "transparent " + ridgeEnd + ")";
+ deck.style.backgroundImage = ridge + ", " + limitedBG;
+
+ /* Remove the background styles from the <window> so we can style it instead. */
+ docElement.style.removeProperty("background-image");
+ docElement.style.removeProperty("background-color");
+ },
+
+ removeLWTStyling: function() {
+ let affectedNodes = AppConstants.platform == "macosx" ?
+ ["tab-view-deck", "titlebar"] :
+ ["tab-view-deck"];
+ for (let id of affectedNodes) {
+ let node = this.document.getElementById(id);
+ node.style.removeProperty("background-image");
+ }
+ let docElement = this.document.documentElement;
+ docElement.removeAttribute("customization-lwtheme");
+ let data = docElement._lightweightTheme.getData();
+ if (data && data.headerURL) {
+ docElement.style.backgroundImage = this._getHeaderImageRef(data);
+ docElement.style.backgroundColor = data.accentcolor || "white";
+ }
+ },
+
+ _getHeaderImageRef: function(aData) {
+ return "url(\"" + aData.headerURL.replace(/"/g, '\\"') + "\")";
+ },
+
+ maybeShowTip: function(aAnchor) {
+ let shown = false;
+ const kShownPref = "browser.customizemode.tip0.shown";
+ try {
+ shown = Services.prefs.getBoolPref(kShownPref);
+ } catch (ex) {}
+ if (shown)
+ return;
+
+ let anchorNode = aAnchor || this.document.getElementById("customization-panelHolder");
+ let messageNode = this.tipPanel.querySelector(".customization-tipPanel-contentMessage");
+ if (!messageNode.childElementCount) {
+ // Put the tip contents in the popup.
+ let bundle = this.document.getElementById("bundle_browser");
+ const kLabelClass = "customization-tipPanel-link";
+ messageNode.innerHTML = bundle.getFormattedString("customizeTips.tip0", [
+ "<label class=\"customization-tipPanel-em\" value=\"" +
+ bundle.getString("customizeTips.tip0.hint") + "\"/>",
+ this.document.getElementById("bundle_brand").getString("brandShortName"),
+ "<label class=\"" + kLabelClass + " text-link\" value=\"" +
+ bundle.getString("customizeTips.tip0.learnMore") + "\"/>"
+ ]);
+
+ messageNode.querySelector("." + kLabelClass).addEventListener("click", () => {
+ let url = Services.urlFormatter.formatURLPref("browser.customizemode.tip0.learnMoreUrl");
+ let browser = this.browser;
+ browser.selectedTab = browser.addTab(url);
+ this.hideTip();
+ });
+ }
+
+ this.tipPanel.hidden = false;
+ this.tipPanel.openPopup(anchorNode);
+ Services.prefs.setBoolPref(kShownPref, true);
+ },
+
+ hideTip: function() {
+ this.tipPanel.hidePopup();
+ },
+
+ _getCustomizableChildForNode: function(aNode) {
+ // NB: adjusted from _getCustomizableParent to keep that method fast
+ // (it's used during drags), and avoid multiple DOM loops
+ let areas = CustomizableUI.areas;
+ // Caching this length is important because otherwise we'll also iterate
+ // over items we add to the end from within the loop.
+ let numberOfAreas = areas.length;
+ for (let i = 0; i < numberOfAreas; i++) {
+ let area = areas[i];
+ let areaNode = aNode.ownerDocument.getElementById(area);
+ let customizationTarget = areaNode && areaNode.customizationTarget;
+ if (customizationTarget && customizationTarget != areaNode) {
+ areas.push(customizationTarget.id);
+ }
+ let overflowTarget = areaNode && areaNode.getAttribute("overflowtarget");
+ if (overflowTarget) {
+ areas.push(overflowTarget);
+ }
+ }
+ areas.push(kPaletteId);
+
+ while (aNode && aNode.parentNode) {
+ let parent = aNode.parentNode;
+ if (areas.indexOf(parent.id) != -1) {
+ return aNode;
+ }
+ aNode = parent;
+ }
+ return null;
+ },
+
+ addToToolbar: function(aNode) {
+ aNode = this._getCustomizableChildForNode(aNode);
+ if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
+ aNode = aNode.firstChild;
+ }
+ CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_NAVBAR);
+ if (!this._customizing) {
+ CustomizableUI.dispatchToolboxEvent("customizationchange");
+ }
+ },
+
+ addToPanel: function(aNode) {
+ aNode = this._getCustomizableChildForNode(aNode);
+ if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
+ aNode = aNode.firstChild;
+ }
+ CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_PANEL);
+ if (!this._customizing) {
+ CustomizableUI.dispatchToolboxEvent("customizationchange");
+ }
+ },
+
+ removeFromArea: function(aNode) {
+ aNode = this._getCustomizableChildForNode(aNode);
+ if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
+ aNode = aNode.firstChild;
+ }
+ CustomizableUI.removeWidgetFromArea(aNode.id);
+ if (!this._customizing) {
+ CustomizableUI.dispatchToolboxEvent("customizationchange");
+ }
+ },
+
+ populatePalette: function() {
+ let fragment = this.document.createDocumentFragment();
+ let toolboxPalette = this.window.gNavToolbox.palette;
+
+ try {
+ let unusedWidgets = CustomizableUI.getUnusedWidgets(toolboxPalette);
+ for (let widget of unusedWidgets) {
+ let paletteItem = this.makePaletteItem(widget, "palette");
+ if (!paletteItem) {
+ continue;
+ }
+ fragment.appendChild(paletteItem);
+ }
+
+ this.visiblePalette.appendChild(fragment);
+ this._stowedPalette = this.window.gNavToolbox.palette;
+ this.window.gNavToolbox.palette = this.visiblePalette;
+ } catch (ex) {
+ log.error(ex);
+ }
+ },
+
+ // XXXunf Maybe this should use -moz-element instead of wrapping the node?
+ // Would ensure no weird interactions/event handling from original node,
+ // and makes it possible to put this in a lazy-loaded iframe/real tab
+ // while still getting rid of the need for overlays.
+ makePaletteItem: function(aWidget, aPlace) {
+ let widgetNode = aWidget.forWindow(this.window).node;
+ if (!widgetNode) {
+ log.error("Widget with id " + aWidget.id + " does not return a valid node");
+ return null;
+ }
+ // Do not build a palette item for hidden widgets; there's not much to show.
+ if (widgetNode.hidden) {
+ return null;
+ }
+
+ let wrapper = this.createOrUpdateWrapper(widgetNode, aPlace);
+ wrapper.appendChild(widgetNode);
+ return wrapper;
+ },
+
+ depopulatePalette: function() {
+ return Task.spawn(function*() {
+ this.visiblePalette.hidden = true;
+ let paletteChild = this.visiblePalette.firstChild;
+ let nextChild;
+ while (paletteChild) {
+ nextChild = paletteChild.nextElementSibling;
+ let provider = CustomizableUI.getWidget(paletteChild.id).provider;
+ if (provider == CustomizableUI.PROVIDER_XUL) {
+ let unwrappedPaletteItem =
+ yield this.deferredUnwrapToolbarItem(paletteChild);
+ this._stowedPalette.appendChild(unwrappedPaletteItem);
+ } else if (provider == CustomizableUI.PROVIDER_API) {
+ // XXXunf Currently this doesn't destroy the (now unused) node. It would
+ // be good to do so, but we need to keep strong refs to it in
+ // CustomizableUI (can't iterate of WeakMaps), and there's the
+ // question of what behavior wrappers should have if consumers
+ // keep hold of them.
+ // widget.destroyInstance(widgetNode);
+ } else if (provider == CustomizableUI.PROVIDER_SPECIAL) {
+ this.visiblePalette.removeChild(paletteChild);
+ }
+
+ paletteChild = nextChild;
+ }
+ this.visiblePalette.hidden = false;
+ this.window.gNavToolbox.palette = this._stowedPalette;
+ }.bind(this)).then(null, log.error);
+ },
+
+ isCustomizableItem: function(aNode) {
+ return aNode.localName == "toolbarbutton" ||
+ aNode.localName == "toolbaritem" ||
+ aNode.localName == "toolbarseparator" ||
+ aNode.localName == "toolbarspring" ||
+ aNode.localName == "toolbarspacer";
+ },
+
+ isWrappedToolbarItem: function(aNode) {
+ return aNode.localName == "toolbarpaletteitem";
+ },
+
+ deferredWrapToolbarItem: function(aNode, aPlace) {
+ return new Promise(resolve => {
+ dispatchFunction(() => {
+ let wrapper = this.wrapToolbarItem(aNode, aPlace);
+ resolve(wrapper);
+ });
+ });
+ },
+
+ wrapToolbarItem: function(aNode, aPlace) {
+ if (!this.isCustomizableItem(aNode)) {
+ return aNode;
+ }
+ let wrapper = this.createOrUpdateWrapper(aNode, aPlace);
+
+ // It's possible that this toolbar node is "mid-flight" and doesn't have
+ // a parent, in which case we skip replacing it. This can happen if a
+ // toolbar item has been dragged into the palette. In that case, we tell
+ // CustomizableUI to remove the widget from its area before putting the
+ // widget in the palette - so the node will have no parent.
+ if (aNode.parentNode) {
+ aNode = aNode.parentNode.replaceChild(wrapper, aNode);
+ }
+ wrapper.appendChild(aNode);
+ return wrapper;
+ },
+
+ createOrUpdateWrapper: function(aNode, aPlace, aIsUpdate) {
+ let wrapper;
+ if (aIsUpdate && aNode.parentNode && aNode.parentNode.localName == "toolbarpaletteitem") {
+ wrapper = aNode.parentNode;
+ aPlace = wrapper.getAttribute("place");
+ } else {
+ wrapper = this.document.createElement("toolbarpaletteitem");
+ // "place" is used by toolkit to add the toolbarpaletteitem-palette
+ // binding to a toolbarpaletteitem, which gives it a label node for when
+ // it's sitting in the palette.
+ wrapper.setAttribute("place", aPlace);
+ }
+
+
+ // Ensure the wrapped item doesn't look like it's in any special state, and
+ // can't be interactved with when in the customization palette.
+ if (aNode.hasAttribute("command")) {
+ wrapper.setAttribute("itemcommand", aNode.getAttribute("command"));
+ aNode.removeAttribute("command");
+ }
+
+ if (aNode.hasAttribute("observes")) {
+ wrapper.setAttribute("itemobserves", aNode.getAttribute("observes"));
+ aNode.removeAttribute("observes");
+ }
+
+ if (aNode.getAttribute("checked") == "true") {
+ wrapper.setAttribute("itemchecked", "true");
+ aNode.removeAttribute("checked");
+ }
+
+ if (aNode.hasAttribute("id")) {
+ wrapper.setAttribute("id", "wrapper-" + aNode.getAttribute("id"));
+ }
+
+ if (aNode.hasAttribute("label")) {
+ wrapper.setAttribute("title", aNode.getAttribute("label"));
+ wrapper.setAttribute("tooltiptext", aNode.getAttribute("label"));
+ } else if (aNode.hasAttribute("title")) {
+ wrapper.setAttribute("title", aNode.getAttribute("title"));
+ wrapper.setAttribute("tooltiptext", aNode.getAttribute("title"));
+ }
+
+ if (aNode.hasAttribute("flex")) {
+ wrapper.setAttribute("flex", aNode.getAttribute("flex"));
+ }
+
+ if (aPlace == "panel") {
+ if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
+ wrapper.setAttribute("haswideitem", "true");
+ } else if (wrapper.hasAttribute("haswideitem")) {
+ wrapper.removeAttribute("haswideitem");
+ }
+ }
+
+ let removable = aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode);
+ wrapper.setAttribute("removable", removable);
+
+ let contextMenuAttrName = "";
+ if (aNode.getAttribute("context")) {
+ contextMenuAttrName = "context";
+ } else if (aNode.getAttribute("contextmenu")) {
+ contextMenuAttrName = "contextmenu";
+ }
+ let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
+ let contextMenuForPlace = aPlace == "panel" ?
+ kPanelItemContextMenu :
+ kPaletteItemContextMenu;
+ if (aPlace != "toolbar") {
+ wrapper.setAttribute("context", contextMenuForPlace);
+ }
+ // Only keep track of the menu if it is non-default.
+ if (currentContextMenu &&
+ currentContextMenu != contextMenuForPlace) {
+ aNode.setAttribute("wrapped-context", currentContextMenu);
+ aNode.setAttribute("wrapped-contextAttrName", contextMenuAttrName)
+ aNode.removeAttribute(contextMenuAttrName);
+ } else if (currentContextMenu == contextMenuForPlace) {
+ aNode.removeAttribute(contextMenuAttrName);
+ }
+
+ // Only add listeners for newly created wrappers:
+ if (!aIsUpdate) {
+ wrapper.addEventListener("mousedown", this);
+ wrapper.addEventListener("mouseup", this);
+ }
+
+ return wrapper;
+ },
+
+ deferredUnwrapToolbarItem: function(aWrapper) {
+ return new Promise(resolve => {
+ dispatchFunction(() => {
+ let item = null;
+ try {
+ item = this.unwrapToolbarItem(aWrapper);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ resolve(item);
+ });
+ });
+ },
+
+ unwrapToolbarItem: function(aWrapper) {
+ if (aWrapper.nodeName != "toolbarpaletteitem") {
+ return aWrapper;
+ }
+ aWrapper.removeEventListener("mousedown", this);
+ aWrapper.removeEventListener("mouseup", this);
+
+ let place = aWrapper.getAttribute("place");
+
+ let toolbarItem = aWrapper.firstChild;
+ if (!toolbarItem) {
+ log.error("no toolbarItem child for " + aWrapper.tagName + "#" + aWrapper.id);
+ aWrapper.remove();
+ return null;
+ }
+
+ if (aWrapper.hasAttribute("itemobserves")) {
+ toolbarItem.setAttribute("observes", aWrapper.getAttribute("itemobserves"));
+ }
+
+ if (aWrapper.hasAttribute("itemchecked")) {
+ toolbarItem.checked = true;
+ }
+
+ if (aWrapper.hasAttribute("itemcommand")) {
+ let commandID = aWrapper.getAttribute("itemcommand");
+ toolbarItem.setAttribute("command", commandID);
+
+ // XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing
+ let command = this.document.getElementById(commandID);
+ if (command && command.hasAttribute("disabled")) {
+ toolbarItem.setAttribute("disabled", command.getAttribute("disabled"));
+ }
+ }
+
+ let wrappedContext = toolbarItem.getAttribute("wrapped-context");
+ if (wrappedContext) {
+ let contextAttrName = toolbarItem.getAttribute("wrapped-contextAttrName");
+ toolbarItem.setAttribute(contextAttrName, wrappedContext);
+ toolbarItem.removeAttribute("wrapped-contextAttrName");
+ toolbarItem.removeAttribute("wrapped-context");
+ } else if (place == "panel") {
+ toolbarItem.setAttribute("context", kPanelItemContextMenu);
+ }
+
+ if (aWrapper.parentNode) {
+ aWrapper.parentNode.replaceChild(toolbarItem, aWrapper);
+ }
+ return toolbarItem;
+ },
+
+ _wrapToolbarItem: function*(aArea) {
+ let target = CustomizableUI.getCustomizeTargetForArea(aArea, this.window);
+ if (!target || this.areas.has(target)) {
+ return null;
+ }
+
+ this._addDragHandlers(target);
+ for (let child of target.children) {
+ if (this.isCustomizableItem(child) && !this.isWrappedToolbarItem(child)) {
+ yield this.deferredWrapToolbarItem(child, CustomizableUI.getPlaceForItem(child)).then(null, log.error);
+ }
+ }
+ this.areas.add(target);
+ return target;
+ },
+
+ _wrapToolbarItemSync: function(aArea) {
+ let target = CustomizableUI.getCustomizeTargetForArea(aArea, this.window);
+ if (!target || this.areas.has(target)) {
+ return null;
+ }
+
+ this._addDragHandlers(target);
+ try {
+ for (let child of target.children) {
+ if (this.isCustomizableItem(child) && !this.isWrappedToolbarItem(child)) {
+ this.wrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
+ }
+ }
+ } catch (ex) {
+ log.error(ex, ex.stack);
+ }
+
+ this.areas.add(target);
+ return target;
+ },
+
+ _wrapToolbarItems: function*() {
+ for (let area of CustomizableUI.areas) {
+ yield this._wrapToolbarItem(area);
+ }
+ },
+
+ _addDragHandlers: function(aTarget) {
+ aTarget.addEventListener("dragstart", this, true);
+ aTarget.addEventListener("dragover", this, true);
+ aTarget.addEventListener("dragexit", this, true);
+ aTarget.addEventListener("drop", this, true);
+ aTarget.addEventListener("dragend", this, true);
+ },
+
+ _wrapItemsInArea: function(target) {
+ for (let child of target.children) {
+ if (this.isCustomizableItem(child)) {
+ this.wrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
+ }
+ }
+ },
+
+ _removeDragHandlers: function(aTarget) {
+ aTarget.removeEventListener("dragstart", this, true);
+ aTarget.removeEventListener("dragover", this, true);
+ aTarget.removeEventListener("dragexit", this, true);
+ aTarget.removeEventListener("drop", this, true);
+ aTarget.removeEventListener("dragend", this, true);
+ },
+
+ _unwrapItemsInArea: function(target) {
+ for (let toolbarItem of target.children) {
+ if (this.isWrappedToolbarItem(toolbarItem)) {
+ this.unwrapToolbarItem(toolbarItem);
+ }
+ }
+ },
+
+ _unwrapToolbarItems: function() {
+ return Task.spawn(function*() {
+ for (let target of this.areas) {
+ for (let toolbarItem of target.children) {
+ if (this.isWrappedToolbarItem(toolbarItem)) {
+ yield this.deferredUnwrapToolbarItem(toolbarItem);
+ }
+ }
+ this._removeDragHandlers(target);
+ }
+ this.areas.clear();
+ }.bind(this)).then(null, log.error);
+ },
+
+ _removeExtraToolbarsIfEmpty: function() {
+ let toolbox = this.window.gNavToolbox;
+ for (let child of toolbox.children) {
+ if (child.hasAttribute("customindex")) {
+ let placements = CustomizableUI.getWidgetIdsInArea(child.id);
+ if (!placements.length) {
+ CustomizableUI.removeExtraToolbar(child.id);
+ }
+ }
+ }
+ },
+
+ persistCurrentSets: function(aSetBeforePersisting) {
+ let document = this.document;
+ let toolbars = document.querySelectorAll("toolbar[customizable='true'][currentset]");
+ for (let toolbar of toolbars) {
+ if (aSetBeforePersisting) {
+ let set = toolbar.currentSet;
+ toolbar.setAttribute("currentset", set);
+ }
+ // Persist the currentset attribute directly on hardcoded toolbars.
+ document.persist(toolbar.id, "currentset");
+ }
+ },
+
+ reset: function() {
+ this.resetting = true;
+ // Disable the reset button temporarily while resetting:
+ let btn = this.document.getElementById("customization-reset-button");
+ BrowserUITelemetry.countCustomizationEvent("reset");
+ btn.disabled = true;
+ return Task.spawn(function*() {
+ this._removePanelCustomizationPlaceholders();
+ yield this.depopulatePalette();
+ yield this._unwrapToolbarItems();
+
+ CustomizableUI.reset();
+
+ this._updateLWThemeButtonIcon();
+
+ yield this._wrapToolbarItems();
+ this.populatePalette();
+
+ this.persistCurrentSets(true);
+
+ this._updateResetButton();
+ this._updateUndoResetButton();
+ this._updateEmptyPaletteNotice();
+ this._showPanelCustomizationPlaceholders();
+ this.resetting = false;
+ if (!this._wantToBeInCustomizeMode) {
+ this.exit();
+ }
+ }.bind(this)).then(null, log.error);
+ },
+
+ undoReset: function() {
+ this.resetting = true;
+
+ return Task.spawn(function*() {
+ this._removePanelCustomizationPlaceholders();
+ yield this.depopulatePalette();
+ yield this._unwrapToolbarItems();
+
+ CustomizableUI.undoReset();
+
+ this._updateLWThemeButtonIcon();
+
+ yield this._wrapToolbarItems();
+ this.populatePalette();
+
+ this.persistCurrentSets(true);
+
+ this._updateResetButton();
+ this._updateUndoResetButton();
+ this._updateEmptyPaletteNotice();
+ this.resetting = false;
+ }.bind(this)).then(null, log.error);
+ },
+
+ _onToolbarVisibilityChange: function(aEvent) {
+ let toolbar = aEvent.target;
+ if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
+ toolbar.setAttribute("customizing", "true");
+ } else {
+ toolbar.removeAttribute("customizing");
+ }
+ this._onUIChange();
+ this.updateLWTStyling();
+ },
+
+ onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
+ this._onUIChange();
+ },
+
+ onWidgetAdded: function(aWidgetId, aArea, aPosition) {
+ this._onUIChange();
+ },
+
+ onWidgetRemoved: function(aWidgetId, aArea) {
+ this._onUIChange();
+ },
+
+ onWidgetBeforeDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
+ if (aContainer.ownerGlobal != this.window || this.resetting) {
+ return;
+ }
+ if (aContainer.id == CustomizableUI.AREA_PANEL) {
+ this._removePanelCustomizationPlaceholders();
+ }
+ // If we get called for widgets that aren't in the window yet, they might not have
+ // a parentNode at all.
+ if (aNodeToChange.parentNode) {
+ this.unwrapToolbarItem(aNodeToChange.parentNode);
+ }
+ if (aSecondaryNode) {
+ this.unwrapToolbarItem(aSecondaryNode.parentNode);
+ }
+ },
+
+ onWidgetAfterDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
+ if (aContainer.ownerGlobal != this.window || this.resetting) {
+ return;
+ }
+ // If the node is still attached to the container, wrap it again:
+ if (aNodeToChange.parentNode) {
+ let place = CustomizableUI.getPlaceForItem(aNodeToChange);
+ this.wrapToolbarItem(aNodeToChange, place);
+ if (aSecondaryNode) {
+ this.wrapToolbarItem(aSecondaryNode, place);
+ }
+ } else {
+ // If not, it got removed.
+
+ // If an API-based widget is removed while customizing, append it to the palette.
+ // The _applyDrop code itself will take care of positioning it correctly, if
+ // applicable. We need the code to be here so removing widgets using CustomizableUI's
+ // API also does the right thing (and adds it to the palette)
+ let widgetId = aNodeToChange.id;
+ let widget = CustomizableUI.getWidget(widgetId);
+ if (widget.provider == CustomizableUI.PROVIDER_API) {
+ let paletteItem = this.makePaletteItem(widget, "palette");
+ this.visiblePalette.appendChild(paletteItem);
+ }
+ }
+ if (aContainer.id == CustomizableUI.AREA_PANEL) {
+ this._showPanelCustomizationPlaceholders();
+ }
+ },
+
+ onWidgetDestroyed: function(aWidgetId) {
+ let wrapper = this.document.getElementById("wrapper-" + aWidgetId);
+ if (wrapper) {
+ let wasInPanel = wrapper.parentNode == this.panelUIContents;
+ wrapper.remove();
+ if (wasInPanel) {
+ this._showPanelCustomizationPlaceholders();
+ }
+ }
+ },
+
+ onWidgetAfterCreation: function(aWidgetId, aArea) {
+ // If the node was added to an area, we would have gotten an onWidgetAdded notification,
+ // plus associated DOM change notifications, so only do stuff for the palette:
+ if (!aArea) {
+ let widgetNode = this.document.getElementById(aWidgetId);
+ if (widgetNode) {
+ this.wrapToolbarItem(widgetNode, "palette");
+ } else {
+ let widget = CustomizableUI.getWidget(aWidgetId);
+ this.visiblePalette.appendChild(this.makePaletteItem(widget, "palette"));
+ }
+ }
+ },
+
+ onAreaNodeRegistered: function(aArea, aContainer) {
+ if (aContainer.ownerDocument == this.document) {
+ this._wrapItemsInArea(aContainer);
+ this._addDragHandlers(aContainer);
+ DragPositionManager.add(this.window, aArea, aContainer);
+ this.areas.add(aContainer);
+ }
+ },
+
+ onAreaNodeUnregistered: function(aArea, aContainer, aReason) {
+ if (aContainer.ownerDocument == this.document && aReason == CustomizableUI.REASON_AREA_UNREGISTERED) {
+ this._unwrapItemsInArea(aContainer);
+ this._removeDragHandlers(aContainer);
+ DragPositionManager.remove(this.window, aArea, aContainer);
+ this.areas.delete(aContainer);
+ }
+ },
+
+ openAddonsManagerThemes: function(aEvent) {
+ aEvent.target.parentNode.parentNode.hidePopup();
+ this.window.BrowserOpenAddonsMgr('addons://list/theme');
+ },
+
+ getMoreThemes: function(aEvent) {
+ aEvent.target.parentNode.parentNode.hidePopup();
+ let getMoreURL = Services.urlFormatter.formatURLPref("lightweightThemes.getMoreURL");
+ this.window.openUILinkIn(getMoreURL, "tab");
+ },
+
+ onLWThemesMenuShowing: function(aEvent) {
+ const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
+ const RECENT_LWT_COUNT = 5;
+
+ this._clearLWThemesMenu(aEvent.target);
+
+ function previewTheme(aEvent) {
+ LightweightThemeManager.previewTheme(aEvent.target.theme.id != DEFAULT_THEME_ID ?
+ aEvent.target.theme : null);
+ }
+
+ function resetPreview() {
+ LightweightThemeManager.resetPreview();
+ }
+
+ let onThemeSelected = panel => {
+ this._updateLWThemeButtonIcon();
+ this._onUIChange();
+ panel.hidePopup();
+ };
+
+ AddonManager.getAddonByID(DEFAULT_THEME_ID, function(aDefaultTheme) {
+ let doc = this.window.document;
+
+ function buildToolbarButton(aTheme) {
+ let tbb = doc.createElement("toolbarbutton");
+ tbb.theme = aTheme;
+ tbb.setAttribute("label", aTheme.name);
+ if (aDefaultTheme == aTheme) {
+ // The actual icon is set up so it looks nice in about:addons, but
+ // we'd like the version that's correct for the OS we're on, so we set
+ // an attribute that our styling will then use to display the icon.
+ tbb.setAttribute("defaulttheme", "true");
+ } else {
+ tbb.setAttribute("image", aTheme.iconURL);
+ }
+ if (aTheme.description)
+ tbb.setAttribute("tooltiptext", aTheme.description);
+ tbb.setAttribute("tabindex", "0");
+ tbb.classList.add("customization-lwtheme-menu-theme");
+ tbb.setAttribute("aria-checked", aTheme.isActive);
+ tbb.setAttribute("role", "menuitemradio");
+ if (aTheme.isActive) {
+ tbb.setAttribute("active", "true");
+ }
+ tbb.addEventListener("focus", previewTheme);
+ tbb.addEventListener("mouseover", previewTheme);
+ tbb.addEventListener("blur", resetPreview);
+ tbb.addEventListener("mouseout", resetPreview);
+
+ return tbb;
+ }
+
+ let themes = [aDefaultTheme];
+ let lwts = LightweightThemeManager.usedThemes;
+ if (lwts.length > RECENT_LWT_COUNT)
+ lwts.length = RECENT_LWT_COUNT;
+ let currentLwt = LightweightThemeManager.currentTheme;
+ for (let lwt of lwts) {
+ lwt.isActive = !!currentLwt && (lwt.id == currentLwt.id);
+ themes.push(lwt);
+ }
+
+ let footer = doc.getElementById("customization-lwtheme-menu-footer");
+ let panel = footer.parentNode;
+ let recommendedLabel = doc.getElementById("customization-lwtheme-menu-recommended");
+ for (let theme of themes) {
+ let button = buildToolbarButton(theme);
+ button.addEventListener("command", () => {
+ if ("userDisabled" in button.theme)
+ button.theme.userDisabled = false;
+ else
+ LightweightThemeManager.currentTheme = button.theme;
+ onThemeSelected(panel);
+ });
+ panel.insertBefore(button, recommendedLabel);
+ }
+
+ let lwthemePrefs = Services.prefs.getBranch("lightweightThemes.");
+ let recommendedThemes = lwthemePrefs.getComplexValue("recommendedThemes",
+ Ci.nsISupportsString).data;
+ recommendedThemes = JSON.parse(recommendedThemes);
+ let sb = Services.strings.createBundle("chrome://browser/locale/lightweightThemes.properties");
+ for (let theme of recommendedThemes) {
+ theme.name = sb.GetStringFromName("lightweightThemes." + theme.id + ".name");
+ theme.description = sb.GetStringFromName("lightweightThemes." + theme.id + ".description");
+ let button = buildToolbarButton(theme);
+ button.addEventListener("command", () => {
+ LightweightThemeManager.setLocalTheme(button.theme);
+ recommendedThemes = recommendedThemes.filter((aTheme) => { return aTheme.id != button.theme.id; });
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(recommendedThemes);
+ lwthemePrefs.setComplexValue("recommendedThemes",
+ Ci.nsISupportsString, string);
+ onThemeSelected(panel);
+ });
+ panel.insertBefore(button, footer);
+ }
+ let hideRecommendedLabel = (footer.previousSibling == recommendedLabel);
+ recommendedLabel.hidden = hideRecommendedLabel;
+ }.bind(this));
+ },
+
+ _clearLWThemesMenu: function(panel) {
+ let footer = this.document.getElementById("customization-lwtheme-menu-footer");
+ let recommendedLabel = this.document.getElementById("customization-lwtheme-menu-recommended");
+ for (let element of [footer, recommendedLabel]) {
+ while (element.previousSibling &&
+ element.previousSibling.localName == "toolbarbutton") {
+ element.previousSibling.remove();
+ }
+ }
+
+ // Workaround for bug 1059934
+ panel.removeAttribute("height");
+ },
+
+ _onUIChange: function() {
+ this._changed = true;
+ if (!this.resetting) {
+ this._updateResetButton();
+ this._updateUndoResetButton();
+ this._updateEmptyPaletteNotice();
+ }
+ CustomizableUI.dispatchToolboxEvent("customizationchange");
+ },
+
+ _updateEmptyPaletteNotice: function() {
+ let paletteItems = this.visiblePalette.getElementsByTagName("toolbarpaletteitem");
+ this.paletteEmptyNotice.hidden = !!paletteItems.length;
+ },
+
+ _updateResetButton: function() {
+ let btn = this.document.getElementById("customization-reset-button");
+ btn.disabled = CustomizableUI.inDefaultState;
+ },
+
+ _updateUndoResetButton: function() {
+ let undoResetButton = this.document.getElementById("customization-undo-reset-button");
+ undoResetButton.hidden = !CustomizableUI.canUndoReset;
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "toolbarvisibilitychange":
+ this._onToolbarVisibilityChange(aEvent);
+ break;
+ case "dragstart":
+ this._onDragStart(aEvent);
+ break;
+ case "dragover":
+ this._onDragOver(aEvent);
+ break;
+ case "drop":
+ this._onDragDrop(aEvent);
+ break;
+ case "dragexit":
+ this._onDragExit(aEvent);
+ break;
+ case "dragend":
+ this._onDragEnd(aEvent);
+ break;
+ case "command":
+ if (aEvent.originalTarget == this.window.PanelUI.menuButton) {
+ this.exit();
+ aEvent.preventDefault();
+ }
+ break;
+ case "mousedown":
+ this._onMouseDown(aEvent);
+ break;
+ case "mouseup":
+ this._onMouseUp(aEvent);
+ break;
+ case "keypress":
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ this.exit();
+ }
+ break;
+ case "unload":
+ this.uninit();
+ break;
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ this._updateResetButton();
+ this._updateUndoResetButton();
+ if (AppConstants.CAN_DRAW_IN_TITLEBAR) {
+ this._updateTitlebarButton();
+ }
+ break;
+ case "lightweight-theme-window-updated":
+ if (aSubject == this.window) {
+ aData = JSON.parse(aData);
+ if (!aData) {
+ this.removeLWTStyling();
+ } else {
+ this.updateLWTStyling(aData);
+ }
+ }
+ break;
+ }
+ },
+
+ _updateTitlebarButton: function() {
+ if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
+ return;
+ }
+ let drawInTitlebar = true;
+ try {
+ drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref);
+ } catch (ex) { }
+ let button = this.document.getElementById("customization-titlebar-visibility-button");
+ // Drawing in the titlebar means 'hiding' the titlebar:
+ if (drawInTitlebar) {
+ button.removeAttribute("checked");
+ } else {
+ button.setAttribute("checked", "true");
+ }
+ },
+
+ toggleTitlebar: function(aShouldShowTitlebar) {
+ if (!AppConstants.CAN_DRAW_IN_TITLEBAR) {
+ return;
+ }
+ // Drawing in the titlebar means not showing the titlebar, hence the negation:
+ Services.prefs.setBoolPref(kDrawInTitlebarPref, !aShouldShowTitlebar);
+ },
+
+ _onDragStart: function(aEvent) {
+ __dumpDragData(aEvent);
+ let item = aEvent.target;
+ while (item && item.localName != "toolbarpaletteitem") {
+ if (item.localName == "toolbar") {
+ return;
+ }
+ item = item.parentNode;
+ }
+
+ let draggedItem = item.firstChild;
+ let placeForItem = CustomizableUI.getPlaceForItem(item);
+ let isRemovable = placeForItem == "palette" ||
+ CustomizableUI.isWidgetRemovable(draggedItem);
+ if (item.classList.contains(kPlaceholderClass) || !isRemovable) {
+ return;
+ }
+
+ let dt = aEvent.dataTransfer;
+ let documentId = aEvent.target.ownerDocument.documentElement.id;
+ let isInToolbar = placeForItem == "toolbar";
+
+ dt.mozSetDataAt(kDragDataTypePrefix + documentId, draggedItem.id, 0);
+ dt.effectAllowed = "move";
+
+ let itemRect = draggedItem.getBoundingClientRect();
+ let itemCenter = {x: itemRect.left + itemRect.width / 2,
+ y: itemRect.top + itemRect.height / 2};
+ this._dragOffset = {x: aEvent.clientX - itemCenter.x,
+ y: aEvent.clientY - itemCenter.y};
+
+ gDraggingInToolbars = new Set();
+
+ // Hack needed so that the dragimage will still show the
+ // item as it appeared before it was hidden.
+ this._initializeDragAfterMove = function() {
+ // For automated tests, we sometimes start exiting customization mode
+ // before this fires, which leaves us with placeholders inserted after
+ // we've exited. So we need to check that we are indeed customizing.
+ if (this._customizing && !this._transitioning) {
+ item.hidden = true;
+ this._showPanelCustomizationPlaceholders();
+ DragPositionManager.start(this.window);
+ if (item.nextSibling) {
+ this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
+ this._dragOverItem = item.nextSibling;
+ } else if (isInToolbar && item.previousSibling) {
+ this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
+ this._dragOverItem = item.previousSibling;
+ }
+ }
+ this._initializeDragAfterMove = null;
+ this.window.clearTimeout(this._dragInitializeTimeout);
+ }.bind(this);
+ this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0);
+ },
+
+ _onDragOver: function(aEvent) {
+ if (this._isUnwantedDragDrop(aEvent)) {
+ return;
+ }
+ if (this._initializeDragAfterMove) {
+ this._initializeDragAfterMove();
+ }
+
+ __dumpDragData(aEvent);
+
+ let document = aEvent.target.ownerDocument;
+ let documentId = document.documentElement.id;
+ if (!aEvent.dataTransfer.mozTypesAt(0)) {
+ return;
+ }
+
+ let draggedItemId =
+ aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
+ let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
+ let targetArea = this._getCustomizableParent(aEvent.currentTarget);
+ let originArea = this._getCustomizableParent(draggedWrapper);
+
+ // Do nothing if the target or origin are not customizable.
+ if (!targetArea || !originArea) {
+ return;
+ }
+
+ // Do nothing if the widget is not allowed to be removed.
+ if (targetArea.id == kPaletteId &&
+ !CustomizableUI.isWidgetRemovable(draggedItemId)) {
+ return;
+ }
+
+ // Do nothing if the widget is not allowed to move to the target area.
+ if (targetArea.id != kPaletteId &&
+ !CustomizableUI.canWidgetMoveToArea(draggedItemId, targetArea.id)) {
+ return;
+ }
+
+ let targetIsToolbar = CustomizableUI.getAreaType(targetArea.id) == "toolbar";
+ let targetNode = this._getDragOverNode(aEvent, targetArea, targetIsToolbar, draggedItemId);
+
+ // We need to determine the place that the widget is being dropped in
+ // the target.
+ let dragOverItem, dragValue;
+ if (targetNode == targetArea.customizationTarget) {
+ // We'll assume if the user is dragging directly over the target, that
+ // they're attempting to append a child to that target.
+ dragOverItem = (targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
+ targetNode.lastChild) || targetNode;
+ dragValue = "after";
+ } else {
+ let targetParent = targetNode.parentNode;
+ let position = Array.indexOf(targetParent.children, targetNode);
+ if (position == -1) {
+ dragOverItem = targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
+ targetParent.lastChild;
+ dragValue = "after";
+ } else {
+ dragOverItem = targetParent.children[position];
+ if (!targetIsToolbar) {
+ dragValue = "before";
+ } else {
+ // Check if the aDraggedItem is hovered past the first half of dragOverItem
+ let window = dragOverItem.ownerGlobal;
+ let direction = window.getComputedStyle(dragOverItem, null).direction;
+ let itemRect = dragOverItem.getBoundingClientRect();
+ let dropTargetCenter = itemRect.left + (itemRect.width / 2);
+ let existingDir = dragOverItem.getAttribute("dragover");
+ if ((existingDir == "before") == (direction == "ltr")) {
+ dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2;
+ } else {
+ dropTargetCenter -= (parseInt(dragOverItem.style.borderRightWidth) || 0) / 2;
+ }
+ let before = direction == "ltr" ? aEvent.clientX < dropTargetCenter : aEvent.clientX > dropTargetCenter;
+ dragValue = before ? "before" : "after";
+ }
+ }
+ }
+
+ if (this._dragOverItem && dragOverItem != this._dragOverItem) {
+ this._cancelDragActive(this._dragOverItem, dragOverItem);
+ }
+
+ if (dragOverItem != this._dragOverItem || dragValue != dragOverItem.getAttribute("dragover")) {
+ if (dragOverItem != targetArea.customizationTarget) {
+ this._setDragActive(dragOverItem, dragValue, draggedItemId, targetIsToolbar);
+ } else if (targetIsToolbar) {
+ this._updateToolbarCustomizationOutline(this.window, targetArea);
+ }
+ this._dragOverItem = dragOverItem;
+ }
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ },
+
+ _onDragDrop: function(aEvent) {
+ if (this._isUnwantedDragDrop(aEvent)) {
+ return;
+ }
+
+ __dumpDragData(aEvent);
+ this._initializeDragAfterMove = null;
+ this.window.clearTimeout(this._dragInitializeTimeout);
+
+ let targetArea = this._getCustomizableParent(aEvent.currentTarget);
+ let document = aEvent.target.ownerDocument;
+ let documentId = document.documentElement.id;
+ let draggedItemId =
+ aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
+ let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
+ let originArea = this._getCustomizableParent(draggedWrapper);
+ if (this._dragSizeMap) {
+ this._dragSizeMap = new WeakMap();
+ }
+ // Do nothing if the target area or origin area are not customizable.
+ if (!targetArea || !originArea) {
+ return;
+ }
+ let targetNode = this._dragOverItem;
+ let dropDir = targetNode.getAttribute("dragover");
+ // Need to insert *after* this node if we promised the user that:
+ if (targetNode != targetArea && dropDir == "after") {
+ if (targetNode.nextSibling) {
+ targetNode = targetNode.nextSibling;
+ } else {
+ targetNode = targetArea;
+ }
+ }
+ // If the target node is a placeholder, get its sibling as the real target.
+ while (targetNode.classList.contains(kPlaceholderClass) && targetNode.nextSibling) {
+ targetNode = targetNode.nextSibling;
+ }
+ if (targetNode.tagName == "toolbarpaletteitem") {
+ targetNode = targetNode.firstChild;
+ }
+
+ this._cancelDragActive(this._dragOverItem, null, true);
+ this._removePanelCustomizationPlaceholders();
+
+ try {
+ this._applyDrop(aEvent, targetArea, originArea, draggedItemId, targetNode);
+ } catch (ex) {
+ log.error(ex, ex.stack);
+ }
+
+ this._showPanelCustomizationPlaceholders();
+ },
+
+ _applyDrop: function(aEvent, aTargetArea, aOriginArea, aDraggedItemId, aTargetNode) {
+ let document = aEvent.target.ownerDocument;
+ let draggedItem = document.getElementById(aDraggedItemId);
+ draggedItem.hidden = false;
+ draggedItem.removeAttribute("mousedown");
+
+ // Do nothing if the target was dropped onto itself (ie, no change in area
+ // or position).
+ if (draggedItem == aTargetNode) {
+ return;
+ }
+
+ // Is the target area the customization palette?
+ if (aTargetArea.id == kPaletteId) {
+ // Did we drag from outside the palette?
+ if (aOriginArea.id !== kPaletteId) {
+ if (!CustomizableUI.isWidgetRemovable(aDraggedItemId)) {
+ return;
+ }
+
+ CustomizableUI.removeWidgetFromArea(aDraggedItemId);
+ BrowserUITelemetry.countCustomizationEvent("remove");
+ // Special widgets are removed outright, we can return here:
+ if (CustomizableUI.isSpecialWidget(aDraggedItemId)) {
+ return;
+ }
+ }
+ draggedItem = draggedItem.parentNode;
+
+ // If the target node is the palette itself, just append
+ if (aTargetNode == this.visiblePalette) {
+ this.visiblePalette.appendChild(draggedItem);
+ } else {
+ // The items in the palette are wrapped, so we need the target node's parent here:
+ this.visiblePalette.insertBefore(draggedItem, aTargetNode.parentNode);
+ }
+ if (aOriginArea.id !== kPaletteId) {
+ // The dragend event already fires when the item moves within the palette.
+ this._onDragEnd(aEvent);
+ }
+ return;
+ }
+
+ if (!CustomizableUI.canWidgetMoveToArea(aDraggedItemId, aTargetArea.id)) {
+ return;
+ }
+
+ // Skipintoolbarset items won't really be moved:
+ if (draggedItem.getAttribute("skipintoolbarset") == "true") {
+ // These items should never leave their area:
+ if (aTargetArea != aOriginArea) {
+ return;
+ }
+ let place = draggedItem.parentNode.getAttribute("place");
+ this.unwrapToolbarItem(draggedItem.parentNode);
+ if (aTargetNode == aTargetArea.customizationTarget) {
+ aTargetArea.customizationTarget.appendChild(draggedItem);
+ } else {
+ this.unwrapToolbarItem(aTargetNode.parentNode);
+ aTargetArea.customizationTarget.insertBefore(draggedItem, aTargetNode);
+ this.wrapToolbarItem(aTargetNode, place);
+ }
+ this.wrapToolbarItem(draggedItem, place);
+ BrowserUITelemetry.countCustomizationEvent("move");
+ return;
+ }
+
+ // Is the target the customization area itself? If so, we just add the
+ // widget to the end of the area.
+ if (aTargetNode == aTargetArea.customizationTarget) {
+ CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id);
+ // For the purposes of BrowserUITelemetry, we consider both moving a widget
+ // within the same area, and adding a widget from one area to another area
+ // as a "move". An "add" is only when we move an item from the palette into
+ // an area.
+ let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
+ BrowserUITelemetry.countCustomizationEvent(custEventType);
+ this._onDragEnd(aEvent);
+ return;
+ }
+
+ // We need to determine the place that the widget is being dropped in
+ // the target.
+ let placement;
+ let itemForPlacement = aTargetNode;
+ // Skip the skipintoolbarset items when determining the place of the item:
+ while (itemForPlacement && itemForPlacement.getAttribute("skipintoolbarset") == "true" &&
+ itemForPlacement.parentNode &&
+ itemForPlacement.parentNode.nodeName == "toolbarpaletteitem") {
+ itemForPlacement = itemForPlacement.parentNode.nextSibling;
+ if (itemForPlacement && itemForPlacement.nodeName == "toolbarpaletteitem") {
+ itemForPlacement = itemForPlacement.firstChild;
+ }
+ }
+ if (itemForPlacement && !itemForPlacement.classList.contains(kPlaceholderClass)) {
+ let targetNodeId = (itemForPlacement.nodeName == "toolbarpaletteitem") ?
+ itemForPlacement.firstChild && itemForPlacement.firstChild.id :
+ itemForPlacement.id;
+ placement = CustomizableUI.getPlacementOfWidget(targetNodeId);
+ }
+ if (!placement) {
+ log.debug("Could not get a position for " + aTargetNode.nodeName + "#" + aTargetNode.id + "." + aTargetNode.className);
+ }
+ let position = placement ? placement.position : null;
+
+ // Is the target area the same as the origin? Since we've already handled
+ // the possibility that the target is the customization palette, we know
+ // that the widget is moving within a customizable area.
+ if (aTargetArea == aOriginArea) {
+ CustomizableUI.moveWidgetWithinArea(aDraggedItemId, position);
+ } else {
+ CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id, position);
+ }
+
+ this._onDragEnd(aEvent);
+
+ // For BrowserUITelemetry, an "add" is only when we move an item from the palette
+ // into an area. Otherwise, it's a move.
+ let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
+ BrowserUITelemetry.countCustomizationEvent(custEventType);
+
+ // If we dropped onto a skipintoolbarset item, manually correct the drop location:
+ if (aTargetNode != itemForPlacement) {
+ let draggedWrapper = draggedItem.parentNode;
+ let container = draggedWrapper.parentNode;
+ container.insertBefore(draggedWrapper, aTargetNode.parentNode);
+ }
+ },
+
+ _onDragExit: function(aEvent) {
+ if (this._isUnwantedDragDrop(aEvent)) {
+ return;
+ }
+
+ __dumpDragData(aEvent);
+
+ // When leaving customization areas, cancel the drag on the last dragover item
+ // We've attached the listener to areas, so aEvent.currentTarget will be the area.
+ // We don't care about dragexit events fired on descendants of the area,
+ // so we check that the event's target is the same as the area to which the listener
+ // was attached.
+ if (this._dragOverItem && aEvent.target == aEvent.currentTarget) {
+ this._cancelDragActive(this._dragOverItem);
+ this._dragOverItem = null;
+ }
+ },
+
+ /**
+ * To workaround bug 460801 we manually forward the drop event here when dragend wouldn't be fired.
+ */
+ _onDragEnd: function(aEvent) {
+ if (this._isUnwantedDragDrop(aEvent)) {
+ return;
+ }
+ this._initializeDragAfterMove = null;
+ this.window.clearTimeout(this._dragInitializeTimeout);
+ __dumpDragData(aEvent, "_onDragEnd");
+
+ let document = aEvent.target.ownerDocument;
+ document.documentElement.removeAttribute("customizing-movingItem");
+
+ let documentId = document.documentElement.id;
+ if (!aEvent.dataTransfer.mozTypesAt(0)) {
+ return;
+ }
+
+ let draggedItemId =
+ aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
+
+ let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
+
+ // DraggedWrapper might no longer available if a widget node is
+ // destroyed after starting (but before stopping) a drag.
+ if (draggedWrapper) {
+ draggedWrapper.hidden = false;
+ draggedWrapper.removeAttribute("mousedown");
+ }
+
+ if (this._dragOverItem) {
+ this._cancelDragActive(this._dragOverItem);
+ this._dragOverItem = null;
+ }
+ this._updateToolbarCustomizationOutline(this.window);
+ this._showPanelCustomizationPlaceholders();
+ DragPositionManager.stop();
+ },
+
+ _isUnwantedDragDrop: function(aEvent) {
+ // The simulated events generated by synthesizeDragStart/synthesizeDrop in
+ // mochitests are used only for testing whether the right data is being put
+ // into the dataTransfer. Neither cause a real drop to occur, so they don't
+ // set the source node. There isn't a means of testing real drag and drops,
+ // so this pref skips the check but it should only be set by test code.
+ if (this._skipSourceNodeCheck) {
+ return false;
+ }
+
+ /* 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.
+ return !mozSourceNode ||
+ mozSourceNode.ownerGlobal != this.window;
+ },
+
+ _setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) {
+ if (!aItem) {
+ return;
+ }
+
+ if (aItem.getAttribute("dragover") != aValue) {
+ aItem.setAttribute("dragover", aValue);
+
+ let window = aItem.ownerGlobal;
+ let draggedItem = window.document.getElementById(aDraggedItemId);
+ if (!aInToolbar) {
+ this._setGridDragActive(aItem, draggedItem, aValue);
+ } else {
+ let targetArea = this._getCustomizableParent(aItem);
+ this._updateToolbarCustomizationOutline(window, targetArea);
+ let makeSpaceImmediately = false;
+ if (!gDraggingInToolbars.has(targetArea.id)) {
+ gDraggingInToolbars.add(targetArea.id);
+ let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItemId);
+ let originArea = this._getCustomizableParent(draggedWrapper);
+ makeSpaceImmediately = originArea == targetArea;
+ }
+ // Calculate width of the item when it'd be dropped in this position
+ let width = this._getDragItemSize(aItem, draggedItem).width;
+ let direction = window.getComputedStyle(aItem).direction;
+ let prop, otherProp;
+ // If we're inserting before in ltr, or after in rtl:
+ if ((aValue == "before") == (direction == "ltr")) {
+ prop = "borderLeftWidth";
+ otherProp = "border-right-width";
+ } else {
+ // otherwise:
+ prop = "borderRightWidth";
+ otherProp = "border-left-width";
+ }
+ if (makeSpaceImmediately) {
+ aItem.setAttribute("notransition", "true");
+ }
+ aItem.style[prop] = width + 'px';
+ aItem.style.removeProperty(otherProp);
+ if (makeSpaceImmediately) {
+ // Force a layout flush:
+ aItem.getBoundingClientRect();
+ aItem.removeAttribute("notransition");
+ }
+ }
+ }
+ },
+ _cancelDragActive: function(aItem, aNextItem, aNoTransition) {
+ this._updateToolbarCustomizationOutline(aItem.ownerGlobal);
+ let currentArea = this._getCustomizableParent(aItem);
+ if (!currentArea) {
+ return;
+ }
+ let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
+ if (isToolbar) {
+ if (aNoTransition) {
+ aItem.setAttribute("notransition", "true");
+ }
+ aItem.removeAttribute("dragover");
+ // Remove both property values in the case that the end padding
+ // had been set.
+ aItem.style.removeProperty("border-left-width");
+ aItem.style.removeProperty("border-right-width");
+ if (aNoTransition) {
+ // Force a layout flush:
+ aItem.getBoundingClientRect();
+ aItem.removeAttribute("notransition");
+ }
+ } else {
+ aItem.removeAttribute("dragover");
+ if (aNextItem) {
+ let nextArea = this._getCustomizableParent(aNextItem);
+ if (nextArea == currentArea) {
+ // No need to do anything if we're still dragging in this area:
+ return;
+ }
+ }
+ // Otherwise, clear everything out:
+ let positionManager = DragPositionManager.getManagerForArea(currentArea);
+ positionManager.clearPlaceholders(currentArea, aNoTransition);
+ }
+ },
+
+ _setGridDragActive: function(aDragOverNode, aDraggedItem, aValue) {
+ let targetArea = this._getCustomizableParent(aDragOverNode);
+ let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItem.id);
+ let originArea = this._getCustomizableParent(draggedWrapper);
+ let positionManager = DragPositionManager.getManagerForArea(targetArea);
+ let draggedSize = this._getDragItemSize(aDragOverNode, aDraggedItem);
+ let isWide = aDraggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
+ positionManager.insertPlaceholder(targetArea, aDragOverNode, isWide, draggedSize,
+ originArea == targetArea);
+ },
+
+ _getDragItemSize: function(aDragOverNode, aDraggedItem) {
+ // Cache it good, cache it real good.
+ if (!this._dragSizeMap)
+ this._dragSizeMap = new WeakMap();
+ if (!this._dragSizeMap.has(aDraggedItem))
+ this._dragSizeMap.set(aDraggedItem, new WeakMap());
+ let itemMap = this._dragSizeMap.get(aDraggedItem);
+ let targetArea = this._getCustomizableParent(aDragOverNode);
+ let currentArea = this._getCustomizableParent(aDraggedItem);
+ // Return the size for this target from cache, if it exists.
+ let size = itemMap.get(targetArea);
+ if (size)
+ return size;
+
+ // Calculate size of the item when it'd be dropped in this position.
+ let currentParent = aDraggedItem.parentNode;
+ let currentSibling = aDraggedItem.nextSibling;
+ const kAreaType = "cui-areatype";
+ let areaType, currentType;
+
+ if (targetArea != currentArea) {
+ // Move the widget temporarily next to the placeholder.
+ aDragOverNode.parentNode.insertBefore(aDraggedItem, aDragOverNode);
+ // Update the node's areaType.
+ areaType = CustomizableUI.getAreaType(targetArea.id);
+ currentType = aDraggedItem.hasAttribute(kAreaType) &&
+ aDraggedItem.getAttribute(kAreaType);
+ if (areaType)
+ aDraggedItem.setAttribute(kAreaType, areaType);
+ this.wrapToolbarItem(aDraggedItem, areaType || "palette");
+ CustomizableUI.onWidgetDrag(aDraggedItem.id, targetArea.id);
+ } else {
+ aDraggedItem.parentNode.hidden = false;
+ }
+
+ // Fetch the new size.
+ let rect = aDraggedItem.parentNode.getBoundingClientRect();
+ size = {width: rect.width, height: rect.height};
+ // Cache the found value of size for this target.
+ itemMap.set(targetArea, size);
+
+ if (targetArea != currentArea) {
+ this.unwrapToolbarItem(aDraggedItem.parentNode);
+ // Put the item back into its previous position.
+ currentParent.insertBefore(aDraggedItem, currentSibling);
+ // restore the areaType
+ if (areaType) {
+ if (currentType === false)
+ aDraggedItem.removeAttribute(kAreaType);
+ else
+ aDraggedItem.setAttribute(kAreaType, currentType);
+ }
+ this.createOrUpdateWrapper(aDraggedItem, null, true);
+ CustomizableUI.onWidgetDrag(aDraggedItem.id);
+ } else {
+ aDraggedItem.parentNode.hidden = true;
+ }
+ return size;
+ },
+
+ _getCustomizableParent: function(aElement) {
+ let areas = CustomizableUI.areas;
+ areas.push(kPaletteId);
+ while (aElement) {
+ if (areas.indexOf(aElement.id) != -1) {
+ return aElement;
+ }
+ aElement = aElement.parentNode;
+ }
+ return null;
+ },
+
+ _getDragOverNode: function(aEvent, aAreaElement, aInToolbar, aDraggedItemId) {
+ let expectedParent = aAreaElement.customizationTarget || aAreaElement;
+ // Our tests are stupid. Cope:
+ if (!aEvent.clientX && !aEvent.clientY) {
+ return aEvent.target;
+ }
+ // Offset the drag event's position with the offset to the center of
+ // the thing we're dragging
+ let dragX = aEvent.clientX - this._dragOffset.x;
+ let dragY = aEvent.clientY - this._dragOffset.y;
+
+ // Ensure this is within the container
+ let boundsContainer = expectedParent;
+ // NB: because the panel UI itself is inside a scrolling container, we need
+ // to use the parent bounds (otherwise, if the panel UI is scrolled down,
+ // the numbers we get are in window coordinates which leads to various kinds
+ // of weirdness)
+ if (boundsContainer == this.panelUIContents) {
+ boundsContainer = boundsContainer.parentNode;
+ }
+ let bounds = boundsContainer.getBoundingClientRect();
+ dragX = Math.min(bounds.right, Math.max(dragX, bounds.left));
+ dragY = Math.min(bounds.bottom, Math.max(dragY, bounds.top));
+
+ let targetNode;
+ if (aInToolbar) {
+ targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY);
+ while (targetNode && targetNode.parentNode != expectedParent) {
+ targetNode = targetNode.parentNode;
+ }
+ } else {
+ let positionManager = DragPositionManager.getManagerForArea(aAreaElement);
+ // Make it relative to the container:
+ dragX -= bounds.left;
+ // NB: but if we're in the panel UI, we need to use the actual panel
+ // contents instead of the scrolling container to determine our origin
+ // offset against:
+ if (expectedParent == this.panelUIContents) {
+ dragY -= this.panelUIContents.getBoundingClientRect().top;
+ } else {
+ dragY -= bounds.top;
+ }
+ // Find the closest node:
+ targetNode = positionManager.find(aAreaElement, dragX, dragY, aDraggedItemId);
+ }
+ return targetNode || aEvent.target;
+ },
+
+ _onMouseDown: function(aEvent) {
+ log.debug("_onMouseDown");
+ if (aEvent.button != 0) {
+ return;
+ }
+ let doc = aEvent.target.ownerDocument;
+ doc.documentElement.setAttribute("customizing-movingItem", true);
+ let item = this._getWrapper(aEvent.target);
+ if (item && !item.classList.contains(kPlaceholderClass) &&
+ item.getAttribute("removable") == "true") {
+ item.setAttribute("mousedown", "true");
+ }
+ },
+
+ _onMouseUp: function(aEvent) {
+ log.debug("_onMouseUp");
+ if (aEvent.button != 0) {
+ return;
+ }
+ let doc = aEvent.target.ownerDocument;
+ doc.documentElement.removeAttribute("customizing-movingItem");
+ let item = this._getWrapper(aEvent.target);
+ if (item) {
+ item.removeAttribute("mousedown");
+ }
+ },
+
+ _getWrapper: function(aElement) {
+ while (aElement && aElement.localName != "toolbarpaletteitem") {
+ if (aElement.localName == "toolbar")
+ return null;
+ aElement = aElement.parentNode;
+ }
+ return aElement;
+ },
+
+ _showPanelCustomizationPlaceholders: function() {
+ let doc = this.document;
+ let contents = this.panelUIContents;
+ let narrowItemsAfterWideItem = 0;
+ let node = contents.lastChild;
+ while (node && !node.classList.contains(CustomizableUI.WIDE_PANEL_CLASS) &&
+ (!node.firstChild || !node.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS))) {
+ if (!node.hidden && !node.classList.contains(kPlaceholderClass)) {
+ narrowItemsAfterWideItem++;
+ }
+ node = node.previousSibling;
+ }
+
+ let orphanedItems = narrowItemsAfterWideItem % CustomizableUI.PANEL_COLUMN_COUNT;
+ let placeholders = CustomizableUI.PANEL_COLUMN_COUNT - orphanedItems;
+
+ let currentPlaceholderCount = contents.querySelectorAll("." + kPlaceholderClass).length;
+ if (placeholders > currentPlaceholderCount) {
+ while (placeholders-- > currentPlaceholderCount) {
+ let placeholder = doc.createElement("toolbarpaletteitem");
+ placeholder.classList.add(kPlaceholderClass);
+ // XXXjaws The toolbarbutton child here is only necessary to get
+ // the styling right here.
+ let placeholderChild = doc.createElement("toolbarbutton");
+ placeholderChild.classList.add(kPlaceholderClass + "-child");
+ placeholder.appendChild(placeholderChild);
+ contents.appendChild(placeholder);
+ }
+ } else if (placeholders < currentPlaceholderCount) {
+ while (placeholders++ < currentPlaceholderCount) {
+ contents.querySelectorAll("." + kPlaceholderClass)[0].remove();
+ }
+ }
+ },
+
+ _removePanelCustomizationPlaceholders: function() {
+ let contents = this.panelUIContents;
+ let oldPlaceholders = contents.getElementsByClassName(kPlaceholderClass);
+ while (oldPlaceholders.length) {
+ contents.removeChild(oldPlaceholders[0]);
+ }
+ },
+
+ /**
+ * Update toolbar customization targets during drag events to add or remove
+ * outlines to indicate that an area is customizable.
+ *
+ * @param aWindow The XUL window in which outlines should be updated.
+ * @param {Element} [aToolbarArea=null] The element of the customizable toolbar area to add the
+ * outline to. If aToolbarArea is falsy, the outline will be
+ * removed from all toolbar areas.
+ */
+ _updateToolbarCustomizationOutline: function(aWindow, aToolbarArea = null) {
+ // Remove the attribute from existing customization targets
+ for (let area of CustomizableUI.areas) {
+ if (CustomizableUI.getAreaType(area) != CustomizableUI.TYPE_TOOLBAR) {
+ continue;
+ }
+ let target = CustomizableUI.getCustomizeTargetForArea(area, aWindow);
+ target.removeAttribute("customizing-dragovertarget");
+ }
+
+ // Now set the attribute on the desired target
+ if (aToolbarArea) {
+ if (CustomizableUI.getAreaType(aToolbarArea.id) != CustomizableUI.TYPE_TOOLBAR)
+ return;
+ let target = CustomizableUI.getCustomizeTargetForArea(aToolbarArea.id, aWindow);
+ target.setAttribute("customizing-dragovertarget", true);
+ }
+ },
+
+ _findVisiblePreviousSiblingNode: function(aReferenceNode) {
+ while (aReferenceNode &&
+ aReferenceNode.localName == "toolbarpaletteitem" &&
+ aReferenceNode.firstChild.hidden) {
+ aReferenceNode = aReferenceNode.previousSibling;
+ }
+ return aReferenceNode;
+ },
+};
+
+function __dumpDragData(aEvent, caller) {
+ if (!gDebug) {
+ return;
+ }
+ let str = "Dumping drag data (" + (caller ? caller + " in " : "") + "CustomizeMode.jsm) {\n";
+ str += " type: " + aEvent["type"] + "\n";
+ for (let el of ["target", "currentTarget", "relatedTarget"]) {
+ if (aEvent[el]) {
+ str += " " + el + ": " + aEvent[el] + "(localName=" + aEvent[el].localName + "; id=" + aEvent[el].id + ")\n";
+ }
+ }
+ for (let prop in aEvent.dataTransfer) {
+ if (typeof aEvent.dataTransfer[prop] != "function") {
+ str += " dataTransfer[" + prop + "]: " + aEvent.dataTransfer[prop] + "\n";
+ }
+ }
+ str += "}";
+ log.debug(str);
+}
+
+function dispatchFunction(aFunc) {
+ Services.tm.currentThread.dispatch(aFunc, Ci.nsIThread.DISPATCH_NORMAL);
+}
diff --git a/browser/components/customizableui/DragPositionManager.jsm b/browser/components/customizableui/DragPositionManager.jsm
new file mode 100644
index 000000000..1b4eb59dc
--- /dev/null
+++ b/browser/components/customizableui/DragPositionManager.jsm
@@ -0,0 +1,420 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource:///modules/CustomizableUI.jsm");
+
+var gManagers = new WeakMap();
+
+const kPaletteId = "customization-palette";
+const kPlaceholderClass = "panel-customization-placeholder";
+
+this.EXPORTED_SYMBOLS = ["DragPositionManager"];
+
+function AreaPositionManager(aContainer) {
+ // Caching the direction and bounds of the container for quick access later:
+ let window = aContainer.ownerGlobal;
+ this._dir = window.getComputedStyle(aContainer).direction;
+ let containerRect = aContainer.getBoundingClientRect();
+ this._containerInfo = {
+ left: containerRect.left,
+ right: containerRect.right,
+ top: containerRect.top,
+ width: containerRect.width
+ };
+ this._inPanel = aContainer.id == CustomizableUI.AREA_PANEL;
+ this._horizontalDistance = null;
+ this.update(aContainer);
+}
+
+AreaPositionManager.prototype = {
+ _nodePositionStore: null,
+ _wideCache: null,
+
+ update: function(aContainer) {
+ this._nodePositionStore = new WeakMap();
+ this._wideCache = new Set();
+ let last = null;
+ let singleItemHeight;
+ for (let child of aContainer.children) {
+ if (child.hidden) {
+ continue;
+ }
+ let isNodeWide = this._checkIfWide(child);
+ if (isNodeWide) {
+ this._wideCache.add(child.id);
+ }
+ let coordinates = this._lazyStoreGet(child);
+ // We keep a baseline horizontal distance between non-wide nodes around
+ // for use when we can't compare with previous/next nodes
+ if (!this._horizontalDistance && last && !isNodeWide) {
+ this._horizontalDistance = coordinates.left - last.left;
+ }
+ // We also keep the basic height of non-wide items for use below:
+ if (!isNodeWide && !singleItemHeight) {
+ singleItemHeight = coordinates.height;
+ }
+ last = !isNodeWide ? coordinates : null;
+ }
+ if (this._inPanel) {
+ this._heightToWidthFactor = CustomizableUI.PANEL_COLUMN_COUNT;
+ } else {
+ this._heightToWidthFactor = this._containerInfo.width / singleItemHeight;
+ }
+ },
+
+ /**
+ * Find the closest node in the container given the coordinates.
+ * "Closest" is defined in a somewhat strange manner: we prefer nodes
+ * which are in the same row over nodes that are in a different row.
+ * In order to implement this, we use a weighted cartesian distance
+ * where dy is more heavily weighted by a factor corresponding to the
+ * ratio between the container's width and the height of its elements.
+ */
+ find: function(aContainer, aX, aY, aDraggedItemId) {
+ let closest = null;
+ let minCartesian = Number.MAX_VALUE;
+ let containerX = this._containerInfo.left;
+ let containerY = this._containerInfo.top;
+ for (let node of aContainer.children) {
+ let coordinates = this._lazyStoreGet(node);
+ let offsetX = coordinates.x - containerX;
+ let offsetY = coordinates.y - containerY;
+ let hDiff = offsetX - aX;
+ let vDiff = offsetY - aY;
+ // For wide widgets, we're always going to be further from the center
+ // horizontally. Compensate:
+ if (this.isWide(node)) {
+ hDiff /= CustomizableUI.PANEL_COLUMN_COUNT;
+ }
+ // Then compensate for the height/width ratio so that we prefer items
+ // which are in the same row:
+ hDiff /= this._heightToWidthFactor;
+
+ let cartesianDiff = hDiff * hDiff + vDiff * vDiff;
+ if (cartesianDiff < minCartesian) {
+ minCartesian = cartesianDiff;
+ closest = node;
+ }
+ }
+
+ // Now correct this node based on what we're dragging
+ if (closest) {
+ let doc = aContainer.ownerDocument;
+ let draggedItem = doc.getElementById(aDraggedItemId);
+ // If dragging a wide item, always pick the first item in a row:
+ if (this._inPanel && draggedItem &&
+ draggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
+ return this._firstInRow(closest);
+ }
+ let targetBounds = this._lazyStoreGet(closest);
+ let farSide = this._dir == "ltr" ? "right" : "left";
+ let outsideX = targetBounds[farSide];
+ // Check if we're closer to the next target than to this one:
+ // Only move if we're not targeting a node in a different row:
+ if (aY > targetBounds.top && aY < targetBounds.bottom) {
+ if ((this._dir == "ltr" && aX > outsideX) ||
+ (this._dir == "rtl" && aX < outsideX)) {
+ return closest.nextSibling || aContainer;
+ }
+ }
+ }
+ return closest;
+ },
+
+ /**
+ * "Insert" a "placeholder" by shifting the subsequent children out of the
+ * way. We go through all the children, and shift them based on the position
+ * they would have if we had inserted something before aBefore. We use CSS
+ * transforms for this, which are CSS transitioned.
+ */
+ insertPlaceholder: function(aContainer, aBefore, aWide, aSize, aIsFromThisArea) {
+ let isShifted = false;
+ let shiftDown = aWide;
+ for (let child of aContainer.children) {
+ // Don't need to shift hidden nodes:
+ if (child.getAttribute("hidden") == "true") {
+ continue;
+ }
+ // If this is the node before which we're inserting, start shifting
+ // everything that comes after. One exception is inserting at the end
+ // of the menupanel, in which case we do not shift the placeholders:
+ if (child == aBefore && !child.classList.contains(kPlaceholderClass)) {
+ isShifted = true;
+ // If the node before which we're inserting is wide, we should
+ // shift everything one row down:
+ if (!shiftDown && this.isWide(child)) {
+ shiftDown = true;
+ }
+ }
+ // If we're moving items before a wide node that were already there,
+ // it's possible it's not necessary to shift nodes
+ // including & after the wide node.
+ if (this.__undoShift) {
+ isShifted = false;
+ }
+ if (isShifted) {
+ // Conversely, if we're adding something before a wide node, for
+ // simplicity's sake we move everything including the wide node down:
+ if (this.__moveDown) {
+ shiftDown = true;
+ }
+ if (aIsFromThisArea && !this._lastPlaceholderInsertion) {
+ child.setAttribute("notransition", "true");
+ }
+ // Determine the CSS transform based on the next node:
+ child.style.transform = this._getNextPos(child, shiftDown, aSize);
+ } else {
+ // If we're not shifting this node, reset the transform
+ child.style.transform = "";
+ }
+ }
+ if (aContainer.lastChild && aIsFromThisArea &&
+ !this._lastPlaceholderInsertion) {
+ // Flush layout:
+ aContainer.lastChild.getBoundingClientRect();
+ // then remove all the [notransition]
+ for (let child of aContainer.children) {
+ child.removeAttribute("notransition");
+ }
+ }
+ delete this.__moveDown;
+ delete this.__undoShift;
+ this._lastPlaceholderInsertion = aBefore;
+ },
+
+ isWide: function(aNode) {
+ return this._wideCache.has(aNode.id);
+ },
+
+ _checkIfWide: function(aNode) {
+ return this._inPanel && aNode && aNode.firstChild &&
+ aNode.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
+ },
+
+ /**
+ * Reset all the transforms in this container, optionally without
+ * transitioning them.
+ * @param aContainer the container in which to reset transforms
+ * @param aNoTransition if truthy, adds a notransition attribute to the node
+ * while resetting the transform.
+ */
+ clearPlaceholders: function(aContainer, aNoTransition) {
+ for (let child of aContainer.children) {
+ if (aNoTransition) {
+ child.setAttribute("notransition", true);
+ }
+ child.style.transform = "";
+ if (aNoTransition) {
+ // Need to force a reflow otherwise this won't work.
+ child.getBoundingClientRect();
+ child.removeAttribute("notransition");
+ }
+ }
+ // We snapped back, so we can assume there's no more
+ // "last" placeholder insertion point to keep track of.
+ if (aNoTransition) {
+ this._lastPlaceholderInsertion = null;
+ }
+ },
+
+ _getNextPos: function(aNode, aShiftDown, aSize) {
+ // Shifting down is easy:
+ if (this._inPanel && aShiftDown) {
+ return "translate(0, " + aSize.height + "px)";
+ }
+ return this._diffWithNext(aNode, aSize);
+ },
+
+ _diffWithNext: function(aNode, aSize) {
+ let xDiff;
+ let yDiff = null;
+ let nodeBounds = this._lazyStoreGet(aNode);
+ let side = this._dir == "ltr" ? "left" : "right";
+ let next = this._getVisibleSiblingForDirection(aNode, "next");
+ // First we determine the transform along the x axis.
+ // Usually, there will be a next node to base this on:
+ if (next) {
+ let otherBounds = this._lazyStoreGet(next);
+ xDiff = otherBounds[side] - nodeBounds[side];
+ // If the next node is a wide item in the panel, check if we could maybe
+ // just move further out in the same row, without snapping to the next
+ // one. This happens, for example, if moving an item that's before a wide
+ // node within its own row of items. There will be space to drop this
+ // item within the row, and the rest of the items do not need to shift.
+ if (this.isWide(next)) {
+ let otherXDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds,
+ this._firstInRow(aNode));
+ // If this has the same sign as our original shift, we're still
+ // snapping to the start of the row. In this case, we should move
+ // everything after us a row down, so as not to display two nodes on
+ // top of each other:
+ // (we would be able to get away with checking for equality instead of
+ // equal signs here, but one of these is based on the x coordinate of
+ // the first item in row N and one on that for row N - 1, so this is
+ // safer, as their margins might differ)
+ if ((otherXDiff < 0) == (xDiff < 0)) {
+ this.__moveDown = true;
+ } else {
+ // Otherwise, we succeeded and can move further out. This also means
+ // we can stop shifting the rest of the content:
+ xDiff = otherXDiff;
+ this.__undoShift = true;
+ }
+ } else {
+ // We set this explicitly because otherwise some strange difference
+ // between the height and the actual difference between line creeps in
+ // and messes with alignments
+ yDiff = otherBounds.top - nodeBounds.top;
+ }
+ } else {
+ // We don't have a sibling whose position we can use. First, let's see
+ // if we're also the first item (which complicates things):
+ let firstNode = this._firstInRow(aNode);
+ if (aNode == firstNode) {
+ // Maybe we stored the horizontal distance between non-wide nodes,
+ // if not, we'll use the width of the incoming node as a proxy:
+ xDiff = this._horizontalDistance || aSize.width;
+ } else {
+ // If not, we should be able to get the distance to the previous node
+ // and use the inverse, unless there's no room for another node (ie we
+ // are the last node and there's no room for another one)
+ xDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, firstNode);
+ }
+ }
+
+ // If we've not determined the vertical difference yet, check it here
+ if (yDiff === null) {
+ // If the next node is behind rather than in front, we must have moved
+ // vertically:
+ if ((xDiff > 0 && this._dir == "rtl") || (xDiff < 0 && this._dir == "ltr")) {
+ yDiff = aSize.height;
+ } else {
+ // Otherwise, we haven't
+ yDiff = 0;
+ }
+ }
+ return "translate(" + xDiff + "px, " + yDiff + "px)";
+ },
+
+ /**
+ * Helper function to find the transform a node if there isn't a next node
+ * to base that on.
+ * @param aNode the node to transform
+ * @param aNodeBounds the bounding rect info of this node
+ * @param aFirstNodeInRow the first node in aNode's row
+ */
+ _moveNextBasedOnPrevious: function(aNode, aNodeBounds, aFirstNodeInRow) {
+ let next = this._getVisibleSiblingForDirection(aNode, "previous");
+ let otherBounds = this._lazyStoreGet(next);
+ let side = this._dir == "ltr" ? "left" : "right";
+ let xDiff = aNodeBounds[side] - otherBounds[side];
+ // If, however, this means we move outside the container's box
+ // (i.e. the row in which this item is placed is full)
+ // we should move it to align with the first item in the next row instead
+ let bound = this._containerInfo[this._dir == "ltr" ? "right" : "left"];
+ if ((this._dir == "ltr" && xDiff + aNodeBounds.right > bound) ||
+ (this._dir == "rtl" && xDiff + aNodeBounds.left < bound)) {
+ xDiff = this._lazyStoreGet(aFirstNodeInRow)[side] - aNodeBounds[side];
+ }
+ return xDiff;
+ },
+
+ /**
+ * Get position details from our cache. If the node is not yet cached, get its position
+ * information and cache it now.
+ * @param aNode the node whose position info we want
+ * @return the position info
+ */
+ _lazyStoreGet: function(aNode) {
+ let rect = this._nodePositionStore.get(aNode);
+ if (!rect) {
+ // getBoundingClientRect() returns a DOMRect that is live, meaning that
+ // as the element moves around, the rects values change. We don't want
+ // that - we want a snapshot of what the rect values are right at this
+ // moment, and nothing else. So we have to clone the values.
+ let clientRect = aNode.getBoundingClientRect();
+ rect = {
+ left: clientRect.left,
+ right: clientRect.right,
+ width: clientRect.width,
+ height: clientRect.height,
+ top: clientRect.top,
+ bottom: clientRect.bottom,
+ };
+ rect.x = rect.left + rect.width / 2;
+ rect.y = rect.top + rect.height / 2;
+ Object.freeze(rect);
+ this._nodePositionStore.set(aNode, rect);
+ }
+ return rect;
+ },
+
+ _firstInRow: function(aNode) {
+ // XXXmconley: I'm not entirely sure why we need to take the floor of these
+ // values - it looks like, periodically, we're getting fractional pixels back
+ // from lazyStoreGet. I've filed bug 994247 to investigate.
+ let bound = Math.floor(this._lazyStoreGet(aNode).top);
+ let rv = aNode;
+ let prev;
+ while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) {
+ if (Math.floor(this._lazyStoreGet(prev).bottom) <= bound) {
+ return rv;
+ }
+ rv = prev;
+ }
+ return rv;
+ },
+
+ _getVisibleSiblingForDirection: function(aNode, aDirection) {
+ let rv = aNode;
+ do {
+ rv = rv[aDirection + "Sibling"];
+ } while (rv && rv.getAttribute("hidden") == "true")
+ return rv;
+ }
+}
+
+var DragPositionManager = {
+ start: function(aWindow) {
+ let areas = CustomizableUI.areas.filter((area) => CustomizableUI.getAreaType(area) != "toolbar");
+ areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow));
+ areas.push(aWindow.document.getElementById(kPaletteId));
+ for (let areaNode of areas) {
+ let positionManager = gManagers.get(areaNode);
+ if (positionManager) {
+ positionManager.update(areaNode);
+ } else {
+ gManagers.set(areaNode, new AreaPositionManager(areaNode));
+ }
+ }
+ },
+
+ add: function(aWindow, aArea, aContainer) {
+ if (CustomizableUI.getAreaType(aArea) != "toolbar") {
+ return;
+ }
+
+ gManagers.set(aContainer, new AreaPositionManager(aContainer));
+ },
+
+ remove: function(aWindow, aArea, aContainer) {
+ if (CustomizableUI.getAreaType(aArea) != "toolbar") {
+ return;
+ }
+
+ gManagers.delete(aContainer);
+ },
+
+ stop: function() {
+ gManagers = new WeakMap();
+ },
+
+ getManagerForArea: function(aArea) {
+ return gManagers.get(aArea);
+ }
+};
+
+Object.freeze(DragPositionManager);
diff --git a/browser/components/customizableui/PanelWideWidgetTracker.jsm b/browser/components/customizableui/PanelWideWidgetTracker.jsm
new file mode 100644
index 000000000..768cebbca
--- /dev/null
+++ b/browser/components/customizableui/PanelWideWidgetTracker.jsm
@@ -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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["PanelWideWidgetTracker"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+
+var gPanel = CustomizableUI.AREA_PANEL;
+// We keep track of the widget placements for the panel locally:
+var gPanelPlacements = [];
+
+// All the wide widgets we know of:
+var gWideWidgets = new Set();
+// All the widgets we know of:
+var gSeenWidgets = new Set();
+
+var PanelWideWidgetTracker = {
+ // Listeners used to validate panel contents whenever they change:
+ onWidgetAdded: function(aWidgetId, aArea, aPosition) {
+ if (aArea == gPanel) {
+ gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
+ let moveForward = this.shouldMoveForward(aWidgetId, aPosition);
+ this.adjustWidgets(aWidgetId, moveForward);
+ }
+ },
+ onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
+ if (aArea == gPanel) {
+ gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
+ let moveForward = this.shouldMoveForward(aWidgetId, aNewPosition);
+ this.adjustWidgets(aWidgetId, moveForward);
+ }
+ },
+ onWidgetRemoved: function(aWidgetId, aPrevArea) {
+ if (aPrevArea == gPanel) {
+ gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
+ this.adjustWidgets(aWidgetId, false);
+ }
+ },
+ onWidgetReset: function(aWidgetId) {
+ gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
+ },
+ // Listener to keep abreast of any new nodes. We use the DOM one because
+ // we need access to the actual node's classlist, so we can't use the ones above.
+ // Furthermore, onWidgetCreated only fires for API-based widgets, not for XUL ones.
+ onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
+ if (!gSeenWidgets.has(aNode.id)) {
+ if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
+ gWideWidgets.add(aNode.id);
+ }
+ gSeenWidgets.add(aNode.id);
+ }
+ },
+ // When widgets get destroyed, we remove them from our sets of stuff we care about:
+ onWidgetDestroyed: function(aWidgetId) {
+ gSeenWidgets.delete(aWidgetId);
+ gWideWidgets.delete(aWidgetId);
+ },
+ shouldMoveForward: function(aWidgetId, aPosition) {
+ let currentWidgetAtPosition = gPanelPlacements[aPosition + 1];
+ let rv = gWideWidgets.has(currentWidgetAtPosition) && !gWideWidgets.has(aWidgetId);
+ // We might now think we can move forward, but for that we need at least 2 more small
+ // widgets to be present:
+ if (rv) {
+ let furtherWidgets = gPanelPlacements.slice(aPosition + 2);
+ let realWidgets = 0;
+ if (furtherWidgets.length >= 2) {
+ while (furtherWidgets.length && realWidgets < 2) {
+ let w = furtherWidgets.shift();
+ if (!gWideWidgets.has(w) && this.checkWidgetStatus(w)) {
+ realWidgets++;
+ }
+ }
+ }
+ if (realWidgets < 2) {
+ rv = false;
+ }
+ }
+ return rv;
+ },
+ adjustWidgets: function(aWidgetId, aMoveForwards) {
+ if (this.adjusting) {
+ return;
+ }
+ this.adjusting = true;
+ let widgetsAffected = gPanelPlacements.filter((w) => gWideWidgets.has(w));
+ // If we're moving the wide widgets forwards (down/to the right in the panel)
+ // we want to start with the last widgets. Otherwise we move widgets over other wide
+ // widgets, which might mess up their order. Likewise, if moving backwards we should start with
+ // the first widget and work our way down/right from there.
+ let compareFn = aMoveForwards ? ((a, b) => a < b) : ((a, b) => a > b);
+ widgetsAffected.sort((a, b) => compareFn(gPanelPlacements.indexOf(a),
+ gPanelPlacements.indexOf(b)));
+ for (let widget of widgetsAffected) {
+ this.adjustPosition(widget, aMoveForwards);
+ }
+ this.adjusting = false;
+ },
+ // This function is called whenever an item gets moved in the menu panel. It
+ // adjusts the position of widgets within the panel to prevent "gaps" between
+ // wide widgets that could be filled up with single column widgets
+ adjustPosition: function(aWidgetId, aMoveForwards) {
+ // Make sure that there are n % columns = 0 narrow buttons before the widget.
+ let placementIndex = gPanelPlacements.indexOf(aWidgetId);
+ let prevSiblingCount = 0;
+ let fixedPos = null;
+ while (placementIndex--) {
+ let thisWidgetId = gPanelPlacements[placementIndex];
+ if (gWideWidgets.has(thisWidgetId)) {
+ continue;
+ }
+ let widgetStatus = this.checkWidgetStatus(thisWidgetId);
+ if (!widgetStatus) {
+ continue;
+ }
+ if (widgetStatus == "public-only") {
+ fixedPos = !fixedPos ? placementIndex : Math.min(fixedPos, placementIndex);
+ prevSiblingCount = 0;
+ } else {
+ prevSiblingCount++;
+ }
+ }
+
+ if (fixedPos !== null || prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT) {
+ let desiredPos = (fixedPos !== null) ? fixedPos : gPanelPlacements.indexOf(aWidgetId);
+ let desiredChange = -(prevSiblingCount % CustomizableUI.PANEL_COLUMN_COUNT);
+ if (aMoveForwards && fixedPos == null) {
+ // +1 because otherwise we'd count ourselves:
+ desiredChange = CustomizableUI.PANEL_COLUMN_COUNT + desiredChange + 1;
+ }
+ desiredPos += desiredChange;
+ CustomizableUI.moveWidgetWithinArea(aWidgetId, desiredPos);
+ }
+ },
+
+ /*
+ * Check whether a widget id is actually known anywhere.
+ * @returns false if the widget doesn't exist,
+ * "public-only" if it's not shown in private windows
+ * "real" if it does exist and is shown even in private windows
+ */
+ checkWidgetStatus: function(aWidgetId) {
+ let widgetWrapper = CustomizableUI.getWidget(aWidgetId);
+ // This widget might not actually exist:
+ if (!widgetWrapper) {
+ return false;
+ }
+ // This widget might still not actually exist:
+ if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL &&
+ widgetWrapper.instances.length == 0) {
+ return false;
+ }
+
+ // Or it might only be there some of the time:
+ if (widgetWrapper.provider == CustomizableUI.PROVIDER_API &&
+ widgetWrapper.showInPrivateBrowsing === false) {
+ return "public-only";
+ }
+ return "real";
+ },
+
+ init: function() {
+ // Initialize our local placements copy and register the listener
+ gPanelPlacements = CustomizableUI.getWidgetIdsInArea(gPanel);
+ CustomizableUI.addListener(this);
+ },
+};
diff --git a/browser/components/customizableui/ScrollbarSampler.jsm b/browser/components/customizableui/ScrollbarSampler.jsm
new file mode 100644
index 000000000..44736e4c4
--- /dev/null
+++ b/browser/components/customizableui/ScrollbarSampler.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["ScrollbarSampler"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var gSystemScrollbarWidth = null;
+
+this.ScrollbarSampler = {
+ getSystemScrollbarWidth: function() {
+ if (gSystemScrollbarWidth !== null) {
+ return Promise.resolve(gSystemScrollbarWidth);
+ }
+
+ return new Promise(resolve => {
+ this._sampleSystemScrollbarWidth().then(function(systemScrollbarWidth) {
+ gSystemScrollbarWidth = systemScrollbarWidth;
+ resolve(gSystemScrollbarWidth);
+ });
+ });
+ },
+
+ resetSystemScrollbarWidth: function() {
+ gSystemScrollbarWidth = null;
+ },
+
+ _sampleSystemScrollbarWidth: function() {
+ let hwin = Services.appShell.hiddenDOMWindow;
+ let hdoc = hwin.document.documentElement;
+ let iframe = hwin.document.createElementNS("http://www.w3.org/1999/xhtml",
+ "html:iframe");
+ iframe.setAttribute("srcdoc", '<body style="overflow-y: scroll"></body>');
+ hdoc.appendChild(iframe);
+
+ let cwindow = iframe.contentWindow;
+ let utils = cwindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ return new Promise(resolve => {
+ cwindow.addEventListener("load", function onLoad(aEvent) {
+ cwindow.removeEventListener("load", onLoad);
+ let sbWidth = {};
+ try {
+ utils.getScrollbarSize(true, sbWidth, {});
+ } catch (e) {
+ Cu.reportError("Could not sample scrollbar size: " + e + " -- " +
+ e.stack);
+ sbWidth.value = 0;
+ }
+ // Minimum width of 10 so that we have enough padding:
+ sbWidth.value = Math.max(sbWidth.value, 10);
+ resolve(sbWidth.value);
+ iframe.remove();
+ });
+ });
+ }
+};
+Object.freeze(this.ScrollbarSampler);
diff --git a/browser/components/customizableui/content/customizeMode.inc.xul b/browser/components/customizableui/content/customizeMode.inc.xul
new file mode 100644
index 000000000..b665630a2
--- /dev/null
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -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/. -->
+
+<hbox id="customization-container" flex="1" hidden="true">
+ <vbox flex="1" id="customization-palette-container">
+ <label id="customization-header">
+ &customizeMode.menuAndToolbars.header2;
+ </label>
+ <hbox id="customization-empty" hidden="true">
+ <label>&customizeMode.menuAndToolbars.empty;</label>
+ <label onclick="BrowserOpenAddonsMgr('addons://discover/');"
+ onkeypress="BrowserOpenAddonsMgr('addons://discover/');"
+ id="customization-more-tools"
+ class="text-link">
+ &customizeMode.menuAndToolbars.emptyLink;
+ </label>
+ </hbox>
+ <vbox id="customization-palette" class="customization-palette"/>
+ <spacer id="customization-spacer"/>
+ <hbox id="customization-footer">
+#ifdef CAN_DRAW_IN_TITLEBAR
+ <button id="customization-titlebar-visibility-button" class="customizationmode-button"
+ label="&customizeMode.titlebar;" type="checkbox"
+#NB: because oncommand fires after click, by the time we've fired, the checkbox binding
+# will already have switched the button's state, so this is correct:
+ oncommand="gCustomizeMode.toggleTitlebar(this.hasAttribute('checked'))"/>
+#endif
+ <button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars;" class="customizationmode-button" type="menu">
+ <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
+ </button>
+ <button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu">
+ <panel type="arrow" id="customization-lwtheme-menu"
+ onpopupshowing="gCustomizeMode.onLWThemesMenuShowing(event);"
+ position="topcenter bottomleft"
+ flip="none"
+ role="menu">
+ <label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/>
+ <label id="customization-lwtheme-menu-recommended" value="&customizeMode.lwthemes.recommended;"/>
+ <hbox id="customization-lwtheme-menu-footer">
+ <toolbarbutton class="customization-lwtheme-menu-footeritem"
+ label="&customizeMode.lwthemes.menuManage;"
+ accesskey="&customizeMode.lwthemes.menuManage.accessKey;"
+ tabindex="0"
+ oncommand="gCustomizeMode.openAddonsManagerThemes(event);"/>
+ <toolbarbutton class="customization-lwtheme-menu-footeritem"
+ label="&customizeMode.lwthemes.menuGetMore;"
+ accesskey="&customizeMode.lwthemes.menuGetMore.accessKey;"
+ tabindex="0"
+ oncommand="gCustomizeMode.getMoreThemes(event);"/>
+ </hbox>
+ </panel>
+ </button>
+
+ <spacer id="customization-footer-spacer"/>
+ <button id="customization-undo-reset-button"
+ class="customizationmode-button"
+ hidden="true"
+ oncommand="gCustomizeMode.undoReset();"
+ label="&undoCmd.label;"/>
+ <button id="customization-reset-button"
+ oncommand="gCustomizeMode.reset();"
+ label="&customizeMode.restoreDefaults;"
+ class="customizationmode-button"/>
+ </hbox>
+ </vbox>
+ <vbox id="customization-panel-container">
+ <vbox id="customization-panelWrapper">
+ <html:style html:type="text/html" scoped="scoped">
+ @import url(chrome://global/skin/popup.css);
+ </html:style>
+ <box class="panel-arrowbox">
+ <box flex="1"/>
+ <image class="panel-arrow" side="top"/>
+ </box>
+ <box class="panel-arrowcontent" side="top" flex="1">
+ <hbox id="customization-panelHolder"/>
+ <box class="panel-inner-arrowcontentfooter" hidden="true"/>
+ </box>
+ </vbox>
+ </vbox>
+</hbox>
diff --git a/browser/components/customizableui/content/jar.mn b/browser/components/customizableui/content/jar.mn
new file mode 100644
index 000000000..05c0112cd
--- /dev/null
+++ b/browser/components/customizableui/content/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/.
+
+browser.jar:
+ content/browser/customizableui/panelUI.css
+ content/browser/customizableui/panelUI.js
+ content/browser/customizableui/panelUI.xml
+ content/browser/customizableui/toolbar.xml
+
diff --git a/browser/components/customizableui/content/moz.build b/browser/components/customizableui/content/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/components/customizableui/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/components/customizableui/content/panelUI.css b/browser/components/customizableui/content/panelUI.css
new file mode 100644
index 000000000..ba44636f1
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.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/. */
+
+.panel-viewstack[viewtype="main"] > .panel-clickcapturer {
+ pointer-events: none;
+}
+
+.panel-mainview,
+.panel-viewcontainer,
+.panel-viewstack {
+ overflow: hidden;
+}
+
+.panel-viewstack {
+ position: relative;
+}
+
+.panel-subviews {
+ -moz-stack-sizing: ignore;
+ transform: translateX(0);
+ overflow-y: auto;
+}
+
+.panel-subviews[panelopen] {
+ transition: transform var(--panelui-subview-transition-duration);
+}
+
+.panel-viewcontainer[panelopen]:-moz-any(:not([viewtype="main"]),[transitioning="true"]) {
+ transition: height var(--panelui-subview-transition-duration);
+}
diff --git a/browser/components/customizableui/content/panelUI.inc.xul b/browser/components/customizableui/content/panelUI.inc.xul
new file mode 100644
index 000000000..1b8fc0236
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -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/. -->
+
+<panel id="PanelUI-popup"
+ role="group"
+ type="arrow"
+ hidden="true"
+ flip="slide"
+ position="bottomcenter topright"
+ noautofocus="true">
+ <panelmultiview id="PanelUI-multiView" mainViewId="PanelUI-mainView">
+ <panelview id="PanelUI-mainView" context="customizationPanelContextMenu">
+ <vbox id="PanelUI-contents-scroller">
+ <vbox id="PanelUI-contents" class="panelUI-grid"/>
+ </vbox>
+
+ <footer id="PanelUI-footer">
+ <toolbarbutton id="PanelUI-update-status"
+ oncommand="gMenuButtonUpdateBadge.onMenuPanelCommand(event);"
+ wrap="true"
+ hidden="true"/>
+ <hbox id="PanelUI-footer-fxa">
+ <hbox id="PanelUI-fxa-status"
+ defaultlabel="&fxaSignIn.label;"
+ signedinTooltiptext="&fxaSignedIn.tooltip;"
+ tooltiptext="&fxaSignedIn.tooltip;"
+ errorlabel="&fxaSignInError.label;"
+ unverifiedlabel="&fxaUnverified.label;"
+ onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
+ <image id="PanelUI-fxa-avatar"/>
+ <toolbarbutton id="PanelUI-fxa-label"
+ fxabrandname="&syncBrand.fxAccount.label;"/>
+ </hbox>
+ <toolbarseparator/>
+ <toolbarbutton id="PanelUI-fxa-icon"
+ oncommand="gSyncUI.doSync();"
+ closemenu="none">
+ <observes element="sync-status" attribute="syncstatus"/>
+ <observes element="sync-status" attribute="tooltiptext"/>
+ </toolbarbutton>
+ </hbox>
+
+ <hbox id="PanelUI-footer-inner">
+ <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
+ exitLabel="&appMenuCustomizeExit.label;"
+ tooltiptext="&appMenuCustomize.tooltip;"
+ exitTooltiptext="&appMenuCustomizeExit.tooltip;"
+ closemenu="none"
+ oncommand="gCustomizeMode.toggle();"/>
+ <toolbarseparator/>
+ <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
+ closemenu="none"
+ tooltiptext="&appMenuHelp.tooltip;"
+ oncommand="PanelUI.showHelpView(this);"/>
+ <toolbarseparator/>
+ <toolbarbutton id="PanelUI-quit"
+#ifdef XP_WIN
+ label="&quitApplicationCmdWin2.label;"
+ tooltiptext="&quitApplicationCmdWin2.tooltip;"
+#else
+#ifdef XP_MACOSX
+ label="&quitApplicationCmdMac2.label;"
+#else
+ label="&quitApplicationCmd.label;"
+#endif
+#endif
+ command="cmd_quitApplication"/>
+ </hbox>
+ </footer>
+ </panelview>
+
+ <panelview id="PanelUI-history" flex="1">
+ <label value="&appMenuHistory.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <toolbarbutton id="appMenuViewHistorySidebar"
+ label="&appMenuHistory.viewSidebar.label;"
+ type="checkbox"
+ class="subviewbutton"
+ key="key_gotoHistory"
+ oncommand="SidebarUI.toggle('viewHistorySidebar'); PanelUI.hide();">
+ <observes element="viewHistorySidebar" attribute="checked"/>
+ </toolbarbutton>
+ <toolbarbutton id="appMenuClearRecentHistory"
+ label="&appMenuHistory.clearRecent.label;"
+ class="subviewbutton"
+ command="Tools:Sanitize"/>
+ <toolbarbutton id="appMenuRestoreLastSession"
+ label="&appMenuHistory.restoreSession.label;"
+ class="subviewbutton"
+ command="Browser:RestoreLastSession"/>
+ <menuseparator id="PanelUI-recentlyClosedTabs-separator"/>
+ <vbox id="PanelUI-recentlyClosedTabs" tooltip="bhTooltip"/>
+ <menuseparator id="PanelUI-recentlyClosedWindows-separator"/>
+ <vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
+ <menuseparator id="PanelUI-historyItems-separator"/>
+ <vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
+ </vbox>
+ <toolbarbutton id="PanelUI-historyMore"
+ class="panel-subview-footer subviewbutton"
+ label="&appMenuHistory.showAll.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
+ </panelview>
+
+ <panelview id="PanelUI-remotetabs" flex="1" class="PanelUI-subView">
+ <label value="&appMenuRemoteTabs.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
+ <!-- When Sync is ready to sync -->
+ <vbox id="PanelUI-remotetabs-main" observes="sync-syncnow-state">
+ <vbox id="PanelUI-remotetabs-buttons">
+ <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
+ class="subviewbutton"
+ observes="viewTabsSidebar"
+ label="&appMenuRemoteTabs.sidebar.label;"/>
+ <toolbarbutton id="PanelUI-remotetabs-syncnow"
+ observes="sync-status"
+ class="subviewbutton"
+ oncommand="gSyncUI.doSync();"
+ closemenu="none"/>
+ <menuseparator id="PanelUI-remotetabs-separator"/>
+ </vbox>
+ <deck id="PanelUI-remotetabs-deck">
+ <!-- Sync is ready to Sync and the "tabs" engine is enabled -->
+ <vbox id="PanelUI-remotetabs-tabspane">
+ <vbox id="PanelUI-remotetabs-tabslist"
+ notabsforclientlabel="&appMenuRemoteTabs.notabs.label;"
+ />
+ </vbox>
+ <!-- Sync is ready to Sync but the "tabs" engine isn't enabled-->
+ <hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
+ <vbox class="PanelUI-remotetabs-instruction-box">
+ <hbox pack="center">
+ <image class="fxaSyncIllustration" alt=""/>
+ </hbox>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
+ <hbox pack="center">
+ <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ label="&appMenuRemoteTabs.openprefs.label;"
+ oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ <!-- Sync is ready to Sync but we are still fetching the tabs to show -->
+ <vbox id="PanelUI-remotetabs-fetching">
+ <!-- Show intentionally blank panel, see bug 1239845 -->
+ </vbox>
+ <!-- Sync has only 1 (ie, this) device connected -->
+ <hbox id="PanelUI-remotetabs-nodevicespane" pack="center" flex="1">
+ <vbox class="PanelUI-remotetabs-instruction-box">
+ <hbox pack="center">
+ <image class="fxaSyncIllustration" alt=""/>
+ </hbox>
+ <label class="PanelUI-remotetabs-instruction-title">&appMenuRemoteTabs.noclients.title;</label>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.noclients.subtitle;</label>
+ <!-- The inner HTML for PanelUI-remotetabs-mobile-promo is built at runtime -->
+ <label id="PanelUI-remotetabs-mobile-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"/>
+ </vbox>
+ </hbox>
+ </deck>
+ </vbox>
+ <!-- a box to ensure contained boxes are centered horizonally -->
+ <hbox pack="center" flex="1">
+ <!-- When Sync is not configured -->
+ <vbox id="PanelUI-remotetabs-setupsync"
+ flex="1"
+ align="center"
+ class="PanelUI-remotetabs-instruction-box"
+ observes="sync-setup-state">
+ <image class="fxaSyncIllustration" alt=""/>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
+ <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ label="&appMenuRemoteTabs.signin.label;"
+ oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
+ </vbox>
+ <!-- When Sync needs re-authentication. This uses the exact same messaging
+ as "Sync is not configured" but remains a separate box so we get
+ the goodness of observing broadcasters to manage the hidden states -->
+ <vbox id="PanelUI-remotetabs-reauthsync"
+ flex="1"
+ align="center"
+ class="PanelUI-remotetabs-instruction-box"
+ observes="sync-reauth-state">
+ <image class="fxaSyncIllustration" alt=""/>
+ <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
+ <toolbarbutton class="PanelUI-remotetabs-prefs-button"
+ label="&appMenuRemoteTabs.signin.label;"
+ oncommand="gSyncUI.openSetup(null, 'synced-tabs');"/>
+ </vbox>
+ </hbox>
+ </vbox>
+ </panelview>
+
+ <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
+ <label value="&bookmarksMenu.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <toolbarbutton id="panelMenuBookmarkThisPage"
+ class="subviewbutton"
+ observes="bookmarkThisPageBroadcaster"
+ command="Browser:AddBookmarkAs"
+ onclick="PanelUI.hide();"/>
+ <toolbarseparator/>
+ <toolbarbutton id="panelMenu_viewBookmarksSidebar"
+ label="&viewBookmarksSidebar2.label;"
+ class="subviewbutton"
+ key="viewBookmarksSidebarKb"
+ oncommand="SidebarUI.toggle('viewBookmarksSidebar'); PanelUI.hide();">
+ <observes element="viewBookmarksSidebar" attribute="checked"/>
+ </toolbarbutton>
+ <toolbarbutton id="panelMenu_viewBookmarksToolbar"
+ label="&viewBookmarksToolbar.label;"
+ type="checkbox"
+ toolbarId="PersonalToolbar"
+ class="subviewbutton"
+ oncommand="onViewToolbarCommand(event); PanelUI.hide();"/>
+ <toolbarseparator/>
+ <toolbarbutton id="panelMenu_bookmarksToolbar"
+ label="&personalbarCmd.label;"
+ class="subviewbutton cui-withicon"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('BookmarksToolbar'); PanelUI.hide();"/>
+ <toolbarbutton id="panelMenu_unsortedBookmarks"
+ label="&otherBookmarksCmd.label;"
+ class="subviewbutton cui-withicon"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks'); PanelUI.hide();"/>
+ <toolbarseparator class="small-separator"/>
+ <toolbaritem id="panelMenu_bookmarksMenu"
+ orient="vertical"
+ smoothscroll="false"
+ onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
+ oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
+ flatList="true"
+ tooltip="bhTooltip">
+ <!-- bookmarks menu items will go here -->
+ </toolbaritem>
+ </vbox>
+ <toolbarbutton id="panelMenu_showAllBookmarks"
+ label="&showAllBookmarks2.label;"
+ class="subviewbutton panel-subview-footer"
+ command="Browser:ShowAllBookmarks"
+ onclick="PanelUI.hide();"/>
+ </panelview>
+
+ <panelview id="PanelUI-socialapi" flex="1"/>
+
+ <panelview id="PanelUI-feeds" flex="1" oncommand="FeedHandler.subscribeToFeed(null, event);">
+ <label value="&feedsMenu2.label;" class="panel-subview-header"/>
+ </panelview>
+
+ <panelview id="PanelUI-containers" flex="1">
+ <label value="&containersMenu.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-containersItems"/>
+ </panelview>
+
+ <panelview id="PanelUI-helpView" flex="1" class="PanelUI-subView">
+ <label value="&helpMenu.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-helpItems" class="panel-subview-body"/>
+ </panelview>
+
+ <panelview id="PanelUI-developer" flex="1">
+ <label value="&webDeveloperMenu.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-developerItems" class="panel-subview-body"/>
+ </panelview>
+
+ <panelview id="PanelUI-sidebar" flex="1">
+ <label value="&appMenuSidebars.label;" class="panel-subview-header"/>
+ <vbox id="PanelUI-sidebarItems" class="panel-subview-body"/>
+ </panelview>
+
+ <panelview id="PanelUI-characterEncodingView" flex="1">
+ <label value="&charsetMenu2.label;" class="panel-subview-header"/>
+ <vbox class="panel-subview-body">
+ <vbox id="PanelUI-characterEncodingView-pinned"
+ class="PanelUI-characterEncodingView-list"/>
+ <toolbarseparator/>
+ <vbox id="PanelUI-characterEncodingView-charsets"
+ class="PanelUI-characterEncodingView-list"/>
+ <toolbarseparator/>
+ <vbox>
+ <label id="PanelUI-characterEncodingView-autodetect-label"/>
+ <vbox id="PanelUI-characterEncodingView-autodetect"
+ class="PanelUI-characterEncodingView-list"/>
+ </vbox>
+ </vbox>
+ </panelview>
+
+ <panelview id="PanelUI-panicView" flex="1">
+ <vbox class="panel-subview-body">
+ <hbox id="PanelUI-panic-timeframe">
+ <image id="PanelUI-panic-timeframe-icon" alt=""/>
+ <vbox flex="1">
+ <hbox id="PanelUI-panic-header">
+ <image id="PanelUI-panic-timeframe-icon-small" alt=""/>
+ <description id="PanelUI-panic-mainDesc" flex="1">&panicButton.view.mainTimeframeDesc;</description>
+ </hbox>
+ <radiogroup id="PanelUI-panic-timeSpan" aria-labelledby="PanelUI-panic-mainDesc" closemenu="none">
+ <radio id="PanelUI-panic-5min" label="&panicButton.view.5min;" selected="true"
+ value="5" class="subviewradio"/>
+ <radio id="PanelUI-panic-2hr" label="&panicButton.view.2hr;"
+ value="2" class="subviewradio"/>
+ <radio id="PanelUI-panic-day" label="&panicButton.view.day;"
+ value="6" class="subviewradio"/>
+ </radiogroup>
+ </vbox>
+ </hbox>
+ <vbox id="PanelUI-panic-explanations">
+ <label id="PanelUI-panic-actionlist-main-label">&panicButton.view.mainActionDesc;</label>
+
+ <label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
+ <label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist">&panicButton.view.deleteCookies;</label>
+ <label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist">&panicButton.view.deleteHistory;</label>
+ <label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist">&panicButton.view.openNewWindow;</label>
+
+ <label id="PanelUI-panic-warning">&panicButton.view.undoWarning;</label>
+ </vbox>
+ <button id="PanelUI-panic-view-button"
+ label="&panicButton.view.forgetButton;"/>
+ </vbox>
+ </panelview>
+
+ </panelmultiview>
+ <!-- These menupopups are located here to prevent flickering,
+ see bug 492960 comment 20. -->
+ <menupopup id="customizationPanelItemContextMenu">
+ <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
+ closemenu="single"
+ class="customize-context-moveToToolbar"
+ accesskey="&customizeMenu.moveToToolbar.accesskey;"
+ label="&customizeMenu.moveToToolbar.label;"/>
+ <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
+ closemenu="single"
+ class="customize-context-removeFromPanel"
+ accesskey="&customizeMenu.removeFromMenu.accesskey;"
+ label="&customizeMenu.removeFromMenu.label;"/>
+ <menuseparator/>
+ <menuitem command="cmd_CustomizeToolbars"
+ class="viewCustomizeToolbar"
+ accesskey="&viewCustomizeToolbar.accesskey;"
+ label="&viewCustomizeToolbar.label;"/>
+ </menupopup>
+
+ <menupopup id="customizationPaletteItemContextMenu">
+ <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
+ class="customize-context-addToToolbar"
+ accesskey="&customizeMenu.addToToolbar.accesskey;"
+ label="&customizeMenu.addToToolbar.label;"/>
+ <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
+ class="customize-context-addToPanel"
+ accesskey="&customizeMenu.addToPanel.accesskey;"
+ label="&customizeMenu.addToPanel.label;"/>
+ </menupopup>
+
+ <menupopup id="customizationPanelContextMenu">
+ <menuitem command="cmd_CustomizeToolbars"
+ accesskey="&customizeMenu.addMoreItems.accesskey;"
+ label="&customizeMenu.addMoreItems.label;"/>
+ </menupopup>
+</panel>
+
+<panel id="widget-overflow"
+ role="group"
+ type="arrow"
+ noautofocus="true"
+ context="toolbar-context-menu"
+ position="bottomcenter topright"
+ hidden="true">
+ <vbox id="widget-overflow-scroller">
+ <vbox id="widget-overflow-list" class="widget-overflow-list"
+ overflowfortoolbar="nav-bar"/>
+ </vbox>
+</panel>
+
+<panel id="customization-tipPanel"
+ type="arrow"
+ flip="none"
+ side="left"
+ position="leftcenter topright"
+ noautohide="true"
+ hidden="true">
+ <hbox class="customization-tipPanel-wrapper">
+ <vbox class="customization-tipPanel-infoBox"/>
+ <vbox class="customization-tipPanel-content" flex="1">
+ <description class="customization-tipPanel-contentMessage"/>
+ <image class="customization-tipPanel-contentImage"/>
+ </vbox>
+ <vbox pack="start" align="end" class="customization-tipPanel-closeBox">
+ <toolbarbutton oncommand="gCustomizeMode.hideTip()" class="close-icon"/>
+ </vbox>
+ </hbox>
+</panel>
+
+<panel id="panic-button-success-notification"
+ type="arrow"
+ position="bottomcenter topright"
+ hidden="true"
+ role="alert"
+ orient="vertical">
+ <hbox id="panic-button-success-header">
+ <image id="panic-button-success-icon" alt=""/>
+ <vbox>
+ <description>&panicButton.thankyou.msg1;</description>
+ <description>&panicButton.thankyou.msg2;</description>
+ </vbox>
+ </hbox>
+ <button label="&panicButton.thankyou.buttonlabel;"
+ id="panic-button-success-closebutton"
+ oncommand="PanicButtonNotifier.close()"/>
+</panel>
diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js
new file mode 100644
index 000000000..66fa0c184
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.js
@@ -0,0 +1,558 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
+ "resource:///modules/ScrollbarSampler.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
+ "resource://gre/modules/ShortcutUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+/**
+ * Maintains the state and dispatches events for the main menu panel.
+ */
+
+const PanelUI = {
+ /** Panel events that we listen for. **/
+ get kEvents() {
+ return ["popupshowing", "popupshown", "popuphiding", "popuphidden"];
+ },
+ /**
+ * Used for lazily getting and memoizing elements from the document. Lazy
+ * getters are set in init, and memoizing happens after the first retrieval.
+ */
+ get kElements() {
+ return {
+ contents: "PanelUI-contents",
+ mainView: "PanelUI-mainView",
+ multiView: "PanelUI-multiView",
+ helpView: "PanelUI-helpView",
+ menuButton: "PanelUI-menu-button",
+ panel: "PanelUI-popup",
+ scroller: "PanelUI-contents-scroller"
+ };
+ },
+
+ _initialized: false,
+ init: function() {
+ for (let [k, v] of Object.entries(this.kElements)) {
+ // Need to do fresh let-bindings per iteration
+ let getKey = k;
+ let id = v;
+ this.__defineGetter__(getKey, function() {
+ delete this[getKey];
+ return this[getKey] = document.getElementById(id);
+ });
+ }
+
+ this.menuButton.addEventListener("mousedown", this);
+ this.menuButton.addEventListener("keypress", this);
+ this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
+ window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
+ CustomizableUI.addListener(this);
+ this._initialized = true;
+ },
+
+ _eventListenersAdded: false,
+ _ensureEventListenersAdded: function() {
+ if (this._eventListenersAdded)
+ return;
+ this._addEventListeners();
+ },
+
+ _addEventListeners: function() {
+ for (let event of this.kEvents) {
+ this.panel.addEventListener(event, this);
+ }
+
+ this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false);
+ this._eventListenersAdded = true;
+ },
+
+ uninit: function() {
+ for (let event of this.kEvents) {
+ this.panel.removeEventListener(event, this);
+ }
+ this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
+ this.menuButton.removeEventListener("mousedown", this);
+ this.menuButton.removeEventListener("keypress", this);
+ window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
+ CustomizableUI.removeListener(this);
+ this._overlayScrollListenerBoundFn = null;
+ },
+
+ /**
+ * Customize mode extracts the mainView and puts it somewhere else while the
+ * user customizes. Upon completion, this function can be called to put the
+ * panel back to where it belongs in normal browsing mode.
+ *
+ * @param aMainView
+ * The mainView node to put back into place.
+ */
+ setMainView: function(aMainView) {
+ this._ensureEventListenersAdded();
+ this.multiView.setMainView(aMainView);
+ },
+
+ /**
+ * Opens the menu panel if it's closed, or closes it if it's
+ * open.
+ *
+ * @param aEvent the event that triggers the toggle.
+ */
+ toggle: function(aEvent) {
+ // Don't show the panel if the window is in customization mode,
+ // since this button doubles as an exit path for the user in this case.
+ if (document.documentElement.hasAttribute("customizing")) {
+ return;
+ }
+ this._ensureEventListenersAdded();
+ if (this.panel.state == "open") {
+ this.hide();
+ } else if (this.panel.state == "closed") {
+ this.show(aEvent);
+ }
+ },
+
+ /**
+ * Opens the menu panel. If the event target has a child with the
+ * toolbarbutton-icon attribute, the panel will be anchored on that child.
+ * Otherwise, the panel is anchored on the event target itself.
+ *
+ * @param aEvent the event (if any) that triggers showing the menu.
+ */
+ show: function(aEvent) {
+ return new Promise(resolve => {
+ this.ensureReady().then(() => {
+ if (this.panel.state == "open" ||
+ document.documentElement.hasAttribute("customizing")) {
+ resolve();
+ return;
+ }
+
+ let editControlPlacement = CustomizableUI.getPlacementOfWidget("edit-controls");
+ if (editControlPlacement && editControlPlacement.area == CustomizableUI.AREA_PANEL) {
+ updateEditUIVisibility();
+ }
+
+ let personalBookmarksPlacement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
+ if (personalBookmarksPlacement &&
+ personalBookmarksPlacement.area == CustomizableUI.AREA_PANEL) {
+ PlacesToolbarHelper.customizeChange();
+ }
+
+ let anchor;
+ if (!aEvent ||
+ aEvent.type == "command") {
+ anchor = this.menuButton;
+ } else {
+ anchor = aEvent.target;
+ }
+
+ this.panel.addEventListener("popupshown", function onPopupShown() {
+ this.removeEventListener("popupshown", onPopupShown);
+ resolve();
+ });
+
+ let iconAnchor =
+ document.getAnonymousElementByAttribute(anchor, "class",
+ "toolbarbutton-icon");
+ this.panel.openPopup(iconAnchor || anchor);
+ }, (reason) => {
+ console.error("Error showing the PanelUI menu", reason);
+ });
+ });
+ },
+
+ /**
+ * If the menu panel is being shown, hide it.
+ */
+ hide: function() {
+ if (document.documentElement.hasAttribute("customizing")) {
+ return;
+ }
+
+ this.panel.hidePopup();
+ },
+
+ handleEvent: function(aEvent) {
+ // Ignore context menus and menu button menus showing and hiding:
+ if (aEvent.type.startsWith("popup") &&
+ aEvent.target != this.panel) {
+ return;
+ }
+ switch (aEvent.type) {
+ case "popupshowing":
+ this._adjustLabelsForAutoHyphens();
+ // Fall through
+ case "popupshown":
+ // Fall through
+ case "popuphiding":
+ // Fall through
+ case "popuphidden":
+ this._updatePanelButton(aEvent.target);
+ break;
+ case "mousedown":
+ if (aEvent.button == 0)
+ this.toggle(aEvent);
+ break;
+ case "keypress":
+ this.toggle(aEvent);
+ break;
+ }
+ },
+
+ get isReady() {
+ return !!this._isReady;
+ },
+
+ /**
+ * Registering the menu panel is done lazily for performance reasons. This
+ * method is exposed so that CustomizationMode can force panel-readyness in the
+ * event that customization mode is started before the panel has been opened
+ * by the user.
+ *
+ * @param aCustomizing (optional) set to true if this was called while entering
+ * customization mode. If that's the case, we trust that customization
+ * mode will handle calling beginBatchUpdate and endBatchUpdate.
+ *
+ * @return a Promise that resolves once the panel is ready to roll.
+ */
+ ensureReady: function(aCustomizing=false) {
+ if (this._readyPromise) {
+ return this._readyPromise;
+ }
+ this._readyPromise = Task.spawn(function*() {
+ if (!this._initialized) {
+ yield new Promise(resolve => {
+ let delayedStartupObserver = (aSubject, aTopic, aData) => {
+ if (aSubject == window) {
+ Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
+ resolve();
+ }
+ };
+ Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
+ });
+ }
+
+ this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
+ getLocale());
+ if (!this._scrollWidth) {
+ // In order to properly center the contents of the panel, while ensuring
+ // that we have enough space on either side to show a scrollbar, we have to
+ // do a bit of hackery. In particular, we calculate a new width for the
+ // scroller, based on the system scrollbar width.
+ this._scrollWidth =
+ (yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
+ let cstyle = window.getComputedStyle(this.scroller);
+ let widthStr = cstyle.width;
+ // Get the calculated padding on the left and right sides of
+ // the scroller too. We'll use that in our final calculation so
+ // that if a scrollbar appears, we don't have the contents right
+ // up against the edge of the scroller.
+ let paddingLeft = cstyle.paddingLeft;
+ let paddingRight = cstyle.paddingRight;
+ let calcStr = [widthStr, this._scrollWidth,
+ paddingLeft, paddingRight].join(" + ");
+ this.scroller.style.width = "calc(" + calcStr + ")";
+ }
+
+ if (aCustomizing) {
+ CustomizableUI.registerMenuPanel(this.contents);
+ } else {
+ this.beginBatchUpdate();
+ try {
+ CustomizableUI.registerMenuPanel(this.contents);
+ } finally {
+ this.endBatchUpdate();
+ }
+ }
+ this._updateQuitTooltip();
+ this.panel.hidden = false;
+ this._isReady = true;
+ }.bind(this)).then(null, Cu.reportError);
+
+ return this._readyPromise;
+ },
+
+ /**
+ * Switch the panel to the main view if it's not already
+ * in that view.
+ */
+ showMainView: function() {
+ this._ensureEventListenersAdded();
+ this.multiView.showMainView();
+ },
+
+ /**
+ * Switch the panel to the help view if it's not already
+ * in that view.
+ */
+ showHelpView: function(aAnchor) {
+ this._ensureEventListenersAdded();
+ this.multiView.showSubView("PanelUI-helpView", aAnchor);
+ },
+
+ /**
+ * Shows a subview in the panel with a given ID.
+ *
+ * @param aViewId the ID of the subview to show.
+ * @param aAnchor the element that spawned the subview.
+ * @param aPlacementArea the CustomizableUI area that aAnchor is in.
+ */
+ showSubView: Task.async(function*(aViewId, aAnchor, aPlacementArea) {
+ this._ensureEventListenersAdded();
+ let viewNode = document.getElementById(aViewId);
+ if (!viewNode) {
+ Cu.reportError("Could not show panel subview with id: " + aViewId);
+ return;
+ }
+
+ if (!aAnchor) {
+ Cu.reportError("Expected an anchor when opening subview with id: " + aViewId);
+ return;
+ }
+
+ if (aPlacementArea == CustomizableUI.AREA_PANEL) {
+ this.multiView.showSubView(aViewId, aAnchor);
+ } else if (!aAnchor.open) {
+ aAnchor.open = true;
+
+ let tempPanel = document.createElement("panel");
+ tempPanel.setAttribute("type", "arrow");
+ tempPanel.setAttribute("id", "customizationui-widget-panel");
+ tempPanel.setAttribute("class", "cui-widget-panel");
+ tempPanel.setAttribute("viewId", aViewId);
+ if (aAnchor.getAttribute("tabspecific")) {
+ tempPanel.setAttribute("tabspecific", true);
+ }
+ if (this._disableAnimations) {
+ tempPanel.setAttribute("animate", "false");
+ }
+ tempPanel.setAttribute("context", "");
+ document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
+ // If the view has a footer, set a convenience class on the panel.
+ tempPanel.classList.toggle("cui-widget-panelWithFooter",
+ viewNode.querySelector(".panel-subview-footer"));
+
+ let multiView = document.createElement("panelmultiview");
+ multiView.setAttribute("id", "customizationui-widget-multiview");
+ multiView.setAttribute("nosubviews", "true");
+ tempPanel.appendChild(multiView);
+ multiView.setAttribute("mainViewIsSubView", "true");
+ multiView.setMainView(viewNode);
+ viewNode.classList.add("cui-widget-panelview");
+
+ let viewShown = false;
+ let panelRemover = () => {
+ viewNode.classList.remove("cui-widget-panelview");
+ if (viewShown) {
+ CustomizableUI.removePanelCloseListeners(tempPanel);
+ tempPanel.removeEventListener("popuphidden", panelRemover);
+
+ let evt = new CustomEvent("ViewHiding", {detail: viewNode});
+ viewNode.dispatchEvent(evt);
+ }
+ aAnchor.open = false;
+
+ this.multiView.appendChild(viewNode);
+ tempPanel.remove();
+ };
+
+ // Emit the ViewShowing event so that the widget definition has a chance
+ // to lazily populate the subview with things.
+ let detail = {
+ blockers: new Set(),
+ addBlocker(aPromise) {
+ this.blockers.add(aPromise);
+ },
+ };
+
+ let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
+ viewNode.dispatchEvent(evt);
+
+ let cancel = evt.defaultPrevented;
+ if (detail.blockers.size) {
+ try {
+ let results = yield Promise.all(detail.blockers);
+ cancel = cancel || results.some(val => val === false);
+ } catch (e) {
+ Components.utils.reportError(e);
+ cancel = true;
+ }
+ }
+
+ if (cancel) {
+ panelRemover();
+ return;
+ }
+
+ viewShown = true;
+ CustomizableUI.addPanelCloseListeners(tempPanel);
+ tempPanel.addEventListener("popuphidden", panelRemover);
+
+ let iconAnchor =
+ document.getAnonymousElementByAttribute(aAnchor, "class",
+ "toolbarbutton-icon");
+
+ if (iconAnchor && aAnchor.id) {
+ iconAnchor.setAttribute("consumeanchor", aAnchor.id);
+ }
+ tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright");
+ }
+ }),
+
+ /**
+ * NB: The enable- and disableSingleSubviewPanelAnimations methods only
+ * affect the hiding/showing animations of single-subview panels (tempPanel
+ * in the showSubView method).
+ */
+ disableSingleSubviewPanelAnimations: function() {
+ this._disableAnimations = true;
+ },
+
+ enableSingleSubviewPanelAnimations: function() {
+ this._disableAnimations = false;
+ },
+
+ onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
+ if (aContainer != this.contents) {
+ return;
+ }
+ if (aWasRemoval) {
+ aNode.removeAttribute("auto-hyphens");
+ }
+ },
+
+ onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, aIsRemoval) {
+ if (aContainer != this.contents) {
+ return;
+ }
+ if (!aIsRemoval &&
+ (this.panel.state == "open" ||
+ document.documentElement.hasAttribute("customizing"))) {
+ this._adjustLabelsForAutoHyphens(aNode);
+ }
+ },
+
+ /**
+ * Signal that we're about to make a lot of changes to the contents of the
+ * panels all at once. For performance, we ignore the mutations.
+ */
+ beginBatchUpdate: function() {
+ this._ensureEventListenersAdded();
+ this.multiView.ignoreMutations = true;
+ },
+
+ /**
+ * Signal that we're done making bulk changes to the panel. We now pay
+ * attention to mutations. This automatically synchronizes the multiview
+ * container with whichever view is displayed if the panel is open.
+ */
+ endBatchUpdate: function(aReason) {
+ this._ensureEventListenersAdded();
+ this.multiView.ignoreMutations = false;
+ },
+
+ _adjustLabelsForAutoHyphens: function(aNode) {
+ let toolbarButtons = aNode ? [aNode] :
+ this.contents.querySelectorAll(".toolbarbutton-1");
+ for (let node of toolbarButtons) {
+ let label = node.getAttribute("label");
+ if (!label) {
+ continue;
+ }
+ if (label.includes("\u00ad")) {
+ node.setAttribute("auto-hyphens", "off");
+ } else {
+ node.removeAttribute("auto-hyphens");
+ }
+ }
+ },
+
+ /**
+ * Sets the anchor node into the open or closed state, depending
+ * on the state of the panel.
+ */
+ _updatePanelButton: function() {
+ this.menuButton.open = this.panel.state == "open" ||
+ this.panel.state == "showing";
+ },
+
+ _onHelpViewShow: function(aEvent) {
+ // Call global menu setup function
+ buildHelpMenu();
+
+ let helpMenu = document.getElementById("menu_HelpPopup");
+ let items = this.getElementsByTagName("vbox")[0];
+ let attrs = ["oncommand", "onclick", "label", "key", "disabled"];
+ let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ // Remove all buttons from the view
+ while (items.firstChild) {
+ items.removeChild(items.firstChild);
+ }
+
+ // Add the current set of menuitems of the Help menu to this view
+ let menuItems = Array.prototype.slice.call(helpMenu.getElementsByTagName("menuitem"));
+ let fragment = document.createDocumentFragment();
+ for (let node of menuItems) {
+ if (node.hidden)
+ continue;
+ let button = document.createElementNS(NSXUL, "toolbarbutton");
+ // Copy specific attributes from a menuitem of the Help menu
+ for (let attrName of attrs) {
+ if (!node.hasAttribute(attrName))
+ continue;
+ button.setAttribute(attrName, node.getAttribute(attrName));
+ }
+ button.setAttribute("class", "subviewbutton");
+ fragment.appendChild(button);
+ }
+ items.appendChild(fragment);
+ },
+
+ _updateQuitTooltip: function() {
+ if (AppConstants.platform == "win") {
+ return;
+ }
+
+ let tooltipId = AppConstants.platform == "macosx" ?
+ "quit-button.tooltiptext.mac" :
+ "quit-button.tooltiptext.linux2";
+
+ let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ let stringArgs = [brands.GetStringFromName("brandShortName")];
+
+ let key = document.getElementById("key_quitApplication");
+ stringArgs.push(ShortcutUtils.prettifyShortcut(key));
+ let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
+ let quitButton = document.getElementById("PanelUI-quit");
+ quitButton.setAttribute("tooltiptext", tooltipString);
+ },
+
+ _overlayScrollListenerBoundFn: null,
+ _overlayScrollListener: function(aMQL) {
+ ScrollbarSampler.resetSystemScrollbarWidth();
+ this._scrollWidth = null;
+ },
+};
+
+XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
+
+/**
+ * Gets the currently selected locale for display.
+ * @return the selected locale or "en-US" if none is selected
+ */
+function getLocale() {
+ try {
+ let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry);
+ return chromeRegistry.getSelectedLocale("browser");
+ } catch (ex) {
+ return "en-US";
+ }
+}
diff --git a/browser/components/customizableui/content/panelUI.xml b/browser/components/customizableui/content/panelUI.xml
new file mode 100644
index 000000000..6893bd8ff
--- /dev/null
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -0,0 +1,509 @@
+<?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="browserPanelUIBindings"
+ 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="panelmultiview">
+ <resources>
+ <stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
+ </resources>
+ <content>
+ <xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
+ <xul:stack anonid="viewStack" xbl:inherits="viewtype,transitioning" viewtype="main" class="panel-viewstack">
+ <xul:vbox anonid="mainViewContainer" class="panel-mainview" xbl:inherits="viewtype"/>
+
+ <!-- Used to capture click events over the PanelUI-mainView if we're in
+ subview mode. That way, any click on the PanelUI-mainView causes us
+ to revert to the mainView mode, whereupon PanelUI-click-capture then
+ allows click events to go through it. -->
+ <xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
+
+ <!-- We manually set display: none (via a CSS attribute selector) on the
+ subviews that are not being displayed. We're using this over a deck
+ because a deck assumes the size of its largest child, regardless of
+ whether or not it is shown. That's not good for our case, since we
+ want to allow each subview to be uniquely sized. -->
+ <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
+ <children includes="panelview"/>
+ </xul:vbox>
+ </xul:stack>
+ </xul:box>
+ </content>
+ <implementation implements="nsIDOMEventListener">
+ <field name="_clickCapturer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
+ </field>
+ <field name="_viewContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
+ </field>
+ <field name="_mainViewContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
+ </field>
+ <field name="_subViews" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "subViews");
+ </field>
+ <field name="_viewStack" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
+ </field>
+ <field name="_panel" readonly="true">
+ this.parentNode;
+ </field>
+
+ <field name="_currentSubView">null</field>
+ <field name="_anchorElement">null</field>
+ <field name="_mainViewHeight">0</field>
+ <field name="_subViewObserver">null</field>
+ <field name="__transitioning">false</field>
+ <field name="_ignoreMutations">false</field>
+
+ <property name="showingSubView" readonly="true"
+ onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
+ <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
+ <property name="_mainView" readonly="true"
+ onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
+ <property name="showingSubViewAsMainView" readonly="true"
+ onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
+
+ <property name="ignoreMutations">
+ <getter>
+ return this._ignoreMutations;
+ </getter>
+ <setter><![CDATA[
+ this._ignoreMutations = val;
+ if (!val && this._panel.state == "open") {
+ if (this.showingSubView) {
+ this._syncContainerWithSubView();
+ } else {
+ this._syncContainerWithMainView();
+ }
+ }
+ ]]></setter>
+ </property>
+
+ <property name="_transitioning">
+ <getter>
+ return this.__transitioning;
+ </getter>
+ <setter><![CDATA[
+ this.__transitioning = val;
+ if (val) {
+ this.setAttribute("transitioning", "true");
+ } else {
+ this.removeAttribute("transitioning");
+ }
+ ]]></setter>
+ </property>
+ <constructor><![CDATA[
+ this._clickCapturer.addEventListener("click", this);
+ this._panel.addEventListener("popupshowing", this);
+ this._panel.addEventListener("popupshown", this);
+ this._panel.addEventListener("popuphidden", this);
+ this._subViews.addEventListener("overflow", this);
+ this._mainViewContainer.addEventListener("overflow", this);
+
+ // Get a MutationObserver ready to react to subview size changes. We
+ // only attach this MutationObserver when a subview is being displayed.
+ this._subViewObserver =
+ new MutationObserver(this._syncContainerWithSubView.bind(this));
+ this._mainViewObserver =
+ new MutationObserver(this._syncContainerWithMainView.bind(this));
+
+ this._mainViewContainer.setAttribute("panelid",
+ this._panel.id);
+
+ if (this._mainView) {
+ this.setMainView(this._mainView);
+ }
+ this.setAttribute("viewtype", "main");
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ if (this._mainView) {
+ this._mainView.removeAttribute("mainview");
+ }
+ this._mainViewObserver.disconnect();
+ this._subViewObserver.disconnect();
+ this._panel.removeEventListener("popupshowing", this);
+ this._panel.removeEventListener("popupshown", this);
+ this._panel.removeEventListener("popuphidden", this);
+ this._subViews.removeEventListener("overflow", this);
+ this._mainViewContainer.removeEventListener("overflow", this);
+ this._clickCapturer.removeEventListener("click", this);
+ ]]></destructor>
+
+ <method name="setMainView">
+ <parameter name="aNewMainView"/>
+ <body><![CDATA[
+ if (this._mainView) {
+ this._mainViewObserver.disconnect();
+ this._subViews.appendChild(this._mainView);
+ this._mainView.removeAttribute("mainview");
+ }
+ this._mainViewId = aNewMainView.id;
+ aNewMainView.setAttribute("mainview", "true");
+ this._mainViewContainer.appendChild(aNewMainView);
+ ]]></body>
+ </method>
+
+ <method name="showMainView">
+ <body><![CDATA[
+ if (this.showingSubView) {
+ let viewNode = this._currentSubView;
+ let evt = document.createEvent("CustomEvent");
+ evt.initCustomEvent("ViewHiding", true, true, viewNode);
+ viewNode.dispatchEvent(evt);
+
+ viewNode.removeAttribute("current");
+ this._currentSubView = null;
+
+ this._subViewObserver.disconnect();
+
+ this._setViewContainerHeight(this._mainViewHeight);
+
+ this.setAttribute("viewtype", "main");
+ }
+
+ this._shiftMainView();
+ ]]></body>
+ </method>
+
+ <method name="showSubView">
+ <parameter name="aViewId"/>
+ <parameter name="aAnchor"/>
+ <body><![CDATA[
+ Task.spawn(function*() {
+ let viewNode = this.querySelector("#" + aViewId);
+ viewNode.setAttribute("current", true);
+ // Emit the ViewShowing event so that the widget definition has a chance
+ // to lazily populate the subview with things.
+ let detail = {
+ blockers: new Set(),
+ addBlocker(aPromise) {
+ this.blockers.add(aPromise);
+ },
+ };
+
+ let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
+ viewNode.dispatchEvent(evt);
+
+ let cancel = evt.defaultPrevented;
+ if (detail.blockers.size) {
+ try {
+ let results = yield Promise.all(detail.blockers);
+ cancel = cancel || results.some(val => val === false);
+ } catch (e) {
+ Components.utils.reportError(e);
+ cancel = true;
+ }
+ }
+
+ if (cancel) {
+ return;
+ }
+
+ this._currentSubView = viewNode;
+
+ // Now we have to transition the panel. There are a few parts to this:
+ //
+ // 1) The main view content gets shifted so that the center of the anchor
+ // node is at the left-most edge of the panel.
+ // 2) The subview deck slides in so that it takes up almost all of the
+ // panel.
+ // 3) If the subview is taller then the main panel contents, then the panel
+ // must grow to meet that new height. Otherwise, it must shrink.
+ //
+ // All three of these actions make use of CSS transformations, so they
+ // should all occur simultaneously.
+ this.setAttribute("viewtype", "subview");
+ this._shiftMainView(aAnchor);
+
+ this._mainViewHeight = this._viewStack.clientHeight;
+
+ let newHeight = this._heightOfSubview(viewNode, this._subViews);
+ this._setViewContainerHeight(newHeight);
+
+ this._subViewObserver.observe(viewNode, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true
+ });
+ }.bind(this));
+ ]]></body>
+ </method>
+
+ <method name="_setViewContainerHeight">
+ <parameter name="aHeight"/>
+ <body><![CDATA[
+ let container = this._viewContainer;
+ this._transitioning = true;
+
+ let onTransitionEnd = () => {
+ container.removeEventListener("transitionend", onTransitionEnd);
+ this._transitioning = false;
+ };
+
+ container.addEventListener("transitionend", onTransitionEnd);
+ container.style.height = `${aHeight}px`;
+ ]]></body>
+ </method>
+
+ <method name="_shiftMainView">
+ <parameter name="aAnchor"/>
+ <body><![CDATA[
+ if (aAnchor) {
+ // We need to find the edge of the anchor, relative to the main panel.
+ // Then we need to add half the width of the anchor. This is the target
+ // that we need to transition to.
+ let anchorRect = aAnchor.getBoundingClientRect();
+ let mainViewRect = this._mainViewContainer.getBoundingClientRect();
+ let center = aAnchor.clientWidth / 2;
+ let direction = aAnchor.ownerDocument.defaultView.getComputedStyle(aAnchor, null).direction;
+ let edge;
+ if (direction == "ltr") {
+ edge = anchorRect.left - mainViewRect.left;
+ } else {
+ edge = mainViewRect.right - anchorRect.right;
+ }
+
+ // If the anchor is an element on the far end of the mainView we
+ // don't want to shift the mainView too far, we would reveal empty
+ // space otherwise.
+ let cstyle = window.getComputedStyle(document.documentElement, null);
+ let exitSubViewGutterWidth =
+ cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
+ let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
+ let target = Math.min(maxShift, edge + center);
+
+ let neg = direction == "ltr" ? "-" : "";
+ this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
+ aAnchor.setAttribute("panel-multiview-anchor", true);
+ } else {
+ this._mainViewContainer.style.transform = "";
+ if (this.anchorElement)
+ this.anchorElement.removeAttribute("panel-multiview-anchor");
+ }
+ this.anchorElement = aAnchor;
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
+ // Shouldn't act on e.g. context menus being shown from within the panel.
+ return;
+ }
+ switch (aEvent.type) {
+ case "click":
+ if (aEvent.originalTarget == this._clickCapturer) {
+ this.showMainView();
+ }
+ break;
+ case "overflow":
+ if (aEvent.target.localName == "vbox") {
+ // Resize the right view on the next tick.
+ if (this.showingSubView) {
+ setTimeout(this._syncContainerWithSubView.bind(this), 0);
+ } else if (!this.transitioning) {
+ setTimeout(this._syncContainerWithMainView.bind(this), 0);
+ }
+ }
+ break;
+ case "popupshowing":
+ this.setAttribute("panelopen", "true");
+ // Bug 941196 - The panel can get taller when opening a subview. Disabling
+ // autoPositioning means that the panel won't jump around if an opened
+ // subview causes the panel to exceed the dimensions of the screen in the
+ // direction that the panel originally opened in. This property resets
+ // every time the popup closes, which is why we have to set it each time.
+ this._panel.autoPosition = false;
+ this._syncContainerWithMainView();
+
+ this._mainViewObserver.observe(this._mainView, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true
+ });
+
+ break;
+ case "popupshown":
+ this._setMaxHeight();
+ break;
+ case "popuphidden":
+ this.removeAttribute("panelopen");
+ this._mainView.style.removeProperty("height");
+ this.showMainView();
+ this._mainViewObserver.disconnect();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_shouldSetPosition">
+ <body><![CDATA[
+ return this.getAttribute("nosubviews") == "true";
+ ]]></body>
+ </method>
+
+ <method name="_shouldSetHeight">
+ <body><![CDATA[
+ return this.getAttribute("nosubviews") != "true";
+ ]]></body>
+ </method>
+
+ <method name="_setMaxHeight">
+ <body><![CDATA[
+ if (!this._shouldSetHeight())
+ return;
+
+ // Ignore the mutation that'll fire when we set the height of
+ // the main view.
+ this.ignoreMutations = true;
+ this._mainView.style.height =
+ this.getBoundingClientRect().height + "px";
+ this.ignoreMutations = false;
+ ]]></body>
+ </method>
+ <method name="_adjustContainerHeight">
+ <body><![CDATA[
+ if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
+ let height;
+ if (this.showingSubViewAsMainView) {
+ height = this._heightOfSubview(this._mainView);
+ } else {
+ height = this._mainView.scrollHeight;
+ }
+ this._viewContainer.style.height = height + "px";
+ }
+ ]]></body>
+ </method>
+ <method name="_syncContainerWithSubView">
+ <body><![CDATA[
+ // Check that this panel is still alive:
+ if (!this._panel || !this._panel.parentNode) {
+ return;
+ }
+
+ if (!this.ignoreMutations && this.showingSubView) {
+ let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
+ this._viewContainer.style.height = newHeight + "px";
+ }
+ ]]></body>
+ </method>
+ <method name="_syncContainerWithMainView">
+ <body><![CDATA[
+ // Check that this panel is still alive:
+ if (!this._panel || !this._panel.parentNode) {
+ return;
+ }
+
+ if (this._shouldSetPosition()) {
+ this._panel.adjustArrowPosition();
+ }
+
+ if (this._shouldSetHeight()) {
+ this._adjustContainerHeight();
+ }
+ ]]></body>
+ </method>
+
+ <!-- Call this when the height of one of your views (the main view or a
+ subview) changes and you want the heights of the multiview and panel
+ to be the same as the view's height.
+ If the caller can give a hint of the expected height change with the
+ optional aExpectedChange parameter, it prevents flicker. -->
+ <method name="setHeightToFit">
+ <parameter name="aExpectedChange"/>
+ <body><![CDATA[
+ // Set the max-height to zero, wait until the height is actually
+ // updated, and then remove it. If it's not removed, weird things can
+ // happen, like widgets in the panel won't respond to clicks even
+ // though they're visible.
+ let count = 5;
+ let height = getComputedStyle(this).height;
+ if (aExpectedChange)
+ this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
+ else
+ this.style.maxHeight = "0";
+ let interval = setInterval(() => {
+ if (height != getComputedStyle(this).height || --count == 0) {
+ clearInterval(interval);
+ this.style.removeProperty("max-height");
+ }
+ }, 0);
+ ]]></body>
+ </method>
+
+ <method name="_heightOfSubview">
+ <parameter name="aSubview"/>
+ <parameter name="aContainerToCheck"/>
+ <body><![CDATA[
+ function getFullHeight(element) {
+ // XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
+ // that works with overflow: auto elements. Fortunately for us,
+ // we have exactly 1 (potentially) scrolling element in here (the subview body),
+ // and rounding 1 value is OK - rounding more than 1 and adding them means we get
+ // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
+ // So, use scrollHeight *only* if the element is vertically scrollable.
+ let height;
+ let elementCS;
+ if (element.scrollTopMax) {
+ height = element.scrollHeight;
+ // Bounding client rects include borders, scrollHeight doesn't:
+ elementCS = win.getComputedStyle(element);
+ height += parseFloat(elementCS.borderTopWidth) +
+ parseFloat(elementCS.borderBottomWidth);
+ } else {
+ height = element.getBoundingClientRect().height;
+ if (height > 0) {
+ elementCS = win.getComputedStyle(element);
+ }
+ }
+ if (elementCS) {
+ // Include margins - but not borders or paddings because they
+ // were dealt with above.
+ height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
+ }
+ return height;
+ }
+ let win = aSubview.ownerDocument.defaultView;
+ let body = aSubview.querySelector(".panel-subview-body");
+ let height = getFullHeight(body || aSubview);
+ if (body) {
+ let header = aSubview.querySelector(".panel-subview-header");
+ let footer = aSubview.querySelector(".panel-subview-footer");
+ height += (header ? getFullHeight(header) : 0) +
+ (footer ? getFullHeight(footer) : 0);
+ }
+ if (aContainerToCheck) {
+ let containerCS = win.getComputedStyle(aContainerToCheck);
+ height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
+ }
+ return Math.ceil(height);
+ ]]></body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="panelview">
+ <implementation>
+ <property name="panelMultiView" readonly="true">
+ <getter><![CDATA[
+ if (this.parentNode.localName != "panelmultiview") {
+ return document.getBindingParent(this.parentNode);
+ }
+
+ return this.parentNode;
+ ]]></getter>
+ </property>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/browser/components/customizableui/content/toolbar.xml b/browser/components/customizableui/content/toolbar.xml
new file mode 100644
index 000000000..4e6964c9f
--- /dev/null
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -0,0 +1,618 @@
+<?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="browserToolbarBindings"
+ 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" role="xul:toolbar">
+ <resources>
+ <stylesheet src="chrome://global/skin/toolbar.css"/>
+ </resources>
+ <implementation>
+ <field name="overflowedDuringConstruction">null</field>
+
+ <constructor><![CDATA[
+ let scope = {};
+ Cu.import("resource:///modules/CustomizableUI.jsm", scope);
+ // Add an early overflow event listener that will mark if the
+ // toolbar overflowed during construction.
+ if (scope.CustomizableUI.isAreaOverflowable(this.id)) {
+ this.addEventListener("overflow", this);
+ this.addEventListener("underflow", this);
+ }
+
+ 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 onReadyStateChange() {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", onReadyStateChange, false);
+ self._init();
+ }, false);
+ }
+ ]]></constructor>
+
+ <method name="_init">
+ <body><![CDATA[
+ let scope = {};
+ Cu.import("resource:///modules/CustomizableUI.jsm", scope);
+ let CustomizableUI = scope.CustomizableUI;
+
+ // Bug 989289: Forcibly set the now unsupported "mode" and "iconsize"
+ // attributes, just in case they accidentally get restored from
+ // persistence from a user that's been upgrading and downgrading.
+ if (CustomizableUI.isBuiltinToolbar(this.id)) {
+ const kAttributes = new Map([["mode", "icons"], ["iconsize", "small"]]);
+ for (let [attribute, value] of kAttributes) {
+ if (this.getAttribute(attribute) != value) {
+ this.setAttribute(attribute, value);
+ document.persist(this.id, attribute);
+ }
+ if (this.toolbox) {
+ if (this.toolbox.getAttribute(attribute) != value) {
+ this.toolbox.setAttribute(attribute, value);
+ document.persist(this.toolbox.id, attribute);
+ }
+ }
+ }
+ }
+
+ // Searching for the toolbox palette in the toolbar binding because
+ // toolbars are constructed first.
+ let toolbox = this.toolbox;
+ if (toolbox && !toolbox.palette) {
+ for (let node of toolbox.children) {
+ if (node.localName == "toolbarpalette") {
+ // Hold on to the palette but remove it from the document.
+ toolbox.palette = node;
+ toolbox.removeChild(node);
+ break;
+ }
+ }
+ }
+
+ // pass the current set of children for comparison with placements:
+ let children = Array.from(this.childNodes)
+ .filter(node => node.getAttribute("skipintoolbarset") != "true" && node.id)
+ .map(node => node.id);
+ CustomizableUI.registerToolbarNode(this, children);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.type == "overflow" && aEvent.detail > 0) {
+ if (this.overflowable && this.overflowable.initialized) {
+ this.overflowable.onOverflow(aEvent);
+ } else {
+ this.overflowedDuringConstruction = aEvent;
+ }
+ } else if (aEvent.type == "underflow" && aEvent.detail > 0) {
+ this.overflowedDuringConstruction = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="insertItem">
+ <parameter name="aId"/>
+ <parameter name="aBeforeElt"/>
+ <parameter name="aWrapper"/>
+ <body><![CDATA[
+ if (aWrapper) {
+ Cu.reportError("Can't insert " + aId + ": using insertItem " +
+ "no longer supports wrapper elements.");
+ return null;
+ }
+
+ // Hack, the customizable UI code makes this be the last position
+ let pos = null;
+ if (aBeforeElt) {
+ let beforeInfo = CustomizableUI.getPlacementOfWidget(aBeforeElt.id);
+ if (beforeInfo.area != this.id) {
+ Cu.reportError("Can't insert " + aId + " before " +
+ aBeforeElt.id + " which isn't in this area (" +
+ this.id + ").");
+ return null;
+ }
+ pos = beforeInfo.position;
+ }
+
+ CustomizableUI.addWidgetToArea(aId, this.id, pos);
+ return this.ownerDocument.getElementById(aId);
+ ]]></body>
+ </method>
+
+ <property name="toolbarName"
+ onget="return this.getAttribute('toolbarname');"
+ onset="this.setAttribute('toolbarname', val); return val;"/>
+
+ <property name="customizationTarget" readonly="true">
+ <getter><![CDATA[
+ if (this._customizationTarget)
+ return this._customizationTarget;
+
+ let id = this.getAttribute("customizationtarget");
+ if (id)
+ this._customizationTarget = document.getElementById(id);
+
+ if (this._customizationTarget)
+ this._customizationTarget.insertItem = this.insertItem.bind(this);
+ else
+ this._customizationTarget = this;
+
+ return this._customizationTarget;
+ ]]></getter>
+ </property>
+
+ <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) {
+ if (toolbox.externalToolbars.indexOf(this) == -1)
+ toolbox.externalToolbars.push(this);
+
+ this._toolbox = toolbox;
+ }
+ }
+
+ if (!this._toolbox && this.parentNode &&
+ this.parentNode.localName == "toolbox") {
+ this._toolbox = this.parentNode;
+ }
+
+ return this._toolbox;
+ ]]></getter>
+ </property>
+
+ <property name="currentSet">
+ <getter><![CDATA[
+ let currentWidgets = new Set();
+ for (let node of this.customizationTarget.children) {
+ let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
+ if (realNode.getAttribute("skipintoolbarset") != "true") {
+ currentWidgets.add(realNode.id);
+ }
+ }
+ if (this.getAttribute("overflowing") == "true") {
+ let overflowTarget = this.getAttribute("overflowtarget");
+ let overflowList = this.ownerDocument.getElementById(overflowTarget);
+ for (let node of overflowList.children) {
+ let realNode = node.localName == "toolbarpaletteitem" ? node.firstChild : node;
+ if (realNode.getAttribute("skipintoolbarset") != "true") {
+ currentWidgets.add(realNode.id);
+ }
+ }
+ }
+ let orderedPlacements = CustomizableUI.getWidgetIdsInArea(this.id);
+ return orderedPlacements.filter((x) => currentWidgets.has(x)).join(',');
+ ]]></getter>
+ <setter><![CDATA[
+ // Get list of new and old ids:
+ let newVal = (val || '').split(',').filter(x => x);
+ let oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
+
+ // Get a list of items only in the new list
+ let newIds = newVal.filter(id => oldIds.indexOf(id) == -1);
+ CustomizableUI.beginBatchUpdate();
+ try {
+ for (let newId of newIds) {
+ oldIds = CustomizableUI.getWidgetIdsInArea(this.id);
+ let nextId = newId;
+ let pos;
+ do {
+ // Get the next item
+ nextId = newVal[newVal.indexOf(nextId) + 1];
+ // Figure out where it is in the old list
+ pos = oldIds.indexOf(nextId);
+ // If it's not in the old list, repeat:
+ } while (pos == -1 && nextId);
+ if (pos == -1) {
+ pos = null; // We didn't find anything, insert at the end
+ }
+ CustomizableUI.addWidgetToArea(newId, this.id, pos);
+ }
+
+ let currentIds = this.currentSet.split(',');
+ let removedIds = currentIds.filter(id => newIds.indexOf(id) == -1 && newVal.indexOf(id) == -1);
+ for (let removedId of removedIds) {
+ CustomizableUI.removeWidgetFromArea(removedId);
+ }
+ } finally {
+ CustomizableUI.endBatchUpdate();
+ }
+ ]]></setter>
+ </property>
+
+
+ </implementation>
+ </binding>
+
+ <binding id="toolbar-menubar-stub">
+ <implementation>
+ <property name="toolbox" readonly="true">
+ <getter><![CDATA[
+ if (this._toolbox)
+ return this._toolbox;
+
+ if (this.parentNode && this.parentNode.localName == "toolbox") {
+ this._toolbox = this.parentNode;
+ }
+
+ return this._toolbox;
+ ]]></getter>
+ </property>
+ <property name="currentSet" readonly="true">
+ <getter><![CDATA[
+ return this.getAttribute("defaultset");
+ ]]></getter>
+ </property>
+ <method name="insertItem">
+ <body><![CDATA[
+ return null;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
+ verbatim copies of their toolkit counterparts - they just inherit from
+ the customizableui's toolbar binding instead of toolkit's. We're currently
+ OK with the maintainance burden of having two copies of a binding, since
+ the long term goal is to move the customization framework into toolkit. -->
+
+ <binding id="toolbar-menubar-autohide"
+ extends="chrome://browser/content/customizableui/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) {
+ let node = event.target;
+ while (node != this.toolbar) {
+ if (node.localName == "menupopup")
+ return;
+ node = node.parentNode;
+ }
+
+ let 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://browser/content/customizableui/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) {
+ return this._dragBindingAlive;
+ };
+ } catch (e) {}
+ }
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+
+<!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
+ and immediately direct such content elsewhere. -->
+ <binding id="addonbar-delegating">
+ <implementation>
+ <constructor><![CDATA[
+ // Reading these immediately so nobody messes with them anymore:
+ this._delegatingToolbar = this.getAttribute("toolbar-delegate");
+ this._wasCollapsed = this.getAttribute("collapsed") == "true";
+ // Leaving those in here to unbreak some code:
+ 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 onReadyStateChange() {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", onReadyStateChange, false);
+ self._init();
+ }, false);
+ }
+ ]]></constructor>
+
+ <method name="_init">
+ <body><![CDATA[
+ // Searching for the toolbox palette in the toolbar binding because
+ // toolbars are constructed first.
+ let toolbox = this.toolbox;
+ if (toolbox && !toolbox.palette) {
+ for (let node of toolbox.children) {
+ if (node.localName == "toolbarpalette") {
+ // Hold on to the palette but remove it from the document.
+ toolbox.palette = node;
+ toolbox.removeChild(node);
+ }
+ }
+ }
+
+ // pass the current set of children for comparison with placements:
+ let children = [];
+ for (let node of this.childNodes) {
+ if (node.getAttribute("skipintoolbarset") != "true" && node.id) {
+ // Force everything to be removable so that buildArea can chuck stuff
+ // out if the user has customized things / we've been here before:
+ if (!this._whiteListed.has(node.id)) {
+ node.setAttribute("removable", "true");
+ }
+ children.push(node);
+ }
+ }
+ CustomizableUI.registerToolbarNode(this, children);
+ let existingMigratedItems = (this.getAttribute("migratedset") || "").split(',');
+ for (let migratedItem of existingMigratedItems.filter((x) => !!x)) {
+ this._currentSetMigrated.add(migratedItem);
+ }
+ this.evictNodes();
+ // We can't easily use |this| or strong bindings for the observer fn here
+ // because that creates leaky circular references when the node goes away,
+ // and XBL destructors are unreliable.
+ let mutationObserver = new MutationObserver(function(mutations) {
+ if (!mutations.length) {
+ return;
+ }
+ let toolbar = mutations[0].target;
+ // Can't use our own attribute because we might not have one if we're set to
+ // collapsed
+ let areCustomizing = toolbar.ownerDocument.documentElement.getAttribute("customizing");
+ if (!toolbar._isModifying && !areCustomizing) {
+ toolbar.evictNodes();
+ }
+ });
+ mutationObserver.observe(this, {childList: true});
+ ]]></body>
+ </method>
+ <method name="evictNodes">
+ <body><![CDATA[
+ this._isModifying = true;
+ let i = this.childNodes.length;
+ while (i--) {
+ let node = this.childNodes[i];
+ if (this.childNodes[i].id) {
+ this.evictNode(this.childNodes[i]);
+ } else {
+ node.remove();
+ }
+ }
+ this._isModifying = false;
+ this._updateMigratedSet();
+ ]]></body>
+ </method>
+ <method name="evictNode">
+ <parameter name="aNode"/>
+ <body>
+ <![CDATA[
+ if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
+ return;
+ }
+ const kItemMaxWidth = 100;
+ let oldParent = aNode.parentNode;
+ aNode.setAttribute("removable", "true");
+ this._currentSetMigrated.add(aNode.id);
+
+ let movedOut = false;
+ if (!this._wasCollapsed) {
+ try {
+ let nodeWidth = aNode.getBoundingClientRect().width;
+ if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
+ throw new Error(aNode.id + " is too big (" + nodeWidth +
+ "px wide), moving to the palette");
+ }
+ CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
+ movedOut = true;
+ } catch (ex) {
+ // This will throw if the node is too big, or can't be moved there for
+ // some reason. Report this:
+ Cu.reportError(ex);
+ }
+ }
+
+ /* We won't have moved the widget if either the add-on bar was collapsed,
+ * or if it was too wide to be inserted into the navbar. */
+ if (!movedOut) {
+ try {
+ CustomizableUI.removeWidgetFromArea(aNode.id);
+ } catch (ex) {
+ Cu.reportError(ex);
+ aNode.remove();
+ }
+ }
+
+ // Surprise: addWidgetToArea(palette) will get you nothing if the palette
+ // is not constructed yet. Fix:
+ if (aNode.parentNode == oldParent) {
+ let palette = this.toolbox.palette;
+ if (palette && oldParent != palette) {
+ palette.appendChild(aNode);
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="insertItem">
+ <parameter name="aId"/>
+ <parameter name="aBeforeElt"/>
+ <parameter name="aWrapper"/>
+ <body><![CDATA[
+ if (aWrapper) {
+ Cu.reportError("Can't insert " + aId + ": using insertItem " +
+ "no longer supports wrapper elements.");
+ return null;
+ }
+
+ let widget = CustomizableUI.getWidget(aId);
+ widget = widget && widget.forWindow(window);
+ let node = widget && widget.node;
+ if (!node) {
+ return null;
+ }
+
+ this._isModifying = true;
+ // Temporarily add it here so it can have a width, then ditch it:
+ this.appendChild(node);
+ this.evictNode(node);
+ this._isModifying = false;
+ this._updateMigratedSet();
+ // We will now have moved stuff around; kick off some events
+ // so add-ons know we've just moved their stuff:
+ // XXXgijs: only in this window. It's hard to know for sure what's the right
+ // thing to do here - typically insertItem is used on each window, so
+ // this seems to make the most sense, even if some of the effects of
+ // evictNode might affect multiple windows.
+ CustomizableUI.dispatchToolboxEvent("customizationchange", {}, window);
+ CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
+ return node;
+ ]]></body>
+ </method>
+ <method name="getMigratedItems">
+ <body><![CDATA[
+ return [... this._currentSetMigrated];
+ ]]></body>
+ </method>
+ <method name="_updateMigratedSet">
+ <body><![CDATA[
+ let newMigratedItems = this.getMigratedItems().join(',');
+ if (this.getAttribute("migratedset") != newMigratedItems) {
+ this.setAttribute("migratedset", newMigratedItems);
+ this.ownerDocument.persist(this.id, "migratedset");
+ }
+ ]]></body>
+ </method>
+ <property name="customizationTarget" readonly="true">
+ <getter><![CDATA[
+ return this;
+ ]]></getter>
+ </property>
+ <property name="currentSet">
+ <getter><![CDATA[
+ return Array.from(this.children, node => node.id).join(",");
+ ]]></getter>
+ <setter><![CDATA[
+ let v = val.split(',');
+ let newButtons = v.filter(x => x && (!this._whiteListed.has(x) &&
+ !CustomizableUI.isSpecialWidget(x) &&
+ !this._currentSetMigrated.has(x)));
+ for (let newButton of newButtons) {
+ this._currentSetMigrated.add(newButton);
+ this.insertItem(newButton);
+ }
+ this._updateMigratedSet();
+ ]]></setter>
+ </property>
+ <property name="toolbox" readonly="true">
+ <getter><![CDATA[
+ if (!this._toolbox && this.parentNode &&
+ this.parentNode.localName == "toolbox") {
+ this._toolbox = this.parentNode;
+ }
+
+ return this._toolbox;
+ ]]></getter>
+ </property>
+ <field name="_whiteListed" readonly="true">new Set(["addonbar-closebutton", "status-bar"])</field>
+ <field name="_isModifying">false</field>
+ <field name="_currentSetMigrated">new Set()</field>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/browser/components/customizableui/moz.build b/browser/components/customizableui/moz.build
new file mode 100644
index 000000000..72ec391d8
--- /dev/null
+++ b/browser/components/customizableui/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'content',
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+EXTRA_JS_MODULES += [
+ 'CustomizableUI.jsm',
+ 'CustomizableWidgets.jsm',
+ 'CustomizeMode.jsm',
+ 'DragPositionManager.jsm',
+ 'PanelWideWidgetTracker.jsm',
+ 'ScrollbarSampler.jsm',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
+ DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Toolbars and Customization')
diff --git a/browser/components/customizableui/test/.eslintrc.js b/browser/components/customizableui/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/customizableui/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/customizableui/test/browser.ini b/browser/components/customizableui/test/browser.ini
new file mode 100644
index 000000000..1c1f30498
--- /dev/null
+++ b/browser/components/customizableui/test/browser.ini
@@ -0,0 +1,154 @@
+[DEFAULT]
+support-files =
+ head.js
+ support/test_967000_charEncoding_page.html
+ support/feeds_test_page.html
+ support/test-feed.xml
+
+[browser_873501_handle_specials.js]
+[browser_876926_customize_mode_wrapping.js]
+[browser_876944_customize_mode_create_destroy.js]
+[browser_877006_missing_view.js]
+[browser_877178_unregisterArea.js]
+[browser_877447_skip_missing_ids.js]
+[browser_878452_drag_to_panel.js]
+[browser_880164_customization_context_menus.js]
+[browser_880382_drag_wide_widgets_in_panel.js]
+[browser_884402_customize_from_overflow.js]
+skip-if = os == "linux"
+[browser_885052_customize_mode_observers_disabed.js]
+tags = fullscreen
+# Bug 951403 - Disabled on OSX for frequent failures
+skip-if = os == "mac"
+
+[browser_885530_showInPrivateBrowsing.js]
+[browser_886323_buildArea_removable_nodes.js]
+[browser_887438_currentset_shim.js]
+[browser_888817_currentset_updating.js]
+[browser_890140_orphaned_placeholders.js]
+[browser_890262_destroyWidget_after_add_to_panel.js]
+[browser_892955_isWidgetRemovable_for_removed_widgets.js]
+[browser_892956_destroyWidget_defaultPlacements.js]
+[browser_909779_overflow_toolbars_new_window.js]
+skip-if = os == "linux"
+
+[browser_901207_searchbar_in_panel.js]
+[browser_913972_currentset_overflow.js]
+skip-if = os == "linux"
+
+[browser_914138_widget_API_overflowable_toolbar.js]
+skip-if = os == "linux"
+
+[browser_914863_disabled_help_quit_buttons.js]
+[browser_918049_skipintoolbarset_dnd.js]
+[browser_923857_customize_mode_event_wrapping_during_reset.js]
+[browser_927717_customize_drag_empty_toolbar.js]
+
+# Bug 1163231 - Causes failures on Developer Edition on Windows 7.
+# [browser_932928_show_notice_when_palette_empty.js]
+
+[browser_934113_menubar_removable.js]
+# Because this test is about the menubar, it can't be run on mac
+skip-if = os == "mac"
+
+[browser_934951_zoom_in_toolbar.js]
+[browser_938980_navbar_collapsed.js]
+[browser_938995_indefaultstate_nonremovable.js]
+[browser_940013_registerToolbarNode_calls_registerArea.js]
+[browser_940307_panel_click_closure_handling.js]
+[browser_940946_removable_from_navbar_customizemode.js]
+[browser_941083_invalidate_wrapper_cache_createWidget.js]
+[browser_942581_unregisterArea_keeps_placements.js]
+[browser_943683_migration_test.js]
+[browser_944887_destroyWidget_should_destroy_in_palette.js]
+[browser_945739_showInPrivateBrowsing_customize_mode.js]
+[browser_947914_button_addons.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_copy.js]
+subsuite = clipboard
+skip-if = os == "linux" # Intermittent failures on Linux
+[browser_947914_button_cut.js]
+subsuite = clipboard
+skip-if = os == "linux" # Intermittent failures on Linux
+[browser_947914_button_find.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_history.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_newPrivateWindow.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_newWindow.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_paste.js]
+subsuite = clipboard
+skip-if = os == "linux" # Intermittent failures on Linux
+[browser_947914_button_print.js]
+skip-if = os == "linux" # Intermittent failures on Linux
+[browser_947914_button_savePage.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_zoomIn.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_zoomOut.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947914_button_zoomReset.js]
+skip-if = os == "linux" # Intermittent failures
+[browser_947987_removable_default.js]
+[browser_948985_non_removable_defaultArea.js]
+[browser_952963_areaType_getter_no_area.js]
+[browser_956602_remove_special_widget.js]
+[browser_962069_drag_to_overflow_chevron.js]
+[browser_962884_opt_in_disable_hyphens.js]
+[browser_963639_customizing_attribute_non_customizable_toolbar.js]
+[browser_967000_button_charEncoding.js]
+[browser_967000_button_feeds.js]
+[browser_967000_button_sync.js]
+[browser_968447_bookmarks_toolbar_items_in_panel.js]
+skip-if = os == "linux" # Intemittent failures - bug 979207
+[browser_968565_insert_before_hidden_items.js]
+[browser_969427_recreate_destroyed_widget_after_reset.js]
+[browser_969661_character_encoding_navbar_disabled.js]
+[browser_970511_undo_restore_default.js]
+[browser_972267_customizationchange_events.js]
+[browser_973641_button_addon.js]
+[browser_973932_addonbar_currentset.js]
+[browser_975719_customtoolbars_behaviour.js]
+[browser_976792_insertNodeInWindow.js]
+skip-if = os == "linux"
+[browser_978084_dragEnd_after_move.js]
+[browser_980155_add_overflow_toolbar.js]
+[browser_981305_separator_insertion.js]
+[browser_981418-widget-onbeforecreated-handler.js]
+[browser_982656_restore_defaults_builtin_widgets.js]
+[browser_984455_bookmarks_items_reparenting.js]
+skip-if = os == "linux"
+[browser_985815_propagate_setToolbarVisibility.js]
+[browser_987177_destroyWidget_xul.js]
+[browser_987177_xul_wrapper_updating.js]
+[browser_987185_syncButton.js]
+[browser_987492_window_api.js]
+[browser_987640_charEncoding.js]
+[browser_988072_sidebar_events.js]
+[browser_989338_saved_placements_not_resaved.js]
+[browser_989751_subviewbutton_class.js]
+[browser_992747_toggle_noncustomizable_toolbar.js]
+[browser_993322_widget_notoolbar.js]
+[browser_995164_registerArea_during_customize_mode.js]
+[browser_996364_registerArea_different_properties.js]
+[browser_996635_remove_non_widgets.js]
+[browser_1003588_no_specials_in_panel.js]
+[browser_1007336_lwthemes_in_customize_mode.js]
+skip-if = os == "linux" # crashing on Linux due to bug 1271683
+[browser_1008559_anchor_undo_restore.js]
+[browser_1042100_default_placements_update.js]
+[browser_1058573_showToolbarsDropdown.js]
+[browser_1087303_button_fullscreen.js]
+tags = fullscreen
+skip-if = os == "mac"
+[browser_1087303_button_preferences.js]
+[browser_1089591_still_customizable_after_reset.js]
+[browser_1096763_seen_widgets_post_reset.js]
+[browser_1161838_inserted_new_default_buttons.js]
+[browser_bootstrapped_custom_toolbar.js]
+[browser_customizemode_contextmenu_menubuttonstate.js]
+[browser_panel_toggle.js]
+[browser_switch_to_customize_mode.js]
+[browser_check_tooltips_in_navbar.js]
diff --git a/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js b/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js
new file mode 100644
index 000000000..22fbb5c0c
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1003588_no_specials_in_panel.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function simulateItemDragAndEnd(aToDrag, aTarget) {
+ var ds = Components.classes["@mozilla.org/widget/dragservice;1"].
+ getService(Components.interfaces.nsIDragService);
+
+ ds.startDragSession();
+ try {
+ var [result, dataTransfer] = EventUtils.synthesizeDragOver(aToDrag.parentNode, aTarget);
+ EventUtils.synthesizeDropAfterDragOver(result, dataTransfer, aTarget);
+ // Send dragend to move dragging item back to initial place.
+ EventUtils.sendDragEvent({ type: "dragend", dataTransfer: dataTransfer },
+ aToDrag.parentNode);
+ } finally {
+ ds.endDragSession(true);
+ }
+}
+
+add_task(function* checkNoAddingToPanel() {
+ let area = CustomizableUI.AREA_PANEL;
+ let previousPlacements = getAreaWidgetIds(area);
+ CustomizableUI.addWidgetToArea("separator", area);
+ CustomizableUI.addWidgetToArea("spring", area);
+ CustomizableUI.addWidgetToArea("spacer", area);
+ assertAreaPlacements(area, previousPlacements);
+
+ let oldNumberOfItems = previousPlacements.length;
+ if (getAreaWidgetIds(area).length != oldNumberOfItems) {
+ CustomizableUI.reset();
+ }
+});
+
+add_task(function* checkAddingToToolbar() {
+ let area = CustomizableUI.AREA_NAVBAR;
+ let previousPlacements = getAreaWidgetIds(area);
+ CustomizableUI.addWidgetToArea("separator", area);
+ CustomizableUI.addWidgetToArea("spring", area);
+ CustomizableUI.addWidgetToArea("spacer", area);
+ let expectedPlacements = [...previousPlacements].concat([
+ /separator/,
+ /spring/,
+ /spacer/
+ ]);
+ assertAreaPlacements(area, expectedPlacements);
+
+ let newlyAddedElements = getAreaWidgetIds(area).slice(-3);
+ while (newlyAddedElements.length) {
+ CustomizableUI.removeWidgetFromArea(newlyAddedElements.shift());
+ }
+
+ assertAreaPlacements(area, previousPlacements);
+
+ let oldNumberOfItems = previousPlacements.length;
+ if (getAreaWidgetIds(area).length != oldNumberOfItems) {
+ CustomizableUI.reset();
+ }
+});
+
+
+add_task(function* checkDragging() {
+ let startArea = CustomizableUI.AREA_NAVBAR;
+ let targetArea = CustomizableUI.AREA_PANEL;
+ let startingToolbarPlacements = getAreaWidgetIds(startArea);
+ let startingTargetPlacements = getAreaWidgetIds(targetArea);
+
+ CustomizableUI.addWidgetToArea("separator", startArea);
+ CustomizableUI.addWidgetToArea("spring", startArea);
+ CustomizableUI.addWidgetToArea("spacer", startArea);
+
+ let placementsWithSpecials = getAreaWidgetIds(startArea);
+ let elementsToMove = [];
+ for (let id of placementsWithSpecials) {
+ if (CustomizableUI.isSpecialWidget(id)) {
+ elementsToMove.push(id);
+ }
+ }
+ is(elementsToMove.length, 3, "Should have 3 elements to try and drag.");
+
+ yield startCustomizing();
+ for (let id of elementsToMove) {
+ simulateItemDragAndEnd(document.getElementById(id), PanelUI.contents);
+ }
+
+ assertAreaPlacements(startArea, placementsWithSpecials);
+ assertAreaPlacements(targetArea, startingTargetPlacements);
+
+ for (let id of elementsToMove) {
+ simulateItemDrag(document.getElementById(id), gCustomizeMode.visiblePalette);
+ }
+
+ assertAreaPlacements(startArea, startingToolbarPlacements);
+ assertAreaPlacements(targetArea, startingTargetPlacements);
+
+ ok(!gCustomizeMode.visiblePalette.querySelector("toolbarspring,toolbarseparator,toolbarspacer"),
+ "No specials should make it to the palette alive.");
+ yield endCustomizing();
+});
+
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ CustomizableUI.reset();
+});
+
diff --git a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
new file mode 100644
index 000000000..db4f88e6d
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.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/. */
+
+"use strict";
+
+const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
+const {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
+
+add_task(function* () {
+ Services.prefs.clearUserPref("lightweightThemes.usedThemes");
+ Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
+ LightweightThemeManager.clearBuiltInThemes();
+
+ yield startCustomizing();
+
+ let themesButton = document.getElementById("customization-lwtheme-button");
+ let popup = document.getElementById("customization-lwtheme-menu");
+
+ let popupShownPromise = popupShown(popup);
+ EventUtils.synthesizeMouseAtCenter(themesButton, {});
+ info("Clicked on themes button");
+ yield popupShownPromise;
+
+ // close current tab and re-open Customize menu to confirm correct number of Themes
+ yield endCustomizing();
+ info("Exited customize mode");
+ yield startCustomizing();
+ info("Started customizing a second time");
+ popupShownPromise = popupShown(popup);
+ EventUtils.synthesizeMouseAtCenter(themesButton, {});
+ info("Clicked on themes button a second time");
+ yield popupShownPromise;
+
+ let header = document.getElementById("customization-lwtheme-menu-header");
+ let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
+
+ is(header.nextSibling.nextSibling, recommendedHeader,
+ "There should only be one theme (default) in the 'My Themes' section by default");
+ is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "That theme should be the default theme");
+
+ let firstLWTheme = recommendedHeader.nextSibling;
+ let firstLWThemeId = firstLWTheme.theme.id;
+ let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
+ firstLWTheme.doCommand();
+ info("Clicked on first theme");
+ yield themeChangedPromise;
+
+ popupShownPromise = popupShown(popup);
+ EventUtils.synthesizeMouseAtCenter(themesButton, {});
+ info("Clicked on themes button");
+ yield popupShownPromise;
+
+ is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
+ let installedThemeId = header.nextSibling.nextSibling.theme.id;
+ ok(installedThemeId.startsWith(firstLWThemeId),
+ "The second theme in the 'My Themes' section should be the newly installed theme: " +
+ "Installed theme id: " + installedThemeId + "; First theme ID: " + firstLWThemeId);
+ is(header.nextSibling.nextSibling.nextSibling, recommendedHeader,
+ "There should be two themes in the 'My Themes' section");
+
+ let defaultTheme = header.nextSibling;
+ defaultTheme.doCommand();
+ is(Services.prefs.getCharPref("lightweightThemes.selectedThemeID"), "", "No lwtheme should be selected");
+
+ // ensure current theme isn't set to "Default"
+ popupShownPromise = popupShown(popup);
+ EventUtils.synthesizeMouseAtCenter(themesButton, {});
+ info("Clicked on themes button a second time");
+ yield popupShownPromise;
+
+ firstLWTheme = recommendedHeader.nextSibling;
+ themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
+ firstLWTheme.doCommand();
+ info("Clicked on first theme again");
+ yield themeChangedPromise;
+
+ // check that "Restore Defaults" button resets theme
+ yield gCustomizeMode.reset();
+ is(LightweightThemeManager.currentTheme, null, "Current theme reset to default");
+
+ yield endCustomizing();
+ Services.prefs.setCharPref("lightweightThemes.usedThemes", "[]");
+ Services.prefs.setCharPref("lightweightThemes.recommendedThemes", "[]");
+ info("Removed all recommended themes");
+ yield startCustomizing();
+ popupShownPromise = popupShown(popup);
+ EventUtils.synthesizeMouseAtCenter(themesButton, {});
+ info("Clicked on themes button a second time");
+ yield popupShownPromise;
+ header = document.getElementById("customization-lwtheme-menu-header");
+ is(header.hidden, false, "Header should never be hidden");
+ is(header.nextSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
+ is(header.nextSibling.hidden, false, "The default theme should never be hidden");
+ recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
+ is(header.nextSibling.nextSibling, recommendedHeader,
+ "There should only be one theme (default) in the 'My Themes' section by default");
+ let footer = document.getElementById("customization-lwtheme-menu-footer");
+ is(recommendedHeader.nextSibling.id, footer.id, "There should be no recommended themes in the menu");
+ is(recommendedHeader.hidden, true, "The recommendedHeader should be hidden since there are no recommended themes");
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+
+ Services.prefs.clearUserPref("lightweightThemes.usedThemes");
+ Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
+});
diff --git a/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js b/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js
new file mode 100644
index 000000000..56657914b
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1008559_anchor_undo_restore.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kAnchorAttribute = "cui-anchorid";
+
+/**
+ * Check that anchor gets set correctly when moving an item from the panel to the toolbar
+ * using 'undo'
+ */
+add_task(function*() {
+ yield startCustomizing();
+ let button = document.getElementById("history-panelmenu");
+ is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
+ "Button (" + button.id + ") starts out with correct anchor");
+
+ let navbar = document.getElementById("nav-bar").customizationTarget;
+ simulateItemDrag(button, navbar);
+ is(CustomizableUI.getPlacementOfWidget(button.id).area, "nav-bar",
+ "Button (" + button.id + ") ends up in nav-bar");
+
+ ok(!button.hasAttribute(kAnchorAttribute),
+ "Button (" + button.id + ") has no anchor in toolbar");
+
+ let resetButton = document.getElementById("customization-reset-button");
+ ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
+ yield gCustomizeMode.reset();
+
+ is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
+ "Button (" + button.id + ") has anchor again");
+
+ let undoButton = document.getElementById("customization-undo-reset-button");
+ ok(!undoButton.hasAttribute("disabled"), "Should be able to undo now.");
+ yield gCustomizeMode.undoReset();
+
+ ok(!button.hasAttribute(kAnchorAttribute),
+ "Button (" + button.id + ") once again has no anchor in toolbar");
+
+ yield gCustomizeMode.reset();
+
+ yield endCustomizing();
+});
+
+
+/**
+ * Check that anchor gets set correctly when moving an item from the panel to the toolbar
+ * using 'reset'
+ */
+add_task(function*() {
+ yield startCustomizing();
+ let button = document.getElementById("bookmarks-menu-button");
+ ok(!button.hasAttribute(kAnchorAttribute),
+ "Button (" + button.id + ") has no anchor in toolbar");
+
+ let panel = document.getElementById("PanelUI-contents");
+ simulateItemDrag(button, panel);
+ is(CustomizableUI.getPlacementOfWidget(button.id).area, "PanelUI-contents",
+ "Button (" + button.id + ") ends up in panel");
+ is(button.getAttribute(kAnchorAttribute), "PanelUI-menu-button",
+ "Button (" + button.id + ") has correct anchor in the panel");
+
+ let resetButton = document.getElementById("customization-reset-button");
+ ok(!resetButton.hasAttribute("disabled"), "Should be able to reset now.");
+ yield gCustomizeMode.reset();
+
+ ok(!button.hasAttribute(kAnchorAttribute),
+ "Button (" + button.id + ") once again has no anchor in toolbar");
+
+ yield endCustomizing();
+});
diff --git a/browser/components/customizableui/test/browser_1042100_default_placements_update.js b/browser/components/customizableui/test/browser_1042100_default_placements_update.js
new file mode 100644
index 000000000..129dbd754
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1042100_default_placements_update.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// NB: This uses some ugly hacks to get into the CUI module from elsewhere...
+// don't try this at home, kids.
+function test() {
+ // Customize something to make sure stuff changed:
+ CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
+
+ // Check what version we're on:
+ let CustomizableUIBSPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+
+ is(CustomizableUIBSPass.gFuturePlacements.size, 0,
+ "All future placements should be dealt with by now.");
+
+ let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass;
+ CustomizableUIInternal._introduceNewBuiltinWidgets();
+ is(gFuturePlacements.size, 0,
+ "No change to future placements initially.");
+
+ let currentVersion = CustomizableUIBSPass.kVersion;
+
+
+ // Add our widget to the defaults:
+ let testWidgetNew = {
+ id: "test-messing-with-default-placements-new",
+ label: "Test messing with default placements - should be inserted",
+ defaultArea: CustomizableUI.AREA_NAVBAR,
+ introducedInVersion: currentVersion + 1,
+ };
+
+ let normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetNew,
+ CustomizableUI.SOURCE_BUILTIN);
+ ok(normalizedWidget, "Widget should be normalizable");
+ if (!normalizedWidget) {
+ return;
+ }
+ CustomizableUIBSPass.gPalette.set(testWidgetNew.id, normalizedWidget);
+
+ let testWidgetOld = {
+ id: "test-messing-with-default-placements-old",
+ label: "Test messing with default placements - should NOT be inserted",
+ defaultArea: CustomizableUI.AREA_NAVBAR,
+ introducedInVersion: currentVersion,
+ };
+
+ normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetOld,
+ CustomizableUI.SOURCE_BUILTIN);
+ ok(normalizedWidget, "Widget should be normalizable");
+ if (!normalizedWidget) {
+ return;
+ }
+ CustomizableUIBSPass.gPalette.set(testWidgetOld.id, normalizedWidget);
+
+
+ // Now increase the version in the module:
+ CustomizableUIBSPass.kVersion++;
+
+ let hadSavedState = !!CustomizableUIBSPass.gSavedState
+ if (!hadSavedState) {
+ CustomizableUIBSPass.gSavedState = {currentVersion: CustomizableUIBSPass.kVersion - 1};
+ }
+
+ // Then call the re-init routine so we re-add the builtin widgets
+ CustomizableUIInternal._introduceNewBuiltinWidgets();
+ is(gFuturePlacements.size, 1,
+ "Should have 1 more future placement");
+ let haveNavbarPlacements = gFuturePlacements.has(CustomizableUI.AREA_NAVBAR);
+ ok(haveNavbarPlacements, "Should have placements for nav-bar");
+ if (haveNavbarPlacements) {
+ let placements = [...gFuturePlacements.get(CustomizableUI.AREA_NAVBAR)];
+
+ // Ignore widgets that are placed using the pref facility and not the
+ // versioned facility. They're independent of kVersion and the saved
+ // state's current version, so they may be present in the placements.
+ for (let i = 0; i < placements.length; ) {
+ if (placements[i] == testWidgetNew.id) {
+ i++;
+ continue;
+ }
+ let pref = "browser.toolbarbuttons.introduced." + placements[i];
+ let introduced = false;
+ try {
+ introduced = Services.prefs.getBoolPref(pref);
+ } catch (ex) {}
+ if (!introduced) {
+ i++;
+ continue;
+ }
+ placements.splice(i, 1);
+ }
+
+ is(placements.length, 1, "Should have 1 newly placed widget in nav-bar");
+ is(placements[0], testWidgetNew.id, "Should have our test widget to be placed in nav-bar");
+ }
+
+ gFuturePlacements.delete(CustomizableUI.AREA_NAVBAR);
+ CustomizableUIBSPass.kVersion--;
+ gPalette.delete(testWidgetNew.id);
+ gPalette.delete(testWidgetOld.id);
+ if (!hadSavedState) {
+ CustomizableUIBSPass.gSavedState = null;
+ }
+}
+
diff --git a/browser/components/customizableui/test/browser_1058573_showToolbarsDropdown.js b/browser/components/customizableui/test/browser_1058573_showToolbarsDropdown.js
new file mode 100644
index 000000000..42a032ff8
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1058573_showToolbarsDropdown.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";
+
+add_task(function*() {
+ info("Check that toggleable toolbars dropdown in always shown");
+
+ info("Remove all possible custom toolbars");
+ yield removeCustomToolbars();
+
+ info("Enter customization mode");
+ yield startCustomizing();
+
+ let toolbarsToggle = document.getElementById("customization-toolbar-visibility-button");
+ ok(toolbarsToggle, "The toolbars toggle dropdown exists");
+ ok(!toolbarsToggle.hasAttribute("hidden"),
+ "The toolbars toggle dropdown is displayed");
+});
+
+add_task(function* asyncCleanup() {
+ info("Exit customization mode");
+ yield endCustomizing();
+});
diff --git a/browser/components/customizableui/test/browser_1087303_button_fullscreen.js b/browser/components/customizableui/test/browser_1087303_button_fullscreen.js
new file mode 100644
index 000000000..c6b87d6ab
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1087303_button_fullscreen.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";
+
+add_task(function*() {
+ info("Check fullscreen button existence and functionality");
+
+ yield PanelUI.show();
+
+ let fullscreenButton = document.getElementById("fullscreen-button");
+ ok(fullscreenButton, "Fullscreen button appears in Panel Menu");
+
+ let fullscreenPromise = promiseFullscreenChange();
+ fullscreenButton.click();
+ yield fullscreenPromise;
+
+ ok(window.fullScreen, "Fullscreen mode was opened");
+
+ // exit full screen mode
+ fullscreenPromise = promiseFullscreenChange();
+ window.fullScreen = !window.fullScreen;
+ yield fullscreenPromise;
+
+ ok(!window.fullScreen, "Successfully exited fullscreen");
+});
+
+function promiseFullscreenChange() {
+ let deferred = Promise.defer();
+ info("Wait for fullscreen change");
+
+ let timeoutId = setTimeout(() => {
+ window.removeEventListener("fullscreen", onFullscreenChange, true);
+ deferred.reject("Fullscreen change did not happen within " + 20000 + "ms");
+ }, 20000);
+
+ function onFullscreenChange(event) {
+ clearTimeout(timeoutId);
+ window.removeEventListener("fullscreen", onFullscreenChange, true);
+ info("Fullscreen event received");
+ deferred.resolve();
+ }
+ window.addEventListener("fullscreen", onFullscreenChange, true);
+ return deferred.promise;
+}
diff --git a/browser/components/customizableui/test/browser_1087303_button_preferences.js b/browser/components/customizableui/test/browser_1087303_button_preferences.js
new file mode 100644
index 000000000..b1fdb85b6
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1087303_button_preferences.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";
+
+var newTab = null;
+
+add_task(function*() {
+ info("Check preferences button existence and functionality");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let preferencesButton = document.getElementById("preferences-button");
+ ok(preferencesButton, "Preferences button exists in Panel Menu");
+ preferencesButton.click();
+
+ newTab = gBrowser.selectedTab;
+ yield waitForPageLoad(newTab);
+
+ let openedPage = gBrowser.currentURI.spec;
+ is(openedPage, "about:preferences", "Preferences page was opened");
+});
+
+add_task(function asyncCleanup() {
+ if (gBrowser.tabs.length == 1)
+ gBrowser.addTab("about:blank");
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+ info("Tabs were restored");
+});
+
+function waitForPageLoad(aTab) {
+ let deferred = Promise.defer();
+
+ let timeoutId = setTimeout(() => {
+ aTab.linkedBrowser.removeEventListener("load", onTabLoad, true);
+ deferred.reject("Page didn't load within " + 20000 + "ms");
+ }, 20000);
+
+ function onTabLoad(event) {
+ clearTimeout(timeoutId);
+ aTab.linkedBrowser.removeEventListener("load", onTabLoad, true);
+ info("Tab event received: " + "load");
+ deferred.resolve();
+ }
+ aTab.linkedBrowser.addEventListener("load", onTabLoad, true, true);
+ return deferred.promise;
+}
diff --git a/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js b/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js
new file mode 100644
index 000000000..1f502e8e2
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1089591_still_customizable_after_reset.js
@@ -0,0 +1,24 @@
+"use strict";
+
+// Dragging the elements again after a reset should work
+add_task(function* () {
+ yield startCustomizing();
+ let historyButton = document.getElementById("wrapper-history-panelmenu");
+ let devButton = document.getElementById("wrapper-developer-button");
+
+ ok(historyButton && devButton, "Draggable elements should exist");
+ simulateItemDrag(historyButton, devButton);
+ yield gCustomizeMode.reset();
+ ok(CustomizableUI.inDefaultState, "Should be back in default state");
+
+ historyButton = document.getElementById("wrapper-history-panelmenu");
+ devButton = document.getElementById("wrapper-developer-button");
+ ok(historyButton && devButton, "Draggable elements should exist");
+ simulateItemDrag(historyButton, devButton);
+
+ yield endCustomizing();
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js b/browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js
new file mode 100644
index 000000000..b5a325afb
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js
@@ -0,0 +1,31 @@
+"use strict";
+
+const BUTTONID = "test-seenwidget-post-reset";
+
+add_task(function*() {
+ CustomizableUI.createWidget({
+ id: BUTTONID,
+ label: "Test widget seen post reset",
+ defaultArea: CustomizableUI.AREA_NAVBAR
+ });
+
+ const kPrefCustomizationState = "browser.uiCustomization.state";
+ let bsPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+ ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should be seen after createWidget is called.");
+ CustomizableUI.reset();
+ ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should still be seen after reset.");
+ ok(!Services.prefs.prefHasUserValue(kPrefCustomizationState), "Pref shouldn't be set right now, because that'd break undo.");
+ CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
+ gCustomizeMode.removeFromArea(document.getElementById(BUTTONID));
+ let hasUserValue = Services.prefs.prefHasUserValue(kPrefCustomizationState);
+ ok(hasUserValue, "Pref should be set right now.");
+ if (hasUserValue) {
+ let seenArray = JSON.parse(Services.prefs.getCharPref(kPrefCustomizationState)).seen;
+ isnot(seenArray.indexOf(BUTTONID), -1, "Widget should be in saved 'seen' list.");
+ }
+});
+
+registerCleanupFunction(function() {
+ CustomizableUI.destroyWidget(BUTTONID);
+ CustomizableUI.reset();
+});
diff --git a/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js b/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js
new file mode 100644
index 000000000..42768debf
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1161838_inserted_new_default_buttons.js
@@ -0,0 +1,78 @@
+"use strict";
+
+// NB: This uses some ugly hacks to get into the CUI module from elsewhere...
+// don't try this at home, kids.
+function test() {
+ // Customize something to make sure stuff changed:
+ CustomizableUI.addWidgetToArea("feed-button", CustomizableUI.AREA_NAVBAR);
+
+ let CustomizableUIBSPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+
+ is(CustomizableUIBSPass.gFuturePlacements.size, 0,
+ "All future placements should be dealt with by now.");
+
+ let {CustomizableUIInternal, gFuturePlacements, gPalette} = CustomizableUIBSPass;
+
+ // Force us to have a saved state:
+ CustomizableUIInternal.saveState();
+ CustomizableUIInternal.loadSavedState();
+
+ CustomizableUIInternal._introduceNewBuiltinWidgets();
+ is(gFuturePlacements.size, 0,
+ "No change to future placements initially.");
+
+ // Add our widget to the defaults:
+ let testWidgetNew = {
+ id: "test-messing-with-default-placements-new-pref",
+ label: "Test messing with default placements - pref-based",
+ defaultArea: CustomizableUI.AREA_NAVBAR,
+ introducedInVersion: "pref",
+ };
+
+ let normalizedWidget = CustomizableUIInternal.normalizeWidget(testWidgetNew,
+ CustomizableUI.SOURCE_BUILTIN);
+ ok(normalizedWidget, "Widget should be normalizable");
+ if (!normalizedWidget) {
+ return;
+ }
+ CustomizableUIBSPass.gPalette.set(testWidgetNew.id, normalizedWidget);
+
+ // Now adjust default placements for area:
+ let navbarArea = CustomizableUIBSPass.gAreas.get(CustomizableUI.AREA_NAVBAR);
+ let navbarPlacements = navbarArea.get("defaultPlacements");
+ navbarPlacements.splice(navbarPlacements.indexOf("bookmarks-menu-button") + 1, 0, testWidgetNew.id);
+
+ let savedPlacements = CustomizableUIBSPass.gSavedState.placements[CustomizableUI.AREA_NAVBAR];
+ // Then call the re-init routine so we re-add the builtin widgets
+ CustomizableUIInternal._introduceNewBuiltinWidgets();
+ is(gFuturePlacements.size, 1,
+ "Should have 1 more future placement");
+ let futureNavbarPlacements = gFuturePlacements.get(CustomizableUI.AREA_NAVBAR);
+ ok(futureNavbarPlacements, "Should have placements for nav-bar");
+ if (futureNavbarPlacements) {
+ ok(futureNavbarPlacements.has(testWidgetNew.id), "widget should be in future placements");
+ }
+ CustomizableUIInternal._placeNewDefaultWidgetsInArea(CustomizableUI.AREA_NAVBAR);
+
+ let indexInSavedPlacements = savedPlacements.indexOf(testWidgetNew.id);
+ info("Saved placements: " + savedPlacements.join(', '));
+ isnot(indexInSavedPlacements, -1, "Widget should have been inserted");
+ is(indexInSavedPlacements, savedPlacements.indexOf("bookmarks-menu-button") + 1,
+ "Widget should be in the right place.");
+
+ if (futureNavbarPlacements) {
+ ok(!futureNavbarPlacements.has(testWidgetNew.id), "widget should be out of future placements");
+ }
+
+ if (indexInSavedPlacements != -1) {
+ savedPlacements.splice(indexInSavedPlacements, 1);
+ }
+
+ gFuturePlacements.delete(CustomizableUI.AREA_NAVBAR);
+ let indexInDefaultPlacements = navbarPlacements.indexOf(testWidgetNew.id);
+ if (indexInDefaultPlacements != -1) {
+ navbarPlacements.splice(indexInDefaultPlacements, 1);
+ }
+ gPalette.delete(testWidgetNew.id);
+ CustomizableUI.reset();
+}
diff --git a/browser/components/customizableui/test/browser_873501_handle_specials.js b/browser/components/customizableui/test/browser_873501_handle_specials.js
new file mode 100644
index 000000000..b07c8e0d7
--- /dev/null
+++ b/browser/components/customizableui/test/browser_873501_handle_specials.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";
+
+const kToolbarName = "test-specials-toolbar";
+
+registerCleanupFunction(removeCustomToolbars);
+
+// Add a toolbar with two springs and the downloads button.
+add_task(function* addToolbarWith2SpringsAndDownloadsButton() {
+ // Create the toolbar with a single spring:
+ createToolbarWithPlacements(kToolbarName, ["spring"]);
+ ok(document.getElementById(kToolbarName), "Toolbar should be created.");
+
+ // Check it's there with a generated ID:
+ assertAreaPlacements(kToolbarName, [/customizableui-special-spring\d+/]);
+ let [springId] = getAreaWidgetIds(kToolbarName);
+
+ // Add a second spring, check if that's there and doesn't share IDs
+ CustomizableUI.addWidgetToArea("spring", kToolbarName);
+ assertAreaPlacements(kToolbarName, [springId,
+ /customizableui-special-spring\d+/]);
+ let [, spring2Id] = getAreaWidgetIds(kToolbarName);
+
+ isnot(springId, spring2Id, "Springs shouldn't have identical IDs.");
+
+ // Try moving the downloads button to this new toolbar, between the two springs:
+ CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
+ assertAreaPlacements(kToolbarName, [springId, "downloads-button", spring2Id]);
+ yield removeCustomToolbars();
+});
+
+// Add separators around the downloads button.
+add_task(function* addSeparatorsAroundDownloadsButton() {
+ createToolbarWithPlacements(kToolbarName, ["separator"]);
+ ok(document.getElementById(kToolbarName), "Toolbar should be created.");
+
+ // Check it's there with a generated ID:
+ assertAreaPlacements(kToolbarName, [/customizableui-special-separator\d+/]);
+ let [separatorId] = getAreaWidgetIds(kToolbarName);
+
+ CustomizableUI.addWidgetToArea("separator", kToolbarName);
+ assertAreaPlacements(kToolbarName, [separatorId,
+ /customizableui-special-separator\d+/]);
+ let [, separator2Id] = getAreaWidgetIds(kToolbarName);
+
+ isnot(separatorId, separator2Id, "Separator ids shouldn't be equal.");
+
+ CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
+ assertAreaPlacements(kToolbarName, [separatorId, "downloads-button", separator2Id]);
+ yield removeCustomToolbars();
+});
+
+// Add spacers around the downloads button.
+add_task(function* addSpacersAroundDownloadsButton() {
+ createToolbarWithPlacements(kToolbarName, ["spacer"]);
+ ok(document.getElementById(kToolbarName), "Toolbar should be created.");
+
+ // Check it's there with a generated ID:
+ assertAreaPlacements(kToolbarName, [/customizableui-special-spacer\d+/]);
+ let [spacerId] = getAreaWidgetIds(kToolbarName);
+
+ CustomizableUI.addWidgetToArea("spacer", kToolbarName);
+ assertAreaPlacements(kToolbarName, [spacerId,
+ /customizableui-special-spacer\d+/]);
+ let [, spacer2Id] = getAreaWidgetIds(kToolbarName);
+
+ isnot(spacerId, spacer2Id, "Spacer ids shouldn't be equal.");
+
+ CustomizableUI.addWidgetToArea("downloads-button", kToolbarName, 1);
+ assertAreaPlacements(kToolbarName, [spacerId, "downloads-button", spacer2Id]);
+ yield removeCustomToolbars();
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
new file mode 100644
index 000000000..a3204c271
--- /dev/null
+++ b/browser/components/customizableui/test/browser_876926_customize_mode_wrapping.js
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 kXULWidgetId = "a-test-button"; // we'll create a button with this ID.
+const kAPIWidgetId = "feed-button";
+const kPanel = CustomizableUI.AREA_PANEL;
+const kToolbar = CustomizableUI.AREA_NAVBAR;
+const kVisiblePalette = "customization-palette";
+const kPlaceholderClass = "panel-customization-placeholder";
+
+function checkWrapper(id) {
+ is(document.querySelectorAll("#wrapper-" + id).length, 1, "There should be exactly 1 wrapper for " + id + " in the customizing window.");
+}
+
+var move = {
+ "drag": function(id, target) {
+ let targetNode = document.getElementById(target);
+ if (targetNode.customizationTarget) {
+ targetNode = targetNode.customizationTarget;
+ }
+ simulateItemDrag(document.getElementById(id), targetNode);
+ },
+ "dragToItem": function(id, target) {
+ let targetNode = document.getElementById(target);
+ if (targetNode.customizationTarget) {
+ targetNode = targetNode.customizationTarget;
+ }
+ let items = targetNode.querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
+ if (target == kPanel) {
+ targetNode = items[items.length - 1];
+ } else {
+ targetNode = items[0];
+ }
+ simulateItemDrag(document.getElementById(id), targetNode);
+ },
+ "API": function(id, target) {
+ if (target == kVisiblePalette) {
+ return CustomizableUI.removeWidgetFromArea(id);
+ }
+ return CustomizableUI.addWidgetToArea(id, target, null);
+ }
+};
+
+function isLast(containerId, defaultPlacements, id) {
+ assertAreaPlacements(containerId, defaultPlacements.concat([id]));
+ is(document.getElementById(containerId).customizationTarget.lastChild.firstChild.id, id,
+ "Widget " + id + " should be in " + containerId + " in customizing window.");
+ is(otherWin.document.getElementById(containerId).customizationTarget.lastChild.id, id,
+ "Widget " + id + " should be in " + containerId + " in other window.");
+}
+
+function getLastVisibleNodeInToolbar(containerId, win=window) {
+ let container = win.document.getElementById(containerId).customizationTarget;
+ let rv = container.lastChild;
+ while (rv && (rv.getAttribute('hidden') == 'true' || (rv.firstChild && rv.firstChild.getAttribute('hidden') == 'true'))) {
+ rv = rv.previousSibling;
+ }
+ return rv;
+}
+
+function isLastVisibleInToolbar(containerId, defaultPlacements, id) {
+ let newPlacements;
+ for (let i = defaultPlacements.length - 1; i >= 0; i--) {
+ let el = document.getElementById(defaultPlacements[i]);
+ if (el && el.getAttribute('hidden') != 'true') {
+ newPlacements = [...defaultPlacements];
+ newPlacements.splice(i + 1, 0, id);
+ break;
+ }
+ }
+ if (!newPlacements) {
+ assertAreaPlacements(containerId, defaultPlacements.concat([id]));
+ } else {
+ assertAreaPlacements(containerId, newPlacements);
+ }
+ is(getLastVisibleNodeInToolbar(containerId).firstChild.id, id,
+ "Widget " + id + " should be in " + containerId + " in customizing window.");
+ is(getLastVisibleNodeInToolbar(containerId, otherWin).id, id,
+ "Widget " + id + " should be in " + containerId + " in other window.");
+}
+
+function isFirst(containerId, defaultPlacements, id) {
+ assertAreaPlacements(containerId, [id].concat(defaultPlacements));
+ is(document.getElementById(containerId).customizationTarget.firstChild.firstChild.id, id,
+ "Widget " + id + " should be in " + containerId + " in customizing window.");
+ is(otherWin.document.getElementById(containerId).customizationTarget.firstChild.id, id,
+ "Widget " + id + " should be in " + containerId + " in other window.");
+}
+
+function checkToolbar(id, method) {
+ // Place at start of the toolbar:
+ let toolbarPlacements = getAreaWidgetIds(kToolbar);
+ move[method](id, kToolbar);
+ if (method == "dragToItem") {
+ isFirst(kToolbar, toolbarPlacements, id);
+ } else if (method == "drag") {
+ isLastVisibleInToolbar(kToolbar, toolbarPlacements, id);
+ } else {
+ isLast(kToolbar, toolbarPlacements, id);
+ }
+ checkWrapper(id);
+}
+
+function checkPanel(id, method) {
+ let panelPlacements = getAreaWidgetIds(kPanel);
+ move[method](id, kPanel);
+ let children = document.getElementById(kPanel).querySelectorAll("toolbarpaletteitem:not(." + kPlaceholderClass + ")");
+ let otherChildren = otherWin.document.getElementById(kPanel).children;
+ let newPlacements = panelPlacements.concat([id]);
+ // Relative position of the new item from the end:
+ let position = -1;
+ // For the drag to item case, we drag to the last item, making the dragged item the
+ // penultimate item. We can't well use the first item because the panel has complicated
+ // rules about rearranging wide items (which, by default, the first two items are).
+ if (method == "dragToItem") {
+ newPlacements.pop();
+ newPlacements.splice(panelPlacements.length - 1, 0, id);
+ position = -2;
+ }
+ assertAreaPlacements(kPanel, newPlacements);
+ is(children[children.length + position].firstChild.id, id,
+ "Widget " + id + " should be in " + kPanel + " in customizing window.");
+ is(otherChildren[otherChildren.length + position].id, id,
+ "Widget " + id + " should be in " + kPanel + " in other window.");
+ checkWrapper(id);
+}
+
+function checkPalette(id, method) {
+ // Move back to palette:
+ move[method](id, kVisiblePalette);
+ ok(CustomizableUI.inDefaultState, "Should end in default state");
+ let visibleChildren = gCustomizeMode.visiblePalette.children;
+ let expectedChild = method == "dragToItem" ? visibleChildren[0] : visibleChildren[visibleChildren.length - 1];
+ is(expectedChild.firstChild.id, id, "Widget " + id + " was moved using " + method + " and should now be wrapped in palette in customizing window.");
+ if (id == kXULWidgetId) {
+ ok(otherWin.gNavToolbox.palette.querySelector("#" + id), "Widget " + id + " should be in invisible palette in other window.");
+ }
+ checkWrapper(id);
+}
+
+// This test needs a XUL button that's in the palette by default. No such
+// button currently exists, so we create a simple one.
+function createXULButtonForWindow(win) {
+ createDummyXULButton(kXULWidgetId, "test-button", win);
+}
+
+function removeXULButtonForWindow(win) {
+ win.gNavToolbox.palette.querySelector(`#${kXULWidgetId}`).remove();
+}
+
+var otherWin;
+
+// Moving widgets in two windows, one with customize mode and one without, should work.
+add_task(function* MoveWidgetsInTwoWindows() {
+ yield startCustomizing();
+ otherWin = yield openAndLoadWindow(null, true);
+ yield otherWin.PanelUI.ensureReady();
+ // Create the XUL button to use in the test in both windows.
+ createXULButtonForWindow(window);
+ createXULButtonForWindow(otherWin);
+ ok(CustomizableUI.inDefaultState, "Should start in default state");
+
+ for (let widgetId of [kXULWidgetId, kAPIWidgetId]) {
+ for (let method of ["API", "drag", "dragToItem"]) {
+ info("Moving widget " + widgetId + " using " + method);
+ checkToolbar(widgetId, method);
+ checkPanel(widgetId, method);
+ checkPalette(widgetId, method);
+ checkPanel(widgetId, method);
+ checkToolbar(widgetId, method);
+ checkPalette(widgetId, method);
+ }
+ }
+ yield promiseWindowClosed(otherWin);
+ otherWin = null;
+ yield endCustomizing();
+ removeXULButtonForWindow(window);
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.js
new file mode 100644
index 000000000..ec454dc8d
--- /dev/null
+++ b/browser/components/customizableui/test/browser_876944_customize_mode_create_destroy.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";
+
+const kTestWidget1 = "test-customize-mode-create-destroy1";
+const kTestWidget2 = "test-customize-mode-create-destroy2";
+
+// Creating and destroying a widget should correctly wrap/unwrap stuff
+add_task(function* testWrapUnwrap() {
+ yield startCustomizing();
+ CustomizableUI.createWidget({id: kTestWidget1, label: 'Pretty label', tooltiptext: 'Pretty tooltip'});
+ let elem = document.getElementById(kTestWidget1);
+ let wrapper = document.getElementById("wrapper-" + kTestWidget1);
+ ok(elem, "There should be an item");
+ ok(wrapper, "There should be a wrapper");
+ is(wrapper.firstChild.id, kTestWidget1, "Wrapper should have test widget");
+ is(wrapper.parentNode.id, "customization-palette", "Wrapper should be in palette");
+ CustomizableUI.destroyWidget(kTestWidget1);
+ wrapper = document.getElementById("wrapper-" + kTestWidget1);
+ ok(!wrapper, "There should be a wrapper");
+ let item = document.getElementById(kTestWidget1);
+ ok(!item, "There should no longer be an item");
+});
+
+// Creating and destroying a widget should correctly deal with panel placeholders
+add_task(function* testPanelPlaceholders() {
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ // The value of expectedPlaceholders depends on the default palette layout.
+ // Bug 1229236 is for these tests to be smarter so the test doesn't need to
+ // change when the default placements change.
+ let expectedPlaceholders = 1 + (isInDevEdition() ? 1 : 0);
+ is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct.");
+ CustomizableUI.createWidget({id: kTestWidget2, label: 'Pretty label', tooltiptext: 'Pretty tooltip', defaultArea: CustomizableUI.AREA_PANEL});
+ let elem = document.getElementById(kTestWidget2);
+ let wrapper = document.getElementById("wrapper-" + kTestWidget2);
+ ok(elem, "There should be an item");
+ ok(wrapper, "There should be a wrapper");
+ is(wrapper.firstChild.id, kTestWidget2, "Wrapper should have test widget");
+ is(wrapper.parentNode, panel, "Wrapper should be in panel");
+ expectedPlaceholders = isInDevEdition() ? 1 : 3;
+ is(panel.querySelectorAll(".panel-customization-placeholder").length, expectedPlaceholders, "The number of placeholders should be correct.");
+ CustomizableUI.destroyWidget(kTestWidget2);
+ wrapper = document.getElementById("wrapper-" + kTestWidget2);
+ ok(!wrapper, "There should be a wrapper");
+ let item = document.getElementById(kTestWidget2);
+ ok(!item, "There should no longer be an item");
+ yield endCustomizing();
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ try {
+ CustomizableUI.destroyWidget(kTestWidget1);
+ } catch (ex) {}
+ try {
+ CustomizableUI.destroyWidget(kTestWidget2);
+ } catch (ex) {}
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_877006_missing_view.js b/browser/components/customizableui/test/browser_877006_missing_view.js
new file mode 100644
index 000000000..a1495c1fe
--- /dev/null
+++ b/browser/components/customizableui/test/browser_877006_missing_view.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Should be able to add broken view widget
+add_task(function testAddbrokenViewWidget() {
+ const kWidgetId = 'test-877006-broken-widget';
+ let widgetSpec = {
+ id: kWidgetId,
+ type: 'view',
+ viewId: 'idontexist',
+ /* Empty handler so we try to attach it maybe? */
+ onViewShowing: function() {
+ }
+ };
+
+ let noError = true;
+ try {
+ CustomizableUI.createWidget(widgetSpec);
+ CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
+ } catch (ex) {
+ Cu.reportError(ex);
+ noError = false;
+ }
+ ok(noError, "Should not throw an exception trying to add a broken view widget.");
+
+ noError = true;
+ try {
+ CustomizableUI.destroyWidget(kWidgetId);
+ } catch (ex) {
+ Cu.reportError(ex);
+ noError = false;
+ }
+ ok(noError, "Should not throw an exception trying to remove the broken view widget.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_877178_unregisterArea.js b/browser/components/customizableui/test/browser_877178_unregisterArea.js
new file mode 100644
index 000000000..28037787b
--- /dev/null
+++ b/browser/components/customizableui/test/browser_877178_unregisterArea.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";
+
+registerCleanupFunction(removeCustomToolbars);
+
+// Sanity checks
+add_task(function sanityChecks() {
+ SimpleTest.doesThrow(() => CustomizableUI.registerArea("@foo"),
+ "Registering areas with an invalid ID should throw.");
+
+ SimpleTest.doesThrow(() => CustomizableUI.registerArea([]),
+ "Registering areas with an invalid ID should throw.");
+
+ SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("@foo"),
+ "Unregistering areas with an invalid ID should throw.");
+
+ SimpleTest.doesThrow(() => CustomizableUI.unregisterArea([]),
+ "Unregistering areas with an invalid ID should throw.");
+
+ SimpleTest.doesThrow(() => CustomizableUI.unregisterArea("unknown"),
+ "Unregistering an area that's not registered should throw.");
+});
+
+// Check areas are loaded with their default placements.
+add_task(function checkLoadedAres() {
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+});
+
+// Check registering and unregistering a new area.
+add_task(function checkRegisteringAndUnregistering() {
+ const kToolbarId = "test-registration-toolbar";
+ const kButtonId = "test-registration-button";
+ createDummyXULButton(kButtonId);
+ createToolbarWithPlacements(kToolbarId, ["spring", kButtonId, "spring"]);
+ assertAreaPlacements(kToolbarId,
+ [/customizableui-special-spring\d+/,
+ kButtonId,
+ /customizableui-special-spring\d+/]);
+ ok(!CustomizableUI.inDefaultState, "With a new toolbar it is no longer in a default state.");
+ removeCustomToolbars(); // Will call unregisterArea for us
+ ok(CustomizableUI.inDefaultState, "When the toolbar is unregistered, " +
+ "everything will return to the default state.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_877447_skip_missing_ids.js b/browser/components/customizableui/test/browser_877447_skip_missing_ids.js
new file mode 100644
index 000000000..0cba7ae4f
--- /dev/null
+++ b/browser/components/customizableui/test/browser_877447_skip_missing_ids.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";
+
+registerCleanupFunction(removeCustomToolbars);
+
+add_task(function skipMissingIDS() {
+ const kButtonId = "look-at-me-disappear-button";
+ CustomizableUI.reset();
+ ok(CustomizableUI.inDefaultState, "Should be in the default state.");
+ let btn = createDummyXULButton(kButtonId, "Gone!");
+ CustomizableUI.addWidgetToArea(kButtonId, CustomizableUI.AREA_NAVBAR);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in the default state.");
+ is(btn.parentNode.parentNode.id, CustomizableUI.AREA_NAVBAR, "Button should be in navbar");
+ btn.remove();
+ is(btn.parentNode, null, "Button is no longer in the navbar");
+ ok(CustomizableUI.inDefaultState, "Should be back in the default state, " +
+ "despite unknown button ID in placements.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_878452_drag_to_panel.js b/browser/components/customizableui/test/browser_878452_drag_to_panel.js
new file mode 100644
index 000000000..8a8d82294
--- /dev/null
+++ b/browser/components/customizableui/test/browser_878452_drag_to_panel.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";
+
+// Dragging an item from the palette to another button in the panel should work.
+add_task(function*() {
+ yield startCustomizing();
+ let btn = document.getElementById("feed-button");
+ let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+
+ let lastButtonIndex = placements.length - 1;
+ let lastButton = placements[lastButtonIndex];
+ let placementsAfterInsert = placements.slice(0, lastButtonIndex).concat(["feed-button", lastButton]);
+ let lastButtonNode = document.getElementById(lastButton);
+ simulateItemDrag(btn, lastButtonNode);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let palette = document.getElementById("customization-palette");
+ simulateItemDrag(btn, palette);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Dragging an item from the palette to the panel itself should also work.
+add_task(function*() {
+ yield startCustomizing();
+ let btn = document.getElementById("feed-button");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+
+ let placementsAfterAppend = placements.concat(["feed-button"]);
+ simulateItemDrag(btn, panel);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let palette = document.getElementById("customization-palette");
+ simulateItemDrag(btn, palette);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Dragging an item from the palette to an empty panel should also work.
+add_task(function*() {
+ let widgetIds = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+ while (widgetIds.length) {
+ CustomizableUI.removeWidgetFromArea(widgetIds.shift());
+ }
+ yield startCustomizing();
+ let btn = document.getElementById("feed-button");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+
+ assertAreaPlacements(panel.id, []);
+
+ let placementsAfterAppend = ["feed-button"];
+ simulateItemDrag(btn, panel);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let palette = document.getElementById("customization-palette");
+ simulateItemDrag(btn, palette);
+ assertAreaPlacements(panel.id, []);
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_880164_customization_context_menus.js b/browser/components/customizableui/test/browser_880164_customization_context_menus.js
new file mode 100644
index 000000000..57a0db773
--- /dev/null
+++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.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";
+
+requestLongerTimeout(2);
+
+const isOSX = (Services.appinfo.OS === "Darwin");
+
+// Right-click on the home button should
+// show a context menu with options to move it.
+add_task(function*() {
+ let contextMenu = document.getElementById("toolbar-context-menu");
+ let shownPromise = popupShown(contextMenu);
+ let homeButton = document.getElementById("home-button");
+ EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2 });
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-moveToPanel", true],
+ [".customize-context-removeFromToolbar", true],
+ ["---"]
+ ];
+ if (!isOSX) {
+ expectedEntries.push(["#toggle_toolbar-menubar", true]);
+ }
+ expectedEntries.push(
+ ["#toggle_PersonalToolbar", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ );
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenPromise;
+});
+
+// Right-click on an empty bit of tabstrip should
+// show a context menu without options to move it,
+// but with tab-specific options instead.
+add_task(function*() {
+ // ensure there are tabs to reload/bookmark:
+ let extraTab = gBrowser.selectedTab = gBrowser.addTab();
+ yield promiseTabLoadEvent(extraTab, "http://example.com/");
+ let contextMenu = document.getElementById("toolbar-context-menu");
+ let shownPromise = popupShown(contextMenu);
+ let tabstrip = document.getElementById("tabbrowser-tabs");
+ let rect = tabstrip.getBoundingClientRect();
+ EventUtils.synthesizeMouse(tabstrip, rect.width - 2, 2, {type: "contextmenu", button: 2 });
+ yield shownPromise;
+
+ let closedTabsAvailable = SessionStore.getClosedTabCount(window) == 0;
+ info("Closed tabs: " + closedTabsAvailable);
+ let expectedEntries = [
+ ["#toolbar-context-reloadAllTabs", true],
+ ["#toolbar-context-bookmarkAllTabs", true],
+ ["#toolbar-context-undoCloseTab", !closedTabsAvailable],
+ ["---"]
+ ];
+ if (!isOSX) {
+ expectedEntries.push(["#toggle_toolbar-menubar", true]);
+ }
+ expectedEntries.push(
+ ["#toggle_PersonalToolbar", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ );
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenPromise;
+ gBrowser.removeTab(extraTab);
+});
+
+// Right-click on an empty bit of extra toolbar should
+// show a context menu with moving options disabled,
+// and a toggle option for the extra toolbar
+add_task(function*() {
+ let contextMenu = document.getElementById("toolbar-context-menu");
+ let shownPromise = popupShown(contextMenu);
+ let toolbar = createToolbarWithPlacements("880164_empty_toolbar", []);
+ toolbar.setAttribute("context", "toolbar-context-menu");
+ toolbar.setAttribute("toolbarname", "Fancy Toolbar for Context Menu");
+ EventUtils.synthesizeMouseAtCenter(toolbar, {type: "contextmenu", button: 2 });
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-moveToPanel", false],
+ [".customize-context-removeFromToolbar", false],
+ ["---"]
+ ];
+ if (!isOSX) {
+ expectedEntries.push(["#toggle_toolbar-menubar", true]);
+ }
+ expectedEntries.push(
+ ["#toggle_PersonalToolbar", true],
+ ["#toggle_880164_empty_toolbar", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ );
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenPromise;
+ removeCustomToolbars();
+});
+
+
+// Right-click on the urlbar-container should
+// show a context menu with disabled options to move it.
+add_task(function*() {
+ let contextMenu = document.getElementById("toolbar-context-menu");
+ let shownPromise = popupShown(contextMenu);
+ let urlBarContainer = document.getElementById("urlbar-container");
+ // Need to make sure not to click within an edit field.
+ EventUtils.synthesizeMouse(urlBarContainer, 100, 1, {type: "contextmenu", button: 2 });
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-moveToPanel", false],
+ [".customize-context-removeFromToolbar", false],
+ ["---"]
+ ];
+ if (!isOSX) {
+ expectedEntries.push(["#toggle_toolbar-menubar", true]);
+ }
+ expectedEntries.push(
+ ["#toggle_PersonalToolbar", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ );
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenPromise;
+});
+
+// Right-click on the searchbar and moving it to the menu
+// and back should move the search-container instead.
+add_task(function*() {
+ let searchbar = document.getElementById("searchbar");
+ gCustomizeMode.addToPanel(searchbar);
+ let placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
+
+ let shownPanelPromise = promisePanelShown(window);
+ PanelUI.toggle({type: "command"});
+ yield shownPanelPromise;
+ let hiddenPanelPromise = promisePanelHidden(window);
+ PanelUI.toggle({type: "command"});
+ yield hiddenPanelPromise;
+
+ gCustomizeMode.addToToolbar(searchbar);
+ placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in navbar");
+ gCustomizeMode.removeFromArea(searchbar);
+ placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement, null, "Should be in palette");
+ CustomizableUI.reset();
+ placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in navbar");
+});
+
+// Right-click on an item within the menu panel should
+// show a context menu with options to move it.
+add_task(function*() {
+ let shownPanelPromise = promisePanelShown(window);
+ PanelUI.toggle({type: "command"});
+ yield shownPanelPromise;
+
+ let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+ let shownContextPromise = popupShown(contextMenu);
+ let newWindowButton = document.getElementById("new-window-button");
+ ok(newWindowButton, "new-window-button was found");
+ EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
+ yield shownContextPromise;
+
+ is(PanelUI.panel.state, "open", "The PanelUI should still be open.");
+
+ let expectedEntries = [
+ [".customize-context-moveToToolbar", true],
+ [".customize-context-removeFromPanel", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ ];
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+
+ let hiddenPromise = promisePanelHidden(window);
+ PanelUI.toggle({type: "command"});
+ yield hiddenPromise;
+});
+
+// Right-click on the home button while in customization mode
+// should show a context menu with options to move it.
+add_task(function*() {
+ yield startCustomizing();
+ let contextMenu = document.getElementById("toolbar-context-menu");
+ let shownPromise = popupShown(contextMenu);
+ let homeButton = document.getElementById("wrapper-home-button");
+ EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2});
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-moveToPanel", true],
+ [".customize-context-removeFromToolbar", true],
+ ["---"]
+ ];
+ if (!isOSX) {
+ expectedEntries.push(["#toggle_toolbar-menubar", true]);
+ }
+ expectedEntries.push(
+ ["#toggle_PersonalToolbar", true],
+ ["---"],
+ [".viewCustomizeToolbar", false]
+ );
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+});
+
+// Right-click on an item in the palette should
+// show a context menu with options to move it.
+add_task(function*() {
+ let contextMenu = document.getElementById("customizationPaletteItemContextMenu");
+ let shownPromise = popupShown(contextMenu);
+ let openFileButton = document.getElementById("wrapper-open-file-button");
+ EventUtils.synthesizeMouse(openFileButton, 2, 2, {type: "contextmenu", button: 2});
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-addToToolbar", true],
+ [".customize-context-addToPanel", true]
+ ];
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+});
+
+// Right-click on an item in the panel while in customization mode
+// should show a context menu with options to move it.
+add_task(function*() {
+ let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+ let shownPromise = popupShown(contextMenu);
+ let newWindowButton = document.getElementById("wrapper-new-window-button");
+ EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-moveToToolbar", true],
+ [".customize-context-removeFromPanel", true],
+ ["---"],
+ [".viewCustomizeToolbar", false]
+ ];
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+ yield endCustomizing();
+});
+
+// Test the toolbarbutton panel context menu in customization mode
+// without opening the panel before customization mode
+add_task(function*() {
+ this.otherWin = yield openAndLoadWindow(null, true);
+
+ yield new Promise(resolve => waitForFocus(resolve, this.otherWin));
+
+ yield startCustomizing(this.otherWin);
+
+ let contextMenu = this.otherWin.document.getElementById("customizationPanelItemContextMenu");
+ let shownPromise = popupShown(contextMenu);
+ let newWindowButton = this.otherWin.document.getElementById("wrapper-new-window-button");
+ EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2}, this.otherWin);
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-moveToToolbar", true],
+ [".customize-context-removeFromPanel", true],
+ ["---"],
+ [".viewCustomizeToolbar", false]
+ ];
+ checkContextMenu(contextMenu, expectedEntries, this.otherWin);
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+ yield endCustomizing(this.otherWin);
+ yield promiseWindowClosed(this.otherWin);
+ this.otherWin = null;
+
+ yield new Promise(resolve => waitForFocus(resolve, window));
+});
+
+// Bug 945191 - Combined buttons show wrong context menu options
+// when they are in the toolbar.
+add_task(function*() {
+ yield startCustomizing();
+ let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+ let shownPromise = popupShown(contextMenu);
+ let zoomControls = document.getElementById("wrapper-zoom-controls");
+ EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
+ yield shownPromise;
+ // Execute the command to move the item from the panel to the toolbar.
+ contextMenu.childNodes[0].doCommand();
+ let hiddenPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenPromise;
+ yield endCustomizing();
+
+ zoomControls = document.getElementById("zoom-controls");
+ is(zoomControls.parentNode.id, "nav-bar-customization-target", "Zoom-controls should be on the nav-bar");
+
+ contextMenu = document.getElementById("toolbar-context-menu");
+ shownPromise = popupShown(contextMenu);
+ EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
+ yield shownPromise;
+
+ let expectedEntries = [
+ [".customize-context-moveToPanel", true],
+ [".customize-context-removeFromToolbar", true],
+ ["---"]
+ ];
+ if (!isOSX) {
+ expectedEntries.push(["#toggle_toolbar-menubar", true]);
+ }
+ expectedEntries.push(
+ ["#toggle_PersonalToolbar", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ );
+ checkContextMenu(contextMenu, expectedEntries);
+
+ hiddenPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenPromise;
+ yield resetCustomization();
+});
+
+// Bug 947586 - After customization, panel items show wrong context menu options
+add_task(function*() {
+ yield startCustomizing();
+ yield endCustomizing();
+
+ yield PanelUI.show();
+
+ let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+ let shownContextPromise = popupShown(contextMenu);
+ let newWindowButton = document.getElementById("new-window-button");
+ ok(newWindowButton, "new-window-button was found");
+ EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
+ yield shownContextPromise;
+
+ is(PanelUI.panel.state, "open", "The PanelUI should still be open.");
+
+ let expectedEntries = [
+ [".customize-context-moveToToolbar", true],
+ [".customize-context-removeFromPanel", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ ];
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+
+ let hiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield hiddenPromise;
+});
+
+
+// Bug 982027 - moving icon around removes custom context menu.
+add_task(function*() {
+ let widgetId = "custom-context-menu-toolbarbutton";
+ let expectedContext = "myfancycontext";
+ let widget = createDummyXULButton(widgetId, "Test ctxt menu");
+ widget.setAttribute("context", expectedContext);
+ CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR);
+ is(widget.getAttribute("context"), expectedContext, "Should have context menu when added to the toolbar.");
+
+ yield startCustomizing();
+ is(widget.getAttribute("context"), "", "Should not have own context menu in the toolbar now that we're customizing.");
+ is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped when in toolbar.");
+
+ let panel = PanelUI.contents;
+ simulateItemDrag(widget, panel);
+ is(widget.getAttribute("context"), "", "Should not have own context menu when in the panel.");
+ is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're in the panel.");
+
+ simulateItemDrag(widget, document.getElementById("nav-bar").customizationTarget);
+ is(widget.getAttribute("context"), "", "Should not have own context menu when back in toolbar because we're still customizing.");
+ is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're back in the toolbar.");
+
+ yield endCustomizing();
+ is(widget.getAttribute("context"), expectedContext, "Should have context menu again now that we're out of customize mode.");
+ CustomizableUI.removeWidgetFromArea(widgetId);
+ widget.remove();
+ ok(CustomizableUI.inDefaultState, "Should be in default state after removing button.");
+});
diff --git a/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
new file mode 100644
index 000000000..9057d0557
--- /dev/null
+++ b/browser/components/customizableui/test/browser_880382_drag_wide_widgets_in_panel.js
@@ -0,0 +1,497 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+requestLongerTimeout(5);
+
+// Dragging the zoom controls to be before the print button should not move any controls.
+add_task(function*() {
+ yield startCustomizing();
+ let zoomControls = document.getElementById("zoom-controls");
+ let printButton = document.getElementById("print-button");
+ let placementsAfterMove = ["edit-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "zoom-controls",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(zoomControls, printButton);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let newWindowButton = document.getElementById("new-window-button");
+ simulateItemDrag(zoomControls, newWindowButton);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Dragging the zoom controls to be before the save button should not move any controls.
+add_task(function*() {
+ yield startCustomizing();
+ let zoomControls = document.getElementById("zoom-controls");
+ let savePageButton = document.getElementById("save-page-button");
+ let placementsAfterMove = ["edit-controls",
+ "zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(zoomControls, savePageButton);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ ok(CustomizableUI.inDefaultState, "Should be in default state.");
+});
+
+
+// Dragging the zoom controls to be before the new-window button should not move any widgets.
+add_task(function*() {
+ yield startCustomizing();
+ let zoomControls = document.getElementById("zoom-controls");
+ let newWindowButton = document.getElementById("new-window-button");
+ let placementsAfterMove = ["edit-controls",
+ "zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(zoomControls, newWindowButton);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging the zoom controls to be before the history-panelmenu should move the zoom-controls in to the row higher than the history-panelmenu.
+add_task(function*() {
+ yield startCustomizing();
+ let zoomControls = document.getElementById("zoom-controls");
+ let historyPanelMenu = document.getElementById("history-panelmenu");
+ let placementsAfterMove = ["edit-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "zoom-controls",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(zoomControls, historyPanelMenu);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let newWindowButton = document.getElementById("new-window-button");
+ simulateItemDrag(zoomControls, newWindowButton);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Dragging the zoom controls to be before the preferences-button should move the zoom-controls
+// in to the row higher than the preferences-button.
+add_task(function*() {
+ yield startCustomizing();
+ let zoomControls = document.getElementById("zoom-controls");
+ let preferencesButton = document.getElementById("preferences-button");
+ let placementsAfterMove = ["edit-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "zoom-controls",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(zoomControls, preferencesButton);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let newWindowButton = document.getElementById("new-window-button");
+ simulateItemDrag(zoomControls, newWindowButton);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Dragging an item from the palette to before the zoom-controls should move it and two other buttons before the zoom controls.
+add_task(function*() {
+ yield startCustomizing();
+ let openFileButton = document.getElementById("open-file-button");
+ let zoomControls = document.getElementById("zoom-controls");
+ let placementsAfterInsert = ["edit-controls",
+ "open-file-button",
+ "new-window-button",
+ "privatebrowsing-button",
+ "zoom-controls",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterInsert);
+ simulateItemDrag(openFileButton, zoomControls);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let palette = document.getElementById("customization-palette");
+ // Check that the palette items are re-wrapped correctly.
+ let feedWrapper = document.getElementById("wrapper-feed-button");
+ let feedButton = document.getElementById("feed-button");
+ is(feedButton.parentNode, feedWrapper,
+ "feed-button should be a child of wrapper-feed-button");
+ is(feedWrapper.getAttribute("place"), "palette",
+ "The feed-button wrapper should have it's place set to 'palette'");
+ simulateItemDrag(openFileButton, palette);
+ is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
+ "The open-file-button should be wrapped by a toolbarpaletteitem");
+ let newWindowButton = document.getElementById("new-window-button");
+ simulateItemDrag(zoomControls, newWindowButton);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Dragging an item from the palette to before the edit-controls
+// should move it and two other buttons before the edit and zoom controls.
+add_task(function*() {
+ yield startCustomizing();
+ let openFileButton = document.getElementById("open-file-button");
+ let editControls = document.getElementById("edit-controls");
+ let placementsAfterInsert = ["open-file-button",
+ "new-window-button",
+ "privatebrowsing-button",
+ "edit-controls",
+ "zoom-controls",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterInsert);
+ simulateItemDrag(openFileButton, editControls);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterInsert);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ let palette = document.getElementById("customization-palette");
+ // Check that the palette items are re-wrapped correctly.
+ let feedWrapper = document.getElementById("wrapper-feed-button");
+ let feedButton = document.getElementById("feed-button");
+ is(feedButton.parentNode, feedWrapper,
+ "feed-button should be a child of wrapper-feed-button");
+ is(feedWrapper.getAttribute("place"), "palette",
+ "The feed-button wrapper should have it's place set to 'palette'");
+ simulateItemDrag(openFileButton, palette);
+ is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
+ "The open-file-button should be wrapped by a toolbarpaletteitem");
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Dragging the edit-controls to be before the zoom-controls button
+// should not move any widgets.
+add_task(function*() {
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let zoomControls = document.getElementById("zoom-controls");
+ let placementsAfterMove = ["edit-controls",
+ "zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(editControls, zoomControls);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging the edit-controls to be before the new-window-button should
+// move the zoom-controls before the edit-controls.
+add_task(function*() {
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let newWindowButton = document.getElementById("new-window-button");
+ let placementsAfterMove = ["zoom-controls",
+ "edit-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(editControls, newWindowButton);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(editControls, zoomControls);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging the edit-controls to be before the privatebrowsing-button
+// should move the edit-controls in to the row higher than the
+// privatebrowsing-button.
+add_task(function*() {
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let privateBrowsingButton = document.getElementById("privatebrowsing-button");
+ let placementsAfterMove = ["zoom-controls",
+ "edit-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(editControls, privateBrowsingButton);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(editControls, zoomControls);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging the edit-controls to be before the save-page-button
+// should move the edit-controls in to the row higher than the
+// save-page-button.
+add_task(function*() {
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let savePageButton = document.getElementById("save-page-button");
+ let placementsAfterMove = ["zoom-controls",
+ "edit-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(editControls, savePageButton);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(editControls, zoomControls);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging the edit-controls to the panel itself should append
+// the edit controls to the bottom of the panel.
+add_task(function*() {
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let placementsAfterMove = ["zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "edit-controls",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(editControls, panel);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(editControls, zoomControls);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging the edit-controls to the customization-palette and
+// back should work.
+add_task(function*() {
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let palette = document.getElementById("customization-palette");
+ let placementsAfterMove = ["zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "developer-button",
+ "sync-button",
+ ];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ let paletteChildElementCount = palette.childElementCount;
+ simulateItemDrag(editControls, palette);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ is(paletteChildElementCount + 1, palette.childElementCount,
+ "The palette should have a new child, congratulations!");
+ is(editControls.parentNode.id, "wrapper-edit-controls",
+ "The edit-controls should be properly wrapped.");
+ is(editControls.parentNode.getAttribute("place"), "palette",
+ "The edit-controls should have the place of 'palette'.");
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(editControls, zoomControls);
+ is(paletteChildElementCount, palette.childElementCount,
+ "The palette child count should have returned to its prior value.");
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging the edit-controls to each of the panel placeholders
+// should append the edit-controls to the bottom of the panel.
+add_task(function*() {
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let numPlaceholders = 2;
+ for (let i = 0; i < numPlaceholders; i++) {
+ // This test relies on there being a specific number of widgets in the
+ // panel. The addition of sync-button screwed this up, so we remove it
+ // here. We should either fix the tests to not rely on the specific layout,
+ // or fix bug 1007910 which would change the placeholder logic in different
+ // ways. Bug 1229236 is for these tests to be smarter.
+ CustomizableUI.removeWidgetFromArea("sync-button");
+ // NB: We can't just iterate over all of the placeholders
+ // because each drag-drop action recreates them.
+ let placeholder = panel.getElementsByClassName("panel-customization-placeholder")[i];
+ let placementsAfterMove = ["zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "edit-controls",
+ "developer-button"];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(editControls, placeholder);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(editControls, zoomControls);
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+ }
+});
+
+// Dragging the open-file-button back on to itself should work.
+add_task(function*() {
+ yield startCustomizing();
+ let openFileButton = document.getElementById("open-file-button");
+ is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
+ "open-file-button should be wrapped by a toolbarpaletteitem");
+ simulateItemDrag(openFileButton, openFileButton);
+ is(openFileButton.parentNode.tagName, "toolbarpaletteitem",
+ "open-file-button should be wrapped by a toolbarpaletteitem");
+ let editControls = document.getElementById("edit-controls");
+ is(editControls.parentNode.tagName, "toolbarpaletteitem",
+ "edit-controls should be wrapped by a toolbarpaletteitem");
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+// Dragging a small button onto the last big button should work.
+add_task(function*() {
+ // Bug 1007910 requires there be a placeholder on the final row for this
+ // test to work as written. The addition of sync-button meant that's not true
+ // so we remove it from here. Bug 1229236 is for these tests to be smarter.
+ CustomizableUI.removeWidgetFromArea("sync-button");
+ yield startCustomizing();
+ let editControls = document.getElementById("edit-controls");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let target = panel.getElementsByClassName("panel-customization-placeholder")[0];
+ let placementsAfterMove = ["zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "edit-controls",
+ "developer-button"];
+ removeDeveloperButtonIfDevEdition(placementsAfterMove);
+ simulateItemDrag(editControls, target);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+ let itemToDrag = "email-link-button"; // any button in the palette by default.
+ let button = document.getElementById(itemToDrag);
+ placementsAfterMove.splice(11, 0, itemToDrag);
+ simulateItemDrag(button, editControls);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterMove);
+
+ // Put stuff back:
+ let palette = document.getElementById("customization-palette");
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(button, palette);
+ simulateItemDrag(editControls, zoomControls);
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_884402_customize_from_overflow.js b/browser/components/customizableui/test/browser_884402_customize_from_overflow.js
new file mode 100644
index 000000000..f50767c06
--- /dev/null
+++ b/browser/components/customizableui/test/browser_884402_customize_from_overflow.js
@@ -0,0 +1,81 @@
+"use strict";
+
+var overflowPanel = document.getElementById("widget-overflow");
+
+const isOSX = (Services.appinfo.OS === "Darwin");
+
+var originalWindowWidth;
+registerCleanupFunction(function() {
+ overflowPanel.removeAttribute("animate");
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+});
+
+// Right-click on an item within the overflow panel should
+// show a context menu with options to move it.
+add_task(function*() {
+
+ overflowPanel.setAttribute("animate", "false");
+
+ originalWindowWidth = window.outerWidth;
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ window.resizeTo(400, window.outerHeight);
+
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+ let chevron = document.getElementById("nav-bar-overflow-button");
+ let shownPanelPromise = promisePanelElementShown(window, overflowPanel);
+ chevron.click();
+ yield shownPanelPromise;
+
+ let contextMenu = document.getElementById("toolbar-context-menu");
+ let shownContextPromise = popupShown(contextMenu);
+ let homeButton = document.getElementById("home-button");
+ ok(homeButton, "home-button was found");
+ is(homeButton.getAttribute("overflowedItem"), "true", "Home button is overflowing");
+ EventUtils.synthesizeMouse(homeButton, 2, 2, {type: "contextmenu", button: 2});
+ yield shownContextPromise;
+
+ is(overflowPanel.state, "open", "The widget overflow panel should still be open.");
+
+ let expectedEntries = [
+ [".customize-context-moveToPanel", true],
+ [".customize-context-removeFromToolbar", true],
+ ["---"]
+ ];
+ if (!isOSX) {
+ expectedEntries.push(["#toggle_toolbar-menubar", true]);
+ }
+ expectedEntries.push(
+ ["#toggle_PersonalToolbar", true],
+ ["---"],
+ [".viewCustomizeToolbar", true]
+ );
+ checkContextMenu(contextMenu, expectedEntries);
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ let hiddenPromise = promisePanelElementHidden(window, overflowPanel);
+ let moveToPanel = contextMenu.querySelector(".customize-context-moveToPanel");
+ if (moveToPanel) {
+ moveToPanel.click();
+ }
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+ yield hiddenPromise;
+
+ let homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button");
+ ok(homeButtonPlacement, "Home button should still have a placement");
+ is(homeButtonPlacement && homeButtonPlacement.area, "PanelUI-contents", "Home button should be in the panel now");
+ CustomizableUI.reset();
+
+ // In some cases, it can take a tick for the navbar to overflow again. Wait for it:
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+ homeButtonPlacement = CustomizableUI.getPlacementOfWidget("home-button");
+ ok(homeButtonPlacement, "Home button should still have a placement");
+ is(homeButtonPlacement && homeButtonPlacement.area, "nav-bar", "Home button should be back in the navbar now");
+
+ is(homeButton.getAttribute("overflowedItem"), "true", "Home button should still be overflowed");
+});
diff --git a/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js b/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js
new file mode 100644
index 000000000..ea6f5a4e3
--- /dev/null
+++ b/browser/components/customizableui/test/browser_885052_customize_mode_observers_disabed.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function isFullscreenSizeMode() {
+ let sizemode = document.documentElement.getAttribute("sizemode");
+ return sizemode == "fullscreen";
+}
+
+// Observers should be disabled when in customization mode.
+add_task(function*() {
+ // Open and close the panel to make sure that the
+ // area is generated before getting a child of the area.
+ let shownPanelPromise = promisePanelShown(window);
+ PanelUI.toggle({type: "command"});
+ yield shownPanelPromise;
+ let hiddenPanelPromise = promisePanelHidden(window);
+ PanelUI.toggle({type: "command"});
+ yield hiddenPanelPromise;
+
+ let fullscreenButton = document.getElementById("fullscreen-button");
+ ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.")
+ ok(!isFullscreenSizeMode(), "Should not be in fullscreen sizemode before we enter fullscreen.");
+
+ BrowserFullScreen();
+ yield waitForCondition(() => isFullscreenSizeMode());
+ ok(fullscreenButton.checked, "Fullscreen button should be checked when in fullscreen.")
+
+ yield startCustomizing();
+
+ let fullscreenButtonWrapper = document.getElementById("wrapper-fullscreen-button");
+ ok(fullscreenButtonWrapper.hasAttribute("itemobserves"), "Observer should be moved to wrapper");
+ fullscreenButton = document.getElementById("fullscreen-button");
+ ok(!fullscreenButton.hasAttribute("observes"), "Observer should be removed from button");
+ ok(!fullscreenButton.checked, "Fullscreen button should no longer be checked during customization mode");
+
+ yield endCustomizing();
+
+ BrowserFullScreen();
+ fullscreenButton = document.getElementById("fullscreen-button");
+ yield waitForCondition(() => !isFullscreenSizeMode());
+ ok(!fullscreenButton.checked, "Fullscreen button should not be checked when not in fullscreen.")
+});
diff --git a/browser/components/customizableui/test/browser_885530_showInPrivateBrowsing.js b/browser/components/customizableui/test/browser_885530_showInPrivateBrowsing.js
new file mode 100644
index 000000000..e55c21862
--- /dev/null
+++ b/browser/components/customizableui/test/browser_885530_showInPrivateBrowsing.js
@@ -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/. */
+
+"use strict";
+
+const kWidgetId = "some-widget";
+
+function assertWidgetExists(aWindow, aExists) {
+ if (aExists) {
+ ok(aWindow.document.getElementById(kWidgetId),
+ "Should have found test widget in the window");
+ } else {
+ is(aWindow.document.getElementById(kWidgetId), null,
+ "Should not have found test widget in the window");
+ }
+}
+
+// A widget that is created with showInPrivateBrowsing undefined should
+// have that value default to true.
+add_task(function() {
+ let wrapper = CustomizableUI.createWidget({
+ id: kWidgetId
+ });
+ ok(wrapper.showInPrivateBrowsing,
+ "showInPrivateBrowsing should have defaulted to true.");
+ CustomizableUI.destroyWidget(kWidgetId);
+});
+
+// Add a widget via the API with showInPrivateBrowsing set to false
+// and ensure it does not appear in pre-existing or newly created
+// private windows.
+add_task(function*() {
+ let plain1 = yield openAndLoadWindow();
+ let private1 = yield openAndLoadWindow({private: true});
+ CustomizableUI.createWidget({
+ id: kWidgetId,
+ removable: true,
+ showInPrivateBrowsing: false
+ });
+ CustomizableUI.addWidgetToArea(kWidgetId,
+ CustomizableUI.AREA_NAVBAR);
+ assertWidgetExists(plain1, true);
+ assertWidgetExists(private1, false);
+
+ // Now open up some new windows. The widget should exist in the new
+ // plain window, but not the new private window.
+ let plain2 = yield openAndLoadWindow();
+ let private2 = yield openAndLoadWindow({private: true});
+ assertWidgetExists(plain2, true);
+ assertWidgetExists(private2, false);
+
+ // Try moving the widget around and make sure it doesn't get added
+ // to the private windows. We'll start by appending it to the tabstrip.
+ CustomizableUI.addWidgetToArea(kWidgetId,
+ CustomizableUI.AREA_TABSTRIP);
+ assertWidgetExists(plain1, true);
+ assertWidgetExists(plain2, true);
+ assertWidgetExists(private1, false);
+ assertWidgetExists(private2, false);
+
+ // And then move it to the beginning of the tabstrip.
+ CustomizableUI.moveWidgetWithinArea(kWidgetId, 0);
+ assertWidgetExists(plain1, true);
+ assertWidgetExists(plain2, true);
+ assertWidgetExists(private1, false);
+ assertWidgetExists(private2, false);
+
+ CustomizableUI.removeWidgetFromArea("some-widget");
+ assertWidgetExists(plain1, false);
+ assertWidgetExists(plain2, false);
+ assertWidgetExists(private1, false);
+ assertWidgetExists(private2, false);
+
+ yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed));
+
+ CustomizableUI.destroyWidget("some-widget");
+});
+
+// Add a widget via the API with showInPrivateBrowsing set to true,
+// and ensure that it appears in pre-existing or newly created
+// private browsing windows.
+add_task(function*() {
+ let plain1 = yield openAndLoadWindow();
+ let private1 = yield openAndLoadWindow({private: true});
+
+ CustomizableUI.createWidget({
+ id: kWidgetId,
+ removable: true,
+ showInPrivateBrowsing: true
+ });
+ CustomizableUI.addWidgetToArea(kWidgetId,
+ CustomizableUI.AREA_NAVBAR);
+ assertWidgetExists(plain1, true);
+ assertWidgetExists(private1, true);
+
+ // Now open up some new windows. The widget should exist in the new
+ // plain window, but not the new private window.
+ let plain2 = yield openAndLoadWindow();
+ let private2 = yield openAndLoadWindow({private: true});
+
+ assertWidgetExists(plain2, true);
+ assertWidgetExists(private2, true);
+
+ // Try moving the widget around and make sure it doesn't get added
+ // to the private windows. We'll start by appending it to the tabstrip.
+ CustomizableUI.addWidgetToArea(kWidgetId,
+ CustomizableUI.AREA_TABSTRIP);
+ assertWidgetExists(plain1, true);
+ assertWidgetExists(plain2, true);
+ assertWidgetExists(private1, true);
+ assertWidgetExists(private2, true);
+
+ // And then move it to the beginning of the tabstrip.
+ CustomizableUI.moveWidgetWithinArea(kWidgetId, 0);
+ assertWidgetExists(plain1, true);
+ assertWidgetExists(plain2, true);
+ assertWidgetExists(private1, true);
+ assertWidgetExists(private2, true);
+
+ CustomizableUI.removeWidgetFromArea("some-widget");
+ assertWidgetExists(plain1, false);
+ assertWidgetExists(plain2, false);
+ assertWidgetExists(private1, false);
+ assertWidgetExists(private2, false);
+
+ yield Promise.all([plain1, plain2, private1, private2].map(promiseWindowClosed));
+
+ CustomizableUI.destroyWidget("some-widget");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_886323_buildArea_removable_nodes.js b/browser/components/customizableui/test/browser_886323_buildArea_removable_nodes.js
new file mode 100644
index 000000000..f46141c4f
--- /dev/null
+++ b/browser/components/customizableui/test/browser_886323_buildArea_removable_nodes.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";
+
+const kButtonId = "test-886323-removable-moved-node";
+const kLazyAreaId = "test-886323-lazy-area-for-removability-testing";
+
+var gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+var gLazyArea;
+
+// Removable nodes shouldn't be moved by buildArea
+add_task(function*() {
+ let dummyBtn = createDummyXULButton(kButtonId, "Dummy");
+ dummyBtn.setAttribute("removable", "true");
+ gNavBar.customizationTarget.appendChild(dummyBtn);
+ let popupSet = document.getElementById("mainPopupSet");
+ gLazyArea = document.createElementNS(kNSXUL, "panel");
+ gLazyArea.id = kLazyAreaId;
+ gLazyArea.setAttribute("hidden", "true");
+ popupSet.appendChild(gLazyArea);
+ CustomizableUI.registerArea(kLazyAreaId, {
+ type: CustomizableUI.TYPE_MENU_PANEL,
+ defaultPlacements: []
+ });
+ CustomizableUI.addWidgetToArea(kButtonId, kLazyAreaId);
+ assertAreaPlacements(kLazyAreaId, [kButtonId],
+ "Placements should have changed because widget is removable.");
+ let btn = document.getElementById(kButtonId);
+ btn.setAttribute("removable", "false");
+ gLazyArea.customizationTarget = gLazyArea;
+ CustomizableUI.registerToolbarNode(gLazyArea, []);
+ assertAreaPlacements(kLazyAreaId, [], "Placements should no longer include widget.");
+ is(btn.parentNode.id, gNavBar.customizationTarget.id,
+ "Button shouldn't actually have moved as it's not removable");
+ btn = document.getElementById(kButtonId);
+ if (btn) btn.remove();
+ CustomizableUI.removeWidgetFromArea(kButtonId);
+ CustomizableUI.unregisterArea(kLazyAreaId);
+ gLazyArea.remove();
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_887438_currentset_shim.js b/browser/components/customizableui/test/browser_887438_currentset_shim.js
new file mode 100644
index 000000000..a04299819
--- /dev/null
+++ b/browser/components/customizableui/test/browser_887438_currentset_shim.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";
+
+var navbar = document.getElementById("nav-bar");
+var navbarCT = navbar.customizationTarget;
+var overflowPanelList = document.getElementById("widget-overflow-list");
+
+// Reading currentset
+add_task(function() {
+ let nodeIds = [];
+ for (let node of navbarCT.childNodes) {
+ if (node.getAttribute("skipintoolbarset") != "true") {
+ nodeIds.push(node.id);
+ }
+ }
+ for (let node of overflowPanelList.childNodes) {
+ if (node.getAttribute("skipintoolbarset") != "true") {
+ nodeIds.push(node.id);
+ }
+ }
+ let currentSet = navbar.currentSet;
+ is(currentSet.split(',').length, nodeIds.length, "Should be just as many nodes as there are.");
+ is(currentSet, nodeIds.join(','), "Current set and node IDs should match.");
+});
+
+// Insert, then remove items
+add_task(function() {
+ let currentSet = navbar.currentSet;
+ let newCurrentSet = currentSet.replace('home-button', 'feed-button,sync-button,home-button');
+ navbar.currentSet = newCurrentSet;
+ is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
+ let feedBtn = document.getElementById("feed-button");
+ let syncBtn = document.getElementById("sync-button");
+ ok(feedBtn, "Feed button should have been added.");
+ ok(syncBtn, "Sync button should have been added.");
+ if (feedBtn && syncBtn) {
+ let feedParent = feedBtn.parentNode;
+ let syncParent = syncBtn.parentNode;
+ ok(feedParent == navbarCT || feedParent == overflowPanelList,
+ "Feed button should be in navbar or overflow");
+ ok(syncParent == navbarCT || syncParent == overflowPanelList,
+ "Feed button should be in navbar or overflow");
+ is(feedBtn.nextElementSibling, syncBtn, "Feed button should be next to sync button.");
+ let homeBtn = document.getElementById("home-button");
+ is(syncBtn.nextElementSibling, homeBtn, "Sync button should be next to home button.");
+ }
+ navbar.currentSet = currentSet;
+ is(currentSet, navbar.currentSet, "Should be able to remove the added items.");
+});
+
+// Simultaneous insert/remove:
+add_task(function() {
+ let currentSet = navbar.currentSet;
+ let newCurrentSet = currentSet.replace('home-button', 'feed-button');
+ navbar.currentSet = newCurrentSet;
+ is(newCurrentSet, navbar.currentSet, "Current set should match expected current set.");
+ let feedBtn = document.getElementById("feed-button");
+ ok(feedBtn, "Feed button should have been added.");
+ let homeBtn = document.getElementById("home-button");
+ ok(!homeBtn, "Home button should have been removed.");
+ if (feedBtn) {
+ let feedParent = feedBtn.parentNode;
+ ok(feedParent == navbarCT || feedParent == overflowPanelList,
+ "Feed button should be in navbar or overflow");
+ }
+ navbar.currentSet = currentSet;
+ is(currentSet, navbar.currentSet, "Should be able to return to original state.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_888817_currentset_updating.js b/browser/components/customizableui/test/browser_888817_currentset_updating.js
new file mode 100644
index 000000000..6e7c4e95a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_888817_currentset_updating.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";
+
+// Adding, moving and removing items should update the relevant currentset attributes
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Should be in the default state when we start");
+ let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
+ setToolbarVisibility(personalbar, true);
+ ok(!CustomizableUI.inDefaultState, "Making the bookmarks toolbar visible takes it out of the default state");
+
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
+ let navbarCurrentset = navbar.getAttribute("currentset") || navbar.currentSet;
+ let personalbarCurrentset = personalbar.getAttribute("currentset") || personalbar.currentSet;
+
+ let otherWin = yield openAndLoadWindow();
+ let otherNavbar = otherWin.document.getElementById(CustomizableUI.AREA_NAVBAR);
+ let otherPersonalbar = otherWin.document.getElementById(CustomizableUI.AREA_BOOKMARKS);
+
+ CustomizableUI.moveWidgetWithinArea("home-button", 0);
+ navbarCurrentset = "home-button," + navbarCurrentset.replace(",home-button", "");
+ is(navbar.getAttribute("currentset"), navbarCurrentset,
+ "Should have updated currentSet after move.");
+ is(otherNavbar.getAttribute("currentset"), navbarCurrentset,
+ "Should have updated other window's currentSet after move.");
+
+ CustomizableUI.addWidgetToArea("home-button", CustomizableUI.AREA_BOOKMARKS);
+ navbarCurrentset = navbarCurrentset.replace("home-button,", "");
+ personalbarCurrentset = personalbarCurrentset + ",home-button";
+ is(navbar.getAttribute("currentset"), navbarCurrentset,
+ "Should have updated navbar currentSet after implied remove.");
+ is(otherNavbar.getAttribute("currentset"), navbarCurrentset,
+ "Should have updated other window's navbar currentSet after implied remove.");
+ is(personalbar.getAttribute("currentset"), personalbarCurrentset,
+ "Should have updated personalbar currentSet after add.");
+ is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset,
+ "Should have updated other window's personalbar currentSet after add.");
+
+ CustomizableUI.removeWidgetFromArea("home-button");
+ personalbarCurrentset = personalbarCurrentset.replace(",home-button", "");
+ is(personalbar.getAttribute("currentset"), personalbarCurrentset,
+ "Should have updated currentSet after remove.");
+ is(otherPersonalbar.getAttribute("currentset"), personalbarCurrentset,
+ "Should have updated other window's currentSet after remove.");
+
+ yield promiseWindowClosed(otherWin);
+ // Reset in asyncCleanup will put our button back for us.
+});
+
+add_task(function* asyncCleanup() {
+ let personalbar = document.getElementById(CustomizableUI.AREA_BOOKMARKS);
+ setToolbarVisibility(personalbar, false);
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js
new file mode 100644
index 000000000..84b126a9b
--- /dev/null
+++ b/browser/components/customizableui/test/browser_890140_orphaned_placeholders.js
@@ -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/. */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// One orphaned item should have two placeholders next to it.
+add_task(function*() {
+ yield startCustomizing();
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ }
+ if (!isInDevEdition()) {
+ ok(CustomizableUI.inDefaultState, "Should be in default state.");
+ } else {
+ ok(!CustomizableUI.inDefaultState, "Should not be in default state if on DevEdition.");
+ }
+
+ // This test relies on an exact number of widgets being in the panel.
+ // Remove the sync-button to satisfy that. (bug 1229236)
+ CustomizableUI.removeWidgetFromArea("sync-button");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
+ is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders before exiting");
+
+ yield endCustomizing();
+ yield startCustomizing();
+ is(getVisiblePlaceholderCount(panel), 2, "Should only have 2 visible placeholders after re-entering");
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
+ }
+
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Two orphaned items should have one placeholder next to them (case 1).
+add_task(function*() {
+ yield startCustomizing();
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
+ }
+
+ // This test relies on an exact number of widgets being in the panel.
+ // Remove the sync-button to satisfy that. (bug 1229236)
+ CustomizableUI.removeWidgetFromArea("sync-button");
+
+ let btn = document.getElementById("open-file-button");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+ let placementsAfterAppend = placements;
+
+ placementsAfterAppend = placements.concat(["open-file-button"]);
+ simulateItemDrag(btn, panel);
+
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
+
+ ok(!CustomizableUI.inDefaultState, "Should not be in default state.");
+
+ is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting");
+
+ yield endCustomizing();
+ yield startCustomizing();
+ is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering");
+
+ let palette = document.getElementById("customization-palette");
+ simulateItemDrag(btn, palette);
+
+ btn = document.getElementById("open-file-button");
+ simulateItemDrag(btn, palette);
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
+ }
+
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// Two orphaned items should have one placeholder next to them (case 2).
+add_task(function*() {
+ yield startCustomizing();
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
+ }
+ // This test relies on an exact number of widgets being in the panel.
+ // Remove the sync-button to satisfy that. (bug 1229236)
+ CustomizableUI.removeWidgetFromArea("sync-button");
+
+ let btn = document.getElementById("add-ons-button");
+ let btn2 = document.getElementById("developer-button");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let palette = document.getElementById("customization-palette");
+ let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+
+ let placementsAfterAppend = placements.filter(p => p != btn.id && p != btn2.id);
+ simulateItemDrag(btn, palette);
+ simulateItemDrag(btn2, palette);
+
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder before exiting");
+
+ yield endCustomizing();
+ yield startCustomizing();
+ is(getVisiblePlaceholderCount(panel), 1, "Should only have 1 visible placeholder after re-entering");
+
+ simulateItemDrag(btn, panel);
+ simulateItemDrag(btn2, panel);
+
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placements);
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
+ }
+
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// A wide widget at the bottom of the panel should have three placeholders after it.
+add_task(function*() {
+ yield startCustomizing();
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_PANEL);
+ }
+
+ // This test relies on an exact number of widgets being in the panel.
+ // Remove the sync-button to satisfy that. (bug 1229236)
+ CustomizableUI.removeWidgetFromArea("sync-button");
+
+ let btn = document.getElementById("edit-controls");
+ let btn2 = document.getElementById("developer-button");
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ let palette = document.getElementById("customization-palette");
+ let placements = getAreaWidgetIds(CustomizableUI.AREA_PANEL);
+
+ placements.pop();
+ simulateItemDrag(btn2, palette);
+
+ let placementsAfterAppend = placements.concat([placements.shift()]);
+ simulateItemDrag(btn, panel);
+ assertAreaPlacements(CustomizableUI.AREA_PANEL, placementsAfterAppend);
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state.");
+ is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders before exiting");
+
+ yield endCustomizing();
+ yield startCustomizing();
+ is(getVisiblePlaceholderCount(panel), 3, "Should have 3 visible placeholders after re-entering");
+
+ simulateItemDrag(btn2, panel);
+
+ let zoomControls = document.getElementById("zoom-controls");
+ simulateItemDrag(btn, zoomControls);
+
+ if (isInDevEdition()) {
+ CustomizableUI.addWidgetToArea("developer-button", CustomizableUI.AREA_NAVBAR, 2);
+ }
+
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should be in default state again.");
+});
+
+// The default placements should have two placeholders at the bottom (or 1 in win8).
+add_task(function*() {
+ yield startCustomizing();
+ let numPlaceholders = -1;
+
+ if (isInDevEdition()) {
+ numPlaceholders = 3;
+ } else {
+ numPlaceholders = 2;
+ }
+
+ let panel = document.getElementById(CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should be in default state.");
+
+ // This test relies on an exact number of widgets being in the panel.
+ // Remove the sync-button to satisfy that. (bug 1229236)
+ CustomizableUI.removeWidgetFromArea("sync-button");
+
+ is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders before exiting");
+
+ yield endCustomizing();
+ yield startCustomizing();
+ is(getVisiblePlaceholderCount(panel), numPlaceholders, "Should have " + numPlaceholders + " visible placeholders after re-entering");
+
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state.");
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
+
+function getVisiblePlaceholderCount(aPanel) {
+ let visiblePlaceholders = aPanel.querySelectorAll(".panel-customization-placeholder:not([hidden=true])");
+ return visiblePlaceholders.length;
+}
diff --git a/browser/components/customizableui/test/browser_890262_destroyWidget_after_add_to_panel.js b/browser/components/customizableui/test/browser_890262_destroyWidget_after_add_to_panel.js
new file mode 100644
index 000000000..13f2bd7ba
--- /dev/null
+++ b/browser/components/customizableui/test/browser_890262_destroyWidget_after_add_to_panel.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 kLazyAreaId = "test-890262-lazy-area";
+const kWidget1Id = "test-890262-widget1";
+const kWidget2Id = "test-890262-widget2";
+
+setupArea();
+
+// Destroying a widget after defaulting it to a non-legacy area should work.
+add_task(function() {
+ CustomizableUI.createWidget({
+ id: kWidget1Id,
+ removable: true,
+ defaultArea: kLazyAreaId
+ });
+ let noError = true;
+ try {
+ CustomizableUI.destroyWidget(kWidget1Id);
+ } catch (ex) {
+ Cu.reportError(ex);
+ noError = false;
+ }
+ ok(noError, "Shouldn't throw an exception for a widget that was created in a not-yet-constructed area");
+});
+
+// Destroying a widget after moving it to a non-legacy area should work.
+add_task(function() {
+ CustomizableUI.createWidget({
+ id: kWidget2Id,
+ removable: true,
+ defaultArea: CustomizableUI.AREA_NAVBAR
+ });
+
+ CustomizableUI.addWidgetToArea(kWidget2Id, kLazyAreaId);
+ let noError = true;
+ try {
+ CustomizableUI.destroyWidget(kWidget2Id);
+ } catch (ex) {
+ Cu.reportError(ex);
+ noError = false;
+ }
+ ok(noError, "Shouldn't throw an exception for a widget that was added to a not-yet-constructed area");
+});
+
+add_task(function* asyncCleanup() {
+ let lazyArea = document.getElementById(kLazyAreaId);
+ if (lazyArea) {
+ lazyArea.remove();
+ }
+ try {
+ CustomizableUI.unregisterArea(kLazyAreaId);
+ } catch (ex) {} // If we didn't register successfully for some reason
+ yield resetCustomization();
+});
+
+function setupArea() {
+ let lazyArea = document.createElementNS(kNSXUL, "hbox");
+ lazyArea.id = kLazyAreaId;
+ document.getElementById("nav-bar").appendChild(lazyArea);
+ CustomizableUI.registerArea(kLazyAreaId, {
+ type: CustomizableUI.TYPE_TOOLBAR,
+ defaultPlacements: []
+ });
+}
diff --git a/browser/components/customizableui/test/browser_892955_isWidgetRemovable_for_removed_widgets.js b/browser/components/customizableui/test/browser_892955_isWidgetRemovable_for_removed_widgets.js
new file mode 100644
index 000000000..67ef82b82
--- /dev/null
+++ b/browser/components/customizableui/test/browser_892955_isWidgetRemovable_for_removed_widgets.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const kWidgetId = "test-892955-remove-widget";
+
+// Removing a destroyed widget should work.
+add_task(function*() {
+ let widgetSpec = {
+ id: kWidgetId,
+ defaultArea: CustomizableUI.AREA_NAVBAR
+ };
+
+ CustomizableUI.createWidget(widgetSpec);
+ CustomizableUI.destroyWidget(kWidgetId);
+ let noError = true;
+ try {
+ CustomizableUI.removeWidgetFromArea(kWidgetId);
+ } catch (ex) {
+ noError = false;
+ Cu.reportError(ex);
+ }
+ ok(noError, "Shouldn't throw an error removing a destroyed widget.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_892956_destroyWidget_defaultPlacements.js b/browser/components/customizableui/test/browser_892956_destroyWidget_defaultPlacements.js
new file mode 100644
index 000000000..c7047c797
--- /dev/null
+++ b/browser/components/customizableui/test/browser_892956_destroyWidget_defaultPlacements.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";
+
+const kWidgetId = "test-892956-destroyWidget-defaultPlacement";
+
+// destroyWidget should clean up defaultPlacements if the widget had a defaultArea
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Should be in the default state when we start");
+
+ let widgetSpec = {
+ id: kWidgetId,
+ defaultArea: CustomizableUI.AREA_NAVBAR
+ };
+ CustomizableUI.createWidget(widgetSpec);
+ CustomizableUI.destroyWidget(kWidgetId);
+ ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.js
new file mode 100644
index 000000000..3bc449add
--- /dev/null
+++ b/browser/components/customizableui/test/browser_901207_searchbar_in_panel.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";
+
+logActiveElement();
+
+function* waitForSearchBarFocus()
+{
+ let searchbar = document.getElementById("searchbar");
+ yield waitForCondition(function () {
+ logActiveElement();
+ return document.activeElement === searchbar.textbox.inputField;
+ });
+}
+
+// Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel.
+add_task(function*() {
+ let searchbar = document.getElementById("searchbar");
+ gCustomizeMode.addToPanel(searchbar);
+ let placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
+
+ let shownPanelPromise = promisePanelShown(window);
+ sendWebSearchKeyCommand();
+ yield shownPanelPromise;
+
+ yield waitForSearchBarFocus();
+
+ let hiddenPanelPromise = promisePanelHidden(window);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield hiddenPanelPromise;
+ CustomizableUI.reset();
+});
+
+// Ctrl+K should give focus to the searchbar when the searchbar is in the menupanel and the panel is already opened.
+add_task(function*() {
+ let searchbar = document.getElementById("searchbar");
+ gCustomizeMode.addToPanel(searchbar);
+ let placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
+
+ let shownPanelPromise = promisePanelShown(window);
+ PanelUI.toggle({type: "command"});
+ yield shownPanelPromise;
+
+ sendWebSearchKeyCommand();
+
+ yield waitForSearchBarFocus();
+
+ let hiddenPanelPromise = promisePanelHidden(window);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield hiddenPanelPromise;
+ CustomizableUI.reset();
+});
+
+// Ctrl+K should open the overflow panel and focus the search bar if the search bar is overflowed.
+add_task(function*() {
+ this.originalWindowWidth = window.outerWidth;
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+
+ window.resizeTo(360, window.outerHeight);
+ yield waitForCondition(() => navbar.getAttribute("overflowing") == "true");
+ ok(!navbar.querySelector("#search-container"), "Search container should be overflowing");
+
+ let shownPanelPromise = promiseOverflowShown(window);
+ sendWebSearchKeyCommand();
+ yield shownPanelPromise;
+
+ let chevron = document.getElementById("nav-bar-overflow-button");
+ yield waitForCondition(() => chevron.open);
+
+ yield waitForSearchBarFocus();
+
+ let hiddenPanelPromise = promiseOverflowHidden(window);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield hiddenPanelPromise;
+ navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ window.resizeTo(this.originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+ ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
+});
+
+// Ctrl+K should focus the search bar if it is in the navbar and not overflowing.
+add_task(function*() {
+ let placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar");
+
+ sendWebSearchKeyCommand();
+
+ yield waitForSearchBarFocus();
+});
+
+
+function sendWebSearchKeyCommand() {
+ if (Services.appinfo.OS === "Darwin")
+ EventUtils.synthesizeKey("k", { accelKey: true });
+ else
+ EventUtils.synthesizeKey("k", { ctrlKey: true });
+}
+
+function logActiveElement() {
+ let element = document.activeElement;
+ let str = "";
+ while (element && element.parentNode) {
+ str = " (" + element.localName + "#" + element.id + "." + [...element.classList].join(".") + ") >" + str;
+ element = element.parentNode;
+ }
+ info("Active element: " + element ? str : "null");
+}
diff --git a/browser/components/customizableui/test/browser_909779_overflow_toolbars_new_window.js b/browser/components/customizableui/test/browser_909779_overflow_toolbars_new_window.js
new file mode 100644
index 000000000..f39d13ff4
--- /dev/null
+++ b/browser/components/customizableui/test/browser_909779_overflow_toolbars_new_window.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/. */
+
+"use strict";
+
+// Resize to a small window, open a new window, check that new window handles overflow properly
+add_task(function*() {
+ let originalWindowWidth = window.outerWidth;
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ let oldChildCount = navbar.customizationTarget.childElementCount;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+ ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
+ let newWindow = yield openAndLoadWindow();
+ let otherNavBar = newWindow.document.getElementById(CustomizableUI.AREA_NAVBAR);
+ yield waitForCondition(() => otherNavBar.hasAttribute("overflowing"));
+ ok(otherNavBar.hasAttribute("overflowing"), "Other window should have an overflowing toolbar.");
+ yield promiseWindowClosed(newWindow);
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+ ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_913972_currentset_overflow.js b/browser/components/customizableui/test/browser_913972_currentset_overflow.js
new file mode 100644
index 000000000..7d754d79b
--- /dev/null
+++ b/browser/components/customizableui/test/browser_913972_currentset_overflow.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";
+
+var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+// Resize to a small window, resize back, shouldn't affect currentSet
+add_task(function*() {
+ let originalWindowWidth = window.outerWidth;
+ let oldCurrentSet = navbar.currentSet;
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+ let oldChildCount = navbar.customizationTarget.childElementCount;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+ is(navbar.currentSet, oldCurrentSet, "Currentset should be the same when overflowing.");
+ ok(CustomizableUI.inDefaultState, "Should still be in default state when overflowing.");
+ ok(navbar.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+ ok(!navbar.hasAttribute("overflowing"), "Should no longer have an overflowing toolbar.");
+ is(navbar.currentSet, oldCurrentSet, "Currentset should still be the same now we're no longer overflowing.");
+ ok(CustomizableUI.inDefaultState, "Should still be in default state now we're no longer overflowing.");
+
+ // Verify actual physical placements match those of the placement array:
+ let placementCounter = 0;
+ let placements = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
+ for (let node of navbar.customizationTarget.childNodes) {
+ if (node.getAttribute("skipintoolbarset") == "true") {
+ continue;
+ }
+ is(placements[placementCounter++], node.id, "Nodes should match after overflow");
+ }
+ is(placements.length, placementCounter, "Should have as many nodes as expected");
+ is(navbar.customizationTarget.childElementCount, oldChildCount, "Number of nodes should match");
+});
+
+// Enter and exit customization mode, check that currentSet works
+add_task(function*() {
+ let oldCurrentSet = navbar.currentSet;
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+ yield startCustomizing();
+ ok(CustomizableUI.inDefaultState, "Should be in default state in customization mode.");
+ is(navbar.currentSet, oldCurrentSet, "Currentset should be the same in customization mode.");
+ yield endCustomizing();
+ ok(CustomizableUI.inDefaultState, "Should be in default state after customization mode.");
+ is(navbar.currentSet, oldCurrentSet, "Currentset should be the same after customization mode.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_914138_widget_API_overflowable_toolbar.js b/browser/components/customizableui/test/browser_914138_widget_API_overflowable_toolbar.js
new file mode 100644
index 000000000..35ba79bec
--- /dev/null
+++ b/browser/components/customizableui/test/browser_914138_widget_API_overflowable_toolbar.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";
+
+var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+var overflowList = document.getElementById(navbar.getAttribute("overflowtarget"));
+
+const kTestBtn1 = "test-addWidgetToArea-overflow";
+const kTestBtn2 = "test-removeWidgetFromArea-overflow";
+const kTestBtn3 = "test-createWidget-overflow";
+const kHomeBtn = "home-button";
+const kDownloadsBtn = "downloads-button";
+const kSearchBox = "search-container";
+const kStarBtn = "bookmarks-menu-button";
+
+var originalWindowWidth;
+
+// Adding a widget should add it next to the widget it's being inserted next to.
+add_task(function*() {
+ originalWindowWidth = window.outerWidth;
+ createDummyXULButton(kTestBtn1, "Test");
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+ ok(!navbar.querySelector("#" + kHomeBtn), "Home button should no longer be in the navbar");
+ let homeBtnNode = overflowList.querySelector("#" + kHomeBtn);
+ ok(homeBtnNode, "Home button should be overflowing");
+ ok(homeBtnNode && homeBtnNode.getAttribute("overflowedItem") == "true", "Home button should have overflowedItem attribute");
+
+ let placementOfHomeButton = CustomizableUI.getWidgetIdsInArea(navbar.id).indexOf(kHomeBtn);
+ CustomizableUI.addWidgetToArea(kTestBtn1, navbar.id, placementOfHomeButton);
+ ok(!navbar.querySelector("#" + kTestBtn1), "New button should not be in the navbar");
+ let newButtonNode = overflowList.querySelector("#" + kTestBtn1);
+ ok(newButtonNode, "New button should be overflowing");
+ ok(newButtonNode && newButtonNode.getAttribute("overflowedItem") == "true", "New button should have overflowedItem attribute");
+ let nextEl = newButtonNode && newButtonNode.nextSibling;
+ is(nextEl && nextEl.id, kHomeBtn, "Test button should be next to home button.");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+ ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
+ ok(navbar.querySelector("#" + kHomeBtn), "Home button should be in the navbar");
+ ok(homeBtnNode && (homeBtnNode.getAttribute("overflowedItem") != "true"), "Home button should no longer have overflowedItem attribute");
+ ok(!overflowList.querySelector("#" + kHomeBtn), "Home button should no longer be overflowing");
+ ok(navbar.querySelector("#" + kTestBtn1), "Test button should be in the navbar");
+ ok(!overflowList.querySelector("#" + kTestBtn1), "Test button should no longer be overflowing");
+ ok(newButtonNode && (newButtonNode.getAttribute("overflowedItem") != "true"), "New button should no longer have overflowedItem attribute");
+ let el = document.getElementById(kTestBtn1);
+ if (el) {
+ CustomizableUI.removeWidgetFromArea(kTestBtn1);
+ el.remove();
+ }
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+});
+
+// Removing a widget should remove it from the overflow list if that is where it is, and update it accordingly.
+add_task(function*() {
+ createDummyXULButton(kTestBtn2, "Test");
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+ CustomizableUI.addWidgetToArea(kTestBtn2, navbar.id);
+ ok(!navbar.hasAttribute("overflowing"), "Should still have a non-overflowing toolbar.");
+
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+ ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar");
+ ok(overflowList.querySelector("#" + kTestBtn2), "Test button should be overflowing");
+
+ CustomizableUI.removeWidgetFromArea(kTestBtn2);
+
+ ok(!overflowList.querySelector("#" + kTestBtn2), "Test button should not be overflowing.");
+ ok(!navbar.querySelector("#" + kTestBtn2), "Test button should not be in the navbar");
+ ok(gNavToolbox.palette.querySelector("#" + kTestBtn2), "Test button should be in the palette");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+ ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
+ let el = document.getElementById(kTestBtn2);
+ if (el) {
+ CustomizableUI.removeWidgetFromArea(kTestBtn2);
+ el.remove();
+ }
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+});
+
+// Constructing a widget while overflown should set the right class on it.
+add_task(function*() {
+ originalWindowWidth = window.outerWidth;
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+ ok(!navbar.querySelector("#" + kHomeBtn), "Home button should no longer be in the navbar");
+ let homeBtnNode = overflowList.querySelector("#" + kHomeBtn);
+ ok(homeBtnNode, "Home button should be overflowing");
+ ok(homeBtnNode && homeBtnNode.getAttribute("overflowedItem") == "true", "Home button should have overflowedItem class");
+
+ let testBtnSpec = {id: kTestBtn3, label: "Overflowable widget test", defaultArea: "nav-bar"};
+ CustomizableUI.createWidget(testBtnSpec);
+ let testNode = overflowList.querySelector("#" + kTestBtn3);
+ ok(testNode, "Test button should be overflowing");
+ ok(testNode && testNode.getAttribute("overflowedItem") == "true", "Test button should have overflowedItem class");
+
+ CustomizableUI.destroyWidget(kTestBtn3);
+ testNode = document.getElementById(kTestBtn3);
+ ok(!testNode, "Test button should be gone");
+
+ CustomizableUI.createWidget(testBtnSpec);
+ testNode = overflowList.querySelector("#" + kTestBtn3);
+ ok(testNode, "Test button should be overflowing");
+ ok(testNode && testNode.getAttribute("overflowedItem") == "true", "Test button should have overflowedItem class");
+
+ CustomizableUI.removeWidgetFromArea(kTestBtn3);
+ testNode = document.getElementById(kTestBtn3);
+ ok(!testNode, "Test button should be gone");
+ CustomizableUI.destroyWidget(kTestBtn3);
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+});
+
+add_task(function* asyncCleanup() {
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js b/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.js
new file mode 100644
index 000000000..b5757eabb
--- /dev/null
+++ b/browser/components/customizableui/test/browser_914863_disabled_help_quit_buttons.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/. */
+
+// Entering then exiting customization mode should reenable the Help and Exit buttons.
+add_task(function*() {
+ yield startCustomizing();
+ let helpButton = document.getElementById("PanelUI-help");
+ let quitButton = document.getElementById("PanelUI-quit");
+ ok(helpButton.getAttribute("disabled") == "true", "Help button should be disabled while in customization mode.");
+ ok(quitButton.getAttribute("disabled") == "true", "Quit button should be disabled while in customization mode.");
+ yield endCustomizing();
+
+ ok(!helpButton.hasAttribute("disabled"), "Help button should not be disabled.");
+ ok(!quitButton.hasAttribute("disabled"), "Quit button should not be disabled.");
+});
diff --git a/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.js
new file mode 100644
index 000000000..dffe388dc
--- /dev/null
+++ b/browser/components/customizableui/test/browser_918049_skipintoolbarset_dnd.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/. */
+
+"use strict";
+
+var navbar;
+var skippedItem;
+
+// Attempting to drag a skipintoolbarset item should work.
+add_task(function*() {
+ navbar = document.getElementById("nav-bar");
+ skippedItem = document.createElement("toolbarbutton");
+ skippedItem.id = "test-skipintoolbarset-item";
+ skippedItem.setAttribute("label", "Test");
+ skippedItem.setAttribute("skipintoolbarset", "true");
+ skippedItem.setAttribute("removable", "true");
+ navbar.customizationTarget.appendChild(skippedItem);
+ let downloadsButton = document.getElementById("downloads-button");
+ yield startCustomizing();
+ ok(CustomizableUI.inDefaultState, "Should still be in default state");
+ simulateItemDrag(skippedItem, downloadsButton);
+ ok(CustomizableUI.inDefaultState, "Should still be in default state");
+ let skippedItemWrapper = skippedItem.parentNode;
+ is(skippedItemWrapper.nextSibling && skippedItemWrapper.nextSibling.id,
+ downloadsButton.parentNode.id, "Should be next to downloads button");
+ simulateItemDrag(downloadsButton, skippedItem);
+ let downloadWrapper = downloadsButton.parentNode;
+ is(downloadWrapper.nextSibling && downloadWrapper.nextSibling.id,
+ skippedItem.parentNode.id, "Should be next to skipintoolbarset item");
+ ok(CustomizableUI.inDefaultState, "Should still be in default state");
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ skippedItem.remove();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js b/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.js
new file mode 100644
index 000000000..87aca51eb
--- /dev/null
+++ b/browser/components/customizableui/test/browser_923857_customize_mode_event_wrapping_during_reset.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";
+
+// Customize mode reset button should revert correctly
+add_task(function*() {
+ yield startCustomizing();
+ let devButton = document.getElementById("developer-button");
+ let downloadsButton = document.getElementById("downloads-button");
+ let searchBox = document.getElementById("search-container");
+ let palette = document.getElementById("customization-palette");
+ ok(devButton && downloadsButton && searchBox && palette, "Stuff should exist");
+ simulateItemDrag(devButton, downloadsButton);
+ simulateItemDrag(searchBox, palette);
+ yield gCustomizeMode.reset();
+ ok(CustomizableUI.inDefaultState, "Should be back in default state");
+ yield endCustomizing();
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js b/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.js
new file mode 100644
index 000000000..d79f6e364
--- /dev/null
+++ b/browser/components/customizableui/test/browser_927717_customize_drag_empty_toolbar.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";
+
+const kTestToolbarId = "test-empty-drag";
+
+// Attempting to drag an item to an empty container should work.
+add_task(function*() {
+ yield createToolbarWithPlacements(kTestToolbarId, []);
+ yield startCustomizing();
+ let downloadButton = document.getElementById("downloads-button");
+ let customToolbar = document.getElementById(kTestToolbarId);
+ simulateItemDrag(downloadButton, customToolbar);
+ assertAreaPlacements(kTestToolbarId, ["downloads-button"]);
+ ok(downloadButton.parentNode && downloadButton.parentNode.parentNode == customToolbar,
+ "Button should really be in toolbar");
+ yield endCustomizing();
+ removeCustomToolbars();
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js b/browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js
new file mode 100644
index 000000000..3cbf6be42
--- /dev/null
+++ b/browser/components/customizableui/test/browser_932928_show_notice_when_palette_empty.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// There should be an advert to get more addons when the palette is empty.
+add_task(function*() {
+ yield startCustomizing();
+ let visiblePalette = document.getElementById("customization-palette");
+ let emptyPaletteNotice = document.getElementById("customization-empty");
+ is(emptyPaletteNotice.hidden, true, "The empty palette notice should not be shown when there are items in the palette.");
+
+ while (visiblePalette.childElementCount) {
+ gCustomizeMode.addToToolbar(visiblePalette.children[0]);
+ }
+ is(visiblePalette.childElementCount, 0, "There shouldn't be any items remaining in the visible palette.");
+ is(emptyPaletteNotice.hidden, false, "The empty palette notice should be shown when there are no items in the palette.");
+
+ yield endCustomizing();
+ yield startCustomizing();
+ visiblePalette = document.getElementById("customization-palette");
+ emptyPaletteNotice = document.getElementById("customization-empty");
+ is(emptyPaletteNotice.hidden, false,
+ "The empty palette notice should be shown when there are no items in the palette and cust. mode is re-entered.");
+
+ gCustomizeMode.removeFromArea(document.getElementById("wrapper-home-button"));
+ is(emptyPaletteNotice.hidden, true,
+ "The empty palette notice should not be shown when there is at least one item in the palette.");
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_934113_menubar_removable.js b/browser/components/customizableui/test/browser_934113_menubar_removable.js
new file mode 100644
index 000000000..1d788bced
--- /dev/null
+++ b/browser/components/customizableui/test/browser_934113_menubar_removable.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Attempting to drag the menubar to the navbar shouldn't work.
+add_task(function*() {
+ yield startCustomizing();
+ let menuItems = document.getElementById("menubar-items");
+ let navbar = document.getElementById("nav-bar");
+ let menubar = document.getElementById("toolbar-menubar");
+ // Force the menu to be shown.
+ const kAutohide = menubar.getAttribute("autohide");
+ menubar.setAttribute("autohide", "false");
+ simulateItemDrag(menuItems, navbar.customizationTarget);
+
+ is(getAreaWidgetIds("nav-bar").indexOf("menubar-items"), -1, "Menu bar shouldn't be in the navbar.");
+ ok(!navbar.querySelector("#menubar-items"), "Shouldn't find menubar items in the navbar.");
+ ok(menubar.querySelector("#menubar-items"), "Should find menubar items in the menubar.");
+ isnot(getAreaWidgetIds("toolbar-menubar").indexOf("menubar-items"), -1,
+ "Menubar items shouldn't be missing from the navbar.");
+ menubar.setAttribute("autohide", kAutohide);
+ yield endCustomizing();
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js b/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js
new file mode 100644
index 000000000..dcc183051
--- /dev/null
+++ b/browser/components/customizableui/test/browser_934951_zoom_in_toolbar.js
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const kTimeoutInMS = 20000;
+
+// Bug 934951 - Zoom controls percentage label doesn't update when it's in the toolbar and you navigate.
+add_task(function*() {
+ CustomizableUI.addWidgetToArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
+ let tab1 = gBrowser.addTab("about:mozilla");
+ yield BrowserTestUtils.browserLoaded(tab1.linkedBrowser);
+ let tab2 = gBrowser.addTab("about:robots");
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+ gBrowser.selectedTab = tab1;
+ let zoomResetButton = document.getElementById("zoom-reset-button");
+
+ registerCleanupFunction(() => {
+ info("Cleaning up.");
+ CustomizableUI.reset();
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab1);
+ });
+
+ is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
+ let zoomChangePromise = promiseObserverNotification("browser-fullZoom:zoomChange");
+ FullZoom.enlarge();
+ yield zoomChangePromise;
+ is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
+
+ let tabSelectPromise = promiseTabSelect();
+ gBrowser.selectedTab = tab2;
+ yield tabSelectPromise;
+ is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:robots");
+
+ gBrowser.selectedTab = tab1;
+ let zoomResetPromise = promiseObserverNotification("browser-fullZoom:zoomReset");
+ FullZoom.reset();
+ yield zoomResetPromise;
+ is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:mozilla");
+
+ // Test zoom label updates while navigating pages in the same tab.
+ FullZoom.enlarge();
+ yield zoomChangePromise;
+ is(parseInt(zoomResetButton.label, 10), 110, "Zoom is changed to 110% for about:mozilla");
+ let attributeChangePromise = promiseAttributeMutation(zoomResetButton, "label", (v) => {
+ return parseInt(v, 10) == 100;
+ });
+ yield promiseTabLoadEvent(tab1, "about:home");
+ yield attributeChangePromise;
+ is(parseInt(zoomResetButton.label, 10), 100, "Default zoom is 100% for about:home");
+ yield promiseTabHistoryNavigation(-1, function() {
+ return parseInt(zoomResetButton.label, 10) == 110;
+ });
+ is(parseInt(zoomResetButton.label, 10), 110, "Zoom is still 110% for about:mozilla");
+ FullZoom.reset();
+});
+
+function promiseObserverNotification(aObserver) {
+ let deferred = Promise.defer();
+ function notificationCallback(e) {
+ Services.obs.removeObserver(notificationCallback, aObserver, false);
+ clearTimeout(timeoutId);
+ deferred.resolve();
+ }
+ let timeoutId = setTimeout(() => {
+ Services.obs.removeObserver(notificationCallback, aObserver, false);
+ deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
+ }, kTimeoutInMS);
+ Services.obs.addObserver(notificationCallback, aObserver, false);
+ return deferred.promise;
+}
+
+function promiseTabSelect() {
+ let deferred = Promise.defer();
+ let container = window.gBrowser.tabContainer;
+ let timeoutId = setTimeout(() => {
+ container.removeEventListener("TabSelect", callback);
+ deferred.reject("TabSelect did not happen within 20 seconds");
+ }, kTimeoutInMS);
+ function callback(e) {
+ container.removeEventListener("TabSelect", callback);
+ clearTimeout(timeoutId);
+ executeSoon(deferred.resolve);
+ }
+ container.addEventListener("TabSelect", callback);
+ return deferred.promise;
+}
diff --git a/browser/components/customizableui/test/browser_938980_navbar_collapsed.js b/browser/components/customizableui/test/browser_938980_navbar_collapsed.js
new file mode 100644
index 000000000..fc7fa1a0a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_938980_navbar_collapsed.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";
+
+requestLongerTimeout(2);
+
+var bookmarksToolbar = document.getElementById("PersonalToolbar");
+var navbar = document.getElementById("nav-bar");
+var tabsToolbar = document.getElementById("TabsToolbar");
+
+// Customization reset should restore visibility to default-visible toolbars.
+add_task(function*() {
+ is(navbar.collapsed, false, "Test should start with navbar visible");
+ setToolbarVisibility(navbar, false);
+ is(navbar.collapsed, true, "navbar should be hidden now");
+
+ yield resetCustomization();
+
+ is(navbar.collapsed, false, "Customization reset should restore visibility to the navbar");
+});
+
+// Customization reset should restore collapsed-state to default-collapsed toolbars.
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
+
+ is(bookmarksToolbar.collapsed, true, "Test should start with bookmarks toolbar collapsed");
+ ok(bookmarksToolbar.collapsed, "bookmarksToolbar should be collapsed");
+ ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
+ is(navbar.collapsed, false, "The nav-bar should be shown by default");
+
+ setToolbarVisibility(bookmarksToolbar, true);
+ setToolbarVisibility(navbar, false);
+ ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now");
+ ok(navbar.collapsed, "navbar should be collapsed");
+ is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
+
+ yield startCustomizing();
+ yield gCustomizeMode.reset();
+ yield endCustomizing();
+
+ is(bookmarksToolbar.collapsed, true, "Customization reset should restore collapsed-state to the bookmarks toolbar");
+ ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
+ ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
+ ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
+});
+
+// Check that the menubar will be collapsed by resetting, if the platform supports it.
+add_task(function*() {
+ let menubar = document.getElementById("toolbar-menubar");
+ const canMenubarCollapse = CustomizableUI.isToolbarDefaultCollapsed(menubar.id);
+ if (!canMenubarCollapse) {
+ return;
+ }
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
+
+ is(menubar.getBoundingClientRect().height, 0, "menubar should be hidden by default");
+ setToolbarVisibility(menubar, true);
+ isnot(menubar.getBoundingClientRect().height, 0, "menubar should be visible now");
+
+ yield startCustomizing();
+ yield gCustomizeMode.reset();
+
+ is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset in customization mode");
+ is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset in customization mode");
+
+ yield endCustomizing();
+
+ is(menubar.getAttribute("autohide"), "true", "The menubar should have autohide=true after reset");
+ is(menubar.getBoundingClientRect().height, 0, "The menubar should have height=0 after reset");
+});
+
+// Customization reset should restore collapsed-state to default-collapsed toolbars.
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
+ ok(bookmarksToolbar.collapsed, "bookmarksToolbar should be collapsed");
+ ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
+
+ setToolbarVisibility(bookmarksToolbar, true);
+ ok(!bookmarksToolbar.collapsed, "bookmarksToolbar should be visible now");
+ is(CustomizableUI.inDefaultState, false, "Should no longer be in default state");
+
+ yield startCustomizing();
+
+ ok(!bookmarksToolbar.collapsed, "The bookmarksToolbar should be visible before reset");
+ ok(!navbar.collapsed, "The navbar should be visible before reset");
+ ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
+
+ yield gCustomizeMode.reset();
+
+ ok(bookmarksToolbar.collapsed, "The bookmarksToolbar should be collapsed after reset");
+ ok(!tabsToolbar.collapsed, "TabsToolbar should not be collapsed");
+ ok(!navbar.collapsed, "The navbar should still be visible after reset");
+ ok(CustomizableUI.inDefaultState, "Everything should be back to default state");
+ yield endCustomizing();
+});
+
+// Check that the menubar will be collapsed by resetting, if the platform supports it.
+add_task(function*() {
+ let menubar = document.getElementById("toolbar-menubar");
+ const canMenubarCollapse = CustomizableUI.isToolbarDefaultCollapsed(menubar.id);
+ if (!canMenubarCollapse) {
+ return;
+ }
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
+ yield startCustomizing();
+ let resetButton = document.getElementById("customization-reset-button");
+ is(resetButton.disabled, true, "The reset button should be disabled when in default state");
+
+ setToolbarVisibility(menubar, true);
+ is(resetButton.disabled, false, "The reset button should be enabled when not in default state")
+ ok(!CustomizableUI.inDefaultState, "No longer in default state when the menubar is shown");
+
+ yield gCustomizeMode.reset();
+
+ is(resetButton.disabled, true, "The reset button should be disabled when in default state");
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state");
+
+ yield endCustomizing();
+});
diff --git a/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js b/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.js
new file mode 100644
index 000000000..1f06c1aac
--- /dev/null
+++ b/browser/components/customizableui/test/browser_938995_indefaultstate_nonremovable.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";
+
+const kWidgetId = "test-non-removable-widget";
+
+// Adding non-removable items to a toolbar or the panel shouldn't change inDefaultState
+add_task(function() {
+ ok(CustomizableUI.inDefaultState, "Should start in default state");
+
+ let button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling");
+ CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
+ button.setAttribute("removable", "false");
+ ok(CustomizableUI.inDefaultState, "Should still be in default state after navbar addition");
+ button.remove();
+
+ button = createDummyXULButton(kWidgetId, "Test non-removable inDefaultState handling");
+ CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL);
+ button.setAttribute("removable", "false");
+ ok(CustomizableUI.inDefaultState, "Should still be in default state after panel addition");
+ button.remove();
+ ok(CustomizableUI.inDefaultState, "Should be in default state after destroying both widgets");
+});
diff --git a/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js b/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.js
new file mode 100644
index 000000000..c554bffab
--- /dev/null
+++ b/browser/components/customizableui/test/browser_940013_registerToolbarNode_calls_registerArea.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";
+
+const kToolbarId = "test-registerToolbarNode-toolbar";
+const kButtonId = "test-registerToolbarNode-button";
+registerCleanupFunction(cleanup);
+
+// Registering a toolbar with defaultset attribute should work
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+ let btn = createDummyXULButton(kButtonId);
+ let toolbar = document.createElement("toolbar");
+ toolbar.id = kToolbarId;
+ toolbar.setAttribute("customizable", true);
+ toolbar.setAttribute("defaultset", kButtonId);
+ gNavToolbox.appendChild(toolbar);
+ ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
+ "Toolbar should have been registered automatically.");
+ is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
+ "Area should be registered as toolbar");
+ assertAreaPlacements(kToolbarId, [kButtonId]);
+ ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible.");
+ CustomizableUI.unregisterArea(kToolbarId, true);
+ toolbar.remove();
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+ btn.remove();
+});
+
+// Registering a toolbar without a defaultset attribute should
+// wait for the registerArea call
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+ let btn = createDummyXULButton(kButtonId);
+ let toolbar = document.createElement("toolbar");
+ toolbar.id = kToolbarId;
+ toolbar.setAttribute("customizable", true);
+ gNavToolbox.appendChild(toolbar);
+ ok(CustomizableUI.areas.indexOf(kToolbarId) == -1,
+ "Toolbar should not yet have been registered automatically.");
+ CustomizableUI.registerArea(kToolbarId, {defaultPlacements: [kButtonId]});
+ ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
+ "Toolbar should have been registered now.");
+ is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
+ "Area should be registered as toolbar");
+ assertAreaPlacements(kToolbarId, [kButtonId]);
+ ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible.");
+ CustomizableUI.unregisterArea(kToolbarId, true);
+ toolbar.remove();
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+ btn.remove();
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
+
+function cleanup() {
+ let toolbar = document.getElementById(kToolbarId);
+ if (toolbar) {
+ toolbar.remove();
+ }
+ let btn = document.getElementById(kButtonId) ||
+ gNavToolbox.querySelector("#" + kButtonId);
+ if (btn) {
+ btn.remove();
+ }
+}
diff --git a/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
new file mode 100644
index 000000000..944879a1b
--- /dev/null
+++ b/browser/components/customizableui/test/browser_940307_panel_click_closure_handling.js
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var button, menuButton;
+/* Clicking a button should close the panel */
+add_task(function*() {
+ button = document.createElement("toolbarbutton");
+ button.id = "browser_940307_button";
+ button.setAttribute("label", "Button");
+ PanelUI.contents.appendChild(button);
+ yield PanelUI.show();
+ let hiddenAgain = promisePanelHidden(window);
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ yield hiddenAgain;
+ button.remove();
+});
+
+/* Clicking a menu button should close the panel, opening the popup shouldn't. */
+add_task(function*() {
+ menuButton = document.createElement("toolbarbutton");
+ menuButton.setAttribute("type", "menu-button");
+ menuButton.id = "browser_940307_menubutton";
+ menuButton.setAttribute("label", "Menu button");
+
+ let menuPopup = document.createElement("menupopup");
+ menuPopup.id = "browser_940307_menupopup";
+
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("label", "Menu item");
+ menuItem.id = "browser_940307_menuitem";
+
+ menuPopup.appendChild(menuItem);
+ menuButton.appendChild(menuPopup);
+ PanelUI.contents.appendChild(menuButton);
+
+ yield PanelUI.show();
+ let hiddenAgain = promisePanelHidden(window);
+ let innerButton = document.getAnonymousElementByAttribute(menuButton, "anonid", "button");
+ EventUtils.synthesizeMouseAtCenter(innerButton, {});
+ yield hiddenAgain;
+
+ // Now click the dropmarker to show the menu
+ yield PanelUI.show();
+ hiddenAgain = promisePanelHidden(window);
+ let menuShown = promisePanelElementShown(window, menuPopup);
+ let dropmarker = document.getAnonymousElementByAttribute(menuButton, "type", "menu-button");
+ EventUtils.synthesizeMouseAtCenter(dropmarker, {});
+ yield menuShown;
+ // Panel should stay open:
+ ok(isPanelUIOpen(), "Panel should still be open");
+ let menuHidden = promisePanelElementHidden(window, menuPopup);
+ // Then click the menu item to close all the things
+ EventUtils.synthesizeMouseAtCenter(menuItem, {});
+ yield menuHidden;
+ yield hiddenAgain;
+ menuButton.remove();
+});
+
+add_task(function*() {
+ let searchbar = document.getElementById("searchbar");
+ gCustomizeMode.addToPanel(searchbar);
+ let placement = CustomizableUI.getPlacementOfWidget("search-container");
+ is(placement.area, CustomizableUI.AREA_PANEL, "Should be in panel");
+ yield PanelUI.show();
+ yield waitForCondition(() => "value" in searchbar && searchbar.value === "");
+
+ // Focusing a non-empty searchbox will cause us to open the
+ // autocomplete panel and search for suggestions, which would
+ // trigger network requests. Temporarily disable suggestions.
+ yield SpecialPowers.pushPrefEnv({set: [["browser.search.suggest.enabled", false]]});
+
+ searchbar.value = "foo";
+ searchbar.focus();
+ // Reaching into this context menu is pretty evil, but hey... it's a test.
+ let textbox = document.getAnonymousElementByAttribute(searchbar.textbox, "anonid", "textbox-input-box");
+ let contextmenu = document.getAnonymousElementByAttribute(textbox, "anonid", "input-box-contextmenu");
+ let contextMenuShown = promisePanelElementShown(window, contextmenu);
+ EventUtils.synthesizeMouseAtCenter(searchbar, {type: "contextmenu", button: 2});
+ yield contextMenuShown;
+
+ ok(isPanelUIOpen(), "Panel should still be open");
+
+ let selectAll = contextmenu.querySelector("[cmd='cmd_selectAll']");
+ let contextMenuHidden = promisePanelElementHidden(window, contextmenu);
+ EventUtils.synthesizeMouseAtCenter(selectAll, {});
+ yield contextMenuHidden;
+
+ // Hide the suggestion panel.
+ searchbar.textbox.popup.hidePopup();
+
+ ok(isPanelUIOpen(), "Panel should still be open");
+
+ let hiddenPanelPromise = promisePanelHidden(window);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield hiddenPanelPromise;
+ ok(!isPanelUIOpen(), "Panel should no longer be open");
+
+ // We focused the search bar earlier - ensure we don't keep doing that.
+ gURLBar.select();
+
+ CustomizableUI.reset();
+});
+
+add_task(function*() {
+ button = document.createElement("toolbarbutton");
+ button.id = "browser_946166_button_disabled";
+ button.setAttribute("disabled", "true");
+ button.setAttribute("label", "Button");
+ PanelUI.contents.appendChild(button);
+ yield PanelUI.show();
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ is(PanelUI.panel.state, "open", "Popup stays open");
+ button.removeAttribute("disabled");
+ let hiddenAgain = promisePanelHidden(window);
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ yield hiddenAgain;
+ button.remove();
+});
+
+registerCleanupFunction(function() {
+ if (button && button.parentNode) {
+ button.remove();
+ }
+ if (menuButton && menuButton.parentNode) {
+ menuButton.remove();
+ }
+ // Sadly this isn't task.jsm-enabled, so we can't wait for this to happen. But we should
+ // definitely close it here and hope it won't interfere with other tests.
+ // Of course, all the tests are meant to do this themselves, but if they fail...
+ if (isPanelUIOpen()) {
+ PanelUI.hide();
+ }
+});
diff --git a/browser/components/customizableui/test/browser_940946_removable_from_navbar_customizemode.js b/browser/components/customizableui/test/browser_940946_removable_from_navbar_customizemode.js
new file mode 100644
index 000000000..c81b004c1
--- /dev/null
+++ b/browser/components/customizableui/test/browser_940946_removable_from_navbar_customizemode.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";
+
+const kTestBtnId = "test-removable-navbar-customize-mode";
+
+// Items without the removable attribute in the navbar should be considered non-removable
+add_task(function*() {
+ let btn = createDummyXULButton(kTestBtnId, "Test removable in navbar in customize mode");
+ document.getElementById("nav-bar").customizationTarget.appendChild(btn);
+ yield startCustomizing();
+ ok(!CustomizableUI.isWidgetRemovable(kTestBtnId), "Widget should not be considered removable");
+ yield endCustomizing();
+ document.getElementById(kTestBtnId).remove();
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_941083_invalidate_wrapper_cache_createWidget.js b/browser/components/customizableui/test/browser_941083_invalidate_wrapper_cache_createWidget.js
new file mode 100644
index 000000000..1d7f86fd2
--- /dev/null
+++ b/browser/components/customizableui/test/browser_941083_invalidate_wrapper_cache_createWidget.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/. */
+
+"use strict";
+
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=941083
+
+const kWidgetId = "test-invalidate-wrapper-cache";
+
+// Check createWidget invalidates the widget cache
+add_task(function() {
+ let groupWrapper = CustomizableUI.getWidget(kWidgetId);
+ ok(groupWrapper, "Should get group wrapper.");
+ let singleWrapper = groupWrapper.forWindow(window);
+ ok(singleWrapper, "Should get single wrapper.");
+
+ CustomizableUI.createWidget({id: kWidgetId, label: "Test invalidating widgets caching"});
+
+ let newGroupWrapper = CustomizableUI.getWidget(kWidgetId);
+ ok(newGroupWrapper, "Should get a group wrapper again.");
+ isnot(newGroupWrapper, groupWrapper, "Wrappers shouldn't be the same.");
+ isnot(newGroupWrapper.provider, groupWrapper.provider, "Wrapper providers shouldn't be the same.");
+
+ let newSingleWrapper = newGroupWrapper.forWindow(window);
+ isnot(newSingleWrapper, singleWrapper, "Single wrappers shouldn't be the same.");
+ isnot(newSingleWrapper.provider, singleWrapper.provider, "Single wrapper providers shouldn't be the same.");
+
+ CustomizableUI.destroyWidget(kWidgetId);
+ ok(!CustomizableUI.getWidget(kWidgetId), "Shouldn't get a wrapper after destroying the widget.");
+});
diff --git a/browser/components/customizableui/test/browser_942581_unregisterArea_keeps_placements.js b/browser/components/customizableui/test/browser_942581_unregisterArea_keeps_placements.js
new file mode 100644
index 000000000..61adac982
--- /dev/null
+++ b/browser/components/customizableui/test/browser_942581_unregisterArea_keeps_placements.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/. */
+
+"use strict";
+
+const kToolbarName = "test-unregisterArea-placements-toolbar";
+const kTestWidgetPfx = "test-widget-for-unregisterArea-placements-";
+const kTestWidgetCount = 3;
+registerCleanupFunction(removeCustomToolbars);
+
+// unregisterArea should keep placements by default and restore them when re-adding the area
+add_task(function*() {
+ let widgetIds = [];
+ for (let i = 0; i < kTestWidgetCount; i++) {
+ let id = kTestWidgetPfx + i;
+ widgetIds.push(id);
+ let spec = {id: id, type: 'button', removable: true, label: "unregisterArea test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ }
+ for (let i = kTestWidgetCount; i < kTestWidgetCount * 2; i++) {
+ let id = kTestWidgetPfx + i;
+ widgetIds.push(id);
+ createDummyXULButton(id, "unregisterArea XUL test " + i);
+ }
+ let toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds);
+ checkAbstractAndRealPlacements(toolbarNode, widgetIds);
+
+ // Now move one of them:
+ CustomizableUI.moveWidgetWithinArea(kTestWidgetPfx + kTestWidgetCount, 0);
+ // Clone the array so we know this is the modified one:
+ let modifiedWidgetIds = [...widgetIds];
+ let movedWidget = modifiedWidgetIds.splice(kTestWidgetCount, 1)[0];
+ modifiedWidgetIds.unshift(movedWidget);
+
+ // Check it:
+ checkAbstractAndRealPlacements(toolbarNode, modifiedWidgetIds);
+
+ // Then unregister
+ CustomizableUI.unregisterArea(kToolbarName);
+
+ // Check we tell the outside world no dangerous things:
+ checkWidgetFates(widgetIds);
+ // Only then remove the real node
+ toolbarNode.remove();
+
+ // Now move one of the items to the palette, and another to the navbar:
+ let lastWidget = modifiedWidgetIds.pop();
+ CustomizableUI.removeWidgetFromArea(lastWidget);
+ lastWidget = modifiedWidgetIds.pop();
+ CustomizableUI.addWidgetToArea(lastWidget, CustomizableUI.AREA_NAVBAR);
+
+ // Recreate ourselves with the default placements being the same:
+ toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds);
+ // Then check that after doing this, our actual placements match
+ // the modified list, not the default one.
+ checkAbstractAndRealPlacements(toolbarNode, modifiedWidgetIds);
+
+ // Now remove completely:
+ CustomizableUI.unregisterArea(kToolbarName, true);
+ checkWidgetFates(modifiedWidgetIds);
+ toolbarNode.remove();
+
+ // One more time:
+ // Recreate ourselves with the default placements being the same:
+ toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds);
+ // Should now be back to default:
+ checkAbstractAndRealPlacements(toolbarNode, widgetIds);
+ CustomizableUI.unregisterArea(kToolbarName, true);
+ checkWidgetFates(widgetIds);
+ toolbarNode.remove();
+
+ // XXXgijs: ensure cleanup function doesn't barf:
+ gAddedToolbars.delete(kToolbarName);
+
+ // Remove all the XUL widgets, destroy the others:
+ for (let widget of widgetIds) {
+ let widgetWrapper = CustomizableUI.getWidget(widget);
+ if (widgetWrapper.provider == CustomizableUI.PROVIDER_XUL) {
+ gNavToolbox.palette.querySelector("#" + widget).remove();
+ } else {
+ CustomizableUI.destroyWidget(widget);
+ }
+ }
+});
+
+function checkAbstractAndRealPlacements(aNode, aExpectedPlacements) {
+ assertAreaPlacements(kToolbarName, aExpectedPlacements);
+ let physicalWidgetIds = Array.from(aNode.childNodes, (node) => node.id);
+ placementArraysEqual(aNode.id, physicalWidgetIds, aExpectedPlacements);
+}
+
+function checkWidgetFates(aWidgetIds) {
+ for (let widget of aWidgetIds) {
+ ok(!CustomizableUI.getPlacementOfWidget(widget), "Widget should be in palette");
+ ok(!document.getElementById(widget), "Widget should not be in the DOM");
+ let widgetInPalette = !!gNavToolbox.palette.querySelector("#" + widget);
+ let widgetProvider = CustomizableUI.getWidget(widget).provider;
+ let widgetIsXULWidget = widgetProvider == CustomizableUI.PROVIDER_XUL;
+ is(widgetInPalette, widgetIsXULWidget, "Just XUL Widgets should be in the palette");
+ }
+}
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_943683_migration_test.js b/browser/components/customizableui/test/browser_943683_migration_test.js
new file mode 100644
index 000000000..fe30df9e3
--- /dev/null
+++ b/browser/components/customizableui/test/browser_943683_migration_test.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";
+
+const kWidgetId = "test-addonbar-migration";
+const kWidgetId2 = "test-addonbar-migration2";
+
+var addonbar = document.getElementById(CustomizableUI.AREA_ADDONBAR);
+var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+var btn;
+var btn2;
+
+// Check we migrate normal stuff to the navbar
+add_task(function*() {
+ btn = createDummyXULButton(kWidgetId, "Test");
+ btn2 = createDummyXULButton(kWidgetId2, "Test2");
+ addonbar.insertItem(btn.id);
+ ok(btn.parentNode == navbar.customizationTarget, "Button should end up in navbar");
+ let migrationArray = addonbar.getMigratedItems();
+ is(migrationArray.length, 1, "Should have migrated 1 item");
+ is(migrationArray[0], kWidgetId, "Should have migrated our 1 item");
+
+ addonbar.currentSet = addonbar.currentSet + "," + kWidgetId2;
+ ok(btn2.parentNode == navbar.customizationTarget, "Second button should end up in the navbar");
+ migrationArray = addonbar.getMigratedItems();
+ is(migrationArray.length, 2, "Should have migrated 2 items");
+ isnot(migrationArray.indexOf(kWidgetId2), -1, "Should have migrated our second item");
+
+ let otherWindow = yield openAndLoadWindow(undefined, true);
+ try {
+ let addonBar = otherWindow.document.getElementById("addon-bar");
+ let otherMigrationArray = addonBar.getMigratedItems();
+ is(migrationArray.length, otherMigrationArray.length,
+ "Other window should have the same number of migrated items.");
+ if (migrationArray.length == otherMigrationArray.length) {
+ for (let widget of migrationArray) {
+ isnot(otherMigrationArray.indexOf(widget), -1,
+ "Migrated widget " + widget + " should also be listed as migrated in the other window.");
+ }
+ }
+ } finally {
+ yield promiseWindowClosed(otherWindow);
+ }
+ btn.remove();
+ btn2.remove();
+ CustomizableUI.reset();
+});
diff --git a/browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.js b/browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.js
new file mode 100644
index 000000000..a724b0c7f
--- /dev/null
+++ b/browser/components/customizableui/test/browser_944887_destroyWidget_should_destroy_in_palette.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 kWidgetId = "test-destroy-in-palette";
+
+// Check destroyWidget destroys the node if it's in the palette
+add_task(function*() {
+ CustomizableUI.createWidget({id: kWidgetId, label: "Test destroying widgets in palette."});
+ yield startCustomizing();
+ yield endCustomizing();
+ ok(gNavToolbox.palette.querySelector("#" + kWidgetId), "Widget still exists in palette.");
+ CustomizableUI.destroyWidget(kWidgetId);
+ ok(!gNavToolbox.palette.querySelector("#" + kWidgetId), "Widget no longer exists in palette.");
+});
diff --git a/browser/components/customizableui/test/browser_945739_showInPrivateBrowsing_customize_mode.js b/browser/components/customizableui/test/browser_945739_showInPrivateBrowsing_customize_mode.js
new file mode 100644
index 000000000..6b8acbee0
--- /dev/null
+++ b/browser/components/customizableui/test/browser_945739_showInPrivateBrowsing_customize_mode.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const kWidgetId = "test-private-browsing-customize-mode-widget";
+
+// Add a widget via the API with showInPrivateBrowsing set to false
+// and ensure it does not appear in the list of unused widgets in private
+// windows.
+add_task(function* testPrivateBrowsingCustomizeModeWidget() {
+ CustomizableUI.createWidget({
+ id: kWidgetId,
+ showInPrivateBrowsing: false
+ });
+
+ let normalWidgetArray = CustomizableUI.getUnusedWidgets(gNavToolbox.palette);
+ normalWidgetArray = normalWidgetArray.map((w) => w.id);
+ ok(normalWidgetArray.indexOf(kWidgetId) > -1,
+ "Widget should appear as unused in non-private window");
+
+ let privateWindow = yield openAndLoadWindow({private: true});
+ let privateWidgetArray = CustomizableUI.getUnusedWidgets(privateWindow.gNavToolbox.palette);
+ privateWidgetArray = privateWidgetArray.map((w) => w.id);
+ is(privateWidgetArray.indexOf(kWidgetId), -1,
+ "Widget should not appear as unused in private window");
+ yield promiseWindowClosed(privateWindow);
+
+ CustomizableUI.destroyWidget(kWidgetId);
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_addons.js b/browser/components/customizableui/test/browser_947914_button_addons.js
new file mode 100644
index 000000000..b942ee771
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_addons.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";
+
+var initialLocation = gBrowser.currentURI.spec;
+var newTab = null;
+
+add_task(function*() {
+ info("Check addons button existence and functionality");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let addonsButton = document.getElementById("add-ons-button");
+ ok(addonsButton, "Add-ons button exists in Panel Menu");
+ addonsButton.click();
+
+ newTab = gBrowser.selectedTab;
+ yield waitForCondition(() => gBrowser.currentURI &&
+ gBrowser.currentURI.spec == "about:addons");
+
+ let addonsPage = gBrowser.selectedBrowser.contentWindow.document.
+ getElementById("addons-page");
+ ok(addonsPage, "Add-ons page was opened");
+});
+
+add_task(function* asyncCleanup() {
+ gBrowser.addTab(initialLocation);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ info("Tabs were restored");
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_copy.js b/browser/components/customizableui/test/browser_947914_button_copy.js
new file mode 100644
index 000000000..c778c956f
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_copy.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var initialLocation = gBrowser.currentURI.spec;
+var globalClipboard;
+
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
+ info("Check copy button existence and functionality");
+
+ let testText = "copy text test";
+
+ gURLBar.focus();
+ info("The URL bar was focused");
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let copyButton = document.getElementById("copy-button");
+ ok(copyButton, "Copy button exists in Panel Menu");
+ ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled");
+
+ // copy text from URL bar
+ gURLBar.value = testText;
+ gURLBar.focus();
+ gURLBar.select();
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ ok(!copyButton.hasAttribute("disabled"), "Copy button is enabled when selecting");
+
+ copyButton.click();
+ is(gURLBar.value, testText, "Selected text is unaltered when clicking copy");
+
+ // check that the text was added to the clipboard
+ let clipboard = Services.clipboard;
+ let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ globalClipboard = clipboard.kGlobalClipboard;
+
+ transferable.init(null);
+ transferable.addDataFlavor("text/unicode");
+ clipboard.getData(transferable, globalClipboard);
+ let str = {}, strLength = {};
+ transferable.getTransferData("text/unicode", str, strLength);
+ let clipboardValue = "";
+
+ if (str.value) {
+ str.value.QueryInterface(Ci.nsISupportsString);
+ clipboardValue = str.value.data;
+ }
+ is(clipboardValue, testText, "Data was copied to the clipboard.");
+ });
+});
+
+registerCleanupFunction(function cleanup() {
+ Services.clipboard.emptyClipboard(globalClipboard);
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_cut.js b/browser/components/customizableui/test/browser_947914_button_cut.js
new file mode 100644
index 000000000..e6e614368
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_cut.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";
+
+var initialLocation = gBrowser.currentURI.spec;
+var globalClipboard;
+
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
+ info("Check cut button existence and functionality");
+
+ let testText = "cut text test";
+
+ gURLBar.focus();
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let cutButton = document.getElementById("cut-button");
+ ok(cutButton, "Cut button exists in Panel Menu");
+ ok(cutButton.hasAttribute("disabled"), "Cut button is disabled");
+
+ // cut text from URL bar
+ gURLBar.value = testText;
+ gURLBar.focus();
+ gURLBar.select();
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled when selecting");
+ cutButton.click();
+ is(gURLBar.value, "", "Selected text is removed from source when clicking on cut");
+
+ // check that the text was added to the clipboard
+ let clipboard = Services.clipboard;
+ let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+ globalClipboard = clipboard.kGlobalClipboard;
+
+ transferable.init(null);
+ transferable.addDataFlavor("text/unicode");
+ clipboard.getData(transferable, globalClipboard);
+ let str = {}, strLength = {};
+ transferable.getTransferData("text/unicode", str, strLength);
+ let clipboardValue = "";
+
+ if (str.value) {
+ str.value.QueryInterface(Ci.nsISupportsString);
+ clipboardValue = str.value.data;
+ }
+ is(clipboardValue, testText, "Data was copied to the clipboard.");
+ });
+});
+
+registerCleanupFunction(function cleanup() {
+ Services.clipboard.emptyClipboard(globalClipboard);
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_find.js b/browser/components/customizableui/test/browser_947914_button_find.js
new file mode 100644
index 000000000..cf3b79e34
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_find.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";
+
+add_task(function*() {
+ info("Check find button existence and functionality");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let findButton = document.getElementById("find-button");
+ ok(findButton, "Find button exists in Panel Menu");
+
+ findButton.click();
+ ok(!gFindBar.hasAttribute("hidden"), "Findbar opened successfully");
+
+ // close find bar
+ gFindBar.close();
+ info("Findbar was closed");
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_history.js b/browser/components/customizableui/test/browser_947914_button_history.js
new file mode 100644
index 000000000..64080fcc3
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_history.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";
+
+add_task(function*() {
+ info("Check history button existence and functionality");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let historyButton = document.getElementById("history-panelmenu");
+ ok(historyButton, "History button appears in Panel Menu");
+
+ historyButton.click();
+ let historyPanel = document.getElementById("PanelUI-history");
+ ok(historyPanel.getAttribute("current"), "History Panel is in view");
+
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise
+ info("Menu panel was closed");
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.js b/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.js
new file mode 100644
index 000000000..c2006bef0
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_newPrivateWindow.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";
+
+add_task(function*() {
+ info("Check private browsing button existence and functionality");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let windowWasHandled = false;
+ let privateWindow = null;
+
+ let observerWindowOpened = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ privateWindow = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
+ privateWindow.addEventListener("load", function newWindowHandler() {
+ privateWindow.removeEventListener("load", newWindowHandler, false);
+ is(privateWindow.location.href, "chrome://browser/content/browser.xul",
+ "A new browser window was opened");
+ ok(PrivateBrowsingUtils.isWindowPrivate(privateWindow), "Window is private");
+ windowWasHandled = true;
+ }, false);
+ }
+ }
+ }
+
+ Services.ww.registerNotification(observerWindowOpened);
+
+ let privateBrowsingButton = document.getElementById("privatebrowsing-button");
+ ok(privateBrowsingButton, "Private browsing button exists in Panel Menu");
+ privateBrowsingButton.click();
+
+ try {
+ yield waitForCondition(() => windowWasHandled);
+ yield promiseWindowClosed(privateWindow);
+ info("The new private window was closed");
+ }
+ catch (e) {
+ ok(false, "The new private browser window was not properly handled");
+ }
+ finally {
+ Services.ww.unregisterNotification(observerWindowOpened);
+ }
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_newWindow.js b/browser/components/customizableui/test/browser_947914_button_newWindow.js
new file mode 100644
index 000000000..47162ee86
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_newWindow.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";
+
+add_task(function*() {
+ info("Check new window button existence and functionality");
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let windowWasHandled = false;
+ let newWindow = null;
+
+ let observerWindowOpened = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ newWindow = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
+ newWindow.addEventListener("load", function newWindowHandler() {
+ newWindow.removeEventListener("load", newWindowHandler, false);
+ is(newWindow.location.href, "chrome://browser/content/browser.xul",
+ "A new browser window was opened");
+ ok(!PrivateBrowsingUtils.isWindowPrivate(newWindow), "Window is not private");
+ windowWasHandled = true;
+ }, false);
+ }
+ }
+ }
+
+ Services.ww.registerNotification(observerWindowOpened);
+
+ let newWindowButton = document.getElementById("new-window-button");
+ ok(newWindowButton, "New Window button exists in Panel Menu");
+ newWindowButton.click();
+
+ try {
+ yield waitForCondition(() => windowWasHandled);
+ yield promiseWindowClosed(newWindow);
+ info("The new window was closed");
+ }
+ catch (e) {
+ ok(false, "The new browser window was not properly handled");
+ }
+ finally {
+ Services.ww.unregisterNotification(observerWindowOpened);
+ }
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_paste.js b/browser/components/customizableui/test/browser_947914_button_paste.js
new file mode 100644
index 000000000..fc83ead56
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_paste.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var initialLocation = gBrowser.currentURI.spec;
+var globalClipboard;
+
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({gBrowser, url: "about:blank"}, function*() {
+ info("Check paste button existence and functionality");
+
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+ globalClipboard = Services.clipboard.kGlobalClipboard;
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let pasteButton = document.getElementById("paste-button");
+ ok(pasteButton, "Paste button exists in Panel Menu");
+
+ // add text to clipboard
+ let text = "Sample text for testing";
+ clipboard.copyString(text);
+
+ // test paste button by pasting text to URL bar
+ gURLBar.focus();
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ ok(!pasteButton.hasAttribute("disabled"), "Paste button is enabled");
+ pasteButton.click();
+
+ is(gURLBar.value, text, "Text pasted successfully");
+ });
+});
+
+registerCleanupFunction(function cleanup() {
+ Services.clipboard.emptyClipboard(globalClipboard);
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_print.js b/browser/components/customizableui/test/browser_947914_button_print.js
new file mode 100644
index 000000000..af7abcaeb
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_print.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const isOSX = (Services.appinfo.OS === "Darwin");
+
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com/",
+ }, function* () {
+ info("Check print button existence and functionality");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ yield waitForCondition(() => document.getElementById("print-button") != null);
+
+ let printButton = document.getElementById("print-button");
+ ok(printButton, "Print button exists in Panel Menu");
+
+ if (isOSX) {
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+ info("Menu panel was closed");
+ }
+ else {
+ printButton.click();
+ yield waitForCondition(() => gInPrintPreviewMode);
+
+ ok(gInPrintPreviewMode, "Entered print preview mode");
+
+ // close print preview
+ if (gInPrintPreviewMode) {
+ PrintUtils.exitPrintPreview();
+ yield waitForCondition(() => !window.gInPrintPreviewMode);
+ info("Exited print preview")
+ }
+ }
+ });
+});
+
diff --git a/browser/components/customizableui/test/browser_947914_button_savePage.js b/browser/components/customizableui/test/browser_947914_button_savePage.js
new file mode 100644
index 000000000..543ff3ca6
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_savePage.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";
+
+add_task(function*() {
+ info("Check save page button existence");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let savePageButton = document.getElementById("save-page-button");
+ ok(savePageButton, "Save Page button exists in Panel Menu");
+
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+ info("Menu panel was closed");
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_zoomIn.js b/browser/components/customizableui/test/browser_947914_button_zoomIn.js
new file mode 100644
index 000000000..4463d87d6
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_zoomIn.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";
+
+var initialPageZoom = ZoomManager.zoom;
+
+add_task(function*() {
+ info("Check zoom in button existence and functionality");
+
+ is(initialPageZoom, 1, "Initial zoom factor should be 1");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let zoomInButton = document.getElementById("zoom-in-button");
+ ok(zoomInButton, "Zoom in button exists in Panel Menu");
+
+ zoomInButton.click();
+ let pageZoomLevel = parseInt(ZoomManager.zoom * 100);
+ let zoomResetButton = document.getElementById("zoom-reset-button");
+ let expectedZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
+ ok(pageZoomLevel > 100 && pageZoomLevel == expectedZoomLevel, "Page zoomed in correctly");
+
+ // close the Panel
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+ info("Menu panel was closed");
+});
+
+add_task(function* asyncCleanup() {
+ // reset zoom level
+ ZoomManager.zoom = initialPageZoom;
+ info("Zoom level was restored");
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_zoomOut.js b/browser/components/customizableui/test/browser_947914_button_zoomOut.js
new file mode 100644
index 000000000..f9f51ac9a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_zoomOut.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/. */
+
+"use strict";
+
+var initialPageZoom = ZoomManager.zoom;
+
+add_task(function*() {
+ info("Check zoom out button existence and functionality");
+
+ is(initialPageZoom, 1, "Initial zoom factor should be 1");
+
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let zoomOutButton = document.getElementById("zoom-out-button");
+ ok(zoomOutButton, "Zoom out button exists in Panel Menu");
+
+ zoomOutButton.click();
+ let pageZoomLevel = Math.round(ZoomManager.zoom * 100);
+
+ let zoomResetButton = document.getElementById("zoom-reset-button");
+ let expectedZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
+ ok(pageZoomLevel < 100 && pageZoomLevel == expectedZoomLevel, "Page zoomed out correctly");
+
+ // close the panel
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+ info("Menu panel was closed");
+});
+
+add_task(function* asyncCleanup() {
+ // reset zoom level
+ ZoomManager.zoom = initialPageZoom;
+ info("Zoom level was restored");
+});
diff --git a/browser/components/customizableui/test/browser_947914_button_zoomReset.js b/browser/components/customizableui/test/browser_947914_button_zoomReset.js
new file mode 100644
index 000000000..372097665
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947914_button_zoomReset.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";
+
+var initialPageZoom = ZoomManager.zoom;
+
+add_task(function*() {
+ info("Check zoom reset button existence and functionality");
+
+ is(initialPageZoom, 1, "Page zoom reset correctly");
+ ZoomManager.zoom = 0.5;
+ yield PanelUI.show();
+ info("Menu panel was opened");
+
+ let zoomResetButton = document.getElementById("zoom-reset-button");
+ ok(zoomResetButton, "Zoom reset button exists in Panel Menu");
+
+ zoomResetButton.click();
+ yield new Promise(SimpleTest.executeSoon);
+
+ let pageZoomLevel = Math.floor(ZoomManager.zoom * 100);
+ let expectedZoomLevel = 100;
+ let buttonZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
+ is(pageZoomLevel, expectedZoomLevel, "Page zoom reset correctly");
+ is(pageZoomLevel, buttonZoomLevel, "Button displays the correct zoom level");
+
+ // close the panel
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+ info("Menu panel was closed");
+});
+
+add_task(function* asyncCleanup() {
+ // reset zoom level
+ ZoomManager.zoom = initialPageZoom;
+ info("Zoom level was restored");
+});
diff --git a/browser/components/customizableui/test/browser_947987_removable_default.js b/browser/components/customizableui/test/browser_947987_removable_default.js
new file mode 100644
index 000000000..98325ec2a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_947987_removable_default.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 kWidgetId = "test-removable-widget-default";
+const kNavBar = CustomizableUI.AREA_NAVBAR;
+var widgetCounter = 0;
+
+registerCleanupFunction(removeCustomToolbars);
+
+// Sanity checks
+add_task(function() {
+ let brokenSpec = {id: kWidgetId + (widgetCounter++), removable: false};
+ SimpleTest.doesThrow(() => CustomizableUI.createWidget(brokenSpec),
+ "Creating non-removable widget without defaultArea should throw.");
+
+ // Widget without removable set should be removable:
+ let wrapper = CustomizableUI.createWidget({id: kWidgetId + (widgetCounter++)});
+ ok(CustomizableUI.isWidgetRemovable(wrapper.id), "Should be removable by default.");
+ CustomizableUI.destroyWidget(wrapper.id);
+});
+
+// Test non-removable widget with defaultArea
+add_task(function*() {
+ // Non-removable widget with defaultArea should work:
+ let spec = {id: kWidgetId + (widgetCounter++), removable: false,
+ defaultArea: kNavBar};
+ let widgetWrapper;
+ try {
+ widgetWrapper = CustomizableUI.createWidget(spec);
+ } catch (ex) {
+ ok(false, "Creating a non-removable widget with a default area should not throw.");
+ return;
+ }
+
+ let placement = CustomizableUI.getPlacementOfWidget(spec.id);
+ ok(placement, "Widget should be placed.");
+ is(placement.area, kNavBar, "Widget should be in navbar");
+ let singleWrapper = widgetWrapper.forWindow(window);
+ ok(singleWrapper, "Widget should exist in window.");
+ ok(singleWrapper.node, "Widget node should exist in window.");
+ let expectedParent = CustomizableUI.getCustomizeTargetForArea(kNavBar, window);
+ is(singleWrapper.node.parentNode, expectedParent, "Widget should be in navbar.");
+
+ let otherWin = yield openAndLoadWindow(true);
+ placement = CustomizableUI.getPlacementOfWidget(spec.id);
+ ok(placement, "Widget should be placed.");
+ is(placement && placement.area, kNavBar, "Widget should be in navbar");
+
+ singleWrapper = widgetWrapper.forWindow(otherWin);
+ ok(singleWrapper, "Widget should exist in other window.");
+ if (singleWrapper) {
+ ok(singleWrapper.node, "Widget node should exist in other window.");
+ if (singleWrapper.node) {
+ let expectedParent = CustomizableUI.getCustomizeTargetForArea(kNavBar, otherWin);
+ is(singleWrapper.node.parentNode, expectedParent,
+ "Widget should be in navbar in other window.");
+ }
+ }
+ CustomizableUI.destroyWidget(spec.id);
+ yield promiseWindowClosed(otherWin);
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_948985_non_removable_defaultArea.js b/browser/components/customizableui/test/browser_948985_non_removable_defaultArea.js
new file mode 100644
index 000000000..456c9ed02
--- /dev/null
+++ b/browser/components/customizableui/test/browser_948985_non_removable_defaultArea.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/. */
+
+const kWidgetId = "test-destroy-non-removable-defaultArea";
+
+add_task(function() {
+ let spec = {id: kWidgetId, label: "Test non-removable defaultArea re-adding.",
+ removable: false, defaultArea: CustomizableUI.AREA_NAVBAR};
+ CustomizableUI.createWidget(spec);
+ let placement = CustomizableUI.getPlacementOfWidget(kWidgetId);
+ ok(placement, "Should have placed the widget.");
+ is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
+ CustomizableUI.destroyWidget(kWidgetId);
+ CustomizableUI.removeWidgetFromArea(kWidgetId);
+
+ CustomizableUI.createWidget(spec);
+ ok(placement, "Should have placed the widget.");
+ is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
+ CustomizableUI.destroyWidget(kWidgetId);
+ CustomizableUI.removeWidgetFromArea(kWidgetId);
+
+ const kPrefCustomizationAutoAdd = "browser.uiCustomization.autoAdd";
+ Services.prefs.setBoolPref(kPrefCustomizationAutoAdd, false);
+ CustomizableUI.createWidget(spec);
+ ok(placement, "Should have placed the widget.");
+ is(placement && placement.area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
+ CustomizableUI.destroyWidget(kWidgetId);
+ CustomizableUI.removeWidgetFromArea(kWidgetId);
+ Services.prefs.clearUserPref(kPrefCustomizationAutoAdd);
+});
+
diff --git a/browser/components/customizableui/test/browser_952963_areaType_getter_no_area.js b/browser/components/customizableui/test/browser_952963_areaType_getter_no_area.js
new file mode 100644
index 000000000..fc05a99fd
--- /dev/null
+++ b/browser/components/customizableui/test/browser_952963_areaType_getter_no_area.js
@@ -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/. */
+
+"use strict";
+
+const kToolbarName = "test-unregisterArea-areaType";
+const kUnregisterAreaTestWidget = "test-widget-for-unregisterArea-areaType";
+const kTestWidget = "test-widget-no-area-areaType";
+registerCleanupFunction(removeCustomToolbars);
+
+function checkAreaType(widget) {
+ try {
+ is(widget.areaType, null, "areaType should be null");
+ } catch (ex) {
+ info("Fetching areaType threw: " + ex);
+ ok(false, "areaType getter shouldn't throw.");
+ }
+}
+
+// widget wrappers in unregisterArea'd areas and nowhere shouldn't throw when checking areaTypes.
+add_task(function*() {
+ // Using the ID before it's been created will imply a XUL wrapper; we'll test
+ // an API-based wrapper below
+ let toolbarNode = createToolbarWithPlacements(kToolbarName, [kUnregisterAreaTestWidget]);
+ CustomizableUI.unregisterArea(kToolbarName);
+ toolbarNode.remove();
+
+ let w = CustomizableUI.getWidget(kUnregisterAreaTestWidget);
+ checkAreaType(w);
+
+ w = CustomizableUI.getWidget(kTestWidget);
+ checkAreaType(w);
+
+ let spec = {id: kUnregisterAreaTestWidget, type: 'button', removable: true,
+ label: "areaType test", tooltiptext: "areaType test"};
+ CustomizableUI.createWidget(spec);
+ toolbarNode = createToolbarWithPlacements(kToolbarName, [kUnregisterAreaTestWidget]);
+ CustomizableUI.unregisterArea(kToolbarName);
+ toolbarNode.remove();
+ w = CustomizableUI.getWidget(spec.id);
+ checkAreaType(w);
+ CustomizableUI.removeWidgetFromArea(kUnregisterAreaTestWidget);
+ checkAreaType(w);
+ // XXXgijs: ensure cleanup function doesn't barf:
+ gAddedToolbars.delete(kToolbarName);
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
+
diff --git a/browser/components/customizableui/test/browser_956602_remove_special_widget.js b/browser/components/customizableui/test/browser_956602_remove_special_widget.js
new file mode 100644
index 000000000..f87b2e4c8
--- /dev/null
+++ b/browser/components/customizableui/test/browser_956602_remove_special_widget.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/. */
+
+"use strict";
+
+
+// Adding a separator and then dragging it out of the navbar shouldn't throw
+add_task(function*() {
+ try {
+ let navbar = document.getElementById("nav-bar");
+ let separatorSelector = "toolbarseparator[id^=customizableui-special-separator]";
+ ok(!navbar.querySelector(separatorSelector), "Shouldn't be a separator in the navbar");
+ CustomizableUI.addWidgetToArea('separator', 'nav-bar');
+ yield startCustomizing();
+ let separator = navbar.querySelector(separatorSelector);
+ ok(separator, "There should be a separator in the navbar now.");
+ let palette = document.getElementById("customization-palette");
+ simulateItemDrag(separator, palette);
+ ok(!palette.querySelector(separatorSelector), "No separator in the palette.");
+ } catch (ex) {
+ Cu.reportError(ex);
+ ok(false, "Shouldn't throw an exception moving an item to the navbar.");
+ } finally {
+ yield endCustomizing();
+ }
+});
+
+add_task(function* asyncCleanup() {
+ resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js b/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.js
new file mode 100644
index 000000000..7c4f6cfa4
--- /dev/null
+++ b/browser/components/customizableui/test/browser_962069_drag_to_overflow_chevron.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";
+
+var originalWindowWidth;
+
+// Drag to overflow chevron should open the overflow panel.
+add_task(function*() {
+ originalWindowWidth = window.outerWidth;
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ ok(!navbar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+ ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+ let widgetOverflowPanel = document.getElementById("widget-overflow");
+ let panelShownPromise = promisePanelElementShown(window, widgetOverflowPanel);
+ let identityBox = document.getElementById("identity-box");
+ let overflowChevron = document.getElementById("nav-bar-overflow-button");
+
+ // Listen for hiding immediately so we don't miss the event because of the
+ // async-ness of the 'shown' yield...
+ let panelHiddenPromise = promisePanelElementHidden(window, widgetOverflowPanel);
+
+ var ds = Components.classes["@mozilla.org/widget/dragservice;1"].
+ getService(Components.interfaces.nsIDragService);
+
+ ds.startDragSession();
+ try {
+ var [result, dataTransfer] = EventUtils.synthesizeDragOver(identityBox, overflowChevron);
+
+ // Wait for showing panel before ending drag session.
+ yield panelShownPromise;
+
+ EventUtils.synthesizeDropAfterDragOver(result, dataTransfer, overflowChevron);
+ } finally {
+ ds.endDragSession(true);
+ }
+
+ info("Overflow panel is shown.");
+
+ widgetOverflowPanel.hidePopup();
+ yield panelHiddenPromise;
+});
+
+add_task(function*() {
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+ ok(!navbar.hasAttribute("overflowing"), "Should not have an overflowing toolbar.");
+});
diff --git a/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js b/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.js
new file mode 100644
index 000000000..cf2603999
--- /dev/null
+++ b/browser/components/customizableui/test/browser_962884_opt_in_disable_hyphens.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/. */
+
+"use strict";
+
+add_task(function*() {
+ const kNormalLabel = "Character Encoding";
+ CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_NAVBAR);
+ let characterEncoding = document.getElementById("characterencoding-button");
+ const kOriginalLabel = characterEncoding.getAttribute("label");
+ characterEncoding.setAttribute("label", "\u00ad" + kNormalLabel);
+ CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_PANEL);
+
+ yield PanelUI.show();
+
+ is(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should be disabled if the &shy; character is present in the label");
+ let multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text");
+ let multilineTextCS = getComputedStyle(multilineText);
+ is(multilineTextCS.MozHyphens, "manual", "-moz-hyphens should be set to manual when the &shy; character is present.")
+
+ let hiddenPanelPromise = promisePanelHidden(window);
+ PanelUI.toggle();
+ yield hiddenPanelPromise;
+
+ characterEncoding.setAttribute("label", kNormalLabel);
+
+ yield PanelUI.show();
+
+ isnot(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should not be disabled if the &shy; character is not present in the label");
+ multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text");
+ multilineTextCS = getComputedStyle(multilineText);
+ is(multilineTextCS.MozHyphens, "auto", "-moz-hyphens should be set to auto by default.")
+
+ hiddenPanelPromise = promisePanelHidden(window);
+ PanelUI.toggle();
+ yield hiddenPanelPromise;
+
+ characterEncoding.setAttribute("label", "\u00ad" + kNormalLabel);
+ CustomizableUI.removeWidgetFromArea("characterencoding-button");
+ yield startCustomizing();
+
+ isnot(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should not be disabled when the widget is in the palette");
+
+ gCustomizeMode.addToPanel(characterEncoding);
+ is(characterEncoding.getAttribute("auto-hyphens"), "off",
+ "Hyphens should be disabled if the &shy; character is present in the label in customization mode");
+ multilineText = document.getAnonymousElementByAttribute(characterEncoding, "class", "toolbarbutton-multiline-text");
+ multilineTextCS = getComputedStyle(multilineText);
+ is(multilineTextCS.MozHyphens, "manual", "-moz-hyphens should be set to manual when the &shy; character is present in customization mode.")
+
+ yield endCustomizing();
+
+ CustomizableUI.addWidgetToArea("characterencoding-button", CustomizableUI.AREA_NAVBAR);
+ ok(!characterEncoding.hasAttribute("auto-hyphens"),
+ "Removing the widget from the panel should remove the auto-hyphens attribute");
+
+ characterEncoding.setAttribute("label", kOriginalLabel);
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js
new file mode 100644
index 000000000..e5710c50a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.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 kToolbar = "test-toolbar-963639-non-customizable-customizing-attribute";
+
+add_task(function*() {
+ info("Test for Bug 963639 - CustomizeMode _onToolbarVisibilityChange sets @customizing on non-customizable toolbars");
+
+ let toolbar = document.createElement("toolbar");
+ toolbar.id = kToolbar;
+ gNavToolbox.appendChild(toolbar);
+
+ let testToolbar = document.getElementById(kToolbar)
+ ok(testToolbar, "Toolbar was created.");
+ is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 1,
+ "Toolbar was added to the navigator toolbox");
+
+ toolbar.setAttribute("toolbarname", "NonCustomizableToolbarCustomizingAttribute");
+ toolbar.setAttribute("collapsed", "true");
+
+ yield startCustomizing();
+ window.setToolbarVisibility(toolbar, "true");
+ isnot(toolbar.getAttribute("customizing"), "true",
+ "Toolbar doesn't have the customizing attribute");
+
+ yield endCustomizing();
+ gNavToolbox.removeChild(toolbar);
+
+ is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 0,
+ "Toolbar was removed from the navigator toolbox");
+});
diff --git a/browser/components/customizableui/test/browser_967000_button_charEncoding.js b/browser/components/customizableui/test/browser_967000_button_charEncoding.js
new file mode 100644
index 000000000..0688ebbd6
--- /dev/null
+++ b/browser/components/customizableui/test/browser_967000_button_charEncoding.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";
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html";
+
+add_task(function*() {
+ info("Check Character Encoding button functionality");
+
+ // add the Character Encoding button to the panel
+ CustomizableUI.addWidgetToArea("characterencoding-button",
+ CustomizableUI.AREA_PANEL);
+
+ // check the button's functionality
+ yield PanelUI.show();
+
+ let charEncodingButton = document.getElementById("characterencoding-button");
+ ok(charEncodingButton, "The Character Encoding button was added to the Panel Menu");
+ is(charEncodingButton.getAttribute("disabled"), "true",
+ "The Character encoding button is initially disabled");
+
+ let panelHidePromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHidePromise;
+
+ let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE, true, true);
+
+ yield PanelUI.show();
+ ok(!charEncodingButton.hasAttribute("disabled"), "The Character encoding button gets enabled");
+ let characterEncodingView = document.getElementById("PanelUI-characterEncodingView");
+ let subviewShownPromise = subviewShown(characterEncodingView);
+ charEncodingButton.click();
+ yield subviewShownPromise;
+
+ ok(characterEncodingView.hasAttribute("current"), "The Character encoding panel is displayed");
+
+ let pinnedEncodings = document.getElementById("PanelUI-characterEncodingView-pinned");
+ let charsetsList = document.getElementById("PanelUI-characterEncodingView-charsets");
+ ok(pinnedEncodings, "Pinned charsets are available");
+ ok(charsetsList, "Charsets list is available");
+
+ let checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
+ is(checkedButtons.length, 2, "There should be 2 checked items (1 charset, 1 detector).");
+ is(checkedButtons[0].getAttribute("label"), "Unicode", "The unicode encoding is correctly selected");
+ is(characterEncodingView.querySelectorAll("#PanelUI-characterEncodingView-autodetect toolbarbutton[checked='true']").length,
+ 1,
+ "There should be 1 checked detector.");
+
+ panelHidePromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHidePromise;
+
+ yield BrowserTestUtils.removeTab(newTab);
+});
+
+add_task(function* asyncCleanup() {
+ // reset the panel to the default state
+ yield resetCustomization();
+ ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
+});
diff --git a/browser/components/customizableui/test/browser_967000_button_feeds.js b/browser/components/customizableui/test/browser_967000_button_feeds.js
new file mode 100644
index 000000000..8f391941a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_967000_button_feeds.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/. */
+
+"use strict";
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/feeds_test_page.html";
+const TEST_FEED = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test-feed.xml"
+
+var newTab = null;
+var initialLocation = gBrowser.currentURI.spec;
+
+add_task(function*() {
+ info("Check Subscribe button functionality");
+
+ // add the Subscribe button to the panel
+ CustomizableUI.addWidgetToArea("feed-button",
+ CustomizableUI.AREA_PANEL);
+
+ // check the button's functionality
+ yield PanelUI.show();
+
+ let feedButton = document.getElementById("feed-button");
+ ok(feedButton, "The Subscribe button was added to the Panel Menu");
+ is(feedButton.getAttribute("disabled"), "true", "The Subscribe button is initially disabled");
+
+ let panelHidePromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHidePromise;
+
+ newTab = gBrowser.selectedTab;
+ yield promiseTabLoadEvent(newTab, TEST_PAGE);
+
+ yield PanelUI.show();
+
+ yield waitForCondition(() => !feedButton.hasAttribute("disabled"));
+ ok(!feedButton.hasAttribute("disabled"), "The Subscribe button gets enabled");
+
+ feedButton.click();
+ yield promiseTabLoadEvent(newTab, TEST_FEED);
+
+ is(gBrowser.currentURI.spec, TEST_FEED, "Subscribe page opened");
+ ok(!isPanelUIOpen(), "Panel is closed");
+
+ if (isPanelUIOpen()) {
+ panelHidePromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHidePromise;
+ }
+});
+
+add_task(function* asyncCleanup() {
+ // reset the panel UI to the default state
+ yield resetCustomization();
+ ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
+
+ // restore the initial location
+ gBrowser.addTab(initialLocation);
+ gBrowser.removeTab(newTab);
+});
diff --git a/browser/components/customizableui/test/browser_967000_button_sync.js b/browser/components/customizableui/test/browser_967000_button_sync.js
new file mode 100644
index 000000000..15a3235e0
--- /dev/null
+++ b/browser/components/customizableui/test/browser_967000_button_sync.js
@@ -0,0 +1,335 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+let {SyncedTabs} = Cu.import("resource://services-sync/SyncedTabs.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm");
+
+// These are available on the widget implementation, but it seems impossible
+// to grab that impl at runtime.
+const DECKINDEX_TABS = 0;
+const DECKINDEX_TABSDISABLED = 1;
+const DECKINDEX_FETCHING = 2;
+const DECKINDEX_NOCLIENTS = 3;
+
+var initialLocation = gBrowser.currentURI.spec;
+var newTab = null;
+
+// A helper to notify there are new tabs. Returns a promise that is resolved
+// once the UI has been updated.
+function updateTabsPanel() {
+ let promiseTabsUpdated = promiseObserverNotified("synced-tabs-menu:test:tabs-updated");
+ Services.obs.notifyObservers(null, SyncedTabs.TOPIC_TABS_CHANGED, null);
+ return promiseTabsUpdated;
+}
+
+// This is the mock we use for SyncedTabs.jsm - tests may override various
+// functions.
+let mockedInternal = {
+ get isConfiguredToSyncTabs() { return true; },
+ getTabClients() { return []; },
+ syncTabs() {},
+ hasSyncedThisSession: false,
+};
+
+
+add_task(function* setup() {
+ let oldInternal = SyncedTabs._internal;
+ SyncedTabs._internal = mockedInternal;
+
+ registerCleanupFunction(() => {
+ SyncedTabs._internal = oldInternal;
+ });
+});
+
+// The test expects the about:preferences#sync page to open in the current tab
+function* openPrefsFromMenuPanel(expectedPanelId, entryPoint) {
+ info("Check Sync button functionality");
+ Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", "http://example.com/");
+
+ // add the Sync button to the panel
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+
+ // check the button's functionality
+ yield PanelUI.show();
+
+ if (entryPoint == "uitour") {
+ UITour.tourBrowsersByWindow.set(window, new Set());
+ UITour.tourBrowsersByWindow.get(window).add(gBrowser.selectedBrowser);
+ }
+
+ let syncButton = document.getElementById("sync-button");
+ ok(syncButton, "The Sync button was added to the Panel Menu");
+
+ syncButton.click();
+ let syncPanel = document.getElementById("PanelUI-remotetabs");
+ ok(syncPanel.getAttribute("current"), "Sync Panel is in view");
+
+ // Sync is not configured - verify that state is reflected.
+ let subpanel = document.getElementById(expectedPanelId)
+ ok(!subpanel.hidden, "sync setup element is visible");
+
+ // Find and click the "setup" button.
+ let setupButton = subpanel.querySelector(".PanelUI-remotetabs-prefs-button");
+ setupButton.click();
+
+ let deferred = Promise.defer();
+ let handler = (e) => {
+ if (e.originalTarget != gBrowser.selectedBrowser.contentDocument ||
+ e.target.location.href == "about:blank") {
+ info("Skipping spurious 'load' event for " + e.target.location.href);
+ return;
+ }
+ gBrowser.selectedBrowser.removeEventListener("load", handler, true);
+ deferred.resolve();
+ }
+ gBrowser.selectedBrowser.addEventListener("load", handler, true);
+
+ yield deferred.promise;
+ newTab = gBrowser.selectedTab;
+
+ is(gBrowser.currentURI.spec, "about:preferences?entrypoint=" + entryPoint + "#sync",
+ "Firefox Sync preference page opened with `menupanel` entrypoint");
+ ok(!isPanelUIOpen(), "The panel closed");
+
+ if (isPanelUIOpen()) {
+ let panelHidePromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHidePromise;
+ }
+}
+
+function* asyncCleanup() {
+ Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
+ // reset the panel UI to the default state
+ yield resetCustomization();
+ ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
+
+ // restore the tabs
+ gBrowser.addTab(initialLocation);
+ gBrowser.removeTab(newTab);
+ UITour.tourBrowsersByWindow.delete(window);
+}
+
+// When Sync is not setup.
+add_task(() => openPrefsFromMenuPanel("PanelUI-remotetabs-setupsync", "synced-tabs"));
+add_task(asyncCleanup);
+
+// When Sync is configured in a "needs reauthentication" state.
+add_task(function* () {
+ // configure our broadcasters so we are in the right state.
+ document.getElementById("sync-reauth-state").hidden = false;
+ document.getElementById("sync-setup-state").hidden = true;
+ document.getElementById("sync-syncnow-state").hidden = true;
+ yield openPrefsFromMenuPanel("PanelUI-remotetabs-reauthsync", "synced-tabs")
+});
+
+// Test the mobile promo links
+add_task(function* () {
+ // change the preferences for the mobile links.
+ Services.prefs.setCharPref("identity.mobilepromo.android", "http://example.com/?os=android&tail=");
+ Services.prefs.setCharPref("identity.mobilepromo.ios", "http://example.com/?os=ios&tail=");
+
+ mockedInternal.getTabClients = () => [];
+ mockedInternal.syncTabs = () => Promise.resolve();
+
+ document.getElementById("sync-reauth-state").hidden = true;
+ document.getElementById("sync-setup-state").hidden = true;
+ document.getElementById("sync-syncnow-state").hidden = false;
+
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+
+ let syncPanel = document.getElementById("PanelUI-remotetabs");
+ let links = syncPanel.querySelectorAll(".remotetabs-promo-link");
+
+ is(links.length, 2, "found 2 links as expected");
+
+ // test each link and left and middle mouse buttons
+ for (let link of links) {
+ for (let button = 0; button < 2; button++) {
+ yield PanelUI.show();
+ EventUtils.sendMouseEvent({ type: "click", button }, link, window);
+ // the panel should have been closed.
+ ok(!isPanelUIOpen(), "click closed the panel");
+ // should be a new tab - wait for the load.
+ is(gBrowser.tabs.length, 2, "there's a new tab");
+ yield new Promise(resolve => {
+ if (gBrowser.selectedBrowser.currentURI.spec == "about:blank") {
+ gBrowser.selectedBrowser.addEventListener("load", function listener(e) {
+ gBrowser.selectedBrowser.removeEventListener("load", listener, true);
+ resolve();
+ }, true);
+ return;
+ }
+ // the new tab has already transitioned away from about:blank so we
+ // are good to go.
+ resolve();
+ });
+
+ let os = link.getAttribute("mobile-promo-os");
+ let expectedUrl = `http://example.com/?os=${os}&tail=synced-tabs`;
+ is(gBrowser.selectedBrowser.currentURI.spec, expectedUrl, "correct URL");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ }
+ }
+
+ // test each link and right mouse button - should be a noop.
+ yield PanelUI.show();
+ for (let link of links) {
+ EventUtils.sendMouseEvent({ type: "click", button: 2 }, link, window);
+ // the panel should still be open
+ ok(isPanelUIOpen(), "panel remains open after right-click");
+ is(gBrowser.tabs.length, 1, "no new tab was opened");
+ }
+ PanelUI.hide();
+
+ Services.prefs.clearUserPref("identity.mobilepromo.android");
+ Services.prefs.clearUserPref("identity.mobilepromo.ios");
+});
+
+// Test the "Sync Now" button
+add_task(function* () {
+ mockedInternal.getTabClients = () => [];
+ mockedInternal.syncTabs = () => {
+ return Promise.resolve();
+ }
+
+ // configure our broadcasters so we are in the right state.
+ document.getElementById("sync-reauth-state").hidden = true;
+ document.getElementById("sync-setup-state").hidden = true;
+ document.getElementById("sync-syncnow-state").hidden = false;
+
+ // add the Sync button to the panel
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+ yield PanelUI.show();
+ document.getElementById("sync-button").click();
+ let syncPanel = document.getElementById("PanelUI-remotetabs");
+ ok(syncPanel.getAttribute("current"), "Sync Panel is in view");
+
+ let subpanel = document.getElementById("PanelUI-remotetabs-main")
+ ok(!subpanel.hidden, "main pane is visible");
+ let deck = document.getElementById("PanelUI-remotetabs-deck");
+
+ // The widget is still fetching tabs, as we've neutered everything that
+ // provides them
+ is(deck.selectedIndex, DECKINDEX_FETCHING, "first deck entry is visible");
+
+ let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
+
+ let didSync = false;
+ let oldDoSync = gSyncUI.doSync;
+ gSyncUI.doSync = function() {
+ didSync = true;
+ mockedInternal.hasSyncedThisSession = true;
+ gSyncUI.doSync = oldDoSync;
+ }
+ syncNowButton.click();
+ ok(didSync, "clicking the button called the correct function");
+
+ // Tell the widget there are tabs available, but with zero clients.
+ mockedInternal.getTabClients = () => {
+ return Promise.resolve([]);
+ }
+ yield updateTabsPanel();
+ // The UI should be showing the "no clients" pane.
+ is(deck.selectedIndex, DECKINDEX_NOCLIENTS, "no-clients deck entry is visible");
+
+ // Tell the widget there are tabs available - we have 3 clients, one with no
+ // tabs.
+ mockedInternal.getTabClients = () => {
+ return Promise.resolve([
+ {
+ id: "guid_mobile",
+ type: "client",
+ name: "My Phone",
+ tabs: [],
+ },
+ {
+ id: "guid_desktop",
+ type: "client",
+ name: "My Desktop",
+ tabs: [
+ {
+ title: "http://example.com/10",
+ lastUsed: 10, // the most recent
+ },
+ {
+ title: "http://example.com/1",
+ lastUsed: 1, // the least recent.
+ },
+ {
+ title: "http://example.com/5",
+ lastUsed: 5,
+ },
+ ],
+ },
+ {
+ id: "guid_second_desktop",
+ name: "My Other Desktop",
+ tabs: [
+ {
+ title: "http://example.com/6",
+ lastUsed: 6,
+ }
+ ],
+ },
+ ]);
+ };
+ yield updateTabsPanel();
+
+ // The UI should be showing tabs!
+ is(deck.selectedIndex, DECKINDEX_TABS, "no-clients deck entry is visible");
+ let tabList = document.getElementById("PanelUI-remotetabs-tabslist");
+ let node = tabList.firstChild;
+ // First entry should be the client with the most-recent tab.
+ is(node.getAttribute("itemtype"), "client", "node is a client entry");
+ is(node.textContent, "My Desktop", "correct client");
+ // Next entry is the most-recent tab
+ node = node.nextSibling;
+ is(node.getAttribute("itemtype"), "tab", "node is a tab");
+ is(node.getAttribute("label"), "http://example.com/10");
+
+ // Next entry is the next-most-recent tab
+ node = node.nextSibling;
+ is(node.getAttribute("itemtype"), "tab", "node is a tab");
+ is(node.getAttribute("label"), "http://example.com/5");
+
+ // Next entry is the least-recent tab from the first client.
+ node = node.nextSibling;
+ is(node.getAttribute("itemtype"), "tab", "node is a tab");
+ is(node.getAttribute("label"), "http://example.com/1");
+
+ // Next is a menuseparator between the clients.
+ node = node.nextSibling;
+ is(node.nodeName, "menuseparator");
+
+ // Next is the client with 1 tab.
+ node = node.nextSibling;
+ is(node.getAttribute("itemtype"), "client", "node is a client entry");
+ is(node.textContent, "My Other Desktop", "correct client");
+ // Its single tab
+ node = node.nextSibling;
+ is(node.getAttribute("itemtype"), "tab", "node is a tab");
+ is(node.getAttribute("label"), "http://example.com/6");
+
+ // Next is a menuseparator between the clients.
+ node = node.nextSibling;
+ is(node.nodeName, "menuseparator");
+
+ // Next is the client with no tab.
+ node = node.nextSibling;
+ is(node.getAttribute("itemtype"), "client", "node is a client entry");
+ is(node.textContent, "My Phone", "correct client");
+ // There is a single node saying there's no tabs for the client.
+ node = node.nextSibling;
+ is(node.nodeName, "label", "node is a label");
+ is(node.getAttribute("itemtype"), "", "node is neither a tab nor a client");
+
+ node = node.nextSibling;
+ is(node, null, "no more entries");
+});
diff --git a/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.js
new file mode 100644
index 000000000..88c30bf81
--- /dev/null
+++ b/browser/components/customizableui/test/browser_968447_bookmarks_toolbar_items_in_panel.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";
+
+// Bug 968447 - The Bookmarks Toolbar Items doesn't appear as a
+// normal menu panel button in new windows.
+add_task(function*() {
+ const buttonId = "bookmarks-toolbar-placeholder";
+ yield startCustomizing();
+ CustomizableUI.addWidgetToArea("personal-bookmarks", CustomizableUI.AREA_PANEL);
+ yield endCustomizing();
+
+ yield PanelUI.show();
+
+ let bookmarksToolbarPlaceholder = document.getElementById(buttonId);
+ ok(bookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"),
+ "Button should have toolbarbutton-1 class");
+ is(bookmarksToolbarPlaceholder.getAttribute("wrap"), "true",
+ "Button should have the 'wrap' attribute");
+
+ info("Waiting for panel to close");
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+
+ info("Waiting for window to open");
+ let newWin = yield openAndLoadWindow({}, true);
+
+ info("Waiting for panel in new window to open");
+ let hideTrace = function() {
+ info(new Error().stack);
+ info("Panel was hidden.");
+ };
+ newWin.PanelUI.panel.addEventListener("popuphidden", hideTrace);
+
+ yield newWin.PanelUI.show();
+ let newWinBookmarksToolbarPlaceholder = newWin.document.getElementById(buttonId);
+ ok(newWinBookmarksToolbarPlaceholder.classList.contains("toolbarbutton-1"),
+ "Button in new window should have toolbarbutton-1 class");
+ is(newWinBookmarksToolbarPlaceholder.getAttribute("wrap"), "true",
+ "Button in new window should have 'wrap' attribute");
+
+ newWin.PanelUI.panel.removeEventListener("popuphidden", hideTrace);
+ // XXXgijs on Linux, we're sometimes seeing the panel being hidden early
+ // in the newly created window, probably because something else steals focus.
+ if (newWin.PanelUI.panel.state != "closed") {
+ info("Panel is still open in new window, waiting for it to close");
+ panelHiddenPromise = promisePanelHidden(newWin);
+ newWin.PanelUI.hide();
+ yield panelHiddenPromise;
+ } else {
+ info("panel was already closed");
+ }
+
+ info("Waiting for new window to close");
+ yield promiseWindowClosed(newWin);
+});
+
+add_task(function* asyncCleanUp() {
+ yield endCustomizing();
+ CustomizableUI.reset();
+});
+
diff --git a/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.js
new file mode 100644
index 000000000..f7504fc41
--- /dev/null
+++ b/browser/components/customizableui/test/browser_968565_insert_before_hidden_items.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";
+
+const kHidden1Id = "test-hidden-button-1";
+const kHidden2Id = "test-hidden-button-2";
+
+var navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+// When we drag an item onto a customizable area, and not over a specific target, we
+// should assume that we're appending them to the area. If doing so, we should scan
+// backwards over any hidden items and insert the item before those hidden items.
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Should be in the default state");
+
+ // Iterate backwards over the items in the nav-bar until we find the first
+ // one that is not hidden.
+ let placements = CustomizableUI.getWidgetsInArea(CustomizableUI.AREA_NAVBAR);
+ let lastVisible = null;
+ for (let widgetGroup of placements.reverse()) {
+ let widget = widgetGroup.forWindow(window);
+ if (widget && widget.node && !widget.node.hidden) {
+ lastVisible = widget.node;
+ break;
+ }
+ }
+
+ if (!lastVisible) {
+ ok(false, "Apparently, there are no visible items in the nav-bar.");
+ }
+
+ info("The last visible item in the nav-bar has ID: " + lastVisible.id);
+
+ let hidden1 = createDummyXULButton(kHidden1Id, "You can't see me");
+ let hidden2 = createDummyXULButton(kHidden2Id, "You can't see me either.");
+ hidden1.hidden = hidden2.hidden = true;
+
+ // Make sure we have some hidden items at the end of the nav-bar.
+ navbar.insertItem(hidden1.id);
+ navbar.insertItem(hidden2.id);
+
+ // Drag an item and drop it onto the nav-bar customization target, but
+ // not over a particular item.
+ yield startCustomizing();
+ let downloadsButton = document.getElementById("downloads-button");
+ simulateItemDrag(downloadsButton, navbar.customizationTarget);
+
+ yield endCustomizing();
+
+ is(downloadsButton.previousSibling.id, lastVisible.id,
+ "The downloads button should be placed after the last visible item.");
+
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_969427_recreate_destroyed_widget_after_reset.js b/browser/components/customizableui/test/browser_969427_recreate_destroyed_widget_after_reset.js
new file mode 100644
index 000000000..b5479fcb7
--- /dev/null
+++ b/browser/components/customizableui/test/browser_969427_recreate_destroyed_widget_after_reset.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";
+
+function getPlacementArea(id) {
+ let placement = CustomizableUI.getPlacementOfWidget(id);
+ return placement && placement.area;
+}
+
+// Check that a destroyed widget recreated after a reset call goes to
+// the navigation bar.
+add_task(function() {
+ const kWidgetId = "test-recreate-after-reset";
+ let spec = {id: kWidgetId, label: "Test re-create after reset.",
+ removable: true, defaultArea: CustomizableUI.AREA_NAVBAR};
+
+ CustomizableUI.createWidget(spec);
+ is(getPlacementArea(kWidgetId), CustomizableUI.AREA_NAVBAR,
+ "widget is in the navigation bar");
+
+ CustomizableUI.destroyWidget(kWidgetId);
+ isnot(getPlacementArea(kWidgetId), CustomizableUI.AREA_NAVBAR,
+ "widget removed from the navigation bar");
+
+ CustomizableUI.reset();
+
+ CustomizableUI.createWidget(spec);
+ is(getPlacementArea(kWidgetId), CustomizableUI.AREA_NAVBAR,
+ "widget recreated and added back to the nav bar");
+
+ CustomizableUI.destroyWidget(kWidgetId);
+});
diff --git a/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js b/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.js
new file mode 100644
index 000000000..6f057a100
--- /dev/null
+++ b/browser/components/customizableui/test/browser_969661_character_encoding_navbar_disabled.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";
+
+
+// Adding the character encoding menu to the panel, exiting customize mode,
+// and moving it to the nav-bar should have it enabled, not disabled.
+add_task(function*() {
+ yield startCustomizing();
+ CustomizableUI.addWidgetToArea("characterencoding-button", "PanelUI-contents");
+ yield endCustomizing();
+ yield PanelUI.show();
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+ CustomizableUI.addWidgetToArea("characterencoding-button", 'nav-bar');
+ let button = document.getElementById("characterencoding-button");
+ ok(!button.hasAttribute("disabled"), "Button shouldn't be disabled");
+});
+
+add_task(function asyncCleanup() {
+ resetCustomization();
+});
+
diff --git a/browser/components/customizableui/test/browser_970511_undo_restore_default.js b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
new file mode 100644
index 000000000..e7b3ca674
--- /dev/null
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.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";
+
+requestLongerTimeout(2);
+
+// Restoring default should reset theme and show an "undo" option which undoes the restoring operation.
+add_task(function*() {
+ let homeButtonId = "home-button";
+ CustomizableUI.removeWidgetFromArea(homeButtonId);
+ yield startCustomizing();
+ ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
+ is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
+ let undoResetButton = document.getElementById("customization-undo-reset-button");
+ is(undoResetButton.hidden, true, "The undo button is hidden before reset");
+
+ let themesButton = document.getElementById("customization-lwtheme-button");
+ let popup = document.getElementById("customization-lwtheme-menu");
+ let popupShownPromise = popupShown(popup);
+ EventUtils.synthesizeMouseAtCenter(themesButton, {});
+ info("Clicked on themes button");
+ yield popupShownPromise;
+
+ let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
+ let firstLWTheme = recommendedHeader.nextSibling;
+ let firstLWThemeId = firstLWTheme.theme.id;
+ let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
+ firstLWTheme.doCommand();
+ info("Clicked on first theme");
+ yield themeChangedPromise;
+
+ is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme changed to first option");
+
+ yield gCustomizeMode.reset();
+
+ ok(CustomizableUI.inDefaultState, "In default state after reset");
+ is(undoResetButton.hidden, false, "The undo button is visible after reset");
+ is(LightweightThemeManager.currentTheme, null, "Theme reset to default");
+
+ yield gCustomizeMode.undoReset()
+
+ is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme has been reset from default to original choice");
+ ok(!CustomizableUI.inDefaultState, "Not in default state after undo-reset");
+ is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button");
+ is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
+
+ yield gCustomizeMode.reset();
+});
+
+// Performing an action after a reset will hide the reset button.
+add_task(function*() {
+ let homeButtonId = "home-button";
+ CustomizableUI.removeWidgetFromArea(homeButtonId);
+ ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
+ is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
+ let undoResetButton = document.getElementById("customization-undo-reset-button");
+ is(undoResetButton.hidden, true, "The undo button is hidden before reset");
+
+ yield gCustomizeMode.reset();
+
+ ok(CustomizableUI.inDefaultState, "In default state after reset");
+ is(undoResetButton.hidden, false, "The undo button is visible after reset");
+
+ CustomizableUI.addWidgetToArea(homeButtonId, CustomizableUI.AREA_PANEL);
+ is(undoResetButton.hidden, true, "The undo button is hidden after another change");
+});
+
+// "Restore defaults", exiting customize, and re-entering shouldn't show the Undo button
+add_task(function*() {
+ let undoResetButton = document.getElementById("customization-undo-reset-button");
+ is(undoResetButton.hidden, true, "The undo button is hidden before a reset");
+ ok(!CustomizableUI.inDefaultState, "The browser should not be in default state");
+ yield gCustomizeMode.reset();
+
+ is(undoResetButton.hidden, false, "The undo button is visible after a reset");
+ yield endCustomizing();
+ yield startCustomizing();
+ is(undoResetButton.hidden, true, "The undo reset button should be hidden after entering customization mode");
+});
+
+// Bug 971626 - Restore Defaults should collapse the Title Bar
+add_task(function*() {
+ if (Services.appinfo.OS != "WINNT" &&
+ Services.appinfo.OS != "Darwin") {
+ return;
+ }
+ let prefName = "browser.tabs.drawInTitlebar";
+ let defaultValue = Services.prefs.getBoolPref(prefName);
+ let restoreDefaultsButton = document.getElementById("customization-reset-button");
+ let titleBarButton = document.getElementById("customization-titlebar-visibility-button");
+ let undoResetButton = document.getElementById("customization-undo-reset-button");
+ ok(CustomizableUI.inDefaultState, "Should be in default state at start of test");
+ ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled when in default state");
+ is(titleBarButton.hasAttribute("checked"), !defaultValue, "Title bar button should reflect pref value");
+ is(undoResetButton.hidden, true, "Undo reset button should be hidden at start of test");
+
+ Services.prefs.setBoolPref(prefName, !defaultValue);
+ ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled when pref changed");
+ is(titleBarButton.hasAttribute("checked"), defaultValue, "Title bar button should reflect changed pref value");
+ ok(!CustomizableUI.inDefaultState, "With titlebar flipped, no longer default");
+ is(undoResetButton.hidden, true, "Undo reset button should be hidden after pref change");
+
+ yield gCustomizeMode.reset();
+ ok(restoreDefaultsButton.disabled, "Restore defaults button should be disabled after reset");
+ is(titleBarButton.hasAttribute("checked"), !defaultValue, "Title bar button should reflect default value after reset");
+ is(Services.prefs.getBoolPref(prefName), defaultValue, "Reset should reset drawInTitlebar");
+ ok(CustomizableUI.inDefaultState, "In default state after titlebar reset");
+ is(undoResetButton.hidden, false, "Undo reset button should be visible after reset");
+ ok(!undoResetButton.disabled, "Undo reset button should be enabled after reset");
+
+ yield gCustomizeMode.undoReset();
+ ok(!restoreDefaultsButton.disabled, "Restore defaults button should be enabled after undo-reset");
+ is(titleBarButton.hasAttribute("checked"), defaultValue, "Title bar button should reflect undo-reset value");
+ ok(!CustomizableUI.inDefaultState, "No longer in default state after undo");
+ is(Services.prefs.getBoolPref(prefName), !defaultValue, "Undo-reset goes back to previous pref value");
+ is(undoResetButton.hidden, true, "Undo reset button should be hidden after undo-reset clicked");
+
+ Services.prefs.clearUserPref(prefName);
+ ok(CustomizableUI.inDefaultState, "In default state after pref cleared");
+ is(undoResetButton.hidden, true, "Undo reset button should be hidden at end of test");
+});
+
+add_task(function* asyncCleanup() {
+ yield gCustomizeMode.reset();
+ yield endCustomizing();
+});
diff --git a/browser/components/customizableui/test/browser_972267_customizationchange_events.js b/browser/components/customizableui/test/browser_972267_customizationchange_events.js
new file mode 100644
index 000000000..b37dbe954
--- /dev/null
+++ b/browser/components/customizableui/test/browser_972267_customizationchange_events.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";
+
+// Create a new window, then move the home button to the menu and check both windows have
+// customizationchange events fire on the toolbox:
+add_task(function*() {
+ let newWindow = yield openAndLoadWindow();
+ let otherToolbox = newWindow.gNavToolbox;
+
+ let handlerCalledCount = 0;
+ let handler = (ev) => {
+ handlerCalledCount++;
+ };
+
+ let homeButton = document.getElementById("home-button");
+
+ gNavToolbox.addEventListener("customizationchange", handler);
+ otherToolbox.addEventListener("customizationchange", handler);
+
+ gCustomizeMode.addToPanel(homeButton);
+
+ is(handlerCalledCount, 2, "Should be called for both windows.");
+
+ // If the test is run in isolation and the panel has never been open,
+ // the button will be in the palette. Deal with this case:
+ if (homeButton.parentNode.id == "BrowserToolbarPalette") {
+ yield PanelUI.ensureReady();
+ isnot(homeButton.parentNode.id, "BrowserToolbarPalette", "Home button should now be in panel");
+ }
+
+ handlerCalledCount = 0;
+ gCustomizeMode.addToToolbar(homeButton);
+ is(handlerCalledCount, 2, "Should be called for both windows.");
+
+ gNavToolbox.removeEventListener("customizationchange", handler);
+ otherToolbox.removeEventListener("customizationchange", handler);
+
+ yield promiseWindowClosed(newWindow);
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_973641_button_addon.js b/browser/components/customizableui/test/browser_973641_button_addon.js
new file mode 100755
index 000000000..796bf3d0e
--- /dev/null
+++ b/browser/components/customizableui/test/browser_973641_button_addon.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 kButton = "test_button_for_addon";
+var initialLocation = gBrowser.currentURI.spec;
+
+add_task(function*() {
+ info("Check addon button functionality");
+
+ // create mocked addon button on the navigation bar
+ let widgetSpec = {
+ id: kButton,
+ type: 'button',
+ onClick: function() {
+ gBrowser.selectedTab = gBrowser.addTab("about:addons");
+ }
+ };
+ CustomizableUI.createWidget(widgetSpec);
+ CustomizableUI.addWidgetToArea(kButton, CustomizableUI.AREA_NAVBAR);
+
+ // check the button's functionality in navigation bar
+ let addonButton = document.getElementById(kButton);
+ let navBar = document.getElementById("nav-bar");
+ ok(addonButton, "Addon button exists");
+ ok(navBar.contains(addonButton), "Addon button is in the navbar");
+ yield checkButtonFunctionality(addonButton);
+
+ resetTabs();
+
+ // move the add-on button in the Panel Menu
+ CustomizableUI.addWidgetToArea(kButton, CustomizableUI.AREA_PANEL);
+ ok(!navBar.contains(addonButton), "Addon button was removed from the browser bar");
+
+ // check the addon button's functionality in the Panel Menu
+ yield PanelUI.show();
+ var panelMenu = document.getElementById("PanelUI-mainView");
+ let addonButtonInPanel = panelMenu.getElementsByAttribute("id", kButton);
+ ok(panelMenu.contains(addonButton), "Addon button was added to the Panel Menu");
+ yield checkButtonFunctionality(addonButtonInPanel[0]);
+});
+
+add_task(function* asyncCleanup() {
+ resetTabs();
+
+ // reset the UI to the default state
+ yield resetCustomization();
+ ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
+
+ // destroy the widget
+ CustomizableUI.destroyWidget(kButton);
+});
+
+function resetTabs() {
+ // close all opened tabs
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeTab(gBrowser.selectedTab);
+ }
+
+ // restore the initial tab
+ gBrowser.addTab(initialLocation);
+ gBrowser.removeTab(gBrowser.selectedTab);
+}
+
+function* checkButtonFunctionality(aButton) {
+ aButton.click();
+ yield waitForCondition(() => gBrowser.currentURI &&
+ gBrowser.currentURI.spec == "about:addons");
+}
diff --git a/browser/components/customizableui/test/browser_973932_addonbar_currentset.js b/browser/components/customizableui/test/browser_973932_addonbar_currentset.js
new file mode 100644
index 000000000..66fa6ef47
--- /dev/null
+++ b/browser/components/customizableui/test/browser_973932_addonbar_currentset.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var addonbarID = CustomizableUI.AREA_ADDONBAR;
+var addonbar = document.getElementById(addonbarID);
+
+// Check that currentset is correctly updated after a reset:
+add_task(function*() {
+ let placements = CustomizableUI.getWidgetIdsInArea(addonbarID);
+ is(placements.join(','), addonbar.getAttribute("currentset"), "Addon-bar currentset should match default placements");
+ ok(CustomizableUI.inDefaultState, "Should be in default state");
+ info("Adding a spring to add-on bar shim");
+ CustomizableUI.addWidgetToArea("spring", addonbarID, 1);
+ ok(addonbar.getElementsByTagName("toolbarspring").length, "There should be a spring in the toolbar");
+ ok(!CustomizableUI.inDefaultState, "Should no longer be in default state");
+ placements = CustomizableUI.getWidgetIdsInArea(addonbarID);
+ is(placements.join(','), addonbar.getAttribute("currentset"), "Addon-bar currentset should match placements after spring addition");
+
+ yield startCustomizing();
+ yield gCustomizeMode.reset();
+ ok(CustomizableUI.inDefaultState, "Should be in default state after reset");
+ placements = CustomizableUI.getWidgetIdsInArea(addonbarID);
+ is(placements.join(','), addonbar.getAttribute("currentset"), "Addon-bar currentset should match default placements after reset");
+ ok(!addonbar.getElementsByTagName("toolbarspring").length, "There should be no spring in the toolbar");
+ yield endCustomizing();
+});
+
diff --git a/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js b/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js
new file mode 100644
index 000000000..73fc7c1ff
--- /dev/null
+++ b/browser/components/customizableui/test/browser_975719_customtoolbars_behaviour.js
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+const kXULWidgetId = "a-test-button"; // we'll create a button with this ID.
+
+add_task(function setup() {
+ // create a XUL button and add it to the palette.
+ createDummyXULButton(kXULWidgetId, "test-button");
+});
+
+add_task(function* customizeToolbarAndKeepIt() {
+ ok(gNavToolbox.toolbarset, "There should be a toolbarset");
+ let toolbarID = "testAustralisCustomToolbar";
+ gNavToolbox.appendCustomToolbar(toolbarID, "");
+ let toolbarDOMID = getToolboxCustomToolbarId(toolbarID);
+ let toolbarElement = document.getElementById(toolbarDOMID);
+ ok(toolbarElement, "There should be a toolbar");
+ if (!toolbarElement) {
+ ok(false, "No toolbar created, bailing out of the test.");
+ return;
+ }
+ is(toolbarElement.nextSibling, gNavToolbox.toolbarset,
+ "Toolbar should have been inserted in toolbox, before toolbarset element");
+ let cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
+ is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR,
+ "CustomizableUI should know the area and think it's a toolbar");
+ if (cuiAreaType != CustomizableUI.TYPE_TOOLBAR) {
+ ok(false, "Toolbar not registered successfully, bailing out of the test.");
+ toolbarElement.remove();
+ return;
+ }
+ ok(!CustomizableUI.getWidgetIdsInArea(toolbarDOMID).length, "There should be no widgets in the area yet.");
+ CustomizableUI.addWidgetToArea("open-file-button", toolbarDOMID, 0);
+ ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button.");
+ assertAreaPlacements(toolbarDOMID, ["open-file-button"]);
+
+ gNavToolbox.toolbarset.setAttribute("toolbar1", toolbarID + ":open-file-button");
+ document.persist(gNavToolbox.toolbarset.id, "toolbar1");
+
+ yield startCustomizing();
+ // First, exit customize mode without doing anything, and verify the toolbar doesn't get removed.
+ yield endCustomizing();
+ ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state, the toolbar should still be there.");
+ cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
+ is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR,
+ "CustomizableUI should still know the area and think it's a toolbar");
+ ok(toolbarElement.parentNode, "Toolbar should still be in the DOM.");
+ ok(toolbarElement.hasChildNodes(), "Toolbar should still have items in it.");
+ assertAreaPlacements(toolbarDOMID, ["open-file-button"]);
+
+ let newWindow = yield openAndLoadWindow({}, true);
+ is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar1"),
+ gNavToolbox.toolbarset.getAttribute("toolbar1"),
+ "Attribute should be the same in new window");
+ yield promiseWindowClosed(newWindow);
+
+ // Then customize again, and this time empty out the toolbar and verify it *does* get removed.
+ yield startCustomizing();
+ let openFileButton = document.getElementById("open-file-button");
+ let palette = document.getElementById("customization-palette");
+ simulateItemDrag(openFileButton, palette);
+ ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state because there's still a non-collapsed toolbar.");
+ ok(!toolbarElement.hasChildNodes(), "Toolbar should have no more child nodes.");
+
+ toolbarElement.collapsed = true;
+ ok(CustomizableUI.inDefaultState, "Should be in default state because there's now just a collapsed toolbar.");
+ toolbarElement.collapsed = false;
+ ok(!CustomizableUI.inDefaultState, "Shouldn't be in default state because there's a non-collapsed toolbar again.");
+ yield endCustomizing();
+ ok(CustomizableUI.inDefaultState, "Should be in default state because the toolbar should have been removed.");
+
+ newWindow = yield openAndLoadWindow({}, true);
+ ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar1"),
+ "Attribute should be gone in new window");
+ yield promiseWindowClosed(newWindow);
+
+ ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM.");
+ cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
+ is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area");
+});
+
+add_task(function* resetShouldDealWithCustomToolbars() {
+ ok(gNavToolbox.toolbarset, "There should be a toolbarset");
+ let toolbarID = "testAustralisCustomToolbar";
+ gNavToolbox.appendCustomToolbar(toolbarID, "");
+ let toolbarDOMID = getToolboxCustomToolbarId(toolbarID);
+ let toolbarElement = document.getElementById(toolbarDOMID);
+ ok(toolbarElement, "There should be a toolbar");
+ if (!toolbarElement) {
+ ok(false, "No toolbar created, bailing out of the test.");
+ return;
+ }
+ is(toolbarElement.nextSibling, gNavToolbox.toolbarset,
+ "Toolbar should have been inserted in toolbox, before toolbarset element");
+ let cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
+ is(cuiAreaType, CustomizableUI.TYPE_TOOLBAR,
+ "CustomizableUI should know the area and think it's a toolbar");
+ if (cuiAreaType != CustomizableUI.TYPE_TOOLBAR) {
+ ok(false, "Toolbar not registered successfully, bailing out of the test.");
+ toolbarElement.remove();
+ return;
+ }
+ ok(!CustomizableUI.getWidgetIdsInArea(toolbarDOMID).length, "There should be no widgets in the area yet.");
+ CustomizableUI.addWidgetToArea(kXULWidgetId, toolbarDOMID, 0);
+ ok(toolbarElement.hasChildNodes(), "Toolbar should now have a button.");
+ assertAreaPlacements(toolbarDOMID, [kXULWidgetId]);
+
+ gNavToolbox.toolbarset.setAttribute("toolbar2", `${toolbarID}:${kXULWidgetId}`);
+ document.persist(gNavToolbox.toolbarset.id, "toolbar2");
+
+ let newWindow = yield openAndLoadWindow({}, true);
+ is(newWindow.gNavToolbox.toolbarset.getAttribute("toolbar2"),
+ gNavToolbox.toolbarset.getAttribute("toolbar2"),
+ "Attribute should be the same in new window");
+ yield promiseWindowClosed(newWindow);
+
+ CustomizableUI.reset();
+
+ newWindow = yield openAndLoadWindow({}, true);
+ ok(!newWindow.gNavToolbox.toolbarset.hasAttribute("toolbar2"),
+ "Attribute should be gone in new window");
+ yield promiseWindowClosed(newWindow);
+
+ ok(CustomizableUI.inDefaultState, "Should be in default state after reset.");
+ let xulButton = document.getElementById(kXULWidgetId);
+ ok(!xulButton, "XUL button shouldn't be in the document anymore.");
+ ok(gNavToolbox.palette.querySelector(`#${kXULWidgetId}`), "XUL button should be in the palette");
+ ok(!toolbarElement.hasChildNodes(), "Toolbar should have no more child nodes.");
+ ok(!toolbarElement.parentNode, "Toolbar should no longer be in the DOM.");
+ cuiAreaType = CustomizableUI.getAreaType(toolbarDOMID);
+ is(cuiAreaType, null, "CustomizableUI should have forgotten all about the area");
+});
+
+
+add_task(function*() {
+ let newWin = yield openAndLoadWindow({}, true);
+ ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar1"), "New window shouldn't have attribute toolbar1");
+ ok(!newWin.gNavToolbox.toolbarset.hasAttribute("toolbar2"), "New window shouldn't have attribute toolbar2");
+ yield promiseWindowClosed(newWin);
+});
diff --git a/browser/components/customizableui/test/browser_976792_insertNodeInWindow.js b/browser/components/customizableui/test/browser_976792_insertNodeInWindow.js
new file mode 100644
index 000000000..3bfa8c25d
--- /dev/null
+++ b/browser/components/customizableui/test/browser_976792_insertNodeInWindow.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";
+
+const kToolbarName = "test-insertNodeInWindow-placements-toolbar";
+const kTestWidgetPrefix = "test-widget-for-insertNodeInWindow-placements-";
+
+
+/*
+Tries to replicate the situation of having a placement list like this:
+
+exists-1,trying-to-insert-this,doesn't-exist,exists-2
+*/
+add_task(function*() {
+ let testWidgetExists = [true, false, false, true];
+ let widgetIds = [];
+ for (let i = 0; i < testWidgetExists.length; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ if (testWidgetExists[i]) {
+ let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ }
+ }
+
+ let toolbarNode = createToolbarWithPlacements(kToolbarName, widgetIds);
+ assertAreaPlacements(kToolbarName, widgetIds);
+
+ let btnId = kTestWidgetPrefix + 1;
+ let btn = createDummyXULButton(btnId, "test");
+ CustomizableUI.ensureWidgetPlacedInWindow(btnId, window);
+
+ is(btn.parentNode.id, kToolbarName, "New XUL widget should be placed inside new toolbar");
+
+ is(btn.previousSibling.id, toolbarNode.firstChild.id,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ btn.remove();
+ removeCustomToolbars();
+ yield resetCustomization();
+});
+
+
+/*
+Tests nodes get placed inside the toolbar's overflow as expected. Replicates a
+situation similar to:
+
+exists-1,exists-2,overflow-1,trying-to-insert-this,overflow-2
+*/
+add_task(function*() {
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+ let widgetIds = [];
+ for (let i = 0; i < 5; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ CustomizableUI.addWidgetToArea(id, "nav-bar");
+ }
+
+ for (let id of widgetIds) {
+ document.getElementById(id).style.minWidth = "200px";
+ }
+
+ let originalWindowWidth = window.outerWidth;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+
+ let testWidgetId = kTestWidgetPrefix + 3;
+
+ CustomizableUI.destroyWidget(testWidgetId);
+
+ let btn = createDummyXULButton(testWidgetId, "test");
+ CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window);
+
+ is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar");
+ is(btn.previousSibling.id, kTestWidgetPrefix + 2,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+ is(btn.nextSibling.id, kTestWidgetPrefix + 4,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName);
+ btn.remove();
+ yield resetCustomization();
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+});
+
+
+/*
+Tests nodes get placed inside the toolbar's overflow as expected. Replicates a
+placements situation similar to:
+
+exists-1,exists-2,overflow-1,doesn't-exist,trying-to-insert-this,overflow-2
+*/
+add_task(function*() {
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+ let widgetIds = [];
+ for (let i = 0; i < 5; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ CustomizableUI.addWidgetToArea(id, "nav-bar");
+ }
+
+ for (let id of widgetIds) {
+ document.getElementById(id).style.minWidth = "200px";
+ }
+
+ let originalWindowWidth = window.outerWidth;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+
+ let testWidgetId = kTestWidgetPrefix + 3;
+
+ CustomizableUI.destroyWidget(kTestWidgetPrefix + 2);
+ CustomizableUI.destroyWidget(testWidgetId);
+
+ let btn = createDummyXULButton(testWidgetId, "test");
+ CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window);
+
+ is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar");
+ is(btn.previousSibling.id, kTestWidgetPrefix + 1,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+ is(btn.nextSibling.id, kTestWidgetPrefix + 4,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName);
+ btn.remove();
+ yield resetCustomization();
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+});
+
+
+/*
+Tests nodes get placed inside the toolbar's overflow as expected. Replicates a
+placements situation similar to:
+
+exists-1,exists-2,overflow-1,doesn't-exist,trying-to-insert-this,doesn't-exist
+*/
+add_task(function*() {
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+ let widgetIds = [];
+ for (let i = 0; i < 5; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ CustomizableUI.addWidgetToArea(id, "nav-bar");
+ }
+
+ for (let id of widgetIds) {
+ document.getElementById(id).style.minWidth = "200px";
+ }
+
+ let originalWindowWidth = window.outerWidth;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+
+ let testWidgetId = kTestWidgetPrefix + 3;
+
+ CustomizableUI.destroyWidget(kTestWidgetPrefix + 2);
+ CustomizableUI.destroyWidget(testWidgetId);
+ CustomizableUI.destroyWidget(kTestWidgetPrefix + 4);
+
+ let btn = createDummyXULButton(testWidgetId, "test");
+ CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window);
+
+ is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar");
+ is(btn.previousSibling.id, kTestWidgetPrefix + 1,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+ is(btn.nextSibling, null,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName);
+ btn.remove();
+ yield resetCustomization();
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+});
+
+
+/*
+Tests nodes get placed inside the toolbar's overflow as expected. Replicates a
+placements situation similar to:
+
+exists-1,exists-2,overflow-1,can't-overflow,trying-to-insert-this,overflow-2
+*/
+add_task(function*() {
+ let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+ let widgetIds = [];
+ for (let i = 5; i >= 0; i--) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ CustomizableUI.addWidgetToArea(id, "nav-bar", 0);
+ }
+
+ for (let i = 10; i < 15; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ let spec = {id: id, type: "button", removable: true, label: "insertNodeInWindow test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ CustomizableUI.addWidgetToArea(id, "nav-bar");
+ }
+
+ for (let id of widgetIds) {
+ document.getElementById(id).style.minWidth = "200px";
+ }
+
+ let originalWindowWidth = window.outerWidth;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => navbar.hasAttribute("overflowing"));
+
+ // Find last widget that doesn't allow overflowing
+ let nonOverflowing = navbar.customizationTarget.lastChild;
+ is(nonOverflowing.getAttribute("overflows"), "false", "Last child is expected to not allow overflowing");
+ isnot(nonOverflowing.getAttribute("skipintoolbarset"), "true", "Last child is expected to not be skipintoolbarset");
+
+ let testWidgetId = kTestWidgetPrefix + 10;
+ CustomizableUI.destroyWidget(testWidgetId);
+
+ let btn = createDummyXULButton(testWidgetId, "test");
+ CustomizableUI.ensureWidgetPlacedInWindow(testWidgetId, window);
+
+ is(btn.parentNode.id, navbar.overflowable._list.id, "New XUL widget should be placed inside overflow of toolbar");
+ is(btn.nextSibling.id, kTestWidgetPrefix + 11,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ CustomizableUI.removeWidgetFromArea(btn.id, kToolbarName);
+ btn.remove();
+ yield resetCustomization();
+ yield waitForCondition(() => !navbar.hasAttribute("overflowing"));
+});
+
+
+/*
+Tests nodes get placed inside the toolbar's overflow as expected. Replicates a
+placements situation similar to:
+
+exists-1,exists-2,overflow-1,trying-to-insert-this,can't-overflow,overflow-2
+*/
+add_task(function*() {
+ let widgetIds = [];
+ let missingId = 2;
+ let nonOverflowableId = 3;
+ for (let i = 0; i < 5; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ if (i != missingId) {
+ // Setting min-width to make the overflow state not depend on styling of the button and/or
+ // screen width
+ let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i,
+ onCreated: function(node) {
+ node.style.minWidth = "200px";
+ if (id == (kTestWidgetPrefix + nonOverflowableId)) {
+ node.setAttribute("overflows", false);
+ }
+ }};
+ info("Creating: " + id);
+ CustomizableUI.createWidget(spec);
+ }
+ }
+
+ let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds);
+ assertAreaPlacements(kToolbarName, widgetIds);
+ ok(!toolbarNode.hasAttribute("overflowing"), "Toolbar shouldn't overflow to start with.");
+
+ let originalWindowWidth = window.outerWidth;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => toolbarNode.hasAttribute("overflowing"));
+ ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+ let btnId = kTestWidgetPrefix + missingId;
+ let btn = createDummyXULButton(btnId, "test");
+ CustomizableUI.ensureWidgetPlacedInWindow(btnId, window);
+
+ is(btn.parentNode.id, kToolbarName + "-overflow-list", "New XUL widget should be placed inside new toolbar's overflow");
+ is(btn.previousSibling.id, kTestWidgetPrefix + 1,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+ is(btn.nextSibling.id, kTestWidgetPrefix + 4,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !toolbarNode.hasAttribute("overflowing"));
+
+ btn.remove();
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ removeCustomToolbars();
+ yield resetCustomization();
+});
+
+
+/*
+Tests nodes do *not* get placed in the toolbar's overflow. Replicates a
+plcements situation similar to:
+
+exists-1,trying-to-insert-this,exists-2,overflowed-1
+*/
+add_task(function*() {
+ let widgetIds = [];
+ let missingId = 1;
+ for (let i = 0; i < 5; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ if (i != missingId) {
+ // Setting min-width to make the overflow state not depend on styling of the button and/or
+ // screen width
+ let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i,
+ onCreated: function(node) { node.style.minWidth = "100px"; }};
+ info("Creating: " + id);
+ CustomizableUI.createWidget(spec);
+ }
+ }
+
+ let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds);
+ assertAreaPlacements(kToolbarName, widgetIds);
+ ok(!toolbarNode.hasAttribute("overflowing"), "Toolbar shouldn't overflow to start with.");
+
+ let originalWindowWidth = window.outerWidth;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => toolbarNode.hasAttribute("overflowing"));
+ ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+ let btnId = kTestWidgetPrefix + missingId;
+ let btn = createDummyXULButton(btnId, "test");
+ CustomizableUI.ensureWidgetPlacedInWindow(btnId, window);
+
+ is(btn.parentNode.id, kToolbarName + "-target", "New XUL widget should be placed inside new toolbar");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !toolbarNode.hasAttribute("overflowing"));
+
+ btn.remove();
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ removeCustomToolbars();
+ yield resetCustomization();
+});
+
+
+/*
+Tests inserting a node onto the end of an overflowing toolbar *doesn't* put it in
+the overflow list when the widget disallows overflowing. ie:
+
+exists-1,exists-2,overflows-1,trying-to-insert-this
+
+Where trying-to-insert-this has overflows=false
+*/
+add_task(function*() {
+ let widgetIds = [];
+ let missingId = 3;
+ for (let i = 0; i < 5; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ if (i != missingId) {
+ // Setting min-width to make the overflow state not depend on styling of the button and/or
+ // screen width
+ let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i,
+ onCreated: function(node) { node.style.minWidth = "200px"; }};
+ info("Creating: " + id);
+ CustomizableUI.createWidget(spec);
+ }
+ }
+
+ let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds);
+ assertAreaPlacements(kToolbarName, widgetIds);
+ ok(!toolbarNode.hasAttribute("overflowing"), "Toolbar shouldn't overflow to start with.");
+
+ let originalWindowWidth = window.outerWidth;
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => toolbarNode.hasAttribute("overflowing"));
+ ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+
+ let btnId = kTestWidgetPrefix + missingId;
+ let btn = createDummyXULButton(btnId, "test");
+ btn.setAttribute("overflows", false);
+ CustomizableUI.ensureWidgetPlacedInWindow(btnId, window);
+
+ is(btn.parentNode.id, kToolbarName + "-target", "New XUL widget should be placed inside new toolbar");
+ is(btn.nextSibling, null,
+ "insertNodeInWindow should have placed new XUL widget in correct place in DOM according to placements");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+ yield waitForCondition(() => !toolbarNode.hasAttribute("overflowing"));
+
+ btn.remove();
+ widgetIds.forEach(id => CustomizableUI.destroyWidget(id));
+ removeCustomToolbars();
+ yield resetCustomization();
+});
+
+
+add_task(function* asyncCleanUp() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js b/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js
new file mode 100644
index 000000000..a653c2d51
--- /dev/null
+++ b/browser/components/customizableui/test/browser_978084_dragEnd_after_move.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var draggedItem;
+
+/**
+ * Check that customizing-movingItem gets removed on a drop when the item is moved.
+ */
+
+// Drop on the palette
+add_task(function*() {
+ draggedItem = document.createElement("toolbarbutton");
+ draggedItem.id = "test-dragEnd-after-move1";
+ draggedItem.setAttribute("label", "Test");
+ draggedItem.setAttribute("removable", "true");
+ let navbar = document.getElementById("nav-bar");
+ navbar.customizationTarget.appendChild(draggedItem);
+ yield startCustomizing();
+ simulateItemDrag(draggedItem, gCustomizeMode.visiblePalette);
+ is(document.documentElement.hasAttribute("customizing-movingItem"), false,
+ "Make sure customizing-movingItem is removed after dragging to the palette");
+ yield endCustomizing();
+});
+
+// Drop on a customization target itself
+add_task(function*() {
+ draggedItem = document.createElement("toolbarbutton");
+ draggedItem.id = "test-dragEnd-after-move2";
+ draggedItem.setAttribute("label", "Test");
+ draggedItem.setAttribute("removable", "true");
+ let dest = createToolbarWithPlacements("test-dragEnd");
+ let navbar = document.getElementById("nav-bar");
+ navbar.customizationTarget.appendChild(draggedItem);
+ yield startCustomizing();
+ simulateItemDrag(draggedItem, dest.customizationTarget);
+ is(document.documentElement.hasAttribute("customizing-movingItem"), false,
+ "Make sure customizing-movingItem is removed");
+ yield endCustomizing();
+});
+
+add_task(function* asyncCleanup() {
+ yield endCustomizing();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_980155_add_overflow_toolbar.js b/browser/components/customizableui/test/browser_980155_add_overflow_toolbar.js
new file mode 100644
index 000000000..15197ac86
--- /dev/null
+++ b/browser/components/customizableui/test/browser_980155_add_overflow_toolbar.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 kToolbarName = "test-new-overflowable-toolbar";
+const kTestWidgetPrefix = "test-widget-for-overflowable-toolbar-";
+
+add_task(function* addOverflowingToolbar() {
+ let originalWindowWidth = window.outerWidth;
+
+ let widgetIds = [];
+ for (let i = 0; i < 10; i++) {
+ let id = kTestWidgetPrefix + i;
+ widgetIds.push(id);
+ let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i};
+ CustomizableUI.createWidget(spec);
+ }
+
+ let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds);
+ assertAreaPlacements(kToolbarName, widgetIds);
+
+ for (let id of widgetIds) {
+ document.getElementById(id).style.minWidth = "200px";
+ }
+
+ isnot(toolbarNode.overflowable, null, "Toolbar should have overflowable controller");
+ isnot(toolbarNode.customizationTarget, null, "Toolbar should have customization target");
+ isnot(toolbarNode.customizationTarget, toolbarNode, "Customization target should not be toolbar node");
+
+ let oldChildCount = toolbarNode.customizationTarget.childElementCount;
+ let overflowableList = document.getElementById(kToolbarName + "-overflow-list");
+ let oldOverflowCount = overflowableList.childElementCount;
+
+ isnot(oldChildCount, 0, "Toolbar should have non-overflowing widgets");
+
+ window.resizeTo(400, window.outerHeight);
+ yield waitForCondition(() => toolbarNode.hasAttribute("overflowing"));
+ ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+ ok(toolbarNode.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
+ ok(overflowableList.childElementCount > oldOverflowCount, "Should have more overflowed widgets.");
+
+ window.resizeTo(originalWindowWidth, window.outerHeight);
+});
+
+
+add_task(function* asyncCleanup() {
+ removeCustomToolbars();
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_981305_separator_insertion.js b/browser/components/customizableui/test/browser_981305_separator_insertion.js
new file mode 100644
index 000000000..8d4d86c2a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_981305_separator_insertion.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";
+
+var tempElements = [];
+
+function insertTempItemsIntoMenu(parentMenu) {
+ // Last element is null to insert at the end:
+ let beforeEls = [parentMenu.firstChild, parentMenu.lastChild, null];
+ for (let i = 0; i < beforeEls.length; i++) {
+ let sep = document.createElement("menuseparator");
+ tempElements.push(sep);
+ parentMenu.insertBefore(sep, beforeEls[i]);
+ let menu = document.createElement("menu");
+ tempElements.push(menu);
+ parentMenu.insertBefore(menu, beforeEls[i]);
+ // And another separator for good measure:
+ sep = document.createElement("menuseparator");
+ tempElements.push(sep);
+ parentMenu.insertBefore(sep, beforeEls[i]);
+ }
+}
+
+function checkSeparatorInsertion(menuId, buttonId, subviewId) {
+ return function*() {
+ info("Checking for duplicate separators in " + buttonId + " widget");
+ let menu = document.getElementById(menuId);
+ insertTempItemsIntoMenu(menu);
+
+ let placement = CustomizableUI.getPlacementOfWidget(buttonId);
+ let changedPlacement = false;
+ if (!placement || placement.area != CustomizableUI.AREA_PANEL) {
+ CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_PANEL);
+ changedPlacement = true;
+ }
+ yield PanelUI.show();
+
+ let button = document.getElementById(buttonId);
+ button.click();
+
+ yield waitForCondition(() => !PanelUI.multiView.hasAttribute("transitioning"));
+ let subview = document.getElementById(subviewId);
+ ok(subview.firstChild, "Subview should have a kid");
+ is(subview.firstChild.localName, "toolbarbutton", "There should be no separators to start with");
+
+ for (let kid of subview.children) {
+ if (kid.localName == "menuseparator") {
+ ok(kid.previousSibling && kid.previousSibling.localName != "menuseparator",
+ "Separators should never have another separator next to them, and should never be the first node.");
+ }
+ }
+
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+
+ if (changedPlacement) {
+ CustomizableUI.reset();
+ }
+ };
+}
+
+add_task(checkSeparatorInsertion("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems"));
+add_task(checkSeparatorInsertion("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems"));
+
+registerCleanupFunction(function() {
+ for (let el of tempElements) {
+ el.remove();
+ }
+ tempElements = null;
+});
diff --git a/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.js
new file mode 100644
index 000000000..9a7227a47
--- /dev/null
+++ b/browser/components/customizableui/test/browser_981418-widget-onbeforecreated-handler.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";
+const kWidgetId = 'test-981418-widget-onbeforecreated';
+
+// Should be able to add broken view widget
+add_task(function* testAddOnBeforeCreatedWidget() {
+ let viewShownDeferred = Promise.defer();
+ let onBeforeCreatedCalled = false;
+ let widgetSpec = {
+ id: kWidgetId,
+ type: 'view',
+ viewId: kWidgetId + 'idontexistyet',
+ onBeforeCreated: function(doc) {
+ let view = doc.createElement("panelview");
+ view.id = kWidgetId + 'idontexistyet';
+ let label = doc.createElement("label");
+ label.setAttribute("value", "Hello world");
+ label.className = 'panel-subview-header';
+ view.appendChild(label);
+ document.getElementById("PanelUI-multiView").appendChild(view);
+ onBeforeCreatedCalled = true;
+ },
+ onViewShowing: function() {
+ viewShownDeferred.resolve();
+ }
+ };
+
+ let noError = true;
+ try {
+ CustomizableUI.createWidget(widgetSpec);
+ CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
+ } catch (ex) {
+ Cu.reportError(ex);
+ noError = false;
+ }
+ ok(noError, "Should not throw an exception trying to add the widget.");
+ ok(onBeforeCreatedCalled, "onBeforeCreated should have been called");
+
+ let widgetNode = document.getElementById(kWidgetId);
+ ok(widgetNode, "Widget should exist");
+ if (widgetNode) {
+ try {
+ widgetNode.click();
+
+ let tempPanel = document.getElementById("customizationui-widget-panel");
+ let panelShownPromise = promisePanelElementShown(window, tempPanel);
+
+ let shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000);
+ yield viewShownDeferred.promise;
+ yield panelShownPromise;
+ clearTimeout(shownTimeout);
+ ok(true, "Found view shown");
+
+ let panelHiddenPromise = promisePanelElementHidden(window, tempPanel);
+ tempPanel.hidePopup();
+ yield panelHiddenPromise;
+
+ CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_PANEL);
+ yield PanelUI.show();
+
+ viewShownDeferred = Promise.defer();
+ widgetNode.click();
+
+ shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000);
+ yield viewShownDeferred.promise;
+ clearTimeout(shownTimeout);
+ ok(true, "Found view shown");
+
+ let panelHidden = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHidden;
+ } catch (ex) {
+ ok(false, "Unexpected exception (like a timeout for one of the yields) " +
+ "when testing view widget.");
+ }
+ }
+
+ noError = true;
+ try {
+ CustomizableUI.destroyWidget(kWidgetId);
+ } catch (ex) {
+ Cu.reportError(ex);
+ noError = false;
+ }
+ ok(noError, "Should not throw an exception trying to remove the broken view widget.");
+});
+
+add_task(function* asyncCleanup() {
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_982656_restore_defaults_builtin_widgets.js b/browser/components/customizableui/test/browser_982656_restore_defaults_builtin_widgets.js
new file mode 100644
index 000000000..e7f8d0cf4
--- /dev/null
+++ b/browser/components/customizableui/test/browser_982656_restore_defaults_builtin_widgets.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";
+
+// Restoring default should not place addon widgets back in the toolbar
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Default state to begin");
+
+ const kWidgetId = "bug982656-add-on-widget-should-not-restore-to-default-area";
+ let widgetSpec = {
+ id: kWidgetId,
+ defaultArea: CustomizableUI.AREA_NAVBAR
+ };
+ CustomizableUI.createWidget(widgetSpec);
+
+ ok(!CustomizableUI.inDefaultState, "Not in default state after widget added");
+ is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, CustomizableUI.AREA_NAVBAR, "Widget should be in navbar");
+
+ yield resetCustomization();
+
+ ok(CustomizableUI.inDefaultState, "Back in default state after reset");
+ is(CustomizableUI.getPlacementOfWidget(kWidgetId), null, "Widget now in palette");
+ CustomizableUI.destroyWidget(kWidgetId);
+});
+
+
+// resetCustomization shouldn't move 3rd party widgets out of custom toolbars
+add_task(function*() {
+ const kToolbarId = "bug982656-toolbar-with-defaultset";
+ const kWidgetId = "bug982656-add-on-widget-should-restore-to-default-area-when-area-is-not-builtin";
+ ok(CustomizableUI.inDefaultState, "Everything should be in its default state.");
+ let toolbar = createToolbarWithPlacements(kToolbarId);
+ ok(CustomizableUI.areas.indexOf(kToolbarId) != -1,
+ "Toolbar has been registered.");
+ is(CustomizableUI.getAreaType(kToolbarId), CustomizableUI.TYPE_TOOLBAR,
+ "Area should be registered as toolbar");
+
+ let widgetSpec = {
+ id: kWidgetId,
+ defaultArea: kToolbarId
+ };
+ CustomizableUI.createWidget(widgetSpec);
+
+ ok(!CustomizableUI.inDefaultState, "No longer in default state after toolbar is registered and visible.");
+ is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, kToolbarId, "Widget should be in custom toolbar");
+
+ yield resetCustomization();
+ ok(CustomizableUI.inDefaultState, "Back in default state after reset");
+ is(CustomizableUI.getPlacementOfWidget(kWidgetId).area, kToolbarId, "Widget still in custom toolbar");
+ ok(toolbar.collapsed, "Custom toolbar should be collapsed after reset");
+
+ toolbar.remove();
+ CustomizableUI.destroyWidget(kWidgetId);
+ CustomizableUI.unregisterArea(kToolbarId);
+});
diff --git a/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js b/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js
new file mode 100644
index 000000000..42b346c10
--- /dev/null
+++ b/browser/components/customizableui/test/browser_984455_bookmarks_items_reparenting.js
@@ -0,0 +1,267 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 gNavBar = document.getElementById(CustomizableUI.AREA_NAVBAR);
+var gOverflowList = document.getElementById(gNavBar.getAttribute("overflowtarget"));
+
+const kBookmarksButton = "bookmarks-menu-button";
+const kBookmarksItems = "personal-bookmarks";
+const kOriginalWindowWidth = window.outerWidth;
+const kSmallWidth = 400;
+
+/**
+ * Helper function that opens the bookmarks menu, and returns a Promise that
+ * resolves as soon as the menu is ready for interaction.
+ */
+function bookmarksMenuPanelShown() {
+ let deferred = Promise.defer();
+ let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup");
+ let onTransitionEnd = (e) => {
+ if (e.target == bookmarksMenuPopup) {
+ bookmarksMenuPopup.removeEventListener("transitionend", onTransitionEnd);
+ deferred.resolve();
+ }
+ }
+ bookmarksMenuPopup.addEventListener("transitionend", onTransitionEnd);
+ return deferred.promise;
+}
+
+/**
+ * Checks that the placesContext menu is correctly attached to the
+ * controller of some view. Returns a Promise that resolves as soon
+ * as the context menu is closed.
+ *
+ * @param aItemWithContextMenu the item that we need to synthesize hte
+ * right click on in order to open the context menu.
+ */
+function checkPlacesContextMenu(aItemWithContextMenu) {
+ return Task.spawn(function* () {
+ let contextMenu = document.getElementById("placesContext");
+ let newBookmarkItem = document.getElementById("placesContext_new:bookmark");
+ info("Waiting for context menu on " + aItemWithContextMenu.id);
+ let shownPromise = popupShown(contextMenu);
+ EventUtils.synthesizeMouseAtCenter(aItemWithContextMenu,
+ {type: "contextmenu", button: 2});
+ yield shownPromise;
+
+ ok(!newBookmarkItem.hasAttribute("disabled"),
+ "New bookmark item shouldn't be disabled");
+
+ info("Closing context menu");
+ yield closePopup(contextMenu);
+ });
+}
+
+/**
+ * Opens the bookmarks menu panel, and then opens each of the "special"
+ * submenus in that list. Then it checks that those submenu's context menus
+ * are properly hooked up to a controller.
+ */
+function checkSpecialContextMenus() {
+ return Task.spawn(function* () {
+ let bookmarksMenuButton = document.getElementById(kBookmarksButton);
+ let bookmarksMenuPopup = document.getElementById("BMB_bookmarksPopup");
+
+ const kSpecialItemIDs = {
+ "BMB_bookmarksToolbar": "BMB_bookmarksToolbarPopup",
+ "BMB_unsortedBookmarks": "BMB_unsortedBookmarksPopup",
+ };
+
+ // Open the bookmarks menu button context menus and ensure that
+ // they have the proper views attached.
+ let shownPromise = bookmarksMenuPanelShown();
+ let dropmarker = document.getAnonymousElementByAttribute(bookmarksMenuButton,
+ "anonid", "dropmarker");
+ EventUtils.synthesizeMouseAtCenter(dropmarker, {});
+ info("Waiting for bookmarks menu popup to show after clicking dropmarker.")
+ yield shownPromise;
+
+ for (let menuID in kSpecialItemIDs) {
+ let menuItem = document.getElementById(menuID);
+ let menuPopup = document.getElementById(kSpecialItemIDs[menuID]);
+ info("Waiting to open menu for " + menuID);
+ let shownPromise = popupShown(menuPopup);
+ menuPopup.openPopup(menuItem, null, 0, 0, false, false, null);
+ yield shownPromise;
+
+ yield checkPlacesContextMenu(menuPopup);
+ info("Closing menu for " + menuID);
+ yield closePopup(menuPopup);
+ }
+
+ info("Closing bookmarks menu");
+ yield closePopup(bookmarksMenuPopup);
+ });
+}
+
+/**
+ * Closes a focused popup by simulating pressing the Escape key,
+ * and returns a Promise that resolves as soon as the popup is closed.
+ *
+ * @param aPopup the popup node to close.
+ */
+function closePopup(aPopup) {
+ let hiddenPromise = popupHidden(aPopup);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ return hiddenPromise;
+}
+
+/**
+ * Helper function that checks that the context menu of the
+ * bookmark toolbar items chevron popup is correctly hooked up
+ * to the controller of a view.
+ */
+function checkBookmarksItemsChevronContextMenu() {
+ return Task.spawn(function*() {
+ let chevronPopup = document.getElementById("PlacesChevronPopup");
+ let shownPromise = popupShown(chevronPopup);
+ let chevron = document.getElementById("PlacesChevron");
+ EventUtils.synthesizeMouseAtCenter(chevron, {});
+ info("Waiting for bookmark toolbar item chevron popup to show");
+ yield shownPromise;
+ yield waitForCondition(() => {
+ for (let child of chevronPopup.children) {
+ if (child.style.visibility != "hidden")
+ return true;
+ }
+ return false;
+ });
+ yield checkPlacesContextMenu(chevronPopup);
+ info("Waiting for bookmark toolbar item chevron popup to close");
+ yield closePopup(chevronPopup);
+ });
+}
+
+/**
+ * Forces the window to a width that causes the nav-bar to overflow
+ * its contents. Returns a Promise that resolves as soon as the
+ * overflowable nav-bar is showing its chevron.
+ */
+function overflowEverything() {
+ info("Waiting for overflow");
+ window.resizeTo(kSmallWidth, window.outerHeight);
+ return waitForCondition(() => gNavBar.hasAttribute("overflowing"));
+}
+
+/**
+ * Returns the window to its original size from the start of the test,
+ * and returns a Promise that resolves when the nav-bar is no longer
+ * overflowing.
+ */
+function stopOverflowing() {
+ info("Waiting until we stop overflowing");
+ window.resizeTo(kOriginalWindowWidth, window.outerHeight);
+ return waitForCondition(() => !gNavBar.hasAttribute("overflowing"));
+}
+
+/**
+ * Checks that an item with ID aID is overflowing in the nav-bar.
+ *
+ * @param aID the ID of the node to check for overflowingness.
+ */
+function checkOverflowing(aID) {
+ ok(!gNavBar.querySelector("#" + aID),
+ "Item with ID " + aID + " should no longer be in the gNavBar");
+ let item = gOverflowList.querySelector("#" + aID);
+ ok(item, "Item with ID " + aID + " should be overflowing");
+ is(item.getAttribute("overflowedItem"), "true",
+ "Item with ID " + aID + " should have overflowedItem attribute");
+}
+
+/**
+ * Checks that an item with ID aID is not overflowing in the nav-bar.
+ *
+ * @param aID the ID of hte node to check for non-overflowingness.
+ */
+function checkNotOverflowing(aID) {
+ ok(!gOverflowList.querySelector("#" + aID),
+ "Item with ID " + aID + " should no longer be overflowing");
+ let item = gNavBar.querySelector("#" + aID);
+ ok(item, "Item with ID " + aID + " should be in the nav bar");
+ ok(!item.hasAttribute("overflowedItem"),
+ "Item with ID " + aID + " should not have overflowedItem attribute");
+}
+
+/**
+ * Test that overflowing the bookmarks menu button doesn't break the
+ * context menus for the Unsorted and Bookmarks Toolbar menu items.
+ */
+add_task(function* testOverflowingBookmarksButtonContextMenu() {
+ ok(!gNavBar.hasAttribute("overflowing"), "Should start with a non-overflowing toolbar.");
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+
+ // Open the Unsorted and Bookmarks Toolbar context menus and ensure
+ // that they have views attached.
+ yield checkSpecialContextMenus();
+
+ yield overflowEverything();
+ checkOverflowing(kBookmarksButton);
+
+ yield stopOverflowing();
+ checkNotOverflowing(kBookmarksButton);
+
+ yield checkSpecialContextMenus();
+});
+
+/**
+ * Test that the bookmarks toolbar items context menu still works if moved
+ * to the menu from the overflow panel, and then back to the toolbar.
+ */
+add_task(function* testOverflowingBookmarksItemsContextMenu() {
+ info("Ensuring panel is ready.");
+ yield PanelUI.ensureReady();
+
+ let bookmarksToolbarItems = document.getElementById(kBookmarksItems);
+ gCustomizeMode.addToToolbar(bookmarksToolbarItems);
+ yield checkPlacesContextMenu(bookmarksToolbarItems);
+
+ yield overflowEverything();
+ checkOverflowing(kBookmarksItems)
+
+ gCustomizeMode.addToPanel(bookmarksToolbarItems);
+
+ yield stopOverflowing();
+
+ gCustomizeMode.addToToolbar(bookmarksToolbarItems);
+ yield checkPlacesContextMenu(bookmarksToolbarItems);
+});
+
+/**
+ * Test that overflowing the bookmarks toolbar items doesn't cause the
+ * context menu in the bookmarks toolbar items chevron to stop working.
+ */
+add_task(function* testOverflowingBookmarksItemsChevronContextMenu() {
+ // If it's not already there, let's move the bookmarks toolbar items to
+ // the nav-bar.
+ let bookmarksToolbarItems = document.getElementById(kBookmarksItems);
+ gCustomizeMode.addToToolbar(bookmarksToolbarItems);
+
+ // We make the PlacesToolbarItems element be super tiny in order to force
+ // the bookmarks toolbar items into overflowing and making the chevron
+ // show itself.
+ let placesToolbarItems = document.getElementById("PlacesToolbarItems");
+ let placesChevron = document.getElementById("PlacesChevron");
+ placesToolbarItems.style.maxWidth = "10px";
+ info("Waiting for chevron to no longer be collapsed");
+ yield waitForCondition(() => !placesChevron.collapsed);
+
+ yield checkBookmarksItemsChevronContextMenu();
+
+ yield overflowEverything();
+ checkOverflowing(kBookmarksItems);
+
+ yield stopOverflowing();
+ checkNotOverflowing(kBookmarksItems);
+
+ yield checkBookmarksItemsChevronContextMenu();
+
+ placesToolbarItems.style.removeProperty("max-width");
+});
+
+add_task(function* asyncCleanup() {
+ window.resizeTo(kOriginalWindowWidth, window.outerHeight);
+ yield resetCustomization();
+});
diff --git a/browser/components/customizableui/test/browser_985815_propagate_setToolbarVisibility.js b/browser/components/customizableui/test/browser_985815_propagate_setToolbarVisibility.js
new file mode 100644
index 000000000..c341c2158
--- /dev/null
+++ b/browser/components/customizableui/test/browser_985815_propagate_setToolbarVisibility.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(function*() {
+ ok(CustomizableUI.inDefaultState, "Should start in default state.");
+ this.otherWin = yield openAndLoadWindow({private: true}, true);
+ yield startCustomizing(this.otherWin);
+ let resetButton = this.otherWin.document.getElementById("customization-reset-button");
+ ok(resetButton.disabled, "Reset button should be disabled");
+
+ if (typeof CustomizableUI.setToolbarVisibility == "function") {
+ CustomizableUI.setToolbarVisibility("PersonalToolbar", true);
+ } else {
+ setToolbarVisibility(document.getElementById("PersonalToolbar"), true);
+ }
+
+ let otherPersonalToolbar = this.otherWin.document.getElementById("PersonalToolbar");
+ let personalToolbar = document.getElementById("PersonalToolbar");
+ ok(!otherPersonalToolbar.collapsed, "Toolbar should be uncollapsed in private window");
+ ok(!personalToolbar.collapsed, "Toolbar should be uncollapsed in normal window");
+ ok(!resetButton.disabled, "Reset button should be enabled");
+
+ yield this.otherWin.gCustomizeMode.reset();
+
+ ok(otherPersonalToolbar.collapsed, "Toolbar should be collapsed in private window");
+ ok(personalToolbar.collapsed, "Toolbar should be collapsed in normal window");
+ ok(resetButton.disabled, "Reset button should be disabled");
+
+ yield endCustomizing(this.otherWin);
+
+ yield promiseWindowClosed(this.otherWin);
+});
+
+
+add_task(function* asyncCleanup() {
+ if (this.otherWin && !this.otherWin.closed) {
+ yield promiseWindowClosed(this.otherWin);
+ }
+ if (!CustomizableUI.inDefaultState) {
+ CustomizableUI.reset();
+ }
+});
diff --git a/browser/components/customizableui/test/browser_987177_destroyWidget_xul.js b/browser/components/customizableui/test/browser_987177_destroyWidget_xul.js
new file mode 100644
index 000000000..6a4d0aab4
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987177_destroyWidget_xul.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";
+
+const BUTTONID = "test-XUL-wrapper-destroyWidget";
+
+
+add_task(function() {
+ let btn = createDummyXULButton(BUTTONID, "XUL btn");
+ gNavToolbox.palette.appendChild(btn);
+ let firstWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window);
+ ok(firstWrapper, "Should get a wrapper");
+ ok(firstWrapper.node, "Node should be there on first wrapper.");
+
+ btn.remove();
+ CustomizableUI.destroyWidget(BUTTONID);
+ let secondWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window);
+ isnot(firstWrapper, secondWrapper, "Wrappers should be different after destroyWidget call.");
+ ok(!firstWrapper.node, "No node should be there on old wrapper.");
+ ok(!secondWrapper.node, "No node should be there on new wrapper.");
+
+ btn = createDummyXULButton(BUTTONID, "XUL btn");
+ gNavToolbox.palette.appendChild(btn);
+ let thirdWrapper = CustomizableUI.getWidget(BUTTONID).forWindow(window);
+ ok(thirdWrapper, "Should get a wrapper");
+ is(secondWrapper, thirdWrapper, "Should get the second wrapper again.");
+ ok(firstWrapper.node, "Node should be there on old wrapper.");
+ ok(secondWrapper.node, "Node should be there on second wrapper.");
+ ok(thirdWrapper.node, "Node should be there on third wrapper.");
+});
+
diff --git a/browser/components/customizableui/test/browser_987177_xul_wrapper_updating.js b/browser/components/customizableui/test/browser_987177_xul_wrapper_updating.js
new file mode 100644
index 000000000..f838e204d
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987177_xul_wrapper_updating.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";
+
+const BUTTONID = "test-XUL-wrapper-widget";
+add_task(function() {
+ let btn = createDummyXULButton(BUTTONID, "XUL btn");
+ gNavToolbox.palette.appendChild(btn);
+ let groupWrapper = CustomizableUI.getWidget(BUTTONID);
+ ok(groupWrapper, "Should get a group wrapper");
+ let singleWrapper = groupWrapper.forWindow(window);
+ ok(singleWrapper, "Should get a single wrapper");
+ is(singleWrapper.node, btn, "Node should be in the wrapper");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
+
+ CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
+
+ let otherSingleWrapper = groupWrapper.forWindow(window);
+ is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after adding the node to the navbar.");
+ is(singleWrapper.node, btn, "Node should be in the wrapper");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
+
+ CustomizableUI.removeWidgetFromArea(BUTTONID);
+
+ otherSingleWrapper = groupWrapper.forWindow(window);
+ isnot(singleWrapper, otherSingleWrapper, "Shouldn't get the same wrapper after removing it from the navbar.");
+ singleWrapper = otherSingleWrapper;
+ is(singleWrapper.node, btn, "Node should be in the wrapper");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
+
+ btn.remove();
+ otherSingleWrapper = groupWrapper.forWindow(window);
+ is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after physically removing the node.");
+ is(singleWrapper.node, null, "Wrapper's node should be null now that it's left the DOM.");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, null, "That instance should be null.");
+
+ btn = createDummyXULButton(BUTTONID, "XUL btn");
+ gNavToolbox.palette.appendChild(btn);
+ otherSingleWrapper = groupWrapper.forWindow(window);
+ is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after readding the node.");
+ is(singleWrapper.node, btn, "Node should be in the wrapper");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
+
+ CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
+
+ otherSingleWrapper = groupWrapper.forWindow(window);
+ is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after adding the node to the navbar.");
+ is(singleWrapper.node, btn, "Node should be in the wrapper");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
+
+ CustomizableUI.removeWidgetFromArea(BUTTONID);
+
+ otherSingleWrapper = groupWrapper.forWindow(window);
+ isnot(singleWrapper, otherSingleWrapper, "Shouldn't get the same wrapper after removing it from the navbar.");
+ singleWrapper = otherSingleWrapper;
+ is(singleWrapper.node, btn, "Node should be in the wrapper");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, btn, "Button should be that instance.");
+
+ btn.remove();
+ otherSingleWrapper = groupWrapper.forWindow(window);
+ is(singleWrapper, otherSingleWrapper, "Should get the same wrapper after physically removing the node.");
+ is(singleWrapper.node, null, "Wrapper's node should be null now that it's left the DOM.");
+ is(groupWrapper.instances.length, 1, "There should be 1 instance on the group wrapper");
+ is(groupWrapper.instances[0].node, null, "That instance should be null.");
+});
diff --git a/browser/components/customizableui/test/browser_987185_syncButton.js b/browser/components/customizableui/test/browser_987185_syncButton.js
new file mode 100755
index 000000000..988d738be
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987185_syncButton.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 syncService = {};
+Components.utils.import("resource://services-sync/service.js", syncService);
+
+var needsSetup;
+var originalSync;
+var service = syncService.Service;
+var syncWasCalled = false;
+
+add_task(function* testSyncButtonFunctionality() {
+ info("Check Sync button functionality");
+ storeInitialValues();
+ mockFunctions();
+
+ // add the Sync button to the panel
+ CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+
+ // check the button's functionality
+ yield PanelUI.show();
+ info("The panel menu was opened");
+
+ let syncButton = document.getElementById("sync-button");
+ ok(syncButton, "The Sync button was added to the Panel Menu");
+ // click the button - the panel should open.
+ syncButton.click();
+ let syncPanel = document.getElementById("PanelUI-remotetabs");
+ ok(syncPanel.getAttribute("current"), "Sync Panel is in view");
+
+ // Find and click the "setup" button.
+ let syncNowButton = document.getElementById("PanelUI-remotetabs-syncnow");
+ syncNowButton.click();
+
+ info("The sync button was clicked");
+
+ yield waitForCondition(() => syncWasCalled);
+});
+
+add_task(function* asyncCleanup() {
+ // reset the panel UI to the default state
+ yield resetCustomization();
+ ok(CustomizableUI.inDefaultState, "The panel UI is in default state again.");
+
+ if (isPanelUIOpen()) {
+ let panelHidePromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHidePromise;
+ }
+
+ restoreValues();
+});
+
+function mockFunctions() {
+ // mock needsSetup
+ gSyncUI._needsSetup = () => Promise.resolve(false);
+
+ // mock service.errorHandler.syncAndReportErrors()
+ service.errorHandler.syncAndReportErrors = mocked_syncAndReportErrors;
+}
+
+function mocked_syncAndReportErrors() {
+ syncWasCalled = true;
+}
+
+function restoreValues() {
+ gSyncUI._needsSetup = needsSetup;
+ service.sync = originalSync;
+}
+
+function storeInitialValues() {
+ needsSetup = gSyncUI._needsSetup;
+ originalSync = service.sync;
+}
diff --git a/browser/components/customizableui/test/browser_987492_window_api.js b/browser/components/customizableui/test/browser_987492_window_api.js
new file mode 100644
index 000000000..1718303e1
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987492_window_api.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";
+
+
+add_task(function* testOneWindow() {
+ let windows = [];
+ for (let win of CustomizableUI.windows)
+ windows.push(win);
+ is(windows.length, 1, "Should have one customizable window");
+});
+
+
+add_task(function* testOpenCloseWindow() {
+ let newWindow = null;
+ let openListener = {
+ onWindowOpened: function(window) {
+ newWindow = window;
+ }
+ }
+ CustomizableUI.addListener(openListener);
+ let win = yield openAndLoadWindow(null, true);
+ isnot(newWindow, null, "Should have gotten onWindowOpen event");
+ is(newWindow, win, "onWindowOpen event should have received expected window");
+ CustomizableUI.removeListener(openListener);
+
+ let windows = [];
+ for (let win of CustomizableUI.windows)
+ windows.push(win);
+ is(windows.length, 2, "Should have two customizable windows");
+ isnot(windows.indexOf(window), -1, "Current window should be in window collection.");
+ isnot(windows.indexOf(newWindow), -1, "New window should be in window collection.");
+
+ let closedWindow = null;
+ let closeListener = {
+ onWindowClosed: function(window) {
+ closedWindow = window;
+ }
+ }
+ CustomizableUI.addListener(closeListener);
+ yield promiseWindowClosed(newWindow);
+ isnot(closedWindow, null, "Should have gotten onWindowClosed event")
+ is(newWindow, closedWindow, "Closed window should match previously opened window");
+ CustomizableUI.removeListener(closeListener);
+
+ windows = [];
+ for (let win of CustomizableUI.windows)
+ windows.push(win);
+ is(windows.length, 1, "Should have one customizable window");
+ isnot(windows.indexOf(window), -1, "Current window should be in window collection.");
+ is(windows.indexOf(closedWindow), -1, "Closed window should not be in window collection.");
+});
diff --git a/browser/components/customizableui/test/browser_987640_charEncoding.js b/browser/components/customizableui/test/browser_987640_charEncoding.js
new file mode 100644
index 000000000..dfe02f940
--- /dev/null
+++ b/browser/components/customizableui/test/browser_987640_charEncoding.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/. */
+
+"use strict";
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/customizableui/test/support/test_967000_charEncoding_page.html";
+
+add_task(function*() {
+ info("Check Character Encoding panel functionality");
+
+ // add the Character Encoding button to the panel
+ CustomizableUI.addWidgetToArea("characterencoding-button",
+ CustomizableUI.AREA_PANEL);
+
+ let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE, true, true);
+
+ yield PanelUI.show();
+ let charEncodingButton = document.getElementById("characterencoding-button");
+ let characterEncodingView = document.getElementById("PanelUI-characterEncodingView");
+ let subviewShownPromise = subviewShown(characterEncodingView);
+ charEncodingButton.click();
+ yield subviewShownPromise;
+
+ let checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
+ let initialEncoding = checkedButtons[0];
+ is(initialEncoding.getAttribute("label"), "Unicode", "The unicode encoding is initially selected");
+
+ // change the encoding
+ let encodings = characterEncodingView.querySelectorAll("toolbarbutton");
+ let newEncoding = encodings[0].hasAttribute("checked") ? encodings[1] : encodings[0];
+ let tabLoadPromise = promiseTabLoadEvent(gBrowser.selectedTab, TEST_PAGE);
+ newEncoding.click();
+ yield tabLoadPromise;
+
+ // check that the new encodng is applied
+ yield PanelUI.show();
+ charEncodingButton.click();
+ checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
+ let selectedEncodingName = checkedButtons[0].getAttribute("label");
+ ok(selectedEncodingName != "Unicode", "The encoding was changed to " + selectedEncodingName);
+
+ // reset the initial encoding
+ yield PanelUI.show();
+ charEncodingButton.click();
+ tabLoadPromise = promiseTabLoadEvent(gBrowser.selectedTab, TEST_PAGE);
+ initialEncoding.click();
+ yield tabLoadPromise;
+ yield PanelUI.show();
+ charEncodingButton.click();
+ checkedButtons = characterEncodingView.querySelectorAll("toolbarbutton[checked='true']");
+ is(checkedButtons[0].getAttribute("label"), "Unicode", "The encoding was reset to Unicode");
+ yield BrowserTestUtils.removeTab(newTab);
+});
+
+add_task(function* asyncCleanup() {
+ // reset the panel to the default state
+ yield resetCustomization();
+ ok(CustomizableUI.inDefaultState, "The UI is in default state again.");
+});
diff --git a/browser/components/customizableui/test/browser_988072_sidebar_events.js b/browser/components/customizableui/test/browser_988072_sidebar_events.js
new file mode 100644
index 000000000..6791be67a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_988072_sidebar_events.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";
+
+var gSidebarMenu = document.getElementById("viewSidebarMenu");
+var gTestSidebarItem = null;
+
+var EVENTS = {
+ click: 0, command: 0,
+ onclick: 0, oncommand: 0
+};
+
+window.sawEvent = function(event, isattr) {
+ let type = (isattr ? "on" : "") + event.type
+ EVENTS[type]++;
+};
+
+registerCleanupFunction(() => {
+ delete window.sawEvent;
+
+ // Ensure sidebar is hidden after each test:
+ if (!document.getElementById("sidebar-box").hidden) {
+ SidebarUI.hide();
+ }
+});
+
+function checkExpectedEvents(expected) {
+ for (let type of Object.keys(EVENTS)) {
+ let count = (type in expected ? expected[type] : 0);
+ is(EVENTS[type], count, "Should have seen the right number of " + type + " events");
+ EVENTS[type] = 0;
+ }
+}
+
+function createSidebarItem() {
+ gTestSidebarItem = document.createElement("menuitem");
+ gTestSidebarItem.id = "testsidebar";
+ gTestSidebarItem.setAttribute("label", "Test Sidebar");
+ gSidebarMenu.insertBefore(gTestSidebarItem, gSidebarMenu.firstChild);
+}
+
+function addWidget() {
+ CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
+ PanelUI.disableSingleSubviewPanelAnimations();
+}
+
+function removeWidget() {
+ CustomizableUI.removeWidgetFromArea("sidebar-button");
+ PanelUI.enableSingleSubviewPanelAnimations();
+}
+
+// Filters out the trailing menuseparators from the sidebar list
+function getSidebarList() {
+ let sidebars = [...gSidebarMenu.children].filter(sidebar => {
+ if (sidebar.localName == "menuseparator")
+ return false;
+ if (sidebar.getAttribute("hidden") == "true")
+ return false;
+ return true;
+ });
+ return sidebars;
+}
+
+function compareElements(original, displayed) {
+ let attrs = ["label", "key", "disabled", "hidden", "origin", "image", "checked"];
+ for (let attr of attrs) {
+ is(displayed.getAttribute(attr), original.getAttribute(attr), "Should have the same " + attr + " attribute");
+ }
+}
+
+function compareList(original, displayed) {
+ is(displayed.length, original.length, "Should have the same number of children");
+
+ for (let i = 0; i < Math.min(original.length, displayed.length); i++) {
+ compareElements(displayed[i], original[i]);
+ }
+}
+
+var showSidebarPopup = Task.async(function*() {
+ let button = document.getElementById("sidebar-button");
+ let subview = document.getElementById("PanelUI-sidebar");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+
+ let subviewShownPromise = subviewShown(subview);
+ EventUtils.synthesizeMouseAtCenter(button, {});
+ return Promise.all([subviewShownPromise, popupShownPromise]);
+});
+
+// Check the sidebar widget shows the default items
+add_task(function*() {
+ addWidget();
+
+ yield showSidebarPopup();
+
+ let sidebars = getSidebarList();
+ let displayed = [...document.getElementById("PanelUI-sidebarItems").children];
+ compareList(sidebars, displayed);
+
+ let subview = document.getElementById("PanelUI-sidebar");
+ let subviewHiddenPromise = subviewHidden(subview);
+ document.getElementById("customizationui-widget-panel").hidePopup();
+ yield subviewHiddenPromise;
+
+ removeWidget();
+});
+
+function add_sidebar_task(description, setup, teardown) {
+ add_task(function*() {
+ info(description);
+ createSidebarItem();
+ addWidget();
+ yield setup();
+
+ CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar");
+
+ yield showSidebarPopup();
+
+ let sidebars = getSidebarList();
+ let displayed = [...document.getElementById("PanelUI-sidebarItems").children];
+ compareList(sidebars, displayed);
+
+ is(displayed[0].label, "Test Sidebar", "Should have the right element at the top");
+ let subview = document.getElementById("PanelUI-sidebar");
+ let subviewHiddenPromise = subviewHidden(subview);
+ EventUtils.synthesizeMouseAtCenter(displayed[0], {});
+ yield subviewHiddenPromise;
+
+ yield teardown();
+ gTestSidebarItem.remove();
+ removeWidget();
+ });
+}
+
+add_sidebar_task(
+ "Check that a sidebar that uses a command event listener works",
+function*() {
+ gTestSidebarItem.addEventListener("command", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ command: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses a click event listener works",
+function*() {
+ gTestSidebarItem.addEventListener("click", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ click: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses both click and command event listeners works",
+function*() {
+ gTestSidebarItem.addEventListener("command", window.sawEvent);
+ gTestSidebarItem.addEventListener("click", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ command: 1, click: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses an oncommand attribute works",
+function*() {
+ gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
+}, function*() {
+ checkExpectedEvents({ oncommand: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses an onclick attribute works",
+function*() {
+ gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
+}, function*() {
+ checkExpectedEvents({ onclick: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses both onclick and oncommand attributes works",
+function*() {
+ gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
+ gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
+}, function*() {
+ checkExpectedEvents({ onclick: 1, oncommand: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses an onclick attribute and a command listener works",
+function*() {
+ gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
+ gTestSidebarItem.addEventListener("command", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ onclick: 1, command: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses an oncommand attribute and a click listener works",
+function*() {
+ gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
+ gTestSidebarItem.addEventListener("click", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ click: 1, oncommand: 1 });
+});
+
+add_sidebar_task(
+ "A sidebar with both onclick attribute and click listener sees only one event :(",
+function*() {
+ gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
+ gTestSidebarItem.addEventListener("click", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ onclick: 1 });
+});
+
+add_sidebar_task(
+ "A sidebar with both oncommand attribute and command listener sees only one event :(",
+function*() {
+ gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
+ gTestSidebarItem.addEventListener("command", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ oncommand: 1 });
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses a broadcaster with an oncommand attribute works",
+function*() {
+ let broadcaster = document.createElement("broadcaster");
+ broadcaster.setAttribute("id", "testbroadcaster");
+ broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("label", "Test Sidebar");
+ document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
+
+ gTestSidebarItem.setAttribute("observes", "testbroadcaster");
+}, function*() {
+ checkExpectedEvents({ oncommand: 1 });
+ document.getElementById("testbroadcaster").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses a broadcaster with an onclick attribute works",
+function*() {
+ let broadcaster = document.createElement("broadcaster");
+ broadcaster.setAttribute("id", "testbroadcaster");
+ broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("label", "Test Sidebar");
+ document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
+
+ gTestSidebarItem.setAttribute("observes", "testbroadcaster");
+}, function*() {
+ checkExpectedEvents({ onclick: 1 });
+ document.getElementById("testbroadcaster").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses a broadcaster with both onclick and oncommand attributes works",
+function*() {
+ let broadcaster = document.createElement("broadcaster");
+ broadcaster.setAttribute("id", "testbroadcaster");
+ broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("label", "Test Sidebar");
+ document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
+
+ gTestSidebarItem.setAttribute("observes", "testbroadcaster");
+}, function*() {
+ checkExpectedEvents({ onclick: 1, oncommand: 1 });
+ document.getElementById("testbroadcaster").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar with a click listener and a broadcaster with an oncommand attribute works",
+function*() {
+ let broadcaster = document.createElement("broadcaster");
+ broadcaster.setAttribute("id", "testbroadcaster");
+ broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("label", "Test Sidebar");
+ document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
+
+ gTestSidebarItem.setAttribute("observes", "testbroadcaster");
+ gTestSidebarItem.addEventListener("click", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ click: 1, oncommand: 1 });
+ document.getElementById("testbroadcaster").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar with a command listener and a broadcaster with an onclick attribute works",
+function*() {
+ let broadcaster = document.createElement("broadcaster");
+ broadcaster.setAttribute("id", "testbroadcaster");
+ broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("label", "Test Sidebar");
+ document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
+
+ gTestSidebarItem.setAttribute("observes", "testbroadcaster");
+ gTestSidebarItem.addEventListener("command", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ onclick: 1, command: 1 });
+ document.getElementById("testbroadcaster").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar with a click listener and a broadcaster with an onclick " +
+ "attribute only sees one event :(",
+function*() {
+ let broadcaster = document.createElement("broadcaster");
+ broadcaster.setAttribute("id", "testbroadcaster");
+ broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("label", "Test Sidebar");
+ document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
+
+ gTestSidebarItem.setAttribute("observes", "testbroadcaster");
+ gTestSidebarItem.addEventListener("click", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ onclick: 1 });
+ document.getElementById("testbroadcaster").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar with a command listener and a broadcaster with an oncommand " +
+ "attribute only sees one event :(",
+function*() {
+ let broadcaster = document.createElement("broadcaster");
+ broadcaster.setAttribute("id", "testbroadcaster");
+ broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
+ broadcaster.setAttribute("label", "Test Sidebar");
+ document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
+
+ gTestSidebarItem.setAttribute("observes", "testbroadcaster");
+ gTestSidebarItem.addEventListener("command", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ oncommand: 1 });
+ document.getElementById("testbroadcaster").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses a command element with a command event listener works",
+function*() {
+ let command = document.createElement("command");
+ command.setAttribute("id", "testcommand");
+ document.getElementById("mainCommandSet").appendChild(command);
+ command.addEventListener("command", window.sawEvent);
+
+ gTestSidebarItem.setAttribute("command", "testcommand");
+}, function*() {
+ checkExpectedEvents({ command: 1 });
+ document.getElementById("testcommand").remove();
+});
+
+add_sidebar_task(
+ "Check that a sidebar that uses a command element with an oncommand attribute works",
+function*() {
+ let command = document.createElement("command");
+ command.setAttribute("id", "testcommand");
+ command.setAttribute("oncommand", "window.sawEvent(event, true)");
+ document.getElementById("mainCommandSet").appendChild(command);
+
+ gTestSidebarItem.setAttribute("command", "testcommand");
+}, function*() {
+ checkExpectedEvents({ oncommand: 1 });
+ document.getElementById("testcommand").remove();
+});
+
+add_sidebar_task("Check that a sidebar that uses a command element with a " +
+ "command event listener and oncommand attribute works",
+function*() {
+ let command = document.createElement("command");
+ command.setAttribute("id", "testcommand");
+ command.setAttribute("oncommand", "window.sawEvent(event, true)");
+ document.getElementById("mainCommandSet").appendChild(command);
+ command.addEventListener("command", window.sawEvent);
+
+ gTestSidebarItem.setAttribute("command", "testcommand");
+}, function*() {
+ checkExpectedEvents({ command: 1, oncommand: 1 });
+ document.getElementById("testcommand").remove();
+});
+
+add_sidebar_task(
+ "A sidebar with a command element will still see click events",
+function*() {
+ let command = document.createElement("command");
+ command.setAttribute("id", "testcommand");
+ command.setAttribute("oncommand", "window.sawEvent(event, true)");
+ document.getElementById("mainCommandSet").appendChild(command);
+ command.addEventListener("command", window.sawEvent);
+
+ gTestSidebarItem.setAttribute("command", "testcommand");
+ gTestSidebarItem.addEventListener("click", window.sawEvent);
+}, function*() {
+ checkExpectedEvents({ click: 1, command: 1, oncommand: 1 });
+ document.getElementById("testcommand").remove();
+});
diff --git a/browser/components/customizableui/test/browser_989338_saved_placements_not_resaved.js b/browser/components/customizableui/test/browser_989338_saved_placements_not_resaved.js
new file mode 100644
index 000000000..2a1b01bf7
--- /dev/null
+++ b/browser/components/customizableui/test/browser_989338_saved_placements_not_resaved.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";
+
+const BUTTONID = "test-widget-saved-earlier";
+const AREAID = "test-area-saved-earlier";
+
+var hadSavedState;
+function test() {
+ // Hack our way into the module to fake a saved state that isn't there...
+ let backstagePass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+ hadSavedState = backstagePass.gSavedState != null;
+ if (!hadSavedState) {
+ backstagePass.gSavedState = {placements: {}};
+ }
+ backstagePass.gSavedState.placements[AREAID] = [BUTTONID];
+ // Put bogus stuff in the saved state for the nav-bar, so as to check the current placements
+ // override this one...
+ backstagePass.gSavedState.placements[CustomizableUI.AREA_NAVBAR] = ["bogus-navbar-item"];
+
+ backstagePass.gDirty = true;
+ backstagePass.CustomizableUIInternal.saveState();
+
+ let newSavedState = JSON.parse(Services.prefs.getCharPref("browser.uiCustomization.state"));
+ let savedArea = Array.isArray(newSavedState.placements[AREAID]);
+ ok(savedArea, "Should have re-saved the state, even though the area isn't registered");
+
+ if (savedArea) {
+ placementArraysEqual(AREAID, newSavedState.placements[AREAID], [BUTTONID]);
+ }
+ ok(!backstagePass.gPlacements.has(AREAID), "Placements map shouldn't have been affected");
+
+ let savedNavbar = Array.isArray(newSavedState.placements[CustomizableUI.AREA_NAVBAR]);
+ ok(savedNavbar, "Should have saved nav-bar contents");
+ if (savedNavbar) {
+ placementArraysEqual(CustomizableUI.AREA_NAVBAR, newSavedState.placements[CustomizableUI.AREA_NAVBAR],
+ CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR));
+ }
+}
+
+registerCleanupFunction(function() {
+ let backstagePass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+ if (!hadSavedState) {
+ backstagePass.gSavedState = null;
+ } else {
+ let savedPlacements = backstagePass.gSavedState.placements;
+ delete savedPlacements[AREAID];
+ let realNavBarPlacements = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
+ savedPlacements[CustomizableUI.AREA_NAVBAR] = realNavBarPlacements;
+ }
+ backstagePass.gDirty = true;
+ backstagePass.CustomizableUIInternal.saveState();
+});
+
diff --git a/browser/components/customizableui/test/browser_989751_subviewbutton_class.js b/browser/components/customizableui/test/browser_989751_subviewbutton_class.js
new file mode 100644
index 000000000..0d11324ed
--- /dev/null
+++ b/browser/components/customizableui/test/browser_989751_subviewbutton_class.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";
+
+const kCustomClass = "acustomclassnoonewilluse";
+var tempElement = null;
+
+function insertClassNameToMenuChildren(parentMenu) {
+ let el = parentMenu.querySelector("menuitem:first-of-type");
+ el.classList.add(kCustomClass);
+ tempElement = el;
+}
+
+function checkSubviewButtonClass(menuId, buttonId, subviewId) {
+ return function*() {
+ info("Checking for items without the subviewbutton class in " + buttonId + " widget");
+ let menu = document.getElementById(menuId);
+ insertClassNameToMenuChildren(menu);
+
+ let placement = CustomizableUI.getPlacementOfWidget(buttonId);
+ let changedPlacement = false;
+ if (!placement || placement.area != CustomizableUI.AREA_PANEL) {
+ CustomizableUI.addWidgetToArea(buttonId, CustomizableUI.AREA_PANEL);
+ changedPlacement = true;
+ }
+ yield PanelUI.show();
+
+ let button = document.getElementById(buttonId);
+ button.click();
+
+ yield waitForCondition(() => !PanelUI.multiView.hasAttribute("transitioning"));
+ let subview = document.getElementById(subviewId);
+ ok(subview.firstChild, "Subview should have a kid");
+ let subviewchildren = subview.querySelectorAll("toolbarbutton");
+ for (let i = 0; i < subviewchildren.length; i++) {
+ let item = subviewchildren[i];
+ let itemReadable = "Item '" + item.label + "' (classes: " + item.className + ")";
+ ok(item.classList.contains("subviewbutton"), itemReadable + " should have the subviewbutton class.");
+ if (i == 0) {
+ ok(item.classList.contains(kCustomClass), itemReadable + " should still have its own class, too.");
+ }
+ }
+
+ let panelHiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield panelHiddenPromise;
+
+ if (changedPlacement) {
+ CustomizableUI.reset();
+ }
+ };
+}
+
+add_task(checkSubviewButtonClass("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems"));
+add_task(checkSubviewButtonClass("viewSidebarMenu", "sidebar-button", "PanelUI-sidebarItems"));
+
+registerCleanupFunction(function() {
+ tempElement.classList.remove(kCustomClass)
+ tempElement = null;
+});
diff --git a/browser/components/customizableui/test/browser_992747_toggle_noncustomizable_toolbar.js b/browser/components/customizableui/test/browser_992747_toggle_noncustomizable_toolbar.js
new file mode 100644
index 000000000..eb0a8c8ee
--- /dev/null
+++ b/browser/components/customizableui/test/browser_992747_toggle_noncustomizable_toolbar.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";
+
+const TOOLBARID = "test-noncustomizable-toolbar-for-toggling";
+function test() {
+ let tb = document.createElementNS(kNSXUL, "toolbar");
+ tb.id = TOOLBARID;
+ gNavToolbox.appendChild(tb);
+ try {
+ CustomizableUI.setToolbarVisibility(TOOLBARID, false);
+ } catch (ex) {
+ ok(false, "Should not throw exceptions trying to set toolbar visibility.");
+ }
+ is(tb.getAttribute("collapsed"), "true", "Toolbar should be collapsed");
+ try {
+ CustomizableUI.setToolbarVisibility(TOOLBARID, true);
+ } catch (ex) {
+ ok(false, "Should not throw exceptions trying to set toolbar visibility.");
+ }
+ is(tb.getAttribute("collapsed"), "false", "Toolbar should be uncollapsed");
+ tb.remove();
+}
+
diff --git a/browser/components/customizableui/test/browser_993322_widget_notoolbar.js b/browser/components/customizableui/test/browser_993322_widget_notoolbar.js
new file mode 100644
index 000000000..9264eb78a
--- /dev/null
+++ b/browser/components/customizableui/test/browser_993322_widget_notoolbar.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const BUTTONID = "test-API-created-widget-toolbar-gone";
+const TOOLBARID = "test-API-created-extra-toolbar";
+
+add_task(function*() {
+ let toolbar = createToolbarWithPlacements(TOOLBARID, []);
+ CustomizableUI.addWidgetToArea(BUTTONID, TOOLBARID);
+ is(CustomizableUI.getPlacementOfWidget(BUTTONID).area, TOOLBARID, "Should be on toolbar");
+ is(toolbar.children.length, 0, "Toolbar has no kid");
+
+ CustomizableUI.unregisterArea(TOOLBARID);
+ CustomizableUI.createWidget({id: BUTTONID, label: "Test widget toolbar gone"});
+
+ let currentWidget = CustomizableUI.getWidget(BUTTONID);
+
+ yield startCustomizing();
+ let buttonNode = document.getElementById(BUTTONID);
+ ok(buttonNode, "Should find button in window");
+ if (buttonNode) {
+ is(buttonNode.parentNode.localName, "toolbarpaletteitem", "Node should be wrapped");
+ is(buttonNode.parentNode.getAttribute("place"), "palette", "Node should be in palette");
+ is(buttonNode, gNavToolbox.palette.querySelector("#" + BUTTONID), "Node should really be in palette.");
+ }
+ is(currentWidget.forWindow(window).node, buttonNode, "Should have the same node for customize mode");
+ yield endCustomizing();
+
+ CustomizableUI.destroyWidget(BUTTONID);
+ CustomizableUI.unregisterArea(TOOLBARID, true);
+ toolbar.remove();
+ gAddedToolbars.clear();
+});
diff --git a/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js b/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js
new file mode 100644
index 000000000..4d292a929
--- /dev/null
+++ b/browser/components/customizableui/test/browser_995164_registerArea_during_customize_mode.js
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TOOLBARID = "test-toolbar-added-during-customize-mode";
+
+// The ID of a button that is not placed (ie, is in the palette) by default
+const kNonPlacedWidgetId = "open-file-button";
+
+add_task(function*() {
+ yield startCustomizing();
+ let toolbar = createToolbarWithPlacements(TOOLBARID, []);
+ CustomizableUI.addWidgetToArea(kNonPlacedWidgetId, TOOLBARID);
+ let button = document.getElementById(kNonPlacedWidgetId);
+ ok(button, "Button should exist.");
+ is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should be a wrapper.");
+
+ simulateItemDrag(button, gNavToolbox.palette);
+ ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette");
+ ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette.");
+
+ button.scrollIntoView();
+ simulateItemDrag(button, toolbar);
+ ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette");
+ is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar");
+ ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar.");
+
+ yield endCustomizing();
+ isnot(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should not be a wrapper outside customize mode.");
+ yield startCustomizing();
+
+ is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should be a wrapper back in customize mode.");
+
+ simulateItemDrag(button, gNavToolbox.palette);
+ ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette");
+ ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette.");
+
+ ok(!CustomizableUI.inDefaultState, "Not in default state while toolbar is not collapsed yet.");
+ setToolbarVisibility(toolbar, false);
+ ok(CustomizableUI.inDefaultState, "In default state while toolbar is collapsed.");
+
+ setToolbarVisibility(toolbar, true);
+
+ info("Check that removing the area registration from within customize mode works");
+ CustomizableUI.unregisterArea(TOOLBARID);
+ ok(CustomizableUI.inDefaultState, "Now that the toolbar is no longer registered, should be in default state.");
+ ok(!gCustomizeMode.areas.has(toolbar), "Toolbar shouldn't be known to customize mode.");
+
+ CustomizableUI.registerArea(TOOLBARID, {legacy: true, defaultPlacements: []});
+ CustomizableUI.registerToolbarNode(toolbar, []);
+ ok(!CustomizableUI.inDefaultState, "Now that the toolbar is registered again, should no longer be in default state.");
+ ok(gCustomizeMode.areas.has(toolbar), "Toolbar should be known to customize mode again.");
+
+ button.scrollIntoView();
+ simulateItemDrag(button, toolbar);
+ ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette");
+ is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar");
+ ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar.");
+
+ let otherWin = yield openAndLoadWindow({}, true);
+ let otherTB = otherWin.document.createElementNS(kNSXUL, "toolbar");
+ otherTB.id = TOOLBARID;
+ otherTB.setAttribute("customizable", "true");
+ let wasInformedCorrectlyOfAreaAppearing = false;
+ let listener = {
+ onAreaNodeRegistered: function(aArea, aNode) {
+ if (aNode == otherTB) {
+ wasInformedCorrectlyOfAreaAppearing = true;
+ }
+ }
+ };
+ CustomizableUI.addListener(listener);
+ otherWin.gNavToolbox.appendChild(otherTB);
+ ok(wasInformedCorrectlyOfAreaAppearing, "Should have been told area was registered.");
+ CustomizableUI.removeListener(listener);
+
+ ok(otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is on other toolbar, too.");
+
+ simulateItemDrag(button, gNavToolbox.palette);
+ ok(!CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved to the palette");
+ ok(gNavToolbox.palette.querySelector(`#${kNonPlacedWidgetId}`), "Button really is in palette.");
+ ok(!otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is in palette in other window, too.");
+
+ button.scrollIntoView();
+ simulateItemDrag(button, toolbar);
+ ok(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId), "Button moved out of palette");
+ is(CustomizableUI.getPlacementOfWidget(kNonPlacedWidgetId).area, TOOLBARID, "Button's back on toolbar");
+ ok(toolbar.querySelector(`#${kNonPlacedWidgetId}`), "Button really is on toolbar.");
+ ok(otherTB.querySelector(`#${kNonPlacedWidgetId}`), "Button is on other toolbar, too.");
+
+ let wasInformedCorrectlyOfAreaDisappearing = false;
+ // XXXgijs So we could be using promiseWindowClosed here. However, after
+ // repeated random oranges, I'm instead relying on onWindowClosed below to
+ // fire appropriately - it is linked to an unload event as well, and so
+ // reusing it prevents a potential race between unload handlers where the
+ // one from promiseWindowClosed could fire before the onWindowClosed
+ // (and therefore onAreaNodeRegistered) one, causing the test to fail.
+ let windowCloseDeferred = Promise.defer();
+ listener = {
+ onAreaNodeUnregistered: function(aArea, aNode, aReason) {
+ if (aArea == TOOLBARID) {
+ is(aNode, otherTB, "Should be informed about other toolbar");
+ is(aReason, CustomizableUI.REASON_WINDOW_CLOSED, "Reason should be correct.");
+ wasInformedCorrectlyOfAreaDisappearing = (aReason === CustomizableUI.REASON_WINDOW_CLOSED);
+ }
+ },
+ onWindowClosed: function(aWindow) {
+ if (aWindow == otherWin) {
+ windowCloseDeferred.resolve(aWindow);
+ } else {
+ info("Other window was closed!");
+ info("Other window title: " + (aWindow.document && aWindow.document.title));
+ info("Our window title: " + (otherWin.document && otherWin.document.title));
+ }
+ },
+ };
+ CustomizableUI.addListener(listener);
+ otherWin.close();
+ let windowClosed = yield windowCloseDeferred.promise;
+
+ is(windowClosed, otherWin, "Window should have sent onWindowClosed notification.");
+ ok(wasInformedCorrectlyOfAreaDisappearing, "Should be told about window closing.");
+ // Closing the other window should not be counted against this window's customize mode:
+ is(button.parentNode.localName, "toolbarpaletteitem", "Button's parent node should still be a wrapper.");
+ ok(gCustomizeMode.areas.has(toolbar), "Toolbar should still be a customizable area for this customize mode instance.");
+
+ yield gCustomizeMode.reset();
+
+ yield endCustomizing();
+
+ CustomizableUI.removeListener(listener);
+ wasInformedCorrectlyOfAreaDisappearing = false;
+ listener = {
+ onAreaNodeUnregistered: function(aArea, aNode, aReason) {
+ if (aArea == TOOLBARID) {
+ is(aNode, toolbar, "Should be informed about this window's toolbar");
+ is(aReason, CustomizableUI.REASON_AREA_UNREGISTERED, "Reason for final removal should be correct.");
+ wasInformedCorrectlyOfAreaDisappearing = (aReason === CustomizableUI.REASON_AREA_UNREGISTERED);
+ }
+ },
+ }
+ CustomizableUI.addListener(listener);
+ removeCustomToolbars();
+ ok(wasInformedCorrectlyOfAreaDisappearing, "Should be told about area being unregistered.");
+ CustomizableUI.removeListener(listener);
+ ok(CustomizableUI.inDefaultState, "Should be fine after exiting customize mode.");
+});
diff --git a/browser/components/customizableui/test/browser_996364_registerArea_different_properties.js b/browser/components/customizableui/test/browser_996364_registerArea_different_properties.js
new file mode 100644
index 000000000..b9de5f687
--- /dev/null
+++ b/browser/components/customizableui/test/browser_996364_registerArea_different_properties.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";
+
+// Calling CustomizableUI.registerArea twice with no
+// properties should not throw an exception.
+add_task(function() {
+ try {
+ CustomizableUI.registerArea("area-996364", {});
+ CustomizableUI.registerArea("area-996364", {});
+ } catch (ex) {
+ ok(false, ex.message);
+ }
+
+ CustomizableUI.unregisterArea("area-996364", true);
+});
+
+add_task(function() {
+ let exceptionThrown = false;
+ try {
+ CustomizableUI.registerArea("area-996364-2", {type: CustomizableUI.TYPE_TOOLBAR, defaultCollapsed: "false"});
+ } catch (ex) {
+ exceptionThrown = true;
+ }
+ ok(exceptionThrown, "defaultCollapsed is not allowed as an external property");
+
+ // No need to unregister the area because registration fails.
+});
+
+add_task(function() {
+ let exceptionThrown;
+ try {
+ CustomizableUI.registerArea("area-996364-3", {type: CustomizableUI.TYPE_TOOLBAR});
+ CustomizableUI.registerArea("area-996364-3", {type: CustomizableUI.TYPE_MENU_PANEL});
+ } catch (ex) {
+ exceptionThrown = ex;
+ }
+ ok(exceptionThrown, "Exception expected, an area cannot change types: " + (exceptionThrown ? exceptionThrown : "[no exception]"));
+
+ CustomizableUI.unregisterArea("area-996364-3", true);
+});
+
+add_task(function() {
+ let exceptionThrown;
+ try {
+ CustomizableUI.registerArea("area-996364-4", {type: CustomizableUI.TYPE_MENU_PANEL});
+ CustomizableUI.registerArea("area-996364-4", {type: CustomizableUI.TYPE_TOOLBAR});
+ } catch (ex) {
+ exceptionThrown = ex;
+ }
+ ok(exceptionThrown, "Exception expected, an area cannot change types: " + (exceptionThrown ? exceptionThrown : "[no exception]"));
+
+ CustomizableUI.unregisterArea("area-996364-4", true);
+});
+
+add_task(function() {
+ let exceptionThrown;
+ try {
+ CustomizableUI.registerArea("area-996899-1", { anchor: "PanelUI-menu-button",
+ type: CustomizableUI.TYPE_MENU_PANEL,
+ defaultPlacements: [] });
+ CustomizableUI.registerArea("area-996899-1", { anchor: "home-button",
+ type: CustomizableUI.TYPE_MENU_PANEL,
+ defaultPlacements: [] });
+ } catch (ex) {
+ exceptionThrown = ex;
+ }
+ ok(!exceptionThrown, "Changing anchors shouldn't throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]"));
+ CustomizableUI.unregisterArea("area-996899-1", true);
+});
+
+add_task(function() {
+ let exceptionThrown;
+ try {
+ CustomizableUI.registerArea("area-996899-2", { anchor: "PanelUI-menu-button",
+ type: CustomizableUI.TYPE_MENU_PANEL,
+ defaultPlacements: [] });
+ CustomizableUI.registerArea("area-996899-2", { anchor: "PanelUI-menu-button",
+ type: CustomizableUI.TYPE_MENU_PANEL,
+ defaultPlacements: ["feed-button"] });
+ } catch (ex) {
+ exceptionThrown = ex;
+ }
+ ok(!exceptionThrown, "Changing defaultPlacements shouldn't throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]"));
+ CustomizableUI.unregisterArea("area-996899-2", true);
+});
+
+add_task(function() {
+ let exceptionThrown;
+ try {
+ CustomizableUI.registerArea("area-996899-3", { legacy: true });
+ CustomizableUI.registerArea("area-996899-3", { legacy: false });
+ } catch (ex) {
+ exceptionThrown = ex;
+ }
+ ok(exceptionThrown, "Changing 'legacy' should throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]"));
+ CustomizableUI.unregisterArea("area-996899-3", true);
+});
+
+add_task(function() {
+ let exceptionThrown;
+ try {
+ CustomizableUI.registerArea("area-996899-4", { overflowable: true });
+ CustomizableUI.registerArea("area-996899-4", { overflowable: false });
+ } catch (ex) {
+ exceptionThrown = ex;
+ }
+ ok(exceptionThrown, "Changing 'overflowable' should throw an exception: " + (exceptionThrown ? exceptionThrown : "[no exception]"));
+ CustomizableUI.unregisterArea("area-996899-4", true);
+});
diff --git a/browser/components/customizableui/test/browser_996635_remove_non_widgets.js b/browser/components/customizableui/test/browser_996635_remove_non_widgets.js
new file mode 100644
index 000000000..14a446eec
--- /dev/null
+++ b/browser/components/customizableui/test/browser_996635_remove_non_widgets.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// NB: This is testing what happens if something that /isn't/ a customizable
+// widget gets used in CustomizableUI APIs. Don't use this as an example of
+// what should happen in a "normal" case or how you should use the API.
+function test() {
+ // First create a button that isn't customizable, and add it in the nav-bar,
+ // but not in the customizable part of it (the customization target) but
+ // next to the main (hamburger) menu button.
+ const buttonID = "Test-non-widget-non-removable-button";
+ let btn = document.createElement("toolbarbutton");
+ btn.id = buttonID;
+ btn.label = "Hi";
+ btn.setAttribute("style", "width: 20px; height: 20px; background-color: red");
+ document.getElementById("nav-bar").appendChild(btn);
+ registerCleanupFunction(function() {
+ btn.remove();
+ });
+
+ // Now try to add this non-customizable button to the tabstrip. This will
+ // update the internal bookkeeping (ie placements) information, but shouldn't
+ // move the node.
+ CustomizableUI.addWidgetToArea(buttonID, CustomizableUI.AREA_TABSTRIP);
+ let placement = CustomizableUI.getPlacementOfWidget(buttonID);
+ // Check our bookkeeping
+ ok(placement, "Button should be placed");
+ is(placement && placement.area, CustomizableUI.AREA_TABSTRIP, "Should be placed on tabstrip.");
+ // Check we didn't move the node.
+ is(btn.parentNode && btn.parentNode.id, "nav-bar", "Actual button should still be on navbar.");
+
+ // Now remove the node again. This should remove the bookkeeping, but again
+ // not affect the actual node.
+ CustomizableUI.removeWidgetFromArea(buttonID);
+ placement = CustomizableUI.getPlacementOfWidget(buttonID);
+ // Check our bookkeeping:
+ ok(!placement, "Button should no longer have a placement.");
+ // Check our node.
+ is(btn.parentNode && btn.parentNode.id, "nav-bar", "Actual button should still be on navbar.");
+}
+
diff --git a/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js b/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.js
new file mode 100644
index 000000000..2c5f0c79c
--- /dev/null
+++ b/browser/components/customizableui/test/browser_bootstrapped_custom_toolbar.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";
+
+requestLongerTimeout(2);
+
+const kTestBarID = "testBar";
+const kWidgetID = "characterencoding-button";
+
+function createTestBar(aLegacy) {
+ let testBar = document.createElement("toolbar");
+ testBar.id = kTestBarID;
+ testBar.setAttribute("customizable", "true");
+ CustomizableUI.registerArea(kTestBarID, {
+ type: CustomizableUI.TYPE_TOOLBAR,
+ legacy: aLegacy,
+ });
+ gNavToolbox.appendChild(testBar);
+ return testBar;
+}
+
+/**
+ * Helper function that does the following:
+ *
+ * 1) Creates a custom toolbar and registers it
+ * with CustomizableUI. Sets the legacy attribute
+ * of the object passed to registerArea to aLegacy.
+ * 2) Adds the widget with ID aWidgetID to that new
+ * toolbar.
+ * 3) Enters customize mode and makes sure that the
+ * widget is still in the right toolbar.
+ * 4) Exits customize mode, then removes and deregisters
+ * the custom toolbar.
+ * 5) Checks that the widget has no placement.
+ * 6) Re-adds and re-registers a custom toolbar with the same
+ * ID and options as the first one.
+ * 7) Enters customize mode and checks that the widget is
+ * properly back in the toolbar.
+ * 8) Exits customize mode, removes and de-registers the
+ * toolbar, and resets the toolbars to default.
+ */
+function checkRestoredPresence(aWidgetID, aLegacy) {
+ return Task.spawn(function* () {
+ let testBar = createTestBar(aLegacy);
+ CustomizableUI.addWidgetToArea(aWidgetID, kTestBarID);
+ let placement = CustomizableUI.getPlacementOfWidget(aWidgetID);
+ is(placement.area, kTestBarID,
+ "Expected " + aWidgetID + " to be in the test toolbar");
+
+ CustomizableUI.unregisterArea(testBar.id);
+ testBar.remove();
+
+ placement = CustomizableUI.getPlacementOfWidget(aWidgetID);
+ is(placement, null, "Expected " + aWidgetID + " to be in the palette");
+
+ testBar = createTestBar(aLegacy);
+
+ yield startCustomizing();
+ placement = CustomizableUI.getPlacementOfWidget(aWidgetID);
+ is(placement.area, kTestBarID,
+ "Expected " + aWidgetID + " to be in the test toolbar");
+ yield endCustomizing();
+
+ CustomizableUI.unregisterArea(testBar.id);
+ testBar.remove();
+
+ yield resetCustomization();
+ });
+}
+
+add_task(function* () {
+ yield checkRestoredPresence("downloads-button", false);
+ yield checkRestoredPresence("downloads-button", true);
+});
+
+add_task(function* () {
+ yield checkRestoredPresence("characterencoding-button", false);
+ yield checkRestoredPresence("characterencoding-button", true);
+});
diff --git a/browser/components/customizableui/test/browser_check_tooltips_in_navbar.js b/browser/components/customizableui/test/browser_check_tooltips_in_navbar.js
new file mode 100644
index 000000000..31dd42ad8
--- /dev/null
+++ b/browser/components/customizableui/test/browser_check_tooltips_in_navbar.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";
+
+add_task(function* check_tooltips_in_navbar() {
+ yield startCustomizing();
+ let homeButtonWrapper = document.getElementById("wrapper-home-button");
+ let homeButton = document.getElementById("home-button");
+ is(homeButtonWrapper.getAttribute("tooltiptext"), homeButton.getAttribute("label"), "the wrapper's tooltip should match the button's label");
+ ok(homeButtonWrapper.getAttribute("tooltiptext"), "the button should have tooltip text");
+ yield endCustomizing();
+});
diff --git a/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js b/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js
new file mode 100644
index 000000000..8e1950291
--- /dev/null
+++ b/browser/components/customizableui/test/browser_customizemode_contextmenu_menubuttonstate.js
@@ -0,0 +1,24 @@
+"use strict";
+
+add_task(function*() {
+ ok(!PanelUI.menuButton.hasAttribute("open"), "Menu button should not be 'pressed' outside customize mode");
+ yield startCustomizing();
+
+ is(PanelUI.menuButton.getAttribute("open"), "true", "Menu button should be 'pressed' when in customize mode");
+
+ let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+ let shownPromise = popupShown(contextMenu);
+ let newWindowButton = document.getElementById("wrapper-new-window-button");
+ EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
+ yield shownPromise;
+ is(PanelUI.menuButton.getAttribute("open"), "true", "Menu button should be 'pressed' when in customize mode after opening a context menu");
+
+ let hiddenContextPromise = popupHidden(contextMenu);
+ contextMenu.hidePopup();
+ yield hiddenContextPromise;
+ is(PanelUI.menuButton.getAttribute("open"), "true", "Menu button should be 'pressed' when in customize mode after hiding a context menu");
+ yield endCustomizing();
+
+ ok(!PanelUI.menuButton.hasAttribute("open"), "Menu button should not be 'pressed' after ending customize mode");
+});
+
diff --git a/browser/components/customizableui/test/browser_panel_toggle.js b/browser/components/customizableui/test/browser_panel_toggle.js
new file mode 100644
index 000000000..4c286fb85
--- /dev/null
+++ b/browser/components/customizableui/test/browser_panel_toggle.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";
+
+/**
+ * Test opening and closing the menu panel UI.
+ */
+
+// Show and hide the menu panel programmatically without an event (like UITour.jsm would)
+add_task(function*() {
+ let shownPromise = promisePanelShown(window);
+ PanelUI.show();
+ yield shownPromise;
+
+ is(PanelUI.panel.getAttribute("panelopen"), "true", "Check that panel has panelopen attribute");
+ is(PanelUI.panel.state, "open", "Check that panel state is 'open'");
+
+ let hiddenPromise = promisePanelHidden(window);
+ PanelUI.hide();
+ yield hiddenPromise;
+
+ ok(!PanelUI.panel.hasAttribute("panelopen"), "Check that panel doesn't have the panelopen attribute");
+ is(PanelUI.panel.state, "closed", "Check that panel state is 'closed'");
+});
+
+// Toggle the menu panel open and closed
+add_task(function*() {
+ let shownPromise = promisePanelShown(window);
+ PanelUI.toggle({type: "command"});
+ yield shownPromise;
+
+ is(PanelUI.panel.getAttribute("panelopen"), "true", "Check that panel has panelopen attribute");
+ is(PanelUI.panel.state, "open", "Check that panel state is 'open'");
+
+ let hiddenPromise = promisePanelHidden(window);
+ PanelUI.toggle({type: "command"});
+ yield hiddenPromise;
+
+ ok(!PanelUI.panel.hasAttribute("panelopen"), "Check that panel doesn't have the panelopen attribute");
+ is(PanelUI.panel.state, "closed", "Check that panel state is 'closed'");
+});
diff --git a/browser/components/customizableui/test/browser_switch_to_customize_mode.js b/browser/components/customizableui/test/browser_switch_to_customize_mode.js
new file mode 100644
index 000000000..459ea7a1c
--- /dev/null
+++ b/browser/components/customizableui/test/browser_switch_to_customize_mode.js
@@ -0,0 +1,34 @@
+"use strict";
+
+add_task(function*() {
+ yield startCustomizing();
+ is(gBrowser.tabs.length, 2, "Should have 2 tabs");
+
+ let paletteKidCount = document.getElementById("customization-palette").childElementCount;
+ let nonCustomizingTab = gBrowser.tabContainer.querySelector("tab:not([customizemode=true])");
+ let finishedCustomizing = BrowserTestUtils.waitForEvent(gNavToolbox, "aftercustomization");
+ yield BrowserTestUtils.switchTab(gBrowser, nonCustomizingTab);
+ yield finishedCustomizing;
+
+ let startedCount = 0;
+ let handler = e => startedCount++;
+ gNavToolbox.addEventListener("customizationstarting", handler);
+ yield startCustomizing();
+ CustomizableUI.removeWidgetFromArea("home-button");
+ yield gCustomizeMode.reset().catch(e => {
+ ok(false, "Threw an exception trying to reset after making modifications in customize mode: " + e);
+ });
+
+ let newKidCount = document.getElementById("customization-palette").childElementCount;
+ is(newKidCount, paletteKidCount, "Should have just as many items in the palette as before.");
+ yield endCustomizing();
+ is(startedCount, 1, "Should have only started once");
+ gNavToolbox.removeEventListener("customizationstarting", handler);
+ let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])");
+ for (let toolbar of customizableToolbars) {
+ ok(!toolbar.hasAttribute("customizing"), "Toolbar " + toolbar.id + " is no longer customizing");
+ }
+ let menuitem = document.getElementById("PanelUI-customize");
+ isnot(menuitem.getAttribute("label"), menuitem.getAttribute("exitLabel"), "Should have exited successfully");
+});
+
diff --git a/browser/components/customizableui/test/head.js b/browser/components/customizableui/test/head.js
new file mode 100644
index 000000000..7b8d84e20
--- /dev/null
+++ b/browser/components/customizableui/test/head.js
@@ -0,0 +1,499 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Avoid leaks by using tmp for imports...
+var tmp = {};
+Cu.import("resource://gre/modules/Promise.jsm", tmp);
+Cu.import("resource:///modules/CustomizableUI.jsm", tmp);
+Cu.import("resource://gre/modules/AppConstants.jsm", tmp);
+var {Promise, CustomizableUI, AppConstants} = tmp;
+
+var EventUtils = {};
+Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
+registerCleanupFunction(() => Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck"));
+
+// Remove temporary e10s related new window options in customize ui,
+// they break a lot of tests.
+CustomizableUI.destroyWidget("e10s-button");
+CustomizableUI.removeWidgetFromArea("e10s-button");
+
+var {synthesizeDragStart, synthesizeDrop} = EventUtils;
+
+const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const kTabEventFailureTimeoutInMs = 20000;
+
+function createDummyXULButton(id, label, win = window) {
+ let btn = document.createElementNS(kNSXUL, "toolbarbutton");
+ btn.id = id;
+ btn.setAttribute("label", label || id);
+ btn.className = "toolbarbutton-1 chromeclass-toolbar-additional";
+ win.gNavToolbox.palette.appendChild(btn);
+ return btn;
+}
+
+var gAddedToolbars = new Set();
+
+function createToolbarWithPlacements(id, placements = []) {
+ gAddedToolbars.add(id);
+ let tb = document.createElementNS(kNSXUL, "toolbar");
+ tb.id = id;
+ tb.setAttribute("customizable", "true");
+ CustomizableUI.registerArea(id, {
+ type: CustomizableUI.TYPE_TOOLBAR,
+ defaultPlacements: placements
+ });
+ gNavToolbox.appendChild(tb);
+ return tb;
+}
+
+function createOverflowableToolbarWithPlacements(id, placements) {
+ gAddedToolbars.add(id);
+
+ let tb = document.createElementNS(kNSXUL, "toolbar");
+ tb.id = id;
+ tb.setAttribute("customizationtarget", id + "-target");
+
+ let customizationtarget = document.createElementNS(kNSXUL, "hbox");
+ customizationtarget.id = id + "-target";
+ customizationtarget.setAttribute("flex", "1");
+ tb.appendChild(customizationtarget);
+
+ let overflowPanel = document.createElementNS(kNSXUL, "panel");
+ overflowPanel.id = id + "-overflow";
+ document.getElementById("mainPopupSet").appendChild(overflowPanel);
+
+ let overflowList = document.createElementNS(kNSXUL, "vbox");
+ overflowList.id = id + "-overflow-list";
+ overflowPanel.appendChild(overflowList);
+
+ let chevron = document.createElementNS(kNSXUL, "toolbarbutton");
+ chevron.id = id + "-chevron";
+ tb.appendChild(chevron);
+
+ CustomizableUI.registerArea(id, {
+ type: CustomizableUI.TYPE_TOOLBAR,
+ defaultPlacements: placements,
+ overflowable: true,
+ });
+
+ tb.setAttribute("customizable", "true");
+ tb.setAttribute("overflowable", "true");
+ tb.setAttribute("overflowpanel", overflowPanel.id);
+ tb.setAttribute("overflowtarget", overflowList.id);
+ tb.setAttribute("overflowbutton", chevron.id);
+
+ gNavToolbox.appendChild(tb);
+ return tb;
+}
+
+function removeCustomToolbars() {
+ CustomizableUI.reset();
+ for (let toolbarId of gAddedToolbars) {
+ CustomizableUI.unregisterArea(toolbarId, true);
+ let tb = document.getElementById(toolbarId);
+ if (tb.hasAttribute("overflowpanel")) {
+ let panel = document.getElementById(tb.getAttribute("overflowpanel"));
+ if (panel)
+ panel.remove();
+ }
+ tb.remove();
+ }
+ gAddedToolbars.clear();
+}
+
+function getToolboxCustomToolbarId(toolbarName) {
+ return "__customToolbar_" + toolbarName.replace(" ", "_");
+}
+
+function resetCustomization() {
+ return CustomizableUI.reset();
+}
+
+function isInDevEdition() {
+ return AppConstants.MOZ_DEV_EDITION;
+}
+
+function removeDeveloperButtonIfDevEdition(areaPanelPlacements) {
+ if (isInDevEdition()) {
+ areaPanelPlacements.splice(areaPanelPlacements.indexOf("developer-button"), 1);
+ }
+}
+
+function assertAreaPlacements(areaId, expectedPlacements) {
+ let actualPlacements = getAreaWidgetIds(areaId);
+ placementArraysEqual(areaId, actualPlacements, expectedPlacements);
+}
+
+function placementArraysEqual(areaId, actualPlacements, expectedPlacements) {
+ is(actualPlacements.length, expectedPlacements.length,
+ "Area " + areaId + " should have " + expectedPlacements.length + " items.");
+ let minItems = Math.min(expectedPlacements.length, actualPlacements.length);
+ for (let i = 0; i < minItems; i++) {
+ if (typeof expectedPlacements[i] == "string") {
+ is(actualPlacements[i], expectedPlacements[i],
+ "Item " + i + " in " + areaId + " should match expectations.");
+ } else if (expectedPlacements[i] instanceof RegExp) {
+ ok(expectedPlacements[i].test(actualPlacements[i]),
+ "Item " + i + " (" + actualPlacements[i] + ") in " +
+ areaId + " should match " + expectedPlacements[i]);
+ } else {
+ ok(false, "Unknown type of expected placement passed to " +
+ " assertAreaPlacements. Is your test broken?");
+ }
+ }
+}
+
+function todoAssertAreaPlacements(areaId, expectedPlacements) {
+ let actualPlacements = getAreaWidgetIds(areaId);
+ let isPassing = actualPlacements.length == expectedPlacements.length;
+ let minItems = Math.min(expectedPlacements.length, actualPlacements.length);
+ for (let i = 0; i < minItems; i++) {
+ if (typeof expectedPlacements[i] == "string") {
+ isPassing = isPassing && actualPlacements[i] == expectedPlacements[i];
+ } else if (expectedPlacements[i] instanceof RegExp) {
+ isPassing = isPassing && expectedPlacements[i].test(actualPlacements[i]);
+ } else {
+ ok(false, "Unknown type of expected placement passed to " +
+ " assertAreaPlacements. Is your test broken?");
+ }
+ }
+ todo(isPassing, "The area placements for " + areaId +
+ " should equal the expected placements.");
+}
+
+function getAreaWidgetIds(areaId) {
+ return CustomizableUI.getWidgetIdsInArea(areaId);
+}
+
+function simulateItemDrag(aToDrag, aTarget) {
+ synthesizeDrop(aToDrag.parentNode, aTarget);
+}
+
+function endCustomizing(aWindow=window) {
+ if (aWindow.document.documentElement.getAttribute("customizing") != "true") {
+ return true;
+ }
+ Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true);
+ let deferredEndCustomizing = Promise.defer();
+ function onCustomizationEnds() {
+ Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", false);
+ aWindow.gNavToolbox.removeEventListener("aftercustomization", onCustomizationEnds);
+ deferredEndCustomizing.resolve();
+ }
+ aWindow.gNavToolbox.addEventListener("aftercustomization", onCustomizationEnds);
+ aWindow.gCustomizeMode.exit();
+
+ return deferredEndCustomizing.promise;
+}
+
+function startCustomizing(aWindow=window) {
+ if (aWindow.document.documentElement.getAttribute("customizing") == "true") {
+ return null;
+ }
+ Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", true);
+ let deferred = Promise.defer();
+ function onCustomizing() {
+ aWindow.gNavToolbox.removeEventListener("customizationready", onCustomizing);
+ Services.prefs.setBoolPref("browser.uiCustomization.disableAnimation", false);
+ deferred.resolve();
+ }
+ aWindow.gNavToolbox.addEventListener("customizationready", onCustomizing);
+ aWindow.gCustomizeMode.enter();
+ return deferred.promise;
+}
+
+function promiseObserverNotified(aTopic) {
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(onNotification, aTopic);
+ deferred.resolve({subject: aSubject, data: aData});
+ }, aTopic, false);
+ return deferred.promise;
+}
+
+function openAndLoadWindow(aOptions, aWaitForDelayedStartup=false) {
+ let deferred = Promise.defer();
+ let win = OpenBrowserWindow(aOptions);
+ if (aWaitForDelayedStartup) {
+ Services.obs.addObserver(function onDS(aSubject, aTopic, aData) {
+ if (aSubject != win) {
+ return;
+ }
+ Services.obs.removeObserver(onDS, "browser-delayed-startup-finished");
+ deferred.resolve(win);
+ }, "browser-delayed-startup-finished", false);
+
+ } else {
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ deferred.resolve(win);
+ });
+ }
+ return deferred.promise;
+}
+
+function promiseWindowClosed(win) {
+ let deferred = Promise.defer();
+ win.addEventListener("unload", function onunload() {
+ win.removeEventListener("unload", onunload);
+ deferred.resolve();
+ });
+ win.close();
+ return deferred.promise;
+}
+
+function promisePanelShown(win) {
+ let panelEl = win.PanelUI.panel;
+ return promisePanelElementShown(win, panelEl);
+}
+
+function promiseOverflowShown(win) {
+ let panelEl = win.document.getElementById("widget-overflow");
+ return promisePanelElementShown(win, panelEl);
+}
+
+function promisePanelElementShown(win, aPanel) {
+ let deferred = Promise.defer();
+ let timeoutId = win.setTimeout(() => {
+ deferred.reject("Panel did not show within 20 seconds.");
+ }, 20000);
+ function onPanelOpen(e) {
+ aPanel.removeEventListener("popupshown", onPanelOpen);
+ win.clearTimeout(timeoutId);
+ deferred.resolve();
+ }
+ aPanel.addEventListener("popupshown", onPanelOpen);
+ return deferred.promise;
+}
+
+function promisePanelHidden(win) {
+ let panelEl = win.PanelUI.panel;
+ return promisePanelElementHidden(win, panelEl);
+}
+
+function promiseOverflowHidden(win) {
+ let panelEl = document.getElementById("widget-overflow");
+ return promisePanelElementHidden(win, panelEl);
+}
+
+function promisePanelElementHidden(win, aPanel) {
+ let deferred = Promise.defer();
+ let timeoutId = win.setTimeout(() => {
+ deferred.reject("Panel did not hide within 20 seconds.");
+ }, 20000);
+ function onPanelClose(e) {
+ aPanel.removeEventListener("popuphidden", onPanelClose);
+ win.clearTimeout(timeoutId);
+ deferred.resolve();
+ }
+ aPanel.addEventListener("popuphidden", onPanelClose);
+ return deferred.promise;
+}
+
+function isPanelUIOpen() {
+ return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing";
+}
+
+function subviewShown(aSubview) {
+ let deferred = Promise.defer();
+ let win = aSubview.ownerGlobal;
+ let timeoutId = win.setTimeout(() => {
+ deferred.reject("Subview (" + aSubview.id + ") did not show within 20 seconds.");
+ }, 20000);
+ function onViewShowing(e) {
+ aSubview.removeEventListener("ViewShowing", onViewShowing);
+ win.clearTimeout(timeoutId);
+ deferred.resolve();
+ }
+ aSubview.addEventListener("ViewShowing", onViewShowing);
+ return deferred.promise;
+}
+
+function subviewHidden(aSubview) {
+ let deferred = Promise.defer();
+ let win = aSubview.ownerGlobal;
+ let timeoutId = win.setTimeout(() => {
+ deferred.reject("Subview (" + aSubview.id + ") did not hide within 20 seconds.");
+ }, 20000);
+ function onViewHiding(e) {
+ aSubview.removeEventListener("ViewHiding", onViewHiding);
+ win.clearTimeout(timeoutId);
+ deferred.resolve();
+ }
+ aSubview.addEventListener("ViewHiding", onViewHiding);
+ return deferred.promise;
+}
+
+function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
+ function tryNow() {
+ tries++;
+ if (aConditionFn()) {
+ deferred.resolve();
+ } else if (tries < aMaxTries) {
+ tryAgain();
+ } else {
+ deferred.reject("Condition timed out: " + aConditionFn.toSource());
+ }
+ }
+ function tryAgain() {
+ setTimeout(tryNow, aCheckInterval);
+ }
+ let deferred = Promise.defer();
+ let tries = 0;
+ tryAgain();
+ return deferred.promise;
+}
+
+function waitFor(aTimeout=100) {
+ let deferred = Promise.defer();
+ setTimeout(() => deferred.resolve(), aTimeout);
+ return deferred.promise;
+}
+
+/**
+ * Starts a load in an existing tab and waits for it to finish (via some event).
+ *
+ * @param aTab The tab to load into.
+ * @param aUrl The url to load.
+ * @param aEventType The load event type to wait for. Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ */
+function promiseTabLoadEvent(aTab, aURL) {
+ let browser = aTab.linkedBrowser;
+
+ BrowserTestUtils.loadURI(browser, aURL);
+ return BrowserTestUtils.browserLoaded(browser);
+}
+
+/**
+ * Navigate back or forward in tab history and wait for it to finish.
+ *
+ * @param aDirection Number to indicate to move backward or forward in history.
+ * @param aConditionFn Function that returns the result of an evaluated condition
+ * that needs to be `true` to resolve the promise.
+ * @return {Promise} resolved when navigation has finished.
+ */
+function promiseTabHistoryNavigation(aDirection = -1, aConditionFn) {
+ let deferred = Promise.defer();
+
+ let timeoutId = setTimeout(() => {
+ gBrowser.removeEventListener("pageshow", listener, true);
+ deferred.reject("Pageshow did not happen within " + kTabEventFailureTimeoutInMs + "ms");
+ }, kTabEventFailureTimeoutInMs);
+
+ function listener(event) {
+ gBrowser.removeEventListener("pageshow", listener, true);
+ clearTimeout(timeoutId);
+
+ if (aConditionFn) {
+ waitForCondition(aConditionFn).then(() => deferred.resolve(),
+ aReason => deferred.reject(aReason));
+ } else {
+ deferred.resolve();
+ }
+ }
+ gBrowser.addEventListener("pageshow", listener, true);
+
+ content.history.go(aDirection);
+
+ return deferred.promise;
+}
+
+/**
+ * Wait for an attribute on a node to change
+ *
+ * @param aNode Node on which the mutation is expected
+ * @param aAttribute The attribute we're interested in
+ * @param aFilterFn A function to check if the new value is what we want.
+ * @return {Promise} resolved when the requisite mutation shows up.
+ */
+function promiseAttributeMutation(aNode, aAttribute, aFilterFn) {
+ return new Promise((resolve, reject) => {
+ info("waiting for mutation of attribute '" + aAttribute + "'.");
+ let obs = new MutationObserver((mutations) => {
+ for (let mut of mutations) {
+ let attr = mut.attributeName;
+ let newValue = mut.target.getAttribute(attr);
+ if (aFilterFn(newValue)) {
+ ok(true, "mutation occurred: attribute '" + attr + "' changed to '" + newValue + "' from '" + mut.oldValue + "'.");
+ obs.disconnect();
+ resolve();
+ } else {
+ info("Ignoring mutation that produced value " + newValue + " because of filter.");
+ }
+ }
+ });
+ obs.observe(aNode, {attributeFilter: [aAttribute]});
+ });
+}
+
+function popupShown(aPopup) {
+ return promisePopupEvent(aPopup, "shown");
+}
+
+function popupHidden(aPopup) {
+ return promisePopupEvent(aPopup, "hidden");
+}
+
+/**
+ * Returns a Promise that resolves when aPopup fires an event of type
+ * aEventType. Times out and rejects after 20 seconds.
+ *
+ * @param aPopup the popup to monitor for events.
+ * @param aEventSuffix the _suffix_ for the popup event type to watch for.
+ *
+ * Example usage:
+ * let popupShownPromise = promisePopupEvent(somePopup, "shown");
+ * // ... something that opens a popup
+ * yield popupShownPromise;
+ *
+ * let popupHiddenPromise = promisePopupEvent(somePopup, "hidden");
+ * // ... something that hides a popup
+ * yield popupHiddenPromise;
+ */
+function promisePopupEvent(aPopup, aEventSuffix) {
+ let deferred = Promise.defer();
+ let eventType = "popup" + aEventSuffix;
+
+ function onPopupEvent(e) {
+ aPopup.removeEventListener(eventType, onPopupEvent);
+ deferred.resolve();
+ }
+
+ aPopup.addEventListener(eventType, onPopupEvent);
+ return deferred.promise;
+}
+
+// This is a simpler version of the context menu check that
+// exists in contextmenu_common.js.
+function checkContextMenu(aContextMenu, aExpectedEntries, aWindow=window) {
+ let childNodes = [...aContextMenu.childNodes];
+ // Ignore hidden nodes:
+ childNodes = childNodes.filter((n) => !n.hidden);
+
+ for (let i = 0; i < childNodes.length; i++) {
+ let menuitem = childNodes[i];
+ try {
+ if (aExpectedEntries[i][0] == "---") {
+ is(menuitem.localName, "menuseparator", "menuseparator expected");
+ continue;
+ }
+
+ let selector = aExpectedEntries[i][0];
+ ok(menuitem.matches(selector), "menuitem should match " + selector + " selector");
+ let commandValue = menuitem.getAttribute("command");
+ let relatedCommand = commandValue ? aWindow.document.getElementById(commandValue) : null;
+ let menuItemDisabled = relatedCommand ?
+ relatedCommand.getAttribute("disabled") == "true" :
+ menuitem.getAttribute("disabled") == "true";
+ is(menuItemDisabled, !aExpectedEntries[i][1], "disabled state for " + selector);
+ } catch (e) {
+ ok(false, "Exception when checking context menu: " + e);
+ }
+ }
+}
diff --git a/browser/components/customizableui/test/support/feeds_test_page.html b/browser/components/customizableui/test/support/feeds_test_page.html
new file mode 100644
index 000000000..be78e4dff
--- /dev/null
+++ b/browser/components/customizableui/test/support/feeds_test_page.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <title>Feeds test page</title>
+ <link rel="alternate" type="application/rss+xml" href="test-feed.xml" title="Test feed">
+</head>
+
+<body>
+ This is a test page for feeds
+</body>
+</html>
diff --git a/browser/components/customizableui/test/support/test-feed.xml b/browser/components/customizableui/test/support/test-feed.xml
new file mode 100644
index 000000000..0e700b6d8
--- /dev/null
+++ b/browser/components/customizableui/test/support/test-feed.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
+
+ <entry>
+
+ <title>Item</title>
+ <link href="http://example.org/first"/>
+ <id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/browser/components/customizableui/test/support/test_967000_charEncoding_page.html b/browser/components/customizableui/test/support/test_967000_charEncoding_page.html
new file mode 100644
index 000000000..c8d35115c
--- /dev/null
+++ b/browser/components/customizableui/test/support/test_967000_charEncoding_page.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test page</title>
+ </head>
+
+ <body>
+ This is a test page
+ </body>
+</html>
diff --git a/browser/components/dirprovider/DirectoryProvider.cpp b/browser/components/dirprovider/DirectoryProvider.cpp
new file mode 100644
index 000000000..7b4f81c7d
--- /dev/null
+++ b/browser/components/dirprovider/DirectoryProvider.cpp
@@ -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/. */
+
+#include "nsIDirectoryService.h"
+#include "DirectoryProvider.h"
+
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMArray.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "nsIPrefLocalizedString.h"
+
+namespace mozilla {
+namespace browser {
+
+NS_IMPL_ISUPPORTS(DirectoryProvider,
+ nsIDirectoryServiceProvider,
+ nsIDirectoryServiceProvider2)
+
+NS_IMETHODIMP
+DirectoryProvider::GetFile(const char *aKey, bool *aPersist, nsIFile* *aResult)
+{
+ return NS_ERROR_FAILURE;
+}
+
+static void
+AppendFileKey(const char *key, nsIProperties* aDirSvc,
+ nsCOMArray<nsIFile> &array)
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aDirSvc->Get(key, NS_GET_IID(nsIFile), getter_AddRefs(file));
+ if (NS_FAILED(rv))
+ return;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return;
+
+ array.AppendObject(file);
+}
+
+// Appends the distribution-specific search engine directories to the
+// array. The directory structure is as follows:
+
+// appdir/
+// \- distribution/
+// \- searchplugins/
+// |- common/
+// \- locale/
+// |- <locale 1>/
+// ...
+// \- <locale N>/
+
+// common engines are loaded for all locales. If there is no locale
+// directory for the current locale, there is a pref:
+// "distribution.searchplugins.defaultLocale"
+// which specifies a default locale to use.
+
+static void
+AppendDistroSearchDirs(nsIProperties* aDirSvc, nsCOMArray<nsIFile> &array)
+{
+ nsCOMPtr<nsIFile> searchPlugins;
+ nsresult rv = aDirSvc->Get(XRE_APP_DISTRIBUTION_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(searchPlugins));
+ if (NS_FAILED(rv))
+ return;
+ searchPlugins->AppendNative(NS_LITERAL_CSTRING("searchplugins"));
+
+ bool exists;
+ rv = searchPlugins->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return;
+
+ nsCOMPtr<nsIFile> commonPlugins;
+ rv = searchPlugins->Clone(getter_AddRefs(commonPlugins));
+ if (NS_SUCCEEDED(rv)) {
+ commonPlugins->AppendNative(NS_LITERAL_CSTRING("common"));
+ rv = commonPlugins->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ array.AppendObject(commonPlugins);
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+
+ nsCOMPtr<nsIFile> localePlugins;
+ rv = searchPlugins->Clone(getter_AddRefs(localePlugins));
+ if (NS_FAILED(rv))
+ return;
+
+ localePlugins->AppendNative(NS_LITERAL_CSTRING("locale"));
+
+ nsCString defLocale;
+ rv = prefs->GetCharPref("distribution.searchplugins.defaultLocale",
+ getter_Copies(defLocale));
+ if (NS_SUCCEEDED(rv)) {
+
+ nsCOMPtr<nsIFile> defLocalePlugins;
+ rv = localePlugins->Clone(getter_AddRefs(defLocalePlugins));
+ if (NS_SUCCEEDED(rv)) {
+
+ defLocalePlugins->AppendNative(defLocale);
+ rv = defLocalePlugins->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ array.AppendObject(defLocalePlugins);
+ return; // all done
+ }
+ }
+ }
+
+ // we didn't have a defaultLocale, use the user agent locale
+ nsCString locale;
+ nsCOMPtr<nsIPrefLocalizedString> prefString;
+ rv = prefs->GetComplexValue("general.useragent.locale",
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefString));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString wLocale;
+ prefString->GetData(getter_Copies(wLocale));
+ CopyUTF16toUTF8(wLocale, locale);
+ } else {
+ rv = prefs->GetCharPref("general.useragent.locale", getter_Copies(locale));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+
+ nsCOMPtr<nsIFile> curLocalePlugins;
+ rv = localePlugins->Clone(getter_AddRefs(curLocalePlugins));
+ if (NS_SUCCEEDED(rv)) {
+
+ curLocalePlugins->AppendNative(locale);
+ rv = curLocalePlugins->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ array.AppendObject(curLocalePlugins);
+ return; // all done
+ }
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+DirectoryProvider::GetFiles(const char *aKey, nsISimpleEnumerator* *aResult)
+{
+ /**
+ * We want to preserve the following order, since the search service loads
+ * engines in first-loaded-wins order.
+ * - distro search plugin locations (Loaded by the search service using
+ * NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)
+ *
+ * - engines shipped in chrome (Loaded from jar files by the search
+ * service)
+ *
+ * Then other locations, from NS_APP_SEARCH_DIR_LIST:
+ * - extension search plugin locations (prepended below using
+ * NS_NewUnionEnumerator)
+ * - user search plugin locations (profile)
+ */
+
+ nsresult rv;
+
+ if (!strcmp(aKey, NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)) {
+ nsCOMPtr<nsIProperties> dirSvc
+ (do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
+ if (!dirSvc)
+ return NS_ERROR_FAILURE;
+
+ nsCOMArray<nsIFile> distroFiles;
+ AppendDistroSearchDirs(dirSvc, distroFiles);
+
+ return NS_NewArrayEnumerator(aResult, distroFiles);
+ }
+
+ if (!strcmp(aKey, NS_APP_SEARCH_DIR_LIST)) {
+ nsCOMPtr<nsIProperties> dirSvc
+ (do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
+ if (!dirSvc)
+ return NS_ERROR_FAILURE;
+
+ nsCOMArray<nsIFile> baseFiles;
+
+ AppendFileKey(NS_APP_USER_SEARCH_DIR, dirSvc, baseFiles);
+
+ nsCOMPtr<nsISimpleEnumerator> baseEnum;
+ rv = NS_NewArrayEnumerator(getter_AddRefs(baseEnum), baseFiles);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> list;
+ rv = dirSvc->Get(XRE_EXTENSIONS_DIR_LIST,
+ NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(list));
+ if (NS_FAILED(rv))
+ return rv;
+
+ static char const *const kAppendSPlugins[] = {"searchplugins", nullptr};
+
+ nsCOMPtr<nsISimpleEnumerator> extEnum =
+ new AppendingEnumerator(list, kAppendSPlugins);
+ if (!extEnum)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_NewUnionEnumerator(aResult, extEnum, baseEnum);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMPL_ISUPPORTS(DirectoryProvider::AppendingEnumerator, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+DirectoryProvider::AppendingEnumerator::HasMoreElements(bool *aResult)
+{
+ *aResult = mNext ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DirectoryProvider::AppendingEnumerator::GetNext(nsISupports* *aResult)
+{
+ if (aResult)
+ NS_ADDREF(*aResult = mNext);
+
+ mNext = nullptr;
+
+ nsresult rv;
+
+ // Ignore all errors
+
+ bool more;
+ while (NS_SUCCEEDED(mBase->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> nextbasesupp;
+ mBase->GetNext(getter_AddRefs(nextbasesupp));
+
+ nsCOMPtr<nsIFile> nextbase(do_QueryInterface(nextbasesupp));
+ if (!nextbase)
+ continue;
+
+ nextbase->Clone(getter_AddRefs(mNext));
+ if (!mNext)
+ continue;
+
+ char const *const * i = mAppendList;
+ while (*i) {
+ mNext->AppendNative(nsDependentCString(*i));
+ ++i;
+ }
+
+ bool exists;
+ rv = mNext->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ break;
+
+ mNext = nullptr;
+ }
+
+ return NS_OK;
+}
+
+DirectoryProvider::AppendingEnumerator::AppendingEnumerator
+ (nsISimpleEnumerator* aBase,
+ char const *const *aAppendList) :
+ mBase(aBase),
+ mAppendList(aAppendList)
+{
+ // Initialize mNext to begin.
+ GetNext(nullptr);
+}
+
+} // namespace browser
+} // namespace mozilla
diff --git a/browser/components/dirprovider/DirectoryProvider.h b/browser/components/dirprovider/DirectoryProvider.h
new file mode 100644
index 000000000..43fa85ab9
--- /dev/null
+++ b/browser/components/dirprovider/DirectoryProvider.h
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DirectoryProvider_h__
+#define DirectoryProvider_h__
+
+#include "nsIDirectoryService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIFile.h"
+#include "mozilla/Attributes.h"
+
+#define NS_BROWSERDIRECTORYPROVIDER_CONTRACTID \
+ "@mozilla.org/browser/directory-provider;1"
+
+namespace mozilla {
+namespace browser {
+
+class DirectoryProvider final : public nsIDirectoryServiceProvider2
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
+
+private:
+ ~DirectoryProvider() {}
+
+ class AppendingEnumerator final : public nsISimpleEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ AppendingEnumerator(nsISimpleEnumerator* aBase,
+ char const *const *aAppendList);
+
+ private:
+ ~AppendingEnumerator() {}
+
+ nsCOMPtr<nsISimpleEnumerator> mBase;
+ char const *const *const mAppendList;
+ nsCOMPtr<nsIFile> mNext;
+ };
+};
+
+} // namespace browser
+} // namespace mozilla
+
+#endif // DirectoryProvider_h__
diff --git a/browser/components/dirprovider/moz.build b/browser/components/dirprovider/moz.build
new file mode 100644
index 000000000..799d4deb3
--- /dev/null
+++ b/browser/components/dirprovider/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.browser += [
+ 'DirectoryProvider.h',
+]
+
+SOURCES += [
+ 'DirectoryProvider.cpp',
+]
+
+FINAL_LIBRARY = 'browsercomps'
+
+LOCAL_INCLUDES += [
+ '../build'
+]
diff --git a/browser/components/dirprovider/tests/unit/.eslintrc.js b/browser/components/dirprovider/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/dirprovider/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/distribution.js b/browser/components/distribution.js
new file mode 100644
index 000000000..589129a5a
--- /dev/null
+++ b/browser/components/distribution.js
@@ -0,0 +1,504 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [ "DistributionCustomizer" ];
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
+ "distribution-customization-complete";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+this.DistributionCustomizer = function DistributionCustomizer() {
+ // For parallel xpcshell testing purposes allow loading the distribution.ini
+ // file from the profile folder through an hidden pref.
+ let loadFromProfile = false;
+ try {
+ loadFromProfile = Services.prefs.getBoolPref("distribution.testing.loadFromProfile");
+ } catch (ex) {}
+ let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ try {
+ let iniFile = loadFromProfile ? dirSvc.get("ProfD", Ci.nsIFile)
+ : dirSvc.get("XREAppDist", Ci.nsIFile);
+ if (loadFromProfile) {
+ iniFile.leafName = "distribution";
+ }
+ iniFile.append("distribution.ini");
+ if (iniFile.exists())
+ this._iniFile = iniFile;
+ } catch (ex) {}
+}
+
+DistributionCustomizer.prototype = {
+ _iniFile: null,
+
+ get _ini() {
+ let ini = null;
+ try {
+ if (this._iniFile) {
+ ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+ getService(Ci.nsIINIParserFactory).
+ createINIParser(this._iniFile);
+ }
+ } catch (e) {
+ // Unable to parse INI.
+ Cu.reportError("Unable to parse distribution.ini");
+ }
+ this.__defineGetter__("_ini", () => ini);
+ return this._ini;
+ },
+
+ get _locale() {
+ let locale;
+ try {
+ locale = this._prefs.getCharPref("general.useragent.locale");
+ }
+ catch (e) {
+ locale = "en-US";
+ }
+ this.__defineGetter__("_locale", () => locale);
+ return this._locale;
+ },
+
+ get _language() {
+ let language = this._locale.split("-")[0];
+ this.__defineGetter__("_language", () => language);
+ return this._language;
+ },
+
+ get _prefSvc() {
+ let svc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ this.__defineGetter__("_prefSvc", () => svc);
+ return this._prefSvc;
+ },
+
+ get _prefs() {
+ let branch = this._prefSvc.getBranch(null);
+ this.__defineGetter__("_prefs", () => branch);
+ return this._prefs;
+ },
+
+ get _ioSvc() {
+ let svc = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ this.__defineGetter__("_ioSvc", () => svc);
+ return this._ioSvc;
+ },
+
+ _makeURI: function DIST__makeURI(spec) {
+ return this._ioSvc.newURI(spec, null, null);
+ },
+
+ _parseBookmarksSection: Task.async(function* (parentGuid, section) {
+ let keys = Array.from(enumerate(this._ini.getKeys(section))).sort();
+ let re = /^item\.(\d+)\.(\w+)\.?(\w*)/;
+ let items = {};
+ let defaultIndex = -1;
+ let maxIndex = -1;
+
+ for (let key of keys) {
+ let m = re.exec(key);
+ if (m) {
+ let [, itemIndex, iprop, ilocale] = m;
+ itemIndex = parseInt(itemIndex);
+
+ if (ilocale)
+ continue;
+
+ if (keys.indexOf(key + "." + this._locale) >= 0) {
+ key += "." + this._locale;
+ } else if (keys.indexOf(key + "." + this._language) >= 0) {
+ key += "." + this._language;
+ }
+
+ if (!items[itemIndex])
+ items[itemIndex] = {};
+ items[itemIndex][iprop] = this._ini.getString(section, key);
+
+ if (iprop == "type" && items[itemIndex]["type"] == "default")
+ defaultIndex = itemIndex;
+
+ if (maxIndex < itemIndex)
+ maxIndex = itemIndex;
+ } else {
+ dump(`Key did not match: ${key}\n`);
+ }
+ }
+
+ let prependIndex = 0;
+ for (let itemIndex = 0; itemIndex <= maxIndex; itemIndex++) {
+ if (!items[itemIndex])
+ continue;
+
+ let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ let item = items[itemIndex];
+
+ switch (item.type) {
+ case "default":
+ break;
+
+ case "folder":
+ if (itemIndex < defaultIndex)
+ index = prependIndex++;
+
+ let folder = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid, index, title: item.title
+ });
+
+ yield this._parseBookmarksSection(folder.guid,
+ "BookmarksFolder-" + item.folderId);
+
+ if (item.description) {
+ let folderId = yield PlacesUtils.promiseItemId(folder.guid);
+ PlacesUtils.annotations.setItemAnnotation(folderId,
+ "bookmarkProperties/description",
+ item.description, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ break;
+
+ case "separator":
+ if (itemIndex < defaultIndex)
+ index = prependIndex++;
+
+ yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid, index
+ });
+ break;
+
+ case "livemark":
+ if (itemIndex < defaultIndex)
+ index = prependIndex++;
+
+ // Don't bother updating the livemark contents on creation.
+ let parentId = yield PlacesUtils.promiseItemId(parentGuid);
+ yield PlacesUtils.livemarks.addLivemark({
+ feedURI: this._makeURI(item.feedLink),
+ siteURI: this._makeURI(item.siteLink),
+ parentId, index, title: item.title
+ });
+ break;
+
+ case "bookmark":
+ default:
+ if (itemIndex < defaultIndex)
+ index = prependIndex++;
+
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid, index, title: item.title, url: item.link
+ });
+
+ if (item.description) {
+ let bmId = yield PlacesUtils.promiseItemId(bm.guid);
+ PlacesUtils.annotations.setItemAnnotation(bmId,
+ "bookmarkProperties/description",
+ item.description, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ if (item.icon && item.iconData) {
+ try {
+ let faviconURI = this._makeURI(item.icon);
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ faviconURI, item.iconData, 0,
+ Services.scriptSecurityManager.getSystemPrincipal());
+
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ this._makeURI(item.link), faviconURI, false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+
+ if (item.keyword) {
+ try {
+ yield PlacesUtils.keywords.insert({ keyword: item.keyword,
+ url: item.link });
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+
+ break;
+ }
+ }
+ }),
+
+ _newProfile: false,
+ _customizationsApplied: false,
+ applyCustomizations: function DIST_applyCustomizations() {
+ this._customizationsApplied = true;
+
+ if (!Services.prefs.prefHasUserValue("browser.migration.version"))
+ this._newProfile = true;
+
+ if (!this._ini)
+ return this._checkCustomizationComplete();
+
+ // nsPrefService loads very early. Reload prefs so we can set
+ // distribution defaults during the prefservice:after-app-defaults
+ // notification (see applyPrefDefaults below)
+ this._prefSvc.QueryInterface(Ci.nsIObserver);
+ this._prefSvc.observe(null, "reload-default-prefs", null);
+ },
+
+ _bookmarksApplied: false,
+ applyBookmarks: Task.async(function* () {
+ yield this._doApplyBookmarks();
+ this._bookmarksApplied = true;
+ this._checkCustomizationComplete();
+ }),
+
+ _doApplyBookmarks: Task.async(function* () {
+ if (!this._ini)
+ return;
+
+ let sections = enumToObject(this._ini.getSections());
+
+ // The global section, and several of its fields, is required
+ // (we also check here to be consistent with applyPrefDefaults below)
+ if (!sections["Global"])
+ return;
+
+ let globalPrefs = enumToObject(this._ini.getKeys("Global"));
+ if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
+ return;
+
+ let bmProcessedPref;
+ try {
+ bmProcessedPref = this._ini.getString("Global",
+ "bookmarks.initialized.pref");
+ }
+ catch (e) {
+ bmProcessedPref = "distribution." +
+ this._ini.getString("Global", "id") + ".bookmarksProcessed";
+ }
+
+ let bmProcessed = false;
+ try {
+ bmProcessed = this._prefs.getBoolPref(bmProcessedPref);
+ }
+ catch (e) {}
+
+ if (!bmProcessed) {
+ if (sections["BookmarksMenu"])
+ yield this._parseBookmarksSection(PlacesUtils.bookmarks.menuGuid,
+ "BookmarksMenu");
+ if (sections["BookmarksToolbar"])
+ yield this._parseBookmarksSection(PlacesUtils.bookmarks.toolbarGuid,
+ "BookmarksToolbar");
+ this._prefs.setBoolPref(bmProcessedPref, true);
+ }
+ }),
+
+ _prefDefaultsApplied: false,
+ applyPrefDefaults: function DIST_applyPrefDefaults() {
+ this._prefDefaultsApplied = true;
+ if (!this._ini)
+ return this._checkCustomizationComplete();
+
+ let sections = enumToObject(this._ini.getSections());
+
+ // The global section, and several of its fields, is required
+ if (!sections["Global"])
+ return this._checkCustomizationComplete();
+ let globalPrefs = enumToObject(this._ini.getKeys("Global"));
+ if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
+ return this._checkCustomizationComplete();
+
+ let defaults = new Preferences({defaultBranch: true});
+
+ // Global really contains info we set as prefs. They're only
+ // separate because they are "special" (read: required)
+
+ defaults.set("distribution.id", this._ini.getString("Global", "id"));
+ defaults.set("distribution.version", this._ini.getString("Global", "version"));
+
+ let partnerAbout;
+ try {
+ if (globalPrefs["about." + this._locale]) {
+ partnerAbout = this._ini.getString("Global", "about." + this._locale);
+ } else if (globalPrefs["about." + this._language]) {
+ partnerAbout = this._ini.getString("Global", "about." + this._language);
+ } else {
+ partnerAbout = this._ini.getString("Global", "about");
+ }
+ defaults.set("distribution.about", partnerAbout);
+ } catch (e) {
+ /* ignore bad prefs due to bug 895473 and move on */
+ Cu.reportError(e);
+ }
+
+ var usedPreferences = [];
+
+ if (sections["Preferences-" + this._locale]) {
+ for (let key of enumerate(this._ini.getKeys("Preferences-" + this._locale))) {
+ try {
+ let value = this._ini.getString("Preferences-" + this._locale, key);
+ if (value) {
+ defaults.set(key, parseValue(value));
+ }
+ usedPreferences.push(key);
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ if (sections["Preferences-" + this._language]) {
+ for (let key of enumerate(this._ini.getKeys("Preferences-" + this._language))) {
+ if (usedPreferences.indexOf(key) > -1) {
+ continue;
+ }
+ try {
+ let value = this._ini.getString("Preferences-" + this._language, key);
+ if (value) {
+ defaults.set(key, parseValue(value));
+ }
+ usedPreferences.push(key);
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ if (sections["Preferences"]) {
+ for (let key of enumerate(this._ini.getKeys("Preferences"))) {
+ if (usedPreferences.indexOf(key) > -1) {
+ continue;
+ }
+ try {
+ let value = this._ini.getString("Preferences", key);
+ if (value) {
+ value = value.replace(/%LOCALE%/g, this._locale);
+ value = value.replace(/%LANGUAGE%/g, this._language);
+ defaults.set(key, parseValue(value));
+ }
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"].
+ createInstance(Ci.nsIPrefLocalizedString);
+
+ var usedLocalizablePreferences = [];
+
+ if (sections["LocalizablePreferences-" + this._locale]) {
+ for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) {
+ try {
+ let value = this._ini.getString("LocalizablePreferences-" + this._locale, key);
+ if (value) {
+ value = parseValue(value);
+ localizedStr.data = "data:text/plain," + key + "=" + value;
+ defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
+ }
+ usedLocalizablePreferences.push(key);
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ if (sections["LocalizablePreferences-" + this._language]) {
+ for (let key of enumerate(this._ini.getKeys("LocalizablePreferences-" + this._language))) {
+ if (usedLocalizablePreferences.indexOf(key) > -1) {
+ continue;
+ }
+ try {
+ let value = this._ini.getString("LocalizablePreferences-" + this._language, key);
+ if (value) {
+ value = parseValue(value);
+ localizedStr.data = "data:text/plain," + key + "=" + value;
+ defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
+ }
+ usedLocalizablePreferences.push(key);
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ if (sections["LocalizablePreferences"]) {
+ for (let key of enumerate(this._ini.getKeys("LocalizablePreferences"))) {
+ if (usedLocalizablePreferences.indexOf(key) > -1) {
+ continue;
+ }
+ try {
+ let value = this._ini.getString("LocalizablePreferences", key);
+ if (value) {
+ value = parseValue(value);
+ value = value.replace(/%LOCALE%/g, this._locale);
+ value = value.replace(/%LANGUAGE%/g, this._language);
+ localizedStr.data = "data:text/plain," + key + "=" + value;
+ }
+ defaults._prefBranch.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ return this._checkCustomizationComplete();
+ },
+
+ _checkCustomizationComplete: function DIST__checkCustomizationComplete() {
+ const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
+
+ if (this._newProfile) {
+ let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+
+ try {
+ var showPersonalToolbar = Services.prefs.getBoolPref("browser.showPersonalToolbar");
+ if (showPersonalToolbar) {
+ xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
+ }
+ } catch (e) {}
+ try {
+ var showMenubar = Services.prefs.getBoolPref("browser.showMenubar");
+ if (showMenubar) {
+ xulStore.setValue(BROWSER_DOCURL, "toolbar-menubar", "autohide", "false");
+ }
+ } catch (e) {}
+ }
+
+ let prefDefaultsApplied = this._prefDefaultsApplied || !this._ini;
+ if (this._customizationsApplied && this._bookmarksApplied &&
+ prefDefaultsApplied) {
+ let os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null);
+ }
+ }
+};
+
+function parseValue(value) {
+ try {
+ value = JSON.parse(value);
+ } catch (e) {
+ // JSON.parse catches numbers and booleans.
+ // Anything else, we assume is a string.
+ // Remove the quotes that aren't needed anymore.
+ value = value.replace(/^"/, "");
+ value = value.replace(/"$/, "");
+ }
+ return value;
+}
+
+function* enumerate(UTF8Enumerator) {
+ while (UTF8Enumerator.hasMore())
+ yield UTF8Enumerator.getNext();
+}
+
+function enumToObject(UTF8Enumerator) {
+ let ret = {};
+ for (let i of enumerate(UTF8Enumerator))
+ ret[i] = 1;
+ return ret;
+}
diff --git a/browser/components/downloads/DownloadsCommon.jsm b/browser/components/downloads/DownloadsCommon.jsm
new file mode 100644
index 000000000..b6684817d
--- /dev/null
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -0,0 +1,1553 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadsCommon",
+];
+
+/**
+ * Handles the Downloads panel shared methods and data access.
+ *
+ * This file includes the following constructors and global objects:
+ *
+ * DownloadsCommon
+ * This object is exposed directly to the consumers of this JavaScript module,
+ * and provides shared methods for all the instances of the user interface.
+ *
+ * DownloadsData
+ * Retrieves the list of past and completed downloads from the underlying
+ * Downloads API data, and provides asynchronous notifications allowing
+ * to build a consistent view of the available data.
+ *
+ * DownloadsIndicatorData
+ * This object registers itself with DownloadsData as a view, and transforms the
+ * notifications it receives into overall status data, that is then broadcast to
+ * the registered download status indicators.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const { 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, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
+ "resource://gre/modules/DownloadUIHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+ "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm")
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "DownloadsLogger", () => {
+ let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+ let consoleOptions = {
+ maxLogLevelPref: "browser.download.loglevel",
+ prefix: "Downloads"
+ };
+ return new ConsoleAPI(consoleOptions);
+});
+
+const nsIDM = Ci.nsIDownloadManager;
+
+const kDownloadsStringBundleUrl =
+ "chrome://browser/locale/downloads/downloads.properties";
+
+const kDownloadsStringsRequiringFormatting = {
+ sizeWithUnits: true,
+ shortTimeLeftSeconds: true,
+ shortTimeLeftMinutes: true,
+ shortTimeLeftHours: true,
+ shortTimeLeftDays: true,
+ statusSeparator: true,
+ statusSeparatorBeforeNumber: true,
+ fileExecutableSecurityWarning: true
+};
+
+const kDownloadsStringsRequiringPluralForm = {
+ otherDownloads2: true
+};
+
+const kPartialDownloadSuffix = ".part";
+
+const kPrefBranch = Services.prefs.getBranch("browser.download.");
+
+var PrefObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ getPref(name) {
+ try {
+ switch (typeof this.prefs[name]) {
+ case "boolean":
+ return kPrefBranch.getBoolPref(name);
+ }
+ } catch (ex) { }
+ return this.prefs[name];
+ },
+ observe(aSubject, aTopic, aData) {
+ if (this.prefs.hasOwnProperty(aData)) {
+ delete this[aData];
+ return this[aData] = this.getPref(aData);
+ }
+ },
+ register(prefs) {
+ this.prefs = prefs;
+ kPrefBranch.addObserver("", this, true);
+ for (let key in prefs) {
+ let name = key;
+ XPCOMUtils.defineLazyGetter(this, name, function () {
+ return PrefObserver.getPref(name);
+ });
+ }
+ },
+};
+
+PrefObserver.register({
+ // prefName: defaultValue
+ animateNotifications: true,
+ showPanelDropmarker: true,
+});
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsCommon
+
+/**
+ * This object is exposed directly to the consumers of this JavaScript module,
+ * and provides shared methods for all the instances of the user interface.
+ */
+this.DownloadsCommon = {
+ ATTENTION_NONE: "",
+ ATTENTION_SUCCESS: "success",
+ ATTENTION_WARNING: "warning",
+ ATTENTION_SEVERE: "severe",
+
+ /**
+ * Returns an object whose keys are the string names from the downloads string
+ * bundle, and whose values are either the translated strings or functions
+ * returning formatted strings.
+ */
+ get strings() {
+ let strings = {};
+ let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
+ let enumerator = sb.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ let stringName = string.key;
+ if (stringName in kDownloadsStringsRequiringFormatting) {
+ strings[stringName] = function () {
+ // Convert "arguments" to a real array before calling into XPCOM.
+ return sb.formatStringFromName(stringName,
+ Array.slice(arguments, 0),
+ arguments.length);
+ };
+ } else if (stringName in kDownloadsStringsRequiringPluralForm) {
+ strings[stringName] = function (aCount) {
+ // Convert "arguments" to a real array before calling into XPCOM.
+ let formattedString = sb.formatStringFromName(stringName,
+ Array.slice(arguments, 0),
+ arguments.length);
+ return PluralForm.get(aCount, formattedString);
+ };
+ } else {
+ strings[stringName] = string.value;
+ }
+ }
+ delete this.strings;
+ return this.strings = strings;
+ },
+
+ /**
+ * Generates a very short string representing the given time left.
+ *
+ * @param aSeconds
+ * Value to be formatted. It represents the number of seconds, it must
+ * be positive but does not need to be an integer.
+ *
+ * @return Formatted string, for example "30s" or "2h". The returned value is
+ * maximum three characters long, at least in English.
+ */
+ formatTimeLeft(aSeconds) {
+ // Decide what text to show for the time
+ let seconds = Math.round(aSeconds);
+ if (!seconds) {
+ return "";
+ } else if (seconds <= 30) {
+ return DownloadsCommon.strings["shortTimeLeftSeconds"](seconds);
+ }
+ let minutes = Math.round(aSeconds / 60);
+ if (minutes < 60) {
+ return DownloadsCommon.strings["shortTimeLeftMinutes"](minutes);
+ }
+ let hours = Math.round(minutes / 60);
+ if (hours < 48) { // two days
+ return DownloadsCommon.strings["shortTimeLeftHours"](hours);
+ }
+ let days = Math.round(hours / 24);
+ return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99));
+ },
+
+ /**
+ * Indicates whether we should show visual notification on the indicator
+ * when a download event is triggered.
+ */
+ get animateNotifications() {
+ return PrefObserver.animateNotifications;
+ },
+
+ /**
+ * Indicates whether we should show the dropmarker in the Downloads Panel.
+ */
+ get showPanelDropmarker() {
+ return PrefObserver.showPanelDropmarker;
+ },
+
+ /**
+ * Get access to one of the DownloadsData or PrivateDownloadsData objects,
+ * depending on the privacy status of the window in question.
+ *
+ * @param aWindow
+ * The browser window which owns the download button.
+ */
+ getData(aWindow) {
+ if (PrivateBrowsingUtils.isContentWindowPrivate(aWindow)) {
+ return PrivateDownloadsData;
+ } else {
+ return DownloadsData;
+ }
+ },
+
+ /**
+ * Initializes the Downloads back-end and starts receiving events for both the
+ * private and non-private downloads data objects.
+ */
+ initializeAllDataLinks() {
+ DownloadsData.initializeDataLink();
+ PrivateDownloadsData.initializeDataLink();
+ },
+
+ /**
+ * Get access to one of the DownloadsIndicatorData or
+ * PrivateDownloadsIndicatorData objects, depending on the privacy status of
+ * the window in question.
+ */
+ getIndicatorData(aWindow) {
+ if (PrivateBrowsingUtils.isContentWindowPrivate(aWindow)) {
+ return PrivateDownloadsIndicatorData;
+ } else {
+ return DownloadsIndicatorData;
+ }
+ },
+
+ /**
+ * Returns a reference to the DownloadsSummaryData singleton - creating one
+ * in the process if one hasn't been instantiated yet.
+ *
+ * @param aWindow
+ * The browser window which owns the download button.
+ * @param aNumToExclude
+ * The number of items on the top of the downloads list to exclude
+ * from the summary.
+ */
+ getSummary(aWindow, aNumToExclude) {
+ if (PrivateBrowsingUtils.isContentWindowPrivate(aWindow)) {
+ if (this._privateSummary) {
+ return this._privateSummary;
+ }
+ return this._privateSummary = new DownloadsSummaryData(true, aNumToExclude);
+ } else {
+ if (this._summary) {
+ return this._summary;
+ }
+ return this._summary = new DownloadsSummaryData(false, aNumToExclude);
+ }
+ },
+ _summary: null,
+ _privateSummary: null,
+
+ /**
+ * Returns the legacy state integer value for the provided Download object.
+ */
+ stateOfDownload(download) {
+ // Collapse state using the correct priority.
+ if (!download.stopped) {
+ return nsIDM.DOWNLOAD_DOWNLOADING;
+ }
+ if (download.succeeded) {
+ return nsIDM.DOWNLOAD_FINISHED;
+ }
+ if (download.error) {
+ if (download.error.becauseBlockedByParentalControls) {
+ return nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
+ }
+ if (download.error.becauseBlockedByReputationCheck) {
+ return nsIDM.DOWNLOAD_DIRTY;
+ }
+ return nsIDM.DOWNLOAD_FAILED;
+ }
+ if (download.canceled) {
+ if (download.hasPartialData) {
+ return nsIDM.DOWNLOAD_PAUSED;
+ }
+ return nsIDM.DOWNLOAD_CANCELED;
+ }
+ return nsIDM.DOWNLOAD_NOTSTARTED;
+ },
+
+ /**
+ * Helper function required because the Downloads Panel and the Downloads View
+ * don't share the controller yet.
+ */
+ removeAndFinalizeDownload(download) {
+ Downloads.getList(Downloads.ALL)
+ .then(list => list.remove(download))
+ .then(() => download.finalize(true))
+ .catch(Cu.reportError);
+ },
+
+ /**
+ * Given an iterable collection of Download objects, generates and returns
+ * statistics about that collection.
+ *
+ * @param downloads An iterable collection of Download objects.
+ *
+ * @return Object whose properties are the generated statistics. Currently,
+ * we return the following properties:
+ *
+ * numActive : The total number of downloads.
+ * numPaused : The total number of paused downloads.
+ * numDownloading : The total number of downloads being downloaded.
+ * totalSize : The total size of all downloads once completed.
+ * totalTransferred: The total amount of transferred data for these
+ * downloads.
+ * slowestSpeed : The slowest download rate.
+ * rawTimeLeft : The estimated time left for the downloads to
+ * complete.
+ * percentComplete : The percentage of bytes successfully downloaded.
+ */
+ summarizeDownloads(downloads) {
+ let summary = {
+ numActive: 0,
+ numPaused: 0,
+ numDownloading: 0,
+ totalSize: 0,
+ totalTransferred: 0,
+ // slowestSpeed is Infinity so that we can use Math.min to
+ // find the slowest speed. We'll set this to 0 afterwards if
+ // it's still at Infinity by the time we're done iterating all
+ // download.
+ slowestSpeed: Infinity,
+ rawTimeLeft: -1,
+ percentComplete: -1
+ }
+
+ for (let download of downloads) {
+ summary.numActive++;
+
+ if (!download.stopped) {
+ summary.numDownloading++;
+ if (download.hasProgress && download.speed > 0) {
+ let sizeLeft = download.totalBytes - download.currentBytes;
+ summary.rawTimeLeft = Math.max(summary.rawTimeLeft,
+ sizeLeft / download.speed);
+ summary.slowestSpeed = Math.min(summary.slowestSpeed,
+ download.speed);
+ }
+ } else if (download.canceled && download.hasPartialData) {
+ summary.numPaused++;
+ }
+
+ // Only add to total values if we actually know the download size.
+ if (download.succeeded) {
+ summary.totalSize += download.target.size;
+ summary.totalTransferred += download.target.size;
+ } else if (download.hasProgress) {
+ summary.totalSize += download.totalBytes;
+ summary.totalTransferred += download.currentBytes;
+ }
+ }
+
+ if (summary.totalSize != 0) {
+ summary.percentComplete = (summary.totalTransferred /
+ summary.totalSize) * 100;
+ }
+
+ if (summary.slowestSpeed == Infinity) {
+ summary.slowestSpeed = 0;
+ }
+
+ return summary;
+ },
+
+ /**
+ * If necessary, smooths the estimated number of seconds remaining for one
+ * or more downloads to complete.
+ *
+ * @param aSeconds
+ * Current raw estimate on number of seconds left for one or more
+ * downloads. This is a floating point value to help get sub-second
+ * accuracy for current and future estimates.
+ */
+ smoothSeconds(aSeconds, aLastSeconds) {
+ // We apply an algorithm similar to the DownloadUtils.getTimeLeft function,
+ // though tailored to a single time estimation for all downloads. We never
+ // apply something if the new value is less than half the previous value.
+ let shouldApplySmoothing = aLastSeconds >= 0 &&
+ aSeconds > aLastSeconds / 2;
+ if (shouldApplySmoothing) {
+ // Apply hysteresis to favor downward over upward swings. Trust only 30%
+ // of the new value if lower, and 10% if higher (exponential smoothing).
+ let diff = aSeconds - aLastSeconds;
+ aSeconds = aLastSeconds + (diff < 0 ? .3 : .1) * diff;
+
+ // If the new time is similar, reuse something close to the last time
+ // left, but subtract a little to provide forward progress.
+ diff = aSeconds - aLastSeconds;
+ let diffPercent = diff / aLastSeconds * 100;
+ if (Math.abs(diff) < 5 || Math.abs(diffPercent) < 5) {
+ aSeconds = aLastSeconds - (diff < 0 ? .4 : .2);
+ }
+ }
+
+ // In the last few seconds of downloading, we are always subtracting and
+ // never adding to the time left. Ensure that we never fall below one
+ // second left until all downloads are actually finished.
+ return aLastSeconds = Math.max(aSeconds, 1);
+ },
+
+ /**
+ * Opens a downloaded file.
+ *
+ * @param aFile
+ * the downloaded file to be opened.
+ * @param aMimeInfo
+ * the mime type info object. May be null.
+ * @param aOwnerWindow
+ * the window with which this action is associated.
+ */
+ openDownloadedFile(aFile, aMimeInfo, aOwnerWindow) {
+ if (!(aFile instanceof Ci.nsIFile)) {
+ throw new Error("aFile must be a nsIFile object");
+ }
+ if (aMimeInfo && !(aMimeInfo instanceof Ci.nsIMIMEInfo)) {
+ throw new Error("Invalid value passed for aMimeInfo");
+ }
+ if (!(aOwnerWindow instanceof Ci.nsIDOMWindow)) {
+ throw new Error("aOwnerWindow must be a dom-window object");
+ }
+
+ let promiseShouldLaunch;
+ if (aFile.isExecutable()) {
+ // We get a prompter for the provided window here, even though anchoring
+ // to the most recently active window should work as well.
+ promiseShouldLaunch =
+ DownloadUIHelper.getPrompter(aOwnerWindow)
+ .confirmLaunchExecutable(aFile.path);
+ } else {
+ promiseShouldLaunch = Promise.resolve(true);
+ }
+
+ promiseShouldLaunch.then(shouldLaunch => {
+ if (!shouldLaunch) {
+ return;
+ }
+
+ // Actually open the file.
+ try {
+ if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
+ aMimeInfo.launchWithFile(aFile);
+ return;
+ }
+ } catch (ex) { }
+
+ // If either we don't have the mime info, or the preferred action failed,
+ // attempt to launch the file directly.
+ try {
+ aFile.launch();
+ } catch (ex) {
+ // If launch fails, try sending it through the system's external "file:"
+ // URL handler.
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService)
+ .loadUrl(NetUtil.newURI(aFile));
+ }
+ }).then(null, Cu.reportError);
+ },
+
+ /**
+ * Show a downloaded file in the system file manager.
+ *
+ * @param aFile
+ * a downloaded file.
+ */
+ showDownloadedFile(aFile) {
+ if (!(aFile instanceof Ci.nsIFile)) {
+ throw new Error("aFile must be a nsIFile object");
+ }
+ try {
+ // Show the directory containing the file and select the file.
+ aFile.reveal();
+ } catch (ex) {
+ // If reveal fails for some reason (e.g., it's not implemented on unix
+ // or the file doesn't exist), try using the parent if we have it.
+ let parent = aFile.parent;
+ if (parent) {
+ this.showDirectory(parent);
+ }
+ }
+ },
+
+ /**
+ * Show the specified folder in the system file manager.
+ *
+ * @param aDirectory
+ * a directory to be opened with system file manager.
+ */
+ showDirectory(aDirectory) {
+ if (!(aDirectory instanceof Ci.nsIFile)) {
+ throw new Error("aDirectory must be a nsIFile object");
+ }
+ try {
+ aDirectory.launch();
+ } catch (ex) {
+ // If launch fails (probably because it's not implemented), let
+ // the OS handler try to open the directory.
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService)
+ .loadUrl(NetUtil.newURI(aDirectory));
+ }
+ },
+
+ /**
+ * Displays an alert message box which asks the user if they want to
+ * unblock the downloaded file or not.
+ *
+ * @param options
+ * An object with the following properties:
+ * {
+ * verdict:
+ * The detailed reason why the download was blocked, according to
+ * the "Downloads.Error.BLOCK_VERDICT_" constants. If an unknown
+ * reason is specified, "Downloads.Error.BLOCK_VERDICT_MALWARE" is
+ * assumed.
+ * window:
+ * The window with which this action is associated.
+ * dialogType:
+ * String that determines which actions are available:
+ * - "unblock" to offer just "unblock".
+ * - "chooseUnblock" to offer "unblock" and "confirmBlock".
+ * - "chooseOpen" to offer "open" and "confirmBlock".
+ * }
+ *
+ * @return {Promise}
+ * @resolves String representing the action that should be executed:
+ * - "open" to allow the download and open the file.
+ * - "unblock" to allow the download without opening the file.
+ * - "confirmBlock" to delete the blocked data permanently.
+ * - "cancel" to do nothing and cancel the operation.
+ */
+ confirmUnblockDownload: Task.async(function* ({ verdict, window,
+ dialogType }) {
+ let s = DownloadsCommon.strings;
+
+ // All the dialogs have an action button and a cancel button, while only
+ // some of them have an additonal button to remove the file. The cancel
+ // button must always be the one at BUTTON_POS_1 because this is the value
+ // returned by confirmEx when using ESC or closing the dialog (bug 345067).
+ let title = s.unblockHeaderUnblock;
+ let firstButtonText = s.unblockButtonUnblock;
+ let firstButtonAction = "unblock";
+ let buttonFlags =
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
+ (Ci.nsIPrompt.BUTTON_TITLE_CANCEL * Ci.nsIPrompt.BUTTON_POS_1);
+
+ switch (dialogType) {
+ case "unblock":
+ // Use only the unblock action. The default is to cancel.
+ buttonFlags += Ci.nsIPrompt.BUTTON_POS_1_DEFAULT;
+ break;
+ case "chooseUnblock":
+ // Use the unblock and remove file actions. The default is remove file.
+ buttonFlags +=
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) +
+ Ci.nsIPrompt.BUTTON_POS_2_DEFAULT;
+ break;
+ case "chooseOpen":
+ // Use the unblock and open file actions. The default is open file.
+ title = s.unblockHeaderOpen;
+ firstButtonText = s.unblockButtonOpen;
+ firstButtonAction = "open";
+ buttonFlags +=
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2) +
+ Ci.nsIPrompt.BUTTON_POS_0_DEFAULT;
+ break;
+ default:
+ Cu.reportError("Unexpected dialog type: " + dialogType);
+ return "cancel";
+ }
+
+ let message;
+ switch (verdict) {
+ case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
+ message = s.unblockTypeUncommon2;
+ break;
+ case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
+ message = s.unblockTypePotentiallyUnwanted2;
+ break;
+ default: // Assume Downloads.Error.BLOCK_VERDICT_MALWARE
+ message = s.unblockTypeMalware;
+ break;
+ }
+ message += "\n\n" + s.unblockTip2;
+
+ Services.ww.registerNotification(function onOpen(subj, topic) {
+ if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+ // Make sure to listen for "DOMContentLoaded" because it is fired
+ // before the "load" event.
+ subj.addEventListener("DOMContentLoaded", function onLoad() {
+ subj.removeEventListener("DOMContentLoaded", onLoad);
+ if (subj.document.documentURI ==
+ "chrome://global/content/commonDialog.xul") {
+ Services.ww.unregisterNotification(onOpen);
+ let dialog = subj.document.getElementById("commonDialog");
+ if (dialog) {
+ // Change the dialog to use a warning icon.
+ dialog.classList.add("alert-dialog");
+ }
+ }
+ });
+ }
+ });
+
+ let rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
+ firstButtonText, null,
+ s.unblockButtonConfirmBlock, null, {});
+ return [firstButtonAction, "cancel", "confirmBlock"][rv];
+ }),
+};
+
+XPCOMUtils.defineLazyGetter(this.DownloadsCommon, "log", () => {
+ return DownloadsLogger.log.bind(DownloadsLogger);
+});
+XPCOMUtils.defineLazyGetter(this.DownloadsCommon, "error", () => {
+ return DownloadsLogger.error.bind(DownloadsLogger);
+});
+
+/**
+ * Returns true if we are executing on Windows Vista or a later version.
+ */
+XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
+ let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+ if (os != "WINNT") {
+ return false;
+ }
+ let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+ return parseFloat(sysInfo.getProperty("version")) >= 6;
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsData
+
+/**
+ * Retrieves the list of past and completed downloads from the underlying
+ * Download Manager data, and provides asynchronous notifications allowing to
+ * build a consistent view of the available data.
+ *
+ * This object responds to real-time changes in the underlying Download Manager
+ * data. For example, the deletion of one or more downloads is notified through
+ * the nsIObserver interface, while any state or progress change is notified
+ * through the nsIDownloadProgressListener interface.
+ *
+ * Note that using this object does not automatically start the Download Manager
+ * service. Consumers will see an empty list of downloads until the service is
+ * actually started. This is useful to display a neutral progress indicator in
+ * the main browser window until the autostart timeout elapses.
+ *
+ * Note that DownloadsData and PrivateDownloadsData are two equivalent singleton
+ * objects, one accessing non-private downloads, and the other accessing private
+ * ones.
+ */
+function DownloadsDataCtor(aPrivate) {
+ this._isPrivate = aPrivate;
+
+ // Contains all the available Download objects and their integer state.
+ this.oldDownloadStates = new Map();
+
+ // Array of view objects that should be notified when the available download
+ // data changes.
+ this._views = [];
+}
+
+DownloadsDataCtor.prototype = {
+ /**
+ * Starts receiving events for current downloads.
+ */
+ initializeDataLink() {
+ if (!this._dataLinkInitialized) {
+ let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
+ : Downloads.PUBLIC);
+ promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
+ this._dataLinkInitialized = true;
+ }
+ },
+ _dataLinkInitialized: false,
+
+ /**
+ * Iterator for all the available Download objects. This is empty until the
+ * data has been loaded using the JavaScript API for downloads.
+ */
+ get downloads() {
+ return this.oldDownloadStates.keys();
+ },
+
+ /**
+ * True if there are finished downloads that can be removed from the list.
+ */
+ get canRemoveFinished() {
+ for (let download of this.downloads) {
+ // Stopped, paused, and failed downloads with partial data are removed.
+ if (download.stopped && !(download.canceled && download.hasPartialData)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Asks the back-end to remove finished downloads from the list.
+ */
+ removeFinished() {
+ let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
+ : Downloads.PUBLIC);
+ promiseList.then(list => list.removeFinished())
+ .then(null, Cu.reportError);
+ let indicatorData = this._isPrivate ? PrivateDownloadsIndicatorData
+ : DownloadsIndicatorData;
+ indicatorData.attention = DownloadsCommon.ATTENTION_NONE;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Integration with the asynchronous Downloads back-end
+
+ onDownloadAdded(download) {
+ // Download objects do not store the end time of downloads, as the Downloads
+ // API does not need to persist this information for all platforms. Once a
+ // download terminates on a Desktop browser, it becomes a history download,
+ // for which the end time is stored differently, as a Places annotation.
+ download.endTime = Date.now();
+
+ this.oldDownloadStates.set(download,
+ DownloadsCommon.stateOfDownload(download));
+
+ for (let view of this._views) {
+ view.onDownloadAdded(download, true);
+ }
+ },
+
+ onDownloadChanged(download) {
+ let oldState = this.oldDownloadStates.get(download);
+ let newState = DownloadsCommon.stateOfDownload(download);
+ this.oldDownloadStates.set(download, newState);
+
+ if (oldState != newState) {
+ if (download.succeeded ||
+ (download.canceled && !download.hasPartialData) ||
+ download.error) {
+ // Store the end time that may be displayed by the views.
+ download.endTime = Date.now();
+
+ // This state transition code should actually be located in a Downloads
+ // API module (bug 941009). Moreover, the fact that state is stored as
+ // annotations should be ideally hidden behind methods of
+ // nsIDownloadHistory (bug 830415).
+ if (!this._isPrivate) {
+ try {
+ let downloadMetaData = {
+ state: DownloadsCommon.stateOfDownload(download),
+ endTime: download.endTime,
+ };
+ if (download.succeeded) {
+ downloadMetaData.fileSize = download.target.size;
+ }
+ if (download.error && download.error.reputationCheckVerdict) {
+ downloadMetaData.reputationCheckVerdict =
+ download.error.reputationCheckVerdict;
+ }
+
+ PlacesUtils.annotations.setPageAnnotation(
+ NetUtil.newURI(download.source.url),
+ "downloads/metaData",
+ JSON.stringify(downloadMetaData), 0,
+ PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ }
+
+ for (let view of this._views) {
+ try {
+ view.onDownloadStateChanged(download);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ if (download.succeeded ||
+ (download.error && download.error.becauseBlocked)) {
+ this._notifyDownloadEvent("finish");
+ }
+ }
+
+ if (!download.newDownloadNotified) {
+ download.newDownloadNotified = true;
+ this._notifyDownloadEvent("start");
+ }
+
+ for (let view of this._views) {
+ view.onDownloadChanged(download);
+ }
+ },
+
+ onDownloadRemoved(download) {
+ this.oldDownloadStates.delete(download);
+
+ for (let view of this._views) {
+ view.onDownloadRemoved(download);
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Registration of views
+
+ /**
+ * Adds an object to be notified when the available download data changes.
+ * The specified object is initialized with the currently available downloads.
+ *
+ * @param aView
+ * DownloadsView object to be added. This reference must be passed to
+ * removeView before termination.
+ */
+ addView(aView) {
+ this._views.push(aView);
+ this._updateView(aView);
+ },
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * DownloadsView object to be removed.
+ */
+ removeView(aView) {
+ let index = this._views.indexOf(aView);
+ if (index != -1) {
+ this._views.splice(index, 1);
+ }
+ },
+
+ /**
+ * Ensures that the currently loaded data is added to the specified view.
+ *
+ * @param aView
+ * DownloadsView object to be initialized.
+ */
+ _updateView(aView) {
+ // Indicate to the view that a batch loading operation is in progress.
+ aView.onDataLoadStarting();
+
+ // Sort backwards by start time, ensuring that the most recent
+ // downloads are added first regardless of their state.
+ let downloadsArray = [...this.downloads];
+ downloadsArray.sort((a, b) => b.startTime - a.startTime);
+ downloadsArray.forEach(download => aView.onDownloadAdded(download, false));
+
+ // Notify the view that all data is available.
+ aView.onDataLoadCompleted();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Notifications sent to the most recent browser window only
+
+ /**
+ * Set to true after the first download causes the downloads panel to be
+ * displayed.
+ */
+ get panelHasShownBefore() {
+ try {
+ return Services.prefs.getBoolPref("browser.download.panel.shown");
+ } catch (ex) { }
+ return false;
+ },
+
+ set panelHasShownBefore(aValue) {
+ Services.prefs.setBoolPref("browser.download.panel.shown", aValue);
+ return aValue;
+ },
+
+ /**
+ * Displays a new or finished download notification in the most recent browser
+ * window, if one is currently available with the required privacy type.
+ *
+ * @param aType
+ * Set to "start" for new downloads, "finish" for completed downloads.
+ */
+ _notifyDownloadEvent(aType) {
+ DownloadsCommon.log("Attempting to notify that a new download has started or finished.");
+
+ // Show the panel in the most recent browser window, if present.
+ let browserWin = RecentWindow.getMostRecentBrowserWindow({ private: this._isPrivate });
+ if (!browserWin) {
+ return;
+ }
+
+ if (this.panelHasShownBefore) {
+ // For new downloads after the first one, don't show the panel
+ // automatically, but provide a visible notification in the topmost
+ // browser window, if the status indicator is already visible.
+ DownloadsCommon.log("Showing new download notification.");
+ browserWin.DownloadsIndicatorView.showEventNotification(aType);
+ return;
+ }
+ this.panelHasShownBefore = true;
+ browserWin.DownloadsPanel.showPanel();
+ }
+};
+
+XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsData", function() {
+ return new DownloadsDataCtor(true);
+});
+
+XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
+ return new DownloadsDataCtor(false);
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewPrototype
+
+/**
+ * A prototype for an object that registers itself with DownloadsData as soon
+ * as a view is registered with it.
+ */
+const DownloadsViewPrototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Registration of views
+
+ /**
+ * Array of view objects that should be notified when the available status
+ * data changes.
+ *
+ * SUBCLASSES MUST OVERRIDE THIS PROPERTY.
+ */
+ _views: null,
+
+ /**
+ * Determines whether this view object is over the private or non-private
+ * downloads.
+ *
+ * SUBCLASSES MUST OVERRIDE THIS PROPERTY.
+ */
+ _isPrivate: false,
+
+ /**
+ * Adds an object to be notified when the available status data changes.
+ * The specified object is initialized with the currently available status.
+ *
+ * @param aView
+ * View object to be added. This reference must be
+ * passed to removeView before termination.
+ */
+ addView(aView) {
+ // Start receiving events when the first of our views is registered.
+ if (this._views.length == 0) {
+ if (this._isPrivate) {
+ PrivateDownloadsData.addView(this);
+ } else {
+ DownloadsData.addView(this);
+ }
+ }
+
+ this._views.push(aView);
+ this.refreshView(aView);
+ },
+
+ /**
+ * Updates the properties of an object previously added using addView.
+ *
+ * @param aView
+ * View object to be updated.
+ */
+ refreshView(aView) {
+ // Update immediately even if we are still loading data asynchronously.
+ // Subclasses must provide these two functions!
+ this._refreshProperties();
+ this._updateView(aView);
+ },
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * View object to be removed.
+ */
+ removeView(aView) {
+ let index = this._views.indexOf(aView);
+ if (index != -1) {
+ this._views.splice(index, 1);
+ }
+
+ // Stop receiving events when the last of our views is unregistered.
+ if (this._views.length == 0) {
+ if (this._isPrivate) {
+ PrivateDownloadsData.removeView(this);
+ } else {
+ DownloadsData.removeView(this);
+ }
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData
+
+ /**
+ * Indicates whether we are still loading downloads data asynchronously.
+ */
+ _loading: false,
+
+ /**
+ * Called before multiple downloads are about to be loaded.
+ */
+ onDataLoadStarting() {
+ this._loading = true;
+ },
+
+ /**
+ * Called after data loading finished.
+ */
+ onDataLoadCompleted() {
+ this._loading = false;
+ },
+
+ /**
+ * Called when a new download data item is available, either during the
+ * asynchronous data load or when a new download is started.
+ *
+ * @param download
+ * Download object that was just added.
+ * @param newest
+ * When true, indicates that this item is the most recent and should be
+ * added in the topmost position. This happens when a new download is
+ * started. When false, indicates that the item is the least recent
+ * with regard to the items that have been already added. The latter
+ * generally happens during the asynchronous data load.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadAdded(download, newest) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Called when the overall state of a Download has changed. In particular,
+ * this is called only once when the download succeeds or is blocked
+ * permanently, and is never called if only the current progress changed.
+ *
+ * The onDownloadChanged notification will always be sent afterwards.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadStateChanged(download) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Called every time any state property of a Download may have changed,
+ * including progress properties.
+ *
+ * Note that progress notification changes are throttled at the Downloads.jsm
+ * API level, and there is no throttling mechanism in the front-end.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadChanged(download) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Called when a data item is removed, ensures that the widget associated with
+ * the view item is removed from the user interface.
+ *
+ * @param download
+ * Download object that is being removed.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadRemoved(download) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Private function used to refresh the internal properties being sent to
+ * each registered view.
+ *
+ * @note Subclasses should override this.
+ */
+ _refreshProperties() {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Private function used to refresh an individual view.
+ *
+ * @note Subclasses should override this.
+ */
+ _updateView() {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsIndicatorData
+
+/**
+ * This object registers itself with DownloadsData as a view, and transforms the
+ * notifications it receives into overall status data, that is then broadcast to
+ * the registered download status indicators.
+ *
+ * Note that using this object does not automatically start the Download Manager
+ * service. Consumers will see an empty list of downloads until the service is
+ * actually started. This is useful to display a neutral progress indicator in
+ * the main browser window until the autostart timeout elapses.
+ */
+function DownloadsIndicatorDataCtor(aPrivate) {
+ this._isPrivate = aPrivate;
+ this._views = [];
+}
+DownloadsIndicatorDataCtor.prototype = {
+ __proto__: DownloadsViewPrototype,
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * DownloadsIndicatorView object to be removed.
+ */
+ removeView(aView) {
+ DownloadsViewPrototype.removeView.call(this, aView);
+
+ if (this._views.length == 0) {
+ this._itemCount = 0;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData
+
+ onDataLoadCompleted() {
+ DownloadsViewPrototype.onDataLoadCompleted.call(this);
+ this._updateViews();
+ },
+
+ onDownloadAdded(download, newest) {
+ this._itemCount++;
+ this._updateViews();
+ },
+
+ onDownloadStateChanged(download) {
+ if (!download.succeeded && download.error && download.error.reputationCheckVerdict) {
+ switch (download.error.reputationCheckVerdict) {
+ case Downloads.Error.BLOCK_VERDICT_UNCOMMON: // fall-through
+ case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
+ // Existing higher level attention indication trumps ATTENTION_WARNING.
+ if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
+ this.attention = DownloadsCommon.ATTENTION_WARNING;
+ }
+ break;
+ case Downloads.Error.BLOCK_VERDICT_MALWARE:
+ this.attention = DownloadsCommon.ATTENTION_SEVERE;
+ break;
+ default:
+ this.attention = DownloadsCommon.ATTENTION_SEVERE;
+ Cu.reportError("Unknown reputation verdict: " +
+ download.error.reputationCheckVerdict);
+ }
+ } else if (download.succeeded) {
+ // Existing higher level attention indication trumps ATTENTION_SUCCESS.
+ if (this._attention != DownloadsCommon.ATTENTION_SEVERE &&
+ this._attention != DownloadsCommon.ATTENTION_WARNING) {
+ this.attention = DownloadsCommon.ATTENTION_SUCCESS;
+ }
+ } else if (download.error) {
+ // Existing higher level attention indication trumps ATTENTION_WARNING.
+ if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
+ this.attention = DownloadsCommon.ATTENTION_WARNING;
+ }
+ }
+
+ // Since the state of a download changed, reset the estimated time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ },
+
+ onDownloadChanged(download) {
+ this._updateViews();
+ },
+
+ onDownloadRemoved(download) {
+ this._itemCount--;
+ this._updateViews();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Propagation of properties to our views
+
+ // The following properties are updated by _refreshProperties and are then
+ // propagated to the views. See _refreshProperties for details.
+ _hasDownloads: false,
+ _counter: "",
+ _percentComplete: -1,
+ _paused: false,
+
+ /**
+ * Indicates whether the download indicators should be highlighted.
+ */
+ set attention(aValue) {
+ this._attention = aValue;
+ this._updateViews();
+ return aValue;
+ },
+ _attention: DownloadsCommon.ATTENTION_NONE,
+
+ /**
+ * Indicates whether the user is interacting with downloads, thus the
+ * attention indication should not be shown even if requested.
+ */
+ set attentionSuppressed(aValue) {
+ this._attentionSuppressed = aValue;
+ this._attention = DownloadsCommon.ATTENTION_NONE;
+ this._updateViews();
+ return aValue;
+ },
+ _attentionSuppressed: false,
+
+ /**
+ * Computes aggregate values and propagates the changes to our views.
+ */
+ _updateViews() {
+ // Do not update the status indicators during batch loads of download items.
+ if (this._loading) {
+ return;
+ }
+
+ this._refreshProperties();
+ this._views.forEach(this._updateView, this);
+ },
+
+ /**
+ * Updates the specified view with the current aggregate values.
+ *
+ * @param aView
+ * DownloadsIndicatorView object to be updated.
+ */
+ _updateView(aView) {
+ aView.hasDownloads = this._hasDownloads;
+ aView.counter = this._counter;
+ aView.percentComplete = this._percentComplete;
+ aView.paused = this._paused;
+ aView.attention = this._attentionSuppressed ? DownloadsCommon.ATTENTION_NONE
+ : this._attention;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Property updating based on current download status
+
+ /**
+ * Number of download items that are available to be displayed.
+ */
+ _itemCount: 0,
+
+ /**
+ * Floating point value indicating the last number of seconds estimated until
+ * the longest download will finish. We need to store this value so that we
+ * don't continuously apply smoothing if the actual download state has not
+ * changed. This is set to -1 if the previous value is unknown.
+ */
+ _lastRawTimeLeft: -1,
+
+ /**
+ * Last number of seconds estimated until all in-progress downloads with a
+ * known size and speed will finish. This value is stored to allow smoothing
+ * in case of small variations. This is set to -1 if the previous value is
+ * unknown.
+ */
+ _lastTimeLeft: -1,
+
+ /**
+ * A generator function for the Download objects this summary is currently
+ * interested in. This generator is passed off to summarizeDownloads in order
+ * to generate statistics about the downloads we care about - in this case,
+ * it's all active downloads.
+ */
+ * _activeDownloads() {
+ let downloads = this._isPrivate ? PrivateDownloadsData.downloads
+ : DownloadsData.downloads;
+ for (let download of downloads) {
+ if (!download.stopped || (download.canceled && download.hasPartialData)) {
+ yield download;
+ }
+ }
+ },
+
+ /**
+ * Computes aggregate values based on the current state of downloads.
+ */
+ _refreshProperties() {
+ let summary =
+ DownloadsCommon.summarizeDownloads(this._activeDownloads());
+
+ // Determine if the indicator should be shown or get attention.
+ this._hasDownloads = (this._itemCount > 0);
+
+ // If all downloads are paused, show the progress indicator as paused.
+ this._paused = summary.numActive > 0 &&
+ summary.numActive == summary.numPaused;
+
+ this._percentComplete = summary.percentComplete;
+
+ // Display the estimated time left, if present.
+ if (summary.rawTimeLeft == -1) {
+ // There are no downloads with a known time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ this._counter = "";
+ } else {
+ // Compute the new time left only if state actually changed.
+ if (this._lastRawTimeLeft != summary.rawTimeLeft) {
+ this._lastRawTimeLeft = summary.rawTimeLeft;
+ this._lastTimeLeft = DownloadsCommon.smoothSeconds(summary.rawTimeLeft,
+ this._lastTimeLeft);
+ }
+ this._counter = DownloadsCommon.formatTimeLeft(this._lastTimeLeft);
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsIndicatorData", function() {
+ return new DownloadsIndicatorDataCtor(true);
+});
+
+XPCOMUtils.defineLazyGetter(this, "DownloadsIndicatorData", function() {
+ return new DownloadsIndicatorDataCtor(false);
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsSummaryData
+
+/**
+ * DownloadsSummaryData is a view for DownloadsData that produces a summary
+ * of all downloads after a certain exclusion point aNumToExclude. For example,
+ * if there were 5 downloads in progress, and a DownloadsSummaryData was
+ * constructed with aNumToExclude equal to 3, then that DownloadsSummaryData
+ * would produce a summary of the last 2 downloads.
+ *
+ * @param aIsPrivate
+ * True if the browser window which owns the download button is a private
+ * window.
+ * @param aNumToExclude
+ * The number of items to exclude from the summary, starting from the
+ * top of the list.
+ */
+function DownloadsSummaryData(aIsPrivate, aNumToExclude) {
+ this._numToExclude = aNumToExclude;
+ // Since we can have multiple instances of DownloadsSummaryData, we
+ // override these values from the prototype so that each instance can be
+ // completely separated from one another.
+ this._loading = false;
+
+ this._downloads = [];
+
+ // Floating point value indicating the last number of seconds estimated until
+ // the longest download will finish. We need to store this value so that we
+ // don't continuously apply smoothing if the actual download state has not
+ // changed. This is set to -1 if the previous value is unknown.
+ this._lastRawTimeLeft = -1;
+
+ // Last number of seconds estimated until all in-progress downloads with a
+ // known size and speed will finish. This value is stored to allow smoothing
+ // in case of small variations. This is set to -1 if the previous value is
+ // unknown.
+ this._lastTimeLeft = -1;
+
+ // The following properties are updated by _refreshProperties and are then
+ // propagated to the views.
+ this._showingProgress = false;
+ this._details = "";
+ this._description = "";
+ this._numActive = 0;
+ this._percentComplete = -1;
+
+ this._isPrivate = aIsPrivate;
+ this._views = [];
+}
+
+DownloadsSummaryData.prototype = {
+ __proto__: DownloadsViewPrototype,
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * DownloadsSummary view to be removed.
+ */
+ removeView(aView) {
+ DownloadsViewPrototype.removeView.call(this, aView);
+
+ if (this._views.length == 0) {
+ // Clear out our collection of Download objects. If we ever have
+ // another view registered with us, this will get re-populated.
+ this._downloads = [];
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData - see the documentation in
+ //// DownloadsViewPrototype for more information on what these functions
+ //// are used for.
+
+ onDataLoadCompleted() {
+ DownloadsViewPrototype.onDataLoadCompleted.call(this);
+ this._updateViews();
+ },
+
+ onDownloadAdded(download, newest) {
+ if (newest) {
+ this._downloads.unshift(download);
+ } else {
+ this._downloads.push(download);
+ }
+
+ this._updateViews();
+ },
+
+ onDownloadStateChanged() {
+ // Since the state of a download changed, reset the estimated time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ },
+
+ onDownloadChanged() {
+ this._updateViews();
+ },
+
+ onDownloadRemoved(download) {
+ let itemIndex = this._downloads.indexOf(download);
+ this._downloads.splice(itemIndex, 1);
+ this._updateViews();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Propagation of properties to our views
+
+ /**
+ * Computes aggregate values and propagates the changes to our views.
+ */
+ _updateViews() {
+ // Do not update the status indicators during batch loads of download items.
+ if (this._loading) {
+ return;
+ }
+
+ this._refreshProperties();
+ this._views.forEach(this._updateView, this);
+ },
+
+ /**
+ * Updates the specified view with the current aggregate values.
+ *
+ * @param aView
+ * DownloadsIndicatorView object to be updated.
+ */
+ _updateView(aView) {
+ aView.showingProgress = this._showingProgress;
+ aView.percentComplete = this._percentComplete;
+ aView.description = this._description;
+ aView.details = this._details;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Property updating based on current download status
+
+ /**
+ * A generator function for the Download objects this summary is currently
+ * interested in. This generator is passed off to summarizeDownloads in order
+ * to generate statistics about the downloads we care about - in this case,
+ * it's the downloads in this._downloads after the first few to exclude,
+ * which was set when constructing this DownloadsSummaryData instance.
+ */
+ * _downloadsForSummary() {
+ if (this._downloads.length > 0) {
+ for (let i = this._numToExclude; i < this._downloads.length; ++i) {
+ yield this._downloads[i];
+ }
+ }
+ },
+
+ /**
+ * Computes aggregate values based on the current state of downloads.
+ */
+ _refreshProperties() {
+ // Pre-load summary with default values.
+ let summary =
+ DownloadsCommon.summarizeDownloads(this._downloadsForSummary());
+
+ this._description = DownloadsCommon.strings
+ .otherDownloads2(summary.numActive);
+ this._percentComplete = summary.percentComplete;
+
+ // If all downloads are paused, show the progress indicator as paused.
+ this._showingProgress = summary.numDownloading > 0 ||
+ summary.numPaused > 0;
+
+ // Display the estimated time left, if present.
+ if (summary.rawTimeLeft == -1) {
+ // There are no downloads with a known time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ this._details = "";
+ } else {
+ // Compute the new time left only if state actually changed.
+ if (this._lastRawTimeLeft != summary.rawTimeLeft) {
+ this._lastRawTimeLeft = summary.rawTimeLeft;
+ this._lastTimeLeft = DownloadsCommon.smoothSeconds(summary.rawTimeLeft,
+ this._lastTimeLeft);
+ }
+ [this._details] = DownloadUtils.getDownloadStatusNoRate(
+ summary.totalTransferred, summary.totalSize, summary.slowestSpeed,
+ this._lastTimeLeft);
+ }
+ },
+}
diff --git a/browser/components/downloads/DownloadsTaskbar.jsm b/browser/components/downloads/DownloadsTaskbar.jsm
new file mode 100644
index 000000000..cf915abb5
--- /dev/null
+++ b/browser/components/downloads/DownloadsTaskbar.jsm
@@ -0,0 +1,177 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Handles the download progress indicator in the taskbar.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadsTaskbar",
+];
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function () {
+ if (!("@mozilla.org/windows-taskbar;1" in Cc)) {
+ return null;
+ }
+ let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"]
+ .getService(Ci.nsIWinTaskbar);
+ return winTaskbar.available && winTaskbar;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function () {
+ return ("@mozilla.org/widget/macdocksupport;1" in Cc) &&
+ Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsITaskbarProgress);
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsTaskbar
+
+/**
+ * Handles the download progress indicator in the taskbar.
+ */
+this.DownloadsTaskbar = {
+ /**
+ * Underlying DownloadSummary providing the aggregate download information, or
+ * null if the indicator has never been initialized.
+ */
+ _summary: null,
+
+ /**
+ * nsITaskbarProgress object to which download information is dispatched.
+ * This can be null if the indicator has never been initialized or if the
+ * indicator is currently hidden on Windows.
+ */
+ _taskbarProgress: null,
+
+ /**
+ * This method is called after a new browser window is opened, and ensures
+ * that the download progress indicator is displayed in the taskbar.
+ *
+ * On Windows, the indicator is attached to the first browser window that
+ * calls this method. When the window is closed, the indicator is moved to
+ * another browser window, if available, in no particular order. When there
+ * are no browser windows visible, the indicator is hidden.
+ *
+ * On Mac OS X, the indicator is initialized globally when this method is
+ * called for the first time. Subsequent calls have no effect.
+ *
+ * @param aBrowserWindow
+ * nsIDOMWindow object of the newly opened browser window to which the
+ * indicator may be attached.
+ */
+ registerIndicator(aBrowserWindow) {
+ if (!this._taskbarProgress) {
+ if (gMacTaskbarProgress) {
+ // On Mac OS X, we have to register the global indicator only once.
+ this._taskbarProgress = gMacTaskbarProgress;
+ // Free the XPCOM reference on shutdown, to prevent detecting a leak.
+ Services.obs.addObserver(() => {
+ this._taskbarProgress = null;
+ gMacTaskbarProgress = null;
+ }, "quit-application-granted", false);
+ } else if (gWinTaskbar) {
+ // On Windows, the indicator is currently hidden because we have no
+ // previous browser window, thus we should attach the indicator now.
+ this._attachIndicator(aBrowserWindow);
+ } else {
+ // The taskbar indicator is not available on this platform.
+ return;
+ }
+ }
+
+ // Ensure that the DownloadSummary object will be created asynchronously.
+ if (!this._summary) {
+ Downloads.getSummary(Downloads.ALL).then(summary => {
+ // In case the method is re-entered, we simply ignore redundant
+ // invocations of the callback, instead of keeping separate state.
+ if (this._summary) {
+ return;
+ }
+ this._summary = summary;
+ return this._summary.addView(this);
+ }).then(null, Cu.reportError);
+ }
+ },
+
+ /**
+ * On Windows, attaches the taskbar indicator to the specified browser window.
+ */
+ _attachIndicator(aWindow) {
+ // Activate the indicator on the specified window.
+ let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow).docShell;
+ this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell);
+
+ // If the DownloadSummary object has already been created, we should update
+ // the state of the new indicator, otherwise it will be updated as soon as
+ // the DownloadSummary view is registered.
+ if (this._summary) {
+ this.onSummaryChanged();
+ }
+
+ aWindow.addEventListener("unload", () => {
+ // Locate another browser window, excluding the one being closed.
+ let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+ if (browserWindow) {
+ // Move the progress indicator to the other browser window.
+ this._attachIndicator(browserWindow);
+ } else {
+ // The last browser window has been closed. We remove the reference to
+ // the taskbar progress object so that the indicator will be registered
+ // again on the next browser window that is opened.
+ this._taskbarProgress = null;
+ }
+ }, false);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// DownloadSummary view
+
+ onSummaryChanged() {
+ // If the last browser window has been closed, we have no indicator any more.
+ if (!this._taskbarProgress) {
+ return;
+ }
+
+ if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) {
+ this._taskbarProgress.setProgressState(
+ Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0);
+ } else {
+ // For a brief moment before completion, some download components may
+ // report more transferred bytes than the total number of bytes. Thus,
+ // ensure that we never break the expectations of the progress indicator.
+ let progressCurrentBytes = Math.min(this._summary.progressTotalBytes,
+ this._summary.progressCurrentBytes);
+ this._taskbarProgress.setProgressState(
+ Ci.nsITaskbarProgress.STATE_NORMAL,
+ progressCurrentBytes,
+ this._summary.progressTotalBytes);
+ }
+ },
+};
diff --git a/browser/components/downloads/DownloadsViewUI.jsm b/browser/components/downloads/DownloadsViewUI.jsm
new file mode 100644
index 000000000..3b0e4c1b7
--- /dev/null
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -0,0 +1,395 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This module is imported by code that uses the "download.xml" binding, and
+ * provides prototypes for objects that handle input and display information.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadsViewUI",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+ "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+this.DownloadsViewUI = {
+ /**
+ * Returns true if the given string is the name of a command that can be
+ * handled by the Downloads user interface, including standard commands.
+ */
+ isCommandName(name) {
+ return name.startsWith("cmd_") || name.startsWith("downloadsCmd_");
+ },
+};
+
+/**
+ * A download element shell is responsible for handling the commands and the
+ * displayed data for a single element that uses the "download.xml" binding.
+ *
+ * The information to display is obtained through the associated Download object
+ * from the JavaScript API for downloads, and commands are executed using a
+ * combination of Download methods and DownloadsCommon.jsm helper functions.
+ *
+ * Specialized versions of this shell must be defined, and they are required to
+ * implement the "download" property or getter. Currently these objects are the
+ * HistoryDownloadElementShell and the DownloadsViewItem for the panel. The
+ * history view may use a HistoryDownload object in place of a Download object.
+ */
+this.DownloadsViewUI.DownloadElementShell = function () {}
+
+this.DownloadsViewUI.DownloadElementShell.prototype = {
+ /**
+ * The richlistitem for the download, initialized by the derived object.
+ */
+ element: null,
+
+ /**
+ * URI string for the file type icon displayed in the download element.
+ */
+ get image() {
+ if (!this.download.target.path) {
+ // Old history downloads may not have a target path.
+ return "moz-icon://.unknown?size=32";
+ }
+
+ // When a download that was previously in progress finishes successfully, it
+ // means that the target file now exists and we can extract its specific
+ // icon, for example from a Windows executable. To ensure that the icon is
+ // reloaded, however, we must change the URI used by the XUL image element,
+ // for example by adding a query parameter. This only works if we add one of
+ // the parameters explicitly supported by the nsIMozIconURI interface.
+ return "moz-icon://" + this.download.target.path + "?size=32" +
+ (this.download.succeeded ? "&state=normal" : "");
+ },
+
+ /**
+ * The user-facing label for the download. This is normally the leaf name of
+ * the download target file. In case this is a very old history download for
+ * which the target file is unknown, the download source URI is displayed.
+ */
+ get displayName() {
+ if (!this.download.target.path) {
+ return this.download.source.url;
+ }
+ return OS.Path.basename(this.download.target.path);
+ },
+
+ /**
+ * The progress element for the download, or undefined in case the XBL binding
+ * has not been applied yet.
+ */
+ get _progressElement() {
+ if (!this.__progressElement) {
+ // If the element is not available now, we will try again the next time.
+ this.__progressElement =
+ this.element.ownerDocument.getAnonymousElementByAttribute(
+ this.element, "anonid",
+ "progressmeter");
+ }
+ return this.__progressElement;
+ },
+
+ /**
+ * Processes a major state change in the user interface, then proceeds with
+ * the normal progress update. This function is not called for every progress
+ * update in order to improve performance.
+ */
+ _updateState() {
+ this.element.setAttribute("displayName", this.displayName);
+ this.element.setAttribute("image", this.image);
+ this.element.setAttribute("state",
+ DownloadsCommon.stateOfDownload(this.download));
+
+ if (!this.download.succeeded && this.download.error &&
+ this.download.error.becauseBlockedByReputationCheck) {
+ this.element.setAttribute("verdict",
+ this.download.error.reputationCheckVerdict);
+ } else {
+ this.element.removeAttribute("verdict");
+ }
+
+ // Since state changed, reset the time left estimation.
+ this.lastEstimatedSecondsLeft = Infinity;
+
+ this._updateProgress();
+ },
+
+ /**
+ * Updates the elements that change regularly for in-progress downloads,
+ * namely the progress bar and the status line.
+ */
+ _updateProgress() {
+ if (this.download.succeeded) {
+ // We only need to add or remove this attribute for succeeded downloads.
+ if (this.download.target.exists) {
+ this.element.setAttribute("exists", "true");
+ } else {
+ this.element.removeAttribute("exists");
+ }
+ }
+
+ // When a block is confirmed, the removal of blocked data will not trigger a
+ // state change for the download, so this class must be updated here.
+ this.element.classList.toggle("temporary-block",
+ !!this.download.hasBlockedData);
+
+ // The progress bar is only displayed for in-progress downloads.
+ if (this.download.hasProgress) {
+ this.element.setAttribute("progressmode", "normal");
+ this.element.setAttribute("progress", this.download.progress);
+ } else {
+ this.element.setAttribute("progressmode", "undetermined");
+ }
+
+ if (this.download.stopped && this.download.canceled &&
+ this.download.hasPartialData) {
+ this.element.setAttribute("progresspaused", "true");
+ } else {
+ this.element.removeAttribute("progresspaused");
+ }
+
+ // Dispatch the ValueChange event for accessibility, if possible.
+ if (this._progressElement) {
+ let event = this.element.ownerDocument.createEvent("Events");
+ event.initEvent("ValueChange", true, true);
+ this._progressElement.dispatchEvent(event);
+ }
+
+ let status = this.statusTextAndTip;
+ this.element.setAttribute("status", status.text);
+ this.element.setAttribute("statusTip", status.tip);
+ },
+
+ lastEstimatedSecondsLeft: Infinity,
+
+ /**
+ * Returns the text for the status line and the associated tooltip. These are
+ * returned by a single property because they are computed together. The
+ * result may be overridden by derived objects.
+ */
+ get statusTextAndTip() {
+ return this.rawStatusTextAndTip;
+ },
+
+ /**
+ * Derived objects may call this to get the status text.
+ */
+ get rawStatusTextAndTip() {
+ let s = DownloadsCommon.strings;
+
+ let text = "";
+ let tip = "";
+
+ if (!this.download.stopped) {
+ let totalBytes = this.download.hasProgress ? this.download.totalBytes
+ : -1;
+ // By default, extended status information including the individual
+ // download rate is displayed in the tooltip. The history view overrides
+ // the getter and displays the datails in the main area instead.
+ [text] = DownloadUtils.getDownloadStatusNoRate(
+ this.download.currentBytes,
+ totalBytes,
+ this.download.speed,
+ this.lastEstimatedSecondsLeft);
+ let newEstimatedSecondsLeft;
+ [tip, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus(
+ this.download.currentBytes,
+ totalBytes,
+ this.download.speed,
+ this.lastEstimatedSecondsLeft);
+ this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
+ } else if (this.download.canceled && this.download.hasPartialData) {
+ let totalBytes = this.download.hasProgress ? this.download.totalBytes
+ : -1;
+ let transfer = DownloadUtils.getTransferTotal(this.download.currentBytes,
+ totalBytes);
+
+ // We use the same XUL label to display both the state and the amount
+ // transferred, for example "Paused - 1.1 MB".
+ text = s.statusSeparatorBeforeNumber(s.statePaused, transfer);
+ } else if (!this.download.succeeded && !this.download.canceled &&
+ !this.download.error) {
+ text = s.stateStarting;
+ } else {
+ let stateLabel;
+
+ if (this.download.succeeded) {
+ // For completed downloads, show the file size (e.g. "1.5 MB").
+ if (this.download.target.size !== undefined) {
+ let [size, unit] =
+ DownloadUtils.convertByteUnits(this.download.target.size);
+ stateLabel = s.sizeWithUnits(size, unit);
+ } else {
+ // History downloads may not have a size defined.
+ stateLabel = s.sizeUnknown;
+ }
+ } else if (this.download.canceled) {
+ stateLabel = s.stateCanceled;
+ } else if (this.download.error.becauseBlockedByParentalControls) {
+ stateLabel = s.stateBlockedParentalControls;
+ } else if (this.download.error.becauseBlockedByReputationCheck) {
+ stateLabel = this.rawBlockedTitleAndDetails[0];
+ } else {
+ stateLabel = s.stateFailed;
+ }
+
+ let referrer = this.download.source.referrer || this.download.source.url;
+ let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer);
+
+ let date = new Date(this.download.endTime);
+ let [displayDate, fullDate] = DownloadUtils.getReadableDates(date);
+
+ let firstPart = s.statusSeparator(stateLabel, displayHost);
+ text = s.statusSeparator(firstPart, displayDate);
+ tip = s.statusSeparator(fullHost, fullDate);
+ }
+
+ return { text, tip: tip || text };
+ },
+
+ /**
+ * Returns [title, [details1, details2]] for blocked downloads.
+ */
+ get rawBlockedTitleAndDetails() {
+ let s = DownloadsCommon.strings;
+ if (!this.download.error ||
+ !this.download.error.becauseBlockedByReputationCheck) {
+ return [null, null];
+ }
+ switch (this.download.error.reputationCheckVerdict) {
+ case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
+ return [s.blockedUncommon2, [s.unblockTypeUncommon2, s.unblockTip2]];
+ case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
+ return [s.blockedPotentiallyUnwanted,
+ [s.unblockTypePotentiallyUnwanted2, s.unblockTip2]];
+ case Downloads.Error.BLOCK_VERDICT_MALWARE:
+ return [s.blockedMalware, [s.unblockTypeMalware, s.unblockTip2]];
+ }
+ throw new Error("Unexpected reputationCheckVerdict: " +
+ this.download.error.reputationCheckVerdict);
+ // return anyway to avoid a JS strict warning.
+ return [null, null];
+ },
+
+ /**
+ * Shows the appropriate unblock dialog based on the verdict, and executes the
+ * action selected by the user in the dialog, which may involve unblocking,
+ * opening or removing the file.
+ *
+ * @param window
+ * The window to which the dialog should be anchored.
+ * @param dialogType
+ * Can be "unblock", "chooseUnblock", or "chooseOpen".
+ */
+ confirmUnblock(window, dialogType) {
+ DownloadsCommon.confirmUnblockDownload({
+ verdict: this.download.error.reputationCheckVerdict,
+ window,
+ dialogType,
+ }).then(action => {
+ if (action == "open") {
+ return this.unblockAndOpenDownload();
+ } else if (action == "unblock") {
+ return this.download.unblock();
+ } else if (action == "confirmBlock") {
+ return this.download.confirmBlock();
+ }
+ }).catch(Cu.reportError);
+ },
+
+ /**
+ * Unblocks the downloaded file and opens it.
+ *
+ * @return A promise that's resolved after the file has been opened.
+ */
+ unblockAndOpenDownload() {
+ return this.download.unblock().then(() => this.downloadsCmd_open());
+ },
+
+ /**
+ * Returns the name of the default command to use for the current state of the
+ * download, when there is a double click or another default interaction. If
+ * there is no default command for the current state, returns an empty string.
+ * The commands are implemented as functions on this object or derived ones.
+ */
+ get currentDefaultCommandName() {
+ switch (DownloadsCommon.stateOfDownload(this.download)) {
+ case Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+ return "downloadsCmd_cancel";
+ case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
+ case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+ return "downloadsCmd_retry";
+ case Ci.nsIDownloadManager.DOWNLOAD_PAUSED:
+ return "downloadsCmd_pauseResume";
+ case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
+ return "downloadsCmd_open";
+ case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+ return "downloadsCmd_openReferrer";
+ case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+ return "downloadsCmd_showBlockedInfo";
+ }
+ return "";
+ },
+
+ /**
+ * Returns true if the specified command can be invoked on the current item.
+ * The commands are implemented as functions on this object or derived ones.
+ *
+ * @param aCommand
+ * Name of the command to check, for example "downloadsCmd_retry".
+ */
+ isCommandEnabled(aCommand) {
+ switch (aCommand) {
+ case "downloadsCmd_retry":
+ return this.download.canceled || this.download.error;
+ case "downloadsCmd_pauseResume":
+ return this.download.hasPartialData && !this.download.error;
+ case "downloadsCmd_openReferrer":
+ return !!this.download.source.referrer;
+ case "downloadsCmd_confirmBlock":
+ case "downloadsCmd_chooseUnblock":
+ case "downloadsCmd_chooseOpen":
+ case "downloadsCmd_unblock":
+ case "downloadsCmd_unblockAndOpen":
+ return this.download.hasBlockedData;
+ }
+ return false;
+ },
+
+ downloadsCmd_cancel() {
+ // This is the correct way to avoid race conditions when cancelling.
+ this.download.cancel().catch(() => {});
+ this.download.removePartialData().catch(Cu.reportError);
+ },
+
+ downloadsCmd_retry() {
+ // Errors when retrying are already reported as download failures.
+ this.download.start().catch(() => {});
+ },
+
+ downloadsCmd_pauseResume() {
+ if (this.download.stopped) {
+ this.download.start();
+ } else {
+ this.download.cancel();
+ }
+ },
+
+ downloadsCmd_confirmBlock() {
+ this.download.confirmBlock().catch(Cu.reportError);
+ },
+};
diff --git a/browser/components/downloads/content/allDownloadsViewOverlay.js b/browser/components/downloads/content/allDownloadsViewOverlay.js
new file mode 100644
index 000000000..58078cd08
--- /dev/null
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -0,0 +1,1439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+ "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
+ "resource:///modules/DownloadsViewUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const nsIDM = Ci.nsIDownloadManager;
+
+const DESTINATION_FILE_URI_ANNO = "downloads/destinationFileURI";
+const DOWNLOAD_META_DATA_ANNO = "downloads/metaData";
+
+/**
+ * Represents a download from the browser history. It implements part of the
+ * interface of the Download object.
+ *
+ * @param aPlacesNode
+ * The Places node from which the history download should be initialized.
+ */
+function HistoryDownload(aPlacesNode) {
+ // TODO (bug 829201): history downloads should get the referrer from Places.
+ this.source = {
+ url: aPlacesNode.uri,
+ };
+ this.target = {
+ path: undefined,
+ exists: false,
+ size: undefined,
+ };
+
+ // In case this download cannot obtain its end time from the Places metadata,
+ // use the time from the Places node, that is the start time of the download.
+ this.endTime = aPlacesNode.time / 1000;
+}
+
+HistoryDownload.prototype = {
+ /**
+ * Pushes information from Places metadata into this object.
+ */
+ updateFromMetaData(metaData) {
+ try {
+ this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
+ .getService(Ci.nsIFileProtocolHandler)
+ .getFileFromURLSpec(metaData.targetFileSpec).path;
+ } catch (ex) {
+ this.target.path = undefined;
+ }
+
+ if ("state" in metaData) {
+ this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED;
+ this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED ||
+ metaData.state == nsIDM.DOWNLOAD_PAUSED;
+ this.endTime = metaData.endTime;
+
+ // Recreate partial error information from the state saved in history.
+ if (metaData.state == nsIDM.DOWNLOAD_FAILED) {
+ this.error = { message: "History download failed." };
+ } else if (metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL) {
+ this.error = { becauseBlockedByParentalControls: true };
+ } else if (metaData.state == nsIDM.DOWNLOAD_DIRTY) {
+ this.error = {
+ becauseBlockedByReputationCheck: true,
+ reputationCheckVerdict: metaData.reputationCheckVerdict || "",
+ };
+ } else {
+ this.error = null;
+ }
+
+ // Normal history downloads are assumed to exist until the user interface
+ // is refreshed, at which point these values may be updated.
+ this.target.exists = true;
+ this.target.size = metaData.fileSize;
+ } else {
+ // Metadata might be missing from a download that has started but hasn't
+ // stopped already. Normally, this state is overridden with the one from
+ // the corresponding in-progress session download. But if the browser is
+ // terminated abruptly and additionally the file with information about
+ // in-progress downloads is lost, we may end up using this state. We use
+ // the failed state to allow the download to be restarted.
+ //
+ // On the other hand, if the download is missing the target file
+ // annotation as well, it is just a very old one, and we can assume it
+ // succeeded.
+ this.succeeded = !this.target.path;
+ this.error = this.target.path ? { message: "Unstarted download." } : null;
+ this.canceled = false;
+
+ // These properties may be updated if the user interface is refreshed.
+ this.target.exists = false;
+ this.target.size = undefined;
+ }
+ },
+
+ /**
+ * History downloads are never in progress.
+ */
+ stopped: true,
+
+ /**
+ * No percentage indication is shown for history downloads.
+ */
+ hasProgress: false,
+
+ /**
+ * History downloads cannot be restarted using their partial data, even if
+ * they are indicated as paused in their Places metadata. The only way is to
+ * use the information from a persisted session download, that will be shown
+ * instead of the history download. In case this session download is not
+ * available, we show the history download as canceled, not paused.
+ */
+ hasPartialData: false,
+
+ /**
+ * This method mimicks the "start" method of session downloads, and is called
+ * when the user retries a history download.
+ *
+ * At present, we always ask the user for a new target path when retrying a
+ * history download. In the future we may consider reusing the known target
+ * path if the folder still exists and the file name is not already used,
+ * except when the user preferences indicate that the target path should be
+ * requested every time a new download is started.
+ */
+ start() {
+ let browserWin = RecentWindow.getMostRecentBrowserWindow();
+ let initiatingDoc = browserWin ? browserWin.document : document;
+
+ // Do not suggest a file name if we don't know the original target.
+ let leafName = this.target.path ? OS.Path.basename(this.target.path) : null;
+ DownloadURL(this.source.url, leafName, initiatingDoc);
+
+ return Promise.resolve();
+ },
+
+ /**
+ * This method mimicks the "refresh" method of session downloads, except that
+ * it cannot notify that the data changed to the Downloads View.
+ */
+ refresh: Task.async(function* () {
+ try {
+ this.target.size = (yield OS.File.stat(this.target.path)).size;
+ this.target.exists = true;
+ } catch (ex) {
+ // We keep the known file size from the metadata, if any.
+ this.target.exists = false;
+ }
+ }),
+};
+
+/**
+ * A download element shell is responsible for handling the commands and the
+ * displayed data for a single download view element.
+ *
+ * The shell may contain a session download, a history download, or both. When
+ * both a history and a session download are present, the session download gets
+ * priority and its information is displayed.
+ *
+ * On construction, a new richlistitem is created, and can be accessed through
+ * the |element| getter. The shell doesn't insert the item in a richlistbox, the
+ * caller must do it and remove the element when it's no longer needed.
+ *
+ * The caller is also responsible for forwarding status notifications for
+ * session downloads, calling the onStateChanged and onChanged methods.
+ *
+ * @param [optional] aSessionDownload
+ * The session download, required if aHistoryDownload is not set.
+ * @param [optional] aHistoryDownload
+ * The history download, required if aSessionDownload is not set.
+ */
+function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) {
+ this.element = document.createElement("richlistitem");
+ this.element._shell = this;
+
+ this.element.classList.add("download");
+ this.element.classList.add("download-state");
+
+ if (aSessionDownload) {
+ this.sessionDownload = aSessionDownload;
+ }
+ if (aHistoryDownload) {
+ this.historyDownload = aHistoryDownload;
+ }
+}
+
+HistoryDownloadElementShell.prototype = {
+ __proto__: DownloadsViewUI.DownloadElementShell.prototype,
+
+ /**
+ * Manages the "active" state of the shell. By default all the shells without
+ * a session download are inactive, thus their UI is not updated. They must
+ * be activated when entering the visible area. Session downloads are always
+ * active.
+ */
+ ensureActive() {
+ if (!this._active) {
+ this._active = true;
+ this.element.setAttribute("active", true);
+ this._updateUI();
+ }
+ },
+ get active() {
+ return !!this._active;
+ },
+
+ /**
+ * Overrides the base getter to return the Download or HistoryDownload object
+ * for displaying information and executing commands in the user interface.
+ */
+ get download() {
+ return this._sessionDownload || this._historyDownload;
+ },
+
+ _sessionDownload: null,
+ get sessionDownload() {
+ return this._sessionDownload;
+ },
+ set sessionDownload(aValue) {
+ if (this._sessionDownload != aValue) {
+ if (!aValue && !this._historyDownload) {
+ throw new Error("Should always have either a Download or a HistoryDownload");
+ }
+
+ this._sessionDownload = aValue;
+
+ this.ensureActive();
+ this._updateUI();
+ }
+ return aValue;
+ },
+
+ _historyDownload: null,
+ get historyDownload() {
+ return this._historyDownload;
+ },
+ set historyDownload(aValue) {
+ if (this._historyDownload != aValue) {
+ if (!aValue && !this._sessionDownload) {
+ throw new Error("Should always have either a Download or a HistoryDownload");
+ }
+
+ this._historyDownload = aValue;
+
+ // We don't need to update the UI if we had a session data item, because
+ // the places information isn't used in this case.
+ if (!this._sessionDownload) {
+ this._updateUI();
+ }
+ }
+ return aValue;
+ },
+
+ _updateUI() {
+ // There is nothing to do if the item has always been invisible.
+ if (!this.active) {
+ return;
+ }
+
+ // Since the state changed, we may need to check the target file again.
+ this._targetFileChecked = false;
+
+ this._updateState();
+ },
+
+ get statusTextAndTip() {
+ let status = this.rawStatusTextAndTip;
+
+ // The base object would show extended progress information in the tooltip,
+ // but we move this to the main view and never display a tooltip.
+ if (!this.download.stopped) {
+ status.text = status.tip;
+ }
+ status.tip = "";
+
+ return status;
+ },
+
+ onStateChanged() {
+ this._updateState();
+
+ if (this.element.selected) {
+ goUpdateDownloadCommands();
+ } else {
+ // If a state change occurs in an item that is not currently selected,
+ // this is the only command that may be affected.
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ }
+ },
+
+ onChanged() {
+ // This cannot be placed within onStateChanged because
+ // when a download goes from hasBlockedData to !hasBlockedData
+ // it will still remain in the same state.
+ this.element.classList.toggle("temporary-block",
+ !!this.download.hasBlockedData);
+ this._updateProgress();
+ },
+
+ isCommandEnabled(aCommand) {
+ // The only valid command for inactive elements is cmd_delete.
+ if (!this.active && aCommand != "cmd_delete") {
+ return false;
+ }
+ switch (aCommand) {
+ case "downloadsCmd_open":
+ // This property is false if the download did not succeed.
+ return this.download.target.exists;
+ case "downloadsCmd_show":
+ // TODO: Bug 827010 - Handle part-file asynchronously.
+ if (this._sessionDownload && this.download.target.partFilePath) {
+ let partFile = new FileUtils.File(this.download.target.partFilePath);
+ if (partFile.exists()) {
+ return true;
+ }
+ }
+
+ // This property is false if the download did not succeed.
+ return this.download.target.exists;
+ case "cmd_delete":
+ // We don't want in-progress downloads to be removed accidentally.
+ return this.download.stopped;
+ case "downloadsCmd_cancel":
+ return !!this._sessionDownload;
+ }
+ return DownloadsViewUI.DownloadElementShell.prototype
+ .isCommandEnabled.call(this, aCommand);
+ },
+
+ doCommand(aCommand) {
+ if (DownloadsViewUI.isCommandName(aCommand)) {
+ this[aCommand]();
+ }
+ },
+
+ downloadsCmd_open() {
+ let file = new FileUtils.File(this.download.target.path);
+ DownloadsCommon.openDownloadedFile(file, null, window);
+ },
+
+ downloadsCmd_show() {
+ let file = new FileUtils.File(this.download.target.path);
+ DownloadsCommon.showDownloadedFile(file);
+ },
+
+ downloadsCmd_openReferrer() {
+ openURL(this.download.source.referrer);
+ },
+
+ cmd_delete() {
+ if (this._sessionDownload) {
+ DownloadsCommon.removeAndFinalizeDownload(this.download);
+ }
+ if (this._historyDownload) {
+ let uri = NetUtil.newURI(this.download.source.url);
+ PlacesUtils.bhistory.removePage(uri);
+ }
+ },
+
+ downloadsCmd_unblock() {
+ this.confirmUnblock(window, "unblock");
+ },
+
+ downloadsCmd_chooseUnblock() {
+ this.confirmUnblock(window, "chooseUnblock");
+ },
+
+ downloadsCmd_chooseOpen() {
+ this.confirmUnblock(window, "chooseOpen");
+ },
+
+ // Returns whether or not the download handled by this shell should
+ // show up in the search results for the given term. Both the display
+ // name for the download and the url are searched.
+ matchesSearchTerm(aTerm) {
+ if (!aTerm) {
+ return true;
+ }
+ aTerm = aTerm.toLowerCase();
+ return this.displayName.toLowerCase().includes(aTerm) ||
+ this.download.source.url.toLowerCase().includes(aTerm);
+ },
+
+ // Handles return keypress on the element (the keypress listener is
+ // set in the DownloadsPlacesView object).
+ doDefaultCommand() {
+ let command = this.currentDefaultCommandName;
+ if (command && this.isCommandEnabled(command)) {
+ this.doCommand(command);
+ }
+ },
+
+ /**
+ * This method is called by the outer download view, after the controller
+ * commands have already been updated. In case we did not check for the
+ * existence of the target file already, we can do it now and then update
+ * the commands as needed.
+ */
+ onSelect() {
+ if (!this.active) {
+ return;
+ }
+
+ // If this is a history download for which no target file information is
+ // available, we cannot retrieve information about the target file.
+ if (!this.download.target.path) {
+ return;
+ }
+
+ // Start checking for existence. This may be done twice if onSelect is
+ // called again before the information is collected.
+ if (!this._targetFileChecked) {
+ this._checkTargetFileOnSelect().catch(Cu.reportError);
+ }
+ },
+
+ _checkTargetFileOnSelect: Task.async(function* () {
+ try {
+ yield this.download.refresh();
+ } finally {
+ // Do not try to check for existence again if this failed once.
+ this._targetFileChecked = true;
+ }
+
+ // Update the commands only if the element is still selected.
+ if (this.element.selected) {
+ goUpdateDownloadCommands();
+ }
+
+ // Ensure the interface has been updated based on the new values. We need to
+ // do this because history downloads can't trigger update notifications.
+ this._updateProgress();
+ }),
+};
+
+/**
+ * Relays commands from the download.xml binding to the selected items.
+ */
+const DownloadsView = {
+ onDownloadCommand(event, command) {
+ goDoCommand(command);
+ },
+
+ onDownloadClick() {},
+};
+
+/**
+ * A Downloads Places View is a places view designed to show a places query
+ * for history downloads alongside the session downloads.
+ *
+ * As we don't use the places controller, some methods implemented by other
+ * places views are not implemented by this view.
+ *
+ * A richlistitem in this view can represent either a past download or a session
+ * download, or both. Session downloads are shown first in the view, and as long
+ * as they exist they "collapses" their history "counterpart" (So we don't show two
+ * items for every download).
+ */
+function DownloadsPlacesView(aRichListBox, aActive = true) {
+ this._richlistbox = aRichListBox;
+ this._richlistbox._placesView = this;
+ window.controllers.insertControllerAt(0, this);
+
+ // Map download URLs to download element shells regardless of their type
+ this._downloadElementsShellsForURI = new Map();
+
+ // Map download data items to their element shells.
+ this._viewItemsForDownloads = new WeakMap();
+
+ // Points to the last session download element. We keep track of this
+ // in order to keep all session downloads above past downloads.
+ this._lastSessionDownloadElement = null;
+
+ this._searchTerm = "";
+
+ this._active = aActive;
+
+ // Register as a downloads view. The places data will be initialized by
+ // the places setter.
+ this._initiallySelectedElement = null;
+ this._downloadsData = DownloadsCommon.getData(window.opener || window);
+ this._downloadsData.addView(this);
+
+ // Get the Download button out of the attention state since we're about to
+ // view all downloads.
+ DownloadsCommon.getIndicatorData(window).attention = DownloadsCommon.ATTENTION_NONE;
+
+ // Make sure to unregister the view if the window is closed.
+ window.addEventListener("unload", () => {
+ window.controllers.removeController(this);
+ this._downloadsData.removeView(this);
+ this.result = null;
+ }, true);
+ // Resizing the window may change items visibility.
+ window.addEventListener("resize", () => {
+ this._ensureVisibleElementsAreActive();
+ }, true);
+}
+
+DownloadsPlacesView.prototype = {
+ get associatedElement() {
+ return this._richlistbox;
+ },
+
+ get active() {
+ return this._active;
+ },
+ set active(val) {
+ this._active = val;
+ if (this._active)
+ this._ensureVisibleElementsAreActive();
+ return this._active;
+ },
+
+ /**
+ * This cache exists in order to optimize the load of the Downloads View, when
+ * Places annotations for history downloads must be read. In fact, annotations
+ * are stored in a single table, and reading all of them at once is much more
+ * efficient than an individual query.
+ *
+ * When this property is first requested, it reads the annotations for all the
+ * history downloads and stores them indefinitely.
+ *
+ * The historical annotations are not expected to change for the duration of
+ * the session, except in the case where a session download is running for the
+ * same URI as a history download. To ensure we don't use stale data, URIs
+ * corresponding to session downloads are permanently removed from the cache.
+ * This is a very small mumber compared to history downloads.
+ *
+ * This property returns a Map from each download source URI found in Places
+ * annotations to an object with the format:
+ *
+ * { targetFileSpec, state, endTime, fileSize, ... }
+ *
+ * The targetFileSpec property is the value of "downloads/destinationFileURI",
+ * while the other properties are taken from "downloads/metaData". Any of the
+ * properties may be missing from the object.
+ */
+ get _cachedPlacesMetaData() {
+ if (!this.__cachedPlacesMetaData) {
+ this.__cachedPlacesMetaData = new Map();
+
+ // Read the metadata annotations first, but ignore invalid JSON.
+ for (let result of PlacesUtils.annotations.getAnnotationsWithName(
+ DOWNLOAD_META_DATA_ANNO)) {
+ try {
+ this.__cachedPlacesMetaData.set(result.uri.spec,
+ JSON.parse(result.annotationValue));
+ } catch (ex) {}
+ }
+
+ // Add the target file annotations to the metadata.
+ for (let result of PlacesUtils.annotations.getAnnotationsWithName(
+ DESTINATION_FILE_URI_ANNO)) {
+ let metaData = this.__cachedPlacesMetaData.get(result.uri.spec);
+ if (!metaData) {
+ metaData = {};
+ this.__cachedPlacesMetaData.set(result.uri.spec, metaData);
+ }
+ metaData.targetFileSpec = result.annotationValue;
+ }
+ }
+
+ return this.__cachedPlacesMetaData;
+ },
+ __cachedPlacesMetaData: null,
+
+ /**
+ * Reads current metadata from Places annotations for the specified URI, and
+ * returns an object with the format:
+ *
+ * { targetFileSpec, state, endTime, fileSize, ... }
+ *
+ * The targetFileSpec property is the value of "downloads/destinationFileURI",
+ * while the other properties are taken from "downloads/metaData". Any of the
+ * properties may be missing from the object.
+ */
+ _getPlacesMetaDataFor(spec) {
+ let metaData = {};
+
+ try {
+ let uri = NetUtil.newURI(spec);
+ try {
+ metaData = JSON.parse(PlacesUtils.annotations.getPageAnnotation(
+ uri, DOWNLOAD_META_DATA_ANNO));
+ } catch (ex) {}
+ metaData.targetFileSpec = PlacesUtils.annotations.getPageAnnotation(
+ uri, DESTINATION_FILE_URI_ANNO);
+ } catch (ex) {}
+
+ return metaData;
+ },
+
+ /**
+ * Given a data item for a session download, or a places node for a past
+ * download, updates the view as necessary.
+ * 1. If the given data is a places node, we check whether there are any
+ * elements for the same download url. If there are, then we just reset
+ * their places node. Otherwise we add a new download element.
+ * 2. If the given data is a data item, we first check if there's a history
+ * download in the list that is not associated with a data item. If we
+ * found one, we use it for the data item as well and reposition it
+ * alongside the other session downloads. If we don't, then we go ahead
+ * and create a new element for the download.
+ *
+ * @param [optional] sessionDownload
+ * A Download object, or null for history downloads.
+ * @param [optional] aPlacesNode
+ * The Places node for a history download, or null for session downloads.
+ * @param [optional] aNewest
+ * @see onDownloadAdded. Ignored for history downloads.
+ * @param [optional] aDocumentFragment
+ * To speed up the appending of multiple elements to the end of the
+ * list which are coming in a single batch (i.e. invalidateContainer),
+ * a document fragment may be passed to which the new elements would
+ * be appended. It's the caller's job to ensure the fragment is merged
+ * to the richlistbox at the end.
+ */
+ _addDownloadData(sessionDownload, aPlacesNode, aNewest = false,
+ aDocumentFragment = null) {
+ let downloadURI = aPlacesNode ? aPlacesNode.uri
+ : sessionDownload.source.url;
+ let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
+ if (!shellsForURI) {
+ shellsForURI = new Set();
+ this._downloadElementsShellsForURI.set(downloadURI, shellsForURI);
+ }
+
+ // When a session download is attached to a shell, we ensure not to keep
+ // stale metadata around for the corresponding history download. This
+ // prevents stale state from being used if the view is rebuilt.
+ //
+ // Note that we will eagerly load the data in the cache at this point, even
+ // if we have seen no history download. The case where no history download
+ // will appear at all is rare enough in normal usage, so we can apply this
+ // simpler solution rather than keeping a list of cache items to ignore.
+ if (sessionDownload) {
+ this._cachedPlacesMetaData.delete(sessionDownload.source.url);
+ }
+
+ let newOrUpdatedShell = null;
+
+ // Trivial: if there are no shells for this download URI, we always
+ // need to create one.
+ let shouldCreateShell = shellsForURI.size == 0;
+
+ // However, if we do have shells for this download uri, there are
+ // few options:
+ // 1) There's only one shell and it's for a history download (it has
+ // no data item). In this case, we update this shell and move it
+ // if necessary
+ // 2) There are multiple shells, indicating multiple downloads for
+ // the same download uri are running. In this case we create
+ // another shell for the download (so we have one shell for each data
+ // item).
+ //
+ // Note: If a cancelled session download is already in the list, and the
+ // download is retried, onDownloadAdded is called again for the same
+ // data item. Thus, we also check that we make sure we don't have a view item
+ // already.
+ if (!shouldCreateShell &&
+ sessionDownload && !this._viewItemsForDownloads.has(sessionDownload)) {
+ // If there's a past-download-only shell for this download-uri with no
+ // associated data item, use it for the new data item. Otherwise, go ahead
+ // and create another shell.
+ shouldCreateShell = true;
+ for (let shell of shellsForURI) {
+ if (!shell.sessionDownload) {
+ shouldCreateShell = false;
+ shell.sessionDownload = sessionDownload;
+ newOrUpdatedShell = shell;
+ this._viewItemsForDownloads.set(sessionDownload, shell);
+ break;
+ }
+ }
+ }
+
+ if (shouldCreateShell) {
+ // If we are adding a new history download here, it means there is no
+ // associated session download, thus we must read the Places metadata,
+ // because it will not be obscured by the session download.
+ let historyDownload = null;
+ if (aPlacesNode) {
+ let metaData = this._cachedPlacesMetaData.get(aPlacesNode.uri) ||
+ this._getPlacesMetaDataFor(aPlacesNode.uri);
+ historyDownload = new HistoryDownload(aPlacesNode);
+ historyDownload.updateFromMetaData(metaData);
+ }
+ let shell = new HistoryDownloadElementShell(sessionDownload,
+ historyDownload);
+ shell.element._placesNode = aPlacesNode;
+ newOrUpdatedShell = shell;
+ shellsForURI.add(shell);
+ if (sessionDownload) {
+ this._viewItemsForDownloads.set(sessionDownload, shell);
+ }
+ } else if (aPlacesNode) {
+ // We are updating information for a history download for which we have
+ // at least one download element shell already. There are two cases:
+ // 1) There are one or more download element shells for this source URI,
+ // each with an associated session download. We update the Places node
+ // because we may need it later, but we don't need to read the Places
+ // metadata until the last session download is removed.
+ // 2) Occasionally, we may receive a duplicate notification for a history
+ // download with no associated session download. We have exactly one
+ // download element shell in this case, but the metdata cannot have
+ // changed, just the reference to the Places node object is different.
+ // So, we update all the node references and keep the metadata intact.
+ for (let shell of shellsForURI) {
+ if (!shell.historyDownload) {
+ // Create the element to host the metadata when needed.
+ shell.historyDownload = new HistoryDownload(aPlacesNode);
+ }
+ shell.element._placesNode = aPlacesNode;
+ }
+ }
+
+ if (newOrUpdatedShell) {
+ if (aNewest) {
+ this._richlistbox.insertBefore(newOrUpdatedShell.element,
+ this._richlistbox.firstChild);
+ if (!this._lastSessionDownloadElement) {
+ this._lastSessionDownloadElement = newOrUpdatedShell.element;
+ }
+ // Some operations like retrying an history download move an element to
+ // the top of the richlistbox, along with other session downloads.
+ // More generally, if a new download is added, should be made visible.
+ this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element);
+ } else if (sessionDownload) {
+ let before = this._lastSessionDownloadElement ?
+ this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
+ this._richlistbox.insertBefore(newOrUpdatedShell.element, before);
+ this._lastSessionDownloadElement = newOrUpdatedShell.element;
+ } else {
+ let appendTo = aDocumentFragment || this._richlistbox;
+ appendTo.appendChild(newOrUpdatedShell.element);
+ }
+
+ if (this.searchTerm) {
+ newOrUpdatedShell.element.hidden =
+ !newOrUpdatedShell.element._shell.matchesSearchTerm(this.searchTerm);
+ }
+ }
+
+ // If aDocumentFragment is defined this is a batch change, so it's up to
+ // the caller to append the fragment and activate the visible shells.
+ if (!aDocumentFragment) {
+ this._ensureVisibleElementsAreActive();
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ }
+ },
+
+ _removeElement(aElement) {
+ // If the element was selected exclusively, select its next
+ // sibling first, if not, try for previous sibling, if any.
+ if ((aElement.nextSibling || aElement.previousSibling) &&
+ this._richlistbox.selectedItems &&
+ this._richlistbox.selectedItems.length == 1 &&
+ this._richlistbox.selectedItems[0] == aElement) {
+ this._richlistbox.selectItem(aElement.nextSibling ||
+ aElement.previousSibling);
+ }
+
+ if (this._lastSessionDownloadElement == aElement) {
+ this._lastSessionDownloadElement = aElement.previousSibling;
+ }
+
+ this._richlistbox.removeItemFromSelection(aElement);
+ this._richlistbox.removeChild(aElement);
+ this._ensureVisibleElementsAreActive();
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ },
+
+ _removeHistoryDownloadFromView(aPlacesNode) {
+ let downloadURI = aPlacesNode.uri;
+ let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
+ if (shellsForURI) {
+ for (let shell of shellsForURI) {
+ if (shell.sessionDownload) {
+ shell.historyDownload = null;
+ } else {
+ this._removeElement(shell.element);
+ shellsForURI.delete(shell);
+ if (shellsForURI.size == 0)
+ this._downloadElementsShellsForURI.delete(downloadURI);
+ }
+ }
+ }
+ },
+
+ _removeSessionDownloadFromView(download) {
+ let shells = this._downloadElementsShellsForURI
+ .get(download.source.url);
+ if (shells.size == 0) {
+ throw new Error("Should have had at leaat one shell for this uri");
+ }
+
+ let shell = this._viewItemsForDownloads.get(download);
+ if (!shells.has(shell)) {
+ throw new Error("Missing download element shell in shells list for url");
+ }
+
+ // If there's more than one item for this download uri, we can let the
+ // view item for this this particular data item go away.
+ // If there's only one item for this download uri, we should only
+ // keep it if it is associated with a history download.
+ if (shells.size > 1 || !shell.historyDownload) {
+ this._removeElement(shell.element);
+ shells.delete(shell);
+ if (shells.size == 0) {
+ this._downloadElementsShellsForURI.delete(download.source.url);
+ }
+ } else {
+ // We have one download element shell containing both a session download
+ // and a history download, and we are now removing the session download.
+ // Previously, we did not use the Places metadata because it was obscured
+ // by the session download. Since this is no longer the case, we have to
+ // read the latest metadata before removing the session download.
+ let url = shell.historyDownload.source.url;
+ let metaData = this._getPlacesMetaDataFor(url);
+ shell.historyDownload.updateFromMetaData(metaData);
+ shell.sessionDownload = null;
+ // Move it below the session-download items;
+ if (this._lastSessionDownloadElement == shell.element) {
+ this._lastSessionDownloadElement = shell.element.previousSibling;
+ } else {
+ let before = this._lastSessionDownloadElement ?
+ this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
+ this._richlistbox.insertBefore(shell.element, before);
+ }
+ }
+ },
+
+ _ensureVisibleElementsAreActive() {
+ if (!this.active || this._ensureVisibleTimer ||
+ !this._richlistbox.firstChild) {
+ return;
+ }
+
+ this._ensureVisibleTimer = setTimeout(() => {
+ delete this._ensureVisibleTimer;
+ if (!this._richlistbox.firstChild) {
+ return;
+ }
+
+ let rlbRect = this._richlistbox.getBoundingClientRect();
+ let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let nodes = winUtils.nodesFromRect(rlbRect.left, rlbRect.top,
+ 0, rlbRect.width, rlbRect.height, 0,
+ true, false);
+ // nodesFromRect returns nodes in z-index order, and for the same z-index
+ // sorts them in inverted DOM order, thus starting from the one that would
+ // be on top.
+ let firstVisibleNode, lastVisibleNode;
+ for (let node of nodes) {
+ if (node.localName === "richlistitem" && node._shell) {
+ node._shell.ensureActive();
+ // The first visible node is the last match.
+ firstVisibleNode = node;
+ // While the last visible node is the first match.
+ if (!lastVisibleNode) {
+ lastVisibleNode = node;
+ }
+ }
+ }
+
+ // Also activate the first invisible nodes in both boundaries (that is,
+ // above and below the visible area) to ensure proper keyboard navigation
+ // in both directions.
+ let nodeBelowVisibleArea = lastVisibleNode && lastVisibleNode.nextSibling;
+ if (nodeBelowVisibleArea && nodeBelowVisibleArea._shell) {
+ nodeBelowVisibleArea._shell.ensureActive();
+ }
+
+ let nodeAboveVisibleArea = firstVisibleNode &&
+ firstVisibleNode.previousSibling;
+ if (nodeAboveVisibleArea && nodeAboveVisibleArea._shell) {
+ nodeAboveVisibleArea._shell.ensureActive();
+ }
+ }, 10);
+ },
+
+ _place: "",
+ get place() {
+ return this._place;
+ },
+ set place(val) {
+ // Don't reload everything if we don't have to.
+ if (this._place == val) {
+ // XXXmano: places.js relies on this behavior (see Bug 822203).
+ this.searchTerm = "";
+ return val;
+ }
+
+ this._place = val;
+
+ let history = PlacesUtils.history;
+ let queries = { }, options = { };
+ history.queryStringToQueries(val, queries, { }, options);
+ if (!queries.value.length) {
+ queries.value = [history.getNewQuery()];
+ }
+
+ let result = history.executeQueries(queries.value, queries.value.length,
+ options.value);
+ result.addObserver(this, false);
+ return val;
+ },
+
+ _result: null,
+ get result() {
+ return this._result;
+ },
+ set result(val) {
+ if (this._result == val) {
+ return val;
+ }
+
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._resultNode.containerOpen = false;
+ }
+
+ if (val) {
+ this._result = val;
+ this._resultNode = val.root;
+ this._resultNode.containerOpen = true;
+ this._ensureInitialSelection();
+ } else {
+ delete this._resultNode;
+ delete this._result;
+ }
+
+ return val;
+ },
+
+ get selectedNodes() {
+ return [for (element of this._richlistbox.selectedItems)
+ if (element._placesNode)
+ element._placesNode];
+ },
+
+ get selectedNode() {
+ let selectedNodes = this.selectedNodes;
+ return selectedNodes.length == 1 ? selectedNodes[0] : null;
+ },
+
+ get hasSelection() {
+ return this.selectedNodes.length > 0;
+ },
+
+ containerStateChanged(aNode, aOldState, aNewState) {
+ this.invalidateContainer(aNode)
+ },
+
+ invalidateContainer(aContainer) {
+ if (aContainer != this._resultNode) {
+ throw new Error("Unexpected container node");
+ }
+ if (!aContainer.containerOpen) {
+ throw new Error("Root container for the downloads query cannot be closed");
+ }
+
+ let suppressOnSelect = this._richlistbox.suppressOnSelect;
+ this._richlistbox.suppressOnSelect = true;
+ try {
+ // Remove the invalidated history downloads from the list and unset the
+ // places node for data downloads.
+ // Loop backwards since _removeHistoryDownloadFromView may removeChild().
+ for (let i = this._richlistbox.childNodes.length - 1; i >= 0; --i) {
+ let element = this._richlistbox.childNodes[i];
+ if (element._placesNode) {
+ this._removeHistoryDownloadFromView(element._placesNode);
+ }
+ }
+ } finally {
+ this._richlistbox.suppressOnSelect = suppressOnSelect;
+ }
+
+ if (aContainer.childCount > 0) {
+ let elementsToAppendFragment = document.createDocumentFragment();
+ for (let i = 0; i < aContainer.childCount; i++) {
+ try {
+ this._addDownloadData(null, aContainer.getChild(i), false,
+ elementsToAppendFragment);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ // _addDownloadData may not add new elements if there were already
+ // data items in place.
+ if (elementsToAppendFragment.firstChild) {
+ this._appendDownloadsFragment(elementsToAppendFragment);
+ this._ensureVisibleElementsAreActive();
+ }
+ }
+
+ goUpdateDownloadCommands();
+ },
+
+ _appendDownloadsFragment(aDOMFragment) {
+ // Workaround multiple reflows hang by removing the richlistbox
+ // and adding it back when we're done.
+
+ // Hack for bug 836283: reset xbl fields to their old values after the
+ // binding is reattached to avoid breaking the selection state
+ let xblFields = new Map();
+ for (let key of Object.getOwnPropertyNames(this._richlistbox)) {
+ let value = this._richlistbox[key];
+ xblFields.set(key, value);
+ }
+
+ let parentNode = this._richlistbox.parentNode;
+ let nextSibling = this._richlistbox.nextSibling;
+ parentNode.removeChild(this._richlistbox);
+ this._richlistbox.appendChild(aDOMFragment);
+ parentNode.insertBefore(this._richlistbox, nextSibling);
+
+ for (let [key, value] of xblFields) {
+ this._richlistbox[key] = value;
+ }
+ },
+
+ nodeInserted(aParent, aPlacesNode) {
+ this._addDownloadData(null, aPlacesNode);
+ },
+
+ nodeRemoved(aParent, aPlacesNode, aOldIndex) {
+ this._removeHistoryDownloadFromView(aPlacesNode);
+ },
+
+ nodeAnnotationChanged() {},
+ nodeIconChanged() {},
+ nodeTitleChanged() {},
+ nodeKeywordChanged() {},
+ nodeDateAddedChanged() {},
+ nodeLastModifiedChanged() {},
+ nodeHistoryDetailsChanged() {},
+ nodeTagsChanged() {},
+ sortingChanged() {},
+ nodeMoved() {},
+ nodeURIChanged() {},
+ batching() {},
+
+ get controller() {
+ return this._richlistbox.controller;
+ },
+
+ get searchTerm() {
+ return this._searchTerm;
+ },
+ set searchTerm(aValue) {
+ if (this._searchTerm != aValue) {
+ for (let element of this._richlistbox.childNodes) {
+ element.hidden = !element._shell.matchesSearchTerm(aValue);
+ }
+ this._ensureVisibleElementsAreActive();
+ }
+ return this._searchTerm = aValue;
+ },
+
+ /**
+ * When the view loads, we want to select the first item.
+ * However, because session downloads, for which the data is loaded
+ * asynchronously, always come first in the list, and because the list
+ * may (or may not) already contain history downloads at that point, it
+ * turns out that by the time we can select the first item, the user may
+ * have already started using the view.
+ * To make things even more complicated, in other cases, the places data
+ * may be loaded after the session downloads data. Thus we cannot rely on
+ * the order in which the data comes in.
+ * We work around this by attempting to select the first element twice,
+ * once after the places data is loaded and once when the session downloads
+ * data is done loading. However, if the selection has changed in-between,
+ * we assume the user has already started using the view and give up.
+ */
+ _ensureInitialSelection() {
+ // Either they're both null, or the selection has not changed in between.
+ if (this._richlistbox.selectedItem == this._initiallySelectedElement) {
+ let firstDownloadElement = this._richlistbox.firstChild;
+ if (firstDownloadElement != this._initiallySelectedElement) {
+ // We may be called before _ensureVisibleElementsAreActive,
+ // or before the download binding is attached. Therefore, ensure the
+ // first item is activated, and pass the item to the richlistbox
+ // setters only at a point we know for sure the binding is attached.
+ firstDownloadElement._shell.ensureActive();
+ Services.tm.mainThread.dispatch(() => {
+ this._richlistbox.selectedItem = firstDownloadElement;
+ this._richlistbox.currentItem = firstDownloadElement;
+ this._initiallySelectedElement = firstDownloadElement;
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ }
+ },
+
+ onDataLoadStarting() {},
+ onDataLoadCompleted() {
+ this._ensureInitialSelection();
+ },
+
+ onDownloadAdded(download, newest) {
+ this._addDownloadData(download, null, newest);
+ },
+
+ onDownloadStateChanged(download) {
+ this._viewItemsForDownloads.get(download).onStateChanged();
+ },
+
+ onDownloadChanged(download) {
+ this._viewItemsForDownloads.get(download).onChanged();
+ },
+
+ onDownloadRemoved(download) {
+ this._removeSessionDownloadFromView(download);
+ },
+
+ // nsIController
+ supportsCommand(aCommand) {
+ // Firstly, determine if this is a command that we can handle.
+ if (!DownloadsViewUI.isCommandName(aCommand)) {
+ return false;
+ }
+ if (!(aCommand in this) &&
+ !(aCommand in HistoryDownloadElementShell.prototype)) {
+ return false;
+ }
+ // If this function returns true, other controllers won't get a chance to
+ // process the command even if isCommandEnabled returns false, so it's
+ // important to check if the list is focused here to handle common commands
+ // like copy and paste correctly. The clear downloads command, instead, is
+ // specific to the downloads list but can be invoked from the toolbar, so we
+ // can just return true unconditionally.
+ return aCommand == "downloadsCmd_clearDownloads" ||
+ document.activeElement == this._richlistbox;
+ },
+
+ // nsIController
+ isCommandEnabled(aCommand) {
+ switch (aCommand) {
+ case "cmd_copy":
+ return this._richlistbox.selectedItems.length > 0;
+ case "cmd_selectAll":
+ return true;
+ case "cmd_paste":
+ return this._canDownloadClipboardURL();
+ case "downloadsCmd_clearDownloads":
+ return this._canClearDownloads();
+ default:
+ return Array.every(this._richlistbox.selectedItems,
+ element => element._shell.isCommandEnabled(aCommand));
+ }
+ },
+
+ _canClearDownloads() {
+ // Downloads can be cleared if there's at least one removable download in
+ // the list (either a history download or a completed session download).
+ // Because history downloads are always removable and are listed after the
+ // session downloads, check from bottom to top.
+ for (let elt = this._richlistbox.lastChild; elt; elt = elt.previousSibling) {
+ // Stopped, paused, and failed downloads with partial data are removed.
+ let download = elt._shell.download;
+ if (download.stopped && !(download.canceled && download.hasPartialData)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ _copySelectedDownloadsToClipboard() {
+ let urls = [for (element of this._richlistbox.selectedItems)
+ element._shell.download.source.url];
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(urls.join("\n"));
+ },
+
+ _getURLFromClipboardData() {
+ let trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(null);
+
+ let flavors = ["text/x-moz-url", "text/unicode"];
+ flavors.forEach(trans.addDataFlavor);
+
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ // Getting the data or creating the nsIURI might fail.
+ try {
+ let data = {};
+ trans.getAnyTransferData({}, data, {});
+ let [url, name] = data.value.QueryInterface(Ci.nsISupportsString)
+ .data.split("\n");
+ if (url) {
+ return [NetUtil.newURI(url, null, null).spec, name];
+ }
+ } catch (ex) {}
+
+ return ["", ""];
+ },
+
+ _canDownloadClipboardURL() {
+ let [url, name] = this._getURLFromClipboardData();
+ return url != "";
+ },
+
+ _downloadURLFromClipboard() {
+ let [url, name] = this._getURLFromClipboardData();
+ let browserWin = RecentWindow.getMostRecentBrowserWindow();
+ let initiatingDoc = browserWin ? browserWin.document : document;
+ DownloadURL(url, name, initiatingDoc);
+ },
+
+ // nsIController
+ doCommand(aCommand) {
+ // Commands may be invoked with keyboard shortcuts even if disabled.
+ if (!this.isCommandEnabled(aCommand)) {
+ return;
+ }
+
+ // If this command is not selection-specific, execute it.
+ if (aCommand in this) {
+ this[aCommand]();
+ return;
+ }
+
+ // Cloning the nodelist into an array to get a frozen list of selected items.
+ // Otherwise, the selectedItems nodelist is live and doCommand may alter the
+ // selection while we are trying to do one particular action, like removing
+ // items from history.
+ let selectedElements = [...this._richlistbox.selectedItems];
+ for (let element of selectedElements) {
+ element._shell.doCommand(aCommand);
+ }
+ },
+
+ // nsIController
+ onEvent() {},
+
+ cmd_copy() {
+ this._copySelectedDownloadsToClipboard();
+ },
+
+ cmd_selectAll() {
+ this._richlistbox.selectAll();
+ },
+
+ cmd_paste() {
+ this._downloadURLFromClipboard();
+ },
+
+ downloadsCmd_clearDownloads() {
+ this._downloadsData.removeFinished();
+ if (this.result) {
+ Cc["@mozilla.org/browser/download-history;1"]
+ .getService(Ci.nsIDownloadHistory)
+ .removeAllDownloads();
+ }
+ // There may be no selection or focus change as a result
+ // of these change, and we want the command updated immediately.
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ },
+
+ onContextMenu(aEvent) {
+ let element = this._richlistbox.selectedItem;
+ if (!element || !element._shell) {
+ return false;
+ }
+
+ // Set the state attribute so that only the appropriate items are displayed.
+ let contextMenu = document.getElementById("downloadsContextMenu");
+ let download = element._shell.download;
+ contextMenu.setAttribute("state",
+ DownloadsCommon.stateOfDownload(download));
+ contextMenu.classList.toggle("temporary-block",
+ !!download.hasBlockedData);
+
+ if (!download.stopped) {
+ // The hasPartialData property of a download may change at any time after
+ // it has started, so ensure we update the related command now.
+ goUpdateCommand("downloadsCmd_pauseResume");
+ }
+
+ return true;
+ },
+
+ onKeyPress(aEvent) {
+ let selectedElements = this._richlistbox.selectedItems;
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
+ // In the content tree, opening bookmarks by pressing return is only
+ // supported when a single item is selected. To be consistent, do the
+ // same here.
+ if (selectedElements.length == 1) {
+ let element = selectedElements[0];
+ if (element._shell) {
+ element._shell.doDefaultCommand();
+ }
+ }
+ }
+ else if (aEvent.charCode == " ".charCodeAt(0)) {
+ // Pause/Resume every selected download
+ for (let element of selectedElements) {
+ if (element._shell.isCommandEnabled("downloadsCmd_pauseResume")) {
+ element._shell.doCommand("downloadsCmd_pauseResume");
+ }
+ }
+ }
+ },
+
+ onDoubleClick(aEvent) {
+ if (aEvent.button != 0) {
+ return;
+ }
+
+ let selectedElements = this._richlistbox.selectedItems;
+ if (selectedElements.length != 1) {
+ return;
+ }
+
+ let element = selectedElements[0];
+ if (element._shell) {
+ element._shell.doDefaultCommand();
+ }
+ },
+
+ onScroll() {
+ this._ensureVisibleElementsAreActive();
+ },
+
+ onSelect() {
+ goUpdateDownloadCommands();
+
+ let selectedElements = this._richlistbox.selectedItems;
+ for (let elt of selectedElements) {
+ if (elt._shell) {
+ elt._shell.onSelect();
+ }
+ }
+ },
+
+ onDragStart(aEvent) {
+ // TODO Bug 831358: Support d&d for multiple selection.
+ // For now, we just drag the first element.
+ let selectedItem = this._richlistbox.selectedItem;
+ if (!selectedItem) {
+ return;
+ }
+
+ let targetPath = selectedItem._shell.download.target.path;
+ if (!targetPath) {
+ return;
+ }
+
+ // We must check for existence synchronously because this is a DOM event.
+ let file = new FileUtils.File(targetPath);
+ if (!file.exists()) {
+ return;
+ }
+
+ let dt = aEvent.dataTransfer;
+ dt.mozSetDataAt("application/x-moz-file", file, 0);
+ let url = Services.io.newFileURI(file).spec;
+ dt.setData("text/uri-list", url);
+ dt.setData("text/plain", url);
+ dt.effectAllowed = "copyMove";
+ dt.addElement(selectedItem);
+ },
+
+ onDragOver(aEvent) {
+ let types = aEvent.dataTransfer.types;
+ if (types.includes("text/uri-list") ||
+ types.includes("text/x-moz-url") ||
+ types.includes("text/plain")) {
+ aEvent.preventDefault();
+ }
+ },
+
+ onDrop(aEvent) {
+ let dt = aEvent.dataTransfer;
+ // If dragged item is from our source, do not try to
+ // redownload already downloaded file.
+ if (dt.mozGetDataAt("application/x-moz-file", 0)) {
+ return;
+ }
+
+ let links = Services.droppedLinkHandler.dropLinks(aEvent);
+ if (!links.length)
+ return;
+ let browserWin = RecentWindow.getMostRecentBrowserWindow();
+ let initiatingDoc = browserWin ? browserWin.document : document;
+ for (let link of links) {
+ if (link.url.startsWith("about:"))
+ continue;
+ DownloadURL(link.url, link.name, initiatingDoc);
+ }
+ },
+};
+
+for (let methodName of ["load", "applyFilter", "selectNode", "selectItems"]) {
+ DownloadsPlacesView.prototype[methodName] = function () {
+ throw new Error("|" + methodName +
+ "| is not implemented by the downloads view.");
+ }
+}
+
+function goUpdateDownloadCommands() {
+ function updateCommandsForObject(object) {
+ for (let name in object) {
+ if (DownloadsViewUI.isCommandName(name)) {
+ goUpdateCommand(name);
+ }
+ }
+ }
+ updateCommandsForObject(DownloadsPlacesView.prototype);
+ updateCommandsForObject(HistoryDownloadElementShell.prototype);
+}
diff --git a/browser/components/downloads/content/allDownloadsViewOverlay.xul b/browser/components/downloads/content/allDownloadsViewOverlay.xul
new file mode 100644
index 000000000..cb8c699bf
--- /dev/null
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.xul
@@ -0,0 +1,131 @@
+<?xml version="1.0"?>
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/allDownloadsViewOverlay.css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
+]>
+
+<!-- This overlay provides a downloads view that lists both session downloads,
+ using the DownloadsView API, and history downloads, using places queries.
+ The view also implements a command controller and a context menu for
+ managing the downloads list. In order to use this view:
+ 1. Apply this overlay to your window.
+ 2. Insert in all the overlay entry-points, namely:
+ <richlistbox id="downloadsRichListBox"/>
+ <commandset id="downloadCommands"/>
+ <menupopup id="downloadsContextMenu"/>
+ 3. Make sure your window has the editMenuOverlay overlay applied,
+ because the view implements cmd_copy and cmd_delete.
+ 4. Make sure your window has the globalOverlay.js script loaded.
+ 5. To initialize the view
+ let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
+ // This is what the Places Library uses. It could be tweaked a bit as long as the
+ // transition-type is set correctly
+ view.place = "place:transition=7&sort=4";
+-->
+<overlay id="downloadsViewOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/downloads/allDownloadsViewOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/contentAreaUtils.js"/>
+
+ <richlistbox flex="1"
+ seltype="multiple"
+ id="downloadsRichListBox" context="downloadsContextMenu"
+ onscroll="return this._placesView.onScroll();"
+ onkeypress="return this._placesView.onKeyPress(event);"
+ ondblclick="return this._placesView.onDoubleClick(event);"
+ oncontextmenu="return this._placesView.onContextMenu(event);"
+ ondragstart="this._placesView.onDragStart(event);"
+ ondragover="this._placesView.onDragOver(event);"
+ ondrop="this._placesView.onDrop(event);"
+ onfocus="goUpdateDownloadCommands();"
+ onselect="this._placesView.onSelect();"
+ onblur="goUpdateDownloadCommands();"/>
+
+ <commandset id="downloadCommands"
+ commandupdater="true"
+ events="focus,select,contextmenu"
+ oncommandupdate="goUpdateDownloadCommands();">
+ <command id="downloadsCmd_pauseResume"
+ oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
+ <command id="downloadsCmd_cancel"
+ oncommand="goDoCommand('downloadsCmd_cancel')"/>
+ <command id="downloadsCmd_unblock"
+ oncommand="goDoCommand('downloadsCmd_unblock')"/>
+ <command id="downloadsCmd_chooseUnblock"
+ oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/>
+ <command id="downloadsCmd_chooseOpen"
+ oncommand="goDoCommand('downloadsCmd_chooseOpen')"/>
+ <command id="downloadsCmd_confirmBlock"
+ oncommand="goDoCommand('downloadsCmd_confirmBlock')"/>
+ <command id="downloadsCmd_open"
+ oncommand="goDoCommand('downloadsCmd_open')"/>
+ <command id="downloadsCmd_show"
+ oncommand="goDoCommand('downloadsCmd_show')"/>
+ <command id="downloadsCmd_retry"
+ oncommand="goDoCommand('downloadsCmd_retry')"/>
+ <command id="downloadsCmd_openReferrer"
+ oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
+ <command id="downloadsCmd_clearDownloads"
+ oncommand="goDoCommand('downloadsCmd_clearDownloads')"/>
+ </commandset>
+
+ <menupopup id="downloadsContextMenu" class="download-state">
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadPauseMenuItem"
+ label="&cmd.pause.label;"
+ accesskey="&cmd.pause.accesskey;"/>
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadResumeMenuItem"
+ label="&cmd.resume.label;"
+ accesskey="&cmd.resume.accesskey;"/>
+ <menuitem command="downloadsCmd_cancel"
+ class="downloadCancelMenuItem"
+ label="&cmd.cancel.label;"
+ accesskey="&cmd.cancel.accesskey;"/>
+ <menuitem command="downloadsCmd_unblock"
+ class="downloadUnblockMenuItem"
+ label="&cmd.unblock2.label;"
+ accesskey="&cmd.unblock2.accesskey;"/>
+ <menuitem command="cmd_delete"
+ class="downloadRemoveFromHistoryMenuItem"
+ label="&cmd.removeFromHistory.label;"
+ accesskey="&cmd.removeFromHistory.accesskey;"/>
+ <menuitem command="downloadsCmd_show"
+ class="downloadShowMenuItem"
+#ifdef XP_MACOSX
+ label="&cmd.showMac.label;"
+ accesskey="&cmd.showMac.accesskey;"
+#else
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+#endif
+ />
+
+ <menuseparator class="downloadCommandsSeparator"/>
+
+ <menuitem command="downloadsCmd_openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"/>
+ <menuitem command="cmd_copy"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"/>
+
+ <menuseparator/>
+
+ <menuitem command="downloadsCmd_clearDownloads"
+ label="&cmd.clearDownloads.label;"
+ accesskey="&cmd.clearDownloads.accesskey;"/>
+ </menupopup>
+</overlay>
diff --git a/browser/components/downloads/content/contentAreaDownloadsView.css b/browser/components/downloads/content/contentAreaDownloadsView.css
new file mode 100644
index 000000000..abaae1f7b
--- /dev/null
+++ b/browser/components/downloads/content/contentAreaDownloadsView.css
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#downloadsListEmptyDescription {
+ display: none;
+}
+
+#downloadsRichListBox:empty + #downloadsListEmptyDescription {
+ display: -moz-box;
+}
diff --git a/browser/components/downloads/content/contentAreaDownloadsView.js b/browser/components/downloads/content/contentAreaDownloadsView.js
new file mode 100644
index 000000000..6e4f18599
--- /dev/null
+++ b/browser/components/downloads/content/contentAreaDownloadsView.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/. */
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var ContentAreaDownloadsView = {
+ init() {
+ let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
+ // Do not display the Places downloads in private windows
+ if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
+ view.place = "place:transition=7&sort=4";
+ }
+ // Set focus to Downloads list once it is created
+ document.getElementById("downloadsRichListBox").focus();
+ },
+};
diff --git a/browser/components/downloads/content/contentAreaDownloadsView.xul b/browser/components/downloads/content/contentAreaDownloadsView.xul
new file mode 100644
index 000000000..91c986656
--- /dev/null
+++ b/browser/components/downloads/content/contentAreaDownloadsView.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://browser/content/downloads/contentAreaDownloadsView.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/contentAreaDownloadsView.css"?>
+
+<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
+]>
+
+<window id="contentAreaDownloadsView"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&downloads.title;"
+ onload="ContentAreaDownloadsView.init();">
+
+ <script type="application/javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/downloads/contentAreaDownloadsView.js"/>
+
+ <commandset id="editMenuCommands"/>
+
+ <keyset id="editMenuKeys">
+#ifdef XP_MACOSX
+ <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
+#endif
+ </keyset>
+
+ <stack flex="1">
+ <richlistbox id="downloadsRichListBox"/>
+ <description id="downloadsListEmptyDescription"
+ value="&downloadsListEmpty.label;"
+ mousethrough="always"/>
+ </stack>
+ <commandset id="downloadCommands"/>
+ <menupopup id="downloadsContextMenu"/>
+</window>
diff --git a/browser/components/downloads/content/download.xml b/browser/components/downloads/content/download.xml
new file mode 100644
index 000000000..29a1530fd
--- /dev/null
+++ b/browser/components/downloads/content/download.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
+<!-- vim: set ts=2 et sw=2 tw=80: -->
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+
+<bindings id="downloadBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="download"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content orient="horizontal"
+ onclick="DownloadsView.onDownloadClick(event);">
+ <xul:hbox class="downloadMainArea"
+ flex="1"
+ align="center">
+ <xul:stack>
+ <xul:image class="downloadTypeIcon"
+ validate="always"
+ xbl:inherits="src=image"/>
+ <xul:image class="downloadBlockedBadge" />
+ </xul:stack>
+ <xul:vbox pack="center"
+ flex="1"
+ class="downloadContainer"
+ style="width: &downloadDetails.width;">
+ <!-- We're letting localizers put a min-width in here primarily
+ because of the downloads summary at the bottom of the list of
+ download items. An element in the summary has the same min-width
+ on a description, and we don't want the panel to change size if the
+ summary isn't being displayed, so we ensure that items share the
+ same minimum width.
+ -->
+ <xul:description class="downloadTarget"
+ crop="center"
+ style="min-width: &downloadsSummary.minWidth2;"
+ xbl:inherits="value=displayName,tooltiptext=displayName"/>
+ <xul:progressmeter anonid="progressmeter"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ xbl:inherits="mode=progressmode,value=progress,paused=progresspaused"/>
+ <xul:description class="downloadDetails"
+ crop="end"
+ xbl:inherits="value=status,tooltiptext=statusTip"/>
+ </xul:vbox>
+ </xul:hbox>
+ <xul:toolbarseparator />
+ <xul:stack class="downloadButtonArea">
+ <xul:button class="downloadButton downloadCancel downloadIconCancel"
+ tooltiptext="&cmd.cancel.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
+ <xul:button class="downloadButton downloadRetry downloadIconRetry"
+ tooltiptext="&cmd.retry.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
+ <xul:button class="downloadButton downloadShow downloadIconShow"
+#ifdef XP_MACOSX
+ tooltiptext="&cmd.showMac.label;"
+#else
+ tooltiptext="&cmd.show.label;"
+#endif
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
+ <xul:button class="downloadButton downloadConfirmBlock downloadIconCancel"
+ tooltiptext="&cmd.removeFile.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_confirmBlock');"/>
+ <xul:button class="downloadButton downloadChooseUnblock downloadIconShow"
+ tooltiptext="&cmd.chooseUnblock.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseUnblock');"/>
+ <xul:button class="downloadButton downloadChooseOpen downloadIconShow"
+ tooltiptext="&cmd.chooseOpen.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/>
+ <xul:button class="downloadButton downloadShowBlockedInfo"
+ tooltiptext="&cmd.chooseUnblock.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_showBlockedInfo');"/>
+ </xul:stack>
+ </content>
+ </binding>
+
+ <binding id="download-toolbarbutton"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged">
+ <content>
+ <xul:stack class="toolbarbutton-badge-stack">
+ <children />
+ <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>
+</bindings>
diff --git a/browser/components/downloads/content/downloads.css b/browser/components/downloads/content/downloads.css
new file mode 100644
index 000000000..dd47c6f91
--- /dev/null
+++ b/browser/components/downloads/content/downloads.css
@@ -0,0 +1,267 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*** Downloads Panel ***/
+
+richlistitem[type="download"] {
+ -moz-binding: url('chrome://browser/content/downloads/download.xml#download');
+}
+
+richlistitem[type="download"]:not([selected]) button {
+ /* Only focus buttons in the selected item. */
+ -moz-user-focus: none;
+}
+
+.downloadsHideDropmarker > #downloadsFooterButtonsSplitter,
+.downloadsHideDropmarker > #downloadsFooterDropmarker {
+ display: none;
+}
+
+richlistitem[type="download"].download-state[state="1"]:not([exists]) > .downloadButtonArea,
+richlistitem[type="download"].download-state[state="1"]:not([exists]) > toolbarseparator {
+ display: none;
+}
+
+#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
+#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
+#downloadsFooter:not([showingsummary]) #downloadsSummary {
+ display: none;
+}
+
+#downloadsFooter[showingdropdown] > stack > #downloadsSummary,
+#downloadsFooter[showingsummary] > stack:hover > #downloadsSummary,
+#downloadsFooter[showingsummary]:not([showingdropdown]) > stack:not(:hover) > #downloadsFooterButtons {
+ /* If we used "visibility: hidden;" then the mouseenter event of
+ #downloadsHistory wouldn't be triggered immediately, and the hover styling
+ of the button would not apply until the mouse is moved again.
+
+ "-moz-user-focus: ignore;" prevents the elements with "opacity: 0;" from
+ being focused with the keyboard. */
+ opacity: 0;
+ -moz-user-focus: ignore;
+}
+
+/*** Downloads View ***/
+
+/**
+ * The downloads richlistbox may list thousands of items, and it turns out
+ * XBL binding attachment, and even more so detachment, is a performance hog.
+ * This hack makes sure we don't apply any binding to inactive items (inactive
+ * items are history downloads that haven't been in the visible area).
+ * We can do this because the richlistbox implementation does not interact
+ * much with the richlistitem binding. However, this may turn out to have
+ * some side effects (see bug 828111 for the details).
+ *
+ * We might be able to do away with this workaround once bug 653881 is fixed.
+ */
+richlistitem.download {
+ -moz-binding: none;
+}
+
+richlistitem.download[active] {
+ -moz-binding: url("chrome://browser/content/downloads/download.xml#download");
+}
+
+richlistitem.download button {
+ /* These buttons should never get focus, as that would "disable"
+ the downloads view controller (it's only used when the richlistbox
+ is focused). */
+ -moz-user-focus: none;
+}
+
+/*** Visibility of controls inside download items ***/
+.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */)
+ .downloadBlockedBadge,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="5"], /* Starting (queued) */
+ [state="0"], /* Downloading */
+ [state="4"], /* Paused */
+ [state="7"]) /* Scanning */)
+ .downloadProgress,
+
+.download-state:not( [state="0"] /* Downloading */)
+ .downloadPauseMenuItem,
+
+.download-state:not( [state="4"] /* Paused */)
+ .downloadResumeMenuItem,
+
+/* Blocked (dirty) downloads that have not been confirmed and
+ have temporary data. */
+.download-state:not( [state="8"] /* Blocked (dirty) */)
+ .downloadUnblockMenuItem,
+.download-state[state="8"]:not(.temporary-block)
+ .downloadUnblockMenuItem,
+
+.download-state:not(:-moz-any([state="2"], /* Failed */
+ [state="4"]) /* Paused */)
+ .downloadCancelMenuItem,
+
+.download-state:not(:-moz-any([state="1"], /* Finished */
+ [state="2"], /* Failed */
+ [state="3"], /* Canceled */
+ [state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */)
+ .downloadRemoveFromHistoryMenuItem,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="0"], /* Downloading */
+ [state="1"], /* Finished */
+ [state="4"], /* Paused */
+ [state="5"]) /* Starting (queued) */)
+ .downloadShowMenuItem,
+
+.download-state[state="7"] .downloadCommandsSeparator
+
+{
+ display: none;
+}
+
+/*** Visibility of download buttons ***/
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="5"], /* Starting (queued) */
+ [state="0"], /* Downloading */
+ [state="4"]) /* Paused */)
+ .downloadCancel,
+
+/* Blocked (dirty) downloads that have not been confirmed and
+ have temporary data, for the Malware case. */
+.download-state:not( [state="8"] /* Blocked (dirty) */)
+ .downloadConfirmBlock,
+.download-state[state="8"]:not(.temporary-block)
+ .downloadConfirmBlock,
+.download-state[state="8"].temporary-block:not([verdict="Malware"])
+ .downloadConfirmBlock,
+
+/* Blocked (dirty) downloads that have not been confirmed and
+ have temporary data, for the Potentially Unwanted case. */
+.download-state:not( [state="8"] /* Blocked (dirty) */)
+ .downloadChooseUnblock,
+.download-state[state="8"]:not(.temporary-block)
+ .downloadChooseUnblock,
+.download-state[state="8"].temporary-block:not([verdict="PotentiallyUnwanted"])
+ .downloadChooseUnblock,
+
+/* Blocked (dirty) downloads that have not been confirmed and
+ have temporary data, for the Uncommon case. */
+.download-state:not( [state="8"] /* Blocked (dirty) */)
+ .downloadChooseOpen,
+.download-state[state="8"]:not(.temporary-block)
+ .downloadChooseOpen,
+.download-state[state="8"].temporary-block:not([verdict="Uncommon"])
+ .downloadChooseOpen,
+
+.download-state:not(:-moz-any([state="2"], /* Failed */
+ [state="3"]) /* Canceled */)
+ .downloadRetry,
+
+.download-state:not( [state="1"] /* Finished */)
+ .downloadShow,
+
+.download-state:-moz-any( [state="6"], /* Blocked (parental) */
+ [state="7"], /* Scanning */
+ [state="9"]) /* Blocked (policy) */
+ > toolbarseparator,
+
+/* The "show blocked info" button is shown only in the downloads panel. */
+.downloadShowBlockedInfo
+{
+ display: none;
+}
+
+/*** Downloads panel ***/
+
+#downloadsPanel[hasdownloads] #emptyDownloads,
+#downloadsPanel:not([hasdownloads]) #downloadsListBox {
+ display: none;
+}
+
+/*** Downloads panel multiview (main view and blocked-downloads subview) ***/
+
+/* Hide all the usual buttons. */
+#downloadsPanel-mainView .download-state[state="8"] .downloadCancel,
+#downloadsPanel-mainView .download-state[state="8"] .downloadConfirmBlock,
+#downloadsPanel-mainView .download-state[state="8"] .downloadChooseUnblock,
+#downloadsPanel-mainView .download-state[state="8"] .downloadChooseOpen,
+#downloadsPanel-mainView .download-state[state="8"] .downloadRetry,
+#downloadsPanel-mainView .download-state[state="8"] .downloadShow {
+ display: none;
+}
+
+/* Make the panel wide enough to show the download list items without improperly
+ truncating them. */
+#downloadsPanel-multiView > .panel-viewcontainer,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-mainview {
+ max-width: unset;
+}
+
+/* Show the "show blocked info" button. */
+#downloadsPanel-mainView .download-state[state="8"] .downloadShowBlockedInfo {
+ display: inline;
+}
+
+/** When the main view is showing... **/
+
+/* The subview should be off to the right and not visible at all. */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
+ transform: translateX(101%);
+ transition: transform var(--panelui-subview-transition-duration);
+}
+
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews:-moz-locale-dir(rtl) {
+ transform: translateX(-101%);
+}
+
+/** When the subview is showing... **/
+
+/* Hide the buttons of all downloads except the one that triggered the
+ subview. */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state:not([showingsubview]) .downloadButton {
+ visibility: hidden;
+}
+
+/* For the download that triggered the subview, move its button farther to the
+ right by removing padding so that a minimum amount of the main view's right
+ edge needs to be shown. */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] {
+ padding: 0;
+}
+
+/* The main view should slide to the left and its right edge should remain
+ visible. */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview {
+ transform: translateX(calc(-100% + 38px));
+ transition: transform var(--panelui-subview-transition-duration);
+}
+
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview:-moz-locale-dir(rtl) {
+ transform: translateX(calc(100% - 38px));
+}
+
+/* The subview should leave the right edge of the main view uncovered. */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
+ /* Use a margin instead of a transform like above so that the subview's width
+ isn't wider than the panel. */
+ -moz-margin-start: 38px !important;
+}
+
+/* Prevent keyboard interaction in the main view by preventing all elements in
+ the main view from being focused... */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview #downloadsListBox,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview richlistitem,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview .downloadButton,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview .downloadsPanelFooterButton,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] > .panel-mainview #downloadsSummary {
+ -moz-user-focus: ignore;
+}
+/* ... except for the downloadShowBlockedInfo button in the blocked download.
+ Selecting it with the keyboard should show the main view again. */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadShowBlockedInfo {
+ -moz-user-focus: normal;
+}
diff --git a/browser/components/downloads/content/downloads.js b/browser/components/downloads/content/downloads.js
new file mode 100644
index 000000000..57397c815
--- /dev/null
+++ b/browser/components/downloads/content/downloads.js
@@ -0,0 +1,1732 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Handles the Downloads panel user interface for each browser window.
+ *
+ * This file includes the following constructors and global objects:
+ *
+ * DownloadsPanel
+ * Main entry point for the downloads panel interface.
+ *
+ * DownloadsOverlayLoader
+ * Allows loading the downloads panel and the status indicator interfaces on
+ * demand, to improve startup performance.
+ *
+ * DownloadsView
+ * Builds and updates the downloads list widget, responding to changes in the
+ * download state and real-time data. In addition, handles part of the user
+ * interaction events raised by the downloads list widget.
+ *
+ * DownloadsViewItem
+ * Builds and updates a single item in the downloads list widget, responding to
+ * changes in the download state and real-time data, and handles the user
+ * interaction events related to a single item in the downloads list widgets.
+ *
+ * DownloadsViewController
+ * Handles part of the user interaction events raised by the downloads list
+ * widget, in particular the "commands" that apply to multiple items, and
+ * dispatches the commands that apply to individual items.
+ */
+
+/**
+ * A few words on focus and focusrings
+ *
+ * We do quite a few hacks in the Downloads Panel for focusrings. In fact, we
+ * basically suppress most if not all XUL-level focusrings, and style/draw
+ * them ourselves (using :focus instead of -moz-focusring). There are a few
+ * reasons for this:
+ *
+ * 1) Richlists on OSX don't have focusrings; instead, they are shown as
+ * selected. This makes for some ambiguity when we have a focused/selected
+ * item in the list, and the mouse is hovering a completed download (which
+ * highlights).
+ * 2) Windows doesn't show focusrings until after the first time that tab is
+ * pressed (and by then you're focusing the second item in the panel).
+ * 3) Richlistbox sets -moz-focusring even when we select it with a mouse.
+ *
+ * In general, the desired behaviour is to focus the first item after pressing
+ * tab/down, and show that focus with a ring. Then, if the mouse moves over
+ * the panel, to hide that focus ring; essentially resetting us to the state
+ * before pressing the key.
+ *
+ * We end up capturing the tab/down key events, and preventing their default
+ * behaviour. We then set a "keyfocus" attribute on the panel, which allows
+ * us to draw a ring around the currently focused element. If the panel is
+ * closed or the mouse moves over the panel, we remove the attribute.
+ */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
+ "resource:///modules/DownloadsViewUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsPanel
+
+/**
+ * Main entry point for the downloads panel interface.
+ */
+const DownloadsPanel = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Initialization and termination
+
+ /**
+ * Internal state of the downloads panel, based on one of the kState
+ * constants. This is not the same state as the XUL panel element.
+ */
+ _state: 0,
+
+ /** The panel is not linked to downloads data yet. */
+ get kStateUninitialized() {
+ return 0;
+ },
+ /** This object is linked to data, but the panel is invisible. */
+ get kStateHidden() {
+ return 1;
+ },
+ /** The panel will be shown as soon as possible. */
+ get kStateWaitingData() {
+ return 2;
+ },
+ /** The panel is almost shown - we're just waiting to get a handle on the
+ anchor. */
+ get kStateWaitingAnchor() {
+ return 3;
+ },
+ /** The panel is open. */
+ get kStateShown() {
+ return 4;
+ },
+
+ /**
+ * Location of the panel overlay.
+ */
+ get kDownloadsOverlay() {
+ return "chrome://browser/content/downloads/downloadsOverlay.xul";
+ },
+
+ /**
+ * Starts loading the download data in background, without opening the panel.
+ * Use showPanel instead to load the data and open the panel at the same time.
+ *
+ * @param aCallback
+ * Called when initialization is complete.
+ */
+ initialize(aCallback) {
+ DownloadsCommon.log("Attempting to initialize DownloadsPanel for a window.");
+ if (this._state != this.kStateUninitialized) {
+ DownloadsCommon.log("DownloadsPanel is already initialized.");
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
+ aCallback);
+ return;
+ }
+ this._state = this.kStateHidden;
+
+ window.addEventListener("unload", this.onWindowUnload, false);
+
+ // Load and resume active downloads if required. If there are downloads to
+ // be shown in the panel, they will be loaded asynchronously.
+ DownloadsCommon.initializeAllDataLinks();
+
+ // Now that data loading has eventually started, load the required XUL
+ // elements and initialize our views.
+ DownloadsCommon.log("Ensuring DownloadsPanel overlay loaded.");
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay, () => {
+ DownloadsViewController.initialize();
+ DownloadsCommon.log("Attaching DownloadsView...");
+ DownloadsCommon.getData(window).addView(DownloadsView);
+ DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
+ .addView(DownloadsSummary);
+ DownloadsCommon.log("DownloadsView attached - the panel for this window",
+ "should now see download items come in.");
+ DownloadsPanel._attachEventListeners();
+ DownloadsCommon.log("DownloadsPanel initialized.");
+ aCallback();
+ });
+ },
+
+ /**
+ * Closes the downloads panel and frees the internal resources related to the
+ * downloads. The downloads panel can be reopened later, even after this
+ * function has been called.
+ */
+ terminate() {
+ DownloadsCommon.log("Attempting to terminate DownloadsPanel for a window.");
+ if (this._state == this.kStateUninitialized) {
+ DownloadsCommon.log("DownloadsPanel was never initialized. Nothing to do.");
+ return;
+ }
+
+ window.removeEventListener("unload", this.onWindowUnload, false);
+
+ // Ensure that the panel is closed before shutting down.
+ this.hidePanel();
+
+ DownloadsViewController.terminate();
+ DownloadsCommon.getData(window).removeView(DownloadsView);
+ DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
+ .removeView(DownloadsSummary);
+ this._unattachEventListeners();
+
+ this._state = this.kStateUninitialized;
+
+ DownloadsSummary.active = false;
+ DownloadsCommon.log("DownloadsPanel terminated.");
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Panel interface
+
+ /**
+ * Main panel element in the browser window, or null if the panel overlay
+ * hasn't been loaded yet.
+ */
+ get panel() {
+ // If the downloads panel overlay hasn't loaded yet, just return null
+ // without resetting this.panel.
+ let downloadsPanel = document.getElementById("downloadsPanel");
+ if (!downloadsPanel)
+ return null;
+
+ delete this.panel;
+ return this.panel = downloadsPanel;
+ },
+
+ /**
+ * Starts opening the downloads panel interface, anchored to the downloads
+ * button of the browser window. The list of downloads to display is
+ * initialized the first time this method is called, and the panel is shown
+ * only when data is ready.
+ */
+ showPanel() {
+ DownloadsCommon.log("Opening the downloads panel.");
+
+ if (this.isPanelShowing) {
+ DownloadsCommon.log("Panel is already showing - focusing instead.");
+ this._focusPanel();
+ return;
+ }
+
+ this.initialize(() => {
+ let downloadsFooterButtons =
+ document.getElementById("downloadsFooterButtons");
+ if (DownloadsCommon.showPanelDropmarker) {
+ downloadsFooterButtons.classList.remove("downloadsHideDropmarker");
+ } else {
+ downloadsFooterButtons.classList.add("downloadsHideDropmarker");
+ }
+
+ // Delay displaying the panel because this function will sometimes be
+ // called while another window is closing (like the window for selecting
+ // whether to save or open the file), and that would cause the panel to
+ // close immediately.
+ setTimeout(() => this._openPopupIfDataReady(), 0);
+ });
+
+ DownloadsCommon.log("Waiting for the downloads panel to appear.");
+ this._state = this.kStateWaitingData;
+ },
+
+ /**
+ * Hides the downloads panel, if visible, but keeps the internal state so that
+ * the panel can be reopened quickly if required.
+ */
+ hidePanel() {
+ DownloadsCommon.log("Closing the downloads panel.");
+
+ if (!this.isPanelShowing) {
+ DownloadsCommon.log("Downloads panel is not showing - nothing to do.");
+ return;
+ }
+
+ this.panel.hidePopup();
+
+ // Ensure that we allow the panel to be reopened. Note that, if the popup
+ // was open, then the onPopupHidden event handler has already updated the
+ // current state, otherwise we must update the state ourselves.
+ this._state = this.kStateHidden;
+ DownloadsCommon.log("Downloads panel is now closed.");
+ },
+
+ /**
+ * Indicates whether the panel is shown or will be shown.
+ */
+ get isPanelShowing() {
+ return this._state == this.kStateWaitingData ||
+ this._state == this.kStateWaitingAnchor ||
+ this._state == this.kStateShown;
+ },
+
+ /**
+ * Returns whether the user has started keyboard navigation.
+ */
+ get keyFocusing() {
+ return this.panel.hasAttribute("keyfocus");
+ },
+
+ /**
+ * Set to true if the user has started keyboard navigation, and we should be
+ * showing focusrings in the panel. Also adds a mousemove event handler to
+ * the panel which disables keyFocusing.
+ */
+ set keyFocusing(aValue) {
+ if (aValue) {
+ this.panel.setAttribute("keyfocus", "true");
+ this.panel.addEventListener("mousemove", this);
+ } else {
+ this.panel.removeAttribute("keyfocus");
+ this.panel.removeEventListener("mousemove", this);
+ }
+ return aValue;
+ },
+
+ /**
+ * Handles the mousemove event for the panel, which disables focusring
+ * visualization.
+ */
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "mousemove":
+ this.keyFocusing = false;
+ break;
+ case "keydown":
+ return this._onKeyDown(aEvent);
+ case "keypress":
+ return this._onKeyPress(aEvent);
+ case "popupshown":
+ if (this.setHeightToFitOnShow) {
+ this.setHeightToFitOnShow = false;
+ this.setHeightToFit();
+ }
+ break;
+ }
+ },
+
+ setHeightToFit() {
+ if (this._state == this.kStateShown) {
+ DownloadsBlockedSubview.view.setHeightToFit();
+ } else {
+ this.setHeightToFitOnShow = true;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsView
+
+ /**
+ * Called after data loading finished.
+ */
+ onViewLoadCompleted() {
+ this._openPopupIfDataReady();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// User interface event functions
+
+ onWindowUnload() {
+ // This function is registered as an event listener, we can't use "this".
+ DownloadsPanel.terminate();
+ },
+
+ onPopupShown(aEvent) {
+ // Ignore events raised by nested popups.
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+
+ DownloadsCommon.log("Downloads panel has shown.");
+ this._state = this.kStateShown;
+
+ // Since at most one popup is open at any given time, we can set globally.
+ DownloadsCommon.getIndicatorData(window).attentionSuppressed = true;
+
+ // Ensure that the first item is selected when the panel is focused.
+ if (DownloadsView.richListBox.itemCount > 0 &&
+ DownloadsView.richListBox.selectedIndex == -1) {
+ DownloadsView.richListBox.selectedIndex = 0;
+ }
+
+ this._focusPanel();
+ },
+
+ onPopupHidden(aEvent) {
+ // Ignore events raised by nested popups.
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+
+ DownloadsCommon.log("Downloads panel has hidden.");
+
+ // Removes the keyfocus attribute so that we stop handling keyboard
+ // navigation.
+ this.keyFocusing = false;
+
+ // Since at most one popup is open at any given time, we can set globally.
+ DownloadsCommon.getIndicatorData(window).attentionSuppressed = false;
+
+ // Allow the anchor to be hidden.
+ DownloadsButton.releaseAnchor();
+
+ // Allow the panel to be reopened.
+ this._state = this.kStateHidden;
+ },
+
+ onFooterPopupShowing(aEvent) {
+ let itemClearList = document.getElementById("downloadsDropdownItemClearList");
+ if (DownloadsCommon.getData(window).canRemoveFinished) {
+ itemClearList.removeAttribute("hidden");
+ } else {
+ itemClearList.setAttribute("hidden", "true");
+ }
+ DownloadsViewController.updateCommands();
+
+ document.getElementById("downloadsFooter")
+ .setAttribute("showingdropdown", true);
+ },
+
+ onFooterPopupHidden(aEvent) {
+ document.getElementById("downloadsFooter")
+ .removeAttribute("showingdropdown");
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Related operations
+
+ /**
+ * Shows or focuses the user interface dedicated to downloads history.
+ */
+ showDownloadsHistory() {
+ DownloadsCommon.log("Showing download history.");
+ // Hide the panel before showing another window, otherwise focus will return
+ // to the browser window when the panel closes automatically.
+ this.hidePanel();
+
+ BrowserDownloadsUI();
+ },
+
+ openDownloadsFolder() {
+ Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
+ DownloadsCommon.showDirectory(new FileUtils.File(downloadsPath));
+ }).catch(Cu.reportError);
+ this.hidePanel();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Internal functions
+
+ /**
+ * Attach event listeners to a panel element. These listeners should be
+ * removed in _unattachEventListeners. This is called automatically after the
+ * panel has successfully loaded.
+ */
+ _attachEventListeners() {
+ // Handle keydown to support accel-V.
+ this.panel.addEventListener("keydown", this, false);
+ // Handle keypress to be able to preventDefault() events before they reach
+ // the richlistbox, for keyboard navigation.
+ this.panel.addEventListener("keypress", this, false);
+ // Handle height adjustment on show.
+ this.panel.addEventListener("popupshown", this, false);
+ },
+
+ /**
+ * Unattach event listeners that were added in _attachEventListeners. This
+ * is called automatically on panel termination.
+ */
+ _unattachEventListeners() {
+ this.panel.removeEventListener("keydown", this, false);
+ this.panel.removeEventListener("keypress", this, false);
+ this.panel.removeEventListener("popupshown", this, false);
+ },
+
+ _onKeyPress(aEvent) {
+ // Handle unmodified keys only.
+ if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) {
+ return;
+ }
+
+ let richListBox = DownloadsView.richListBox;
+
+ // If the user has pressed the tab, up, or down cursor key, start keyboard
+ // navigation, thus enabling focusrings in the panel. Keyboard navigation
+ // is automatically disabled if the user moves the mouse on the panel, or
+ // if the panel is closed.
+ if ((aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_TAB ||
+ aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP ||
+ aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) &&
+ !this.keyFocusing) {
+ this.keyFocusing = true;
+ // Ensure there's a selection, we will show the focus ring around it and
+ // prevent the richlistbox from changing the selection.
+ if (DownloadsView.richListBox.selectedIndex == -1) {
+ DownloadsView.richListBox.selectedIndex = 0;
+ }
+ aEvent.preventDefault();
+ return;
+ }
+
+ if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
+ // If the last element in the list is selected, or the footer is already
+ // focused, focus the footer.
+ if (richListBox.selectedItem === richListBox.lastChild ||
+ document.activeElement.parentNode.id === "downloadsFooter") {
+ DownloadsFooter.focus();
+ aEvent.preventDefault();
+ return;
+ }
+ }
+
+ // Pass keypress events to the richlistbox view when it's focused.
+ if (document.activeElement === richListBox) {
+ DownloadsView.onDownloadKeyPress(aEvent);
+ }
+ },
+
+ /**
+ * Keydown listener that listens for the keys to start key focusing, as well
+ * as the the accel-V "paste" event, which initiates a file download if the
+ * pasted item can be resolved to a URI.
+ */
+ _onKeyDown(aEvent) {
+ // If the footer is focused and the downloads list has at least 1 element
+ // in it, focus the last element in the list when going up.
+ if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP &&
+ document.activeElement.parentNode.id === "downloadsFooter" &&
+ DownloadsView.richListBox.firstChild) {
+ DownloadsView.richListBox.focus();
+ DownloadsView.richListBox.selectedItem = DownloadsView.richListBox.lastChild;
+ aEvent.preventDefault();
+ return;
+ }
+
+ let pasting = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_V &&
+ aEvent.getModifierState("Accel");
+
+ if (!pasting) {
+ return;
+ }
+
+ DownloadsCommon.log("Received a paste event.");
+
+ let trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ trans.init(null);
+ let flavors = ["text/x-moz-url", "text/unicode"];
+ flavors.forEach(trans.addDataFlavor);
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+ // Getting the data or creating the nsIURI might fail
+ try {
+ let data = {};
+ trans.getAnyTransferData({}, data, {});
+ let [url, name] = data.value
+ .QueryInterface(Ci.nsISupportsString)
+ .data
+ .split("\n");
+ if (!url) {
+ return;
+ }
+
+ let uri = NetUtil.newURI(url);
+ DownloadsCommon.log("Pasted URL seems valid. Starting download.");
+ DownloadURL(uri.spec, name, document);
+ } catch (ex) {}
+ },
+
+ /**
+ * Move focus to the main element in the downloads panel, unless another
+ * element in the panel is already focused.
+ */
+ _focusPanel() {
+ // We may be invoked while the panel is still waiting to be shown.
+ if (this._state != this.kStateShown) {
+ return;
+ }
+
+ let element = document.commandDispatcher.focusedElement;
+ while (element && element != this.panel) {
+ element = element.parentNode;
+ }
+ if (!element) {
+ if (DownloadsView.richListBox.itemCount > 0) {
+ DownloadsView.richListBox.focus();
+ } else {
+ DownloadsFooter.focus();
+ }
+ }
+ },
+
+ /**
+ * Opens the downloads panel when data is ready to be displayed.
+ */
+ _openPopupIfDataReady() {
+ // We don't want to open the popup if we already displayed it, or if we are
+ // still loading data.
+ if (this._state != this.kStateWaitingData || DownloadsView.loading) {
+ return;
+ }
+
+ this._state = this.kStateWaitingAnchor;
+
+ // Ensure the anchor is visible. If that is not possible, show the panel
+ // anchored to the top area of the window, near the default anchor position.
+ DownloadsButton.getAnchor(anchor => {
+ // If somehow we've switched states already (by getting a panel hiding
+ // event before an overlay is loaded, for example), bail out.
+ if (this._state != this.kStateWaitingAnchor) {
+ return;
+ }
+
+ // At this point, if the window is minimized, opening the panel could fail
+ // without any notification, and there would be no way to either open or
+ // close the panel any more. To prevent this, check if the window is
+ // minimized and in that case force the panel to the closed state.
+ if (window.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
+ DownloadsButton.releaseAnchor();
+ this._state = this.kStateHidden;
+ return;
+ }
+
+ if (!anchor) {
+ DownloadsCommon.error("Downloads button cannot be found.");
+ return;
+ }
+
+ // When the panel is opened, we check if the target files of visible items
+ // still exist, and update the allowed items interactions accordingly. We
+ // do these checks on a background thread, and don't prevent the panel to
+ // be displayed while these checks are being performed.
+ for (let viewItem of DownloadsView._visibleViewItems.values()) {
+ viewItem.download.refresh().catch(Cu.reportError);
+ }
+
+ DownloadsCommon.log("Opening downloads panel popup.");
+ this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, null);
+ });
+ },
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsPanel", DownloadsPanel);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsOverlayLoader
+
+/**
+ * Allows loading the downloads panel and the status indicator interfaces on
+ * demand, to improve startup performance.
+ */
+const DownloadsOverlayLoader = {
+ /**
+ * We cannot load two overlays at the same time, thus we use a queue of
+ * pending load requests.
+ */
+ _loadRequests: [],
+
+ /**
+ * True while we are waiting for an overlay to be loaded.
+ */
+ _overlayLoading: false,
+
+ /**
+ * This object has a key for each overlay URI that is already loaded.
+ */
+ _loadedOverlays: {},
+
+ /**
+ * Loads the specified overlay and invokes the given callback when finished.
+ *
+ * @param aOverlay
+ * String containing the URI of the overlay to load in the current
+ * window. If this overlay has already been loaded using this
+ * function, then the overlay is not loaded again.
+ * @param aCallback
+ * Invoked when loading is completed. If the overlay is already
+ * loaded, the function is called immediately.
+ */
+ ensureOverlayLoaded(aOverlay, aCallback) {
+ // The overlay is already loaded, invoke the callback immediately.
+ if (aOverlay in this._loadedOverlays) {
+ aCallback();
+ return;
+ }
+
+ // The callback will be invoked when loading is finished.
+ this._loadRequests.push({ overlay: aOverlay, callback: aCallback });
+ if (this._overlayLoading) {
+ return;
+ }
+
+ this._overlayLoading = true;
+ DownloadsCommon.log("Loading overlay ", aOverlay);
+ document.loadOverlay(aOverlay, () => {
+ this._overlayLoading = false;
+ this._loadedOverlays[aOverlay] = true;
+
+ this.processPendingRequests();
+ });
+ },
+
+ /**
+ * Re-processes all the currently pending requests, invoking the callbacks
+ * and/or loading more overlays as needed. In most cases, there will be a
+ * single request for one overlay, that will be processed immediately.
+ */
+ processPendingRequests() {
+ // Re-process all the currently pending requests, yet allow more requests
+ // to be appended at the end of the array if we're not ready for them.
+ let currentLength = this._loadRequests.length;
+ for (let i = 0; i < currentLength; i++) {
+ let request = this._loadRequests.shift();
+
+ // We must call ensureOverlayLoaded again for each request, to check if
+ // the associated callback can be invoked now, or if we must still wait
+ // for the associated overlay to load.
+ this.ensureOverlayLoaded(request.overlay, request.callback);
+ }
+ },
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsOverlayLoader", DownloadsOverlayLoader);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsView
+
+/**
+ * Builds and updates the downloads list widget, responding to changes in the
+ * download state and real-time data. In addition, handles part of the user
+ * interaction events raised by the downloads list widget.
+ */
+const DownloadsView = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Functions handling download items in the list
+
+ /**
+ * Maximum number of items shown by the list at any given time.
+ */
+ kItemCountLimit: 5,
+
+ /**
+ * Indicates whether there is an open contextMenu for a download item.
+ */
+ contextMenuOpen: false,
+
+ /**
+ * Indicates whether there is a DownloadsBlockedSubview open.
+ */
+ subViewOpen: false,
+
+ /**
+ * Indicates whether we are still loading downloads data asynchronously.
+ */
+ loading: false,
+
+ /**
+ * Ordered array of all Download objects. We need to keep this array because
+ * only a limited number of items are shown at once, and if an item that is
+ * currently visible is removed from the list, we might need to take another
+ * item from the array and make it appear at the bottom.
+ */
+ _downloads: [],
+
+ /**
+ * Associates the visible Download objects with their corresponding
+ * DownloadsViewItem object. There is a limited number of view items in the
+ * panel at any given time.
+ */
+ _visibleViewItems: new Map(),
+
+ /**
+ * Called when the number of items in the list changes.
+ */
+ _itemCountChanged() {
+ DownloadsCommon.log("The downloads item count has changed - we are tracking",
+ this._downloads.length, "downloads in total.");
+ let count = this._downloads.length;
+ let hiddenCount = count - this.kItemCountLimit;
+
+ if (count > 0) {
+ DownloadsCommon.log("Setting the panel's hasdownloads attribute to true.");
+ DownloadsPanel.panel.setAttribute("hasdownloads", "true");
+ } else {
+ DownloadsCommon.log("Removing the panel's hasdownloads attribute.");
+ DownloadsPanel.panel.removeAttribute("hasdownloads");
+ }
+
+ // If we've got some hidden downloads, we should activate the
+ // DownloadsSummary. The DownloadsSummary will determine whether or not
+ // it's appropriate to actually display the summary.
+ DownloadsSummary.active = hiddenCount > 0;
+ },
+
+ /**
+ * Element corresponding to the list of downloads.
+ */
+ get richListBox() {
+ delete this.richListBox;
+ return this.richListBox = document.getElementById("downloadsListBox");
+ },
+
+ /**
+ * Element corresponding to the button for showing more downloads.
+ */
+ get downloadsHistory() {
+ delete this.downloadsHistory;
+ return this.downloadsHistory = document.getElementById("downloadsHistory");
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData
+
+ /**
+ * Called before multiple downloads are about to be loaded.
+ */
+ onDataLoadStarting() {
+ DownloadsCommon.log("onDataLoadStarting called for DownloadsView.");
+ this.loading = true;
+ },
+
+ /**
+ * Called after data loading finished.
+ */
+ onDataLoadCompleted() {
+ DownloadsCommon.log("onDataLoadCompleted called for DownloadsView.");
+
+ this.loading = false;
+
+ // We suppressed item count change notifications during the batch load, at
+ // this point we should just call the function once.
+ this._itemCountChanged();
+
+ // Notify the panel that all the initially available downloads have been
+ // loaded. This ensures that the interface is visible, if still required.
+ DownloadsPanel.onViewLoadCompleted();
+ },
+
+ /**
+ * Called when a new download data item is available, either during the
+ * asynchronous data load or when a new download is started.
+ *
+ * @param aDownload
+ * Download object that was just added.
+ * @param aNewest
+ * When true, indicates that this item is the most recent and should be
+ * added in the topmost position. This happens when a new download is
+ * started. When false, indicates that the item is the least recent
+ * and should be appended. The latter generally happens during the
+ * asynchronous data load.
+ */
+ onDownloadAdded(download, aNewest) {
+ DownloadsCommon.log("A new download data item was added - aNewest =",
+ aNewest);
+
+ if (aNewest) {
+ this._downloads.unshift(download);
+ } else {
+ this._downloads.push(download);
+ }
+
+ let itemsNowOverflow = this._downloads.length > this.kItemCountLimit;
+ if (aNewest || !itemsNowOverflow) {
+ // The newly added item is visible in the panel and we must add the
+ // corresponding element. This is either because it is the first item, or
+ // because it was added at the bottom but the list still doesn't overflow.
+ this._addViewItem(download, aNewest);
+ }
+ if (aNewest && itemsNowOverflow) {
+ // If the list overflows, remove the last item from the panel to make room
+ // for the new one that we just added at the top.
+ this._removeViewItem(this._downloads[this.kItemCountLimit]);
+ }
+
+ // For better performance during batch loads, don't update the count for
+ // every item, because the interface won't be visible until load finishes.
+ if (!this.loading) {
+ this._itemCountChanged();
+ }
+ },
+
+ onDownloadStateChanged(download) {
+ let viewItem = this._visibleViewItems.get(download);
+ if (viewItem) {
+ viewItem.onStateChanged();
+ }
+ },
+
+ onDownloadChanged(download) {
+ let viewItem = this._visibleViewItems.get(download);
+ if (viewItem) {
+ viewItem.onChanged();
+ }
+ },
+
+ /**
+ * Called when a data item is removed. Ensures that the widget associated
+ * with the view item is removed from the user interface.
+ *
+ * @param download
+ * Download object that is being removed.
+ */
+ onDownloadRemoved(download) {
+ DownloadsCommon.log("A download data item was removed.");
+
+ let itemIndex = this._downloads.indexOf(download);
+ this._downloads.splice(itemIndex, 1);
+
+ if (itemIndex < this.kItemCountLimit) {
+ // The item to remove is visible in the panel.
+ this._removeViewItem(download);
+ if (this._downloads.length >= this.kItemCountLimit) {
+ // Reinsert the next item into the panel.
+ this._addViewItem(this._downloads[this.kItemCountLimit - 1], false);
+ }
+ }
+
+ this._itemCountChanged();
+
+ // Adjust the panel height if we removed items.
+ DownloadsPanel.setHeightToFit();
+ },
+
+ /**
+ * Associates each richlistitem for a download with its corresponding
+ * DownloadsViewItem object.
+ */
+ _itemsForElements: new Map(),
+
+ itemForElement(element) {
+ return this._itemsForElements.get(element);
+ },
+
+ /**
+ * Creates a new view item associated with the specified data item, and adds
+ * it to the top or the bottom of the list.
+ */
+ _addViewItem(download, aNewest)
+ {
+ DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
+ "aNewest =", aNewest);
+
+ let element = document.createElement("richlistitem");
+ let viewItem = new DownloadsViewItem(download, element);
+ this._visibleViewItems.set(download, viewItem);
+ this._itemsForElements.set(element, viewItem);
+ if (aNewest) {
+ this.richListBox.insertBefore(element, this.richListBox.firstChild);
+ } else {
+ this.richListBox.appendChild(element);
+ }
+ },
+
+ /**
+ * Removes the view item associated with the specified data item.
+ */
+ _removeViewItem(download) {
+ DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list.");
+ let element = this._visibleViewItems.get(download).element;
+ let previousSelectedIndex = this.richListBox.selectedIndex;
+ this.richListBox.removeChild(element);
+ if (previousSelectedIndex != -1) {
+ this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
+ this.richListBox.itemCount - 1);
+ }
+ this._visibleViewItems.delete(download);
+ this._itemsForElements.delete(element);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// User interface event functions
+
+ /**
+ * Helper function to do commands on a specific download item.
+ *
+ * @param aEvent
+ * Event object for the event being handled. If the event target is
+ * not a richlistitem that represents a download, this function will
+ * walk up the parent nodes until it finds a DOM node that is.
+ * @param aCommand
+ * The command to be performed.
+ */
+ onDownloadCommand(aEvent, aCommand) {
+ let target = aEvent.target;
+ while (target.nodeName != "richlistitem") {
+ target = target.parentNode;
+ }
+ DownloadsView.itemForElement(target).doCommand(aCommand);
+ },
+
+ onDownloadClick(aEvent) {
+ // Handle primary clicks only, and exclude the action button.
+ if (aEvent.button == 0 &&
+ !aEvent.originalTarget.hasAttribute("oncommand")) {
+ let target = aEvent.target;
+ while (target.nodeName != "richlistitem") {
+ target = target.parentNode;
+ }
+ let download = DownloadsView.itemForElement(target).download;
+ if (download.hasBlockedData) {
+ goDoCommand("downloadsCmd_showBlockedInfo");
+ } else {
+ goDoCommand("downloadsCmd_open");
+ }
+ }
+ },
+
+ /**
+ * Handles keypress events on a download item.
+ */
+ onDownloadKeyPress(aEvent) {
+ // Pressing the key on buttons should not invoke the action because the
+ // event has already been handled by the button itself.
+ if (aEvent.originalTarget.hasAttribute("command") ||
+ aEvent.originalTarget.hasAttribute("oncommand")) {
+ return;
+ }
+
+ if (aEvent.charCode == " ".charCodeAt(0)) {
+ goDoCommand("downloadsCmd_pauseResume");
+ return;
+ }
+
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
+ goDoCommand("downloadsCmd_doDefault");
+ }
+ },
+
+ /**
+ * Event handlers to keep track of context menu state (open/closed) for
+ * download items.
+ */
+ onContextPopupShown(aEvent) {
+ // Ignore events raised by nested popups.
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+
+ DownloadsCommon.log("Context menu has shown.");
+ this.contextMenuOpen = true;
+ },
+
+ onContextPopupHidden(aEvent) {
+ // Ignore events raised by nested popups.
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+
+ DownloadsCommon.log("Context menu has hidden.");
+ this.contextMenuOpen = false;
+ },
+
+ /**
+ * Mouse listeners to handle selection on hover.
+ */
+ onDownloadMouseOver(aEvent) {
+ if (!(this.contextMenuOpen || this.subViewOpen) &&
+ aEvent.target.parentNode == this.richListBox) {
+ this.richListBox.selectedItem = aEvent.target;
+ }
+ },
+
+ onDownloadMouseOut(aEvent) {
+ if (!(this.contextMenuOpen || this.subViewOpen) &&
+ aEvent.target.parentNode == this.richListBox) {
+ // If the destination element is outside of the richlistitem, clear the
+ // selection.
+ let element = aEvent.relatedTarget;
+ while (element && element != aEvent.target) {
+ element = element.parentNode;
+ }
+ if (!element) {
+ this.richListBox.selectedIndex = -1;
+ }
+ }
+ },
+
+ onDownloadContextMenu(aEvent) {
+ let element = this.richListBox.selectedItem;
+ if (!element) {
+ return;
+ }
+
+ DownloadsViewController.updateCommands();
+
+ // Set the state attribute so that only the appropriate items are displayed.
+ let contextMenu = document.getElementById("downloadsContextMenu");
+ contextMenu.setAttribute("state", element.getAttribute("state"));
+ contextMenu.classList.toggle("temporary-block",
+ element.classList.contains("temporary-block"));
+ },
+
+ onDownloadDragStart(aEvent) {
+ let element = this.richListBox.selectedItem;
+ if (!element) {
+ return;
+ }
+
+ // We must check for existence synchronously because this is a DOM event.
+ let file = new FileUtils.File(DownloadsView.itemForElement(element)
+ .download.target.path);
+ if (!file.exists()) {
+ return;
+ }
+
+ let dataTransfer = aEvent.dataTransfer;
+ dataTransfer.mozSetDataAt("application/x-moz-file", file, 0);
+ dataTransfer.effectAllowed = "copyMove";
+ let spec = NetUtil.newURI(file).spec;
+ dataTransfer.setData("text/uri-list", spec);
+ dataTransfer.setData("text/plain", spec);
+ dataTransfer.addElement(element);
+
+ aEvent.stopPropagation();
+ },
+}
+
+XPCOMUtils.defineConstant(this, "DownloadsView", DownloadsView);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewItem
+
+/**
+ * Builds and updates a single item in the downloads list widget, responding to
+ * changes in the download state and real-time data, and handles the user
+ * interaction events related to a single item in the downloads list widgets.
+ *
+ * @param download
+ * Download object to be associated with the view item.
+ * @param aElement
+ * XUL element corresponding to the single download item in the view.
+ */
+function DownloadsViewItem(download, aElement) {
+ this.download = download;
+ this.element = aElement;
+ this.element._shell = this;
+
+ this.element.setAttribute("type", "download");
+ this.element.classList.add("download-state");
+
+ this._updateState();
+}
+
+DownloadsViewItem.prototype = {
+ __proto__: DownloadsViewUI.DownloadElementShell.prototype,
+
+ /**
+ * The XUL element corresponding to the associated richlistbox item.
+ */
+ _element: null,
+
+ onStateChanged() {
+ this._updateState();
+ },
+
+ onChanged() {
+ this._updateProgress();
+ },
+
+ isCommandEnabled(aCommand) {
+ switch (aCommand) {
+ case "downloadsCmd_open": {
+ if (!this.download.succeeded) {
+ return false;
+ }
+
+ let file = new FileUtils.File(this.download.target.path);
+ return file.exists();
+ }
+ case "downloadsCmd_show": {
+ let file = new FileUtils.File(this.download.target.path);
+ if (file.exists()) {
+ return true;
+ }
+
+ if (!this.download.target.partFilePath) {
+ return false;
+ }
+
+ let partFile = new FileUtils.File(this.download.target.partFilePath);
+ return partFile.exists();
+ }
+ case "cmd_delete":
+ case "downloadsCmd_cancel":
+ case "downloadsCmd_copyLocation":
+ case "downloadsCmd_doDefault":
+ return true;
+ case "downloadsCmd_showBlockedInfo":
+ return this.download.hasBlockedData;
+ }
+ return DownloadsViewUI.DownloadElementShell.prototype
+ .isCommandEnabled.call(this, aCommand);
+ },
+
+ doCommand(aCommand) {
+ if (this.isCommandEnabled(aCommand)) {
+ this[aCommand]();
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Item commands
+
+ cmd_delete() {
+ DownloadsCommon.removeAndFinalizeDownload(this.download);
+ PlacesUtils.bhistory.removePage(
+ NetUtil.newURI(this.download.source.url));
+ },
+
+ downloadsCmd_unblock() {
+ DownloadsPanel.hidePanel();
+ this.confirmUnblock(window, "unblock");
+ },
+
+ downloadsCmd_chooseUnblock() {
+ DownloadsPanel.hidePanel();
+ this.confirmUnblock(window, "chooseUnblock");
+ },
+
+ downloadsCmd_unblockAndOpen() {
+ DownloadsPanel.hidePanel();
+ this.unblockAndOpenDownload().catch(Cu.reportError);
+ },
+
+ downloadsCmd_open() {
+ this.download.launch().catch(Cu.reportError);
+
+ // We explicitly close the panel here to give the user the feedback that
+ // their click has been received, and we're handling the action.
+ // Otherwise, we'd have to wait for the file-type handler to execute
+ // before the panel would close. This also helps to prevent the user from
+ // accidentally opening a file several times.
+ DownloadsPanel.hidePanel();
+ },
+
+ downloadsCmd_show() {
+ let file = new FileUtils.File(this.download.target.path);
+ DownloadsCommon.showDownloadedFile(file);
+
+ // We explicitly close the panel here to give the user the feedback that
+ // their click has been received, and we're handling the action.
+ // Otherwise, we'd have to wait for the operating system file manager
+ // window to open before the panel closed. This also helps to prevent the
+ // user from opening the containing folder several times.
+ DownloadsPanel.hidePanel();
+ },
+
+ downloadsCmd_showBlockedInfo() {
+ DownloadsBlockedSubview.toggle(this.element,
+ ...this.rawBlockedTitleAndDetails);
+ },
+
+ downloadsCmd_openReferrer() {
+ openURL(this.download.source.referrer);
+ },
+
+ downloadsCmd_copyLocation() {
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.download.source.url);
+ },
+
+ downloadsCmd_doDefault() {
+ let defaultCommand = this.currentDefaultCommandName;
+ if (defaultCommand && this.isCommandEnabled(defaultCommand)) {
+ this.doCommand(defaultCommand);
+ }
+ },
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewController
+
+/**
+ * Handles part of the user interaction events raised by the downloads list
+ * widget, in particular the "commands" that apply to multiple items, and
+ * dispatches the commands that apply to individual items.
+ */
+const DownloadsViewController = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Initialization and termination
+
+ initialize() {
+ window.controllers.insertControllerAt(0, this);
+ },
+
+ terminate() {
+ window.controllers.removeController(this);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIController
+
+ supportsCommand(aCommand) {
+ if (aCommand === "downloadsCmd_clearList") {
+ return true;
+ }
+ // Firstly, determine if this is a command that we can handle.
+ if (!DownloadsViewUI.isCommandName(aCommand)) {
+ return false;
+ }
+ if (!(aCommand in this) &&
+ !(aCommand in DownloadsViewItem.prototype)) {
+ return false;
+ }
+ // The currently supported commands depend on whether the blocked subview is
+ // showing. If it is, then take the following path.
+ if (DownloadsBlockedSubview.view.showingSubView) {
+ let blockedSubviewCmds = [
+ "downloadsCmd_unblockAndOpen",
+ "cmd_delete",
+ ];
+ return blockedSubviewCmds.indexOf(aCommand) >= 0;
+ }
+ // If the blocked subview is not showing, then determine if focus is on a
+ // control in the downloads list.
+ let element = document.commandDispatcher.focusedElement;
+ while (element && element != DownloadsView.richListBox) {
+ element = element.parentNode;
+ }
+ // We should handle the command only if the downloads list is among the
+ // ancestors of the focused element.
+ return !!element;
+ },
+
+ isCommandEnabled(aCommand) {
+ // Handle commands that are not selection-specific.
+ if (aCommand == "downloadsCmd_clearList") {
+ return DownloadsCommon.getData(window).canRemoveFinished;
+ }
+
+ // Other commands are selection-specific.
+ let element = DownloadsView.richListBox.selectedItem;
+ return element && DownloadsView.itemForElement(element)
+ .isCommandEnabled(aCommand);
+ },
+
+ doCommand(aCommand) {
+ // If this command is not selection-specific, execute it.
+ if (aCommand in this) {
+ this[aCommand]();
+ return;
+ }
+
+ // Other commands are selection-specific.
+ let element = DownloadsView.richListBox.selectedItem;
+ if (element) {
+ // The doCommand function also checks if the command is enabled.
+ DownloadsView.itemForElement(element).doCommand(aCommand);
+ }
+ },
+
+ onEvent() {},
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Other functions
+
+ updateCommands() {
+ function updateCommandsForObject(object) {
+ for (let name in object) {
+ if (DownloadsViewUI.isCommandName(name)) {
+ goUpdateCommand(name);
+ }
+ }
+ }
+ updateCommandsForObject(this);
+ updateCommandsForObject(DownloadsViewItem.prototype);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Selection-independent commands
+
+ downloadsCmd_clearList() {
+ DownloadsCommon.getData(window).removeFinished();
+ },
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewController);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsSummary
+
+/**
+ * Manages the summary at the bottom of the downloads panel list if the number
+ * of items in the list exceeds the panels limit.
+ */
+const DownloadsSummary = {
+
+ /**
+ * Sets the active state of the summary. When active, the summary subscribes
+ * to the DownloadsCommon DownloadsSummaryData singleton.
+ *
+ * @param aActive
+ * Set to true to activate the summary.
+ */
+ set active(aActive) {
+ if (aActive == this._active || !this._summaryNode) {
+ return this._active;
+ }
+ if (aActive) {
+ DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
+ .refreshView(this);
+ } else {
+ DownloadsFooter.showingSummary = false;
+ }
+
+ return this._active = aActive;
+ },
+
+ /**
+ * Returns the active state of the downloads summary.
+ */
+ get active() {
+ return this._active;
+ },
+
+ _active: false,
+
+ /**
+ * Sets whether or not we show the progress bar.
+ *
+ * @param aShowingProgress
+ * True if we should show the progress bar.
+ */
+ set showingProgress(aShowingProgress) {
+ if (aShowingProgress) {
+ this._summaryNode.setAttribute("inprogress", "true");
+ } else {
+ this._summaryNode.removeAttribute("inprogress");
+ }
+ // If progress isn't being shown, then we simply do not show the summary.
+ return DownloadsFooter.showingSummary = aShowingProgress;
+ },
+
+ /**
+ * Sets the amount of progress that is visible in the progress bar.
+ *
+ * @param aValue
+ * A value between 0 and 100 to represent the progress of the
+ * summarized downloads.
+ */
+ set percentComplete(aValue) {
+ if (this._progressNode) {
+ this._progressNode.setAttribute("value", aValue);
+ }
+ return aValue;
+ },
+
+ /**
+ * Sets the description for the download summary.
+ *
+ * @param aValue
+ * A string representing the description of the summarized
+ * downloads.
+ */
+ set description(aValue) {
+ if (this._descriptionNode) {
+ this._descriptionNode.setAttribute("value", aValue);
+ this._descriptionNode.setAttribute("tooltiptext", aValue);
+ }
+ return aValue;
+ },
+
+ /**
+ * Sets the details for the download summary, such as the time remaining,
+ * the amount of bytes transferred, etc.
+ *
+ * @param aValue
+ * A string representing the details of the summarized
+ * downloads.
+ */
+ set details(aValue) {
+ if (this._detailsNode) {
+ this._detailsNode.setAttribute("value", aValue);
+ this._detailsNode.setAttribute("tooltiptext", aValue);
+ }
+ return aValue;
+ },
+
+ /**
+ * Focuses the root element of the summary.
+ */
+ focus() {
+ if (this._summaryNode) {
+ this._summaryNode.focus();
+ }
+ },
+
+ /**
+ * Respond to keydown events on the Downloads Summary node.
+ *
+ * @param aEvent
+ * The keydown event being handled.
+ */
+ onKeyDown(aEvent) {
+ if (aEvent.charCode == " ".charCodeAt(0) ||
+ aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
+ DownloadsPanel.showDownloadsHistory();
+ }
+ },
+
+ /**
+ * Respond to click events on the Downloads Summary node.
+ *
+ * @param aEvent
+ * The click event being handled.
+ */
+ onClick(aEvent) {
+ DownloadsPanel.showDownloadsHistory();
+ },
+
+ /**
+ * Element corresponding to the root of the downloads summary.
+ */
+ get _summaryNode() {
+ let node = document.getElementById("downloadsSummary");
+ if (!node) {
+ return null;
+ }
+ delete this._summaryNode;
+ return this._summaryNode = node;
+ },
+
+ /**
+ * Element corresponding to the progress bar in the downloads summary.
+ */
+ get _progressNode() {
+ let node = document.getElementById("downloadsSummaryProgress");
+ if (!node) {
+ return null;
+ }
+ delete this._progressNode;
+ return this._progressNode = node;
+ },
+
+ /**
+ * Element corresponding to the main description of the downloads
+ * summary.
+ */
+ get _descriptionNode() {
+ let node = document.getElementById("downloadsSummaryDescription");
+ if (!node) {
+ return null;
+ }
+ delete this._descriptionNode;
+ return this._descriptionNode = node;
+ },
+
+ /**
+ * Element corresponding to the secondary description of the downloads
+ * summary.
+ */
+ get _detailsNode() {
+ let node = document.getElementById("downloadsSummaryDetails");
+ if (!node) {
+ return null;
+ }
+ delete this._detailsNode;
+ return this._detailsNode = node;
+ }
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsSummary", DownloadsSummary);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsFooter
+
+/**
+ * Manages events sent to to the footer vbox, which contains both the
+ * DownloadsSummary as well as the "Show All Downloads" button.
+ */
+const DownloadsFooter = {
+
+ /**
+ * Focuses the appropriate element within the footer. If the summary
+ * is visible, focus it. If not, focus the "Show All Downloads"
+ * button.
+ */
+ focus() {
+ if (this._showingSummary) {
+ DownloadsSummary.focus();
+ } else {
+ DownloadsView.downloadsHistory.focus();
+ }
+ },
+
+ _showingSummary: false,
+
+ /**
+ * Sets whether or not the Downloads Summary should be displayed in the
+ * footer. If not, the "Show All Downloads" button is shown instead.
+ */
+ set showingSummary(aValue) {
+ if (this._footerNode) {
+ if (aValue) {
+ this._footerNode.setAttribute("showingsummary", "true");
+ } else {
+ this._footerNode.removeAttribute("showingsummary");
+ }
+ if (!aValue && this._showingSummary) {
+ // Make sure the panel's height shrinks when the summary is hidden.
+ DownloadsPanel.setHeightToFit();
+ }
+ this._showingSummary = aValue;
+ }
+ return aValue;
+ },
+
+ /**
+ * Element corresponding to the footer of the downloads panel.
+ */
+ get _footerNode() {
+ let node = document.getElementById("downloadsFooter");
+ if (!node) {
+ return null;
+ }
+ delete this._footerNode;
+ return this._footerNode = node;
+ }
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsFooter", DownloadsFooter);
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsBlockedSubview
+
+/**
+ * Manages the blocked subview that slides in when you click a blocked download.
+ */
+const DownloadsBlockedSubview = {
+
+ get subview() {
+ let subview = document.getElementById("downloadsPanel-blockedSubview");
+ delete this.subview;
+ return this.subview = subview;
+ },
+
+ /**
+ * Elements in the subview.
+ */
+ get elements() {
+ let idSuffixes = [
+ "title",
+ "details1",
+ "details2",
+ "openButton",
+ "deleteButton",
+ ];
+ let elements = idSuffixes.reduce((memo, s) => {
+ memo[s] = document.getElementById("downloadsPanel-blockedSubview-" + s);
+ return memo;
+ }, {});
+ delete this.elements;
+ return this.elements = elements;
+ },
+
+ /**
+ * The multiview that contains both the main view and the subview.
+ */
+ get view() {
+ let view = document.getElementById("downloadsPanel-multiView");
+ delete this.view;
+ return this.view = view;
+ },
+
+ /**
+ * The blocked-download richlistitem element that was clicked to show the
+ * subview. If the subview is not showing, this is undefined.
+ */
+ element: undefined,
+
+ /**
+ * Slides in the blocked subview.
+ *
+ * @param element
+ * The blocked-download richlistitem element that was clicked.
+ * @param title
+ * The title to show in the subview.
+ * @param details
+ * An array of strings with information about the block.
+ */
+ toggle(element, title, details) {
+ if (this.view.showingSubView) {
+ this.hide();
+ return;
+ }
+
+ this.element = element;
+ element.setAttribute("showingsubview", "true");
+ DownloadsView.subViewOpen = true;
+ DownloadsViewController.updateCommands();
+
+ let e = this.elements;
+ let s = DownloadsCommon.strings;
+ e.title.textContent = title;
+ e.details1.textContent = details[0];
+ e.details2.textContent = details[1];
+ e.openButton.label = s.unblockButtonOpen;
+ e.deleteButton.label = s.unblockButtonConfirmBlock;
+
+ let verdict = element.getAttribute("verdict");
+ this.subview.setAttribute("verdict", verdict);
+ this.subview.addEventListener("ViewHiding", this);
+
+ this.view.showSubView(this.subview.id);
+
+ // Without this, the mainView is more narrow than the panel once all
+ // downloads are removed from the panel.
+ document.getElementById("downloadsPanel-mainView").style.minWidth =
+ window.getComputedStyle(this.view).width;
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "ViewHiding":
+ this.subview.removeEventListener(event.type, this);
+ this.element.removeAttribute("showingsubview");
+ DownloadsView.subViewOpen = false;
+ delete this.element;
+ break;
+ default:
+ DownloadsCommon.log("Unhandled DownloadsBlockedSubview event: " +
+ event.type);
+ break;
+ }
+ },
+
+ /**
+ * Slides out the blocked subview and shows the main view.
+ */
+ hide() {
+ this.view.showMainView();
+ // The point of this is to focus the proper element in the panel now that
+ // the main view is showing again. showPanel handles that.
+ DownloadsPanel.showPanel();
+ },
+
+ /**
+ * Deletes the download and hides the entire panel.
+ */
+ confirmBlock() {
+ goDoCommand("cmd_delete");
+ DownloadsPanel.hidePanel();
+ },
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsBlockedSubview",
+ DownloadsBlockedSubview);
diff --git a/browser/components/downloads/content/downloadsOverlay.xul b/browser/components/downloads/content/downloadsOverlay.xul
new file mode 100644
index 000000000..9fe2ee022
--- /dev/null
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -0,0 +1,210 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# vim: set ts=2 et sw=2 tw=80:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="downloadsOverlay">
+
+ <commandset>
+ <command id="downloadsCmd_doDefault"
+ oncommand="goDoCommand('downloadsCmd_doDefault')"/>
+ <command id="downloadsCmd_pauseResume"
+ oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
+ <command id="downloadsCmd_cancel"
+ oncommand="goDoCommand('downloadsCmd_cancel')"/>
+ <command id="downloadsCmd_unblock"
+ oncommand="goDoCommand('downloadsCmd_unblock')"/>
+ <command id="downloadsCmd_chooseUnblock"
+ oncommand="goDoCommand('downloadsCmd_chooseUnblock')"/>
+ <command id="downloadsCmd_unblockAndOpen"
+ oncommand="goDoCommand('downloadsCmd_unblockAndOpen')"/>
+ <command id="downloadsCmd_confirmBlock"
+ oncommand="goDoCommand('downloadsCmd_confirmBlock')"/>
+ <command id="downloadsCmd_open"
+ oncommand="goDoCommand('downloadsCmd_open')"/>
+ <command id="downloadsCmd_show"
+ oncommand="goDoCommand('downloadsCmd_show')"/>
+ <command id="downloadsCmd_retry"
+ oncommand="goDoCommand('downloadsCmd_retry')"/>
+ <command id="downloadsCmd_openReferrer"
+ oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
+ <command id="downloadsCmd_copyLocation"
+ oncommand="goDoCommand('downloadsCmd_copyLocation')"/>
+ <command id="downloadsCmd_clearList"
+ oncommand="goDoCommand('downloadsCmd_clearList')"/>
+ </commandset>
+
+ <popupset id="mainPopupSet">
+ <!-- The panel has level="top" to ensure that it is never hidden by the
+ taskbar on Windows. See bug 672365. For accessibility to screen
+ readers, we use a label on the panel instead of the anchor because the
+ panel can also be displayed without an anchor. -->
+ <panel id="downloadsPanel"
+ aria-label="&downloads.title;"
+ role="group"
+ type="arrow"
+ orient="vertical"
+ level="top"
+ onpopupshown="DownloadsPanel.onPopupShown(event);"
+ onpopuphidden="DownloadsPanel.onPopupHidden(event);">
+ <!-- The following popup menu should be a child of the panel element,
+ otherwise flickering may occur when the cursor is moved over the area
+ of a disabled menu item that overlaps the panel. See bug 492960. -->
+ <menupopup id="downloadsContextMenu"
+ onpopupshown="DownloadsView.onContextPopupShown(event);"
+ onpopuphidden="DownloadsView.onContextPopupHidden(event);"
+ class="download-state">
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadPauseMenuItem"
+ label="&cmd.pause.label;"
+ accesskey="&cmd.pause.accesskey;"/>
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadResumeMenuItem"
+ label="&cmd.resume.label;"
+ accesskey="&cmd.resume.accesskey;"/>
+ <menuitem command="downloadsCmd_cancel"
+ class="downloadCancelMenuItem"
+ label="&cmd.cancel.label;"
+ accesskey="&cmd.cancel.accesskey;"/>
+ <menuitem command="downloadsCmd_unblock"
+ class="downloadUnblockMenuItem"
+ label="&cmd.unblock2.label;"
+ accesskey="&cmd.unblock2.accesskey;"/>
+ <menuitem command="cmd_delete"
+ class="downloadRemoveFromHistoryMenuItem"
+ label="&cmd.removeFromHistory.label;"
+ accesskey="&cmd.removeFromHistory.accesskey;"/>
+ <menuitem command="downloadsCmd_show"
+ class="downloadShowMenuItem"
+#ifdef XP_MACOSX
+ label="&cmd.showMac.label;"
+ accesskey="&cmd.showMac.accesskey;"
+#else
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+#endif
+ />
+
+ <menuseparator class="downloadCommandsSeparator"/>
+
+ <menuitem command="downloadsCmd_openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"/>
+ <menuitem command="downloadsCmd_copyLocation"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"/>
+
+ <menuseparator/>
+
+ <menuitem command="downloadsCmd_clearList"
+ label="&cmd.clearList2.label;"
+ accesskey="&cmd.clearList2.accesskey;"/>
+ </menupopup>
+
+ <panelmultiview id="downloadsPanel-multiView"
+ mainViewId="downloadsPanel-mainView"
+ align="stretch">
+
+ <panelview id="downloadsPanel-mainView"
+ flex="1"
+ align="stretch">
+ <richlistbox id="downloadsListBox"
+ context="downloadsContextMenu"
+ onmouseover="DownloadsView.onDownloadMouseOver(event);"
+ onmouseout="DownloadsView.onDownloadMouseOut(event);"
+ oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
+ ondragstart="DownloadsView.onDownloadDragStart(event);"/>
+ <description id="emptyDownloads"
+ mousethrough="always">
+ &downloadsPanelEmpty.label;
+ </description>
+ <spacer flex="1"/>
+ <vbox id="downloadsFooter"
+ class="downloadsPanelFooter">
+ <stack>
+ <hbox id="downloadsSummary"
+ align="center"
+ orient="horizontal"
+ onkeydown="DownloadsSummary.onKeyDown(event);"
+ onclick="DownloadsSummary.onClick(event);">
+ <image class="downloadTypeIcon" />
+ <vbox pack="center"
+ class="downloadContainer"
+ style="width: &downloadDetails.width;">
+ <description id="downloadsSummaryDescription"
+ style="min-width: &downloadsSummary.minWidth2;"/>
+ <progressmeter id="downloadsSummaryProgress"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ mode="normal" />
+ <description id="downloadsSummaryDetails"
+ crop="end"/>
+ </vbox>
+ </hbox>
+ <hbox id="downloadsFooterButtons">
+ <button id="downloadsHistory"
+ class="downloadsPanelFooterButton"
+ label="&downloadsHistory.label;"
+ accesskey="&downloadsHistory.accesskey;"
+ flex="1"
+ oncommand="DownloadsPanel.showDownloadsHistory();"/>
+ <toolbarseparator id="downloadsFooterButtonsSplitter"
+ class="downloadsDropmarkerSplitter"/>
+ <button id="downloadsFooterDropmarker"
+ class="downloadsPanelFooterButton downloadsDropmarker"
+ type="menu">
+ <menupopup id="downloadSubPanel"
+ onpopupshowing="DownloadsPanel.onFooterPopupShowing(event);"
+ onpopuphidden="DownloadsPanel.onFooterPopupHidden(event);"
+ position="after_end">
+ <menuitem id="downloadsDropdownItemClearList"
+ command="downloadsCmd_clearList"
+ label="&cmd.clearList2.label;"/>
+ <menuitem id="downloadsDropdownItemOpenDownloadsFolder"
+ oncommand="DownloadsPanel.openDownloadsFolder();"
+ label="&openDownloadsFolder.label;"/>
+ </menupopup>
+ </button>
+ </hbox>
+ </stack>
+ </vbox>
+ </panelview>
+
+ <panelview id="downloadsPanel-blockedSubview"
+ orient="vertical"
+ flex="1">
+ <description id="downloadsPanel-blockedSubview-title"/>
+ <description id="downloadsPanel-blockedSubview-details1"/>
+ <description id="downloadsPanel-blockedSubview-details2"/>
+ <spacer flex="1"/>
+ <hbox id="downloadsPanel-blockedSubview-buttons"
+ class="downloadsPanelFooter"
+ align="stretch">
+ <button id="downloadsPanel-blockedSubview-openButton"
+ class="downloadsPanelFooterButton"
+ command="downloadsCmd_unblockAndOpen"
+ flex="1"/>
+ <toolbarseparator/>
+ <button id="downloadsPanel-blockedSubview-deleteButton"
+ class="downloadsPanelFooterButton"
+ oncommand="DownloadsBlockedSubview.confirmBlock();"
+ default="true"
+ flex="1"/>
+ </hbox>
+ </panelview>
+
+ </panelmultiview>
+
+ </panel>
+ </popupset>
+</overlay>
diff --git a/browser/components/downloads/content/indicator.js b/browser/components/downloads/content/indicator.js
new file mode 100644
index 000000000..4c22a6e5d
--- /dev/null
+++ b/browser/components/downloads/content/indicator.js
@@ -0,0 +1,606 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Handles the indicator that displays the progress of ongoing downloads, which
+ * is also used as the anchor for the downloads panel.
+ *
+ * This module includes the following constructors and global objects:
+ *
+ * DownloadsButton
+ * Main entry point for the downloads indicator. Depending on how the toolbars
+ * have been customized, this object determines if we should show a fully
+ * functional indicator, a placeholder used during customization and in the
+ * customization palette, or a neutral view as a temporary anchor for the
+ * downloads panel.
+ *
+ * DownloadsIndicatorView
+ * Builds and updates the actual downloads status widget, responding to changes
+ * in the global status data, or provides a neutral view if the indicator is
+ * removed from the toolbars and only used as a temporary anchor. In addition,
+ * handles the user interaction events raised by the widget.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsButton
+
+/**
+ * Main entry point for the downloads indicator. Depending on how the toolbars
+ * have been customized, this object determines if we should show a fully
+ * functional indicator, a placeholder used during customization and in the
+ * customization palette, or a neutral view as a temporary anchor for the
+ * downloads panel.
+ */
+const DownloadsButton = {
+ /**
+ * Location of the indicator overlay.
+ */
+ get kIndicatorOverlay() {
+ return "chrome://browser/content/downloads/indicatorOverlay.xul";
+ },
+
+ /**
+ * Returns a reference to the downloads button position placeholder, or null
+ * if not available because it has been removed from the toolbars.
+ */
+ get _placeholder() {
+ return document.getElementById("downloads-button");
+ },
+
+ /**
+ * This function is called asynchronously just after window initialization.
+ *
+ * NOTE: This function should limit the input/output it performs to improve
+ * startup time.
+ */
+ initializeIndicator() {
+ DownloadsIndicatorView.ensureInitialized();
+ },
+
+ /**
+ * Indicates whether toolbar customization is in progress.
+ */
+ _customizing: false,
+
+ /**
+ * This function is called when toolbar customization starts.
+ *
+ * During customization, we never show the actual download progress indication
+ * or the event notifications, but we show a neutral placeholder. The neutral
+ * placeholder is an ordinary button defined in the browser window that can be
+ * moved freely between the toolbars and the customization palette.
+ */
+ customizeStart() {
+ // Prevent the indicator from being displayed as a temporary anchor
+ // during customization, even if requested using the getAnchor method.
+ this._customizing = true;
+ this._anchorRequested = false;
+ },
+
+ /**
+ * This function is called when toolbar customization ends.
+ */
+ customizeDone() {
+ this._customizing = false;
+ DownloadsIndicatorView.afterCustomize();
+ },
+
+ /**
+ * Determines the position where the indicator should appear, and moves its
+ * associated element to the new position.
+ *
+ * @return Anchor element, or null if the indicator is not visible.
+ */
+ _getAnchorInternal() {
+ let indicator = DownloadsIndicatorView.indicator;
+ if (!indicator) {
+ // Exit now if the indicator overlay isn't loaded yet, or if the button
+ // is not in the document.
+ return null;
+ }
+
+ indicator.open = this._anchorRequested;
+
+ let widget = CustomizableUI.getWidget("downloads-button")
+ .forWindow(window);
+ // Determine if the indicator is located on an invisible toolbar.
+ if (!isElementVisible(indicator.parentNode) && !widget.overflowed) {
+ return null;
+ }
+
+ return DownloadsIndicatorView.indicatorAnchor;
+ },
+
+ /**
+ * Checks whether the indicator is, or will soon be visible in the browser
+ * window.
+ *
+ * @param aCallback
+ * Called once the indicator overlay has loaded. Gets a boolean
+ * argument representing the indicator visibility.
+ */
+ checkIsVisible(aCallback) {
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, () => {
+ if (!this._placeholder) {
+ aCallback(false);
+ } else {
+ let element = DownloadsIndicatorView.indicator || this._placeholder;
+ aCallback(isElementVisible(element.parentNode));
+ }
+ });
+ },
+
+ /**
+ * Indicates whether we should try and show the indicator temporarily as an
+ * anchor for the panel, even if the indicator would be hidden by default.
+ */
+ _anchorRequested: false,
+
+ /**
+ * Ensures that there is an anchor available for the panel.
+ *
+ * @param aCallback
+ * Called when the anchor is available, passing the element where the
+ * panel should be anchored, or null if an anchor is not available (for
+ * example because both the tab bar and the navigation bar are hidden).
+ */
+ getAnchor(aCallback) {
+ // Do not allow anchoring the panel to the element while customizing.
+ if (this._customizing) {
+ aCallback(null);
+ return;
+ }
+
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay, () => {
+ this._anchorRequested = true;
+ aCallback(this._getAnchorInternal());
+ });
+ },
+
+ /**
+ * Allows the temporary anchor to be hidden.
+ */
+ releaseAnchor() {
+ this._anchorRequested = false;
+ this._getAnchorInternal();
+ },
+
+ get _tabsToolbar() {
+ delete this._tabsToolbar;
+ return this._tabsToolbar = document.getElementById("TabsToolbar");
+ },
+
+ get _navBar() {
+ delete this._navBar;
+ return this._navBar = document.getElementById("nav-bar");
+ }
+};
+
+Object.defineProperty(this, "DownloadsButton", {
+ value: DownloadsButton,
+ enumerable: true,
+ writable: false
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsIndicatorView
+
+/**
+ * Builds and updates the actual downloads status widget, responding to changes
+ * in the global status data, or provides a neutral view if the indicator is
+ * removed from the toolbars and only used as a temporary anchor. In addition,
+ * handles the user interaction events raised by the widget.
+ */
+const DownloadsIndicatorView = {
+ /**
+ * True when the view is connected with the underlying downloads data.
+ */
+ _initialized: false,
+
+ /**
+ * True when the user interface elements required to display the indicator
+ * have finished loading in the browser window, and can be referenced.
+ */
+ _operational: false,
+
+ /**
+ * Prepares the downloads indicator to be displayed.
+ */
+ ensureInitialized() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+
+ window.addEventListener("unload", this.onWindowUnload, false);
+ DownloadsCommon.getIndicatorData(window).addView(this);
+ },
+
+ /**
+ * Frees the internal resources related to the indicator.
+ */
+ ensureTerminated() {
+ if (!this._initialized) {
+ return;
+ }
+ this._initialized = false;
+
+ window.removeEventListener("unload", this.onWindowUnload, false);
+ DownloadsCommon.getIndicatorData(window).removeView(this);
+
+ // Reset the view properties, so that a neutral indicator is displayed if we
+ // are visible only temporarily as an anchor.
+ this.counter = "";
+ this.percentComplete = 0;
+ this.paused = false;
+ this.attention = DownloadsCommon.ATTENTION_NONE;
+ },
+
+ /**
+ * Ensures that the user interface elements required to display the indicator
+ * are loaded, then invokes the given callback.
+ */
+ _ensureOperational(aCallback) {
+ if (this._operational) {
+ if (aCallback) {
+ aCallback();
+ }
+ return;
+ }
+
+ // If we don't have a _placeholder, there's no chance that the overlay
+ // will load correctly: bail (and don't set _operational to true!)
+ if (!DownloadsButton._placeholder) {
+ return;
+ }
+
+ DownloadsOverlayLoader.ensureOverlayLoaded(
+ DownloadsButton.kIndicatorOverlay,
+ () => {
+ this._operational = true;
+
+ // If the view is initialized, we need to update the elements now that
+ // they are finally available in the document.
+ if (this._initialized) {
+ DownloadsCommon.getIndicatorData(window).refreshView(this);
+ }
+
+ if (aCallback) {
+ aCallback();
+ }
+ });
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Direct control functions
+
+ /**
+ * Set while we are waiting for a notification to fade out.
+ */
+ _notificationTimeout: null,
+
+ /**
+ * Check if the panel containing aNode is open.
+ * @param aNode
+ * the node whose panel we're interested in.
+ */
+ _isAncestorPanelOpen(aNode) {
+ while (aNode && aNode.localName != "panel") {
+ aNode = aNode.parentNode;
+ }
+ return aNode && aNode.state == "open";
+ },
+
+ /**
+ * If the status indicator is visible in its assigned position, shows for a
+ * brief time a visual notification of a relevant event, like a new download.
+ *
+ * @param aType
+ * Set to "start" for new downloads, "finish" for completed downloads.
+ */
+ showEventNotification(aType) {
+ if (!this._initialized) {
+ return;
+ }
+
+ if (!DownloadsCommon.animateNotifications) {
+ return;
+ }
+
+ // No need to show visual notification if the panel is visible.
+ if (DownloadsPanel.isPanelShowing) {
+ return;
+ }
+
+ let anchor = DownloadsButton._placeholder;
+ let widgetGroup = CustomizableUI.getWidget("downloads-button");
+ let widget = widgetGroup.forWindow(window);
+ if (widget.overflowed || widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ if (anchor && this._isAncestorPanelOpen(anchor)) {
+ // If the containing panel is open, don't do anything, because the
+ // notification would appear under the open panel. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=984023
+ return;
+ }
+
+ // Otherwise, try to use the anchor of the panel:
+ anchor = widget.anchor;
+ }
+ if (!anchor || !isElementVisible(anchor.parentNode)) {
+ // Our container isn't visible, so can't show the animation:
+ return;
+ }
+
+ if (this._notificationTimeout) {
+ clearTimeout(this._notificationTimeout);
+ }
+
+ // The notification element is positioned to show in the same location as
+ // the downloads button. It's not in the downloads button itself in order to
+ // be able to anchor the notification elsewhere if required, and to ensure
+ // the notification isn't clipped by overflow properties of the anchor's
+ // container.
+ let notifier = this.notifier;
+ if (notifier.style.transform == '') {
+ let anchorRect = anchor.getBoundingClientRect();
+ let notifierRect = notifier.getBoundingClientRect();
+ let topDiff = anchorRect.top - notifierRect.top;
+ let leftDiff = anchorRect.left - notifierRect.left;
+ let heightDiff = anchorRect.height - notifierRect.height;
+ let widthDiff = anchorRect.width - notifierRect.width;
+ let translateX = (leftDiff + .5 * widthDiff) + "px";
+ let translateY = (topDiff + .5 * heightDiff) + "px";
+ notifier.style.transform = "translate(" + translateX + ", " + translateY + ")";
+ }
+ notifier.setAttribute("notification", aType);
+ this._notificationTimeout = setTimeout(() => {
+ notifier.removeAttribute("notification");
+ notifier.style.transform = '';
+ }, 1000);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsIndicatorData
+
+ /**
+ * Indicates whether the indicator should be shown because there are some
+ * downloads to be displayed.
+ */
+ set hasDownloads(aValue) {
+ if (this._hasDownloads != aValue || (!this._operational && aValue)) {
+ this._hasDownloads = aValue;
+
+ // If there is at least one download, ensure that the view elements are
+ if (aValue) {
+ this._ensureOperational();
+ }
+ }
+ return aValue;
+ },
+ get hasDownloads() {
+ return this._hasDownloads;
+ },
+ _hasDownloads: false,
+
+ /**
+ * Status text displayed in the indicator. If this is set to an empty value,
+ * then the small downloads icon is displayed instead of the text.
+ */
+ set counter(aValue) {
+ if (!this._operational) {
+ return this._counter;
+ }
+
+ if (this._counter !== aValue) {
+ this._counter = aValue;
+ if (this._counter)
+ this.indicator.setAttribute("counter", "true");
+ else
+ this.indicator.removeAttribute("counter");
+ // We have to set the attribute instead of using the property because the
+ // XBL binding isn't applied if the element is invisible for any reason.
+ this._indicatorCounter.setAttribute("value", aValue);
+ }
+ return aValue;
+ },
+ _counter: null,
+
+ /**
+ * Progress indication to display, from 0 to 100, or -1 if unknown. The
+ * progress bar is hidden if the current progress is unknown and no status
+ * text is set in the "counter" property.
+ */
+ set percentComplete(aValue) {
+ if (!this._operational) {
+ return this._percentComplete;
+ }
+
+ if (this._percentComplete !== aValue) {
+ this._percentComplete = aValue;
+ if (this._percentComplete >= 0)
+ this.indicator.setAttribute("progress", "true");
+ else
+ this.indicator.removeAttribute("progress");
+ // We have to set the attribute instead of using the property because the
+ // XBL binding isn't applied if the element is invisible for any reason.
+ this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
+ }
+ return aValue;
+ },
+ _percentComplete: null,
+
+ /**
+ * Indicates whether the progress won't advance because of a paused state.
+ * Setting this property forces a paused progress bar to be displayed, even if
+ * the current progress information is unavailable.
+ */
+ set paused(aValue) {
+ if (!this._operational) {
+ return this._paused;
+ }
+
+ if (this._paused != aValue) {
+ this._paused = aValue;
+ if (this._paused) {
+ this.indicator.setAttribute("paused", "true")
+ } else {
+ this.indicator.removeAttribute("paused");
+ }
+ }
+ return aValue;
+ },
+ _paused: false,
+
+ /**
+ * Set when the indicator should draw user attention to itself.
+ */
+ set attention(aValue) {
+ if (!this._operational) {
+ return this._attention;
+ }
+
+ if (this._attention != aValue) {
+ this._attention = aValue;
+
+ // Check if the downloads button is in the menu panel, to determine which
+ // button needs to get a badge.
+ let widgetGroup = CustomizableUI.getWidget("downloads-button");
+ let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
+
+ if (aValue == DownloadsCommon.ATTENTION_NONE) {
+ this.indicator.removeAttribute("attention");
+ if (inMenu) {
+ gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+ }
+ } else {
+ this.indicator.setAttribute("attention", aValue);
+ if (inMenu) {
+ let badgeClass = "download-" + aValue;
+ gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, badgeClass);
+ }
+ }
+ }
+ return aValue;
+ },
+ _attention: DownloadsCommon.ATTENTION_NONE,
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// User interface event functions
+
+ onWindowUnload() {
+ // This function is registered as an event listener, we can't use "this".
+ DownloadsIndicatorView.ensureTerminated();
+ },
+
+ onCommand(aEvent) {
+ // If the downloads button is in the menu panel, open the Library
+ let widgetGroup = CustomizableUI.getWidget("downloads-button");
+ if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ DownloadsPanel.showDownloadsHistory();
+ } else {
+ DownloadsPanel.showPanel();
+ }
+
+ aEvent.stopPropagation();
+ },
+
+ onDragOver(aEvent) {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+
+ onDrop(aEvent) {
+ let dt = aEvent.dataTransfer;
+ // If dragged item is from our source, do not try to
+ // redownload already downloaded file.
+ if (dt.mozGetDataAt("application/x-moz-file", 0))
+ return;
+
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ if (!links.length)
+ return;
+ let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
+ let handled = false;
+ for (let link of links) {
+ if (link.url.startsWith("about:"))
+ continue;
+ saveURL(link.url, link.name, null, true, true, null, sourceDoc);
+ handled = true;
+ }
+ if (handled) {
+ aEvent.preventDefault();
+ }
+ },
+
+ _indicator: null,
+ __indicatorCounter: null,
+ __indicatorProgress: null,
+
+ /**
+ * Returns a reference to the main indicator element, or null if the element
+ * is not present in the browser window yet.
+ */
+ get indicator() {
+ if (this._indicator) {
+ return this._indicator;
+ }
+
+ let indicator = document.getElementById("downloads-button");
+ if (!indicator || indicator.getAttribute("indicator") != "true") {
+ return null;
+ }
+
+ return this._indicator = indicator;
+ },
+
+ get indicatorAnchor() {
+ let widget = CustomizableUI.getWidget("downloads-button")
+ .forWindow(window);
+ if (widget.overflowed) {
+ return widget.anchor;
+ }
+ return document.getElementById("downloads-indicator-anchor");
+ },
+
+ get _indicatorCounter() {
+ return this.__indicatorCounter ||
+ (this.__indicatorCounter = document.getElementById("downloads-indicator-counter"));
+ },
+
+ get _indicatorProgress() {
+ return this.__indicatorProgress ||
+ (this.__indicatorProgress = document.getElementById("downloads-indicator-progress"));
+ },
+
+ get notifier() {
+ return this._notifier ||
+ (this._notifier = document.getElementById("downloads-notification-anchor"));
+ },
+
+ _onCustomizedAway() {
+ this._indicator = null;
+ this.__indicatorCounter = null;
+ this.__indicatorProgress = null;
+ },
+
+ afterCustomize() {
+ // If the cached indicator is not the one currently in the document,
+ // invalidate our references
+ if (this._indicator != document.getElementById("downloads-button")) {
+ this._onCustomizedAway();
+ this._operational = false;
+ this.ensureTerminated();
+ this.ensureInitialized();
+ }
+ },
+};
+
+Object.defineProperty(this, "DownloadsIndicatorView", {
+ value: DownloadsIndicatorView,
+ enumerable: true,
+ writable: false
+});
diff --git a/browser/components/downloads/content/indicatorOverlay.xul b/browser/components/downloads/content/indicatorOverlay.xul
new file mode 100644
index 000000000..07987c88c
--- /dev/null
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- -->
+<!-- vim: set ts=2 et sw=2 tw=80: -->
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+ %browserDTD;
+]>
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="indicatorOverlay">
+
+ <!-- We dynamically add the stack with the progress meter and notification icon,
+ originally loaded lazily because of performance reasons, to the existing
+ downloads-button. -->
+ <toolbarbutton id="downloads-button" indicator="true">
+ <!-- The panel's anchor area is smaller than the outer button, but must
+ always be visible and must not move or resize when the indicator
+ state changes, otherwise the panel could change its position or lose
+ its arrow unexpectedly. -->
+ <stack id="downloads-indicator-anchor"
+ consumeanchor="downloads-button">
+ <vbox id="downloads-indicator-progress-area" pack="center">
+ <description id="downloads-indicator-counter"/>
+ <progressmeter id="downloads-indicator-progress" class="plain"
+ min="0" max="100"/>
+ </vbox>
+ <vbox id="downloads-indicator-icon"/>
+ </stack>
+ </toolbarbutton>
+</overlay>
diff --git a/browser/components/downloads/jar.mn b/browser/components/downloads/jar.mn
new file mode 100644
index 000000000..2663454fa
--- /dev/null
+++ b/browser/components/downloads/jar.mn
@@ -0,0 +1,16 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+* content/browser/downloads/download.xml (content/download.xml)
+ content/browser/downloads/downloads.css (content/downloads.css)
+ content/browser/downloads/downloads.js (content/downloads.js)
+* content/browser/downloads/downloadsOverlay.xul (content/downloadsOverlay.xul)
+ content/browser/downloads/indicator.js (content/indicator.js)
+ content/browser/downloads/indicatorOverlay.xul (content/indicatorOverlay.xul)
+* content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul)
+ content/browser/downloads/allDownloadsViewOverlay.js (content/allDownloadsViewOverlay.js)
+* content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul)
+ content/browser/downloads/contentAreaDownloadsView.js (content/contentAreaDownloadsView.js)
+ content/browser/downloads/contentAreaDownloadsView.css (content/contentAreaDownloadsView.css)
diff --git a/browser/components/downloads/moz.build b/browser/components/downloads/moz.build
new file mode 100644
index 000000000..2b06a6465
--- /dev/null
+++ b/browser/components/downloads/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('*'):
+ BUG_COMPONENT = ('Firefox', 'Downloads Panel')
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'DownloadsCommon.jsm',
+ 'DownloadsTaskbar.jsm',
+ 'DownloadsViewUI.jsm',
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Downloads Panel')
diff --git a/browser/components/downloads/test/browser/.eslintrc.js b/browser/components/downloads/test/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/downloads/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/downloads/test/browser/browser.ini b/browser/components/downloads/test/browser/browser.ini
new file mode 100644
index 000000000..76f026c78
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_basic_functionality.js]
+[browser_first_download_panel.js]
+skip-if = os == "linux" # Bug 949434
+[browser_overflow_anchor.js]
+skip-if = os == "linux" # Bug 952422
+[browser_confirm_unblock_download.js]
+[browser_iframe_gone_mid_download.js]
+[browser_indicatorDrop.js]
+[browser_libraryDrop.js]
+[browser_downloads_panel_block.js]
+[browser_downloads_panel_footer.js]
+[browser_downloads_panel_height.js]
diff --git a/browser/components/downloads/test/browser/browser_basic_functionality.js b/browser/components/downloads/test/browser/browser_basic_functionality.js
new file mode 100644
index 000000000..564a344a7
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_basic_functionality.js
@@ -0,0 +1,56 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+registerCleanupFunction(function*() {
+ yield task_resetState();
+});
+
+/**
+ * Make sure the downloads panel can display items in the right order and
+ * contains the expected data.
+ */
+add_task(function* test_basic_functionality() {
+ // Display one of each download state.
+ const DownloadData = [
+ { state: nsIDM.DOWNLOAD_NOTSTARTED },
+ { state: nsIDM.DOWNLOAD_PAUSED },
+ { state: nsIDM.DOWNLOAD_FINISHED },
+ { state: nsIDM.DOWNLOAD_FAILED },
+ { state: nsIDM.DOWNLOAD_CANCELED },
+ ];
+
+ // Wait for focus first
+ yield promiseFocus();
+
+ // Ensure that state is reset in case previous tests didn't finish.
+ yield task_resetState();
+
+ // For testing purposes, show all the download items at once.
+ var originalCountLimit = DownloadsView.kItemCountLimit;
+ DownloadsView.kItemCountLimit = DownloadData.length;
+ registerCleanupFunction(function () {
+ DownloadsView.kItemCountLimit = originalCountLimit;
+ });
+
+ // Populate the downloads database with the data required by this test.
+ yield task_addDownloads(DownloadData);
+
+ // Open the user interface and wait for data to be fully loaded.
+ yield task_openPanel();
+
+ // Test item data and count. This also tests the ordering of the display.
+ let richlistbox = document.getElementById("downloadsListBox");
+ /* disabled for failing intermittently (bug 767828)
+ is(richlistbox.children.length, DownloadData.length,
+ "There is the correct number of richlistitems");
+ */
+ let itemCount = richlistbox.children.length;
+ for (let i = 0; i < itemCount; i++) {
+ let element = richlistbox.children[itemCount - i - 1];
+ let download = DownloadsView.itemForElement(element).download;
+ is(DownloadsCommon.stateOfDownload(download), DownloadData[i].state,
+ "Download states match up");
+ }
+});
diff --git a/browser/components/downloads/test/browser/browser_confirm_unblock_download.js b/browser/components/downloads/test/browser/browser_confirm_unblock_download.js
new file mode 100644
index 000000000..8ba37ba64
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_confirm_unblock_download.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests the dialog which allows the user to unblock a downloaded file.
+
+registerCleanupFunction(() => {});
+
+function* assertDialogResult({ args, buttonToClick, expectedResult }) {
+ promiseAlertDialogOpen(buttonToClick);
+ is(yield DownloadsCommon.confirmUnblockDownload(args), expectedResult);
+}
+
+/**
+ * Tests the "unblock" dialog, for each of the possible verdicts.
+ */
+add_task(function* test_unblock_dialog_unblock() {
+ for (let verdict of [Downloads.Error.BLOCK_VERDICT_MALWARE,
+ Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
+ Downloads.Error.BLOCK_VERDICT_UNCOMMON]) {
+ let args = { verdict, window, dialogType: "unblock" };
+
+ // Test both buttons.
+ yield assertDialogResult({
+ args,
+ buttonToClick: "accept",
+ expectedResult: "unblock",
+ });
+ yield assertDialogResult({
+ args,
+ buttonToClick: "cancel",
+ expectedResult: "cancel",
+ });
+ }
+});
+
+/**
+ * Tests the "chooseUnblock" dialog for potentially unwanted downloads.
+ */
+add_task(function* test_chooseUnblock_dialog() {
+ let args = {
+ verdict: Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
+ window,
+ dialogType: "chooseUnblock",
+ };
+
+ // Test each of the three buttons.
+ yield assertDialogResult({
+ args,
+ buttonToClick: "accept",
+ expectedResult: "unblock",
+ });
+ yield assertDialogResult({
+ args,
+ buttonToClick: "cancel",
+ expectedResult: "cancel",
+ });
+ yield assertDialogResult({
+ args,
+ buttonToClick: "extra1",
+ expectedResult: "confirmBlock",
+ });
+});
+
+/**
+ * Tests the "chooseOpen" dialog for uncommon downloads.
+ */
+add_task(function* test_chooseOpen_dialog() {
+ let args = {
+ verdict: Downloads.Error.BLOCK_VERDICT_UNCOMMON,
+ window,
+ dialogType: "chooseOpen",
+ };
+
+ // Test each of the three buttons.
+ yield assertDialogResult({
+ args,
+ buttonToClick: "accept",
+ expectedResult: "open",
+ });
+ yield assertDialogResult({
+ args,
+ buttonToClick: "cancel",
+ expectedResult: "cancel",
+ });
+ yield assertDialogResult({
+ args,
+ buttonToClick: "extra1",
+ expectedResult: "confirmBlock",
+ });
+});
diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_block.js b/browser/components/downloads/test/browser/browser_downloads_panel_block.js
new file mode 100644
index 000000000..05056e842
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_block.js
@@ -0,0 +1,183 @@
+"use strict";
+
+add_task(function* mainTest() {
+ yield task_resetState();
+
+ let verdicts = [
+ Downloads.Error.BLOCK_VERDICT_UNCOMMON,
+ Downloads.Error.BLOCK_VERDICT_MALWARE,
+ Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
+ ];
+ yield task_addDownloads(verdicts.map(v => makeDownload(v)));
+
+ // Check that the richlistitem for each download is correct.
+ for (let i = 0; i < verdicts.length; i++) {
+ yield openPanel();
+
+ // The current item is always the first one in the listbox since each
+ // iteration of this loop removes the item at the end.
+ let item = DownloadsView.richListBox.firstChild;
+
+ // Open the panel and click the item to show the subview.
+ EventUtils.sendMouseEvent({ type: "click" }, item);
+ yield promiseSubviewShown(true);
+
+ // Items are listed in newest-to-oldest order, so e.g. the first item's
+ // verdict is the last element in the verdicts array.
+ Assert.ok(DownloadsBlockedSubview.subview.getAttribute("verdict"),
+ verdicts[verdicts.count - i - 1]);
+
+ // Click the sliver of the main view that's still showing on the left to go
+ // back to it.
+ EventUtils.synthesizeMouse(DownloadsPanel.panel, 10, 10, {}, window);
+ yield promiseSubviewShown(false);
+
+ // Show the subview again.
+ EventUtils.sendMouseEvent({ type: "click" }, item);
+ yield promiseSubviewShown(true);
+
+ // Click the Open button. The download should be unblocked and then opened,
+ // i.e., unblockAndOpenDownload() should be called on the item. The panel
+ // should also be closed as a result, so wait for that too.
+ let unblockOpenPromise = promiseUnblockAndOpenDownloadCalled(item);
+ let hidePromise = promisePanelHidden();
+ EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.openButton,
+ 10, 10, {}, window);
+ yield unblockOpenPromise;
+ yield hidePromise;
+
+ window.focus();
+ yield SimpleTest.promiseFocus(window);
+
+ // Reopen the panel and show the subview again.
+ yield openPanel();
+
+ EventUtils.sendMouseEvent({ type: "click" }, item);
+ yield promiseSubviewShown(true);
+
+ // Click the Remove button. The panel should close and the item should be
+ // removed from it.
+ EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.deleteButton,
+ 10, 10, {}, window);
+ yield promisePanelHidden();
+ yield openPanel();
+
+ Assert.ok(!item.parentNode);
+ DownloadsPanel.hidePanel();
+ yield promisePanelHidden();
+ }
+
+ yield task_resetState();
+});
+
+function* openPanel() {
+ // This function is insane but something intermittently causes the panel to be
+ // closed as soon as it's opening on Linux ASAN. Maybe it would also happen
+ // on other build machines if the test ran often enough. Not only is the
+ // panel closed, it's closed while it's opening, leaving DownloadsPanel._state
+ // such that when you try to open the panel again, it thinks it's already
+ // open, but it's not. The result is that the test times out.
+ //
+ // What this does is call DownloadsPanel.showPanel over and over again until
+ // the panel is really open. There are a few wrinkles:
+ //
+ // (1) When panel.state is "open", check four more times (for a total of five)
+ // before returning to make the panel stays open.
+ // (2) If the panel is not open, check the _state. It should be either
+ // kStateUninitialized or kStateHidden. If it's not, then the panel is in the
+ // process of opening -- or maybe it's stuck in that process -- so reset the
+ // _state to kStateHidden.
+ // (3) If the _state is not kStateUninitialized or kStateHidden, then it may
+ // actually be properly opening and not stuck at all. To avoid always closing
+ // the panel while it's properly opening, use an exponential backoff mechanism
+ // for retries.
+ //
+ // If all that fails, then the test will time out, but it would have timed out
+ // anyway.
+
+ yield promiseFocus();
+ yield new Promise(resolve => {
+ let verifyCount = 5;
+ let backoff = 0;
+ let iBackoff = 0;
+ let interval = setInterval(() => {
+ if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
+ if (verifyCount > 0) {
+ verifyCount--;
+ } else {
+ clearInterval(interval);
+ resolve();
+ }
+ } else {
+ if (iBackoff < backoff) {
+ // Keep backing off before trying again.
+ iBackoff++;
+ } else {
+ // Try (or retry) opening the panel.
+ verifyCount = 5;
+ backoff = Math.max(1, 2 * backoff);
+ iBackoff = 0;
+ if (DownloadsPanel._state != DownloadsPanel.kStateUninitialized) {
+ DownloadsPanel._state = DownloadsPanel.kStateHidden;
+ }
+ DownloadsPanel.showPanel();
+ }
+ }
+ }, 100);
+ });
+}
+
+function promisePanelHidden() {
+ return new Promise(resolve => {
+ if (!DownloadsPanel.panel || DownloadsPanel.panel.state == "closed") {
+ resolve();
+ return;
+ }
+ DownloadsPanel.panel.addEventListener("popuphidden", function onHidden() {
+ DownloadsPanel.panel.removeEventListener("popuphidden", onHidden);
+ setTimeout(resolve, 0);
+ });
+ });
+}
+
+function makeDownload(verdict) {
+ return {
+ state: nsIDM.DOWNLOAD_DIRTY,
+ hasBlockedData: true,
+ errorObj: {
+ result: Components.results.NS_ERROR_FAILURE,
+ message: "Download blocked.",
+ becauseBlocked: true,
+ becauseBlockedByReputationCheck: true,
+ reputationCheckVerdict: verdict,
+ },
+ };
+}
+
+function promiseSubviewShown(shown) {
+ // More terribleness, but I'm tired of fighting intermittent timeouts on try.
+ // Just poll for the subview and wait a second before resolving the promise.
+ return new Promise(resolve => {
+ let interval = setInterval(() => {
+ if (shown == DownloadsBlockedSubview.view.showingSubView &&
+ !DownloadsBlockedSubview.view._transitioning) {
+ clearInterval(interval);
+ setTimeout(resolve, 1000);
+ return;
+ }
+ }, 0);
+ });
+}
+
+function promiseUnblockAndOpenDownloadCalled(item) {
+ return new Promise(resolve => {
+ let realFn = item._shell.unblockAndOpenDownload;
+ item._shell.unblockAndOpenDownload = () => {
+ item._shell.unblockAndOpenDownload = realFn;
+ resolve();
+ // unblockAndOpenDownload returns a promise (that's resolved when the file
+ // is opened).
+ return Promise.resolve();
+ };
+ });
+}
diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_footer.js b/browser/components/downloads/test/browser/browser_downloads_panel_footer.js
new file mode 100644
index 000000000..4083dde98
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_footer.js
@@ -0,0 +1,95 @@
+"use strict";
+
+function *task_openDownloadsSubPanel() {
+ let downloadSubPanel = document.getElementById("downloadSubPanel");
+ let popupShownPromise = BrowserTestUtils.waitForEvent(downloadSubPanel, "popupshown");
+
+ let downloadsDropmarker = document.getElementById("downloadsFooterDropmarker");
+ EventUtils.synthesizeMouseAtCenter(downloadsDropmarker, {}, window);
+
+ yield popupShownPromise;
+}
+
+add_task(function* test_openDownloadsFolder() {
+ yield SpecialPowers.pushPrefEnv({"set": [["browser.download.showPanelDropmarker", true]]});
+ yield task_openPanel();
+
+ yield task_openDownloadsSubPanel();
+
+ yield new Promise(resolve => {
+ sinon.stub(DownloadsCommon, "showDirectory", file => {
+ resolve(Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
+ is(file.path, downloadsPath, "Check the download folder path.");
+ }));
+ });
+
+ let itemOpenDownloadsFolder =
+ document.getElementById("downloadsDropdownItemOpenDownloadsFolder");
+ EventUtils.synthesizeMouseAtCenter(itemOpenDownloadsFolder, {}, window);
+ });
+
+ yield task_resetState();
+});
+
+add_task(function* test_clearList() {
+ const kTestCases = [{
+ downloads: [
+ { state: nsIDM.DOWNLOAD_NOTSTARTED },
+ { state: nsIDM.DOWNLOAD_FINISHED },
+ { state: nsIDM.DOWNLOAD_FAILED },
+ { state: nsIDM.DOWNLOAD_CANCELED },
+ ],
+ expectClearListShown: true,
+ expectedItemNumber: 0,
+ },{
+ downloads: [
+ { state: nsIDM.DOWNLOAD_NOTSTARTED },
+ { state: nsIDM.DOWNLOAD_FINISHED },
+ { state: nsIDM.DOWNLOAD_FAILED },
+ { state: nsIDM.DOWNLOAD_PAUSED },
+ { state: nsIDM.DOWNLOAD_CANCELED },
+ ],
+ expectClearListShown: true,
+ expectedItemNumber: 1,
+ },{
+ downloads: [
+ { state: nsIDM.DOWNLOAD_PAUSED },
+ ],
+ expectClearListShown: false,
+ expectedItemNumber: 1,
+ }];
+
+ for (let testCase of kTestCases) {
+ yield verify_clearList(testCase);
+ }
+});
+
+function *verify_clearList(testCase) {
+ let downloads = testCase.downloads;
+ yield task_addDownloads(downloads);
+
+ yield task_openPanel();
+ is(DownloadsView._downloads.length, downloads.length,
+ "Expect the number of download items");
+
+ yield task_openDownloadsSubPanel();
+
+ let itemClearList = document.getElementById("downloadsDropdownItemClearList");
+ let itemNumberPromise = BrowserTestUtils.waitForCondition(() => {
+ return DownloadsView._downloads.length === testCase.expectedItemNumber;
+ });
+ if (testCase.expectClearListShown) {
+ isnot("true", itemClearList.getAttribute("hidden"),
+ "Should show Clear Preview Panel button");
+ EventUtils.synthesizeMouseAtCenter(itemClearList, {}, window);
+ } else {
+ is("true", itemClearList.getAttribute("hidden"),
+ "Should not show Clear Preview Panel button");
+ }
+
+ yield itemNumberPromise;
+ is(DownloadsView._downloads.length, testCase.expectedItemNumber,
+ "Download items remained.");
+
+ yield task_resetState();
+}
diff --git a/browser/components/downloads/test/browser/browser_downloads_panel_height.js b/browser/components/downloads/test/browser/browser_downloads_panel_height.js
new file mode 100644
index 000000000..1638e4f0e
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_height.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test exists because we use a <panelmultiview> element and it handles
+ * some of the height changes for us. We need to verify that the height is
+ * updated correctly if downloads are removed while the panel is hidden.
+ */
+add_task(function* test_height_reduced_after_removal() {
+ yield task_addDownloads([
+ { state: nsIDM.DOWNLOAD_FINISHED },
+ ]);
+
+ yield task_openPanel();
+ let panel = document.getElementById("downloadsPanel");
+ let heightBeforeRemoval = panel.getBoundingClientRect().height;
+
+ // We want to close the panel before we remove the download from the list.
+ DownloadsPanel.hidePanel();
+ yield task_resetState();
+
+ yield task_openPanel();
+ let heightAfterRemoval = panel.getBoundingClientRect().height;
+ Assert.greater(heightBeforeRemoval, heightAfterRemoval);
+
+ yield task_resetState();
+});
diff --git a/browser/components/downloads/test/browser/browser_first_download_panel.js b/browser/components/downloads/test/browser/browser_first_download_panel.js
new file mode 100644
index 000000000..2cd871360
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_first_download_panel.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the downloads panel only opens automatically on the first
+ * download it notices. All subsequent downloads, even across sessions, should
+ * not open the panel automatically.
+ */
+add_task(function* test_first_download_panel() {
+ // Clear the download panel has shown preference first as this test is used to
+ // verify this preference's behaviour.
+ let oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown");
+ Services.prefs.setBoolPref("browser.download.panel.shown", false);
+
+ registerCleanupFunction(function*() {
+ // Clean up when the test finishes.
+ yield task_resetState();
+
+ // Set the preference instead of clearing it afterwards to ensure the
+ // right value is used no matter what the default was. This ensures the
+ // panel doesn't appear and affect other tests.
+ Services.prefs.setBoolPref("browser.download.panel.shown", oldPrefValue);
+ });
+
+ // Ensure that state is reset in case previous tests didn't finish.
+ yield task_resetState();
+
+ // With this set to false, we should automatically open the panel the first
+ // time a download is started.
+ DownloadsCommon.getData(window).panelHasShownBefore = false;
+
+ let promise = promisePanelOpened();
+ DownloadsCommon.getData(window)._notifyDownloadEvent("start");
+ yield promise;
+
+ // If we got here, that means the panel opened.
+ DownloadsPanel.hidePanel();
+
+ ok(DownloadsCommon.getData(window).panelHasShownBefore,
+ "Should have recorded that the panel was opened on a download.")
+
+ // Next, make sure that if we start another download, we don't open the
+ // panel automatically.
+ let originalOnPopupShown = DownloadsPanel.onPopupShown;
+ DownloadsPanel.onPopupShown = function () {
+ originalOnPopupShown.apply(this, arguments);
+ ok(false, "Should not have opened the downloads panel.");
+ };
+
+ DownloadsCommon.getData(window)._notifyDownloadEvent("start");
+
+ // Wait 2 seconds to ensure that the panel does not open.
+ yield new Promise(resolve => setTimeout(resolve, 2000));
+ DownloadsPanel.onPopupShown = originalOnPopupShown;
+});
diff --git a/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js b/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js
new file mode 100644
index 000000000..ebdd4f9af
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js
@@ -0,0 +1,62 @@
+const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
+
+function test_deleted_iframe(perSitePref, windowOptions={}) {
+ return function*() {
+ Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, perSitePref);
+ let {DownloadLastDir} = Cu.import("resource://gre/modules/DownloadLastDir.jsm", {});
+
+ let win = yield promiseOpenAndLoadWindow(windowOptions);
+ let tab = win.gBrowser.addTab();
+ yield promiseTabLoadEvent(tab, "about:mozilla");
+
+ let doc = tab.linkedBrowser.contentDocument;
+ let iframe = doc.createElement("iframe");
+ doc.body.appendChild(iframe);
+
+ ok(iframe.contentWindow, "iframe should have a window");
+ let gDownloadLastDir = new DownloadLastDir(iframe.contentWindow);
+ let cw = iframe.contentWindow;
+ let promiseIframeWindowGone = new Promise((resolve, reject) => {
+ Services.obs.addObserver(function obs(subject, topic) {
+ if (subject == cw) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }
+ }, "dom-window-destroyed", false);
+ });
+ iframe.remove();
+ yield promiseIframeWindowGone;
+ cw = null;
+ ok(!iframe.contentWindow, "Managed to destroy iframe");
+
+ let someDir = "blah";
+ try {
+ someDir = yield new Promise((resolve, reject) => {
+ gDownloadLastDir.getFileAsync("http://www.mozilla.org/", function(dir) {
+ resolve(dir);
+ });
+ });
+ } catch (ex) {
+ ok(false, "Got an exception trying to get the directory where things should be saved.");
+ Cu.reportError(ex);
+ }
+ // NB: someDir can legitimately be null here when set, hence the 'blah' workaround:
+ isnot(someDir, "blah", "Should get a file even after the window was destroyed.");
+
+ try {
+ gDownloadLastDir.setFile("http://www.mozilla.org/", null);
+ } catch (ex) {
+ ok(false, "Got an exception trying to set the directory where things should be saved.");
+ Cu.reportError(ex);
+ }
+
+ yield promiseWindowClosed(win);
+ Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
+ };
+}
+
+add_task(test_deleted_iframe(false));
+add_task(test_deleted_iframe(false));
+add_task(test_deleted_iframe(true, {private: true}));
+add_task(test_deleted_iframe(true, {private: true}));
+
diff --git a/browser/components/downloads/test/browser/browser_indicatorDrop.js b/browser/components/downloads/test/browser/browser_indicatorDrop.js
new file mode 100644
index 000000000..368d85ccf
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_indicatorDrop.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+ "resource://testing-common/httpd.js");
+
+registerCleanupFunction(function*() {
+ yield task_resetState();
+ yield task_clearHistory();
+});
+
+add_task(function* test_indicatorDrop() {
+ let downloadButton = document.getElementById("downloads-button");
+ ok(downloadButton, "download button present");
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ function* task_drop(urls) {
+ let dragData = [[{type: "text/plain", data: urls.join("\n")}]];
+
+ let list = yield Downloads.getList(Downloads.ALL);
+
+ let added = new Set();
+ let succeeded = new Set();
+ yield new Promise(function(resolve) {
+ let view = {
+ onDownloadAdded: function(download) {
+ added.add(download.source.url);
+ },
+ onDownloadChanged: function(download) {
+ if (!added.has(download.source.url))
+ return;
+ if (!download.succeeded)
+ return;
+ succeeded.add(download.source.url);
+ if (succeeded.size == urls.length) {
+ list.removeView(view).then(resolve);
+ }
+ }
+ };
+ list.addView(view).then(function() {
+ EventUtils.synthesizeDrop(downloadButton, downloadButton, dragData, "link", window);
+ });
+ });
+
+ for (let url of urls) {
+ ok(added.has(url), url + " is added to download");
+ }
+ }
+
+ // Ensure that state is reset in case previous tests didn't finish.
+ yield task_resetState();
+
+ yield setDownloadDir();
+
+ startServer();
+
+ yield* task_drop([httpUrl("file1.txt")]);
+ yield* task_drop([httpUrl("file1.txt"),
+ httpUrl("file2.txt"),
+ httpUrl("file3.txt")]);
+});
diff --git a/browser/components/downloads/test/browser/browser_libraryDrop.js b/browser/components/downloads/test/browser/browser_libraryDrop.js
new file mode 100644
index 000000000..fa7df8a87
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_libraryDrop.js
@@ -0,0 +1,72 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+ "resource://testing-common/httpd.js");
+
+registerCleanupFunction(function*() {
+ yield task_resetState();
+ yield task_clearHistory();
+});
+
+add_task(function* test_indicatorDrop() {
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let EventUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+ function task_drop(win, urls) {
+ let dragData = [[{type: "text/plain", data: urls.join("\n")}]];
+
+ let listBox = win.document.getElementById("downloadsRichListBox");
+ ok(listBox, "download list box present");
+
+ let list = yield Downloads.getList(Downloads.ALL);
+
+ let added = new Set();
+ let succeeded = new Set();
+ yield new Promise(function(resolve) {
+ let view = {
+ onDownloadAdded: function(download) {
+ added.add(download.source.url);
+ },
+ onDownloadChanged: function(download) {
+ if (!added.has(download.source.url))
+ return;
+ if (!download.succeeded)
+ return;
+ succeeded.add(download.source.url);
+ if (succeeded.size == urls.length) {
+ list.removeView(view).then(resolve);
+ }
+ }
+ };
+ list.addView(view).then(function() {
+ EventUtils.synthesizeDrop(listBox, listBox, dragData, "link", win);
+ });
+ });
+
+ for (let url of urls) {
+ ok(added.has(url), url + " is added to download");
+ }
+ }
+
+ // Ensure that state is reset in case previous tests didn't finish.
+ yield task_resetState();
+
+ setDownloadDir();
+
+ startServer();
+
+ let win = yield openLibrary("Downloads");
+ registerCleanupFunction(function() {
+ win.close();
+ });
+
+ yield* task_drop(win, [httpUrl("file1.txt")]);
+ yield* task_drop(win, [httpUrl("file1.txt"),
+ httpUrl("file2.txt"),
+ httpUrl("file3.txt")]);
+});
diff --git a/browser/components/downloads/test/browser/browser_overflow_anchor.js b/browser/components/downloads/test/browser/browser_overflow_anchor.js
new file mode 100644
index 000000000..a293a81cf
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_overflow_anchor.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+registerCleanupFunction(function*() {
+ // Clean up when the test finishes.
+ yield task_resetState();
+});
+
+/**
+ * Make sure the downloads button and indicator overflows into the nav-bar
+ * chevron properly, and then when those buttons are clicked in the overflow
+ * panel that the downloads panel anchors to the chevron.
+ */
+add_task(function* test_overflow_anchor() {
+ // Ensure that state is reset in case previous tests didn't finish.
+ yield task_resetState();
+
+ // Record the original width of the window so we can put it back when
+ // this test finishes.
+ let oldWidth = window.outerWidth;
+
+ // The downloads button should not be overflowed to begin with.
+ let button = CustomizableUI.getWidget("downloads-button")
+ .forWindow(window);
+ ok(!button.overflowed, "Downloads button should not be overflowed.");
+
+ // Hack - we lock the size of the default flex-y items in the nav-bar,
+ // namely, the URL and search inputs. That way we can resize the
+ // window without worrying about them flexing.
+ const kFlexyItems = ["urlbar-container", "search-container"];
+ registerCleanupFunction(() => unlockWidth(kFlexyItems));
+ lockWidth(kFlexyItems);
+
+ // Resize the window to half of its original size. That should
+ // be enough to overflow the downloads button.
+ window.resizeTo(oldWidth / 2, window.outerHeight);
+ yield waitForOverflowed(button, true);
+
+ let promise = promisePanelOpened();
+ button.node.doCommand();
+ yield promise;
+
+ let panel = DownloadsPanel.panel;
+ let chevron = document.getElementById("nav-bar-overflow-button");
+ is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
+
+ DownloadsPanel.hidePanel();
+
+ // Unlock the widths on the flex-y items.
+ unlockWidth(kFlexyItems);
+
+ // Put the window back to its original dimensions.
+ window.resizeTo(oldWidth, window.outerHeight);
+
+ // The downloads button should eventually be un-overflowed.
+ yield waitForOverflowed(button, false);
+
+ // Now try opening the panel again.
+ promise = promisePanelOpened();
+ button.node.doCommand();
+ yield promise;
+
+ is(panel.anchorNode.id, "downloads-indicator-anchor");
+
+ DownloadsPanel.hidePanel();
+});
+
+/**
+ * For some node IDs, finds the nodes and sets their min-width's to their
+ * current width, preventing them from flex-shrinking.
+ *
+ * @param aItemIDs an array of item IDs to set min-width on.
+ */
+function lockWidth(aItemIDs) {
+ for (let itemID of aItemIDs) {
+ let item = document.getElementById(itemID);
+ let curWidth = item.getBoundingClientRect().width + "px";
+ item.style.minWidth = curWidth;
+ }
+}
+
+/**
+ * Clears the min-width's set on a set of IDs by lockWidth.
+ *
+ * @param aItemIDs an array of ItemIDs to remove min-width on.
+ */
+function unlockWidth(aItemIDs) {
+ for (let itemID of aItemIDs) {
+ let item = document.getElementById(itemID);
+ item.style.minWidth = "";
+ }
+}
+
+/**
+ * Waits for a node to enter or exit the overflowed state.
+ *
+ * @param aItem the node to wait for.
+ * @param aIsOverflowed if we're waiting for the item to be overflowed.
+ */
+function waitForOverflowed(aItem, aIsOverflowed) {
+ let deferOverflow = Promise.defer();
+ if (aItem.overflowed == aIsOverflowed) {
+ return deferOverflow.resolve();
+ }
+
+ let observer = new MutationObserver(function(aMutations) {
+ if (aItem.overflowed == aIsOverflowed) {
+ observer.disconnect();
+ deferOverflow.resolve();
+ }
+ });
+ observer.observe(aItem.node, {attributes: true});
+
+ return deferOverflow.promise;
+}
diff --git a/browser/components/downloads/test/browser/head.js b/browser/components/downloads/test/browser/head.js
new file mode 100644
index 000000000..bcf703eb6
--- /dev/null
+++ b/browser/components/downloads/test/browser/head.js
@@ -0,0 +1,300 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provides infrastructure for automated download components tests.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+const nsIDM = Ci.nsIDownloadManager;
+
+var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
+gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+// Load mocking/stubbing library, sinon
+// docs: http://sinonjs.org/docs/
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
+
+registerCleanupFunction(function () {
+ gTestTargetFile.remove(false);
+
+ delete window.sinon;
+ delete window.setImmediate;
+ delete window.clearImmediate;
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// Asynchronous support subroutines
+
+function promiseOpenAndLoadWindow(aOptions)
+{
+ return new Promise((resolve, reject) => {
+ let win = OpenBrowserWindow(aOptions);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ resolve(win);
+ });
+ });
+}
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @param [optional] event
+ * The load event type to wait for. Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url, eventType="load")
+{
+ let deferred = Promise.defer();
+ info("Wait tab event: " + eventType);
+
+ function handle(event) {
+ if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank" ||
+ (url && event.target.location.href != url)) {
+ info("Skipping spurious '" + eventType + "'' event" +
+ " for " + event.target.location.href);
+ return;
+ }
+ // Remove reference to tab from the cleanup function:
+ realCleanup = () => {};
+ tab.linkedBrowser.removeEventListener(eventType, handle, true);
+ info("Tab event received: " + eventType);
+ deferred.resolve(event);
+ }
+
+ // Juggle a bit to avoid leaks:
+ let realCleanup = () => tab.linkedBrowser.removeEventListener(eventType, handle, true);
+ registerCleanupFunction(() => realCleanup());
+
+ tab.linkedBrowser.addEventListener(eventType, handle, true, true);
+ if (url)
+ tab.linkedBrowser.loadURI(url);
+ return deferred.promise;
+}
+
+function promiseWindowClosed(win)
+{
+ let promise = new Promise((resolve, reject) => {
+ Services.obs.addObserver(function obs(subject, topic) {
+ if (subject == win) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }
+ }, "domwindowclosed", false);
+ });
+ win.close();
+ return promise;
+}
+
+
+function promiseFocus()
+{
+ let deferred = Promise.defer();
+ waitForFocus(deferred.resolve);
+ return deferred.promise;
+}
+
+function promisePanelOpened()
+{
+ let deferred = Promise.defer();
+
+ if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
+ return deferred.resolve();
+ }
+
+ // Hook to wait until the panel is shown.
+ let originalOnPopupShown = DownloadsPanel.onPopupShown;
+ DownloadsPanel.onPopupShown = function () {
+ DownloadsPanel.onPopupShown = originalOnPopupShown;
+ originalOnPopupShown.apply(this, arguments);
+
+ // Defer to the next tick of the event loop so that we don't continue
+ // processing during the DOM event handler itself.
+ setTimeout(deferred.resolve, 0);
+ };
+
+ return deferred.promise;
+}
+
+function* task_resetState()
+{
+ // Remove all downloads.
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ let downloads = yield publicList.getAll();
+ for (let download of downloads) {
+ publicList.remove(download);
+ yield download.finalize(true);
+ }
+
+ DownloadsPanel.hidePanel();
+
+ yield promiseFocus();
+}
+
+function* task_addDownloads(aItems)
+{
+ let startTimeMs = Date.now();
+
+ let publicList = yield Downloads.getList(Downloads.PUBLIC);
+ for (let item of aItems) {
+ let download = {
+ source: {
+ url: "http://www.example.com/test-download.txt",
+ },
+ target: {
+ path: gTestTargetFile.path,
+ },
+ succeeded: item.state == nsIDM.DOWNLOAD_FINISHED,
+ canceled: item.state == nsIDM.DOWNLOAD_CANCELED ||
+ item.state == nsIDM.DOWNLOAD_PAUSED,
+ error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null,
+ hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED,
+ hasBlockedData: item.hasBlockedData || false,
+ startTime: new Date(startTimeMs++),
+ };
+ // `"errorObj" in download` must be false when there's no error.
+ if (item.errorObj) {
+ download.errorObj = item.errorObj;
+ }
+ yield publicList.add(yield Downloads.createDownload(download));
+ }
+}
+
+function* task_openPanel()
+{
+ yield promiseFocus();
+
+ let promise = promisePanelOpened();
+ DownloadsPanel.showPanel();
+ yield promise;
+}
+
+function* setDownloadDir() {
+ let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ tmpDir.append("testsavedir");
+ if (!tmpDir.exists()) {
+ tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ registerCleanupFunction(function () {
+ try {
+ tmpDir.remove(true);
+ } catch (e) {
+ // On Windows debug build this may fail.
+ }
+ });
+ }
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["browser.download.folderList", 2],
+ ["browser.download.dir", tmpDir, Ci.nsIFile],
+ ]}, resolve);
+ });
+}
+
+
+let gHttpServer = null;
+function startServer() {
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ registerCleanupFunction(function*() {
+ yield new Promise(function(resolve) {
+ gHttpServer.stop(resolve);
+ });
+ });
+
+ gHttpServer.registerPathHandler("/file1.txt", (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write("file1");
+ response.processAsync();
+ response.finish();
+ });
+ gHttpServer.registerPathHandler("/file2.txt", (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write("file2");
+ response.processAsync();
+ response.finish();
+ });
+ gHttpServer.registerPathHandler("/file3.txt", (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write("file3");
+ response.processAsync();
+ response.finish();
+ });
+}
+
+function httpUrl(aFileName) {
+ return "http://localhost:" + gHttpServer.identity.primaryPort + "/" +
+ aFileName;
+}
+
+function task_clearHistory() {
+ return new Promise(function(resolve) {
+ Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+ resolve();
+ }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
+ PlacesUtils.history.clear();
+ });
+}
+
+function openLibrary(aLeftPaneRoot) {
+ let library = window.openDialog("chrome://browser/content/places/places.xul",
+ "", "chrome,toolbar=yes,dialog=no,resizable",
+ aLeftPaneRoot);
+
+ return new Promise(resolve => {
+ waitForFocus(resolve, library);
+ });
+}
+
+function promiseAlertDialogOpen(buttonAction) {
+ return new Promise(resolve => {
+ Services.ww.registerNotification(function onOpen(subj, topic, data) {
+ if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+ // The test listens for the "load" event which guarantees that the alert
+ // class has already been added (it is added when "DOMContentLoaded" is
+ // fired).
+ subj.addEventListener("load", function onLoad() {
+ subj.removeEventListener("load", onLoad);
+ if (subj.document.documentURI ==
+ "chrome://global/content/commonDialog.xul") {
+ Services.ww.unregisterNotification(onOpen);
+
+ let dialog = subj.document.getElementById("commonDialog");
+ ok(dialog.classList.contains("alert-dialog"),
+ "The dialog element should contain an alert class.");
+
+ let doc = subj.document.documentElement;
+ doc.getButton(buttonAction).click();
+ resolve();
+ }
+ });
+ }
+ });
+ });
+}
diff --git a/browser/components/downloads/test/unit/.eslintrc.js b/browser/components/downloads/test/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/downloads/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/downloads/test/unit/head.js b/browser/components/downloads/test/unit/head.js
new file mode 100644
index 000000000..d7ce4d48a
--- /dev/null
+++ b/browser/components/downloads/test/unit/head.js
@@ -0,0 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provides infrastructure for automated download components tests.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource:///modules/DownloadsCommon.jsm");
diff --git a/browser/components/downloads/test/unit/test_DownloadsCommon.js b/browser/components/downloads/test/unit/test_DownloadsCommon.js
new file mode 100644
index 000000000..46afbaef9
--- /dev/null
+++ b/browser/components/downloads/test/unit/test_DownloadsCommon.js
@@ -0,0 +1,37 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests for the functions located directly in the "DownloadsCommon" object.
+ */
+
+function testFormatTimeLeft(aSeconds, aExpectedValue, aExpectedUnitString)
+{
+ let expected = "";
+ if (aExpectedValue) {
+ // Format the expected result based on the current language.
+ expected = DownloadsCommon.strings[aExpectedUnitString](aExpectedValue);
+ }
+ do_check_eq(DownloadsCommon.formatTimeLeft(aSeconds), expected);
+}
+
+function run_test()
+{
+ testFormatTimeLeft( 0, "", "");
+ testFormatTimeLeft( 1, "1", "shortTimeLeftSeconds");
+ testFormatTimeLeft( 29, "29", "shortTimeLeftSeconds");
+ testFormatTimeLeft( 30, "30", "shortTimeLeftSeconds");
+ testFormatTimeLeft( 31, "1", "shortTimeLeftMinutes");
+ testFormatTimeLeft( 60, "1", "shortTimeLeftMinutes");
+ testFormatTimeLeft( 89, "1", "shortTimeLeftMinutes");
+ testFormatTimeLeft( 90, "2", "shortTimeLeftMinutes");
+ testFormatTimeLeft( 91, "2", "shortTimeLeftMinutes");
+ testFormatTimeLeft( 3600, "1", "shortTimeLeftHours");
+ testFormatTimeLeft( 86400, "24", "shortTimeLeftHours");
+ testFormatTimeLeft( 169200, "47", "shortTimeLeftHours");
+ testFormatTimeLeft( 172800, "2", "shortTimeLeftDays");
+ testFormatTimeLeft(8553600, "99", "shortTimeLeftDays");
+ testFormatTimeLeft(8640000, "99", "shortTimeLeftDays");
+}
diff --git a/browser/components/downloads/test/unit/xpcshell.ini b/browser/components/downloads/test/unit/xpcshell.ini
new file mode 100644
index 000000000..f53a8cf89
--- /dev/null
+++ b/browser/components/downloads/test/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head = head.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_DownloadsCommon.js]
diff --git a/browser/components/extensions/.eslintrc.js b/browser/components/extensions/.eslintrc.js
new file mode 100644
index 000000000..81a11c4ac
--- /dev/null
+++ b/browser/components/extensions/.eslintrc.js
@@ -0,0 +1,22 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": "../../../toolkit/components/extensions/.eslintrc.js",
+
+ "globals": {
+ "AllWindowEvents": true,
+ "browserActionFor": true,
+ "currentWindow": true,
+ "EventEmitter": true,
+ "getCookieStoreIdForTab": true,
+ "IconDetails": true,
+ "makeWidgetId": true,
+ "pageActionFor": true,
+ "PanelPopup": true,
+ "TabContext": true,
+ "ViewPopup": true,
+ "WindowEventManager": true,
+ "WindowListManager": true,
+ "WindowManager": true,
+ },
+};
diff --git a/browser/components/extensions/ext-bookmarks.js b/browser/components/extensions/ext-bookmarks.js
new file mode 100644
index 000000000..399f6212d
--- /dev/null
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -0,0 +1,374 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+const {
+ SingletonEventManager,
+} = ExtensionUtils;
+
+XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
+ "resource://devtools/shared/event-emitter.js");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+let listenerCount = 0;
+
+function getTree(rootGuid, onlyChildren) {
+ function convert(node, parent) {
+ let treenode = {
+ id: node.guid,
+ title: node.title || "",
+ index: node.index,
+ dateAdded: node.dateAdded / 1000,
+ };
+
+ if (parent && node.guid != PlacesUtils.bookmarks.rootGuid) {
+ treenode.parentId = parent.guid;
+ }
+
+ if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) {
+ // This isn't quite correct. Recently Bookmarked ends up here ...
+ treenode.url = node.uri;
+ } else {
+ treenode.dateGroupModified = node.lastModified / 1000;
+
+ if (node.children && !onlyChildren) {
+ treenode.children = node.children.map(child => convert(child, node));
+ }
+ }
+
+ return treenode;
+ }
+
+ return PlacesUtils.promiseBookmarksTree(rootGuid, {
+ excludeItemsCallback: item => {
+ if (item.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
+ return true;
+ }
+ return item.annos &&
+ item.annos.find(a => a.name == PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
+ },
+ }).then(root => {
+ if (onlyChildren) {
+ let children = root.children || [];
+ return children.map(child => convert(child, root));
+ }
+ // It seems like the array always just contains the root node.
+ return [convert(root, null)];
+ }).catch(e => Promise.reject({message: e.message}));
+}
+
+function convert(result) {
+ let node = {
+ id: result.guid,
+ title: result.title || "",
+ index: result.index,
+ dateAdded: result.dateAdded.getTime(),
+ };
+
+ if (result.guid != PlacesUtils.bookmarks.rootGuid) {
+ node.parentId = result.parentGuid;
+ }
+
+ if (result.type == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+ node.url = result.url.href; // Output is always URL object.
+ } else {
+ node.dateGroupModified = result.lastModified.getTime();
+ }
+
+ return node;
+}
+
+let observer = {
+ skipTags: true,
+ skipDescendantsOnItemRemoval: true,
+
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+
+ onItemAdded(id, parentId, index, itemType, uri, title, dateAdded, guid, parentGuid, source) {
+ if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+ return;
+ }
+
+ let bookmark = {
+ id: guid,
+ parentId: parentGuid,
+ index,
+ title,
+ dateAdded: dateAdded / 1000,
+ };
+
+ if (itemType == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+ bookmark.url = uri.spec;
+ } else {
+ bookmark.dateGroupModified = bookmark.dateAdded;
+ }
+
+ this.emit("created", bookmark);
+ },
+
+ onItemVisited() {},
+
+ onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, itemType, guid, oldParentGuid, newParentGuid, source) {
+ if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+ return;
+ }
+
+ let info = {
+ parentId: newParentGuid,
+ index: newIndex,
+ oldParentId: oldParentGuid,
+ oldIndex,
+ };
+ this.emit("moved", {guid, info});
+ },
+
+ onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid, source) {
+ if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+ return;
+ }
+
+ let node = {
+ id: guid,
+ parentId: parentGuid,
+ index,
+ };
+
+ if (itemType == PlacesUtils.bookmarks.TYPE_BOOKMARK) {
+ node.url = uri.spec;
+ }
+
+ this.emit("removed", {guid, info: {parentId: parentGuid, index, node}});
+ },
+
+ onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal, source) {
+ if (itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+ return;
+ }
+
+ let info = {};
+ if (prop == "title") {
+ info.title = val;
+ } else if (prop == "uri") {
+ info.url = val;
+ } else {
+ // Not defined yet.
+ return;
+ }
+
+ this.emit("changed", {guid, info});
+ },
+};
+EventEmitter.decorate(observer);
+
+function decrementListeners() {
+ listenerCount -= 1;
+ if (!listenerCount) {
+ PlacesUtils.bookmarks.removeObserver(observer);
+ }
+}
+
+function incrementListeners() {
+ listenerCount++;
+ if (listenerCount == 1) {
+ PlacesUtils.bookmarks.addObserver(observer, false);
+ }
+}
+
+extensions.registerSchemaAPI("bookmarks", "addon_parent", context => {
+ return {
+ bookmarks: {
+ get: function(idOrIdList) {
+ let list = Array.isArray(idOrIdList) ? idOrIdList : [idOrIdList];
+
+ return Task.spawn(function* () {
+ let bookmarks = [];
+ for (let id of list) {
+ let bookmark = yield PlacesUtils.bookmarks.fetch({guid: id});
+ if (!bookmark) {
+ throw new Error("Bookmark not found");
+ }
+ bookmarks.push(convert(bookmark));
+ }
+ return bookmarks;
+ }).catch(error => Promise.reject({message: error.message}));
+ },
+
+ getChildren: function(id) {
+ // TODO: We should optimize this.
+ return getTree(id, true);
+ },
+
+ getTree: function() {
+ return getTree(PlacesUtils.bookmarks.rootGuid, false);
+ },
+
+ getSubTree: function(id) {
+ return getTree(id, false);
+ },
+
+ search: function(query) {
+ return PlacesUtils.bookmarks.search(query).then(result => result.map(convert));
+ },
+
+ getRecent: function(numberOfItems) {
+ return PlacesUtils.bookmarks.getRecent(numberOfItems).then(result => result.map(convert));
+ },
+
+ create: function(bookmark) {
+ let info = {
+ title: bookmark.title || "",
+ };
+
+ // If url is NULL or missing, it will be a folder.
+ if (bookmark.url !== null) {
+ info.type = PlacesUtils.bookmarks.TYPE_BOOKMARK;
+ info.url = bookmark.url || "";
+ } else {
+ info.type = PlacesUtils.bookmarks.TYPE_FOLDER;
+ }
+
+ if (bookmark.index !== null) {
+ info.index = bookmark.index;
+ }
+
+ if (bookmark.parentId !== null) {
+ info.parentGuid = bookmark.parentId;
+ } else {
+ info.parentGuid = PlacesUtils.bookmarks.unfiledGuid;
+ }
+
+ try {
+ return PlacesUtils.bookmarks.insert(info).then(convert)
+ .catch(error => Promise.reject({message: error.message}));
+ } catch (e) {
+ return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
+ }
+ },
+
+ move: function(id, destination) {
+ let info = {
+ guid: id,
+ };
+
+ if (destination.parentId !== null) {
+ info.parentGuid = destination.parentId;
+ }
+ info.index = (destination.index === null) ?
+ PlacesUtils.bookmarks.DEFAULT_INDEX : destination.index;
+
+ try {
+ return PlacesUtils.bookmarks.update(info).then(convert)
+ .catch(error => Promise.reject({message: error.message}));
+ } catch (e) {
+ return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
+ }
+ },
+
+ update: function(id, changes) {
+ let info = {
+ guid: id,
+ };
+
+ if (changes.title !== null) {
+ info.title = changes.title;
+ }
+ if (changes.url !== null) {
+ info.url = changes.url;
+ }
+
+ try {
+ return PlacesUtils.bookmarks.update(info).then(convert)
+ .catch(error => Promise.reject({message: error.message}));
+ } catch (e) {
+ return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
+ }
+ },
+
+ remove: function(id) {
+ let info = {
+ guid: id,
+ };
+
+ // The API doesn't give you the old bookmark at the moment
+ try {
+ return PlacesUtils.bookmarks.remove(info, {preventRemovalOfNonEmptyFolders: true}).then(result => {})
+ .catch(error => Promise.reject({message: error.message}));
+ } catch (e) {
+ return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
+ }
+ },
+
+ removeTree: function(id) {
+ let info = {
+ guid: id,
+ };
+
+ try {
+ return PlacesUtils.bookmarks.remove(info).then(result => {})
+ .catch(error => Promise.reject({message: error.message}));
+ } catch (e) {
+ return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
+ }
+ },
+
+ onCreated: new SingletonEventManager(context, "bookmarks.onCreated", fire => {
+ let listener = (event, bookmark) => {
+ context.runSafe(fire, bookmark.id, bookmark);
+ };
+
+ observer.on("created", listener);
+ incrementListeners();
+ return () => {
+ observer.off("created", listener);
+ decrementListeners();
+ };
+ }).api(),
+
+ onRemoved: new SingletonEventManager(context, "bookmarks.onRemoved", fire => {
+ let listener = (event, data) => {
+ context.runSafe(fire, data.guid, data.info);
+ };
+
+ observer.on("removed", listener);
+ incrementListeners();
+ return () => {
+ observer.off("removed", listener);
+ decrementListeners();
+ };
+ }).api(),
+
+ onChanged: new SingletonEventManager(context, "bookmarks.onChanged", fire => {
+ let listener = (event, data) => {
+ context.runSafe(fire, data.guid, data.info);
+ };
+
+ observer.on("changed", listener);
+ incrementListeners();
+ return () => {
+ observer.off("changed", listener);
+ decrementListeners();
+ };
+ }).api(),
+
+ onMoved: new SingletonEventManager(context, "bookmarks.onMoved", fire => {
+ let listener = (event, data) => {
+ context.runSafe(fire, data.guid, data.info);
+ };
+
+ observer.on("moved", listener);
+ incrementListeners();
+ return () => {
+ observer.off("moved", listener);
+ decrementListeners();
+ };
+ }).api(),
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-browserAction.js b/browser/components/extensions/ext-browserAction.js
new file mode 100644
index 000000000..97c6fd22c
--- /dev/null
+++ b/browser/components/extensions/ext-browserAction.js
@@ -0,0 +1,528 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
+ "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+ "resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
+ return require("devtools/shared/css/color").colorUtils;
+});
+
+Cu.import("resource://devtools/shared/event-emitter.js");
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+var {
+ EventManager,
+ IconDetails,
+} = ExtensionUtils;
+
+const POPUP_PRELOAD_TIMEOUT_MS = 200;
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function isAncestorOrSelf(target, node) {
+ for (; node; node = node.parentNode) {
+ if (node === target) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// WeakMap[Extension -> BrowserAction]
+var browserActionMap = new WeakMap();
+
+// Responsible for the browser_action section of the manifest as well
+// as the associated popup.
+function BrowserAction(options, extension) {
+ this.extension = extension;
+
+ let widgetId = makeWidgetId(extension.id);
+ this.id = `${widgetId}-browser-action`;
+ this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
+ this.widget = null;
+
+ this.pendingPopup = null;
+ this.pendingPopupTimeout = null;
+
+ this.tabManager = TabManager.for(extension);
+
+ this.defaults = {
+ enabled: true,
+ title: options.default_title || extension.name,
+ badgeText: "",
+ badgeBackgroundColor: null,
+ icon: IconDetails.normalize({path: options.default_icon}, extension),
+ popup: options.default_popup || "",
+ };
+
+ this.browserStyle = options.browser_style || false;
+ if (options.browser_style === null) {
+ this.extension.logger.warn("Please specify whether you want browser_style " +
+ "or not in your browser_action options.");
+ }
+
+ this.tabContext = new TabContext(tab => Object.create(this.defaults),
+ extension);
+
+ EventEmitter.decorate(this);
+}
+
+BrowserAction.prototype = {
+ build() {
+ let widget = CustomizableUI.createWidget({
+ id: this.id,
+ viewId: this.viewId,
+ type: "view",
+ removable: true,
+ label: this.defaults.title || this.extension.name,
+ tooltiptext: this.defaults.title || "",
+ defaultArea: CustomizableUI.AREA_NAVBAR,
+
+ onBeforeCreated: document => {
+ let view = document.createElementNS(XUL_NS, "panelview");
+ view.id = this.viewId;
+ view.setAttribute("flex", "1");
+
+ document.getElementById("PanelUI-multiView").appendChild(view);
+ },
+
+ onDestroyed: document => {
+ let view = document.getElementById(this.viewId);
+ if (view) {
+ this.clearPopup();
+ CustomizableUI.hidePanelForNode(view);
+ view.remove();
+ }
+ },
+
+ onCreated: node => {
+ node.classList.add("badged-button");
+ node.classList.add("webextension-browser-action");
+ node.setAttribute("constrain-size", "true");
+
+ node.onmousedown = event => this.handleEvent(event);
+
+ this.updateButton(node, this.defaults);
+ },
+
+ onViewShowing: event => {
+ let document = event.target.ownerDocument;
+ let tabbrowser = document.defaultView.gBrowser;
+
+ let tab = tabbrowser.selectedTab;
+ let popupURL = this.getProperty(tab, "popup");
+ this.tabManager.addActiveTabPermission(tab);
+
+ // Popups are shown only if a popup URL is defined; otherwise
+ // a "click" event is dispatched. This is done for compatibility with the
+ // Google Chrome onClicked extension API.
+ if (popupURL) {
+ try {
+ let popup = this.getPopup(document.defaultView, popupURL);
+ event.detail.addBlocker(popup.attach(event.target));
+ } catch (e) {
+ Cu.reportError(e);
+ event.preventDefault();
+ }
+ } else {
+ // This isn't not a hack, but it seems to provide the correct behavior
+ // with the fewest complications.
+ event.preventDefault();
+ this.emit("click");
+ }
+ },
+ });
+
+ this.tabContext.on("tab-select", // eslint-disable-line mozilla/balanced-listeners
+ (evt, tab) => { this.updateWindow(tab.ownerGlobal); });
+
+ this.widget = widget;
+ },
+
+ /**
+ * Triggers this browser action for the given window, with the same effects as
+ * if it were clicked by a user.
+ *
+ * This has no effect if the browser action is disabled for, or not
+ * present in, the given window.
+ */
+ triggerAction: Task.async(function* (window) {
+ let popup = ViewPopup.for(this.extension, window);
+ if (popup) {
+ popup.closePopup();
+ return;
+ }
+
+ let widget = this.widget.forWindow(window);
+ let tab = window.gBrowser.selectedTab;
+
+ if (!widget || !this.getProperty(tab, "enabled")) {
+ return;
+ }
+
+ // Popups are shown only if a popup URL is defined; otherwise
+ // a "click" event is dispatched. This is done for compatibility with the
+ // Google Chrome onClicked extension API.
+ if (this.getProperty(tab, "popup")) {
+ if (this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ yield window.PanelUI.show();
+ }
+
+ let event = new window.CustomEvent("command", {bubbles: true, cancelable: true});
+ widget.node.dispatchEvent(event);
+ } else {
+ this.emit("click");
+ }
+ }),
+
+ handleEvent(event) {
+ let button = event.target;
+ let window = button.ownerDocument.defaultView;
+
+ switch (event.type) {
+ case "mousedown":
+ if (event.button == 0) {
+ // Begin pre-loading the browser for the popup, so it's more likely to
+ // be ready by the time we get a complete click.
+ let tab = window.gBrowser.selectedTab;
+ let popupURL = this.getProperty(tab, "popup");
+ let enabled = this.getProperty(tab, "enabled");
+
+ if (popupURL && enabled) {
+ // Add permission for the active tab so it will exist for the popup.
+ // Store the tab to revoke the permission during clearPopup.
+ if (!this.pendingPopup && !this.tabManager.hasActiveTabPermission(tab)) {
+ this.tabManager.addActiveTabPermission(tab);
+ this.tabToRevokeDuringClearPopup = tab;
+ }
+
+ this.pendingPopup = this.getPopup(window, popupURL);
+ window.addEventListener("mouseup", this, true);
+ } else {
+ this.clearPopup();
+ }
+ }
+ break;
+
+ case "mouseup":
+ if (event.button == 0) {
+ this.clearPopupTimeout();
+ // If we have a pending pre-loaded popup, cancel it after we've waited
+ // long enough that we can be relatively certain it won't be opening.
+ if (this.pendingPopup) {
+ let {node} = this.widget.forWindow(window);
+ if (isAncestorOrSelf(node, event.originalTarget)) {
+ this.pendingPopupTimeout = setTimeout(() => this.clearPopup(),
+ POPUP_PRELOAD_TIMEOUT_MS);
+ } else {
+ this.clearPopup();
+ }
+ }
+ }
+ break;
+ }
+ },
+
+ /**
+ * Returns a potentially pre-loaded popup for the given URL in the given
+ * window. If a matching pre-load popup already exists, returns that.
+ * Otherwise, initializes a new one.
+ *
+ * If a pre-load popup exists which does not match, it is destroyed before a
+ * new one is created.
+ *
+ * @param {Window} window
+ * The browser window in which to create the popup.
+ * @param {string} popupURL
+ * The URL to load into the popup.
+ * @returns {ViewPopup}
+ */
+ getPopup(window, popupURL) {
+ this.clearPopupTimeout();
+ let {pendingPopup} = this;
+ this.pendingPopup = null;
+
+ if (pendingPopup) {
+ if (pendingPopup.window === window && pendingPopup.popupURL === popupURL) {
+ return pendingPopup;
+ }
+ pendingPopup.destroy();
+ }
+
+ let fixedWidth = this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL;
+ return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth);
+ },
+
+ /**
+ * Clears any pending pre-loaded popup and related timeouts.
+ */
+ clearPopup() {
+ this.clearPopupTimeout();
+ if (this.pendingPopup) {
+ if (this.tabToRevokeDuringClearPopup) {
+ this.tabManager.revokeActiveTabPermission(this.tabToRevokeDuringClearPopup);
+ this.tabToRevokeDuringClearPopup = null;
+ }
+ this.pendingPopup.destroy();
+ this.pendingPopup = null;
+ }
+ },
+
+ /**
+ * Clears any pending timeouts to clear stale, pre-loaded popups.
+ */
+ clearPopupTimeout() {
+ if (this.pendingPopup) {
+ this.pendingPopup.window.removeEventListener("mouseup", this, true);
+ }
+
+ if (this.pendingPopupTimeout) {
+ clearTimeout(this.pendingPopupTimeout);
+ this.pendingPopupTimeout = null;
+ }
+ },
+
+ // Update the toolbar button |node| with the tab context data
+ // in |tabData|.
+ updateButton(node, tabData) {
+ let title = tabData.title || this.extension.name;
+ node.setAttribute("tooltiptext", title);
+ node.setAttribute("label", title);
+
+ if (tabData.badgeText) {
+ node.setAttribute("badge", tabData.badgeText);
+ } else {
+ node.removeAttribute("badge");
+ }
+
+ if (tabData.enabled) {
+ node.removeAttribute("disabled");
+ } else {
+ node.setAttribute("disabled", "true");
+ }
+
+ let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
+ "class", "toolbarbutton-badge");
+ if (badgeNode) {
+ let color = tabData.badgeBackgroundColor;
+ if (color) {
+ color = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 255})`;
+ }
+ badgeNode.style.backgroundColor = color || "";
+ }
+
+ const LEGACY_CLASS = "toolbarbutton-legacy-addon";
+ node.classList.remove(LEGACY_CLASS);
+
+ let baseSize = 16;
+ let {icon, size} = IconDetails.getPreferredIcon(tabData.icon, this.extension, baseSize);
+
+ // If the best available icon size is not divisible by 16, check if we have
+ // an 18px icon to fall back to, and trim off the padding instead.
+ if (size % 16 && !icon.endsWith(".svg")) {
+ let result = IconDetails.getPreferredIcon(tabData.icon, this.extension, 18);
+
+ if (result.size % 18 == 0) {
+ baseSize = 18;
+ icon = result.icon;
+ node.classList.add(LEGACY_CLASS);
+ }
+ }
+
+ // These URLs should already be properly escaped, but make doubly sure CSS
+ // string escape characters are escaped here, since they could lead to a
+ // sandbox break.
+ let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
+
+ let getIcon = size => escape(IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+
+ node.setAttribute("style", `
+ --webextension-menupanel-image: url("${getIcon(32)}");
+ --webextension-menupanel-image-2x: url("${getIcon(64)}");
+ --webextension-toolbar-image: url("${escape(icon)}");
+ --webextension-toolbar-image-2x: url("${getIcon(baseSize * 2)}");
+ `);
+ },
+
+ // Update the toolbar button for a given window.
+ updateWindow(window) {
+ let widget = this.widget.forWindow(window);
+ if (widget) {
+ let tab = window.gBrowser.selectedTab;
+ this.updateButton(widget.node, this.tabContext.get(tab));
+ }
+ },
+
+ // Update the toolbar button when the extension changes the icon,
+ // title, badge, etc. If it only changes a parameter for a single
+ // tab, |tab| will be that tab. Otherwise it will be null.
+ updateOnChange(tab) {
+ if (tab) {
+ if (tab.selected) {
+ this.updateWindow(tab.ownerGlobal);
+ }
+ } else {
+ for (let window of WindowListManager.browserWindows()) {
+ this.updateWindow(window);
+ }
+ }
+ },
+
+ // tab is allowed to be null.
+ // prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor".
+ setProperty(tab, prop, value) {
+ if (tab == null) {
+ this.defaults[prop] = value;
+ } else if (value != null) {
+ this.tabContext.get(tab)[prop] = value;
+ } else {
+ delete this.tabContext.get(tab)[prop];
+ }
+
+ this.updateOnChange(tab);
+ },
+
+ // tab is allowed to be null.
+ // prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor".
+ getProperty(tab, prop) {
+ if (tab == null) {
+ return this.defaults[prop];
+ }
+ return this.tabContext.get(tab)[prop];
+ },
+
+ shutdown() {
+ this.tabContext.shutdown();
+ CustomizableUI.destroyWidget(this.id);
+ },
+};
+
+BrowserAction.for = (extension) => {
+ return browserActionMap.get(extension);
+};
+
+global.browserActionFor = BrowserAction.for;
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_browser_action", (type, directive, extension, manifest) => {
+ let browserAction = new BrowserAction(manifest.browser_action, extension);
+ browserAction.build();
+ browserActionMap.set(extension, browserAction);
+});
+
+extensions.on("shutdown", (type, extension) => {
+ if (browserActionMap.has(extension)) {
+ browserActionMap.get(extension).shutdown();
+ browserActionMap.delete(extension);
+ }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+extensions.registerSchemaAPI("browserAction", "addon_parent", context => {
+ let {extension} = context;
+ return {
+ browserAction: {
+ onClicked: new EventManager(context, "browserAction.onClicked", fire => {
+ let listener = () => {
+ let tab = TabManager.activeTab;
+ fire(TabManager.convert(extension, tab));
+ };
+ BrowserAction.for(extension).on("click", listener);
+ return () => {
+ BrowserAction.for(extension).off("click", listener);
+ };
+ }).api(),
+
+ enable: function(tabId) {
+ let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
+ BrowserAction.for(extension).setProperty(tab, "enabled", true);
+ },
+
+ disable: function(tabId) {
+ let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
+ BrowserAction.for(extension).setProperty(tab, "enabled", false);
+ },
+
+ setTitle: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ let title = details.title;
+ // Clear the tab-specific title when given a null string.
+ if (tab && title == "") {
+ title = null;
+ }
+ BrowserAction.for(extension).setProperty(tab, "title", title);
+ },
+
+ getTitle: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ let title = BrowserAction.for(extension).getProperty(tab, "title");
+ return Promise.resolve(title);
+ },
+
+ setIcon: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ let icon = IconDetails.normalize(details, extension, context);
+ BrowserAction.for(extension).setProperty(tab, "icon", icon);
+ },
+
+ setBadgeText: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ BrowserAction.for(extension).setProperty(tab, "badgeText", details.text);
+ },
+
+ getBadgeText: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ let text = BrowserAction.for(extension).getProperty(tab, "badgeText");
+ return Promise.resolve(text);
+ },
+
+ setPopup: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ // Note: Chrome resolves arguments to setIcon relative to the calling
+ // context, but resolves arguments to setPopup relative to the extension
+ // root.
+ // For internal consistency, we currently resolve both relative to the
+ // calling context.
+ let url = details.popup && context.uri.resolve(details.popup);
+ BrowserAction.for(extension).setProperty(tab, "popup", url);
+ },
+
+ getPopup: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ let popup = BrowserAction.for(extension).getProperty(tab, "popup");
+ return Promise.resolve(popup);
+ },
+
+ setBadgeBackgroundColor: function(details) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let color = details.color;
+ if (!Array.isArray(color)) {
+ let col = colorUtils.colorToRGBA(color);
+ color = col && [col.r, col.g, col.b, Math.round(col.a * 255)];
+ }
+ BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", color);
+ },
+
+ getBadgeBackgroundColor: function(details, callback) {
+ let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+
+ let color = BrowserAction.for(extension).getProperty(tab, "badgeBackgroundColor");
+ return Promise.resolve(color || [0xd9, 0, 0, 255]);
+ },
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-c-contextMenus.js b/browser/components/extensions/ext-c-contextMenus.js
new file mode 100644
index 000000000..9fde90808
--- /dev/null
+++ b/browser/components/extensions/ext-c-contextMenus.js
@@ -0,0 +1,158 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// If id is not specified for an item we use an integer.
+// This ID need only be unique within a single addon. Since all addon code that
+// can use this API runs in the same process, this local variable suffices.
+var gNextMenuItemID = 0;
+
+// Map[Extension -> Map[string or id, ContextMenusClickPropHandler]]
+var gPropHandlers = new Map();
+
+// The contextMenus API supports an "onclick" attribute in the create/update
+// methods to register a callback. This class manages these onclick properties.
+class ContextMenusClickPropHandler {
+ constructor(context) {
+ this.context = context;
+ // Map[string or integer -> callback]
+ this.onclickMap = new Map();
+ this.dispatchEvent = this.dispatchEvent.bind(this);
+ }
+
+ // A listener on contextMenus.onClicked that forwards the event to the only
+ // listener, if any.
+ dispatchEvent(info, tab) {
+ let onclick = this.onclickMap.get(info.menuItemId);
+ if (onclick) {
+ // No need for runSafe or anything because we are already being run inside
+ // an event handler -- the event is just being forwarded to the actual
+ // handler.
+ onclick(info, tab);
+ }
+ }
+
+ // Sets the `onclick` handler for the given menu item.
+ // The `onclick` function MUST be owned by `this.context`.
+ setListener(id, onclick) {
+ if (this.onclickMap.size === 0) {
+ this.context.childManager.getParentEvent("contextMenus.onClicked").addListener(this.dispatchEvent);
+ this.context.callOnClose(this);
+ }
+ this.onclickMap.set(id, onclick);
+
+ let propHandlerMap = gPropHandlers.get(this.context.extension);
+ if (!propHandlerMap) {
+ propHandlerMap = new Map();
+ } else {
+ // If the current callback was created in a different context, remove it
+ // from the other context.
+ let propHandler = propHandlerMap.get(id);
+ if (propHandler && propHandler !== this) {
+ propHandler.unsetListener(id);
+ }
+ }
+ propHandlerMap.set(id, this);
+ gPropHandlers.set(this.context.extension, propHandlerMap);
+ }
+
+ // Deletes the `onclick` handler for the given menu item.
+ // The `onclick` function MUST be owned by `this.context`.
+ unsetListener(id) {
+ if (!this.onclickMap.delete(id)) {
+ return;
+ }
+ if (this.onclickMap.size === 0) {
+ this.context.childManager.getParentEvent("contextMenus.onClicked").removeListener(this.dispatchEvent);
+ this.context.forgetOnClose(this);
+ }
+ let propHandlerMap = gPropHandlers.get(this.context.extension);
+ propHandlerMap.delete(id);
+ if (propHandlerMap.size === 0) {
+ gPropHandlers.delete(this.context.extension);
+ }
+ }
+
+ // Deletes the `onclick` handler for the given menu item, if any, regardless
+ // of the context where it was created.
+ unsetListenerFromAnyContext(id) {
+ let propHandlerMap = gPropHandlers.get(this.context.extension);
+ let propHandler = propHandlerMap && propHandlerMap.get(id);
+ if (propHandler) {
+ propHandler.unsetListener(id);
+ }
+ }
+
+ // Remove all `onclick` handlers of the extension.
+ deleteAllListenersFromExtension() {
+ let propHandlerMap = gPropHandlers.get(this.context.extension);
+ if (propHandlerMap) {
+ for (let [id, propHandler] of propHandlerMap) {
+ propHandler.unsetListener(id);
+ }
+ }
+ }
+
+ // Removes all `onclick` handlers from this context.
+ close() {
+ for (let id of this.onclickMap.keys()) {
+ this.unsetListener(id);
+ }
+ }
+}
+
+extensions.registerSchemaAPI("contextMenus", "addon_child", context => {
+ let onClickedProp = new ContextMenusClickPropHandler(context);
+
+ return {
+ contextMenus: {
+ create(createProperties, callback) {
+ if (createProperties.id === null) {
+ createProperties.id = ++gNextMenuItemID;
+ }
+ let {onclick} = createProperties;
+ delete createProperties.onclick;
+ context.childManager.callParentAsyncFunction("contextMenus.createInternal", [
+ createProperties,
+ ]).then(() => {
+ if (onclick) {
+ onClickedProp.setListener(createProperties.id, onclick);
+ }
+ if (callback) {
+ callback();
+ }
+ });
+ return createProperties.id;
+ },
+
+ update(id, updateProperties) {
+ let {onclick} = updateProperties;
+ delete updateProperties.onclick;
+ return context.childManager.callParentAsyncFunction("contextMenus.update", [
+ id,
+ updateProperties,
+ ]).then(() => {
+ if (onclick) {
+ onClickedProp.setListener(id, onclick);
+ } else if (onclick === null) {
+ onClickedProp.unsetListenerFromAnyContext(id);
+ }
+ // else onclick is not set so it should not be changed.
+ });
+ },
+
+ remove(id) {
+ onClickedProp.unsetListenerFromAnyContext(id);
+ return context.childManager.callParentAsyncFunction("contextMenus.remove", [
+ id,
+ ]);
+ },
+
+ removeAll() {
+ onClickedProp.deleteAllListenersFromExtension();
+
+ return context.childManager.callParentAsyncFunction("contextMenus.removeAll", []);
+ },
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-c-omnibox.js b/browser/components/extensions/ext-c-omnibox.js
new file mode 100644
index 000000000..3b9b6e2f7
--- /dev/null
+++ b/browser/components/extensions/ext-c-omnibox.js
@@ -0,0 +1,32 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
+var {
+ runSafeSyncWithoutClone,
+ SingletonEventManager,
+} = ExtensionUtils;
+
+extensions.registerSchemaAPI("omnibox", "addon_child", context => {
+ return {
+ omnibox: {
+ onInputChanged: new SingletonEventManager(context, "omnibox.onInputChanged", fire => {
+ let listener = (text, id) => {
+ runSafeSyncWithoutClone(fire, text, suggestions => {
+ // TODO: Switch to using callParentFunctionNoReturn once bug 1314903 is fixed.
+ context.childManager.callParentAsyncFunction("omnibox_internal.addSuggestions", [
+ id,
+ suggestions,
+ ]);
+ });
+ };
+ context.childManager.getParentEvent("omnibox_internal.onInputChanged").addListener(listener);
+ return () => {
+ context.childManager.getParentEvent("omnibox_internal.onInputChanged").removeListener(listener);
+ };
+ }).api(),
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-c-tabs.js b/browser/components/extensions/ext-c-tabs.js
new file mode 100644
index 000000000..d5ce9fbf9
--- /dev/null
+++ b/browser/components/extensions/ext-c-tabs.js
@@ -0,0 +1,35 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+extensions.registerSchemaAPI("tabs", "addon_child", context => {
+ return {
+ tabs: {
+ connect: function(tabId, connectInfo) {
+ let name = "";
+ if (connectInfo && connectInfo.name !== null) {
+ name = connectInfo.name;
+ }
+ let recipient = {
+ extensionId: context.extension.id,
+ tabId,
+ };
+ if (connectInfo && connectInfo.frameId !== null) {
+ recipient.frameId = connectInfo.frameId;
+ }
+ return context.messenger.connect(context.messageManager, name, recipient);
+ },
+
+ sendMessage: function(tabId, message, options, responseCallback) {
+ let recipient = {
+ extensionId: context.extension.id,
+ tabId: tabId,
+ };
+ if (options && options.frameId !== null) {
+ recipient.frameId = options.frameId;
+ }
+ return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
+ },
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-commands.js b/browser/components/extensions/ext-commands.js
new file mode 100644
index 000000000..416544e86
--- /dev/null
+++ b/browser/components/extensions/ext-commands.js
@@ -0,0 +1,259 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://devtools/shared/event-emitter.js");
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
+var {
+ EventManager,
+ PlatformInfo,
+} = ExtensionUtils;
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+// WeakMap[Extension -> CommandList]
+var commandsMap = new WeakMap();
+
+function CommandList(manifest, extension) {
+ this.extension = extension;
+ this.id = makeWidgetId(extension.id);
+ this.windowOpenListener = null;
+
+ // Map[{String} commandName -> {Object} commandProperties]
+ this.commands = this.loadCommandsFromManifest(manifest);
+
+ // WeakMap[Window -> <xul:keyset>]
+ this.keysetsMap = new WeakMap();
+
+ this.register();
+ EventEmitter.decorate(this);
+}
+
+CommandList.prototype = {
+ /**
+ * Registers the commands to all open windows and to any which
+ * are later created.
+ */
+ register() {
+ for (let window of WindowListManager.browserWindows()) {
+ this.registerKeysToDocument(window);
+ }
+
+ this.windowOpenListener = (window) => {
+ if (!this.keysetsMap.has(window)) {
+ this.registerKeysToDocument(window);
+ }
+ };
+
+ WindowListManager.addOpenListener(this.windowOpenListener);
+ },
+
+ /**
+ * Unregisters the commands from all open windows and stops commands
+ * from being registered to windows which are later created.
+ */
+ unregister() {
+ for (let window of WindowListManager.browserWindows()) {
+ if (this.keysetsMap.has(window)) {
+ this.keysetsMap.get(window).remove();
+ }
+ }
+
+ WindowListManager.removeOpenListener(this.windowOpenListener);
+ },
+
+ /**
+ * Creates a Map from commands for each command in the manifest.commands object.
+ *
+ * @param {Object} manifest The manifest JSON object.
+ * @returns {Map<string, object>}
+ */
+ loadCommandsFromManifest(manifest) {
+ let commands = new Map();
+ // For Windows, chrome.runtime expects 'win' while chrome.commands
+ // expects 'windows'. We can special case this for now.
+ let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
+ for (let name of Object.keys(manifest.commands)) {
+ let command = manifest.commands[name];
+ let shortcut = command.suggested_key[os] || command.suggested_key.default;
+ if (shortcut) {
+ commands.set(name, {
+ description: command.description,
+ shortcut: shortcut.replace(/\s+/g, ""),
+ });
+ }
+ }
+ return commands;
+ },
+
+ /**
+ * Registers the commands to a document.
+ * @param {ChromeWindow} window The XUL window to insert the Keyset.
+ */
+ registerKeysToDocument(window) {
+ let doc = window.document;
+ let keyset = doc.createElementNS(XUL_NS, "keyset");
+ keyset.id = `ext-keyset-id-${this.id}`;
+ this.commands.forEach((command, name) => {
+ let keyElement = this.buildKey(doc, name, command.shortcut);
+ keyset.appendChild(keyElement);
+ });
+ doc.documentElement.appendChild(keyset);
+ this.keysetsMap.set(window, keyset);
+ },
+
+ /**
+ * Builds a XUL Key element and attaches an onCommand listener which
+ * emits a command event with the provided name when fired.
+ *
+ * @param {Document} doc The XUL document.
+ * @param {string} name The name of the command.
+ * @param {string} shortcut The shortcut provided in the manifest.
+ * @see https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/key
+ *
+ * @returns {Document} The newly created Key element.
+ */
+ buildKey(doc, name, shortcut) {
+ let keyElement = this.buildKeyFromShortcut(doc, shortcut);
+
+ // We need to have the attribute "oncommand" for the "command" listener to fire,
+ // and it is currently ignored when set to the empty string.
+ keyElement.setAttribute("oncommand", "//");
+
+ /* eslint-disable mozilla/balanced-listeners */
+ // We remove all references to the key elements when the extension is shutdown,
+ // therefore the listeners for these elements will be garbage collected.
+ keyElement.addEventListener("command", (event) => {
+ if (name == "_execute_page_action") {
+ let win = event.target.ownerDocument.defaultView;
+ pageActionFor(this.extension).triggerAction(win);
+ } else if (name == "_execute_browser_action") {
+ let win = event.target.ownerDocument.defaultView;
+ browserActionFor(this.extension).triggerAction(win);
+ } else {
+ TabManager.for(this.extension)
+ .addActiveTabPermission(TabManager.activeTab);
+ this.emit("command", name);
+ }
+ });
+ /* eslint-enable mozilla/balanced-listeners */
+
+ return keyElement;
+ },
+
+ /**
+ * Builds a XUL Key element from the provided shortcut.
+ *
+ * @param {Document} doc The XUL document.
+ * @param {string} shortcut The shortcut provided in the manifest.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/key
+ * @returns {Document} The newly created Key element.
+ */
+ buildKeyFromShortcut(doc, shortcut) {
+ let keyElement = doc.createElementNS(XUL_NS, "key");
+
+ let parts = shortcut.split("+");
+
+ // The key is always the last element.
+ let chromeKey = parts.pop();
+
+ // The modifiers are the remaining elements.
+ keyElement.setAttribute("modifiers", this.getModifiersAttribute(parts));
+
+ if (/^[A-Z0-9]$/.test(chromeKey)) {
+ // We use the key attribute for all single digits and characters.
+ keyElement.setAttribute("key", chromeKey);
+ } else {
+ keyElement.setAttribute("keycode", this.getKeycodeAttribute(chromeKey));
+ }
+
+ return keyElement;
+ },
+
+ /**
+ * Determines the corresponding XUL keycode from the given chrome key.
+ *
+ * For example:
+ *
+ * input | output
+ * ---------------------------------------
+ * "PageUP" | "VK_PAGE_UP"
+ * "Delete" | "VK_DELETE"
+ *
+ * @param {string} chromeKey The chrome key (e.g. "PageUp", "Space", ...)
+ * @returns {string} The constructed value for the Key's 'keycode' attribute.
+ */
+ getKeycodeAttribute(chromeKey) {
+ return `VK${chromeKey.replace(/([A-Z])/g, "_$&").toUpperCase()}`;
+ },
+
+ /**
+ * Determines the corresponding XUL modifiers from the chrome modifiers.
+ *
+ * For example:
+ *
+ * input | output
+ * ---------------------------------------
+ * ["Ctrl", "Shift"] | "accel shift"
+ * ["MacCtrl"] | "control"
+ *
+ * @param {Array} chromeModifiers The array of chrome modifiers.
+ * @returns {string} The constructed value for the Key's 'modifiers' attribute.
+ */
+ getModifiersAttribute(chromeModifiers) {
+ let modifiersMap = {
+ "Alt": "alt",
+ "Command": "accel",
+ "Ctrl": "accel",
+ "MacCtrl": "control",
+ "Shift": "shift",
+ };
+ return Array.from(chromeModifiers, modifier => {
+ return modifiersMap[modifier];
+ }).join(" ");
+ },
+};
+
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_commands", (type, directive, extension, manifest) => {
+ commandsMap.set(extension, new CommandList(manifest, extension));
+});
+
+extensions.on("shutdown", (type, extension) => {
+ let commandsList = commandsMap.get(extension);
+ if (commandsList) {
+ commandsList.unregister();
+ commandsMap.delete(extension);
+ }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+extensions.registerSchemaAPI("commands", "addon_parent", context => {
+ let {extension} = context;
+ return {
+ commands: {
+ getAll() {
+ let commands = commandsMap.get(extension).commands;
+ return Promise.resolve(Array.from(commands, ([name, command]) => {
+ return ({
+ name,
+ description: command.description,
+ shortcut: command.shortcut,
+ });
+ }));
+ },
+ onCommand: new EventManager(context, "commands.onCommand", fire => {
+ let listener = (eventName, commandName) => {
+ fire(commandName);
+ };
+ commandsMap.get(extension).on("command", listener);
+ return () => {
+ commandsMap.get(extension).off("command", listener);
+ };
+ }).api(),
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-contextMenus.js b/browser/components/extensions/ext-contextMenus.js
new file mode 100644
index 000000000..b3bf8aa53
--- /dev/null
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -0,0 +1,537 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+Cu.import("resource://gre/modules/MatchPattern.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var {
+ EventManager,
+ ExtensionError,
+ IconDetails,
+} = ExtensionUtils;
+
+// Map[Extension -> Map[ID -> MenuItem]]
+// Note: we want to enumerate all the menu items so
+// this cannot be a weak map.
+var gContextMenuMap = new Map();
+
+// Map[Extension -> MenuItem]
+var gRootItems = new Map();
+
+// If id is not specified for an item we use an integer.
+var gNextMenuItemID = 0;
+
+// Used to assign unique names to radio groups.
+var gNextRadioGroupID = 0;
+
+// The max length of a menu item's label.
+var gMaxLabelLength = 64;
+
+// When a new contextMenu is opened, this function is called and
+// we populate the |xulMenu| with all the items from extensions
+// to be displayed. We always clear all the items again when
+// popuphidden fires.
+var gMenuBuilder = {
+ build: function(contextData) {
+ let xulMenu = contextData.menu;
+ xulMenu.addEventListener("popuphidden", this);
+ this.xulMenu = xulMenu;
+ for (let [, root] of gRootItems) {
+ let rootElement = this.buildElementWithChildren(root, contextData);
+ if (!rootElement.firstChild || !rootElement.firstChild.childNodes.length) {
+ // If the root has no visible children, there is no reason to show
+ // the root menu item itself either.
+ continue;
+ }
+ rootElement.setAttribute("ext-type", "top-level-menu");
+ rootElement = this.removeTopLevelMenuIfNeeded(rootElement);
+
+ // Display the extension icon on the root element.
+ if (root.extension.manifest.icons) {
+ let parentWindow = contextData.menu.ownerGlobal;
+ let extension = root.extension;
+
+ let {icon} = IconDetails.getPreferredIcon(extension.manifest.icons, extension,
+ 16 * parentWindow.devicePixelRatio);
+
+ // The extension icons in the manifest are not pre-resolved, since
+ // they're sometimes used by the add-on manager when the extension is
+ // not enabled, and its URLs are not resolvable.
+ let resolvedURL = root.extension.baseURI.resolve(icon);
+
+ if (rootElement.localName == "menu") {
+ rootElement.setAttribute("class", "menu-iconic");
+ } else if (rootElement.localName == "menuitem") {
+ rootElement.setAttribute("class", "menuitem-iconic");
+ }
+ rootElement.setAttribute("image", resolvedURL);
+ }
+
+ xulMenu.appendChild(rootElement);
+ this.itemsToCleanUp.add(rootElement);
+ }
+ },
+
+ buildElementWithChildren(item, contextData) {
+ let element = this.buildSingleElement(item, contextData);
+ let groupName;
+ for (let child of item.children) {
+ if (child.type == "radio" && !child.groupName) {
+ if (!groupName) {
+ groupName = `webext-radio-group-${gNextRadioGroupID++}`;
+ }
+ child.groupName = groupName;
+ } else {
+ groupName = null;
+ }
+
+ if (child.enabledForContext(contextData)) {
+ let childElement = this.buildElementWithChildren(child, contextData);
+ // Here element must be a menu element and its first child
+ // is a menupopup, we have to append its children to this
+ // menupopup.
+ element.firstChild.appendChild(childElement);
+ }
+ }
+
+ return element;
+ },
+
+ removeTopLevelMenuIfNeeded(element) {
+ // If there is only one visible top level element we don't need the
+ // root menu element for the extension.
+ let menuPopup = element.firstChild;
+ if (menuPopup && menuPopup.childNodes.length == 1) {
+ let onlyChild = menuPopup.firstChild;
+ onlyChild.remove();
+ return onlyChild;
+ }
+
+ return element;
+ },
+
+ buildSingleElement(item, contextData) {
+ let doc = contextData.menu.ownerDocument;
+ let element;
+ if (item.children.length > 0) {
+ element = this.createMenuElement(doc, item);
+ } else if (item.type == "separator") {
+ element = doc.createElement("menuseparator");
+ } else {
+ element = doc.createElement("menuitem");
+ }
+
+ return this.customizeElement(element, item, contextData);
+ },
+
+ createMenuElement(doc, item) {
+ let element = doc.createElement("menu");
+ // Menu elements need to have a menupopup child for its menu items.
+ let menupopup = doc.createElement("menupopup");
+ element.appendChild(menupopup);
+ return element;
+ },
+
+ customizeElement(element, item, contextData) {
+ let label = item.title;
+ if (label) {
+ if (contextData.isTextSelected && label.indexOf("%s") > -1) {
+ let selection = contextData.selectionText;
+ // The rendering engine will truncate the title if it's longer than 64 characters.
+ // But if it makes sense let's try truncate selection text only, to handle cases like
+ // 'look up "%s" in MyDictionary' more elegantly.
+ let maxSelectionLength = gMaxLabelLength - label.length + 2;
+ if (maxSelectionLength > 4) {
+ selection = selection.substring(0, maxSelectionLength - 3) + "...";
+ }
+ label = label.replace(/%s/g, selection);
+ }
+
+ element.setAttribute("label", label);
+ }
+
+ if (item.type == "checkbox") {
+ element.setAttribute("type", "checkbox");
+ if (item.checked) {
+ element.setAttribute("checked", "true");
+ }
+ } else if (item.type == "radio") {
+ element.setAttribute("type", "radio");
+ element.setAttribute("name", item.groupName);
+ if (item.checked) {
+ element.setAttribute("checked", "true");
+ }
+ }
+
+ if (!item.enabled) {
+ element.setAttribute("disabled", "true");
+ }
+
+ element.addEventListener("command", event => { // eslint-disable-line mozilla/balanced-listeners
+ if (event.target !== event.currentTarget) {
+ return;
+ }
+ const wasChecked = item.checked;
+ if (item.type == "checkbox") {
+ item.checked = !item.checked;
+ } else if (item.type == "radio") {
+ // Deselect all radio items in the current radio group.
+ for (let child of item.parent.children) {
+ if (child.type == "radio" && child.groupName == item.groupName) {
+ child.checked = false;
+ }
+ }
+ // Select the clicked radio item.
+ item.checked = true;
+ }
+
+ item.tabManager.addActiveTabPermission();
+
+ let tab = item.tabManager.convert(contextData.tab);
+ let info = item.getClickInfo(contextData, wasChecked);
+ item.extension.emit("webext-contextmenu-menuitem-click", info, tab);
+ });
+
+ return element;
+ },
+
+ handleEvent: function(event) {
+ if (this.xulMenu != event.target || event.type != "popuphidden") {
+ return;
+ }
+
+ delete this.xulMenu;
+ let target = event.target;
+ target.removeEventListener("popuphidden", this);
+ for (let item of this.itemsToCleanUp) {
+ item.remove();
+ }
+ this.itemsToCleanUp.clear();
+ },
+
+ itemsToCleanUp: new Set(),
+};
+
+function contextMenuObserver(subject, topic, data) {
+ subject = subject.wrappedJSObject;
+ gMenuBuilder.build(subject);
+}
+
+function getContexts(contextData) {
+ let contexts = new Set(["all"]);
+
+ if (contextData.inFrame) {
+ contexts.add("frame");
+ }
+
+ if (contextData.isTextSelected) {
+ contexts.add("selection");
+ }
+
+ if (contextData.onLink) {
+ contexts.add("link");
+ }
+
+ if (contextData.onEditableArea) {
+ contexts.add("editable");
+ }
+
+ if (contextData.onImage) {
+ contexts.add("image");
+ }
+
+ if (contextData.onVideo) {
+ contexts.add("video");
+ }
+
+ if (contextData.onAudio) {
+ contexts.add("audio");
+ }
+
+ if (contexts.size == 1) {
+ contexts.add("page");
+ }
+
+ return contexts;
+}
+
+function MenuItem(extension, createProperties, isRoot = false) {
+ this.extension = extension;
+ this.children = [];
+ this.parent = null;
+ this.tabManager = TabManager.for(extension);
+
+ this.setDefaults();
+ this.setProps(createProperties);
+ if (!this.hasOwnProperty("_id")) {
+ this.id = gNextMenuItemID++;
+ }
+ // If the item is not the root and has no parent
+ // it must be a child of the root.
+ if (!isRoot && !this.parent) {
+ this.root.addChild(this);
+ }
+}
+
+MenuItem.prototype = {
+ setProps(createProperties) {
+ for (let propName in createProperties) {
+ if (createProperties[propName] === null) {
+ // Omitted optional argument.
+ continue;
+ }
+ this[propName] = createProperties[propName];
+ }
+
+ if (createProperties.documentUrlPatterns != null) {
+ this.documentUrlMatchPattern = new MatchPattern(this.documentUrlPatterns);
+ }
+
+ if (createProperties.targetUrlPatterns != null) {
+ this.targetUrlMatchPattern = new MatchPattern(this.targetUrlPatterns);
+ }
+ },
+
+ setDefaults() {
+ this.setProps({
+ type: "normal",
+ checked: false,
+ contexts: ["all"],
+ enabled: true,
+ });
+ },
+
+ set id(id) {
+ if (this.hasOwnProperty("_id")) {
+ throw new Error("Id of a MenuItem cannot be changed");
+ }
+ let isIdUsed = gContextMenuMap.get(this.extension).has(id);
+ if (isIdUsed) {
+ throw new Error("Id already exists");
+ }
+ this._id = id;
+ },
+
+ get id() {
+ return this._id;
+ },
+
+ ensureValidParentId(parentId) {
+ if (parentId === undefined) {
+ return;
+ }
+ let menuMap = gContextMenuMap.get(this.extension);
+ if (!menuMap.has(parentId)) {
+ throw new Error("Could not find any MenuItem with id: " + parentId);
+ }
+ for (let item = menuMap.get(parentId); item; item = item.parent) {
+ if (item === this) {
+ throw new ExtensionError("MenuItem cannot be an ancestor (or self) of its new parent.");
+ }
+ }
+ },
+
+ set parentId(parentId) {
+ this.ensureValidParentId(parentId);
+
+ if (this.parent) {
+ this.parent.detachChild(this);
+ }
+
+ if (parentId === undefined) {
+ this.root.addChild(this);
+ } else {
+ let menuMap = gContextMenuMap.get(this.extension);
+ menuMap.get(parentId).addChild(this);
+ }
+ },
+
+ get parentId() {
+ return this.parent ? this.parent.id : undefined;
+ },
+
+ addChild(child) {
+ if (child.parent) {
+ throw new Error("Child MenuItem already has a parent.");
+ }
+ this.children.push(child);
+ child.parent = this;
+ },
+
+ detachChild(child) {
+ let idx = this.children.indexOf(child);
+ if (idx < 0) {
+ throw new Error("Child MenuItem not found, it cannot be removed.");
+ }
+ this.children.splice(idx, 1);
+ child.parent = null;
+ },
+
+ get root() {
+ let extension = this.extension;
+ if (!gRootItems.has(extension)) {
+ let root = new MenuItem(extension,
+ {title: extension.name},
+ /* isRoot = */ true);
+ gRootItems.set(extension, root);
+ }
+
+ return gRootItems.get(extension);
+ },
+
+ remove() {
+ if (this.parent) {
+ this.parent.detachChild(this);
+ }
+ let children = this.children.slice(0);
+ for (let child of children) {
+ child.remove();
+ }
+
+ let menuMap = gContextMenuMap.get(this.extension);
+ menuMap.delete(this.id);
+ if (this.root == this) {
+ gRootItems.delete(this.extension);
+ }
+ },
+
+ getClickInfo(contextData, wasChecked) {
+ let mediaType;
+ if (contextData.onVideo) {
+ mediaType = "video";
+ }
+ if (contextData.onAudio) {
+ mediaType = "audio";
+ }
+ if (contextData.onImage) {
+ mediaType = "image";
+ }
+
+ let info = {
+ menuItemId: this.id,
+ editable: contextData.onEditableArea,
+ };
+
+ function setIfDefined(argName, value) {
+ if (value !== undefined) {
+ info[argName] = value;
+ }
+ }
+
+ setIfDefined("parentMenuItemId", this.parentId);
+ setIfDefined("mediaType", mediaType);
+ setIfDefined("linkUrl", contextData.linkUrl);
+ setIfDefined("srcUrl", contextData.srcUrl);
+ setIfDefined("pageUrl", contextData.pageUrl);
+ setIfDefined("frameUrl", contextData.frameUrl);
+ setIfDefined("selectionText", contextData.selectionText);
+
+ if ((this.type === "checkbox") || (this.type === "radio")) {
+ info.checked = this.checked;
+ info.wasChecked = wasChecked;
+ }
+
+ return info;
+ },
+
+ enabledForContext(contextData) {
+ let contexts = getContexts(contextData);
+ if (!this.contexts.some(n => contexts.has(n))) {
+ return false;
+ }
+
+ let docPattern = this.documentUrlMatchPattern;
+ let pageURI = Services.io.newURI(contextData.pageUrl, null, null);
+ if (docPattern && !docPattern.matches(pageURI)) {
+ return false;
+ }
+
+ let targetPattern = this.targetUrlMatchPattern;
+ if (targetPattern) {
+ let targetUrls = [];
+ if (contextData.onImage || contextData.onAudio || contextData.onVideo) {
+ // TODO: double check if srcUrl is always set when we need it
+ targetUrls.push(contextData.srcUrl);
+ }
+ if (contextData.onLink) {
+ targetUrls.push(contextData.linkUrl);
+ }
+ if (!targetUrls.some(targetUrl => targetPattern.matches(NetUtil.newURI(targetUrl)))) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+};
+
+var gExtensionCount = 0;
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("startup", (type, extension) => {
+ gContextMenuMap.set(extension, new Map());
+ if (++gExtensionCount == 1) {
+ Services.obs.addObserver(contextMenuObserver,
+ "on-build-contextmenu",
+ false);
+ }
+});
+
+extensions.on("shutdown", (type, extension) => {
+ gContextMenuMap.delete(extension);
+ gRootItems.delete(extension);
+ if (--gExtensionCount == 0) {
+ Services.obs.removeObserver(contextMenuObserver,
+ "on-build-contextmenu");
+ }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+extensions.registerSchemaAPI("contextMenus", "addon_parent", context => {
+ let {extension} = context;
+ return {
+ contextMenus: {
+ createInternal: function(createProperties) {
+ // Note that the id is required by the schema. If the addon did not set
+ // it, the implementation of contextMenus.create in the child should
+ // have added it.
+ let menuItem = new MenuItem(extension, createProperties);
+ gContextMenuMap.get(extension).set(menuItem.id, menuItem);
+ },
+
+ update: function(id, updateProperties) {
+ let menuItem = gContextMenuMap.get(extension).get(id);
+ if (menuItem) {
+ menuItem.setProps(updateProperties);
+ }
+ },
+
+ remove: function(id) {
+ let menuItem = gContextMenuMap.get(extension).get(id);
+ if (menuItem) {
+ menuItem.remove();
+ }
+ },
+
+ removeAll: function() {
+ let root = gRootItems.get(extension);
+ if (root) {
+ root.remove();
+ }
+ },
+
+ onClicked: new EventManager(context, "contextMenus.onClicked", fire => {
+ let listener = (event, info, tab) => {
+ fire(info, tab);
+ };
+
+ extension.on("webext-contextmenu-menuitem-click", listener);
+ return () => {
+ extension.off("webext-contextmenu-menuitem-click", listener);
+ };
+ }).api(),
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-desktop-runtime.js b/browser/components/extensions/ext-desktop-runtime.js
new file mode 100644
index 000000000..0fdb45562
--- /dev/null
+++ b/browser/components/extensions/ext-desktop-runtime.js
@@ -0,0 +1,26 @@
+"use strict";
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("uninstall", (msg, extension) => {
+ if (extension.uninstallURL) {
+ let browser = WindowManager.topWindow.gBrowser;
+ browser.addTab(extension.uninstallURL, {relatedToCurrent: true});
+ }
+});
+
+global.openOptionsPage = (extension) => {
+ let window = WindowManager.topWindow;
+ if (!window) {
+ return Promise.reject({message: "No browser window available"});
+ }
+
+ if (extension.manifest.options_ui.open_in_tab) {
+ window.switchToTabHavingURI(extension.manifest.options_ui.page, true);
+ return Promise.resolve();
+ }
+
+ let viewId = `addons://detail/${encodeURIComponent(extension.id)}/preferences`;
+
+ return window.BrowserOpenAddonsMgr(viewId);
+};
+
diff --git a/browser/components/extensions/ext-history.js b/browser/components/extensions/ext-history.js
new file mode 100644
index 000000000..a47df1621
--- /dev/null
+++ b/browser/components/extensions/ext-history.js
@@ -0,0 +1,246 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
+ "resource://devtools/shared/event-emitter.js");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+const {
+ normalizeTime,
+ SingletonEventManager,
+} = ExtensionUtils;
+
+let nsINavHistoryService = Ci.nsINavHistoryService;
+const TRANSITION_TO_TRANSITION_TYPES_MAP = new Map([
+ ["link", nsINavHistoryService.TRANSITION_LINK],
+ ["typed", nsINavHistoryService.TRANSITION_TYPED],
+ ["auto_bookmark", nsINavHistoryService.TRANSITION_BOOKMARK],
+ ["auto_subframe", nsINavHistoryService.TRANSITION_EMBED],
+ ["manual_subframe", nsINavHistoryService.TRANSITION_FRAMED_LINK],
+]);
+
+let TRANSITION_TYPE_TO_TRANSITIONS_MAP = new Map();
+for (let [transition, transitionType] of TRANSITION_TO_TRANSITION_TYPES_MAP) {
+ TRANSITION_TYPE_TO_TRANSITIONS_MAP.set(transitionType, transition);
+}
+
+function getTransitionType(transition) {
+ // cannot set a default value for the transition argument as the framework sets it to null
+ transition = transition || "link";
+ let transitionType = TRANSITION_TO_TRANSITION_TYPES_MAP.get(transition);
+ if (!transitionType) {
+ throw new Error(`|${transition}| is not a supported transition for history`);
+ }
+ return transitionType;
+}
+
+function getTransition(transitionType) {
+ return TRANSITION_TYPE_TO_TRANSITIONS_MAP.get(transitionType) || "link";
+}
+
+/*
+ * Converts a nsINavHistoryResultNode into a HistoryItem
+ *
+ * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
+ */
+function convertNodeToHistoryItem(node) {
+ return {
+ id: node.pageGuid,
+ url: node.uri,
+ title: node.title,
+ lastVisitTime: PlacesUtils.toDate(node.time).getTime(),
+ visitCount: node.accessCount,
+ };
+}
+
+/*
+ * Converts a nsINavHistoryResultNode into a VisitItem
+ *
+ * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
+ */
+function convertNodeToVisitItem(node) {
+ return {
+ id: node.pageGuid,
+ visitId: node.visitId,
+ visitTime: PlacesUtils.toDate(node.time).getTime(),
+ referringVisitId: node.fromVisitId,
+ transition: getTransition(node.visitType),
+ };
+}
+
+/*
+ * Converts a nsINavHistoryContainerResultNode into an array of objects
+ *
+ * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryContainerResultNode
+ */
+function convertNavHistoryContainerResultNode(container, converter) {
+ let results = [];
+ container.containerOpen = true;
+ for (let i = 0; i < container.childCount; i++) {
+ let node = container.getChild(i);
+ results.push(converter(node));
+ }
+ container.containerOpen = false;
+ return results;
+}
+
+var _observer;
+
+function getObserver() {
+ if (!_observer) {
+ _observer = {
+ onDeleteURI: function(uri, guid, reason) {
+ this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
+ },
+ onVisit: function(uri, visitId, time, sessionId, referringId, transitionType, guid, hidden, visitCount, typed) {
+ let data = {
+ id: guid,
+ url: uri.spec,
+ title: "",
+ lastVisitTime: time / 1000, // time from Places is microseconds,
+ visitCount,
+ typedCount: typed,
+ };
+ this.emit("visited", data);
+ },
+ onBeginUpdateBatch: function() {},
+ onEndUpdateBatch: function() {},
+ onTitleChanged: function() {},
+ onClearHistory: function() {
+ this.emit("visitRemoved", {allHistory: true, urls: []});
+ },
+ onPageChanged: function() {},
+ onFrecencyChanged: function() {},
+ onManyFrecenciesChanged: function() {},
+ onDeleteVisits: function(uri, time, guid, reason) {
+ this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
+ },
+ };
+ EventEmitter.decorate(_observer);
+ PlacesUtils.history.addObserver(_observer, false);
+ }
+ return _observer;
+}
+
+extensions.registerSchemaAPI("history", "addon_parent", context => {
+ return {
+ history: {
+ addUrl: function(details) {
+ let transition, date;
+ try {
+ transition = getTransitionType(details.transition);
+ } catch (error) {
+ return Promise.reject({message: error.message});
+ }
+ if (details.visitTime) {
+ date = normalizeTime(details.visitTime);
+ }
+ let pageInfo = {
+ title: details.title,
+ url: details.url,
+ visits: [
+ {
+ transition,
+ date,
+ },
+ ],
+ };
+ try {
+ return PlacesUtils.history.insert(pageInfo).then(() => undefined);
+ } catch (error) {
+ return Promise.reject({message: error.message});
+ }
+ },
+
+ deleteAll: function() {
+ return PlacesUtils.history.clear();
+ },
+
+ deleteRange: function(filter) {
+ let newFilter = {
+ beginDate: normalizeTime(filter.startTime),
+ endDate: normalizeTime(filter.endTime),
+ };
+ // History.removeVisitsByFilter returns a boolean, but our API should return nothing
+ return PlacesUtils.history.removeVisitsByFilter(newFilter).then(() => undefined);
+ },
+
+ deleteUrl: function(details) {
+ let url = details.url;
+ // History.remove returns a boolean, but our API should return nothing
+ return PlacesUtils.history.remove(url).then(() => undefined);
+ },
+
+ search: function(query) {
+ let beginTime = (query.startTime == null) ?
+ PlacesUtils.toPRTime(Date.now() - 24 * 60 * 60 * 1000) :
+ PlacesUtils.toPRTime(normalizeTime(query.startTime));
+ let endTime = (query.endTime == null) ?
+ Number.MAX_VALUE :
+ PlacesUtils.toPRTime(normalizeTime(query.endTime));
+ if (beginTime > endTime) {
+ return Promise.reject({message: "The startTime cannot be after the endTime"});
+ }
+
+ let options = PlacesUtils.history.getNewQueryOptions();
+ options.sortingMode = options.SORT_BY_DATE_DESCENDING;
+ options.maxResults = query.maxResults || 100;
+
+ let historyQuery = PlacesUtils.history.getNewQuery();
+ historyQuery.searchTerms = query.text;
+ historyQuery.beginTime = beginTime;
+ historyQuery.endTime = endTime;
+ let queryResult = PlacesUtils.history.executeQuery(historyQuery, options).root;
+ let results = convertNavHistoryContainerResultNode(queryResult, convertNodeToHistoryItem);
+ return Promise.resolve(results);
+ },
+
+ getVisits: function(details) {
+ let url = details.url;
+ if (!url) {
+ return Promise.reject({message: "A URL must be provided for getVisits"});
+ }
+
+ let options = PlacesUtils.history.getNewQueryOptions();
+ options.sortingMode = options.SORT_BY_DATE_DESCENDING;
+ options.resultType = options.RESULTS_AS_VISIT;
+
+ let historyQuery = PlacesUtils.history.getNewQuery();
+ historyQuery.uri = NetUtil.newURI(url);
+ let queryResult = PlacesUtils.history.executeQuery(historyQuery, options).root;
+ let results = convertNavHistoryContainerResultNode(queryResult, convertNodeToVisitItem);
+ return Promise.resolve(results);
+ },
+
+ onVisited: new SingletonEventManager(context, "history.onVisited", fire => {
+ let listener = (event, data) => {
+ context.runSafe(fire, data);
+ };
+
+ getObserver().on("visited", listener);
+ return () => {
+ getObserver().off("visited", listener);
+ };
+ }).api(),
+
+ onVisitRemoved: new SingletonEventManager(context, "history.onVisitRemoved", fire => {
+ let listener = (event, data) => {
+ context.runSafe(fire, data);
+ };
+
+ getObserver().on("visitRemoved", listener);
+ return () => {
+ getObserver().off("visitRemoved", listener);
+ };
+ }).api(),
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-omnibox.js b/browser/components/extensions/ext-omnibox.js
new file mode 100644
index 000000000..9b2f60ca4
--- /dev/null
+++ b/browser/components/extensions/ext-omnibox.js
@@ -0,0 +1,104 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSearchHandler",
+ "resource://gre/modules/ExtensionSearchHandler.jsm");
+var {
+ SingletonEventManager,
+} = ExtensionUtils;
+
+// WeakMap[extension -> keyword]
+let gKeywordMap = new WeakMap();
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_omnibox", (type, directive, extension, manifest) => {
+ let keyword = manifest.omnibox.keyword;
+ try {
+ // This will throw if the keyword is already registered.
+ ExtensionSearchHandler.registerKeyword(keyword, extension);
+ gKeywordMap.set(extension, keyword);
+ } catch (e) {
+ extension.manifestError(e.message);
+ }
+});
+
+extensions.on("shutdown", (type, extension) => {
+ let keyword = gKeywordMap.get(extension);
+ if (keyword) {
+ ExtensionSearchHandler.unregisterKeyword(keyword);
+ gKeywordMap.delete(extension);
+ }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+extensions.registerSchemaAPI("omnibox", "addon_parent", context => {
+ let {extension} = context;
+ return {
+ omnibox: {
+ setDefaultSuggestion(suggestion) {
+ let keyword = gKeywordMap.get(extension);
+ try {
+ // This will throw if the keyword failed to register.
+ ExtensionSearchHandler.setDefaultSuggestion(keyword, suggestion);
+ } catch (e) {
+ return Promise.reject(e.message);
+ }
+ },
+
+ onInputStarted: new SingletonEventManager(context, "omnibox.onInputStarted", fire => {
+ let listener = (eventName) => {
+ fire();
+ };
+ extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
+ return () => {
+ extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
+ };
+ }).api(),
+
+ onInputCancelled: new SingletonEventManager(context, "omnibox.onInputCancelled", fire => {
+ let listener = (eventName) => {
+ fire();
+ };
+ extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
+ return () => {
+ extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
+ };
+ }).api(),
+
+ onInputEntered: new SingletonEventManager(context, "omnibox.onInputEntered", fire => {
+ let listener = (eventName, text, disposition) => {
+ fire(text, disposition);
+ };
+ extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
+ return () => {
+ extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
+ };
+ }).api(),
+ },
+
+ omnibox_internal: {
+ addSuggestions(id, suggestions) {
+ let keyword = gKeywordMap.get(extension);
+ try {
+ ExtensionSearchHandler.addSuggestions(keyword, id, suggestions);
+ } catch (e) {
+ // Silently fail because the extension developer can not know for sure if the user
+ // has already invalidated the callback when asynchronously providing suggestions.
+ }
+ },
+
+ onInputChanged: new SingletonEventManager(context, "omnibox_internal.onInputChanged", fire => {
+ let listener = (eventName, text, id) => {
+ fire(text, id);
+ };
+ extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
+ return () => {
+ extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
+ };
+ }).api(),
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-pageAction.js b/browser/components/extensions/ext-pageAction.js
new file mode 100644
index 000000000..153f05d7a
--- /dev/null
+++ b/browser/components/extensions/ext-pageAction.js
@@ -0,0 +1,287 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+ EventManager,
+ IconDetails,
+} = ExtensionUtils;
+
+// WeakMap[Extension -> PageAction]
+var pageActionMap = new WeakMap();
+
+// Handles URL bar icons, including the |page_action| manifest entry
+// and associated API.
+function PageAction(options, extension) {
+ this.extension = extension;
+ this.id = makeWidgetId(extension.id) + "-page-action";
+
+ this.tabManager = TabManager.for(extension);
+
+ this.defaults = {
+ show: false,
+ title: options.default_title || extension.name,
+ icon: IconDetails.normalize({path: options.default_icon}, extension),
+ popup: options.default_popup || "",
+ };
+
+ this.browserStyle = options.browser_style || false;
+ if (options.browser_style === null) {
+ this.extension.logger.warn("Please specify whether you want browser_style " +
+ "or not in your page_action options.");
+ }
+
+ this.tabContext = new TabContext(tab => Object.create(this.defaults),
+ extension);
+
+ this.tabContext.on("location-change", this.handleLocationChange.bind(this)); // eslint-disable-line mozilla/balanced-listeners
+
+ // WeakMap[ChromeWindow -> <xul:image>]
+ this.buttons = new WeakMap();
+
+ EventEmitter.decorate(this);
+}
+
+PageAction.prototype = {
+ // Returns the value of the property |prop| for the given tab, where
+ // |prop| is one of "show", "title", "icon", "popup".
+ getProperty(tab, prop) {
+ return this.tabContext.get(tab)[prop];
+ },
+
+ // Sets the value of the property |prop| for the given tab to the
+ // given value, symmetrically to |getProperty|.
+ //
+ // If |tab| is currently selected, updates the page action button to
+ // reflect the new value.
+ setProperty(tab, prop, value) {
+ if (value != null) {
+ this.tabContext.get(tab)[prop] = value;
+ } else {
+ delete this.tabContext.get(tab)[prop];
+ }
+
+ if (tab.selected) {
+ this.updateButton(tab.ownerGlobal);
+ }
+ },
+
+ // Updates the page action button in the given window to reflect the
+ // properties of the currently selected tab:
+ //
+ // Updates "tooltiptext" and "aria-label" to match "title" property.
+ // Updates "image" to match the "icon" property.
+ // Shows or hides the icon, based on the "show" property.
+ updateButton(window) {
+ let tabData = this.tabContext.get(window.gBrowser.selectedTab);
+
+ if (!(tabData.show || this.buttons.has(window))) {
+ // Don't bother creating a button for a window until it actually
+ // needs to be shown.
+ return;
+ }
+
+ let button = this.getButton(window);
+
+ if (tabData.show) {
+ // Update the title and icon only if the button is visible.
+
+ let title = tabData.title || this.extension.name;
+ button.setAttribute("tooltiptext", title);
+ button.setAttribute("aria-label", title);
+
+ // These URLs should already be properly escaped, but make doubly sure CSS
+ // string escape characters are escaped here, since they could lead to a
+ // sandbox break.
+ let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
+
+ let getIcon = size => escape(IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
+
+ button.setAttribute("style", `
+ --webextension-urlbar-image: url("${getIcon(16)}");
+ --webextension-urlbar-image-2x: url("${getIcon(32)}");
+ `);
+
+ button.classList.add("webextension-page-action");
+ }
+
+ button.hidden = !tabData.show;
+ },
+
+ // Create an |image| node and add it to the |urlbar-icons|
+ // container in the given window.
+ addButton(window) {
+ let document = window.document;
+
+ let button = document.createElement("image");
+ button.id = this.id;
+ button.setAttribute("class", "urlbar-icon");
+
+ button.addEventListener("click", event => { // eslint-disable-line mozilla/balanced-listeners
+ if (event.button == 0) {
+ this.handleClick(window);
+ }
+ });
+
+ document.getElementById("urlbar-icons").appendChild(button);
+
+ return button;
+ },
+
+ // Returns the page action button for the given window, creating it if
+ // it doesn't already exist.
+ getButton(window) {
+ if (!this.buttons.has(window)) {
+ let button = this.addButton(window);
+ this.buttons.set(window, button);
+ }
+
+ return this.buttons.get(window);
+ },
+
+ /**
+ * Triggers this page action for the given window, with the same effects as
+ * if it were clicked by a user.
+ *
+ * This has no effect if the page action is hidden for the selected tab.
+ *
+ * @param {Window} window
+ */
+ triggerAction(window) {
+ let pageAction = pageActionMap.get(this.extension);
+ if (pageAction.getProperty(window.gBrowser.selectedTab, "show")) {
+ pageAction.handleClick(window);
+ }
+ },
+
+ // Handles a click event on the page action button for the given
+ // window.
+ // If the page action has a |popup| property, a panel is opened to
+ // that URL. Otherwise, a "click" event is emitted, and dispatched to
+ // the any click listeners in the add-on.
+ handleClick(window) {
+ let tab = window.gBrowser.selectedTab;
+ let popupURL = this.tabContext.get(tab).popup;
+
+ this.tabManager.addActiveTabPermission(tab);
+
+ // If the widget has a popup URL defined, we open a popup, but do not
+ // dispatch a click event to the extension.
+ // If it has no popup URL defined, we dispatch a click event, but do not
+ // open a popup.
+ if (popupURL) {
+ new PanelPopup(this.extension, this.getButton(window), popupURL,
+ this.browserStyle);
+ } else {
+ this.emit("click", tab);
+ }
+ },
+
+ handleLocationChange(eventType, tab, fromBrowse) {
+ if (fromBrowse) {
+ this.tabContext.clear(tab);
+ }
+ this.updateButton(tab.ownerGlobal);
+ },
+
+ shutdown() {
+ this.tabContext.shutdown();
+
+ for (let window of WindowListManager.browserWindows()) {
+ if (this.buttons.has(window)) {
+ this.buttons.get(window).remove();
+ }
+ }
+ },
+};
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_page_action", (type, directive, extension, manifest) => {
+ let pageAction = new PageAction(manifest.page_action, extension);
+ pageActionMap.set(extension, pageAction);
+});
+
+extensions.on("shutdown", (type, extension) => {
+ if (pageActionMap.has(extension)) {
+ pageActionMap.get(extension).shutdown();
+ pageActionMap.delete(extension);
+ }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+PageAction.for = extension => {
+ return pageActionMap.get(extension);
+};
+
+global.pageActionFor = PageAction.for;
+
+extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
+ let {extension} = context;
+ return {
+ pageAction: {
+ onClicked: new EventManager(context, "pageAction.onClicked", fire => {
+ let listener = (evt, tab) => {
+ fire(TabManager.convert(extension, tab));
+ };
+ let pageAction = PageAction.for(extension);
+
+ pageAction.on("click", listener);
+ return () => {
+ pageAction.off("click", listener);
+ };
+ }).api(),
+
+ show(tabId) {
+ let tab = TabManager.getTab(tabId, context);
+ PageAction.for(extension).setProperty(tab, "show", true);
+ },
+
+ hide(tabId) {
+ let tab = TabManager.getTab(tabId, context);
+ PageAction.for(extension).setProperty(tab, "show", false);
+ },
+
+ setTitle(details) {
+ let tab = TabManager.getTab(details.tabId, context);
+
+ // Clear the tab-specific title when given a null string.
+ PageAction.for(extension).setProperty(tab, "title", details.title || null);
+ },
+
+ getTitle(details) {
+ let tab = TabManager.getTab(details.tabId, context);
+
+ let title = PageAction.for(extension).getProperty(tab, "title");
+ return Promise.resolve(title);
+ },
+
+ setIcon(details) {
+ let tab = TabManager.getTab(details.tabId, context);
+
+ let icon = IconDetails.normalize(details, extension, context);
+ PageAction.for(extension).setProperty(tab, "icon", icon);
+ },
+
+ setPopup(details) {
+ let tab = TabManager.getTab(details.tabId, context);
+
+ // Note: Chrome resolves arguments to setIcon relative to the calling
+ // context, but resolves arguments to setPopup relative to the extension
+ // root.
+ // For internal consistency, we currently resolve both relative to the
+ // calling context.
+ let url = details.popup && context.uri.resolve(details.popup);
+ PageAction.for(extension).setProperty(tab, "popup", url);
+ },
+
+ getPopup(details) {
+ let tab = TabManager.getTab(details.tabId, context);
+
+ let popup = PageAction.for(extension).getProperty(tab, "popup");
+ return Promise.resolve(popup);
+ },
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-sessions.js b/browser/components/extensions/ext-sessions.js
new file mode 100644
index 000000000..4c13a1ac3
--- /dev/null
+++ b/browser/components/extensions/ext-sessions.js
@@ -0,0 +1,92 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+ promiseObserved,
+} = ExtensionUtils;
+
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+
+function getRecentlyClosed(maxResults, extension) {
+ let recentlyClosed = [];
+
+ // Get closed windows
+ let closedWindowData = SessionStore.getClosedWindowData(false);
+ for (let window of closedWindowData) {
+ recentlyClosed.push({
+ lastModified: window.closedAt,
+ window: WindowManager.convertFromSessionStoreClosedData(window, extension)});
+ }
+
+ // Get closed tabs
+ for (let window of WindowListManager.browserWindows()) {
+ let closedTabData = SessionStore.getClosedTabData(window, false);
+ for (let tab of closedTabData) {
+ recentlyClosed.push({
+ lastModified: tab.closedAt,
+ tab: TabManager.for(extension).convertFromSessionStoreClosedData(tab, window)});
+ }
+ }
+
+ // Sort windows and tabs
+ recentlyClosed.sort((a, b) => b.lastModified - a.lastModified);
+ return recentlyClosed.slice(0, maxResults);
+}
+
+function createSession(restored, extension, sessionId) {
+ if (!restored) {
+ return Promise.reject({message: `Could not restore object using sessionId ${sessionId}.`});
+ }
+ let sessionObj = {lastModified: Date.now()};
+ if (restored instanceof Ci.nsIDOMChromeWindow) {
+ return promiseObserved("sessionstore-single-window-restored", subject => subject == restored).then(() => {
+ sessionObj.window = WindowManager.convert(extension, restored, {populate: true});
+ return Promise.resolve([sessionObj]);
+ });
+ }
+ sessionObj.tab = TabManager.for(extension).convert(restored);
+ return Promise.resolve([sessionObj]);
+}
+
+extensions.registerSchemaAPI("sessions", "addon_parent", context => {
+ let {extension} = context;
+ return {
+ sessions: {
+ getRecentlyClosed: function(filter) {
+ let maxResults = filter.maxResults == undefined ? this.MAX_SESSION_RESULTS : filter.maxResults;
+ return Promise.resolve(getRecentlyClosed(maxResults, extension));
+ },
+ restore: function(sessionId) {
+ let session, closedId;
+ if (sessionId) {
+ closedId = sessionId;
+ session = SessionStore.undoCloseById(closedId);
+ } else if (SessionStore.lastClosedObjectType == "window") {
+ // If the most recently closed object is a window, just undo closing the most recent window.
+ session = SessionStore.undoCloseWindow(0);
+ } else {
+ // It is a tab, and we cannot call SessionStore.undoCloseTab without a window,
+ // so we must find the tab in which case we can just use its closedId.
+ let recentlyClosedTabs = [];
+ for (let window of WindowListManager.browserWindows()) {
+ let closedTabData = SessionStore.getClosedTabData(window, false);
+ for (let tab of closedTabData) {
+ recentlyClosedTabs.push(tab);
+ }
+ }
+
+ // Sort the tabs.
+ recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt);
+
+ // Use the closedId of the most recently closed tab to restore it.
+ closedId = recentlyClosedTabs[0].closedId;
+ session = SessionStore.undoCloseById(closedId);
+ }
+ return createSession(session, extension, closedId);
+ },
+ },
+ };
+});
diff --git a/browser/components/extensions/ext-tabs.js b/browser/components/extensions/ext-tabs.js
new file mode 100644
index 000000000..bb575aaab
--- /dev/null
+++ b/browser/components/extensions/ext-tabs.js
@@ -0,0 +1,1093 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
+ "resource://gre/modules/MatchPattern.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+
+var {
+ EventManager,
+ ignoreEvent,
+} = ExtensionUtils;
+
+// This function is pretty tightly tied to Extension.jsm.
+// Its job is to fill in the |tab| property of the sender.
+function getSender(extension, target, sender) {
+ if ("tabId" in sender) {
+ // The message came from an ExtensionContext. In that case, it should
+ // include a tabId property (which is filled in by the page-open
+ // listener below).
+ let tab = TabManager.getTab(sender.tabId, null, null);
+ delete sender.tabId;
+ if (tab) {
+ sender.tab = TabManager.convert(extension, tab);
+ return;
+ }
+ }
+ if (target instanceof Ci.nsIDOMXULElement) {
+ // If the message was sent from a content script to a <browser> element,
+ // then we can just get the `tab` from `target`.
+ let tabbrowser = target.ownerGlobal.gBrowser;
+ if (tabbrowser) {
+ let tab = tabbrowser.getTabForBrowser(target);
+
+ // `tab` can be `undefined`, e.g. for extension popups. This condition is
+ // reached if `getSender` is called for a popup without a valid `tabId`.
+ if (tab) {
+ sender.tab = TabManager.convert(extension, tab);
+ }
+ }
+ }
+}
+
+// Used by Extension.jsm
+global.tabGetSender = getSender;
+
+/* eslint-disable mozilla/balanced-listeners */
+
+extensions.on("page-shutdown", (type, context) => {
+ if (context.viewType == "tab") {
+ if (context.extension.id !== context.xulBrowser.contentPrincipal.addonId) {
+ // Only close extension tabs.
+ // This check prevents about:addons from closing when it contains a
+ // WebExtension as an embedded inline options page.
+ return;
+ }
+ let {gBrowser} = context.xulBrowser.ownerGlobal;
+ if (gBrowser) {
+ let tab = gBrowser.getTabForBrowser(context.xulBrowser);
+ if (tab) {
+ gBrowser.removeTab(tab);
+ }
+ }
+ }
+});
+
+extensions.on("fill-browser-data", (type, browser, data) => {
+ data.tabId = browser ? TabManager.getBrowserId(browser) : -1;
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+global.currentWindow = function(context) {
+ let {xulWindow} = context;
+ if (xulWindow && context.viewType != "background") {
+ return xulWindow;
+ }
+ return WindowManager.topWindow;
+};
+
+let tabListener = {
+ init() {
+ if (this.initialized) {
+ return;
+ }
+
+ this.adoptedTabs = new WeakMap();
+
+ this.handleWindowOpen = this.handleWindowOpen.bind(this);
+ this.handleWindowClose = this.handleWindowClose.bind(this);
+
+ AllWindowEvents.addListener("TabClose", this);
+ AllWindowEvents.addListener("TabOpen", this);
+ WindowListManager.addOpenListener(this.handleWindowOpen);
+ WindowListManager.addCloseListener(this.handleWindowClose);
+
+ EventEmitter.decorate(this);
+
+ this.initialized = true;
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ if (event.detail.adoptedTab) {
+ this.adoptedTabs.set(event.detail.adoptedTab, event.target);
+ }
+
+ // We need to delay sending this event until the next tick, since the
+ // tab does not have its final index when the TabOpen event is dispatched.
+ Promise.resolve().then(() => {
+ if (event.detail.adoptedTab) {
+ this.emitAttached(event.originalTarget);
+ } else {
+ this.emitCreated(event.originalTarget);
+ }
+ });
+ break;
+
+ case "TabClose":
+ let tab = event.originalTarget;
+
+ if (event.detail.adoptedBy) {
+ this.emitDetached(tab, event.detail.adoptedBy);
+ } else {
+ this.emitRemoved(tab, false);
+ }
+ break;
+ }
+ },
+
+ handleWindowOpen(window) {
+ if (window.arguments[0] instanceof window.XULElement) {
+ // If the first window argument is a XUL element, it means the
+ // window is about to adopt a tab from another window to replace its
+ // initial tab.
+ //
+ // Note that this event handler depends on running before the
+ // delayed startup code in browser.js, which is currently triggered
+ // by the first MozAfterPaint event. That code handles finally
+ // adopting the tab, and clears it from the arguments list in the
+ // process, so if we run later than it, we're too late.
+ let tab = window.arguments[0];
+ this.adoptedTabs.set(tab, window.gBrowser.tabs[0]);
+
+ // We need to be sure to fire this event after the onDetached event
+ // for the original tab.
+ let listener = (event, details) => {
+ if (details.tab == tab) {
+ this.off("tab-detached", listener);
+
+ Promise.resolve().then(() => {
+ this.emitAttached(details.adoptedBy);
+ });
+ }
+ };
+
+ this.on("tab-detached", listener);
+ } else {
+ for (let tab of window.gBrowser.tabs) {
+ this.emitCreated(tab);
+ }
+ }
+ },
+
+ handleWindowClose(window) {
+ for (let tab of window.gBrowser.tabs) {
+ if (this.adoptedTabs.has(tab)) {
+ this.emitDetached(tab, this.adoptedTabs.get(tab));
+ } else {
+ this.emitRemoved(tab, true);
+ }
+ }
+ },
+
+ emitAttached(tab) {
+ let newWindowId = WindowManager.getId(tab.ownerGlobal);
+ let tabId = TabManager.getId(tab);
+
+ this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
+ },
+
+ emitDetached(tab, adoptedBy) {
+ let oldWindowId = WindowManager.getId(tab.ownerGlobal);
+ let tabId = TabManager.getId(tab);
+
+ this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
+ },
+
+ emitCreated(tab) {
+ this.emit("tab-created", {tab});
+ },
+
+ emitRemoved(tab, isWindowClosing) {
+ let windowId = WindowManager.getId(tab.ownerGlobal);
+ let tabId = TabManager.getId(tab);
+
+ // When addons run in-process, `window.close()` is synchronous. Most other
+ // addon-invoked calls are asynchronous since they go through a proxy
+ // context via the message manager. This includes event registrations such
+ // as `tabs.onRemoved.addListener`.
+ // So, even if `window.close()` were to be called (in-process) after calling
+ // `tabs.onRemoved.addListener`, then the tab would be closed before the
+ // event listener is registered. To make sure that the event listener is
+ // notified, we dispatch `tabs.onRemoved` asynchronously.
+ Services.tm.mainThread.dispatch(() => {
+ this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ tabReadyInitialized: false,
+ tabReadyPromises: new WeakMap(),
+ initializingTabs: new WeakSet(),
+
+ initTabReady() {
+ if (!this.tabReadyInitialized) {
+ AllWindowEvents.addListener("progress", this);
+
+ this.tabReadyInitialized = true;
+ }
+ },
+
+ onLocationChange(browser, webProgress, request, locationURI, flags) {
+ if (webProgress.isTopLevel) {
+ let gBrowser = browser.ownerGlobal.gBrowser;
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ // Now we are certain that the first page in the tab was loaded.
+ this.initializingTabs.delete(tab);
+
+ // browser.innerWindowID is now set, resolve the promises if any.
+ let deferred = this.tabReadyPromises.get(tab);
+ if (deferred) {
+ deferred.resolve(tab);
+ this.tabReadyPromises.delete(tab);
+ }
+ }
+ },
+
+ /**
+ * Returns a promise that resolves when the tab is ready.
+ * Tabs created via the `tabs.create` method are "ready" once the location
+ * changes to the requested URL. Other tabs are assumed to be ready once their
+ * inner window ID is known.
+ *
+ * @param {XULElement} tab The <tab> element.
+ * @returns {Promise} Resolves with the given tab once ready.
+ */
+ awaitTabReady(tab) {
+ let deferred = this.tabReadyPromises.get(tab);
+ if (!deferred) {
+ deferred = PromiseUtils.defer();
+ if (!this.initializingTabs.has(tab) && tab.linkedBrowser.innerWindowID) {
+ deferred.resolve(tab);
+ } else {
+ this.initTabReady();
+ this.tabReadyPromises.set(tab, deferred);
+ }
+ }
+ return deferred.promise;
+ },
+};
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("startup", () => {
+ tabListener.init();
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+extensions.registerSchemaAPI("tabs", "addon_parent", context => {
+ let {extension} = context;
+ let self = {
+ tabs: {
+ onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
+ let tab = event.originalTarget;
+ let tabId = TabManager.getId(tab);
+ let windowId = WindowManager.getId(tab.ownerGlobal);
+ fire({tabId, windowId});
+ }).api(),
+
+ onCreated: new EventManager(context, "tabs.onCreated", fire => {
+ let listener = (eventName, event) => {
+ fire(TabManager.convert(extension, event.tab));
+ };
+
+ tabListener.on("tab-created", listener);
+ return () => {
+ tabListener.off("tab-created", listener);
+ };
+ }).api(),
+
+ /**
+ * Since multiple tabs currently can't be highlighted, onHighlighted
+ * essentially acts an alias for self.tabs.onActivated but returns
+ * the tabId in an array to match the API.
+ * @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
+ */
+ onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
+ let tab = event.originalTarget;
+ let tabIds = [TabManager.getId(tab)];
+ let windowId = WindowManager.getId(tab.ownerGlobal);
+ fire({tabIds, windowId});
+ }).api(),
+
+ onAttached: new EventManager(context, "tabs.onAttached", fire => {
+ let listener = (eventName, event) => {
+ fire(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
+ };
+
+ tabListener.on("tab-attached", listener);
+ return () => {
+ tabListener.off("tab-attached", listener);
+ };
+ }).api(),
+
+ onDetached: new EventManager(context, "tabs.onDetached", fire => {
+ let listener = (eventName, event) => {
+ fire(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
+ };
+
+ tabListener.on("tab-detached", listener);
+ return () => {
+ tabListener.off("tab-detached", listener);
+ };
+ }).api(),
+
+ onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
+ let listener = (eventName, event) => {
+ fire(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
+ };
+
+ tabListener.on("tab-removed", listener);
+ return () => {
+ tabListener.off("tab-removed", listener);
+ };
+ }).api(),
+
+ onReplaced: ignoreEvent(context, "tabs.onReplaced"),
+
+ onMoved: new EventManager(context, "tabs.onMoved", fire => {
+ // There are certain circumstances where we need to ignore a move event.
+ //
+ // Namely, the first time the tab is moved after it's created, we need
+ // to report the final position as the initial position in the tab's
+ // onAttached or onCreated event. This is because most tabs are inserted
+ // in a temporary location and then moved after the TabOpen event fires,
+ // which generates a TabOpen event followed by a TabMove event, which
+ // does not match the contract of our API.
+ let ignoreNextMove = new WeakSet();
+
+ let openListener = event => {
+ ignoreNextMove.add(event.target);
+ // Remove the tab from the set on the next tick, since it will already
+ // have been moved by then.
+ Promise.resolve().then(() => {
+ ignoreNextMove.delete(event.target);
+ });
+ };
+
+ let moveListener = event => {
+ let tab = event.originalTarget;
+
+ if (ignoreNextMove.has(tab)) {
+ ignoreNextMove.delete(tab);
+ return;
+ }
+
+ fire(TabManager.getId(tab), {
+ windowId: WindowManager.getId(tab.ownerGlobal),
+ fromIndex: event.detail,
+ toIndex: tab._tPos,
+ });
+ };
+
+ AllWindowEvents.addListener("TabMove", moveListener);
+ AllWindowEvents.addListener("TabOpen", openListener);
+ return () => {
+ AllWindowEvents.removeListener("TabMove", moveListener);
+ AllWindowEvents.removeListener("TabOpen", openListener);
+ };
+ }).api(),
+
+ onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
+ function sanitize(extension, changeInfo) {
+ let result = {};
+ let nonempty = false;
+ for (let prop in changeInfo) {
+ if ((prop != "favIconUrl" && prop != "url") || extension.hasPermission("tabs")) {
+ nonempty = true;
+ result[prop] = changeInfo[prop];
+ }
+ }
+ return [nonempty, result];
+ }
+
+ let fireForBrowser = (browser, changed) => {
+ let [needed, changeInfo] = sanitize(extension, changed);
+ if (needed) {
+ let gBrowser = browser.ownerGlobal.gBrowser;
+ let tabElem = gBrowser.getTabForBrowser(browser);
+
+ let tab = TabManager.convert(extension, tabElem);
+ fire(tab.id, changeInfo, tab);
+ }
+ };
+
+ let listener = event => {
+ let needed = [];
+ if (event.type == "TabAttrModified") {
+ let changed = event.detail.changed;
+ if (changed.includes("image")) {
+ needed.push("favIconUrl");
+ }
+ if (changed.includes("muted")) {
+ needed.push("mutedInfo");
+ }
+ if (changed.includes("soundplaying")) {
+ needed.push("audible");
+ }
+ } else if (event.type == "TabPinned") {
+ needed.push("pinned");
+ } else if (event.type == "TabUnpinned") {
+ needed.push("pinned");
+ }
+
+ if (needed.length && !extension.hasPermission("tabs")) {
+ needed = needed.filter(attr => attr != "url" && attr != "favIconUrl");
+ }
+
+ if (needed.length) {
+ let tab = TabManager.convert(extension, event.originalTarget);
+
+ let changeInfo = {};
+ for (let prop of needed) {
+ changeInfo[prop] = tab[prop];
+ }
+ fire(tab.id, changeInfo, tab);
+ }
+ };
+ let progressListener = {
+ onStateChange(browser, webProgress, request, stateFlags, statusCode) {
+ if (!webProgress.isTopLevel) {
+ return;
+ }
+
+ let status;
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
+ status = "loading";
+ } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ status = "complete";
+ }
+ } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ statusCode == Cr.NS_BINDING_ABORTED) {
+ status = "complete";
+ }
+
+ fireForBrowser(browser, {status});
+ },
+
+ onLocationChange(browser, webProgress, request, locationURI, flags) {
+ if (!webProgress.isTopLevel) {
+ return;
+ }
+
+ fireForBrowser(browser, {
+ status: webProgress.isLoadingDocument ? "loading" : "complete",
+ url: locationURI.spec,
+ });
+ },
+ };
+
+ AllWindowEvents.addListener("progress", progressListener);
+ AllWindowEvents.addListener("TabAttrModified", listener);
+ AllWindowEvents.addListener("TabPinned", listener);
+ AllWindowEvents.addListener("TabUnpinned", listener);
+
+ return () => {
+ AllWindowEvents.removeListener("progress", progressListener);
+ AllWindowEvents.removeListener("TabAttrModified", listener);
+ AllWindowEvents.removeListener("TabPinned", listener);
+ AllWindowEvents.removeListener("TabUnpinned", listener);
+ };
+ }).api(),
+
+ create: function(createProperties) {
+ return new Promise((resolve, reject) => {
+ let window = createProperties.windowId !== null ?
+ WindowManager.getWindow(createProperties.windowId, context) :
+ WindowManager.topWindow;
+ if (!window.gBrowser) {
+ let obs = (finishedWindow, topic, data) => {
+ if (finishedWindow != window) {
+ return;
+ }
+ Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
+ resolve(window);
+ };
+ Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
+ } else {
+ resolve(window);
+ }
+ }).then(window => {
+ let url;
+
+ if (createProperties.url !== null) {
+ url = context.uri.resolve(createProperties.url);
+
+ if (!context.checkLoadURL(url, {dontReportErrors: true})) {
+ return Promise.reject({message: `Illegal URL: ${url}`});
+ }
+ }
+
+ if (createProperties.cookieStoreId && !extension.hasPermission("cookies")) {
+ return Promise.reject({message: `No permission for cookieStoreId: ${createProperties.cookieStoreId}`});
+ }
+
+ let options = {};
+ if (createProperties.cookieStoreId) {
+ if (!global.isValidCookieStoreId(createProperties.cookieStoreId)) {
+ return Promise.reject({message: `Illegal cookieStoreId: ${createProperties.cookieStoreId}`});
+ }
+
+ let privateWindow = PrivateBrowsingUtils.isBrowserPrivate(window.gBrowser);
+ if (privateWindow && !global.isPrivateCookieStoreId(createProperties.cookieStoreId)) {
+ return Promise.reject({message: `Illegal to set non-private cookieStorageId in a private window`});
+ }
+
+ if (!privateWindow && global.isPrivateCookieStoreId(createProperties.cookieStoreId)) {
+ return Promise.reject({message: `Illegal to set private cookieStorageId in a non-private window`});
+ }
+
+ if (global.isContainerCookieStoreId(createProperties.cookieStoreId)) {
+ let containerId = global.getContainerForCookieStoreId(createProperties.cookieStoreId);
+ if (!containerId) {
+ return Promise.reject({message: `No cookie store exists with ID ${createProperties.cookieStoreId}`});
+ }
+
+ options.userContextId = containerId;
+ }
+ }
+
+ tabListener.initTabReady();
+ let tab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL, options);
+
+ let active = true;
+ if (createProperties.active !== null) {
+ active = createProperties.active;
+ }
+ if (active) {
+ window.gBrowser.selectedTab = tab;
+ }
+
+ if (createProperties.index !== null) {
+ window.gBrowser.moveTabTo(tab, createProperties.index);
+ }
+
+ if (createProperties.pinned) {
+ window.gBrowser.pinTab(tab);
+ }
+
+ if (createProperties.url && !createProperties.url.startsWith("about:")) {
+ // We can't wait for a location change event for about:newtab,
+ // since it may be pre-rendered, in which case its initial
+ // location change event has already fired.
+
+ // Mark the tab as initializing, so that operations like
+ // `executeScript` wait until the requested URL is loaded in
+ // the tab before dispatching messages to the inner window
+ // that contains the URL we're attempting to load.
+ tabListener.initializingTabs.add(tab);
+ }
+
+ return TabManager.convert(extension, tab);
+ });
+ },
+
+ remove: function(tabs) {
+ if (!Array.isArray(tabs)) {
+ tabs = [tabs];
+ }
+
+ for (let tabId of tabs) {
+ let tab = TabManager.getTab(tabId, context);
+ tab.ownerGlobal.gBrowser.removeTab(tab);
+ }
+
+ return Promise.resolve();
+ },
+
+ update: function(tabId, updateProperties) {
+ let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ let tabbrowser = tab.ownerGlobal.gBrowser;
+
+ if (updateProperties.url !== null) {
+ let url = context.uri.resolve(updateProperties.url);
+
+ if (!context.checkLoadURL(url, {dontReportErrors: true})) {
+ return Promise.reject({message: `Illegal URL: ${url}`});
+ }
+
+ tab.linkedBrowser.loadURI(url);
+ }
+
+ if (updateProperties.active !== null) {
+ if (updateProperties.active) {
+ tabbrowser.selectedTab = tab;
+ } else {
+ // Not sure what to do here? Which tab should we select?
+ }
+ }
+ if (updateProperties.muted !== null) {
+ if (tab.muted != updateProperties.muted) {
+ tab.toggleMuteAudio(extension.uuid);
+ }
+ }
+ if (updateProperties.pinned !== null) {
+ if (updateProperties.pinned) {
+ tabbrowser.pinTab(tab);
+ } else {
+ tabbrowser.unpinTab(tab);
+ }
+ }
+ // FIXME: highlighted/selected, openerTabId
+
+ return Promise.resolve(TabManager.convert(extension, tab));
+ },
+
+ reload: function(tabId, reloadProperties) {
+ let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (reloadProperties && reloadProperties.bypassCache) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ }
+ tab.linkedBrowser.reloadWithFlags(flags);
+
+ return Promise.resolve();
+ },
+
+ get: function(tabId) {
+ let tab = TabManager.getTab(tabId, context);
+
+ return Promise.resolve(TabManager.convert(extension, tab));
+ },
+
+ getCurrent() {
+ let tab;
+ if (context.tabId) {
+ tab = TabManager.convert(extension, TabManager.getTab(context.tabId, context));
+ }
+ return Promise.resolve(tab);
+ },
+
+ query: function(queryInfo) {
+ let pattern = null;
+ if (queryInfo.url !== null) {
+ if (!extension.hasPermission("tabs")) {
+ return Promise.reject({message: 'The "tabs" permission is required to use the query API with the "url" parameter'});
+ }
+
+ pattern = new MatchPattern(queryInfo.url);
+ }
+
+ function matches(tab) {
+ let props = ["active", "pinned", "highlighted", "status", "title", "index"];
+ for (let prop of props) {
+ if (queryInfo[prop] !== null && queryInfo[prop] != tab[prop]) {
+ return false;
+ }
+ }
+
+ if (queryInfo.audible !== null) {
+ if (queryInfo.audible != tab.audible) {
+ return false;
+ }
+ }
+
+ if (queryInfo.muted !== null) {
+ if (queryInfo.muted != tab.mutedInfo.muted) {
+ return false;
+ }
+ }
+
+ if (queryInfo.cookieStoreId !== null &&
+ tab.cookieStoreId != queryInfo.cookieStoreId) {
+ return false;
+ }
+
+ if (pattern && !pattern.matches(Services.io.newURI(tab.url, null, null))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ let result = [];
+ for (let window of WindowListManager.browserWindows()) {
+ let lastFocused = window === WindowManager.topWindow;
+ if (queryInfo.lastFocusedWindow !== null && queryInfo.lastFocusedWindow !== lastFocused) {
+ continue;
+ }
+
+ let windowType = WindowManager.windowType(window);
+ if (queryInfo.windowType !== null && queryInfo.windowType !== windowType) {
+ continue;
+ }
+
+ if (queryInfo.windowId !== null) {
+ if (queryInfo.windowId === WindowManager.WINDOW_ID_CURRENT) {
+ if (currentWindow(context) !== window) {
+ continue;
+ }
+ } else if (queryInfo.windowId !== WindowManager.getId(window)) {
+ continue;
+ }
+ }
+
+ if (queryInfo.currentWindow !== null) {
+ let eq = window === currentWindow(context);
+ if (queryInfo.currentWindow != eq) {
+ continue;
+ }
+ }
+
+ let tabs = TabManager.for(extension).getTabs(window);
+ for (let tab of tabs) {
+ if (matches(tab)) {
+ result.push(tab);
+ }
+ }
+ }
+ return Promise.resolve(result);
+ },
+
+ captureVisibleTab: function(windowId, options) {
+ if (!extension.hasPermission("<all_urls>")) {
+ return Promise.reject({message: "The <all_urls> permission is required to use the captureVisibleTab API"});
+ }
+
+ let window = windowId == null ?
+ WindowManager.topWindow :
+ WindowManager.getWindow(windowId, context);
+
+ let tab = window.gBrowser.selectedTab;
+ return tabListener.awaitTabReady(tab).then(() => {
+ let browser = tab.linkedBrowser;
+ let recipient = {
+ innerWindowID: browser.innerWindowID,
+ };
+
+ if (!options) {
+ options = {};
+ }
+ if (options.format == null) {
+ options.format = "png";
+ }
+ if (options.quality == null) {
+ options.quality = 92;
+ }
+
+ let message = {
+ options,
+ width: browser.clientWidth,
+ height: browser.clientHeight,
+ };
+
+ return context.sendMessage(browser.messageManager, "Extension:Capture",
+ message, {recipient});
+ });
+ },
+
+ detectLanguage: function(tabId) {
+ let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ return tabListener.awaitTabReady(tab).then(() => {
+ let browser = tab.linkedBrowser;
+ let recipient = {innerWindowID: browser.innerWindowID};
+
+ return context.sendMessage(browser.messageManager, "Extension:DetectLanguage",
+ {}, {recipient});
+ });
+ },
+
+ // Used to executeScript, insertCSS and removeCSS.
+ _execute: function(tabId, details, kind, method) {
+ let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ let options = {
+ js: [],
+ css: [],
+ remove_css: method == "removeCSS",
+ };
+
+ // We require a `code` or a `file` property, but we can't accept both.
+ if ((details.code === null) == (details.file === null)) {
+ return Promise.reject({message: `${method} requires either a 'code' or a 'file' property, but not both`});
+ }
+
+ if (details.frameId !== null && details.allFrames) {
+ return Promise.reject({message: `'frameId' and 'allFrames' are mutually exclusive`});
+ }
+
+ if (TabManager.for(extension).hasActiveTabPermission(tab)) {
+ // If we have the "activeTab" permission for this tab, ignore
+ // the host whitelist.
+ options.matchesHost = ["<all_urls>"];
+ } else {
+ options.matchesHost = extension.whiteListedHosts.serialize();
+ }
+
+ if (details.code !== null) {
+ options[kind + "Code"] = details.code;
+ }
+ if (details.file !== null) {
+ let url = context.uri.resolve(details.file);
+ if (!extension.isExtensionURL(url)) {
+ return Promise.reject({message: "Files to be injected must be within the extension"});
+ }
+ options[kind].push(url);
+ }
+ if (details.allFrames) {
+ options.all_frames = details.allFrames;
+ }
+ if (details.frameId !== null) {
+ options.frame_id = details.frameId;
+ }
+ if (details.matchAboutBlank) {
+ options.match_about_blank = details.matchAboutBlank;
+ }
+ if (details.runAt !== null) {
+ options.run_at = details.runAt;
+ } else {
+ options.run_at = "document_idle";
+ }
+
+ return tabListener.awaitTabReady(tab).then(() => {
+ let browser = tab.linkedBrowser;
+ let recipient = {
+ innerWindowID: browser.innerWindowID,
+ };
+
+ return context.sendMessage(browser.messageManager, "Extension:Execute", {options}, {recipient});
+ });
+ },
+
+ executeScript: function(tabId, details) {
+ return self.tabs._execute(tabId, details, "js", "executeScript");
+ },
+
+ insertCSS: function(tabId, details) {
+ return self.tabs._execute(tabId, details, "css", "insertCSS").then(() => {});
+ },
+
+ removeCSS: function(tabId, details) {
+ return self.tabs._execute(tabId, details, "css", "removeCSS").then(() => {});
+ },
+
+ move: function(tabIds, moveProperties) {
+ let index = moveProperties.index;
+ let tabsMoved = [];
+ if (!Array.isArray(tabIds)) {
+ tabIds = [tabIds];
+ }
+
+ let destinationWindow = null;
+ if (moveProperties.windowId !== null) {
+ destinationWindow = WindowManager.getWindow(moveProperties.windowId, context);
+ // Fail on an invalid window.
+ if (!destinationWindow) {
+ return Promise.reject({message: `Invalid window ID: ${moveProperties.windowId}`});
+ }
+ }
+
+ /*
+ Indexes are maintained on a per window basis so that a call to
+ move([tabA, tabB], {index: 0})
+ -> tabA to 0, tabB to 1 if tabA and tabB are in the same window
+ move([tabA, tabB], {index: 0})
+ -> tabA to 0, tabB to 0 if tabA and tabB are in different windows
+ */
+ let indexMap = new Map();
+
+ let tabs = tabIds.map(tabId => TabManager.getTab(tabId, context));
+ for (let tab of tabs) {
+ // If the window is not specified, use the window from the tab.
+ let window = destinationWindow || tab.ownerGlobal;
+ let gBrowser = window.gBrowser;
+
+ let insertionPoint = indexMap.get(window) || index;
+ // If the index is -1 it should go to the end of the tabs.
+ if (insertionPoint == -1) {
+ insertionPoint = gBrowser.tabs.length;
+ }
+
+ // We can only move pinned tabs to a point within, or just after,
+ // the current set of pinned tabs. Unpinned tabs, likewise, can only
+ // be moved to a position after the current set of pinned tabs.
+ // Attempts to move a tab to an illegal position are ignored.
+ let numPinned = gBrowser._numPinnedTabs;
+ let ok = tab.pinned ? insertionPoint <= numPinned : insertionPoint >= numPinned;
+ if (!ok) {
+ continue;
+ }
+
+ indexMap.set(window, insertionPoint + 1);
+
+ if (tab.ownerGlobal != window) {
+ // If the window we are moving the tab in is different, then move the tab
+ // to the new window.
+ tab = gBrowser.adoptTab(tab, insertionPoint, false);
+ } else {
+ // If the window we are moving is the same, just move the tab.
+ gBrowser.moveTabTo(tab, insertionPoint);
+ }
+ tabsMoved.push(tab);
+ }
+
+ return Promise.resolve(tabsMoved.map(tab => TabManager.convert(extension, tab)));
+ },
+
+ duplicate: function(tabId) {
+ let tab = TabManager.getTab(tabId, context);
+
+ let gBrowser = tab.ownerGlobal.gBrowser;
+ let newTab = gBrowser.duplicateTab(tab);
+
+ return new Promise(resolve => {
+ // We need to use SSTabRestoring because any attributes set before
+ // are ignored. SSTabRestored is too late and results in a jump in
+ // the UI. See http://bit.ly/session-store-api for more information.
+ newTab.addEventListener("SSTabRestoring", function listener() {
+ // As the tab is restoring, move it to the correct position.
+ newTab.removeEventListener("SSTabRestoring", listener);
+ // Pinned tabs that are duplicated are inserted
+ // after the existing pinned tab and pinned.
+ if (tab.pinned) {
+ gBrowser.pinTab(newTab);
+ }
+ gBrowser.moveTabTo(newTab, tab._tPos + 1);
+ });
+
+ newTab.addEventListener("SSTabRestored", function listener() {
+ // Once it has been restored, select it and return the promise.
+ newTab.removeEventListener("SSTabRestored", listener);
+ gBrowser.selectedTab = newTab;
+ return resolve(TabManager.convert(extension, newTab));
+ });
+ });
+ },
+
+ getZoom(tabId) {
+ let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ let {ZoomManager} = tab.ownerGlobal;
+ let zoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+
+ return Promise.resolve(zoom);
+ },
+
+ setZoom(tabId, zoom) {
+ let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ let {FullZoom, ZoomManager} = tab.ownerGlobal;
+
+ if (zoom === 0) {
+ // A value of zero means use the default zoom factor.
+ return FullZoom.reset(tab.linkedBrowser);
+ } else if (zoom >= ZoomManager.MIN && zoom <= ZoomManager.MAX) {
+ FullZoom.setZoom(zoom, tab.linkedBrowser);
+ } else {
+ return Promise.reject({
+ message: `Zoom value ${zoom} out of range (must be between ${ZoomManager.MIN} and ${ZoomManager.MAX})`,
+ });
+ }
+
+ return Promise.resolve();
+ },
+
+ _getZoomSettings(tabId) {
+ let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ let {FullZoom} = tab.ownerGlobal;
+
+ return {
+ mode: "automatic",
+ scope: FullZoom.siteSpecific ? "per-origin" : "per-tab",
+ defaultZoomFactor: 1,
+ };
+ },
+
+ getZoomSettings(tabId) {
+ return Promise.resolve(this._getZoomSettings(tabId));
+ },
+
+ setZoomSettings(tabId, settings) {
+ let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+
+ let currentSettings = this._getZoomSettings(tab.id);
+
+ if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
+ return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
+ }
+ return Promise.resolve();
+ },
+
+ onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
+ let getZoomLevel = browser => {
+ let {ZoomManager} = browser.ownerGlobal;
+
+ return ZoomManager.getZoomForBrowser(browser);
+ };
+
+ // Stores the last known zoom level for each tab's browser.
+ // WeakMap[<browser> -> number]
+ let zoomLevels = new WeakMap();
+
+ // Store the zoom level for all existing tabs.
+ for (let window of WindowListManager.browserWindows()) {
+ for (let tab of window.gBrowser.tabs) {
+ let browser = tab.linkedBrowser;
+ zoomLevels.set(browser, getZoomLevel(browser));
+ }
+ }
+
+ let tabCreated = (eventName, event) => {
+ let browser = event.tab.linkedBrowser;
+ zoomLevels.set(browser, getZoomLevel(browser));
+ };
+
+
+ let zoomListener = event => {
+ let browser = event.originalTarget;
+
+ // For non-remote browsers, this event is dispatched on the document
+ // rather than on the <browser>.
+ if (browser instanceof Ci.nsIDOMDocument) {
+ browser = browser.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ }
+
+ let {gBrowser} = browser.ownerGlobal;
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (!tab) {
+ // We only care about zoom events in the top-level browser of a tab.
+ return;
+ }
+
+ let oldZoomFactor = zoomLevels.get(browser);
+ let newZoomFactor = getZoomLevel(browser);
+
+ if (oldZoomFactor != newZoomFactor) {
+ zoomLevels.set(browser, newZoomFactor);
+
+ let tabId = TabManager.getId(tab);
+ fire({
+ tabId,
+ oldZoomFactor,
+ newZoomFactor,
+ zoomSettings: self.tabs._getZoomSettings(tabId),
+ });
+ }
+ };
+
+ tabListener.on("tab-attached", tabCreated);
+ tabListener.on("tab-created", tabCreated);
+
+ AllWindowEvents.addListener("FullZoomChange", zoomListener);
+ AllWindowEvents.addListener("TextZoomChange", zoomListener);
+ return () => {
+ tabListener.off("tab-attached", tabCreated);
+ tabListener.off("tab-created", tabCreated);
+
+ AllWindowEvents.removeListener("FullZoomChange", zoomListener);
+ AllWindowEvents.removeListener("TextZoomChange", zoomListener);
+ };
+ }).api(),
+ },
+ };
+ return self;
+});
diff --git a/browser/components/extensions/ext-utils.js b/browser/components/extensions/ext-utils.js
new file mode 100644
index 000000000..57c38a339
--- /dev/null
+++ b/browser/components/extensions/ext-utils.js
@@ -0,0 +1,1243 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+ "resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
+ "@mozilla.org/content/style-sheet-service;1",
+ "nsIStyleSheetService");
+
+XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
+ return require("devtools/shared/css/color").colorUtils;
+});
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+const POPUP_LOAD_TIMEOUT_MS = 200;
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+var {
+ DefaultWeakMap,
+ EventManager,
+ promiseEvent,
+} = ExtensionUtils;
+
+// This file provides some useful code for the |tabs| and |windows|
+// modules. All of the code is installed on |global|, which is a scope
+// shared among the different ext-*.js scripts.
+
+global.makeWidgetId = id => {
+ id = id.toLowerCase();
+ // FIXME: This allows for collisions.
+ return id.replace(/[^a-z0-9_-]/g, "_");
+};
+
+function promisePopupShown(popup) {
+ return new Promise(resolve => {
+ if (popup.state == "open") {
+ resolve();
+ } else {
+ popup.addEventListener("popupshown", function onPopupShown(event) {
+ popup.removeEventListener("popupshown", onPopupShown);
+ resolve();
+ });
+ }
+ });
+}
+
+XPCOMUtils.defineLazyGetter(this, "popupStylesheets", () => {
+ let stylesheets = ["chrome://browser/content/extension.css"];
+
+ if (AppConstants.platform === "macosx") {
+ stylesheets.push("chrome://browser/content/extension-mac.css");
+ }
+ return stylesheets;
+});
+
+XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => {
+ let stylesheets = [];
+
+ if (AppConstants.platform === "macosx") {
+ stylesheets.push("chrome://browser/content/extension-mac-panel.css");
+ }
+ if (AppConstants.platform === "win") {
+ stylesheets.push("chrome://browser/content/extension-win-panel.css");
+ }
+ return stylesheets;
+});
+
+class BasePopup {
+ constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false) {
+ this.extension = extension;
+ this.popupURL = popupURL;
+ this.viewNode = viewNode;
+ this.browserStyle = browserStyle;
+ this.window = viewNode.ownerGlobal;
+ this.destroyed = false;
+ this.fixedWidth = fixedWidth;
+
+ extension.callOnClose(this);
+
+ this.contentReady = new Promise(resolve => {
+ this._resolveContentReady = resolve;
+ });
+
+ this.viewNode.addEventListener(this.DESTROY_EVENT, this);
+
+ let doc = viewNode.ownerDocument;
+ let arrowContent = doc.getAnonymousElementByAttribute(this.panel, "class", "panel-arrowcontent");
+ this.borderColor = doc.defaultView.getComputedStyle(arrowContent).borderTopColor;
+
+ this.browser = null;
+ this.browserLoaded = new Promise((resolve, reject) => {
+ this.browserLoadedDeferred = {resolve, reject};
+ });
+ this.browserReady = this.createBrowser(viewNode, popupURL);
+
+ BasePopup.instances.get(this.window).set(extension, this);
+ }
+
+ static for(extension, window) {
+ return BasePopup.instances.get(window).get(extension);
+ }
+
+ close() {
+ this.closePopup();
+ }
+
+ destroy() {
+ this.extension.forgetOnClose(this);
+
+ this.destroyed = true;
+ this.browserLoadedDeferred.reject(new Error("Popup destroyed"));
+ return this.browserReady.then(() => {
+ this.destroyBrowser(this.browser);
+ this.browser.remove();
+
+ this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
+ this.viewNode.style.maxHeight = "";
+
+ if (this.panel) {
+ this.panel.style.removeProperty("--arrowpanel-background");
+ this.panel.style.removeProperty("--panel-arrow-image-vertical");
+ }
+
+ BasePopup.instances.get(this.window).delete(this.extension);
+
+ this.browser = null;
+ this.viewNode = null;
+ });
+ }
+
+ destroyBrowser(browser) {
+ let mm = browser.messageManager;
+ // If the browser has already been removed from the document, because the
+ // popup was closed externally, there will be no message manager here.
+ if (mm) {
+ mm.removeMessageListener("DOMTitleChanged", this);
+ mm.removeMessageListener("Extension:BrowserBackgroundChanged", this);
+ mm.removeMessageListener("Extension:BrowserContentLoaded", this);
+ mm.removeMessageListener("Extension:BrowserResized", this);
+ mm.removeMessageListener("Extension:DOMWindowClose", this);
+ }
+ }
+
+ // Returns the name of the event fired on `viewNode` when the popup is being
+ // destroyed. This must be implemented by every subclass.
+ get DESTROY_EVENT() {
+ throw new Error("Not implemented");
+ }
+
+ get STYLESHEETS() {
+ let sheets = [];
+
+ if (this.browserStyle) {
+ sheets.push(...popupStylesheets);
+ }
+ if (!this.fixedWidth) {
+ sheets.push(...standaloneStylesheets);
+ }
+
+ return sheets;
+ }
+
+ get panel() {
+ let panel = this.viewNode;
+ while (panel && panel.localName != "panel") {
+ panel = panel.parentNode;
+ }
+ return panel;
+ }
+
+ receiveMessage({name, data}) {
+ switch (name) {
+ case "DOMTitleChanged":
+ this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
+ break;
+
+ case "Extension:BrowserBackgroundChanged":
+ this.setBackground(data.background);
+ break;
+
+ case "Extension:BrowserContentLoaded":
+ this.browserLoadedDeferred.resolve();
+ break;
+
+ case "Extension:BrowserResized":
+ this._resolveContentReady();
+ if (this.ignoreResizes) {
+ this.dimensions = data;
+ } else {
+ this.resizeBrowser(data);
+ }
+ break;
+
+ case "Extension:DOMWindowClose":
+ this.closePopup();
+ break;
+ }
+ }
+
+ handleEvent(event) {
+ switch (event.type) {
+ case this.DESTROY_EVENT:
+ this.destroy();
+ break;
+ }
+ }
+
+ createBrowser(viewNode, popupURL = null) {
+ let document = viewNode.ownerDocument;
+ this.browser = document.createElementNS(XUL_NS, "browser");
+ this.browser.setAttribute("type", "content");
+ this.browser.setAttribute("disableglobalhistory", "true");
+ this.browser.setAttribute("transparent", "true");
+ this.browser.setAttribute("class", "webextension-popup-browser");
+ this.browser.setAttribute("tooltip", "aHTMLTooltip");
+
+ // We only need flex sizing for the sake of the slide-in sub-views of the
+ // main menu panel, so that the browser occupies the full width of the view,
+ // and also takes up any extra height that's available to it.
+ this.browser.setAttribute("flex", "1");
+
+ // Note: When using noautohide panels, the popup manager will add width and
+ // height attributes to the panel, breaking our resize code, if the browser
+ // starts out smaller than 30px by 10px. This isn't an issue now, but it
+ // will be if and when we popup debugging.
+
+ viewNode.appendChild(this.browser);
+
+ extensions.emit("extension-browser-inserted", this.browser);
+ let windowId = WindowManager.getId(this.browser.ownerGlobal);
+ this.browser.messageManager.sendAsyncMessage("Extension:InitExtensionView", {
+ viewType: "popup",
+ windowId,
+ });
+ // TODO(robwu): Rework this to use the Extension:ExtensionViewLoaded message
+ // to detect loads and so on. And definitely move this content logic inside
+ // a file in the child process.
+
+ let initBrowser = browser => {
+ let mm = browser.messageManager;
+ mm.addMessageListener("DOMTitleChanged", this);
+ mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
+ mm.addMessageListener("Extension:BrowserContentLoaded", this);
+ mm.addMessageListener("Extension:BrowserResized", this);
+ mm.addMessageListener("Extension:DOMWindowClose", this, true);
+ };
+
+ if (!popupURL) {
+ initBrowser(this.browser);
+ return this.browser;
+ }
+
+ return promiseEvent(this.browser, "load").then(() => {
+ initBrowser(this.browser);
+
+ let mm = this.browser.messageManager;
+
+ mm.loadFrameScript(
+ "chrome://extensions/content/ext-browser-content.js", false);
+
+ mm.sendAsyncMessage("Extension:InitBrowser", {
+ allowScriptsToClose: true,
+ fixedWidth: this.fixedWidth,
+ maxWidth: 800,
+ maxHeight: 600,
+ stylesheets: this.STYLESHEETS,
+ });
+
+ this.browser.setAttribute("src", popupURL);
+ });
+ }
+
+ resizeBrowser({width, height, detail}) {
+ if (this.fixedWidth) {
+ // Figure out how much extra space we have on the side of the panel
+ // opposite the arrow.
+ let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
+ let maxHeight = this.viewHeight + this.extraHeight[side];
+
+ height = Math.min(height, maxHeight);
+ this.browser.style.height = `${height}px`;
+
+ // Set a maximum height on the <panelview> element to our preferred
+ // maximum height, so that the PanelUI resizing code can make an accurate
+ // calculation. If we don't do this, the flex sizing logic will prevent us
+ // from ever reporting a preferred size smaller than the height currently
+ // available to us in the panel.
+ height = Math.max(height, this.viewHeight);
+ this.viewNode.style.maxHeight = `${height}px`;
+ } else {
+ this.browser.style.width = `${width}px`;
+ this.browser.style.height = `${height}px`;
+ }
+
+ let event = new this.window.CustomEvent("WebExtPopupResized", {detail});
+ this.browser.dispatchEvent(event);
+ }
+
+ setBackground(background) {
+ let panelBackground = "";
+ let panelArrow = "";
+
+ if (background) {
+ let borderColor = this.borderColor || background;
+
+ panelBackground = background;
+ panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`<?xml version="1.0" encoding="UTF-8"?>
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
+ <path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
+ <path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
+ </svg>
+ `)}")`;
+ }
+
+ this.panel.style.setProperty("--arrowpanel-background", panelBackground);
+ this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
+ this.background = background;
+ }
+}
+
+/**
+ * A map of active popups for a given browser window.
+ *
+ * WeakMap[window -> WeakMap[Extension -> BasePopup]]
+ */
+BasePopup.instances = new DefaultWeakMap(() => new WeakMap());
+
+class PanelPopup extends BasePopup {
+ constructor(extension, imageNode, popupURL, browserStyle) {
+ let document = imageNode.ownerDocument;
+
+ let panel = document.createElement("panel");
+ panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
+ panel.setAttribute("class", "browser-extension-panel");
+ panel.setAttribute("tabspecific", "true");
+ panel.setAttribute("type", "arrow");
+ panel.setAttribute("role", "group");
+
+ document.getElementById("mainPopupSet").appendChild(panel);
+
+ super(extension, panel, popupURL, browserStyle);
+
+ this.contentReady.then(() => {
+ panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
+
+ let event = new this.window.CustomEvent("WebExtPopupLoaded", {
+ bubbles: true,
+ detail: {extension},
+ });
+ this.browser.dispatchEvent(event);
+ });
+ }
+
+ get DESTROY_EVENT() {
+ return "popuphidden";
+ }
+
+ destroy() {
+ super.destroy();
+ this.viewNode.remove();
+ }
+
+ closePopup() {
+ promisePopupShown(this.viewNode).then(() => {
+ // Make sure we're not already destroyed.
+ if (this.viewNode) {
+ this.viewNode.hidePopup();
+ }
+ });
+ }
+}
+
+class ViewPopup extends BasePopup {
+ constructor(extension, window, popupURL, browserStyle, fixedWidth) {
+ let document = window.document;
+
+ // Create a temporary panel to hold the browser while it pre-loads its
+ // content. This panel will never be shown, but the browser's docShell will
+ // be swapped with the browser in the real panel when it's ready.
+ let panel = document.createElement("panel");
+ panel.setAttribute("type", "arrow");
+ document.getElementById("mainPopupSet").appendChild(panel);
+
+ super(extension, panel, popupURL, browserStyle, fixedWidth);
+
+ this.ignoreResizes = true;
+
+ this.attached = false;
+ this.tempPanel = panel;
+
+ this.browser.classList.add("webextension-preload-browser");
+ }
+
+ /**
+ * Attaches the pre-loaded browser to the given view node, and reserves a
+ * promise which resolves when the browser is ready.
+ *
+ * @param {Element} viewNode
+ * The node to attach the browser to.
+ * @returns {Promise<boolean>}
+ * Resolves when the browser is ready. Resolves to `false` if the
+ * browser was destroyed before it was fully loaded, and the popup
+ * should be closed, or `true` otherwise.
+ */
+ attach(viewNode) {
+ return Task.spawn(function* () {
+ this.viewNode = viewNode;
+ this.viewNode.addEventListener(this.DESTROY_EVENT, this);
+
+ // Wait until the browser element is fully initialized, and give it at least
+ // a short grace period to finish loading its initial content, if necessary.
+ //
+ // In practice, the browser that was created by the mousdown handler should
+ // nearly always be ready by this point.
+ yield Promise.all([
+ this.browserReady,
+ Promise.race([
+ // This promise may be rejected if the popup calls window.close()
+ // before it has fully loaded.
+ this.browserLoaded.catch(() => {}),
+ new Promise(resolve => setTimeout(resolve, POPUP_LOAD_TIMEOUT_MS)),
+ ]),
+ ]);
+
+ if (!this.destroyed && !this.panel) {
+ this.destroy();
+ }
+
+ if (this.destroyed) {
+ return false;
+ }
+
+ this.attached = true;
+
+ // Store the initial height of the view, so that we never resize menu panel
+ // sub-views smaller than the initial height of the menu.
+ this.viewHeight = this.viewNode.boxObject.height;
+
+ // Calculate the extra height available on the screen above and below the
+ // menu panel. Use that to calculate the how much the sub-view may grow.
+ let popupRect = this.panel.getBoundingClientRect();
+
+ this.setBackground(this.background);
+
+ let win = this.window;
+ let popupBottom = win.mozInnerScreenY + popupRect.bottom;
+ let popupTop = win.mozInnerScreenY + popupRect.top;
+
+ let screenBottom = win.screen.availTop + win.screen.availHeight;
+ this.extraHeight = {
+ bottom: Math.max(0, screenBottom - popupBottom),
+ top: Math.max(0, popupTop - win.screen.availTop),
+ };
+
+ // Create a new browser in the real popup.
+ let browser = this.browser;
+ this.createBrowser(this.viewNode);
+
+ this.browser.swapDocShells(browser);
+ this.destroyBrowser(browser);
+
+ this.ignoreResizes = false;
+ if (this.dimensions) {
+ this.resizeBrowser(this.dimensions);
+ }
+
+ this.tempPanel.remove();
+ this.tempPanel = null;
+
+ let event = new this.window.CustomEvent("WebExtPopupLoaded", {
+ bubbles: true,
+ detail: {extension: this.extension},
+ });
+ this.browser.dispatchEvent(event);
+
+ return true;
+ }.bind(this));
+ }
+
+ destroy() {
+ return super.destroy().then(() => {
+ if (this.tempPanel) {
+ this.tempPanel.remove();
+ this.tempPanel = null;
+ }
+ });
+ }
+
+ get DESTROY_EVENT() {
+ return "ViewHiding";
+ }
+
+ closePopup() {
+ if (this.attached) {
+ CustomizableUI.hidePanelForNode(this.viewNode);
+ } else {
+ this.destroy();
+ }
+ }
+}
+
+Object.assign(global, {PanelPopup, ViewPopup});
+
+// Manages tab-specific context data, and dispatching tab select events
+// across all windows.
+global.TabContext = function TabContext(getDefaults, extension) {
+ this.extension = extension;
+ this.getDefaults = getDefaults;
+
+ this.tabData = new WeakMap();
+ this.lastLocation = new WeakMap();
+
+ AllWindowEvents.addListener("progress", this);
+ AllWindowEvents.addListener("TabSelect", this);
+
+ EventEmitter.decorate(this);
+};
+
+TabContext.prototype = {
+ get(tab) {
+ if (!this.tabData.has(tab)) {
+ this.tabData.set(tab, this.getDefaults(tab));
+ }
+
+ return this.tabData.get(tab);
+ },
+
+ clear(tab) {
+ this.tabData.delete(tab);
+ },
+
+ handleEvent(event) {
+ if (event.type == "TabSelect") {
+ let tab = event.target;
+ this.emit("tab-select", tab);
+ this.emit("location-change", tab);
+ }
+ },
+
+ onStateChange(browser, webProgress, request, stateFlags, statusCode) {
+ let flags = Ci.nsIWebProgressListener;
+
+ if (!(~stateFlags & (flags.STATE_IS_WINDOW | flags.STATE_START) ||
+ this.lastLocation.has(browser))) {
+ this.lastLocation.set(browser, request.URI);
+ }
+ },
+
+ onLocationChange(browser, webProgress, request, locationURI, flags) {
+ let gBrowser = browser.ownerGlobal.gBrowser;
+ let lastLocation = this.lastLocation.get(browser);
+ if (browser === gBrowser.selectedBrowser &&
+ !(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ this.emit("location-change", tab, true);
+ }
+ this.lastLocation.set(browser, browser.currentURI);
+ },
+
+ shutdown() {
+ AllWindowEvents.removeListener("progress", this);
+ AllWindowEvents.removeListener("TabSelect", this);
+ },
+};
+
+// Manages tab mappings and permissions for a specific extension.
+function ExtensionTabManager(extension) {
+ this.extension = extension;
+
+ // A mapping of tab objects to the inner window ID the extension currently has
+ // the active tab permission for. The active permission for a given tab is
+ // valid only for the inner window that was active when the permission was
+ // granted. If the tab navigates, the inner window ID changes, and the
+ // permission automatically becomes stale.
+ //
+ // WeakMap[tab => inner-window-id<int>]
+ this.hasTabPermissionFor = new WeakMap();
+}
+
+ExtensionTabManager.prototype = {
+ addActiveTabPermission(tab = TabManager.activeTab) {
+ if (this.extension.hasPermission("activeTab")) {
+ // Note that, unlike Chrome, we don't currently clear this permission with
+ // the tab navigates. If the inner window is revived from BFCache before
+ // we've granted this permission to a new inner window, the extension
+ // maintains its permissions for it.
+ this.hasTabPermissionFor.set(tab, tab.linkedBrowser.innerWindowID);
+ }
+ },
+
+ revokeActiveTabPermission(tab = TabManager.activeTab) {
+ this.hasTabPermissionFor.delete(tab);
+ },
+
+ // Returns true if the extension has the "activeTab" permission for this tab.
+ // This is somewhat more permissive than the generic "tabs" permission, as
+ // checked by |hasTabPermission|, in that it also allows programmatic script
+ // injection without an explicit host permission.
+ hasActiveTabPermission(tab) {
+ // This check is redundant with addTabPermission, but cheap.
+ if (this.extension.hasPermission("activeTab")) {
+ return (this.hasTabPermissionFor.has(tab) &&
+ this.hasTabPermissionFor.get(tab) === tab.linkedBrowser.innerWindowID);
+ }
+ return false;
+ },
+
+ hasTabPermission(tab) {
+ return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
+ },
+
+ convert(tab) {
+ let window = tab.ownerGlobal;
+ let browser = tab.linkedBrowser;
+
+ let mutedInfo = {muted: tab.muted};
+ if (tab.muteReason === null) {
+ mutedInfo.reason = "user";
+ } else if (tab.muteReason) {
+ mutedInfo.reason = "extension";
+ mutedInfo.extensionId = tab.muteReason;
+ }
+
+ let result = {
+ id: TabManager.getId(tab),
+ index: tab._tPos,
+ windowId: WindowManager.getId(window),
+ selected: tab.selected,
+ highlighted: tab.selected,
+ active: tab.selected,
+ pinned: tab.pinned,
+ status: TabManager.getStatus(tab),
+ incognito: WindowManager.isBrowserPrivate(browser),
+ width: browser.frameLoader.lazyWidth || browser.clientWidth,
+ height: browser.frameLoader.lazyHeight || browser.clientHeight,
+ audible: tab.soundPlaying,
+ mutedInfo,
+ };
+ if (this.extension.hasPermission("cookies")) {
+ result.cookieStoreId = getCookieStoreIdForTab(result, tab);
+ }
+
+ if (this.hasTabPermission(tab)) {
+ result.url = browser.currentURI.spec;
+ let title = browser.contentTitle || tab.label;
+ if (title) {
+ result.title = title;
+ }
+ let icon = window.gBrowser.getIcon(tab);
+ if (icon) {
+ result.favIconUrl = icon;
+ }
+ }
+
+ return result;
+ },
+
+ // Converts tabs returned from SessionStore.getClosedTabData and
+ // SessionStore.getClosedWindowData into API tab objects
+ convertFromSessionStoreClosedData(tab, window) {
+ let result = {
+ sessionId: String(tab.closedId),
+ index: tab.pos ? tab.pos : 0,
+ windowId: WindowManager.getId(window),
+ selected: false,
+ highlighted: false,
+ active: false,
+ pinned: false,
+ incognito: Boolean(tab.state && tab.state.isPrivate),
+ };
+
+ if (this.hasTabPermission(tab)) {
+ let entries = tab.state ? tab.state.entries : tab.entries;
+ result.url = entries[0].url;
+ result.title = entries[0].title;
+ if (tab.image) {
+ result.favIconUrl = tab.image;
+ }
+ }
+
+ return result;
+ },
+
+ getTabs(window) {
+ return Array.from(window.gBrowser.tabs)
+ .filter(tab => !tab.closing)
+ .map(tab => this.convert(tab));
+ },
+};
+
+// Sends the tab and windowId upon request. This is primarily used to support
+// the synchronous `browser.extension.getViews` API.
+let onGetTabAndWindowId = {
+ receiveMessage({name, target, sync}) {
+ let {gBrowser} = target.ownerGlobal;
+ let tab = gBrowser && gBrowser.getTabForBrowser(target);
+ if (tab) {
+ let reply = {
+ tabId: TabManager.getId(tab),
+ windowId: WindowManager.getId(tab.ownerGlobal),
+ };
+ if (sync) {
+ return reply;
+ }
+ target.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", reply);
+ }
+ },
+};
+/* eslint-disable mozilla/balanced-listeners */
+Services.mm.addMessageListener("Extension:GetTabAndWindowId", onGetTabAndWindowId);
+/* eslint-enable mozilla/balanced-listeners */
+
+
+// Manages global mappings between XUL tabs and extension tab IDs.
+global.TabManager = {
+ _tabs: new WeakMap(),
+ _nextId: 1,
+ _initialized: false,
+
+ // We begin listening for TabOpen and TabClose events once we've started
+ // assigning IDs to tabs, so that we can remap the IDs of tabs which are moved
+ // between windows.
+ initListener() {
+ if (this._initialized) {
+ return;
+ }
+
+ AllWindowEvents.addListener("TabOpen", this);
+ AllWindowEvents.addListener("TabClose", this);
+ WindowListManager.addOpenListener(this.handleWindowOpen.bind(this));
+
+ this._initialized = true;
+ },
+
+ handleEvent(event) {
+ if (event.type == "TabOpen") {
+ let {adoptedTab} = event.detail;
+ if (adoptedTab) {
+ // This tab is being created to adopt a tab from a different window.
+ // Copy the ID from the old tab to the new.
+ let tab = event.target;
+ this._tabs.set(tab, this.getId(adoptedTab));
+
+ tab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
+ windowId: WindowManager.getId(tab.ownerGlobal),
+ });
+ }
+ } else if (event.type == "TabClose") {
+ let {adoptedBy} = event.detail;
+ if (adoptedBy) {
+ // This tab is being closed because it was adopted by a new window.
+ // Copy its ID to the new tab, in case it was created as the first tab
+ // of a new window, and did not have an `adoptedTab` detail when it was
+ // opened.
+ this._tabs.set(adoptedBy, this.getId(event.target));
+
+ adoptedBy.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
+ windowId: WindowManager.getId(adoptedBy),
+ });
+ }
+ }
+ },
+
+ handleWindowOpen(window) {
+ if (window.arguments && window.arguments[0] instanceof window.XULElement) {
+ // If the first window argument is a XUL element, it means the
+ // window is about to adopt a tab from another window to replace its
+ // initial tab.
+ let adoptedTab = window.arguments[0];
+
+ this._tabs.set(window.gBrowser.tabs[0], this.getId(adoptedTab));
+ }
+ },
+
+ getId(tab) {
+ if (this._tabs.has(tab)) {
+ return this._tabs.get(tab);
+ }
+ this.initListener();
+
+ let id = this._nextId++;
+ this._tabs.set(tab, id);
+ return id;
+ },
+
+ getBrowserId(browser) {
+ let gBrowser = browser.ownerGlobal.gBrowser;
+ // Some non-browser windows have gBrowser but not
+ // getTabForBrowser!
+ if (gBrowser && gBrowser.getTabForBrowser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (tab) {
+ return this.getId(tab);
+ }
+ }
+ return -1;
+ },
+
+ /**
+ * Returns the XUL <tab> element associated with the given tab ID. If no tab
+ * with the given ID exists, and no default value is provided, an error is
+ * raised, belonging to the scope of the given context.
+ *
+ * @param {integer} tabId
+ * The ID of the tab to retrieve.
+ * @param {ExtensionContext} context
+ * The context of the caller.
+ * This value may be omitted if `default_` is not `undefined`.
+ * @param {*} default_
+ * The value to return if no tab exists with the given ID.
+ * @returns {Element<tab>}
+ * A XUL <tab> element.
+ */
+ getTab(tabId, context, default_ = undefined) {
+ // FIXME: Speed this up without leaking memory somehow.
+ for (let window of WindowListManager.browserWindows()) {
+ if (!window.gBrowser) {
+ continue;
+ }
+ for (let tab of window.gBrowser.tabs) {
+ if (this.getId(tab) == tabId) {
+ return tab;
+ }
+ }
+ }
+ if (default_ !== undefined) {
+ return default_;
+ }
+ throw new context.cloneScope.Error(`Invalid tab ID: ${tabId}`);
+ },
+
+ get activeTab() {
+ let window = WindowManager.topWindow;
+ if (window && window.gBrowser) {
+ return window.gBrowser.selectedTab;
+ }
+ return null;
+ },
+
+ getStatus(tab) {
+ return tab.getAttribute("busy") == "true" ? "loading" : "complete";
+ },
+
+ convert(extension, tab) {
+ return TabManager.for(extension).convert(tab);
+ },
+};
+
+// WeakMap[Extension -> ExtensionTabManager]
+let tabManagers = new WeakMap();
+
+// Returns the extension-specific tab manager for the given extension, or
+// creates one if it doesn't already exist.
+TabManager.for = function(extension) {
+ if (!tabManagers.has(extension)) {
+ tabManagers.set(extension, new ExtensionTabManager(extension));
+ }
+ return tabManagers.get(extension);
+};
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("shutdown", (type, extension) => {
+ tabManagers.delete(extension);
+});
+/* eslint-enable mozilla/balanced-listeners */
+
+function memoize(fn) {
+ let weakMap = new DefaultWeakMap(fn);
+ return weakMap.get.bind(weakMap);
+}
+
+// Manages mapping between XUL windows and extension window IDs.
+global.WindowManager = {
+ _windows: new WeakMap(),
+ _nextId: 0,
+
+ // Note: These must match the values in windows.json.
+ WINDOW_ID_NONE: -1,
+ WINDOW_ID_CURRENT: -2,
+
+ get topWindow() {
+ return Services.wm.getMostRecentWindow("navigator:browser");
+ },
+
+ windowType(window) {
+ // TODO: Make this work.
+
+ let {chromeFlags} = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow);
+
+ if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
+ return "popup";
+ }
+
+ return "normal";
+ },
+
+ updateGeometry(window, options) {
+ if (options.left !== null || options.top !== null) {
+ let left = options.left !== null ? options.left : window.screenX;
+ let top = options.top !== null ? options.top : window.screenY;
+ window.moveTo(left, top);
+ }
+
+ if (options.width !== null || options.height !== null) {
+ let width = options.width !== null ? options.width : window.outerWidth;
+ let height = options.height !== null ? options.height : window.outerHeight;
+ window.resizeTo(width, height);
+ }
+ },
+
+ isBrowserPrivate: memoize(browser => {
+ return PrivateBrowsingUtils.isBrowserPrivate(browser);
+ }),
+
+ getId(window) {
+ if (this._windows.has(window)) {
+ return this._windows.get(window);
+ }
+ let id = this._nextId++;
+ this._windows.set(window, id);
+ return id;
+ },
+
+ getWindow(id, context) {
+ if (id == this.WINDOW_ID_CURRENT) {
+ return currentWindow(context);
+ }
+
+ for (let window of WindowListManager.browserWindows(true)) {
+ if (this.getId(window) == id) {
+ return window;
+ }
+ }
+ return null;
+ },
+
+ getState(window) {
+ const STATES = {
+ [window.STATE_MAXIMIZED]: "maximized",
+ [window.STATE_MINIMIZED]: "minimized",
+ [window.STATE_NORMAL]: "normal",
+ };
+ let state = STATES[window.windowState];
+ if (window.fullScreen) {
+ state = "fullscreen";
+ }
+ return state;
+ },
+
+ setState(window, state) {
+ if (state != "fullscreen" && window.fullScreen) {
+ window.fullScreen = false;
+ }
+
+ switch (state) {
+ case "maximized":
+ window.maximize();
+ break;
+
+ case "minimized":
+ case "docked":
+ window.minimize();
+ break;
+
+ case "normal":
+ // Restore sometimes returns the window to its previous state, rather
+ // than to the "normal" state, so it may need to be called anywhere from
+ // zero to two times.
+ window.restore();
+ if (window.windowState != window.STATE_NORMAL) {
+ window.restore();
+ }
+ if (window.windowState != window.STATE_NORMAL) {
+ // And on OS-X, where normal vs. maximized is basically a heuristic,
+ // we need to cheat.
+ window.sizeToContent();
+ }
+ break;
+
+ case "fullscreen":
+ window.fullScreen = true;
+ break;
+
+ default:
+ throw new Error(`Unexpected window state: ${state}`);
+ }
+ },
+
+ convert(extension, window, getInfo) {
+ let xulWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow);
+
+ let result = {
+ id: this.getId(window),
+ focused: window.document.hasFocus(),
+ top: window.screenY,
+ left: window.screenX,
+ width: window.outerWidth,
+ height: window.outerHeight,
+ incognito: PrivateBrowsingUtils.isWindowPrivate(window),
+ type: this.windowType(window),
+ state: this.getState(window),
+ alwaysOnTop: xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ,
+ };
+
+ if (getInfo && getInfo.populate) {
+ result.tabs = TabManager.for(extension).getTabs(window);
+ }
+
+ return result;
+ },
+
+ // Converts windows returned from SessionStore.getClosedWindowData
+ // into API window objects
+ convertFromSessionStoreClosedData(window, extension) {
+ let result = {
+ sessionId: String(window.closedId),
+ focused: false,
+ incognito: false,
+ type: "normal", // this is always "normal" for a closed window
+ state: this.getState(window),
+ alwaysOnTop: false,
+ };
+
+ if (window.tabs.length) {
+ result.tabs = [];
+ window.tabs.forEach((tab, index) => {
+ result.tabs.push(TabManager.for(extension).convertFromSessionStoreClosedData(tab, window, index));
+ });
+ }
+
+ return result;
+ },
+};
+
+// Manages listeners for window opening and closing. A window is
+// considered open when the "load" event fires on it. A window is
+// closed when a "domwindowclosed" notification fires for it.
+global.WindowListManager = {
+ _openListeners: new Set(),
+ _closeListeners: new Set(),
+
+ // Returns an iterator for all browser windows. Unless |includeIncomplete| is
+ // true, only fully-loaded windows are returned.
+ * browserWindows(includeIncomplete = false) {
+ // The window type parameter is only available once the window's document
+ // element has been created. This means that, when looking for incomplete
+ // browser windows, we need to ignore the type entirely for windows which
+ // haven't finished loading, since we would otherwise skip browser windows
+ // in their early loading stages.
+ // This is particularly important given that the "domwindowcreated" event
+ // fires for browser windows when they're in that in-between state, and just
+ // before we register our own "domwindowcreated" listener.
+
+ let e = Services.wm.getEnumerator("");
+ while (e.hasMoreElements()) {
+ let window = e.getNext();
+
+ let ok = includeIncomplete;
+ if (window.document.readyState == "complete") {
+ ok = window.document.documentElement.getAttribute("windowtype") == "navigator:browser";
+ }
+
+ if (ok) {
+ yield window;
+ }
+ }
+ },
+
+ addOpenListener(listener) {
+ if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
+ Services.ww.registerNotification(this);
+ }
+ this._openListeners.add(listener);
+
+ for (let window of this.browserWindows(true)) {
+ if (window.document.readyState != "complete") {
+ window.addEventListener("load", this);
+ }
+ }
+ },
+
+ removeOpenListener(listener) {
+ this._openListeners.delete(listener);
+ if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
+ Services.ww.unregisterNotification(this);
+ }
+ },
+
+ addCloseListener(listener) {
+ if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
+ Services.ww.registerNotification(this);
+ }
+ this._closeListeners.add(listener);
+ },
+
+ removeCloseListener(listener) {
+ this._closeListeners.delete(listener);
+ if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
+ Services.ww.unregisterNotification(this);
+ }
+ },
+
+ handleEvent(event) {
+ event.currentTarget.removeEventListener(event.type, this);
+ let window = event.target.defaultView;
+ if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+ return;
+ }
+
+ for (let listener of this._openListeners) {
+ listener(window);
+ }
+ },
+
+ observe(window, topic, data) {
+ if (topic == "domwindowclosed") {
+ if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+ return;
+ }
+
+ window.removeEventListener("load", this);
+ for (let listener of this._closeListeners) {
+ listener(window);
+ }
+ } else {
+ window.addEventListener("load", this);
+ }
+ },
+};
+
+// Provides a facility to listen for DOM events across all XUL windows.
+global.AllWindowEvents = {
+ _listeners: new Map(),
+
+ // If |type| is a normal event type, invoke |listener| each time
+ // that event fires in any open window. If |type| is "progress", add
+ // a web progress listener that covers all open windows.
+ addListener(type, listener) {
+ if (type == "domwindowopened") {
+ return WindowListManager.addOpenListener(listener);
+ } else if (type == "domwindowclosed") {
+ return WindowListManager.addCloseListener(listener);
+ }
+
+ if (this._listeners.size == 0) {
+ WindowListManager.addOpenListener(this.openListener);
+ }
+
+ if (!this._listeners.has(type)) {
+ this._listeners.set(type, new Set());
+ }
+ let list = this._listeners.get(type);
+ list.add(listener);
+
+ // Register listener on all existing windows.
+ for (let window of WindowListManager.browserWindows()) {
+ this.addWindowListener(window, type, listener);
+ }
+ },
+
+ removeListener(eventType, listener) {
+ if (eventType == "domwindowopened") {
+ return WindowListManager.removeOpenListener(listener);
+ } else if (eventType == "domwindowclosed") {
+ return WindowListManager.removeCloseListener(listener);
+ }
+
+ let listeners = this._listeners.get(eventType);
+ listeners.delete(listener);
+ if (listeners.size == 0) {
+ this._listeners.delete(eventType);
+ if (this._listeners.size == 0) {
+ WindowListManager.removeOpenListener(this.openListener);
+ }
+ }
+
+ // Unregister listener from all existing windows.
+ let useCapture = eventType === "focus" || eventType === "blur";
+ for (let window of WindowListManager.browserWindows()) {
+ if (eventType == "progress") {
+ window.gBrowser.removeTabsProgressListener(listener);
+ } else {
+ window.removeEventListener(eventType, listener, useCapture);
+ }
+ }
+ },
+
+ /* eslint-disable mozilla/balanced-listeners */
+ addWindowListener(window, eventType, listener) {
+ let useCapture = eventType === "focus" || eventType === "blur";
+
+ if (eventType == "progress") {
+ window.gBrowser.addTabsProgressListener(listener);
+ } else {
+ window.addEventListener(eventType, listener, useCapture);
+ }
+ },
+ /* eslint-enable mozilla/balanced-listeners */
+
+ // Runs whenever the "load" event fires for a new window.
+ openListener(window) {
+ for (let [eventType, listeners] of AllWindowEvents._listeners) {
+ for (let listener of listeners) {
+ this.addWindowListener(window, eventType, listener);
+ }
+ }
+ },
+};
+
+AllWindowEvents.openListener = AllWindowEvents.openListener.bind(AllWindowEvents);
+
+// Subclass of EventManager where we just need to call
+// add/removeEventListener on each XUL window.
+global.WindowEventManager = function(context, name, event, listener) {
+ EventManager.call(this, context, name, fire => {
+ let listener2 = (...args) => listener(fire, ...args);
+ AllWindowEvents.addListener(event, listener2);
+ return () => {
+ AllWindowEvents.removeListener(event, listener2);
+ };
+ });
+};
+
+WindowEventManager.prototype = Object.create(EventManager.prototype);
diff --git a/browser/components/extensions/ext-windows.js b/browser/components/extensions/ext-windows.js
new file mode 100644
index 000000000..5956ae15b
--- /dev/null
+++ b/browser/components/extensions/ext-windows.js
@@ -0,0 +1,231 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+ EventManager,
+ promiseObserved,
+} = ExtensionUtils;
+
+function onXULFrameLoaderCreated({target}) {
+ target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
+}
+
+extensions.registerSchemaAPI("windows", "addon_parent", context => {
+ let {extension} = context;
+ return {
+ windows: {
+ onCreated:
+ new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
+ fire(WindowManager.convert(extension, window));
+ }).api(),
+
+ onRemoved:
+ new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
+ fire(WindowManager.getId(window));
+ }).api(),
+
+ onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
+ // Keep track of the last windowId used to fire an onFocusChanged event
+ let lastOnFocusChangedWindowId;
+
+ let listener = event => {
+ // Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
+ // event when switching focus between two Firefox windows.
+ Promise.resolve().then(() => {
+ let window = Services.focus.activeWindow;
+ let windowId = window ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
+ if (windowId !== lastOnFocusChangedWindowId) {
+ fire(windowId);
+ lastOnFocusChangedWindowId = windowId;
+ }
+ });
+ };
+ AllWindowEvents.addListener("focus", listener);
+ AllWindowEvents.addListener("blur", listener);
+ return () => {
+ AllWindowEvents.removeListener("focus", listener);
+ AllWindowEvents.removeListener("blur", listener);
+ };
+ }).api(),
+
+ get: function(windowId, getInfo) {
+ let window = WindowManager.getWindow(windowId, context);
+ return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+ },
+
+ getCurrent: function(getInfo) {
+ let window = currentWindow(context);
+ return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+ },
+
+ getLastFocused: function(getInfo) {
+ let window = WindowManager.topWindow;
+ return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+ },
+
+ getAll: function(getInfo) {
+ let windows = Array.from(WindowListManager.browserWindows(),
+ window => WindowManager.convert(extension, window, getInfo));
+ return Promise.resolve(windows);
+ },
+
+ create: function(createData) {
+ let needResize = (createData.left !== null || createData.top !== null ||
+ createData.width !== null || createData.height !== null);
+
+ if (needResize) {
+ if (createData.state !== null && createData.state != "normal") {
+ return Promise.reject({message: `"state": "${createData.state}" may not be combined with "left", "top", "width", or "height"`});
+ }
+ createData.state = "normal";
+ }
+
+ function mkstr(s) {
+ let result = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ result.data = s;
+ return result;
+ }
+
+ let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+
+ if (createData.tabId !== null) {
+ if (createData.url !== null) {
+ return Promise.reject({message: "`tabId` may not be used in conjunction with `url`"});
+ }
+
+ if (createData.allowScriptsToClose) {
+ return Promise.reject({message: "`tabId` may not be used in conjunction with `allowScriptsToClose`"});
+ }
+
+ let tab = TabManager.getTab(createData.tabId, context);
+
+ // Private browsing tabs can only be moved to private browsing
+ // windows.
+ let incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
+ if (createData.incognito !== null && createData.incognito != incognito) {
+ return Promise.reject({message: "`incognito` property must match the incognito state of tab"});
+ }
+ createData.incognito = incognito;
+
+ args.appendElement(tab, /* weak = */ false);
+ } else if (createData.url !== null) {
+ if (Array.isArray(createData.url)) {
+ let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ for (let url of createData.url) {
+ array.appendElement(mkstr(url), /* weak = */ false);
+ }
+ args.appendElement(array, /* weak = */ false);
+ } else {
+ args.appendElement(mkstr(createData.url), /* weak = */ false);
+ }
+ } else {
+ args.appendElement(mkstr(aboutNewTabService.newTabURL), /* weak = */ false);
+ }
+
+ let features = ["chrome"];
+
+ if (createData.type === null || createData.type == "normal") {
+ features.push("dialog=no", "all");
+ } else {
+ // All other types create "popup"-type windows by default.
+ features.push("dialog", "resizable", "minimizable", "centerscreen", "titlebar", "close");
+ }
+
+ if (createData.incognito !== null) {
+ if (createData.incognito) {
+ features.push("private");
+ } else {
+ features.push("non-private");
+ }
+ }
+
+ let {allowScriptsToClose, url} = createData;
+ if (allowScriptsToClose === null) {
+ allowScriptsToClose = typeof url === "string" && url.startsWith("moz-extension://");
+ }
+
+ let window = Services.ww.openWindow(null, "chrome://browser/content/browser.xul", "_blank",
+ features.join(","), args);
+
+ WindowManager.updateGeometry(window, createData);
+
+ // TODO: focused, type
+
+ return new Promise(resolve => {
+ window.addEventListener("load", function listener() {
+ window.removeEventListener("load", listener);
+ if (["maximized", "normal"].includes(createData.state)) {
+ window.document.documentElement.setAttribute("sizemode", createData.state);
+ }
+ resolve(promiseObserved("browser-delayed-startup-finished", win => win == window));
+ });
+ }).then(() => {
+ // Some states only work after delayed-startup-finished
+ if (["minimized", "fullscreen", "docked"].includes(createData.state)) {
+ WindowManager.setState(window, createData.state);
+ }
+ if (allowScriptsToClose) {
+ for (let {linkedBrowser} of window.gBrowser.tabs) {
+ onXULFrameLoaderCreated({target: linkedBrowser});
+ linkedBrowser.addEventListener( // eslint-disable-line mozilla/balanced-listeners
+ "XULFrameLoaderCreated", onXULFrameLoaderCreated);
+ }
+ }
+ return WindowManager.convert(extension, window, {populate: true});
+ });
+ },
+
+ update: function(windowId, updateInfo) {
+ if (updateInfo.state !== null && updateInfo.state != "normal") {
+ if (updateInfo.left !== null || updateInfo.top !== null ||
+ updateInfo.width !== null || updateInfo.height !== null) {
+ return Promise.reject({message: `"state": "${updateInfo.state}" may not be combined with "left", "top", "width", or "height"`});
+ }
+ }
+
+ let window = WindowManager.getWindow(windowId, context);
+ if (updateInfo.focused) {
+ Services.focus.activeWindow = window;
+ }
+
+ if (updateInfo.state !== null) {
+ WindowManager.setState(window, updateInfo.state);
+ }
+
+ if (updateInfo.drawAttention) {
+ // Bug 1257497 - Firefox can't cancel attention actions.
+ window.getAttention();
+ }
+
+ WindowManager.updateGeometry(window, updateInfo);
+
+ // TODO: All the other properties, focused=false...
+
+ return Promise.resolve(WindowManager.convert(extension, window));
+ },
+
+ remove: function(windowId) {
+ let window = WindowManager.getWindow(windowId, context);
+ window.close();
+
+ return new Promise(resolve => {
+ let listener = () => {
+ AllWindowEvents.removeListener("domwindowclosed", listener);
+ resolve();
+ };
+ AllWindowEvents.addListener("domwindowclosed", listener);
+ });
+ },
+ },
+ };
+});
diff --git a/browser/components/extensions/extension-mac-panel.css b/browser/components/extensions/extension-mac-panel.css
new file mode 100644
index 000000000..2e9ed6bdb
--- /dev/null
+++ b/browser/components/extensions/extension-mac-panel.css
@@ -0,0 +1,3 @@
+body {
+ border-radius: 3.5px;
+}
diff --git a/browser/components/extensions/extension-mac.css b/browser/components/extensions/extension-mac.css
new file mode 100644
index 000000000..49cd3b359
--- /dev/null
+++ b/browser/components/extensions/extension-mac.css
@@ -0,0 +1,11 @@
+button,
+select,
+input[type="checkbox"] + label::before {
+ border-radius: 4px;
+}
+
+.panel-section-footer {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ overflow: hidden;
+}
diff --git a/browser/components/extensions/extension-win-panel.css b/browser/components/extensions/extension-win-panel.css
new file mode 100644
index 000000000..ddafe3ea5
--- /dev/null
+++ b/browser/components/extensions/extension-win-panel.css
@@ -0,0 +1,7 @@
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ body {
+ border-radius: 4px;
+ }
+}
diff --git a/browser/components/extensions/extension.css b/browser/components/extensions/extension.css
new file mode 100644
index 000000000..6b59033e3
--- /dev/null
+++ b/browser/components/extensions/extension.css
@@ -0,0 +1,572 @@
+/* stylelint-disable property-no-vendor-prefix */
+/* stylelint-disable property-no-vendor-prefix */
+/* Base */
+button,
+select,
+option,
+input {
+ -moz-appearance: none;
+}
+
+/* Variables */
+html,
+body {
+ background: transparent;
+ box-sizing: border-box;
+ color: #222426;
+ cursor: default;
+ display: flex;
+ flex-direction: column;
+ font: caption;
+ margin: 0;
+ padding: 0;
+ -moz-user-select: none;
+}
+
+body * {
+ box-sizing: border-box;
+ text-align: start;
+}
+
+/* stylelint-disable property-no-vendor-prefix */
+/* Buttons */
+button,
+select {
+ background-color: #fbfbfb;
+ border: 1px solid #b1b1b1;
+ box-shadow: 0 0 0 0 transparent;
+ font: caption;
+ height: 24px;
+ outline: 0 !important;
+ padding: 0 8px 0;
+ transition-duration: 250ms;
+ transition-property: box-shadow, border;
+}
+
+select {
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==);
+ background-position: calc(100% - 4px) center;
+ background-repeat: no-repeat;
+ padding-inline-end: 24px;
+ text-overflow: ellipsis;
+}
+
+label {
+ font: caption;
+}
+
+button::-moz-focus-inner {
+ border: 0;
+ outline: 0;
+}
+
+/* Dropdowns */
+select {
+ background-color: #fbfbfb;
+ border: 1px solid #b1b1b1;
+ box-shadow: 0 0 0 0 transparent;
+ font: caption;
+ height: 24px;
+ outline: 0 !important;
+ padding: 0 8px 0;
+ transition-duration: 250ms;
+ transition-property: box-shadow, border;
+}
+
+select {
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==);
+ background-position: calc(100% - 4px) center;
+ background-repeat: no-repeat;
+ padding-inline-end: 24px;
+ text-overflow: ellipsis;
+}
+
+select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 #000;
+}
+
+select:-moz-focusring * {
+ color: #000;
+ text-shadow: none;
+}
+
+button.hover,
+select.hover {
+ background-color: #ebebeb;
+ border: 1px solid #b1b1b1;
+}
+
+button.pressed,
+select.pressed {
+ background-color: #d4d4d4;
+ border: 1px solid #858585;
+}
+
+button.disabled,
+select.disabled {
+ color: #999;
+ opacity: .5;
+}
+
+button.focused,
+select.focused {
+ border-color: #fff;
+ box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+button.default {
+ background-color: #0996f8;
+ border-color: #0670cc;
+ color: #fff;
+}
+
+button.default.hover {
+ background-color: #0670cc;
+ border-color: #005bab;
+}
+
+button.default.pressed {
+ background-color: #005bab;
+ border-color: #004480;
+}
+
+button.default.focused {
+ border-color: #fff;
+}
+
+/* Radio Buttons */
+.radioItem {
+ margin-bottom: 6px;
+ text-align: left;
+}
+
+input[type="radio"] {
+ display: none;
+}
+
+input[type="radio"] + label {
+ -moz-user-select: none;
+}
+
+input[type="radio"] + label::before {
+ background-color: #fff;
+ background-position: center;
+ border: 1px solid #b1b1b1;
+ border-radius: 50%;
+ content: "";
+ display: inline-block;
+ height: 16px;
+ margin-right: 6px;
+ vertical-align: text-top;
+ width: 16px;
+}
+
+input[type="radio"]:hover + label::before,
+.radioItem.hover input[type="radio"]:not(active) + label::before {
+ background-color: #fbfbfb;
+ border-color: #b1b1b1;
+}
+
+input[type="radio"]:hover:active + label::before,
+.radioItem.pressed input[type="radio"]:not(active) + label::before {
+ background-color: #ebebeb;
+ border-color: #858585;
+}
+
+input[type="radio"]:checked + label::before {
+ background-color: #0996f8;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8Y2lyY2xlIGN4PSI4IiBjeT0iOCIgcj0iNCIgZmlsbD0iI2ZmZiIgLz4KPC9zdmc+Cg==);
+ border-color: #0670cc;
+}
+
+input[type="radio"]:checked:hover + label::before,
+.radioItem.hover input[type="radio"]:checked:not(active) + label::before {
+ background-color: #0670cc;
+ border-color: #005bab;
+}
+
+input[type="radio"]:checked:hover:active + label::before,
+.radioItem.pressed input[type="radio"]:checked:not(active) + label::before {
+ background-color: #005bab;
+ border-color: #004480;
+}
+
+.radioItem.disabled input[type="radio"] + label,
+.radioItem.disabled input[type="radio"]:hover + label,
+.radioItem.disabled input[type="radio"]:hover:active + label {
+ color: #999;
+ opacity: .5;
+}
+
+.radioItem.focused input[type="radio"] + label::before {
+ border-color: #0996f8;
+ box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+.radioItem.focused input[type="radio"]:checked + label::before {
+ border-color: #fff;
+}
+
+/* Checkboxes */
+.checkboxItem {
+ margin-bottom: 6px;
+ text-align: left;
+}
+
+input[type="checkbox"] {
+ display: none;
+}
+
+input[type="checkbox"] + label {
+ -moz-user-select: none;
+}
+
+input[type="checkbox"] + label::before {
+ background-color: #fff;
+ background-position: center;
+ border: 1px solid #b1b1b1;
+ content: "";
+ display: inline-block;
+ height: 16px;
+ margin-right: 6px;
+ vertical-align: text-top;
+ width: 16px;
+}
+
+input[type="checkbox"]:hover + label::before,
+.checkboxItem.hover input[type="checkbox"]:not(active) + label::before {
+ background-color: #fbfbfb;
+ border-color: #b1b1b1;
+}
+
+input[type="checkbox"]:hover:active + label::before,
+.checkboxItem.pressed input[type="checkbox"]:not(active) + label::before {
+ background-color: #ebebeb;
+ border-color: #858585;
+}
+
+input[type="checkbox"]:checked + label::before {
+ background-color: #0996f8;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNNy43LDEyLjkgQzcuNCwxMy4zIDYuOCwxMy40IDYuNCwxMyBMMy4yLDkuOCBDMi44LDkuNCAyLjgsOC42IDMuMiw4LjIgQzMuNiw3LjggNC40LDcuOCA0LjgsOC4yIEw2LjksMTAuMyBMMTEuMSw0LjQgQzExLjUsMy45IDEyLjIsMy44IDEyLjcsNC4xIEMxMy4yLDQuNSAxMy4zLDUuMiAxMyw1LjcgTDcuNywxMi45IEw3LjcsMTIuOSBaIiBmaWxsPSIjZmZmIiAvPgo8L3N2Zz4K);
+ border-color: #0670cc;
+}
+
+input[type="checkbox"]:checked:hover + label::before,
+.checkboxItem.hover input[type="checkbox"]:checked:not(active) + label::before {
+ background-color: #0670cc;
+ border-color: #005bab;
+}
+
+input[type="checkbox"]:checked:hover:active + label::before,
+.checkboxItem.pressed input[type="checkbox"]:checked:not(active) + label::before {
+ background-color: #005bab;
+ border-color: #004480;
+}
+
+.checkboxItem.disabled input[type="checkbox"] + label,
+.checkboxItem.disabled input[type="checkbox"]:hover + label,
+.checkboxItem.disabled input[type="checkbox"]:hover:active + label {
+ color: #999;
+ opacity: .5;
+}
+
+.checkboxItem.focused input[type="checkbox"] + label::before {
+ border-color: #0996f8;
+ box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+.checkboxItem.focused input[type="checkbox"]:checked + label::before {
+ border-color: #fff;
+}
+
+/* Expander Button */
+button.expander {
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg==);
+ background-position: center;
+ background-repeat: no-repeat;
+ height: 24px;
+ padding: 0;
+ width: 24px;
+}
+
+/* Interactive States */
+button:hover:not(.pressed):not(.disabled):not(.focused),
+select:hover:not(.pressed):not(.disabled):not(.focused) {
+ background-color: #ebebeb;
+ border: 1px solid #b1b1b1;
+}
+
+button:hover:active:not(.hover):not(.disabled):not(.focused),
+select:hover:active:not(.hover):not(.disabled):not(.focused) {
+ background-color: #d4d4d4;
+ border: 1px solid #858585;
+}
+
+button.default:hover:not(.pressed):not(.disabled):not(.focused) {
+ background-color: #0670cc;
+ border-color: #005bab;
+}
+
+button.default:hover:active:not(.hover):not(.disabled):not(.focused) {
+ background-color: #005bab;
+ border-color: #004480;
+}
+
+button:focus:not(.disabled) {
+ border-color: #fff !important;
+ box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+/* Fields */
+input[type="text"],
+textarea {
+ background-color: #fff;
+ border: 1px solid #b1b1b1;
+ box-shadow: 0 0 0 0 rgba(97, 181, 255, 0);
+ font: caption;
+ padding: 0 6px 0;
+ transition-duration: 250ms;
+ transition-property: box-shadow;
+}
+
+input[type="text"] {
+ height: 24px;
+}
+
+input[type="text"].hover,
+textarea.hover {
+ border: 1px solid #858585;
+}
+
+input[type="text"].disabled,
+textarea.disabled {
+ color: #999;
+ opacity: .5;
+}
+
+input[type="text"].focused,
+textarea.focused {
+ border-color: #0996f8;
+ box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+/* Interactive States */
+input[type="text"]:not(disabled):hover,
+textarea:not(disabled):hover {
+ border: 1px solid #858585;
+}
+
+input[type="text"]:focus,
+input[type="text"]:focus:hover,
+textarea:focus,
+textarea:focus:hover {
+ border-color: #0996f8;
+ box-shadow: 0 0 0 2px rgba(97, 181, 255, 0.75);
+}
+
+/* stylelint-disable property-no-vendor-prefix */
+.panel-section {
+ display: flex;
+ flex-direction: row;
+}
+
+.panel-section-separator {
+ background-color: rgba(0, 0, 0, 0.15);
+ min-height: 1px;
+}
+
+/* Panel Section - Header */
+.panel-section-header {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.15);
+ padding: 16px;
+}
+
+.panel-section-header > .icon-section-header {
+ background-position: center center;
+ background-repeat: no-repeat;
+ height: 32px;
+ margin-right: 16px;
+ position: relative;
+ width: 32px;
+}
+
+.panel-section-header > .text-section-header {
+ align-self: center;
+ font-size: 1.385em;
+ font-weight: lighter;
+}
+
+/* Panel Section - List */
+.panel-section-list {
+ flex-direction: column;
+ padding: 4px 0;
+}
+
+.panel-list-item {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ height: 24px;
+ padding: 0 16px;
+}
+
+.panel-list-item:not(.disabled):hover {
+ background-color: rgba(0, 0, 0, 0.06);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.panel-list-item:not(.disabled):hover:active {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.panel-list-item.disabled {
+ color: #999;
+}
+
+.panel-list-item > .icon {
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.panel-list-item > .text {
+ flex-grow: 10;
+}
+
+.panel-list-item > .text-shortcut {
+ color: #808080;
+ font-family: "Lucida Grande", caption;
+ font-size: .847em;
+ justify-content: flex-end;
+}
+
+.panel-section-list .panel-section-separator {
+ margin: 4px 0;
+}
+
+/* Panel Section - Form Elements */
+.panel-section-formElements {
+ display: flex;
+ flex-direction: column;
+ padding: 16px;
+}
+
+.panel-formElements-item {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 12px;
+}
+
+.panel-formElements-item:last-child {
+ margin-bottom: 0;
+}
+
+.panel-formElements-item label {
+ flex-shrink: 0;
+ margin-right: 6px;
+ text-align: right;
+}
+
+.panel-formElements-item input[type="text"],
+.panel-formElements-item select {
+ flex-grow: 1;
+}
+
+/* Panel Section - Footer */
+.panel-section-footer {
+ background-color: rgba(0, 0, 0, 0.06);
+ border-top: 1px solid rgba(0, 0, 0, 0.15);
+ color: #1a1a1a;
+ display: flex;
+ flex-direction: row;
+ height: 41px;
+ margin-top: -1px;
+ padding: 0;
+}
+
+.panel-section-footer-button {
+ flex: 1 1 auto;
+ height: 100%;
+ margin: 0 -1px;
+ padding: 12px;
+ text-align: center;
+}
+
+.panel-section-footer-button > .text-shortcut {
+ color: #808080;
+ font-family: "Lucida Grande", caption;
+ font-size: .847em;
+}
+
+.panel-section-footer-button:hover {
+ background-color: rgba(0, 0, 0, 0.06);
+}
+
+.panel-section-footer-button:hover:active {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.panel-section-footer-button.default {
+ background-color: #0996f8;
+ box-shadow: 0 1px 0 #0670cc inset;
+ color: #fff;
+}
+
+.panel-section-footer-button.default:hover {
+ background-color: #0670cc;
+ box-shadow: 0 1px 0 #005bab inset;
+}
+
+.panel-section-footer-button.default:hover:active {
+ background-color: #005bab;
+ box-shadow: 0 1px 0 #004480 inset;
+}
+
+.panel-section-footer-separator {
+ background-color: rgba(0, 0, 0, 0.1);
+ width: 1px;
+ z-index: 99;
+}
+
+/* Panel Section - Tabs */
+.panel-section-tabs {
+ color: #1a1a1a;
+ display: flex;
+ flex-direction: row;
+ height: 41px;
+ margin-bottom: -1px;
+ padding: 0;
+}
+
+.panel-section-tabs-button {
+ flex: 1 1 auto;
+ height: 100%;
+ margin: 0 -1px;
+ padding: 12px;
+ text-align: center;
+}
+
+.panel-section-tabs-button:hover {
+ background-color: rgba(0, 0, 0, 0.06);
+}
+
+.panel-section-tabs-button:hover:active {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.panel-section-tabs-button.selected {
+ box-shadow: 0 -1px 0 #0670cc inset, 0 -4px 0 #0996f8 inset;
+ color: #0996f8;
+}
+
+.panel-section-tabs-button.selected:hover {
+ color: #0670cc;
+}
+
+.panel-section-tabs-separator {
+ background-color: rgba(0, 0, 0, 0.1);
+ width: 1px;
+ z-index: 99;
+}
diff --git a/browser/components/extensions/extension.svg b/browser/components/extensions/extension.svg
new file mode 100644
index 000000000..a16455253
--- /dev/null
+++ b/browser/components/extensions/extension.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#66cc52" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#60bf4c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+</svg>
diff --git a/browser/components/extensions/extensions-browser.manifest b/browser/components/extensions/extensions-browser.manifest
new file mode 100644
index 000000000..ed5cca813
--- /dev/null
+++ b/browser/components/extensions/extensions-browser.manifest
@@ -0,0 +1,31 @@
+# scripts
+category webextension-scripts bookmarks chrome://browser/content/ext-bookmarks.js
+category webextension-scripts browserAction chrome://browser/content/ext-browserAction.js
+category webextension-scripts commands chrome://browser/content/ext-commands.js
+category webextension-scripts contextMenus chrome://browser/content/ext-contextMenus.js
+category webextension-scripts desktop-runtime chrome://browser/content/ext-desktop-runtime.js
+category webextension-scripts history chrome://browser/content/ext-history.js
+category webextension-scripts omnibox chrome://browser/content/ext-omnibox.js
+category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js
+category webextension-scripts sessions chrome://browser/content/ext-sessions.js
+category webextension-scripts tabs chrome://browser/content/ext-tabs.js
+category webextension-scripts utils chrome://browser/content/ext-utils.js
+category webextension-scripts windows chrome://browser/content/ext-windows.js
+
+# scripts that must run in the same process as addon code.
+category webextension-scripts-addon contextMenus chrome://browser/content/ext-c-contextMenus.js
+category webextension-scripts-addon omnibox chrome://browser/content/ext-c-omnibox.js
+category webextension-scripts-addon tabs chrome://browser/content/ext-c-tabs.js
+
+# schemas
+category webextension-schemas bookmarks chrome://browser/content/schemas/bookmarks.json
+category webextension-schemas browser_action chrome://browser/content/schemas/browser_action.json
+category webextension-schemas commands chrome://browser/content/schemas/commands.json
+category webextension-schemas context_menus chrome://browser/content/schemas/context_menus.json
+category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
+category webextension-schemas history chrome://browser/content/schemas/history.json
+category webextension-schemas omnibox chrome://browser/content/schemas/omnibox.json
+category webextension-schemas page_action chrome://browser/content/schemas/page_action.json
+category webextension-schemas sessions chrome://browser/content/schemas/sessions.json
+category webextension-schemas tabs chrome://browser/content/schemas/tabs.json
+category webextension-schemas windows chrome://browser/content/schemas/windows.json
diff --git a/browser/components/extensions/jar.mn b/browser/components/extensions/jar.mn
new file mode 100644
index 000000000..a7b506ec4
--- /dev/null
+++ b/browser/components/extensions/jar.mn
@@ -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/.
+
+browser.jar:
+ content/browser/extension.css
+#ifdef XP_MACOSX
+ content/browser/extension-mac.css
+ content/browser/extension-mac-panel.css
+#endif
+#ifdef XP_WIN
+ content/browser/extension-win-panel.css
+#endif
+ content/browser/extension.svg
+ content/browser/ext-bookmarks.js
+ content/browser/ext-browserAction.js
+ content/browser/ext-commands.js
+ content/browser/ext-contextMenus.js
+ content/browser/ext-desktop-runtime.js
+ content/browser/ext-history.js
+ content/browser/ext-omnibox.js
+ content/browser/ext-pageAction.js
+ content/browser/ext-sessions.js
+ content/browser/ext-tabs.js
+ content/browser/ext-utils.js
+ content/browser/ext-windows.js
+ content/browser/ext-c-contextMenus.js
+ content/browser/ext-c-omnibox.js
+ content/browser/ext-c-tabs.js
diff --git a/browser/components/extensions/moz.build b/browser/components/extensions/moz.build
new file mode 100644
index 000000000..5b3654c3a
--- /dev/null
+++ b/browser/components/extensions/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_COMPONENTS += [
+ 'extensions-browser.manifest',
+]
+
+DIRS += ['schemas']
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
diff --git a/browser/components/extensions/schemas/LICENSE b/browser/components/extensions/schemas/LICENSE
new file mode 100644
index 000000000..9314092fd
--- /dev/null
+++ b/browser/components/extensions/schemas/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2006-2008 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.
diff --git a/browser/components/extensions/schemas/bookmarks.json b/browser/components/extensions/schemas/bookmarks.json
new file mode 100644
index 000000000..fb74c633e
--- /dev/null
+++ b/browser/components/extensions/schemas/bookmarks.json
@@ -0,0 +1,568 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "Permission",
+ "choices": [{
+ "type": "string",
+ "enum": [
+ "bookmarks"
+ ]
+ }]
+ }
+ ]
+ },
+ {
+ "namespace": "bookmarks",
+ "description": "Use the <code>browser.bookmarks</code> API to create, organize, and otherwise manipulate bookmarks. Also see $(topic:override)[Override Pages], which you can use to create a custom Bookmark Manager page.",
+ "permissions": ["bookmarks"],
+ "types": [
+ {
+ "id": "BookmarkTreeNodeUnmodifiable",
+ "type": "string",
+ "enum": ["managed"],
+ "description": "Indicates the reason why this node is unmodifiable. The <var>managed</var> value indicates that this node was configured by the system administrator or by the custodian of a supervised user. Omitted if the node can be modified by the user and the extension (default)."
+ },
+ {
+ "id": "BookmarkTreeNode",
+ "type": "object",
+ "description": "A node (either a bookmark or a folder) in the bookmark tree. Child nodes are ordered within their parent folder.",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "The unique identifier for the node. IDs are unique within the current profile, and they remain valid even after the browser is restarted."
+ },
+ "parentId": {
+ "type": "string",
+ "optional": true,
+ "description": "The <code>id</code> of the parent folder. Omitted for the root node."
+ },
+ "index": {
+ "type": "integer",
+ "optional": true,
+ "description": "The 0-based position of this node within its parent folder."
+ },
+ "url": {
+ "type": "string",
+ "optional": true,
+ "description": "The URL navigated to when a user clicks the bookmark. Omitted for folders."
+ },
+ "title": {
+ "type": "string",
+ "description": "The text displayed for the node."
+ },
+ "dateAdded": {
+ "type": "number",
+ "optional": true,
+ "description": "When this node was created, in milliseconds since the epoch (<code>new Date(dateAdded)</code>)."
+ },
+ "dateGroupModified": {
+ "type": "number",
+ "optional": true,
+ "description": "When the contents of this folder last changed, in milliseconds since the epoch."
+ },
+ "unmodifiable": {
+ "$ref": "BookmarkTreeNodeUnmodifiable",
+ "optional": true,
+ "description": "Indicates the reason why this node is unmodifiable. The <var>managed</var> value indicates that this node was configured by the system administrator or by the custodian of a supervised user. Omitted if the node can be modified by the user and the extension (default)."
+ },
+ "children": {
+ "type": "array",
+ "optional": true,
+ "items": { "$ref": "BookmarkTreeNode" },
+ "description": "An ordered list of children of this node."
+ }
+ }
+ },
+ {
+ "id": "CreateDetails",
+ "description": "Object passed to the create() function.",
+ "type": "object",
+ "properties": {
+ "parentId": {
+ "type": "string",
+ "optional": true,
+ "description": "Defaults to the Other Bookmarks folder."
+ },
+ "index": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true
+ },
+ "title": {
+ "type": "string",
+ "optional": true
+ },
+ "url": {
+ "type": "string",
+ "optional": true
+ }
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "get",
+ "type": "function",
+ "description": "Retrieves the specified BookmarkTreeNode(s).",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "idOrIdList",
+ "description": "A single string-valued id, or an array of string-valued ids",
+ "choices": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": { "$ref": "BookmarkTreeNode" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getChildren",
+ "type": "function",
+ "description": "Retrieves the children of the specified BookmarkTreeNode id.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": { "$ref": "BookmarkTreeNode"}
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getRecent",
+ "type": "function",
+ "description": "Retrieves the recently added bookmarks.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "minimum": 1,
+ "name": "numberOfItems",
+ "description": "The maximum number of items to return."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": { "$ref": "BookmarkTreeNode" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getTree",
+ "type": "function",
+ "description": "Retrieves the entire Bookmarks hierarchy.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": { "$ref": "BookmarkTreeNode" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getSubTree",
+ "type": "function",
+ "description": "Retrieves part of the Bookmarks hierarchy, starting at the specified node.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id",
+ "description": "The ID of the root of the subtree to retrieve."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": { "$ref": "BookmarkTreeNode" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "search",
+ "type": "function",
+ "description": "Searches for BookmarkTreeNodes matching the given query. Queries specified with an object produce BookmarkTreeNodes matching all specified properties.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "query",
+ "description": "Either a string of words and quoted phrases that are matched against bookmark URLs and titles, or an object. If an object, the properties <code>query</code>, <code>url</code>, and <code>title</code> may be specified and bookmarks matching all specified properties will be produced.",
+ "choices": [
+ {
+ "type": "string",
+ "description": "A string of words and quoted phrases that are matched against bookmark URLs and titles."
+ },
+ {
+ "type": "object",
+ "description": "An object specifying properties and values to match when searching. Produces bookmarks matching all properties.",
+ "properties": {
+ "query": {
+ "type": "string",
+ "optional": true,
+ "description": "A string of words and quoted phrases that are matched against bookmark URLs and titles."
+ },
+ "url": {
+ "type": "string",
+ "format": "url",
+ "optional": true,
+ "description": "The URL of the bookmark; matches verbatim. Note that folders have no URL."
+ },
+ "title": {
+ "type": "string",
+ "optional": true,
+ "description": "The title of the bookmark; matches verbatim."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": { "$ref": "BookmarkTreeNode" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "create",
+ "type": "function",
+ "description": "Creates a bookmark or folder under the specified parentId. If url is NULL or missing, it will be a folder.",
+ "async": "callback",
+ "parameters": [
+ {
+ "$ref": "CreateDetails",
+ "name": "bookmark"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "BookmarkTreeNode"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "move",
+ "type": "function",
+ "description": "Moves the specified BookmarkTreeNode to the provided location.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "object",
+ "name": "destination",
+ "properties": {
+ "parentId": {
+ "type": "string",
+ "optional": true
+ },
+ "index": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "BookmarkTreeNode"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "update",
+ "type": "function",
+ "description": "Updates the properties of a bookmark or folder. Specify only the properties that you want to change; unspecified properties will be left unchanged. <b>Note:</b> Currently, only 'title' and 'url' are supported.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "object",
+ "name": "changes",
+ "properties": {
+ "title": {
+ "type": "string",
+ "optional": true
+ },
+ "url": {
+ "type": "string",
+ "optional": true
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "BookmarkTreeNode"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "remove",
+ "type": "function",
+ "description": "Removes a bookmark or an empty bookmark folder.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "removeTree",
+ "type": "function",
+ "description": "Recursively removes a bookmark folder.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "import",
+ "unsupported": true,
+ "type": "function",
+ "description": "Imports bookmarks from an html bookmark file",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "export",
+ "unsupported": true,
+ "type": "function",
+ "description": "Exports bookmarks to an html bookmark file",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onCreated",
+ "type": "function",
+ "description": "Fired when a bookmark or folder is created.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "$ref": "BookmarkTreeNode",
+ "name": "bookmark"
+ }
+ ]
+ },
+ {
+ "name": "onRemoved",
+ "type": "function",
+ "description": "Fired when a bookmark or folder is removed. When a folder is removed recursively, a single notification is fired for the folder, and none for its contents.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "object",
+ "name": "removeInfo",
+ "properties": {
+ "parentId": { "type": "string" },
+ "index": { "type": "integer" },
+ "node": { "$ref": "BookmarkTreeNode" }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onChanged",
+ "type": "function",
+ "description": "Fired when a bookmark or folder changes. <b>Note:</b> Currently, only title and url changes trigger this.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "object",
+ "name": "changeInfo",
+ "properties": {
+ "title": { "type": "string" },
+ "url": {
+ "type": "string",
+ "optional": true
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onMoved",
+ "type": "function",
+ "description": "Fired when a bookmark or folder is moved to a different parent folder.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "object",
+ "name": "moveInfo",
+ "properties": {
+ "parentId": { "type": "string" },
+ "index": { "type": "integer" },
+ "oldParentId": { "type": "string" },
+ "oldIndex": { "type": "integer" }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onChildrenReordered",
+ "unsupported": true,
+ "type": "function",
+ "description": "Fired when the children of a folder have changed their order due to the order being sorted in the UI. This is not called as a result of a move().",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "id"
+ },
+ {
+ "type": "object",
+ "name": "reorderInfo",
+ "properties": {
+ "childIds": {
+ "type": "array",
+ "items": { "type": "string" }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onImportBegan",
+ "unsupported": true,
+ "type": "function",
+ "description": "Fired when a bookmark import session is begun. Expensive observers should ignore onCreated updates until onImportEnded is fired. Observers should still handle other notifications immediately.",
+ "parameters": []
+ },
+ {
+ "name": "onImportEnded",
+ "unsupported": true,
+ "type": "function",
+ "description": "Fired when a bookmark import session is ended.",
+ "parameters": []
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/browser_action.json b/browser/components/extensions/schemas/browser_action.json
new file mode 100644
index 000000000..1a7da956a
--- /dev/null
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -0,0 +1,430 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "WebExtensionManifest",
+ "properties": {
+ "browser_action": {
+ "type": "object",
+ "additionalProperties": { "$ref": "UnrecognizedProperty" },
+ "properties": {
+ "default_title": {
+ "type": "string",
+ "optional": true,
+ "preprocess": "localize"
+ },
+ "default_icon": {
+ "$ref": "IconPath",
+ "optional": true
+ },
+ "default_popup": {
+ "type": "string",
+ "format": "relativeUrl",
+ "optional": true,
+ "preprocess": "localize"
+ },
+ "browser_style": {
+ "type": "boolean",
+ "optional": true
+ }
+ },
+ "optional": true
+ }
+ }
+ }
+ ]
+ },
+ {
+ "namespace": "browserAction",
+ "description": "Use browser actions to put icons in the main browser toolbar, to the right of the address bar. In addition to its icon, a browser action can also have a tooltip, a badge, and a popup.",
+ "permissions": ["manifest:browser_action"],
+ "types": [
+ {
+ "id": "ColorArray",
+ "type": "array",
+ "items": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 255
+ },
+ "minItems": 4,
+ "maxItems": 4
+ },
+ {
+ "id": "ImageDataType",
+ "type": "object",
+ "isInstanceOf": "ImageData",
+ "additionalProperties": { "type": "any" },
+ "postprocess": "convertImageDataToURL",
+ "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
+ }
+ ],
+ "functions": [
+ {
+ "name": "setTitle",
+ "type": "function",
+ "description": "Sets the title of the browser action. This shows up in the tooltip.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string",
+ "description": "The string the browser action should display when moused over."
+ },
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getTitle",
+ "type": "function",
+ "description": "Gets the title of the browser action.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Specify the tab to get the title from. If no tab is specified, the non-tab-specific title is returned."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "result",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setIcon",
+ "type": "function",
+ "description": "Sets the icon for the browser action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "imageData": {
+ "choices": [
+ { "$ref": "ImageDataType" },
+ {
+ "type": "object",
+ "additionalProperties": {"$ref": "ImageDataType"}
+ }
+ ],
+ "optional": true,
+ "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
+ },
+ "path": {
+ "choices": [
+ { "type": "string" },
+ {
+ "type": "object",
+ "additionalProperties": {"type": "string"}
+ }
+ ],
+ "optional": true,
+ "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
+ },
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "setPopup",
+ "type": "function",
+ "description": "Sets the html document to be opened as a popup when the user clicks on the browser action's icon.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+ },
+ "popup": {
+ "type": "string",
+ "description": "The html file to show in a popup. If set to the empty string (''), no popup is shown."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getPopup",
+ "type": "function",
+ "description": "Gets the html document set as the popup for this browser action.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Specify the tab to get the popup from. If no tab is specified, the non-tab-specific popup is returned."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "result",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setBadgeText",
+ "type": "function",
+ "description": "Sets the badge text for the browser action. The badge is displayed on top of the icon.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string",
+ "description": "Any number of characters can be passed, but only about four can fit in the space."
+ },
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getBadgeText",
+ "type": "function",
+ "description": "Gets the badge text of the browser action. If no tab is specified, the non-tab-specific badge text is returned.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Specify the tab to get the badge text from. If no tab is specified, the non-tab-specific badge text is returned."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "result",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setBadgeBackgroundColor",
+ "type": "function",
+ "description": "Sets the background color for the badge.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "color": {
+ "description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
+ "choices": [
+ {"type": "string"},
+ {"$ref": "ColorArray"}
+ ]
+ },
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getBadgeBackgroundColor",
+ "type": "function",
+ "description": "Gets the background color of the browser action.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "optional": true,
+ "description": "Specify the tab to get the badge background color from. If no tab is specified, the non-tab-specific badge background color is returned."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "ColorArray"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "enable",
+ "type": "function",
+ "description": "Enables the browser action for a tab. By default, browser actions are enabled.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "optional": true,
+ "name": "tabId",
+ "minimum": 0,
+ "description": "The id of the tab for which you want to modify the browser action."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "disable",
+ "type": "function",
+ "description": "Disables the browser action for a tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "optional": true,
+ "name": "tabId",
+ "minimum": 0,
+ "description": "The id of the tab for which you want to modify the browser action."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "openPopup",
+ "type": "function",
+ "description": "Opens the extension popup window in the active window but does not grant tab permissions.",
+ "unsupported": true,
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "popupView",
+ "type": "object",
+ "optional": true,
+ "description": "JavaScript 'window' object for the popup window if it was succesfully opened.",
+ "additionalProperties": { "type": "any" }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onClicked",
+ "type": "function",
+ "description": "Fired when a browser action icon is clicked. This event will not fire if the browser action has a popup.",
+ "parameters": [
+ {
+ "name": "tab",
+ "$ref": "tabs.Tab"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/commands.json b/browser/components/extensions/schemas/commands.json
new file mode 100644
index 000000000..a1632088e
--- /dev/null
+++ b/browser/components/extensions/schemas/commands.json
@@ -0,0 +1,148 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "id": "KeyName",
+ "choices": [
+ {
+ "type": "string",
+ "pattern": "^\\s*(Alt|Ctrl|Command|MacCtrl)\\s*\\+\\s*(Shift\\s*\\+\\s*)?([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)\\s*$"
+ },
+ {
+ "type": "string",
+ "pattern": "^(MediaNextTrack|MediaPlayPause|MediaPrevTrack|MediaStop)$"
+ }
+ ]
+ },
+ {
+ "$extend": "WebExtensionManifest",
+ "properties": {
+ "commands": {
+ "type": "object",
+ "optional": true,
+ "additionalProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "UnrecognizedProperty" },
+ "properties": {
+ "suggested_key": {
+ "type": "object",
+ "optional": true,
+ "properties": {
+ "default": {
+ "$ref": "KeyName",
+ "optional": true
+ },
+ "mac": {
+ "$ref": "KeyName",
+ "optional": true
+ },
+ "linux": {
+ "$ref": "KeyName",
+ "optional": true
+ },
+ "windows": {
+ "$ref": "KeyName",
+ "optional": true
+ },
+ "chromeos": {
+ "type": "string",
+ "optional": true
+ },
+ "android": {
+ "type": "string",
+ "optional": true
+ },
+ "ios": {
+ "type": "string",
+ "optional": true
+ },
+ "additionalProperties": {
+ "type": "string",
+ "deprecated": "Unknown platform name",
+ "optional": true
+ }
+ }
+ },
+ "description": {
+ "type": "string",
+ "optional": true
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "namespace": "commands",
+ "description": "Use the commands API to add keyboard shortcuts that trigger actions in your extension, for example, an action to open the browser action or send a command to the xtension.",
+ "permissions": ["manifest:commands"],
+ "types": [
+ {
+ "id": "Command",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "optional": true,
+ "description": "The name of the Extension Command"
+ },
+ "description": {
+ "type": "string",
+ "optional": true,
+ "description": "The Extension Command description"
+ },
+ "shortcut": {
+ "type": "string",
+ "optional": true,
+ "description": "The shortcut active for this command, or blank if not active."
+ }
+ }
+ }
+ ],
+ "events": [
+ {
+ "name": "onCommand",
+ "description": "Fired when a registered command is activated using a keyboard shortcut.",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "command",
+ "type": "string"
+ }
+ ]
+ }
+ ],
+ "functions": [
+ {
+ "name": "getAll",
+ "type": "function",
+ "async": "callback",
+ "description": "Returns all the registered extension commands for this extension and their shortcut (if active).",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "commands",
+ "type": "array",
+ "items": {
+ "$ref": "Command"
+ }
+ }
+ ],
+ "description": "Called to return the registered commands."
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/context_menus.json b/browser/components/extensions/schemas/context_menus.json
new file mode 100644
index 000000000..b31af51f3
--- /dev/null
+++ b/browser/components/extensions/schemas/context_menus.json
@@ -0,0 +1,424 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "Permission",
+ "choices": [{
+ "type": "string",
+ "enum": [
+ "contextMenus"
+ ]
+ }]
+ }
+ ]
+ },
+ {
+ "namespace": "contextMenus",
+ "description": "Use the <code>browser.contextMenus</code> API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
+ "permissions": ["contextMenus"],
+ "properties": {
+ "ACTION_MENU_TOP_LEVEL_LIMIT": {
+ "value": 6,
+ "description": "The maximum number of top level extension items that can be added to an extension action context menu. Any items beyond this limit will be ignored."
+ }
+ },
+ "types": [
+ {
+ "id": "ContextType",
+ "type": "string",
+ "enum": ["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio", "launcher", "browser_action", "page_action"],
+ "description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'launcher'. The 'launcher' context is only supported by apps and is used to add menu items to the context menu that appears when clicking on the app icon in the launcher/taskbar/dock/etc. Different platforms might put limitations on what is actually supported in a launcher context menu."
+ },
+ {
+ "id": "ItemType",
+ "type": "string",
+ "enum": ["normal", "checkbox", "radio", "separator"],
+ "description": "The type of menu item."
+ },
+ {
+ "id": "OnClickData",
+ "type": "object",
+ "description": "Information sent when a context menu item is clicked.",
+ "properties": {
+ "menuItemId": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "description": "The ID of the menu item that was clicked."
+ },
+ "parentMenuItemId": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "optional": true,
+ "description": "The parent ID, if any, for the item clicked."
+ },
+ "mediaType": {
+ "type": "string",
+ "optional": true,
+ "description": "One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements."
+ },
+ "linkUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "If the element is a link, the URL it points to."
+ },
+ "srcUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "Will be present for elements with a 'src' URL."
+ },
+ "pageUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "The URL of the page where the menu item was clicked. This property is not set if the click occured in a context where there is no current page, such as in a launcher context menu."
+ },
+ "frameUrl": {
+ "type": "string",
+ "optional": true,
+ "description": " The URL of the frame of the element where the context menu was clicked, if it was in a frame."
+ },
+ "selectionText": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the context selection, if any."
+ },
+ "editable": {
+ "type": "boolean",
+ "description": "A flag indicating whether the element is editable (text input, textarea, etc.)."
+ },
+ "wasChecked": {
+ "type": "boolean",
+ "optional": true,
+ "description": "A flag indicating the state of a checkbox or radio item before it was clicked."
+ },
+ "checked": {
+ "type": "boolean",
+ "optional": true,
+ "description": "A flag indicating the state of a checkbox or radio item after it is clicked."
+ }
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "create",
+ "type": "function",
+ "description": "Creates a new context menu item. Note that if an error occurs during creation, you may not find out until the creation callback fires (the details will be in $(ref:runtime.lastError)).",
+ "returns": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "description": "The ID of the newly created item."
+ },
+ "parameters": [
+ {
+ "type": "object",
+ "name": "createProperties",
+ "properties": {
+ "type": {
+ "$ref": "ItemType",
+ "optional": true,
+ "description": "The type of menu item. Defaults to 'normal' if not specified."
+ },
+ "id": {
+ "type": "string",
+ "optional": true,
+ "description": "The unique ID to assign to this item. Mandatory for event pages. Cannot be the same as another ID for this extension."
+ },
+ "title": {
+ "type": "string",
+ "optional": true,
+ "description": "The text to be displayed in the item; this is <em>required</em> unless <code>type</code> is 'separator'. When the context is 'selection', you can use <code>%s</code> within the string to show the selected text. For example, if this parameter's value is \"Translate '%s' to Pig Latin\" and the user selects the word \"cool\", the context menu item for the selection is \"Translate 'cool' to Pig Latin\"."
+ },
+ "checked": {
+ "type": "boolean",
+ "optional": true,
+ "description": "The initial state of a checkbox or radio item: true for selected and false for unselected. Only one radio item can be selected at a time in a given group of radio items."
+ },
+ "contexts": {
+ "type": "array",
+ "items": {
+ "$ref": "ContextType"
+ },
+ "minItems": 1,
+ "optional": true,
+ "description": "List of contexts this menu item will appear in. Defaults to ['page'] if not specified."
+ },
+ "onclick": {
+ "type": "function",
+ "optional": true,
+ "description": "A function that will be called back when the menu item is clicked. Event pages cannot use this; instead, they should register a listener for $(ref:contextMenus.onClicked).",
+ "parameters": [
+ {
+ "name": "info",
+ "$ref": "contextMenusInternal.OnClickData",
+ "description": "Information about the item clicked and the context where the click happened."
+ },
+ {
+ "name": "tab",
+ "$ref": "tabs.Tab",
+ "description": "The details of the tab where the click took place. Note: this parameter only present for extensions."
+ }
+ ]
+ },
+ "parentId": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "optional": true,
+ "description": "The ID of a parent menu item; this makes the item a child of a previously added item."
+ },
+ "documentUrlPatterns": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true,
+ "description": "Lets you restrict the item to apply only to documents whose URL matches one of the given patterns. (This applies to frames as well.) For details on the format of a pattern, see $(topic:match_patterns)[Match Patterns]."
+ },
+ "targetUrlPatterns": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true,
+ "description": "Similar to documentUrlPatterns, but lets you filter based on the src attribute of img/audio/video tags and the href of anchor tags."
+ },
+ "enabled": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether this context menu item is enabled or disabled. Defaults to true."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when the item has been created in the browser. If there were any problems creating the item, details will be available in $(ref:runtime.lastError).",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "createInternal",
+ "type": "function",
+ "allowedContexts": ["addon_parent_only"],
+ "async": "callback",
+ "description": "Identical to contextMenus.create, except: the 'id' field is required and allows an integer, 'onclick' is not allowed, and the method is async (and the return value is not a menu item ID).",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "createProperties",
+ "properties": {
+ "type": {
+ "$ref": "ItemType",
+ "optional": true
+ },
+ "id": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ]
+ },
+ "title": {
+ "type": "string",
+ "optional": true
+ },
+ "checked": {
+ "type": "boolean",
+ "optional": true
+ },
+ "contexts": {
+ "type": "array",
+ "items": {
+ "$ref": "ContextType"
+ },
+ "minItems": 1,
+ "optional": true
+ },
+ "parentId": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "optional": true
+ },
+ "documentUrlPatterns": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true
+ },
+ "targetUrlPatterns": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true
+ },
+ "enabled": {
+ "type": "boolean",
+ "optional": true
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "update",
+ "type": "function",
+ "description": "Updates a previously created context menu item.",
+ "async": "callback",
+ "parameters": [
+ {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "name": "id",
+ "description": "The ID of the item to update."
+ },
+ {
+ "type": "object",
+ "name": "updateProperties",
+ "description": "The properties to update. Accepts the same values as the create function.",
+ "properties": {
+ "type": {
+ "$ref": "ItemType",
+ "optional": true
+ },
+ "title": {
+ "type": "string",
+ "optional": true
+ },
+ "checked": {
+ "type": "boolean",
+ "optional": true
+ },
+ "contexts": {
+ "type": "array",
+ "items": {
+ "$ref": "ContextType"
+ },
+ "minItems": 1,
+ "optional": true
+ },
+ "onclick": {
+ "type": "function",
+ "optional": "omit-key-if-missing",
+ "parameters": [
+ {
+ "name": "info",
+ "$ref": "contextMenusInternal.OnClickData"
+ },
+ {
+ "name": "tab",
+ "$ref": "tabs.Tab",
+ "description": "The details of the tab where the click took place. Note: this parameter only present for extensions."
+ }
+ ]
+ },
+ "parentId": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "optional": true,
+ "description": "Note: You cannot change an item to be a child of one of its own descendants."
+ },
+ "documentUrlPatterns": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true
+ },
+ "targetUrlPatterns": {
+ "type": "array",
+ "items": {"type": "string"},
+ "optional": true
+ },
+ "enabled": {
+ "type": "boolean",
+ "optional": true
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [],
+ "description": "Called when the context menu has been updated."
+ }
+ ]
+ },
+ {
+ "name": "remove",
+ "type": "function",
+ "description": "Removes a context menu item.",
+ "async": "callback",
+ "parameters": [
+ {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "name": "menuItemId",
+ "description": "The ID of the context menu item to remove."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [],
+ "description": "Called when the context menu has been removed."
+ }
+ ]
+ },
+ {
+ "name": "removeAll",
+ "type": "function",
+ "description": "Removes all context menu items added by this extension.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [],
+ "description": "Called when removal is complete."
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onClicked",
+ "type": "function",
+ "description": "Fired when a context menu item is clicked.",
+ "parameters": [
+ {
+ "name": "info",
+ "$ref": "OnClickData",
+ "description": "Information about the item clicked and the context where the click happened."
+ },
+ {
+ "name": "tab",
+ "$ref": "tabs.Tab",
+ "description": "The details of the tab where the click took place. If the click did not take place in a tab, this parameter will be missing.",
+ "optional": true
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/context_menus_internal.json b/browser/components/extensions/schemas/context_menus_internal.json
new file mode 100644
index 000000000..c3cb7aff0
--- /dev/null
+++ b/browser/components/extensions/schemas/context_menus_internal.json
@@ -0,0 +1,78 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "contextMenusInternal",
+ "description": "Use the <code>browser.contextMenus</code> API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
+ "types": [
+ {
+ "id": "OnClickData",
+ "type": "object",
+ "description": "Information sent when a context menu item is clicked.",
+ "properties": {
+ "menuItemId": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "description": "The ID of the menu item that was clicked."
+ },
+ "parentMenuItemId": {
+ "choices": [
+ { "type": "integer" },
+ { "type": "string" }
+ ],
+ "optional": true,
+ "description": "The parent ID, if any, for the item clicked."
+ },
+ "mediaType": {
+ "type": "string",
+ "optional": true,
+ "description": "One of 'image', 'video', or 'audio' if the context menu was activated on one of these types of elements."
+ },
+ "linkUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "If the element is a link, the URL it points to."
+ },
+ "srcUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "Will be present for elements with a 'src' URL."
+ },
+ "pageUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "The URL of the page where the menu item was clicked. This property is not set if the click occured in a context where there is no current page, such as in a launcher context menu."
+ },
+ "frameUrl": {
+ "type": "string",
+ "optional": true,
+ "description": " The URL of the frame of the element where the context menu was clicked, if it was in a frame."
+ },
+ "selectionText": {
+ "type": "string",
+ "optional": true,
+ "description": "The text for the context selection, if any."
+ },
+ "editable": {
+ "type": "boolean",
+ "description": "A flag indicating whether the element is editable (text input, textarea, etc.)."
+ },
+ "wasChecked": {
+ "type": "boolean",
+ "optional": true,
+ "description": "A flag indicating the state of a checkbox or radio item before it was clicked."
+ },
+ "checked": {
+ "type": "boolean",
+ "optional": true,
+ "description": "A flag indicating the state of a checkbox or radio item after it is clicked."
+ }
+ }
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/history.json b/browser/components/extensions/schemas/history.json
new file mode 100644
index 000000000..e05569e38
--- /dev/null
+++ b/browser/components/extensions/schemas/history.json
@@ -0,0 +1,316 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "Permission",
+ "choices": [{
+ "type": "string",
+ "enum": [
+ "history"
+ ]
+ }]
+ }
+ ]
+ },
+ {
+ "namespace": "history",
+ "description": "Use the <code>browser.history</code> API to interact with the browser's record of visited pages. You can add, remove, and query for URLs in the browser's history. To override the history page with your own version, see $(topic:override)[Override Pages].",
+ "permissions": ["history"],
+ "types": [
+ {
+ "id": "TransitionType",
+ "type": "string",
+ "enum": ["link", "typed", "auto_bookmark", "auto_subframe", "manual_subframe", "generated", "auto_toplevel", "form_submit", "reload", "keyword", "keyword_generated"],
+ "description": "The $(topic:transition-types)[transition type] for this visit from its referrer."
+ },
+ {
+ "id": "HistoryItem",
+ "type": "object",
+ "description": "An object encapsulating one result of a history query.",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "The unique identifier for the item."
+ },
+ "url": {
+ "type": "string",
+ "optional": true,
+ "description": "The URL navigated to by a user."
+ },
+ "title": {
+ "type": "string",
+ "optional": true,
+ "description": "The title of the page when it was last loaded."
+ },
+ "lastVisitTime": {
+ "type": "number",
+ "optional": true,
+ "description": "When this page was last loaded, represented in milliseconds since the epoch."
+ },
+ "visitCount": {
+ "type": "integer",
+ "optional": true,
+ "description": "The number of times the user has navigated to this page."
+ },
+ "typedCount": {
+ "type": "integer",
+ "optional": true,
+ "description": "The number of times the user has navigated to this page by typing in the address."
+ }
+ }
+ },
+ {
+ "id": "VisitItem",
+ "type": "object",
+ "description": "An object encapsulating one visit to a URL.",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "The unique identifier for the item."
+ },
+ "visitId": {
+ "type": "string",
+ "description": "The unique identifier for this visit."
+ },
+ "visitTime": {
+ "type": "number",
+ "optional": true,
+ "description": "When this visit occurred, represented in milliseconds since the epoch."
+ },
+ "referringVisitId": {
+ "type": "string",
+ "description": "The visit ID of the referrer."
+ },
+ "transition": {
+ "$ref": "TransitionType",
+ "description": "The $(topic:transition-types)[transition type] for this visit from its referrer."
+ }
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "search",
+ "type": "function",
+ "description": "Searches the history for the last visit time of each page matching the query.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "query",
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string",
+ "description": "A free-text query to the history service. Leave empty to retrieve all pages."
+ },
+ "startTime": {
+ "$ref": "extensionTypes.Date",
+ "optional": true,
+ "description": "Limit results to those visited after this date. If not specified, this defaults to 24 hours in the past."
+ },
+ "endTime": {
+ "$ref": "extensionTypes.Date",
+ "optional": true,
+ "description": "Limit results to those visited before this date."
+ },
+ "maxResults": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 1,
+ "description": "The maximum number of results to retrieve. Defaults to 100."
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": {
+ "$ref": "HistoryItem"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getVisits",
+ "type": "function",
+ "description": "Retrieves information about visits to a URL.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string",
+ "description": "The URL for which to retrieve visit information. It must be in the format as returned from a call to history.search."
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": [
+ {
+ "name": "results",
+ "type": "array",
+ "items": {
+ "$ref": "VisitItem"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "addUrl",
+ "type": "function",
+ "description": "Adds a URL to the history with a default visitTime of the current time and a default $(topic:transition-types)[transition type] of \"link\".",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string",
+ "description": "The URL to add. Must be a valid URL that can be added to history."
+ },
+ "title": {
+ "type": "string",
+ "optional": true,
+ "description": "The title of the page."
+ },
+ "transition": {
+ "$ref": "TransitionType",
+ "optional": true,
+ "description": "The $(topic:transition-types)[transition type] for this visit from its referrer."
+ },
+ "visitTime": {
+ "$ref": "extensionTypes.Date",
+ "optional": true,
+ "description": "The date when this visit occurred."
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "deleteUrl",
+ "type": "function",
+ "description": "Removes all occurrences of the given URL from the history.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string",
+ "description": "The URL to remove."
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "deleteRange",
+ "type": "function",
+ "description": "Removes all items within the specified date range from the history. Pages will not be removed from the history unless all visits fall within the range.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "range",
+ "type": "object",
+ "properties": {
+ "startTime": {
+ "$ref": "extensionTypes.Date",
+ "description": "Items added to history after this date."
+ },
+ "endTime": {
+ "$ref": "extensionTypes.Date",
+ "description": "Items added to history before this date."
+ }
+ }
+ },
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "deleteAll",
+ "type": "function",
+ "description": "Deletes all items from the history.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "callback",
+ "type": "function",
+ "parameters": []
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onVisited",
+ "type": "function",
+ "description": "Fired when a URL is visited, providing the HistoryItem data for that URL. This event fires before the page has loaded.",
+ "parameters": [
+ {
+ "name": "result",
+ "$ref": "HistoryItem"
+ }
+ ]
+ },
+ {
+ "name": "onVisitRemoved",
+ "type": "function",
+ "description": "Fired when one or more URLs are removed from the history service. When all visits have been removed the URL is purged from history.",
+ "parameters": [
+ {
+ "name": "removed",
+ "type": "object",
+ "properties": {
+ "allHistory": {
+ "type": "boolean",
+ "description": "True if all history was removed. If true, then urls will be empty."
+ },
+ "urls": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/jar.mn b/browser/components/extensions/schemas/jar.mn
new file mode 100644
index 000000000..c9fc9a808
--- /dev/null
+++ b/browser/components/extensions/schemas/jar.mn
@@ -0,0 +1,16 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/schemas/bookmarks.json
+ content/browser/schemas/browser_action.json
+ content/browser/schemas/commands.json
+ content/browser/schemas/context_menus.json
+ content/browser/schemas/context_menus_internal.json
+ content/browser/schemas/history.json
+ content/browser/schemas/omnibox.json
+ content/browser/schemas/page_action.json
+ content/browser/schemas/sessions.json
+ content/browser/schemas/tabs.json
+ content/browser/schemas/windows.json
diff --git a/browser/components/extensions/schemas/moz.build b/browser/components/extensions/schemas/moz.build
new file mode 100644
index 000000000..aac3a838c
--- /dev/null
+++ b/browser/components/extensions/schemas/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/components/extensions/schemas/omnibox.json b/browser/components/extensions/schemas/omnibox.json
new file mode 100644
index 000000000..34428fab7
--- /dev/null
+++ b/browser/components/extensions/schemas/omnibox.json
@@ -0,0 +1,248 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "WebExtensionManifest",
+ "properties": {
+ "omnibox": {
+ "type": "object",
+ "additionalProperties": { "$ref": "UnrecognizedProperty" },
+ "properties": {
+ "keyword": {
+ "type": "string",
+ "pattern": "^[^?\\s:]([^\\s:]*[^/\\s:])?$"
+ }
+ },
+ "optional": true
+ }
+ }
+ }
+ ]
+ },
+ {
+ "namespace": "omnibox",
+ "description": "The omnibox API allows you to register a keyword with Firefox's address bar.",
+ "permissions": ["manifest:omnibox"],
+ "types": [
+ {
+ "id": "DescriptionStyleType",
+ "type": "string",
+ "description": "The style type.",
+ "enum": ["url", "match", "dim"]
+ },
+ {
+ "id": "OnInputEnteredDisposition",
+ "type": "string",
+ "enum": ["currentTab", "newForegroundTab", "newBackgroundTab"],
+ "description": "The window disposition for the omnibox query. This is the recommended context to display results. For example, if the omnibox command is to navigate to a certain URL, a disposition of 'newForegroundTab' means the navigation should take place in a new selected tab."
+ },
+ {
+ "id": "SuggestResult",
+ "type": "object",
+ "description": "A suggest result.",
+ "properties": {
+ "content": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The text that is put into the URL bar, and that is sent to the extension when the user chooses this entry."
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The text that is displayed in the URL dropdown. Can contain XML-style markup for styling. The supported tags are 'url' (for a literal URL), 'match' (for highlighting text that matched what the user's query), and 'dim' (for dim helper text). The styles can be nested, eg. <dim><match>dimmed match</match></dim>. You must escape the five predefined entities to display them as text: stackoverflow.com/a/1091953/89484 "
+ },
+ "descriptionStyles": {
+ "optional": true,
+ "unsupported": true,
+ "type": "array",
+ "description": "An array of style ranges for the description, as provided by the extension.",
+ "items": {
+ "type": "object",
+ "description": "The style ranges for the description, as provided by the extension.",
+ "properties": {
+ "offset": { "type": "integer" },
+ "type": { "description": "The style type", "$ref": "DescriptionStyleType"},
+ "length": { "type": "integer", "optional": true }
+ }
+ }
+ },
+ "descriptionStylesRaw": {
+ "optional": true,
+ "unsupported": true,
+ "type": "array",
+ "description": "An array of style ranges for the description, as provided by ToValue().",
+ "items": {
+ "type": "object",
+ "description": "The style ranges for the description, as provided by ToValue().",
+ "properties": {
+ "offset": { "type": "integer" },
+ "type": { "type": "integer" }
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": "DefaultSuggestResult",
+ "type": "object",
+ "description": "A suggest result.",
+ "properties": {
+ "description": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The text that is displayed in the URL dropdown."
+ },
+ "descriptionStyles": {
+ "optional": true,
+ "unsupported": true,
+ "type": "array",
+ "description": "An array of style ranges for the description, as provided by the extension.",
+ "items": {
+ "type": "object",
+ "description": "The style ranges for the description, as provided by the extension.",
+ "properties": {
+ "offset": { "type": "integer" },
+ "type": { "description": "The style type", "$ref": "DescriptionStyleType"},
+ "length": { "type": "integer", "optional": true }
+ }
+ }
+ },
+ "descriptionStylesRaw": {
+ "optional": true,
+ "unsupported": true,
+ "type": "array",
+ "description": "An array of style ranges for the description, as provided by ToValue().",
+ "items": {
+ "type": "object",
+ "description": "The style ranges for the description, as provided by ToValue().",
+ "properties": {
+ "offset": { "type": "integer" },
+ "type": { "type": "integer" }
+ }
+ }
+ }
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "setDefaultSuggestion",
+ "type": "function",
+ "description": "Sets the description and styling for the default suggestion. The default suggestion is the text that is displayed in the first suggestion row underneath the URL bar.",
+ "parameters": [
+ {
+ "name": "suggestion",
+ "$ref": "DefaultSuggestResult",
+ "description": "A partial SuggestResult object, without the 'content' parameter."
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onInputStarted",
+ "type": "function",
+ "description": "User has started a keyword input session by typing the extension's keyword. This is guaranteed to be sent exactly once per input session, and before any onInputChanged events.",
+ "parameters": []
+ },
+ {
+ "name": "onInputChanged",
+ "type": "function",
+ "description": "User has changed what is typed into the omnibox.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "text"
+ },
+ {
+ "name": "suggest",
+ "type": "function",
+ "description": "A callback passed to the onInputChanged event used for sending suggestions back to the browser.",
+ "parameters": [
+ {
+ "name": "suggestResults",
+ "type": "array",
+ "description": "Array of suggest results",
+ "items": {
+ "$ref": "SuggestResult"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "onInputEntered",
+ "type": "function",
+ "description": "User has accepted what is typed into the omnibox.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "text"
+ },
+ {
+ "name": "disposition",
+ "$ref": "OnInputEnteredDisposition"
+ }
+ ]
+ },
+ {
+ "name": "onInputCancelled",
+ "type": "function",
+ "description": "User has ended the keyword input session without accepting the input.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "namespace": "omnibox_internal",
+ "description": "The internal namespace used by the omnibox API.",
+ "defaultContexts": ["addon_parent_only"],
+ "functions": [
+ {
+ "name": "addSuggestions",
+ "type": "function",
+ "async": "callback",
+ "description": "Internal function used by omnibox.onInputChanged for adding search suggestions",
+ "parameters": [
+ {
+ "name": "id",
+ "type": "integer",
+ "description": "The ID of the callback received by onInputChangedInternal"
+ },
+ {
+ "name": "suggestResults",
+ "type": "array",
+ "description": "Array of suggest results",
+ "items": {
+ "$ref": "omnibox.SuggestResult"
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onInputChanged",
+ "type": "function",
+ "description": "Identical to omnibox.onInputChanged except no 'suggest' callback is provided.",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "text"
+ }
+ ]
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/browser/components/extensions/schemas/page_action.json b/browser/components/extensions/schemas/page_action.json
new file mode 100644
index 000000000..f4f9ee8db
--- /dev/null
+++ b/browser/components/extensions/schemas/page_action.json
@@ -0,0 +1,234 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "WebExtensionManifest",
+ "properties": {
+ "page_action": {
+ "type": "object",
+ "additionalProperties": { "$ref": "UnrecognizedProperty" },
+ "properties": {
+ "default_title": {
+ "type": "string",
+ "optional": true,
+ "preprocess": "localize"
+ },
+ "default_icon": {
+ "$ref": "IconPath",
+ "optional": true
+ },
+ "default_popup": {
+ "type": "string",
+ "format": "relativeUrl",
+ "optional": true,
+ "preprocess": "localize"
+ },
+ "browser_style": {
+ "type": "boolean",
+ "optional": true
+ }
+ },
+ "optional": true
+ }
+ }
+ }
+ ]
+ },
+ {
+ "namespace": "pageAction",
+ "description": "Use the <code>browser.pageAction</code> API to put icons inside the address bar. Page actions represent actions that can be taken on the current page, but that aren't applicable to all pages.",
+ "permissions": ["manifest:page_action"],
+ "types": [
+ {
+ "id": "ImageDataType",
+ "type": "object",
+ "isInstanceOf": "ImageData",
+ "additionalProperties": { "type": "any" },
+ "postprocess": "convertImageDataToURL",
+ "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
+ }
+ ],
+ "functions": [
+ {
+ "name": "show",
+ "type": "function",
+ "async": "callback",
+ "description": "Shows the page action. The page action is shown whenever the tab is selected.",
+ "parameters": [
+ {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "hide",
+ "type": "function",
+ "async": "callback",
+ "description": "Hides the page action.",
+ "parameters": [
+ {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "setTitle",
+ "type": "function",
+ "description": "Sets the title of the page action. This is displayed in a tooltip over the page action.",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+ "title": {"type": "string", "description": "The tooltip string."}
+ }
+ }
+ ]
+ },
+ {
+ "name": "getTitle",
+ "type": "function",
+ "description": "Gets the title of the page action.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "description": "Specify the tab to get the title from."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "result",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setIcon",
+ "type": "function",
+ "description": "Sets the icon for the page action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+ "imageData": {
+ "choices": [
+ { "$ref": "ImageDataType" },
+ {
+ "type": "object",
+ "additionalProperties": {"$ref": "ImageDataType"}
+ }
+ ],
+ "optional": true,
+ "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
+ },
+ "path": {
+ "choices": [
+ { "type": "string" },
+ {
+ "type": "object",
+ "additionalProperties": {"type": "string"}
+ }
+ ],
+ "optional": true,
+ "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "setPopup",
+ "type": "function",
+ "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+ "popup": {
+ "type": "string",
+ "description": "The html file to show in a popup. If set to the empty string (''), no popup is shown."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "getPopup",
+ "type": "function",
+ "description": "Gets the html document set as the popup for this page action.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "details",
+ "type": "object",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "description": "Specify the tab to get the popup from."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "result",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onClicked",
+ "type": "function",
+ "description": "Fired when a page action icon is clicked. This event will not fire if the page action has a popup.",
+ "parameters": [
+ {
+ "name": "tab",
+ "$ref": "tabs.Tab"
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/sessions.json b/browser/components/extensions/schemas/sessions.json
new file mode 100644
index 000000000..690bb8ebc
--- /dev/null
+++ b/browser/components/extensions/schemas/sessions.json
@@ -0,0 +1,146 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "Permission",
+ "choices": [{
+ "type": "string",
+ "enum": [
+ "sessions"
+ ]
+ }]
+ }
+ ]
+ },
+ {
+ "namespace": "sessions",
+ "description": "Use the <code>chrome.sessions</code> API to query and restore tabs and windows from a browsing session.",
+ "permissions": ["sessions"],
+ "types": [
+ {
+ "id": "Filter",
+ "type": "object",
+ "properties": {
+ "maxResults": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 25,
+ "optional": true,
+ "description": "The maximum number of entries to be fetched in the requested list. Omit this parameter to fetch the maximum number of entries ($(ref:sessions.MAX_SESSION_RESULTS))."
+ }
+ }
+ },
+ {
+ "id": "Session",
+ "type": "object",
+ "properties": {
+ "lastModified": {"type": "integer", "description": "The time when the window or tab was closed or modified, represented in milliseconds since the epoch."},
+ "tab": {"$ref": "tabs.Tab", "optional": true, "description": "The $(ref:tabs.Tab), if this entry describes a tab. Either this or $(ref:sessions.Session.window) will be set."},
+ "window": {"$ref": "windows.Window", "optional": true, "description": "The $(ref:windows.Window), if this entry describes a window. Either this or $(ref:sessions.Session.tab) will be set."}
+ }
+ },
+ {
+ "id": "Device",
+ "type": "object",
+ "properties": {
+ "info": {"type": "string"},
+ "deviceName": {"type": "string", "description": "The name of the foreign device."},
+ "sessions": {"type": "array", "items": {"$ref": "Session"}, "description": "A list of open window sessions for the foreign device, sorted from most recently to least recently modified session."}
+ }
+ }
+ ],
+ "functions": [
+ {
+ "name": "getRecentlyClosed",
+ "type": "function",
+ "description": "Gets the list of recently closed tabs and/or windows.",
+ "async": "callback",
+ "parameters": [
+ {
+ "$ref": "Filter",
+ "name": "filter",
+ "optional": true,
+ "default": {}
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "sessions", "type": "array", "items": { "$ref": "Session" }, "description": "The list of closed entries in reverse order that they were closed (the most recently closed tab or window will be at index <code>0</code>). The entries may contain either tabs or windows."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getDevices",
+ "unsupported": true,
+ "type": "function",
+ "description": "Retrieves all devices with synced sessions.",
+ "async": "callback",
+ "parameters": [
+ {
+ "$ref": "Filter",
+ "name": "filter",
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "devices", "type": "array", "items": { "$ref": "Device" }, "description": "The list of $(ref:sessions.Device) objects for each synced session, sorted in order from device with most recently modified session to device with least recently modified session. $(ref:tabs.Tab) objects are sorted by recency in the $(ref:windows.Window) of the $(ref:sessions.Session) objects."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "restore",
+ "type": "function",
+ "description": "Reopens a $(ref:windows.Window) or $(ref:tabs.Tab), with an optional callback to run when the entry has been restored.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "sessionId",
+ "optional": true,
+ "description": "The $(ref:windows.Window.sessionId), or $(ref:tabs.Tab.sessionId) to restore. If this parameter is not specified, the most recently closed session is restored."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "$ref": "Session",
+ "name": "restoredSession",
+ "description": "A $(ref:sessions.Session) containing the restored $(ref:windows.Window) or $(ref:tabs.Tab) object."
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onChanged",
+ "unsupported": true,
+ "description": "Fired when recently closed tabs and/or windows are changed. This event does not monitor synced sessions changes.",
+ "type": "function"
+ }
+ ],
+ "properties": {
+ "MAX_SESSION_RESULTS": {
+ "value": 25,
+ "description": "The maximum number of $(ref:sessions.Session) that will be included in a requested list."
+ }
+ }
+ }
+]
diff --git a/browser/components/extensions/schemas/tabs.json b/browser/components/extensions/schemas/tabs.json
new file mode 100644
index 000000000..23ce33a4b
--- /dev/null
+++ b/browser/components/extensions/schemas/tabs.json
@@ -0,0 +1,1295 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "Permission",
+ "choices": [{
+ "type": "string",
+ "enum": [
+ "activeTab",
+ "tabs"
+ ]
+ }]
+ }
+ ]
+ },
+ {
+ "namespace": "tabs",
+ "description": "Use the <code>browser.tabs</code> API to interact with the browser's tab system. You can use this API to create, modify, and rearrange tabs in the browser.",
+ "types": [
+ { "id": "MutedInfoReason",
+ "type": "string",
+ "description": "An event that caused a muted state change.",
+ "enum": [
+ {"name": "user", "description": "A user input action has set/overridden the muted state."},
+ {"name": "capture", "description": "Tab capture started, forcing a muted state change."},
+ {"name": "extension", "description": "An extension, identified by the extensionId field, set the muted state."}
+ ]
+ },
+ {
+ "id": "MutedInfo",
+ "type": "object",
+ "description": "Tab muted state and the reason for the last state change.",
+ "properties": {
+ "muted": {
+ "type": "boolean",
+ "description": "Whether the tab is prevented from playing sound (but hasn't necessarily recently produced sound). Equivalent to whether the muted audio indicator is showing."
+ },
+ "reason": {
+ "$ref": "MutedInfoReason",
+ "optional": true,
+ "description": "The reason the tab was muted or unmuted. Not set if the tab's mute state has never been changed."
+ },
+ "extensionId": {
+ "type": "string",
+ "optional": true,
+ "description": "The ID of the extension that changed the muted state. Not set if an extension was not the reason the muted state last changed."
+ }
+ }
+ },
+ {
+ "id": "Tab",
+ "type": "object",
+ "properties": {
+ "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."},
+ "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."},
+ "windowId": {"type": "integer", "minimum": 0, "description": "The ID of the window the tab is contained within."},
+ "openerTabId": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
+ "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true},
+ "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted."},
+ "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"},
+ "pinned": {"type": "boolean", "description": "Whether the tab is pinned."},
+ "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."},
+ "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."},
+ "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
+ "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
+ "favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission. It may also be an empty string if the tab is loading."},
+ "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
+ "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
+ "width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
+ "height": {"type": "integer", "optional": true, "description": "The height of the tab in pixels."},
+ "sessionId": {"unsupported": true, "type": "string", "optional": true, "description": "The session ID used to uniquely identify a Tab obtained from the $(ref:sessions) API."},
+ "cookieStoreId": {"type": "string", "description": "The CookieStoreId used for the tab."}
+ }
+ },
+ {
+ "id": "ZoomSettingsMode",
+ "type": "string",
+ "description": "Defines how zoom changes are handled, i.e. which entity is responsible for the actual scaling of the page; defaults to <code>automatic</code>.",
+ "enum": [
+ {
+ "name": "automatic",
+ "description": "Zoom changes are handled automatically by the browser."
+ },
+ {
+ "name": "manual",
+ "description": "Overrides the automatic handling of zoom changes. The <code>onZoomChange</code> event will still be dispatched, and it is the responsibility of the extension to listen for this event and manually scale the page. This mode does not support <code>per-origin</code> zooming, and will thus ignore the <code>scope</code> zoom setting and assume <code>per-tab</code>."
+ },
+ {
+ "name": "disabled",
+ "description": "Disables all zooming in the tab. The tab will revert to the default zoom level, and all attempted zoom changes will be ignored."
+ }
+ ]
+ },
+ {
+ "id": "ZoomSettingsScope",
+ "type": "string",
+ "description": "Defines whether zoom changes will persist for the page's origin, or only take effect in this tab; defaults to <code>per-origin</code> when in <code>automatic</code> mode, and <code>per-tab</code> otherwise.",
+ "enum": [
+ {
+ "name": "per-origin",
+ "description": "Zoom changes will persist in the zoomed page's origin, i.e. all other tabs navigated to that same origin will be zoomed as well. Moreover, <code>per-origin</code> zoom changes are saved with the origin, meaning that when navigating to other pages in the same origin, they will all be zoomed to the same zoom factor. The <code>per-origin</code> scope is only available in the <code>automatic</code> mode."
+ },
+ {
+ "name": "per-tab",
+ "description": "Zoom changes only take effect in this tab, and zoom changes in other tabs will not affect the zooming of this tab. Also, <code>per-tab</code> zoom changes are reset on navigation; navigating a tab will always load pages with their <code>per-origin</code> zoom factors."
+ }
+ ]
+ },
+ {
+ "id": "ZoomSettings",
+ "type": "object",
+ "description": "Defines how zoom changes in a tab are handled and at what scope.",
+ "properties": {
+ "mode": {
+ "$ref": "ZoomSettingsMode",
+ "description": "Defines how zoom changes are handled, i.e. which entity is responsible for the actual scaling of the page; defaults to <code>automatic</code>.",
+ "optional": true
+ },
+ "scope": {
+ "$ref": "ZoomSettingsScope",
+ "description": "Defines whether zoom changes will persist for the page's origin, or only take effect in this tab; defaults to <code>per-origin</code> when in <code>automatic</code> mode, and <code>per-tab</code> otherwise.",
+ "optional": true
+ },
+ "defaultZoomFactor": {
+ "type": "number",
+ "optional": true,
+ "description": "Used to return the default zoom level for the current tab in calls to tabs.getZoomSettings."
+ }
+ }
+ },
+ {
+ "id": "TabStatus",
+ "type": "string",
+ "enum": ["loading", "complete"],
+ "description": "Whether the tabs have completed loading."
+ },
+ {
+ "id": "WindowType",
+ "type": "string",
+ "enum": ["normal", "popup", "panel", "app", "devtools"],
+ "description": "The type of window."
+ }
+ ],
+ "properties": {
+ "TAB_ID_NONE": {
+ "value": -1,
+ "description": "An ID which represents the absence of a browser tab."
+ }
+ },
+ "functions": [
+ {
+ "name": "get",
+ "type": "function",
+ "description": "Retrieves details about the specified tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"name": "tab", "$ref": "Tab"}
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getCurrent",
+ "type": "function",
+ "description": "Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "tab",
+ "$ref": "Tab",
+ "optional": true
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "connect",
+ "type": "function",
+ "description": "Connects to the content script(s) in the specified tab. The $(ref:runtime.onConnect) event is fired in each content script running in the specified tab for the current extension. For more details, see $(topic:messaging)[Content Script Messaging].",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0
+ },
+ {
+ "type": "object",
+ "name": "connectInfo",
+ "properties": {
+ "name": { "type": "string", "optional": true, "description": "Will be passed into onConnect for content scripts that are listening for the connection event." },
+ "frameId": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "description": "Open a port to a specific $(topic:frame_ids)[frame] identified by <code>frameId</code> instead of all frames in the tab."
+ }
+ },
+ "optional": true
+ }
+ ],
+ "returns": {
+ "$ref": "runtime.Port",
+ "description": "A port that can be used to communicate with the content scripts running in the specified tab. The port's $(ref:runtime.Port) event is fired if the tab closes or does not exist. "
+ }
+ },
+ {
+ "name": "sendRequest",
+ "deprecated": "Please use $(ref:runtime.sendMessage).",
+ "unsupported": true,
+ "type": "function",
+ "description": "Sends a single request to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The $(ref:extension.onRequest) event is fired in each content script running in the specified tab for the current extension.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0
+ },
+ {
+ "type": "any",
+ "name": "request"
+ },
+ {
+ "type": "function",
+ "name": "responseCallback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "response",
+ "type": "any",
+ "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "sendMessage",
+ "type": "function",
+ "description": "Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The $(ref:runtime.onMessage) event is fired in each content script running in the specified tab for the current extension.",
+ "async": "responseCallback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0
+ },
+ {
+ "type": "any",
+ "name": "message"
+ },
+ {
+ "type": "object",
+ "name": "options",
+ "properties": {
+ "frameId": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "description": "Send a message to a specific $(topic:frame_ids)[frame] identified by <code>frameId</code> instead of all frames in the tab."
+ }
+ },
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "responseCallback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "response",
+ "type": "any",
+ "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getSelected",
+ "deprecated": "Please use $(ref:tabs.query) <code>{active: true}</code>.",
+ "unsupported": true,
+ "type": "function",
+ "description": "Gets the tab that is selected in the specified window.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": -2,
+ "optional": true,
+ "description": "Defaults to the $(topic:current-window)[current window]."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"name": "tab", "$ref": "Tab"}
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getAllInWindow",
+ "deprecated": "Please use $(ref:tabs.query) <code>{windowId: windowId}</code>.",
+ "unsupported": true,
+ "type": "function",
+ "description": "Gets details about all tabs in the specified window.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": -2,
+ "optional": true,
+ "description": "Defaults to the $(topic:current-window)[current window]."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {"name": "tabs", "type": "array", "items": { "$ref": "Tab" } }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "create",
+ "type": "function",
+ "description": "Creates a new tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "createProperties",
+ "properties": {
+ "windowId": {
+ "type": "integer",
+ "minimum": -2,
+ "optional": true,
+ "description": "The window to create the new tab in. Defaults to the $(topic:current-window)[current window]."
+ },
+ "index": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The position the tab should take in the window. The provided value will be clamped to between zero and the number of tabs in the window."
+ },
+ "url": {
+ "type": "string",
+ "optional": true,
+ "description": "The URL to navigate the tab to initially. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page."
+ },
+ "active": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tab should become the active tab in the window. Does not affect whether the window is focused (see $(ref:windows.update)). Defaults to <var>true</var>."
+ },
+ "selected": {
+ "deprecated": "Please use <em>active</em>.",
+ "unsupported": true,
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tab should become the selected tab in the window. Defaults to <var>true</var>"
+ },
+ "pinned": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tab should be pinned. Defaults to <var>false</var>"
+ },
+ "openerTabId": {
+ "unsupported": true,
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as the newly created tab."
+ },
+ "cookieStoreId": {
+ "type": "string",
+ "optional": true,
+ "description": "The CookieStoreId for the tab that opened this tab."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "tab",
+ "$ref": "Tab",
+ "description": "Details about the created tab. Will contain the ID of the new tab."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "duplicate",
+ "type": "function",
+ "description": "Duplicates a tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "description": "The ID of the tab which is to be duplicated."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "tab",
+ "optional": true,
+ "description": "Details about the duplicated tab. The $(ref:tabs.Tab) object doesn't contain <code>url</code>, <code>title</code> and <code>favIconUrl</code> if the <code>\"tabs\"</code> permission has not been requested.",
+ "$ref": "Tab"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "query",
+ "type": "function",
+ "description": "Gets all tabs that have the specified properties, or all tabs if no properties are specified.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "queryInfo",
+ "properties": {
+ "active": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tabs are active in their windows."
+ },
+ "pinned": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tabs are pinned."
+ },
+ "audible": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tabs are audible."
+ },
+ "muted": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tabs are muted."
+ },
+ "highlighted": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tabs are highlighted."
+ },
+ "currentWindow": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tabs are in the $(topic:current-window)[current window]."
+ },
+ "lastFocusedWindow": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tabs are in the last focused window."
+ },
+ "status": {
+ "$ref": "TabStatus",
+ "optional": true,
+ "description": "Whether the tabs have completed loading."
+ },
+ "title": {
+ "type": "string",
+ "optional": true,
+ "description": "Match page titles against a pattern."
+ },
+ "url": {
+ "choices": [
+ {"type": "string"},
+ {"type": "array", "items": {"type": "string"}}
+ ],
+ "optional": true,
+ "description": "Match tabs against one or more $(topic:match_patterns)[URL patterns]. Note that fragment identifiers are not matched."
+ },
+ "windowId": {
+ "type": "integer",
+ "optional": true,
+ "minimum": -2,
+ "description": "The ID of the parent window, or $(ref:windows.WINDOW_ID_CURRENT) for the $(topic:current-window)[current window]."
+ },
+ "windowType": {
+ "$ref": "WindowType",
+ "optional": true,
+ "description": "The type of window the tabs are in."
+ },
+ "index": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "description": "The position of the tabs within their windows."
+ },
+ "cookieStoreId": {
+ "type": "string",
+ "optional": true,
+ "description": "The CookieStoreId used for the tab."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "result",
+ "type": "array",
+ "items": {
+ "$ref": "Tab"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "highlight",
+ "type": "function",
+ "description": "Highlights the given tabs.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "highlightInfo",
+ "properties": {
+ "windowId": {
+ "type": "integer",
+ "optional": true,
+ "description": "The window that contains the tabs.",
+ "minimum": -2
+ },
+ "tabs": {
+ "description": "One or more tab indices to highlight.",
+ "choices": [
+ {"type": "array", "items": {"type": "integer", "minimum": 0}},
+ {"type": "integer"}
+ ]
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "window",
+ "$ref": "windows.Window",
+ "description": "Contains details about the window whose tabs were highlighted."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "update",
+ "type": "function",
+ "description": "Modifies the properties of a tab. Properties that are not specified in <var>updateProperties</var> are not modified.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "Defaults to the selected tab of the $(topic:current-window)[current window]."
+ },
+ {
+ "type": "object",
+ "name": "updateProperties",
+ "properties": {
+ "url": {
+ "type": "string",
+ "optional": true,
+ "description": "A URL to navigate the tab to."
+ },
+ "active": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tab should be active. Does not affect whether the window is focused (see $(ref:windows.update))."
+ },
+ "highlighted": {
+ "unsupported": true,
+ "type": "boolean",
+ "optional": true,
+ "description": "Adds or removes the tab from the current selection."
+ },
+ "selected": {
+ "unsupported": true,
+ "deprecated": "Please use <em>highlighted</em>.",
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tab should be selected."
+ },
+ "pinned": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tab should be pinned."
+ },
+ "muted": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the tab should be muted."
+ },
+ "openerTabId": {
+ "unsupported": true,
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "tab",
+ "$ref": "Tab",
+ "optional": true,
+ "description": "Details about the updated tab. The $(ref:tabs.Tab) object doesn't contain <code>url</code>, <code>title</code> and <code>favIconUrl</code> if the <code>\"tabs\"</code> permission has not been requested."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "move",
+ "type": "function",
+ "description": "Moves one or more tabs to a new position within its window, or to a new window. Note that tabs can only be moved to and from normal (window.type === \"normal\") windows.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "tabIds",
+ "description": "The tab or list of tabs to move.",
+ "choices": [
+ {"type": "integer", "minimum": 0},
+ {"type": "array", "items": {"type": "integer", "minimum": 0}}
+ ]
+ },
+ {
+ "type": "object",
+ "name": "moveProperties",
+ "properties": {
+ "windowId": {
+ "type": "integer",
+ "minimum": -2,
+ "optional": true,
+ "description": "Defaults to the window the tab is currently in."
+ },
+ "index": {
+ "type": "integer",
+ "minimum": -1,
+ "description": "The position to move the window to. -1 will place the tab at the end of the window."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "tabs",
+ "description": "Details about the moved tabs.",
+ "choices": [
+ {"$ref": "Tab"},
+ {"type": "array", "items": {"$ref": "Tab"}}
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "reload",
+ "type": "function",
+ "description": "Reload a tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab to reload; defaults to the selected tab of the current window."
+ },
+ {
+ "type": "object",
+ "name": "reloadProperties",
+ "optional": true,
+ "properties": {
+ "bypassCache": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether using any local cache. Default is false."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "remove",
+ "type": "function",
+ "description": "Closes one or more tabs.",
+ "async": "callback",
+ "parameters": [
+ {
+ "name": "tabIds",
+ "description": "The tab or list of tabs to close.",
+ "choices": [
+ {"type": "integer", "minimum": 0},
+ {"type": "array", "items": {"type": "integer", "minimum": 0}}
+ ]
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "detectLanguage",
+ "type": "function",
+ "description": "Detects the primary language of the content in a tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "Defaults to the active tab of the $(topic:current-window)[current window]."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "language",
+ "description": "An ISO language code such as <code>en</code> or <code>fr</code>. For a complete list of languages supported by this method, see <a href='http://src.chromium.org/viewvc/chrome/trunk/src/third_party/cld/languages/internal/languages.cc'>kLanguageInfoTable</a>. The 2nd to 4th columns will be checked and the first non-NULL value will be returned except for Simplified Chinese for which zh-CN will be returned. For an unknown language, <code>und</code> will be returned."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "captureVisibleTab",
+ "type": "function",
+ "description": "Captures the visible area of the currently active tab in the specified window. You must have $(topic:declare_permissions)[&lt;all_urls&gt;] permission to use this method.",
+ "permissions": ["<all_urls>"],
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": -2,
+ "optional": true,
+ "description": "The target window. Defaults to the $(topic:current-window)[current window]."
+ },
+ {
+ "$ref": "extensionTypes.ImageDetails",
+ "name": "options",
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "type": "string",
+ "name": "dataUrl",
+ "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "executeScript",
+ "type": "function",
+ "description": "Injects JavaScript code into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab in which to run the script; defaults to the active tab of the current window."
+ },
+ {
+ "$ref": "extensionTypes.InjectDetails",
+ "name": "details",
+ "description": "Details of the script to run."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called after all the JavaScript has been executed.",
+ "parameters": [
+ {
+ "name": "result",
+ "optional": true,
+ "type": "array",
+ "items": {"type": "any"},
+ "description": "The result of the script in every injected frame."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "insertCSS",
+ "type": "function",
+ "description": "Injects CSS into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab in which to insert the CSS; defaults to the active tab of the current window."
+ },
+ {
+ "$ref": "extensionTypes.InjectDetails",
+ "name": "details",
+ "description": "Details of the CSS text to insert."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when all the CSS has been inserted.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "removeCSS",
+ "type": "function",
+ "description": "Removes injected CSS from a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab from which to remove the injected CSS; defaults to the active tab of the current window."
+ },
+ {
+ "$ref": "extensionTypes.InjectDetails",
+ "name": "details",
+ "description": "Details of the CSS text to remove."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called when all the CSS has been removed.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "setZoom",
+ "type": "function",
+ "description": "Zooms a specified tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab to zoom; defaults to the active tab of the current window."
+ },
+ {
+ "type": "number",
+ "name": "zoomFactor",
+ "description": "The new zoom factor. Use a value of 0 here to set the tab to its current default zoom factor. Values greater than zero specify a (possibly non-default) zoom factor for the tab."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called after the zoom factor has been changed.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getZoom",
+ "type": "function",
+ "description": "Gets the current zoom factor of a specified tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "optional": true,
+ "description": "The ID of the tab to get the current zoom factor from; defaults to the active tab of the current window."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called with the tab's current zoom factor after it has been fetched.",
+ "parameters": [
+ {
+ "type": "number",
+ "name": "zoomFactor",
+ "description": "The tab's current zoom factor."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "setZoomSettings",
+ "type": "function",
+ "description": "Sets the zoom settings for a specified tab, which define how zoom changes are handled. These settings are reset to defaults upon navigating the tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "optional": true,
+ "minimum": 0,
+ "description": "The ID of the tab to change the zoom settings for; defaults to the active tab of the current window."
+ },
+ {
+ "$ref": "ZoomSettings",
+ "name": "zoomSettings",
+ "description": "Defines how zoom changes are handled and at what scope."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "description": "Called after the zoom settings have been changed.",
+ "parameters": []
+ }
+ ]
+ },
+ {
+ "name": "getZoomSettings",
+ "type": "function",
+ "description": "Gets the current zoom settings of a specified tab.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "optional": true,
+ "minimum": 0,
+ "description": "The ID of the tab to get the current zoom settings from; defaults to the active tab of the current window."
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "description": "Called with the tab's current zoom settings.",
+ "parameters": [
+ {
+ "$ref": "ZoomSettings",
+ "name": "zoomSettings",
+ "description": "The tab's current zoom settings."
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onCreated",
+ "type": "function",
+ "description": "Fired when a tab is created. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.",
+ "parameters": [
+ {
+ "$ref": "Tab",
+ "name": "tab",
+ "description": "Details of the tab that was created."
+ }
+ ]
+ },
+ {
+ "name": "onUpdated",
+ "type": "function",
+ "description": "Fired when a tab is updated.",
+ "parameters": [
+ {"type": "integer", "name": "tabId", "minimum": 0},
+ {
+ "type": "object",
+ "name": "changeInfo",
+ "description": "Lists the changes to the state of the tab that was updated.",
+ "properties": {
+ "status": {
+ "type": "string",
+ "optional": true,
+ "description": "The status of the tab. Can be either <em>loading</em> or <em>complete</em>."
+ },
+ "url": {
+ "type": "string",
+ "optional": true,
+ "description": "The tab's URL if it has changed."
+ },
+ "pinned": {
+ "type": "boolean",
+ "optional": true,
+ "description": "The tab's new pinned state."
+ },
+ "audible": {
+ "type": "boolean",
+ "optional": true,
+ "description": "The tab's new audible state."
+ },
+ "mutedInfo": {
+ "$ref": "MutedInfo",
+ "optional": true,
+ "description": "The tab's new muted state and the reason for the change."
+ },
+ "favIconUrl": {
+ "type": "string",
+ "optional": true,
+ "description": "The tab's new favicon URL."
+ }
+ }
+ },
+ {
+ "$ref": "Tab",
+ "name": "tab",
+ "description": "Gives the state of the tab that was updated."
+ }
+ ]
+ },
+ {
+ "name": "onMoved",
+ "type": "function",
+ "description": "Fired when a tab is moved within a window. Only one move event is fired, representing the tab the user directly moved. Move events are not fired for the other tabs that must move in response. This event is not fired when a tab is moved between windows. For that, see $(ref:tabs.onDetached).",
+ "parameters": [
+ {"type": "integer", "name": "tabId", "minimum": 0},
+ {
+ "type": "object",
+ "name": "moveInfo",
+ "properties": {
+ "windowId": {"type": "integer", "minimum": 0},
+ "fromIndex": {"type": "integer", "minimum": 0},
+ "toIndex": {"type": "integer", "minimum": 0}
+ }
+ }
+ ]
+ },
+ {
+ "name": "onSelectionChanged",
+ "deprecated": "Please use $(ref:tabs.onActivated).",
+ "unsupported": true,
+ "type": "function",
+ "description": "Fires when the selected tab in a window changes.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "description": "The ID of the tab that has become active."
+ },
+ {
+ "type": "object",
+ "name": "selectInfo",
+ "properties": {
+ "windowId": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The ID of the window the selected tab changed inside of."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onActiveChanged",
+ "deprecated": "Please use $(ref:tabs.onActivated).",
+ "unsupported": true,
+ "type": "function",
+ "description": "Fires when the selected tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to $(ref:tabs.onUpdated) events to be notified when a URL is set.",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "tabId",
+ "minimum": 0,
+ "description": "The ID of the tab that has become active."
+ },
+ {
+ "type": "object",
+ "name": "selectInfo",
+ "properties": {
+ "windowId": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The ID of the window the selected tab changed inside of."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onActivated",
+ "type": "function",
+ "description": "Fires when the active tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "activeInfo",
+ "properties": {
+ "tabId": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The ID of the tab that has become active."
+ },
+ "windowId": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The ID of the window the active tab changed inside of."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onHighlightChanged",
+ "deprecated": "Please use $(ref:tabs.onHighlighted).",
+ "unsupported": true,
+ "type": "function",
+ "description": "Fired when the highlighted or selected tabs in a window changes.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "selectInfo",
+ "properties": {
+ "windowId": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The window whose tabs changed."
+ },
+ "tabIds": {
+ "type": "array",
+ "items": {"type": "integer", "minimum": 0},
+ "description": "All highlighted tabs in the window."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onHighlighted",
+ "type": "function",
+ "description": "Fired when the highlighted or selected tabs in a window changes.",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "highlightInfo",
+ "properties": {
+ "windowId": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The window whose tabs changed."
+ },
+ "tabIds": {
+ "type": "array",
+ "items": {"type": "integer", "minimum": 0},
+ "description": "All highlighted tabs in the window."
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onDetached",
+ "type": "function",
+ "description": "Fired when a tab is detached from a window, for example because it is being moved between windows.",
+ "parameters": [
+ {"type": "integer", "name": "tabId", "minimum": 0},
+ {
+ "type": "object",
+ "name": "detachInfo",
+ "properties": {
+ "oldWindowId": {"type": "integer", "minimum": 0},
+ "oldPosition": {"type": "integer", "minimum": 0}
+ }
+ }
+ ]
+ },
+ {
+ "name": "onAttached",
+ "type": "function",
+ "description": "Fired when a tab is attached to a window, for example because it was moved between windows.",
+ "parameters": [
+ {"type": "integer", "name": "tabId", "minimum": 0},
+ {
+ "type": "object",
+ "name": "attachInfo",
+ "properties": {
+ "newWindowId": {"type": "integer", "minimum": 0},
+ "newPosition": {"type": "integer", "minimum": 0}
+ }
+ }
+ ]
+ },
+ {
+ "name": "onRemoved",
+ "type": "function",
+ "description": "Fired when a tab is closed.",
+ "parameters": [
+ {"type": "integer", "name": "tabId", "minimum": 0},
+ {
+ "type": "object",
+ "name": "removeInfo",
+ "properties": {
+ "windowId": {"type": "integer", "minimum": 0, "description": "The window whose tab is closed." },
+ "isWindowClosing": {"type": "boolean", "description": "True when the tab is being closed because its window is being closed." }
+ }
+ }
+ ]
+ },
+ {
+ "name": "onReplaced",
+ "type": "function",
+ "description": "Fired when a tab is replaced with another tab due to prerendering or instant.",
+ "parameters": [
+ {"type": "integer", "name": "addedTabId", "minimum": 0},
+ {"type": "integer", "name": "removedTabId", "minimum": 0}
+ ]
+ },
+ {
+ "name": "onZoomChange",
+ "type": "function",
+ "description": "Fired when a tab is zoomed.",
+ "parameters": [{
+ "type": "object",
+ "name": "ZoomChangeInfo",
+ "properties": {
+ "tabId": {"type": "integer", "minimum": 0},
+ "oldZoomFactor": {"type": "number"},
+ "newZoomFactor": {"type": "number"},
+ "zoomSettings": {"$ref": "ZoomSettings"}
+ }
+ }]
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/schemas/windows.json b/browser/components/extensions/schemas/windows.json
new file mode 100644
index 000000000..8453358b5
--- /dev/null
+++ b/browser/components/extensions/schemas/windows.json
@@ -0,0 +1,508 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "windows",
+ "description": "Use the <code>browser.windows</code> API to interact with browser windows. You can use this API to create, modify, and rearrange windows in the browser.",
+ "types": [
+ {
+ "id": "WindowType",
+ "type": "string",
+ "description": "The type of browser window this is. Under some circumstances a Window may not be assigned type property, for example when querying closed windows from the $(ref:sessions) API.",
+ "enum": ["normal", "popup", "panel", "app", "devtools"]
+ },
+ {
+ "id": "WindowState",
+ "type": "string",
+ "description": "The state of this browser window. Under some circumstances a Window may not be assigned state property, for example when querying closed windows from the $(ref:sessions) API.",
+ "enum": ["normal", "minimized", "maximized", "fullscreen", "docked"]
+ },
+ {
+ "id": "Window",
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "optional": true,
+ "minimum": 0,
+ "description": "The ID of the window. Window IDs are unique within a browser session. Under some circumstances a Window may not be assigned an ID, for example when querying windows using the $(ref:sessions) API, in which case a session ID may be present."
+ },
+ "focused": {
+ "type": "boolean",
+ "description": "Whether the window is currently the focused window."
+ },
+ "top": {
+ "type": "integer",
+ "optional": true,
+ "description": "The offset of the window from the top edge of the screen in pixels. Under some circumstances a Window may not be assigned top property, for example when querying closed windows from the $(ref:sessions) API."
+ },
+ "left": {
+ "type": "integer",
+ "optional": true,
+ "description": "The offset of the window from the left edge of the screen in pixels. Under some circumstances a Window may not be assigned left property, for example when querying closed windows from the $(ref:sessions) API."
+ },
+ "width": {
+ "type": "integer",
+ "optional": true,
+ "description": "The width of the window, including the frame, in pixels. Under some circumstances a Window may not be assigned width property, for example when querying closed windows from the $(ref:sessions) API."
+ },
+ "height": {
+ "type": "integer",
+ "optional": true,
+ "description": "The height of the window, including the frame, in pixels. Under some circumstances a Window may not be assigned height property, for example when querying closed windows from the $(ref:sessions) API."
+ },
+ "tabs": {
+ "type": "array",
+ "items": { "$ref": "tabs.Tab" },
+ "optional": true,
+ "description": "Array of $(ref:tabs.Tab) objects representing the current tabs in the window."
+ },
+ "incognito": {
+ "type": "boolean",
+ "description": "Whether the window is incognito."
+ },
+ "type": {
+ "$ref": "WindowType",
+ "optional": true,
+ "description": "The type of browser window this is."
+ },
+ "state": {
+ "$ref": "WindowState",
+ "optional": true,
+ "description": "The state of this browser window."
+ },
+ "alwaysOnTop": {
+ "type": "boolean",
+ "description": "Whether the window is set to be always on top."
+ },
+ "sessionId": {
+ "type": "string",
+ "optional": true,
+ "description": "The session ID used to uniquely identify a Window obtained from the $(ref:sessions) API."
+ }
+ }
+ },
+ {
+ "id": "CreateType",
+ "type": "string",
+ "description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set.",
+ "enum": ["normal", "popup", "panel", "detached_panel"]
+ }
+ ],
+ "properties": {
+ "WINDOW_ID_NONE": {
+ "value": -1,
+ "description": "The windowId value that represents the absence of a browser window."
+ },
+ "WINDOW_ID_CURRENT": {
+ "value": -2,
+ "description": "The windowId value that represents the $(topic:current-window)[current window]."
+ }
+ },
+ "functions": [
+ {
+ "name": "get",
+ "type": "function",
+ "description": "Gets details about a window.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": -2
+ },
+ {
+ "type": "object",
+ "name": "getInfo",
+ "optional": true,
+ "description": "",
+ "properties": {
+ "populate": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+ },
+ "windowTypes": {
+ "type": "array",
+ "items": {
+ "$ref": "WindowType"
+ },
+ "optional": true,
+ "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "window",
+ "$ref": "Window"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getCurrent",
+ "type": "function",
+ "description": "Gets the $(topic:current-window)[current window].",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "getInfo",
+ "optional": true,
+ "description": "",
+ "properties": {
+ "populate": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+ },
+ "windowTypes": {
+ "type": "array",
+ "items": { "$ref": "WindowType" },
+ "optional": true,
+ "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "window",
+ "$ref": "Window"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getLastFocused",
+ "type": "function",
+ "description": "Gets the window that was most recently focused &mdash; typically the window 'on top'.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "getInfo",
+ "optional": true,
+ "description": "",
+ "properties": {
+ "populate": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+ },
+ "windowTypes": {
+ "type": "array",
+ "items": { "$ref": "WindowType" },
+ "optional": true,
+ "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "window",
+ "$ref": "Window"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "getAll",
+ "type": "function",
+ "description": "Gets all windows.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "getInfo",
+ "optional": true,
+ "description": "",
+ "properties": {
+ "populate": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, each $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects for that window. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+ },
+ "windowTypes": {
+ "type": "array",
+ "items": { "$ref": "WindowType" },
+ "optional": true,
+ "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "parameters": [
+ {
+ "name": "windows",
+ "type": "array",
+ "items": { "$ref": "Window" }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "create",
+ "type": "function",
+ "description": "Creates (opens) a new browser with any optional sizing, position or default URL provided.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "object",
+ "name": "createData",
+ "optional": true,
+ "default": {},
+ "properties": {
+ "url": {
+ "description": "A URL or array of URLs to open as tabs in the window. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page.",
+ "optional": true,
+ "choices": [
+ { "type": "string", "format": "relativeUrl" },
+ {
+ "type": "array",
+ "items": { "type": "string", "format": "relativeUrl" }
+ }
+ ]
+ },
+ "tabId": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The id of the tab for which you want to adopt to the new window."
+ },
+ "left": {
+ "type": "integer",
+ "optional": true,
+ "description": "The number of pixels to position the new window from the left edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."
+ },
+ "top": {
+ "type": "integer",
+ "optional": true,
+ "description": "The number of pixels to position the new window from the top edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The width in pixels of the new window, including the frame. If not specified defaults to a natural width."
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The height in pixels of the new window, including the frame. If not specified defaults to a natural height."
+ },
+ "focused": {
+ "unsupported": true,
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, opens an active window. If false, opens an inactive window."
+ },
+ "incognito": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Whether the new window should be an incognito window."
+ },
+ "type": {
+ "$ref": "CreateType",
+ "optional": true,
+ "description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set."
+ },
+ "state": {
+ "$ref": "WindowState",
+ "optional": true,
+ "description": "The initial state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
+ },
+ "allowScriptsToClose": {
+ "type": "boolean",
+ "optional": true,
+ "description": "Allow scripts to close the window."
+ }
+ },
+ "optional": true
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "window",
+ "$ref": "Window",
+ "description": "Contains details about the created window.",
+ "optional": true
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "update",
+ "type": "function",
+ "description": "Updates the properties of a window. Specify only the properties that you want to change; unspecified properties will be left unchanged.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": -2
+ },
+ {
+ "type": "object",
+ "name": "updateInfo",
+ "properties": {
+ "left": {
+ "type": "integer",
+ "optional": true,
+ "description": "The offset from the left edge of the screen to move the window to in pixels. This value is ignored for panels."
+ },
+ "top": {
+ "type": "integer",
+ "optional": true,
+ "description": "The offset from the top edge of the screen to move the window to in pixels. This value is ignored for panels."
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The width to resize the window to in pixels. This value is ignored for panels."
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 0,
+ "optional": true,
+ "description": "The height to resize the window to in pixels. This value is ignored for panels."
+ },
+ "focused": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, brings the window to the front. If false, brings the next window in the z-order to the front."
+ },
+ "drawAttention": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, causes the window to be displayed in a manner that draws the user's attention to the window, without changing the focused window. The effect lasts until the user changes focus to the window. This option has no effect if the window already has focus. Set to false to cancel a previous draw attention request."
+ },
+ "state": {
+ "$ref": "WindowState",
+ "optional": true,
+ "description": "The new state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
+ }
+ }
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": [
+ {
+ "name": "window",
+ "$ref": "Window"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "remove",
+ "type": "function",
+ "description": "Removes (closes) a window, and all the tabs inside it.",
+ "async": "callback",
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": 0
+ },
+ {
+ "type": "function",
+ "name": "callback",
+ "optional": true,
+ "parameters": []
+ }
+ ]
+ }
+ ],
+ "events": [
+ {
+ "name": "onCreated",
+ "type": "function",
+ "description": "Fired when a window is created.",
+ "filters": [
+ {
+ "name": "windowTypes",
+ "type": "array",
+ "items": { "$ref": "WindowType" },
+ "description": "Conditions that the window's type being created must satisfy. By default it will satisfy <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+ }
+ ],
+ "parameters": [
+ {
+ "$ref": "Window",
+ "name": "window",
+ "description": "Details of the window that was created."
+ }
+ ]
+ },
+ {
+ "name": "onRemoved",
+ "type": "function",
+ "description": "Fired when a window is removed (closed).",
+ "filters": [
+ {
+ "name": "windowTypes",
+ "type": "array",
+ "items": { "$ref": "WindowType" },
+ "description": "Conditions that the window's type being removed must satisfy. By default it will satisfy <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+ }
+ ],
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": 0,
+ "description": "ID of the removed window."
+ }
+ ]
+ },
+ {
+ "name": "onFocusChanged",
+ "type": "function",
+ "description": "Fired when the currently focused window changes. Will be $(ref:windows.WINDOW_ID_NONE) if all browser windows have lost focus. Note: On some Linux window managers, WINDOW_ID_NONE will always be sent immediately preceding a switch from one browser window to another.",
+ "filters": [
+ {
+ "name": "windowTypes",
+ "type": "array",
+ "items": { "$ref": "WindowType" },
+ "description": "Conditions that the window's type being removed must satisfy. By default it will satisfy <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+ }
+ ],
+ "parameters": [
+ {
+ "type": "integer",
+ "name": "windowId",
+ "minimum": -1,
+ "description": "ID of the newly focused window."
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/browser/components/extensions/test/browser/.eslintrc.js b/browser/components/extensions/test/browser/.eslintrc.js
new file mode 100644
index 000000000..0e673ecb9
--- /dev/null
+++ b/browser/components/extensions/test/browser/.eslintrc.js
@@ -0,0 +1,36 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": "../../../../../testing/mochitest/browser.eslintrc.js",
+
+ "env": {
+ "webextensions": true,
+ },
+
+ "globals": {
+ "NetUtil": true,
+ "XPCOMUtils": true,
+ "Task": true,
+
+ // Browser window globals.
+ "PanelUI": false,
+
+ // Test harness globals
+ "ExtensionTestUtils": false,
+ "TestUtils": false,
+
+ "clickBrowserAction": true,
+ "clickPageAction": true,
+ "closeContextMenu": true,
+ "closeExtensionContextMenu": true,
+ "focusWindow": true,
+ "makeWidgetId": true,
+ "openContextMenu": true,
+ "openExtensionContextMenu": true,
+ "CustomizableUI": true,
+ },
+
+ "rules": {
+ "no-shadow": 0,
+ },
+};
diff --git a/browser/components/extensions/test/browser/browser.ini b/browser/components/extensions/test/browser/browser.ini
new file mode 100644
index 000000000..1e894dcb5
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -0,0 +1,115 @@
+[DEFAULT]
+support-files =
+ head.js
+ head_pageAction.js
+ head_sessions.js
+ context.html
+ ctxmenu-image.png
+ context_tabs_onUpdated_page.html
+ context_tabs_onUpdated_iframe.html
+ file_popup_api_injection_a.html
+ file_popup_api_injection_b.html
+ file_iframe_document.html
+ file_iframe_document.sjs
+ file_bypass_cache.sjs
+ file_language_fr_en.html
+ file_language_ja.html
+ file_language_tlh.html
+ file_dummy.html
+ searchSuggestionEngine.xml
+ searchSuggestionEngine.sjs
+ ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
+tags = webextensions
+
+
+[browser_ext_browserAction_context.js]
+[browser_ext_browserAction_disabled.js]
+[browser_ext_browserAction_pageAction_icon.js]
+[browser_ext_browserAction_pageAction_icon_permissions.js]
+[browser_ext_browserAction_popup.js]
+[browser_ext_browserAction_popup_resize.js]
+[browser_ext_browserAction_simple.js]
+[browser_ext_commands_execute_browser_action.js]
+[browser_ext_commands_execute_page_action.js]
+[browser_ext_commands_getAll.js]
+[browser_ext_commands_onCommand.js]
+[browser_ext_contentscript_connect.js]
+[browser_ext_contextMenus.js]
+[browser_ext_contextMenus_checkboxes.js]
+[browser_ext_contextMenus_icons.js]
+[browser_ext_contextMenus_onclick.js]
+[browser_ext_contextMenus_radioGroups.js]
+[browser_ext_contextMenus_uninstall.js]
+[browser_ext_contextMenus_urlPatterns.js]
+[browser_ext_currentWindow.js]
+[browser_ext_getViews.js]
+[browser_ext_incognito_popup.js]
+[browser_ext_incognito_views.js]
+[browser_ext_lastError.js]
+[browser_ext_legacy_extension_context_contentscript.js]
+[browser_ext_omnibox.js]
+[browser_ext_optionsPage_privileges.js]
+[browser_ext_pageAction_context.js]
+[browser_ext_pageAction_popup.js]
+[browser_ext_pageAction_popup_resize.js]
+[browser_ext_pageAction_simple.js]
+[browser_ext_pageAction_title.js]
+[browser_ext_popup_api_injection.js]
+[browser_ext_popup_background.js]
+[browser_ext_popup_corners.js]
+[browser_ext_popup_sendMessage.js]
+[browser_ext_popup_shutdown.js]
+[browser_ext_runtime_openOptionsPage.js]
+[browser_ext_runtime_openOptionsPage_uninstall.js]
+[browser_ext_runtime_setUninstallURL.js]
+[browser_ext_sessions_getRecentlyClosed.js]
+[browser_ext_sessions_getRecentlyClosed_private.js]
+[browser_ext_sessions_getRecentlyClosed_tabs.js]
+[browser_ext_sessions_restore.js]
+[browser_ext_simple.js]
+[browser_ext_tab_runtimeConnect.js]
+[browser_ext_tabs_audio.js]
+[browser_ext_tabs_captureVisibleTab.js]
+[browser_ext_tabs_create.js]
+[browser_ext_tabs_create_invalid_url.js]
+[browser_ext_tabs_detectLanguage.js]
+[browser_ext_tabs_duplicate.js]
+[browser_ext_tabs_events.js]
+[browser_ext_tabs_executeScript.js]
+[browser_ext_tabs_executeScript_good.js]
+[browser_ext_tabs_executeScript_bad.js]
+[browser_ext_tabs_executeScript_runAt.js]
+[browser_ext_tabs_getCurrent.js]
+[browser_ext_tabs_insertCSS.js]
+[browser_ext_tabs_removeCSS.js]
+[browser_ext_tabs_move.js]
+[browser_ext_tabs_move_window.js]
+[browser_ext_tabs_move_window_multiple.js]
+[browser_ext_tabs_move_window_pinned.js]
+[browser_ext_tabs_onHighlighted.js]
+[browser_ext_tabs_onUpdated.js]
+[browser_ext_tabs_query.js]
+[browser_ext_tabs_reload.js]
+[browser_ext_tabs_reload_bypass_cache.js]
+[browser_ext_tabs_sendMessage.js]
+[browser_ext_tabs_cookieStoreId.js]
+[browser_ext_tabs_update.js]
+[browser_ext_tabs_zoom.js]
+[browser_ext_tabs_update_url.js]
+[browser_ext_topwindowid.js]
+[browser_ext_webNavigation_frameId0.js]
+[browser_ext_webNavigation_getFrames.js]
+[browser_ext_webNavigation_urlbar_transitions.js]
+[browser_ext_webRequest.js]
+[browser_ext_windows.js]
+[browser_ext_windows_allowScriptsToClose.js]
+[browser_ext_windows_create.js]
+tags = fullscreen
+[browser_ext_windows_create_params.js]
+[browser_ext_windows_create_tabId.js]
+[browser_ext_windows_create_url.js]
+[browser_ext_windows_events.js]
+[browser_ext_windows_size.js]
+skip-if = os == 'mac' # Fails when windows are randomly opened in fullscreen mode
+[browser_ext_windows_update.js]
+tags = fullscreen
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
new file mode 100644
index 000000000..8a26dbb3c
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -0,0 +1,398 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* runTests(options) {
+ async function background(getTests) {
+ async function checkDetails(expecting, tabId) {
+ let title = await browser.browserAction.getTitle({tabId});
+ browser.test.assertEq(expecting.title, title,
+ "expected value from getTitle");
+
+ let popup = await browser.browserAction.getPopup({tabId});
+ browser.test.assertEq(expecting.popup, popup,
+ "expected value from getPopup");
+
+ let badge = await browser.browserAction.getBadgeText({tabId});
+ browser.test.assertEq(expecting.badge, badge,
+ "expected value from getBadge");
+
+ let badgeBackgroundColor = await browser.browserAction.getBadgeBackgroundColor({tabId});
+ browser.test.assertEq(String(expecting.badgeBackgroundColor),
+ String(badgeBackgroundColor),
+ "expected value from getBadgeBackgroundColor");
+ }
+
+ let expectDefaults = expecting => {
+ return checkDetails(expecting);
+ };
+
+ let tabs = [];
+ let tests = getTests(tabs, expectDefaults);
+
+ {
+ let tabId = 0xdeadbeef;
+ let calls = [
+ () => browser.browserAction.enable(tabId),
+ () => browser.browserAction.disable(tabId),
+ () => browser.browserAction.setTitle({tabId, title: "foo"}),
+ () => browser.browserAction.setIcon({tabId, path: "foo.png"}),
+ () => browser.browserAction.setPopup({tabId, popup: "foo.html"}),
+ () => browser.browserAction.setBadgeText({tabId, text: "foo"}),
+ () => browser.browserAction.setBadgeBackgroundColor({tabId, color: [0xff, 0, 0, 0xff]}),
+ ];
+
+ for (let call of calls) {
+ await browser.test.assertRejects(
+ new Promise(resolve => resolve(call())),
+ RegExp(`Invalid tab ID: ${tabId}`),
+ "Expected invalid tab ID error");
+ }
+ }
+
+ // Runs the next test in the `tests` array, checks the results,
+ // and passes control back to the outer test scope.
+ function nextTest() {
+ let test = tests.shift();
+
+ test(async expecting => {
+ // Check that the API returns the expected values, and then
+ // run the next test.
+ let tabs = await browser.tabs.query({active: true, currentWindow: true});
+ await checkDetails(expecting, tabs[0].id);
+
+ // Check that the actual icon has the expected values, then
+ // run the next test.
+ browser.test.sendMessage("nextTest", expecting, tests.length);
+ });
+ }
+
+ browser.test.onMessage.addListener((msg) => {
+ if (msg != "runNextTest") {
+ browser.test.fail("Expecting 'runNextTest' message");
+ }
+
+ nextTest();
+ });
+
+ browser.tabs.query({active: true, currentWindow: true}, resultTabs => {
+ tabs[0] = resultTabs[0].id;
+
+ nextTest();
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: options.manifest,
+
+ files: options.files || {},
+
+ background: `(${background})(${options.getTests})`,
+ });
+
+ let browserActionId;
+ function checkDetails(details) {
+ if (!browserActionId) {
+ browserActionId = `${makeWidgetId(extension.id)}-browser-action`;
+ }
+
+ let button = document.getElementById(browserActionId);
+
+ ok(button, "button exists");
+
+ let title = details.title || options.manifest.name;
+
+ is(getListStyleImage(button), details.icon, "icon URL is correct");
+ is(button.getAttribute("tooltiptext"), title, "image title is correct");
+ is(button.getAttribute("label"), title, "image label is correct");
+ is(button.getAttribute("badge"), details.badge, "badge text is correct");
+ is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
+
+ if (details.badge && details.badgeBackgroundColor) {
+ let badge = button.ownerDocument.getAnonymousElementByAttribute(
+ button, "class", "toolbarbutton-badge");
+
+ let badgeColor = window.getComputedStyle(badge).backgroundColor;
+ let color = details.badgeBackgroundColor;
+ let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
+
+ is(badgeColor, expectedColor, "badge color is correct");
+ }
+
+
+ // TODO: Popup URL.
+ }
+
+ let awaitFinish = new Promise(resolve => {
+ extension.onMessage("nextTest", (expecting, testsRemaining) => {
+ checkDetails(expecting);
+
+ if (testsRemaining) {
+ extension.sendMessage("runNextTest");
+ } else {
+ resolve();
+ }
+ });
+ });
+
+ yield extension.startup();
+
+ yield awaitFinish;
+
+ yield extension.unload();
+}
+
+add_task(function* testTabSwitchContext() {
+ yield runTests({
+ manifest: {
+ "browser_action": {
+ "default_icon": "default.png",
+ "default_popup": "__MSG_popup__",
+ "default_title": "Default __MSG_title__",
+ },
+
+ "default_locale": "en",
+
+ "permissions": ["tabs"],
+ },
+
+ "files": {
+ "_locales/en/messages.json": {
+ "popup": {
+ "message": "default.html",
+ "description": "Popup",
+ },
+
+ "title": {
+ "message": "Title",
+ "description": "Title",
+ },
+ },
+
+ "default.png": imageBuffer,
+ "default-2.png": imageBuffer,
+ "1.png": imageBuffer,
+ "2.png": imageBuffer,
+ },
+
+ getTests(tabs, expectDefaults) {
+ const DEFAULT_BADGE_COLOR = [0xd9, 0, 0, 255];
+
+ let details = [
+ {"icon": browser.runtime.getURL("default.png"),
+ "popup": browser.runtime.getURL("default.html"),
+ "title": "Default Title",
+ "badge": "",
+ "badgeBackgroundColor": DEFAULT_BADGE_COLOR},
+ {"icon": browser.runtime.getURL("1.png"),
+ "popup": browser.runtime.getURL("default.html"),
+ "title": "Default Title",
+ "badge": "",
+ "badgeBackgroundColor": DEFAULT_BADGE_COLOR},
+ {"icon": browser.runtime.getURL("2.png"),
+ "popup": browser.runtime.getURL("2.html"),
+ "title": "Title 2",
+ "badge": "2",
+ "badgeBackgroundColor": [0xff, 0, 0, 0xff],
+ "disabled": true},
+ {"icon": browser.runtime.getURL("1.png"),
+ "popup": browser.runtime.getURL("default-2.html"),
+ "title": "Default Title 2",
+ "badge": "d2",
+ "badgeBackgroundColor": [0, 0xff, 0, 0xff],
+ "disabled": true},
+ {"icon": browser.runtime.getURL("1.png"),
+ "popup": browser.runtime.getURL("default-2.html"),
+ "title": "Default Title 2",
+ "badge": "d2",
+ "badgeBackgroundColor": [0, 0xff, 0, 0xff],
+ "disabled": false},
+ {"icon": browser.runtime.getURL("default-2.png"),
+ "popup": browser.runtime.getURL("default-2.html"),
+ "title": "Default Title 2",
+ "badge": "d2",
+ "badgeBackgroundColor": [0, 0xff, 0, 0xff]},
+ ];
+
+ return [
+ async expect => {
+ browser.test.log("Initial state, expect default properties.");
+
+ await expectDefaults(details[0]);
+ expect(details[0]);
+ },
+ async expect => {
+ browser.test.log("Change the icon in the current tab. Expect default properties excluding the icon.");
+ browser.browserAction.setIcon({tabId: tabs[0], path: "1.png"});
+
+ await expectDefaults(details[0]);
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Create a new tab. Expect default properties.");
+ let tab = await browser.tabs.create({active: true, url: "about:blank?0"});
+ tabs.push(tab.id);
+
+ await expectDefaults(details[0]);
+ expect(details[0]);
+ },
+ async expect => {
+ browser.test.log("Change properties. Expect new properties.");
+ let tabId = tabs[1];
+ browser.browserAction.setIcon({tabId, path: "2.png"});
+ browser.browserAction.setPopup({tabId, popup: "2.html"});
+ browser.browserAction.setTitle({tabId, title: "Title 2"});
+ browser.browserAction.setBadgeText({tabId, text: "2"});
+ browser.browserAction.setBadgeBackgroundColor({tabId, color: "#ff0000"});
+ browser.browserAction.disable(tabId);
+
+ await expectDefaults(details[0]);
+ expect(details[2]);
+ },
+ expect => {
+ browser.test.log("Navigate to a new page. Expect no changes.");
+
+ // TODO: This listener should not be necessary, but the |tabs.update|
+ // callback currently fires too early in e10s windows.
+ browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
+ if (tabId == tabs[1] && changed.url) {
+ browser.tabs.onUpdated.removeListener(listener);
+ expect(details[2]);
+ }
+ });
+
+ browser.tabs.update(tabs[1], {url: "about:blank?1"});
+ },
+ async expect => {
+ browser.test.log("Switch back to the first tab. Expect previously set properties.");
+ await browser.tabs.update(tabs[0], {active: true});
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Change default values, expect those changes reflected.");
+ browser.browserAction.setIcon({path: "default-2.png"});
+ browser.browserAction.setPopup({popup: "default-2.html"});
+ browser.browserAction.setTitle({title: "Default Title 2"});
+ browser.browserAction.setBadgeText({text: "d2"});
+ browser.browserAction.setBadgeBackgroundColor({color: [0, 0xff, 0, 0xff]});
+ browser.browserAction.disable();
+
+ await expectDefaults(details[3]);
+ expect(details[3]);
+ },
+ async expect => {
+ browser.test.log("Re-enable by default. Expect enabled.");
+ browser.browserAction.enable();
+
+ await expectDefaults(details[4]);
+ expect(details[4]);
+ },
+ async expect => {
+ browser.test.log("Switch back to tab 2. Expect former value, unaffected by changes to defaults in previous step.");
+ await browser.tabs.update(tabs[1], {active: true});
+
+ await expectDefaults(details[3]);
+ expect(details[2]);
+ },
+ async expect => {
+ browser.test.log("Delete tab, switch back to tab 1. Expect previous results again.");
+ await browser.tabs.remove(tabs[1]);
+ expect(details[4]);
+ },
+ async expect => {
+ browser.test.log("Create a new tab. Expect new default properties.");
+ let tab = await browser.tabs.create({active: true, url: "about:blank?2"});
+ tabs.push(tab.id);
+ expect(details[5]);
+ },
+ async expect => {
+ browser.test.log("Delete tab.");
+ await browser.tabs.remove(tabs[2]);
+ expect(details[4]);
+ },
+ ];
+ },
+ });
+});
+
+add_task(function* testDefaultTitle() {
+ yield runTests({
+ manifest: {
+ "name": "Foo Extension",
+
+ "browser_action": {
+ "default_icon": "icon.png",
+ },
+
+ "permissions": ["tabs"],
+ },
+
+ files: {
+ "icon.png": imageBuffer,
+ },
+
+ getTests(tabs, expectDefaults) {
+ const DEFAULT_BADGE_COLOR = [0xd9, 0, 0, 255];
+
+ let details = [
+ {"title": "Foo Extension",
+ "popup": "",
+ "badge": "",
+ "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
+ "icon": browser.runtime.getURL("icon.png")},
+ {"title": "Foo Title",
+ "popup": "",
+ "badge": "",
+ "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
+ "icon": browser.runtime.getURL("icon.png")},
+ {"title": "Bar Title",
+ "popup": "",
+ "badge": "",
+ "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
+ "icon": browser.runtime.getURL("icon.png")},
+ {"title": "",
+ "popup": "",
+ "badge": "",
+ "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
+ "icon": browser.runtime.getURL("icon.png")},
+ ];
+
+ return [
+ async expect => {
+ browser.test.log("Initial state. Expect extension title as default title.");
+
+ await expectDefaults(details[0]);
+ expect(details[0]);
+ },
+ async expect => {
+ browser.test.log("Change the title. Expect new title.");
+ browser.browserAction.setTitle({tabId: tabs[0], title: "Foo Title"});
+
+ await expectDefaults(details[0]);
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Change the default. Expect same properties.");
+ browser.browserAction.setTitle({title: "Bar Title"});
+
+ await expectDefaults(details[2]);
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Clear the title. Expect new default title.");
+ browser.browserAction.setTitle({tabId: tabs[0], title: ""});
+
+ await expectDefaults(details[2]);
+ expect(details[2]);
+ },
+ async expect => {
+ browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
+ browser.browserAction.setTitle({title: ""});
+
+ await expectDefaults(details[3]);
+ expect(details[3]);
+ },
+ ];
+ },
+ });
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_disabled.js b/browser/components/extensions/test/browser/browser_ext_browserAction_disabled.js
new file mode 100644
index 000000000..c0b9c1a1d
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_disabled.js
@@ -0,0 +1,68 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testDisabled() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {},
+ },
+
+ background: function() {
+ let clicked = false;
+
+ browser.browserAction.onClicked.addListener(() => {
+ browser.test.log("Got click event");
+ clicked = true;
+ });
+
+ browser.test.onMessage.addListener((msg, expectClick) => {
+ if (msg == "enable") {
+ browser.test.log("enable browserAction");
+ browser.browserAction.enable();
+ } else if (msg == "disable") {
+ browser.test.log("disable browserAction");
+ browser.browserAction.disable();
+ } else if (msg == "check-clicked") {
+ browser.test.assertEq(expectClick, clicked, "got click event?");
+ clicked = false;
+ } else {
+ browser.test.fail("Unexpected message");
+ }
+
+ browser.test.sendMessage("next-test");
+ });
+
+ browser.test.sendMessage("ready");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ yield clickBrowserAction(extension);
+ yield new Promise(resolve => setTimeout(resolve, 0));
+
+ extension.sendMessage("check-clicked", true);
+ yield extension.awaitMessage("next-test");
+
+ extension.sendMessage("disable");
+ yield extension.awaitMessage("next-test");
+
+ yield clickBrowserAction(extension);
+ yield new Promise(resolve => setTimeout(resolve, 0));
+
+ extension.sendMessage("check-clicked", false);
+ yield extension.awaitMessage("next-test");
+
+ extension.sendMessage("enable");
+ yield extension.awaitMessage("next-test");
+
+ yield clickBrowserAction(extension);
+ yield new Promise(resolve => setTimeout(resolve, 0));
+
+ extension.sendMessage("check-clicked", true);
+ yield extension.awaitMessage("next-test");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
new file mode 100644
index 000000000..9665d6832
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -0,0 +1,321 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Test that various combinations of icon details specs, for both paths
+// and ImageData objects, result in the correct image being displayed in
+// all display resolutions.
+add_task(function* testDetailsObjects() {
+ function background() {
+ function getImageData(color) {
+ let canvas = document.createElement("canvas");
+ canvas.width = 2;
+ canvas.height = 2;
+ let canvasContext = canvas.getContext("2d");
+
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height);
+ canvasContext.fillStyle = color;
+ canvasContext.fillRect(0, 0, 1, 1);
+
+ return {
+ url: canvas.toDataURL("image/png"),
+ imageData: canvasContext.getImageData(0, 0, canvas.width, canvas.height),
+ };
+ }
+
+ let imageData = {
+ red: getImageData("red"),
+ green: getImageData("green"),
+ };
+
+ /* eslint-disable comma-dangle, indent */
+ let iconDetails = [
+ // Only paths.
+ {details: {"path": "a.png"},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": browser.runtime.getURL("data/a.png")}},
+ {details: {"path": "/a.png"},
+ resolutions: {
+ "1": browser.runtime.getURL("a.png"),
+ "2": browser.runtime.getURL("a.png")}},
+ {details: {"path": {"19": "a.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": browser.runtime.getURL("data/a.png")}},
+ {details: {"path": {"38": "a.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": browser.runtime.getURL("data/a.png")}},
+ {details: {"path": {"19": "a.png", "38": "a-x2.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": browser.runtime.getURL("data/a-x2.png")}},
+
+ // Test that CSS strings are escaped properly.
+ {details: {"path": 'a.png#" \\'},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png#%22%20%5C"),
+ "2": browser.runtime.getURL("data/a.png#%22%20%5C")}},
+
+ // Only ImageData objects.
+ {details: {"imageData": imageData.red.imageData},
+ resolutions: {
+ "1": imageData.red.url,
+ "2": imageData.red.url}},
+ {details: {"imageData": {"19": imageData.red.imageData}},
+ resolutions: {
+ "1": imageData.red.url,
+ "2": imageData.red.url}},
+ {details: {"imageData": {"38": imageData.red.imageData}},
+ resolutions: {
+ "1": imageData.red.url,
+ "2": imageData.red.url}},
+ {details: {"imageData": {
+ "19": imageData.red.imageData,
+ "38": imageData.green.imageData}},
+ resolutions: {
+ "1": imageData.red.url,
+ "2": imageData.green.url}},
+
+ // Mixed path and imageData objects.
+ //
+ // The behavior is currently undefined if both |path| and
+ // |imageData| specify icons of the same size.
+ {details: {
+ "path": {"19": "a.png"},
+ "imageData": {"38": imageData.red.imageData}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": imageData.red.url}},
+ {details: {
+ "path": {"38": "a.png"},
+ "imageData": {"19": imageData.red.imageData}},
+ resolutions: {
+ "1": imageData.red.url,
+ "2": browser.runtime.getURL("data/a.png")}},
+
+ // A path or ImageData object by itself is treated as a 19px icon.
+ {details: {
+ "path": "a.png",
+ "imageData": {"38": imageData.red.imageData}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": imageData.red.url}},
+ {details: {
+ "path": {"38": "a.png"},
+ "imageData": imageData.red.imageData},
+ resolutions: {
+ "1": imageData.red.url,
+ "2": browser.runtime.getURL("data/a.png")}},
+
+ // Various resolutions
+ {details: {"path": {"18": "a.png", "36": "a-x2.png"}},
+ legacy: true,
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": browser.runtime.getURL("data/a-x2.png")}},
+ {details: {"path": {"16": "a.png", "30": "a-x2.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/a.png"),
+ "2": browser.runtime.getURL("data/a-x2.png")}},
+ {details: {"path": {"16": "16.png", "100": "100.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/16.png"),
+ "2": browser.runtime.getURL("data/100.png")}},
+ {details: {"path": {"2": "2.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/2.png"),
+ "2": browser.runtime.getURL("data/2.png")}},
+ {details: {"path": {
+ "16": "16.svg",
+ "18": "18.svg"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/16.svg"),
+ "2": browser.runtime.getURL("data/18.svg")}},
+ {details: {"path": {
+ "6": "6.png",
+ "18": "18.png",
+ "36": "36.png",
+ "48": "48.png",
+ "128": "128.png"}},
+ legacy: true,
+ resolutions: {
+ "1": browser.runtime.getURL("data/18.png"),
+ "2": browser.runtime.getURL("data/36.png")},
+ menuResolutions: {
+ "1": browser.runtime.getURL("data/36.png"),
+ "2": browser.runtime.getURL("data/128.png")}},
+ {details: {"path": {
+ "16": "16.png",
+ "18": "18.png",
+ "32": "32.png",
+ "48": "48.png",
+ "64": "64.png",
+ "128": "128.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/16.png"),
+ "2": browser.runtime.getURL("data/32.png")},
+ menuResolutions: {
+ "1": browser.runtime.getURL("data/32.png"),
+ "2": browser.runtime.getURL("data/64.png")}},
+ {details: {"path": {
+ "18": "18.png",
+ "32": "32.png",
+ "48": "48.png",
+ "128": "128.png"}},
+ resolutions: {
+ "1": browser.runtime.getURL("data/32.png"),
+ "2": browser.runtime.getURL("data/32.png")}},
+ ];
+
+ // Allow serializing ImageData objects for logging.
+ ImageData.prototype.toJSON = () => "<ImageData>";
+
+ let tabId;
+
+ browser.test.onMessage.addListener((msg, test) => {
+ if (msg != "setIcon") {
+ browser.test.fail("expecting 'setIcon' message");
+ }
+
+ let details = iconDetails[test.index];
+
+ let detailString = JSON.stringify(details);
+ browser.test.log(`Setting browerAction/pageAction to ${detailString} expecting URLs ${JSON.stringify(details.resolutions)}`);
+
+ Promise.all([
+ browser.browserAction.setIcon(Object.assign({tabId}, details.details)),
+ browser.pageAction.setIcon(Object.assign({tabId}, details.details)),
+ ]).then(() => {
+ browser.test.sendMessage("iconSet");
+ });
+ });
+
+ // Generate a list of tests and resolutions to send back to the test
+ // context.
+ //
+ // This process is a bit convoluted, because the outer test context needs
+ // to handle checking the button nodes and changing the screen resolution,
+ // but it can't pass us icon definitions with ImageData objects. This
+ // shouldn't be a problem, since structured clones should handle ImageData
+ // objects without issue. Unfortunately, |cloneInto| implements a slightly
+ // different algorithm than we use in web APIs, and does not handle them
+ // correctly.
+ let tests = [];
+ for (let [idx, icon] of iconDetails.entries()) {
+ tests.push({
+ index: idx,
+ legacy: !!icon.legacy,
+ menuResolutions: icon.menuResolutions,
+ resolutions: icon.resolutions,
+ });
+ }
+
+ // Sort by resolution, so we don't needlessly switch back and forth
+ // between each test.
+ tests.sort(test => test.resolution);
+
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ tabId = tabs[0].id;
+ browser.pageAction.show(tabId).then(() => {
+ browser.test.sendMessage("ready", tests);
+ });
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {},
+ "page_action": {},
+ "background": {
+ "page": "data/background.html",
+ }
+ },
+
+ files: {
+ "data/background.html": `<script src="background.js"></script>`,
+ "data/background.js": background,
+
+ "data/16.svg": imageBuffer,
+ "data/18.svg": imageBuffer,
+
+ "data/16.png": imageBuffer,
+ "data/18.png": imageBuffer,
+ "data/32.png": imageBuffer,
+ "data/36.png": imageBuffer,
+ "data/48.png": imageBuffer,
+ "data/64.png": imageBuffer,
+ "data/128.png": imageBuffer,
+
+ "a.png": imageBuffer,
+ "data/2.png": imageBuffer,
+ "data/100.png": imageBuffer,
+ "data/a.png": imageBuffer,
+ "data/a-x2.png": imageBuffer,
+ },
+ });
+
+ const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
+
+ yield extension.startup();
+
+ let pageActionId = `${makeWidgetId(extension.id)}-page-action`;
+ let browserActionWidget = getBrowserActionWidget(extension);
+
+ let tests = yield extension.awaitMessage("ready");
+ for (let test of tests) {
+ extension.sendMessage("setIcon", test);
+ yield extension.awaitMessage("iconSet");
+
+ let browserActionButton = browserActionWidget.forWindow(window).node;
+ let pageActionImage = document.getElementById(pageActionId);
+
+
+ // Test icon sizes in the toolbar/urlbar.
+ for (let resolution of Object.keys(test.resolutions)) {
+ yield SpecialPowers.pushPrefEnv({set: [[RESOLUTION_PREF, resolution]]});
+
+ is(window.devicePixelRatio, +resolution, "window has the required resolution");
+
+ let imageURL = test.resolutions[resolution];
+ is(getListStyleImage(browserActionButton), imageURL, `browser action has the correct image at ${resolution}x resolution`);
+ is(getListStyleImage(pageActionImage), imageURL, `page action has the correct image at ${resolution}x resolution`);
+
+ let isLegacy = browserActionButton.classList.contains("toolbarbutton-legacy-addon");
+ is(isLegacy, test.legacy, "Legacy class should be present?");
+
+ yield SpecialPowers.popPrefEnv();
+ }
+
+ if (!test.menuResolutions) {
+ continue;
+ }
+
+
+ // Test icon sizes in the menu panel.
+ CustomizableUI.addWidgetToArea(browserActionWidget.id,
+ CustomizableUI.AREA_PANEL);
+
+ yield showBrowserAction(extension);
+ browserActionButton = browserActionWidget.forWindow(window).node;
+
+ for (let resolution of Object.keys(test.menuResolutions)) {
+ yield SpecialPowers.pushPrefEnv({set: [[RESOLUTION_PREF, resolution]]});
+
+ is(window.devicePixelRatio, +resolution, "window has the required resolution");
+
+ let imageURL = test.menuResolutions[resolution];
+ is(getListStyleImage(browserActionButton), imageURL, `browser action has the correct menu image at ${resolution}x resolution`);
+
+ yield SpecialPowers.popPrefEnv();
+ }
+
+ yield closeBrowserAction(extension);
+
+ CustomizableUI.addWidgetToArea(browserActionWidget.id,
+ CustomizableUI.AREA_NAVBAR);
+ }
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
new file mode 100644
index 000000000..110746cae
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon_permissions.js
@@ -0,0 +1,210 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Test that an error is thrown when providing invalid icon sizes
+add_task(function* testInvalidIconSizes() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {},
+ "page_action": {},
+ },
+
+ background: function() {
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ let tabId = tabs[0].id;
+
+ let promises = [];
+ for (let api of ["pageAction", "browserAction"]) {
+ // helper function to run setIcon and check if it fails
+ let assertSetIconThrows = function(detail, error, message) {
+ detail.tabId = tabId;
+ promises.push(
+ browser.test.assertRejects(
+ browser[api].setIcon(detail),
+ /must be an integer/,
+ "setIcon with invalid icon size"));
+ };
+
+ let imageData = new ImageData(1, 1);
+
+ // test invalid icon size inputs
+ for (let type of ["path", "imageData"]) {
+ let img = type == "imageData" ? imageData : "test.png";
+
+ assertSetIconThrows({[type]: {"abcdef": img}});
+ assertSetIconThrows({[type]: {"48px": img}});
+ assertSetIconThrows({[type]: {"20.5": img}});
+ assertSetIconThrows({[type]: {"5.0": img}});
+ assertSetIconThrows({[type]: {"-300": img}});
+ assertSetIconThrows({[type]: {"abc": img, "5": img}});
+ }
+
+ assertSetIconThrows({imageData: {"abcdef": imageData}, path: {"5": "test.png"}});
+ assertSetIconThrows({path: {"abcdef": "test.png"}, imageData: {"5": imageData}});
+ }
+
+ Promise.all(promises).then(() => {
+ browser.test.notifyPass("setIcon with invalid icon size");
+ });
+ });
+ },
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitFinish("setIcon with invalid icon size")]);
+
+ yield extension.unload();
+});
+
+
+// Test that default icon details in the manifest.json file are handled
+// correctly.
+add_task(function* testDefaultDetails() {
+ // TODO: Test localized variants.
+ let icons = [
+ "foo/bar.png",
+ "/foo/bar.png",
+ {"19": "foo/bar.png"},
+ {"38": "foo/bar.png"},
+ ];
+
+ if (window.devicePixelRatio > 1) {
+ icons.push({"19": "baz/quux.png", "38": "foo/bar.png"});
+ } else {
+ icons.push({"19": "foo/bar.png", "38": "baz/quux@2x.png"});
+ }
+
+ let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/foo/bar\.png$`);
+
+ for (let icon of icons) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {"default_icon": icon},
+ "page_action": {"default_icon": icon},
+ },
+
+ background: function() {
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ let tabId = tabs[0].id;
+
+ browser.pageAction.show(tabId).then(() => {
+ browser.test.sendMessage("ready");
+ });
+ });
+ },
+
+ files: {
+ "foo/bar.png": imageBuffer,
+ "baz/quux.png": imageBuffer,
+ "baz/quux@2x.png": imageBuffer,
+ },
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
+
+ let browserActionId = makeWidgetId(extension.id) + "-browser-action";
+ let pageActionId = makeWidgetId(extension.id) + "-page-action";
+
+ let browserActionButton = document.getElementById(browserActionId);
+ let image = getListStyleImage(browserActionButton);
+
+ ok(expectedURL.test(image), `browser action image ${image} matches ${expectedURL}`);
+
+ let pageActionImage = document.getElementById(pageActionId);
+ image = getListStyleImage(pageActionImage);
+
+ ok(expectedURL.test(image), `page action image ${image} matches ${expectedURL}`);
+
+ yield extension.unload();
+
+ let node = document.getElementById(pageActionId);
+ is(node, null, "pageAction image removed from document");
+ }
+});
+
+
+// Check that attempts to load a privileged URL as an icon image fail.
+add_task(function* testSecureURLsDenied() {
+ // Test URLs passed to setIcon.
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {},
+ "page_action": {},
+ },
+
+ background: function() {
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ let tabId = tabs[0].id;
+
+ let urls = ["chrome://browser/content/browser.xul",
+ "javascript:true"];
+
+ let promises = [];
+ for (let url of urls) {
+ for (let api of ["pageAction", "browserAction"]) {
+ promises.push(
+ browser.test.assertRejects(
+ browser[api].setIcon({tabId, path: url}),
+ /Illegal URL/,
+ `Load of '${url}' should fail.`));
+ }
+ }
+
+ Promise.all(promises).then(() => {
+ browser.test.notifyPass("setIcon security tests");
+ });
+ });
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("setIcon security tests");
+ yield extension.unload();
+});
+
+
+add_task(function* testSecureManifestURLsDenied() {
+ // Test URLs included in the manifest.
+
+ let urls = ["chrome://browser/content/browser.xul",
+ "javascript:true"];
+
+ let apis = ["browser_action", "page_action"];
+
+ for (let url of urls) {
+ for (let api of apis) {
+ info(`TEST ${api} icon url: ${url}`);
+
+ let matchURLForbidden = url => ({
+ message: new RegExp(`match the format "strictRelativeUrl"`),
+ });
+
+ let messages = [matchURLForbidden(url)];
+
+ let waitForConsole = new Promise(resolve => {
+ // Not necessary in browser-chrome tests, but monitorConsole gripes
+ // if we don't call it.
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.monitorConsole(resolve, messages);
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ [api]: {
+ "default_icon": url,
+ },
+ },
+ });
+
+ yield Assert.rejects(extension.startup(),
+ null,
+ "Manifest rejected");
+
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+ }
+ }
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
new file mode 100644
index 000000000..9f04b3c11
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
@@ -0,0 +1,413 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function getBrowserAction(extension) {
+ const {GlobalManager, Management: {global: {browserActionFor}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let ext = GlobalManager.extensionMap.get(extension.id);
+ return browserActionFor(ext);
+}
+
+let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>${url}</body></html>`;
+
+function* testInArea(area) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "background": {
+ "page": "data/background.html",
+ },
+ "browser_action": {
+ "default_popup": "popup-a.html",
+ "browser_style": true,
+ },
+ },
+
+ files: {
+ "popup-a.html": scriptPage("popup-a.js"),
+ "popup-a.js": function() {
+ window.onload = () => {
+ let color = window.getComputedStyle(document.body).color;
+ browser.test.assertEq("rgb(34, 36, 38)", color);
+ browser.runtime.sendMessage("from-popup-a");
+ };
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg == "close-popup-using-window.close") {
+ window.close();
+ }
+ });
+ },
+
+ "data/popup-b.html": scriptPage("popup-b.js"),
+ "data/popup-b.js": function() {
+ window.onload = () => {
+ browser.runtime.sendMessage("from-popup-b");
+ };
+ },
+
+ "data/popup-c.html": scriptPage("popup-c.js"),
+ "data/popup-c.js": function() {
+ // Close the popup before the document is fully-loaded to make sure that
+ // we handle this case sanely.
+ browser.runtime.sendMessage("from-popup-c");
+ window.close();
+ },
+
+ "data/background.html": scriptPage("background.js"),
+
+ "data/background.js": function() {
+ let sendClick;
+ let tests = [
+ () => {
+ browser.test.log(`Click browser action, expect popup "a".`);
+ sendClick({expectEvent: false, expectPopup: "a"});
+ },
+ () => {
+ browser.test.log(`Click browser action again, expect popup "a".`);
+ sendClick({expectEvent: false, expectPopup: "a"});
+ },
+ () => {
+ browser.test.log(`Call triggerAction, expect popup "a" again. Leave popup open.`);
+ sendClick({expectEvent: false, expectPopup: "a", closePopup: false}, "trigger-action");
+ },
+ () => {
+ browser.test.log(`Call triggerAction again. Expect remaining popup closed.`);
+ sendClick({expectEvent: false, expectPopup: null}, "trigger-action");
+ browser.test.sendMessage("next-test", {waitUntilClosed: true});
+ },
+ () => {
+ browser.test.log(`Call triggerAction again. Expect popup "a" again.`);
+ sendClick({expectEvent: false, expectPopup: "a"}, "trigger-action");
+ },
+ () => {
+ browser.test.log(`Set popup to "c" and click browser action. Expect popup "c".`);
+ browser.browserAction.setPopup({popup: "popup-c.html"});
+ sendClick({expectEvent: false, expectPopup: "c", closePopup: false});
+ },
+ () => {
+ browser.test.log(`Set popup to "b" and click browser action. Expect popup "b".`);
+ browser.browserAction.setPopup({popup: "popup-b.html"});
+ sendClick({expectEvent: false, expectPopup: "b"});
+ },
+ () => {
+ browser.test.log(`Click browser action again, expect popup "b".`);
+ sendClick({expectEvent: false, expectPopup: "b"});
+ },
+ () => {
+ browser.test.log(`Clear popup URL. Click browser action. Expect click event.`);
+ browser.browserAction.setPopup({popup: ""});
+ sendClick({expectEvent: true, expectPopup: null});
+ },
+ () => {
+ browser.test.log(`Click browser action again. Expect another click event.`);
+ sendClick({expectEvent: true, expectPopup: null});
+ },
+ () => {
+ browser.test.log(`Call triggerAction. Expect click event.`);
+ sendClick({expectEvent: true, expectPopup: null}, "trigger-action");
+ },
+ () => {
+ browser.test.log(`Set popup to "a" and click browser action. Expect popup "a", and leave open.`);
+ browser.browserAction.setPopup({popup: "/popup-a.html"});
+ sendClick({expectEvent: false, expectPopup: "a", closePopup: false});
+ },
+ () => {
+ browser.test.log(`Tell popup "a" to call window.close(). Expect popup closed.`);
+ browser.test.sendMessage("next-test", {closePopupUsingWindow: true});
+ },
+ ];
+
+ let expect = {};
+ sendClick = ({expectEvent, expectPopup, runNextTest, waitUntilClosed, closePopup}, message = "send-click") => {
+ if (closePopup == undefined) {
+ closePopup = true;
+ }
+
+ expect = {event: expectEvent, popup: expectPopup, runNextTest, waitUntilClosed, closePopup};
+ browser.test.sendMessage(message);
+ };
+
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg == "close-popup-using-window.close") {
+ return;
+ } else if (expect.popup) {
+ browser.test.assertEq(msg, `from-popup-${expect.popup}`,
+ "expected popup opened");
+ } else {
+ browser.test.fail(`unexpected popup: ${msg}`);
+ }
+
+ expect.popup = null;
+ browser.test.sendMessage("next-test", expect);
+ });
+
+ browser.browserAction.onClicked.addListener(() => {
+ if (expect.event) {
+ browser.test.succeed("expected click event received");
+ } else {
+ browser.test.fail("unexpected click event");
+ }
+
+ expect.event = false;
+ browser.test.sendMessage("next-test", expect);
+ });
+
+ browser.test.onMessage.addListener((msg) => {
+ if (msg == "close-popup-using-window.close") {
+ browser.runtime.sendMessage("close-popup-using-window.close");
+ return;
+ }
+
+ if (msg != "next-test") {
+ browser.test.fail("Expecting 'next-test' message");
+ }
+
+ if (tests.length) {
+ let test = tests.shift();
+ test();
+ } else {
+ browser.test.notifyPass("browseraction-tests-done");
+ }
+ });
+
+ browser.test.sendMessage("next-test");
+ },
+ },
+ });
+
+ extension.onMessage("send-click", () => {
+ clickBrowserAction(extension);
+ });
+
+ extension.onMessage("trigger-action", () => {
+ getBrowserAction(extension).triggerAction(window);
+ });
+
+ let widget;
+ extension.onMessage("next-test", Task.async(function* (expecting = {}) {
+ if (!widget) {
+ widget = getBrowserActionWidget(extension);
+ CustomizableUI.addWidgetToArea(widget.id, area);
+ }
+ if (expecting.waitUntilClosed) {
+ let panel = getBrowserActionPopup(extension);
+ if (panel && panel.state != "closed") {
+ yield promisePopupHidden(panel);
+ }
+ } else if (expecting.closePopupUsingWindow) {
+ let panel = getBrowserActionPopup(extension);
+ ok(panel, "Expect panel to exist");
+ yield promisePopupShown(panel);
+
+ extension.sendMessage("close-popup-using-window.close");
+
+ yield promisePopupHidden(panel);
+ ok(true, "Panel is closed");
+ } else if (expecting.closePopup) {
+ yield closeBrowserAction(extension);
+ }
+
+ extension.sendMessage("next-test");
+ }));
+
+ yield Promise.all([extension.startup(), extension.awaitFinish("browseraction-tests-done")]);
+
+ yield extension.unload();
+
+ let view = document.getElementById(widget.viewId);
+ is(view, null, "browserAction view removed from document");
+}
+
+add_task(function* testBrowserActionInToolbar() {
+ yield testInArea(CustomizableUI.AREA_NAVBAR);
+});
+
+add_task(function* testBrowserActionInPanel() {
+ yield testInArea(CustomizableUI.AREA_PANEL);
+});
+
+add_task(function* testBrowserActionClickCanceled() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+ "permissions": ["activeTab"],
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head></html>`,
+ },
+ });
+
+ yield extension.startup();
+
+ const {GlobalManager, Management: {global: {browserActionFor}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let ext = GlobalManager.extensionMap.get(extension.id);
+ let browserAction = browserActionFor(ext);
+
+ let widget = getBrowserActionWidget(extension).forWindow(window);
+ let tab = window.gBrowser.selectedTab;
+
+ // Test canceled click.
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, window);
+
+ isnot(browserAction.pendingPopup, null, "Have pending popup");
+ is(browserAction.pendingPopup.window, window, "Have pending popup for the correct window");
+
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+
+ is(browserAction.tabToRevokeDuringClearPopup, tab, "Tab to revoke was saved");
+ is(browserAction.tabManager.hasActiveTabPermission(tab), true, "Active tab was granted permission");
+
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mouseup", button: 0}, window);
+
+ is(browserAction.pendingPopup, null, "Pending popup was cleared");
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+
+ is(browserAction.tabToRevokeDuringClearPopup, null, "Tab to revoke was removed");
+ is(browserAction.tabManager.hasActiveTabPermission(tab), false, "Permission was revoked from tab");
+
+ // Test completed click.
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, window);
+
+ isnot(browserAction.pendingPopup, null, "Have pending popup");
+ is(browserAction.pendingPopup.window, window, "Have pending popup for the correct window");
+
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+
+ // We need to do these tests during the mouseup event cycle, since the click
+ // and command events will be dispatched immediately after mouseup, and void
+ // the results.
+ let mouseUpPromise = BrowserTestUtils.waitForEvent(widget.node, "mouseup", false, event => {
+ isnot(browserAction.pendingPopup, null, "Pending popup was not cleared");
+ isnot(browserAction.pendingPopupTimeout, null, "Have a pending popup timeout");
+ return true;
+ });
+
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mouseup", button: 0}, window);
+
+ yield mouseUpPromise;
+
+ is(browserAction.pendingPopup, null, "Pending popup was cleared");
+ is(browserAction.pendingPopupTimeout, null, "Pending popup timeout was cleared");
+
+ yield promisePopupShown(getBrowserActionPopup(extension));
+ yield closeBrowserAction(extension);
+
+ yield extension.unload();
+});
+
+add_task(function* testBrowserActionDisabled() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+ },
+
+ background() {
+ browser.browserAction.disable();
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8"><script src="popup.js"></script></head></html>`,
+ "popup.js"() {
+ browser.test.fail("Should not get here");
+ },
+ },
+ });
+
+ yield extension.startup();
+
+ const {GlobalManager, Management: {global: {browserActionFor}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let ext = GlobalManager.extensionMap.get(extension.id);
+ let browserAction = browserActionFor(ext);
+
+ let widget = getBrowserActionWidget(extension).forWindow(window);
+
+ // Test canceled click.
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, window);
+
+ is(browserAction.pendingPopup, null, "Have no pending popup");
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+
+ EventUtils.synthesizeMouseAtCenter(document.documentElement, {type: "mouseup", button: 0}, window);
+
+ is(browserAction.pendingPopup, null, "Have no pending popup");
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+
+
+ // Test completed click.
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, window);
+
+ is(browserAction.pendingPopup, null, "Have no pending popup");
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+
+ // We need to do these tests during the mouseup event cycle, since the click
+ // and command events will be dispatched immediately after mouseup, and void
+ // the results.
+ let mouseUpPromise = BrowserTestUtils.waitForEvent(widget.node, "mouseup", false, event => {
+ is(browserAction.pendingPopup, null, "Have no pending popup");
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+ return true;
+ });
+
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mouseup", button: 0}, window);
+
+ yield mouseUpPromise;
+
+ is(browserAction.pendingPopup, null, "Have no pending popup");
+ is(browserAction.pendingPopupTimeout, null, "Have no pending popup timeout");
+
+ // Give the popup a chance to load and trigger a failure, if it was
+ // erroneously opened.
+ yield new Promise(resolve => setTimeout(resolve, 250));
+
+ yield extension.unload();
+});
+
+add_task(function* testBrowserActionTabPopulation() {
+ // Note: This test relates to https://bugzilla.mozilla.org/show_bug.cgi?id=1310019
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+ "permissions": ["activeTab"],
+ },
+
+ files: {
+ "popup.html": scriptPage("popup.js"),
+ "popup.js": function() {
+ browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
+ browser.test.assertEq("mochitest index /",
+ tabs[0].title,
+ "Tab has the expected title on first click");
+ browser.test.sendMessage("tabTitle");
+ });
+ },
+ },
+ });
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+
+ yield extension.startup();
+
+ let widget = getBrowserActionWidget(extension).forWindow(win);
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, win);
+
+ yield extension.awaitMessage("tabTitle");
+
+ EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mouseup", button: 0}, win);
+
+ yield extension.unload();
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
new file mode 100644
index 000000000..6c19b17f1
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
@@ -0,0 +1,304 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* openPanel(extension, win = window, awaitLoad = false) {
+ clickBrowserAction(extension, win);
+
+ return yield awaitExtensionPanel(extension, win, awaitLoad);
+}
+
+add_task(function* testBrowserActionPopupResize() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+ },
+
+ files: {
+ "popup.html": '<!DOCTYPE html><html><head><meta charset="utf-8"></head></html>',
+ },
+ });
+
+ yield extension.startup();
+
+ let browser = yield openPanel(extension);
+
+ function* checkSize(expected) {
+ let dims = yield promiseContentDimensions(browser);
+
+ is(dims.window.innerHeight, expected, `Panel window should be ${expected}px tall`);
+ is(dims.body.clientHeight, dims.body.scrollHeight,
+ "Panel body should be tall enough to fit its contents");
+
+ // Tolerate if it is 1px too wide, as that may happen with the current resizing method.
+ ok(Math.abs(dims.window.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
+ is(dims.body.clientWidth, dims.body.scrollWidth,
+ "Panel body should be wide enough to fit its contents");
+ }
+
+ /* eslint-disable mozilla/no-cpows-in-tests */
+ function setSize(size) {
+ content.document.body.style.height = `${size}px`;
+ content.document.body.style.width = `${size}px`;
+ }
+ /* eslint-enable mozilla/no-cpows-in-tests */
+
+ let sizes = [
+ 200,
+ 400,
+ 300,
+ ];
+
+ for (let size of sizes) {
+ yield alterContent(browser, setSize, size);
+ yield checkSize(size);
+ }
+
+ yield closeBrowserAction(extension);
+ yield extension.unload();
+});
+
+function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
+ let docType = standardsMode ? "<!DOCTYPE html>" : "";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+ },
+
+ files: {
+ "popup.html": `${docType}
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ body > span {
+ display: inline-block;
+ width: 10px;
+ height: 150px;
+ border: 2px solid black;
+ }
+ .big > span {
+ width: 300px;
+ height: 100px;
+ }
+ .bigger > span {
+ width: 150px;
+ height: 150px;
+ }
+ .huge > span {
+ height: ${2 * screen.height}px;
+ }
+ </style>
+ </head>
+ <body>
+ <span></span>
+ <span></span>
+ <span></span>
+ <span></span>
+ </body>
+ </html>`,
+ },
+ });
+
+ yield extension.startup();
+
+ /* eslint-disable mozilla/no-cpows-in-tests */
+
+ if (arrowSide == "top") {
+ // Test the standalone panel for a toolbar button.
+ let browser = yield openPanel(extension, browserWin, true);
+
+ let dims = yield promiseContentDimensions(browser);
+
+ is(dims.isStandards, standardsMode, "Document has the expected compat mode");
+
+ let {innerWidth, innerHeight} = dims.window;
+
+ dims = yield alterContent(browser, () => {
+ content.document.body.classList.add("bigger");
+ });
+
+ let win = dims.window;
+ is(win.innerHeight, innerHeight, "Window height should not change");
+ ok(win.innerWidth > innerWidth, `Window width should increase (${win.innerWidth} > ${innerWidth})`);
+
+
+ dims = yield alterContent(browser, () => {
+ content.document.body.classList.remove("bigger");
+ });
+
+ win = dims.window;
+ is(win.innerHeight, innerHeight, "Window height should not change");
+
+ // The getContentSize calculation is not always reliable to single-pixel
+ // precision.
+ ok(Math.abs(win.innerWidth - innerWidth) <= 1,
+ `Window width should return to approximately its original value (${win.innerWidth} ~= ${innerWidth})`);
+
+ yield closeBrowserAction(extension, browserWin);
+ }
+
+
+ // Test the PanelUI panel for a menu panel button.
+ let widget = getBrowserActionWidget(extension);
+ CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
+
+ let browser = yield openPanel(extension, browserWin);
+
+ let {panel} = browserWin.PanelUI;
+ let origPanelRect = panel.getBoundingClientRect();
+
+ // Check that the panel is still positioned as expected.
+ let checkPanelPosition = () => {
+ is(panel.getAttribute("side"), arrowSide, "Panel arrow is positioned as expected");
+
+ let panelRect = panel.getBoundingClientRect();
+ if (arrowSide == "top") {
+ ok(panelRect.top, origPanelRect.top, "Panel has not moved downwards");
+ ok(panelRect.bottom >= origPanelRect.bottom, `Panel has not shrunk from original size (${panelRect.bottom} >= ${origPanelRect.bottom})`);
+
+ let screenBottom = browserWin.screen.availTop + browserWin.screen.availHeight;
+ let panelBottom = browserWin.mozInnerScreenY + panelRect.bottom;
+ ok(panelBottom <= screenBottom, `Bottom of popup should be on-screen. (${panelBottom} <= ${screenBottom})`);
+ } else {
+ ok(panelRect.bottom, origPanelRect.bottom, "Panel has not moved upwards");
+ ok(panelRect.top <= origPanelRect.top, `Panel has not shrunk from original size (${panelRect.top} <= ${origPanelRect.top})`);
+
+ let panelTop = browserWin.mozInnerScreenY + panelRect.top;
+ ok(panelTop >= browserWin.screen.availTop, `Top of popup should be on-screen. (${panelTop} >= ${browserWin.screen.availTop})`);
+ }
+ };
+
+ yield awaitBrowserLoaded(browser);
+
+ // Wait long enough to make sure the initial resize debouncing timer has
+ // expired.
+ yield new Promise(resolve => setTimeout(resolve, 100));
+
+ let dims = yield promiseContentDimensions(browser);
+
+ is(dims.isStandards, standardsMode, "Document has the expected compat mode");
+
+ // If the browser's preferred height is smaller than the initial height of the
+ // panel, then it will still take up the full available vertical space. Even
+ // so, we need to check that we've gotten the preferred height calculation
+ // correct, so check that explicitly.
+ let getHeight = () => parseFloat(browser.style.height);
+
+ let {innerWidth, innerHeight} = dims.window;
+ let height = getHeight();
+
+
+ let setClass = className => {
+ content.document.body.className = className;
+ };
+
+ info("Increase body children's width. " +
+ "Expect them to wrap, and the frame to grow vertically rather than widen.");
+
+ dims = yield alterContent(browser, setClass, "big");
+ let win = dims.window;
+
+ ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
+
+ is(win.innerWidth, innerWidth, "Window width should not change");
+ ok(win.innerHeight >= innerHeight, `Window height should increase (${win.innerHeight} >= ${innerHeight})`);
+ is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
+
+ checkPanelPosition();
+
+
+ info("Increase body children's width and height. " +
+ "Expect them to wrap, and the frame to grow vertically rather than widen.");
+
+ dims = yield alterContent(browser, setClass, "bigger");
+ win = dims.window;
+
+ ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
+
+ is(win.innerWidth, innerWidth, "Window width should not change");
+ ok(win.innerHeight >= innerHeight, `Window height should increase (${win.innerHeight} >= ${innerHeight})`);
+ is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
+
+ checkPanelPosition();
+
+
+ info("Increase body height beyond the height of the screen. " +
+ "Expect the panel to grow to accommodate, but not larger than the height of the screen.");
+
+ dims = yield alterContent(browser, setClass, "huge");
+ win = dims.window;
+
+ ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
+
+ is(win.innerWidth, innerWidth, "Window width should not change");
+ ok(win.innerHeight > innerHeight, `Window height should increase (${win.innerHeight} > ${innerHeight})`);
+ ok(win.innerHeight < screen.height, `Window height be less than the screen height (${win.innerHeight} < ${screen.height})`);
+ ok(win.scrollMaxY > 0, `Document should be vertically scrollable (${win.scrollMaxY} > 0)`);
+
+ checkPanelPosition();
+
+
+ info("Restore original styling. Expect original dimensions.");
+ dims = yield alterContent(browser, setClass, "");
+ win = dims.window;
+
+ is(getHeight(), height, "Browser height should return to its original value");
+
+ is(win.innerWidth, innerWidth, "Window width should not change");
+ is(win.innerHeight, innerHeight, "Window height should return to its original value");
+ is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
+
+ checkPanelPosition();
+
+ yield closeBrowserAction(extension, browserWin);
+
+ yield extension.unload();
+}
+
+add_task(function* testBrowserActionMenuResizeStandards() {
+ yield testPopupSize(true);
+});
+
+add_task(function* testBrowserActionMenuResizeQuirks() {
+ yield testPopupSize(false);
+});
+
+// Test that we still make reasonable maximum size calculations when the window
+// is close enough to the bottom of the screen that the menu panel opens above,
+// rather than below, its button.
+add_task(function* testBrowserActionMenuResizeBottomArrow() {
+ const WIDTH = 800;
+ const HEIGHT = 300;
+
+ let left = screen.availLeft + screen.availWidth - WIDTH;
+ let top = screen.availTop + screen.availHeight - HEIGHT;
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+
+ win.resizeTo(WIDTH, HEIGHT);
+
+ // Sometimes we run into problems on Linux with resizing being asynchronous
+ // and window managers not allowing us to move the window so that any part of
+ // it is off-screen, so we need to try more than once.
+ for (let i = 0; i < 20; i++) {
+ win.moveTo(left, top);
+
+ if (win.screenX == left && win.screenY == top) {
+ break;
+ }
+
+ yield new Promise(resolve => setTimeout(resolve, 100));
+ }
+
+ yield testPopupSize(true, win, "bottom");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js b/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
new file mode 100644
index 000000000..e83010958
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js
@@ -0,0 +1,59 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "unrecognized_property": "with-a-random-value",
+ },
+ },
+
+ files: {
+ "popup.html": `
+ <!DOCTYPE html>
+ <html><body>
+ <script src="popup.js"></script>
+ </body></html>
+ `,
+
+ "popup.js": function() {
+ window.onload = () => {
+ browser.runtime.sendMessage("from-popup");
+ };
+ },
+ },
+
+ background: function() {
+ browser.runtime.onMessage.addListener(msg => {
+ browser.test.assertEq(msg, "from-popup", "correct message received");
+ browser.test.sendMessage("popup");
+ });
+ },
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [{
+ message: /Reading manifest: Error processing browser_action.unrecognized_property: An unexpected property was found/,
+ }]);
+ });
+
+ yield extension.startup();
+
+ // Do this a few times to make sure the pop-up is reloaded each time.
+ for (let i = 0; i < 3; i++) {
+ clickBrowserAction(extension);
+
+ yield extension.awaitMessage("popup");
+
+ closeBrowserAction(extension);
+ }
+
+ yield extension.unload();
+
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js b/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js
new file mode 100644
index 000000000..f97a735d4
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js
@@ -0,0 +1,113 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* testExecuteBrowserActionWithOptions(options = {}) {
+ let extensionOptions = {};
+
+ extensionOptions.manifest = {
+ "commands": {
+ "_execute_browser_action": {
+ "suggested_key": {
+ "default": "Alt+Shift+J",
+ },
+ },
+ },
+ "browser_action": {
+ "browser_style": true,
+ },
+ };
+
+ if (options.withPopup) {
+ extensionOptions.manifest.browser_action.default_popup = "popup.html";
+
+ extensionOptions.files = {
+ "popup.html": `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="popup.js"></script>
+ </head>
+ </html>
+ `,
+
+ "popup.js": function() {
+ browser.runtime.sendMessage("from-browser-action-popup");
+ },
+ };
+ }
+
+ extensionOptions.background = () => {
+ browser.test.onMessage.addListener((message, withPopup) => {
+ browser.commands.onCommand.addListener((commandName) => {
+ if (commandName == "_execute_browser_action") {
+ browser.test.fail("The onCommand listener should never fire for _execute_browser_action.");
+ }
+ });
+
+ browser.browserAction.onClicked.addListener(() => {
+ if (withPopup) {
+ browser.test.fail("The onClick listener should never fire if the browserAction has a popup.");
+ browser.test.notifyFail("execute-browser-action-on-clicked-fired");
+ } else {
+ browser.test.notifyPass("execute-browser-action-on-clicked-fired");
+ }
+ });
+
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg == "from-browser-action-popup") {
+ browser.test.notifyPass("execute-browser-action-popup-opened");
+ }
+ });
+
+ browser.test.sendMessage("send-keys");
+ });
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionOptions);
+
+ extension.onMessage("send-keys", () => {
+ EventUtils.synthesizeKey("j", {altKey: true, shiftKey: true});
+ });
+
+ yield extension.startup();
+
+ if (options.inArea) {
+ let widget = getBrowserActionWidget(extension);
+ CustomizableUI.addWidgetToArea(widget.id, options.inArea);
+ }
+
+ extension.sendMessage("withPopup", options.withPopup);
+
+ if (options.withPopup) {
+ yield extension.awaitFinish("execute-browser-action-popup-opened");
+ yield closeBrowserAction(extension);
+ } else {
+ yield extension.awaitFinish("execute-browser-action-on-clicked-fired");
+ }
+ yield extension.unload();
+}
+
+add_task(function* test_execute_browser_action_with_popup() {
+ yield testExecuteBrowserActionWithOptions({
+ withPopup: true,
+ });
+});
+
+add_task(function* test_execute_browser_action_without_popup() {
+ yield testExecuteBrowserActionWithOptions();
+});
+
+add_task(function* test_execute_browser_action_in_hamburger_menu_with_popup() {
+ yield testExecuteBrowserActionWithOptions({
+ withPopup: true,
+ inArea: CustomizableUI.AREA_PANEL,
+ });
+});
+
+add_task(function* test_execute_browser_action_in_hamburger_menu_without_popup() {
+ yield testExecuteBrowserActionWithOptions({
+ inArea: CustomizableUI.AREA_PANEL,
+ });
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_commands_execute_page_action.js b/browser/components/extensions/test/browser/browser_ext_commands_execute_page_action.js
new file mode 100644
index 000000000..83684493e
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_commands_execute_page_action.js
@@ -0,0 +1,133 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_execute_page_action_without_popup() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "commands": {
+ "_execute_page_action": {
+ "suggested_key": {
+ "default": "Alt+Shift+J",
+ },
+ },
+ "send-keys-command": {
+ "suggested_key": {
+ "default": "Alt+Shift+3",
+ },
+ },
+ },
+ "page_action": {},
+ },
+
+ background: function() {
+ let isShown = false;
+
+ browser.commands.onCommand.addListener((commandName) => {
+ if (commandName == "_execute_page_action") {
+ browser.test.fail(`The onCommand listener should never fire for ${commandName}.`);
+ } else if (commandName == "send-keys-command") {
+ if (!isShown) {
+ isShown = true;
+ browser.tabs.query({currentWindow: true, active: true}, tabs => {
+ tabs.forEach(tab => {
+ browser.pageAction.show(tab.id);
+ });
+ browser.test.sendMessage("send-keys");
+ });
+ }
+ }
+ });
+
+ browser.pageAction.onClicked.addListener(() => {
+ browser.test.assertTrue(isShown, "The onClicked event should fire if the page action is shown.");
+ browser.test.notifyPass("page-action-without-popup");
+ });
+
+ browser.test.sendMessage("send-keys");
+ },
+ });
+
+ extension.onMessage("send-keys", () => {
+ EventUtils.synthesizeKey("j", {altKey: true, shiftKey: true});
+ EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("page-action-without-popup");
+ yield extension.unload();
+});
+
+add_task(function* test_execute_page_action_with_popup() {
+ let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>Test Popup</body></html>`;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "commands": {
+ "_execute_page_action": {
+ "suggested_key": {
+ "default": "Alt+Shift+J",
+ },
+ },
+ "send-keys-command": {
+ "suggested_key": {
+ "default": "Alt+Shift+3",
+ },
+ },
+ },
+ "page_action": {
+ "default_popup": "popup.html",
+ },
+ },
+
+ files: {
+ "popup.html": scriptPage("popup.js"),
+ "popup.js": function() {
+ browser.runtime.sendMessage("popup-opened");
+ },
+ },
+
+ background: function() {
+ let isShown = false;
+
+ browser.commands.onCommand.addListener((message) => {
+ if (message == "_execute_page_action") {
+ browser.test.fail(`The onCommand listener should never fire for ${message}.`);
+ }
+
+ if (message == "send-keys-command") {
+ if (!isShown) {
+ isShown = true;
+ browser.tabs.query({currentWindow: true, active: true}, tabs => {
+ tabs.forEach(tab => {
+ browser.pageAction.show(tab.id);
+ });
+ browser.test.sendMessage("send-keys");
+ });
+ }
+ }
+ });
+
+ browser.pageAction.onClicked.addListener(() => {
+ browser.test.fail(`The onClicked listener should never fire when the pageAction has a popup.`);
+ });
+
+ browser.runtime.onMessage.addListener(msg => {
+ browser.test.assertEq(msg, "popup-opened", "expected popup opened");
+ browser.test.assertTrue(isShown, "The onClicked event should fire if the page action is shown.");
+ browser.test.notifyPass("page-action-with-popup");
+ });
+
+ browser.test.sendMessage("send-keys");
+ },
+ });
+
+ extension.onMessage("send-keys", () => {
+ EventUtils.synthesizeKey("j", {altKey: true, shiftKey: true});
+ EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("page-action-with-popup");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_commands_getAll.js b/browser/components/extensions/test/browser/browser_ext_commands_getAll.js
new file mode 100644
index 000000000..5885e8aee
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_commands_getAll.js
@@ -0,0 +1,81 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
+
+add_task(function* () {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "name": "Commands Extension",
+ "commands": {
+ "with-desciption": {
+ "suggested_key": {
+ "default": "Ctrl+Shift+Y",
+ },
+ "description": "should have a description",
+ },
+ "without-description": {
+ "suggested_key": {
+ "default": "Ctrl+Shift+D",
+ },
+ },
+ "with-platform-info": {
+ "suggested_key": {
+ "mac": "Ctrl+Shift+M",
+ "linux": "Ctrl+Shift+L",
+ "windows": "Ctrl+Shift+W",
+ "android": "Ctrl+Shift+A",
+ },
+ },
+ },
+ },
+
+ background: function() {
+ browser.test.onMessage.addListener((message, additionalScope) => {
+ browser.commands.getAll((commands) => {
+ let errorMessage = "getAll should return an array of commands";
+ browser.test.assertEq(commands.length, 3, errorMessage);
+
+ let command = commands.find(c => c.name == "with-desciption");
+
+ errorMessage = "The description should match what is provided in the manifest";
+ browser.test.assertEq("should have a description", command.description, errorMessage);
+
+ errorMessage = "The shortcut should match the default shortcut provided in the manifest";
+ browser.test.assertEq("Ctrl+Shift+Y", command.shortcut, errorMessage);
+
+ command = commands.find(c => c.name == "without-description");
+
+ errorMessage = "The description should be empty when it is not provided";
+ browser.test.assertEq(null, command.description, errorMessage);
+
+ errorMessage = "The shortcut should match the default shortcut provided in the manifest";
+ browser.test.assertEq("Ctrl+Shift+D", command.shortcut, errorMessage);
+
+ let platformKeys = {
+ macosx: "M",
+ linux: "L",
+ win: "W",
+ android: "A",
+ };
+
+ command = commands.find(c => c.name == "with-platform-info");
+ let platformKey = platformKeys[additionalScope.platform];
+ let shortcut = `Ctrl+Shift+${platformKey}`;
+ errorMessage = `The shortcut should match the one provided in the manifest for OS='${additionalScope.platform}'`;
+ browser.test.assertEq(shortcut, command.shortcut, errorMessage);
+
+ browser.test.notifyPass("commands");
+ });
+ });
+ browser.test.sendMessage("ready");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+ extension.sendMessage("additional-scope", {platform: AppConstants.platform});
+ yield extension.awaitFinish("commands");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_commands_onCommand.js b/browser/components/extensions/test/browser/browser_ext_commands_onCommand.js
new file mode 100644
index 000000000..dd959dcec
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_commands_onCommand.js
@@ -0,0 +1,229 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+add_task(function* test_user_defined_commands() {
+ const testCommands = [
+ // Ctrl Shortcuts
+ {
+ name: "toggle-ctrl-a",
+ shortcut: "Ctrl+A",
+ key: "A",
+ modifiers: {
+ accelKey: true,
+ },
+ },
+ {
+ name: "toggle-ctrl-up",
+ shortcut: "Ctrl+Up",
+ key: "VK_UP",
+ modifiers: {
+ accelKey: true,
+ },
+ },
+ // Alt Shortcuts
+ {
+ name: "toggle-alt-a",
+ shortcut: "Alt+A",
+ key: "A",
+ modifiers: {
+ altKey: true,
+ },
+ },
+ {
+ name: "toggle-alt-down",
+ shortcut: "Alt+Down",
+ key: "VK_DOWN",
+ modifiers: {
+ altKey: true,
+ },
+ },
+ // Mac Shortcuts
+ {
+ name: "toggle-command-shift-page-up",
+ shortcutMac: "Command+Shift+PageUp",
+ key: "VK_PAGE_UP",
+ modifiers: {
+ accelKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-mac-control-shift+period",
+ shortcut: "Ctrl+Shift+Period",
+ shortcutMac: "MacCtrl+Shift+Period",
+ key: "VK_PERIOD",
+ modifiers: {
+ ctrlKey: true,
+ shiftKey: true,
+ },
+ },
+ // Ctrl+Shift Shortcuts
+ {
+ name: "toggle-ctrl-shift-left",
+ shortcut: "Ctrl+Shift+Left",
+ key: "VK_LEFT",
+ modifiers: {
+ accelKey: true,
+ shiftKey: true,
+ },
+ },
+ // Alt+Shift Shortcuts
+ {
+ name: "toggle-alt-shift-1",
+ shortcut: "Alt+Shift+1",
+ key: "1",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-alt-shift-a",
+ shortcut: "Alt+Shift+A",
+ key: "A",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ {
+ name: "toggle-alt-shift-right",
+ shortcut: "Alt+Shift+Right",
+ key: "VK_RIGHT",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ // Misc Shortcuts
+ {
+ name: "valid-command-with-unrecognized-property-name",
+ shortcut: "Alt+Shift+3",
+ key: "3",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ unrecognized_property: "with-a-random-value",
+ },
+ {
+ name: "spaces-in-shortcut-name",
+ shortcut: " Alt + Shift + 2 ",
+ key: "2",
+ modifiers: {
+ altKey: true,
+ shiftKey: true,
+ },
+ },
+ ];
+
+ // Create a window before the extension is loaded.
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots");
+ yield BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser);
+
+ let commands = {};
+ let isMac = AppConstants.platform == "macosx";
+ let totalMacOnlyCommands = 0;
+
+ for (let testCommand of testCommands) {
+ let command = {
+ suggested_key: {},
+ };
+
+ if (testCommand.shortcut) {
+ command.suggested_key.default = testCommand.shortcut;
+ }
+
+ if (testCommand.shortcutMac) {
+ command.suggested_key.mac = testCommand.shortcutMac;
+ }
+
+ if (testCommand.shortcutMac && !testCommand.shortcut) {
+ totalMacOnlyCommands++;
+ }
+
+ if (testCommand.unrecognized_property) {
+ command.unrecognized_property = testCommand.unrecognized_property;
+ }
+
+ commands[testCommand.name] = command;
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "commands": commands,
+ },
+
+ background: function() {
+ browser.commands.onCommand.addListener(commandName => {
+ browser.test.sendMessage("oncommand", commandName);
+ });
+ browser.test.sendMessage("ready");
+ },
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [{
+ message: /Reading manifest: Error processing commands.*.unrecognized_property: An unexpected property was found/,
+ }]);
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ function* runTest(window) {
+ for (let testCommand of testCommands) {
+ if (testCommand.shortcutMac && !testCommand.shortcut && !isMac) {
+ continue;
+ }
+ EventUtils.synthesizeKey(testCommand.key, testCommand.modifiers, window);
+ let message = yield extension.awaitMessage("oncommand");
+ is(message, testCommand.name, `Expected onCommand listener to fire with the correct name: ${testCommand.name}`);
+ }
+ }
+
+ // Create another window after the extension is loaded.
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.loadURI(win2.gBrowser.selectedBrowser, "about:robots");
+ yield BrowserTestUtils.browserLoaded(win2.gBrowser.selectedBrowser);
+
+ let totalTestCommands = Object.keys(testCommands).length;
+ let expectedCommandsRegistered = isMac ? totalTestCommands : totalTestCommands - totalMacOnlyCommands;
+
+ // Confirm the keysets have been added to both windows.
+ let keysetID = `ext-keyset-id-${makeWidgetId(extension.id)}`;
+ let keyset = win1.document.getElementById(keysetID);
+ ok(keyset != null, "Expected keyset to exist");
+ is(keyset.childNodes.length, expectedCommandsRegistered, "Expected keyset to have the correct number of children");
+
+ keyset = win2.document.getElementById(keysetID);
+ ok(keyset != null, "Expected keyset to exist");
+ is(keyset.childNodes.length, expectedCommandsRegistered, "Expected keyset to have the correct number of children");
+
+ // Confirm that the commands are registered to both windows.
+ yield focusWindow(win1);
+ yield runTest(win1);
+
+ yield focusWindow(win2);
+ yield runTest(win2);
+
+ yield extension.unload();
+
+ // Confirm that the keysets have been removed from both windows after the extension is unloaded.
+ keyset = win1.document.getElementById(keysetID);
+ is(keyset, null, "Expected keyset to be removed from the window");
+
+ keyset = win2.document.getElementById(keysetID);
+ is(keyset, null, "Expected keyset to be removed from the window");
+
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js b/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
new file mode 100644
index 000000000..8b2d9badf
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
@@ -0,0 +1,67 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["http://mochi.test/"],
+ },
+
+ background: function() {
+ let ports_received = 0;
+ let port_messages_received = 0;
+
+ browser.runtime.onConnect.addListener((port) => {
+ browser.test.assertTrue(!!port, "port1 received");
+
+ ports_received++;
+ browser.test.assertEq(1, ports_received, "1 port received");
+
+ port.onMessage.addListener((msg, msgPort) => {
+ browser.test.assertEq("port message", msg, "listener1 port message received");
+ browser.test.assertEq(port, msgPort, "onMessage should receive port as second argument");
+
+ port_messages_received++;
+ browser.test.assertEq(1, port_messages_received, "1 port message received");
+ });
+ });
+ browser.runtime.onConnect.addListener((port) => {
+ browser.test.assertTrue(!!port, "port2 received");
+
+ ports_received++;
+ browser.test.assertEq(2, ports_received, "2 ports received");
+
+ port.onMessage.addListener((msg, msgPort) => {
+ browser.test.assertEq("port message", msg, "listener2 port message received");
+ browser.test.assertEq(port, msgPort, "onMessage should receive port as second argument");
+
+ port_messages_received++;
+ browser.test.assertEq(2, port_messages_received, "2 port messages received");
+
+ browser.test.notifyPass("contentscript_connect.pass");
+ });
+ });
+
+ browser.tabs.executeScript({file: "script.js"}).catch(e => {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("contentscript_connect.pass");
+ });
+ },
+
+ files: {
+ "script.js": function() {
+ let port = browser.runtime.connect();
+ port.postMessage("port message");
+ },
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("contentscript_connect.pass");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus.js b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
new file mode 100644
index 000000000..fa1483b20
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -0,0 +1,342 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+
+ background: function() {
+ browser.contextMenus.create({
+ id: "clickme-image",
+ title: "Click me!",
+ contexts: ["image"],
+ });
+ browser.contextMenus.create({
+ id: "clickme-page",
+ title: "Click me!",
+ contexts: ["page"],
+ });
+ browser.test.notifyPass();
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish();
+
+ let contentAreaContextMenu = yield openContextMenu("#img1");
+ let item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+ is(item.length, 1, "contextMenu item for image was found");
+ yield closeContextMenu();
+
+ contentAreaContextMenu = yield openContextMenu("body");
+ item = contentAreaContextMenu.getElementsByAttribute("label", "Click me!");
+ is(item.length, 1, "contextMenu item for page was found");
+ yield closeContextMenu();
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+});
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+
+ background: async function() {
+ // A generic onclick callback function.
+ function genericOnClick(info, tab) {
+ browser.test.sendMessage("onclick", {info, tab});
+ }
+
+ browser.contextMenus.onClicked.addListener((info, tab) => {
+ browser.test.sendMessage("browser.contextMenus.onClicked", {info, tab});
+ });
+
+ browser.contextMenus.create({
+ contexts: ["all"],
+ type: "separator",
+ });
+
+ let contexts = ["page", "selection", "image", "editable"];
+ for (let i = 0; i < contexts.length; i++) {
+ let context = contexts[i];
+ let title = context;
+ browser.contextMenus.create({
+ title: title,
+ contexts: [context],
+ id: "ext-" + context,
+ onclick: genericOnClick,
+ });
+ if (context == "selection") {
+ browser.contextMenus.update("ext-selection", {
+ title: "selection is: '%s'",
+ onclick: (info, tab) => {
+ browser.contextMenus.removeAll();
+ genericOnClick(info, tab);
+ },
+ });
+ }
+ }
+
+ let parent = browser.contextMenus.create({
+ title: "parent",
+ });
+ browser.contextMenus.create({
+ title: "child1",
+ parentId: parent,
+ onclick: genericOnClick,
+ });
+ let child2 = browser.contextMenus.create({
+ title: "child2",
+ parentId: parent,
+ onclick: genericOnClick,
+ });
+
+ let parentToDel = browser.contextMenus.create({
+ title: "parentToDel",
+ });
+ browser.contextMenus.create({
+ title: "child1",
+ parentId: parentToDel,
+ onclick: genericOnClick,
+ });
+ browser.contextMenus.create({
+ title: "child2",
+ parentId: parentToDel,
+ onclick: genericOnClick,
+ });
+ browser.contextMenus.remove(parentToDel);
+
+ browser.contextMenus.create({
+ title: "Without onclick property",
+ id: "ext-without-onclick",
+ });
+
+ await browser.test.assertRejects(
+ browser.contextMenus.update(parent, {parentId: child2}),
+ /cannot be an ancestor/,
+ "Should not be able to reparent an item as descendent of itself");
+
+ browser.test.notifyPass("contextmenus");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("contextmenus");
+
+ let expectedClickInfo = {
+ menuItemId: "ext-image",
+ mediaType: "image",
+ srcUrl: "http://mochi.test:8888/browser/browser/components/extensions/test/browser/ctxmenu-image.png",
+ pageUrl: PAGE,
+ editable: false,
+ };
+
+ function checkClickInfo(result) {
+ for (let i of Object.keys(expectedClickInfo)) {
+ is(result.info[i], expectedClickInfo[i],
+ "click info " + i + " expected to be: " + expectedClickInfo[i] + " but was: " + result.info[i]);
+ }
+ is(expectedClickInfo.pageSrc, result.tab.url, "click info page source is the right tab");
+ }
+
+ let extensionMenuRoot = yield openExtensionContextMenu();
+
+ // Check some menu items
+ let items = extensionMenuRoot.getElementsByAttribute("label", "image");
+ is(items.length, 1, "contextMenu item for image was found (context=image)");
+ let image = items[0];
+
+ items = extensionMenuRoot.getElementsByAttribute("label", "selection-edited");
+ is(items.length, 0, "contextMenu item for selection was not found (context=image)");
+
+ items = extensionMenuRoot.getElementsByAttribute("label", "parentToDel");
+ is(items.length, 0, "contextMenu item for removed parent was not found (context=image)");
+
+ items = extensionMenuRoot.getElementsByAttribute("label", "parent");
+ is(items.length, 1, "contextMenu item for parent was found (context=image)");
+
+ is(items[0].childNodes[0].childNodes.length, 2, "child items for parent were found (context=image)");
+
+ // Click on ext-image item and check the click results
+ yield closeExtensionContextMenu(image);
+
+ let result = yield extension.awaitMessage("onclick");
+ checkClickInfo(result);
+ result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+ checkClickInfo(result);
+
+
+ // Test "editable" context and OnClick data property.
+ extensionMenuRoot = yield openExtensionContextMenu("#edit-me");
+
+ // Check some menu items.
+ items = extensionMenuRoot.getElementsByAttribute("label", "editable");
+ is(items.length, 1, "contextMenu item for text input element was found (context=editable)");
+ let editable = items[0];
+
+ // Click on ext-editable item and check the click results.
+ yield closeExtensionContextMenu(editable);
+
+ expectedClickInfo = {
+ menuItemId: "ext-editable",
+ pageUrl: PAGE,
+ editable: true,
+ };
+
+ result = yield extension.awaitMessage("onclick");
+ checkClickInfo(result);
+ result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+ checkClickInfo(result);
+
+
+ // Select some text
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* (arg) {
+ let doc = content.document;
+ let range = doc.createRange();
+ let selection = content.getSelection();
+ selection.removeAllRanges();
+ let textNode = doc.getElementById("img1").previousSibling;
+ range.setStart(textNode, 0);
+ range.setEnd(textNode, 100);
+ selection.addRange(range);
+ });
+
+ // Bring up context menu again
+ extensionMenuRoot = yield openExtensionContextMenu();
+
+ // Check some menu items
+ items = extensionMenuRoot.getElementsByAttribute("label", "Without onclick property");
+ is(items.length, 1, "contextMenu item was found (context=page)");
+
+ yield closeExtensionContextMenu(items[0]);
+
+ expectedClickInfo = {
+ menuItemId: "ext-without-onclick",
+ pageUrl: PAGE,
+ };
+
+ result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+ checkClickInfo(result);
+
+ // Bring up context menu again
+ extensionMenuRoot = yield openExtensionContextMenu();
+
+ // Check some menu items
+ items = extensionMenuRoot.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
+ is(items.length, 1, "contextMenu item for selection was found (context=selection)");
+ let selectionItem = items[0];
+
+ items = extensionMenuRoot.getElementsByAttribute("label", "selection");
+ is(items.length, 0, "contextMenu item label update worked (context=selection)");
+
+ yield closeExtensionContextMenu(selectionItem);
+
+ expectedClickInfo = {
+ menuItemId: "ext-selection",
+ pageUrl: PAGE,
+ selectionText: "just some text 1234567890123456789012345678901234567890123456789012345678901234567890123456789012",
+ };
+
+ result = yield extension.awaitMessage("onclick");
+ checkClickInfo(result);
+ result = yield extension.awaitMessage("browser.contextMenus.onClicked");
+ checkClickInfo(result);
+
+ let contentAreaContextMenu = yield openContextMenu("#img1");
+ items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+ is(items.length, 0, "top level item was not found (after removeAll()");
+ yield closeContextMenu();
+
+ yield extension.unload();
+ yield BrowserTestUtils.removeTab(tab1);
+});
+
+add_task(function* testRemoveAllWithTwoExtensions() {
+ const tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
+ const manifest = {permissions: ["contextMenus"]};
+
+ const first = ExtensionTestUtils.loadExtension({manifest, background() {
+ browser.contextMenus.create({title: "alpha", contexts: ["all"]});
+
+ browser.contextMenus.onClicked.addListener(() => {
+ browser.contextMenus.removeAll();
+ });
+ browser.test.onMessage.addListener(msg => {
+ if (msg == "ping") {
+ browser.test.sendMessage("pong-alpha");
+ return;
+ }
+ browser.contextMenus.create({title: "gamma", contexts: ["all"]});
+ });
+ }});
+
+ const second = ExtensionTestUtils.loadExtension({manifest, background() {
+ browser.contextMenus.create({title: "beta", contexts: ["all"]});
+
+ browser.contextMenus.onClicked.addListener(() => {
+ browser.contextMenus.removeAll();
+ });
+
+ browser.test.onMessage.addListener(() => {
+ browser.test.sendMessage("pong-beta");
+ });
+ }});
+
+ yield first.startup();
+ yield second.startup();
+
+ function* confirmMenuItems(...items) {
+ // Round-trip to extension to make sure that the context menu state has been
+ // updated by the async contextMenus.create / contextMenus.removeAll calls.
+ first.sendMessage("ping");
+ second.sendMessage("ping");
+ yield first.awaitMessage("pong-alpha");
+ yield second.awaitMessage("pong-beta");
+
+ const menu = yield openContextMenu();
+ for (const id of ["alpha", "beta", "gamma"]) {
+ const expected = items.includes(id);
+ const found = menu.getElementsByAttribute("label", id);
+ is(found.length, expected, `menu item ${id} ${expected ? "" : "not "}found`);
+ }
+ // Return the first menu item, we need to click it.
+ return menu.getElementsByAttribute("label", items[0])[0];
+ }
+
+ // Confirm alpha, beta exist; click alpha to remove it.
+ const alpha = yield confirmMenuItems("alpha", "beta");
+ yield closeExtensionContextMenu(alpha);
+
+ // Confirm only beta exists.
+ yield confirmMenuItems("beta");
+ yield closeContextMenu();
+
+ // Create gamma, confirm, click.
+ first.sendMessage("create");
+ const beta = yield confirmMenuItems("beta", "gamma");
+ yield closeExtensionContextMenu(beta);
+
+ // Confirm only gamma is left.
+ yield confirmMenuItems("gamma");
+ yield closeContextMenu();
+
+ yield first.unload();
+ yield second.unload();
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js
new file mode 100644
index 000000000..a3fa9d32c
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js
@@ -0,0 +1,96 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+
+ background: function() {
+ // Report onClickData info back.
+ browser.contextMenus.onClicked.addListener(info => {
+ browser.test.sendMessage("contextmenus-click", info);
+ });
+
+ browser.contextMenus.create({
+ title: "Checkbox",
+ type: "checkbox",
+ });
+
+ browser.contextMenus.create({
+ type: "separator",
+ });
+
+ browser.contextMenus.create({
+ title: "Checkbox",
+ type: "checkbox",
+ checked: true,
+ });
+
+ browser.contextMenus.create({
+ title: "Checkbox",
+ type: "checkbox",
+ });
+
+ browser.test.notifyPass("contextmenus-checkboxes");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("contextmenus-checkboxes");
+
+ function confirmCheckboxStates(extensionMenuRoot, expectedStates) {
+ let checkboxItems = extensionMenuRoot.getElementsByAttribute("type", "checkbox");
+
+ is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
+
+ is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
+ is(checkboxItems[1].hasAttribute("checked"), expectedStates[1], `checkbox item 2 has state (checked=${expectedStates[1]})`);
+ is(checkboxItems[2].hasAttribute("checked"), expectedStates[2], `checkbox item 3 has state (checked=${expectedStates[2]})`);
+
+ return extensionMenuRoot.getElementsByAttribute("type", "checkbox");
+ }
+
+ function confirmOnClickData(onClickData, id, was, checked) {
+ is(onClickData.wasChecked, was, `checkbox item ${id} was ${was ? "" : "not "}checked before the click`);
+ is(onClickData.checked, checked, `checkbox item ${id} is ${checked ? "" : "not "}checked after the click`);
+ }
+
+ let extensionMenuRoot = yield openExtensionContextMenu();
+ let items = confirmCheckboxStates(extensionMenuRoot, [false, true, false]);
+ yield closeExtensionContextMenu(items[0]);
+
+ let result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 1, false, true);
+
+ extensionMenuRoot = yield openExtensionContextMenu();
+ items = confirmCheckboxStates(extensionMenuRoot, [true, true, false]);
+ yield closeExtensionContextMenu(items[2]);
+
+ result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 3, false, true);
+
+ extensionMenuRoot = yield openExtensionContextMenu();
+ items = confirmCheckboxStates(extensionMenuRoot, [true, true, true]);
+ yield closeExtensionContextMenu(items[0]);
+
+ result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 1, true, false);
+
+ extensionMenuRoot = yield openExtensionContextMenu();
+ items = confirmCheckboxStates(extensionMenuRoot, [false, true, true]);
+ yield closeExtensionContextMenu(items[2]);
+
+ result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 3, true, false);
+
+ yield extension.unload();
+ yield BrowserTestUtils.removeTab(tab1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
new file mode 100644
index 000000000..a3d31bd19
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -0,0 +1,76 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+ let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
+ let decodedImageData = atob(encodedImageData);
+ const IMAGE_ARRAYBUFFER = Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ "icons": {
+ "18": "extension.png",
+ },
+ },
+
+ files: {
+ "extension.png": IMAGE_ARRAYBUFFER,
+ },
+
+ background: function() {
+ let menuitemId = browser.contextMenus.create({
+ title: "child-to-delete",
+ onclick: () => {
+ browser.contextMenus.remove(menuitemId);
+ },
+ });
+
+ browser.contextMenus.create({
+ title: "child",
+ });
+
+ browser.test.onMessage.addListener(() => {
+ browser.test.sendMessage("pong");
+ });
+ browser.test.notifyPass("contextmenus-icons");
+ },
+ });
+
+ let confirmContextMenuIcon = (rootElement) => {
+ let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/extension\.png$`);
+ let imageUrl = rootElement.getAttribute("image");
+ ok(expectedURL.test(imageUrl), "The context menu should display the extension icon next to the root element");
+ };
+
+ yield extension.startup();
+ yield extension.awaitFinish("contextmenus-icons");
+
+ let extensionMenu = yield openExtensionContextMenu();
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let topLevelMenuItem = contextMenu.getElementsByAttribute("ext-type", "top-level-menu")[0];
+ confirmContextMenuIcon(topLevelMenuItem);
+
+ let childToDelete = extensionMenu.getElementsByAttribute("label", "child-to-delete")[0];
+ yield closeExtensionContextMenu(childToDelete);
+ // Now perform a roundtrip to the extension process to make sure that the
+ // click event has had a chance to fire.
+ extension.sendMessage("ping");
+ yield extension.awaitMessage("pong");
+
+ yield openExtensionContextMenu();
+
+ contextMenu = document.getElementById("contentAreaContextMenu");
+ topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child")[0];
+
+ confirmContextMenuIcon(topLevelMenuItem);
+ yield closeContextMenu();
+
+ yield extension.unload();
+ yield BrowserTestUtils.removeTab(tab1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
new file mode 100644
index 000000000..96453863d
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
@@ -0,0 +1,196 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Loaded both as a background script and a tab page.
+function testScript() {
+ let page = location.pathname.includes("tab.html") ? "tab" : "background";
+ let clickCounts = {
+ old: 0,
+ new: 0,
+ };
+ browser.contextMenus.onClicked.addListener(() => {
+ // Async to give other onclick handlers a chance to fire.
+ setTimeout(() => {
+ browser.test.sendMessage("onClicked-fired", page);
+ });
+ });
+ browser.test.onMessage.addListener((toPage, msg) => {
+ if (toPage !== page) {
+ return;
+ }
+ browser.test.log(`Received ${msg} for ${toPage}`);
+ if (msg == "get-click-counts") {
+ browser.test.sendMessage("click-counts", clickCounts);
+ } else if (msg == "clear-click-counts") {
+ clickCounts.old = clickCounts.new = 0;
+ browser.test.sendMessage("next");
+ } else if (msg == "create-with-onclick") {
+ browser.contextMenus.create({
+ id: "iden",
+ title: "tifier",
+ onclick() {
+ ++clickCounts.old;
+ browser.test.log(`onclick fired for original onclick property in ${page}`);
+ },
+ }, () => browser.test.sendMessage("next"));
+ } else if (msg == "create-without-onclick") {
+ browser.contextMenus.create({
+ id: "iden",
+ title: "tifier",
+ }, () => browser.test.sendMessage("next"));
+ } else if (msg == "update-without-onclick") {
+ browser.contextMenus.update("iden", {
+ enabled: true, // Already enabled, so this does nothing.
+ }, () => browser.test.sendMessage("next"));
+ } else if (msg == "update-with-onclick") {
+ browser.contextMenus.update("iden", {
+ onclick() {
+ ++clickCounts.new;
+ browser.test.log(`onclick fired for updated onclick property in ${page}`);
+ },
+ }, () => browser.test.sendMessage("next"));
+ } else if (msg == "remove") {
+ browser.contextMenus.remove("iden", () => browser.test.sendMessage("next"));
+ } else if (msg == "removeAll") {
+ browser.contextMenus.removeAll(() => browser.test.sendMessage("next"));
+ }
+ });
+
+ if (page == "background") {
+ browser.test.log("Opening tab.html");
+ browser.tabs.create({
+ url: "tab.html",
+ active: false, // To not interfere with the context menu tests.
+ });
+ } else {
+ // Sanity check - the pages must be in the same process.
+ let pages = browser.extension.getViews();
+ browser.test.assertTrue(pages.includes(window),
+ "Expected this tab to be an extension view");
+ pages = pages.filter(w => w !== window);
+ browser.test.assertEq(pages[0], browser.extension.getBackgroundPage(),
+ "Expected the other page to be a background page");
+ browser.test.sendMessage("tab.html ready");
+ }
+}
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+ background: testScript,
+ files: {
+ "tab.html": `<!DOCTYPE html><meta charset="utf-8"><script src="tab.js"></script>`,
+ "tab.js": testScript,
+ },
+ });
+ yield extension.startup();
+ yield extension.awaitMessage("tab.html ready");
+
+ function* clickContextMenu() {
+ // Using openContextMenu instead of openExtensionContextMenu because the
+ // test extension has only one context menu item.
+ let extensionMenuRoot = yield openContextMenu();
+ let items = extensionMenuRoot.getElementsByAttribute("label", "tifier");
+ is(items.length, 1, "Expected one context menu item");
+ yield closeExtensionContextMenu(items[0]);
+ // One of them is "tab", the other is "background".
+ info(`onClicked from: ${yield extension.awaitMessage("onClicked-fired")}`);
+ info(`onClicked from: ${yield extension.awaitMessage("onClicked-fired")}`);
+ }
+
+ function* getCounts(page) {
+ extension.sendMessage(page, "get-click-counts");
+ return yield extension.awaitMessage("click-counts");
+ }
+ function* resetCounts() {
+ extension.sendMessage("tab", "clear-click-counts");
+ extension.sendMessage("background", "clear-click-counts");
+ yield extension.awaitMessage("next");
+ yield extension.awaitMessage("next");
+ }
+
+ // During this test, at most one "onclick" attribute is expected at any time.
+ for (let pageOne of ["background", "tab"]) {
+ for (let pageTwo of ["background", "tab"]) {
+ info(`Testing with menu created by ${pageOne} and updated by ${pageTwo}`);
+ extension.sendMessage(pageOne, "create-with-onclick");
+ yield extension.awaitMessage("next");
+
+ // Test that update without onclick attribute does not clear the existing
+ // onclick handler.
+ extension.sendMessage(pageTwo, "update-without-onclick");
+ yield extension.awaitMessage("next");
+ yield clickContextMenu();
+ let clickCounts = yield getCounts(pageOne);
+ is(clickCounts.old, 1, `Original onclick should still be present in ${pageOne}`);
+ is(clickCounts.new, 0, `Not expecting any new handlers in ${pageOne}`);
+ if (pageOne !== pageTwo) {
+ clickCounts = yield getCounts(pageTwo);
+ is(clickCounts.old, 0, `Not expecting any handlers in ${pageTwo}`);
+ is(clickCounts.new, 0, `Not expecting any new handlers in ${pageTwo}`);
+ }
+ yield resetCounts();
+
+ // Test that update with onclick handler in a different page clears the
+ // existing handler and activates the new onclick handler.
+ extension.sendMessage(pageTwo, "update-with-onclick");
+ yield extension.awaitMessage("next");
+ yield clickContextMenu();
+ clickCounts = yield getCounts(pageOne);
+ is(clickCounts.old, 0, `Original onclick should be gone from ${pageOne}`);
+ if (pageOne !== pageTwo) {
+ is(clickCounts.new, 0, `Still not expecting new handlers in ${pageOne}`);
+ }
+ clickCounts = yield getCounts(pageTwo);
+ if (pageOne !== pageTwo) {
+ is(clickCounts.old, 0, `Not expecting an old onclick in ${pageTwo}`);
+ }
+ is(clickCounts.new, 1, `New onclick should be triggered in ${pageTwo}`);
+ yield resetCounts();
+
+ // Test that updating the handler (different again from the last `update`
+ // call, but the same as the `create` call) clears the existing handler
+ // and activates the new onclick handler.
+ extension.sendMessage(pageOne, "update-with-onclick");
+ yield extension.awaitMessage("next");
+ yield clickContextMenu();
+ clickCounts = yield getCounts(pageOne);
+ is(clickCounts.new, 1, `onclick should be triggered in ${pageOne}`);
+ if (pageOne !== pageTwo) {
+ clickCounts = yield getCounts(pageTwo);
+ is(clickCounts.new, 0, `onclick should be gone from ${pageTwo}`);
+ }
+ yield resetCounts();
+
+ // Test that removing the context menu and recreating it with the same ID
+ // (in a different context) does not leave behind any onclick handlers.
+ extension.sendMessage(pageTwo, "remove");
+ yield extension.awaitMessage("next");
+ extension.sendMessage(pageTwo, "create-without-onclick");
+ yield extension.awaitMessage("next");
+ yield clickContextMenu();
+ clickCounts = yield getCounts(pageOne);
+ is(clickCounts.new, 0, `Did not expect any click handlers in ${pageOne}`);
+ if (pageOne !== pageTwo) {
+ clickCounts = yield getCounts(pageTwo);
+ is(clickCounts.new, 0, `Did not expect any click handlers in ${pageTwo}`);
+ }
+ yield resetCounts();
+
+ // Remove context menu for the next iteration of the test. And just to get
+ // more coverage, let's use removeAll instead of remove.
+ extension.sendMessage(pageOne, "removeAll");
+ yield extension.awaitMessage("next");
+ }
+ }
+ yield extension.unload();
+ yield BrowserTestUtils.removeTab(tab1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_radioGroups.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_radioGroups.js
new file mode 100644
index 000000000..3c5fa584b
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_radioGroups.js
@@ -0,0 +1,100 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+
+ background: function() {
+ // Report onClickData info back.
+ browser.contextMenus.onClicked.addListener(info => {
+ browser.test.sendMessage("contextmenus-click", info);
+ });
+
+ browser.contextMenus.create({
+ title: "radio-group-1",
+ type: "radio",
+ checked: true,
+ });
+
+ browser.contextMenus.create({
+ type: "separator",
+ });
+
+ browser.contextMenus.create({
+ title: "radio-group-2",
+ type: "radio",
+ });
+
+ browser.contextMenus.create({
+ title: "radio-group-2",
+ type: "radio",
+ });
+
+ browser.test.notifyPass("contextmenus-radio-groups");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("contextmenus-radio-groups");
+
+ function confirmRadioGroupStates(extensionMenuRoot, expectedStates) {
+ let radioItems = extensionMenuRoot.getElementsByAttribute("type", "radio");
+ let radioGroup1 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-1");
+ let radioGroup2 = extensionMenuRoot.getElementsByAttribute("label", "radio-group-2");
+
+ is(radioItems.length, 3, "there should be 3 radio items in the context menu");
+ is(radioGroup1.length, 1, "the first radio group should only have 1 radio item");
+ is(radioGroup2.length, 2, "the second radio group should only have 2 radio items");
+
+ is(radioGroup1[0].hasAttribute("checked"), expectedStates[0], `radio item 1 has state (checked=${expectedStates[0]})`);
+ is(radioGroup2[0].hasAttribute("checked"), expectedStates[1], `radio item 2 has state (checked=${expectedStates[1]})`);
+ is(radioGroup2[1].hasAttribute("checked"), expectedStates[2], `radio item 3 has state (checked=${expectedStates[2]})`);
+
+ return extensionMenuRoot.getElementsByAttribute("type", "radio");
+ }
+
+ function confirmOnClickData(onClickData, id, was, checked) {
+ is(onClickData.wasChecked, was, `radio item ${id} was ${was ? "" : "not "}checked before the click`);
+ is(onClickData.checked, checked, `radio item ${id} is ${checked ? "" : "not "}checked after the click`);
+ }
+
+ let extensionMenuRoot = yield openExtensionContextMenu();
+ let items = confirmRadioGroupStates(extensionMenuRoot, [true, false, false]);
+ yield closeExtensionContextMenu(items[1]);
+
+ let result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 2, false, true);
+
+ extensionMenuRoot = yield openExtensionContextMenu();
+ items = confirmRadioGroupStates(extensionMenuRoot, [true, true, false]);
+ yield closeExtensionContextMenu(items[2]);
+
+ result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 3, false, true);
+
+ extensionMenuRoot = yield openExtensionContextMenu();
+ items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
+ yield closeExtensionContextMenu(items[0]);
+
+ result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 1, true, true);
+
+ extensionMenuRoot = yield openExtensionContextMenu();
+ items = confirmRadioGroupStates(extensionMenuRoot, [true, false, true]);
+ yield closeExtensionContextMenu(items[0]);
+
+ result = yield extension.awaitMessage("contextmenus-click");
+ confirmOnClickData(result, 1, true, true);
+
+ yield extension.unload();
+ yield BrowserTestUtils.removeTab(tab1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_uninstall.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_uninstall.js
new file mode 100644
index 000000000..fdf06d656
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_uninstall.js
@@ -0,0 +1,84 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+ // Install an extension.
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+
+ background: function() {
+ browser.contextMenus.create({title: "a"});
+ browser.contextMenus.create({title: "b"});
+ browser.test.notifyPass("contextmenus-icons");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("contextmenus-icons");
+
+ // Open the context menu.
+ let contextMenu = yield openContextMenu("#img1");
+
+ // Confirm that the extension menu item exists.
+ let topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+ is(topLevelExtensionMenuItems.length, 1, "the top level extension menu item exists");
+
+ yield closeContextMenu();
+
+ // Uninstall the extension.
+ yield extension.unload();
+
+ // Open the context menu.
+ contextMenu = yield openContextMenu("#img1");
+
+ // Confirm that the extension menu item has been removed.
+ topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+ is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
+
+ yield closeContextMenu();
+
+ // Install a new extension.
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+ background: function() {
+ browser.contextMenus.create({title: "c"});
+ browser.contextMenus.create({title: "d"});
+ browser.test.notifyPass("contextmenus-uninstall-second-extension");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("contextmenus-uninstall-second-extension");
+
+ // Open the context menu.
+ contextMenu = yield openContextMenu("#img1");
+
+ // Confirm that only the new extension menu item is in the context menu.
+ topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+ is(topLevelExtensionMenuItems.length, 1, "only one top level extension menu item should exist");
+
+ // Close the context menu.
+ yield closeContextMenu();
+
+ // Uninstall the extension.
+ yield extension.unload();
+
+ // Open the context menu.
+ contextMenu = yield openContextMenu("#img1");
+
+ // Confirm that no extension menu items exist.
+ topLevelExtensionMenuItems = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+ is(topLevelExtensionMenuItems.length, 0, "no top level extension menu items should exist");
+
+ yield closeContextMenu();
+
+ yield BrowserTestUtils.removeTab(tab1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_urlPatterns.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_urlPatterns.js
new file mode 100644
index 000000000..7849b8778
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_urlPatterns.js
@@ -0,0 +1,254 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["contextMenus"],
+ },
+
+ background: function() {
+ // Test menu items using targetUrlPatterns.
+ browser.contextMenus.create({
+ title: "targetUrlPatterns-patternMatches-contextAll",
+ targetUrlPatterns: ["*://*/*ctxmenu-image.png", "*://*/*some-link"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "targetUrlPatterns-patternMatches-contextImage",
+ targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
+ contexts: ["image"],
+ });
+
+ browser.contextMenus.create({
+ title: "targetUrlPatterns-patternMatches-contextLink",
+ targetUrlPatterns: ["*://*/*some-link"],
+ contexts: ["link"],
+ });
+
+ browser.contextMenus.create({
+ title: "targetUrlPatterns-patternDoesNotMatch-contextAll",
+ targetUrlPatterns: ["*://*/does-not-match"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "targetUrlPatterns-patternDoesNotMatch-contextImage",
+ targetUrlPatterns: ["*://*/does-not-match"],
+ contexts: ["image"],
+ });
+
+ browser.contextMenus.create({
+ title: "targetUrlPatterns-patternDoesNotMatch-contextLink",
+ targetUrlPatterns: ["*://*/does-not-match"],
+ contexts: ["link"],
+ });
+
+ // Test menu items using documentUrlPatterns.
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternMatches-contextAll",
+ documentUrlPatterns: ["*://*/*context.html"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternMatches-contextImage",
+ documentUrlPatterns: ["*://*/*context.html", "http://*/url-that-does-not-match"],
+ contexts: ["image"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternMatches-contextLink",
+ documentUrlPatterns: ["*://*/*context.html", "*://*/does-not-match"],
+ contexts: ["link"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternDoesNotMatch-contextAll",
+ documentUrlPatterns: ["*://*/does-not-match"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternDoesNotMatch-contextImage",
+ documentUrlPatterns: ["*://*/does-not-match"],
+ contexts: ["image"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternDoesNotMatch-contextLink",
+ documentUrlPatterns: ["*://*/does-not-match"],
+ contexts: ["link"],
+ });
+
+ // Test menu items using both targetUrlPatterns and documentUrlPatterns.
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextAll",
+ documentUrlPatterns: ["*://*/*context.html"],
+ targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextAll",
+ documentUrlPatterns: ["*://does-not-match"],
+ targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextAll",
+ documentUrlPatterns: ["*://*/*context.html"],
+ targetUrlPatterns: ["*://does-not-match"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextAll",
+ documentUrlPatterns: ["*://does-not-match"],
+ targetUrlPatterns: ["*://does-not-match"],
+ contexts: ["all"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextImage",
+ documentUrlPatterns: ["*://*/*context.html"],
+ targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
+ contexts: ["image"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextImage",
+ documentUrlPatterns: ["*://does-not-match"],
+ targetUrlPatterns: ["*://*/*ctxmenu-image.png"],
+ contexts: ["image"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextImage",
+ documentUrlPatterns: ["*://*/*context.html"],
+ targetUrlPatterns: ["*://does-not-match"],
+ contexts: ["image"],
+ });
+
+ browser.contextMenus.create({
+ title: "documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextImage",
+ documentUrlPatterns: ["*://does-not-match"],
+ targetUrlPatterns: ["*://does-not-match"],
+ contexts: ["image"],
+ });
+
+ browser.test.notifyPass("contextmenus-urlPatterns");
+ },
+ });
+
+ function* confirmContextMenuItems(menu, expected) {
+ for (let [label, shouldShow] of expected) {
+ let items = menu.getElementsByAttribute("label", label);
+ if (shouldShow) {
+ is(items.length, 1, `The menu item for label ${label} was correctly shown`);
+ } else {
+ is(items.length, 0, `The menu item for label ${label} was correctly not shown`);
+ }
+ }
+ }
+
+ yield extension.startup();
+ yield extension.awaitFinish("contextmenus-urlPatterns");
+
+ let extensionContextMenu = yield openExtensionContextMenu("#img1");
+ let expected = [
+ ["targetUrlPatterns-patternMatches-contextAll", true],
+ ["targetUrlPatterns-patternMatches-contextImage", true],
+ ["targetUrlPatterns-patternMatches-contextLink", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ["documentUrlPatterns-patternMatches-contextAll", true],
+ ["documentUrlPatterns-patternMatches-contextImage", true],
+ ["documentUrlPatterns-patternMatches-contextLink", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextAll", true],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextAll", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextImage", true],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextImage", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ];
+ yield confirmContextMenuItems(extensionContextMenu, expected);
+ yield closeContextMenu();
+
+ let contextMenu = yield openContextMenu("body");
+ expected = [
+ ["targetUrlPatterns-patternMatches-contextAll", false],
+ ["targetUrlPatterns-patternMatches-contextImage", false],
+ ["targetUrlPatterns-patternMatches-contextLink", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ["documentUrlPatterns-patternMatches-contextAll", true],
+ ["documentUrlPatterns-patternMatches-contextImage", false],
+ ["documentUrlPatterns-patternMatches-contextLink", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextAll", false],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextAll", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternMatches-contextImage", false],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternMatches-contextImage", false],
+ ["documentUrlPatterns-patternMatches-targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["documentUrlPatterns-patternDoesNotMatch-targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ];
+ yield confirmContextMenuItems(contextMenu, expected);
+ yield closeContextMenu();
+
+ contextMenu = yield openContextMenu("#link1");
+ expected = [
+ ["targetUrlPatterns-patternMatches-contextAll", true],
+ ["targetUrlPatterns-patternMatches-contextImage", false],
+ ["targetUrlPatterns-patternMatches-contextLink", true],
+ ["targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ["documentUrlPatterns-patternMatches-contextAll", true],
+ ["documentUrlPatterns-patternMatches-contextImage", false],
+ ["documentUrlPatterns-patternMatches-contextLink", true],
+ ["documentUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ];
+ yield confirmContextMenuItems(contextMenu, expected);
+ yield closeContextMenu();
+
+ contextMenu = yield openContextMenu("#img-wrapped-in-link");
+ expected = [
+ ["targetUrlPatterns-patternMatches-contextAll", true],
+ ["targetUrlPatterns-patternMatches-contextImage", true],
+ ["targetUrlPatterns-patternMatches-contextLink", true],
+ ["targetUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["targetUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ["documentUrlPatterns-patternMatches-contextAll", true],
+ ["documentUrlPatterns-patternMatches-contextImage", true],
+ ["documentUrlPatterns-patternMatches-contextLink", true],
+ ["documentUrlPatterns-patternDoesNotMatch-contextAll", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextImage", false],
+ ["documentUrlPatterns-patternDoesNotMatch-contextLink", false],
+ ];
+ yield confirmContextMenuItems(contextMenu, expected);
+ yield closeContextMenu();
+
+ yield extension.unload();
+ yield BrowserTestUtils.removeTab(tab1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_currentWindow.js b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
new file mode 100644
index 000000000..11660bf4d
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -0,0 +1,149 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function genericChecker() {
+ let kind = "background";
+ let path = window.location.pathname;
+ if (path.includes("/popup.html")) {
+ kind = "popup";
+ } else if (path.includes("/page.html")) {
+ kind = "page";
+ }
+
+ browser.test.onMessage.addListener((msg, ...args) => {
+ if (msg == kind + "-check-current1") {
+ browser.tabs.query({
+ currentWindow: true,
+ }, function(tabs) {
+ browser.test.sendMessage("result", tabs[0].windowId);
+ });
+ } else if (msg == kind + "-check-current2") {
+ browser.tabs.query({
+ windowId: browser.windows.WINDOW_ID_CURRENT,
+ }, function(tabs) {
+ browser.test.sendMessage("result", tabs[0].windowId);
+ });
+ } else if (msg == kind + "-check-current3") {
+ browser.windows.getCurrent(function(window) {
+ browser.test.sendMessage("result", window.id);
+ });
+ } else if (msg == kind + "-open-page") {
+ browser.tabs.create({windowId: args[0], url: browser.runtime.getURL("page.html")});
+ } else if (msg == kind + "-close-page") {
+ browser.tabs.query({
+ windowId: args[0],
+ }, tabs => {
+ let tab = tabs.find(tab => tab.url.includes("/page.html"));
+ browser.tabs.remove(tab.id, () => {
+ browser.test.sendMessage("closed");
+ });
+ });
+ }
+ });
+ browser.test.sendMessage(kind + "-ready");
+}
+
+add_task(function* () {
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ yield focusWindow(win2);
+
+ yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots");
+ yield BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser);
+
+ yield BrowserTestUtils.loadURI(win2.gBrowser.selectedBrowser, "about:config");
+ yield BrowserTestUtils.browserLoaded(win2.gBrowser.selectedBrowser);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+
+ "browser_action": {
+ "default_popup": "popup.html",
+ },
+ },
+
+ files: {
+ "page.html": `
+ <!DOCTYPE html>
+ <html><body>
+ <script src="page.js"></script>
+ </body></html>
+ `,
+
+ "page.js": genericChecker,
+
+ "popup.html": `
+ <!DOCTYPE html>
+ <html><body>
+ <script src="popup.js"></script>
+ </body></html>
+ `,
+
+ "popup.js": genericChecker,
+ },
+
+ background: genericChecker,
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
+
+ let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let winId1 = WindowManager.getId(win1);
+ let winId2 = WindowManager.getId(win2);
+
+ function* checkWindow(kind, winId, name) {
+ extension.sendMessage(kind + "-check-current1");
+ is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`);
+ extension.sendMessage(kind + "-check-current2");
+ is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 2) [${kind}]`);
+ extension.sendMessage(kind + "-check-current3");
+ is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 3) [${kind}]`);
+ }
+
+ yield focusWindow(win1);
+ yield checkWindow("background", winId1, "win1");
+ yield focusWindow(win2);
+ yield checkWindow("background", winId2, "win2");
+
+ function* triggerPopup(win, callback) {
+ yield clickBrowserAction(extension, win);
+ yield awaitExtensionPanel(extension, win);
+
+ yield extension.awaitMessage("popup-ready");
+
+ yield callback();
+
+ closeBrowserAction(extension, win);
+ }
+
+ // Set focus to some other window.
+ yield focusWindow(window);
+
+ yield triggerPopup(win1, function* () {
+ yield checkWindow("popup", winId1, "win1");
+ });
+
+ yield triggerPopup(win2, function* () {
+ yield checkWindow("popup", winId2, "win2");
+ });
+
+ function* triggerPage(winId, name) {
+ extension.sendMessage("background-open-page", winId);
+ yield extension.awaitMessage("page-ready");
+ yield checkWindow("page", winId, name);
+ extension.sendMessage("background-close-page", winId);
+ yield extension.awaitMessage("closed");
+ }
+
+ yield triggerPage(winId1, "win1");
+ yield triggerPage(winId2, "win2");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_getViews.js b/browser/components/extensions/test/browser/browser_ext_getViews.js
new file mode 100644
index 000000000..684e19ac5
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -0,0 +1,198 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function genericChecker() {
+ let kind = "background";
+ let path = window.location.pathname;
+ if (path.indexOf("popup") != -1) {
+ kind = "popup";
+ } else if (path.indexOf("tab") != -1) {
+ kind = "tab";
+ }
+ window.kind = kind;
+
+ browser.test.onMessage.addListener((msg, ...args) => {
+ if (msg == kind + "-check-views") {
+ let windowId = args[0];
+ let counts = {
+ "background": 0,
+ "tab": 0,
+ "popup": 0,
+ "kind": 0,
+ "window": 0,
+ };
+ if (Number.isInteger(windowId)) {
+ counts.window = browser.extension.getViews({windowId: windowId}).length;
+ }
+ if (kind !== "background") {
+ counts.kind = browser.extension.getViews({type: kind}).length;
+ }
+ let views = browser.extension.getViews();
+ let background;
+ for (let i = 0; i < views.length; i++) {
+ let view = views[i];
+ browser.test.assertTrue(view.kind in counts, "view type is valid");
+ counts[view.kind]++;
+ if (view.kind == "background") {
+ browser.test.assertTrue(view === browser.extension.getBackgroundPage(),
+ "background page is correct");
+ background = view;
+ }
+ }
+ if (background) {
+ browser.runtime.getBackgroundPage().then(view => {
+ browser.test.assertEq(background, view, "runtime.getBackgroundPage() is correct");
+ browser.test.sendMessage("counts", counts);
+ });
+ } else {
+ browser.test.sendMessage("counts", counts);
+ }
+ } else if (msg == kind + "-open-tab") {
+ browser.tabs.create({windowId: args[0], url: browser.runtime.getURL("tab.html")});
+ } else if (msg == kind + "-close-tab") {
+ browser.tabs.query({
+ windowId: args[0],
+ }, tabs => {
+ let tab = tabs.find(tab => tab.url.indexOf("tab.html") != -1);
+ browser.tabs.remove(tab.id, () => {
+ browser.test.sendMessage("closed");
+ });
+ });
+ }
+ });
+ browser.test.sendMessage(kind + "-ready");
+}
+
+add_task(function* () {
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+
+ "browser_action": {
+ "default_popup": "popup.html",
+ },
+ },
+
+ files: {
+ "tab.html": `
+ <!DOCTYPE html>
+ <html><body>
+ <script src="tab.js"></script>
+ </body></html>
+ `,
+
+ "tab.js": genericChecker,
+
+ "popup.html": `
+ <!DOCTYPE html>
+ <html><body>
+ <script src="popup.js"></script>
+ </body></html>
+ `,
+
+ "popup.js": genericChecker,
+ },
+
+ background: genericChecker,
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
+
+ info("started");
+
+ let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let winId1 = WindowManager.getId(win1);
+ let winId2 = WindowManager.getId(win2);
+
+ function* openTab(winId) {
+ extension.sendMessage("background-open-tab", winId);
+ yield extension.awaitMessage("tab-ready");
+ }
+
+ function* checkViews(kind, tabCount, popupCount, kindCount, windowId = undefined, windowCount = 0) {
+ extension.sendMessage(kind + "-check-views", windowId);
+ let counts = yield extension.awaitMessage("counts");
+ is(counts.background, 1, "background count correct");
+ is(counts.tab, tabCount, "tab count correct");
+ is(counts.popup, popupCount, "popup count correct");
+ is(counts.kind, kindCount, "count for type correct");
+ is(counts.window, windowCount, "count for window correct");
+ }
+
+ yield checkViews("background", 0, 0, 0);
+
+ yield openTab(winId1);
+
+ yield checkViews("background", 1, 0, 0, winId1, 1);
+ yield checkViews("tab", 1, 0, 1);
+
+ yield openTab(winId2);
+
+ yield checkViews("background", 2, 0, 0, winId2, 1);
+
+ function* triggerPopup(win, callback) {
+ yield clickBrowserAction(extension, win);
+ yield awaitExtensionPanel(extension, win);
+
+ yield extension.awaitMessage("popup-ready");
+
+ yield callback();
+
+ closeBrowserAction(extension, win);
+ }
+
+ // The popup occasionally closes prematurely if we open it immediately here.
+ // I'm not sure what causes it to close (it's something internal, and seems to
+ // be focus-related, but it's not caused by JS calling hidePopup), but even a
+ // short timeout seems to consistently fix it.
+ yield new Promise(resolve => win1.setTimeout(resolve, 10));
+
+ yield triggerPopup(win1, function* () {
+ yield checkViews("background", 2, 1, 0, winId1, 2);
+ yield checkViews("popup", 2, 1, 1);
+ });
+
+ yield triggerPopup(win2, function* () {
+ yield checkViews("background", 2, 1, 0, winId2, 2);
+ yield checkViews("popup", 2, 1, 1);
+ });
+
+ info("checking counts after popups");
+
+ yield checkViews("background", 2, 0, 0, winId1, 1);
+
+ info("closing one tab");
+
+ extension.sendMessage("background-close-tab", winId1);
+ yield extension.awaitMessage("closed");
+
+ info("one tab closed, one remains");
+
+ yield checkViews("background", 1, 0, 0);
+
+ info("opening win1 popup");
+
+ yield triggerPopup(win1, function* () {
+ yield checkViews("background", 1, 1, 0);
+ yield checkViews("tab", 1, 1, 1);
+ yield checkViews("popup", 1, 1, 1);
+ });
+
+ info("opening win2 popup");
+
+ yield triggerPopup(win2, function* () {
+ yield checkViews("background", 1, 1, 0);
+ yield checkViews("tab", 1, 1, 1);
+ yield checkViews("popup", 1, 1, 1);
+ });
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_incognito_popup.js b/browser/components/extensions/test/browser/browser_ext_incognito_popup.js
new file mode 100644
index 000000000..174b2179d
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_incognito_popup.js
@@ -0,0 +1,108 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testIncognitoPopup() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ "browser_action": {
+ "default_popup": "popup.html",
+ },
+ "page_action": {
+ "default_popup": "popup.html",
+ },
+ },
+
+ background: async function() {
+ let resolveMessage;
+ browser.runtime.onMessage.addListener(msg => {
+ if (resolveMessage && msg.message == "popup-details") {
+ resolveMessage(msg);
+ }
+ });
+
+ let awaitPopup = windowId => {
+ return new Promise(resolve => {
+ resolveMessage = resolve;
+ }).then(msg => {
+ browser.test.assertEq(windowId, msg.windowId, "Got popup message from correct window");
+ return msg;
+ });
+ };
+
+ let testWindow = async window => {
+ let [tab] = await browser.tabs.query({active: true, windowId: window.id});
+
+ await browser.pageAction.show(tab.id);
+ browser.test.sendMessage("click-pageAction");
+
+ let msg = await awaitPopup(window.id);
+ browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in pageAction popup");
+
+ browser.test.sendMessage("click-browserAction");
+
+ msg = await awaitPopup(window.id);
+ browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in browserAction popup");
+ };
+
+ const URL = "http://example.com/incognito";
+ let windowReady = new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab) {
+ if (changed.status == "complete" && tab.url == URL) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+
+ try {
+ {
+ let window = await browser.windows.getCurrent();
+
+ await testWindow(window);
+ }
+
+ {
+ let window = await browser.windows.create({incognito: true, url: URL});
+ await windowReady;
+
+ await testWindow(window);
+
+ await browser.windows.remove(window.id);
+ }
+
+ browser.test.notifyPass("incognito");
+ } catch (error) {
+ browser.test.fail(`Error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("incognito");
+ }
+ },
+
+ files: {
+ "popup.html": '<html><head><meta charset="utf-8"><script src="popup.js"></script></head></html>',
+
+ "popup.js": async function() {
+ let win = await browser.windows.getCurrent();
+ browser.runtime.sendMessage({
+ message: "popup-details",
+ windowId: win.id,
+ incognito: browser.extension.inIncognitoContext,
+ });
+ window.close();
+ },
+ },
+ });
+
+ extension.onMessage("click-browserAction", () => {
+ clickBrowserAction(extension, Services.wm.getMostRecentWindow("navigator:browser"));
+ });
+
+ extension.onMessage("click-pageAction", () => {
+ clickPageAction(extension, Services.wm.getMostRecentWindow("navigator:browser"));
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("incognito");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_incognito_views.js b/browser/components/extensions/test/browser/browser_ext_incognito_views.js
new file mode 100644
index 000000000..4865b2d4f
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_incognito_views.js
@@ -0,0 +1,121 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testIncognitoViews() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ "browser_action": {
+ "default_popup": "popup.html",
+ },
+ },
+
+ background: async function() {
+ window.isBackgroundPage = true;
+
+ let resolveMessage;
+ browser.runtime.onMessage.addListener(msg => {
+ if (resolveMessage && msg.message == "popup-details") {
+ resolveMessage(msg);
+ }
+ });
+
+ let awaitPopup = windowId => {
+ return new Promise(resolve => {
+ resolveMessage = resolve;
+ }).then(msg => {
+ browser.test.assertEq(windowId, msg.windowId, "Got popup message from correct window");
+ return msg;
+ });
+ };
+
+ let testWindow = async window => {
+ browser.test.sendMessage("click-browserAction");
+
+ let msg = await awaitPopup(window.id);
+ browser.test.assertEq(window.incognito, msg.incognito, "Correct incognito status in browserAction popup");
+ };
+
+ const URL = "http://example.com/incognito";
+ let windowReady = new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab) {
+ if (changed.status == "complete" && tab.url == URL) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+
+ try {
+ {
+ let window = await browser.windows.getCurrent();
+
+ await testWindow(window);
+ }
+
+ {
+ let window = await browser.windows.create({incognito: true, url: URL});
+ await windowReady;
+
+ await testWindow(window);
+
+ await browser.windows.remove(window.id);
+ }
+
+ browser.test.notifyPass("incognito-views");
+ } catch (error) {
+ browser.test.fail(`Error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("incognito-views");
+ }
+ },
+
+ files: {
+ "popup.html": '<html><head><meta charset="utf-8"><script src="popup.js"></script></head></html>',
+
+ "popup.js": async function() {
+ let views = browser.extension.getViews();
+
+ if (browser.extension.inIncognitoContext) {
+ let bgPage = browser.extension.getBackgroundPage();
+ browser.test.assertEq(null, bgPage, "Should not be able to access background page in incognito context");
+
+ bgPage = await browser.runtime.getBackgroundPage();
+ browser.test.assertEq(null, bgPage, "Should not be able to access background page in incognito context");
+
+ browser.test.assertEq(1, views.length, "Should only see one view in incognito popup");
+ browser.test.assertEq(window, views[0], "This window should be the only view");
+ } else {
+ let bgPage = browser.extension.getBackgroundPage();
+ browser.test.assertEq(true, bgPage.isBackgroundPage,
+ "Should be able to access background page in non-incognito context");
+
+ bgPage = await browser.runtime.getBackgroundPage();
+ browser.test.assertEq(true, bgPage.isBackgroundPage,
+ "Should be able to access background page in non-incognito context");
+
+ browser.test.assertEq(2, views.length, "Should only two views in non-incognito popup");
+ browser.test.assertEq(bgPage, views[0], "The background page should be the first view");
+ browser.test.assertEq(window, views[1], "This window should be the second view");
+ }
+
+ let win = await browser.windows.getCurrent();
+ browser.runtime.sendMessage({
+ message: "popup-details",
+ windowId: win.id,
+ incognito: browser.extension.inIncognitoContext,
+ });
+
+ window.close();
+ },
+ },
+ });
+
+ extension.onMessage("click-browserAction", () => {
+ clickBrowserAction(extension, Services.wm.getMostRecentWindow("navigator:browser"));
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("incognito-views");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_lastError.js b/browser/components/extensions/test/browser/browser_ext_lastError.js
new file mode 100644
index 000000000..499e709aa
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_lastError.js
@@ -0,0 +1,55 @@
+"use strict";
+
+function* sendMessage(options) {
+ function background(options) {
+ browser.runtime.sendMessage(result => {
+ browser.test.assertEq(undefined, result, "Argument value");
+ if (options.checkLastError) {
+ let lastError = browser[options.checkLastError].lastError;
+ browser.test.assertEq("runtime.sendMessage's message argument is missing",
+ lastError && lastError.message,
+ "lastError value");
+ }
+ browser.test.sendMessage("done");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: `(${background})(${JSON.stringify(options)})`,
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitMessage("done");
+
+ yield extension.unload();
+}
+
+add_task(function* testLastError() {
+ // Not necessary in browser-chrome tests, but monitorConsole gripes
+ // if we don't call it.
+ SimpleTest.waitForExplicitFinish();
+
+ // Check that we have no unexpected console messages when lastError is
+ // checked.
+ for (let api of ["extension", "runtime"]) {
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [{message: /message argument is missing/, forbid: true}]);
+ });
+
+ yield sendMessage({checkLastError: api});
+
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+ }
+
+ // Check that we do have a console message when lastError is not checked.
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [{message: /Unchecked lastError value: Error: runtime.sendMessage's message argument is missing/}]);
+ });
+
+ yield sendMessage({});
+
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js b/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
new file mode 100644
index 000000000..01557a745
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_legacy_extension_context_contentscript.js
@@ -0,0 +1,173 @@
+"use strict";
+
+const {
+ LegacyExtensionContext,
+} = Cu.import("resource://gre/modules/LegacyExtensionsUtils.jsm", {});
+
+function promiseAddonStartup(extension) {
+ const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ return new Promise((resolve) => {
+ let listener = (evt, extensionInstance) => {
+ Management.off("startup", listener);
+ resolve(extensionInstance);
+ };
+ Management.on("startup", listener);
+ });
+}
+
+/**
+ * This test case ensures that the LegacyExtensionContext can receive a connection
+ * from a content script and that the received port contains the expected sender
+ * tab info.
+ */
+add_task(function* test_legacy_extension_context_contentscript_connection() {
+ function backgroundScript() {
+ // Extract the assigned uuid from the background page url and send it
+ // in a test message.
+ let uuid = window.location.hostname;
+
+ browser.test.onMessage.addListener(async msg => {
+ if (msg == "open-test-tab") {
+ let tab = await browser.tabs.create({url: "http://example.com/"});
+ browser.test.sendMessage("get-expected-sender-info",
+ {uuid, tab});
+ } else if (msg == "close-current-tab") {
+ try {
+ let [tab] = await browser.tabs.query({active: true});
+ await browser.tabs.remove(tab.id);
+ browser.test.sendMessage("current-tab-closed", true);
+ } catch (e) {
+ browser.test.sendMessage("current-tab-closed", false);
+ }
+ }
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ function contentScript() {
+ browser.runtime.sendMessage("webextension -> legacy_extension message", (reply) => {
+ browser.test.assertEq("legacy_extension -> webextension reply", reply,
+ "Got the expected reply from the LegacyExtensionContext");
+ browser.test.sendMessage("got-reply-message");
+ });
+
+ let port = browser.runtime.connect();
+
+ port.onMessage.addListener(msg => {
+ browser.test.assertEq("legacy_extension -> webextension port message", msg,
+ "Got the expected message from the LegacyExtensionContext");
+ port.postMessage("webextension -> legacy_extension port message");
+ });
+ }
+
+ let extensionData = {
+ background: `new ${backgroundScript}`,
+ manifest: {
+ content_scripts: [
+ {
+ matches: ["http://example.com/*"],
+ js: ["content-script.js"],
+ },
+ ],
+ },
+ files: {
+ "content-script.js": `new ${contentScript}`,
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+
+ let waitForExtensionReady = extension.awaitMessage("ready");
+
+ let waitForExtensionInstance = promiseAddonStartup(extension);
+
+ extension.startup();
+
+ let extensionInstance = yield waitForExtensionInstance;
+
+ // Connect to the target extension.id as an external context
+ // using the given custom sender info.
+ let legacyContext = new LegacyExtensionContext(extensionInstance);
+
+ let waitConnectPort = new Promise(resolve => {
+ let {browser} = legacyContext.api;
+ browser.runtime.onConnect.addListener(port => {
+ resolve(port);
+ });
+ });
+
+ let waitMessage = new Promise(resolve => {
+ let {browser} = legacyContext.api;
+ browser.runtime.onMessage.addListener((singleMsg, msgSender, sendReply) => {
+ sendReply("legacy_extension -> webextension reply");
+ resolve({singleMsg, msgSender});
+ });
+ });
+
+ is(legacyContext.envType, "legacy_extension",
+ "LegacyExtensionContext instance has the expected type");
+
+ ok(legacyContext.api, "Got the API object");
+
+ yield waitForExtensionReady;
+
+ extension.sendMessage("open-test-tab");
+
+ let {uuid, tab} = yield extension.awaitMessage("get-expected-sender-info");
+
+ let {singleMsg, msgSender} = yield waitMessage;
+ is(singleMsg, "webextension -> legacy_extension message",
+ "Got the expected message");
+ ok(msgSender, "Got a message sender object");
+
+ is(msgSender.id, uuid, "The sender has the expected id property");
+ is(msgSender.url, "http://example.com/", "The sender has the expected url property");
+ ok(msgSender.tab, "The sender has a tab property");
+ is(msgSender.tab.id, tab.id, "The port sender has the expected tab.id");
+
+ // Wait confirmation that the reply has been received.
+ yield extension.awaitMessage("got-reply-message");
+
+ let port = yield waitConnectPort;
+
+ ok(port, "Got the Port API object");
+ ok(port.sender, "The port has a sender property");
+
+ is(port.sender.id, uuid, "The port sender has an id property");
+ is(port.sender.url, "http://example.com/", "The port sender has the expected url property");
+ ok(port.sender.tab, "The port sender has a tab property");
+ is(port.sender.tab.id, tab.id, "The port sender has the expected tab.id");
+
+ let waitPortMessage = new Promise(resolve => {
+ port.onMessage.addListener((msg) => {
+ resolve(msg);
+ });
+ });
+
+ port.postMessage("legacy_extension -> webextension port message");
+
+ let msg = yield waitPortMessage;
+
+ is(msg, "webextension -> legacy_extension port message",
+ "LegacyExtensionContext received the expected message from the webextension");
+
+ let waitForDisconnect = new Promise(resolve => {
+ port.onDisconnect.addListener(resolve);
+ });
+
+ let waitForTestDone = extension.awaitMessage("current-tab-closed");
+
+ extension.sendMessage("close-current-tab");
+
+ yield waitForDisconnect;
+
+ info("Got the disconnect event on tab closed");
+
+ let success = yield waitForTestDone;
+
+ ok(success, "Test completed successfully");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_omnibox.js b/browser/components/extensions/test/browser/browser_ext_omnibox.js
new file mode 100644
index 000000000..98d3573c5
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_omnibox.js
@@ -0,0 +1,286 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* setup() {
+ const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ });
+}
+
+add_task(function* () {
+ let keyword = "test";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "omnibox": {
+ "keyword": keyword,
+ },
+ },
+
+ background: function() {
+ browser.omnibox.onInputStarted.addListener(() => {
+ browser.test.sendMessage("on-input-started-fired");
+ });
+
+ let synchronous = true;
+ let suggestions = null;
+ let suggestCallback = null;
+
+ browser.omnibox.onInputChanged.addListener((text, suggest) => {
+ if (synchronous && suggestions) {
+ suggest(suggestions);
+ } else {
+ suggestCallback = suggest;
+ }
+ browser.test.sendMessage("on-input-changed-fired", {text});
+ });
+
+ browser.omnibox.onInputCancelled.addListener(() => {
+ browser.test.sendMessage("on-input-cancelled-fired");
+ });
+
+ browser.omnibox.onInputEntered.addListener((text, disposition) => {
+ browser.test.sendMessage("on-input-entered-fired", {text, disposition});
+ });
+
+ browser.test.onMessage.addListener((msg, data) => {
+ switch (msg) {
+ case "set-suggestions":
+ suggestions = data.suggestions;
+ browser.test.sendMessage("suggestions-set");
+ break;
+ case "set-default-suggestion":
+ browser.omnibox.setDefaultSuggestion(data.suggestion);
+ browser.test.sendMessage("default-suggestion-set");
+ break;
+ case "set-synchronous":
+ synchronous = data.synchronous;
+ break;
+ case "test-multiple-suggest-calls":
+ suggestions.forEach(suggestion => suggestCallback([suggestion]));
+ browser.test.sendMessage("test-ready");
+ break;
+ case "test-suggestions-after-delay":
+ Promise.resolve().then(() => {
+ suggestCallback(suggestions);
+ browser.test.sendMessage("test-ready");
+ });
+ break;
+ }
+ });
+ },
+ });
+
+ function* expectEvent(event, expected = {}) {
+ let actual = yield extension.awaitMessage(event);
+ if (expected.text) {
+ is(actual.text, expected.text,
+ `Expected "${event}" to have fired with text: "${expected.text}".`);
+ }
+ if (expected.disposition) {
+ is(actual.disposition, expected.disposition,
+ `Expected "${event}" to have fired with disposition: "${expected.disposition}".`);
+ }
+ }
+
+ function* startInputSession() {
+ gURLBar.focus();
+ gURLBar.value = keyword;
+ EventUtils.synthesizeKey(" ", {});
+ yield expectEvent("on-input-started-fired");
+ EventUtils.synthesizeKey("t", {});
+ yield expectEvent("on-input-changed-fired", {text: "t"});
+ return "t";
+ }
+
+ function* testInputEvents() {
+ gURLBar.focus();
+
+ // Start an input session by typing in <keyword><space>.
+ for (let letter of keyword) {
+ EventUtils.synthesizeKey(letter, {});
+ }
+ EventUtils.synthesizeKey(" ", {});
+ yield expectEvent("on-input-started-fired");
+
+ // We should expect input changed events now that the keyword is active.
+ EventUtils.synthesizeKey("b", {});
+ yield expectEvent("on-input-changed-fired", {text: "b"});
+
+ EventUtils.synthesizeKey("c", {});
+ yield expectEvent("on-input-changed-fired", {text: "bc"});
+
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ yield expectEvent("on-input-changed-fired", {text: "b"});
+
+ // Even though the input is <keyword><space> We should not expect an
+ // input started event to fire since the keyword is active.
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ yield expectEvent("on-input-changed-fired", {text: ""});
+
+ // Make the keyword inactive by hitting backspace.
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ yield expectEvent("on-input-cancelled-fired");
+
+ // Activate the keyword by typing a space.
+ // Expect onInputStarted to fire.
+ EventUtils.synthesizeKey(" ", {});
+ yield expectEvent("on-input-started-fired");
+
+ // onInputChanged should fire even if a space is entered.
+ EventUtils.synthesizeKey(" ", {});
+ yield expectEvent("on-input-changed-fired", {text: " "});
+
+ // The active session should cancel if the input blurs.
+ gURLBar.blur();
+ yield expectEvent("on-input-cancelled-fired");
+ }
+
+ function* testHeuristicResult(expectedText, setDefaultSuggestion) {
+ if (setDefaultSuggestion) {
+ extension.sendMessage("set-default-suggestion", {
+ suggestion: {
+ description: expectedText,
+ },
+ });
+ yield extension.awaitMessage("default-suggestion-set");
+ }
+
+ let text = yield startInputSession();
+
+ let item = gURLBar.popup.richlistbox.children[0];
+
+ is(item.getAttribute("title"), expectedText,
+ `Expected heuristic result to have title: "${expectedText}".`);
+
+ is(item.getAttribute("displayurl"), `${keyword} ${text}`,
+ `Expected heuristic result to have displayurl: "${keyword} ${text}".`);
+
+ EventUtils.synthesizeMouseAtCenter(item, {});
+
+ yield expectEvent("on-input-entered-fired", {
+ text,
+ disposition: "currentTab",
+ });
+ }
+
+ function* testDisposition(suggestionIndex, expectedDisposition, expectedText) {
+ yield startInputSession();
+
+ // Select the suggestion.
+ for (let i = 0; i < suggestionIndex; i++) {
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ }
+
+ let item = gURLBar.popup.richlistbox.children[suggestionIndex];
+ if (expectedDisposition == "currentTab") {
+ EventUtils.synthesizeMouseAtCenter(item, {});
+ } else if (expectedDisposition == "newForegroundTab") {
+ EventUtils.synthesizeMouseAtCenter(item, {accelKey: true});
+ } else if (expectedDisposition == "newBackgroundTab") {
+ EventUtils.synthesizeMouseAtCenter(item, {shiftKey: true, accelKey: true});
+ }
+
+ yield expectEvent("on-input-entered-fired", {
+ text: expectedText,
+ disposition: expectedDisposition,
+ });
+ }
+
+ function* testSuggestions(info) {
+ extension.sendMessage("set-synchronous", {synchronous: false});
+
+ function expectSuggestion({content, description}, index) {
+ let item = gURLBar.popup.richlistbox.children[index + 1]; // Skip the heuristic result.
+
+ ok(!!item, "Expected item to exist");
+ is(item.getAttribute("title"), description,
+ `Expected suggestion to have title: "${description}".`);
+
+ is(item.getAttribute("displayurl"), `${keyword} ${content}`,
+ `Expected suggestion to have displayurl: "${keyword} ${content}".`);
+ }
+
+ let text = yield startInputSession();
+
+ extension.sendMessage(info.test);
+ yield extension.awaitMessage("test-ready");
+
+ info.suggestions.forEach(expectSuggestion);
+
+ EventUtils.synthesizeMouseAtCenter(gURLBar.popup.richlistbox.children[0], {});
+ yield expectEvent("on-input-entered-fired", {
+ text,
+ disposition: "currentTab",
+ });
+ }
+
+ yield setup();
+ yield extension.startup();
+
+ yield testInputEvents();
+
+ // Test the heuristic result with default suggestions.
+ yield testHeuristicResult("Generated extension", false /* setDefaultSuggestion */);
+ yield testHeuristicResult("hello world", true /* setDefaultSuggestion */);
+ yield testHeuristicResult("foo bar", true /* setDefaultSuggestion */);
+
+ let suggestions = [
+ {content: "a", description: "select a"},
+ {content: "b", description: "select b"},
+ {content: "c", description: "select c"},
+ ];
+
+ extension.sendMessage("set-suggestions", {suggestions});
+ yield extension.awaitMessage("suggestions-set");
+
+ // Test each suggestion and search disposition.
+ yield testDisposition(1, "currentTab", suggestions[0].content);
+ yield testDisposition(2, "newForegroundTab", suggestions[1].content);
+ yield testDisposition(3, "newBackgroundTab", suggestions[2].content);
+
+ extension.sendMessage("set-suggestions", {suggestions});
+ yield extension.awaitMessage("suggestions-set");
+
+ // Test adding suggestions asynchronously.
+ yield testSuggestions({
+ test: "test-multiple-suggest-calls",
+ skipHeuristic: true,
+ suggestions,
+ });
+ yield testSuggestions({
+ test: "test-suggestions-after-delay",
+ skipHeuristic: true,
+ suggestions,
+ });
+
+ // Start monitoring the console.
+ SimpleTest.waitForExplicitFinish();
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [{
+ message: new RegExp(`The keyword provided is already registered: "${keyword}"`),
+ }]);
+ });
+
+ // Try registering another extension with the same keyword
+ let extension2 = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "omnibox": {
+ "keyword": keyword,
+ },
+ },
+ });
+
+ yield extension2.startup();
+
+ // Stop monitoring the console and confirm the correct errors are logged.
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+
+ yield extension2.unload();
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_optionsPage_privileges.js b/browser/components/extensions/test/browser/browser_ext_optionsPage_privileges.js
new file mode 100644
index 000000000..3e7342dd1
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_optionsPage_privileges.js
@@ -0,0 +1,66 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_tab_options_privileges() {
+ function backgroundScript() {
+ browser.runtime.onMessage.addListener(({msgName, tabId}) => {
+ if (msgName == "removeTabId") {
+ browser.tabs.remove(tabId).then(() => {
+ browser.test.notifyPass("options-ui-privileges");
+ }).catch(error => {
+ browser.test.log(`Error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("options-ui-privileges");
+ });
+ }
+ });
+ browser.runtime.openOptionsPage();
+ }
+
+ async function optionsScript() {
+ try {
+ let [tab] = await browser.tabs.query({url: "http://example.com/"});
+ browser.test.assertEq("http://example.com/", tab.url, "Got the expect tab");
+
+ tab = await browser.tabs.getCurrent();
+ browser.runtime.sendMessage({msgName: "removeTabId", tabId: tab.id});
+ } catch (error) {
+ browser.test.log(`Error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("options-ui-privileges");
+ }
+ }
+
+ const ID = "options_privileges@tests.mozilla.org";
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+
+ manifest: {
+ applications: {gecko: {id: ID}},
+ "permissions": ["tabs"],
+ "options_ui": {
+ "page": "options.html",
+ },
+ },
+ files: {
+ "options.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="options.js" type="text/javascript"></script>
+ </head>
+ </html>`,
+ "options.js": optionsScript,
+ },
+ background: backgroundScript,
+ });
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("options-ui-privileges");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
new file mode 100644
index 000000000..2c2a4cd2f
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -0,0 +1,178 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* global runTests */
+
+Services.scriptloader.loadSubScript(new URL("head_pageAction.js", gTestPath).href,
+ this);
+
+add_task(function* testTabSwitchContext() {
+ yield runTests({
+ manifest: {
+ "name": "Foo Extension",
+
+ "page_action": {
+ "default_icon": "default.png",
+ "default_popup": "__MSG_popup__",
+ "default_title": "Default __MSG_title__ \u263a",
+ },
+
+ "default_locale": "en",
+
+ "permissions": ["tabs"],
+ },
+
+ "files": {
+ "_locales/en/messages.json": {
+ "popup": {
+ "message": "default.html",
+ "description": "Popup",
+ },
+
+ "title": {
+ "message": "Title",
+ "description": "Title",
+ },
+ },
+
+ "_locales/es_ES/messages.json": {
+ "popup": {
+ "message": "default.html",
+ "description": "Popup",
+ },
+
+ "title": {
+ "message": "T\u00edtulo",
+ "description": "Title",
+ },
+ },
+
+ "default.png": imageBuffer,
+ "1.png": imageBuffer,
+ "2.png": imageBuffer,
+ },
+
+ getTests(tabs) {
+ let details = [
+ {"icon": browser.runtime.getURL("default.png"),
+ "popup": browser.runtime.getURL("default.html"),
+ "title": "Default T\u00edtulo \u263a"},
+ {"icon": browser.runtime.getURL("1.png"),
+ "popup": browser.runtime.getURL("default.html"),
+ "title": "Default T\u00edtulo \u263a"},
+ {"icon": browser.runtime.getURL("2.png"),
+ "popup": browser.runtime.getURL("2.html"),
+ "title": "Title 2"},
+ {"icon": browser.runtime.getURL("2.png"),
+ "popup": browser.runtime.getURL("2.html"),
+ "title": "Default T\u00edtulo \u263a"},
+ ];
+
+ let promiseTabLoad = details => {
+ return new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
+ if (tabId == details.id && changed.url == details.url) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+ };
+
+ return [
+ expect => {
+ browser.test.log("Initial state. No icon visible.");
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Show the icon on the first tab, expect default properties.");
+ await browser.pageAction.show(tabs[0]);
+ expect(details[0]);
+ },
+ expect => {
+ browser.test.log("Change the icon. Expect default properties excluding the icon.");
+ browser.pageAction.setIcon({tabId: tabs[0], path: "1.png"});
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Create a new tab. No icon visible.");
+ let tab = await browser.tabs.create({active: true, url: "about:blank?0"});
+ tabs.push(tab.id);
+ expect(null);
+ },
+ expect => {
+ browser.test.log("Await tab load. No icon visible.");
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Change properties. Expect new properties.");
+ let tabId = tabs[1];
+ await browser.pageAction.show(tabId);
+
+ browser.pageAction.setIcon({tabId, path: "2.png"});
+ browser.pageAction.setPopup({tabId, popup: "2.html"});
+ browser.pageAction.setTitle({tabId, title: "Title 2"});
+
+ expect(details[2]);
+ },
+ async expect => {
+ browser.test.log("Change the hash. Expect same properties.");
+
+ let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
+ browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
+ await promise;
+
+ expect(details[2]);
+ },
+ expect => {
+ browser.test.log("Clear the title. Expect default title.");
+ browser.pageAction.setTitle({tabId: tabs[1], title: ""});
+
+ expect(details[3]);
+ },
+ async expect => {
+ browser.test.log("Navigate to a new page. Expect icon hidden.");
+
+ // TODO: This listener should not be necessary, but the |tabs.update|
+ // callback currently fires too early in e10s windows.
+ let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
+
+ browser.tabs.update(tabs[1], {url: "about:blank?1"});
+
+ await promise;
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Show the icon. Expect default properties again.");
+
+ await browser.pageAction.show(tabs[1]);
+ expect(details[0]);
+ },
+ async expect => {
+ browser.test.log("Switch back to the first tab. Expect previously set properties.");
+ await browser.tabs.update(tabs[0], {active: true});
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Hide the icon on tab 2. Switch back, expect hidden.");
+ await browser.pageAction.hide(tabs[1]);
+
+ await browser.tabs.update(tabs[1], {active: true});
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Switch back to tab 1. Expect previous results again.");
+ await browser.tabs.remove(tabs[1]);
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Hide the icon. Expect hidden.");
+
+ await browser.pageAction.hide(tabs[0]);
+ expect(null);
+ },
+ ];
+ },
+ });
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
new file mode 100644
index 000000000..83defdd68
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
@@ -0,0 +1,238 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testPageActionPopup() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+ let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head></html>`;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "background": {
+ "page": "data/background.html",
+ },
+ "page_action": {
+ "default_popup": "popup-a.html",
+ },
+ },
+
+ files: {
+ "popup-a.html": scriptPage("popup-a.js"),
+ "popup-a.js": function() {
+ window.onload = () => {
+ let background = window.getComputedStyle(document.body).backgroundColor;
+ browser.test.assertEq("transparent", background);
+ browser.runtime.sendMessage("from-popup-a");
+ };
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg == "close-popup") {
+ window.close();
+ }
+ });
+ },
+
+ "data/popup-b.html": scriptPage("popup-b.js"),
+ "data/popup-b.js": function() {
+ browser.runtime.sendMessage("from-popup-b");
+ },
+
+ "data/background.html": scriptPage("background.js"),
+
+ "data/background.js": async function() {
+ let tabId;
+
+ let sendClick;
+ let tests = [
+ () => {
+ sendClick({expectEvent: false, expectPopup: "a"});
+ },
+ () => {
+ sendClick({expectEvent: false, expectPopup: "a"});
+ },
+ () => {
+ browser.pageAction.setPopup({tabId, popup: "popup-b.html"});
+ sendClick({expectEvent: false, expectPopup: "b"});
+ },
+ () => {
+ sendClick({expectEvent: false, expectPopup: "b"});
+ },
+ () => {
+ browser.pageAction.setPopup({tabId, popup: ""});
+ sendClick({expectEvent: true, expectPopup: null});
+ },
+ () => {
+ sendClick({expectEvent: true, expectPopup: null});
+ },
+ () => {
+ browser.pageAction.setPopup({tabId, popup: "/popup-a.html"});
+ sendClick({expectEvent: false, expectPopup: "a", runNextTest: true});
+ },
+ () => {
+ browser.test.sendMessage("next-test", {expectClosed: true});
+ },
+ () => {
+ sendClick({expectEvent: false, expectPopup: "a", runNextTest: true});
+ },
+ () => {
+ browser.test.sendMessage("next-test", {closeOnTabSwitch: true});
+ },
+ ];
+
+ let expect = {};
+ sendClick = ({expectEvent, expectPopup, runNextTest}) => {
+ expect = {event: expectEvent, popup: expectPopup, runNextTest};
+ browser.test.sendMessage("send-click");
+ };
+
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg == "close-popup") {
+ return;
+ } else if (expect.popup) {
+ browser.test.assertEq(msg, `from-popup-${expect.popup}`,
+ "expected popup opened");
+ } else {
+ browser.test.fail(`unexpected popup: ${msg}`);
+ }
+
+ expect.popup = null;
+ if (expect.runNextTest) {
+ expect.runNextTest = false;
+ tests.shift()();
+ } else {
+ browser.test.sendMessage("next-test");
+ }
+ });
+
+ browser.pageAction.onClicked.addListener(() => {
+ if (expect.event) {
+ browser.test.succeed("expected click event received");
+ } else {
+ browser.test.fail("unexpected click event");
+ }
+
+ expect.event = false;
+ browser.test.sendMessage("next-test");
+ });
+
+ browser.test.onMessage.addListener(msg => {
+ if (msg == "close-popup") {
+ browser.runtime.sendMessage("close-popup");
+ return;
+ }
+
+ if (msg != "next-test") {
+ browser.test.fail("Expecting 'next-test' message");
+ }
+
+ if (tests.length) {
+ let test = tests.shift();
+ test();
+ } else {
+ browser.test.notifyPass("pageaction-tests-done");
+ }
+ });
+
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+ tabId = tab.id;
+
+ await browser.pageAction.show(tabId);
+ browser.test.sendMessage("next-test");
+ },
+ },
+ });
+
+ extension.onMessage("send-click", () => {
+ clickPageAction(extension);
+ });
+
+ let pageActionId, panelId;
+ extension.onMessage("next-test", Task.async(function* (expecting = {}) {
+ pageActionId = `${makeWidgetId(extension.id)}-page-action`;
+ panelId = `${makeWidgetId(extension.id)}-panel`;
+ let panel = document.getElementById(panelId);
+ if (expecting.expectClosed) {
+ ok(panel, "Expect panel to exist");
+ yield promisePopupShown(panel);
+
+ extension.sendMessage("close-popup");
+
+ yield promisePopupHidden(panel);
+ ok(true, `Panel is closed`);
+ } else if (expecting.closeOnTabSwitch) {
+ ok(panel, "Expect panel to exist");
+ yield promisePopupShown(panel);
+
+ let oldTab = gBrowser.selectedTab;
+ ok(oldTab != gBrowser.tabs[0], "Should have an inactive tab to switch to");
+
+ let hiddenPromise = promisePopupHidden(panel);
+
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ yield hiddenPromise;
+ info("Panel closed");
+
+ gBrowser.selectedTab = oldTab;
+ } else if (panel) {
+ yield promisePopupShown(panel);
+ panel.hidePopup();
+ }
+
+ if (panel) {
+ panel = document.getElementById(panelId);
+ is(panel, null, "panel successfully removed from document after hiding");
+ }
+
+ extension.sendMessage("next-test");
+ }));
+
+
+ yield extension.startup();
+ yield extension.awaitFinish("pageaction-tests-done");
+
+ yield extension.unload();
+
+ let node = document.getElementById(pageActionId);
+ is(node, null, "pageAction image removed from document");
+
+ let panel = document.getElementById(panelId);
+ is(panel, null, "pageAction panel removed from document");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+
+add_task(function* testPageActionSecurity() {
+ const URL = "chrome://browser/content/browser.xul";
+
+ let apis = ["browser_action", "page_action"];
+
+ for (let api of apis) {
+ info(`TEST ${api} icon url: ${URL}`);
+
+ let messages = [/Access to restricted URI denied/];
+
+ let waitForConsole = new Promise(resolve => {
+ // Not necessary in browser-chrome tests, but monitorConsole gripes
+ // if we don't call it.
+ SimpleTest.waitForExplicitFinish();
+
+ SimpleTest.monitorConsole(resolve, messages);
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ [api]: {"default_popup": URL},
+ },
+ });
+
+ yield Assert.rejects(extension.startup(),
+ null,
+ "Manifest rejected");
+
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+ }
+});
+
+add_task(forceGC);
diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
new file mode 100644
index 000000000..98c4c3488
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
@@ -0,0 +1,169 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let delay = ms => new Promise(resolve => {
+ setTimeout(resolve, ms);
+});
+
+add_task(function* testPageActionPopupResize() {
+ let browser;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "page_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+ },
+ background: function() {
+ /* global browser */
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ const tabId = tabs[0].id;
+
+ browser.pageAction.show(tabId).then(() => {
+ browser.test.sendMessage("action-shown");
+ });
+ });
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body><div></div></body></html>`,
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("action-shown");
+
+ clickPageAction(extension, window);
+
+ browser = yield awaitExtensionPanel(extension);
+
+ function* checkSize(expected) {
+ let dims = yield promiseContentDimensions(browser);
+ let {body, root} = dims;
+
+ is(dims.window.innerHeight, expected, `Panel window should be ${expected}px tall`);
+ is(body.clientHeight, body.scrollHeight,
+ "Panel body should be tall enough to fit its contents");
+ is(root.clientHeight, root.scrollHeight,
+ "Panel root should be tall enough to fit its contents");
+
+ // Tolerate if it is 1px too wide, as that may happen with the current resizing method.
+ ok(Math.abs(dims.window.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
+ is(body.clientWidth, body.scrollWidth,
+ "Panel body should be wide enough to fit its contents");
+ }
+
+ /* eslint-disable mozilla/no-cpows-in-tests */
+ function setSize(size) {
+ let elem = content.document.body.firstChild;
+ elem.style.height = `${size}px`;
+ elem.style.width = `${size}px`;
+ }
+ /* eslint-enable mozilla/no-cpows-in-tests */
+
+ let sizes = [
+ 200,
+ 400,
+ 300,
+ ];
+
+ for (let size of sizes) {
+ yield alterContent(browser, setSize, size);
+ yield checkSize(size);
+ }
+
+ yield alterContent(browser, setSize, 1400);
+
+ let dims = yield promiseContentDimensions(browser);
+ let {body, root} = dims;
+
+ if (AppConstants.platform == "win") {
+ while (dims.window.innerWidth < 800) {
+ yield delay(50);
+ dims = yield promiseContentDimensions(browser);
+ }
+ }
+
+ is(dims.window.innerWidth, 800, "Panel window width");
+ ok(body.clientWidth <= 800, `Panel body width ${body.clientWidth} is less than 800`);
+ is(body.scrollWidth, 1400, "Panel body scroll width");
+
+ is(dims.window.innerHeight, 600, "Panel window height");
+ ok(root.clientHeight <= 600, `Panel root height (${root.clientHeight}px) is less than 600px`);
+ is(root.scrollHeight, 1400, "Panel root scroll height");
+
+ yield extension.unload();
+});
+
+add_task(function* testPageActionPopupReflow() {
+ let browser;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "page_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+ },
+ background: function() {
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ const tabId = tabs[0].id;
+
+ browser.pageAction.show(tabId).then(() => {
+ browser.test.sendMessage("action-shown");
+ });
+ });
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8"></head>
+ <body>
+ The quick mauve fox jumps over the opalescent toad, with its glowing
+ eyes, and its vantablack mouth, and its bottomless chasm where you
+ would hope to find a heart, that looks straight into the deepest
+ pits of hell. The fox shivers, and cowers, and tries to run, but
+ the toad is utterly without pity. It turns, ever so slightly...
+ </body>
+ </html>`,
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("action-shown");
+
+ clickPageAction(extension, window);
+
+ browser = yield awaitExtensionPanel(extension);
+
+ /* eslint-disable mozilla/no-cpows-in-tests */
+ function setSize(size) {
+ content.document.body.style.fontSize = `${size}px`;
+ }
+ /* eslint-enable mozilla/no-cpows-in-tests */
+
+ let dims = yield alterContent(browser, setSize, 18);
+
+ if (AppConstants.platform == "win") {
+ while (dims.window.innerWidth < 800) {
+ yield delay(50);
+ dims = yield promiseContentDimensions(browser);
+ }
+ }
+
+ is(dims.window.innerWidth, 800, "Panel window should be 800px wide");
+ is(dims.body.clientWidth, 800, "Panel body should be 800px wide");
+ is(dims.body.clientWidth, dims.body.scrollWidth,
+ "Panel body should be wide enough to fit its contents");
+
+ ok(dims.window.innerHeight > 36,
+ `Panel window height (${dims.window.innerHeight}px) should be taller than two lines of text.`);
+
+ is(dims.body.clientHeight, dims.body.scrollHeight,
+ "Panel body should be tall enough to fit its contents");
+ is(dims.root.clientHeight, dims.root.scrollHeight,
+ "Panel root should be tall enough to fit its contents");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_simple.js b/browser/components/extensions/test/browser/browser_ext_pageAction_simple.js
new file mode 100644
index 000000000..d1d173801
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_simple.js
@@ -0,0 +1,60 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "page_action": {
+ "default_popup": "popup.html",
+ "unrecognized_property": "with-a-random-value",
+ },
+ },
+
+ files: {
+ "popup.html": `
+ <!DOCTYPE html>
+ <html><body>
+ <script src="popup.js"></script>
+ </body></html>
+ `,
+
+ "popup.js": function() {
+ browser.runtime.sendMessage("from-popup");
+ },
+ },
+
+ background: function() {
+ browser.runtime.onMessage.addListener(msg => {
+ browser.test.assertEq(msg, "from-popup", "correct message received");
+ browser.test.sendMessage("popup");
+ });
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ let tabId = tabs[0].id;
+
+ browser.pageAction.show(tabId).then(() => {
+ browser.test.sendMessage("page-action-shown");
+ });
+ });
+ },
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, [{
+ message: /Reading manifest: Error processing page_action.unrecognized_property: An unexpected property was found/,
+ }]);
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("page-action-shown");
+
+ clickPageAction(extension);
+
+ yield extension.awaitMessage("popup");
+
+ yield extension.unload();
+
+ SimpleTest.endMonitorConsole();
+ yield waitForConsole;
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_title.js b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
new file mode 100644
index 000000000..793cd4499
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
@@ -0,0 +1,226 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* global runTests */
+
+Services.scriptloader.loadSubScript(new URL("head_pageAction.js", gTestPath).href,
+ this);
+
+add_task(function* testTabSwitchContext() {
+ yield runTests({
+ manifest: {
+ "name": "Foo Extension",
+
+ "page_action": {
+ "default_icon": "default.png",
+ "default_popup": "__MSG_popup__",
+ "default_title": "Default __MSG_title__ \u263a",
+ },
+
+ "default_locale": "en",
+
+ "permissions": ["tabs"],
+ },
+
+ "files": {
+ "_locales/en/messages.json": {
+ "popup": {
+ "message": "default.html",
+ "description": "Popup",
+ },
+
+ "title": {
+ "message": "Title",
+ "description": "Title",
+ },
+ },
+
+ "_locales/es_ES/messages.json": {
+ "popup": {
+ "message": "default.html",
+ "description": "Popup",
+ },
+
+ "title": {
+ "message": "T\u00edtulo",
+ "description": "Title",
+ },
+ },
+
+ "default.png": imageBuffer,
+ "1.png": imageBuffer,
+ "2.png": imageBuffer,
+ },
+
+ getTests(tabs) {
+ let details = [
+ {"icon": browser.runtime.getURL("default.png"),
+ "popup": browser.runtime.getURL("default.html"),
+ "title": "Default T\u00edtulo \u263a"},
+ {"icon": browser.runtime.getURL("1.png"),
+ "popup": browser.runtime.getURL("default.html"),
+ "title": "Default T\u00edtulo \u263a"},
+ {"icon": browser.runtime.getURL("2.png"),
+ "popup": browser.runtime.getURL("2.html"),
+ "title": "Title 2"},
+ {"icon": browser.runtime.getURL("2.png"),
+ "popup": browser.runtime.getURL("2.html"),
+ "title": "Default T\u00edtulo \u263a"},
+ ];
+
+ let promiseTabLoad = details => {
+ return new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
+ if (tabId == details.id && changed.url == details.url) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+ };
+ return [
+ expect => {
+ browser.test.log("Initial state. No icon visible.");
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Show the icon on the first tab, expect default properties.");
+ await browser.pageAction.show(tabs[0]);
+ expect(details[0]);
+ },
+ expect => {
+ browser.test.log("Change the icon. Expect default properties excluding the icon.");
+ browser.pageAction.setIcon({tabId: tabs[0], path: "1.png"});
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Create a new tab. No icon visible.");
+ let tab = await browser.tabs.create({active: true, url: "about:blank?0"});
+ tabs.push(tab.id);
+ expect(null);
+ },
+ expect => {
+ browser.test.log("Await tab load. No icon visible.");
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Change properties. Expect new properties.");
+ let tabId = tabs[1];
+
+ await browser.pageAction.show(tabId);
+ browser.pageAction.setIcon({tabId, path: "2.png"});
+ browser.pageAction.setPopup({tabId, popup: "2.html"});
+ browser.pageAction.setTitle({tabId, title: "Title 2"});
+
+ expect(details[2]);
+ },
+ async expect => {
+ browser.test.log("Change the hash. Expect same properties.");
+
+ let promise = promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"});
+
+ browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
+
+ await promise;
+ expect(details[2]);
+ },
+ expect => {
+ browser.test.log("Clear the title. Expect default title.");
+ browser.pageAction.setTitle({tabId: tabs[1], title: ""});
+
+ expect(details[3]);
+ },
+ async expect => {
+ browser.test.log("Navigate to a new page. Expect icon hidden.");
+
+ // TODO: This listener should not be necessary, but the |tabs.update|
+ // callback currently fires too early in e10s windows.
+ let promise = promiseTabLoad({id: tabs[1], url: "about:blank?1"});
+
+ browser.tabs.update(tabs[1], {url: "about:blank?1"});
+
+ await promise;
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Show the icon. Expect default properties again.");
+ await browser.pageAction.show(tabs[1]);
+ expect(details[0]);
+ },
+ async expect => {
+ browser.test.log("Switch back to the first tab. Expect previously set properties.");
+ await browser.tabs.update(tabs[0], {active: true});
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Hide the icon on tab 2. Switch back, expect hidden.");
+ await browser.pageAction.hide(tabs[1]);
+ await browser.tabs.update(tabs[1], {active: true});
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Switch back to tab 1. Expect previous results again.");
+ await browser.tabs.remove(tabs[1]);
+ expect(details[1]);
+ },
+ async expect => {
+ browser.test.log("Hide the icon. Expect hidden.");
+ await browser.pageAction.hide(tabs[0]);
+ expect(null);
+ },
+ ];
+ },
+ });
+});
+
+add_task(function* testDefaultTitle() {
+ yield runTests({
+ manifest: {
+ "name": "Foo Extension",
+
+ "page_action": {
+ "default_icon": "icon.png",
+ },
+
+ "permissions": ["tabs"],
+ },
+
+ files: {
+ "icon.png": imageBuffer,
+ },
+
+ getTests(tabs) {
+ let details = [
+ {"title": "Foo Extension",
+ "popup": "",
+ "icon": browser.runtime.getURL("icon.png")},
+ {"title": "Foo Title",
+ "popup": "",
+ "icon": browser.runtime.getURL("icon.png")},
+ ];
+
+ return [
+ expect => {
+ browser.test.log("Initial state. No icon visible.");
+ expect(null);
+ },
+ async expect => {
+ browser.test.log("Show the icon on the first tab, expect extension title as default title.");
+ await browser.pageAction.show(tabs[0]);
+ expect(details[0]);
+ },
+ expect => {
+ browser.test.log("Change the title. Expect new title.");
+ browser.pageAction.setTitle({tabId: tabs[0], title: "Foo Title"});
+ expect(details[1]);
+ },
+ expect => {
+ browser.test.log("Clear the title. Expect extension title.");
+ browser.pageAction.setTitle({tabId: tabs[0], title: ""});
+ expect(details[0]);
+ },
+ ];
+ },
+ });
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
new file mode 100644
index 000000000..6f8a541a9
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
@@ -0,0 +1,101 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testPageActionPopup() {
+ const BASE = "http://example.com/browser/browser/components/extensions/test/browser";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": `${BASE}/file_popup_api_injection_a.html`,
+ },
+ "page_action": {
+ "default_popup": `${BASE}/file_popup_api_injection_b.html`,
+ },
+ },
+
+ files: {
+ "popup-a.html": `<html><head><meta charset="utf-8">
+ <script type="application/javascript" src="popup-a.js"></script></head></html>`,
+ "popup-a.js": 'browser.test.sendMessage("from-popup-a");',
+
+ "popup-b.html": `<html><head><meta charset="utf-8">
+ <script type="application/javascript" src="popup-b.js"></script></head></html>`,
+ "popup-b.js": 'browser.test.sendMessage("from-popup-b");',
+ },
+
+ background: function() {
+ let tabId;
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ tabId = tabs[0].id;
+ browser.pageAction.show(tabId).then(() => {
+ browser.test.sendMessage("ready");
+ });
+ });
+
+ browser.test.onMessage.addListener(() => {
+ browser.browserAction.setPopup({popup: "/popup-a.html"});
+ browser.pageAction.setPopup({tabId, popup: "popup-b.html"});
+
+ browser.test.sendMessage("ok");
+ });
+ },
+ });
+
+ let promiseConsoleMessage = pattern => new Promise(resolve => {
+ Services.console.registerListener(function listener(msg) {
+ if (pattern.test(msg.message)) {
+ resolve(msg.message);
+ Services.console.unregisterListener(listener);
+ }
+ });
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+
+ // Check that unprivileged documents don't get the API.
+ // BrowserAction:
+ let awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: BrowserAction/);
+ SimpleTest.expectUncaughtException();
+ yield clickBrowserAction(extension);
+ yield promisePopupShown(getBrowserActionPopup(extension));
+
+ let message = yield awaitMessage;
+ ok(message.includes("WebExt Privilege Escalation: BrowserAction: typeof(browser) = undefined"),
+ `No BrowserAction API injection`);
+
+ yield closeBrowserAction(extension);
+
+ // PageAction
+ awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: PageAction/);
+ SimpleTest.expectUncaughtException();
+ yield clickPageAction(extension);
+
+ message = yield awaitMessage;
+ ok(message.includes("WebExt Privilege Escalation: PageAction: typeof(browser) = undefined"),
+ `No PageAction API injection: ${message}`);
+
+ yield closePageAction(extension);
+
+ SimpleTest.expectUncaughtException(false);
+
+
+ // Check that privileged documents *do* get the API.
+ extension.sendMessage("next");
+ yield extension.awaitMessage("ok");
+
+
+ yield clickBrowserAction(extension);
+ yield extension.awaitMessage("from-popup-a");
+ yield promisePopupShown(getBrowserActionPopup(extension));
+ yield closeBrowserAction(extension);
+
+ yield clickPageAction(extension);
+ yield extension.awaitMessage("from-popup-b");
+ yield closePageAction(extension);
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_popup_background.js b/browser/components/extensions/test/browser/browser_ext_popup_background.js
new file mode 100644
index 000000000..8b310c674
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_background.js
@@ -0,0 +1,133 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testPopupBackground() {
+ let extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ browser.pageAction.show(tabs[0].id);
+ });
+ },
+
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+
+ "page_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body style="width: 100px; height: 100px; background-color: green;">
+ </body>
+ </html>`,
+ },
+ });
+
+ yield extension.startup();
+
+ function* testPanel(browser, standAlone) {
+ let panel = getPanelForNode(browser);
+ let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
+ let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
+
+ let borderColor = getComputedStyle(arrowContent).borderTopColor;
+
+ let checkArrow = (background = null) => {
+ let image = getComputedStyle(arrow).listStyleImage;
+
+ if (background == null || !standAlone) {
+ ok(image.startsWith('url("chrome://'), `We should have the built-in background image (got: ${image})`);
+ return;
+ }
+
+ if (AppConstants.platform == "mac") {
+ // Panels have a drop shadow rather than a border on OS-X, so we extend
+ // the background color through the border area instead.
+ borderColor = background;
+ }
+
+ image = decodeURIComponent(image);
+ let borderIndex = image.indexOf(`fill="${borderColor}"`);
+ let backgroundIndex = image.lastIndexOf(`fill="${background}"`);
+
+ ok(borderIndex >= 0, `Have border fill (index=${borderIndex})`);
+ ok(backgroundIndex >= 0, `Have background fill (index=${backgroundIndex})`);
+ is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
+ isnot(borderIndex, backgroundIndex, "Border and background fills are separate elements");
+ };
+
+ function getBackground(browser) {
+ return ContentTask.spawn(browser, null, function* () {
+ return content.getComputedStyle(content.document.body)
+ .backgroundColor;
+ });
+ }
+
+ /* eslint-disable mozilla/no-cpows-in-tests */
+ let setBackground = color => {
+ content.document.body.style.backgroundColor = color;
+ };
+ /* eslint-enable mozilla/no-cpows-in-tests */
+
+ yield new Promise(resolve => setTimeout(resolve, 100));
+
+ info("Test that initial background color is applied");
+
+ checkArrow(yield getBackground(browser));
+
+ info("Test that dynamically-changed background color is applied");
+
+ yield alterContent(browser, setBackground, "black");
+
+ checkArrow(yield getBackground(browser));
+
+ info("Test that non-opaque background color results in default styling");
+
+ yield alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
+
+ checkArrow(null);
+ }
+
+ {
+ info("Test stand-alone browserAction popup");
+
+ clickBrowserAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ yield testPanel(browser, true);
+ yield closeBrowserAction(extension);
+ }
+
+ {
+ info("Test menu panel browserAction popup");
+
+ let widget = getBrowserActionWidget(extension);
+ CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
+
+ clickBrowserAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ yield testPanel(browser, false);
+ yield closeBrowserAction(extension);
+ }
+
+ {
+ info("Test pageAction popup");
+
+ clickPageAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ yield testPanel(browser, true);
+ yield closePageAction(extension);
+ }
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_popup_corners.js b/browser/components/extensions/test/browser/browser_ext_popup_corners.js
new file mode 100644
index 000000000..52985ee46
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_corners.js
@@ -0,0 +1,98 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testPopupBorderRadius() {
+ let extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ browser.pageAction.show(tabs[0].id);
+ });
+ },
+
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+
+ "page_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html>
+ <html>
+ <head><meta charset="utf-8"></head>
+ <body style="width: 100px; height: 100px;"></body>
+ </html>`,
+ },
+ });
+
+ yield extension.startup();
+
+ function* testPanel(browser, standAlone = true) {
+ let panel = getPanelForNode(browser);
+ let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
+
+ let panelStyle = getComputedStyle(arrowContent);
+
+ let viewNode = browser.parentNode === panel ? browser : browser.parentNode;
+ let viewStyle = getComputedStyle(viewNode);
+
+ let props = ["borderTopLeftRadius", "borderTopRightRadius",
+ "borderBottomRightRadius", "borderBottomLeftRadius"];
+
+ /* eslint-disable mozilla/no-cpows-in-tests */
+ let bodyStyle = yield ContentTask.spawn(browser, props, function* (props) {
+ let bodyStyle = content.getComputedStyle(content.document.body);
+
+ return new Map(props.map(prop => [prop, bodyStyle[prop]]));
+ });
+ /* eslint-enable mozilla/no-cpows-in-tests */
+
+ for (let prop of props) {
+ if (standAlone) {
+ is(viewStyle[prop], panelStyle[prop], `Panel and view ${prop} should be the same`);
+ is(bodyStyle.get(prop), panelStyle[prop], `Panel and body ${prop} should be the same`);
+ } else {
+ is(viewStyle[prop], "0px", `View node ${prop} should be 0px`);
+ is(bodyStyle.get(prop), "0px", `Body node ${prop} should be 0px`);
+ }
+ }
+ }
+
+ {
+ info("Test stand-alone browserAction popup");
+
+ clickBrowserAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ yield testPanel(browser);
+ yield closeBrowserAction(extension);
+ }
+
+ {
+ info("Test menu panel browserAction popup");
+
+ let widget = getBrowserActionWidget(extension);
+ CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
+
+ clickBrowserAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ yield testPanel(browser, false);
+ yield closeBrowserAction(extension);
+ }
+
+ {
+ info("Test pageAction popup");
+
+ clickPageAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ yield testPanel(browser);
+ yield closePageAction(extension);
+ }
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_popup_sendMessage.js b/browser/components/extensions/test/browser/browser_ext_popup_sendMessage.js
new file mode 100644
index 000000000..472ee7bbd
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_sendMessage.js
@@ -0,0 +1,93 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_popup_sendMessage_reply() {
+ let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>${url}</body></html>`;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+
+ "page_action": {
+ "default_popup": "popup.html",
+ "browser_style": true,
+ },
+ },
+
+ files: {
+ "popup.html": scriptPage("popup.js"),
+ "popup.js": async function() {
+ browser.runtime.onMessage.addListener(async msg => {
+ if (msg == "popup-ping") {
+ return Promise.resolve("popup-pong");
+ }
+ });
+
+ let response = await browser.runtime.sendMessage("background-ping");
+ browser.test.sendMessage("background-ping-response", response);
+ },
+ },
+
+ async background() {
+ browser.runtime.onMessage.addListener(async msg => {
+ if (msg == "background-ping") {
+ let response = await browser.runtime.sendMessage("popup-ping");
+
+ browser.test.sendMessage("popup-ping-response", response);
+
+ await new Promise(resolve => {
+ // Wait long enough that we're relatively sure the docShells have
+ // been swapped. Note that this value is fairly arbitrary. The load
+ // event that triggers the swap should happen almost immediately
+ // after the message is sent. The extra quarter of a second gives us
+ // enough leeway that we can expect to respond after the swap in the
+ // vast majority of cases.
+ setTimeout(resolve, 250);
+ });
+
+ return "background-pong";
+ }
+ });
+
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+
+ await browser.pageAction.show(tab.id);
+
+ browser.test.sendMessage("page-action-ready");
+ },
+ });
+
+ yield extension.startup();
+
+ {
+ clickBrowserAction(extension);
+
+ let pong = yield extension.awaitMessage("background-ping-response");
+ is(pong, "background-pong", "Got pong");
+
+ pong = yield extension.awaitMessage("popup-ping-response");
+ is(pong, "popup-pong", "Got pong");
+
+ yield closeBrowserAction(extension);
+ }
+
+ yield extension.awaitMessage("page-action-ready");
+
+ {
+ clickPageAction(extension);
+
+ let pong = yield extension.awaitMessage("background-ping-response");
+ is(pong, "background-pong", "Got pong");
+
+ pong = yield extension.awaitMessage("popup-ping-response");
+ is(pong, "popup-pong", "Got pong");
+
+ yield closePageAction(extension);
+ }
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js b/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
new file mode 100644
index 000000000..66d37e857
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
@@ -0,0 +1,77 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+let getExtension = () => {
+ return ExtensionTestUtils.loadExtension({
+ background: async function() {
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+ await browser.pageAction.show(tab.id);
+ browser.test.sendMessage("pageAction ready");
+ },
+
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+
+ "page_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html>
+ <html><head><meta charset="utf-8"></head></html>`,
+ },
+ });
+};
+
+add_task(function* testStandaloneBrowserAction() {
+ info("Test stand-alone browserAction popup");
+
+ let extension = getExtension();
+ yield extension.startup();
+ yield extension.awaitMessage("pageAction ready");
+
+ clickBrowserAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ let panel = getPanelForNode(browser);
+
+ yield extension.unload();
+
+ is(panel.parentNode, null, "Panel should be removed from the document");
+});
+
+add_task(function* testMenuPanelBrowserAction() {
+ let extension = getExtension();
+ yield extension.startup();
+ yield extension.awaitMessage("pageAction ready");
+
+ let widget = getBrowserActionWidget(extension);
+ CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
+
+ clickBrowserAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ let panel = getPanelForNode(browser);
+
+ yield extension.unload();
+
+ is(panel.state, "closed", "Panel should be closed");
+});
+
+add_task(function* testPageAction() {
+ let extension = getExtension();
+ yield extension.startup();
+ yield extension.awaitMessage("pageAction ready");
+
+ clickPageAction(extension);
+ let browser = yield awaitExtensionPanel(extension);
+ let panel = getPanelForNode(browser);
+
+ yield extension.unload();
+
+ is(panel.parentNode, null, "Panel should be removed from the document");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
new file mode 100644
index 000000000..1631ececa
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -0,0 +1,276 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+requestLongerTimeout(2);
+
+function add_tasks(task) {
+ add_task(task.bind(null, {embedded: false}));
+
+ add_task(task.bind(null, {embedded: true}));
+}
+
+function* loadExtension(options) {
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+
+ embedded: options.embedded,
+
+ manifest: Object.assign({
+ "permissions": ["tabs"],
+ }, options.manifest),
+
+ files: {
+ "options.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="options.js" type="text/javascript"></script>
+ </head>
+ </html>`,
+
+ "options.js": function() {
+ window.iAmOption = true;
+ browser.runtime.sendMessage("options.html");
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg == "ping") {
+ respond("pong");
+ }
+ });
+ },
+ },
+
+ background: options.background,
+ });
+
+ yield extension.startup();
+
+ return extension;
+}
+
+add_tasks(function* test_inline_options(extraOptions) {
+ info(`Test options opened inline (${JSON.stringify(extraOptions)})`);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+ let extension = yield loadExtension(Object.assign({}, extraOptions, {
+ manifest: {
+ applications: {gecko: {id: "inline_options@tests.mozilla.org"}},
+ "options_ui": {
+ "page": "options.html",
+ },
+ },
+
+ background: async function() {
+ let _optionsPromise;
+ let awaitOptions = () => {
+ browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
+
+ return new Promise(resolve => {
+ _optionsPromise = {resolve};
+ });
+ };
+
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ if (msg == "options.html") {
+ if (_optionsPromise) {
+ _optionsPromise.resolve(sender.tab);
+ _optionsPromise = null;
+ } else {
+ browser.test.fail("Saw unexpected options page load");
+ }
+ }
+ });
+
+ try {
+ let [firstTab] = await browser.tabs.query({currentWindow: true, active: true});
+
+ browser.test.log("Open options page. Expect fresh load.");
+
+ let [, optionsTab] = await Promise.all([
+ browser.runtime.openOptionsPage(),
+ awaitOptions(),
+ ]);
+
+ browser.test.assertEq("about:addons", optionsTab.url, "Tab contains AddonManager");
+ browser.test.assertTrue(optionsTab.active, "Tab is active");
+ browser.test.assertTrue(optionsTab.id != firstTab.id, "Tab is a new tab");
+
+ browser.test.assertEq(0, browser.extension.getViews({type: "popup"}).length, "viewType is not popup");
+ browser.test.assertEq(1, browser.extension.getViews({type: "tab"}).length, "viewType is tab");
+ browser.test.assertEq(1, browser.extension.getViews({windowId: optionsTab.windowId}).length, "windowId matches");
+
+ let views = browser.extension.getViews();
+ browser.test.assertEq(2, views.length, "Expected the options page and the background page");
+ browser.test.assertTrue(views.includes(window), "One of the views is the background page");
+ browser.test.assertTrue(views.some(w => w.iAmOption), "One of the views is the options page");
+
+ browser.test.log("Switch tabs.");
+ await browser.tabs.update(firstTab.id, {active: true});
+
+ browser.test.log("Open options page again. Expect tab re-selected, no new load.");
+
+ await browser.runtime.openOptionsPage();
+ let [tab] = await browser.tabs.query({currentWindow: true, active: true});
+
+ browser.test.assertEq(optionsTab.id, tab.id, "Tab is the same as the previous options tab");
+ browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
+
+ browser.test.log("Ping options page.");
+ let pong = await browser.runtime.sendMessage("ping");
+ browser.test.assertEq("pong", pong, "Got pong.");
+
+ browser.test.log("Remove options tab.");
+ await browser.tabs.remove(optionsTab.id);
+
+ browser.test.log("Open options page again. Expect fresh load.");
+ [, tab] = await Promise.all([
+ browser.runtime.openOptionsPage(),
+ awaitOptions(),
+ ]);
+ browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
+ browser.test.assertTrue(tab.active, "Tab is active");
+ browser.test.assertTrue(tab.id != optionsTab.id, "Tab is a new tab");
+
+ await browser.tabs.remove(tab.id);
+
+ browser.test.notifyPass("options-ui");
+ } catch (error) {
+ browser.test.fail(`Error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("options-ui");
+ }
+ },
+ }));
+
+ yield extension.awaitFinish("options-ui");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_tasks(function* test_tab_options(extraOptions) {
+ info(`Test options opened in a tab (${JSON.stringify(extraOptions)})`);
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+ let extension = yield loadExtension(Object.assign({}, extraOptions, {
+ manifest: {
+ applications: {gecko: {id: "tab_options@tests.mozilla.org"}},
+ "options_ui": {
+ "page": "options.html",
+ "open_in_tab": true,
+ },
+ },
+
+ background: async function() {
+ let _optionsPromise;
+ let awaitOptions = () => {
+ browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
+
+ return new Promise(resolve => {
+ _optionsPromise = {resolve};
+ });
+ };
+
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ if (msg == "options.html") {
+ if (_optionsPromise) {
+ _optionsPromise.resolve(sender.tab);
+ _optionsPromise = null;
+ } else {
+ browser.test.fail("Saw unexpected options page load");
+ }
+ }
+ });
+
+ let optionsURL = browser.extension.getURL("options.html");
+
+ try {
+ let [firstTab] = await browser.tabs.query({currentWindow: true, active: true});
+
+ browser.test.log("Open options page. Expect fresh load.");
+ let [, optionsTab] = await Promise.all([
+ browser.runtime.openOptionsPage(),
+ awaitOptions(),
+ ]);
+ browser.test.assertEq(optionsURL, optionsTab.url, "Tab contains options.html");
+ browser.test.assertTrue(optionsTab.active, "Tab is active");
+ browser.test.assertTrue(optionsTab.id != firstTab.id, "Tab is a new tab");
+
+ browser.test.assertEq(0, browser.extension.getViews({type: "popup"}).length, "viewType is not popup");
+ browser.test.assertEq(1, browser.extension.getViews({type: "tab"}).length, "viewType is tab");
+ browser.test.assertEq(1, browser.extension.getViews({windowId: optionsTab.windowId}).length, "windowId matches");
+
+ let views = browser.extension.getViews();
+ browser.test.assertEq(2, views.length, "Expected the options page and the background page");
+ browser.test.assertTrue(views.includes(window), "One of the views is the background page");
+ browser.test.assertTrue(views.some(w => w.iAmOption), "One of the views is the options page");
+
+ browser.test.log("Switch tabs.");
+ await browser.tabs.update(firstTab.id, {active: true});
+
+ browser.test.log("Open options page again. Expect tab re-selected, no new load.");
+
+ await browser.runtime.openOptionsPage();
+ let [tab] = await browser.tabs.query({currentWindow: true, active: true});
+
+ browser.test.assertEq(optionsTab.id, tab.id, "Tab is the same as the previous options tab");
+ browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
+
+ // Unfortunately, we can't currently do this, since onMessage doesn't
+ // currently support responses when there are multiple listeners.
+ //
+ // browser.test.log("Ping options page.");
+ // return new Promise(resolve => browser.runtime.sendMessage("ping", resolve));
+
+ browser.test.log("Remove options tab.");
+ await browser.tabs.remove(optionsTab.id);
+
+ browser.test.log("Open options page again. Expect fresh load.");
+ [, tab] = await Promise.all([
+ browser.runtime.openOptionsPage(),
+ awaitOptions(),
+ ]);
+ browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
+ browser.test.assertTrue(tab.active, "Tab is active");
+ browser.test.assertTrue(tab.id != optionsTab.id, "Tab is a new tab");
+
+ await browser.tabs.remove(tab.id);
+
+ browser.test.notifyPass("options-ui-tab");
+ } catch (error) {
+ browser.test.fail(`Error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("options-ui-tab");
+ }
+ },
+ }));
+
+ yield extension.awaitFinish("options-ui-tab");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_tasks(function* test_options_no_manifest(extraOptions) {
+ info(`Test with no manifest key (${JSON.stringify(extraOptions)})`);
+
+ let extension = yield loadExtension(Object.assign({}, extraOptions, {
+ manifest: {
+ applications: {gecko: {id: "no_options@tests.mozilla.org"}},
+ },
+
+ async background() {
+ browser.test.log("Try to open options page when not specified in the manifest.");
+
+ await browser.test.assertRejects(
+ browser.runtime.openOptionsPage(),
+ /No `options_ui` declared/,
+ "Expected error from openOptionsPage()");
+
+ browser.test.notifyPass("options-no-manifest");
+ },
+ }));
+
+ yield extension.awaitFinish("options-no-manifest");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js
new file mode 100644
index 000000000..0c123b70e
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage_uninstall.js
@@ -0,0 +1,101 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* loadExtension(options) {
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+
+ manifest: Object.assign({
+ "permissions": ["tabs"],
+ }, options.manifest),
+
+ files: {
+ "options.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="options.js" type="text/javascript"></script>
+ </head>
+ </html>`,
+
+ "options.js": function() {
+ browser.runtime.sendMessage("options.html");
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg == "ping") {
+ respond("pong");
+ }
+ });
+ },
+ },
+
+ background: options.background,
+ });
+
+ yield extension.startup();
+
+ return extension;
+}
+
+add_task(function* test_inline_options_uninstall() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+ let extension = yield loadExtension({
+ manifest: {
+ applications: {gecko: {id: "inline_options_uninstall@tests.mozilla.org"}},
+ "options_ui": {
+ "page": "options.html",
+ },
+ },
+
+ background: async function() {
+ let _optionsPromise;
+ let awaitOptions = () => {
+ browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
+
+ return new Promise(resolve => {
+ _optionsPromise = {resolve};
+ });
+ };
+
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ if (msg == "options.html") {
+ if (_optionsPromise) {
+ _optionsPromise.resolve(sender.tab);
+ _optionsPromise = null;
+ } else {
+ browser.test.fail("Saw unexpected options page load");
+ }
+ }
+ });
+
+ try {
+ let [firstTab] = await browser.tabs.query({currentWindow: true, active: true});
+
+ browser.test.log("Open options page. Expect fresh load.");
+ let [, tab] = await Promise.all([
+ browser.runtime.openOptionsPage(),
+ awaitOptions(),
+ ]);
+
+ browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
+ browser.test.assertTrue(tab.active, "Tab is active");
+ browser.test.assertTrue(tab.id != firstTab.id, "Tab is a new tab");
+
+ browser.test.sendMessage("options-ui-open");
+ } catch (error) {
+ browser.test.fail(`Error: ${error} :: ${error.stack}`);
+ }
+ },
+ });
+
+ yield extension.awaitMessage("options-ui-open");
+ yield extension.unload();
+
+ is(gBrowser.selectedBrowser.currentURI.spec, "about:addons",
+ "Add-on manager tab should still be open");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js b/browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js
new file mode 100644
index 000000000..1c7ef4969
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js
@@ -0,0 +1,94 @@
+"use strict";
+
+let {AddonManager} = Components.utils.import("resource://gre/modules/AddonManager.jsm", {});
+let {Extension} = Components.utils.import("resource://gre/modules/Extension.jsm", {});
+
+function* makeAndInstallXPI(id, backgroundScript, loadedURL) {
+ let xpi = Extension.generateXPI({
+ manifest: {applications: {gecko: {id}}},
+ background: backgroundScript,
+ });
+ SimpleTest.registerCleanupFunction(function cleanupXPI() {
+ Services.obs.notifyObservers(xpi, "flush-cache-entry", null);
+ xpi.remove(false);
+ });
+
+ let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, loadedURL);
+
+
+ info(`installing ${xpi.path}`);
+ let addon = yield AddonManager.installTemporaryAddon(xpi);
+ info("installed");
+
+ // A WebExtension is started asynchronously, we have our test extension
+ // open a new tab to signal that the background script has executed.
+ let loadTab = yield loadPromise;
+ yield BrowserTestUtils.removeTab(loadTab);
+
+ return addon;
+}
+
+
+add_task(function* test_setuninstallurl_badargs() {
+ async function background() {
+ await browser.test.assertRejects(
+ browser.runtime.setUninstallURL("this is not a url"),
+ /Invalid URL/,
+ "setUninstallURL with an invalid URL should fail");
+
+ await browser.test.assertRejects(
+ browser.runtime.setUninstallURL("file:///etc/passwd"),
+ /must have the scheme http or https/,
+ "setUninstallURL with an illegal URL should fail");
+
+ browser.test.notifyPass("setUninstallURL bad params");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background,
+ });
+ yield extension.startup();
+ yield extension.awaitFinish();
+ yield extension.unload();
+});
+
+// Test the documented behavior of setUninstallURL() that passing an
+// empty string is equivalent to not setting an uninstall URL
+// (i.e., no new tab is opened upon uninstall)
+add_task(function* test_setuninstall_empty_url() {
+ async function backgroundScript() {
+ await browser.runtime.setUninstallURL("");
+ browser.tabs.create({url: "http://example.com/addon_loaded"});
+ }
+
+ let addon = yield makeAndInstallXPI("test_uinstallurl2@tests.mozilla.org",
+ backgroundScript,
+ "http://example.com/addon_loaded");
+
+ addon.uninstall(true);
+ info("uninstalled");
+
+ // no need to explicitly check for the absence of a new tab,
+ // BrowserTestUtils will eventually complain if one is opened.
+});
+
+add_task(function* test_setuninstallurl() {
+ async function backgroundScript() {
+ await browser.runtime.setUninstallURL("http://example.com/addon_uninstalled");
+ browser.tabs.create({url: "http://example.com/addon_loaded"});
+ }
+
+ let addon = yield makeAndInstallXPI("test_uinstallurl@tests.mozilla.org",
+ backgroundScript,
+ "http://example.com/addon_loaded");
+
+ // look for a new tab with the uninstall url.
+ let uninstallPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/addon_uninstalled");
+
+ addon.uninstall(true);
+ info("uninstalled");
+
+ let uninstalledTab = yield uninstallPromise;
+ isnot(uninstalledTab, null, "opened tab with uninstall url");
+ yield BrowserTestUtils.removeTab(uninstalledTab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js
new file mode 100644
index 000000000..413f7bde6
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js
@@ -0,0 +1,97 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
+
+requestLongerTimeout(2);
+
+Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,
+ this);
+
+add_task(function* test_sessions_get_recently_closed() {
+ function* openAndCloseWindow(url = "http://example.com", tabUrls) {
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, url);
+ yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ if (tabUrls) {
+ for (let url of tabUrls) {
+ yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+ }
+ }
+ yield BrowserTestUtils.closeWindow(win);
+ }
+
+ function background() {
+ Promise.all([
+ browser.sessions.getRecentlyClosed(),
+ browser.tabs.query({active: true, currentWindow: true}),
+ ]).then(([recentlyClosed, tabs]) => {
+ browser.test.sendMessage("initialData", {recentlyClosed, currentWindowId: tabs[0].windowId});
+ });
+
+ browser.test.onMessage.addListener((msg, filter) => {
+ if (msg == "check-sessions") {
+ browser.sessions.getRecentlyClosed(filter).then(recentlyClosed => {
+ browser.test.sendMessage("recentlyClosed", recentlyClosed);
+ });
+ }
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["sessions", "tabs"],
+ },
+ background,
+ });
+
+ // Open and close a window that will be ignored, to prove that we are removing previous entries
+ yield openAndCloseWindow();
+
+ yield extension.startup();
+
+ let {recentlyClosed, currentWindowId} = yield extension.awaitMessage("initialData");
+ recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
+
+ yield openAndCloseWindow();
+ extension.sendMessage("check-sessions");
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 1, currentWindowId);
+
+ yield openAndCloseWindow("about:config", ["about:robots", "about:mozilla"]);
+ extension.sendMessage("check-sessions");
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ // Check for multiple tabs in most recently closed window
+ is(recentlyClosed[0].window.tabs.length, 3, "most recently closed window has the expected number of tabs");
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+ yield BrowserTestUtils.removeTab(tab);
+
+ tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+ yield BrowserTestUtils.removeTab(tab);
+
+ yield openAndCloseWindow();
+ extension.sendMessage("check-sessions");
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ let finalResult = recentlyClosed.filter(onlyNewItemsFilter);
+ checkRecentlyClosed(finalResult, 5, currentWindowId);
+
+ isnot(finalResult[0].window, undefined, "first item is a window");
+ is(finalResult[0].tab, undefined, "first item is not a tab");
+ isnot(finalResult[1].tab, undefined, "second item is a tab");
+ is(finalResult[1].window, undefined, "second item is not a window");
+ isnot(finalResult[2].tab, undefined, "third item is a tab");
+ is(finalResult[2].window, undefined, "third item is not a window");
+ isnot(finalResult[3].window, undefined, "fourth item is a window");
+ is(finalResult[3].tab, undefined, "fourth item is not a tab");
+ isnot(finalResult[4].window, undefined, "fifth item is a window");
+ is(finalResult[4].tab, undefined, "fifth item is not a tab");
+
+ // test with filter
+ extension.sendMessage("check-sessions", {maxResults: 2});
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 2, currentWindowId);
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
new file mode 100644
index 000000000..217c8e130
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
@@ -0,0 +1,61 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */
+
+SimpleTest.requestCompleteLog();
+
+Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href,
+ this);
+
+add_task(function* test_sessions_get_recently_closed_private() {
+ function background() {
+ browser.test.onMessage.addListener((msg, filter) => {
+ if (msg == "check-sessions") {
+ browser.sessions.getRecentlyClosed(filter).then(recentlyClosed => {
+ browser.test.sendMessage("recentlyClosed", recentlyClosed);
+ });
+ }
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["sessions", "tabs"],
+ },
+ background,
+ });
+
+ // Open a private browsing window.
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+ yield extension.startup();
+
+ let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let privateWinId = WindowManager.getId(privateWin);
+
+ extension.sendMessage("check-sessions");
+ let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
+
+ // Open and close two tabs in the private window
+ let tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
+ yield BrowserTestUtils.removeTab(tab);
+
+ tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
+ yield BrowserTestUtils.removeTab(tab);
+
+ extension.sendMessage("check-sessions");
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ checkRecentlyClosed(recentlyClosed.filter(onlyNewItemsFilter), 2, privateWinId, true);
+
+ // Close the private window.
+ yield BrowserTestUtils.closeWindow(privateWin);
+
+ extension.sendMessage("check-sessions");
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ is(recentlyClosed.filter(onlyNewItemsFilter).length, 0, "the closed private window info was not found in recently closed data");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js
new file mode 100644
index 000000000..ae0daff9a
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js
@@ -0,0 +1,96 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function expectedTabInfo(tab, window) {
+ let browser = tab.linkedBrowser;
+ return {
+ url: browser.currentURI.spec,
+ title: browser.contentTitle,
+ favIconUrl: window.gBrowser.getIcon(tab),
+ };
+}
+
+function checkTabInfo(expected, actual) {
+ for (let prop in expected) {
+ is(actual[prop], expected[prop], `Expected value found for ${prop} of tab object.`);
+ }
+}
+
+add_task(async function test_sessions_get_recently_closed_tabs() {
+ async function background() {
+ browser.test.onMessage.addListener(async msg => {
+ if (msg == "check-sessions") {
+ let recentlyClosed = await browser.sessions.getRecentlyClosed();
+ browser.test.sendMessage("recentlyClosed", recentlyClosed);
+ }
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["sessions", "tabs"],
+ },
+ background,
+ });
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:addons");
+ await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ let expectedTabs = [];
+ let tab = win.gBrowser.selectedTab;
+ expectedTabs.push(expectedTabInfo(tab, win));
+
+ for (let url of ["about:robots", "about:mozilla"]) {
+ tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+ expectedTabs.push(expectedTabInfo(tab, win));
+ }
+
+ await extension.startup();
+
+ // Test with a closed tab.
+ await BrowserTestUtils.removeTab(tab);
+
+ extension.sendMessage("check-sessions");
+ let recentlyClosed = await extension.awaitMessage("recentlyClosed");
+ let tabInfo = recentlyClosed[0].tab;
+ let expectedTab = expectedTabs.pop();
+ checkTabInfo(expectedTab, tabInfo);
+
+ // Test with a closed window containing tabs.
+ await BrowserTestUtils.closeWindow(win);
+
+ extension.sendMessage("check-sessions");
+ recentlyClosed = await extension.awaitMessage("recentlyClosed");
+ let tabInfos = recentlyClosed[0].window.tabs;
+ is(tabInfos.length, 2, "Expected number of tabs in closed window.");
+ for (let x = 0; x < tabInfos.length; x++) {
+ checkTabInfo(expectedTabs[x], tabInfos[x]);
+ }
+
+ await extension.unload();
+
+ // Test without tabs permission.
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["sessions"],
+ },
+ background,
+ });
+
+ await extension.startup();
+
+ extension.sendMessage("check-sessions");
+ recentlyClosed = await extension.awaitMessage("recentlyClosed");
+ tabInfos = recentlyClosed[0].window.tabs;
+ is(tabInfos.length, 2, "Expected number of tabs in closed window.");
+ for (let tabInfo of tabInfos) {
+ for (let prop in expectedTabs[0]) {
+ is(undefined,
+ tabInfo[prop],
+ `${prop} of tab object is undefined without tabs permission.`);
+ }
+ }
+
+ await extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_restore.js b/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
new file mode 100644
index 000000000..6f1c6cf9a
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
@@ -0,0 +1,134 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+
+add_task(function* test_sessions_restore() {
+ function background() {
+ browser.test.onMessage.addListener((msg, data) => {
+ if (msg == "check-sessions") {
+ browser.sessions.getRecentlyClosed().then(recentlyClosed => {
+ browser.test.sendMessage("recentlyClosed", recentlyClosed);
+ });
+ } else if (msg == "restore") {
+ browser.sessions.restore(data).then(sessions => {
+ browser.test.sendMessage("restored", sessions);
+ });
+ } else if (msg == "restore-reject") {
+ browser.sessions.restore("not-a-valid-session-id").then(
+ sessions => {
+ browser.test.fail("restore rejected with an invalid sessionId");
+ },
+ error => {
+ browser.test.assertTrue(
+ error.message.includes("Could not restore object using sessionId not-a-valid-session-id."));
+ browser.test.sendMessage("restore-rejected");
+ }
+ );
+ }
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["sessions", "tabs"],
+ },
+ background,
+ });
+
+ yield extension.startup();
+
+ let {Management: {global: {WindowManager, TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ function checkLocalTab(tab, expectedUrl) {
+ let realTab = TabManager.getTab(tab.id);
+ let tabState = JSON.parse(SessionStore.getTabState(realTab));
+ is(tabState.entries[0].url, expectedUrl, "restored tab has the expected url");
+ }
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:config");
+ yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ for (let url of ["about:robots", "about:mozilla"]) {
+ yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+ }
+ yield BrowserTestUtils.closeWindow(win);
+
+ extension.sendMessage("check-sessions");
+ let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+
+ // Check that our expected window is the most recently closed.
+ is(recentlyClosed[0].window.tabs.length, 3, "most recently closed window has the expected number of tabs");
+
+ // Restore the window.
+ extension.sendMessage("restore");
+ let restored = yield extension.awaitMessage("restored");
+
+ is(restored.length, 1, "restore returned the expected number of sessions");
+ is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
+ checkLocalTab(restored[0].window.tabs[0], "about:config");
+ checkLocalTab(restored[0].window.tabs[1], "about:robots");
+ checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
+
+ // Close the window again.
+ let window = WindowManager.getWindow(restored[0].window.id);
+ yield BrowserTestUtils.closeWindow(window);
+
+ // Restore the window using the sessionId.
+ extension.sendMessage("check-sessions");
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ extension.sendMessage("restore", recentlyClosed[0].window.sessionId);
+ restored = yield extension.awaitMessage("restored");
+
+ is(restored.length, 1, "restore returned the expected number of sessions");
+ is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
+ checkLocalTab(restored[0].window.tabs[0], "about:config");
+ checkLocalTab(restored[0].window.tabs[1], "about:robots");
+ checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
+
+ // Close the window again.
+ window = WindowManager.getWindow(restored[0].window.id);
+ yield BrowserTestUtils.closeWindow(window);
+
+ // Open and close a tab.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Restore the most recently closed item.
+ extension.sendMessage("restore");
+ restored = yield extension.awaitMessage("restored");
+
+ is(restored.length, 1, "restore returned the expected number of sessions");
+ tab = restored[0].tab;
+ ok(tab, "restore returned a tab");
+ checkLocalTab(tab, "about:robots");
+
+ // Close the tab again.
+ let realTab = TabManager.getTab(tab.id);
+ yield BrowserTestUtils.removeTab(realTab);
+
+ // Restore the tab using the sessionId.
+ extension.sendMessage("check-sessions");
+ recentlyClosed = yield extension.awaitMessage("recentlyClosed");
+ extension.sendMessage("restore", recentlyClosed[0].tab.sessionId);
+ restored = yield extension.awaitMessage("restored");
+
+ is(restored.length, 1, "restore returned the expected number of sessions");
+ tab = restored[0].tab;
+ ok(tab, "restore returned a tab");
+ checkLocalTab(tab, "about:robots");
+
+ // Close the tab again.
+ realTab = TabManager.getTab(tab.id);
+ yield BrowserTestUtils.removeTab(realTab);
+
+ // Try to restore something with an invalid sessionId.
+ extension.sendMessage("restore-reject");
+ restored = yield extension.awaitMessage("restore-rejected");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_simple.js b/browser/components/extensions/test/browser/browser_ext_simple.js
new file mode 100644
index 000000000..ffa00c9db
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_simple.js
@@ -0,0 +1,57 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_simple() {
+ let extensionData = {
+ manifest: {
+ "name": "Simple extension test",
+ "version": "1.0",
+ "manifest_version": 2,
+ "description": "",
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ info("load complete");
+ yield extension.startup();
+ info("startup complete");
+ yield extension.unload();
+ info("extension unloaded successfully");
+});
+
+add_task(function* test_background() {
+ function backgroundScript() {
+ browser.test.log("running background script");
+
+ browser.test.onMessage.addListener((x, y) => {
+ browser.test.assertEq(x, 10, "x is 10");
+ browser.test.assertEq(y, 20, "y is 20");
+
+ browser.test.notifyPass("background test passed");
+ });
+
+ browser.test.sendMessage("running", 1);
+ }
+
+ let extensionData = {
+ background: "(" + backgroundScript.toString() + ")()",
+ manifest: {
+ "name": "Simple extension test",
+ "version": "1.0",
+ "manifest_version": 2,
+ "description": "",
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ info("load complete");
+ let [, x] = yield Promise.all([extension.startup(), extension.awaitMessage("running")]);
+ is(x, 1, "got correct value from extension");
+ info("startup complete");
+ extension.sendMessage(10, 20);
+ yield extension.awaitFinish();
+ info("test complete");
+ yield extension.unload();
+ info("extension unloaded successfully");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tab_runtimeConnect.js b/browser/components/extensions/test/browser/browser_ext_tab_runtimeConnect.js
new file mode 100644
index 000000000..a5541a002
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tab_runtimeConnect.js
@@ -0,0 +1,74 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ let messages_received = [];
+
+ let tabId;
+
+ browser.runtime.onConnect.addListener((port) => {
+ browser.test.assertTrue(!!port, "tab to background port received");
+ browser.test.assertEq("tab-connection-name", port.name, "port name should be defined and equal to connectInfo.name");
+ browser.test.assertTrue(!!port.sender.tab, "port.sender.tab should be defined");
+ browser.test.assertEq(tabId, port.sender.tab.id, "port.sender.tab.id should be equal to the expected tabId");
+
+ port.onMessage.addListener((msg) => {
+ messages_received.push(msg);
+
+ if (messages_received.length == 1) {
+ browser.test.assertEq("tab to background port message", msg, "'tab to background' port message received");
+ port.postMessage("background to tab port message");
+ }
+
+ if (messages_received.length == 2) {
+ browser.test.assertTrue(!!msg.tabReceived, "'background to tab' reply port message received");
+ browser.test.assertEq("background to tab port message", msg.tabReceived, "reply port content contains the message received");
+
+ browser.test.notifyPass("tabRuntimeConnect.pass");
+ }
+ });
+ });
+
+ browser.tabs.create({url: "tab.html"},
+ (tab) => { tabId = tab.id; });
+ },
+
+ files: {
+ "tab.js": function() {
+ let port = browser.runtime.connect({name: "tab-connection-name"});
+ port.postMessage("tab to background port message");
+ port.onMessage.addListener((msg) => {
+ port.postMessage({tabReceived: msg});
+ });
+ },
+ "tab.html": `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>test tab extension page</title>
+ <meta charset="utf-8">
+ <script src="tab.js" async></script>
+ </head>
+ <body>
+ <h1>test tab extension page</h1>
+ </body>
+ </html>
+ `,
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabRuntimeConnect.pass");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_audio.js b/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
new file mode 100644
index 000000000..f9f6956d4
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
@@ -0,0 +1,203 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank?1");
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank?2");
+
+ gBrowser.selectedTab = tab1;
+
+ async function background() {
+ function promiseUpdated(tabId, attr) {
+ return new Promise(resolve => {
+ let onUpdated = (tabId_, changeInfo, tab) => {
+ if (tabId == tabId_ && attr in changeInfo) {
+ browser.tabs.onUpdated.removeListener(onUpdated);
+
+ resolve({changeInfo, tab});
+ }
+ };
+ browser.tabs.onUpdated.addListener(onUpdated);
+ });
+ }
+
+ let deferred = {};
+ browser.test.onMessage.addListener((message, tabId, result) => {
+ if (message == "change-tab-done" && deferred[tabId]) {
+ deferred[tabId].resolve(result);
+ }
+ });
+
+ function changeTab(tabId, attr, on) {
+ return new Promise((resolve, reject) => {
+ deferred[tabId] = {resolve, reject};
+ browser.test.sendMessage("change-tab", tabId, attr, on);
+ });
+ }
+
+
+ try {
+ let tabs = await browser.tabs.query({lastFocusedWindow: true});
+ browser.test.assertEq(tabs.length, 3, "We have three tabs");
+
+ for (let tab of tabs) {
+ // Note: We want to check that these are actual boolean values, not
+ // just that they evaluate as false.
+ browser.test.assertEq(false, tab.mutedInfo.muted, "Tab is not muted");
+ browser.test.assertEq(undefined, tab.mutedInfo.reason, "Tab has no muted info reason");
+ browser.test.assertEq(false, tab.audible, "Tab is not audible");
+ }
+
+ let windowId = tabs[0].windowId;
+ let tabIds = [tabs[1].id, tabs[2].id];
+
+ browser.test.log("Test initial queries for muted and audible return no tabs");
+ let silent = await browser.tabs.query({windowId, audible: false});
+ let audible = await browser.tabs.query({windowId, audible: true});
+ let muted = await browser.tabs.query({windowId, muted: true});
+ let nonMuted = await browser.tabs.query({windowId, muted: false});
+
+ browser.test.assertEq(3, silent.length, "Three silent tabs");
+ browser.test.assertEq(0, audible.length, "No audible tabs");
+
+ browser.test.assertEq(0, muted.length, "No muted tabs");
+ browser.test.assertEq(3, nonMuted.length, "Three non-muted tabs");
+
+ browser.test.log("Toggle muted and audible externally on one tab each, and check results");
+ [muted, audible] = await Promise.all([
+ promiseUpdated(tabIds[0], "mutedInfo"),
+ promiseUpdated(tabIds[1], "audible"),
+ changeTab(tabIds[0], "muted", true),
+ changeTab(tabIds[1], "audible", true),
+ ]);
+
+ for (let obj of [muted.changeInfo, muted.tab]) {
+ browser.test.assertEq(true, obj.mutedInfo.muted, "Tab is muted");
+ browser.test.assertEq("user", obj.mutedInfo.reason, "Tab was muted by the user");
+ }
+
+ browser.test.assertEq(true, audible.changeInfo.audible, "Tab audible state changed");
+ browser.test.assertEq(true, audible.tab.audible, "Tab is audible");
+
+ browser.test.log("Re-check queries. Expect one audible and one muted tab");
+ silent = await browser.tabs.query({windowId, audible: false});
+ audible = await browser.tabs.query({windowId, audible: true});
+ muted = await browser.tabs.query({windowId, muted: true});
+ nonMuted = await browser.tabs.query({windowId, muted: false});
+
+ browser.test.assertEq(2, silent.length, "Two silent tabs");
+ browser.test.assertEq(1, audible.length, "One audible tab");
+
+ browser.test.assertEq(1, muted.length, "One muted tab");
+ browser.test.assertEq(2, nonMuted.length, "Two non-muted tabs");
+
+ browser.test.assertEq(true, muted[0].mutedInfo.muted, "Tab is muted");
+ browser.test.assertEq("user", muted[0].mutedInfo.reason, "Tab was muted by the user");
+
+ browser.test.assertEq(true, audible[0].audible, "Tab is audible");
+
+ browser.test.log("Toggle muted internally on two tabs, and check results");
+ [nonMuted, muted] = await Promise.all([
+ promiseUpdated(tabIds[0], "mutedInfo"),
+ promiseUpdated(tabIds[1], "mutedInfo"),
+ browser.tabs.update(tabIds[0], {muted: false}),
+ browser.tabs.update(tabIds[1], {muted: true}),
+ ]);
+
+ for (let obj of [nonMuted.changeInfo, nonMuted.tab]) {
+ browser.test.assertEq(false, obj.mutedInfo.muted, "Tab is not muted");
+ }
+ for (let obj of [muted.changeInfo, muted.tab]) {
+ browser.test.assertEq(true, obj.mutedInfo.muted, "Tab is muted");
+ }
+
+ for (let obj of [nonMuted.changeInfo, nonMuted.tab, muted.changeInfo, muted.tab]) {
+ browser.test.assertEq("extension", obj.mutedInfo.reason, "Mute state changed by extension");
+
+ // FIXME: browser.runtime.id is currently broken.
+ browser.test.assertEq(browser.i18n.getMessage("@@extension_id"),
+ obj.mutedInfo.extensionId,
+ "Mute state changed by extension");
+ }
+
+ browser.test.log("Test that mutedInfo is preserved by sessionstore");
+ let tab = await changeTab(tabIds[1], "duplicate").then(browser.tabs.get);
+
+ browser.test.assertEq(true, tab.mutedInfo.muted, "Tab is muted");
+
+ browser.test.assertEq("extension", tab.mutedInfo.reason, "Mute state changed by extension");
+
+ // FIXME: browser.runtime.id is currently broken.
+ browser.test.assertEq(browser.i18n.getMessage("@@extension_id"),
+ tab.mutedInfo.extensionId,
+ "Mute state changed by extension");
+
+ browser.test.log("Unmute externally, and check results");
+ [nonMuted] = await Promise.all([
+ promiseUpdated(tabIds[1], "mutedInfo"),
+ changeTab(tabIds[1], "muted", false),
+ browser.tabs.remove(tab.id),
+ ]);
+
+ for (let obj of [nonMuted.changeInfo, nonMuted.tab]) {
+ browser.test.assertEq(false, obj.mutedInfo.muted, "Tab is not muted");
+ browser.test.assertEq("user", obj.mutedInfo.reason, "Mute state changed by user");
+ }
+
+ browser.test.notifyPass("tab-audio");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("tab-audio");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ extension.onMessage("change-tab", (tabId, attr, on) => {
+ let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let tab = TabManager.getTab(tabId);
+
+ if (attr == "muted") {
+ // Ideally we'd simulate a click on the tab audio icon for this, but the
+ // handler relies on CSS :hover states, which are complicated and fragile
+ // to simulate.
+ if (tab.muted != on) {
+ tab.toggleMuteAudio();
+ }
+ } else if (attr == "audible") {
+ let browser = tab.linkedBrowser;
+ if (on) {
+ browser.audioPlaybackStarted();
+ } else {
+ browser.audioPlaybackStopped();
+ }
+ } else if (attr == "duplicate") {
+ // This is a bit of a hack. It won't be necessary once we have
+ // `tabs.duplicate`.
+ let newTab = gBrowser.duplicateTab(tab);
+ BrowserTestUtils.waitForEvent(newTab, "SSTabRestored", () => true).then(() => {
+ extension.sendMessage("change-tab-done", tabId, TabManager.getId(newTab));
+ });
+ return;
+ }
+
+ extension.sendMessage("change-tab-done", tabId);
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("tab-audio");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js b/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
new file mode 100644
index 000000000..1491a19ab
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
@@ -0,0 +1,155 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* runTest(options) {
+ options.neutral = [0xaa, 0xaa, 0xaa];
+
+ let html = `
+ <!DOCTYPE html>
+ <html lang="en">
+ <head><meta charset="UTF-8"></head>
+ <body style="background-color: rgb(${options.color})">
+ <!-- Fill most of the image with a neutral color to test edge-to-edge scaling. -->
+ <div style="position: absolute;
+ left: 2px;
+ right: 2px;
+ top: 2px;
+ bottom: 2px;
+ background: rgb(${options.neutral});"></div>
+ </body>
+ </html>
+ `;
+
+ let url = `data:text/html,${encodeURIComponent(html)}`;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
+
+ tab.linkedBrowser.fullZoom = options.fullZoom;
+
+ async function background(options) {
+ browser.test.log(`Test color ${options.color} at fullZoom=${options.fullZoom}`);
+
+ try {
+ let [tab] = await browser.tabs.query({currentWindow: true, active: true});
+
+ let [jpeg, png, ...pngs] = await Promise.all([
+ browser.tabs.captureVisibleTab(tab.windowId, {format: "jpeg", quality: 95}),
+ browser.tabs.captureVisibleTab(tab.windowId, {format: "png", quality: 95}),
+ browser.tabs.captureVisibleTab(tab.windowId, {quality: 95}),
+ browser.tabs.captureVisibleTab(tab.windowId),
+ ]);
+
+ browser.test.assertTrue(pngs.every(url => url == png), "All PNGs are identical");
+
+ browser.test.assertTrue(jpeg.startsWith("data:image/jpeg;base64,"), "jpeg is JPEG");
+ browser.test.assertTrue(png.startsWith("data:image/png;base64,"), "png is PNG");
+
+ let promises = [jpeg, png].map(url => new Promise(resolve => {
+ let img = new Image();
+ img.src = url;
+ img.onload = () => resolve(img);
+ }));
+
+ [jpeg, png] = await Promise.all(promises);
+ let tabDims = `${tab.width}\u00d7${tab.height}`;
+
+ let images = {jpeg, png};
+ for (let format of Object.keys(images)) {
+ let img = images[format];
+
+ let dims = `${img.width}\u00d7${img.height}`;
+ browser.test.assertEq(tabDims, dims, `${format} dimensions are correct`);
+
+ let canvas = document.createElement("canvas");
+ canvas.width = img.width;
+ canvas.height = img.height;
+ canvas.mozOpaque = true;
+
+ let ctx = canvas.getContext("2d");
+ ctx.drawImage(img, 0, 0);
+
+ // Check the colors of the first and last pixels of the image, to make
+ // sure we capture the entire frame, and scale it correctly.
+ let coords = [
+ {x: 0, y: 0,
+ color: options.color},
+ {x: img.width - 1,
+ y: img.height - 1,
+ color: options.color},
+ {x: img.width / 2 | 0,
+ y: img.height / 2 | 0,
+ color: options.neutral},
+ ];
+
+ for (let {x, y, color} of coords) {
+ let imageData = ctx.getImageData(x, y, 1, 1).data;
+
+ if (format == "png") {
+ browser.test.assertEq(`rgba(${color},255)`, `rgba(${[...imageData]})`, `${format} image color is correct at (${x}, ${y})`);
+ } else {
+ // Allow for some deviation in JPEG version due to lossy compression.
+ const SLOP = 3;
+
+ browser.test.log(`Testing ${format} image color at (${x}, ${y}), have rgba(${[...imageData]}), expecting approx. rgba(${color},255)`);
+
+ browser.test.assertTrue(Math.abs(color[0] - imageData[0]) <= SLOP, `${format} image color.red is correct at (${x}, ${y})`);
+ browser.test.assertTrue(Math.abs(color[1] - imageData[1]) <= SLOP, `${format} image color.green is correct at (${x}, ${y})`);
+ browser.test.assertTrue(Math.abs(color[2] - imageData[2]) <= SLOP, `${format} image color.blue is correct at (${x}, ${y})`);
+ browser.test.assertEq(255, imageData[3], `${format} image color.alpha is correct at (${x}, ${y})`);
+ }
+ }
+ }
+
+ browser.test.notifyPass("captureVisibleTab");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("captureVisibleTab");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["<all_urls>"],
+ },
+
+ background: `(${background})(${JSON.stringify(options)})`,
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("captureVisibleTab");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+}
+
+add_task(function* testCaptureVisibleTab() {
+ yield runTest({color: [0, 0, 0], fullZoom: 1});
+
+ yield runTest({color: [0, 0, 0], fullZoom: 2});
+
+ yield runTest({color: [0, 0, 0], fullZoom: 0.5});
+
+ yield runTest({color: [255, 255, 255], fullZoom: 1});
+});
+
+add_task(function* testCaptureVisibleTabPermissions() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background() {
+ browser.test.assertFalse("captureVisibleTab" in browser.tabs,
+ 'Extension without "<all_tabs>" permission should not have access to captureVisibleTab');
+ browser.test.notifyPass("captureVisibleTabPermissions");
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("captureVisibleTabPermissions");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js b/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
new file mode 100644
index 000000000..dc0647e3c
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_cookieStoreId.js
@@ -0,0 +1,156 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* setup() {
+ // make sure userContext is enabled.
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true],
+ ]});
+});
+
+add_task(function* () {
+ info("Start testing tabs.create with cookieStoreId");
+
+ let testCases = [
+ // No private window
+ {privateTab: false, cookieStoreId: null, success: true, expectedCookieStoreId: "firefox-default"},
+ {privateTab: false, cookieStoreId: "firefox-default", success: true, expectedCookieStoreId: "firefox-default"},
+ {privateTab: false, cookieStoreId: "firefox-container-1", success: true, expectedCookieStoreId: "firefox-container-1"},
+ {privateTab: false, cookieStoreId: "firefox-container-2", success: true, expectedCookieStoreId: "firefox-container-2"},
+ {privateTab: false, cookieStoreId: "firefox-container-42", failure: "exist"},
+ {privateTab: false, cookieStoreId: "firefox-private", failure: "defaultToPrivate"},
+ {privateTab: false, cookieStoreId: "wow", failure: "illegal"},
+
+ // Private window
+ {privateTab: true, cookieStoreId: null, success: true, expectedCookieStoreId: "firefox-private"},
+ {privateTab: true, cookieStoreId: "firefox-private", success: true, expectedCookieStoreId: "firefox-private"},
+ {privateTab: true, cookieStoreId: "firefox-default", failure: "privateToDefault"},
+ {privateTab: true, cookieStoreId: "firefox-container-1", failure: "privateToDefault"},
+ {privateTab: true, cookieStoreId: "wow", failure: "illegal"},
+ ];
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs", "cookies"],
+ },
+
+ background: function() {
+ function testTab(data, tab) {
+ browser.test.assertTrue(data.success, "we want a success");
+ browser.test.assertTrue(!!tab, "we have a tab");
+ browser.test.assertEq(data.expectedCookieStoreId, tab.cookieStoreId, "tab should have the correct cookieStoreId");
+ }
+
+ async function runTest(data) {
+ try {
+ // Tab Creation
+ let tab;
+ try {
+ tab = await browser.tabs.create({
+ windowId: data.privateTab ? this.privateWindowId : this.defaultWindowId,
+ cookieStoreId: data.cookieStoreId,
+ });
+
+ browser.test.assertTrue(!data.failure, "we want a success");
+ } catch (error) {
+ browser.test.assertTrue(!!data.failure, "we want a failure");
+
+ if (data.failure == "illegal") {
+ browser.test.assertTrue(/Illegal cookieStoreId/.test(error.message),
+ "runtime.lastError should report the expected error message");
+ } else if (data.failure == "defaultToPrivate") {
+ browser.test.assertTrue("Illegal to set private cookieStorageId in a non private window",
+ error.message,
+ "runtime.lastError should report the expected error message");
+ } else if (data.failure == "privateToDefault") {
+ browser.test.assertTrue("Illegal to set non private cookieStorageId in a private window",
+ error.message,
+ "runtime.lastError should report the expected error message");
+ } else if (data.failure == "exist") {
+ browser.test.assertTrue(/No cookie store exists/.test(error.message),
+ "runtime.lastError should report the expected error message");
+ } else {
+ browser.test.fail("The test is broken");
+ }
+
+ browser.test.sendMessage("test-done");
+ return;
+ }
+
+ // Tests for tab creation
+ testTab(data, tab);
+
+ {
+ // Tests for tab querying
+ let [tab] = await browser.tabs.query({
+ windowId: data.privateTab ? this.privateWindowId : this.defaultWindowId,
+ cookieStoreId: data.cookieStoreId,
+ });
+
+ browser.test.assertTrue(tab != undefined, "Tab found!");
+ testTab(data, tab);
+ }
+
+ let stores = await browser.cookies.getAllCookieStores();
+
+ let store = stores.find(store => store.id === tab.cookieStoreId);
+ browser.test.assertTrue(!!store, "We have a store for this tab.");
+
+ await browser.tabs.remove(tab.id);
+
+ browser.test.sendMessage("test-done");
+ } catch (e) {
+ browser.test.fail("An exception has been thrown");
+ }
+ }
+
+ async function initialize() {
+ let win = await browser.windows.create({incognito: true});
+ this.privateWindowId = win.id;
+
+ win = await browser.windows.create({incognito: false});
+ this.defaultWindowId = win.id;
+
+ browser.test.sendMessage("ready");
+ }
+
+ async function shutdown() {
+ await browser.windows.remove(this.privateWindowId);
+ await browser.windows.remove(this.defaultWindowId);
+ browser.test.sendMessage("gone");
+ }
+
+ // Waiting for messages
+ browser.test.onMessage.addListener((msg, data) => {
+ if (msg == "be-ready") {
+ initialize();
+ } else if (msg == "test") {
+ runTest(data);
+ } else {
+ browser.test.assertTrue("finish", msg, "Shutting down");
+ shutdown();
+ }
+ });
+ },
+ });
+
+ yield extension.startup();
+
+ info("Tests must be ready...");
+ extension.sendMessage("be-ready");
+ yield extension.awaitMessage("ready");
+ info("Tests are ready to run!");
+
+ for (let test of testCases) {
+ info(`test tab.create with cookieStoreId: "${test.cookieStoreId}"`);
+ extension.sendMessage("test", test);
+ yield extension.awaitMessage("test-done");
+ }
+
+ info("Waiting for shutting down...");
+ extension.sendMessage("finish");
+ yield extension.awaitMessage("gone");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_create.js b/browser/components/extensions/test/browser/browser_ext_tabs_create.js
new file mode 100644
index 000000000..8bc5a68a2
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_create.js
@@ -0,0 +1,166 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+ gBrowser.selectedTab = tab;
+
+ // TODO: Multiple windows.
+
+ // Using pre-loaded new tab pages interferes with onUpdated events.
+ // It probably shouldn't.
+ SpecialPowers.setBoolPref("browser.newtab.preload", false);
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("browser.newtab.preload");
+ });
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+
+ "background": {"page": "bg/background.html"},
+ },
+
+ files: {
+ "bg/blank.html": `<html><head><meta charset="utf-8"></head></html>`,
+
+ "bg/background.html": `<html><head>
+ <meta charset="utf-8">
+ <script src="background.js"></script>
+ </head></html>`,
+
+ "bg/background.js": function() {
+ let activeTab;
+ let activeWindow;
+
+ function runTests() {
+ const DEFAULTS = {
+ index: 2,
+ windowId: activeWindow,
+ active: true,
+ pinned: false,
+ url: "about:newtab",
+ };
+
+ let tests = [
+ {
+ create: {url: "http://example.com/"},
+ result: {url: "http://example.com/"},
+ },
+ {
+ create: {url: "blank.html"},
+ result: {url: browser.runtime.getURL("bg/blank.html")},
+ },
+ {
+ create: {},
+ result: {url: "about:newtab"},
+ },
+ {
+ create: {active: false},
+ result: {active: false},
+ },
+ {
+ create: {active: true},
+ result: {active: true},
+ },
+ {
+ create: {pinned: true},
+ result: {pinned: true, index: 0},
+ },
+ {
+ create: {pinned: true, active: true},
+ result: {pinned: true, active: true, index: 0},
+ },
+ {
+ create: {pinned: true, active: false},
+ result: {pinned: true, active: false, index: 0},
+ },
+ {
+ create: {index: 1},
+ result: {index: 1},
+ },
+ {
+ create: {index: 1, active: false},
+ result: {index: 1, active: false},
+ },
+ {
+ create: {windowId: activeWindow},
+ result: {windowId: activeWindow},
+ },
+ ];
+
+ async function nextTest() {
+ if (!tests.length) {
+ browser.test.notifyPass("tabs.create");
+ return;
+ }
+
+ let test = tests.shift();
+ let expected = Object.assign({}, DEFAULTS, test.result);
+
+ browser.test.log(`Testing tabs.create(${JSON.stringify(test.create)}), expecting ${JSON.stringify(test.result)}`);
+
+ let updatedPromise = new Promise(resolve => {
+ let onUpdated = (changedTabId, changed) => {
+ if (changed.url) {
+ browser.tabs.onUpdated.removeListener(onUpdated);
+ resolve({tabId: changedTabId, url: changed.url});
+ }
+ };
+ browser.tabs.onUpdated.addListener(onUpdated);
+ });
+
+ let createdPromise = new Promise(resolve => {
+ let onCreated = tab => {
+ browser.test.assertTrue("id" in tab, `Expected tabs.onCreated callback to receive tab object`);
+ resolve();
+ };
+ browser.tabs.onCreated.addListener(onCreated);
+ });
+
+ let [tab] = await Promise.all([
+ browser.tabs.create(test.create),
+ createdPromise,
+ ]);
+ let tabId = tab.id;
+
+ for (let key of Object.keys(expected)) {
+ if (key === "url") {
+ // FIXME: This doesn't get updated until later in the load cycle.
+ continue;
+ }
+
+ browser.test.assertEq(expected[key], tab[key], `Expected value for tab.${key}`);
+ }
+
+ let updated = await updatedPromise;
+ browser.test.assertEq(tabId, updated.tabId, `Expected value for tab.id`);
+ browser.test.assertEq(expected.url, updated.url, `Expected value for tab.url`);
+
+ await browser.tabs.remove(tabId);
+ await browser.tabs.update(activeTab, {active: true});
+
+ nextTest();
+ }
+
+ nextTest();
+ }
+
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ activeTab = tabs[0].id;
+ activeWindow = tabs[0].windowId;
+
+ runTests();
+ });
+ },
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.create");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_create_invalid_url.js b/browser/components/extensions/test/browser/browser_ext_tabs_create_invalid_url.js
new file mode 100644
index 000000000..49938bf22
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_create_invalid_url.js
@@ -0,0 +1,66 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* testTabsCreateInvalidURL(tabsCreateURL) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.test.sendMessage("ready");
+ browser.test.onMessage.addListener((msg, tabsCreateURL) => {
+ browser.tabs.create({url: tabsCreateURL}, (tab) => {
+ browser.test.assertEq(undefined, tab, "on error tab should be undefined");
+ browser.test.assertTrue(/Illegal URL/.test(browser.runtime.lastError.message),
+ "runtime.lastError should report the expected error message");
+
+ // Remove the opened tab is any.
+ if (tab) {
+ browser.tabs.remove(tab.id);
+ }
+ browser.test.sendMessage("done");
+ });
+ });
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitMessage("ready");
+
+ info(`test tab.create on invalid URL "${tabsCreateURL}"`);
+
+ extension.sendMessage("start", tabsCreateURL);
+ yield extension.awaitMessage("done");
+
+ yield extension.unload();
+}
+
+add_task(function* () {
+ info("Start testing tabs.create on invalid URLs");
+
+ let dataURLPage = `data:text/html,
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <h1>data url page</h1>
+ </body>
+ </html>`;
+
+ let testCases = [
+ {tabsCreateURL: "about:addons"},
+ {tabsCreateURL: "javascript:console.log('tabs.update execute javascript')"},
+ {tabsCreateURL: dataURLPage},
+ ];
+
+ for (let {tabsCreateURL} of testCases) {
+ yield* testTabsCreateInvalidURL(tabsCreateURL);
+ }
+
+ info("done");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_detectLanguage.js b/browser/components/extensions/test/browser/browser_ext_tabs_detectLanguage.js
new file mode 100644
index 000000000..f28606001
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_detectLanguage.js
@@ -0,0 +1,47 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testDetectLanguage() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: async function() {
+ const BASE_PATH = "browser/browser/components/extensions/test/browser";
+
+ function loadTab(url) {
+ return browser.tabs.create({url});
+ }
+
+ try {
+ let tab = await loadTab(`http://example.co.jp/${BASE_PATH}/file_language_ja.html`);
+ let lang = await browser.tabs.detectLanguage(tab.id);
+ browser.test.assertEq("ja", lang, "Japanese document should be detected as Japanese");
+ await browser.tabs.remove(tab.id);
+
+ tab = await loadTab(`http://example.co.jp/${BASE_PATH}/file_language_fr_en.html`);
+ lang = await browser.tabs.detectLanguage(tab.id);
+ browser.test.assertEq("fr", lang, "French/English document should be detected as primarily French");
+ await browser.tabs.remove(tab.id);
+
+ tab = await loadTab(`http://example.co.jp/${BASE_PATH}/file_language_tlh.html`);
+ lang = await browser.tabs.detectLanguage(tab.id);
+ browser.test.assertEq("und", lang, "Klingon document should not be detected, should return 'und'");
+ await browser.tabs.remove(tab.id);
+
+ browser.test.notifyPass("detectLanguage");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("detectLanguage");
+ }
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("detectLanguage");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
new file mode 100644
index 000000000..c4b0ffd2d
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
@@ -0,0 +1,146 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testDuplicateTab() {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query({
+ lastFocusedWindow: true,
+ }, function(tabs) {
+ let source = tabs[1];
+ // By moving it 0, we check that the new tab is created next
+ // to the existing one.
+ browser.tabs.move(source.id, {index: 0}, () => {
+ browser.tabs.duplicate(source.id, (tab) => {
+ browser.test.assertEq("http://example.net/", tab.url);
+ // Should be the second tab, next to the one duplicated.
+ browser.test.assertEq(1, tab.index);
+ // Should be selected by default.
+ browser.test.assertTrue(tab.selected);
+ browser.test.notifyPass("tabs.duplicate");
+ });
+ });
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.duplicate");
+ yield extension.unload();
+
+ while (gBrowser.tabs[0].linkedBrowser.currentURI.spec === "http://example.net/") {
+ yield BrowserTestUtils.removeTab(gBrowser.tabs[0]);
+ }
+});
+
+add_task(function* testDuplicateTabLazily() {
+ async function background() {
+ let tabLoadComplete = new Promise(resolve => {
+ browser.test.onMessage.addListener((message, tabId, result) => {
+ if (message == "duplicate-tab-done") {
+ resolve(tabId);
+ }
+ });
+ });
+
+ function awaitLoad(tabId) {
+ return new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId_, changed, tab) {
+ if (tabId == tabId_ && changed.status == "complete") {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+ }
+
+ try {
+ let url = "http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
+ let tab = await browser.tabs.create({url});
+ let startTabId = tab.id;
+
+ await awaitLoad(startTabId);
+ browser.test.sendMessage("duplicate-tab", startTabId);
+
+ let unloadedTabId = await tabLoadComplete;
+ let loadedtab = await browser.tabs.get(startTabId);
+ browser.test.assertEq("Dummy test page", loadedtab.title, "Title should be returned for loaded pages");
+ browser.test.assertEq("complete", loadedtab.status, "Tab status should be complete for loaded pages");
+
+ let unloadedtab = await browser.tabs.get(unloadedTabId);
+ browser.test.assertEq("Dummy test page", unloadedtab.title, "Title should be returned after page has been unloaded");
+
+ await browser.tabs.remove([tab.id, unloadedTabId]);
+ browser.test.notifyPass("tabs.hasCorrectTabTitle");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("tabs.hasCorrectTabTitle");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ extension.onMessage("duplicate-tab", tabId => {
+ let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let tab = TabManager.getTab(tabId);
+ // This is a bit of a hack to load a tab in the background.
+ let newTab = gBrowser.duplicateTab(tab, false);
+
+ BrowserTestUtils.waitForEvent(newTab, "SSTabRestored", () => true).then(() => {
+ extension.sendMessage("duplicate-tab-done", TabManager.getId(newTab));
+ });
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.hasCorrectTabTitle");
+ yield extension.unload();
+});
+
+add_task(function* testDuplicatePinnedTab() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+ gBrowser.pinTab(tab);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query({
+ lastFocusedWindow: true,
+ }, function(tabs) {
+ // Duplicate the pinned tab, example.net.
+ browser.tabs.duplicate(tabs[0].id, (tab) => {
+ browser.test.assertEq("http://example.net/", tab.url);
+ // Should be the second tab, next to the one duplicated.
+ browser.test.assertEq(1, tab.index);
+ // Should be pinned.
+ browser.test.assertTrue(tab.pinned);
+ browser.test.notifyPass("tabs.duplicate.pinned");
+ });
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.duplicate.pinned");
+ yield extension.unload();
+
+ while (gBrowser.tabs[0].linkedBrowser.currentURI.spec === "http://example.net/") {
+ yield BrowserTestUtils.removeTab(gBrowser.tabs[0]);
+ }
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_events.js b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
new file mode 100644
index 000000000..75dea40fd
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
@@ -0,0 +1,280 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testTabEvents() {
+ async function background() {
+ let events = [];
+ browser.tabs.onCreated.addListener(tab => {
+ events.push({type: "onCreated", tab});
+ });
+
+ browser.tabs.onAttached.addListener((tabId, info) => {
+ events.push(Object.assign({type: "onAttached", tabId}, info));
+ });
+
+ browser.tabs.onDetached.addListener((tabId, info) => {
+ events.push(Object.assign({type: "onDetached", tabId}, info));
+ });
+
+ browser.tabs.onRemoved.addListener((tabId, info) => {
+ events.push(Object.assign({type: "onRemoved", tabId}, info));
+ });
+
+ browser.tabs.onMoved.addListener((tabId, info) => {
+ events.push(Object.assign({type: "onMoved", tabId}, info));
+ });
+
+ async function expectEvents(names) {
+ browser.test.log(`Expecting events: ${names.join(", ")}`);
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ browser.test.assertEq(names.length, events.length, "Got expected number of events");
+ for (let [i, name] of names.entries()) {
+ browser.test.assertEq(name, i in events && events[i].type,
+ `Got expected ${name} event`);
+ }
+ return events.splice(0);
+ }
+
+ try {
+ browser.test.log("Create second browser window");
+
+ let windows = await Promise.all([
+ browser.windows.getCurrent(),
+ browser.windows.create({url: "about:blank"}),
+ ]);
+
+ let windowId = windows[0].id;
+ let otherWindowId = windows[1].id;
+
+ let [created] = await expectEvents(["onCreated"]);
+ let initialTab = created.tab;
+
+
+ browser.test.log("Create tab in window 1");
+ let tab = await browser.tabs.create({windowId, index: 0, url: "about:blank"});
+ let oldIndex = tab.index;
+ browser.test.assertEq(0, oldIndex, "Tab has the expected index");
+
+ [created] = await expectEvents(["onCreated"]);
+ browser.test.assertEq(tab.id, created.tab.id, "Got expected tab ID");
+ browser.test.assertEq(oldIndex, created.tab.index, "Got expected tab index");
+
+
+ browser.test.log("Move tab to window 2");
+ await browser.tabs.move([tab.id], {windowId: otherWindowId, index: 0});
+
+ let [detached, attached] = await expectEvents(["onDetached", "onAttached"]);
+ browser.test.assertEq(oldIndex, detached.oldPosition, "Expected old index");
+ browser.test.assertEq(windowId, detached.oldWindowId, "Expected old window ID");
+
+ browser.test.assertEq(0, attached.newPosition, "Expected new index");
+ browser.test.assertEq(otherWindowId, attached.newWindowId, "Expected new window ID");
+
+
+ browser.test.log("Move tab within the same window");
+ let [moved] = await browser.tabs.move([tab.id], {index: 1});
+ browser.test.assertEq(1, moved.index, "Expected new index");
+
+ [moved] = await expectEvents(["onMoved"]);
+ browser.test.assertEq(tab.id, moved.tabId, "Expected tab ID");
+ browser.test.assertEq(0, moved.fromIndex, "Expected old index");
+ browser.test.assertEq(1, moved.toIndex, "Expected new index");
+ browser.test.assertEq(otherWindowId, moved.windowId, "Expected window ID");
+
+
+ browser.test.log("Remove tab");
+ await browser.tabs.remove(tab.id);
+ let [removed] = await expectEvents(["onRemoved"]);
+
+ browser.test.assertEq(tab.id, removed.tabId, "Expected removed tab ID");
+ browser.test.assertEq(otherWindowId, removed.windowId, "Expected removed tab window ID");
+ // Note: We want to test for the actual boolean value false here.
+ browser.test.assertEq(false, removed.isWindowClosing, "Expected isWindowClosing value");
+
+
+ browser.test.log("Close second window");
+ await browser.windows.remove(otherWindowId);
+ [removed] = await expectEvents(["onRemoved"]);
+ browser.test.assertEq(initialTab.id, removed.tabId, "Expected removed tab ID");
+ browser.test.assertEq(otherWindowId, removed.windowId, "Expected removed tab window ID");
+ browser.test.assertEq(true, removed.isWindowClosing, "Expected isWindowClosing value");
+
+
+ browser.test.log("Create additional tab in window 1");
+ tab = await browser.tabs.create({windowId, url: "about:blank"});
+ await expectEvents(["onCreated"]);
+
+
+ browser.test.log("Create a new window, adopting the new tab");
+ // We have to explicitly wait for the event here, since its timing is
+ // not predictable.
+ let promiseAttached = new Promise(resolve => {
+ browser.tabs.onAttached.addListener(function listener(tabId) {
+ browser.tabs.onAttached.removeListener(listener);
+ resolve();
+ });
+ });
+
+ let [window] = await Promise.all([
+ browser.windows.create({tabId: tab.id}),
+ promiseAttached,
+ ]);
+
+ [detached, attached] = await expectEvents(["onDetached", "onAttached"]);
+
+ browser.test.assertEq(tab.id, detached.tabId, "Expected onDetached tab ID");
+
+ browser.test.assertEq(tab.id, attached.tabId, "Expected onAttached tab ID");
+ browser.test.assertEq(0, attached.newPosition, "Expected onAttached new index");
+ browser.test.assertEq(window.id, attached.newWindowId,
+ "Expected onAttached new window id");
+
+ browser.test.log("Close the new window");
+ await browser.windows.remove(window.id);
+
+ browser.test.notifyPass("tabs-events");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("tabs-events");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs-events");
+ yield extension.unload();
+});
+
+add_task(function* testTabEventsSize() {
+ function background() {
+ function sendSizeMessages(tab, type) {
+ browser.test.sendMessage(`${type}-dims`, {width: tab.width, height: tab.height});
+ }
+
+ browser.tabs.onCreated.addListener(tab => {
+ sendSizeMessages(tab, "on-created");
+ });
+
+ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
+ if (changeInfo.status == "complete") {
+ sendSizeMessages(tab, "on-updated");
+ }
+ });
+
+ browser.test.onMessage.addListener(async (msg, arg) => {
+ if (msg === "create-tab") {
+ let tab = await browser.tabs.create({url: "http://example.com/"});
+ sendSizeMessages(tab, "create");
+ browser.test.sendMessage("created-tab-id", tab.id);
+ } else if (msg === "update-tab") {
+ let tab = await browser.tabs.update(arg, {url: "http://example.org/"});
+ sendSizeMessages(tab, "update");
+ } else if (msg === "remove-tab") {
+ browser.tabs.remove(arg);
+ browser.test.sendMessage("tab-removed");
+ }
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+ background,
+ });
+
+ const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref(RESOLUTION_PREF);
+ });
+
+ function checkDimensions(dims, type) {
+ is(dims.width, gBrowser.selectedBrowser.clientWidth, `tab from ${type} reports expected width`);
+ is(dims.height, gBrowser.selectedBrowser.clientHeight, `tab from ${type} reports expected height`);
+ }
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
+
+ for (let resolution of [2, 1]) {
+ SpecialPowers.setCharPref(RESOLUTION_PREF, String(resolution));
+ is(window.devicePixelRatio, resolution, "window has the required resolution");
+
+ extension.sendMessage("create-tab");
+ let tabId = yield extension.awaitMessage("created-tab-id");
+
+ checkDimensions(yield extension.awaitMessage("create-dims"), "create");
+ checkDimensions(yield extension.awaitMessage("on-created-dims"), "onCreated");
+ checkDimensions(yield extension.awaitMessage("on-updated-dims"), "onUpdated");
+
+ extension.sendMessage("update-tab", tabId);
+
+ checkDimensions(yield extension.awaitMessage("update-dims"), "update");
+ checkDimensions(yield extension.awaitMessage("on-updated-dims"), "onUpdated");
+
+ extension.sendMessage("remove-tab", tabId);
+ yield extension.awaitMessage("tab-removed");
+ }
+
+ yield extension.unload();
+ SpecialPowers.clearUserPref(RESOLUTION_PREF);
+});
+
+add_task(function* testTabRemovalEvent() {
+ async function background() {
+ function awaitLoad(tabId) {
+ return new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId_, changed, tab) {
+ if (tabId == tabId_ && changed.status == "complete") {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+ }
+
+ chrome.tabs.onRemoved.addListener((tabId, info) => {
+ browser.test.log("Make sure the removed tab is not available in the tabs.query callback.");
+ chrome.tabs.query({}, tabs => {
+ for (let tab of tabs) {
+ browser.test.assertTrue(tab.id != tabId, "Tab query should not include removed tabId");
+ }
+ browser.test.notifyPass("tabs-events");
+ });
+ });
+
+ try {
+ let url = "http://example.com/browser/browser/components/extensions/test/browser/context.html";
+ let tab = await browser.tabs.create({url: url});
+ await awaitLoad(tab.id);
+
+ await browser.tabs.remove(tab.id);
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("tabs-events");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs-events");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
new file mode 100644
index 000000000..5a15f2e39
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -0,0 +1,234 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testExecuteScript() {
+ let {MessageChannel} = Cu.import("resource://gre/modules/MessageChannel.jsm", {});
+
+ function countMM(messageManagerMap) {
+ let count = 0;
+ // List of permanent message managers in the main process. We should not
+ // count them in the test because MessageChannel unsubscribes when the
+ // message manager closes, which never happens to these, of course.
+ let globalMMs = [
+ Services.mm,
+ Services.ppmm,
+ Services.ppmm.getChildAt(0),
+ ];
+ for (let mm of messageManagerMap.keys()) {
+ // Sanity check: mm is a message manager.
+ try {
+ mm.QueryInterface(Ci.nsIMessageSender);
+ } catch (e) {
+ mm.QueryInterface(Ci.nsIMessageBroadcaster);
+ }
+ if (!globalMMs.includes(mm)) {
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ let messageManagersSize = countMM(MessageChannel.messageManagers);
+ let responseManagersSize = countMM(MessageChannel.responseManagers);
+
+ const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+ const URL = BASE + "file_iframe_document.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL, true);
+
+ async function background() {
+ try {
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+ let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
+
+ browser.test.log(`FRAMES: ${frames[1].frameId} ${JSON.stringify(frames)}\n`);
+ await Promise.all([
+ browser.tabs.executeScript({
+ code: "42",
+ }).then(result => {
+ browser.test.assertEq(1, result.length, "Expected one callback result");
+ browser.test.assertEq(42, result[0], "Expected callback result");
+ }),
+
+ browser.tabs.executeScript({
+ file: "script.js",
+ code: "42",
+ }).then(result => {
+ browser.test.fail("Expected not to be able to execute a script with both file and code");
+ }, error => {
+ browser.test.assertTrue(/a 'code' or a 'file' property, but not both/.test(error.message),
+ "Got expected error");
+ }),
+
+ browser.tabs.executeScript({
+ file: "script.js",
+ }).then(result => {
+ browser.test.assertEq(1, result.length, "Expected one callback result");
+ browser.test.assertEq(undefined, result[0], "Expected callback result");
+ }),
+
+ browser.tabs.executeScript({
+ file: "script2.js",
+ }).then(result => {
+ browser.test.assertEq(1, result.length, "Expected one callback result");
+ browser.test.assertEq(27, result[0], "Expected callback result");
+ }),
+
+ browser.tabs.executeScript({
+ code: "location.href;",
+ allFrames: true,
+ }).then(result => {
+ browser.test.assertTrue(Array.isArray(result), "Result is an array");
+
+ browser.test.assertEq(2, result.length, "Result has correct length");
+
+ browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "First result is correct");
+ browser.test.assertEq("http://mochi.test:8888/", result[1], "Second result is correct");
+ }),
+
+ browser.tabs.executeScript({
+ code: "location.href;",
+ runAt: "document_end",
+ }).then(result => {
+ browser.test.assertEq(1, result.length, "Expected callback result");
+ browser.test.assertEq("string", typeof result[0], "Result is a string");
+
+ browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "Result is correct");
+ }),
+
+ browser.tabs.executeScript({
+ code: "window",
+ }).then(result => {
+ browser.test.fail("Expected error when returning non-structured-clonable object");
+ }, error => {
+ browser.test.assertEq("Script returned non-structured-clonable data",
+ error.message, "Got expected error");
+ }),
+
+ browser.tabs.executeScript({
+ code: "Promise.resolve(window)",
+ }).then(result => {
+ browser.test.fail("Expected error when returning non-structured-clonable object");
+ }, error => {
+ browser.test.assertEq("Script returned non-structured-clonable data",
+ error.message, "Got expected error");
+ }),
+
+ browser.tabs.executeScript({
+ frameId: Number.MAX_SAFE_INTEGER,
+ code: "42",
+ }).then(result => {
+ browser.test.fail("Expected error when specifying invalid frame ID");
+ }, error => {
+ let details = {
+ frame_id: Number.MAX_SAFE_INTEGER,
+ matchesHost: ["http://mochi.test/", "http://example.com/"],
+ };
+ browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
+ error.message, "Got expected error");
+ }),
+
+ browser.tabs.create({url: "http://example.net/", active: false}).then(async tab => {
+ await browser.tabs.executeScript(tab.id, {
+ code: "42",
+ }).then(result => {
+ browser.test.fail("Expected error when trying to execute on invalid domain");
+ }, error => {
+ let details = {
+ matchesHost: ["http://mochi.test/", "http://example.com/"],
+ };
+ browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
+ error.message, "Got expected error");
+ });
+
+ await browser.tabs.remove(tab.id);
+ }),
+
+ browser.tabs.executeScript({
+ code: "Promise.resolve(42)",
+ }).then(result => {
+ browser.test.assertEq(42, result[0], "Got expected promise resolution value as result");
+ }),
+
+ browser.tabs.executeScript({
+ code: "location.href;",
+ runAt: "document_end",
+ allFrames: true,
+ }).then(result => {
+ browser.test.assertTrue(Array.isArray(result), "Result is an array");
+
+ browser.test.assertEq(2, result.length, "Result has correct length");
+
+ browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), "First result is correct");
+ browser.test.assertEq("http://mochi.test:8888/", result[1], "Second result is correct");
+ }),
+
+ browser.tabs.executeScript({
+ code: "location.href;",
+ frameId: frames[0].frameId,
+ }).then(result => {
+ browser.test.assertEq(1, result.length, "Expected one result");
+ browser.test.assertTrue(/\/file_iframe_document\.html$/.test(result[0]), `Result for frameId[0] is correct: ${result[0]}`);
+ }),
+
+ browser.tabs.executeScript({
+ code: "location.href;",
+ frameId: frames[1].frameId,
+ }).then(result => {
+ browser.test.assertEq(1, result.length, "Expected one result");
+ browser.test.assertEq("http://mochi.test:8888/", result[0], "Result for frameId[1] is correct");
+ }),
+
+ browser.tabs.create({url: "http://example.com/"}).then(async tab => {
+ let result = await browser.tabs.executeScript(tab.id, {code: "location.href"});
+
+ browser.test.assertEq("http://example.com/", result[0], "Script executed correctly in new tab");
+
+ await browser.tabs.remove(tab.id);
+ }),
+
+ new Promise(resolve => {
+ browser.runtime.onMessage.addListener(message => {
+ browser.test.assertEq("script ran", message, "Expected runtime message");
+ resolve();
+ });
+ }),
+ ]);
+
+ browser.test.notifyPass("executeScript");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("executeScript");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["http://mochi.test/", "http://example.com/", "webNavigation"],
+ },
+
+ background,
+
+ files: {
+ "script.js": function() {
+ browser.runtime.sendMessage("script ran");
+ },
+
+ "script2.js": "27",
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("executeScript");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Make sure that we're not holding on to references to closed message
+ // managers.
+ is(countMM(MessageChannel.messageManagers), messageManagersSize, "Message manager count");
+ is(countMM(MessageChannel.responseManagers), responseManagersSize, "Response manager count");
+ is(MessageChannel.pendingResponses.size, 0, "Pending response count");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
new file mode 100644
index 000000000..d11354ead
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
@@ -0,0 +1,217 @@
+"use strict";
+
+// This is a pretty terrible hack, but it's the best we can do until we
+// support |executeScript| callbacks and |lastError|.
+function* testHasNoPermission(params) {
+ let contentSetup = params.contentSetup || (() => Promise.resolve());
+
+ async function background(contentSetup) {
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.test.assertEq(msg, "second script ran", "second script ran");
+ browser.test.notifyPass("executeScript");
+ });
+
+ browser.test.onMessage.addListener(msg => {
+ browser.test.assertEq(msg, "execute-script");
+
+ browser.tabs.query({currentWindow: true}, tabs => {
+ browser.tabs.executeScript({
+ file: "script.js",
+ });
+
+ // Execute a script we know we have permissions for in the
+ // second tab, in the hopes that it will execute after the
+ // first one. This has intermittent failure written all over
+ // it, but it's just about the best we can do until we
+ // support callbacks for executeScript.
+ browser.tabs.executeScript(tabs[1].id, {
+ file: "second-script.js",
+ });
+ });
+ });
+
+ await contentSetup();
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: params.manifest,
+
+ background: `(${background})(${contentSetup})`,
+
+ files: {
+ "script.js": function() {
+ browser.runtime.sendMessage("first script ran");
+ },
+
+ "second-script.js": function() {
+ browser.runtime.sendMessage("second script ran");
+ },
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ if (params.setup) {
+ yield params.setup(extension);
+ }
+
+ extension.sendMessage("execute-script");
+
+ yield extension.awaitFinish("executeScript");
+ yield extension.unload();
+}
+
+add_task(function* testBadPermissions() {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+ info("Test no special permissions");
+ yield testHasNoPermission({
+ manifest: {"permissions": ["http://example.com/"]},
+ });
+
+ info("Test tabs permissions");
+ yield testHasNoPermission({
+ manifest: {"permissions": ["http://example.com/", "tabs"]},
+ });
+
+ info("Test no special permissions, commands, key press");
+ yield testHasNoPermission({
+ manifest: {
+ "permissions": ["http://example.com/"],
+ "commands": {
+ "test-tabs-executeScript": {
+ "suggested_key": {
+ "default": "Alt+Shift+K",
+ },
+ },
+ },
+ },
+ contentSetup() {
+ browser.commands.onCommand.addListener(function(command) {
+ if (command == "test-tabs-executeScript") {
+ browser.test.sendMessage("tabs-command-key-pressed");
+ }
+ });
+ return Promise.resolve();
+ },
+ setup: function* (extension) {
+ yield EventUtils.synthesizeKey("k", {altKey: true, shiftKey: true});
+ yield extension.awaitMessage("tabs-command-key-pressed");
+ },
+ });
+
+ info("Test active tab, commands, no key press");
+ yield testHasNoPermission({
+ manifest: {
+ "permissions": ["http://example.com/", "activeTab"],
+ "commands": {
+ "test-tabs-executeScript": {
+ "suggested_key": {
+ "default": "Alt+Shift+K",
+ },
+ },
+ },
+ },
+ });
+
+ info("Test active tab, browser action, no click");
+ yield testHasNoPermission({
+ manifest: {
+ "permissions": ["http://example.com/", "activeTab"],
+ "browser_action": {},
+ },
+ });
+
+ info("Test active tab, page action, no click");
+ yield testHasNoPermission({
+ manifest: {
+ "permissions": ["http://example.com/", "activeTab"],
+ "page_action": {},
+ },
+ async contentSetup() {
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+ await browser.pageAction.show(tab.id);
+ },
+ });
+
+ yield BrowserTestUtils.removeTab(tab2);
+ yield BrowserTestUtils.removeTab(tab1);
+});
+
+add_task(function* testBadURL() {
+ async function background() {
+ let promises = [
+ new Promise(resolve => {
+ browser.tabs.executeScript({
+ file: "http://example.com/script.js",
+ }, result => {
+ browser.test.assertEq(undefined, result, "Result value");
+
+ browser.test.assertTrue(browser.extension.lastError instanceof Error,
+ "runtime.lastError is Error");
+
+ browser.test.assertTrue(browser.runtime.lastError instanceof Error,
+ "runtime.lastError is Error");
+
+ browser.test.assertEq(
+ "Files to be injected must be within the extension",
+ browser.extension.lastError && browser.extension.lastError.message,
+ "extension.lastError value");
+
+ browser.test.assertEq(
+ "Files to be injected must be within the extension",
+ browser.runtime.lastError && browser.runtime.lastError.message,
+ "runtime.lastError value");
+
+ resolve();
+ });
+ }),
+
+ browser.tabs.executeScript({
+ file: "http://example.com/script.js",
+ }).catch(error => {
+ browser.test.assertTrue(error instanceof Error, "Error is Error");
+
+ browser.test.assertEq(null, browser.extension.lastError,
+ "extension.lastError value");
+
+ browser.test.assertEq(null, browser.runtime.lastError,
+ "runtime.lastError value");
+
+ browser.test.assertEq(
+ "Files to be injected must be within the extension",
+ error && error.message,
+ "error value");
+ }),
+ ];
+
+ await Promise.all(promises);
+
+ browser.test.notifyPass("executeScript-lastError");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["<all_urls>"],
+ },
+
+ background,
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("executeScript-lastError");
+
+ yield extension.unload();
+});
+
+// TODO: Test that |executeScript| fails if the tab has navigated to a
+// new page, and no longer matches our expected state. This involves
+// intentionally trying to trigger a race condition, and is probably not
+// even worth attempting until we have proper |executeScript| callbacks.
+
+add_task(forceGC);
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
new file mode 100644
index 000000000..cf4721310
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
@@ -0,0 +1,189 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+requestLongerTimeout(2);
+
+function* testHasPermission(params) {
+ let contentSetup = params.contentSetup || (() => Promise.resolve());
+
+ async function background(contentSetup) {
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.test.assertEq(msg, "script ran", "script ran");
+ browser.test.notifyPass("executeScript");
+ });
+
+ browser.test.onMessage.addListener(msg => {
+ browser.test.assertEq(msg, "execute-script");
+
+ browser.tabs.executeScript({
+ file: "script.js",
+ });
+ });
+
+ await contentSetup();
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: params.manifest,
+
+ background: `(${background})(${contentSetup})`,
+
+ files: {
+ "script.js": function() {
+ browser.runtime.sendMessage("script ran");
+ },
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ if (params.setup) {
+ yield params.setup(extension);
+ }
+
+ extension.sendMessage("execute-script");
+
+ yield extension.awaitFinish("executeScript");
+
+ if (params.tearDown) {
+ yield params.tearDown(extension);
+ }
+
+ yield extension.unload();
+}
+
+add_task(function* testGoodPermissions() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
+
+ info("Test explicit host permission");
+ yield testHasPermission({
+ manifest: {"permissions": ["http://mochi.test/"]},
+ });
+
+ info("Test explicit host subdomain permission");
+ yield testHasPermission({
+ manifest: {"permissions": ["http://*.mochi.test/"]},
+ });
+
+ info("Test explicit <all_urls> permission");
+ yield testHasPermission({
+ manifest: {"permissions": ["<all_urls>"]},
+ });
+
+ info("Test activeTab permission with a command key press");
+ yield testHasPermission({
+ manifest: {
+ "permissions": ["activeTab"],
+ "commands": {
+ "test-tabs-executeScript": {
+ "suggested_key": {
+ "default": "Alt+Shift+K",
+ },
+ },
+ },
+ },
+ contentSetup() {
+ browser.commands.onCommand.addListener(function(command) {
+ if (command == "test-tabs-executeScript") {
+ browser.test.sendMessage("tabs-command-key-pressed");
+ }
+ });
+ return Promise.resolve();
+ },
+ setup: function* (extension) {
+ yield EventUtils.synthesizeKey("k", {altKey: true, shiftKey: true});
+ yield extension.awaitMessage("tabs-command-key-pressed");
+ },
+ });
+
+ info("Test activeTab permission with a browser action click");
+ yield testHasPermission({
+ manifest: {
+ "permissions": ["activeTab"],
+ "browser_action": {},
+ },
+ contentSetup() {
+ browser.browserAction.onClicked.addListener(() => {
+ browser.test.log("Clicked.");
+ });
+ return Promise.resolve();
+ },
+ setup: clickBrowserAction,
+ tearDown: closeBrowserAction,
+ });
+
+ info("Test activeTab permission with a page action click");
+ yield testHasPermission({
+ manifest: {
+ "permissions": ["activeTab"],
+ "page_action": {},
+ },
+ contentSetup: async () => {
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+ await browser.pageAction.show(tab.id);
+ },
+ setup: clickPageAction,
+ tearDown: closePageAction,
+ });
+
+ info("Test activeTab permission with a browser action w/popup click");
+ yield testHasPermission({
+ manifest: {
+ "permissions": ["activeTab"],
+ "browser_action": {"default_popup": "_blank.html"},
+ },
+ setup: async extension => {
+ await clickBrowserAction(extension);
+ return awaitExtensionPanel(extension, window, "_blank.html");
+ },
+ tearDown: closeBrowserAction,
+ });
+
+ info("Test activeTab permission with a page action w/popup click");
+ yield testHasPermission({
+ manifest: {
+ "permissions": ["activeTab"],
+ "page_action": {"default_popup": "_blank.html"},
+ },
+ contentSetup: async () => {
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+ await browser.pageAction.show(tab.id);
+ },
+ setup: clickPageAction,
+ tearDown: closePageAction,
+ });
+
+ info("Test activeTab permission with a context menu click");
+ yield testHasPermission({
+ manifest: {
+ "permissions": ["activeTab", "contextMenus"],
+ },
+ contentSetup() {
+ browser.contextMenus.create({title: "activeTab", contexts: ["all"]});
+ return Promise.resolve();
+ },
+ setup: function* (extension) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("a[href]", {type: "contextmenu", button: 2},
+ gBrowser.selectedBrowser);
+ yield awaitPopupShown;
+
+ let item = contextMenu.querySelector("[label=activeTab]");
+
+ yield EventUtils.synthesizeMouseAtCenter(item, {}, window);
+
+ yield awaitPopupHidden;
+ },
+ });
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(forceGC);
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js
new file mode 100644
index 000000000..7b2ffe175
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js
@@ -0,0 +1,67 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testExecuteScriptAtOnUpdated() {
+ const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+ const URL = BASE + "file_iframe_document.html";
+ // This is a regression test for bug 1325830.
+ // The bug (executeScript not completing any more) occurred when executeScript
+ // was called early at the onUpdated event, unless the tabs.create method is
+ // called. So this test does not use tabs.create to open new tabs.
+ // Note that if this test is run together with other tests that do call
+ // tabs.create, then this test case does not properly test the conditions of
+ // the regression any more. To verify that the regression has been resolved,
+ // this test must be run in isolation.
+
+ function background() {
+ // Using variables to prevent listeners from running more than once, instead
+ // of removing the listener. This is to minimize any IPC, since the bug that
+ // is being tested is sensitive to timing.
+ let ignore = false;
+ let url;
+ browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
+ if (changeInfo.status === "loading" && tab.url === url && !ignore) {
+ ignore = true;
+ browser.tabs.executeScript(tabId, {
+ code: "document.URL",
+ }).then(results => {
+ browser.test.assertEq(url, results[0], "Content script should run");
+ browser.test.notifyPass("executeScript-at-onUpdated");
+ }, error => {
+ browser.test.fail(`Unexpected error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("executeScript-at-onUpdated");
+ });
+ // (running this log call after executeScript to minimize IPC between
+ // onUpdated and executeScript.)
+ browser.test.log(`Found expected navigation to ${url}`);
+ } else {
+ // The bug occurs when executeScript is called before a tab is
+ // initialized.
+ browser.tabs.executeScript(tabId, {code: ""});
+ }
+ });
+ browser.test.onMessage.addListener(testUrl => {
+ url = testUrl;
+ browser.test.sendMessage("open-test-tab");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["http://mochi.test/", "tabs"],
+ },
+ background,
+ });
+
+ yield extension.startup();
+ extension.sendMessage(URL);
+ yield extension.awaitMessage("open-test-tab");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL, true);
+
+ yield extension.awaitFinish("executeScript-at-onUpdated");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
new file mode 100644
index 000000000..a4c0ed6f1
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
@@ -0,0 +1,107 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/**
+ * These tests ensure that the runAt argument to tabs.executeScript delays
+ * script execution until the document has reached the correct state.
+ *
+ * Since tests of this nature are especially race-prone, it relies on a
+ * server-JS script to delay the completion of our test page's load cycle long
+ * enough for us to attempt to load our scripts in the earlies phase we support.
+ *
+ * And since we can't actually rely on that timing, it retries any attempts that
+ * fail to load as early as expected, but don't load at any illegal time.
+ */
+
+add_task(function* testExecuteScript() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true);
+
+ async function background() {
+ let tab;
+
+ const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+ const URL = BASE + "file_iframe_document.sjs";
+
+ const MAX_TRIES = 10;
+
+ try {
+ [tab] = await browser.tabs.query({active: true, currentWindow: true});
+
+ let success = false;
+ for (let tries = 0; !success && tries < MAX_TRIES; tries++) {
+ let url = `${URL}?r=${Math.random()}`;
+
+ let loadingPromise = new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab_) {
+ if (tabId == tab.id && changed.status == "loading" && tab_.url == url) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+
+ // TODO: Test allFrames and frameId.
+
+ await browser.tabs.update({url});
+ await loadingPromise;
+
+ let states = await Promise.all([
+ // Send the executeScript requests in the reverse order that we expect
+ // them to execute in, to avoid them passing only because of timing
+ // races.
+ browser.tabs.executeScript({
+ code: "document.readyState",
+ runAt: "document_idle",
+ }),
+ browser.tabs.executeScript({
+ code: "document.readyState",
+ runAt: "document_end",
+ }),
+ browser.tabs.executeScript({
+ code: "document.readyState",
+ runAt: "document_start",
+ }),
+ ].reverse());
+
+ browser.test.log(`Got states: ${states}`);
+
+ // Make sure that none of our scripts executed earlier than expected,
+ // regardless of retries.
+ browser.test.assertTrue(states[1] == "interactive" || states[1] == "complete",
+ `document_end state is valid: ${states[1]}`);
+ browser.test.assertTrue(states[2] == "complete",
+ `document_idle state is valid: ${states[2]}`);
+
+ // If we have the earliest valid states for each script, we're done.
+ // Otherwise, try again.
+ success = (states[0] == "loading" &&
+ states[1] == "interactive" &&
+ states[2] == "complete");
+ }
+
+ browser.test.assertTrue(success, "Got the earliest expected states at least once");
+
+ browser.test.notifyPass("executeScript-runAt");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("executeScript-runAt");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["http://mochi.test/", "tabs"],
+ },
+
+ background,
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("executeScript-runAt");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js b/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
new file mode 100644
index 000000000..b67d935cb
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
@@ -0,0 +1,70 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+
+ "browser_action": {"default_popup": "popup.html"},
+ },
+
+ files: {
+ "tab.js": function() {
+ let url = document.location.href;
+
+ browser.tabs.getCurrent(currentTab => {
+ browser.test.assertEq(currentTab.url, url, "getCurrent in non-active background tab");
+
+ // Activate the tab.
+ browser.tabs.onActivated.addListener(function listener({tabId}) {
+ if (tabId == currentTab.id) {
+ browser.tabs.onActivated.removeListener(listener);
+
+ browser.tabs.getCurrent(currentTab => {
+ browser.test.assertEq(currentTab.id, tabId, "in active background tab");
+ browser.test.assertEq(currentTab.url, url, "getCurrent in non-active background tab");
+
+ browser.test.sendMessage("tab-finished");
+ });
+ }
+ });
+ browser.tabs.update(currentTab.id, {active: true});
+ });
+ },
+
+ "popup.js": function() {
+ browser.tabs.getCurrent(tab => {
+ browser.test.assertEq(tab, undefined, "getCurrent in popup script");
+ browser.test.sendMessage("popup-finished");
+ });
+ },
+
+ "tab.html": `<head><meta charset="utf-8"><script src="tab.js"></script></head>`,
+ "popup.html": `<head><meta charset="utf-8"><script src="popup.js"></script></head>`,
+ },
+
+ background: function() {
+ browser.tabs.getCurrent(tab => {
+ browser.test.assertEq(tab, undefined, "getCurrent in background script");
+ browser.test.sendMessage("background-finished");
+ });
+
+ browser.tabs.create({url: "tab.html", active: false});
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitMessage("background-finished");
+ yield extension.awaitMessage("tab-finished");
+
+ clickBrowserAction(extension);
+ yield awaitExtensionPanel(extension);
+ yield extension.awaitMessage("popup-finished");
+ yield closeBrowserAction(extension);
+
+ // The extension tab is automatically closed when the extension unloads.
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js b/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
new file mode 100644
index 000000000..a8e172d94
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
@@ -0,0 +1,86 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testExecuteScript() {
+ let {MessageChannel} = Cu.import("resource://gre/modules/MessageChannel.jsm", {});
+
+ let messageManagersSize = MessageChannel.messageManagers.size;
+ let responseManagersSize = MessageChannel.responseManagers.size;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
+
+ async function background() {
+ let tasks = [
+ {
+ background: "transparent",
+ foreground: "rgb(0, 113, 4)",
+ promise: () => {
+ return browser.tabs.insertCSS({
+ file: "file2.css",
+ });
+ },
+ },
+ {
+ background: "rgb(42, 42, 42)",
+ foreground: "rgb(0, 113, 4)",
+ promise: () => {
+ return browser.tabs.insertCSS({
+ code: "* { background: rgb(42, 42, 42) }",
+ });
+ },
+ },
+ ];
+
+ function checkCSS() {
+ let computedStyle = window.getComputedStyle(document.body);
+ return [computedStyle.backgroundColor, computedStyle.color];
+ }
+
+ try {
+ for (let {promise, background, foreground} of tasks) {
+ let result = await promise();
+
+ browser.test.assertEq(undefined, result, "Expected callback result");
+
+ [result] = await browser.tabs.executeScript({
+ code: `(${checkCSS})()`,
+ });
+
+ browser.test.assertEq(background, result[0], "Expected background color");
+ browser.test.assertEq(foreground, result[1], "Expected foreground color");
+ }
+
+ browser.test.notifyPass("insertCSS");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFailure("insertCSS");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["http://mochi.test/"],
+ },
+
+ background,
+
+ files: {
+ "file2.css": "* { color: rgb(0, 113, 4) }",
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("insertCSS");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Make sure that we're not holding on to references to closed message
+ // managers.
+ is(MessageChannel.messageManagers.size, messageManagersSize, "Message manager count");
+ is(MessageChannel.responseManagers.size, responseManagersSize, "Response manager count");
+ is(MessageChannel.pendingResponses.size, 0, "Pending response count");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_move.js b/browser/components/extensions/test/browser/browser_ext_tabs_move.js
new file mode 100644
index 000000000..917cdc146
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move.js
@@ -0,0 +1,103 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:config");
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: async function() {
+ let [tab] = await browser.tabs.query({lastFocusedWindow: true});
+
+ browser.tabs.move(tab.id, {index: 0});
+ let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+ browser.test.assertEq(tabs[0].url, tab.url, "should be first tab");
+ browser.test.notifyPass("tabs.move.single");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.move.single");
+ yield extension.unload();
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: async function() {
+ let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+ tabs.sort(function(a, b) { return a.url > b.url; });
+
+ browser.tabs.move(tabs.map(tab => tab.id), {index: 0});
+
+ tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+ browser.test.assertEq(tabs[0].url, "about:blank", "should be first tab");
+ browser.test.assertEq(tabs[1].url, "about:config", "should be second tab");
+ browser.test.assertEq(tabs[2].url, "about:robots", "should be third tab");
+
+ browser.test.notifyPass("tabs.move.multiple");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.move.multiple");
+ yield extension.unload();
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ async background() {
+ let [, tab] = await browser.tabs.query({lastFocusedWindow: true});
+
+ // Assuming that tab.id of 12345 does not exist.
+ await browser.test.assertRejects(
+ browser.tabs.move([tab.id, 12345], {index: 0}),
+ /Invalid tab/,
+ "Should receive invalid tab error");
+
+ let tabs = await browser.tabs.query({lastFocusedWindow: true});
+ browser.test.assertEq(tabs[1].url, tab.url, "should be second tab");
+ browser.test.notifyPass("tabs.move.invalid");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.move.invalid");
+ yield extension.unload();
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: async function() {
+ let [tab] = await browser.tabs.query({lastFocusedWindow: true});
+ browser.tabs.move(tab.id, {index: -1});
+
+ let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+ browser.test.assertEq(tabs[2].url, tab.url, "should be last tab");
+ browser.test.notifyPass("tabs.move.last");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.move.last");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js b/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
new file mode 100644
index 000000000..f3bce364a
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
@@ -0,0 +1,98 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+ let window1 = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ async background() {
+ let tabs = await browser.tabs.query({url: "<all_urls>"});
+ let destination = tabs[0];
+ let source = tabs[1]; // skip over about:blank in window1
+
+ // Assuming that this windowId does not exist.
+ await browser.test.assertRejects(
+ browser.tabs.move(source.id, {windowId: 123144576, index: 0}),
+ /Invalid window/,
+ "Should receive invalid window error");
+
+ browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
+
+ tabs = await browser.tabs.query({url: "<all_urls>"});
+ browser.test.assertEq(tabs[0].url, "http://example.com/");
+ browser.test.assertEq(tabs[0].windowId, destination.windowId);
+ browser.test.notifyPass("tabs.move.window");
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.move.window");
+ yield extension.unload();
+
+ for (let tab of window.gBrowser.tabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ yield BrowserTestUtils.closeWindow(window1);
+});
+
+add_task(function* test_currentWindowAfterTabMoved() {
+ const files = {
+ "current.html": "<meta charset=utf-8><script src=current.js></script>",
+ "current.js": function() {
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "current") {
+ browser.windows.getCurrent(win => {
+ browser.test.sendMessage("id", win.id);
+ });
+ }
+ });
+ browser.test.sendMessage("ready");
+ },
+ };
+
+ async function background() {
+ let tabId;
+
+ const url = browser.extension.getURL("current.html");
+
+ browser.test.onMessage.addListener(async msg => {
+ if (msg === "move") {
+ await browser.windows.create({tabId});
+ browser.test.sendMessage("moved");
+ } else if (msg === "close") {
+ await browser.tabs.remove(tabId);
+ browser.test.sendMessage("done");
+ }
+ });
+
+ let tab = await browser.tabs.create({url});
+ tabId = tab.id;
+ }
+
+ const extension = ExtensionTestUtils.loadExtension({files, background});
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ extension.sendMessage("current");
+ const first = yield extension.awaitMessage("id");
+
+ extension.sendMessage("move");
+ yield extension.awaitMessage("moved");
+
+ extension.sendMessage("current");
+ const second = yield extension.awaitMessage("id");
+
+ isnot(first, second, "current window id is different after moving the tab");
+
+ extension.sendMessage("close");
+ yield extension.awaitMessage("done");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_move_window_multiple.js b/browser/components/extensions/test/browser/browser_ext_tabs_move_window_multiple.js
new file mode 100644
index 000000000..dacd547f2
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move_window_multiple.js
@@ -0,0 +1,43 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let window1 = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, "http://example.net/");
+ yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, "http://example.com/");
+ yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.net/");
+ yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query(
+ {url: "<all_urls>"},
+ tabs => {
+ let move1 = tabs[1];
+ let move3 = tabs[3];
+ browser.tabs.move([move1.id, move3.id], {index: 0});
+ browser.tabs.query(
+ {url: "<all_urls>"},
+ tabs => {
+ browser.test.assertEq(tabs[0].url, move1.url);
+ browser.test.assertEq(tabs[2].url, move3.url);
+ browser.test.notifyPass("tabs.move.multiple");
+ });
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.move.multiple");
+ yield extension.unload();
+
+ for (let tab of window.gBrowser.tabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ yield BrowserTestUtils.closeWindow(window1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_move_window_pinned.js b/browser/components/extensions/test/browser/browser_ext_tabs_move_window_pinned.js
new file mode 100644
index 000000000..c592dc56d
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move_window_pinned.js
@@ -0,0 +1,42 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+ let window1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
+ window1.gBrowser.pinTab(tab1);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query(
+ {url: "<all_urls>"},
+ tabs => {
+ let destination = tabs[0];
+ let source = tabs[1]; // remember, pinning moves it to the left.
+ browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
+
+ browser.tabs.query(
+ {url: "<all_urls>"},
+ tabs => {
+ browser.test.assertEq(true, tabs[0].pinned);
+ browser.test.notifyPass("tabs.move.pin");
+ });
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.move.pin");
+ yield extension.unload();
+
+ for (let tab of window.gBrowser.tabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ yield BrowserTestUtils.closeWindow(window1);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_onHighlighted.js b/browser/components/extensions/test/browser/browser_ext_tabs_onHighlighted.js
new file mode 100644
index 000000000..9cc2554d6
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onHighlighted.js
@@ -0,0 +1,126 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testTabEvents() {
+ async function background() {
+ /** The list of active tab ID's */
+ let tabIds = [];
+
+ /**
+ * Stores the events that fire for each tab.
+ *
+ * events {
+ * tabId1: [event1, event2, ...],
+ * tabId2: [event1, event2, ...],
+ * }
+ */
+ let events = {};
+
+ browser.tabs.onActivated.addListener((info) => {
+ if (info.tabId in events) {
+ events[info.tabId].push("onActivated");
+ } else {
+ events[info.tabId] = ["onActivated"];
+ }
+ });
+
+ browser.tabs.onHighlighted.addListener((info) => {
+ if (info.tabIds[0] in events) {
+ events[info.tabIds[0]].push("onHighlighted");
+ } else {
+ events[info.tabIds[0]] = ["onHighlighted"];
+ }
+ });
+
+ /**
+ * Asserts that the expected events are fired for the tab with id = tabId.
+ * The events associated to the specified tab are removed after this check is made.
+ *
+ * @param {number} tabId
+ * @param {Array<string>} expectedEvents
+ */
+ async function expectEvents(tabId, expectedEvents) {
+ browser.test.log(`Expecting events: ${expectedEvents.join(", ")}`);
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ browser.test.assertEq(expectedEvents.length, events[tabId].length,
+ `Got expected number of events for ${tabId}`);
+
+ for (let [i, name] of expectedEvents.entries()) {
+ browser.test.assertEq(name, i in events[tabId] && events[tabId][i],
+ `Got expected ${name} event`);
+ }
+ delete events[tabId];
+ }
+
+ /**
+ * Opens a new tab and asserts that the correct events are fired.
+ *
+ * @param {number} windowId
+ */
+ async function openTab(windowId) {
+ let tab = await browser.tabs.create({windowId});
+
+ tabIds.push(tab.id);
+ browser.test.log(`Opened tab ${tab.id}`);
+
+ await expectEvents(tab.id, [
+ "onActivated",
+ "onHighlighted",
+ ]);
+ }
+
+ /**
+ * Highlights an existing tab and asserts that the correct events are fired.
+ *
+ * @param {number} tabId
+ */
+ async function highlightTab(tabId) {
+ browser.test.log(`Highlighting tab ${tabId}`);
+ let tab = await browser.tabs.update(tabId, {active: true});
+
+ browser.test.assertEq(tab.id, tabId, `Tab ${tab.id} highlighted`);
+
+ await expectEvents(tab.id, [
+ "onActivated",
+ "onHighlighted",
+ ]);
+ }
+
+ /**
+ * The main entry point to the tests.
+ */
+ let tabs = await browser.tabs.query({active: true, currentWindow: true});
+
+ let activeWindow = tabs[0].windowId;
+ await Promise.all([
+ openTab(activeWindow),
+ openTab(activeWindow),
+ openTab(activeWindow),
+ ]);
+
+ await Promise.all([
+ highlightTab(tabIds[0]),
+ highlightTab(tabIds[1]),
+ highlightTab(tabIds[2]),
+ ]);
+
+ await Promise.all(tabIds.map(id => browser.tabs.remove(id)));
+
+ browser.test.notifyPass("tabs.highlight");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.highlight");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
new file mode 100644
index 000000000..2c26bbd16
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
@@ -0,0 +1,198 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+requestLongerTimeout(2);
+
+add_task(function* () {
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ yield focusWindow(win1);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ "content_scripts": [{
+ "matches": ["http://mochi.test/*/context_tabs_onUpdated_page.html"],
+ "js": ["content-script.js"],
+ "run_at": "document_start",
+ }],
+ },
+
+ background: function() {
+ let pageURL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html";
+
+ let expectedSequence = [
+ {status: "loading"},
+ {status: "loading", url: pageURL},
+ {status: "complete"},
+ ];
+ let collectedSequence = [];
+
+ browser.tabs.onUpdated.addListener(function(tabId, updatedInfo) {
+ // onUpdated also fires with updatedInfo.faviconUrl, so explicitly
+ // check for updatedInfo.status before recording the event.
+ if ("status" in updatedInfo) {
+ collectedSequence.push(updatedInfo);
+ }
+ });
+
+ browser.runtime.onMessage.addListener(function() {
+ if (collectedSequence.length !== expectedSequence.length) {
+ browser.test.assertEq(
+ JSON.stringify(expectedSequence),
+ JSON.stringify(collectedSequence),
+ "got unexpected number of updateInfo data"
+ );
+ } else {
+ for (let i = 0; i < expectedSequence.length; i++) {
+ browser.test.assertEq(
+ expectedSequence[i].status,
+ collectedSequence[i].status,
+ "check updatedInfo status"
+ );
+ if (expectedSequence[i].url || collectedSequence[i].url) {
+ browser.test.assertEq(
+ expectedSequence[i].url,
+ collectedSequence[i].url,
+ "check updatedInfo url"
+ );
+ }
+ }
+ }
+
+ browser.test.notifyPass("tabs.onUpdated");
+ });
+
+ browser.tabs.create({url: pageURL});
+ },
+ files: {
+ "content-script.js": `
+ window.addEventListener("message", function(evt) {
+ if (evt.data == "frame-updated") {
+ browser.runtime.sendMessage("load-completed");
+ }
+ }, true);
+ `,
+ },
+ });
+
+ yield Promise.all([
+ extension.startup(),
+ extension.awaitFinish("tabs.onUpdated"),
+ ]);
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.closeWindow(win1);
+});
+
+function* do_test_update(background, withPermissions = true) {
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ yield focusWindow(win1);
+
+ let manifest = {};
+ if (withPermissions) {
+ manifest.permissions = ["tabs"];
+ }
+ let extension = ExtensionTestUtils.loadExtension({manifest, background});
+
+ yield Promise.all([
+ yield extension.startup(),
+ yield extension.awaitFinish("finish"),
+ ]);
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.closeWindow(win1);
+}
+
+add_task(function* test_pinned() {
+ yield do_test_update(function background() {
+ // Create a new tab for testing update.
+ browser.tabs.create({}, function(tab) {
+ browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
+ // Check callback
+ browser.test.assertEq(tabId, tab.id, "Check tab id");
+ browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
+ if ("pinned" in changeInfo) {
+ browser.test.assertTrue(changeInfo.pinned, "Check changeInfo.pinned");
+ browser.tabs.onUpdated.removeListener(onUpdated);
+ // Remove created tab.
+ browser.tabs.remove(tabId);
+ browser.test.notifyPass("finish");
+ return;
+ }
+ });
+ browser.tabs.update(tab.id, {pinned: true});
+ });
+ });
+});
+
+add_task(function* test_unpinned() {
+ yield do_test_update(function background() {
+ // Create a new tab for testing update.
+ browser.tabs.create({pinned: true}, function(tab) {
+ browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
+ // Check callback
+ browser.test.assertEq(tabId, tab.id, "Check tab id");
+ browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
+ if ("pinned" in changeInfo) {
+ browser.test.assertFalse(changeInfo.pinned, "Check changeInfo.pinned");
+ browser.tabs.onUpdated.removeListener(onUpdated);
+ // Remove created tab.
+ browser.tabs.remove(tabId);
+ browser.test.notifyPass("finish");
+ return;
+ }
+ });
+ browser.tabs.update(tab.id, {pinned: false});
+ });
+ });
+});
+
+add_task(function* test_url() {
+ yield do_test_update(function background() {
+ // Create a new tab for testing update.
+ browser.tabs.create({}, function(tab) {
+ browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
+ // Check callback
+ browser.test.assertEq(tabId, tab.id, "Check tab id");
+ browser.test.log("onUpdate: " + JSON.stringify(changeInfo));
+ if ("url" in changeInfo) {
+ browser.test.assertEq("about:blank", changeInfo.url,
+ "Check changeInfo.url");
+ browser.tabs.onUpdated.removeListener(onUpdated);
+ // Remove created tab.
+ browser.tabs.remove(tabId);
+ browser.test.notifyPass("finish");
+ return;
+ }
+ });
+ browser.tabs.update(tab.id, {url: "about:blank"});
+ });
+ });
+});
+
+add_task(function* test_without_tabs_permission() {
+ yield do_test_update(function background() {
+ browser.tabs.create({url: "about:blank"}, function(tab) {
+ browser.tabs.onUpdated.addListener(function onUpdated(tabId, changeInfo) {
+ if (tabId == tab.id) {
+ browser.test.assertFalse("url" in changeInfo, "url should not be included without tabs permission");
+ browser.test.assertFalse("favIconUrl" in changeInfo, "favIconUrl should not be included without tabs permission");
+
+ if (changeInfo.status == "complete") {
+ browser.tabs.onUpdated.removeListener(onUpdated);
+ browser.tabs.remove(tabId);
+ browser.test.notifyPass("finish");
+ }
+ }
+ });
+ browser.tabs.reload(tab.id);
+ });
+ }, false /* withPermissions */);
+});
+
+add_task(forceGC);
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_query.js b/browser/components/extensions/test/browser/browser_ext_tabs_query.js
new file mode 100644
index 000000000..7804d1454
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_query.js
@@ -0,0 +1,224 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+requestLongerTimeout(2);
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:config");
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query({
+ lastFocusedWindow: true,
+ }, function(tabs) {
+ browser.test.assertEq(tabs.length, 3, "should have three tabs");
+
+ tabs.sort((tab1, tab2) => tab1.index - tab2.index);
+
+ browser.test.assertEq(tabs[0].url, "about:blank", "first tab blank");
+ tabs.shift();
+
+ browser.test.assertTrue(tabs[0].active, "tab 0 active");
+ browser.test.assertFalse(tabs[1].active, "tab 1 inactive");
+
+ browser.test.assertFalse(tabs[0].pinned, "tab 0 unpinned");
+ browser.test.assertFalse(tabs[1].pinned, "tab 1 unpinned");
+
+ browser.test.assertEq(tabs[0].url, "about:robots", "tab 0 url correct");
+ browser.test.assertEq(tabs[1].url, "about:config", "tab 1 url correct");
+
+ browser.test.assertEq(tabs[0].status, "complete", "tab 0 status correct");
+ browser.test.assertEq(tabs[1].status, "complete", "tab 1 status correct");
+
+ browser.test.assertEq(tabs[0].title, "Gort! Klaatu barada nikto!", "tab 0 title correct");
+
+ browser.test.notifyPass("tabs.query");
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.query");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+
+ tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+ tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+ let tab3 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://test1.example.org/MochiKit/");
+
+ // test simple queries
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query({
+ url: "<all_urls>",
+ }, function(tabs) {
+ browser.test.assertEq(tabs.length, 3, "should have three tabs");
+
+ tabs.sort((tab1, tab2) => tab1.index - tab2.index);
+
+ browser.test.assertEq(tabs[0].url, "http://example.com/", "tab 0 url correct");
+ browser.test.assertEq(tabs[1].url, "http://example.net/", "tab 1 url correct");
+ browser.test.assertEq(tabs[2].url, "http://test1.example.org/MochiKit/", "tab 2 url correct");
+
+ browser.test.notifyPass("tabs.query");
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.query");
+ yield extension.unload();
+
+ // match pattern
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query({
+ url: "http://*/MochiKit*",
+ }, function(tabs) {
+ browser.test.assertEq(tabs.length, 1, "should have one tab");
+
+ browser.test.assertEq(tabs[0].url, "http://test1.example.org/MochiKit/", "tab 0 url correct");
+
+ browser.test.notifyPass("tabs.query");
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.query");
+ yield extension.unload();
+
+ // match array of patterns
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query({
+ url: ["http://*/MochiKit*", "http://*.com/*"],
+ }, function(tabs) {
+ browser.test.assertEq(tabs.length, 2, "should have two tabs");
+
+ tabs.sort((tab1, tab2) => tab1.index - tab2.index);
+
+ browser.test.assertEq(tabs[0].url, "http://example.com/", "tab 0 url correct");
+ browser.test.assertEq(tabs[1].url, "http://test1.example.org/MochiKit/", "tab 1 url correct");
+
+ browser.test.notifyPass("tabs.query");
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.query");
+ yield extension.unload();
+
+ // test width and height
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.test.onMessage.addListener(async msg => {
+ let tabs = await browser.tabs.query({active: true});
+
+ browser.test.assertEq(tabs.length, 1, "should have one tab");
+ browser.test.sendMessage("dims", {width: tabs[0].width, height: tabs[0].height});
+ });
+ browser.test.sendMessage("ready");
+ },
+ });
+
+ const RESOLUTION_PREF = "layout.css.devPixelsPerPx";
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref(RESOLUTION_PREF);
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
+
+ for (let resolution of [2, 1]) {
+ SpecialPowers.setCharPref(RESOLUTION_PREF, String(resolution));
+ is(window.devicePixelRatio, resolution, "window has the required resolution");
+
+ let {clientHeight, clientWidth} = gBrowser.selectedBrowser;
+
+ extension.sendMessage("check-size");
+ let dims = yield extension.awaitMessage("dims");
+ is(dims.width, clientWidth, "tab reports expected width");
+ is(dims.height, clientHeight, "tab reports expected height");
+ }
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+ yield BrowserTestUtils.removeTab(tab3);
+ SpecialPowers.clearUserPref(RESOLUTION_PREF);
+});
+
+add_task(function* testQueryPermissions() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": [],
+ },
+
+ async background() {
+ try {
+ let tabs = await browser.tabs.query({currentWindow: true, active: true});
+ browser.test.assertEq(tabs.length, 1, "Expect query to return tabs");
+ browser.test.notifyPass("queryPermissions");
+ } catch (e) {
+ browser.test.notifyFail("queryPermissions");
+ }
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("queryPermissions");
+
+ yield extension.unload();
+});
+
+add_task(function* testQueryWithURLPermissions() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": [],
+ },
+
+ async background() {
+ await browser.test.assertRejects(
+ browser.tabs.query({"url": "http://www.bbc.com/"}),
+ 'The "tabs" permission is required to use the query API with the "url" parameter',
+ "Expected tabs.query with 'url' to fail with permissions error message");
+
+ browser.test.notifyPass("queryWithURLPermissions");
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("queryWithURLPermissions");
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_reload.js b/browser/components/extensions/test/browser/browser_ext_tabs_reload.js
new file mode 100644
index 000000000..99b2d426b
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_reload.js
@@ -0,0 +1,54 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ files: {
+ "tab.js": function() {
+ browser.runtime.sendMessage("tab-loaded");
+ },
+ "tab.html":
+ `<head>
+ <meta charset="utf-8">
+ <script src="tab.js"></script>
+ </head>`,
+ },
+
+ async background() {
+ let tabLoadedCount = 0;
+
+ let tab = await browser.tabs.create({url: "tab.html", active: true});
+
+ browser.runtime.onMessage.addListener(msg => {
+ if (msg == "tab-loaded") {
+ tabLoadedCount++;
+
+ if (tabLoadedCount == 1) {
+ // Reload the tab once passing no arguments.
+ return browser.tabs.reload();
+ }
+
+ if (tabLoadedCount == 2) {
+ // Reload the tab again with explicit arguments.
+ return browser.tabs.reload(tab.id, {
+ bypassCache: false,
+ });
+ }
+
+ if (tabLoadedCount == 3) {
+ browser.test.notifyPass("tabs.reload");
+ }
+ }
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.reload");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js b/browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js
new file mode 100644
index 000000000..648361724
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_reload_bypass_cache.js
@@ -0,0 +1,58 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs", "<all_urls>"],
+ },
+
+ async background() {
+ const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+ const URL = BASE + "file_bypass_cache.sjs";
+
+ function awaitLoad(tabId) {
+ return new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId_, changed, tab) {
+ if (tabId == tabId_ && changed.status == "complete" && tab.url == URL) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+ }
+
+ try {
+ let tab = await browser.tabs.create({url: URL});
+ await awaitLoad(tab.id);
+
+ await browser.tabs.reload(tab.id, {bypassCache: false});
+ await awaitLoad(tab.id);
+
+ let [textContent] = await browser.tabs.executeScript(tab.id, {code: "document.body.textContent"});
+ browser.test.assertEq("", textContent, "`textContent` should be empty when bypassCache=false");
+
+ await browser.tabs.reload(tab.id, {bypassCache: true});
+ await awaitLoad(tab.id);
+
+ [textContent] = await browser.tabs.executeScript(tab.id, {code: "document.body.textContent"});
+
+ let [pragma, cacheControl] = textContent.split(":");
+ browser.test.assertEq("no-cache", pragma, "`pragma` should be set to `no-cache` when bypassCache is true");
+ browser.test.assertEq("no-cache", cacheControl, "`cacheControl` should be set to `no-cache` when bypassCache is true");
+
+ await browser.tabs.remove(tab.id);
+
+ browser.test.notifyPass("tabs.reload_bypass_cache");
+ } catch (error) {
+ browser.test.fail(`${error} :: ${error.stack}`);
+ browser.test.notifyFail("tabs.reload_bypass_cache");
+ }
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("tabs.reload_bypass_cache");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js b/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
new file mode 100644
index 000000000..e0eadab64
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
@@ -0,0 +1,95 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testExecuteScript() {
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
+
+ async function background() {
+ let tasks = [
+ // Insert CSS file.
+ {
+ background: "transparent",
+ foreground: "rgb(0, 113, 4)",
+ promise: () => {
+ return browser.tabs.insertCSS({
+ file: "file2.css",
+ });
+ },
+ },
+ // Insert CSS code.
+ {
+ background: "rgb(42, 42, 42)",
+ foreground: "rgb(0, 113, 4)",
+ promise: () => {
+ return browser.tabs.insertCSS({
+ code: "* { background: rgb(42, 42, 42) }",
+ });
+ },
+ },
+ // Remove CSS code again.
+ {
+ background: "transparent",
+ foreground: "rgb(0, 113, 4)",
+ promise: () => {
+ return browser.tabs.removeCSS({
+ code: "* { background: rgb(42, 42, 42) }",
+ });
+ },
+ },
+ // Remove CSS file again.
+ {
+ background: "transparent",
+ foreground: "rgb(0, 0, 0)",
+ promise: () => {
+ return browser.tabs.removeCSS({
+ file: "file2.css",
+ });
+ },
+ },
+ ];
+
+ function checkCSS() {
+ let computedStyle = window.getComputedStyle(document.body);
+ return [computedStyle.backgroundColor, computedStyle.color];
+ }
+
+ try {
+ for (let {promise, background, foreground} of tasks) {
+ let result = await promise();
+ browser.test.assertEq(undefined, result, "Expected callback result");
+
+ [result] = await browser.tabs.executeScript({
+ code: `(${checkCSS})()`,
+ });
+ browser.test.assertEq(background, result[0], "Expected background color");
+ browser.test.assertEq(foreground, result[1], "Expected foreground color");
+ }
+
+ browser.test.notifyPass("removeCSS");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFailure("removeCSS");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["http://mochi.test/"],
+ },
+
+ background,
+
+ files: {
+ "file2.css": "* { color: rgb(0, 113, 4) }",
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("removeCSS");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js b/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
new file mode 100644
index 000000000..64e97afb1
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
@@ -0,0 +1,227 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* tabsSendMessageReply() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+
+ "content_scripts": [{
+ "matches": ["http://example.com/"],
+ "js": ["content-script.js"],
+ "run_at": "document_start",
+ }],
+ },
+
+ background: async function() {
+ let firstTab;
+ let promiseResponse = new Promise(resolve => {
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg == "content-script-ready") {
+ let tabId = sender.tab.id;
+
+ Promise.all([
+ promiseResponse,
+
+ browser.tabs.sendMessage(tabId, "respond-now"),
+ browser.tabs.sendMessage(tabId, "respond-now-2"),
+ new Promise(resolve => browser.tabs.sendMessage(tabId, "respond-soon", resolve)),
+ browser.tabs.sendMessage(tabId, "respond-promise"),
+ browser.tabs.sendMessage(tabId, "respond-never"),
+ new Promise(resolve => {
+ browser.runtime.sendMessage("respond-never", response => { resolve(response); });
+ }),
+
+ browser.tabs.sendMessage(tabId, "respond-error").catch(error => Promise.resolve({error})),
+ browser.tabs.sendMessage(tabId, "throw-error").catch(error => Promise.resolve({error})),
+
+ browser.tabs.sendMessage(firstTab, "no-listener").catch(error => Promise.resolve({error})),
+ ]).then(([response, respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondNever2, respondError, throwError, noListener]) => {
+ browser.test.assertEq("expected-response", response, "Content script got the expected response");
+
+ browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response");
+ browser.test.assertEq("respond-now-2", respondNow2, "Got the expected immediate response from the second listener");
+ browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response");
+ browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response");
+ browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution");
+ browser.test.assertEq(undefined, respondNever2, "Got the expected no-response resolution");
+
+ browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response");
+ browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response");
+
+ browser.test.assertEq("Could not establish connection. Receiving end does not exist.",
+ noListener.error.message,
+ "Got the expected no listener response");
+
+ return browser.tabs.remove(tabId);
+ }).then(() => {
+ browser.test.notifyPass("sendMessage");
+ });
+
+ return Promise.resolve("expected-response");
+ } else if (msg[0] == "got-response") {
+ resolve(msg[1]);
+ }
+ });
+ });
+
+ let tabs = await browser.tabs.query({currentWindow: true, active: true});
+ firstTab = tabs[0].id;
+ browser.tabs.create({url: "http://example.com/"});
+ },
+
+ files: {
+ "content-script.js": async function() {
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg == "respond-now") {
+ respond(msg);
+ } else if (msg == "respond-soon") {
+ setTimeout(() => { respond(msg); }, 0);
+ return true;
+ } else if (msg == "respond-promise") {
+ return Promise.resolve(msg);
+ } else if (msg == "respond-never") {
+ return;
+ } else if (msg == "respond-error") {
+ return Promise.reject(new Error(msg));
+ } else if (msg == "throw-error") {
+ throw new Error(msg);
+ }
+ });
+
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg == "respond-now") {
+ respond("hello");
+ } else if (msg == "respond-now-2") {
+ respond(msg);
+ }
+ });
+
+ let response = await browser.runtime.sendMessage("content-script-ready");
+ browser.runtime.sendMessage(["got-response", response]);
+ },
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("sendMessage");
+
+ yield extension.unload();
+});
+
+
+add_task(function* tabsSendHidden() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+
+ "content_scripts": [{
+ "matches": ["http://example.com/content*"],
+ "js": ["content-script.js"],
+ "run_at": "document_start",
+ }],
+ },
+
+ background: async function() {
+ let resolveContent;
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ if (msg[0] == "content-ready") {
+ resolveContent(msg[1]);
+ }
+ });
+
+ let awaitContent = url => {
+ return new Promise(resolve => {
+ resolveContent = resolve;
+ }).then(result => {
+ browser.test.assertEq(url, result, "Expected content script URL");
+ });
+ };
+
+ try {
+ const URL1 = "http://example.com/content1.html";
+ const URL2 = "http://example.com/content2.html";
+
+ let tab = await browser.tabs.create({url: URL1});
+ await awaitContent(URL1);
+
+ let url = await browser.tabs.sendMessage(tab.id, URL1);
+ browser.test.assertEq(URL1, url, "Should get response from expected content window");
+
+ await browser.tabs.update(tab.id, {url: URL2});
+ await awaitContent(URL2);
+
+ url = await browser.tabs.sendMessage(tab.id, URL2);
+ browser.test.assertEq(URL2, url, "Should get response from expected content window");
+
+ // Repeat once just to be sure the first message was processed by all
+ // listeners before we exit the test.
+ url = await browser.tabs.sendMessage(tab.id, URL2);
+ browser.test.assertEq(URL2, url, "Should get response from expected content window");
+
+ await browser.tabs.remove(tab.id);
+
+ browser.test.notifyPass("contentscript-bfcache-window");
+ } catch (error) {
+ browser.test.fail(`Error: ${error} :: ${error.stack}`);
+ browser.test.notifyFail("contentscript-bfcache-window");
+ }
+ },
+
+ files: {
+ "content-script.js": function() {
+ // Store this in a local variable to make sure we don't touch any
+ // properties of the possibly-hidden content window.
+ let href = window.location.href;
+
+ browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.test.assertEq(href, msg, "Should be in the expected content window");
+
+ return Promise.resolve(href);
+ });
+
+ browser.runtime.sendMessage(["content-ready", href]);
+ },
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("contentscript-bfcache-window");
+
+ yield extension.unload();
+});
+
+
+add_task(function* tabsSendMessageNoExceptionOnNonExistentTab() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ async background() {
+ let url = "http://example.com/mochitest/browser/browser/components/extensions/test/browser/file_dummy.html";
+ let tab = await browser.tabs.create({url});
+
+ try {
+ browser.tabs.sendMessage(tab.id, "message");
+ browser.tabs.sendMessage(tab.id + 100, "message");
+ } catch (e) {
+ browser.test.fail("no exception should be raised on tabs.sendMessage to nonexistent tabs");
+ }
+
+ await browser.tabs.remove(tab.id);
+
+ browser.test.notifyPass("tabs.sendMessage");
+ },
+ });
+
+ yield Promise.all([
+ extension.startup(),
+ extension.awaitFinish("tabs.sendMessage"),
+ ]);
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_update.js b/browser/components/extensions/test/browser/browser_ext_tabs_update.js
new file mode 100644
index 000000000..8e56a746c
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_update.js
@@ -0,0 +1,45 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:config");
+
+ gBrowser.selectedTab = tab1;
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background: function() {
+ browser.tabs.query({
+ lastFocusedWindow: true,
+ }, function(tabs) {
+ browser.test.assertEq(tabs.length, 3, "should have three tabs");
+
+ tabs.sort((tab1, tab2) => tab1.index - tab2.index);
+
+ browser.test.assertEq(tabs[0].url, "about:blank", "first tab blank");
+ tabs.shift();
+
+ browser.test.assertTrue(tabs[0].active, "tab 0 active");
+ browser.test.assertFalse(tabs[1].active, "tab 1 inactive");
+
+ browser.tabs.update(tabs[1].id, {active: true}, function() {
+ browser.test.sendMessage("check");
+ });
+ });
+ },
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("check")]);
+
+ ok(gBrowser.selectedTab == tab2, "correct tab selected");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js b/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
new file mode 100644
index 000000000..b43855fb1
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
@@ -0,0 +1,110 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* testTabsUpdateURL(existentTabURL, tabsUpdateURL, isErrorExpected) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ files: {
+ "tab.html": `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <h1>tab page</h1>
+ </body>
+ </html>
+ `.trim(),
+ },
+ background: function() {
+ browser.test.sendMessage("ready", browser.runtime.getURL("tab.html"));
+
+ browser.test.onMessage.addListener(async (msg, tabsUpdateURL, isErrorExpected) => {
+ let tabs = await browser.tabs.query({lastFocusedWindow: true});
+
+ try {
+ let tab = await browser.tabs.update(tabs[1].id, {url: tabsUpdateURL});
+
+ browser.test.assertFalse(isErrorExpected, `tabs.update with URL ${tabsUpdateURL} should be rejected`);
+ browser.test.assertTrue(tab, "on success the tab should be defined");
+ } catch (error) {
+ browser.test.assertTrue(isErrorExpected, `tabs.update with URL ${tabsUpdateURL} should not be rejected`);
+ browser.test.assertTrue(/^Illegal URL/.test(error.message),
+ "tabs.update should be rejected with the expected error message");
+ }
+
+ browser.test.sendMessage("done");
+ });
+ },
+ });
+
+ yield extension.startup();
+
+ let mozExtTabURL = yield extension.awaitMessage("ready");
+
+ if (tabsUpdateURL == "self") {
+ tabsUpdateURL = mozExtTabURL;
+ }
+
+ info(`tab.update URL "${tabsUpdateURL}" on tab with URL "${existentTabURL}"`);
+
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, existentTabURL);
+
+ extension.sendMessage("start", tabsUpdateURL, isErrorExpected);
+ yield extension.awaitMessage("done");
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield extension.unload();
+}
+
+add_task(function* () {
+ info("Start testing tabs.update on javascript URLs");
+
+ let dataURLPage = `data:text/html,
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <h1>data url page</h1>
+ </body>
+ </html>`;
+
+ let checkList = [
+ {
+ tabsUpdateURL: "http://example.net",
+ isErrorExpected: false,
+ },
+ {
+ tabsUpdateURL: "self",
+ isErrorExpected: false,
+ },
+ {
+ tabsUpdateURL: "about:addons",
+ isErrorExpected: true,
+ },
+ {
+ tabsUpdateURL: "javascript:console.log('tabs.update execute javascript')",
+ isErrorExpected: true,
+ },
+ {
+ tabsUpdateURL: dataURLPage,
+ isErrorExpected: true,
+ },
+ ];
+
+ let testCases = checkList
+ .map((check) => Object.assign({}, check, {existentTabURL: "about:blank"}));
+
+ for (let {existentTabURL, tabsUpdateURL, isErrorExpected} of testCases) {
+ yield* testTabsUpdateURL(existentTabURL, tabsUpdateURL, isErrorExpected);
+ }
+
+ info("done");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
new file mode 100644
index 000000000..c2e54d3ea
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
@@ -0,0 +1,222 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const SITE_SPECIFIC_PREF = "browser.zoom.siteSpecific";
+
+add_task(function* () {
+ let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+ let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
+
+ gBrowser.selectedTab = tab1;
+
+ async function background() {
+ function promiseUpdated(tabId, attr) {
+ return new Promise(resolve => {
+ let onUpdated = (tabId_, changeInfo, tab) => {
+ if (tabId == tabId_ && attr in changeInfo) {
+ browser.tabs.onUpdated.removeListener(onUpdated);
+
+ resolve({changeInfo, tab});
+ }
+ };
+ browser.tabs.onUpdated.addListener(onUpdated);
+ });
+ }
+
+ let deferred = {};
+ browser.test.onMessage.addListener((message, msg, result) => {
+ if (message == "msg-done" && deferred[msg]) {
+ deferred[msg].resolve(result);
+ }
+ });
+
+ let _id = 0;
+ function msg(...args) {
+ return new Promise((resolve, reject) => {
+ let id = ++_id;
+ deferred[id] = {resolve, reject};
+ browser.test.sendMessage("msg", id, ...args);
+ });
+ }
+
+
+ let zoomEvents = [];
+ let eventPromises = [];
+ browser.tabs.onZoomChange.addListener(info => {
+ zoomEvents.push(info);
+ if (eventPromises.length) {
+ eventPromises.shift().resolve();
+ }
+ });
+
+ let awaitZoom = async (tabId, newValue) => {
+ let listener;
+
+ await new Promise(async resolve => {
+ listener = info => {
+ if (info.tabId == tabId && info.newZoomFactor == newValue) {
+ resolve();
+ }
+ };
+ browser.tabs.onZoomChange.addListener(listener);
+
+ let zoomFactor = await browser.tabs.getZoom(tabId);
+ if (zoomFactor == newValue) {
+ resolve();
+ }
+ });
+
+ browser.tabs.onZoomChange.removeListener(listener);
+ };
+
+ let checkZoom = async (tabId, newValue, oldValue = null) => {
+ let awaitEvent;
+ if (oldValue != null && !zoomEvents.length) {
+ awaitEvent = new Promise(resolve => {
+ eventPromises.push({resolve});
+ });
+ }
+
+ let [apiZoom, realZoom] = await Promise.all([
+ browser.tabs.getZoom(tabId),
+ msg("get-zoom", tabId),
+ awaitEvent,
+ ]);
+
+ browser.test.assertEq(newValue, apiZoom, `Got expected zoom value from API`);
+ browser.test.assertEq(newValue, realZoom, `Got expected zoom value from parent`);
+
+ if (oldValue != null) {
+ let event = zoomEvents.shift();
+ browser.test.assertEq(tabId, event.tabId, `Got expected zoom event tab ID`);
+ browser.test.assertEq(newValue, event.newZoomFactor, `Got expected zoom event zoom factor`);
+ browser.test.assertEq(oldValue, event.oldZoomFactor, `Got expected zoom event old zoom factor`);
+
+ browser.test.assertEq(3, Object.keys(event.zoomSettings).length, `Zoom settings should have 3 keys`);
+ browser.test.assertEq("automatic", event.zoomSettings.mode, `Mode should be "automatic"`);
+ browser.test.assertEq("per-origin", event.zoomSettings.scope, `Scope should be "per-origin"`);
+ browser.test.assertEq(1, event.zoomSettings.defaultZoomFactor, `Default zoom should be 1`);
+ }
+ };
+
+ try {
+ let tabs = await browser.tabs.query({lastFocusedWindow: true});
+ browser.test.assertEq(tabs.length, 3, "We have three tabs");
+
+ let tabIds = [tabs[1].id, tabs[2].id];
+ await checkZoom(tabIds[0], 1);
+
+ await browser.tabs.setZoom(tabIds[0], 2);
+ await checkZoom(tabIds[0], 2, 1);
+
+ let zoomSettings = await browser.tabs.getZoomSettings(tabIds[0]);
+ browser.test.assertEq(3, Object.keys(zoomSettings).length, `Zoom settings should have 3 keys`);
+ browser.test.assertEq("automatic", zoomSettings.mode, `Mode should be "automatic"`);
+ browser.test.assertEq("per-origin", zoomSettings.scope, `Scope should be "per-origin"`);
+ browser.test.assertEq(1, zoomSettings.defaultZoomFactor, `Default zoom should be 1`);
+
+
+ browser.test.log(`Switch to tab 2`);
+ await browser.tabs.update(tabIds[1], {active: true});
+ await checkZoom(tabIds[1], 1);
+
+
+ browser.test.log(`Navigate tab 2 to origin of tab 1`);
+ browser.tabs.update(tabIds[1], {url: "http://example.com"});
+ await promiseUpdated(tabIds[1], "url");
+ await checkZoom(tabIds[1], 2, 1);
+
+
+ browser.test.log(`Update zoom in tab 2, expect changes in both tabs`);
+ await browser.tabs.setZoom(tabIds[1], 1.5);
+ await checkZoom(tabIds[1], 1.5, 2);
+
+
+ browser.test.log(`Switch to tab 1, expect asynchronous zoom change just after the switch`);
+ await Promise.all([
+ awaitZoom(tabIds[0], 1.5),
+ browser.tabs.update(tabIds[0], {active: true}),
+ ]);
+ await checkZoom(tabIds[0], 1.5, 2);
+
+
+ browser.test.log("Set zoom to 0, expect it set to 1");
+ await browser.tabs.setZoom(tabIds[0], 0);
+ await checkZoom(tabIds[0], 1, 1.5);
+
+
+ browser.test.log("Change zoom externally, expect changes reflected");
+ await msg("enlarge");
+ await checkZoom(tabIds[0], 1.1, 1);
+
+ await Promise.all([
+ browser.tabs.setZoom(tabIds[0], 0),
+ browser.tabs.setZoom(tabIds[1], 0),
+ ]);
+ await Promise.all([
+ checkZoom(tabIds[0], 1, 1.1),
+ checkZoom(tabIds[1], 1, 1.5),
+ ]);
+
+
+ browser.test.log("Check that invalid zoom values throw an error");
+ await browser.test.assertRejects(
+ browser.tabs.setZoom(tabIds[0], 42),
+ /Zoom value 42 out of range/,
+ "Expected an out of range error");
+
+ browser.test.log("Disable site-specific zoom, expect correct scope");
+ await msg("site-specific", false);
+ zoomSettings = await browser.tabs.getZoomSettings(tabIds[0]);
+
+ browser.test.assertEq("per-tab", zoomSettings.scope, `Scope should be "per-tab"`);
+ await msg("site-specific", null);
+
+ browser.test.notifyPass("tab-zoom");
+ } catch (e) {
+ browser.test.fail(`Error: ${e} :: ${e.stack}`);
+ browser.test.notifyFail("tab-zoom");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ extension.onMessage("msg", (id, msg, ...args) => {
+ let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let resp;
+ if (msg == "get-zoom") {
+ let tab = TabManager.getTab(args[0]);
+ resp = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+ } else if (msg == "set-zoom") {
+ let tab = TabManager.getTab(args[0]);
+ ZoomManager.setZoomForBrowser(tab.linkedBrowser);
+ } else if (msg == "enlarge") {
+ FullZoom.enlarge();
+ } else if (msg == "site-specific") {
+ if (args[0] == null) {
+ SpecialPowers.clearUserPref(SITE_SPECIFIC_PREF);
+ } else {
+ SpecialPowers.setBoolPref(SITE_SPECIFIC_PREF, args[0]);
+ }
+ }
+
+ extension.sendMessage("msg-done", id, resp);
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitFinish("tab-zoom");
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_topwindowid.js b/browser/components/extensions/test/browser/browser_ext_topwindowid.js
new file mode 100644
index 000000000..9176ac946
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_topwindowid.js
@@ -0,0 +1,23 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_topwindowid_cleanup() {
+ let {Frames} = Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+ let {outerWindowID, messageManager} = tab.linkedBrowser;
+
+ ok(Frames.topWindowIds.has(outerWindowID), "Outer window ID is registered");
+
+ let awaitDisconnect = TestUtils.topicObserved("message-manager-disconnect",
+ subject => subject === messageManager);
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ yield awaitDisconnect;
+
+ ok(!Frames.topWindowIds.has(outerWindowID), "Outer window ID is no longer registered");
+});
+
diff --git a/browser/components/extensions/test/browser/browser_ext_webNavigation_frameId0.js b/browser/components/extensions/test/browser/browser_ext_webNavigation_frameId0.js
new file mode 100644
index 000000000..0058ca065
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_frameId0.js
@@ -0,0 +1,45 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* webNavigation_getFrameId_of_existing_main_frame() {
+ // Whether the frame ID in the extension API is 0 is determined by a map that
+ // is maintained by |Frames| in ExtensionManagement.jsm. This map is filled
+ // using data from content processes. But if ExtensionManagement.jsm is not
+ // imported, then the "Extension:TopWindowID" message gets lost.
+ // As a result, if the state is not synchronized again, the webNavigation API
+ // will mistakenly report a non-zero frame ID for top-level frames.
+ //
+ // If you want to be absolutely sure that the frame ID is correct, don't open
+ // tabs before starting an extension, or explicitly load the module in the
+ // main process:
+ // Cu.import("resource://gre/modules/ExtensionManagement.jsm", {});
+ //
+ // Or simply run the test again.
+ const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
+ const DUMMY_URL = BASE + "file_dummy.html";
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_URL, true);
+
+ async function background(DUMMY_URL) {
+ let tabs = await browser.tabs.query({active: true, currentWindow: true});
+ let frames = await browser.webNavigation.getAllFrames({tabId: tabs[0].id});
+ browser.test.assertEq(1, frames.length, "The dummy page has one frame");
+ browser.test.assertEq(0, frames[0].frameId, "Main frame's ID must be 0");
+ browser.test.assertEq(DUMMY_URL, frames[0].url, "Main frame URL must match");
+ browser.test.notifyPass("frameId checked");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["webNavigation"],
+ },
+
+ background: `(${background})(${JSON.stringify(DUMMY_URL)});`,
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("frameId checked");
+ yield extension.unload();
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
new file mode 100644
index 000000000..6b4a597ad
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
@@ -0,0 +1,168 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testWebNavigationGetNonExistentTab() {
+ let extension = ExtensionTestUtils.loadExtension({
+ background: async function() {
+ // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+ // starts from 1.
+ await browser.test.assertRejects(
+ browser.webNavigation.getAllFrames({tabId: 0}),
+ "Invalid tab ID: 0",
+ "getAllFrames rejected Promise should pass the expected error");
+
+ // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+ // starts from 1, processId is currently marked as optional and it is ignored.
+ await browser.test.assertRejects(
+ browser.webNavigation.getFrame({tabId: 0, frameId: 15, processId: 20}),
+ "Invalid tab ID: 0",
+ "getFrame rejected Promise should pass the expected error");
+
+ browser.test.sendMessage("getNonExistentTab.done");
+ },
+ manifest: {
+ permissions: ["webNavigation"],
+ },
+ });
+ info("load complete");
+
+ yield extension.startup();
+ info("startup complete");
+
+ yield extension.awaitMessage("getNonExistentTab.done");
+
+ yield extension.unload();
+ info("extension unloaded");
+});
+
+add_task(function* testWebNavigationFrames() {
+ let extension = ExtensionTestUtils.loadExtension({
+ background: async function() {
+ let tabId;
+ let collectedDetails = [];
+
+ browser.webNavigation.onCompleted.addListener(async details => {
+ collectedDetails.push(details);
+
+ if (details.frameId !== 0) {
+ // wait for the top level iframe to be complete
+ return;
+ }
+
+ let getAllFramesDetails = await browser.webNavigation.getAllFrames({tabId});
+
+ let getFramePromises = getAllFramesDetails.map(({frameId}) => {
+ // processId is currently marked as optional and it is ignored.
+ return browser.webNavigation.getFrame({tabId, frameId, processId: 0});
+ });
+
+ let getFrameResults = await Promise.all(getFramePromises);
+ browser.test.sendMessage("webNavigationFrames.done", {
+ collectedDetails, getAllFramesDetails, getFrameResults,
+ });
+
+ // Pick a random frameId.
+ let nonExistentFrameId = Math.floor(Math.random() * 10000);
+
+ // Increment the picked random nonExistentFrameId until it doesn't exists.
+ while (getAllFramesDetails.filter((details) => details.frameId == nonExistentFrameId).length > 0) {
+ nonExistentFrameId += 1;
+ }
+
+ // Check that getFrame Promise is rejected with the expected error message on nonexistent frameId.
+ await browser.test.assertRejects(
+ browser.webNavigation.getFrame({tabId, frameId: nonExistentFrameId, processId: 20}),
+ `No frame found with frameId: ${nonExistentFrameId}`,
+ "getFrame promise should be rejected with the expected error message on unexistent frameId");
+
+ await browser.tabs.remove(tabId);
+ browser.test.sendMessage("webNavigationFrames.done");
+ });
+
+ let tab = await browser.tabs.create({url: "tab.html"});
+ tabId = tab.id;
+ },
+ manifest: {
+ permissions: ["webNavigation", "tabs"],
+ },
+ files: {
+ "tab.html": `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <iframe src="subframe.html"></iframe>
+ <iframe src="subframe.html"></iframe>
+ </body>
+ </html>
+ `,
+ "subframe.html": `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ </html>
+ `,
+ },
+ });
+ info("load complete");
+
+ yield extension.startup();
+ info("startup complete");
+
+ let {
+ collectedDetails,
+ getAllFramesDetails,
+ getFrameResults,
+ } = yield extension.awaitMessage("webNavigationFrames.done");
+
+ is(getAllFramesDetails.length, 3, "expected number of frames found");
+ is(getAllFramesDetails.length, collectedDetails.length,
+ "number of frames found should equal the number onCompleted events collected");
+
+ is(getAllFramesDetails[0].frameId, 0, "the root frame has the expected frameId");
+ is(getAllFramesDetails[0].parentFrameId, -1, "the root frame has the expected parentFrameId");
+
+ // ordered by frameId
+ let sortByFrameId = (el1, el2) => {
+ let val1 = el1 ? el1.frameId : -1;
+ let val2 = el2 ? el2.frameId : -1;
+ return val1 - val2;
+ };
+
+ collectedDetails = collectedDetails.sort(sortByFrameId);
+ getAllFramesDetails = getAllFramesDetails.sort(sortByFrameId);
+ getFrameResults = getFrameResults.sort(sortByFrameId);
+
+ info("check frame details content");
+
+ is(getFrameResults.length, getAllFramesDetails.length,
+ "getFrame and getAllFrames should return the same number of results");
+
+ Assert.deepEqual(getFrameResults, getAllFramesDetails,
+ "getFrame and getAllFrames should return the same results");
+
+ info(`check frame details collected and retrieved with getAllFrames`);
+
+ for (let [i, collected] of collectedDetails.entries()) {
+ let getAllFramesDetail = getAllFramesDetails[i];
+
+ is(getAllFramesDetail.frameId, collected.frameId, "frameId");
+ is(getAllFramesDetail.parentFrameId, collected.parentFrameId, "parentFrameId");
+ is(getAllFramesDetail.tabId, collected.tabId, "tabId");
+
+ // This can be uncommented once Bug 1246125 has been fixed
+ // is(getAllFramesDetail.url, collected.url, "url");
+ }
+
+ info("frame details content checked");
+
+ yield extension.awaitMessage("webNavigationFrames.done");
+
+ yield extension.unload();
+ info("extension unloaded");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js b/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
new file mode 100644
index 000000000..f2ea0d901
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js
@@ -0,0 +1,251 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+function* promiseAutocompleteResultPopup(inputText) {
+ gURLBar.focus();
+ gURLBar.value = inputText;
+ gURLBar.controller.startSearch(inputText);
+ yield promisePopupShown(gURLBar.popup);
+ yield BrowserTestUtils.waitForCondition(() => {
+ return gURLBar.controller.searchStatus >=
+ Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+ });
+}
+
+function* addBookmark(bookmark) {
+ if (bookmark.keyword) {
+ yield PlacesUtils.keywords.insert({
+ keyword: bookmark.keyword,
+ url: bookmark.url,
+ });
+ }
+
+ yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: bookmark.url,
+ title: bookmark.title,
+ });
+
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.eraseEverything();
+ });
+}
+
+function addSearchEngine(basename) {
+ return new Promise((resolve, reject) => {
+ info("Waiting for engine to be added: " + basename);
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: (engine) => {
+ info(`Search engine added: ${basename}`);
+ registerCleanupFunction(() => Services.search.removeEngine(engine));
+ resolve(engine);
+ },
+ onError: (errCode) => {
+ ok(false, `addEngine failed with error code ${errCode}`);
+ reject();
+ },
+ });
+ });
+}
+
+function* prepareSearchEngine() {
+ let oldCurrentEngine = Services.search.currentEngine;
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+ let engine = yield addSearchEngine(TEST_ENGINE_BASENAME);
+ Services.search.currentEngine = engine;
+
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+ Services.search.currentEngine = oldCurrentEngine;
+
+ // Make sure the popup is closed for the next test.
+ gURLBar.blur();
+ gURLBar.popup.selectedIndex = -1;
+ gURLBar.popup.hidePopup();
+ ok(!gURLBar.popup.popupOpen, "popup should be closed");
+
+ // Clicking suggestions causes visits to search results pages, so clear that
+ // history now.
+ yield PlacesTestUtils.clearHistory();
+ });
+}
+
+add_task(function* test_webnavigation_urlbar_typed_transitions() {
+ function backgroundScript() {
+ browser.webNavigation.onCommitted.addListener((msg) => {
+ browser.test.assertEq("http://example.com/?q=typed", msg.url,
+ "Got the expected url");
+ // assert from_address_bar transition qualifier
+ browser.test.assertTrue(msg.transitionQualifiers &&
+ msg.transitionQualifiers.includes("from_address_bar"),
+ "Got the expected from_address_bar transitionQualifier");
+ browser.test.assertEq("typed", msg.transitionType,
+ "Got the expected transitionType");
+ browser.test.notifyPass("webNavigation.from_address_bar.typed");
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: backgroundScript,
+ manifest: {
+ permissions: ["webNavigation"],
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitMessage("ready");
+
+ gURLBar.focus();
+ gURLBar.textValue = "http://example.com/?q=typed";
+
+ EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
+
+ yield extension.awaitFinish("webNavigation.from_address_bar.typed");
+
+ yield extension.unload();
+ info("extension unloaded");
+});
+
+add_task(function* test_webnavigation_urlbar_bookmark_transitions() {
+ function backgroundScript() {
+ browser.webNavigation.onCommitted.addListener((msg) => {
+ browser.test.assertEq("http://example.com/?q=bookmark", msg.url,
+ "Got the expected url");
+
+ // assert from_address_bar transition qualifier
+ browser.test.assertTrue(msg.transitionQualifiers &&
+ msg.transitionQualifiers.includes("from_address_bar"),
+ "Got the expected from_address_bar transitionQualifier");
+ browser.test.assertEq("auto_bookmark", msg.transitionType,
+ "Got the expected transitionType");
+ browser.test.notifyPass("webNavigation.from_address_bar.auto_bookmark");
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: backgroundScript,
+ manifest: {
+ permissions: ["webNavigation"],
+ },
+ });
+
+ yield addBookmark({
+ title: "Bookmark To Click",
+ url: "http://example.com/?q=bookmark",
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitMessage("ready");
+
+ yield promiseAutocompleteResultPopup("Bookmark To Click");
+
+ let item = gURLBar.popup.richlistbox.getItemAtIndex(1);
+ item.click();
+ yield extension.awaitFinish("webNavigation.from_address_bar.auto_bookmark");
+
+ yield extension.unload();
+ info("extension unloaded");
+});
+
+add_task(function* test_webnavigation_urlbar_keyword_transition() {
+ function backgroundScript() {
+ browser.webNavigation.onCommitted.addListener((msg) => {
+ browser.test.assertEq(`http://example.com/?q=search`, msg.url,
+ "Got the expected url");
+
+ // assert from_address_bar transition qualifier
+ browser.test.assertTrue(msg.transitionQualifiers &&
+ msg.transitionQualifiers.includes("from_address_bar"),
+ "Got the expected from_address_bar transitionQualifier");
+ browser.test.assertEq("keyword", msg.transitionType,
+ "Got the expected transitionType");
+ browser.test.notifyPass("webNavigation.from_address_bar.keyword");
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: backgroundScript,
+ manifest: {
+ permissions: ["webNavigation"],
+ },
+ });
+
+ yield addBookmark({
+ title: "Test Keyword",
+ url: "http://example.com/?q=%s",
+ keyword: "testkw",
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitMessage("ready");
+
+ yield promiseAutocompleteResultPopup("testkw search");
+
+ let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
+ item.click();
+
+ yield extension.awaitFinish("webNavigation.from_address_bar.keyword");
+
+ yield extension.unload();
+ info("extension unloaded");
+});
+
+add_task(function* test_webnavigation_urlbar_search_transitions() {
+ function backgroundScript() {
+ browser.webNavigation.onCommitted.addListener((msg) => {
+ browser.test.assertEq("http://mochi.test:8888/", msg.url,
+ "Got the expected url");
+
+ // assert from_address_bar transition qualifier
+ browser.test.assertTrue(msg.transitionQualifiers &&
+ msg.transitionQualifiers.includes("from_address_bar"),
+ "Got the expected from_address_bar transitionQualifier");
+ browser.test.assertEq("generated", msg.transitionType,
+ "Got the expected 'generated' transitionType");
+ browser.test.notifyPass("webNavigation.from_address_bar.generated");
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: backgroundScript,
+ manifest: {
+ permissions: ["webNavigation"],
+ },
+ });
+
+ yield extension.startup();
+
+ yield extension.awaitMessage("ready");
+
+ yield prepareSearchEngine();
+ yield promiseAutocompleteResultPopup("foo");
+
+ let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
+ item.click();
+
+ yield extension.awaitFinish("webNavigation.from_address_bar.generated");
+
+ yield extension.unload();
+ info("extension unloaded");
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_webRequest.js b/browser/components/extensions/test/browser/browser_ext_webRequest.js
new file mode 100644
index 000000000..ab9f58480
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_webRequest.js
@@ -0,0 +1,95 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* globals makeExtension */
+"use strict";
+
+Services.scriptloader.loadSubScript(new URL("head_webrequest.js", gTestPath).href,
+ this);
+
+Cu.import("resource:///modules/HiddenFrame.jsm", this);
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function createHiddenBrowser(url) {
+ let frame = new HiddenFrame();
+ return new Promise(resolve =>
+ frame.get().then(subframe => {
+ let doc = subframe.document;
+ let browser = doc.createElementNS(XUL_NS, "browser");
+ browser.setAttribute("type", "content");
+ browser.setAttribute("disableglobalhistory", "true");
+ browser.setAttribute("src", url);
+
+ doc.documentElement.appendChild(browser);
+ resolve({frame: frame, browser: browser});
+ }));
+}
+
+let extension;
+let dummy = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/file_dummy.html";
+
+add_task(function* setup() {
+ // SelfSupport has a tendency to fire when running this test alone, without
+ // a good way to turn it off we just set the url to ""
+ yield SpecialPowers.pushPrefEnv({
+ set: [["browser.selfsupport.url", ""]],
+ });
+ extension = makeExtension();
+ yield extension.startup();
+});
+
+add_task(function* test_newWindow() {
+ let expect = {
+ "file_dummy.html": {
+ type: "main_frame",
+ },
+ };
+ // NOTE: When running solo, favicon will be loaded at some point during
+ // the tests in this file, so all tests ignore it. When running with
+ // other tests in this directory, favicon gets loaded at some point before
+ // we run, and we never see the request, thus it cannot be handled as part
+ // of expect above.
+ extension.sendMessage("set-expected", {expect, ignore: ["favicon.ico"]});
+ yield extension.awaitMessage("continue");
+
+ let openedWindow = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.openNewForegroundTab(openedWindow.gBrowser, dummy + "?newWindow");
+
+ yield extension.awaitMessage("done");
+ yield BrowserTestUtils.closeWindow(openedWindow);
+});
+
+add_task(function* test_newTab() {
+ // again, in this window
+ let expect = {
+ "file_dummy.html": {
+ type: "main_frame",
+ },
+ };
+ extension.sendMessage("set-expected", {expect, ignore: ["favicon.ico"]});
+ yield extension.awaitMessage("continue");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, dummy + "?newTab");
+
+ yield extension.awaitMessage("done");
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_subframe() {
+ let expect = {
+ "file_dummy.html": {
+ type: "main_frame",
+ },
+ };
+ // test a content subframe attached to hidden window
+ extension.sendMessage("set-expected", {expect, ignore: ["favicon.ico"]});
+ yield extension.awaitMessage("continue");
+ let frameInfo = yield createHiddenBrowser(dummy + "?subframe");
+ yield extension.awaitMessage("done");
+ // cleanup
+ frameInfo.browser.remove();
+ frameInfo.frame.destroy();
+});
+
+add_task(function* teardown() {
+ yield extension.unload();
+});
+
diff --git a/browser/components/extensions/test/browser/browser_ext_windows.js b/browser/components/extensions/test/browser/browser_ext_windows.js
new file mode 100644
index 000000000..d3dd6ecdb
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows.js
@@ -0,0 +1,33 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ let raisedWin = Services.ww.openWindow(
+ null, Services.prefs.getCharPref("browser.chromeURL"), "_blank",
+ "chrome,dialog=no,all,alwaysRaised", null);
+
+ yield TestUtils.topicObserved("browser-delayed-startup-finished",
+ subject => subject == raisedWin);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: function() {
+ browser.windows.getAll((wins) => {
+ browser.test.assertEq(wins.length, 2, "Expect two windows");
+
+ browser.test.assertEq(false, wins[0].alwaysOnTop,
+ "Expect first window not to be always on top");
+ browser.test.assertEq(true, wins[1].alwaysOnTop,
+ "Expect first window to be always on top");
+
+ browser.test.notifyPass("alwaysOnTop");
+ });
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("alwaysOnTop");
+ yield extension.unload();
+
+ yield BrowserTestUtils.closeWindow(raisedWin);
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_allowScriptsToClose.js b/browser/components/extensions/test/browser/browser_ext_windows_allowScriptsToClose.js
new file mode 100644
index 000000000..13f8b2eee
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_allowScriptsToClose.js
@@ -0,0 +1,61 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Tests allowScriptsToClose option
+add_task(function* test_allowScriptsToClose() {
+ const files = {
+ "dummy.html": "<meta charset=utf-8><script src=close.js></script>",
+ "close.js": function() {
+ window.close();
+ if (!window.closed) {
+ browser.test.sendMessage("close-failed");
+ }
+ },
+ };
+
+ function background() {
+ browser.test.onMessage.addListener((msg, options) => {
+ function listener(_, {status}, {url}) {
+ if (status == "complete" && url == options.url) {
+ browser.tabs.onUpdated.removeListener(listener);
+ browser.tabs.executeScript({file: "close.js"});
+ }
+ }
+ options.url = browser.runtime.getURL(options.url);
+ browser.windows.create(options);
+ if (msg === "create+execute") {
+ browser.tabs.onUpdated.addListener(listener);
+ }
+ });
+ browser.test.notifyPass();
+ }
+
+ const example = "http://example.com/";
+ const manifest = {permissions: ["tabs", example]};
+
+ const extension = ExtensionTestUtils.loadExtension({files, background, manifest});
+ yield SpecialPowers.pushPrefEnv({set: [["dom.allow_scripts_to_close_windows", false]]});
+
+ yield extension.startup();
+ yield extension.awaitFinish();
+
+ extension.sendMessage("create", {url: "dummy.html"});
+ let win = yield BrowserTestUtils.waitForNewWindow();
+ yield BrowserTestUtils.windowClosed(win);
+ info("script allowed to close the window");
+
+ extension.sendMessage("create+execute", {url: example});
+ win = yield BrowserTestUtils.waitForNewWindow();
+ yield extension.awaitMessage("close-failed");
+ info("script prevented from closing the window");
+ win.close();
+
+ extension.sendMessage("create+execute", {url: example, allowScriptsToClose: true});
+ win = yield BrowserTestUtils.waitForNewWindow();
+ yield BrowserTestUtils.windowClosed(win);
+ info("script allowed to close the window");
+
+ yield SpecialPowers.popPrefEnv();
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_create.js b/browser/components/extensions/test/browser/browser_ext_windows_create.js
new file mode 100644
index 000000000..f209c9836
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create.js
@@ -0,0 +1,142 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+requestLongerTimeout(2);
+
+add_task(function* testWindowCreate() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ let _checkWindowPromise;
+ browser.test.onMessage.addListener(msg => {
+ if (msg == "checked-window") {
+ _checkWindowPromise.resolve();
+ _checkWindowPromise = null;
+ }
+ });
+
+ let os;
+
+ function checkWindow(expected) {
+ return new Promise(resolve => {
+ _checkWindowPromise = {resolve};
+ browser.test.sendMessage("check-window", expected);
+ });
+ }
+
+ async function createWindow(params, expected, keep = false) {
+ let window = await browser.windows.create(...params);
+ // params is null when testing create without createData
+ params = params[0] || {};
+
+ for (let key of Object.keys(params)) {
+ if (key == "state" && os == "mac" && params.state == "normal") {
+ // OS-X doesn't have a hard distinction between "normal" and
+ // "maximized" states.
+ browser.test.assertTrue(window.state == "normal" || window.state == "maximized",
+ `Expected window.state (currently ${window.state}) to be "normal" but will accept "maximized"`);
+ } else {
+ browser.test.assertEq(params[key], window[key], `Got expected value for window.${key}`);
+ }
+ }
+
+ browser.test.assertEq(1, window.tabs.length, "tabs property got populated");
+
+ await checkWindow(expected);
+ if (keep) {
+ return window;
+ }
+
+ if (params.state == "fullscreen" && os == "win") {
+ // FIXME: Closing a fullscreen window causes a window leak in
+ // Windows tests.
+ await browser.windows.update(window.id, {state: "normal"});
+ }
+ await browser.windows.remove(window.id);
+ }
+
+ try {
+ ({os} = await browser.runtime.getPlatformInfo());
+
+ // Set the current window to state: "normal" because the test is failing on Windows
+ // where the current window is maximized.
+ let currentWindow = await browser.windows.getCurrent();
+ await browser.windows.update(currentWindow.id, {state: "normal"});
+
+ await createWindow([], {state: "STATE_NORMAL"});
+ await createWindow([{state: "maximized"}], {state: "STATE_MAXIMIZED"});
+ await createWindow([{state: "minimized"}], {state: "STATE_MINIMIZED"});
+ await createWindow([{state: "normal"}], {state: "STATE_NORMAL", hiddenChrome: []});
+ await createWindow([{state: "fullscreen"}], {state: "STATE_FULLSCREEN"});
+
+ let window = await createWindow(
+ [{type: "popup"}],
+ {hiddenChrome: ["menubar", "toolbar", "location", "directories", "status", "extrachrome"],
+ chromeFlags: ["CHROME_OPENAS_DIALOG"]},
+ true);
+
+ let tabs = await browser.tabs.query({windowType: "popup", active: true});
+
+ browser.test.assertEq(1, tabs.length, "Expected only one popup");
+ browser.test.assertEq(window.id, tabs[0].windowId, "Expected new window to be returned in query");
+
+ await browser.windows.remove(window.id);
+
+ browser.test.notifyPass("window-create");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("window-create");
+ }
+ },
+ });
+
+ let latestWindow;
+ let windowListener = (window, topic) => {
+ if (topic == "domwindowopened") {
+ latestWindow = window;
+ }
+ };
+ Services.ww.registerNotification(windowListener);
+
+ extension.onMessage("check-window", expected => {
+ if (expected.state != null) {
+ let {windowState} = latestWindow;
+ if (latestWindow.fullScreen) {
+ windowState = latestWindow.STATE_FULLSCREEN;
+ }
+
+ if (expected.state == "STATE_NORMAL" && AppConstants.platform == "macosx") {
+ ok(windowState == window.STATE_NORMAL || windowState == window.STATE_MAXIMIZED,
+ `Expected windowState (currently ${windowState}) to be STATE_NORMAL but will accept STATE_MAXIMIZED`);
+ } else {
+ is(windowState, window[expected.state],
+ `Expected window state to be ${expected.state}`);
+ }
+ }
+ if (expected.hiddenChrome) {
+ let chromeHidden = latestWindow.document.documentElement.getAttribute("chromehidden");
+ is(chromeHidden.trim().split(/\s+/).sort().join(" "),
+ expected.hiddenChrome.sort().join(" "),
+ "Got expected hidden chrome");
+ }
+ if (expected.chromeFlags) {
+ let {chromeFlags} = latestWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow);
+ for (let flag of expected.chromeFlags) {
+ ok(chromeFlags & Ci.nsIWebBrowserChrome[flag],
+ `Expected window to have the ${flag} flag`);
+ }
+ }
+
+ extension.sendMessage("checked-window");
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("window-create");
+ yield extension.unload();
+
+ Services.ww.unregisterNotification(windowListener);
+ latestWindow = null;
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_create_params.js b/browser/components/extensions/test/browser/browser_ext_windows_create_params.js
new file mode 100644
index 000000000..c54d94e05
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_params.js
@@ -0,0 +1,33 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+
+// Tests that incompatible parameters can't be used together.
+add_task(function* testWindowCreateParams() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ try {
+ for (let state of ["minimized", "maximized", "fullscreen"]) {
+ for (let param of ["left", "top", "width", "height"]) {
+ let expected = `"state": "${state}" may not be combined with "left", "top", "width", or "height"`;
+
+ await browser.test.assertRejects(
+ browser.windows.create({state, [param]: 100}),
+ RegExp(expected),
+ `Got expected error from create(${param}=100)`);
+ }
+ }
+
+ browser.test.notifyPass("window-create-params");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("window-create-params");
+ }
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("window-create-params");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js b/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
new file mode 100644
index 000000000..52ffaea8b
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
@@ -0,0 +1,140 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testWindowCreate() {
+ async function background() {
+ let promiseTabAttached = () => {
+ return new Promise(resolve => {
+ browser.tabs.onAttached.addListener(function listener() {
+ browser.tabs.onAttached.removeListener(listener);
+ resolve();
+ });
+ });
+ };
+
+ let promiseTabUpdated = (expected) => {
+ return new Promise(resolve => {
+ browser.tabs.onUpdated.addListener(function listener(tabId, changeInfo, tab) {
+ if (changeInfo.url === expected) {
+ browser.tabs.onUpdated.removeListener(listener);
+ resolve();
+ }
+ });
+ });
+ };
+
+ try {
+ let window = await browser.windows.getCurrent();
+ let windowId = window.id;
+
+ browser.test.log("Create additional tab in window 1");
+ let tab = await browser.tabs.create({windowId, url: "about:blank"});
+ let tabId = tab.id;
+
+ browser.test.log("Create a new window, adopting the new tab");
+
+ // Note that we want to check against actual boolean values for
+ // all of the `incognito` property tests.
+ browser.test.assertEq(false, tab.incognito, "Tab is not private");
+
+ {
+ let [, window] = await Promise.all([
+ promiseTabAttached(),
+ browser.windows.create({tabId: tabId}),
+ ]);
+ browser.test.assertEq(false, window.incognito, "New window is not private");
+ browser.test.assertEq(tabId, window.tabs[0].id, "tabs property populated correctly");
+
+ browser.test.log("Close the new window");
+ await browser.windows.remove(window.id);
+ }
+
+ {
+ browser.test.log("Create a new private window");
+ let privateWindow = await browser.windows.create({incognito: true});
+ browser.test.assertEq(true, privateWindow.incognito, "Private window is private");
+
+ browser.test.log("Create additional tab in private window");
+ let privateTab = await browser.tabs.create({windowId: privateWindow.id});
+ browser.test.assertEq(true, privateTab.incognito, "Private tab is private");
+
+ browser.test.log("Create a new window, adopting the new private tab");
+ let [, newWindow] = await Promise.all([
+ promiseTabAttached(),
+ browser.windows.create({tabId: privateTab.id}),
+ ]);
+ browser.test.assertEq(true, newWindow.incognito, "New private window is private");
+
+ browser.test.log("Close the new private window");
+ await browser.windows.remove(newWindow.id);
+
+ browser.test.log("Close the private window");
+ await browser.windows.remove(privateWindow.id);
+ }
+
+
+ browser.test.log("Try to create a window with both a tab and a URL");
+ [tab] = await browser.tabs.query({windowId, active: true});
+ await browser.test.assertRejects(
+ browser.windows.create({tabId: tab.id, url: "http://example.com/"}),
+ /`tabId` may not be used in conjunction with `url`/,
+ "Create call failed as expected");
+
+ browser.test.log("Try to create a window with both a tab and an invalid incognito setting");
+ await browser.test.assertRejects(
+ browser.windows.create({tabId: tab.id, incognito: true}),
+ /`incognito` property must match the incognito state of tab/,
+ "Create call failed as expected");
+
+
+ browser.test.log("Try to create a window with an invalid tabId");
+ await browser.test.assertRejects(
+ browser.windows.create({tabId: 0}),
+ /Invalid tab ID: 0/,
+ "Create call failed as expected");
+
+
+ browser.test.log("Try to create a window with two URLs");
+ let readyPromise = Promise.all([
+ // tabs.onUpdated can be invoked between the call of windows.create and
+ // the invocation of its callback/promise, so set up the listeners
+ // before creating the window.
+ promiseTabUpdated("http://example.com/"),
+ promiseTabUpdated("http://example.org/"),
+ ]);
+
+ window = await browser.windows.create({url: ["http://example.com/", "http://example.org/"]});
+ await readyPromise;
+
+ browser.test.assertEq(2, window.tabs.length, "2 tabs were opened in new window");
+ browser.test.assertEq("about:blank", window.tabs[0].url, "about:blank, page not loaded yet");
+ browser.test.assertEq("about:blank", window.tabs[1].url, "about:blank, page not loaded yet");
+
+ window = await browser.windows.get(window.id, {populate: true});
+
+ browser.test.assertEq(2, window.tabs.length, "2 tabs were opened in new window");
+ browser.test.assertEq("http://example.com/", window.tabs[0].url, "Correct URL was loaded in tab 1");
+ browser.test.assertEq("http://example.org/", window.tabs[1].url, "Correct URL was loaded in tab 2");
+
+ await browser.windows.remove(window.id);
+
+ browser.test.notifyPass("window-create");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("window-create");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ "permissions": ["tabs"],
+ },
+
+ background,
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("window-create");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_create_url.js b/browser/components/extensions/test/browser/browser_ext_windows_create_url.js
new file mode 100644
index 000000000..c5c7aaf20
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_url.js
@@ -0,0 +1,84 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testWindowCreate() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["tabs"],
+ },
+
+ background: async function() {
+ const EXTENSION_URL = browser.runtime.getURL("test.html");
+ const REMOTE_URL = browser.runtime.getURL("test.html");
+
+ let windows = new class extends Map { // eslint-disable-line new-parens
+ get(id) {
+ if (!this.has(id)) {
+ let window = {
+ tabs: new Map(),
+ };
+ window.promise = new Promise(resolve => {
+ window.resolvePromise = resolve;
+ });
+
+ this.set(id, window);
+ }
+
+ return super.get(id);
+ }
+ };
+
+ browser.tabs.onUpdated.addListener((tabId, changed, tab) => {
+ if (changed.status == "complete" && tab.url !== "about:blank") {
+ let window = windows.get(tab.windowId);
+ window.tabs.set(tab.index, tab);
+
+ if (window.tabs.size === window.expectedTabs) {
+ window.resolvePromise(window);
+ }
+ }
+ });
+
+ async function create(options) {
+ let window = await browser.windows.create(options);
+ let win = windows.get(window.id);
+
+ win.expectedTabs = Array.isArray(options.url) ? options.url.length : 1;
+
+ return win.promise;
+ }
+
+ try {
+ let windows = await Promise.all([
+ create({url: REMOTE_URL}),
+ create({url: "test.html"}),
+ create({url: EXTENSION_URL}),
+ create({url: [REMOTE_URL, "test.html", EXTENSION_URL]}),
+ ]);
+ browser.test.assertEq(REMOTE_URL, windows[0].tabs.get(0).url, "Single, absolute, remote URL");
+
+ browser.test.assertEq(REMOTE_URL, windows[1].tabs.get(0).url, "Single, relative URL");
+
+ browser.test.assertEq(REMOTE_URL, windows[2].tabs.get(0).url, "Single, absolute, extension URL");
+
+ browser.test.assertEq(REMOTE_URL, windows[3].tabs.get(0).url, "url[0]: Absolute, remote URL");
+ browser.test.assertEq(EXTENSION_URL, windows[3].tabs.get(1).url, "url[1]: Relative URL");
+ browser.test.assertEq(EXTENSION_URL, windows[3].tabs.get(2).url, "url[2]: Absolute, extension URL");
+
+ browser.test.notifyPass("window-create-url");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("window-create-url");
+ }
+ },
+
+ files: {
+ "test.html": `<DOCTYPE html><html><head><meta charset="utf-8"></head></html>`,
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("window-create-url");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_events.js b/browser/components/extensions/test/browser/browser_ext_windows_events.js
new file mode 100644
index 000000000..dc3485b98
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_events.js
@@ -0,0 +1,115 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+SimpleTest.requestCompleteLog();
+
+add_task(function* testWindowsEvents() {
+ function background() {
+ browser.windows.onCreated.addListener(window => {
+ browser.test.log(`onCreated: windowId=${window.id}`);
+
+ browser.test.assertTrue(Number.isInteger(window.id),
+ "Window object's id is an integer");
+ browser.test.assertEq("normal", window.type,
+ "Window object returned with the correct type");
+ browser.test.sendMessage("window-created", window.id);
+ });
+
+ let lastWindowId, os;
+ browser.windows.onFocusChanged.addListener(async windowId => {
+ browser.test.log(`onFocusChange: windowId=${windowId} lastWindowId=${lastWindowId}`);
+
+ if (windowId === browser.windows.WINDOW_ID_NONE && os === "linux") {
+ browser.test.log("Ignoring a superfluous WINDOW_ID_NONE (blur) event on Linux");
+ return;
+ }
+
+ browser.test.assertTrue(lastWindowId !== windowId,
+ "onFocusChanged fired once for the given window");
+ lastWindowId = windowId;
+
+ browser.test.assertTrue(Number.isInteger(windowId),
+ "windowId is an integer");
+
+ let window = await browser.windows.getLastFocused();
+
+ browser.test.assertEq(windowId, window.id,
+ "Last focused window has the correct id");
+ browser.test.sendMessage(`window-focus-changed`, window.id);
+ });
+
+ browser.windows.onRemoved.addListener(windowId => {
+ browser.test.log(`onRemoved: windowId=${windowId}`);
+
+ browser.test.assertTrue(Number.isInteger(windowId),
+ "windowId is an integer");
+ browser.test.sendMessage(`window-removed`, windowId);
+ browser.test.notifyPass("windows.events");
+ });
+
+ browser.runtime.getPlatformInfo(info => {
+ os = info.os;
+ browser.test.sendMessage("ready");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: `(${background})()`,
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+ let currentWindow = window;
+ let currentWindowId = WindowManager.getId(currentWindow);
+ info(`Current window ID: ${currentWindowId}`);
+
+ info(`Create browser window 1`);
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win1Id = yield extension.awaitMessage("window-created");
+ info(`Window 1 ID: ${win1Id}`);
+
+ // This shouldn't be necessary, but tests intermittently fail, so let's give
+ // it a try.
+ win1.focus();
+
+ let winId = yield extension.awaitMessage(`window-focus-changed`);
+ is(winId, win1Id, "Got focus change event for the correct window ID.");
+
+ info(`Create browser window 2`);
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+ let win2Id = yield extension.awaitMessage("window-created");
+ info(`Window 2 ID: ${win2Id}`);
+
+ win2.focus();
+
+ winId = yield extension.awaitMessage(`window-focus-changed`);
+ is(winId, win2Id, "Got focus change event for the correct window ID.");
+
+ info(`Focus browser window 1`);
+ yield focusWindow(win1);
+
+ winId = yield extension.awaitMessage(`window-focus-changed`);
+ is(winId, win1Id, "Got focus change event for the correct window ID.");
+
+ info(`Close browser window 2`);
+ yield BrowserTestUtils.closeWindow(win2);
+
+ winId = yield extension.awaitMessage(`window-removed`);
+ is(winId, win2Id, "Got removed event for the correct window ID.");
+
+ info(`Close browser window 1`);
+ yield BrowserTestUtils.closeWindow(win1);
+
+ winId = yield extension.awaitMessage(`window-removed`);
+ is(winId, win1Id, "Got removed event for the correct window ID.");
+
+ winId = yield extension.awaitMessage(`window-focus-changed`);
+ is(winId, currentWindowId, "Got focus change event for the correct window ID.");
+
+ yield extension.awaitFinish("windows.events");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_size.js b/browser/components/extensions/test/browser/browser_ext_windows_size.js
new file mode 100644
index 000000000..be822fea1
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_size.js
@@ -0,0 +1,114 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testWindowCreate() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ let _checkWindowPromise;
+ browser.test.onMessage.addListener((msg, arg) => {
+ if (msg == "checked-window") {
+ _checkWindowPromise.resolve(arg);
+ _checkWindowPromise = null;
+ }
+ });
+
+ let getWindowSize = () => {
+ return new Promise(resolve => {
+ _checkWindowPromise = {resolve};
+ browser.test.sendMessage("check-window");
+ });
+ };
+
+ const KEYS = ["left", "top", "width", "height"];
+ function checkGeom(expected, actual) {
+ for (let key of KEYS) {
+ browser.test.assertEq(expected[key], actual[key], `Expected '${key}' value`);
+ }
+ }
+
+ let windowId;
+ async function checkWindow(expected, retries = 5) {
+ let geom = await getWindowSize();
+
+ if (retries && KEYS.some(key => expected[key] != geom[key])) {
+ browser.test.log(`Got mismatched size (${JSON.stringify(expected)} != ${JSON.stringify(geom)}). ` +
+ `Retrying after a short delay.`);
+
+ await new Promise(resolve => setTimeout(resolve, 200));
+
+ return checkWindow(expected, retries - 1);
+ }
+
+ browser.test.log(`Check actual window size`);
+ checkGeom(expected, geom);
+
+ browser.test.log("Check API-reported window size");
+
+ geom = await browser.windows.get(windowId);
+
+ checkGeom(expected, geom);
+ }
+
+ try {
+ let geom = {left: 100, top: 100, width: 500, height: 300};
+
+ let window = await browser.windows.create(geom);
+ windowId = window.id;
+
+ await checkWindow(geom);
+
+ let update = {left: 150, width: 600};
+ Object.assign(geom, update);
+ await browser.windows.update(windowId, update);
+ await checkWindow(geom);
+
+ update = {top: 150, height: 400};
+ Object.assign(geom, update);
+ await browser.windows.update(windowId, update);
+ await checkWindow(geom);
+
+ geom = {left: 200, top: 200, width: 800, height: 600};
+ await browser.windows.update(windowId, geom);
+ await checkWindow(geom);
+
+ let platformInfo = await browser.runtime.getPlatformInfo();
+ if (platformInfo.os != "linux") {
+ geom = {left: -50, top: -50, width: 800, height: 600};
+ await browser.windows.update(windowId, geom);
+ await checkWindow(geom);
+ }
+
+ await browser.windows.remove(windowId);
+ browser.test.notifyPass("window-size");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("window-size");
+ }
+ },
+ });
+
+ let latestWindow;
+ let windowListener = (window, topic) => {
+ if (topic == "domwindowopened") {
+ latestWindow = window;
+ }
+ };
+ Services.ww.registerNotification(windowListener);
+
+ extension.onMessage("check-window", () => {
+ extension.sendMessage("checked-window", {
+ top: latestWindow.screenY,
+ left: latestWindow.screenX,
+ width: latestWindow.outerWidth,
+ height: latestWindow.outerHeight,
+ });
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("window-size");
+ yield extension.unload();
+
+ Services.ww.unregisterNotification(windowListener);
+ latestWindow = null;
+});
diff --git a/browser/components/extensions/test/browser/browser_ext_windows_update.js b/browser/components/extensions/test/browser/browser_ext_windows_update.js
new file mode 100644
index 000000000..b9475547a
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -0,0 +1,189 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* () {
+ function promiseWaitForFocus(window) {
+ return new Promise(resolve => {
+ waitForFocus(function() {
+ ok(Services.focus.activeWindow === window, "correct window focused");
+ resolve();
+ }, window);
+ });
+ }
+
+ let window1 = window;
+ let window2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ Services.focus.activeWindow = window2;
+ yield promiseWaitForFocus(window2);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: function() {
+ browser.windows.getAll(undefined, function(wins) {
+ browser.test.assertEq(wins.length, 2, "should have two windows");
+
+ // Sort the unfocused window to the lower index.
+ wins.sort(function(win1, win2) {
+ if (win1.focused === win2.focused) {
+ return 0;
+ }
+
+ return win1.focused ? 1 : -1;
+ });
+
+ browser.windows.update(wins[0].id, {focused: true}, function() {
+ browser.test.sendMessage("check");
+ });
+ });
+ },
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("check")]);
+
+ yield promiseWaitForFocus(window1);
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.closeWindow(window2);
+});
+
+
+add_task(function* testWindowUpdate() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ let _checkWindowPromise;
+ browser.test.onMessage.addListener(msg => {
+ if (msg == "checked-window") {
+ _checkWindowPromise.resolve();
+ _checkWindowPromise = null;
+ }
+ });
+
+ let os;
+ function checkWindow(expected) {
+ return new Promise(resolve => {
+ _checkWindowPromise = {resolve};
+ browser.test.sendMessage("check-window", expected);
+ });
+ }
+
+ let currentWindowId;
+ async function updateWindow(windowId, params, expected) {
+ let window = await browser.windows.update(windowId, params);
+
+ browser.test.assertEq(currentWindowId, window.id, "Expected WINDOW_ID_CURRENT to refer to the same window");
+ for (let key of Object.keys(params)) {
+ if (key == "state" && os == "mac" && params.state == "normal") {
+ // OS-X doesn't have a hard distinction between "normal" and
+ // "maximized" states.
+ browser.test.assertTrue(window.state == "normal" || window.state == "maximized",
+ `Expected window.state (currently ${window.state}) to be "normal" but will accept "maximized"`);
+ } else {
+ browser.test.assertEq(params[key], window[key], `Got expected value for window.${key}`);
+ }
+ }
+
+ return checkWindow(expected);
+ }
+
+ try {
+ let windowId = browser.windows.WINDOW_ID_CURRENT;
+
+ ({os} = await browser.runtime.getPlatformInfo());
+
+ let window = await browser.windows.getCurrent();
+ currentWindowId = window.id;
+
+ await updateWindow(windowId, {state: "maximized"}, {state: "STATE_MAXIMIZED"});
+ await updateWindow(windowId, {state: "minimized"}, {state: "STATE_MINIMIZED"});
+ await updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"});
+ await updateWindow(windowId, {state: "fullscreen"}, {state: "STATE_FULLSCREEN"});
+ await updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"});
+
+ browser.test.notifyPass("window-update");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("window-update");
+ }
+ },
+ });
+
+ extension.onMessage("check-window", expected => {
+ if (expected.state != null) {
+ let {windowState} = window;
+ if (window.fullScreen) {
+ windowState = window.STATE_FULLSCREEN;
+ }
+
+ // Temporarily accepting STATE_MAXIMIZED on Linux because of bug 1307759.
+ if (expected.state == "STATE_NORMAL" && (AppConstants.platform == "macosx" || AppConstants.platform == "linux")) {
+ ok(windowState == window.STATE_NORMAL || windowState == window.STATE_MAXIMIZED,
+ `Expected windowState (currently ${windowState}) to be STATE_NORMAL but will accept STATE_MAXIMIZED`);
+ } else {
+ is(windowState, window[expected.state],
+ `Expected window state to be ${expected.state}`);
+ }
+ }
+
+ extension.sendMessage("checked-window");
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("window-update");
+ yield extension.unload();
+});
+
+add_task(function* () {
+ let window2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: function() {
+ browser.windows.getAll(undefined, function(wins) {
+ browser.test.assertEq(wins.length, 2, "should have two windows");
+
+ let unfocused = wins.find(win => !win.focused);
+ browser.windows.update(unfocused.id, {drawAttention: true}, function() {
+ browser.test.sendMessage("check");
+ });
+ });
+ },
+ });
+
+ yield Promise.all([extension.startup(), extension.awaitMessage("check")]);
+
+ yield extension.unload();
+
+ yield BrowserTestUtils.closeWindow(window2);
+});
+
+
+// Tests that incompatible parameters can't be used together.
+add_task(function* testWindowUpdateParams() {
+ let extension = ExtensionTestUtils.loadExtension({
+ async background() {
+ try {
+ for (let state of ["minimized", "maximized", "fullscreen"]) {
+ for (let param of ["left", "top", "width", "height"]) {
+ let expected = `"state": "${state}" may not be combined with "left", "top", "width", or "height"`;
+
+ let windowId = browser.windows.WINDOW_ID_CURRENT;
+ await browser.test.assertRejects(
+ browser.windows.update(windowId, {state, [param]: 100}),
+ RegExp(expected),
+ `Got expected error for create(${param}=100`);
+ }
+ }
+
+ browser.test.notifyPass("window-update-params");
+ } catch (e) {
+ browser.test.fail(`${e} :: ${e.stack}`);
+ browser.test.notifyFail("window-update-params");
+ }
+ },
+ });
+
+ yield extension.startup();
+ yield extension.awaitFinish("window-update-params");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/browser/context.html b/browser/components/extensions/test/browser/context.html
new file mode 100644
index 000000000..954feea52
--- /dev/null
+++ b/browser/components/extensions/test/browser/context.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ just some text 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ <img src="ctxmenu-image.png" id="img1">
+
+ <p>
+ <a href="some-link" id="link1">Some link</a>
+ </p>
+
+ <p>
+ <a href="image-around-some-link">
+ <img src="ctxmenu-image.png" id="img-wrapped-in-link">
+ </a>
+ </p>
+
+ <p>
+ <input type="text" id="edit-me">
+ </p>
+ </body>
+</html>
diff --git a/browser/components/extensions/test/browser/context_tabs_onUpdated_iframe.html b/browser/components/extensions/test/browser/context_tabs_onUpdated_iframe.html
new file mode 100644
index 000000000..0e9b54b52
--- /dev/null
+++ b/browser/components/extensions/test/browser/context_tabs_onUpdated_iframe.html
@@ -0,0 +1,19 @@
+<html>
+ <body>
+ <h3>test iframe</h3>
+ <script>
+ "use strict";
+
+ window.onload = function() {
+ window.onhashchange = function() {
+ window.parent.postMessage("updated-iframe-url", "*");
+ };
+ // NOTE: without the this setTimeout the location change is not fired
+ // even without the "fire only for top level windows" fix
+ setTimeout(function() {
+ window.location.hash = "updated-iframe-url";
+ }, 0);
+ };
+ </script>
+ </body>
+</html>
diff --git a/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html b/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html
new file mode 100644
index 000000000..0f2ce1e8f
--- /dev/null
+++ b/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html
@@ -0,0 +1,18 @@
+<html>
+ <body>
+ <h3>test page</h3>
+ <iframe src="about:blank"></iframe>
+ <script>
+ "use strict";
+
+ window.onmessage = function(evt) {
+ if (evt.data === "updated-iframe-url") {
+ window.postMessage("frame-updated", "*");
+ }
+ };
+ window.onload = function() {
+ document.querySelector("iframe").setAttribute("src", "context_tabs_onUpdated_iframe.html");
+ };
+ </script>
+ </body>
+</html>
diff --git a/browser/components/extensions/test/browser/ctxmenu-image.png b/browser/components/extensions/test/browser/ctxmenu-image.png
new file mode 100644
index 000000000..4c3be5084
--- /dev/null
+++ b/browser/components/extensions/test/browser/ctxmenu-image.png
Binary files differ
diff --git a/browser/components/extensions/test/browser/file_bypass_cache.sjs b/browser/components/extensions/test/browser/file_bypass_cache.sjs
new file mode 100644
index 000000000..c91c76b88
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_bypass_cache.sjs
@@ -0,0 +1,11 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain; charset=UTF-8", false);
+
+ if (request.hasHeader("pragma") && request.hasHeader("cache-control")) {
+ response.write(`${request.getHeader("pragma")}:${request.getHeader("cache-control")}`);
+ }
+} \ No newline at end of file
diff --git a/browser/components/extensions/test/browser/file_dummy.html b/browser/components/extensions/test/browser/file_dummy.html
new file mode 100644
index 000000000..1a87e2840
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_dummy.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/components/extensions/test/browser/file_iframe_document.html b/browser/components/extensions/test/browser/file_iframe_document.html
new file mode 100644
index 000000000..fcadccf02
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_iframe_document.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title></title>
+</head>
+<body>
+ <iframe src="/"></iframe>
+</body>
+</html>
diff --git a/browser/components/extensions/test/browser/file_iframe_document.sjs b/browser/components/extensions/test/browser/file_iframe_document.sjs
new file mode 100644
index 000000000..661a768af
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_iframe_document.sjs
@@ -0,0 +1,41 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */
+"use strict";
+
+// This script slows the load of an HTML document so that we can reliably test
+// all phases of the load cycle supported by the extension API.
+
+/* eslint-disable no-unused-vars */
+
+const DELAY = 1 * 1000; // Delay one second before completing the request.
+
+const Ci = Components.interfaces;
+
+let nsTimer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", "initWithCallback");
+
+let timer;
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(`<!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title></title>
+ </head>
+ <body>
+ `);
+
+ // Note: We need to store a reference to the timer to prevent it from being
+ // canceled when it's GCed.
+ timer = new nsTimer(() => {
+ response.write(`
+ <iframe src="/"></iframe>
+ </body>
+ </html>`);
+ response.finish();
+ }, DELAY, Ci.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/browser/components/extensions/test/browser/file_language_fr_en.html b/browser/components/extensions/test/browser/file_language_fr_en.html
new file mode 100644
index 000000000..5e3c7b3b0
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_language_fr_en.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+ <meta charset="UTF-8">
+ <title></title>
+</head>
+<body>
+ France is the largest country in Western Europe and the third-largest in Europe as a whole.
+ A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter
+ Cet article concerne le pays européen aujourd’hui appelé République française. Pour d’autres usages du nom France,
+ Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus.
+ Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumps over the lazy dog.
+</body>
+</html>
diff --git a/browser/components/extensions/test/browser/file_language_ja.html b/browser/components/extensions/test/browser/file_language_ja.html
new file mode 100644
index 000000000..ed07ba70e
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_language_ja.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="ja">
+<head>
+ <meta charset="UTF-8">
+ <title></title>
+</head>
+<body>
+ このペ ジでは アカウントに指定された予算の履歴を一覧にしています それぞれの項目には 予算額と特定期間のステ タスが表示されます 現在または今後の予算を設定するには
+</body>
+</html>
diff --git a/browser/components/extensions/test/browser/file_language_tlh.html b/browser/components/extensions/test/browser/file_language_tlh.html
new file mode 100644
index 000000000..dd7da7bdb
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_language_tlh.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="tlh">
+<head>
+ <meta charset="UTF-8">
+ <title></title>
+</head>
+<body>
+ tlhIngan maH!
+ Hab SoSlI' Quch!
+ Heghlu'meH QaQ jajvam
+</body>
+</html>
diff --git a/browser/components/extensions/test/browser/file_popup_api_injection_a.html b/browser/components/extensions/test/browser/file_popup_api_injection_a.html
new file mode 100644
index 000000000..750ff1db3
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_popup_api_injection_a.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="application/javascript">
+ "use strict";
+ throw new Error(`WebExt Privilege Escalation: BrowserAction: typeof(browser) = ${typeof(browser)}`);
+ </script>
+</head>
+</html>
diff --git a/browser/components/extensions/test/browser/file_popup_api_injection_b.html b/browser/components/extensions/test/browser/file_popup_api_injection_b.html
new file mode 100644
index 000000000..b8c287e55
--- /dev/null
+++ b/browser/components/extensions/test/browser/file_popup_api_injection_b.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="application/javascript">
+ "use strict";
+ throw new Error(`WebExt Privilege Escalation: PageAction: typeof(browser) = ${typeof(browser)}`);
+ </script>
+</head>
+</html>
diff --git a/browser/components/extensions/test/browser/head.js b/browser/components/extensions/test/browser/head.js
new file mode 100644
index 000000000..f8d59c944
--- /dev/null
+++ b/browser/components/extensions/test/browser/head.js
@@ -0,0 +1,263 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* exported CustomizableUI makeWidgetId focusWindow forceGC
+ * getBrowserActionWidget
+ * clickBrowserAction clickPageAction
+ * getBrowserActionPopup getPageActionPopup
+ * closeBrowserAction closePageAction
+ * promisePopupShown promisePopupHidden
+ * openContextMenu closeContextMenu
+ * openExtensionContextMenu closeExtensionContextMenu
+ * imageBuffer getListStyleImage getPanelForNode
+ * awaitExtensionPanel awaitPopupResize
+ * promiseContentDimensions alterContent
+ */
+
+var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
+var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
+
+// Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
+// times in debug builds, which results in intermittent timeouts. Until we have
+// a better solution, we force a GC after certain strategic tests, which tend to
+// accumulate a high number of unreaped windows.
+function forceGC() {
+ if (AppConstants.DEBUG) {
+ Cu.forceGC();
+ }
+}
+
+function makeWidgetId(id) {
+ id = id.toLowerCase();
+ return id.replace(/[^a-z0-9_-]/g, "_");
+}
+
+var focusWindow = Task.async(function* focusWindow(win) {
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ if (fm.activeWindow == win) {
+ return;
+ }
+
+ let promise = new Promise(resolve => {
+ win.addEventListener("focus", function listener() {
+ win.removeEventListener("focus", listener, true);
+ resolve();
+ }, true);
+ });
+
+ win.focus();
+ yield promise;
+});
+
+let img = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==";
+var imageBuffer = Uint8Array.from(atob(img), byte => byte.charCodeAt(0)).buffer;
+
+function getListStyleImage(button) {
+ let style = button.ownerDocument.defaultView.getComputedStyle(button);
+
+ let match = /^url\("(.*)"\)$/.exec(style.listStyleImage);
+
+ return match && match[1];
+}
+
+function promisePopupShown(popup) {
+ return new Promise(resolve => {
+ if (popup.state == "open") {
+ resolve();
+ } else {
+ let onPopupShown = event => {
+ popup.removeEventListener("popupshown", onPopupShown);
+ resolve();
+ };
+ popup.addEventListener("popupshown", onPopupShown);
+ }
+ });
+}
+
+function promisePopupHidden(popup) {
+ return new Promise(resolve => {
+ let onPopupHidden = event => {
+ popup.removeEventListener("popuphidden", onPopupHidden);
+ resolve();
+ };
+ popup.addEventListener("popuphidden", onPopupHidden);
+ });
+}
+
+function promiseContentDimensions(browser) {
+ return ContentTask.spawn(browser, null, function* () {
+ function copyProps(obj, props) {
+ let res = {};
+ for (let prop of props) {
+ res[prop] = obj[prop];
+ }
+ return res;
+ }
+
+ return {
+ window: copyProps(content,
+ ["innerWidth", "innerHeight", "outerWidth", "outerHeight",
+ "scrollX", "scrollY", "scrollMaxX", "scrollMaxY"]),
+ body: copyProps(content.document.body,
+ ["clientWidth", "clientHeight", "scrollWidth", "scrollHeight"]),
+ root: copyProps(content.document.documentElement,
+ ["clientWidth", "clientHeight", "scrollWidth", "scrollHeight"]),
+
+ isStandards: content.document.compatMode !== "BackCompat",
+ };
+ });
+}
+
+function* awaitPopupResize(browser) {
+ return BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized",
+ event => event.detail === "delayed");
+}
+
+function alterContent(browser, task, arg = null) {
+ return Promise.all([
+ ContentTask.spawn(browser, arg, task),
+ awaitPopupResize(browser),
+ ]).then(() => {
+ return promiseContentDimensions(browser);
+ });
+}
+
+function getPanelForNode(node) {
+ while (node.localName != "panel") {
+ node = node.parentNode;
+ }
+ return node;
+}
+
+var awaitBrowserLoaded = browser => ContentTask.spawn(browser, null, () => {
+ if (content.document.readyState !== "complete") {
+ return ContentTaskUtils.waitForEvent(content, "load").then(() => {});
+ }
+});
+
+var awaitExtensionPanel = Task.async(function* (extension, win = window, awaitLoad = true) {
+ let {originalTarget: browser} = yield BrowserTestUtils.waitForEvent(
+ win.document, "WebExtPopupLoaded", true,
+ event => event.detail.extension.id === extension.id);
+
+ yield Promise.all([
+ promisePopupShown(getPanelForNode(browser)),
+
+ awaitLoad && awaitBrowserLoaded(browser),
+ ]);
+
+ return browser;
+});
+
+function getBrowserActionWidget(extension) {
+ return CustomizableUI.getWidget(makeWidgetId(extension.id) + "-browser-action");
+}
+
+function getBrowserActionPopup(extension, win = window) {
+ let group = getBrowserActionWidget(extension);
+
+ if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
+ return win.document.getElementById("customizationui-widget-panel");
+ }
+ return win.PanelUI.panel;
+}
+
+var showBrowserAction = Task.async(function* (extension, win = window) {
+ let group = getBrowserActionWidget(extension);
+ let widget = group.forWindow(win);
+
+ if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
+ ok(!widget.overflowed, "Expect widget not to be overflowed");
+ } else if (group.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ yield win.PanelUI.show();
+ }
+});
+
+var clickBrowserAction = Task.async(function* (extension, win = window) {
+ yield showBrowserAction(extension, win);
+
+ let widget = getBrowserActionWidget(extension).forWindow(win);
+
+ EventUtils.synthesizeMouseAtCenter(widget.node, {}, win);
+});
+
+function closeBrowserAction(extension, win = window) {
+ let group = getBrowserActionWidget(extension);
+
+ let node = win.document.getElementById(group.viewId);
+ CustomizableUI.hidePanelForNode(node);
+
+ return Promise.resolve();
+}
+
+function* openContextMenu(selector = "#img1") {
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
+ yield BrowserTestUtils.synthesizeMouseAtCenter(selector, {type: "contextmenu"}, gBrowser.selectedBrowser);
+ yield popupShownPromise;
+ return contentAreaContextMenu;
+}
+
+function* closeContextMenu() {
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ contentAreaContextMenu.hidePopup();
+ yield popupHiddenPromise;
+}
+
+function* openExtensionContextMenu(selector = "#img1") {
+ let contextMenu = yield openContextMenu(selector);
+ let topLevelMenu = contextMenu.getElementsByAttribute("ext-type", "top-level-menu");
+
+ // Return null if the extension only has one item and therefore no extension menu.
+ if (topLevelMenu.length == 0) {
+ return null;
+ }
+
+ let extensionMenu = topLevelMenu[0].childNodes[0];
+ let popupShownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(extensionMenu, {});
+ yield popupShownPromise;
+ return extensionMenu;
+}
+
+function* closeExtensionContextMenu(itemToSelect) {
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popuphidden");
+ EventUtils.synthesizeMouseAtCenter(itemToSelect, {});
+ yield popupHiddenPromise;
+}
+
+function getPageActionPopup(extension, win = window) {
+ let panelId = makeWidgetId(extension.id) + "-panel";
+ return win.document.getElementById(panelId);
+}
+
+function clickPageAction(extension, win = window) {
+ // This would normally be set automatically on navigation, and cleared
+ // when the user types a value into the URL bar, to show and hide page
+ // identity info and icons such as page action buttons.
+ //
+ // Unfortunately, that doesn't happen automatically in browser chrome
+ // tests.
+ /* globals SetPageProxyState */
+ SetPageProxyState("valid");
+
+ let pageActionId = makeWidgetId(extension.id) + "-page-action";
+ let elem = win.document.getElementById(pageActionId);
+
+ EventUtils.synthesizeMouseAtCenter(elem, {}, win);
+ return new Promise(SimpleTest.executeSoon);
+}
+
+function closePageAction(extension, win = window) {
+ let node = getPageActionPopup(extension, win);
+ if (node) {
+ return promisePopupShown(node).then(() => {
+ node.hidePopup();
+ });
+ }
+
+ return Promise.resolve();
+}
diff --git a/browser/components/extensions/test/browser/head_pageAction.js b/browser/components/extensions/test/browser/head_pageAction.js
new file mode 100644
index 000000000..f2d81e512
--- /dev/null
+++ b/browser/components/extensions/test/browser/head_pageAction.js
@@ -0,0 +1,157 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* exported runTests */
+/* globals getListStyleImage */
+
+function* runTests(options) {
+ function background(getTests) {
+ let tabs;
+ let tests;
+
+ // Gets the current details of the page action, and returns a
+ // promise that resolves to an object containing them.
+ async function getDetails() {
+ let [tab] = await browser.tabs.query({active: true, currentWindow: true});
+ let tabId = tab.id;
+
+ browser.test.log(`Get details: tab={id: ${tabId}, url: ${JSON.stringify(tab.url)}}`);
+
+ return {
+ title: await browser.pageAction.getTitle({tabId}),
+ popup: await browser.pageAction.getPopup({tabId}),
+ };
+ }
+
+
+ // Runs the next test in the `tests` array, checks the results,
+ // and passes control back to the outer test scope.
+ function nextTest() {
+ let test = tests.shift();
+
+ test(async expecting => {
+ function finish() {
+ // Check that the actual icon has the expected values, then
+ // run the next test.
+ browser.test.sendMessage("nextTest", expecting, tests.length);
+ }
+
+ if (expecting) {
+ // Check that the API returns the expected values, and then
+ // run the next test.
+ let details = await getDetails();
+
+ browser.test.assertEq(expecting.title, details.title,
+ "expected value from getTitle");
+
+ browser.test.assertEq(expecting.popup, details.popup,
+ "expected value from getPopup");
+ }
+
+ finish();
+ });
+ }
+
+ async function runTests() {
+ tabs = [];
+ tests = getTests(tabs);
+
+ let resultTabs = await browser.tabs.query({active: true, currentWindow: true});
+
+ tabs[0] = resultTabs[0].id;
+
+ nextTest();
+ }
+
+ browser.test.onMessage.addListener((msg) => {
+ if (msg == "runTests") {
+ runTests();
+ } else if (msg == "runNextTest") {
+ nextTest();
+ } else {
+ browser.test.fail(`Unexpected message: ${msg}`);
+ }
+ });
+
+ runTests();
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: options.manifest,
+
+ files: options.files || {},
+
+ background: `(${background})(${options.getTests})`,
+ });
+
+ let pageActionId;
+ let currentWindow = window;
+ let windows = [];
+
+ function checkDetails(details) {
+ let image = currentWindow.document.getElementById(pageActionId);
+ if (details == null) {
+ ok(image == null || image.hidden, "image is hidden");
+ } else {
+ ok(image, "image exists");
+
+ is(getListStyleImage(image), details.icon, "icon URL is correct");
+
+ let title = details.title || options.manifest.name;
+ is(image.getAttribute("tooltiptext"), title, "image title is correct");
+ is(image.getAttribute("aria-label"), title, "image aria-label is correct");
+ // TODO: Popup URL.
+ }
+ }
+
+ let testNewWindows = 1;
+
+ let awaitFinish = new Promise(resolve => {
+ extension.onMessage("nextTest", (expecting, testsRemaining) => {
+ if (!pageActionId) {
+ pageActionId = `${makeWidgetId(extension.id)}-page-action`;
+ }
+
+ checkDetails(expecting);
+
+ if (testsRemaining) {
+ extension.sendMessage("runNextTest");
+ } else if (testNewWindows) {
+ testNewWindows--;
+
+ BrowserTestUtils.openNewBrowserWindow().then(window => {
+ windows.push(window);
+ currentWindow = window;
+ return focusWindow(window);
+ }).then(() => {
+ extension.sendMessage("runTests");
+ });
+ } else {
+ resolve();
+ }
+ });
+ });
+
+ yield SpecialPowers.pushPrefEnv({set: [["general.useragent.locale", "es-ES"]]});
+
+ yield extension.startup();
+
+ yield awaitFinish;
+
+ yield extension.unload();
+
+ yield SpecialPowers.popPrefEnv();
+
+ let node = document.getElementById(pageActionId);
+ is(node, null, "pageAction image removed from document");
+
+ currentWindow = null;
+ for (let win of windows.splice(0)) {
+ node = win.document.getElementById(pageActionId);
+ is(node, null, "pageAction image removed from second document");
+
+ yield BrowserTestUtils.closeWindow(win);
+ }
+}
+
diff --git a/browser/components/extensions/test/browser/head_sessions.js b/browser/components/extensions/test/browser/head_sessions.js
new file mode 100644
index 000000000..ca3a86c24
--- /dev/null
+++ b/browser/components/extensions/test/browser/head_sessions.js
@@ -0,0 +1,47 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* exported recordInitialTimestamps onlyNewItemsFilter checkRecentlyClosed */
+
+let initialTimestamps = [];
+
+function recordInitialTimestamps(timestamps) {
+ initialTimestamps = timestamps;
+}
+
+function onlyNewItemsFilter(item) {
+ return !initialTimestamps.includes(item.lastModified);
+}
+
+function checkWindow(window) {
+ for (let prop of ["focused", "incognito", "alwaysOnTop"]) {
+ is(window[prop], false, `closed window has the expected value for ${prop}`);
+ }
+ for (let prop of ["state", "type"]) {
+ is(window[prop], "normal", `closed window has the expected value for ${prop}`);
+ }
+}
+
+function checkTab(tab, windowId, incognito) {
+ for (let prop of ["selected", "highlighted", "active", "pinned"]) {
+ is(tab[prop], false, `closed tab has the expected value for ${prop}`);
+ }
+ is(tab.windowId, windowId, "closed tab has the expected value for windowId");
+ is(tab.incognito, incognito, "closed tab has the expected value for incognito");
+}
+
+function checkRecentlyClosed(recentlyClosed, expectedCount, windowId, incognito = false) {
+ let sessionIds = new Set();
+ is(recentlyClosed.length, expectedCount, "the expected number of closed tabs/windows was found");
+ for (let item of recentlyClosed) {
+ if (item.window) {
+ sessionIds.add(item.window.sessionId);
+ checkWindow(item.window);
+ } else if (item.tab) {
+ sessionIds.add(item.tab.sessionId);
+ checkTab(item.tab, windowId, incognito);
+ }
+ }
+ is(sessionIds.size, expectedCount, "each item has a unique sessionId");
+}
diff --git a/browser/components/extensions/test/browser/searchSuggestionEngine.sjs b/browser/components/extensions/test/browser/searchSuggestionEngine.sjs
new file mode 100644
index 000000000..1978b4f66
--- /dev/null
+++ b/browser/components/extensions/test/browser/searchSuggestionEngine.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/components/extensions/test/browser/searchSuggestionEngine.xml b/browser/components/extensions/test/browser/searchSuggestionEngine.xml
new file mode 100644
index 000000000..703d45925
--- /dev/null
+++ b/browser/components/extensions/test/browser/searchSuggestionEngine.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/extensions/test/browser/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/components/extensions/test/mochitest/mochitest.ini b/browser/components/extensions/test/mochitest/mochitest.ini
new file mode 100644
index 000000000..39290db61
--- /dev/null
+++ b/browser/components/extensions/test/mochitest/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ ../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+tags = webextensions
+
+[test_ext_all_apis.html]
diff --git a/browser/components/extensions/test/mochitest/test_ext_all_apis.html b/browser/components/extensions/test/mochitest/test_ext_all_apis.html
new file mode 100644
index 000000000..176d380c2
--- /dev/null
+++ b/browser/components/extensions/test/mochitest/test_ext_all_apis.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>WebExtension test</title>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+"use strict";
+/* exported expectedContentApisTargetSpecific, expectedBackgroundApisTargetSpecific */
+let expectedContentApisTargetSpecific = [
+];
+
+let expectedBackgroundApisTargetSpecific = [
+ "tabs.MutedInfoReason",
+ "tabs.TAB_ID_NONE",
+ "tabs.TabStatus",
+ "tabs.WindowType",
+ "tabs.ZoomSettingsMode",
+ "tabs.ZoomSettingsScope",
+ "tabs.connect",
+ "tabs.create",
+ "tabs.detectLanguage",
+ "tabs.duplicate",
+ "tabs.executeScript",
+ "tabs.get",
+ "tabs.getCurrent",
+ "tabs.getZoom",
+ "tabs.getZoomSettings",
+ "tabs.highlight",
+ "tabs.insertCSS",
+ "tabs.move",
+ "tabs.onActivated",
+ "tabs.onAttached",
+ "tabs.onCreated",
+ "tabs.onDetached",
+ "tabs.onHighlighted",
+ "tabs.onMoved",
+ "tabs.onRemoved",
+ "tabs.onReplaced",
+ "tabs.onUpdated",
+ "tabs.onZoomChange",
+ "tabs.query",
+ "tabs.reload",
+ "tabs.remove",
+ "tabs.removeCSS",
+ "tabs.sendMessage",
+ "tabs.setZoom",
+ "tabs.setZoomSettings",
+ "tabs.update",
+ "windows.CreateType",
+ "windows.WINDOW_ID_CURRENT",
+ "windows.WINDOW_ID_NONE",
+ "windows.WindowState",
+ "windows.WindowType",
+ "windows.create",
+ "windows.get",
+ "windows.getAll",
+ "windows.getCurrent",
+ "windows.getLastFocused",
+ "windows.onCreated",
+ "windows.onFocusChanged",
+ "windows.onRemoved",
+ "windows.remove",
+ "windows.update",
+];
+</script>
+<script src="test_ext_all_apis.js"></script>
+
+</body>
+</html>
diff --git a/browser/components/extensions/test/xpcshell/.eslintrc.js b/browser/components/extensions/test/xpcshell/.eslintrc.js
new file mode 100644
index 000000000..2bfe540ea
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/.eslintrc.js
@@ -0,0 +1,9 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc.js",
+
+ "globals": {
+ "browser": false,
+ },
+};
diff --git a/browser/components/extensions/test/xpcshell/head.js b/browser/components/extensions/test/xpcshell/head.js
new file mode 100644
index 000000000..de4a4a3f6
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/head.js
@@ -0,0 +1,55 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+/* exported createHttpServer */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Extension",
+ "resource://gre/modules/Extension.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
+ "resource://gre/modules/Extension.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
+ "resource://gre/modules/ExtensionManagement.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils",
+ "resource://testing-common/ExtensionXPCShellUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+ "resource://testing-common/httpd.js");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
+ "resource://gre/modules/Schemas.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+ExtensionTestUtils.init(this);
+
+
+/**
+ * Creates a new HttpServer for testing, and begins listening on the
+ * specified port. Automatically shuts down the server when the test
+ * unit ends.
+ *
+ * @param {integer} [port]
+ * The port to listen on. If omitted, listen on a random
+ * port. The latter is the preferred behavior.
+ *
+ * @returns {HttpServer}
+ */
+function createHttpServer(port = -1) {
+ let server = new HttpServer();
+ server.start(port);
+
+ do_register_cleanup(() => {
+ return new Promise(resolve => {
+ server.stop(resolve);
+ });
+ });
+
+ return server;
+}
diff --git a/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js b/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
new file mode 100644
index 000000000..142c0a37c
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
@@ -0,0 +1,601 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function backgroundScript() {
+ let unsortedId, ourId;
+ let initialBookmarkCount = 0;
+ let createdBookmarks = new Set();
+ let createdFolderId;
+ let collectedEvents = [];
+ const nonExistentId = "000000000000";
+ const bookmarkGuids = {
+ menuGuid: "menu________",
+ toolbarGuid: "toolbar_____",
+ unfiledGuid: "unfiled_____",
+ };
+
+ function checkOurBookmark(bookmark) {
+ browser.test.assertEq(ourId, bookmark.id, "Bookmark has the expected Id");
+ browser.test.assertTrue("parentId" in bookmark, "Bookmark has a parentId");
+ browser.test.assertEq(0, bookmark.index, "Bookmark has the expected index"); // We assume there are no other bookmarks.
+ browser.test.assertEq("http://example.org/", bookmark.url, "Bookmark has the expected url");
+ browser.test.assertEq("test bookmark", bookmark.title, "Bookmark has the expected title");
+ browser.test.assertTrue("dateAdded" in bookmark, "Bookmark has a dateAdded");
+ browser.test.assertFalse("dateGroupModified" in bookmark, "Bookmark does not have a dateGroupModified");
+ browser.test.assertFalse("unmodifiable" in bookmark, "Bookmark is not unmodifiable");
+ }
+
+ function checkBookmark(expected, bookmark) {
+ browser.test.assertEq(expected.url, bookmark.url, "Bookmark has the expected url");
+ browser.test.assertEq(expected.title, bookmark.title, "Bookmark has the expected title");
+ browser.test.assertEq(expected.index, bookmark.index, "Bookmark has expected index");
+ if ("parentId" in expected) {
+ browser.test.assertEq(expected.parentId, bookmark.parentId, "Bookmark has the expected parentId");
+ }
+ }
+
+ function expectedError() {
+ browser.test.fail("Did not get expected error");
+ }
+
+ function checkOnCreated(id, parentId, index, title, url, dateAdded) {
+ let createdData = collectedEvents.pop();
+ browser.test.assertEq("onCreated", createdData.event, "onCreated was the last event received");
+ browser.test.assertEq(id, createdData.id, "onCreated event received the expected id");
+ let bookmark = createdData.bookmark;
+ browser.test.assertEq(id, bookmark.id, "onCreated event received the expected bookmark id");
+ browser.test.assertEq(parentId, bookmark.parentId, "onCreated event received the expected bookmark parentId");
+ browser.test.assertEq(index, bookmark.index, "onCreated event received the expected bookmark index");
+ browser.test.assertEq(title, bookmark.title, "onCreated event received the expected bookmark title");
+ browser.test.assertEq(url, bookmark.url, "onCreated event received the expected bookmark url");
+ browser.test.assertEq(dateAdded, bookmark.dateAdded, "onCreated event received the expected bookmark dateAdded");
+ }
+
+ function checkOnChanged(id, url, title) {
+ // If both url and title are changed, then url is fired last.
+ let changedData = collectedEvents.pop();
+ browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
+ browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
+ browser.test.assertEq(url, changedData.info.url, "onChanged event received the expected url");
+ // title is fired first.
+ changedData = collectedEvents.pop();
+ browser.test.assertEq("onChanged", changedData.event, "onChanged was the last event received");
+ browser.test.assertEq(id, changedData.id, "onChanged event received the expected id");
+ browser.test.assertEq(title, changedData.info.title, "onChanged event received the expected title");
+ }
+
+ function checkOnMoved(id, parentId, oldParentId, index, oldIndex) {
+ let movedData = collectedEvents.pop();
+ browser.test.assertEq("onMoved", movedData.event, "onMoved was the last event received");
+ browser.test.assertEq(id, movedData.id, "onMoved event received the expected id");
+ let info = movedData.info;
+ browser.test.assertEq(parentId, info.parentId, "onMoved event received the expected parentId");
+ browser.test.assertEq(oldParentId, info.oldParentId, "onMoved event received the expected oldParentId");
+ browser.test.assertEq(index, info.index, "onMoved event received the expected index");
+ browser.test.assertEq(oldIndex, info.oldIndex, "onMoved event received the expected oldIndex");
+ }
+
+ function checkOnRemoved(id, parentId, index, url) {
+ let removedData = collectedEvents.pop();
+ browser.test.assertEq("onRemoved", removedData.event, "onRemoved was the last event received");
+ browser.test.assertEq(id, removedData.id, "onRemoved event received the expected id");
+ let info = removedData.info;
+ browser.test.assertEq(parentId, removedData.info.parentId, "onRemoved event received the expected parentId");
+ browser.test.assertEq(index, removedData.info.index, "onRemoved event received the expected index");
+ let node = info.node;
+ browser.test.assertEq(id, node.id, "onRemoved event received the expected node id");
+ browser.test.assertEq(parentId, node.parentId, "onRemoved event received the expected node parentId");
+ browser.test.assertEq(index, node.index, "onRemoved event received the expected node index");
+ browser.test.assertEq(url, node.url, "onRemoved event received the expected node url");
+ }
+
+ browser.bookmarks.onChanged.addListener((id, info) => {
+ collectedEvents.push({event: "onChanged", id, info});
+ });
+
+ browser.bookmarks.onCreated.addListener((id, bookmark) => {
+ collectedEvents.push({event: "onCreated", id, bookmark});
+ });
+
+ browser.bookmarks.onMoved.addListener((id, info) => {
+ collectedEvents.push({event: "onMoved", id, info});
+ });
+
+ browser.bookmarks.onRemoved.addListener((id, info) => {
+ collectedEvents.push({event: "onRemoved", id, info});
+ });
+
+ browser.bookmarks.get(["not-a-bookmark-guid"]).then(expectedError, invalidGuidError => {
+ browser.test.assertTrue(
+ invalidGuidError.message.includes("Invalid value for property 'guid': not-a-bookmark-guid"),
+ "Expected error thrown when trying to get a bookmark using an invalid guid"
+ );
+
+ return browser.bookmarks.get([nonExistentId]).then(expectedError, nonExistentIdError => {
+ browser.test.assertTrue(
+ nonExistentIdError.message.includes("Bookmark not found"),
+ "Expected error thrown when trying to get a bookmark using a non-existent Id"
+ );
+ });
+ }).then(() => {
+ return browser.bookmarks.search({});
+ }).then(results => {
+ initialBookmarkCount = results.length;
+ return browser.bookmarks.create({title: "test bookmark", url: "http://example.org"});
+ }).then(result => {
+ ourId = result.id;
+ checkOurBookmark(result);
+ browser.test.assertEq(1, collectedEvents.length, "1 expected event received");
+ checkOnCreated(ourId, bookmarkGuids.unfiledGuid, 0, "test bookmark", "http://example.org/", result.dateAdded);
+
+ return browser.bookmarks.get(ourId);
+ }).then(results => {
+ browser.test.assertEq(results.length, 1);
+ checkOurBookmark(results[0]);
+
+ unsortedId = results[0].parentId;
+ return browser.bookmarks.get(unsortedId);
+ }).then(results => {
+ let folder = results[0];
+ browser.test.assertEq(1, results.length, "1 bookmark was returned");
+
+ browser.test.assertEq(unsortedId, folder.id, "Folder has the expected id");
+ browser.test.assertTrue("parentId" in folder, "Folder has a parentId");
+ browser.test.assertTrue("index" in folder, "Folder has an index");
+ browser.test.assertFalse("url" in folder, "Folder does not have a url");
+ browser.test.assertEq("Other Bookmarks", folder.title, "Folder has the expected title");
+ browser.test.assertTrue("dateAdded" in folder, "Folder has a dateAdded");
+ browser.test.assertTrue("dateGroupModified" in folder, "Folder has a dateGroupModified");
+ browser.test.assertFalse("unmodifiable" in folder, "Folder is not unmodifiable"); // TODO: Do we want to enable this?
+
+ return browser.bookmarks.getChildren(unsortedId);
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "The folder has one child");
+ checkOurBookmark(results[0]);
+
+ return browser.bookmarks.update(nonExistentId, {title: "new test title"}).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("No bookmarks found for the provided GUID"),
+ "Expected error thrown when trying to update a non-existent bookmark"
+ );
+
+ return browser.bookmarks.update(ourId, {title: "new test title", url: "http://example.com/"});
+ });
+ }).then(result => {
+ browser.test.assertEq("new test title", result.title, "Updated bookmark has the expected title");
+ browser.test.assertEq("http://example.com/", result.url, "Updated bookmark has the expected URL");
+ browser.test.assertEq(ourId, result.id, "Updated bookmark has the expected id");
+
+ browser.test.assertEq(2, collectedEvents.length, "2 expected events received");
+ checkOnChanged(ourId, "http://example.com/", "new test title");
+
+ return Promise.resolve().then(() => {
+ return browser.bookmarks.update(ourId, {url: "this is not a valid url"});
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Invalid bookmark:"),
+ "Expected error thrown when trying update with an invalid url"
+ );
+ return browser.bookmarks.getTree();
+ });
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "getTree returns one result");
+ let bookmark = results[0].children.find(bookmarkItem => bookmarkItem.id == unsortedId);
+ browser.test.assertEq(
+ "Other Bookmarks",
+ bookmark.title,
+ "Folder returned from getTree has the expected title"
+ );
+
+ return browser.bookmarks.create({parentId: "invalid"}).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Invalid bookmark"),
+ "Expected error thrown when trying to create a bookmark with an invalid parentId"
+ );
+ browser.test.assertTrue(
+ error.message.includes(`"parentGuid":"invalid"`),
+ "Expected error thrown when trying to create a bookmark with an invalid parentId"
+ );
+ });
+ }).then(() => {
+ return browser.bookmarks.remove(ourId);
+ }).then(result => {
+ browser.test.assertEq(undefined, result, "Removing a bookmark returns undefined");
+
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnRemoved(ourId, bookmarkGuids.unfiledGuid, 0, "http://example.com/");
+
+ return browser.bookmarks.get(ourId).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Bookmark not found"),
+ "Expected error thrown when trying to get a removed bookmark"
+ );
+ });
+ }).then(() => {
+ return browser.bookmarks.remove(nonExistentId).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("No bookmarks found for the provided GUID"),
+ "Expected error thrown when trying removed a non-existent bookmark"
+ );
+ });
+ }).then(() => {
+ // test bookmarks.search
+ return Promise.all([
+ browser.bookmarks.create({title: "MØzillä", url: "http://møzîllä.örg/"}),
+ browser.bookmarks.create({title: "Example", url: "http://example.org/"}),
+ browser.bookmarks.create({title: "Mozilla Folder"}),
+ browser.bookmarks.create({title: "EFF", url: "http://eff.org/"}),
+ browser.bookmarks.create({title: "Menu Item", url: "http://menu.org/", parentId: bookmarkGuids.menuGuid}),
+ browser.bookmarks.create({title: "Toolbar Item", url: "http://toolbar.org/", parentId: bookmarkGuids.toolbarGuid}),
+ ]);
+ }).then(results => {
+ browser.test.assertEq(6, collectedEvents.length, "6 expected events received");
+ checkOnCreated(results[5].id, bookmarkGuids.toolbarGuid, 0, "Toolbar Item", "http://toolbar.org/", results[5].dateAdded);
+ checkOnCreated(results[4].id, bookmarkGuids.menuGuid, 0, "Menu Item", "http://menu.org/", results[4].dateAdded);
+ checkOnCreated(results[3].id, bookmarkGuids.unfiledGuid, 0, "EFF", "http://eff.org/", results[3].dateAdded);
+ checkOnCreated(results[2].id, bookmarkGuids.unfiledGuid, 0, "Mozilla Folder", undefined, results[2].dateAdded);
+ checkOnCreated(results[1].id, bookmarkGuids.unfiledGuid, 0, "Example", "http://example.org/", results[1].dateAdded);
+ checkOnCreated(results[0].id, bookmarkGuids.unfiledGuid, 0, "MØzillä", "http://møzîllä.örg/", results[0].dateAdded);
+
+ for (let result of results) {
+ if (result.title !== "Mozilla Folder") {
+ createdBookmarks.add(result.id);
+ }
+ }
+ let folderResult = results[2];
+ createdFolderId = folderResult.id;
+ return Promise.all([
+ browser.bookmarks.create({title: "Mozilla", url: "http://allizom.org/", parentId: createdFolderId}),
+ browser.bookmarks.create({title: "Mozilla Corporation", url: "http://allizom.com/", parentId: createdFolderId}),
+ browser.bookmarks.create({title: "Firefox", url: "http://allizom.org/firefox/", parentId: createdFolderId}),
+ ]).then(newBookmarks => {
+ browser.test.assertEq(3, collectedEvents.length, "3 expected events received");
+ checkOnCreated(newBookmarks[2].id, createdFolderId, 0, "Firefox", "http://allizom.org/firefox/", newBookmarks[2].dateAdded);
+ checkOnCreated(newBookmarks[1].id, createdFolderId, 0, "Mozilla Corporation", "http://allizom.com/", newBookmarks[1].dateAdded);
+ checkOnCreated(newBookmarks[0].id, createdFolderId, 0, "Mozilla", "http://allizom.org/", newBookmarks[0].dateAdded);
+
+ return browser.bookmarks.create({
+ title: "About Mozilla",
+ url: "http://allizom.org/about/",
+ parentId: createdFolderId,
+ index: 1,
+ });
+ }).then(result => {
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnCreated(result.id, createdFolderId, 1, "About Mozilla", "http://allizom.org/about/", result.dateAdded);
+
+ // returns all items on empty object
+ return browser.bookmarks.search({});
+ }).then(bookmarksSearchResults => {
+ browser.test.assertTrue(bookmarksSearchResults.length >= 9, "At least as many bookmarks as added were returned by search({})");
+
+ return Promise.resolve().then(() => {
+ return browser.bookmarks.remove(createdFolderId);
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Cannot remove a non-empty folder"),
+ "Expected error thrown when trying to remove a non-empty folder"
+ );
+ return browser.bookmarks.getSubTree(createdFolderId);
+ });
+ });
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of nodes returned by getSubTree");
+ browser.test.assertEq("Mozilla Folder", results[0].title, "Folder has the expected title");
+ let children = results[0].children;
+ browser.test.assertEq(4, children.length, "Expected number of bookmarks returned by getSubTree");
+ browser.test.assertEq("Firefox", children[0].title, "Bookmark has the expected title");
+ browser.test.assertEq("About Mozilla", children[1].title, "Bookmark has the expected title");
+ browser.test.assertEq(1, children[1].index, "Bookmark has the expected index");
+ browser.test.assertEq("Mozilla Corporation", children[2].title, "Bookmark has the expected title");
+ browser.test.assertEq("Mozilla", children[3].title, "Bookmark has the expected title");
+
+ // throws an error for invalid query objects
+ Promise.resolve().then(() => {
+ return browser.bookmarks.search();
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Incorrect argument types for bookmarks.search"),
+ "Expected error thrown when trying to search with no arguments"
+ );
+ });
+
+ Promise.resolve().then(() => {
+ return browser.bookmarks.search(null);
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Incorrect argument types for bookmarks.search"),
+ "Expected error thrown when trying to search with null as an argument"
+ );
+ });
+
+ Promise.resolve().then(() => {
+ return browser.bookmarks.search(function() {});
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Incorrect argument types for bookmarks.search"),
+ "Expected error thrown when trying to search with a function as an argument"
+ );
+ });
+
+ Promise.resolve().then(() => {
+ return browser.bookmarks.search({banana: "banana"});
+ }).then(expectedError, error => {
+ let substr = `an unexpected "banana" property`;
+ browser.test.assertTrue(
+ error.message.includes(substr),
+ `Expected error ${JSON.stringify(error.message)} to contain ${JSON.stringify(substr)}`);
+ });
+
+ Promise.resolve().then(() => {
+ return browser.bookmarks.search({url: "spider-man vs. batman"});
+ }).then(expectedError, error => {
+ let substr = 'must match the format "url"';
+ browser.test.assertTrue(
+ error.message.includes(substr),
+ `Expected error ${JSON.stringify(error.message)} to contain ${JSON.stringify(substr)}`);
+ });
+
+ // queries the full url
+ return browser.bookmarks.search("http://example.org/");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for url search");
+ checkBookmark({title: "Example", url: "http://example.org/", index: 2}, results[0]);
+
+ // queries a partial url
+ return browser.bookmarks.search("example.org");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for url search");
+ checkBookmark({title: "Example", url: "http://example.org/", index: 2}, results[0]);
+
+ // queries the title
+ return browser.bookmarks.search("EFF");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for title search");
+ checkBookmark({title: "EFF", url: "http://eff.org/", index: 0, parentId: bookmarkGuids.unfiledGuid}, results[0]);
+
+ // finds menu items
+ return browser.bookmarks.search("Menu Item");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for menu item search");
+ checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 0, parentId: bookmarkGuids.menuGuid}, results[0]);
+
+ // finds toolbar items
+ return browser.bookmarks.search("Toolbar Item");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for toolbar item search");
+ checkBookmark({title: "Toolbar Item", url: "http://toolbar.org/", index: 0, parentId: bookmarkGuids.toolbarGuid}, results[0]);
+
+ // finds folders
+ return browser.bookmarks.search("Mozilla Folder");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of folders returned");
+ browser.test.assertEq("Mozilla Folder", results[0].title, "Folder has the expected title");
+
+ // is case-insensitive
+ return browser.bookmarks.search("corporation");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returnedfor case-insensitive search");
+ browser.test.assertEq("Mozilla Corporation", results[0].title, "Bookmark has the expected title");
+
+ // is case-insensitive for non-ascii
+ return browser.bookmarks.search("MøZILLÄ");
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for non-ascii search");
+ browser.test.assertEq("MØzillä", results[0].title, "Bookmark has the expected title");
+
+ // returns multiple results
+ return browser.bookmarks.search("allizom");
+ }).then(results => {
+ browser.test.assertEq(4, results.length, "Expected number of multiple results returned");
+ browser.test.assertEq("Mozilla", results[0].title, "Bookmark has the expected title");
+ browser.test.assertEq("Mozilla Corporation", results[1].title, "Bookmark has the expected title");
+ browser.test.assertEq("Firefox", results[2].title, "Bookmark has the expected title");
+ browser.test.assertEq("About Mozilla", results[3].title, "Bookmark has the expected title");
+
+ // accepts a url field
+ return browser.bookmarks.search({url: "http://allizom.com/"});
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for url field");
+ checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+
+ // normalizes urls
+ return browser.bookmarks.search({url: "http://allizom.com"});
+ }).then(results => {
+ browser.test.assertEq(results.length, 1, "Expected number of results returned for normalized url field");
+ checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+
+ // normalizes urls even more
+ return browser.bookmarks.search({url: "http:allizom.com"});
+ }).then(results => {
+ browser.test.assertEq(results.length, 1, "Expected number of results returned for normalized url field");
+ checkBookmark({title: "Mozilla Corporation", url: "http://allizom.com/", index: 2}, results[0]);
+
+ // accepts a title field
+ return browser.bookmarks.search({title: "Mozilla"});
+ }).then(results => {
+ browser.test.assertEq(results.length, 1, "Expected number of results returned for title field");
+ checkBookmark({title: "Mozilla", url: "http://allizom.org/", index: 3}, results[0]);
+
+ // can combine title and query
+ return browser.bookmarks.search({title: "Mozilla", query: "allizom"});
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "Expected number of results returned for title and query fields");
+ checkBookmark({title: "Mozilla", url: "http://allizom.org/", index: 3}, results[0]);
+
+ // uses AND conditions
+ return browser.bookmarks.search({title: "EFF", query: "allizom"});
+ }).then(results => {
+ browser.test.assertEq(
+ 0,
+ results.length,
+ "Expected number of results returned for non-matching title and query fields"
+ );
+
+ // returns an empty array on item not found
+ return browser.bookmarks.search("microsoft");
+ }).then(results => {
+ browser.test.assertEq(0, results.length, "Expected number of results returned for non-matching search");
+
+ return Promise.resolve().then(() => {
+ return browser.bookmarks.getRecent("");
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Incorrect argument types for bookmarks.getRecent"),
+ "Expected error thrown when calling getRecent with an empty string"
+ );
+ });
+ }).then(() => {
+ return Promise.resolve().then(() => {
+ return browser.bookmarks.getRecent(1.234);
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Incorrect argument types for bookmarks.getRecent"),
+ "Expected error thrown when calling getRecent with a decimal number"
+ );
+ });
+ }).then(() => {
+ return Promise.all([
+ browser.bookmarks.search("corporation"),
+ browser.bookmarks.getChildren(bookmarkGuids.menuGuid),
+ ]);
+ }).then(results => {
+ let corporationBookmark = results[0][0];
+ let childCount = results[1].length;
+
+ browser.test.assertEq(2, corporationBookmark.index, "Bookmark has the expected index");
+
+ return browser.bookmarks.move(corporationBookmark.id, {index: 0}).then(result => {
+ browser.test.assertEq(0, result.index, "Bookmark has the expected index");
+
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnMoved(corporationBookmark.id, createdFolderId, createdFolderId, 0, 2);
+
+ return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.menuGuid});
+ }).then(result => {
+ browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
+ browser.test.assertEq(childCount, result.index, "Bookmark has the expected index");
+
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, createdFolderId, 1, 0);
+
+ return browser.bookmarks.move(corporationBookmark.id, {index: 0});
+ }).then(result => {
+ browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
+ browser.test.assertEq(0, result.index, "Bookmark has the expected index");
+
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnMoved(corporationBookmark.id, bookmarkGuids.menuGuid, bookmarkGuids.menuGuid, 0, 1);
+
+ return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.toolbarGuid, index: 1});
+ }).then(result => {
+ browser.test.assertEq(bookmarkGuids.toolbarGuid, result.parentId, "Bookmark has the expected parent");
+ browser.test.assertEq(1, result.index, "Bookmark has the expected index");
+
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnMoved(corporationBookmark.id, bookmarkGuids.toolbarGuid, bookmarkGuids.menuGuid, 1, 0);
+
+ createdBookmarks.add(corporationBookmark.id);
+ });
+ }).then(() => {
+ return browser.bookmarks.getRecent(4);
+ }).then(results => {
+ browser.test.assertEq(4, results.length, "Expected number of results returned by getRecent");
+ let prevDate = results[0].dateAdded;
+ for (let bookmark of results) {
+ browser.test.assertTrue(bookmark.dateAdded <= prevDate, "The recent bookmarks are sorted by dateAdded");
+ prevDate = bookmark.dateAdded;
+ }
+ let bookmarksByTitle = results.sort((a, b) => {
+ return a.title.localeCompare(b.title);
+ });
+ browser.test.assertEq("About Mozilla", bookmarksByTitle[0].title, "Bookmark has the expected title");
+ browser.test.assertEq("Firefox", bookmarksByTitle[1].title, "Bookmark has the expected title");
+ browser.test.assertEq("Mozilla", bookmarksByTitle[2].title, "Bookmark has the expected title");
+ browser.test.assertEq("Mozilla Corporation", bookmarksByTitle[3].title, "Bookmark has the expected title");
+
+ return browser.bookmarks.search({});
+ }).then(results => {
+ let startBookmarkCount = results.length;
+
+ return browser.bookmarks.search({title: "Mozilla Folder"}).then(result => {
+ return browser.bookmarks.removeTree(result[0].id);
+ }).then(() => {
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnRemoved(createdFolderId, bookmarkGuids.unfiledGuid, 1);
+
+ return browser.bookmarks.search({}).then(searchResults => {
+ browser.test.assertEq(
+ startBookmarkCount - 4,
+ searchResults.length,
+ "Expected number of results returned after removeTree");
+ });
+ });
+ }).then(() => {
+ return browser.bookmarks.create({title: "Empty Folder"});
+ }).then(result => {
+ let emptyFolderId = result.id;
+
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnCreated(emptyFolderId, bookmarkGuids.unfiledGuid, 3, "Empty Folder", undefined, result.dateAdded);
+
+ browser.test.assertEq("Empty Folder", result.title, "Folder has the expected title");
+ return browser.bookmarks.remove(emptyFolderId).then(() => {
+ browser.test.assertEq(1, collectedEvents.length, "1 expected events received");
+ checkOnRemoved(emptyFolderId, bookmarkGuids.unfiledGuid, 3);
+
+ return browser.bookmarks.get(emptyFolderId).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("Bookmark not found"),
+ "Expected error thrown when trying to get a removed folder"
+ );
+ });
+ });
+ }).then(() => {
+ return browser.bookmarks.getChildren(nonExistentId).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("root is null"),
+ "Expected error thrown when trying to getChildren for a non-existent folder"
+ );
+ });
+ }).then(() => {
+ return Promise.resolve().then(() => {
+ return browser.bookmarks.move(nonExistentId, {});
+ }).then(expectedError, error => {
+ browser.test.assertTrue(
+ error.message.includes("No bookmarks found for the provided GUID"),
+ "Expected error thrown when calling move with a non-existent bookmark"
+ );
+ });
+ }).then(() => {
+ // remove all created bookmarks
+ let promises = Array.from(createdBookmarks, guid => browser.bookmarks.remove(guid));
+ return Promise.all(promises);
+ }).then(() => {
+ browser.test.assertEq(createdBookmarks.size, collectedEvents.length, "expected number of events received");
+
+ return browser.bookmarks.search({});
+ }).then(results => {
+ browser.test.assertEq(initialBookmarkCount, results.length, "All created bookmarks have been removed");
+
+ return browser.test.notifyPass("bookmarks");
+ }).catch(error => {
+ browser.test.fail(`Error: ${String(error)} :: ${error.stack}`);
+ browser.test.notifyFail("bookmarks");
+ });
+}
+
+let extensionData = {
+ background: `(${backgroundScript})()`,
+ manifest: {
+ permissions: ["bookmarks"],
+ },
+};
+
+add_task(function* test_contentscript() {
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ yield extension.startup();
+ yield extension.awaitFinish("bookmarks");
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/xpcshell/test_ext_history.js b/browser/components/extensions/test/xpcshell/test_ext_history.js
new file mode 100644
index 000000000..78df33151
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_history.js
@@ -0,0 +1,487 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
+ "resource://gre/modules/ExtensionUtils.jsm");
+
+add_task(function* test_delete() {
+ function background() {
+ let historyClearedCount = 0;
+ let removedUrls = [];
+
+ browser.history.onVisitRemoved.addListener(data => {
+ if (data.allHistory) {
+ historyClearedCount++;
+ browser.test.assertEq(0, data.urls.length, "onVisitRemoved received an empty urls array");
+ } else {
+ browser.test.assertEq(1, data.urls.length, "onVisitRemoved received one URL");
+ removedUrls.push(data.urls[0]);
+ }
+ });
+
+ browser.test.onMessage.addListener((msg, arg) => {
+ if (msg === "delete-url") {
+ browser.history.deleteUrl({url: arg}).then(result => {
+ browser.test.assertEq(undefined, result, "browser.history.deleteUrl returns nothing");
+ browser.test.sendMessage("url-deleted");
+ });
+ } else if (msg === "delete-range") {
+ browser.history.deleteRange(arg).then(result => {
+ browser.test.assertEq(undefined, result, "browser.history.deleteRange returns nothing");
+ browser.test.sendMessage("range-deleted", removedUrls);
+ });
+ } else if (msg === "delete-all") {
+ browser.history.deleteAll().then(result => {
+ browser.test.assertEq(undefined, result, "browser.history.deleteAll returns nothing");
+ browser.test.sendMessage("history-cleared", [historyClearedCount, removedUrls]);
+ });
+ }
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ const BASE_URL = "http://mozilla.com/test_history/";
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["history"],
+ },
+ background: `(${background})()`,
+ });
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+ yield PlacesTestUtils.clearHistory();
+
+ let historyClearedCount;
+ let visits = [];
+ let visitDate = new Date(1999, 9, 9, 9, 9).getTime();
+
+ function pushVisit(subvisits) {
+ visitDate += 1000;
+ subvisits.push({date: new Date(visitDate)});
+ }
+
+ // Add 5 visits for one uri and 3 visits for 3 others
+ for (let i = 0; i < 4; ++i) {
+ let visit = {
+ url: `${BASE_URL}${i}`,
+ title: "visit " + i,
+ visits: [],
+ };
+ if (i === 0) {
+ for (let j = 0; j < 5; ++j) {
+ pushVisit(visit.visits);
+ }
+ } else {
+ pushVisit(visit.visits);
+ }
+ visits.push(visit);
+ }
+
+ yield PlacesUtils.history.insertMany(visits);
+ equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 5, "5 visits for uri found in history database");
+
+ let testUrl = visits[2].url;
+ ok((yield PlacesTestUtils.isPageInDB(testUrl)), "expected url found in history database");
+
+ extension.sendMessage("delete-url", testUrl);
+ yield extension.awaitMessage("url-deleted");
+ equal((yield PlacesTestUtils.isPageInDB(testUrl)), false, "expected url not found in history database");
+
+ // delete 3 of the 5 visits for url 1
+ let filter = {
+ startTime: visits[0].visits[0].date,
+ endTime: visits[0].visits[2].date,
+ };
+
+ extension.sendMessage("delete-range", filter);
+ let removedUrls = yield extension.awaitMessage("range-deleted");
+ ok(!removedUrls.includes(visits[0].url), `${visits[0].url} not received by onVisitRemoved`);
+ ok((yield PlacesTestUtils.isPageInDB(visits[0].url)), "expected uri found in history database");
+ equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 2, "2 visits for uri found in history database");
+ ok((yield PlacesTestUtils.isPageInDB(visits[1].url)), "expected uri found in history database");
+ equal((yield PlacesTestUtils.visitsInDB(visits[1].url)), 1, "1 visit for uri found in history database");
+
+ // delete the rest of the visits for url 1, and the visit for url 2
+ filter.startTime = visits[0].visits[0].date;
+ filter.endTime = visits[1].visits[0].date;
+
+ extension.sendMessage("delete-range", filter);
+ yield extension.awaitMessage("range-deleted");
+
+ equal((yield PlacesTestUtils.isPageInDB(visits[0].url)), false, "expected uri not found in history database");
+ equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 0, "0 visits for uri found in history database");
+ equal((yield PlacesTestUtils.isPageInDB(visits[1].url)), false, "expected uri not found in history database");
+ equal((yield PlacesTestUtils.visitsInDB(visits[1].url)), 0, "0 visits for uri found in history database");
+
+ ok((yield PlacesTestUtils.isPageInDB(visits[3].url)), "expected uri found in history database");
+
+ extension.sendMessage("delete-all");
+ [historyClearedCount, removedUrls] = yield extension.awaitMessage("history-cleared");
+ equal(PlacesUtils.history.hasHistoryEntries, false, "history is empty");
+ equal(historyClearedCount, 2, "onVisitRemoved called for each clearing of history");
+ equal(removedUrls.length, 3, "onVisitRemoved called the expected number of times");
+ for (let i = 1; i < 3; ++i) {
+ let url = visits[i].url;
+ ok(removedUrls.includes(url), `${url} received by onVisitRemoved`);
+ }
+ yield extension.unload();
+});
+
+add_task(function* test_search() {
+ const SINGLE_VISIT_URL = "http://example.com/";
+ const DOUBLE_VISIT_URL = "http://example.com/2/";
+ const MOZILLA_VISIT_URL = "http://mozilla.com/";
+ const REFERENCE_DATE = new Date();
+ // pages/visits to add via History.insert
+ const PAGE_INFOS = [
+ {
+ url: SINGLE_VISIT_URL,
+ title: `test visit for ${SINGLE_VISIT_URL}`,
+ visits: [
+ {date: new Date(Number(REFERENCE_DATE) - 1000)},
+ ],
+ },
+ {
+ url: DOUBLE_VISIT_URL,
+ title: `test visit for ${DOUBLE_VISIT_URL}`,
+ visits: [
+ {date: REFERENCE_DATE},
+ {date: new Date(Number(REFERENCE_DATE) - 2000)},
+ ],
+ },
+ {
+ url: MOZILLA_VISIT_URL,
+ title: `test visit for ${MOZILLA_VISIT_URL}`,
+ visits: [
+ {date: new Date(Number(REFERENCE_DATE) - 3000)},
+ ],
+ },
+ ];
+
+ function background(BGSCRIPT_REFERENCE_DATE) {
+ const futureTime = Date.now() + 24 * 60 * 60 * 1000;
+
+ browser.test.onMessage.addListener(msg => {
+ browser.history.search({text: ""}).then(results => {
+ browser.test.sendMessage("empty-search", results);
+ return browser.history.search({text: "mozilla.com"});
+ }).then(results => {
+ browser.test.sendMessage("text-search", results);
+ return browser.history.search({text: "example.com", maxResults: 1});
+ }).then(results => {
+ browser.test.sendMessage("max-results-search", results);
+ return browser.history.search({text: "", startTime: BGSCRIPT_REFERENCE_DATE - 2000, endTime: BGSCRIPT_REFERENCE_DATE - 1000});
+ }).then(results => {
+ browser.test.sendMessage("date-range-search", results);
+ return browser.history.search({text: "", startTime: futureTime});
+ }).then(results => {
+ browser.test.assertEq(0, results.length, "no results returned for late start time");
+ return browser.history.search({text: "", endTime: 0});
+ }).then(results => {
+ browser.test.assertEq(0, results.length, "no results returned for early end time");
+ return browser.history.search({text: "", startTime: Date.now(), endTime: 0});
+ }).then(results => {
+ browser.test.fail("history.search rejects with startTime that is after the endTime");
+ }, error => {
+ browser.test.assertEq(
+ "The startTime cannot be after the endTime",
+ error.message,
+ "history.search rejects with startTime that is after the endTime");
+ }).then(() => {
+ browser.test.notifyPass("search");
+ });
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["history"],
+ },
+ background: `(${background})(${Number(REFERENCE_DATE)})`,
+ });
+
+ function findResult(url, results) {
+ return results.find(r => r.url === url);
+ }
+
+ function checkResult(results, url, expectedCount) {
+ let result = findResult(url, results);
+ notEqual(result, null, `history.search result was found for ${url}`);
+ equal(result.visitCount, expectedCount, `history.search reports ${expectedCount} visit(s)`);
+ equal(result.title, `test visit for ${url}`, "title for search result is correct");
+ }
+
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+ yield PlacesTestUtils.clearHistory();
+
+ yield PlacesUtils.history.insertMany(PAGE_INFOS);
+
+ extension.sendMessage("check-history");
+
+ let results = yield extension.awaitMessage("empty-search");
+ equal(results.length, 3, "history.search with empty text returned 3 results");
+ checkResult(results, SINGLE_VISIT_URL, 1);
+ checkResult(results, DOUBLE_VISIT_URL, 2);
+ checkResult(results, MOZILLA_VISIT_URL, 1);
+
+ results = yield extension.awaitMessage("text-search");
+ equal(results.length, 1, "history.search with specific text returned 1 result");
+ checkResult(results, MOZILLA_VISIT_URL, 1);
+
+ results = yield extension.awaitMessage("max-results-search");
+ equal(results.length, 1, "history.search with maxResults returned 1 result");
+ checkResult(results, DOUBLE_VISIT_URL, 2);
+
+ results = yield extension.awaitMessage("date-range-search");
+ equal(results.length, 2, "history.search with a date range returned 2 result");
+ checkResult(results, DOUBLE_VISIT_URL, 2);
+ checkResult(results, SINGLE_VISIT_URL, 1);
+
+ yield extension.awaitFinish("search");
+ yield extension.unload();
+ yield PlacesTestUtils.clearHistory();
+});
+
+add_task(function* test_add_url() {
+ function background() {
+ const TEST_DOMAIN = "http://example.com/";
+
+ browser.test.onMessage.addListener((msg, testData) => {
+ let [details, type] = testData;
+ details.url = details.url || `${TEST_DOMAIN}${type}`;
+ if (msg === "add-url") {
+ details.title = `Title for ${type}`;
+ browser.history.addUrl(details).then(() => {
+ return browser.history.search({text: details.url});
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "1 result found when searching for added URL");
+ browser.test.sendMessage("url-added", {details, result: results[0]});
+ });
+ } else if (msg === "expect-failure") {
+ let expectedMsg = testData[2];
+ browser.history.addUrl(details).then(() => {
+ browser.test.fail(`Expected error thrown for ${type}`);
+ }, error => {
+ browser.test.assertTrue(
+ error.message.includes(expectedMsg),
+ `"Expected error thrown when trying to add a URL with ${type}`
+ );
+ browser.test.sendMessage("add-failed");
+ });
+ }
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let addTestData = [
+ [{}, "default"],
+ [{visitTime: new Date()}, "with_date"],
+ [{visitTime: Date.now()}, "with_ms_number"],
+ [{visitTime: new Date().toISOString()}, "with_iso_string"],
+ [{transition: "typed"}, "valid_transition"],
+ ];
+
+ let failTestData = [
+ [{transition: "generated"}, "an invalid transition", "|generated| is not a supported transition for history"],
+ [{visitTime: Date.now() + 1000000}, "a future date", "cannot be a future date"],
+ [{url: "about.config"}, "an invalid url", "about.config is not a valid URL"],
+ ];
+
+ function* checkUrl(results) {
+ ok((yield PlacesTestUtils.isPageInDB(results.details.url)), `${results.details.url} found in history database`);
+ ok(PlacesUtils.isValidGuid(results.result.id), "URL was added with a valid id");
+ equal(results.result.title, results.details.title, "URL was added with the correct title");
+ if (results.details.visitTime) {
+ equal(results.result.lastVisitTime,
+ Number(ExtensionUtils.normalizeTime(results.details.visitTime)),
+ "URL was added with the correct date");
+ }
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["history"],
+ },
+ background: `(${background})()`,
+ });
+
+ yield PlacesTestUtils.clearHistory();
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ for (let data of addTestData) {
+ extension.sendMessage("add-url", data);
+ let results = yield extension.awaitMessage("url-added");
+ yield checkUrl(results);
+ }
+
+ for (let data of failTestData) {
+ extension.sendMessage("expect-failure", data);
+ yield extension.awaitMessage("add-failed");
+ }
+
+ yield extension.unload();
+});
+
+add_task(function* test_get_visits() {
+ function background() {
+ const TEST_DOMAIN = "http://example.com/";
+ const FIRST_DATE = Date.now();
+ const INITIAL_DETAILS = {
+ url: TEST_DOMAIN,
+ visitTime: FIRST_DATE,
+ transition: "link",
+ };
+
+ let visitIds = new Set();
+
+ function checkVisit(visit, expected) {
+ visitIds.add(visit.visitId);
+ browser.test.assertEq(expected.visitTime, visit.visitTime, "visit has the correct visitTime");
+ browser.test.assertEq(expected.transition, visit.transition, "visit has the correct transition");
+ browser.history.search({text: expected.url}).then(results => {
+ // all results will have the same id, so we only need to use the first one
+ browser.test.assertEq(results[0].id, visit.id, "visit has the correct id");
+ });
+ }
+
+ let details = Object.assign({}, INITIAL_DETAILS);
+
+ browser.history.addUrl(details).then(() => {
+ return browser.history.getVisits({url: details.url});
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "the expected number of visits were returned");
+ checkVisit(results[0], details);
+ details.url = `${TEST_DOMAIN}/1/`;
+ return browser.history.addUrl(details);
+ }).then(() => {
+ return browser.history.getVisits({url: details.url});
+ }).then(results => {
+ browser.test.assertEq(1, results.length, "the expected number of visits were returned");
+ checkVisit(results[0], details);
+ details.visitTime = FIRST_DATE - 1000;
+ details.transition = "typed";
+ return browser.history.addUrl(details);
+ }).then(() => {
+ return browser.history.getVisits({url: details.url});
+ }).then(results => {
+ browser.test.assertEq(2, results.length, "the expected number of visits were returned");
+ checkVisit(results[0], INITIAL_DETAILS);
+ checkVisit(results[1], details);
+ }).then(() => {
+ browser.test.assertEq(3, visitIds.size, "each visit has a unique visitId");
+ browser.test.notifyPass("get-visits");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["history"],
+ },
+ background: `(${background})()`,
+ });
+
+ yield PlacesTestUtils.clearHistory();
+ yield extension.startup();
+
+ yield extension.awaitFinish("get-visits");
+ yield extension.unload();
+});
+
+add_task(function* test_on_visited() {
+ const SINGLE_VISIT_URL = "http://example.com/1/";
+ const DOUBLE_VISIT_URL = "http://example.com/2/";
+ let visitDate = new Date(1999, 9, 9, 9, 9).getTime();
+
+ // pages/visits to add via History.insertMany
+ const PAGE_INFOS = [
+ {
+ url: SINGLE_VISIT_URL,
+ title: `visit to ${SINGLE_VISIT_URL}`,
+ visits: [
+ {date: new Date(visitDate)},
+ ],
+ },
+ {
+ url: DOUBLE_VISIT_URL,
+ title: `visit to ${DOUBLE_VISIT_URL}`,
+ visits: [
+ {date: new Date(visitDate += 1000)},
+ {date: new Date(visitDate += 1000)},
+ ],
+ },
+ ];
+
+ function background() {
+ let onVisitedData = [];
+
+ browser.history.onVisited.addListener(data => {
+ if (data.url.includes("moz-extension")) {
+ return;
+ }
+ onVisitedData.push(data);
+ if (onVisitedData.length == 3) {
+ browser.test.sendMessage("on-visited-data", onVisitedData);
+ }
+ });
+
+ browser.test.sendMessage("ready");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["history"],
+ },
+ background: `(${background})()`,
+ });
+
+ yield PlacesTestUtils.clearHistory();
+ yield extension.startup();
+ yield extension.awaitMessage("ready");
+
+ yield PlacesUtils.history.insertMany(PAGE_INFOS);
+
+ let onVisitedData = yield extension.awaitMessage("on-visited-data");
+
+ function checkOnVisitedData(index, expected) {
+ let onVisited = onVisitedData[index];
+ ok(PlacesUtils.isValidGuid(onVisited.id), "onVisited received a valid id");
+ equal(onVisited.url, expected.url, "onVisited received the expected url");
+ // Title will be blank until bug 1287928 lands
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1287928
+ equal(onVisited.title, "", "onVisited received a blank title");
+ equal(onVisited.lastVisitTime, expected.time, "onVisited received the expected time");
+ equal(onVisited.visitCount, expected.visitCount, "onVisited received the expected visitCount");
+ }
+
+ let expected = {
+ url: PAGE_INFOS[0].url,
+ title: PAGE_INFOS[0].title,
+ time: PAGE_INFOS[0].visits[0].date.getTime(),
+ visitCount: 1,
+ };
+ checkOnVisitedData(0, expected);
+
+ expected.url = PAGE_INFOS[1].url;
+ expected.title = PAGE_INFOS[1].title;
+ expected.time = PAGE_INFOS[1].visits[0].date.getTime();
+ checkOnVisitedData(1, expected);
+
+ expected.time = PAGE_INFOS[1].visits[1].date.getTime();
+ expected.visitCount = 2;
+ checkOnVisitedData(2, expected);
+
+ yield extension.unload();
+});
diff --git a/browser/components/extensions/test/xpcshell/test_ext_manifest_commands.js b/browser/components/extensions/test/xpcshell/test_ext_manifest_commands.js
new file mode 100644
index 000000000..4de7afe01
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_manifest_commands.js
@@ -0,0 +1,24 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+
+add_task(function* test_manifest_commands() {
+ let normalized = yield ExtensionTestUtils.normalizeManifest({
+ "commands": {
+ "toggle-feature": {
+ "suggested_key": {"default": "Shifty+Y"},
+ "description": "Send a 'toggle-feature' event to the extension",
+ },
+ },
+ });
+
+ let expectedError = (
+ String.raw`commands.toggle-feature.suggested_key.default: Value must either: ` +
+ String.raw`match the pattern /^\s*(Alt|Ctrl|Command|MacCtrl)\s*\+\s*(Shift\s*\+\s*)?([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)\s*$/, or ` +
+ String.raw`match the pattern /^(MediaNextTrack|MediaPlayPause|MediaPrevTrack|MediaStop)$/`
+ );
+
+ ok(normalized.error.includes(expectedError),
+ `The manifest error ${JSON.stringify(normalized.error)} must contain ${JSON.stringify(expectedError)}`);
+});
diff --git a/browser/components/extensions/test/xpcshell/test_ext_manifest_omnibox.js b/browser/components/extensions/test/xpcshell/test_ext_manifest_omnibox.js
new file mode 100644
index 000000000..2cb141235
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_manifest_omnibox.js
@@ -0,0 +1,61 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* testKeyword(params) {
+ let normalized = yield ExtensionTestUtils.normalizeManifest({
+ "omnibox": {
+ "keyword": params.keyword,
+ },
+ });
+
+ if (params.expectError) {
+ let expectedError = (
+ String.raw`omnibox.keyword: String "${params.keyword}" ` +
+ String.raw`must match /^[^?\s:]([^\s:]*[^/\s:])?$/`
+ );
+ ok(normalized.error.includes(expectedError),
+ `The manifest error ${JSON.stringify(normalized.error)} ` +
+ `must contain ${JSON.stringify(expectedError)}`);
+ } else {
+ equal(normalized.error, undefined, "Should not have an error");
+ equal(normalized.errors.length, 0, "Should not have warnings");
+ }
+}
+
+add_task(function* test_manifest_commands() {
+ // accepted single character keywords
+ yield testKeyword({keyword: "a", expectError: false});
+ yield testKeyword({keyword: "-", expectError: false});
+ yield testKeyword({keyword: "嗨", expectError: false});
+ yield testKeyword({keyword: "*", expectError: false});
+ yield testKeyword({keyword: "/", expectError: false});
+
+ // rejected single character keywords
+ yield testKeyword({keyword: "?", expectError: true});
+ yield testKeyword({keyword: " ", expectError: true});
+ yield testKeyword({keyword: ":", expectError: true});
+
+ // accepted multi-character keywords
+ yield testKeyword({keyword: "aa", expectError: false});
+ yield testKeyword({keyword: "http", expectError: false});
+ yield testKeyword({keyword: "f?a", expectError: false});
+ yield testKeyword({keyword: "fa?", expectError: false});
+ yield testKeyword({keyword: "f/x", expectError: false});
+ yield testKeyword({keyword: "/fx", expectError: false});
+
+ // rejected multi-character keywords
+ yield testKeyword({keyword: " a", expectError: true});
+ yield testKeyword({keyword: "a ", expectError: true});
+ yield testKeyword({keyword: " ", expectError: true});
+ yield testKeyword({keyword: " a ", expectError: true});
+ yield testKeyword({keyword: "?fx", expectError: true});
+ yield testKeyword({keyword: "fx/", expectError: true});
+ yield testKeyword({keyword: "f:x", expectError: true});
+ yield testKeyword({keyword: "fx:", expectError: true});
+ yield testKeyword({keyword: "f x", expectError: true});
+
+ // miscellaneous tests
+ yield testKeyword({keyword: "こんにちは", expectError: false});
+ yield testKeyword({keyword: "http://", expectError: true});
+});
diff --git a/browser/components/extensions/test/xpcshell/test_ext_manifest_permissions.js b/browser/components/extensions/test/xpcshell/test_ext_manifest_permissions.js
new file mode 100644
index 000000000..2c436535d
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_manifest_permissions.js
@@ -0,0 +1,57 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* globals chrome */
+
+function* testPermission(options) {
+ function background(bgOptions) {
+ browser.test.sendMessage("typeof-namespace", {
+ browser: typeof browser[bgOptions.namespace],
+ chrome: typeof chrome[bgOptions.namespace],
+ });
+ }
+
+ let extensionDetails = {
+ background: `(${background})(${JSON.stringify(options)})`,
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionDetails);
+
+ yield extension.startup();
+
+ let types = yield extension.awaitMessage("typeof-namespace");
+ equal(types.browser, "undefined", `Type of browser.${options.namespace} without manifest entry`);
+ equal(types.chrome, "undefined", `Type of chrome.${options.namespace} without manifest entry`);
+
+ yield extension.unload();
+
+ extensionDetails.manifest = options.manifest;
+ extension = ExtensionTestUtils.loadExtension(extensionDetails);
+
+ yield extension.startup();
+
+ types = yield extension.awaitMessage("typeof-namespace");
+ equal(types.browser, "object", `Type of browser.${options.namespace} with manifest entry`);
+ equal(types.chrome, "object", `Type of chrome.${options.namespace} with manifest entry`);
+
+ yield extension.unload();
+}
+
+add_task(function* test_browserAction() {
+ yield testPermission({
+ namespace: "browserAction",
+ manifest: {
+ browser_action: {},
+ },
+ });
+});
+
+add_task(function* test_pageAction() {
+ yield testPermission({
+ namespace: "pageAction",
+ manifest: {
+ page_action: {},
+ },
+ });
+});
diff --git a/browser/components/extensions/test/xpcshell/xpcshell.ini b/browser/components/extensions/test/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..b9148a697
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head = head.js
+tail =
+firefox-appdir = browser
+tags = webextensions
+
+[test_ext_bookmarks.js]
+[test_ext_history.js]
+[test_ext_manifest_commands.js]
+[test_ext_manifest_omnibox.js]
+[test_ext_manifest_permissions.js]
diff --git a/browser/components/feeds/BrowserFeeds.manifest b/browser/components/feeds/BrowserFeeds.manifest
new file mode 100644
index 000000000..ac5c299fa
--- /dev/null
+++ b/browser/components/feeds/BrowserFeeds.manifest
@@ -0,0 +1,25 @@
+# This component must restrict its registration for the app-startup category
+# to the specific list of apps that use it so it doesn't get loaded in xpcshell.
+# Thus we restrict it to these apps:
+#
+# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
+# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
+# graphene: {d1bfe7d9-c01e-4237-998b-7b5f960a4314}
+
+component {229fa115-9412-4d32-baf3-2fc407f76fb1} FeedConverter.js
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.video.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.audio.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
+component {2376201c-bbc6-472f-9b62-7548040a61c6} FeedConverter.js
+contract @mozilla.org/browser/feeds/result-service;1 {2376201c-bbc6-472f-9b62-7548040a61c6}
+component {4f91ef2e-57ba-472e-ab7a-b4999e42d6c0} FeedConverter.js
+contract @mozilla.org/network/protocol;1?name=feed {4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}
+component {1c31ed79-accd-4b94-b517-06e0c81999d5} FeedConverter.js
+contract @mozilla.org/network/protocol;1?name=pcast {1c31ed79-accd-4b94-b517-06e0c81999d5}
+component {49bb6593-3aff-4eb3-a068-2712c28bd58e} FeedWriter.js
+contract @mozilla.org/browser/feeds/result-writer;1 {49bb6593-3aff-4eb3-a068-2712c28bd58e}
+component {792a7e82-06a0-437c-af63-b2d12e808acc} WebContentConverter.js
+contract @mozilla.org/embeddor.implemented/web-content-handler-registrar;1 {792a7e82-06a0-437c-af63-b2d12e808acc}
+category app-startup WebContentConverter service,@mozilla.org/embeddor.implemented/web-content-handler-registrar;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} application={d1bfe7d9-c01e-4237-998b-7b5f960a4314}
diff --git a/browser/components/feeds/FeedConverter.js b/browser/components/feeds/FeedConverter.js
new file mode 100644
index 000000000..aa70620d4
--- /dev/null
+++ b/browser/components/feeds/FeedConverter.js
@@ -0,0 +1,568 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/debug.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+function LOG(str) {
+ dump("*** " + str + "\n");
+}
+
+const FS_CONTRACTID = "@mozilla.org/browser/feeds/result-service;1";
+const FPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=feed";
+const PCPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=pcast";
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_ANY = "*/*";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+function getPrefAppForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_APP;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_APP;
+
+ default:
+ return PREF_SELECTED_APP;
+ }
+}
+
+function getPrefWebForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_WEB;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_WEB;
+
+ default:
+ return PREF_SELECTED_WEB;
+ }
+}
+
+function getPrefActionForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_ACTION;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_ACTION;
+
+ default:
+ return PREF_SELECTED_ACTION;
+ }
+}
+
+function getPrefReaderForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_READER;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_READER;
+
+ default:
+ return PREF_SELECTED_READER;
+ }
+}
+
+function safeGetCharPref(pref, defaultValue) {
+ var prefs =
+ Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ try {
+ return prefs.getCharPref(pref);
+ }
+ catch (e) {
+ }
+ return defaultValue;
+}
+
+function FeedConverter() {
+}
+FeedConverter.prototype = {
+ classID: Components.ID("{229fa115-9412-4d32-baf3-2fc407f76fb1}"),
+
+ /**
+ * This is the downloaded text data for the feed.
+ */
+ _data: null,
+
+ /**
+ * This is the object listening to the conversion, which is ultimately the
+ * docshell for the load.
+ */
+ _listener: null,
+
+ /**
+ * Records if the feed was sniffed
+ */
+ _sniffed: false,
+
+ /**
+ * See nsIStreamConverter.idl
+ */
+ convert(sourceStream, sourceType, destinationType,
+ context) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * See nsIStreamConverter.idl
+ */
+ asyncConvertData(sourceType, destinationType,
+ listener, context) {
+ this._listener = listener;
+ },
+
+ /**
+ * Whether or not the preview page is being forced.
+ */
+ _forcePreviewPage: false,
+
+ /**
+ * Release our references to various things once we're done using them.
+ */
+ _releaseHandles() {
+ this._listener = null;
+ this._request = null;
+ this._processor = null;
+ },
+
+ /**
+ * See nsIFeedResultListener.idl
+ */
+ handleResult(result) {
+ // Feeds come in various content types, which our feed sniffer coerces to
+ // the maybe.feed type. However, feeds are used as a transport for
+ // different data types, e.g. news/blogs (traditional feed), video/audio
+ // (podcasts) and photos (photocasts, photostreams). Each of these is
+ // different in that there's a different class of application suitable for
+ // handling feeds of that type, but without a content-type differentiation
+ // it is difficult for us to disambiguate.
+ //
+ // The other problem is that if the user specifies an auto-action handler
+ // for one feed application, the fact that the content type is shared means
+ // that all other applications will auto-load with that handler too,
+ // regardless of the content-type.
+ //
+ // This means that content-type alone is not enough to determine whether
+ // or not a feed should be auto-handled. This means that for feeds we need
+ // to always use this stream converter, even when an auto-action is
+ // specified, not the basic one provided by WebContentConverter. This
+ // converter needs to consume all of the data and parse it, and based on
+ // that determination make a judgment about type.
+ //
+ // Since there are no content types for this content, and I'm not going to
+ // invent any, the upshot is that while a user can set an auto-handler for
+ // generic feed content, the system will prevent them from setting an auto-
+ // handler for other stream types. In those cases, the user will always see
+ // the preview page and have to select a handler. We can guess and show
+ // a client handler, but will not be able to show web handlers for those
+ // types.
+ //
+ // If this is just a feed, not some kind of specialized application, then
+ // auto-handlers can be set and we should obey them.
+ try {
+ let feedService =
+ Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+ if (!this._forcePreviewPage && result.doc) {
+ let feed = result.doc.QueryInterface(Ci.nsIFeed);
+ let handler = safeGetCharPref(getPrefActionForType(feed.type), "ask");
+
+ if (handler != "ask") {
+ if (handler == "reader")
+ handler = safeGetCharPref(getPrefReaderForType(feed.type), "bookmarks");
+ switch (handler) {
+ case "web":
+ let wccr =
+ Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService);
+ if ((feed.type == Ci.nsIFeed.TYPE_FEED &&
+ wccr.getAutoHandler(TYPE_MAYBE_FEED)) ||
+ (feed.type == Ci.nsIFeed.TYPE_VIDEO &&
+ wccr.getAutoHandler(TYPE_MAYBE_VIDEO_FEED)) ||
+ (feed.type == Ci.nsIFeed.TYPE_AUDIO &&
+ wccr.getAutoHandler(TYPE_MAYBE_AUDIO_FEED))) {
+ wccr.loadPreferredHandler(this._request);
+ return;
+ }
+ break;
+
+ default:
+ LOG("unexpected handler: " + handler);
+ // fall through -- let feed service handle error
+ case "bookmarks":
+ case "client":
+ case "default":
+ try {
+ let title = feed.title ? feed.title.plainText() : "";
+ let desc = feed.subtitle ? feed.subtitle.plainText() : "";
+ feedService.addToClientReader(result.uri.spec, title, desc, feed.type, handler);
+ return;
+ } catch (ex) { /* fallback to preview mode */ }
+ }
+ }
+ }
+
+ let ios =
+ Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let chromeChannel;
+
+ // handling a redirect, hence forwarding the loadInfo from the old channel
+ // to the newchannel.
+ let oldChannel = this._request.QueryInterface(Ci.nsIChannel);
+ let loadInfo = oldChannel.loadInfo;
+
+ // If there was no automatic handler, or this was a podcast,
+ // photostream or some other kind of application, show the preview page
+ // if the parser returned a document.
+ if (result.doc) {
+
+ // Store the result in the result service so that the display
+ // page can access it.
+ feedService.addFeedResult(result);
+
+ // Now load the actual XUL document.
+ let aboutFeedsURI = ios.newURI("about:feeds", null, null);
+ chromeChannel = ios.newChannelFromURIWithLoadInfo(aboutFeedsURI, loadInfo);
+ chromeChannel.originalURI = result.uri;
+
+ // carry the origin attributes from the channel that loaded the feed.
+ chromeChannel.owner =
+ Services.scriptSecurityManager.createCodebasePrincipal(aboutFeedsURI,
+ loadInfo.originAttributes);
+ } else {
+ chromeChannel = ios.newChannelFromURIWithLoadInfo(result.uri, loadInfo);
+ }
+
+ chromeChannel.loadGroup = this._request.loadGroup;
+ chromeChannel.asyncOpen(this._listener, null);
+ }
+ finally {
+ this._releaseHandles();
+ }
+ },
+
+ /**
+ * See nsIStreamListener.idl
+ */
+ onDataAvailable(request, context, inputStream,
+ sourceOffset, count) {
+ if (this._processor)
+ this._processor.onDataAvailable(request, context, inputStream,
+ sourceOffset, count);
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStartRequest(request, context) {
+ let channel = request.QueryInterface(Ci.nsIChannel);
+
+ // Check for a header that tells us there was no sniffing
+ // The value doesn't matter.
+ try {
+ let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ // Make sure to check requestSucceeded before the potentially-throwing
+ // getResponseHeader.
+ if (!httpChannel.requestSucceeded) {
+ // Just give up, but don't forget to cancel the channel first!
+ request.cancel(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ // Note: this throws if the header is not set.
+ httpChannel.getResponseHeader("X-Moz-Is-Feed");
+ }
+ catch (ex) {
+ this._sniffed = true;
+ }
+
+ this._request = request;
+
+ // Save and reset the forced state bit early, in case there's some kind of
+ // error.
+ let feedService =
+ Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+ this._forcePreviewPage = feedService.forcePreviewPage;
+ feedService.forcePreviewPage = false;
+
+ // Parse feed data as it comes in
+ this._processor =
+ Cc["@mozilla.org/feed-processor;1"].
+ createInstance(Ci.nsIFeedProcessor);
+ this._processor.listener = this;
+ this._processor.parseAsync(null, channel.URI);
+
+ this._processor.onStartRequest(request, context);
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStopRequest(request, context, status) {
+ if (this._processor)
+ this._processor.onStopRequest(request, context, status);
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsIFeedResultListener) ||
+ iid.equals(Ci.nsIStreamConverter) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver)||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+};
+
+/**
+ * Keeps parsed FeedResults around for use elsewhere in the UI after the stream
+ * converter completes.
+ */
+function FeedResultService() {
+}
+
+FeedResultService.prototype = {
+ classID: Components.ID("{2376201c-bbc6-472f-9b62-7548040a61c6}"),
+
+ /**
+ * A URI spec -> [nsIFeedResult] hash. We have to keep a list as the
+ * value in case the same URI is requested concurrently.
+ */
+ _results: { },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ forcePreviewPage: false,
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ addToClientReader(spec, title, subtitle, feedType, feedReader) {
+ if (!feedReader) {
+ feedReader = "default";
+ }
+
+ let handler = safeGetCharPref(getPrefActionForType(feedType), "bookmarks");
+ if (handler == "ask" || handler == "reader")
+ handler = feedReader;
+
+ switch (handler) {
+ case "client":
+ Services.cpmm.sendAsyncMessage("FeedConverter:ExecuteClientApp",
+ { spec,
+ title,
+ subtitle,
+ feedHandler: getPrefAppForType(feedType) });
+ break;
+ case "default":
+ // Default system feed reader
+ Services.cpmm.sendAsyncMessage("FeedConverter:ExecuteClientApp",
+ { spec,
+ title,
+ subtitle,
+ feedHandler: "default" });
+ break;
+ default:
+ // "web" should have been handled elsewhere
+ LOG("unexpected handler: " + handler);
+ // fall through
+ case "bookmarks":
+ Services.cpmm.sendAsyncMessage("FeedConverter:addLiveBookmark",
+ { spec, title, subtitle });
+ break;
+ }
+ },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ addFeedResult(feedResult) {
+ NS_ASSERT(feedResult.uri != null, "null URI!");
+ NS_ASSERT(feedResult.uri != null, "null feedResult!");
+ let spec = feedResult.uri.spec;
+ if (!this._results[spec])
+ this._results[spec] = [];
+ this._results[spec].push(feedResult);
+ },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ getFeedResult(uri) {
+ NS_ASSERT(uri != null, "null URI!");
+ let resultList = this._results[uri.spec];
+ for (let result of resultList) {
+ if (result.uri == uri)
+ return result;
+ }
+ return null;
+ },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ removeFeedResult(uri) {
+ NS_ASSERT(uri != null, "null URI!");
+ let resultList = this._results[uri.spec];
+ if (!resultList)
+ return;
+ let deletions = 0;
+ for (let i = 0; i < resultList.length; ++i) {
+ if (resultList[i].uri == uri) {
+ delete resultList[i];
+ ++deletions;
+ }
+ }
+
+ // send the holes to the end
+ resultList.sort();
+ // and trim the list
+ resultList.splice(resultList.length - deletions, deletions);
+ if (resultList.length == 0)
+ delete this._results[uri.spec];
+ },
+
+ createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsIFeedResultService) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+};
+
+/**
+ * A protocol handler that attempts to deal with the variant forms of feed:
+ * URIs that are actually either http or https.
+ */
+function GenericProtocolHandler() {
+}
+GenericProtocolHandler.prototype = {
+ _init(scheme) {
+ let ios =
+ Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ this._http = ios.getProtocolHandler("http");
+ this._scheme = scheme;
+ },
+
+ get scheme() {
+ return this._scheme;
+ },
+
+ get protocolFlags() {
+ let {URI_DANGEROUS_TO_LOAD, ALLOWS_PROXY_HTTP, ALLOWS_PROXY} =
+ Ci.nsIProtocolHandler;
+ return URI_DANGEROUS_TO_LOAD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP;
+ },
+
+ get defaultPort() {
+ return this._http.defaultPort;
+ },
+
+ allowPort(port, scheme) {
+ return this._http.allowPort(port, scheme);
+ },
+
+ newURI(spec, originalCharset, baseURI) {
+ // Feed URIs can be either nested URIs of the form feed:realURI (in which
+ // case we create a nested URI for the realURI) or feed://example.com, in
+ // which case we create a nested URI for the real protocol which is http.
+
+ let scheme = this._scheme + ":";
+ if (spec.substr(0, scheme.length) != scheme)
+ throw Cr.NS_ERROR_MALFORMED_URI;
+
+ let prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : "";
+ let inner = Services.io.newURI(spec.replace(scheme, prefix),
+ originalCharset, baseURI);
+
+ if (!["http", "https"].includes(inner.scheme))
+ throw Cr.NS_ERROR_MALFORMED_URI;
+
+ let uri = Services.io.QueryInterface(Ci.nsINetUtil).newSimpleNestedURI(inner);
+ uri.spec = inner.spec.replace(prefix, scheme);
+ return uri;
+ },
+
+ newChannel2(aUri, aLoadInfo) {
+ let inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
+ let channel = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).
+ newChannelFromURIWithLoadInfo(inner, aLoadInfo);
+
+ if (channel instanceof Components.interfaces.nsIHttpChannel)
+ // Set this so we know this is supposed to be a feed
+ channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
+ channel.originalURI = aUri;
+ return channel;
+ },
+
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsIProtocolHandler) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function FeedProtocolHandler() {
+ this._init('feed');
+}
+FeedProtocolHandler.prototype = new GenericProtocolHandler();
+FeedProtocolHandler.prototype.classID = Components.ID("{4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}");
+
+function PodCastProtocolHandler() {
+ this._init('pcast');
+}
+PodCastProtocolHandler.prototype = new GenericProtocolHandler();
+PodCastProtocolHandler.prototype.classID = Components.ID("{1c31ed79-accd-4b94-b517-06e0c81999d5}");
+
+var components = [FeedConverter,
+ FeedResultService,
+ FeedProtocolHandler,
+ PodCastProtocolHandler];
+
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/browser/components/feeds/FeedWriter.js b/browser/components/feeds/FeedWriter.js
new file mode 100644
index 000000000..20f1399b0
--- /dev/null
+++ b/browser/components/feeds/FeedWriter.js
@@ -0,0 +1,1007 @@
+/* -*- 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/NetUtil.jsm");
+
+const FEEDWRITER_CID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
+const FEEDWRITER_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
+
+function LOG(str) {
+ let prefB = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+ let shouldLog = false;
+ try {
+ shouldLog = prefB.getBoolPref("feeds.log");
+ }
+ catch (ex) {
+ }
+
+ if (shouldLog)
+ dump("*** Feeds: " + str + "\n");
+}
+
+/**
+ * Wrapper function for nsIIOService::newURI.
+ * @param aURLSpec
+ * The URL string from which to create an nsIURI.
+ * @returns an nsIURI object, or null if the creation of the URI failed.
+ */
+function makeURI(aURLSpec, aCharset) {
+ let ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ try {
+ return ios.newURI(aURLSpec, aCharset, null);
+ } catch (ex) { }
+
+ return null;
+}
+
+const XML_NS = "http://www.w3.org/XML/1998/namespace";
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
+
+const TITLE_ID = "feedTitleText";
+const SUBTITLE_ID = "feedSubtitleText";
+
+/**
+ * Converts a number of bytes to the appropriate unit that results in a
+ * number that needs fewer than 4 digits
+ *
+ * @return a pair: [new value with 3 sig. figs., its unit]
+ */
+function convertByteUnits(aBytes) {
+ let units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
+ let unitIndex = 0;
+
+ // convert to next unit if it needs 4 digits (after rounding), but only if
+ // we know the name of the next unit
+ while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
+ aBytes /= 1024;
+ unitIndex++;
+ }
+
+ // Get rid of insignificant bits by truncating to 1 or 0 decimal points
+ // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
+ aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
+
+ return [aBytes, units[unitIndex]];
+}
+
+function FeedWriter() {
+ this._selectedApp = undefined;
+ this._selectedAppMenuItem = null;
+ this._subscribeCallback = null;
+ this._defaultHandlerMenuItem = null;
+}
+
+FeedWriter.prototype = {
+ _getPropertyAsBag(container, property) {
+ return container.fields.getProperty(property).
+ QueryInterface(Ci.nsIPropertyBag2);
+ },
+
+ _getPropertyAsString(container, property) {
+ try {
+ return container.fields.getPropertyAsAString(property);
+ }
+ catch (e) {
+ }
+ return "";
+ },
+
+ _setContentText(id, text) {
+ let element = this._document.getElementById(id);
+ let textNode = text.createDocumentFragment(element);
+ while (element.hasChildNodes())
+ element.removeChild(element.firstChild);
+ element.appendChild(textNode);
+ if (text.base) {
+ element.setAttributeNS(XML_NS, 'base', text.base.spec);
+ }
+ },
+
+ /**
+ * Safely sets the href attribute on an anchor tag, providing the URI
+ * specified can be loaded according to rules.
+ * @param element
+ * The element to set a URI attribute on
+ * @param attribute
+ * The attribute of the element to set the URI to, e.g. href or src
+ * @param uri
+ * The URI spec to set as the href
+ */
+ _safeSetURIAttribute(element, attribute, uri) {
+ let secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+ const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
+ try {
+ // TODO Is this necessary?
+ secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
+ // checkLoadURIStrWithPrincipal will throw if the link URI should not be
+ // loaded, either because our feedURI isn't allowed to load it or per
+ // the rules specified in |flags|, so we'll never "linkify" the link...
+ }
+ catch (e) {
+ // Not allowed to load this link because secman.checkLoadURIStr threw
+ return;
+ }
+
+ element.setAttribute(attribute, uri);
+ },
+
+ __bundle: null,
+ get _bundle() {
+ if (!this.__bundle) {
+ this.__bundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(URI_BUNDLE);
+ }
+ return this.__bundle;
+ },
+
+ _getFormattedString(key, params) {
+ return this._bundle.formatStringFromName(key, params, params.length);
+ },
+
+ _getString(key) {
+ return this._bundle.GetStringFromName(key);
+ },
+
+ _setCheckboxCheckedState(aValue) {
+ let checkbox = this._document.getElementById("alwaysUse");
+ if (checkbox) {
+ // see checkbox.xml, xbl bindings are not applied within the sandbox! TODO
+ let change = (aValue != (checkbox.getAttribute("checked") == "true"));
+ if (aValue)
+ checkbox.setAttribute("checked", "true");
+ else
+ checkbox.removeAttribute("checked");
+
+ if (change) {
+ let event = this._document.createEvent("Events");
+ event.initEvent("CheckboxStateChange", true, true);
+ checkbox.dispatchEvent(event);
+ }
+ }
+ },
+
+ /**
+ * Returns a date suitable for displaying in the feed preview.
+ * If the date cannot be parsed, the return value is "false".
+ * @param dateString
+ * A date as extracted from a feed entry. (entry.updated)
+ */
+ _parseDate(dateString) {
+ // Convert the date into the user's local time zone
+ let dateObj = new Date(dateString);
+
+ // Make sure the date we're given is valid.
+ if (!dateObj.getTime())
+ return false;
+
+ return this._dateFormatter.format(dateObj);
+ },
+
+ __dateFormatter: null,
+ get _dateFormatter() {
+ if (!this.__dateFormatter) {
+ const locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric',
+ hour: 'numeric', minute: 'numeric' };
+ this.__dateFormatter = new Intl.DateTimeFormat(locale, dtOptions);
+ }
+ return this.__dateFormatter;
+ },
+
+ /**
+ * Returns the feed type.
+ */
+ __feedType: null,
+ _getFeedType() {
+ if (this.__feedType != null)
+ return this.__feedType;
+
+ try {
+ // grab the feed because it's got the feed.type in it.
+ let container = this._getContainer();
+ let feed = container.QueryInterface(Ci.nsIFeed);
+ this.__feedType = feed.type;
+ return feed.type;
+ } catch (ex) { }
+
+ return Ci.nsIFeed.TYPE_FEED;
+ },
+
+ /**
+ * Writes the feed title into the preview document.
+ * @param container
+ * The feed container
+ */
+ _setTitleText(container) {
+ if (container.title) {
+ let title = container.title.plainText();
+ this._setContentText(TITLE_ID, container.title);
+ this._document.title = title;
+ }
+
+ let feed = container.QueryInterface(Ci.nsIFeed);
+ if (feed && feed.subtitle)
+ this._setContentText(SUBTITLE_ID, container.subtitle);
+ },
+
+ /**
+ * Writes the title image into the preview document if one is present.
+ * @param container
+ * The feed container
+ */
+ _setTitleImage(container) {
+ try {
+ let parts = container.image;
+
+ // Set up the title image (supplied by the feed)
+ let feedTitleImage = this._document.getElementById("feedTitleImage");
+ this._safeSetURIAttribute(feedTitleImage, "src",
+ parts.getPropertyAsAString("url"));
+
+ // Set up the title image link
+ let feedTitleLink = this._document.getElementById("feedTitleLink");
+
+ let titleText = this._getFormattedString("linkTitleTextFormat",
+ [parts.getPropertyAsAString("title")]);
+ let feedTitleText = this._document.getElementById("feedTitleText");
+ let titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
+
+ // Fix the margin on the main title, so that the image doesn't run over
+ // the underline
+ feedTitleLink.setAttribute('title', titleText);
+ feedTitleText.style.marginRight = titleImageWidth + 'px';
+
+ this._safeSetURIAttribute(feedTitleLink, "href",
+ parts.getPropertyAsAString("link"));
+ }
+ catch (e) {
+ LOG("Failed to set Title Image (this is benign): " + e);
+ }
+ },
+
+ /**
+ * Writes all entries contained in the feed.
+ * @param container
+ * The container of entries in the feed
+ */
+ _writeFeedContent(container) {
+ // Build the actual feed content
+ let feed = container.QueryInterface(Ci.nsIFeed);
+ if (feed.items.length == 0)
+ return;
+
+ let feedContent = this._document.getElementById("feedContent");
+
+ for (let i = 0; i < feed.items.length; ++i) {
+ let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
+ entry.QueryInterface(Ci.nsIFeedContainer);
+
+ let entryContainer = this._document.createElementNS(HTML_NS, "div");
+ entryContainer.className = "entry";
+
+ // If the entry has a title, make it a link
+ if (entry.title) {
+ let a = this._document.createElementNS(HTML_NS, "a");
+ let span = this._document.createElementNS(HTML_NS, "span");
+ a.appendChild(span);
+ if (entry.title.base)
+ span.setAttributeNS(XML_NS, "base", entry.title.base.spec);
+ span.appendChild(entry.title.createDocumentFragment(a));
+
+ // Entries are not required to have links, so entry.link can be null.
+ if (entry.link)
+ this._safeSetURIAttribute(a, "href", entry.link.spec);
+
+ let title = this._document.createElementNS(HTML_NS, "h3");
+ title.appendChild(a);
+
+ let lastUpdated = this._parseDate(entry.updated);
+ if (lastUpdated) {
+ let dateDiv = this._document.createElementNS(HTML_NS, "div");
+ dateDiv.className = "lastUpdated";
+ dateDiv.textContent = lastUpdated;
+ title.appendChild(dateDiv);
+ }
+
+ entryContainer.appendChild(title);
+ }
+
+ let body = this._document.createElementNS(HTML_NS, "div");
+ let summary = entry.summary || entry.content;
+ let docFragment = null;
+ if (summary) {
+ if (summary.base)
+ body.setAttributeNS(XML_NS, "base", summary.base.spec);
+ else
+ LOG("no base?");
+ docFragment = summary.createDocumentFragment(body);
+ if (docFragment)
+ body.appendChild(docFragment);
+
+ // If the entry doesn't have a title, append a # permalink
+ // See http://scripting.com/rss.xml for an example
+ if (!entry.title && entry.link) {
+ let a = this._document.createElementNS(HTML_NS, "a");
+ a.appendChild(this._document.createTextNode("#"));
+ this._safeSetURIAttribute(a, "href", entry.link.spec);
+ body.appendChild(this._document.createTextNode(" "));
+ body.appendChild(a);
+ }
+
+ }
+ body.className = "feedEntryContent";
+ entryContainer.appendChild(body);
+
+ if (entry.enclosures && entry.enclosures.length > 0) {
+ let enclosuresDiv = this._buildEnclosureDiv(entry);
+ entryContainer.appendChild(enclosuresDiv);
+ }
+
+ let clearDiv = this._document.createElementNS(HTML_NS, "div");
+ clearDiv.style.clear = "both";
+
+ feedContent.appendChild(entryContainer);
+ feedContent.appendChild(clearDiv);
+ }
+ },
+
+ /**
+ * Takes a url to a media item and returns the best name it can come up with.
+ * Frequently this is the filename portion (e.g. passing in
+ * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
+ * cases, this will return the entire url (e.g. passing in
+ * http://example.com/somedirectory/ would return
+ * http://example.com/somedirectory/).
+ * @param aURL
+ * The URL string from which to create a display name
+ * @returns a string
+ */
+ _getURLDisplayName(aURL) {
+ let url = makeURI(aURL);
+ url.QueryInterface(Ci.nsIURL);
+ if (url == null || url.fileName.length == 0)
+ return decodeURIComponent(aURL);
+
+ return decodeURIComponent(url.fileName);
+ },
+
+ /**
+ * Takes a FeedEntry with enclosures, generates the HTML code to represent
+ * them, and returns that.
+ * @param entry
+ * FeedEntry with enclosures
+ * @returns element
+ */
+ _buildEnclosureDiv(entry) {
+ let enclosuresDiv = this._document.createElementNS(HTML_NS, "div");
+ enclosuresDiv.className = "enclosures";
+
+ enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel")));
+
+ for (let i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) {
+ let enc = entry.enclosures.queryElementAt(i_enc, Ci.nsIWritablePropertyBag2);
+
+ if (!(enc.hasKey("url")))
+ continue;
+
+ let enclosureDiv = this._document.createElementNS(HTML_NS, "div");
+ enclosureDiv.setAttribute("class", "enclosure");
+
+ let mozicon = "moz-icon://.txt?size=16";
+ let type_text = null;
+ let size_text = null;
+
+ if (enc.hasKey("type")) {
+ type_text = enc.get("type");
+ if (enc.hasKey("typeDesc"))
+ type_text = enc.get("typeDesc");
+
+ if (type_text && type_text.length > 0)
+ mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
+ }
+
+ if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
+ let enc_size = convertByteUnits(parseInt(enc.get("length")));
+
+ size_text = this._getFormattedString("enclosureSizeText",
+ [enc_size[0],
+ this._getString(enc_size[1])]);
+ }
+
+ let iconimg = this._document.createElementNS(HTML_NS, "img");
+ iconimg.setAttribute("src", mozicon);
+ iconimg.setAttribute("class", "type-icon");
+ enclosureDiv.appendChild(iconimg);
+
+ enclosureDiv.appendChild(this._document.createTextNode( " " ));
+
+ let enc_href = this._document.createElementNS(HTML_NS, "a");
+ enc_href.appendChild(this._document.createTextNode(this._getURLDisplayName(enc.get("url"))));
+ this._safeSetURIAttribute(enc_href, "href", enc.get("url"));
+ enclosureDiv.appendChild(enc_href);
+
+ if (type_text && size_text)
+ enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ", " + size_text + ")"));
+
+ else if (type_text)
+ enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ")"))
+
+ else if (size_text)
+ enclosureDiv.appendChild(this._document.createTextNode( " (" + size_text + ")"))
+
+ enclosuresDiv.appendChild(enclosureDiv);
+ }
+
+ return enclosuresDiv;
+ },
+
+ /**
+ * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
+ * Displays error information if there was one.
+ * @returns A valid nsIFeedContainer object containing the contents of
+ * the feed.
+ */
+ _getContainer() {
+ let feedService =
+ Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+
+ let result;
+ try {
+ result =
+ feedService.getFeedResult(this._getOriginalURI(this._window));
+ }
+ catch (e) {
+ LOG("Subscribe Preview: feed not available?!");
+ }
+
+ if (result.bozo) {
+ LOG("Subscribe Preview: feed result is bozo?!");
+ }
+
+ let container;
+ try {
+ container = result.doc;
+ }
+ catch (e) {
+ LOG("Subscribe Preview: no result.doc? Why didn't the original reload?");
+ return null;
+ }
+ return container;
+ },
+
+ /**
+ * Get moz-icon url for a file
+ * @param file
+ * A nsIFile object for which the moz-icon:// is returned
+ * @returns moz-icon url of the given file as a string
+ */
+ _getFileIconURL(file) {
+ let ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let fph = ios.getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler);
+ let urlSpec = fph.getURLSpecFromFile(file);
+ return "moz-icon://" + urlSpec + "?size=16";
+ },
+
+ /**
+ * Displays a prompt from which the user may choose a (client) feed reader.
+ * @param aCallback the callback method, passes in true if a feed reader was
+ * selected, false otherwise.
+ */
+ _chooseClientApp(aCallback) {
+ this._subscribeCallback = aCallback;
+ this._mm.sendAsyncMessage("FeedWriter:ChooseClientApp",
+ { title: this._getString("chooseApplicationDialogTitle"),
+ feedType: this._getFeedType() });
+ },
+
+ _setSubscribeUsingLabel() {
+ let stringLabel = "subscribeFeedUsing";
+ switch (this._getFeedType()) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ stringLabel = "subscribeVideoPodcastUsing";
+ break;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ stringLabel = "subscribeAudioPodcastUsing";
+ break;
+ }
+
+ let subscribeUsing = this._document.getElementById("subscribeUsingDescription");
+ let textNode = this._document.createTextNode(this._getString(stringLabel));
+ subscribeUsing.insertBefore(textNode, subscribeUsing.firstChild);
+ },
+
+ _setAlwaysUseLabel() {
+ let checkbox = this._document.getElementById("alwaysUse");
+ if (checkbox && this._handlersList) {
+ let handlerName = this._handlersList.selectedOptions[0]
+ .textContent;
+ let stringLabel = "alwaysUseForFeeds";
+ switch (this._getFeedType()) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ stringLabel = "alwaysUseForVideoPodcasts";
+ break;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ stringLabel = "alwaysUseForAudioPodcasts";
+ break;
+ }
+
+ let label = this._getFormattedString(stringLabel, [handlerName]);
+
+ let checkboxText = this._document.getElementById("checkboxText");
+ if (checkboxText.lastChild.nodeType == checkboxText.TEXT_NODE) {
+ checkboxText.lastChild.textContent = label;
+ } else {
+ LOG("FeedWriter._setAlwaysUseLabel: Expected textNode as lastChild of alwaysUse label");
+ let textNode = this._document.createTextNode(label);
+ checkboxText.appendChild(textNode);
+ }
+ }
+ },
+
+ // nsIDomEventListener
+ handleEvent(event) {
+ if (event.target.ownerDocument != this._document) {
+ LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
+ return;
+ }
+
+ switch (event.type) {
+ case "click":
+ if (event.target.id == "subscribeButton") {
+ this.subscribe();
+ }
+ break;
+ case "change":
+ LOG("Change fired");
+ if (event.target.selectedOptions[0].id == "chooseApplicationMenuItem") {
+ this._chooseClientApp(() => {
+ // Select the (per-prefs) selected handler if no application
+ // was selected
+ LOG("Selected handler after callback");
+ this._setAlwaysUseLabel();
+ });
+ } else {
+ this._setAlwaysUseLabel();
+ }
+ break;
+ }
+ },
+
+ _getWebHandlerElementsForURL(aURL) {
+ return this._handlersList.querySelectorAll('[webhandlerurl="' + aURL + '"]');
+ },
+
+ _setSelectedHandlerResponse(handler, url) {
+ LOG(`Selecting handler response ${handler} ${url}`);
+ switch (handler) {
+ case "web": {
+ if (this._handlersList) {
+ let handlers =
+ this._getWebHandlerElementsForURL(url);
+ if (handlers.length == 0) {
+ LOG(`Selected web handler isn't in the menulist ${url}`);
+ return;
+ }
+
+ handlers[0].selected = true;
+ }
+ break;
+ }
+ case "client":
+ case "default":
+ // do nothing, these are handled by the onchange event
+ break;
+ case "bookmarks":
+ default: {
+ let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
+ if (liveBookmarksMenuItem)
+ liveBookmarksMenuItem.selected = true;
+ }
+ }
+ },
+
+ _initSubscriptionUI(setupMessage) {
+ if (!this._handlersList)
+ return;
+ LOG("UI init");
+
+ let feedType = this._getFeedType();
+
+ // change the background
+ let header = this._document.getElementById("feedHeader");
+ switch (feedType) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ header.className = 'videoPodcastBackground';
+ break;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ header.className = 'audioPodcastBackground';
+ break;
+
+ default:
+ header.className = 'feedBackground';
+ }
+
+ let liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem");
+
+ // Last-selected application
+ let menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.setAttribute("id", "selectedAppMenuItem");
+ menuItem.setAttribute("handlerType", "client");
+
+ // Hide the menuitem until we select an app
+ menuItem.style.display = "none";
+ this._selectedAppMenuItem = menuItem;
+
+ this._handlersList.appendChild(this._selectedAppMenuItem);
+
+ // Create the menuitem for the default reader, but don't show/populate it until
+ // we get confirmation of what it is from the parent
+ menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.setAttribute("id", "defaultHandlerMenuItem");
+ menuItem.setAttribute("handlerType", "client");
+ menuItem.style.display = "none";
+
+ this._defaultHandlerMenuItem = menuItem;
+ this._handlersList.appendChild(this._defaultHandlerMenuItem);
+
+ // "Choose Application..." menuitem
+ menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.setAttribute("id", "chooseApplicationMenuItem");
+ menuItem.textContent = this._getString("chooseApplicationMenuItem");
+
+ this._handlersList.appendChild(menuItem);
+
+ // separator
+ let chooseAppSep = liveBookmarksMenuItem.nextElementSibling.cloneNode(false);
+ chooseAppSep.textContent = liveBookmarksMenuItem.nextElementSibling.textContent;
+ this._handlersList.appendChild(chooseAppSep);
+
+ for (let handler of setupMessage.handlers) {
+ if (!handler.uri) {
+ LOG("Handler with name " + handler.name + " has no URI!? Skipping...");
+ continue;
+ }
+ menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.className = "menuitem-iconic";
+ menuItem.textContent = handler.name;
+ menuItem.setAttribute("handlerType", "web");
+ menuItem.setAttribute("webhandlerurl", handler.uri);
+ this._handlersList.appendChild(menuItem);
+ }
+
+ this._setSelectedHandlerResponse(setupMessage.reader.handler, setupMessage.reader.url);
+
+ if (setupMessage.defaultMenuItem) {
+ LOG(`Setting default menu item ${setupMessage.defaultMenuItem}`);
+ this._setApplicationLauncherMenuItem(this._defaultHandlerMenuItem, setupMessage.defaultMenuItem);
+ }
+ if (setupMessage.selectedMenuItem) {
+ LOG(`Setting selected menu item ${setupMessage.selectedMenuItem}`);
+ this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, setupMessage.selectedMenuItem);
+ }
+
+ // "Subscribe using..."
+ this._setSubscribeUsingLabel();
+
+ // "Always use..." checkbox initial state
+ this._setCheckboxCheckedState(setupMessage.reader.alwaysUse);
+ this._setAlwaysUseLabel();
+
+ // We update the "Always use.." checkbox label whenever the selected item
+ // in the list is changed
+ this._handlersList.addEventListener("change", this);
+
+ // Set up the "Subscribe Now" button
+ this._document.getElementById("subscribeButton")
+ .addEventListener("click", this);
+
+ // first-run ui
+ if (setupMessage.showFirstRunUI) {
+ let textfeedinfo1, textfeedinfo2;
+ switch (feedType) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ textfeedinfo1 = "feedSubscriptionVideoPodcast1";
+ textfeedinfo2 = "feedSubscriptionVideoPodcast2";
+ break;
+ case Ci.nsIFeed.TYPE_AUDIO:
+ textfeedinfo1 = "feedSubscriptionAudioPodcast1";
+ textfeedinfo2 = "feedSubscriptionAudioPodcast2";
+ break;
+ default:
+ textfeedinfo1 = "feedSubscriptionFeed1";
+ textfeedinfo2 = "feedSubscriptionFeed2";
+ }
+
+ let feedinfo1 = this._document.getElementById("feedSubscriptionInfo1");
+ let feedinfo1Str = this._getString(textfeedinfo1);
+ let feedinfo2 = this._document.getElementById("feedSubscriptionInfo2");
+ let feedinfo2Str = this._getString(textfeedinfo2);
+
+ feedinfo1.textContent = feedinfo1Str;
+ feedinfo2.textContent = feedinfo2Str;
+
+ header.setAttribute('firstrun', 'true');
+
+ this._mm.sendAsyncMessage("FeedWriter:ShownFirstRun");
+ }
+ },
+
+ /**
+ * Returns the original URI object of the feed and ensures that this
+ * component is only ever invoked from the preview document.
+ * @param aWindow
+ * The window of the document invoking the BrowserFeedWriter
+ */
+ _getOriginalURI(aWindow) {
+ let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ let chan = docShell.currentDocumentChannel;
+
+ // We probably need to call InheritFromDocShellToDoc for this, but right now
+ // we can't call it from JS.
+ let attrs = docShell.getOriginAttributes();
+ let ssm = Services.scriptSecurityManager;
+ let nullPrincipal = ssm.createNullPrincipal(attrs);
+
+ // this channel is not going to be openend, use a nullPrincipal
+ // and the most restrctive securityFlag.
+ let resolvedURI = NetUtil.newChannel({
+ uri: "about:feeds",
+ loadingPrincipal: nullPrincipal,
+ securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ }).URI;
+
+ if (resolvedURI.equals(chan.URI))
+ return chan.originalURI;
+
+ return null;
+ },
+
+ _window: null,
+ _document: null,
+ _feedURI: null,
+ _feedPrincipal: null,
+ _handlersList: null,
+
+ // BrowserFeedWriter WebIDL methods
+ init(aWindow) {
+ let window = aWindow;
+ this._feedURI = this._getOriginalURI(window);
+ if (!this._feedURI)
+ return;
+
+ this._window = window;
+ this._document = window.document;
+ this._handlersList = this._document.getElementById("handlersMenuList");
+
+ let secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+ this._feedPrincipal = secman.createCodebasePrincipal(this._feedURI, {});
+
+ LOG("Subscribe Preview: feed uri = " + this._window.location.href);
+
+
+ this._mm.addMessageListener("FeedWriter:PreferenceUpdated", this);
+ this._mm.addMessageListener("FeedWriter:SetApplicationLauncherMenuItem", this);
+ this._mm.addMessageListener("FeedWriter:GetSubscriptionUIResponse", this);
+ this._mm.addMessageListener("FeedWriter:SetFeedPrefsAndSubscribeResponse", this);
+
+ const feedType = this._getFeedType();
+ this._mm.sendAsyncMessage("FeedWriter:GetSubscriptionUI",
+ { feedType });
+ },
+
+ receiveMessage(msg) {
+ if (!this._window) {
+ // this._window is null unless this.init was called with a trusted
+ // window object.
+ return;
+ }
+ LOG(`received message from parent ${msg.name}`);
+ switch (msg.name) {
+ case "FeedWriter:PreferenceUpdated":
+ // This is called when browser-feeds.js spots a pref change
+ // This will happen when
+ // - about:preferences#applications changes
+ // - another feed reader page changes the preference
+ // - when this page itself changes the select and there isn't a redirect
+ // bookmarks and launching an external app means the page stays open after subscribe
+ const feedType = this._getFeedType();
+ LOG(`Got prefChange! ${JSON.stringify(msg.data)} current type: ${feedType}`);
+ let feedTypePref = msg.data.default;
+ if (feedType in msg.data) {
+ feedTypePref = msg.data[feedType];
+ }
+ LOG(`Got pref ${JSON.stringify(feedTypePref)}`);
+ this._setCheckboxCheckedState(feedTypePref.alwaysUse);
+ this._setSelectedHandlerResponse(feedTypePref.handler, feedTypePref.url);
+ this._setAlwaysUseLabel();
+ break;
+ case "FeedWriter:SetFeedPrefsAndSubscribeResponse":
+ LOG(`FeedWriter:SetFeedPrefsAndSubscribeResponse - Redirecting ${msg.data.redirect}`);
+ this._window.location.href = msg.data.redirect;
+ break;
+ case "FeedWriter:GetSubscriptionUIResponse":
+ // Set up the subscription UI
+ this._initSubscriptionUI(msg.data);
+ break;
+ case "FeedWriter:SetApplicationLauncherMenuItem":
+ LOG(`FeedWriter:SetApplicationLauncherMenuItem - picked ${msg.data.name}`);
+ this._setApplicationLauncherMenuItem(this._selectedAppMenuItem, msg.data.name);
+ // Potentially a bit racy, but I don't think we can get into a state where this callback is set and
+ // we're not coming back from ChooseClientApp in browser-feeds.js
+ if (this._subscribeCallback) {
+ this._subscribeCallback();
+ this._subscribeCallback = null;
+ }
+ break;
+ }
+ },
+
+ _setApplicationLauncherMenuItem(menuItem, aName) {
+ /* unselect all handlers */
+ [...this._handlersList.children].forEach((option) => {
+ option.removeAttribute("selected");
+ });
+ menuItem.textContent = aName;
+ menuItem.style.display = "";
+ menuItem.selected = true;
+ },
+
+ writeContent() {
+ if (!this._window)
+ return;
+
+ try {
+ // Set up the feed content
+ let container = this._getContainer();
+ if (!container)
+ return;
+
+ this._setTitleText(container);
+ this._setTitleImage(container);
+ this._writeFeedContent(container);
+ }
+ finally {
+ this._removeFeedFromCache();
+ }
+ },
+
+ close() {
+ this._document.getElementById("subscribeButton")
+ .removeEventListener("click", this, false);
+ this._handlersList
+ .removeEventListener("change", this, false);
+ this._document = null;
+ this._window = null;
+ this._handlersList = null;
+
+ this._removeFeedFromCache();
+ this.__bundle = null;
+ this._feedURI = null;
+
+ this._selectedApp = undefined;
+ this._selectedAppMenuItem = null;
+ this._defaultHandlerMenuItem = null;
+ },
+
+ _removeFeedFromCache() {
+ if (this._feedURI) {
+ let feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+ feedService.removeFeedResult(this._feedURI);
+ this._feedURI = null;
+ }
+ },
+
+ subscribe() {
+ let feedType = this._getFeedType();
+
+ // Subscribe to the feed using the selected handler and save prefs
+ let defaultHandler = "reader";
+ let useAsDefault = this._document.getElementById("alwaysUse").getAttribute("checked");
+
+ let selectedItem = this._handlersList.selectedOptions[0];
+ let subscribeCallback = () => {
+ let feedReader = null;
+ let settings = {
+ feedType,
+ useAsDefault,
+ // Pull the title and subtitle out of the document
+ feedTitle: this._document.getElementById(TITLE_ID).textContent,
+ feedSubtitle: this._document.getElementById(SUBTITLE_ID).textContent,
+ feedLocation: this._window.location.href
+ };
+ if (selectedItem.hasAttribute("webhandlerurl")) {
+ feedReader = "web";
+ settings.uri = selectedItem.getAttribute("webhandlerurl");
+ } else {
+ switch (selectedItem.id) {
+ case "selectedAppMenuItem":
+ feedReader = "client";
+ break;
+ case "defaultHandlerMenuItem":
+ feedReader = "default";
+ break;
+ case "liveBookmarksMenuItem":
+ defaultHandler = "bookmarks";
+ feedReader = "bookmarks";
+ break;
+ }
+ }
+ settings.reader = feedReader;
+
+ // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
+ // to either "reader" (If a web reader or if an application is selected),
+ // or to "bookmarks" (if the live bookmarks option is selected).
+ // Otherwise, we should set it to "ask"
+ if (!useAsDefault) {
+ defaultHandler = "ask";
+ }
+ settings.action = defaultHandler;
+ LOG(`FeedWriter:SetFeedPrefsAndSubscribe - ${JSON.stringify(settings)}`);
+ this._mm.sendAsyncMessage("FeedWriter:SetFeedPrefsAndSubscribe",
+ settings);
+ }
+
+ // Show the file picker before subscribing if the
+ // choose application menuitem was chosen using the keyboard
+ if (selectedItem.id == "chooseApplicationMenuItem") {
+ this._chooseClientApp(function(aResult) {
+ if (aResult) {
+ selectedItem =
+ this._handlersList.selectedOptions[0];
+ subscribeCallback();
+ }
+ }.bind(this));
+ } else {
+ subscribeCallback();
+ }
+ },
+
+ get _mm() {
+ let mm = this._window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIContentFrameMessageManager);
+ delete this._mm;
+ return this._mm = mm;
+ },
+
+ classID: FEEDWRITER_CID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsIObserver,
+ Ci.nsINavHistoryObserver,
+ Ci.nsIDOMGlobalPropertyInitializer])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FeedWriter]);
diff --git a/browser/components/feeds/WebContentConverter.js b/browser/components/feeds/WebContentConverter.js
new file mode 100644
index 000000000..2cb5cd145
--- /dev/null
+++ b/browser/components/feeds/WebContentConverter.js
@@ -0,0 +1,1071 @@
+/* -*- 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/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+function LOG(str) {
+ dump("*** " + str + "\n");
+}
+
+const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
+const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
+
+const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
+const WCC_CLASSNAME = "Web Service Handler";
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_ANY = "*/*";
+
+const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto.";
+const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types.";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
+const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost";
+
+const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
+
+const NS_ERROR_MODULE_DOM = 2152923136;
+const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
+
+function WebContentConverter() {
+}
+WebContentConverter.prototype = {
+ convert() { },
+ asyncConvertData() { },
+ onDataAvailable() { },
+ onStopRequest() { },
+
+ onStartRequest(request, context) {
+ let wccr =
+ Cc[WCCR_CONTRACTID].
+ getService(Ci.nsIWebContentConverterService);
+ wccr.loadPreferredHandler(request);
+ },
+
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsIStreamConverter) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+let WebContentConverterFactory = {
+ createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return new WebContentConverter().QueryInterface(iid);
+ },
+
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function ServiceInfo(contentType, uri, name) {
+ this._contentType = contentType;
+ this._uri = uri;
+ this._name = name;
+}
+ServiceInfo.prototype = {
+ /**
+ * See nsIHandlerApp
+ */
+ get name() {
+ return this._name;
+ },
+
+ /**
+ * See nsIHandlerApp
+ */
+ equals(aHandlerApp) {
+ if (!aHandlerApp)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo &&
+ aHandlerApp.contentType == this.contentType &&
+ aHandlerApp.uri == this.uri)
+ return true;
+
+ return false;
+ },
+
+ /**
+ * See nsIWebContentHandlerInfo
+ */
+ get contentType() {
+ return this._contentType;
+ },
+
+ /**
+ * See nsIWebContentHandlerInfo
+ */
+ get uri() {
+ return this._uri;
+ },
+
+ /**
+ * See nsIWebContentHandlerInfo
+ */
+ getHandlerURI(uri) {
+ return this._uri.replace(/%s/gi, encodeURIComponent(uri));
+ },
+
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsIWebContentHandlerInfo) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+const Utils = {
+ makeURI(aURL, aOriginCharset, aBaseURI) {
+ return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
+ },
+
+ checkAndGetURI(aURIString, aContentWindow) {
+ let uri;
+ try {
+ let baseURI = aContentWindow.document.baseURIObject;
+ uri = this.makeURI(aURIString, null, baseURI);
+ } catch (ex) {
+ throw NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ // For security reasons we reject non-http(s) urls (see bug 354316),
+ // we may need to revise this once we support more content types
+ if (uri.scheme != "http" && uri.scheme != "https") {
+ throw this.getSecurityError(
+ "Permission denied to add " + uri.spec + " as a content or protocol handler",
+ aContentWindow);
+ }
+
+ // We also reject handlers registered from a different host (see bug 402287)
+ // The pref allows us to test the feature
+ let pb = Services.prefs;
+ if (!pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST) &&
+ (!["http:", "https:"].includes(aContentWindow.location.protocol) ||
+ aContentWindow.location.hostname != uri.host)) {
+ throw this.getSecurityError(
+ "Permission denied to add " + uri.spec + " as a content or protocol handler",
+ aContentWindow);
+ }
+
+ // If the uri doesn't contain '%s', it won't be a good handler
+ if (uri.spec.indexOf("%s") < 0)
+ throw NS_ERROR_DOM_SYNTAX_ERR;
+
+ return uri;
+ },
+
+ // NB: Throws if aProtocol is not allowed.
+ checkProtocolHandlerAllowed(aProtocol, aURIString, aWindowOrNull) {
+ // First, check to make sure this isn't already handled internally (we don't
+ // want to let them take over, say "chrome").
+ let handler = Services.io.getProtocolHandler(aProtocol);
+ if (!(handler instanceof Ci.nsIExternalProtocolHandler)) {
+ // This is handled internally, so we don't want them to register
+ throw this.getSecurityError(
+ `Permission denied to add ${aURIString} as a protocol handler`,
+ aWindowOrNull);
+ }
+
+ // check if it is in the black list
+ let pb = Services.prefs;
+ let allowed;
+ try {
+ allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol);
+ }
+ catch (e) {
+ allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default");
+ }
+ if (!allowed) {
+ throw this.getSecurityError(
+ `Not allowed to register a protocol handler for ${aProtocol}`,
+ aWindowOrNull);
+ }
+ },
+
+ // Return a SecurityError exception from the given Window if one is given. If
+ // none is given, just return the given error string, for lack of anything
+ // better.
+ getSecurityError(errorString, aWindowOrNull) {
+ if (!aWindowOrNull) {
+ return errorString;
+ }
+
+ return new aWindowOrNull.DOMException(errorString, "SecurityError");
+ },
+
+ /**
+ * Mappings from known feed types to our internal content type.
+ */
+ _mappings: {
+ "application/rss+xml": TYPE_MAYBE_FEED,
+ "application/atom+xml": TYPE_MAYBE_FEED,
+ },
+
+ resolveContentType(aContentType) {
+ if (aContentType in this._mappings)
+ return this._mappings[aContentType];
+ return aContentType;
+ }
+};
+
+function WebContentConverterRegistrar() {
+ this._contentTypes = {};
+ this._autoHandleContentTypes = {};
+}
+
+WebContentConverterRegistrar.prototype = {
+ get stringBundle() {
+ let sb = Services.strings.createBundle(STRING_BUNDLE_URI);
+ delete WebContentConverterRegistrar.prototype.stringBundle;
+ return WebContentConverterRegistrar.prototype.stringBundle = sb;
+ },
+
+ _getFormattedString(key, params) {
+ return this.stringBundle.formatStringFromName(key, params, params.length);
+ },
+
+ _getString(key) {
+ return this.stringBundle.GetStringFromName(key);
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ getAutoHandler(contentType) {
+ contentType = Utils.resolveContentType(contentType);
+ if (contentType in this._autoHandleContentTypes)
+ return this._autoHandleContentTypes[contentType];
+ return null;
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ setAutoHandler(contentType, handler) {
+ if (handler && !this._typeIsRegistered(contentType, handler.uri))
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ contentType = Utils.resolveContentType(contentType);
+ this._setAutoHandler(contentType, handler);
+
+ let ps = Services.prefs;
+ let autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
+ if (handler)
+ autoBranch.setCharPref(contentType, handler.uri);
+ else if (autoBranch.prefHasUserValue(contentType))
+ autoBranch.clearUserPref(contentType);
+
+ ps.savePrefFile(null);
+ },
+
+ /**
+ * Update the internal data structure (not persistent)
+ */
+ _setAutoHandler(contentType, handler) {
+ if (handler)
+ this._autoHandleContentTypes[contentType] = handler;
+ else if (contentType in this._autoHandleContentTypes)
+ delete this._autoHandleContentTypes[contentType];
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ getWebContentHandlerByURI(contentType, uri) {
+ return this.getContentHandlers(contentType)
+ .find(e => e.uri == uri) || null;
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ loadPreferredHandler(request) {
+ let channel = request.QueryInterface(Ci.nsIChannel);
+ let contentType = Utils.resolveContentType(channel.contentType);
+ let handler = this.getAutoHandler(contentType);
+ if (handler) {
+ request.cancel(Cr.NS_ERROR_FAILURE);
+
+ let webNavigation =
+ channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation);
+ webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec),
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, null, null);
+ }
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ removeProtocolHandler(aProtocol, aURITemplate) {
+ let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ let handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
+ let handlers = handlerInfo.possibleApplicationHandlers;
+ for (let i = 0; i < handlers.length; i++) {
+ try { // We only want to test web handlers
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ if (handler.uriTemplate == aURITemplate) {
+ handlers.removeElementAt(i);
+ let hs = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hs.store(handlerInfo);
+ return;
+ }
+ } catch (e) { /* it wasn't a web handler */ }
+ }
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ removeContentHandler(contentType, uri) {
+ function notURI(serviceInfo) {
+ return serviceInfo.uri != uri;
+ }
+
+ if (contentType in this._contentTypes) {
+ this._contentTypes[contentType] =
+ this._contentTypes[contentType].filter(notURI);
+ }
+ },
+
+ /**
+ * These are types for which there is a separate content converter aside
+ * from our built in generic one. We should not automatically register
+ * a factory for creating a converter for these types.
+ */
+ _blockedTypes: {
+ "application/vnd.mozilla.maybe.feed": true,
+ },
+
+ /**
+ * Determines if a web handler is already registered.
+ *
+ * @param aProtocol
+ * The scheme of the web handler we are checking for.
+ * @param aURITemplate
+ * The URI template that the handler uses to handle the protocol.
+ * @return true if it is already registered, false otherwise.
+ */
+ _protocolHandlerRegistered(aProtocol, aURITemplate) {
+ let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ let handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
+ let handlers = handlerInfo.possibleApplicationHandlers;
+ for (let i = 0; i < handlers.length; i++) {
+ try { // We only want to test web handlers
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ if (handler.uriTemplate == aURITemplate)
+ return true;
+ } catch (e) { /* it wasn't a web handler */ }
+ }
+ return false;
+ },
+
+ /**
+ * See nsIWebContentHandlerRegistrar
+ */
+ registerProtocolHandler(aProtocol, aURIString, aTitle, aBrowserOrWindow) {
+ LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
+ let haveWindow = (aBrowserOrWindow instanceof Ci.nsIDOMWindow);
+ let uri;
+ if (haveWindow) {
+ uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow);
+ } else {
+ // aURIString must not be a relative URI.
+ uri = Utils.makeURI(aURIString, null);
+ }
+
+ // If the protocol handler is already registered, just return early.
+ if (this._protocolHandlerRegistered(aProtocol, uri.spec)) {
+ return;
+ }
+
+ let browser;
+ if (haveWindow) {
+ let browserWindow =
+ this._getBrowserWindowForContentWindow(aBrowserOrWindow);
+ browser = this._getBrowserForContentWindow(browserWindow,
+ aBrowserOrWindow);
+ } else {
+ browser = aBrowserOrWindow;
+ }
+ if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
+ // Inside the private browsing mode, we don't want to alert the user to save
+ // a protocol handler. We log it to the error console so that web developers
+ // would have some way to tell what's going wrong.
+ Services.console.
+ logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
+ return;
+ }
+
+ Utils.checkProtocolHandlerAllowed(aProtocol, aURIString,
+ haveWindow ? aBrowserOrWindow : null);
+
+ // Now Ask the user and provide the proper callback
+ let message = this._getFormattedString("addProtocolHandler",
+ [aTitle, uri.host, aProtocol]);
+
+ let notificationIcon = uri.prePath + "/favicon.ico";
+ let notificationValue = "Protocol Registration: " + aProtocol;
+ let addButton = {
+ label: this._getString("addProtocolHandlerAddButton"),
+ accessKey: this._getString("addProtocolHandlerAddButtonAccesskey"),
+ protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
+
+ callback(aNotification, aButtonInfo) {
+ let protocol = aButtonInfo.protocolInfo.protocol;
+ let uri = aButtonInfo.protocolInfo.uri;
+ let name = aButtonInfo.protocolInfo.name;
+
+ let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handler.name = name;
+ handler.uriTemplate = uri;
+
+ let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ let handlerInfo = eps.getProtocolHandlerInfo(protocol);
+ handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
+
+ // Since the user has agreed to add a new handler, chances are good
+ // that the next time they see a handler of this type, they're going
+ // to want to use it. Reset the handlerInfo to ask before the next
+ // use.
+ handlerInfo.alwaysAskBeforeHandling = true;
+
+ let hs = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hs.store(handlerInfo);
+ }
+ };
+ let notificationBox = browser.getTabBrowser().getNotificationBox(browser);
+ notificationBox.appendNotification(message,
+ notificationValue,
+ notificationIcon,
+ notificationBox.PRIORITY_INFO_LOW,
+ [addButton]);
+ },
+
+ /**
+ * See nsIWebContentHandlerRegistrar
+ * If a DOM window is provided, then the request came from content, so we
+ * prompt the user to confirm the registration.
+ */
+ registerContentHandler(aContentType, aURIString, aTitle, aWindowOrBrowser) {
+ LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
+
+ // Make sure to do our URL checks up front, before our content type check,
+ // just like the WebContentConverterRegistrarContent does.
+ let haveWindow = aWindowOrBrowser &&
+ (aWindowOrBrowser instanceof Ci.nsIDOMWindow);
+ let uri;
+ if (haveWindow) {
+ uri = Utils.checkAndGetURI(aURIString, aWindowOrBrowser);
+ } else if (aWindowOrBrowser) {
+ // uri was vetted in the content process.
+ uri = Utils.makeURI(aURIString, null);
+ }
+
+ // We only support feed types at present.
+ let contentType = Utils.resolveContentType(aContentType);
+ // XXX We should be throwing a Utils.getSecurityError() here in at least
+ // some cases. See bug 1266492.
+ if (contentType != TYPE_MAYBE_FEED) {
+ return;
+ }
+
+ if (aWindowOrBrowser) {
+ let notificationBox;
+ if (haveWindow) {
+ let browserWindow = this._getBrowserWindowForContentWindow(aWindowOrBrowser);
+ let browserElement = this._getBrowserForContentWindow(browserWindow, aWindowOrBrowser);
+ notificationBox = browserElement.getTabBrowser().getNotificationBox(browserElement);
+ } else {
+ notificationBox = aWindowOrBrowser.getTabBrowser()
+ .getNotificationBox(aWindowOrBrowser);
+ }
+
+ this._appendFeedReaderNotification(uri, aTitle, notificationBox);
+ }
+ else {
+ this._registerContentHandler(contentType, aURIString, aTitle);
+ }
+ },
+
+ /**
+ * Returns the browser chrome window in which the content window is in
+ */
+ _getBrowserWindowForContentWindow(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .wrappedJSObject;
+ },
+
+ /**
+ * Returns the <xul:browser> element associated with the given content
+ * window.
+ *
+ * @param aBrowserWindow
+ * The browser window in which the content window is in.
+ * @param aContentWindow
+ * The content window. It's possible to pass a child content window
+ * (i.e. the content window of a frame/iframe).
+ */
+ _getBrowserForContentWindow(aBrowserWindow, aContentWindow) {
+ // This depends on pseudo APIs of browser.js and tabbrowser.xml
+ aContentWindow = aContentWindow.top;
+ return aBrowserWindow.gBrowser.browsers.find((browser) =>
+ browser.contentWindow == aContentWindow);
+ },
+
+ /**
+ * Appends a notifcation for the given feed reader details.
+ *
+ * The notification could be either a pseudo-dialog which lets
+ * the user to add the feed reader:
+ * [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader? (Add) [x] ]
+ *
+ * or a simple message for the case where the feed reader is already registered:
+ * [ [icon] %feed-reader-name% is already registered as a Feed Reader [x] ]
+ *
+ * A new notification isn't appended if the given notificationbox has a
+ * notification for the same feed reader.
+ *
+ * @param aURI
+ * The url of the feed reader as a nsIURI object
+ * @param aName
+ * The feed reader name as it was passed to registerContentHandler
+ * @param aNotificationBox
+ * The notification box to which a notification might be appended
+ * @return true if a notification has been appended, false otherwise.
+ */
+ _appendFeedReaderNotification(aURI, aName, aNotificationBox) {
+ let uriSpec = aURI.spec;
+ let notificationValue = "feed reader notification: " + uriSpec;
+ let notificationIcon = aURI.prePath + "/favicon.ico";
+
+ // Don't append a new notification if the notificationbox
+ // has a notification for the given feed reader already
+ if (aNotificationBox.getNotificationWithValue(notificationValue))
+ return false;
+
+ let buttons;
+ let message;
+ if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec))
+ message = this._getFormattedString("handlerRegistered", [aName]);
+ else {
+ message = this._getFormattedString("addHandler", [aName, aURI.host]);
+ let self = this;
+ let addButton = {
+ _outer: self,
+ label: self._getString("addHandlerAddButton"),
+ accessKey: self._getString("addHandlerAddButtonAccesskey"),
+ feedReaderInfo: { uri: uriSpec, name: aName },
+
+ /* static */
+ callback(aNotification, aButtonInfo) {
+ let uri = aButtonInfo.feedReaderInfo.uri;
+ let name = aButtonInfo.feedReaderInfo.name;
+ let outer = aButtonInfo._outer;
+
+ // The reader could have been added from another window mean while
+ if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri))
+ outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name);
+
+ // avoid reference cycles
+ aButtonInfo._outer = null;
+
+ return false;
+ }
+ };
+ buttons = [addButton];
+ }
+
+ aNotificationBox.appendNotification(message,
+ notificationValue,
+ notificationIcon,
+ aNotificationBox.PRIORITY_INFO_LOW,
+ buttons);
+ return true;
+ },
+
+ /**
+ * Save Web Content Handler metadata to persistent preferences.
+ * @param contentType
+ * The content Type being handled
+ * @param uri
+ * The uri of the web service
+ * @param title
+ * The human readable name of the web service
+ *
+ * This data is stored under:
+ *
+ * browser.contentHandlers.type0 = content/type
+ * browser.contentHandlers.uri0 = http://www.foo.com/q=%s
+ * browser.contentHandlers.title0 = Foo 2.0alphr
+ */
+ _saveContentHandlerToPrefs(contentType, uri, title) {
+ let ps = Services.prefs;
+ let i = 0;
+ let typeBranch = null;
+ while (true) {
+ typeBranch =
+ ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + ".");
+ try {
+ typeBranch.getCharPref("type");
+ ++i;
+ }
+ catch (e) {
+ // No more handlers
+ break;
+ }
+ }
+ if (typeBranch) {
+ typeBranch.setCharPref("type", contentType);
+ let pls =
+ Cc["@mozilla.org/pref-localizedstring;1"].
+ createInstance(Ci.nsIPrefLocalizedString);
+ pls.data = uri;
+ typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls);
+ pls.data = title;
+ typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls);
+
+ ps.savePrefFile(null);
+ }
+ },
+
+ /**
+ * Determines if there is a type with a particular uri registered for the
+ * specified content type already.
+ * @param contentType
+ * The content type that the uri handles
+ * @param uri
+ * The uri of the content type
+ */
+ _typeIsRegistered(contentType, uri) {
+ if (!(contentType in this._contentTypes))
+ return false;
+
+ return this._contentTypes[contentType]
+ .some(t => t.uri == uri);
+ },
+
+ /**
+ * Gets a stream converter contract id for the specified content type.
+ * @param contentType
+ * The source content type for the conversion.
+ * @returns A contract id to construct a converter to convert between the
+ * contentType and *\/*.
+ */
+ _getConverterContractID(contentType) {
+ const template = "@mozilla.org/streamconv;1?from=%s&to=*/*";
+ return template.replace(/%s/, contentType);
+ },
+
+ /**
+ * Register a web service handler for a content type.
+ *
+ * @param contentType
+ * the content type being handled
+ * @param uri
+ * the URI of the web service
+ * @param title
+ * the human readable name of the web service
+ */
+ _registerContentHandler(contentType, uri, title) {
+ this._updateContentTypeHandlerMap(contentType, uri, title);
+ this._saveContentHandlerToPrefs(contentType, uri, title);
+
+ if (contentType == TYPE_MAYBE_FEED) {
+ // Make the new handler the last-selected reader in the preview page
+ // and make sure the preview page is shown the next time a feed is visited
+ let pb = Services.prefs.getBranch(null);
+ pb.setCharPref(PREF_SELECTED_READER, "web");
+
+ let supportsString =
+ Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ supportsString.data = uri;
+ pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString,
+ supportsString);
+ pb.setCharPref(PREF_SELECTED_ACTION, "ask");
+ this._setAutoHandler(TYPE_MAYBE_FEED, null);
+ }
+ },
+
+ /**
+ * Update the content type -> handler map. This mapping is not persisted, use
+ * registerContentHandler or _saveContentHandlerToPrefs for that purpose.
+ * @param contentType
+ * The content Type being handled
+ * @param uri
+ * The uri of the web service
+ * @param title
+ * The human readable name of the web service
+ */
+ _updateContentTypeHandlerMap(contentType, uri, title) {
+ if (!(contentType in this._contentTypes))
+ this._contentTypes[contentType] = [];
+
+ // Avoid adding duplicates
+ if (this._typeIsRegistered(contentType, uri))
+ return;
+
+ this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
+
+ if (!(contentType in this._blockedTypes)) {
+ let converterContractID = this._getConverterContractID(contentType);
+ let cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID,
+ WebContentConverterFactory);
+ }
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ getContentHandlers(contentType, countRef) {
+ if (countRef) {
+ countRef.value = 0;
+ }
+ if (!(contentType in this._contentTypes))
+ return [];
+
+ let handlers = this._contentTypes[contentType];
+ if (countRef) {
+ countRef.value = handlers.length;
+ }
+ return handlers;
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ resetHandlersForType(contentType) {
+ // currently unused within the tree, so only useful for extensions; previous
+ // impl. was buggy (and even infinite-looped!), so I argue that this is a
+ // definite improvement
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Registers a handler from the settings on a preferences branch.
+ *
+ * Since we support up to six predefined readers, we need to handle gaps
+ * better, since the first branch with user-added values will be .6
+ *
+ * How we deal with that is to check to see if there's no prefs in the
+ * branch and stop cycling once that's true. This doesn't fix the case
+ * where a user manually removes a reader, but that's not supported yet!
+ *
+ * @param branch
+ * an nsIPrefBranch containing "type", "uri", and "title" preferences
+ * corresponding to the content handler to be registered
+ */
+ _registerContentHandlerHavingBranch(branch) {
+ let vals = branch.getChildList("");
+ if (vals.length == 0)
+ return;
+
+ let type = branch.getCharPref("type");
+ let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
+ let title = branch.getComplexValue("title",
+ Ci.nsIPrefLocalizedString).data;
+ this._updateContentTypeHandlerMap(type, uri, title);
+ },
+
+ /**
+ * Load the auto handler, content handler and protocol tables from
+ * preferences.
+ */
+ _init() {
+ let ps = Services.prefs;
+
+ let children = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
+ .getChildList("");
+
+ // first get the numbers of the providers by getting all ###.uri prefs
+ let nums = children.map((child) => {
+ let match = /^(\d+)\.uri$/.exec(child);
+ return match ? match[1] : "";
+ }).filter(child => !!child)
+ .sort();
+
+
+ // now register them
+ for (let num of nums) {
+ let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
+ try {
+ this._registerContentHandlerHavingBranch(branch);
+ } catch (ex) {
+ // do nothing, the next branch might have values
+ }
+ }
+
+ // We need to do this _after_ registering all of the available handlers,
+ // so that getWebContentHandlerByURI can return successfully.
+ let autoBranch;
+ try {
+ autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
+ } catch (e) {
+ // No auto branch yet, that's fine
+ // LOG("WCCR.init: There is no auto branch, benign");
+ }
+
+ if (autoBranch) {
+ for (let type of autoBranch.getChildList("")) {
+ let uri = autoBranch.getCharPref(type);
+ if (uri) {
+ let handler = this.getWebContentHandlerByURI(type, uri);
+ if (handler) {
+ this._setAutoHandler(type, handler);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * See nsIObserver
+ */
+ observe(subject, topic, data) {
+ let os = Services.obs;
+ switch (topic) {
+ case "app-startup":
+ os.addObserver(this, "browser-ui-startup-complete", false);
+ break;
+ case "browser-ui-startup-complete":
+ os.removeObserver(this, "browser-ui-startup-complete");
+ this._init();
+ break;
+ }
+ },
+
+ /**
+ * See nsIFactory
+ */
+ createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+
+ classID: WCCR_CLASSID,
+
+ /**
+ * See nsISupports
+ */
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIWebContentConverterService,
+ Ci.nsIWebContentHandlerRegistrar,
+ Ci.nsIObserver,
+ Ci.nsIFactory]),
+
+ _xpcom_categories: [{
+ category: "app-startup",
+ service: true
+ }]
+};
+
+function WebContentConverterRegistrarContent() {
+ this._contentTypes = {};
+}
+
+WebContentConverterRegistrarContent.prototype = {
+
+ /**
+ * Load the auto handler, content handler and protocol tables from
+ * preferences.
+ */
+ _init() {
+ let ps = Services.prefs;
+
+ let children = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
+ .getChildList("");
+
+ // first get the numbers of the providers by getting all ###.uri prefs
+ let nums = children.map((child) => {
+ let match = /^(\d+)\.uri$/.exec(child);
+ return match ? match[1] : "";
+ }).filter(child => !!child)
+ .sort();
+
+ // now register them
+ for (num of nums) {
+ let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
+ try {
+ this._registerContentHandlerHavingBranch(branch);
+ } catch (ex) {
+ // do nothing, the next branch might have values
+ }
+ }
+ },
+
+ _typeIsRegistered(contentType, uri) {
+ return this._contentTypes[contentType]
+ .some(e => e.uri == uri);
+ },
+
+ /**
+ * Since we support up to six predefined readers, we need to handle gaps
+ * better, since the first branch with user-added values will be .6
+ *
+ * How we deal with that is to check to see if there's no prefs in the
+ * branch and stop cycling once that's true. This doesn't fix the case
+ * where a user manually removes a reader, but that's not supported yet!
+ *
+ * @param branch
+ * The pref branch to register the content handler under
+ *
+ */
+ _registerContentHandlerHavingBranch(branch) {
+ let vals = branch.getChildList("");
+ if (vals.length == 0)
+ return;
+
+ let type = branch.getCharPref("type");
+ let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
+ let title = branch.getComplexValue("title",
+ Ci.nsIPrefLocalizedString).data;
+ this._updateContentTypeHandlerMap(type, uri, title);
+ },
+
+ _updateContentTypeHandlerMap(contentType, uri, title) {
+ if (!(contentType in this._contentTypes))
+ this._contentTypes[contentType] = [];
+
+ // Avoid adding duplicates
+ if (this._typeIsRegistered(contentType, uri))
+ return;
+
+ this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
+
+ if (!(contentType in this._blockedTypes)) {
+ let converterContractID = this._getConverterContractID(contentType);
+ let cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID,
+ WebContentConverterFactory);
+ }
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ getContentHandlers(contentType, countRef) {
+ this._init();
+ if (countRef) {
+ countRef.value = 0;
+ }
+
+ if (!(contentType in this._contentTypes))
+ return [];
+
+ let handlers = this._contentTypes[contentType];
+ if (countRef) {
+ countRef.value = handlers.length;
+ }
+ return handlers;
+ },
+
+ setAutoHandler(contentType, handler) {
+ Services.cpmm.sendAsyncMessage("WCCR:setAutoHandler",
+ { contentType, handler });
+ },
+
+ getWebContentHandlerByURI(contentType, uri) {
+ return this.getContentHandlers(contentType)
+ .find(e => e.uri == uri) || null;
+ },
+
+ /**
+ * See nsIWebContentHandlerRegistrar
+ */
+ registerContentHandler(aContentType, aURIString, aTitle, aBrowserOrWindow) {
+ // aBrowserOrWindow must be a window.
+ let messageManager = aBrowserOrWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsITabChild)
+ .messageManager;
+
+ let uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow);
+ // XXX We should be throwing a Utils.getSecurityError() here in at least
+ // some cases. See bug 1266492.
+ if (Utils.resolveContentType(aContentType) != TYPE_MAYBE_FEED) {
+ return;
+ }
+
+ messageManager.sendAsyncMessage("WCCR:registerContentHandler",
+ { contentType: aContentType,
+ uri: uri.spec,
+ title: aTitle });
+ },
+
+ registerProtocolHandler(aProtocol, aURIString, aTitle, aBrowserOrWindow) {
+ // aBrowserOrWindow must be a window.
+ let messageManager = aBrowserOrWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsITabChild)
+ .messageManager;
+
+ let uri = Utils.checkAndGetURI(aURIString, aBrowserOrWindow);
+ Utils.checkProtocolHandlerAllowed(aProtocol, aURIString, aBrowserOrWindow);
+
+ messageManager.sendAsyncMessage("WCCR:registerProtocolHandler",
+ { protocol: aProtocol,
+ uri: uri.spec,
+ title: aTitle });
+ },
+
+ /**
+ * See nsIFactory
+ */
+ createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+
+ classID: WCCR_CLASSID,
+
+ /**
+ * See nsISupports
+ */
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIWebContentHandlerRegistrar,
+ Ci.nsIWebContentConverterService,
+ Ci.nsIFactory])
+};
+
+this.NSGetFactory =
+ (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) ?
+ XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrarContent]) :
+ XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);
diff --git a/browser/components/feeds/content/subscribe.js b/browser/components/feeds/content/subscribe.js
new file mode 100644
index 000000000..05a564bf1
--- /dev/null
+++ b/browser/components/feeds/content/subscribe.js
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+/* global BrowserFeedWriter */
+
+var SubscribeHandler = {
+ /**
+ * The nsIFeedWriter object that produces the UI
+ */
+ _feedWriter: null,
+
+ init: function SH_init() {
+ this._feedWriter = new BrowserFeedWriter();
+ },
+
+ writeContent: function SH_writeContent() {
+ this._feedWriter.writeContent();
+ },
+
+ uninit: function SH_uninit() {
+ this._feedWriter.close();
+ }
+};
diff --git a/browser/components/feeds/content/subscribe.xhtml b/browser/components/feeds/content/subscribe.xhtml
new file mode 100644
index 000000000..a55c80053
--- /dev/null
+++ b/browser/components/feeds/content/subscribe.xhtml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % feedDTD
+ SYSTEM "chrome://browser/locale/feeds/subscribe.dtd">
+ %feedDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<html id="feedHandler"
+ xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&feedPage.title;</title>
+ <link rel="stylesheet"
+ href="chrome://browser/skin/feeds/subscribe.css"
+ type="text/css"
+ media="all"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/feeds/subscribe.js"/>
+ </head>
+ <body onload="SubscribeHandler.writeContent();" onunload="SubscribeHandler.uninit();">
+ <div id="feedHeaderContainer">
+ <div id="feedHeader" dir="&locale.dir;">
+ <div id="feedIntroText">
+ <p id="feedSubscriptionInfo1" />
+ <p id="feedSubscriptionInfo2" />
+ </div>
+ <div id="feedSubscribeLine">
+ <label id="subscribeUsingDescription">
+ <select id="handlersMenuList">
+ <option id="liveBookmarksMenuItem" selected="true">&feedLiveBookmarks;</option>
+ <option disabled="true">&#x2501;&#x2501;&#x2501;&#x2501;&#x2501;&#x2501;&#x2501;</option>
+ </select>
+ </label>
+ <label id="checkboxText">
+ <input type="checkbox" id="alwaysUse" class="alwaysUse" checked="false"/>
+ </label>
+ <button id="subscribeButton">&feedSubscribeNow;</button>
+ </div>
+ </div>
+ <div id="feedHeaderContainerSpacer"/>
+ </div>
+
+ <script type="application/javascript">
+ /* import-globals-from subscribe.js */
+ SubscribeHandler.init();
+ </script>
+
+ <div id="feedBody">
+ <div id="feedTitle">
+ <a id="feedTitleLink">
+ <img id="feedTitleImage"/>
+ </a>
+ <div id="feedTitleContainer">
+ <h1 id="feedTitleText"/>
+ <h2 id="feedSubtitleText"/>
+ </div>
+ </div>
+ <div id="feedContent"/>
+ </div>
+ </body>
+</html>
diff --git a/browser/components/feeds/jar.mn b/browser/components/feeds/jar.mn
new file mode 100644
index 000000000..8570112c1
--- /dev/null
+++ b/browser/components/feeds/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/feeds/subscribe.xhtml (content/subscribe.xhtml)
+ content/browser/feeds/subscribe.js (content/subscribe.js)
diff --git a/browser/components/feeds/moz.build b/browser/components/feeds/moz.build
new file mode 100644
index 000000000..c22129165
--- /dev/null
+++ b/browser/components/feeds/moz.build
@@ -0,0 +1,41 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
+MOCHITEST_MANIFESTS += ['test/mochitest.ini']
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += [
+ 'nsIFeedResultService.idl',
+ 'nsIWebContentConverterRegistrar.idl',
+]
+
+XPIDL_MODULE = 'browser-feeds'
+
+SOURCES += [
+ 'nsFeedSniffer.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'BrowserFeeds.manifest',
+ 'FeedConverter.js',
+ 'FeedWriter.js',
+ 'WebContentConverter.js',
+]
+
+FINAL_LIBRARY = 'browsercomps'
+
+for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
+ DEFINES[var] = CONFIG[var]
+
+LOCAL_INCLUDES += [
+ '../build',
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'RSS Discovery and Preview')
diff --git a/browser/components/feeds/nsFeedSniffer.cpp b/browser/components/feeds/nsFeedSniffer.cpp
new file mode 100644
index 000000000..f2d0da776
--- /dev/null
+++ b/browser/components/feeds/nsFeedSniffer.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFeedSniffer.h"
+
+
+#include "nsNetCID.h"
+#include "nsXPCOM.h"
+#include "nsCOMPtr.h"
+#include "nsStringStream.h"
+
+#include "nsBrowserCompsCID.h"
+
+#include "nsICategoryManager.h"
+#include "nsIServiceManager.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIStreamConverterService.h"
+#include "nsIStreamConverter.h"
+
+#include "nsIStreamListener.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIMIMEHeaderParam.h"
+
+#include "nsMimeTypes.h"
+#include "nsIURI.h"
+#include <algorithm>
+
+#define TYPE_ATOM "application/atom+xml"
+#define TYPE_RSS "application/rss+xml"
+#define TYPE_MAYBE_FEED "application/vnd.mozilla.maybe.feed"
+
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_RSS "http://purl.org/rss/1.0/"
+
+#define MAX_BYTES 512u
+
+NS_IMPL_ISUPPORTS(nsFeedSniffer,
+ nsIContentSniffer,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+nsresult
+nsFeedSniffer::ConvertEncodedData(nsIRequest* request,
+ const uint8_t* data,
+ uint32_t length)
+{
+ nsresult rv = NS_OK;
+
+ mDecodedData = "";
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
+ if (!httpChannel)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsAutoCString contentEncoding;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
+ contentEncoding);
+ if (!contentEncoding.IsEmpty()) {
+ nsCOMPtr<nsIStreamConverterService> converterService(do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID));
+ if (converterService) {
+ ToLowerCase(contentEncoding);
+
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = converterService->AsyncConvertData(contentEncoding.get(),
+ "uncompressed", this, nullptr,
+ getter_AddRefs(converter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ converter->OnStartRequest(request, nullptr);
+
+ nsCOMPtr<nsIStringInputStream> rawStream =
+ do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
+ if (!rawStream)
+ return NS_ERROR_FAILURE;
+
+ rv = rawStream->SetData((const char*)data, length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = converter->OnDataAvailable(request, nullptr, rawStream, 0, length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ converter->OnStopRequest(request, nullptr, NS_OK);
+ }
+ }
+ return rv;
+}
+
+template<int N>
+static bool
+StringBeginsWithLowercaseLiteral(nsAString& aString,
+ const char (&aSubstring)[N])
+{
+ return StringHead(aString, N).LowerCaseEqualsLiteral(aSubstring);
+}
+
+bool
+HasAttachmentDisposition(nsIHttpChannel* httpChannel)
+{
+ if (!httpChannel)
+ return false;
+
+ uint32_t disp;
+ nsresult rv = httpChannel->GetContentDisposition(&disp);
+
+ if (NS_SUCCEEDED(rv) && disp == nsIChannel::DISPOSITION_ATTACHMENT)
+ return true;
+
+ return false;
+}
+
+/**
+ * @return the first occurrence of a character within a string buffer,
+ * or nullptr if not found
+ */
+static const char*
+FindChar(char c, const char *begin, const char *end)
+{
+ for (; begin < end; ++begin) {
+ if (*begin == c)
+ return begin;
+ }
+ return nullptr;
+}
+
+/**
+ *
+ * Determine if a substring is the "documentElement" in the document.
+ *
+ * All of our sniffed substrings: <rss, <feed, <rdf:RDF must be the "document"
+ * element within the XML DOM, i.e. the root container element. Otherwise,
+ * it's possible that someone embedded one of these tags inside a document of
+ * another type, e.g. a HTML document, and we don't want to show the preview
+ * page if the document isn't actually a feed.
+ *
+ * @param start
+ * The beginning of the data being sniffed
+ * @param end
+ * The end of the data being sniffed, right before the substring that
+ * was found.
+ * @returns true if the found substring is the documentElement, false
+ * otherwise.
+ */
+static bool
+IsDocumentElement(const char *start, const char* end)
+{
+ // For every tag in the buffer, check to see if it's a PI, Doctype or
+ // comment, our desired substring or something invalid.
+ while ( (start = FindChar('<', start, end)) ) {
+ ++start;
+ if (start >= end)
+ return false;
+
+ // Check to see if the character following the '<' is either '?' or '!'
+ // (processing instruction or doctype or comment)... these are valid nodes
+ // to have in the prologue.
+ if (*start != '?' && *start != '!')
+ return false;
+
+ // Now advance the iterator until the '>' (We do this because we don't want
+ // to sniff indicator substrings that are embedded within other nodes, e.g.
+ // comments: <!-- <rdf:RDF .. > -->
+ start = FindChar('>', start, end);
+ if (!start)
+ return false;
+
+ ++start;
+ }
+ return true;
+}
+
+/**
+ * Determines whether or not a string exists as the root element in an XML data
+ * string buffer.
+ * @param dataString
+ * The data being sniffed
+ * @param substring
+ * The substring being tested for existence and root-ness.
+ * @returns true if the substring exists and is the documentElement, false
+ * otherwise.
+ */
+static bool
+ContainsTopLevelSubstring(nsACString& dataString, const char *substring)
+{
+ nsACString::const_iterator start, end;
+ dataString.BeginReading(start);
+ dataString.EndReading(end);
+
+ if (!FindInReadable(nsCString(substring), start, end)){
+ return false;
+ }
+
+ auto offset = start.get() - dataString.Data();
+
+ const char *begin = dataString.BeginReading();
+
+ // Only do the validation when we find the substring.
+ return IsDocumentElement(begin, begin + offset);
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::GetMIMETypeFromContent(nsIRequest* request,
+ const uint8_t* data,
+ uint32_t length,
+ nsACString& sniffedType)
+{
+ nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(request));
+ if (!channel)
+ return NS_ERROR_NO_INTERFACE;
+
+ // Check that this is a GET request, since you can't subscribe to a POST...
+ nsAutoCString method;
+ channel->GetRequestMethod(method);
+ if (!method.EqualsLiteral("GET")) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // We need to find out if this is a load of a view-source document. In this
+ // case we do not want to override the content type, since the source display
+ // does not need to be converted from feed format to XUL. More importantly,
+ // we don't want to change the content type from something
+ // nsContentDLF::CreateInstance knows about (e.g. application/xml, text/html
+ // etc) to something that only the application fe knows about (maybe.feed)
+ // thus deactivating syntax highlighting.
+ nsCOMPtr<nsIURI> originalURI;
+ channel->GetOriginalURI(getter_AddRefs(originalURI));
+
+ nsAutoCString scheme;
+ originalURI->GetScheme(scheme);
+ if (scheme.EqualsLiteral("view-source")) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // Check the Content-Type to see if it is set correctly. If it is set to
+ // something specific that we think is a reliable indication of a feed, don't
+ // bother sniffing since we assume the site maintainer knows what they're
+ // doing.
+ nsAutoCString contentType;
+ channel->GetContentType(contentType);
+ bool noSniff = contentType.EqualsLiteral(TYPE_RSS) ||
+ contentType.EqualsLiteral(TYPE_ATOM);
+
+ // Check to see if this was a feed request from the location bar or from
+ // the feed: protocol. This is also a reliable indication.
+ // The value of the header doesn't matter.
+ if (!noSniff) {
+ nsAutoCString sniffHeader;
+ nsresult foundHeader =
+ channel->GetRequestHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
+ sniffHeader);
+ noSniff = NS_SUCCEEDED(foundHeader);
+ }
+
+ if (noSniff) {
+ // check for an attachment after we have a likely feed.
+ if(HasAttachmentDisposition(channel)) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // set the feed header as a response header, since we have good metadata
+ // telling us that the feed is supposed to be RSS or Atom
+ channel->SetResponseHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
+ NS_LITERAL_CSTRING("1"), false);
+ sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
+ return NS_OK;
+ }
+
+ // Don't sniff arbitrary types. Limit sniffing to situations that
+ // we think can reasonably arise.
+ if (!contentType.EqualsLiteral(TEXT_HTML) &&
+ !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) &&
+ // Same criterion as XMLHttpRequest. Should we be checking for "+xml"
+ // and check for text/xml and application/xml by hand instead?
+ contentType.Find("xml") == -1) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // Now we need to potentially decompress data served with
+ // Content-Encoding: gzip
+ nsresult rv = ConvertEncodedData(request, data, length);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // We cap the number of bytes to scan at MAX_BYTES to prevent picking up
+ // false positives by accidentally reading document content, e.g. a "how to
+ // make a feed" page.
+ const char* testData;
+ if (mDecodedData.IsEmpty()) {
+ testData = (const char*)data;
+ length = std::min(length, MAX_BYTES);
+ } else {
+ testData = mDecodedData.get();
+ length = std::min(mDecodedData.Length(), MAX_BYTES);
+ }
+
+ // The strategy here is based on that described in:
+ // http://blogs.msdn.com/rssteam/articles/PublishersGuide.aspx
+ // for interoperarbility purposes.
+
+ // Thus begins the actual sniffing.
+ nsDependentCSubstring dataString((const char*)testData, length);
+
+ bool isFeed = false;
+
+ // RSS 0.91/0.92/2.0
+ isFeed = ContainsTopLevelSubstring(dataString, "<rss");
+
+ // Atom 1.0
+ if (!isFeed)
+ isFeed = ContainsTopLevelSubstring(dataString, "<feed");
+
+ // RSS 1.0
+ if (!isFeed) {
+ bool foundNS_RDF = FindInReadable(NS_LITERAL_CSTRING(NS_RDF), dataString);
+ bool foundNS_RSS = FindInReadable(NS_LITERAL_CSTRING(NS_RSS), dataString);
+ isFeed = ContainsTopLevelSubstring(dataString, "<rdf:RDF") &&
+ foundNS_RDF && foundNS_RSS;
+ }
+
+ // If we sniffed a feed, coerce our internal type
+ if (isFeed && !HasAttachmentDisposition(channel))
+ sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
+ else
+ sniffedType.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ return NS_OK;
+}
+
+nsresult
+nsFeedSniffer::AppendSegmentToString(nsIInputStream* inputStream,
+ void* closure,
+ const char* rawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t* writeCount)
+{
+ nsCString* decodedData = static_cast<nsCString*>(closure);
+ decodedData->Append(rawSegment, count);
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnDataAvailable(nsIRequest* request, nsISupports* context,
+ nsIInputStream* stream, uint64_t offset,
+ uint32_t count)
+{
+ uint32_t read;
+ return stream->ReadSegments(AppendSegmentToString, &mDecodedData, count,
+ &read);
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult status)
+{
+ return NS_OK;
+}
diff --git a/browser/components/feeds/nsFeedSniffer.h b/browser/components/feeds/nsFeedSniffer.h
new file mode 100644
index 000000000..b7ac002bd
--- /dev/null
+++ b/browser/components/feeds/nsFeedSniffer.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsIContentSniffer.h"
+#include "nsIStreamListener.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+class nsFeedSniffer final : public nsIContentSniffer,
+ nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTSNIFFER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ static nsresult AppendSegmentToString(nsIInputStream* inputStream,
+ void* closure,
+ const char* rawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t* writeCount);
+
+protected:
+ ~nsFeedSniffer() {}
+
+ nsresult ConvertEncodedData(nsIRequest* request, const uint8_t* data,
+ uint32_t length);
+
+private:
+ nsCString mDecodedData;
+};
+
diff --git a/browser/components/feeds/nsIFeedResultService.idl b/browser/components/feeds/nsIFeedResultService.idl
new file mode 100644
index 000000000..f745fa693
--- /dev/null
+++ b/browser/components/feeds/nsIFeedResultService.idl
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIURI;
+interface nsIRequest;
+interface nsIFeedResult;
+
+/**
+ * nsIFeedResultService provides a globally-accessible object for retrieving
+ * the results of feed processing.
+ */
+[scriptable, uuid(95309fd2-7b3a-47fb-97f3-5c460d9473cd)]
+interface nsIFeedResultService : nsISupports
+{
+ /**
+ * When set to true, forces the preview page to be displayed, regardless
+ * of the user's preferences.
+ */
+ attribute boolean forcePreviewPage;
+
+ /**
+ * Adds a URI to the user's specified external feed handler, or live
+ * bookmarks.
+ * @param uri
+ * The uri of the feed to add.
+ * @param title
+ * The title of the feed to add.
+ * @param subtitle
+ * The subtitle of the feed to add.
+ * @param feedType
+ * The nsIFeed type of the feed. See nsIFeed.idl
+ * @param feedReader
+ * The type of feed reader we're using (client, bookmarks, default)
+ * If this parameter is null, the type is set to default
+ */
+ void addToClientReader(in AUTF8String uri,
+ in AString title,
+ in AString subtitle,
+ in unsigned long feedType,
+ [optional] in AString feedReader);
+
+ /**
+ * Registers a Feed Result object with a globally accessible service
+ * so that it can be accessed by a singleton method outside the usual
+ * flow of control in document loading.
+ *
+ * @param feedResult
+ * An object implementing nsIFeedResult representing the feed.
+ */
+ void addFeedResult(in nsIFeedResult feedResult);
+
+ /**
+ * Gets a Feed Handler object registered using addFeedResult.
+ *
+ * @param uri
+ * The URI of the feed a handler is being requested for
+ */
+ nsIFeedResult getFeedResult(in nsIURI uri);
+
+ /**
+ * Unregisters a Feed Handler object registered using addFeedResult.
+ * @param uri
+ * The feed URI the handler was registered under. This must be
+ * the same *instance* the feed was registered under.
+ */
+ void removeFeedResult(in nsIURI uri);
+};
diff --git a/browser/components/feeds/nsIWebContentConverterRegistrar.idl b/browser/components/feeds/nsIWebContentConverterRegistrar.idl
new file mode 100644
index 000000000..08ce2f4ae
--- /dev/null
+++ b/browser/components/feeds/nsIWebContentConverterRegistrar.idl
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIMIMEInfo.idl"
+#include "nsIWebContentHandlerRegistrar.idl"
+
+interface nsIRequest;
+
+[scriptable, uuid(eb361098-5158-4b21-8f98-50b445f1f0b2)]
+interface nsIWebContentHandlerInfo : nsIHandlerApp
+{
+ /**
+ * The content type handled by the handler
+ */
+ readonly attribute AString contentType;
+
+ /**
+ * The uri of the handler, with an embedded %s where the URI of the loaded
+ * document will be encoded.
+ */
+ readonly attribute AString uri;
+
+ /**
+ * Gets the service URL Spec, with the loading document URI encoded in it.
+ * @param uri
+ * The URI of the document being loaded
+ * @returns The URI of the service with the loading document URI encoded in
+ * it.
+ */
+ AString getHandlerURI(in AString uri);
+};
+
+[scriptable, uuid(de7cc06e-e778-45cb-b7db-7a114e1e75b1)]
+interface nsIWebContentConverterService : nsIWebContentHandlerRegistrar
+{
+ /**
+ * Specifies the handler to be used to automatically handle all links of a
+ * certain content type from now on.
+ * @param contentType
+ * The content type to automatically load with the specified handler
+ * @param handler
+ * A web service handler. If this is null, no automatic action is
+ * performed and the user must choose.
+ * @throws NS_ERROR_NOT_AVAILABLE if the service refered to by |handler| is
+ * not already registered.
+ */
+ void setAutoHandler(in AString contentType, in nsIWebContentHandlerInfo handler);
+
+ /**
+ * Gets the auto handler specified for a particular content type
+ * @param contentType
+ * The content type to look up an auto handler for.
+ * @returns The web service handler that will automatically handle all
+ * documents of the specified type. null if there is no automatic
+ * handler. (Handlers may be registered, just none of them specified
+ * as "automatic").
+ */
+ nsIWebContentHandlerInfo getAutoHandler(in AString contentType);
+
+ /**
+ * Gets a web handler for the specified service URI
+ * @param contentType
+ * The content type of the service being located
+ * @param uri
+ * The service URI of the handler to locate.
+ * @returns A web service handler that uses the specified uri.
+ */
+ nsIWebContentHandlerInfo getWebContentHandlerByURI(in AString contentType,
+ in AString uri);
+
+ /**
+ * Loads the preferred handler when content of a registered type is about
+ * to be loaded.
+ * @param request
+ * The nsIRequest for the load of the content
+ */
+ void loadPreferredHandler(in nsIRequest request);
+
+ /**
+ * Removes a registered protocol handler
+ * @param protocol
+ * The protocol scheme to remove a service handler for
+ * @param uri
+ * The uri of the service handler to remove
+ */
+ void removeProtocolHandler(in AString protocol, in AString uri);
+
+ /**
+ * Removes a registered content handler
+ * @param contentType
+ * The content type to remove a service handler for
+ * @param uri
+ * The uri of the service handler to remove
+ */
+ void removeContentHandler(in AString contentType, in AString uri);
+
+ /**
+ * Gets the list of content handlers for a particular type.
+ * @param contentType
+ * The content type to get handlers for
+ * @returns An array of nsIWebContentHandlerInfo objects
+ */
+ void getContentHandlers(in AString contentType,
+ [optional] out unsigned long count,
+ [retval,array,size_is(count)] out nsIWebContentHandlerInfo handlers);
+
+ /**
+ * Resets the list of available content handlers to the default set from
+ * the distribution.
+ * @param contentType
+ * The content type to reset handlers for
+ */
+ void resetHandlersForType(in AString contentType);
+};
+
diff --git a/browser/components/feeds/test/.eslintrc.js b/browser/components/feeds/test/.eslintrc.js
new file mode 100644
index 000000000..3c788d6d6
--- /dev/null
+++ b/browser/components/feeds/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/mochitest.eslintrc.js"
+ ]
+};
diff --git a/browser/components/feeds/test/bug368464-data.xml b/browser/components/feeds/test/bug368464-data.xml
new file mode 100644
index 000000000..2745b061d
--- /dev/null
+++ b/browser/components/feeds/test/bug368464-data.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+ <rdf:RDF
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://my.netscape.com/rdf/simple/0.9/">
+ <channel>
+ <title>Tinderbox - Firefox</title>
+ <description>Build bustages for Firefox</description>
+ <link>http://tinderbox.mozilla.org/showbuilds.cgi?tree=Firefox</link>
+ </channel>
+ <image>
+ <title>Bad</title>
+ <url>http://tinderbox.mozilla.org/channelflames.gif</url>
+ <link>http://tinderbox.mozilla.org/showbuilds.cgi?tree=Firefox</link>
+ </image>
+ <item><title>The tree is currently closed</title><link>http://tinderbox.mozilla.org/showbuilds.cgi?tree=Firefox</link></item>
+
+<item><title>MacOSX Darwin 8.8.4 qm-xserve01 dep unit test is in flames</title><link>http://tinderbox.mozilla.org/showbuilds.cgi?tree=Firefox</link></item>
+</rdf:RDF>
diff --git a/browser/components/feeds/test/bug408328-data.xml b/browser/components/feeds/test/bug408328-data.xml
new file mode 100644
index 000000000..e9385e5ab
--- /dev/null
+++ b/browser/components/feeds/test/bug408328-data.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2003-12-13T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
+
+ <entry>
+
+ <title>Good item</title>
+ <link href="http://example.org/first"/>
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
+ <updated>2003-12-13T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+ <entry>
+
+ <title>data: link</title>
+ <link href="data:text/plain,Hi"/>
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6b</id>
+ <updated>2003-12-13T18:30:03Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+ <entry>
+
+ <title>javascript: link</title>
+ <link href="javascript:alert('Hi')"/>
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6c</id>
+ <updated>2003-12-13T18:30:04Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+ <entry>
+
+ <title>file: link</title>
+ <link href="file:///var/"/>
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6d</id>
+ <updated>2003-12-13T18:30:05Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+ <entry>
+
+ <title>chrome: link</title>
+ <link href="chrome://browser/content/browser.js"/>
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6e</id>
+ <updated>2003-12-13T18:30:06Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/browser/components/feeds/test/bug436801-data.xml b/browser/components/feeds/test/bug436801-data.xml
new file mode 100644
index 000000000..0e45c7ed8
--- /dev/null
+++ b/browser/components/feeds/test/bug436801-data.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:base="http://www.example.com/">
+
+ <title type="xhtml" xml:base="/foo/bar/">
+ <div xmlns="http://www.w3.org/1999/xhtml">Example of a <em>special</em> feed (<img height="20px" src="baz.png" alt="base test sprite"/>)</div>
+ </title>
+
+ <subtitle type="html" xml:base="/foo/bar/">
+ <![CDATA[
+ With a <em>special</em> subtitle (<img height="20px" src="baz.png" alt="base test sprite"/>)
+ ]]>
+ </subtitle>
+
+ <link href="http://example.org/"/>
+
+ <updated>2010-09-02T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+
+ <id>urn:uuid:22906062-ecbd-46e2-b6a7-3039506a398f</id>
+
+ <entry>
+ <title type="xhtml" xml:base="/foo/bar/">
+ <div xmlns="http://www.w3.org/1999/xhtml">Some <abbr title="Extensible Hyper-text Mark-up Language">XHTML</abbr> examples (<img height="20px" src="baz.png" alt="base test sprite"/>)</div>
+ </title>
+ <id>urn:uuid:b48083a7-71a7-4c9c-8515-b7c0d22955e7</id>
+ <updated>2010-09-02T18:30:02Z</updated>
+ <summary>Some text.</summary>
+ </entry>
+
+ <entry>
+ <title type="html" xml:base="/foo/bar/">
+ <![CDATA[
+ Some <abbr title="Hyper-text Mark-up Language">HTML</abbr> examples (<img height="20px" src="baz.png" alt="base test sprite"/>)
+ ]]>
+ </title>
+ <id>urn:uuid:1424967a-280a-414d-b0ab-8b11c4ac1bb7</id>
+ <updated>2010-09-02T18:30:02Z</updated>
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/browser/components/feeds/test/bug494328-data.xml b/browser/components/feeds/test/bug494328-data.xml
new file mode 100644
index 000000000..58342bafc
--- /dev/null
+++ b/browser/components/feeds/test/bug494328-data.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0">
+ <channel>
+ <title>Channel title</title>
+ <description>Channel description</description>
+ <link>Channel link</link>
+ <item>
+ <title>Episode 1</title>
+ <enclosure url="http://www.example.com/podcasts/Episode%201" length="0" type="audio/x-m4a" />
+ </item>
+ <item>
+ <title>Episode 2</title>
+ <enclosure url="http://www.example.com/podcasts/Episode%20%232" length="0" type="audio/x-m4a" />
+ </item>
+ <item>
+ <title>Episode 3</title>
+ <enclosure url="http://www.example.com/podcasts/Episode%20%233/" length="0" type="audio/x-m4a" />
+ </item>
+ <item>
+ <title>Episode 4</title>
+ <enclosure url="http://www.example.com/podcasts/Is%20This%20Episode%20%234%3F" length="0" type="audio/x-m4a" />
+ </item>
+ </channel>
+</rss>
diff --git a/browser/components/feeds/test/bug589543-data.xml b/browser/components/feeds/test/bug589543-data.xml
new file mode 100644
index 000000000..0e700b6d8
--- /dev/null
+++ b/browser/components/feeds/test/bug589543-data.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
+
+ <entry>
+
+ <title>Item</title>
+ <link href="http://example.org/first"/>
+ <id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/browser/components/feeds/test/chrome/.eslintrc.js b/browser/components/feeds/test/chrome/.eslintrc.js
new file mode 100644
index 000000000..8c0f4f574
--- /dev/null
+++ b/browser/components/feeds/test/chrome/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/browser/components/feeds/test/chrome/chrome.ini b/browser/components/feeds/test/chrome/chrome.ini
new file mode 100644
index 000000000..7bad142ab
--- /dev/null
+++ b/browser/components/feeds/test/chrome/chrome.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files = sample_feed.atom
+ !/browser/components/feeds/test/bug408328-data.xml
+ !/browser/components/feeds/test/valid-feed.xml
+ !/browser/components/feeds/test/valid-unsniffable-feed.xml
+
+[test_423060.xul]
+[test_bug368464.html]
+[test_bug408328.html]
+[test_maxSniffing.html]
diff --git a/browser/components/feeds/test/chrome/sample_feed.atom b/browser/components/feeds/test/chrome/sample_feed.atom
new file mode 100644
index 000000000..add75efb4
--- /dev/null
+++ b/browser/components/feeds/test/chrome/sample_feed.atom
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2003-12-13T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
+
+ <entry>
+
+ <title>Atom-Powered Robots Run Amok</title>
+ <link href="http://example.org/2003/12/13/atom03"/>
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
+ <updated>2003-12-13T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/browser/components/feeds/test/chrome/test_423060.xul b/browser/components/feeds/test/chrome/test_423060.xul
new file mode 100644
index 000000000..465cf2dd2
--- /dev/null
+++ b/browser/components/feeds/test/chrome/test_423060.xul
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<window title="Make sure feed preview works when a default reader is selected"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var wccrID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
+ /* abort the test if web feed handlers are not available */
+ if (!Cc[wccrID])
+ SimpleTest.finish()
+
+ /* Turn off the first run UI */
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ prefBranch.setBoolPref("browser.feeds.showFirstRunUI", false);
+
+ /* register a handler for the feed type */
+ const MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+ var handlerPage = "http://mochi.test:8888/tests/toolkit/components/places/tests/chrome/demohandler.html?feedurl=%s";
+ var wccr = Cc[wccrID].getService(Ci.nsIWebContentConverterService);
+ wccr.registerContentHandler(MAYBE_FEED, handlerPage, "Demo handler", null);
+ var demoHandler = wccr.getWebContentHandlerByURI(MAYBE_FEED, handlerPage);
+ wccr.setAutoHandler(MAYBE_FEED, demoHandler);
+
+ /* Don't show the preview page */
+ prefBranch.setCharPref("browser.feeds.handler", "reader");
+
+ function finishUp() {
+ var theframe = document.getElementById('theframe');
+ var previewURL = "http://mochi.test:8888/tests/toolkit/components/places/tests/chrome/demohandler.html?feedurl=http%3A%2F%2Fmochi.test%3A8888%2Ftests%2Ftoolkit%2Fcomponents%2Fplaces%2Ftests%2Fchrome%2Fsample_feed.atom";
+ is(theframe.contentDocument.URL, previewURL);
+
+ /* remove our demoHandler */
+ wccr.setAutoHandler(MAYBE_FEED, null);
+ wccr.removeContentHandler(MAYBE_FEED, handlerPage);
+ prefBranch.setCharPref("browser.feeds.handler", "ask");
+ prefBranch.setBoolPref("browser.feeds.showFirstRunUI", true);
+
+ SimpleTest.finish();
+ }
+ </script>
+ <html:iframe src="http://mochi.test:8888/tests/toolkit/components/places/tests/chrome/sample_feed.atom" height="400px"
+ id="theframe" onload="finishUp();">
+ </html:iframe>
+</window>
diff --git a/browser/components/feeds/test/chrome/test_bug368464.html b/browser/components/feeds/test/chrome/test_bug368464.html
new file mode 100644
index 000000000..dd7486f66
--- /dev/null
+++ b/browser/components/feeds/test/chrome/test_bug368464.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=368464
+-->
+<head>
+ <title>Test that RSS 0.90 isn't sniffed</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=368464">Mozilla Bug 368464</a>
+<p id="display"><iframe id="testFrame" src="http://mochi.test:8888/tests/browser/components/feeds/test/bug368464-data.xml"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 368464 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ ok($("testFrame").contentDocument.documentElement.id != "feedHandler",
+ "RSS 0.90 shouldn't be sniffed as a feed");
+});
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/components/feeds/test/chrome/test_bug408328.html b/browser/components/feeds/test/chrome/test_bug408328.html
new file mode 100644
index 000000000..e4901320a
--- /dev/null
+++ b/browser/components/feeds/test/chrome/test_bug408328.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=408328
+-->
+<head>
+ <title>Test feed preview safe-linkification</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408328">Mozilla Bug 408328</a>
+<p id="display"><iframe id="testFrame" src="http://mochi.test:8888/tests/browser/components/feeds/test/bug408328-data.xml"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 408328 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var links = $("testFrame").contentDocument.getElementById("feedContent").getElementsByTagName("a");
+ is(links.length, 5, "wrong number of linked items in feed preview");
+ for (var i = 0; i < links.length; i++) {
+ if (links[i].href)
+ is(links[i].href, "http://example.org/first", "bad linkified item");
+ }
+});
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/components/feeds/test/chrome/test_maxSniffing.html b/browser/components/feeds/test/chrome/test_maxSniffing.html
new file mode 100644
index 000000000..7a2044687
--- /dev/null
+++ b/browser/components/feeds/test/chrome/test_maxSniffing.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=739040
+-->
+<head>
+ <title>Test that we only sniff 512 bytes</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=739040">Mozilla Bug 739040</a>
+<p id="display">
+ <iframe id="validTestFrame" src="http://mochi.test:8888/tests/browser/components/feeds/test/valid-feed.xml"></iframe>
+ <iframe id="unsniffableTestFrame" src="http://mochi.test:8888/tests/browser/components/feeds/test/valid-unsniffable-feed.xml"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 739040 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is($("validTestFrame").contentDocument.documentElement.id, "feedHandler",
+ "valid feed should be sniffed");
+ isnot($("unsniffableTestFrame").contentDocument.documentElement.id, "feedHandler",
+ "unsniffable feed should not be sniffed");
+});
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/components/feeds/test/mochitest.ini b/browser/components/feeds/test/mochitest.ini
new file mode 100644
index 000000000..fc1e6a1a9
--- /dev/null
+++ b/browser/components/feeds/test/mochitest.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+ bug368464-data.xml
+ bug408328-data.xml
+ bug436801-data.xml
+ bug494328-data.xml
+ bug589543-data.xml
+ valid-feed.xml
+ valid-unsniffable-feed.xml
+
+[test_bug436801.html]
+[test_bug494328.html]
+[test_bug589543.html]
+[test_registerHandler.html]
diff --git a/browser/components/feeds/test/test_bug436801.html b/browser/components/feeds/test/test_bug436801.html
new file mode 100644
index 000000000..29fb5acf0
--- /dev/null
+++ b/browser/components/feeds/test/test_bug436801.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=436801
+-->
+<head>
+ <title>Test feed preview subscribe UI</title>
+ <script type="text/javascript" src="/MochiKit/packed.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436801">Mozilla Bug 436801</a>
+<p id="display"><iframe id="testFrame" src="bug436801-data.xml"></iframe></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function () {
+ var doc = SpecialPowers.wrap($("testFrame")).contentDocument;
+
+ checkNode(doc.getElementById("feedTitleText"), [
+ "ELEMENT", "h1", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "Example of a "],
+ ["ELEMENT", "em", [
+ ["TEXT", "special"],
+ ]],
+ ["TEXT", " feed ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+
+ checkNode(doc.getElementById("feedSubtitleText"), [
+ "ELEMENT", "h2", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "With a "],
+ ["ELEMENT", "em", [
+ ["TEXT", "special"],
+ ]],
+ ["TEXT", " subtitle ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+
+ checkNode(doc.querySelector(".entry").firstChild.firstChild.firstChild, [
+ "ELEMENT", "span", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "Some "],
+ ["ELEMENT", "abbr", { title: "Extensible Hyper-text Mark-up Language" }, [
+ ["TEXT", "XHTML"],
+ ]],
+ ["TEXT", " examples ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+
+ checkNode(doc.querySelectorAll(".entry")[1].firstChild.firstChild.firstChild, [
+ "ELEMENT", "span", { "xml:base": "http://www.example.com/foo/bar/" }, [
+ ["TEXT", "Some "],
+ ["ELEMENT", "abbr", { title: "Hyper-text Mark-up Language" }, [
+ ["TEXT", "HTML"],
+ ]],
+ ["TEXT", " examples ("],
+ ["ELEMENT", "img", { "src": "baz.png" }],
+ ["TEXT", ")"],
+ ]
+ ]);
+});
+
+addLoadEvent(SimpleTest.finish);
+
+function checkNode(node, schema) {
+ var typeName = schema.shift() + "_NODE";
+ var type = Node[typeName];
+ is(node.nodeType, type, "Node should be expected type " + typeName);
+ if (type == Node.TEXT_NODE) {
+ var text = schema.shift();
+ is(node.data, text, "Text should match");
+ return;
+ }
+ // type == Node.ELEMENT_NODE
+ var tag = schema.shift();
+ is(node.localName, tag, "Element should have expected tag");
+ while (schema.length) {
+ let val = schema.shift();
+ if (Array.isArray(val))
+ var childSchema = val;
+ else
+ var attrSchema = val;
+ }
+ if (attrSchema) {
+ var nsTable = {
+ xml: "http://www.w3.org/XML/1998/namespace",
+ };
+ for (var name in attrSchema) {
+ var [ns, nsName] = name.split(":");
+ let val = nsName ? node.getAttributeNS(nsTable[ns], nsName) :
+ node.getAttribute(name);
+ is(val, attrSchema[name], "Attribute " + name + " should match");
+ }
+ }
+ if (childSchema) {
+ var numChildren = node.childNodes.length;
+ is(childSchema.length, numChildren,
+ "Element should have expected number of children");
+ for (var i = 0; i < numChildren; i++)
+ checkNode(node.childNodes[i], childSchema[i]);
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/components/feeds/test/test_bug494328.html b/browser/components/feeds/test/test_bug494328.html
new file mode 100644
index 000000000..054f62c1d
--- /dev/null
+++ b/browser/components/feeds/test/test_bug494328.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=494328
+-->
+<head>
+ <title>Test for bug 494328</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=494328">Mozilla Bug 494328</a>
+<p id="display"><iframe id="testFrame" src="bug494328-data.xml"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 494328 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var links = SpecialPowers.wrap($("testFrame")).contentDocument.getElementById("feedContent").querySelectorAll("div.enclosure > a");
+ is(links[0].textContent, "Episode 1", "filename decoded incorrectly");
+ is(links[1].textContent, "Episode #2", "filename decoded incorrectly");
+ is(links[2].textContent, "http://www.example.com/podcasts/Episode #3/", "filename decoded incorrectly");
+ is(links[3].textContent, "Is This Episode #4?", "filename decoded incorrectly");
+});
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/components/feeds/test/test_bug589543.html b/browser/components/feeds/test/test_bug589543.html
new file mode 100644
index 000000000..cee2a9661
--- /dev/null
+++ b/browser/components/feeds/test/test_bug589543.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=589543
+-->
+<head>
+ <title>Test feed preview subscribe UI</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=589543">Mozilla Bug 589543</a>
+<p id="display"><iframe id="testFrame" src="bug589543-data.xml"></iframe></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 589543 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var doc = SpecialPowers.wrap($("testFrame")).contentDocument;
+ var popup = doc.getElementById("handlersMenuList");
+ isnot(popup, null, "Feed preview should have a handlers popup");
+});
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/components/feeds/test/test_registerHandler.html b/browser/components/feeds/test/test_registerHandler.html
new file mode 100644
index 000000000..34e61d034
--- /dev/null
+++ b/browser/components/feeds/test/test_registerHandler.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=402788
+-->
+<head>
+ <title>Test for Bug 402788</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=402788">Mozilla Bug 402788</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 402788 **/
+
+ // return false if an exception has been catched, true otherwise
+ function testRegisterHandler(aIsProtocol, aTxt, aUri, aTitle)
+ {
+ try {
+ if (aIsProtocol)
+ navigator.registerProtocolHandler(aTxt, aUri, aTitle);
+ else
+ navigator.registerContentHandler(aTxt, aUri, aTitle);
+ }
+ catch (e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ ok(navigator.registerProtocolHandler, "navigator.registerProtocolHandler should be defined");
+ ok(navigator.registerContentHandler, "navigator.registerContentHandler should be defined");
+
+ // testing a generic case
+ is(testRegisterHandler(true, "foo", "http://mochi.test:8888/%s", "Foo handler"), true, "registering a foo protocol handler should work");
+ is(testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/%s", "Foo handler"), true, "registering a foo content handler should work");
+
+ // testing with wrong uris
+ is(testRegisterHandler(true, "foo", "http://mochi.test:8888/", "Foo handler"), false, "a protocol handler uri should contain %s");
+ is(testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/", "Foo handler"), false, "a content handler uri should contain %s");
+
+ // the spec explicitly allows relative urls to be passed
+ is(testRegisterHandler(true, "foo", "foo/%s", "Foo handler"), true, "a protocol handler uri should be valid");
+ is(testRegisterHandler(false, "application/rss+xml", "foo/%s", "Foo handler"), true, "a content handler uri should be valid");
+
+ // we should only accept to register when the handler has the same host as the current page (bug 402287)
+ is(testRegisterHandler(true, "foo", "http://remotehost:8888/%s", "Foo handler"), false, "registering a foo protocol handler with a different host should not work");
+ is(testRegisterHandler(false, "application/rss+xml", "http://remotehost:8888/%s", "Foo handler"), false, "registering a foo content handler with a different host should not work");
+
+ // restriction to http(s) for the uri of the handler (bug 401343)
+ // https should work (http already tested in the generic case)
+ is(testRegisterHandler(true, "foo", "https://mochi.test:8888/%s", "Foo handler"), true, "registering a foo protocol handler with https scheme should work");
+ is(testRegisterHandler(false, "application/rss+xml", "https://mochi.test:8888/%s", "Foo handler"), true, "registering a foo content handler with https scheme should work");
+ // ftp should not work
+ is(testRegisterHandler(true, "foo", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with ftp scheme should not work");
+ is(testRegisterHandler(false, "application/rss+xml", "ftp://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with ftp scheme should not work");
+ // chrome should not work
+ is(testRegisterHandler(true, "foo", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with chrome scheme should not work");
+ is(testRegisterHandler(false, "application/rss+xml", "chrome://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with chrome scheme should not work");
+ // foo should not work
+ is(testRegisterHandler(true, "foo", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo protocol handler with foo scheme should not work");
+ is(testRegisterHandler(false, "application/rss+xml", "foo://mochi.test:8888/%s", "Foo handler"), false, "registering a foo content handler with foo scheme should not work");
+
+ // for security reasons, protocol handlers should never be registered for some schemes (chrome, vbscript, ...) (bug 402788)
+ is(testRegisterHandler(true, "chrome", "http://mochi.test:8888/%s", "chrome handler"), false, "registering a chrome protocol handler should not work");
+ is(testRegisterHandler(true, "vbscript", "http://mochi.test:8888/%s", "vbscript handler"), false, "registering a vbscript protocol handler should not work");
+ is(testRegisterHandler(true, "javascript", "http://mochi.test:8888/%s", "javascript handler"), false, "registering a javascript protocol handler should not work");
+ is(testRegisterHandler(true, "moz-icon", "http://mochi.test:8888/%s", "moz-icon handler"), false, "registering a moz-icon protocol handler should not work");
+
+ // for security reasons, content handlers should never be registered for some types (html, ...)
+ is(testRegisterHandler(false, "application/rss+xml", "http://mochi.test:8888/%s", "Foo handler"), true, "registering rss content handlers should work");
+ is(testRegisterHandler(false, "application/atom+xml", "http://mochi.test:8888/%s", "Foo handler"), true, "registering atom content handlers should work");
+ todo_is(testRegisterHandler(false, "text/html", "http://mochi.test:8888/%s", "Foo handler"), false, "registering html content handlers should not work"); // bug 403798
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/components/feeds/test/unit/.eslintrc.js b/browser/components/feeds/test/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/feeds/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/feeds/test/unit/head_feeds.js b/browser/components/feeds/test/unit/head_feeds.js
new file mode 100644
index 000000000..3b1135ef7
--- /dev/null
+++ b/browser/components/feeds/test/unit/head_feeds.js
@@ -0,0 +1,5 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
diff --git a/browser/components/feeds/test/unit/test_355473.js b/browser/components/feeds/test/unit/test_355473.js
new file mode 100644
index 000000000..8a20d1389
--- /dev/null
+++ b/browser/components/feeds/test/unit/test_355473.js
@@ -0,0 +1,43 @@
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test() {
+ var feedFeedURI = ios.newURI("feed://example.com/feed.xml", null, null);
+ var httpFeedURI = ios.newURI("feed:http://example.com/feed.xml", null, null);
+ var httpURI = ios.newURI("http://example.com/feed.xml", null, null);
+
+ var httpsFeedURI =
+ ios.newURI("feed:https://example.com/feed.xml", null, null);
+ var httpsURI = ios.newURI("https://example.com/feed.xml", null, null);
+
+ var feedChannel = NetUtil.newChannel({
+ uri: feedFeedURI,
+ loadUsingSystemPrincipal: true
+ });
+
+ var httpChannel = NetUtil.newChannel({
+ uri: httpFeedURI,
+ loadUsingSystemPrincipal: true
+ });
+
+ var httpsChannel = NetUtil.newChannel({
+ uri: httpsFeedURI,
+ loadUsingSystemPrincipal: true
+ });
+
+ // not setting .originalURI to the original URI is naughty
+ do_check_true(feedFeedURI.equals(feedChannel.originalURI));
+ do_check_true(httpFeedURI.equals(httpChannel.originalURI));
+ do_check_true(httpsFeedURI.equals(httpsChannel.originalURI));
+
+ // actually using the horrible mess that's a feed: URI is suicidal
+ do_check_true(httpURI.equals(feedChannel.URI));
+ do_check_true(httpURI.equals(httpChannel.URI));
+ do_check_true(httpsURI.equals(httpsChannel.URI));
+
+ // check that we throw creating feed: URIs from file and ftp
+ Assert.throws(function() { ios.newURI("feed:ftp://example.com/feed.xml", null, null); },
+ "Should throw an exception when trying to create a feed: URI with an ftp: inner");
+ Assert.throws(function() { ios.newURI("feed:file:///var/feed.xml", null, null); },
+ "Should throw an exception when trying to create a feed: URI with a file: inner");
+}
diff --git a/browser/components/feeds/test/unit/test_758990.js b/browser/components/feeds/test/unit/test_758990.js
new file mode 100644
index 000000000..e6f88baf2
--- /dev/null
+++ b/browser/components/feeds/test/unit/test_758990.js
@@ -0,0 +1,42 @@
+function run_test() {
+ var success = false;
+ try {
+ ios.newURI("feed:javascript:alert('hi');", null, null);
+ }
+ catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success)
+ do_throw("We didn't throw NS_ERROR_MALFORMED_URI creating a feed:javascript: URI");
+
+ success = false;
+ try {
+ ios.newURI("feed:data:text/html,hi", null, null);
+ }
+ catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success)
+ do_throw("We didn't throw NS_ERROR_MALFORMED_URI creating a feed:data: URI");
+
+ success = false;
+ try {
+ ios.newURI("pcast:javascript:alert('hi');", null, null);
+ }
+ catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success)
+ do_throw("We didn't throw NS_ERROR_MALFORMED_URI creating a pcast:javascript: URI");
+
+ success = false;
+ try {
+ ios.newURI("pcast:data:text/html,hi", null, null);
+ }
+ catch (e) {
+ success = e.result == Cr.NS_ERROR_MALFORMED_URI;
+ }
+ if (!success)
+ do_throw("We didn't throw NS_ERROR_MALFORMED_URI creating a pcast:data: URI");
+
+}
diff --git a/browser/components/feeds/test/unit/xpcshell.ini b/browser/components/feeds/test/unit/xpcshell.ini
new file mode 100644
index 000000000..9faf57396
--- /dev/null
+++ b/browser/components/feeds/test/unit/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head_feeds.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_355473.js]
+[test_758990.js]
diff --git a/browser/components/feeds/test/valid-feed.xml b/browser/components/feeds/test/valid-feed.xml
new file mode 100644
index 000000000..0e700b6d8
--- /dev/null
+++ b/browser/components/feeds/test/valid-feed.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
+
+ <entry>
+
+ <title>Item</title>
+ <link href="http://example.org/first"/>
+ <id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/browser/components/feeds/test/valid-unsniffable-feed.xml b/browser/components/feeds/test/valid-unsniffable-feed.xml
new file mode 100644
index 000000000..e75315739
--- /dev/null
+++ b/browser/components/feeds/test/valid-unsniffable-feed.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 512 bytes!
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ -->
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/"/>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:e2df8375-99be-4848-b05e-b9d407555267</id>
+
+ <entry>
+
+ <title>Item</title>
+ <link href="http://example.org/first"/>
+ <id>urn:uuid:9e0f4bed-33d3-4a9d-97ab-ecaa31b3f14a</id>
+ <updated>2010-08-22T18:30:02Z</updated>
+
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/browser/components/migration/.eslintrc.js b/browser/components/migration/.eslintrc.js
new file mode 100644
index 000000000..6693f83d0
--- /dev/null
+++ b/browser/components/migration/.eslintrc.js
@@ -0,0 +1,82 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": [
+ "../../.eslintrc.js"
+ ],
+
+ "globals": {
+ "Components": true,
+ "dump": true,
+ "Iterator": true
+ },
+
+ "env": { "browser": true },
+
+ "rules": {
+ "block-scoped-var": "error",
+ // "brace-style": ["warn", "1tbs", {"allowSingleLine": true}],
+ "comma-dangle": "off",
+ "comma-spacing": ["warn", {"before": false, "after": true}],
+ "comma-style": ["warn", "last"],
+ // "complexity": "warn",
+ "consistent-return": "error",
+ //"curly": "error",
+ "dot-notation": "error",
+ "eol-last": "error",
+ "indent": ["warn", 2, {"SwitchCase": 1}],
+ // "key-spacing": ["warn", {"beforeColon": false, "afterColon": true}],
+ "keyword-spacing": "warn",
+ "max-nested-callbacks": ["error", 3],
+ "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", { "commentPattern": ".*[Ii]ntentional(?:ly)?\\s+fall(?:ing)?[\\s-]*through.*" }],
+ "no-lonely-if": "error",
+ "no-mixed-spaces-and-tabs": "error",
+ "no-multi-spaces": "warn",
+ "no-multi-str": "warn",
+ "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", { "varsIgnorePattern": "^C[ciur]$" }],
+ "no-with": "error",
+ "padded-blocks": ["warn", "never"],
+ "quotes": ["error", "double", { "avoidEscape": true, "allowTemplateLiterals": true }],
+ "semi": ["error", "always", {"omitLastInOneLineBlock": true }],
+ "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 }],
+ "strict": ["error", "global"],
+ "use-isnan": "error",
+ "valid-typeof": "error",
+ "yoda": "error"
+ }
+};
+
diff --git a/browser/components/migration/360seProfileMigrator.js b/browser/components/migration/360seProfileMigrator.js
new file mode 100644
index 000000000..42347d542
--- /dev/null
+++ b/browser/components/migration/360seProfileMigrator.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";
+
+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/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+
+const kBookmarksFileName = "360sefav.db";
+
+function copyToTempUTF8File(file, charset) {
+ let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ inputStream.init(file, -1, -1, 0);
+ let inputStr = NetUtil.readInputStreamToString(
+ inputStream, inputStream.available(), { charset });
+
+ // Use random to reduce the likelihood of a name collision in createUnique.
+ let rand = Math.floor(Math.random() * Math.pow(2, 15));
+ let leafName = "mozilla-temp-" + rand;
+ let tempUTF8File = FileUtils.getFile(
+ "TmpD", ["mozilla-temp-files", leafName], true);
+ tempUTF8File.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ let out = FileUtils.openAtomicFileOutputStream(tempUTF8File);
+ try {
+ let bufferedOut = Cc["@mozilla.org/network/buffered-output-stream;1"]
+ .createInstance(Ci.nsIBufferedOutputStream);
+ bufferedOut.init(out, 4096);
+ try {
+ let converterOut = Cc["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Ci.nsIConverterOutputStream);
+ converterOut.init(bufferedOut, "utf-8", 0, 0x0000);
+ try {
+ converterOut.writeString(inputStr || "");
+ bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
+ } finally {
+ converterOut.close();
+ }
+ } finally {
+ bufferedOut.close();
+ }
+ } finally {
+ out.close();
+ }
+
+ return tempUTF8File;
+}
+
+function parseINIStrings(file) {
+ let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+ getService(Ci.nsIINIParserFactory);
+ let parser = factory.createINIParser(file);
+ let obj = {};
+ let sections = parser.getSections();
+ while (sections.hasMore()) {
+ let section = sections.getNext();
+ obj[section] = {};
+
+ let keys = parser.getKeys(section);
+ while (keys.hasMore()) {
+ let key = keys.getNext();
+ obj[section][key] = parser.getString(section, key);
+ }
+ }
+ return obj;
+}
+
+function getHash(aStr) {
+ // return the two-digit hexadecimal code for a byte
+ let 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;
+ hasher.updateFromStream(stringStream, -1);
+
+ // convert the binary hash data to a hex string.
+ let binary = hasher.finish(false);
+ return Array.from(binary, (c, i) => toHexString(binary.charCodeAt(i))).join("").toLowerCase();
+}
+
+function Bookmarks(aProfileFolder) {
+ let file = aProfileFolder.clone();
+ file.append(kBookmarksFileName);
+
+ this._file = file;
+}
+Bookmarks.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get exists() {
+ return this._file.exists() && this._file.isReadable();
+ },
+
+ migrate(aCallback) {
+ return Task.spawn(function* () {
+ let idToGuid = new Map();
+ let folderGuid = PlacesUtils.bookmarks.toolbarGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ folderGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("360se", folderGuid);
+ }
+ idToGuid.set(0, folderGuid);
+
+ let connection = yield Sqlite.openConnection({
+ path: this._file.path
+ });
+
+ try {
+ let rows = yield connection.execute(
+ `WITH RECURSIVE
+ bookmark(id, parent_id, is_folder, title, url, pos) AS (
+ VALUES(0, -1, 1, '', '', 0)
+ UNION
+ SELECT f.id, f.parent_id, f.is_folder, f.title, f.url, f.pos
+ FROM tb_fav AS f
+ JOIN bookmark AS b ON f.parent_id = b.id
+ ORDER BY f.pos ASC
+ )
+ SELECT id, parent_id, is_folder, title, url FROM bookmark WHERE id`);
+
+ for (let row of rows) {
+ let id = parseInt(row.getResultByName("id"), 10);
+ let parent_id = parseInt(row.getResultByName("parent_id"), 10);
+ let is_folder = parseInt(row.getResultByName("is_folder"), 10);
+ let title = row.getResultByName("title");
+ let url = row.getResultByName("url");
+
+ let parentGuid = idToGuid.get(parent_id) || idToGuid.get("fallback");
+ if (!parentGuid) {
+ parentGuid = PlacesUtils.bookmarks.unfiledGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ parentGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("360se", parentGuid);
+ }
+ idToGuid.set("fallback", parentGuid);
+ }
+
+ try {
+ if (is_folder == 1) {
+ let newFolderGuid = (yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title
+ })).guid;
+
+ idToGuid.set(id, newFolderGuid);
+ } else {
+ yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid,
+ url,
+ title
+ });
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ } finally {
+ yield connection.close();
+ }
+ }.bind(this)).then(() => aCallback(true),
+ e => { Cu.reportError(e); aCallback(false) });
+ }
+};
+
+function Qihoo360seProfileMigrator() {
+ let paths = [
+ // for v6 and above
+ {
+ users: ["360se6", "apps", "data", "users"],
+ defaultUser: "default"
+ },
+ // for earlier versions
+ {
+ users: ["360se"],
+ defaultUser: "data"
+ }
+ ];
+ this._usersDir = null;
+ this._defaultUserPath = null;
+ for (let path of paths) {
+ let usersDir = FileUtils.getDir("AppData", path.users, false);
+ if (usersDir.exists()) {
+ this._usersDir = usersDir;
+ this._defaultUserPath = path.defaultUser;
+ break;
+ }
+ }
+}
+
+Qihoo360seProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+Object.defineProperty(Qihoo360seProfileMigrator.prototype, "sourceProfiles", {
+ get: function() {
+ if ("__sourceProfiles" in this)
+ return this.__sourceProfiles;
+
+ if (!this._usersDir) {
+ this.__sourceProfiles = [];
+ return this.__sourceProfiles;
+ }
+
+ let profiles = [];
+ let noLoggedInUser = true;
+ try {
+ let loginIni = this._usersDir.clone();
+ loginIni.append("login.ini");
+ if (!loginIni.exists()) {
+ throw new Error("360 Secure Browser's 'login.ini' does not exist.");
+ }
+ if (!loginIni.isReadable()) {
+ throw new Error("360 Secure Browser's 'login.ini' file could not be read.");
+ }
+
+ let loginIniInUtf8 = copyToTempUTF8File(loginIni, "gbk");
+ let loginIniObj = parseINIStrings(loginIniInUtf8);
+ try {
+ loginIniInUtf8.remove(false);
+ } catch (ex) {}
+
+ let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
+
+ /*
+ * NowLogin section may:
+ * 1. be missing or without email, before any user logs in.
+ * 2. represents the current logged in user
+ * 3. represents the most recent logged in user
+ *
+ * In the second case, user represented by NowLogin should be the first
+ * profile; otherwise the default user should be selected by default.
+ */
+ if (nowLoginEmail) {
+ if (loginIniObj.NowLogin.IsLogined === "1") {
+ noLoggedInUser = false;
+ }
+
+ profiles.push({
+ id: this._getIdFromConfig(loginIniObj.NowLogin),
+ name: nowLoginEmail,
+ });
+ }
+
+ for (let section in loginIniObj) {
+ if (!loginIniObj[section].email ||
+ (nowLoginEmail && loginIniObj[section].email == nowLoginEmail)) {
+ continue;
+ }
+
+ profiles.push({
+ id: this._getIdFromConfig(loginIniObj[section]),
+ name: loginIniObj[section].email,
+ });
+ }
+ } catch (e) {
+ Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
+ } finally {
+ profiles[noLoggedInUser ? "unshift" : "push"]({
+ id: this._defaultUserPath,
+ name: "Default",
+ });
+ }
+
+ this.__sourceProfiles = profiles.filter(profile => {
+ let resources = this.getResources(profile);
+ return resources && resources.length > 0;
+ });
+ return this.__sourceProfiles;
+ }
+});
+
+Qihoo360seProfileMigrator.prototype._getIdFromConfig = function(aConfig) {
+ return aConfig.UserMd5 || getHash(aConfig.email);
+};
+
+Qihoo360seProfileMigrator.prototype.getResources = function(aProfile) {
+ let profileFolder = this._usersDir.clone();
+ profileFolder.append(aProfile.id);
+
+ if (!profileFolder.exists()) {
+ return [];
+ }
+
+ let resources = [
+ new Bookmarks(profileFolder)
+ ];
+ return resources.filter(r => r.exists);
+};
+
+Qihoo360seProfileMigrator.prototype.getLastUsedDate = function() {
+ let bookmarksPaths = this.sourceProfiles.map(({id}) => {
+ return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
+ });
+ if (!bookmarksPaths.length) {
+ return Promise.resolve(new Date(0));
+ }
+ let datePromises = bookmarksPaths.map(path => {
+ return OS.File.stat(path).catch(() => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
+Qihoo360seProfileMigrator.prototype.classDescription = "360 Secure Browser Profile Migrator";
+Qihoo360seProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=360se";
+Qihoo360seProfileMigrator.prototype.classID = Components.ID("{d0037b95-296a-4a4e-94b2-c3d075d20ab1}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Qihoo360seProfileMigrator]);
diff --git a/browser/components/migration/AutoMigrate.jsm b/browser/components/migration/AutoMigrate.jsm
new file mode 100644
index 000000000..b38747825
--- /dev/null
+++ b/browser/components/migration/AutoMigrate.jsm
@@ -0,0 +1,670 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["AutoMigrate"];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+const kAutoMigrateEnabledPref = "browser.migrate.automigrate.enabled";
+const kUndoUIEnabledPref = "browser.migrate.automigrate.ui.enabled";
+
+const kAutoMigrateBrowserPref = "browser.migrate.automigrate.browser";
+const kAutoMigrateImportedItemIds = "browser.migrate.automigrate.imported-items";
+
+const kAutoMigrateLastUndoPromptDateMsPref = "browser.migrate.automigrate.lastUndoPromptDateMs";
+const kAutoMigrateDaysToOfferUndoPref = "browser.migrate.automigrate.daysToOfferUndo";
+
+const kAutoMigrateUndoSurveyPref = "browser.migrate.automigrate.undo-survey";
+const kAutoMigrateUndoSurveyLocalePref = "browser.migrate.automigrate.undo-survey-locales";
+
+const kNotificationId = "automigration-undo";
+
+Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.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, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
+ "resource://gre/modules/NewTabUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+ "resource://gre/modules/TelemetryStopwatch.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
+ const kBrandBundle = "chrome://branding/locale/brand.properties";
+ return Services.strings.createBundle(kBrandBundle);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gHardcodedStringBundle", function() {
+ const kBundleURI = "chrome://browser/content/migration/extra-migration-strings.properties";
+ return Services.strings.createBundle(kBundleURI);
+});
+
+Cu.importGlobalProperties(["URL"]);
+
+/* globals kUndoStateFullPath */
+XPCOMUtils.defineLazyGetter(this, "kUndoStateFullPath", function() {
+ return OS.Path.join(OS.Constants.Path.profileDir, "initialMigrationMetadata.jsonlz4");
+});
+
+const AutoMigrate = {
+ get resourceTypesToUse() {
+ let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ return BOOKMARKS | HISTORY | PASSWORDS;
+ },
+
+ _checkIfEnabled() {
+ let pref = Preferences.get(kAutoMigrateEnabledPref, false);
+ // User-set values should take precedence:
+ if (Services.prefs.prefHasUserValue(kAutoMigrateEnabledPref)) {
+ return pref;
+ }
+ // If we're using the default value, make sure the distribution.ini
+ // value is taken into account even early on startup.
+ try {
+ let distributionFile = Services.dirsvc.get("XREAppDist", Ci.nsIFile);
+ distributionFile.append("distribution.ini");
+ let parser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+ getService(Ci.nsIINIParserFactory).
+ createINIParser(distributionFile);
+ return JSON.parse(parser.getString("Preferences", kAutoMigrateEnabledPref));
+ } catch (ex) { /* ignore exceptions (file doesn't exist, invalid value, etc.) */ }
+
+ return pref;
+ },
+
+ init() {
+ this.enabled = this._checkIfEnabled();
+ },
+
+ /**
+ * Automatically pick a migrator and resources to migrate,
+ * then migrate those and start up.
+ *
+ * @throws if automatically deciding on migrators/data
+ * failed for some reason.
+ */
+ migrate(profileStartup, migratorKey, profileToMigrate) {
+ let histogram = Services.telemetry.getHistogramById(
+ "FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
+ histogram.add(0);
+ let {migrator, pickedKey} = this.pickMigrator(migratorKey);
+ histogram.add(5);
+
+ profileToMigrate = this.pickProfile(migrator, profileToMigrate);
+ histogram.add(10);
+
+ let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
+ if (!(resourceTypes & this.resourceTypesToUse)) {
+ throw new Error("No usable resources were found for the selected browser!");
+ }
+ histogram.add(15);
+
+ let sawErrors = false;
+ let migrationObserver = (subject, topic) => {
+ if (topic == "Migration:ItemError") {
+ sawErrors = true;
+ } else if (topic == "Migration:Ended") {
+ histogram.add(25);
+ if (sawErrors) {
+ histogram.add(26);
+ }
+ Services.obs.removeObserver(migrationObserver, "Migration:Ended");
+ Services.obs.removeObserver(migrationObserver, "Migration:ItemError");
+ Services.prefs.setCharPref(kAutoMigrateBrowserPref, pickedKey);
+ // Save the undo history and block shutdown on that save completing.
+ AsyncShutdown.profileBeforeChange.addBlocker(
+ "AutoMigrate Undo saving", this.saveUndoState(), () => {
+ return {state: this._saveUndoStateTrackerForShutdown};
+ });
+ }
+ };
+
+ MigrationUtils.initializeUndoData();
+ Services.obs.addObserver(migrationObserver, "Migration:Ended", false);
+ Services.obs.addObserver(migrationObserver, "Migration:ItemError", false);
+ migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
+ histogram.add(20);
+ },
+
+ /**
+ * Pick and return a migrator to use for automatically migrating.
+ *
+ * @param {String} migratorKey optional, a migrator key to prefer/pick.
+ * @returns {Object} an object with the migrator to use for migrating, as
+ * well as the key we eventually ended up using to obtain it.
+ */
+ pickMigrator(migratorKey) {
+ if (!migratorKey) {
+ let defaultKey = MigrationUtils.getMigratorKeyForDefaultBrowser();
+ if (!defaultKey) {
+ throw new Error("Could not determine default browser key to migrate from");
+ }
+ migratorKey = defaultKey;
+ }
+ if (migratorKey == "firefox") {
+ throw new Error("Can't automatically migrate from Firefox.");
+ }
+
+ let migrator = MigrationUtils.getMigrator(migratorKey);
+ if (!migrator) {
+ throw new Error("Migrator specified or a default was found, but the migrator object is not available (or has no data).");
+ }
+ return {migrator, pickedKey: migratorKey};
+ },
+
+ /**
+ * Pick a source profile (from the original browser) to use.
+ *
+ * @param {Migrator} migrator the migrator object to use
+ * @param {String} suggestedId the id of the profile to migrate, if pre-specified, or null
+ * @returns the profile to migrate, or null if migrating
+ * from the default profile.
+ */
+ pickProfile(migrator, suggestedId) {
+ let profiles = migrator.sourceProfiles;
+ if (profiles && !profiles.length) {
+ throw new Error("No profile data found to migrate.");
+ }
+ if (suggestedId) {
+ if (!profiles) {
+ throw new Error("Profile specified but only a default profile found.");
+ }
+ let suggestedProfile = profiles.find(profile => profile.id == suggestedId);
+ if (!suggestedProfile) {
+ throw new Error("Profile specified was not found.");
+ }
+ return suggestedProfile;
+ }
+ if (profiles && profiles.length > 1) {
+ throw new Error("Don't know how to pick a profile when more than 1 profile is present.");
+ }
+ return profiles ? profiles[0] : null;
+ },
+
+ _pendingUndoTasks: false,
+ canUndo: Task.async(function* () {
+ if (this._savingPromise) {
+ yield this._savingPromise;
+ }
+ if (this._pendingUndoTasks) {
+ return false;
+ }
+ let fileExists = false;
+ try {
+ fileExists = yield OS.File.exists(kUndoStateFullPath);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ return fileExists;
+ }),
+
+ undo: Task.async(function* () {
+ let browserId = Preferences.get(kAutoMigrateBrowserPref, "unknown");
+ TelemetryStopwatch.startKeyed("FX_STARTUP_MIGRATION_UNDO_TOTAL_MS", browserId);
+ let histogram = Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_UNDO");
+ histogram.add(0);
+ if (!(yield this.canUndo())) {
+ histogram.add(5);
+ throw new Error("Can't undo!");
+ }
+
+ this._pendingUndoTasks = true;
+ this._removeNotificationBars();
+ histogram.add(10);
+
+ let readPromise = OS.File.read(kUndoStateFullPath, {
+ encoding: "utf-8",
+ compression: "lz4",
+ });
+ let stateData = this._dejsonifyUndoState(yield readPromise);
+ histogram.add(12);
+
+ this._errorMap = {bookmarks: 0, visits: 0, logins: 0};
+ let reportErrorTelemetry = (type) => {
+ let histogramId = `FX_STARTUP_MIGRATION_UNDO_${type.toUpperCase()}_ERRORCOUNT`;
+ Services.telemetry.getKeyedHistogramById(histogramId).add(browserId, this._errorMap[type]);
+ };
+
+ let startTelemetryStopwatch = resourceType => {
+ let histogramId = `FX_STARTUP_MIGRATION_UNDO_${resourceType.toUpperCase()}_MS`;
+ TelemetryStopwatch.startKeyed(histogramId, browserId);
+ };
+ let stopTelemetryStopwatch = resourceType => {
+ let histogramId = `FX_STARTUP_MIGRATION_UNDO_${resourceType.toUpperCase()}_MS`;
+ TelemetryStopwatch.finishKeyed(histogramId, browserId);
+ };
+ startTelemetryStopwatch("bookmarks");
+ yield this._removeUnchangedBookmarks(stateData.get("bookmarks")).catch(ex => {
+ Cu.reportError("Uncaught exception when removing unchanged bookmarks!");
+ Cu.reportError(ex);
+ });
+ stopTelemetryStopwatch("bookmarks");
+ reportErrorTelemetry("bookmarks");
+ histogram.add(15);
+
+ startTelemetryStopwatch("visits");
+ yield this._removeSomeVisits(stateData.get("visits")).catch(ex => {
+ Cu.reportError("Uncaught exception when removing history visits!");
+ Cu.reportError(ex);
+ });
+ stopTelemetryStopwatch("visits");
+ reportErrorTelemetry("visits");
+ histogram.add(20);
+
+ startTelemetryStopwatch("logins");
+ yield this._removeUnchangedLogins(stateData.get("logins")).catch(ex => {
+ Cu.reportError("Uncaught exception when removing unchanged logins!");
+ Cu.reportError(ex);
+ });
+ stopTelemetryStopwatch("logins");
+ reportErrorTelemetry("logins");
+ histogram.add(25);
+
+ // This is async, but no need to wait for it.
+ NewTabUtils.links.populateCache(() => {
+ NewTabUtils.allPages.update();
+ }, true);
+
+ this._purgeUndoState(this.UNDO_REMOVED_REASON_UNDO_USED);
+ histogram.add(30);
+ TelemetryStopwatch.finishKeyed("FX_STARTUP_MIGRATION_UNDO_TOTAL_MS", browserId);
+ }),
+
+ _removeNotificationBars() {
+ let browserWindows = Services.wm.getEnumerator("navigator:browser");
+ while (browserWindows.hasMoreElements()) {
+ let win = browserWindows.getNext();
+ if (!win.closed) {
+ for (let browser of win.gBrowser.browsers) {
+ let nb = win.gBrowser.getNotificationBox(browser);
+ let notification = nb.getNotificationWithValue(kNotificationId);
+ if (notification) {
+ nb.removeNotification(notification);
+ }
+ }
+ }
+ }
+ },
+
+ _purgeUndoState(reason) {
+ // We don't wait for the off-main-thread removal to complete. OS.File will
+ // ensure it happens before shutdown.
+ OS.File.remove(kUndoStateFullPath, {ignoreAbsent: true}).then(() => {
+ this._pendingUndoTasks = false;
+ });
+
+ let migrationBrowser = Preferences.get(kAutoMigrateBrowserPref, "unknown");
+ Services.prefs.clearUserPref(kAutoMigrateBrowserPref);
+
+ let histogram =
+ Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_UNDO_REASON");
+ histogram.add(migrationBrowser, reason);
+ },
+
+ getBrowserUsedForMigration() {
+ let browserId = Services.prefs.getCharPref(kAutoMigrateBrowserPref);
+ if (browserId) {
+ return MigrationUtils.getBrowserName(browserId);
+ }
+ return null;
+ },
+
+ /**
+ * Show the user a notification bar indicating we automatically imported
+ * their data and offering them the possibility of removing it.
+ * @param target (xul:browser)
+ * The browser in which we should show the notification.
+ */
+ maybeShowUndoNotification: Task.async(function* (target) {
+ if (!(yield this.canUndo())) {
+ return;
+ }
+
+ // The tab might have navigated since we requested the undo state:
+ let canUndoFromThisPage = ["about:home", "about:newtab"].includes(target.currentURI.spec);
+ if (!canUndoFromThisPage ||
+ !Preferences.get(kUndoUIEnabledPref, false)) {
+ return;
+ }
+
+ let win = target.ownerGlobal;
+ let notificationBox = win.gBrowser.getNotificationBox(target);
+ if (!notificationBox || notificationBox.getNotificationWithValue(kNotificationId)) {
+ return;
+ }
+
+ // At this stage we're committed to show the prompt - unless we shouldn't,
+ // in which case we remove the undo prefs (which will cause canUndo() to
+ // return false from now on.):
+ if (!this.shouldStillShowUndoPrompt()) {
+ this._purgeUndoState(this.UNDO_REMOVED_REASON_OFFER_EXPIRED);
+ this._removeNotificationBars();
+ return;
+ }
+
+ let browserName = this.getBrowserUsedForMigration();
+ if (!browserName) {
+ browserName = gHardcodedStringBundle.GetStringFromName("automigration.undo.unknownbrowser");
+ }
+ const kMessageId = "automigration.undo.message." +
+ Preferences.get(kAutoMigrateImportedItemIds, "all");
+ const kBrandShortName = gBrandBundle.GetStringFromName("brandShortName");
+ let message = gHardcodedStringBundle.formatStringFromName(kMessageId,
+ [browserName, kBrandShortName], 2);
+
+ let buttons = [
+ {
+ label: gHardcodedStringBundle.GetStringFromName("automigration.undo.keep2.label"),
+ accessKey: gHardcodedStringBundle.GetStringFromName("automigration.undo.keep2.accesskey"),
+ callback: () => {
+ this._purgeUndoState(this.UNDO_REMOVED_REASON_OFFER_REJECTED);
+ this._removeNotificationBars();
+ },
+ },
+ {
+ label: gHardcodedStringBundle.GetStringFromName("automigration.undo.dontkeep2.label"),
+ accessKey: gHardcodedStringBundle.GetStringFromName("automigration.undo.dontkeep2.accesskey"),
+ callback: () => {
+ this._maybeOpenUndoSurveyTab(win);
+ this.undo();
+ },
+ },
+ ];
+ notificationBox.appendNotification(
+ message, kNotificationId, null, notificationBox.PRIORITY_INFO_HIGH, buttons
+ );
+ let remainingDays = Preferences.get(kAutoMigrateDaysToOfferUndoPref, 0);
+ Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_UNDO_OFFERED").add(4 - remainingDays);
+ }),
+
+ shouldStillShowUndoPrompt() {
+ let today = new Date();
+ // Round down to midnight:
+ today = new Date(today.getFullYear(), today.getMonth(), today.getDate());
+ // We store the unix timestamp corresponding to midnight on the last day
+ // on which we prompted. Fetch that and compare it to today's date.
+ // (NB: stored as a string because int prefs are too small for unix
+ // timestamps.)
+ let previousPromptDateMsStr = Preferences.get(kAutoMigrateLastUndoPromptDateMsPref, "0");
+ let previousPromptDate = new Date(parseInt(previousPromptDateMsStr, 10));
+ if (previousPromptDate < today) {
+ let remainingDays = Preferences.get(kAutoMigrateDaysToOfferUndoPref, 4) - 1;
+ Preferences.set(kAutoMigrateDaysToOfferUndoPref, remainingDays);
+ Preferences.set(kAutoMigrateLastUndoPromptDateMsPref, today.valueOf().toString());
+ if (remainingDays <= 0) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ UNDO_REMOVED_REASON_UNDO_USED: 0,
+ UNDO_REMOVED_REASON_SYNC_SIGNIN: 1,
+ UNDO_REMOVED_REASON_PASSWORD_CHANGE: 2,
+ UNDO_REMOVED_REASON_BOOKMARK_CHANGE: 3,
+ UNDO_REMOVED_REASON_OFFER_EXPIRED: 4,
+ UNDO_REMOVED_REASON_OFFER_REJECTED: 5,
+
+ _jsonifyUndoState(state) {
+ if (!state) {
+ return "null";
+ }
+ // Deal with date serialization.
+ let bookmarks = state.get("bookmarks");
+ for (let bm of bookmarks) {
+ bm.lastModified = bm.lastModified.getTime();
+ }
+ let serializableState = {
+ bookmarks,
+ logins: state.get("logins"),
+ visits: state.get("visits"),
+ };
+ return JSON.stringify(serializableState);
+ },
+
+ _dejsonifyUndoState(state) {
+ state = JSON.parse(state);
+ if (!state) {
+ return new Map();
+ }
+ for (let bm of state.bookmarks) {
+ bm.lastModified = new Date(bm.lastModified);
+ }
+ return new Map([
+ ["bookmarks", state.bookmarks],
+ ["logins", state.logins],
+ ["visits", state.visits],
+ ]);
+ },
+
+ /**
+ * Store the items we've saved into a pref. We use this to be able to show
+ * a detailed message to the user indicating what we've imported.
+ * @param state (Map)
+ * The 'undo' state for the import, which contains info about
+ * how many items of each kind we've (tried to) import.
+ */
+ _setImportedItemPrefFromState(state) {
+ let itemsWithData = [];
+ if (state) {
+ for (let itemType of state.keys()) {
+ if (state.get(itemType).length) {
+ itemsWithData.push(itemType);
+ }
+ }
+ }
+ if (itemsWithData.length == 3) {
+ itemsWithData = "all";
+ } else {
+ itemsWithData = itemsWithData.sort().join(".");
+ }
+ if (itemsWithData) {
+ Preferences.set(kAutoMigrateImportedItemIds, itemsWithData);
+ }
+ },
+
+ /**
+ * Used for the shutdown blocker's information field.
+ */
+ _saveUndoStateTrackerForShutdown: "not running",
+ /**
+ * Store the information required for using 'undo' of the automatic
+ * migration in the user's profile.
+ */
+ saveUndoState: Task.async(function* () {
+ let resolveSavingPromise;
+ this._saveUndoStateTrackerForShutdown = "processing undo history";
+ this._savingPromise = new Promise(resolve => { resolveSavingPromise = resolve });
+ let state = yield MigrationUtils.stopAndRetrieveUndoData();
+
+ if (!state || ![...state.values()].some(ary => ary.length > 0)) {
+ // If we didn't import anything, abort now.
+ resolveSavingPromise();
+ return Promise.resolve();
+ }
+
+ this._saveUndoStateTrackerForShutdown = "saving imported item list";
+ this._setImportedItemPrefFromState(state);
+
+ this._saveUndoStateTrackerForShutdown = "writing undo history";
+ this._undoSavePromise = OS.File.writeAtomic(
+ kUndoStateFullPath, this._jsonifyUndoState(state), {
+ encoding: "utf-8",
+ compression: "lz4",
+ tmpPath: kUndoStateFullPath + ".tmp",
+ });
+ this._undoSavePromise.then(
+ rv => {
+ resolveSavingPromise(rv);
+ delete this._savingPromise;
+ },
+ e => {
+ Cu.reportError("Could not write undo state for automatic migration.");
+ throw e;
+ });
+ return this._undoSavePromise;
+ }),
+
+ _removeUnchangedBookmarks: Task.async(function* (bookmarks) {
+ if (!bookmarks.length) {
+ return;
+ }
+
+ let guidToLMMap = new Map(bookmarks.map(b => [b.guid, b.lastModified]));
+ let bookmarksFromDB = [];
+ let bmPromises = Array.from(guidToLMMap.keys()).map(guid => {
+ // Ignore bookmarks where the promise doesn't resolve (ie that are missing)
+ // Also check that the bookmark fetch returns isn't null before adding it.
+ try {
+ return PlacesUtils.bookmarks.fetch(guid).then(bm => bm && bookmarksFromDB.push(bm), () => {});
+ } catch (ex) {
+ // Ignore immediate exceptions, too.
+ }
+ return Promise.resolve();
+ });
+ // We can't use the result of Promise.all because that would include nulls
+ // for bookmarks that no longer exist (which we're catching above).
+ yield Promise.all(bmPromises);
+ let unchangedBookmarks = bookmarksFromDB.filter(bm => {
+ return bm.lastModified.getTime() == guidToLMMap.get(bm.guid).getTime();
+ });
+
+ // We need to remove items without children first, followed by their
+ // parents, etc. In order to do this, find out how many ancestors each item
+ // has that also appear in our list of things to remove, and sort the items
+ // by those numbers. This ensures that children are always removed before
+ // their parents.
+ function determineAncestorCount(bm) {
+ if (bm._ancestorCount) {
+ return bm._ancestorCount;
+ }
+ let myCount = 0;
+ let parentBM = unchangedBookmarks.find(item => item.guid == bm.parentGuid);
+ if (parentBM) {
+ myCount = determineAncestorCount(parentBM) + 1;
+ }
+ bm._ancestorCount = myCount;
+ return myCount;
+ }
+ unchangedBookmarks.forEach(determineAncestorCount);
+ unchangedBookmarks.sort((a, b) => b._ancestorCount - a._ancestorCount);
+ for (let {guid} of unchangedBookmarks) {
+ // Can't just use a .catch() because Bookmarks.remove() can throw (rather
+ // than returning rejected promises).
+ try {
+ yield PlacesUtils.bookmarks.remove(guid, {preventRemovalOfNonEmptyFolders: true});
+ } catch (err) {
+ if (err && err.message != "Cannot remove a non-empty folder.") {
+ this._errorMap.bookmarks++;
+ Cu.reportError(err);
+ }
+ }
+ }
+ }),
+
+ _removeUnchangedLogins: Task.async(function* (logins) {
+ for (let login of logins) {
+ let foundLogins = LoginHelper.searchLoginsWithObject({guid: login.guid});
+ if (foundLogins.length) {
+ let foundLogin = foundLogins[0];
+ foundLogin.QueryInterface(Ci.nsILoginMetaInfo);
+ if (foundLogin.timePasswordChanged == login.timePasswordChanged) {
+ try {
+ Services.logins.removeLogin(foundLogin);
+ } catch (ex) {
+ Cu.reportError("Failed to remove a login for " + foundLogins.hostname);
+ Cu.reportError(ex);
+ this._errorMap.logins++;
+ }
+ }
+ }
+ }
+ }),
+
+ _removeSomeVisits: Task.async(function* (visits) {
+ for (let urlVisits of visits) {
+ let urlObj;
+ try {
+ urlObj = new URL(urlVisits.url);
+ } catch (ex) {
+ continue;
+ }
+ let visitData = {
+ url: urlObj,
+ beginDate: PlacesUtils.toDate(urlVisits.first),
+ endDate: PlacesUtils.toDate(urlVisits.last),
+ limit: urlVisits.visitCount,
+ };
+ try {
+ yield PlacesUtils.history.removeVisitsByFilter(visitData);
+ } catch (ex) {
+ this._errorMap.visits++;
+ try {
+ visitData.url = visitData.url.href;
+ } catch (ignoredEx) {}
+ Cu.reportError("Failed to remove a visit: " + JSON.stringify(visitData));
+ Cu.reportError(ex);
+ }
+ }
+ }),
+
+ /**
+ * Maybe open a new tab with a survey. The tab will only be opened if all of
+ * the following are true:
+ * - the 'browser.migrate.automigrate.undo-survey' pref is not empty.
+ * It should contain the URL of the survey to open.
+ * - the 'browser.migrate.automigrate.undo-survey-locales' pref, a
+ * comma-separated list of language codes, contains the language code
+ * that is currently in use for the 'global' chrome pacakge (ie the
+ * locale in which the user is currently using Firefox).
+ * The URL will be passed through nsIURLFormatter to allow including
+ * build ids etc. The special additional formatting variable
+ * "%IMPORTEDBROWSER" is also replaced with the name of the browser
+ * from which we imported data.
+ *
+ * @param {Window} chromeWindow A reference to the window in which to open a link.
+ */
+ _maybeOpenUndoSurveyTab(chromeWindow) {
+ let canDoSurveyInLocale = false;
+ try {
+ let surveyLocales = Preferences.get(kAutoMigrateUndoSurveyLocalePref, "");
+ surveyLocales = surveyLocales.split(",").map(str => str.trim());
+ // Strip out any empty elements, so an empty pref doesn't
+ // lead to a an array with 1 empty string in it.
+ surveyLocales = new Set(surveyLocales.filter(str => !!str));
+ let chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry);
+ canDoSurveyInLocale =
+ surveyLocales.has(chromeRegistry.getSelectedLocale("global"));
+ } catch (ex) {
+ /* ignore exceptions and just don't do the survey. */
+ }
+
+ let migrationBrowser = this.getBrowserUsedForMigration();
+ let rawURL = Preferences.get(kAutoMigrateUndoSurveyPref, "");
+ if (!canDoSurveyInLocale || !migrationBrowser || !rawURL) {
+ return;
+ }
+
+ let url = Services.urlFormatter.formatURL(rawURL);
+ url = url.replace("%IMPORTEDBROWSER%", encodeURIComponent(migrationBrowser));
+ chromeWindow.openUILinkIn(url, "tab");
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIObserver, Ci.nsINavBookmarkObserver, Ci.nsISupportsWeakReference]
+ ),
+};
+
+AutoMigrate.init();
diff --git a/browser/components/migration/BrowserProfileMigrators.manifest b/browser/components/migration/BrowserProfileMigrators.manifest
new file mode 100644
index 000000000..e16fba13a
--- /dev/null
+++ b/browser/components/migration/BrowserProfileMigrators.manifest
@@ -0,0 +1,33 @@
+component {6F8BB968-C14F-4D6F-9733-6C6737B35DCE} ProfileMigrator.js
+contract @mozilla.org/toolkit/profile-migrator;1 {6F8BB968-C14F-4D6F-9733-6C6737B35DCE}
+
+#if defined(XP_WIN) || defined(XP_MACOSX)
+component {4bf85aa5-4e21-46ca-825f-f9c51a5e8c76} ChromeProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=canary {4bf85aa5-4e21-46ca-825f-f9c51a5e8c76}
+#endif
+component {4cec1de4-1671-4fc3-a53e-6c539dc77a26} ChromeProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=chrome {4cec1de4-1671-4fc3-a53e-6c539dc77a26}
+component {8cece922-9720-42de-b7db-7cef88cb07ca} ChromeProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=chromium {8cece922-9720-42de-b7db-7cef88cb07ca}
+
+component {91185366-ba97-4438-acba-48deaca63386} FirefoxProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=firefox {91185366-ba97-4438-acba-48deaca63386}
+
+#ifdef HAS_IE_MIGRATOR
+component {3d2532e3-4932-4774-b7ba-968f5899d3a4} IEProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=ie {3d2532e3-4932-4774-b7ba-968f5899d3a4}
+#endif
+
+#ifdef HAS_EDGE_MIGRATOR
+component {62e8834b-2d17-49f5-96ff-56344903a2ae} EdgeProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=edge {62e8834b-2d17-49f5-96ff-56344903a2ae}
+#endif
+
+#ifdef HAS_SAFARI_MIGRATOR
+component {4b609ecf-60b2-4655-9df4-dc149e474da1} SafariProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=safari {4b609ecf-60b2-4655-9df4-dc149e474da1}
+#endif
+#ifdef HAS_360SE_MIGRATOR
+component {d0037b95-296a-4a4e-94b2-c3d075d20ab1} 360seProfileMigrator.js
+contract @mozilla.org/profile/migrator;1?app=browser&type=360se {d0037b95-296a-4a4e-94b2-c3d075d20ab1}
+#endif
diff --git a/browser/components/migration/ChromeProfileMigrator.js b/browser/components/migration/ChromeProfileMigrator.js
new file mode 100644
index 000000000..ec0c8d444
--- /dev/null
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -0,0 +1,557 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
+
+const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
+const S100NS_PER_MS = 10;
+
+const AUTH_TYPE = {
+ SCHEME_HTML: 0,
+ SCHEME_BASIC: 1,
+ SCHEME_DIGEST: 2
+};
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+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/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
+ "resource://gre/modules/OSCrypto.jsm");
+/**
+ * Get an nsIFile instance representing the expected location of user data
+ * for this copy of Chrome/Chromium/Canary on different OSes.
+ * @param subfoldersWin {Array} an array of subfolders to use for Windows
+ * @param subfoldersOSX {Array} an array of subfolders to use for OS X
+ * @param subfoldersUnix {Array} an array of subfolders to use for *nix systems
+ * @returns {nsIFile} the place we expect data to live. Might not actually exist!
+ */
+function getDataFolder(subfoldersWin, subfoldersOSX, subfoldersUnix) {
+ let dirServiceID, subfolders;
+ if (AppConstants.platform == "win") {
+ dirServiceID = "LocalAppData";
+ subfolders = subfoldersWin.concat(["User Data"]);
+ } else if (AppConstants.platform == "macosx") {
+ dirServiceID = "ULibDir";
+ subfolders = ["Application Support"].concat(subfoldersOSX);
+ } else {
+ dirServiceID = "Home";
+ subfolders = [".config"].concat(subfoldersUnix);
+ }
+ return FileUtils.getDir(dirServiceID, subfolders, false);
+}
+
+/**
+ * Convert Chrome time format to Date object
+ *
+ * @param aTime
+ * Chrome time
+ * @return converted Date object
+ * @note Google Chrome uses FILETIME / 10 as time.
+ * FILETIME is based on same structure of Windows.
+ */
+function chromeTimeToDate(aTime)
+{
+ return new Date((aTime * S100NS_PER_MS - S100NS_FROM1601TO1970) / 10000);
+}
+
+/**
+ * Convert Date object to Chrome time format
+ *
+ * @param aDate
+ * Date object or integer equivalent
+ * @return Chrome time
+ * @note For details on Chrome time, see chromeTimeToDate.
+ */
+function dateToChromeTime(aDate) {
+ return (aDate * 10000 + S100NS_FROM1601TO1970) / S100NS_PER_MS;
+}
+
+/**
+ * Insert bookmark items into specific folder.
+ *
+ * @param parentGuid
+ * GUID of the folder where items will be inserted
+ * @param items
+ * bookmark items to be inserted
+ * @param errorAccumulator
+ * function that gets called with any errors thrown so we don't drop them on the floor.
+ */
+function* insertBookmarkItems(parentGuid, items, errorAccumulator) {
+ for (let item of items) {
+ try {
+ if (item.type == "url") {
+ if (item.url.trim().startsWith("chrome:")) {
+ // Skip invalid chrome URIs. Creating an actual URI always reports
+ // messages to the console, so we avoid doing that.
+ continue;
+ }
+ yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid, url: item.url, title: item.name
+ });
+ } else if (item.type == "folder") {
+ let newFolderGuid = (yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, title: item.name
+ })).guid;
+
+ yield insertBookmarkItems(newFolderGuid, item.children, errorAccumulator);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ errorAccumulator(e);
+ }
+ }
+}
+
+function ChromeProfileMigrator() {
+ let chromeUserDataFolder =
+ getDataFolder(["Google", "Chrome"], ["Google", "Chrome"], ["google-chrome"]);
+ this._chromeUserDataFolder = chromeUserDataFolder.exists() ?
+ chromeUserDataFolder : null;
+}
+
+ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+ChromeProfileMigrator.prototype.getResources =
+ function Chrome_getResources(aProfile) {
+ if (this._chromeUserDataFolder) {
+ let profileFolder = this._chromeUserDataFolder.clone();
+ profileFolder.append(aProfile.id);
+ if (profileFolder.exists()) {
+ let possibleResources = [
+ GetBookmarksResource(profileFolder),
+ GetHistoryResource(profileFolder),
+ GetCookiesResource(profileFolder),
+ ];
+ if (AppConstants.platform == "win") {
+ possibleResources.push(GetWindowsPasswordsResource(profileFolder));
+ }
+ return possibleResources.filter(r => r != null);
+ }
+ }
+ return [];
+ };
+
+ChromeProfileMigrator.prototype.getLastUsedDate =
+ function Chrome_getLastUsedDate() {
+ let datePromises = this.sourceProfiles.map(profile => {
+ let basePath = OS.Path.join(this._chromeUserDataFolder.path, profile.id);
+ let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(leafName => {
+ let path = OS.Path.join(basePath, leafName);
+ return OS.File.stat(path).catch(() => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ return Promise.all(fileDatePromises).then(dates => {
+ return Math.max.apply(Math, dates);
+ });
+ });
+ return Promise.all(datePromises).then(dates => {
+ dates.push(0);
+ return new Date(Math.max.apply(Math, dates));
+ });
+ };
+
+Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
+ get: function Chrome_sourceProfiles() {
+ if ("__sourceProfiles" in this)
+ return this.__sourceProfiles;
+
+ if (!this._chromeUserDataFolder)
+ return [];
+
+ let profiles = [];
+ try {
+ // Local State is a JSON file that contains profile info.
+ let localState = this._chromeUserDataFolder.clone();
+ localState.append("Local State");
+ if (!localState.exists())
+ throw new Error("Chrome's 'Local State' file does not exist.");
+ if (!localState.isReadable())
+ throw new Error("Chrome's 'Local State' file could not be read.");
+
+ let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
+ fstream.init(localState, -1, 0, 0);
+ let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
+ { charset: "UTF-8" });
+ let info_cache = JSON.parse(inputStream).profile.info_cache;
+ for (let profileFolderName in info_cache) {
+ let profileFolder = this._chromeUserDataFolder.clone();
+ profileFolder.append(profileFolderName);
+ profiles.push({
+ id: profileFolderName,
+ name: info_cache[profileFolderName].name || profileFolderName,
+ });
+ }
+ } catch (e) {
+ Cu.reportError("Error detecting Chrome profiles: " + e);
+ // If we weren't able to detect any profiles above, fallback to the Default profile.
+ let defaultProfileFolder = this._chromeUserDataFolder.clone();
+ defaultProfileFolder.append("Default");
+ if (defaultProfileFolder.exists()) {
+ profiles = [{
+ id: "Default",
+ name: "Default",
+ }];
+ }
+ }
+
+ // Only list profiles from which any data can be imported
+ this.__sourceProfiles = profiles.filter(function(profile) {
+ let resources = this.getResources(profile);
+ return resources && resources.length > 0;
+ }, this);
+ return this.__sourceProfiles;
+ }
+});
+
+Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
+ get: function Chrome_sourceHomePageURL() {
+ let prefsFile = this._chromeUserDataFolder.clone();
+ prefsFile.append("Preferences");
+ if (prefsFile.exists()) {
+ // XXX reading and parsing JSON is synchronous.
+ let fstream = Cc[FILE_INPUT_STREAM_CID].
+ createInstance(Ci.nsIFileInputStream);
+ fstream.init(prefsFile, -1, 0, 0);
+ try {
+ return JSON.parse(
+ NetUtil.readInputStreamToString(fstream, fstream.available(),
+ { charset: "UTF-8" })
+ ).homepage;
+ }
+ catch (e) {
+ Cu.reportError("Error parsing Chrome's preferences file: " + e);
+ }
+ }
+ return "";
+ }
+});
+
+Object.defineProperty(ChromeProfileMigrator.prototype, "sourceLocked", {
+ get: function Chrome_sourceLocked() {
+ // There is an exclusive lock on some SQLite databases. Assume they are locked for now.
+ return true;
+ },
+});
+
+function GetBookmarksResource(aProfileFolder) {
+ let bookmarksFile = aProfileFolder.clone();
+ bookmarksFile.append("Bookmarks");
+ if (!bookmarksFile.exists())
+ return null;
+
+ return {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ migrate: function(aCallback) {
+ return Task.spawn(function* () {
+ let gotErrors = false;
+ let errorGatherer = function() { gotErrors = true };
+ let jsonStream = yield new Promise((resolve, reject) => {
+ let options = {
+ uri: NetUtil.newURI(bookmarksFile),
+ loadUsingSystemPrincipal: true
+ };
+ NetUtil.asyncFetch(options, (inputStream, resultCode) => {
+ if (Components.isSuccessCode(resultCode)) {
+ resolve(inputStream);
+ } else {
+ reject(new Error("Could not read Bookmarks file"));
+ }
+ });
+ });
+
+ // Parse Chrome bookmark file that is JSON format
+ let bookmarkJSON = NetUtil.readInputStreamToString(
+ jsonStream, jsonStream.available(), { charset : "UTF-8" });
+ let roots = JSON.parse(bookmarkJSON).roots;
+
+ // Importing bookmark bar items
+ if (roots.bookmark_bar.children &&
+ roots.bookmark_bar.children.length > 0) {
+ // Toolbar
+ let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ parentGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid);
+ }
+ yield insertBookmarkItems(parentGuid, roots.bookmark_bar.children, errorGatherer);
+ }
+
+ // Importing bookmark menu items
+ if (roots.other.children &&
+ roots.other.children.length > 0) {
+ // Bookmark menu
+ let parentGuid = PlacesUtils.bookmarks.menuGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ parentGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid);
+ }
+ yield insertBookmarkItems(parentGuid, roots.other.children, errorGatherer);
+ }
+ if (gotErrors) {
+ throw new Error("The migration included errors.");
+ }
+ }.bind(this)).then(() => aCallback(true),
+ () => aCallback(false));
+ }
+ };
+}
+
+function GetHistoryResource(aProfileFolder) {
+ let historyFile = aProfileFolder.clone();
+ historyFile.append("History");
+ if (!historyFile.exists())
+ return null;
+
+ return {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ migrate(aCallback) {
+ Task.spawn(function* () {
+ const MAX_AGE_IN_DAYS = Services.prefs.getIntPref("browser.migrate.chrome.history.maxAgeInDays");
+ const LIMIT = Services.prefs.getIntPref("browser.migrate.chrome.history.limit");
+
+ let query = "SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0";
+ if (MAX_AGE_IN_DAYS) {
+ let maxAge = dateToChromeTime(Date.now() - MAX_AGE_IN_DAYS * 24 * 60 * 60 * 1000);
+ query += " AND last_visit_time > " + maxAge;
+ }
+ if (LIMIT) {
+ query += " ORDER BY last_visit_time DESC LIMIT " + LIMIT;
+ }
+
+ let rows =
+ yield MigrationUtils.getRowsFromDBWithoutLocks(historyFile.path, "Chrome history", query);
+ let places = [];
+ for (let row of rows) {
+ try {
+ // if having typed_count, we changes transition type to typed.
+ let transType = PlacesUtils.history.TRANSITION_LINK;
+ if (row.getResultByName("typed_count") > 0)
+ transType = PlacesUtils.history.TRANSITION_TYPED;
+
+ places.push({
+ uri: NetUtil.newURI(row.getResultByName("url")),
+ title: row.getResultByName("title"),
+ visits: [{
+ transitionType: transType,
+ visitDate: chromeTimeToDate(
+ row.getResultByName(
+ "last_visit_time")) * 1000,
+ }],
+ });
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+
+ if (places.length > 0) {
+ yield new Promise((resolve, reject) => {
+ MigrationUtils.insertVisitsWrapper(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ if (this._success) {
+ resolve();
+ } else {
+ reject(new Error("Couldn't add visits"));
+ }
+ }
+ });
+ });
+ }
+ }).then(() => { aCallback(true) },
+ ex => {
+ Cu.reportError(ex);
+ aCallback(false);
+ });
+ }
+ };
+}
+
+function GetCookiesResource(aProfileFolder) {
+ let cookiesFile = aProfileFolder.clone();
+ cookiesFile.append("Cookies");
+ if (!cookiesFile.exists())
+ return null;
+
+ return {
+ type: MigrationUtils.resourceTypes.COOKIES,
+
+ migrate: Task.async(function* (aCallback) {
+ // We don't support decrypting cookies yet so only import plaintext ones.
+ let rows = yield MigrationUtils.getRowsFromDBWithoutLocks(cookiesFile.path, "Chrome cookies",
+ `SELECT host_key, name, value, path, expires_utc, secure, httponly, encrypted_value
+ FROM cookies
+ WHERE length(encrypted_value) = 0`).catch(ex => {
+ Cu.reportError(ex);
+ aCallback(false);
+ });
+ // If the promise was rejected we will have already called aCallback,
+ // so we can just return here.
+ if (!rows) {
+ return;
+ }
+
+ for (let row of rows) {
+ let host_key = row.getResultByName("host_key");
+ if (host_key.match(/^\./)) {
+ // 1st character of host_key may be ".", so we have to remove it
+ host_key = host_key.substr(1);
+ }
+
+ try {
+ let expiresUtc =
+ chromeTimeToDate(row.getResultByName("expires_utc")) / 1000;
+ Services.cookies.add(host_key,
+ row.getResultByName("path"),
+ row.getResultByName("name"),
+ row.getResultByName("value"),
+ row.getResultByName("secure"),
+ row.getResultByName("httponly"),
+ false,
+ parseInt(expiresUtc),
+ {});
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ aCallback(true);
+ }),
+ };
+}
+
+function GetWindowsPasswordsResource(aProfileFolder) {
+ let loginFile = aProfileFolder.clone();
+ loginFile.append("Login Data");
+ if (!loginFile.exists())
+ return null;
+
+ return {
+ type: MigrationUtils.resourceTypes.PASSWORDS,
+
+ migrate: Task.async(function* (aCallback) {
+ let rows = yield MigrationUtils.getRowsFromDBWithoutLocks(loginFile.path, "Chrome passwords",
+ `SELECT origin_url, action_url, username_element, username_value,
+ password_element, password_value, signon_realm, scheme, date_created,
+ times_used FROM logins WHERE blacklisted_by_user = 0`).catch(ex => {
+ Cu.reportError(ex);
+ aCallback(false);
+ });
+ // If the promise was rejected we will have already called aCallback,
+ // so we can just return here.
+ if (!rows) {
+ return;
+ }
+ let crypto = new OSCrypto();
+
+ for (let row of rows) {
+ try {
+ let origin_url = NetUtil.newURI(row.getResultByName("origin_url"));
+ // Ignore entries for non-http(s)/ftp URLs because we likely can't
+ // use them anyway.
+ const kValidSchemes = new Set(["https", "http", "ftp"]);
+ if (!kValidSchemes.has(origin_url.scheme)) {
+ continue;
+ }
+ let loginInfo = {
+ username: row.getResultByName("username_value"),
+ password: crypto.
+ decryptData(crypto.arrayToString(row.getResultByName("password_value")),
+ null),
+ hostname: origin_url.prePath,
+ formSubmitURL: null,
+ httpRealm: null,
+ usernameElement: row.getResultByName("username_element"),
+ passwordElement: row.getResultByName("password_element"),
+ timeCreated: chromeTimeToDate(row.getResultByName("date_created") + 0).getTime(),
+ timesUsed: row.getResultByName("times_used") + 0,
+ };
+
+ switch (row.getResultByName("scheme")) {
+ case AUTH_TYPE.SCHEME_HTML:
+ let action_url = NetUtil.newURI(row.getResultByName("action_url"));
+ if (!kValidSchemes.has(action_url.scheme)) {
+ continue; // This continues the outer for loop.
+ }
+ loginInfo.formSubmitURL = action_url.prePath;
+ break;
+ case AUTH_TYPE.SCHEME_BASIC:
+ case AUTH_TYPE.SCHEME_DIGEST:
+ // signon_realm format is URIrealm, so we need remove URI
+ loginInfo.httpRealm = row.getResultByName("signon_realm")
+ .substring(loginInfo.hostname.length + 1);
+ break;
+ default:
+ throw new Error("Login data scheme type not supported: " +
+ row.getResultByName("scheme"));
+ }
+ MigrationUtils.insertLoginWrapper(loginInfo);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ crypto.finalize();
+ aCallback(true);
+ }),
+ };
+}
+
+ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
+ChromeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
+ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
+
+
+/**
+ * Chromium migration
+ **/
+function ChromiumProfileMigrator() {
+ let chromiumUserDataFolder = getDataFolder(["Chromium"], ["Chromium"], ["chromium"]);
+ this._chromeUserDataFolder = chromiumUserDataFolder.exists() ? chromiumUserDataFolder : null;
+}
+
+ChromiumProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
+ChromiumProfileMigrator.prototype.classDescription = "Chromium Profile Migrator";
+ChromiumProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
+ChromiumProfileMigrator.prototype.classID = Components.ID("{8cece922-9720-42de-b7db-7cef88cb07ca}");
+
+var componentsArray = [ChromeProfileMigrator, ChromiumProfileMigrator];
+
+/**
+ * Chrome Canary
+ * Not available on Linux
+ **/
+function CanaryProfileMigrator() {
+ let chromeUserDataFolder = getDataFolder(["Google", "Chrome SxS"], ["Google", "Chrome Canary"]);
+ this._chromeUserDataFolder = chromeUserDataFolder.exists() ? chromeUserDataFolder : null;
+}
+CanaryProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
+CanaryProfileMigrator.prototype.classDescription = "Chrome Canary Profile Migrator";
+CanaryProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=canary";
+CanaryProfileMigrator.prototype.classID = Components.ID("{4bf85aa5-4e21-46ca-825f-f9c51a5e8c76}");
+
+if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
+ componentsArray.push(CanaryProfileMigrator);
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(componentsArray);
diff --git a/browser/components/migration/ESEDBReader.jsm b/browser/components/migration/ESEDBReader.jsm
new file mode 100644
index 000000000..0768c65aa
--- /dev/null
+++ b/browser/components/migration/ESEDBReader.jsm
@@ -0,0 +1,590 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["ESEDBReader"]; /* exported ESEDBReader */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
+ let consoleOptions = {
+ maxLogLevelPref: "browser.esedbreader.loglevel",
+ prefix: "ESEDBReader",
+ };
+ return new ConsoleAPI(consoleOptions);
+});
+
+// We have a globally unique identifier for ESE instances. A new one
+// is used for each different database opened.
+let gESEInstanceCounter = 0;
+
+// We limit the length of strings that we read from databases.
+const MAX_STR_LENGTH = 64 * 1024;
+
+// Kernel-related types:
+const KERNEL = {};
+KERNEL.FILETIME = new ctypes.StructType("FILETIME", [
+ {dwLowDateTime: ctypes.uint32_t},
+ {dwHighDateTime: ctypes.uint32_t}
+]);
+KERNEL.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
+ {wYear: ctypes.uint16_t},
+ {wMonth: ctypes.uint16_t},
+ {wDayOfWeek: ctypes.uint16_t},
+ {wDay: ctypes.uint16_t},
+ {wHour: ctypes.uint16_t},
+ {wMinute: ctypes.uint16_t},
+ {wSecond: ctypes.uint16_t},
+ {wMilliseconds: ctypes.uint16_t}
+]);
+
+// DB column types, cribbed from the ESE header
+var COLUMN_TYPES = {
+ JET_coltypBit: 1, /* True, False, or NULL */
+ JET_coltypUnsignedByte: 2, /* 1-byte integer, unsigned */
+ JET_coltypShort: 3, /* 2-byte integer, signed */
+ JET_coltypLong: 4, /* 4-byte integer, signed */
+ JET_coltypCurrency: 5, /* 8 byte integer, signed */
+ JET_coltypIEEESingle: 6, /* 4-byte IEEE single precision */
+ JET_coltypIEEEDouble: 7, /* 8-byte IEEE double precision */
+ JET_coltypDateTime: 8, /* Integral date, fractional time */
+ JET_coltypBinary: 9, /* Binary data, < 255 bytes */
+ JET_coltypText: 10, /* ANSI text, case insensitive, < 255 bytes */
+ JET_coltypLongBinary: 11, /* Binary data, long value */
+ JET_coltypLongText: 12, /* ANSI text, long value */
+
+ JET_coltypUnsignedLong: 14, /* 4-byte unsigned integer */
+ JET_coltypLongLong: 15, /* 8-byte signed integer */
+ JET_coltypGUID: 16, /* 16-byte globally unique identifier */
+};
+
+// Not very efficient, but only used for error messages
+function getColTypeName(numericValue) {
+ return Object.keys(COLUMN_TYPES).find(t => COLUMN_TYPES[t] == numericValue) || "unknown";
+}
+
+// All type constants and method wrappers go on this object:
+const ESE = {};
+ESE.JET_ERR = ctypes.long;
+ESE.JET_PCWSTR = ctypes.char16_t.ptr;
+// The ESE header calls this JET_API_PTR, but because it isn't ever used as a
+// pointer and because OS.File code implies that the name you give a type
+// matters, I opted for a different name.
+// Note that this is defined differently on 32 vs. 64-bit in the header.
+ESE.JET_API_ITEM = ctypes.voidptr_t.size == 4 ? ctypes.unsigned_long : ctypes.uint64_t;
+ESE.JET_INSTANCE = ESE.JET_API_ITEM;
+ESE.JET_SESID = ESE.JET_API_ITEM;
+ESE.JET_TABLEID = ESE.JET_API_ITEM;
+ESE.JET_COLUMNID = ctypes.unsigned_long;
+ESE.JET_GRBIT = ctypes.unsigned_long;
+ESE.JET_COLTYP = ctypes.unsigned_long;
+ESE.JET_DBID = ctypes.unsigned_long;
+
+ESE.JET_COLUMNDEF = new ctypes.StructType("JET_COLUMNDEF", [
+ {"cbStruct": ctypes.unsigned_long},
+ {"columnid": ESE.JET_COLUMNID },
+ {"coltyp": ESE.JET_COLTYP },
+ {"wCountry": ctypes.unsigned_short }, // sepcifies the country/region for the column definition
+ {"langid": ctypes.unsigned_short },
+ {"cp": ctypes.unsigned_short },
+ {"wCollate": ctypes.unsigned_short }, /* Must be 0 */
+ {"cbMax": ctypes.unsigned_long },
+ {"grbit": ESE.JET_GRBIT }
+]);
+
+// Track open databases
+let gOpenDBs = new Map();
+
+// Track open libraries
+let gLibs = {};
+this.ESE = ESE; // Required for tests.
+this.KERNEL = KERNEL; // ditto
+this.gLibs = gLibs; // ditto
+
+function convertESEError(errorCode) {
+ switch (errorCode) {
+ case -1213 /* JET_errPageSizeMismatch */:
+ case -1002 /* JET_errInvalidName*/:
+ case -1507 /* JET_errColumnNotFound */:
+ // The DB format has changed and we haven't updated this migration code:
+ return "The database format has changed, error code: " + errorCode;
+ case -1207 /* JET_errDatabaseLocked */:
+ case -1302 /* JET_errTableLocked */:
+ return "The database or table is locked, error code: " + errorCode;
+ case -1809 /* JET_errPermissionDenied*/:
+ case -1907 /* JET_errAccessDenied */:
+ return "Access or permission denied, error code: " + errorCode;
+ case -1044 /* JET_errInvalidFilename */:
+ return "Invalid file name";
+ case -1811 /* JET_errFileNotFound */:
+ return "File not found";
+ case -550 /* JET_errDatabaseDirtyShutdown */:
+ return "Database in dirty shutdown state (without the requisite logs?)";
+ case -514 /* JET_errBadLogVersion */:
+ return "Database log version does not match the version of ESE in use.";
+ default:
+ return "Unknown error: " + errorCode;
+ }
+}
+
+function handleESEError(method, methodName, shouldThrow = true, errorLog = true) {
+ return function () {
+ let rv;
+ try {
+ rv = method.apply(null, arguments);
+ } catch (ex) {
+ log.error("Error calling into ctypes method", methodName, ex);
+ throw ex;
+ }
+ let resultCode = parseInt(rv.toString(10), 10);
+ if (resultCode < 0) {
+ if (errorLog) {
+ log.error("Got error " + resultCode + " calling " + methodName);
+ }
+ if (shouldThrow) {
+ throw new Error(convertESEError(rv));
+ }
+ } else if (resultCode > 0 && errorLog) {
+ log.warn("Got warning " + resultCode + " calling " + methodName);
+ }
+ return resultCode;
+ };
+}
+
+
+function declareESEFunction(methodName, ...args) {
+ let declaration = ["Jet" + methodName, ctypes.winapi_abi, ESE.JET_ERR].concat(args);
+ let ctypeMethod = gLibs.ese.declare.apply(gLibs.ese, declaration);
+ ESE[methodName] = handleESEError(ctypeMethod, methodName);
+ ESE["FailSafe" + methodName] = handleESEError(ctypeMethod, methodName, false);
+ ESE["Manual" + methodName] = handleESEError(ctypeMethod, methodName, false, false);
+}
+
+function declareESEFunctions() {
+ declareESEFunction("GetDatabaseFileInfoW", ESE.JET_PCWSTR, ctypes.voidptr_t,
+ ctypes.unsigned_long, ctypes.unsigned_long);
+
+ declareESEFunction("GetSystemParameterW", ESE.JET_INSTANCE, ESE.JET_SESID,
+ ctypes.unsigned_long, ESE.JET_API_ITEM.ptr,
+ ESE.JET_PCWSTR, ctypes.unsigned_long);
+ declareESEFunction("SetSystemParameterW", ESE.JET_INSTANCE.ptr,
+ ESE.JET_SESID, ctypes.unsigned_long, ESE.JET_API_ITEM,
+ ESE.JET_PCWSTR);
+ declareESEFunction("CreateInstanceW", ESE.JET_INSTANCE.ptr, ESE.JET_PCWSTR);
+ declareESEFunction("Init", ESE.JET_INSTANCE.ptr);
+
+ declareESEFunction("BeginSessionW", ESE.JET_INSTANCE, ESE.JET_SESID.ptr,
+ ESE.JET_PCWSTR, ESE.JET_PCWSTR);
+ declareESEFunction("AttachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
+ ESE.JET_GRBIT);
+ declareESEFunction("DetachDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR);
+ declareESEFunction("OpenDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
+ ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT);
+ declareESEFunction("OpenTableW", ESE.JET_SESID, ESE.JET_DBID, ESE.JET_PCWSTR,
+ ctypes.voidptr_t, ctypes.unsigned_long, ESE.JET_GRBIT,
+ ESE.JET_TABLEID.ptr);
+
+ declareESEFunction("GetColumnInfoW", ESE.JET_SESID, ESE.JET_DBID,
+ ESE.JET_PCWSTR, ESE.JET_PCWSTR, ctypes.voidptr_t,
+ ctypes.unsigned_long, ctypes.unsigned_long);
+
+ declareESEFunction("Move", ESE.JET_SESID, ESE.JET_TABLEID, ctypes.long,
+ ESE.JET_GRBIT);
+
+ declareESEFunction("RetrieveColumn", ESE.JET_SESID, ESE.JET_TABLEID,
+ ESE.JET_COLUMNID, ctypes.voidptr_t, ctypes.unsigned_long,
+ ctypes.unsigned_long.ptr, ESE.JET_GRBIT, ctypes.voidptr_t);
+
+ declareESEFunction("CloseTable", ESE.JET_SESID, ESE.JET_TABLEID);
+ declareESEFunction("CloseDatabase", ESE.JET_SESID, ESE.JET_DBID,
+ ESE.JET_GRBIT);
+
+ declareESEFunction("EndSession", ESE.JET_SESID, ESE.JET_GRBIT);
+
+ declareESEFunction("Term", ESE.JET_INSTANCE);
+}
+
+function unloadLibraries() {
+ log.debug("Unloading");
+ if (gOpenDBs.size) {
+ log.error("Shouldn't unload libraries before DBs are closed!");
+ for (let db of gOpenDBs.values()) {
+ db._close();
+ }
+ }
+ for (let k of Object.keys(ESE)) {
+ delete ESE[k];
+ }
+ gLibs.ese.close();
+ gLibs.kernel.close();
+ delete gLibs.ese;
+ delete gLibs.kernel;
+}
+
+function loadLibraries() {
+ Services.obs.addObserver(unloadLibraries, "xpcom-shutdown", false);
+ gLibs.ese = ctypes.open("esent.dll");
+ gLibs.kernel = ctypes.open("kernel32.dll");
+ KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime",
+ ctypes.default_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
+
+ declareESEFunctions();
+}
+
+function ESEDB(rootPath, dbPath, logPath) {
+ log.info("Created db");
+ this.rootPath = rootPath;
+ this.dbPath = dbPath;
+ this.logPath = logPath;
+ this._references = 0;
+ this._init();
+}
+
+ESEDB.prototype = {
+ rootPath: null,
+ dbPath: null,
+ logPath: null,
+ _opened: false,
+ _attached: false,
+ _sessionCreated: false,
+ _instanceCreated: false,
+ _dbId: null,
+ _sessionId: null,
+ _instanceId: null,
+
+ _init() {
+ if (!gLibs.ese) {
+ loadLibraries();
+ }
+ this.incrementReferenceCounter();
+ this._internalOpen();
+ },
+
+ _internalOpen() {
+ try {
+ let dbinfo = new ctypes.unsigned_long();
+ ESE.GetDatabaseFileInfoW(this.dbPath, dbinfo.address(),
+ ctypes.unsigned_long.size, 17);
+
+ let pageSize = ctypes.UInt64.lo(dbinfo.value);
+ ESE.SetSystemParameterW(null, 0, 64 /* JET_paramDatabasePageSize*/,
+ pageSize, null);
+
+ this._instanceId = new ESE.JET_INSTANCE();
+ ESE.CreateInstanceW(this._instanceId.address(),
+ "firefox-dbreader-" + (gESEInstanceCounter++));
+ this._instanceCreated = true;
+
+ ESE.SetSystemParameterW(this._instanceId.address(), 0,
+ 0 /* JET_paramSystemPath*/, 0, this.rootPath);
+ ESE.SetSystemParameterW(this._instanceId.address(), 0,
+ 1 /* JET_paramTempPath */, 0, this.rootPath);
+ ESE.SetSystemParameterW(this._instanceId.address(), 0,
+ 2 /* JET_paramLogFilePath*/, 0, this.logPath);
+
+ // Shouldn't try to call JetTerm if the following call fails.
+ this._instanceCreated = false;
+ ESE.Init(this._instanceId.address());
+ this._instanceCreated = true;
+ this._sessionId = new ESE.JET_SESID();
+ ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null,
+ null);
+ this._sessionCreated = true;
+
+ const JET_bitDbReadOnly = 1;
+ ESE.AttachDatabaseW(this._sessionId, this.dbPath, JET_bitDbReadOnly);
+ this._attached = true;
+ this._dbId = new ESE.JET_DBID();
+ ESE.OpenDatabaseW(this._sessionId, this.dbPath, null,
+ this._dbId.address(), JET_bitDbReadOnly);
+ this._opened = true;
+ } catch (ex) {
+ try {
+ this._close();
+ } catch (innerException) {
+ Cu.reportError(innerException);
+ }
+ // Make sure caller knows we failed.
+ throw ex;
+ }
+ gOpenDBs.set(this.dbPath, this);
+ },
+
+ checkForColumn(tableName, columnName) {
+ if (!this._opened) {
+ throw new Error("The database was closed!");
+ }
+
+ let columnInfo;
+ try {
+ columnInfo = this._getColumnInfo(tableName, [{name: columnName}]);
+ } catch (ex) {
+ return null;
+ }
+ return columnInfo[0];
+ },
+
+ tableExists(tableName) {
+ if (!this._opened) {
+ throw new Error("The database was closed!");
+ }
+
+ let tableId = new ESE.JET_TABLEID();
+ let rv = ESE.ManualOpenTableW(this._sessionId, this._dbId, tableName, null,
+ 0, 4 /* JET_bitTableReadOnly */,
+ tableId.address());
+ if (rv == -1305 /* JET_errObjectNotFound */) {
+ return false;
+ }
+ if (rv < 0) {
+ log.error("Got error " + rv + " calling OpenTableW");
+ throw new Error(convertESEError(rv));
+ }
+
+ if (rv > 0) {
+ log.error("Got warning " + rv + " calling OpenTableW");
+ }
+ ESE.FailSafeCloseTable(this._sessionId, tableId);
+ return true;
+ },
+
+ tableItems: function*(tableName, columns) {
+ if (!this._opened) {
+ throw new Error("The database was closed!");
+ }
+
+ let tableOpened = false;
+ let tableId;
+ try {
+ tableId = this._openTable(tableName);
+ tableOpened = true;
+
+ let columnInfo = this._getColumnInfo(tableName, columns);
+
+ let rv = ESE.ManualMove(this._sessionId, tableId,
+ -2147483648 /* JET_MoveFirst */, 0);
+ if (rv == -1603 /* JET_errNoCurrentRecord */) {
+ // There are no rows in the table.
+ this._closeTable(tableId);
+ return;
+ }
+ if (rv != 0) {
+ throw new Error(convertESEError(rv));
+ }
+
+ do {
+ let rowContents = {};
+ for (let column of columnInfo) {
+ let [buffer, bufferSize] = this._getBufferForColumn(column);
+ // We handle errors manually so we accurately deal with NULL values.
+ let err = ESE.ManualRetrieveColumn(this._sessionId, tableId,
+ column.id, buffer.address(),
+ bufferSize, null, 0, null);
+ rowContents[column.name] = this._convertResult(column, buffer, err);
+ }
+ yield rowContents;
+ } while (ESE.ManualMove(this._sessionId, tableId, 1 /* JET_MoveNext */, 0) === 0);
+ } catch (ex) {
+ if (tableOpened) {
+ this._closeTable(tableId);
+ }
+ throw ex;
+ }
+ this._closeTable(tableId);
+ },
+
+ _openTable(tableName) {
+ let tableId = new ESE.JET_TABLEID();
+ ESE.OpenTableW(this._sessionId, this._dbId, tableName, null,
+ 0, 4 /* JET_bitTableReadOnly */, tableId.address());
+ return tableId;
+ },
+
+ _getBufferForColumn(column) {
+ let buffer;
+ if (column.type == "string") {
+ let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
+ // size on the column is in bytes, 2 bytes to a wchar, so:
+ let charCount = column.dbSize >> 1;
+ buffer = new wchar_tArray(charCount);
+ } else if (column.type == "boolean") {
+ buffer = new ctypes.uint8_t();
+ } else if (column.type == "date") {
+ buffer = new KERNEL.FILETIME();
+ } else if (column.type == "guid") {
+ let byteArray = ctypes.ArrayType(ctypes.uint8_t);
+ buffer = new byteArray(column.dbSize);
+ } else {
+ throw new Error("Unknown type " + column.type);
+ }
+ return [buffer, buffer.constructor.size];
+ },
+
+ _convertResult(column, buffer, err) {
+ if (err != 0) {
+ if (err == 1004) {
+ // Deal with null values:
+ buffer = null;
+ } else {
+ Cu.reportError("Unexpected JET error: " + err + ";" + " retrieving value for column " + column.name);
+ throw new Error(convertESEError(err));
+ }
+ }
+ if (column.type == "string") {
+ return buffer ? buffer.readString() : "";
+ }
+ if (column.type == "boolean") {
+ return buffer ? (buffer.value == 255) : false;
+ }
+ if (column.type == "guid") {
+ if (buffer.length != 16) {
+ Cu.reportError("Buffer size for guid field " + column.id + " should have been 16!");
+ return "";
+ }
+ let rv = "{";
+ for (let i = 0; i < 16; i++) {
+ if (i == 4 || i == 6 || i == 8 || i == 10) {
+ rv += "-";
+ }
+ let byteValue = buffer.addressOfElement(i).contents;
+ // Ensure there's a leading 0
+ rv += ("0" + byteValue.toString(16)).substr(-2);
+ }
+ return rv + "}";
+ }
+ if (column.type == "date") {
+ if (!buffer) {
+ return null;
+ }
+ let systemTime = new KERNEL.SYSTEMTIME();
+ let result = KERNEL.FileTimeToSystemTime(buffer.address(), systemTime.address());
+ if (result == 0) {
+ throw new Error(ctypes.winLastError);
+ }
+
+ // System time is in UTC, so we use Date.UTC to get milliseconds from epoch,
+ // then divide by 1000 to get seconds, and round down:
+ return new Date(Date.UTC(systemTime.wYear,
+ systemTime.wMonth - 1,
+ systemTime.wDay,
+ systemTime.wHour,
+ systemTime.wMinute,
+ systemTime.wSecond,
+ systemTime.wMilliseconds));
+ }
+ return undefined;
+ },
+
+ _getColumnInfo(tableName, columns) {
+ let rv = [];
+ for (let column of columns) {
+ let columnInfoFromDB = new ESE.JET_COLUMNDEF();
+ ESE.GetColumnInfoW(this._sessionId, this._dbId, tableName, column.name,
+ columnInfoFromDB.address(), ESE.JET_COLUMNDEF.size, 0 /* JET_ColInfo */);
+ let dbType = parseInt(columnInfoFromDB.coltyp.toString(10), 10);
+ let dbSize = parseInt(columnInfoFromDB.cbMax.toString(10), 10);
+ if (column.type == "string") {
+ if (dbType != COLUMN_TYPES.JET_coltypLongText &&
+ dbType != COLUMN_TYPES.JET_coltypText) {
+ throw new Error("Invalid column type for column " + column.name +
+ "; expected text type, got type " + getColTypeName(dbType));
+ }
+ if (dbSize > MAX_STR_LENGTH) {
+ throw new Error("Column " + column.name + " has more than 64k data in it. This API is not designed to handle data that large.");
+ }
+ } else if (column.type == "boolean") {
+ if (dbType != COLUMN_TYPES.JET_coltypBit) {
+ throw new Error("Invalid column type for column " + column.name +
+ "; expected bit type, got type " + getColTypeName(dbType));
+ }
+ } else if (column.type == "date") {
+ if (dbType != COLUMN_TYPES.JET_coltypLongLong) {
+ throw new Error("Invalid column type for column " + column.name +
+ "; expected long long type, got type " + getColTypeName(dbType));
+ }
+ } else if (column.type == "guid") {
+ if (dbType != COLUMN_TYPES.JET_coltypGUID) {
+ throw new Error("Invalid column type for column " + column.name +
+ "; expected guid type, got type " + getColTypeName(dbType));
+ }
+ } else if (column.type) {
+ throw new Error("Unknown column type " + column.type + " requested for column " +
+ column.name + ", don't know what to do.");
+ }
+
+ rv.push({name: column.name, id: columnInfoFromDB.columnid, type: column.type, dbSize, dbType});
+ }
+ return rv;
+ },
+
+ _closeTable(tableId) {
+ ESE.FailSafeCloseTable(this._sessionId, tableId);
+ },
+
+ _close() {
+ this._internalClose();
+ gOpenDBs.delete(this.dbPath);
+ },
+
+ _internalClose() {
+ if (this._opened) {
+ log.debug("close db");
+ ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0);
+ log.debug("finished close db");
+ this._opened = false;
+ }
+ if (this._attached) {
+ log.debug("detach db");
+ ESE.FailSafeDetachDatabaseW(this._sessionId, this.dbPath);
+ this._attached = false;
+ }
+ if (this._sessionCreated) {
+ log.debug("end session");
+ ESE.FailSafeEndSession(this._sessionId, 0);
+ this._sessionCreated = false;
+ }
+ if (this._instanceCreated) {
+ log.debug("term");
+ ESE.FailSafeTerm(this._instanceId);
+ this._instanceCreated = false;
+ }
+ },
+
+ incrementReferenceCounter() {
+ this._references++;
+ },
+
+ decrementReferenceCounter() {
+ this._references--;
+ if (this._references <= 0) {
+ this._close();
+ }
+ },
+};
+
+let ESEDBReader = {
+ openDB(rootDir, dbFile, logDir) {
+ let dbFilePath = dbFile.path;
+ if (gOpenDBs.has(dbFilePath)) {
+ let db = gOpenDBs.get(dbFilePath);
+ db.incrementReferenceCounter();
+ return db;
+ }
+ // ESE is really picky about the trailing slashes according to the docs,
+ // so we do as we're told and ensure those are there:
+ return new ESEDB(rootDir.path + "\\", dbFilePath, logDir.path + "\\");
+ },
+
+ closeDB(db) {
+ db.decrementReferenceCounter();
+ },
+
+ COLUMN_TYPES,
+};
+
diff --git a/browser/components/migration/EdgeProfileMigrator.js b/browser/components/migration/EdgeProfileMigrator.js
new file mode 100644
index 000000000..afdcc2773
--- /dev/null
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -0,0 +1,450 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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/AppConstants.jsm");
+Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+Cu.import("resource:///modules/MSMigrationUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ESEDBReader",
+ "resource:///modules/ESEDBReader.jsm");
+
+const kEdgeRegistryRoot = "SOFTWARE\\Classes\\Local Settings\\Software\\" +
+ "Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\" +
+ "microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge";
+const kEdgeDatabasePath = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
+
+XPCOMUtils.defineLazyGetter(this, "gEdgeDatabase", function() {
+ let edgeDir = MSMigrationUtils.getEdgeLocalDataFolder();
+ if (!edgeDir) {
+ return null;
+ }
+ edgeDir.appendRelativePath(kEdgeDatabasePath);
+ if (!edgeDir.exists() || !edgeDir.isReadable() || !edgeDir.isDirectory()) {
+ return null;
+ }
+ let expectedLocation = edgeDir.clone();
+ expectedLocation.appendRelativePath("nouser1\\120712-0049\\DBStore\\spartan.edb");
+ if (expectedLocation.exists() && expectedLocation.isReadable() && expectedLocation.isFile()) {
+ return expectedLocation;
+ }
+ // We used to recurse into arbitrary subdirectories here, but that code
+ // went unused, so it likely isn't necessary, even if we don't understand
+ // where the magic folders above come from, they seem to be the same for
+ // everyone. Just return null if they're not there:
+ return null;
+});
+
+/**
+ * Get rows from a table in the Edge DB as an array of JS objects.
+ *
+ * @param {String} tableName the name of the table to read.
+ * @param {String[]|function} columns a list of column specifiers
+ * (see ESEDBReader.jsm) or a function that
+ * generates them based on the database
+ * reference once opened.
+ * @param {function} filterFn a function that is called for each row.
+ * Only rows for which it returns a truthy
+ * value are included in the result.
+ * @param {nsIFile} dbFile the database file to use. Defaults to
+ * the main Edge database.
+ * @returns {Array} An array of row objects.
+ */
+function readTableFromEdgeDB(tableName, columns, filterFn, dbFile = gEdgeDatabase) {
+ let database;
+ let rows = [];
+ try {
+ let logFile = dbFile.parent;
+ logFile.append("LogFiles");
+ database = ESEDBReader.openDB(dbFile.parent, dbFile, logFile);
+
+ if (typeof columns == "function") {
+ columns = columns(database);
+ }
+
+ let tableReader = database.tableItems(tableName, columns);
+ for (let row of tableReader) {
+ if (filterFn(row)) {
+ rows.push(row);
+ }
+ }
+ } catch (ex) {
+ Cu.reportError("Failed to extract items from table " + tableName + " in Edge database at " +
+ dbFile.path + " due to the following error: " + ex);
+ // Deliberately make this fail so we expose failure in the UI:
+ throw ex;
+ } finally {
+ if (database) {
+ ESEDBReader.closeDB(database);
+ }
+ }
+ return rows;
+}
+
+function EdgeTypedURLMigrator() {
+}
+
+EdgeTypedURLMigrator.prototype = {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ get _typedURLs() {
+ if (!this.__typedURLs) {
+ this.__typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
+ }
+ return this.__typedURLs;
+ },
+
+ get exists() {
+ return this._typedURLs.size > 0;
+ },
+
+ migrate: function(aCallback) {
+ let typedURLs = this._typedURLs;
+ let places = [];
+ for (let [urlString, time] of typedURLs) {
+ let uri;
+ try {
+ uri = Services.io.newURI(urlString, null, null);
+ if (["http", "https", "ftp"].indexOf(uri.scheme) == -1) {
+ continue;
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ continue;
+ }
+
+ // Note that the time will be in microseconds (PRTime),
+ // and Date.now() returns milliseconds. Places expects PRTime,
+ // so we multiply the Date.now return value to make up the difference.
+ let visitDate = time || (Date.now() * 1000);
+ places.push({
+ uri,
+ visits: [{ transitionType: Ci.nsINavHistoryService.TRANSITION_TYPED,
+ visitDate}]
+ });
+ }
+
+ if (places.length == 0) {
+ aCallback(typedURLs.size == 0);
+ return;
+ }
+
+ MigrationUtils.insertVisitsWrapper(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ },
+};
+
+function EdgeReadingListMigrator() {
+}
+
+EdgeReadingListMigrator.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get exists() {
+ return !!gEdgeDatabase;
+ },
+
+ migrate(callback) {
+ this._migrateReadingList(PlacesUtils.bookmarks.menuGuid).then(
+ () => callback(true),
+ ex => {
+ Cu.reportError(ex);
+ callback(false);
+ }
+ );
+ },
+
+ _migrateReadingList: Task.async(function*(parentGuid) {
+ let columnFn = db => {
+ let columns = [
+ {name: "URL", type: "string"},
+ {name: "Title", type: "string"},
+ {name: "AddedDate", type: "date"}
+ ];
+
+ // Later versions have an IsDeleted column:
+ let isDeletedColumn = db.checkForColumn("ReadingList", "IsDeleted");
+ if (isDeletedColumn && isDeletedColumn.dbType == ESEDBReader.COLUMN_TYPES.JET_coltypBit) {
+ columns.push({name: "IsDeleted", type: "boolean"});
+ }
+ return columns;
+ };
+
+ let filterFn = row => {
+ return !row.IsDeleted;
+ };
+
+ let readingListItems = readTableFromEdgeDB("ReadingList", columnFn, filterFn);
+ if (!readingListItems.length) {
+ return;
+ }
+
+ let destFolderGuid = yield this._ensureReadingListFolder(parentGuid);
+ let exceptionThrown;
+ for (let item of readingListItems) {
+ let dateAdded = item.AddedDate || new Date();
+ yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid: destFolderGuid, url: item.URL, title: item.Title, dateAdded
+ }).catch(ex => {
+ if (!exceptionThrown) {
+ exceptionThrown = ex;
+ }
+ Cu.reportError(ex);
+ });
+ }
+ if (exceptionThrown) {
+ throw exceptionThrown;
+ }
+ }),
+
+ _ensureReadingListFolder: Task.async(function*(parentGuid) {
+ if (!this.__readingListFolderGuid) {
+ let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList");
+ let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle};
+ this.__readingListFolderGuid = (yield MigrationUtils.insertBookmarkWrapper(folderSpec)).guid;
+ }
+ return this.__readingListFolderGuid;
+ }),
+};
+
+function EdgeBookmarksMigrator(dbOverride) {
+ this.dbOverride = dbOverride;
+}
+
+EdgeBookmarksMigrator.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get db() { return this.dbOverride || gEdgeDatabase },
+
+ get TABLE_NAME() { return "Favorites" },
+
+ get exists() {
+ if (!("_exists" in this)) {
+ this._exists = !!this.db;
+ }
+ return this._exists;
+ },
+
+ migrate(callback) {
+ this._migrateBookmarks(PlacesUtils.bookmarks.menuGuid).then(
+ () => callback(true),
+ ex => {
+ Cu.reportError(ex);
+ callback(false);
+ }
+ );
+ },
+
+ _migrateBookmarks: Task.async(function*(rootGuid) {
+ let {bookmarks, folderMap} = this._fetchBookmarksFromDB();
+ if (!bookmarks.length) {
+ return;
+ }
+ yield this._importBookmarks(bookmarks, folderMap, rootGuid);
+ }),
+
+ _importBookmarks: Task.async(function*(bookmarks, folderMap, rootGuid) {
+ if (!MigrationUtils.isStartupMigration) {
+ rootGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Edge", rootGuid);
+ }
+
+ let exceptionThrown;
+ for (let bookmark of bookmarks) {
+ // If this is a folder, we might have created it already to put other bookmarks in.
+ if (bookmark.IsFolder && bookmark._guid) {
+ continue;
+ }
+
+ // If this is a folder, just create folders up to and including that folder.
+ // Otherwise, create folders until we have a parent for this bookmark.
+ // This avoids duplicating logic for the bookmarks bar.
+ let folderId = bookmark.IsFolder ? bookmark.ItemId : bookmark.ParentId;
+ let parentGuid = yield this._getGuidForFolder(folderId, folderMap, rootGuid).catch(ex => {
+ if (!exceptionThrown) {
+ exceptionThrown = ex;
+ }
+ Cu.reportError(ex);
+ });
+
+ // If this was a folder, we're done with this item
+ if (bookmark.IsFolder) {
+ continue;
+ }
+
+ if (!parentGuid) {
+ // If we couldn't sort out a parent, fall back to importing on the root:
+ parentGuid = rootGuid;
+ }
+ let placesInfo = {
+ parentGuid,
+ url: bookmark.URL,
+ dateAdded: bookmark.DateUpdated || new Date(),
+ title: bookmark.Title,
+ };
+
+ yield MigrationUtils.insertBookmarkWrapper(placesInfo).catch(ex => {
+ if (!exceptionThrown) {
+ exceptionThrown = ex;
+ }
+ Cu.reportError(ex);
+ });
+ }
+
+ if (exceptionThrown) {
+ throw exceptionThrown;
+ }
+ }),
+
+ _fetchBookmarksFromDB() {
+ let folderMap = new Map();
+ let columns = [
+ {name: "URL", type: "string"},
+ {name: "Title", type: "string"},
+ {name: "DateUpdated", type: "date"},
+ {name: "IsFolder", type: "boolean"},
+ {name: "IsDeleted", type: "boolean"},
+ {name: "ParentId", type: "guid"},
+ {name: "ItemId", type: "guid"}
+ ];
+ let filterFn = row => {
+ if (row.IsDeleted) {
+ return false;
+ }
+ if (row.IsFolder) {
+ folderMap.set(row.ItemId, row);
+ }
+ return true;
+ };
+ let bookmarks = readTableFromEdgeDB(this.TABLE_NAME, columns, filterFn, this.db);
+ return {bookmarks, folderMap};
+ },
+
+ _getGuidForFolder: Task.async(function*(folderId, folderMap, rootGuid) {
+ // If the folderId is not known as a folder in the folder map, we assume
+ // we just need the root
+ if (!folderMap.has(folderId)) {
+ return rootGuid;
+ }
+ let folder = folderMap.get(folderId);
+ // If the folder already has a places guid, just return that.
+ if (folder._guid) {
+ return folder._guid;
+ }
+
+ // Hacks! The bookmarks bar is special:
+ if (folder.Title == "_Favorites_Bar_") {
+ let toolbarGuid = PlacesUtils.bookmarks.toolbarGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ toolbarGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Edge", toolbarGuid);
+ }
+ folder._guid = toolbarGuid;
+ return folder._guid;
+ }
+ // Otherwise, get the right parent guid recursively:
+ let parentGuid = yield this._getGuidForFolder(folder.ParentId, folderMap, rootGuid);
+ let folderInfo = {
+ title: folder.Title,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ dateAdded: folder.DateUpdated || new Date(),
+ parentGuid,
+ };
+ // and add ourselves as a kid, and return the guid we got.
+ let parentBM = yield MigrationUtils.insertBookmarkWrapper(folderInfo);
+ folder._guid = parentBM.guid;
+ return folder._guid;
+ }),
+};
+
+function EdgeProfileMigrator() {
+ this.wrappedJSObject = this;
+}
+
+EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+EdgeProfileMigrator.prototype.getESEMigratorForTesting = function(dbOverride) {
+ return new EdgeBookmarksMigrator(dbOverride);
+};
+
+EdgeProfileMigrator.prototype.getResources = function() {
+ let resources = [
+ new EdgeBookmarksMigrator(),
+ MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
+ new EdgeTypedURLMigrator(),
+ new EdgeReadingListMigrator(),
+ ];
+ let windowsVaultFormPasswordsMigrator =
+ MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+ windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
+ resources.push(windowsVaultFormPasswordsMigrator);
+ return resources.filter(r => r.exists);
+};
+
+EdgeProfileMigrator.prototype.getLastUsedDate = function() {
+ // Don't do this if we don't have a single profile (see the comment for
+ // sourceProfiles) or if we can't find the database file:
+ if (this.sourceProfiles !== null || !gEdgeDatabase) {
+ return Promise.resolve(new Date(0));
+ }
+ let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
+ let dbPath = gEdgeDatabase.path;
+ let cookieMigrator = MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE);
+ let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
+ let datePromises = [logFilePath, dbPath, ... cookiePaths].map(path => {
+ return OS.File.stat(path).catch(() => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ datePromises.push(new Promise(resolve => {
+ let typedURLs = new Map();
+ try {
+ typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
+ } catch (ex) {}
+ let times = [0, ... typedURLs.values()];
+ resolve(Math.max.apply(Math, times));
+ }));
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
+/* Somewhat counterintuitively, this returns:
+ * - |null| to indicate "There is only 1 (default) profile" (on win10+)
+ * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
+ * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
+ */
+EdgeProfileMigrator.prototype.__defineGetter__("sourceProfiles", function() {
+ let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
+ return isWin10OrHigher ? null : [];
+});
+
+EdgeProfileMigrator.prototype.__defineGetter__("sourceLocked", function() {
+ // There is an exclusive lock on some databases. Assume they are locked for now.
+ return true;
+});
+
+
+EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
+EdgeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=edge";
+EdgeProfileMigrator.prototype.classID = Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EdgeProfileMigrator]);
diff --git a/browser/components/migration/FirefoxProfileMigrator.js b/browser/components/migration/FirefoxProfileMigrator.js
new file mode 100644
index 000000000..60ffcf627
--- /dev/null
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -0,0 +1,255 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+"use strict";
+
+/*
+ * Migrates from a Firefox profile in a lossy manner in order to clean up a
+ * user's profile. Data is only migrated where the benefits outweigh the
+ * potential problems caused by importing undesired/invalid configurations
+ * from the source profile.
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
+ "resource://gre/modules/PlacesBackups.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionMigration",
+ "resource:///modules/sessionstore/SessionMigration.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ProfileAge",
+ "resource://gre/modules/ProfileAge.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+
+function FirefoxProfileMigrator() {
+ this.wrappedJSObject = this; // for testing...
+}
+
+FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+FirefoxProfileMigrator.prototype._getAllProfiles = function() {
+ let allProfiles = new Map();
+ let profiles =
+ Components.classes["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Components.interfaces.nsIToolkitProfileService)
+ .profiles;
+ while (profiles.hasMoreElements()) {
+ let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
+ let rootDir = profile.rootDir;
+
+ if (rootDir.exists() && rootDir.isReadable() &&
+ !rootDir.equals(MigrationUtils.profileStartup.directory)) {
+ allProfiles.set(profile.name, rootDir);
+ }
+ }
+ return allProfiles;
+};
+
+function sorter(a, b) {
+ return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
+}
+
+Object.defineProperty(FirefoxProfileMigrator.prototype, "sourceProfiles", {
+ get: function() {
+ return [...this._getAllProfiles().keys()].map(x => ({id: x, name: x})).sort(sorter);
+ }
+});
+
+FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) {
+ let file = dir.clone();
+ file.append(fileName);
+
+ // File resources are monolithic. We don't make partial copies since
+ // they are not expected to work alone. Return null to avoid trying to
+ // copy non-existing files.
+ return file.exists() ? file : null;
+};
+
+FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
+ let sourceProfileDir = aProfile ? this._getAllProfiles().get(aProfile.id) :
+ Components.classes["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Components.interfaces.nsIToolkitProfileService)
+ .selectedProfile.rootDir;
+ if (!sourceProfileDir || !sourceProfileDir.exists() ||
+ !sourceProfileDir.isReadable())
+ return null;
+
+ // Being a startup-only migrator, we can rely on
+ // MigrationUtils.profileStartup being set.
+ let currentProfileDir = MigrationUtils.profileStartup.directory;
+
+ // Surely data cannot be imported from the current profile.
+ if (sourceProfileDir.equals(currentProfileDir))
+ return null;
+
+ return this._getResourcesInternal(sourceProfileDir, currentProfileDir);
+};
+
+FirefoxProfileMigrator.prototype.getLastUsedDate = function() {
+ // We always pretend we're really old, so that we don't mess
+ // up the determination of which browser is the most 'recent'
+ // to import from.
+ return Promise.resolve(new Date(0));
+};
+
+FirefoxProfileMigrator.prototype._getResourcesInternal = function(sourceProfileDir, currentProfileDir) {
+ let getFileResource = function(aMigrationType, aFileNames) {
+ let files = [];
+ for (let fileName of aFileNames) {
+ let file = this._getFileObject(sourceProfileDir, fileName);
+ if (file)
+ files.push(file);
+ }
+ if (!files.length) {
+ return null;
+ }
+ return {
+ type: aMigrationType,
+ migrate: function(aCallback) {
+ for (let file of files) {
+ file.copyTo(currentProfileDir, "");
+ }
+ aCallback(true);
+ }
+ };
+ }.bind(this);
+
+ let types = MigrationUtils.resourceTypes;
+ let places = getFileResource(types.HISTORY, ["places.sqlite", "places.sqlite-wal"]);
+ let cookies = getFileResource(types.COOKIES, ["cookies.sqlite", "cookies.sqlite-wal"]);
+ let passwords = getFileResource(types.PASSWORDS,
+ ["signons.sqlite", "logins.json", "key3.db",
+ "signedInUser.json"]);
+ let formData = getFileResource(types.FORMDATA, ["formhistory.sqlite"]);
+ let bookmarksBackups = getFileResource(types.OTHERDATA,
+ [PlacesBackups.profileRelativeFolderPath]);
+ let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
+
+ let sessionCheckpoints = this._getFileObject(sourceProfileDir, "sessionCheckpoints.json");
+ let sessionFile = this._getFileObject(sourceProfileDir, "sessionstore.js");
+ let session;
+ if (sessionFile) {
+ session = {
+ type: types.SESSION,
+ migrate: function(aCallback) {
+ sessionCheckpoints.copyTo(currentProfileDir, "sessionCheckpoints.json");
+ let newSessionFile = currentProfileDir.clone();
+ newSessionFile.append("sessionstore.js");
+ let migrationPromise = SessionMigration.migrate(sessionFile.path, newSessionFile.path);
+ migrationPromise.then(function() {
+ let buildID = Services.appinfo.platformBuildID;
+ let mstone = Services.appinfo.platformVersion;
+ // Force the browser to one-off resume the session that we give it:
+ Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
+ // Reset the homepage_override prefs so that the browser doesn't override our
+ // session with the "what's new" page:
+ Services.prefs.setCharPref("browser.startup.homepage_override.mstone", mstone);
+ Services.prefs.setCharPref("browser.startup.homepage_override.buildID", buildID);
+ // It's too early in startup for the pref service to have a profile directory,
+ // so we have to manually tell it where to save the prefs file.
+ let newPrefsFile = currentProfileDir.clone();
+ newPrefsFile.append("prefs.js");
+ Services.prefs.savePrefFile(newPrefsFile);
+ aCallback(true);
+ }, function() {
+ aCallback(false);
+ });
+ }
+ };
+ }
+
+ // Telemetry related migrations.
+ let times = {
+ name: "times", // name is used only by tests.
+ type: types.OTHERDATA,
+ migrate: aCallback => {
+ let file = this._getFileObject(sourceProfileDir, "times.json");
+ if (file) {
+ file.copyTo(currentProfileDir, "");
+ }
+ // And record the fact a migration (ie, a reset) happened.
+ let timesAccessor = new ProfileAge(currentProfileDir.path);
+ timesAccessor.recordProfileReset().then(
+ () => aCallback(true),
+ () => aCallback(false)
+ );
+ }
+ };
+ let telemetry = {
+ name: "telemetry", // name is used only by tests...
+ type: types.OTHERDATA,
+ migrate: aCallback => {
+ let createSubDir = (name) => {
+ let dir = currentProfileDir.clone();
+ dir.append(name);
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ return dir;
+ };
+
+ // If the 'datareporting' directory exists we migrate files from it.
+ let haveStateFile = false;
+ let dataReportingDir = this._getFileObject(sourceProfileDir, "datareporting");
+ if (dataReportingDir && dataReportingDir.isDirectory()) {
+ // Copy only specific files.
+ let toCopy = ["state.json", "session-state.json"];
+
+ let dest = createSubDir("datareporting");
+ let enumerator = dataReportingDir.directoryEntries;
+ while (enumerator.hasMoreElements()) {
+ let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
+ if (file.isDirectory() || toCopy.indexOf(file.leafName) == -1) {
+ continue;
+ }
+
+ if (file.leafName == "state.json") {
+ haveStateFile = true;
+ }
+ file.copyTo(dest, "");
+ }
+ }
+
+ if (!haveStateFile) {
+ // Fall back to migrating the state file that contains the client id from healthreport/.
+ // We first moved the client id management from the FHR implementation to the datareporting
+ // service.
+ // Consequently, we try to migrate an existing FHR state file here as a fallback.
+ let healthReportDir = this._getFileObject(sourceProfileDir, "healthreport");
+ if (healthReportDir && healthReportDir.isDirectory()) {
+ let stateFile = this._getFileObject(healthReportDir, "state.json");
+ if (stateFile) {
+ let dest = createSubDir("healthreport");
+ stateFile.copyTo(dest, "");
+ }
+ }
+ }
+
+ aCallback(true);
+ }
+ };
+
+ return [places, cookies, passwords, formData, dictionary, bookmarksBackups,
+ session, times, telemetry].filter(r => r);
+};
+
+Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
+ get: () => true
+});
+
+
+FirefoxProfileMigrator.prototype.classDescription = "Firefox Profile Migrator";
+FirefoxProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=firefox";
+FirefoxProfileMigrator.prototype.classID = Components.ID("{91185366-ba97-4438-acba-48deaca63386}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FirefoxProfileMigrator]);
diff --git a/browser/components/migration/IEProfileMigrator.js b/browser/components/migration/IEProfileMigrator.js
new file mode 100644
index 000000000..ac055686c
--- /dev/null
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -0,0 +1,542 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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;
+
+const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
+const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+Cu.import("resource:///modules/MSMigrationUtils.jsm");
+
+
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+ "resource://gre/modules/ctypes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
+ "resource://gre/modules/OSCrypto.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+
+Cu.importGlobalProperties(["URL"]);
+
+// Resources
+
+function History() {
+}
+
+History.prototype = {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ get exists() {
+ return true;
+ },
+
+ migrate: function H_migrate(aCallback) {
+ let places = [];
+ let typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
+ let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
+ createInstance(Ci.nsISimpleEnumerator);
+ while (historyEnumerator.hasMoreElements()) {
+ let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
+ let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
+ // MSIE stores some types of URLs in its history that we don't handle,
+ // like HTMLHelp and others. Since we don't properly map handling for
+ // all of them we just avoid importing them.
+ if (["http", "https", "ftp", "file"].indexOf(uri.scheme) == -1) {
+ continue;
+ }
+
+ let title = entry.get("title");
+ // Embed visits have no title and don't need to be imported.
+ if (title.length == 0) {
+ continue;
+ }
+
+ // The typed urls are already fixed-up, so we can use them for comparison.
+ let transitionType = typedURLs.has(uri.spec) ?
+ Ci.nsINavHistoryService.TRANSITION_TYPED :
+ Ci.nsINavHistoryService.TRANSITION_LINK;
+ // use the current date if we have no visits for this entry.
+ // Note that the entry will have a time in microseconds (PRTime),
+ // and Date.now() returns milliseconds. Places expects PRTime,
+ // so we multiply the Date.now return value to make up the difference.
+ let lastVisitTime = entry.get("time") || (Date.now() * 1000);
+
+ places.push(
+ { uri: uri,
+ title: title,
+ visits: [{ transitionType: transitionType,
+ visitDate: lastVisitTime }]
+ }
+ );
+ }
+
+ // Check whether there is any history to import.
+ if (places.length == 0) {
+ aCallback(true);
+ return;
+ }
+
+ MigrationUtils.insertVisitsWrapper(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ }
+};
+
+// IE form password migrator supporting windows from XP until 7 and IE from 7 until 11
+function IE7FormPasswords() {
+ // used to distinguish between this migrator and other passwords migrators in tests.
+ this.name = "IE7FormPasswords";
+}
+
+IE7FormPasswords.prototype = {
+ type: MigrationUtils.resourceTypes.PASSWORDS,
+
+ get exists() {
+ // work only on windows until 7
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ return false;
+ }
+
+ try {
+ let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
+ let key = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(nsIWindowsRegKey);
+ key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
+ nsIWindowsRegKey.ACCESS_READ);
+ let count = key.valueCount;
+ key.close();
+ return count > 0;
+ } catch (e) {
+ return false;
+ }
+ },
+
+ migrate(aCallback) {
+ let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
+ createInstance(Ci.nsISimpleEnumerator);
+ let uris = []; // the uris of the websites that are going to be migrated
+ while (historyEnumerator.hasMoreElements()) {
+ let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
+ let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
+ // MSIE stores some types of URLs in its history that we don't handle, like HTMLHelp
+ // and others. Since we are not going to import the logins that are performed in these URLs
+ // we can just skip them.
+ if (["http", "https", "ftp"].indexOf(uri.scheme) == -1) {
+ continue;
+ }
+
+ uris.push(uri);
+ }
+ this._migrateURIs(uris);
+ aCallback(true);
+ },
+
+ /**
+ * Migrate the logins that were saved for the uris arguments.
+ * @param {nsIURI[]} uris - the uris that are going to be migrated.
+ */
+ _migrateURIs(uris) {
+ this.ctypesKernelHelpers = new MSMigrationUtils.CtypesKernelHelpers();
+ this._crypto = new OSCrypto();
+ let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
+ let key = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(nsIWindowsRegKey);
+ key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
+ nsIWindowsRegKey.ACCESS_READ);
+
+ let urlsSet = new Set(); // set of the already processed urls.
+ // number of the successfully decrypted registry values
+ let successfullyDecryptedValues = 0;
+ /* The logins are stored in the registry, where the key is a hashed URL and its
+ * value contains the encrypted details for all logins for that URL.
+ *
+ * First iterate through IE history, hashing each URL and looking for a match. If
+ * found, decrypt the value, using the URL as a salt. Finally add any found logins
+ * to the Firefox password manager.
+ */
+
+ for (let uri of uris) {
+ try {
+ // remove the query and the ref parts of the URL
+ let urlObject = new URL(uri.spec);
+ let url = urlObject.origin + urlObject.pathname;
+ // if the current url is already processed, it should be skipped
+ if (urlsSet.has(url)) {
+ continue;
+ }
+ urlsSet.add(url);
+ // hash value of the current uri
+ let hashStr = this._crypto.getIELoginHash(url);
+ if (!key.hasValue(hashStr)) {
+ continue;
+ }
+ let value = key.readBinaryValue(hashStr);
+ // if no value was found, the uri is skipped
+ if (value == null) {
+ continue;
+ }
+ let data;
+ try {
+ // the url is used as salt to decrypt the registry value
+ data = this._crypto.decryptData(value, url, true);
+ } catch (e) {
+ continue;
+ }
+ // extract the login details from the decrypted data
+ let ieLogins = this._extractDetails(data, uri);
+ // if at least a credential was found in the current data, successfullyDecryptedValues should
+ // be incremented by one
+ if (ieLogins.length) {
+ successfullyDecryptedValues++;
+ }
+ this._addLogins(ieLogins);
+ } catch (e) {
+ Cu.reportError("Error while importing logins for " + uri.spec + ": " + e);
+ }
+ }
+ // if the number of the imported values is less than the number of values in the key, it means
+ // that not all the values were imported and an error should be reported
+ if (successfullyDecryptedValues < key.valueCount) {
+ Cu.reportError("We failed to decrypt and import some logins. " +
+ "This is likely because we didn't find the URLs where these " +
+ "passwords were submitted in the IE history and which are needed to be used " +
+ "as keys in the decryption.");
+ }
+
+ key.close();
+ this._crypto.finalize();
+ this.ctypesKernelHelpers.finalize();
+ },
+
+ _crypto: null,
+
+ /**
+ * Add the logins to the password manager.
+ * @param {Object[]} logins - array of the login details.
+ */
+ _addLogins(ieLogins) {
+ for (let ieLogin of ieLogins) {
+ try {
+ // create a new login
+ let login = {
+ username: ieLogin.username,
+ password: ieLogin.password,
+ hostname: ieLogin.url,
+ timeCreated: ieLogin.creation,
+ };
+ MigrationUtils.insertLoginWrapper(login);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ /**
+ * Extract the details of one or more logins from the raw decrypted data.
+ * @param {string} data - the decrypted data containing raw information.
+ * @param {nsURI} uri - the nsURI of page where the login has occur.
+ * @returns {Object[]} array of objects where each of them contains the username, password, URL,
+ * and creation time representing all the logins found in the data arguments.
+ */
+ _extractDetails(data, uri) {
+ // the structure of the header of the IE7 decrypted data for all the logins sharing the same URL
+ let loginData = new ctypes.StructType("loginData", [
+ // Bytes 0-3 are not needed and not documented
+ {"unknown1": ctypes.uint32_t},
+ // Bytes 4-7 are the header size
+ {"headerSize": ctypes.uint32_t},
+ // Bytes 8-11 are the data size
+ {"dataSize": ctypes.uint32_t},
+ // Bytes 12-19 are not needed and not documented
+ {"unknown2": ctypes.uint32_t},
+ {"unknown3": ctypes.uint32_t},
+ // Bytes 20-23 are the data count: each username and password is considered as a data
+ {"dataMax": ctypes.uint32_t},
+ // Bytes 24-35 are not needed and not documented
+ {"unknown4": ctypes.uint32_t},
+ {"unknown5": ctypes.uint32_t},
+ {"unknown6": ctypes.uint32_t}
+ ]);
+
+ // the structure of a IE7 decrypted login item
+ let loginItem = new ctypes.StructType("loginItem", [
+ // Bytes 0-3 are the offset of the username
+ {"usernameOffset": ctypes.uint32_t},
+ // Bytes 4-11 are the date
+ {"loDateTime": ctypes.uint32_t},
+ {"hiDateTime": ctypes.uint32_t},
+ // Bytes 12-15 are not needed and not documented
+ {"foo": ctypes.uint32_t},
+ // Bytes 16-19 are the offset of the password
+ {"passwordOffset": ctypes.uint32_t},
+ // Bytes 20-31 are not needed and not documented
+ {"unknown1": ctypes.uint32_t},
+ {"unknown2": ctypes.uint32_t},
+ {"unknown3": ctypes.uint32_t}
+ ]);
+
+ let url = uri.prePath;
+ let results = [];
+ let arr = this._crypto.stringToArray(data);
+ // convert data to ctypes.unsigned_char.array(arr.length)
+ let cdata = ctypes.unsigned_char.array(arr.length)(arr);
+ // Bytes 0-35 contain the loginData data structure for all the logins sharing the same URL
+ let currentLoginData = ctypes.cast(cdata, loginData);
+ let headerSize = currentLoginData.headerSize;
+ let currentInfoIndex = loginData.size;
+ // pointer to the current login item
+ let currentLoginItemPointer = ctypes.cast(cdata.addressOfElement(currentInfoIndex),
+ loginItem.ptr);
+ // currentLoginData.dataMax is the data count: each username and password is considered as
+ // a data. So, the number of logins is the number of data dived by 2
+ let numLogins = currentLoginData.dataMax / 2;
+ for (let n = 0; n < numLogins; n++) {
+ // Bytes 0-31 starting from currentInfoIndex contain the loginItem data structure for the
+ // current login
+ let currentLoginItem = currentLoginItemPointer.contents;
+ let creation = this.ctypesKernelHelpers.
+ fileTimeToSecondsSinceEpoch(currentLoginItem.hiDateTime,
+ currentLoginItem.loDateTime) * 1000;
+ let currentResult = {
+ creation: creation,
+ url: url,
+ };
+ // The username is UTF-16 and null-terminated.
+ currentResult.username =
+ ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.usernameOffset),
+ ctypes.char16_t.ptr).readString();
+ // The password is UTF-16 and null-terminated.
+ currentResult.password =
+ ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.passwordOffset),
+ ctypes.char16_t.ptr).readString();
+ results.push(currentResult);
+ // move to the next login item
+ currentLoginItemPointer = currentLoginItemPointer.increment();
+ }
+ return results;
+ },
+};
+
+function Settings() {
+}
+
+Settings.prototype = {
+ type: MigrationUtils.resourceTypes.SETTINGS,
+
+ get exists() {
+ return true;
+ },
+
+ migrate: function S_migrate(aCallback) {
+ // Converts from yes/no to a boolean.
+ let yesNoToBoolean = v => v == "yes";
+
+ // Converts source format like "en-us,ar-kw;q=0.7,ar-om;q=0.3" into
+ // destination format like "en-us, ar-kw, ar-om".
+ // Final string is sorted by quality (q=) param.
+ function parseAcceptLanguageList(v) {
+ return v.match(/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/gi)
+ .sort(function(a, b) {
+ let qA = parseFloat(a.split(";q=")[1]) || 1.0;
+ let qB = parseFloat(b.split(";q=")[1]) || 1.0;
+ return qB - qA;
+ })
+ .map(a => a.split(";")[0]);
+ }
+
+ // For reference on some of the available IE Registry settings:
+ // * http://msdn.microsoft.com/en-us/library/cc980058%28v=prot.13%29.aspx
+ // * http://msdn.microsoft.com/en-us/library/cc980059%28v=prot.13%29.aspx
+
+ // Note that only settings exposed in our UI should be migrated.
+
+ this._set("Software\\Microsoft\\Internet Explorer\\International",
+ "AcceptLanguage",
+ "intl.accept_languages",
+ parseAcceptLanguageList);
+ // TODO (bug 745853): For now, only x-western font is translated.
+ this._set("Software\\Microsoft\\Internet Explorer\\International\\Scripts\\3",
+ "IEFixedFontName",
+ "font.name.monospace.x-western");
+ this._set(kMainKey,
+ "Use FormSuggest",
+ "browser.formfill.enable",
+ yesNoToBoolean);
+ this._set(kMainKey,
+ "FormSuggest Passwords",
+ "signon.rememberSignons",
+ yesNoToBoolean);
+ this._set(kMainKey,
+ "Anchor Underline",
+ "browser.underline_anchors",
+ yesNoToBoolean);
+ this._set(kMainKey,
+ "Display Inline Images",
+ "permissions.default.image",
+ v => yesNoToBoolean(v) ? 1 : 2);
+ this._set(kMainKey,
+ "Move System Caret",
+ "accessibility.browsewithcaret",
+ yesNoToBoolean);
+ this._set("Software\\Microsoft\\Internet Explorer\\Settings",
+ "Always Use My Colors",
+ "browser.display.document_color_use",
+ v => (!v ? 0 : 2));
+ this._set("Software\\Microsoft\\Internet Explorer\\Settings",
+ "Always Use My Font Face",
+ "browser.display.use_document_fonts",
+ v => !v);
+ this._set(kMainKey,
+ "SmoothScroll",
+ "general.smoothScroll",
+ Boolean);
+ this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
+ "WarnOnClose",
+ "browser.tabs.warnOnClose",
+ Boolean);
+ this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
+ "OpenInForeground",
+ "browser.tabs.loadInBackground",
+ v => !v);
+
+ aCallback(true);
+ },
+
+ /**
+ * Reads a setting from the Registry and stores the converted result into
+ * the appropriate Firefox preference.
+ *
+ * @param aPath
+ * Registry path under HKCU.
+ * @param aKey
+ * Name of the key.
+ * @param aPref
+ * Firefox preference.
+ * @param [optional] aTransformFn
+ * Conversion function from the Registry format to the pref format.
+ */
+ _set: function S__set(aPath, aKey, aPref, aTransformFn) {
+ let value = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ aPath, aKey);
+ // Don't import settings that have never been flipped.
+ if (value === undefined)
+ return;
+
+ if (aTransformFn)
+ value = aTransformFn(value);
+
+ switch (typeof value) {
+ case "string":
+ Services.prefs.setCharPref(aPref, value);
+ break;
+ case "number":
+ Services.prefs.setIntPref(aPref, value);
+ break;
+ case "boolean":
+ Services.prefs.setBoolPref(aPref, value);
+ break;
+ default:
+ throw new Error("Unexpected value type: " + (typeof value));
+ }
+ }
+};
+
+function IEProfileMigrator()
+{
+ this.wrappedJSObject = this; // export this to be able to use it in the unittest.
+}
+
+IEProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+IEProfileMigrator.prototype.getResources = function IE_getResources() {
+ let resources = [
+ MSMigrationUtils.getBookmarksMigrator(),
+ new History(),
+ MSMigrationUtils.getCookiesMigrator(),
+ new Settings(),
+ ];
+ // Only support the form password migrator for Windows XP to 7.
+ if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
+ resources.push(new IE7FormPasswords());
+ }
+ let windowsVaultFormPasswordsMigrator =
+ MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+ windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
+ resources.push(windowsVaultFormPasswordsMigrator);
+ return resources.filter(r => r.exists);
+};
+
+IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
+ let datePromises = ["Favs", "CookD"].map(dirId => {
+ let {path} = Services.dirsvc.get(dirId, Ci.nsIFile);
+ return OS.File.stat(path).catch(() => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ datePromises.push(new Promise(resolve => {
+ let typedURLs = new Map();
+ try {
+ typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
+ } catch (ex) {}
+ let dates = [0, ... typedURLs.values()];
+ resolve(Math.max.apply(Math, dates));
+ }));
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
+Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
+ get: function IE_get_sourceHomePageURL() {
+ let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+ kMainKey, "Default_Page_URL");
+ let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ kMainKey, "Start Page");
+ // If the user didn't customize the Start Page, he is still on the default
+ // page, that may be considered the equivalent of our about:home. There's
+ // no reason to retain it, since it is heavily targeted to IE.
+ let homepage = startPage != defaultStartPage ? startPage : "";
+
+ // IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
+ // are in addition to the Start Page, and no empty entries are possible,
+ // thus a Start Page is always defined if any of these exists, though it
+ // may be the default one.
+ let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ kMainKey, "Secondary Start Pages");
+ if (secondaryPages) {
+ if (homepage)
+ secondaryPages.unshift(homepage);
+ homepage = secondaryPages.join("|");
+ }
+
+ return homepage;
+ }
+});
+
+IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
+IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";
+IEProfileMigrator.prototype.classID = Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IEProfileMigrator]);
diff --git a/browser/components/migration/MSMigrationUtils.jsm b/browser/components/migration/MSMigrationUtils.jsm
new file mode 100644
index 000000000..1e0250b06
--- /dev/null
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -0,0 +1,889 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["MSMigrationUtils"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm");
+
+Cu.importGlobalProperties(["FileReader"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+ "resource://gre/modules/ctypes.jsm");
+
+const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
+const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
+const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
+const FREE_CLOSE_FAILED = 0;
+const INTERNET_EXPLORER_EDGE_GUID = [0x3CCD5499,
+ 0x4B1087A8,
+ 0x886015A2,
+ 0x553BDD88];
+const RESULT_SUCCESS = 0;
+const VAULT_ENUMERATE_ALL_ITEMS = 512;
+const WEB_CREDENTIALS_VAULT_ID = [0x4BF4C442,
+ 0x41A09B8A,
+ 0x4ADD80B3,
+ 0x28DB4D70];
+
+Cu.importGlobalProperties(["File"]);
+
+const wintypes = {
+ BOOL: ctypes.int,
+ DWORD: ctypes.uint32_t,
+ DWORDLONG: ctypes.uint64_t,
+ CHAR: ctypes.char,
+ PCHAR: ctypes.char.ptr,
+ LPCWSTR: ctypes.char16_t.ptr,
+ PDWORD: ctypes.uint32_t.ptr,
+ VOIDP: ctypes.voidptr_t,
+ WORD: ctypes.uint16_t,
+};
+
+// TODO: Bug 1202978 - Refactor MSMigrationUtils ctypes helpers
+function CtypesKernelHelpers() {
+ this._structs = {};
+ this._functions = {};
+ this._libs = {};
+
+ this._structs.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
+ {wYear: wintypes.WORD},
+ {wMonth: wintypes.WORD},
+ {wDayOfWeek: wintypes.WORD},
+ {wDay: wintypes.WORD},
+ {wHour: wintypes.WORD},
+ {wMinute: wintypes.WORD},
+ {wSecond: wintypes.WORD},
+ {wMilliseconds: wintypes.WORD}
+ ]);
+
+ this._structs.FILETIME = new ctypes.StructType("FILETIME", [
+ {dwLowDateTime: wintypes.DWORD},
+ {dwHighDateTime: wintypes.DWORD}
+ ]);
+
+ try {
+ this._libs.kernel32 = ctypes.open("Kernel32");
+
+ this._functions.FileTimeToSystemTime =
+ this._libs.kernel32.declare("FileTimeToSystemTime",
+ ctypes.default_abi,
+ wintypes.BOOL,
+ this._structs.FILETIME.ptr,
+ this._structs.SYSTEMTIME.ptr);
+ } catch (ex) {
+ this.finalize();
+ }
+}
+
+CtypesKernelHelpers.prototype = {
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize() {
+ this._structs = {};
+ this._functions = {};
+ for (let key in this._libs) {
+ let lib = this._libs[key];
+ try {
+ lib.close();
+ } catch (ex) {}
+ }
+ this._libs = {};
+ },
+
+ /**
+ * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct,
+ * and then deduces the number of seconds since the epoch (which
+ * is the data we want for the cookie expiry date).
+ *
+ * @param aTimeHi
+ * Least significant DWORD.
+ * @param aTimeLo
+ * Most significant DWORD.
+ * @return the number of seconds since the epoch
+ */
+ fileTimeToSecondsSinceEpoch(aTimeHi, aTimeLo) {
+ let fileTime = this._structs.FILETIME();
+ fileTime.dwLowDateTime = aTimeLo;
+ fileTime.dwHighDateTime = aTimeHi;
+ let systemTime = this._structs.SYSTEMTIME();
+ let result = this._functions.FileTimeToSystemTime(fileTime.address(),
+ systemTime.address());
+ if (result == 0)
+ throw new Error(ctypes.winLastError);
+
+ // System time is in UTC, so we use Date.UTC to get milliseconds from epoch,
+ // then divide by 1000 to get seconds, and round down:
+ return Math.floor(Date.UTC(systemTime.wYear,
+ systemTime.wMonth - 1,
+ systemTime.wDay,
+ systemTime.wHour,
+ systemTime.wMinute,
+ systemTime.wSecond,
+ systemTime.wMilliseconds) / 1000);
+ }
+};
+
+function CtypesVaultHelpers() {
+ this._structs = {};
+ this._functions = {};
+
+ this._structs.GUID = new ctypes.StructType("GUID", [
+ {id: wintypes.DWORD.array(4)},
+ ]);
+
+ this._structs.VAULT_ITEM_ELEMENT = new ctypes.StructType("VAULT_ITEM_ELEMENT", [
+ // not documented
+ {schemaElementId: wintypes.DWORD},
+ // not documented
+ {unknown1: wintypes.DWORD},
+ // vault type
+ {type: wintypes.DWORD},
+ // not documented
+ {unknown2: wintypes.DWORD},
+ // value of the item
+ {itemValue: wintypes.LPCWSTR},
+ // not documented
+ {unknown3: wintypes.CHAR.array(12)},
+ ]);
+
+ this._structs.VAULT_ELEMENT = new ctypes.StructType("VAULT_ELEMENT", [
+ // vault item schemaId
+ {schemaId: this._structs.GUID},
+ // a pointer to the name of the browser VAULT_ITEM_ELEMENT
+ {pszCredentialFriendlyName: wintypes.LPCWSTR},
+ // a pointer to the url VAULT_ITEM_ELEMENT
+ {pResourceElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // a pointer to the username VAULT_ITEM_ELEMENT
+ {pIdentityElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // not documented
+ {pAuthenticatorElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // not documented
+ {pPackageSid: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ // time stamp in local format
+ {lowLastModified: wintypes.DWORD},
+ {highLastModified: wintypes.DWORD},
+ // not documented
+ {flags: wintypes.DWORD},
+ // not documented
+ {dwPropertiesCount: wintypes.DWORD},
+ // not documented
+ {pPropertyElements: this._structs.VAULT_ITEM_ELEMENT.ptr},
+ ]);
+
+ try {
+ this._vaultcliLib = ctypes.open("vaultcli.dll");
+
+ this._functions.VaultOpenVault =
+ this._vaultcliLib.declare("VaultOpenVault",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // GUID
+ this._structs.GUID.ptr,
+ // Flags
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP.ptr);
+ this._functions.VaultEnumerateItems =
+ this._vaultcliLib.declare("VaultEnumerateItems",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP,
+ // Flags
+ wintypes.DWORD,
+ // Items Count
+ wintypes.PDWORD,
+ // Items
+ ctypes.voidptr_t);
+ this._functions.VaultCloseVault =
+ this._vaultcliLib.declare("VaultCloseVault",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP);
+ this._functions.VaultGetItem =
+ this._vaultcliLib.declare("VaultGetItem",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP,
+ // Schema Id
+ this._structs.GUID.ptr,
+ // Resource
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // Identity
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // Package Sid
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // HWND Owner
+ wintypes.DWORD,
+ // Flags
+ wintypes.DWORD,
+ // Items
+ this._structs.VAULT_ELEMENT.ptr.ptr);
+ this._functions.VaultFree =
+ this._vaultcliLib.declare("VaultFree",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Memory
+ this._structs.VAULT_ELEMENT.ptr);
+ } catch (ex) {
+ this.finalize();
+ }
+}
+
+CtypesVaultHelpers.prototype = {
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize() {
+ this._structs = {};
+ this._functions = {};
+ try {
+ this._vaultcliLib.close();
+ } catch (ex) {}
+ this._vaultcliLib = null;
+ }
+};
+
+/**
+ * Checks whether an host is an IP (v4 or v6) address.
+ *
+ * @param aHost
+ * The host to check.
+ * @return whether aHost is an IP address.
+ */
+function hostIsIPAddress(aHost) {
+ try {
+ Services.eTLD.getBaseDomainFromHost(aHost);
+ } catch (e) {
+ return e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS;
+ }
+ return false;
+}
+
+var gEdgeDir;
+function getEdgeLocalDataFolder() {
+ if (gEdgeDir) {
+ return gEdgeDir.clone();
+ }
+ let packages = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
+ packages.append("Packages");
+ let edgeDir = packages.clone();
+ edgeDir.append("Microsoft.MicrosoftEdge_8wekyb3d8bbwe");
+ try {
+ if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
+ gEdgeDir = edgeDir;
+ return edgeDir.clone();
+ }
+
+ // Let's try the long way:
+ let dirEntries = packages.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ let subDir = dirEntries.getNext();
+ subDir.QueryInterface(Ci.nsIFile);
+ if (subDir.leafName.startsWith("Microsoft.MicrosoftEdge") && subDir.isReadable() &&
+ subDir.isDirectory()) {
+ gEdgeDir = subDir;
+ return subDir.clone();
+ }
+ }
+ } catch (ex) {
+ Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
+ }
+ return null;
+}
+
+
+function Bookmarks(migrationType) {
+ this._migrationType = migrationType;
+}
+
+Bookmarks.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get exists() {
+ return !!this._favoritesFolder;
+ },
+
+ get importedAppLabel() {
+ return this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE ? "IE" : "Edge";
+ },
+
+ __favoritesFolder: null,
+ get _favoritesFolder() {
+ if (!this.__favoritesFolder) {
+ if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+ let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
+ if (favoritesFolder.exists() && favoritesFolder.isReadable()) {
+ this.__favoritesFolder = favoritesFolder;
+ }
+ } else if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) {
+ let edgeDir = getEdgeLocalDataFolder();
+ if (edgeDir) {
+ edgeDir.appendRelativePath(EDGE_FAVORITES);
+ if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
+ this.__favoritesFolder = edgeDir;
+ }
+ }
+ }
+ }
+ return this.__favoritesFolder;
+ },
+
+ __toolbarFolderName: null,
+ get _toolbarFolderName() {
+ if (!this.__toolbarFolderName) {
+ if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+ // Retrieve the name of IE's favorites subfolder that holds the bookmarks
+ // in the toolbar. This was previously stored in the registry and changed
+ // in IE7 to always be called "Links".
+ let folderName = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Internet Explorer\\Toolbar",
+ "LinksFolderName");
+ this.__toolbarFolderName = folderName || "Links";
+ } else {
+ this.__toolbarFolderName = "Links";
+ }
+ }
+ return this.__toolbarFolderName;
+ },
+
+ migrate: function B_migrate(aCallback) {
+ return Task.spawn(function* () {
+ // Import to the bookmarks menu.
+ let folderGuid = PlacesUtils.bookmarks.menuGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ folderGuid =
+ yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
+ }
+ yield this._migrateFolder(this._favoritesFolder, folderGuid);
+ }.bind(this)).then(() => aCallback(true),
+ e => { Cu.reportError(e); aCallback(false) });
+ },
+
+ _migrateFolder: Task.async(function* (aSourceFolder, aDestFolderGuid) {
+ // TODO (bug 741993): the favorites order is stored in the Registry, at
+ // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
+ // for IE, and in a similar location for Edge.
+ // Until we support it, bookmarks are imported in alphabetical order.
+ let entries = aSourceFolder.directoryEntries;
+ let succeeded = true;
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+ try {
+ // Make sure that entry.path == entry.target to not follow .lnk folder
+ // shortcuts which could lead to infinite cycles.
+ // Don't use isSymlink(), since it would throw for invalid
+ // lnk files pointing to URLs or to unresolvable paths.
+ if (entry.path == entry.target && entry.isDirectory()) {
+ let folderGuid;
+ if (entry.leafName == this._toolbarFolderName &&
+ entry.parent.equals(this._favoritesFolder)) {
+ // Import to the bookmarks toolbar.
+ folderGuid = PlacesUtils.bookmarks.toolbarGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ folderGuid =
+ yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
+ }
+ }
+ else {
+ // Import to a new folder.
+ folderGuid = (yield MigrationUtils.insertBookmarkWrapper({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: aDestFolderGuid,
+ title: entry.leafName
+ })).guid;
+ }
+
+ if (entry.isReadable()) {
+ // Recursively import the folder.
+ yield this._migrateFolder(entry, folderGuid);
+ }
+ }
+ else {
+ // Strip the .url extension, to both check this is a valid link file,
+ // and get the associated title.
+ let matches = entry.leafName.match(/(.+)\.url$/i);
+ if (matches) {
+ let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
+ getService(Ci.nsIFileProtocolHandler);
+ let uri = fileHandler.readURLFile(entry);
+ let title = matches[1];
+
+ yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid: aDestFolderGuid, url: uri, title
+ });
+ }
+ }
+ } catch (ex) {
+ Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex);
+ succeeded = false;
+ }
+ }
+ if (!succeeded) {
+ throw new Error("Failed to import all bookmarks correctly.");
+ }
+ }),
+
+};
+
+function Cookies(migrationType) {
+ this._migrationType = migrationType;
+}
+
+Cookies.prototype = {
+ type: MigrationUtils.resourceTypes.COOKIES,
+
+ get exists() {
+ if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+ return !!this._cookiesFolder;
+ }
+ return !!this._cookiesFolders;
+ },
+
+ __cookiesFolder: null,
+ get _cookiesFolder() {
+ // Edge stores cookies in a number of places, and this shouldn't get called:
+ if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_IE) {
+ throw new Error("Shouldn't be looking for a single cookie folder unless we're migrating IE");
+ }
+
+ // Cookies are stored in txt files, in a Cookies folder whose path varies
+ // across the different OS versions. CookD takes care of most of these
+ // cases, though, in Windows Vista/7, UAC makes a difference.
+ // If UAC is enabled, the most common destination is CookD/Low. Though,
+ // if the user runs the application in administrator mode or disables UAC,
+ // cookies are stored in the original CookD destination. Cause running the
+ // browser in administrator mode is unsafe and discouraged, we just care
+ // about the UAC state.
+ if (!this.__cookiesFolder) {
+ let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
+ if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
+ // Check if UAC is enabled.
+ if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
+ cookiesFolder.append("Low");
+ }
+ this.__cookiesFolder = cookiesFolder;
+ }
+ }
+ return this.__cookiesFolder;
+ },
+
+ __cookiesFolders: null,
+ get _cookiesFolders() {
+ if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_EDGE) {
+ throw new Error("Shouldn't be looking for multiple cookie folders unless we're migrating Edge");
+ }
+
+ let folders = [];
+ let edgeDir = getEdgeLocalDataFolder();
+ if (edgeDir) {
+ edgeDir.append("AC");
+ for (let path of EDGE_COOKIE_PATH_OPTIONS) {
+ let folder = edgeDir.clone();
+ let fullPath = path + EDGE_COOKIES_SUFFIX;
+ folder.appendRelativePath(fullPath);
+ if (folder.exists() && folder.isReadable() && folder.isDirectory()) {
+ folders.push(folder);
+ }
+ }
+ }
+ this.__cookiesFolders = folders.length ? folders : null;
+ return this.__cookiesFolders;
+ },
+
+ migrate(aCallback) {
+ this.ctypesKernelHelpers = new CtypesKernelHelpers();
+
+ let cookiesGenerator = (function* genCookie() {
+ let success = false;
+ let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
+ this.__cookiesFolders : [this.__cookiesFolder];
+ for (let folder of folders) {
+ let entries = folder.directoryEntries;
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+ // Skip eventual bogus entries.
+ if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
+ continue;
+
+ this._readCookieFile(entry, function(aSuccess) {
+ // Importing even a single cookie file is considered a success.
+ if (aSuccess)
+ success = true;
+ try {
+ cookiesGenerator.next();
+ } catch (ex) {}
+ });
+
+ yield undefined;
+ }
+ }
+
+ this.ctypesKernelHelpers.finalize();
+
+ aCallback(success);
+ }).apply(this);
+ cookiesGenerator.next();
+ },
+
+ _readCookieFile(aFile, aCallback) {
+ let fileReader = new FileReader();
+ let onLoadEnd = () => {
+ fileReader.removeEventListener("loadend", onLoadEnd, false);
+
+ if (fileReader.readyState != fileReader.DONE) {
+ Cu.reportError("Could not read cookie contents: " + fileReader.error);
+ aCallback(false);
+ return;
+ }
+
+ let success = true;
+ try {
+ this._parseCookieBuffer(fileReader.result);
+ } catch (ex) {
+ Components.utils.reportError("Unable to migrate cookie: " + ex);
+ success = false;
+ } finally {
+ aCallback(success);
+ }
+ };
+ fileReader.addEventListener("loadend", onLoadEnd, false);
+ fileReader.readAsText(File.createFromNsIFile(aFile));
+ },
+
+ /**
+ * Parses a cookie file buffer and returns an array of the contained cookies.
+ *
+ * The cookie file format is a newline-separated-values with a "*" used as
+ * delimeter between multiple records.
+ * Each cookie has the following fields:
+ * - name
+ * - value
+ * - host/path
+ * - flags
+ * - Expiration time most significant integer
+ * - Expiration time least significant integer
+ * - Creation time most significant integer
+ * - Creation time least significant integer
+ * - Record delimiter "*"
+ *
+ * Unfortunately, "*" can also occur inside the value of the cookie, so we
+ * can't rely exclusively on it as a record separator.
+ *
+ * @note All the times are in FILETIME format.
+ */
+ _parseCookieBuffer(aTextBuffer) {
+ // Note the last record is an empty string...
+ let records = [];
+ let lines = aTextBuffer.split("\n");
+ while (lines.length > 0) {
+ let record = lines.splice(0, 9);
+ // ... which means this is going to be a 1-element array for that record
+ if (record.length > 1) {
+ records.push(record);
+ }
+ }
+ for (let record of records) {
+ let [name, value, hostpath, flags,
+ expireTimeLo, expireTimeHi] = record;
+
+ // IE stores deleted cookies with a zero-length value, skip them.
+ if (value.length == 0)
+ continue;
+
+ // IE sometimes has cookies created by apps that use "~~local~~/local/file/path"
+ // as the hostpath, ignore those:
+ if (hostpath.startsWith("~~local~~"))
+ continue;
+
+ let hostLen = hostpath.indexOf("/");
+ let host = hostpath.substr(0, hostLen);
+ let path = hostpath.substr(hostLen);
+
+ // For a non-null domain, assume it's what Mozilla considers
+ // a domain cookie. See bug 222343.
+ if (host.length > 0) {
+ // Fist delete any possible extant matching host cookie.
+ Services.cookies.remove(host, name, path, false, {});
+ // Now make it a domain cookie.
+ if (host[0] != "." && !hostIsIPAddress(host))
+ host = "." + host;
+ }
+
+ // Fallback: expire in 1h (NB: time is in seconds since epoch, so we have
+ // to divide the result of Date.now() (which is in milliseconds) by 1000).
+ let expireTime = Math.floor(Date.now() / 1000) + 3600;
+ try {
+ expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+ Number(expireTimeLo));
+ } catch (ex) {
+ Cu.reportError("Failed to get expiry time for cookie for " + host);
+ }
+
+ Services.cookies.add(host,
+ path,
+ name,
+ value,
+ Number(flags) & 0x1, // secure
+ false, // httpOnly
+ false, // session
+ expireTime,
+ {});
+ }
+ }
+};
+
+function getTypedURLs(registryKeyPath) {
+ // The list of typed URLs is a sort of annotation stored in the registry.
+ // The number of entries stored is not UI-configurable, but has changed
+ // between different Windows versions. We just keep reading up to the first
+ // non-existing entry to support different limits / states of the registry.
+ let typedURLs = new Map();
+ let typedURLKey = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ let typedURLTimeKey = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ let cTypes = new CtypesKernelHelpers();
+ try {
+ typedURLKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ registryKeyPath + "\\TypedURLs",
+ Ci.nsIWindowsRegKey.ACCESS_READ);
+ try {
+ typedURLTimeKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ registryKeyPath + "\\TypedURLsTime",
+ Ci.nsIWindowsRegKey.ACCESS_READ);
+ } catch (ex) {
+ typedURLTimeKey = null;
+ }
+ let entryName;
+ for (let entry = 1; typedURLKey.hasValue((entryName = "url" + entry)); entry++) {
+ let url = typedURLKey.readStringValue(entryName);
+ let timeTyped = 0;
+ if (typedURLTimeKey && typedURLTimeKey.hasValue(entryName)) {
+ let urlTime = "";
+ try {
+ urlTime = typedURLTimeKey.readBinaryValue(entryName);
+ } catch (ex) {
+ Cu.reportError("Couldn't read url time for " + entryName);
+ }
+ if (urlTime.length == 8) {
+ let urlTimeHex = [];
+ for (let i = 0; i < 8; i++) {
+ let c = urlTime.charCodeAt(i).toString(16);
+ if (c.length == 1)
+ c = "0" + c;
+ urlTimeHex.unshift(c);
+ }
+ try {
+ let hi = parseInt(urlTimeHex.slice(0, 4).join(""), 16);
+ let lo = parseInt(urlTimeHex.slice(4, 8).join(""), 16);
+ // Convert to seconds since epoch:
+ timeTyped = cTypes.fileTimeToSecondsSinceEpoch(hi, lo);
+ // Callers expect PRTime, which is microseconds since epoch:
+ timeTyped *= 1000 * 1000;
+ } catch (ex) {
+ // Ignore conversion exceptions. Callers will have to deal
+ // with the fallback value (0).
+ }
+ }
+ }
+ typedURLs.set(url, timeTyped);
+ }
+ } catch (ex) {
+ Cu.reportError("Error reading typed URL history: " + ex);
+ } finally {
+ if (typedURLKey) {
+ typedURLKey.close();
+ }
+ if (typedURLTimeKey) {
+ typedURLTimeKey.close();
+ }
+ cTypes.finalize();
+ }
+ return typedURLs;
+}
+
+
+// Migrator for form passwords on Windows 8 and higher.
+function WindowsVaultFormPasswords () {
+}
+
+WindowsVaultFormPasswords.prototype = {
+ type: MigrationUtils.resourceTypes.PASSWORDS,
+
+ get exists() {
+ // work only on windows 8+
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ // check if there are passwords available for migration.
+ return this.migrate(() => {}, true);
+ }
+ return false;
+ },
+
+ /**
+ * If aOnlyCheckExists is false, import the form passwords on Windows 8 and higher from the vault
+ * and then call the aCallback.
+ * Otherwise, check if there are passwords in the vault.
+ * @param {function} aCallback - a callback called when the migration is done.
+ * @param {boolean} [aOnlyCheckExists=false] - if aOnlyCheckExists is true, just check if there are some
+ * passwords to migrate. Import the passwords from the vault and call aCallback otherwise.
+ * @return true if there are passwords in the vault and aOnlyCheckExists is set to true,
+ * false if there is no password in the vault and aOnlyCheckExists is set to true, undefined if
+ * aOnlyCheckExists is set to false.
+ */
+ migrate(aCallback, aOnlyCheckExists = false) {
+ // check if the vault item is an IE/Edge one
+ function _isIEOrEdgePassword(id) {
+ return id[0] == INTERNET_EXPLORER_EDGE_GUID[0] &&
+ id[1] == INTERNET_EXPLORER_EDGE_GUID[1] &&
+ id[2] == INTERNET_EXPLORER_EDGE_GUID[2] &&
+ id[3] == INTERNET_EXPLORER_EDGE_GUID[3];
+ }
+
+ let ctypesVaultHelpers = new CtypesVaultHelpers();
+ let ctypesKernelHelpers = new CtypesKernelHelpers();
+ let migrationSucceeded = true;
+ let successfulVaultOpen = false;
+ let error, vault;
+ try {
+ // web credentials vault id
+ let vaultGuid = new ctypesVaultHelpers._structs.GUID(WEB_CREDENTIALS_VAULT_ID);
+ error = new wintypes.DWORD();
+ // web credentials vault
+ vault = new wintypes.VOIDP();
+ // open the current vault using the vaultGuid
+ error = ctypesVaultHelpers._functions.VaultOpenVault(vaultGuid.address(), 0, vault.address());
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to open Vault: " + error);
+ }
+ successfulVaultOpen = true;
+
+ let item = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr();
+ let itemCount = new wintypes.DWORD();
+ // enumerate all the available items. This api is going to return a table of all the
+ // available items and item is going to point to the first element of this table.
+ error = ctypesVaultHelpers._functions.VaultEnumerateItems(vault, VAULT_ENUMERATE_ALL_ITEMS,
+ itemCount.address(),
+ item.address());
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to enumerate Vault items: " + error);
+ }
+ for (let j = 0; j < itemCount.value; j++) {
+ try {
+ // if it's not an ie/edge password, skip it
+ if (!_isIEOrEdgePassword(item.contents.schemaId.id)) {
+ continue;
+ }
+ let url = item.contents.pResourceElement.contents.itemValue.readString();
+ let realURL;
+ try {
+ realURL = Services.io.newURI(url, null, null);
+ } catch (ex) { /* leave realURL as null */ }
+ if (!realURL || ["http", "https", "ftp"].indexOf(realURL.scheme) == -1) {
+ // Ignore items for non-URLs or URLs that aren't HTTP(S)/FTP
+ continue;
+ }
+
+ // if aOnlyCheckExists is set to true, the purpose of the call is to return true if there is at
+ // least a password which is true in this case because a password was by now already found
+ if (aOnlyCheckExists) {
+ return true;
+ }
+ let username = item.contents.pIdentityElement.contents.itemValue.readString();
+ // the current login credential object
+ let credential = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr();
+ error = ctypesVaultHelpers._functions.VaultGetItem(vault,
+ item.contents.schemaId.address(),
+ item.contents.pResourceElement,
+ item.contents.pIdentityElement, null,
+ 0, 0, credential.address());
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to get item: " + error);
+ }
+
+ let password = credential.contents.pAuthenticatorElement.contents.itemValue.readString();
+ let creation = Date.now();
+ try {
+ // login manager wants time in milliseconds since epoch, so convert
+ // to seconds since epoch and multiply to get milliseconds:
+ creation = ctypesKernelHelpers.
+ fileTimeToSecondsSinceEpoch(item.contents.highLastModified,
+ item.contents.lowLastModified) * 1000;
+ } catch (ex) {
+ // Ignore exceptions in the dates and just create the login for right now.
+ }
+ // create a new login
+ let login = {
+ username, password,
+ hostname: realURL.prePath,
+ timeCreated: creation,
+ };
+ MigrationUtils.insertLoginWrapper(login);
+
+ // close current item
+ error = ctypesVaultHelpers._functions.VaultFree(credential);
+ if (error == FREE_CLOSE_FAILED) {
+ throw new Error("Unable to free item: " + error);
+ }
+ } catch (e) {
+ migrationSucceeded = false;
+ Cu.reportError(e);
+ } finally {
+ // move to next item in the table returned by VaultEnumerateItems
+ item = item.increment();
+ }
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ migrationSucceeded = false;
+ } finally {
+ if (successfulVaultOpen) {
+ // close current vault
+ error = ctypesVaultHelpers._functions.VaultCloseVault(vault);
+ if (error == FREE_CLOSE_FAILED) {
+ Cu.reportError("Unable to close vault: " + error);
+ }
+ }
+ ctypesKernelHelpers.finalize();
+ ctypesVaultHelpers.finalize();
+ aCallback(migrationSucceeded);
+ }
+ if (aOnlyCheckExists) {
+ return false;
+ }
+ return undefined;
+ }
+};
+
+var MSMigrationUtils = {
+ MIGRATION_TYPE_IE: 1,
+ MIGRATION_TYPE_EDGE: 2,
+ CtypesKernelHelpers: CtypesKernelHelpers,
+ getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
+ return new Bookmarks(migrationType);
+ },
+ getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
+ return new Cookies(migrationType);
+ },
+ getWindowsVaultFormPasswordsMigrator() {
+ return new WindowsVaultFormPasswords();
+ },
+ getTypedURLs,
+ getEdgeLocalDataFolder,
+};
diff --git a/browser/components/migration/MigrationUtils.jsm b/browser/components/migration/MigrationUtils.jsm
new file mode 100644
index 000000000..104efe005
--- /dev/null
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -0,0 +1,1117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["MigrationUtils", "MigratorPrototype"];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const TOPIC_WILL_IMPORT_BOOKMARKS = "initial-migration-will-import-default-bookmarks";
+const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmarks";
+const TOPIC_PLACES_DEFAULTS_FINISHED = "places-browser-init-complete";
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+Cu.importGlobalProperties(["URL"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
+ "resource:///modules/AutoMigrate.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
+ "resource://gre/modules/BookmarkHTMLUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ResponsivenessMonitor",
+ "resource://gre/modules/ResponsivenessMonitor.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+ "resource://gre/modules/TelemetryStopwatch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+
+var gMigrators = null;
+var gProfileStartup = null;
+var gMigrationBundle = null;
+var gPreviousDefaultBrowserKey = "";
+
+let gKeepUndoData = false;
+let gUndoData = null;
+
+XPCOMUtils.defineLazyGetter(this, "gAvailableMigratorKeys", function() {
+ if (AppConstants.platform == "win") {
+ return [
+ "firefox", "edge", "ie", "chrome", "chromium", "360se",
+ "canary"
+ ];
+ }
+ if (AppConstants.platform == "macosx") {
+ return ["firefox", "safari", "chrome", "chromium", "canary"];
+ }
+ if (AppConstants.XP_UNIX) {
+ return ["firefox", "chrome", "chromium"];
+ }
+ return [];
+});
+
+function getMigrationBundle() {
+ if (!gMigrationBundle) {
+ gMigrationBundle = Services.strings.createBundle(
+ "chrome://browser/locale/migration/migration.properties");
+ }
+ return gMigrationBundle;
+}
+
+/**
+ * Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
+ *
+ * To implement a migrator:
+ * 1. Import this module.
+ * 2. Create the prototype for the migrator, extending MigratorPrototype.
+ * Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
+ * 3. Set classDescription, contractID and classID for your migrator, and set
+ * NSGetFactory appropriately.
+ * 4. If the migrator supports multiple profiles, override the sourceProfiles
+ * Here we default for single-profile migrator.
+ * 5. Implement getResources(aProfile) (see below).
+ * 6. If the migrator supports reading the home page of the source browser,
+ * override |sourceHomePageURL| getter.
+ * 7. For startup-only migrators, override |startupOnlyMigrator|.
+ */
+this.MigratorPrototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
+
+ /**
+ * OVERRIDE IF AND ONLY IF the source supports multiple profiles.
+ *
+ * Returns array of profile objects from which data may be imported. The object
+ * should have the following keys:
+ * id - a unique string identifier for the profile
+ * name - a pretty name to display to the user in the UI
+ *
+ * Only profiles from which data can be imported should be listed. Otherwise
+ * the behavior of the migration wizard isn't well-defined.
+ *
+ * For a single-profile source (e.g. safari, ie), this returns null,
+ * and not an empty array. That is the default implementation.
+ */
+ get sourceProfiles() {
+ return null;
+ },
+
+ /**
+ * MUST BE OVERRIDDEN.
+ *
+ * Returns an array of "migration resources" objects for the given profile,
+ * or for the "default" profile, if the migrator does not support multiple
+ * profiles.
+ *
+ * Each migration resource should provide:
+ * - a |type| getter, returning any of the migration types (see
+ * nsIBrowserProfileMigrator).
+ *
+ * - a |migrate| method, taking a single argument, aCallback(bool success),
+ * for migrating the data for this resource. It may do its job
+ * synchronously or asynchronously. Either way, it must call
+ * aCallback(bool aSuccess) when it's done. In the case of an exception
+ * thrown from |migrate|, it's taken as if aCallback(false) is called.
+ *
+ * Note: In the case of a simple asynchronous implementation, you may find
+ * MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
+ *
+ * For each migration type listed in nsIBrowserProfileMigrator, multiple
+ * migration resources may be provided. This practice is useful when the
+ * data for a certain migration type is independently stored in few
+ * locations. For example, the mac version of Safari stores its "reading list"
+ * bookmarks in a separate property list.
+ *
+ * Note that the importation of a particular migration type is reported as
+ * successful if _any_ of its resources succeeded to import (that is, called,
+ * |aCallback(true)|). However, completion-status for a particular migration
+ * type is reported to the UI only once all of its migrators have called
+ * aCallback.
+ *
+ * @note The returned array should only include resources from which data
+ * can be imported. So, for example, before adding a resource for the
+ * BOOKMARKS migration type, you should check if you should check that the
+ * bookmarks file exists.
+ *
+ * @param aProfile
+ * The profile from which data may be imported, or an empty string
+ * in the case of a single-profile migrator.
+ * In the case of multiple-profiles migrator, it is guaranteed that
+ * aProfile is a value returned by the sourceProfiles getter (see
+ * above).
+ */
+ getResources: function MP_getResources(/* aProfile */) {
+ throw new Error("getResources must be overridden");
+ },
+
+ /**
+ * OVERRIDE in order to provide an estimate of when the last time was
+ * that somebody used the browser. It is OK that this is somewhat fuzzy -
+ * history may not be available (or be wiped or not present due to e.g.
+ * incognito mode).
+ *
+ * @return a Promise that resolves to the last used date.
+ *
+ * @note If not overridden, the promise will resolve to the unix epoch.
+ */
+ getLastUsedDate() {
+ return Promise.resolve(new Date(0));
+ },
+
+ /**
+ * OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
+ * that is just the Firefox migrator, see bug 737381). Default: false.
+ *
+ * Startup-only migrators are different in two ways:
+ * - they may only be used during startup.
+ * - the user-profile is half baked during migration. The folder exists,
+ * but it's only accessible through MigrationUtils.profileStartup.
+ * The migrator can call MigrationUtils.profileStartup.doStartup
+ * at any point in order to initialize the profile.
+ */
+ get startupOnlyMigrator() {
+ return false;
+ },
+
+ /**
+ * OVERRIDE IF AND ONLY IF your migrator supports importing the homepage.
+ * @see nsIBrowserProfileMigrator
+ */
+ get sourceHomePageURL() {
+ return "";
+ },
+
+ /**
+ * Override if the data to migrate is locked/in-use and the user should
+ * probably shutdown the source browser.
+ */
+ get sourceLocked() {
+ return false;
+ },
+
+ /**
+ * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
+ * getResources.
+ *
+ * @see nsIBrowserProfileMigrator
+ */
+ getMigrateData: function MP_getMigrateData(aProfile) {
+ let resources = this._getMaybeCachedResources(aProfile);
+ if (!resources) {
+ return [];
+ }
+ let types = resources.map(r => r.type);
+ return types.reduce((a, b) => { a |= b; return a }, 0);
+ },
+
+ getBrowserKey: function MP_getBrowserKey() {
+ return this.contractID.match(/\=([^\=]+)$/)[1];
+ },
+
+ /**
+ * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
+ * migrate for each resource.
+ *
+ * @see nsIBrowserProfileMigrator
+ */
+ migrate: function MP_migrate(aItems, aStartup, aProfile) {
+ let resources = this._getMaybeCachedResources(aProfile);
+ if (resources.length == 0)
+ throw new Error("migrate called for a non-existent source");
+
+ if (aItems != Ci.nsIBrowserProfileMigrator.ALL)
+ resources = resources.filter(r => aItems & r.type);
+
+ // Used to periodically give back control to the main-thread loop.
+ let unblockMainThread = function() {
+ return new Promise(resolve => {
+ Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ };
+
+ let getHistogramIdForResourceType = (resourceType, template) => {
+ if (resourceType == MigrationUtils.resourceTypes.HISTORY) {
+ return template.replace("*", "HISTORY");
+ }
+ if (resourceType == MigrationUtils.resourceTypes.BOOKMARKS) {
+ return template.replace("*", "BOOKMARKS");
+ }
+ if (resourceType == MigrationUtils.resourceTypes.PASSWORDS) {
+ return template.replace("*", "LOGINS");
+ }
+ return null;
+ };
+
+ let browserKey = this.getBrowserKey();
+
+ let maybeStartTelemetryStopwatch = resourceType => {
+ let histogramId = getHistogramIdForResourceType(resourceType, "FX_MIGRATION_*_IMPORT_MS");
+ if (histogramId) {
+ TelemetryStopwatch.startKeyed(histogramId, browserKey);
+ }
+ return histogramId;
+ };
+
+ let maybeStartResponsivenessMonitor = resourceType => {
+ let responsivenessMonitor;
+ let responsivenessHistogramId =
+ getHistogramIdForResourceType(resourceType, "FX_MIGRATION_*_JANK_MS");
+ if (responsivenessHistogramId) {
+ responsivenessMonitor = new ResponsivenessMonitor();
+ }
+ return {responsivenessMonitor, responsivenessHistogramId};
+ };
+
+ let maybeFinishResponsivenessMonitor = (responsivenessMonitor, histogramId) => {
+ if (responsivenessMonitor) {
+ let accumulatedDelay = responsivenessMonitor.finish();
+ if (histogramId) {
+ try {
+ Services.telemetry.getKeyedHistogramById(histogramId)
+ .add(browserKey, accumulatedDelay);
+ } catch (ex) {
+ Cu.reportError(histogramId + ": " + ex);
+ }
+ }
+ }
+ };
+
+ let collectQuantityTelemetry = () => {
+ for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
+ let histogramId =
+ "FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
+ try {
+ Services.telemetry.getKeyedHistogramById(histogramId)
+ .add(browserKey, MigrationUtils._importQuantities[resourceType]);
+ } catch (ex) {
+ Cu.reportError(histogramId + ": " + ex);
+ }
+ }
+ };
+
+ // Called either directly or through the bookmarks import callback.
+ let doMigrate = Task.async(function*() {
+ let resourcesGroupedByItems = new Map();
+ resources.forEach(function(resource) {
+ if (!resourcesGroupedByItems.has(resource.type)) {
+ resourcesGroupedByItems.set(resource.type, new Set());
+ }
+ resourcesGroupedByItems.get(resource.type).add(resource);
+ });
+
+ if (resourcesGroupedByItems.size == 0)
+ throw new Error("No items to import");
+
+ let notify = function(aMsg, aItemType) {
+ Services.obs.notifyObservers(null, aMsg, aItemType);
+ };
+
+ for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
+ MigrationUtils._importQuantities[resourceType] = 0;
+ }
+ notify("Migration:Started");
+ for (let [migrationType, itemResources] of resourcesGroupedByItems) {
+ notify("Migration:ItemBeforeMigrate", migrationType);
+
+ let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType);
+
+ let {responsivenessMonitor, responsivenessHistogramId} =
+ maybeStartResponsivenessMonitor(migrationType);
+
+ let itemSuccess = false;
+ for (let res of itemResources) {
+ let completeDeferred = PromiseUtils.defer();
+ let resourceDone = function(aSuccess) {
+ itemResources.delete(res);
+ itemSuccess |= aSuccess;
+ if (itemResources.size == 0) {
+ notify(itemSuccess ?
+ "Migration:ItemAfterMigrate" : "Migration:ItemError",
+ migrationType);
+ resourcesGroupedByItems.delete(migrationType);
+
+ if (stopwatchHistogramId) {
+ TelemetryStopwatch.finishKeyed(stopwatchHistogramId, browserKey);
+ }
+
+ maybeFinishResponsivenessMonitor(responsivenessMonitor, responsivenessHistogramId);
+
+ if (resourcesGroupedByItems.size == 0) {
+ collectQuantityTelemetry();
+ notify("Migration:Ended");
+ }
+ }
+ completeDeferred.resolve();
+ };
+
+ // If migrate throws, an error occurred, and the callback
+ // (itemMayBeDone) might haven't been called.
+ try {
+ res.migrate(resourceDone);
+ } catch (ex) {
+ Cu.reportError(ex);
+ resourceDone(false);
+ }
+
+ // Certain resources must be ran sequentially or they could fail,
+ // for example bookmarks and history (See bug 1272652).
+ if (migrationType == MigrationUtils.resourceTypes.BOOKMARKS ||
+ migrationType == MigrationUtils.resourceTypes.HISTORY) {
+ yield completeDeferred.promise;
+ }
+
+ yield unblockMainThread();
+ }
+ }
+ });
+
+ if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) {
+ MigrationUtils.profileStartup.doStartup();
+ // First import the default bookmarks.
+ // Note: We do not need to do so for the Firefox migrator
+ // (=startupOnlyMigrator), as it just copies over the places database
+ // from another profile.
+ Task.spawn(function* () {
+ // Tell nsBrowserGlue we're importing default bookmarks.
+ let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].
+ getService(Ci.nsIObserver);
+ browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
+
+ // Import the default bookmarks. We ignore whether or not we succeed.
+ yield BookmarkHTMLUtils.importFromURL(
+ "chrome://browser/locale/bookmarks.html", true).catch(r => r);
+
+ // We'll tell nsBrowserGlue we've imported bookmarks, but before that
+ // we need to make sure we're going to know when it's finished
+ // initializing places:
+ let placesInitedPromise = new Promise(resolve => {
+ let onPlacesInited = function() {
+ Services.obs.removeObserver(onPlacesInited, TOPIC_PLACES_DEFAULTS_FINISHED);
+ resolve();
+ };
+ Services.obs.addObserver(onPlacesInited, TOPIC_PLACES_DEFAULTS_FINISHED, false);
+ });
+ browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
+ yield placesInitedPromise;
+ doMigrate();
+ });
+ return;
+ }
+ doMigrate();
+ },
+
+ /**
+ * DO NOT OVERRIDE - After deCOMing migration, this code
+ * won't be part of the migrator itself.
+ *
+ * @see nsIBrowserProfileMigrator
+ */
+ get sourceExists() {
+ if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
+ return false;
+
+ // For a single-profile source, check if any data is available.
+ // For multiple-profiles source, make sure that at least one
+ // profile is available.
+ let exists = false;
+ try {
+ let profiles = this.sourceProfiles;
+ if (!profiles) {
+ let resources = this._getMaybeCachedResources("");
+ if (resources && resources.length > 0)
+ exists = true;
+ }
+ else {
+ exists = profiles.length > 0;
+ }
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ }
+ return exists;
+ },
+
+ /** * PRIVATE STUFF - DO NOT OVERRIDE ***/
+ _getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
+ let profileKey = aProfile ? aProfile.id : "";
+ if (this._resourcesByProfile) {
+ if (profileKey in this._resourcesByProfile)
+ return this._resourcesByProfile[profileKey];
+ }
+ else {
+ this._resourcesByProfile = { };
+ }
+ this._resourcesByProfile[profileKey] = this.getResources(aProfile);
+ return this._resourcesByProfile[profileKey];
+ }
+};
+
+this.MigrationUtils = Object.freeze({
+ resourceTypes: {
+ SETTINGS: Ci.nsIBrowserProfileMigrator.SETTINGS,
+ COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
+ HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,
+ FORMDATA: Ci.nsIBrowserProfileMigrator.FORMDATA,
+ PASSWORDS: Ci.nsIBrowserProfileMigrator.PASSWORDS,
+ BOOKMARKS: Ci.nsIBrowserProfileMigrator.BOOKMARKS,
+ OTHERDATA: Ci.nsIBrowserProfileMigrator.OTHERDATA,
+ SESSION: Ci.nsIBrowserProfileMigrator.SESSION,
+ },
+
+ /**
+ * Helper for implementing simple asynchronous cases of migration resources'
+ * |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method
+ * just waits for some file to be read, for example, and then migrates
+ * everything right away, you can wrap the async-function with this helper
+ * and not worry about notifying the callback.
+ *
+ * For example, instead of writing:
+ * setTimeout(function() {
+ * try {
+ * ....
+ * aCallback(true);
+ * }
+ * catch() {
+ * aCallback(false);
+ * }
+ * }, 0);
+ *
+ * You may write:
+ * setTimeout(MigrationUtils.wrapMigrateFunction(function() {
+ * if (importingFromMosaic)
+ * throw Cr.NS_ERROR_UNEXPECTED;
+ * }, aCallback), 0);
+ *
+ * ... and aCallback will be called with aSuccess=false when importing
+ * from Mosaic, or with aSuccess=true otherwise.
+ *
+ * @param aFunction
+ * the function that will be called sometime later. If aFunction
+ * throws when it's called, aCallback(false) is called, otherwise
+ * aCallback(true) is called.
+ * @param aCallback
+ * the callback function passed to |migrate|.
+ * @return the wrapped function.
+ */
+ wrapMigrateFunction: function MU_wrapMigrateFunction(aFunction, aCallback) {
+ return function() {
+ let success = false;
+ try {
+ aFunction.apply(null, arguments);
+ success = true;
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ }
+ // Do not change this to call aCallback directly in try try & catch
+ // blocks, because if aCallback throws, we may end up calling aCallback
+ // twice.
+ aCallback(success);
+ };
+ },
+
+ /**
+ * Gets a string from the migration bundle. Shorthand for
+ * nsIStringBundle.GetStringFromName, if aReplacements isn't passed, or for
+ * nsIStringBundle.formatStringFromName if it is.
+ *
+ * This method also takes care of "bumped" keys (See bug 737381 comment 8 for
+ * details).
+ *
+ * @param aKey
+ * The key of the string to retrieve.
+ * @param aReplacements
+ * [optioanl] Array of replacements to run on the retrieved string.
+ * @return the retrieved string.
+ *
+ * @see nsIStringBundle
+ */
+ getLocalizedString: function MU_getLocalizedString(aKey, aReplacements) {
+ aKey = aKey.replace(/_(canary|chromium)$/, "_chrome");
+
+ const OVERRIDES = {
+ "4_firefox": "4_firefox_history_and_bookmarks",
+ "64_firefox": "64_firefox_other"
+ };
+ aKey = OVERRIDES[aKey] || aKey;
+
+ if (aReplacements === undefined)
+ return getMigrationBundle().GetStringFromName(aKey);
+ return getMigrationBundle().formatStringFromName(
+ aKey, aReplacements, aReplacements.length);
+ },
+
+ _getLocalePropertyForBrowser(browserId) {
+ switch (browserId) {
+ case "edge":
+ return "sourceNameEdge";
+ case "ie":
+ return "sourceNameIE";
+ case "safari":
+ return "sourceNameSafari";
+ case "canary":
+ return "sourceNameCanary";
+ case "chrome":
+ return "sourceNameChrome";
+ case "chromium":
+ return "sourceNameChromium";
+ case "firefox":
+ return "sourceNameFirefox";
+ case "360se":
+ return "sourceName360se";
+ }
+ return null;
+ },
+
+ getBrowserName(browserId) {
+ let prop = this._getLocalePropertyForBrowser(browserId);
+ if (prop) {
+ return this.getLocalizedString(prop);
+ }
+ return null;
+ },
+
+ /**
+ * Helper for creating a folder for imported bookmarks from a particular
+ * migration source. The folder is created at the end of the given folder.
+ *
+ * @param sourceNameStr
+ * the source name (first letter capitalized). This is used
+ * for reading the localized source name from the migration
+ * bundle (e.g. if aSourceNameStr is Mosaic, this will try to read
+ * sourceNameMosaic from the migration bundle).
+ * @param parentGuid
+ * the GUID of the folder in which the new folder should be created.
+ * @return the GUID of the new folder.
+ */
+ createImportedBookmarksFolder: Task.async(function* (sourceNameStr, parentGuid) {
+ let source = this.getLocalizedString("sourceName" + sourceNameStr);
+ let title = this.getLocalizedString("importedBookmarksFolder", [source]);
+ return (yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title
+ })).guid;
+ }),
+
+ /**
+ * Get all the rows corresponding to a select query from a database, without
+ * requiring a lock on the database. If fetching data fails (because someone
+ * else tried to write to the DB at the same time, for example), we will
+ * retry the fetch after a 100ms timeout, up to 10 times.
+ *
+ * @param path
+ * the file path to the database we want to open.
+ * @param description
+ * a developer-readable string identifying what kind of database we're
+ * trying to open.
+ * @param selectQuery
+ * the SELECT query to use to fetch the rows.
+ *
+ * @return a promise that resolves to an array of rows. The promise will be
+ * rejected if the read/fetch failed even after retrying.
+ */
+ getRowsFromDBWithoutLocks(path, description, selectQuery) {
+ let dbOptions = {
+ readOnly: true,
+ ignoreLockingMode: true,
+ path,
+ };
+
+ const RETRYLIMIT = 10;
+ const RETRYINTERVAL = 100;
+ return Task.spawn(function* innerGetRows() {
+ let rows = null;
+ for (let retryCount = RETRYLIMIT; retryCount && !rows; retryCount--) {
+ // Attempt to get the rows. If this succeeds, we will bail out of the loop,
+ // close the database in a failsafe way, and pass the rows back.
+ // If fetching the rows throws, we will wait RETRYINTERVAL ms
+ // and try again. This will repeat a maximum of RETRYLIMIT times.
+ let db;
+ let didOpen = false;
+ let exceptionSeen;
+ try {
+ db = yield Sqlite.openConnection(dbOptions);
+ didOpen = true;
+ rows = yield db.execute(selectQuery);
+ } catch (ex) {
+ if (!exceptionSeen) {
+ Cu.reportError(ex);
+ }
+ exceptionSeen = ex;
+ } finally {
+ try {
+ if (didOpen) {
+ yield db.close();
+ }
+ } catch (ex) {}
+ }
+ if (exceptionSeen) {
+ yield new Promise(resolve => setTimeout(resolve, RETRYINTERVAL));
+ }
+ }
+ if (!rows) {
+ throw new Error("Couldn't get rows from the " + description + " database.");
+ }
+ return rows;
+ });
+ },
+
+ get _migrators() {
+ if (!gMigrators) {
+ gMigrators = new Map();
+ }
+ return gMigrators;
+ },
+
+ /*
+ * Returns the migrator for the given source, if any data is available
+ * for this source, or null otherwise.
+ *
+ * @param aKey internal name of the migration source.
+ * Supported values: ie (windows),
+ * edge (windows),
+ * safari (mac),
+ * canary (mac/windows),
+ * chrome (mac/windows/linux),
+ * chromium (mac/windows/linux),
+ * 360se (windows),
+ * firefox.
+ *
+ * If null is returned, either no data can be imported
+ * for the given migrator, or aMigratorKey is invalid (e.g. ie on mac,
+ * or mosaic everywhere). This method should be used rather than direct
+ * getService for future compatibility (see bug 718280).
+ *
+ * @return profile migrator implementing nsIBrowserProfileMigrator, if it can
+ * import any data, null otherwise.
+ */
+ getMigrator: function MU_getMigrator(aKey) {
+ let migrator = null;
+ if (this._migrators.has(aKey)) {
+ migrator = this._migrators.get(aKey);
+ }
+ else {
+ try {
+ migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" +
+ aKey].createInstance(Ci.nsIBrowserProfileMigrator);
+ }
+ catch (ex) { Cu.reportError(ex) }
+ this._migrators.set(aKey, migrator);
+ }
+
+ try {
+ return migrator && migrator.sourceExists ? migrator : null;
+ } catch (ex) { Cu.reportError(ex); return null }
+ },
+
+ /**
+ * Figure out what is the default browser, and if there is a migrator
+ * for it, return that migrator's internal name.
+ * For the time being, the "internal name" of a migrator is its contract-id
+ * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
+ * but it will soon be exposed properly.
+ */
+ getMigratorKeyForDefaultBrowser() {
+ // Canary uses the same description as Chrome so we can't distinguish them.
+ const APP_DESC_TO_KEY = {
+ "Internet Explorer": "ie",
+ "Microsoft Edge": "edge",
+ "Safari": "safari",
+ "Firefox": "firefox",
+ "Nightly": "firefox",
+ "Google Chrome": "chrome", // Windows, Linux
+ "Chrome": "chrome", // OS X
+ "Chromium": "chromium", // Windows, OS X
+ "Chromium Web Browser": "chromium", // Linux
+ "360\u5b89\u5168\u6d4f\u89c8\u5668": "360se",
+ };
+
+ let key = "";
+ try {
+ let browserDesc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService)
+ .getApplicationDescription("http");
+ key = APP_DESC_TO_KEY[browserDesc] || "";
+ // Handle devedition, as well as "FirefoxNightly" on OS X.
+ if (!key && browserDesc.startsWith("Firefox")) {
+ key = "firefox";
+ }
+ }
+ catch (ex) {
+ Cu.reportError("Could not detect default browser: " + ex);
+ }
+
+ // "firefox" is the least useful entry here, and might just be because we've set
+ // ourselves as the default (on Windows 7 and below). In that case, check if we
+ // have a registry key that tells us where to go:
+ if (key == "firefox" && AppConstants.isPlatformAndVersionAtMost("win", "6.2")) {
+ // Because we remove the registry key, reading the registry key only works once.
+ // We save the value for subsequent calls to avoid hard-to-trace bugs when multiple
+ // consumers ask for this key.
+ if (gPreviousDefaultBrowserKey) {
+ key = gPreviousDefaultBrowserKey;
+ } else {
+ // We didn't have a saved value, so check the registry.
+ const kRegPath = "Software\\Mozilla\\Firefox";
+ let oldDefault = WindowsRegistry.readRegKey(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kRegPath, "OldDefaultBrowserCommand");
+ if (oldDefault) {
+ // Remove the key:
+ WindowsRegistry.removeRegKey(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kRegPath, "OldDefaultBrowserCommand");
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin);
+ file.initWithCommandLine(oldDefault);
+ key = APP_DESC_TO_KEY[file.getVersionInfoField("FileDescription")] || key;
+ // Save the value for future callers.
+ gPreviousDefaultBrowserKey = key;
+ } catch (ex) {
+ Cu.reportError("Could not convert old default browser value to description.");
+ }
+ }
+ }
+ }
+ return key;
+ },
+
+ // Whether or not we're in the process of startup migration
+ get isStartupMigration() {
+ return gProfileStartup != null;
+ },
+
+ /**
+ * In the case of startup migration, this is set to the nsIProfileStartup
+ * instance passed to ProfileMigrator's migrate.
+ *
+ * @see showMigrationWizard
+ */
+ get profileStartup() {
+ return gProfileStartup;
+ },
+
+ /**
+ * Show the migration wizard. On mac, this may just focus the wizard if it's
+ * already running, in which case aOpener and aParams are ignored.
+ *
+ * @param {Window} [aOpener]
+ * optional; the window that asks to open the wizard.
+ * @param {Array} [aParams]
+ * optional arguments for the migration wizard, in the form of an array
+ * This is passed as-is for the params argument of
+ * nsIWindowWatcher.openWindow. The array elements we expect are, in
+ * order:
+ * - {Number} migration entry point constant (see below)
+ * - {String} source browser identifier
+ * - {nsIBrowserProfileMigrator} actual migrator object
+ * - {Boolean} whether this is a startup migration
+ * - {Boolean} whether to skip the 'source' page
+ * - {String} an identifier for the profile to use when migrating
+ * NB: If you add new consumers, please add a migration entry point
+ * constant below, and specify at least the first element of the array
+ * (the migration entry point for purposes of telemetry).
+ */
+ showMigrationWizard:
+ function MU_showMigrationWizard(aOpener, aParams) {
+ let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no";
+ if (AppConstants.platform == "macosx" && !this.isStartupMigration) {
+ let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
+ if (win) {
+ win.focus();
+ return;
+ }
+ // On mac, the migration wiazrd should only be modal in the case of
+ // startup-migration.
+ features = "centerscreen,chrome,resizable=no";
+ }
+
+ // nsIWindowWatcher doesn't deal with raw arrays, so we convert the input
+ let params;
+ if (Array.isArray(aParams)) {
+ params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ for (let item of aParams) {
+ let comtaminatedVal;
+ if (item && item instanceof Ci.nsISupports) {
+ comtaminatedVal = item;
+ } else {
+ switch (typeof item) {
+ case "boolean":
+ comtaminatedVal = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ comtaminatedVal.data = item;
+ break;
+ case "number":
+ comtaminatedVal = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ comtaminatedVal.data = item;
+ break;
+ case "string":
+ comtaminatedVal = Cc["@mozilla.org/supports-cstring;1"].
+ createInstance(Ci.nsISupportsCString);
+ comtaminatedVal.data = item;
+ break;
+
+ case "undefined":
+ case "object":
+ if (!item) {
+ comtaminatedVal = null;
+ break;
+ }
+ /* intentionally falling through to error out here for
+ non-null/undefined things: */
+ default:
+ throw new Error("Unexpected parameter type " + (typeof item) + ": " + item);
+ }
+ }
+ params.appendElement(comtaminatedVal, false);
+ }
+ } else {
+ params = aParams;
+ }
+
+ Services.ww.openWindow(aOpener,
+ "chrome://browser/content/migration/migration.xul",
+ "_blank",
+ features,
+ params);
+ },
+
+ /**
+ * Show the migration wizard for startup-migration. This should only be
+ * called by ProfileMigrator (see ProfileMigrator.js), which implements
+ * nsIProfileMigrator.
+ *
+ * @param aProfileStartup
+ * the nsIProfileStartup instance provided to ProfileMigrator.migrate.
+ * @param [optional] aMigratorKey
+ * If set, the migration wizard will import from the corresponding
+ * migrator, bypassing the source-selection page. Otherwise, the
+ * source-selection page will be displayed, either with the default
+ * browser selected, if it could be detected and if there is a
+ * migrator for it, or with the first option selected as a fallback
+ * (The first option is hardcoded to be the most common browser for
+ * the OS we run on. See migration.xul).
+ * @param [optional] aProfileToMigrate
+ * If set, the migration wizard will import from the profile indicated.
+ * @throws if aMigratorKey is invalid or if it points to a non-existent
+ * source.
+ */
+ startupMigration:
+ function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
+ if (!aProfileStartup) {
+ throw new Error("an profile-startup instance is required for startup-migration");
+ }
+ gProfileStartup = aProfileStartup;
+
+ let skipSourcePage = false, migrator = null, migratorKey = "";
+ if (aMigratorKey) {
+ migrator = this.getMigrator(aMigratorKey);
+ if (!migrator) {
+ // aMigratorKey must point to a valid source, so, if it doesn't
+ // cleanup and throw.
+ this.finishMigration();
+ throw new Error("startMigration was asked to open auto-migrate from " +
+ "a non-existent source: " + aMigratorKey);
+ }
+ migratorKey = aMigratorKey;
+ skipSourcePage = true;
+ }
+ else {
+ let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser();
+ if (defaultBrowserKey) {
+ migrator = this.getMigrator(defaultBrowserKey);
+ if (migrator)
+ migratorKey = defaultBrowserKey;
+ }
+ }
+
+ if (!migrator) {
+ // If there's no migrator set so far, ensure that there is at least one
+ // migrator available before opening the wizard.
+ // Note that we don't need to check the default browser first, because
+ // if that one existed we would have used it in the block above this one.
+ if (!gAvailableMigratorKeys.some(key => !!this.getMigrator(key))) {
+ // None of the keys produced a usable migrator, so finish up here:
+ this.finishMigration();
+ return;
+ }
+ }
+
+ let isRefresh = migrator && skipSourcePage &&
+ migratorKey == AppConstants.MOZ_APP_NAME;
+
+ if (!isRefresh && AutoMigrate.enabled) {
+ try {
+ AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
+ return;
+ } catch (ex) {
+ // If automigration failed, continue and show the dialog.
+ Cu.reportError(ex);
+ }
+ }
+
+ let migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FIRSTRUN;
+ if (isRefresh) {
+ migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FXREFRESH;
+ }
+
+ let params = [
+ migrationEntryPoint,
+ migratorKey,
+ migrator,
+ aProfileStartup,
+ skipSourcePage,
+ aProfileToMigrate,
+ ];
+ this.showMigrationWizard(null, params);
+ },
+
+ _importQuantities: {
+ bookmarks: 0,
+ logins: 0,
+ history: 0,
+ },
+
+ insertBookmarkWrapper(bookmark) {
+ this._importQuantities.bookmarks++;
+ let insertionPromise = PlacesUtils.bookmarks.insert(bookmark);
+ if (!gKeepUndoData) {
+ return insertionPromise;
+ }
+ // If we keep undo data, add a promise handler that stores the undo data once
+ // the bookmark has been inserted in the DB, and then returns the bookmark.
+ let {parentGuid} = bookmark;
+ return insertionPromise.then(bm => {
+ let {guid, lastModified, type} = bm;
+ gUndoData.get("bookmarks").push({
+ parentGuid, guid, lastModified, type
+ });
+ return bm;
+ });
+ },
+
+ insertVisitsWrapper(places, options) {
+ this._importQuantities.history += places.length;
+ if (gKeepUndoData) {
+ this._updateHistoryUndo(places);
+ }
+ return PlacesUtils.asyncHistory.updatePlaces(places, options);
+ },
+
+ insertLoginWrapper(login) {
+ this._importQuantities.logins++;
+ let insertedLogin = LoginHelper.maybeImportLogin(login);
+ // Note that this means that if we import a login that has a newer password
+ // than we know about, we will update the login, and an undo of the import
+ // will not revert this. This seems preferable over removing the login
+ // outright or storing the old password in the undo file.
+ if (insertedLogin && gKeepUndoData) {
+ let {guid, timePasswordChanged} = insertedLogin;
+ gUndoData.get("logins").push({guid, timePasswordChanged});
+ }
+ },
+
+ initializeUndoData() {
+ gKeepUndoData = true;
+ gUndoData = new Map([["bookmarks", []], ["visits", []], ["logins", []]]);
+ },
+
+ _postProcessUndoData: Task.async(function*(state) {
+ if (!state) {
+ return state;
+ }
+ let bookmarkFolders = state.get("bookmarks").filter(b => b.type == PlacesUtils.bookmarks.TYPE_FOLDER);
+
+ let bookmarkFolderData = [];
+ let bmPromises = bookmarkFolders.map(({guid}) => {
+ // Ignore bookmarks where the promise doesn't resolve (ie that are missing)
+ // Also check that the bookmark fetch returns isn't null before adding it.
+ return PlacesUtils.bookmarks.fetch(guid).then(bm => bm && bookmarkFolderData.push(bm), () => {});
+ });
+
+ yield Promise.all(bmPromises);
+ let folderLMMap = new Map(bookmarkFolderData.map(b => [b.guid, b.lastModified]));
+ for (let bookmark of bookmarkFolders) {
+ let lastModified = folderLMMap.get(bookmark.guid);
+ // If the bookmark was deleted, the map will be returning null, so check:
+ if (lastModified) {
+ bookmark.lastModified = lastModified;
+ }
+ }
+ return state;
+ }),
+
+ stopAndRetrieveUndoData() {
+ let undoData = gUndoData;
+ gUndoData = null;
+ gKeepUndoData = false;
+ return this._postProcessUndoData(undoData);
+ },
+
+ _updateHistoryUndo(places) {
+ let visits = gUndoData.get("visits");
+ let visitMap = new Map(visits.map(v => [v.url, v]));
+ for (let place of places) {
+ let visitCount = place.visits.length;
+ let first = Math.min.apply(Math, place.visits.map(v => v.visitDate));
+ let last = Math.max.apply(Math, place.visits.map(v => v.visitDate));
+ let url = place.uri.spec;
+ try {
+ new URL(url);
+ } catch (ex) {
+ // This won't save and we won't need to 'undo' it, so ignore this URL.
+ continue;
+ }
+ if (!visitMap.has(url)) {
+ visitMap.set(url, {url, visitCount, first, last});
+ } else {
+ let currentData = visitMap.get(url);
+ currentData.visitCount += visitCount;
+ currentData.first = Math.min(currentData.first, first);
+ currentData.last = Math.max(currentData.last, last);
+ }
+ }
+ gUndoData.set("visits", Array.from(visitMap.values()));
+ },
+
+ /**
+ * Cleans up references to migrators and nsIProfileInstance instances.
+ */
+ finishMigration: function MU_finishMigration() {
+ gMigrators = null;
+ gProfileStartup = null;
+ gMigrationBundle = null;
+ },
+
+ gAvailableMigratorKeys,
+
+ MIGRATION_ENTRYPOINT_UNKNOWN: 0,
+ MIGRATION_ENTRYPOINT_FIRSTRUN: 1,
+ MIGRATION_ENTRYPOINT_FXREFRESH: 2,
+ MIGRATION_ENTRYPOINT_PLACES: 3,
+ MIGRATION_ENTRYPOINT_PASSWORDS: 4,
+
+ _sourceNameToIdMapping: {
+ "nothing": 1,
+ "firefox": 2,
+ "edge": 3,
+ "ie": 4,
+ "chrome": 5,
+ "chromium": 6,
+ "canary": 7,
+ "safari": 8,
+ "360se": 9,
+ },
+ getSourceIdForTelemetry(sourceName) {
+ return this._sourceNameToIdMapping[sourceName] || 0;
+ },
+});
diff --git a/browser/components/migration/ProfileMigrator.js b/browser/components/migration/ProfileMigrator.js
new file mode 100644
index 000000000..f67823bae
--- /dev/null
+++ b/browser/components/migration/ProfileMigrator.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource:///modules/MigrationUtils.jsm");
+
+function ProfileMigrator() {
+}
+
+ProfileMigrator.prototype = {
+ migrate: MigrationUtils.startupMigration.bind(MigrationUtils),
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIProfileMigrator]),
+ classDescription: "Profile Migrator",
+ contractID: "@mozilla.org/toolkit/profile-migrator;1",
+ classID: Components.ID("6F8BB968-C14F-4D6F-9733-6C6737B35DCE")
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ProfileMigrator]);
diff --git a/browser/components/migration/SafariProfileMigrator.js b/browser/components/migration/SafariProfileMigrator.js
new file mode 100644
index 000000000..6a2dbfcb1
--- /dev/null
+++ b/browser/components/migration/SafariProfileMigrator.js
@@ -0,0 +1,650 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PropertyListUtils",
+ "resource://gre/modules/PropertyListUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+function Bookmarks(aBookmarksFile) {
+ this._file = aBookmarksFile;
+}
+Bookmarks.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ migrate: function B_migrate(aCallback) {
+ return Task.spawn(function* () {
+ let dict = yield new Promise(resolve =>
+ PropertyListUtils.read(this._file, resolve)
+ );
+ if (!dict)
+ throw new Error("Could not read Bookmarks.plist");
+ let children = dict.get("Children");
+ if (!children)
+ throw new Error("Invalid Bookmarks.plist format");
+
+ let collection = dict.get("Title") == "com.apple.ReadingList" ?
+ this.READING_LIST_COLLECTION : this.ROOT_COLLECTION;
+ yield this._migrateCollection(children, collection);
+ }.bind(this)).then(() => aCallback(true),
+ e => { Cu.reportError(e); aCallback(false) });
+ },
+
+ // Bookmarks collections in Safari. Constants for migrateCollection.
+ ROOT_COLLECTION: 0,
+ MENU_COLLECTION: 1,
+ TOOLBAR_COLLECTION: 2,
+ READING_LIST_COLLECTION: 3,
+
+ /**
+ * Recursively migrate a Safari collection of bookmarks.
+ *
+ * @param aEntries
+ * the collection's children
+ * @param aCollection
+ * one of the values above.
+ */
+ _migrateCollection: Task.async(function* (aEntries, aCollection) {
+ // A collection of bookmarks in Safari resembles places roots. In the
+ // property list files (Bookmarks.plist, ReadingList.plist) they are
+ // stored as regular bookmarks folders, and thus can only be distinguished
+ // from by their names and places in the hierarchy.
+
+ let entriesFiltered = [];
+ if (aCollection == this.ROOT_COLLECTION) {
+ for (let entry of aEntries) {
+ let type = entry.get("WebBookmarkType");
+ if (type == "WebBookmarkTypeList" && entry.has("Children")) {
+ let title = entry.get("Title");
+ let children = entry.get("Children");
+ if (title == "BookmarksBar")
+ yield this._migrateCollection(children, this.TOOLBAR_COLLECTION);
+ else if (title == "BookmarksMenu")
+ yield this._migrateCollection(children, this.MENU_COLLECTION);
+ else if (title == "com.apple.ReadingList")
+ yield this._migrateCollection(children, this.READING_LIST_COLLECTION);
+ else if (entry.get("ShouldOmitFromUI") !== true)
+ entriesFiltered.push(entry);
+ }
+ else if (type == "WebBookmarkTypeLeaf") {
+ entriesFiltered.push(entry);
+ }
+ }
+ }
+ else {
+ entriesFiltered = aEntries;
+ }
+
+ if (entriesFiltered.length == 0)
+ return;
+
+ let folderGuid = -1;
+ switch (aCollection) {
+ case this.ROOT_COLLECTION: {
+ // In Safari, it is possible (though quite cumbersome) to move
+ // bookmarks to the bookmarks root, which is the parent folder of
+ // all bookmarks "collections". That is somewhat in parallel with
+ // both the places root and the unfiled-bookmarks root.
+ // Because the former is only an implementation detail in our UI,
+ // the unfiled root seems to be the best choice.
+ folderGuid = PlacesUtils.bookmarks.unfiledGuid;
+ break;
+ }
+ case this.MENU_COLLECTION: {
+ folderGuid = PlacesUtils.bookmarks.menuGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ folderGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Safari", folderGuid);
+ }
+ break;
+ }
+ case this.TOOLBAR_COLLECTION: {
+ folderGuid = PlacesUtils.bookmarks.toolbarGuid;
+ if (!MigrationUtils.isStartupMigration) {
+ folderGuid =
+ yield MigrationUtils.createImportedBookmarksFolder("Safari", folderGuid);
+ }
+ break;
+ }
+ case this.READING_LIST_COLLECTION: {
+ // Reading list items are imported as regular bookmarks.
+ // They are imported under their own folder, created either under the
+ // bookmarks menu (in the case of startup migration).
+ folderGuid = (yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: MigrationUtils.getLocalizedString("importedSafariReadingList"),
+ })).guid;
+ break;
+ }
+ default:
+ throw new Error("Unexpected value for aCollection!");
+ }
+ if (folderGuid == -1)
+ throw new Error("Invalid folder GUID");
+
+ yield this._migrateEntries(entriesFiltered, folderGuid);
+ }),
+
+ // migrate the given array of safari bookmarks to the given places
+ // folder.
+ _migrateEntries: Task.async(function* (entries, parentGuid) {
+ for (let entry of entries) {
+ let type = entry.get("WebBookmarkType");
+ if (type == "WebBookmarkTypeList" && entry.has("Children")) {
+ let title = entry.get("Title");
+ let newFolderGuid = (yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, title
+ })).guid;
+
+ // Empty folders may not have a children array.
+ if (entry.has("Children"))
+ yield this._migrateEntries(entry.get("Children"), newFolderGuid, false);
+ }
+ else if (type == "WebBookmarkTypeLeaf" && entry.has("URLString")) {
+ let title;
+ if (entry.has("URIDictionary"))
+ title = entry.get("URIDictionary").get("title");
+
+ try {
+ yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid, url: entry.get("URLString"), title
+ });
+ } catch (ex) {
+ Cu.reportError("Invalid Safari bookmark: " + ex);
+ }
+ }
+ }
+ })
+};
+
+function History(aHistoryFile) {
+ this._file = aHistoryFile;
+}
+History.prototype = {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ // Helper method for converting the visit date property to a PRTime value.
+ // The visit date is stored as a string, so it's not read as a Date
+ // object by PropertyListUtils.
+ _parseCocoaDate: function H___parseCocoaDate(aCocoaDateStr) {
+ let asDouble = parseFloat(aCocoaDateStr);
+ if (!isNaN(asDouble)) {
+ // reference date of NSDate.
+ let date = new Date("1 January 2001, GMT");
+ date.setMilliseconds(asDouble * 1000);
+ return date * 1000;
+ }
+ return 0;
+ },
+
+ migrate: function H_migrate(aCallback) {
+ PropertyListUtils.read(this._file, function migrateHistory(aDict) {
+ try {
+ if (!aDict)
+ throw new Error("Could not read history property list");
+ if (!aDict.has("WebHistoryDates"))
+ throw new Error("Unexpected history-property list format");
+
+ // Safari's History file contains only top-level urls. It does not
+ // distinguish between typed urls and linked urls.
+ let transType = PlacesUtils.history.TRANSITION_LINK;
+
+ let places = [];
+ let entries = aDict.get("WebHistoryDates");
+ for (let entry of entries) {
+ if (entry.has("lastVisitedDate")) {
+ let visitDate = this._parseCocoaDate(entry.get("lastVisitedDate"));
+ try {
+ places.push({ uri: NetUtil.newURI(entry.get("")),
+ title: entry.get("title"),
+ visits: [{ transitionType: transType,
+ visitDate: visitDate }] });
+ }
+ catch (ex) {
+ // Safari's History file may contain malformed URIs which
+ // will be ignored.
+ Cu.reportError(ex);
+ }
+ }
+ }
+ if (places.length > 0) {
+ MigrationUtils.insertVisitsWrapper(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ }
+ else {
+ aCallback(false);
+ }
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ aCallback(false);
+ }
+ }.bind(this));
+ }
+};
+
+/**
+ * Safari's preferences property list is independently used for three purposes:
+ * (a) importation of preferences
+ * (b) importation of search strings
+ * (c) retrieving the home page.
+ *
+ * So, rather than reading it three times, it's cached and managed here.
+ */
+function MainPreferencesPropertyList(aPreferencesFile) {
+ this._file = aPreferencesFile;
+ this._callbacks = [];
+}
+MainPreferencesPropertyList.prototype = {
+ /**
+ * @see PropertyListUtils.read
+ */
+ read: function MPPL_read(aCallback) {
+ if ("_dict" in this) {
+ aCallback(this._dict);
+ return;
+ }
+
+ let alreadyReading = this._callbacks.length > 0;
+ this._callbacks.push(aCallback);
+ if (!alreadyReading) {
+ PropertyListUtils.read(this._file, function readPrefs(aDict) {
+ this._dict = aDict;
+ for (let callback of this._callbacks) {
+ try {
+ callback(aDict);
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ this._callbacks.splice(0);
+ }.bind(this));
+ }
+ },
+
+ // Workaround for nsIBrowserProfileMigrator.sourceHomePageURL until
+ // it's replaced with an async method.
+ _readSync: function MPPL__readSync() {
+ if ("_dict" in this)
+ return this._dict;
+
+ let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ inputStream.init(this._file, -1, -1, 0);
+ let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ binaryStream.setInputStream(inputStream);
+ let bytes = binaryStream.readByteArray(inputStream.available());
+ this._dict = PropertyListUtils._readFromArrayBufferSync(
+ new Uint8Array(bytes).buffer);
+ return this._dict;
+ }
+};
+
+function Preferences(aMainPreferencesPropertyListInstance) {
+ this._mainPreferencesPropertyList = aMainPreferencesPropertyListInstance;
+}
+Preferences.prototype = {
+ type: MigrationUtils.resourceTypes.SETTINGS,
+
+ migrate: function MPR_migrate(aCallback) {
+ this._mainPreferencesPropertyList.read(aDict => {
+ Task.spawn(function* () {
+ if (!aDict)
+ throw new Error("Could not read preferences file");
+
+ this._dict = aDict;
+
+ let invert = webkitVal => !webkitVal;
+ this._set("AutoFillPasswords", "signon.rememberSignons");
+ this._set("OpenNewTabsInFront", "browser.tabs.loadInBackground", invert);
+ this._set("WebKitJavaScriptCanOpenWindowsAutomatically",
+ "dom.disable_open_during_load", invert);
+
+ // layout.spellcheckDefault is a boolean stored as a number.
+ this._set("WebContinuousSpellCheckingEnabled",
+ "layout.spellcheckDefault", Number);
+
+ // Auto-load images
+ // Firefox has an elaborate set of Image preferences. The correlation is:
+ // Mode: Safari Firefox
+ // Blocked FALSE 2
+ // Allowed TRUE 1
+ // Allowed, originating site only -- 3
+ this._set("WebKitDisplayImagesKey", "permissions.default.image",
+ webkitVal => webkitVal ? 1 : 2);
+
+ this._migrateFontSettings();
+ yield this._migrateDownloadsFolder();
+ }.bind(this)).then(() => aCallback(true), ex => {
+ Cu.reportError(ex);
+ aCallback(false);
+ }).catch(Cu.reportError);
+ });
+ },
+
+ /**
+ * Attempts to migrates a preference from Safari. Returns whether the preference
+ * has been migrated.
+ * @param aSafariKey
+ * The dictionary key for the preference of Safari.
+ * @param aMozPref
+ * The gecko/firefox preference to which aSafariKey should be migrated
+ * @param [optional] aConvertFunction(aSafariValue)
+ * a function that converts the safari-preference value to the
+ * appropriate value for aMozPref. If it's not passed, then the
+ * Safari value is set as is.
+ * If aConvertFunction returns undefined, then aMozPref is not set
+ * at all.
+ * @return whether or not aMozPref was set.
+ */
+ _set: function MPR_set(aSafariKey, aMozPref, aConvertFunction) {
+ if (this._dict.has(aSafariKey)) {
+ let safariVal = this._dict.get(aSafariKey);
+ let mozVal = aConvertFunction !== undefined ?
+ aConvertFunction(safariVal) : safariVal;
+ switch (typeof mozVal) {
+ case "string":
+ Services.prefs.setCharPref(aMozPref, mozVal);
+ break;
+ case "number":
+ Services.prefs.setIntPref(aMozPref, mozVal);
+ break;
+ case "boolean":
+ Services.prefs.setBoolPref(aMozPref, mozVal);
+ break;
+ case "undefined":
+ return false;
+ default:
+ throw new Error("Unexpected value type: " + (typeof mozVal));
+ }
+ }
+ return true;
+ },
+
+ // Fonts settings are quite problematic for migration, for a couple of
+ // reasons:
+ // (a) Every font preference in Gecko is set for a particular language.
+ // In Safari, each font preference applies to all languages.
+ // (b) The current underlying implementation of nsIFontEnumerator cannot
+ // really tell you anything about a font: no matter what language or type
+ // you try to enumerate with EnumerateFonts, you get an array of all
+ // fonts in the systems (This also breaks our fonts dialog).
+ // (c) In Gecko, each langauge has a distinct serif and sans-serif font
+ // preference. Safari has only one default font setting. It seems that
+ // it checks if it's a serif or sans serif font, and when a site
+ // explicitly asks to use serif/sans-serif font, it uses the default font
+ // only if it applies to this type.
+ // (d) The solution of guessing the lang-group out of the default charset (as
+ // done in the old Safari migrator) can only work when:
+ // (1) The default charset preference is set.
+ // (2) It's not a unicode charset.
+ // For now, we use the language implied by the system locale as the
+ // lang-group. The only exception is minimal font size, which is an
+ // accessibility preference in Safari (under the Advanced tab). If it is set,
+ // we set it for all languages.
+ // As for the font type of the default font (serif/sans-serif), the default
+ // type for the given language is used (set in font.default.LANGGROUP).
+ _migrateFontSettings: function MPR__migrateFontSettings() {
+ // If "Never use font sizes smaller than [ ] is set", migrate it for all
+ // languages.
+ if (this._dict.has("WebKitMinimumFontSize")) {
+ let minimumSize = this._dict.get("WebKitMinimumFontSize");
+ if (typeof minimumSize == "number") {
+ let prefs = Services.prefs.getChildList("font.minimum-size");
+ for (let pref of prefs) {
+ Services.prefs.setIntPref(pref, minimumSize);
+ }
+ }
+ else {
+ Cu.reportError("WebKitMinimumFontSize was set to an invalid value: " +
+ minimumSize);
+ }
+ }
+
+ // In theory, the lang group could be "x-unicode". This will result
+ // in setting the fonts for "Other Languages".
+ let lang = this._getLocaleLangGroup();
+
+ let anySet = false;
+ let fontType = Services.prefs.getCharPref("font.default." + lang);
+ anySet |= this._set("WebKitFixedFont", "font.name.monospace." + lang);
+ anySet |= this._set("WebKitDefaultFixedFontSize", "font.size.fixed." + lang);
+ anySet |= this._set("WebKitStandardFont",
+ "font.name." + fontType + "." + lang);
+ anySet |= this._set("WebKitDefaultFontSize", "font.size.variable." + lang);
+
+ // If we set font settings for a particular language, we'll also set the
+ // fonts dialog to open with the fonts settings for that langauge.
+ if (anySet)
+ Services.prefs.setCharPref("font.language.group", lang);
+ },
+
+ // Get the language group for the system locale.
+ _getLocaleLangGroup: function MPR__getLocaleLangGroup() {
+ let locale = Services.locale.getLocaleComponentForUserAgent();
+
+ // See nsLanguageAtomService::GetLanguageGroup
+ let localeLangGroup = "x-unicode";
+ let bundle = Services.strings.createBundle(
+ "resource://gre/res/langGroups.properties");
+ try {
+ localeLangGroup = bundle.GetStringFromName(locale);
+ }
+ catch (ex) {
+ let hyphenAt = locale.indexOf("-");
+ if (hyphenAt != -1) {
+ try {
+ localeLangGroup = bundle.GetStringFromName(locale.substr(0, hyphenAt));
+ }
+ catch (ex2) { }
+ }
+ }
+ return localeLangGroup;
+ },
+
+ _migrateDownloadsFolder: Task.async(function* () {
+ if (!this._dict.has("DownloadsPath"))
+ return;
+
+ let downloadsFolder = FileUtils.File(this._dict.get("DownloadsPath"));
+
+ // If the download folder is set to the Desktop or to ~/Downloads, set the
+ // folderList pref appropriately so that "Desktop"/Downloads is shown with
+ // pretty name in the preferences dialog.
+ let folderListVal = 2;
+ if (downloadsFolder.equals(FileUtils.getDir("Desk", []))) {
+ folderListVal = 0;
+ }
+ else {
+ let systemDownloadsPath = yield Downloads.getSystemDownloadsDirectory();
+ let systemDownloadsFolder = FileUtils.File(systemDownloadsPath);
+ if (downloadsFolder.equals(systemDownloadsFolder))
+ folderListVal = 1;
+ }
+ Services.prefs.setIntPref("browser.download.folderList", folderListVal);
+ Services.prefs.setComplexValue("browser.download.dir", Ci.nsILocalFile,
+ downloadsFolder);
+ }),
+};
+
+function SearchStrings(aMainPreferencesPropertyListInstance) {
+ this._mainPreferencesPropertyList = aMainPreferencesPropertyListInstance;
+}
+SearchStrings.prototype = {
+ type: MigrationUtils.resourceTypes.OTHERDATA,
+
+ migrate: function SS_migrate(aCallback) {
+ this._mainPreferencesPropertyList.read(MigrationUtils.wrapMigrateFunction(
+ function migrateSearchStrings(aDict) {
+ if (!aDict)
+ throw new Error("Could not get preferences dictionary");
+
+ if (aDict.has("RecentSearchStrings")) {
+ let recentSearchStrings = aDict.get("RecentSearchStrings");
+ if (recentSearchStrings && recentSearchStrings.length > 0) {
+ let changes = recentSearchStrings.map((searchString) => (
+ {op: "add",
+ fieldname: "searchbar-history",
+ value: searchString}));
+ FormHistory.update(changes);
+ }
+ }
+ }.bind(this), aCallback));
+ }
+};
+
+// On OS X, the cookie-accept policy preference is stored in a separate
+// property list.
+function WebFoundationCookieBehavior(aWebFoundationFile) {
+ this._file = aWebFoundationFile;
+}
+WebFoundationCookieBehavior.prototype = {
+ type: MigrationUtils.resourceTypes.SETTINGS,
+
+ migrate: function WFPL_migrate(aCallback) {
+ PropertyListUtils.read(this._file, MigrationUtils.wrapMigrateFunction(
+ function migrateCookieBehavior(aDict) {
+ if (!aDict)
+ throw new Error("Could not read com.apple.WebFoundation.plist");
+
+ if (aDict.has("NSHTTPAcceptCookies")) {
+ // Setting Safari Firefox
+ // Always Accept always 0
+ // Accept from Originating current page 1
+ // Never Accept never 2
+ let acceptCookies = aDict.get("NSHTTPAcceptCookies");
+ let cookieValue = 0;
+ if (acceptCookies == "never") {
+ cookieValue = 2;
+ } else if (acceptCookies == "current page") {
+ cookieValue = 1;
+ }
+ Services.prefs.setIntPref("network.cookie.cookieBehavior",
+ cookieValue);
+ }
+ }.bind(this), aCallback));
+ }
+};
+
+function SafariProfileMigrator() {
+}
+
+SafariProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+SafariProfileMigrator.prototype.getResources = function SM_getResources() {
+ let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
+ if (!profileDir.exists())
+ return null;
+
+ let resources = [];
+ let pushProfileFileResource = function(aFileName, aConstructor) {
+ let file = profileDir.clone();
+ file.append(aFileName);
+ if (file.exists())
+ resources.push(new aConstructor(file));
+ };
+
+ pushProfileFileResource("History.plist", History);
+ pushProfileFileResource("Bookmarks.plist", Bookmarks);
+
+ // The Reading List feature was introduced at the same time in Windows and
+ // Mac versions of Safari. Not surprisingly, they are stored in the same
+ // format in both versions. Surpsingly, only on Windows there is a
+ // separate property list for it. This code is used on mac too, because
+ // Apple may fix this at some point.
+ pushProfileFileResource("ReadingList.plist", Bookmarks);
+
+ let prefs = this.mainPreferencesPropertyList;
+ if (prefs) {
+ resources.push(new Preferences(prefs));
+ resources.push(new SearchStrings(prefs));
+ }
+
+ let wfFile = FileUtils.getFile("UsrPrfs", ["com.apple.WebFoundation.plist"]);
+ if (wfFile.exists())
+ resources.push(new WebFoundationCookieBehavior(wfFile));
+
+ return resources;
+};
+
+SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() {
+ let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
+ let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
+ let path = OS.Path.join(profileDir.path, file);
+ return OS.File.stat(path).catch(() => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
+Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyList", {
+ get: function get_mainPreferencesPropertyList() {
+ if (this._mainPreferencesPropertyList === undefined) {
+ let file = FileUtils.getDir("UsrPrfs", [], false);
+ if (file.exists()) {
+ file.append("com.apple.Safari.plist");
+ if (file.exists()) {
+ this._mainPreferencesPropertyList =
+ new MainPreferencesPropertyList(file);
+ return this._mainPreferencesPropertyList;
+ }
+ }
+ this._mainPreferencesPropertyList = null;
+ return this._mainPreferencesPropertyList;
+ }
+ return this._mainPreferencesPropertyList;
+ }
+});
+
+Object.defineProperty(SafariProfileMigrator.prototype, "sourceHomePageURL", {
+ get: function get_sourceHomePageURL() {
+ if (this.mainPreferencesPropertyList) {
+ let dict = this.mainPreferencesPropertyList._readSync();
+ if (dict.has("HomePage"))
+ return dict.get("HomePage");
+ }
+ return "";
+ }
+});
+
+SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
+SafariProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=safari";
+SafariProfileMigrator.prototype.classID = Components.ID("{4b609ecf-60b2-4655-9df4-dc149e474da1}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SafariProfileMigrator]);
diff --git a/browser/components/migration/content/aboutWelcomeBack.xhtml b/browser/components/migration/content/aboutWelcomeBack.xhtml
new file mode 100644
index 000000000..d9fdb6c2c
--- /dev/null
+++ b/browser/components/migration/content/aboutWelcomeBack.xhtml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % restorepageDTD SYSTEM "chrome://browser/locale/aboutSessionRestore.dtd">
+ %restorepageDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <head>
+ <title>&welcomeback2.tabtitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css" media="all"/>
+ <link rel="stylesheet" href="chrome://browser/skin/aboutWelcomeBack.css" type="text/css" media="all"/>
+ <link rel="icon" type="image/png" href="chrome://global/skin/icons/information-16.png"/>
+
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSessionRestore.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <div class="container">
+
+ <div class="title">
+ <h1 class="title-text">&welcomeback2.pageTitle;</h1>
+ </div>
+
+ <div class="description">
+
+ <p>&welcomeback2.pageInfo1;</p>
+ <!-- Note a href in the anchor below is added by JS -->
+ <p>&welcomeback2.beforelink.pageInfo2;<a id="linkMoreTroubleshooting" target="_blank">&welcomeback2.link.pageInfo2;</a>&welcomeback2.afterlink.pageInfo2;</p>
+
+ <div>
+ <div class="radioRestoreContainer">
+ <input class="radioRestoreButton" id="radioRestoreAll" type="radio"
+ name="restore" checked="checked"/>
+ <label class="radioRestoreLabel" for="radioRestoreAll">&welcomeback2.label.restoreAll;</label>
+ </div>
+
+ <div class="radioRestoreContainer">
+ <input class="radioRestoreButton" id="radioRestoreChoose" type="radio"
+ name="restore"/>
+ <label class="radioRestoreLabel" for="radioRestoreChoose">&welcomeback2.label.restoreSome;</label>
+ </div>
+ </div>
+ </div>
+
+ <div class="tree-container">
+ <xul:tree id="tabList" flex="1" seltype="single" hidecolumnpicker="true"
+ onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
+ _window_label="&restorepage.windowLabel;">
+ <xul:treecols>
+ <xul:treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
+ <xul:splitter class="tree-splitter"/>
+ <xul:treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
+ </xul:treecols>
+ <xul:treechildren flex="1"/>
+ </xul:tree>
+ </div>
+
+ <div class="button-container">
+ <xul:button class="primary"
+ id="errorTryAgain"
+ label="&welcomeback2.restoreButton;"
+ accesskey="&welcomeback2.restoreButton.access;"
+ oncommand="restoreSession();"/>
+ </div>
+
+ <input type="text" id="sessionData" style="display: none;"/>
+
+ </div>
+ </body>
+</html>
diff --git a/browser/components/migration/content/extra-migration-strings.properties b/browser/components/migration/content/extra-migration-strings.properties
new file mode 100644
index 000000000..208906b31
--- /dev/null
+++ b/browser/components/migration/content/extra-migration-strings.properties
@@ -0,0 +1,14 @@
+# Automigration undo notification.
+# %1$S will be replaced with the name of the browser we imported from, %2$S will be replaced with brandShortName
+automigration.undo.message.all = Pick up where you left off. We’ve imported these sites and your bookmarks, history and passwords from %1$S into %2$S.
+automigration.undo.message.bookmarks = Pick up where you left off. We’ve imported these sites and your bookmarks from %1$S into %2$S.
+automigration.undo.message.bookmarks.logins = Pick up where you left off. We’ve imported these sites and your bookmarks and passwords from %1$S into %2$S.
+automigration.undo.message.bookmarks.visits = Pick up where you left off. We’ve imported these sites and your bookmarks and history from %1$S into %2$S.
+automigration.undo.message.logins = Pick up where you left off. We’ve imported your passwords from %1$S into %2$S.
+automigration.undo.message.logins.visits = Pick up where you left off. We’ve imported these sites and your history and passwords from %1$S into %2$S.
+automigration.undo.message.visits = Pick up where you left off. We’ve imported these sites and your history from %1$S into %2$S.
+automigration.undo.keep2.label = OK, Got it
+automigration.undo.keep2.accesskey = O
+automigration.undo.dontkeep2.label = No Thanks
+automigration.undo.dontkeep2.accesskey = N
+automigration.undo.unknownbrowser = Unknown Browser
diff --git a/browser/components/migration/content/migration.js b/browser/components/migration/content/migration.js
new file mode 100644
index 000000000..eb2175628
--- /dev/null
+++ b/browser/components/migration/content/migration.js
@@ -0,0 +1,549 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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;
+
+const kIMig = Ci.nsIBrowserProfileMigrator;
+const kIPStartup = Ci.nsIProfileStartup;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm");
+
+var MigrationWizard = { /* exported MigrationWizard */
+ _source: "", // Source Profile Migrator ContractID suffix
+ _itemsFlags: kIMig.ALL, // Selected Import Data Sources (16-bit bitfield)
+ _selectedProfile: null, // Selected Profile name to import from
+ _wiz: null,
+ _migrator: null,
+ _autoMigrate: null,
+
+ init: function ()
+ {
+ let os = Services.obs;
+ os.addObserver(this, "Migration:Started", false);
+ os.addObserver(this, "Migration:ItemBeforeMigrate", false);
+ os.addObserver(this, "Migration:ItemAfterMigrate", false);
+ os.addObserver(this, "Migration:ItemError", false);
+ os.addObserver(this, "Migration:Ended", false);
+
+ this._wiz = document.documentElement;
+
+ let args = window.arguments;
+ let entryPointId = args[0] || MigrationUtils.MIGRATION_ENTRYPOINT_UNKNOWN;
+ Services.telemetry.getHistogramById("FX_MIGRATION_ENTRY_POINT").add(entryPointId);
+ this.isInitialMigration = entryPointId == MigrationUtils.MIGRATION_ENTRYPOINT_FIRSTRUN;
+
+ if (args.length > 1) {
+ this._source = args[1];
+ this._migrator = args[2] instanceof kIMig ? args[2] : null;
+ this._autoMigrate = args[3].QueryInterface(kIPStartup);
+ this._skipImportSourcePage = args[4];
+ if (this._migrator && args[5]) {
+ let sourceProfiles = this._migrator.sourceProfiles;
+ this._selectedProfile = sourceProfiles.find(profile => profile.id == args[5]);
+ }
+
+ if (this._autoMigrate) {
+ // Show the "nothing" option in the automigrate case to provide an
+ // easily identifiable way to avoid migration and create a new profile.
+ document.getElementById("nothing").hidden = false;
+ }
+ }
+
+ this.onImportSourcePageShow();
+ },
+
+ uninit: function ()
+ {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(this, "Migration:Started");
+ os.removeObserver(this, "Migration:ItemBeforeMigrate");
+ os.removeObserver(this, "Migration:ItemAfterMigrate");
+ os.removeObserver(this, "Migration:ItemError");
+ os.removeObserver(this, "Migration:Ended");
+ MigrationUtils.finishMigration();
+ },
+
+ // 1 - Import Source
+ onImportSourcePageShow: function ()
+ {
+ // Show warning message to close the selected browser when needed
+ function toggleCloseBrowserWarning() {
+ let visibility = "hidden";
+ if (group.selectedItem.id != "nothing") {
+ let migrator = MigrationUtils.getMigrator(group.selectedItem.id);
+ visibility = migrator.sourceLocked ? "visible" : "hidden";
+ }
+ document.getElementById("closeSourceBrowser").style.visibility = visibility;
+ }
+ this._wiz.canRewind = false;
+
+ var selectedMigrator = null;
+ this._availableMigrators = [];
+
+ // Figure out what source apps are are available to import from:
+ var group = document.getElementById("importSourceGroup");
+ for (var i = 0; i < group.childNodes.length; ++i) {
+ var migratorKey = group.childNodes[i].id;
+ if (migratorKey != "nothing") {
+ var migrator = MigrationUtils.getMigrator(migratorKey);
+ if (migrator) {
+ // Save this as the first selectable item, if we don't already have
+ // one, or if it is the migrator that was passed to us.
+ if (!selectedMigrator || this._source == migratorKey)
+ selectedMigrator = group.childNodes[i];
+ this._availableMigrators.push([migratorKey, migrator]);
+ } else {
+ // Hide this option
+ group.childNodes[i].hidden = true;
+ }
+ }
+ }
+ if (this.isInitialMigration) {
+ Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_BROWSER_COUNT")
+ .add(this._availableMigrators.length);
+ let defaultBrowser = MigrationUtils.getMigratorKeyForDefaultBrowser();
+ // This will record 0 for unknown default browser IDs.
+ defaultBrowser = MigrationUtils.getSourceIdForTelemetry(defaultBrowser);
+ Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_EXISTING_DEFAULT_BROWSER")
+ .add(defaultBrowser);
+ }
+
+ group.addEventListener("command", toggleCloseBrowserWarning);
+
+ if (selectedMigrator) {
+ group.selectedItem = selectedMigrator;
+ toggleCloseBrowserWarning();
+ } else {
+ // We didn't find a migrator, notify the user
+ document.getElementById("noSources").hidden = false;
+
+ this._wiz.canAdvance = false;
+
+ document.getElementById("importBookmarks").hidden = true;
+ document.getElementById("importAll").hidden = true;
+ }
+
+ // Advance to the next page if the caller told us to.
+ if (this._migrator && this._skipImportSourcePage) {
+ this._wiz.advance();
+ this._wiz.canRewind = false;
+ }
+ },
+
+ onImportSourcePageAdvanced: function ()
+ {
+ var newSource = document.getElementById("importSourceGroup").selectedItem.id;
+
+ if (newSource == "nothing") {
+ // Need to do telemetry here because we're closing the dialog before we get to
+ // do actual migration. For actual migration, this doesn't happen until after
+ // migration takes place.
+ Services.telemetry.getHistogramById("FX_MIGRATION_SOURCE_BROWSER")
+ .add(MigrationUtils.getSourceIdForTelemetry("nothing"));
+ document.documentElement.cancel();
+ return false;
+ }
+
+ if (!this._migrator || (newSource != this._source)) {
+ // Create the migrator for the selected source.
+ this._migrator = MigrationUtils.getMigrator(newSource);
+
+ this._itemsFlags = kIMig.ALL;
+ this._selectedProfile = null;
+ }
+ this._source = newSource;
+
+ // check for more than one source profile
+ var sourceProfiles = this._migrator.sourceProfiles;
+ if (this._skipImportSourcePage) {
+ this._wiz.currentPage.next = "homePageImport";
+ }
+ else if (sourceProfiles && sourceProfiles.length > 1) {
+ this._wiz.currentPage.next = "selectProfile";
+ }
+ else {
+ if (this._autoMigrate)
+ this._wiz.currentPage.next = "homePageImport";
+ else
+ this._wiz.currentPage.next = "importItems";
+
+ if (sourceProfiles && sourceProfiles.length == 1)
+ this._selectedProfile = sourceProfiles[0];
+ else
+ this._selectedProfile = null;
+ }
+ return undefined;
+ },
+
+ // 2 - [Profile Selection]
+ onSelectProfilePageShow: function ()
+ {
+ // Disabling this for now, since we ask about import sources in automigration
+ // too and don't want to disable the back button
+ // if (this._autoMigrate)
+ // document.documentElement.getButton("back").disabled = true;
+
+ var profiles = document.getElementById("profiles");
+ while (profiles.hasChildNodes())
+ profiles.removeChild(profiles.firstChild);
+
+ // Note that this block is still reached even if the user chose 'From File'
+ // and we canceled the dialog. When that happens, _migrator will be null.
+ if (this._migrator) {
+ var sourceProfiles = this._migrator.sourceProfiles;
+
+ for (let profile of sourceProfiles) {
+ var item = document.createElement("radio");
+ item.id = profile.id;
+ item.setAttribute("label", profile.name);
+ profiles.appendChild(item);
+ }
+ }
+
+ profiles.selectedItem = this._selectedProfile ? document.getElementById(this._selectedProfile.id) : profiles.firstChild;
+ },
+
+ onSelectProfilePageRewound: function ()
+ {
+ var profiles = document.getElementById("profiles");
+ this._selectedProfile = this._migrator.sourceProfiles.find(
+ profile => profile.id == profiles.selectedItem.id
+ ) || null;
+ },
+
+ onSelectProfilePageAdvanced: function ()
+ {
+ var profiles = document.getElementById("profiles");
+ this._selectedProfile = this._migrator.sourceProfiles.find(
+ profile => profile.id == profiles.selectedItem.id
+ ) || null;
+
+ // If we're automigrating or just doing bookmarks don't show the item selection page
+ if (this._autoMigrate)
+ this._wiz.currentPage.next = "homePageImport";
+ },
+
+ // 3 - ImportItems
+ onImportItemsPageShow: function ()
+ {
+ var dataSources = document.getElementById("dataSources");
+ while (dataSources.hasChildNodes())
+ dataSources.removeChild(dataSources.firstChild);
+
+ var items = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
+ for (var i = 0; i < 16; ++i) {
+ var itemID = (items >> i) & 0x1 ? Math.pow(2, i) : 0;
+ if (itemID > 0) {
+ var checkbox = document.createElement("checkbox");
+ checkbox.id = itemID;
+ checkbox.setAttribute("label",
+ MigrationUtils.getLocalizedString(itemID + "_" + this._source));
+ dataSources.appendChild(checkbox);
+ if (!this._itemsFlags || this._itemsFlags & itemID)
+ checkbox.checked = true;
+ }
+ }
+ },
+
+ onImportItemsPageRewound: function ()
+ {
+ this._wiz.canAdvance = true;
+ this.onImportItemsPageAdvanced();
+ },
+
+ onImportItemsPageAdvanced: function ()
+ {
+ var dataSources = document.getElementById("dataSources");
+ this._itemsFlags = 0;
+ for (var i = 0; i < dataSources.childNodes.length; ++i) {
+ var checkbox = dataSources.childNodes[i];
+ if (checkbox.localName == "checkbox" && checkbox.checked)
+ this._itemsFlags |= parseInt(checkbox.id);
+ }
+ },
+
+ onImportItemCommand: function ()
+ {
+ var items = document.getElementById("dataSources");
+ var checkboxes = items.getElementsByTagName("checkbox");
+
+ var oneChecked = false;
+ for (var i = 0; i < checkboxes.length; ++i) {
+ if (checkboxes[i].checked) {
+ oneChecked = true;
+ break;
+ }
+ }
+
+ this._wiz.canAdvance = oneChecked;
+ },
+
+ // 4 - Home Page Selection
+ onHomePageMigrationPageShow: function ()
+ {
+ // only want this on the first run
+ if (!this._autoMigrate) {
+ this._wiz.advance();
+ return;
+ }
+
+ var brandBundle = document.getElementById("brandBundle");
+ var pageTitle, pageDesc, mainStr;
+ // These strings don't exist when not using official branding. If that's
+ // the case, just skip this page.
+ try {
+ pageTitle = brandBundle.getString("homePageMigrationPageTitle");
+ pageDesc = brandBundle.getString("homePageMigrationDescription");
+ mainStr = brandBundle.getString("homePageSingleStartMain");
+ }
+ catch (e) {
+ this._wiz.advance();
+ return;
+ }
+
+ document.getElementById("homePageImport").setAttribute("label", pageTitle);
+ document.getElementById("homePageImportDesc").setAttribute("value", pageDesc);
+
+ this._wiz._adjustWizardHeader();
+
+ var singleStart = document.getElementById("homePageSingleStart");
+ singleStart.setAttribute("label", mainStr);
+ singleStart.setAttribute("value", "DEFAULT");
+
+ var appName = MigrationUtils.getBrowserName(this._source);
+
+ // semi-wallpaper for crash when multiple profiles exist, since we haven't initialized mSourceProfile in places
+ this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
+
+ var oldHomePageURL = this._migrator.sourceHomePageURL;
+
+ if (oldHomePageURL && appName) {
+ var oldHomePageLabel =
+ brandBundle.getFormattedString("homePageImport", [appName]);
+ var oldHomePage = document.getElementById("oldHomePage");
+ oldHomePage.setAttribute("label", oldHomePageLabel);
+ oldHomePage.setAttribute("value", oldHomePageURL);
+ oldHomePage.removeAttribute("hidden");
+ }
+ else {
+ // if we don't have at least two options, just advance
+ this._wiz.advance();
+ }
+ },
+
+ onHomePageMigrationPageAdvanced: function ()
+ {
+ // we might not have a selectedItem if we're in fallback mode
+ try {
+ var radioGroup = document.getElementById("homePageRadiogroup");
+
+ this._newHomePage = radioGroup.selectedItem.value;
+ } catch (ex) {}
+ },
+
+ // 5 - Migrating
+ onMigratingPageShow: function ()
+ {
+ this._wiz.getButton("cancel").disabled = true;
+ this._wiz.canRewind = false;
+ this._wiz.canAdvance = false;
+
+ // When automigrating, show all of the data that can be received from this source.
+ if (this._autoMigrate)
+ this._itemsFlags = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
+
+ this._listItems("migratingItems");
+ setTimeout(() => this.onMigratingMigrate(), 0);
+ },
+
+ onMigratingMigrate: function ()
+ {
+ this._migrator.migrate(this._itemsFlags, this._autoMigrate, this._selectedProfile);
+
+ Services.telemetry.getHistogramById("FX_MIGRATION_SOURCE_BROWSER")
+ .add(MigrationUtils.getSourceIdForTelemetry(this._source));
+ if (!this._autoMigrate) {
+ let hist = Services.telemetry.getKeyedHistogramById("FX_MIGRATION_USAGE");
+ let exp = 0;
+ let items = this._itemsFlags;
+ while (items) {
+ if (items & 1) {
+ hist.add(this._source, exp);
+ }
+ items = items >> 1;
+ exp++;
+ }
+ }
+ },
+
+ _listItems: function (aID)
+ {
+ var items = document.getElementById(aID);
+ while (items.hasChildNodes())
+ items.removeChild(items.firstChild);
+
+ var itemID;
+ for (var i = 0; i < 16; ++i) {
+ itemID = (this._itemsFlags >> i) & 0x1 ? Math.pow(2, i) : 0;
+ if (itemID > 0) {
+ var label = document.createElement("label");
+ label.id = itemID + "_migrated";
+ try {
+ label.setAttribute("value",
+ MigrationUtils.getLocalizedString(itemID + "_" + this._source));
+ items.appendChild(label);
+ }
+ catch (e) {
+ // if the block above throws, we've enumerated all the import data types we
+ // currently support and are now just wasting time, break.
+ break;
+ }
+ }
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ var label;
+ switch (aTopic) {
+ case "Migration:Started":
+ break;
+ case "Migration:ItemBeforeMigrate":
+ label = document.getElementById(aData + "_migrated");
+ if (label)
+ label.setAttribute("style", "font-weight: bold");
+ break;
+ case "Migration:ItemAfterMigrate":
+ label = document.getElementById(aData + "_migrated");
+ if (label)
+ label.removeAttribute("style");
+ break;
+ case "Migration:Ended":
+ if (this.isInitialMigration) {
+ // Ensure errors in reporting data recency do not affect the rest of the migration.
+ try {
+ this.reportDataRecencyTelemetry();
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ if (this._autoMigrate) {
+ let hasImportedHomepage = !!(this._newHomePage && this._newHomePage != "DEFAULT");
+ Services.telemetry.getKeyedHistogramById("FX_MIGRATION_IMPORTED_HOMEPAGE")
+ .add(this._source, hasImportedHomepage);
+ if (this._newHomePage) {
+ try {
+ // set homepage properly
+ var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ var prefBranch = prefSvc.getBranch(null);
+
+ if (this._newHomePage == "DEFAULT") {
+ prefBranch.clearUserPref("browser.startup.homepage");
+ }
+ else {
+ var str = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = this._newHomePage;
+ prefBranch.setComplexValue("browser.startup.homepage",
+ Components.interfaces.nsISupportsString,
+ str);
+ }
+
+ var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ var prefFile = dirSvc.get("ProfDS", Components.interfaces.nsIFile);
+ prefFile.append("prefs.js");
+ prefSvc.savePrefFile(prefFile);
+ } catch (ex) {
+ dump(ex);
+ }
+ }
+
+ // We're done now.
+ this._wiz.canAdvance = true;
+ this._wiz.advance();
+
+ setTimeout(close, 5000);
+ }
+ else {
+ this._wiz.canAdvance = true;
+ var nextButton = this._wiz.getButton("next");
+ nextButton.click();
+ }
+ break;
+ case "Migration:ItemError":
+ let type = "undefined";
+ let numericType = parseInt(aData);
+ switch (numericType) {
+ case Ci.nsIBrowserProfileMigrator.SETTINGS:
+ type = "settings";
+ break;
+ case Ci.nsIBrowserProfileMigrator.COOKIES:
+ type = "cookies";
+ break;
+ case Ci.nsIBrowserProfileMigrator.HISTORY:
+ type = "history";
+ break;
+ case Ci.nsIBrowserProfileMigrator.FORMDATA:
+ type = "form data";
+ break;
+ case Ci.nsIBrowserProfileMigrator.PASSWORDS:
+ type = "passwords";
+ break;
+ case Ci.nsIBrowserProfileMigrator.BOOKMARKS:
+ type = "bookmarks";
+ break;
+ case Ci.nsIBrowserProfileMigrator.OTHERDATA:
+ type = "misc. data";
+ break;
+ }
+ Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService)
+ .logStringMessage("some " + type + " did not successfully migrate.");
+ Services.telemetry.getKeyedHistogramById("FX_MIGRATION_ERRORS")
+ .add(this._source, Math.log2(numericType));
+ break;
+ }
+ },
+
+ onDonePageShow: function ()
+ {
+ this._wiz.getButton("cancel").disabled = true;
+ this._wiz.canRewind = false;
+ this._listItems("doneItems");
+ },
+
+ reportDataRecencyTelemetry() {
+ let histogram = Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_DATA_RECENCY");
+ let lastUsedPromises = [];
+ for (let [key, migrator] of this._availableMigrators) {
+ // No block-scoped let in for...of loop conditions, so get the source:
+ let localKey = key;
+ lastUsedPromises.push(migrator.getLastUsedDate().then(date => {
+ const ONE_YEAR = 24 * 365;
+ let diffInHours = Math.round((Date.now() - date) / (60 * 60 * 1000));
+ if (diffInHours > ONE_YEAR) {
+ diffInHours = ONE_YEAR;
+ }
+ histogram.add(localKey, diffInHours);
+ return [localKey, diffInHours];
+ }));
+ }
+ Promise.all(lastUsedPromises).then(migratorUsedTimeDiff => {
+ // Sort low to high.
+ migratorUsedTimeDiff.sort(([keyA, diffA], [keyB, diffB]) => diffA - diffB); /* eslint no-unused-vars: off */
+ let usedMostRecentBrowser = migratorUsedTimeDiff.length && this._source == migratorUsedTimeDiff[0][0];
+ let usedRecentBrowser =
+ Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_USED_RECENT_BROWSER");
+ usedRecentBrowser.add(this._source, usedMostRecentBrowser);
+ });
+ },
+};
diff --git a/browser/components/migration/content/migration.xul b/browser/components/migration/content/migration.xul
new file mode 100644
index 000000000..e85091002
--- /dev/null
+++ b/browser/components/migration/content/migration.xul
@@ -0,0 +1,109 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/migration/migration.dtd" >
+
+<wizard id="migrationWizard"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="Browser:MigrationWizard"
+ title="&migrationWizard.title;"
+ onload="MigrationWizard.init()"
+ onunload="MigrationWizard.uninit()"
+ style="width: 40em;"
+ buttons="accept,cancel"
+ branded="true">
+
+ <script type="application/javascript" src="chrome://browser/content/migration/migration.js"/>
+
+ <stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
+
+ <wizardpage id="importSource" pageid="importSource" next="selectProfile"
+ label="&importSource.title;"
+ onpageadvanced="return MigrationWizard.onImportSourcePageAdvanced();">
+#ifdef XP_WIN
+ <description id="importAll" control="importSourceGroup">&importFrom.label;</description>
+#else
+ <description id="importAll" control="importSourceGroup">&importFromUnix.label;</description>
+#endif
+ <description id="importBookmarks" control="importSourceGroup" hidden="true">&importFromBookmarks.label;</description>
+
+ <radiogroup id="importSourceGroup" align="start">
+# NB: if you add items to this list, please also assign them a unique migrator ID in MigrationUtils.jsm
+ <radio id="firefox" label="&importFromFirefox.label;" accesskey="&importFromFirefox.accesskey;"/>
+#ifdef XP_WIN
+ <radio id="edge" label="&importFromEdge.label;" accesskey="&importFromEdge.accesskey;"/>
+ <radio id="ie" label="&importFromIE.label;" accesskey="&importFromIE.accesskey;"/>
+ <radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
+ <radio id="chromium" label="&importFromChromium.label;" accesskey="&importFromChromium.accesskey;"/>
+ <radio id="canary" label="&importFromCanary.label;" accesskey="&importFromCanary.accesskey;"/>
+ <radio id="360se" label="&importFrom360se.label;" accesskey="&importFrom360se.accesskey;"/>
+#elifdef XP_MACOSX
+ <radio id="safari" label="&importFromSafari.label;" accesskey="&importFromSafari.accesskey;"/>
+ <radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
+ <radio id="chromium" label="&importFromChromium.label;" accesskey="&importFromChromium.accesskey;"/>
+ <radio id="canary" label="&importFromCanary.label;" accesskey="&importFromCanary.accesskey;"/>
+#elifdef XP_UNIX
+ <radio id="chrome" label="&importFromChrome.label;" accesskey="&importFromChrome.accesskey;"/>
+ <radio id="chromium" label="&importFromChromium.label;" accesskey="&importFromChromium.accesskey;"/>
+#endif
+ <radio id="nothing" label="&importFromNothing.label;" accesskey="&importFromNothing.accesskey;" hidden="true"/>
+ </radiogroup>
+ <label id="noSources" hidden="true">&noMigrationSources.label;</label>
+ <spacer flex="1"/>
+ <description class="header" id="closeSourceBrowser" style="visibility:hidden">&closeSourceBrowser.label;</description>
+ </wizardpage>
+
+ <wizardpage id="selectProfile" pageid="selectProfile" label="&selectProfile.title;"
+ next="importItems"
+ onpageshow="return MigrationWizard.onSelectProfilePageShow();"
+ onpagerewound="return MigrationWizard.onSelectProfilePageRewound();"
+ onpageadvanced="return MigrationWizard.onSelectProfilePageAdvanced();">
+ <description control="profiles">&selectProfile.label;</description>
+
+ <radiogroup id="profiles" align="left"/>
+ </wizardpage>
+
+ <wizardpage id="importItems" pageid="importItems" label="&importItems.title;"
+ next="homePageImport"
+ onpageshow="return MigrationWizard.onImportItemsPageShow();"
+ onpagerewound="return MigrationWizard.onImportItemsPageRewound();"
+ onpageadvanced="return MigrationWizard.onImportItemsPageAdvanced();"
+ oncommand="MigrationWizard.onImportItemCommand();">
+ <description control="dataSources">&importItems.label;</description>
+
+ <vbox id="dataSources" style="overflow: auto; -moz-appearance: listbox" align="left" flex="1" role="group"/>
+ </wizardpage>
+
+ <wizardpage id="homePageImport" pageid="homePageImport"
+ next="migrating"
+ onpageshow="return MigrationWizard.onHomePageMigrationPageShow();"
+ onpageadvanced="return MigrationWizard.onHomePageMigrationPageAdvanced();">
+
+ <description id="homePageImportDesc" control="homePageRadioGroup"/>
+ <radiogroup id="homePageRadiogroup">
+ <radio id="homePageSingleStart" selected="true" />
+ <radio id="oldHomePage" hidden="true" />
+ </radiogroup>
+ </wizardpage>
+
+ <wizardpage id="migrating" pageid="migrating" label="&migrating.title;"
+ next="done"
+ onpageshow="MigrationWizard.onMigratingPageShow();">
+ <description control="migratingItems">&migrating.label;</description>
+
+ <vbox id="migratingItems" style="overflow: auto;" align="left" role="group"/>
+ </wizardpage>
+
+ <wizardpage id="done" pageid="done" label="&done.title;"
+ onpageshow="MigrationWizard.onDonePageShow();">
+ <description control="doneItems">&done.label;</description>
+
+ <vbox id="doneItems" style="overflow: auto;" align="left" role="group"/>
+ </wizardpage>
+
+</wizard>
+
diff --git a/browser/components/migration/jar.mn b/browser/components/migration/jar.mn
new file mode 100644
index 000000000..110788bc4
--- /dev/null
+++ b/browser/components/migration/jar.mn
@@ -0,0 +1,9 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+* content/browser/migration/migration.xul (content/migration.xul)
+ content/browser/migration/migration.js (content/migration.js)
+ content/browser/migration/extra-migration-strings.properties (content/extra-migration-strings.properties)
+ content/browser/aboutWelcomeBack.xhtml (content/aboutWelcomeBack.xhtml)
diff --git a/browser/components/migration/moz.build b/browser/components/migration/moz.build
new file mode 100644
index 000000000..751ea0cd9
--- /dev/null
+++ b/browser/components/migration/moz.build
@@ -0,0 +1,62 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+MARIONETTE_UNIT_MANIFESTS += ['tests/marionette/manifest.ini']
+
+BROWSER_CHROME_MANIFESTS += [ 'tests/browser/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += [
+ 'nsIBrowserProfileMigrator.idl',
+]
+
+XPIDL_MODULE = 'migration'
+
+EXTRA_COMPONENTS += [
+ 'ChromeProfileMigrator.js',
+ 'FirefoxProfileMigrator.js',
+ 'ProfileMigrator.js',
+]
+
+EXTRA_PP_COMPONENTS += [
+ 'BrowserProfileMigrators.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'AutoMigrate.jsm',
+ 'MigrationUtils.jsm',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += [
+ 'nsIEHistoryEnumerator.cpp',
+ ]
+ EXTRA_COMPONENTS += [
+ '360seProfileMigrator.js',
+ 'EdgeProfileMigrator.js',
+ 'IEProfileMigrator.js',
+ ]
+ EXTRA_JS_MODULES += [
+ 'ESEDBReader.jsm',
+ 'MSMigrationUtils.jsm',
+ ]
+ DEFINES['HAS_360SE_MIGRATOR'] = True
+ DEFINES['HAS_IE_MIGRATOR'] = True
+ DEFINES['HAS_EDGE_MIGRATOR'] = True
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ EXTRA_COMPONENTS += [
+ 'SafariProfileMigrator.js',
+ ]
+ DEFINES['HAS_SAFARI_MIGRATOR'] = True
+
+FINAL_LIBRARY = 'browsercomps'
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Migration')
diff --git a/browser/components/migration/nsIBrowserProfileMigrator.idl b/browser/components/migration/nsIBrowserProfileMigrator.idl
new file mode 100644
index 000000000..a251c3683
--- /dev/null
+++ b/browser/components/migration/nsIBrowserProfileMigrator.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"
+
+interface nsIArray;
+interface nsIProfileStartup;
+
+[scriptable, uuid(22b56ffc-3149-43c5-b5a9-b3a6b678de93)]
+interface nsIBrowserProfileMigrator : nsISupports
+{
+ /**
+ * profile items to migrate. use with migrate().
+ */
+ const unsigned short ALL = 0x0000;
+ const unsigned short SETTINGS = 0x0001;
+ const unsigned short COOKIES = 0x0002;
+ const unsigned short HISTORY = 0x0004;
+ const unsigned short FORMDATA = 0x0008;
+ const unsigned short PASSWORDS = 0x0010;
+ const unsigned short BOOKMARKS = 0x0020;
+ const unsigned short OTHERDATA = 0x0040;
+ const unsigned short SESSION = 0x0080;
+
+ /**
+ * Copy user profile information to the current active profile.
+ * @param aItems list of data items to migrate. see above for values.
+ * @param aStartup helper interface which is non-null if called during startup.
+ * @param aProfile profile to migrate from, if there is more than one.
+ */
+ void migrate(in unsigned short aItems, in nsIProfileStartup aStartup, in jsval aProfile);
+
+ /**
+ * A bit field containing profile items that this migrator
+ * offers for import.
+ * @param aProfile the profile that we are looking for available data
+ * to import
+ * @param aDoingStartup "true" if the profile is not currently being used.
+ * @return bit field containing profile items (see above)
+ * @note a return value of 0 represents no items rather than ALL.
+ */
+ unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
+
+ /**
+ * Get the last time data from this browser was modified
+ * @return a promise that resolves to a JS Date object
+ */
+ jsval getLastUsedDate();
+
+ /**
+ * Whether or not there is any data that can be imported from this
+ * browser (i.e. whether or not it is installed, and there exists
+ * a user profile)
+ */
+ readonly attribute boolean sourceExists;
+
+
+ /**
+ * An enumeration of available profiles. If the import source does
+ * not support profiles, this attribute is null.
+ */
+ readonly attribute jsval sourceProfiles;
+
+ /**
+ * The import source homepage. Returns null if not present/available
+ */
+ readonly attribute AUTF8String sourceHomePageURL;
+
+
+ /**
+ * Whether the source browser data is locked/in-use meaning migration likely
+ * won't succeed and the user should be warned.
+ */
+ readonly attribute boolean sourceLocked;
+};
diff --git a/browser/components/migration/nsIEHistoryEnumerator.cpp b/browser/components/migration/nsIEHistoryEnumerator.cpp
new file mode 100644
index 000000000..116e9a860
--- /dev/null
+++ b/browser/components/migration/nsIEHistoryEnumerator.cpp
@@ -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 "nsIEHistoryEnumerator.h"
+
+#include <urlhist.h>
+#include <shlguid.h>
+
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsIURI.h"
+#include "nsIVariant.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsWindowsMigrationUtils.h"
+#include "prtime.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIEHistoryEnumerator
+
+NS_IMPL_ISUPPORTS(nsIEHistoryEnumerator, nsISimpleEnumerator)
+
+nsIEHistoryEnumerator::nsIEHistoryEnumerator()
+{
+ ::CoInitialize(nullptr);
+}
+
+nsIEHistoryEnumerator::~nsIEHistoryEnumerator()
+{
+ ::CoUninitialize();
+}
+
+void
+nsIEHistoryEnumerator::EnsureInitialized()
+{
+ if (mURLEnumerator)
+ return;
+
+ HRESULT hr = ::CoCreateInstance(CLSID_CUrlHistory,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IUrlHistoryStg2,
+ getter_AddRefs(mIEHistory));
+ if (FAILED(hr))
+ return;
+
+ hr = mIEHistory->EnumUrls(getter_AddRefs(mURLEnumerator));
+ if (FAILED(hr))
+ return;
+}
+
+NS_IMETHODIMP
+nsIEHistoryEnumerator::HasMoreElements(bool* _retval)
+{
+ *_retval = false;
+
+ EnsureInitialized();
+ MOZ_ASSERT(mURLEnumerator, "Should have instanced an IE History URLEnumerator");
+ if (!mURLEnumerator)
+ return NS_OK;
+
+ STATURL statURL;
+ ULONG fetched;
+
+ // First argument is not implemented, so doesn't matter what we pass.
+ HRESULT hr = mURLEnumerator->Next(1, &statURL, &fetched);
+ if (FAILED(hr) || fetched != 1UL) {
+ // Reached the last entry.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (statURL.pwcsUrl) {
+ nsDependentString url(statURL.pwcsUrl);
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
+ ::CoTaskMemFree(statURL.pwcsUrl);
+ if (NS_FAILED(rv)) {
+ // Got a corrupt or invalid URI, continue to the next entry.
+ return HasMoreElements(_retval);
+ }
+ }
+
+ nsDependentString title(statURL.pwcsTitle ? statURL.pwcsTitle : L"");
+
+ bool lastVisitTimeIsValid;
+ PRTime lastVisited = WinMigrationFileTimeToPRTime(&(statURL.ftLastVisited), &lastVisitTimeIsValid);
+
+ mCachedNextEntry = do_CreateInstance("@mozilla.org/hash-property-bag;1");
+ MOZ_ASSERT(mCachedNextEntry, "Should have instanced a new property bag");
+ if (mCachedNextEntry) {
+ mCachedNextEntry->SetPropertyAsInterface(NS_LITERAL_STRING("uri"), uri);
+ mCachedNextEntry->SetPropertyAsAString(NS_LITERAL_STRING("title"), title);
+ if (lastVisitTimeIsValid) {
+ mCachedNextEntry->SetPropertyAsInt64(NS_LITERAL_STRING("time"), lastVisited);
+ }
+
+ *_retval = true;
+ }
+
+ if (statURL.pwcsTitle)
+ ::CoTaskMemFree(statURL.pwcsTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIEHistoryEnumerator::GetNext(nsISupports** _retval)
+{
+ *_retval = nullptr;
+
+ if (!mCachedNextEntry)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*_retval = mCachedNextEntry);
+ // Release the cached entry, so it can't be returned twice.
+ mCachedNextEntry = nullptr;
+
+ return NS_OK;
+}
diff --git a/browser/components/migration/nsIEHistoryEnumerator.h b/browser/components/migration/nsIEHistoryEnumerator.h
new file mode 100644
index 000000000..1572a8dd5
--- /dev/null
+++ b/browser/components/migration/nsIEHistoryEnumerator.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 iehistoryenumerator___h___
+#define iehistoryenumerator___h___
+
+#include <urlhist.h>
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIWritablePropertyBag2.h"
+
+class nsIEHistoryEnumerator final : public nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ nsIEHistoryEnumerator();
+
+private:
+ ~nsIEHistoryEnumerator();
+
+ /**
+ * Initializes the history reader, if needed.
+ */
+ void EnsureInitialized();
+
+ RefPtr<IUrlHistoryStg2> mIEHistory;
+ RefPtr<IEnumSTATURL> mURLEnumerator;
+
+ nsCOMPtr<nsIWritablePropertyBag2> mCachedNextEntry;
+};
+
+#endif
diff --git a/browser/components/migration/nsWindowsMigrationUtils.h b/browser/components/migration/nsWindowsMigrationUtils.h
new file mode 100644
index 000000000..0288d93d3
--- /dev/null
+++ b/browser/components/migration/nsWindowsMigrationUtils.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/. */
+
+#ifndef windowsmigrationutils__h__
+#define windowsmigrationutils__h__
+
+#include "prtime.h"
+
+static
+PRTime WinMigrationFileTimeToPRTime(FILETIME* filetime, bool* isValid)
+{
+ SYSTEMTIME st;
+ *isValid = ::FileTimeToSystemTime(filetime, &st);
+ if (!*isValid) {
+ return 0;
+ }
+ PRExplodedTime prt;
+ prt.tm_year = st.wYear;
+ // SYSTEMTIME's day-of-month parameter is 1-based,
+ // PRExplodedTime's is 0-based.
+ prt.tm_month = st.wMonth - 1;
+ prt.tm_mday = st.wDay;
+ prt.tm_hour = st.wHour;
+ prt.tm_min = st.wMinute;
+ prt.tm_sec = st.wSecond;
+ prt.tm_usec = st.wMilliseconds * 1000;
+ prt.tm_wday = 0;
+ prt.tm_yday = 0;
+ prt.tm_params.tp_gmt_offset = 0;
+ prt.tm_params.tp_dst_offset = 0;
+ return PR_ImplodeTime(&prt);
+}
+
+#endif
+
diff --git a/browser/components/migration/tests/browser/.eslintrc.js b/browser/components/migration/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..3ea6eeb8c
--- /dev/null
+++ b/browser/components/migration/tests/browser/.eslintrc.js
@@ -0,0 +1,9 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js",
+ "../../../../../testing/mochitest/mochitest.eslintrc.js",
+ ]
+};
+
diff --git a/browser/components/migration/tests/browser/browser.ini b/browser/components/migration/tests/browser/browser.ini
new file mode 100644
index 000000000..94edfe7aa
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser.ini
@@ -0,0 +1,3 @@
+[browser_undo_notification.js]
+[browser_undo_notification_wording.js]
+[browser_undo_notification_multiple_dismissal.js]
diff --git a/browser/components/migration/tests/browser/browser_undo_notification.js b/browser/components/migration/tests/browser/browser_undo_notification.js
new file mode 100644
index 000000000..6c97922e0
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_undo_notification.js
@@ -0,0 +1,67 @@
+"use strict";
+
+let scope = {};
+Cu.import("resource:///modules/AutoMigrate.jsm", scope);
+let oldCanUndo = scope.AutoMigrate.canUndo;
+let oldUndo = scope.AutoMigrate.undo;
+registerCleanupFunction(function() {
+ scope.AutoMigrate.canUndo = oldCanUndo;
+ scope.AutoMigrate.undo = oldUndo;
+});
+
+const kExpectedNotificationId = "automigration-undo";
+
+add_task(function* autoMigrationUndoNotificationShows() {
+ let getNotification = browser =>
+ gBrowser.getNotificationBox(browser).getNotificationWithValue(kExpectedNotificationId);
+
+ scope.AutoMigrate.canUndo = () => true;
+ let undoCalled;
+ scope.AutoMigrate.undo = () => { undoCalled = true };
+ for (let url of ["about:newtab", "about:home"]) {
+ undoCalled = false;
+ // Can't use pushPrefEnv because of bug 1323779
+ Services.prefs.setCharPref("browser.migrate.automigrate.browser", "someunknownbrowser");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, false);
+ let browser = tab.linkedBrowser;
+ if (!getNotification(browser)) {
+ info(`Notification for ${url} not immediately present, waiting for it.`);
+ yield BrowserTestUtils.waitForNotificationBar(gBrowser, browser, kExpectedNotificationId);
+ }
+
+ ok(true, `Got notification for ${url}`);
+ let notification = getNotification(browser);
+ let notificationBox = notification.parentNode;
+ notification.querySelector("button.notification-button-default").click();
+ ok(!undoCalled, "Undo should not be called when clicking the default button");
+ is(notification, notificationBox._closedNotification, "Notification should be closing");
+ yield BrowserTestUtils.removeTab(tab);
+
+ undoCalled = false;
+ Services.prefs.setCharPref("browser.migrate.automigrate.browser", "chrome");
+ tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, false);
+ browser = tab.linkedBrowser;
+ if (!getNotification(browser)) {
+ info(`Notification for ${url} not immediately present, waiting for it.`);
+ yield BrowserTestUtils.waitForNotificationBar(gBrowser, browser, kExpectedNotificationId);
+ }
+
+ ok(true, `Got notification for ${url}`);
+ notification = getNotification(browser);
+ notificationBox = notification.parentNode;
+ // Set up the survey:
+ yield SpecialPowers.pushPrefEnv({set: [
+ ["browser.migrate.automigrate.undo-survey", "https://example.com/?browser=%IMPORTEDBROWSER%"],
+ ["browser.migrate.automigrate.undo-survey-locales", "en-US"],
+ ]});
+ let tabOpenedPromise = BrowserTestUtils.waitForNewTab(gBrowser, "https://example.com/?browser=Google%20Chrome");
+ notification.querySelector("button:not(.notification-button-default)").click();
+ ok(undoCalled, "Undo should be called when clicking the non-default (Don't Keep) button");
+ is(notification, notificationBox._closedNotification, "Notification should be closing");
+ let surveyTab = yield tabOpenedPromise;
+ ok(surveyTab, "Should have opened a tab with a survey");
+ yield BrowserTestUtils.removeTab(surveyTab);
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
+
diff --git a/browser/components/migration/tests/browser/browser_undo_notification_multiple_dismissal.js b/browser/components/migration/tests/browser/browser_undo_notification_multiple_dismissal.js
new file mode 100644
index 000000000..90b5d0d08
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_undo_notification_multiple_dismissal.js
@@ -0,0 +1,122 @@
+"use strict";
+
+
+const kExpectedNotificationId = "automigration-undo";
+
+/**
+ * Pretend we can undo something, trigger a notification, pick the undo option,
+ * and verify that the notifications are all dismissed immediately.
+ */
+add_task(function* checkNotificationsDismissed() {
+ yield SpecialPowers.pushPrefEnv({set: [
+ ["browser.migrate.automigrate.enabled", true],
+ ["browser.migrate.automigrate.ui.enabled", true],
+ ]});
+ let getNotification = browser =>
+ gBrowser.getNotificationBox(browser).getNotificationWithValue(kExpectedNotificationId);
+
+ Services.prefs.setCharPref("browser.migrate.automigrate.browser", "someunknownbrowser");
+
+ let {guid, lastModified} = yield PlacesUtils.bookmarks.insert(
+ {title: "Some imported bookmark", parentGuid: PlacesUtils.bookmarks.toolbarGuid, url: "http://www.example.com"}
+ );
+
+ let testUndoData = {
+ visits: [],
+ bookmarks: [{guid, lastModified: lastModified.getTime()}],
+ logins: [],
+ };
+ let path = OS.Path.join(OS.Constants.Path.profileDir, "initialMigrationMetadata.jsonlz4");
+ registerCleanupFunction(() => {
+ return OS.File.remove(path, {ignoreAbsent: true});
+ });
+ yield OS.File.writeAtomic(path, JSON.stringify(testUndoData), {
+ encoding: "utf-8",
+ compression: "lz4",
+ tmpPath: path + ".tmp",
+ });
+
+ let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", false);
+ if (!getNotification(firstTab.linkedBrowser)) {
+ info(`Notification not immediately present on first tab, waiting for it.`);
+ yield BrowserTestUtils.waitForNotificationBar(gBrowser, firstTab.linkedBrowser, kExpectedNotificationId);
+ }
+ let secondTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", false);
+ if (!getNotification(secondTab.linkedBrowser)) {
+ info(`Notification not immediately present on second tab, waiting for it.`);
+ yield BrowserTestUtils.waitForNotificationBar(gBrowser, secondTab.linkedBrowser, kExpectedNotificationId);
+ }
+
+ // Create a listener for the removal in the first tab, and a listener for bookmarks removal,
+ // then click 'Don't keep' in the second tab, and verify that the notification is removed
+ // before we start removing bookmarks.
+ let haveRemovedBookmark = false;
+ let bmObserver;
+ let bookmarkRemovedPromise = new Promise(resolve => {
+ bmObserver = {
+ onItemRemoved(itemId, parentId, index, itemType, uri, removedGuid) {
+ if (guid == removedGuid) {
+ haveRemovedBookmark = true;
+ resolve();
+ }
+ },
+ };
+ PlacesUtils.bookmarks.addObserver(bmObserver, false);
+ registerCleanupFunction(() => PlacesUtils.bookmarks.removeObserver(bmObserver));
+ });
+
+ let firstTabNotificationRemovedPromise = new Promise(resolve => {
+ let notification = getNotification(firstTab.linkedBrowser);
+ // Save this reference because notification.parentNode will be null once it's removed.
+ let notificationBox = notification.parentNode;
+ let mut = new MutationObserver(mutations => {
+ // Yucky, but we have to detect either the removal via animation (with marginTop)
+ // or when the element is removed. We can't just detect the element being removed
+ // because this happens asynchronously (after the animation) and so it'd race
+ // with the rest of the undo happening.
+ for (let mutation of mutations) {
+ if (mutation.target == notification && mutation.attributeName == "style" &&
+ parseInt(notification.style.marginTop, 10) < 0) {
+ ok(!haveRemovedBookmark, "Should not have removed bookmark yet");
+ mut.disconnect();
+ resolve();
+ return;
+ }
+ if (mutation.target == notificationBox && mutation.removedNodes.length &&
+ mutation.removedNodes[0] == notification) {
+ ok(!haveRemovedBookmark, "Should not have removed bookmark yet");
+ mut.disconnect();
+ resolve();
+ return;
+ }
+ }
+ });
+ mut.observe(notification.parentNode, {childList: true});
+ mut.observe(notification, {attributes: true});
+ });
+
+ let prefResetPromise = new Promise(resolve => {
+ const kObservedPref = "browser.migrate.automigrate.browser";
+ let obs = () => {
+ Services.prefs.removeObserver(kObservedPref, obs);
+ ok(!Services.prefs.prefHasUserValue(kObservedPref),
+ "Pref should have been reset");
+ resolve();
+ };
+ Services.prefs.addObserver(kObservedPref, obs, false);
+ });
+
+ // Click "Don't keep" button:
+ let notificationToActivate = getNotification(secondTab.linkedBrowser);
+ notificationToActivate.querySelector("button:not(.notification-button-default)").click();
+ info("Waiting for notification to be removed in first (background) tab");
+ yield firstTabNotificationRemovedPromise;
+ info("Waiting for bookmark to be removed");
+ yield bookmarkRemovedPromise;
+ info("Waiting for prefs to be reset");
+ yield prefResetPromise;
+
+ info("Removing spare tabs");
+ yield BrowserTestUtils.removeTab(firstTab);
+ yield BrowserTestUtils.removeTab(secondTab);
+});
diff --git a/browser/components/migration/tests/browser/browser_undo_notification_wording.js b/browser/components/migration/tests/browser/browser_undo_notification_wording.js
new file mode 100644
index 000000000..f0a9ceec9
--- /dev/null
+++ b/browser/components/migration/tests/browser/browser_undo_notification_wording.js
@@ -0,0 +1,67 @@
+"use strict";
+
+let scope = {};
+Cu.import("resource:///modules/AutoMigrate.jsm", scope);
+let oldCanUndo = scope.AutoMigrate.canUndo;
+registerCleanupFunction(function() {
+ scope.AutoMigrate.canUndo = oldCanUndo;
+});
+
+const kExpectedNotificationId = "automigration-undo";
+
+add_task(function* autoMigrationUndoNotificationShows() {
+ let getNotification = browser =>
+ gBrowser.getNotificationBox(browser).getNotificationWithValue(kExpectedNotificationId);
+ let localizedVersionOf = str => {
+ if (str == "logins") {
+ return "passwords";
+ }
+ if (str == "visits") {
+ return "history";
+ }
+ return str;
+ };
+
+ scope.AutoMigrate.canUndo = () => true;
+ let url = "about:newtab";
+ Services.prefs.setCharPref("browser.migrate.automigrate.browser", "someunknownbrowser");
+ const kSubsets = [
+ ["bookmarks", "logins", "visits"],
+ ["bookmarks", "logins"],
+ ["bookmarks", "visits"],
+ ["logins", "visits"],
+ ["bookmarks"],
+ ["logins"],
+ ["visits"],
+ ];
+ const kAllItems = ["bookmarks", "logins", "visits"];
+ for (let subset of kSubsets) {
+ let state = new Map(subset.map(item => [item, [{}]]));
+ scope.AutoMigrate._setImportedItemPrefFromState(state);
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, false);
+ let browser = tab.linkedBrowser;
+
+ if (!getNotification(browser)) {
+ info(`Notification for ${url} not immediately present, waiting for it.`);
+ yield BrowserTestUtils.waitForNotificationBar(gBrowser, browser, kExpectedNotificationId);
+ }
+
+ ok(true, `Got notification for ${url}`);
+ let notification = getNotification(browser);
+ let notificationText = document.getAnonymousElementByAttribute(notification, "class", "messageText");
+ notificationText = notificationText.textContent;
+ for (let potentiallyImported of kAllItems) {
+ let localizedImportItem = localizedVersionOf(potentiallyImported);
+ if (subset.includes(potentiallyImported)) {
+ ok(notificationText.includes(localizedImportItem),
+ "Expected notification to contain " + localizedImportItem);
+ } else {
+ ok(!notificationText.includes(localizedImportItem),
+ "Expected notification not to contain " + localizedImportItem);
+ }
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
+
diff --git a/browser/components/migration/tests/marionette/manifest.ini b/browser/components/migration/tests/marionette/manifest.ini
new file mode 100644
index 000000000..3f404e724
--- /dev/null
+++ b/browser/components/migration/tests/marionette/manifest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+run-if = buildapp == 'browser'
+
+[test_refresh_firefox.py]
+
diff --git a/browser/components/migration/tests/marionette/test_refresh_firefox.py b/browser/components/migration/tests/marionette/test_refresh_firefox.py
new file mode 100644
index 000000000..b348a3dcd
--- /dev/null
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -0,0 +1,416 @@
+import os
+import shutil
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestFirefoxRefresh(MarionetteTestCase):
+ _username = "marionette-test-login"
+ _password = "marionette-test-password"
+ _bookmarkURL = "about:mozilla"
+ _bookmarkText = "Some bookmark from Marionette"
+
+ _cookieHost = "firefox-refresh.marionette-test.mozilla.org"
+ _cookiePath = "some/cookie/path"
+ _cookieName = "somecookie"
+ _cookieValue = "some cookie value"
+
+ _historyURL = "http://firefox-refresh.marionette-test.mozilla.org/"
+ _historyTitle = "Test visit for Firefox Reset"
+
+ _formHistoryFieldName = "some-very-unique-marionette-only-firefox-reset-field"
+ _formHistoryValue = "special-pumpkin-value"
+
+ _expectedURLs = ["about:robots", "about:mozilla"]
+
+ def savePassword(self):
+ self.runCode("""
+ let myLogin = new global.LoginInfo(
+ "test.marionette.mozilla.com",
+ "http://test.marionette.mozilla.com/some/form/",
+ null,
+ arguments[0],
+ arguments[1],
+ "username",
+ "password"
+ );
+ Services.logins.addLogin(myLogin)
+ """, script_args=[self._username, self._password])
+
+ def createBookmark(self):
+ self.marionette.execute_script("""
+ let url = arguments[0];
+ let title = arguments[1];
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.bookmarksMenuFolder,
+ makeURI(url), 0, title);
+ """, script_args=[self._bookmarkURL, self._bookmarkText])
+
+ def createHistory(self):
+ error = self.runAsyncCode("""
+ // Copied from PlacesTestUtils, which isn't available in Marionette tests.
+ let didReturn;
+ PlacesUtils.asyncHistory.updatePlaces(
+ [{title: arguments[1], uri: makeURI(arguments[0]), visits: [{
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ visitDate: (Date.now() - 5000) * 1000,
+ referrerURI: makeURI("about:mozilla"),
+ }]
+ }],
+ {
+ handleError(resultCode, place) {
+ didReturn = true;
+ marionetteScriptFinished("Unexpected error in adding visit: " + resultCode);
+ },
+ handleResult() {},
+ handleCompletion() {
+ if (!didReturn) {
+ marionetteScriptFinished(false);
+ }
+ },
+ }
+ );
+ """, script_args=[self._historyURL, self._historyTitle])
+ if error:
+ print error
+
+ def createFormHistory(self):
+ error = self.runAsyncCode("""
+ let updateDefinition = {
+ op: "add",
+ fieldname: arguments[0],
+ value: arguments[1],
+ firstUsed: (Date.now() - 5000) * 1000,
+ };
+ let finished = false;
+ global.FormHistory.update(updateDefinition, {
+ handleError(error) {
+ finished = true;
+ marionetteScriptFinished(error);
+ },
+ handleCompletion() {
+ if (!finished) {
+ marionetteScriptFinished(false);
+ }
+ }
+ });
+ """, script_args=[self._formHistoryFieldName, self._formHistoryValue])
+ if error:
+ print error
+
+ def createCookie(self):
+ self.runCode("""
+ // Expire in 15 minutes:
+ let expireTime = Math.floor(Date.now() / 1000) + 15 * 60;
+ Services.cookies.add(arguments[0], arguments[1], arguments[2], arguments[3],
+ true, false, false, expireTime);
+ """, script_args=[self._cookieHost, self._cookiePath, self._cookieName, self._cookieValue])
+
+ def createSession(self):
+ self.runAsyncCode("""
+ const COMPLETE_STATE = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+ let {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+ let expectedURLs = Array.from(arguments[0])
+ gBrowser.addTabsProgressListener({
+ onStateChange(browser, webprogress, request, flags, status) {
+ try {
+ request && request.QueryInterface(Ci.nsIChannel);
+ } catch (ex) {}
+ let uriLoaded = request.originalURI && request.originalURI.spec;
+ if ((flags & COMPLETE_STATE == COMPLETE_STATE) && uriLoaded &&
+ expectedURLs.includes(uriLoaded)) {
+ TabStateFlusher.flush(browser).then(function() {
+ expectedURLs.splice(expectedURLs.indexOf(uriLoaded), 1);
+ if (!expectedURLs.length) {
+ gBrowser.removeTabsProgressListener(this);
+ marionetteScriptFinished();
+ }
+ });
+ }
+ }
+ });
+ for (let url of expectedURLs) {
+ gBrowser.addTab(url);
+ }
+ """, script_args=[self._expectedURLs])
+
+ def checkPassword(self):
+ loginInfo = self.marionette.execute_script("""
+ let ary = Services.logins.findLogins({},
+ "test.marionette.mozilla.com",
+ "http://test.marionette.mozilla.com/some/form/",
+ null, {});
+ return ary.length ? ary : {username: "null", password: "null"};
+ """)
+ self.assertEqual(len(loginInfo), 1)
+ self.assertEqual(loginInfo[0]['username'], self._username)
+ self.assertEqual(loginInfo[0]['password'], self._password)
+
+ loginCount = self.marionette.execute_script("""
+ return Services.logins.getAllLogins().length;
+ """)
+ self.assertEqual(loginCount, 1, "No other logins are present")
+
+ def checkBookmark(self):
+ titleInBookmarks = self.marionette.execute_script("""
+ let url = arguments[0];
+ let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(makeURI(url), {}, {});
+ return bookmarkIds.length == 1 ? PlacesUtils.bookmarks.getItemTitle(bookmarkIds[0]) : "";
+ """, script_args=[self._bookmarkURL])
+ self.assertEqual(titleInBookmarks, self._bookmarkText)
+
+ def checkHistory(self):
+ historyResults = self.runAsyncCode("""
+ let placeInfos = [];
+ PlacesUtils.asyncHistory.getPlacesInfo(makeURI(arguments[0]), {
+ handleError(resultCode, place) {
+ placeInfos = null;
+ marionetteScriptFinished("Unexpected error in fetching visit: " + resultCode);
+ },
+ handleResult(placeInfo) {
+ placeInfos.push(placeInfo);
+ },
+ handleCompletion() {
+ if (placeInfos) {
+ if (!placeInfos.length) {
+ marionetteScriptFinished("No visits found");
+ } else {
+ marionetteScriptFinished(placeInfos);
+ }
+ }
+ },
+ });
+ """, script_args=[self._historyURL])
+ if type(historyResults) == str:
+ self.fail(historyResults)
+ return
+
+ historyCount = len(historyResults)
+ self.assertEqual(historyCount, 1, "Should have exactly 1 entry for URI, got %d" % historyCount)
+ if historyCount == 1:
+ self.assertEqual(historyResults[0]['title'], self._historyTitle)
+
+ def checkFormHistory(self):
+ formFieldResults = self.runAsyncCode("""
+ let results = [];
+ global.FormHistory.search(["value"], {fieldname: arguments[0]}, {
+ handleError(error) {
+ results = error;
+ },
+ handleResult(result) {
+ results.push(result);
+ },
+ handleCompletion() {
+ marionetteScriptFinished(results);
+ },
+ });
+ """, script_args=[self._formHistoryFieldName])
+ if type(formFieldResults) == str:
+ self.fail(formFieldResults)
+ return
+
+ formFieldResultCount = len(formFieldResults)
+ self.assertEqual(formFieldResultCount, 1, "Should have exactly 1 entry for this field, got %d" % formFieldResultCount)
+ if formFieldResultCount == 1:
+ self.assertEqual(formFieldResults[0]['value'], self._formHistoryValue)
+
+ formHistoryCount = self.runAsyncCode("""
+ let count;
+ let callbacks = {
+ handleResult: rv => count = rv,
+ handleCompletion() {
+ marionetteScriptFinished(count);
+ },
+ };
+ global.FormHistory.count({}, callbacks);
+ """)
+ self.assertEqual(formHistoryCount, 1, "There should be only 1 entry in the form history")
+
+ def checkCookie(self):
+ cookieInfo = self.runCode("""
+ try {
+ let cookieEnum = Services.cookies.getCookiesFromHost(arguments[0]);
+ let cookie = null;
+ while (cookieEnum.hasMoreElements()) {
+ let hostCookie = cookieEnum.getNext();
+ hostCookie.QueryInterface(Ci.nsICookie2);
+ // getCookiesFromHost returns any cookie from the BASE host.
+ if (hostCookie.rawHost != arguments[0])
+ continue;
+ if (cookie != null) {
+ return "more than 1 cookie! That shouldn't happen!";
+ }
+ cookie = hostCookie;
+ }
+ return {path: cookie.path, name: cookie.name, value: cookie.value};
+ } catch (ex) {
+ return "got exception trying to fetch cookie: " + ex;
+ }
+ """, script_args=[self._cookieHost])
+ if not isinstance(cookieInfo, dict):
+ self.fail(cookieInfo)
+ return
+ self.assertEqual(cookieInfo['path'], self._cookiePath)
+ self.assertEqual(cookieInfo['value'], self._cookieValue)
+ self.assertEqual(cookieInfo['name'], self._cookieName)
+
+ def checkSession(self):
+ tabURIs = self.runCode("""
+ return [... gBrowser.browsers].map(b => b.currentURI && b.currentURI.spec)
+ """)
+ self.assertSequenceEqual(tabURIs, ["about:welcomeback"])
+
+ tabURIs = self.runAsyncCode("""
+ let mm = gBrowser.selectedBrowser.messageManager;
+ let fs = function() {
+ content.document.getElementById("errorTryAgain").click();
+ };
+ let {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+ window.addEventListener("SSWindowStateReady", function testSSPostReset() {
+ window.removeEventListener("SSWindowStateReady", testSSPostReset, false);
+ Promise.all(gBrowser.browsers.map(b => TabStateFlusher.flush(b))).then(function() {
+ marionetteScriptFinished([... gBrowser.browsers].map(b => b.currentURI && b.currentURI.spec));
+ });
+ }, false);
+ mm.loadFrameScript("data:application/javascript,(" + fs.toString() + ")()", true);
+ """)
+ self.assertSequenceEqual(tabURIs, ["about:blank"] + self._expectedURLs)
+ pass
+
+ def checkProfile(self, hasMigrated=False):
+ self.checkPassword()
+ self.checkBookmark()
+ self.checkHistory()
+ self.checkFormHistory()
+ self.checkCookie()
+ if hasMigrated:
+ self.checkSession()
+
+ def createProfileData(self):
+ self.savePassword()
+ self.createBookmark()
+ self.createHistory()
+ self.createFormHistory()
+ self.createCookie()
+ self.createSession()
+
+ def setUpScriptData(self):
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+ self.marionette.execute_script("""
+ global.LoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", "nsILoginInfo", "init");
+ global.profSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
+ global.Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+ global.FormHistory = Cu.import("resource://gre/modules/FormHistory.jsm", {}).FormHistory;
+ """, new_sandbox=False, sandbox='system')
+
+ def runCode(self, script, *args, **kwargs):
+ return self.marionette.execute_script(script, new_sandbox=False, sandbox='system', *args, **kwargs)
+
+ def runAsyncCode(self, script, *args, **kwargs):
+ return self.marionette.execute_async_script(script, new_sandbox=False, sandbox='system', *args, **kwargs)
+
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ self.setUpScriptData()
+
+ self.reset_profile_path = None
+ self.desktop_backup_path = None
+
+ self.createProfileData()
+
+ def tearDown(self):
+ # Force yet another restart with a clean profile to disconnect from the
+ # profile and environment changes we've made, to leave a more or less
+ # blank slate for the next person.
+ self.marionette.restart(clean=True, in_app=False)
+ self.setUpScriptData()
+
+ # Super
+ MarionetteTestCase.tearDown(self)
+
+ # Some helpers to deal with removing a load of files
+ import errno, stat
+ def handleRemoveReadonly(func, path, exc):
+ excvalue = exc[1]
+ if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
+ os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
+ func(path)
+ else:
+ raise
+
+ if self.desktop_backup_path:
+ shutil.rmtree(self.desktop_backup_path, ignore_errors=False, onerror=handleRemoveReadonly)
+
+ if self.reset_profile_path:
+ # Remove ourselves from profiles.ini
+ profileLeafName = os.path.basename(os.path.normpath(self.reset_profile_path))
+ self.runCode("""
+ let [salt, name] = arguments[0].split(".");
+ let profile = global.profSvc.getProfileByName(name);
+ profile.remove(false)
+ global.profSvc.flush();
+ """, script_args=[profileLeafName])
+ # And delete all the files.
+ shutil.rmtree(self.reset_profile_path, ignore_errors=False, onerror=handleRemoveReadonly)
+
+ def doReset(self):
+ self.runCode("""
+ // Ensure the current (temporary) profile is in profiles.ini:
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let profileName = "marionette-test-profile-" + Date.now();
+ let myProfile = global.profSvc.createProfile(profD, profileName);
+ global.profSvc.flush()
+
+ // Now add the reset parameters:
+ let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ let allMarionettePrefs = Services.prefs.getChildList("marionette.");
+ let prefObj = {};
+ for (let pref of allMarionettePrefs) {
+ let prefSuffix = pref.substr("marionette.".length);
+ let prefVal = global.Preferences.get(pref);
+ prefObj[prefSuffix] = prefVal;
+ }
+ let marionetteInfo = JSON.stringify(prefObj);
+ env.set("MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS", marionetteInfo);
+ env.set("MOZ_RESET_PROFILE_RESTART", "1");
+ env.set("XRE_PROFILE_PATH", arguments[0]);
+ env.set("XRE_PROFILE_NAME", profileName);
+ """, script_args=[self.marionette.instance.profile.profile])
+
+ profileLeafName = os.path.basename(os.path.normpath(self.marionette.instance.profile.profile))
+
+ # Now restart the browser to get it reset:
+ self.marionette.restart(clean=False, in_app=True)
+ self.setUpScriptData()
+
+ # Determine the new profile path (we'll need to remove it when we're done)
+ self.reset_profile_path = self.runCode("""
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ return profD.path;
+ """)
+
+ # Determine the backup path
+ self.desktop_backup_path = self.runCode("""
+ let container;
+ try {
+ container = Services.dirsvc.get("Desk", Ci.nsIFile);
+ } catch (ex) {
+ container = Services.dirsvc.get("Home", Ci.nsIFile);
+ }
+ let bundle = Services.strings.createBundle("chrome://mozapps/locale/profile/profileSelection.properties");
+ let dirName = bundle.formatStringFromName("resetBackupDirectory", [Services.appinfo.name], 1);
+ container.append(dirName);
+ container.append(arguments[0]);
+ return container.path;
+ """, script_args = [profileLeafName])
+
+ self.assertTrue(os.path.isdir(self.reset_profile_path), "Reset profile path should be present")
+ self.assertTrue(os.path.isdir(self.desktop_backup_path), "Backup profile path should be present")
+
+ def testReset(self):
+ self.checkProfile()
+
+ self.doReset()
+
+ # Now check that we're doing OK...
+ self.checkProfile(hasMigrated=True)
diff --git a/browser/components/migration/tests/unit/.eslintrc.js b/browser/components/migration/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..ba65517f9
--- /dev/null
+++ b/browser/components/migration/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/migration/tests/unit/AppData/Local/Google/Chrome/User Data/Default/Login Data b/browser/components/migration/tests/unit/AppData/Local/Google/Chrome/User Data/Default/Login Data
new file mode 100644
index 000000000..914149c71
--- /dev/null
+++ b/browser/components/migration/tests/unit/AppData/Local/Google/Chrome/User Data/Default/Login Data
Binary files differ
diff --git a/browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Default/Cookies b/browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Default/Cookies
new file mode 100644
index 000000000..83d855cb3
--- /dev/null
+++ b/browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Default/Cookies
Binary files differ
diff --git a/browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Local State b/browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Local State
new file mode 100644
index 000000000..01b99455e
--- /dev/null
+++ b/browser/components/migration/tests/unit/Library/Application Support/Google/Chrome/Local State
@@ -0,0 +1,22 @@
+{
+ "profile" : {
+ "info_cache" : {
+ "Default" : {
+ "active_time" : 1430950755.65137,
+ "is_using_default_name" : true,
+ "is_ephemeral" : false,
+ "is_omitted_from_profile_list" : false,
+ "user_name" : "",
+ "background_apps" : false,
+ "is_using_default_avatar" : true,
+ "avatar_icon" : "chrome://theme/IDR_PROFILE_AVATAR_0",
+ "name" : "Person 1"
+ }
+ },
+ "profiles_created" : 1,
+ "last_used" : "Default",
+ "last_active_profiles" : [
+ "Default"
+ ]
+ }
+}
diff --git a/browser/components/migration/tests/unit/Library/Safari/Bookmarks.plist b/browser/components/migration/tests/unit/Library/Safari/Bookmarks.plist
new file mode 100644
index 000000000..40783c7b1
--- /dev/null
+++ b/browser/components/migration/tests/unit/Library/Safari/Bookmarks.plist
Binary files differ
diff --git a/browser/components/migration/tests/unit/head_migration.js b/browser/components/migration/tests/unit/head_migration.js
new file mode 100644
index 000000000..d3c258d54
--- /dev/null
+++ b/browser/components/migration/tests/unit/head_migration.js
@@ -0,0 +1,69 @@
+"use strict";
+
+/* exported gProfD, promiseMigration, registerFakePath */
+
+var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.importGlobalProperties([ "URL" ]);
+
+Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/PromiseUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://testing-common/TestUtils.jsm");
+Cu.import("resource://testing-common/PlacesTestUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+// Initialize profile.
+var gProfD = do_get_profile();
+
+Cu.import("resource://testing-common/AppInfo.jsm"); /* globals updateAppInfo */
+updateAppInfo();
+
+/**
+ * Migrates the requested resource and waits for the migration to be complete.
+ */
+function promiseMigration(migrator, resourceType, aProfile = null) {
+ // Ensure resource migration is available.
+ let availableSources = migrator.getMigrateData(aProfile, false);
+ Assert.ok((availableSources & resourceType) > 0, "Resource supported by migrator");
+
+ return new Promise (resolve => {
+ Services.obs.addObserver(function onMigrationEnded() {
+ Services.obs.removeObserver(onMigrationEnded, "Migration:Ended");
+ resolve();
+ }, "Migration:Ended", false);
+
+ migrator.migrate(resourceType, null, aProfile);
+ });
+}
+
+/**
+ * Replaces a directory service entry with a given nsIFile.
+ */
+function registerFakePath(key, file) {
+ // Register our own provider for the Library directory.
+ let provider = {
+ getFile(prop, persistent) {
+ persistent.value = true;
+ if (prop == key) {
+ return file;
+ }
+ throw Cr.NS_ERROR_FAILURE;
+ },
+ QueryInterface: XPCOMUtils.generateQI([ Ci.nsIDirectoryServiceProvider ])
+ };
+ Services.dirsvc.QueryInterface(Ci.nsIDirectoryService)
+ .registerProvider(provider);
+ do_register_cleanup(() => {
+ Services.dirsvc.QueryInterface(Ci.nsIDirectoryService)
+ .unregisterProvider(provider);
+ });
+}
diff --git a/browser/components/migration/tests/unit/test_Chrome_cookies.js b/browser/components/migration/tests/unit/test_Chrome_cookies.js
new file mode 100644
index 000000000..006693951
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_Chrome_cookies.js
@@ -0,0 +1,51 @@
+"use strict";
+
+Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
+
+add_task(function* () {
+ registerFakePath("ULibDir", do_get_file("Library/"));
+ let migrator = MigrationUtils.getMigrator("chrome");
+
+ Assert.ok(migrator.sourceExists, "Sanity check the source exists");
+
+ const COOKIE = {
+ expiry: 2145934800,
+ host: "unencryptedcookie.invalid",
+ isHttpOnly: false,
+ isSession: false,
+ name: "testcookie",
+ path: "/",
+ value: "testvalue",
+ };
+
+ // Sanity check.
+ Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 0,
+ "There are no cookies initially");
+
+ const PROFILE = {
+ id: "Default",
+ name: "Person 1",
+ };
+
+ // Migrate unencrypted cookies.
+ yield promiseMigration(migrator, MigrationUtils.resourceTypes.COOKIES, PROFILE);
+
+ Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 1,
+ "Migrated the expected number of unencrypted cookies");
+ Assert.equal(Services.cookies.countCookiesFromHost("encryptedcookie.invalid"), 0,
+ "Migrated the expected number of encrypted cookies");
+
+ // Now check the cookie details.
+ let enumerator = Services.cookies.getCookiesFromHost(COOKIE.host, {});
+ Assert.ok(enumerator.hasMoreElements(), "Cookies available");
+ let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+
+ for (let prop of Object.keys(COOKIE)) {
+ Assert.equal(foundCookie[prop], COOKIE[prop], "Check cookie " + prop);
+ }
+
+ // Cleanup.
+ ForgetAboutSite.removeDataFromDomain(COOKIE.host);
+ Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 0,
+ "There are no cookies after cleanup");
+});
diff --git a/browser/components/migration/tests/unit/test_Chrome_passwords.js b/browser/components/migration/tests/unit/test_Chrome_passwords.js
new file mode 100644
index 000000000..49147bd61
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_Chrome_passwords.js
@@ -0,0 +1,219 @@
+"use strict";
+
+Cu.import("resource://gre/modules/OSCrypto.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PROFILE = {
+ id: "Default",
+ name: "Person 1",
+};
+
+const TEST_LOGINS = [
+ {
+ id: 1, // id of the row in the chrome login db
+ username: "username",
+ password: "password",
+ hostname: "https://c9.io",
+ formSubmitURL: "https://c9.io",
+ httpRealm: null,
+ usernameField: "inputEmail",
+ passwordField: "inputPassword",
+ timeCreated: 1437418416037,
+ timePasswordChanged: 1437418416037,
+ timesUsed: 1,
+ },
+ {
+ id: 2,
+ username: "username@gmail.com",
+ password: "password2",
+ hostname: "https://accounts.google.com",
+ formSubmitURL: "https://accounts.google.com",
+ httpRealm: null,
+ usernameField: "Email",
+ passwordField: "Passwd",
+ timeCreated: 1437418446598,
+ timePasswordChanged: 1437418446598,
+ timesUsed: 6,
+ },
+ {
+ id: 3,
+ username: "username",
+ password: "password3",
+ hostname: "https://www.facebook.com",
+ formSubmitURL: "https://www.facebook.com",
+ httpRealm: null,
+ usernameField: "email",
+ passwordField: "pass",
+ timeCreated: 1437418478851,
+ timePasswordChanged: 1437418478851,
+ timesUsed: 1,
+ },
+ {
+ id: 4,
+ username: "user",
+ password: "password",
+ hostname: "http://httpbin.org",
+ formSubmitURL: null,
+ httpRealm: "me@kennethreitz.com", // Digest auth.
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1437787462368,
+ timePasswordChanged: 1437787462368,
+ timesUsed: 1,
+ },
+ {
+ id: 5,
+ username: "buser",
+ password: "bpassword",
+ hostname: "http://httpbin.org",
+ formSubmitURL: null,
+ httpRealm: "Fake Realm", // Basic auth.
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1437787539233,
+ timePasswordChanged: 1437787539233,
+ timesUsed: 1,
+ },
+];
+
+var crypto = new OSCrypto();
+var dbConn;
+
+function promiseSetPassword(login) {
+ return new Promise((resolve, reject) => {
+ let stmt = dbConn.createAsyncStatement(`
+ UPDATE logins
+ SET password_value = :password_value
+ WHERE rowid = :rowid
+ `);
+ let passwordValue = crypto.stringToArray(crypto.encryptData(login.password));
+ stmt.bindBlobByName("password_value", passwordValue, passwordValue.length);
+ stmt.params.rowid = login.id;
+
+ stmt.executeAsync({
+ handleError(aError) {
+ reject("Error with the query: " + aError.message);
+ },
+
+ handleCompletion(aReason) {
+ if (aReason === Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ resolve();
+ } else {
+ reject("Query has failed: " + aReason);
+ }
+ },
+ });
+ stmt.finalize();
+ });
+}
+
+function checkLoginsAreEqual(passwordManagerLogin, chromeLogin, id) {
+ passwordManagerLogin.QueryInterface(Ci.nsILoginMetaInfo);
+
+ Assert.equal(passwordManagerLogin.username, chromeLogin.username,
+ "The two logins ID " + id + " have the same username");
+ Assert.equal(passwordManagerLogin.password, chromeLogin.password,
+ "The two logins ID " + id + " have the same password");
+ Assert.equal(passwordManagerLogin.hostname, chromeLogin.hostname,
+ "The two logins ID " + id + " have the same hostname");
+ Assert.equal(passwordManagerLogin.formSubmitURL, chromeLogin.formSubmitURL,
+ "The two logins ID " + id + " have the same formSubmitURL");
+ Assert.equal(passwordManagerLogin.httpRealm, chromeLogin.httpRealm,
+ "The two logins ID " + id + " have the same httpRealm");
+ Assert.equal(passwordManagerLogin.usernameField, chromeLogin.usernameField,
+ "The two logins ID " + id + " have the same usernameElement");
+ Assert.equal(passwordManagerLogin.passwordField, chromeLogin.passwordField,
+ "The two logins ID " + id + " have the same passwordElement");
+ Assert.equal(passwordManagerLogin.timeCreated, chromeLogin.timeCreated,
+ "The two logins ID " + id + " have the same timeCreated");
+ Assert.equal(passwordManagerLogin.timePasswordChanged, chromeLogin.timePasswordChanged,
+ "The two logins ID " + id + " have the same timePasswordChanged");
+ Assert.equal(passwordManagerLogin.timesUsed, chromeLogin.timesUsed,
+ "The two logins ID " + id + " have the same timesUsed");
+}
+
+function generateDifferentLogin(login) {
+ let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+
+ newLogin.init(login.hostname, login.formSubmitURL, null,
+ login.username, login.password + 1, login.usernameField + 1,
+ login.passwordField + 1);
+ newLogin.QueryInterface(Ci.nsILoginMetaInfo);
+ newLogin.timeCreated = login.timeCreated + 1;
+ newLogin.timePasswordChanged = login.timePasswordChanged + 1;
+ newLogin.timesUsed = login.timesUsed + 1;
+ return newLogin;
+}
+
+add_task(function* setup() {
+ let loginDataFile = do_get_file("AppData/Local/Google/Chrome/User Data/Default/Login Data");
+ dbConn = Services.storage.openUnsharedDatabase(loginDataFile);
+ registerFakePath("LocalAppData", do_get_file("AppData/Local/"));
+
+ do_register_cleanup(() => {
+ Services.logins.removeAllLogins();
+ dbConn.asyncClose();
+ crypto.finalize();
+ });
+});
+
+add_task(function* test_importIntoEmptyDB() {
+ for (let login of TEST_LOGINS) {
+ yield promiseSetPassword(login);
+ }
+
+ let migrator = MigrationUtils.getMigrator("chrome");
+ Assert.ok(migrator.sourceExists, "Sanity check the source exists");
+
+ let logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, 0, "There are no logins initially");
+
+ // Migrate the logins.
+ yield promiseMigration(migrator, MigrationUtils.resourceTypes.PASSWORDS, PROFILE);
+
+ logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, TEST_LOGINS.length, "Check login count after importing the data");
+ Assert.equal(logins.length, MigrationUtils._importQuantities.logins,
+ "Check telemetry matches the actual import.");
+
+ for (let i = 0; i < TEST_LOGINS.length; i++) {
+ checkLoginsAreEqual(logins[i], TEST_LOGINS[i], i + 1);
+ }
+});
+
+// Test that existing logins for the same primary key don't get overwritten
+add_task(function* test_importExistingLogins() {
+ let migrator = MigrationUtils.getMigrator("chrome");
+ Assert.ok(migrator.sourceExists, "Sanity check the source exists");
+
+ Services.logins.removeAllLogins();
+ let logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, 0, "There are no logins after removing all of them");
+
+ let newLogins = [];
+
+ // Create 3 new logins that are different but where the key properties are still the same.
+ for (let i = 0; i < 3; i++) {
+ newLogins.push(generateDifferentLogin(TEST_LOGINS[i]));
+ Services.logins.addLogin(newLogins[i]);
+ }
+
+ logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, newLogins.length, "Check login count after the insertion");
+
+ for (let i = 0; i < newLogins.length; i++) {
+ checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
+ }
+ // Migrate the logins.
+ yield promiseMigration(migrator, MigrationUtils.resourceTypes.PASSWORDS, PROFILE);
+
+ logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, TEST_LOGINS.length,
+ "Check there are still the same number of logins after re-importing the data");
+ Assert.equal(logins.length, MigrationUtils._importQuantities.logins,
+ "Check telemetry matches the actual import.");
+
+ for (let i = 0; i < newLogins.length; i++) {
+ checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
+ }
+});
diff --git a/browser/components/migration/tests/unit/test_Edge_availability.js b/browser/components/migration/tests/unit/test_Edge_availability.js
new file mode 100644
index 000000000..dba0e27bb
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_Edge_availability.js
@@ -0,0 +1,20 @@
+"use strict";
+
+const EDGE_AVAILABLE_MIGRATIONS =
+ MigrationUtils.resourceTypes.COOKIES |
+ MigrationUtils.resourceTypes.BOOKMARKS |
+ MigrationUtils.resourceTypes.HISTORY |
+ MigrationUtils.resourceTypes.PASSWORDS;
+
+add_task(function* () {
+ let migrator = MigrationUtils.getMigrator("edge");
+ Cu.import("resource://gre/modules/AppConstants.jsm");
+ Assert.equal(!!(migrator && migrator.sourceExists), AppConstants.isPlatformAndVersionAtLeast("win", "10"),
+ "Edge should be available for migration if and only if we're on Win 10+");
+ if (migrator) {
+ let migratableData = migrator.getMigrateData(null, false);
+ Assert.equal(migratableData, EDGE_AVAILABLE_MIGRATIONS,
+ "All the data types we expect should be available");
+ }
+});
+
diff --git a/browser/components/migration/tests/unit/test_Edge_db_migration.js b/browser/components/migration/tests/unit/test_Edge_db_migration.js
new file mode 100644
index 000000000..56ff612d5
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_Edge_db_migration.js
@@ -0,0 +1,471 @@
+"use strict";
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+let eseBackStage = Cu.import("resource:///modules/ESEDBReader.jsm");
+let ESE = eseBackStage.ESE;
+let KERNEL = eseBackStage.KERNEL;
+let gLibs = eseBackStage.gLibs;
+let COLUMN_TYPES = eseBackStage.COLUMN_TYPES;
+let declareESEFunction = eseBackStage.declareESEFunction;
+let loadLibraries = eseBackStage.loadLibraries;
+
+let gESEInstanceCounter = 1;
+
+ESE.JET_COLUMNCREATE_W = new ctypes.StructType("JET_COLUMNCREATE_W", [
+ {"cbStruct": ctypes.unsigned_long},
+ {"szColumnName": ESE.JET_PCWSTR},
+ {"coltyp": ESE.JET_COLTYP },
+ {"cbMax": ctypes.unsigned_long },
+ {"grbit": ESE.JET_GRBIT },
+ {"pvDefault": ctypes.voidptr_t},
+ {"cbDefault": ctypes.unsigned_long },
+ {"cp": ctypes.unsigned_long },
+ {"columnid": ESE.JET_COLUMNID},
+ {"err": ESE.JET_ERR},
+]);
+
+function createColumnCreationWrapper({name, type, cbMax}) {
+ // We use a wrapper object because we need to be sure the JS engine won't GC
+ // data that we're "only" pointing to.
+ let wrapper = {};
+ wrapper.column = new ESE.JET_COLUMNCREATE_W();
+ wrapper.column.cbStruct = ESE.JET_COLUMNCREATE_W.size;
+ let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
+ wrapper.name = new wchar_tArray(name.length + 1);
+ wrapper.name.value = String(name);
+ wrapper.column.szColumnName = wrapper.name;
+ wrapper.column.coltyp = type;
+ let fallback = 0;
+ switch (type) {
+ case COLUMN_TYPES.JET_coltypText:
+ fallback = 255;
+ // Intentional fall-through
+ case COLUMN_TYPES.JET_coltypLongText:
+ wrapper.column.cbMax = cbMax || fallback || 64 * 1024;
+ break;
+ case COLUMN_TYPES.JET_coltypGUID:
+ wrapper.column.cbMax = 16;
+ break;
+ case COLUMN_TYPES.JET_coltypBit:
+ wrapper.column.cbMax = 1;
+ break;
+ case COLUMN_TYPES.JET_coltypLongLong:
+ wrapper.column.cbMax = 8;
+ break;
+ default:
+ throw new Error("Unknown column type!");
+ }
+
+ wrapper.column.columnid = new ESE.JET_COLUMNID();
+ wrapper.column.grbit = 0;
+ wrapper.column.pvDefault = null;
+ wrapper.column.cbDefault = 0;
+ wrapper.column.cp = 0;
+
+ return wrapper;
+}
+
+// "forward declarations" of indexcreate and setinfo structs, which we don't use.
+ESE.JET_INDEXCREATE = new ctypes.StructType("JET_INDEXCREATE");
+ESE.JET_SETINFO = new ctypes.StructType("JET_SETINFO");
+
+ESE.JET_TABLECREATE_W = new ctypes.StructType("JET_TABLECREATE_W", [
+ {"cbStruct": ctypes.unsigned_long},
+ {"szTableName": ESE.JET_PCWSTR},
+ {"szTemplateTableName": ESE.JET_PCWSTR},
+ {"ulPages": ctypes.unsigned_long},
+ {"ulDensity": ctypes.unsigned_long},
+ {"rgcolumncreate": ESE.JET_COLUMNCREATE_W.ptr},
+ {"cColumns": ctypes.unsigned_long},
+ {"rgindexcreate": ESE.JET_INDEXCREATE.ptr},
+ {"cIndexes": ctypes.unsigned_long},
+ {"grbit": ESE.JET_GRBIT},
+ {"tableid": ESE.JET_TABLEID},
+ {"cCreated": ctypes.unsigned_long},
+]);
+
+function createTableCreationWrapper(tableName, columns) {
+ let wrapper = {};
+ let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
+ wrapper.name = new wchar_tArray(tableName.length + 1);
+ wrapper.name.value = String(tableName);
+ wrapper.table = new ESE.JET_TABLECREATE_W();
+ wrapper.table.cbStruct = ESE.JET_TABLECREATE_W.size;
+ wrapper.table.szTableName = wrapper.name;
+ wrapper.table.szTemplateTableName = null;
+ wrapper.table.ulPages = 1;
+ wrapper.table.ulDensity = 0;
+ let columnArrayType = ESE.JET_COLUMNCREATE_W.array(columns.length);
+ wrapper.columnAry = new columnArrayType();
+ wrapper.table.rgcolumncreate = wrapper.columnAry.addressOfElement(0);
+ wrapper.table.cColumns = columns.length;
+ wrapper.columns = [];
+ for (let i = 0; i < columns.length; i++) {
+ let column = columns[i];
+ let columnWrapper = createColumnCreationWrapper(column);
+ wrapper.columnAry.addressOfElement(i).contents = columnWrapper.column;
+ wrapper.columns.push(columnWrapper);
+ }
+ wrapper.table.rgindexcreate = null;
+ wrapper.table.cIndexes = 0;
+ return wrapper;
+}
+
+function convertValueForWriting(value, valueType) {
+ let buffer;
+ let valueOfValueType = ctypes.UInt64.lo(valueType);
+ switch (valueOfValueType) {
+ case COLUMN_TYPES.JET_coltypLongLong:
+ if (value instanceof Date) {
+ buffer = new KERNEL.FILETIME();
+ let sysTime = new KERNEL.SYSTEMTIME();
+ sysTime.wYear = value.getUTCFullYear();
+ sysTime.wMonth = value.getUTCMonth() + 1;
+ sysTime.wDay = value.getUTCDate();
+ sysTime.wHour = value.getUTCHours();
+ sysTime.wMinute = value.getUTCMinutes();
+ sysTime.wSecond = value.getUTCSeconds();
+ sysTime.wMilliseconds = value.getUTCMilliseconds();
+ let rv = KERNEL.SystemTimeToFileTime(sysTime.address(), buffer.address());
+ if (!rv) {
+ throw new Error("Failed to get FileTime.");
+ }
+ return [buffer, KERNEL.FILETIME.size];
+ }
+ throw new Error("Unrecognized value for longlong column");
+ case COLUMN_TYPES.JET_coltypLongText:
+ let wchar_tArray = ctypes.ArrayType(ctypes.char16_t);
+ buffer = new wchar_tArray(value.length + 1);
+ buffer.value = String(value);
+ return [buffer, buffer.length * 2];
+ case COLUMN_TYPES.JET_coltypBit:
+ buffer = new ctypes.uint8_t();
+ // Bizarre boolean values, but whatever:
+ buffer.value = value ? 255 : 0;
+ return [buffer, 1];
+ case COLUMN_TYPES.JET_coltypGUID:
+ let byteArray = ctypes.ArrayType(ctypes.uint8_t);
+ buffer = new byteArray(16);
+ let j = 0;
+ for (let i = 0; i < value.length; i++) {
+ if (!(/[0-9a-f]/i).test(value[i])) {
+ continue;
+ }
+ let byteAsHex = value.substr(i, 2);
+ buffer[j++] = parseInt(byteAsHex, 16);
+ i++;
+ }
+ return [buffer, 16];
+ }
+
+ throw new Error("Unknown type " + valueType);
+}
+
+let initializedESE = false;
+
+let eseDBWritingHelpers = {
+ setupDB(dbFile, tableName, columns, rows) {
+ if (!initializedESE) {
+ initializedESE = true;
+ loadLibraries();
+
+ KERNEL.SystemTimeToFileTime = gLibs.kernel.declare("SystemTimeToFileTime",
+ ctypes.default_abi, ctypes.bool, KERNEL.SYSTEMTIME.ptr, KERNEL.FILETIME.ptr);
+
+ declareESEFunction("CreateDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
+ ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT);
+ declareESEFunction("CreateTableColumnIndexW", ESE.JET_SESID, ESE.JET_DBID,
+ ESE.JET_TABLECREATE_W.ptr);
+ declareESEFunction("BeginTransaction", ESE.JET_SESID);
+ declareESEFunction("CommitTransaction", ESE.JET_SESID, ESE.JET_GRBIT);
+ declareESEFunction("PrepareUpdate", ESE.JET_SESID, ESE.JET_TABLEID,
+ ctypes.unsigned_long);
+ declareESEFunction("Update", ESE.JET_SESID, ESE.JET_TABLEID,
+ ctypes.voidptr_t, ctypes.unsigned_long,
+ ctypes.unsigned_long.ptr);
+ declareESEFunction("SetColumn", ESE.JET_SESID, ESE.JET_TABLEID,
+ ESE.JET_COLUMNID, ctypes.voidptr_t,
+ ctypes.unsigned_long, ESE.JET_GRBIT, ESE.JET_SETINFO.ptr);
+ ESE.SetSystemParameterW(null, 0, 64 /* JET_paramDatabasePageSize*/,
+ 8192, null);
+ }
+
+ let rootPath = dbFile.parent.path + "\\";
+ let logPath = rootPath + "LogFiles\\";
+
+ try {
+ this._instanceId = new ESE.JET_INSTANCE();
+ ESE.CreateInstanceW(this._instanceId.address(),
+ "firefox-dbwriter-" + (gESEInstanceCounter++));
+ this._instanceCreated = true;
+
+ ESE.SetSystemParameterW(this._instanceId.address(), 0,
+ 0 /* JET_paramSystemPath*/, 0, rootPath);
+ ESE.SetSystemParameterW(this._instanceId.address(), 0,
+ 1 /* JET_paramTempPath */, 0, rootPath);
+ ESE.SetSystemParameterW(this._instanceId.address(), 0,
+ 2 /* JET_paramLogFilePath*/, 0, logPath);
+ // Shouldn't try to call JetTerm if the following call fails.
+ this._instanceCreated = false;
+ ESE.Init(this._instanceId.address());
+ this._instanceCreated = true;
+ this._sessionId = new ESE.JET_SESID();
+ ESE.BeginSessionW(this._instanceId, this._sessionId.address(), null,
+ null);
+ this._sessionCreated = true;
+
+ this._dbId = new ESE.JET_DBID();
+ this._dbPath = rootPath + "spartan.edb";
+ ESE.CreateDatabaseW(this._sessionId, this._dbPath, null,
+ this._dbId.address(), 0);
+ this._opened = this._attached = true;
+
+ let tableCreationWrapper = createTableCreationWrapper(tableName, columns);
+ ESE.CreateTableColumnIndexW(this._sessionId, this._dbId,
+ tableCreationWrapper.table.address());
+ this._tableId = tableCreationWrapper.table.tableid;
+
+ let columnIdMap = new Map();
+ if (rows.length) {
+ // Iterate over the struct we passed into ESENT because they have the
+ // created column ids.
+ let columnCount = ctypes.UInt64.lo(tableCreationWrapper.table.cColumns);
+ let columnsPassed = tableCreationWrapper.table.rgcolumncreate;
+ for (let i = 0; i < columnCount; i++) {
+ let column = columnsPassed.contents;
+ columnIdMap.set(column.szColumnName.readString(), column);
+ columnsPassed = columnsPassed.increment();
+ }
+ ESE.ManualMove(this._sessionId, this._tableId,
+ -2147483648 /* JET_MoveFirst */, 0);
+ ESE.BeginTransaction(this._sessionId);
+ for (let row of rows) {
+ ESE.PrepareUpdate(this._sessionId, this._tableId, 0 /* JET_prepInsert */);
+ for (let columnName in row) {
+ let col = columnIdMap.get(columnName);
+ let colId = col.columnid;
+ let [val, valSize] = convertValueForWriting(row[columnName], col.coltyp);
+ /* JET_bitSetOverwriteLV */
+ ESE.SetColumn(this._sessionId, this._tableId, colId, val.address(), valSize, 4, null);
+ }
+ let actualBookmarkSize = new ctypes.unsigned_long();
+ ESE.Update(this._sessionId, this._tableId, null, 0, actualBookmarkSize.address());
+ }
+ ESE.CommitTransaction(this._sessionId, 0 /* JET_bitWaitLastLevel0Commit */);
+ }
+ } finally {
+ try {
+ this._close();
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ _close() {
+ if (this._tableId) {
+ ESE.FailSafeCloseTable(this._sessionId, this._tableId);
+ delete this._tableId;
+ }
+ if (this._opened) {
+ ESE.FailSafeCloseDatabase(this._sessionId, this._dbId, 0);
+ this._opened = false;
+ }
+ if (this._attached) {
+ ESE.FailSafeDetachDatabaseW(this._sessionId, this._dbPath);
+ this._attached = false;
+ }
+ if (this._sessionCreated) {
+ ESE.FailSafeEndSession(this._sessionId, 0);
+ this._sessionCreated = false;
+ }
+ if (this._instanceCreated) {
+ ESE.FailSafeTerm(this._instanceId);
+ this._instanceCreated = false;
+ }
+ },
+};
+
+add_task(function*() {
+ let tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ tempFile.append("fx-xpcshell-edge-db");
+ tempFile.createUnique(tempFile.DIRECTORY_TYPE, 0o600);
+
+ let db = tempFile.clone();
+ db.append("spartan.edb");
+
+ let logs = tempFile.clone();
+ logs.append("LogFiles");
+ logs.create(tempFile.DIRECTORY_TYPE, 0o600);
+
+ let creationDate = new Date(Date.now() - 5000);
+ const kEdgeMenuParent = "62d07e2b-5f0d-4e41-8426-5f5ec9717beb";
+ let itemsInDB = [
+ {
+ URL: "http://www.mozilla.org/",
+ Title: "Mozilla",
+ DateUpdated: new Date(creationDate.valueOf() + 100),
+ ItemId: "1c00c10a-15f6-4618-92dd-22575102a4da",
+ ParentId: kEdgeMenuParent,
+ IsFolder: false,
+ IsDeleted: false,
+ },
+ {
+ Title: "Folder",
+ DateUpdated: new Date(creationDate.valueOf() + 200),
+ ItemId: "564b21f2-05d6-4f7d-8499-304d00ccc3aa",
+ ParentId: kEdgeMenuParent,
+ IsFolder: true,
+ IsDeleted: false,
+ },
+ {
+ Title: "Item in folder",
+ URL: "http://www.iteminfolder.org/",
+ DateUpdated: new Date(creationDate.valueOf() + 300),
+ ItemId: "c295ddaf-04a1-424a-866c-0ebde011e7c8",
+ ParentId: "564b21f2-05d6-4f7d-8499-304d00ccc3aa",
+ IsFolder: false,
+ IsDeleted: false,
+ },
+ {
+ Title: "Deleted folder",
+ DateUpdated: new Date(creationDate.valueOf() + 400),
+ ItemId: "a547573c-4d4d-4406-a736-5b5462d93bca",
+ ParentId: kEdgeMenuParent,
+ IsFolder: true,
+ IsDeleted: true,
+ },
+ {
+ Title: "Deleted item",
+ URL: "http://www.deleteditem.org/",
+ DateUpdated: new Date(creationDate.valueOf() + 500),
+ ItemId: "37a574bb-b44b-4bbc-a414-908615536435",
+ ParentId: kEdgeMenuParent,
+ IsFolder: false,
+ IsDeleted: true,
+ },
+ {
+ Title: "Item in deleted folder (should be in root)",
+ URL: "http://www.itemindeletedfolder.org/",
+ DateUpdated: new Date(creationDate.valueOf() + 600),
+ ItemId: "74dd1cc3-4c5d-471f-bccc-7bc7c72fa621",
+ ParentId: "a547573c-4d4d-4406-a736-5b5462d93bca",
+ IsFolder: false,
+ IsDeleted: false,
+ },
+ {
+ Title: "_Favorites_Bar_",
+ DateUpdated: new Date(creationDate.valueOf() + 700),
+ ItemId: "921dc8a0-6c83-40ef-8df1-9bd1c5c56aaf",
+ ParentId: kEdgeMenuParent,
+ IsFolder: true,
+ IsDeleted: false,
+ },
+ {
+ Title: "Item in favorites bar",
+ URL: "http://www.iteminfavoritesbar.org/",
+ DateUpdated: new Date(creationDate.valueOf() + 800),
+ ItemId: "9f2b1ff8-b651-46cf-8f41-16da8bcb6791",
+ ParentId: "921dc8a0-6c83-40ef-8df1-9bd1c5c56aaf",
+ IsFolder: false,
+ IsDeleted: false,
+ },
+ ];
+ eseDBWritingHelpers.setupDB(db, "Favorites", [
+ {type: COLUMN_TYPES.JET_coltypLongText, name: "URL", cbMax: 4096},
+ {type: COLUMN_TYPES.JET_coltypLongText, name: "Title", cbMax: 4096},
+ {type: COLUMN_TYPES.JET_coltypLongLong, name: "DateUpdated"},
+ {type: COLUMN_TYPES.JET_coltypGUID, name: "ItemId"},
+ {type: COLUMN_TYPES.JET_coltypBit, name: "IsDeleted"},
+ {type: COLUMN_TYPES.JET_coltypBit, name: "IsFolder"},
+ {type: COLUMN_TYPES.JET_coltypGUID, name: "ParentId"},
+ ], itemsInDB);
+
+ let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=edge"]
+ .createInstance(Ci.nsIBrowserProfileMigrator);
+ let bookmarksMigrator = migrator.wrappedJSObject.getESEMigratorForTesting(db);
+ Assert.ok(bookmarksMigrator.exists, "Should recognize table we just created");
+
+ let source = MigrationUtils.getLocalizedString("sourceNameEdge");
+ let sourceLabel = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
+
+ let seenBookmarks = [];
+ let bookmarkObserver = {
+ onItemAdded(itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid) {
+ if (title.startsWith("Deleted")) {
+ ok(false, "Should not see deleted items being bookmarked!");
+ }
+ seenBookmarks.push({itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid});
+ },
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onItemRemoved() {},
+ onItemChanged() {},
+ onItemVisited() {},
+ onItemMoved() {},
+ };
+ PlacesUtils.bookmarks.addObserver(bookmarkObserver, false);
+
+ let migrateResult = yield new Promise(resolve => bookmarksMigrator.migrate(resolve)).catch(ex => {
+ Cu.reportError(ex);
+ Assert.ok(false, "Got an exception trying to migrate data! " + ex);
+ return false;
+ });
+ PlacesUtils.bookmarks.removeObserver(bookmarkObserver);
+ Assert.ok(migrateResult, "Migration should succeed");
+ Assert.equal(seenBookmarks.length, 7, "Should have seen 7 items being bookmarked.");
+ Assert.equal(seenBookmarks.filter(bm => bm.title != sourceLabel).length,
+ MigrationUtils._importQuantities.bookmarks,
+ "Telemetry should have items except for 'From Microsoft Edge' folders");
+
+ let menuParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.menuGuid);
+ Assert.equal(menuParents.length, 1, "Should have a single folder added to the menu");
+ let toolbarParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.toolbarGuid);
+ Assert.equal(toolbarParents.length, 1, "Should have a single item added to the toolbar");
+ let menuParentGuid = menuParents[0].itemGuid;
+ let toolbarParentGuid = toolbarParents[0].itemGuid;
+
+ let expectedTitlesInMenu = itemsInDB.filter(item => item.ParentId == kEdgeMenuParent).map(item => item.Title);
+ // Hacky, but seems like much the simplest way:
+ expectedTitlesInMenu.push("Item in deleted folder (should be in root)");
+ let expectedTitlesInToolbar = itemsInDB.filter(item => item.ParentId == "921dc8a0-6c83-40ef-8df1-9bd1c5c56aaf").map(item => item.Title);
+
+ let edgeNameStr = MigrationUtils.getLocalizedString("sourceNameEdge");
+ let importParentFolderName = MigrationUtils.getLocalizedString("importedBookmarksFolder", [edgeNameStr]);
+
+ for (let bookmark of seenBookmarks) {
+ let shouldBeInMenu = expectedTitlesInMenu.includes(bookmark.title);
+ let shouldBeInToolbar = expectedTitlesInToolbar.includes(bookmark.title);
+ if (bookmark.title == "Folder" || bookmark.title == importParentFolderName) {
+ Assert.equal(bookmark.itemType, PlacesUtils.bookmarks.TYPE_FOLDER,
+ "Bookmark " + bookmark.title + " should be a folder");
+ } else {
+ Assert.notEqual(bookmark.itemType, PlacesUtils.bookmarks.TYPE_FOLDER,
+ "Bookmark " + bookmark.title + " should not be a folder");
+ }
+
+ if (shouldBeInMenu) {
+ Assert.equal(bookmark.parentGuid, menuParentGuid, "Item '" + bookmark.title + "' should be in menu");
+ } else if (shouldBeInToolbar) {
+ Assert.equal(bookmark.parentGuid, toolbarParentGuid, "Item '" + bookmark.title + "' should be in toolbar");
+ } else if (bookmark.itemGuid == menuParentGuid || bookmark.itemGuid == toolbarParentGuid) {
+ Assert.ok(true, "Expect toolbar and menu folders to not be in menu or toolbar");
+ } else {
+ // Bit hacky, but we do need to check this.
+ Assert.equal(bookmark.title, "Item in folder", "Subfoldered item shouldn't be in menu or toolbar");
+ let parent = seenBookmarks.find(maybeParent => maybeParent.itemGuid == bookmark.parentGuid);
+ Assert.equal(parent && parent.title, "Folder", "Subfoldered item should be in subfolder labeled 'Folder'");
+ }
+
+ let dbItem = itemsInDB.find(someItem => bookmark.title == someItem.Title);
+ if (!dbItem) {
+ Assert.equal(bookmark.title, importParentFolderName, "Only the extra layer of folders isn't in the input we stuck in the DB.");
+ Assert.ok([menuParentGuid, toolbarParentGuid].includes(bookmark.itemGuid), "This item should be one of the containers");
+ } else {
+ Assert.equal(dbItem.URL || null, bookmark.url && bookmark.url.spec, "URL is correct");
+ Assert.equal(dbItem.DateUpdated.valueOf(), (new Date(bookmark.dateAdded / 1000)).valueOf(), "Date added is correct");
+ }
+ }
+});
+
diff --git a/browser/components/migration/tests/unit/test_IE7_passwords.js b/browser/components/migration/tests/unit/test_IE7_passwords.js
new file mode 100644
index 000000000..1ce016a7d
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_IE7_passwords.js
@@ -0,0 +1,397 @@
+"use strict";
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
+ "resource://gre/modules/OSCrypto.jsm");
+
+const IE7_FORM_PASSWORDS_MIGRATOR_NAME = "IE7FormPasswords";
+const LOGINS_KEY = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
+const EXTENSION = "-backup";
+const TESTED_WEBSITES = {
+ twitter: {
+ uri: makeURI("https://twitter.com"),
+ hash: "A89D42BC6406E27265B1AD0782B6F376375764A301",
+ data: [12, 0, 0, 0, 56, 0, 0, 0, 38, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 8, 0, 0, 0, 18, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 9, 0, 0, 0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 0, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 0, 0, 0],
+ logins: [
+ {
+ username: "abcdefgh",
+ password: "123456789",
+ hostname: "https://twitter.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439325854000,
+ timeLastUsed: 1439325854000,
+ timePasswordChanged: 1439325854000,
+ timesUsed: 1,
+ },
+ ],
+ },
+ facebook: {
+ uri: makeURI("https://www.facebook.com/"),
+ hash: "EF44D3E034009CB0FD1B1D81A1FF3F3335213BD796",
+ data: [12, 0, 0, 0, 152, 0, 0, 0, 160, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, 182, 125, 18, 121, 212, 208, 1, 9, 0, 0, 0, 20, 0, 0, 0, 88, 182, 125, 18, 121, 212, 208, 1, 9, 0, 0, 0, 40, 0, 0, 0, 134, 65, 33, 37, 121, 212, 208, 1, 9, 0, 0, 0, 60, 0, 0, 0, 134, 65, 33, 37, 121, 212, 208, 1, 9, 0, 0, 0, 80, 0, 0, 0, 45, 242, 246, 62, 121, 212, 208, 1, 9, 0, 0, 0, 100, 0, 0, 0, 45, 242, 246, 62, 121, 212, 208, 1, 9, 0, 0, 0, 120, 0, 0, 0, 28, 10, 193, 80, 121, 212, 208, 1, 9, 0, 0, 0, 140, 0, 0, 0, 28, 10, 193, 80, 121, 212, 208, 1, 9, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 48, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 48, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 49, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 49, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 50, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 50, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 51, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 51, 0, 0, 0],
+ logins: [
+ {
+ username: "username0",
+ password: "password0",
+ hostname: "https://www.facebook.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439326966000,
+ timeLastUsed: 1439326966000,
+ timePasswordChanged: 1439326966000,
+ timesUsed: 1,
+ },
+ {
+ username: "username1",
+ password: "password1",
+ hostname: "https://www.facebook.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439326997000,
+ timeLastUsed: 1439326997000,
+ timePasswordChanged: 1439326997000,
+ timesUsed: 1,
+ },
+ {
+ username: "username2",
+ password: "password2",
+ hostname: "https://www.facebook.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439327040000,
+ timeLastUsed: 1439327040000,
+ timePasswordChanged: 1439327040000,
+ timesUsed: 1,
+ },
+ {
+ username: "username3",
+ password: "password3",
+ hostname: "https://www.facebook.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439327070000,
+ timeLastUsed: 1439327070000,
+ timePasswordChanged: 1439327070000,
+ timesUsed: 1,
+ },
+ ],
+ },
+ live: {
+ uri: makeURI("https://login.live.com/"),
+ hash: "7B506F2D6B81D939A8E0456F036EE8970856FF705E",
+ data: [12, 0, 0, 0, 56, 0, 0, 0, 44, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 212, 17, 219, 140, 148, 212, 208, 1, 9, 0, 0, 0, 20, 0, 0, 0, 212, 17, 219, 140, 148, 212, 208, 1, 11, 0, 0, 0, 114, 0, 105, 0, 97, 0, 100, 0, 104, 0, 49, 6, 74, 6, 39, 6, 54, 6, 0, 0, 39, 6, 66, 6, 49, 6, 35, 6, 80, 0, 192, 0, 223, 0, 119, 0, 246, 0, 114, 0, 100, 0, 0, 0],
+ logins: [
+ {
+ username: "riadhرياض",
+ password: "اقرأPÀßwörd",
+ hostname: "https://login.live.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439338767000,
+ timeLastUsed: 1439338767000,
+ timePasswordChanged: 1439338767000,
+ timesUsed: 1,
+ },
+ ],
+ },
+ reddit: {
+ uri: makeURI("http://www.reddit.com/"),
+ hash: "B644028D1C109A91EC2C4B9D1F145E55A1FAE42065",
+ data: [12, 0, 0, 0, 152, 0, 0, 0, 212, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 8, 234, 114, 153, 212, 208, 1, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 97, 93, 131, 116, 153, 212, 208, 1, 3, 0, 0, 0, 14, 0, 0, 0, 97, 93, 131, 116, 153, 212, 208, 1, 16, 0, 0, 0, 48, 0, 0, 0, 88, 150, 78, 174, 153, 212, 208, 1, 4, 0, 0, 0, 58, 0, 0, 0, 88, 150, 78, 174, 153, 212, 208, 1, 29, 0, 0, 0, 118, 0, 0, 0, 79, 102, 137, 34, 154, 212, 208, 1, 15, 0, 0, 0, 150, 0, 0, 0, 79, 102, 137, 34, 154, 212, 208, 1, 30, 0, 0, 0, 97, 0, 0, 0, 0, 0, 252, 140, 173, 138, 146, 48, 0, 0, 66, 0, 105, 0, 116, 0, 116, 0, 101, 0, 32, 0, 98, 0, 101, 0, 115, 0, 116, 0, 228, 0, 116, 0, 105, 0, 103, 0, 101, 0, 110, 0, 0, 0, 205, 145, 110, 127, 198, 91, 1, 120, 0, 0, 31, 4, 48, 4, 64, 4, 62, 4, 59, 4, 76, 4, 32, 0, 67, 4, 65, 4, 63, 4, 53, 4, 72, 4, 61, 4, 62, 4, 32, 0, 65, 4, 49, 4, 64, 4, 62, 4, 72, 4, 53, 4, 61, 4, 46, 0, 32, 0, 18, 4, 62, 4, 57, 4, 66, 4, 56, 4, 0, 0, 40, 6, 51, 6, 69, 6, 32, 0, 39, 6, 68, 6, 68, 6, 71, 6, 32, 0, 39, 6, 68, 6, 49, 6, 45, 6, 69, 6, 70, 6, 0, 0, 118, 0, 101, 0, 117, 0, 105, 0, 108, 0, 108, 0, 101, 0, 122, 0, 32, 0, 108, 0, 101, 0, 32, 0, 118, 0, 233, 0, 114, 0, 105, 0, 102, 0, 105, 0, 101, 0, 114, 0, 32, 0, 224, 0, 32, 0, 110, 0, 111, 0, 117, 0, 118, 0, 101, 0, 97, 0, 117, 0, 0, 0],
+ logins: [
+ {
+ username: "購読を",
+ password: "Bitte bestätigen",
+ hostname: "http://www.reddit.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439340874000,
+ timeLastUsed: 1439340874000,
+ timePasswordChanged: 1439340874000,
+ timesUsed: 1,
+ },
+ {
+ username: "重置密码",
+ password: "Пароль успешно сброшен. Войти",
+ hostname: "http://www.reddit.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439340971000,
+ timeLastUsed: 1439340971000,
+ timePasswordChanged: 1439340971000,
+ timesUsed: 1,
+ },
+ {
+ username: "بسم الله الرحمن",
+ password: "veuillez le vérifier à nouveau",
+ hostname: "http://www.reddit.com",
+ formSubmitURL: "",
+ httpRealm: null,
+ usernameField: "",
+ passwordField: "",
+ timeCreated: 1439341166000,
+ timeLastUsed: 1439341166000,
+ timePasswordChanged: 1439341166000,
+ timesUsed: 1,
+ },
+ ],
+ },
+};
+
+const TESTED_URLS = [
+ "http://a.foo.com",
+ "http://b.foo.com",
+ "http://c.foo.com",
+ "http://www.test.net",
+ "http://www.test.net/home",
+ "http://www.test.net/index",
+ "https://a.bar.com",
+ "https://b.bar.com",
+ "https://c.bar.com",
+];
+
+var nsIWindowsRegKey = Ci.nsIWindowsRegKey;
+var Storage2Key;
+
+/*
+ * If the key value exists, it's going to be backed up and replaced, so the value could be restored.
+ * Otherwise a new value is going to be created.
+ */
+function backupAndStore(key, name, value) {
+ if (key.hasValue(name)) {
+ // backup the the current value
+ let type = key.getValueType(name);
+ // create a new value using use the current value name followed by EXTENSION as its new name
+ switch (type) {
+ case nsIWindowsRegKey.TYPE_STRING:
+ key.writeStringValue(name + EXTENSION, key.readStringValue(name));
+ break;
+ case nsIWindowsRegKey.TYPE_BINARY:
+ key.writeBinaryValue(name + EXTENSION, key.readBinaryValue(name));
+ break;
+ case nsIWindowsRegKey.TYPE_INT:
+ key.writeIntValue(name + EXTENSION, key.readIntValue(name));
+ break;
+ case nsIWindowsRegKey.TYPE_INT64:
+ key.writeInt64Value(name + EXTENSION, key.readInt64Value(name));
+ break;
+ }
+ }
+ key.writeBinaryValue(name, value);
+}
+
+// Remove all values where their names are members of the names array from the key of registry
+function removeAllValues(key, names) {
+ for (let name of names) {
+ key.removeValue(name);
+ }
+}
+
+// Restore all the backed up values
+function restore(key) {
+ let count = key.valueCount;
+ let names = []; // the names of the key values
+ for (let i = 0; i < count; ++i) {
+ names.push(key.getValueName(i));
+ }
+
+ for (let name of names) {
+ // backed up values have EXTENSION at the end of their names
+ if (name.lastIndexOf(EXTENSION) == name.length - EXTENSION.length) {
+ let valueName = name.substr(0, name.length - EXTENSION.length);
+ let type = key.getValueType(name);
+ // create a new value using the name before the backup and removed the backed up one
+ switch (type) {
+ case nsIWindowsRegKey.TYPE_STRING:
+ key.writeStringValue(valueName, key.readStringValue(name));
+ key.removeValue(name);
+ break;
+ case nsIWindowsRegKey.TYPE_BINARY:
+ key.writeBinaryValue(valueName, key.readBinaryValue(name));
+ key.removeValue(name);
+ break;
+ case nsIWindowsRegKey.TYPE_INT:
+ key.writeIntValue(valueName, key.readIntValue(name));
+ key.removeValue(name);
+ break;
+ case nsIWindowsRegKey.TYPE_INT64:
+ key.writeInt64Value(valueName, key.readInt64Value(name));
+ key.removeValue(name);
+ break;
+ }
+ }
+ }
+}
+
+function checkLoginsAreEqual(passwordManagerLogin, IELogin, id) {
+ passwordManagerLogin.QueryInterface(Ci.nsILoginMetaInfo);
+ for (let attribute in IELogin) {
+ Assert.equal(passwordManagerLogin[attribute], IELogin[attribute],
+ "The two logins ID " + id + " have the same " + attribute);
+ }
+}
+
+function createRegistryPath(path) {
+ let loginPath = path.split("\\");
+ let parentKey = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(nsIWindowsRegKey);
+ let currentPath = [];
+ for (let currentKey of loginPath) {
+ parentKey.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, currentPath.join("\\"),
+ nsIWindowsRegKey.ACCESS_ALL);
+
+ if (!parentKey.hasChild(currentKey)) {
+ parentKey.createChild(currentKey, 0);
+ }
+ currentPath.push(currentKey);
+ parentKey.close();
+ }
+}
+
+function getFirstResourceOfType(type) {
+ let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=ie"]
+ .createInstance(Ci.nsISupports)
+ .wrappedJSObject;
+ let migrators = migrator.getResources();
+ for (let m of migrators) {
+ if (m.name == IE7_FORM_PASSWORDS_MIGRATOR_NAME && m.type == type) {
+ return m;
+ }
+ }
+ throw new Error("failed to find the " + type + " migrator");
+}
+
+function makeURI(aURL) {
+ return Services.io.newURI(aURL, null, null);
+}
+
+add_task(function* setup() {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ Assert.throws(() => getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS),
+ "The migrator doesn't exist for win8+");
+ return;
+ }
+ // create the path to Storage2 in the registry if it doest exist.
+ createRegistryPath(LOGINS_KEY);
+ Storage2Key = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(nsIWindowsRegKey);
+ Storage2Key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, LOGINS_KEY,
+ nsIWindowsRegKey.ACCESS_ALL);
+
+ // create a dummy value otherwise the migrator doesn't exist
+ if (!Storage2Key.hasValue("dummy")) {
+ Storage2Key.writeBinaryValue("dummy", "dummy");
+ }
+});
+
+add_task(function* test_passwordsNotAvailable() {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ return;
+ }
+
+ let migrator = getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS);
+ Assert.ok(migrator.exists, "The migrator has to exist");
+ let logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, 0, "There are no logins at the beginning of the test");
+
+ let uris = []; // the uris of the migrated logins
+ for (let url of TESTED_URLS) {
+ uris.push(makeURI(url));
+ // in this test, there is no IE login data in the registry, so after the migration, the number
+ // of logins in the store should be 0
+ migrator._migrateURIs(uris);
+ logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, 0,
+ "There are no logins after doing the migration without adding values to the registry");
+ }
+});
+
+add_task(function* test_passwordsAvailable() {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ return;
+ }
+
+ let crypto = new OSCrypto();
+ let hashes = []; // the hashes of all migrator websites, this is going to be used for the clean up
+
+ do_register_cleanup(() => {
+ Services.logins.removeAllLogins();
+ logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, 0, "There are no logins after the cleanup");
+ // remove all the values created in this test from the registry
+ removeAllValues(Storage2Key, hashes);
+ // restore all backed up values
+ restore(Storage2Key);
+
+ // clean the dummy value
+ if (Storage2Key.hasValue("dummy")) {
+ Storage2Key.removeValue("dummy");
+ }
+ Storage2Key.close();
+ crypto.finalize();
+ });
+
+ let migrator = getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS);
+ Assert.ok(migrator.exists, "The migrator has to exist");
+ let logins = Services.logins.getAllLogins({});
+ Assert.equal(logins.length, 0, "There are no logins at the beginning of the test");
+
+ let uris = []; // the uris of the migrated logins
+
+ let loginCount = 0;
+ for (let current in TESTED_WEBSITES) {
+ let website = TESTED_WEBSITES[current];
+ // backup the current the registry value if it exists and replace the existing value/create a
+ // new value with the encrypted data
+ backupAndStore(Storage2Key, website.hash,
+ crypto.encryptData(crypto.arrayToString(website.data),
+ website.uri.spec, true));
+ Assert.ok(migrator.exists, "The migrator has to exist");
+ uris.push(website.uri);
+ hashes.push(website.hash);
+
+ migrator._migrateURIs(uris);
+ logins = Services.logins.getAllLogins({});
+ // check that the number of logins in the password manager has increased as expected which means
+ // that all the values for the current website were imported
+ loginCount += website.logins.length;
+ Assert.equal(logins.length, loginCount,
+ "The number of logins has increased after the migration");
+ // NB: because telemetry records any login data passed to the login manager, it
+ // also gets told about logins that are duplicates or invalid (for one reason
+ // or another) and so its counts might exceed those of the login manager itself.
+ Assert.greaterOrEqual(MigrationUtils._importQuantities.logins, loginCount,
+ "Telemetry quantities equal or exceed the actual import.");
+ // Reset - this normally happens at the start of a new migration, but we're calling
+ // the migrator directly so can't rely on that:
+ MigrationUtils._importQuantities.logins = 0;
+
+ let startIndex = loginCount - website.logins.length;
+ // compares the imported password manager logins with their expected logins
+ for (let i = 0; i < website.logins.length; i++) {
+ checkLoginsAreEqual(logins[startIndex + i], website.logins[i],
+ " " + current + " - " + i + " ");
+ }
+ }
+});
diff --git a/browser/components/migration/tests/unit/test_IE_bookmarks.js b/browser/components/migration/tests/unit/test_IE_bookmarks.js
new file mode 100644
index 000000000..a166c0502
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_IE_bookmarks.js
@@ -0,0 +1,44 @@
+"use strict";
+
+add_task(function* () {
+ let migrator = MigrationUtils.getMigrator("ie");
+ // Sanity check for the source.
+ Assert.ok(migrator.sourceExists);
+
+ // Wait for the imported bookmarks. Check that "From Internet Explorer"
+ // folders are created in the menu and on the toolbar.
+ let source = MigrationUtils.getLocalizedString("sourceNameIE");
+ let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
+
+ let expectedParents = [ PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.toolbarFolderId ];
+
+ let itemCount = 0;
+ let bmObserver = {
+ onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
+ if (aTitle != label) {
+ itemCount++;
+ }
+ if (expectedParents.length > 0 && aTitle == label) {
+ let index = expectedParents.indexOf(aParentId);
+ Assert.notEqual(index, -1);
+ expectedParents.splice(index, 1);
+ }
+ },
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onItemRemoved() {},
+ onItemChanged() {},
+ onItemVisited() {},
+ onItemMoved() {},
+ };
+ PlacesUtils.bookmarks.addObserver(bmObserver, false);
+
+ yield promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
+ PlacesUtils.bookmarks.removeObserver(bmObserver);
+ Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount,
+ "Ensure telemetry matches actual number of imported items.");
+
+ // Check the bookmarks have been imported to all the expected parents.
+ Assert.equal(expectedParents.length, 0, "Got all the expected parents");
+});
diff --git a/browser/components/migration/tests/unit/test_IE_cookies.js b/browser/components/migration/tests/unit/test_IE_cookies.js
new file mode 100644
index 000000000..37a7462f2
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_IE_cookies.js
@@ -0,0 +1,111 @@
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+ "resource://gre/modules/ctypes.jsm");
+
+add_task(function* () {
+ let migrator = MigrationUtils.getMigrator("ie");
+ // Sanity check for the source.
+ Assert.ok(migrator.sourceExists);
+
+ const BOOL = ctypes.bool;
+ const LPCTSTR = ctypes.char16_t.ptr;
+ const DWORD = ctypes.uint32_t;
+ const LPDWORD = DWORD.ptr;
+
+ let wininet = ctypes.open("Wininet");
+
+ /*
+ BOOL InternetSetCookieW(
+ _In_ LPCTSTR lpszUrl,
+ _In_ LPCTSTR lpszCookieName,
+ _In_ LPCTSTR lpszCookieData
+ );
+ */
+ let setIECookie = wininet.declare("InternetSetCookieW",
+ ctypes.default_abi,
+ BOOL,
+ LPCTSTR,
+ LPCTSTR,
+ LPCTSTR);
+
+ /*
+ BOOL InternetGetCookieW(
+ _In_ LPCTSTR lpszUrl,
+ _In_ LPCTSTR lpszCookieName,
+ _Out_ LPCTSTR lpszCookieData,
+ _Inout_ LPDWORD lpdwSize
+ );
+ */
+ let getIECookie = wininet.declare("InternetGetCookieW",
+ ctypes.default_abi,
+ BOOL,
+ LPCTSTR,
+ LPCTSTR,
+ LPCTSTR,
+ LPDWORD);
+
+ // We need to randomize the cookie to avoid clashing with other cookies
+ // that might have been set by previous tests and not properly cleared.
+ let date = (new Date()).getDate();
+ const COOKIE = {
+ get host() {
+ return new URL(this.href).host;
+ },
+ href: `http://mycookietest.${Math.random()}.com`,
+ name: "testcookie",
+ value: "testvalue",
+ expiry: new Date(new Date().setDate(date + 2))
+ };
+ let data = ctypes.char16_t.array()(256);
+ let sizeRef = DWORD(256).address();
+
+ do_register_cleanup(() => {
+ // Remove the cookie.
+ try {
+ let expired = new Date(new Date().setDate(date - 2));
+ let rv = setIECookie(COOKIE.href, COOKIE.name,
+ `; expires=${expired.toUTCString()}`);
+ Assert.ok(rv, "Expired the IE cookie");
+ Assert.ok(!getIECookie(COOKIE.href, COOKIE.name, data, sizeRef),
+ "The cookie has been properly removed");
+ } catch (ex) {}
+
+ // Close the library.
+ try {
+ wininet.close();
+ } catch (ex) {}
+ });
+
+ // Create the persistent cookie in IE.
+ let value = `${COOKIE.value}; expires=${COOKIE.expiry.toUTCString()}`;
+ let rv = setIECookie(COOKIE.href, COOKIE.name, value);
+ Assert.ok(rv, "Added a persistent IE cookie: " + value);
+
+ // Sanity check the cookie has been created.
+ Assert.ok(getIECookie(COOKIE.href, COOKIE.name, data, sizeRef),
+ "Found the added persistent IE cookie");
+ do_print("Found cookie: " + data.readString());
+ Assert.equal(data.readString(), `${COOKIE.name}=${COOKIE.value}`,
+ "Found the expected cookie");
+
+ // Sanity check that there are no cookies.
+ Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 0,
+ "There are no cookies initially");
+
+ // Migrate cookies.
+ yield promiseMigration(migrator, MigrationUtils.resourceTypes.COOKIES);
+
+ Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 1,
+ "Migrated the expected number of cookies");
+
+ // Now check the cookie details.
+ let enumerator = Services.cookies.getCookiesFromHost(COOKIE.host, {});
+ Assert.ok(enumerator.hasMoreElements());
+ let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+
+ Assert.equal(foundCookie.name, COOKIE.name);
+ Assert.equal(foundCookie.value, COOKIE.value);
+ Assert.equal(foundCookie.host, "." + COOKIE.host);
+ Assert.equal(foundCookie.expiry, Math.floor(COOKIE.expiry / 1000));
+});
diff --git a/browser/components/migration/tests/unit/test_Safari_bookmarks.js b/browser/components/migration/tests/unit/test_Safari_bookmarks.js
new file mode 100644
index 000000000..edc32dc72
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_Safari_bookmarks.js
@@ -0,0 +1,46 @@
+"use strict";
+
+add_task(function* () {
+ registerFakePath("ULibDir", do_get_file("Library/"));
+
+ let migrator = MigrationUtils.getMigrator("safari");
+ // Sanity check for the source.
+ Assert.ok(migrator.sourceExists);
+
+ // Wait for the imported bookmarks. Check that "From Safari"
+ // folders are created on the toolbar.
+ let source = MigrationUtils.getLocalizedString("sourceNameSafari");
+ let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
+
+ let expectedParents = [ PlacesUtils.toolbarFolderId ];
+ let itemCount = 0;
+
+ let bmObserver = {
+ onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
+ if (aTitle != label) {
+ itemCount++;
+ }
+ if (expectedParents.length > 0 && aTitle == label) {
+ let index = expectedParents.indexOf(aParentId);
+ Assert.ok(index != -1, "Found expected parent");
+ expectedParents.splice(index, 1);
+ }
+ },
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onItemRemoved() {},
+ onItemChanged() {},
+ onItemVisited() {},
+ onItemMoved() {},
+ };
+ PlacesUtils.bookmarks.addObserver(bmObserver, false);
+
+ yield promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
+ PlacesUtils.bookmarks.removeObserver(bmObserver);
+
+ // Check the bookmarks have been imported to all the expected parents.
+ Assert.ok(!expectedParents.length, "No more expected parents");
+ Assert.equal(itemCount, 13, "Should import all 13 items.");
+ // Check that the telemetry matches:
+ Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount, "Telemetry reporting correct.");
+});
diff --git a/browser/components/migration/tests/unit/test_automigration.js b/browser/components/migration/tests/unit/test_automigration.js
new file mode 100644
index 000000000..bc9076a6c
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_automigration.js
@@ -0,0 +1,695 @@
+"use strict";
+
+let AutoMigrateBackstage = Cu.import("resource:///modules/AutoMigrate.jsm"); /* globals AutoMigrate */
+
+let gShimmedMigratorKeyPicker = null;
+let gShimmedMigrator = null;
+
+const kUsecPerMin = 60 * 1000000;
+
+// This is really a proxy on MigrationUtils, but if we specify that directly,
+// we get in trouble because the object itself is frozen, and Proxies can't
+// return a different value to an object when directly proxying a frozen
+// object.
+AutoMigrateBackstage.MigrationUtils = new Proxy({}, {
+ get(target, name) {
+ if (name == "getMigratorKeyForDefaultBrowser" && gShimmedMigratorKeyPicker) {
+ return gShimmedMigratorKeyPicker;
+ }
+ if (name == "getMigrator" && gShimmedMigrator) {
+ return function() { return gShimmedMigrator };
+ }
+ return MigrationUtils[name];
+ },
+});
+
+do_register_cleanup(function() {
+ AutoMigrateBackstage.MigrationUtils = MigrationUtils;
+});
+
+// This should be replaced by using History.fetch with a fetchVisits option,
+// once that becomes available
+function* visitsForURL(url)
+{
+ let visitCount = 0;
+ let db = yield PlacesUtils.promiseDBConnection();
+ visitCount = yield db.execute(
+ `SELECT count(*) FROM moz_historyvisits v
+ JOIN moz_places h ON h.id = v.place_id
+ WHERE url_hash = hash(:url) AND url = :url`,
+ {url});
+ visitCount = visitCount[0].getInt64(0);
+ return visitCount;
+}
+
+
+/**
+ * Test automatically picking a browser to migrate from
+ */
+add_task(function* checkMigratorPicking() {
+ Assert.throws(() => AutoMigrate.pickMigrator("firefox"),
+ /Can't automatically migrate from Firefox/,
+ "Should throw when explicitly picking Firefox.");
+
+ Assert.throws(() => AutoMigrate.pickMigrator("gobbledygook"),
+ /migrator object is not available/,
+ "Should throw when passing unknown migrator key");
+ gShimmedMigratorKeyPicker = function() {
+ return "firefox";
+ };
+ Assert.throws(() => AutoMigrate.pickMigrator(),
+ /Can't automatically migrate from Firefox/,
+ "Should throw when implicitly picking Firefox.");
+ gShimmedMigratorKeyPicker = function() {
+ return "gobbledygook";
+ };
+ Assert.throws(() => AutoMigrate.pickMigrator(),
+ /migrator object is not available/,
+ "Should throw when an unknown migrator is the default");
+ gShimmedMigratorKeyPicker = function() {
+ return "";
+ };
+ Assert.throws(() => AutoMigrate.pickMigrator(),
+ /Could not determine default browser key/,
+ "Should throw when an unknown migrator is the default");
+});
+
+
+/**
+ * Test automatically picking a profile to migrate from
+ */
+add_task(function* checkProfilePicking() {
+ let fakeMigrator = {sourceProfiles: [{id: "a"}, {id: "b"}]};
+ let profB = fakeMigrator.sourceProfiles[1];
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
+ /Don't know how to pick a profile when more/,
+ "Should throw when there are multiple profiles.");
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
+ /Profile specified was not found/,
+ "Should throw when the profile supplied doesn't exist.");
+ let profileToMigrate = AutoMigrate.pickProfile(fakeMigrator, "b");
+ Assert.equal(profileToMigrate, profB, "Should return profile supplied");
+
+ fakeMigrator.sourceProfiles = null;
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
+ /Profile specified but only a default profile found./,
+ "Should throw when the profile supplied doesn't exist.");
+ profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
+ Assert.equal(profileToMigrate, null, "Should return default profile when that's the only one.");
+
+ fakeMigrator.sourceProfiles = [];
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
+ /No profile data found/,
+ "Should throw when no profile data is present.");
+
+ fakeMigrator.sourceProfiles = [{id: "a"}];
+ let profA = fakeMigrator.sourceProfiles[0];
+ profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
+ Assert.equal(profileToMigrate, profA, "Should return the only profile if only one is present.");
+});
+
+/**
+ * Test the complete automatic process including browser and profile selection,
+ * and actual migration (which implies startup)
+ */
+add_task(function* checkIntegration() {
+ gShimmedMigrator = {
+ get sourceProfiles() {
+ do_print("Read sourceProfiles");
+ return null;
+ },
+ getMigrateData(profileToMigrate) {
+ this._getMigrateDataArgs = profileToMigrate;
+ return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
+ },
+ migrate(types, startup, profileToMigrate) {
+ this._migrateArgs = [types, startup, profileToMigrate];
+ },
+ };
+ gShimmedMigratorKeyPicker = function() {
+ return "gobbledygook";
+ };
+ AutoMigrate.migrate("startup");
+ Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
+ "getMigrateData called with 'null' as a profile");
+
+ let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
+ Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
+ "migrate called with 'null' as a profile");
+});
+
+/**
+ * Test the undo preconditions and a no-op undo in the automigrator.
+ */
+add_task(function* checkUndoPreconditions() {
+ let shouldAddData = false;
+ gShimmedMigrator = {
+ get sourceProfiles() {
+ do_print("Read sourceProfiles");
+ return null;
+ },
+ getMigrateData(profileToMigrate) {
+ this._getMigrateDataArgs = profileToMigrate;
+ return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
+ },
+ migrate(types, startup, profileToMigrate) {
+ this._migrateArgs = [types, startup, profileToMigrate];
+ if (shouldAddData) {
+ // Insert a login and check that that worked.
+ MigrationUtils.insertLoginWrapper({
+ hostname: "www.mozilla.org",
+ formSubmitURL: "http://www.mozilla.org",
+ username: "user",
+ password: "pass",
+ });
+ }
+ TestUtils.executeSoon(function() {
+ Services.obs.notifyObservers(null, "Migration:Ended", undefined);
+ });
+ },
+ };
+
+ gShimmedMigratorKeyPicker = function() {
+ return "gobbledygook";
+ };
+ AutoMigrate.migrate("startup");
+ let migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
+ Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
+ "getMigrateData called with 'null' as a profile");
+
+ let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
+ Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
+ "migrate called with 'null' as a profile");
+
+ yield migrationFinishedPromise;
+ Assert.ok(Preferences.has("browser.migrate.automigrate.browser"),
+ "Should have set browser pref");
+ Assert.ok(!(yield AutoMigrate.canUndo()), "Should not be able to undo migration, as there's no data");
+ gShimmedMigrator._migrateArgs = null;
+ gShimmedMigrator._getMigrateDataArgs = null;
+ Preferences.reset("browser.migrate.automigrate.browser");
+ shouldAddData = true;
+
+ AutoMigrate.migrate("startup");
+ migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
+ Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
+ "getMigrateData called with 'null' as a profile");
+ Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
+ "migrate called with 'null' as a profile");
+
+ yield migrationFinishedPromise;
+ let storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+ "http://www.mozilla.org", null);
+ Assert.equal(storedLogins.length, 1, "Should have 1 login");
+
+ Assert.ok(Preferences.has("browser.migrate.automigrate.browser"),
+ "Should have set browser pref");
+ Assert.ok((yield AutoMigrate.canUndo()), "Should be able to undo migration, as now there's data");
+
+ yield AutoMigrate.undo();
+ Assert.ok(true, "Should be able to finish an undo cycle.");
+
+ // Check that the undo removed the passwords:
+ storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+ "http://www.mozilla.org", null);
+ Assert.equal(storedLogins.length, 0, "Should have no logins");
+});
+
+/**
+ * Fake a migration and then try to undo it to verify all data gets removed.
+ */
+add_task(function* checkUndoRemoval() {
+ MigrationUtils.initializeUndoData();
+ Preferences.set("browser.migrate.automigrate.browser", "automationbrowser");
+ // Insert a login and check that that worked.
+ MigrationUtils.insertLoginWrapper({
+ hostname: "www.mozilla.org",
+ formSubmitURL: "http://www.mozilla.org",
+ username: "user",
+ password: "pass",
+ });
+ let storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+ "http://www.mozilla.org", null);
+ Assert.equal(storedLogins.length, 1, "Should have 1 login");
+
+ // Insert a bookmark and check that we have exactly 1 bookmark for that URI.
+ yield MigrationUtils.insertBookmarkWrapper({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "http://www.example.org/",
+ title: "Some example bookmark",
+ });
+
+ let bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
+ Assert.ok(bookmark, "Should have a bookmark before undo");
+ Assert.equal(bookmark.title, "Some example bookmark", "Should have correct bookmark before undo.");
+
+ // Insert 2 history visits
+ let now_uSec = Date.now() * 1000;
+ let visitedURI = Services.io.newURI("http://www.example.com/", null, null);
+ let frecencyUpdatePromise = new Promise(resolve => {
+ let expectedChanges = 2;
+ let observer = {
+ onFrecencyChanged: function() {
+ if (!--expectedChanges) {
+ PlacesUtils.history.removeObserver(observer);
+ resolve();
+ }
+ },
+ };
+ PlacesUtils.history.addObserver(observer, false);
+ });
+ yield MigrationUtils.insertVisitsWrapper([{
+ uri: visitedURI,
+ visits: [
+ {
+ transitionType: PlacesUtils.history.TRANSITION_LINK,
+ visitDate: now_uSec,
+ },
+ {
+ transitionType: PlacesUtils.history.TRANSITION_LINK,
+ visitDate: now_uSec - 100 * kUsecPerMin,
+ },
+ ]
+ }]);
+ yield frecencyUpdatePromise;
+
+ // Verify that both visits get reported.
+ let opts = PlacesUtils.history.getNewQueryOptions();
+ opts.resultType = opts.RESULTS_AS_VISIT;
+ let query = PlacesUtils.history.getNewQuery();
+ query.uri = visitedURI;
+ let visits = PlacesUtils.history.executeQuery(query, opts);
+ visits.root.containerOpen = true;
+ Assert.equal(visits.root.childCount, 2, "Should have 2 visits");
+ // Clean up:
+ visits.root.containerOpen = false;
+
+ yield AutoMigrate.saveUndoState();
+
+ // Verify that we can undo, then undo:
+ Assert.ok(AutoMigrate.canUndo(), "Should be possible to undo migration");
+ yield AutoMigrate.undo();
+
+ let histograms = [
+ "FX_STARTUP_MIGRATION_UNDO_BOOKMARKS_ERRORCOUNT",
+ "FX_STARTUP_MIGRATION_UNDO_LOGINS_ERRORCOUNT",
+ "FX_STARTUP_MIGRATION_UNDO_VISITS_ERRORCOUNT",
+ ];
+ for (let histogramId of histograms) {
+ let keyedHistogram = Services.telemetry.getKeyedHistogramById(histogramId);
+ let histogramData = keyedHistogram.snapshot().automationbrowser;
+ Assert.equal(histogramData.sum, 0, `Should have reported 0 errors to ${histogramId}.`);
+ Assert.greaterOrEqual(histogramData.counts[0], 1, `Should have reported value of 0 one time to ${histogramId}.`);
+ }
+ histograms = [
+ "FX_STARTUP_MIGRATION_UNDO_BOOKMARKS_MS",
+ "FX_STARTUP_MIGRATION_UNDO_LOGINS_MS",
+ "FX_STARTUP_MIGRATION_UNDO_VISITS_MS",
+ "FX_STARTUP_MIGRATION_UNDO_TOTAL_MS",
+ ];
+ for (let histogramId of histograms) {
+ Assert.greater(Services.telemetry.getKeyedHistogramById(histogramId).snapshot().automationbrowser.sum, 0,
+ `Should have reported non-zero time spent using undo for ${histogramId}`);
+ }
+
+ // Check that the undo removed the history visits:
+ visits = PlacesUtils.history.executeQuery(query, opts);
+ visits.root.containerOpen = true;
+ Assert.equal(visits.root.childCount, 0, "Should have no more visits");
+ visits.root.containerOpen = false;
+
+ // Check that the undo removed the bookmarks:
+ bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
+ Assert.ok(!bookmark, "Should have no bookmarks after undo");
+
+ // Check that the undo removed the passwords:
+ storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+ "http://www.mozilla.org", null);
+ Assert.equal(storedLogins.length, 0, "Should have no logins");
+});
+
+add_task(function* checkUndoBookmarksState() {
+ MigrationUtils.initializeUndoData();
+ const {TYPE_FOLDER, TYPE_BOOKMARK} = PlacesUtils.bookmarks;
+ let title = "Some example bookmark";
+ let url = "http://www.example.com";
+ let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
+ let {guid, lastModified} = yield MigrationUtils.insertBookmarkWrapper({
+ title, url, parentGuid
+ });
+ Assert.deepEqual((yield MigrationUtils.stopAndRetrieveUndoData()).get("bookmarks"),
+ [{lastModified, parentGuid, guid, type: TYPE_BOOKMARK}]);
+
+ MigrationUtils.initializeUndoData();
+ ({guid, lastModified} = yield MigrationUtils.insertBookmarkWrapper({
+ title, parentGuid, type: TYPE_FOLDER
+ }));
+ let folder = {guid, lastModified, parentGuid, type: TYPE_FOLDER};
+ let folderGuid = folder.guid;
+ ({guid, lastModified} = yield MigrationUtils.insertBookmarkWrapper({
+ title, url, parentGuid: folderGuid
+ }));
+ let kid1 = {guid, lastModified, parentGuid: folderGuid, type: TYPE_BOOKMARK};
+ ({guid, lastModified} = yield MigrationUtils.insertBookmarkWrapper({
+ title, url, parentGuid: folderGuid
+ }));
+ let kid2 = {guid, lastModified, parentGuid: folderGuid, type: TYPE_BOOKMARK};
+
+ let bookmarksUndo = (yield MigrationUtils.stopAndRetrieveUndoData()).get("bookmarks");
+ Assert.equal(bookmarksUndo.length, 3);
+ // We expect that the last modified time from first kid #1 and then kid #2
+ // has been propagated to the folder:
+ folder.lastModified = kid2.lastModified;
+ // Not just using deepEqual on the entire array (which should work) because
+ // the failure messages get truncated by xpcshell which is unhelpful.
+ Assert.deepEqual(bookmarksUndo[0], folder);
+ Assert.deepEqual(bookmarksUndo[1], kid1);
+ Assert.deepEqual(bookmarksUndo[2], kid2);
+ yield PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(function* testBookmarkRemovalByUndo() {
+ const {TYPE_FOLDER} = PlacesUtils.bookmarks;
+ MigrationUtils.initializeUndoData();
+ let title = "Some example bookmark";
+ let url = "http://www.mymagicaluniqueurl.com";
+ let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
+ let {guid} = yield MigrationUtils.insertBookmarkWrapper({
+ title: "Some folder", parentGuid, type: TYPE_FOLDER
+ });
+ let folderGuid = guid;
+ let itemsToRemove = [];
+ ({guid} = yield MigrationUtils.insertBookmarkWrapper({
+ title: "Inner folder", parentGuid: folderGuid, type: TYPE_FOLDER
+ }));
+ let innerFolderGuid = guid;
+ itemsToRemove.push(innerFolderGuid);
+
+ ({guid} = yield MigrationUtils.insertBookmarkWrapper({
+ title: "Inner inner folder", parentGuid: innerFolderGuid, type: TYPE_FOLDER
+ }));
+ itemsToRemove.push(guid);
+
+ ({guid} = yield MigrationUtils.insertBookmarkWrapper({
+ title: "Inner nested item", url: "http://inner-nested-example.com", parentGuid: guid
+ }));
+ itemsToRemove.push(guid);
+
+ ({guid} = yield MigrationUtils.insertBookmarkWrapper({
+ title, url, parentGuid: folderGuid
+ }));
+ itemsToRemove.push(guid);
+
+ for (let toBeRemovedGuid of itemsToRemove) {
+ let dbResultForGuid = yield PlacesUtils.bookmarks.fetch(toBeRemovedGuid);
+ Assert.ok(dbResultForGuid, "Should be able to find items that will be removed.");
+ }
+ let bookmarkUndoState = (yield MigrationUtils.stopAndRetrieveUndoData()).get("bookmarks");
+ // Now insert a separate item into this folder, not related to the migration.
+ let newItem = yield PlacesUtils.bookmarks.insert(
+ {title: "Not imported", parentGuid: folderGuid, url: "http://www.example.com"}
+ );
+
+ yield AutoMigrate._removeUnchangedBookmarks(bookmarkUndoState);
+ Assert.ok(true, "Successfully removed imported items.");
+
+ let itemFromDB = yield PlacesUtils.bookmarks.fetch(newItem.guid);
+ Assert.ok(itemFromDB, "Item we inserted outside of migration is still there.");
+ itemFromDB = yield PlacesUtils.bookmarks.fetch(folderGuid);
+ Assert.ok(itemFromDB, "Folder we inserted in migration is still there because of new kids.");
+ for (let removedGuid of itemsToRemove) {
+ let dbResultForGuid = yield PlacesUtils.bookmarks.fetch(removedGuid);
+ let dbgStr = dbResultForGuid && dbResultForGuid.title;
+ Assert.equal(null, dbResultForGuid, "Should not be able to find items that should have been removed, but found " + dbgStr);
+ }
+ yield PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(function* checkUndoLoginsState() {
+ MigrationUtils.initializeUndoData();
+ MigrationUtils.insertLoginWrapper({
+ username: "foo",
+ password: "bar",
+ hostname: "https://example.com",
+ formSubmitURL: "https://example.com/",
+ timeCreated: new Date(),
+ });
+ let storedLogins = Services.logins.findLogins({}, "https://example.com", "", "");
+ let storedLogin = storedLogins[0];
+ storedLogin.QueryInterface(Ci.nsILoginMetaInfo);
+ let {guid, timePasswordChanged} = storedLogin;
+ let undoLoginData = (yield MigrationUtils.stopAndRetrieveUndoData()).get("logins");
+ Assert.deepEqual([{guid, timePasswordChanged}], undoLoginData);
+ Services.logins.removeAllLogins();
+});
+
+add_task(function* testLoginsRemovalByUndo() {
+ MigrationUtils.initializeUndoData();
+ MigrationUtils.insertLoginWrapper({
+ username: "foo",
+ password: "bar",
+ hostname: "https://example.com",
+ formSubmitURL: "https://example.com/",
+ timeCreated: new Date(),
+ });
+ MigrationUtils.insertLoginWrapper({
+ username: "foo",
+ password: "bar",
+ hostname: "https://example.org",
+ formSubmitURL: "https://example.org/",
+ timeCreated: new Date(new Date().getTime() - 10000),
+ });
+ // This should update the existing login
+ LoginHelper.maybeImportLogin({
+ username: "foo",
+ password: "bazzy",
+ hostname: "https://example.org",
+ formSubmitURL: "https://example.org/",
+ timePasswordChanged: new Date(),
+ });
+ Assert.equal(1, LoginHelper.searchLoginsWithObject({hostname: "https://example.org", formSubmitURL: "https://example.org/"}).length,
+ "Should be only 1 login for example.org (that was updated)");
+ let undoLoginData = (yield MigrationUtils.stopAndRetrieveUndoData()).get("logins");
+
+ yield AutoMigrate._removeUnchangedLogins(undoLoginData);
+ Assert.equal(0, LoginHelper.searchLoginsWithObject({hostname: "https://example.com", formSubmitURL: "https://example.com/"}).length,
+ "unchanged example.com entry should have been removed.");
+ Assert.equal(1, LoginHelper.searchLoginsWithObject({hostname: "https://example.org", formSubmitURL: "https://example.org/"}).length,
+ "changed example.org entry should have persisted.");
+ Services.logins.removeAllLogins();
+});
+
+add_task(function* checkUndoVisitsState() {
+ MigrationUtils.initializeUndoData();
+ yield MigrationUtils.insertVisitsWrapper([{
+ uri: NetUtil.newURI("http://www.example.com/"),
+ title: "Example",
+ visits: [{
+ visitDate: new Date("2015-07-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }, {
+ visitDate: new Date("2015-09-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }, {
+ visitDate: new Date("2015-08-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }],
+ }, {
+ uri: NetUtil.newURI("http://www.example.org/"),
+ title: "Example",
+ visits: [{
+ visitDate: new Date("2016-04-03").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }, {
+ visitDate: new Date("2015-08-03").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }],
+ }, {
+ uri: NetUtil.newURI("http://www.example.com/"),
+ title: "Example",
+ visits: [{
+ visitDate: new Date("2015-10-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }],
+ }]);
+ let undoVisitData = (yield MigrationUtils.stopAndRetrieveUndoData()).get("visits");
+ Assert.deepEqual(Array.from(undoVisitData.map(v => v.url)).sort(),
+ ["http://www.example.com/", "http://www.example.org/"]);
+ Assert.deepEqual(undoVisitData.find(v => v.url == "http://www.example.com/"), {
+ url: "http://www.example.com/",
+ visitCount: 4,
+ first: new Date("2015-07-10").getTime() * 1000,
+ last: new Date("2015-10-10").getTime() * 1000,
+ });
+ Assert.deepEqual(undoVisitData.find(v => v.url == "http://www.example.org/"), {
+ url: "http://www.example.org/",
+ visitCount: 2,
+ first: new Date("2015-08-03").getTime() * 1000,
+ last: new Date("2016-04-03").getTime() * 1000,
+ });
+
+ yield PlacesTestUtils.clearHistory();
+});
+
+add_task(function* checkUndoVisitsState() {
+ MigrationUtils.initializeUndoData();
+ yield MigrationUtils.insertVisitsWrapper([{
+ uri: NetUtil.newURI("http://www.example.com/"),
+ title: "Example",
+ visits: [{
+ visitDate: new Date("2015-07-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }, {
+ visitDate: new Date("2015-09-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }, {
+ visitDate: new Date("2015-08-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }],
+ }, {
+ uri: NetUtil.newURI("http://www.example.org/"),
+ title: "Example",
+ visits: [{
+ visitDate: new Date("2016-04-03").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }, {
+ visitDate: new Date("2015-08-03").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }],
+ }, {
+ uri: NetUtil.newURI("http://www.example.com/"),
+ title: "Example",
+ visits: [{
+ visitDate: new Date("2015-10-10").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }],
+ }, {
+ uri: NetUtil.newURI("http://www.mozilla.org/"),
+ title: "Example",
+ visits: [{
+ visitDate: new Date("2015-01-01").getTime() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+ }],
+ }]);
+
+ // We have to wait until frecency updates have been handled in order
+ // to accurately determine whether we're doing the right thing.
+ let frecencyUpdatesHandled = new Promise(resolve => {
+ PlacesUtils.history.addObserver({
+ onFrecencyChanged(aURI) {
+ if (aURI.spec == "http://www.unrelated.org/") {
+ PlacesUtils.history.removeObserver(this);
+ resolve();
+ }
+ }
+ }, false);
+ });
+ yield PlacesUtils.history.insertMany([{
+ url: "http://www.example.com/",
+ title: "Example",
+ visits: [{
+ date: new Date("2015-08-16"),
+ }],
+ }, {
+ url: "http://www.example.org/",
+ title: "Example",
+ visits: [{
+ date: new Date("2016-01-03"),
+ }, {
+ date: new Date("2015-05-03"),
+ }],
+ }, {
+ url: "http://www.unrelated.org/",
+ title: "Unrelated",
+ visits: [{
+ date: new Date("2015-09-01"),
+ }],
+ }]);
+ yield frecencyUpdatesHandled;
+ let undoVisitData = (yield MigrationUtils.stopAndRetrieveUndoData()).get("visits");
+
+ let frecencyChangesExpected = new Map([
+ ["http://www.example.com/", PromiseUtils.defer()],
+ ["http://www.example.org/", PromiseUtils.defer()]
+ ]);
+ let uriDeletedExpected = new Map([
+ ["http://www.mozilla.org/", PromiseUtils.defer()],
+ ]);
+ let wrongMethodDeferred = PromiseUtils.defer();
+ let observer = {
+ onBeginUpdateBatch: function() {},
+ onEndUpdateBatch: function() {},
+ onVisit: function(uri) {
+ wrongMethodDeferred.reject(new Error("Unexpected call to onVisit " + uri.spec));
+ },
+ onTitleChanged: function(uri) {
+ wrongMethodDeferred.reject(new Error("Unexpected call to onTitleChanged " + uri.spec));
+ },
+ onClearHistory: function() {
+ wrongMethodDeferred.reject("Unexpected call to onClearHistory");
+ },
+ onPageChanged: function(uri) {
+ wrongMethodDeferred.reject(new Error("Unexpected call to onPageChanged " + uri.spec));
+ },
+ onFrecencyChanged: function(aURI) {
+ do_print("frecency change");
+ Assert.ok(frecencyChangesExpected.has(aURI.spec),
+ "Should be expecting frecency change for " + aURI.spec);
+ frecencyChangesExpected.get(aURI.spec).resolve();
+ },
+ onManyFrecenciesChanged: function() {
+ do_print("Many frecencies changed");
+ wrongMethodDeferred.reject(new Error("This test can't deal with onManyFrecenciesChanged to be called"));
+ },
+ onDeleteURI: function(aURI) {
+ do_print("delete uri");
+ Assert.ok(uriDeletedExpected.has(aURI.spec),
+ "Should be expecting uri deletion for " + aURI.spec);
+ uriDeletedExpected.get(aURI.spec).resolve();
+ },
+ };
+ PlacesUtils.history.addObserver(observer, false);
+
+ yield AutoMigrate._removeSomeVisits(undoVisitData);
+ PlacesUtils.history.removeObserver(observer);
+ yield Promise.all(uriDeletedExpected.values());
+ yield Promise.all(frecencyChangesExpected.values());
+
+ Assert.equal(yield visitsForURL("http://www.example.com/"), 1,
+ "1 example.com visit (out of 5) should have persisted despite being within the range, due to limiting");
+ Assert.equal(yield visitsForURL("http://www.mozilla.org/"), 0,
+ "0 mozilla.org visits should have persisted (out of 1).");
+ Assert.equal(yield visitsForURL("http://www.example.org/"), 2,
+ "2 example.org visits should have persisted (out of 4).");
+ Assert.equal(yield visitsForURL("http://www.unrelated.org/"), 1,
+ "1 unrelated.org visits should have persisted as it's not involved in the import.");
+ yield PlacesTestUtils.clearHistory();
+});
+
+add_task(function* checkHistoryRemovalCompletion() {
+ AutoMigrate._errorMap = {bookmarks: 0, visits: 0, logins: 0};
+ yield AutoMigrate._removeSomeVisits([{url: "http://www.example.com/", limit: -1}]);
+ ok(true, "Removing visits should complete even if removing some visits failed.");
+ Assert.equal(AutoMigrate._errorMap.visits, 1, "Should have logged the error for visits.");
+
+ // Unfortunately there's not a reliable way to make removing bookmarks be
+ // unhappy unless the DB is messed up (e.g. contains children but has
+ // parents removed already).
+ yield AutoMigrate._removeUnchangedBookmarks([
+ {guid: PlacesUtils.bookmarks, lastModified: new Date(0), parentGuid: 0},
+ {guid: "gobbledygook", lastModified: new Date(0), parentGuid: 0},
+ ]);
+ ok(true, "Removing bookmarks should complete even if some items are gone or bogus.");
+ Assert.equal(AutoMigrate._errorMap.bookmarks, 0,
+ "Should have ignored removing non-existing (or builtin) bookmark.");
+
+
+ yield AutoMigrate._removeUnchangedLogins([
+ {guid: "gobbledygook", timePasswordChanged: new Date(0)},
+ ]);
+ ok(true, "Removing logins should complete even if logins don't exist.");
+ Assert.equal(AutoMigrate._errorMap.logins, 0,
+ "Should have ignored removing non-existing logins.");
+});
diff --git a/browser/components/migration/tests/unit/test_fx_telemetry.js b/browser/components/migration/tests/unit/test_fx_telemetry.js
new file mode 100644
index 000000000..a276f52f8
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_fx_telemetry.js
@@ -0,0 +1,288 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* globals do_get_tempdir */
+
+"use strict";
+
+function run_test() {
+ run_next_test();
+}
+
+function readFile(file) {
+ let stream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ stream.init(file, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(stream);
+ let contents = sis.read(file.fileSize);
+ sis.close();
+ return contents;
+}
+
+function checkDirectoryContains(dir, files) {
+ print("checking " + dir.path + " - should contain " + Object.keys(files));
+ let seen = new Set();
+ let enumerator = dir.directoryEntries;
+ while (enumerator.hasMoreElements()) {
+ let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
+ print("found file: " + file.path);
+ Assert.ok(file.leafName in files, file.leafName + " exists, but shouldn't");
+
+ let expectedContents = files[file.leafName];
+ if (typeof expectedContents != "string") {
+ // it's a subdir - recurse!
+ Assert.ok(file.isDirectory(), "should be a subdir");
+ let newDir = dir.clone();
+ newDir.append(file.leafName);
+ checkDirectoryContains(newDir, expectedContents);
+ } else {
+ Assert.ok(!file.isDirectory(), "should be a regular file");
+ let contents = readFile(file);
+ Assert.equal(contents, expectedContents);
+ }
+ seen.add(file.leafName);
+ }
+ let missing = [];
+ for (let x in files) {
+ if (!seen.has(x)) {
+ missing.push(x);
+ }
+ }
+ Assert.deepEqual(missing, [], "no missing files in " + dir.path);
+}
+
+function getTestDirs() {
+ // we make a directory structure in a temp dir which mirrors what we are
+ // testing.
+ let tempDir = do_get_tempdir();
+ let srcDir = tempDir.clone();
+ srcDir.append("test_source_dir");
+ srcDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+ let targetDir = tempDir.clone();
+ targetDir.append("test_target_dir");
+ targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+ // no need to cleanup these dirs - the xpcshell harness will do it for us.
+ return [srcDir, targetDir];
+}
+
+function writeToFile(dir, leafName, contents) {
+ let file = dir.clone();
+ file.append(leafName);
+
+ let outputStream = FileUtils.openFileOutputStream(file);
+ outputStream.write(contents, contents.length);
+ outputStream.close();
+}
+
+function createSubDir(dir, subDirName) {
+ let subDir = dir.clone();
+ subDir.append(subDirName);
+ subDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ return subDir;
+}
+
+function promiseMigrator(name, srcDir, targetDir) {
+ let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=firefox"]
+ .createInstance(Ci.nsISupports)
+ .wrappedJSObject;
+ let migrators = migrator._getResourcesInternal(srcDir, targetDir);
+ for (let m of migrators) {
+ if (m.name == name) {
+ return new Promise(resolve => m.migrate(resolve));
+ }
+ }
+ throw new Error("failed to find the " + name + " migrator");
+}
+
+function promiseTelemetryMigrator(srcDir, targetDir) {
+ return promiseMigrator("telemetry", srcDir, targetDir);
+}
+
+add_task(function* test_empty() {
+ let [srcDir, targetDir] = getTestDirs();
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true with empty directories");
+ // check both are empty
+ checkDirectoryContains(srcDir, {});
+ checkDirectoryContains(targetDir, {});
+});
+
+add_task(function* test_migrate_files() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // Set up datareporting files, some to copy, some not.
+ let stateContent = JSON.stringify({
+ clientId: "68d5474e-19dc-45c1-8e9a-81fca592707c",
+ });
+ let sessionStateContent = "foobar 5432";
+ let subDir = createSubDir(srcDir, "datareporting");
+ writeToFile(subDir, "state.json", stateContent);
+ writeToFile(subDir, "session-state.json", sessionStateContent);
+ writeToFile(subDir, "other.file", "do not copy");
+
+ let archived = createSubDir(subDir, "archived");
+ writeToFile(archived, "other.file", "do not copy");
+
+ // Set up FHR files, they should not be copied.
+ writeToFile(srcDir, "healthreport.sqlite", "do not copy");
+ writeToFile(srcDir, "healthreport.sqlite-wal", "do not copy");
+ subDir = createSubDir(srcDir, "healthreport");
+ writeToFile(subDir, "state.json", "do not copy");
+ writeToFile(subDir, "other.file", "do not copy");
+
+ // Perform migration.
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true with important telemetry files copied");
+
+ checkDirectoryContains(targetDir, {
+ "datareporting": {
+ "state.json": stateContent,
+ "session-state.json": sessionStateContent,
+ },
+ });
+});
+
+add_task(function* test_fallback_fhr_state() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // Test that we fall back to migrating FHR state if the datareporting
+ // state file does not exist.
+ let stateContent = JSON.stringify({
+ clientId: "68d5474e-19dc-45c1-8e9a-81fca592707c",
+ });
+ let subDir = createSubDir(srcDir, "healthreport");
+ writeToFile(subDir, "state.json", stateContent);
+
+ // Perform migration.
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true");
+
+ checkDirectoryContains(targetDir, {
+ "healthreport": {
+ "state.json": stateContent,
+ },
+ });
+});
+
+
+add_task(function* test_datareporting_not_dir() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ writeToFile(srcDir, "datareporting", "I'm a file but should be a directory");
+
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true even though the directory was a file");
+
+ checkDirectoryContains(targetDir, {});
+});
+
+add_task(function* test_datareporting_empty() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // Migrate with an empty 'datareporting' subdir.
+ createSubDir(srcDir, "datareporting");
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true");
+
+ // We should end up with no migrated files.
+ checkDirectoryContains(targetDir, {
+ "datareporting": {},
+ });
+});
+
+add_task(function* test_healthreport_empty() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // Migrate with no 'datareporting' and an empty 'healthreport' subdir.
+ createSubDir(srcDir, "healthreport");
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true");
+
+ // We should end up with no migrated files.
+ checkDirectoryContains(targetDir, {});
+});
+
+add_task(function* test_datareporting_many() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // Create some datareporting files.
+ let subDir = createSubDir(srcDir, "datareporting");
+ let shouldBeCopied = "should be copied";
+ writeToFile(subDir, "state.json", shouldBeCopied);
+ writeToFile(subDir, "session-state.json", shouldBeCopied);
+ writeToFile(subDir, "something.else", "should not");
+ createSubDir(subDir, "emptyDir");
+
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true");
+
+ checkDirectoryContains(targetDir, {
+ "datareporting" : {
+ "state.json": shouldBeCopied,
+ "session-state.json": shouldBeCopied,
+ }
+ });
+});
+
+add_task(function* test_no_session_state() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // Check that migration still works properly if we only have state.json.
+ let subDir = createSubDir(srcDir, "datareporting");
+ let stateContent = "abcd984";
+ writeToFile(subDir, "state.json", stateContent);
+
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true");
+
+ checkDirectoryContains(targetDir, {
+ "datareporting" : {
+ "state.json": stateContent,
+ }
+ });
+});
+
+add_task(function* test_no_state() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // Check that migration still works properly if we only have session-state.json.
+ let subDir = createSubDir(srcDir, "datareporting");
+ let sessionStateContent = "abcd512";
+ writeToFile(subDir, "session-state.json", sessionStateContent);
+
+ let ok = yield promiseTelemetryMigrator(srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true");
+
+ checkDirectoryContains(targetDir, {
+ "datareporting" : {
+ "session-state.json": sessionStateContent,
+ }
+ });
+});
+
+add_task(function* test_times_migration() {
+ let [srcDir, targetDir] = getTestDirs();
+
+ // create a times.json in the source directory.
+ let contents = JSON.stringify({created: 1234});
+ writeToFile(srcDir, "times.json", contents);
+
+ let earliest = Date.now();
+ let ok = yield promiseMigrator("times", srcDir, targetDir);
+ Assert.ok(ok, "callback should have been true");
+ let latest = Date.now();
+
+ let timesFile = targetDir.clone();
+ timesFile.append("times.json");
+
+ let raw = readFile(timesFile);
+ let times = JSON.parse(raw);
+ Assert.ok(times.reset >= earliest && times.reset <= latest);
+ // and it should have left the creation time alone.
+ Assert.equal(times.created, 1234);
+});
diff --git a/browser/components/migration/tests/unit/xpcshell.ini b/browser/components/migration/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..1b9f0a5f1
--- /dev/null
+++ b/browser/components/migration/tests/unit/xpcshell.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+head = head_migration.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ Library/**
+ AppData/**
+
+[test_automigration.js]
+[test_Chrome_cookies.js]
+skip-if = os != "mac" # Relies on ULibDir
+[test_Chrome_passwords.js]
+skip-if = os != "win"
+[test_Edge_availability.js]
+[test_Edge_db_migration.js]
+skip-if = os != "win" || os_version == "5.1" || os_version == "5.2" # Relies on post-XP bits of ESEDB
+[test_fx_telemetry.js]
+[test_IE_bookmarks.js]
+skip-if = os != "win"
+[test_IE_cookies.js]
+skip-if = os != "win"
+[test_IE7_passwords.js]
+skip-if = os != "win"
+[test_Safari_bookmarks.js]
+skip-if = os != "mac"
diff --git a/browser/components/moz.build b/browser/components/moz.build
new file mode 100644
index 000000000..5ad29d088
--- /dev/null
+++ b/browser/components/moz.build
@@ -0,0 +1,65 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'about',
+ 'contextualidentity',
+ 'customizableui',
+ 'dirprovider',
+ 'downloads',
+ 'extensions',
+ 'feeds',
+ 'migration',
+ 'newtab',
+ 'originattributes',
+ 'places',
+ 'preferences',
+ 'privatebrowsing',
+ 'search',
+ 'sessionstore',
+ 'shell',
+ 'selfsupport',
+ 'syncedtabs',
+ 'uitour',
+ 'translation',
+]
+
+DIRS += ['build']
+
+XPIDL_SOURCES += [
+ 'nsIBrowserGlue.idl',
+ 'nsIBrowserHandler.idl',
+]
+
+XPIDL_MODULE = 'browsercompsbase'
+
+EXTRA_PP_COMPONENTS += [
+ 'BrowserComponents.manifest',
+]
+
+EXTRA_COMPONENTS += [
+ 'nsBrowserContentHandler.js',
+ 'nsBrowserGlue.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'distribution.js',
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ 'safebrowsing/content/test/browser.ini',
+ 'tests/browser/browser.ini'
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'tests/unit/xpcshell.ini'
+]
+
+with Files('safebrowsing/*'):
+ BUG_COMPONENT = ('Toolkit', 'Phishing Protection')
+
+with Files('controlcenter/**'):
+ BUG_COMPONENT = ('Firefox', 'General')
diff --git a/browser/components/newtab/NewTabComponents.manifest b/browser/components/newtab/NewTabComponents.manifest
new file mode 100644
index 000000000..42db65acd
--- /dev/null
+++ b/browser/components/newtab/NewTabComponents.manifest
@@ -0,0 +1,2 @@
+component {dfcd2adc-7867-4d3a-ba70-17501f208142} aboutNewTabService.js
+contract @mozilla.org/browser/aboutnewtab-service;1 {dfcd2adc-7867-4d3a-ba70-17501f208142}
diff --git a/browser/components/newtab/NewTabMessages.jsm b/browser/components/newtab/NewTabMessages.jsm
new file mode 100644
index 000000000..0816ed65a
--- /dev/null
+++ b/browser/components/newtab/NewTabMessages.jsm
@@ -0,0 +1,242 @@
+/* global
+ NewTabWebChannel,
+ NewTabPrefsProvider,
+ PlacesProvider,
+ PreviewProvider,
+ NewTabSearchProvider,
+ Preferences,
+ XPCOMUtils,
+ Task
+*/
+
+/* exported NewTabMessages */
+
+"use strict";
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesProvider",
+ "resource:///modules/PlacesProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
+ "resource:///modules/PreviewProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabSearchProvider",
+ "resource:///modules/NewTabSearchProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
+ "resource:///modules/NewTabWebChannel.jsm");
+
+this.EXPORTED_SYMBOLS = ["NewTabMessages"];
+
+const PREF_ENABLED = "browser.newtabpage.remote";
+const CURRENT_ENGINE = "browser-search-engine-modified";
+
+// Action names are from the content's perspective. in from chrome == out from content
+// Maybe replace the ACTION objects by a bi-directional Map a bit later?
+const ACTIONS = {
+ inboundActions: [
+ "REQUEST_PREFS",
+ "REQUEST_THUMB",
+ "REQUEST_FRECENT",
+ "REQUEST_UISTRINGS",
+ "REQUEST_SEARCH_SUGGESTIONS",
+ "REQUEST_MANAGE_ENGINES",
+ "REQUEST_SEARCH_STATE",
+ "REQUEST_REMOVE_FORM_HISTORY",
+ "REQUEST_PERFORM_SEARCH",
+ "REQUEST_CYCLE_ENGINE",
+ ],
+ prefs: {
+ inPrefs: "REQUEST_PREFS",
+ outPrefs: "RECEIVE_PREFS",
+ },
+ preview: {
+ inThumb: "REQUEST_THUMB",
+ outThumb: "RECEIVE_THUMB",
+ },
+ links: {
+ inFrecent: "REQUEST_FRECENT",
+ outFrecent: "RECEIVE_FRECENT",
+ outPlacesChange: "RECEIVE_PLACES_CHANGE",
+ },
+ search: {
+ inSearch: {
+ UIStrings: "REQUEST_UISTRINGS",
+ suggestions: "REQUEST_SEARCH_SUGGESTIONS",
+ manageEngines: "REQUEST_MANAGE_ENGINES",
+ state: "REQUEST_SEARCH_STATE",
+ removeFormHistory: "REQUEST_REMOVE_FORM_HISTORY",
+ performSearch: "REQUEST_PERFORM_SEARCH",
+ cycleEngine: "REQUEST_CYCLE_ENGINE"
+ },
+ outSearch: {
+ UIStrings: "RECEIVE_UISTRINGS",
+ suggestions: "RECEIVE_SEARCH_SUGGESTIONS",
+ state: "RECEIVE_SEARCH_STATE",
+ currentEngine: "RECEIVE_CURRENT_ENGINE"
+ },
+ }
+};
+
+let NewTabMessages = {
+
+ _prefs: {},
+
+ /** NEWTAB EVENT HANDLERS **/
+
+ handleContentRequest(actionName, {data, target}) {
+ switch (actionName) {
+ case ACTIONS.prefs.inPrefs:
+ // Return to the originator all newtabpage prefs
+ let results = NewTabPrefsProvider.prefs.newtabPagePrefs;
+ NewTabWebChannel.send(ACTIONS.prefs.outPrefs, results, target);
+ break;
+ case ACTIONS.preview.inThumb:
+ // Return to the originator a preview URL
+ PreviewProvider.getThumbnail(data).then(imgData => {
+ NewTabWebChannel.send(ACTIONS.preview.outThumb, {url: data, imgData}, target);
+ });
+ break;
+ case ACTIONS.links.inFrecent:
+ // Return to the originator the top frecent links
+ PlacesProvider.links.getLinks().then(links => {
+ NewTabWebChannel.send(ACTIONS.links.outFrecent, links, target);
+ });
+ break;
+ case ACTIONS.search.inSearch.UIStrings:
+ // Return to the originator all search strings to display
+ let strings = NewTabSearchProvider.search.searchSuggestionUIStrings;
+ NewTabWebChannel.send(ACTIONS.search.outSearch.UIStrings, strings, target);
+ break;
+ case ACTIONS.search.inSearch.suggestions:
+ // Return to the originator all search suggestions
+ Task.spawn(function*() {
+ try {
+ let {engineName, searchString} = data;
+ let suggestions = yield NewTabSearchProvider.search.asyncGetSuggestions(engineName, searchString, target);
+ NewTabWebChannel.send(ACTIONS.search.outSearch.suggestions, suggestions, target);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ });
+ break;
+ case ACTIONS.search.inSearch.manageEngines:
+ // Open about:preferences to manage search state
+ NewTabSearchProvider.search.manageEngines(target.browser);
+ break;
+ case ACTIONS.search.inSearch.state:
+ // Return the state of the search component (i.e current engine and visible engine details)
+ Task.spawn(function*() {
+ try {
+ let state = yield NewTabSearchProvider.search.asyncGetState();
+ NewTabWebChannel.broadcast(ACTIONS.search.outSearch.state, state);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ });
+ break;
+ case ACTIONS.search.inSearch.removeFormHistory:
+ // Remove a form history entry from the search component
+ let suggestion = data;
+ NewTabSearchProvider.search.removeFormHistory(target, suggestion);
+ break;
+ case ACTIONS.search.inSearch.performSearch:
+ // Perform a search
+ NewTabSearchProvider.search.asyncPerformSearch(target, data).catch(Cu.reportError);
+ break;
+ case ACTIONS.search.inSearch.cycleEngine:
+ // Set the new current engine
+ NewTabSearchProvider.search.asyncCycleEngine(data).catch(Cu.reportError);
+ break;
+ }
+ },
+
+ /*
+ * Broadcast places change to all open newtab pages
+ */
+ handlePlacesChange(type, data) {
+ NewTabWebChannel.broadcast(ACTIONS.links.outPlacesChange, {type, data});
+ },
+
+ /*
+ * Broadcast current engine has changed to all open newtab pages
+ */
+ _handleCurrentEngineChange(name, value) { // jshint unused: false
+ let engine = value;
+ NewTabWebChannel.broadcast(ACTIONS.search.outSearch.currentEngine, engine);
+ },
+
+ /*
+ * Broadcast preference changes to all open newtab pages
+ */
+ handlePrefChange(actionName, value) {
+ let prefChange = {};
+ prefChange[actionName] = value;
+ NewTabWebChannel.broadcast(ACTIONS.prefs.outPrefs, prefChange);
+ },
+
+ _handleEnabledChange(prefName, value) {
+ if (prefName === PREF_ENABLED) {
+ if (this._prefs.enabled && !value) {
+ this.uninit();
+ } else if (!this._prefs.enabled && value) {
+ this.init();
+ }
+ }
+ },
+
+ init() {
+ this.handleContentRequest = this.handleContentRequest.bind(this);
+ this._handleEnabledChange = this._handleEnabledChange.bind(this);
+ this._handleCurrentEngineChange = this._handleCurrentEngineChange.bind(this);
+
+ PlacesProvider.links.init();
+ NewTabPrefsProvider.prefs.init();
+ NewTabSearchProvider.search.init();
+ NewTabWebChannel.init();
+
+ this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
+
+ if (this._prefs.enabled) {
+ for (let action of ACTIONS.inboundActions) {
+ NewTabWebChannel.on(action, this.handleContentRequest);
+ }
+
+ NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handleEnabledChange);
+ NewTabSearchProvider.search.on(CURRENT_ENGINE, this._handleCurrentEngineChange);
+
+ for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
+ NewTabPrefsProvider.prefs.on(pref, this.handlePrefChange);
+ }
+
+ PlacesProvider.links.on("deleteURI", this.handlePlacesChange);
+ PlacesProvider.links.on("clearHistory", this.handlePlacesChange);
+ PlacesProvider.links.on("linkChanged", this.handlePlacesChange);
+ PlacesProvider.links.on("manyLinksChanged", this.handlePlacesChange);
+ }
+ },
+
+ uninit() {
+ this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
+
+ if (this._prefs.enabled) {
+ NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handleEnabledChange);
+ NewTabSearchProvider.search.off(CURRENT_ENGINE, this._handleCurrentEngineChange);
+
+ for (let action of ACTIONS.inboundActions) {
+ NewTabWebChannel.off(action, this.handleContentRequest);
+ }
+
+ for (let pref of NewTabPrefsProvider.newtabPagePrefSet) {
+ NewTabPrefsProvider.prefs.off(pref, this.handlePrefChange);
+ }
+ }
+
+ NewTabPrefsProvider.prefs.uninit();
+ NewTabSearchProvider.search.uninit();
+ NewTabWebChannel.uninit();
+ }
+};
diff --git a/browser/components/newtab/NewTabPrefsProvider.jsm b/browser/components/newtab/NewTabPrefsProvider.jsm
new file mode 100644
index 000000000..c1d8b4149
--- /dev/null
+++ b/browser/components/newtab/NewTabPrefsProvider.jsm
@@ -0,0 +1,114 @@
+/* global Services, Preferences, EventEmitter, XPCOMUtils */
+/* exported NewTabPrefsProvider */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["NewTabPrefsProvider"];
+
+const {interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
+ const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+ return EventEmitter;
+});
+
+// Supported prefs and data type
+const gPrefsMap = new Map([
+ ["browser.newtabpage.remote", "bool"],
+ ["browser.newtabpage.remote.mode", "str"],
+ ["browser.newtabpage.remote.version", "str"],
+ ["browser.newtabpage.enabled", "bool"],
+ ["browser.newtabpage.enhanced", "bool"],
+ ["browser.newtabpage.introShown", "bool"],
+ ["browser.newtabpage.updateIntroShown", "bool"],
+ ["browser.newtabpage.pinned", "str"],
+ ["browser.newtabpage.blocked", "str"],
+ ["intl.locale.matchOS", "bool"],
+ ["general.useragent.locale", "localized"],
+ ["browser.search.hiddenOneOffs", "str"],
+]);
+
+// prefs that are important for the newtab page
+const gNewtabPagePrefs = new Set([
+ "browser.newtabpage.enabled",
+ "browser.newtabpage.enhanced",
+ "browser.newtabpage.pinned",
+ "browser.newtabpage.blocked",
+ "browser.newtabpage.introShown",
+ "browser.newtabpage.updateIntroShown",
+ "browser.search.hiddenOneOffs",
+]);
+
+let PrefsProvider = function PrefsProvider() {
+ EventEmitter.decorate(this);
+};
+
+PrefsProvider.prototype = {
+
+ observe(subject, topic, data) { // jshint ignore:line
+ if (topic === "nsPref:changed") {
+ if (gPrefsMap.has(data)) {
+ switch (gPrefsMap.get(data)) {
+ case "bool":
+ this.emit(data, Preferences.get(data, false));
+ break;
+ case "str":
+ this.emit(data, Preferences.get(data, ""));
+ break;
+ case "localized":
+ try {
+ this.emit(data, Preferences.get(data, "", Ci.nsIPrefLocalizedString));
+ } catch (e) {
+ this.emit(data, Preferences.get(data, ""));
+ }
+ break;
+ default:
+ this.emit(data);
+ break;
+ }
+ }
+ } else {
+ Cu.reportError(new Error("NewTabPrefsProvider observing unknown topic"));
+ }
+ },
+
+ /*
+ * Return the preferences that are important to the newtab page
+ */
+ get newtabPagePrefs() {
+ let results = {};
+ for (let pref of gNewtabPagePrefs) {
+ results[pref] = Preferences.get(pref, null);
+ }
+ return results;
+ },
+
+ get prefsMap() {
+ return gPrefsMap;
+ },
+
+ init() {
+ for (let pref of gPrefsMap.keys()) {
+ Services.prefs.addObserver(pref, this, false);
+ }
+ },
+
+ uninit() {
+ for (let pref of gPrefsMap.keys()) {
+ Services.prefs.removeObserver(pref, this, false);
+ }
+ }
+};
+
+/**
+ * Singleton that serves as the default new tab pref provider for the grid.
+ */
+const gPrefs = new PrefsProvider();
+
+let NewTabPrefsProvider = {
+ prefs: gPrefs,
+ newtabPagePrefSet: gNewtabPagePrefs,
+};
diff --git a/browser/components/newtab/NewTabRemoteResources.jsm b/browser/components/newtab/NewTabRemoteResources.jsm
new file mode 100644
index 000000000..57351b15c
--- /dev/null
+++ b/browser/components/newtab/NewTabRemoteResources.jsm
@@ -0,0 +1,15 @@
+/* exported NewTabRemoteResources */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["NewTabRemoteResources"];
+
+const NewTabRemoteResources = {
+ MODE_CHANNEL_MAP: {
+ production: {origin: "https://content.cdn.mozilla.net"},
+ staging: {origin: "https://s3_proxy_tiles.stage.mozaws.net"},
+ test: {origin: "https://example.com"},
+ test2: {origin: "http://mochi.test:8888"},
+ dev: {origin: "http://localhost:8888"}
+ }
+};
diff --git a/browser/components/newtab/NewTabSearchProvider.jsm b/browser/components/newtab/NewTabSearchProvider.jsm
new file mode 100644
index 000000000..a50d8c706
--- /dev/null
+++ b/browser/components/newtab/NewTabSearchProvider.jsm
@@ -0,0 +1,103 @@
+/* global XPCOMUtils, ContentSearch, Task, Services, EventEmitter */
+/* exported NewTabSearchProvider */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["NewTabSearchProvider"];
+
+const {utils: Cu, interfaces: Ci} = Components;
+const CURRENT_ENGINE = "browser-search-engine-modified";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
+ "resource:///modules/ContentSearch.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
+ const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+ return EventEmitter;
+});
+
+function SearchProvider() {
+ EventEmitter.decorate(this);
+}
+
+SearchProvider.prototype = {
+
+ observe(subject, topic, data) { // jshint unused:false
+ // all other topics are not relevant to content searches and can be
+ // ignored by NewTabSearchProvider
+ if (data === "engine-current" && topic === CURRENT_ENGINE) {
+ Task.spawn(function* () {
+ try {
+ let state = yield ContentSearch.currentStateObj(true);
+ let engine = state.currentEngine;
+ this.emit(CURRENT_ENGINE, engine);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }.bind(this));
+ }
+ },
+
+ init() {
+ try {
+ Services.obs.addObserver(this, CURRENT_ENGINE, true);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ]),
+
+ uninit() {
+ try {
+ Services.obs.removeObserver(this, CURRENT_ENGINE, true);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ get searchSuggestionUIStrings() {
+ return ContentSearch.searchSuggestionUIStrings;
+ },
+
+ removeFormHistory({browser}, suggestion) {
+ ContentSearch.removeFormHistoryEntry({target: browser}, suggestion);
+ },
+
+ manageEngines(browser) {
+ const browserWin = browser.ownerGlobal;
+ browserWin.openPreferences("paneSearch");
+ },
+
+ asyncGetState: Task.async(function*() {
+ let state = yield ContentSearch.currentStateObj(true);
+ return state;
+ }),
+
+ asyncPerformSearch: Task.async(function*({browser}, searchData) {
+ ContentSearch.performSearch({target: browser}, searchData);
+ yield ContentSearch.addFormHistoryEntry({target: browser}, searchData.searchString);
+ }),
+
+ asyncCycleEngine: Task.async(function*(engineName) {
+ Services.search.currentEngine = Services.search.getEngineByName(engineName);
+ let state = yield ContentSearch.currentStateObj(true);
+ let newEngine = state.currentEngine;
+ this.emit(CURRENT_ENGINE, newEngine);
+ }),
+
+ asyncGetSuggestions: Task.async(function*(engineName, searchString, target) {
+ let suggestions = ContentSearch.getSuggestions(engineName, searchString, target.browser);
+ return suggestions;
+ }),
+};
+
+const NewTabSearchProvider = {
+ search: new SearchProvider(),
+};
diff --git a/browser/components/newtab/NewTabURL.jsm b/browser/components/newtab/NewTabURL.jsm
new file mode 100644
index 000000000..5000eae2e
--- /dev/null
+++ b/browser/components/newtab/NewTabURL.jsm
@@ -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/. */
+
+/* globals XPCOMUtils, aboutNewTabService*/
+/* exported NewTabURL */
+
+"use strict";
+
+const {utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = ["NewTabURL"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+this.NewTabURL = {
+
+ get: function() {
+ return aboutNewTabService.newTabURL;
+ },
+
+ get overridden() {
+ return aboutNewTabService.overridden;
+ },
+
+ override: function(newURL) {
+ aboutNewTabService.newTabURL = newURL;
+ },
+
+ reset: function() {
+ aboutNewTabService.resetNewTabURL();
+ }
+};
diff --git a/browser/components/newtab/NewTabWebChannel.jsm b/browser/components/newtab/NewTabWebChannel.jsm
new file mode 100644
index 000000000..40ee73684
--- /dev/null
+++ b/browser/components/newtab/NewTabWebChannel.jsm
@@ -0,0 +1,299 @@
+/* global
+ NewTabPrefsProvider,
+ Services,
+ EventEmitter,
+ Preferences,
+ XPCOMUtils,
+ WebChannel,
+ NewTabRemoteResources
+*/
+/* exported NewTabWebChannel */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["NewTabWebChannel"];
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
+ "resource:///modules/NewTabRemoteResources.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
+ "resource://gre/modules/WebChannel.jsm");
+XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
+ const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+ return EventEmitter;
+});
+
+const CHAN_ID = "newtab";
+const PREF_ENABLED = "browser.newtabpage.remote";
+const PREF_MODE = "browser.newtabpage.remote.mode";
+
+/**
+ * NewTabWebChannel is the conduit for all communication with unprivileged newtab instances.
+ *
+ * It allows for the ability to broadcast to all newtab browsers.
+ * If the browser.newtab.remote pref is false, the object will be in an uninitialized state.
+ *
+ * Mode choices:
+ * 'production': pages from our production CDN
+ * 'staging': pages from our staging CDN
+ * 'test': intended for tests
+ * 'test2': intended for tests
+ * 'dev': intended for development
+ *
+ * An unknown mode will result in 'production' mode, which is the default
+ *
+ * Incoming messages are expected to be JSON-serialized and in the format:
+ *
+ * {
+ * type: "REQUEST_SCREENSHOT",
+ * data: {
+ * url: "https://example.com"
+ * }
+ * }
+ *
+ * Or:
+ *
+ * {
+ * type: "REQUEST_SCREENSHOT",
+ * }
+ *
+ * Outgoing messages are expected to be objects serializable by structured cloning, in a similar format:
+ * {
+ * type: "RECEIVE_SCREENSHOT",
+ * data: {
+ * "url": "https://example.com",
+ * "image": "dataURi:....."
+ * }
+ * }
+ */
+let NewTabWebChannelImpl = function NewTabWebChannelImpl() {
+ EventEmitter.decorate(this);
+ this._handlePrefChange = this._handlePrefChange.bind(this);
+ this._incomingMessage = this._incomingMessage.bind(this);
+};
+
+NewTabWebChannelImpl.prototype = {
+ _prefs: {},
+ _channel: null,
+
+ // a WeakMap containing browsers as keys and a weak ref to their principal
+ // as value
+ _principals: null,
+
+ // a Set containing weak refs to browsers
+ _browsers: null,
+
+ /*
+ * Returns current channel's ID
+ */
+ get chanId() {
+ return CHAN_ID;
+ },
+
+ /*
+ * Returns the number of browsers currently tracking
+ */
+ get numBrowsers() {
+ return this._getBrowserRefs().length;
+ },
+
+ /*
+ * Returns current channel's origin
+ */
+ get origin() {
+ if (!(this._prefs.mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
+ this._prefs.mode = "production";
+ }
+ return NewTabRemoteResources.MODE_CHANNEL_MAP[this._prefs.mode].origin;
+ },
+
+ /*
+ * Unloads all browsers and principals
+ */
+ _unloadAll() {
+ if (this._principals != null) {
+ this._principals = new WeakMap();
+ }
+ this._browsers = new Set();
+ this.emit("targetUnloadAll");
+ },
+
+ /*
+ * Checks if a browser is known
+ *
+ * This will cause an iteration through all known browsers.
+ * That's ok, we don't expect a lot of browsers
+ */
+ _isBrowserKnown(browser) {
+ for (let bRef of this._getBrowserRefs()) {
+ let b = bRef.get();
+ if (b && b.permanentKey === browser.permanentKey) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /*
+ * Obtains all known browser refs
+ */
+ _getBrowserRefs() {
+ // Some code may try to emit messages after teardown.
+ if (!this._browsers) {
+ return [];
+ }
+ let refs = [];
+ for (let bRef of this._browsers) {
+ /*
+ * even though we hold a weak ref to browser, it seems that browser
+ * objects aren't gc'd immediately after a tab closes. They stick around
+ * in memory, but thankfully they don't have a documentURI in that case
+ */
+ let browser = bRef.get();
+ if (browser && browser.documentURI) {
+ refs.push(bRef);
+ } else {
+ // need to clean up principals because the browser object is not gc'ed
+ // immediately
+ this._principals.delete(browser);
+ this._browsers.delete(bRef);
+ this.emit("targetUnload");
+ }
+ }
+ return refs;
+ },
+
+ /*
+ * Receives a message from content.
+ *
+ * Keeps track of browsers for broadcast, relays messages to listeners.
+ */
+ _incomingMessage(id, message, target) {
+ if (this.chanId !== id) {
+ Cu.reportError(new Error("NewTabWebChannel unexpected message destination"));
+ }
+
+ /*
+ * need to differentiate by browser, because event targets are created each
+ * time a message is sent.
+ */
+ if (!this._isBrowserKnown(target.browser)) {
+ this._browsers.add(Cu.getWeakReference(target.browser));
+ this._principals.set(target.browser, Cu.getWeakReference(target.principal));
+ this.emit("targetAdd");
+ }
+
+ try {
+ let msg = JSON.parse(message);
+ this.emit(msg.type, {data: msg.data, target: target});
+ } catch (err) {
+ Cu.reportError(err);
+ }
+ },
+
+ /*
+ * Sends a message to all known browsers
+ */
+ broadcast(actionType, message) {
+ for (let bRef of this._getBrowserRefs()) {
+ let browser = bRef.get();
+ try {
+ let principal = this._principals.get(browser).get();
+ if (principal && browser && browser.documentURI) {
+ this._channel.send({type: actionType, data: message}, {browser, principal});
+ }
+ } catch (e) {
+ Cu.reportError(new Error("NewTabWebChannel WeakRef is dead"));
+ this._principals.delete(browser);
+ }
+ }
+ },
+
+ /*
+ * Sends a message to a specific target
+ */
+ send(actionType, message, target) {
+ try {
+ this._channel.send({type: actionType, data: message}, target);
+ } catch (e) {
+ // Web Channel might be dead
+ Cu.reportError(e);
+ }
+ },
+
+ /*
+ * Pref change observer callback
+ */
+ _handlePrefChange(prefName, newState, forceState) { // eslint-disable-line no-unused-vars
+ switch (prefName) {
+ case PREF_ENABLED:
+ if (!this._prefs.enabled && newState) {
+ // changing state from disabled to enabled
+ this.setupState();
+ } else if (this._prefs.enabled && !newState) {
+ // changing state from enabled to disabled
+ this.tearDownState();
+ }
+ break;
+ case PREF_MODE:
+ if (this._prefs.mode !== newState) {
+ // changing modes
+ this.tearDownState();
+ this.setupState();
+ }
+ break;
+ }
+ },
+
+ /*
+ * Sets up the internal state
+ */
+ setupState() {
+ this._prefs.enabled = Preferences.get(PREF_ENABLED, false);
+
+ let mode = Preferences.get(PREF_MODE, "production");
+ if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
+ mode = "production";
+ }
+ this._prefs.mode = mode;
+ this._principals = new WeakMap();
+ this._browsers = new Set();
+
+ if (this._prefs.enabled) {
+ this._channel = new WebChannel(this.chanId, Services.io.newURI(this.origin, null, null));
+ this._channel.listen(this._incomingMessage);
+ }
+ },
+
+ tearDownState() {
+ if (this._channel) {
+ this._channel.stopListening();
+ }
+ this._prefs = {};
+ this._unloadAll();
+ this._channel = null;
+ this._principals = null;
+ this._browsers = null;
+ },
+
+ init() {
+ this.setupState();
+ NewTabPrefsProvider.prefs.on(PREF_ENABLED, this._handlePrefChange);
+ NewTabPrefsProvider.prefs.on(PREF_MODE, this._handlePrefChange);
+ },
+
+ uninit() {
+ this.tearDownState();
+ NewTabPrefsProvider.prefs.off(PREF_ENABLED, this._handlePrefChange);
+ NewTabPrefsProvider.prefs.off(PREF_MODE, this._handlePrefChange);
+ }
+};
+
+let NewTabWebChannel = new NewTabWebChannelImpl();
diff --git a/browser/components/newtab/PlacesProvider.jsm b/browser/components/newtab/PlacesProvider.jsm
new file mode 100644
index 000000000..f478b5c5c
--- /dev/null
+++ b/browser/components/newtab/PlacesProvider.jsm
@@ -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/. */
+
+/* global XPCOMUtils, Services, PlacesUtils, EventEmitter */
+/* global gLinks */
+/* exported PlacesProvider */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PlacesProvider"];
+
+const {interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
+ const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
+ return EventEmitter;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
+ "resource://gre/modules/NewTabUtils.jsm");
+
+// The maximum number of results PlacesProvider retrieves from history.
+const HISTORY_RESULTS_LIMIT = 100;
+
+/* Queries history to retrieve the most visited sites. Emits events when the
+ * history changes.
+ * Implements the EventEmitter interface.
+ */
+let Links = function Links() {
+ EventEmitter.decorate(this);
+};
+
+Links.prototype = {
+ /**
+ * Set this to change the maximum number of links the provider will provide.
+ */
+ get maxNumLinks() {
+ // getter, so it can't be replaced dynamically
+ return HISTORY_RESULTS_LIMIT;
+ },
+
+ /**
+ * A set of functions called by @mozilla.org/browser/nav-historyservice
+ * All history events are emitted from this object.
+ */
+ historyObserver: {
+ onDeleteURI: function historyObserver_onDeleteURI(aURI) {
+ // let observers remove sensetive data associated with deleted visit
+ gLinks.emit("deleteURI", {
+ url: aURI.spec,
+ });
+ },
+
+ onClearHistory: function historyObserver_onClearHistory() {
+ gLinks.emit("clearHistory");
+ },
+
+ onFrecencyChanged: function historyObserver_onFrecencyChanged(aURI,
+ aNewFrecency, aGUID, aHidden, aLastVisitDate) { // jshint ignore:line
+ // The implementation of the query in getLinks excludes hidden and
+ // unvisited pages, so it's important to exclude them here, too.
+ if (!aHidden && aLastVisitDate &&
+ NewTabUtils.linkChecker.checkLoadURI(aURI.spec)) {
+ gLinks.emit("linkChanged", {
+ url: aURI.spec,
+ frecency: aNewFrecency,
+ lastVisitDate: aLastVisitDate,
+ type: "history",
+ });
+ }
+ },
+
+ onManyFrecenciesChanged: function historyObserver_onManyFrecenciesChanged() {
+ // Called when frecencies are invalidated and also when clearHistory is called
+ // See toolkit/components/places/tests/unit/test_frecency_observers.js
+ gLinks.emit("manyLinksChanged");
+ },
+
+ onTitleChanged: function historyObserver_onTitleChanged(aURI, aNewTitle) {
+ if (NewTabUtils.linkChecker.checkLoadURI(aURI.spec)) {
+ gLinks.emit("linkChanged", {
+ url: aURI.spec,
+ title: aNewTitle
+ });
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
+ Ci.nsISupportsWeakReference])
+ },
+
+ /**
+ * Must be called before the provider is used.
+ * Makes it easy to disable under pref
+ */
+ init: function PlacesProvider_init() {
+ try {
+ PlacesUtils.history.addObserver(this.historyObserver, true);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Gets the current set of links delivered by this provider.
+ *
+ * @returns {Promise} Returns a promise with the array of links as payload.
+ */
+ getLinks: Task.async(function*() {
+ // Select a single page per host with highest frecency, highest recency.
+ // Choose N top such pages. Note +rev_host, to turn off optimizer per :mak
+ // suggestion.
+ let sqlQuery = `SELECT url, title, frecency,
+ last_visit_date as lastVisitDate,
+ "history" as type
+ FROM moz_places
+ WHERE frecency in (
+ SELECT MAX(frecency) as frecency
+ FROM moz_places
+ WHERE hidden = 0 AND last_visit_date NOTNULL
+ GROUP BY +rev_host
+ ORDER BY frecency DESC
+ LIMIT :limit
+ )
+ GROUP BY rev_host HAVING MAX(lastVisitDate)
+ ORDER BY frecency DESC, lastVisitDate DESC, url`;
+
+ let links = yield this.executePlacesQuery(sqlQuery, {
+ columns: ["url", "title", "lastVisitDate", "frecency", "type"],
+ params: {limit: this.maxNumLinks}
+ });
+
+ return links.filter(link => NewTabUtils.linkChecker.checkLoadURI(link.url));
+ }),
+
+ /**
+ * Executes arbitrary query against places database
+ *
+ * @param {String} aSql
+ * SQL query to execute
+ * @param {Object} [optional] aOptions
+ * aOptions.columns - an array of column names. if supplied the returned
+ * items will consist of objects keyed on column names. Otherwise
+ * an array of raw values is returned in the select order
+ * aOptions.param - an object of SQL binding parameters
+ * aOptions.callback - a callback to handle query rows
+ *
+ * @returns {Promise} Returns a promise with the array of retrieved items
+ */
+ executePlacesQuery: Task.async(function*(aSql, aOptions={}) {
+ let {columns, params, callback} = aOptions;
+ let items = [];
+ let queryError = null;
+ let conn = yield PlacesUtils.promiseDBConnection();
+ yield conn.executeCached(aSql, params, aRow => {
+ try {
+ // check if caller wants to handle query raws
+ if (callback) {
+ callback(aRow);
+ }
+ // otherwise fill in the item and add items array
+ else {
+ let item = null;
+ // if columns array is given construct an object
+ if (columns && Array.isArray(columns)) {
+ item = {};
+ columns.forEach(column => {
+ item[column] = aRow.getResultByName(column);
+ });
+ } else {
+ // if no columns - make an array of raw values
+ item = [];
+ for (let i = 0; i < aRow.numEntries; i++) {
+ item.push(aRow.getResultByIndex(i));
+ }
+ }
+ items.push(item);
+ }
+ } catch (e) {
+ queryError = e;
+ throw StopIteration;
+ }
+ });
+ if (queryError) {
+ throw new Error(queryError);
+ }
+ return items;
+ }),
+};
+
+/**
+ * Singleton that serves as the default link provider for the grid.
+ */
+const gLinks = new Links(); // jshint ignore:line
+
+let PlacesProvider = {
+ links: gLinks,
+};
+
+// Kept only for backwards-compatibility
+XPCOMUtils.defineLazyGetter(PlacesProvider, "LinkChecker",
+ () => NewTabUtils.linkChecker);
+
diff --git a/browser/components/newtab/PreviewProvider.jsm b/browser/components/newtab/PreviewProvider.jsm
new file mode 100644
index 000000000..8624b8544
--- /dev/null
+++ b/browser/components/newtab/PreviewProvider.jsm
@@ -0,0 +1,49 @@
+/* global XPCOMUtils, BackgroundPageThumbs, FileUtils, PageThumbsStorage, Task, MIMEService */
+/* exported PreviewProvider */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PreviewProvider"];
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BackgroundPageThumbs",
+ "resource://gre/modules/BackgroundPageThumbs.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "MIMEService",
+ "@mozilla.org/mime;1", "nsIMIMEService");
+
+let PreviewProvider = {
+ /**
+ * Returns a thumbnail as a data URI for a url, creating it if necessary
+ *
+ * @param {String} url
+ * a url to obtain a thumbnail for
+ * @return {Promise} A Promise that resolves with a base64 encoded thumbnail
+ */
+ getThumbnail: Task.async(function* PreviewProvider_getThumbnail(url) {
+ try {
+ yield BackgroundPageThumbs.captureIfMissing(url);
+ let imgPath = PageThumbsStorage.getFilePathForURL(url);
+
+ // OS.File object used to easily read off-thread
+ let file = yield OS.File.open(imgPath, {read: true, existing: true});
+
+ // nsIFile object needed for MIMEService
+ let nsFile = FileUtils.File(imgPath);
+
+ let contentType = MIMEService.getTypeFromFile(nsFile);
+ let bytes = yield file.read();
+ let encodedData = btoa(String.fromCharCode.apply(null, bytes));
+ file.close();
+ return `data:${contentType};base64,${encodedData}`;
+ } catch (err) {
+ Cu.reportError(`PreviewProvider_getThumbnail error: ${err}`);
+ throw err;
+ }
+ })
+};
diff --git a/browser/components/newtab/aboutNewTabService.js b/browser/components/newtab/aboutNewTabService.js
new file mode 100644
index 000000000..54c3749e8
--- /dev/null
+++ b/browser/components/newtab/aboutNewTabService.js
@@ -0,0 +1,289 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 XPCOMUtils, NewTabPrefsProvider, Services,
+ Locale, UpdateUtils, NewTabRemoteResources
+*/
+"use strict";
+
+const {utils: Cu, interfaces: Ci} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Locale",
+ "resource://gre/modules/Locale.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
+ "resource:///modules/NewTabRemoteResources.jsm");
+
+const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
+
+const REMOTE_NEWTAB_PATH = "/newtab/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
+
+const ABOUT_URL = "about:newtab";
+
+// Pref that tells if remote newtab is enabled
+const PREF_REMOTE_ENABLED = "browser.newtabpage.remote";
+
+// Pref branch necesssary for testing
+const PREF_REMOTE_CS_TEST = "browser.newtabpage.remote.content-signing-test";
+
+// The preference that tells whether to match the OS locale
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+
+// The preference that tells what locale the user selected
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+
+// The preference that tells what remote mode is enabled.
+const PREF_REMOTE_MODE = "browser.newtabpage.remote.mode";
+
+// The preference that tells which remote version is expected.
+const PREF_REMOTE_VERSION = "browser.newtabpage.remote.version";
+
+const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
+
+function AboutNewTabService() {
+ NewTabPrefsProvider.prefs.on(PREF_REMOTE_ENABLED, this._handleToggleEvent.bind(this));
+
+ this._updateRemoteMaybe = this._updateRemoteMaybe.bind(this);
+
+ // trigger remote change if needed, according to pref
+ this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED));
+}
+
+/*
+ * A service that allows for the overriding, at runtime, of the newtab page's url.
+ * Additionally, the service manages pref state between a remote and local newtab page.
+ *
+ * There is tight coupling with browser/about/AboutRedirector.cpp.
+ *
+ * 1. Browser chrome access:
+ *
+ * When the user issues a command to open a new tab page, usually clicking a button
+ * in the browser chrome or using shortcut keys, the browser chrome code invokes the
+ * service to obtain the newtab URL. It then loads that URL in a new tab.
+ *
+ * When not overridden, the default URL emitted by the service is "about:newtab".
+ * When overridden, it returns the overriden URL.
+ *
+ * 2. Redirector Access:
+ *
+ * When the URL loaded is about:newtab, the default behavior, or when entered in the
+ * URL bar, the redirector is hit. The service is then called to return either of
+ * two URLs, a chrome or remote one, based on the browser.newtabpage.remote pref.
+ *
+ * NOTE: "about:newtab" will always result in a default newtab page, and never an overridden URL.
+ *
+ * Access patterns:
+ *
+ * The behavior is different when accessing the service via browser chrome or via redirector
+ * largely to maintain compatibility with expectations of add-on developers.
+ *
+ * Loading a chrome resource, or an about: URL in the redirector with either the
+ * LOAD_NORMAL or LOAD_REPLACE flags yield unexpected behaviors, so a roundtrip
+ * to the redirector from browser chrome is avoided.
+ */
+AboutNewTabService.prototype = {
+
+ _newTabURL: ABOUT_URL,
+ _remoteEnabled: false,
+ _remoteURL: null,
+ _overridden: false,
+
+ classID: Components.ID("{dfcd2adc-7867-4d3a-ba70-17501f208142}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutNewTabService]),
+ _xpcom_categories: [{
+ service: true
+ }],
+
+ _handleToggleEvent(prefName, stateEnabled, forceState) { // jshint unused:false
+ if (this.toggleRemote(stateEnabled, forceState)) {
+ Services.obs.notifyObservers(null, "newtab-url-changed", ABOUT_URL);
+ }
+ },
+
+ /**
+ * React to changes to the remote newtab pref.
+ *
+ * If browser.newtabpage.remote is true, this will change the default URL to the
+ * remote newtab page URL. If browser.newtabpage.remote is false, the default URL
+ * will be a local chrome URL.
+ *
+ * This will only act if there is a change of state and if not overridden.
+ *
+ * @returns {Boolean} Returns if there has been a state change
+ *
+ * @param {Boolean} stateEnabled remote state to set to
+ * @param {Boolean} forceState force state change
+ */
+ toggleRemote(stateEnabled, forceState) {
+
+ if (!forceState && (this._overriden || stateEnabled === this._remoteEnabled)) {
+ // exit there is no change of state
+ return false;
+ }
+
+ let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
+ if (stateEnabled) {
+ if (!csTest) {
+ this._remoteURL = this.generateRemoteURL();
+ } else {
+ this._remoteURL = this._newTabURL;
+ }
+ NewTabPrefsProvider.prefs.on(
+ PREF_SELECTED_LOCALE,
+ this._updateRemoteMaybe);
+ NewTabPrefsProvider.prefs.on(
+ PREF_MATCH_OS_LOCALE,
+ this._updateRemoteMaybe);
+ NewTabPrefsProvider.prefs.on(
+ PREF_REMOTE_MODE,
+ this._updateRemoteMaybe);
+ NewTabPrefsProvider.prefs.on(
+ PREF_REMOTE_VERSION,
+ this._updateRemoteMaybe);
+ this._remoteEnabled = true;
+ } else {
+ NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe);
+ NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe);
+ NewTabPrefsProvider.prefs.off(PREF_REMOTE_MODE, this._updateRemoteMaybe);
+ NewTabPrefsProvider.prefs.off(PREF_REMOTE_VERSION, this._updateRemoteMaybe);
+ this._remoteEnabled = false;
+ }
+ if (!csTest) {
+ this._newTabURL = ABOUT_URL;
+ }
+ return true;
+ },
+
+ /*
+ * Generate a default url based on remote mode, version, locale and update channel
+ */
+ generateRemoteURL() {
+ let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
+ let path = REMOTE_NEWTAB_PATH
+ .replace("%VERSION%", this.remoteVersion)
+ .replace("%LOCALE%", Locale.getLocale())
+ .replace("%CHANNEL%", releaseName);
+ let mode = Services.prefs.getCharPref(PREF_REMOTE_MODE, "production");
+ if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
+ mode = "production";
+ }
+ return NewTabRemoteResources.MODE_CHANNEL_MAP[mode].origin + path;
+ },
+
+ /*
+ * Returns the default URL.
+ *
+ * This URL only depends on the browser.newtabpage.remote pref. Overriding
+ * the newtab page has no effect on the result of this function.
+ *
+ * The result is also the remote URL if this is in a test (PREF_REMOTE_CS_TEST)
+ *
+ * @returns {String} the default newtab URL, remote or local depending on browser.newtabpage.remote
+ */
+ get defaultURL() {
+ let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
+ if (this._remoteEnabled || csTest) {
+ return this._remoteURL;
+ }
+ return LOCAL_NEWTAB_URL;
+ },
+
+ /*
+ * Updates the remote location when the page is not overriden.
+ *
+ * Useful when there is a dependent pref change
+ */
+ _updateRemoteMaybe() {
+ if (!this._remoteEnabled || this._overridden) {
+ return;
+ }
+
+ let url = this.generateRemoteURL();
+ if (url !== this._remoteURL) {
+ this._remoteURL = url;
+ Services.obs.notifyObservers(null, "newtab-url-changed",
+ this._remoteURL);
+ }
+ },
+
+ /**
+ * Returns the release name from an Update Channel name
+ *
+ * @returns {String} a release name based on the update channel. Defaults to nightly
+ */
+ releaseFromUpdateChannel(channelName) {
+ return VALID_CHANNELS.has(channelName) ? channelName : "nightly";
+ },
+
+ get newTabURL() {
+ return this._newTabURL;
+ },
+
+ get remoteVersion() {
+ return Services.prefs.getCharPref(PREF_REMOTE_VERSION, "1");
+ },
+
+ get remoteReleaseName() {
+ return this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
+ },
+
+ set newTabURL(aNewTabURL) {
+ let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
+ aNewTabURL = aNewTabURL.trim();
+ if (aNewTabURL === ABOUT_URL) {
+ // avoid infinite redirects in case one sets the URL to about:newtab
+ this.resetNewTabURL();
+ return;
+ } else if (aNewTabURL === "") {
+ aNewTabURL = "about:blank";
+ }
+ let remoteURL = this.generateRemoteURL();
+ let prefRemoteEnabled = Services.prefs.getBoolPref(PREF_REMOTE_ENABLED);
+ let isResetLocal = !prefRemoteEnabled && aNewTabURL === LOCAL_NEWTAB_URL;
+ let isResetRemote = prefRemoteEnabled && aNewTabURL === remoteURL;
+
+ if (isResetLocal || isResetRemote) {
+ if (this._overriden && !csTest) {
+ // only trigger a reset if previously overridden and this is no test
+ this.resetNewTabURL();
+ }
+ return;
+ }
+ // turn off remote state if needed
+ if (!csTest) {
+ this.toggleRemote(false);
+ } else {
+ // if this is a test, we want the remoteURL to be set
+ this._remoteURL = aNewTabURL;
+ }
+ this._newTabURL = aNewTabURL;
+ this._overridden = true;
+ Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
+ },
+
+ get overridden() {
+ return this._overridden;
+ },
+
+ get remoteEnabled() {
+ return this._remoteEnabled;
+ },
+
+ resetNewTabURL() {
+ this._overridden = false;
+ this._newTabURL = ABOUT_URL;
+ this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED), true);
+ Services.obs.notifyObservers(null, "newtab-url-changed", this._newTabURL);
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutNewTabService]);
diff --git a/browser/components/newtab/moz.build b/browser/components/newtab/moz.build
new file mode 100644
index 000000000..37c9983f7
--- /dev/null
+++ b/browser/components/newtab/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'tests/xpcshell/xpcshell.ini',
+]
+
+EXTRA_JS_MODULES += [
+ 'NewTabMessages.jsm',
+ 'NewTabPrefsProvider.jsm',
+ 'NewTabRemoteResources.jsm',
+ 'NewTabSearchProvider.jsm',
+ 'NewTabURL.jsm',
+ 'NewTabWebChannel.jsm',
+ 'PlacesProvider.jsm',
+ 'PreviewProvider.jsm'
+]
+
+XPIDL_SOURCES += [
+ 'nsIAboutNewTabService.idl',
+]
+
+XPIDL_MODULE = 'browser-newtab'
+
+EXTRA_COMPONENTS += [
+ 'aboutNewTabService.js',
+ 'NewTabComponents.manifest',
+]
diff --git a/browser/components/newtab/nsIAboutNewTabService.idl b/browser/components/newtab/nsIAboutNewTabService.idl
new file mode 100644
index 000000000..bc25c7492
--- /dev/null
+++ b/browser/components/newtab/nsIAboutNewTabService.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Allows to override about:newtab to point to a different location
+ * than the one specified within AboutRedirector.cpp
+ */
+
+[scriptable, uuid(dfcd2adc-7867-4d3a-ba70-17501f208142)]
+interface nsIAboutNewTabService : nsISupports
+{
+ /**
+ * Returns the url of the resource for the newtab page if not overridden,
+ * otherwise a string represenation of the new URL.
+ */
+ attribute ACString newTabURL;
+
+ /**
+ * Returns the default URL (remote or local depending on pref)
+ */
+ attribute ACString defaultURL;
+
+ /**
+ * Returns true if the default resource got overridden.
+ */
+ readonly attribute bool overridden;
+
+ /**
+ * Returns true if the default resource is remotely hosted and isn't
+ * overridden
+ */
+ readonly attribute bool remoteEnabled;
+
+
+ /**
+ * Returns the version of the remote newtab page expected
+ */
+ readonly attribute ACString remoteVersion;
+
+ /**
+ * Returns the expected channel for the remote the newtab page
+ */
+ readonly attribute ACString remoteReleaseName;
+
+ /**
+ * Generates and returns the remote newtab page url
+ */
+ ACString generateRemoteURL();
+
+ /**
+ * Returns a remote new tab release name given an update channel name
+ */
+ ACString releaseFromUpdateChannel(in ACString channelName);
+
+ /**
+ * Resets to the default resource and also resets the
+ * overridden attribute to false.
+ */
+ void resetNewTabURL();
+};
diff --git a/browser/components/newtab/tests/browser/.eslintrc.js b/browser/components/newtab/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/newtab/tests/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/newtab/tests/browser/blue_page.html b/browser/components/newtab/tests/browser/blue_page.html
new file mode 100644
index 000000000..a7f000bfd
--- /dev/null
+++ b/browser/components/newtab/tests/browser/blue_page.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body style="background-color: blue">
+</body>
+</html>
diff --git a/browser/components/newtab/tests/browser/browser.ini b/browser/components/newtab/tests/browser/browser.ini
new file mode 100644
index 000000000..fa740be9e
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+support-files =
+ blue_page.html
+ dummy_page.html
+ newtabwebchannel_basic.html
+ newtabmessages_places.html
+ newtabmessages_prefs.html
+ newtabmessages_preview.html
+ newtabmessages_search.html
+
+[browser_PreviewProvider.js]
+[browser_remotenewtab_pageloads.js]
+[browser_newtab_overrides.js]
+[browser_newtabmessages.js]
+skip-if = true # Bug 1271177, bug 1262719
+[browser_newtabwebchannel.js]
diff --git a/browser/components/newtab/tests/browser/browser_PreviewProvider.js b/browser/components/newtab/tests/browser/browser_PreviewProvider.js
new file mode 100644
index 000000000..b1e3eda9f
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_PreviewProvider.js
@@ -0,0 +1,90 @@
+/* globals XPCOMUtils, Services, PreviewProvider, registerCleanupFunction */
+"use strict";
+
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PreviewProvider",
+ "resource:///modules/PreviewProvider.jsm");
+
+var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
+
+registerCleanupFunction(function() {
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeTab(gBrowser.tabs[1]);
+ }
+ Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
+});
+
+const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
+
+function pixelsForDataURI(dataURI, options) {
+ return new Promise(resolve => {
+ if (!options) {
+ options = {};
+ }
+ let {width, height} = options;
+ if (!width) {
+ width = 100;
+ }
+ if (!height) {
+ height = 100;
+ }
+
+ let htmlns = "http://www.w3.org/1999/xhtml";
+ let img = document.createElementNS(htmlns, "img");
+ img.setAttribute("src", dataURI);
+
+ img.addEventListener("load", function onLoad() {
+ img.removeEventListener("load", onLoad, true);
+ let canvas = document.createElementNS(htmlns, "canvas");
+ canvas.setAttribute("width", width);
+ canvas.setAttribute("height", height);
+ let ctx = canvas.getContext("2d");
+ ctx.drawImage(img, 0, 0, width, height);
+ let result = ctx.getImageData(0, 0, width, height).data;
+ resolve(result);
+ });
+ });
+}
+
+function* chunk_four(listData) {
+ let index = 0;
+ while (index < listData.length) {
+ yield listData.slice(index, index + 5);
+ index += 4;
+ }
+}
+
+add_task(function* open_page() {
+ let dataURI = yield PreviewProvider.getThumbnail(TEST_URL);
+ let pixels = yield pixelsForDataURI(dataURI, {width: 10, height: 10});
+ let rgbCount = {r: 0, g: 0, b: 0, a: 0};
+ for (let [r, g, b, a] of chunk_four(pixels)) {
+ if (r === 255) {
+ rgbCount.r += 1;
+ }
+ if (g === 255) {
+ rgbCount.g += 1;
+ }
+ if (b === 255) {
+ rgbCount.b += 1;
+ }
+ if (a === 255) {
+ rgbCount.a += 1;
+ }
+ }
+ Assert.equal(`${rgbCount.r},${rgbCount.g},${rgbCount.b},${rgbCount.a}`,
+ "0,0,100,100", "there should be 100 blue-only pixels at full opacity");
+});
+
+add_task(function* invalid_url() {
+ try {
+ yield PreviewProvider.getThumbnail("invalid:URL");
+ } catch (err) {
+ Assert.ok(true, "URL Failed");
+ }
+});
diff --git a/browser/components/newtab/tests/browser/browser_newtab_overrides.js b/browser/components/newtab/tests/browser/browser_newtab_overrides.js
new file mode 100644
index 000000000..eab9092a0
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_newtab_overrides.js
@@ -0,0 +1,139 @@
+/* globals
+ XPCOMUtils,
+ aboutNewTabService,
+ Services,
+ ContentTask,
+ TestUtils,
+ BrowserOpenTab,
+ registerCleanupFunction,
+ is,
+ content
+*/
+
+"use strict";
+
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+registerCleanupFunction(function() {
+ Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+ aboutNewTabService.resetNewTabURL();
+});
+
+/*
+ * Tests that the default newtab page is always returned when one types "about:newtab" in the URL bar,
+ * even when overridden.
+ */
+add_task(function* redirector_ignores_override() {
+ let overrides = [
+ "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
+ "about:home",
+ ];
+
+ for (let overrideURL of overrides) {
+ let notificationPromise = nextChangeNotificationPromise(overrideURL, `newtab page now points to ${overrideURL}`);
+ aboutNewTabService.newTabURL = overrideURL;
+
+ yield notificationPromise;
+ Assert.ok(aboutNewTabService.overridden, "url has been overridden");
+
+ let tabOptions = {
+ gBrowser,
+ url: "about:newtab",
+ };
+
+ /*
+ * Simulate typing "about:newtab" in the url bar.
+ *
+ * Bug 1240169 - We expect the redirector to lead the user to "about:newtab", the default URL,
+ * due to invoking AboutRedirector. A user interacting with the chrome otherwise would lead
+ * to the overriding URLs.
+ */
+ yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
+ yield ContentTask.spawn(browser, {}, function*() {
+ Assert.equal(content.location.href, "about:newtab", "Got right URL");
+ Assert.equal(content.document.location.href, "about:newtab", "Got right URL");
+ Assert.equal(content.document.nodePrincipal,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ "nodePrincipal should match systemPrincipal");
+ });
+ }); // jshint ignore:line
+ }
+});
+
+/*
+ * Tests loading an overridden newtab page by simulating opening a newtab page from chrome
+ */
+add_task(function* override_loads_in_browser() {
+ let overrides = [
+ "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
+ "about:home",
+ " about:home",
+ ];
+
+ for (let overrideURL of overrides) {
+ let notificationPromise = nextChangeNotificationPromise(overrideURL.trim(), `newtab page now points to ${overrideURL}`);
+ aboutNewTabService.newTabURL = overrideURL;
+
+ yield notificationPromise;
+ Assert.ok(aboutNewTabService.overridden, "url has been overridden");
+
+ // simulate a newtab open as a user would
+ BrowserOpenTab(); // jshint ignore:line
+
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield ContentTask.spawn(browser, {url: overrideURL}, function*(args) {
+ Assert.equal(content.location.href, args.url.trim(), "Got right URL");
+ Assert.equal(content.document.location.href, args.url.trim(), "Got right URL");
+ }); // jshint ignore:line
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+/*
+ * Tests edge cases when someone overrides the newtabpage with whitespace
+ */
+add_task(function* override_blank_loads_in_browser() {
+ let overrides = [
+ "",
+ " ",
+ "\n\t",
+ " about:blank",
+ ];
+
+ for (let overrideURL of overrides) {
+ let notificationPromise = nextChangeNotificationPromise("about:blank", "newtab page now points to about:blank");
+ aboutNewTabService.newTabURL = overrideURL;
+
+ yield notificationPromise;
+ Assert.ok(aboutNewTabService.overridden, "url has been overridden");
+
+ // simulate a newtab open as a user would
+ BrowserOpenTab(); // jshint ignore:line
+
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield ContentTask.spawn(browser, {}, function*() {
+ Assert.equal(content.location.href, "about:blank", "Got right URL");
+ Assert.equal(content.document.location.href, "about:blank", "Got right URL");
+ }); // jshint ignore:line
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+});
+
+function nextChangeNotificationPromise(aNewURL, testMessage) {
+ return TestUtils.topicObserved("newtab-url-changed", function observer(aSubject, aData) { // jshint unused:false
+ Assert.equal(aData, aNewURL, testMessage);
+ return true;
+ }.bind(this));
+}
diff --git a/browser/components/newtab/tests/browser/browser_newtabmessages.js b/browser/components/newtab/tests/browser/browser_newtabmessages.js
new file mode 100644
index 000000000..319ca1c34
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_newtabmessages.js
@@ -0,0 +1,222 @@
+/* globals Cu, XPCOMUtils, Preferences, is, registerCleanupFunction, NewTabWebChannel,
+PlacesTestUtils, NewTabMessages, ok, Services, PlacesUtils, NetUtil, Task */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
+ "resource:///modules/NewTabWebChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
+ "resource:///modules/NewTabMessages.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+let setup = Task.async(function*() {
+ Preferences.set("browser.newtabpage.enhanced", true);
+ Preferences.set("browser.newtabpage.remote.mode", "test");
+ Preferences.set("browser.newtabpage.remote", true);
+ NewTabMessages.init();
+ yield PlacesTestUtils.clearHistory();
+});
+
+let cleanup = Task.async(function*() {
+ NewTabMessages.uninit();
+ Preferences.set("browser.newtabpage.remote", false);
+ Preferences.set("browser.newtabpage.remote.mode", "production");
+});
+registerCleanupFunction(cleanup);
+
+/*
+ * Sanity tests for pref messages
+ */
+add_task(function* prefMessages_request() {
+ yield setup();
+
+ let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_prefs.html";
+
+ let tabOptions = {
+ gBrowser,
+ url: testURL
+ };
+
+ let prefResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("responseAck", () => {
+ ok(true, "a request response has been received");
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab(tabOptions, function*() {
+ yield prefResponseAck;
+ let prefChangeAck = new Promise(resolve => {
+ NewTabWebChannel.once("responseAck", () => {
+ ok(true, "a change response has been received");
+ resolve();
+ });
+ });
+ Preferences.set("browser.newtabpage.enhanced", false);
+ yield prefChangeAck;
+ });
+ yield cleanup();
+});
+
+/*
+ * Sanity tests for preview messages
+ */
+add_task(function* previewMessages_request() {
+ yield setup();
+ var oldEnabledPref = Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+ Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", false);
+
+ let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_preview.html";
+
+ let tabOptions = {
+ gBrowser,
+ url: testURL
+ };
+
+ let previewResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("responseAck", () => {
+ ok(true, "a request response has been received");
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab(tabOptions, function*() {
+ yield previewResponseAck;
+ });
+ yield cleanup();
+ Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", oldEnabledPref);
+});
+
+/*
+ * Sanity tests for places messages
+ */
+add_task(function* placesMessages_request() {
+ yield setup();
+ let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_places.html";
+
+ // url prefix for test history population
+ const TEST_URL = "https://mozilla.com/";
+ // time when the test starts execution
+ const TIME_NOW = (new Date()).getTime();
+
+ // utility function to compute past timestamp
+ function timeDaysAgo(numDays) {
+ return TIME_NOW - (numDays * 24 * 60 * 60 * 1000);
+ }
+
+ // utility function to make a visit for insertion into places db
+ function makeVisit(index, daysAgo, isTyped, domain=TEST_URL) {
+ let {
+ TRANSITION_TYPED,
+ TRANSITION_LINK
+ } = PlacesUtils.history;
+
+ return {
+ uri: NetUtil.newURI(`${domain}${index}`),
+ visitDate: timeDaysAgo(daysAgo),
+ transition: (isTyped) ? TRANSITION_TYPED : TRANSITION_LINK,
+ };
+ }
+
+ yield PlacesTestUtils.clearHistory();
+
+ // all four visits must come from different domains to avoid deduplication
+ let visits = [
+ makeVisit(0, 0, true, "http://bar.com/"), // frecency 200, today
+ makeVisit(1, 0, true, "http://foo.com/"), // frecency 200, today
+ makeVisit(2, 2, true, "http://buz.com/"), // frecency 200, 2 days ago
+ makeVisit(3, 2, false, "http://aaa.com/"), // frecency 10, 2 days ago, transition
+ ];
+
+ yield PlacesTestUtils.addVisits(visits);
+
+ /** Test Begins **/
+
+ let tabOptions = {
+ gBrowser,
+ url: testURL
+ };
+
+ let placesResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("numItemsAck", (_, msg) => {
+ ok(true, "a request response has been received");
+ is(msg.data, visits.length + 1, "received an expected number of history items");
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab(tabOptions, function*() {
+ yield placesResponseAck;
+ ok(true, "a change response has been received");
+ let placesChangeAck = new Promise(resolve => {
+ NewTabWebChannel.once("clearHistoryAck", (_, msg) => {
+ is(msg.data, "clearHistory", "a clear history message has been received");
+ resolve();
+ });
+ });
+ yield PlacesTestUtils.clearHistory();
+ yield placesChangeAck;
+ });
+ yield cleanup();
+});
+
+/*
+ * Sanity tests for search messages
+ */
+add_task(function* searchMessages_request() {
+ yield setup();
+ let testURL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabmessages_search.html";
+
+ // create dummy test engines
+ Services.search.addEngineWithDetails("Engine1", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ Services.search.addEngineWithDetails("Engine2", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ let tabOptions = {
+ gBrowser,
+ url: testURL
+ };
+
+ let UIStringsResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("UIStringsAck", (_, msg) => {
+ ok(true, "a search request response for UI string has been received");
+ ok(msg.data, "received the UI Strings");
+ resolve();
+ });
+ });
+ let suggestionsResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("suggestionsAck", (_, msg) => {
+ ok(true, "a search request response for suggestions has been received");
+ ok(msg.data, "received the suggestions");
+ resolve();
+ });
+ });
+ let stateResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("stateAck", (_, msg) => {
+ ok(true, "a search request response for state has been received");
+ ok(msg.data, "received a state object");
+ resolve();
+ });
+ });
+ let currentEngineResponseAck = new Promise(resolve => {
+ NewTabWebChannel.once("currentEngineAck", (_, msg) => {
+ ok(true, "a search request response for current engine has been received");
+ ok(msg.data, "received a current engine");
+ resolve();
+ });
+ });
+
+ yield BrowserTestUtils.withNewTab(tabOptions, function*() {
+ yield UIStringsResponseAck;
+ yield suggestionsResponseAck;
+ yield stateResponseAck;
+ yield currentEngineResponseAck;
+ });
+
+ cleanup();
+});
diff --git a/browser/components/newtab/tests/browser/browser_newtabwebchannel.js b/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
new file mode 100644
index 000000000..f003b105b
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_newtabwebchannel.js
@@ -0,0 +1,251 @@
+/* globals XPCOMUtils, Cu, Preferences, NewTabWebChannel, is, registerCleanupFunction */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabWebChannel",
+ "resource:///modules/NewTabWebChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabMessages",
+ "resource:///modules/NewTabMessages.jsm");
+
+const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
+const TEST_URL_2 = "http://mochi.test:8888/browser/browser/components/newtab/tests/browser/newtabwebchannel_basic.html";
+
+function setup(mode = "test") {
+ Preferences.set("browser.newtabpage.remote.mode", mode);
+ Preferences.set("browser.newtabpage.remote", true);
+ NewTabWebChannel.init();
+ NewTabMessages.init();
+}
+
+function cleanup() {
+ NewTabMessages.uninit();
+ NewTabWebChannel.uninit();
+ Preferences.set("browser.newtabpage.remote", false);
+ Preferences.set("browser.newtabpage.remote.mode", "production");
+}
+registerCleanupFunction(cleanup);
+
+/*
+ * Tests flow of messages from newtab to chrome and chrome to newtab
+ */
+add_task(function* open_webchannel_basic() {
+ setup();
+
+ let tabOptions = {
+ gBrowser,
+ url: TEST_URL
+ };
+
+ let messagePromise = new Promise(resolve => {
+ NewTabWebChannel.once("foo", function(name, msg) {
+ is(name, "foo", "Correct message type sent: foo");
+ is(msg.data, "bar", "Correct data sent: bar");
+ resolve(msg.target);
+ });
+ });
+
+ let replyPromise = new Promise(resolve => {
+ NewTabWebChannel.once("reply", function(name, msg) {
+ is(name, "reply", "Correct message type sent: reply");
+ is(msg.data, "quuz", "Correct data sent: quuz");
+ resolve(msg.target);
+ });
+ });
+
+ let unloadPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnload", function(name) {
+ is(name, "targetUnload", "Correct message type sent: targetUnload");
+ resolve();
+ });
+ });
+
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
+ let target = yield messagePromise;
+ is(NewTabWebChannel.numBrowsers, 1, "One target expected");
+ is(target.browser, browser, "Same browser");
+ NewTabWebChannel.send("respond", null, target);
+ yield replyPromise;
+ });
+
+ Cu.forceGC();
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield unloadPromise;
+ cleanup();
+});
+
+/*
+ * Tests message broadcast reaches all open newtab pages
+ */
+add_task(function* webchannel_broadcast() {
+ setup();
+
+ let countingMessagePromise = new Promise(resolve => {
+ let count = 0;
+ NewTabWebChannel.on("foo", function test_message(name, msg) {
+ count += 1;
+ if (count === 2) {
+ NewTabWebChannel.off("foo", test_message);
+ resolve(msg.target);
+ }
+ }.bind(this));
+ });
+
+ let countingReplyPromise = new Promise(resolve => {
+ let count = 0;
+ NewTabWebChannel.on("reply", function test_message(name, msg) {
+ count += 1;
+ if (count === 2) {
+ NewTabWebChannel.off("reply", test_message);
+ resolve(msg.target);
+ }
+ }.bind(this));
+ });
+
+ let countingUnloadPromise = new Promise(resolve => {
+ let count = 0;
+ NewTabWebChannel.on("targetUnload", function test_message() {
+ count += 1;
+ if (count === 2) {
+ NewTabWebChannel.off("targetUnload", test_message);
+ resolve();
+ }
+ });
+ });
+
+ let tabs = [];
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
+ tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
+
+ yield countingMessagePromise;
+ is(NewTabWebChannel.numBrowsers, 2, "Two targets expected");
+
+ NewTabWebChannel.broadcast("respond", null);
+ yield countingReplyPromise;
+
+ for (let tab of tabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ Cu.forceGC();
+
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield countingUnloadPromise;
+ cleanup();
+});
+
+/*
+ * Tests switching modes
+ */
+add_task(function* webchannel_switch() {
+ setup();
+
+ function newMessagePromise() {
+ return new Promise(resolve => {
+ NewTabWebChannel.once("foo", function(name, msg) {
+ resolve(msg.target);
+ }.bind(this));
+ });
+ }
+
+ let replyCount = 0;
+ function newReplyPromise() {
+ return new Promise(resolve => {
+ NewTabWebChannel.on("reply", function() {
+ replyCount += 1;
+ resolve();
+ });
+ });
+ }
+
+ let unloadPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnload", function() {
+ resolve();
+ });
+ });
+
+ let unloadAllPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnloadAll", function() {
+ resolve();
+ });
+ });
+
+ let tabs = [];
+ let messagePromise;
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+
+ messagePromise = newMessagePromise();
+ tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL));
+ yield messagePromise;
+ is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
+
+ messagePromise = newMessagePromise();
+ Preferences.set("browser.newtabpage.remote.mode", "test2");
+ tabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL_2));
+ yield unloadAllPromise;
+ yield messagePromise;
+ is(NewTabWebChannel.numBrowsers, 1, "Correct number of targets");
+
+ NewTabWebChannel.broadcast("respond", null);
+ yield newReplyPromise();
+ is(replyCount, 1, "only current channel is listened to for replies");
+
+ const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist";
+ let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref);
+ let newWhitelist = origWhitelist + " http://mochi.test:8888";
+ Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist);
+ try {
+ NewTabWebChannel.broadcast("respond_object", null);
+ yield newReplyPromise();
+ } finally {
+ Services.prefs.clearUserPref(webchannelWhitelistPref);
+ }
+
+ for (let tab of tabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+
+ Cu.forceGC();
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield unloadPromise;
+ cleanup();
+});
+
+add_task(function* open_webchannel_reload() {
+ setup();
+
+ let tabOptions = {
+ gBrowser,
+ url: TEST_URL
+ };
+
+ let messagePromise = new Promise(resolve => {
+ NewTabWebChannel.once("foo", function(name, msg) {
+ is(name, "foo", "Correct message type sent: foo");
+ is(msg.data, "bar", "Correct data sent: bar");
+ resolve(msg.target);
+ });
+ });
+ let unloadPromise = new Promise(resolve => {
+ NewTabWebChannel.once("targetUnload", function() {
+ resolve();
+ });
+ });
+
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield BrowserTestUtils.withNewTab(tabOptions, function*(browser) {
+ let target = yield messagePromise;
+ is(NewTabWebChannel.numBrowsers, 1, "One target expected");
+ is(target.browser, browser, "Same browser");
+
+ browser.reload();
+ });
+
+ Cu.forceGC();
+ is(NewTabWebChannel.numBrowsers, 0, "Sanity check");
+ yield unloadPromise;
+ cleanup();
+});
diff --git a/browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js b/browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js
new file mode 100644
index 000000000..e99aeffc2
--- /dev/null
+++ b/browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js
@@ -0,0 +1,52 @@
+/* globals Cu, XPCOMUtils, TestUtils, aboutNewTabService, ContentTask, content, is */
+"use strict";
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+const TEST_URL = "https://example.com/browser/browser/components/newtab/tests/browser/dummy_page.html";
+
+/*
+ * Tests opening a newtab page with a remote URL. Simulates a newtab open from chrome
+ */
+add_task(function* open_newtab() {
+ let notificationPromise = nextChangeNotificationPromise(TEST_URL, "newtab page now points to test url");
+ aboutNewTabService.newTabURL = TEST_URL;
+
+ yield notificationPromise;
+ Assert.ok(aboutNewTabService.overridden, "url has been overridden");
+
+ /*
+ * Simulate a newtab open as a user would.
+ *
+ * Bug 1240169 - We cannot set the URL to about:newtab because that would invoke the redirector.
+ * The redirector always yields the loading of a default newtab URL. We expect the user to use
+ * the browser UI to access overriding URLs, for istance by click on the "+" button in the tab
+ * bar, or by using the new tab shortcut key.
+ */
+ BrowserOpenTab(); // jshint ignore:line
+
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield ContentTask.spawn(browser, {url: TEST_URL}, function*(args) {
+ Assert.equal(content.document.location.href, args.url,
+ "document.location should match the external resource");
+ Assert.equal(content.document.documentURI, args.url,
+ "document.documentURI should match the external resource");
+ Assert.equal(content.document.nodePrincipal.URI.spec, args.url,
+ "nodePrincipal should match the external resource");
+ });
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+function nextChangeNotificationPromise(aNewURL, testMessage) {
+ return TestUtils.topicObserved("newtab-url-changed", function observer(aSubject, aData) { // jshint unused:false
+ Assert.equal(aData, aNewURL, testMessage);
+ return true;
+ }.bind(this));
+}
diff --git a/browser/components/newtab/tests/browser/dummy_page.html b/browser/components/newtab/tests/browser/dummy_page.html
new file mode 100644
index 000000000..4b0689bde
--- /dev/null
+++ b/browser/components/newtab/tests/browser/dummy_page.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+<p>Dummy Page</p>
+</body>
+</html>
diff --git a/browser/components/newtab/tests/browser/newtabmessages_places.html b/browser/components/newtab/tests/browser/newtabmessages_places.html
new file mode 100644
index 000000000..e89bc4a22
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabmessages_places.html
@@ -0,0 +1,49 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Newtab WebChannel test</title>
+ </head>
+ <body>
+ <script>
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ if (e.detail.message) {
+ let reply;
+ switch (e.detail.message.type) {
+ case "RECEIVE_FRECENT":
+ reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "numItemsAck", data: e.detail.message.data.length}),
+ })
+ });
+ window.dispatchEvent(reply);
+ break;
+ case "RECEIVE_PLACES_CHANGE":
+ if (e.detail.message.data.type === "clearHistory") {
+ reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "clearHistoryAck", data: e.detail.message.data.type}),
+ })
+ });
+ window.dispatchEvent(reply);
+ }
+ break;
+ }
+ }
+ }, true);
+
+ document.onreadystatechange = function () {
+ if (document.readyState === "complete") {
+ let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_FRECENT"}),
+ })
+ });
+ window.dispatchEvent(msg);
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/browser/components/newtab/tests/browser/newtabmessages_prefs.html b/browser/components/newtab/tests/browser/newtabmessages_prefs.html
new file mode 100644
index 000000000..9b38af4b9
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabmessages_prefs.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Newtab WebChannel test</title>
+ </head>
+ <body>
+ <script>
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ if (e.detail.message && e.detail.message.type === "RECEIVE_PREFS") {
+ let reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "responseAck"}),
+ })
+ });
+ window.dispatchEvent(reply);
+ }
+ }, true);
+
+ document.onreadystatechange = function () {
+ let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_PREFS"}),
+ })
+ });
+ window.dispatchEvent(msg);
+ };
+
+ </script>
+ </body>
+</html>
diff --git a/browser/components/newtab/tests/browser/newtabmessages_preview.html b/browser/components/newtab/tests/browser/newtabmessages_preview.html
new file mode 100644
index 000000000..4fe55132d
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabmessages_preview.html
@@ -0,0 +1,37 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Newtab WebChannel test</title>
+ </head>
+ <body>
+ <script>
+ let thumbURL = "https://example.com/browser/browser/components/newtab/tests/browser/blue_page.html";
+
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ if (e.detail.message && e.detail.message.type === "RECEIVE_THUMB") {
+ if (e.detail.message.data.imgData && e.detail.message.data.url === thumbURL) {
+ let reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "responseAck"}),
+ })
+ });
+ window.dispatchEvent(reply);
+ }
+ }
+ }, true);
+
+ document.onreadystatechange = function () {
+ if (document.readyState === "complete") {
+ let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_THUMB", data: thumbURL}),
+ })
+ });
+ window.dispatchEvent(msg);
+ }
+ };
+ </script>
+ </body>
+</html>
diff --git a/browser/components/newtab/tests/browser/newtabmessages_search.html b/browser/components/newtab/tests/browser/newtabmessages_search.html
new file mode 100644
index 000000000..b8b21c42a
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabmessages_search.html
@@ -0,0 +1,113 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Newtab WebChannel test</title>
+ </head>
+ <body>
+ <script>
+ let suggestionsData = {
+ engineName: "Engine1",
+ searchString: "test",
+ };
+ let removeFormHistoryData = "test";
+ let performSearchData = {
+ engineName: "Engine1",
+ healthReportKey: "1",
+ searchPurpose: "d",
+ searchString: "test",
+ };
+ let cycleEngineData = "Engine2";
+
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ if (e.detail.message) {
+ let reply;
+ switch (e.detail.message.type) {
+ case "RECEIVE_UISTRINGS":
+ reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "UIStringsAck", data: e.detail.message.data}),
+ }
+ });
+ window.dispatchEvent(reply);
+ break;
+ case "RECEIVE_SEARCH_SUGGESTIONS":
+ reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "suggestionsAck", data: e.detail.message.data}),
+ }
+ });
+ window.dispatchEvent(reply);
+ break;
+ case "RECEIVE_SEARCH_STATE":
+ reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "stateAck", data: e.detail.message.data}),
+ }
+ });
+ window.dispatchEvent(reply);
+ break;
+ case "RECEIVE_CURRENT_ENGINE":
+ reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "currentEngineAck", data: e.detail.message.data}),
+ }
+ });
+ window.dispatchEvent(reply);
+ break;
+ }
+ }
+ }, true);
+
+ document.onreadystatechange = function () {
+ if (document.readyState === "complete") {
+ let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_UISTRINGS"}),
+ }
+ });
+ window.dispatchEvent(msg);
+ msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_SEARCH_SUGGESTIONS", data: suggestionsData}),
+ }
+ });
+ window.dispatchEvent(msg);
+ msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_SEARCH_STATE"}),
+ }
+ });
+ window.dispatchEvent(msg);
+ msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_REMOVE_FORM_HISTORY", data: removeFormHistoryData}),
+ }
+ });
+ window.dispatchEvent(msg);
+ msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_PERFORM_SEARCH", data: performSearchData}),
+ }
+ });
+ window.dispatchEvent(msg);
+ msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: {
+ id: "newtab",
+ message: JSON.stringify({type: "REQUEST_CYCLE_ENGINE", data: cycleEngineData}),
+ }
+ });
+ window.dispatchEvent(msg);
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/browser/components/newtab/tests/browser/newtabwebchannel_basic.html b/browser/components/newtab/tests/browser/newtabwebchannel_basic.html
new file mode 100644
index 000000000..7f3c79920
--- /dev/null
+++ b/browser/components/newtab/tests/browser/newtabwebchannel_basic.html
@@ -0,0 +1,36 @@
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Newtab WebChannel test</title>
+ </head>
+ <body>
+ <script>
+ document.onreadystatechange = function () {
+ let msg = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: JSON.stringify({
+ id: "newtab",
+ message: JSON.stringify({type: "foo", data: "bar"}),
+ })
+ });
+ window.dispatchEvent(msg);
+ };
+
+ window.addEventListener("WebChannelMessageToContent", function(e) {
+ if (e.detail.message && e.detail.message.type.startsWith("respond")) {
+ var detail = {
+ id: "newtab",
+ message: JSON.stringify({type: "reply", data: "quuz"}),
+ };
+ if (e.detail.message.type !== "respond_object") {
+ detail = JSON.stringify(detail);
+ }
+ let reply = new window.CustomEvent("WebChannelMessageToChrome", {
+ detail: detail
+ });
+ window.dispatchEvent(reply);
+ }
+ }, true);
+
+ </script>
+ </body>
+</html>
diff --git a/browser/components/newtab/tests/xpcshell/.eslintrc.js b/browser/components/newtab/tests/xpcshell/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/newtab/tests/xpcshell/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js b/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
new file mode 100644
index 000000000..21f68ab70
--- /dev/null
+++ b/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
@@ -0,0 +1,236 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* globals Services, XPCOMUtils, NewTabPrefsProvider, Preferences, aboutNewTabService, do_register_cleanup */
+
+"use strict";
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Locale",
+ "resource://gre/modules/Locale.jsm");
+
+const DEFAULT_HREF = aboutNewTabService.generateRemoteURL();
+const DEFAULT_CHROME_URL = "chrome://browser/content/newtab/newTab.xhtml";
+const DOWNLOADS_URL = "chrome://browser/content/downloads/contentAreaDownloadsView.xul";
+const DEFAULT_VERSION = aboutNewTabService.remoteVersion;
+
+function cleanup() {
+ Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+ Services.prefs.setCharPref("browser.newtabpage.remote.version", DEFAULT_VERSION);
+ aboutNewTabService.resetNewTabURL();
+ NewTabPrefsProvider.prefs.uninit();
+}
+
+do_register_cleanup(cleanup);
+
+/**
+ * Test the overriding of the default URL
+ */
+add_task(function* test_override_remote_disabled() {
+ NewTabPrefsProvider.prefs.init();
+ let notificationPromise;
+ Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+
+ // tests default is the local newtab resource
+ Assert.equal(aboutNewTabService.defaultURL, DEFAULT_CHROME_URL,
+ `Default newtab URL should be ${DEFAULT_CHROME_URL}`);
+
+ // override with some remote URL
+ let url = "http://example.com/";
+ notificationPromise = nextChangeNotificationPromise(url);
+ aboutNewTabService.newTabURL = url;
+ yield notificationPromise;
+ Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
+ Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
+ Assert.equal(aboutNewTabService.newTabURL, url, "Newtab URL should be the custom URL");
+
+ // test reset with remote disabled
+ notificationPromise = nextChangeNotificationPromise("about:newtab");
+ aboutNewTabService.resetNewTabURL();
+ yield notificationPromise;
+ Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
+ Assert.equal(aboutNewTabService.newTabURL, "about:newtab", "Newtab URL should be the default");
+
+ // test override to a chrome URL
+ notificationPromise = nextChangeNotificationPromise(DOWNLOADS_URL);
+ aboutNewTabService.newTabURL = DOWNLOADS_URL;
+ yield notificationPromise;
+ Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
+ Assert.equal(aboutNewTabService.newTabURL, DOWNLOADS_URL, "Newtab URL should be the custom URL");
+
+ cleanup();
+});
+
+add_task(function* test_override_remote_enabled() {
+ NewTabPrefsProvider.prefs.init();
+ let notificationPromise;
+ // change newtab page to remote
+ notificationPromise = nextChangeNotificationPromise("about:newtab");
+ Services.prefs.setBoolPref("browser.newtabpage.remote", true);
+ yield notificationPromise;
+ let remoteHref = aboutNewTabService.generateRemoteURL();
+ Assert.equal(aboutNewTabService.defaultURL, remoteHref, "Newtab URL should be the default remote URL");
+ Assert.ok(!aboutNewTabService.overridden, "Newtab URL should not be overridden");
+ Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
+
+ // change to local newtab page while remote is enabled
+ notificationPromise = nextChangeNotificationPromise(DEFAULT_CHROME_URL);
+ aboutNewTabService.newTabURL = DEFAULT_CHROME_URL;
+ yield notificationPromise;
+ Assert.equal(aboutNewTabService.newTabURL, DEFAULT_CHROME_URL,
+ "Newtab URL set to chrome url");
+ Assert.equal(aboutNewTabService.defaultURL, DEFAULT_CHROME_URL,
+ "Newtab URL defaultURL set to the default chrome URL");
+ Assert.ok(aboutNewTabService.overridden, "Newtab URL should be overridden");
+ Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
+
+ cleanup();
+});
+
+/**
+ * Tests reponse to updates to prefs
+ */
+add_task(function* test_updates() {
+ /*
+ * Simulates a "cold-boot" situation, with some pref already set before testing a series
+ * of changes.
+ */
+ Preferences.set("browser.newtabpage.remote", true);
+ aboutNewTabService.resetNewTabURL(); // need to set manually because pref notifs are off
+ let notificationPromise;
+ let productionModeBaseUrl = "https://content.cdn.mozilla.net";
+ let testModeBaseUrl = "https://example.com";
+ let expectedPath = `/newtab` +
+ `/v${aboutNewTabService.remoteVersion}` +
+ `/${aboutNewTabService.remoteReleaseName}` +
+ "/en-GB" +
+ "/index.html";
+ let expectedHref = productionModeBaseUrl + expectedPath;
+ Preferences.set("intl.locale.matchOS", true);
+ Preferences.set("general.useragent.locale", "en-GB");
+ Preferences.set("browser.newtabpage.remote.mode", "production");
+ NewTabPrefsProvider.prefs.init();
+
+ // test update checks for prefs
+ notificationPromise = nextChangeNotificationPromise(
+ expectedHref, "Remote href should be updated");
+ Preferences.set("intl.locale.matchOS", false);
+ yield notificationPromise;
+
+ notificationPromise = nextChangeNotificationPromise(
+ DEFAULT_HREF, "Remote href changes back to default");
+ Preferences.set("general.useragent.locale", "en-US");
+ yield notificationPromise;
+
+ // test update fires when mode is changed
+ expectedPath = expectedPath.replace("/en-GB/", "/en-US/");
+ notificationPromise = nextChangeNotificationPromise(
+ testModeBaseUrl + expectedPath, "Remote href changes back to origin of test mode");
+ Preferences.set("browser.newtabpage.remote.mode", "test");
+ yield notificationPromise;
+
+ // test invalid mode ends up pointing to production url
+ notificationPromise = nextChangeNotificationPromise(
+ DEFAULT_HREF, "Remote href changes back to production default");
+ Preferences.set("browser.newtabpage.remote.mode", "invalid");
+ yield notificationPromise;
+
+ // test update fires on override and reset
+ let testURL = "https://example.com/";
+ notificationPromise = nextChangeNotificationPromise(
+ testURL, "a notification occurs on override");
+ aboutNewTabService.newTabURL = testURL;
+ yield notificationPromise;
+
+ // from overridden to default
+ notificationPromise = nextChangeNotificationPromise(
+ "about:newtab", "a notification occurs on reset");
+ aboutNewTabService.resetNewTabURL();
+ Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
+ Assert.equal(aboutNewTabService.defaultURL, DEFAULT_HREF, "Default URL should be the remote page");
+ yield notificationPromise;
+
+ // override to default URL from default URL
+ notificationPromise = nextChangeNotificationPromise(
+ testURL, "a notification only occurs for a change in overridden urls");
+ aboutNewTabService.newTabURL = aboutNewTabService.generateRemoteURL();
+ Assert.ok(aboutNewTabService.remoteEnabled, "Newtab remote should be enabled");
+ aboutNewTabService.newTabURL = testURL;
+ yield notificationPromise;
+ Assert.ok(!aboutNewTabService.remoteEnabled, "Newtab remote should not be enabled");
+
+ // reset twice, only one notification for default URL
+ notificationPromise = nextChangeNotificationPromise(
+ "about:newtab", "reset occurs");
+ aboutNewTabService.resetNewTabURL();
+ yield notificationPromise;
+
+ cleanup();
+});
+
+/**
+ * Verifies that releaseFromUpdateChannel
+ * Returns the correct release names
+ */
+add_task(function* test_release_names() {
+ let valid_channels = ["esr", "release", "beta", "aurora", "nightly"];
+ let invalid_channels = new Set(["default", "invalid"]);
+
+ for (let channel of valid_channels) {
+ Assert.equal(channel, aboutNewTabService.releaseFromUpdateChannel(channel),
+ "release == channel name when valid");
+ }
+
+ for (let channel of invalid_channels) {
+ Assert.equal("nightly", aboutNewTabService.releaseFromUpdateChannel(channel),
+ "release == nightly when invalid");
+ }
+});
+
+/**
+ * Verifies that remote version updates changes the remote newtab url
+ */
+add_task(function* test_version_update() {
+ NewTabPrefsProvider.prefs.init();
+
+ Services.prefs.setBoolPref("browser.newtabpage.remote", true);
+ Assert.ok(aboutNewTabService.remoteEnabled, "remote mode enabled");
+
+ let productionModeBaseUrl = "https://content.cdn.mozilla.net";
+ let version_incr = String(parseInt(DEFAULT_VERSION) + 1);
+ let expectedPath = `/newtab` +
+ `/v${version_incr}` +
+ `/${aboutNewTabService.remoteReleaseName}` +
+ `/${Locale.getLocale()}` +
+ `/index.html`;
+ let expectedHref = productionModeBaseUrl + expectedPath;
+
+ let notificationPromise;
+ notificationPromise = nextChangeNotificationPromise(expectedHref);
+ Preferences.set("browser.newtabpage.remote.version", version_incr);
+ yield notificationPromise;
+
+ cleanup();
+});
+
+function nextChangeNotificationPromise(aNewURL, testMessage) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint unused:false
+ Services.obs.removeObserver(observer, aTopic);
+ Assert.equal(aData, aNewURL, testMessage);
+ resolve();
+ }, "newtab-url-changed", false);
+ });
+}
diff --git a/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js b/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js
new file mode 100644
index 000000000..f364d0300
--- /dev/null
+++ b/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js
@@ -0,0 +1,50 @@
+"use strict";
+
+/* global XPCOMUtils, equal, Preferences, NewTabPrefsProvider, run_next_test */
+/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
+
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_observe() {
+ let prefsMap = NewTabPrefsProvider.prefs.prefsMap;
+ for (let prefName of prefsMap.keys()) {
+ let prefValueType = prefsMap.get(prefName);
+
+ let beforeVal;
+ let afterVal;
+
+ switch (prefValueType) {
+ case "bool":
+ beforeVal = false;
+ afterVal = true;
+ Preferences.set(prefName, beforeVal);
+ break;
+ case "localized":
+ case "str":
+ beforeVal = "";
+ afterVal = "someStr";
+ Preferences.set(prefName, beforeVal);
+ break;
+ }
+ NewTabPrefsProvider.prefs.init();
+ let promise = new Promise(resolve => {
+ NewTabPrefsProvider.prefs.once(prefName, (name, data) => { // jshint ignore:line
+ resolve([name, data]);
+ });
+ });
+ Preferences.set(prefName, afterVal);
+ let [actualName, actualData] = yield promise;
+ equal(prefName, actualName, `emitter sent the correct pref: ${prefName}`);
+ equal(afterVal, actualData, `emitter collected correct pref data for ${prefName}`);
+ NewTabPrefsProvider.prefs.uninit();
+ }
+});
diff --git a/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js b/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js
new file mode 100644
index 000000000..3e60b282a
--- /dev/null
+++ b/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js
@@ -0,0 +1,82 @@
+"use strict";
+
+/* global XPCOMUtils, NewTabSearchProvider, run_next_test, ok, equal, do_check_true, do_get_profile, Services */
+/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
+
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabSearchProvider",
+ "resource:///modules/NewTabSearchProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
+ "resource:///modules/ContentSearch.jsm");
+
+// ensure a profile exists
+do_get_profile();
+
+function run_test() {
+ run_next_test();
+}
+
+function hasProp(obj) {
+ return function(aProp) {
+ ok(obj.hasOwnProperty(aProp), `expect to have property ${aProp}`);
+ };
+}
+
+add_task(function* test_search() {
+ ContentSearch.init();
+ let observerPromise = new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ if (aData === "init-complete" && aTopic === "browser-search-service") {
+ Services.obs.removeObserver(observer, "browser-search-service");
+ resolve();
+ }
+ }, "browser-search-service", false);
+ });
+ Services.search.init();
+ yield observerPromise;
+ do_check_true(Services.search.isInitialized);
+
+ // get initial state of search and check it has correct properties
+ let state = yield NewTabSearchProvider.search.asyncGetState();
+ let stateProps = hasProp(state);
+ ["engines", "currentEngine"].forEach(stateProps);
+
+ // check that the current engine is correct and has correct properties
+ let {currentEngine} = state;
+ equal(currentEngine.name, Services.search.currentEngine.name, "Current engine has been correctly set");
+ var engineProps = hasProp(currentEngine);
+ ["name", "placeholder", "iconBuffer"].forEach(engineProps);
+
+ // create dummy test engines to test observer
+ Services.search.addEngineWithDetails("TestSearch1", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+ Services.search.addEngineWithDetails("TestSearch2", "", "", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ // set one of the dummy test engines to the default engine
+ Services.search.defaultEngine = Services.search.getEngineByName("TestSearch1");
+
+ // test that the event emitter is working by setting a new current engine "TestSearch2"
+ let engineName = "TestSearch2";
+ NewTabSearchProvider.search.init();
+
+ // event emitter will fire when current engine is changed
+ let promise = new Promise(resolve => {
+ NewTabSearchProvider.search.once("browser-search-engine-modified", (name, data) => { // jshint ignore:line
+ resolve([name, data.name]);
+ });
+ });
+
+ // set a new current engine
+ Services.search.currentEngine = Services.search.getEngineByName(engineName);
+ let expectedEngineName = Services.search.currentEngine.name;
+
+ // emitter should fire and return the new engine
+ let [eventName, actualEngineName] = yield promise;
+ equal(eventName, "browser-search-engine-modified", `emitter sent the correct event ${eventName}`);
+ equal(expectedEngineName, actualEngineName, `emitter set the correct engine ${expectedEngineName}`);
+ NewTabSearchProvider.search.uninit();
+});
diff --git a/browser/components/newtab/tests/xpcshell/test_NewTabURL.js b/browser/components/newtab/tests/xpcshell/test_NewTabURL.js
new file mode 100644
index 000000000..1505e638c
--- /dev/null
+++ b/browser/components/newtab/tests/xpcshell/test_NewTabURL.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* globals Services, NewTabURL, XPCOMUtils, aboutNewTabService, NewTabPrefsProvider */
+"use strict";
+
+const {utils: Cu} = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/NewTabURL.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
+ "resource:///modules/NewTabPrefsProvider.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
+ "@mozilla.org/browser/aboutnewtab-service;1",
+ "nsIAboutNewTabService");
+
+add_task(function*() {
+ let defaultURL = aboutNewTabService.newTabURL;
+ Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+
+ Assert.equal(NewTabURL.get(), defaultURL, `Default newtab URL should be ${defaultURL}`);
+ let url = "http://example.com/";
+ let notificationPromise = promiseNewtabURLNotification(url);
+ NewTabURL.override(url);
+ yield notificationPromise;
+ Assert.ok(NewTabURL.overridden, "Newtab URL should be overridden");
+ Assert.equal(NewTabURL.get(), url, "Newtab URL should be the custom URL");
+
+ notificationPromise = promiseNewtabURLNotification(defaultURL);
+ NewTabURL.reset();
+ yield notificationPromise;
+ Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
+ Assert.equal(NewTabURL.get(), defaultURL, "Newtab URL should be the default");
+
+ // change newtab page to remote
+ NewTabPrefsProvider.prefs.init();
+ Services.prefs.setBoolPref("browser.newtabpage.remote", true);
+ Assert.equal(NewTabURL.get(), "about:newtab", `Newtab URL should be about:newtab`);
+ Assert.ok(!NewTabURL.overridden, "Newtab URL should not be overridden");
+ NewTabPrefsProvider.prefs.uninit();
+});
+
+function promiseNewtabURLNotification(aNewURL) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) { // jshint ignore:line
+ Services.obs.removeObserver(observer, aTopic);
+ Assert.equal(aData, aNewURL, "Data for newtab-url-changed notification should be new URL.");
+ resolve();
+ }, "newtab-url-changed", false);
+ });
+}
diff --git a/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js b/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
new file mode 100644
index 000000000..22815741b
--- /dev/null
+++ b/browser/components/newtab/tests/xpcshell/test_PlacesProvider.js
@@ -0,0 +1,358 @@
+"use strict";
+
+/* global XPCOMUtils, PlacesUtils, PlacesTestUtils, PlacesProvider, NetUtil */
+/* global do_get_profile, run_next_test, add_task */
+/* global equal, ok */
+
+const {
+ utils: Cu,
+ interfaces: Ci,
+} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesProvider",
+ "resource:///modules/PlacesProvider.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+// ensure a profile exists
+do_get_profile();
+
+function run_test() {
+ PlacesProvider.links.init();
+ run_next_test();
+}
+
+// url prefix for test history population
+const TEST_URL = "https://mozilla.com/";
+// time when the test starts execution
+const TIME_NOW = (new Date()).getTime();
+
+// utility function to compute past timestap
+function timeDaysAgo(numDays) {
+ return TIME_NOW - (numDays * 24 * 60 * 60 * 1000);
+}
+
+// utility function to make a visit for insetion into places db
+function makeVisit(index, daysAgo, isTyped, domain=TEST_URL) {
+ let {
+ TRANSITION_TYPED,
+ TRANSITION_LINK
+ } = PlacesUtils.history;
+
+ return {
+ uri: NetUtil.newURI(`${domain}${index}`),
+ visitDate: timeDaysAgo(daysAgo),
+ transition: (isTyped) ? TRANSITION_TYPED : TRANSITION_LINK,
+ };
+}
+
+/** Test LinkChecker **/
+
+add_task(function test_LinkChecker_securityCheck() {
+
+ let urls = [
+ {url: "javascript:alert('hello')", expected: false}, // jshint ignore:line
+ {url: "data:image/png;base64,XXX", expected: false},
+ {url: "about:newtab", expected: true},
+ {url: "https://example.com", expected: true},
+ {url: "ftp://example.com", expected: true},
+ {url: "file://home/file/image.png", expected: true},
+ {url: "resource:///modules/PlacesProvider.jsm", expected: true},
+ ];
+ for (let {url, expected} of urls) {
+ let observed = PlacesProvider.LinkChecker.checkLoadURI(url);
+ equal(observed, expected, `can load "${url}"?`);
+ }
+});
+
+/** Test Provider **/
+
+add_task(function* test_Links_getLinks() {
+ yield PlacesTestUtils.clearHistory();
+ let provider = PlacesProvider.links;
+
+ let links = yield provider.getLinks();
+ equal(links.length, 0, "empty history yields empty links");
+
+ // add a visit
+ let testURI = NetUtil.newURI("http://mozilla.com");
+ yield PlacesTestUtils.addVisits(testURI);
+
+ links = yield provider.getLinks();
+ equal(links.length, 1, "adding a visit yields a link");
+ equal(links[0].url, testURI.spec, "added visit corresponds to added url");
+});
+
+add_task(function* test_Links_getLinks_Order() {
+ yield PlacesTestUtils.clearHistory();
+ let provider = PlacesProvider.links;
+
+ // all four visits must come from different domains to avoid deduplication
+ let visits = [
+ makeVisit(0, 0, true, "http://bar.com/"), // frecency 200, today
+ makeVisit(1, 0, true, "http://foo.com/"), // frecency 200, today
+ makeVisit(2, 2, true, "http://buz.com/"), // frecency 200, 2 days ago
+ makeVisit(3, 2, false, "http://aaa.com/"), // frecency 10, 2 days ago, transition
+ ];
+
+ let links = yield provider.getLinks();
+ equal(links.length, 0, "empty history yields empty links");
+ yield PlacesTestUtils.addVisits(visits);
+
+ links = yield provider.getLinks();
+ equal(links.length, visits.length, "number of links added is the same as obtain by getLinks");
+ for (let i = 0; i < links.length; i++) {
+ equal(links[i].url, visits[i].uri.spec, "links are obtained in the expected order");
+ }
+});
+
+add_task(function* test_Links_getLinks_Deduplication() {
+ yield PlacesTestUtils.clearHistory();
+ let provider = PlacesProvider.links;
+
+ // all for visits must come from different domains to avoid deduplication
+ let visits = [
+ makeVisit(0, 2, true, "http://bar.com/"), // frecency 200, 2 days ago
+ makeVisit(1, 0, true, "http://bar.com/"), // frecency 200, today
+ makeVisit(2, 0, false, "http://foo.com/"), // frecency 10, today
+ makeVisit(3, 0, true, "http://foo.com/"), // frecency 200, today
+ ];
+
+ let links = yield provider.getLinks();
+ equal(links.length, 0, "empty history yields empty links");
+ yield PlacesTestUtils.addVisits(visits);
+
+ links = yield provider.getLinks();
+ equal(links.length, 2, "only two links must be left after deduplication");
+ equal(links[0].url, visits[1].uri.spec, "earliest link is present");
+ equal(links[1].url, visits[3].uri.spec, "most fresent link is present");
+});
+
+add_task(function* test_Links_onLinkChanged() {
+ let provider = PlacesProvider.links;
+
+ let url = "https://example.com/onFrecencyChanged1";
+ let linkChangedMsgCount = 0;
+
+ let linkChangedPromise = new Promise(resolve => {
+ let handler = (_, link) => { // jshint ignore:line
+ /* There are 3 linkChanged events:
+ * 1. visit insertion (-1 frecency by default)
+ * 2. frecency score update (after transition type calculation etc)
+ * 3. title change
+ */
+ if (link.url === url) {
+ equal(link.url, url, `expected url on linkChanged event`);
+ linkChangedMsgCount += 1;
+ if (linkChangedMsgCount === 3) {
+ ok(true, `all linkChanged events captured`);
+ provider.off("linkChanged", this);
+ resolve();
+ }
+ }
+ };
+ provider.on("linkChanged", handler);
+ });
+
+ // add a visit
+ let testURI = NetUtil.newURI(url);
+ yield PlacesTestUtils.addVisits(testURI);
+ yield linkChangedPromise;
+
+ yield PlacesTestUtils.clearHistory();
+});
+
+add_task(function* test_Links_onClearHistory() {
+ let provider = PlacesProvider.links;
+
+ let clearHistoryPromise = new Promise(resolve => {
+ let handler = () => {
+ ok(true, `clearHistory event captured`);
+ provider.off("clearHistory", handler);
+ resolve();
+ };
+ provider.on("clearHistory", handler);
+ });
+
+ // add visits
+ for (let i = 0; i <= 10; i++) {
+ let url = `https://example.com/onClearHistory${i}`;
+ let testURI = NetUtil.newURI(url);
+ yield PlacesTestUtils.addVisits(testURI);
+ }
+ yield PlacesTestUtils.clearHistory();
+ yield clearHistoryPromise;
+});
+
+add_task(function* test_Links_onDeleteURI() {
+ let provider = PlacesProvider.links;
+
+ let testURL = "https://example.com/toDelete";
+
+ let deleteURIPromise = new Promise(resolve => {
+ let handler = (_, {url}) => { // jshint ignore:line
+ equal(testURL, url, "deleted url and expected url are the same");
+ provider.off("deleteURI", handler);
+ resolve();
+ };
+
+ provider.on("deleteURI", handler);
+ });
+
+ let testURI = NetUtil.newURI(testURL);
+ yield PlacesTestUtils.addVisits(testURI);
+ yield PlacesUtils.history.remove(testURL);
+ yield deleteURIPromise;
+});
+
+add_task(function* test_Links_onManyLinksChanged() {
+ let provider = PlacesProvider.links;
+
+ let promise = new Promise(resolve => {
+ let handler = () => {
+ ok(true);
+ provider.off("manyLinksChanged", handler);
+ resolve();
+ };
+
+ provider.on("manyLinksChanged", handler);
+ });
+
+ let testURL = "https://example.com/toDelete";
+ let testURI = NetUtil.newURI(testURL);
+ yield PlacesTestUtils.addVisits(testURI);
+
+ // trigger DecayFrecency
+ PlacesUtils.history.QueryInterface(Ci.nsIObserver).
+ observe(null, "idle-daily", "");
+
+ yield promise;
+});
+
+add_task(function* test_Links_execute_query() {
+ yield PlacesTestUtils.clearHistory();
+ let provider = PlacesProvider.links;
+
+ let visits = [
+ makeVisit(0, 0, true), // frecency 200, today
+ makeVisit(1, 0, true), // frecency 200, today
+ makeVisit(2, 2, true), // frecency 200, 2 days ago
+ makeVisit(3, 2, false), // frecency 10, 2 days ago, transition
+ ];
+
+ yield PlacesTestUtils.addVisits(visits);
+
+ function testItemValue(results, index, value) {
+ equal(results[index][0], `${TEST_URL}${value}`, "raw url");
+ equal(results[index][1], `test visit for ${TEST_URL}${value}`, "raw title");
+ }
+
+ function testItemObject(results, index, columnValues) {
+ Object.keys(columnValues).forEach(name => {
+ equal(results[index][name], columnValues[name], "object name " + name);
+ });
+ }
+
+ // select all 4 records
+ let results = yield provider.executePlacesQuery("select url, title from moz_places");
+ equal(results.length, 4, "expect 4 items");
+ // check for insert order sequence
+ for (let i = 0; i < results.length; i++) {
+ testItemValue(results, i, i);
+ }
+
+ // test parameter passing
+ results = yield provider.executePlacesQuery(
+ "select url, title from moz_places limit :limit",
+ {params: {limit: 2}}
+ );
+ equal(results.length, 2, "expect 2 items");
+ for (let i = 0; i < results.length; i++) {
+ testItemValue(results, i, i);
+ }
+
+ // test extracting items by name
+ results = yield provider.executePlacesQuery(
+ "select url, title from moz_places limit :limit",
+ {columns: ["url", "title"], params: {limit: 4}}
+ );
+ equal(results.length, 4, "expect 4 items");
+ for (let i = 0; i < results.length; i++) {
+ testItemObject(results, i, {
+ "url": `${TEST_URL}${i}`,
+ "title": `test visit for ${TEST_URL}${i}`,
+ });
+ }
+
+ // test ordering
+ results = yield provider.executePlacesQuery(
+ "select url, title, last_visit_date, frecency from moz_places " +
+ "order by frecency DESC, last_visit_date DESC, url DESC limit :limit",
+ {columns: ["url", "title", "last_visit_date", "frecency"], params: {limit: 4}}
+ );
+ equal(results.length, 4, "expect 4 items");
+ testItemObject(results, 0, {url: `${TEST_URL}1`});
+ testItemObject(results, 1, {url: `${TEST_URL}0`});
+ testItemObject(results, 2, {url: `${TEST_URL}2`});
+ testItemObject(results, 3, {url: `${TEST_URL}3`});
+
+ // test callback passing
+ results = [];
+ function handleRow(aRow) {
+ results.push({
+ url: aRow.getResultByName("url"),
+ title: aRow.getResultByName("title"),
+ last_visit_date: aRow.getResultByName("last_visit_date"),
+ frecency: aRow.getResultByName("frecency")
+ });
+ }
+ yield provider.executePlacesQuery(
+ "select url, title, last_visit_date, frecency from moz_places " +
+ "order by frecency DESC, last_visit_date DESC, url DESC",
+ {callback: handleRow}
+ );
+ equal(results.length, 4, "expect 4 items");
+ testItemObject(results, 0, {url: `${TEST_URL}1`});
+ testItemObject(results, 1, {url: `${TEST_URL}0`});
+ testItemObject(results, 2, {url: `${TEST_URL}2`});
+ testItemObject(results, 3, {url: `${TEST_URL}3`});
+
+ // negative test cases
+ // bad sql
+ try {
+ yield provider.executePlacesQuery("select from moz");
+ do_throw("bad sql should've thrown");
+ }
+ catch (e) {
+ do_check_true("expected failure - bad sql");
+ }
+ // missing bindings
+ try {
+ yield provider.executePlacesQuery("select * from moz_places limit :limit");
+ do_throw("bad sql should've thrown");
+ }
+ catch (e) {
+ do_check_true("expected failure - missing bidning");
+ }
+ // non-existent column name
+ try {
+ yield provider.executePlacesQuery("select * from moz_places limit :limit",
+ {columns: ["no-such-column"], params: {limit: 4}});
+ do_throw("bad sql should've thrown");
+ }
+ catch (e) {
+ do_check_true("expected failure - wrong column name");
+ }
+
+ // cleanup
+ yield PlacesTestUtils.clearHistory();
+});
diff --git a/browser/components/newtab/tests/xpcshell/xpcshell.ini b/browser/components/newtab/tests/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..c249ee3e2
--- /dev/null
+++ b/browser/components/newtab/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head =
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_AboutNewTabService.js]
+[test_NewTabPrefsProvider.js]
+[test_NewTabSearchProvider.js]
+[test_NewTabURL.js]
+[test_PlacesProvider.js]
diff --git a/browser/components/nsBrowserContentHandler.js b/browser/components/nsBrowserContentHandler.js
new file mode 100644
index 000000000..e8fe0fe93
--- /dev/null
+++ b/browser/components/nsBrowserContentHandler.js
@@ -0,0 +1,805 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 no-undef:2 */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LaterRun",
+ "resource:///modules/LaterRun.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
+ "resource:///modules/ShellService.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
+ "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
+
+const nsISupports = Components.interfaces.nsISupports;
+
+const nsIBrowserDOMWindow = Components.interfaces.nsIBrowserDOMWindow;
+const nsIBrowserHandler = Components.interfaces.nsIBrowserHandler;
+const nsIBrowserHistory = Components.interfaces.nsIBrowserHistory;
+const nsIChannel = Components.interfaces.nsIChannel;
+const nsICommandLine = Components.interfaces.nsICommandLine;
+const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
+const nsIContentHandler = Components.interfaces.nsIContentHandler;
+const nsIDocShellTreeItem = Components.interfaces.nsIDocShellTreeItem;
+const nsIDOMChromeWindow = Components.interfaces.nsIDOMChromeWindow;
+const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
+const nsIFileURL = Components.interfaces.nsIFileURL;
+const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor;
+const nsINetUtil = Components.interfaces.nsINetUtil;
+const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
+const nsISupportsString = Components.interfaces.nsISupportsString;
+const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+const nsIWebNavigationInfo = Components.interfaces.nsIWebNavigationInfo;
+const nsICommandLineValidator = Components.interfaces.nsICommandLineValidator;
+
+const NS_BINDING_ABORTED = Components.results.NS_BINDING_ABORTED;
+const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
+const NS_ERROR_ABORT = Components.results.NS_ERROR_ABORT;
+
+function shouldLoadURI(aURI) {
+ if (aURI && !aURI.schemeIs("chrome"))
+ return true;
+
+ dump("*** Preventing external load of chrome: URI into browser window\n");
+ dump(" Use --chrome <uri> instead\n");
+ return false;
+}
+
+function resolveURIInternal(aCmdLine, aArgument) {
+ var uri = aCmdLine.resolveURI(aArgument);
+ var uriFixup = Services.uriFixup;
+
+ if (!(uri instanceof nsIFileURL)) {
+ return uriFixup.createFixupURI(aArgument,
+ uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS);
+ }
+
+ try {
+ if (uri.file.exists())
+ return uri;
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ // We have interpreted the argument as a relative file URI, but the file
+ // doesn't exist. Try URI fixup heuristics: see bug 290782.
+
+ try {
+ uri = uriFixup.createFixupURI(aArgument, 0);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ return uri;
+}
+
+var gFirstWindow = false;
+
+const OVERRIDE_NONE = 0;
+const OVERRIDE_NEW_PROFILE = 1;
+const OVERRIDE_NEW_MSTONE = 2;
+const OVERRIDE_NEW_BUILD_ID = 3;
+/**
+ * Determines whether a home page override is needed.
+ * Returns:
+ * OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
+ * OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
+ * Gecko milestone (i.e. right after an upgrade).
+ * OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
+ * same Gecko milestone (i.e. after a nightly upgrade).
+ * OVERRIDE_NONE otherwise.
+ */
+function needHomepageOverride(prefb) {
+ var savedmstone = null;
+ try {
+ savedmstone = prefb.getCharPref("browser.startup.homepage_override.mstone");
+ } catch (e) {}
+
+ if (savedmstone == "ignore")
+ return OVERRIDE_NONE;
+
+ var mstone = Services.appinfo.platformVersion;
+
+ var savedBuildID = null;
+ try {
+ savedBuildID = prefb.getCharPref("browser.startup.homepage_override.buildID");
+ } catch (e) {}
+
+ var buildID = Services.appinfo.platformBuildID;
+
+ if (mstone != savedmstone) {
+ // Bug 462254. Previous releases had a default pref to suppress the EULA
+ // agreement if the platform's installer had already shown one. Now with
+ // about:rights we've removed the EULA stuff and default pref, but we need
+ // a way to make existing profiles retain the default that we removed.
+ if (savedmstone)
+ prefb.setBoolPref("browser.rights.3.shown", true);
+
+ prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
+ prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+ return (savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE);
+ }
+
+ if (buildID != savedBuildID) {
+ prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+ return OVERRIDE_NEW_BUILD_ID;
+ }
+
+ return OVERRIDE_NONE;
+}
+
+/**
+ * Gets the override page for the first run after the application has been
+ * updated.
+ * @param defaultOverridePage
+ * The default override page.
+ * @return The override page.
+ */
+function getPostUpdateOverridePage(defaultOverridePage) {
+ var um = Components.classes["@mozilla.org/updates/update-manager;1"]
+ .getService(Components.interfaces.nsIUpdateManager);
+ try {
+ // If the updates.xml file is deleted then getUpdateAt will throw.
+ var update = um.getUpdateAt(0)
+ .QueryInterface(Components.interfaces.nsIPropertyBag);
+ } catch (e) {
+ // This should never happen.
+ Components.utils.reportError("Unable to find update: " + e);
+ return defaultOverridePage;
+ }
+
+ let actions = update.getProperty("actions");
+ // When the update doesn't specify actions fallback to the original behavior
+ // of displaying the default override page.
+ if (!actions)
+ return defaultOverridePage;
+
+ // The existence of silent or the non-existence of showURL in the actions both
+ // mean that an override page should not be displayed.
+ if (actions.indexOf("silent") != -1 || actions.indexOf("showURL") == -1)
+ return "";
+
+ return update.getProperty("openURL") || defaultOverridePage;
+}
+
+// Flag used to indicate that the arguments to openWindow can be passed directly.
+const NO_EXTERNAL_URIS = 1;
+
+function openWindow(parent, url, target, features, args, noExternalArgs) {
+ if (noExternalArgs == NO_EXTERNAL_URIS) {
+ // Just pass in the defaultArgs directly
+ var argstring;
+ if (args) {
+ argstring = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ argstring.data = args;
+ }
+
+ return Services.ww.openWindow(parent, url, target, features, argstring);
+ }
+
+ // Pass an array to avoid the browser "|"-splitting behavior.
+ var argArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+
+ // add args to the arguments array
+ var stringArgs = null;
+ if (args instanceof Array) // array
+ stringArgs = args;
+ else if (args) // string
+ stringArgs = [args];
+
+ if (stringArgs) {
+ // put the URIs into argArray
+ var uriArray = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ stringArgs.forEach(function (uri) {
+ var sstring = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ sstring.data = uri;
+ uriArray.appendElement(sstring, /* weak = */ false);
+ });
+ argArray.appendElement(uriArray, /* weak =*/ false);
+ } else {
+ argArray.appendElement(null, /* weak =*/ false);
+ }
+
+ // Pass these as null to ensure that we always trigger the "single URL"
+ // behavior in browser.js's gBrowserInit.onLoad (which handles the window
+ // arguments)
+ argArray.appendElement(null, /* weak =*/ false); // charset
+ argArray.appendElement(null, /* weak =*/ false); // referer
+ argArray.appendElement(null, /* weak =*/ false); // postData
+ argArray.appendElement(null, /* weak =*/ false); // allowThirdPartyFixup
+
+ return Services.ww.openWindow(parent, url, target, features, argArray);
+}
+
+function openPreferences() {
+ var args = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+
+ var wuri = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ wuri.data = "about:preferences";
+
+ args.appendElement(wuri, /* weak = */ false);
+
+ Services.ww.openWindow(null, gBrowserContentHandler.chromeURL,
+ "_blank",
+ "chrome,dialog=no,all",
+ args);
+}
+
+function logSystemBasedSearch(engine) {
+ var countId = (engine.identifier || ("other-" + engine.name)) + ".system";
+ var count = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+ count.add(countId);
+}
+
+function doSearch(searchTerm, cmdLine) {
+ var engine = Services.search.defaultEngine;
+ logSystemBasedSearch(engine);
+
+ var submission = engine.getSubmission(searchTerm, null, "system");
+
+ // fill our nsIMutableArray with uri-as-wstring, null, null, postData
+ var args = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+
+ var wuri = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ wuri.data = submission.uri.spec;
+
+ args.appendElement(wuri, /* weak =*/ false);
+ args.appendElement(null, /* weak =*/ false);
+ args.appendElement(null, /* weak =*/ false);
+ args.appendElement(submission.postData, /* weak =*/ false);
+
+ // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
+ // preferences, but need nsIBrowserDOMWindow extensions
+
+ return Services.ww.openWindow(null, gBrowserContentHandler.chromeURL,
+ "_blank",
+ "chrome,dialog=no,all" +
+ gBrowserContentHandler.getFeatures(cmdLine),
+ args);
+}
+
+function nsBrowserContentHandler() {
+}
+nsBrowserContentHandler.prototype = {
+ classID: Components.ID("{5d0ce354-df01-421a-83fb-7ead0990c24e}"),
+
+ _xpcom_factory: {
+ createInstance: function bch_factory_ci(outer, iid) {
+ if (outer)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return gBrowserContentHandler.QueryInterface(iid);
+ }
+ },
+
+ /* helper functions */
+
+ mChromeURL : null,
+
+ get chromeURL() {
+ if (this.mChromeURL) {
+ return this.mChromeURL;
+ }
+
+ this.mChromeURL = Services.prefs.getCharPref("browser.chromeURL");
+
+ return this.mChromeURL;
+ },
+
+ /* nsISupports */
+ QueryInterface : XPCOMUtils.generateQI([nsICommandLineHandler,
+ nsIBrowserHandler,
+ nsIContentHandler,
+ nsICommandLineValidator]),
+
+ /* nsICommandLineHandler */
+ handle : function bch_handle(cmdLine) {
+ if (cmdLine.handleFlag("browser", false)) {
+ // Passing defaultArgs, so use NO_EXTERNAL_URIS
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,all" + this.getFeatures(cmdLine),
+ this.defaultArgs, NO_EXTERNAL_URIS);
+ cmdLine.preventDefault = true;
+ }
+
+ // In the past, when an instance was not already running, the -remote
+ // option returned an error code. Any script or application invoking the
+ // -remote option is expected to be handling this case, otherwise they
+ // wouldn't be doing anything when there is no Firefox already running.
+ // Making the -remote option always return an error code makes those
+ // scripts or applications handle the situation as if Firefox was not
+ // already running.
+ if (cmdLine.handleFlag("remote", true)) {
+ throw NS_ERROR_ABORT;
+ }
+
+ var uriparam;
+ try {
+ while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
+ let uri = resolveURIInternal(cmdLine, uriparam);
+ if (!shouldLoadURI(uri))
+ continue;
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,all" + this.getFeatures(cmdLine),
+ uri.spec);
+ cmdLine.preventDefault = true;
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ try {
+ while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
+ let uri = resolveURIInternal(cmdLine, uriparam);
+ handURIToExistingBrowser(uri, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine);
+ cmdLine.preventDefault = true;
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
+ if (chromeParam) {
+
+ // Handle old preference dialog URLs.
+ if (chromeParam == "chrome://browser/content/pref/pref.xul" ||
+ chromeParam == "chrome://browser/content/preferences/preferences.xul") {
+ openPreferences();
+ cmdLine.preventDefault = true;
+ } else try {
+ let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
+ let isLocal = uri => {
+ let localSchemes = new Set(["chrome", "file", "resource"]);
+ if (uri instanceof Components.interfaces.nsINestedURI) {
+ uri = uri.QueryInterface(Components.interfaces.nsINestedURI).innerMostURI;
+ }
+ return localSchemes.has(uri.scheme);
+ };
+ if (isLocal(resolvedURI)) {
+ // If the URI is local, we are sure it won't wrongly inherit chrome privs
+ var features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
+ openWindow(null, resolvedURI.spec, "_blank", features);
+ cmdLine.preventDefault = true;
+ } else {
+ dump("*** Preventing load of web URI as chrome\n");
+ dump(" If you're trying to load a webpage, do not pass --chrome.\n");
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+ if (cmdLine.handleFlag("preferences", false)) {
+ openPreferences();
+ cmdLine.preventDefault = true;
+ }
+ if (cmdLine.handleFlag("silent", false))
+ cmdLine.preventDefault = true;
+
+ try {
+ var privateWindowParam = cmdLine.handleFlagWithParam("private-window", false);
+ if (privateWindowParam) {
+ let resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
+ handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, true);
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ if (e.result != Components.results.NS_ERROR_INVALID_ARG) {
+ throw e;
+ }
+ // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
+ if (cmdLine.handleFlag("private-window", false)) {
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,private,all" + this.getFeatures(cmdLine),
+ "about:privatebrowsing");
+ cmdLine.preventDefault = true;
+ }
+ }
+
+ var searchParam = cmdLine.handleFlagWithParam("search", false);
+ if (searchParam) {
+ doSearch(searchParam, cmdLine);
+ cmdLine.preventDefault = true;
+ }
+
+ // The global PB Service consumes this flag, so only eat it in per-window
+ // PB builds.
+ if (cmdLine.handleFlag("private", false)) {
+ PrivateBrowsingUtils.enterTemporaryAutoStartMode();
+ }
+
+ var fileParam = cmdLine.handleFlagWithParam("file", false);
+ if (fileParam) {
+ var file = cmdLine.resolveFile(fileParam);
+ var fileURI = Services.io.newFileURI(file);
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,all" + this.getFeatures(cmdLine),
+ fileURI.spec);
+ cmdLine.preventDefault = true;
+ }
+
+ if (AppConstants.platform == "win") {
+ // Handle "? searchterm" for Windows Vista start menu integration
+ for (var i = cmdLine.length - 1; i >= 0; --i) {
+ var param = cmdLine.getArgument(i);
+ if (param.match(/^\? /)) {
+ cmdLine.removeArguments(i, i);
+ cmdLine.preventDefault = true;
+
+ searchParam = param.substr(2);
+ doSearch(searchParam, cmdLine);
+ }
+ }
+ }
+ },
+
+ get helpInfo() {
+ let info =
+ " --browser Open a browser window.\n" +
+ " --new-window <url> Open <url> in a new window.\n" +
+ " --new-tab <url> Open <url> in a new tab.\n" +
+ " --private-window <url> Open <url> in a new private window.\n";
+ if (AppConstants.platform == "win") {
+ info += " --preferences Open Options dialog.\n";
+ } else {
+ info += " --preferences Open Preferences dialog.\n";
+ }
+ info += " --search <term> Search <term> with your default search engine.\n";
+ return info;
+ },
+
+ /* nsIBrowserHandler */
+
+ get defaultArgs() {
+ var prefb = Services.prefs;
+
+ if (!gFirstWindow) {
+ gFirstWindow = true;
+ if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
+ return "about:privatebrowsing";
+ }
+ }
+
+ var override;
+ var overridePage = "";
+ var additionalPage = "";
+ var willRestoreSession = false;
+ try {
+ // Read the old value of homepage_override.mstone before
+ // needHomepageOverride updates it, so that we can later add it to the
+ // URL if we do end up showing an overridePage. This makes it possible
+ // to have the overridePage's content vary depending on the version we're
+ // upgrading from.
+ let old_mstone = "unknown";
+ try {
+ old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
+ } catch (ex) {}
+ override = needHomepageOverride(prefb);
+ if (override != OVERRIDE_NONE) {
+ switch (override) {
+ case OVERRIDE_NEW_PROFILE:
+ // New profile.
+ overridePage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url");
+ additionalPage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url.additional");
+ // Turn on 'later run' pages for new profiles.
+ LaterRun.enabled = true;
+ break;
+ case OVERRIDE_NEW_MSTONE:
+ // Check whether we will restore a session. If we will, we assume
+ // that this is an "update" session. This does not take crashes
+ // into account because that requires waiting for the session file
+ // to be read. If a crash occurs after updating, before restarting,
+ // we may open the startPage in addition to restoring the session.
+ var ss = Components.classes["@mozilla.org/browser/sessionstartup;1"]
+ .getService(Components.interfaces.nsISessionStartup);
+ willRestoreSession = ss.isAutomaticRestoreEnabled();
+
+ overridePage = Services.urlFormatter.formatURLPref("startup.homepage_override_url");
+ if (prefb.prefHasUserValue("app.update.postupdate"))
+ overridePage = getPostUpdateOverridePage(overridePage);
+
+ overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
+ break;
+ }
+ }
+ } catch (ex) {}
+
+ // formatURLPref might return "about:blank" if getting the pref fails
+ if (overridePage == "about:blank")
+ overridePage = "";
+
+ if (!additionalPage) {
+ additionalPage = LaterRun.getURL() || "";
+ }
+
+ if (additionalPage && additionalPage != "about:blank") {
+ if (overridePage) {
+ overridePage += "|" + additionalPage;
+ } else {
+ overridePage = additionalPage;
+ }
+ }
+
+ var startPage = "";
+ try {
+ var choice = prefb.getIntPref("browser.startup.page");
+ if (choice == 1 || choice == 3)
+ startPage = this.startPage;
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ if (startPage == "about:blank")
+ startPage = "";
+
+ let skipStartPage = override == OVERRIDE_NEW_PROFILE &&
+ prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
+ // Only show the startPage if we're not restoring an update session and are
+ // not set to skip the start page on this profile
+ if (overridePage && startPage && !willRestoreSession && !skipStartPage)
+ return overridePage + "|" + startPage;
+
+ return overridePage || startPage || "about:blank";
+ },
+
+ get startPage() {
+ var uri = Services.prefs.getComplexValue("browser.startup.homepage",
+ nsIPrefLocalizedString).data;
+ if (!uri) {
+ Services.prefs.clearUserPref("browser.startup.homepage");
+ uri = Services.prefs.getComplexValue("browser.startup.homepage",
+ nsIPrefLocalizedString).data;
+ }
+ return uri;
+ },
+
+ mFeatures : null,
+
+ getFeatures : function bch_features(cmdLine) {
+ if (this.mFeatures === null) {
+ this.mFeatures = "";
+
+ try {
+ var width = cmdLine.handleFlagWithParam("width", false);
+ var height = cmdLine.handleFlagWithParam("height", false);
+
+ if (width)
+ this.mFeatures += ",width=" + width;
+ if (height)
+ this.mFeatures += ",height=" + height;
+ }
+ catch (e) {
+ }
+
+ // The global PB Service consumes this flag, so only eat it in per-window
+ // PB builds.
+ if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
+ this.mFeatures = ",private";
+ }
+ }
+
+ return this.mFeatures;
+ },
+
+ /* nsIContentHandler */
+
+ handleContent : function bch_handleContent(contentType, context, request) {
+ try {
+ var webNavInfo = Components.classes["@mozilla.org/webnavigation-info;1"]
+ .getService(nsIWebNavigationInfo);
+ if (!webNavInfo.isTypeSupported(contentType, null)) {
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+ } catch (e) {
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ request.QueryInterface(nsIChannel);
+ handURIToExistingBrowser(request.URI,
+ nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, null);
+ request.cancel(NS_BINDING_ABORTED);
+ },
+
+ /* nsICommandLineValidator */
+ validate : function bch_validate(cmdLine) {
+ // Other handlers may use osint so only handle the osint flag if the url
+ // flag is also present and the command line is valid.
+ var osintFlagIdx = cmdLine.findFlag("osint", false);
+ var urlFlagIdx = cmdLine.findFlag("url", false);
+ if (urlFlagIdx > -1 && (osintFlagIdx > -1 ||
+ cmdLine.state == nsICommandLine.STATE_REMOTE_EXPLICIT)) {
+ var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
+ if (cmdLine.length != urlFlagIdx + 2 || /firefoxurl:/.test(urlParam))
+ throw NS_ERROR_ABORT;
+ var isDefault = false;
+ try {
+ var url = Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "win10-default-browser";
+ if (urlParam == url) {
+ isDefault = ShellService.isDefaultBrowser(false, false);
+ }
+ } catch (ex) {}
+ if (isDefault) {
+ // Firefox is already the default HTTP handler.
+ // We don't have to show the instruction page.
+ throw NS_ERROR_ABORT;
+ }
+ cmdLine.handleFlag("osint", false)
+ }
+ },
+};
+var gBrowserContentHandler = new nsBrowserContentHandler();
+
+function handURIToExistingBrowser(uri, location, cmdLine, forcePrivate)
+{
+ if (!shouldLoadURI(uri))
+ return;
+
+ // Unless using a private window is forced, open external links in private
+ // windows only if we're in perma-private mode.
+ var allowPrivate = forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
+ var navWin = RecentWindow.getMostRecentBrowserWindow({private: allowPrivate});
+ if (!navWin) {
+ // if we couldn't load it in an existing window, open a new one
+ var features = "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine);
+ if (forcePrivate) {
+ features += ",private";
+ }
+ openWindow(null, gBrowserContentHandler.chromeURL, "_blank", features, uri.spec);
+ return;
+ }
+
+ var navNav = navWin.QueryInterface(nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation);
+ var rootItem = navNav.QueryInterface(nsIDocShellTreeItem).rootTreeItem;
+ var rootWin = rootItem.QueryInterface(nsIInterfaceRequestor)
+ .getInterface(nsIDOMWindow);
+ var bwin = rootWin.QueryInterface(nsIDOMChromeWindow).browserDOMWindow;
+ bwin.openURI(uri, null, location,
+ nsIBrowserDOMWindow.OPEN_EXTERNAL);
+}
+
+function nsDefaultCommandLineHandler() {
+}
+
+nsDefaultCommandLineHandler.prototype = {
+ classID: Components.ID("{47cd0651-b1be-4a0f-b5c4-10e5a573ef71}"),
+
+ /* nsISupports */
+ QueryInterface : function dch_QI(iid) {
+ if (!iid.equals(nsISupports) &&
+ !iid.equals(nsICommandLineHandler))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+
+ _haveProfile: false,
+
+ /* nsICommandLineHandler */
+ handle : function dch_handle(cmdLine) {
+ // The -url flag is inserted by the operating system when the default
+ // application handler is used. We check for default browser to remove
+ // instances where users explicitly decide to "open with" the browser.
+ // Note that users who launch firefox manually with the -url flag will
+ // get erroneously counted.
+ try {
+ if (cmdLine.findFlag("url", false) &&
+ ShellService.isDefaultBrowser(false, false)) {
+ Services.telemetry.getHistogramById("FX_STARTUP_EXTERNAL_CONTENT_HANDLER").add();
+ }
+ } catch (e) {}
+
+ var urilist = [];
+
+ if (AppConstants.platform == "win") {
+ // If we don't have a profile selected yet (e.g. the Profile Manager is
+ // displayed) we will crash if we open an url and then select a profile. To
+ // prevent this handle all url command line flags and set the command line's
+ // preventDefault to true to prevent the display of the ui. The initial
+ // command line will be retained when nsAppRunner calls LaunchChild though
+ // urls launched after the initial launch will be lost.
+ if (!this._haveProfile) {
+ try {
+ // This will throw when a profile has not been selected.
+ Services.dirsvc.get("ProfD", Components.interfaces.nsILocalFile);
+ this._haveProfile = true;
+ }
+ catch (e) {
+ while ((ar = cmdLine.handleFlagWithParam("url", false)));
+ cmdLine.preventDefault = true;
+ }
+ }
+ }
+
+ try {
+ var ar;
+ while ((ar = cmdLine.handleFlagWithParam("url", false))) {
+ var uri = resolveURIInternal(cmdLine, ar);
+ urilist.push(uri);
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ for (let i = 0; i < cmdLine.length; ++i) {
+ var curarg = cmdLine.getArgument(i);
+ if (curarg.match(/^-/)) {
+ Components.utils.reportError("Warning: unrecognized command line flag " + curarg + "\n");
+ // To emulate the pre-nsICommandLine behavior, we ignore
+ // the argument after an unrecognized flag.
+ ++i;
+ } else {
+ try {
+ urilist.push(resolveURIInternal(cmdLine, curarg));
+ }
+ catch (e) {
+ Components.utils.reportError("Error opening URI '" + curarg + "' from the command line: " + e + "\n");
+ }
+ }
+ }
+
+ if (urilist.length) {
+ if (cmdLine.state != nsICommandLine.STATE_INITIAL_LAUNCH &&
+ urilist.length == 1) {
+ // Try to find an existing window and load our URI into the
+ // current tab, new tab, or new window as prefs determine.
+ try {
+ handURIToExistingBrowser(urilist[0], nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, cmdLine);
+ return;
+ }
+ catch (e) {
+ }
+ }
+
+ var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
+ if (URLlist.length) {
+ openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
+ "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
+ URLlist);
+ }
+
+ }
+ else if (!cmdLine.preventDefault) {
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
+ cmdLine.state != nsICommandLine.STATE_INITIAL_LAUNCH &&
+ WindowsUIUtils.inTabletMode) {
+ // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (win) {
+ win.focus();
+ return;
+ }
+ }
+ // Passing defaultArgs, so use NO_EXTERNAL_URIS
+ openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
+ "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
+ gBrowserContentHandler.defaultArgs, NO_EXTERNAL_URIS);
+ }
+ },
+
+ helpInfo : "",
+};
+
+var components = [nsBrowserContentHandler, nsDefaultCommandLineHandler];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js
new file mode 100644
index 000000000..d69a11f6b
--- /dev/null
+++ b/browser/components/nsBrowserGlue.js
@@ -0,0 +1,2867 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/AsyncPrefs.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils", "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
+XPCOMUtils.defineLazyServiceGetter(this, "AlertsService", "@mozilla.org/alerts-service;1", "nsIAlertsService");
+
+// lazy module getters
+[
+ ["AboutHome", "resource:///modules/AboutHome.jsm"],
+ ["AboutNewTab", "resource:///modules/AboutNewTab.jsm"],
+ ["AddonManager", "resource://gre/modules/AddonManager.jsm"],
+ ["AddonWatcher", "resource://gre/modules/AddonWatcher.jsm"],
+ ["AsyncShutdown", "resource://gre/modules/AsyncShutdown.jsm"],
+ ["AutoCompletePopup", "resource://gre/modules/AutoCompletePopup.jsm"],
+ ["BookmarkHTMLUtils", "resource://gre/modules/BookmarkHTMLUtils.jsm"],
+ ["BookmarkJSONUtils", "resource://gre/modules/BookmarkJSONUtils.jsm"],
+ ["BrowserUITelemetry", "resource:///modules/BrowserUITelemetry.jsm"],
+ ["BrowserUsageTelemetry", "resource:///modules/BrowserUsageTelemetry.jsm"],
+ ["ContentClick", "resource:///modules/ContentClick.jsm"],
+ ["ContentPrefServiceParent", "resource://gre/modules/ContentPrefServiceParent.jsm"],
+ ["ContentSearch", "resource:///modules/ContentSearch.jsm"],
+ ["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
+ ["DirectoryLinksProvider", "resource:///modules/DirectoryLinksProvider.jsm"],
+ ["Feeds", "resource:///modules/Feeds.jsm"],
+ ["FileUtils", "resource://gre/modules/FileUtils.jsm"],
+ ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
+ ["Integration", "resource://gre/modules/Integration.jsm"],
+ ["LightweightThemeManager", "resource://gre/modules/LightweightThemeManager.jsm"],
+ ["LoginHelper", "resource://gre/modules/LoginHelper.jsm"],
+ ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
+ ["NetUtil", "resource://gre/modules/NetUtil.jsm"],
+ ["NewTabMessages", "resource:///modules/NewTabMessages.jsm"],
+ ["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"],
+ ["OS", "resource://gre/modules/osfile.jsm"],
+ ["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
+ ["PdfJs", "resource://pdf.js/PdfJs.jsm"],
+ ["PermissionUI", "resource:///modules/PermissionUI.jsm"],
+ ["PlacesBackups", "resource://gre/modules/PlacesBackups.jsm"],
+ ["PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"],
+ ["PluralForm", "resource://gre/modules/PluralForm.jsm"],
+ ["PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"],
+ ["ProcessHangMonitor", "resource:///modules/ProcessHangMonitor.jsm"],
+ ["ReaderParent", "resource:///modules/ReaderParent.jsm"],
+ ["RecentWindow", "resource:///modules/RecentWindow.jsm"],
+ ["RemotePrompt", "resource:///modules/RemotePrompt.jsm"],
+ ["SelfSupportBackend", "resource:///modules/SelfSupportBackend.jsm"],
+ ["SessionStore", "resource:///modules/sessionstore/SessionStore.jsm"],
+ ["ShellService", "resource:///modules/ShellService.jsm"],
+ ["SimpleServiceDiscovery", "resource://gre/modules/SimpleServiceDiscovery.jsm"],
+ ["TabCrashHandler", "resource:///modules/ContentCrashHandlers.jsm"],
+ ["Task", "resource://gre/modules/Task.jsm"],
+ ["UITour", "resource:///modules/UITour.jsm"],
+ ["URLBarZoom", "resource:///modules/URLBarZoom.jsm"],
+ ["WebChannel", "resource://gre/modules/WebChannel.jsm"],
+ ["WindowsRegistry", "resource://gre/modules/WindowsRegistry.jsm"],
+ ["webrtcUI", "resource:///modules/webrtcUI.jsm"],
+].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
+
+if (AppConstants.MOZ_CRASHREPORTER) {
+ XPCOMUtils.defineLazyModuleGetter(this, "PluginCrashReporter",
+ "resource:///modules/ContentCrashHandlers.jsm");
+ XPCOMUtils.defineLazyModuleGetter(this, "UnsubmittedCrashHandler",
+ "resource:///modules/ContentCrashHandlers.jsm");
+ XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
+ "resource://gre/modules/CrashSubmit.jsm");
+}
+
+XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
+ return Services.strings.createBundle('chrome://branding/locale/brand.properties');
+});
+
+XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
+ return Services.strings.createBundle('chrome://browser/locale/browser.properties');
+});
+
+// Seconds of idle before trying to create a bookmarks backup.
+const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
+// Minimum interval between backups. We try to not create more than one backup
+// per interval.
+const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
+// Maximum interval between backups. If the last backup is older than these
+// days we will try to create a new one more aggressively.
+const BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS = 3;
+
+// Factory object
+const BrowserGlueServiceFactory = {
+ _instance: null,
+ createInstance: function BGSF_createInstance(outer, iid) {
+ if (outer != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return this._instance == null ?
+ this._instance = new BrowserGlue() : this._instance;
+ }
+};
+
+// Constructor
+
+function BrowserGlue() {
+ XPCOMUtils.defineLazyServiceGetter(this, "_idleService",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+ XPCOMUtils.defineLazyGetter(this, "_distributionCustomizer", function() {
+ Cu.import("resource:///modules/distribution.js");
+ return new DistributionCustomizer();
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_sanitizer",
+ function() {
+ let sanitizerScope = {};
+ Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", sanitizerScope);
+ return sanitizerScope.Sanitizer;
+ });
+
+ this._init();
+}
+
+/*
+ * OS X has the concept of zero-window sessions and therefore ignores the
+ * browser-lastwindow-close-* topics.
+ */
+const OBSERVE_LASTWINDOW_CLOSE_TOPICS = AppConstants.platform != "macosx";
+
+BrowserGlue.prototype = {
+ _saveSession: false,
+ _isPlacesInitObserver: false,
+ _isPlacesLockedObserver: false,
+ _isPlacesShutdownObserver: false,
+ _isPlacesDatabaseLocked: false,
+ _migrationImportsDefaultBookmarks: false,
+
+ _setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) {
+ if (!this._saveSession && !aForce)
+ return;
+
+ Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
+
+ // This method can be called via [NSApplication terminate:] on Mac, which
+ // ends up causing prefs not to be flushed to disk, so we need to do that
+ // explicitly here. See bug 497652.
+ Services.prefs.savePrefFile(null);
+ },
+
+ _setSyncAutoconnectDelay: function BG__setSyncAutoconnectDelay() {
+ // Assume that a non-zero value for services.sync.autoconnectDelay should override
+ if (Services.prefs.prefHasUserValue("services.sync.autoconnectDelay")) {
+ let prefDelay = Services.prefs.getIntPref("services.sync.autoconnectDelay");
+
+ if (prefDelay > 0)
+ return;
+ }
+
+ // delays are in seconds
+ const MAX_DELAY = 300;
+ let delay = 3;
+ let browserEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserEnum.hasMoreElements()) {
+ delay += browserEnum.getNext().gBrowser.tabs.length;
+ }
+ delay = delay <= MAX_DELAY ? delay : MAX_DELAY;
+
+ Cu.import("resource://services-sync/main.js");
+ Weave.Service.scheduler.delayedAutoConnect(delay);
+ },
+
+ // nsIObserver implementation
+ observe: function BG_observe(subject, topic, data) {
+ switch (topic) {
+ case "notifications-open-settings":
+ this._openPreferences("content");
+ break;
+ case "prefservice:after-app-defaults":
+ this._onAppDefaults();
+ break;
+ case "final-ui-startup":
+ this._finalUIStartup();
+ break;
+ case "browser-delayed-startup-finished":
+ this._onFirstWindowLoaded(subject);
+ Services.obs.removeObserver(this, "browser-delayed-startup-finished");
+ break;
+ case "sessionstore-windows-restored":
+ this._onWindowsRestored();
+ break;
+ case "browser:purge-session-history":
+ // reset the console service's error buffer
+ Services.console.logStringMessage(null); // clear the console (in case it's open)
+ Services.console.reset();
+ break;
+ case "restart-in-safe-mode":
+ this._onSafeModeRestart();
+ break;
+ case "quit-application-requested":
+ this._onQuitRequest(subject, data);
+ break;
+ case "quit-application-granted":
+ this._onQuitApplicationGranted();
+ break;
+ case "browser-lastwindow-close-requested":
+ if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
+ // The application is not actually quitting, but the last full browser
+ // window is about to be closed.
+ this._onQuitRequest(subject, "lastwindow");
+ }
+ break;
+ case "browser-lastwindow-close-granted":
+ if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
+ this._setPrefToSaveSession();
+ }
+ break;
+ case "weave:service:ready":
+ this._setSyncAutoconnectDelay();
+ break;
+ case "fxaccounts:onverified":
+ this._showSyncStartedDoorhanger();
+ break;
+ case "fxaccounts:device_disconnected":
+ this._onDeviceDisconnected();
+ break;
+ case "weave:engine:clients:display-uris":
+ this._onDisplaySyncURIs(subject);
+ break;
+ case "session-save":
+ this._setPrefToSaveSession(true);
+ subject.QueryInterface(Ci.nsISupportsPRBool);
+ subject.data = true;
+ break;
+ case "places-init-complete":
+ if (!this._migrationImportsDefaultBookmarks)
+ this._initPlaces(false);
+
+ Services.obs.removeObserver(this, "places-init-complete");
+ this._isPlacesInitObserver = false;
+ // no longer needed, since history was initialized completely.
+ Services.obs.removeObserver(this, "places-database-locked");
+ this._isPlacesLockedObserver = false;
+ break;
+ case "places-database-locked":
+ this._isPlacesDatabaseLocked = true;
+ // Stop observing, so further attempts to load history service
+ // will not show the prompt.
+ Services.obs.removeObserver(this, "places-database-locked");
+ this._isPlacesLockedObserver = false;
+ break;
+ case "idle":
+ this._backupBookmarks();
+ break;
+ case "distribution-customization-complete":
+ Services.obs.removeObserver(this, "distribution-customization-complete");
+ // Customization has finished, we don't need the customizer anymore.
+ delete this._distributionCustomizer;
+ break;
+ case "browser-glue-test": // used by tests
+ if (data == "post-update-notification") {
+ if (Services.prefs.prefHasUserValue("app.update.postupdate"))
+ this._showUpdateNotification();
+ }
+ else if (data == "force-ui-migration") {
+ this._migrateUI();
+ }
+ else if (data == "force-distribution-customization") {
+ this._distributionCustomizer.applyPrefDefaults();
+ this._distributionCustomizer.applyCustomizations();
+ // To apply distribution bookmarks use "places-init-complete".
+ }
+ else if (data == "force-places-init") {
+ this._initPlaces(false);
+ }
+ else if (data == "smart-bookmarks-init") {
+ this.ensurePlacesDefaultQueriesInitialized().then(() => {
+ Services.obs.notifyObservers(null, "test-smart-bookmarks-done", null);
+ });
+ }
+ break;
+ case "initial-migration-will-import-default-bookmarks":
+ this._migrationImportsDefaultBookmarks = true;
+ break;
+ case "initial-migration-did-import-default-bookmarks":
+ this._initPlaces(true);
+ break;
+ case "handle-xul-text-link":
+ let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool);
+ if (!linkHandled.data) {
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (win) {
+ data = JSON.parse(data);
+ let where = win.whereToOpenLink(data);
+ // Preserve legacy behavior of non-modifier left-clicks
+ // opening in a new selected tab.
+ if (where == "current") {
+ where = "tab";
+ }
+ win.openUILinkIn(data.href, where);
+ linkHandled.data = true;
+ }
+ }
+ break;
+ case "profile-before-change":
+ // Any component depending on Places should be finalized in
+ // _onPlacesShutdown. Any component that doesn't need to act after
+ // the UI has gone should be finalized in _onQuitApplicationGranted.
+ this._dispose();
+ break;
+ case "keyword-search":
+ // This notification is broadcast by the docshell when it "fixes up" a
+ // URI that it's been asked to load into a keyword search.
+ let engine = null;
+ try {
+ engine = subject.QueryInterface(Ci.nsISearchEngine);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ win.BrowserSearch.recordSearchInTelemetry(engine, "urlbar");
+ break;
+ case "browser-search-engine-modified":
+ // Ensure we cleanup the hiddenOneOffs pref when removing
+ // an engine, and that newly added engines are visible.
+ if (data == "engine-added" || data == "engine-removed") {
+ let engineName = subject.QueryInterface(Ci.nsISearchEngine).name;
+ let Preferences =
+ Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+ let pref = Preferences.get("browser.search.hiddenOneOffs");
+ let hiddenList = pref ? pref.split(",") : [];
+ hiddenList = hiddenList.filter(x => x !== engineName);
+ Preferences.set("browser.search.hiddenOneOffs",
+ hiddenList.join(","));
+ }
+ break;
+ case "flash-plugin-hang":
+ this._handleFlashHang();
+ break;
+ case "xpi-signature-changed":
+ let disabledAddons = JSON.parse(data).disabled;
+ AddonManager.getAddonsByIDs(disabledAddons, (addons) => {
+ for (let addon of addons) {
+ if (addon.type != "experiment") {
+ this._notifyUnsignedAddonsDisabled();
+ break;
+ }
+ }
+ });
+ break;
+ case "autocomplete-did-enter-text":
+ this._handleURLBarTelemetry(subject.QueryInterface(Ci.nsIAutoCompleteInput));
+ break;
+ case "test-initialize-sanitizer":
+ this._sanitizer.onStartup();
+ break;
+ case AddonWatcher.TOPIC_SLOW_ADDON_DETECTED:
+ this._notifySlowAddon(data);
+ break;
+ }
+ },
+
+ _handleURLBarTelemetry(input) {
+ if (!input ||
+ input.id != "urlbar" ||
+ input.inPrivateContext ||
+ input.popup.selectedIndex < 0) {
+ return;
+ }
+ let controller =
+ input.popup.view.QueryInterface(Ci.nsIAutoCompleteController);
+ let idx = input.popup.selectedIndex;
+ let value = controller.getValueAt(idx);
+ let action = input._parseActionUrl(value);
+ let actionType;
+ if (action) {
+ actionType =
+ action.type == "searchengine" && action.params.searchSuggestion ?
+ "searchsuggestion" :
+ action.type;
+ }
+ if (!actionType) {
+ let styles = new Set(controller.getStyleAt(idx).split(/\s+/));
+ let style = ["autofill", "tag", "bookmark"].find(s => styles.has(s));
+ actionType = style || "history";
+ }
+
+ Services.telemetry
+ .getHistogramById("FX_URLBAR_SELECTED_RESULT_INDEX")
+ .add(idx);
+
+ // Ideally this would be a keyed histogram and we'd just add(actionType),
+ // but keyed histograms aren't currently shown on the telemetry dashboard
+ // (bug 1151756).
+ //
+ // You can add values but don't change any of the existing values.
+ // Otherwise you'll break our data.
+ let buckets = {
+ autofill: 0,
+ bookmark: 1,
+ history: 2,
+ keyword: 3,
+ searchengine: 4,
+ searchsuggestion: 5,
+ switchtab: 6,
+ tag: 7,
+ visiturl: 8,
+ remotetab: 9,
+ extension: 10,
+ };
+ if (actionType in buckets) {
+ Services.telemetry
+ .getHistogramById("FX_URLBAR_SELECTED_RESULT_TYPE")
+ .add(buckets[actionType]);
+ } else {
+ Cu.reportError("Unknown FX_URLBAR_SELECTED_RESULT_TYPE type: " +
+ actionType);
+ }
+ },
+
+ // initialization (called on application startup)
+ _init: function BG__init() {
+ let os = Services.obs;
+ os.addObserver(this, "notifications-open-settings", false);
+ os.addObserver(this, "prefservice:after-app-defaults", false);
+ os.addObserver(this, "final-ui-startup", false);
+ os.addObserver(this, "browser-delayed-startup-finished", false);
+ os.addObserver(this, "sessionstore-windows-restored", false);
+ os.addObserver(this, "browser:purge-session-history", false);
+ os.addObserver(this, "quit-application-requested", false);
+ os.addObserver(this, "quit-application-granted", false);
+ if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
+ os.addObserver(this, "browser-lastwindow-close-requested", false);
+ os.addObserver(this, "browser-lastwindow-close-granted", false);
+ }
+ os.addObserver(this, "weave:service:ready", false);
+ os.addObserver(this, "fxaccounts:onverified", false);
+ os.addObserver(this, "fxaccounts:device_disconnected", false);
+ os.addObserver(this, "weave:engine:clients:display-uris", false);
+ os.addObserver(this, "session-save", false);
+ os.addObserver(this, "places-init-complete", false);
+ this._isPlacesInitObserver = true;
+ os.addObserver(this, "places-database-locked", false);
+ this._isPlacesLockedObserver = true;
+ os.addObserver(this, "distribution-customization-complete", false);
+ os.addObserver(this, "handle-xul-text-link", false);
+ os.addObserver(this, "profile-before-change", false);
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ os.addObserver(this, "keyword-search", false);
+ }
+ os.addObserver(this, "browser-search-engine-modified", false);
+ os.addObserver(this, "restart-in-safe-mode", false);
+ os.addObserver(this, "flash-plugin-hang", false);
+ os.addObserver(this, "xpi-signature-changed", false);
+ os.addObserver(this, "autocomplete-did-enter-text", false);
+
+ if (AppConstants.NIGHTLY_BUILD) {
+ os.addObserver(this, AddonWatcher.TOPIC_SLOW_ADDON_DETECTED, false);
+ }
+
+ this._flashHangCount = 0;
+ this._firstWindowReady = new Promise(resolve => this._firstWindowLoaded = resolve);
+
+ if (AppConstants.platform == "win" ||
+ AppConstants.platform == "macosx") {
+ // Handles prompting to inform about incompatibilites when accessibility
+ // and e10s are active together.
+ E10SAccessibilityCheck.init();
+ }
+ },
+
+ // cleanup (called on application shutdown)
+ _dispose: function BG__dispose() {
+ let os = Services.obs;
+ os.removeObserver(this, "notifications-open-settings");
+ os.removeObserver(this, "prefservice:after-app-defaults");
+ os.removeObserver(this, "final-ui-startup");
+ os.removeObserver(this, "sessionstore-windows-restored");
+ os.removeObserver(this, "browser:purge-session-history");
+ os.removeObserver(this, "quit-application-requested");
+ os.removeObserver(this, "quit-application-granted");
+ os.removeObserver(this, "restart-in-safe-mode");
+ if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
+ os.removeObserver(this, "browser-lastwindow-close-requested");
+ os.removeObserver(this, "browser-lastwindow-close-granted");
+ }
+ os.removeObserver(this, "weave:service:ready");
+ os.removeObserver(this, "fxaccounts:onverified");
+ os.removeObserver(this, "fxaccounts:device_disconnected");
+ os.removeObserver(this, "weave:engine:clients:display-uris");
+ os.removeObserver(this, "session-save");
+ if (this._bookmarksBackupIdleTime) {
+ this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
+ delete this._bookmarksBackupIdleTime;
+ }
+ if (this._isPlacesInitObserver)
+ os.removeObserver(this, "places-init-complete");
+ if (this._isPlacesLockedObserver)
+ os.removeObserver(this, "places-database-locked");
+ os.removeObserver(this, "handle-xul-text-link");
+ os.removeObserver(this, "profile-before-change");
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ os.removeObserver(this, "keyword-search");
+ }
+ os.removeObserver(this, "browser-search-engine-modified");
+ os.removeObserver(this, "flash-plugin-hang");
+ os.removeObserver(this, "xpi-signature-changed");
+ os.removeObserver(this, "autocomplete-did-enter-text");
+ },
+
+ _onAppDefaults: function BG__onAppDefaults() {
+ // apply distribution customizations (prefs)
+ // other customizations are applied in _finalUIStartup()
+ this._distributionCustomizer.applyPrefDefaults();
+ },
+
+ _notifySlowAddon: function BG_notifySlowAddon(addonId) {
+ let addonCallback = function(addon) {
+ if (!addon) {
+ Cu.reportError("couldn't look up addon: " + addonId);
+ return;
+ }
+ let win = RecentWindow.getMostRecentBrowserWindow();
+
+ if (!win) {
+ return;
+ }
+
+ let brandBundle = win.document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let message = win.gNavigatorBundle.getFormattedString("addonwatch.slow", [addon.name, brandShortName]);
+ let notificationBox = win.document.getElementById("global-notificationbox");
+ let notificationId = 'addon-slow:' + addonId;
+ let notification = notificationBox.getNotificationWithValue(notificationId);
+
+ // Monitor the response of users
+ const STATE_WARNING_DISPLAYED = 0;
+ const STATE_USER_PICKED_DISABLE = 1;
+ const STATE_USER_PICKED_IGNORE_FOR_NOW = 2;
+ const STATE_USER_PICKED_IGNORE_FOREVER = 3;
+ const STATE_USER_CLOSED_NOTIFICATION = 4;
+
+ let update = function(response) {
+ Services.telemetry.getHistogramById("SLOW_ADDON_WARNING_STATES").add(response);
+ }
+
+ let complete = false;
+ let start = Date.now();
+ let done = function(response) {
+ // Only report the first reason for closing.
+ if (complete) {
+ return;
+ }
+ complete = true;
+ update(response);
+ Services.telemetry.getHistogramById("SLOW_ADDON_WARNING_RESPONSE_TIME").add(Date.now() - start);
+ };
+
+ update(STATE_WARNING_DISPLAYED);
+
+ if (notification) {
+ notification.label = message;
+ } else {
+ let buttons = [
+ {
+ label: win.gNavigatorBundle.getFormattedString("addonwatch.disable.label", [addon.name]),
+ accessKey: "", // workaround for bug 1192901
+ callback: function() {
+ done(STATE_USER_PICKED_DISABLE);
+ addon.userDisabled = true;
+ if (addon.pendingOperations == addon.PENDING_NONE) {
+ return;
+ }
+ let restartMessage = win.gNavigatorBundle.getFormattedString("addonwatch.restart.message", [addon.name, brandShortName]);
+ let restartButton = [
+ {
+ label: win.gNavigatorBundle.getFormattedString("addonwatch.restart.label", [brandShortName]),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.restart.accesskey"),
+ callback: function() {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+ }
+ }
+ ];
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(restartMessage, "restart-" + addonId, "",
+ priority, restartButton);
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("addonwatch.ignoreSession.label"),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.ignoreSession.accesskey"),
+ callback: function() {
+ done(STATE_USER_PICKED_IGNORE_FOR_NOW);
+ AddonWatcher.ignoreAddonForSession(addonId);
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("addonwatch.ignorePerm.label"),
+ accessKey: win.gNavigatorBundle.getString("addonwatch.ignorePerm.accesskey"),
+ callback: function() {
+ done(STATE_USER_PICKED_IGNORE_FOREVER);
+ AddonWatcher.ignoreAddonPermanently(addonId);
+ }
+ },
+ ];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notification = notificationBox.appendNotification(
+ message, notificationId, "",
+ priority, buttons,
+ function(topic) {
+ if (topic == "removed") {
+ // Other callbacks are called before this one and only the first
+ // call to `done` is taken into account, so if this call to `done`
+ // gets through, this means that the user has closed the notification
+ // manually.
+ done(STATE_USER_CLOSED_NOTIFICATION);
+ }
+ });
+ }
+ };
+ AddonManager.getAddonByID(addonId, addonCallback);
+ },
+
+ // runs on startup, before the first command line handler is invoked
+ // (i.e. before the first window is opened)
+ _finalUIStartup: function BG__finalUIStartup() {
+ this._sanitizer.onStartup();
+ // check if we're in safe mode
+ if (Services.appinfo.inSafeMode) {
+ Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul",
+ "_blank", "chrome,centerscreen,modal,resizable=no", null);
+ }
+
+ // apply distribution customizations
+ // prefs are applied in _onAppDefaults()
+ this._distributionCustomizer.applyCustomizations();
+
+ // handle any UI migration
+ this._migrateUI();
+
+ // This is support code for the location bar search suggestions; passing
+ // from opt-in to opt-out should respect the user's choice, thus we need
+ // to cache that choice in a pref for future use.
+ // Note: this is not in migrateUI because we need to uplift it. This
+ // code is also short-lived, since we can remove it as soon as opt-out
+ // search suggestions shipped in release (Bug 1344928).
+ try {
+ let urlbarPrefs = Services.prefs.getBranch("browser.urlbar.");
+ if (!urlbarPrefs.prefHasUserValue("searchSuggestionsChoice") &&
+ urlbarPrefs.getBoolPref("userMadeSearchSuggestionsChoice")) {
+ urlbarPrefs.setBoolPref("searchSuggestionsChoice",
+ urlbarPrefs.getBoolPref("suggest.searches"));
+ }
+ } catch (ex) { /* missing any of the prefs is not critical */ }
+
+ PageThumbs.init();
+ webrtcUI.init();
+ AboutHome.init();
+
+ DirectoryLinksProvider.init();
+ NewTabUtils.init();
+ NewTabUtils.links.addProvider(DirectoryLinksProvider);
+ AboutNewTab.init();
+
+ NewTabMessages.init();
+
+ SessionStore.init();
+ BrowserUsageTelemetry.init();
+ BrowserUITelemetry.init();
+ ContentSearch.init();
+ FormValidationHandler.init();
+
+ ContentClick.init();
+ RemotePrompt.init();
+ Feeds.init();
+ ContentPrefServiceParent.init();
+
+ LoginManagerParent.init();
+ ReaderParent.init();
+ URLBarZoom.init();
+
+ SelfSupportBackend.init();
+
+ // Ensure we keep track of places/pw-mananager undo by init'ing this early.
+ Cu.import("resource:///modules/AutoMigrate.jsm");
+
+ if (!AppConstants.RELEASE_OR_BETA) {
+ let themeName = gBrowserBundle.GetStringFromName("deveditionTheme.name");
+ let vendorShortName = gBrandBundle.GetStringFromName("vendorShortName");
+
+ LightweightThemeManager.addBuiltInTheme({
+ id: "firefox-devedition@mozilla.org",
+ name: themeName,
+ headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
+ iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
+ author: vendorShortName,
+ });
+ }
+
+ TabCrashHandler.init();
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ PluginCrashReporter.init();
+ UnsubmittedCrashHandler.init();
+ }
+
+ Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+ },
+
+ _checkForOldBuildUpdates: function () {
+ // check for update if our build is old
+ if (AppConstants.MOZ_UPDATER &&
+ Services.prefs.getBoolPref("app.update.enabled") &&
+ Services.prefs.getBoolPref("app.update.checkInstallTime")) {
+
+ let buildID = Services.appinfo.appBuildID;
+ let today = new Date().getTime();
+ let buildDate = new Date(buildID.slice(0, 4), // year
+ buildID.slice(4, 6) - 1, // months are zero-based.
+ buildID.slice(6, 8), // day
+ buildID.slice(8, 10), // hour
+ buildID.slice(10, 12), // min
+ buildID.slice(12, 14)) // ms
+ .getTime();
+
+ const millisecondsIn24Hours = 86400000;
+ let acceptableAge = Services.prefs.getIntPref("app.update.checkInstallTime.days") * millisecondsIn24Hours;
+
+ if (buildDate + acceptableAge < today) {
+ Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService).checkForBackgroundUpdates();
+ }
+ }
+ },
+
+ _onSafeModeRestart: function BG_onSafeModeRestart() {
+ // prompt the user to confirm
+ let strings = gBrowserBundle;
+ let promptTitle = strings.GetStringFromName("safeModeRestartPromptTitle");
+ let promptMessage = strings.GetStringFromName("safeModeRestartPromptMessage");
+ let restartText = strings.GetStringFromName("safeModeRestartButton");
+ let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+ Services.prompt.BUTTON_TITLE_IS_STRING) +
+ (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_CANCEL) +
+ Services.prompt.BUTTON_POS_0_DEFAULT;
+
+ let rv = Services.prompt.confirmEx(null, promptTitle, promptMessage,
+ buttonFlags, restartText, null, null,
+ null, {});
+ if (rv != 0)
+ return;
+
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ if (!cancelQuit.data) {
+ Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
+ }
+ },
+
+ _trackSlowStartup: function () {
+ if (Services.startup.interrupted ||
+ Services.prefs.getBoolPref("browser.slowStartup.notificationDisabled"))
+ return;
+
+ let currentTime = Date.now() - Services.startup.getStartupInfo().process;
+ let averageTime = 0;
+ let samples = 0;
+ try {
+ averageTime = Services.prefs.getIntPref("browser.slowStartup.averageTime");
+ samples = Services.prefs.getIntPref("browser.slowStartup.samples");
+ } catch (e) { }
+
+ let totalTime = (averageTime * samples) + currentTime;
+ samples++;
+ averageTime = totalTime / samples;
+
+ if (samples >= Services.prefs.getIntPref("browser.slowStartup.maxSamples")) {
+ if (averageTime > Services.prefs.getIntPref("browser.slowStartup.timeThreshold"))
+ this._calculateProfileAgeInDays().then(this._showSlowStartupNotification, null);
+ averageTime = 0;
+ samples = 0;
+ }
+
+ Services.prefs.setIntPref("browser.slowStartup.averageTime", averageTime);
+ Services.prefs.setIntPref("browser.slowStartup.samples", samples);
+ },
+
+ _calculateProfileAgeInDays: Task.async(function* () {
+ let ProfileAge = Cu.import("resource://gre/modules/ProfileAge.jsm", {}).ProfileAge;
+ let profileAge = new ProfileAge(null, null);
+
+ let creationDate = yield profileAge.created;
+ let resetDate = yield profileAge.reset;
+
+ // if the profile was reset, consider the
+ // reset date for its age.
+ let profileDate = resetDate || creationDate;
+
+ const ONE_DAY = 24 * 60 * 60 * 1000;
+ return (Date.now() - profileDate) / ONE_DAY;
+ }),
+
+ _showSlowStartupNotification: function (profileAge) {
+ if (profileAge < 90) // 3 months
+ return;
+
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (!win)
+ return;
+
+ let productName = gBrandBundle.GetStringFromName("brandFullName");
+ let message = win.gNavigatorBundle.getFormattedString("slowStartup.message", [productName]);
+
+ let buttons = [
+ {
+ label: win.gNavigatorBundle.getString("slowStartup.helpButton.label"),
+ accessKey: win.gNavigatorBundle.getString("slowStartup.helpButton.accesskey"),
+ callback: function () {
+ win.openUILinkIn("https://support.mozilla.org/kb/reset-firefox-easily-fix-most-problems", "tab");
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("slowStartup.disableNotificationButton.label"),
+ accessKey: win.gNavigatorBundle.getString("slowStartup.disableNotificationButton.accesskey"),
+ callback: function () {
+ Services.prefs.setBoolPref("browser.slowStartup.notificationDisabled", true);
+ }
+ }
+ ];
+
+ let nb = win.document.getElementById("global-notificationbox");
+ nb.appendNotification(message, "slow-startup",
+ "chrome://browser/skin/slowStartup-16.png",
+ nb.PRIORITY_INFO_LOW, buttons);
+ },
+
+ /**
+ * Show a notification bar offering a reset.
+ *
+ * @param reason
+ * String of either "unused" or "uninstall", specifying the reason
+ * why a profile reset is offered.
+ */
+ _resetProfileNotification: function (reason) {
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (!win)
+ return;
+
+ Cu.import("resource://gre/modules/ResetProfile.jsm");
+ if (!ResetProfile.resetSupported())
+ return;
+
+ let productName = gBrandBundle.GetStringFromName("brandShortName");
+ let resetBundle = Services.strings
+ .createBundle("chrome://global/locale/resetProfile.properties");
+
+ let message;
+ if (reason == "unused") {
+ message = resetBundle.formatStringFromName("resetUnusedProfile.message", [productName], 1);
+ } else if (reason == "uninstall") {
+ message = resetBundle.formatStringFromName("resetUninstalled.message", [productName], 1);
+ } else {
+ throw new Error(`Unknown reason (${reason}) given to _resetProfileNotification.`);
+ }
+ let buttons = [
+ {
+ label: resetBundle.formatStringFromName("refreshProfile.resetButton.label", [productName], 1),
+ accessKey: resetBundle.GetStringFromName("refreshProfile.resetButton.accesskey"),
+ callback: function () {
+ ResetProfile.openConfirmationDialog(win);
+ }
+ },
+ ];
+
+ let nb = win.document.getElementById("global-notificationbox");
+ nb.appendNotification(message, "reset-profile-notification",
+ "chrome://global/skin/icons/question-16.png",
+ nb.PRIORITY_INFO_LOW, buttons);
+ },
+
+ _notifyUnsignedAddonsDisabled: function () {
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (!win)
+ return;
+
+ let message = win.gNavigatorBundle.getString("unsignedAddonsDisabled.message");
+ let buttons = [
+ {
+ label: win.gNavigatorBundle.getString("unsignedAddonsDisabled.learnMore.label"),
+ accessKey: win.gNavigatorBundle.getString("unsignedAddonsDisabled.learnMore.accesskey"),
+ callback: function () {
+ win.BrowserOpenAddonsMgr("addons://list/extension?unsigned=true");
+ }
+ },
+ ];
+
+ let nb = win.document.getElementById("high-priority-global-notificationbox");
+ nb.appendNotification(message, "unsigned-addons-disabled", "",
+ nb.PRIORITY_WARNING_MEDIUM, buttons);
+ },
+
+ _firstWindowTelemetry: function(aWindow) {
+ let SCALING_PROBE_NAME = "";
+ switch (AppConstants.platform) {
+ case "win":
+ SCALING_PROBE_NAME = "DISPLAY_SCALING_MSWIN";
+ break;
+ case "macosx":
+ SCALING_PROBE_NAME = "DISPLAY_SCALING_OSX";
+ break;
+ case "linux":
+ SCALING_PROBE_NAME = "DISPLAY_SCALING_LINUX";
+ break;
+ }
+ if (SCALING_PROBE_NAME) {
+ let scaling = aWindow.devicePixelRatio * 100;
+ try {
+ Services.telemetry.getHistogramById(SCALING_PROBE_NAME).add(scaling);
+ } catch (ex) {}
+ }
+ },
+
+ // the first browser window has finished initializing
+ _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) {
+ // Initialize PdfJs when running in-process and remote. This only
+ // happens once since PdfJs registers global hooks. If the PdfJs
+ // extension is installed the init method below will be overridden
+ // leaving initialization to the extension.
+ // parent only: configure default prefs, set up pref observers, register
+ // pdf content handler, and initializes parent side message manager
+ // shim for privileged api access.
+ PdfJs.init(true);
+ // child only: similar to the call above for parent - register content
+ // handler and init message manager child shim for privileged api access.
+ // With older versions of the extension installed, this load will fail
+ // passively.
+ Services.ppmm.loadProcessScript("resource://pdf.js/pdfjschildbootstrap.js", true);
+
+ if (AppConstants.platform == "win") {
+ // For Windows 7, initialize the jump list module.
+ const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+ if (WINTASKBAR_CONTRACTID in Cc &&
+ Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
+ let temp = {};
+ Cu.import("resource:///modules/WindowsJumpLists.jsm", temp);
+ temp.WinTaskbarJumpList.startup();
+ }
+ }
+
+ ProcessHangMonitor.init();
+
+ // A channel for "remote troubleshooting" code...
+ let channel = new WebChannel("remote-troubleshooting", "remote-troubleshooting");
+ channel.listen((id, data, target) => {
+ if (data.command == "request") {
+ let {Troubleshoot} = Cu.import("resource://gre/modules/Troubleshoot.jsm", {});
+ Troubleshoot.snapshot(data => {
+ // for privacy we remove crash IDs and all preferences (but bug 1091944
+ // exists to expose prefs once we are confident of privacy implications)
+ delete data.crashes;
+ delete data.modifiedPreferences;
+ channel.send(data, target);
+ });
+ }
+ });
+
+ this._trackSlowStartup();
+
+ // Offer to reset a user's profile if it hasn't been used for 60 days.
+ const OFFER_PROFILE_RESET_INTERVAL_MS = 60 * 24 * 60 * 60 * 1000;
+ let lastUse = Services.appinfo.replacedLockTime;
+ let disableResetPrompt = false;
+ try {
+ disableResetPrompt = Services.prefs.getBoolPref("browser.disableResetPrompt");
+ } catch (e) {}
+
+ if (!disableResetPrompt && lastUse &&
+ Date.now() - lastUse >= OFFER_PROFILE_RESET_INTERVAL_MS) {
+ this._resetProfileNotification("unused");
+ } else if (AppConstants.platform == "win" && !disableResetPrompt) {
+ // Check if we were just re-installed and offer Firefox Reset
+ let updateChannel;
+ try {
+ updateChannel = Cu.import("resource://gre/modules/UpdateUtils.jsm", {}).UpdateUtils.UpdateChannel;
+ } catch (ex) {}
+ if (updateChannel) {
+ let uninstalledValue =
+ WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\Firefox",
+ `Uninstalled-${updateChannel}`);
+ let removalSuccessful =
+ WindowsRegistry.removeRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\Firefox",
+ `Uninstalled-${updateChannel}`);
+ if (removalSuccessful && uninstalledValue == "True") {
+ this._resetProfileNotification("uninstall");
+ }
+ }
+ }
+
+ this._checkForOldBuildUpdates();
+
+ AutoCompletePopup.init();
+ DateTimePickerHelper.init();
+
+ this._firstWindowTelemetry(aWindow);
+ this._firstWindowLoaded();
+ },
+
+ /**
+ * Application shutdown handler.
+ */
+ _onQuitApplicationGranted: function () {
+ // This pref must be set here because SessionStore will use its value
+ // on quit-application.
+ this._setPrefToSaveSession();
+
+ // Call trackStartupCrashEnd here in case the delayed call on startup hasn't
+ // yet occurred (see trackStartupCrashEnd caller in browser.js).
+ try {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.trackStartupCrashEnd();
+ } catch (e) {
+ Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
+ }
+
+ if (this._bookmarksBackupIdleTime) {
+ this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
+ delete this._bookmarksBackupIdleTime;
+ }
+
+ BrowserUsageTelemetry.uninit();
+ SelfSupportBackend.uninit();
+ PageThumbs.uninit();
+ NewTabMessages.uninit();
+ AboutNewTab.uninit();
+ webrtcUI.uninit();
+ FormValidationHandler.uninit();
+ AutoCompletePopup.uninit();
+ DateTimePickerHelper.uninit();
+ if (AppConstants.NIGHTLY_BUILD) {
+ AddonWatcher.uninit();
+ }
+ },
+
+ _initServiceDiscovery: function () {
+ if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
+ return;
+ }
+ var rokuDevice = {
+ id: "roku:ecp",
+ target: "roku:ecp",
+ factory: function(aService) {
+ Cu.import("resource://gre/modules/RokuApp.jsm");
+ return new RokuApp(aService);
+ },
+ types: ["video/mp4"],
+ extensions: ["mp4"]
+ };
+
+ // Register targets
+ SimpleServiceDiscovery.registerDevice(rokuDevice);
+
+ // Search for devices continuously every 120 seconds
+ SimpleServiceDiscovery.search(120 * 1000);
+ },
+
+ // All initial windows have opened.
+ _onWindowsRestored: function BG__onWindowsRestored() {
+ if (AppConstants.MOZ_DEV_EDITION) {
+ this._createExtraDefaultProfile();
+ }
+
+ this._initServiceDiscovery();
+
+ // Show update notification, if needed.
+ if (Services.prefs.prefHasUserValue("app.update.postupdate"))
+ this._showUpdateNotification();
+
+ // Load the "more info" page for a locked places.sqlite
+ // This property is set earlier by places-database-locked topic.
+ if (this._isPlacesDatabaseLocked) {
+ this._showPlacesLockedNotificationBox();
+ }
+
+ // For any add-ons that were installed disabled and can be enabled offer
+ // them to the user.
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ AddonManager.getAllAddons(addons => {
+ for (let addon of addons) {
+ // If this add-on has already seen (or seen is undefined for non-XPI
+ // add-ons) then skip it.
+ if (addon.seen !== false) {
+ continue;
+ }
+
+ // If this add-on cannot be enabled (either already enabled or
+ // appDisabled) then skip it.
+ if (!(addon.permissions & AddonManager.PERM_CAN_ENABLE)) {
+ continue;
+ }
+
+ win.openUILinkIn("about:newaddon?id=" + addon.id, "tab");
+ }
+ });
+
+ let signingRequired;
+ if (AppConstants.MOZ_REQUIRE_SIGNING) {
+ signingRequired = true;
+ } else {
+ signingRequired = Services.prefs.getBoolPref("xpinstall.signatures.required");
+ }
+
+ if (signingRequired) {
+ let disabledAddons = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
+ AddonManager.getAddonsByIDs(disabledAddons, (addons) => {
+ for (let addon of addons) {
+ if (addon.type == "experiment")
+ continue;
+
+ if (addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
+ this._notifyUnsignedAddonsDisabled();
+ break;
+ }
+ }
+ });
+ }
+
+ // Perform default browser checking.
+ if (ShellService) {
+ let shouldCheck = AppConstants.DEBUG ? false :
+ ShellService.shouldCheckDefaultBrowser;
+
+ const skipDefaultBrowserCheck =
+ Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheckOnFirstRun") &&
+ Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheck");
+
+ const usePromptLimit = !AppConstants.RELEASE_OR_BETA;
+ let promptCount =
+ usePromptLimit ? Services.prefs.getIntPref("browser.shell.defaultBrowserCheckCount") : 0;
+
+ let willRecoverSession = false;
+ try {
+ let ss = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+ willRecoverSession =
+ (ss.sessionType == Ci.nsISessionStartup.RECOVER_SESSION);
+ }
+ catch (ex) { /* never mind; suppose SessionStore is broken */ }
+
+ // startup check, check all assoc
+ let isDefault = false;
+ let isDefaultError = false;
+ try {
+ isDefault = ShellService.isDefaultBrowser(true, false);
+ } catch (ex) {
+ isDefaultError = true;
+ }
+
+ if (isDefault) {
+ let now = (Math.floor(Date.now() / 1000)).toString();
+ Services.prefs.setCharPref("browser.shell.mostRecentDateSetAsDefault", now);
+ }
+
+ let willPrompt = shouldCheck && !isDefault && !willRecoverSession;
+
+ // Skip the "Set Default Browser" check during first-run or after the
+ // browser has been run a few times.
+ if (willPrompt) {
+ if (skipDefaultBrowserCheck) {
+ Services.prefs.setBoolPref("browser.shell.skipDefaultBrowserCheck", false);
+ willPrompt = false;
+ } else {
+ promptCount++;
+ }
+ if (usePromptLimit && promptCount > 3) {
+ willPrompt = false;
+ }
+ }
+
+ if (usePromptLimit && willPrompt) {
+ Services.prefs.setIntPref("browser.shell.defaultBrowserCheckCount", promptCount);
+ }
+
+ try {
+ // Report default browser status on startup to telemetry
+ // so we can track whether we are the default.
+ Services.telemetry.getHistogramById("BROWSER_IS_USER_DEFAULT")
+ .add(isDefault);
+ Services.telemetry.getHistogramById("BROWSER_IS_USER_DEFAULT_ERROR")
+ .add(isDefaultError);
+ Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_ALWAYS_CHECK")
+ .add(shouldCheck);
+ Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_DIALOG_PROMPT_RAWCOUNT")
+ .add(promptCount);
+ }
+ catch (ex) { /* Don't break the default prompt if telemetry is broken. */ }
+
+ if (willPrompt) {
+ Services.tm.mainThread.dispatch(function() {
+ DefaultBrowserCheck.prompt(RecentWindow.getMostRecentBrowserWindow());
+ }.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ }
+
+ E10SAccessibilityCheck.onWindowsRestored();
+ },
+
+ _createExtraDefaultProfile: function () {
+ if (!AppConstants.MOZ_DEV_EDITION) {
+ return;
+ }
+ // If Developer Edition is the only installed Firefox version and no other
+ // profiles are present, create a second one for use by other versions.
+ // This helps Firefox versions earlier than 35 avoid accidentally using the
+ // unsuitable Developer Edition profile.
+ let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
+ .getService(Ci.nsIToolkitProfileService);
+ let profileCount = profileService.profileCount;
+ if (profileCount == 1 && profileService.selectedProfile.name != "default") {
+ let newProfile;
+ try {
+ newProfile = profileService.createProfile(null, "default");
+ profileService.defaultProfile = newProfile;
+ profileService.flush();
+ } catch (e) {
+ Cu.reportError("Could not create profile 'default': " + e);
+ }
+ if (newProfile) {
+ // We don't want a default profile with Developer Edition settings, an
+ // empty profile directory will do. The profile service of the other
+ // Firefox will populate it with its own stuff.
+ let newProfilePath = newProfile.rootDir.path;
+ OS.File.removeDir(newProfilePath).then(() => {
+ return OS.File.makeDir(newProfilePath);
+ }).then(null, e => {
+ Cu.reportError("Could not empty profile 'default': " + e);
+ });
+ }
+ }
+ },
+
+ _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
+ // If user has already dismissed quit request, then do nothing
+ if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data)
+ return;
+
+ // There are several cases where we won't show a dialog here:
+ // 1. There is only 1 tab open in 1 window
+ // 2. The session will be restored at startup, indicated by
+ // browser.startup.page == 3 or browser.sessionstore.resume_session_once == true
+ // 3. browser.warnOnQuit == false
+ // 4. The browser is currently in Private Browsing mode
+ // 5. The browser will be restarted.
+ //
+ // Otherwise these are the conditions and the associated dialogs that will be shown:
+ // 1. aQuitType == "lastwindow" or "quit" and browser.showQuitWarning == true
+ // - The quit dialog will be shown
+ // 2. aQuitType == "lastwindow" && browser.tabs.warnOnClose == true
+ // - The "closing multiple tabs" dialog will be shown
+ //
+ // aQuitType == "lastwindow" is overloaded. "lastwindow" is used to indicate
+ // "the last window is closing but we're not quitting (a non-browser window is open)"
+ // and also "we're quitting by closing the last window".
+
+ if (aQuitType == "restart")
+ return;
+
+ var windowcount = 0;
+ var pagecount = 0;
+ var browserEnum = Services.wm.getEnumerator("navigator:browser");
+ let allWindowsPrivate = true;
+ while (browserEnum.hasMoreElements()) {
+ // XXXbz should we skip closed windows here?
+ windowcount++;
+
+ var browser = browserEnum.getNext();
+ if (!PrivateBrowsingUtils.isWindowPrivate(browser))
+ allWindowsPrivate = false;
+ var tabbrowser = browser.document.getElementById("content");
+ if (tabbrowser)
+ pagecount += tabbrowser.browsers.length - tabbrowser._numPinnedTabs;
+ }
+
+ this._saveSession = false;
+ if (pagecount < 2)
+ return;
+
+ if (!aQuitType)
+ aQuitType = "quit";
+
+ // browser.warnOnQuit is a hidden global boolean to override all quit prompts
+ // browser.showQuitWarning specifically covers quitting
+ // browser.tabs.warnOnClose is the global "warn when closing multiple tabs" pref
+
+ var sessionWillBeRestored = Services.prefs.getIntPref("browser.startup.page") == 3 ||
+ Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
+ if (sessionWillBeRestored || !Services.prefs.getBoolPref("browser.warnOnQuit"))
+ return;
+
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ // On last window close or quit && showQuitWarning, we want to show the
+ // quit warning.
+ if (!Services.prefs.getBoolPref("browser.showQuitWarning")) {
+ if (aQuitType == "lastwindow") {
+ // If aQuitType is "lastwindow" and we aren't showing the quit warning,
+ // we should show the window closing warning instead. warnAboutClosing
+ // tabs checks browser.tabs.warnOnClose and returns if it's ok to close
+ // the window. It doesn't actually close the window.
+ aCancelQuit.data =
+ !win.gBrowser.warnAboutClosingTabs(win.gBrowser.closingTabsEnum.ALL);
+ }
+ return;
+ }
+
+ let prompt = Services.prompt;
+ let quitBundle = Services.strings.createBundle("chrome://browser/locale/quitDialog.properties");
+ let appName = gBrandBundle.GetStringFromName("brandShortName");
+ let quitDialogTitle = quitBundle.formatStringFromName("quitDialogTitle",
+ [appName], 1);
+ let neverAskText = quitBundle.GetStringFromName("neverAsk2");
+ let neverAsk = {value: false};
+
+ let choice;
+ if (allWindowsPrivate) {
+ let text = quitBundle.formatStringFromName("messagePrivate", [appName], 1);
+ let flags = prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0 +
+ prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_1 +
+ prompt.BUTTON_POS_0_DEFAULT;
+ choice = prompt.confirmEx(win, quitDialogTitle, text, flags,
+ quitBundle.GetStringFromName("quitTitle"),
+ quitBundle.GetStringFromName("cancelTitle"),
+ null,
+ neverAskText, neverAsk);
+
+ // The order of the buttons differs between the prompt.confirmEx calls
+ // here so we need to fix this for proper handling below.
+ if (choice == 0) {
+ choice = 2;
+ }
+ } else {
+ let text = quitBundle.formatStringFromName(
+ windowcount == 1 ? "messageNoWindows" : "message", [appName], 1);
+ let flags = prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0 +
+ prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_1 +
+ prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_2 +
+ prompt.BUTTON_POS_0_DEFAULT;
+ choice = prompt.confirmEx(win, quitDialogTitle, text, flags,
+ quitBundle.GetStringFromName("saveTitle"),
+ quitBundle.GetStringFromName("cancelTitle"),
+ quitBundle.GetStringFromName("quitTitle"),
+ neverAskText, neverAsk);
+ }
+
+ switch (choice) {
+ case 2: // Quit
+ if (neverAsk.value)
+ Services.prefs.setBoolPref("browser.showQuitWarning", false);
+ break;
+ case 1: // Cancel
+ aCancelQuit.QueryInterface(Ci.nsISupportsPRBool);
+ aCancelQuit.data = true;
+ break;
+ case 0: // Save & Quit
+ this._saveSession = true;
+ if (neverAsk.value) {
+ // always save state when shutting down
+ Services.prefs.setIntPref("browser.startup.page", 3);
+ }
+ break;
+ }
+ },
+
+ _showUpdateNotification: function BG__showUpdateNotification() {
+ Services.prefs.clearUserPref("app.update.postupdate");
+
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ try {
+ // If the updates.xml file is deleted then getUpdateAt will throw.
+ var update = um.getUpdateAt(0).QueryInterface(Ci.nsIPropertyBag);
+ }
+ catch (e) {
+ // This should never happen.
+ Cu.reportError("Unable to find update: " + e);
+ return;
+ }
+
+ var actions = update.getProperty("actions");
+ if (!actions || actions.indexOf("silent") != -1)
+ return;
+
+ var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+ getService(Ci.nsIURLFormatter);
+ var appName = gBrandBundle.GetStringFromName("brandShortName");
+
+ function getNotifyString(aPropData) {
+ var propValue = update.getProperty(aPropData.propName);
+ if (!propValue) {
+ if (aPropData.prefName)
+ propValue = formatter.formatURLPref(aPropData.prefName);
+ else if (aPropData.stringParams)
+ propValue = gBrowserBundle.formatStringFromName(aPropData.stringName,
+ aPropData.stringParams,
+ aPropData.stringParams.length);
+ else
+ propValue = gBrowserBundle.GetStringFromName(aPropData.stringName);
+ }
+ return propValue;
+ }
+
+ if (actions.indexOf("showNotification") != -1) {
+ let text = getNotifyString({propName: "notificationText",
+ stringName: "puNotifyText",
+ stringParams: [appName]});
+ let url = getNotifyString({propName: "notificationURL",
+ prefName: "startup.homepage_override_url"});
+ let label = getNotifyString({propName: "notificationButtonLabel",
+ stringName: "pu.notifyButton.label"});
+ let key = getNotifyString({propName: "notificationButtonAccessKey",
+ stringName: "pu.notifyButton.accesskey"});
+
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ let notifyBox = win.document.getElementById("high-priority-global-notificationbox");
+
+ let buttons = [
+ {
+ label: label,
+ accessKey: key,
+ popup: null,
+ callback: function(aNotificationBar, aButton) {
+ win.openUILinkIn(url, "tab");
+ }
+ }
+ ];
+
+ notifyBox.appendNotification(text, "post-update-notification",
+ null, notifyBox.PRIORITY_INFO_LOW,
+ buttons);
+ }
+
+ if (actions.indexOf("showAlert") == -1)
+ return;
+
+ let title = getNotifyString({propName: "alertTitle",
+ stringName: "puAlertTitle",
+ stringParams: [appName]});
+ let text = getNotifyString({propName: "alertText",
+ stringName: "puAlertText",
+ stringParams: [appName]});
+ let url = getNotifyString({propName: "alertURL",
+ prefName: "startup.homepage_override_url"});
+
+ function clickCallback(subject, topic, data) {
+ // This callback will be called twice but only once with this topic
+ if (topic != "alertclickcallback")
+ return;
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ win.openUILinkIn(data, "tab");
+ }
+
+ try {
+ // This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot
+ // be displayed per the idl.
+ AlertsService.showAlertNotification(null, title, text,
+ true, url, clickCallback);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ /**
+ * Initialize Places
+ * - imports the bookmarks html file if bookmarks database is empty, try to
+ * restore bookmarks from a JSON backup if the backend indicates that the
+ * database was corrupt.
+ *
+ * These prefs can be set up by the frontend:
+ *
+ * WARNING: setting these preferences to true will overwite existing bookmarks
+ *
+ * - browser.places.importBookmarksHTML
+ * Set to true will import the bookmarks.html file from the profile folder.
+ * - browser.places.smartBookmarksVersion
+ * Set during HTML import to indicate that Smart Bookmarks were created.
+ * Set to -1 to disable Smart Bookmarks creation.
+ * Set to 0 to restore current Smart Bookmarks.
+ * - browser.bookmarks.restore_default_bookmarks
+ * Set to true by safe-mode dialog to indicate we must restore default
+ * bookmarks.
+ */
+ _initPlaces: function BG__initPlaces(aInitialMigrationPerformed) {
+ // We must instantiate the history service since it will tell us if we
+ // need to import or restore bookmarks due to first-run, corruption or
+ // forced migration (due to a major schema change).
+ // If the database is corrupt or has been newly created we should
+ // import bookmarks.
+ let dbStatus = PlacesUtils.history.databaseStatus;
+ let importBookmarks = !aInitialMigrationPerformed &&
+ (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE ||
+ dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+
+ // Check if user or an extension has required to import bookmarks.html
+ let importBookmarksHTML = false;
+ try {
+ importBookmarksHTML =
+ Services.prefs.getBoolPref("browser.places.importBookmarksHTML");
+ if (importBookmarksHTML)
+ importBookmarks = true;
+ } catch (ex) {}
+
+ // Support legacy bookmarks.html format for apps that depend on that format.
+ let autoExportHTML = false;
+ try {
+ autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML");
+ } catch (ex) {} // Do not export.
+ if (autoExportHTML) {
+ // Sqlite.jsm and Places shutdown happen at profile-before-change, thus,
+ // to be on the safe side, this should run earlier.
+ AsyncShutdown.profileChangeTeardown.addBlocker(
+ "Places: export bookmarks.html",
+ () => BookmarkHTMLUtils.exportToFile(BookmarkHTMLUtils.defaultPath));
+ }
+
+ Task.spawn(function* () {
+ // Check if Safe Mode or the user has required to restore bookmarks from
+ // default profile's bookmarks.html
+ let restoreDefaultBookmarks = false;
+ try {
+ restoreDefaultBookmarks =
+ Services.prefs.getBoolPref("browser.bookmarks.restore_default_bookmarks");
+ if (restoreDefaultBookmarks) {
+ // Ensure that we already have a bookmarks backup for today.
+ yield this._backupBookmarks();
+ importBookmarks = true;
+ }
+ } catch (ex) {}
+
+ // This may be reused later, check for "=== undefined" to see if it has
+ // been populated already.
+ let lastBackupFile;
+
+ // If the user did not require to restore default bookmarks, or import
+ // from bookmarks.html, we will try to restore from JSON
+ if (importBookmarks && !restoreDefaultBookmarks && !importBookmarksHTML) {
+ // get latest JSON backup
+ lastBackupFile = yield PlacesBackups.getMostRecentBackup();
+ if (lastBackupFile) {
+ // restore from JSON backup
+ yield BookmarkJSONUtils.importFromFile(lastBackupFile, true);
+ importBookmarks = false;
+ }
+ else {
+ // We have created a new database but we don't have any backup available
+ importBookmarks = true;
+ if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) {
+ // If bookmarks.html is available in current profile import it...
+ importBookmarksHTML = true;
+ }
+ else {
+ // ...otherwise we will restore defaults
+ restoreDefaultBookmarks = true;
+ }
+ }
+ }
+
+ // If bookmarks are not imported, then initialize smart bookmarks. This
+ // happens during a common startup.
+ // Otherwise, if any kind of import runs, smart bookmarks creation should be
+ // delayed till the import operations has finished. Not doing so would
+ // cause them to be overwritten by the newly imported bookmarks.
+ if (!importBookmarks) {
+ // Now apply distribution customized bookmarks.
+ // This should always run after Places initialization.
+ try {
+ yield this._distributionCustomizer.applyBookmarks();
+ yield this.ensurePlacesDefaultQueriesInitialized();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ else {
+ // An import operation is about to run.
+ // Don't try to recreate smart bookmarks if autoExportHTML is true or
+ // smart bookmarks are disabled.
+ let smartBookmarksVersion = 0;
+ try {
+ smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion");
+ } catch (ex) {}
+ if (!autoExportHTML && smartBookmarksVersion != -1)
+ Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
+
+ let bookmarksUrl = null;
+ if (restoreDefaultBookmarks) {
+ // User wants to restore bookmarks.html file from default profile folder
+ bookmarksUrl = "chrome://browser/locale/bookmarks.html";
+ }
+ else if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) {
+ bookmarksUrl = OS.Path.toFileURI(BookmarkHTMLUtils.defaultPath);
+ }
+
+ if (bookmarksUrl) {
+ // Import from bookmarks.html file.
+ try {
+ yield BookmarkHTMLUtils.importFromURL(bookmarksUrl, true);
+ } catch (e) {
+ Cu.reportError("Bookmarks.html file could be corrupt. " + e);
+ }
+ try {
+ // Now apply distribution customized bookmarks.
+ // This should always run after Places initialization.
+ yield this._distributionCustomizer.applyBookmarks();
+ // Ensure that smart bookmarks are created once the operation is
+ // complete.
+ yield this.ensurePlacesDefaultQueriesInitialized();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+
+ }
+ else {
+ Cu.reportError(new Error("Unable to find bookmarks.html file."));
+ }
+
+ // Reset preferences, so we won't try to import again at next run
+ if (importBookmarksHTML)
+ Services.prefs.setBoolPref("browser.places.importBookmarksHTML", false);
+ if (restoreDefaultBookmarks)
+ Services.prefs.setBoolPref("browser.bookmarks.restore_default_bookmarks",
+ false);
+ }
+
+ // Initialize bookmark archiving on idle.
+ if (!this._bookmarksBackupIdleTime) {
+ this._bookmarksBackupIdleTime = BOOKMARKS_BACKUP_IDLE_TIME_SEC;
+
+ // If there is no backup, or the last bookmarks backup is too old, use
+ // a more aggressive idle observer.
+ if (lastBackupFile === undefined)
+ lastBackupFile = yield PlacesBackups.getMostRecentBackup();
+ if (!lastBackupFile) {
+ this._bookmarksBackupIdleTime /= 2;
+ }
+ else {
+ let lastBackupTime = PlacesBackups.getDateForFile(lastBackupFile);
+ let profileLastUse = Services.appinfo.replacedLockTime || Date.now();
+
+ // If there is a backup after the last profile usage date it's fine,
+ // regardless its age. Otherwise check how old is the last
+ // available backup compared to that session.
+ if (profileLastUse > lastBackupTime) {
+ let backupAge = Math.round((profileLastUse - lastBackupTime) / 86400000);
+ // Report the age of the last available backup.
+ try {
+ Services.telemetry
+ .getHistogramById("PLACES_BACKUPS_DAYSFROMLAST")
+ .add(backupAge);
+ } catch (ex) {
+ Cu.reportError(new Error("Unable to report telemetry."));
+ }
+
+ if (backupAge > BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS)
+ this._bookmarksBackupIdleTime /= 2;
+ }
+ }
+ this._idleService.addIdleObserver(this, this._bookmarksBackupIdleTime);
+ }
+
+ }.bind(this)).catch(ex => {
+ Cu.reportError(ex);
+ }).then(() => {
+ // NB: deliberately after the catch so that we always do this, even if
+ // we threw halfway through initializing in the Task above.
+ Services.obs.notifyObservers(null, "places-browser-init-complete", "");
+ });
+ },
+
+ /**
+ * If a backup for today doesn't exist, this creates one.
+ */
+ _backupBookmarks: function BG__backupBookmarks() {
+ return Task.spawn(function*() {
+ let lastBackupFile = yield PlacesBackups.getMostRecentBackup();
+ // Should backup bookmarks if there are no backups or the maximum
+ // interval between backups elapsed.
+ if (!lastBackupFile ||
+ new Date() - PlacesBackups.getDateForFile(lastBackupFile) > BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS * 86400000) {
+ let maxBackups = Services.prefs.getIntPref("browser.bookmarks.max_backups");
+ yield PlacesBackups.create(maxBackups);
+ }
+ });
+ },
+
+ /**
+ * Show the notificationBox for a locked places database.
+ */
+ _showPlacesLockedNotificationBox: function BG__showPlacesLockedNotificationBox() {
+ var applicationName = gBrandBundle.GetStringFromName("brandShortName");
+ var placesBundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties");
+ var title = placesBundle.GetStringFromName("lockPrompt.title");
+ var text = placesBundle.formatStringFromName("lockPrompt.text", [applicationName], 1);
+ var buttonText = placesBundle.GetStringFromName("lockPromptInfoButton.label");
+ var accessKey = placesBundle.GetStringFromName("lockPromptInfoButton.accessKey");
+
+ var helpTopic = "places-locked";
+ var url = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+ getService(Components.interfaces.nsIURLFormatter).
+ formatURLPref("app.support.baseURL");
+ url += helpTopic;
+
+ var win = RecentWindow.getMostRecentBrowserWindow();
+
+ var buttons = [
+ {
+ label: buttonText,
+ accessKey: accessKey,
+ popup: null,
+ callback: function(aNotificationBar, aButton) {
+ win.openUILinkIn(url, "tab");
+ }
+ }
+ ];
+
+ var notifyBox = win.gBrowser.getNotificationBox();
+ var notification = notifyBox.appendNotification(text, title, null,
+ notifyBox.PRIORITY_CRITICAL_MEDIUM,
+ buttons);
+ notification.persistence = -1; // Until user closes it
+ },
+
+ _showSyncStartedDoorhanger: function () {
+ let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+ let productName = gBrandBundle.GetStringFromName("brandShortName");
+ let title = bundle.GetStringFromName("syncStartNotification.title");
+ let body = bundle.formatStringFromName("syncStartNotification.body2",
+ [productName], 1);
+
+ let clickCallback = (subject, topic, data) => {
+ if (topic != "alertclickcallback")
+ return;
+ this._openPreferences("sync");
+ }
+ AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
+ },
+
+ _migrateUI: function BG__migrateUI() {
+ const UI_VERSION = 42;
+ const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
+
+ let currentUIVersion;
+ if (Services.prefs.prefHasUserValue("browser.migration.version")) {
+ currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
+ } else {
+ // This is a new profile, nothing to migrate.
+ Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
+ return;
+ }
+
+ if (currentUIVersion >= UI_VERSION)
+ return;
+
+ let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+
+ if (currentUIVersion < 9) {
+ // This code adds the customizable downloads buttons.
+ let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
+
+ // Since the Downloads button is located in the navigation bar by default,
+ // migration needs to happen only if the toolbar was customized using a
+ // previous UI version, and the button was not already placed on the
+ // toolbar manually.
+ if (currentset &&
+ currentset.indexOf("downloads-button") == -1) {
+ // The element is added either after the search bar or before the home
+ // button. As a last resort, the element is added just before the
+ // non-customizable window controls.
+ if (currentset.indexOf("search-container") != -1) {
+ currentset = currentset.replace(/(^|,)search-container($|,)/,
+ "$1search-container,downloads-button$2")
+ } else if (currentset.indexOf("home-button") != -1) {
+ currentset = currentset.replace(/(^|,)home-button($|,)/,
+ "$1downloads-button,home-button$2")
+ } else {
+ currentset = currentset.replace(/(^|,)window-controls($|,)/,
+ "$1downloads-button,window-controls$2")
+ }
+ xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
+ }
+ }
+
+ if (AppConstants.platform == "win") {
+ if (currentUIVersion < 10) {
+ // For Windows systems with display set to > 96dpi (i.e. systemDefaultScale
+ // will return a value > 1.0), we want to discard any saved full-zoom settings,
+ // as we'll now be scaling the content according to the system resolution
+ // scale factor (Windows "logical DPI" setting)
+ let sm = Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
+ if (sm.systemDefaultScale > 1.0) {
+ let cps2 = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ cps2.removeByName("browser.content.full-zoom", null);
+ }
+ }
+ }
+
+ if (currentUIVersion < 11) {
+ Services.prefs.clearUserPref("dom.disable_window_move_resize");
+ Services.prefs.clearUserPref("dom.disable_window_flip");
+ Services.prefs.clearUserPref("dom.event.contextmenu.enabled");
+ Services.prefs.clearUserPref("javascript.enabled");
+ Services.prefs.clearUserPref("permissions.default.image");
+ }
+
+ if (currentUIVersion < 14) {
+ // DOM Storage doesn't specially handle about: pages anymore.
+ let path = OS.Path.join(OS.Constants.Path.profileDir,
+ "chromeappsstore.sqlite");
+ OS.File.remove(path);
+ }
+
+ if (currentUIVersion < 16) {
+ xulStore.removeValue(BROWSER_DOCURL, "nav-bar", "collapsed");
+ }
+
+ // Insert the bookmarks-menu-button into the nav-bar if it isn't already
+ // there.
+ if (currentUIVersion < 17) {
+ let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
+ // Need to migrate only if toolbar is customized.
+ if (currentset) {
+ if (!currentset.includes("bookmarks-menu-button")) {
+ // The button isn't in the nav-bar, so let's look for an appropriate
+ // place to put it.
+ if (currentset.includes("bookmarks-menu-button-container")) {
+ currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
+ "$1bookmarks-menu-button$2");
+ } else if (currentset.includes("downloads-button")) {
+ currentset = currentset.replace(/(^|,)downloads-button($|,)/,
+ "$1bookmarks-menu-button,downloads-button$2");
+ } else if (currentset.includes("home-button")) {
+ currentset = currentset.replace(/(^|,)home-button($|,)/,
+ "$1bookmarks-menu-button,home-button$2");
+ } else {
+ // Just append.
+ currentset = currentset.replace(/(^|,)window-controls($|,)/,
+ "$1bookmarks-menu-button,window-controls$2")
+ }
+ xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
+ }
+ }
+ }
+
+ if (currentUIVersion < 18) {
+ // Remove iconsize and mode from all the toolbars
+ let toolbars = ["navigator-toolbox", "nav-bar", "PersonalToolbar",
+ "addon-bar", "TabsToolbar", "toolbar-menubar"];
+ for (let resourceName of ["mode", "iconsize"]) {
+ for (let toolbarId of toolbars) {
+ xulStore.removeValue(BROWSER_DOCURL, toolbarId, resourceName);
+ }
+ }
+ }
+
+ if (currentUIVersion < 19) {
+ let detector = null;
+ try {
+ detector = Services.prefs.getComplexValue("intl.charset.detector",
+ Ci.nsIPrefLocalizedString).data;
+ } catch (ex) {}
+ if (!(detector == "" ||
+ detector == "ja_parallel_state_machine" ||
+ detector == "ruprob" ||
+ detector == "ukprob")) {
+ // If the encoding detector pref value is not reachable from the UI,
+ // reset to default (varies by localization).
+ Services.prefs.clearUserPref("intl.charset.detector");
+ }
+ }
+
+ if (currentUIVersion < 20) {
+ // Remove persisted collapsed state from TabsToolbar.
+ xulStore.removeValue(BROWSER_DOCURL, "TabsToolbar", "collapsed");
+ }
+
+ if (currentUIVersion < 23) {
+ const kSelectedEnginePref = "browser.search.selectedEngine";
+ if (Services.prefs.prefHasUserValue(kSelectedEnginePref)) {
+ try {
+ let name = Services.prefs.getComplexValue(kSelectedEnginePref,
+ Ci.nsIPrefLocalizedString).data;
+ Services.search.currentEngine = Services.search.getEngineByName(name);
+ } catch (ex) {}
+ }
+ }
+
+ if (currentUIVersion < 24) {
+ // Reset homepage pref for users who have it set to start.mozilla.org
+ // or google.com/firefox.
+ const HOMEPAGE_PREF = "browser.startup.homepage";
+ if (Services.prefs.prefHasUserValue(HOMEPAGE_PREF)) {
+ const DEFAULT =
+ Services.prefs.getDefaultBranch(HOMEPAGE_PREF)
+ .getComplexValue("", Ci.nsIPrefLocalizedString).data;
+ let value =
+ Services.prefs.getComplexValue(HOMEPAGE_PREF, Ci.nsISupportsString);
+ let updated =
+ value.data.replace(/https?:\/\/start\.mozilla\.org[^|]*/i, DEFAULT)
+ .replace(/https?:\/\/(www\.)?google\.[a-z.]+\/firefox[^|]*/i,
+ DEFAULT);
+ if (updated != value.data) {
+ if (updated == DEFAULT) {
+ Services.prefs.clearUserPref(HOMEPAGE_PREF);
+ } else {
+ value.data = updated;
+ Services.prefs.setComplexValue(HOMEPAGE_PREF,
+ Ci.nsISupportsString, value);
+ }
+ }
+ }
+ }
+
+ if (currentUIVersion < 25) {
+ // Make sure the doNotTrack value conforms to the conversion from
+ // three-state to two-state. (This reverts a setting of "please track me"
+ // to the default "don't say anything").
+ try {
+ if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled") &&
+ Services.prefs.getIntPref("privacy.donottrackheader.value") != 1) {
+ Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
+ Services.prefs.clearUserPref("privacy.donottrackheader.value");
+ }
+ }
+ catch (ex) {}
+ }
+
+ if (currentUIVersion < 26) {
+ // Refactor urlbar suggestion preferences to make it extendable and
+ // allow new suggestion types (e.g: search suggestions).
+ let types = ["history", "bookmark", "openpage"];
+ let defaultBehavior = 0;
+ try {
+ defaultBehavior = Services.prefs.getIntPref("browser.urlbar.default.behavior");
+ } catch (ex) {}
+ try {
+ let autocompleteEnabled = Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled");
+ if (!autocompleteEnabled) {
+ defaultBehavior = -1;
+ }
+ } catch (ex) {}
+
+ // If the default behavior is:
+ // -1 - all new "...suggest.*" preferences will be false
+ // 0 - all new "...suggest.*" preferences will use the default values
+ // > 0 - all new "...suggest.*" preferences will be inherited
+ for (let type of types) {
+ let prefValue = defaultBehavior == 0;
+ if (defaultBehavior > 0) {
+ prefValue = !!(defaultBehavior & Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()]);
+ }
+ Services.prefs.setBoolPref("browser.urlbar.suggest." + type, prefValue);
+ }
+
+ // Typed behavior will be used only for results from history.
+ if (defaultBehavior != -1 &&
+ !!(defaultBehavior & Ci.mozIPlacesAutoComplete["BEHAVIOR_TYPED"])) {
+ Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", true);
+ }
+ }
+
+ if (currentUIVersion < 27) {
+ // Fix up document color use:
+ const kOldColorPref = "browser.display.use_document_colors";
+ if (Services.prefs.prefHasUserValue(kOldColorPref) &&
+ !Services.prefs.getBoolPref(kOldColorPref)) {
+ Services.prefs.setIntPref("browser.display.document_color_use", 2);
+ }
+ }
+
+ if (currentUIVersion < 29) {
+ let group = null;
+ try {
+ group = Services.prefs.getComplexValue("font.language.group",
+ Ci.nsIPrefLocalizedString);
+ } catch (ex) {}
+ if (group &&
+ ["tr", "x-baltic", "x-central-euro"].some(g => g == group.data)) {
+ // Latin groups were consolidated.
+ group.data = "x-western";
+ Services.prefs.setComplexValue("font.language.group",
+ Ci.nsIPrefLocalizedString, group);
+ }
+ }
+
+ if (currentUIVersion < 30) {
+ // Convert old devedition theme pref to lightweight theme storage
+ let lightweightThemeSelected = false;
+ let selectedThemeID = null;
+ try {
+ lightweightThemeSelected = Services.prefs.prefHasUserValue("lightweightThemes.selectedThemeID");
+ selectedThemeID = Services.prefs.getCharPref("lightweightThemes.selectedThemeID");
+ } catch (e) {}
+
+ let defaultThemeSelected = false;
+ try {
+ defaultThemeSelected = Services.prefs.getCharPref("general.skins.selectedSkin") == "classic/1.0";
+ } catch (e) {}
+
+ // If we are on the devedition channel, the devedition theme is on by
+ // default. But we need to handle the case where they didn't want it
+ // applied, and unapply the theme.
+ let userChoseToNotUseDeveditionTheme =
+ !defaultThemeSelected ||
+ (lightweightThemeSelected && selectedThemeID != "firefox-devedition@mozilla.org");
+
+ if (userChoseToNotUseDeveditionTheme && selectedThemeID == "firefox-devedition@mozilla.org") {
+ Services.prefs.setCharPref("lightweightThemes.selectedThemeID", "");
+ }
+
+ Services.prefs.clearUserPref("browser.devedition.showCustomizeButton");
+ }
+
+ if (currentUIVersion < 31) {
+ xulStore.removeValue(BROWSER_DOCURL, "bookmarks-menu-button", "class");
+ xulStore.removeValue(BROWSER_DOCURL, "home-button", "class");
+ }
+
+ if (currentUIVersion < 32) {
+ this._notifyNotificationsUpgrade().catch(Cu.reportError);
+ }
+
+ // version 35 migrated tab groups data.
+
+ if (currentUIVersion < 36) {
+ xulStore.removeValue("chrome://passwordmgr/content/passwordManager.xul",
+ "passwordCol",
+ "hidden");
+ }
+
+ if (currentUIVersion < 37) {
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+ }
+
+ if (currentUIVersion < 38) {
+ LoginHelper.removeLegacySignonFiles();
+ }
+
+ if (currentUIVersion < 39) {
+ // Remove the 'defaultset' value for all the toolbars
+ let toolbars = ["nav-bar", "PersonalToolbar",
+ "addon-bar", "TabsToolbar", "toolbar-menubar"];
+ for (let toolbarId of toolbars) {
+ xulStore.removeValue(BROWSER_DOCURL, toolbarId, "defaultset");
+ }
+ }
+
+ if (currentUIVersion < 40) {
+ const kOldSafeBrowsingPref = "browser.safebrowsing.enabled";
+ // Default value is set to true, a user pref means that the pref was
+ // set to false.
+ if (Services.prefs.prefHasUserValue(kOldSafeBrowsingPref) &&
+ !Services.prefs.getBoolPref(kOldSafeBrowsingPref)) {
+ Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled",
+ false);
+ // Should just remove support for the pref entirely, even if it's
+ // only in about:config
+ Services.prefs.clearUserPref(kOldSafeBrowsingPref);
+ }
+ }
+
+ if (currentUIVersion < 41) {
+ const Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+ Preferences.resetBranch("loop.");
+ }
+
+ if (currentUIVersion < 42) {
+ let backupFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ backupFile.append("tabgroups-session-backup.json");
+ OS.File.remove(backupFile.path, {ignoreAbsent: true}).catch(ex => Cu.reportError(ex));
+ }
+
+ // Update the migration version.
+ Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
+ },
+
+ _hasExistingNotificationPermission: function BG__hasExistingNotificationPermission() {
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ if (permission.type == "desktop-notification") {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ _notifyNotificationsUpgrade: Task.async(function* () {
+ if (!this._hasExistingNotificationPermission()) {
+ return;
+ }
+ yield this._firstWindowReady;
+ function clickCallback(subject, topic, data) {
+ if (topic != "alertclickcallback")
+ return;
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ win.openUILinkIn(data, "tab");
+ }
+ // Show the application icon for XUL notifications. We assume system-level
+ // notifications will include their own icon.
+ let imageURL = this._hasSystemAlertsService() ? "" :
+ "chrome://branding/content/about-logo.png";
+ let title = gBrowserBundle.GetStringFromName("webNotifications.upgradeTitle");
+ let text = gBrowserBundle.GetStringFromName("webNotifications.upgradeBody");
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "push#w_upgraded-notifications";
+
+ AlertsService.showAlertNotification(imageURL, title, text,
+ true, url, clickCallback);
+ }),
+
+ _hasSystemAlertsService: function() {
+ try {
+ return !!Cc["@mozilla.org/system-alerts-service;1"].getService(
+ Ci.nsIAlertsService);
+ } catch (e) {}
+ return false;
+ },
+
+ // ------------------------------
+ // public nsIBrowserGlue members
+ // ------------------------------
+
+ sanitize: function BG_sanitize(aParentWindow) {
+ this._sanitizer.sanitize(aParentWindow);
+ },
+
+ ensurePlacesDefaultQueriesInitialized: Task.async(function* () {
+ // This is the current smart bookmarks version, it must be increased every
+ // time they change.
+ // When adding a new smart bookmark below, its newInVersion property must
+ // be set to the version it has been added in. We will compare its value
+ // to users' smartBookmarksVersion and add new smart bookmarks without
+ // recreating old deleted ones.
+ const SMART_BOOKMARKS_VERSION = 8;
+ const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
+ const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
+
+ // TODO bug 399268: should this be a pref?
+ const MAX_RESULTS = 10;
+
+ // Get current smart bookmarks version. If not set, create them.
+ let smartBookmarksCurrentVersion = 0;
+ try {
+ smartBookmarksCurrentVersion = Services.prefs.getIntPref(SMART_BOOKMARKS_PREF);
+ } catch (ex) {}
+
+ // If version is current, or smart bookmarks are disabled, bail out.
+ if (smartBookmarksCurrentVersion == -1 ||
+ smartBookmarksCurrentVersion >= SMART_BOOKMARKS_VERSION) {
+ return;
+ }
+
+ try {
+ let menuIndex = 0;
+ let toolbarIndex = 0;
+ let bundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties");
+ let queryOptions = Ci.nsINavHistoryQueryOptions;
+
+ let smartBookmarks = {
+ MostVisited: {
+ title: bundle.GetStringFromName("mostVisitedTitle"),
+ url: "place:sort=" + queryOptions.SORT_BY_VISITCOUNT_DESCENDING +
+ "&maxResults=" + MAX_RESULTS,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ newInVersion: 1
+ },
+ RecentTags: {
+ title: bundle.GetStringFromName("recentTagsTitle"),
+ url: "place:type=" + queryOptions.RESULTS_AS_TAG_QUERY +
+ "&sort=" + queryOptions.SORT_BY_LASTMODIFIED_DESCENDING +
+ "&maxResults=" + MAX_RESULTS,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ newInVersion: 1
+ },
+ };
+
+ // Set current guid, parentGuid and index of existing Smart Bookmarks.
+ // We will use those to create a new version of the bookmark at the same
+ // position.
+ let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ for (let itemId of smartBookmarkItemIds) {
+ let queryId = PlacesUtils.annotations.getItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
+ if (queryId in smartBookmarks) {
+ // Known smart bookmark.
+ let smartBookmark = smartBookmarks[queryId];
+ smartBookmark.guid = yield PlacesUtils.promiseItemGuid(itemId);
+
+ if (!smartBookmark.url) {
+ yield PlacesUtils.bookmarks.remove(smartBookmark.guid);
+ continue;
+ }
+
+ let bm = yield PlacesUtils.bookmarks.fetch(smartBookmark.guid);
+ smartBookmark.parentGuid = bm.parentGuid;
+ smartBookmark.index = bm.index;
+ }
+ else {
+ // We don't remove old Smart Bookmarks because user could still
+ // find them useful, or could have personalized them.
+ // Instead we remove the Smart Bookmark annotation.
+ PlacesUtils.annotations.removeItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
+ }
+ }
+
+ for (let queryId of Object.keys(smartBookmarks)) {
+ let smartBookmark = smartBookmarks[queryId];
+
+ // We update or create only changed or new smart bookmarks.
+ // Also we respect user choices, so we won't try to create a smart
+ // bookmark if it has been removed.
+ if (smartBookmarksCurrentVersion > 0 &&
+ smartBookmark.newInVersion <= smartBookmarksCurrentVersion &&
+ !smartBookmark.guid || !smartBookmark.url)
+ continue;
+
+ // Remove old version of the smart bookmark if it exists, since it
+ // will be replaced in place.
+ if (smartBookmark.guid) {
+ yield PlacesUtils.bookmarks.remove(smartBookmark.guid);
+ }
+
+ // Create the new smart bookmark and store its updated guid.
+ if (!("index" in smartBookmark)) {
+ if (smartBookmark.parentGuid == PlacesUtils.bookmarks.toolbarGuid)
+ smartBookmark.index = toolbarIndex++;
+ else if (smartBookmark.parentGuid == PlacesUtils.bookmarks.menuGuid)
+ smartBookmark.index = menuIndex++;
+ }
+ smartBookmark = yield PlacesUtils.bookmarks.insert(smartBookmark);
+ let itemId = yield PlacesUtils.promiseItemId(smartBookmark.guid);
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ SMART_BOOKMARKS_ANNO,
+ queryId, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ // If we are creating all Smart Bookmarks from ground up, add a
+ // separator below them in the bookmarks menu.
+ if (smartBookmarksCurrentVersion == 0 &&
+ smartBookmarkItemIds.length == 0) {
+ let bm = yield PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: menuIndex });
+ // Don't add a separator if the menu was empty or there is one already.
+ if (bm && bm.type != PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+ yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: menuIndex });
+ }
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ } finally {
+ Services.prefs.setIntPref(SMART_BOOKMARKS_PREF, SMART_BOOKMARKS_VERSION);
+ Services.prefs.savePrefFile(null);
+ }
+ }),
+
+ /**
+ * Open preferences even if there are no open windows.
+ */
+ _openPreferences(...args) {
+ if (Services.appShell.hiddenDOMWindow.openPreferences) {
+ Services.appShell.hiddenDOMWindow.openPreferences(...args);
+ return;
+ }
+
+ let chromeWindow = RecentWindow.getMostRecentBrowserWindow();
+ chromeWindow.openPreferences(...args);
+ },
+
+ /**
+ * Called as an observer when Sync's "display URIs" notification is fired.
+ *
+ * We open the received URIs in background tabs.
+ */
+ _onDisplaySyncURIs: function _onDisplaySyncURIs(data) {
+ try {
+ // The payload is wrapped weirdly because of how Sync does notifications.
+ const URIs = data.wrappedJSObject.object;
+
+ const findWindow = () => RecentWindow.getMostRecentBrowserWindow({private: false});
+
+ // win can be null, but it's ok, we'll assign it later in openTab()
+ let win = findWindow();
+
+ const openTab = URI => {
+ let tab;
+ if (!win) {
+ Services.appShell.hiddenDOMWindow.open(URI.uri);
+ win = findWindow();
+ tab = win.gBrowser.tabs[0];
+ } else {
+ tab = win.gBrowser.addTab(URI.uri);
+ }
+ tab.setAttribute("attention", true);
+ return tab;
+ };
+
+ const firstTab = openTab(URIs[0]);
+ URIs.slice(1).forEach(URI => openTab(URI));
+
+ let title, body;
+ const deviceName = Weave.Service.clientsEngine.getClientName(URIs[0].clientId);
+ const bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+ if (URIs.length == 1) {
+ // Due to bug 1305895, tabs from iOS may not have device information, so
+ // we have separate strings to handle those cases. (See Also
+ // unnamedTabsArrivingNotificationNoDevice.body below)
+ if (deviceName) {
+ title = bundle.formatStringFromName("tabArrivingNotificationWithDevice.title", [deviceName], 1);
+ } else {
+ title = bundle.GetStringFromName("tabArrivingNotification.title");
+ }
+ // Use the page URL as the body. We strip the fragment and query to
+ // reduce size, and also format it the same way that the url bar would.
+ body = URIs[0].uri.replace(/[?#].*$/, "");
+ if (win.gURLBar) {
+ body = win.gURLBar.trimValue(body);
+ }
+ } else {
+ title = bundle.GetStringFromName("tabsArrivingNotification.title");
+ const allSameDevice = URIs.every(URI => URI.clientId == URIs[0].clientId);
+ const unknownDevice = allSameDevice && !deviceName;
+ let tabArrivingBody;
+ if (unknownDevice) {
+ tabArrivingBody = "unnamedTabsArrivingNotificationNoDevice.body";
+ } else if (allSameDevice) {
+ tabArrivingBody = "unnamedTabsArrivingNotification2.body";
+ } else {
+ tabArrivingBody = "unnamedTabsArrivingNotificationMultiple2.body"
+ }
+
+ body = bundle.GetStringFromName(tabArrivingBody);
+ body = PluralForm.get(URIs.length, body);
+ body = body.replace("#1", URIs.length);
+ body = body.replace("#2", deviceName);
+ }
+
+ const clickCallback = (subject, topic, data) => {
+ if (topic == "alertclickcallback") {
+ win.gBrowser.selectedTab = firstTab;
+ }
+ }
+
+ // Specify an icon because on Windows no icon is shown at the moment
+ let imageURL;
+ if (AppConstants.platform == "win") {
+ imageURL = "chrome://branding/content/icon64.png";
+ }
+ AlertsService.showAlertNotification(imageURL, title, body, true, null, clickCallback);
+ } catch (ex) {
+ Cu.reportError("Error displaying tab(s) received by Sync: " + ex);
+ }
+ },
+
+ _onDeviceDisconnected() {
+ let bundle = Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+ let title = bundle.GetStringFromName("deviceDisconnectedNotification.title");
+ let body = bundle.GetStringFromName("deviceDisconnectedNotification.body");
+
+ let clickCallback = (subject, topic, data) => {
+ if (topic != "alertclickcallback")
+ return;
+ this._openPreferences("sync");
+ }
+ AlertsService.showAlertNotification(null, title, body, true, null, clickCallback);
+ },
+
+ _handleFlashHang: function() {
+ ++this._flashHangCount;
+ if (this._flashHangCount < 2) {
+ return;
+ }
+ // protected mode only applies to win32
+ if (Services.appinfo.XPCOMABI != "x86-msvc") {
+ return;
+ }
+
+ if (Services.prefs.getBoolPref("dom.ipc.plugins.flash.disable-protected-mode")) {
+ return;
+ }
+ if (!Services.prefs.getBoolPref("browser.flash-protected-mode-flip.enable")) {
+ return;
+ }
+ if (Services.prefs.getBoolPref("browser.flash-protected-mode-flip.done")) {
+ return;
+ }
+ Services.prefs.setBoolPref("dom.ipc.plugins.flash.disable-protected-mode", true);
+ Services.prefs.setBoolPref("browser.flash-protected-mode-flip.done", true);
+
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (!win) {
+ return;
+ }
+ let productName = gBrandBundle.GetStringFromName("brandShortName");
+ let message = win.gNavigatorBundle.
+ getFormattedString("flashHang.message", [productName]);
+ let buttons = [{
+ label: win.gNavigatorBundle.getString("flashHang.helpButton.label"),
+ accessKey: win.gNavigatorBundle.getString("flashHang.helpButton.accesskey"),
+ callback: function() {
+ win.openUILinkIn("https://support.mozilla.org/kb/flash-protected-mode-autodisabled", "tab");
+ }
+ }];
+ let nb = win.document.getElementById("global-notificationbox");
+ nb.appendNotification(message, "flash-hang", null,
+ nb.PRIORITY_INFO_MEDIUM, buttons);
+ },
+
+ // for XPCOM
+ classID: Components.ID("{eab9012e-5f74-4cbc-b2b5-a590235513cc}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIBrowserGlue]),
+
+ // redefine the default factory for XPCOMUtils
+ _xpcom_factory: BrowserGlueServiceFactory,
+}
+
+/**
+ * ContentPermissionIntegration is responsible for showing the user
+ * simple permission prompts when content requests additional
+ * capabilities.
+ *
+ * While there are some built-in permission prompts, createPermissionPrompt
+ * can also be overridden by system add-ons or tests to provide new ones.
+ *
+ * This override ability is provided by Integration.jsm. See
+ * PermissionUI.jsm for an example of how to provide a new prompt
+ * from an add-on.
+ */
+const ContentPermissionIntegration = {
+ /**
+ * Creates a PermissionPrompt for a given permission type and
+ * nsIContentPermissionRequest.
+ *
+ * @param {string} type
+ * The type of the permission request from content. This normally
+ * matches the "type" field of an nsIContentPermissionType, but it
+ * can be something else if the permission does not use the
+ * nsIContentPermissionRequest model. Note that this type might also
+ * be different from the permission key used in the permissions
+ * database.
+ * Example: "geolocation"
+ * @param {nsIContentPermissionRequest} request
+ * The request for a permission from content.
+ * @return {PermissionPrompt} (see PermissionUI.jsm),
+ * or undefined if the type cannot be handled.
+ */
+ createPermissionPrompt(type, request) {
+ switch (type) {
+ case "geolocation": {
+ return new PermissionUI.GeolocationPermissionPrompt(request);
+ }
+ case "desktop-notification": {
+ return new PermissionUI.DesktopNotificationPermissionPrompt(request);
+ }
+ }
+ return undefined;
+ },
+};
+
+function ContentPermissionPrompt() {}
+
+ContentPermissionPrompt.prototype = {
+ classID: Components.ID("{d8903bf6-68d5-4e97-bcd1-e4d3012f721a}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
+
+ /**
+ * This implementation of nsIContentPermissionPrompt.prompt ensures
+ * that there's only one nsIContentPermissionType in the request,
+ * and that it's of type nsIContentPermissionType. Failing to
+ * satisfy either of these conditions will result in this method
+ * throwing NS_ERRORs. If the combined ContentPermissionIntegration
+ * cannot construct a prompt for this particular request, an
+ * NS_ERROR_FAILURE will be thrown.
+ *
+ * Any time an error is thrown, the nsIContentPermissionRequest is
+ * cancelled automatically.
+ *
+ * @param {nsIContentPermissionRequest} request
+ * The request that we're to show a prompt for.
+ */
+ prompt(request) {
+ try {
+ // Only allow exactly one permission request here.
+ let types = request.types.QueryInterface(Ci.nsIArray);
+ if (types.length != 1) {
+ throw Components.Exception(
+ "Expected an nsIContentPermissionRequest with only 1 type.",
+ Cr.NS_ERROR_UNEXPECTED);
+ }
+
+ let type = types.queryElementAt(0, Ci.nsIContentPermissionType).type;
+ let combinedIntegration =
+ Integration.contentPermission.getCombined(ContentPermissionIntegration);
+
+ let permissionPrompt =
+ combinedIntegration.createPermissionPrompt(type, request);
+ if (!permissionPrompt) {
+ throw Components.Exception(
+ `Failed to handle permission of type ${type}`,
+ Cr.NS_ERROR_FAILURE);
+ }
+
+ permissionPrompt.prompt();
+ } catch (ex) {
+ Cu.reportError(ex);
+ request.cancel();
+ throw ex;
+ }
+ },
+};
+
+var DefaultBrowserCheck = {
+ get OPTIONPOPUP() { return "defaultBrowserNotificationPopup" },
+ _setAsDefaultTimer: null,
+ _setAsDefaultButtonClickStartTime: 0,
+
+ closePrompt: function(aNode) {
+ if (this._notification) {
+ this._notification.close();
+ }
+ },
+
+ setAsDefault: function() {
+ let claimAllTypes = true;
+ let setAsDefaultError = false;
+ if (AppConstants.platform == "win") {
+ try {
+ // In Windows 8+, the UI for selecting default protocol is much
+ // nicer than the UI for setting file type associations. So we
+ // only show the protocol association screen on Windows 8+.
+ // Windows 8 is version 6.2.
+ let version = Services.sysinfo.getProperty("version");
+ claimAllTypes = (parseFloat(version) < 6.2);
+ } catch (ex) { }
+ }
+ try {
+ ShellService.setDefaultBrowser(claimAllTypes, false);
+
+ if (this._setAsDefaultTimer) {
+ this._setAsDefaultTimer.cancel();
+ }
+
+ this._setAsDefaultButtonClickStartTime = Math.floor(Date.now() / 1000);
+ this._setAsDefaultTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._setAsDefaultTimer.init(() => {
+ let isDefault = false;
+ let isDefaultError = false;
+ try {
+ isDefault = ShellService.isDefaultBrowser(true, false);
+ } catch (ex) {
+ isDefaultError = true;
+ }
+
+ let now = Math.floor(Date.now() / 1000);
+ let runTime = now - this._setAsDefaultButtonClickStartTime;
+ if (isDefault || runTime > 600) {
+ this._setAsDefaultTimer.cancel();
+ this._setAsDefaultTimer = null;
+ Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_TIME_TO_COMPLETION_SECONDS")
+ .add(runTime);
+ }
+ Services.telemetry.getHistogramById("BROWSER_IS_USER_DEFAULT_ERROR")
+ .add(isDefaultError);
+ }, 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
+ } catch (ex) {
+ setAsDefaultError = true;
+ Cu.reportError(ex);
+ }
+ // Here BROWSER_IS_USER_DEFAULT and BROWSER_SET_USER_DEFAULT_ERROR appear
+ // to be inverse of each other, but that is only because this function is
+ // called when the browser is set as the default. During startup we record
+ // the BROWSER_IS_USER_DEFAULT value without recording BROWSER_SET_USER_DEFAULT_ERROR.
+ Services.telemetry.getHistogramById("BROWSER_IS_USER_DEFAULT")
+ .add(!setAsDefaultError);
+ Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_ERROR")
+ .add(setAsDefaultError);
+ },
+
+ _createPopup: function(win, notNowStrings, neverStrings) {
+ let doc = win.document;
+ let popup = doc.createElement("menupopup");
+ popup.id = this.OPTIONPOPUP;
+
+ let notNowItem = doc.createElement("menuitem");
+ notNowItem.id = "defaultBrowserNotNow";
+ notNowItem.setAttribute("label", notNowStrings.label);
+ notNowItem.setAttribute("accesskey", notNowStrings.accesskey);
+ popup.appendChild(notNowItem);
+
+ let neverItem = doc.createElement("menuitem");
+ neverItem.id = "defaultBrowserNever";
+ neverItem.setAttribute("label", neverStrings.label);
+ neverItem.setAttribute("accesskey", neverStrings.accesskey);
+ popup.appendChild(neverItem);
+
+ popup.addEventListener("command", this);
+
+ let popupset = doc.getElementById("mainPopupSet");
+ popupset.appendChild(popup);
+ },
+
+ handleEvent: function(event) {
+ if (event.type == "command") {
+ if (event.target.id == "defaultBrowserNever") {
+ ShellService.shouldCheckDefaultBrowser = false;
+ }
+ this.closePrompt();
+ }
+ },
+
+ prompt: function(win) {
+ let useNotificationBar = Services.prefs.getBoolPref("browser.defaultbrowser.notificationbar");
+
+ let brandBundle = win.document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+
+ let shellBundle = win.document.getElementById("bundle_shell");
+ let buttonPrefix = "setDefaultBrowser" + (useNotificationBar ? "" : "Alert");
+ let yesButton = shellBundle.getFormattedString(buttonPrefix + "Confirm.label",
+ [brandShortName]);
+ let notNowButton = shellBundle.getString(buttonPrefix + "NotNow.label");
+
+ if (useNotificationBar) {
+ let promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage2",
+ [brandShortName]);
+ let optionsMessage = shellBundle.getString("setDefaultBrowserOptions.label");
+ let optionsKey = shellBundle.getString("setDefaultBrowserOptions.accesskey");
+
+ let neverLabel = shellBundle.getString("setDefaultBrowserNever.label");
+ let neverKey = shellBundle.getString("setDefaultBrowserNever.accesskey");
+
+ let yesButtonKey = shellBundle.getString("setDefaultBrowserConfirm.accesskey");
+ let notNowButtonKey = shellBundle.getString("setDefaultBrowserNotNow.accesskey");
+
+ let notificationBox = win.document.getElementById("high-priority-global-notificationbox");
+
+ this._createPopup(win, {
+ label: notNowButton,
+ accesskey: notNowButtonKey
+ }, {
+ label: neverLabel,
+ accesskey: neverKey
+ });
+
+ let buttons = [
+ {
+ label: yesButton,
+ accessKey: yesButtonKey,
+ callback: () => {
+ this.setAsDefault();
+ this.closePrompt();
+ }
+ },
+ {
+ label: optionsMessage,
+ accessKey: optionsKey,
+ popup: this.OPTIONPOPUP
+ }
+ ];
+
+ let iconPixels = win.devicePixelRatio > 1 ? "32" : "16";
+ let iconURL = "chrome://branding/content/icon" + iconPixels + ".png";
+ const priority = notificationBox.PRIORITY_WARNING_HIGH;
+ let callback = this._onNotificationEvent.bind(this);
+ this._notification = notificationBox.appendNotification(promptMessage, "default-browser",
+ iconURL, priority, buttons,
+ callback);
+ } else {
+ // Modal prompt
+ let promptTitle = shellBundle.getString("setDefaultBrowserTitle");
+ let promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage",
+ [brandShortName]);
+ let askLabel = shellBundle.getFormattedString("setDefaultBrowserDontAsk",
+ [brandShortName]);
+
+ let ps = Services.prompt;
+ let shouldAsk = { value: true };
+ let buttonFlags = (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) +
+ (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1) +
+ ps.BUTTON_POS_0_DEFAULT;
+ let rv = ps.confirmEx(win, promptTitle, promptMessage, buttonFlags,
+ yesButton, notNowButton, null, askLabel, shouldAsk);
+ if (rv == 0) {
+ this.setAsDefault();
+ } else if (!shouldAsk.value) {
+ ShellService.shouldCheckDefaultBrowser = false;
+ }
+
+ try {
+ let resultEnum = rv * 2 + shouldAsk.value;
+ Services.telemetry.getHistogramById("BROWSER_SET_DEFAULT_RESULT")
+ .add(resultEnum);
+ } catch (ex) { /* Don't break if Telemetry is acting up. */ }
+ }
+ },
+
+ _onNotificationEvent: function(eventType) {
+ if (eventType == "removed") {
+ let doc = this._notification.ownerDocument;
+ let popup = doc.getElementById(this.OPTIONPOPUP);
+ popup.removeEventListener("command", this);
+ popup.remove();
+ delete this._notification;
+ }
+ },
+};
+
+var E10SAccessibilityCheck = {
+ // tracks when an a11y init observer fires prior to the
+ // first window being opening.
+ _wantsPrompt: false,
+
+ init: function() {
+ Services.obs.addObserver(this, "a11y-init-or-shutdown", true);
+ Services.obs.addObserver(this, "quit-application-granted", true);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
+
+ get forcedOn() {
+ try {
+ return Services.prefs.getBoolPref("browser.tabs.remote.force-enable");
+ } catch (e) {}
+ return false;
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "quit-application-granted":
+ // Tag the profile with a11y load state. We use this in nsAppRunner
+ // checks on the next start.
+ Services.prefs.setBoolPref("accessibility.loadedInLastSession",
+ Services.appinfo.accessibilityEnabled);
+ break;
+ case "a11y-init-or-shutdown":
+ if (data == "1") {
+ // Update this so users can check this while still running
+ Services.prefs.setBoolPref("accessibility.loadedInLastSession", true);
+ this._showE10sAccessibilityWarning();
+ }
+ break;
+ }
+ },
+
+ onWindowsRestored: function() {
+ if (this._wantsPrompt) {
+ this._wantsPrompt = false;
+ this._showE10sAccessibilityWarning();
+ }
+ },
+
+ _warnedAboutAccessibility: false,
+
+ _showE10sAccessibilityWarning: function() {
+ // We don't prompt about a11y incompat if e10s is off.
+ if (!Services.appinfo.browserTabsRemoteAutostart) {
+ return;
+ }
+
+ // If the user set the forced pref and it's true, ignore a11y init.
+ // If the pref doesn't exist or if it's false, prompt.
+ if (this.forcedOn) {
+ return;
+ }
+
+ // Only prompt once per session
+ if (this._warnedAboutAccessibility) {
+ return;
+ }
+ this._warnedAboutAccessibility = true;
+
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ if (!win || !win.gBrowser || !win.gBrowser.selectedBrowser) {
+ Services.console.logStringMessage(
+ "Accessibility support is partially disabled due to compatibility issues with new features.");
+ this._wantsPrompt = true;
+ this._warnedAboutAccessibility = false;
+ return;
+ }
+ let browser = win.gBrowser.selectedBrowser;
+
+ // We disable a11y for content and prompt on the chrome side letting
+ // a11y users know they need to disable e10s and restart.
+ let promptMessage = win.gNavigatorBundle.getFormattedString(
+ "e10s.accessibilityNotice.mainMessage2",
+ [gBrandBundle.GetStringFromName("brandShortName")]
+ );
+ let notification;
+ let restartCallback = function() {
+ 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
+ }
+ // Restart the browser
+ Services.startup.quit(Services.startup.eAttemptQuit | Services.startup.eRestart);
+ };
+ // main option: an Ok button, keeps running with content accessibility disabled
+ let mainAction = {
+ label: win.gNavigatorBundle.getString("e10s.accessibilityNotice.acceptButton.label"),
+ accessKey: win.gNavigatorBundle.getString("e10s.accessibilityNotice.acceptButton.accesskey"),
+ callback: function () {
+ // If the user invoked the button option remove the notification,
+ // otherwise keep the alert icon around in the address bar.
+ notification.remove();
+ },
+ dismiss: true
+ };
+ // secondary option: a restart now button. When we restart e10s will be disabled due to
+ // accessibility having been loaded in the previous session.
+ let secondaryActions = [{
+ label: win.gNavigatorBundle.getString("e10s.accessibilityNotice.enableAndRestart.label"),
+ accessKey: win.gNavigatorBundle.getString("e10s.accessibilityNotice.enableAndRestart.accesskey"),
+ callback: restartCallback,
+ }];
+ let options = {
+ popupIconURL: "chrome://browser/skin/e10s-64@2x.png",
+ learnMoreURL: Services.urlFormatter.formatURLPref("app.support.e10sAccessibilityUrl"),
+ persistWhileVisible: true,
+ hideNotNow: true,
+ };
+
+ notification =
+ win.PopupNotifications.show(browser, "a11y_enabled_with_e10s",
+ promptMessage, null, mainAction,
+ secondaryActions, options);
+ },
+};
+
+var components = [BrowserGlue, ContentPermissionPrompt];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
+
+
+// Listen for UITour messages.
+// Do it here instead of the UITour module itself so that the UITour module is lazy loaded
+// when the first message is received.
+var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+globalMM.addMessageListener("UITour:onPageEvent", function(aMessage) {
+ UITour.onPageEvent(aMessage, aMessage.data);
+});
diff --git a/browser/components/nsIBrowserGlue.idl b/browser/components/nsIBrowserGlue.idl
new file mode 100644
index 000000000..01f164f5b
--- /dev/null
+++ b/browser/components/nsIBrowserGlue.idl
@@ -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 "nsISupports.idl"
+
+interface nsIDOMWindow;
+
+/**
+ * nsIBrowserGlue is a dirty and rather fluid interface to host shared utility
+ * methods used by browser UI code, but which are not local to a browser window.
+ * The component implementing this interface is meant to be a singleton
+ * (service) and should progressively replace some of the shared "glue" code
+ * scattered in browser/base/content (e.g. bits of utilOverlay.js,
+ * contentAreaUtils.js, globalOverlay.js, browser.js), avoiding dynamic
+ * inclusion and initialization of a ton of JS code for *each* window.
+ * Dued to its nature and origin, this interface won't probably be the most
+ * elegant or stable in the mozilla codebase, but its aim is rather pragmatic:
+ * 1) reducing the performance overhead which affects browser window load;
+ * 2) allow global hooks (e.g. startup and shutdown observers) which survive
+ * browser windows to accomplish browser-related activities, such as shutdown
+ * sanitization (see bug #284086)
+ *
+ */
+
+[scriptable, uuid(b0e7c156-d00c-4605-a77d-27c7418f23ae)]
+interface nsIBrowserGlue : nsISupports
+{
+ /**
+ * Deletes privacy sensitive data according to user preferences
+ *
+ * @param aParentWindow an optionally null window which is the parent of the
+ * sanitization dialog
+ *
+ */
+ void sanitize(in nsIDOMWindow aParentWindow);
+};
diff --git a/browser/components/nsIBrowserHandler.idl b/browser/components/nsIBrowserHandler.idl
new file mode 100644
index 000000000..74292f9d9
--- /dev/null
+++ b/browser/components/nsIBrowserHandler.idl
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICommandLine;
+
+[scriptable, uuid(8D3F5A9D-118D-4548-A137-CF7718679069)]
+interface nsIBrowserHandler : nsISupports
+{
+ attribute AUTF8String startPage;
+ attribute AUTF8String defaultArgs;
+
+ /**
+ * Extract the width and height specified on the command line, if present.
+ * @return A feature string with a prepended comma, e.g. ",width=500,height=400"
+ */
+ AUTF8String getFeatures(in nsICommandLine aCmdLine);
+};
diff --git a/browser/components/originattributes/moz.build b/browser/components/originattributes/moz.build
new file mode 100644
index 000000000..ea5943ea1
--- /dev/null
+++ b/browser/components/originattributes/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser/browser.ini',
+]
+
+MOCHITEST_MANIFESTS += [
+ 'test/mochitest/mochitest.ini'
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'OriginAttributes')
diff --git a/browser/components/originattributes/test/browser/.eslintrc.js b/browser/components/originattributes/test/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/originattributes/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/originattributes/test/browser/browser.ini b/browser/components/originattributes/test/browser/browser.ini
new file mode 100644
index 000000000..61f674377
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -0,0 +1,64 @@
+[DEFAULT]
+tags = usercontextid firstpartyisolation originattributes
+support-files =
+ dummy.html
+ file_broadcastChannel.html
+ file_broadcastChanneliFrame.html
+ file_cache.html
+ file_favicon.html
+ file_favicon.png
+ file_favicon.png^headers^
+ file_favicon_cache.html
+ file_favicon_cache.png
+ file_favicon_thirdParty.html
+ file_firstPartyBasic.html
+ file_sharedworker.html
+ file_sharedworker.js
+ file_thirdPartyChild.audio.ogg
+ file_thirdPartyChild.embed.png
+ file_thirdPartyChild.fetch.html
+ file_thirdPartyChild.iframe.html
+ file_thirdPartyChild.img.png
+ file_thirdPartyChild.import.js
+ file_thirdPartyChild.link.css
+ file_thirdPartyChild.object.png
+ file_thirdPartyChild.request.html
+ file_thirdPartyChild.script.js
+ file_thirdPartyChild.sharedworker.js
+ file_thirdPartyChild.track.vtt
+ file_thirdPartyChild.video.ogv
+ file_thirdPartyChild.worker.fetch.html
+ file_thirdPartyChild.worker.js
+ file_thirdPartyChild.worker.request.html
+ file_thirdPartyChild.worker.xhr.html
+ file_thirdPartyChild.xhr.html
+ head.js
+ test.js
+ test.js^headers^
+ test.html
+ test2.html
+ test2.js
+ test2.js^headers^
+ test_firstParty.html
+ test_firstParty_cookie.html
+ test_firstParty_html_redirect.html
+ test_firstParty_http_redirect.html
+ test_firstParty_http_redirect.html^headers^
+ test_firstParty_iframe_http_redirect.html
+ test_firstParty_postMessage.html
+ window.html
+ worker_blobify.js
+ worker_deblobify.js
+
+[browser_broadcastChannel.js]
+[browser_cache.js]
+[browser_cookieIsolation.js]
+[browser_favicon_firstParty.js]
+[browser_favicon_userContextId.js]
+[browser_firstPartyIsolation.js]
+[browser_localStorageIsolation.js]
+[browser_blobURLIsolation.js]
+[browser_imageCacheIsolation.js]
+[browser_sharedworker.js]
+[browser_httpauth.js]
+[browser_clientAuth.js]
diff --git a/browser/components/originattributes/test/browser/browser_blobURLIsolation.js b/browser/components/originattributes/test/browser/browser_blobURLIsolation.js
new file mode 100644
index 000000000..1d1b7c8e1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_blobURLIsolation.js
@@ -0,0 +1,97 @@
+/**
+ * Bug 1264573 - A test case for blob url isolation.
+ */
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/" +
+ "originattributes/test/browser/file_firstPartyBasic.html";
+const SCRIPT_WORKER_BLOBIFY = "worker_blobify.js";
+const SCRIPT_WORKER_DEBLOBIFY = "worker_deblobify.js";
+
+function page_blobify(browser, input) {
+ return ContentTask.spawn(browser, input, function(input) {
+ return { blobURL: content.URL.createObjectURL(new content.Blob([input])) };
+ });
+}
+
+function page_deblobify(browser, blobURL) {
+ return ContentTask.spawn(browser, blobURL, function* (blobURL) {
+ if ("error" in blobURL) {
+ return blobURL;
+ }
+ blobURL = blobURL.blobURL;
+
+ function blobURLtoBlob(blobURL) {
+ return new content.Promise(function (resolve) {
+ let xhr = new content.XMLHttpRequest();
+ xhr.open("GET", blobURL, true);
+ xhr.onload = function () {
+ resolve(xhr.response);
+ };
+ xhr.onerror = function () {
+ resolve("xhr error");
+ };
+ xhr.responseType = "blob";
+ xhr.send();
+ });
+ }
+
+ function blobToString(blob) {
+ return new content.Promise(function (resolve) {
+ let fileReader = new content.FileReader();
+ fileReader.onload = function () {
+ resolve(fileReader.result);
+ };
+ fileReader.readAsText(blob);
+ });
+ }
+
+ let blob = yield blobURLtoBlob(blobURL);
+ if (blob == "xhr error") {
+ return "xhr error";
+ }
+
+ return yield blobToString(blob);
+ });
+}
+
+function workerIO(browser, scriptFile, message) {
+ return ContentTask.spawn(browser, {scriptFile, message}, function* (args) {
+ let worker = new content.Worker(args.scriptFile);
+ let promise = new content.Promise(function(resolve) {
+ let listenFunction = function(event) {
+ worker.removeEventListener("message", listenFunction, false);
+ worker.terminate();
+ resolve(event.data);
+ };
+ worker.addEventListener("message", listenFunction, false);
+ });
+ worker.postMessage(args.message);
+ return yield promise;
+ });
+}
+
+let worker_blobify = (browser, input) => workerIO(browser, SCRIPT_WORKER_BLOBIFY, input);
+let worker_deblobify = (browser, blobURL) => workerIO(browser, SCRIPT_WORKER_DEBLOBIFY, blobURL);
+
+function doTest(blobify, deblobify) {
+ let blobURL = null;
+ return function* (browser) {
+ if (blobURL === null) {
+ let input = Math.random().toString();
+ blobURL = yield blobify(browser, input);
+ return input;
+ }
+ let result = yield deblobify(browser, blobURL);
+ blobURL = null;
+ return result;
+ }
+}
+
+let tests = [];
+for (let blobify of [page_blobify, worker_blobify]) {
+ for (let deblobify of [page_deblobify, worker_deblobify]) {
+ tests.push(doTest(blobify, deblobify));
+ }
+}
+
+IsolationTestTools.runTests(TEST_PAGE, tests);
diff --git a/browser/components/originattributes/test/browser/browser_broadcastChannel.js b/browser/components/originattributes/test/browser/browser_broadcastChannel.js
new file mode 100644
index 000000000..3a2bd7405
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_broadcastChannel.js
@@ -0,0 +1,47 @@
+/*
+ * Bug 1264571 - A test case of broadcast channels for first party isolation.
+ */
+
+const TEST_DOMAIN = "http://example.net/";
+const TEST_PATH = TEST_DOMAIN + "browser/browser/components/originattributes/test/browser/";
+const TEST_PAGE = TEST_PATH + "file_broadcastChannel.html";
+
+function* doTest(aBrowser) {
+ let response = yield ContentTask.spawn(aBrowser, null, function* () {
+
+ let displayItem = content.document.getElementById("display");
+
+ // If there is nothing in the 'display', we will try to send a message to
+ // the broadcast channel and wait until this message has been delivered.
+ // The way that how we make sure the message is delivered is based on an
+ // iframe which will reply everything it receives from the broadcast channel
+ // to the current window through the postMessage. So, we can know that the
+ // boradcast message is sent successfully when the window receives a message
+ // from the iframe.
+ if (displayItem.innerHTML === "") {
+ let data = Math.random().toString();
+
+ let receivedData = yield new Promise(resolve => {
+ let listenFunc = event => {
+ content.removeEventListener("message", listenFunc);
+ resolve(event.data);
+ };
+
+ let bc = new content.BroadcastChannel("testBroadcastChannel");
+
+ content.addEventListener("message", listenFunc, false);
+ bc.postMessage(data);
+ });
+
+ is(receivedData, data, "The value should be the same.");
+
+ return receivedData;
+ }
+
+ return displayItem.innerHTML;
+ });
+
+ return response;
+}
+
+IsolationTestTools.runTests(TEST_PAGE, doTest);
diff --git a/browser/components/originattributes/test/browser/browser_cache.js b/browser/components/originattributes/test/browser/browser_cache.js
new file mode 100644
index 000000000..d5f3a8f58
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_cache.js
@@ -0,0 +1,259 @@
+/*
+ * Bug 1264577 - A test case for testing caches of various submodules.
+ * This test case will load two pages that each page loads various resources
+ * within the same third party domain for the same originAttributes or different
+ * originAttributes. And then, it verifies the number of cache entries and
+ * the originAttributes of loading channels. If these two pages belong to
+ * the same originAttributes, the number of cache entries for a certain
+ * resource would be one. Otherwise, it would be two.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
+let protocolProxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService(Ci.nsIProtocolProxyService);
+
+const TEST_DOMAIN = "http://example.net";
+const TEST_PATH = "/browser/browser/components/originattributes/test/browser/";
+const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "file_cache.html";
+
+let suffixes = ["iframe.html", "link.css", "script.js", "img.png", "object.png",
+ "embed.png", "xhr.html", "worker.xhr.html", "audio.ogg",
+ "video.ogv", "track.vtt",
+ "fetch.html", "worker.fetch.html",
+ "request.html", "worker.request.html",
+ "import.js", "worker.js", "sharedworker.js"];
+
+// A random value for isolating video/audio elements across different tests.
+let randomSuffix;
+
+function clearAllImageCaches() {
+ let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
+ .getService(SpecialPowers.Ci.imgITools);
+ let imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function cacheDataForContext(loadContextInfo) {
+ return new Promise(resolve => {
+ let cacheEntries = [];
+ let cacheVisitor = {
+ onCacheStorageInfo(num, consumption) {},
+ onCacheEntryInfo(uri, idEnhance) {
+ cacheEntries.push({ uri: uri,
+ idEnhance: idEnhance });
+ },
+ onCacheEntryVisitCompleted() {
+ resolve(cacheEntries);
+ },
+ QueryInterface(iid) {
+ if (iid.equals(Ci.nsICacheStorageVisitor))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ };
+ // Visiting the disk cache also visits memory storage so we do not
+ // need to use Services.cache2.memoryCacheStorage() here.
+ let storage = Services.cache2.diskCacheStorage(loadContextInfo, false);
+ storage.asyncVisitStorage(cacheVisitor, true);
+ });
+}
+
+let countMatchingCacheEntries = function (cacheEntries, domain, fileSuffix) {
+ return cacheEntries.map(entry => entry.uri.asciiSpec)
+ .filter(spec => spec.includes(domain))
+ .filter(spec => spec.includes("file_thirdPartyChild." + fileSuffix))
+ .length;
+};
+
+function observeChannels(onChannel) {
+ // We use a dummy proxy filter to catch all channels, even those that do not
+ // generate an "http-on-modify-request" notification, such as link preconnects.
+ let proxyFilter = {
+ applyFilter : function (aProxyService, aChannel, aProxy) {
+ // We have the channel; provide it to the callback.
+ onChannel(aChannel);
+ // Pass on aProxy unmodified.
+ return aProxy;
+ }
+ };
+ protocolProxyService.registerChannelFilter(proxyFilter, 0);
+ // Return the stop() function:
+ return () => protocolProxyService.unregisterChannelFilter(proxyFilter);
+}
+
+function startObservingChannels(aMode) {
+ let stopObservingChannels = observeChannels(function (channel) {
+ let originalURISpec = channel.originalURI.spec;
+ if (originalURISpec.includes("example.net")) {
+ let loadInfo = channel.loadInfo;
+
+ switch (aMode) {
+ case TEST_MODE_FIRSTPARTY:
+ ok(loadInfo.originAttributes.firstPartyDomain === "example.com" ||
+ loadInfo.originAttributes.firstPartyDomain === "example.org",
+ "first party for " + originalURISpec + " is " + loadInfo.originAttributes.firstPartyDomain);
+ break;
+
+ case TEST_MODE_NO_ISOLATION:
+ ok(ChromeUtils.isOriginAttributesEqual(loadInfo.originAttributes, ChromeUtils.fillNonDefaultOriginAttributes()),
+ "OriginAttributes for " + originalURISpec + " is default.");
+ break;
+
+ case TEST_MODE_CONTAINERS:
+ ok(loadInfo.originAttributes.userContextId === 1 ||
+ loadInfo.originAttributes.userContextId === 2,
+ "userContextId for " + originalURISpec + " is " + loadInfo.originAttributes.userContextId);
+ break;
+
+ default:
+ ok(false, "Unknown test mode.");
+ }
+ }
+ });
+ return stopObservingChannels;
+}
+
+let stopObservingChannels;
+
+// The init function, which clears image and network caches, and generates
+// the random value for isolating video and audio elements across different
+// test runs.
+function* doInit(aMode) {
+ yield SpecialPowers.pushPrefEnv({"set": [["network.predictor.enabled", false],
+ ["network.predictor.enable-prefetch", false]]});
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ randomSuffix = Math.random();
+ stopObservingChannels = startObservingChannels(aMode);
+}
+
+// In the test function, we dynamically generate the video and audio element,
+// and assign a random suffix to their URL to isolate them across different
+// test runs.
+function* doTest(aBrowser) {
+
+ let argObj = {
+ randomSuffix: randomSuffix,
+ urlPrefix: TEST_DOMAIN + TEST_PATH,
+ };
+
+ yield ContentTask.spawn(aBrowser, argObj, function* (arg) {
+ let videoURL = arg.urlPrefix + "file_thirdPartyChild.video.ogv";
+ let audioURL = arg.urlPrefix + "file_thirdPartyChild.audio.ogg";
+ let trackURL = arg.urlPrefix + "file_thirdPartyChild.track.vtt";
+ let URLSuffix = "?r=" + arg.randomSuffix;
+
+ // Create the audio and video elements.
+ let audio = content.document.createElement('audio');
+ let video = content.document.createElement('video');
+ let audioSource = content.document.createElement('source');
+ let audioTrack = content.document.createElement('track');
+
+ // Append the audio and track element into the body, and wait until they're finished.
+ yield new Promise(resolve => {
+ let audioLoaded = false;
+ let trackLoaded = false;
+
+ let audioListener = () => {
+ audio.removeEventListener("canplaythrough", audioListener);
+
+ audioLoaded = true;
+ if (audioLoaded && trackLoaded) {
+ resolve();
+ }
+ };
+
+ let trackListener = () => {
+ audioTrack.removeEventListener("load", trackListener);
+
+ trackLoaded = true;
+ if (audioLoaded && trackLoaded) {
+ resolve();
+ }
+ };
+
+ // Add the event listeners before everything in case we lose events.
+ audioTrack.addEventListener("load", trackListener, false);
+ audio.addEventListener("canplaythrough", audioListener, false);
+
+ // Assign attributes for the audio element.
+ audioSource.setAttribute("src", audioURL + URLSuffix);
+ audioSource.setAttribute("type", "audio/ogg");
+ audioTrack.setAttribute("src", trackURL);
+ audioTrack.setAttribute("kind", "subtitles");
+
+ audio.appendChild(audioSource);
+ audio.appendChild(audioTrack);
+ audio.autoplay = true;
+
+ content.document.body.appendChild(audio);
+ });
+
+ // Append the video element into the body, and wait until it's finished.
+ yield new Promise(resolve => {
+ let listener = () => {
+ video.removeEventListener("canplaythrough", listener);
+ resolve();
+ };
+
+ // Add the event listener before everything in case we lose the event.
+ video.addEventListener("canplaythrough", listener, false);
+
+ // Assign attributes for the video element.
+ video.setAttribute("src", videoURL + URLSuffix);
+ video.setAttribute("type", "video/ogg");
+
+ content.document.body.appendChild(video);
+ });
+ });
+
+ return 0;
+}
+
+// The check function, which checks the number of cache entries.
+function* doCheck(aShouldIsolate, aInputA, aInputB) {
+ let expectedEntryCount = 1;
+ let data = [];
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.default));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.private));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, {})));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { userContextId: 1 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { userContextId: 1 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { userContextId: 2 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { userContextId: 2 })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { firstPartyDomain: "example.com" })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { firstPartyDomain: "example.com" })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(false, { firstPartyDomain: "example.org" })));
+ data = data.concat(yield cacheDataForContext(LoadContextInfo.custom(true, { firstPartyDomain: "example.org" })));
+
+ if (aShouldIsolate) {
+ expectedEntryCount = 2;
+ }
+
+ for (let suffix of suffixes) {
+ let foundEntryCount = countMatchingCacheEntries(data, "example.net", suffix);
+ let result = (expectedEntryCount === foundEntryCount);
+ ok(result, "Cache entries expected for " + suffix + ": " + expectedEntryCount +
+ ", and found " + foundEntryCount);
+ }
+
+ stopObservingChannels();
+ stopObservingChannels = undefined;
+ return true;
+}
+
+let testArgs = {
+ url: TEST_PAGE,
+ firstFrameSetting: DEFAULT_FRAME_SETTING,
+ secondFrameSetting: [TEST_TYPE_FRAME],
+};
+
+IsolationTestTools.runTests(testArgs, doTest, doCheck, doInit);
diff --git a/browser/components/originattributes/test/browser/browser_clientAuth.js b/browser/components/originattributes/test/browser/browser_clientAuth.js
new file mode 100644
index 000000000..48961dce0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_clientAuth.js
@@ -0,0 +1,44 @@
+let certCached = true;
+let secondTabStarted = false;
+
+function onCertDialogLoaded(subject) {
+ certCached = false;
+ // Click OK.
+ subject.acceptDialog();
+}
+
+Services.obs.addObserver(onCertDialogLoaded, "cert-dialog-loaded", false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(onCertDialogLoaded, "cert-dialog-loaded");
+});
+
+function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["security.default_personal_cert", "Ask Every Time"]]
+ });
+}
+
+function getResult() {
+ // The first tab always returns true.
+ if (!secondTabStarted) {
+ certCached = true;
+ secondTabStarted = true;
+ return true;
+ }
+
+ // The second tab returns true if the cert is cached, so it will be different
+ // from the result of the first tab, and considered isolated.
+ let ret = certCached;
+ certCached = true;
+ secondTabStarted = false;
+ return ret;
+}
+
+// aGetResultImmediately must be true because we need to get the result before
+// the next tab is opened.
+IsolationTestTools.runTests("https://requireclientcert.example.com",
+ getResult,
+ null, // aCompareResultFunc
+ setup, // aBeginFunc
+ true); // aGetResultImmediately
diff --git a/browser/components/originattributes/test/browser/browser_cookieIsolation.js b/browser/components/originattributes/test/browser/browser_cookieIsolation.js
new file mode 100644
index 000000000..6259723ba
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_cookieIsolation.js
@@ -0,0 +1,31 @@
+/**
+ * Bug 1312541 - A test case for document.cookie isolation.
+ */
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/" +
+ "originattributes/test/browser/file_firstPartyBasic.html";
+
+// Use a random key so we don't access it in later tests.
+const key = "key" + Math.random().toString();
+const re = new RegExp(key + "=([0-9\.]+)");
+
+// Define the testing function
+function* doTest(aBrowser) {
+ return yield ContentTask.spawn(aBrowser, {key, re},
+ function ({key, re}) {
+ let result = re.exec(content.document.cookie);
+ if (result) {
+ return result[1];
+ }
+ // No value is found, so we create one.
+ let value = Math.random().toString();
+ content.document.cookie = key + "=" + value;
+ return value;
+ });
+}
+
+registerCleanupFunction(() => {
+ Services.cookies.removeAll();
+});
+
+IsolationTestTools.runTests(TEST_PAGE, doTest);
diff --git a/browser/components/originattributes/test/browser/browser_favicon_firstParty.js b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
new file mode 100644
index 000000000..b3a18947b
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -0,0 +1,343 @@
+/**
+ * Bug 1277803 - A test case for testing favicon loading across different first party domains.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+const FIRST_PARTY_ONE = "example.com";
+const FIRST_PARTY_TWO = "example.org";
+const THIRD_PARTY = "mochi.test:8888";
+
+const TEST_SITE_ONE = "http://" + FIRST_PARTY_ONE;
+const TEST_SITE_TWO = "http://" + FIRST_PARTY_TWO;
+const THIRD_PARTY_SITE = "http://" + THIRD_PARTY;
+const TEST_DIRECTORY = "/browser/browser/components/originattributes/test/browser/";
+
+const TEST_PAGE = TEST_DIRECTORY + "file_favicon.html";
+const TEST_THIRD_PARTY_PAGE = TEST_DIRECTORY + "file_favicon_thirdParty.html";
+const TEST_CACHE_PAGE = TEST_DIRECTORY + "file_favicon_cache.html";
+
+const FAVICON_URI = TEST_DIRECTORY + "file_favicon.png";
+const TEST_FAVICON_CACHE_URI = TEST_DIRECTORY + "file_favicon_cache.png";
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+
+function clearAllImageCaches() {
+ let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
+ .getService(SpecialPowers.Ci.imgITools);
+ let imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function clearAllPlacesFavicons() {
+ let faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
+ .getService(Ci.nsIFaviconService);
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "places-favicons-expired") {
+ resolve();
+ Services.obs.removeObserver(observer, "places-favicons-expired", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired", false);
+ faviconService.expireAllFavicons();
+ });
+}
+
+function observeFavicon(aFirstPartyDomain, aExpectedCookie, aPageURI) {
+ let faviconReqXUL = false;
+ let faviconReqPlaces = false;
+ let expectedPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(aPageURI, { firstPartyDomain: aFirstPartyDomain });
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ // Make sure that the topic is 'http-on-modify-request'.
+ if (aTopic === "http-on-modify-request") {
+ // We check the firstPartyDomain for the originAttributes of the loading
+ // channel. All requests for the favicon should contain the correct
+ // firstPartyDomain. There are two requests for a favicon loading, one
+ // from the Places library and one from the XUL image. The difference
+ // of them is the loading principal. The Places will use the content
+ // principal and the XUL image will use the system principal.
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+ let loadingPrincipal = reqLoadInfo.loadingPrincipal;
+ let triggeringPrincipal = reqLoadInfo.triggeringPrincipal;
+
+ // Make sure this is a favicon request.
+ if (!httpChannel.URI.spec.endsWith(FAVICON_URI)) {
+ return;
+ }
+
+ // Check the first party domain.
+ is(reqLoadInfo.originAttributes.firstPartyDomain, aFirstPartyDomain,
+ "The loadInfo has correct first party domain");
+
+ if (loadingPrincipal.equals(systemPrincipal)) {
+ faviconReqXUL = true;
+ ok(triggeringPrincipal.equals(expectedPrincipal),
+ "The triggeringPrincipal of favicon loading from XUL should be the content principal.");
+ } else {
+ faviconReqPlaces = true;
+ ok(loadingPrincipal.equals(expectedPrincipal),
+ "The loadingPrincipal of favicon loading from Places should be the content prinicpal");
+ }
+
+ let faviconCookie = httpChannel.getRequestHeader("cookie");
+
+ is(faviconCookie, aExpectedCookie, "The cookie of the favicon loading is correct.");
+ } else {
+ ok(false, "Received unexpected topic: ", aTopic);
+ }
+
+ if (faviconReqXUL && faviconReqPlaces) {
+ Services.obs.removeObserver(observer, "http-on-modify-request", false);
+ resolve();
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+ });
+}
+
+function waitOnFaviconResponse(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "http-on-examine-response" ||
+ aTopic === "http-on-examine-cached-response") {
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let loadInfo = httpChannel.loadInfo;
+
+ if (httpChannel.URI.spec !== aFaviconURL) {
+ return;
+ }
+
+ let result = {
+ topic: aTopic,
+ firstPartyDomain: loadInfo.originAttributes.firstPartyDomain
+ };
+
+ resolve(result);
+ Services.obs.removeObserver(observer, "http-on-examine-response", false);
+ Services.obs.removeObserver(observer, "http-on-examine-cached-response", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+ Services.obs.addObserver(observer, "http-on-examine-cached-response", false);
+ });
+}
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ onPageChanged(uri, attr, value, id) {
+
+ if (attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
+ value === aFaviconURL) {
+ resolve();
+ PlacesUtils.history.removeObserver(observer, false);
+ }
+ },
+ };
+
+ PlacesUtils.history.addObserver(observer, false);
+ });
+}
+
+function* openTab(aURL) {
+ let tab = gBrowser.addTab(aURL);
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+function* assignCookiesUnderFirstParty(aURL, aFirstParty, aCookieValue) {
+ // Open a tab under the given aFirstParty, and this tab will have an
+ // iframe which loads the aURL.
+ let tabInfo = yield openTabInFirstParty(aURL, aFirstParty);
+
+ // Add cookies into the iframe.
+ yield ContentTask.spawn(tabInfo.browser, aCookieValue, function* (value) {
+ content.document.cookie = value;
+ });
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+function* generateCookies(aThirdParty) {
+ // we generate two different cookies for two first party domains.
+ let cookies = [];
+ cookies.push(Math.random().toString());
+ cookies.push(Math.random().toString());
+
+ let firstSiteURL;
+ let secondSiteURL;
+
+ if (aThirdParty) {
+ // Add cookies into the third party site with different first party domain.
+ firstSiteURL = THIRD_PARTY_SITE;
+ secondSiteURL = THIRD_PARTY_SITE;
+ } else {
+ // Add cookies into sites.
+ firstSiteURL = TEST_SITE_ONE;
+ secondSiteURL = TEST_SITE_TWO;
+ }
+
+ yield assignCookiesUnderFirstParty(firstSiteURL, TEST_SITE_ONE, cookies[0]);
+ yield assignCookiesUnderFirstParty(secondSiteURL, TEST_SITE_TWO, cookies[1]);
+
+ return cookies;
+}
+
+function* doTest(aTestPage, aExpectedCookies, aFaviconURL) {
+ let firstPageURI = makeURI(TEST_SITE_ONE + aTestPage);
+ let secondPageURI = makeURI(TEST_SITE_TWO + aTestPage);
+
+ // Start to observe the event of that favicon has been fully loaded.
+ let promiseFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
+
+ // Start to observe the favicon requests earlier in case we miss it.
+ let promiseObserveFavicon = observeFavicon(FIRST_PARTY_ONE, aExpectedCookies[0], firstPageURI);
+
+ // Open the tab for the first site.
+ let tabInfo = yield openTab(TEST_SITE_ONE + aTestPage);
+
+ // Waiting until favicon requests are all made.
+ yield promiseObserveFavicon;
+
+ // Waiting until favicon loaded.
+ yield promiseFaviconLoaded;
+
+ // Close the tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+
+ // Start to observe the favicon requests earlier in case we miss it.
+ promiseObserveFavicon = observeFavicon(FIRST_PARTY_TWO, aExpectedCookies[1], secondPageURI);
+
+ // Open the tab for the second site.
+ tabInfo = yield openTab(TEST_SITE_TWO + aTestPage);
+
+ // Waiting until favicon requests are all made.
+ yield promiseObserveFavicon;
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+add_task(function* setup() {
+ // Make sure first party isolation is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.firstparty.isolate", true]
+ ]});
+});
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+ // Clear all cookies.
+ let cookieMgr = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ cookieMgr.removeAll();
+
+ // Clear all image caches and network caches.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+});
+
+add_task(function* test_favicon_firstParty() {
+ for (let testThirdParty of [false, true]) {
+ // Clear all image caches and network caches before running the test.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ yield clearAllPlacesFavicons();
+
+ let cookies = yield generateCookies(testThirdParty);
+
+ if (testThirdParty) {
+ yield doTest(TEST_THIRD_PARTY_PAGE, cookies, THIRD_PARTY_SITE + FAVICON_URI);
+ } else {
+ yield doTest(TEST_PAGE, cookies, TEST_SITE_ONE + FAVICON_URI);
+ }
+ }
+});
+
+add_task(function* test_favicon_cache_firstParty() {
+ // Clear all image caches and network caches before running the test.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Start to observer the event of that favicon has been fully loaded and cached.
+ let promiseForFaviconLoaded = waitOnFaviconLoaded(THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI);
+
+ // Start to observer for the favicon response of the first tab.
+ let responsePromise = waitOnFaviconResponse(THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI);
+
+ // Open the tab for the first site.
+ let tabInfoA = yield openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
+
+ // Waiting for the favicon response.
+ let response = yield responsePromise;
+
+ // Make sure the favicon is loaded through the network and its first party domain is correct.
+ is(response.topic, "http-on-examine-response", "The favicon image should be loaded through network.");
+ is(response.firstPartyDomain, FIRST_PARTY_ONE, "We should only observe the network response for the first first party.");
+
+ // Waiting until the favicon has been loaded and cached.
+ yield promiseForFaviconLoaded;
+
+ // Open the tab again for checking the image cache is working correctly.
+ let tabInfoB = yield openTab(TEST_SITE_ONE + TEST_CACHE_PAGE);
+
+ // Start to observe the favicon response, the second tab actually will not
+ // make any network request since the favicon will be loaded by the cache for
+ // both Places and XUL image. So here, we are going to observe the favicon
+ // response for the third tab which opens with the second first party.
+ let promiseForFaviconResponse = waitOnFaviconResponse(THIRD_PARTY_SITE + TEST_FAVICON_CACHE_URI);
+
+ // Open the tab for the second site.
+ let tabInfoC = yield openTab(TEST_SITE_TWO + TEST_CACHE_PAGE);
+
+ // Wait for the favicon response. In this case, we suppose to catch the
+ // response for the third tab but not the second tab since it will not
+ // go through the network.
+ response = yield promiseForFaviconResponse;
+
+ // Check that the favicon response has came from the network and it has the
+ // correct first party domain.
+ is(response.topic, "http-on-examine-response", "The favicon image should be loaded through network again.");
+ is(response.firstPartyDomain, FIRST_PARTY_TWO, "We should only observe the network response for the second first party.");
+
+ yield BrowserTestUtils.removeTab(tabInfoA.tab);
+ yield BrowserTestUtils.removeTab(tabInfoB.tab);
+ yield BrowserTestUtils.removeTab(tabInfoC.tab);
+});
diff --git a/browser/components/originattributes/test/browser/browser_favicon_userContextId.js b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
new file mode 100644
index 000000000..507e0a6d4
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
@@ -0,0 +1,257 @@
+/**
+ * Bug 1277803 - A test caes for testing favicon loading across different userContextId.
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+const TEST_SITE = "http://example.net";
+const TEST_THIRD_PARTY_SITE = "http://mochi.test:8888";
+
+const TEST_PAGE = TEST_SITE + "/browser/browser/components/originattributes/" +
+ "test/browser/file_favicon.html";
+const FAVICON_URI = TEST_SITE + "/browser/browser/components/originattributes/" +
+ "test/browser/file_favicon.png";
+const TEST_THIRD_PARTY_PAGE = "http://example.com/browser/browser/components/" +
+ "originattributes/test/browser/file_favicon_thirdParty.html";
+const THIRD_PARTY_FAVICON_URI = TEST_THIRD_PARTY_SITE + "/browser/browser/components/" +
+ "originattributes/test/browser/file_favicon.png";
+
+const USER_CONTEXT_ID_PERSONAL = 1;
+const USER_CONTEXT_ID_WORK = 2;
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+
+function clearAllImageCaches() {
+ var tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
+ .getService(SpecialPowers.Ci.imgITools);
+ var imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function clearAllPlacesFavicons() {
+ let faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
+ .getService(Ci.nsIFaviconService);
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "places-favicons-expired") {
+ resolve();
+ Services.obs.removeObserver(observer, "places-favicons-expired", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired", false);
+ faviconService.expireAllFavicons();
+ });
+}
+
+function FaviconObserver(aUserContextId, aExpectedCookie, aPageURI, aFaviconURL) {
+ this.reset(aUserContextId, aExpectedCookie, aPageURI, aFaviconURL);
+}
+
+FaviconObserver.prototype = {
+ observe(aSubject, aTopic, aData) {
+ // Make sure that the topic is 'http-on-modify-request'.
+ if (aTopic === "http-on-modify-request") {
+ // We check the userContextId for the originAttributes of the loading
+ // channel. All requests for the favicon should contain the correct
+ // userContextId. There are two requests for a favicon loading, one
+ // from the Places library and one from the XUL image. The difference
+ // of them is the loading principal. The Places will use the content
+ // principal and the XUL image will use the system principal.
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+ let loadingPrincipal;
+ let triggeringPrincipal;
+
+ // Make sure this is a favicon request.
+ if (httpChannel.URI.spec !== this._faviconURL) {
+ return;
+ }
+
+ if (reqLoadInfo) {
+ loadingPrincipal = reqLoadInfo.loadingPrincipal;
+ triggeringPrincipal = reqLoadInfo.triggeringPrincipal;
+ }
+
+ // Check the userContextId.
+ is(reqLoadInfo.originAttributes.userContextId, this._curUserContextId,
+ "The loadInfo has correct userContextId");
+
+ if (loadingPrincipal.equals(systemPrincipal)) {
+ this._faviconReqXUL = true;
+ ok(triggeringPrincipal.equals(this._expectedPrincipal),
+ "The triggeringPrincipal of favicon loading from XUL should be the content principal.");
+ } else {
+ this._faviconReqPlaces = true;
+ ok(loadingPrincipal.equals(this._expectedPrincipal),
+ "The loadingPrincipal of favicon loading from Places should be the content prinicpal");
+ }
+
+ let faviconCookie = httpChannel.getRequestHeader("cookie");
+
+ is(faviconCookie, this._expectedCookie, "The cookie of the favicon loading is correct.");
+ } else {
+ ok(false, "Received unexpected topic: ", aTopic);
+ }
+
+ if (this._faviconReqXUL && this._faviconReqPlaces) {
+ this._faviconLoaded.resolve();
+ }
+ },
+
+ reset(aUserContextId, aExpectedCookie, aPageURI, aFaviconURL) {
+ this._curUserContextId = aUserContextId;
+ this._expectedCookie = aExpectedCookie;
+ this._expectedPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(aPageURI, { userContextId: aUserContextId });
+ this._faviconReqXUL = false;
+ this._faviconReqPlaces = false;
+ this._faviconURL = aFaviconURL;
+ this._faviconLoaded = new Promise.defer();
+ },
+
+ get promise() {
+ return this._faviconLoaded.promise;
+ }
+};
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ onPageChanged(uri, attr, value, id) {
+
+ if (attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
+ value === aFaviconURL) {
+ resolve();
+ PlacesUtils.history.removeObserver(observer, false);
+ }
+ },
+ };
+
+ PlacesUtils.history.addObserver(observer, false);
+ });
+}
+
+function* generateCookies(aHost) {
+ // we generate two different cookies for two userContextIds.
+ let cookies = [];
+ cookies.push(Math.random().toString());
+ cookies.push(Math.random().toString());
+
+ // Then, we add cookies into the site for 'personal' and 'work'.
+ let tabInfoA = yield openTabInUserContext(aHost, USER_CONTEXT_ID_PERSONAL);
+ let tabInfoB = yield openTabInUserContext(aHost, USER_CONTEXT_ID_WORK);
+
+ yield ContentTask.spawn(tabInfoA.browser, cookies[0], function* (value) {
+ content.document.cookie = value;
+ });
+
+ yield ContentTask.spawn(tabInfoB.browser, cookies[1], function* (value) {
+ content.document.cookie = value;
+ });
+
+ yield BrowserTestUtils.removeTab(tabInfoA.tab);
+ yield BrowserTestUtils.removeTab(tabInfoB.tab);
+
+ return cookies;
+}
+
+function* doTest(aTestPage, aFaviconHost, aFaviconURL) {
+ let cookies = yield generateCookies(aFaviconHost);
+ let pageURI = makeURI(aTestPage);
+
+ // Create the observer object for observing request channels of the personal
+ // container.
+ let observer = new FaviconObserver(USER_CONTEXT_ID_PERSONAL, cookies[0], pageURI, aFaviconURL);
+
+ // Add the observer earlier in case we miss it.
+ let promiseWaitOnFaviconLoaded = waitOnFaviconLoaded(aFaviconURL);
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+
+ // Open the tab with the personal container.
+ let tabInfo = yield openTabInUserContext(aTestPage, USER_CONTEXT_ID_PERSONAL);
+
+ // Waiting for favicon requests are all made.
+ yield observer.promise;
+ // Waiting for favicon loaded.
+ yield promiseWaitOnFaviconLoaded;
+
+ // Close the tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+
+ // Reset the observer for observing requests for the work container.
+ observer.reset(USER_CONTEXT_ID_WORK, cookies[1], pageURI, aFaviconURL);
+ tabInfo = yield openTabInUserContext(aTestPage, USER_CONTEXT_ID_WORK);
+
+ // Waiting for favicon requests are all made.
+ yield observer.promise;
+
+ Services.obs.removeObserver(observer, "http-on-modify-request", false);
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+add_task(function* setup() {
+ // Make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["privacy.userContext.enabled", true]
+ ]});
+});
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+ // Clear all cookies.
+ let cookieMgr = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ cookieMgr.removeAll();
+
+ // Clear all image caches and network caches.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ clearAllPlacesFavicons();
+});
+
+add_task(function* test_favicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ yield clearAllPlacesFavicons();
+
+ yield doTest(TEST_PAGE, TEST_SITE, FAVICON_URI);
+});
+
+add_task(function* test_thirdPartyFavicon_userContextId() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all network caches.
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear Places favicon caches.
+ yield clearAllPlacesFavicons();
+
+ yield doTest(TEST_THIRD_PARTY_PAGE, TEST_THIRD_PARTY_SITE, THIRD_PARTY_FAVICON_URI);
+});
diff --git a/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
new file mode 100644
index 000000000..ddda6afae
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_firstPartyIsolation.js
@@ -0,0 +1,174 @@
+const BASE_URL = "http://mochi.test:8888/browser/browser/components/originattributes/test/browser/";
+const BASE_DOMAIN = "mochi.test";
+
+add_task(function* setup() {
+ Services.prefs.setBoolPref("privacy.firstparty.isolate", true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.firstparty.isolate");
+ });
+});
+
+/**
+ * Test for the top-level document and child iframes should have the
+ * firstPartyDomain attribute.
+ */
+add_task(function* principal_test() {
+ let tab = gBrowser.addTab(BASE_URL + "test_firstParty.html");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, function (url) {
+ return url == BASE_URL + "test_firstParty.html";
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, { firstPartyDomain: BASE_DOMAIN }, function* (attrs) {
+ info("document principal: " + content.document.nodePrincipal.origin);
+ Assert.equal(docShell.getOriginAttributes().firstPartyDomain, "",
+ "top-level docShell shouldn't have firstPartyDomain attribute.");
+ Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "The document should have firstPartyDomain");
+
+ for (let i = 1; i < 4; i++) {
+ let iframe = content.document.getElementById("iframe" + i);
+ info("iframe principal: " + iframe.contentDocument.nodePrincipal.origin);
+ Assert.equal(iframe.frameLoader.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain, "iframe's docshell should have firstPartyDomain");
+ Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "iframe should have firstPartyDomain");
+ }
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Test for the cookie jars of the top-level document and child iframe should be
+ * isolated by firstPartyDomain.
+ */
+add_task(function* cookie_test() {
+ let tab = gBrowser.addTab(BASE_URL + "test_firstParty_cookie.html");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser, true);
+
+ let iter = Services.cookies.enumerator;
+ let count = 0;
+ while (iter.hasMoreElements()) {
+ count++;
+ let cookie = iter.getNext().QueryInterface(Ci.nsICookie2);
+ Assert.equal(cookie.value, "foo", "Cookie value should be foo");
+ Assert.equal(cookie.originAttributes.firstPartyDomain, BASE_DOMAIN, "Cookie's origin attributes should be " + BASE_DOMAIN);
+ }
+
+ // one cookie is from requesting test.js from top-level doc, and the other from
+ // requesting test2.js from iframe test2.html.
+ Assert.equal(count, 2, "Should have two cookies");
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Test for after redirect, the top-level document should update the firstPartyDomain
+ * attribute. However if the redirect is happening on the iframe, the attribute
+ * should remain the same.
+ */
+add_task(function* redirect_test() {
+ let tab = gBrowser.addTab(BASE_URL + "test_firstParty_http_redirect.html");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield ContentTask.spawn(tab.linkedBrowser, { firstPartyDomain: "example.com" }, function* (attrs) {
+ info("document principal: " + content.document.nodePrincipal.origin);
+ info("document uri: " + content.document.documentURI);
+
+ Assert.equal(content.document.documentURI, "http://example.com/browser/browser/components/originattributes/test/browser/dummy.html",
+ "The page should have been redirected to http://example.com/browser/browser/components/originattributes/test/browser/dummy.html");
+ Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "The document should have firstPartyDomain");
+ });
+
+ // Since this is a HTML redirect, we wait until the final page is loaded.
+ let tab2 = gBrowser.addTab(BASE_URL + "test_firstParty_html_redirect.html");
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser, false, function(url) {
+ return url == "http://example.com/";
+ });
+
+ yield ContentTask.spawn(tab2.linkedBrowser, { firstPartyDomain: "example.com" }, function* (attrs) {
+ info("2nd tab document principal: " + content.document.nodePrincipal.origin);
+ info("2nd tab document uri: " + content.document.documentURI);
+ Assert.equal(content.document.documentURI, "http://example.com/",
+ "The page should have been redirected to http://example.com");
+ Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "The document should have firstPartyDomain");
+ });
+
+ let tab3 = gBrowser.addTab(BASE_URL + "test_firstParty_iframe_http_redirect.html");
+ yield BrowserTestUtils.browserLoaded(tab3.linkedBrowser, true, function(url) {
+ return url == (BASE_URL + "test_firstParty_iframe_http_redirect.html");
+ });
+
+ // This redirect happens on the iframe, so unlike the two redirect tests above,
+ // the firstPartyDomain should still stick to the current top-level document,
+ // which is mochi.test.
+ yield ContentTask.spawn(tab3.linkedBrowser, { firstPartyDomain: "mochi.test" }, function* (attrs) {
+ let iframe = content.document.getElementById("iframe1");
+ info("iframe document principal: " + iframe.contentDocument.nodePrincipal.origin);
+ info("iframe document uri: " + iframe.contentDocument.documentURI);
+
+ Assert.equal(iframe.contentDocument.documentURI, "http://example.com/browser/browser/components/originattributes/test/browser/dummy.html",
+ "The page should have been redirected to http://example.com/browser/browser/components/originattributes/test/browser/dummy.html");
+ Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "The iframe should have firstPartyDomain: " + attrs.firstPartyDomain);
+ });
+
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab3);
+});
+
+/**
+ * Test for postMessage between document and iframe.
+ */
+add_task(function* postMessage_test() {
+ let tab = gBrowser.addTab(BASE_URL + "test_firstParty_postMessage.html");
+
+ // The top-level page will post a message to its child iframe, and wait for
+ // another message from the iframe, once it receives the message, it will
+ // create another iframe, dummy.html.
+ // So we wait until dummy.html is loaded
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, function (url) {
+ return url == BASE_URL + "dummy.html";
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ info("document principal: " + content.document.nodePrincipal.origin);
+ let value = content.document.getElementById("message").textContent;
+ Assert.equal(value, "OK");
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * When the web page calls window.open, the new window should have the same
+ * firstPartyDomain attribute.
+ */
+add_task(function* openWindow_test() {
+ Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.link.open_newwindow");
+ });
+
+ let tab = gBrowser.addTab(BASE_URL + "window.html");
+ let win = yield BrowserTestUtils.waitForNewWindow();
+
+ yield ContentTask.spawn(win.gBrowser.selectedBrowser, { firstPartyDomain: "mochi.test" }, function* (attrs) {
+ Assert.equal(docShell.getOriginAttributes().firstPartyDomain, attrs.firstPartyDomain,
+ "window.open() should have firstPartyDomain attribute");
+ Assert.equal(content.document.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "The document should have firstPartyDomain");
+
+ let iframe = content.document.getElementById("iframe1");
+ Assert.equal(iframe.frameLoader.docShell.getOriginAttributes().firstPartyDomain,
+ attrs.firstPartyDomain, "iframe's docshell should have firstPartyDomain");
+ Assert.equal(iframe.contentDocument.nodePrincipal.originAttributes.firstPartyDomain,
+ attrs.firstPartyDomain, "iframe should have firstPartyDomain");
+ });
+
+ gBrowser.removeTab(tab);
+ yield BrowserTestUtils.closeWindow(win);
+});
+
diff --git a/browser/components/originattributes/test/browser/browser_httpauth.js b/browser/components/originattributes/test/browser/browser_httpauth.js
new file mode 100644
index 000000000..0b7b1540e
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_httpauth.js
@@ -0,0 +1,54 @@
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+let server = new HttpServer();
+server.registerPathHandler('/file.html', fileHandler);
+server.start(-1);
+
+let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
+let FILE_URI = BASE_URI + '/file.html';
+
+let credentialQueue = [];
+
+// Ask the user agent for authorization.
+function fileHandler(metadata, response) {
+ if (!metadata.hasHeader("Authorization")) {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", "Basic realm=\"User Visible Realm\"");
+ return;
+ }
+
+ // This will be "account:password" encoded in base64.
+ credentialQueue.push(metadata.getHeader("Authorization"));
+
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = "<html><body></body></html>";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function onCommonDialogLoaded(subject) {
+ // Submit random account and password
+ let dialog = subject.Dialog;
+ dialog.ui.loginTextbox.setAttribute("value", Math.random());
+ dialog.ui.password1Textbox.setAttribute("value", Math.random());
+ dialog.ui.button0.click();
+}
+
+Services.obs.addObserver(onCommonDialogLoaded, "common-dialog-loaded", false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(onCommonDialogLoaded, "common-dialog-loaded");
+ server.stop(() => {
+ server = null;
+ });
+});
+
+function getResult() {
+ // If two targets are isolated, they should get different credentials.
+ // Otherwise, the credentials will be cached and therefore the same.
+ return credentialQueue.shift();
+}
+
+IsolationTestTools.runTests(FILE_URI, getResult);
+
diff --git a/browser/components/originattributes/test/browser/browser_imageCacheIsolation.js b/browser/components/originattributes/test/browser/browser_imageCacheIsolation.js
new file mode 100644
index 000000000..a24cec9ac
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_imageCacheIsolation.js
@@ -0,0 +1,80 @@
+/*
+ * Bug 1264572 - A test case for image cache isolation.
+ */
+
+requestLongerTimeout(2);
+
+let Cu = Components.utils;
+let {HttpServer} = Cu.import("resource://testing-common/httpd.js", {});
+
+const NUM_ISOLATION_LOADS = 2;
+const NUM_CACHED_LOADS = 1;
+
+let gHits = 0;
+
+let server = new HttpServer();
+server.registerPathHandler('/image.png', imageHandler);
+server.registerPathHandler('/file.html', fileHandler);
+server.start(-1);
+
+registerCleanupFunction(() => {
+ server.stop(() => {
+ server = null;
+ });
+});
+
+let BASE_URI = 'http://localhost:' + server.identity.primaryPort;
+let IMAGE_URI = BASE_URI + '/image.png';
+let FILE_URI = BASE_URI + '/file.html';
+
+function imageHandler(metadata, response) {
+ info('XXX: loading image from server');
+ gHits++;
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function fileHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+ let body = `<html><body><image src=${IMAGE_URI}></body></html>`;
+ response.bodyOutputStream.write(body, body.length);
+}
+
+function doBefore() {
+ // reset hit counter
+ info('XXX resetting gHits');
+ gHits = 0;
+ info('XXX clearing image cache');
+ let imageCache = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .getImgCacheForDocument(null);
+ imageCache.clearCache(true);
+ imageCache.clearCache(false);
+ info('XXX clearning network cache');
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+}
+
+// the test function does nothing on purpose.
+function doTest(aBrowser) {
+ return 0;
+}
+
+// the check function
+function doCheck(shouldIsolate, a, b) {
+ // if we're doing first party isolation and the image cache isolation is
+ // working, then gHits should be 2 because the image would have been loaded
+ // one per first party domain. if first party isolation is disabled, then
+ // gHits should be 1 since there would be one image load from the server and
+ // one load from the image cache.
+ info(`XXX check: gHits == ${gHits}, shouldIsolate == ${shouldIsolate}`);
+ return shouldIsolate ? gHits == NUM_ISOLATION_LOADS
+ : gHits == NUM_CACHED_LOADS;
+}
+
+IsolationTestTools.runTests(FILE_URI, doTest, doCheck, doBefore);
diff --git a/browser/components/originattributes/test/browser/browser_localStorageIsolation.js b/browser/components/originattributes/test/browser/browser_localStorageIsolation.js
new file mode 100644
index 000000000..41bde80e4
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_localStorageIsolation.js
@@ -0,0 +1,24 @@
+/**
+ * Bug 1264567 - A test case for localStorage isolation.
+ */
+
+const TEST_PAGE = "http://mochi.test:8888/browser/browser/components/" +
+ "originattributes/test/browser/file_firstPartyBasic.html";
+
+// Use a random key so we don't access it in later tests.
+const key = Math.random().toString();
+
+// Define the testing function
+function* doTest(aBrowser) {
+ return yield ContentTask.spawn(aBrowser, key, function (key) {
+ let value = content.localStorage.getItem(key);
+ if (value === null) {
+ // No value is found, so we create one.
+ value = Math.random().toString();
+ content.localStorage.setItem(key, value);
+ }
+ return value;
+ });
+}
+
+IsolationTestTools.runTests(TEST_PAGE, doTest);
diff --git a/browser/components/originattributes/test/browser/browser_sharedworker.js b/browser/components/originattributes/test/browser/browser_sharedworker.js
new file mode 100644
index 000000000..7049407f6
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_sharedworker.js
@@ -0,0 +1,26 @@
+/**
+ * Bug 1264593 - A test case for the shared worker by first party isolation.
+ */
+
+const TEST_DOMAIN = "http://example.net/";
+const TEST_PATH = TEST_DOMAIN + "browser/browser/components/originattributes/test/browser/";
+const TEST_PAGE = TEST_PATH + "file_sharedworker.html";
+
+function* getResultFromSharedworker(aBrowser) {
+ let response = yield ContentTask.spawn(aBrowser, null, function* () {
+ let worker = new content.SharedWorker("file_sharedworker.js", "isolationSharedWorkerTest");
+
+ let result = yield new Promise(resolve => {
+ worker.port.onmessage = function (e) {
+ content.document.getElementById("display").innerHTML = e.data;
+ resolve(e.data);
+ };
+ });
+
+ return result;
+ });
+
+ return response;
+}
+
+IsolationTestTools.runTests(TEST_PAGE, getResultFromSharedworker);
diff --git a/browser/components/originattributes/test/browser/dummy.html b/browser/components/originattributes/test/browser/dummy.html
new file mode 100644
index 000000000..1a87e2840
--- /dev/null
+++ b/browser/components/originattributes/test/browser/dummy.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_broadcastChannel.html b/browser/components/originattributes/test/browser/file_broadcastChannel.html
new file mode 100644
index 000000000..14bd7a022
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_broadcastChannel.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Page broadcast channel creator for first party isolation</title>
+</head>
+<body>
+ <div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+ <iframe id="iframe" src="file_broadcastChanneliFrame.html"></iframe>>
+<script type="text/javascript;version=1.7">
+let bc = new BroadcastChannel("testBroadcastChannel");
+bc.onmessage = function (e) {
+ document.getElementById("display").innerHTML = e.data;
+};
+</script>
+</body>
diff --git a/browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html b/browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html
new file mode 100644
index 000000000..a2140e617
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_broadcastChanneliFrame.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Page broadcast channel responder for first party isolation</title>
+</head>
+<body>
+ <div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+<script type="text/javascript;version=1.7">
+let bc = new BroadcastChannel("testBroadcastChannel");
+bc.onmessage = function (e) {
+ window.parent.postMessage(e.data, "*");
+};
+</script>
+</body>
diff --git a/browser/components/originattributes/test/browser/file_cache.html b/browser/components/originattributes/test/browser/file_cache.html
new file mode 100644
index 000000000..788ec899d
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_cache.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<head>
+ <link rel="stylesheet" type="text/css"
+ href="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css">
+ <link rel="preconnect" href="http://example.net">
+</head>
+<body>
+<div>file_cache.html</div>
+
+<iframe src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html">
+</iframe>
+
+<script src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js">
+</script>
+
+<img src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png">
+
+<embed src="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png">
+
+<object data="http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png"
+ type="image/png"></object>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_favicon.html b/browser/components/originattributes/test/browser/file_favicon.html
new file mode 100644
index 000000000..b571134e1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="file_favicon.png" />
+ </head>
+ <body>
+ Favicon!!
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/components/originattributes/test/browser/file_favicon.png b/browser/components/originattributes/test/browser/file_favicon.png
new file mode 100644
index 000000000..5535363c9
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_favicon.png^headers^ b/browser/components/originattributes/test/browser/file_favicon.png^headers^
new file mode 100644
index 000000000..9e23c73b7
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon.png^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/browser/components/originattributes/test/browser/file_favicon_cache.html b/browser/components/originattributes/test/browser/file_favicon_cache.html
new file mode 100644
index 000000000..2a7343b8e
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon_cache.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/file_favicon_cache.png" />
+ </head>
+ <body>
+ Third Party Favicon!!
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/components/originattributes/test/browser/file_favicon_cache.png b/browser/components/originattributes/test/browser/file_favicon_cache.png
new file mode 100644
index 000000000..5535363c9
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon_cache.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_favicon_thirdParty.html b/browser/components/originattributes/test/browser/file_favicon_thirdParty.html
new file mode 100644
index 000000000..4a2dd680a
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_favicon_thirdParty.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="http://mochi.test:8888/browser/browser/components/originattributes/test/browser/file_favicon.png" />
+ </head>
+ <body>
+ Third Party Favicon!!
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/components/originattributes/test/browser/file_firstPartyBasic.html b/browser/components/originattributes/test/browser/file_firstPartyBasic.html
new file mode 100644
index 000000000..713187fb2
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_firstPartyBasic.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>First Party Isolation Tests</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_sharedworker.html b/browser/components/originattributes/test/browser/file_sharedworker.html
new file mode 100644
index 000000000..b9ff793bd
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_sharedworker.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Page SharedWorker creator for first party isolation</title>
+</head>
+<body>
+<div id="display" style="white-space:pre; font-family:monospace; display:inline;"></div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_sharedworker.js b/browser/components/originattributes/test/browser/file_sharedworker.js
new file mode 100644
index 000000000..82f075a37
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_sharedworker.js
@@ -0,0 +1,9 @@
+self.randomValue = Math.random();
+
+/* global onconnect:true */
+
+onconnect = function (e) {
+ let port = e.ports[0];
+ port.postMessage(self.randomValue);
+ port.start();
+};
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg b/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg
new file mode 100644
index 000000000..edda4e912
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.audio.ogg
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png
new file mode 100644
index 000000000..c5916f289
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.embed.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html
new file mode 100644
index 000000000..037901ad0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.fetch.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.fetch.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html
new file mode 100644
index 000000000..b047d5b41
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.iframe.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.html</div>
+<script>
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html", true);
+ xhr.send();
+ var worker = new Worker("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js");
+ var sharedWorker = new SharedWorker("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js");
+
+ fetch("file_thirdPartyChild.fetch.html", {cache: "force-cache"} );
+ fetch(new Request("file_thirdPartyChild.request.html"), {cache: "force-cache"} );
+</script>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png
new file mode 100644
index 000000000..c5916f289
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.img.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js
new file mode 100644
index 000000000..dbf8f8376
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js
@@ -0,0 +1 @@
+// dummy script, to be called by self.importScripts(...)
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css b/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css
new file mode 100644
index 000000000..06d6e2672
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.link.css
@@ -0,0 +1 @@
+/* Dummy CSS file, used by browser_cache.js. */ \ No newline at end of file
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png b/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png
new file mode 100644
index 000000000..c5916f289
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.object.png
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.request.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.request.html
new file mode 100644
index 000000000..108ed2ffa
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.request.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.request.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js
new file mode 100644
index 000000000..6ddf436c0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.script.js
@@ -0,0 +1 @@
+// Dummy child script, used by browser_cache.js
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js
new file mode 100644
index 000000000..b262fa10a
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.sharedworker.js
@@ -0,0 +1 @@
+// dummy file
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt b/browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt
new file mode 100644
index 000000000..b37cb40e4
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.track.vtt
@@ -0,0 +1,13 @@
+WEBVTT FILE
+
+1
+00:00:00.500 --> 00:00:02.000 D:vertical A:start
+blah blah blah
+
+2
+00:00:02.500 --> 00:00:04.300
+this is a test
+
+3
+00:00:05.000 --> 00:00:07.000
+one more line
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv b/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv
new file mode 100644
index 000000000..68dee3cf2
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.video.ogv
Binary files differ
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html
new file mode 100644
index 000000000..47e42d1e5
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.worker.fetch.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js
new file mode 100644
index 000000000..b04e2c7de
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.js
@@ -0,0 +1,9 @@
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html", true);
+xhr.send();
+
+fetch("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.fetch.html", {cache: "force-cache"} );
+var myRequest = new Request("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html");
+fetch(myRequest, {cache: "force-cache"} );
+
+self.importScripts("http://example.net/browser/browser/components/originattributes/test/browser/file_thirdPartyChild.import.js");
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html
new file mode 100644
index 000000000..5b5c55bfe
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.request.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.worker.request.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html
new file mode 100644
index 000000000..9fc107f37
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.worker.xhr.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.worker.xhr.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html b/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html
new file mode 100644
index 000000000..f56e7b3c1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_thirdPartyChild.xhr.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<!-- The child page, used by browser_cache.js -->
+<body>
+<div>thirdPartyChild.html</div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/head.js b/browser/components/originattributes/test/browser/head.js
new file mode 100644
index 000000000..96559a10d
--- /dev/null
+++ b/browser/components/originattributes/test/browser/head.js
@@ -0,0 +1,365 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const TEST_URL_PATH = "/browser/browser/components/originattributes/test/browser/";
+
+// The flags of test modes.
+const TEST_MODE_FIRSTPARTY = 0;
+const TEST_MODE_NO_ISOLATION = 1;
+const TEST_MODE_CONTAINERS = 2;
+
+// The name of each mode.
+const TEST_MODE_NAMES = [ "first party isolation",
+ "no isolation",
+ "containers" ];
+
+// The frame types.
+const TEST_TYPE_FRAME = 0;
+const TEST_TYPE_IFRAME = 1;
+
+// The default frame setting.
+const DEFAULT_FRAME_SETTING = [ TEST_TYPE_IFRAME ];
+
+let gFirstPartyBasicPage = TEST_URL_PATH + "file_firstPartyBasic.html";
+
+/**
+ * Add a tab for the given url with the specific user context id.
+ *
+ * @param aURL
+ * The url of the page.
+ * @param aUserContextId
+ * The user context id for this tab.
+ *
+ * @return tab - The tab object of this tab.
+ * browser - The browser object of this tab.
+ */
+function* openTabInUserContext(aURL, aUserContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab(aURL, {userContextId: aUserContextId});
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+/**
+ * Add a tab for a page with the given first party domain. This page will have
+ * an iframe which is loaded with the given url by default or you could specify
+ * a frame setting to create nested frames. And this function will also modify
+ * the 'content' in the ContentTask to the target frame's window object.
+ *
+ * @param aURL
+ * The url of the iframe.
+ * @param aFirstPartyDomain
+ * The first party domain.
+ * @param aFrameSetting
+ * This setting controls how frames are organized within the page. The
+ * setting is an array of frame types, the first item indicates the
+ * frame type (iframe or frame) of the first layer of the frame structure,
+ * and the second item indicates the second layer, and so on. The aURL will
+ * be loaded at the deepest layer. This is optional.
+ *
+ * @return tab - The tab object of this tab.
+ * browser - The browser object of this tab.
+ */
+function* openTabInFirstParty(aURL, aFirstPartyDomain,
+ aFrameSetting = DEFAULT_FRAME_SETTING) {
+
+ // If the first party domain ends with '/', we remove it.
+ if (aFirstPartyDomain.endsWith('/')) {
+ aFirstPartyDomain = aFirstPartyDomain.slice(0, -1);
+ }
+
+ let basicPageURL = aFirstPartyDomain + gFirstPartyBasicPage;
+
+ // Open the tab for the basic first party page.
+ let tab = gBrowser.addTab(basicPageURL);
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ let pageArgs = { url: aURL,
+ frames: aFrameSetting,
+ typeFrame: TEST_TYPE_FRAME,
+ typeIFrame: TEST_TYPE_IFRAME,
+ basicFrameSrc: basicPageURL};
+
+ // Create the frame structure.
+ yield ContentTask.spawn(browser, pageArgs, function* (arg) {
+ let typeFrame = arg.typeFrame;
+ let typeIFrame = arg.typeIFrame;
+
+ // Redefine the 'content' for allowing us to change its target, and making
+ // ContentTask.spawn can directly work on the frame element.
+ this.frameWindow = content;
+
+ Object.defineProperty(this, "content", {
+ get: () => this.frameWindow
+ });
+
+ let frameElement;
+ let numOfLayers = 0;
+
+ for (let type of arg.frames) {
+ let document = content.document;
+ numOfLayers++;
+
+ if (type === typeFrame) {
+ // Add a frameset which carries the frame element.
+ let frameSet = document.createElement('frameset');
+ frameSet.cols = "50%,50%";
+
+ let frame = document.createElement('frame');
+ let dummyFrame = document.createElement('frame');
+
+ frameSet.appendChild(frame);
+ frameSet.appendChild(dummyFrame);
+
+ document.body.appendChild(frameSet);
+
+ frameElement = frame;
+ } else if (type === typeIFrame) {
+ // Add an iframe.
+ let iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+
+ frameElement = iframe;
+ } else {
+ ok(false, "Invalid frame type.");
+ break;
+ }
+
+ // Wait for the frame to be loaded.
+ yield new Promise(done => {
+ frameElement.addEventListener("load", function loadEnd() {
+ frameElement.removeEventListener("load", loadEnd, true);
+ done();
+ }, true);
+
+ // If it is the deepest layer, we load the target URL. Otherwise, we
+ // load a basic page.
+ if (numOfLayers === arg.frames.length) {
+ frameElement.setAttribute("src", arg.url);
+ } else {
+ frameElement.setAttribute("src", arg.basicFrameSrc);
+ }
+ });
+
+ // Redirect the 'content' to the frame's window.
+ this.frameWindow = frameElement.contentWindow;
+ }
+ });
+
+ return {tab, browser};
+}
+
+this.IsolationTestTools = {
+ /**
+ * Adds isolation tests for first party isolation, no isolation
+ * and containers respectively.
+ *
+ * @param aTask
+ * The testing task which will be run in different settings.
+ */
+ _add_task(aTask) {
+ add_task(function* addTaskForIsolationTests() {
+ let testSettings = [
+ { mode: TEST_MODE_FIRSTPARTY,
+ skip: false,
+ prefs: [["privacy.firstparty.isolate", true]]
+ },
+ { mode: TEST_MODE_NO_ISOLATION,
+ skip: false,
+ prefs: [["privacy.firstparty.isolate", false]]
+ },
+ { mode: TEST_MODE_CONTAINERS,
+ skip: false,
+ prefs: [["privacy.userContext.enabled", true]]
+ },
+ ];
+
+ // Add test tasks.
+ for (let testSetting of testSettings) {
+ IsolationTestTools._addTaskForMode(testSetting.mode,
+ testSetting.prefs,
+ testSetting.skip,
+ aTask);
+ }
+ });
+ },
+
+ _addTaskForMode(aMode, aPref, aSkip, aTask) {
+ if (aSkip) {
+ return;
+ }
+
+ add_task(function* () {
+ info("Starting the test for " + TEST_MODE_NAMES[aMode]);
+
+ // Before run this task, reset the preferences first.
+ yield SpecialPowers.flushPrefEnv();
+
+ // Make sure preferences are set properly.
+ yield SpecialPowers.pushPrefEnv({"set": aPref});
+
+ yield SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]});
+
+ yield aTask(aMode);
+ });
+ },
+
+ /**
+ * Add a tab with the given tab setting, this will open different types of
+ * tabs according to the given test mode. A tab setting means a isolation
+ * target in different test mode; a tab setting indicates a first party
+ * domain when testing the first party isolation, it is a user context
+ * id when testing containers.
+ *
+ * @param aMode
+ * The test mode which decides what type of tabs will be opened.
+ * @param aURL
+ * The url which is going to open.
+ * @param aTabSettingObj
+ * The tab setting object includes 'firstPartyDomain' for the first party
+ * domain and 'userContextId' for Containers.
+ * @param aFrameSetting
+ * This setting controls how frames are organized within the page. The
+ * setting is an array of frame types, the first item indicates the
+ * frame type (iframe or frame) of the first layer of the frame structure,
+ * and the second item indicates the second layer, and so on. The aURL
+ * will be loaded at the deepest layer. This is optional.
+ *
+ * @return tab - The tab object of this tab.
+ * browser - The browser object of this tab.
+ */
+ _addTab(aMode, aURL, aTabSettingObj, aFrameSetting) {
+ if (aMode === TEST_MODE_CONTAINERS) {
+ return openTabInUserContext(aURL, aTabSettingObj.userContextId);
+ }
+
+ return openTabInFirstParty(aURL, aTabSettingObj.firstPartyDomain,
+ aFrameSetting);
+
+ },
+
+ /**
+ * Run isolation tests. The framework will run tests with standard combinations
+ * of prefs and tab settings, and checks whether the isolation is working.
+ *
+ * @param aURL
+ * The URL of the page that will be tested or an object contains 'url',
+ * the tested page, 'firstFrameSetting' for the frame setting of the first
+ * tab, and 'secondFrameSetting' for the second tab.
+ * @param aGetResultFuncs
+ * An array of functions or a single function which are responsible for
+ * returning the isolation result back to the framework for further checking.
+ * Each of these functions will be provided the browser object of the tab,
+ * that allows modifying or fetchings results from the page content.
+ * @param aCompareResultFunc
+ * An optional function which allows modifying the way how does framework
+ * check results. This function will be provided a boolean to indicate
+ * the isolation is no or off and two results. This function should return
+ * a boolean to tell that whether isolation is working. If this function
+ * is not given, the framework will take case checking by itself.
+ * @param aBeforeFunc
+ * An optional function which is called before any tabs are created so
+ * that the test case can set up/reset local state.
+ * @param aGetResultImmediately
+ * An optional boolean to ensure we get results before the next tab is opened.
+ */
+ runTests(aURL, aGetResultFuncs, aCompareResultFunc, aBeforeFunc,
+ aGetResultImmediately) {
+ let pageURL;
+ let firstFrameSetting;
+ let secondFrameSetting;
+
+ // Request a longer timeout since the test will run a test for three times
+ // with different settings. Thus, one test here represents three tests.
+ // For this reason, we triple the timeout.
+ requestLongerTimeout(3);
+
+ if (typeof aURL === "string") {
+ pageURL = aURL;
+ } else if (typeof aURL === "object") {
+ pageURL = aURL.url;
+ firstFrameSetting = aURL.firstFrameSetting;
+ secondFrameSetting = aURL.secondFrameSetting;
+ }
+
+ if (!Array.isArray(aGetResultFuncs)) {
+ aGetResultFuncs = [aGetResultFuncs];
+ }
+
+ let tabSettings = [
+ { firstPartyDomain: "http://example.com", userContextId: 1},
+ { firstPartyDomain: "http://example.org", userContextId: 2}
+ ];
+
+ this._add_task(function* (aMode) {
+ let tabSettingA = 0;
+
+ for (let tabSettingB of [0, 1]) {
+ // Give the test a chance to set up before each case is run.
+ if (aBeforeFunc) {
+ yield aBeforeFunc(aMode);
+ }
+
+ // Create Tabs.
+ let tabInfoA = yield IsolationTestTools._addTab(aMode,
+ pageURL,
+ tabSettings[tabSettingA],
+ firstFrameSetting);
+ let resultsA = [];
+ if (aGetResultImmediately) {
+ for (let getResultFunc of aGetResultFuncs) {
+ resultsA.push(yield getResultFunc(tabInfoA.browser));
+ }
+ }
+ let tabInfoB = yield IsolationTestTools._addTab(aMode,
+ pageURL,
+ tabSettings[tabSettingB],
+ secondFrameSetting);
+ let i = 0;
+ for (let getResultFunc of aGetResultFuncs) {
+ // Fetch results from tabs.
+ let resultA = aGetResultImmediately ? resultsA[i++] :
+ yield getResultFunc(tabInfoA.browser);
+ let resultB = yield getResultFunc(tabInfoB.browser);
+
+ // Compare results.
+ let result = false;
+ let shouldIsolate = (aMode !== TEST_MODE_NO_ISOLATION) &&
+ tabSettingA !== tabSettingB;
+ if (aCompareResultFunc) {
+ result = yield aCompareResultFunc(shouldIsolate, resultA, resultB);
+ } else {
+ result = shouldIsolate ? resultA !== resultB :
+ resultA === resultB;
+ }
+
+ let msg = `Testing ${TEST_MODE_NAMES[aMode]} for ` +
+ `isolation ${shouldIsolate ? "on" : "off"} with TabSettingA ` +
+ `${tabSettingA} and tabSettingB ${tabSettingB}` +
+ `, resultA = ${resultA}, resultB = ${resultB}`;
+
+ ok(result, msg);
+ }
+
+ // Close Tabs.
+ yield BrowserTestUtils.removeTab(tabInfoA.tab);
+ yield BrowserTestUtils.removeTab(tabInfoB.tab);
+ }
+ });
+ }
+};
diff --git a/browser/components/originattributes/test/browser/test.html b/browser/components/originattributes/test/browser/test.html
new file mode 100644
index 000000000..214daa4b7
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script>
+ window.onmessage = function (evt) {
+ if (evt.data != "HI") {
+ return;
+ }
+
+ window.parent.postMessage("OK", "http://mochi.test:8888");
+ };
+ </script>
+</head>
+<body>
+ Hello World.
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test.js b/browser/components/originattributes/test/browser/test.js
new file mode 100644
index 000000000..d290af9b0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test.js
@@ -0,0 +1 @@
+var i = 1;
diff --git a/browser/components/originattributes/test/browser/test.js^headers^ b/browser/components/originattributes/test/browser/test.js^headers^
new file mode 100644
index 000000000..881f5bff0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test.js^headers^
@@ -0,0 +1 @@
+Set-Cookie: test=foo
diff --git a/browser/components/originattributes/test/browser/test2.html b/browser/components/originattributes/test/browser/test2.html
new file mode 100644
index 000000000..370be1560
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="test2.js"></script>
+</head>
+<body>
+ Hello World.
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test2.js b/browser/components/originattributes/test/browser/test2.js
new file mode 100644
index 000000000..d290af9b0
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test2.js
@@ -0,0 +1 @@
+var i = 1;
diff --git a/browser/components/originattributes/test/browser/test2.js^headers^ b/browser/components/originattributes/test/browser/test2.js^headers^
new file mode 100644
index 000000000..43604be7f
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test2.js^headers^
@@ -0,0 +1 @@
+Set-Cookie: test2=foo
diff --git a/browser/components/originattributes/test/browser/test_firstParty.html b/browser/components/originattributes/test/browser/test_firstParty.html
new file mode 100644
index 000000000..a90e01c4c
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div>
+ <iframe id="iframe1" src="http://example.com"></iframe>
+ <iframe id="iframe2" sandbox="" src="http://example.com"></iframe>
+ <iframe id="iframe3" sandbox="allow-same-origin" src="http://example.com"></iframe>
+ </div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_cookie.html b/browser/components/originattributes/test/browser/test_firstParty_cookie.html
new file mode 100644
index 000000000..44547c0d7
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_cookie.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="test.js"></script>
+</head>
+<body>
+ Hello World.
+ <iframe id="iframe1" src="test2.html"></iframe>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_html_redirect.html b/browser/components/originattributes/test/browser/test_firstParty_html_redirect.html
new file mode 100644
index 000000000..3c52d4f8c
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_html_redirect.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf8" http-equiv="refresh" content="0; url=http://example.com/"/>
+ <title>Test for Bug 1260931</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html
new file mode 100644
index 000000000..7b794a011
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^
new file mode 100644
index 000000000..c6d2757aa
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_http_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: http://example.com/browser/browser/components/originattributes/test/browser/dummy.html
diff --git a/browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html b/browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html
new file mode 100644
index 000000000..fd7df46c1
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_iframe_http_redirect.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <div>
+ <iframe id="iframe1" src="test_firstParty_http_redirect.html"></iframe>
+ </div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/test_firstParty_postMessage.html b/browser/components/originattributes/test/browser/test_firstParty_postMessage.html
new file mode 100644
index 000000000..5df8a5950
--- /dev/null
+++ b/browser/components/originattributes/test/browser/test_firstParty_postMessage.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Test for Bug 1260931</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script>
+function onload() {
+ let iframe1 = document.getElementById("iframe1");
+ iframe1.contentWindow.postMessage("HI", "http://mochi.test:8888");
+}
+
+window.onmessage = function (evt) {
+ document.getElementById("message").textContent = evt.data;
+
+ let iframe2 = document.createElement("iframe");
+ iframe2.src = "dummy.html";
+ document.body.appendChild(iframe2);
+};
+</script>
+<body onload="onload()">
+ <div>
+ <iframe id="iframe1" src="test.html"></iframe>
+ <span id="message"></span>
+ </div>
+</body>
+</html>
diff --git a/browser/components/originattributes/test/browser/window.html b/browser/components/originattributes/test/browser/window.html
new file mode 100644
index 000000000..34216030c
--- /dev/null
+++ b/browser/components/originattributes/test/browser/window.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Page creating a popup</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var w = window.open();
+ w.document.body.innerHTML = "<iframe id='iframe1' src='data:text/plain,test2'></iframe>";
+ </script>
+ </body>
+</html>
diff --git a/browser/components/originattributes/test/browser/worker_blobify.js b/browser/components/originattributes/test/browser/worker_blobify.js
new file mode 100644
index 000000000..56c0996a3
--- /dev/null
+++ b/browser/components/originattributes/test/browser/worker_blobify.js
@@ -0,0 +1,11 @@
+// Wait for a string to be posted to this worker.
+// Create a blob containing this string, and then
+// post back a blob URL pointing to the blob.
+self.addEventListener("message", function (e) {
+ try {
+ var blobURL = URL.createObjectURL(new Blob([e.data]));
+ postMessage({ blobURL });
+ } catch (e) {
+ postMessage({ error: e.message });
+ }
+}, false);
diff --git a/browser/components/originattributes/test/browser/worker_deblobify.js b/browser/components/originattributes/test/browser/worker_deblobify.js
new file mode 100644
index 000000000..1d6511a20
--- /dev/null
+++ b/browser/components/originattributes/test/browser/worker_deblobify.js
@@ -0,0 +1,31 @@
+// Wait for a blob URL to be posted to this worker.
+// Obtain the blob, and read the string contained in it.
+// Post back the string.
+
+var postStringInBlob = function (blobObject) {
+ var fileReader = new FileReaderSync();
+ var result = fileReader.readAsText(blobObject);
+ postMessage(result);
+};
+
+self.addEventListener("message", function (e) {
+ if ("error" in e.data) {
+ postMessage(e.data);
+ return;
+ }
+ var blobURL = e.data.blobURL,
+ xhr = new XMLHttpRequest();
+ try {
+ xhr.open("GET", blobURL, true);
+ xhr.onload = function () {
+ postStringInBlob(xhr.response);
+ };
+ xhr.onerror = function () {
+ postMessage({ error: "xhr error" });
+ };
+ xhr.responseType = "blob";
+ xhr.send();
+ } catch (e) {
+ postMessage({ error: e.message });
+ }
+}, false);
diff --git a/browser/components/originattributes/test/mochitest/file_empty.html b/browser/components/originattributes/test/mochitest/file_empty.html
new file mode 100644
index 000000000..bc98b4d2e
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/file_empty.html
@@ -0,0 +1,2 @@
+<h1>I'm just a support file</h1>
+<p>I get loaded to do permission testing.</p> \ No newline at end of file
diff --git a/browser/components/originattributes/test/mochitest/mochitest.ini b/browser/components/originattributes/test/mochitest/mochitest.ini
new file mode 100644
index 000000000..5df9998ca
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+ file_empty.html
+
+[test_permissions_api.html]
diff --git a/browser/components/originattributes/test/mochitest/test_permissions_api.html b/browser/components/originattributes/test/mochitest/test_permissions_api.html
new file mode 100644
index 000000000..63c74d1fe
--- /dev/null
+++ b/browser/components/originattributes/test/mochitest/test_permissions_api.html
@@ -0,0 +1,207 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Test for Permissions API</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+ <pre id="test"></pre>
+ <script type="application/javascript;version=1.8">
+ /*globals SpecialPowers, SimpleTest, is, ok, */
+ 'use strict';
+
+ const {
+ UNKNOWN_ACTION,
+ PROMPT_ACTION,
+ ALLOW_ACTION,
+ DENY_ACTION
+ } = SpecialPowers.Ci.nsIPermissionManager;
+
+ SimpleTest.waitForExplicitFinish();
+
+ const PERMISSIONS = [{
+ name: 'geolocation',
+ type: 'geo'
+ }, {
+ name: 'notifications',
+ type: 'desktop-notification'
+ }, {
+ name: 'push',
+ type: 'desktop-notification'
+ }, ];
+
+ const UNSUPPORTED_PERMISSIONS = [
+ 'foobarbaz', // Not in spec, for testing only.
+ 'midi',
+ ];
+
+ // Create a closure, so that tests are run on the correct window object.
+ function createPermissionTester(aWindow) {
+ return {
+ setPermissions(allow) {
+ const permissions = PERMISSIONS.map(({ type }) => {
+ return {
+ type,
+ allow,
+ 'context': aWindow.document
+ };
+ });
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ SpecialPowers.pushPermissions(permissions, resolve);
+ });
+ });
+ },
+ revokePermissions() {
+ const promisesToRevoke = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ ({ state }) => is(state, 'prompt', `correct state for '${name}'`),
+ () => ok(false, `revoke should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ revokeUnsupportedPermissions() {
+ const promisesToRevoke = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .revoke({ name })
+ .then(
+ () => ok(false, `revoke should not have resolved for '${name}'`),
+ error => is(error.name, 'TypeError', `revoke should have thrown TypeError for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToRevoke);
+ },
+ checkPermissions(state) {
+ const promisesToQuery = PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => is(state, state, `correct state for '${name}'`),
+ () => ok(false, `query should not have rejected for '${name}'`)
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ checkUnsupportedPermissions() {
+ const promisesToQuery = UNSUPPORTED_PERMISSIONS.map(({ name }) => {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(
+ () => ok(false, `query should not have resolved for '${name}'`),
+ error => {
+ is(error.name, 'TypeError',
+ `query should have thrown TypeError for '${name}'`);
+ }
+ );
+ });
+ return Promise.all(promisesToQuery);
+ },
+ promiseStateChanged(name, state) {
+ return aWindow.navigator.permissions
+ .query({ name })
+ .then(status => {
+ return new Promise( resolve => {
+ status.onchange = () => {
+ status.onchange = null;
+ is(status.state, state, `state changed for '${name}'`);
+ resolve();
+ };
+ });
+ },
+ () => ok(false, `query should not have rejected for '${name}'`));
+ },
+ testStatusOnChange() {
+ return new Promise((resolve) => {
+ SpecialPowers.popPermissions(() => {
+ const permission = 'geolocation';
+ const promiseGranted = this.promiseStateChanged(permission, 'granted');
+ this.setPermissions(ALLOW_ACTION);
+ promiseGranted.then(() => {
+ const promisePrompt = this.promiseStateChanged(permission, 'prompt');
+ SpecialPowers.popPermissions();
+ return promisePrompt;
+ }).then(resolve);
+ });
+ });
+ },
+ testInvalidQuery() {
+ return aWindow.navigator.permissions
+ .query({ name: 'invalid' })
+ .then(
+ () => ok(false, 'invalid query should not have resolved'),
+ () => ok(true, 'invalid query should have rejected')
+ );
+ },
+ testInvalidRevoke() {
+ return aWindow.navigator.permissions
+ .revoke({ name: 'invalid' })
+ .then(
+ () => ok(false, 'invalid revoke should not have resolved'),
+ () => ok(true, 'invalid revoke should have rejected')
+ );
+ },
+ };
+ }
+
+ function enablePrefs() {
+ const ops = {
+ 'set': [
+ ['dom.permissions.revoke.enable', true],
+ ['privacy.firstparty.isolate', true],
+ ],
+ };
+ return SpecialPowers.pushPrefEnv(ops);
+ }
+
+ function createIframe() {
+ return new Promise((resolve) => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'file_empty.html';
+ iframe.onload = () => resolve(iframe.contentWindow);
+ document.body.appendChild(iframe);
+ });
+ }
+ debugger;
+ window.onload = () => {
+ enablePrefs()
+ .then(createIframe)
+ .then(createPermissionTester)
+ .then((tester) => {
+ return tester
+ .checkUnsupportedPermissions()
+ .then(() => tester.setPermissions(UNKNOWN_ACTION))
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.setPermissions(PROMPT_ACTION))
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.setPermissions(ALLOW_ACTION))
+ .then(() => tester.checkPermissions('granted'))
+ .then(() => tester.setPermissions(DENY_ACTION))
+ .then(() => tester.checkPermissions('denied'))
+ .then(() => tester.testStatusOnChange())
+ .then(() => tester.testInvalidQuery())
+ .then(() => tester.revokeUnsupportedPermissions())
+ .then(() => tester.revokePermissions())
+ .then(() => tester.checkPermissions('prompt'))
+ .then(() => tester.testInvalidRevoke());
+ })
+ .then(SimpleTest.finish)
+ .catch((e) => {
+ ok(false, `Unexpected error ${e}`);
+ SimpleTest.finish();
+ });
+ };
+ </script>
+</body>
+
+</html>
diff --git a/browser/components/places/PlacesUIUtils.jsm b/browser/components/places/PlacesUIUtils.jsm
new file mode 100644
index 000000000..b25835a71
--- /dev/null
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -0,0 +1,1774 @@
+/* -*- 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 = ["PlacesUIUtils"];
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+
+// PlacesUtils exposes multiple symbols, so we can't use defineLazyModuleGetter.
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
+ "resource://gre/modules/PlacesTransactions.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
+ "resource://gre/modules/CloudSync.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Weave",
+ "resource://services-sync/main.js");
+
+const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+const FAVICON_REQUEST_TIMEOUT = 60 * 1000;
+// Map from windows to arrays of data about pending favicon loads.
+let gFaviconLoadDataMap = new Map();
+
+// copied from utilityOverlay.js
+const TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
+
+// This function isn't public both because it's synchronous and because it is
+// going to be removed in bug 1072833.
+function IsLivemark(aItemId) {
+ // Since this check may be done on each dragover event, it's worth maintaining
+ // a cache.
+ let self = IsLivemark;
+ if (!("ids" in self)) {
+ const LIVEMARK_ANNO = PlacesUtils.LMANNO_FEEDURI;
+
+ let idsVec = PlacesUtils.annotations.getItemsWithAnnotation(LIVEMARK_ANNO);
+ self.ids = new Set(idsVec);
+
+ let obs = Object.freeze({
+ QueryInterface: XPCOMUtils.generateQI(Ci.nsIAnnotationObserver),
+
+ onItemAnnotationSet(itemId, annoName) {
+ if (annoName == LIVEMARK_ANNO)
+ self.ids.add(itemId);
+ },
+
+ onItemAnnotationRemoved(itemId, annoName) {
+ // If annoName is set to an empty string, the item is gone.
+ if (annoName == LIVEMARK_ANNO || annoName == "")
+ self.ids.delete(itemId);
+ },
+
+ onPageAnnotationSet() { },
+ onPageAnnotationRemoved() { },
+ });
+ PlacesUtils.annotations.addObserver(obs);
+ PlacesUtils.registerShutdownFunction(() => {
+ PlacesUtils.annotations.removeObserver(obs);
+ });
+ }
+ return self.ids.has(aItemId);
+}
+
+let InternalFaviconLoader = {
+ /**
+ * This gets called for every inner window that is destroyed.
+ * In the parent process, we process the destruction ourselves. In the child process,
+ * we notify the parent which will then process it based on that message.
+ */
+ observe(subject, topic, data) {
+ let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ this.removeRequestsForInner(innerWindowID);
+ },
+
+ /**
+ * Actually cancel the request, and clear the timeout for cancelling it.
+ */
+ _cancelRequest({uri, innerWindowID, timerID, callback}, reason) {
+ // Break cycle
+ let request = callback.request;
+ delete callback.request;
+ // Ensure we don't time out.
+ clearTimeout(timerID);
+ try {
+ request.cancel();
+ } catch (ex) {
+ Cu.reportError("When cancelling a request for " + uri.spec + " because " + reason + ", it was already canceled!");
+ }
+ },
+
+ /**
+ * Called for every inner that gets destroyed, only in the parent process.
+ */
+ removeRequestsForInner(innerID) {
+ for (let [window, loadDataForWindow] of gFaviconLoadDataMap) {
+ let newLoadDataForWindow = loadDataForWindow.filter(loadData => {
+ let innerWasDestroyed = loadData.innerWindowID == innerID;
+ if (innerWasDestroyed) {
+ this._cancelRequest(loadData, "the inner window was destroyed or a new favicon was loaded for it");
+ }
+ // Keep the items whose inner is still alive.
+ return !innerWasDestroyed;
+ });
+ // Map iteration with for...of is safe against modification, so
+ // now just replace the old value:
+ gFaviconLoadDataMap.set(window, newLoadDataForWindow);
+ }
+ },
+
+ /**
+ * Called when a toplevel chrome window unloads. We use this to tidy up after ourselves,
+ * avoid leaks, and cancel any remaining requests. The last part should in theory be
+ * handled by the inner-window-destroyed handlers. We clean up just to be on the safe side.
+ */
+ onUnload(win) {
+ let loadDataForWindow = gFaviconLoadDataMap.get(win);
+ if (loadDataForWindow) {
+ for (let loadData of loadDataForWindow) {
+ this._cancelRequest(loadData, "the chrome window went away");
+ }
+ }
+ gFaviconLoadDataMap.delete(win);
+ },
+
+ /**
+ * Remove a particular favicon load's loading data from our map tracking
+ * load data per chrome window.
+ *
+ * @param win
+ * the chrome window in which we should look for this load
+ * @param filterData ({innerWindowID, uri, callback})
+ * the data we should use to find this particular load to remove.
+ *
+ * @return the loadData object we removed, or null if we didn't find any.
+ */
+ _removeLoadDataFromWindowMap(win, {innerWindowID, uri, callback}) {
+ let loadDataForWindow = gFaviconLoadDataMap.get(win);
+ if (loadDataForWindow) {
+ let itemIndex = loadDataForWindow.findIndex(loadData => {
+ return loadData.innerWindowID == innerWindowID &&
+ loadData.uri.equals(uri) &&
+ loadData.callback.request == callback.request;
+ });
+ if (itemIndex != -1) {
+ let loadData = loadDataForWindow[itemIndex];
+ loadDataForWindow.splice(itemIndex, 1);
+ return loadData;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Create a function to use as a nsIFaviconDataCallback, so we can remove cancelling
+ * information when the request succeeds. Note that right now there are some edge-cases,
+ * such as about: URIs with chrome:// favicons where the success callback is not invoked.
+ * This is OK: we will 'cancel' the request after the timeout (or when the window goes
+ * away) but that will be a no-op in such cases.
+ */
+ _makeCompletionCallback(win, id) {
+ return {
+ onComplete(uri) {
+ let loadData = InternalFaviconLoader._removeLoadDataFromWindowMap(win, {
+ uri,
+ innerWindowID: id,
+ callback: this,
+ });
+ if (loadData) {
+ clearTimeout(loadData.timerID);
+ }
+ delete this.request;
+ },
+ };
+ },
+
+ ensureInitialized() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+
+ Services.obs.addObserver(this, "inner-window-destroyed", false);
+ Services.ppmm.addMessageListener("Toolkit:inner-window-destroyed", msg => {
+ this.removeRequestsForInner(msg.data);
+ });
+ },
+
+ loadFavicon(browser, principal, uri) {
+ this.ensureInitialized();
+ let win = browser.ownerGlobal;
+ if (!gFaviconLoadDataMap.has(win)) {
+ gFaviconLoadDataMap.set(win, []);
+ let unloadHandler = event => {
+ let doc = event.target;
+ let eventWin = doc.defaultView;
+ if (eventWin == win) {
+ win.removeEventListener("unload", unloadHandler);
+ this.onUnload(win);
+ }
+ };
+ win.addEventListener("unload", unloadHandler, true);
+ }
+
+ let {innerWindowID, currentURI} = browser;
+
+ // Immediately cancel any earlier requests
+ this.removeRequestsForInner(innerWindowID);
+
+ // First we do the actual setAndFetch call:
+ let loadType = PrivateBrowsingUtils.isWindowPrivate(win)
+ ? PlacesUtils.favicons.FAVICON_LOAD_PRIVATE
+ : PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE;
+ let callback = this._makeCompletionCallback(win, innerWindowID);
+ let request = PlacesUtils.favicons.setAndFetchFaviconForPage(currentURI, uri, false,
+ loadType, callback, principal);
+
+ // Now register the result so we can cancel it if/when necessary.
+ if (!request) {
+ // The favicon service can return with success but no-op (and leave request
+ // as null) if the icon is the same as the page (e.g. for images) or if it is
+ // the favicon for an error page. In this case, we do not need to do anything else.
+ return;
+ }
+ callback.request = request;
+ let loadData = {innerWindowID, uri, callback};
+ loadData.timerID = setTimeout(() => {
+ this._cancelRequest(loadData, "it timed out");
+ this._removeLoadDataFromWindowMap(win, loadData);
+ }, FAVICON_REQUEST_TIMEOUT);
+ let loadDataForWindow = gFaviconLoadDataMap.get(win);
+ loadDataForWindow.push(loadData);
+ },
+};
+
+this.PlacesUIUtils = {
+ ORGANIZER_LEFTPANE_VERSION: 7,
+ ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
+ ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
+
+ LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
+ DESCRIPTION_ANNO: "bookmarkProperties/description",
+
+ /**
+ * Makes a URI from a spec, and do fixup
+ * @param aSpec
+ * The string spec of the URI
+ * @return A URI object for the spec.
+ */
+ createFixedURI: function PUIU_createFixedURI(aSpec) {
+ return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
+ },
+
+ getFormattedString: function PUIU_getFormattedString(key, params) {
+ return bundle.formatStringFromName(key, params, params.length);
+ },
+
+ /**
+ * Get a localized plural string for the specified key name and numeric value
+ * substituting parameters.
+ *
+ * @param aKey
+ * String, key for looking up the localized string in the bundle
+ * @param aNumber
+ * Number based on which the final localized form is looked up
+ * @param aParams
+ * Array whose items will substitute #1, #2,... #n parameters
+ * in the string.
+ *
+ * @see https://developer.mozilla.org/en/Localization_and_Plurals
+ * @return The localized plural string.
+ */
+ getPluralString: function PUIU_getPluralString(aKey, aNumber, aParams) {
+ let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey));
+
+ // Replace #1 with aParams[0], #2 with aParams[1], and so on.
+ return str.replace(/\#(\d+)/g, function (matchedId, matchedNumber) {
+ let param = aParams[parseInt(matchedNumber, 10) - 1];
+ return param !== undefined ? param : matchedId;
+ });
+ },
+
+ getString: function PUIU_getString(key) {
+ return bundle.GetStringFromName(key);
+ },
+
+ get _copyableAnnotations() {
+ return [
+ this.DESCRIPTION_ANNO,
+ this.LOAD_IN_SIDEBAR_ANNO,
+ PlacesUtils.READ_ONLY_ANNO,
+ ];
+ },
+
+ /**
+ * Get a transaction for copying a uri item (either a bookmark or a history
+ * entry) from one container to another.
+ *
+ * @param aData
+ * JSON object of dropped or pasted item properties
+ * @param aContainer
+ * The container being copied into
+ * @param aIndex
+ * The index within the container the item is copied to
+ * @return A nsITransaction object that performs the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getURIItemCopyTransaction:
+ function PUIU__getURIItemCopyTransaction(aData, aContainer, aIndex)
+ {
+ let transactions = [];
+ if (aData.dateAdded) {
+ transactions.push(
+ new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
+ );
+ }
+ if (aData.lastModified) {
+ transactions.push(
+ new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
+ );
+ }
+
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function (aAnno) {
+ return this._copyableAnnotations.includes(aAnno.name);
+ }, this);
+ }
+
+ // There's no need to copy the keyword since it's bound to the bookmark url.
+ return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri),
+ aContainer, aIndex, aData.title,
+ null, annos, transactions);
+ },
+
+ /**
+ * Gets a transaction for copying (recursively nesting to include children)
+ * a folder (or container) and its contents from one folder to another.
+ *
+ * @param aData
+ * Unwrapped dropped folder data - Obj containing folder and children
+ * @param aContainer
+ * The container we are copying into
+ * @param aIndex
+ * The index in the destination container to insert the new items
+ * @return A nsITransaction object that will perform the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getFolderCopyTransaction(aData, aContainer, aIndex) {
+ function getChildItemsTransactions(aRoot) {
+ let transactions = [];
+ let index = aIndex;
+ for (let i = 0; i < aRoot.childCount; ++i) {
+ let child = aRoot.getChild(i);
+ // Temporary hacks until we switch to PlacesTransactions.jsm.
+ let isLivemark =
+ PlacesUtils.annotations.itemHasAnnotation(child.itemId,
+ PlacesUtils.LMANNO_FEEDURI);
+ let [node] = PlacesUtils.unwrapNodes(
+ PlacesUtils.wrapNode(child, PlacesUtils.TYPE_X_MOZ_PLACE, isLivemark),
+ PlacesUtils.TYPE_X_MOZ_PLACE
+ );
+
+ // Make sure that items are given the correct index, this will be
+ // passed by the transaction manager to the backend for the insertion.
+ // Insertion behaves differently for DEFAULT_INDEX (append).
+ if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) {
+ index = i;
+ }
+
+ if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+ if (node.livemark && node.annos) {
+ transactions.push(
+ PlacesUIUtils._getLivemarkCopyTransaction(node, aContainer, index)
+ );
+ }
+ else {
+ transactions.push(
+ PlacesUIUtils._getFolderCopyTransaction(node, aContainer, index)
+ );
+ }
+ }
+ else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
+ transactions.push(new PlacesCreateSeparatorTransaction(-1, index));
+ }
+ else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) {
+ transactions.push(
+ PlacesUIUtils._getURIItemCopyTransaction(node, -1, index)
+ );
+ }
+ else {
+ throw new Error("Unexpected item under a bookmarks folder");
+ }
+ }
+ return transactions;
+ }
+
+ if (aContainer == PlacesUtils.tagsFolderId) { // Copying into a tag folder.
+ let transactions = [];
+ if (!aData.livemark && aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+ let {root} = PlacesUtils.getFolderContents(aData.id, false, false);
+ let urls = PlacesUtils.getURLsForContainerNode(root);
+ root.containerOpen = false;
+ for (let { uri } of urls) {
+ transactions.push(
+ new PlacesTagURITransaction(NetUtil.newURI(uri), [aData.title])
+ );
+ }
+ }
+ return new PlacesAggregatedTransaction("addTags", transactions);
+ }
+
+ if (aData.livemark && aData.annos) { // Copying a livemark.
+ return this._getLivemarkCopyTransaction(aData, aContainer, aIndex);
+ }
+
+ let {root} = PlacesUtils.getFolderContents(aData.id, false, false);
+ let transactions = getChildItemsTransactions(root);
+ root.containerOpen = false;
+
+ if (aData.dateAdded) {
+ transactions.push(
+ new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
+ );
+ }
+ if (aData.lastModified) {
+ transactions.push(
+ new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
+ );
+ }
+
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function (aAnno) {
+ return this._copyableAnnotations.includes(aAnno.name);
+ }, this);
+ }
+
+ return new PlacesCreateFolderTransaction(aData.title, aContainer, aIndex,
+ annos, transactions);
+ },
+
+ /**
+ * Gets a transaction for copying a live bookmark item from one container to
+ * another.
+ *
+ * @param aData
+ * Unwrapped live bookmarkmark data
+ * @param aContainer
+ * The container we are copying into
+ * @param aIndex
+ * The index in the destination container to insert the new items
+ * @return A nsITransaction object that will perform the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getLivemarkCopyTransaction:
+ function PUIU__getLivemarkCopyTransaction(aData, aContainer, aIndex)
+ {
+ if (!aData.livemark || !aData.annos) {
+ throw new Error("node is not a livemark");
+ }
+
+ let feedURI, siteURI;
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function (aAnno) {
+ if (aAnno.name == PlacesUtils.LMANNO_FEEDURI) {
+ feedURI = PlacesUtils._uri(aAnno.value);
+ }
+ else if (aAnno.name == PlacesUtils.LMANNO_SITEURI) {
+ siteURI = PlacesUtils._uri(aAnno.value);
+ }
+ return this._copyableAnnotations.includes(aAnno.name)
+ }, this);
+ }
+
+ return new PlacesCreateLivemarkTransaction(feedURI, siteURI, aData.title,
+ aContainer, aIndex, annos);
+ },
+
+ /**
+ * Constructs a Transaction for the drop or paste of a blob of data into
+ * a container.
+ * @param data
+ * The unwrapped data blob of dropped or pasted data.
+ * @param type
+ * The content type of the data
+ * @param container
+ * The container the data was dropped or pasted into
+ * @param index
+ * The index within the container the item was dropped or pasted at
+ * @param copy
+ * The drag action was copy, so don't move folders or links.
+ * @return An object implementing nsITransaction that can perform
+ * the move/insert.
+ */
+ makeTransaction:
+ function PUIU_makeTransaction(data, type, container, index, copy)
+ {
+ switch (data.type) {
+ case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
+ if (copy) {
+ return this._getFolderCopyTransaction(data, container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ case PlacesUtils.TYPE_X_MOZ_PLACE:
+ if (copy || data.id == -1) { // Id is -1 if the place is not bookmarked.
+ return this._getURIItemCopyTransaction(data, container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
+ if (copy) {
+ // There is no data in a separator, so copying it just amounts to
+ // inserting a new separator.
+ return new PlacesCreateSeparatorTransaction(container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ default:
+ if (type == PlacesUtils.TYPE_X_MOZ_URL ||
+ type == PlacesUtils.TYPE_UNICODE ||
+ type == TAB_DROP_TYPE) {
+ let title = type != PlacesUtils.TYPE_UNICODE ? data.title
+ : data.uri;
+ return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
+ container, index, title);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * ********* PlacesTransactions version of the function defined above ********
+ *
+ * Constructs a Places Transaction for the drop or paste of a blob of data
+ * into a container.
+ *
+ * @param aData
+ * The unwrapped data blob of dropped or pasted data.
+ * @param aType
+ * The content type of the data.
+ * @param aNewParentGuid
+ * GUID of the container the data was dropped or pasted into.
+ * @param aIndex
+ * The index within the container the item was dropped or pasted at.
+ * @param aCopy
+ * The drag action was copy, so don't move folders or links.
+ *
+ * @return a Places Transaction that can be transacted for performing the
+ * move/insert command.
+ */
+ getTransactionForData: function(aData, aType, aNewParentGuid, aIndex, aCopy) {
+ if (!this.SUPPORTED_FLAVORS.includes(aData.type))
+ throw new Error(`Unsupported '${aData.type}' data type`);
+
+ if ("itemGuid" in aData) {
+ if (!this.PLACES_FLAVORS.includes(aData.type))
+ throw new Error (`itemGuid unexpectedly set on ${aData.type} data`);
+
+ let info = { guid: aData.itemGuid
+ , newParentGuid: aNewParentGuid
+ , newIndex: aIndex };
+ if (aCopy) {
+ info.excludingAnnotation = "Places/SmartBookmark";
+ return PlacesTransactions.Copy(info);
+ }
+ return PlacesTransactions.Move(info);
+ }
+
+ // Since it's cheap and harmless, we allow the paste of separators and
+ // bookmarks from builds that use legacy transactions (i.e. when itemGuid
+ // was not set on PLACES_FLAVORS data). Containers are a different story,
+ // and thus disallowed.
+ if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
+ throw new Error("Can't copy a container from a legacy-transactions build");
+
+ if (aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
+ return PlacesTransactions.NewSeparator({ parentGuid: aNewParentGuid
+ , index: aIndex });
+ }
+
+ let title = aData.type != PlacesUtils.TYPE_UNICODE ? aData.title
+ : aData.uri;
+ return PlacesTransactions.NewBookmark({ uri: NetUtil.newURI(aData.uri)
+ , title: title
+ , parentGuid: aNewParentGuid
+ , index: aIndex });
+ },
+
+ /**
+ * Shows the bookmark dialog corresponding to the specified info.
+ *
+ * @param aInfo
+ * Describes the item to be edited/added in the dialog.
+ * See documentation at the top of bookmarkProperties.js
+ * @param aWindow
+ * Owner window for the new dialog.
+ *
+ * @see documentation at the top of bookmarkProperties.js
+ * @return true if any transaction has been performed, false otherwise.
+ */
+ showBookmarkDialog:
+ function PUIU_showBookmarkDialog(aInfo, aParentWindow) {
+ // Preserve size attributes differently based on the fact the dialog has
+ // a folder picker or not, since it needs more horizontal space than the
+ // other controls.
+ let hasFolderPicker = !("hiddenRows" in aInfo) ||
+ !aInfo.hiddenRows.includes("folderPicker");
+ // Use a different chrome url to persist different sizes.
+ let dialogURL = hasFolderPicker ?
+ "chrome://browser/content/places/bookmarkProperties2.xul" :
+ "chrome://browser/content/places/bookmarkProperties.xul";
+
+ let features = "centerscreen,chrome,modal,resizable=yes";
+ aParentWindow.openDialog(dialogURL, "", features, aInfo);
+ return ("performed" in aInfo && aInfo.performed);
+ },
+
+ _getTopBrowserWin: function PUIU__getTopBrowserWin() {
+ return RecentWindow.getMostRecentBrowserWindow();
+ },
+
+ /**
+ * set and fetch a favicon. Can only be used from the parent process.
+ * @param browser {Browser} The XUL browser element for which we're fetching a favicon.
+ * @param principal {Principal} The loading principal to use for the fetch.
+ * @param uri {URI} The URI to fetch.
+ */
+ loadFavicon(browser, principal, uri) {
+ if (gInContentProcess) {
+ throw new Error("Can't track loads from within the child process!");
+ }
+ InternalFaviconLoader.loadFavicon(browser, principal, uri);
+ },
+
+ /**
+ * Returns the closet ancestor places view for the given DOM node
+ * @param aNode
+ * a DOM node
+ * @return the closet ancestor places view if exists, null otherwsie.
+ */
+ getViewForNode: function PUIU_getViewForNode(aNode) {
+ let node = aNode;
+
+ // The view for a <menu> of which its associated menupopup is a places
+ // view, is the menupopup.
+ if (node.localName == "menu" && !node._placesNode &&
+ node.lastChild._placesView)
+ return node.lastChild._placesView;
+
+ while (node instanceof Ci.nsIDOMElement) {
+ if (node._placesView)
+ return node._placesView;
+ if (node.localName == "tree" && node.getAttribute("type") == "places")
+ return node;
+
+ node = node.parentNode;
+ }
+
+ return null;
+ },
+
+ /**
+ * By calling this before visiting an URL, the visit will be associated to a
+ * TRANSITION_TYPED transition (if there is no a referrer).
+ * This is used when visiting pages from the history menu, history sidebar,
+ * url bar, url autocomplete results, and history searches from the places
+ * organizer. If this is not called visits will be marked as
+ * TRANSITION_LINK.
+ */
+ markPageAsTyped: function PUIU_markPageAsTyped(aURL) {
+ PlacesUtils.history.markPageAsTyped(this.createFixedURI(aURL));
+ },
+
+ /**
+ * By calling this before visiting an URL, the visit will be associated to a
+ * TRANSITION_BOOKMARK transition.
+ * This is used when visiting pages from the bookmarks menu,
+ * personal toolbar, and bookmarks from within the places organizer.
+ * If this is not called visits will be marked as TRANSITION_LINK.
+ */
+ markPageAsFollowedBookmark: function PUIU_markPageAsFollowedBookmark(aURL) {
+ PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL));
+ },
+
+ /**
+ * By calling this before visiting an URL, any visit in frames will be
+ * associated to a TRANSITION_FRAMED_LINK transition.
+ * This is actually used to distinguish user-initiated visits in frames
+ * so automatic visits can be correctly ignored.
+ */
+ markPageAsFollowedLink: function PUIU_markPageAsFollowedLink(aURL) {
+ PlacesUtils.history.markPageAsFollowedLink(this.createFixedURI(aURL));
+ },
+
+ /**
+ * Allows opening of javascript/data URI only if the given node is
+ * bookmarked (see bug 224521).
+ * @param aURINode
+ * a URI node
+ * @param aWindow
+ * a window on which a potential error alert is shown on.
+ * @return true if it's safe to open the node in the browser, false otherwise.
+ *
+ */
+ checkURLSecurity: function PUIU_checkURLSecurity(aURINode, aWindow) {
+ if (PlacesUtils.nodeIsBookmark(aURINode))
+ return true;
+
+ var uri = PlacesUtils._uri(aURINode.uri);
+ if (uri.schemeIs("javascript") || uri.schemeIs("data")) {
+ const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
+ var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(BRANDING_BUNDLE_URI).
+ GetStringFromName("brandShortName");
+
+ var errorStr = this.getString("load-js-data-url-error");
+ Services.prompt.alert(aWindow, brandShortName, errorStr);
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Get the description associated with a document, as specified in a <META>
+ * element.
+ * @param doc
+ * A DOM Document to get a description for
+ * @return A description string if a META element was discovered with a
+ * "description" or "httpequiv" attribute, empty string otherwise.
+ */
+ getDescriptionFromDocument: function PUIU_getDescriptionFromDocument(doc) {
+ var metaElements = doc.getElementsByTagName("META");
+ for (var i = 0; i < metaElements.length; ++i) {
+ if (metaElements[i].name.toLowerCase() == "description" ||
+ metaElements[i].httpEquiv.toLowerCase() == "description") {
+ return metaElements[i].content;
+ }
+ }
+ return "";
+ },
+
+ /**
+ * Retrieve the description of an item
+ * @param aItemId
+ * item identifier
+ * @return the description of the given item, or an empty string if it is
+ * not set.
+ */
+ getItemDescription: function PUIU_getItemDescription(aItemId) {
+ if (PlacesUtils.annotations.itemHasAnnotation(aItemId, this.DESCRIPTION_ANNO))
+ return PlacesUtils.annotations.getItemAnnotation(aItemId, this.DESCRIPTION_ANNO);
+ return "";
+ },
+
+ /**
+ * Check whether or not the given node represents a removable entry (either in
+ * history or in bookmarks).
+ *
+ * @param aNode
+ * a node, except the root node of a query.
+ * @return true if the aNode represents a removable entry, false otherwise.
+ */
+ canUserRemove: function (aNode) {
+ let parentNode = aNode.parent;
+ if (!parentNode) {
+ // canUserRemove doesn't accept root nodes.
+ return false;
+ }
+
+ // If it's not a bookmark, we can remove it unless it's a child of a
+ // livemark.
+ if (aNode.itemId == -1) {
+ // Rather than executing a db query, checking the existence of the feedURI
+ // annotation, detect livemark children by the fact that they are the only
+ // direct non-bookmark children of bookmark folders.
+ return !PlacesUtils.nodeIsFolder(parentNode);
+ }
+
+ // Generally it's always possible to remove children of a query.
+ if (PlacesUtils.nodeIsQuery(parentNode))
+ return true;
+
+ // Otherwise it has to be a child of an editable folder.
+ return !this.isContentsReadOnly(parentNode);
+ },
+
+ /**
+ * DO NOT USE THIS API IN ADDONS. IT IS VERY LIKELY TO CHANGE WHEN THE SWITCH
+ * TO GUIDS IS COMPLETE (BUG 1071511).
+ *
+ * Check whether or not the given node or item-id points to a folder which
+ * should not be modified by the user (i.e. its children should be unremovable
+ * and unmovable, new children should be disallowed, etc).
+ * These semantics are not inherited, meaning that read-only folder may
+ * contain editable items (for instance, the places root is read-only, but all
+ * of its direct children aren't).
+ *
+ * You should only pass folder item ids or folder nodes for aNodeOrItemId.
+ * While this is only enforced for the node case (if an item id of a separator
+ * or a bookmark is passed, false is returned), it's considered the caller's
+ * job to ensure that it checks a folder.
+ * Also note that folder-shortcuts should only be passed as result nodes.
+ * Otherwise they are just treated as bookmarks (i.e. false is returned).
+ *
+ * @param aNodeOrItemId
+ * any item id or result node.
+ * @throws if aNodeOrItemId is neither an item id nor a folder result node.
+ * @note livemark "folders" are considered read-only (but see bug 1072833).
+ * @return true if aItemId points to a read-only folder, false otherwise.
+ */
+ isContentsReadOnly: function (aNodeOrItemId) {
+ let itemId;
+ if (typeof(aNodeOrItemId) == "number") {
+ itemId = aNodeOrItemId;
+ }
+ else if (PlacesUtils.nodeIsFolder(aNodeOrItemId)) {
+ itemId = PlacesUtils.getConcreteItemId(aNodeOrItemId);
+ }
+ else {
+ throw new Error("invalid value for aNodeOrItemId");
+ }
+
+ if (itemId == PlacesUtils.placesRootId || IsLivemark(itemId))
+ return true;
+
+ // leftPaneFolderId, and as a result, allBookmarksFolderId, is a lazy getter
+ // performing at least a synchronous DB query (and on its very first call
+ // in a fresh profile, it also creates the entire structure).
+ // Therefore we don't want to this function, which is called very often by
+ // isCommandEnabled, to ever be the one that invokes it first, especially
+ // because isCommandEnabled may be called way before the left pane folder is
+ // even created (for example, if the user only uses the bookmarks menu or
+ // toolbar for managing bookmarks). To do so, we avoid comparing to those
+ // special folder if the lazy getter is still in place. This is safe merely
+ // because the only way to access the left pane contents goes through
+ // "resolving" the leftPaneFolderId getter.
+ if ("get" in Object.getOwnPropertyDescriptor(this, "leftPaneFolderId"))
+ return false;
+
+ return itemId == this.leftPaneFolderId ||
+ itemId == this.allBookmarksFolderId;
+ },
+
+ /**
+ * Gives the user a chance to cancel loading lots of tabs at once
+ */
+ confirmOpenInTabs(numTabsToOpen, aWindow) {
+ const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
+ var reallyOpen = true;
+
+ if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) {
+ if (numTabsToOpen >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
+ // default to true: if it were false, we wouldn't get this far
+ var warnOnOpen = { value: true };
+
+ var messageKey = "tabs.openWarningMultipleBranded";
+ var openKey = "tabs.openButtonMultiple";
+ const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
+ var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(BRANDING_BUNDLE_URI).
+ GetStringFromName("brandShortName");
+
+ var buttonPressed = Services.prompt.confirmEx(
+ aWindow,
+ this.getString("tabs.openWarningTitle"),
+ this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]),
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1),
+ this.getString(openKey), null, null,
+ this.getFormattedString("tabs.openWarningPromptMeBranded",
+ [brandShortName]),
+ warnOnOpen
+ );
+
+ reallyOpen = (buttonPressed == 0);
+ // don't set the pref unless they press OK and it's false
+ if (reallyOpen && !warnOnOpen.value)
+ Services.prefs.setBoolPref(WARN_ON_OPEN_PREF, false);
+ }
+ }
+
+ return reallyOpen;
+ },
+
+ /** aItemsToOpen needs to be an array of objects of the form:
+ * {uri: string, isBookmark: boolean}
+ */
+ _openTabset: function PUIU__openTabset(aItemsToOpen, aEvent, aWindow) {
+ if (!aItemsToOpen.length)
+ return;
+
+ // Prefer the caller window if it's a browser window, otherwise use
+ // the top browser window.
+ var browserWindow = null;
+ browserWindow =
+ aWindow && aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser" ?
+ aWindow : this._getTopBrowserWin();
+
+ var urls = [];
+ let skipMarking = browserWindow && PrivateBrowsingUtils.isWindowPrivate(browserWindow);
+ for (let item of aItemsToOpen) {
+ urls.push(item.uri);
+ if (skipMarking) {
+ continue;
+ }
+
+ if (item.isBookmark)
+ this.markPageAsFollowedBookmark(item.uri);
+ else
+ this.markPageAsTyped(item.uri);
+ }
+
+ // whereToOpenLink doesn't return "window" when there's no browser window
+ // open (Bug 630255).
+ var where = browserWindow ?
+ browserWindow.whereToOpenLink(aEvent, false, true) : "window";
+ if (where == "window") {
+ // There is no browser window open, thus open a new one.
+ var uriList = PlacesUtils.toISupportsString(urls.join("|"));
+ var args = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ args.appendElement(uriList, /* weak =*/ false);
+ browserWindow = Services.ww.openWindow(aWindow,
+ "chrome://browser/content/browser.xul",
+ null, "chrome,dialog=no,all", args);
+ return;
+ }
+
+ var loadInBackground = where == "tabshifted" ? true : false;
+ // For consistency, we want all the bookmarks to open in new tabs, instead
+ // of having one of them replace the currently focused tab. Hence we call
+ // loadTabs with aReplace set to false.
+ browserWindow.gBrowser.loadTabs(urls, loadInBackground, false);
+ },
+
+ openLiveMarkNodesInTabs:
+ function PUIU_openLiveMarkNodesInTabs(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ PlacesUtils.livemarks.getLivemark({id: aNode.itemId})
+ .then(aLivemark => {
+ let urlsToOpen = [];
+
+ let nodes = aLivemark.getNodesForContainer(aNode);
+ for (let node of nodes) {
+ urlsToOpen.push({uri: node.uri, isBookmark: false});
+ }
+
+ if (this.confirmOpenInTabs(urlsToOpen.length, window)) {
+ this._openTabset(urlsToOpen, aEvent, window);
+ }
+ }, Cu.reportError);
+ },
+
+ openContainerNodeInTabs:
+ function PUIU_openContainerInTabs(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
+ if (this.confirmOpenInTabs(urlsToOpen.length, window)) {
+ this._openTabset(urlsToOpen, aEvent, window);
+ }
+ },
+
+ openURINodesInTabs: function PUIU_openURINodesInTabs(aNodes, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ let urlsToOpen = [];
+ for (var i=0; i < aNodes.length; i++) {
+ // Skip over separators and folders.
+ if (PlacesUtils.nodeIsURI(aNodes[i]))
+ urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])});
+ }
+ this._openTabset(urlsToOpen, aEvent, window);
+ },
+
+ /**
+ * Loads the node's URL in the appropriate tab or window or as a web
+ * panel given the user's preference specified by modifier keys tracked by a
+ * DOM mouse/key event.
+ * @param aNode
+ * An uri result node.
+ * @param aEvent
+ * The DOM mouse/key event with modifier keys set that track the
+ * user's preferred destination window or tab.
+ * @param aView
+ * The controller associated with aNode.
+ */
+ openNodeWithEvent:
+ function PUIU_openNodeWithEvent(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+ this._openNodeIn(aNode, window.whereToOpenLink(aEvent, false, true), window);
+ },
+
+ /**
+ * Loads the node's URL in the appropriate tab or window or as a
+ * web panel.
+ * see also openUILinkIn
+ */
+ openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aView, aPrivate) {
+ let window = aView.ownerWindow;
+ this._openNodeIn(aNode, aWhere, window, aPrivate);
+ },
+
+ _openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aWindow, aPrivate=false) {
+ if (aNode && PlacesUtils.nodeIsURI(aNode) &&
+ this.checkURLSecurity(aNode, aWindow)) {
+ let isBookmark = PlacesUtils.nodeIsBookmark(aNode);
+
+ if (!PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
+ if (isBookmark)
+ this.markPageAsFollowedBookmark(aNode.uri);
+ else
+ this.markPageAsTyped(aNode.uri);
+ }
+
+ // Check whether the node is a bookmark which should be opened as
+ // a web panel
+ if (aWhere == "current" && isBookmark) {
+ if (PlacesUtils.annotations
+ .itemHasAnnotation(aNode.itemId, this.LOAD_IN_SIDEBAR_ANNO)) {
+ let browserWin = this._getTopBrowserWin();
+ if (browserWin) {
+ browserWin.openWebPanel(aNode.title, aNode.uri);
+ return;
+ }
+ }
+ }
+
+ aWindow.openUILinkIn(aNode.uri, aWhere, {
+ allowPopups: aNode.uri.startsWith("javascript:"),
+ inBackground: Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground"),
+ private: aPrivate,
+ });
+ }
+ },
+
+ /**
+ * Helper for guessing scheme from an url string.
+ * Used to avoid nsIURI overhead in frequently called UI functions.
+ *
+ * @param aUrlString the url to guess the scheme from.
+ *
+ * @return guessed scheme for this url string.
+ *
+ * @note this is not supposed be perfect, so use it only for UI purposes.
+ */
+ guessUrlSchemeForUI: function PUIU_guessUrlSchemeForUI(aUrlString) {
+ return aUrlString.substr(0, aUrlString.indexOf(":"));
+ },
+
+ getBestTitle: function PUIU_getBestTitle(aNode, aDoNotCutTitle) {
+ var title;
+ if (!aNode.title && PlacesUtils.nodeIsURI(aNode)) {
+ // if node title is empty, try to set the label using host and filename
+ // PlacesUtils._uri() will throw if aNode.uri is not a valid URI
+ try {
+ var uri = PlacesUtils._uri(aNode.uri);
+ var host = uri.host;
+ var fileName = uri.QueryInterface(Ci.nsIURL).fileName;
+ // if fileName is empty, use path to distinguish labels
+ if (aDoNotCutTitle) {
+ title = host + uri.path;
+ } else {
+ title = host + (fileName ?
+ (host ? "/" + this.ellipsis + "/" : "") + fileName :
+ uri.path);
+ }
+ }
+ catch (e) {
+ // Use (no title) for non-standard URIs (data:, javascript:, ...)
+ title = "";
+ }
+ }
+ else
+ title = aNode.title;
+
+ return title || this.getString("noTitle");
+ },
+
+ get leftPaneQueries() {
+ // build the map
+ this.leftPaneFolderId;
+ return this.leftPaneQueries;
+ },
+
+ // Get the folder id for the organizer left-pane folder.
+ get leftPaneFolderId() {
+ let leftPaneRoot = -1;
+ let allBookmarksId;
+
+ // Shortcuts to services.
+ let bs = PlacesUtils.bookmarks;
+ let as = PlacesUtils.annotations;
+
+ // This is the list of the left pane queries.
+ let queries = {
+ "PlacesRoot": { title: "" },
+ "History": { title: this.getString("OrganizerQueryHistory") },
+ "Downloads": { title: this.getString("OrganizerQueryDownloads") },
+ "Tags": { title: this.getString("OrganizerQueryTags") },
+ "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") },
+ "BookmarksToolbar":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"),
+ concreteId: PlacesUtils.toolbarFolderId },
+ "BookmarksMenu":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
+ concreteId: PlacesUtils.bookmarksMenuFolderId },
+ "UnfiledBookmarks":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("OtherBookmarksFolderTitle"),
+ concreteId: PlacesUtils.unfiledBookmarksFolderId },
+ };
+ // All queries but PlacesRoot.
+ const EXPECTED_QUERY_COUNT = 7;
+
+ // Removes an item and associated annotations, ignoring eventual errors.
+ function safeRemoveItem(aItemId) {
+ try {
+ if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) &&
+ !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) {
+ // Some extension annotated their roots with our query annotation,
+ // so we should not delete them.
+ return;
+ }
+ // removeItemAnnotation does not check if item exists, nor the anno,
+ // so this is safe to do.
+ as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO);
+ // This will throw if the annotation is an orphan.
+ bs.removeItem(aItemId);
+ }
+ catch (e) { /* orphan anno */ }
+ }
+
+ // Returns true if item really exists, false otherwise.
+ function itemExists(aItemId) {
+ try {
+ bs.getItemIndex(aItemId);
+ return true;
+ }
+ catch (e) {
+ return false;
+ }
+ }
+
+ // Get all items marked as being the left pane folder.
+ let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO);
+ if (items.length > 1) {
+ // Something went wrong, we cannot have more than one left pane folder,
+ // remove all left pane folders and continue. We will create a new one.
+ items.forEach(safeRemoveItem);
+ }
+ else if (items.length == 1 && items[0] != -1) {
+ leftPaneRoot = items[0];
+ // Check that organizer left pane root is valid.
+ let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO);
+ if (version != this.ORGANIZER_LEFTPANE_VERSION ||
+ !itemExists(leftPaneRoot)) {
+ // Invalid root, we must rebuild the left pane.
+ safeRemoveItem(leftPaneRoot);
+ leftPaneRoot = -1;
+ }
+ }
+
+ if (leftPaneRoot != -1) {
+ // A valid left pane folder has been found.
+ // Build the leftPaneQueries Map. This is used to quickly access them,
+ // associating a mnemonic name to the real item ids.
+ delete this.leftPaneQueries;
+ this.leftPaneQueries = {};
+
+ let items = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO);
+ // While looping through queries we will also check for their validity.
+ let queriesCount = 0;
+ let corrupt = false;
+ for (let i = 0; i < items.length; i++) {
+ let queryName = as.getItemAnnotation(items[i], this.ORGANIZER_QUERY_ANNO);
+
+ // Some extension did use our annotation to decorate their items
+ // with icons, so we should check only our elements, to avoid dataloss.
+ if (!(queryName in queries))
+ continue;
+
+ let query = queries[queryName];
+ query.itemId = items[i];
+
+ if (!itemExists(query.itemId)) {
+ // Orphan annotation, bail out and create a new left pane root.
+ corrupt = true;
+ break;
+ }
+
+ // Check that all queries have valid parents.
+ let parentId = bs.getFolderIdForItem(query.itemId);
+ if (!items.includes(parentId) && parentId != leftPaneRoot) {
+ // The parent is not part of the left pane, bail out and create a new
+ // left pane root.
+ corrupt = true;
+ break;
+ }
+
+ // Titles could have been corrupted or the user could have changed his
+ // locale. Check title and eventually fix it.
+ if (bs.getItemTitle(query.itemId) != query.title)
+ bs.setItemTitle(query.itemId, query.title);
+ if ("concreteId" in query) {
+ if (bs.getItemTitle(query.concreteId) != query.concreteTitle)
+ bs.setItemTitle(query.concreteId, query.concreteTitle);
+ }
+
+ // Add the query to our cache.
+ this.leftPaneQueries[queryName] = query.itemId;
+ queriesCount++;
+ }
+
+ // Note: it's not enough to just check for queriesCount, since we may
+ // find an invalid query just after accounting for a sufficient number of
+ // valid ones. As well as we can't just rely on corrupt since we may find
+ // less valid queries than expected.
+ if (corrupt || queriesCount != EXPECTED_QUERY_COUNT) {
+ // Queries number is wrong, so the left pane must be corrupt.
+ // Note: we can't just remove the leftPaneRoot, because some query could
+ // have a bad parent, so we have to remove all items one by one.
+ items.forEach(safeRemoveItem);
+ safeRemoveItem(leftPaneRoot);
+ }
+ else {
+ // Everything is fine, return the current left pane folder.
+ delete this.leftPaneFolderId;
+ return this.leftPaneFolderId = leftPaneRoot;
+ }
+ }
+
+ // Create a new left pane folder.
+ var callback = {
+ // Helper to create an organizer special query.
+ create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) {
+ let itemId = bs.insertBookmark(aParentId,
+ PlacesUtils._uri(aQueryUrl),
+ bs.DEFAULT_INDEX,
+ queries[aQueryName].title);
+ // Mark as special organizer query.
+ as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName,
+ 0, as.EXPIRE_NEVER);
+ // We should never backup this, since it changes between profiles.
+ as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
+ 0, as.EXPIRE_NEVER);
+ // Add to the queries map.
+ PlacesUIUtils.leftPaneQueries[aQueryName] = itemId;
+ return itemId;
+ },
+
+ // Helper to create an organizer special folder.
+ create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
+ // Left Pane Root Folder.
+ let folderId = bs.createFolder(aParentId,
+ queries[aFolderName].title,
+ bs.DEFAULT_INDEX);
+ // We should never backup this, since it changes between profiles.
+ as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
+ 0, as.EXPIRE_NEVER);
+
+ if (aIsRoot) {
+ // Mark as special left pane root.
+ as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
+ PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
+ 0, as.EXPIRE_NEVER);
+ }
+ else {
+ // Mark as special organizer folder.
+ as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName,
+ 0, as.EXPIRE_NEVER);
+ PlacesUIUtils.leftPaneQueries[aFolderName] = folderId;
+ }
+ return folderId;
+ },
+
+ runBatched: function CB_runBatched(aUserData) {
+ delete PlacesUIUtils.leftPaneQueries;
+ PlacesUIUtils.leftPaneQueries = { };
+
+ // Left Pane Root Folder.
+ leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true);
+
+ // History Query.
+ this.create_query("History", leftPaneRoot,
+ "place:type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
+
+ // Downloads.
+ this.create_query("Downloads", leftPaneRoot,
+ "place:transition=" +
+ Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
+
+ // Tags Query.
+ this.create_query("Tags", leftPaneRoot,
+ "place:type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING);
+
+ // All Bookmarks Folder.
+ allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false);
+
+ // All Bookmarks->Bookmarks Toolbar Query.
+ this.create_query("BookmarksToolbar", allBookmarksId,
+ "place:folder=TOOLBAR");
+
+ // All Bookmarks->Bookmarks Menu Query.
+ this.create_query("BookmarksMenu", allBookmarksId,
+ "place:folder=BOOKMARKS_MENU");
+
+ // All Bookmarks->Unfiled Bookmarks Query.
+ this.create_query("UnfiledBookmarks", allBookmarksId,
+ "place:folder=UNFILED_BOOKMARKS");
+ }
+ };
+ bs.runInBatchMode(callback, null);
+
+ delete this.leftPaneFolderId;
+ return this.leftPaneFolderId = leftPaneRoot;
+ },
+
+ /**
+ * Get the folder id for the organizer left-pane folder.
+ */
+ get allBookmarksFolderId() {
+ // ensure the left-pane root is initialized;
+ this.leftPaneFolderId;
+ delete this.allBookmarksFolderId;
+ return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"];
+ },
+
+ /**
+ * If an item is a left-pane query, returns the name of the query
+ * or an empty string if not.
+ *
+ * @param aItemId id of a container
+ * @return the name of the query, or empty string if not a left-pane query
+ */
+ getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(aItemId) {
+ var queryName = "";
+ // If the let pane hasn't been built, use the annotation service
+ // directly, to avoid building the left pane too early.
+ if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) {
+ try {
+ queryName = PlacesUtils.annotations.
+ getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO);
+ }
+ catch (ex) {
+ // doesn't have the annotation
+ queryName = "";
+ }
+ }
+ else {
+ // If the left pane has already been built, use the name->id map
+ // cached in PlacesUIUtils.
+ for (let [name, id] of Object.entries(this.leftPaneQueries)) {
+ if (aItemId == id)
+ queryName = name;
+ }
+ }
+ return queryName;
+ },
+
+ shouldShowTabsFromOtherComputersMenuitem: function() {
+ let weaveOK = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED &&
+ Weave.Svc.Prefs.get("firstSync", "") != "notReady";
+ return weaveOK;
+ },
+
+ /**
+ * WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT'S LIKELY TO BE REMOVED IN A
+ * FUTURE RELEASE.
+ *
+ * Checks if a place: href represents a folder shortcut.
+ *
+ * @param queryString
+ * the query string to check (a place: href)
+ * @return whether or not queryString represents a folder shortcut.
+ * @throws if queryString is malformed.
+ */
+ isFolderShortcutQueryString(queryString) {
+ // Based on GetSimpleBookmarksQueryFolder in nsNavHistory.cpp.
+
+ let queriesParam = { }, optionsParam = { };
+ PlacesUtils.history.queryStringToQueries(queryString,
+ queriesParam,
+ { },
+ optionsParam);
+ let queries = queries.value;
+ if (queries.length == 0)
+ throw new Error(`Invalid place: uri: ${queryString}`);
+ return queries.length == 1 &&
+ queries[0].folderCount == 1 &&
+ !queries[0].hasBeginTime &&
+ !queries[0].hasEndTime &&
+ !queries[0].hasDomain &&
+ !queries[0].hasURI &&
+ !queries[0].hasSearchTerms &&
+ !queries[0].tags.length == 0 &&
+ optionsParam.value.maxResults == 0;
+ },
+
+ /**
+ * WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT"S LIKELY TO BE REMOVED IN A
+ * FUTURE RELEASE.
+ *
+ * Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
+ * Given a partial node-like object, having at least the itemId property set, this
+ * method completes the rest of the properties necessary for initialising the edit
+ * overlay with it.
+ *
+ * @param aNodeLike
+ * an object having at least the itemId nsINavHistoryResultNode property set,
+ * along with any other properties available.
+ */
+ completeNodeLikeObjectForItemId(aNodeLike) {
+ if (this.useAsyncTransactions) {
+ // When async-transactions are enabled, node-likes must have
+ // bookmarkGuid set, and we cannot set it synchronously.
+ throw new Error("completeNodeLikeObjectForItemId cannot be used when " +
+ "async transactions are enabled");
+ }
+ if (!("itemId" in aNodeLike))
+ throw new Error("itemId missing in aNodeLike");
+
+ let itemId = aNodeLike.itemId;
+ let defGetter = XPCOMUtils.defineLazyGetter.bind(XPCOMUtils, aNodeLike);
+
+ if (!("title" in aNodeLike))
+ defGetter("title", () => PlacesUtils.bookmarks.getItemTitle(itemId));
+
+ if (!("uri" in aNodeLike)) {
+ defGetter("uri", () => {
+ let uri = null;
+ try {
+ uri = PlacesUtils.bookmarks.getBookmarkURI(itemId);
+ }
+ catch (ex) { }
+ return uri ? uri.spec : "";
+ });
+ }
+
+ if (!("type" in aNodeLike)) {
+ defGetter("type", () => {
+ if (aNodeLike.uri.length > 0) {
+ if (/^place:/.test(aNodeLike.uri)) {
+ if (this.isFolderShortcutQueryString(aNodeLike.uri))
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
+
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
+ }
+
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
+ }
+
+ let itemType = PlacesUtils.bookmarks.getItemType(itemId);
+ if (itemType == PlacesUtils.bookmarks.TYPE_FOLDER)
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
+
+ throw new Error("Unexpected item type");
+ });
+ }
+ },
+
+ /**
+ * Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
+ *
+ * Given a bookmark object for either a url bookmark or a folder, returned by
+ * Bookmarks.fetch (see Bookmark.jsm), this creates a node-like object suitable for
+ * initialising the edit overlay with it.
+ *
+ * @param aFetchInfo
+ * a bookmark object returned by Bookmarks.fetch.
+ * @return a node-like object suitable for initialising editBookmarkOverlay.
+ * @throws if aFetchInfo is representing a separator.
+ */
+ promiseNodeLikeFromFetchInfo: Task.async(function* (aFetchInfo) {
+ if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR)
+ throw new Error("promiseNodeLike doesn't support separators");
+
+ return Object.freeze({
+ itemId: yield PlacesUtils.promiseItemId(aFetchInfo.guid),
+ bookmarkGuid: aFetchInfo.guid,
+ title: aFetchInfo.title,
+ uri: aFetchInfo.url !== undefined ? aFetchInfo.url.href : "",
+
+ get type() {
+ if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_FOLDER)
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
+
+ if (this.uri.length == 0)
+ throw new Error("Unexpected item type");
+
+ if (/^place:/.test(this.uri)) {
+ if (this.isFolderShortcutQueryString(this.uri))
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
+
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
+ }
+
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
+ }
+ });
+ }),
+
+ /**
+ * Shortcut for calling promiseNodeLikeFromFetchInfo on the result of
+ * Bookmarks.fetch for the given guid/info object.
+ *
+ * @see promiseNodeLikeFromFetchInfo above and Bookmarks.fetch in Bookmarks.jsm.
+ */
+ fetchNodeLike: Task.async(function* (aGuidOrInfo) {
+ let info = yield PlacesUtils.bookmarks.fetch(aGuidOrInfo);
+ if (!info)
+ return null;
+ return (yield this.promiseNodeLikeFromFetchInfo(info));
+ })
+};
+
+
+PlacesUIUtils.PLACES_FLAVORS = [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
+ PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
+ PlacesUtils.TYPE_X_MOZ_PLACE];
+
+PlacesUIUtils.URI_FLAVORS = [PlacesUtils.TYPE_X_MOZ_URL,
+ TAB_DROP_TYPE,
+ PlacesUtils.TYPE_UNICODE],
+
+PlacesUIUtils.SUPPORTED_FLAVORS = [...PlacesUIUtils.PLACES_FLAVORS,
+ ...PlacesUIUtils.URI_FLAVORS];
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
+ "@mozilla.org/rdf/rdf-service;1",
+ "nsIRDFService");
+
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
+ return Services.prefs.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+});
+
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "useAsyncTransactions", function() {
+ try {
+ return Services.prefs.getBoolPref("browser.places.useAsyncTransactions");
+ }
+ catch (ex) { }
+ return false;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "URIFixup",
+ "@mozilla.org/docshell/urifixup;1",
+ "nsIURIFixup");
+
+XPCOMUtils.defineLazyGetter(this, "bundle", function() {
+ const PLACES_STRING_BUNDLE_URI =
+ "chrome://browser/locale/places/places.properties";
+ return Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(PLACES_STRING_BUNDLE_URI);
+});
+
+/**
+ * This is a compatibility shim for old PUIU.ptm users.
+ *
+ * If you're looking for transactions and writing new code using them, directly
+ * use the transactions objects exported by the PlacesUtils.jsm module.
+ *
+ * This object will be removed once enough users are converted to the new API.
+ */
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() {
+ // Ensure PlacesUtils is imported in scope.
+ PlacesUtils;
+
+ return {
+ aggregateTransactions: (aName, aTransactions) =>
+ new PlacesAggregatedTransaction(aName, aTransactions),
+
+ createFolder: (aName, aContainer, aIndex, aAnnotations,
+ aChildItemsTransactions) =>
+ new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations,
+ aChildItemsTransactions),
+
+ createItem: (aURI, aContainer, aIndex, aTitle, aKeyword,
+ aAnnotations, aChildTransactions) =>
+ new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle,
+ aKeyword, aAnnotations,
+ aChildTransactions),
+
+ createSeparator: (aContainer, aIndex) =>
+ new PlacesCreateSeparatorTransaction(aContainer, aIndex),
+
+ createLivemark: (aFeedURI, aSiteURI, aName, aContainer, aIndex,
+ aAnnotations) =>
+ new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer,
+ aIndex, aAnnotations),
+
+ moveItem: (aItemId, aNewContainer, aNewIndex) =>
+ new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex),
+
+ removeItem: (aItemId) =>
+ new PlacesRemoveItemTransaction(aItemId),
+
+ editItemTitle: (aItemId, aNewTitle) =>
+ new PlacesEditItemTitleTransaction(aItemId, aNewTitle),
+
+ editBookmarkURI: (aItemId, aNewURI) =>
+ new PlacesEditBookmarkURITransaction(aItemId, aNewURI),
+
+ setItemAnnotation: (aItemId, aAnnotationObject) =>
+ new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject),
+
+ setPageAnnotation: (aURI, aAnnotationObject) =>
+ new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject),
+
+ editBookmarkKeyword: (aItemId, aNewKeyword) =>
+ new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword),
+
+ editLivemarkSiteURI: (aLivemarkId, aSiteURI) =>
+ new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI),
+
+ editLivemarkFeedURI: (aLivemarkId, aFeedURI) =>
+ new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI),
+
+ editItemDateAdded: (aItemId, aNewDateAdded) =>
+ new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded),
+
+ editItemLastModified: (aItemId, aNewLastModified) =>
+ new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified),
+
+ sortFolderByName: (aFolderId) =>
+ new PlacesSortFolderByNameTransaction(aFolderId),
+
+ tagURI: (aURI, aTags) =>
+ new PlacesTagURITransaction(aURI, aTags),
+
+ untagURI: (aURI, aTags) =>
+ new PlacesUntagURITransaction(aURI, aTags),
+
+ /**
+ * Transaction for setting/unsetting Load-in-sidebar annotation.
+ *
+ * @param aBookmarkId
+ * id of the bookmark where to set Load-in-sidebar annotation.
+ * @param aLoadInSidebar
+ * boolean value.
+ * @return nsITransaction object.
+ */
+ setLoadInSidebar: function(aItemId, aLoadInSidebar)
+ {
+ let annoObj = { name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+ type: Ci.nsIAnnotationService.TYPE_INT32,
+ flags: 0,
+ value: aLoadInSidebar,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
+ },
+
+ /**
+ * Transaction for editing the description of a bookmark or a folder.
+ *
+ * @param aItemId
+ * id of the item to edit.
+ * @param aDescription
+ * new description.
+ * @return nsITransaction object.
+ */
+ editItemDescription: function(aItemId, aDescription)
+ {
+ let annoObj = { name: PlacesUIUtils.DESCRIPTION_ANNO,
+ type: Ci.nsIAnnotationService.TYPE_STRING,
+ flags: 0,
+ value: aDescription,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
+ },
+
+ // nsITransactionManager forwarders.
+
+ beginBatch: () =>
+ PlacesUtils.transactionManager.beginBatch(null),
+
+ endBatch: () =>
+ PlacesUtils.transactionManager.endBatch(false),
+
+ doTransaction: (txn) =>
+ PlacesUtils.transactionManager.doTransaction(txn),
+
+ undoTransaction: () =>
+ PlacesUtils.transactionManager.undoTransaction(),
+
+ redoTransaction: () =>
+ PlacesUtils.transactionManager.redoTransaction(),
+
+ get numberOfUndoItems() {
+ return PlacesUtils.transactionManager.numberOfUndoItems;
+ },
+ get numberOfRedoItems() {
+ return PlacesUtils.transactionManager.numberOfRedoItems;
+ },
+ get maxTransactionCount() {
+ return PlacesUtils.transactionManager.maxTransactionCount;
+ },
+ set maxTransactionCount(val) {
+ PlacesUtils.transactionManager.maxTransactionCount = val;
+ },
+
+ clear: () =>
+ PlacesUtils.transactionManager.clear(),
+
+ peekUndoStack: () =>
+ PlacesUtils.transactionManager.peekUndoStack(),
+
+ peekRedoStack: () =>
+ PlacesUtils.transactionManager.peekRedoStack(),
+
+ getUndoStack: () =>
+ PlacesUtils.transactionManager.getUndoStack(),
+
+ getRedoStack: () =>
+ PlacesUtils.transactionManager.getRedoStack(),
+
+ AddListener: (aListener) =>
+ PlacesUtils.transactionManager.AddListener(aListener),
+
+ RemoveListener: (aListener) =>
+ PlacesUtils.transactionManager.RemoveListener(aListener)
+ }
+});
diff --git a/browser/components/places/content/bookmarkProperties.js b/browser/components/places/content/bookmarkProperties.js
new file mode 100644
index 000000000..afcf65736
--- /dev/null
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -0,0 +1,693 @@
+/* -*- 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 panel is initialized based on data given in the js object passed
+ * as window.arguments[0]. The object must have the following fields set:
+ * @ action (String). Possible values:
+ * - "add" - for adding a new item.
+ * @ type (String). Possible values:
+ * - "bookmark"
+ * @ loadBookmarkInSidebar - optional, the default state for the
+ * "Load this bookmark in the sidebar" field.
+ * - "folder"
+ * @ URIList (Array of nsIURI objects) - optional, list of uris to
+ * be bookmarked under the new folder.
+ * - "livemark"
+ * @ uri (nsIURI object) - optional, the default uri for the new item.
+ * The property is not used for the "folder with items" type.
+ * @ title (String) - optional, the default title for the new item.
+ * @ description (String) - optional, the default description for the new
+ * item.
+ * @ defaultInsertionPoint (InsertionPoint JS object) - optional, the
+ * default insertion point for the new item.
+ * @ keyword (String) - optional, the default keyword for the new item.
+ * @ postData (String) - optional, POST data to accompany the keyword.
+ * @ charSet (String) - optional, character-set to accompany the keyword.
+ * Notes:
+ * 1) If |uri| is set for a bookmark/livemark item and |title| isn't,
+ * the dialog will query the history tables for the title associated
+ * with the given uri. If the dialog is set to adding a folder with
+ * bookmark items under it (see URIList), a default static title is
+ * used ("[Folder Name]").
+ * 2) The index field of the default insertion point is ignored if
+ * the folder picker is shown.
+ * - "edit" - for editing a bookmark item or a folder.
+ * @ type (String). Possible values:
+ * - "bookmark"
+ * @ node (an nsINavHistoryResultNode object) - a node representing
+ * the bookmark.
+ * - "folder" (also applies to livemarks)
+ * @ node (an nsINavHistoryResultNode object) - a node representing
+ * the folder.
+ * @ hiddenRows (Strings array) - optional, list of rows to be hidden
+ * regardless of the item edited or added by the dialog.
+ * Possible values:
+ * - "title"
+ * - "location"
+ * - "description"
+ * - "keyword"
+ * - "tags"
+ * - "loadInSidebar"
+ * - "folderPicker" - hides both the tree and the menu.
+ *
+ * window.arguments[0].performed is set to true if any transaction has
+ * been performed by the dialog.
+ */
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+
+const BOOKMARK_ITEM = 0;
+const BOOKMARK_FOLDER = 1;
+const LIVEMARK_CONTAINER = 2;
+
+const ACTION_EDIT = 0;
+const ACTION_ADD = 1;
+
+var elementsHeight = new Map();
+
+var BookmarkPropertiesPanel = {
+
+ /** UI Text Strings */
+ __strings: null,
+ get _strings() {
+ if (!this.__strings) {
+ this.__strings = document.getElementById("stringBundle");
+ }
+ return this.__strings;
+ },
+
+ _action: null,
+ _itemType: null,
+ _itemId: -1,
+ _uri: null,
+ _loadInSidebar: false,
+ _title: "",
+ _description: "",
+ _URIs: [],
+ _keyword: "",
+ _postData: null,
+ _charSet: "",
+ _feedURI: null,
+ _siteURI: null,
+
+ _defaultInsertionPoint: null,
+ _hiddenRows: [],
+ _batching: false,
+
+ /**
+ * This method returns the correct label for the dialog's "accept"
+ * button based on the variant of the dialog.
+ */
+ _getAcceptLabel: function BPP__getAcceptLabel() {
+ if (this._action == ACTION_ADD) {
+ if (this._URIs.length)
+ return this._strings.getString("dialogAcceptLabelAddMulti");
+
+ if (this._itemType == LIVEMARK_CONTAINER)
+ return this._strings.getString("dialogAcceptLabelAddLivemark");
+
+ if (this._dummyItem || this._loadInSidebar)
+ return this._strings.getString("dialogAcceptLabelAddItem");
+
+ return this._strings.getString("dialogAcceptLabelSaveItem");
+ }
+ return this._strings.getString("dialogAcceptLabelEdit");
+ },
+
+ /**
+ * This method returns the correct title for the current variant
+ * of this dialog.
+ */
+ _getDialogTitle: function BPP__getDialogTitle() {
+ if (this._action == ACTION_ADD) {
+ if (this._itemType == BOOKMARK_ITEM)
+ return this._strings.getString("dialogTitleAddBookmark");
+ if (this._itemType == LIVEMARK_CONTAINER)
+ return this._strings.getString("dialogTitleAddLivemark");
+
+ // add folder
+ NS_ASSERT(this._itemType == BOOKMARK_FOLDER, "Unknown item type");
+ if (this._URIs.length)
+ return this._strings.getString("dialogTitleAddMulti");
+
+ return this._strings.getString("dialogTitleAddFolder");
+ }
+ if (this._action == ACTION_EDIT) {
+ return this._strings.getFormattedString("dialogTitleEdit", [this._title]);
+ }
+ return "";
+ },
+
+ /**
+ * Determines the initial data for the item edited or added by this dialog
+ */
+ _determineItemInfo() {
+ let dialogInfo = window.arguments[0];
+ this._action = dialogInfo.action == "add" ? ACTION_ADD : ACTION_EDIT;
+ this._hiddenRows = dialogInfo.hiddenRows ? dialogInfo.hiddenRows : [];
+ if (this._action == ACTION_ADD) {
+ NS_ASSERT("type" in dialogInfo, "missing type property for add action");
+
+ if ("title" in dialogInfo)
+ this._title = dialogInfo.title;
+
+ if ("defaultInsertionPoint" in dialogInfo) {
+ this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint;
+ }
+ else {
+ this._defaultInsertionPoint =
+ new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+ }
+
+ switch (dialogInfo.type) {
+ case "bookmark":
+ this._itemType = BOOKMARK_ITEM;
+ if ("uri" in dialogInfo) {
+ NS_ASSERT(dialogInfo.uri instanceof Ci.nsIURI,
+ "uri property should be a uri object");
+ this._uri = dialogInfo.uri;
+ if (typeof(this._title) != "string") {
+ this._title = this._getURITitleFromHistory(this._uri) ||
+ this._uri.spec;
+ }
+ }
+ else {
+ this._uri = PlacesUtils._uri("about:blank");
+ this._title = this._strings.getString("newBookmarkDefault");
+ this._dummyItem = true;
+ }
+
+ if ("loadBookmarkInSidebar" in dialogInfo)
+ this._loadInSidebar = dialogInfo.loadBookmarkInSidebar;
+
+ if ("keyword" in dialogInfo) {
+ this._keyword = dialogInfo.keyword;
+ this._isAddKeywordDialog = true;
+ if ("postData" in dialogInfo)
+ this._postData = dialogInfo.postData;
+ if ("charSet" in dialogInfo)
+ this._charSet = dialogInfo.charSet;
+ }
+ break;
+
+ case "folder":
+ this._itemType = BOOKMARK_FOLDER;
+ if (!this._title) {
+ if ("URIList" in dialogInfo) {
+ this._title = this._strings.getString("bookmarkAllTabsDefault");
+ this._URIs = dialogInfo.URIList;
+ }
+ else
+ this._title = this._strings.getString("newFolderDefault");
+ this._dummyItem = true;
+ }
+ break;
+
+ case "livemark":
+ this._itemType = LIVEMARK_CONTAINER;
+ if ("feedURI" in dialogInfo)
+ this._feedURI = dialogInfo.feedURI;
+ if ("siteURI" in dialogInfo)
+ this._siteURI = dialogInfo.siteURI;
+
+ if (!this._title) {
+ if (this._feedURI) {
+ this._title = this._getURITitleFromHistory(this._feedURI) ||
+ this._feedURI.spec;
+ }
+ else
+ this._title = this._strings.getString("newLivemarkDefault");
+ }
+ }
+
+ if ("description" in dialogInfo)
+ this._description = dialogInfo.description;
+ }
+ else { // edit
+ this._node = dialogInfo.node;
+ this._title = this._node.title;
+ if (PlacesUtils.nodeIsFolder(this._node))
+ this._itemType = BOOKMARK_FOLDER;
+ else if (PlacesUtils.nodeIsURI(this._node))
+ this._itemType = BOOKMARK_ITEM;
+ }
+ },
+
+ /**
+ * This method returns the title string corresponding to a given URI.
+ * If none is available from the bookmark service (probably because
+ * the given URI doesn't appear in bookmarks or history), we synthesize
+ * a title from the first 100 characters of the URI.
+ *
+ * @param aURI
+ * nsIURI object for which we want the title
+ *
+ * @returns a title string
+ */
+ _getURITitleFromHistory: function BPP__getURITitleFromHistory(aURI) {
+ NS_ASSERT(aURI instanceof Ci.nsIURI);
+
+ // get the title from History
+ return PlacesUtils.history.getPageTitle(aURI);
+ },
+
+ /**
+ * This method should be called by the onload of the Bookmark Properties
+ * dialog to initialize the state of the panel.
+ */
+ onDialogLoad: Task.async(function* () {
+ this._determineItemInfo();
+
+ document.title = this._getDialogTitle();
+ var acceptButton = document.documentElement.getButton("accept");
+ acceptButton.label = this._getAcceptLabel();
+
+ // Do not use sizeToContent, otherwise, due to bug 90276, the dialog will
+ // grow at every opening.
+ // Since elements can be uncollapsed asynchronously, we must observe their
+ // mutations and resize the dialog using a cached element size.
+ this._height = window.outerHeight;
+ this._mutationObserver = new MutationObserver(mutations => {
+ for (let mutation of mutations) {
+ let target = mutation.target;
+ let id = target.id;
+ if (!/^editBMPanel_.*(Row|Checkbox)$/.test(id))
+ continue;
+
+ let collapsed = target.getAttribute("collapsed") === "true";
+ let wasCollapsed = mutation.oldValue === "true";
+ if (collapsed == wasCollapsed)
+ continue;
+
+ if (collapsed) {
+ this._height -= elementsHeight.get(id);
+ elementsHeight.delete(id);
+ } else {
+ elementsHeight.set(id, target.boxObject.height);
+ this._height += elementsHeight.get(id);
+ }
+ window.resizeTo(window.outerWidth, this._height);
+ }
+ });
+
+ this._mutationObserver.observe(document,
+ { subtree: true,
+ attributeOldValue: true,
+ attributeFilter: ["collapsed"] });
+
+ // Some controls are flexible and we want to update their cached size when
+ // the dialog is resized.
+ window.addEventListener("resize", this);
+
+ this._beginBatch();
+
+ switch (this._action) {
+ case ACTION_EDIT:
+ gEditItemOverlay.initPanel({ node: this._node
+ , hiddenRows: this._hiddenRows
+ , focusedElement: "first" });
+ acceptButton.disabled = gEditItemOverlay.readOnly;
+ break;
+ case ACTION_ADD:
+ this._node = yield this._promiseNewItem();
+ // Edit the new item
+ gEditItemOverlay.initPanel({ node: this._node
+ , hiddenRows: this._hiddenRows
+ , postData: this._postData
+ , focusedElement: "first" });
+
+ // Empty location field if the uri is about:blank, this way inserting a new
+ // url will be easier for the user, Accept button will be automatically
+ // disabled by the input listener until the user fills the field.
+ let locationField = this._element("locationField");
+ if (locationField.value == "about:blank")
+ locationField.value = "";
+
+ // if this is an uri related dialog disable accept button until
+ // the user fills an uri value.
+ if (this._itemType == BOOKMARK_ITEM)
+ acceptButton.disabled = !this._inputIsValid();
+ break;
+ }
+
+ if (!gEditItemOverlay.readOnly) {
+ // Listen on uri fields to enable accept button if input is valid
+ if (this._itemType == BOOKMARK_ITEM) {
+ this._element("locationField")
+ .addEventListener("input", this, false);
+ if (this._isAddKeywordDialog) {
+ this._element("keywordField")
+ .addEventListener("input", this, false);
+ }
+ }
+ }
+ }),
+
+ // nsIDOMEventListener
+ handleEvent: function BPP_handleEvent(aEvent) {
+ var target = aEvent.target;
+ switch (aEvent.type) {
+ case "input":
+ if (target.id == "editBMPanel_locationField" ||
+ target.id == "editBMPanel_keywordField") {
+ // Check uri fields to enable accept button if input is valid
+ document.documentElement
+ .getButton("accept").disabled = !this._inputIsValid();
+ }
+ break;
+ case "resize":
+ for (let [id, oldHeight] of elementsHeight) {
+ let newHeight = document.getElementById(id).boxObject.height;
+ this._height += - oldHeight + newHeight;
+ elementsHeight.set(id, newHeight);
+ }
+ break;
+ }
+ },
+
+ // Hack for implementing batched-Undo around the editBookmarkOverlay
+ // instant-apply code. For all the details see the comment above beginBatch
+ // in browser-places.js
+ _batchBlockingDeferred: null,
+ _beginBatch() {
+ if (this._batching)
+ return;
+ if (PlacesUIUtils.useAsyncTransactions) {
+ this._batchBlockingDeferred = PromiseUtils.defer();
+ PlacesTransactions.batch(function* () {
+ yield this._batchBlockingDeferred.promise;
+ }.bind(this));
+ }
+ else {
+ PlacesUtils.transactionManager.beginBatch(null);
+ }
+ this._batching = true;
+ },
+
+ _endBatch() {
+ if (!this._batching)
+ return;
+
+ if (PlacesUIUtils.useAsyncTransactions) {
+ this._batchBlockingDeferred.resolve();
+ this._batchBlockingDeferred = null;
+ }
+ else {
+ PlacesUtils.transactionManager.endBatch(false);
+ }
+ this._batching = false;
+ },
+
+ // nsISupports
+ QueryInterface: function BPP_QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDOMEventListener) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ _element: function BPP__element(aID) {
+ return document.getElementById("editBMPanel_" + aID);
+ },
+
+ onDialogUnload() {
+ // gEditItemOverlay does not exist anymore here, so don't rely on it.
+ this._mutationObserver.disconnect();
+ delete this._mutationObserver;
+
+ window.removeEventListener("resize", this);
+
+ // Calling removeEventListener with arguments which do not identify any
+ // currently registered EventListener on the EventTarget has no effect.
+ this._element("locationField")
+ .removeEventListener("input", this, false);
+ },
+
+ onDialogAccept() {
+ // We must blur current focused element to save its changes correctly
+ document.commandDispatcher.focusedElement.blur();
+ // The order here is important! We have to uninit the panel first, otherwise
+ // late changes could force it to commit more transactions.
+ gEditItemOverlay.uninitPanel(true);
+ this._endBatch();
+ window.arguments[0].performed = true;
+ },
+
+ onDialogCancel() {
+ // The order here is important! We have to uninit the panel first, otherwise
+ // changes done as part of Undo may change the panel contents and by
+ // that force it to commit more transactions.
+ gEditItemOverlay.uninitPanel(true);
+ this._endBatch();
+ if (PlacesUIUtils.useAsyncTransactions)
+ PlacesTransactions.undo().catch(Components.utils.reportError);
+ else
+ PlacesUtils.transactionManager.undoTransaction();
+ window.arguments[0].performed = false;
+ },
+
+ /**
+ * This method checks to see if the input fields are in a valid state.
+ *
+ * @returns true if the input is valid, false otherwise
+ */
+ _inputIsValid: function BPP__inputIsValid() {
+ if (this._itemType == BOOKMARK_ITEM &&
+ !this._containsValidURI("locationField"))
+ return false;
+ if (this._isAddKeywordDialog && !this._element("keywordField").value.length)
+ return false;
+
+ return true;
+ },
+
+ /**
+ * Determines whether the XUL textbox with the given ID contains a
+ * string that can be converted into an nsIURI.
+ *
+ * @param aTextboxID
+ * the ID of the textbox element whose contents we'll test
+ *
+ * @returns true if the textbox contains a valid URI string, false otherwise
+ */
+ _containsValidURI: function BPP__containsValidURI(aTextboxID) {
+ try {
+ var value = this._element(aTextboxID).value;
+ if (value) {
+ PlacesUIUtils.createFixedURI(value);
+ return true;
+ }
+ } catch (e) { }
+ return false;
+ },
+
+ /**
+ * [New Item Mode] Get the insertion point details for the new item, given
+ * dialog state and opening arguments.
+ *
+ * The container-identifier and insertion-index are returned separately in
+ * the form of [containerIdentifier, insertionIndex]
+ */
+ _getInsertionPointDetails: function BPP__getInsertionPointDetails() {
+ var containerId = this._defaultInsertionPoint.itemId;
+ var indexInContainer = this._defaultInsertionPoint.index;
+
+ return [containerId, indexInContainer];
+ },
+
+ /**
+ * Returns a transaction for creating a new bookmark item representing the
+ * various fields and opening arguments of the dialog.
+ */
+ _getCreateNewBookmarkTransaction:
+ function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) {
+ var annotations = [];
+ var childTransactions = [];
+
+ if (this._description) {
+ let annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
+ type : Ci.nsIAnnotationService.TYPE_STRING,
+ flags : 0,
+ value : this._description,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+ childTransactions.push(editItemTxn);
+ }
+
+ if (this._loadInSidebar) {
+ let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+ value : true };
+ let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+ childTransactions.push(setLoadTxn);
+ }
+
+ // XXX TODO: this should be in a transaction!
+ if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUtils.setCharsetForURI(this._uri, this._charSet);
+
+ let createTxn = new PlacesCreateBookmarkTransaction(this._uri,
+ aContainer,
+ aIndex,
+ this._title,
+ this._keyword,
+ annotations,
+ childTransactions,
+ this._postData);
+
+ return new PlacesAggregatedTransaction(this._getDialogTitle(),
+ [createTxn]);
+ },
+
+ /**
+ * Returns a childItems-transactions array representing the URIList with
+ * which the dialog has been opened.
+ */
+ _getTransactionsForURIList: function BPP__getTransactionsForURIList() {
+ var transactions = [];
+ for (let uri of this._URIs) {
+ // uri should be an object in the form { url, title }. Though add-ons
+ // could still use the legacy form, where it's an nsIURI.
+ let [_uri, _title] = uri instanceof Ci.nsIURI ?
+ [uri, this._getURITitleFromHistory(uri)] : [uri.uri, uri.title];
+
+ let createTxn =
+ new PlacesCreateBookmarkTransaction(_uri, -1,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ _title);
+ transactions.push(createTxn);
+ }
+ return transactions;
+ },
+
+ /**
+ * Returns a transaction for creating a new folder item representing the
+ * various fields and opening arguments of the dialog.
+ */
+ _getCreateNewFolderTransaction:
+ function BPP__getCreateNewFolderTransaction(aContainer, aIndex) {
+ var annotations = [];
+ var childItemsTransactions;
+ if (this._URIs.length)
+ childItemsTransactions = this._getTransactionsForURIList();
+
+ if (this._description)
+ annotations.push(this._getDescriptionAnnotation(this._description));
+
+ return new PlacesCreateFolderTransaction(this._title, aContainer,
+ aIndex, annotations,
+ childItemsTransactions);
+ },
+
+ _createNewItem: Task.async(function* () {
+ let [container, index] = this._getInsertionPointDetails();
+ let txn;
+ switch (this._itemType) {
+ case BOOKMARK_FOLDER:
+ txn = this._getCreateNewFolderTransaction(container, index);
+ break;
+ case LIVEMARK_CONTAINER:
+ txn = new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
+ this._title, container, index);
+ break;
+ default: // BOOKMARK_ITEM
+ txn = this._getCreateNewBookmarkTransaction(container, index);
+ }
+
+ PlacesUtils.transactionManager.doTransaction(txn);
+ // This is a temporary hack until we use PlacesTransactions.jsm
+ if (txn._promise) {
+ yield txn._promise;
+ }
+
+ let folderGuid = yield PlacesUtils.promiseItemGuid(container);
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: folderGuid,
+ index: index
+ });
+ this._itemId = yield PlacesUtils.promiseItemId(bm.guid);
+
+ return Object.freeze({
+ itemId: this._itemId,
+ bookmarkGuid: bm.guid,
+ title: this._title,
+ uri: this._uri ? this._uri.spec : "",
+ type: this._itemType == BOOKMARK_ITEM ?
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
+ });
+ }),
+
+ _promiseNewItem: Task.async(function* () {
+ if (!PlacesUIUtils.useAsyncTransactions)
+ return this._createNewItem();
+
+ let [containerId, index] = this._getInsertionPointDetails();
+ let parentGuid = yield PlacesUtils.promiseItemGuid(containerId);
+ let annotations = [];
+ if (this._description) {
+ annotations.push({ name: PlacesUIUtils.DESCRIPTION_ANNO
+ , value: this._description });
+ }
+ if (this._loadInSidebar) {
+ annotations.push({ name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO
+ , value: true });
+ }
+
+ let itemGuid;
+ let info = { parentGuid, index, title: this._title, annotations };
+ if (this._itemType == BOOKMARK_ITEM) {
+ info.url = this._uri;
+ if (this._keyword)
+ info.keyword = this._keyword;
+ if (this._postData)
+ info.postData = this._postData;
+
+ if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUtils.setCharsetForURI(this._uri, this._charSet);
+
+ itemGuid = yield PlacesTransactions.NewBookmark(info).transact();
+ }
+ else if (this._itemType == LIVEMARK_CONTAINER) {
+ info.feedUrl = this._feedURI;
+ if (this._siteURI)
+ info.siteUrl = this._siteURI;
+
+ itemGuid = yield PlacesTransactions.NewLivemark(info).transact();
+ }
+ else if (this._itemType == BOOKMARK_FOLDER) {
+ itemGuid = yield PlacesTransactions.NewFolder(info).transact();
+ for (let uri of this._URIs) {
+ let placeInfo = yield PlacesUtils.promisePlaceInfo(uri);
+ let title = placeInfo ? placeInfo.title : "";
+ yield PlacesTransactions.transact({ parentGuid: itemGuid, uri, title });
+ }
+ }
+ else {
+ throw new Error(`unexpected value for _itemType: ${this._itemType}`);
+ }
+
+ this._itemGuid = itemGuid;
+ this._itemId = yield PlacesUtils.promiseItemId(itemGuid);
+ return Object.freeze({
+ itemId: this._itemId,
+ bookmarkGuid: this._itemGuid,
+ title: this._title,
+ uri: this._uri ? this._uri.spec : "",
+ type: this._itemType == BOOKMARK_ITEM ?
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
+ });
+ })
+};
diff --git a/browser/components/places/content/bookmarkProperties.xul b/browser/components/places/content/bookmarkProperties.xul
new file mode 100644
index 000000000..2c04f8b05
--- /dev/null
+++ b/browser/components/places/content/bookmarkProperties.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE dialog [
+ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+ %editBookmarkOverlayDTD;
+]>
+
+<dialog id="bookmarkproperties"
+ buttons="accept, cancel"
+ buttoniconaccept="save"
+ ondialogaccept="BookmarkPropertiesPanel.onDialogAccept();"
+ ondialogcancel="BookmarkPropertiesPanel.onDialogCancel();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="BookmarkPropertiesPanel.onDialogLoad();"
+ onunload="BookmarkPropertiesPanel.onDialogUnload();"
+ style="min-width: 30em;"
+ persist="screenX screenY width">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="stringBundle"
+ src="chrome://browser/locale/places/bookmarkProperties.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/bookmarkProperties.js"/>
+
+<vbox id="editBookmarkPanelContent"/>
+
+</dialog>
diff --git a/browser/components/places/content/bookmarksPanel.js b/browser/components/places/content/bookmarksPanel.js
new file mode 100644
index 000000000..871d69725
--- /dev/null
+++ b/browser/components/places/content/bookmarksPanel.js
@@ -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/. */
+
+function init() {
+ document.getElementById("bookmarks-view").place =
+ "place:queryType=1&folder=" + window.top.PlacesUIUtils.allBookmarksFolderId;
+}
+
+function searchBookmarks(aSearchString) {
+ var tree = document.getElementById('bookmarks-view');
+ if (!aSearchString)
+ tree.place = tree.place;
+ else
+ tree.applyFilter(aSearchString,
+ [PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.toolbarFolderId]);
+}
+
+window.addEventListener("SidebarFocused",
+ () => document.getElementById("search-box").focus(),
+ false);
diff --git a/browser/components/places/content/bookmarksPanel.xul b/browser/components/places/content/bookmarksPanel.xul
new file mode 100644
index 000000000..332b9e7a9
--- /dev/null
+++ b/browser/components/places/content/bookmarksPanel.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE page SYSTEM "chrome://browser/locale/places/places.dtd">
+
+<page id="bookmarksPanel"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="init();"
+ onunload="SidebarUtils.setMouseoverURL('');">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/bookmarks/bookmarksPanel.js"/>
+
+ <commandset id="placesCommands"/>
+ <commandset id="editMenuCommands"/>
+ <menupopup id="placesContext"/>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <hbox id="sidebar-search-container" align="center">
+ <label id="sidebar-search-label"
+ value="&search.label;" accesskey="&search.accesskey;" control="search-box"/>
+ <textbox id="search-box" flex="1" type="search" class="compact"
+ aria-controls="bookmarks-view"
+ oncommand="searchBookmarks(this.value);"/>
+ </hbox>
+
+ <tree id="bookmarks-view" class="sidebar-placesTree" type="places"
+ flex="1"
+ hidecolumnpicker="true"
+ context="placesContext"
+ onkeypress="SidebarUtils.handleTreeKeyPress(event);"
+ onclick="SidebarUtils.handleTreeClick(this, event, true);"
+ onmousemove="SidebarUtils.handleTreeMouseMove(event);"
+ onmouseout="SidebarUtils.setMouseoverURL('');">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="bookmarks-view-children" view="bookmarks-view"
+ class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/>
+ </tree>
+</page>
diff --git a/browser/components/places/content/browserPlacesViews.js b/browser/components/places/content/browserPlacesViews.js
new file mode 100644
index 000000000..c6ee9b6ce
--- /dev/null
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -0,0 +1,1996 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * The base view implements everything that's common to the toolbar and
+ * menu views.
+ */
+function PlacesViewBase(aPlace, aOptions) {
+ this.place = aPlace;
+ this.options = aOptions;
+ this._controller = new PlacesController(this);
+ this._viewElt.controllers.appendController(this._controller);
+}
+
+PlacesViewBase.prototype = {
+ // The xul element that holds the entire view.
+ _viewElt: null,
+ get viewElt() {
+ return this._viewElt;
+ },
+
+ get associatedElement() {
+ return this._viewElt;
+ },
+
+ get controllers() {
+ return this._viewElt.controllers;
+ },
+
+ // The xul element that represents the root container.
+ _rootElt: null,
+
+ // Set to true for views that are represented by native widgets (i.e.
+ // the native mac menu).
+ _nativeView: false,
+
+ QueryInterface: XPCOMUtils.generateQI(
+ [Components.interfaces.nsINavHistoryResultObserver,
+ Components.interfaces.nsISupportsWeakReference]),
+
+ _place: "",
+ get place() {
+ return this._place;
+ },
+ set place(val) {
+ this._place = val;
+
+ let history = PlacesUtils.history;
+ let queries = { }, options = { };
+ history.queryStringToQueries(val, queries, { }, options);
+ if (!queries.value.length)
+ queries.value = [history.getNewQuery()];
+
+ let result = history.executeQueries(queries.value, queries.value.length,
+ options.value);
+ result.addObserver(this, false);
+ return val;
+ },
+
+ _result: null,
+ get result() {
+ return this._result;
+ },
+ set result(val) {
+ if (this._result == val)
+ return val;
+
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._resultNode.containerOpen = false;
+ }
+
+ if (this._rootElt.localName == "menupopup")
+ this._rootElt._built = false;
+
+ this._result = val;
+ if (val) {
+ this._resultNode = val.root;
+ this._rootElt._placesNode = this._resultNode;
+ this._domNodes = new Map();
+ this._domNodes.set(this._resultNode, this._rootElt);
+
+ // This calls _rebuild through invalidateContainer.
+ this._resultNode.containerOpen = true;
+ }
+ else {
+ this._resultNode = null;
+ delete this._domNodes;
+ }
+
+ return val;
+ },
+
+ _options: null,
+ get options() {
+ return this._options;
+ },
+ set options(val) {
+ if (!val)
+ val = {};
+
+ if (!("extraClasses" in val))
+ val.extraClasses = {};
+ this._options = val;
+
+ return val;
+ },
+
+ /**
+ * Gets the DOM node used for the given places node.
+ *
+ * @param aPlacesNode
+ * a places result node.
+ * @throws if there is no DOM node set for aPlacesNode.
+ */
+ _getDOMNodeForPlacesNode:
+ function PVB__getDOMNodeForPlacesNode(aPlacesNode) {
+ let node = this._domNodes.get(aPlacesNode, null);
+ if (!node) {
+ throw new Error("No DOM node set for aPlacesNode.\nnode.type: " +
+ aPlacesNode.type + ". node.parent: " + aPlacesNode);
+ }
+ return node;
+ },
+
+ get controller() {
+ return this._controller;
+ },
+
+ get selType() {
+ return "single";
+ },
+ selectItems: function() { },
+ selectAll: function() { },
+
+ get selectedNode() {
+ if (this._contextMenuShown) {
+ let anchor = this._contextMenuShown.triggerNode;
+ if (!anchor)
+ return null;
+
+ if (anchor._placesNode)
+ return this._rootElt == anchor ? null : anchor._placesNode;
+
+ anchor = anchor.parentNode;
+ return this._rootElt == anchor ? null : (anchor._placesNode || null);
+ }
+ return null;
+ },
+
+ get hasSelection() {
+ return this.selectedNode != null;
+ },
+
+ get selectedNodes() {
+ let selectedNode = this.selectedNode;
+ return selectedNode ? [selectedNode] : [];
+ },
+
+ get removableSelectionRanges() {
+ // On static content the current selectedNode would be the selection's
+ // parent node. We don't want to allow removing a node when the
+ // selection is not explicit.
+ if (document.popupNode &&
+ (document.popupNode == "menupopup" || !document.popupNode._placesNode))
+ return [];
+
+ return [this.selectedNodes];
+ },
+
+ get draggableSelection() {
+ return [this._draggedElt];
+ },
+
+ get insertionPoint() {
+ // There is no insertion point for history queries, so bail out now and
+ // save a lot of work when updating commands.
+ let resultNode = this._resultNode;
+ if (PlacesUtils.nodeIsQuery(resultNode) &&
+ PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
+ return null;
+
+ // By default, the insertion point is at the top level, at the end.
+ let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ let container = this._resultNode;
+ let orientation = Ci.nsITreeView.DROP_BEFORE;
+ let tagName = null;
+
+ let selectedNode = this.selectedNode;
+ if (selectedNode) {
+ let popup = document.popupNode;
+ if (!popup._placesNode || popup._placesNode == this._resultNode ||
+ popup._placesNode.itemId == -1 || !selectedNode.parent) {
+ // If a static menuitem is selected, or if the root node is selected,
+ // the insertion point is inside the folder, at the end.
+ container = selectedNode;
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+ else {
+ // In all other cases the insertion point is before that node.
+ container = selectedNode.parent;
+ index = container.getChildIndex(selectedNode);
+ if (PlacesUtils.nodeIsTagQuery(container)) {
+ tagName = container.title;
+ // TODO (Bug 1160193): properly support dropping on a tag root.
+ if (!tagName)
+ return null;
+ }
+ }
+ }
+
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
+ index, orientation, tagName);
+ },
+
+ buildContextMenu: function PVB_buildContextMenu(aPopup) {
+ this._contextMenuShown = aPopup;
+ window.updateCommands("places");
+ return this.controller.buildContextMenu(aPopup);
+ },
+
+ destroyContextMenu: function PVB_destroyContextMenu(aPopup) {
+ this._contextMenuShown = null;
+ },
+
+ _cleanPopup: function PVB_cleanPopup(aPopup, aDelay) {
+ // Remove Places nodes from the popup.
+ let child = aPopup._startMarker;
+ while (child.nextSibling != aPopup._endMarker) {
+ let sibling = child.nextSibling;
+ if (sibling._placesNode && !aDelay) {
+ aPopup.removeChild(sibling);
+ }
+ else if (sibling._placesNode && aDelay) {
+ // HACK (bug 733419): the popups originating from the OS X native
+ // menubar don't live-update while open, thus we don't clean it
+ // until the next popupshowing, to avoid zombie menuitems.
+ if (!aPopup._delayedRemovals)
+ aPopup._delayedRemovals = [];
+ aPopup._delayedRemovals.push(sibling);
+ child = child.nextSibling;
+ }
+ else {
+ child = child.nextSibling;
+ }
+ }
+ },
+
+ _rebuildPopup: function PVB__rebuildPopup(aPopup) {
+ let resultNode = aPopup._placesNode;
+ if (!resultNode.containerOpen)
+ return;
+
+ if (this.controller.hasCachedLivemarkInfo(resultNode)) {
+ this._setEmptyPopupStatus(aPopup, false);
+ aPopup._built = true;
+ this._populateLivemarkPopup(aPopup);
+ return;
+ }
+
+ this._cleanPopup(aPopup);
+
+ let cc = resultNode.childCount;
+ if (cc > 0) {
+ this._setEmptyPopupStatus(aPopup, false);
+
+ for (let i = 0; i < cc; ++i) {
+ let child = resultNode.getChild(i);
+ this._insertNewItemToPopup(child, aPopup, null);
+ }
+ }
+ else {
+ this._setEmptyPopupStatus(aPopup, true);
+ }
+ aPopup._built = true;
+ },
+
+ _removeChild: function PVB__removeChild(aChild) {
+ // If document.popupNode pointed to this child, null it out,
+ // otherwise controller's command-updating may rely on the removed
+ // item still being "selected".
+ if (document.popupNode == aChild)
+ document.popupNode = null;
+
+ aChild.parentNode.removeChild(aChild);
+ },
+
+ _setEmptyPopupStatus:
+ function PVB__setEmptyPopupStatus(aPopup, aEmpty) {
+ if (!aPopup._emptyMenuitem) {
+ let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
+ aPopup._emptyMenuitem = document.createElement("menuitem");
+ aPopup._emptyMenuitem.setAttribute("label", label);
+ aPopup._emptyMenuitem.setAttribute("disabled", true);
+ aPopup._emptyMenuitem.className = "bookmark-item";
+ if (typeof this.options.extraClasses.entry == "string")
+ aPopup._emptyMenuitem.classList.add(this.options.extraClasses.entry);
+ }
+
+ if (aEmpty) {
+ aPopup.setAttribute("emptyplacesresult", "true");
+ // Don't add the menuitem if there is static content.
+ if (!aPopup._startMarker.previousSibling &&
+ !aPopup._endMarker.nextSibling)
+ aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker);
+ }
+ else {
+ aPopup.removeAttribute("emptyplacesresult");
+ try {
+ aPopup.removeChild(aPopup._emptyMenuitem);
+ } catch (ex) {}
+ }
+ },
+
+ _createMenuItemForPlacesNode:
+ function PVB__createMenuItemForPlacesNode(aPlacesNode) {
+ this._domNodes.delete(aPlacesNode);
+
+ let element;
+ let type = aPlacesNode.type;
+ if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
+ element = document.createElement("menuseparator");
+ element.setAttribute("class", "small-separator");
+ }
+ else {
+ let itemId = aPlacesNode.itemId;
+ if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
+ element = document.createElement("menuitem");
+ element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
+ element.setAttribute("scheme",
+ PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri));
+ }
+ else if (PlacesUtils.containerTypes.includes(type)) {
+ element = document.createElement("menu");
+ element.setAttribute("container", "true");
+
+ if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
+ element.setAttribute("query", "true");
+ if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
+ element.setAttribute("tagContainer", "true");
+ else if (PlacesUtils.nodeIsDay(aPlacesNode))
+ element.setAttribute("dayContainer", "true");
+ else if (PlacesUtils.nodeIsHost(aPlacesNode))
+ element.setAttribute("hostContainer", "true");
+ }
+ else if (itemId != -1) {
+ PlacesUtils.livemarks.getLivemark({ id: itemId })
+ .then(aLivemark => {
+ element.setAttribute("livemark", "true");
+ if (AppConstants.platform === "macosx") {
+ // OS X native menubar doesn't track list-style-images since
+ // it doesn't have a frame (bug 733415). Thus enforce updating.
+ element.setAttribute("image", "");
+ element.removeAttribute("image");
+ }
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ }, () => undefined);
+ }
+
+ let popup = document.createElement("menupopup");
+ popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
+
+ if (!this._nativeView) {
+ popup.setAttribute("placespopup", "true");
+ }
+
+ element.appendChild(popup);
+ element.className = "menu-iconic bookmark-item";
+ if (typeof this.options.extraClasses.entry == "string") {
+ element.classList.add(this.options.extraClasses.entry);
+ }
+
+ this._domNodes.set(aPlacesNode, popup);
+ }
+ else
+ throw "Unexpected node";
+
+ element.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
+
+ let icon = aPlacesNode.icon;
+ if (icon)
+ element.setAttribute("image", icon);
+ }
+
+ element._placesNode = aPlacesNode;
+ if (!this._domNodes.has(aPlacesNode))
+ this._domNodes.set(aPlacesNode, element);
+
+ return element;
+ },
+
+ _insertNewItemToPopup:
+ function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) {
+ let element = this._createMenuItemForPlacesNode(aNewChild);
+ let before = aBefore || aPopup._endMarker;
+
+ if (element.localName == "menuitem" || element.localName == "menu") {
+ if (typeof this.options.extraClasses.entry == "string")
+ element.classList.add(this.options.extraClasses.entry);
+ }
+
+ aPopup.insertBefore(element, before);
+ return element;
+ },
+
+ _setLivemarkSiteURIMenuItem:
+ function PVB__setLivemarkSiteURIMenuItem(aPopup) {
+ let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode);
+ let siteUrl = livemarkInfo && livemarkInfo.siteURI ?
+ livemarkInfo.siteURI.spec : null;
+ if (!siteUrl && aPopup._siteURIMenuitem) {
+ aPopup.removeChild(aPopup._siteURIMenuitem);
+ aPopup._siteURIMenuitem = null;
+ aPopup.removeChild(aPopup._siteURIMenuseparator);
+ aPopup._siteURIMenuseparator = null;
+ }
+ else if (siteUrl && !aPopup._siteURIMenuitem) {
+ // Add "Open (Feed Name)" menuitem.
+ aPopup._siteURIMenuitem = document.createElement("menuitem");
+ aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
+ if (typeof this.options.extraClasses.entry == "string") {
+ aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry);
+ }
+ aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
+ aPopup._siteURIMenuitem.setAttribute("oncommand",
+ "openUILink(this.getAttribute('targetURI'), event);");
+
+ // If a user middle-clicks this item we serve the oncommand event.
+ // We are using checkForMiddleClick because of Bug 246720.
+ // Note: stopPropagation is needed to avoid serving middle-click
+ // with BT_onClick that would open all items in tabs.
+ aPopup._siteURIMenuitem.setAttribute("onclick",
+ "checkForMiddleClick(this, event); event.stopPropagation();");
+ let label =
+ PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
+ [aPopup.parentNode.getAttribute("label")])
+ aPopup._siteURIMenuitem.setAttribute("label", label);
+ aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker);
+
+ aPopup._siteURIMenuseparator = document.createElement("menuseparator");
+ aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker);
+ }
+ },
+
+ /**
+ * Add, update or remove the livemark status menuitem.
+ * @param aPopup
+ * The livemark container popup
+ * @param aStatus
+ * The livemark status
+ */
+ _setLivemarkStatusMenuItem:
+ function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
+ let statusMenuitem = aPopup._statusMenuitem;
+ if (!statusMenuitem) {
+ // Create the status menuitem and cache it in the popup object.
+ statusMenuitem = document.createElement("menuitem");
+ statusMenuitem.className = "livemarkstatus-menuitem";
+ if (typeof this.options.extraClasses.entry == "string") {
+ statusMenuitem.classList.add(this.options.extraClasses.entry);
+ }
+ statusMenuitem.setAttribute("disabled", true);
+ aPopup._statusMenuitem = statusMenuitem;
+ }
+
+ if (aStatus == Ci.mozILivemark.STATUS_LOADING ||
+ aStatus == Ci.mozILivemark.STATUS_FAILED) {
+ // Status has changed, update the cached status menuitem.
+ let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
+ "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
+ statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
+ if (aPopup._startMarker.nextSibling != statusMenuitem)
+ aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling);
+ }
+ else if (aPopup._statusMenuitem.parentNode == aPopup) {
+ // The livemark has finished loading.
+ aPopup.removeChild(aPopup._statusMenuitem);
+ }
+ },
+
+ toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // We may get the popup for menus, but we need the menu itself.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+ if (aValue)
+ elt.setAttribute("cutting", "true");
+ else
+ elt.removeAttribute("cutting");
+ },
+
+ nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ elt.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aURIString));
+ },
+
+ nodeIconChanged: function PVB_nodeIconChanged(aPlacesNode) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // There's no UI representation for the root node, thus there's nothing to
+ // be done when the icon changes.
+ if (elt == this._rootElt)
+ return;
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ let icon = aPlacesNode.icon;
+ if (!icon)
+ elt.removeAttribute("image");
+ else if (icon != elt.getAttribute("image"))
+ elt.setAttribute("image", icon);
+ },
+
+ nodeAnnotationChanged:
+ function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // All livemarks have a feedURI, so use it as our indicator of a livemark
+ // being modified.
+ if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+ let menu = elt.parentNode;
+ if (!menu.hasAttribute("livemark")) {
+ menu.setAttribute("livemark", "true");
+ if (AppConstants.platform === "macosx") {
+ // OS X native menubar doesn't track list-style-images since
+ // it doesn't have a frame (bug 733415). Thus enforce updating.
+ menu.setAttribute("image", "");
+ menu.removeAttribute("image");
+ }
+ }
+
+ PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
+ .then(aLivemark => {
+ // Controller will use this to build the meta data for the node.
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ this.invalidateContainer(aPlacesNode);
+ }, () => undefined);
+ }
+ },
+
+ nodeTitleChanged:
+ function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // There's no UI representation for the root node, thus there's
+ // nothing to be done when the title changes.
+ if (elt == this._rootElt)
+ return;
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (!aNewTitle && elt.localName != "toolbarbutton") {
+ // Many users consider toolbars as shortcuts containers, so explicitly
+ // allow empty labels on toolbarbuttons. For any other element try to be
+ // smarter, guessing a title from the uri.
+ elt.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
+ }
+ else {
+ elt.setAttribute("label", aNewTitle);
+ }
+ },
+
+ nodeRemoved:
+ function PVB_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (parentElt._built) {
+ parentElt.removeChild(elt);
+
+ // Figure out if we need to show the "<Empty>" menu-item.
+ // TODO Bug 517701: This doesn't seem to handle the case of an empty
+ // root.
+ if (parentElt._startMarker.nextSibling == parentElt._endMarker)
+ this._setEmptyPopupStatus(parentElt, true);
+ }
+ },
+
+ nodeHistoryDetailsChanged:
+ function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
+ if (aPlacesNode.parent &&
+ this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) {
+ // Find the node in the parent.
+ let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent);
+ for (let child = popup._startMarker.nextSibling;
+ child != popup._endMarker;
+ child = child.nextSibling) {
+ if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
+ if (aCount)
+ child.setAttribute("visited", "true");
+ else
+ child.removeAttribute("visited");
+ break;
+ }
+ }
+ }
+ },
+
+ nodeTagsChanged: function() { },
+ nodeDateAddedChanged: function() { },
+ nodeLastModifiedChanged: function() { },
+ nodeKeywordChanged: function() { },
+ sortingChanged: function() { },
+ batching: function() { },
+
+ nodeInserted:
+ function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ if (!parentElt._built)
+ return;
+
+ let index = Array.prototype.indexOf.call(parentElt.childNodes, parentElt._startMarker) +
+ aIndex + 1;
+ this._insertNewItemToPopup(aPlacesNode, parentElt,
+ parentElt.childNodes[index]);
+ this._setEmptyPopupStatus(parentElt, false);
+ },
+
+ nodeMoved:
+ function PBV_nodeMoved(aPlacesNode,
+ aOldParentPlacesNode, aOldIndex,
+ aNewParentPlacesNode, aNewIndex) {
+ // Note: the current implementation of moveItem does not actually
+ // use this notification when the item in question is moved from one
+ // folder to another. Instead, it calls nodeRemoved and nodeInserted
+ // for the two folders. Thus, we can assume old-parent == new-parent.
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ // If our root node is a folder, it might be moved. There's nothing
+ // we need to do in that case.
+ if (elt == this._rootElt)
+ return;
+
+ let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
+ if (parentElt._built) {
+ // Move the node.
+ parentElt.removeChild(elt);
+ let index = Array.prototype.indexOf.call(parentElt.childNodes, parentElt._startMarker) +
+ aNewIndex + 1;
+ parentElt.insertBefore(elt, parentElt.childNodes[index]);
+ }
+ },
+
+ containerStateChanged:
+ function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) {
+ if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
+ aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
+ this.invalidateContainer(aPlacesNode);
+
+ if (PlacesUtils.nodeIsFolder(aPlacesNode)) {
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ if (queryOptions.excludeItems) {
+ return;
+ }
+
+ PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
+ .then(aLivemark => {
+ let shouldInvalidate =
+ !this.controller.hasCachedLivemarkInfo(aPlacesNode);
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
+ aLivemark.registerForUpdates(aPlacesNode, this);
+ // Prioritize the current livemark.
+ aLivemark.reload();
+ PlacesUtils.livemarks.reloadLivemarks();
+ if (shouldInvalidate)
+ this.invalidateContainer(aPlacesNode);
+ }
+ else {
+ aLivemark.unregisterForUpdates(aPlacesNode);
+ }
+ }, () => undefined);
+ }
+ }
+ },
+
+ _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup)
+ {
+ this._setLivemarkSiteURIMenuItem(aPopup);
+ // Show the loading status only if there are no entries yet.
+ if (aPopup._startMarker.nextSibling == aPopup._endMarker)
+ this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
+
+ PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId })
+ .then(aLivemark => {
+ let placesNode = aPopup._placesNode;
+ if (!placesNode.containerOpen)
+ return;
+
+ if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING)
+ this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
+ this._cleanPopup(aPopup,
+ this._nativeView && aPopup.parentNode.hasAttribute("open"));
+
+ let children = aLivemark.getNodesForContainer(placesNode);
+ for (let i = 0; i < children.length; i++) {
+ let child = children[i];
+ this.nodeInserted(placesNode, child, i);
+ if (child.accessCount)
+ this._getDOMNodeForPlacesNode(child).setAttribute("visited", true);
+ else
+ this._getDOMNodeForPlacesNode(child).removeAttribute("visited");
+ }
+ }, Components.utils.reportError);
+ },
+
+ invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ elt._built = false;
+
+ // If the menupopup is open we should live-update it.
+ if (elt.parentNode.open)
+ this._rebuildPopup(elt);
+ },
+
+ uninit: function PVB_uninit() {
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._resultNode.containerOpen = false;
+ this._resultNode = null;
+ this._result = null;
+ }
+
+ if (this._controller) {
+ this._controller.terminate();
+ // Removing the controller will fail if it is already no longer there.
+ // This can happen if the view element was removed/reinserted without
+ // our knowledge. There is no way to check for that having happened
+ // without the possibility of an exception. :-(
+ try {
+ this._viewElt.controllers.removeController(this._controller);
+ } catch (ex) {
+ } finally {
+ this._controller = null;
+ }
+ }
+
+ delete this._viewElt._placesView;
+ },
+
+ get isRTL() {
+ if ("_isRTL" in this)
+ return this._isRTL;
+
+ return this._isRTL = document.defaultView
+ .getComputedStyle(this.viewElt, "")
+ .direction == "rtl";
+ },
+
+ get ownerWindow() {
+ return window;
+ },
+
+ /**
+ * Adds an "Open All in Tabs" menuitem to the bottom of the popup.
+ * @param aPopup
+ * a Places popup.
+ */
+ _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) {
+ // The command items are never added to the root popup.
+ if (aPopup == this._rootElt)
+ return;
+
+ let hasMultipleURIs = false;
+
+ // Check if the popup contains at least 2 menuitems with places nodes.
+ // We don't currently support opening multiple uri nodes when they are not
+ // populated by the result.
+ if (aPopup._placesNode.childCount > 0) {
+ let currentChild = aPopup.firstChild;
+ let numURINodes = 0;
+ while (currentChild) {
+ if (currentChild.localName == "menuitem" && currentChild._placesNode) {
+ if (++numURINodes == 2)
+ break;
+ }
+ currentChild = currentChild.nextSibling;
+ }
+ hasMultipleURIs = numURINodes > 1;
+ }
+
+ let isLiveMark = false;
+ if (this.controller.hasCachedLivemarkInfo(aPopup._placesNode)) {
+ hasMultipleURIs = true;
+ isLiveMark = true;
+ }
+
+ if (!hasMultipleURIs) {
+ aPopup.setAttribute("singleitempopup", "true");
+ } else {
+ aPopup.removeAttribute("singleitempopup");
+ }
+
+ if (!hasMultipleURIs) {
+ // We don't have to show any option.
+ if (aPopup._endOptOpenAllInTabs) {
+ aPopup.removeChild(aPopup._endOptOpenAllInTabs);
+ aPopup._endOptOpenAllInTabs = null;
+
+ aPopup.removeChild(aPopup._endOptSeparator);
+ aPopup._endOptSeparator = null;
+ }
+ }
+ else if (!aPopup._endOptOpenAllInTabs) {
+ // Create a separator before options.
+ aPopup._endOptSeparator = document.createElement("menuseparator");
+ aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
+ aPopup.appendChild(aPopup._endOptSeparator);
+
+ // Add the "Open All in Tabs" menuitem.
+ aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
+ aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
+
+ if (typeof this.options.extraClasses.entry == "string")
+ aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry);
+ if (typeof this.options.extraClasses.footer == "string")
+ aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer);
+
+ if (isLiveMark) {
+ aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
+ "PlacesUIUtils.openLiveMarkNodesInTabs(this.parentNode._placesNode, event, " +
+ "PlacesUIUtils.getViewForNode(this));");
+ } else {
+ aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
+ "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
+ "PlacesUIUtils.getViewForNode(this));");
+ }
+ aPopup._endOptOpenAllInTabs.setAttribute("onclick",
+ "checkForMiddleClick(this, event); event.stopPropagation();");
+ aPopup._endOptOpenAllInTabs.setAttribute("label",
+ gNavigatorBundle.getString("menuOpenAllInTabs.label"));
+ aPopup.appendChild(aPopup._endOptOpenAllInTabs);
+ }
+ },
+
+ _ensureMarkers: function PVB__ensureMarkers(aPopup) {
+ if (aPopup._startMarker)
+ return;
+
+ // _startMarker is an hidden menuseparator that lives before places nodes.
+ aPopup._startMarker = document.createElement("menuseparator");
+ aPopup._startMarker.hidden = true;
+ aPopup.insertBefore(aPopup._startMarker, aPopup.firstChild);
+
+ // _endMarker is a DOM node that lives after places nodes, specified with
+ // the 'insertionPoint' option or will be a hidden menuseparator.
+ let node = ("insertionPoint" in this.options) ?
+ aPopup.querySelector(this.options.insertionPoint) : null;
+ if (node) {
+ aPopup._endMarker = node;
+ } else {
+ aPopup._endMarker = document.createElement("menuseparator");
+ aPopup._endMarker.hidden = true;
+ }
+ aPopup.appendChild(aPopup._endMarker);
+
+ // Move the markers to the right position.
+ let firstNonStaticNodeFound = false;
+ for (let i = 0; i < aPopup.childNodes.length; i++) {
+ let child = aPopup.childNodes[i];
+ // Menus that have static content at the end, but are initially empty,
+ // use a special "builder" attribute to figure out where to start
+ // inserting places nodes.
+ if (child.getAttribute("builder") == "end") {
+ aPopup.insertBefore(aPopup._endMarker, child);
+ break;
+ }
+
+ if (child._placesNode && !child.hasAttribute("simulated-places-node") &&
+ !firstNonStaticNodeFound) {
+ firstNonStaticNodeFound = true;
+ aPopup.insertBefore(aPopup._startMarker, child);
+ }
+ }
+ if (!firstNonStaticNodeFound) {
+ aPopup.insertBefore(aPopup._startMarker, aPopup._endMarker);
+ }
+ },
+
+ _onPopupShowing: function PVB__onPopupShowing(aEvent) {
+ // Avoid handling popupshowing of inner views.
+ let popup = aEvent.originalTarget;
+
+ this._ensureMarkers(popup);
+
+ // Remove any delayed element, see _cleanPopup for details.
+ if ("_delayedRemovals" in popup) {
+ while (popup._delayedRemovals.length > 0) {
+ popup.removeChild(popup._delayedRemovals.shift());
+ }
+ }
+
+ if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
+ if (!popup._placesNode.containerOpen)
+ popup._placesNode.containerOpen = true;
+ if (!popup._built)
+ this._rebuildPopup(popup);
+
+ this._mayAddCommandsItems(popup);
+ }
+ },
+
+ _addEventListeners:
+ function PVB__addEventListeners(aObject, aEventNames, aCapturing) {
+ for (let i = 0; i < aEventNames.length; i++) {
+ aObject.addEventListener(aEventNames[i], this, aCapturing);
+ }
+ },
+
+ _removeEventListeners:
+ function PVB__removeEventListeners(aObject, aEventNames, aCapturing) {
+ for (let i = 0; i < aEventNames.length; i++) {
+ aObject.removeEventListener(aEventNames[i], this, aCapturing);
+ }
+ },
+};
+
+function PlacesToolbar(aPlace) {
+ let startTime = Date.now();
+ // Add some smart getters for our elements.
+ let thisView = this;
+ [
+ ["_viewElt", "PlacesToolbar"],
+ ["_rootElt", "PlacesToolbarItems"],
+ ["_dropIndicator", "PlacesToolbarDropIndicator"],
+ ["_chevron", "PlacesChevron"],
+ ["_chevronPopup", "PlacesChevronPopup"]
+ ].forEach(function (elementGlobal) {
+ let [name, id] = elementGlobal;
+ thisView.__defineGetter__(name, function () {
+ let element = document.getElementById(id);
+ if (!element)
+ return null;
+
+ delete thisView[name];
+ return thisView[name] = element;
+ });
+ });
+
+ this._viewElt._placesView = this;
+
+ this._addEventListeners(this._viewElt, this._cbEvents, false);
+ this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
+ this._addEventListeners(this._rootElt, ["overflow", "underflow"], true);
+ this._addEventListeners(window, ["resize", "unload"], false);
+
+ // If personal-bookmarks has been dragged to the tabs toolbar,
+ // we have to track addition and removals of tabs, to properly
+ // recalculate the available space for bookmarks.
+ // TODO (bug 734730): Use a performant mutation listener when available.
+ if (this._viewElt.parentNode.parentNode == document.getElementById("TabsToolbar")) {
+ this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
+ }
+
+ PlacesViewBase.call(this, aPlace);
+
+ Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS")
+ .add(Date.now() - startTime);
+}
+
+PlacesToolbar.prototype = {
+ __proto__: PlacesViewBase.prototype,
+
+ _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop",
+ "mousemove", "mouseover", "mouseout"],
+
+ QueryInterface: function PT_QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDOMEventListener) ||
+ aIID.equals(Ci.nsITimerCallback))
+ return this;
+
+ return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
+ },
+
+ uninit: function PT_uninit() {
+ this._removeEventListeners(this._viewElt, this._cbEvents, false);
+ this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
+ true);
+ this._removeEventListeners(this._rootElt, ["overflow", "underflow"], true);
+ this._removeEventListeners(window, ["resize", "unload"], false);
+ this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
+
+ if (this._chevron._placesView) {
+ this._chevron._placesView.uninit();
+ }
+
+ PlacesViewBase.prototype.uninit.apply(this, arguments);
+ },
+
+ _openedMenuButton: null,
+ _allowPopupShowing: true,
+
+ _rebuild: function PT__rebuild() {
+ // Clear out references to existing nodes, since they will be removed
+ // and re-added.
+ if (this._overFolder.elt)
+ this._clearOverFolder();
+
+ this._openedMenuButton = null;
+ while (this._rootElt.hasChildNodes()) {
+ this._rootElt.removeChild(this._rootElt.firstChild);
+ }
+
+ let cc = this._resultNode.childCount;
+ for (let i = 0; i < cc; ++i) {
+ this._insertNewItem(this._resultNode.getChild(i), null);
+ }
+
+ if (this._chevronPopup.hasAttribute("type")) {
+ // Chevron has already been initialized, but since we are forcing
+ // a rebuild of the toolbar, it has to be rebuilt.
+ // Otherwise, it will be initialized when the toolbar overflows.
+ this._chevronPopup.place = this.place;
+ }
+ },
+
+ _insertNewItem:
+ function PT__insertNewItem(aChild, aBefore) {
+ this._domNodes.delete(aChild);
+
+ let type = aChild.type;
+ let button;
+ if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
+ button = document.createElement("toolbarseparator");
+ }
+ else {
+ button = document.createElement("toolbarbutton");
+ button.className = "bookmark-item";
+ button.setAttribute("label", aChild.title || "");
+ let icon = aChild.icon;
+ if (icon)
+ button.setAttribute("image", icon);
+
+ if (PlacesUtils.containerTypes.includes(type)) {
+ button.setAttribute("type", "menu");
+ button.setAttribute("container", "true");
+
+ if (PlacesUtils.nodeIsQuery(aChild)) {
+ button.setAttribute("query", "true");
+ if (PlacesUtils.nodeIsTagQuery(aChild))
+ button.setAttribute("tagContainer", "true");
+ }
+ else if (PlacesUtils.nodeIsFolder(aChild)) {
+ PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
+ .then(aLivemark => {
+ button.setAttribute("livemark", "true");
+ this.controller.cacheLivemarkInfo(aChild, aLivemark);
+ }, () => undefined);
+ }
+
+ let popup = document.createElement("menupopup");
+ popup.setAttribute("placespopup", "true");
+ button.appendChild(popup);
+ popup._placesNode = PlacesUtils.asContainer(aChild);
+ popup.setAttribute("context", "placesContext");
+
+ this._domNodes.set(aChild, popup);
+ }
+ else if (PlacesUtils.nodeIsURI(aChild)) {
+ button.setAttribute("scheme",
+ PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
+ }
+ }
+
+ button._placesNode = aChild;
+ if (!this._domNodes.has(aChild))
+ this._domNodes.set(aChild, button);
+
+ if (aBefore)
+ this._rootElt.insertBefore(button, aBefore);
+ else
+ this._rootElt.appendChild(button);
+ },
+
+ _updateChevronPopupNodesVisibility:
+ function PT__updateChevronPopupNodesVisibility() {
+ for (let i = 0, node = this._chevronPopup._startMarker.nextSibling;
+ node != this._chevronPopup._endMarker;
+ i++, node = node.nextSibling) {
+ node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden";
+ }
+ },
+
+ _onChevronPopupShowing:
+ function PT__onChevronPopupShowing(aEvent) {
+ // Handle popupshowing only for the chevron popup, not for nested ones.
+ if (aEvent.target != this._chevronPopup)
+ return;
+
+ if (!this._chevron._placesView)
+ this._chevron._placesView = new PlacesMenu(aEvent, this.place);
+
+ this._updateChevronPopupNodesVisibility();
+ },
+
+ handleEvent: function PT_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ this.uninit();
+ break;
+ case "resize":
+ // This handler updates nodes visibility in both the toolbar
+ // and the chevron popup when a window resize does not change
+ // the overflow status of the toolbar.
+ this.updateChevron();
+ break;
+ case "overflow":
+ if (!this._isOverflowStateEventRelevant(aEvent))
+ return;
+ this._onOverflow();
+ break;
+ case "underflow":
+ if (!this._isOverflowStateEventRelevant(aEvent))
+ return;
+ this._onUnderflow();
+ break;
+ case "TabOpen":
+ case "TabClose":
+ this.updateChevron();
+ break;
+ case "dragstart":
+ this._onDragStart(aEvent);
+ break;
+ case "dragover":
+ this._onDragOver(aEvent);
+ break;
+ case "dragexit":
+ this._onDragExit(aEvent);
+ break;
+ case "dragend":
+ this._onDragEnd(aEvent);
+ break;
+ case "drop":
+ this._onDrop(aEvent);
+ break;
+ case "mouseover":
+ this._onMouseOver(aEvent);
+ break;
+ case "mousemove":
+ this._onMouseMove(aEvent);
+ break;
+ case "mouseout":
+ this._onMouseOut(aEvent);
+ break;
+ case "popupshowing":
+ this._onPopupShowing(aEvent);
+ break;
+ case "popuphidden":
+ this._onPopupHidden(aEvent);
+ break;
+ default:
+ throw "Trying to handle unexpected event.";
+ }
+ },
+
+ updateOverflowStatus: function() {
+ if (this._rootElt.scrollLeftMin != this._rootElt.scrollLeftMax) {
+ this._onOverflow();
+ } else {
+ this._onUnderflow();
+ }
+ },
+
+ _isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) {
+ // Ignore events not aimed at ourselves, as well as purely vertical ones:
+ return aEvent.target == aEvent.currentTarget && aEvent.detail > 0;
+ },
+
+ _onOverflow: function PT_onOverflow() {
+ // Attach the popup binding to the chevron popup if it has not yet
+ // been initialized.
+ if (!this._chevronPopup.hasAttribute("type")) {
+ this._chevronPopup.setAttribute("place", this.place);
+ this._chevronPopup.setAttribute("type", "places");
+ }
+ this._chevron.collapsed = false;
+ this.updateChevron();
+ },
+
+ _onUnderflow: function PT_onUnderflow() {
+ this.updateChevron();
+ this._chevron.collapsed = true;
+ },
+
+ updateChevron: function PT_updateChevron() {
+ // If the chevron is collapsed there's nothing to update.
+ if (this._chevron.collapsed)
+ return;
+
+ // Update the chevron on a timer. This will avoid repeated work when
+ // lot of changes happen in a small timeframe.
+ if (this._updateChevronTimer)
+ this._updateChevronTimer.cancel();
+
+ this._updateChevronTimer = this._setTimer(100);
+ },
+
+ _updateChevronTimerCallback: function PT__updateChevronTimerCallback() {
+ let scrollRect = this._rootElt.getBoundingClientRect();
+ let childOverflowed = false;
+ for (let i = 0; i < this._rootElt.childNodes.length; i++) {
+ let child = this._rootElt.childNodes[i];
+ // Once a child overflows, all the next ones will.
+ if (!childOverflowed) {
+ let childRect = child.getBoundingClientRect();
+ childOverflowed = this.isRTL ? (childRect.left < scrollRect.left)
+ : (childRect.right > scrollRect.right);
+
+ }
+ child.style.visibility = childOverflowed ? "hidden" : "visible";
+ }
+
+ // We rebuild the chevron on popupShowing, so if it is open
+ // we must update it.
+ if (this._chevron.open)
+ this._updateChevronPopupNodesVisibility();
+ },
+
+ nodeInserted:
+ function PT_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ if (parentElt == this._rootElt) {
+ let children = this._rootElt.childNodes;
+ this._insertNewItem(aPlacesNode,
+ aIndex < children.length ? children[aIndex] : null);
+ this.updateChevron();
+ return;
+ }
+
+ PlacesViewBase.prototype.nodeInserted.apply(this, arguments);
+ },
+
+ nodeRemoved:
+ function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (parentElt == this._rootElt) {
+ this._removeChild(elt);
+ this.updateChevron();
+ return;
+ }
+
+ PlacesViewBase.prototype.nodeRemoved.apply(this, arguments);
+ },
+
+ nodeMoved:
+ function PT_nodeMoved(aPlacesNode,
+ aOldParentPlacesNode, aOldIndex,
+ aNewParentPlacesNode, aNewIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
+ if (parentElt == this._rootElt) {
+ // Container is on the toolbar.
+
+ // Move the element.
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ this._removeChild(elt);
+ this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
+
+ // The chevron view may get nodeMoved after the toolbar. In such a case,
+ // we should ensure (by manually swapping menuitems) that the actual nodes
+ // are in the final position before updateChevron tries to updates their
+ // visibility, or the chevron may go out of sync.
+ // Luckily updateChevron runs on a timer, so, by the time it updates
+ // nodes, the menu has already handled the notification.
+
+ this.updateChevron();
+ return;
+ }
+
+ PlacesViewBase.prototype.nodeMoved.apply(this, arguments);
+ },
+
+ nodeAnnotationChanged:
+ function PT_nodeAnnotationChanged(aPlacesNode, aAnno) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ if (elt == this._rootElt)
+ return;
+
+ // We're notified for the menupopup, not the containing toolbarbutton.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (elt.parentNode == this._rootElt) {
+ // Node is on the toolbar.
+
+ // All livemarks have a feedURI, so use it as our indicator.
+ if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+ elt.setAttribute("livemark", true);
+
+ PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
+ .then(aLivemark => {
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ this.invalidateContainer(aPlacesNode);
+ }, Components.utils.reportError);
+ }
+ }
+ else {
+ // Node is in a submenu.
+ PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
+ }
+ },
+
+ nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // There's no UI representation for the root node, thus there's
+ // nothing to be done when the title changes.
+ if (elt == this._rootElt)
+ return;
+
+ PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (elt.parentNode == this._rootElt) {
+ // Node is on the toolbar
+ this.updateChevron();
+ }
+ },
+
+ invalidateContainer: function PT_invalidateContainer(aPlacesNode) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ if (elt == this._rootElt) {
+ // Container is the toolbar itself.
+ this._rebuild();
+ return;
+ }
+
+ PlacesViewBase.prototype.invalidateContainer.apply(this, arguments);
+ },
+
+ _overFolder: { elt: null,
+ openTimer: null,
+ hoverTime: 350,
+ closeTimer: null },
+
+ _clearOverFolder: function PT__clearOverFolder() {
+ // The mouse is no longer dragging over the stored menubutton.
+ // Close the menubutton, clear out drag styles, and clear all
+ // timers for opening/closing it.
+ if (this._overFolder.elt && this._overFolder.elt.lastChild) {
+ if (!this._overFolder.elt.lastChild.hasAttribute("dragover")) {
+ this._overFolder.elt.lastChild.hidePopup();
+ }
+ this._overFolder.elt.removeAttribute("dragover");
+ this._overFolder.elt = null;
+ }
+ if (this._overFolder.openTimer) {
+ this._overFolder.openTimer.cancel();
+ this._overFolder.openTimer = null;
+ }
+ if (this._overFolder.closeTimer) {
+ this._overFolder.closeTimer.cancel();
+ this._overFolder.closeTimer = null;
+ }
+ },
+
+ /**
+ * This function returns information about where to drop when dragging over
+ * the toolbar. The returned object has the following properties:
+ * - ip: the insertion point for the bookmarks service.
+ * - beforeIndex: child index to drop before, for the drop indicator.
+ * - folderElt: the folder to drop into, if applicable.
+ */
+ _getDropPoint: function PT__getDropPoint(aEvent) {
+ if (!PlacesUtils.nodeIsFolder(this._resultNode))
+ return null;
+
+ let dropPoint = { ip: null, beforeIndex: null, folderElt: null };
+ let elt = aEvent.target;
+ if (elt._placesNode && elt != this._rootElt &&
+ elt.localName != "menupopup") {
+ let eltRect = elt.getBoundingClientRect();
+ let eltIndex = Array.prototype.indexOf.call(this._rootElt.childNodes, elt);
+ if (PlacesUtils.nodeIsFolder(elt._placesNode) &&
+ !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) {
+ // This is a folder.
+ // If we are in the middle of it, drop inside it.
+ // Otherwise, drop before it, with regards to RTL mode.
+ let threshold = eltRect.width * 0.25;
+ if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold)
+ : (aEvent.clientX < eltRect.left + threshold)) {
+ // Drop before this folder.
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ eltIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = eltIndex;
+ }
+ else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
+ : (aEvent.clientX < eltRect.right - threshold)) {
+ // Drop inside this folder.
+ let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
+ elt._placesNode.title : null;
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode),
+ -1, Ci.nsITreeView.DROP_ON,
+ tagName);
+ dropPoint.beforeIndex = eltIndex;
+ dropPoint.folderElt = elt;
+ }
+ else {
+ // Drop after this folder.
+ let beforeIndex =
+ (eltIndex == this._rootElt.childNodes.length - 1) ?
+ -1 : eltIndex + 1;
+
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ beforeIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = beforeIndex;
+ }
+ }
+ else {
+ // This is a non-folder node or a read-only folder.
+ // Drop before it with regards to RTL mode.
+ let threshold = eltRect.width * 0.5;
+ if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
+ : (aEvent.clientX < eltRect.left + threshold)) {
+ // Drop before this bookmark.
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ eltIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = eltIndex;
+ }
+ else {
+ // Drop after this bookmark.
+ let beforeIndex =
+ eltIndex == this._rootElt.childNodes.length - 1 ?
+ -1 : eltIndex + 1;
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ beforeIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = beforeIndex;
+ }
+ }
+ }
+ else {
+ // We are most likely dragging on the empty area of the
+ // toolbar, we should drop after the last node.
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ -1, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = -1;
+ }
+
+ return dropPoint;
+ },
+
+ _setTimer: function PT_setTimer(aTime) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ notify: function PT_notify(aTimer) {
+ if (aTimer == this._updateChevronTimer) {
+ this._updateChevronTimer = null;
+ this._updateChevronTimerCallback();
+ }
+
+ // * Timer to turn off indicator bar.
+ else if (aTimer == this._ibTimer) {
+ this._dropIndicator.collapsed = true;
+ this._ibTimer = null;
+ }
+
+ // * Timer to open a menubutton that's being dragged over.
+ else if (aTimer == this._overFolder.openTimer) {
+ // Set the autoopen attribute on the folder's menupopup so that
+ // the menu will automatically close when the mouse drags off of it.
+ this._overFolder.elt.lastChild.setAttribute("autoopened", "true");
+ this._overFolder.elt.open = true;
+ this._overFolder.openTimer = null;
+ }
+
+ // * Timer to close a menubutton that's been dragged off of.
+ else if (aTimer == this._overFolder.closeTimer) {
+ // Close the menubutton if we are not dragging over it or one of
+ // its children. The autoopened attribute will let the menu know to
+ // close later if the menu is still being dragged over.
+ let currentPlacesNode = PlacesControllerDragHelper.currentDropTarget;
+ let inHierarchy = false;
+ while (currentPlacesNode) {
+ if (currentPlacesNode == this._rootElt) {
+ inHierarchy = true;
+ break;
+ }
+ currentPlacesNode = currentPlacesNode.parentNode;
+ }
+ // The _clearOverFolder() function will close the menu for
+ // _overFolder.elt. So null it out if we don't want to close it.
+ if (inHierarchy)
+ this._overFolder.elt = null;
+
+ // Clear out the folder and all associated timers.
+ this._clearOverFolder();
+ }
+ },
+
+ _onMouseOver: function PT__onMouseOver(aEvent) {
+ let button = aEvent.target;
+ if (button.parentNode == this._rootElt && button._placesNode &&
+ PlacesUtils.nodeIsURI(button._placesNode))
+ window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri, null);
+ },
+
+ _onMouseOut: function PT__onMouseOut(aEvent) {
+ window.XULBrowserWindow.setOverLink("", null);
+ },
+
+ _cleanupDragDetails: function PT__cleanupDragDetails() {
+ // Called on dragend and drop.
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this._draggedElt = null;
+ if (this._ibTimer)
+ this._ibTimer.cancel();
+
+ this._dropIndicator.collapsed = true;
+ },
+
+ _onDragStart: function PT__onDragStart(aEvent) {
+ // Sub menus have their own d&d handlers.
+ let draggedElt = aEvent.target;
+ if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode)
+ return;
+
+ if (draggedElt.localName == "toolbarbutton" &&
+ draggedElt.getAttribute("type") == "menu") {
+ // If the drag gesture on a container is toward down we open instead
+ // of dragging.
+ let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY;
+ let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX;
+ if ((translateY) >= Math.abs(translateX/2)) {
+ // Don't start the drag.
+ aEvent.preventDefault();
+ // Open the menu.
+ draggedElt.open = true;
+ return;
+ }
+
+ // If the menu is open, close it.
+ if (draggedElt.open) {
+ draggedElt.lastChild.hidePopup();
+ draggedElt.open = false;
+ }
+ }
+
+ // Activate the view and cache the dragged element.
+ this._draggedElt = draggedElt._placesNode;
+ this._rootElt.focus();
+
+ this._controller.setDataTransfer(aEvent);
+ aEvent.stopPropagation();
+ },
+
+ _onDragOver: function PT__onDragOver(aEvent) {
+ // Cache the dataTransfer
+ PlacesControllerDragHelper.currentDropTarget = aEvent.target;
+ let dt = aEvent.dataTransfer;
+
+ let dropPoint = this._getDropPoint(aEvent);
+ if (!dropPoint || !dropPoint.ip ||
+ !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
+ this._dropIndicator.collapsed = true;
+ aEvent.stopPropagation();
+ return;
+ }
+
+ if (this._ibTimer) {
+ this._ibTimer.cancel();
+ this._ibTimer = null;
+ }
+
+ if (dropPoint.folderElt || aEvent.originalTarget == this._chevron) {
+ // Dropping over a menubutton or chevron button.
+ // Set styles and timer to open relative menupopup.
+ let overElt = dropPoint.folderElt || this._chevron;
+ if (this._overFolder.elt != overElt) {
+ this._clearOverFolder();
+ this._overFolder.elt = overElt;
+ this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
+ }
+ if (!this._overFolder.elt.hasAttribute("dragover"))
+ this._overFolder.elt.setAttribute("dragover", "true");
+
+ this._dropIndicator.collapsed = true;
+ }
+ else {
+ // Dragging over a normal toolbarbutton,
+ // show indicator bar and move it to the appropriate drop point.
+ let ind = this._dropIndicator;
+ ind.parentNode.collapsed = false;
+ let halfInd = ind.clientWidth / 2;
+ let translateX;
+ if (this.isRTL) {
+ halfInd = Math.ceil(halfInd);
+ translateX = 0 - this._rootElt.getBoundingClientRect().right - halfInd;
+ if (this._rootElt.firstChild) {
+ if (dropPoint.beforeIndex == -1)
+ translateX += this._rootElt.lastChild.getBoundingClientRect().left;
+ else {
+ translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
+ .getBoundingClientRect().right;
+ }
+ }
+ }
+ else {
+ halfInd = Math.floor(halfInd);
+ translateX = 0 - this._rootElt.getBoundingClientRect().left +
+ halfInd;
+ if (this._rootElt.firstChild) {
+ if (dropPoint.beforeIndex == -1)
+ translateX += this._rootElt.lastChild.getBoundingClientRect().right;
+ else {
+ translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
+ .getBoundingClientRect().left;
+ }
+ }
+ }
+
+ ind.style.transform = "translate(" + Math.round(translateX) + "px)";
+ ind.style.marginInlineStart = (-ind.clientWidth) + "px";
+ ind.collapsed = false;
+
+ // Clear out old folder information.
+ this._clearOverFolder();
+ }
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ },
+
+ _onDrop: function PT__onDrop(aEvent) {
+ PlacesControllerDragHelper.currentDropTarget = aEvent.target;
+
+ let dropPoint = this._getDropPoint(aEvent);
+ if (dropPoint && dropPoint.ip) {
+ PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer)
+ .then(null, Components.utils.reportError);
+ aEvent.preventDefault();
+ }
+
+ this._cleanupDragDetails();
+ aEvent.stopPropagation();
+ },
+
+ _onDragExit: function PT__onDragExit(aEvent) {
+ PlacesControllerDragHelper.currentDropTarget = null;
+
+ // Set timer to turn off indicator bar (if we turn it off
+ // here, dragenter might be called immediately after, creating
+ // flicker).
+ if (this._ibTimer)
+ this._ibTimer.cancel();
+ this._ibTimer = this._setTimer(10);
+
+ // If we hovered over a folder, close it now.
+ if (this._overFolder.elt)
+ this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
+ },
+
+ _onDragEnd: function PT_onDragEnd(aEvent) {
+ this._cleanupDragDetails();
+ },
+
+ _onPopupShowing: function PT__onPopupShowing(aEvent) {
+ if (!this._allowPopupShowing) {
+ this._allowPopupShowing = true;
+ aEvent.preventDefault();
+ return;
+ }
+
+ let parent = aEvent.target.parentNode;
+ if (parent.localName == "toolbarbutton")
+ this._openedMenuButton = parent;
+
+ PlacesViewBase.prototype._onPopupShowing.apply(this, arguments);
+ },
+
+ _onPopupHidden: function PT__onPopupHidden(aEvent) {
+ let popup = aEvent.target;
+ let placesNode = popup._placesNode;
+ // Avoid handling popuphidden of inner views
+ if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
+ // UI performance: folder queries are cheap, keep the resultnode open
+ // so we don't rebuild its contents whenever the popup is reopened.
+ // Though, we want to always close feed containers so their expiration
+ // status will be checked at next opening.
+ if (!PlacesUtils.nodeIsFolder(placesNode) ||
+ this.controller.hasCachedLivemarkInfo(placesNode)) {
+ placesNode.containerOpen = false;
+ }
+ }
+
+ let parent = popup.parentNode;
+ if (parent.localName == "toolbarbutton") {
+ this._openedMenuButton = null;
+ // Clear the dragover attribute if present, if we are dragging into a
+ // folder in the hierachy of current opened popup we don't clear
+ // this attribute on clearOverFolder. See Notify for closeTimer.
+ if (parent.hasAttribute("dragover"))
+ parent.removeAttribute("dragover");
+ }
+ },
+
+ _onMouseMove: function PT__onMouseMove(aEvent) {
+ // Used in dragStart to prevent dragging folders when dragging down.
+ this._cachedMouseMoveEvent = aEvent;
+
+ if (this._openedMenuButton == null ||
+ PlacesControllerDragHelper.getSession())
+ return;
+
+ let target = aEvent.originalTarget;
+ if (this._openedMenuButton != target &&
+ target.localName == "toolbarbutton" &&
+ target.type == "menu") {
+ this._openedMenuButton.open = false;
+ target.open = true;
+ }
+ }
+};
+
+/**
+ * View for Places menus. This object should be created during the first
+ * popupshowing that's dispatched on the menu.
+ */
+function PlacesMenu(aPopupShowingEvent, aPlace, aOptions) {
+ this._rootElt = aPopupShowingEvent.target; // <menupopup>
+ this._viewElt = this._rootElt.parentNode; // <menu>
+ this._viewElt._placesView = this;
+ this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
+ this._addEventListeners(window, ["unload"], false);
+
+ if (AppConstants.platform === "macosx") {
+ // Must walk up to support views in sub-menus, like Bookmarks Toolbar menu.
+ for (let elt = this._viewElt.parentNode; elt; elt = elt.parentNode) {
+ if (elt.localName == "menubar") {
+ this._nativeView = true;
+ break;
+ }
+ }
+ }
+
+ PlacesViewBase.call(this, aPlace, aOptions);
+ this._onPopupShowing(aPopupShowingEvent);
+}
+
+PlacesMenu.prototype = {
+ __proto__: PlacesViewBase.prototype,
+
+ QueryInterface: function PM_QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDOMEventListener))
+ return this;
+
+ return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
+ },
+
+ _removeChild: function PM_removeChild(aChild) {
+ PlacesViewBase.prototype._removeChild.apply(this, arguments);
+ },
+
+ uninit: function PM_uninit() {
+ this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
+ true);
+ this._removeEventListeners(window, ["unload"], false);
+
+ PlacesViewBase.prototype.uninit.apply(this, arguments);
+ },
+
+ handleEvent: function PM_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ this.uninit();
+ break;
+ case "popupshowing":
+ this._onPopupShowing(aEvent);
+ break;
+ case "popuphidden":
+ this._onPopupHidden(aEvent);
+ break;
+ }
+ },
+
+ _onPopupHidden: function PM__onPopupHidden(aEvent) {
+ // Avoid handling popuphidden of inner views.
+ let popup = aEvent.originalTarget;
+ let placesNode = popup._placesNode;
+ if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this)
+ return;
+
+ // UI performance: folder queries are cheap, keep the resultnode open
+ // so we don't rebuild its contents whenever the popup is reopened.
+ // Though, we want to always close feed containers so their expiration
+ // status will be checked at next opening.
+ if (!PlacesUtils.nodeIsFolder(placesNode) ||
+ this.controller.hasCachedLivemarkInfo(placesNode))
+ placesNode.containerOpen = false;
+
+ // The autoopened attribute is set for folders which have been
+ // automatically opened when dragged over. Turn off this attribute
+ // when the folder closes because it is no longer applicable.
+ popup.removeAttribute("autoopened");
+ popup.removeAttribute("dragstart");
+ }
+};
+
+function PlacesPanelMenuView(aPlace, aViewId, aRootId, aOptions) {
+ this._viewElt = document.getElementById(aViewId);
+ this._rootElt = document.getElementById(aRootId);
+ this._viewElt._placesView = this;
+ this.options = aOptions;
+
+ PlacesViewBase.call(this, aPlace, aOptions);
+}
+
+PlacesPanelMenuView.prototype = {
+ __proto__: PlacesViewBase.prototype,
+
+ QueryInterface: function PAMV_QueryInterface(aIID) {
+ return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
+ },
+
+ uninit: function PAMV_uninit() {
+ PlacesViewBase.prototype.uninit.apply(this, arguments);
+ },
+
+ _insertNewItem:
+ function PAMV__insertNewItem(aChild, aBefore) {
+ this._domNodes.delete(aChild);
+
+ let type = aChild.type;
+ let button;
+ if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
+ button = document.createElement("toolbarseparator");
+ button.setAttribute("class", "small-separator");
+ }
+ else {
+ button = document.createElement("toolbarbutton");
+ button.className = "bookmark-item";
+ if (typeof this.options.extraClasses.entry == "string")
+ button.classList.add(this.options.extraClasses.entry);
+ button.setAttribute("label", aChild.title || "");
+ let icon = aChild.icon;
+ if (icon)
+ button.setAttribute("image", icon);
+
+ if (PlacesUtils.containerTypes.includes(type)) {
+ button.setAttribute("container", "true");
+
+ if (PlacesUtils.nodeIsQuery(aChild)) {
+ button.setAttribute("query", "true");
+ if (PlacesUtils.nodeIsTagQuery(aChild))
+ button.setAttribute("tagContainer", "true");
+ }
+ else if (PlacesUtils.nodeIsFolder(aChild)) {
+ PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
+ .then(aLivemark => {
+ button.setAttribute("livemark", "true");
+ this.controller.cacheLivemarkInfo(aChild, aLivemark);
+ }, () => undefined);
+ }
+ }
+ else if (PlacesUtils.nodeIsURI(aChild)) {
+ button.setAttribute("scheme",
+ PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
+ }
+ }
+
+ button._placesNode = aChild;
+ if (!this._domNodes.has(aChild))
+ this._domNodes.set(aChild, button);
+
+ this._rootElt.insertBefore(button, aBefore);
+ },
+
+ nodeInserted:
+ function PAMV_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ if (parentElt != this._rootElt)
+ return;
+
+ let children = this._rootElt.childNodes;
+ this._insertNewItem(aPlacesNode,
+ aIndex < children.length ? children[aIndex] : null);
+ },
+
+ nodeRemoved:
+ function PAMV_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ if (parentElt != this._rootElt)
+ return;
+
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ this._removeChild(elt);
+ },
+
+ nodeMoved:
+ function PAMV_nodeMoved(aPlacesNode,
+ aOldParentPlacesNode, aOldIndex,
+ aNewParentPlacesNode, aNewIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
+ if (parentElt != this._rootElt)
+ return;
+
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ this._removeChild(elt);
+ this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
+ },
+
+ nodeAnnotationChanged:
+ function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ // There's no UI representation for the root node.
+ if (elt == this._rootElt)
+ return;
+
+ if (elt.parentNode != this._rootElt)
+ return;
+
+ // All livemarks have a feedURI, so use it as our indicator.
+ if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+ elt.setAttribute("livemark", true);
+
+ PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
+ .then(aLivemark => {
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ this.invalidateContainer(aPlacesNode);
+ }, Components.utils.reportError);
+ }
+ },
+
+ nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // There's no UI representation for the root node.
+ if (elt == this._rootElt)
+ return;
+
+ PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
+ },
+
+ invalidateContainer: function PAMV_invalidateContainer(aPlacesNode) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ if (elt != this._rootElt)
+ return;
+
+ // Container is the toolbar itself.
+ while (this._rootElt.hasChildNodes()) {
+ this._rootElt.removeChild(this._rootElt.firstChild);
+ }
+
+ for (let i = 0; i < this._resultNode.childCount; ++i) {
+ this._insertNewItem(this._resultNode.getChild(i), null);
+ }
+ }
+};
diff --git a/browser/components/places/content/controller.js b/browser/components/places/content/controller.js
new file mode 100644
index 000000000..0d66fbcaf
--- /dev/null
+++ b/browser/components/places/content/controller.js
@@ -0,0 +1,1742 @@
+/* -*- 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/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "ForgetAboutSite",
+ "resource://gre/modules/ForgetAboutSite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+// XXXmano: we should move most/all of these constants to PlacesUtils
+const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1";
+
+// No change to the view, preserve current selection
+const RELOAD_ACTION_NOTHING = 0;
+// Inserting items new to the view, select the inserted rows
+const RELOAD_ACTION_INSERT = 1;
+// Removing items from the view, select the first item after the last selected
+const RELOAD_ACTION_REMOVE = 2;
+// Moving items within a view, don't treat the dropped items as additional
+// rows.
+const RELOAD_ACTION_MOVE = 3;
+
+// When removing a bunch of pages we split them in chunks to give some breath
+// to the main-thread.
+const REMOVE_PAGES_CHUNKLEN = 300;
+
+/**
+ * Represents an insertion point within a container where we can insert
+ * items.
+ * @param aItemId
+ * The identifier of the parent container
+ * @param aIndex
+ * The index within the container where we should insert
+ * @param aOrientation
+ * The orientation of the insertion. NOTE: the adjustments to the
+ * insertion point to accommodate the orientation should be done by
+ * the person who constructs the IP, not the user. The orientation
+ * is provided for informational purposes only!
+ * @param [optional] aTag
+ * The tag name if this IP is set to a tag, null otherwise.
+ * @param [optional] aDropNearItemId
+ * When defined we will calculate index based on this itemId
+ * @constructor
+ */
+function InsertionPoint(aItemId, aIndex, aOrientation, aTagName = null,
+ aDropNearItemId = false) {
+ this.itemId = aItemId;
+ this._index = aIndex;
+ this.orientation = aOrientation;
+ this.tagName = aTagName;
+ this.dropNearItemId = aDropNearItemId;
+}
+
+InsertionPoint.prototype = {
+ set index(val) {
+ return this._index = val;
+ },
+
+ promiseGuid: function () {
+ return PlacesUtils.promiseItemGuid(this.itemId);
+ },
+
+ get index() {
+ if (this.dropNearItemId > 0) {
+ // If dropNearItemId is set up we must calculate the real index of
+ // the item near which we will drop.
+ var index = PlacesUtils.bookmarks.getItemIndex(this.dropNearItemId);
+ return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1;
+ }
+ return this._index;
+ },
+
+ get isTag() {
+ return typeof(this.tagName) == "string";
+ }
+};
+
+/**
+ * Places Controller
+ */
+
+function PlacesController(aView) {
+ this._view = aView;
+ XPCOMUtils.defineLazyServiceGetter(this, "clipboard",
+ "@mozilla.org/widget/clipboard;1",
+ "nsIClipboard");
+ XPCOMUtils.defineLazyGetter(this, "profileName", function () {
+ return Services.dirsvc.get("ProfD", Ci.nsIFile).leafName;
+ });
+
+ this._cachedLivemarkInfoObjects = new Map();
+}
+
+PlacesController.prototype = {
+ /**
+ * The places view.
+ */
+ _view: null,
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIClipboardOwner
+ ]),
+
+ // nsIClipboardOwner
+ LosingOwnership: function PC_LosingOwnership (aXferable) {
+ this.cutNodes = [];
+ },
+
+ terminate: function PC_terminate() {
+ this._releaseClipboardOwnership();
+ },
+
+ supportsCommand: function PC_supportsCommand(aCommand) {
+ // Non-Places specific commands that we also support
+ switch (aCommand) {
+ case "cmd_undo":
+ case "cmd_redo":
+ case "cmd_cut":
+ case "cmd_copy":
+ case "cmd_paste":
+ case "cmd_delete":
+ case "cmd_selectAll":
+ return true;
+ }
+
+ // All other Places Commands are prefixed with "placesCmd_" ... this
+ // filters out other commands that we do _not_ support (see 329587).
+ const CMD_PREFIX = "placesCmd_";
+ return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
+ },
+
+ isCommandEnabled: function PC_isCommandEnabled(aCommand) {
+ switch (aCommand) {
+ case "cmd_undo":
+ if (!PlacesUIUtils.useAsyncTransactions)
+ return PlacesUtils.transactionManager.numberOfUndoItems > 0;
+
+ return PlacesTransactions.topUndoEntry != null;
+ case "cmd_redo":
+ if (!PlacesUIUtils.useAsyncTransactions)
+ return PlacesUtils.transactionManager.numberOfRedoItems > 0;
+
+ return PlacesTransactions.topRedoEntry != null;
+ case "cmd_cut":
+ case "placesCmd_cut":
+ case "placesCmd_moveBookmarks":
+ for (let node of this._view.selectedNodes) {
+ // If selection includes history nodes or tags-as-bookmark, disallow
+ // cutting.
+ if (node.itemId == -1 ||
+ (node.parent && PlacesUtils.nodeIsTagQuery(node.parent))) {
+ return false;
+ }
+ }
+ // Otherwise fall through the cmd_delete check.
+ case "cmd_delete":
+ case "placesCmd_delete":
+ case "placesCmd_deleteDataHost":
+ return this._hasRemovableSelection();
+ case "cmd_copy":
+ case "placesCmd_copy":
+ return this._view.hasSelection;
+ case "cmd_paste":
+ case "placesCmd_paste":
+ return this._canInsert(true) && this._isClipboardDataPasteable();
+ case "cmd_selectAll":
+ if (this._view.selType != "single") {
+ let rootNode = this._view.result.root;
+ if (rootNode.containerOpen && rootNode.childCount > 0)
+ return true;
+ }
+ return false;
+ case "placesCmd_open":
+ case "placesCmd_open:window":
+ case "placesCmd_open:privatewindow":
+ case "placesCmd_open:tab":
+ var selectedNode = this._view.selectedNode;
+ return selectedNode && PlacesUtils.nodeIsURI(selectedNode);
+ case "placesCmd_new:folder":
+ return this._canInsert();
+ case "placesCmd_new:bookmark":
+ return this._canInsert();
+ case "placesCmd_new:separator":
+ return this._canInsert() &&
+ !PlacesUtils.asQuery(this._view.result.root).queryOptions.excludeItems &&
+ this._view.result.sortingMode ==
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ case "placesCmd_show:info": {
+ let selectedNode = this._view.selectedNode;
+ return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1
+ }
+ case "placesCmd_reload": {
+ // Livemark containers
+ let selectedNode = this._view.selectedNode;
+ return selectedNode && this.hasCachedLivemarkInfo(selectedNode);
+ }
+ case "placesCmd_sortBy:name": {
+ let selectedNode = this._view.selectedNode;
+ return selectedNode &&
+ PlacesUtils.nodeIsFolder(selectedNode) &&
+ !PlacesUIUtils.isContentsReadOnly(selectedNode) &&
+ this._view.result.sortingMode ==
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ }
+ case "placesCmd_createBookmark":
+ var node = this._view.selectedNode;
+ return node && PlacesUtils.nodeIsURI(node) && node.itemId == -1;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function PC_doCommand(aCommand) {
+ switch (aCommand) {
+ case "cmd_undo":
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ PlacesUtils.transactionManager.undoTransaction();
+ return;
+ }
+ PlacesTransactions.undo().then(null, Components.utils.reportError);
+ break;
+ case "cmd_redo":
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ PlacesUtils.transactionManager.redoTransaction();
+ return;
+ }
+ PlacesTransactions.redo().then(null, Components.utils.reportError);
+ break;
+ case "cmd_cut":
+ case "placesCmd_cut":
+ this.cut();
+ break;
+ case "cmd_copy":
+ case "placesCmd_copy":
+ this.copy();
+ break;
+ case "cmd_paste":
+ case "placesCmd_paste":
+ this.paste().then(null, Components.utils.reportError);
+ break;
+ case "cmd_delete":
+ case "placesCmd_delete":
+ this.remove("Remove Selection").then(null, Components.utils.reportError);
+ break;
+ case "placesCmd_deleteDataHost":
+ var host;
+ if (PlacesUtils.nodeIsHost(this._view.selectedNode)) {
+ var queries = this._view.selectedNode.getQueries();
+ host = queries[0].domain;
+ }
+ else
+ host = NetUtil.newURI(this._view.selectedNode.uri).host;
+ ForgetAboutSite.removeDataFromDomain(host);
+ break;
+ case "cmd_selectAll":
+ this.selectAll();
+ break;
+ case "placesCmd_open":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "current", this._view);
+ break;
+ case "placesCmd_open:window":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "window", this._view);
+ break;
+ case "placesCmd_open:privatewindow":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "window", this._view, true);
+ break;
+ case "placesCmd_open:tab":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab", this._view);
+ break;
+ case "placesCmd_new:folder":
+ this.newItem("folder");
+ break;
+ case "placesCmd_new:bookmark":
+ this.newItem("bookmark");
+ break;
+ case "placesCmd_new:separator":
+ this.newSeparator().catch(Components.utils.reportError);
+ break;
+ case "placesCmd_show:info":
+ this.showBookmarkPropertiesForSelection();
+ break;
+ case "placesCmd_moveBookmarks":
+ this.moveSelectedBookmarks();
+ break;
+ case "placesCmd_reload":
+ this.reloadSelectedLivemark();
+ break;
+ case "placesCmd_sortBy:name":
+ this.sortFolderByName().then(null, Components.utils.reportError);
+ break;
+ case "placesCmd_createBookmark":
+ let node = this._view.selectedNode;
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , hiddenRows: [ "description"
+ , "keyword"
+ , "location"
+ , "loadInSidebar" ]
+ , uri: NetUtil.newURI(node.uri)
+ , title: node.title
+ }, window.top);
+ break;
+ }
+ },
+
+ onEvent: function PC_onEvent(eventName) { },
+
+
+ /**
+ * Determine whether or not the selection can be removed, either by the
+ * delete or cut operations based on whether or not any of its contents
+ * are non-removable. We don't need to worry about recursion here since it
+ * is a policy decision that a removable item not be placed inside a non-
+ * removable item.
+ *
+ * @return true if all nodes in the selection can be removed,
+ * false otherwise.
+ */
+ _hasRemovableSelection() {
+ var ranges = this._view.removableSelectionRanges;
+ if (!ranges.length)
+ return false;
+
+ var root = this._view.result.root;
+
+ for (var j = 0; j < ranges.length; j++) {
+ var nodes = ranges[j];
+ for (var i = 0; i < nodes.length; ++i) {
+ // Disallow removing the view's root node
+ if (nodes[i] == root)
+ return false;
+
+ if (!PlacesUIUtils.canUserRemove(nodes[i]))
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Determines whether or not nodes can be inserted relative to the selection.
+ */
+ _canInsert: function PC__canInsert(isPaste) {
+ var ip = this._view.insertionPoint;
+ return ip != null && (isPaste || ip.isTag != true);
+ },
+
+ /**
+ * Looks at the data on the clipboard to see if it is paste-able.
+ * Paste-able data is:
+ * - in a format that the view can receive
+ * @return true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor,
+ * - clipboard data is of type TEXT_UNICODE and
+ * is a valid URI.
+ */
+ _isClipboardDataPasteable: function PC__isClipboardDataPasteable() {
+ // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
+ // pasteable, with no need to unwrap all the nodes.
+
+ var flavors = PlacesUIUtils.PLACES_FLAVORS;
+ var clipboard = this.clipboard;
+ var hasPlacesData =
+ clipboard.hasDataMatchingFlavors(flavors, flavors.length,
+ Ci.nsIClipboard.kGlobalClipboard);
+ if (hasPlacesData)
+ return this._view.insertionPoint != null;
+
+ // if the clipboard doesn't have TYPE_X_MOZ_PLACE_* data, we also allow
+ // pasting of valid "text/unicode" and "text/x-moz-url" data
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+
+ xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_URL);
+ xferable.addDataFlavor(PlacesUtils.TYPE_UNICODE);
+ clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
+
+ try {
+ // getAnyTransferData will throw if no data is available.
+ var data = { }, type = { };
+ xferable.getAnyTransferData(type, data, { });
+ data = data.value.QueryInterface(Ci.nsISupportsString).data;
+ if (type.value != PlacesUtils.TYPE_X_MOZ_URL &&
+ type.value != PlacesUtils.TYPE_UNICODE)
+ return false;
+
+ // unwrapNodes() will throw if the data blob is malformed.
+ PlacesUtils.unwrapNodes(data, type.value);
+ return this._view.insertionPoint != null;
+ }
+ catch (e) {
+ // getAnyTransferData or unwrapNodes failed
+ return false;
+ }
+ },
+
+ /**
+ * Gathers information about the selected nodes according to the following
+ * rules:
+ * "link" node is a URI
+ * "bookmark" node is a bookmark
+ * "livemarkChild" node is a child of a livemark
+ * "tagChild" node is a child of a tag
+ * "folder" node is a folder
+ * "query" node is a query
+ * "separator" node is a separator line
+ * "host" node is a host
+ *
+ * @return an array of objects corresponding the selected nodes. Each
+ * object has each of the properties above set if its corresponding
+ * node matches the rule. In addition, the annotations names for each
+ * node are set on its corresponding object as properties.
+ * Notes:
+ * 1) This can be slow, so don't call it anywhere performance critical!
+ */
+ _buildSelectionMetadata: function PC__buildSelectionMetadata() {
+ var metadata = [];
+ var nodes = this._view.selectedNodes;
+
+ for (var i = 0; i < nodes.length; i++) {
+ var nodeData = {};
+ var node = nodes[i];
+ var nodeType = node.type;
+ var uri = null;
+
+ // We don't use the nodeIs* methods here to avoid going through the type
+ // property way too often
+ switch (nodeType) {
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY:
+ nodeData["query"] = true;
+ if (node.parent) {
+ switch (PlacesUtils.asQuery(node.parent).queryOptions.resultType) {
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
+ nodeData["host"] = true;
+ break;
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
+ nodeData["day"] = true;
+ break;
+ }
+ }
+ break;
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT:
+ nodeData["folder"] = true;
+ break;
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR:
+ nodeData["separator"] = true;
+ break;
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI:
+ nodeData["link"] = true;
+ uri = NetUtil.newURI(node.uri);
+ if (PlacesUtils.nodeIsBookmark(node)) {
+ nodeData["bookmark"] = true;
+ var parentNode = node.parent;
+ if (parentNode) {
+ if (PlacesUtils.nodeIsTagQuery(parentNode))
+ nodeData["tagChild"] = true;
+ else if (this.hasCachedLivemarkInfo(parentNode))
+ nodeData["livemarkChild"] = true;
+ }
+ }
+ break;
+ }
+
+ // annotations
+ if (uri) {
+ let names = PlacesUtils.annotations.getPageAnnotationNames(uri);
+ for (let j = 0; j < names.length; ++j)
+ nodeData[names[j]] = true;
+ }
+
+ // For items also include the item-specific annotations
+ if (node.itemId != -1) {
+ let names = PlacesUtils.annotations
+ .getItemAnnotationNames(node.itemId);
+ for (let j = 0; j < names.length; ++j)
+ nodeData[names[j]] = true;
+ }
+ metadata.push(nodeData);
+ }
+
+ return metadata;
+ },
+
+ /**
+ * Determines if a context-menu item should be shown
+ * @param aMenuItem
+ * the context menu item
+ * @param aMetaData
+ * meta data about the selection
+ * @return true if the conditions (see buildContextMenu) are satisfied
+ * and the item can be displayed, false otherwise.
+ */
+ _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
+ var selectiontype = aMenuItem.getAttribute("selectiontype");
+ if (!selectiontype) {
+ selectiontype = "single|multiple";
+ }
+ var selectionTypes = selectiontype.split("|");
+ if (selectionTypes.includes("any")) {
+ return true;
+ }
+ var count = aMetaData.length;
+ if (count > 1 && !selectionTypes.includes("multiple"))
+ return false;
+ if (count == 1 && !selectionTypes.includes("single"))
+ return false;
+ // NB: if there is no selection, we show the item if and only if
+ // the selectiontype includes 'none' - the metadata list will be
+ // empty so none of the other criteria will apply anyway.
+ if (count == 0)
+ return selectionTypes.includes("none");
+
+ var forceHideAttr = aMenuItem.getAttribute("forcehideselection");
+ if (forceHideAttr) {
+ var forceHideRules = forceHideAttr.split("|");
+ for (let i = 0; i < aMetaData.length; ++i) {
+ for (let j = 0; j < forceHideRules.length; ++j) {
+ if (forceHideRules[j] in aMetaData[i])
+ return false;
+ }
+ }
+ }
+
+ var selectionAttr = aMenuItem.getAttribute("selection");
+ if (!selectionAttr) {
+ return !aMenuItem.hidden;
+ }
+
+ if (selectionAttr == "any")
+ return true;
+
+ var showRules = selectionAttr.split("|");
+ var anyMatched = false;
+ function metaDataNodeMatches(metaDataNode, rules) {
+ for (var i = 0; i < rules.length; i++) {
+ if (rules[i] in metaDataNode)
+ return true;
+ }
+ return false;
+ }
+
+ for (var i = 0; i < aMetaData.length; ++i) {
+ if (metaDataNodeMatches(aMetaData[i], showRules))
+ anyMatched = true;
+ else
+ return false;
+ }
+ return anyMatched;
+ },
+
+ /**
+ * Detects information (meta-data rules) about the current selection in the
+ * view (see _buildSelectionMetadata) and sets the visibility state for each
+ * of the menu-items in the given popup with the following rules applied:
+ * 0) The "ignoreitem" attribute may be set to "true" for this code not to
+ * handle that menuitem.
+ * 1) The "selectiontype" attribute may be set on a menu-item to "single"
+ * if the menu-item should be visible only if there is a single node
+ * selected, or to "multiple" if the menu-item should be visible only if
+ * multiple nodes are selected, or to "none" if the menuitems should be
+ * visible for if there are no selected nodes, or to a |-separated
+ * combination of these.
+ * If the attribute is not set or set to an invalid value, the menu-item
+ * may be visible irrespective of the selection.
+ * 2) The "selection" attribute may be set on a menu-item to the various
+ * meta-data rules for which it may be visible. The rules should be
+ * separated with the | character.
+ * 3) A menu-item may be visible only if at least one of the rules set in
+ * its selection attribute apply to each of the selected nodes in the
+ * view.
+ * 4) The "forcehideselection" attribute may be set on a menu-item to rules
+ * for which it should be hidden. This attribute takes priority over the
+ * selection attribute. A menu-item would be hidden if at least one of the
+ * given rules apply to one of the selected nodes. The rules should be
+ * separated with the | character.
+ * 5) The "hideifnoinsertionpoint" attribute may be set on a menu-item to
+ * true if it should be hidden when there's no insertion point
+ * 6) The visibility state of a menu-item is unchanged if none of these
+ * attribute are set.
+ * 7) These attributes should not be set on separators for which the
+ * visibility state is "auto-detected."
+ * 8) The "hideifprivatebrowsing" attribute may be set on a menu-item to
+ * true if it should be hidden inside the private browsing mode
+ * @param aPopup
+ * The menupopup to build children into.
+ * @return true if at least one item is visible, false otherwise.
+ */
+ buildContextMenu: function PC_buildContextMenu(aPopup) {
+ var metadata = this._buildSelectionMetadata();
+ var ip = this._view.insertionPoint;
+ var noIp = !ip || ip.isTag;
+
+ var separator = null;
+ var visibleItemsBeforeSep = false;
+ var usableItemCount = 0;
+ for (var i = 0; i < aPopup.childNodes.length; ++i) {
+ var item = aPopup.childNodes[i];
+ if (item.getAttribute("ignoreitem") == "true") {
+ continue;
+ }
+ if (item.localName != "menuseparator") {
+ // We allow pasting into tag containers, so special case that.
+ var hideIfNoIP = item.getAttribute("hideifnoinsertionpoint") == "true" &&
+ noIp && !(ip && ip.isTag && item.id == "placesContext_paste");
+ var hideIfPrivate = item.getAttribute("hideifprivatebrowsing") == "true" &&
+ PrivateBrowsingUtils.isWindowPrivate(window);
+ var shouldHideItem = hideIfNoIP || hideIfPrivate ||
+ !this._shouldShowMenuItem(item, metadata);
+ item.hidden = item.disabled = shouldHideItem;
+
+ if (!item.hidden) {
+ visibleItemsBeforeSep = true;
+ usableItemCount++;
+
+ // Show the separator above the menu-item if any
+ if (separator) {
+ separator.hidden = false;
+ separator = null;
+ }
+ }
+ }
+ else { // menuseparator
+ // Initially hide it. It will be unhidden if there will be at least one
+ // visible menu-item above and below it.
+ item.hidden = true;
+
+ // We won't show the separator at all if no items are visible above it
+ if (visibleItemsBeforeSep)
+ separator = item;
+
+ // New separator, count again:
+ visibleItemsBeforeSep = false;
+ }
+ }
+
+ // Set Open Folder/Links In Tabs items enabled state if they're visible
+ if (usableItemCount > 0) {
+ var openContainerInTabsItem = document.getElementById("placesContext_openContainer:tabs");
+ if (!openContainerInTabsItem.hidden) {
+ var containerToUse = this._view.selectedNode || this._view.result.root;
+ if (PlacesUtils.nodeIsContainer(containerToUse)) {
+ if (!PlacesUtils.hasChildURIs(containerToUse)) {
+ openContainerInTabsItem.disabled = true;
+ // Ensure that we don't display the menu if nothing is enabled:
+ usableItemCount--;
+ }
+ }
+ }
+ }
+
+ return usableItemCount > 0;
+ },
+
+ /**
+ * Select all links in the current view.
+ */
+ selectAll: function PC_selectAll() {
+ this._view.selectAll();
+ },
+
+ /**
+ * Opens the bookmark properties for the selected URI Node.
+ */
+ showBookmarkPropertiesForSelection() {
+ let node = this._view.selectedNode;
+ if (!node)
+ return;
+
+ PlacesUIUtils.showBookmarkDialog({ action: "edit"
+ , node
+ , hiddenRows: [ "folderPicker" ]
+ }, window.top);
+ },
+
+ /**
+ * This method can be run on a URI parameter to ensure that it didn't
+ * receive a string instead of an nsIURI object.
+ */
+ _assertURINotString: function PC__assertURINotString(value) {
+ NS_ASSERT((typeof(value) == "object") && !(value instanceof String),
+ "This method should be passed a URI as a nsIURI object, not as a string.");
+ },
+
+ /**
+ * Reloads the selected livemark if any.
+ */
+ reloadSelectedLivemark: function PC_reloadSelectedLivemark() {
+ var selectedNode = this._view.selectedNode;
+ if (selectedNode) {
+ let itemId = selectedNode.itemId;
+ PlacesUtils.livemarks.getLivemark({ id: itemId })
+ .then(aLivemark => {
+ aLivemark.reload(true);
+ }, Components.utils.reportError);
+ }
+ },
+
+ /**
+ * Opens the links in the selected folder, or the selected links in new tabs.
+ */
+ openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
+ var node = this._view.selectedNode;
+ var nodes = this._view.selectedNodes;
+ // In the case of no selection, open the root node:
+ if (!node && !nodes.length) {
+ node = this._view.result.root;
+ }
+ if (node && PlacesUtils.nodeIsContainer(node))
+ PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this._view);
+ else
+ PlacesUIUtils.openURINodesInTabs(nodes, aEvent, this._view);
+ },
+
+ /**
+ * Shows the Add Bookmark UI for the current insertion point.
+ *
+ * @param aType
+ * the type of the new item (bookmark/livemark/folder)
+ */
+ newItem: function PC_newItem(aType) {
+ let ip = this._view.insertionPoint;
+ if (!ip)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ let performed =
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: aType
+ , defaultInsertionPoint: ip
+ , hiddenRows: [ "folderPicker" ]
+ }, window.top);
+ if (performed) {
+ // Select the new item.
+ let insertedNodeId = PlacesUtils.bookmarks
+ .getIdForItemAt(ip.itemId, ip.index);
+ this._view.selectItems([insertedNodeId], false);
+ }
+ },
+
+ /**
+ * Create a new Bookmark separator somewhere.
+ */
+ newSeparator: Task.async(function* () {
+ var ip = this._view.insertionPoint;
+ if (!ip)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txn = new PlacesCreateSeparatorTransaction(ip.itemId, ip.index);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ // Select the new item.
+ let insertedNodeId = PlacesUtils.bookmarks
+ .getIdForItemAt(ip.itemId, ip.index);
+ this._view.selectItems([insertedNodeId], false);
+ return;
+ }
+
+ let txn = PlacesTransactions.NewSeparator({ parentGuid: yield ip.promiseGuid()
+ , index: ip.index });
+ let guid = yield txn.transact();
+ let itemId = yield PlacesUtils.promiseItemId(guid);
+ // Select the new item.
+ this._view.selectItems([itemId], false);
+ }),
+
+ /**
+ * Opens a dialog for moving the selected nodes.
+ */
+ moveSelectedBookmarks: function PC_moveBookmarks() {
+ window.openDialog("chrome://browser/content/places/moveBookmarks.xul",
+ "", "chrome, modal",
+ this._view.selectedNodes);
+ },
+
+ /**
+ * Sort the selected folder by name
+ */
+ sortFolderByName: Task.async(function* () {
+ let itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ var txn = new PlacesSortFolderByNameTransaction(itemId);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ return;
+ }
+ let guid = yield PlacesUtils.promiseItemGuid(itemId);
+ yield PlacesTransactions.SortByName(guid).transact();
+ }),
+
+ /**
+ * Walk the list of folders we're removing in this delete operation, and
+ * see if the selected node specified is already implicitly being removed
+ * because it is a child of that folder.
+ * @param node
+ * Node to check for containment.
+ * @param pastFolders
+ * List of folders the calling function has already traversed
+ * @return true if the node should be skipped, false otherwise.
+ */
+ _shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
+ /**
+ * Determines if a node is contained by another node within a resultset.
+ * @param node
+ * The node to check for containment for
+ * @param parent
+ * The parent container to check for containment in
+ * @return true if node is a member of parent's children, false otherwise.
+ */
+ function isContainedBy(node, parent) {
+ var cursor = node.parent;
+ while (cursor) {
+ if (cursor == parent)
+ return true;
+ cursor = cursor.parent;
+ }
+ return false;
+ }
+
+ for (var j = 0; j < pastFolders.length; ++j) {
+ if (isContainedBy(node, pastFolders[j]))
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Creates a set of transactions for the removal of a range of items.
+ * A range is an array of adjacent nodes in a view.
+ * @param [in] range
+ * An array of nodes to remove. Should all be adjacent.
+ * @param [out] transactions
+ * An array of transactions.
+ * @param [optional] removedFolders
+ * An array of folder nodes that have already been removed.
+ */
+ _removeRange: function PC__removeRange(range, transactions, removedFolders) {
+ NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
+ if (!removedFolders)
+ removedFolders = [];
+
+ for (var i = 0; i < range.length; ++i) {
+ var node = range[i];
+ if (this._shouldSkipNode(node, removedFolders))
+ continue;
+
+ if (PlacesUtils.nodeIsTagQuery(node.parent)) {
+ // This is a uri node inside a tag container. It needs a special
+ // untag transaction.
+ var tagItemId = PlacesUtils.getConcreteItemId(node.parent);
+ var uri = NetUtil.newURI(node.uri);
+ if (PlacesUIUtils.useAsyncTransactions) {
+ let tag = node.parent.title;
+ if (!tag)
+ tag = PlacesUtils.bookmarks.getItemTitle(tagItemId);
+ transactions.push(PlacesTransactions.Untag({ uri: uri, tag: tag }));
+ }
+ else {
+ let txn = new PlacesUntagURITransaction(uri, [tagItemId]);
+ transactions.push(txn);
+ }
+ }
+ else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
+ PlacesUtils.nodeIsQuery(node.parent) &&
+ PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
+ // This is a tag container.
+ // Untag all URIs tagged with this tag only if the tag container is
+ // child of the "Tags" query in the library, in all other places we
+ // must only remove the query node.
+ let tag = node.title;
+ let URIs = PlacesUtils.tagging.getURIsForTag(tag);
+ if (PlacesUIUtils.useAsyncTransactions) {
+ transactions.push(PlacesTransactions.Untag({ tag: tag, uris: URIs }));
+ }
+ else {
+ for (var j = 0; j < URIs.length; j++) {
+ let txn = new PlacesUntagURITransaction(URIs[j], [tag]);
+ transactions.push(txn);
+ }
+ }
+ }
+ else if (PlacesUtils.nodeIsURI(node) &&
+ PlacesUtils.nodeIsQuery(node.parent) &&
+ PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ // This is a uri node inside an history query.
+ PlacesUtils.bhistory.removePage(NetUtil.newURI(node.uri));
+ // History deletes are not undoable, so we don't have a transaction.
+ }
+ else if (node.itemId == -1 &&
+ PlacesUtils.nodeIsQuery(node) &&
+ PlacesUtils.asQuery(node).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ // This is a dynamically generated history query, like queries
+ // grouped by site, time or both. Dynamically generated queries don't
+ // have an itemId even if they are descendants of a bookmark.
+ this._removeHistoryContainer(node);
+ // History deletes are not undoable, so we don't have a transaction.
+ }
+ else {
+ // This is a common bookmark item.
+ if (PlacesUtils.nodeIsFolder(node)) {
+ // If this is a folder we add it to our array of folders, used
+ // to skip nodes that are children of an already removed folder.
+ removedFolders.push(node);
+ }
+ if (PlacesUIUtils.useAsyncTransactions) {
+ transactions.push(
+ PlacesTransactions.Remove({ guid: node.bookmarkGuid }));
+ }
+ else {
+ let txn = new PlacesRemoveItemTransaction(node.itemId);
+ transactions.push(txn);
+ }
+ }
+ }
+ },
+
+ /**
+ * Removes the set of selected ranges from bookmarks.
+ * @param txnName
+ * See |remove|.
+ */
+ _removeRowsFromBookmarks: Task.async(function* (txnName) {
+ var ranges = this._view.removableSelectionRanges;
+ var transactions = [];
+ var removedFolders = [];
+
+ for (var i = 0; i < ranges.length; i++)
+ this._removeRange(ranges[i], transactions, removedFolders);
+
+ if (transactions.length > 0) {
+ if (PlacesUIUtils.useAsyncTransactions) {
+ yield PlacesTransactions.batch(transactions);
+ }
+ else {
+ var txn = new PlacesAggregatedTransaction(txnName, transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ }
+ }),
+
+ /**
+ * Removes the set of selected ranges from history.
+ *
+ * @note history deletes are not undoable.
+ */
+ _removeRowsFromHistory: function PC__removeRowsFromHistory() {
+ let nodes = this._view.selectedNodes;
+ let URIs = [];
+ for (let i = 0; i < nodes.length; ++i) {
+ let node = nodes[i];
+ if (PlacesUtils.nodeIsURI(node)) {
+ let uri = NetUtil.newURI(node.uri);
+ // Avoid duplicates.
+ if (URIs.indexOf(uri) < 0) {
+ URIs.push(uri);
+ }
+ }
+ else if (PlacesUtils.nodeIsQuery(node) &&
+ PlacesUtils.asQuery(node).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ this._removeHistoryContainer(node);
+ }
+ }
+
+ // Do removal in chunks to give some breath to main-thread.
+ function* pagesChunkGenerator(aURIs) {
+ while (aURIs.length) {
+ let URIslice = aURIs.splice(0, REMOVE_PAGES_CHUNKLEN);
+ PlacesUtils.bhistory.removePages(URIslice, URIslice.length);
+ Services.tm.mainThread.dispatch(() => gen.next(),
+ Ci.nsIThread.DISPATCH_NORMAL);
+ yield undefined;
+ }
+ }
+ let gen = pagesChunkGenerator(URIs);
+ gen.next();
+ },
+
+ /**
+ * Removes history visits for an history container node.
+ * @param [in] aContainerNode
+ * The container node to remove.
+ *
+ * @note history deletes are not undoable.
+ */
+ _removeHistoryContainer: function PC__removeHistoryContainer(aContainerNode) {
+ if (PlacesUtils.nodeIsHost(aContainerNode)) {
+ // Site container.
+ PlacesUtils.bhistory.removePagesFromHost(aContainerNode.title, true);
+ }
+ else if (PlacesUtils.nodeIsDay(aContainerNode)) {
+ // Day container.
+ let query = aContainerNode.getQueries()[0];
+ let beginTime = query.beginTime;
+ let endTime = query.endTime;
+ NS_ASSERT(query && beginTime && endTime,
+ "A valid date container query should exist!");
+ // We want to exclude beginTime from the removal because
+ // removePagesByTimeframe includes both extremes, while date containers
+ // exclude the lower extreme. So, if we would not exclude it, we would
+ // end up removing more history than requested.
+ PlacesUtils.bhistory.removePagesByTimeframe(beginTime + 1, endTime);
+ }
+ },
+
+ /**
+ * Removes the selection
+ * @param aTxnName
+ * A name for the transaction if this is being performed
+ * as part of another operation.
+ */
+ remove: Task.async(function* (aTxnName) {
+ if (!this._hasRemovableSelection())
+ return;
+
+ NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name");
+
+ var root = this._view.result.root;
+
+ if (PlacesUtils.nodeIsFolder(root)) {
+ if (PlacesUIUtils.useAsyncTransactions)
+ yield this._removeRowsFromBookmarks(aTxnName);
+ else
+ this._removeRowsFromBookmarks(aTxnName);
+ }
+ else if (PlacesUtils.nodeIsQuery(root)) {
+ var queryType = PlacesUtils.asQuery(root).queryOptions.queryType;
+ if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS) {
+ if (PlacesUIUtils.useAsyncTransactions)
+ yield this._removeRowsFromBookmarks(aTxnName);
+ else
+ this._removeRowsFromBookmarks(aTxnName);
+ }
+ else if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ this._removeRowsFromHistory();
+ }
+ else {
+ NS_ASSERT(false, "implement support for QUERY_TYPE_UNIFIED");
+ }
+ }
+ else
+ NS_ASSERT(false, "unexpected root");
+ }),
+
+ /**
+ * Fills a DataTransfer object with the content of the selection that can be
+ * dropped elsewhere.
+ * @param aEvent
+ * The dragstart event.
+ */
+ setDataTransfer: function PC_setDataTransfer(aEvent) {
+ let dt = aEvent.dataTransfer;
+
+ let result = this._view.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+
+ function addData(type, index, feedURI) {
+ let wrapNode = PlacesUtils.wrapNode(node, type, feedURI);
+ dt.mozSetDataAt(type, wrapNode, index);
+ }
+
+ function addURIData(index, feedURI) {
+ addData(PlacesUtils.TYPE_X_MOZ_URL, index, feedURI);
+ addData(PlacesUtils.TYPE_UNICODE, index, feedURI);
+ addData(PlacesUtils.TYPE_HTML, index, feedURI);
+ }
+
+ try {
+ let nodes = this._view.draggableSelection;
+ for (let i = 0; i < nodes.length; ++i) {
+ var node = nodes[i];
+
+ // This order is _important_! It controls how this and other
+ // applications select data to be inserted based on type.
+ addData(PlacesUtils.TYPE_X_MOZ_PLACE, i);
+
+ // Drop the feed uri for livemark containers
+ let livemarkInfo = this.getCachedLivemarkInfo(node);
+ if (livemarkInfo) {
+ addURIData(i, livemarkInfo.feedURI.spec);
+ }
+ else if (node.uri) {
+ addURIData(i);
+ }
+ }
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ },
+
+ get clipboardAction () {
+ let action = {};
+ let actionOwner;
+ try {
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION)
+ this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
+ xferable.getTransferData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, action, {});
+ [action, actionOwner] =
+ action.value.QueryInterface(Ci.nsISupportsString).data.split(",");
+ } catch (ex) {
+ // Paste from external sources don't have any associated action, just
+ // fallback to a copy action.
+ return "copy";
+ }
+ // For cuts also check who inited the action, since cuts across different
+ // instances should instead be handled as copies (The sources are not
+ // available for this instance).
+ if (action == "cut" && actionOwner != this.profileName)
+ action = "copy";
+
+ return action;
+ },
+
+ _releaseClipboardOwnership: function PC__releaseClipboardOwnership() {
+ if (this.cutNodes.length > 0) {
+ // This clears the logical clipboard, doesn't remove data.
+ this.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
+ }
+ },
+
+ _clearClipboard: function PC__clearClipboard() {
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ // Empty transferables may cause crashes, so just add an unknown type.
+ const TYPE = "text/x-moz-place-empty";
+ xferable.addDataFlavor(TYPE);
+ xferable.setTransferData(TYPE, PlacesUtils.toISupportsString(""), 0);
+ this.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
+ },
+
+ _populateClipboard: function PC__populateClipboard(aNodes, aAction) {
+ // This order is _important_! It controls how this and other applications
+ // select data to be inserted based on type.
+ let contents = [
+ { type: PlacesUtils.TYPE_X_MOZ_PLACE, entries: [] },
+ { type: PlacesUtils.TYPE_X_MOZ_URL, entries: [] },
+ { type: PlacesUtils.TYPE_HTML, entries: [] },
+ { type: PlacesUtils.TYPE_UNICODE, entries: [] },
+ ];
+
+ // Avoid handling descendants of a copied node, the transactions take care
+ // of them automatically.
+ let copiedFolders = [];
+ aNodes.forEach(function (node) {
+ if (this._shouldSkipNode(node, copiedFolders))
+ return;
+ if (PlacesUtils.nodeIsFolder(node))
+ copiedFolders.push(node);
+
+ let livemarkInfo = this.getCachedLivemarkInfo(node);
+ let feedURI = livemarkInfo && livemarkInfo.feedURI.spec;
+
+ contents.forEach(function (content) {
+ content.entries.push(
+ PlacesUtils.wrapNode(node, content.type, feedURI)
+ );
+ });
+ }, this);
+
+ function addData(type, data) {
+ xferable.addDataFlavor(type);
+ xferable.setTransferData(type, PlacesUtils.toISupportsString(data),
+ data.length * 2);
+ }
+
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ let hasData = false;
+ // This order matters here! It controls how this and other applications
+ // select data to be inserted based on type.
+ contents.forEach(function (content) {
+ if (content.entries.length > 0) {
+ hasData = true;
+ let glue =
+ content.type == PlacesUtils.TYPE_X_MOZ_PLACE ? "," : PlacesUtils.endl;
+ addData(content.type, content.entries.join(glue));
+ }
+ });
+
+ // Track the exected action in the xferable. This must be the last flavor
+ // since it's the least preferred one.
+ // Enqueue a unique instance identifier to distinguish operations across
+ // concurrent instances of the application.
+ addData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, aAction + "," + this.profileName);
+
+ if (hasData) {
+ this.clipboard.setData(xferable,
+ this.cutNodes.length > 0 ? this : null,
+ Ci.nsIClipboard.kGlobalClipboard);
+ }
+ },
+
+ _cutNodes: [],
+ get cutNodes() {
+ return this._cutNodes;
+ },
+ set cutNodes(aNodes) {
+ let self = this;
+ function updateCutNodes(aValue) {
+ self._cutNodes.forEach(function (aNode) {
+ self._view.toggleCutNode(aNode, aValue);
+ });
+ }
+
+ updateCutNodes(false);
+ this._cutNodes = aNodes;
+ updateCutNodes(true);
+ return aNodes;
+ },
+
+ /**
+ * Copy Bookmarks and Folders to the clipboard
+ */
+ copy: function PC_copy() {
+ let result = this._view.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+ try {
+ this._populateClipboard(this._view.selectedNodes, "copy");
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ },
+
+ /**
+ * Cut Bookmarks and Folders to the clipboard
+ */
+ cut: function PC_cut() {
+ let result = this._view.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+ try {
+ this._populateClipboard(this._view.selectedNodes, "cut");
+ this.cutNodes = this._view.selectedNodes;
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ },
+
+ /**
+ * Paste Bookmarks and Folders from the clipboard
+ */
+ paste: Task.async(function* () {
+ // No reason to proceed if there isn't a valid insertion point.
+ let ip = this._view.insertionPoint;
+ if (!ip)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ let action = this.clipboardAction;
+
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ // This order matters here! It controls the preferred flavors for this
+ // paste operation.
+ [ PlacesUtils.TYPE_X_MOZ_PLACE,
+ PlacesUtils.TYPE_X_MOZ_URL,
+ PlacesUtils.TYPE_UNICODE,
+ ].forEach(type => xferable.addDataFlavor(type));
+
+ this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
+
+ // Now get the clipboard contents, in the best available flavor.
+ let data = {}, type = {}, items = [];
+ try {
+ xferable.getAnyTransferData(type, data, {});
+ data = data.value.QueryInterface(Ci.nsISupportsString).data;
+ type = type.value;
+ items = PlacesUtils.unwrapNodes(data, type);
+ } catch (ex) {
+ // No supported data exists or nodes unwrap failed, just bail out.
+ return;
+ }
+
+ let itemsToSelect = [];
+ if (PlacesUIUtils.useAsyncTransactions) {
+ if (ip.isTag) {
+ let uris = items.filter(item => "uri" in item).map(item => NetUtil.newURI(item.uri));
+ yield PlacesTransactions.Tag({ uris: uris, tag: ip.tagName }).transact();
+ }
+ else {
+ yield PlacesTransactions.batch(function* () {
+ let insertionIndex = ip.index;
+ let parent = yield ip.promiseGuid();
+
+ for (let item of items) {
+ let doCopy = action == "copy";
+
+ // If this is not a copy, check for safety that we can move the
+ // source, otherwise report an error and fallback to a copy.
+ if (!doCopy &&
+ !PlacesControllerDragHelper.canMoveUnwrappedNode(item)) {
+ Components.utils.reportError("Tried to move an unmovable " +
+ "Places node, reverting to a copy operation.");
+ doCopy = true;
+ }
+ let guid = yield PlacesUIUtils.getTransactionForData(
+ item, type, parent, insertionIndex, doCopy).transact();
+ itemsToSelect.push(yield PlacesUtils.promiseItemId(guid));
+
+ // Adjust index to make sure items are pasted in the correct
+ // position. If index is DEFAULT_INDEX, items are just appended.
+ if (insertionIndex != PlacesUtils.bookmarks.DEFAULT_INDEX)
+ insertionIndex++;
+ }
+ });
+ }
+ }
+ else {
+ let transactions = [];
+ let insertionIndex = ip.index;
+ for (let i = 0; i < items.length; ++i) {
+ if (ip.isTag) {
+ // Pasting into a tag container means tagging the item, regardless of
+ // the requested action.
+ let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
+ [ip.itemId]);
+ transactions.push(tagTxn);
+ continue;
+ }
+
+ // Adjust index to make sure items are pasted in the correct position.
+ // If index is DEFAULT_INDEX, items are just appended.
+ if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
+ insertionIndex = ip.index + i;
+
+ // If this is not a copy, check for safety that we can move the source,
+ // otherwise report an error and fallback to a copy.
+ if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) {
+ Components.utils.reportError("Tried to move an unmovable Places " +
+ "node, reverting to a copy operation.");
+ action = "copy";
+ }
+ transactions.push(
+ PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
+ insertionIndex, action == "copy")
+ );
+ }
+
+ let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
+ PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
+
+ for (let i = 0; i < transactions.length; ++i) {
+ itemsToSelect.push(
+ PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i)
+ );
+ }
+ }
+
+ // Cut/past operations are not repeatable, so clear the clipboard.
+ if (action == "cut") {
+ this._clearClipboard();
+ }
+
+ if (itemsToSelect.length > 0)
+ this._view.selectItems(itemsToSelect, false);
+ }),
+
+ /**
+ * Cache the livemark info for a node. This allows the controller and the
+ * views to treat the given node as a livemark.
+ * @param aNode
+ * a places result node.
+ * @param aLivemarkInfo
+ * a mozILivemarkInfo object.
+ */
+ cacheLivemarkInfo: function PC_cacheLivemarkInfo(aNode, aLivemarkInfo) {
+ this._cachedLivemarkInfoObjects.set(aNode, aLivemarkInfo);
+ },
+
+ /**
+ * Returns whether or not there's cached mozILivemarkInfo object for a node.
+ * @param aNode
+ * a places result node.
+ * @return true if there's a cached mozILivemarkInfo object for
+ * aNode, false otherwise.
+ */
+ hasCachedLivemarkInfo: function PC_hasCachedLivemarkInfo(aNode) {
+ return this._cachedLivemarkInfoObjects.has(aNode);
+ },
+
+ /**
+ * Returns the cached livemark info for a node, if set by cacheLivemarkInfo,
+ * null otherwise.
+ * @param aNode
+ * a places result node.
+ * @return the mozILivemarkInfo object for aNode, if set, null otherwise.
+ */
+ getCachedLivemarkInfo: function PC_getCachedLivemarkInfo(aNode) {
+ return this._cachedLivemarkInfoObjects.get(aNode, null);
+ }
+};
+
+/**
+ * Handles drag and drop operations for views. Note that this is view agnostic!
+ * You should not use PlacesController._view within these methods, since
+ * the view that the item(s) have been dropped on was not necessarily active.
+ * Drop functions are passed the view that is being dropped on.
+ */
+var PlacesControllerDragHelper = {
+ /**
+ * DOM Element currently being dragged over
+ */
+ currentDropTarget: null,
+
+ /**
+ * Determines if the mouse is currently being dragged over a child node of
+ * this menu. This is necessary so that the menu doesn't close while the
+ * mouse is dragging over one of its submenus
+ * @param node
+ * The container node
+ * @return true if the user is dragging over a node within the hierarchy of
+ * the container, false otherwise.
+ */
+ draggingOverChildNode: function PCDH_draggingOverChildNode(node) {
+ let currentNode = this.currentDropTarget;
+ while (currentNode) {
+ if (currentNode == node)
+ return true;
+ currentNode = currentNode.parentNode;
+ }
+ return false;
+ },
+
+ /**
+ * @return The current active drag session. Returns null if there is none.
+ */
+ getSession: function PCDH__getSession() {
+ return this.dragService.getCurrentSession();
+ },
+
+ /**
+ * Extract the first accepted flavor from a list of flavors.
+ * @param aFlavors
+ * The flavors list of type DOMStringList.
+ */
+ getFirstValidFlavor: function PCDH_getFirstValidFlavor(aFlavors) {
+ for (let i = 0; i < aFlavors.length; i++) {
+ if (PlacesUIUtils.SUPPORTED_FLAVORS.includes(aFlavors[i]))
+ return aFlavors[i];
+ }
+
+ // If no supported flavor is found, check if data includes text/plain
+ // contents. If so, request them as text/unicode, a conversion will happen
+ // automatically.
+ if (aFlavors.contains("text/plain")) {
+ return PlacesUtils.TYPE_UNICODE;
+ }
+
+ return null;
+ },
+
+ /**
+ * Determines whether or not the data currently being dragged can be dropped
+ * on a places view.
+ * @param ip
+ * The insertion point where the items should be dropped.
+ */
+ canDrop: function PCDH_canDrop(ip, dt) {
+ let dropCount = dt.mozItemCount;
+
+ // Check every dragged item.
+ for (let i = 0; i < dropCount; i++) {
+ let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
+ if (!flavor)
+ return false;
+
+ // Urls can be dropped on any insertionpoint.
+ // XXXmano: remember that this method is called for each dragover event!
+ // Thus we shouldn't use unwrapNodes here at all if possible.
+ // I think it would be OK to accept bogus data here (e.g. text which was
+ // somehow wrapped as TAB_DROP_TYPE, this is not in our control, and
+ // will just case the actual drop to be a no-op), and only rule out valid
+ // expected cases, which are either unsupported flavors, or items which
+ // cannot be dropped in the current insertionpoint. The last case will
+ // likely force us to use unwrapNodes for the private data types of
+ // places.
+ if (flavor == TAB_DROP_TYPE)
+ continue;
+
+ let data = dt.mozGetDataAt(flavor, i);
+ let dragged;
+ try {
+ dragged = PlacesUtils.unwrapNodes(data, flavor)[0];
+ }
+ catch (e) {
+ return false;
+ }
+
+ // Only bookmarks and urls can be dropped into tag containers.
+ if (ip.isTag &&
+ dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
+ (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
+ (dragged.uri && dragged.uri.startsWith("place:")) ))
+ return false;
+
+ // The following loop disallows the dropping of a folder on itself or
+ // on any of its descendants.
+ if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
+ (dragged.uri && dragged.uri.startsWith("place:")) ) {
+ let parentId = ip.itemId;
+ while (parentId != PlacesUtils.placesRootId) {
+ if (dragged.concreteId == parentId || dragged.id == parentId)
+ return false;
+ parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Determines if an unwrapped node can be moved.
+ *
+ * @param aUnwrappedNode
+ * A node unwrapped by PlacesUtils.unwrapNodes().
+ * @return True if the node can be moved, false otherwise.
+ */
+ canMoveUnwrappedNode: function (aUnwrappedNode) {
+ return aUnwrappedNode.id > 0 &&
+ !PlacesUtils.isRootItem(aUnwrappedNode.id) &&
+ (!aUnwrappedNode.parent || !PlacesUIUtils.isContentsReadOnly(aUnwrappedNode.parent)) &&
+ aUnwrappedNode.parent != PlacesUtils.tagsFolderId &&
+ aUnwrappedNode.grandParentId != PlacesUtils.tagsFolderId;
+ },
+
+ /**
+ * Determines if a node can be moved.
+ *
+ * @param aNode
+ * A nsINavHistoryResultNode node.
+ * @param [optional] aDOMNode
+ * A XUL DOM node.
+ * @return True if the node can be moved, false otherwise.
+ */
+ canMoveNode(aNode, aDOMNode) {
+ // Only bookmark items are movable.
+ if (aNode.itemId == -1)
+ return false;
+
+ let parentNode = aNode.parent;
+ if (!parentNode) {
+ // Normally parentless places nodes can not be moved,
+ // but simulated bookmarked URI nodes are special.
+ return !!aDOMNode &&
+ aDOMNode.hasAttribute("simulated-places-node") &&
+ PlacesUtils.nodeIsBookmark(aNode);
+ }
+
+ // Once tags and bookmarked are divorced, the tag-query check should be
+ // removed.
+ return !(PlacesUtils.nodeIsFolder(parentNode) &&
+ PlacesUIUtils.isContentsReadOnly(parentNode)) &&
+ !PlacesUtils.nodeIsTagQuery(parentNode);
+ },
+
+ /**
+ * Handles the drop of one or more items onto a view.
+ * @param insertionPoint
+ * The insertion point where the items should be dropped
+ */
+ onDrop: Task.async(function* (insertionPoint, dt) {
+ let doCopy = ["copy", "link"].includes(dt.dropEffect);
+
+ let transactions = [];
+ let dropCount = dt.mozItemCount;
+ let movedCount = 0;
+ let parentGuid = PlacesUIUtils.useAsyncTransactions ?
+ (yield insertionPoint.promiseGuid()) : null;
+ let tagName = insertionPoint.tagName;
+ for (let i = 0; i < dropCount; ++i) {
+ let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
+ if (!flavor)
+ return;
+
+ let data = dt.mozGetDataAt(flavor, i);
+ let unwrapped;
+ if (flavor != TAB_DROP_TYPE) {
+ // There's only ever one in the D&D case.
+ unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0];
+ }
+ else if (data instanceof XULElement && data.localName == "tab" &&
+ data.ownerGlobal instanceof ChromeWindow) {
+ let uri = data.linkedBrowser.currentURI;
+ let spec = uri ? uri.spec : "about:blank";
+ unwrapped = { uri: spec,
+ title: data.label,
+ type: PlacesUtils.TYPE_X_MOZ_URL};
+ }
+ else
+ throw new Error("bogus data was passed as a tab");
+
+ let index = insertionPoint.index;
+
+ // Adjust insertion index to prevent reversal of dragged items. When you
+ // drag multiple elts upward: need to increment index or each successive
+ // elt will be inserted at the same index, each above the previous.
+ let dragginUp = insertionPoint.itemId == unwrapped.parent &&
+ index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
+ if (index != -1 && dragginUp)
+ index+= movedCount++;
+
+ // If dragging over a tag container we should tag the item.
+ if (insertionPoint.isTag) {
+ let uri = NetUtil.newURI(unwrapped.uri);
+ let tagItemId = insertionPoint.itemId;
+ if (PlacesUIUtils.useAsyncTransactions)
+ transactions.push(PlacesTransactions.Tag({ uri: uri, tag: tagName }));
+ else
+ transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
+ }
+ else {
+ // If this is not a copy, check for safety that we can move the source,
+ // otherwise report an error and fallback to a copy.
+ if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
+ Components.utils.reportError("Tried to move an unmovable Places " +
+ "node, reverting to a copy operation.");
+ doCopy = true;
+ }
+ if (PlacesUIUtils.useAsyncTransactions) {
+ transactions.push(
+ PlacesUIUtils.getTransactionForData(unwrapped,
+ flavor,
+ parentGuid,
+ index,
+ doCopy));
+ }
+ else {
+ transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
+ flavor, insertionPoint.itemId,
+ index, doCopy));
+ }
+ }
+ }
+
+ if (PlacesUIUtils.useAsyncTransactions) {
+ yield PlacesTransactions.batch(transactions);
+ }
+ else {
+ let txn = new PlacesAggregatedTransaction("DropItems", transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ }),
+
+ /**
+ * Checks if we can insert into a container.
+ * @param aContainer
+ * The container were we are want to drop
+ */
+ disallowInsertion: function(aContainer) {
+ NS_ASSERT(aContainer, "empty container");
+ // Allow dropping into Tag containers and editable folders.
+ return !PlacesUtils.nodeIsTagQuery(aContainer) &&
+ (!PlacesUtils.nodeIsFolder(aContainer) ||
+ PlacesUIUtils.isContentsReadOnly(aContainer));
+ }
+};
+
+
+XPCOMUtils.defineLazyServiceGetter(PlacesControllerDragHelper, "dragService",
+ "@mozilla.org/widget/dragservice;1",
+ "nsIDragService");
+
+function goUpdatePlacesCommands() {
+ // Get the controller for one of the places commands.
+ var placesController = doGetPlacesControllerForCommand("placesCmd_open");
+ function updatePlacesCommand(aCommand) {
+ goSetCommandEnabled(aCommand, placesController &&
+ placesController.isCommandEnabled(aCommand));
+ }
+
+ updatePlacesCommand("placesCmd_open");
+ updatePlacesCommand("placesCmd_open:window");
+ updatePlacesCommand("placesCmd_open:privatewindow");
+ updatePlacesCommand("placesCmd_open:tab");
+ updatePlacesCommand("placesCmd_new:folder");
+ updatePlacesCommand("placesCmd_new:bookmark");
+ updatePlacesCommand("placesCmd_new:separator");
+ updatePlacesCommand("placesCmd_show:info");
+ updatePlacesCommand("placesCmd_moveBookmarks");
+ updatePlacesCommand("placesCmd_reload");
+ updatePlacesCommand("placesCmd_sortBy:name");
+ updatePlacesCommand("placesCmd_cut");
+ updatePlacesCommand("placesCmd_copy");
+ updatePlacesCommand("placesCmd_paste");
+ updatePlacesCommand("placesCmd_delete");
+}
+
+function doGetPlacesControllerForCommand(aCommand)
+{
+ // A context menu may be built for non-focusable views. Thus, we first try
+ // to look for a view associated with document.popupNode
+ let popupNode;
+ try {
+ popupNode = document.popupNode;
+ } catch (e) {
+ // The document went away (bug 797307).
+ return null;
+ }
+ if (popupNode) {
+ let view = PlacesUIUtils.getViewForNode(popupNode);
+ if (view && view._contextMenuShown)
+ return view.controllers.getControllerForCommand(aCommand);
+ }
+
+ // When we're not building a context menu, only focusable views
+ // are possible. Thus, we can safely use the command dispatcher.
+ let controller = top.document.commandDispatcher
+ .getControllerForCommand(aCommand);
+ if (controller)
+ return controller;
+
+ return null;
+}
+
+function goDoPlacesCommand(aCommand)
+{
+ let controller = doGetPlacesControllerForCommand(aCommand);
+ if (controller && controller.isCommandEnabled(aCommand))
+ controller.doCommand(aCommand);
+}
diff --git a/browser/components/places/content/downloadsViewOverlay.xul b/browser/components/places/content/downloadsViewOverlay.xul
new file mode 100644
index 000000000..5706632ba
--- /dev/null
+++ b/browser/components/places/content/downloadsViewOverlay.xul
@@ -0,0 +1,47 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
+]>
+
+<overlay id="downloadsViewOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"><![CDATA[
+ const DOWNLOADS_QUERY = "place:transition=" +
+ Components.interfaces.nsINavHistoryService.TRANSITION_DOWNLOAD +
+ "&sort=" +
+ Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
+
+ ContentArea.setContentViewForQueryString(DOWNLOADS_QUERY,
+ () => new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false),
+ { showDetailsPane: false,
+ toolbarSet: "back-button, forward-button, organizeButton, clearDownloadsButton, libraryToolbarSpacer, searchFilter" });
+ ]]></script>
+
+ <window id="places">
+ <commandset id="downloadCommands"/>
+ <menupopup id="downloadsContextMenu"/>
+ </window>
+
+ <deck id="placesViewsDeck">
+ <richlistbox id="downloadsRichListBox"/>
+ </deck>
+
+ <toolbar id="placesToolbar">
+ <toolbarbutton id="clearDownloadsButton"
+#ifdef XP_MACOSX
+ class="tabbable"
+#endif
+ insertbefore="libraryToolbarSpacer"
+ label="&clearDownloadsButton.label;"
+ command="downloadsCmd_clearDownloads"
+ tooltiptext="&clearDownloadsButton.tooltip;"/>
+ </toolbar>
+
+</overlay>
diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js
new file mode 100644
index 000000000..e26cfb138
--- /dev/null
+++ b/browser/components/places/content/editBookmarkOverlay.js
@@ -0,0 +1,1168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
+const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
+
+var gEditItemOverlay = {
+ _observersAdded: false,
+ _staticFoldersListBuilt: false,
+
+ _paneInfo: null,
+ _setPaneInfo(aInitInfo) {
+ if (!aInitInfo)
+ return this._paneInfo = null;
+
+ if ("uris" in aInitInfo && "node" in aInitInfo)
+ throw new Error("ambiguous pane info");
+ if (!("uris" in aInitInfo) && !("node" in aInitInfo))
+ throw new Error("Neither node nor uris set for pane info");
+
+ let node = "node" in aInitInfo ? aInitInfo.node : null;
+
+ // Since there's no true UI for folder shortcuts (they show up just as their target
+ // folders), when the pane shows for them it's opened in read-only mode, showing the
+ // properties of the target folder.
+ let itemId = node ? node.itemId : -1;
+ let itemGuid = PlacesUIUtils.useAsyncTransactions && node ?
+ PlacesUtils.getConcreteItemGuid(node) : null;
+ let isItem = itemId != -1;
+ let isFolderShortcut = isItem &&
+ node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
+ let isTag = node && PlacesUtils.nodeIsTagQuery(node);
+ if (isTag) {
+ itemId = PlacesUtils.getConcreteItemId(node);
+ // For now we don't have access to the item guid synchronously for tags,
+ // so we'll need to fetch it later.
+ }
+ let isURI = node && PlacesUtils.nodeIsURI(node);
+ let uri = isURI ? NetUtil.newURI(node.uri) : null;
+ let title = node ? node.title : null;
+ let isBookmark = isItem && isURI;
+ let bulkTagging = !node;
+ let uris = bulkTagging ? aInitInfo.uris : null;
+ let visibleRows = new Set();
+ let isParentReadOnly = false;
+ let postData = aInitInfo.postData;
+ if (node && "parent" in node) {
+ let parent = node.parent;
+ if (parent) {
+ isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) ||
+ PlacesUIUtils.isContentsReadOnly(parent);
+ }
+ }
+ let focusedElement = aInitInfo.focusedElement;
+
+ return this._paneInfo = { itemId, itemGuid, isItem,
+ isURI, uri, title,
+ isBookmark, isFolderShortcut, isParentReadOnly,
+ bulkTagging, uris,
+ visibleRows, postData, isTag, focusedElement };
+ },
+
+ get initialized() {
+ return this._paneInfo != null;
+ },
+
+ // Backwards-compatibility getters
+ get itemId() {
+ if (!this.initialized || this._paneInfo.bulkTagging)
+ return -1;
+ return this._paneInfo.itemId;
+ },
+
+ get uri() {
+ if (!this.initialized)
+ return null;
+ if (this._paneInfo.bulkTagging)
+ return this._paneInfo.uris[0];
+ return this._paneInfo.uri;
+ },
+
+ get multiEdit() {
+ return this.initialized && this._paneInfo.bulkTagging;
+ },
+
+ // Check if the pane is initialized to show only read-only fields.
+ get readOnly() {
+ // TODO (Bug 1120314): Folder shortcuts are currently read-only due to some
+ // quirky implementation details (the most important being the "smart"
+ // semantics of node.title that makes hard to edit the right entry).
+ // This pane is read-only if:
+ // * the panel is not initialized
+ // * the node is a folder shortcut
+ // * the node is not bookmarked and not a tag container
+ // * the node is child of a read-only container and is not a bookmarked
+ // URI nor a tag container
+ return !this.initialized ||
+ this._paneInfo.isFolderShortcut ||
+ (!this._paneInfo.isItem && !this._paneInfo.isTag) ||
+ (this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark && !this._paneInfo.isTag);
+ },
+
+ // the first field which was edited after this panel was initialized for
+ // a certain item
+ _firstEditedField: "",
+
+ _initNamePicker() {
+ if (this._paneInfo.bulkTagging)
+ throw new Error("_initNamePicker called unexpectedly");
+
+ // title may by null, which, for us, is the same as an empty string.
+ this._initTextField(this._namePicker, this._paneInfo.title || "");
+ },
+
+ _initLocationField() {
+ if (!this._paneInfo.isURI)
+ throw new Error("_initLocationField called unexpectedly");
+ this._initTextField(this._locationField, this._paneInfo.uri.spec);
+ },
+
+ _initDescriptionField() {
+ if (!this._paneInfo.isItem)
+ throw new Error("_initDescriptionField called unexpectedly");
+
+ this._initTextField(this._descriptionField,
+ PlacesUIUtils.getItemDescription(this._paneInfo.itemId));
+ },
+
+ _initKeywordField: Task.async(function* (newKeyword = "") {
+ if (!this._paneInfo.isBookmark) {
+ throw new Error("_initKeywordField called unexpectedly");
+ }
+
+ // Reset the field status synchronously now, eventually we'll reinit it
+ // later if we find an existing keyword. This way we can ensure to be in a
+ // consistent status when reusing the panel across different bookmarks.
+ this._keyword = newKeyword;
+ this._initTextField(this._keywordField, newKeyword);
+
+ if (!newKeyword) {
+ let entries = [];
+ yield PlacesUtils.keywords.fetch({ url: this._paneInfo.uri.spec },
+ e => entries.push(e));
+ if (entries.length > 0) {
+ // We show an existing keyword if either POST data was not provided, or
+ // if the POST data is the same.
+ let existingKeyword = entries[0].keyword;
+ let postData = this._paneInfo.postData;
+ if (postData) {
+ let sameEntry = entries.find(e => e.postData === postData);
+ existingKeyword = sameEntry ? sameEntry.keyword : "";
+ }
+ if (existingKeyword) {
+ this._keyword = existingKeyword;
+ // Update the text field to the existing keyword.
+ this._initTextField(this._keywordField, this._keyword);
+ }
+ }
+ }
+ }),
+
+ _initLoadInSidebar: Task.async(function* () {
+ if (!this._paneInfo.isBookmark)
+ throw new Error("_initLoadInSidebar called unexpectedly");
+
+ this._loadInSidebarCheckbox.checked =
+ PlacesUtils.annotations.itemHasAnnotation(
+ this._paneInfo.itemId, PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
+ }),
+
+ /**
+ * Initialize the panel.
+ *
+ * @param aInfo
+ * An object having:
+ * 1. one of the following properties:
+ * - node: either a result node or a node-like object representing the
+ * item to be edited. A node-like object must have the following
+ * properties (with values that match exactly those a result node
+ * would have): itemId, bookmarkGuid, uri, title, type.
+ * - uris: an array of uris for bulk tagging.
+ *
+ * 2. any of the following optional properties:
+ * - hiddenRows (Strings array): list of rows to be hidden regardless
+ * of the item edited. Possible values: "title", "location",
+ * "description", "keyword", "loadInSidebar", "feedLocation",
+ * "siteLocation", folderPicker"
+ */
+ initPanel(aInfo) {
+ if (typeof(aInfo) != "object" || aInfo === null)
+ throw new Error("aInfo must be an object.");
+ if ("node" in aInfo) {
+ try {
+ aInfo.node.type;
+ } catch (e) {
+ // If the lazy loader for |type| generates an exception, it means that
+ // this bookmark could not be loaded. This sometimes happens when tests
+ // create a bookmark by clicking the bookmark star, then try to cleanup
+ // before the bookmark panel has finished opening. Either way, if we
+ // cannot retrieve the bookmark information, we cannot open the panel.
+ return;
+ }
+ }
+
+ // For sanity ensure that the implementer has uninited the panel before
+ // trying to init it again, or we could end up leaking due to observers.
+ if (this.initialized)
+ this.uninitPanel(false);
+
+ let { itemId, isItem, isURI,
+ isBookmark, bulkTagging, uris,
+ visibleRows, focusedElement } = this._setPaneInfo(aInfo);
+
+ let showOrCollapse =
+ (rowId, isAppropriateForInput, nameInHiddenRows = null) => {
+ let visible = isAppropriateForInput;
+ if (visible && "hiddenRows" in aInfo && nameInHiddenRows)
+ visible &= aInfo.hiddenRows.indexOf(nameInHiddenRows) == -1;
+ if (visible)
+ visibleRows.add(rowId);
+ return !(this._element(rowId).collapsed = !visible);
+ };
+
+ if (showOrCollapse("nameRow", !bulkTagging, "name")) {
+ this._initNamePicker();
+ this._namePicker.readOnly = this.readOnly;
+ }
+
+ // In some cases we want to hide the location field, since it's not
+ // human-readable, but we still want to initialize it.
+ showOrCollapse("locationRow", isURI, "location");
+ if (isURI) {
+ this._initLocationField();
+ this._locationField.readOnly = this.readOnly;
+ }
+
+ // hide the description field for
+ if (showOrCollapse("descriptionRow", isItem && !this.readOnly,
+ "description")) {
+ this._initDescriptionField();
+ this._descriptionField.readOnly = this.readOnly;
+ }
+
+ if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
+ this._initKeywordField().catch(Components.utils.reportError);
+ this._keywordField.readOnly = this.readOnly;
+ }
+
+ // Collapse the tag selector if the item does not accept tags.
+ if (showOrCollapse("tagsRow", isURI || bulkTagging, "tags"))
+ this._initTagsField().catch(Components.utils.reportError);
+ else if (!this._element("tagsSelectorRow").collapsed)
+ this.toggleTagsSelector().catch(Components.utils.reportError);
+
+ // Load in sidebar.
+ if (showOrCollapse("loadInSidebarCheckbox", isBookmark, "loadInSidebar")) {
+ this._initLoadInSidebar();
+ }
+
+ // Folder picker.
+ // Technically we should check that the item is not moveable, but that's
+ // not cheap (we don't always have the parent), and there's no use case for
+ // this (it's only the Star UI that shows the folderPicker)
+ if (showOrCollapse("folderRow", isItem, "folderPicker")) {
+ let containerId = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
+ this._initFolderMenuList(containerId);
+ }
+
+ // Selection count.
+ if (showOrCollapse("selectionCount", bulkTagging)) {
+ this._element("itemsCountText").value =
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ uris.length,
+ [uris.length]);
+ }
+
+ // Observe changes.
+ if (!this._observersAdded) {
+ PlacesUtils.bookmarks.addObserver(this, false);
+ window.addEventListener("unload", this, false);
+ this._observersAdded = true;
+ }
+
+ // The focusedElement possible values are:
+ // * preferred: focus the field that the user touched first the last
+ // time the pane was shown (either namePicker or tagsField)
+ // * first: focus the first non collapsed textbox
+ // Note: since all controls are collapsed by default, we don't get the
+ // default XUL dialog behavior, that selects the first control, so we set
+ // the focus explicitly.
+ let elt;
+ if (focusedElement === "preferred") {
+ elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField"));
+ } else if (focusedElement === "first") {
+ elt = document.querySelector("textbox:not([collapsed=true])");
+ }
+ if (elt) {
+ elt.focus();
+ elt.select();
+ }
+ },
+
+ /**
+ * Finds tags that are in common among this._currentInfo.uris;
+ */
+ _getCommonTags() {
+ if ("_cachedCommonTags" in this._paneInfo)
+ return this._paneInfo._cachedCommonTags;
+
+ let uris = [...this._paneInfo.uris];
+ let firstURI = uris.shift();
+ let commonTags = new Set(PlacesUtils.tagging.getTagsForURI(firstURI));
+ if (commonTags.size == 0)
+ return this._cachedCommonTags = [];
+
+ for (let uri of uris) {
+ let curentURITags = PlacesUtils.tagging.getTagsForURI(uri);
+ for (let tag of commonTags) {
+ if (!curentURITags.includes(tag)) {
+ commonTags.delete(tag)
+ if (commonTags.size == 0)
+ return this._paneInfo.cachedCommonTags = [];
+ }
+ }
+ }
+ return this._paneInfo._cachedCommonTags = [...commonTags];
+ },
+
+ _initTextField(aElement, aValue) {
+ if (aElement.value != aValue) {
+ aElement.value = aValue;
+
+ // Clear the undo stack
+ let editor = aElement.editor;
+ if (editor)
+ editor.transactionManager.clear();
+ }
+ },
+
+ /**
+ * Appends a menu-item representing a bookmarks folder to a menu-popup.
+ * @param aMenupopup
+ * The popup to which the menu-item should be added.
+ * @param aFolderId
+ * The identifier of the bookmarks folder.
+ * @return the new menu item.
+ */
+ _appendFolderItemToMenupopup(aMenupopup, aFolderId) {
+ // First make sure the folders-separator is visible
+ this._element("foldersSeparator").hidden = false;
+
+ var folderMenuItem = document.createElement("menuitem");
+ var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
+ folderMenuItem.folderId = aFolderId;
+ folderMenuItem.setAttribute("label", folderTitle);
+ folderMenuItem.className = "menuitem-iconic folder-icon";
+ aMenupopup.appendChild(folderMenuItem);
+ return folderMenuItem;
+ },
+
+ _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) {
+ // clean up first
+ var menupopup = this._folderMenuList.menupopup;
+ while (menupopup.childNodes.length > 6)
+ menupopup.removeChild(menupopup.lastChild);
+
+ const bms = PlacesUtils.bookmarks;
+ const annos = PlacesUtils.annotations;
+
+ // Build the static list
+ var unfiledItem = this._element("unfiledRootItem");
+ if (!this._staticFoldersListBuilt) {
+ unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
+ unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
+ var bmMenuItem = this._element("bmRootItem");
+ bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
+ bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
+ var toolbarItem = this._element("toolbarFolderItem");
+ toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
+ toolbarItem.folderId = PlacesUtils.toolbarFolderId;
+ this._staticFoldersListBuilt = true;
+ }
+
+ // List of recently used folders:
+ var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO);
+
+ /**
+ * The value of the LAST_USED_ANNO annotation is the time (in the form of
+ * Date.getTime) at which the folder has been last used.
+ *
+ * First we build the annotated folders array, each item has both the
+ * folder identifier and the time at which it was last-used by this dialog
+ * set. Then we sort it descendingly based on the time field.
+ */
+ this._recentFolders = [];
+ for (let i = 0; i < folderIds.length; i++) {
+ var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
+ this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed });
+ }
+ this._recentFolders.sort(function(a, b) {
+ if (b.lastUsed < a.lastUsed)
+ return -1;
+ if (b.lastUsed > a.lastUsed)
+ return 1;
+ return 0;
+ });
+
+ var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
+ this._recentFolders.length);
+ for (let i = 0; i < numberOfItems; i++) {
+ this._appendFolderItemToMenupopup(menupopup,
+ this._recentFolders[i].folderId);
+ }
+
+ var defaultItem = this._getFolderMenuItem(aSelectedFolder);
+ this._folderMenuList.selectedItem = defaultItem;
+
+ // Set a selectedIndex attribute to show special icons
+ this._folderMenuList.setAttribute("selectedIndex",
+ this._folderMenuList.selectedIndex);
+
+ // Hide the folders-separator if no folder is annotated as recently-used
+ this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
+ this._folderMenuList.disabled = this.readOnly;
+ },
+
+ QueryInterface:
+ XPCOMUtils.generateQI([Components.interfaces.nsIDOMEventListener,
+ Components.interfaces.nsINavBookmarkObserver]),
+
+ _element(aID) {
+ return document.getElementById("editBMPanel_" + aID);
+ },
+
+ uninitPanel(aHideCollapsibleElements) {
+ if (aHideCollapsibleElements) {
+ // Hide the folder tree if it was previously visible.
+ var folderTreeRow = this._element("folderTreeRow");
+ if (!folderTreeRow.collapsed)
+ this.toggleFolderTreeVisibility();
+
+ // Hide the tag selector if it was previously visible.
+ var tagsSelectorRow = this._element("tagsSelectorRow");
+ if (!tagsSelectorRow.collapsed)
+ this.toggleTagsSelector();
+ }
+
+ if (this._observersAdded) {
+ PlacesUtils.bookmarks.removeObserver(this);
+ this._observersAdded = false;
+ }
+
+ this._setPaneInfo(null);
+ this._firstEditedField = "";
+ },
+
+ onTagsFieldChange() {
+ if (this._paneInfo.isURI || this._paneInfo.bulkTagging) {
+ this._updateTags().then(
+ anyChanges => {
+ if (anyChanges)
+ this._mayUpdateFirstEditField("tagsField");
+ }, Components.utils.reportError);
+ }
+ },
+
+ /**
+ * For a given array of currently-set tags and the tags-input-field
+ * value, returns which tags should be removed and which should be added in
+ * the form of { removedTags: [...], newTags: [...] }.
+ */
+ _getTagsChanges(aCurrentTags) {
+ let inputTags = this._getTagsArrayFromTagsInputField();
+
+ // Optimize the trivial cases (which are actually the most common).
+ if (inputTags.length == 0 && aCurrentTags.length == 0)
+ return { newTags: [], removedTags: [] };
+ if (inputTags.length == 0)
+ return { newTags: [], removedTags: aCurrentTags };
+ if (aCurrentTags.length == 0)
+ return { newTags: inputTags, removedTags: [] };
+
+ let removedTags = aCurrentTags.filter(t => !inputTags.includes(t));
+ let newTags = inputTags.filter(t => !aCurrentTags.includes(t));
+ return { removedTags, newTags };
+ },
+
+ // Adds and removes tags for one or more uris.
+ _setTagsFromTagsInputField: Task.async(function* (aCurrentTags, aURIs) {
+ let { removedTags, newTags } = this._getTagsChanges(aCurrentTags);
+ if (removedTags.length + newTags.length == 0)
+ return false;
+
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txns = [];
+ for (let uri of aURIs) {
+ if (removedTags.length > 0)
+ txns.push(new PlacesUntagURITransaction(uri, removedTags));
+ if (newTags.length > 0)
+ txns.push(new PlacesTagURITransaction(uri, newTags));
+ }
+
+ PlacesUtils.transactionManager.doTransaction(
+ new PlacesAggregatedTransaction("Update tags", txns));
+ return true;
+ }
+
+ let setTags = function* () {
+ if (newTags.length > 0) {
+ yield PlacesTransactions.Tag({ urls: aURIs, tags: newTags })
+ .transact();
+ }
+ if (removedTags.length > 0) {
+ yield PlacesTransactions.Untag({ urls: aURIs, tags: removedTags })
+ .transact();
+ }
+ };
+
+ // Only in the library info-pane it's safe (and necessary) to batch these.
+ // TODO bug 1093030: cleanup this mess when the bookmarksProperties dialog
+ // and star UI code don't "run a batch in the background".
+ if (window.document.documentElement.id == "places")
+ PlacesTransactions.batch(setTags).catch(Components.utils.reportError);
+ else
+ Task.spawn(setTags).catch(Components.utils.reportError);
+ return true;
+ }),
+
+ _updateTags: Task.async(function*() {
+ let uris = this._paneInfo.bulkTagging ?
+ this._paneInfo.uris : [this._paneInfo.uri];
+ let currentTags = this._paneInfo.bulkTagging ?
+ yield this._getCommonTags() :
+ PlacesUtils.tagging.getTagsForURI(uris[0]);
+ let anyChanges = yield this._setTagsFromTagsInputField(currentTags, uris);
+ if (!anyChanges)
+ return false;
+
+ // The panel could have been closed in the meanwhile.
+ if (!this._paneInfo)
+ return false;
+
+ // Ensure the tagsField is in sync, clean it up from empty tags
+ currentTags = this._paneInfo.bulkTagging ?
+ this._getCommonTags() :
+ PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri);
+ this._initTextField(this._tagsField, currentTags.join(", "), false);
+ return true;
+ }),
+
+ /**
+ * Stores the first-edit field for this dialog, if the passed-in field
+ * is indeed the first edited field
+ * @param aNewField
+ * the id of the field that may be set (without the "editBMPanel_"
+ * prefix)
+ */
+ _mayUpdateFirstEditField(aNewField) {
+ // * The first-edit-field behavior is not applied in the multi-edit case
+ // * if this._firstEditedField is already set, this is not the first field,
+ // so there's nothing to do
+ if (this._paneInfo.bulkTagging || this._firstEditedField)
+ return;
+
+ this._firstEditedField = aNewField;
+
+ // set the pref
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField);
+ },
+
+ onNamePickerChange() {
+ if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag))
+ return;
+
+ // Here we update either the item title or its cached static title
+ let newTitle = this._namePicker.value;
+ if (!newTitle &&
+ PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) == PlacesUtils.tagsFolderId) {
+ // We don't allow setting an empty title for a tag, restore the old one.
+ this._initNamePicker();
+ }
+ else {
+ this._mayUpdateFirstEditField("namePicker");
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txn = new PlacesEditItemTitleTransaction(this._paneInfo.itemId,
+ newTitle);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ return;
+ }
+ Task.spawn(function* () {
+ let guid = this._paneInfo.isTag
+ ? (yield PlacesUtils.promiseItemGuid(this._paneInfo.itemId))
+ : this._paneInfo.itemGuid;
+ PlacesTransactions.EditTitle({ guid, title: newTitle })
+ .transact().catch(Components.utils.reportError);
+ }).catch(Components.utils.reportError);
+ }
+ },
+
+ onDescriptionFieldChange() {
+ if (this.readOnly || !this._paneInfo.isItem)
+ return;
+
+ let itemId = this._paneInfo.itemId;
+ let description = this._element("descriptionField").value;
+ if (description != PlacesUIUtils.getItemDescription(this._paneInfo.itemId)) {
+ let annotation =
+ { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txn = new PlacesSetItemAnnotationTransaction(itemId,
+ annotation);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ return;
+ }
+ let guid = this._paneInfo.itemGuid;
+ PlacesTransactions.Annotate({ guid, annotation })
+ .transact().catch(Components.utils.reportError);
+ }
+ },
+
+ onLocationFieldChange() {
+ if (this.readOnly || !this._paneInfo.isBookmark)
+ return;
+
+ let newURI;
+ try {
+ newURI = PlacesUIUtils.createFixedURI(this._locationField.value);
+ }
+ catch (ex) {
+ // TODO: Bug 1089141 - Provide some feedback about the invalid url.
+ return;
+ }
+
+ if (this._paneInfo.uri.equals(newURI))
+ return;
+
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txn = new PlacesEditBookmarkURITransaction(this._paneInfo.itemId, newURI);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ return;
+ }
+ let guid = this._paneInfo.itemGuid;
+ PlacesTransactions.EditUrl({ guid, url: newURI })
+ .transact().catch(Components.utils.reportError);
+ },
+
+ onKeywordFieldChange() {
+ if (this.readOnly || !this._paneInfo.isBookmark)
+ return;
+
+ let itemId = this._paneInfo.itemId;
+ let oldKeyword = this._keyword;
+ let keyword = this._keyword = this._keywordField.value;
+ let postData = this._paneInfo.postData;
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txn = new PlacesEditBookmarkKeywordTransaction(itemId,
+ keyword,
+ postData,
+ oldKeyword);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ return;
+ }
+ let guid = this._paneInfo.itemGuid;
+ PlacesTransactions.EditKeyword({ guid, keyword, postData, oldKeyword })
+ .transact().catch(Components.utils.reportError);
+ },
+
+ onLoadInSidebarCheckboxCommand() {
+ if (!this.initialized || !this._paneInfo.isBookmark)
+ return;
+
+ let annotation = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO };
+ if (this._loadInSidebarCheckbox.checked)
+ annotation.value = true;
+
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let itemId = this._paneInfo.itemId;
+ let txn = new PlacesSetItemAnnotationTransaction(itemId,
+ annotation);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ return;
+ }
+ let guid = this._paneInfo.itemGuid;
+ PlacesTransactions.Annotate({ guid, annotation })
+ .transact().catch(Components.utils.reportError);
+ },
+
+ toggleFolderTreeVisibility() {
+ var expander = this._element("foldersExpander");
+ var folderTreeRow = this._element("folderTreeRow");
+ if (!folderTreeRow.collapsed) {
+ expander.className = "expander-down";
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextdown"));
+ folderTreeRow.collapsed = true;
+ this._element("chooseFolderSeparator").hidden =
+ this._element("chooseFolderMenuItem").hidden = false;
+ }
+ else {
+ expander.className = "expander-up"
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextup"));
+ folderTreeRow.collapsed = false;
+
+ // XXXmano: Ideally we would only do this once, but for some odd reason,
+ // the editable mode set on this tree, together with its collapsed state
+ // breaks the view.
+ const FOLDER_TREE_PLACE_URI =
+ "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
+ PlacesUIUtils.allBookmarksFolderId;
+ this._folderTree.place = FOLDER_TREE_PLACE_URI;
+
+ this._element("chooseFolderSeparator").hidden =
+ this._element("chooseFolderMenuItem").hidden = true;
+ var currentFolder = this._getFolderIdFromMenuList();
+ this._folderTree.selectItems([currentFolder]);
+ this._folderTree.focus();
+ }
+ },
+
+ _getFolderIdFromMenuList() {
+ var selectedItem = this._folderMenuList.selectedItem;
+ NS_ASSERT("folderId" in selectedItem,
+ "Invalid menuitem in the folders-menulist");
+ return selectedItem.folderId;
+ },
+
+ /**
+ * Get the corresponding menu-item in the folder-menu-list for a bookmarks
+ * folder if such an item exists. Otherwise, this creates a menu-item for the
+ * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
+ * the new item replaces the last menu-item.
+ * @param aFolderId
+ * The identifier of the bookmarks folder.
+ */
+ _getFolderMenuItem(aFolderId) {
+ let menupopup = this._folderMenuList.menupopup;
+ let menuItem = Array.prototype.find.call(
+ menupopup.childNodes, menuItem => menuItem.folderId === aFolderId);
+ if (menuItem !== undefined)
+ return menuItem;
+
+ // 3 special folders + separator + folder-items-count limit
+ if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
+ menupopup.removeChild(menupopup.lastChild);
+
+ return this._appendFolderItemToMenupopup(menupopup, aFolderId);
+ },
+
+ onFolderMenuListCommand(aEvent) {
+ // Set a selectedIndex attribute to show special icons
+ this._folderMenuList.setAttribute("selectedIndex",
+ this._folderMenuList.selectedIndex);
+
+ if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
+ // reset the selection back to where it was and expand the tree
+ // (this menu-item is hidden when the tree is already visible
+ let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId);
+ let item = this._getFolderMenuItem(containerId);
+ this._folderMenuList.selectedItem = item;
+ // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
+ // menulist right away
+ setTimeout(() => this.toggleFolderTreeVisibility(), 100);
+ return;
+ }
+
+ // Move the item
+ let containerId = this._getFolderIdFromMenuList();
+ if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId &&
+ this._paneInfo.itemId != containerId) {
+ if (PlacesUIUtils.useAsyncTransactions) {
+ Task.spawn(function* () {
+ let newParentGuid = yield PlacesUtils.promiseItemGuid(containerId);
+ let guid = this._paneInfo.itemGuid;
+ yield PlacesTransactions.Move({ guid, newParentGuid }).transact();
+ }.bind(this));
+ }
+ else {
+ let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId,
+ containerId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+
+ // Mark the containing folder as recently-used if it isn't in the
+ // static list
+ if (containerId != PlacesUtils.unfiledBookmarksFolderId &&
+ containerId != PlacesUtils.toolbarFolderId &&
+ containerId != PlacesUtils.bookmarksMenuFolderId) {
+ this._markFolderAsRecentlyUsed(containerId)
+ .catch(Components.utils.reportError);
+ }
+
+ // Auto-show the bookmarks toolbar when adding / moving an item there.
+ if (containerId == PlacesUtils.toolbarFolderId) {
+ Services.obs.notifyObservers(null, "autoshow-bookmarks-toolbar", null);
+ }
+ }
+
+ // Update folder-tree selection
+ var folderTreeRow = this._element("folderTreeRow");
+ if (!folderTreeRow.collapsed) {
+ var selectedNode = this._folderTree.selectedNode;
+ if (!selectedNode ||
+ PlacesUtils.getConcreteItemId(selectedNode) != containerId)
+ this._folderTree.selectItems([containerId]);
+ }
+ },
+
+ onFolderTreeSelect() {
+ var selectedNode = this._folderTree.selectedNode;
+
+ // Disable the "New Folder" button if we cannot create a new folder
+ this._element("newFolderButton")
+ .disabled = !this._folderTree.insertionPoint || !selectedNode;
+
+ if (!selectedNode)
+ return;
+
+ var folderId = PlacesUtils.getConcreteItemId(selectedNode);
+ if (this._getFolderIdFromMenuList() == folderId)
+ return;
+
+ var folderItem = this._getFolderMenuItem(folderId);
+ this._folderMenuList.selectedItem = folderItem;
+ folderItem.doCommand();
+ },
+
+ _markFolderAsRecentlyUsed: Task.async(function* (aFolderId) {
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txns = [];
+
+ // Expire old unused recent folders.
+ let annotation = this._getLastUsedAnnotationObject(false);
+ while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
+ let folderId = this._recentFolders.pop().folderId;
+ let annoTxn = new PlacesSetItemAnnotationTransaction(folderId,
+ annotation);
+ txns.push(annoTxn);
+ }
+
+ // Mark folder as recently used
+ annotation = this._getLastUsedAnnotationObject(true);
+ let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId,
+ annotation);
+ txns.push(annoTxn);
+
+ let aggregate =
+ new PlacesAggregatedTransaction("Update last used folders", txns);
+ PlacesUtils.transactionManager.doTransaction(aggregate);
+ return;
+ }
+
+ // Expire old unused recent folders.
+ let guids = [];
+ while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
+ let folderId = this._recentFolders.pop().folderId;
+ let guid = yield PlacesUtils.promiseItemGuid(folderId);
+ guids.push(guid);
+ }
+ if (guids.length > 0) {
+ let annotation = this._getLastUsedAnnotationObject(false);
+ PlacesTransactions.Annotate({ guids, annotation })
+ .transact().catch(Components.utils.reportError);
+ }
+
+ // Mark folder as recently used
+ let annotation = this._getLastUsedAnnotationObject(true);
+ let guid = yield PlacesUtils.promiseItemGuid(aFolderId);
+ PlacesTransactions.Annotate({ guid, annotation })
+ .transact().catch(Components.utils.reportError);
+ }),
+
+ /**
+ * Returns an object which could then be used to set/unset the
+ * LAST_USED_ANNO annotation for a folder.
+ *
+ * @param aLastUsed
+ * Whether to set or unset the LAST_USED_ANNO annotation.
+ * @returns an object representing the annotation which could then be used
+ * with the transaction manager.
+ */
+ _getLastUsedAnnotationObject(aLastUsed) {
+ return { name: LAST_USED_ANNO,
+ value: aLastUsed ? new Date().getTime() : null };
+ },
+
+ _rebuildTagsSelectorList: Task.async(function* () {
+ let tagsSelector = this._element("tagsSelector");
+ let tagsSelectorRow = this._element("tagsSelectorRow");
+ if (tagsSelectorRow.collapsed)
+ return;
+
+ // Save the current scroll position and restore it after the rebuild.
+ let firstIndex = tagsSelector.getIndexOfFirstVisibleRow();
+ let selectedIndex = tagsSelector.selectedIndex;
+ let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label
+ : null;
+
+ while (tagsSelector.hasChildNodes()) {
+ tagsSelector.removeChild(tagsSelector.lastChild);
+ }
+
+ let tagsInField = this._getTagsArrayFromTagsInputField();
+ let allTags = PlacesUtils.tagging.allTags;
+ for (tag of allTags) {
+ let elt = document.createElement("listitem");
+ elt.setAttribute("type", "checkbox");
+ elt.setAttribute("label", tag);
+ if (tagsInField.includes(tag))
+ elt.setAttribute("checked", "true");
+ tagsSelector.appendChild(elt);
+ if (selectedTag === tag)
+ selectedIndex = tagsSelector.getIndexOfItem(elt);
+ }
+
+ // Restore position.
+ // The listbox allows to scroll only if the required offset doesn't
+ // overflow its capacity, thus need to adjust the index for removals.
+ firstIndex =
+ Math.min(firstIndex,
+ tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows());
+ tagsSelector.scrollToIndex(firstIndex);
+ if (selectedIndex >= 0 && tagsSelector.itemCount > 0) {
+ selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1);
+ tagsSelector.selectedIndex = selectedIndex;
+ tagsSelector.ensureIndexIsVisible(selectedIndex);
+ }
+ }),
+
+ toggleTagsSelector: Task.async(function* () {
+ var tagsSelector = this._element("tagsSelector");
+ var tagsSelectorRow = this._element("tagsSelectorRow");
+ var expander = this._element("tagsSelectorExpander");
+ if (tagsSelectorRow.collapsed) {
+ expander.className = "expander-up";
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextup"));
+ tagsSelectorRow.collapsed = false;
+ yield this._rebuildTagsSelectorList();
+
+ // This is a no-op if we've added the listener.
+ tagsSelector.addEventListener("CheckboxStateChange", this, false);
+ }
+ else {
+ expander.className = "expander-down";
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextdown"));
+ tagsSelectorRow.collapsed = true;
+ }
+ }),
+
+ /**
+ * Splits "tagsField" element value, returning an array of valid tag strings.
+ *
+ * @return Array of tag strings found in the field value.
+ */
+ _getTagsArrayFromTagsInputField() {
+ let tags = this._element("tagsField").value;
+ return tags.trim()
+ .split(/\s*,\s*/) // Split on commas and remove spaces.
+ .filter(tag => tag.length > 0); // Kill empty tags.
+ },
+
+ newFolder: Task.async(function* () {
+ let ip = this._folderTree.insertionPoint;
+
+ // default to the bookmarks menu folder
+ if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
+ ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+ }
+
+ // XXXmano: add a separate "New Folder" string at some point...
+ let title = this._element("newFolderButton").label;
+ if (PlacesUIUtils.useAsyncTransactions) {
+ let parentGuid = yield ip.promiseGuid();
+ yield PlacesTransactions.NewFolder({ parentGuid, title, index: ip.index })
+ .transact().catch(Components.utils.reportError);
+ }
+ else {
+ let txn = new PlacesCreateFolderTransaction(title, ip.itemId, ip.index);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+
+ this._folderTree.focus();
+ this._folderTree.selectItems([ip.itemId]);
+ PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true;
+ this._folderTree.selectItems([this._lastNewItem]);
+ this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
+ this._folderTree.columns.getFirstColumn());
+ }),
+
+ // nsIDOMEventListener
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "CheckboxStateChange":
+ // Update the tags field when items are checked/unchecked in the listbox
+ let tags = this._getTagsArrayFromTagsInputField();
+ let tagCheckbox = aEvent.target;
+
+ let curTagIndex = tags.indexOf(tagCheckbox.label);
+ let tagsSelector = this._element("tagsSelector");
+ tagsSelector.selectedItem = tagCheckbox;
+
+ if (tagCheckbox.checked) {
+ if (curTagIndex == -1)
+ tags.push(tagCheckbox.label);
+ }
+ else if (curTagIndex != -1) {
+ tags.splice(curTagIndex, 1);
+ }
+ this._element("tagsField").value = tags.join(", ");
+ this._updateTags();
+ break;
+ case "unload":
+ this.uninitPanel(false);
+ break;
+ }
+ },
+
+ _initTagsField: Task.async(function* () {
+ let tags;
+ if (this._paneInfo.isURI)
+ tags = PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri);
+ else if (this._paneInfo.bulkTagging)
+ tags = this._getCommonTags();
+ else
+ throw new Error("_promiseTagsStr called unexpectedly");
+
+ this._initTextField(this._tagsField, tags.join(", "));
+ }),
+
+ _onTagsChange(aItemId) {
+ let paneInfo = this._paneInfo;
+ let updateTagsField = false;
+ if (paneInfo.isURI) {
+ if (paneInfo.isBookmark && aItemId == paneInfo.itemId) {
+ updateTagsField = true;
+ }
+ else if (!paneInfo.isBookmark) {
+ let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
+ updateTagsField = changedURI.equals(paneInfo.uri);
+ }
+ }
+ else if (paneInfo.bulkTagging) {
+ let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
+ if (paneInfo.uris.some(uri => uri.equals(changedURI))) {
+ updateTagsField = true;
+ delete this._paneInfo._cachedCommonTags;
+ }
+ }
+ else {
+ throw new Error("_onTagsChange called unexpectedly");
+ }
+
+ if (updateTagsField)
+ this._initTagsField().catch(Components.utils.reportError);
+
+ // Any tags change should be reflected in the tags selector.
+ if (this._element("tagsSelector"))
+ this._rebuildTagsSelectorList().catch(Components.utils.reportError);
+ },
+
+ _onItemTitleChange(aItemId, aNewTitle) {
+ if (!this._paneInfo.isBookmark)
+ return;
+ if (aItemId == this._paneInfo.itemId) {
+ this._paneInfo.title = aNewTitle;
+ this._initTextField(this._namePicker, aNewTitle);
+ }
+ else if (this._paneInfo.visibleRows.has("folderRow")) {
+ // If the title of a folder which is listed within the folders
+ // menulist has been changed, we need to update the label of its
+ // representing element.
+ let menupopup = this._folderMenuList.menupopup;
+ for (menuitem of menupopup.childNodes) {
+ if ("folderId" in menuitem && menuitem.folderId == aItemId) {
+ menuitem.label = aNewTitle;
+ break;
+ }
+ }
+ }
+ },
+
+ // nsINavBookmarkObserver
+ onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue,
+ aLastModified, aItemType) {
+ if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) {
+ this._onTagsChange(aItemId);
+ }
+ else if (aProperty == "title" && this._paneInfo.isItem) {
+ // This also updates titles of folders in the folder menu list.
+ this._onItemTitleChange(aItemId, aValue);
+ }
+ else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) {
+ return;
+ }
+
+ switch (aProperty) {
+ case "uri":
+ let newURI = NetUtil.newURI(aValue);
+ if (!newURI.equals(this._paneInfo.uri)) {
+ this._paneInfo.uri = newURI;
+ if (this._paneInfo.visibleRows.has("locationRow"))
+ this._initLocationField();
+
+ if (this._paneInfo.visibleRows.has("tagsRow")) {
+ delete this._paneInfo._cachedCommonTags;
+ this._onTagsChange(aItemId);
+ }
+ }
+ break;
+ case "keyword":
+ if (this._paneInfo.visibleRows.has("keywordRow"))
+ this._initKeywordField(aValue).catch(Components.utils.reportError);
+ break;
+ case PlacesUIUtils.DESCRIPTION_ANNO:
+ if (this._paneInfo.visibleRows.has("descriptionRow"))
+ this._initDescriptionField();
+ break;
+ case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
+ if (this._paneInfo.visibleRows.has("loadInSidebarCheckbox"))
+ this._initLoadInSidebar();
+ break;
+ }
+ },
+
+ onItemMoved(aItemId, aOldParent, aOldIndex,
+ aNewParent, aNewIndex, aItemType) {
+ if (!this._paneInfo.isItem ||
+ !this._paneInfo.visibleRows.has("folderPicker") ||
+ this._paneInfo.itemId != aItemOd ||
+ aNewParent == this._getFolderIdFromMenuList()) {
+ return;
+ }
+
+ // Just setting selectItem _does not_ trigger oncommand, so we don't
+ // recurse.
+ this._folderMenuList.selectedItem = this._getFolderMenuItem(aNewParent);
+ },
+
+ onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) {
+ this._lastNewItem = aItemId;
+ },
+
+ onItemRemoved() { },
+ onBeginUpdateBatch() { },
+ onEndUpdateBatch() { },
+ onItemVisited() { },
+};
+
+
+for (let elt of ["folderMenuList", "folderTree", "namePicker",
+ "locationField", "descriptionField", "keywordField",
+ "tagsField", "loadInSidebarCheckbox"]) {
+ let eltScoped = elt;
+ XPCOMUtils.defineLazyGetter(gEditItemOverlay, `_${eltScoped}`,
+ () => gEditItemOverlay._element(eltScoped));
+}
diff --git a/browser/components/places/content/editBookmarkOverlay.xul b/browser/components/places/content/editBookmarkOverlay.xul
new file mode 100644
index 000000000..140e752c0
--- /dev/null
+++ b/browser/components/places/content/editBookmarkOverlay.xul
@@ -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/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+%editBookmarkOverlayDTD;
+]>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<overlay id="editBookmarkOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <vbox id="editBookmarkPanelContent" flex="1">
+ <hbox id="editBMPanel_selectionCount" pack="center">
+ <label id="editBMPanel_itemsCountText"/>
+ </hbox>
+
+ <grid id="editBookmarkPanelGrid" flex="1">
+ <columns id="editBMPanel_columns">
+ <column id="editBMPanel_labelColumn" />
+ <column flex="1" id="editBMPanel_editColumn" />
+ </columns>
+ <rows id="editBMPanel_rows">
+ <row id="editBMPanel_nameRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.name.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.name.accesskey;"
+ control="editBMPanel_namePicker"/>
+ <textbox id="editBMPanel_namePicker"
+ onchange="gEditItemOverlay.onNamePickerChange();"/>
+ </row>
+
+ <row id="editBMPanel_locationRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.location.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.location.accesskey;"
+ control="editBMPanel_locationField"/>
+ <textbox id="editBMPanel_locationField"
+ class="uri-element"
+ onchange="gEditItemOverlay.onLocationFieldChange();"/>
+ </row>
+
+ <row id="editBMPanel_folderRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.folder.label;"
+ class="editBMPanel_rowLabel"
+ control="editBMPanel_folderMenuList"/>
+ <hbox flex="1" align="center">
+ <menulist id="editBMPanel_folderMenuList"
+ class="folder-icon"
+ flex="1"
+ oncommand="gEditItemOverlay.onFolderMenuListCommand(event);">
+ <menupopup>
+ <!-- Static item for special folders -->
+ <menuitem id="editBMPanel_toolbarFolderItem"
+ class="menuitem-iconic folder-icon"/>
+ <menuitem id="editBMPanel_bmRootItem"
+ class="menuitem-iconic folder-icon"/>
+ <menuitem id="editBMPanel_unfiledRootItem"
+ class="menuitem-iconic folder-icon"/>
+ <menuseparator id="editBMPanel_chooseFolderSeparator"/>
+ <menuitem id="editBMPanel_chooseFolderMenuItem"
+ label="&editBookmarkOverlay.choose.label;"
+ class="menuitem-iconic folder-icon"/>
+ <menuseparator id="editBMPanel_foldersSeparator" hidden="true"/>
+ </menupopup>
+ </menulist>
+ <button id="editBMPanel_foldersExpander"
+ class="expander-down"
+ tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
+ tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
+ tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
+ oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"/>
+ </hbox>
+ </row>
+
+ <row id="editBMPanel_folderTreeRow"
+ collapsed="true"
+ flex="1">
+ <spacer/>
+ <vbox flex="1">
+ <tree id="editBMPanel_folderTree"
+ flex="1"
+ class="placesTree"
+ type="places"
+ height="150"
+ minheight="150"
+ editable="true"
+ onselect="gEditItemOverlay.onFolderTreeSelect();"
+ hidecolumnpicker="true">
+ <treecols>
+ <treecol anonid="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <hbox id="editBMPanel_newFolderBox">
+ <button label="&editBookmarkOverlay.newFolderButton.label;"
+ id="editBMPanel_newFolderButton"
+ accesskey="&editBookmarkOverlay.newFolderButton.accesskey;"
+ oncommand="gEditItemOverlay.newFolder().catch(Components.utils.reportError);"/>
+ </hbox>
+ </vbox>
+ </row>
+
+ <row id="editBMPanel_tagsRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.tags.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.tags.accesskey;"
+ control="editBMPanel_tagsField"/>
+ <hbox flex="1" align="center">
+ <textbox id="editBMPanel_tagsField"
+ type="autocomplete"
+ class="padded"
+ flex="1"
+ autocompletesearch="places-tag-autocomplete"
+ completedefaultindex="true"
+ tabscrolling="true"
+ showcommentcolumn="true"
+ placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"
+ onchange="gEditItemOverlay.onTagsFieldChange();"/>
+ <button id="editBMPanel_tagsSelectorExpander"
+ class="expander-down"
+ tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
+ tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
+ tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
+ oncommand="gEditItemOverlay.toggleTagsSelector();"/>
+ </hbox>
+ </row>
+
+ <row id="editBMPanel_tagsSelectorRow"
+ align="center"
+ collapsed="true">
+ <spacer/>
+ <listbox id="editBMPanel_tagsSelector"
+ height="150"/>
+ </row>
+
+ <row id="editBMPanel_keywordRow"
+ align="center"
+ collapsed="true">
+ <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+ <label value="&editBookmarkOverlay.keyword.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.keyword.accesskey;"
+ control="editBMPanel_keywordField"/>
+ <textbox id="editBMPanel_keywordField"
+ onchange="gEditItemOverlay.onKeywordFieldChange();"/>
+ </row>
+
+ <row id="editBMPanel_descriptionRow"
+ collapsed="true">
+ <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+ <label value="&editBookmarkOverlay.description.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.description.accesskey;"
+ control="editBMPanel_descriptionField"/>
+ <textbox id="editBMPanel_descriptionField"
+ multiline="true"
+ rows="4"
+ onchange="gEditItemOverlay.onDescriptionFieldChange();"/>
+ </row>
+ </rows>
+ </grid>
+
+ <checkbox id="editBMPanel_loadInSidebarCheckbox"
+ collapsed="true"
+ label="&editBookmarkOverlay.loadInSidebar.label;"
+ accesskey="&editBookmarkOverlay.loadInSidebar.accesskey;"
+ oncommand="gEditItemOverlay.onLoadInSidebarCheckboxCommand();">
+ <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+ </checkbox>
+
+ <!-- If the ids are changing or additional fields are being added, be sure
+ to sync the values in places.js -->
+ <broadcaster id="additionalInfoBroadcaster"/>
+ </vbox>
+</overlay>
diff --git a/browser/components/places/content/history-panel.js b/browser/components/places/content/history-panel.js
new file mode 100644
index 000000000..20dbbb5bd
--- /dev/null
+++ b/browser/components/places/content/history-panel.js
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/TelemetryStopwatch.jsm");
+
+var gHistoryTree;
+var gSearchBox;
+var gHistoryGrouping = "";
+var gSearching = false;
+
+function HistorySidebarInit()
+{
+ gHistoryTree = document.getElementById("historyTree");
+ gSearchBox = document.getElementById("search-box");
+
+ gHistoryGrouping = document.getElementById("viewButton").
+ getAttribute("selectedsort");
+
+ if (gHistoryGrouping == "site")
+ document.getElementById("bysite").setAttribute("checked", "true");
+ else if (gHistoryGrouping == "visited")
+ document.getElementById("byvisited").setAttribute("checked", "true");
+ else if (gHistoryGrouping == "lastvisited")
+ document.getElementById("bylastvisited").setAttribute("checked", "true");
+ else if (gHistoryGrouping == "dayandsite")
+ document.getElementById("bydayandsite").setAttribute("checked", "true");
+ else
+ document.getElementById("byday").setAttribute("checked", "true");
+
+ searchHistory("");
+}
+
+function GroupBy(groupingType)
+{
+ gHistoryGrouping = groupingType;
+ searchHistory(gSearchBox.value);
+}
+
+function searchHistory(aInput)
+{
+ var query = PlacesUtils.history.getNewQuery();
+ var options = PlacesUtils.history.getNewQueryOptions();
+
+ const NHQO = Ci.nsINavHistoryQueryOptions;
+ var sortingMode;
+ var resultType;
+
+ switch (gHistoryGrouping) {
+ case "visited":
+ resultType = NHQO.RESULTS_AS_URI;
+ sortingMode = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
+ break;
+ case "lastvisited":
+ resultType = NHQO.RESULTS_AS_URI;
+ sortingMode = NHQO.SORT_BY_DATE_DESCENDING;
+ break;
+ case "dayandsite":
+ resultType = NHQO.RESULTS_AS_DATE_SITE_QUERY;
+ break;
+ case "site":
+ resultType = NHQO.RESULTS_AS_SITE_QUERY;
+ sortingMode = NHQO.SORT_BY_TITLE_ASCENDING;
+ break;
+ case "day":
+ default:
+ resultType = NHQO.RESULTS_AS_DATE_QUERY;
+ break;
+ }
+
+ if (aInput) {
+ query.searchTerms = aInput;
+ if (gHistoryGrouping != "visited" && gHistoryGrouping != "lastvisited") {
+ sortingMode = NHQO.SORT_BY_FRECENCY_DESCENDING;
+ resultType = NHQO.RESULTS_AS_URI;
+ }
+ }
+
+ options.sortingMode = sortingMode;
+ options.resultType = resultType;
+ options.includeHidden = !!aInput;
+
+ if (gHistoryGrouping == "lastvisited")
+ this.TelemetryStopwatch.start("HISTORY_LASTVISITED_TREE_QUERY_TIME_MS");
+
+ // call load() on the tree manually
+ // instead of setting the place attribute in history-panel.xul
+ // otherwise, we will end up calling load() twice
+ gHistoryTree.load([query], options);
+
+ if (gHistoryGrouping == "lastvisited")
+ this.TelemetryStopwatch.finish("HISTORY_LASTVISITED_TREE_QUERY_TIME_MS");
+}
+
+window.addEventListener("SidebarFocused",
+ () => gSearchBox.focus(),
+ false);
diff --git a/browser/components/places/content/history-panel.xul b/browser/components/places/content/history-panel.xul
new file mode 100644
index 000000000..d1c875a63
--- /dev/null
+++ b/browser/components/places/content/history-panel.xul
@@ -0,0 +1,95 @@
+<?xml version="1.0"?> <!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE page [
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+]>
+
+<!-- we need to keep id="history-panel" for upgrade and switching
+ between versions of the browser -->
+
+<page id="history-panel" orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="HistorySidebarInit();"
+ onunload="SidebarUtils.setMouseoverURL('');">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/history-panel.js"/>
+
+ <commandset id="editMenuCommands"/>
+ <commandset id="placesCommands"/>
+
+ <keyset id="editMenuKeys">
+#ifdef XP_MACOSX
+ <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
+#endif
+ </keyset>
+
+ <!-- required to overlay the context menu -->
+ <menupopup id="placesContext"/>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <hbox id="sidebar-search-container" align="center">
+ <label id="sidebar-search-label"
+ value="&find.label;" accesskey="&find.accesskey;"
+ control="search-box"/>
+ <textbox id="search-box" flex="1" type="search" class="compact"
+ aria-controls="historyTree"
+ oncommand="searchHistory(this.value);"/>
+ <button id="viewButton" style="min-width:0px !important;" type="menu"
+ label="&view.label;" accesskey="&view.accesskey;" selectedsort="day"
+ persist="selectedsort">
+ <menupopup>
+ <menuitem id="bydayandsite" label="&byDayAndSite.label;"
+ accesskey="&byDayAndSite.accesskey;" type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'dayandsite'); GroupBy('dayandsite');"/>
+ <menuitem id="bysite" label="&bySite.label;"
+ accesskey="&bySite.accesskey;" type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'site'); GroupBy('site');"/>
+ <menuitem id="byday" label="&byDate.label;"
+ accesskey="&byDate.accesskey;"
+ type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'day'); GroupBy('day');"/>
+ <menuitem id="byvisited" label="&byMostVisited.label;"
+ accesskey="&byMostVisited.accesskey;"
+ type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'visited'); GroupBy('visited');"/>
+ <menuitem id="bylastvisited" label="&byLastVisited.label;"
+ accesskey="&byLastVisited.accesskey;"
+ type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'lastvisited'); GroupBy('lastvisited');"/>
+ </menupopup>
+ </button>
+ </hbox>
+
+ <tree id="historyTree"
+ class="sidebar-placesTree"
+ flex="1"
+ type="places"
+ context="placesContext"
+ hidecolumnpicker="true"
+ onkeypress="SidebarUtils.handleTreeKeyPress(event);"
+ onclick="SidebarUtils.handleTreeClick(this, event, true);"
+ onmousemove="SidebarUtils.handleTreeMouseMove(event);"
+ onmouseout="SidebarUtils.setMouseoverURL('');">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/>
+ </tree>
+</page>
diff --git a/browser/components/places/content/menu.xml b/browser/components/places/content/menu.xml
new file mode 100644
index 000000000..f791d76fb
--- /dev/null
+++ b/browser/components/places/content/menu.xml
@@ -0,0 +1,633 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="placesMenuBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="places-popup-base"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
+ <xul:image class="menupopup-drop-indicator" mousethrough="always"/>
+ </xul:vbox>
+ <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
+ smoothscroll="false">
+ <children/>
+ </xul:arrowscrollbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+
+ <field name="AppConstants" readonly="true">
+ (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
+ </field>
+
+ <field name="_indicatorBar">
+ document.getAnonymousElementByAttribute(this, "class",
+ "menupopup-drop-indicator-bar");
+ </field>
+
+ <field name="_scrollBox">
+ document.getAnonymousElementByAttribute(this, "class",
+ "popup-internal-box");
+ </field>
+
+ <!-- This is the view that manage the popup -->
+ <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>
+
+ <!-- Check if we should hide the drop indicator for the target -->
+ <method name="_hideDropIndicator">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let target = aEvent.target;
+
+ // Don't draw the drop indicator outside of markers or if current
+ // node is not a Places node.
+ let betweenMarkers =
+ (this._startMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING) &&
+ (this._endMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_PRECEDING);
+
+ // Hide the dropmarker if current node is not a Places node.
+ return !(target && target._placesNode && betweenMarkers);
+ ]]></body>
+ </method>
+
+ <!-- This function returns information about where to drop when
+ dragging over this popup insertion point -->
+ <method name="_getDropPoint">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ // Can't drop if the menu isn't a folder
+ let resultNode = this._placesNode;
+
+ if (!PlacesUtils.nodeIsFolder(resultNode) ||
+ PlacesControllerDragHelper.disallowInsertion(resultNode)) {
+ return null;
+ }
+
+ var dropPoint = { ip: null, folderElt: null };
+
+ // The element we are dragging over
+ let elt = aEvent.target;
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ // Calculate positions taking care of arrowscrollbox
+ let scrollbox = this._scrollBox;
+ let eventY = aEvent.layerY + (scrollbox.boxObject.y - this.boxObject.y);
+ let scrollboxOffset = scrollbox.scrollBoxObject.y -
+ (scrollbox.boxObject.y - this.boxObject.y);
+ let eltY = elt.boxObject.y - scrollboxOffset;
+ let eltHeight = elt.boxObject.height;
+
+ if (!elt._placesNode) {
+ // If we are dragging over a non places node drop at the end.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_ON);
+ // We can set folderElt if we are dropping over a static menu that
+ // has an internal placespopup.
+ let isMenu = elt.localName == "menu" ||
+ (elt.localName == "toolbarbutton" &&
+ elt.getAttribute("type") == "menu");
+ if (isMenu && elt.lastChild &&
+ elt.lastChild.hasAttribute("placespopup"))
+ dropPoint.folderElt = elt;
+ return dropPoint;
+ }
+
+ let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
+ elt._placesNode.title : null;
+ if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
+ !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) ||
+ PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
+ // This is a folder or a tag container.
+ if (eventY - eltY < eltHeight * 0.20) {
+ // If mouse is in the top part of the element, drop above folder.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_BEFORE,
+ tagName,
+ elt._placesNode.itemId);
+ return dropPoint;
+ }
+ else if (eventY - eltY < eltHeight * 0.80) {
+ // If mouse is in the middle of the element, drop inside folder.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(elt._placesNode),
+ -1,
+ Ci.nsITreeView.DROP_ON,
+ tagName);
+ dropPoint.folderElt = elt;
+ return dropPoint;
+ }
+ }
+ else if (eventY - eltY <= eltHeight / 2) {
+ // This is a non-folder node or a readonly folder.
+ // If the mouse is above the middle, drop above this item.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_BEFORE,
+ tagName,
+ elt._placesNode.itemId);
+ return dropPoint;
+ }
+
+ // Drop below the item.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_AFTER,
+ tagName,
+ elt._placesNode.itemId);
+ return dropPoint;
+ ]]></body>
+ </method>
+
+ <!-- Sub-menus should be opened when the mouse drags over them, and closed
+ when the mouse drags off. The overFolder object manages opening and
+ closing of folders when the mouse hovers. -->
+ <field name="_overFolder"><![CDATA[({
+ _self: this,
+ _folder: {elt: null,
+ openTimer: null,
+ hoverTime: 350,
+ closeTimer: null},
+ _closeMenuTimer: null,
+
+ get elt() {
+ return this._folder.elt;
+ },
+ set elt(val) {
+ return this._folder.elt = val;
+ },
+
+ get openTimer() {
+ return this._folder.openTimer;
+ },
+ set openTimer(val) {
+ return this._folder.openTimer = val;
+ },
+
+ get hoverTime() {
+ return this._folder.hoverTime;
+ },
+ set hoverTime(val) {
+ return this._folder.hoverTime = val;
+ },
+
+ get closeTimer() {
+ return this._folder.closeTimer;
+ },
+ set closeTimer(val) {
+ return this._folder.closeTimer = val;
+ },
+
+ get closeMenuTimer() {
+ return this._closeMenuTimer;
+ },
+ set closeMenuTimer(val) {
+ return this._closeMenuTimer = val;
+ },
+
+ setTimer: function OF__setTimer(aTime) {
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ notify: function OF__notify(aTimer) {
+ // Function to process all timer notifications.
+
+ if (aTimer == this._folder.openTimer) {
+ // Timer to open a submenu that's being dragged over.
+ this._folder.elt.lastChild.setAttribute("autoopened", "true");
+ this._folder.elt.lastChild.showPopup(this._folder.elt);
+ this._folder.openTimer = null;
+ }
+
+ else if (aTimer == this._folder.closeTimer) {
+ // Timer to close a submenu that's been dragged off of.
+ // Only close the submenu if the mouse isn't being dragged over any
+ // of its child menus.
+ var draggingOverChild = PlacesControllerDragHelper
+ .draggingOverChildNode(this._folder.elt);
+ if (draggingOverChild)
+ this._folder.elt = null;
+ this.clear();
+
+ // Close any parent folders which aren't being dragged over.
+ // (This is necessary because of the above code that keeps a folder
+ // open while its children are being dragged over.)
+ if (!draggingOverChild)
+ this.closeParentMenus();
+ }
+
+ else if (aTimer == this.closeMenuTimer) {
+ // Timer to close this menu after the drag exit.
+ var popup = this._self;
+ // if we are no more dragging we can leave the menu open to allow
+ // for better D&D bookmark organization
+ if (PlacesControllerDragHelper.getSession() &&
+ !PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) {
+ popup.hidePopup();
+ // Close any parent menus that aren't being dragged over;
+ // otherwise they'll stay open because they couldn't close
+ // while this menu was being dragged over.
+ this.closeParentMenus();
+ }
+ this._closeMenuTimer = null;
+ }
+ },
+
+ // Helper function to close all parent menus of this menu,
+ // as long as none of the parent's children are currently being
+ // dragged over.
+ closeParentMenus: function OF__closeParentMenus() {
+ var popup = this._self;
+ var parent = popup.parentNode;
+ while (parent) {
+ if (parent.localName == "menupopup" && parent._placesNode) {
+ if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode))
+ break;
+ parent.hidePopup();
+ }
+ parent = parent.parentNode;
+ }
+ },
+
+ // The mouse is no longer dragging over the stored menubutton.
+ // Close the menubutton, clear out drag styles, and clear all
+ // timers for opening/closing it.
+ clear: function OF__clear() {
+ if (this._folder.elt && this._folder.elt.lastChild) {
+ if (!this._folder.elt.lastChild.hasAttribute("dragover"))
+ this._folder.elt.lastChild.hidePopup();
+ // remove menuactive style
+ this._folder.elt.removeAttribute("_moz-menuactive");
+ this._folder.elt = null;
+ }
+ if (this._folder.openTimer) {
+ this._folder.openTimer.cancel();
+ this._folder.openTimer = null;
+ }
+ if (this._folder.closeTimer) {
+ this._folder.closeTimer.cancel();
+ this._folder.closeTimer = null;
+ }
+ }
+ })]]></field>
+
+ <method name="_cleanupDragDetails">
+ <body><![CDATA[
+ // Called on dragend and drop.
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this._rootView._draggedElt = null;
+ this.removeAttribute("dragover");
+ this.removeAttribute("dragstart");
+ this._indicatorBar.hidden = true;
+ ]]></body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="DOMMenuItemActive"><![CDATA[
+ let elt = event.target;
+ if (elt.parentNode != this)
+ return;
+
+ if (this.AppConstants.platform === "macosx") {
+ // XXX: The following check is a temporary hack until bug 420033 is
+ // resolved.
+ let parentElt = elt.parent;
+ while (parentElt) {
+ if (parentElt.id == "bookmarksMenuPopup" ||
+ parentElt.id == "goPopup")
+ return;
+
+ parentElt = parentElt.parentNode;
+ }
+ }
+
+ if (window.XULBrowserWindow) {
+ let elt = event.target;
+ let placesNode = elt._placesNode;
+
+ var linkURI;
+ if (placesNode && PlacesUtils.nodeIsURI(placesNode))
+ linkURI = placesNode.uri;
+ else if (elt.hasAttribute("targetURI"))
+ linkURI = elt.getAttribute("targetURI");
+
+ if (linkURI)
+ window.XULBrowserWindow.setOverLink(linkURI, null);
+ }
+ ]]></handler>
+
+ <handler event="DOMMenuItemInactive"><![CDATA[
+ let elt = event.target;
+ if (elt.parentNode != this)
+ return;
+
+ if (window.XULBrowserWindow)
+ window.XULBrowserWindow.setOverLink("", null);
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ let elt = event.target;
+ if (!elt._placesNode)
+ return;
+
+ let draggedElt = elt._placesNode;
+
+ // Force a copy action if parent node is a query or we are dragging a
+ // not-removable node.
+ if (!PlacesControllerDragHelper.canMoveNode(draggedElt, elt))
+ event.dataTransfer.effectAllowed = "copyLink";
+
+ // Activate the view and cache the dragged element.
+ this._rootView._draggedElt = draggedElt;
+ this._rootView.controller.setDataTransfer(event);
+ this.setAttribute("dragstart", "true");
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = event.target;
+
+ let dropPoint = this._getDropPoint(event);
+ if (dropPoint && dropPoint.ip) {
+ PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer)
+ .then(null, Components.utils.reportError);
+ event.preventDefault();
+ }
+
+ this._cleanupDragDetails();
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = event.target;
+ let dt = event.dataTransfer;
+
+ let dropPoint = this._getDropPoint(event);
+ if (!dropPoint || !dropPoint.ip ||
+ !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
+ this._indicatorBar.hidden = true;
+ event.stopPropagation();
+ return;
+ }
+
+ // Mark this popup as being dragged over.
+ this.setAttribute("dragover", "true");
+
+ if (dropPoint.folderElt) {
+ // We are dragging over a folder.
+ // _overFolder should take the care of opening it on a timer.
+ if (this._overFolder.elt &&
+ this._overFolder.elt != dropPoint.folderElt) {
+ // We are dragging over a new folder, let's clear old values
+ this._overFolder.clear();
+ }
+ if (!this._overFolder.elt) {
+ this._overFolder.elt = dropPoint.folderElt;
+ // Create the timer to open this folder.
+ this._overFolder.openTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+ // Since we are dropping into a folder set the corresponding style.
+ dropPoint.folderElt.setAttribute("_moz-menuactive", true);
+ }
+ else {
+ // We are not dragging over a folder.
+ // Clear out old _overFolder information.
+ this._overFolder.clear();
+ }
+
+ // Autoscroll the popup strip if we drag over the scroll buttons.
+ let anonid = event.originalTarget.getAttribute('anonid');
+ let scrollDir = 0;
+ if (anonid == "scrollbutton-up") {
+ scrollDir = -1;
+ } else if (anonid == "scrollbutton-down") {
+ scrollDir = 1;
+ }
+ if (scrollDir != 0) {
+ this._scrollBox.scrollByIndex(scrollDir, false);
+ }
+
+ // Check if we should hide the drop indicator for this target.
+ if (dropPoint.folderElt || this._hideDropIndicator(event)) {
+ this._indicatorBar.hidden = true;
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ // We should display the drop indicator relative to the arrowscrollbox.
+ let sbo = this._scrollBox.scrollBoxObject;
+ let newMarginTop = 0;
+ if (scrollDir == 0) {
+ let elt = this.firstChild;
+ while (elt && event.screenY > elt.boxObject.screenY +
+ elt.boxObject.height / 2)
+ elt = elt.nextSibling;
+ newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY :
+ sbo.height;
+ }
+ else if (scrollDir == 1)
+ newMarginTop = sbo.height;
+
+ // Set the new marginTop based on arrowscrollbox.
+ newMarginTop += sbo.y - this._scrollBox.boxObject.y;
+ this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px";
+ this._indicatorBar.hidden = false;
+
+ event.preventDefault();
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragexit"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this.removeAttribute("dragover");
+
+ // If we have not moved to a valid new target clear the drop indicator
+ // this happens when moving out of the popup.
+ let target = event.relatedTarget;
+ if (!target || !this.contains(target))
+ this._indicatorBar.hidden = true;
+
+ // Close any folder being hovered over
+ if (this._overFolder.elt) {
+ this._overFolder.closeTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ // The autoopened attribute is set when this folder was automatically
+ // opened after the user dragged over it. If this attribute is set,
+ // auto-close the folder on drag exit.
+ // We should also try to close this popup if the drag has started
+ // from here, the timer will check if we are dragging over a child.
+ if (this.hasAttribute("autoopened") ||
+ this.hasAttribute("dragstart")) {
+ this._overFolder.closeMenuTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ this._cleanupDragDetails();
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+ <!-- Most of this is copied from the arrowpanel binding in popup.xml -->
+ <binding id="places-popup-arrow"
+ extends="chrome://browser/content/places/menu.xml#places-popup-base">
+ <content flip="both" side="top" position="bottomcenter topright">
+ <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">
+ <xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
+ <xul:image class="menupopup-drop-indicator" mousethrough="always"/>
+ </xul:vbox>
+ <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
+ smoothscroll="false">
+ <children/>
+ </xul:arrowscrollbox>
+ </xul:box>
+ </xul:vbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this.style.pointerEvents = 'none';
+ ]]></constructor>
+ <method name="adjustArrowPosition">
+ <body><![CDATA[
+ var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
+
+ var anchor = this.anchorNode;
+ if (!anchor) {
+ arrow.hidden = true;
+ 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 this panel has a "sliding" arrow, we may have previously set margins...
+ arrowbox.style.removeProperty("transform");
+ 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");
+ }
+ }
+
+ arrow.hidden = false;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing" phase="target"><![CDATA[
+ this.adjustArrowPosition();
+ this.setAttribute("animate", "open");
+ ]]></handler>
+ <handler event="popupshown" phase="target"><![CDATA[
+ this.setAttribute("panelopen", "true");
+ let disablePointerEvents;
+ if (!this.hasAttribute("disablepointereventsfortransition")) {
+ let container = document.getAnonymousElementByAttribute(this, "anonid", "container");
+ let cs = getComputedStyle(container);
+ let transitionProp = cs.transitionProperty;
+ let transitionTime = parseFloat(cs.transitionDuration);
+ disablePointerEvents = (transitionProp.includes("transform") ||
+ transitionProp == "all") &&
+ transitionTime > 0;
+ this.setAttribute("disablepointereventsfortransition", disablePointerEvents);
+ } else {
+ disablePointerEvents = this.getAttribute("disablepointereventsfortransition") == "true";
+ }
+ if (!disablePointerEvents) {
+ this.style.removeProperty("pointer-events");
+ }
+ ]]></handler>
+ <handler event="transitionend"><![CDATA[
+ if (event.originalTarget.getAttribute("anonid") == "container" &&
+ event.propertyName == "transform") {
+ this.style.removeProperty("pointer-events");
+ }
+ ]]></handler>
+ <handler event="popuphiding" phase="target"><![CDATA[
+ this.setAttribute("animate", "cancel");
+ ]]></handler>
+ <handler event="popuphidden" phase="target"><![CDATA[
+ this.removeAttribute("panelopen");
+ if (this.getAttribute("disablepointereventsfortransition") == "true") {
+ this.style.pointerEvents = 'none';
+ }
+ this.removeAttribute("animate");
+ ]]></handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/browser/components/places/content/moveBookmarks.js b/browser/components/places/content/moveBookmarks.js
new file mode 100644
index 000000000..5bfdce56e
--- /dev/null
+++ b/browser/components/places/content/moveBookmarks.js
@@ -0,0 +1,65 @@
+/* -*- 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 gMoveBookmarksDialog = {
+ _nodes: null,
+
+ _foldersTree: null,
+ get foldersTree() {
+ if (!this._foldersTree)
+ this._foldersTree = document.getElementById("foldersTree");
+
+ return this._foldersTree;
+ },
+
+ init: function() {
+ this._nodes = window.arguments[0];
+
+ this.foldersTree.place =
+ "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
+ PlacesUIUtils.allBookmarksFolderId;
+ },
+
+ onOK: function MBD_onOK(aEvent) {
+ let selectedNode = this.foldersTree.selectedNode;
+ let selectedFolderId = PlacesUtils.getConcreteItemId(selectedNode);
+
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let transactions = [];
+ for (var i=0; i < this._nodes.length; i++) {
+ // Nothing to do if the node is already under the selected folder
+ if (this._nodes[i].parent.itemId == selectedFolderId)
+ continue;
+
+ let txn = new PlacesMoveItemTransaction(this._nodes[i].itemId,
+ selectedFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ transactions.push(txn);
+ }
+ if (transactions.length != 0) {
+ let txn = new PlacesAggregatedTransaction("Move Items", transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ return;
+ }
+
+ PlacesTransactions.batch(function* () {
+ let newParentGuid = yield PlacesUtils.promiseItemGuid(selectedFolderId);
+ for (let node of this._nodes) {
+ // Nothing to do if the node is already under the selected folder.
+ if (node.parent.itemId == selectedFolderId)
+ continue;
+ yield PlacesTransactions.Move({ guid: node.bookmarkGuid
+ , newParentGuid }).transact();
+ }
+ }.bind(this)).then(null, Components.utils.reportError);
+ },
+
+ newFolder: function MBD_newFolder() {
+ // The command is disabled when the tree is not focused
+ this.foldersTree.focus();
+ goDoCommand("placesCmd_new:folder");
+ }
+};
diff --git a/browser/components/places/content/moveBookmarks.xul b/browser/components/places/content/moveBookmarks.xul
new file mode 100644
index 000000000..b6e75f3da
--- /dev/null
+++ b/browser/components/places/content/moveBookmarks.xul
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % moveBookmarksDTD SYSTEM "chrome://browser/locale/places/moveBookmarks.dtd">
+ %moveBookmarksDTD;
+]>
+
+<dialog id="moveBookmarkDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ ondialogaccept="return gMoveBookmarksDialog.onOK(event);"
+ title="&window.title;"
+ onload="gMoveBookmarksDialog.init();"
+ style="&window.style;"
+ screenX="24"
+ screenY="24"
+ persist="screenX screenY width height">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/places/moveBookmarks.js"/>
+
+ <hbox flex="1">
+ <label id="movetolabel" value="&moveTo.label;" control="foldersTree"/>
+ <hbox flex="1">
+ <tree id="foldersTree"
+ class="placesTree"
+ flex="1"
+ type="places"
+ seltype="single"
+ hidecolumnpicker="true">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="placesListChildren" view="placesList" flex="1"/>
+ </tree>
+ <vbox>
+ <button id="newFolderButton"
+ label="&newFolderButton.label;"
+ accesskey="&newFolderButton.accesskey;"
+ oncommand="gMoveBookmarksDialog.newFolder();"/>
+ </vbox>
+ </hbox>
+ </hbox>
+</dialog>
diff --git a/browser/components/places/content/organizer.css b/browser/components/places/content/organizer.css
new file mode 100644
index 000000000..47b1832c1
--- /dev/null
+++ b/browser/components/places/content/organizer.css
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#searchFilter {
+ width: 23em;
+}
diff --git a/browser/components/places/content/places.css b/browser/components/places/content/places.css
new file mode 100644
index 000000000..de3cc91d8
--- /dev/null
+++ b/browser/components/places/content/places.css
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+tree[type="places"] {
+ -moz-binding: url("chrome://browser/content/places/tree.xml#places-tree");
+}
+
+.toolbar-drop-indicator {
+ position: relative;
+ z-index: 1;
+}
+
+menupopup[placespopup="true"] {
+ -moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-base");
+}
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ #bookmarksChildren,
+ .sidebar-placesTreechildren,
+ .placesTree > treechildren {
+ image-rendering: -moz-crisp-edges;
+ }
+}
diff --git a/browser/components/places/content/places.js b/browser/components/places/content/places.js
new file mode 100644
index 000000000..aa43b20e6
--- /dev/null
+++ b/browser/components/places/content/places.js
@@ -0,0 +1,1405 @@
+/* -*- 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/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/TelemetryStopwatch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "MigrationUtils",
+ "resource:///modules/MigrationUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
+ "resource://gre/modules/BookmarkJSONUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
+ "resource://gre/modules/PlacesBackups.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+ "resource://gre/modules/DownloadUtils.jsm");
+
+const RESTORE_FILEPICKER_FILTER_EXT = "*.json;*.jsonlz4";
+const HISTORY_LIBRARY_SEARCH_TELEMETRY = "PLACES_HISTORY_LIBRARY_SEARCH_TIME_MS";
+
+var PlacesOrganizer = {
+ _places: null,
+
+ // IDs of fields from editBookmarkOverlay that should be hidden when infoBox
+ // is minimal. IDs should be kept in sync with the IDs of the elements
+ // observing additionalInfoBroadcaster.
+ _additionalInfoFields: [
+ "editBMPanel_descriptionRow",
+ "editBMPanel_loadInSidebarCheckbox",
+ "editBMPanel_keywordRow",
+ ],
+
+ _initFolderTree: function() {
+ var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
+ this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
+ },
+
+ selectLeftPaneQuery: function PO_selectLeftPaneQuery(aQueryName) {
+ var itemId = PlacesUIUtils.leftPaneQueries[aQueryName];
+ this._places.selectItems([itemId]);
+ // Forcefully expand all-bookmarks
+ if (aQueryName == "AllBookmarks" || aQueryName == "History")
+ PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+ },
+
+ /**
+ * Opens a given hierarchy in the left pane, stopping at the last reachable
+ * container.
+ *
+ * @param aHierarchy A single container or an array of containers, sorted from
+ * the outmost to the innermost in the hierarchy. Each
+ * container may be either an item id, a Places URI string,
+ * or a named query.
+ * @see PlacesUIUtils.leftPaneQueries for supported named queries.
+ */
+ selectLeftPaneContainerByHierarchy:
+ function PO_selectLeftPaneContainerByHierarchy(aHierarchy) {
+ if (!aHierarchy)
+ throw new Error("Invalid containers hierarchy");
+ let hierarchy = [].concat(aHierarchy);
+ let selectWasSuppressed = this._places.view.selection.selectEventsSuppressed;
+ if (!selectWasSuppressed)
+ this._places.view.selection.selectEventsSuppressed = true;
+ try {
+ for (let container of hierarchy) {
+ switch (typeof container) {
+ case "number":
+ this._places.selectItems([container], false);
+ break;
+ case "string":
+ if (container.substr(0, 6) == "place:")
+ this._places.selectPlaceURI(container);
+ else if (container in PlacesUIUtils.leftPaneQueries)
+ this.selectLeftPaneQuery(container);
+ else
+ throw new Error("Invalid container found: " + container);
+ break;
+ default:
+ throw new Error("Invalid container type found: " + container);
+ }
+ PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+ }
+ } finally {
+ if (!selectWasSuppressed)
+ this._places.view.selection.selectEventsSuppressed = false;
+ }
+ },
+
+ init: function PO_init() {
+ ContentArea.init();
+
+ this._places = document.getElementById("placesList");
+ this._initFolderTree();
+
+ var leftPaneSelection = "AllBookmarks"; // default to all-bookmarks
+ if (window.arguments && window.arguments[0])
+ leftPaneSelection = window.arguments[0];
+
+ this.selectLeftPaneContainerByHierarchy(leftPaneSelection);
+ if (leftPaneSelection === "History") {
+ let historyNode = this._places.selectedNode;
+ if (historyNode.childCount > 0)
+ this._places.selectNode(historyNode.getChild(0));
+ }
+
+ // clear the back-stack
+ this._backHistory.splice(0, this._backHistory.length);
+ document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
+
+ // Set up the search UI.
+ PlacesSearchBox.init();
+
+ window.addEventListener("AppCommand", this, true);
+
+ if (AppConstants.platform === "macosx") {
+ // 1. Map Edit->Find command to OrganizerCommand_find:all. Need to map
+ // both the menuitem and the Find key.
+ let findMenuItem = document.getElementById("menu_find");
+ findMenuItem.setAttribute("command", "OrganizerCommand_find:all");
+ let findKey = document.getElementById("key_find");
+ findKey.setAttribute("command", "OrganizerCommand_find:all");
+
+ // 2. Disable some keybindings from browser.xul
+ let elements = ["cmd_handleBackspace", "cmd_handleShiftBackspace"];
+ for (let i = 0; i < elements.length; i++) {
+ document.getElementById(elements[i]).setAttribute("disabled", "true");
+ }
+ }
+
+ // remove the "Properties" context-menu item, we've our own details pane
+ document.getElementById("placesContext")
+ .removeChild(document.getElementById("placesContext_show:info"));
+
+ ContentArea.focus();
+ },
+
+ QueryInterface: function PO_QueryInterface(aIID) {
+ if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ handleEvent: function PO_handleEvent(aEvent) {
+ if (aEvent.type != "AppCommand")
+ return;
+
+ aEvent.stopPropagation();
+ switch (aEvent.command) {
+ case "Back":
+ if (this._backHistory.length > 0)
+ this.back();
+ break;
+ case "Forward":
+ if (this._forwardHistory.length > 0)
+ this.forward();
+ break;
+ case "Search":
+ PlacesSearchBox.findAll();
+ break;
+ }
+ },
+
+ destroy: function PO_destroy() {
+ },
+
+ _location: null,
+ get location() {
+ return this._location;
+ },
+
+ set location(aLocation) {
+ if (!aLocation || this._location == aLocation)
+ return aLocation;
+
+ if (this.location) {
+ this._backHistory.unshift(this.location);
+ this._forwardHistory.splice(0, this._forwardHistory.length);
+ }
+
+ this._location = aLocation;
+ this._places.selectPlaceURI(aLocation);
+
+ if (!this._places.hasSelection) {
+ // If no node was found for the given place: uri, just load it directly
+ ContentArea.currentPlace = aLocation;
+ }
+ this.updateDetailsPane();
+
+ // update navigation commands
+ if (this._backHistory.length == 0)
+ document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
+ else
+ document.getElementById("OrganizerCommand:Back").removeAttribute("disabled");
+ if (this._forwardHistory.length == 0)
+ document.getElementById("OrganizerCommand:Forward").setAttribute("disabled", true);
+ else
+ document.getElementById("OrganizerCommand:Forward").removeAttribute("disabled");
+
+ return aLocation;
+ },
+
+ _backHistory: [],
+ _forwardHistory: [],
+
+ back: function PO_back() {
+ this._forwardHistory.unshift(this.location);
+ var historyEntry = this._backHistory.shift();
+ this._location = null;
+ this.location = historyEntry;
+ },
+ forward: function PO_forward() {
+ this._backHistory.unshift(this.location);
+ var historyEntry = this._forwardHistory.shift();
+ this._location = null;
+ this.location = historyEntry;
+ },
+
+ /**
+ * Called when a place folder is selected in the left pane.
+ * @param resetSearchBox
+ * true if the search box should also be reset, false otherwise.
+ * The search box should be reset when a new folder in the left
+ * pane is selected; the search scope and text need to be cleared in
+ * preparation for the new folder. Note that if the user manually
+ * resets the search box, either by clicking its reset button or by
+ * deleting its text, this will be false.
+ */
+ _cachedLeftPaneSelectedURI: null,
+ onPlaceSelected: function PO_onPlaceSelected(resetSearchBox) {
+ // Don't change the right-hand pane contents when there's no selection.
+ if (!this._places.hasSelection)
+ return;
+
+ var node = this._places.selectedNode;
+ var queries = PlacesUtils.asQuery(node).getQueries();
+
+ // Items are only excluded on the left pane.
+ var options = node.queryOptions.clone();
+ options.excludeItems = false;
+ var placeURI = PlacesUtils.history.queriesToQueryString(queries,
+ queries.length,
+ options);
+
+ // If either the place of the content tree in the right pane has changed or
+ // the user cleared the search box, update the place, hide the search UI,
+ // and update the back/forward buttons by setting location.
+ if (ContentArea.currentPlace != placeURI || !resetSearchBox) {
+ ContentArea.currentPlace = placeURI;
+ this.location = node.uri;
+ }
+
+ // When we invalidate a container we use suppressSelectionEvent, when it is
+ // unset a select event is fired, in many cases the selection did not really
+ // change, so we should check for it, and return early in such a case. Note
+ // that we cannot return any earlier than this point, because when
+ // !resetSearchBox, we need to update location and hide the UI as above,
+ // even though the selection has not changed.
+ if (node.uri == this._cachedLeftPaneSelectedURI)
+ return;
+ this._cachedLeftPaneSelectedURI = node.uri;
+
+ // At this point, resetSearchBox is true, because the left pane selection
+ // has changed; otherwise we would have returned earlier.
+
+ PlacesSearchBox.searchFilter.reset();
+ this._setSearchScopeForNode(node);
+ this.updateDetailsPane();
+ },
+
+ /**
+ * Sets the search scope based on aNode's properties.
+ * @param aNode
+ * the node to set up scope from
+ */
+ _setSearchScopeForNode: function PO__setScopeForNode(aNode) {
+ let itemId = aNode.itemId;
+
+ if (PlacesUtils.nodeIsHistoryContainer(aNode) ||
+ itemId == PlacesUIUtils.leftPaneQueries["History"]) {
+ PlacesQueryBuilder.setScope("history");
+ }
+ else if (itemId == PlacesUIUtils.leftPaneQueries["Downloads"]) {
+ PlacesQueryBuilder.setScope("downloads");
+ }
+ else {
+ // Default to All Bookmarks for all other nodes, per bug 469437.
+ PlacesQueryBuilder.setScope("bookmarks");
+ }
+ },
+
+ /**
+ * Handle clicks on the places list.
+ * Single Left click, right click or modified click do not result in any
+ * special action, since they're related to selection.
+ * @param aEvent
+ * The mouse event.
+ */
+ onPlacesListClick: function PO_onPlacesListClick(aEvent) {
+ // Only handle clicks on tree children.
+ if (aEvent.target.localName != "treechildren")
+ return;
+
+ let node = this._places.selectedNode;
+ if (node) {
+ let middleClick = aEvent.button == 1 && aEvent.detail == 1;
+ if (middleClick && PlacesUtils.nodeIsContainer(node)) {
+ // The command execution function will take care of seeing if the
+ // selection is a folder or a different container type, and will
+ // load its contents in tabs.
+ PlacesUIUtils.openContainerNodeInTabs(selectedNode, aEvent, this._places);
+ }
+ }
+ },
+
+ /**
+ * Handle focus changes on the places list and the current content view.
+ */
+ updateDetailsPane: function PO_updateDetailsPane() {
+ if (!ContentArea.currentViewOptions.showDetailsPane)
+ return;
+ let view = PlacesUIUtils.getViewForNode(document.activeElement);
+ if (view) {
+ let selectedNodes = view.selectedNode ?
+ [view.selectedNode] : view.selectedNodes;
+ this._fillDetailsPane(selectedNodes);
+ }
+ },
+
+ openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) {
+ if (aContainer.itemId != -1) {
+ PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+ this._places.selectItems([aContainer.itemId], false);
+ }
+ else if (PlacesUtils.nodeIsQuery(aContainer)) {
+ this._places.selectPlaceURI(aContainer.uri);
+ }
+ },
+
+ /**
+ * Returns the options associated with the query currently loaded in the
+ * main places pane.
+ */
+ getCurrentOptions: function PO_getCurrentOptions() {
+ return PlacesUtils.asQuery(ContentArea.currentView.result.root).queryOptions;
+ },
+
+ /**
+ * Returns the queries associated with the query currently loaded in the
+ * main places pane.
+ */
+ getCurrentQueries: function PO_getCurrentQueries() {
+ return PlacesUtils.asQuery(ContentArea.currentView.result.root).getQueries();
+ },
+
+ /**
+ * Show the migration wizard for importing passwords,
+ * cookies, history, preferences, and bookmarks.
+ */
+ importFromBrowser: function PO_importFromBrowser() {
+ // We pass in the type of source we're using for use in telemetry:
+ MigrationUtils.showMigrationWizard(window, [MigrationUtils.MIGRATION_ENTRYPOINT_PLACES]);
+ },
+
+ /**
+ * Open a file-picker and import the selected file into the bookmarks store
+ */
+ importFromFile: function PO_importFromFile() {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel && fp.fileURL) {
+ Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
+ BookmarkHTMLUtils.importFromURL(fp.fileURL.spec, false)
+ .then(null, Components.utils.reportError);
+ }
+ };
+
+ fp.init(window, PlacesUIUtils.getString("SelectImport"),
+ Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+ fp.open(fpCallback);
+ },
+
+ /**
+ * Allows simple exporting of bookmarks.
+ */
+ exportBookmarks: function PO_exportBookmarks() {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel) {
+ Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
+ BookmarkHTMLUtils.exportToFile(fp.file.path)
+ .then(null, Components.utils.reportError);
+ }
+ };
+
+ fp.init(window, PlacesUIUtils.getString("EnterExport"),
+ Ci.nsIFilePicker.modeSave);
+ fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+ fp.defaultString = "bookmarks.html";
+ fp.open(fpCallback);
+ },
+
+ /**
+ * Populates the restore menu with the dates of the backups available.
+ */
+ populateRestoreMenu: function PO_populateRestoreMenu() {
+ let restorePopup = document.getElementById("fileRestorePopup");
+
+ const locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric' };
+ let dateFormatter = new Intl.DateTimeFormat(locale, dtOptions);
+
+ // Remove existing menu items. Last item is the restoreFromFile item.
+ while (restorePopup.childNodes.length > 1)
+ restorePopup.removeChild(restorePopup.firstChild);
+
+ Task.spawn(function* () {
+ let backupFiles = yield PlacesBackups.getBackupFiles();
+ if (backupFiles.length == 0)
+ return;
+
+ // Populate menu with backups.
+ for (let i = 0; i < backupFiles.length; i++) {
+ let fileSize = (yield OS.File.stat(backupFiles[i])).size;
+ let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
+ let sizeString = PlacesUtils.getFormattedString("backupFileSizeText",
+ [size, unit]);
+ let sizeInfo;
+ let bookmarkCount = PlacesBackups.getBookmarkCountForFile(backupFiles[i]);
+ if (bookmarkCount != null) {
+ sizeInfo = " (" + sizeString + " - " +
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ bookmarkCount,
+ [bookmarkCount]) +
+ ")";
+ } else {
+ sizeInfo = " (" + sizeString + ")";
+ }
+
+ let backupDate = PlacesBackups.getDateForFile(backupFiles[i]);
+ let m = restorePopup.insertBefore(document.createElement("menuitem"),
+ document.getElementById("restoreFromFile"));
+ m.setAttribute("label", dateFormatter.format(backupDate) + sizeInfo);
+ m.setAttribute("value", OS.Path.basename(backupFiles[i]));
+ m.setAttribute("oncommand",
+ "PlacesOrganizer.onRestoreMenuItemClick(this);");
+ }
+
+ // Add the restoreFromFile item.
+ restorePopup.insertBefore(document.createElement("menuseparator"),
+ document.getElementById("restoreFromFile"));
+ });
+ },
+
+ /**
+ * Called when a menuitem is selected from the restore menu.
+ */
+ onRestoreMenuItemClick: Task.async(function* (aMenuItem) {
+ let backupName = aMenuItem.getAttribute("value");
+ let backupFilePaths = yield PlacesBackups.getBackupFiles();
+ for (let backupFilePath of backupFilePaths) {
+ if (OS.Path.basename(backupFilePath) == backupName) {
+ PlacesOrganizer.restoreBookmarksFromFile(backupFilePath);
+ break;
+ }
+ }
+ }),
+
+ /**
+ * Called when 'Choose File...' is selected from the restore menu.
+ * Prompts for a file and restores bookmarks to those in the file.
+ */
+ onRestoreBookmarksFromFile: function PO_onRestoreBookmarksFromFile() {
+ let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ let backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel) {
+ this.restoreBookmarksFromFile(fp.file.path);
+ }
+ }.bind(this);
+
+ fp.init(window, PlacesUIUtils.getString("bookmarksRestoreTitle"),
+ Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
+ RESTORE_FILEPICKER_FILTER_EXT);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.displayDirectory = backupsDir;
+ fp.open(fpCallback);
+ },
+
+ /**
+ * Restores bookmarks from a JSON file.
+ */
+ restoreBookmarksFromFile: function PO_restoreBookmarksFromFile(aFilePath) {
+ // check file extension
+ if (!aFilePath.toLowerCase().endsWith("json") &&
+ !aFilePath.toLowerCase().endsWith("jsonlz4")) {
+ this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreFormatError"));
+ return;
+ }
+
+ // confirm ok to delete existing bookmarks
+ var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ if (!prompts.confirm(null,
+ PlacesUIUtils.getString("bookmarksRestoreAlertTitle"),
+ PlacesUIUtils.getString("bookmarksRestoreAlert")))
+ return;
+
+ Task.spawn(function* () {
+ try {
+ yield BookmarkJSONUtils.importFromFile(aFilePath, true);
+ } catch (ex) {
+ PlacesOrganizer._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreParseError"));
+ }
+ });
+ },
+
+ _showErrorAlert: function PO__showErrorAlert(aMsg) {
+ var brandShortName = document.getElementById("brandStrings").
+ getString("brandShortName");
+
+ Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService).
+ alert(window, brandShortName, aMsg);
+ },
+
+ /**
+ * Backup bookmarks to desktop, auto-generate a filename with a date.
+ * The file is a JSON serialization of bookmarks, tags and any annotations
+ * of those items.
+ */
+ backupBookmarks: function PO_backupBookmarks() {
+ let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ let backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel) {
+ // There is no OS.File version of the filepicker yet (Bug 937812).
+ PlacesBackups.saveBookmarksToJSONFile(fp.file.path);
+ }
+ };
+
+ fp.init(window, PlacesUIUtils.getString("bookmarksBackupTitle"),
+ Ci.nsIFilePicker.modeSave);
+ fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
+ RESTORE_FILEPICKER_FILTER_EXT);
+ fp.defaultString = PlacesBackups.getFilenameForDate();
+ fp.defaultExtension = "json";
+ fp.displayDirectory = backupsDir;
+ fp.open(fpCallback);
+ },
+
+ _detectAndSetDetailsPaneMinimalState:
+ function PO__detectAndSetDetailsPaneMinimalState(aNode) {
+ /**
+ * The details of simple folder-items (as opposed to livemarks) or the
+ * of livemark-children are not likely to fill the infoBox anyway,
+ * thus we remove the "More/Less" button and show all details.
+ *
+ * the wasminimal attribute here is used to persist the "more/less"
+ * state in a bookmark->folder->bookmark scenario.
+ */
+ var infoBox = document.getElementById("infoBox");
+ var infoBoxExpanderWrapper = document.getElementById("infoBoxExpanderWrapper");
+ var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster");
+
+ if (!aNode) {
+ infoBoxExpanderWrapper.hidden = true;
+ return;
+ }
+ if (aNode.itemId != -1 &&
+ PlacesUtils.nodeIsFolder(aNode) && !aNode._feedURI) {
+ if (infoBox.getAttribute("minimal") == "true")
+ infoBox.setAttribute("wasminimal", "true");
+ infoBox.removeAttribute("minimal");
+ infoBoxExpanderWrapper.hidden = true;
+ }
+ else {
+ if (infoBox.getAttribute("wasminimal") == "true")
+ infoBox.setAttribute("minimal", "true");
+ infoBox.removeAttribute("wasminimal");
+ infoBoxExpanderWrapper.hidden =
+ this._additionalInfoFields.every(id =>
+ document.getElementById(id).collapsed);
+ }
+ additionalInfoBroadcaster.hidden = infoBox.getAttribute("minimal") == "true";
+ },
+
+ // NOT YET USED
+ updateThumbnailProportions: function PO_updateThumbnailProportions() {
+ var previewBox = document.getElementById("previewBox");
+ var canvas = document.getElementById("itemThumbnail");
+ var height = previewBox.boxObject.height;
+ var width = height * (screen.width / screen.height);
+ canvas.width = width;
+ canvas.height = height;
+ },
+
+ _fillDetailsPane: function PO__fillDetailsPane(aNodeList) {
+ var infoBox = document.getElementById("infoBox");
+ var detailsDeck = document.getElementById("detailsDeck");
+
+ // Make sure the infoBox UI is visible if we need to use it, we hide it
+ // below when we don't.
+ infoBox.hidden = false;
+ let selectedNode = aNodeList.length == 1 ? aNodeList[0] : null;
+
+ // If a textbox within a panel is focused, force-blur it so its contents
+ // are saved
+ if (gEditItemOverlay.itemId != -1) {
+ var focusedElement = document.commandDispatcher.focusedElement;
+ if ((focusedElement instanceof HTMLInputElement ||
+ focusedElement instanceof HTMLTextAreaElement) &&
+ /^editBMPanel.*/.test(focusedElement.parentNode.parentNode.id))
+ focusedElement.blur();
+
+ // don't update the panel if we are already editing this node unless we're
+ // in multi-edit mode
+ if (selectedNode) {
+ let concreteId = PlacesUtils.getConcreteItemId(selectedNode);
+ var nodeIsSame = gEditItemOverlay.itemId == selectedNode.itemId ||
+ gEditItemOverlay.itemId == concreteId ||
+ (selectedNode.itemId == -1 && gEditItemOverlay.uri &&
+ gEditItemOverlay.uri == selectedNode.uri);
+ if (nodeIsSame && detailsDeck.selectedIndex == 1 &&
+ !gEditItemOverlay.multiEdit)
+ return;
+ }
+ }
+
+ // Clean up the panel before initing it again.
+ gEditItemOverlay.uninitPanel(false);
+
+ if (selectedNode && !PlacesUtils.nodeIsSeparator(selectedNode)) {
+ detailsDeck.selectedIndex = 1;
+
+ gEditItemOverlay.initPanel({ node: selectedNode
+ , hiddenRows: ["folderPicker"] });
+
+ this._detectAndSetDetailsPaneMinimalState(selectedNode);
+ }
+ else if (!selectedNode && aNodeList[0]) {
+ if (aNodeList.every(PlacesUtils.nodeIsURI)) {
+ let uris = aNodeList.map(node => PlacesUtils._uri(node.uri));
+ detailsDeck.selectedIndex = 1;
+ gEditItemOverlay.initPanel({ uris
+ , hiddenRows: ["folderPicker",
+ "loadInSidebar",
+ "location",
+ "keyword",
+ "description",
+ "name"]});
+ this._detectAndSetDetailsPaneMinimalState(selectedNode);
+ }
+ else {
+ detailsDeck.selectedIndex = 0;
+ let selectItemDesc = document.getElementById("selectItemDescription");
+ let itemsCountLabel = document.getElementById("itemsCountText");
+ selectItemDesc.hidden = false;
+ itemsCountLabel.value =
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ aNodeList.length, [aNodeList.length]);
+ infoBox.hidden = true;
+ }
+ }
+ else {
+ detailsDeck.selectedIndex = 0;
+ infoBox.hidden = true;
+ let selectItemDesc = document.getElementById("selectItemDescription");
+ let itemsCountLabel = document.getElementById("itemsCountText");
+ let itemsCount = 0;
+ if (ContentArea.currentView.result) {
+ let rootNode = ContentArea.currentView.result.root;
+ if (rootNode.containerOpen)
+ itemsCount = rootNode.childCount;
+ }
+ if (itemsCount == 0) {
+ selectItemDesc.hidden = true;
+ itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.noItems");
+ }
+ else {
+ selectItemDesc.hidden = false;
+ itemsCountLabel.value =
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ itemsCount, [itemsCount]);
+ }
+ }
+ },
+
+ // NOT YET USED
+ _updateThumbnail: function PO__updateThumbnail() {
+ var bo = document.getElementById("previewBox").boxObject;
+ var width = bo.width;
+ var height = bo.height;
+
+ var canvas = document.getElementById("itemThumbnail");
+ var ctx = canvas.getContext('2d');
+ var notAvailableText = canvas.getAttribute("notavailabletext");
+ ctx.save();
+ ctx.fillStyle = "-moz-Dialog";
+ ctx.fillRect(0, 0, width, height);
+ ctx.translate(width/2, height/2);
+
+ ctx.fillStyle = "GrayText";
+ ctx.mozTextStyle = "12pt sans serif";
+ var len = ctx.mozMeasureText(notAvailableText);
+ ctx.translate(-len/2, 0);
+ ctx.mozDrawText(notAvailableText);
+ ctx.restore();
+ },
+
+ toggleAdditionalInfoFields: function PO_toggleAdditionalInfoFields() {
+ var infoBox = document.getElementById("infoBox");
+ var infoBoxExpander = document.getElementById("infoBoxExpander");
+ var infoBoxExpanderLabel = document.getElementById("infoBoxExpanderLabel");
+ var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster");
+
+ if (infoBox.getAttribute("minimal") == "true") {
+ infoBox.removeAttribute("minimal");
+ infoBoxExpanderLabel.value = infoBoxExpanderLabel.getAttribute("lesslabel");
+ infoBoxExpanderLabel.accessKey = infoBoxExpanderLabel.getAttribute("lessaccesskey");
+ infoBoxExpander.className = "expander-up";
+ additionalInfoBroadcaster.removeAttribute("hidden");
+ }
+ else {
+ infoBox.setAttribute("minimal", "true");
+ infoBoxExpanderLabel.value = infoBoxExpanderLabel.getAttribute("morelabel");
+ infoBoxExpanderLabel.accessKey = infoBoxExpanderLabel.getAttribute("moreaccesskey");
+ infoBoxExpander.className = "expander-down";
+ additionalInfoBroadcaster.setAttribute("hidden", "true");
+ }
+ },
+};
+
+/**
+ * A set of utilities relating to search within Bookmarks and History.
+ */
+var PlacesSearchBox = {
+
+ /**
+ * The Search text field
+ */
+ get searchFilter() {
+ return document.getElementById("searchFilter");
+ },
+
+ /**
+ * Folders to include when searching.
+ */
+ _folders: [],
+ get folders() {
+ if (this._folders.length == 0) {
+ this._folders.push(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.toolbarFolderId);
+ }
+ return this._folders;
+ },
+ set folders(aFolders) {
+ this._folders = aFolders;
+ return aFolders;
+ },
+
+ /**
+ * Run a search for the specified text, over the collection specified by
+ * the dropdown arrow. The default is all bookmarks, but can be
+ * localized to the active collection.
+ * @param filterString
+ * The text to search for.
+ */
+ search: function PSB_search(filterString) {
+ var PO = PlacesOrganizer;
+ // If the user empties the search box manually, reset it and load all
+ // contents of the current scope.
+ // XXX this might be to jumpy, maybe should search for "", so results
+ // are ungrouped, and search box not reset
+ if (filterString == "") {
+ PO.onPlaceSelected(false);
+ return;
+ }
+
+ let currentView = ContentArea.currentView;
+ let currentOptions = PO.getCurrentOptions();
+
+ // Search according to the current scope, which was set by
+ // PQB_setScope()
+ switch (PlacesSearchBox.filterCollection) {
+ case "bookmarks":
+ currentView.applyFilter(filterString, this.folders);
+ break;
+ case "history":
+ if (currentOptions.queryType != Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ var query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+ var options = currentOptions.clone();
+ // Make sure we're getting uri results.
+ options.resultType = currentOptions.RESULTS_AS_URI;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
+ options.includeHidden = true;
+ currentView.load([query], options);
+ }
+ else {
+ TelemetryStopwatch.start(HISTORY_LIBRARY_SEARCH_TELEMETRY);
+ currentView.applyFilter(filterString, null, true);
+ TelemetryStopwatch.finish(HISTORY_LIBRARY_SEARCH_TELEMETRY);
+ }
+ break;
+ case "downloads":
+ if (currentView == ContentTree.view) {
+ let query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+ query.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD], 1);
+ let options = currentOptions.clone();
+ // Make sure we're getting uri results.
+ options.resultType = currentOptions.RESULTS_AS_URI;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
+ options.includeHidden = true;
+ currentView.load([query], options);
+ }
+ else {
+ // The new downloads view doesn't use places for searching downloads.
+ currentView.searchTerm = filterString;
+ }
+ break;
+ default:
+ throw "Invalid filterCollection on search";
+ }
+
+ // Update the details panel
+ PlacesOrganizer.updateDetailsPane();
+ },
+
+ /**
+ * Finds across all history, downloads or all bookmarks.
+ */
+ findAll: function PSB_findAll() {
+ switch (this.filterCollection) {
+ case "history":
+ PlacesQueryBuilder.setScope("history");
+ break;
+ case "downloads":
+ PlacesQueryBuilder.setScope("downloads");
+ break;
+ default:
+ PlacesQueryBuilder.setScope("bookmarks");
+ break;
+ }
+ this.focus();
+ },
+
+ /**
+ * Updates the display with the title of the current collection.
+ * @param aTitle
+ * The title of the current collection.
+ */
+ updateCollectionTitle: function PSB_updateCollectionTitle(aTitle) {
+ let title = "";
+ switch (this.filterCollection) {
+ case "history":
+ title = PlacesUIUtils.getString("searchHistory");
+ break;
+ case "downloads":
+ title = PlacesUIUtils.getString("searchDownloads");
+ break;
+ default:
+ title = PlacesUIUtils.getString("searchBookmarks");
+ }
+ this.searchFilter.placeholder = title;
+ },
+
+ /**
+ * Gets/sets the active collection from the dropdown menu.
+ */
+ get filterCollection() {
+ return this.searchFilter.getAttribute("collection");
+ },
+ set filterCollection(collectionName) {
+ if (collectionName == this.filterCollection)
+ return collectionName;
+
+ this.searchFilter.setAttribute("collection", collectionName);
+ this.updateCollectionTitle();
+
+ return collectionName;
+ },
+
+ /**
+ * Focus the search box
+ */
+ focus: function PSB_focus() {
+ this.searchFilter.focus();
+ },
+
+ /**
+ * Set up the gray text in the search bar as the Places View loads.
+ */
+ init: function PSB_init() {
+ this.updateCollectionTitle();
+ },
+
+ /**
+ * Gets or sets the text shown in the Places Search Box
+ */
+ get value() {
+ return this.searchFilter.value;
+ },
+ set value(value) {
+ return this.searchFilter.value = value;
+ },
+};
+
+/**
+ * Functions and data for advanced query builder
+ */
+var PlacesQueryBuilder = {
+
+ queries: [],
+ queryOptions: null,
+
+ /**
+ * Sets the search scope. This can be called when no search is active, and
+ * in that case, when the user does begin a search aScope will be used (see
+ * PSB_search()). If there is an active search, it's performed again to
+ * update the content tree.
+ * @param aScope
+ * The search scope: "bookmarks", "collection", "downloads" or
+ * "history".
+ */
+ setScope: function PQB_setScope(aScope) {
+ // Determine filterCollection, folders, and scopeButtonId based on aScope.
+ var filterCollection;
+ var folders = [];
+ switch (aScope) {
+ case "history":
+ filterCollection = "history";
+ break;
+ case "bookmarks":
+ filterCollection = "bookmarks";
+ folders.push(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.toolbarFolderId,
+ PlacesUtils.unfiledBookmarksFolderId);
+ break;
+ case "downloads":
+ filterCollection = "downloads";
+ break;
+ default:
+ throw "Invalid search scope";
+ }
+
+ // Update the search box. Re-search if there's an active search.
+ PlacesSearchBox.filterCollection = filterCollection;
+ PlacesSearchBox.folders = folders;
+ var searchStr = PlacesSearchBox.searchFilter.value;
+ if (searchStr)
+ PlacesSearchBox.search(searchStr);
+ }
+};
+
+/**
+ * Population and commands for the View Menu.
+ */
+var ViewMenu = {
+ /**
+ * Removes content generated previously from a menupopup.
+ * @param popup
+ * The popup that contains the previously generated content.
+ * @param startID
+ * The id attribute of an element that is the start of the
+ * dynamically generated region - remove elements after this
+ * item only.
+ * Must be contained by popup. Can be null (in which case the
+ * contents of popup are removed).
+ * @param endID
+ * The id attribute of an element that is the end of the
+ * dynamically generated region - remove elements up to this
+ * item only.
+ * Must be contained by popup. Can be null (in which case all
+ * items until the end of the popup will be removed). Ignored
+ * if startID is null.
+ * @returns The element for the caller to insert new items before,
+ * null if the caller should just append to the popup.
+ */
+ _clean: function VM__clean(popup, startID, endID) {
+ if (endID)
+ NS_ASSERT(startID, "meaningless to have valid endID and null startID");
+ if (startID) {
+ var startElement = document.getElementById(startID);
+ NS_ASSERT(startElement.parentNode ==
+ popup, "startElement is not in popup");
+ NS_ASSERT(startElement,
+ "startID does not correspond to an existing element");
+ var endElement = null;
+ if (endID) {
+ endElement = document.getElementById(endID);
+ NS_ASSERT(endElement.parentNode == popup,
+ "endElement is not in popup");
+ NS_ASSERT(endElement,
+ "endID does not correspond to an existing element");
+ }
+ while (startElement.nextSibling != endElement)
+ popup.removeChild(startElement.nextSibling);
+ return endElement;
+ }
+ while (popup.hasChildNodes()) {
+ popup.removeChild(popup.firstChild);
+ }
+ return null;
+ },
+
+ /**
+ * Fills a menupopup with a list of columns
+ * @param event
+ * The popupshowing event that invoked this function.
+ * @param startID
+ * see _clean
+ * @param endID
+ * see _clean
+ * @param type
+ * the type of the menuitem, e.g. "radio" or "checkbox".
+ * Can be null (no-type).
+ * Checkboxes are checked if the column is visible.
+ * @param propertyPrefix
+ * If propertyPrefix is non-null:
+ * propertyPrefix + column ID + ".label" will be used to get the
+ * localized label string.
+ * propertyPrefix + column ID + ".accesskey" will be used to get the
+ * localized accesskey.
+ * If propertyPrefix is null, the column label is used as label and
+ * no accesskey is assigned.
+ */
+ fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, propertyPrefix) {
+ var popup = event.target;
+ var pivot = this._clean(popup, startID, endID);
+
+ var content = document.getElementById("placeContent");
+ var columns = content.columns;
+ for (var i = 0; i < columns.count; ++i) {
+ var column = columns.getColumnAt(i).element;
+ var menuitem = document.createElement("menuitem");
+ menuitem.id = "menucol_" + column.id;
+ menuitem.column = column;
+ var label = column.getAttribute("label");
+ if (propertyPrefix) {
+ var menuitemPrefix = propertyPrefix;
+ // for string properties, use "name" as the id, instead of "title"
+ // see bug #386287 for details
+ var columnId = column.getAttribute("anonid");
+ menuitemPrefix += columnId == "title" ? "name" : columnId;
+ label = PlacesUIUtils.getString(menuitemPrefix + ".label");
+ var accesskey = PlacesUIUtils.getString(menuitemPrefix + ".accesskey");
+ menuitem.setAttribute("accesskey", accesskey);
+ }
+ menuitem.setAttribute("label", label);
+ if (type == "radio") {
+ menuitem.setAttribute("type", "radio");
+ menuitem.setAttribute("name", "columns");
+ // This column is the sort key. Its item is checked.
+ if (column.getAttribute("sortDirection") != "") {
+ menuitem.setAttribute("checked", "true");
+ }
+ }
+ else if (type == "checkbox") {
+ menuitem.setAttribute("type", "checkbox");
+ // Cannot uncheck the primary column.
+ if (column.getAttribute("primary") == "true")
+ menuitem.setAttribute("disabled", "true");
+ // Items for visible columns are checked.
+ if (!column.hidden)
+ menuitem.setAttribute("checked", "true");
+ }
+ if (pivot)
+ popup.insertBefore(menuitem, pivot);
+ else
+ popup.appendChild(menuitem);
+ }
+ event.stopPropagation();
+ },
+
+ /**
+ * Set up the content of the view menu.
+ */
+ populateSortMenu: function VM_populateSortMenu(event) {
+ this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.1.");
+
+ var sortColumn = this._getSortColumn();
+ var viewSortAscending = document.getElementById("viewSortAscending");
+ var viewSortDescending = document.getElementById("viewSortDescending");
+ // We need to remove an existing checked attribute because the unsorted
+ // menu item is not rebuilt every time we open the menu like the others.
+ var viewUnsorted = document.getElementById("viewUnsorted");
+ if (!sortColumn) {
+ viewSortAscending.removeAttribute("checked");
+ viewSortDescending.removeAttribute("checked");
+ viewUnsorted.setAttribute("checked", "true");
+ }
+ else if (sortColumn.getAttribute("sortDirection") == "ascending") {
+ viewSortAscending.setAttribute("checked", "true");
+ viewSortDescending.removeAttribute("checked");
+ viewUnsorted.removeAttribute("checked");
+ }
+ else if (sortColumn.getAttribute("sortDirection") == "descending") {
+ viewSortDescending.setAttribute("checked", "true");
+ viewSortAscending.removeAttribute("checked");
+ viewUnsorted.removeAttribute("checked");
+ }
+ },
+
+ /**
+ * Shows/Hides a tree column.
+ * @param element
+ * The menuitem element for the column
+ */
+ showHideColumn: function VM_showHideColumn(element) {
+ var column = element.column;
+
+ var splitter = column.nextSibling;
+ if (splitter && splitter.localName != "splitter")
+ splitter = null;
+
+ if (element.getAttribute("checked") == "true") {
+ column.setAttribute("hidden", "false");
+ if (splitter)
+ splitter.removeAttribute("hidden");
+ }
+ else {
+ column.setAttribute("hidden", "true");
+ if (splitter)
+ splitter.setAttribute("hidden", "true");
+ }
+ },
+
+ /**
+ * Gets the last column that was sorted.
+ * @returns the currently sorted column, null if there is no sorted column.
+ */
+ _getSortColumn: function VM__getSortColumn() {
+ var content = document.getElementById("placeContent");
+ var cols = content.columns;
+ for (var i = 0; i < cols.count; ++i) {
+ var column = cols.getColumnAt(i).element;
+ var sortDirection = column.getAttribute("sortDirection");
+ if (sortDirection == "ascending" || sortDirection == "descending")
+ return column;
+ }
+ return null;
+ },
+
+ /**
+ * Sorts the view by the specified column.
+ * @param aColumn
+ * The colum that is the sort key. Can be null - the
+ * current sort column or the title column will be used.
+ * @param aDirection
+ * The direction to sort - "ascending" or "descending".
+ * Can be null - the last direction or descending will be used.
+ *
+ * If both aColumnID and aDirection are null, the view will be unsorted.
+ */
+ setSortColumn: function VM_setSortColumn(aColumn, aDirection) {
+ var result = document.getElementById("placeContent").result;
+ if (!aColumn && !aDirection) {
+ result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ return;
+ }
+
+ var columnId;
+ if (aColumn) {
+ columnId = aColumn.getAttribute("anonid");
+ if (!aDirection) {
+ let sortColumn = this._getSortColumn();
+ if (sortColumn)
+ aDirection = sortColumn.getAttribute("sortDirection");
+ }
+ }
+ else {
+ let sortColumn = this._getSortColumn();
+ columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title";
+ }
+
+ // This maps the possible values of columnId (i.e., anonid's of treecols in
+ // placeContent) to the default sortingMode and sortingAnnotation values for
+ // each column.
+ // key: Sort key in the name of one of the
+ // nsINavHistoryQueryOptions.SORT_BY_* constants
+ // dir: Default sort direction to use if none has been specified
+ // anno: The annotation to sort by, if key is "ANNOTATION"
+ var colLookupTable = {
+ title: { key: "TITLE", dir: "ascending" },
+ tags: { key: "TAGS", dir: "ascending" },
+ url: { key: "URI", dir: "ascending" },
+ date: { key: "DATE", dir: "descending" },
+ visitCount: { key: "VISITCOUNT", dir: "descending" },
+ dateAdded: { key: "DATEADDED", dir: "descending" },
+ lastModified: { key: "LASTMODIFIED", dir: "descending" },
+ description: { key: "ANNOTATION",
+ dir: "ascending",
+ anno: PlacesUIUtils.DESCRIPTION_ANNO }
+ };
+
+ // Make sure we have a valid column.
+ if (!colLookupTable.hasOwnProperty(columnId))
+ throw new Error("Invalid column");
+
+ // Use a default sort direction if none has been specified. If aDirection
+ // is invalid, result.sortingMode will be undefined, which has the effect
+ // of unsorting the tree.
+ aDirection = (aDirection || colLookupTable[columnId].dir).toUpperCase();
+
+ var sortConst = "SORT_BY_" + colLookupTable[columnId].key + "_" + aDirection;
+ result.sortingAnnotation = colLookupTable[columnId].anno || "";
+ result.sortingMode = Ci.nsINavHistoryQueryOptions[sortConst];
+ }
+}
+
+var ContentArea = {
+ _specialViews: new Map(),
+
+ init: function CA_init() {
+ this._deck = document.getElementById("placesViewsDeck");
+ this._toolbar = document.getElementById("placesToolbar");
+ ContentTree.init();
+ this._setupView();
+ },
+
+ /**
+ * Gets the content view to be used for loading the given query.
+ * If a custom view was set by setContentViewForQueryString, that
+ * view would be returned, else the default tree view is returned
+ *
+ * @param aQueryString
+ * a query string
+ * @return the view to be used for loading aQueryString.
+ */
+ getContentViewForQueryString:
+ function CA_getContentViewForQueryString(aQueryString) {
+ try {
+ if (this._specialViews.has(aQueryString)) {
+ let { view, options } = this._specialViews.get(aQueryString);
+ if (typeof view == "function") {
+ view = view();
+ this._specialViews.set(aQueryString, { view: view, options: options });
+ }
+ return view;
+ }
+ }
+ catch (ex) {
+ Components.utils.reportError(ex);
+ }
+ return ContentTree.view;
+ },
+
+ /**
+ * Sets a custom view to be used rather than the default places tree
+ * whenever the given query is selected in the left pane.
+ * @param aQueryString
+ * a query string
+ * @param aView
+ * Either the custom view or a function that will return the view
+ * the first (and only) time it's called.
+ * @param [optional] aOptions
+ * Object defining special options for the view.
+ * @see ContentTree.viewOptions for supported options and default values.
+ */
+ setContentViewForQueryString:
+ function CA_setContentViewForQueryString(aQueryString, aView, aOptions) {
+ if (!aQueryString ||
+ typeof aView != "object" && typeof aView != "function")
+ throw new Error("Invalid arguments");
+
+ this._specialViews.set(aQueryString, { view: aView,
+ options: aOptions || {} });
+ },
+
+ get currentView() {
+ return PlacesUIUtils.getViewForNode(this._deck.selectedPanel);
+ },
+ set currentView(aNewView) {
+ let oldView = this.currentView;
+ if (oldView != aNewView) {
+ this._deck.selectedPanel = aNewView.associatedElement;
+
+ // If the content area inactivated view was focused, move focus
+ // to the new view.
+ if (document.activeElement == oldView.associatedElement)
+ aNewView.associatedElement.focus();
+ }
+ return aNewView;
+ },
+
+ get currentPlace() {
+ return this.currentView.place;
+ },
+ set currentPlace(aQueryString) {
+ let oldView = this.currentView;
+ let newView = this.getContentViewForQueryString(aQueryString);
+ newView.place = aQueryString;
+ if (oldView != newView) {
+ oldView.active = false;
+ this.currentView = newView;
+ this._setupView();
+ newView.active = true;
+ }
+ return aQueryString;
+ },
+
+ /**
+ * Applies view options.
+ */
+ _setupView: function CA__setupView() {
+ let options = this.currentViewOptions;
+
+ // showDetailsPane.
+ let detailsDeck = document.getElementById("detailsDeck");
+ detailsDeck.hidden = !options.showDetailsPane;
+
+ // toolbarSet.
+ for (let elt of this._toolbar.childNodes) {
+ // On Windows and Linux the menu buttons are menus wrapped in a menubar.
+ if (elt.id == "placesMenu") {
+ for (let menuElt of elt.childNodes) {
+ menuElt.hidden = !options.toolbarSet.includes(menuElt.id);
+ }
+ }
+ else {
+ elt.hidden = !options.toolbarSet.includes(elt.id);
+ }
+ }
+ },
+
+ /**
+ * Options for the current view.
+ *
+ * @see ContentTree.viewOptions for supported options and default values.
+ */
+ get currentViewOptions() {
+ // Use ContentTree options as default.
+ let viewOptions = ContentTree.viewOptions;
+ if (this._specialViews.has(this.currentPlace)) {
+ let { options } = this._specialViews.get(this.currentPlace);
+ for (let option in options) {
+ viewOptions[option] = options[option];
+ }
+ }
+ return viewOptions;
+ },
+
+ focus: function() {
+ this._deck.selectedPanel.focus();
+ }
+};
+
+var ContentTree = {
+ init: function CT_init() {
+ this._view = document.getElementById("placeContent");
+ },
+
+ get view() {
+ return this._view;
+ },
+
+ get viewOptions() {
+ return Object.seal({
+ showDetailsPane: true,
+ toolbarSet: "back-button, forward-button, organizeButton, viewMenu, maintenanceButton, libraryToolbarSpacer, searchFilter"
+ });
+ },
+
+ openSelectedNode: function CT_openSelectedNode(aEvent) {
+ let view = this.view;
+ PlacesUIUtils.openNodeWithEvent(view.selectedNode, aEvent, view);
+ },
+
+ onClick: function CT_onClick(aEvent) {
+ let node = this.view.selectedNode;
+ if (node) {
+ let doubleClick = aEvent.button == 0 && aEvent.detail == 2;
+ let middleClick = aEvent.button == 1 && aEvent.detail == 1;
+ if (PlacesUtils.nodeIsURI(node) && (doubleClick || middleClick)) {
+ // Open associated uri in the browser.
+ this.openSelectedNode(aEvent);
+ }
+ else if (middleClick && PlacesUtils.nodeIsContainer(node)) {
+ // The command execution function will take care of seeing if the
+ // selection is a folder or a different container type, and will
+ // load its contents in tabs.
+ PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this.view);
+ }
+ }
+ },
+
+ onKeyPress: function CT_onKeyPress(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
+ this.openSelectedNode(aEvent);
+ }
+};
diff --git a/browser/components/places/content/places.xul b/browser/components/places/content/places.xul
new file mode 100644
index 000000000..16c3385cb
--- /dev/null
+++ b/browser/components/places/content/places.xul
@@ -0,0 +1,438 @@
+<?xml version="1.0"?>
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/organizer.css"?>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/organizer.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#else
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+#endif
+
+<!DOCTYPE window [
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<window id="places"
+ title="&places.library.title;"
+ windowtype="Places:Organizer"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="PlacesOrganizer.init();"
+ onunload="PlacesOrganizer.destroy();"
+ width="&places.library.width;" height="&places.library.height;"
+ screenX="10" screenY="10"
+ toggletoolbar="true"
+ persist="width height screenX screenY sizemode">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/places/places.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+ <stringbundleset id="placesStringSet">
+ <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
+ </stringbundleset>
+
+
+#ifdef XP_MACOSX
+#include ../../../base/content/browserMountPoints.inc
+#else
+ <commandset id="editMenuCommands"/>
+ <commandset id="placesCommands"/>
+#endif
+
+ <commandset id="organizerCommandSet">
+ <command id="OrganizerCommand_find:all"
+ oncommand="PlacesSearchBox.findAll();"/>
+ <command id="OrganizerCommand_export"
+ oncommand="PlacesOrganizer.exportBookmarks();"/>
+ <command id="OrganizerCommand_import"
+ oncommand="PlacesOrganizer.importFromFile();"/>
+ <command id="OrganizerCommand_browserImport"
+ oncommand="PlacesOrganizer.importFromBrowser();"/>
+ <command id="OrganizerCommand_backup"
+ oncommand="PlacesOrganizer.backupBookmarks();"/>
+ <command id="OrganizerCommand_restoreFromFile"
+ oncommand="PlacesOrganizer.onRestoreBookmarksFromFile();"/>
+ <command id="OrganizerCommand_search:save"
+ oncommand="PlacesOrganizer.saveSearch();"/>
+ <command id="OrganizerCommand_search:moreCriteria"
+ oncommand="PlacesQueryBuilder.addRow();"/>
+ <command id="OrganizerCommand:Back"
+ oncommand="PlacesOrganizer.back();"/>
+ <command id="OrganizerCommand:Forward"
+ oncommand="PlacesOrganizer.forward();"/>
+ </commandset>
+
+
+ <keyset id="placesOrganizerKeyset">
+ <!-- Instantiation Keys -->
+ <key id="placesKey_close" key="&cmd.close.key;" modifiers="accel"
+ oncommand="close();"/>
+
+ <!-- Command Keys -->
+ <key id="placesKey_find:all"
+ command="OrganizerCommand_find:all"
+ key="&cmd.find.key;"
+ modifiers="accel"/>
+
+ <!-- Back/Forward Keys Support -->
+#ifndef XP_MACOSX
+ <key id="placesKey_goBackKb"
+ keycode="VK_LEFT"
+ command="OrganizerCommand:Back"
+ modifiers="alt"/>
+ <key id="placesKey_goForwardKb"
+ keycode="VK_RIGHT"
+ command="OrganizerCommand:Forward"
+ modifiers="alt"/>
+#else
+ <key id="placesKey_goBackKb"
+ keycode="VK_LEFT"
+ command="OrganizerCommand:Back"
+ modifiers="accel"/>
+ <key id="placesKey_goForwardKb"
+ keycode="VK_RIGHT"
+ command="OrganizerCommand:Forward"
+ modifiers="accel"/>
+#endif
+#ifdef XP_UNIX
+ <key id="placesKey_goBackKb2"
+ key="&goBackCmd.commandKey;"
+ command="OrganizerCommand:Back"
+ modifiers="accel"/>
+ <key id="placesKey_goForwardKb2"
+ key="&goForwardCmd.commandKey;"
+ command="OrganizerCommand:Forward"
+ modifiers="accel"/>
+#endif
+ </keyset>
+
+ <keyset id="editMenuKeys">
+#ifdef XP_MACOSX
+ <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
+#endif
+ </keyset>
+
+ <popupset id="placesPopupset">
+ <menupopup id="placesContext"/>
+ <menupopup id="placesColumnsContext"
+ onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
+ oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
+ </popupset>
+
+ <toolbox id="placesToolbox">
+ <toolbar class="chromeclass-toolbar" id="placesToolbar" align="center">
+ <toolbarbutton id="back-button"
+ command="OrganizerCommand:Back"
+ tooltiptext="&backButton.tooltip;"
+ disabled="true"/>
+
+ <toolbarbutton id="forward-button"
+ command="OrganizerCommand:Forward"
+ tooltiptext="&forwardButton.tooltip;"
+ disabled="true"/>
+
+#ifdef XP_MACOSX
+ <toolbarbutton type="menu" class="tabbable"
+ onpopupshowing="document.getElementById('placeContent').focus()"
+#else
+ <menubar id="placesMenu">
+ <menu accesskey="&organize.accesskey;" class="menu-iconic"
+#endif
+ id="organizeButton" label="&organize.label;"
+ tooltiptext="&organize.tooltip;">
+ <menupopup id="organizeButtonPopup">
+ <menuitem id="newbookmark"
+ command="placesCmd_new:bookmark"
+ label="&cmd.new_bookmark.label;"
+ accesskey="&cmd.new_bookmark.accesskey;"/>
+ <menuitem id="newfolder"
+ command="placesCmd_new:folder"
+ label="&cmd.new_folder.label;"
+ accesskey="&cmd.new_folder.accesskey;"/>
+ <menuitem id="newseparator"
+ command="placesCmd_new:separator"
+ label="&cmd.new_separator.label;"
+ accesskey="&cmd.new_separator.accesskey;"/>
+
+#ifndef XP_MACOSX
+ <menuseparator id="orgUndoSeparator"/>
+
+ <menuitem id="orgUndo"
+ command="cmd_undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ accesskey="&undoCmd.accesskey;"/>
+ <menuitem id="orgRedo"
+ command="cmd_redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ accesskey="&redoCmd.accesskey;"/>
+
+ <menuseparator id="orgCutSeparator"/>
+
+ <menuitem id="orgCut"
+ command="cmd_cut"
+ label="&cutCmd.label;"
+ key="key_cut"
+ accesskey="&cutCmd.accesskey;"
+ selection="separator|link|folder|mixed"/>
+ <menuitem id="orgCopy"
+ command="cmd_copy"
+ label="&copyCmd.label;"
+ key="key_copy"
+ accesskey="&copyCmd.accesskey;"
+ selection="separator|link|folder|mixed"/>
+ <menuitem id="orgPaste"
+ command="cmd_paste"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ accesskey="&pasteCmd.accesskey;"
+ selection="mutable"/>
+ <menuitem id="orgDelete"
+ command="cmd_delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"/>
+
+ <menuseparator id="selectAllSeparator"/>
+
+ <menuitem id="orgSelectAll"
+ command="cmd_selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ accesskey="&selectAllCmd.accesskey;"/>
+
+#endif
+ <menuseparator id="orgMoveSeparator"/>
+
+ <menuitem id="orgMoveBookmarks"
+ command="placesCmd_moveBookmarks"
+ label="&cmd.moveBookmarks.label;"
+ accesskey="&cmd.moveBookmarks.accesskey;"/>
+#ifdef XP_MACOSX
+ <menuitem id="orgDelete"
+ command="cmd_delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"/>
+#else
+ <menuseparator id="orgCloseSeparator"/>
+
+ <menuitem id="orgClose"
+ key="placesKey_close"
+ label="&file.close.label;"
+ accesskey="&file.close.accesskey;"
+ oncommand="close();"/>
+#endif
+ </menupopup>
+#ifdef XP_MACOSX
+ </toolbarbutton>
+ <toolbarbutton type="menu" class="tabbable"
+#else
+ </menu>
+ <menu accesskey="&views.accesskey;" class="menu-iconic"
+#endif
+ id="viewMenu" label="&views.label;"
+ tooltiptext="&views.tooltip;">
+ <menupopup id="viewMenuPopup">
+
+ <menu id="viewColumns"
+ label="&view.columns.label;" accesskey="&view.columns.accesskey;">
+ <menupopup onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
+ oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
+ </menu>
+
+ <menu id="viewSort" label="&view.sort.label;"
+ accesskey="&view.sort.accesskey;">
+ <menupopup onpopupshowing="ViewMenu.populateSortMenu(event);"
+ oncommand="ViewMenu.setSortColumn(event.target.column, null);">
+ <menuitem id="viewUnsorted" type="radio" name="columns"
+ label="&view.unsorted.label;" accesskey="&view.unsorted.accesskey;"
+ oncommand="ViewMenu.setSortColumn(null, null);"/>
+ <menuseparator id="directionSeparator"/>
+ <menuitem id="viewSortAscending" type="radio" name="direction"
+ label="&view.sortAscending.label;" accesskey="&view.sortAscending.accesskey;"
+ oncommand="ViewMenu.setSortColumn(null, 'ascending'); event.stopPropagation();"/>
+ <menuitem id="viewSortDescending" type="radio" name="direction"
+ label="&view.sortDescending.label;" accesskey="&view.sortDescending.accesskey;"
+ oncommand="ViewMenu.setSortColumn(null, 'descending'); event.stopPropagation();"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+#ifdef XP_MACOSX
+ </toolbarbutton>
+ <toolbarbutton type="menu" class="tabbable"
+#else
+ </menu>
+ <menu accesskey="&maintenance.accesskey;" class="menu-iconic"
+#endif
+ id="maintenanceButton" label="&maintenance.label;"
+ tooltiptext="&maintenance.tooltip;">
+ <menupopup id="maintenanceButtonPopup">
+ <menuitem id="backupBookmarks"
+ command="OrganizerCommand_backup"
+ label="&cmd.backup.label;"
+ accesskey="&cmd.backup.accesskey;"/>
+ <menu id="fileRestoreMenu" label="&cmd.restore2.label;"
+ accesskey="&cmd.restore2.accesskey;">
+ <menupopup id="fileRestorePopup" onpopupshowing="PlacesOrganizer.populateRestoreMenu();">
+ <menuitem id="restoreFromFile"
+ command="OrganizerCommand_restoreFromFile"
+ label="&cmd.restoreFromFile.label;"
+ accesskey="&cmd.restoreFromFile.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ <menuitem id="fileImport"
+ command="OrganizerCommand_import"
+ label="&importBookmarksFromHTML.label;"
+ accesskey="&importBookmarksFromHTML.accesskey;"/>
+ <menuitem id="fileExport"
+ command="OrganizerCommand_export"
+ label="&exportBookmarksToHTML.label;"
+ accesskey="&exportBookmarksToHTML.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="browserImport"
+ command="OrganizerCommand_browserImport"
+ label="&importOtherBrowser.label;"
+ accesskey="&importOtherBrowser.accesskey;"/>
+ </menupopup>
+#ifdef XP_MACOSX
+ </toolbarbutton>
+#else
+ </menu>
+ </menubar>
+#endif
+
+ <spacer id="libraryToolbarSpacer" flex="1"/>
+
+ <textbox id="searchFilter"
+ clickSelectsAll="true"
+ type="search"
+ aria-controls="placeContent"
+ oncommand="PlacesSearchBox.search(this.value);"
+ collection="bookmarks">
+ </textbox>
+ </toolbar>
+ </toolbox>
+
+ <hbox flex="1" id="placesView">
+ <tree id="placesList"
+ class="plain placesTree"
+ type="places"
+ hidecolumnpicker="true" context="placesContext"
+ onselect="PlacesOrganizer.onPlaceSelected(true);"
+ onclick="PlacesOrganizer.onPlacesListClick(event);"
+ onfocus="PlacesOrganizer.updateDetailsPane(event);"
+ seltype="single"
+ persist="width"
+ width="200"
+ minwidth="100"
+ maxwidth="400">
+ <treecols>
+ <treecol anonid="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ <splitter collapse="none" persist="state"></splitter>
+ <vbox id="contentView" flex="4">
+ <deck id="placesViewsDeck"
+ selectedIndex="0"
+ flex="1">
+ <tree id="placeContent"
+ class="plain placesTree"
+ context="placesContext"
+ hidecolumnpicker="true"
+ flex="1"
+ type="places"
+ flatList="true"
+ selectfirstnode="true"
+ enableColumnDrag="true"
+ onfocus="PlacesOrganizer.updateDetailsPane(event)"
+ onselect="PlacesOrganizer.updateDetailsPane(event)"
+ onkeypress="ContentTree.onKeyPress(event);"
+ onopenflatcontainer="PlacesOrganizer.openFlatContainer(aContainer);">
+ <treecols id="placeContentColumns" context="placesColumnsContext">
+ <treecol label="&col.name.label;" id="placesContentTitle" anonid="title" flex="5" primary="true" ordinal="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.tags.label;" id="placesContentTags" anonid="tags" flex="2"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.url.label;" id="placesContentUrl" anonid="url" flex="5"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.mostrecentvisit.label;" id="placesContentDate" anonid="date" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.visitcount.label;" id="placesContentVisitCount" anonid="visitCount" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.description.label;" id="placesContentDescription" anonid="description" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.dateadded.label;" id="placesContentDateAdded" anonid="dateAdded" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.lastmodified.label;" id="placesContentLastModified" anonid="lastModified" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ </treecols>
+ <treechildren flex="1" onclick="ContentTree.onClick(event);"/>
+ </tree>
+ </deck>
+ <deck id="detailsDeck" style="height: 11em;">
+ <vbox id="itemsCountBox" align="center">
+ <spacer flex="3"/>
+ <label id="itemsCountText"/>
+ <spacer flex="1"/>
+ <description id="selectItemDescription">
+ &detailsPane.selectAnItemText.description;
+ </description>
+ <spacer flex="3"/>
+ </vbox>
+ <vbox id="infoBox" minimal="true">
+ <vbox id="editBookmarkPanelContent" flex="1"/>
+ <hbox id="infoBoxExpanderWrapper" align="center">
+
+ <button type="image" id="infoBoxExpander"
+ class="expander-down"
+ oncommand="PlacesOrganizer.toggleAdditionalInfoFields();"
+ observes="paneElementsBroadcaster"/>
+
+ <label id="infoBoxExpanderLabel"
+ lesslabel="&detailsPane.less.label;"
+ lessaccesskey="&detailsPane.less.accesskey;"
+ morelabel="&detailsPane.more.label;"
+ moreaccesskey="&detailsPane.more.accesskey;"
+ value="&detailsPane.more.label;"
+ accesskey="&detailsPane.more.accesskey;"
+ control="infoBoxExpander"/>
+
+ </hbox>
+ </vbox>
+ </deck>
+ </vbox>
+ </hbox>
+</window>
diff --git a/browser/components/places/content/placesOverlay.xul b/browser/components/places/content/placesOverlay.xul
new file mode 100644
index 000000000..512eb923e
--- /dev/null
+++ b/browser/components/places/content/placesOverlay.xul
@@ -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/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+]>
+
+<overlay id="placesOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"><![CDATA[
+ // TODO: Bug 406371.
+ // A bunch of browser code depends on us defining these, sad but true :(
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var Cr = Components.results;
+
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+ Components.utils.import("resource://gre/modules/Task.jsm");
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ XPCOMUtils.defineLazyModuleGetter(window,
+ "PlacesUIUtils", "resource:///modules/PlacesUIUtils.jsm");
+ XPCOMUtils.defineLazyModuleGetter(window,
+ "PlacesTransactions", "resource://gre/modules/PlacesTransactions.jsm");
+ ]]></script>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/controller.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/treeView.js"/>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip" noautohide="true"
+ onpopupshowing="return window.top.BookmarksEventHandler.fillInBHTooltip(document, event)">
+ <vbox id="bhTooltipTextBox" flex="1">
+ <label id="bhtTitleText" class="tooltip-label" />
+ <label id="bhtUrlText" crop="center" class="tooltip-label" />
+ </vbox>
+ </tooltip>
+
+ <commandset id="placesCommands"
+ commandupdater="true"
+ events="focus,sort,places"
+ oncommandupdate="goUpdatePlacesCommands();">
+ <command id="placesCmd_open"
+ oncommand="goDoPlacesCommand('placesCmd_open');"/>
+ <command id="placesCmd_open:window"
+ oncommand="goDoPlacesCommand('placesCmd_open:window');"/>
+ <command id="placesCmd_open:privatewindow"
+ oncommand="goDoPlacesCommand('placesCmd_open:privatewindow');"/>
+ <command id="placesCmd_open:tab"
+ oncommand="goDoPlacesCommand('placesCmd_open:tab');"/>
+
+ <command id="placesCmd_new:bookmark"
+ oncommand="goDoPlacesCommand('placesCmd_new:bookmark');"/>
+ <command id="placesCmd_new:folder"
+ oncommand="goDoPlacesCommand('placesCmd_new:folder');"/>
+ <command id="placesCmd_new:separator"
+ oncommand="goDoPlacesCommand('placesCmd_new:separator');"/>
+ <command id="placesCmd_show:info"
+ oncommand="goDoPlacesCommand('placesCmd_show:info');"/>
+ <command id="placesCmd_rename"
+ oncommand="goDoPlacesCommand('placesCmd_show:info');"
+ observes="placesCmd_show:info"/>
+ <command id="placesCmd_reload"
+ oncommand="goDoPlacesCommand('placesCmd_reload');"/>
+ <command id="placesCmd_sortBy:name"
+ oncommand="goDoPlacesCommand('placesCmd_sortBy:name');"/>
+ <command id="placesCmd_moveBookmarks"
+ oncommand="goDoPlacesCommand('placesCmd_moveBookmarks');"/>
+ <command id="placesCmd_deleteDataHost"
+ oncommand="goDoPlacesCommand('placesCmd_deleteDataHost');"/>
+ <command id="placesCmd_createBookmark"
+ oncommand="goDoPlacesCommand('placesCmd_createBookmark');"/>
+
+ <!-- Special versions of cut/copy/paste/delete which check for an open context menu. -->
+ <command id="placesCmd_cut"
+ oncommand="goDoPlacesCommand('placesCmd_cut');"/>
+ <command id="placesCmd_copy"
+ oncommand="goDoPlacesCommand('placesCmd_copy');"/>
+ <command id="placesCmd_paste"
+ oncommand="goDoPlacesCommand('placesCmd_paste');"/>
+ <command id="placesCmd_delete"
+ oncommand="goDoPlacesCommand('placesCmd_delete');"/>
+ </commandset>
+
+ <menupopup id="placesContext"
+ onpopupshowing="this._view = PlacesUIUtils.getViewForNode(document.popupNode);
+ return this._view.buildContextMenu(this);"
+ onpopuphiding="this._view.destroyContextMenu();">
+ <menuitem id="placesContext_open"
+ command="placesCmd_open"
+ label="&cmd.open.label;"
+ accesskey="&cmd.open.accesskey;"
+ default="true"
+ selectiontype="single"
+ selection="link"/>
+ <menuitem id="placesContext_open:newtab"
+ command="placesCmd_open:tab"
+ label="&cmd.open_tab.label;"
+ accesskey="&cmd.open_tab.accesskey;"
+ selectiontype="single"
+ selection="link"/>
+ <menuitem id="placesContext_openContainer:tabs"
+ oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode);
+ view.controller.openSelectionInTabs(event);"
+ onclick="checkForMiddleClick(this, event);"
+ label="&cmd.open_all_in_tabs.label;"
+ accesskey="&cmd.open_all_in_tabs.accesskey;"
+ selectiontype="single|none"
+ selection="folder|host|query"/>
+ <menuitem id="placesContext_openLinks:tabs"
+ oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode);
+ view.controller.openSelectionInTabs(event);"
+ onclick="checkForMiddleClick(this, event);"
+ label="&cmd.open_all_in_tabs.label;"
+ accesskey="&cmd.open_all_in_tabs.accesskey;"
+ selectiontype="multiple"
+ selection="link"/>
+ <menuitem id="placesContext_open:newwindow"
+ command="placesCmd_open:window"
+ label="&cmd.open_window.label;"
+ accesskey="&cmd.open_window.accesskey;"
+ selectiontype="single"
+ selection="link"/>
+ <menuitem id="placesContext_open:newprivatewindow"
+ command="placesCmd_open:privatewindow"
+ label="&cmd.open_private_window.label;"
+ accesskey="&cmd.open_private_window.accesskey;"
+ selectiontype="single"
+ selection="link"
+ hideifprivatebrowsing="true"/>
+ <menuseparator id="placesContext_openSeparator"/>
+ <menuitem id="placesContext_new:bookmark"
+ command="placesCmd_new:bookmark"
+ label="&cmd.new_bookmark.label;"
+ accesskey="&cmd.new_bookmark.accesskey;"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuitem id="placesContext_new:folder"
+ command="placesCmd_new:folder"
+ label="&cmd.new_folder.label;"
+ accesskey="&cmd.context_new_folder.accesskey;"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuitem id="placesContext_new:separator"
+ command="placesCmd_new:separator"
+ label="&cmd.new_separator.label;"
+ accesskey="&cmd.new_separator.accesskey;"
+ closemenu="single"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuseparator id="placesContext_newSeparator"/>
+ <menuitem id="placesContext_createBookmark"
+ command="placesCmd_createBookmark"
+ label="&cmd.bookmarkLink.label;"
+ accesskey="&cmd.bookmarkLink.accesskey;"
+ selection="link"
+ forcehideselection="bookmark|tagChild"/>
+ <menuitem id="placesContext_cut"
+ command="placesCmd_cut"
+ label="&cutCmd.label;"
+ accesskey="&cutCmd.accesskey;"
+ closemenu="single"
+ selection="bookmark|folder|separator|query"
+ forcehideselection="tagChild|livemarkChild"/>
+ <menuitem id="placesContext_copy"
+ command="placesCmd_copy"
+ label="&copyCmd.label;"
+ closemenu="single"
+ accesskey="&copyCmd.accesskey;"
+ selection="any"/>
+ <menuitem id="placesContext_paste"
+ command="placesCmd_paste"
+ label="&pasteCmd.label;"
+ closemenu="single"
+ accesskey="&pasteCmd.accesskey;"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuseparator id="placesContext_editSeparator"/>
+ <menuitem id="placesContext_delete"
+ command="placesCmd_delete"
+ label="&deleteCmd.label;"
+ accesskey="&deleteCmd.accesskey;"
+ closemenu="single"
+ selection="bookmark|tagChild|folder|query|dynamiccontainer|separator|host"/>
+ <menuitem id="placesContext_delete_history"
+ command="placesCmd_delete"
+ label="&cmd.delete.label;"
+ accesskey="&cmd.delete.accesskey;"
+ closemenu="single"
+ selection="link"
+ forcehideselection="bookmark"/>
+ <menuitem id="placesContext_deleteHost"
+ command="placesCmd_deleteDataHost"
+ label="&cmd.deleteDomainData.label;"
+ accesskey="&cmd.deleteDomainData.accesskey;"
+ closemenu="single"
+ selection="link|host"
+ selectiontype="single"
+ hideifprivatebrowsing="true"
+ forcehideselection="bookmark"/>
+ <menuseparator id="placesContext_deleteSeparator"/>
+ <menuitem id="placesContext_sortBy:name"
+ command="placesCmd_sortBy:name"
+ label="&cmd.sortby_name.label;"
+ accesskey="&cmd.context_sortby_name.accesskey;"
+ closemenu="single"
+ selection="folder"/>
+ <menuitem id="placesContext_reload"
+ command="placesCmd_reload"
+ label="&cmd.reloadLivebookmark.label;"
+ accesskey="&cmd.reloadLivebookmark.accesskey;"
+ closemenu="single"
+ selection="livemark/feedURI"/>
+ <menuseparator id="placesContext_sortSeparator"/>
+ <menuitem id="placesContext_show:info"
+ command="placesCmd_show:info"
+ label="&cmd.properties.label;"
+ accesskey="&cmd.properties.accesskey;"
+ selection="bookmark|folder|query"
+ forcehideselection="livemarkChild"/>
+ </menupopup>
+
+</overlay>
diff --git a/browser/components/places/content/sidebarUtils.js b/browser/components/places/content/sidebarUtils.js
new file mode 100644
index 000000000..96c289741
--- /dev/null
+++ b/browser/components/places/content/sidebarUtils.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/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+var SidebarUtils = {
+ handleTreeClick: function SU_handleTreeClick(aTree, aEvent, aGutterSelect) {
+ // right-clicks are not handled here
+ if (aEvent.button == 2)
+ return;
+
+ var tbo = aTree.treeBoxObject;
+ var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
+
+ if (cell.row == -1 || cell.childElt == "twisty")
+ return;
+
+ var mouseInGutter = false;
+ if (aGutterSelect) {
+ var rect = tbo.getCoordsForCellItem(cell.row, cell.col, "image");
+ // getCoordsForCellItem returns the x coordinate in logical coordinates
+ // (i.e., starting from the left and right sides in LTR and RTL modes,
+ // respectively.) Therefore, we make sure to exclude the blank area
+ // before the tree item icon (that is, to the left or right of it in
+ // LTR and RTL modes, respectively) from the click target area.
+ var isRTL = window.getComputedStyle(aTree, null).direction == "rtl";
+ if (isRTL)
+ mouseInGutter = aEvent.clientX > rect.x;
+ else
+ mouseInGutter = aEvent.clientX < rect.x;
+ }
+
+ var metaKey = AppConstants.platform === "macosx" ? aEvent.metaKey
+ : aEvent.ctrlKey;
+ var modifKey = metaKey || aEvent.shiftKey;
+ var isContainer = tbo.view.isContainer(cell.row);
+ var openInTabs = isContainer &&
+ (aEvent.button == 1 ||
+ (aEvent.button == 0 && modifKey)) &&
+ PlacesUtils.hasChildURIs(tbo.view.nodeForTreeIndex(cell.row));
+
+ if (aEvent.button == 0 && isContainer && !openInTabs) {
+ tbo.view.toggleOpenState(cell.row);
+ return;
+ }
+ else if (!mouseInGutter && openInTabs &&
+ aEvent.originalTarget.localName == "treechildren") {
+ tbo.view.selection.select(cell.row);
+ PlacesUIUtils.openContainerNodeInTabs(aTree.selectedNode, aEvent, aTree);
+ }
+ else if (!mouseInGutter && !isContainer &&
+ aEvent.originalTarget.localName == "treechildren") {
+ // Clear all other selection since we're loading a link now. We must
+ // do this *before* attempting to load the link since openURL uses
+ // selection as an indication of which link to load.
+ tbo.view.selection.select(cell.row);
+ PlacesUIUtils.openNodeWithEvent(aTree.selectedNode, aEvent, aTree);
+ }
+ },
+
+ handleTreeKeyPress: function SU_handleTreeKeyPress(aEvent) {
+ // XXX Bug 627901: Post Fx4, this method should take a tree parameter.
+ let tree = aEvent.target;
+ let node = tree.selectedNode;
+ if (node) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
+ PlacesUIUtils.openNodeWithEvent(node, aEvent, tree);
+ }
+ },
+
+ /**
+ * The following function displays the URL of a node that is being
+ * hovered over.
+ */
+ handleTreeMouseMove: function SU_handleTreeMouseMove(aEvent) {
+ if (aEvent.target.localName != "treechildren")
+ return;
+
+ var tree = aEvent.target.parentNode;
+ var tbo = tree.treeBoxObject;
+ var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
+
+ // cell.row is -1 when the mouse is hovering an empty area within the tree.
+ // To avoid showing a URL from a previously hovered node for a currently
+ // hovered non-url node, we must clear the moused-over URL in these cases.
+ if (cell.row != -1) {
+ var node = tree.view.nodeForTreeIndex(cell.row);
+ if (PlacesUtils.nodeIsURI(node))
+ this.setMouseoverURL(node.uri);
+ else
+ this.setMouseoverURL("");
+ }
+ else
+ this.setMouseoverURL("");
+ },
+
+ setMouseoverURL: function SU_setMouseoverURL(aURL) {
+ // When the browser window is closed with an open sidebar, the sidebar
+ // unload event happens after the browser's one. In this case
+ // top.XULBrowserWindow has been nullified already.
+ if (top.XULBrowserWindow) {
+ top.XULBrowserWindow.setOverLink(aURL, null);
+ }
+ }
+};
diff --git a/browser/components/places/content/tree.xml b/browser/components/places/content/tree.xml
new file mode 100644
index 000000000..338ee81f6
--- /dev/null
+++ b/browser/components/places/content/tree.xml
@@ -0,0 +1,801 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="placesTreeBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree">
+ <implementation>
+ <constructor><![CDATA[
+ // Force an initial build.
+ if (this.place)
+ this.place = this.place;
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ // Break the treeviewer->result->treeviewer cycle.
+ // Note: unsetting the result's viewer also unsets
+ // the viewer's reference to our treeBoxObject.
+ var result = this.result;
+ if (result) {
+ result.root.containerOpen = false;
+ }
+
+ // Unregister the controllber before unlinking the view, otherwise it
+ // may still try to update commands on a view with a null result.
+ if (this._controller) {
+ this._controller.terminate();
+ this.controllers.removeController(this._controller);
+ }
+
+ this.view = null;
+ ]]></destructor>
+
+ <property name="controller"
+ readonly="true"
+ onget="return this._controller"/>
+
+ <!-- overriding -->
+ <property name="view">
+ <getter><![CDATA[
+ try {
+ return this.treeBoxObject.view.wrappedJSObject || null;
+ }
+ catch (e) {
+ return null;
+ }
+ ]]></getter>
+ <setter><![CDATA[
+ return this.treeBoxObject.view = val;
+ ]]></setter>
+ </property>
+
+ <property name="associatedElement"
+ readonly="true"
+ onget="return this"/>
+
+ <method name="applyFilter">
+ <parameter name="filterString"/>
+ <parameter name="folderRestrict"/>
+ <parameter name="includeHidden"/>
+ <body><![CDATA[
+ // preserve grouping
+ var queryNode = PlacesUtils.asQuery(this.result.root);
+ var options = queryNode.queryOptions.clone();
+
+ // Make sure we're getting uri results.
+ // We do not yet support searching into grouped queries or into
+ // tag containers, so we must fall to the default case.
+ if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
+ options.resultType == options.RESULTS_AS_TAG_QUERY ||
+ options.resultType == options.RESULTS_AS_TAG_CONTENTS)
+ options.resultType = options.RESULTS_AS_URI;
+
+ var query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+
+ if (folderRestrict) {
+ query.setFolders(folderRestrict, folderRestrict.length);
+ options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ }
+
+ options.includeHidden = !!includeHidden;
+
+ this.load([query], options);
+ ]]></body>
+ </method>
+
+ <method name="load">
+ <parameter name="queries"/>
+ <parameter name="options"/>
+ <body><![CDATA[
+ let result = PlacesUtils.history
+ .executeQueries(queries, queries.length,
+ options);
+ let callback;
+ if (this.flatList) {
+ let onOpenFlatContainer = this.onOpenFlatContainer;
+ if (onOpenFlatContainer)
+ callback = new Function("aContainer", onOpenFlatContainer);
+ }
+
+ if (!this._controller) {
+ this._controller = new PlacesController(this);
+ this.controllers.appendController(this._controller);
+ }
+
+ let treeView = new PlacesTreeView(this.flatList, callback, this._controller);
+
+ // Observer removal is done within the view itself. When the tree
+ // goes away, treeboxobject calls view.setTree(null), which then
+ // calls removeObserver.
+ result.addObserver(treeView, false);
+ this.view = treeView;
+
+ if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) {
+ treeView.selection.select(0);
+ }
+
+ this._cachedInsertionPoint = undefined;
+ ]]></body>
+ </method>
+
+ <property name="flatList">
+ <getter><![CDATA[
+ return this.getAttribute("flatList") == "true";
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.flatList != val) {
+ this.setAttribute("flatList", val);
+ // reload with the last place set
+ if (this.place)
+ this.place = this.place;
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="onOpenFlatContainer">
+ <getter><![CDATA[
+ return this.getAttribute("onopenflatcontainer");
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.onOpenFlatContainer != val) {
+ this.setAttribute("onopenflatcontainer", val);
+ // reload with the last place set
+ if (this.place)
+ this.place = this.place;
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <!--
+ Causes a particular node represented by the specified placeURI to be
+ selected in the tree. All containers above the node in the hierarchy
+ will be opened, so that the node is visible.
+ -->
+ <method name="selectPlaceURI">
+ <parameter name="placeURI"/>
+ <body><![CDATA[
+ // Do nothing if a node matching the given uri is already selected
+ if (this.hasSelection && this.selectedNode.uri == placeURI)
+ return;
+
+ function findNode(container, placeURI, nodesURIChecked) {
+ var containerURI = container.uri;
+ if (containerURI == placeURI)
+ return container;
+ if (nodesURIChecked.includes(containerURI))
+ return null;
+
+ // never check the contents of the same query
+ nodesURIChecked.push(containerURI);
+
+ var wasOpen = container.containerOpen;
+ if (!wasOpen)
+ container.containerOpen = true;
+ for (var i = 0; i < container.childCount; ++i) {
+ var child = container.getChild(i);
+ var childURI = child.uri;
+ if (childURI == placeURI)
+ return child;
+ else if (PlacesUtils.nodeIsContainer(child)) {
+ var nested = findNode(PlacesUtils.asContainer(child), placeURI, nodesURIChecked);
+ if (nested)
+ return nested;
+ }
+ }
+
+ if (!wasOpen)
+ container.containerOpen = false;
+
+ return null;
+ }
+
+ var container = this.result.root;
+ NS_ASSERT(container, "No result, cannot select place URI!");
+ if (!container)
+ return;
+
+ var child = findNode(container, placeURI, []);
+ if (child)
+ this.selectNode(child);
+ else {
+ // If the specified child could not be located, clear the selection
+ var selection = this.view.selection;
+ selection.clearSelection();
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ Causes a particular node to be selected in the tree, resulting in all
+ containers above the node in the hierarchy to be opened, so that the
+ node is visible.
+ -->
+ <method name="selectNode">
+ <parameter name="node"/>
+ <body><![CDATA[
+ var view = this.view;
+
+ var parent = node.parent;
+ if (parent && !parent.containerOpen) {
+ // Build a list of all of the nodes that are the parent of this one
+ // in the result.
+ var parents = [];
+ var root = this.result.root;
+ while (parent && parent != root) {
+ parents.push(parent);
+ parent = parent.parent;
+ }
+
+ // Walk the list backwards (opening from the root of the hierarchy)
+ // opening each folder as we go.
+ for (var i = parents.length - 1; i >= 0; --i) {
+ let index = view.treeIndexForNode(parents[i]);
+ if (index != Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE &&
+ view.isContainer(index) && !view.isContainerOpen(index))
+ view.toggleOpenState(index);
+ }
+ // Select the specified node...
+ }
+
+ let index = view.treeIndexForNode(node);
+ if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
+ return;
+
+ view.selection.select(index);
+ // ... and ensure it's visible, not scrolled off somewhere.
+ this.treeBoxObject.ensureRowIsVisible(index);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <property name="result">
+ <getter><![CDATA[
+ try {
+ return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result;
+ }
+ catch (e) {
+ return null;
+ }
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="place">
+ <getter><![CDATA[
+ return this.getAttribute("place");
+ ]]></getter>
+ <setter><![CDATA[
+ this.setAttribute("place", val);
+
+ var queriesRef = { };
+ var queryCountRef = { };
+ var optionsRef = { };
+ PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef);
+ if (queryCountRef.value == 0)
+ queriesRef.value = [PlacesUtils.history.getNewQuery()];
+ if (!optionsRef.value)
+ optionsRef.value = PlacesUtils.history.getNewQueryOptions();
+
+ this.load(queriesRef.value, optionsRef.value);
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="hasSelection">
+ <getter><![CDATA[
+ return this.view && this.view.selection.count >= 1;
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="selectedNodes">
+ <getter><![CDATA[
+ let nodes = [];
+ if (!this.hasSelection)
+ return nodes;
+
+ let selection = this.view.selection;
+ let rc = selection.getRangeCount();
+ let resultview = this.view;
+ for (let i = 0; i < rc; ++i) {
+ let min = { }, max = { };
+ selection.getRangeAt(i, min, max);
+ for (let j = min.value; j <= max.value; ++j) {
+ nodes.push(resultview.nodeForTreeIndex(j));
+ }
+ }
+ return nodes;
+ ]]></getter>
+ </property>
+
+ <method name="toggleCutNode">
+ <parameter name="aNode"/>
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this.view.toggleCutNode(aNode, aValue);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <property name="removableSelectionRanges">
+ <getter><![CDATA[
+ // This property exists in addition to selectedNodes because it
+ // encodes selection ranges (which only occur in list views) into
+ // the return value. For each removed range, the index at which items
+ // will be re-inserted upon the remove transaction being performed is
+ // the first index of the range, so that the view updates correctly.
+ //
+ // For example, if we remove rows 2,3,4 and 7,8 from a list, when we
+ // undo that operation, if we insert what was at row 3 at row 3 again,
+ // it will show up _after_ the item that was at row 5. So we need to
+ // insert all items at row 2, and the tree view will update correctly.
+ //
+ // Also, this function collapses the selection to remove redundant
+ // data, e.g. when deleting this selection:
+ //
+ // http://www.foo.com/
+ // (-) Some Folder
+ // http://www.bar.com/
+ //
+ // ... returning http://www.bar.com/ as part of the selection is
+ // redundant because it is implied by removing "Some Folder". We
+ // filter out all such redundancies since some partial amount of
+ // the folder's children may be selected.
+ //
+ let nodes = [];
+ if (!this.hasSelection)
+ return nodes;
+
+ var selection = this.view.selection;
+ var rc = selection.getRangeCount();
+ var resultview = this.view;
+ // This list is kept independently of the range selected (i.e. OUTSIDE
+ // the for loop) since the row index of a container is unique for the
+ // entire view, and we could have some really wacky selection and we
+ // don't want to blow up.
+ var containers = { };
+ for (var i = 0; i < rc; ++i) {
+ var range = [];
+ var min = { }, max = { };
+ selection.getRangeAt(i, min, max);
+
+ for (var j = min.value; j <= max.value; ++j) {
+ if (this.view.isContainer(j))
+ containers[j] = true;
+ if (!(this.view.getParentIndex(j) in containers))
+ range.push(resultview.nodeForTreeIndex(j));
+ }
+ nodes.push(range);
+ }
+ return nodes;
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="draggableSelection"
+ onget="return this.selectedNodes"/>
+
+ <!-- nsIPlacesView -->
+ <property name="selectedNode">
+ <getter><![CDATA[
+ var view = this.view;
+ if (!view || view.selection.count != 1)
+ return null;
+
+ var selection = view.selection;
+ var min = { }, max = { };
+ selection.getRangeAt(0, min, max);
+
+ return this.view.nodeForTreeIndex(min.value);
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="insertionPoint">
+ <getter><![CDATA[
+ // invalidated on selection and focus changes
+ if (this._cachedInsertionPoint !== undefined)
+ return this._cachedInsertionPoint;
+
+ // there is no insertion point for history queries
+ // so bail out now and save a lot of work when updating commands
+ var resultNode = this.result.root;
+ if (PlacesUtils.nodeIsQuery(resultNode) &&
+ PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
+ return this._cachedInsertionPoint = null;
+
+ var orientation = Ci.nsITreeView.DROP_BEFORE;
+ // If there is no selection, insert at the end of the container.
+ if (!this.hasSelection) {
+ var index = this.view.rowCount - 1;
+ this._cachedInsertionPoint =
+ this._getInsertionPoint(index, orientation);
+ return this._cachedInsertionPoint;
+ }
+
+ // This is a two-part process. The first part is determining the drop
+ // orientation.
+ // * The default orientation is to drop _before_ the selected item.
+ // * If the selected item is a container, the default orientation
+ // is to drop _into_ that container.
+ //
+ // Warning: It may be tempting to use tree indexes in this code, but
+ // you must not, since the tree is nested and as your tree
+ // index may change when folders before you are opened and
+ // closed. You must convert your tree index to a node, and
+ // then use getChildIndex to find your absolute index in
+ // the parent container instead.
+ //
+ var resultView = this.view;
+ var selection = resultView.selection;
+ var rc = selection.getRangeCount();
+ var min = { }, max = { };
+ selection.getRangeAt(rc - 1, min, max);
+
+ // If the sole selection is a container, and we are not in
+ // a flatlist, insert into it.
+ // Note that this only applies to _single_ selections,
+ // if the last element within a multi-selection is a
+ // container, insert _adjacent_ to the selection.
+ //
+ // If the sole selection is the bookmarks toolbar folder, we insert
+ // into it even if it is not opened
+ if (selection.count == 1 && resultView.isContainer(max.value) &&
+ !this.flatList)
+ orientation = Ci.nsITreeView.DROP_ON;
+
+ this._cachedInsertionPoint =
+ this._getInsertionPoint(max.value, orientation);
+ return this._cachedInsertionPoint;
+ ]]></getter>
+ </property>
+
+ <method name="_getInsertionPoint">
+ <parameter name="index"/>
+ <parameter name="orientation"/>
+ <body><![CDATA[
+ var result = this.result;
+ var resultview = this.view;
+ var container = result.root;
+ var dropNearItemId = -1;
+ NS_ASSERT(container, "null container");
+ // When there's no selection, assume the container is the container
+ // the view is populated from (i.e. the result's itemId).
+ if (index != -1) {
+ var lastSelected = resultview.nodeForTreeIndex(index);
+ if (resultview.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
+ // If the last selected item is an open container, append _into_
+ // it, rather than insert adjacent to it.
+ container = lastSelected;
+ index = -1;
+ }
+ else if (lastSelected.containerOpen &&
+ orientation == Ci.nsITreeView.DROP_AFTER &&
+ lastSelected.hasChildren) {
+ // If the last selected item is an open container and the user is
+ // trying to drag into it as a first item, really insert into it.
+ container = lastSelected;
+ orientation = Ci.nsITreeView.DROP_ON;
+ index = 0;
+ }
+ else {
+ // Use the last-selected node's container.
+ container = lastSelected.parent;
+
+ // See comment in the treeView.js's copy of this method
+ if (!container || !container.containerOpen)
+ return null;
+
+ // Avoid the potentially expensive call to getChildIndex
+ // if we know this container doesn't allow insertion
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ var queryOptions = PlacesUtils.asQuery(result.root).queryOptions;
+ if (queryOptions.sortingMode !=
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
+ // If we are within a sorted view, insert at the end
+ index = -1;
+ }
+ else if (queryOptions.excludeItems ||
+ queryOptions.excludeQueries ||
+ queryOptions.excludeReadOnlyFolders) {
+ // Some item may be invisible, insert near last selected one.
+ // We don't replace index here to avoid requests to the db,
+ // instead it will be calculated later by the controller.
+ index = -1;
+ dropNearItemId = lastSelected.itemId;
+ }
+ else {
+ var lsi = container.getChildIndex(lastSelected);
+ index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
+ }
+ }
+ }
+
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ // TODO (Bug 1160193): properly support dropping on a tag root.
+ let tagName = null;
+ if (PlacesUtils.nodeIsTagQuery(container)) {
+ tagName = container.title;
+ if (!tagName)
+ return null;
+ }
+
+ return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
+ index, orientation,
+ tagName,
+ dropNearItemId);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <method name="selectAll">
+ <body><![CDATA[
+ this.view.selection.selectAll();
+ ]]></body>
+ </method>
+
+ <!-- This method will select the first node in the tree that matches
+ each given item id. It will open any folder nodes that it needs
+ to in order to show the selected items.
+ -->
+ <method name="selectItems">
+ <parameter name="aIDs"/>
+ <parameter name="aOpenContainers"/>
+ <body><![CDATA[
+ // Never open containers in flat lists.
+ if (this.flatList)
+ aOpenContainers = false;
+ // By default, we do search and select within containers which were
+ // closed (note that containers in which nodes were not found are
+ // closed).
+ if (aOpenContainers === undefined)
+ aOpenContainers = true;
+
+ var ids = aIDs; // don't manipulate the caller's array
+
+ // Array of nodes found by findNodes which are to be selected
+ var nodes = [];
+
+ // Array of nodes found by findNodes which should be opened
+ var nodesToOpen = [];
+
+ // A set of GUIDs of container-nodes that were previously searched,
+ // and thus shouldn't be searched again. This is empty at the initial
+ // start of the recursion and gets filled in as the recursion
+ // progresses.
+ var checkedGuidsSet = new Set();
+
+ /**
+ * Recursively search through a node's children for items
+ * with the given IDs. When a matching item is found, remove its ID
+ * from the IDs array, and add the found node to the nodes dictionary.
+ *
+ * NOTE: This method will leave open any node that had matching items
+ * in its subtree.
+ */
+ function findNodes(node) {
+ var foundOne = false;
+ // See if node matches an ID we wanted; add to results.
+ // For simple folder queries, check both itemId and the concrete
+ // item id.
+ var index = ids.indexOf(node.itemId);
+ if (index == -1 &&
+ node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
+ index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId);
+
+ if (index != -1) {
+ nodes.push(node);
+ foundOne = true;
+ ids.splice(index, 1);
+ }
+
+ var concreteGuid = PlacesUtils.getConcreteItemGuid(node);
+ if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
+ checkedGuidsSet.has(concreteGuid))
+ return foundOne;
+
+ // Only follow a query if it has been been explicitly opened by the caller.
+ let shouldOpen = aOpenContainers && PlacesUtils.nodeIsFolder(node);
+ PlacesUtils.asContainer(node);
+ if (!node.containerOpen && !shouldOpen)
+ return foundOne;
+
+ checkedGuidsSet.add(concreteGuid);
+
+ // Remember the beginning state so that we can re-close
+ // this node if we don't find any additional results here.
+ var previousOpenness = node.containerOpen;
+ node.containerOpen = true;
+ for (var child = 0; child < node.childCount && ids.length > 0;
+ child++) {
+ var childNode = node.getChild(child);
+ var found = findNodes(childNode);
+ if (!foundOne)
+ foundOne = found;
+ }
+
+ // If we didn't find any additional matches in this node's
+ // subtree, revert the node to its previous openness.
+ if (foundOne)
+ nodesToOpen.unshift(node);
+ node.containerOpen = previousOpenness;
+ return foundOne;
+ }
+
+ // Disable notifications while looking for nodes.
+ let result = this.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true
+ try {
+ findNodes(this.result.root);
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+
+ // For all the nodes we've found, highlight the corresponding
+ // index in the tree.
+ var resultview = this.view;
+ var selection = this.view.selection;
+ selection.selectEventsSuppressed = true;
+ selection.clearSelection();
+ // Open nodes containing found items
+ for (let i = 0; i < nodesToOpen.length; i++) {
+ nodesToOpen[i].containerOpen = true;
+ }
+ for (let i = 0; i < nodes.length; i++) {
+ var index = resultview.treeIndexForNode(nodes[i]);
+ if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
+ continue;
+ selection.rangedSelect(index, index, true);
+ }
+ selection.selectEventsSuppressed = false;
+ ]]></body>
+ </method>
+
+ <field name="_contextMenuShown">false</field>
+
+ <method name="buildContextMenu">
+ <parameter name="aPopup"/>
+ <body><![CDATA[
+ this._contextMenuShown = true;
+ return this.controller.buildContextMenu(aPopup);
+ ]]></body>
+ </method>
+
+ <method name="destroyContextMenu">
+ <parameter name="aPopup"/>
+ this._contextMenuShown = false;
+ <body/>
+ </method>
+
+ <property name="ownerWindow"
+ readonly="true"
+ onget="return window;"/>
+
+ <field name="_active">true</field>
+ <property name="active"
+ onget="return this._active"
+ onset="return this._active = val"/>
+
+ </implementation>
+ <handlers>
+ <handler event="focus"><![CDATA[
+ this._cachedInsertionPoint = undefined;
+
+ // See select handler. We need the sidebar's places commandset to be
+ // updated as well
+ document.commandDispatcher.updateCommands("focus");
+ ]]></handler>
+ <handler event="select"><![CDATA[
+ this._cachedInsertionPoint = undefined;
+
+ // This additional complexity is here for the sidebars
+ var win = window;
+ while (true) {
+ win.document.commandDispatcher.updateCommands("focus");
+ if (win == window.top)
+ break;
+
+ win = win.parent;
+ }
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ if (event.target.localName != "treechildren")
+ return;
+
+ let nodes = this.selectedNodes;
+ for (let i = 0; i < nodes.length; i++) {
+ let node = nodes[i];
+
+ // Disallow dragging the root node of a tree.
+ if (!node.parent) {
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ // If this node is child of a readonly container (e.g. a livemark)
+ // or cannot be moved, we must force a copy.
+ if (!PlacesControllerDragHelper.canMoveNode(node)) {
+ event.dataTransfer.effectAllowed = "copyLink";
+ break;
+ }
+ }
+
+ this._controller.setDataTransfer(event);
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ if (event.target.localName != "treechildren")
+ return;
+
+ let cell = this.treeBoxObject.getCellAt(event.clientX, event.clientY);
+ let node = cell.row != -1 ?
+ this.view.nodeForTreeIndex(cell.row) :
+ this.result.root;
+ // cache the dropTarget for the view
+ PlacesControllerDragHelper.currentDropTarget = node;
+
+ // We have to calculate the orientation since view.canDrop will use
+ // it and we want to be consistent with the dropfeedback.
+ let tbo = this.treeBoxObject;
+ let rowHeight = tbo.rowHeight;
+ let eventY = event.clientY - tbo.treeBody.boxObject.y -
+ rowHeight * (cell.row - tbo.getFirstVisibleRow());
+
+ let orientation = Ci.nsITreeView.DROP_BEFORE;
+
+ if (cell.row == -1) {
+ // If the row is not valid we try to insert inside the resultNode.
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+ else if (PlacesUtils.nodeIsContainer(node) &&
+ eventY > rowHeight * 0.75) {
+ // If we are below the 75% of a container the treeview we try
+ // to drop after the node.
+ orientation = Ci.nsITreeView.DROP_AFTER;
+ }
+ else if (PlacesUtils.nodeIsContainer(node) &&
+ eventY > rowHeight * 0.25) {
+ // If we are below the 25% of a container the treeview we try
+ // to drop inside the node.
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+
+ if (!this.view.canDrop(cell.row, orientation, event.dataTransfer))
+ return;
+
+ event.preventDefault();
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = null;
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/browser/components/places/content/treeView.js b/browser/components/places/content/treeView.js
new file mode 100644
index 000000000..5baf3a21f
--- /dev/null
+++ b/browser/components/places/content/treeView.js
@@ -0,0 +1,1726 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+
+const PTV_interfaces = [Ci.nsITreeView,
+ Ci.nsINavHistoryResultObserver,
+ Ci.nsINavHistoryResultTreeViewer,
+ Ci.nsISupportsWeakReference];
+
+function PlacesTreeView(aFlatList, aOnOpenFlatContainer, aController) {
+ this._tree = null;
+ this._result = null;
+ this._selection = null;
+ this._rootNode = null;
+ this._rows = [];
+ this._flatList = aFlatList;
+ this._openContainerCallback = aOnOpenFlatContainer;
+ this._controller = aController;
+}
+
+PlacesTreeView.prototype = {
+ get wrappedJSObject() {
+ return this;
+ },
+
+ __xulStore: null,
+ get _xulStore() {
+ if (!this.__xulStore) {
+ this.__xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+ }
+ return this.__xulStore;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(PTV_interfaces),
+
+ // Bug 761494:
+ // ----------
+ // Some addons use methods from nsINavHistoryResultObserver and
+ // nsINavHistoryResultTreeViewer, without QIing to these interfaces first.
+ // That's not a problem when the view is retrieved through the
+ // <tree>.view getter (which returns the wrappedJSObject of this object),
+ // it raises an issue when the view retrieved through the treeBoxObject.view
+ // getter. Thus, to avoid breaking addons, the interfaces are prefetched.
+ classInfo: XPCOMUtils.generateCI({ interfaces: PTV_interfaces }),
+
+ /**
+ * This is called once both the result and the tree are set.
+ */
+ _finishInit: function PTV__finishInit() {
+ let selection = this.selection;
+ if (selection)
+ selection.selectEventsSuppressed = true;
+
+ if (!this._rootNode.containerOpen) {
+ // This triggers containerStateChanged which then builds the visible
+ // section.
+ this._rootNode.containerOpen = true;
+ }
+ else
+ this.invalidateContainer(this._rootNode);
+
+ // "Activate" the sorting column and update commands.
+ this.sortingChanged(this._result.sortingMode);
+
+ if (selection)
+ selection.selectEventsSuppressed = false;
+ },
+
+ /**
+ * Plain Container: container result nodes which may never include sub
+ * hierarchies.
+ *
+ * When the rows array is constructed, we don't set the children of plain
+ * containers. Instead, we keep placeholders for these children. We then
+ * build these children lazily as the tree asks us for information about each
+ * row. Luckily, the tree doesn't ask about rows outside the visible area.
+ *
+ * @see _getNodeForRow and _getRowForNode for the actual magic.
+ *
+ * @note It's guaranteed that all containers are listed in the rows
+ * elements array. It's also guaranteed that separators (if they're not
+ * filtered, see below) are listed in the visible elements array, because
+ * bookmark folders are never built lazily, as described above.
+ *
+ * @param aContainer
+ * A container result node.
+ *
+ * @return true if aContainer is a plain container, false otherwise.
+ */
+ _isPlainContainer: function PTV__isPlainContainer(aContainer) {
+ // Livemarks are always plain containers.
+ if (this._controller.hasCachedLivemarkInfo(aContainer))
+ return true;
+
+ // We don't know enough about non-query containers.
+ if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode))
+ return false;
+
+ switch (aContainer.queryOptions.resultType) {
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY:
+ return false;
+ }
+
+ // If it's a folder, it's not a plain container.
+ let nodeType = aContainer.type;
+ return nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER &&
+ nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
+ },
+
+ /**
+ * Gets the row number for a given node. Assumes that the given node is
+ * visible (i.e. it's not an obsolete node).
+ *
+ * @param aNode
+ * A result node. Do not pass an obsolete node, or any
+ * node which isn't supposed to be in the tree (e.g. separators in
+ * sorted trees).
+ * @param [optional] aForceBuild
+ * @see _isPlainContainer.
+ * If true, the row will be computed even if the node still isn't set
+ * in our rows array.
+ * @param [optional] aParentRow
+ * The row of aNode's parent. Ignored for the root node.
+ * @param [optional] aNodeIndex
+ * The index of aNode in its parent. Only used if aParentRow is
+ * set too.
+ *
+ * @throws if aNode is invisible.
+ * @note If aParentRow and aNodeIndex are passed and parent is a plain
+ * container, this method will just return a calculated row value, without
+ * making assumptions on existence of the node at that position.
+ * @return aNode's row if it's in the rows list or if aForceBuild is set, -1
+ * otherwise.
+ */
+ _getRowForNode:
+ function PTV__getRowForNode(aNode, aForceBuild, aParentRow, aNodeIndex) {
+ if (aNode == this._rootNode)
+ throw new Error("The root node is never visible");
+
+ // A node is removed form the view either if it has no parent or if its
+ // root-ancestor is not the root node (in which case that's the node
+ // for which nodeRemoved was called).
+ let ancestors = Array.from(PlacesUtils.nodeAncestors(aNode));
+ if (ancestors.length == 0 ||
+ ancestors[ancestors.length - 1] != this._rootNode) {
+ throw new Error("Removed node passed to _getRowForNode");
+ }
+
+ // Ensure that the entire chain is open, otherwise that node is invisible.
+ for (let ancestor of ancestors) {
+ if (!ancestor.containerOpen)
+ throw new Error("Invisible node passed to _getRowForNode");
+ }
+
+ // Non-plain containers are initially built with their contents.
+ let parent = aNode.parent;
+ let parentIsPlain = this._isPlainContainer(parent);
+ if (!parentIsPlain) {
+ if (parent == this._rootNode)
+ return this._rows.indexOf(aNode);
+
+ return this._rows.indexOf(aNode, aParentRow);
+ }
+
+ let row = -1;
+ let useNodeIndex = typeof(aNodeIndex) == "number";
+ if (parent == this._rootNode) {
+ row = useNodeIndex ? aNodeIndex : this._rootNode.getChildIndex(aNode);
+ }
+ else if (useNodeIndex && typeof(aParentRow) == "number") {
+ // If we have both the row of the parent node, and the node's index, we
+ // can avoid searching the rows array if the parent is a plain container.
+ row = aParentRow + aNodeIndex + 1;
+ }
+ else {
+ // Look for the node in the nodes array. Start the search at the parent
+ // row. If the parent row isn't passed, we'll pass undefined to indexOf,
+ // which is fine.
+ row = this._rows.indexOf(aNode, aParentRow);
+ if (row == -1 && aForceBuild) {
+ let parentRow = typeof(aParentRow) == "number" ? aParentRow
+ : this._getRowForNode(parent);
+ row = parentRow + parent.getChildIndex(aNode) + 1;
+ }
+ }
+
+ if (row != -1)
+ this._rows[row] = aNode;
+
+ return row;
+ },
+
+ /**
+ * Given a row, finds and returns the parent details of the associated node.
+ *
+ * @param aChildRow
+ * Row number.
+ * @return [parentNode, parentRow]
+ */
+ _getParentByChildRow: function PTV__getParentByChildRow(aChildRow) {
+ let node = this._getNodeForRow(aChildRow);
+ let parent = (node === null) ? this._rootNode : node.parent;
+
+ // The root node is never visible
+ if (parent == this._rootNode)
+ return [this._rootNode, -1];
+
+ let parentRow = this._rows.lastIndexOf(parent, aChildRow - 1);
+ return [parent, parentRow];
+ },
+
+ /**
+ * Gets the node at a given row.
+ */
+ _getNodeForRow: function PTV__getNodeForRow(aRow) {
+ if (aRow < 0) {
+ return null;
+ }
+
+ let node = this._rows[aRow];
+ if (node !== undefined)
+ return node;
+
+ // Find the nearest node.
+ let rowNode, row;
+ for (let i = aRow - 1; i >= 0 && rowNode === undefined; i--) {
+ rowNode = this._rows[i];
+ row = i;
+ }
+
+ // If there's no container prior to the given row, it's a child of
+ // the root node (remember: all containers are listed in the rows array).
+ if (!rowNode)
+ return this._rows[aRow] = this._rootNode.getChild(aRow);
+
+ // Unset elements may exist only in plain containers. Thus, if the nearest
+ // node is a container, it's the row's parent, otherwise, it's a sibling.
+ if (rowNode instanceof Ci.nsINavHistoryContainerResultNode)
+ return this._rows[aRow] = rowNode.getChild(aRow - row - 1);
+
+ let [parent, parentRow] = this._getParentByChildRow(row);
+ return this._rows[aRow] = parent.getChild(aRow - parentRow - 1);
+ },
+
+ /**
+ * This takes a container and recursively appends our rows array per its
+ * contents. Assumes that the rows arrays has no rows for the given
+ * container.
+ *
+ * @param [in] aContainer
+ * A container result node.
+ * @param [in] aFirstChildRow
+ * The first row at which nodes may be inserted to the row array.
+ * In other words, that's aContainer's row + 1.
+ * @param [out] aToOpen
+ * An array of containers to open once the build is done.
+ *
+ * @return the number of rows which were inserted.
+ */
+ _buildVisibleSection:
+ function PTV__buildVisibleSection(aContainer, aFirstChildRow, aToOpen)
+ {
+ // There's nothing to do if the container is closed.
+ if (!aContainer.containerOpen)
+ return 0;
+
+ // Inserting the new elements into the rows array in one shot (by
+ // Array.prototype.concat) is faster than resizing the array (by splice) on each loop
+ // iteration.
+ let cc = aContainer.childCount;
+ let newElements = new Array(cc);
+ this._rows = this._rows.splice(0, aFirstChildRow)
+ .concat(newElements, this._rows);
+
+ if (this._isPlainContainer(aContainer))
+ return cc;
+
+ let sortingMode = this._result.sortingMode;
+
+ let rowsInserted = 0;
+ for (let i = 0; i < cc; i++) {
+ let curChild = aContainer.getChild(i);
+ let curChildType = curChild.type;
+
+ let row = aFirstChildRow + rowsInserted;
+
+ // Don't display separators when sorted.
+ if (curChildType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
+ if (sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
+ // Remove the element for the filtered separator.
+ // Notice that the rows array was initially resized to include all
+ // children.
+ this._rows.splice(row, 1);
+ continue;
+ }
+ }
+
+ this._rows[row] = curChild;
+ rowsInserted++;
+
+ // Recursively do containers.
+ if (!this._flatList &&
+ curChild instanceof Ci.nsINavHistoryContainerResultNode &&
+ !this._controller.hasCachedLivemarkInfo(curChild)) {
+ let uri = curChild.uri;
+ let isopen = false;
+
+ if (uri) {
+ let val = this._xulStore.getValue(document.documentURI, uri, "open");
+ isopen = (val == "true");
+ }
+
+ if (isopen != curChild.containerOpen)
+ aToOpen.push(curChild);
+ else if (curChild.containerOpen && curChild.childCount > 0)
+ rowsInserted += this._buildVisibleSection(curChild, row + 1, aToOpen);
+ }
+ }
+
+ return rowsInserted;
+ },
+
+ /**
+ * This counts how many rows a node takes in the tree. For containers it
+ * will count the node itself plus any child node following it.
+ */
+ _countVisibleRowsForNodeAtRow:
+ function PTV__countVisibleRowsForNodeAtRow(aNodeRow) {
+ let node = this._rows[aNodeRow];
+
+ // If it's not listed yet, we know that it's a leaf node (instanceof also
+ // null-checks).
+ if (!(node instanceof Ci.nsINavHistoryContainerResultNode))
+ return 1;
+
+ let outerLevel = node.indentLevel;
+ for (let i = aNodeRow + 1; i < this._rows.length; i++) {
+ let rowNode = this._rows[i];
+ if (rowNode && rowNode.indentLevel <= outerLevel)
+ return i - aNodeRow;
+ }
+
+ // This node plus its children take up the bottom of the list.
+ return this._rows.length - aNodeRow;
+ },
+
+ _getSelectedNodesInRange:
+ function PTV__getSelectedNodesInRange(aFirstRow, aLastRow) {
+ let selection = this.selection;
+ let rc = selection.getRangeCount();
+ if (rc == 0)
+ return [];
+
+ // The visible-area borders are needed for checking whether a
+ // selected row is also visible.
+ let firstVisibleRow = this._tree.getFirstVisibleRow();
+ let lastVisibleRow = this._tree.getLastVisibleRow();
+
+ let nodesInfo = [];
+ for (let rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
+ let min = { }, max = { };
+ selection.getRangeAt(rangeIndex, min, max);
+
+ // If this range does not overlap the replaced chunk, we don't need to
+ // persist the selection.
+ if (max.value < aFirstRow || min.value > aLastRow)
+ continue;
+
+ let firstRow = Math.max(min.value, aFirstRow);
+ let lastRow = Math.min(max.value, aLastRow);
+ for (let i = firstRow; i <= lastRow; i++) {
+ nodesInfo.push({
+ node: this._rows[i],
+ oldRow: i,
+ wasVisible: i >= firstVisibleRow && i <= lastVisibleRow
+ });
+ }
+ }
+
+ return nodesInfo;
+ },
+
+ /**
+ * Tries to find an equivalent node for a node which was removed. We first
+ * look for the original node, in case it was just relocated. Then, if we
+ * that node was not found, we look for a node that has the same itemId, uri
+ * and time values.
+ *
+ * @param aUpdatedContainer
+ * An ancestor of the node which was removed. It does not have to be
+ * its direct parent.
+ * @param aOldNode
+ * The node which was removed.
+ *
+ * @return the row number of an equivalent node for aOldOne, if one was
+ * found, -1 otherwise.
+ */
+ _getNewRowForRemovedNode:
+ function PTV__getNewRowForRemovedNode(aUpdatedContainer, aOldNode) {
+ let parent = aOldNode.parent;
+ if (parent) {
+ // If the node's parent is still set, the node is not obsolete
+ // and we should just find out its new position.
+ // However, if any of the node's ancestor is closed, the node is
+ // invisible.
+ let ancestors = PlacesUtils.nodeAncestors(aOldNode);
+ for (let ancestor of ancestors) {
+ if (!ancestor.containerOpen)
+ return -1;
+ }
+
+ return this._getRowForNode(aOldNode, true);
+ }
+
+ // There's a broken edge case here.
+ // If a visit appears in two queries, and the second one was
+ // the old node, we'll select the first one after refresh. There's
+ // nothing we could do about that, because aOldNode.parent is
+ // gone by the time invalidateContainer is called.
+ let newNode = aUpdatedContainer.findNodeByDetails(aOldNode.uri,
+ aOldNode.time,
+ aOldNode.itemId,
+ true);
+ if (!newNode)
+ return -1;
+
+ return this._getRowForNode(newNode, true);
+ },
+
+ /**
+ * Restores a given selection state as near as possible to the original
+ * selection state.
+ *
+ * @param aNodesInfo
+ * The persisted selection state as returned by
+ * _getSelectedNodesInRange.
+ * @param aUpdatedContainer
+ * The container which was updated.
+ */
+ _restoreSelection:
+ function PTV__restoreSelection(aNodesInfo, aUpdatedContainer) {
+ if (aNodesInfo.length == 0)
+ return;
+
+ let selection = this.selection;
+
+ // Attempt to ensure that previously-visible selection will be visible
+ // if it's re-selected. However, we can only ensure that for one row.
+ let scrollToRow = -1;
+ for (let i = 0; i < aNodesInfo.length; i++) {
+ let nodeInfo = aNodesInfo[i];
+ let row = this._getNewRowForRemovedNode(aUpdatedContainer,
+ nodeInfo.node);
+ // Select the found node, if any.
+ if (row != -1) {
+ selection.rangedSelect(row, row, true);
+ if (nodeInfo.wasVisible && scrollToRow == -1)
+ scrollToRow = row;
+ }
+ }
+
+ // If only one node was previously selected and there's no selection now,
+ // select the node at its old row, if any.
+ if (aNodesInfo.length == 1 && selection.count == 0) {
+ let row = Math.min(aNodesInfo[0].oldRow, this._rows.length - 1);
+ if (row != -1) {
+ selection.rangedSelect(row, row, true);
+ if (aNodesInfo[0].wasVisible && scrollToRow == -1)
+ scrollToRow = aNodesInfo[0].oldRow;
+ }
+ }
+
+ if (scrollToRow != -1)
+ this._tree.ensureRowIsVisible(scrollToRow);
+ },
+
+ _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) {
+ const MS_PER_MINUTE = 60000;
+ const MS_PER_DAY = 86400000;
+ let timeMs = aTime / 1000; // PRTime is in microseconds
+
+ // Date is calculated starting from midnight, so the modulo with a day are
+ // milliseconds from today's midnight.
+ // getTimezoneOffset corrects that based on local time, notice midnight
+ // can have a different offset during DST-change days.
+ let dateObj = new Date();
+ let now = dateObj.getTime() - dateObj.getTimezoneOffset() * MS_PER_MINUTE;
+ let midnight = now - (now % MS_PER_DAY);
+ midnight += new Date(midnight).getTimezoneOffset() * MS_PER_MINUTE;
+
+ let timeObj = new Date(timeMs);
+ return timeMs >= midnight ? this._todayFormatter.format(timeObj)
+ : this._dateFormatter.format(timeObj);
+ },
+
+ // We use a different formatter for times within the current day,
+ // so we cache both a "today" formatter and a general date formatter.
+ __todayFormatter: null,
+ get _todayFormatter() {
+ if (!this.__todayFormatter) {
+ const locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { hour: 'numeric', minute: 'numeric' };
+ this.__todayFormatter = new Intl.DateTimeFormat(locale, dtOptions);
+ }
+ return this.__todayFormatter;
+ },
+
+ __dateFormatter: null,
+ get _dateFormatter() {
+ if (!this.__dateFormatter) {
+ const locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'numeric', day: 'numeric',
+ hour: 'numeric', minute: 'numeric' };
+ this.__dateFormatter = new Intl.DateTimeFormat(locale, dtOptions);
+ }
+ return this.__dateFormatter;
+ },
+
+ COLUMN_TYPE_UNKNOWN: 0,
+ COLUMN_TYPE_TITLE: 1,
+ COLUMN_TYPE_URI: 2,
+ COLUMN_TYPE_DATE: 3,
+ COLUMN_TYPE_VISITCOUNT: 4,
+ COLUMN_TYPE_DESCRIPTION: 5,
+ COLUMN_TYPE_DATEADDED: 6,
+ COLUMN_TYPE_LASTMODIFIED: 7,
+ COLUMN_TYPE_TAGS: 8,
+
+ _getColumnType: function PTV__getColumnType(aColumn) {
+ let columnType = aColumn.element.getAttribute("anonid") || aColumn.id;
+
+ switch (columnType) {
+ case "title":
+ return this.COLUMN_TYPE_TITLE;
+ case "url":
+ return this.COLUMN_TYPE_URI;
+ case "date":
+ return this.COLUMN_TYPE_DATE;
+ case "visitCount":
+ return this.COLUMN_TYPE_VISITCOUNT;
+ case "description":
+ return this.COLUMN_TYPE_DESCRIPTION;
+ case "dateAdded":
+ return this.COLUMN_TYPE_DATEADDED;
+ case "lastModified":
+ return this.COLUMN_TYPE_LASTMODIFIED;
+ case "tags":
+ return this.COLUMN_TYPE_TAGS;
+ }
+ return this.COLUMN_TYPE_UNKNOWN;
+ },
+
+ _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) {
+ switch (aSortType) {
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING:
+ return [this.COLUMN_TYPE_TITLE, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING:
+ return [this.COLUMN_TYPE_TITLE, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING:
+ return [this.COLUMN_TYPE_DATE, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING:
+ return [this.COLUMN_TYPE_DATE, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
+ return [this.COLUMN_TYPE_URI, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
+ return [this.COLUMN_TYPE_URI, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING:
+ return [this.COLUMN_TYPE_VISITCOUNT, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING:
+ return [this.COLUMN_TYPE_VISITCOUNT, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING:
+ if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
+ return [this.COLUMN_TYPE_DESCRIPTION, false];
+ break;
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING:
+ if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
+ return [this.COLUMN_TYPE_DESCRIPTION, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING:
+ return [this.COLUMN_TYPE_DATEADDED, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING:
+ return [this.COLUMN_TYPE_DATEADDED, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING:
+ return [this.COLUMN_TYPE_LASTMODIFIED, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING:
+ return [this.COLUMN_TYPE_LASTMODIFIED, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_ASCENDING:
+ return [this.COLUMN_TYPE_TAGS, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_DESCENDING:
+ return [this.COLUMN_TYPE_TAGS, true];
+ }
+ return [this.COLUMN_TYPE_UNKNOWN, false];
+ },
+
+ // nsINavHistoryResultObserver
+ nodeInserted: function PTV_nodeInserted(aParentNode, aNode, aNewIndex) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // Bail out for hidden separators.
+ if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted())
+ return;
+
+ let parentRow;
+ if (aParentNode != this._rootNode) {
+ parentRow = this._getRowForNode(aParentNode);
+
+ // Update parent when inserting the first item, since twisty has changed.
+ if (aParentNode.childCount == 1)
+ this._tree.invalidateRow(parentRow);
+ }
+
+ // Compute the new row number of the node.
+ let row = -1;
+ let cc = aParentNode.childCount;
+ if (aNewIndex == 0 || this._isPlainContainer(aParentNode) || cc == 0) {
+ // We don't need to worry about sub hierarchies of the parent node
+ // if it's a plain container, or if the new node is its first child.
+ if (aParentNode == this._rootNode)
+ row = aNewIndex;
+ else
+ row = parentRow + aNewIndex + 1;
+ }
+ else {
+ // Here, we try to find the next visible element in the child list so we
+ // can set the new visible index to be right before that. Note that we
+ // have to search down instead of up, because some siblings could have
+ // children themselves that would be in the way.
+ let separatorsAreHidden = PlacesUtils.nodeIsSeparator(aNode) &&
+ this.isSorted();
+ for (let i = aNewIndex + 1; i < cc; i++) {
+ let node = aParentNode.getChild(i);
+ if (!separatorsAreHidden || PlacesUtils.nodeIsSeparator(node)) {
+ // The children have not been shifted so the next item will have what
+ // should be our index.
+ row = this._getRowForNode(node, false, parentRow, i);
+ break;
+ }
+ }
+ if (row < 0) {
+ // At the end of the child list without finding a visible sibling. This
+ // is a little harder because we don't know how many rows the last item
+ // in our list takes up (it could be a container with many children).
+ let prevChild = aParentNode.getChild(aNewIndex - 1);
+ let prevIndex = this._getRowForNode(prevChild, false, parentRow,
+ aNewIndex - 1);
+ row = prevIndex + this._countVisibleRowsForNodeAtRow(prevIndex);
+ }
+ }
+
+ this._rows.splice(row, 0, aNode);
+ this._tree.rowCountChanged(row, 1);
+
+ if (PlacesUtils.nodeIsContainer(aNode) &&
+ PlacesUtils.asContainer(aNode).containerOpen) {
+ this.invalidateContainer(aNode);
+ }
+ },
+
+ /**
+ * THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being
+ * removed but the node it is collapsed with is not being removed (this then
+ * just swap out the removee with its collapsing partner). The only time
+ * when we really remove things is when deleting URIs, which will apply to
+ * all collapsees. This function is called sometimes when resorting items.
+ * However, we won't do this when sorted by date because dates will never
+ * change for visits, and date sorting is the only time things are collapsed.
+ */
+ nodeRemoved: function PTV_nodeRemoved(aParentNode, aNode, aOldIndex) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // XXX bug 517701: We don't know what to do when the root node is removed.
+ if (aNode == this._rootNode)
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ // Bail out for hidden separators.
+ if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted())
+ return;
+
+ let parentRow = aParentNode == this._rootNode ?
+ undefined : this._getRowForNode(aParentNode, true);
+ let oldRow = this._getRowForNode(aNode, true, parentRow, aOldIndex);
+ if (oldRow < 0)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // If the node was exclusively selected, the node next to it will be
+ // selected.
+ let selectNext = false;
+ let selection = this.selection;
+ if (selection.getRangeCount() == 1) {
+ let min = { }, max = { };
+ selection.getRangeAt(0, min, max);
+ if (min.value == max.value &&
+ this.nodeForTreeIndex(min.value) == aNode)
+ selectNext = true;
+ }
+
+ // Remove the node and its children, if any.
+ let count = this._countVisibleRowsForNodeAtRow(oldRow);
+ this._rows.splice(oldRow, count);
+ this._tree.rowCountChanged(oldRow, -count);
+
+ // Redraw the parent if its twisty state has changed.
+ if (aParentNode != this._rootNode && !aParentNode.hasChildren) {
+ let parentRow = oldRow - 1;
+ this._tree.invalidateRow(parentRow);
+ }
+
+ // Restore selection if the node was exclusively selected.
+ if (!selectNext)
+ return;
+
+ // Restore selection.
+ let rowToSelect = Math.min(oldRow, this._rows.length - 1);
+ if (rowToSelect != -1)
+ this.selection.rangedSelect(rowToSelect, rowToSelect, true);
+ },
+
+ nodeMoved:
+ function PTV_nodeMoved(aNode, aOldParent, aOldIndex, aNewParent, aNewIndex) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // Bail out for hidden separators.
+ if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted())
+ return;
+
+ // Note that at this point the node has already been moved by the backend,
+ // so we must give hints to _getRowForNode to get the old row position.
+ let oldParentRow = aOldParent == this._rootNode ?
+ undefined : this._getRowForNode(aOldParent, true);
+ let oldRow = this._getRowForNode(aNode, true, oldParentRow, aOldIndex);
+ if (oldRow < 0)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // If this node is a container it could take up more than one row.
+ let count = this._countVisibleRowsForNodeAtRow(oldRow);
+
+ // Persist selection state.
+ let nodesToReselect =
+ this._getSelectedNodesInRange(oldRow, oldRow + count);
+ if (nodesToReselect.length > 0)
+ this.selection.selectEventsSuppressed = true;
+
+ // Redraw the parent if its twisty state has changed.
+ if (aOldParent != this._rootNode && !aOldParent.hasChildren) {
+ let parentRow = oldRow - 1;
+ this._tree.invalidateRow(parentRow);
+ }
+
+ // Remove node and its children, if any, from the old position.
+ this._rows.splice(oldRow, count);
+ this._tree.rowCountChanged(oldRow, -count);
+
+ // Insert the node into the new position.
+ this.nodeInserted(aNewParent, aNode, aNewIndex);
+
+ // Restore selection.
+ if (nodesToReselect.length > 0) {
+ this._restoreSelection(nodesToReselect, aNewParent);
+ this.selection.selectEventsSuppressed = false;
+ }
+ },
+
+ _invalidateCellValue: function PTV__invalidateCellValue(aNode,
+ aColumnType) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // Nothing to do for the root node.
+ if (aNode == this._rootNode)
+ return;
+
+ let row = this._getRowForNode(aNode);
+ if (row == -1)
+ return;
+
+ let column = this._findColumnByType(aColumnType);
+ if (column && !column.element.hidden)
+ this._tree.invalidateCell(row, column);
+
+ // Last modified time is altered for almost all node changes.
+ if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) {
+ let lastModifiedColumn =
+ this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED);
+ if (lastModifiedColumn && !lastModifiedColumn.hidden)
+ this._tree.invalidateCell(row, lastModifiedColumn);
+ }
+ },
+
+ _populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) {
+ PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
+ .then(aLivemark => {
+ let placesNode = aNode;
+ // Need to check containerOpen since getLivemark is async.
+ if (!placesNode.containerOpen)
+ return;
+
+ let children = aLivemark.getNodesForContainer(placesNode);
+ for (let i = 0; i < children.length; i++) {
+ let child = children[i];
+ this.nodeInserted(placesNode, child, i);
+ }
+ }, Components.utils.reportError);
+ },
+
+ nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ },
+
+ nodeURIChanged: function PTV_nodeURIChanged(aNode, aNewURI) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_URI);
+ },
+
+ nodeIconChanged: function PTV_nodeIconChanged(aNode) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ },
+
+ nodeHistoryDetailsChanged:
+ function PTV_nodeHistoryDetailsChanged(aNode, aUpdatedVisitDate,
+ aUpdatedVisitCount) {
+ if (aNode.parent && this._controller.hasCachedLivemarkInfo(aNode.parent)) {
+ // Find the node in the parent.
+ let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent);
+ for (let i = parentRow; i < this._rows.length; i++) {
+ let child = this.nodeForTreeIndex(i);
+ if (child.uri == aNode.uri) {
+ this._cellProperties.delete(child);
+ this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE);
+ break;
+ }
+ }
+ return;
+ }
+
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
+ },
+
+ nodeTagsChanged: function PTV_nodeTagsChanged(aNode) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS);
+ },
+
+ nodeKeywordChanged(aNode, aNewKeyword) {},
+
+ nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
+ if (aAnno == PlacesUIUtils.DESCRIPTION_ANNO) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION);
+ }
+ else if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+ PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
+ .then(aLivemark => {
+ this._controller.cacheLivemarkInfo(aNode, aLivemark);
+ let properties = this._cellProperties.get(aNode);
+ this._cellProperties.set(aNode, properties += " livemark");
+ // The livemark attribute is set as a cell property on the title cell.
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ }, Components.utils.reportError);
+ }
+ },
+
+ nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
+ },
+
+ nodeLastModifiedChanged:
+ function PTV_nodeLastModifiedChanged(aNode, aNewValue) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED);
+ },
+
+ containerStateChanged:
+ function PTV_containerStateChanged(aNode, aOldState, aNewState) {
+ this.invalidateContainer(aNode);
+
+ if (PlacesUtils.nodeIsFolder(aNode) ||
+ (this._flatList && aNode == this._rootNode)) {
+ let queryOptions = PlacesUtils.asQuery(this._rootNode).queryOptions;
+ if (queryOptions.excludeItems) {
+ return;
+ }
+ if (aNode.itemId != -1) { // run when there's a valid node id
+ PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
+ .then(aLivemark => {
+ let shouldInvalidate =
+ !this._controller.hasCachedLivemarkInfo(aNode);
+ this._controller.cacheLivemarkInfo(aNode, aLivemark);
+ if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) {
+ aLivemark.registerForUpdates(aNode, this);
+ // Prioritize the current livemark.
+ aLivemark.reload();
+ PlacesUtils.livemarks.reloadLivemarks();
+ if (shouldInvalidate)
+ this.invalidateContainer(aNode);
+ }
+ else {
+ aLivemark.unregisterForUpdates(aNode);
+ }
+ }, () => undefined);
+ }
+ }
+ },
+
+ invalidateContainer: function PTV_invalidateContainer(aContainer) {
+ NS_ASSERT(this._result, "Need to have a result to update");
+ if (!this._tree)
+ return;
+
+ let startReplacement, replaceCount;
+ if (aContainer == this._rootNode) {
+ startReplacement = 0;
+ replaceCount = this._rows.length;
+
+ // If the root node is now closed, the tree is empty.
+ if (!this._rootNode.containerOpen) {
+ this._rows = [];
+ if (replaceCount)
+ this._tree.rowCountChanged(startReplacement, -replaceCount);
+
+ return;
+ }
+ }
+ else {
+ // Update the twisty state.
+ let row = this._getRowForNode(aContainer);
+ this._tree.invalidateRow(row);
+
+ // We don't replace the container node itself, so we should decrease the
+ // replaceCount by 1.
+ startReplacement = row + 1;
+ replaceCount = this._countVisibleRowsForNodeAtRow(row) - 1;
+ }
+
+ // Persist selection state.
+ let nodesToReselect =
+ this._getSelectedNodesInRange(startReplacement,
+ startReplacement + replaceCount);
+
+ // Now update the number of elements.
+ this.selection.selectEventsSuppressed = true;
+
+ // First remove the old elements
+ this._rows.splice(startReplacement, replaceCount);
+
+ // If the container is now closed, we're done.
+ if (!aContainer.containerOpen) {
+ let oldSelectionCount = this.selection.count;
+ if (replaceCount)
+ this._tree.rowCountChanged(startReplacement, -replaceCount);
+
+ // Select the row next to the closed container if any of its
+ // children were selected, and nothing else is selected.
+ if (nodesToReselect.length > 0 &&
+ nodesToReselect.length == oldSelectionCount) {
+ this.selection.rangedSelect(startReplacement, startReplacement, true);
+ this._tree.ensureRowIsVisible(startReplacement);
+ }
+
+ this.selection.selectEventsSuppressed = false;
+ return;
+ }
+
+ // Otherwise, start a batch first.
+ this._tree.beginUpdateBatch();
+ if (replaceCount)
+ this._tree.rowCountChanged(startReplacement, -replaceCount);
+
+ let toOpenElements = [];
+ let elementsAddedCount = this._buildVisibleSection(aContainer,
+ startReplacement,
+ toOpenElements);
+ if (elementsAddedCount)
+ this._tree.rowCountChanged(startReplacement, elementsAddedCount);
+
+ if (!this._flatList) {
+ // Now, open any containers that were persisted.
+ for (let i = 0; i < toOpenElements.length; i++) {
+ let item = toOpenElements[i];
+ let parent = item.parent;
+
+ // Avoid recursively opening containers.
+ while (parent) {
+ if (parent.uri == item.uri)
+ break;
+ parent = parent.parent;
+ }
+
+ // If we don't have a parent, we made it all the way to the root
+ // and didn't find a match, so we can open our item.
+ if (!parent && !item.containerOpen)
+ item.containerOpen = true;
+ }
+ }
+
+ if (this._controller.hasCachedLivemarkInfo(aContainer)) {
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ if (!queryOptions.excludeItems) {
+ this._populateLivemarkContainer(aContainer);
+ }
+ }
+
+ this._tree.endUpdateBatch();
+
+ // Restore selection.
+ this._restoreSelection(nodesToReselect, aContainer);
+ this.selection.selectEventsSuppressed = false;
+ },
+
+ _columns: [],
+ _findColumnByType: function PTV__findColumnByType(aColumnType) {
+ if (this._columns[aColumnType])
+ return this._columns[aColumnType];
+
+ let columns = this._tree.columns;
+ let colCount = columns.count;
+ for (let i = 0; i < colCount; i++) {
+ let column = columns.getColumnAt(i);
+ let columnType = this._getColumnType(column);
+ this._columns[columnType] = column;
+ if (columnType == aColumnType)
+ return column;
+ }
+
+ // That's completely valid. Most of our trees actually include just the
+ // title column.
+ return null;
+ },
+
+ sortingChanged: function PTV__sortingChanged(aSortingMode) {
+ if (!this._tree || !this._result)
+ return;
+
+ // Depending on the sort mode, certain commands may be disabled.
+ window.updateCommands("sort");
+
+ let columns = this._tree.columns;
+
+ // Clear old sorting indicator.
+ let sortedColumn = columns.getSortedColumn();
+ if (sortedColumn)
+ sortedColumn.element.removeAttribute("sortDirection");
+
+ // Set new sorting indicator by looking through all columns for ours.
+ if (aSortingMode == Ci.nsINavHistoryQueryOptions.SORT_BY_NONE)
+ return;
+
+ let [desiredColumn, desiredIsDescending] =
+ this._sortTypeToColumnType(aSortingMode);
+ let column = this._findColumnByType(desiredColumn);
+ if (column) {
+ let sortDir = desiredIsDescending ? "descending" : "ascending";
+ column.element.setAttribute("sortDirection", sortDir);
+ }
+ },
+
+ _inBatchMode: false,
+ batching: function PTV__batching(aToggleMode) {
+ if (this._inBatchMode != aToggleMode) {
+ this._inBatchMode = this.selection.selectEventsSuppressed = aToggleMode;
+ if (this._inBatchMode) {
+ this._tree.beginUpdateBatch();
+ }
+ else {
+ this._tree.endUpdateBatch();
+ }
+ }
+ },
+
+ get result() {
+ return this._result;
+ },
+ set result(val) {
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._rootNode.containerOpen = false;
+ }
+
+ if (val) {
+ this._result = val;
+ this._rootNode = this._result.root;
+ this._cellProperties = new Map();
+ this._cuttingNodes = new Set();
+ }
+ else if (this._result) {
+ delete this._result;
+ delete this._rootNode;
+ delete this._cellProperties;
+ delete this._cuttingNodes;
+ }
+
+ // If the tree is not set yet, setTree will call finishInit.
+ if (this._tree && val)
+ this._finishInit();
+
+ return val;
+ },
+
+ nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) {
+ if (aIndex > this._rows.length)
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ return this._getNodeForRow(aIndex);
+ },
+
+ treeIndexForNode: function PTV_treeNodeForIndex(aNode) {
+ // The API allows passing invisible nodes.
+ try {
+ return this._getRowForNode(aNode, true);
+ }
+ catch (ex) { }
+
+ return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
+ },
+
+ // nsITreeView
+ get rowCount() {
+ return this._rows.length;
+ },
+ get selection() {
+ return this._selection;
+ },
+ set selection(val) {
+ this._selection = val;
+ },
+
+ getRowProperties: function() { return ""; },
+
+ getCellProperties:
+ function PTV_getCellProperties(aRow, aColumn) {
+ // for anonid-trees, we need to add the column-type manually
+ var props = "";
+ let columnType = aColumn.element.getAttribute("anonid");
+ if (columnType)
+ props += columnType;
+ else
+ columnType = aColumn.id;
+
+ // Set the "ltr" property on url cells
+ if (columnType == "url")
+ props += " ltr";
+
+ if (columnType != "title")
+ return props;
+
+ let node = this._getNodeForRow(aRow);
+
+ if (this._cuttingNodes.has(node)) {
+ props += " cutting";
+ }
+
+ let properties = this._cellProperties.get(node);
+ if (properties === undefined) {
+ properties = "";
+ let itemId = node.itemId;
+ let nodeType = node.type;
+ if (PlacesUtils.containerTypes.includes(nodeType)) {
+ if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
+ properties += " query";
+ if (PlacesUtils.nodeIsTagQuery(node))
+ properties += " tagContainer";
+ else if (PlacesUtils.nodeIsDay(node))
+ properties += " dayContainer";
+ else if (PlacesUtils.nodeIsHost(node))
+ properties += " hostContainer";
+ }
+ else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
+ nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
+ if (this._controller.hasCachedLivemarkInfo(node)) {
+ properties += " livemark";
+ }
+ else {
+ PlacesUtils.livemarks.getLivemark({ id: node.itemId })
+ .then(aLivemark => {
+ this._controller.cacheLivemarkInfo(node, aLivemark);
+ let props = this._cellProperties.get(node);
+ this._cellProperties.set(node, props += " livemark");
+ // The livemark attribute is set as a cell property on the title cell.
+ this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
+ }, () => undefined);
+ }
+ }
+
+ if (itemId != -1) {
+ let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
+ if (queryName)
+ properties += " OrganizerQuery_" + queryName;
+ }
+ }
+ else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
+ properties += " separator";
+ else if (PlacesUtils.nodeIsURI(node)) {
+ properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri);
+
+ if (this._controller.hasCachedLivemarkInfo(node.parent)) {
+ properties += " livemarkItem";
+ if (node.accessCount) {
+ properties += " visited";
+ }
+ }
+ }
+
+ this._cellProperties.set(node, properties);
+ }
+
+ return props + " " + properties;
+ },
+
+ getColumnProperties: function(aColumn) { return ""; },
+
+ isContainer: function PTV_isContainer(aRow) {
+ // Only leaf nodes aren't listed in the rows array.
+ let node = this._rows[aRow];
+ if (node === undefined)
+ return false;
+
+ if (PlacesUtils.nodeIsContainer(node)) {
+ // Flat-lists may ignore expandQueries and other query options when
+ // they are asked to open a container.
+ if (this._flatList)
+ return true;
+
+ // treat non-expandable childless queries as non-containers
+ if (PlacesUtils.nodeIsQuery(node)) {
+ let parent = node.parent;
+ if ((PlacesUtils.nodeIsQuery(parent) ||
+ PlacesUtils.nodeIsFolder(parent)) &&
+ !PlacesUtils.asQuery(node).hasChildren)
+ return PlacesUtils.asQuery(parent).queryOptions.expandQueries;
+ }
+ return true;
+ }
+ return false;
+ },
+
+ isContainerOpen: function PTV_isContainerOpen(aRow) {
+ if (this._flatList)
+ return false;
+
+ // All containers are listed in the rows array.
+ return this._rows[aRow].containerOpen;
+ },
+
+ isContainerEmpty: function PTV_isContainerEmpty(aRow) {
+ if (this._flatList)
+ return true;
+
+ let node = this._rows[aRow];
+ if (this._controller.hasCachedLivemarkInfo(node)) {
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ return queryOptions.excludeItems;
+ }
+
+ // All containers are listed in the rows array.
+ return !node.hasChildren;
+ },
+
+ isSeparator: function PTV_isSeparator(aRow) {
+ // All separators are listed in the rows array.
+ let node = this._rows[aRow];
+ return node && PlacesUtils.nodeIsSeparator(node);
+ },
+
+ isSorted: function PTV_isSorted() {
+ return this._result.sortingMode !=
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ },
+
+ canDrop: function PTV_canDrop(aRow, aOrientation, aDataTransfer) {
+ if (!this._result)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // Drop position into a sorted treeview would be wrong.
+ if (this.isSorted())
+ return false;
+
+ let ip = this._getInsertionPoint(aRow, aOrientation);
+ return ip && PlacesControllerDragHelper.canDrop(ip, aDataTransfer);
+ },
+
+ _getInsertionPoint: function PTV__getInsertionPoint(index, orientation) {
+ let container = this._result.root;
+ let dropNearItemId = -1;
+ // When there's no selection, assume the container is the container
+ // the view is populated from (i.e. the result's itemId).
+ if (index != -1) {
+ let lastSelected = this.nodeForTreeIndex(index);
+ if (this.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
+ // If the last selected item is an open container, append _into_
+ // it, rather than insert adjacent to it.
+ container = lastSelected;
+ index = -1;
+ }
+ else if (lastSelected.containerOpen &&
+ orientation == Ci.nsITreeView.DROP_AFTER &&
+ lastSelected.hasChildren) {
+ // If the last selected node is an open container and the user is
+ // trying to drag into it as a first node, really insert into it.
+ container = lastSelected;
+ orientation = Ci.nsITreeView.DROP_ON;
+ index = 0;
+ }
+ else {
+ // Use the last-selected node's container.
+ container = lastSelected.parent;
+
+ // During its Drag & Drop operation, the tree code closes-and-opens
+ // containers very often (part of the XUL "spring-loaded folders"
+ // implementation). And in certain cases, we may reach a closed
+ // container here. However, we can simply bail out when this happens,
+ // because we would then be back here in less than a millisecond, when
+ // the container had been reopened.
+ if (!container || !container.containerOpen)
+ return null;
+
+ // Avoid the potentially expensive call to getChildIndex
+ // if we know this container doesn't allow insertion.
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ if (queryOptions.sortingMode !=
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
+ // If we are within a sorted view, insert at the end.
+ index = -1;
+ }
+ else if (queryOptions.excludeItems ||
+ queryOptions.excludeQueries ||
+ queryOptions.excludeReadOnlyFolders) {
+ // Some item may be invisible, insert near last selected one.
+ // We don't replace index here to avoid requests to the db,
+ // instead it will be calculated later by the controller.
+ index = -1;
+ dropNearItemId = lastSelected.itemId;
+ }
+ else {
+ let lsi = container.getChildIndex(lastSelected);
+ index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
+ }
+ }
+ }
+
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ // TODO (Bug 1160193): properly support dropping on a tag root.
+ let tagName = null;
+ if (PlacesUtils.nodeIsTagQuery(container)) {
+ tagName = container.title;
+ if (!tagName)
+ return null;
+ }
+
+ return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
+ index, orientation,
+ tagName,
+ dropNearItemId);
+ },
+
+ drop: function PTV_drop(aRow, aOrientation, aDataTransfer) {
+ // We are responsible for translating the |index| and |orientation|
+ // parameters into a container id and index within the container,
+ // since this information is specific to the tree view.
+ let ip = this._getInsertionPoint(aRow, aOrientation);
+ if (ip) {
+ PlacesControllerDragHelper.onDrop(ip, aDataTransfer)
+ .then(null, Components.utils.reportError);
+ }
+
+ PlacesControllerDragHelper.currentDropTarget = null;
+ },
+
+ getParentIndex: function PTV_getParentIndex(aRow) {
+ let [, parentRow] = this._getParentByChildRow(aRow);
+ return parentRow;
+ },
+
+ hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) {
+ if (aRow == this._rows.length - 1) {
+ // The last row has no sibling.
+ return false;
+ }
+
+ let node = this._rows[aRow];
+ if (node === undefined || this._isPlainContainer(node.parent)) {
+ // The node is a child of a plain container.
+ // If the next row is either unset or has the same parent,
+ // it's a sibling.
+ let nextNode = this._rows[aRow + 1];
+ return (nextNode == undefined || nextNode.parent == node.parent);
+ }
+
+ let thisLevel = node.indentLevel;
+ for (let i = aAfterIndex + 1; i < this._rows.length; ++i) {
+ let rowNode = this._getNodeForRow(i);
+ let nextLevel = rowNode.indentLevel;
+ if (nextLevel == thisLevel)
+ return true;
+ if (nextLevel < thisLevel)
+ break;
+ }
+
+ return false;
+ },
+
+ getLevel: function(aRow) {
+ return this._getNodeForRow(aRow).indentLevel;
+ },
+
+ getImageSrc: function PTV_getImageSrc(aRow, aColumn) {
+ // Only the title column has an image.
+ if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
+ return "";
+
+ let node = this._getNodeForRow(aRow);
+ return node.icon;
+ },
+
+ getProgressMode: function(aRow, aColumn) { },
+ getCellValue: function(aRow, aColumn) { },
+
+ getCellText: function PTV_getCellText(aRow, aColumn) {
+ let node = this._getNodeForRow(aRow);
+ switch (this._getColumnType(aColumn)) {
+ case this.COLUMN_TYPE_TITLE:
+ // normally, this is just the title, but we don't want empty items in
+ // the tree view so return a special string if the title is empty.
+ // Do it here so that callers can still get at the 0 length title
+ // if they go through the "result" API.
+ if (PlacesUtils.nodeIsSeparator(node))
+ return "";
+ return PlacesUIUtils.getBestTitle(node, true);
+ case this.COLUMN_TYPE_TAGS:
+ return node.tags;
+ case this.COLUMN_TYPE_URI:
+ if (PlacesUtils.nodeIsURI(node))
+ return node.uri;
+ return "";
+ case this.COLUMN_TYPE_DATE:
+ let nodeTime = node.time;
+ if (nodeTime == 0 || !PlacesUtils.nodeIsURI(node)) {
+ // hosts and days shouldn't have a value for the date column.
+ // Actually, you could argue this point, but looking at the
+ // results, seeing the most recently visited date is not what
+ // I expect, and gives me no information I know how to use.
+ // Only show this for URI-based items.
+ return "";
+ }
+
+ return this._convertPRTimeToString(nodeTime);
+ case this.COLUMN_TYPE_VISITCOUNT:
+ return node.accessCount;
+ case this.COLUMN_TYPE_DESCRIPTION:
+ if (node.itemId != -1) {
+ try {
+ return PlacesUtils.annotations.
+ getItemAnnotation(node.itemId, PlacesUIUtils.DESCRIPTION_ANNO);
+ }
+ catch (ex) { /* has no description */ }
+ }
+ return "";
+ case this.COLUMN_TYPE_DATEADDED:
+ if (node.dateAdded)
+ return this._convertPRTimeToString(node.dateAdded);
+ return "";
+ case this.COLUMN_TYPE_LASTMODIFIED:
+ if (node.lastModified)
+ return this._convertPRTimeToString(node.lastModified);
+ return "";
+ }
+ return "";
+ },
+
+ setTree: function PTV_setTree(aTree) {
+ // If we are replacing the tree during a batch, there is a concrete risk
+ // that the treeView goes out of sync, thus it's safer to end the batch now.
+ // This is a no-op if we are not batching.
+ this.batching(false);
+
+ let hasOldTree = this._tree != null;
+ this._tree = aTree;
+
+ if (this._result) {
+ if (hasOldTree) {
+ // detach from result when we are detaching from the tree.
+ // This breaks the reference cycle between us and the result.
+ if (!aTree) {
+ this._result.removeObserver(this);
+ this._rootNode.containerOpen = false;
+ }
+ }
+ if (aTree)
+ this._finishInit();
+ }
+ },
+
+ toggleOpenState: function PTV_toggleOpenState(aRow) {
+ if (!this._result)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ let node = this._rows[aRow];
+ if (this._flatList && this._openContainerCallback) {
+ this._openContainerCallback(node);
+ return;
+ }
+
+ // Persist containers open status, but never persist livemarks.
+ if (!this._controller.hasCachedLivemarkInfo(node)) {
+ let uri = node.uri;
+
+ if (uri) {
+ let docURI = document.documentURI;
+
+ if (node.containerOpen) {
+ this._xulStore.removeValue(docURI, uri, "open");
+ } else {
+ this._xulStore.setValue(docURI, uri, "open", "true");
+ }
+ }
+ }
+
+ node.containerOpen = !node.containerOpen;
+ },
+
+ cycleHeader: function PTV_cycleHeader(aColumn) {
+ if (!this._result)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // Sometimes you want a tri-state sorting, and sometimes you don't. This
+ // rule allows tri-state sorting when the root node is a folder. This will
+ // catch the most common cases. When you are looking at folders, you want
+ // the third state to reset the sorting to the natural bookmark order. When
+ // you are looking at history, that third state has no meaning so we try
+ // to disallow it.
+ //
+ // The problem occurs when you have a query that results in bookmark
+ // folders. One example of this is the subscriptions view. In these cases,
+ // this rule doesn't allow you to sort those sub-folders by their natural
+ // order.
+ let allowTriState = PlacesUtils.nodeIsFolder(this._result.root);
+
+ let oldSort = this._result.sortingMode;
+ let oldSortingAnnotation = this._result.sortingAnnotation;
+ let newSort;
+ let newSortingAnnotation = "";
+ const NHQO = Ci.nsINavHistoryQueryOptions;
+ switch (this._getColumnType(aColumn)) {
+ case this.COLUMN_TYPE_TITLE:
+ if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING)
+ newSort = NHQO.SORT_BY_TITLE_DESCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_TITLE_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_TITLE_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_URI:
+ if (oldSort == NHQO.SORT_BY_URI_ASCENDING)
+ newSort = NHQO.SORT_BY_URI_DESCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_URI_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_URI_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_DATE:
+ if (oldSort == NHQO.SORT_BY_DATE_ASCENDING)
+ newSort = NHQO.SORT_BY_DATE_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_DATE_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_DATE_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_VISITCOUNT:
+ // visit count default is unusual because we sort by descending
+ // by default because you are most likely to be looking for
+ // highly visited sites when you click it
+ if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING)
+ newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
+
+ break;
+ case this.COLUMN_TYPE_DESCRIPTION:
+ if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING &&
+ oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) {
+ newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING;
+ newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO;
+ }
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING &&
+ oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
+ newSort = NHQO.SORT_BY_NONE;
+ else {
+ newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING;
+ newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO;
+ }
+
+ break;
+ case this.COLUMN_TYPE_DATEADDED:
+ if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING)
+ newSort = NHQO.SORT_BY_DATEADDED_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_DATEADDED_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_LASTMODIFIED:
+ if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING)
+ newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_TAGS:
+ if (oldSort == NHQO.SORT_BY_TAGS_ASCENDING)
+ newSort = NHQO.SORT_BY_TAGS_DESCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_TAGS_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_TAGS_ASCENDING;
+
+ break;
+ default:
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ this._result.sortingAnnotation = newSortingAnnotation;
+ this._result.sortingMode = newSort;
+ },
+
+ isEditable: function PTV_isEditable(aRow, aColumn) {
+ // At this point we only support editing the title field.
+ if (aColumn.index != 0)
+ return false;
+
+ let node = this._rows[aRow];
+ if (!node) {
+ Cu.reportError("isEditable called for an unbuilt row.");
+ return false;
+ }
+ let itemId = node.itemId;
+
+ // Only bookmark-nodes are editable. Fortunately, this checks also takes
+ // care of livemark children.
+ if (itemId == -1)
+ return false;
+
+ // The following items are also not editable, even though they are bookmark
+ // items.
+ // * places-roots
+ // * the left pane special folders and queries (those are place: uri
+ // bookmarks)
+ // * separators
+ //
+ // Note that concrete itemIds aren't used intentionally. For example, we
+ // have no reason to disallow renaming a shortcut to the Bookmarks Toolbar,
+ // except for the one under All Bookmarks.
+ if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemId))
+ return false;
+
+ let parentId = PlacesUtils.getConcreteItemId(node.parent);
+ if (parentId == PlacesUIUtils.leftPaneFolderId ||
+ parentId == PlacesUIUtils.allBookmarksFolderId) {
+ // Note that the for the time being this is the check that actually
+ // blocks renaming places "roots", and not the isRootItem check above.
+ // That's because places root are only exposed through folder shortcuts
+ // descendants of the left pane folder.
+ return false;
+ }
+
+ return true;
+ },
+
+ setCellText: function PTV_setCellText(aRow, aColumn, aText) {
+ // We may only get here if the cell is editable.
+ let node = this._rows[aRow];
+ if (node.title != aText) {
+ if (!PlacesUIUtils.useAsyncTransactions) {
+ let txn = new PlacesEditItemTitleTransaction(node.itemId, aText);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ return;
+ }
+ PlacesTransactions.EditTitle({ guid: node.bookmarkGuid, title: aText })
+ .transact().catch(Cu.reportError);
+ }
+ },
+
+ toggleCutNode: function PTV_toggleCutNode(aNode, aValue) {
+ let currentVal = this._cuttingNodes.has(aNode);
+ if (currentVal != aValue) {
+ if (aValue)
+ this._cuttingNodes.add(aNode);
+ else
+ this._cuttingNodes.delete(aNode);
+
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ }
+ },
+
+ selectionChanged: function() { },
+ cycleCell: function(aRow, aColumn) { },
+ isSelectable: function(aRow, aColumn) { return false; },
+ performAction: function(aAction) { },
+ performActionOnRow: function(aAction, aRow) { },
+ performActionOnCell: function(aAction, aRow, aColumn) { }
+};
diff --git a/browser/components/places/jar.mn b/browser/components/places/jar.mn
new file mode 100644
index 000000000..93809510d
--- /dev/null
+++ b/browser/components/places/jar.mn
@@ -0,0 +1,34 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+% overlay chrome://browser/content/places/places.xul chrome://browser/content/places/downloadsViewOverlay.xul
+# Provide another URI for the bookmarkProperties dialog so we can persist the
+# attributes separately
+ content/browser/places/bookmarkProperties2.xul (content/bookmarkProperties.xul)
+* content/browser/places/places.xul (content/places.xul)
+ content/browser/places/places.js (content/places.js)
+ content/browser/places/places.css (content/places.css)
+ content/browser/places/organizer.css (content/organizer.css)
+ content/browser/places/bookmarkProperties.xul (content/bookmarkProperties.xul)
+ content/browser/places/bookmarkProperties.js (content/bookmarkProperties.js)
+ content/browser/places/placesOverlay.xul (content/placesOverlay.xul)
+ content/browser/places/menu.xml (content/menu.xml)
+ content/browser/places/tree.xml (content/tree.xml)
+ content/browser/places/controller.js (content/controller.js)
+ content/browser/places/treeView.js (content/treeView.js)
+ content/browser/places/browserPlacesViews.js (content/browserPlacesViews.js)
+# keep the Places version of the history sidebar at history/history-panel.xul
+# to prevent having to worry about between versions of the browser
+* content/browser/history/history-panel.xul (content/history-panel.xul)
+ content/browser/places/history-panel.js (content/history-panel.js)
+# ditto for the bookmarks sidebar
+ content/browser/bookmarks/bookmarksPanel.xul (content/bookmarksPanel.xul)
+ content/browser/bookmarks/bookmarksPanel.js (content/bookmarksPanel.js)
+ content/browser/bookmarks/sidebarUtils.js (content/sidebarUtils.js)
+ content/browser/places/moveBookmarks.xul (content/moveBookmarks.xul)
+ content/browser/places/moveBookmarks.js (content/moveBookmarks.js)
+ content/browser/places/editBookmarkOverlay.xul (content/editBookmarkOverlay.xul)
+ content/browser/places/editBookmarkOverlay.js (content/editBookmarkOverlay.js)
+* content/browser/places/downloadsViewOverlay.xul (content/downloadsViewOverlay.xul)
diff --git a/browser/components/places/moz.build b/browser/components/places/moz.build
new file mode 100644
index 000000000..e6f88b318
--- /dev/null
+++ b/browser/components/places/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'PlacesUIUtils.jsm',
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Bookmarks & History')
diff --git a/browser/components/places/tests/browser/.eslintrc.js b/browser/components/places/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/places/tests/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/places/tests/browser/bookmark_dummy_1.html b/browser/components/places/tests/browser/bookmark_dummy_1.html
new file mode 100644
index 000000000..c03e0c18c
--- /dev/null
+++ b/browser/components/places/tests/browser/bookmark_dummy_1.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Bookmark Dummy 1</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Bookmark Dummy 1</p>
+</body>
+</html>
diff --git a/browser/components/places/tests/browser/bookmark_dummy_2.html b/browser/components/places/tests/browser/bookmark_dummy_2.html
new file mode 100644
index 000000000..229a730b3
--- /dev/null
+++ b/browser/components/places/tests/browser/bookmark_dummy_2.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Bookmark Dummy 2</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Bookmark Dummy 2</p>
+</body>
+</html>
diff --git a/browser/components/places/tests/browser/browser.ini b/browser/components/places/tests/browser/browser.ini
new file mode 100644
index 000000000..5dce31653
--- /dev/null
+++ b/browser/components/places/tests/browser/browser.ini
@@ -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/.
+
+[DEFAULT]
+support-files =
+ head.js
+ framedPage.html
+ frameLeft.html
+ frameRight.html
+ sidebarpanels_click_test_page.html
+ keyword_form.html
+
+[browser_0_library_left_pane_migration.js]
+[browser_410196_paste_into_tags.js]
+subsuite = clipboard
+[browser_416459_cut.js]
+subsuite = clipboard
+[browser_423515.js]
+[browser_425884.js]
+[browser_435851_copy_query.js]
+subsuite = clipboard
+[browser_475045.js]
+[browser_555547.js]
+[browser_bookmarklet_windowOpen.js]
+support-files =
+ pageopeningwindow.html
+[browser_bookmarkProperties_addFolderDefaultButton.js]
+[browser_bookmarkProperties_addKeywordForThisSearch.js]
+[browser_bookmarkProperties_addLivemark.js]
+[browser_bookmarkProperties_editTagContainer.js]
+[browser_bookmarkProperties_readOnlyRoot.js]
+[browser_bookmarksProperties.js]
+[browser_drag_bookmarks_on_toolbar.js]
+[browser_forgetthissite_single.js]
+[browser_history_sidebar_search.js]
+[browser_library_batch_delete.js]
+[browser_library_commands.js]
+[browser_library_downloads.js]
+[browser_library_infoBox.js]
+[browser_library_left_pane_fixnames.js]
+[browser_library_left_pane_select_hierarchy.js]
+[browser_library_middleclick.js]
+[browser_library_open_leak.js]
+[browser_library_openFlatContainer.js]
+[browser_library_panel_leak.js]
+[browser_library_search.js]
+[browser_library_views_liveupdate.js]
+[browser_markPageAsFollowedLink.js]
+[browser_sidebarpanels_click.js]
+skip-if = true # temporarily disabled for breaking the treeview - bug 658744
+[browser_sort_in_library.js]
+[browser_toolbarbutton_menu_context.js]
+[browser_views_liveupdate.js]
+[browser_bookmark_all_tabs.js]
+support-files =
+ bookmark_dummy_1.html
+ bookmark_dummy_2.html
diff --git a/browser/components/places/tests/browser/browser_0_library_left_pane_migration.js b/browser/components/places/tests/browser/browser_0_library_left_pane_migration.js
new file mode 100644
index 000000000..a7089b497
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_0_library_left_pane_migration.js
@@ -0,0 +1,90 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Test we correctly migrate Library left pane to the latest version.
+ * Note: this test MUST be the first between browser chrome tests, or results
+ * of next tests could be unexpected due to PlacesUIUtils getters.
+ */
+
+const TEST_URI = "http://www.mozilla.org/";
+
+add_task(function* () {
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils is running in chrome context");
+ ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
+ ok(PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION > 0,
+ "Left pane version in chrome context, current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION );
+
+ // Check if we have any left pane folder already set, remove it eventually.
+ let leftPaneItems = PlacesUtils.annotations
+ .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ if (leftPaneItems.length > 0) {
+ // The left pane has already been created, touching it now would cause
+ // next tests to rely on wrong values (and possibly crash)
+ is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
+ // Check version.
+ let version = PlacesUtils.annotations.getItemAnnotation(leftPaneItems[0],
+ PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual");
+ ok(true, "left pane has already been created, skipping test");
+ return;
+ }
+
+ // Create a fake left pane folder with an old version (current version - 1).
+ let folder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.rootGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: ""
+ });
+
+ let folderId = yield PlacesUtils.promiseItemId(folder.guid);
+ PlacesUtils.annotations.setItemAnnotation(folderId,
+ PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
+ PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1,
+ 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+
+ // Check fake left pane root has been correctly created.
+ leftPaneItems =
+ PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
+ is(leftPaneItems[0], folderId, "left pane root itemId is correct");
+
+ // Check version.
+ let version = PlacesUtils.annotations.getItemAnnotation(folderId,
+ PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1, "Left pane version correctly set");
+
+ // Open Library, this will upgrade our left pane version.
+ let organizer = yield promiseLibrary();
+
+ // Check left pane.
+ ok(PlacesUIUtils.leftPaneFolderId > 0, "Left pane folder correctly created");
+ leftPaneItems =
+ PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
+ let leftPaneRoot = leftPaneItems[0];
+ is(leftPaneRoot, PlacesUIUtils.leftPaneFolderId,
+ "leftPaneFolderId getter has correct value");
+
+ // Check version has been upgraded.
+ version = PlacesUtils.annotations.getItemAnnotation(leftPaneRoot,
+ PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
+ "Left pane version has been correctly upgraded");
+
+ // Check left pane is populated.
+ organizer.PlacesOrganizer.selectLeftPaneQuery("History");
+ is(organizer.PlacesOrganizer._places.selectedNode.itemId,
+ PlacesUIUtils.leftPaneQueries["History"],
+ "Library left pane is populated and working");
+
+ yield promiseLibraryClosed(organizer);
+});
diff --git a/browser/components/places/tests/browser/browser_410196_paste_into_tags.js b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
new file mode 100644
index 000000000..2acb1d9b7
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const TEST_URL = Services.io.newURI("http://example.com/", null, null);
+const MOZURISPEC = Services.io.newURI("http://mozilla.com/", null, null);
+
+add_task(function* () {
+ let organizer = yield promiseLibrary();
+
+ ok(PlacesUtils, "PlacesUtils in scope");
+ ok(PlacesUIUtils, "PlacesUIUtils in scope");
+
+ let PlacesOrganizer = organizer.PlacesOrganizer;
+ ok(PlacesOrganizer, "Places organizer in scope");
+
+ let ContentTree = organizer.ContentTree;
+ ok(ContentTree, "ContentTree is in scope");
+
+ let visits = {uri: MOZURISPEC, transition: PlacesUtils.history.TRANSITION_TYPED};
+ yield PlacesTestUtils.addVisits(visits);
+
+ // create an initial tag to work with
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ title: "bookmark/" + TEST_URL.spec,
+ url: TEST_URL
+ });
+
+ ok(bm, "A bookmark was added");
+ PlacesUtils.tagging.tagURI(TEST_URL, ["foo"]);
+ let tags = PlacesUtils.tagging.getTagsForURI(TEST_URL);
+ is(tags[0], "foo", "tag is foo");
+
+ // focus the new tag
+ focusTag(PlacesOrganizer);
+
+ let populate = () => copyHistNode(PlacesOrganizer, ContentTree);
+ yield promiseClipboard(populate, PlacesUtils.TYPE_X_MOZ_PLACE);
+
+ focusTag(PlacesOrganizer);
+ PlacesOrganizer._places.controller.paste();
+
+ // re-focus the history again
+ PlacesOrganizer.selectLeftPaneQuery("History");
+ let histContainer = PlacesOrganizer._places.selectedNode;
+ PlacesUtils.asContainer(histContainer);
+ histContainer.containerOpen = true;
+ PlacesOrganizer._places.selectNode(histContainer.getChild(0));
+ let histNode = ContentTree.view.view.nodeForTreeIndex(0);
+ ok(histNode, "histNode exists: " + histNode.title);
+
+ // check to see if the history node is tagged!
+ tags = PlacesUtils.tagging.getTagsForURI(MOZURISPEC);
+ ok(tags.length == 1, "history node is tagged: " + tags.length);
+
+ // check if a bookmark was created
+ let bookmarks = [];
+ yield PlacesUtils.bookmarks.fetch({url: MOZURISPEC}, bm => {
+ bookmarks.push(bm);
+ });
+ ok(bookmarks.length > 0, "bookmark exists for the tagged history item");
+
+ // is the bookmark visible in the UI?
+ // get the Unsorted Bookmarks node
+ PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+
+ // now we can see what is in the ContentTree tree
+ let unsortedNode = ContentTree.view.view.nodeForTreeIndex(1);
+ ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri);
+ is(unsortedNode.uri, MOZURISPEC.spec, "node uri's are the same");
+
+ yield promiseLibraryClosed(organizer);
+
+ // Remove new Places data we created.
+ PlacesUtils.tagging.untagURI(MOZURISPEC, ["foo"]);
+ PlacesUtils.tagging.untagURI(TEST_URL, ["foo"]);
+ tags = PlacesUtils.tagging.getTagsForURI(TEST_URL);
+ is(tags.length, 0, "tags are gone");
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+ yield PlacesTestUtils.clearHistory();
+});
+
+function focusTag(PlacesOrganizer) {
+ PlacesOrganizer.selectLeftPaneQuery("Tags");
+ let tags = PlacesOrganizer._places.selectedNode;
+ tags.containerOpen = true;
+ let fooTag = tags.getChild(0);
+ let tagNode = fooTag;
+ PlacesOrganizer._places.selectNode(fooTag);
+ is(tagNode.title, 'foo', "tagNode title is foo");
+ let ip = PlacesOrganizer._places.insertionPoint;
+ ok(ip.isTag, "IP is a tag");
+}
+
+function copyHistNode(PlacesOrganizer, ContentTree) {
+ // focus the history object
+ PlacesOrganizer.selectLeftPaneQuery("History");
+ let histContainer = PlacesOrganizer._places.selectedNode;
+ PlacesUtils.asContainer(histContainer);
+ histContainer.containerOpen = true;
+ PlacesOrganizer._places.selectNode(histContainer.getChild(0));
+ let histNode = ContentTree.view.view.nodeForTreeIndex(0);
+ ContentTree.view.selectNode(histNode);
+ is(histNode.uri, MOZURISPEC.spec,
+ "historyNode exists: " + histNode.uri);
+ // copy the history node
+ ContentTree.view.controller.copy();
+}
diff --git a/browser/components/places/tests/browser/browser_416459_cut.js b/browser/components/places/tests/browser/browser_416459_cut.js
new file mode 100644
index 000000000..6f3f8cdd5
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_416459_cut.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = "http://example.com/";
+
+add_task(function* () {
+ yield PlacesUtils.bookmarks.eraseEverything();
+ let organizer = yield promiseLibrary();
+
+ registerCleanupFunction(function* () {
+ yield promiseLibraryClosed(organizer);
+ yield PlacesUtils.bookmarks.eraseEverything();
+ });
+
+ let PlacesOrganizer = organizer.PlacesOrganizer;
+ let ContentTree = organizer.ContentTree;
+
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils in scope");
+ ok(PlacesUIUtils, "PlacesUIUtils in scope");
+ ok(PlacesOrganizer, "PlacesOrganizer in scope");
+ ok(ContentTree, "ContentTree is in scope");
+
+ // Test with multiple entries to ensure they retain their order.
+ let bookmarks = [];
+ bookmarks.push(yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: TEST_URL,
+ title: "0"
+ }));
+ bookmarks.push(yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: TEST_URL,
+ title: "1"
+ }));
+ bookmarks.push(yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: TEST_URL,
+ title: "2"
+ }));
+
+ yield selectBookmarksIn(organizer, bookmarks, "BookmarksToolbar");
+
+ yield promiseClipboard(() => {
+ info("Cutting selection");
+ ContentTree.view.controller.cut();
+ }, PlacesUtils.TYPE_X_MOZ_PLACE);
+
+ info("Selecting UnfiledBookmarks in the left pane");
+ PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+ info("Pasting clipboard");
+ ContentTree.view.controller.paste();
+
+ yield selectBookmarksIn(organizer, bookmarks, "UnfiledBookmarks");
+});
+
+var selectBookmarksIn = Task.async(function* (organizer, bookmarks, aLeftPaneQuery) {
+ let PlacesOrganizer = organizer.PlacesOrganizer;
+ let ContentTree = organizer.ContentTree;
+ info("Selecting " + aLeftPaneQuery + " in the left pane");
+ PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
+
+ let ids = [];
+ for (let {guid} of bookmarks) {
+ let bookmark = yield PlacesUtils.bookmarks.fetch(guid);
+ is (bookmark.parentGuid, PlacesOrganizer._places.selectedNode.targetFolderGuid,
+ "Bookmark has the right parent");
+ ids.push(yield PlacesUtils.promiseItemId(bookmark.guid));
+ }
+
+ info("Selecting the bookmarks in the right pane");
+ ContentTree.view.selectItems(ids);
+
+ for (let node of ContentTree.view.selectedNodes) {
+ is(node.bookmarkIndex, node.title,
+ "Found the expected bookmark in the expected position");
+ }
+});
diff --git a/browser/components/places/tests/browser/browser_423515.js b/browser/components/places/tests/browser/browser_423515.js
new file mode 100644
index 000000000..4d3da8fc1
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_423515.js
@@ -0,0 +1,173 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // sanity check
+ ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
+ ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?");
+ ok(PlacesControllerDragHelper, "checking PlacesControllerDragHelper, running in chrome context?");
+
+ const IDX = PlacesUtils.bookmarks.DEFAULT_INDEX;
+
+ // setup
+ var rootId = PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId, "", IDX);
+ var rootNode = PlacesUtils.getFolderContents(rootId, false, true).root;
+ is(rootNode.childCount, 0, "confirm test root is empty");
+
+ var tests = [];
+
+ // add a regular folder, should be moveable
+ tests.push({
+ populate: function() {
+ this.id =
+ PlacesUtils.bookmarks.createFolder(rootId, "", IDX);
+ },
+ validate: function() {
+ is(rootNode.childCount, 1,
+ "populate added data to the test root");
+ is(PlacesControllerDragHelper.canMoveNode(rootNode.getChild(0)),
+ true, "can move regular folder node");
+ }
+ });
+
+ // add a regular folder shortcut, should be moveable
+ tests.push({
+ populate: function() {
+ this.folderId =
+ PlacesUtils.bookmarks.createFolder(rootId, "foo", IDX);
+ this.shortcutId =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:folder="+this.folderId), IDX, "bar");
+ },
+ validate: function() {
+ is(rootNode.childCount, 2,
+ "populated data to the test root");
+
+ var folderNode = rootNode.getChild(0);
+ is(folderNode.type, 6, "node is folder");
+ is(this.folderId, folderNode.itemId, "folder id and folder node item id match");
+
+ var shortcutNode = rootNode.getChild(1);
+ is(shortcutNode.type, 9, "node is folder shortcut");
+ is(this.shortcutId, shortcutNode.itemId, "shortcut id and shortcut node item id match");
+
+ var concreteId = PlacesUtils.getConcreteItemId(shortcutNode);
+ is(concreteId, folderNode.itemId, "shortcut node id and concrete id match");
+
+ is(PlacesControllerDragHelper.canMoveNode(shortcutNode),
+ true, "can move folder shortcut node");
+ }
+ });
+
+ // add a regular query, should be moveable
+ tests.push({
+ populate: function() {
+ this.bookmarkId =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("http://foo.com"), IDX, "foo");
+ this.queryId =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:terms=foo"), IDX, "bar");
+ },
+ validate: function() {
+ is(rootNode.childCount, 2,
+ "populated data to the test root");
+
+ var bmNode = rootNode.getChild(0);
+ is(bmNode.itemId, this.bookmarkId, "bookmark id and bookmark node item id match");
+
+ var queryNode = rootNode.getChild(1);
+ is(queryNode.itemId, this.queryId, "query id and query node item id match");
+
+ is(PlacesControllerDragHelper.canMoveNode(queryNode),
+ true, "can move query node");
+ }
+ });
+
+ // test that special folders cannot be moved
+ // test that special folders shortcuts can be moved
+ tests.push({
+ folders: [PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.tagsFolderId, PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.toolbarFolderId],
+ shortcuts: {},
+ populate: function() {
+ for (var i = 0; i < this.folders.length; i++) {
+ var id = this.folders[i];
+ this.shortcuts[id] =
+ PlacesUtils.bookmarks.insertBookmark(rootId, makeURI("place:folder=" + id), IDX, "");
+ }
+ },
+ validate: function() {
+ // test toolbar shortcut node
+ is(rootNode.childCount, this.folders.length,
+ "populated data to the test root");
+
+ function getRootChildNode(aId) {
+ var node = PlacesUtils.getFolderContents(PlacesUtils.placesRootId, false, true).root;
+ for (var i = 0; i < node.childCount; i++) {
+ var child = node.getChild(i);
+ if (child.itemId == aId) {
+ node.containerOpen = false;
+ return child;
+ }
+ }
+ node.containerOpen = false;
+ ok(false, "Unable to find child node");
+ return null;
+ }
+
+ for (var i = 0; i < this.folders.length; i++) {
+ var id = this.folders[i];
+
+ var node = getRootChildNode(id);
+ isnot(node, null, "Node found");
+ is(PlacesControllerDragHelper.canMoveNode(node),
+ false, "shouldn't be able to move special folder node");
+
+ var shortcutId = this.shortcuts[id];
+ var shortcutNode = rootNode.getChild(i);
+
+ is(shortcutNode.itemId, shortcutId, "shortcut id and shortcut node item id match");
+
+ dump("can move shortcut node?\n");
+ is(PlacesControllerDragHelper.canMoveNode(shortcutNode),
+ true, "should be able to move special folder shortcut node");
+ }
+ }
+ });
+
+ // test that a tag container cannot be moved
+ tests.push({
+ populate: function() {
+ // tag a uri
+ this.uri = makeURI("http://foo.com");
+ PlacesUtils.tagging.tagURI(this.uri, ["bar"]);
+ registerCleanupFunction(() => PlacesUtils.tagging.untagURI(this.uri, ["bar"]));
+ },
+ validate: function() {
+ // get tag root
+ var query = PlacesUtils.history.getNewQuery();
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY;
+ var tagsNode = PlacesUtils.history.executeQuery(query, options).root;
+
+ tagsNode.containerOpen = true;
+ is(tagsNode.childCount, 1, "has new tag");
+
+ var tagNode = tagsNode.getChild(0);
+
+ is(PlacesControllerDragHelper.canMoveNode(tagNode),
+ false, "should not be able to move tag container node");
+ tagsNode.containerOpen = false;
+ }
+ });
+
+ tests.forEach(function(aTest) {
+ PlacesUtils.bookmarks.removeFolderChildren(rootId);
+ aTest.populate();
+ aTest.validate();
+ });
+
+ rootNode.containerOpen = false;
+ PlacesUtils.bookmarks.removeItem(rootId);
+}
diff --git a/browser/components/places/tests/browser/browser_425884.js b/browser/components/places/tests/browser/browser_425884.js
new file mode 100644
index 000000000..655eb1ffd
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_425884.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ Deep copy of bookmark data, using the front-end codepath:
+
+ - create test folder A
+ - add a subfolder to folder A, and add items to it
+ - validate folder A (sanity check)
+ - copy folder A, creating new folder B, using the front-end path
+ - validate folder B
+ - undo copy transaction
+ - validate folder B (empty)
+ - redo copy transaction
+ - validate folder B's contents
+*/
+
+add_task(function* () {
+ // sanity check
+ ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
+ ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?");
+
+ let toolbarId = PlacesUtils.toolbarFolderId;
+ let toolbarNode = PlacesUtils.getFolderContents(toolbarId).root;
+
+ let oldCount = toolbarNode.childCount;
+ let testRoot = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "test root"
+ });
+ is(toolbarNode.childCount, oldCount+1, "confirm test root node is a container, and is empty");
+
+ let testRootNode = toolbarNode.getChild(toolbarNode.childCount-1);
+ testRootNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ testRootNode.containerOpen = true;
+ is(testRootNode.childCount, 0, "confirm test root node is a container, and is empty");
+
+ // create folder A, fill it, validate its contents
+ let folderA = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: testRoot.guid,
+ title: "A"
+ });
+
+ yield populate(folderA);
+
+ let folderAId = yield PlacesUtils.promiseItemId(folderA.guid);
+ let folderANode = PlacesUtils.getFolderContents(folderAId).root;
+ validate(folderANode);
+ is(testRootNode.childCount, 1, "create test folder");
+
+ // copy it, using the front-end helper functions
+ let serializedNode = PlacesUtils.wrapNode(folderANode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
+ let rawNode = PlacesUtils.unwrapNodes(serializedNode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER).shift();
+ // confirm serialization
+ ok(rawNode.type, "confirm json node");
+ folderANode.containerOpen = false;
+
+ let testRootId = yield PlacesUtils.promiseItemId(testRoot.guid);
+ let transaction = PlacesUIUtils.makeTransaction(rawNode,
+ PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
+ testRootId,
+ -1,
+ true);
+ ok(transaction, "create transaction");
+ PlacesUtils.transactionManager.doTransaction(transaction);
+ // confirm copy
+ is(testRootNode.childCount, 2, "create test folder via copy");
+
+ // validate the copy
+ let folderBNode = testRootNode.getChild(1);
+ validate(folderBNode);
+
+ // undo the transaction, confirm the removal
+ PlacesUtils.transactionManager.undoTransaction();
+ is(testRootNode.childCount, 1, "confirm undo removed the copied folder");
+
+ // redo the transaction
+ PlacesUtils.transactionManager.redoTransaction();
+ is(testRootNode.childCount, 2, "confirm redo re-copied the folder");
+ folderBNode = testRootNode.getChild(1);
+ validate(folderBNode);
+
+ // Close containers, cleaning up their observers.
+ testRootNode.containerOpen = false;
+ toolbarNode.containerOpen = false;
+
+ // clean up
+ PlacesUtils.transactionManager.undoTransaction();
+ yield PlacesUtils.bookmarks.remove(folderA.guid);
+});
+
+var populate = Task.async(function* (parentFolder) {
+ let folder = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: parentFolder.guid,
+ title: "test folder"
+ });
+
+ yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: folder.guid,
+ title: "test bookmark",
+ url: "http://foo"
+ });
+
+ yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: folder.guid
+ });
+});
+
+function validate(aNode) {
+ PlacesUtils.asContainer(aNode);
+ aNode.containerOpen = true;
+ is(aNode.childCount, 1, "confirm child count match");
+ var folderNode = aNode.getChild(0);
+ is(folderNode.title, "test folder", "confirm folder title");
+ PlacesUtils.asContainer(folderNode);
+ folderNode.containerOpen = true;
+ is(folderNode.childCount, 2, "confirm child count match");
+ folderNode.containerOpen = false;
+ aNode.containerOpen = false;
+}
diff --git a/browser/components/places/tests/browser/browser_435851_copy_query.js b/browser/components/places/tests/browser/browser_435851_copy_query.js
new file mode 100644
index 000000000..92f818b41
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_435851_copy_query.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* test that copying a non movable query or folder shortcut makes a new query with the same url, not a deep copy */
+
+const SHORTCUT_URL = "place:folder=2";
+const QUERY_URL = "place:sort=8&maxResults=10";
+
+add_task(function* copy_toolbar_shortcut() {
+ let library = yield promiseLibrary();
+
+ registerCleanupFunction(function () {
+ library.close();
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ });
+
+ library.PlacesOrganizer.selectLeftPaneQuery("BookmarksToolbar");
+
+ yield promiseClipboard(function () { library.PlacesOrganizer._places.controller.copy(); },
+ PlacesUtils.TYPE_X_MOZ_PLACE);
+
+ library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+ library.ContentTree.view.controller.paste();
+
+ let toolbarCopyNode = library.ContentTree.view.view.nodeForTreeIndex(0);
+ is(toolbarCopyNode.type,
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
+ "copy is still a folder shortcut");
+
+ PlacesUtils.bookmarks.removeItem(toolbarCopyNode.itemId);
+ library.PlacesOrganizer.selectLeftPaneQuery("BookmarksToolbar");
+ is(library.PlacesOrganizer._places.selectedNode.type,
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
+ "original is still a folder shortcut");
+});
+
+add_task(function* copy_history_query() {
+ let library = yield promiseLibrary();
+
+ library.PlacesOrganizer.selectLeftPaneQuery("History");
+
+ yield promiseClipboard(function () { library.PlacesOrganizer._places.controller.copy(); },
+ PlacesUtils.TYPE_X_MOZ_PLACE);
+
+ library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+ library.ContentTree.view.controller.paste();
+
+ let historyCopyNode = library.ContentTree.view.view.nodeForTreeIndex(0);
+ is(historyCopyNode.type,
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY,
+ "copy is still a query");
+
+ PlacesUtils.bookmarks.removeItem(historyCopyNode.itemId);
+ library.PlacesOrganizer.selectLeftPaneQuery("History");
+ is(library.PlacesOrganizer._places.selectedNode.type,
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY,
+ "original is still a query");
+});
diff --git a/browser/components/places/tests/browser/browser_475045.js b/browser/components/places/tests/browser/browser_475045.js
new file mode 100644
index 000000000..7d562349b
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_475045.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+// Instead of loading EventUtils.js into the test scope in browser-test.js for all tests,
+// we only need EventUtils.js for a few files which is why we are using loadSubScript.
+var EventUtils = {};
+this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+add_task(function* test() {
+ // Make sure the bookmarks bar is visible and restore its state on cleanup.
+ let toolbar = document.getElementById("PersonalToolbar");
+ ok(toolbar, "PersonalToolbar should not be null");
+
+ if (toolbar.collapsed) {
+ yield promiseSetToolbarVisibility(toolbar, true);
+ registerCleanupFunction(function() {
+ return promiseSetToolbarVisibility(toolbar, false);
+ });
+ }
+
+ // Setup the node we will use to be dropped. The actual node used does not
+ // matter because we will set its data, effect, and mimeType manually.
+ let placesItems = document.getElementById("PlacesToolbarItems");
+ ok(placesItems, "PlacesToolbarItems should not be null");
+ ok(placesItems.localName == "scrollbox", "PlacesToolbarItems should not be null");
+ ok(placesItems.childNodes[0], "PlacesToolbarItems must have at least one child");
+
+ /**
+ * Simulates a drop of a URI onto the bookmarks bar.
+ *
+ * @param aEffect
+ * The effect to use for the drop operation: move, copy, or link.
+ * @param aMimeType
+ * The mime type to use for the drop operation.
+ */
+ let simulateDragDrop = function(aEffect, aMimeType) {
+ const uriSpec = "http://www.mozilla.org/D1995729-A152-4e30-8329-469B01F30AA7";
+ let uri = makeURI(uriSpec);
+ EventUtils.synthesizeDrop(placesItems.childNodes[0],
+ placesItems,
+ [[{type: aMimeType,
+ data: uriSpec}]],
+ aEffect, window);
+
+ // Verify that the drop produces exactly one bookmark.
+ let bookmarkIds = PlacesUtils.bookmarks
+ .getBookmarkIdsForURI(uri);
+ ok(bookmarkIds.length == 1, "There should be exactly one bookmark");
+
+ PlacesUtils.bookmarks.removeItem(bookmarkIds[0]);
+
+ // Verify that we removed the bookmark successfully.
+ ok(!PlacesUtils.bookmarks.isBookmarked(uri), "URI should be removed");
+ }
+
+ // Simulate a bookmark drop for all of the mime types and effects.
+ let mimeTypes = ["text/plain", "text/unicode", "text/x-moz-url"];
+ let effects = ["move", "copy", "link"];
+ effects.forEach(function (effect) {
+ mimeTypes.forEach(function (mimeType) {
+ simulateDragDrop(effect, mimeType);
+ });
+ });
+});
diff --git a/browser/components/places/tests/browser/browser_555547.js b/browser/components/places/tests/browser/browser_555547.js
new file mode 100644
index 000000000..0654139cb
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_555547.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(function* test() {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(sidebarBox.hidden, true, "The sidebar should be hidden");
+
+ // Uncollapse the personal toolbar if needed.
+ let toolbar = document.getElementById("PersonalToolbar");
+ let wasCollapsed = toolbar.collapsed;
+ if (wasCollapsed) {
+ yield promiseSetToolbarVisibility(toolbar, true);
+ }
+
+ let sidebar = yield promiseLoadedSidebar("viewBookmarksSidebar");
+ registerCleanupFunction(() => {
+ SidebarUI.hide();
+ });
+
+ // Focus the tree and check if its controller is returned.
+ let tree = sidebar.contentDocument.getElementById("bookmarks-view");
+ tree.focus();
+
+ let controller = doGetPlacesControllerForCommand("placesCmd_copy");
+ let treeController = tree.controllers
+ .getControllerForCommand("placesCmd_copy");
+ ok(controller == treeController, "tree controller was returned");
+
+ // Open the context menu for a toolbar item, and check if the toolbar's
+ // controller is returned.
+ let toolbarItems = document.getElementById("PlacesToolbarItems");
+ EventUtils.synthesizeMouse(toolbarItems.childNodes[0],
+ 4, 4, { type: "contextmenu", button: 2 },
+ window);
+ controller = doGetPlacesControllerForCommand("placesCmd_copy");
+ let toolbarController = document.getElementById("PlacesToolbar")
+ .controllers
+ .getControllerForCommand("placesCmd_copy");
+ ok(controller == toolbarController, "the toolbar controller was returned");
+
+ document.getElementById("placesContext").hidePopup();
+
+ // Now that the context menu is closed, try to get the tree controller again.
+ tree.focus();
+ controller = doGetPlacesControllerForCommand("placesCmd_copy");
+ ok(controller == treeController, "tree controller was returned");
+
+ if (wasCollapsed) {
+ yield promiseSetToolbarVisibility(toolbar, false);
+ }
+});
+
+function promiseLoadedSidebar(cmd) {
+ return new Promise(resolve => {
+ let sidebar = document.getElementById("sidebar");
+ sidebar.addEventListener("load", function onLoad() {
+ sidebar.removeEventListener("load", onLoad, true);
+ resolve(sidebar);
+ }, true);
+
+ SidebarUI.show(cmd);
+ });
+}
diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_addFolderDefaultButton.js b/browser/components/places/tests/browser/browser_bookmarkProperties_addFolderDefaultButton.js
new file mode 100644
index 000000000..a1f091ebe
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addFolderDefaultButton.js
@@ -0,0 +1,53 @@
+"use strict"
+
+add_task(function* () {
+ info("Bug 475529 - Add is the default button for the new folder dialog + " +
+ "Bug 1206376 - Changing properties of a new bookmark while adding it " +
+ "acts on the last bookmark in the current container");
+
+ // Add a new bookmark at index 0 in the unfiled folder.
+ let insertionIndex = 0;
+ let newBookmark = yield PlacesUtils.bookmarks.insert({
+ index: insertionIndex,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: "http://example.com/",
+ });
+ let newBookmarkId = yield PlacesUtils.promiseItemId(newBookmark.guid);
+
+ yield withSidebarTree("bookmarks", function* (tree) {
+ // Select the new bookmark in the sidebar.
+ tree.selectItems([newBookmarkId]);
+ ok(tree.controller.isCommandEnabled("placesCmd_new:folder"),
+ "'placesCmd_new:folder' on current selected node is enabled");
+
+ // Create a new folder. Since the new bookmark is selected, and new items
+ // are inserted at the index of the currently selected item, the new folder
+ // will be inserted at index 0.
+ yield withBookmarksDialog(
+ false,
+ function openDialog() {
+ tree.controller.doCommand("placesCmd_new:folder");
+ },
+ function* test(dialogWin) {
+ let promiseTitleChangeNotification = promiseBookmarksNotification(
+ "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val =="n");
+
+ fillBookmarkTextField("editBMPanel_namePicker", "n", dialogWin, false);
+
+ // Confirm and close the dialog.
+ EventUtils.synthesizeKey("VK_RETURN", {}, dialogWin);
+ yield promiseTitleChangeNotification;
+
+ let newFolder = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: insertionIndex,
+ });
+
+ is(newFolder.title, "n", "folder name has been edited");
+ yield PlacesUtils.bookmarks.remove(newFolder);
+ yield PlacesUtils.bookmarks.remove(newBookmark);
+ }
+ );
+ });
+});
diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js b/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js
new file mode 100644
index 000000000..5283a1610
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addKeywordForThisSearch.js
@@ -0,0 +1,110 @@
+"use strict"
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser/keyword_form.html";
+
+add_task(function* () {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: TEST_URL,
+ }, function* (browser) {
+ // We must wait for the context menu code to build metadata.
+ yield openContextMenuForContentSelector(browser, '#form1 > input[name="search"]');
+
+ yield withBookmarksDialog(true, AddKeywordForSearchField, function* (dialogWin) {
+ let acceptBtn = dialogWin.document.documentElement.getButton("accept");
+ ok(acceptBtn.disabled, "Accept button is disabled");
+
+ let promiseKeywordNotification = promiseBookmarksNotification(
+ "onItemChanged", (itemId, prop, isAnno, val) => prop == "keyword" && val =="kw");
+
+ fillBookmarkTextField("editBMPanel_keywordField", "kw", dialogWin);
+
+ ok(!acceptBtn.disabled, "Accept button is enabled");
+
+ // The dialog is instant apply.
+ yield promiseKeywordNotification;
+
+ // After the notification, the keywords cache will update asynchronously.
+ info("Check the keyword entry has been created");
+ let entry;
+ yield waitForCondition(function* () {
+ entry = yield PlacesUtils.keywords.fetch("kw");
+ return !!entry;
+ }, "Unable to find the expected keyword");
+ is(entry.keyword, "kw", "keyword is correct");
+ is(entry.url.href, TEST_URL, "URL is correct");
+ is(entry.postData, "accenti%3D%E0%E8%EC%F2%F9&search%3D%25s", "POST data is correct");
+
+ info("Check the charset has been saved");
+ let charset = yield PlacesUtils.getCharsetForURI(NetUtil.newURI(TEST_URL));
+ is(charset, "windows-1252", "charset is correct");
+
+ // Now check getShortcutOrURI.
+ let data = yield getShortcutOrURIAndPostData("kw test");
+ is(getPostDataString(data.postData), "accenti=\u00E0\u00E8\u00EC\u00F2\u00F9&search=test", "getShortcutOrURI POST data is correct");
+ is(data.url, TEST_URL, "getShortcutOrURI URL is correct");
+ });
+ });
+});
+
+add_task(function* reopen_same_field() {
+ yield PlacesUtils.keywords.insert({
+ url: TEST_URL,
+ keyword: "kw",
+ postData: "accenti%3D%E0%E8%EC%F2%F9&search%3D%25s"
+ });
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.keywords.remove("kw");
+ });
+ // Reopening on the same input field should show the existing keyword.
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: TEST_URL,
+ }, function* (browser) {
+ // We must wait for the context menu code to build metadata.
+ yield openContextMenuForContentSelector(browser, '#form1 > input[name="search"]');
+
+ yield withBookmarksDialog(true, AddKeywordForSearchField, function* (dialogWin) {
+ let acceptBtn = dialogWin.document.documentElement.getButton("accept");
+ ok(acceptBtn.disabled, "Accept button is disabled");
+
+ let elt = dialogWin.document.getElementById("editBMPanel_keywordField");
+ is(elt.value, "kw");
+ });
+ });
+});
+
+add_task(function* open_other_field() {
+ yield PlacesUtils.keywords.insert({
+ url: TEST_URL,
+ keyword: "kw2",
+ postData: "search%3D%25s"
+ });
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.keywords.remove("kw2");
+ });
+ // Reopening on another field of the same page that has different postData
+ // should not show the existing keyword.
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: TEST_URL,
+ }, function* (browser) {
+ // We must wait for the context menu code to build metadata.
+ yield openContextMenuForContentSelector(browser, '#form2 > input[name="search"]');
+
+ yield withBookmarksDialog(true, AddKeywordForSearchField, function* (dialogWin) {
+ let acceptBtn = dialogWin.document.documentElement.getButton("accept");
+ ok(acceptBtn.disabled, "Accept button is disabled");
+
+ let elt = dialogWin.document.getElementById("editBMPanel_keywordField");
+ is(elt.value, "");
+ });
+ });
+});
+
+function getPostDataString(stream) {
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ sis.init(stream);
+ return sis.read(stream.available()).split("\n").pop();
+}
diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_addLivemark.js b/browser/components/places/tests/browser/browser_bookmarkProperties_addLivemark.js
new file mode 100644
index 000000000..d9f4c07d7
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_addLivemark.js
@@ -0,0 +1,39 @@
+"use strict"
+
+add_task(function* () {
+ info("Add a live bookmark editing its data");
+
+ yield withSidebarTree("bookmarks", function* (tree) {
+ let itemId = PlacesUIUtils.leftPaneQueries["UnfiledBookmarks"];
+ tree.selectItems([itemId]);
+
+ yield withBookmarksDialog(
+ true,
+ function openDialog() {
+ PlacesCommandHook.addLiveBookmark("http://livemark.com/",
+ "livemark", "description");
+ },
+ function* test(dialogWin) {
+ let promiseTitleChangeNotification = promiseBookmarksNotification(
+ "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "modified");
+
+ fillBookmarkTextField("editBMPanel_namePicker", "modified", dialogWin);
+
+ yield promiseTitleChangeNotification;
+
+ let bookmark = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX
+ });
+
+ is(bookmark.title, "modified", "folder name has been edited");
+
+ let livemark = yield PlacesUtils.livemarks.getLivemark({
+ guid: bookmark.guid
+ });
+ is(livemark.feedURI.spec, "http://livemark.com/", "livemark has the correct url");
+ is(livemark.title, "modified", "livemark has the correct title");
+ }
+ );
+ });
+});
diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js b/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js
new file mode 100644
index 000000000..fde9ea272
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_editTagContainer.js
@@ -0,0 +1,71 @@
+"use strict"
+
+add_task(function* () {
+ info("Bug 479348 - Properties on a root should be read-only.");
+ let uri = NetUtil.newURI("http://example.com/");
+ let bm = yield PlacesUtils.bookmarks.insert({
+ url: uri.spec,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid
+ });
+ registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.remove(bm);
+ });
+
+ PlacesUtils.tagging.tagURI(uri, ["tag1"]);
+
+ let library = yield promiseLibrary();
+ let PlacesOrganizer = library.PlacesOrganizer;
+ registerCleanupFunction(function* () {
+ yield promiseLibraryClosed(library);
+ });
+
+ PlacesOrganizer.selectLeftPaneQuery("Tags");
+ let tree = PlacesOrganizer._places;
+ let tagsContainer = tree.selectedNode;
+ tagsContainer.containerOpen = true;
+ let fooTag = tagsContainer.getChild(0);
+ let tagNode = fooTag;
+ tree.selectNode(fooTag);
+ is(tagNode.title, 'tag1', "tagNode title is correct");
+
+ ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
+ "'placesCmd_show:info' on current selected node is enabled");
+
+ yield withBookmarksDialog(
+ true,
+ function openDialog() {
+ tree.controller.doCommand("placesCmd_show:info");
+ },
+ function* test(dialogWin) {
+ // Check that the dialog is not read-only.
+ ok(!dialogWin.gEditItemOverlay.readOnly, "Dialog should not be read-only");
+
+ // Check that name picker is not read only
+ let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
+ ok(!namepicker.readOnly, "Name field should not be read-only");
+ is(namepicker.value, "tag1", "Node title is correct");
+
+ let promiseTitleChangeNotification = promiseBookmarksNotification(
+ "onItemChanged", (itemId, prop, isAnno, val) => prop == "title" && val == "tag2");
+
+ fillBookmarkTextField("editBMPanel_namePicker", "tag2", dialogWin);
+
+ yield promiseTitleChangeNotification;
+
+ is(namepicker.value, "tag2", "Node title has been properly edited");
+
+ // Check the shortcut's title.
+ is(tree.selectedNode.title, "tag2", "The node has the correct title");
+
+ // Check the tags have been edited.
+ let tags = PlacesUtils.tagging.getTagsForURI(uri);
+ is(tags.length, 1, "Found the right number of tags");
+ ok(tags.includes("tag2"), "Found the expected tag");
+ }
+ );
+
+ // Check the tag change has been reverted.
+ let tags = PlacesUtils.tagging.getTagsForURI(uri);
+ is(tags.length, 1, "Found the right number of tags");
+ ok(tags.includes("tag1"), "Found the expected tag");
+});
diff --git a/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js b/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
new file mode 100644
index 000000000..6f499888c
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_readOnlyRoot.js
@@ -0,0 +1,42 @@
+"use strict"
+
+add_task(function* () {
+ info("Bug 479348 - Properties on a root should be read-only.");
+
+ yield withSidebarTree("bookmarks", function* (tree) {
+ let itemId = PlacesUIUtils.leftPaneQueries["UnfiledBookmarks"];
+ tree.selectItems([itemId]);
+ ok(tree.controller.isCommandEnabled("placesCmd_show:info"),
+ "'placesCmd_show:info' on current selected node is enabled");
+
+ yield withBookmarksDialog(
+ true,
+ function openDialog() {
+ tree.controller.doCommand("placesCmd_show:info");
+ },
+ function* test(dialogWin) {
+ // Check that the dialog is read-only.
+ ok(dialogWin.gEditItemOverlay.readOnly, "Dialog is read-only");
+ // Check that accept button is disabled
+ let acceptButton = dialogWin.document.documentElement.getButton("accept");
+ ok(acceptButton.disabled, "Accept button is disabled");
+
+ // Check that name picker is read only
+ let namepicker = dialogWin.document.getElementById("editBMPanel_namePicker");
+ ok(namepicker.readOnly, "Name field is read-only");
+ is(namepicker.value,
+ PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId),
+ "Node title is correct");
+ // Blur the field and ensure root's name has not been changed.
+ namepicker.blur();
+ is(namepicker.value,
+ PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId),
+ "Root title is correct");
+ // Check the shortcut's title.
+ let bookmark = yield PlacesUtils.bookmarks.fetch(tree.selectedNode.bookmarkGuid);
+ is(bookmark.title, null,
+ "Shortcut title is null");
+ }
+ );
+ });
+});
diff --git a/browser/components/places/tests/browser/browser_bookmark_all_tabs.js b/browser/components/places/tests/browser/browser_bookmark_all_tabs.js
new file mode 100644
index 000000000..afd32b78a
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmark_all_tabs.js
@@ -0,0 +1,37 @@
+/**
+ * Test for Bug 446171 - Name field of bookmarks saved via 'Bookmark All Tabs'
+ * has '(null)' value if history is disabled or just in private browsing mode
+ */
+"use strict"
+
+add_task(function* () {
+ const BASE_URL = "http://example.org/browser/browser/components/places/tests/browser/";
+ const TEST_PAGES = [
+ BASE_URL + "bookmark_dummy_1.html",
+ BASE_URL + "bookmark_dummy_2.html",
+ BASE_URL + "bookmark_dummy_1.html"
+ ];
+
+ function promiseAddTab(url) {
+ return BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ }
+
+ let tabs = yield Promise.all(TEST_PAGES.map(promiseAddTab));
+
+ let URIs = PlacesCommandHook.uniqueCurrentPages;
+ is(URIs.length, 3, "Only unique pages are returned");
+
+ Assert.deepEqual(URIs.map(URI => URI.uri.spec), [
+ "about:blank",
+ BASE_URL + "bookmark_dummy_1.html",
+ BASE_URL + "bookmark_dummy_2.html"
+ ], "Correct URIs are returned");
+
+ Assert.deepEqual(URIs.map(URI => URI.title), [
+ "New Tab", "Bookmark Dummy 1", "Bookmark Dummy 2"
+ ], "Correct titles are returned");
+
+ registerCleanupFunction(function* () {
+ yield Promise.all(tabs.map(BrowserTestUtils.removeTab));
+ });
+});
diff --git a/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js b/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js
new file mode 100644
index 000000000..85ce25311
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js
@@ -0,0 +1,61 @@
+"use strict";
+
+const TEST_URL = 'http://example.com/browser/browser/components/places/tests/browser/pageopeningwindow.html';
+
+function makeBookmarkFor(url, keyword) {
+ return Promise.all([
+ PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "bookmarklet",
+ url: url }),
+ PlacesUtils.keywords.insert({url: url,
+ keyword: keyword})
+ ]);
+
+}
+
+add_task(function* openKeywordBookmarkWithWindowOpen() {
+ // This is the current default, but let's not assume that...
+ yield new Promise((resolve, reject) => {
+ SpecialPowers.pushPrefEnv({ 'set': [[ 'browser.link.open_newwindow', 3 ],
+ [ 'dom.disable_open_during_load', true ]] },
+ resolve);
+ });
+
+ let moztab;
+ let tabOpened = BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla")
+ .then((tab) => { moztab = tab; });
+ let keywordForBM = "openmeatab";
+
+ let bookmarkInfo;
+ let bookmarkCreated =
+ makeBookmarkFor("javascript:void open('" + TEST_URL + "')", keywordForBM)
+ .then((values) => {
+ bookmarkInfo = values[0];
+ });
+ yield Promise.all([tabOpened, bookmarkCreated]);
+
+ registerCleanupFunction(function() {
+ return Promise.all([
+ PlacesUtils.bookmarks.remove(bookmarkInfo),
+ PlacesUtils.keywords.remove(keywordForBM)
+ ]);
+ });
+ gURLBar.value = keywordForBM;
+ gURLBar.focus();
+
+ let tabCreatedPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ info("Waiting for tab being created");
+ let {target: tab} = yield tabCreatedPromise;
+ info("Got tab");
+ let browser = tab.linkedBrowser;
+ if (!browser.currentURI || browser.currentURI.spec != TEST_URL) {
+ info("Waiting for browser load");
+ yield BrowserTestUtils.browserLoaded(browser);
+ }
+ is(browser.currentURI && browser.currentURI.spec, TEST_URL, "Tab with expected URL loaded.");
+ info("Waiting to remove tab");
+ yield Promise.all([ BrowserTestUtils.removeTab(tab),
+ BrowserTestUtils.removeTab(moztab) ]);
+});
diff --git a/browser/components/places/tests/browser/browser_bookmarksProperties.js b/browser/components/places/tests/browser/browser_bookmarksProperties.js
new file mode 100644
index 000000000..f7f9f4762
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -0,0 +1,450 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests the bookmarks Properties dialog.
+ */
+
+// DOM ids of Places sidebar trees.
+const SIDEBAR_HISTORY_TREE_ID = "historyTree";
+const SIDEBAR_BOOKMARKS_TREE_ID = "bookmarks-view";
+
+const SIDEBAR_HISTORY_ID = "viewHistorySidebar";
+const SIDEBAR_BOOKMARKS_ID = "viewBookmarksSidebar";
+
+// For history sidebar.
+const SIDEBAR_HISTORY_BYLASTVISITED_VIEW = "bylastvisited";
+const SIDEBAR_HISTORY_BYMOSTVISITED_VIEW = "byvisited";
+const SIDEBAR_HISTORY_BYDATE_VIEW = "byday";
+const SIDEBAR_HISTORY_BYSITE_VIEW = "bysite";
+const SIDEBAR_HISTORY_BYDATEANDSITE_VIEW = "bydateandsite";
+
+// Action to execute on the current node.
+const ACTION_EDIT = 0;
+const ACTION_ADD = 1;
+
+// If action is ACTION_ADD, set type to one of those, to define what do you
+// want to create.
+const TYPE_FOLDER = 0;
+const TYPE_BOOKMARK = 1;
+
+const TEST_URL = "http://www.example.com/";
+
+const DIALOG_URL = "chrome://browser/content/places/bookmarkProperties.xul";
+const DIALOG_URL_MINIMAL_UI = "chrome://browser/content/places/bookmarkProperties2.xul";
+
+Cu.import("resource:///modules/RecentWindow.jsm");
+var win = RecentWindow.getMostRecentBrowserWindow();
+var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+
+function add_bookmark(aURI) {
+ var bId = PlacesUtils.bookmarks
+ .insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ aURI,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "bookmark/" + aURI.spec);
+ return bId;
+}
+
+// Each test is an obj w/ a desc property and run method.
+var gTests = [];
+var gCurrentTest = null;
+
+// ------------------------------------------------------------------------------
+// Bug 462662 - Pressing Enter to select tag from autocomplete closes bookmarks properties dialog
+gTests.push({
+ desc: "Bug 462662 - Pressing Enter to select tag from autocomplete closes bookmarks properties dialog",
+ sidebar: SIDEBAR_BOOKMARKS_ID,
+ action: ACTION_EDIT,
+ itemType: null,
+ window: null,
+ _itemId: null,
+ _cleanShutdown: false,
+
+ setup: function(aCallback) {
+ // Add a bookmark in unsorted bookmarks folder.
+ this._itemId = add_bookmark(PlacesUtils._uri(TEST_URL));
+ ok(this._itemId > 0, "Correctly added a bookmark");
+ // Add a tag to this bookmark.
+ PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL),
+ ["testTag"]);
+ var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL));
+ is(tags[0], "testTag", "Correctly added a tag");
+ aCallback();
+ },
+
+ selectNode: function(tree) {
+ tree.selectItems([PlacesUtils.unfiledBookmarksFolderId]);
+ PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
+ tree.selectItems([this._itemId]);
+ is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected");
+ },
+
+ run: function() {
+ // open tags autocomplete and press enter
+ var tagsField = this.window.document.getElementById("editBMPanel_tagsField");
+ var self = this;
+
+ this.window.addEventListener("unload", function(event) {
+ self.window.removeEventListener("unload", arguments.callee, true);
+ tagsField.popup.removeEventListener("popuphidden", popupListener, true);
+ ok(self._cleanShutdown, "Dialog window should not be closed by pressing Enter on the autocomplete popup");
+ executeSoon(function () {
+ self.finish();
+ });
+ }, true);
+
+ var popupListener = {
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "popuphidden":
+ // Everything worked fine, we can stop observing the window.
+ self._cleanShutdown = true;
+ self.window.document.documentElement.cancelDialog();
+ break;
+ case "popupshown":
+ tagsField.popup.removeEventListener("popupshown", this, true);
+ // In case this test fails the window will close, the test will fail
+ // since we didn't set _cleanShutdown.
+ var tree = tagsField.popup.tree;
+ // Focus and select first result.
+ isnot(tree, null, "Autocomplete results tree exists");
+ is(tree.view.rowCount, 1, "We have 1 autocomplete result");
+ tagsField.popup.selectedIndex = 0;
+ is(tree.view.selection.count, 1,
+ "We have selected a tag from the autocomplete popup");
+ info("About to focus the autocomplete results tree");
+ tree.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {}, self.window);
+ break;
+ default:
+ ok(false, "unknown event: " + aEvent.type);
+ return;
+ }
+ }
+ };
+ tagsField.popup.addEventListener("popupshown", popupListener, true);
+ tagsField.popup.addEventListener("popuphidden", popupListener, true);
+
+ // Open tags autocomplete popup.
+ info("About to focus the tagsField");
+ executeSoon(() => {
+ tagsField.focus();
+ tagsField.value = "";
+ EventUtils.synthesizeKey("t", {}, this.window);
+ });
+ },
+
+ finish: function() {
+ SidebarUI.hide();
+ runNextTest();
+ },
+
+ cleanup: function() {
+ // Check tags have not changed.
+ var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL));
+ is(tags[0], "testTag", "Tag on node has not changed");
+
+ // Cleanup.
+ PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL), ["testTag"]);
+ PlacesUtils.bookmarks.removeItem(this._itemId);
+ }
+});
+
+// ------------------------------------------------------------------------------
+// Bug 476020 - Pressing Esc while having the tag autocomplete open closes the bookmarks panel
+
+gTests.push({
+ desc: "Bug 476020 - Pressing Esc while having the tag autocomplete open closes the bookmarks panel",
+ sidebar: SIDEBAR_BOOKMARKS_ID,
+ action: ACTION_EDIT,
+ itemType: null,
+ window: null,
+ _itemId: null,
+ _cleanShutdown: false,
+
+ setup: function(aCallback) {
+ // Add a bookmark in unsorted bookmarks folder.
+ this._itemId = add_bookmark(PlacesUtils._uri(TEST_URL));
+ ok(this._itemId > 0, "Correctly added a bookmark");
+ // Add a tag to this bookmark.
+ PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL),
+ ["testTag"]);
+ var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL));
+ is(tags[0], "testTag", "Correctly added a tag");
+ aCallback();
+ },
+
+ selectNode: function(tree) {
+ tree.selectItems([PlacesUtils.unfiledBookmarksFolderId]);
+ PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
+ tree.selectItems([this._itemId]);
+ is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected");
+ },
+
+ run: function() {
+ // open tags autocomplete and press enter
+ var tagsField = this.window.document.getElementById("editBMPanel_tagsField");
+ var self = this;
+
+ this.window.addEventListener("unload", function(event) {
+ self.window.removeEventListener("unload", arguments.callee, true);
+ tagsField.popup.removeEventListener("popuphidden", popupListener, true);
+ ok(self._cleanShutdown, "Dialog window should not be closed by pressing Escape on the autocomplete popup");
+ executeSoon(function () {
+ self.finish();
+ });
+ }, true);
+
+ var popupListener = {
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "popuphidden":
+ // Everything worked fine.
+ self._cleanShutdown = true;
+ self.window.document.documentElement.cancelDialog();
+ break;
+ case "popupshown":
+ tagsField.popup.removeEventListener("popupshown", this, true);
+ // In case this test fails the window will close, the test will fail
+ // since we didn't set _cleanShutdown.
+ var tree = tagsField.popup.tree;
+ // Focus and select first result.
+ isnot(tree, null, "Autocomplete results tree exists");
+ is(tree.view.rowCount, 1, "We have 1 autocomplete result");
+ tagsField.popup.selectedIndex = 0;
+ is(tree.view.selection.count, 1,
+ "We have selected a tag from the autocomplete popup");
+ info("About to focus the autocomplete results tree");
+ tree.focus();
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, self.window);
+ break;
+ default:
+ ok(false, "unknown event: " + aEvent.type);
+ return;
+ }
+ }
+ };
+ tagsField.popup.addEventListener("popupshown", popupListener, true);
+ tagsField.popup.addEventListener("popuphidden", popupListener, true);
+
+ // Open tags autocomplete popup.
+ info("About to focus the tagsField");
+ tagsField.focus();
+ tagsField.value = "";
+ EventUtils.synthesizeKey("t", {}, this.window);
+ },
+
+ finish: function() {
+ SidebarUI.hide();
+ runNextTest();
+ },
+
+ cleanup: function() {
+ // Check tags have not changed.
+ var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL));
+ is(tags[0], "testTag", "Tag on node has not changed");
+
+ // Cleanup.
+ PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL),
+ ["testTag"]);
+ PlacesUtils.bookmarks.removeItem(this._itemId);
+ }
+});
+
+// ------------------------------------------------------------------------------
+// Bug 491269 - Test that editing folder name in bookmarks properties dialog does not accept the dialog
+
+gTests.push({
+ desc: " Bug 491269 - Test that editing folder name in bookmarks properties dialog does not accept the dialog",
+ sidebar: SIDEBAR_HISTORY_ID,
+ action: ACTION_ADD,
+ historyView: SIDEBAR_HISTORY_BYLASTVISITED_VIEW,
+ window: null,
+
+ setup: function(aCallback) {
+ // Add a visit.
+ PlacesTestUtils.addVisits(
+ {uri: PlacesUtils._uri(TEST_URL),
+ transition: PlacesUtils.history.TRANSITION_TYPED}
+ ).then(aCallback);
+ },
+
+ selectNode: function(tree) {
+ var visitNode = tree.view.nodeForTreeIndex(0);
+ tree.selectNode(visitNode);
+ is(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected");
+ is(tree.selectedNode.itemId, -1, "The selected node is not bookmarked");
+ },
+
+ run: function() {
+ // Open folder selector.
+ var foldersExpander = this.window.document.getElementById("editBMPanel_foldersExpander");
+ var folderTree = this.window.document.getElementById("editBMPanel_folderTree");
+ var self = this;
+
+ this.window.addEventListener("unload", function(event) {
+ self.window.removeEventListener("unload", arguments.callee, true);
+ ok(self._cleanShutdown, "Dialog window should not be closed by pressing ESC in folder name textbox");
+ executeSoon(function () {
+ self.finish();
+ });
+ }, true);
+
+ folderTree.addEventListener("DOMAttrModified", function onDOMAttrModified(event) {
+ if (event.attrName != "place")
+ return;
+ folderTree.removeEventListener("DOMAttrModified", arguments.callee, false);
+ executeSoon(function () {
+ // Create a new folder.
+ var newFolderButton = self.window.document.getElementById("editBMPanel_newFolderButton");
+ newFolderButton.doCommand();
+ ok(folderTree.hasAttribute("editing"),
+ "We are editing new folder name in folder tree");
+
+ // Press Escape to discard editing new folder name.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, self.window);
+ ok(!folderTree.hasAttribute("editing"),
+ "We have finished editing folder name in folder tree");
+ self._cleanShutdown = true;
+ self.window.document.documentElement.cancelDialog();
+ });
+ }, false);
+ foldersExpander.doCommand();
+ },
+
+ finish: function() {
+ SidebarUI.hide();
+ runNextTest();
+ },
+
+ cleanup: function() {
+ return PlacesTestUtils.clearHistory();
+ }
+});
+
+// ------------------------------------------------------------------------------
+
+function test() {
+ waitForExplicitFinish();
+ // This test can take some time, if we timeout too early it could run
+ // in the middle of other tests, or hang them.
+ requestLongerTimeout(2);
+
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils in context");
+ ok(PlacesUIUtils, "PlacesUIUtils in context");
+
+ // kick off tests
+ runNextTest();
+}
+
+function runNextTest() {
+ // Cleanup from previous test.
+ if (gCurrentTest) {
+ Promise.resolve(gCurrentTest.cleanup()).then(() => {
+ info("End of test: " + gCurrentTest.desc);
+ gCurrentTest = null;
+ waitForAsyncUpdates(runNextTest);
+ });
+ return;
+ }
+
+ if (gTests.length > 0) {
+ // Goto next tests.
+ gCurrentTest = gTests.shift();
+ info("Start of test: " + gCurrentTest.desc);
+ gCurrentTest.setup(function() {
+ execute_test_in_sidebar();
+ });
+ }
+ else {
+ // Finished all tests.
+ finish();
+ }
+}
+
+/**
+ * Global functions to run a test in Properties dialog context.
+ */
+
+function execute_test_in_sidebar() {
+ var sidebar = document.getElementById("sidebar");
+ sidebar.addEventListener("load", function() {
+ sidebar.removeEventListener("load", arguments.callee, true);
+ // Need to executeSoon since the tree is initialized on sidebar load.
+ executeSoon(open_properties_dialog);
+ }, true);
+ SidebarUI.show(gCurrentTest.sidebar);
+}
+
+function open_properties_dialog() {
+ var sidebar = document.getElementById("sidebar");
+
+ // If this is history sidebar, set the required view.
+ if (gCurrentTest.sidebar == SIDEBAR_HISTORY_ID)
+ sidebar.contentDocument.getElementById(gCurrentTest.historyView).doCommand();
+
+ // Get sidebar's Places tree.
+ var sidebarTreeID = gCurrentTest.sidebar == SIDEBAR_BOOKMARKS_ID ?
+ SIDEBAR_BOOKMARKS_TREE_ID :
+ SIDEBAR_HISTORY_TREE_ID;
+ var tree = sidebar.contentDocument.getElementById(sidebarTreeID);
+ ok(tree, "Sidebar tree has been loaded");
+
+ // Ask current test to select the node to edit.
+ gCurrentTest.selectNode(tree);
+ ok(tree.selectedNode,
+ "We have a places node selected: " + tree.selectedNode.title);
+
+ // Wait for the Properties dialog.
+ function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened")
+ return;
+ ww.unregisterNotification(windowObserver);
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ waitForFocus(() => {
+ // Windows has been loaded, execute our test now.
+ executeSoon(function () {
+ // Ensure overlay is loaded
+ ok(win.gEditItemOverlay.initialized, "EditItemOverlay is initialized");
+ gCurrentTest.window = win;
+ try {
+ gCurrentTest.run();
+ } catch (ex) {
+ ok(false, "An error occured during test run: " + ex.message);
+ }
+ });
+ }, win);
+ }
+ ww.registerNotification(windowObserver);
+
+ var command = null;
+ switch (gCurrentTest.action) {
+ case ACTION_EDIT:
+ command = "placesCmd_show:info";
+ break;
+ case ACTION_ADD:
+ if (gCurrentTest.sidebar == SIDEBAR_BOOKMARKS_ID) {
+ if (gCurrentTest.itemType == TYPE_FOLDER)
+ command = "placesCmd_new:folder";
+ else if (gCurrentTest.itemType == TYPE_BOOKMARK)
+ command = "placesCmd_new:bookmark";
+ else
+ ok(false, "You didn't set a valid itemType for adding an item");
+ }
+ else
+ command = "placesCmd_createBookmark";
+ break;
+ default:
+ ok(false, "You didn't set a valid action for this test");
+ }
+ // Ensure command is enabled for this node.
+ ok(tree.controller.isCommandEnabled(command),
+ " command '" + command + "' on current selected node is enabled");
+
+ // This will open the dialog.
+ tree.controller.doCommand(command);
+}
diff --git a/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js b/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
new file mode 100644
index 000000000..1ab9411f3
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
@@ -0,0 +1,256 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URL = "http://www.mozilla.org";
+const TEST_TITLE = "example_title";
+
+var gBookmarksToolbar = window.document.getElementById("PlacesToolbar");
+var dragDirections = { LEFT: 0, UP: 1, RIGHT: 2, DOWN: 3 };
+
+/**
+ * Tests dragging on toolbar.
+ *
+ * We must test these 2 cases:
+ * - Dragging toward left, top, right should start a drag.
+ * - Dragging toward down should should open the container if the item is a
+ * container, drag the item otherwise.
+ *
+ * @param aElement
+ * DOM node element we will drag
+ * @param aExpectedDragData
+ * Array of flavors and values in the form:
+ * [ ["text/plain: sometext", "text/html: <b>sometext</b>"], [...] ]
+ * Pass an empty array to check that drag even has been canceled.
+ * @param aDirection
+ * Direction for the dragging gesture, see dragDirections helper object.
+ */
+function synthesizeDragWithDirection(aElement, aExpectedDragData, aDirection, aCallback) {
+ // Dragstart listener function.
+ gBookmarksToolbar.addEventListener("dragstart", function(event)
+ {
+ info("A dragstart event has been trapped.");
+ var dataTransfer = event.dataTransfer;
+ is(dataTransfer.mozItemCount, aExpectedDragData.length,
+ "Number of dragged items should be the same.");
+
+ for (var t = 0; t < dataTransfer.mozItemCount; t++) {
+ var types = dataTransfer.mozTypesAt(t);
+ var expecteditem = aExpectedDragData[t];
+ is(types.length, expecteditem.length,
+ "Number of flavors for item " + t + " should be the same.");
+
+ for (var f = 0; f < types.length; f++) {
+ is(types[f], expecteditem[f].substring(0, types[f].length),
+ "Flavor " + types[f] + " for item " + t + " should be the same.");
+ is(dataTransfer.mozGetDataAt(types[f], t),
+ expecteditem[f].substring(types[f].length + 2),
+ "Contents for item " + t + " with flavor " + types[f] + " should be the same.");
+ }
+ }
+
+ if (!aExpectedDragData.length)
+ ok(event.defaultPrevented, "Drag has been canceled.");
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ gBookmarksToolbar.removeEventListener("dragstart", arguments.callee, false);
+
+ // This is likely to cause a click event, and, in case we are dragging a
+ // bookmark, an unwanted page visit. Prevent the click event.
+ aElement.addEventListener("click", prevent, false);
+ EventUtils.synthesizeMouse(aElement,
+ startingPoint.x + xIncrement * 9,
+ startingPoint.y + yIncrement * 9,
+ { type: "mouseup" });
+ aElement.removeEventListener("click", prevent, false);
+
+ // Cleanup eventually opened menus.
+ if (aElement.localName == "menu" && aElement.open)
+ aElement.open = false;
+ aCallback()
+ }, false);
+
+ var prevent = function(aEvent) { aEvent.preventDefault(); }
+
+ var xIncrement = 0;
+ var yIncrement = 0;
+
+ switch (aDirection) {
+ case dragDirections.LEFT:
+ xIncrement = -1;
+ break;
+ case dragDirections.RIGHT:
+ xIncrement = +1;
+ break;
+ case dragDirections.UP:
+ yIncrement = -1;
+ break;
+ case dragDirections.DOWN:
+ yIncrement = +1;
+ break;
+ }
+
+ var rect = aElement.getBoundingClientRect();
+ var startingPoint = { x: (rect.right - rect.left)/2,
+ y: (rect.bottom - rect.top)/2 };
+
+ EventUtils.synthesizeMouse(aElement,
+ startingPoint.x,
+ startingPoint.y,
+ { type: "mousedown" });
+ EventUtils.synthesizeMouse(aElement,
+ startingPoint.x + xIncrement * 1,
+ startingPoint.y + yIncrement * 1,
+ { type: "mousemove" });
+ EventUtils.synthesizeMouse(aElement,
+ startingPoint.x + xIncrement * 9,
+ startingPoint.y + yIncrement * 9,
+ { type: "mousemove" });
+}
+
+function getToolbarNodeForItemId(aItemId) {
+ var children = document.getElementById("PlacesToolbarItems").childNodes;
+ var node = null;
+ for (var i = 0; i < children.length; i++) {
+ if (aItemId == children[i]._placesNode.itemId) {
+ node = children[i];
+ break;
+ }
+ }
+ return node;
+}
+
+function getExpectedDataForPlacesNode(aNode) {
+ var wrappedNode = [];
+ var flavors = ["text/x-moz-place",
+ "text/x-moz-url",
+ "text/plain",
+ "text/html"];
+
+ flavors.forEach(function(aFlavor) {
+ var wrappedFlavor = aFlavor + ": " +
+ PlacesUtils.wrapNode(aNode, aFlavor);
+ wrappedNode.push(wrappedFlavor);
+ });
+
+ return [wrappedNode];
+}
+
+var gTests = [
+
+// ------------------------------------------------------------------------------
+
+ {
+ desc: "Drag a folder on toolbar",
+ run: function() {
+ // Create a test folder to be dragged.
+ var folderId = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.toolbarFolderId,
+ TEST_TITLE,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ var element = getToolbarNodeForItemId(folderId);
+ isnot(element, null, "Found node on toolbar");
+
+ isnot(element._placesNode, null, "Toolbar node has an associated Places node.");
+ var expectedData = getExpectedDataForPlacesNode(element._placesNode);
+
+ info("Dragging left");
+ synthesizeDragWithDirection(element, expectedData, dragDirections.LEFT,
+ function ()
+ {
+ info("Dragging right");
+ synthesizeDragWithDirection(element, expectedData, dragDirections.RIGHT,
+ function ()
+ {
+ info("Dragging up");
+ synthesizeDragWithDirection(element, expectedData, dragDirections.UP,
+ function ()
+ {
+ info("Dragging down");
+ synthesizeDragWithDirection(element, new Array(), dragDirections.DOWN,
+ function () {
+ // Cleanup.
+ PlacesUtils.bookmarks.removeItem(folderId);
+ nextTest();
+ });
+ });
+ });
+ });
+ }
+ },
+
+// ------------------------------------------------------------------------------
+
+ {
+ desc: "Drag a bookmark on toolbar",
+ run: function() {
+ // Create a test bookmark to be dragged.
+ var itemId = PlacesUtils.bookmarks
+ .insertBookmark(PlacesUtils.toolbarFolderId,
+ PlacesUtils._uri(TEST_URL),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ TEST_TITLE);
+ var element = getToolbarNodeForItemId(itemId);
+ isnot(element, null, "Found node on toolbar");
+
+ isnot(element._placesNode, null, "Toolbar node has an associated Places node.");
+ var expectedData = getExpectedDataForPlacesNode(element._placesNode);
+
+ info("Dragging left");
+ synthesizeDragWithDirection(element, expectedData, dragDirections.LEFT,
+ function ()
+ {
+ info("Dragging right");
+ synthesizeDragWithDirection(element, expectedData, dragDirections.RIGHT,
+ function ()
+ {
+ info("Dragging up");
+ synthesizeDragWithDirection(element, expectedData, dragDirections.UP,
+ function ()
+ {
+ info("Dragging down");
+ synthesizeDragWithDirection(element, expectedData, dragDirections.DOWN,
+ function () {
+ // Cleanup.
+ PlacesUtils.bookmarks.removeItem(itemId);
+ nextTest();
+ });
+ });
+ });
+ });
+ }
+ },
+];
+
+function nextTest() {
+ if (gTests.length) {
+ var test = gTests.shift();
+ waitForFocus(function() {
+ info("Start of test: " + test.desc);
+ test.run();
+ });
+ }
+ else if (wasCollapsed) {
+ // Collapse the personal toolbar if needed.
+ promiseSetToolbarVisibility(toolbar, false).then(finish);
+ } else {
+ finish();
+ }
+}
+
+var toolbar = document.getElementById("PersonalToolbar");
+var wasCollapsed = toolbar.collapsed;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Uncollapse the personal toolbar if needed.
+ if (wasCollapsed) {
+ promiseSetToolbarVisibility(toolbar, true).then(nextTest);
+ } else {
+ nextTest();
+ }
+}
+
diff --git a/browser/components/places/tests/browser/browser_forgetthissite_single.js b/browser/components/places/tests/browser/browser_forgetthissite_single.js
new file mode 100644
index 000000000..b1d7936e9
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_forgetthissite_single.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";
+
+const TEST_URIs = [
+ "http://www.mozilla.org/test1",
+ "http://www.mozilla.org/test2"
+];
+
+// This test makes sure that the Forget This Site command is hidden for multiple
+// selections.
+add_task(function* () {
+ // Add a history entry.
+ ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
+
+ let places = [];
+ let transition = PlacesUtils.history.TRANSITION_TYPED;
+ TEST_URIs.forEach(uri => places.push({uri: PlacesUtils._uri(uri), transition}));
+
+ yield PlacesTestUtils.addVisits(places);
+ yield testForgetThisSiteVisibility(1);
+ yield testForgetThisSiteVisibility(2);
+
+ // Cleanup.
+ yield PlacesTestUtils.clearHistory();
+});
+
+var testForgetThisSiteVisibility = Task.async(function* (selectionCount) {
+ let organizer = yield promiseLibrary();
+
+ // Select History in the left pane.
+ organizer.PlacesOrganizer.selectLeftPaneQuery("History");
+ let PO = organizer.PlacesOrganizer;
+ let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ histContainer.containerOpen = true;
+ PO._places.selectNode(histContainer.getChild(0));
+
+ // Select the first history entry.
+ let doc = organizer.document;
+ let tree = doc.getElementById("placeContent");
+ let selection = tree.view.selection;
+ selection.clearSelection();
+ selection.rangedSelect(0, selectionCount - 1, true);
+ is(selection.count, selectionCount, "The selected range is as big as expected");
+
+ // Open the context menu.
+ let contextmenu = doc.getElementById("placesContext");
+ let popupShown = promisePopupShown(contextmenu);
+
+ // Get cell coordinates.
+ let rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "text");
+ // Initiate a context menu for the selected cell.
+ EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {type: "contextmenu", button: 2}, organizer);
+ yield popupShown;
+
+ let forgetThisSite = doc.getElementById("placesContext_deleteHost");
+ let hideForgetThisSite = (selectionCount != 1);
+ is(forgetThisSite.hidden, hideForgetThisSite,
+ `The Forget this site menu item should ${hideForgetThisSite ? "" : "not "}` +
+ ` be hidden with ${selectionCount} items selected`);
+
+ // Close the context menu.
+ contextmenu.hidePopup();
+
+ // Close the library window.
+ yield promiseLibraryClosed(organizer);
+});
+
+function promisePopupShown(popup) {
+ return new Promise(resolve => {
+ popup.addEventListener("popupshown", function onShown() {
+ popup.removeEventListener("popupshown", onShown, true);
+ resolve();
+ }, true);
+ });
+}
diff --git a/browser/components/places/tests/browser/browser_history_sidebar_search.js b/browser/components/places/tests/browser/browser_history_sidebar_search.js
new file mode 100644
index 000000000..89472c4ab
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_history_sidebar_search.js
@@ -0,0 +1,64 @@
+add_task(function* test () {
+ let sidebar = document.getElementById("sidebar");
+
+ // Visited pages listed by descending visit date.
+ let pages = [
+ "http://sidebar.mozilla.org/a",
+ "http://sidebar.mozilla.org/b",
+ "http://sidebar.mozilla.org/c",
+ "http://www.mozilla.org/d",
+ ];
+
+ // Number of pages that will be filtered out by the search.
+ const FILTERED_COUNT = 1;
+
+ yield PlacesTestUtils.clearHistory();
+
+ // Add some visited page.
+ let time = Date.now();
+ let places = [];
+ for (let i = 0; i < pages.length; i++) {
+ places.push({ uri: NetUtil.newURI(pages[i]),
+ visitDate: (time - i) * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED });
+ }
+ yield PlacesTestUtils.addVisits(places);
+
+ yield withSidebarTree("history", function* () {
+ info("Set 'by last visited' view");
+ sidebar.contentDocument.getElementById("bylastvisited").doCommand();
+ let tree = sidebar.contentDocument.getElementById("historyTree");
+ check_tree_order(tree, pages);
+
+ // Set a search value.
+ let searchBox = sidebar.contentDocument.getElementById("search-box");
+ ok(searchBox, "search box is in context");
+ searchBox.value = "sidebar.mozilla";
+ searchBox.doCommand();
+ check_tree_order(tree, pages, -FILTERED_COUNT);
+
+ info("Reset the search");
+ searchBox.value = "";
+ searchBox.doCommand();
+ check_tree_order(tree, pages);
+ });
+
+ yield PlacesTestUtils.clearHistory();
+});
+
+function check_tree_order(tree, pages, aNumberOfRowsDelta = 0) {
+ let treeView = tree.view;
+ let columns = tree.columns;
+ is(columns.count, 1, "There should be only 1 column in the sidebar");
+
+ let found = 0;
+ for (let i = 0; i < treeView.rowCount; i++) {
+ let node = treeView.nodeForTreeIndex(i);
+ // We could inherit delayed visits from previous tests, skip them.
+ if (!pages.includes(node.uri))
+ continue;
+ is(node.uri, pages[i], "Node is in correct position based on its visit date");
+ found++;
+ }
+ ok(found, pages.length + aNumberOfRowsDelta, "Found all expected results");
+}
diff --git a/browser/components/places/tests/browser/browser_library_batch_delete.js b/browser/components/places/tests/browser/browser_library_batch_delete.js
new file mode 100644
index 000000000..6a907c70f
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_batch_delete.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that Library handles correctly batch deletes.
+ */
+
+const TEST_URL = "http://www.batch.delete.me/";
+
+var gTests = [];
+var gLibrary;
+
+// ------------------------------------------------------------------------------
+
+gTests.push({
+ desc: "Create and batch remove bookmarks",
+ run: function() {
+ let testURI = makeURI(TEST_URL);
+ PlacesUtils.history.runInBatchMode({
+ runBatched: function (aUserData) {
+ // Create a folder in unserted and populate it with bookmarks.
+ let folder = PlacesUtils.bookmarks.createFolder(
+ PlacesUtils.unfiledBookmarksFolderId, "deleteme",
+ PlacesUtils.bookmarks.DEFAULT_INDEX
+ );
+ PlacesUtils.bookmarks.createFolder(
+ PlacesUtils.unfiledBookmarksFolderId, "keepme",
+ PlacesUtils.bookmarks.DEFAULT_INDEX
+ );
+ for (let i = 0; i < 10; i++) {
+ PlacesUtils.bookmarks.insertBookmark(folder,
+ testURI,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "bm" + i);
+ }
+ }
+ }, null);
+
+ // Select and open the left pane "History" query.
+ let PO = gLibrary.PlacesOrganizer;
+ PO.selectLeftPaneQuery("UnfiledBookmarks");
+ isnot(PO._places.selectedNode, null, "Selected unsorted bookmarks");
+
+ let unsortedNode = PlacesUtils.asContainer(PO._places.selectedNode);
+ unsortedNode.containerOpen = true;
+ is(unsortedNode.childCount, 2, "Unsorted node has 2 children");
+ let folderNode = unsortedNode.getChild(0);
+ is(folderNode.title, "deleteme", "Folder found in unsorted bookmarks");
+ // Check delete command is available.
+ PO._places.selectNode(folderNode);
+ is(PO._places.selectedNode.title, "deleteme", "Folder node selected");
+ ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is enabled");
+ // Execute the delete command and check bookmark has been removed.
+ PO._places.controller.doCommand("cmd_delete");
+ ok(!PlacesUtils.bookmarks.isBookmarked(testURI),
+ "Bookmark has been correctly removed");
+ // Test live update.
+ is(unsortedNode.childCount, 1, "Unsorted node has 1 child");
+ is(PO._places.selectedNode.title, "keepme", "Folder node selected");
+ unsortedNode.containerOpen = false;
+ nextTest();
+ }
+});
+
+// ------------------------------------------------------------------------------
+
+gTests.push({
+ desc: "Ensure correct selection and functionality in Library",
+ run: function() {
+ let PO = gLibrary.PlacesOrganizer;
+ let ContentTree = gLibrary.ContentTree;
+ // Move selection forth and back.
+ PO.selectLeftPaneQuery("History");
+ PO.selectLeftPaneQuery("UnfiledBookmarks");
+ // Now select the "keepme" folder in the right pane and delete it.
+ ContentTree.view.selectNode(ContentTree.view.result.root.getChild(0));
+ is(ContentTree.view.selectedNode.title, "keepme",
+ "Found folder in content pane");
+ // Test live update.
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ makeURI(TEST_URL),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "bm");
+ is(ContentTree.view.result.root.childCount, 2,
+ "Right pane was correctly updated");
+ nextTest();
+ }
+});
+
+// ------------------------------------------------------------------------------
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function () {
+ PlacesUtils.bookmarks
+ .removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ });
+
+ gLibrary = openLibrary(nextTest);
+}
+
+function nextTest() {
+ if (gTests.length) {
+ var test = gTests.shift();
+ info("Start of test: " + test.desc);
+ test.run();
+ }
+ else {
+ // Close Library window.
+ gLibrary.close();
+ finish();
+ }
+}
diff --git a/browser/components/places/tests/browser/browser_library_commands.js b/browser/components/places/tests/browser/browser_library_commands.js
new file mode 100644
index 000000000..e3bb75a34
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_commands.js
@@ -0,0 +1,235 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test enabled commands in the left pane folder of the Library.
+ */
+
+const TEST_URI = NetUtil.newURI("http://www.mozilla.org/");
+
+registerCleanupFunction(function* () {
+ yield PlacesUtils.bookmarks.eraseEverything();
+ yield PlacesTestUtils.clearHistory();
+});
+
+add_task(function* test_date_container() {
+ let library = yield promiseLibrary();
+ info("Ensure date containers under History cannot be cut but can be deleted");
+
+ yield PlacesTestUtils.addVisits(TEST_URI);
+
+ // Select and open the left pane "History" query.
+ let PO = library.PlacesOrganizer;
+
+ PO.selectLeftPaneQuery('History');
+ isnot(PO._places.selectedNode, null, "We correctly selected History");
+
+ // Check that both delete and cut commands are disabled, cause this is
+ // a child of the left pane folder.
+ ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is disabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is disabled");
+ let historyNode = PlacesUtils.asContainer(PO._places.selectedNode);
+ historyNode.containerOpen = true;
+
+ // Check that we have a child container. It is "Today" container.
+ is(historyNode.childCount, 1, "History node has one child");
+ let todayNode = historyNode.getChild(0);
+ let todayNodeExpectedTitle = PlacesUtils.getString("finduri-AgeInDays-is-0");
+ is(todayNode.title, todayNodeExpectedTitle,
+ "History child is the expected container");
+
+ // Select "Today" container.
+ PO._places.selectNode(todayNode);
+ is(PO._places.selectedNode, todayNode,
+ "We correctly selected Today container");
+ // Check that delete command is enabled but cut command is disabled, cause
+ // this is an history item.
+ ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is disabled");
+ ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is enabled");
+
+ // Execute the delete command and check visit has been removed.
+ let promiseURIRemoved = promiseHistoryNotification("onDeleteURI",
+ v => TEST_URI.equals(v));
+ PO._places.controller.doCommand("cmd_delete");
+ yield promiseURIRemoved;
+
+ // Test live update of "History" query.
+ is(historyNode.childCount, 0, "History node has no more children");
+
+ historyNode.containerOpen = false;
+
+ ok(!(yield promiseIsURIVisited(TEST_URI)), "Visit has been removed");
+
+ library.close();
+});
+
+add_task(function* test_query_on_toolbar() {
+ let library = yield promiseLibrary();
+ info("Ensure queries can be cut or deleted");
+
+ // Select and open the left pane "Bookmarks Toolbar" folder.
+ let PO = library.PlacesOrganizer;
+
+ PO.selectLeftPaneQuery('BookmarksToolbar');
+ isnot(PO._places.selectedNode, null, "We have a valid selection");
+ is(PlacesUtils.getConcreteItemId(PO._places.selectedNode),
+ PlacesUtils.toolbarFolderId,
+ "We have correctly selected bookmarks toolbar node.");
+
+ // Check that both cut and delete commands are disabled, cause this is a child
+ // of AllBookmarksFolderId.
+ ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is disabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is disabled");
+
+ let toolbarNode = PlacesUtils.asContainer(PO._places.selectedNode);
+ toolbarNode.containerOpen = true;
+
+ // Add an History query to the toolbar.
+ let query = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "place:sort=4",
+ title: "special_query",
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0 });
+
+ // Get first child and check it is the just inserted query.
+ ok(toolbarNode.childCount > 0, "Toolbar node has children");
+ let queryNode = toolbarNode.getChild(0);
+ is(queryNode.title, "special_query", "Query node is correctly selected");
+
+ // Select query node.
+ PO._places.selectNode(queryNode);
+ is(PO._places.selectedNode, queryNode, "We correctly selected query node");
+
+ // Check that both cut and delete commands are enabled.
+ ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(PO._places.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is enabled");
+ ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is enabled");
+
+ // Execute the delete command and check bookmark has been removed.
+ let promiseItemRemoved = promiseBookmarksNotification("onItemRemoved",
+ (...args) => query.guid == args[5]);
+ PO._places.controller.doCommand("cmd_delete");
+ yield promiseItemRemoved;
+
+ is((yield PlacesUtils.bookmarks.fetch(query.guid)), null,
+ "Query node bookmark has been correctly removed");
+
+ toolbarNode.containerOpen = false;
+
+ library.close();
+});
+
+add_task(function* test_search_contents() {
+ yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "example page",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: 0 });
+
+ let library = yield promiseLibrary();
+ info("Ensure query contents can be cut or deleted");
+
+ // Select and open the left pane "Bookmarks Toolbar" folder.
+ let PO = library.PlacesOrganizer;
+
+ PO.selectLeftPaneQuery('BookmarksToolbar');
+ isnot(PO._places.selectedNode, null, "We have a valid selection");
+ is(PlacesUtils.getConcreteItemId(PO._places.selectedNode),
+ PlacesUtils.toolbarFolderId,
+ "We have correctly selected bookmarks toolbar node.");
+
+ let searchBox = library.document.getElementById("searchFilter");
+ searchBox.value = "example";
+ library.PlacesSearchBox.search(searchBox.value);
+
+ let bookmarkNode = library.ContentTree.view.selectedNode;
+ is(bookmarkNode.uri, "http://example.com/", "Found the expected bookmark");
+
+ // Check that both cut and delete commands are enabled.
+ ok(library.ContentTree.view.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(library.ContentTree.view.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is enabled");
+ ok(library.ContentTree.view.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is enabled");
+
+ library.close();
+});
+
+add_task(function* test_tags() {
+ yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "example page",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: 0 });
+ PlacesUtils.tagging.tagURI(NetUtil.newURI("http://example.com/"), ["test"]);
+
+ let library = yield promiseLibrary();
+ info("Ensure query contents can be cut or deleted");
+
+ // Select and open the left pane "Bookmarks Toolbar" folder.
+ let PO = library.PlacesOrganizer;
+
+ PO.selectLeftPaneQuery('Tags');
+ let tagsNode = PO._places.selectedNode;
+ isnot(tagsNode, null, "We have a valid selection");
+ let tagsTitle = PlacesUtils.getString("TagsFolderTitle");
+ is(tagsNode.title, tagsTitle,
+ "Tags has been properly selected");
+
+ // Check that both cut and delete commands are disabled.
+ ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is disabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is disabled");
+
+ // Now select the tag.
+ PlacesUtils.asContainer(tagsNode).containerOpen = true;
+ let tag = tagsNode.getChild(0);
+ PO._places.selectNode(tag);
+ is(PO._places.selectedNode.title, "test",
+ "The created tag has been properly selected");
+
+ // Check that cut is disabled but delete is enabled.
+ ok(PO._places.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is disabled");
+ ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is enabled");
+
+ let bookmarkNode = library.ContentTree.view.selectedNode;
+ is(bookmarkNode.uri, "http://example.com/", "Found the expected bookmark");
+
+ // Check that both cut and delete commands are enabled.
+ ok(library.ContentTree.view.controller.isCommandEnabled("cmd_copy"),
+ "Copy command is enabled");
+ ok(!library.ContentTree.view.controller.isCommandEnabled("cmd_cut"),
+ "Cut command is disabled");
+ ok(library.ContentTree.view.controller.isCommandEnabled("cmd_delete"),
+ "Delete command is enabled");
+
+ tagsNode.containerOpen = false;
+
+ library.close();
+});
diff --git a/browser/components/places/tests/browser/browser_library_downloads.js b/browser/components/places/tests/browser/browser_library_downloads.js
new file mode 100644
index 000000000..81daadd71
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_downloads.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/. */
+
+/*
+ * Tests bug 564900: Add folder specifically for downloads to Library left pane.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=564900
+ * This test visits various pages then opens the Library and ensures
+ * that both the Downloads folder shows up and that the correct visits
+ * are shown in it.
+ */
+
+var now = Date.now();
+
+function test() {
+ waitForExplicitFinish();
+
+ let onLibraryReady = function(win) {
+ // Add visits to compare contents with.
+ let places = [
+ { uri: NetUtil.newURI("http://mozilla.com"),
+ visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_TYPED) ]
+ },
+ { uri: NetUtil.newURI("http://google.com"),
+ visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_DOWNLOAD) ]
+ },
+ { uri: NetUtil.newURI("http://en.wikipedia.org"),
+ visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_TYPED) ]
+ },
+ { uri: NetUtil.newURI("http://ubuntu.org"),
+ visits: [ new VisitInfo(PlacesUtils.history.TRANSITION_DOWNLOAD) ]
+ },
+ ]
+ PlacesUtils.asyncHistory.updatePlaces(places, {
+ handleResult: function () {},
+ handleError: function () {
+ ok(false, "gHistory.updatePlaces() failed");
+ },
+ handleCompletion: function () {
+ // Make sure Downloads is present.
+ isnot(win.PlacesOrganizer._places.selectedNode, null,
+ "Downloads is present and selected");
+
+
+ // Check results.
+ let contentRoot = win.ContentArea.currentView.result.root;
+ let len = contentRoot.childCount;
+ const TEST_URIS = ["http://ubuntu.org/", "http://google.com/"];
+ for (let i = 0; i < len; i++) {
+ is(contentRoot.getChild(i).uri, TEST_URIS[i],
+ "Comparing downloads shown at index " + i);
+ }
+
+ win.close();
+ PlacesTestUtils.clearHistory().then(finish);
+ }
+ })
+ }
+
+ openLibrary(onLibraryReady, "Downloads");
+}
+
+function VisitInfo(aTransitionType)
+{
+ this.transitionType =
+ aTransitionType === undefined ?
+ PlacesUtils.history.TRANSITION_LINK : aTransitionType;
+ this.visitDate = now++ * 1000;
+}
+VisitInfo.prototype = {}
diff --git a/browser/components/places/tests/browser/browser_library_infoBox.js b/browser/components/places/tests/browser/browser_library_infoBox.js
new file mode 100644
index 000000000..17cd78f8c
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_infoBox.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/. */
+
+/**
+ * Test appropriate visibility of infoBoxExpanderWrapper and
+ * additionalInfoFields in infoBox section of library
+ */
+
+const TEST_URI = "http://www.mozilla.org/";
+
+var gTests = [];
+var gLibrary;
+
+// ------------------------------------------------------------------------------
+
+gTests.push({
+ desc: "Bug 430148 - Remove or hide the more/less button in details pane...",
+ run: function() {
+ var PO = gLibrary.PlacesOrganizer;
+ let ContentTree = gLibrary.ContentTree;
+ var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
+
+ function addVisitsCallback() {
+ // open all bookmarks node
+ PO.selectLeftPaneQuery("AllBookmarks");
+ isnot(PO._places.selectedNode, null,
+ "Correctly selected all bookmarks node.");
+ checkInfoBoxSelected(PO);
+ ok(infoBoxExpanderWrapper.hidden,
+ "Expander button is hidden for all bookmarks node.");
+ checkAddInfoFieldsCollapsed(PO);
+
+ // open history node
+ PO.selectLeftPaneQuery("History");
+ isnot(PO._places.selectedNode, null, "Correctly selected history node.");
+ checkInfoBoxSelected(PO);
+ ok(infoBoxExpanderWrapper.hidden,
+ "Expander button is hidden for history node.");
+ checkAddInfoFieldsCollapsed(PO);
+
+ // open history child node
+ var historyNode = PO._places.selectedNode.
+ QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ historyNode.containerOpen = true;
+ var childNode = historyNode.getChild(0);
+ isnot(childNode, null, "History node first child is not null.");
+ PO._places.selectNode(childNode);
+ checkInfoBoxSelected(PO);
+ ok(infoBoxExpanderWrapper.hidden,
+ "Expander button is hidden for history child node.");
+ checkAddInfoFieldsCollapsed(PO);
+
+ // open history item
+ var view = ContentTree.view.view;
+ ok(view.rowCount > 0, "History item exists.");
+ view.selection.select(0);
+ ok(infoBoxExpanderWrapper.hidden,
+ "Expander button is hidden for history item.");
+ checkAddInfoFieldsCollapsed(PO);
+
+ historyNode.containerOpen = false;
+
+ // open bookmarks menu node
+ PO.selectLeftPaneQuery("BookmarksMenu");
+ isnot(PO._places.selectedNode, null,
+ "Correctly selected bookmarks menu node.");
+ checkInfoBoxSelected(PO);
+ ok(infoBoxExpanderWrapper.hidden,
+ "Expander button is hidden for bookmarks menu node.");
+ checkAddInfoFieldsCollapsed(PO);
+
+ // open recently bookmarked node
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
+ NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
+ "&folder=UNFILED_BOOKMARKS" +
+ "&folder=TOOLBAR" +
+ "&queryType=" + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
+ "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
+ "&maxResults=10" +
+ "&excludeQueries=1"),
+ 0, "Recent Bookmarks");
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
+ NetUtil.newURI("http://mozilla.org/"),
+ 1, "Mozilla");
+ var menuNode = PO._places.selectedNode.
+ QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ menuNode.containerOpen = true;
+ childNode = menuNode.getChild(0);
+ isnot(childNode, null, "Bookmarks menu child node exists.");
+ is(childNode.title, "Recent Bookmarks",
+ "Correctly selected recently bookmarked node.");
+ PO._places.selectNode(childNode);
+ checkInfoBoxSelected(PO);
+ ok(!infoBoxExpanderWrapper.hidden,
+ "Expander button is not hidden for recently bookmarked node.");
+ checkAddInfoFieldsNotCollapsed(PO);
+
+ // open first bookmark
+ view = ContentTree.view.view;
+ ok(view.rowCount > 0, "Bookmark item exists.");
+ view.selection.select(0);
+ checkInfoBoxSelected(PO);
+ ok(!infoBoxExpanderWrapper.hidden,
+ "Expander button is not hidden for bookmark item.");
+ checkAddInfoFieldsNotCollapsed(PO);
+ checkAddInfoFields(PO, "bookmark item");
+
+ menuNode.containerOpen = false;
+
+ PlacesTestUtils.clearHistory().then(nextTest);
+ }
+ // add a visit to browser history
+ PlacesTestUtils.addVisits(
+ { uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED }
+ ).then(addVisitsCallback);
+ }
+});
+
+function checkInfoBoxSelected(PO) {
+ is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
+ "Selected element in detailsDeck is infoBox.");
+}
+
+function checkAddInfoFieldsCollapsed(PO) {
+ PO._additionalInfoFields.forEach(function (id) {
+ ok(getAndCheckElmtById(id).collapsed,
+ "Additional info field correctly collapsed: #" + id);
+ });
+}
+
+function checkAddInfoFieldsNotCollapsed(PO) {
+ ok(PO._additionalInfoFields.some(function (id) {
+ return !getAndCheckElmtById(id).collapsed;
+ }), "Some additional info field correctly not collapsed");
+}
+
+function checkAddInfoFields(PO, nodeName) {
+ ok(true, "Checking additional info fields visibiity for node: " + nodeName);
+ var expanderButton = getAndCheckElmtById("infoBoxExpander");
+
+ // make sure additional fields are hidden by default
+ PO._additionalInfoFields.forEach(function (id) {
+ ok(getAndCheckElmtById(id).hidden,
+ "Additional info field correctly hidden by default: #" + id);
+ });
+
+ // toggle fields and make sure they are hidden/unhidden as expected
+ expanderButton.click();
+ PO._additionalInfoFields.forEach(function (id) {
+ ok(!getAndCheckElmtById(id).hidden,
+ "Additional info field correctly unhidden after toggle: #" + id);
+ });
+ expanderButton.click();
+ PO._additionalInfoFields.forEach(function (id) {
+ ok(getAndCheckElmtById(id).hidden,
+ "Additional info field correctly hidden after toggle: #" + id);
+ });
+}
+
+function getAndCheckElmtById(id) {
+ var elmt = gLibrary.document.getElementById(id);
+ isnot(elmt, null, "Correctly got element: #" + id);
+ return elmt;
+}
+
+// ------------------------------------------------------------------------------
+
+function nextTest() {
+ if (gTests.length) {
+ var test = gTests.shift();
+ ok(true, "TEST: " + test.desc);
+ dump("TEST: " + test.desc + "\n");
+ test.run();
+ }
+ else {
+ // Close Library window.
+ gLibrary.close();
+ // No need to cleanup anything, we have a correct left pane now.
+ finish();
+ }
+}
+
+function test() {
+ waitForExplicitFinish();
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils is running in chrome context");
+ ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
+
+ // Open Library.
+ openLibrary(function (library) {
+ gLibrary = library;
+ gLibrary.PlacesOrganizer._places.focus();
+ nextTest(gLibrary);
+ });
+}
diff --git a/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js b/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
new file mode 100644
index 000000000..7cea38f20
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_left_pane_fixnames.js
@@ -0,0 +1,94 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test we correctly fix broken Library left pane queries names.
+ */
+
+// Array of left pane queries objects, each one has the following properties:
+// name: query's identifier got from annotations,
+// itemId: query's itemId,
+// correctTitle: original and correct query's title.
+var leftPaneQueries = [];
+
+function onLibraryReady(organizer) {
+ // Check titles have been fixed.
+ for (var i = 0; i < leftPaneQueries.length; i++) {
+ var query = leftPaneQueries[i];
+ is(PlacesUtils.bookmarks.getItemTitle(query.itemId),
+ query.correctTitle, "Title is correct for query " + query.name);
+ if ("concreteId" in query) {
+ is(PlacesUtils.bookmarks.getItemTitle(query.concreteId),
+ query.concreteTitle, "Concrete title is correct for query " + query.name);
+ }
+ }
+
+ // Close Library window.
+ organizer.close();
+ // No need to cleanup anything, we have a correct left pane now.
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils is running in chrome context");
+ ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
+ ok(PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION > 0,
+ "Left pane version in chrome context, current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION );
+
+ // Ensure left pane is initialized.
+ ok(PlacesUIUtils.leftPaneFolderId > 0, "left pane folder is initialized");
+
+ // Get the left pane folder.
+ var leftPaneItems = PlacesUtils.annotations
+ .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+
+ is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
+ // Check version.
+ var version = PlacesUtils.annotations
+ .getItemAnnotation(leftPaneItems[0],
+ PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual");
+
+ // Get all left pane queries.
+ var items = PlacesUtils.annotations
+ .getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_QUERY_ANNO);
+ // Get current queries names.
+ for (var i = 0; i < items.length; i++) {
+ var itemId = items[i];
+ var queryName = PlacesUtils.annotations
+ .getItemAnnotation(items[i],
+ PlacesUIUtils.ORGANIZER_QUERY_ANNO);
+ var query = { name: queryName,
+ itemId: itemId,
+ correctTitle: PlacesUtils.bookmarks.getItemTitle(itemId) }
+ switch (queryName) {
+ case "BookmarksToolbar":
+ query.concreteId = PlacesUtils.toolbarFolderId;
+ query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
+ break;
+ case "BookmarksMenu":
+ query.concreteId = PlacesUtils.bookmarksMenuFolderId;
+ query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
+ break;
+ case "UnfiledBookmarks":
+ query.concreteId = PlacesUtils.unfiledBookmarksFolderId;
+ query.concreteTitle = PlacesUtils.bookmarks.getItemTitle(query.concreteId);
+ break;
+ }
+ leftPaneQueries.push(query);
+ // Rename to a bad title.
+ PlacesUtils.bookmarks.setItemTitle(query.itemId, "badName");
+ if ("concreteId" in query)
+ PlacesUtils.bookmarks.setItemTitle(query.concreteId, "badName");
+ }
+
+ PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter);
+
+ // Open Library, this will kick-off left pane code.
+ openLibrary(onLibraryReady);
+}
diff --git a/browser/components/places/tests/browser/browser_library_left_pane_select_hierarchy.js b/browser/components/places/tests/browser/browser_library_left_pane_select_hierarchy.js
new file mode 100644
index 000000000..b90df120c
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_left_pane_select_hierarchy.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function test() {
+ waitForExplicitFinish();
+ openLibrary(onLibraryReady);
+}
+
+function onLibraryReady(aLibrary) {
+ let hierarchy = [ "AllBookmarks", "BookmarksMenu" ];
+
+ let folder1 = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.bookmarksMenuFolderId,
+ "Folder 1",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ hierarchy.push(folder1);
+ let folder2 = PlacesUtils.bookmarks
+ .createFolder(folder1, "Folder 2",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ hierarchy.push(folder2);
+ let bookmark = PlacesUtils.bookmarks
+ .insertBookmark(folder2, NetUtil.newURI("http://example.com/"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "Bookmark");
+
+ registerCleanupFunction(function() {
+ PlacesUtils.bookmarks.removeItem(folder1);
+ aLibrary.close();
+ });
+
+ aLibrary.PlacesOrganizer.selectLeftPaneContainerByHierarchy(hierarchy);
+
+ is(aLibrary.PlacesOrganizer._places.selectedNode.itemId, folder2,
+ "Found the expected left pane selected node");
+
+ is(aLibrary.ContentTree.view.view.nodeForTreeIndex(0).itemId, bookmark,
+ "Found the expected right pane contents");
+
+ finish();
+}
diff --git a/browser/components/places/tests/browser/browser_library_middleclick.js b/browser/components/places/tests/browser/browser_library_middleclick.js
new file mode 100644
index 000000000..894f89446
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_middleclick.js
@@ -0,0 +1,279 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /**
+ * Tests middle-clicking items in the Library.
+ */
+
+const ENABLE_HISTORY_PREF = "places.history.enabled";
+
+var gLibrary = null;
+var gTests = [];
+var gCurrentTest = null;
+
+// Listener for TabOpen and tabs progress.
+var gTabsListener = {
+ _loadedURIs: [],
+ _openTabsCount: 0,
+
+ handleEvent: function(aEvent) {
+ if (aEvent.type != "TabOpen")
+ return;
+
+ if (++this._openTabsCount == gCurrentTest.URIs.length) {
+ is(gBrowser.tabs.length, gCurrentTest.URIs.length + 1,
+ "We have opened " + gCurrentTest.URIs.length + " new tab(s)");
+ }
+
+ var tab = aEvent.target;
+ is(tab.ownerGlobal, window,
+ "Tab has been opened in current browser window");
+ },
+
+ onLocationChange: function(aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ var spec = aLocationURI.spec;
+ ok(true, spec);
+ // When a new tab is opened, location is first set to "about:blank", so
+ // we can ignore those calls.
+ // Ignore multiple notifications for the same URI too.
+ if (spec == "about:blank" || this._loadedURIs.includes(spec))
+ return;
+
+ ok(gCurrentTest.URIs.includes(spec),
+ "Opened URI found in list: " + spec);
+
+ if (gCurrentTest.URIs.includes(spec))
+ this._loadedURIs.push(spec);
+
+ if (this._loadedURIs.length == gCurrentTest.URIs.length) {
+ // We have correctly opened all URIs.
+
+ // Reset arrays.
+ this._loadedURIs.length = 0;
+
+ this._openTabsCount = 0;
+
+ executeSoon(function () {
+ // Close all tabs.
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeCurrentTab();
+
+ // Test finished. This will move to the next one.
+ waitForFocus(gCurrentTest.finish, gBrowser.ownerGlobal);
+ });
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------
+// Open bookmark in a new tab.
+
+gTests.push({
+ desc: "Open bookmark in a new tab.",
+ URIs: ["about:buildconfig"],
+ _itemId: -1,
+
+ setup: function() {
+ var bs = PlacesUtils.bookmarks;
+ // Add a new unsorted bookmark.
+ this._itemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ PlacesUtils._uri(this.URIs[0]),
+ bs.DEFAULT_INDEX,
+ "Title");
+ // Select unsorted bookmarks root in the left pane.
+ gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+ isnot(gLibrary.PlacesOrganizer._places.selectedNode, null,
+ "We correctly have selection in the Library left pane");
+ // Get our bookmark in the right pane.
+ var bookmarkNode = gLibrary.ContentTree.view.view.nodeForTreeIndex(0);
+ is(bookmarkNode.uri, this.URIs[0], "Found bookmark in the right pane");
+ },
+
+ finish: function() {
+ setTimeout(runNextTest, 0);
+ },
+
+ cleanup: function() {
+ PlacesUtils.bookmarks.removeItem(this._itemId);
+ }
+});
+
+// ------------------------------------------------------------------------------
+// Open a folder in tabs.
+
+gTests.push({
+ desc: "Open a folder in tabs.",
+ URIs: ["about:buildconfig", "about:"],
+ _folderId: -1,
+
+ setup: function() {
+ var bs = PlacesUtils.bookmarks;
+ // Create a new folder.
+ var folderId = bs.createFolder(bs.unfiledBookmarksFolder,
+ "Folder",
+ bs.DEFAULT_INDEX);
+ this._folderId = folderId;
+
+ // Add bookmarks in folder.
+ this.URIs.forEach(function(aURI) {
+ bs.insertBookmark(folderId,
+ PlacesUtils._uri(aURI),
+ bs.DEFAULT_INDEX,
+ "Title");
+ });
+
+ // Select unsorted bookmarks root in the left pane.
+ gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+ isnot(gLibrary.PlacesOrganizer._places.selectedNode, null,
+ "We correctly have selection in the Library left pane");
+ // Get our bookmark in the right pane.
+ var folderNode = gLibrary.ContentTree.view.view.nodeForTreeIndex(0);
+ is(folderNode.title, "Folder", "Found folder in the right pane");
+ },
+
+ finish: function() {
+ setTimeout(runNextTest, 0);
+ },
+
+ cleanup: function() {
+ PlacesUtils.bookmarks.removeItem(this._folderId);
+ }
+});
+
+// ------------------------------------------------------------------------------
+// Open a query in tabs.
+
+gTests.push({
+ desc: "Open a query in tabs.",
+ URIs: ["about:buildconfig", "about:"],
+ _folderId: -1,
+ _queryId: -1,
+
+ setup: function() {
+ var bs = PlacesUtils.bookmarks;
+ // Create a new folder.
+ var folderId = bs.createFolder(bs.unfiledBookmarksFolder,
+ "Folder",
+ bs.DEFAULT_INDEX);
+ this._folderId = folderId;
+
+ // Add bookmarks in folder.
+ this.URIs.forEach(function(aURI) {
+ bs.insertBookmark(folderId,
+ PlacesUtils._uri(aURI),
+ bs.DEFAULT_INDEX,
+ "Title");
+ });
+
+ // Create a bookmarks query containing our bookmarks.
+ var hs = PlacesUtils.history;
+ var options = hs.getNewQueryOptions();
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+ var query = hs.getNewQuery();
+ // The colon included in the terms selects only about: URIs. If not included
+ // we also may get pages like about.html included in the query result.
+ query.searchTerms = "about:";
+ var queryString = hs.queriesToQueryString([query], 1, options);
+ this._queryId = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ PlacesUtils._uri(queryString),
+ 0, // It must be the first.
+ "Query");
+
+ // Select unsorted bookmarks root in the left pane.
+ gLibrary.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+ isnot(gLibrary.PlacesOrganizer._places.selectedNode, null,
+ "We correctly have selection in the Library left pane");
+ // Get our bookmark in the right pane.
+ var folderNode = gLibrary.ContentTree.view.view.nodeForTreeIndex(0);
+ is(folderNode.title, "Query", "Found query in the right pane");
+ },
+
+ finish: function() {
+ setTimeout(runNextTest, 0);
+ },
+
+ cleanup: function() {
+ PlacesUtils.bookmarks.removeItem(this._folderId);
+ PlacesUtils.bookmarks.removeItem(this._queryId);
+ }
+});
+
+// ------------------------------------------------------------------------------
+
+function test() {
+ waitForExplicitFinish();
+ // Increase timeout, this test can be quite slow due to waitForFocus calls.
+ requestLongerTimeout(2);
+
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils in context");
+ ok(PlacesUIUtils, "PlacesUIUtils in context");
+
+ // Add tabs listeners.
+ gBrowser.tabContainer.addEventListener("TabOpen", gTabsListener, false);
+ gBrowser.addTabsProgressListener(gTabsListener);
+
+ // Temporary disable history, so we won't record pages navigation.
+ gPrefService.setBoolPref(ENABLE_HISTORY_PREF, false);
+
+ // Open Library window.
+ openLibrary(function (library) {
+ gLibrary = library;
+ // Kick off tests.
+ runNextTest();
+ });
+}
+
+function runNextTest() {
+ // Cleanup from previous test.
+ if (gCurrentTest)
+ gCurrentTest.cleanup();
+
+ if (gTests.length > 0) {
+ // Goto next test.
+ gCurrentTest = gTests.shift();
+ info("Start of test: " + gCurrentTest.desc);
+ // Test setup will set Library so that the bookmark to be opened is the
+ // first node in the content (right pane) tree.
+ gCurrentTest.setup();
+
+ // Middle click on first node in the content tree of the Library.
+ gLibrary.focus();
+ waitForFocus(function() {
+ mouseEventOnCell(gLibrary.ContentTree.view, 0, 0, { button: 1 });
+ }, gLibrary);
+ }
+ else {
+ // No more tests.
+
+ // Close Library window.
+ gLibrary.close();
+
+ // Remove tabs listeners.
+ gBrowser.tabContainer.removeEventListener("TabOpen", gTabsListener, false);
+ gBrowser.removeTabsProgressListener(gTabsListener);
+
+ // Restore history.
+ try {
+ gPrefService.clearUserPref(ENABLE_HISTORY_PREF);
+ } catch (ex) {}
+
+ finish();
+ }
+}
+
+function mouseEventOnCell(aTree, aRowIndex, aColumnIndex, aEventDetails) {
+ var selection = aTree.view.selection;
+ selection.select(aRowIndex);
+ aTree.treeBoxObject.ensureRowIsVisible(aRowIndex);
+ var column = aTree.columns[aColumnIndex];
+
+ // get cell coordinates
+ var rect = aTree.treeBoxObject.getCoordsForCellItem(aRowIndex, column, "text");
+
+ EventUtils.synthesizeMouse(aTree.body, rect.x, rect.y,
+ aEventDetails, gLibrary);
+}
diff --git a/browser/components/places/tests/browser/browser_library_openFlatContainer.js b/browser/components/places/tests/browser/browser_library_openFlatContainer.js
new file mode 100644
index 000000000..167b33031
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_openFlatContainer.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test opening a flat container in the right pane even if its parent in the
+ * left pane is closed.
+ */
+
+add_task(function* () {
+ let folder = PlacesUtils.bookmarks
+ .createFolder(PlacesUtils.unfiledBookmarksFolderId,
+ "Folder",
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ let bookmark = PlacesUtils.bookmarks
+ .insertBookmark(folder, NetUtil.newURI("http://example.com/"),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "Bookmark");
+
+ let library = yield promiseLibrary("AllBookmarks");
+ registerCleanupFunction(function () {
+ library.close();
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ });
+
+ // Select unfiled later, to ensure it's closed.
+ library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
+ ok(!library.PlacesOrganizer._places.selectedNode.containerOpen,
+ "Unfiled container is closed");
+
+ let folderNode = library.ContentTree.view.view.nodeForTreeIndex(0);
+ is(folderNode.itemId, folder,
+ "Found the expected folder in the right pane");
+ // Select the folder node in the right pane.
+ library.ContentTree.view.selectNode(folderNode);
+
+ synthesizeClickOnSelectedTreeCell(library.ContentTree.view,
+ { clickCount: 2 });
+
+ is(library.ContentTree.view.view.nodeForTreeIndex(0).itemId, bookmark,
+ "Found the expected bookmark in the right pane");
+});
diff --git a/browser/components/places/tests/browser/browser_library_open_leak.js b/browser/components/places/tests/browser/browser_library_open_leak.js
new file mode 100644
index 000000000..f002236a9
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_open_leak.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Bug 474831
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=474831
+ *
+ * Tests for leaks caused by simply opening and closing the Places Library
+ * window. Opens the Places Library window, waits for it to load, closes it,
+ * and finishes.
+ */
+
+function test() {
+ waitForExplicitFinish();
+ openLibrary(function (win) {
+ ok(true, "Library has been correctly opened");
+ win.close();
+ finish();
+ });
+}
diff --git a/browser/components/places/tests/browser/browser_library_panel_leak.js b/browser/components/places/tests/browser/browser_library_panel_leak.js
new file mode 100644
index 000000000..643a261fb
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_panel_leak.js
@@ -0,0 +1,54 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Bug 433231 - Places Library leaks the nsGlobalWindow when closed with a
+ * history entry selected.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=433231
+ *
+ * STRs: Open Library, select an history entry in History, close Library.
+ * ISSUE: We were adding a bookmarks observer when editing a bookmark, when
+ * selecting an history entry the panel was not un-initialized, and
+ * since an histroy entry does not have an itemId, the observer was
+ * never removed.
+ */
+
+const TEST_URI = "http://www.mozilla.org/";
+
+function test() {
+ function onLibraryReady(organizer) {
+ let contentTree = organizer.document.getElementById("placeContent");
+ isnot(contentTree, null, "Sanity check: placeContent tree should exist");
+ isnot(organizer.PlacesOrganizer, null, "Sanity check: PlacesOrganizer should exist");
+ isnot(organizer.gEditItemOverlay, null, "Sanity check: gEditItemOverlay should exist");
+
+ ok(organizer.gEditItemOverlay.initialized, "gEditItemOverlay is initialized");
+ isnot(organizer.gEditItemOverlay.itemId, -1, "Editing a bookmark");
+
+ // Select History in the left pane.
+ organizer.PlacesOrganizer.selectLeftPaneQuery('History');
+ // Select the first history entry.
+ let selection = contentTree.view.selection;
+ selection.clearSelection();
+ selection.rangedSelect(0, 0, true);
+ // Check the panel is editing the history entry.
+ is(organizer.gEditItemOverlay.itemId, -1, "Editing an history entry");
+ // Close Library window.
+ organizer.close();
+ // Clean up history.
+ PlacesTestUtils.clearHistory().then(finish);
+ }
+
+ waitForExplicitFinish();
+ // Add an history entry.
+ ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
+ PlacesTestUtils.addVisits(
+ {uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED}
+ ).then(() => {
+ openLibrary(onLibraryReady);
+ });
+}
diff --git a/browser/components/places/tests/browser/browser_library_search.js b/browser/components/places/tests/browser/browser_library_search.js
new file mode 100644
index 000000000..93af22363
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_search.js
@@ -0,0 +1,182 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Bug 451151
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=451151
+ *
+ * Summary:
+ * Tests frontend Places Library searching -- search, search reset, search scope
+ * consistency.
+ *
+ * Details:
+ * Each test below
+ * 1. selects a folder in the left pane and ensures that the content tree is
+ * appropriately updated,
+ * 2. performs a search and ensures that the content tree is correct for the
+ * folder and search and that the search UI is visible and appropriate to
+ * folder,
+ * 5. resets the search and ensures that the content tree is correct and that
+ * the search UI is hidden, and
+ * 6. if folder scope was clicked, searches again and ensures folder scope
+ * remains selected.
+ */
+
+const TEST_URL = "http://dummy.mozilla.org/";
+const TEST_DOWNLOAD_URL = "http://dummy.mozilla.org/dummy.pdf";
+
+var gLibrary;
+
+var testCases = [
+ function allBookmarksScope() {
+ let defScope = getDefaultScope(PlacesUIUtils.allBookmarksFolderId);
+ search(PlacesUIUtils.allBookmarksFolderId, "dummy", defScope);
+ },
+
+ function historyScope() {
+ let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries["History"]);
+ search(PlacesUIUtils.leftPaneQueries["History"], "dummy", defScope);
+ },
+
+ function downloadsScope() {
+ let defScope = getDefaultScope(PlacesUIUtils.leftPaneQueries["Downloads"]);
+ search(PlacesUIUtils.leftPaneQueries["Downloads"], "dummy", defScope);
+ },
+];
+
+/**
+ * Returns the default search scope for a given folder.
+ *
+ * @param aFolderId
+ * the item ID of a node in the left pane's tree
+ * @return the default scope when the folder is newly selected
+ */
+function getDefaultScope(aFolderId) {
+ switch (aFolderId) {
+ case PlacesUIUtils.leftPaneQueries["History"]:
+ return "scopeBarHistory"
+ case PlacesUIUtils.leftPaneQueries["Downloads"]:
+ return "scopeBarDownloads";
+ default:
+ return "scopeBarAll";
+ }
+}
+
+/**
+ * Returns the single nsINavHistoryQuery represented by a given place URI.
+ *
+ * @param aPlaceURI
+ * a URI that represents a single query
+ * @return an nsINavHistoryQuery object
+ */
+function queryStringToQuery(aPlaceURI) {
+ let queries = {};
+ PlacesUtils.history.queryStringToQueries(aPlaceURI, queries, {}, {});
+ return queries.value[0];
+}
+
+/**
+ * Resets the search by clearing the search box's text and ensures that the
+ * search scope remains as expected.
+ *
+ * @param aExpectedScopeButtonId
+ * this button should be selected after the reset
+ */
+function resetSearch(aExpectedScopeButtonId) {
+ search(null, "", aExpectedScopeButtonId);
+}
+
+/**
+ * Performs a search for a given folder and search string and ensures that the
+ * URI of the right pane's content tree is as expected for the folder and search
+ * string. Also ensures that the search scope button is as expected after the
+ * search.
+ *
+ * @param aFolderId
+ * the item ID of a node in the left pane's tree
+ * @param aSearchStr
+ * the search text; may be empty to reset the search
+ * @param aExpectedScopeButtonId
+ * after searching the selected scope button should be this
+ */
+function search(aFolderId, aSearchStr, aExpectedScopeButtonId) {
+ let doc = gLibrary.document;
+ let folderTree = doc.getElementById("placesList");
+ let contentTree = doc.getElementById("placeContent");
+
+ // First, ensure that selecting the folder in the left pane updates the
+ // content tree properly.
+ if (aFolderId) {
+ folderTree.selectItems([aFolderId]);
+ isnot(folderTree.selectedNode, null,
+ "Sanity check: left pane tree should have selection after selecting!");
+
+ // getFolders() on a History query returns an empty array, so no use
+ // comparing against aFolderId in that case.
+ if (aFolderId !== PlacesUIUtils.leftPaneQueries["History"] &&
+ aFolderId !== PlacesUIUtils.leftPaneQueries["Downloads"]) {
+ // contentTree.place should be equal to contentTree.result.root.uri,
+ // but it's not until bug 476952 is fixed.
+ let query = queryStringToQuery(contentTree.result.root.uri);
+ is(query.getFolders()[0], aFolderId,
+ "Content tree's folder should be what was selected in the left pane");
+ }
+ }
+
+ // Second, ensure that searching updates the content tree and search UI
+ // properly.
+ let searchBox = doc.getElementById("searchFilter");
+ searchBox.value = aSearchStr;
+ gLibrary.PlacesSearchBox.search(searchBox.value);
+ let query = queryStringToQuery(contentTree.result.root.uri);
+ if (aSearchStr) {
+ is(query.searchTerms, aSearchStr,
+ "Content tree's searchTerms should be text in search box");
+ }
+ else {
+ is(query.hasSearchTerms, false,
+ "Content tree's searchTerms should not exist after search reset");
+ }
+}
+
+/**
+ * test() contains window-launching boilerplate that calls this to really kick
+ * things off. Add functions to the testCases array, and this will call them.
+ */
+function onLibraryAvailable() {
+ testCases.forEach(aTest => aTest());
+
+ gLibrary.close();
+ gLibrary = null;
+
+ // Cleanup.
+ PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]);
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ PlacesTestUtils.clearHistory().then(finish);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ // Sanity:
+ ok(PlacesUtils, "PlacesUtils in context");
+
+ // Add visits, a bookmark and a tag.
+ PlacesTestUtils.addVisits(
+ [{ uri: PlacesUtils._uri(TEST_URL), visitDate: Date.now() * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED },
+ { uri: PlacesUtils._uri(TEST_DOWNLOAD_URL), visitDate: Date.now() * 1000,
+ transition: PlacesUtils.history.TRANSITION_DOWNLOAD }]
+ ).then(() => {
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils._uri(TEST_URL),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "dummy");
+ PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]);
+
+ gLibrary = openLibrary(onLibraryAvailable);
+ });
+}
diff --git a/browser/components/places/tests/browser/browser_library_views_liveupdate.js b/browser/components/places/tests/browser/browser_library_views_liveupdate.js
new file mode 100644
index 000000000..c78ed641a
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_library_views_liveupdate.js
@@ -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/. */
+
+/**
+ * Tests Library Left pane view for liveupdate.
+ */
+
+var gLibrary = null;
+
+function test() {
+ waitForExplicitFinish();
+ // This test takes quite some time, and timeouts frequently, so we require
+ // more time to run.
+ // See Bug 525610.
+ requestLongerTimeout(2);
+
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils in context");
+ ok(PlacesUIUtils, "PlacesUIUtils in context");
+
+ // Open Library, we will check the left pane.
+ openLibrary(function (library) {
+ gLibrary = library;
+ startTest();
+ });
+}
+
+/**
+ * Adds bookmarks observer, and executes a bunch of bookmarks operations.
+ */
+function startTest() {
+ var bs = PlacesUtils.bookmarks;
+ // Add observers.
+ bs.addObserver(bookmarksObserver, false);
+ PlacesUtils.annotations.addObserver(bookmarksObserver, false);
+ var addedBookmarks = [];
+
+ // MENU
+ ok(true, "*** Acting on menu bookmarks");
+ var id = bs.insertBookmark(bs.bookmarksMenuFolder,
+ PlacesUtils._uri("http://bm1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "bm1");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(bs.bookmarksMenuFolder,
+ PlacesUtils._uri("place:"),
+ bs.DEFAULT_INDEX,
+ "bm2");
+ bs.setItemTitle(id, "bm2_edited");
+ addedBookmarks.push(id);
+ id = bs.insertSeparator(bs.bookmarksMenuFolder, bs.DEFAULT_INDEX);
+ addedBookmarks.push(id);
+ id = bs.createFolder(bs.bookmarksMenuFolder,
+ "bmf",
+ bs.DEFAULT_INDEX);
+ bs.setItemTitle(id, "bmf_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(id,
+ PlacesUtils._uri("http://bmf1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "bmf1");
+ addedBookmarks.push(id);
+ bs.moveItem(id, bs.bookmarksMenuFolder, 0);
+
+ // TOOLBAR
+ ok(true, "*** Acting on toolbar bookmarks");
+ bs.insertBookmark(bs.toolbarFolder,
+ PlacesUtils._uri("http://tb1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "tb1");
+ bs.setItemTitle(id, "tb1_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(bs.toolbarFolder,
+ PlacesUtils._uri("place:"),
+ bs.DEFAULT_INDEX,
+ "tb2");
+ bs.setItemTitle(id, "tb2_edited");
+ addedBookmarks.push(id);
+ id = bs.insertSeparator(bs.toolbarFolder, bs.DEFAULT_INDEX);
+ addedBookmarks.push(id);
+ id = bs.createFolder(bs.toolbarFolder,
+ "tbf",
+ bs.DEFAULT_INDEX);
+ bs.setItemTitle(id, "tbf_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(id,
+ PlacesUtils._uri("http://tbf1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "bmf1");
+ addedBookmarks.push(id);
+ bs.moveItem(id, bs.toolbarFolder, 0);
+
+ // UNSORTED
+ ok(true, "*** Acting on unsorted bookmarks");
+ id = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ PlacesUtils._uri("http://ub1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "ub1");
+ bs.setItemTitle(id, "ub1_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ PlacesUtils._uri("place:"),
+ bs.DEFAULT_INDEX,
+ "ub2");
+ bs.setItemTitle(id, "ub2_edited");
+ addedBookmarks.push(id);
+ id = bs.insertSeparator(bs.unfiledBookmarksFolder, bs.DEFAULT_INDEX);
+ addedBookmarks.push(id);
+ id = bs.createFolder(bs.unfiledBookmarksFolder,
+ "ubf",
+ bs.DEFAULT_INDEX);
+ bs.setItemTitle(id, "ubf_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(id,
+ PlacesUtils._uri("http://ubf1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "ubf1");
+ addedBookmarks.push(id);
+ bs.moveItem(id, bs.unfiledBookmarksFolder, 0);
+
+ // Remove all added bookmarks.
+ addedBookmarks.forEach(function (aItem) {
+ // If we remove an item after its containing folder has been removed,
+ // this will throw, but we can ignore that.
+ try {
+ bs.removeItem(aItem);
+ } catch (ex) {}
+ });
+
+ // Remove observers.
+ bs.removeObserver(bookmarksObserver);
+ PlacesUtils.annotations.removeObserver(bookmarksObserver);
+ finishTest();
+}
+
+/**
+ * Restores browser state and calls finish.
+ */
+function finishTest() {
+ // Close Library window.
+ gLibrary.close();
+ finish();
+}
+
+/**
+ * The observer is where magic happens, for every change we do it will look for
+ * nodes positions in the affected views.
+ */
+var bookmarksObserver = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver
+ , Ci.nsIAnnotationObserver
+ ]),
+
+ // nsIAnnotationObserver
+ onItemAnnotationSet: function() {},
+ onItemAnnotationRemoved: function() {},
+ onPageAnnotationSet: function() {},
+ onPageAnnotationRemoved: function() {},
+
+ // nsINavBookmarkObserver
+ onItemAdded: function PSB_onItemAdded(aItemId, aFolderId, aIndex, aItemType,
+ aURI) {
+ var node = null;
+ var index = null;
+ [node, index] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places);
+ // Left pane should not be updated for normal bookmarks or separators.
+ switch (aItemType) {
+ case PlacesUtils.bookmarks.TYPE_BOOKMARK:
+ var uriString = aURI.spec;
+ var isQuery = uriString.substr(0, 6) == "place:";
+ if (isQuery) {
+ isnot(node, null, "Found new Places node in left pane");
+ ok(index >= 0, "Node is at index " + index);
+ break;
+ }
+ // Fallback to separator case if this is not a query.
+ case PlacesUtils.bookmarks.TYPE_SEPARATOR:
+ is(node, null, "New Places node not added in left pane");
+ break;
+ default:
+ isnot(node, null, "Found new Places node in left pane");
+ ok(index >= 0, "Node is at index " + index);
+ }
+ },
+
+ onItemRemoved: function PSB_onItemRemoved(aItemId, aFolder, aIndex) {
+ var node = null;
+ [node, ] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places);
+ is(node, null, "Places node not found in left pane");
+ },
+
+ onItemMoved: function(aItemId,
+ aOldFolderId, aOldIndex,
+ aNewFolderId, aNewIndex, aItemType) {
+ var node = null;
+ var index = null;
+ [node, index] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places);
+ // Left pane should not be updated for normal bookmarks or separators.
+ switch (aItemType) {
+ case PlacesUtils.bookmarks.TYPE_BOOKMARK:
+ var uriString = PlacesUtils.bookmarks.getBookmarkURI(aItemId).spec;
+ var isQuery = uriString.substr(0, 6) == "place:";
+ if (isQuery) {
+ isnot(node, null, "Found new Places node in left pane");
+ ok(index >= 0, "Node is at index " + index);
+ break;
+ }
+ // Fallback to separator case if this is not a query.
+ case PlacesUtils.bookmarks.TYPE_SEPARATOR:
+ is(node, null, "New Places node not added in left pane");
+ break;
+ default:
+ isnot(node, null, "Found new Places node in left pane");
+ ok(index >= 0, "Node is at index " + index);
+ }
+ },
+
+ onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {},
+ onEndUpdateBatch: function PSB_onEndUpdateBatch() {},
+ onItemVisited: function() {},
+ onItemChanged: function PSB_onItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty, aNewValue) {
+ if (aProperty == "title") {
+ let validator = function(aTreeRowIndex) {
+ let tree = gLibrary.PlacesOrganizer._places;
+ let cellText = tree.view.getCellText(aTreeRowIndex,
+ tree.columns.getColumnAt(0));
+ return cellText == aNewValue;
+ }
+ let [node, , valid] = getNodeForTreeItem(aItemId, gLibrary.PlacesOrganizer._places, validator);
+ if (node) // Only visible nodes.
+ ok(valid, "Title cell value has been correctly updated");
+ }
+ }
+};
+
+
+/**
+ * Get places node and index for an itemId in a tree view.
+ *
+ * @param aItemId
+ * item id of the item to search.
+ * @param aTree
+ * Tree to search in.
+ * @param aValidator [optional]
+ * function to check row validity if found. Defaults to {return true;}.
+ * @returns [node, index, valid] or [null, null, false] if not found.
+ */
+function getNodeForTreeItem(aItemId, aTree, aValidator) {
+
+ function findNode(aContainerIndex) {
+ if (aTree.view.isContainerEmpty(aContainerIndex))
+ return [null, null, false];
+
+ // The rowCount limit is just for sanity, but we will end looping when
+ // we have checked the last child of this container or we have found node.
+ for (var i = aContainerIndex + 1; i < aTree.view.rowCount; i++) {
+ var node = aTree.view.nodeForTreeIndex(i);
+
+ if (node.itemId == aItemId) {
+ // Minus one because we want relative index inside the container.
+ let valid = aValidator ? aValidator(i) : true;
+ return [node, i - aTree.view.getParentIndex(i) - 1, valid];
+ }
+
+ if (PlacesUtils.nodeIsFolder(node)) {
+ // Open container.
+ aTree.view.toggleOpenState(i);
+ // Search inside it.
+ var foundNode = findNode(i);
+ // Close container.
+ aTree.view.toggleOpenState(i);
+ // Return node if found.
+ if (foundNode[0] != null)
+ return foundNode;
+ }
+
+ // We have finished walking this container.
+ if (!aTree.view.hasNextSibling(aContainerIndex + 1, i))
+ break;
+ }
+ return [null, null, false]
+ }
+
+ // Root node is hidden, so we need to manually walk the first level.
+ for (var i = 0; i < aTree.view.rowCount; i++) {
+ // Open container.
+ aTree.view.toggleOpenState(i);
+ // Search inside it.
+ var foundNode = findNode(i);
+ // Close container.
+ aTree.view.toggleOpenState(i);
+ // Return node if found.
+ if (foundNode[0] != null)
+ return foundNode;
+ }
+ return [null, null, false];
+}
diff --git a/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js b/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js
new file mode 100644
index 000000000..02d564e28
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js
@@ -0,0 +1,68 @@
+/**
+ * Tests that visits across frames are correctly represented in the database.
+ */
+
+const BASE_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser";
+const PAGE_URL = BASE_URL + "/framedPage.html";
+const LEFT_URL = BASE_URL + "/frameLeft.html";
+const RIGHT_URL = BASE_URL + "/frameRight.html";
+
+add_task(function* test() {
+ // We must wait for both frames to be loaded and the visits to be registered.
+ let deferredLeftFrameVisit = PromiseUtils.defer();
+ let deferredRightFrameVisit = PromiseUtils.defer();
+
+ Services.obs.addObserver(function observe(subject) {
+ Task.spawn(function* () {
+ let url = subject.QueryInterface(Ci.nsIURI).spec;
+ if (url == LEFT_URL ) {
+ is((yield getTransitionForUrl(url)), null,
+ "Embed visits should not get a database entry.");
+ deferredLeftFrameVisit.resolve();
+ }
+ else if (url == RIGHT_URL ) {
+ is((yield getTransitionForUrl(url)),
+ PlacesUtils.history.TRANSITION_FRAMED_LINK,
+ "User activated visits should get a FRAMED_LINK transition.");
+ Services.obs.removeObserver(observe, "uri-visit-saved");
+ deferredRightFrameVisit.resolve();
+ }
+ });
+ }, "uri-visit-saved", false);
+
+ // Open a tab and wait for all the subframes to load.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
+
+ // Wait for the left frame visit to be registered.
+ info("Waiting left frame visit");
+ yield deferredLeftFrameVisit.promise;
+
+ // Click on the link in the left frame to cause a page load in the
+ // right frame.
+ info("Clicking link");
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.frames[0].document.getElementById("clickme").click();
+ });
+
+ // Wait for the right frame visit to be registered.
+ info("Waiting right frame visit");
+ yield deferredRightFrameVisit.promise;
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+function* getTransitionForUrl(url) {
+ // Ensure all the transactions completed.
+ yield PlacesTestUtils.promiseAsyncUpdates();
+ let db = yield PlacesUtils.promiseDBConnection();
+ let rows = yield db.execute(`
+ SELECT visit_type
+ FROM moz_historyvisits
+ JOIN moz_places h ON place_id = h.id
+ WHERE url_hash = hash(:url) AND url = :url`,
+ { url });
+ if (rows.length) {
+ return rows[0].getResultByName("visit_type");
+ }
+ return null;
+}
diff --git a/browser/components/places/tests/browser/browser_sidebarpanels_click.js b/browser/components/places/tests/browser/browser_sidebarpanels_click.js
new file mode 100644
index 000000000..80ed2eb2b
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_sidebarpanels_click.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/. */
+
+// This test makes sure that the items in the bookmarks and history sidebar
+// panels are clickable in both LTR and RTL modes.
+
+function test() {
+ waitForExplicitFinish();
+ ignoreAllUncaughtExceptions();
+
+ const BOOKMARKS_SIDEBAR_ID = "viewBookmarksSidebar";
+ const BOOKMARKS_SIDEBAR_TREE_ID = "bookmarks-view";
+ const HISTORY_SIDEBAR_ID = "viewHistorySidebar";
+ const HISTORY_SIDEBAR_TREE_ID = "historyTree";
+ const TEST_URL = "http://mochi.test:8888/browser/browser/components/places/tests/browser/sidebarpanels_click_test_page.html";
+
+ // If a sidebar is already open, close it.
+ if (!document.getElementById("sidebar-box").hidden) {
+ info("Unexpected sidebar found - a previous test failed to cleanup correctly");
+ SidebarUI.hide();
+ }
+
+ let sidebar = document.getElementById("sidebar");
+ let tests = [];
+ let currentTest;
+
+ tests.push({
+ _itemID: null,
+ init: function(aCallback) {
+ // Add a bookmark to the Unfiled Bookmarks folder.
+ this._itemID = PlacesUtils.bookmarks.insertBookmark(
+ PlacesUtils.unfiledBookmarksFolderId, PlacesUtils._uri(TEST_URL),
+ PlacesUtils.bookmarks.DEFAULT_INDEX, "test"
+ );
+ aCallback();
+ },
+ prepare: function() {
+ },
+ selectNode: function(tree) {
+ tree.selectItems([this._itemID]);
+ },
+ cleanup: function(aCallback) {
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ executeSoon(aCallback);
+ },
+ sidebarName: BOOKMARKS_SIDEBAR_ID,
+ treeName: BOOKMARKS_SIDEBAR_TREE_ID,
+ desc: "Bookmarks sidebar test"
+ });
+
+ tests.push({
+ init: function(aCallback) {
+ // Add a history entry.
+ let uri = PlacesUtils._uri(TEST_URL);
+ PlacesTestUtils.addVisits({
+ uri: uri, visitDate: Date.now() * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED
+ }).then(aCallback);
+ },
+ prepare: function() {
+ sidebar.contentDocument.getElementById("byvisited").doCommand();
+ },
+ selectNode: function(tree) {
+ tree.selectNode(tree.view.nodeForTreeIndex(0));
+ is(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected");
+ is(tree.selectedNode.itemId, -1, "The selected node is not bookmarked");
+ },
+ cleanup: function(aCallback) {
+ PlacesTestUtils.clearHistory().then(aCallback);
+ },
+ sidebarName: HISTORY_SIDEBAR_ID,
+ treeName: HISTORY_SIDEBAR_TREE_ID,
+ desc: "History sidebar test"
+ });
+
+ function testPlacesPanel(preFunc, postFunc) {
+ currentTest.init(function() {
+ SidebarUI.show(currentTest.sidebarName);
+ });
+
+ sidebar.addEventListener("load", function() {
+ sidebar.removeEventListener("load", arguments.callee, true);
+ executeSoon(function() {
+ currentTest.prepare();
+
+ if (preFunc)
+ preFunc();
+
+ function observer(aSubject, aTopic, aData) {
+ info("alert dialog observed as expected");
+ Services.obs.removeObserver(observer, "common-dialog-loaded");
+ Services.obs.removeObserver(observer, "tabmodal-dialog-loaded");
+
+ aSubject.Dialog.ui.button0.click();
+
+ executeSoon(function () {
+ SidebarUI.hide();
+ currentTest.cleanup(postFunc);
+ });
+ }
+ Services.obs.addObserver(observer, "common-dialog-loaded", false);
+ Services.obs.addObserver(observer, "tabmodal-dialog-loaded", false);
+
+ let tree = sidebar.contentDocument.getElementById(currentTest.treeName);
+
+ // Select the inserted places item.
+ currentTest.selectNode(tree);
+
+ synthesizeClickOnSelectedTreeCell(tree);
+ // Now, wait for the observer to catch the alert dialog.
+ // If something goes wrong, the test will time out at this stage.
+ // Note that for the history sidebar, the URL itself is not opened,
+ // and Places will show the load-js-data-url-error prompt as an alert
+ // box, which means that the click actually worked, so it's good enough
+ // for the purpose of this test.
+ });
+ }, true);
+ }
+
+ function changeSidebarDirection(aDirection) {
+ sidebar.contentDocument.documentElement.style.direction = aDirection;
+ }
+
+ function runNextTest() {
+ // Remove eventual tabs created by previous sub-tests.
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ }
+
+ if (tests.length == 0) {
+ finish();
+ }
+ else {
+ // Create a new tab and run the test.
+ gBrowser.selectedTab = gBrowser.addTab();
+ currentTest = tests.shift();
+ testPlacesPanel(function() {
+ changeSidebarDirection("ltr");
+ info("Running " + currentTest.desc + " in LTR mode");
+ },
+ function() {
+ testPlacesPanel(function() {
+ // Run the test in RTL mode.
+ changeSidebarDirection("rtl");
+ info("Running " + currentTest.desc + " in RTL mode");
+ },
+ function() {
+ runNextTest();
+ });
+ });
+ }
+ }
+
+ // Ensure history is clean before starting the test.
+ PlacesTestUtils.clearHistory().then(runNextTest);
+}
diff --git a/browser/components/places/tests/browser/browser_sort_in_library.js b/browser/components/places/tests/browser/browser_sort_in_library.js
new file mode 100644
index 000000000..af9c35e59
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_sort_in_library.js
@@ -0,0 +1,249 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests the following bugs:
+ *
+ * Bug 443745 - View>Sort>of "alpha" sort items is default to Z>A instead of A>Z
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=443745
+ *
+ * Bug 444179 - Library>Views>Sort>Sort by Tags does nothing
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=444179
+ *
+ * Basically, fully tests sorting the placeContent tree in the Places Library
+ * window. Sorting is verified by comparing the nsINavHistoryResult returned by
+ * placeContent.result to the expected sort values.
+ */
+
+// Two properties of nsINavHistoryResult control the sort of the tree:
+// sortingMode and sortingAnnotation. sortingMode's value is one of the
+// nsINavHistoryQueryOptions.SORT_BY_* constants. sortingAnnotation is the
+// annotation used to sort for SORT_BY_ANNOTATION_* mode.
+//
+// This lookup table maps the possible values of anonid's of the treecols to
+// objects that represent the treecols' correct state after the user sorts the
+// previously unsorted tree by selecting a column from the Views > Sort menu.
+// sortingMode is constructed from the key and dir properties (i.e.,
+// SORT_BY_<key>_<dir>) and sortingAnnotation is checked against anno. anno
+// may be undefined if key is not "ANNOTATION".
+const SORT_LOOKUP_TABLE = {
+ title: { key: "TITLE", dir: "ASCENDING" },
+ tags: { key: "TAGS", dir: "ASCENDING" },
+ url: { key: "URI", dir: "ASCENDING" },
+ date: { key: "DATE", dir: "DESCENDING" },
+ visitCount: { key: "VISITCOUNT", dir: "DESCENDING" },
+ dateAdded: { key: "DATEADDED", dir: "DESCENDING" },
+ lastModified: { key: "LASTMODIFIED", dir: "DESCENDING" },
+ description: { key: "ANNOTATION",
+ dir: "ASCENDING",
+ anno: "bookmarkProperties/description" }
+};
+
+// This is the column that's sorted if one is not specified and the tree is
+// currently unsorted. Set it to a key substring in the name of one of the
+// nsINavHistoryQueryOptions.SORT_BY_* constants, e.g., "TITLE", "URI".
+// Method ViewMenu.setSortColumn in browser/components/places/content/places.js
+// determines this value.
+const DEFAULT_SORT_KEY = "TITLE";
+
+// Part of the test is checking that sorts stick, so each time we sort we need
+// to remember it.
+var prevSortDir = null;
+var prevSortKey = null;
+
+/**
+ * Ensures that the sort of aTree is aSortingMode and aSortingAnno.
+ *
+ * @param aTree
+ * the tree to check
+ * @param aSortingMode
+ * one of the Ci.nsINavHistoryQueryOptions.SORT_BY_* constants
+ * @param aSortingAnno
+ * checked only if sorting mode is one of the
+ * Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_* constants
+ */
+function checkSort(aTree, aSortingMode, aSortingAnno) {
+ // The placeContent tree's sort is determined by the nsINavHistoryResult it
+ // stores. Get it and check that the sort is what the caller expects.
+ let res = aTree.result;
+ isnot(res, null,
+ "sanity check: placeContent.result should not return null");
+
+ // Check sortingMode.
+ is(res.sortingMode, aSortingMode,
+ "column should now have sortingMode " + aSortingMode);
+
+ // Check sortingAnnotation, but only if sortingMode is ANNOTATION.
+ if ([Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING,
+ Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING].
+ indexOf(aSortingMode) >= 0) {
+ is(res.sortingAnnotation, aSortingAnno,
+ "column should now have sorting annotation " + aSortingAnno);
+ }
+}
+
+/**
+ * Sets the sort of aTree.
+ *
+ * @param aOrganizerWin
+ * the Places window
+ * @param aTree
+ * the tree to sort
+ * @param aUnsortFirst
+ * true if the sort should be set to SORT_BY_NONE before sorting by aCol
+ * and aDir
+ * @param aShouldFail
+ * true if setSortColumn should fail on aCol or aDir
+ * @param aCol
+ * the column of aTree by which to sort
+ * @param aDir
+ * either "ascending" or "descending"
+ */
+function setSort(aOrganizerWin, aTree, aUnsortFirst, aShouldFail, aCol, aDir) {
+ if (aUnsortFirst) {
+ aOrganizerWin.ViewMenu.setSortColumn();
+ checkSort(aTree, Ci.nsINavHistoryQueryOptions.SORT_BY_NONE, "");
+
+ // Remember the sort key and direction.
+ prevSortKey = null;
+ prevSortDir = null;
+ }
+
+ let failed = false;
+ try {
+ aOrganizerWin.ViewMenu.setSortColumn(aCol, aDir);
+
+ // Remember the sort key and direction.
+ if (!aCol && !aDir) {
+ prevSortKey = null;
+ prevSortDir = null;
+ }
+ else {
+ if (aCol)
+ prevSortKey = SORT_LOOKUP_TABLE[aCol.getAttribute("anonid")].key;
+ else if (prevSortKey === null)
+ prevSortKey = DEFAULT_SORT_KEY;
+
+ if (aDir)
+ prevSortDir = aDir.toUpperCase();
+ else if (prevSortDir === null)
+ prevSortDir = SORT_LOOKUP_TABLE[aCol.getAttribute("anonid")].dir;
+ }
+ } catch (exc) {
+ failed = true;
+ }
+
+ is(failed, !!aShouldFail,
+ "setSortColumn on column " +
+ (aCol ? aCol.getAttribute("anonid") : "(no column)") +
+ " with direction " + (aDir || "(no direction)") +
+ " and table previously " + (aUnsortFirst ? "unsorted" : "sorted") +
+ " should " + (aShouldFail ? "" : "not ") + "fail");
+}
+
+/**
+ * Tries sorting by an invalid column and sort direction.
+ *
+ * @param aOrganizerWin
+ * the Places window
+ * @param aPlaceContentTree
+ * the placeContent tree in aOrganizerWin
+ */
+function testInvalid(aOrganizerWin, aPlaceContentTree) {
+ // Invalid column should fail by throwing an exception.
+ let bogusCol = document.createElement("treecol");
+ bogusCol.setAttribute("anonid", "bogusColumn");
+ setSort(aOrganizerWin, aPlaceContentTree, true, true, bogusCol, "ascending");
+
+ // Invalid direction reverts to SORT_BY_NONE.
+ setSort(aOrganizerWin, aPlaceContentTree, false, false, null, "bogus dir");
+ checkSort(aPlaceContentTree, Ci.nsINavHistoryQueryOptions.SORT_BY_NONE, "");
+}
+
+/**
+ * Tests sorting aPlaceContentTree by column only and then by both column
+ * and direction.
+ *
+ * @param aOrganizerWin
+ * the Places window
+ * @param aPlaceContentTree
+ * the placeContent tree in aOrganizerWin
+ * @param aUnsortFirst
+ * true if, before each sort we try, we should sort to SORT_BY_NONE
+ */
+function testSortByColAndDir(aOrganizerWin, aPlaceContentTree, aUnsortFirst) {
+ let cols = aPlaceContentTree.getElementsByTagName("treecol");
+ ok(cols.length > 0, "sanity check: placeContent should contain columns");
+
+ for (let i = 0; i < cols.length; i++) {
+ let col = cols.item(i);
+ ok(col.hasAttribute("anonid"),
+ "sanity check: column " + col.id + " should have anonid");
+
+ let colId = col.getAttribute("anonid");
+ ok(colId in SORT_LOOKUP_TABLE,
+ "sanity check: unexpected placeContent column anonid");
+
+ let sortConst =
+ "SORT_BY_" + SORT_LOOKUP_TABLE[colId].key + "_" +
+ (aUnsortFirst ? SORT_LOOKUP_TABLE[colId].dir : prevSortDir);
+ let expectedSortMode = Ci.nsINavHistoryQueryOptions[sortConst];
+ let expectedAnno = SORT_LOOKUP_TABLE[colId].anno || "";
+
+ // Test sorting by only a column.
+ setSort(aOrganizerWin, aPlaceContentTree, aUnsortFirst, false, col);
+ checkSort(aPlaceContentTree, expectedSortMode, expectedAnno);
+
+ // Test sorting by both a column and a direction.
+ ["ascending", "descending"].forEach(function (dir) {
+ let sortConst =
+ "SORT_BY_" + SORT_LOOKUP_TABLE[colId].key + "_" + dir.toUpperCase();
+ let expectedSortMode = Ci.nsINavHistoryQueryOptions[sortConst];
+ setSort(aOrganizerWin, aPlaceContentTree, aUnsortFirst, false, col, dir);
+ checkSort(aPlaceContentTree, expectedSortMode, expectedAnno);
+ });
+ }
+}
+
+/**
+ * Tests sorting aPlaceContentTree by direction only.
+ *
+ * @param aOrganizerWin
+ * the Places window
+ * @param aPlaceContentTree
+ * the placeContent tree in aOrganizerWin
+ * @param aUnsortFirst
+ * true if, before each sort we try, we should sort to SORT_BY_NONE
+ */
+function testSortByDir(aOrganizerWin, aPlaceContentTree, aUnsortFirst) {
+ ["ascending", "descending"].forEach(function (dir) {
+ let key = (aUnsortFirst ? DEFAULT_SORT_KEY : prevSortKey);
+ let sortConst = "SORT_BY_" + key + "_" + dir.toUpperCase();
+ let expectedSortMode = Ci.nsINavHistoryQueryOptions[sortConst];
+ setSort(aOrganizerWin, aPlaceContentTree, aUnsortFirst, false, null, dir);
+ checkSort(aPlaceContentTree, expectedSortMode, "");
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ openLibrary(function (win) {
+ let tree = win.document.getElementById("placeContent");
+ isnot(tree, null, "sanity check: placeContent tree should exist");
+ // Run the tests.
+ testSortByColAndDir(win, tree, true);
+ testSortByColAndDir(win, tree, false);
+ testSortByDir(win, tree, true);
+ testSortByDir(win, tree, false);
+ testInvalid(win, tree);
+ // Reset the sort to SORT_BY_NONE.
+ setSort(win, tree, false, false);
+ // Close the window and finish.
+ win.close();
+ finish();
+ });
+}
diff --git a/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js b/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js
new file mode 100644
index 000000000..7a0eec22f
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_toolbarbutton_menu_context.js
@@ -0,0 +1,53 @@
+var bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
+var BMB_menuPopup = document.getElementById("BMB_bookmarksPopup");
+var BMB_showAllBookmarks = document.getElementById("BMB_bookmarksShowAll");
+var contextMenu = document.getElementById("placesContext");
+var newBookmarkItem = document.getElementById("placesContext_new:bookmark");
+
+waitForExplicitFinish();
+add_task(function* testPopup() {
+ info("Checking popup context menu before moving the bookmarks button");
+ yield checkPopupContextMenu();
+ let pos = CustomizableUI.getPlacementOfWidget("bookmarks-menu-button").position;
+ CustomizableUI.addWidgetToArea("bookmarks-menu-button", CustomizableUI.AREA_PANEL);
+ CustomizableUI.addWidgetToArea("bookmarks-menu-button", CustomizableUI.AREA_NAVBAR, pos);
+ info("Checking popup context menu after moving the bookmarks button");
+ yield checkPopupContextMenu();
+});
+
+function* checkPopupContextMenu() {
+ let dropmarker = document.getAnonymousElementByAttribute(bookmarksMenuButton, "anonid", "dropmarker");
+ BMB_menuPopup.setAttribute("style", "transition: none;");
+ let popupShownPromise = onPopupEvent(BMB_menuPopup, "shown");
+ EventUtils.synthesizeMouseAtCenter(dropmarker, {});
+ info("Waiting for bookmarks menu to be shown.");
+ yield popupShownPromise;
+ let contextMenuShownPromise = onPopupEvent(contextMenu, "shown");
+ EventUtils.synthesizeMouseAtCenter(BMB_showAllBookmarks, {type: "contextmenu", button: 2 });
+ info("Waiting for context menu on bookmarks menu to be shown.");
+ yield contextMenuShownPromise;
+ ok(!newBookmarkItem.hasAttribute("disabled"), "New bookmark item shouldn't be disabled");
+ let contextMenuHiddenPromise = onPopupEvent(contextMenu, "hidden");
+ contextMenu.hidePopup();
+ BMB_menuPopup.removeAttribute("style");
+ info("Waiting for context menu on bookmarks menu to be hidden.");
+ yield contextMenuHiddenPromise;
+ let popupHiddenPromise = onPopupEvent(BMB_menuPopup, "hidden");
+ // Can't use synthesizeMouseAtCenter because the dropdown panel is in the way
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ info("Waiting for bookmarks menu to be hidden.");
+ yield popupHiddenPromise;
+}
+
+function onPopupEvent(popup, evt) {
+ let fullEvent = "popup" + evt;
+ let deferred = new Promise.defer();
+ let onPopupHandler = (e) => {
+ if (e.target == popup) {
+ popup.removeEventListener(fullEvent, onPopupHandler);
+ deferred.resolve();
+ }
+ };
+ popup.addEventListener(fullEvent, onPopupHandler);
+ return deferred.promise;
+}
diff --git a/browser/components/places/tests/browser/browser_views_liveupdate.js b/browser/components/places/tests/browser/browser_views_liveupdate.js
new file mode 100644
index 000000000..735d6b168
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_views_liveupdate.js
@@ -0,0 +1,475 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests Places views (menu, toolbar, tree) for liveupdate.
+ */
+
+var toolbar = document.getElementById("PersonalToolbar");
+var wasCollapsed = toolbar.collapsed;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Uncollapse the personal toolbar if needed.
+ if (wasCollapsed) {
+ promiseSetToolbarVisibility(toolbar, true).then(openBookmarksSidebar);
+ } else {
+ openBookmarksSidebar();
+ }
+}
+
+function openBookmarksSidebar() {
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils in context");
+ ok(PlacesUIUtils, "PlacesUIUtils in context");
+
+ // Open bookmarks menu.
+ var popup = document.getElementById("bookmarksMenuPopup");
+ ok(popup, "Menu popup element exists");
+ fakeOpenPopup(popup);
+
+ // Open bookmarks sidebar.
+ var sidebar = document.getElementById("sidebar");
+ sidebar.addEventListener("load", function() {
+ sidebar.removeEventListener("load", arguments.callee, true);
+ // Need to executeSoon since the tree is initialized on sidebar load.
+ executeSoon(startTest);
+ }, true);
+ SidebarUI.show("viewBookmarksSidebar");
+}
+
+/**
+ * Simulates popup opening causing it to populate.
+ * We cannot just use menu.open, since it would not work on Mac due to native menubar.
+ */
+function fakeOpenPopup(aPopup) {
+ var popupEvent = document.createEvent("MouseEvent");
+ popupEvent.initMouseEvent("popupshowing", true, true, window, 0,
+ 0, 0, 0, 0, false, false, false, false,
+ 0, null);
+ aPopup.dispatchEvent(popupEvent);
+}
+
+/**
+ * Adds bookmarks observer, and executes a bunch of bookmarks operations.
+ */
+function startTest() {
+ var bs = PlacesUtils.bookmarks;
+ // Add observers.
+ bs.addObserver(bookmarksObserver, false);
+ PlacesUtils.annotations.addObserver(bookmarksObserver, false);
+ var addedBookmarks = [];
+
+ // MENU
+ info("*** Acting on menu bookmarks");
+ var id = bs.insertBookmark(bs.bookmarksMenuFolder,
+ PlacesUtils._uri("http://bm1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "bm1");
+ bs.setItemTitle(id, "bm1_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(bs.bookmarksMenuFolder,
+ PlacesUtils._uri("place:"),
+ bs.DEFAULT_INDEX,
+ "bm2");
+ bs.setItemTitle(id, "");
+ addedBookmarks.push(id);
+ id = bs.insertSeparator(bs.bookmarksMenuFolder, bs.DEFAULT_INDEX);
+ addedBookmarks.push(id);
+ id = bs.createFolder(bs.bookmarksMenuFolder,
+ "bmf",
+ bs.DEFAULT_INDEX);
+ bs.setItemTitle(id, "bmf_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(id,
+ PlacesUtils._uri("http://bmf1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "bmf1");
+ bs.setItemTitle(id, "bmf1_edited");
+ addedBookmarks.push(id);
+ bs.moveItem(id, bs.bookmarksMenuFolder, 0);
+
+ // TOOLBAR
+ info("*** Acting on toolbar bookmarks");
+ id = bs.insertBookmark(bs.toolbarFolder,
+ PlacesUtils._uri("http://tb1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "tb1");
+ bs.setItemTitle(id, "tb1_edited");
+ addedBookmarks.push(id);
+ // Test live update of title.
+ bs.setItemTitle(id, "tb1_edited");
+ id = bs.insertBookmark(bs.toolbarFolder,
+ PlacesUtils._uri("place:"),
+ bs.DEFAULT_INDEX,
+ "tb2");
+ bs.setItemTitle(id, "");
+ addedBookmarks.push(id);
+ id = bs.insertSeparator(bs.toolbarFolder, bs.DEFAULT_INDEX);
+ addedBookmarks.push(id);
+ id = bs.createFolder(bs.toolbarFolder,
+ "tbf",
+ bs.DEFAULT_INDEX);
+ bs.setItemTitle(id, "tbf_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(id,
+ PlacesUtils._uri("http://tbf1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "tbf1");
+ bs.setItemTitle(id, "tbf1_edited");
+ addedBookmarks.push(id);
+ bs.moveItem(id, bs.toolbarFolder, 0);
+
+ // UNSORTED
+ info("*** Acting on unsorted bookmarks");
+ id = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ PlacesUtils._uri("http://ub1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "ub1");
+ bs.setItemTitle(id, "ub1_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(bs.unfiledBookmarksFolder,
+ PlacesUtils._uri("place:"),
+ bs.DEFAULT_INDEX,
+ "ub2");
+ bs.setItemTitle(id, "ub2_edited");
+ addedBookmarks.push(id);
+ id = bs.insertSeparator(bs.unfiledBookmarksFolder, bs.DEFAULT_INDEX);
+ addedBookmarks.push(id);
+ id = bs.createFolder(bs.unfiledBookmarksFolder,
+ "ubf",
+ bs.DEFAULT_INDEX);
+ bs.setItemTitle(id, "ubf_edited");
+ addedBookmarks.push(id);
+ id = bs.insertBookmark(id,
+ PlacesUtils._uri("http://ubf1.mozilla.org/"),
+ bs.DEFAULT_INDEX,
+ "bubf1");
+ bs.setItemTitle(id, "bubf1_edited");
+ addedBookmarks.push(id);
+ bs.moveItem(id, bs.unfiledBookmarksFolder, 0);
+
+ // Remove all added bookmarks.
+ addedBookmarks.forEach(function (aItem) {
+ // If we remove an item after its containing folder has been removed,
+ // this will throw, but we can ignore that.
+ try {
+ bs.removeItem(aItem);
+ } catch (ex) {}
+ });
+
+ // Remove observers.
+ bs.removeObserver(bookmarksObserver);
+ PlacesUtils.annotations.removeObserver(bookmarksObserver);
+ finishTest();
+}
+
+/**
+ * Restores browser state and calls finish.
+ */
+function finishTest() {
+ // Close bookmarks sidebar.
+ SidebarUI.hide();
+
+ // Collapse the personal toolbar if needed.
+ if (wasCollapsed) {
+ promiseSetToolbarVisibility(toolbar, false).then(finish);
+ } else {
+ finish();
+ }
+}
+
+/**
+ * The observer is where magic happens, for every change we do it will look for
+ * nodes positions in the affected views.
+ */
+var bookmarksObserver = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver
+ , Ci.nsIAnnotationObserver
+ ]),
+
+ // nsIAnnotationObserver
+ onItemAnnotationSet: function() {},
+ onItemAnnotationRemoved: function() {},
+ onPageAnnotationSet: function() {},
+ onPageAnnotationRemoved: function() {},
+
+ // nsINavBookmarkObserver
+ onItemAdded: function PSB_onItemAdded(aItemId, aFolderId, aIndex,
+ aItemType, aURI) {
+ var views = getViewsForFolder(aFolderId);
+ ok(views.length > 0, "Found affected views (" + views.length + "): " + views);
+
+ // Check that item has been added in the correct position.
+ for (var i = 0; i < views.length; i++) {
+ var [node, index] = searchItemInView(aItemId, views[i]);
+ isnot(node, null, "Found new Places node in " + views[i]);
+ is(index, aIndex, "Node is at index " + index);
+ }
+ },
+
+ onItemRemoved: function PSB_onItemRemoved(aItemId, aFolderId, aIndex,
+ aItemType) {
+ var views = getViewsForFolder(aFolderId);
+ ok(views.length > 0, "Found affected views (" + views.length + "): " + views);
+ // Check that item has been removed.
+ for (var i = 0; i < views.length; i++) {
+ var node = null;
+ [node, ] = searchItemInView(aItemId, views[i]);
+ is(node, null, "Places node not found in " + views[i]);
+ }
+ },
+
+ onItemMoved: function(aItemId,
+ aOldFolderId, aOldIndex,
+ aNewFolderId, aNewIndex,
+ aItemType) {
+ var views = getViewsForFolder(aNewFolderId);
+ ok(views.length > 0, "Found affected views: " + views);
+
+ // Check that item has been moved in the correct position.
+ for (var i = 0; i < views.length; i++) {
+ var node = null;
+ var index = null;
+ [node, index] = searchItemInView(aItemId, views[i]);
+ isnot(node, null, "Found new Places node in " + views[i]);
+ is(index, aNewIndex, "Node is at index " + index);
+ }
+ },
+
+ onBeginUpdateBatch: function PSB_onBeginUpdateBatch() {},
+ onEndUpdateBatch: function PSB_onEndUpdateBatch() {},
+ onItemVisited: function() {},
+
+ onItemChanged: function PSB_onItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty, aNewValue,
+ aLastModified, aItemType,
+ aParentId) {
+ if (aProperty !== "title")
+ return;
+
+ var views = getViewsForFolder(aParentId);
+ ok(views.length > 0, "Found affected views (" + views.length + "): " + views);
+
+ // Check that item has been moved in the correct position.
+ let validator = function(aElementOrTreeIndex) {
+ if (typeof(aElementOrTreeIndex) == "number") {
+ var sidebar = document.getElementById("sidebar");
+ var tree = sidebar.contentDocument.getElementById("bookmarks-view");
+ let cellText = tree.view.getCellText(aElementOrTreeIndex,
+ tree.columns.getColumnAt(0));
+ if (!aNewValue)
+ return cellText == PlacesUIUtils.getBestTitle(tree.view.nodeForTreeIndex(aElementOrTreeIndex), true);
+ return cellText == aNewValue;
+ }
+ if (!aNewValue && aElementOrTreeIndex.localName != "toolbarbutton") {
+ return aElementOrTreeIndex.getAttribute("label") == PlacesUIUtils.getBestTitle(aElementOrTreeIndex._placesNode);
+ }
+ return aElementOrTreeIndex.getAttribute("label") == aNewValue;
+ };
+
+ for (var i = 0; i < views.length; i++) {
+ var [node, , valid] = searchItemInView(aItemId, views[i], validator);
+ isnot(node, null, "Found changed Places node in " + views[i]);
+ is(node.title, aNewValue, "Node has correct title: " + aNewValue);
+ ok(valid, "Node element has correct label: " + aNewValue);
+ }
+ }
+};
+
+/**
+ * Search an item id in a view.
+ *
+ * @param aItemId
+ * item id of the item to search.
+ * @param aView
+ * either "toolbar", "menu" or "sidebar"
+ * @param aValidator
+ * function to check validity of the found node element.
+ * @returns [node, index, valid] or [null, null, false] if not found.
+ */
+function searchItemInView(aItemId, aView, aValidator) {
+ switch (aView) {
+ case "toolbar":
+ return getNodeForToolbarItem(aItemId, aValidator);
+ case "menu":
+ return getNodeForMenuItem(aItemId, aValidator);
+ case "sidebar":
+ return getNodeForSidebarItem(aItemId, aValidator);
+ }
+
+ return [null, null, false];
+}
+
+/**
+ * Get places node and index for an itemId in bookmarks toolbar view.
+ *
+ * @param aItemId
+ * item id of the item to search.
+ * @returns [node, index] or [null, null] if not found.
+ */
+function getNodeForToolbarItem(aItemId, aValidator) {
+ var toolbar = document.getElementById("PlacesToolbarItems");
+
+ function findNode(aContainer) {
+ var children = aContainer.childNodes;
+ for (var i = 0, staticNodes = 0; i < children.length; i++) {
+ var child = children[i];
+
+ // Is this a Places node?
+ if (!child._placesNode || child.hasAttribute("simulated-places-node")) {
+ staticNodes++;
+ continue;
+ }
+
+ if (child._placesNode.itemId == aItemId) {
+ let valid = aValidator ? aValidator(child) : true;
+ return [child._placesNode, i - staticNodes, valid];
+ }
+
+ // Don't search in queries, they could contain our item in a
+ // different position. Search only folders
+ if (PlacesUtils.nodeIsFolder(child._placesNode)) {
+ var popup = child.lastChild;
+ popup.showPopup(popup);
+ var foundNode = findNode(popup);
+ popup.hidePopup();
+ if (foundNode[0] != null)
+ return foundNode;
+ }
+ }
+ return [null, null];
+ }
+
+ return findNode(toolbar);
+}
+
+/**
+ * Get places node and index for an itemId in bookmarks menu view.
+ *
+ * @param aItemId
+ * item id of the item to search.
+ * @returns [node, index] or [null, null] if not found.
+ */
+function getNodeForMenuItem(aItemId, aValidator) {
+ var menu = document.getElementById("bookmarksMenu");
+
+ function findNode(aContainer) {
+ var children = aContainer.childNodes;
+ for (var i = 0, staticNodes = 0; i < children.length; i++) {
+ var child = children[i];
+
+ // Is this a Places node?
+ if (!child._placesNode || child.hasAttribute("simulated-places-node")) {
+ staticNodes++;
+ continue;
+ }
+
+ if (child._placesNode.itemId == aItemId) {
+ let valid = aValidator ? aValidator(child) : true;
+ return [child._placesNode, i - staticNodes, valid];
+ }
+
+ // Don't search in queries, they could contain our item in a
+ // different position. Search only folders
+ if (PlacesUtils.nodeIsFolder(child._placesNode)) {
+ var popup = child.lastChild;
+ fakeOpenPopup(popup);
+ var foundNode = findNode(popup);
+
+ child.open = false;
+ if (foundNode[0] != null)
+ return foundNode;
+ }
+ }
+ return [null, null, false];
+ }
+
+ return findNode(menu.lastChild);
+}
+
+/**
+ * Get places node and index for an itemId in sidebar tree view.
+ *
+ * @param aItemId
+ * item id of the item to search.
+ * @returns [node, index] or [null, null] if not found.
+ */
+function getNodeForSidebarItem(aItemId, aValidator) {
+ var sidebar = document.getElementById("sidebar");
+ var tree = sidebar.contentDocument.getElementById("bookmarks-view");
+
+ function findNode(aContainerIndex) {
+ if (tree.view.isContainerEmpty(aContainerIndex))
+ return [null, null, false];
+
+ // The rowCount limit is just for sanity, but we will end looping when
+ // we have checked the last child of this container or we have found node.
+ for (var i = aContainerIndex + 1; i < tree.view.rowCount; i++) {
+ var node = tree.view.nodeForTreeIndex(i);
+
+ if (node.itemId == aItemId) {
+ // Minus one because we want relative index inside the container.
+ let valid = aValidator ? aValidator(i) : true;
+ return [node, i - tree.view.getParentIndex(i) - 1, valid];
+ }
+
+ if (PlacesUtils.nodeIsFolder(node)) {
+ // Open container.
+ tree.view.toggleOpenState(i);
+ // Search inside it.
+ var foundNode = findNode(i);
+ // Close container.
+ tree.view.toggleOpenState(i);
+ // Return node if found.
+ if (foundNode[0] != null)
+ return foundNode;
+ }
+
+ // We have finished walking this container.
+ if (!tree.view.hasNextSibling(aContainerIndex + 1, i))
+ break;
+ }
+ return [null, null, false]
+ }
+
+ // Root node is hidden, so we need to manually walk the first level.
+ for (var i = 0; i < tree.view.rowCount; i++) {
+ // Open container.
+ tree.view.toggleOpenState(i);
+ // Search inside it.
+ var foundNode = findNode(i);
+ // Close container.
+ tree.view.toggleOpenState(i);
+ // Return node if found.
+ if (foundNode[0] != null)
+ return foundNode;
+ }
+ return [null, null, false];
+}
+
+/**
+ * Get views affected by changes to a folder.
+ *
+ * @param aFolderId:
+ * item id of the folder we have changed.
+ * @returns a subset of views: ["toolbar", "menu", "sidebar"]
+ */
+function getViewsForFolder(aFolderId) {
+ var rootId = aFolderId;
+ while (!PlacesUtils.isRootItem(rootId))
+ rootId = PlacesUtils.bookmarks.getFolderIdForItem(rootId);
+
+ switch (rootId) {
+ case PlacesUtils.toolbarFolderId:
+ return ["toolbar", "sidebar"]
+ case PlacesUtils.bookmarksMenuFolderId:
+ return ["menu", "sidebar"]
+ case PlacesUtils.unfiledBookmarksFolderId:
+ return ["sidebar"]
+ }
+ return new Array();
+}
diff --git a/browser/components/places/tests/browser/frameLeft.html b/browser/components/places/tests/browser/frameLeft.html
new file mode 100644
index 000000000..5a54fe353
--- /dev/null
+++ b/browser/components/places/tests/browser/frameLeft.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>Left frame</title>
+ </head>
+ <body>
+ <a id="clickme" href="frameRight.html" target="right">Open page in the right frame.</a>
+ </body>
+</html>
diff --git a/browser/components/places/tests/browser/frameRight.html b/browser/components/places/tests/browser/frameRight.html
new file mode 100644
index 000000000..226accc34
--- /dev/null
+++ b/browser/components/places/tests/browser/frameRight.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>Right Frame</title>
+ </head>
+ <body>
+ This is the right frame.
+ </body>
+</html>
diff --git a/browser/components/places/tests/browser/framedPage.html b/browser/components/places/tests/browser/framedPage.html
new file mode 100644
index 000000000..d388562e6
--- /dev/null
+++ b/browser/components/places/tests/browser/framedPage.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <title>Framed page</title>
+ </head>
+ <frameset cols="*,*">
+ <frame name="left" src="frameLeft.html">
+ <frame name="right" src="about:mozilla">
+ </frameset>
+</html>
diff --git a/browser/components/places/tests/browser/head.js b/browser/components/places/tests/browser/head.js
new file mode 100644
index 000000000..aaf78332e
--- /dev/null
+++ b/browser/components/places/tests/browser/head.js
@@ -0,0 +1,460 @@
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+// We need to cache this before test runs...
+var cachedLeftPaneFolderIdGetter;
+var getter = PlacesUIUtils.__lookupGetter__("leftPaneFolderId");
+if (!cachedLeftPaneFolderIdGetter && typeof(getter) == "function") {
+ cachedLeftPaneFolderIdGetter = getter;
+}
+
+// ...And restore it when test ends.
+registerCleanupFunction(function() {
+ let getter = PlacesUIUtils.__lookupGetter__("leftPaneFolderId");
+ if (cachedLeftPaneFolderIdGetter && typeof(getter) != "function") {
+ PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter);
+ }
+});
+
+function openLibrary(callback, aLeftPaneRoot) {
+ let library = window.openDialog("chrome://browser/content/places/places.xul",
+ "", "chrome,toolbar=yes,dialog=no,resizable",
+ aLeftPaneRoot);
+ waitForFocus(function () {
+ callback(library);
+ }, library);
+
+ return library;
+}
+
+/**
+ * Returns a handle to a Library window.
+ * If one is opens returns itm otherwise it opens a new one.
+ *
+ * @param aLeftPaneRoot
+ * Hierarchy to open and select in the left pane.
+ */
+function promiseLibrary(aLeftPaneRoot) {
+ return new Promise(resolve => {
+ let library = Services.wm.getMostRecentWindow("Places:Organizer");
+ if (library && !library.closed) {
+ if (aLeftPaneRoot) {
+ library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
+ }
+ resolve(library);
+ }
+ else {
+ openLibrary(resolve, aLeftPaneRoot);
+ }
+ });
+}
+
+function promiseLibraryClosed(organizer) {
+ return new Promise(resolve => {
+ // Wait for the Organizer window to actually be closed
+ organizer.addEventListener("unload", function onUnload() {
+ organizer.removeEventListener("unload", onUnload);
+ resolve();
+ });
+
+ // Close Library window.
+ organizer.close();
+ });
+}
+
+/**
+ * Waits for a clipboard operation to complete, looking for the expected type.
+ *
+ * @see waitForClipboard
+ *
+ * @param aPopulateClipboardFn
+ * Function to populate the clipboard.
+ * @param aFlavor
+ * Data flavor to expect.
+ */
+function promiseClipboard(aPopulateClipboardFn, aFlavor) {
+ return new Promise(resolve => {
+ waitForClipboard(data => !!data, aPopulateClipboardFn, resolve, aFlavor);
+ });
+}
+
+/**
+ * Waits for all pending async statements on the default connection, before
+ * proceeding with aCallback.
+ *
+ * @param aCallback
+ * Function to be called when done.
+ * @param aScope
+ * Scope for the callback.
+ * @param aArguments
+ * Arguments array for the callback.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ * a write lock. Since all statements on the same connection are
+ * serialized, the end of this write operation means that all writes are
+ * complete. Note that WAL makes so that writers don't block readers, but
+ * this is a problem only across different connections.
+ */
+function waitForAsyncUpdates(aCallback, aScope, aArguments)
+{
+ let scope = aScope || this;
+ let args = aArguments || [];
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
+ begin.executeAsync();
+ begin.finalize();
+
+ let commit = db.createAsyncStatement("COMMIT");
+ commit.executeAsync({
+ handleResult: function() {},
+ handleError: function() {},
+ handleCompletion: function(aReason)
+ {
+ aCallback.apply(scope, args);
+ }
+ });
+ commit.finalize();
+}
+
+function synthesizeClickOnSelectedTreeCell(aTree, aOptions) {
+ let tbo = aTree.treeBoxObject;
+ if (tbo.view.selection.count != 1)
+ throw new Error("The test node should be successfully selected");
+ // Get selection rowID.
+ let min = {}, max = {};
+ tbo.view.selection.getRangeAt(0, min, max);
+ let rowID = min.value;
+ tbo.ensureRowIsVisible(rowID);
+ // Calculate the click coordinates.
+ var rect = tbo.getCoordsForCellItem(rowID, aTree.columns[0], "text");
+ var x = rect.x + rect.width / 2;
+ var y = rect.y + rect.height / 2;
+ // Simulate the click.
+ EventUtils.synthesizeMouse(aTree.body, x, y, aOptions || {},
+ aTree.ownerGlobal);
+}
+
+/**
+ * Asynchronously check a url is visited.
+ *
+ * @param aURI The URI.
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI) {
+ let deferred = Promise.defer();
+
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+ deferred.resolve(aIsVisited);
+ });
+
+ return deferred.promise;
+}
+
+function promiseBookmarksNotification(notification, conditionFn) {
+ info(`promiseBookmarksNotification: waiting for ${notification}`);
+ return new Promise((resolve) => {
+ let proxifiedObserver = new Proxy({}, {
+ get: (target, name) => {
+ if (name == "QueryInterface")
+ return XPCOMUtils.generateQI([ Ci.nsINavBookmarkObserver ]);
+ info(`promiseBookmarksNotification: got ${name} notification`);
+ if (name == notification)
+ return (...args) => {
+ if (conditionFn.apply(this, args)) {
+ PlacesUtils.bookmarks.removeObserver(proxifiedObserver, false);
+ executeSoon(resolve);
+ } else {
+ info(`promiseBookmarksNotification: skip cause condition doesn't apply to ${JSON.stringify(args)}`);
+ }
+ }
+ return () => {};
+ }
+ });
+ PlacesUtils.bookmarks.addObserver(proxifiedObserver, false);
+ });
+}
+
+function promiseHistoryNotification(notification, conditionFn) {
+ info(`Waiting for ${notification}`);
+ return new Promise((resolve) => {
+ let proxifiedObserver = new Proxy({}, {
+ get: (target, name) => {
+ if (name == "QueryInterface")
+ return XPCOMUtils.generateQI([ Ci.nsINavHistoryObserver ]);
+ if (name == notification)
+ return (...args) => {
+ if (conditionFn.apply(this, args)) {
+ PlacesUtils.history.removeObserver(proxifiedObserver, false);
+ executeSoon(resolve);
+ }
+ }
+ return () => {};
+ }
+ });
+ PlacesUtils.history.addObserver(proxifiedObserver, false);
+ });
+}
+
+/**
+ * Makes the specified toolbar visible or invisible and returns a Promise object
+ * that is resolved when the toolbar has completed any animations associated
+ * with hiding or showing the toolbar.
+ *
+ * Note that this code assumes that changes to a toolbar's visibility trigger
+ * a transition on the max-height property of the toolbar element.
+ * Changes to this styling could cause the returned Promise object to be
+ * resolved too early or not at all.
+ *
+ * @param aToolbar
+ * The toolbar to update.
+ * @param aVisible
+ * True to make the toolbar visible, false to make it hidden.
+ *
+ * @return {Promise}
+ * @resolves Any animation associated with updating the toolbar's visibility has
+ * finished.
+ * @rejects Never.
+ */
+function promiseSetToolbarVisibility(aToolbar, aVisible, aCallback) {
+ return new Promise((resolve, reject) => {
+ function listener(event) {
+ if (event.propertyName == "max-height") {
+ aToolbar.removeEventListener("transitionend", listener);
+ resolve();
+ }
+ }
+
+ let transitionProperties =
+ window.getComputedStyle(aToolbar).transitionProperty.split(", ");
+ if (isToolbarVisible(aToolbar) != aVisible &&
+ transitionProperties.some(
+ prop => prop == "max-height" || prop == "all"
+ )) {
+ // Just because max-height is a transitionable property doesn't mean
+ // a transition will be triggered, but it's more likely.
+ aToolbar.addEventListener("transitionend", listener);
+ setToolbarVisibility(aToolbar, aVisible);
+ return;
+ }
+
+ // No animation to wait for
+ setToolbarVisibility(aToolbar, aVisible);
+ resolve();
+ });
+}
+
+/**
+ * Helper function to determine if the given toolbar is in the visible
+ * state according to its autohide/collapsed attribute.
+ *
+ * @aToolbar The toolbar to query.
+ *
+ * @returns True if the relevant attribute on |aToolbar| indicates it is
+ * visible, false otherwise.
+ */
+function isToolbarVisible(aToolbar) {
+ let hidingAttribute = aToolbar.getAttribute("type") == "menubar"
+ ? "autohide"
+ : "collapsed";
+ let hidingValue = aToolbar.getAttribute(hidingAttribute).toLowerCase();
+ // Check for both collapsed="true" and collapsed="collapsed"
+ return hidingValue !== "true" && hidingValue !== hidingAttribute;
+}
+
+/**
+ * Executes a task after opening the bookmarks dialog, then cancels the dialog.
+ *
+ * @param autoCancel
+ * whether to automatically cancel the dialog at the end of the task
+ * @param openFn
+ * generator function causing the dialog to open
+ * @param task
+ * the task to execute once the dialog is open
+ */
+var withBookmarksDialog = Task.async(function* (autoCancel, openFn, taskFn) {
+ let closed = false;
+ let dialogPromise = new Promise(resolve => {
+ Services.ww.registerNotification(function winObserver(subject, topic, data) {
+ if (topic == "domwindowopened") {
+ let win = subject.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function load() {
+ win.removeEventListener("load", load);
+ ok(win.location.href.startsWith("chrome://browser/content/places/bookmarkProperties"),
+ "The bookmark properties dialog is open");
+ // This is needed for the overlay.
+ waitForFocus(() => {
+ resolve(win);
+ }, win);
+ });
+ } else if (topic == "domwindowclosed") {
+ Services.ww.unregisterNotification(winObserver);
+ closed = true;
+ }
+ });
+ });
+
+ info("withBookmarksDialog: opening the dialog");
+ // The dialog might be modal and could block our events loop, so executeSoon.
+ executeSoon(openFn);
+
+ info("withBookmarksDialog: waiting for the dialog");
+ let dialogWin = yield dialogPromise;
+
+ // Ensure overlay is loaded
+ info("waiting for the overlay to be loaded");
+ yield waitForCondition(() => dialogWin.gEditItemOverlay.initialized,
+ "EditItemOverlay should be initialized");
+
+ // Check the first textbox is focused.
+ let doc = dialogWin.document;
+ let elt = doc.querySelector("textbox:not([collapsed=true])");
+ if (elt) {
+ info("waiting for focus on the first textfield");
+ yield waitForCondition(() => doc.activeElement == elt.inputField,
+ "The first non collapsed textbox should have been focused");
+ }
+
+ info("withBookmarksDialog: executing the task");
+ try {
+ yield taskFn(dialogWin);
+ } finally {
+ if (!closed) {
+ if (!autoCancel) {
+ ok(false, "The test should have closed the dialog!");
+ }
+ info("withBookmarksDialog: canceling the dialog");
+ doc.documentElement.cancelDialog();
+ }
+ }
+});
+
+/**
+ * Opens the contextual menu on the element pointed by the given selector.
+ *
+ * @param selector
+ * Valid selector syntax
+ * @return Promise
+ * Returns a Promise that resolves once the context menu has been
+ * opened.
+ */
+var openContextMenuForContentSelector = Task.async(function* (browser, selector) {
+ info("wait for the context menu");
+ let contextPromise = BrowserTestUtils.waitForEvent(document.getElementById("contentAreaContextMenu"),
+ "popupshown");
+ yield ContentTask.spawn(browser, { selector }, function* (args) {
+ let doc = content.document;
+ let elt = doc.querySelector(args.selector)
+ dump(`openContextMenuForContentSelector: found ${elt}\n`);
+
+ /* Open context menu so chrome can access the element */
+ const domWindowUtils =
+ content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ let rect = elt.getBoundingClientRect();
+ let left = rect.left + rect.width / 2;
+ let top = rect.top + rect.height / 2;
+ domWindowUtils.sendMouseEvent("contextmenu", left, top, 2,
+ 1, 0, false, 0, 0, true);
+ });
+ yield contextPromise;
+});
+
+/**
+ * Waits for a specified condition to happen.
+ *
+ * @param conditionFn
+ * a Function or a generator function, returning a boolean for whether
+ * the condition is fulfilled.
+ * @param errorMsg
+ * Error message to use if the condition has not been satisfied after a
+ * meaningful amount of tries.
+ */
+var waitForCondition = Task.async(function* (conditionFn, errorMsg) {
+ for (let tries = 0; tries < 100; ++tries) {
+ if ((yield conditionFn()))
+ return;
+ yield new Promise(resolve => {
+ if (!waitForCondition._timers) {
+ waitForCondition._timers = new Set();
+ registerCleanupFunction(() => {
+ is(waitForCondition._timers.size, 0, "All the wait timers have been removed");
+ delete waitForCondition._timers;
+ });
+ }
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ waitForCondition._timers.add(timer);
+ timer.init(() => {
+ waitForCondition._timers.delete(timer);
+ resolve();
+ }, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ });
+ }
+ ok(false, errorMsg);
+});
+
+/**
+ * Fills a bookmarks dialog text field ensuring to cause expected edit events.
+ *
+ * @param id
+ * id of the text field
+ * @param text
+ * text to fill in
+ * @param win
+ * dialog window
+ * @param [optional] blur
+ * whether to blur at the end.
+ */
+function fillBookmarkTextField(id, text, win, blur = true) {
+ let elt = win.document.getElementById(id);
+ elt.focus();
+ elt.select();
+ for (let c of text.split("")) {
+ EventUtils.synthesizeKey(c, {}, win);
+ }
+ if (blur)
+ elt.blur();
+}
+
+/**
+ * Executes a task after opening the bookmarks or history sidebar. Takes care
+ * of closing the sidebar once done.
+ *
+ * @param type
+ * either "bookmarks" or "history".
+ * @param taskFn
+ * The task to execute once the sidebar is ready. Will get the Places
+ * tree view as input.
+ */
+var withSidebarTree = Task.async(function* (type, taskFn) {
+ let sidebar = document.getElementById("sidebar");
+ info("withSidebarTree: waiting sidebar load");
+ let sidebarLoadedPromise = new Promise(resolve => {
+ sidebar.addEventListener("load", function load() {
+ sidebar.removeEventListener("load", load, true);
+ resolve();
+ }, true);
+ });
+ let sidebarId = type == "bookmarks" ? "viewBookmarksSidebar"
+ : "viewHistorySidebar";
+ SidebarUI.show(sidebarId);
+ yield sidebarLoadedPromise;
+
+ let treeId = type == "bookmarks" ? "bookmarks-view"
+ : "historyTree";
+ let tree = sidebar.contentDocument.getElementById(treeId);
+
+ // Need to executeSoon since the tree is initialized on sidebar load.
+ info("withSidebarTree: executing the task");
+ try {
+ yield taskFn(tree);
+ } finally {
+ SidebarUI.hide();
+ }
+});
diff --git a/browser/components/places/tests/browser/keyword_form.html b/browser/components/places/tests/browser/keyword_form.html
new file mode 100644
index 000000000..a881c0d5a
--- /dev/null
+++ b/browser/components/places/tests/browser/keyword_form.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+
+<html lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=windows-1252">
+</head>
+<body>
+ <form id="form1" method="POST" action="keyword_form.html">
+ <input type="hidden" name="accenti" value="">
+ <input type="text" name="search">
+ </form>
+ <form id="form2" method="POST" action="keyword_form.html">
+ <input type="hidden" name="accenti" value="">
+ <input type="text" name="search">
+ </form>
+</body>
+</html>
diff --git a/browser/components/places/tests/browser/pageopeningwindow.html b/browser/components/places/tests/browser/pageopeningwindow.html
new file mode 100644
index 000000000..282f9c593
--- /dev/null
+++ b/browser/components/places/tests/browser/pageopeningwindow.html
@@ -0,0 +1,9 @@
+<meta charset="UTF-8">
+Hi, I was opened via a <script>document.write(location.search ?
+ "popup call from the opened window... uh oh, that shouldn't happen!" :
+ "bookmarklet, and I will open a new window myself.")</script><br>
+<script>
+ if (!location.search) {
+ open(location.href + "?donotopen=true", '_blank');
+ }
+</script>
diff --git a/browser/components/places/tests/browser/sidebarpanels_click_test_page.html b/browser/components/places/tests/browser/sidebarpanels_click_test_page.html
new file mode 100644
index 000000000..c73eaa540
--- /dev/null
+++ b/browser/components/places/tests/browser/sidebarpanels_click_test_page.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+ <title>browser_sidebarpanels_click.js test page</title>
+</head>
+<body onload="alert('test');">
+</body>
+</html>
diff --git a/browser/components/places/tests/chrome/.eslintrc.js b/browser/components/places/tests/chrome/.eslintrc.js
new file mode 100644
index 000000000..8c0f4f574
--- /dev/null
+++ b/browser/components/places/tests/chrome/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/browser/components/places/tests/chrome/chrome.ini b/browser/components/places/tests/chrome/chrome.ini
new file mode 100644
index 000000000..d7b4a55c8
--- /dev/null
+++ b/browser/components/places/tests/chrome/chrome.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+support-files = head.js
+
+[test_0_bug510634.xul]
+[test_0_multiple_left_pane.xul]
+[test_bug1163447_selectItems_through_shortcut.xul]
+[test_bug427633_no_newfolder_if_noip.xul]
+[test_bug485100-change-case-loses-tag.xul]
+[test_bug549192.xul]
+[test_bug549491.xul]
+[test_bug631374_tags_selector_scroll.xul]
+[test_editBookmarkOverlay_keywords.xul]
+[test_editBookmarkOverlay_tags_liveUpdate.xul]
+[test_selectItems_on_nested_tree.xul]
+[test_treeview_date.xul]
diff --git a/browser/components/places/tests/chrome/head.js b/browser/components/places/tests/chrome/head.js
new file mode 100644
index 000000000..26b97f6d7
--- /dev/null
+++ b/browser/components/places/tests/chrome/head.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
diff --git a/browser/components/places/tests/chrome/test_0_bug510634.xul b/browser/components/places/tests/chrome/test_0_bug510634.xul
new file mode 100644
index 000000000..86e102180
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_0_bug510634.xul
@@ -0,0 +1,99 @@
+<?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://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="510634: Wrong icons on bookmarks sidebar"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <tree id="tree"
+ type="places"
+ flex="1">
+ <treecols>
+ <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ /**
+ * Bug 510634 - Wrong icons on bookmarks sidebar
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=510634
+ *
+ * Ensures that properties for special queries are set on their tree nodes,
+ * even if PlacesUIUtils.leftPaneFolderId was not initialized.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ // We need to cache and restore this getter in order to simulate
+ // Bug 510634
+ let cachedLeftPaneFolderIdGetter =
+ PlacesUIUtils.__lookupGetter__("leftPaneFolderId");
+ // Must also cache and restore this getter as it is affected by
+ // leftPaneFolderId, from bug 564900.
+ let cachedAllBookmarksFolderIdGetter =
+ PlacesUIUtils.__lookupGetter__("allBookmarksFolderId");
+
+ let leftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
+
+ // restore the getter
+ PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter);
+
+ // Setup the places tree contents.
+ let tree = document.getElementById("tree");
+ tree.place = "place:queryType=1&folder=" + leftPaneFolderId;
+
+ // The query-property is set on the title column for each row.
+ let titleColumn = tree.treeBoxObject.columns.getColumnAt(0);
+
+ // Open All Bookmarks
+ tree.selectItems([PlacesUIUtils.leftPaneQueries["AllBookmarks"]]);
+ PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
+ is(PlacesUIUtils.allBookmarksFolderId, tree.selectedNode.itemId,
+ "Opened All Bookmarks");
+
+ ["History", "Downloads", "Tags", "AllBookmarks", "BookmarksToolbar",
+ "BookmarksMenu", "UnfiledBookmarks"].forEach(
+ function(aQueryName, aRow) {
+ let found = false;
+ for (let i = 0; i < tree.view.rowCount && !found; i++) {
+ rowProperties = tree.view.getCellProperties(i, titleColumn).split(" ");
+ found = rowProperties.includes("OrganizerQuery_" + aQueryName);
+ }
+ ok(found, "OrganizerQuery_" + aQueryName + " is set");
+ }
+ );
+
+ // Close the root node
+ tree.result.root.containerOpen = false;
+
+ // Restore the getters for the next test.
+ PlacesUIUtils.__defineGetter__("leftPaneFolderId", cachedLeftPaneFolderIdGetter);
+ PlacesUIUtils.__defineGetter__("allBookmarksFolderId",
+ cachedAllBookmarksFolderIdGetter);
+
+ SimpleTest.finish();
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/browser/components/places/tests/chrome/test_0_multiple_left_pane.xul b/browser/components/places/tests/chrome/test_0_multiple_left_pane.xul
new file mode 100644
index 000000000..09a4d2054
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_0_multiple_left_pane.xul
@@ -0,0 +1,85 @@
+<?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/. -->
+
+<!-- Bug 466422:
+ - Check that we replace the left pane with a correct one if it gets corrupted
+ - and we end up having more than one. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Test handling of multiple left pane folders"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+ </body>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ // Sanity checks.
+ ok(PlacesUtils, "PlacesUtils is running in chrome context");
+ ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
+ ok(PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION > 0,
+ "Left pane version in chrome context, " +
+ "current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION );
+
+ let fakeLeftPanes = [];
+ // We need 2 left pane folders to simulate a corrupt profile.
+ do {
+ let leftPaneItems = PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+
+ // Create a fake left pane folder.
+ let folder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.rootGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER
+ });
+
+ let fakeLeftPaneRoot = yield PlacesUtils.promiseItemId(folder.guid);
+ PlacesUtils.annotations.setItemAnnotation(fakeLeftPaneRoot, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
+ PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ fakeLeftPanes.push(folder.guid);
+ } while (fakeLeftPanes.length < 2);
+
+ // Initialize the left pane queries.
+ PlacesUIUtils.leftPaneFolderId;
+
+ // Check left pane.
+ ok(PlacesUIUtils.leftPaneFolderId > 0,
+ "Left pane folder correctly created");
+ let leftPaneItems = PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ is(leftPaneItems.length, 1,
+ "We correctly have only 1 left pane folder");
+
+ // Check that all old left pane items have been removed.
+ for (let guid of fakeLeftPanes) {
+ ok(!(yield PlacesUtils.bookmarks.fetch({guid})), "This folder should have been removed");
+ }
+ }).then(() => SimpleTest.finish());
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/browser/components/places/tests/chrome/test_bug1163447_selectItems_through_shortcut.xul b/browser/components/places/tests/chrome/test_bug1163447_selectItems_through_shortcut.xul
new file mode 100644
index 000000000..8e3a99533
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_bug1163447_selectItems_through_shortcut.xul
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="1163447: selectItems in Places no longer selects items within Toolbar or Sidebar folders"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript" src="head.js" />
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <tree id="tree"
+ type="places"
+ flex="1">
+ <treecols>
+ <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <script type="application/javascript"><![CDATA[
+
+ /**
+ * Bug 1163447: places-tree should be able to select an item within the toolbar, and
+ * unfiled bookmarks. Yet not follow recursive folder-shortcuts infinitely.
+ */
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ let bmu = PlacesUtils.bookmarks;
+
+ yield bmu.insert({
+ parentGuid: bmu.toolbarGuid,
+ index: bmu.DEFAULT_INDEX,
+ type: bmu.TYPE_BOOKMARK,
+ url: "place:folder=TOOLBAR",
+ title: "shortcut to self - causing infinite recursion if not handled properly"
+ });
+
+ yield bmu.insert({
+ parentGuid: bmu.toolbarGuid,
+ index: bmu.DEFAULT_INDEX,
+ type: bmu.TYPE_BOOKMARK,
+ url: "place:folder=UNFILED_BOOKMARKS",
+ title: "shortcut to unfiled, within toolbar"
+ });
+
+ let folder = yield bmu.insert({
+ parentGuid: bmu.unfiledGuid,
+ index: bmu.DEFAULT_INDEX,
+ type: bmu.TYPE_FOLDER,
+ title: "folder within unfiled"
+ });
+
+ // Setup the places tree contents.
+ let tree = document.getElementById("tree");
+ tree.place = "place:folder=TOOLBAR";
+
+ // Select the folder via the selectItems(itemId) API being tested
+ let itemId = yield PlacesUtils.promiseItemId(folder.guid);
+ tree.selectItems([itemId]);
+
+ is(tree.selectedNode && tree.selectedNode.itemId, itemId, "The node was selected through the shortcut");
+
+ // Cleanup
+ yield bmu.eraseEverything();
+
+ }).catch(err => {
+ ok(false, `Uncaught error: ${err}`);
+ }).then(SimpleTest.finish);
+ }
+ ]]></script>
+</window>
diff --git a/browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul b/browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul
new file mode 100644
index 000000000..b659b2b46
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul
@@ -0,0 +1,91 @@
+<?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://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+ %editBookmarkOverlayDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Bug 427633 - Disable creating a New Folder in the bookmarks dialogs if insertionPoint is invalid"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <vbox id="editBookmarkPanelContent"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ /**
+ * Bug 427633 - Disable creating a New Folder in the bookmarks dialogs if
+ * insertionPoint is invalid.
+ */
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ // Add a bookmark.
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "http://www.example.com/",
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ title: "mozilla"
+ });
+
+ // Init panel.
+ ok(gEditItemOverlay, "gEditItemOverlay is in context");
+ let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
+ gEditItemOverlay.initPanel({ node });
+ ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized");
+
+ let tree = gEditItemOverlay._element("folderTree");
+ yield openFolderTree(tree);
+
+ tree.view.selection.clearSelection();
+ ok(document.getElementById("editBMPanel_newFolderButton").disabled,
+ "New folder button is disabled if there's no selection");
+
+ // Cleanup.
+ yield PlacesUtils.bookmarks.remove(bm.guid);
+ }).then(() => SimpleTest.finish());
+ }
+
+ function openFolderTree(tree) {
+ return new Promise(resolve => {
+ tree.addEventListener("DOMAttrModified", function onAttrModified(event) {
+ if (event.attrName == "place") {
+ tree.removeEventListener("DOMAttrModified", onAttrModified);
+ resolve();
+ }
+ });
+
+ // Open the folder tree.
+ document.getElementById("editBMPanel_foldersExpander").doCommand();
+ });
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul b/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
new file mode 100644
index 000000000..afad950cb
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
@@ -0,0 +1,83 @@
+<?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://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+ %editBookmarkOverlayDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <vbox id="editBookmarkPanelContent"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ let testTag = "foo";
+ let testTagUpper = "Foo";
+ let testURI = Services.io.newURI("http://www.example.com/", null, null);
+
+ // Add a bookmark.
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ title: "mozilla",
+ url: testURI
+ });
+
+ // Init panel
+ ok(gEditItemOverlay, "gEditItemOverlay is in context");
+ let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
+ gEditItemOverlay.initPanel({ node });
+
+ // add a tag
+ document.getElementById("editBMPanel_tagsField").value = testTag;
+ gEditItemOverlay.onTagsFieldChange();
+
+ // test that the tag has been added in the backend
+ is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTag, "tags match");
+
+ // change the tag
+ document.getElementById("editBMPanel_tagsField").value = testTagUpper;
+ gEditItemOverlay.onTagsFieldChange();
+
+ // test that the tag has been added in the backend
+ is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTagUpper, "tags match");
+
+ // Cleanup.
+ PlacesUtils.tagging.untagURI(testURI, [testTag]);
+ yield PlacesUtils.bookmarks.remove(bm.guid);
+ }).then(() => SimpleTest.finish());
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/browser/components/places/tests/chrome/test_bug549192.xul b/browser/components/places/tests/chrome/test_bug549192.xul
new file mode 100644
index 000000000..4e6a89bb1
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_bug549192.xul
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="549192: History view not updated after deleting entry"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript" src="head.js" />
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <tree id="tree"
+ type="places"
+ flatList="true"
+ flex="1">
+ <treecols>
+ <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <script type="application/javascript"><![CDATA[
+ /**
+ * Bug 874407
+ * Ensures that history views are updated properly after visits.
+ * Bug 549192
+ * Ensures that history views are updated after deleting entries.
+ */
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ yield PlacesTestUtils.clearHistory();
+
+ // Add some visits.
+ let timeInMicroseconds = PlacesUtils.toPRTime(Date.now() - 10000);
+
+ function newTimeInMicroseconds() {
+ timeInMicroseconds = timeInMicroseconds + 1000;
+ return timeInMicroseconds;
+ }
+
+ let vtime = Date.now() * 1000;
+ const ttype = PlacesUtils.history.TRANSITION_TYPED;
+ let places =
+ [{ uri: Services.io.newURI("http://example.tld/", null, null),
+ visitDate: newTimeInMicroseconds(), transition: ttype },
+ { uri: Services.io.newURI("http://example2.tld/", null, null),
+ visitDate: newTimeInMicroseconds(), transition: ttype },
+ { uri: Services.io.newURI("http://example3.tld/", null, null),
+ visitDate: newTimeInMicroseconds(), transition: ttype }];
+
+ yield PlacesTestUtils.addVisits(places);
+
+ // Make a history query.
+ let query = PlacesUtils.history.getNewQuery();
+ let opts = PlacesUtils.history.getNewQueryOptions();
+ opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
+ let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts);
+
+ // Setup the places tree contents.
+ var tree = document.getElementById("tree");
+ tree.place = queryURI;
+
+ // loop through the rows and check them.
+ let treeView = tree.view;
+ let selection = treeView.selection;
+ let rc = treeView.rowCount;
+
+ for (let i = 0; i < rc; i++) {
+ selection.select(i);
+ let node = tree.selectedNode;
+ is(node.uri, places[rc - i - 1].uri.spec,
+ "Found expected node at position " + i + ".");
+ }
+
+ is(rc, 3, "Found expected number of rows.");
+
+ // First check live-update of the view when adding visits.
+ places.forEach(place => place.visitDate = newTimeInMicroseconds());
+ yield PlacesTestUtils.addVisits(places);
+
+ for (let i = 0; i < rc; i++) {
+ selection.select(i);
+ let node = tree.selectedNode;
+ is(node.uri, places[rc - i - 1].uri.spec,
+ "Found expected node at position " + i + ".");
+ }
+
+ // Now remove the pages and verify live-update again.
+ for (let i = 0; i < rc; i++) {
+ selection.select(0);
+ let node = tree.selectedNode;
+ tree.controller.remove("Removing page");
+ ok(treeView.treeIndexForNode(node) == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE,
+ node.uri + " removed.");
+ ok(treeView.rowCount == rc - i - 1, "Rows count decreased");
+ }
+
+ // Cleanup.
+ yield PlacesTestUtils.clearHistory();
+ }).then(() => SimpleTest.finish());
+ }
+ ]]></script>
+</window>
diff --git a/browser/components/places/tests/chrome/test_bug549491.xul b/browser/components/places/tests/chrome/test_bug549491.xul
new file mode 100644
index 000000000..5ec7a765a
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_bug549491.xul
@@ -0,0 +1,78 @@
+<?xml version="1.0"?>
+
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="549491: 'The root node is never visible' exception when details of the root node are modified "
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript" src="head.js" />
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <tree id="tree"
+ type="places"
+ flatList="true"
+ flex="1">
+ <treecols>
+ <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="Date" anonid="date" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <script type="application/javascript"><![CDATA[
+ /**
+ * Bug 549491
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=549491
+ *
+ * Ensures that changing the details of places tree's root-node doesn't
+ * throw.
+ */
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ yield PlacesTestUtils.clearHistory();
+
+ yield PlacesTestUtils.addVisits({
+ uri: Services.io.newURI("http://example.tld/", null, null),
+ transition: PlacesUtils.history.TRANSITION_TYPED
+ });
+
+ // Make a history query.
+ let query = PlacesUtils.history.getNewQuery();
+ let opts = PlacesUtils.history.getNewQueryOptions();
+ let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts);
+
+ // Setup the places tree contents.
+ let tree = document.getElementById("tree");
+ tree.place = queryURI;
+
+ let rootNode = tree.result.root;
+ let obs = tree.view.QueryInterface(Ci.nsINavHistoryResultObserver);
+ obs.nodeHistoryDetailsChanged(rootNode, rootNode.time, rootNode.accessCount);
+ obs.nodeTitleChanged(rootNode, rootNode.title);
+ ok(true, "No exceptions thrown");
+
+ // Cleanup.
+ yield PlacesTestUtils.clearHistory();
+ }).then(SimpleTest.finish);
+ }
+ ]]></script>
+</window>
diff --git a/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul b/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
new file mode 100644
index 000000000..b1d73017f
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
@@ -0,0 +1,170 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+ %editBookmarkOverlayDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Bug 631374 - Editing tags in the selector scrolls up the listbox"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <vbox id="editBookmarkPanelContent"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ /**
+ * This test checks that editing tags doesn't scroll the tags selector
+ * listbox to wrong positions.
+ */
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ let bs = PlacesUtils.bookmarks;
+
+ let tags = ["a", "b", "c", "d", "e", "f", "g",
+ "h", "i", "l", "m", "n", "o", "p"];
+
+ // Add a bookmark and tag it.
+ let uri1 = Services.io.newURI("http://www1.mozilla.org/", null, null);
+ let bm1 = yield bs.insert({
+ parentGuid: bs.toolbarGuid,
+ index: bs.DEFAULT_INDEX,
+ type: bs.TYPE_BOOKMARK,
+ title: "mozilla",
+ url: uri1.spec
+ });
+ PlacesUtils.tagging.tagURI(uri1, tags);
+
+ // Add a second bookmark so that tags won't disappear when unchecked.
+ let uri2 = Services.io.newURI("http://www2.mozilla.org/", null, null);
+ let bm2 = yield bs.insert({
+ parentGuid: bs.toolbarGuid,
+ index: bs.DEFAULT_INDEX,
+ type: bs.TYPE_BOOKMARK,
+ title: "mozilla",
+ url: uri2.spec
+ });
+ PlacesUtils.tagging.tagURI(uri2, tags);
+
+ // Init panel.
+ ok(gEditItemOverlay, "gEditItemOverlay is in context");
+ let node1 = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm1);
+ gEditItemOverlay.initPanel({ node: node1 });
+ ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized");
+
+ yield openTagSelector();
+ let tagsSelector = document.getElementById("editBMPanel_tagsSelector");
+
+ // Go by two so there is some untouched tag in the middle.
+ for (let i = 8; i < tags.length; i += 2) {
+ tagsSelector.selectedIndex = i;
+ let listItem = tagsSelector.selectedItem;
+ isnot(listItem, null, "Valid listItem found");
+
+ tagsSelector.ensureElementIsVisible(listItem);
+ let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
+
+ ok(listItem.checked, "Item is checked " + i);
+ let selectedTag = listItem.label;
+
+ // Uncheck the tag.
+ listItem.checked = false;
+ is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
+ "Scroll position did not change");
+
+ // The listbox is rebuilt, so we have to get the new element.
+ let newItem = tagsSelector.selectedItem;
+ isnot(newItem, null, "Valid new listItem found");
+ ok(!newItem.checked, "New listItem is unchecked " + i);
+ is(newItem.label, selectedTag, "Correct tag is still selected");
+
+ // Check the tag.
+ newItem.checked = true;
+ is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
+ "Scroll position did not change");
+ }
+
+ // Remove the second bookmark, then nuke some of the tags.
+ yield bs.remove(bm2.guid);
+
+ // Doing this backwords tests more interesting paths.
+ for (let i = tags.length - 1; i >= 0 ; i -= 2) {
+ tagsSelector.selectedIndex = i;
+ let listItem = tagsSelector.selectedItem;
+ isnot(listItem, null, "Valid listItem found");
+
+ tagsSelector.ensureElementIsVisible(listItem);
+ let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()];
+
+ ok(listItem.checked, "Item is checked " + i);
+ let selectedTag = listItem.label;
+
+ // Uncheck the tag.
+ listItem.checked = false;
+
+ // Ensure the first visible tag is still visible in the list.
+ let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
+ let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() -1;
+ let expectedTagIndex = tags.indexOf(firstVisibleTag);
+ ok(expectedTagIndex >= firstVisibleIndex &&
+ expectedTagIndex <= lastVisibleIndex,
+ "Scroll position is correct");
+
+ // The listbox is rebuilt, so we have to get the new element.
+ let newItem = tagsSelector.selectedItem;
+ isnot(newItem, null, "Valid new listItem found");
+ ok(newItem.checked, "New listItem is checked " + i);
+ is(tagsSelector.selectedItem.label,
+ tags[Math.min(i + 1, tags.length - 2)],
+ "The next tag is now selected");
+ }
+
+ // Cleanup.
+ yield bs.remove(bm1.guid);
+ }).then(SimpleTest.finish).catch(alert);
+ }
+
+ function openTagSelector() {
+ // Wait for the tags selector to be open.
+ let promise = new Promise(resolve => {
+ let row = document.getElementById("editBMPanel_tagsSelectorRow");
+ row.addEventListener("DOMAttrModified", function onAttrModified() {
+ row.removeEventListener("DOMAttrModified", onAttrModified);
+ resolve();
+ });
+ });
+
+ // Open the tags selector.
+ document.getElementById("editBMPanel_tagsSelectorExpander").doCommand();
+
+ return promise;
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul b/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul
new file mode 100644
index 000000000..f553d018b
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+ %editBookmarkOverlayDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Bug 1343256 - Bookmark keywords disappear from one bookmark when adding a keyword to another bookmark"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <vbox id="editBookmarkPanelContent"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+ Task.spawn(test.bind(this))
+ .catch(ex => ok(false, ex))
+ .then(() => PlacesUtils.bookmarks.eraseEverything())
+ .then(SimpleTest.finish);
+ }
+
+ function promiseOnItemChanged() {
+ return new Promise(resolve => {
+ PlacesUtils.bookmarks.addObserver({
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onItemAdded() {},
+ onItemRemoved() {},
+ onItemVisited() {},
+ onItemMoved() {},
+ onItemChanged(id, property, isAnno, value) {
+ PlacesUtils.bookmarks.removeObserver(this);
+ resolve({ property, value });
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
+ }, false);
+ });
+ }
+
+ function* test() {
+ ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context");
+ let keywordField = document.getElementById("editBMPanel_keywordField");
+
+ for (let i = 0; i < 2; ++i) {
+ let bm = yield PlacesUtils.bookmarks.insert({
+ url: `http://www.test${i}.me/`,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ info(`Init panel on bookmark #${i+1}`);
+ let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
+ gEditItemOverlay.initPanel({ node });
+ is(document.getElementById("editBMPanel_keywordField").value, "",
+ "The keyword field should be empty");
+ info("Add a keyword to the bookmark");
+ let promise = promiseOnItemChanged();
+ keywordField.focus();
+ keywordField.value = "kw";
+ synthesizeKey(i.toString(), {});
+ synthesizeKey("VK_RETURN", {});
+ keywordField.blur();
+ let {property, value} = yield promise;
+ is(property, "keyword", "The keyword should have been changed");
+ is(value, `kw${i}`, "The new keyword value is correct");
+ }
+
+ for (let i = 0; i < 2; ++i) {
+ let entry = yield PlacesUtils.keywords.fetch({ url: `http://www.test${i}.me/` });
+ is(entry.keyword, `kw${i}`, `The keyword for http://www.test${i}.me/ is correct`);
+ }
+ };
+ ]]>
+ </script>
+
+</window>
diff --git a/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul b/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
new file mode 100644
index 000000000..1b1cc6473
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
@@ -0,0 +1,204 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+ %editBookmarkOverlayDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <vbox id="editBookmarkPanelContent"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ function checkTagsSelector(aAvailableTags, aCheckedTags) {
+ is(PlacesUtils.tagging.allTags.length, aAvailableTags.length,
+ "tagging service is in sync.");
+ let tagsSelector = document.getElementById("editBMPanel_tagsSelector");
+ let children = tagsSelector.childNodes;
+ is(children.length, aAvailableTags.length,
+ "Found expected number of tags in the tags selector");
+
+ Array.prototype.forEach.call(children, function (aChild) {
+ let tag = aChild.getAttribute("label");
+ ok(true, "Found tag '" + tag + "' in the selector");
+ ok(aAvailableTags.includes(tag), "Found expected tag");
+ let checked = aChild.getAttribute("checked") == "true";
+ is(checked, aCheckedTags.includes(tag),
+ "Tag is correctly marked");
+ });
+ }
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ const TEST_URI = Services.io.newURI("http://www.test.me/", null, null);
+ const TEST_URI2 = Services.io.newURI("http://www.test.again.me/", null, null);
+ const TEST_TAG = "test-tag";
+
+ ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context");
+
+ // Open the tags selector.
+ document.getElementById("editBMPanel_tagsSelectorRow").collapsed = false;
+
+ // Add a bookmark.
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: TEST_URI.spec,
+ title: "test.me"
+ });
+
+ // Init panel.
+ let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
+ gEditItemOverlay.initPanel({ node });
+
+ // Add a tag.
+ PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+ "Correctly added tag to a single bookmark");
+ is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+ "Editing a single bookmark shows the added tag");
+ checkTagsSelector([TEST_TAG], [TEST_TAG]);
+
+ // Remove tag.
+ PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+ "The tag has been removed");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing a single bookmark should not show any tag");
+ checkTagsSelector([], []);
+
+ // Add a second bookmark.
+ let bm2 = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ title: "test.again.me",
+ url: TEST_URI2.spec
+ });
+
+ // Init panel with multiple uris.
+ gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
+
+ // Add a tag to the first uri.
+ PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+ "Correctly added a tag to the first bookmark.");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing multiple bookmarks without matching tags should not show any tag.");
+ checkTagsSelector([TEST_TAG], []);
+
+ // Add a tag to the second uri.
+ PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
+ "Correctly added a tag to the second bookmark.");
+ is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+ "Editing multiple bookmarks should show matching tags.");
+ checkTagsSelector([TEST_TAG], [TEST_TAG]);
+
+ // Remove tag from the first bookmark.
+ PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+ "Correctly removed tag from the first bookmark.");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing multiple bookmarks without matching tags should not show any tag.");
+ checkTagsSelector([TEST_TAG], []);
+
+ // Remove tag from the second bookmark.
+ PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
+ "Correctly removed tag from the second bookmark.");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing multiple bookmarks without matching tags should not show any tag.");
+ checkTagsSelector([], []);
+
+ // Init panel with a nsIURI entry.
+ gEditItemOverlay.initPanel({ uris: [TEST_URI] });
+
+ // Add a tag.
+ PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+ "Correctly added tag to the first entry.");
+ is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+ "Editing a single nsIURI entry shows the added tag");
+ checkTagsSelector([TEST_TAG], [TEST_TAG]);
+
+ // Remove tag.
+ PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+ "Correctly removed tag from the nsIURI entry.");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing a single nsIURI entry should not show any tag");
+ checkTagsSelector([], []);
+
+ // Init panel with multiple nsIURI entries.
+ gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
+
+ // Add a tag to the first entry.
+ PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+ "Tag correctly added.");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing multiple nsIURIs without matching tags should not show any tag.");
+ checkTagsSelector([TEST_TAG], []);
+
+ // Add a tag to the second entry.
+ PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
+ "Tag correctly added.");
+ is(document.getElementById("editBMPanel_tagsField").value, TEST_TAG,
+ "Editing multiple nsIURIs should show matching tags");
+ checkTagsSelector([TEST_TAG], [TEST_TAG]);
+
+ // Remove tag from the first entry.
+ PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+ "Correctly removed tag from the first entry.");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing multiple nsIURIs without matching tags should not show any tag.");
+ checkTagsSelector([TEST_TAG], []);
+
+ // Remove tag from the second entry.
+ PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
+ is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
+ "Correctly removed tag from the second entry.");
+ is(document.getElementById("editBMPanel_tagsField").value, "",
+ "Editing multiple nsIURIs without matching tags should not show any tag.");
+ checkTagsSelector([], []);
+
+ // Cleanup.
+ yield PlacesUtils.bookmarks.remove(bm.guid);
+ yield PlacesUtils.bookmarks.remove(bm2.guid);
+ }).then(SimpleTest.finish);
+ }
+ ]]>
+ </script>
+
+</window>
diff --git a/browser/components/places/tests/chrome/test_selectItems_on_nested_tree.xul b/browser/components/places/tests/chrome/test_selectItems_on_nested_tree.xul
new file mode 100644
index 000000000..032c7a258
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_selectItems_on_nested_tree.xul
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/licenses/publicdomain/
+ -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="549192: History view not updated after deleting entry"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript" src="head.js" />
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <tree id="tree"
+ type="places"
+ flex="1">
+ <treecols>
+ <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <script type="application/javascript"><![CDATA[
+ /**
+ * Ensure that selectItems doesn't recurse infinitely in nested trees.
+ */
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "place:folder=UNFILED_BOOKMARKS",
+ title: "shortcut"
+ });
+
+ yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "place:folder=UNFILED_BOOKMARKS&maxResults=10",
+ title: "query"
+ });
+
+ let folder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "folder"
+ });
+
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://www.mozilla.org/",
+ title: "bookmark"
+ });
+
+ // Setup the places tree contents.
+ let tree = document.getElementById("tree");
+ tree.place = "place:folder=UNFILED_BOOKMARKS";
+
+ // Select the last bookmark.
+ let itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ tree.selectItems([itemId]);
+ is (tree.selectedNode.itemId, itemId, "The right node was selected");
+ }).then(SimpleTest.finish);
+ }
+ ]]></script>
+</window>
diff --git a/browser/components/places/tests/chrome/test_treeview_date.xul b/browser/components/places/tests/chrome/test_treeview_date.xul
new file mode 100644
index 000000000..559232611
--- /dev/null
+++ b/browser/components/places/tests/chrome/test_treeview_date.xul
@@ -0,0 +1,159 @@
+<?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://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="435322: Places tree view's formatting"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript" src="head.js" />
+
+ <body xmlns="http://www.w3.org/1999/xhtml" />
+
+ <tree id="tree"
+ type="places"
+ flatList="true"
+ flex="1">
+ <treecols>
+ <treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="Tags" id="tags" anonid="tags" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="Url" id="url" anonid="url" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="Visit Date" id="date" anonid="date" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="Visit Count" id="visitCount" anonid="visitCount" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ /**
+ * Bug 435322
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=435322
+ *
+ * Ensures that date in places treeviews is correctly formatted.
+ */
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+
+ function uri(spec) {
+ return Services.io.newURI(spec, null, null);
+ }
+
+ Task.spawn(function* () {
+ yield PlacesTestUtils.clearHistory();
+
+ let midnight = new Date();
+ midnight.setHours(0);
+ midnight.setMinutes(0);
+ midnight.setSeconds(0);
+ midnight.setMilliseconds(0);
+
+ // Add a visit 1ms before midnight, a visit at midnight, and
+ // a visit 1ms after midnight.
+ yield PlacesTestUtils.addVisits([
+ {uri: uri("http://before.midnight.com/"),
+ visitDate: (midnight.getTime() - 1) * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED},
+ {uri: uri("http://at.midnight.com/"),
+ visitDate: (midnight.getTime()) * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED},
+ {uri: uri("http://after.midnight.com/"),
+ visitDate: (midnight.getTime() + 1) * 1000,
+ transition: PlacesUtils.history.TRANSITION_TYPED}
+ ]);
+
+ // add a bookmark to the midnight visit
+ let bm = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ url: "http://at.midnight.com/",
+ title: "A bookmark at midnight",
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK
+ });
+
+ // Make a history query.
+ let query = PlacesUtils.history.getNewQuery();
+ let opts = PlacesUtils.history.getNewQueryOptions();
+ let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts);
+
+ // Setup the places tree contents.
+ let tree = document.getElementById("tree");
+ tree.place = queryURI;
+
+ // loop through the rows and check formatting
+ let treeView = tree.view;
+ let rc = treeView.rowCount;
+ ok(rc >= 3, "Rows found");
+ let columns = tree.columns;
+ ok(columns.count > 0, "Columns found");
+ const locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ for (let r = 0; r < rc; r++) {
+ let node = treeView.nodeForTreeIndex(r);
+ ok(node, "Places node found");
+ for (let ci = 0; ci < columns.count; ci++) {
+ let c = columns.getColumnAt(ci);
+ let text = treeView.getCellText(r, c);
+ switch (c.element.getAttribute("anonid")) {
+ case "title":
+ // The title can differ, we did not set any title so we would
+ // expect null, but in such a case the view will generate a title
+ // through PlacesUIUtils.getBestTitle.
+ if (node.title)
+ is(text, node.title, "Title is correct");
+ break;
+ case "url":
+ is(text, node.uri, "Uri is correct");
+ break;
+ case "date":
+ let timeObj = new Date(node.time / 1000);
+ // Default is short date format.
+ let dtOptions = { year: 'numeric', month: 'numeric', day: 'numeric',
+ hour: 'numeric', minute: 'numeric' };
+ // For today's visits we don't show date portion.
+ if (node.uri == "http://at.midnight.com/" ||
+ node.uri == "http://after.midnight.com/") {
+ dtOptions = { hour: 'numeric', minute: 'numeric' };
+ } else if (node.uri != "http://before.midnight.com/") {
+ // Avoid to test spurious uris, due to how the test works
+ // a redirecting uri could be put in the tree while we test.
+ break;
+ }
+ let timeStr = timeObj.toLocaleString(locale, dtOptions);
+
+ is(text, timeStr, "Date format is correct");
+ break;
+ case "visitCount":
+ is(text, 1, "Visit count is correct");
+ break;
+ }
+ }
+ }
+
+ // Cleanup.
+ yield PlacesUtils.bookmarks.remove(bm.guid);
+ yield PlacesTestUtils.clearHistory();
+ }).then(SimpleTest.finish);
+ }
+ ]]>
+ </script>
+</window>
diff --git a/browser/components/places/tests/unit/.eslintrc.js b/browser/components/places/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/places/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/places/tests/unit/bookmarks.glue.html b/browser/components/places/tests/unit/bookmarks.glue.html
new file mode 100644
index 000000000..07b22e9b3
--- /dev/null
+++ b/browser/components/places/tests/unit/bookmarks.glue.html
@@ -0,0 +1,16 @@
+<!DOCTYPE NETSCAPE-Bookmark-file-1>
+<!-- This is an automatically generated file.
+ It will be read and overwritten.
+ DO NOT EDIT! -->
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
+<TITLE>Bookmarks</TITLE>
+<H1>Bookmarks Menu</H1>
+
+<DL><p>
+ <DT><A HREF="http://example.com/" ADD_DATE="1233157972" LAST_MODIFIED="1233157984">example</A>
+ <DT><H3 ADD_DATE="1233157910" LAST_MODIFIED="1233157972" PERSONAL_TOOLBAR_FOLDER="true">Bookmarks Toolbar</H3>
+<DD>Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar
+ <DL><p>
+ <DT><A HREF="http://example.com/" ADD_DATE="1233157972" LAST_MODIFIED="1233157984">example</A>
+ </DL><p>
+</DL><p>
diff --git a/browser/components/places/tests/unit/bookmarks.glue.json b/browser/components/places/tests/unit/bookmarks.glue.json
new file mode 100644
index 000000000..95900e176
--- /dev/null
+++ b/browser/components/places/tests/unit/bookmarks.glue.json
@@ -0,0 +1 @@
+{"title":"","id":1,"dateAdded":1233157910552624,"lastModified":1233157955206833,"type":"text/x-moz-place-container","root":"placesRoot","children":[{"title":"Bookmarks Menu","id":2,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157993171424,"type":"text/x-moz-place-container","root":"bookmarksMenuFolder","children":[{"title":"examplejson","id":27,"parent":2,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":1,"title":"Bookmarks Toolbar","id":3,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157972101126,"annos":[{"name":"bookmarkProperties/description","flags":0,"expires":4,"mimeType":null,"type":3,"value":"Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar"}],"type":"text/x-moz-place-container","root":"toolbarFolder","children":[{"title":"examplejson","id":26,"parent":3,"dateAdded":1233157972101126,"lastModified":1233157984999673,"type":"text/x-moz-place","uri":"http://example.com/"}]},{"index":2,"title":"Tags","id":4,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157910582667,"type":"text/x-moz-place-container","root":"tagsFolder","children":[]},{"index":3,"title":"Other Bookmarks","id":5,"parent":1,"dateAdded":1233157910552624,"lastModified":1233157911033315,"type":"text/x-moz-place-container","root":"unfiledBookmarksFolder","children":[]}]}
diff --git a/browser/components/places/tests/unit/corruptDB.sqlite b/browser/components/places/tests/unit/corruptDB.sqlite
new file mode 100644
index 000000000..b234246ca
--- /dev/null
+++ b/browser/components/places/tests/unit/corruptDB.sqlite
Binary files differ
diff --git a/browser/components/places/tests/unit/distribution.ini b/browser/components/places/tests/unit/distribution.ini
new file mode 100644
index 000000000..93e73cb5c
--- /dev/null
+++ b/browser/components/places/tests/unit/distribution.ini
@@ -0,0 +1,27 @@
+# Distribution Configuration File
+# Bug 516444 demo
+
+[Global]
+id=516444
+version=1.0
+about=Test distribution file
+
+[BookmarksToolbar]
+item.1.title=Toolbar Link Before
+item.1.link=https://example.org/toolbar/before/
+item.1.keyword=e:t:b
+item.1.icon=https://example.org/favicon.png
+item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==
+item.2.type=default
+item.3.title=Toolbar Link After
+item.3.link=https://example.org/toolbar/after/
+item.3.keyword=e:t:a
+
+[BookmarksMenu]
+item.1.title=Menu Link Before
+item.1.link=https://example.org/menu/before/
+item.1.icon=https://example.org/favicon.png
+item.1.iconData=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==
+item.2.type=default
+item.3.title=Menu Link After
+item.3.link=https://example.org/menu/after/
diff --git a/browser/components/places/tests/unit/head_bookmarks.js b/browser/components/places/tests/unit/head_bookmarks.js
new file mode 100644
index 000000000..460295f96
--- /dev/null
+++ b/browser/components/places/tests/unit/head_bookmarks.js
@@ -0,0 +1,133 @@
+/* -*- 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 Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+
+// Import common head.
+var commonFile = do_get_file("../../../../../toolkit/components/places/tests/head_common.js", false);
+if (commonFile) {
+ let uri = Services.io.newFileURI(commonFile);
+ Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+// Put any other stuff relative to this test folder below.
+
+XPCOMUtils.defineLazyGetter(this, "PlacesUIUtils", function() {
+ Cu.import("resource:///modules/PlacesUIUtils.jsm");
+ return PlacesUIUtils;
+});
+
+const ORGANIZER_FOLDER_ANNO = "PlacesOrganizer/OrganizerFolder";
+const ORGANIZER_QUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
+
+// Needed by some test that relies on having an app registered.
+Cu.import("resource://testing-common/AppInfo.jsm", this);
+updateAppInfo({
+ name: "PlacesTest",
+ ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+ version: "1",
+ platformVersion: "",
+});
+
+// Smart bookmarks constants.
+const SMART_BOOKMARKS_VERSION = 8;
+const SMART_BOOKMARKS_ON_TOOLBAR = 1;
+const SMART_BOOKMARKS_ON_MENU = 2; // Takes into account the additional separator.
+
+// Default bookmarks constants.
+const DEFAULT_BOOKMARKS_ON_TOOLBAR = 1;
+const DEFAULT_BOOKMARKS_ON_MENU = 1;
+
+const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
+
+function checkItemHasAnnotation(guid, name) {
+ return PlacesUtils.promiseItemId(guid).then(id => {
+ let hasAnnotation = PlacesUtils.annotations.itemHasAnnotation(id, name);
+ Assert.ok(hasAnnotation, `Expected annotation ${name}`);
+ });
+}
+
+var createCorruptDB = Task.async(function* () {
+ let dbPath = OS.Path.join(OS.Constants.Path.profileDir, "places.sqlite");
+ yield OS.File.remove(dbPath);
+
+ // Create a corrupt database.
+ let dir = yield OS.File.getCurrentDirectory();
+ let src = OS.Path.join(dir, "corruptDB.sqlite");
+ yield OS.File.copy(src, dbPath);
+
+ // Check there's a DB now.
+ Assert.ok((yield OS.File.exists(dbPath)), "should have a DB now");
+});
+
+/**
+ * Rebuilds smart bookmarks listening to console output to report any message or
+ * exception generated.
+ *
+ * @return {Promise}
+ * Resolved when done.
+ */
+function rebuildSmartBookmarks() {
+ let consoleListener = {
+ observe(aMsg) {
+ if (aMsg.message.startsWith("[JavaScript Warning:")) {
+ // TODO (Bug 1300416): Ignore spurious strict warnings.
+ return;
+ }
+ do_throw("Got console message: " + aMsg.message);
+ },
+ QueryInterface: XPCOMUtils.generateQI([ Ci.nsIConsoleListener ]),
+ };
+ Services.console.reset();
+ Services.console.registerListener(consoleListener);
+ do_register_cleanup(() => {
+ try {
+ Services.console.unregisterListener(consoleListener);
+ } catch (ex) { /* will likely fail */ }
+ });
+ Cc["@mozilla.org/browser/browserglue;1"]
+ .getService(Ci.nsIObserver)
+ .observe(null, "browser-glue-test", "smart-bookmarks-init");
+ return promiseTopicObserved("test-smart-bookmarks-done").then(() => {
+ Services.console.unregisterListener(consoleListener);
+ });
+}
+
+const SINGLE_TRY_TIMEOUT = 100;
+const NUMBER_OF_TRIES = 30;
+
+/**
+ * Similar to waitForConditionPromise, but poll for an asynchronous value
+ * every SINGLE_TRY_TIMEOUT ms, for no more than tryCount times.
+ *
+ * @param promiseFn
+ * A function to generate a promise, which resolves to the expected
+ * asynchronous value.
+ * @param timeoutMsg
+ * The reason to reject the returned promise with.
+ * @param [optional] tryCount
+ * Maximum times to try before rejecting the returned promise with
+ * timeoutMsg, defaults to NUMBER_OF_TRIES.
+ * @return {Promise}
+ * @resolves to the asynchronous value being polled.
+ * @rejects if the asynchronous value is not available after tryCount attempts.
+ */
+var waitForResolvedPromise = Task.async(function* (promiseFn, timeoutMsg, tryCount=NUMBER_OF_TRIES) {
+ let tries = 0;
+ do {
+ try {
+ let value = yield promiseFn();
+ return value;
+ } catch (ex) {}
+ yield new Promise(resolve => do_timeout(SINGLE_TRY_TIMEOUT, resolve));
+ } while (++tries <= tryCount);
+ throw new Error(timeoutMsg);
+});
diff --git a/browser/components/places/tests/unit/test_421483.js b/browser/components/places/tests/unit/test_421483.js
new file mode 100644
index 000000000..a0d138372
--- /dev/null
+++ b/browser/components/places/tests/unit/test_421483.js
@@ -0,0 +1,103 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
+
+var gluesvc = Cc["@mozilla.org/browser/browserglue;1"].
+ getService(Ci.nsIObserver);
+// Avoid default bookmarks import.
+gluesvc.observe(null, "initial-migration-will-import-default-bookmarks", "");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* smart_bookmarks_disabled() {
+ Services.prefs.setIntPref("browser.places.smartBookmarksVersion", -1);
+ yield rebuildSmartBookmarks();
+
+ let smartBookmarkItemIds =
+ PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ Assert.equal(smartBookmarkItemIds.length, 0);
+
+ do_print("check that pref has not been bumped up");
+ Assert.equal(Services.prefs.getIntPref("browser.places.smartBookmarksVersion"), -1);
+});
+
+add_task(function* create_smart_bookmarks() {
+ Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
+ yield rebuildSmartBookmarks();
+
+ let smartBookmarkItemIds =
+ PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ Assert.notEqual(smartBookmarkItemIds.length, 0);
+
+ do_print("check that pref has been bumped up");
+ Assert.ok(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0);
+});
+
+add_task(function* remove_smart_bookmark_and_restore() {
+ let smartBookmarkItemIds =
+ PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ let smartBookmarksCount = smartBookmarkItemIds.length;
+ do_print("remove one smart bookmark and restore");
+
+ let guid = yield PlacesUtils.promiseItemGuid(smartBookmarkItemIds[0]);
+ yield PlacesUtils.bookmarks.remove(guid);
+ Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
+
+ yield rebuildSmartBookmarks();
+ smartBookmarkItemIds =
+ PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ Assert.equal(smartBookmarkItemIds.length, smartBookmarksCount);
+
+ do_print("check that pref has been bumped up");
+ Assert.ok(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0);
+});
+
+add_task(function* move_smart_bookmark_rename_and_restore() {
+ let smartBookmarkItemIds =
+ PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ let smartBookmarksCount = smartBookmarkItemIds.length;
+ do_print("smart bookmark should be restored in place");
+
+ let guid = yield PlacesUtils.promiseItemGuid(smartBookmarkItemIds[0]);
+ let bm = yield PlacesUtils.bookmarks.fetch(guid);
+ let oldTitle = bm.title;
+
+ // create a subfolder and move inside it
+ let subfolder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: bm.parentGuid,
+ title: "test",
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER
+ });
+
+ // change title and move into new subfolder
+ yield PlacesUtils.bookmarks.update({
+ guid: guid,
+ parentGuid: subfolder.guid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title: "new title"
+ });
+
+ // restore
+ Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
+ yield rebuildSmartBookmarks();
+
+ smartBookmarkItemIds =
+ PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ Assert.equal(smartBookmarkItemIds.length, smartBookmarksCount);
+
+ guid = yield PlacesUtils.promiseItemGuid(smartBookmarkItemIds[0]);
+ bm = yield PlacesUtils.bookmarks.fetch(guid);
+ Assert.equal(bm.parentGuid, subfolder.guid);
+ Assert.equal(bm.title, oldTitle);
+
+ do_print("check that pref has been bumped up");
+ Assert.ok(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0);
+});
diff --git a/browser/components/places/tests/unit/test_PUIU_makeTransaction.js b/browser/components/places/tests/unit/test_PUIU_makeTransaction.js
new file mode 100644
index 000000000..c0626f53b
--- /dev/null
+++ b/browser/components/places/tests/unit/test_PUIU_makeTransaction.js
@@ -0,0 +1,361 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function waitForBookmarkNotification(aNotification, aCallback, aProperty)
+{
+ PlacesUtils.bookmarks.addObserver({
+ validate: function (aMethodName, aData)
+ {
+ if (aMethodName == aNotification &&
+ (!aProperty || aProperty == aData.property)) {
+ PlacesUtils.bookmarks.removeObserver(this);
+ aCallback(aData);
+ }
+ },
+
+ // nsINavBookmarkObserver
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
+ onBeginUpdateBatch: function onBeginUpdateBatch() {
+ return this.validate(arguments.callee.name, arguments);
+ },
+ onEndUpdateBatch: function onEndUpdateBatch() {
+ return this.validate(arguments.callee.name, arguments);
+ },
+ onItemAdded: function onItemAdded(aItemId, aParentId, aIndex, aItemType,
+ aURI, aTitle)
+ {
+ return this.validate(arguments.callee.name, { id: aItemId,
+ index: aIndex,
+ type: aItemType,
+ url: aURI ? aURI.spec : null,
+ title: aTitle });
+ },
+ onItemRemoved: function onItemRemoved() {
+ return this.validate(arguments.callee.name, arguments);
+ },
+ onItemChanged: function onItemChanged(id, property, aIsAnno,
+ aNewValue, aLastModified, type)
+ {
+ return this.validate(arguments.callee.name,
+ { id,
+ get index() {
+ return PlacesUtils.bookmarks.getItemIndex(this.id);
+ },
+ type,
+ property,
+ get url() {
+ return type == PlacesUtils.bookmarks.TYPE_BOOKMARK ?
+ PlacesUtils.bookmarks.getBookmarkURI(this.id).spec :
+ null;
+ },
+ get title() {
+ return PlacesUtils.bookmarks.getItemTitle(this.id);
+ },
+ });
+ },
+ onItemVisited: function onItemVisited() {
+ return this.validate(arguments.callee.name, arguments);
+ },
+ onItemMoved: function onItemMoved(aItemId, aOldParentId, aOldIndex,
+ aNewParentId, aNewIndex, aItemType)
+ {
+ this.validate(arguments.callee.name, { id: aItemId,
+ index: aNewIndex,
+ type: aItemType });
+ }
+ }, false);
+}
+
+function wrapNodeByIdAndParent(aItemId, aParentId)
+{
+ let wrappedNode;
+ let root = PlacesUtils.getFolderContents(aParentId, false, false).root;
+ for (let i = 0; i < root.childCount; ++i) {
+ let node = root.getChild(i);
+ if (node.itemId == aItemId) {
+ let type;
+ if (PlacesUtils.nodeIsContainer(node)) {
+ type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
+ }
+ else if (PlacesUtils.nodeIsURI(node)) {
+ type = PlacesUtils.TYPE_X_MOZ_PLACE;
+ }
+ else if (PlacesUtils.nodeIsSeparator(node)) {
+ type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
+ }
+ else {
+ do_throw("Unknown node type");
+ }
+ wrappedNode = PlacesUtils.wrapNode(node, type);
+ }
+ }
+ root.containerOpen = false;
+ return JSON.parse(wrappedNode);
+}
+
+add_test(function test_text_paste()
+{
+ const TEST_URL = "http://places.moz.org/"
+ const TEST_TITLE = "Places bookmark"
+
+ waitForBookmarkNotification("onItemAdded", function(aData)
+ {
+ do_check_eq(aData.title, TEST_TITLE);
+ do_check_eq(aData.url, TEST_URL);
+ do_check_eq(aData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ do_check_eq(aData.index, 0);
+ run_next_test();
+ });
+
+ let txn = PlacesUIUtils.makeTransaction(
+ { title: TEST_TITLE, uri: TEST_URL },
+ PlacesUtils.TYPE_X_MOZ_URL,
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ true // Unused for text.
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+});
+
+add_test(function test_container()
+{
+ const TEST_TITLE = "Places folder"
+
+ waitForBookmarkNotification("onItemChanged", function(aChangedData)
+ {
+ do_check_eq(aChangedData.title, TEST_TITLE);
+ do_check_eq(aChangedData.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ do_check_eq(aChangedData.index, 1);
+
+ waitForBookmarkNotification("onItemAdded", function(aAddedData)
+ {
+ do_check_eq(aAddedData.title, TEST_TITLE);
+ do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ do_check_eq(aAddedData.index, 2);
+ let id = aAddedData.id;
+
+ waitForBookmarkNotification("onItemMoved", function(aMovedData)
+ {
+ do_check_eq(aMovedData.id, id);
+ do_check_eq(aMovedData.type, PlacesUtils.bookmarks.TYPE_FOLDER);
+ do_check_eq(aMovedData.index, 1);
+
+ run_next_test();
+ });
+
+ let txn = PlacesUIUtils.makeTransaction(
+ wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId),
+ 0, // Unused for real nodes.
+ PlacesUtils.unfiledBookmarksFolderId,
+ 1, // Move to position 1.
+ false
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+ });
+
+ try {
+ let txn = PlacesUIUtils.makeTransaction(
+ wrapNodeByIdAndParent(aChangedData.id, PlacesUtils.unfiledBookmarksFolderId),
+ 0, // Unused for real nodes.
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ true
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+ } catch (ex) {
+ do_throw(ex);
+ }
+ }, "random-anno");
+
+ let id = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
+ TEST_TITLE,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.annotations.setItemAnnotation(id, PlacesUIUtils.DESCRIPTION_ANNO,
+ "description", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ PlacesUtils.annotations.setItemAnnotation(id, "random-anno",
+ "random-value", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+});
+
+
+add_test(function test_separator()
+{
+ waitForBookmarkNotification("onItemChanged", function(aChangedData)
+ {
+ do_check_eq(aChangedData.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ do_check_eq(aChangedData.index, 3);
+
+ waitForBookmarkNotification("onItemAdded", function(aAddedData)
+ {
+ do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ do_check_eq(aAddedData.index, 4);
+ let id = aAddedData.id;
+
+ waitForBookmarkNotification("onItemMoved", function(aMovedData)
+ {
+ do_check_eq(aMovedData.id, id);
+ do_check_eq(aMovedData.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
+ do_check_eq(aMovedData.index, 1);
+
+ run_next_test();
+ });
+
+ let txn = PlacesUIUtils.makeTransaction(
+ wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId),
+ 0, // Unused for real nodes.
+ PlacesUtils.unfiledBookmarksFolderId,
+ 1, // Move to position 1.
+ false
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+ });
+
+ try {
+ let txn = PlacesUIUtils.makeTransaction(
+ wrapNodeByIdAndParent(aChangedData.id, PlacesUtils.unfiledBookmarksFolderId),
+ 0, // Unused for real nodes.
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ true
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+ } catch (ex) {
+ do_throw(ex);
+ }
+ }, "random-anno");
+
+ let id = PlacesUtils.bookmarks.insertSeparator(PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.annotations.setItemAnnotation(id, "random-anno",
+ "random-value", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+});
+
+add_test(function test_bookmark()
+{
+ const TEST_URL = "http://places.moz.org/"
+ const TEST_TITLE = "Places bookmark"
+
+ waitForBookmarkNotification("onItemChanged", function(aChangedData)
+ {
+ do_check_eq(aChangedData.title, TEST_TITLE);
+ do_check_eq(aChangedData.url, TEST_URL);
+ do_check_eq(aChangedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ do_check_eq(aChangedData.index, 5);
+
+ waitForBookmarkNotification("onItemAdded", function(aAddedData)
+ {
+ do_check_eq(aAddedData.title, TEST_TITLE);
+ do_check_eq(aAddedData.url, TEST_URL);
+ do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ do_check_eq(aAddedData.index, 6);
+ let id = aAddedData.id;
+
+ waitForBookmarkNotification("onItemMoved", function(aMovedData)
+ {
+ do_check_eq(aMovedData.id, id);
+ do_check_eq(aMovedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ do_check_eq(aMovedData.index, 1);
+
+ run_next_test();
+ });
+
+ let txn = PlacesUIUtils.makeTransaction(
+ wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId),
+ 0, // Unused for real nodes.
+ PlacesUtils.unfiledBookmarksFolderId,
+ 1, // Move to position 1.
+ false
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+ });
+
+ try {
+ let txn = PlacesUIUtils.makeTransaction(
+ wrapNodeByIdAndParent(aChangedData.id, PlacesUtils.unfiledBookmarksFolderId),
+ 0, // Unused for real nodes.
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ true
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+ } catch (ex) {
+ do_throw(ex);
+ }
+ }, "random-anno");
+
+ let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ NetUtil.newURI(TEST_URL),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ TEST_TITLE);
+ PlacesUtils.annotations.setItemAnnotation(id, PlacesUIUtils.DESCRIPTION_ANNO,
+ "description", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ PlacesUtils.annotations.setItemAnnotation(id, "random-anno",
+ "random-value", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+});
+
+add_test(function test_visit()
+{
+ const TEST_URL = "http://places.moz.org/"
+ const TEST_TITLE = "Places bookmark"
+
+ waitForBookmarkNotification("onItemAdded", function(aAddedData)
+ {
+ do_check_eq(aAddedData.title, TEST_TITLE);
+ do_check_eq(aAddedData.url, TEST_URL);
+ do_check_eq(aAddedData.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ do_check_eq(aAddedData.index, 7);
+
+ waitForBookmarkNotification("onItemAdded", function(aAddedData2)
+ {
+ do_check_eq(aAddedData2.title, TEST_TITLE);
+ do_check_eq(aAddedData2.url, TEST_URL);
+ do_check_eq(aAddedData2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
+ do_check_eq(aAddedData2.index, 8);
+ run_next_test();
+ });
+
+ try {
+ let node = wrapNodeByIdAndParent(aAddedData.id, PlacesUtils.unfiledBookmarksFolderId);
+ // Simulate a not-bookmarked node, will copy it to a new bookmark.
+ node.id = -1;
+ let txn = PlacesUIUtils.makeTransaction(
+ node,
+ 0, // Unused for real nodes.
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ true
+ );
+ PlacesUtils.transactionManager.doTransaction(txn);
+ } catch (ex) {
+ do_throw(ex);
+ }
+ });
+
+ PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ NetUtil.newURI(TEST_URL),
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ TEST_TITLE);
+});
+
+add_test(function check_annotations() {
+ // As last step check how many items for each annotation exist.
+
+ // Copies should retain the description annotation.
+ let descriptions =
+ PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.DESCRIPTION_ANNO, {});
+ do_check_eq(descriptions.length, 4);
+
+ // Only the original bookmarks should have this annotation.
+ let others = PlacesUtils.annotations.getItemsWithAnnotation("random-anno", {});
+ do_check_eq(others.length, 3);
+ run_next_test();
+});
+
+function run_test()
+{
+ run_next_test();
+}
diff --git a/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js b/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js
new file mode 100644
index 000000000..4db21555f
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_bookmarkshtml.js
@@ -0,0 +1,33 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that nsBrowserGlue correctly exports bookmarks.html at shutdown if
+ * browser.bookmarks.autoExportHTML is set to true.
+ */
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ remove_bookmarks_html();
+
+ Services.prefs.setBoolPref("browser.bookmarks.autoExportHTML", true);
+ do_register_cleanup(() => Services.prefs.clearUserPref("browser.bookmarks.autoExportHTML"));
+
+ // Initialize nsBrowserGlue before Places.
+ Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports);
+
+ // Initialize Places through the History Service.
+ Cc["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Ci.nsINavHistoryService);
+
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, "profile-before-change");
+ check_bookmarks_html();
+ }, "profile-before-change", false);
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_corrupt.js b/browser/components/places/tests/unit/test_browserGlue_corrupt.js
new file mode 100644
index 000000000..5b2a09068
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_corrupt.js
@@ -0,0 +1,59 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that nsBrowserGlue correctly restores bookmarks from a JSON backup if
+ * database is corrupt and one backup is available.
+ */
+
+function run_test() {
+ // Create our bookmarks.html from bookmarks.glue.html.
+ create_bookmarks_html("bookmarks.glue.html");
+
+ remove_all_JSON_backups();
+
+ // Create our JSON backup from bookmarks.glue.json.
+ create_JSON_backup("bookmarks.glue.json");
+
+ run_next_test();
+}
+
+do_register_cleanup(function () {
+ remove_bookmarks_html();
+ remove_all_JSON_backups();
+ return PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(function* test_main() {
+ // Create a corrupt database.
+ yield createCorruptDB();
+
+ // Initialize nsBrowserGlue before Places.
+ Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports);
+
+ // Check the database was corrupt.
+ // nsBrowserGlue uses databaseStatus to manage initialization.
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+
+ // The test will continue once restore has finished and smart bookmarks
+ // have been created.
+ yield promiseTopicObserved("places-browser-init-complete");
+
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+
+ // Check that JSON backup has been restored.
+ // Notice restore from JSON notification is fired before smart bookmarks creation.
+ bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ });
+ Assert.equal(bm.title, "examplejson");
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js
new file mode 100644
index 000000000..7cb4e5e4c
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup.js
@@ -0,0 +1,52 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that nsBrowserGlue correctly imports from bookmarks.html if database
+ * is corrupt but a JSON backup is not available.
+ */
+
+function run_test() {
+ // Create our bookmarks.html from bookmarks.glue.html.
+ create_bookmarks_html("bookmarks.glue.html");
+
+ // Remove JSON backup from profile.
+ remove_all_JSON_backups();
+
+ run_next_test();
+}
+
+do_register_cleanup(remove_bookmarks_html);
+
+add_task(function* () {
+ // Create a corrupt database.
+ yield createCorruptDB();
+
+ // Initialize nsBrowserGlue before Places.
+ Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports);
+
+ // Check the database was corrupt.
+ // nsBrowserGlue uses databaseStatus to manage initialization.
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+
+ // The test will continue once import has finished and smart bookmarks
+ // have been created.
+ yield promiseTopicObserved("places-browser-init-complete");
+
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+
+ // Check that bookmarks html has been restored.
+ bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ });
+ Assert.equal(bm.title, "example");
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js
new file mode 100644
index 000000000..480420091
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_corrupt_nobackup_default.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that nsBrowserGlue correctly restores default bookmarks if database is
+ * corrupt, nor a JSON backup nor bookmarks.html are available.
+ */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+function run_test() {
+ // Remove bookmarks.html from profile.
+ remove_bookmarks_html();
+
+ // Remove JSON backup from profile.
+ remove_all_JSON_backups();
+
+ run_next_test();
+}
+
+add_task(function* () {
+ // Create a corrupt database.
+ yield createCorruptDB();
+
+ // Initialize nsBrowserGlue before Places.
+ Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports);
+
+ // Check the database was corrupt.
+ // nsBrowserGlue uses databaseStatus to manage initialization.
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+
+ // The test will continue once import has finished and smart bookmarks
+ // have been created.
+ yield promiseTopicObserved("places-browser-init-complete");
+
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+
+ // Check that default bookmarks have been restored.
+ bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ });
+
+ // Bug 1283076: Nightly bookmark points to Get Involved page, not Getting Started one
+ let chanTitle = AppConstants.NIGHTLY_BUILD ? "Get Involved" : "Getting Started";
+ do_check_eq(bm.title, chanTitle);
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_distribution.js b/browser/components/places/tests/unit/test_browserGlue_distribution.js
new file mode 100644
index 000000000..c3d6e1d9e
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that nsBrowserGlue correctly imports bookmarks from distribution.ini.
+ */
+
+const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion";
+const PREF_BMPROCESSED = "distribution.516444.bookmarksProcessed";
+const PREF_DISTRIBUTION_ID = "distribution.id";
+
+const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization";
+const TOPIC_CUSTOMIZATION_COMPLETE = "distribution-customization-complete";
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+
+function run_test() {
+ // Set special pref to load distribution.ini from the profile folder.
+ Services.prefs.setBoolPref("distribution.testing.loadFromProfile", true);
+
+ // Copy distribution.ini file to the profile dir.
+ let distroDir = gProfD.clone();
+ distroDir.leafName = "distribution";
+ let iniFile = distroDir.clone();
+ iniFile.append("distribution.ini");
+ if (iniFile.exists()) {
+ iniFile.remove(false);
+ print("distribution.ini already exists, did some test forget to cleanup?");
+ }
+
+ let testDistributionFile = gTestDir.clone();
+ testDistributionFile.append("distribution.ini");
+ testDistributionFile.copyTo(distroDir, "distribution.ini");
+ Assert.ok(testDistributionFile.exists());
+
+ run_next_test();
+}
+
+do_register_cleanup(function () {
+ // Remove the distribution file, even if the test failed, otherwise all
+ // next tests will import it.
+ let iniFile = gProfD.clone();
+ iniFile.leafName = "distribution";
+ iniFile.append("distribution.ini");
+ if (iniFile.exists()) {
+ iniFile.remove(false);
+ }
+ Assert.ok(!iniFile.exists());
+});
+
+add_task(function* () {
+ // Disable Smart Bookmarks creation.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, -1);
+
+ // Initialize Places through the History Service and check that a new
+ // database has been created.
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
+
+ // Force distribution.
+ let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
+ glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
+
+ // Test will continue on customization complete notification.
+ yield promiseTopicObserved(TOPIC_CUSTOMIZATION_COMPLETE);
+
+ // Check the custom bookmarks exist on menu.
+ let menuItem = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 0
+ });
+ Assert.equal(menuItem.title, "Menu Link Before");
+
+ menuItem = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 1 + DEFAULT_BOOKMARKS_ON_MENU
+ });
+ Assert.equal(menuItem.title, "Menu Link After");
+
+ // Check no favicon or keyword exists for this bookmark
+ yield Assert.rejects(waitForResolvedPromise(() => {
+ return PlacesUtils.promiseFaviconData(menuItem.url.href);
+ }, "Favicon not found", 10), /Favicon\snot\sfound/, "Favicon not found");
+
+ let keywordItem = yield PlacesUtils.keywords.fetch({
+ url: menuItem.url.href
+ });
+ Assert.strictEqual(keywordItem, null);
+
+ // Check the custom bookmarks exist on toolbar.
+ let toolbarItem = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ Assert.equal(toolbarItem.title, "Toolbar Link Before");
+
+ // Check the custom favicon and keyword exist for this bookmark
+ let faviconItem = yield waitForResolvedPromise(() => {
+ return PlacesUtils.promiseFaviconData(toolbarItem.url.href);
+ }, "Favicon not found", 10);
+ Assert.equal(faviconItem.uri.spec, "https://example.org/favicon.png");
+ Assert.greater(faviconItem.dataLen, 0);
+ Assert.equal(faviconItem.mimeType, "image/png");
+
+ let base64Icon = "data:image/png;base64," +
+ base64EncodeString(String.fromCharCode.apply(String, faviconItem.data));
+ Assert.equal(base64Icon, SMALLPNG_DATA_URI.spec);
+
+ keywordItem = yield PlacesUtils.keywords.fetch({
+ url: toolbarItem.url.href
+ });
+ Assert.notStrictEqual(keywordItem, null);
+ Assert.equal(keywordItem.keyword, "e:t:b");
+
+ toolbarItem = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 1 + DEFAULT_BOOKMARKS_ON_TOOLBAR
+ });
+ Assert.equal(toolbarItem.title, "Toolbar Link After");
+
+ // Check the bmprocessed pref has been created.
+ Assert.ok(Services.prefs.getBoolPref(PREF_BMPROCESSED));
+
+ // Check distribution prefs have been created.
+ Assert.equal(Services.prefs.getCharPref(PREF_DISTRIBUTION_ID), "516444");
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_migrate.js b/browser/components/places/tests/unit/test_browserGlue_migrate.js
new file mode 100644
index 000000000..817f10c81
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_migrate.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that nsBrowserGlue does not overwrite bookmarks imported from the
+ * migrators. They usually run before nsBrowserGlue, so if we find any
+ * bookmark on init, we should not try to import.
+ */
+
+const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion";
+
+function run_test() {
+ // Create our bookmarks.html from bookmarks.glue.html.
+ create_bookmarks_html("bookmarks.glue.html");
+
+ // Remove current database file.
+ clearDB();
+
+ run_next_test();
+}
+
+do_register_cleanup(remove_bookmarks_html);
+
+add_task(function* test_migrate_bookmarks() {
+ // Initialize Places through the History Service and check that a new
+ // database has been created.
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
+
+ // A migrator would run before nsBrowserGlue Places initialization, so mimic
+ // that behavior adding a bookmark and notifying the migration.
+ let bg = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
+ bg.observe(null, "initial-migration-will-import-default-bookmarks", null);
+
+ yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://mozilla.org/",
+ title: "migrated"
+ });
+
+ let promise = promiseTopicObserved("places-browser-init-complete");
+ bg.observe(null, "initial-migration-did-import-default-bookmarks", null);
+ yield promise;
+
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+
+ // Check the created bookmark still exists.
+ bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: SMART_BOOKMARKS_ON_MENU
+ });
+ Assert.equal(bm.title, "migrated");
+
+ // Check that we have not imported any new bookmark.
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: SMART_BOOKMARKS_ON_MENU + 1
+ })));
+
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_MENU
+ })));
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_prefs.js b/browser/components/places/tests/unit/test_browserGlue_prefs.js
new file mode 100644
index 000000000..9f3504636
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that nsBrowserGlue is correctly interpreting the preferences settable
+ * by the user or by other components.
+ */
+
+const PREF_IMPORT_BOOKMARKS_HTML = "browser.places.importBookmarksHTML";
+const PREF_RESTORE_DEFAULT_BOOKMARKS = "browser.bookmarks.restore_default_bookmarks";
+const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion";
+const PREF_AUTO_EXPORT_HTML = "browser.bookmarks.autoExportHTML";
+
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+const TOPICDATA_FORCE_PLACES_INIT = "force-places-init";
+
+var bg = Cc["@mozilla.org/browser/browserglue;1"].
+ getService(Ci.nsIObserver);
+
+function run_test() {
+ // Create our bookmarks.html from bookmarks.glue.html.
+ create_bookmarks_html("bookmarks.glue.html");
+
+ remove_all_JSON_backups();
+
+ // Create our JSON backup from bookmarks.glue.json.
+ create_JSON_backup("bookmarks.glue.json");
+
+ run_next_test();
+}
+
+do_register_cleanup(function () {
+ remove_bookmarks_html();
+ remove_all_JSON_backups();
+
+ return PlacesUtils.bookmarks.eraseEverything();
+});
+
+function simulatePlacesInit() {
+ do_print("Simulate Places init");
+ // Force nsBrowserGlue::_initPlaces().
+ bg.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT);
+ return promiseTopicObserved("places-browser-init-complete");
+}
+
+add_task(function* test_checkPreferences() {
+ // Initialize Places through the History Service and check that a new
+ // database has been created.
+ Assert.equal(PlacesUtils.history.databaseStatus,
+ PlacesUtils.history.DATABASE_STATUS_CREATE);
+
+ // Wait for Places init notification.
+ yield promiseTopicObserved("places-browser-init-complete");
+
+ // Ensure preferences status.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_AUTO_EXPORT_HTML));
+
+ Assert.throws(() => Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
+ Assert.throws(() => Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
+});
+
+add_task(function* test_import() {
+ do_print("Import from bookmarks.html if importBookmarksHTML is true.");
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Sanity check: we should not have any bookmark on the toolbar.
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ })));
+
+ // Set preferences.
+ Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true);
+
+ yield simulatePlacesInit();
+
+ // Check bookmarks.html has been imported, and a smart bookmark has been
+ // created.
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ });
+ Assert.equal(bm.title, "example");
+
+ // Check preferences have been reverted.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
+});
+
+add_task(function* test_import_noSmartBookmarks() {
+ do_print("import from bookmarks.html, but don't create smart bookmarks " +
+ "if they are disabled");
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Sanity check: we should not have any bookmark on the toolbar.
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ })));
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, -1);
+ Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true);
+
+ yield simulatePlacesInit();
+
+ // Check bookmarks.html has been imported, but smart bookmarks have not
+ // been created.
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ Assert.equal(bm.title, "example");
+
+ // Check preferences have been reverted.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
+});
+
+add_task(function* test_import_autoExport_updatedSmartBookmarks() {
+ do_print("Import from bookmarks.html, but don't create smart bookmarks " +
+ "if autoExportHTML is true and they are at latest version");
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Sanity check: we should not have any bookmark on the toolbar.
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ })));
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 999);
+ Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, true);
+ Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true);
+
+ yield simulatePlacesInit();
+
+ // Check bookmarks.html has been imported, but smart bookmarks have not
+ // been created.
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ Assert.equal(bm.title, "example");
+
+ // Check preferences have been reverted.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
+
+ Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false);
+});
+
+add_task(function* test_import_autoExport_oldSmartBookmarks() {
+ do_print("Import from bookmarks.html, and create smart bookmarks if " +
+ "autoExportHTML is true and they are not at latest version.");
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Sanity check: we should not have any bookmark on the toolbar.
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ })));
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 0);
+ Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, true);
+ Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true);
+
+ yield simulatePlacesInit();
+
+ // Check bookmarks.html has been imported, but smart bookmarks have not
+ // been created.
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ });
+ Assert.equal(bm.title, "example");
+
+ // Check preferences have been reverted.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
+
+ Services.prefs.setBoolPref(PREF_AUTO_EXPORT_HTML, false);
+});
+
+add_task(function* test_restore() {
+ do_print("restore from default bookmarks.html if " +
+ "restore_default_bookmarks is true.");
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Sanity check: we should not have any bookmark on the toolbar.
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ })));
+
+ // Set preferences.
+ Services.prefs.setBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS, true);
+
+ yield simulatePlacesInit();
+
+ // Check bookmarks.html has been restored.
+ Assert.ok(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ }));
+
+ // Check preferences have been reverted.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
+});
+
+add_task(function* test_restore_import() {
+ do_print("setting both importBookmarksHTML and " +
+ "restore_default_bookmarks should restore defaults.");
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Sanity check: we should not have any bookmark on the toolbar.
+ Assert.ok(!(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ })));
+
+ // Set preferences.
+ Services.prefs.setBoolPref(PREF_IMPORT_BOOKMARKS_HTML, true);
+ Services.prefs.setBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS, true);
+
+ yield simulatePlacesInit();
+
+ // Check bookmarks.html has been restored.
+ Assert.ok(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ }));
+
+ // Check preferences have been reverted.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
+ Assert.ok(!Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_restore.js b/browser/components/places/tests/unit/test_browserGlue_restore.js
new file mode 100644
index 000000000..9d7ac5ac1
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_restore.js
@@ -0,0 +1,62 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that nsBrowserGlue correctly restores bookmarks from a JSON backup if
+ * database has been created and one backup is available.
+ */
+
+function run_test() {
+ // Create our bookmarks.html from bookmarks.glue.html.
+ create_bookmarks_html("bookmarks.glue.html");
+
+ remove_all_JSON_backups();
+
+ // Create our JSON backup from bookmarks.glue.json.
+ create_JSON_backup("bookmarks.glue.json");
+
+ // Remove current database file.
+ clearDB();
+
+ run_next_test();
+}
+
+do_register_cleanup(function () {
+ remove_bookmarks_html();
+ remove_all_JSON_backups();
+ return PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(function* test_main() {
+ // Initialize nsBrowserGlue before Places.
+ Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsISupports);
+
+ // Initialize Places through the History Service.
+ let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsINavHistoryService);
+
+ // Check a new database has been created.
+ // nsBrowserGlue uses databaseStatus to manage initialization.
+ Assert.equal(hs.databaseStatus, hs.DATABASE_STATUS_CREATE);
+
+ // The test will continue once restore has finished and smart bookmarks
+ // have been created.
+ yield promiseTopicObserved("places-browser-init-complete");
+
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+
+ // Check that JSON backup has been restored.
+ // Notice restore from JSON notification is fired before smart bookmarks creation.
+ bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: SMART_BOOKMARKS_ON_TOOLBAR
+ });
+ Assert.equal(bm.title, "examplejson");
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js
new file mode 100644
index 000000000..6ecaec4fe
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_smartBookmarks.js
@@ -0,0 +1,285 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that nsBrowserGlue is correctly interpreting the preferences settable
+ * by the user or by other components.
+ */
+
+const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion";
+const PREF_AUTO_EXPORT_HTML = "browser.bookmarks.autoExportHTML";
+const PREF_IMPORT_BOOKMARKS_HTML = "browser.places.importBookmarksHTML";
+const PREF_RESTORE_DEFAULT_BOOKMARKS = "browser.bookmarks.restore_default_bookmarks";
+
+function run_test() {
+ remove_bookmarks_html();
+ remove_all_JSON_backups();
+ run_next_test();
+}
+
+do_register_cleanup(() => PlacesUtils.bookmarks.eraseEverything());
+
+function countFolderChildren(aFolderItemId) {
+ let rootNode = PlacesUtils.getFolderContents(aFolderItemId).root;
+ let cc = rootNode.childCount;
+ // Dump contents.
+ for (let i = 0; i < cc ; i++) {
+ let node = rootNode.getChild(i);
+ let title = PlacesUtils.nodeIsSeparator(node) ? "---" : node.title;
+ print("Found child(" + i + "): " + title);
+ }
+ rootNode.containerOpen = false;
+ return cc;
+}
+
+add_task(function* setup() {
+ // Initialize browserGlue, but remove it's listener to places-init-complete.
+ Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver);
+
+ // Initialize Places.
+ PlacesUtils.history;
+
+ // Wait for Places init notification.
+ yield promiseTopicObserved("places-browser-init-complete");
+
+ // Ensure preferences status.
+ Assert.ok(!Services.prefs.getBoolPref(PREF_AUTO_EXPORT_HTML));
+ Assert.ok(!Services.prefs.getBoolPref(PREF_RESTORE_DEFAULT_BOOKMARKS));
+ Assert.throws(() => Services.prefs.getBoolPref(PREF_IMPORT_BOOKMARKS_HTML));
+});
+
+add_task(function* test_version_0() {
+ do_print("All smart bookmarks are created if smart bookmarks version is 0.");
+
+ // Sanity check: we should have default bookmark.
+ Assert.ok(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ }));
+
+ Assert.ok(yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 0
+ }));
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 0);
+
+ yield rebuildSmartBookmarks();
+
+ // Count items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Check version has been updated.
+ Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION),
+ SMART_BOOKMARKS_VERSION);
+});
+
+add_task(function* test_version_change() {
+ do_print("An existing smart bookmark is replaced when version changes.");
+
+ // Sanity check: we have a smart bookmark on the toolbar.
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+
+ // Change its title.
+ yield PlacesUtils.bookmarks.update({guid: bm.guid, title: "new title"});
+ bm = yield PlacesUtils.bookmarks.fetch({guid: bm.guid});
+ Assert.equal(bm.title, "new title");
+
+ // Sanity check items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1);
+
+ yield rebuildSmartBookmarks();
+
+ // Count items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Check smart bookmark has been replaced, itemId has changed.
+ bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+ Assert.notEqual(bm.title, "new title");
+
+ // Check version has been updated.
+ Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION),
+ SMART_BOOKMARKS_VERSION);
+});
+
+add_task(function* test_version_change_pos() {
+ do_print("bookmarks position is retained when version changes.");
+
+ // Sanity check items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+ let firstItemTitle = bm.title;
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1);
+
+ yield rebuildSmartBookmarks();
+
+ // Count items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Check smart bookmarks are still in correct position.
+ bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm.guid, SMART_BOOKMARKS_ANNO);
+ Assert.equal(bm.title, firstItemTitle);
+
+ // Check version has been updated.
+ Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION),
+ SMART_BOOKMARKS_VERSION);
+});
+
+add_task(function* test_version_change_pos_moved() {
+ do_print("moved bookmarks position is retained when version changes.");
+
+ // Sanity check items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ let bm1 = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 0
+ });
+ yield checkItemHasAnnotation(bm1.guid, SMART_BOOKMARKS_ANNO);
+ let firstItemTitle = bm1.title;
+
+ // Move the first smart bookmark to the end of the menu.
+ yield PlacesUtils.bookmarks.update({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ guid: bm1.guid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX
+ });
+
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX
+ });
+ Assert.equal(bm.guid, bm1.guid);
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1);
+
+ yield rebuildSmartBookmarks();
+
+ // Count items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ bm1 = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX
+ });
+ yield checkItemHasAnnotation(bm1.guid, SMART_BOOKMARKS_ANNO);
+ Assert.equal(bm1.title, firstItemTitle);
+
+ // Move back the smart bookmark to the original position.
+ yield PlacesUtils.bookmarks.update({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ guid: bm1.guid,
+ index: 1
+ });
+
+ // Check version has been updated.
+ Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION),
+ SMART_BOOKMARKS_VERSION);
+});
+
+add_task(function* test_recreation() {
+ do_print("An explicitly removed smart bookmark should not be recreated.");
+
+ // Remove toolbar's smart bookmarks
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: 0
+ });
+ yield PlacesUtils.bookmarks.remove(bm.guid);
+
+ // Sanity check items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 1);
+
+ yield rebuildSmartBookmarks();
+
+ // Count items.
+ // We should not have recreated the smart bookmark on toolbar.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Check version has been updated.
+ Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION),
+ SMART_BOOKMARKS_VERSION);
+});
+
+add_task(function* test_recreation_version_0() {
+ do_print("Even if a smart bookmark has been removed recreate it if version is 0.");
+
+ // Sanity check items.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Set preferences.
+ Services.prefs.setIntPref(PREF_SMART_BOOKMARKS_VERSION, 0);
+
+ yield rebuildSmartBookmarks();
+
+ // Count items.
+ // We should not have recreated the smart bookmark on toolbar.
+ Assert.equal(countFolderChildren(PlacesUtils.toolbarFolderId),
+ SMART_BOOKMARKS_ON_TOOLBAR + DEFAULT_BOOKMARKS_ON_TOOLBAR);
+ Assert.equal(countFolderChildren(PlacesUtils.bookmarksMenuFolderId),
+ SMART_BOOKMARKS_ON_MENU + DEFAULT_BOOKMARKS_ON_MENU);
+
+ // Check version has been updated.
+ Assert.equal(Services.prefs.getIntPref(PREF_SMART_BOOKMARKS_VERSION),
+ SMART_BOOKMARKS_VERSION);
+});
diff --git a/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js b/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js
new file mode 100644
index 000000000..072056b3f
--- /dev/null
+++ b/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.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/. */
+
+const UI_VERSION = 26;
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+const TOPICDATA_BROWSERGLUE_TEST = "force-ui-migration";
+const DEFAULT_BEHAVIOR_PREF = "browser.urlbar.default.behavior";
+const AUTOCOMPLETE_PREF = "browser.urlbar.autocomplete.enabled";
+
+var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"]
+ .getService(Ci.nsIObserver);
+var gGetBoolPref = Services.prefs.getBoolPref;
+
+function run_test() {
+ run_next_test();
+}
+
+do_register_cleanup(cleanup);
+
+function cleanup() {
+ let prefix = "browser.urlbar.suggest.";
+ for (let type of ["history", "bookmark", "openpage", "history.onlyTyped"]) {
+ Services.prefs.clearUserPref(prefix + type);
+ }
+ Services.prefs.clearUserPref("browser.migration.version");
+ Services.prefs.clearUserPref(AUTOCOMPLETE_PREF);
+}
+
+function setupBehaviorAndMigrate(aDefaultBehavior, aAutocompleteEnabled = true) {
+ cleanup();
+ // Migrate browser.urlbar.default.behavior preference.
+ Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
+ Services.prefs.setIntPref(DEFAULT_BEHAVIOR_PREF, aDefaultBehavior);
+ Services.prefs.setBoolPref(AUTOCOMPLETE_PREF, aAutocompleteEnabled);
+ // Simulate a migration.
+ gBrowserGlue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_BROWSERGLUE_TEST);
+}
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 0");
+ setupBehaviorAndMigrate(0);
+
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
+ "History preference should be true.");
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
+ "Bookmark preference should be true.");
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.openpage"),
+ "Openpage preference should be true.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
+ "Typed preference should be false.");
+});
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 1");
+ setupBehaviorAndMigrate(1);
+
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
+ "History preference should be true.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
+ "Bookmark preference should be false.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
+ "Openpage preference should be false");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
+ "Typed preference should be false");
+});
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 2");
+ setupBehaviorAndMigrate(2);
+
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false,
+ "History preference should be false.");
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
+ "Bookmark preference should be true.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
+ "Openpage preference should be false");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
+ "Typed preference should be false");
+});
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 3");
+ setupBehaviorAndMigrate(3);
+
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
+ "History preference should be true.");
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
+ "Bookmark preference should be true.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
+ "Openpage preference should be false");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
+ "Typed preference should be false");
+});
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 19");
+ setupBehaviorAndMigrate(19);
+
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
+ "History preference should be true.");
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
+ "Bookmark preference should be true.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
+ "Openpage preference should be false");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
+ "Typed preference should be false");
+});
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 33");
+ setupBehaviorAndMigrate(33);
+
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
+ "History preference should be true.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
+ "Bookmark preference should be false.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
+ "Openpage preference should be false");
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"),
+ "Typed preference should be true");
+});
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 129");
+ setupBehaviorAndMigrate(129);
+
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
+ "History preference should be true.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
+ "Bookmark preference should be false.");
+ Assert.ok(gGetBoolPref("browser.urlbar.suggest.openpage"),
+ "Openpage preference should be true");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
+ "Typed preference should be false");
+});
+
+add_task(function*() {
+ do_print("Migrate default.behavior = 0, autocomplete.enabled = false");
+ setupBehaviorAndMigrate(0, false);
+
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false,
+ "History preference should be false.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
+ "Bookmark preference should be false.");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
+ "Openpage preference should be false");
+ Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
+ "Typed preference should be false");
+});
diff --git a/browser/components/places/tests/unit/test_clearHistory_shutdown.js b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
new file mode 100644
index 000000000..0c1d78801
--- /dev/null
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -0,0 +1,181 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that requesting clear history at shutdown will really clear history.
+ */
+
+const URIS = [
+ "http://a.example1.com/"
+, "http://b.example1.com/"
+, "http://b.example2.com/"
+, "http://c.example3.com/"
+];
+
+const TOPIC_CONNECTION_CLOSED = "places-connection-closed";
+
+var EXPECTED_NOTIFICATIONS = [
+ "places-shutdown"
+, "places-will-close-connection"
+, "places-expiration-finished"
+, "places-connection-closed"
+];
+
+const UNEXPECTED_NOTIFICATIONS = [
+ "xpcom-shutdown"
+];
+
+const FTP_URL = "ftp://localhost/clearHistoryOnShutdown/";
+
+// Send the profile-after-change notification to the form history component to ensure
+// that it has been initialized.
+var formHistoryStartup = Cc["@mozilla.org/satchel/form-history-startup;1"].
+ getService(Ci.nsIObserver);
+formHistoryStartup.observe(null, "profile-after-change", null);
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+var timeInMicroseconds = Date.now() * 1000;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_execute() {
+ do_print("Initialize browserglue before Places");
+
+ // Avoid default bookmarks import.
+ let glue = Cc["@mozilla.org/browser/browserglue;1"].
+ getService(Ci.nsIObserver);
+ glue.observe(null, "initial-migration-will-import-default-bookmarks", null);
+ glue.observe(null, "test-initialize-sanitizer", null);
+
+
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.offlineApps", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.history", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.downloads", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.formData", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.sessions", true);
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.siteSettings", true);
+
+ Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
+
+ do_print("Add visits.");
+ for (let aUrl of URIS) {
+ yield PlacesTestUtils.addVisits({
+ uri: uri(aUrl), visitDate: timeInMicroseconds++,
+ transition: PlacesUtils.history.TRANSITION_TYPED
+ });
+ }
+ do_print("Add cache.");
+ yield storeCache(FTP_URL, "testData");
+ do_print("Add form history.");
+ yield addFormHistory();
+ Assert.equal((yield getFormHistoryCount()), 1, "Added form history");
+
+ do_print("Simulate and wait shutdown.");
+ yield shutdownPlaces();
+
+ Assert.equal((yield getFormHistoryCount()), 0, "Form history cleared");
+
+ let stmt = DBConn(true).createStatement(
+ "SELECT id FROM moz_places WHERE url = :page_url "
+ );
+
+ try {
+ URIS.forEach(function(aUrl) {
+ stmt.params.page_url = aUrl;
+ do_check_false(stmt.executeStep());
+ stmt.reset();
+ });
+ } finally {
+ stmt.finalize();
+ }
+
+ do_print("Check cache");
+ // Check cache.
+ yield checkCache(FTP_URL);
+});
+
+function addFormHistory() {
+ return new Promise(resolve => {
+ let now = Date.now() * 1000;
+ FormHistory.update({ op: "add",
+ fieldname: "testfield",
+ value: "test",
+ timesUsed: 1,
+ firstUsed: now,
+ lastUsed: now
+ },
+ { handleCompletion(reason) { resolve(); } });
+ });
+}
+
+function getFormHistoryCount() {
+ return new Promise((resolve, reject) => {
+ let count = -1;
+ FormHistory.count({ fieldname: "testfield" },
+ { handleResult(result) { count = result; },
+ handleCompletion(reason) { resolve(count); }
+ });
+ });
+}
+
+function storeCache(aURL, aContent) {
+ let cache = Services.cache2;
+ let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+
+ return new Promise(resolve => {
+ let storeCacheListener = {
+ onCacheEntryCheck: function (entry, appcache) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable: function (entry, isnew, appcache, status) {
+ do_check_eq(status, Cr.NS_OK);
+
+ entry.setMetaDataElement("servertype", "0");
+ var os = entry.openOutputStream(0);
+
+ var written = os.write(aContent, aContent.length);
+ if (written != aContent.length) {
+ do_throw("os.write has not written all data!\n" +
+ " Expected: " + written + "\n" +
+ " Actual: " + aContent.length + "\n");
+ }
+ os.close();
+ entry.close();
+ resolve();
+ }
+ };
+
+ storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ storeCacheListener);
+ });
+}
+
+
+function checkCache(aURL) {
+ let cache = Services.cache2;
+ let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+
+ return new Promise(resolve => {
+ let checkCacheListener = {
+ onCacheEntryAvailable: function (entry, isnew, appcache, status) {
+ do_check_eq(status, Cr.NS_ERROR_CACHE_KEY_NOT_FOUND);
+ resolve();
+ }
+ };
+
+ storage.asyncOpenURI(Services.io.newURI(aURL, null, null), "",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ checkCacheListener);
+ });
+}
diff --git a/browser/components/places/tests/unit/test_leftpane_corruption_handling.js b/browser/components/places/tests/unit/test_leftpane_corruption_handling.js
new file mode 100644
index 000000000..0af6f4e95
--- /dev/null
+++ b/browser/components/places/tests/unit/test_leftpane_corruption_handling.js
@@ -0,0 +1,174 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests that we build a working leftpane in various corruption situations.
+ */
+
+// Used to store the original leftPaneFolderId getter.
+var gLeftPaneFolderIdGetter;
+var gAllBookmarksFolderIdGetter;
+// Used to store the original left Pane status as a JSON string.
+var gReferenceHierarchy;
+var gLeftPaneFolderId;
+
+add_task(function* () {
+ // We want empty roots.
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // Sanity check.
+ Assert.ok(!!PlacesUIUtils);
+
+ // Check getters.
+ gLeftPaneFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "leftPaneFolderId");
+ Assert.equal(typeof(gLeftPaneFolderIdGetter.get), "function");
+ gAllBookmarksFolderIdGetter = Object.getOwnPropertyDescriptor(PlacesUIUtils, "allBookmarksFolderId");
+ Assert.equal(typeof(gAllBookmarksFolderIdGetter.get), "function");
+
+ do_register_cleanup(() => PlacesUtils.bookmarks.eraseEverything());
+});
+
+add_task(function* () {
+ // Add a third party bogus annotated item. Should not be removed.
+ let folder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "test",
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER
+ });
+
+ let folderId = yield PlacesUtils.promiseItemId(folder.guid);
+ PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO,
+ "test", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+
+ // Create the left pane, and store its current status, it will be used
+ // as reference value.
+ gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
+ gReferenceHierarchy = folderIdToHierarchy(gLeftPaneFolderId);
+
+ while (gTests.length) {
+ // Run current test.
+ yield Task.spawn(gTests.shift());
+
+ // Regenerate getters.
+ Object.defineProperty(PlacesUIUtils, "leftPaneFolderId", gLeftPaneFolderIdGetter);
+ gLeftPaneFolderId = PlacesUIUtils.leftPaneFolderId;
+ Object.defineProperty(PlacesUIUtils, "allBookmarksFolderId", gAllBookmarksFolderIdGetter);
+
+ // Check the new left pane folder.
+ let leftPaneHierarchy = folderIdToHierarchy(gLeftPaneFolderId)
+ Assert.equal(gReferenceHierarchy, leftPaneHierarchy);
+
+ folder = yield PlacesUtils.bookmarks.fetch({guid: folder.guid});
+ Assert.equal(folder.title, "test");
+ }
+});
+
+// Corruption cases.
+var gTests = [
+
+ function* test1() {
+ print("1. Do nothing, checks test calibration.");
+ },
+
+ function* test2() {
+ print("2. Delete the left pane folder.");
+ let guid = yield PlacesUtils.promiseItemGuid(gLeftPaneFolderId);
+ yield PlacesUtils.bookmarks.remove(guid);
+ },
+
+ function* test3() {
+ print("3. Delete a child of the left pane folder.");
+ let guid = yield PlacesUtils.promiseItemGuid(gLeftPaneFolderId);
+ let bm = yield PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0});
+ yield PlacesUtils.bookmarks.remove(bm.guid);
+ },
+
+ function* test4() {
+ print("4. Delete AllBookmarks.");
+ let guid = yield PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId);
+ yield PlacesUtils.bookmarks.remove(guid);
+ },
+
+ function* test5() {
+ print("5. Create a duplicated left pane folder.");
+ let folder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "PlacesRoot",
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER
+ });
+
+ let folderId = yield PlacesUtils.promiseItemId(folder.guid);
+ PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_FOLDER_ANNO,
+ "PlacesRoot", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ },
+
+ function* test6() {
+ print("6. Create a duplicated left pane query.");
+ let folder = yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "AllBookmarks",
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER
+ });
+
+ let folderId = yield PlacesUtils.promiseItemId(folder.guid);
+ PlacesUtils.annotations.setItemAnnotation(folderId, ORGANIZER_QUERY_ANNO,
+ "AllBookmarks", 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ },
+
+ function* test7() {
+ print("7. Remove the left pane folder annotation.");
+ PlacesUtils.annotations.removeItemAnnotation(gLeftPaneFolderId,
+ ORGANIZER_FOLDER_ANNO);
+ },
+
+ function* test8() {
+ print("8. Remove a left pane query annotation.");
+ PlacesUtils.annotations.removeItemAnnotation(PlacesUIUtils.allBookmarksFolderId,
+ ORGANIZER_QUERY_ANNO);
+ },
+
+ function* test9() {
+ print("9. Remove a child of AllBookmarks.");
+ let guid = yield PlacesUtils.promiseItemGuid(PlacesUIUtils.allBookmarksFolderId);
+ let bm = yield PlacesUtils.bookmarks.fetch({parentGuid: guid, index: 0});
+ yield PlacesUtils.bookmarks.remove(bm.guid);
+ }
+
+];
+
+/**
+ * Convert a folder item id to a JSON representation of it and its contents.
+ */
+function folderIdToHierarchy(aFolderId) {
+ let root = PlacesUtils.getFolderContents(aFolderId).root;
+ let hier = JSON.stringify(hierarchyToObj(root));
+ root.containerOpen = false;
+ return hier;
+}
+
+function hierarchyToObj(aNode) {
+ let o = {}
+ o.title = aNode.title;
+ o.annos = PlacesUtils.getAnnotationsForItem(aNode.itemId)
+ if (PlacesUtils.nodeIsURI(aNode)) {
+ o.uri = aNode.uri;
+ }
+ else if (PlacesUtils.nodeIsFolder(aNode)) {
+ o.children = [];
+ PlacesUtils.asContainer(aNode).containerOpen = true;
+ for (let i = 0; i < aNode.childCount; ++i) {
+ o.children.push(hierarchyToObj(aNode.getChild(i)));
+ }
+ aNode.containerOpen = false;
+ }
+ return o;
+}
diff --git a/browser/components/places/tests/unit/xpcshell.ini b/browser/components/places/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..1c40e1c53
--- /dev/null
+++ b/browser/components/places/tests/unit/xpcshell.ini
@@ -0,0 +1,25 @@
+[DEFAULT]
+head = head_bookmarks.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ bookmarks.glue.html
+ bookmarks.glue.json
+ corruptDB.sqlite
+ distribution.ini
+
+[test_421483.js]
+[test_browserGlue_bookmarkshtml.js]
+[test_browserGlue_corrupt.js]
+[test_browserGlue_corrupt_nobackup.js]
+[test_browserGlue_corrupt_nobackup_default.js]
+[test_browserGlue_distribution.js]
+[test_browserGlue_migrate.js]
+[test_browserGlue_prefs.js]
+[test_browserGlue_restore.js]
+[test_browserGlue_smartBookmarks.js]
+[test_browserGlue_urlbar_defaultbehavior_migration.js]
+[test_clearHistory_shutdown.js]
+[test_leftpane_corruption_handling.js]
+[test_PUIU_makeTransaction.js]
diff --git a/browser/components/preferences/applicationManager.js b/browser/components/preferences/applicationManager.js
new file mode 100644
index 000000000..2e0f47a69
--- /dev/null
+++ b/browser/components/preferences/applicationManager.js
@@ -0,0 +1,102 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/* import-globals-from in-content/applications.js */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var gAppManagerDialog = {
+ _removed: [],
+
+ init: function appManager_init() {
+ this.handlerInfo = window.arguments[0];
+
+ var bundle = document.getElementById("appManagerBundle");
+ var contentText;
+ if (this.handlerInfo.type == TYPE_MAYBE_FEED)
+ contentText = bundle.getString("handleWebFeeds");
+ else {
+ var description = gApplicationsPane._describeType(this.handlerInfo);
+ var key =
+ (this.handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) ? "handleFile"
+ : "handleProtocol";
+ contentText = bundle.getFormattedString(key, [description]);
+ }
+ contentText = bundle.getFormattedString("descriptionApplications", [contentText]);
+ document.getElementById("appDescription").textContent = contentText;
+
+ var list = document.getElementById("appList");
+ var apps = this.handlerInfo.possibleApplicationHandlers.enumerate();
+ while (apps.hasMoreElements()) {
+ let app = apps.getNext();
+ if (!gApplicationsPane.isValidHandlerApp(app))
+ continue;
+
+ app.QueryInterface(Ci.nsIHandlerApp);
+ var item = list.appendItem(app.name);
+ item.setAttribute("image", gApplicationsPane._getIconURLForHandlerApp(app));
+ item.className = "listitem-iconic";
+ item.app = app;
+ }
+
+ list.selectedIndex = 0;
+ },
+
+ onOK: function appManager_onOK() {
+ if (!this._removed.length) {
+ // return early to avoid calling the |store| method.
+ return;
+ }
+
+ for (var i = 0; i < this._removed.length; ++i)
+ this.handlerInfo.removePossibleApplicationHandler(this._removed[i]);
+
+ this.handlerInfo.store();
+ },
+
+ onCancel: function appManager_onCancel() {
+ // do nothing
+ },
+
+ remove: function appManager_remove() {
+ var list = document.getElementById("appList");
+ this._removed.push(list.selectedItem.app);
+ var index = list.selectedIndex;
+ list.removeItemAt(index);
+ if (list.getRowCount() == 0) {
+ // The list is now empty, make the bottom part disappear
+ document.getElementById("appDetails").hidden = true;
+ }
+ else {
+ // Select the item at the same index, if we removed the last
+ // item of the list, select the previous item
+ if (index == list.getRowCount())
+ --index;
+ list.selectedIndex = index;
+ }
+ },
+
+ onSelect: function appManager_onSelect() {
+ var list = document.getElementById("appList");
+ if (!list.selectedItem) {
+ document.getElementById("remove").disabled = true;
+ return;
+ }
+ document.getElementById("remove").disabled = false;
+ var app = list.selectedItem.app;
+ var address = "";
+ if (app instanceof Ci.nsILocalHandlerApp)
+ address = app.executable.path;
+ else if (app instanceof Ci.nsIWebHandlerApp)
+ address = app.uriTemplate;
+ else if (app instanceof Ci.nsIWebContentHandlerInfo)
+ address = app.uri;
+ document.getElementById("appLocation").value = address;
+ var bundle = document.getElementById("appManagerBundle");
+ var appType = app instanceof Ci.nsILocalHandlerApp ? "descriptionLocalApp"
+ : "descriptionWebApp";
+ document.getElementById("appType").value = bundle.getString(appType);
+ }
+};
diff --git a/browser/components/preferences/applicationManager.xul b/browser/components/preferences/applicationManager.xul
new file mode 100644
index 000000000..ea9d3a53f
--- /dev/null
+++ b/browser/components/preferences/applicationManager.xul
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/applicationManager.dtd">
+
+<dialog id="appManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel"
+ onload="gAppManagerDialog.init();"
+ ondialogaccept="gAppManagerDialog.onOK();"
+ ondialogcancel="gAppManagerDialog.onCancel();"
+ title="&appManager.title;"
+ style="&appManager.style;"
+ persist="screenX screenY">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/applicationManager.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/applications.js"/>
+
+ <commandset id="appManagerCommandSet">
+ <command id="cmd_remove"
+ oncommand="gAppManagerDialog.remove();"
+ disabled="true"/>
+ </commandset>
+
+ <keyset id="appManagerKeyset">
+ <key id="delete" keycode="VK_DELETE" command="cmd_remove"/>
+ </keyset>
+
+ <stringbundleset id="appManagerBundleset">
+ <stringbundle id="appManagerBundle"
+ src="chrome://browser/locale/preferences/applicationManager.properties"/>
+ </stringbundleset>
+
+ <description id="appDescription"/>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <listbox id="appList" onselect="gAppManagerDialog.onSelect();" flex="1"/>
+ <vbox>
+ <button id="remove"
+ label="&remove.label;"
+ accesskey="&remove.accesskey;"
+ command="cmd_remove"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+ <vbox id="appDetails">
+ <separator class="thin"/>
+ <label id="appType"/>
+ <textbox id="appLocation" readonly="true" class="plain"/>
+ </vbox>
+</dialog>
diff --git a/browser/components/preferences/blocklists.js b/browser/components/preferences/blocklists.js
new file mode 100644
index 000000000..bc39eb6bd
--- /dev/null
+++ b/browser/components/preferences/blocklists.js
@@ -0,0 +1,209 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+const BASE_LIST_ID = "base";
+const CONTENT_LIST_ID = "content";
+const TRACK_SUFFIX = "-track-digest256";
+const TRACKING_TABLE_PREF = "urlclassifier.trackingTable";
+const LISTS_PREF_BRANCH = "browser.safebrowsing.provider.mozilla.lists.";
+const UPDATE_TIME_PREF = "browser.safebrowsing.provider.mozilla.nextupdatetime";
+
+var gBlocklistManager = {
+ _type: "",
+ _blockLists: [],
+ _brandShortName : null,
+ _bundle: null,
+ _tree: null,
+
+ _view: {
+ _rowCount: 0,
+ get rowCount() {
+ return this._rowCount;
+ },
+ getCellText: function (row, column) {
+ if (column.id == "listCol") {
+ let list = gBlocklistManager._blockLists[row];
+ let desc = list.description ? list.description : "";
+ let text = gBlocklistManager._bundle.getFormattedString("mozNameTemplate",
+ [list.name, desc]);
+ return text;
+ }
+ return "";
+ },
+
+ isSeparator: function(index) { return false; },
+ isSorted: function() { return false; },
+ isContainer: function(index) { return false; },
+ setTree: function(tree) {},
+ getImageSrc: function(row, column) {},
+ getProgressMode: function(row, column) {},
+ getCellValue: function(row, column) {
+ if (column.id == "selectionCol")
+ return gBlocklistManager._blockLists[row].selected;
+ return undefined;
+ },
+ cycleHeader: function(column) {},
+ getRowProperties: function(row) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ getCellProperties: function(row, column) {
+ if (column.id == "selectionCol") {
+ return "checkmark";
+ }
+
+ return "";
+ }
+ },
+
+ onWindowKeyPress: function (event) {
+ if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+ window.close();
+ } else if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
+ gBlocklistManager.onApplyChanges();
+ }
+ },
+
+ onLoad: function () {
+ this._bundle = document.getElementById("bundlePreferences");
+ let params = window.arguments[0];
+ this.init(params);
+ },
+
+ init: function (params) {
+ if (this._type) {
+ // reusing an open dialog, clear the old observer
+ this.uninit();
+ }
+
+ this._type = "tracking";
+ this._brandShortName = params.brandShortName;
+
+ let blocklistsText = document.getElementById("blocklistsText");
+ while (blocklistsText.hasChildNodes()) {
+ blocklistsText.removeChild(blocklistsText.firstChild);
+ }
+ blocklistsText.appendChild(document.createTextNode(params.introText));
+
+ document.title = params.windowTitle;
+
+ let treecols = document.getElementsByTagName("treecols")[0];
+ treecols.addEventListener("click", event => {
+ if (event.target.nodeName != "treecol" || event.button != 0) {
+ return;
+ }
+ });
+
+ this._loadBlockLists();
+ },
+
+ uninit: function () {},
+
+ onListSelected: function () {
+ for (let list of this._blockLists) {
+ list.selected = false;
+ }
+ this._blockLists[this._tree.currentIndex].selected = true;
+
+ this._updateTree();
+ },
+
+ onApplyChanges: function () {
+ let activeList = this._getActiveList();
+ let selected = null;
+ for (let list of this._blockLists) {
+ if (list.selected) {
+ selected = list;
+ break;
+ }
+ }
+
+ if (activeList !== selected.id) {
+ const Cc = Components.classes, Ci = Components.interfaces;
+ let msg = this._bundle.getFormattedString("blocklistChangeRequiresRestart",
+ [this._brandShortName]);
+ let title = this._bundle.getFormattedString("shouldRestartTitle",
+ [this._brandShortName]);
+ let shouldProceed = Services.prompt.confirm(window, title, msg);
+ if (shouldProceed) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ shouldProceed = !cancelQuit.data;
+
+ if (shouldProceed) {
+ let trackingTable = Services.prefs.getCharPref(TRACKING_TABLE_PREF);
+ if (selected.id != CONTENT_LIST_ID) {
+ trackingTable = trackingTable.replace("," + CONTENT_LIST_ID + TRACK_SUFFIX, "");
+ } else {
+ trackingTable += "," + CONTENT_LIST_ID + TRACK_SUFFIX;
+ }
+ Services.prefs.setCharPref(TRACKING_TABLE_PREF, trackingTable);
+ Services.prefs.setCharPref(UPDATE_TIME_PREF, 42);
+
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit |
+ Ci.nsIAppStartup.eRestart);
+ }
+ }
+
+ // Don't close the dialog in case we didn't quit.
+ return;
+ }
+ window.close();
+ },
+
+ _loadBlockLists: function () {
+ this._blockLists = [];
+
+ // Load blocklists into a table.
+ let branch = Services.prefs.getBranch(LISTS_PREF_BRANCH);
+ let itemArray = branch.getChildList("");
+ for (let itemName of itemArray) {
+ try {
+ this._createOrUpdateBlockList(itemName);
+ } catch (e) {
+ // Ignore bogus or missing list name.
+ continue;
+ }
+ }
+
+ this._updateTree();
+ },
+
+ _createOrUpdateBlockList: function (itemName) {
+ let branch = Services.prefs.getBranch(LISTS_PREF_BRANCH);
+ let key = branch.getCharPref(itemName);
+ let value = this._bundle.getString(key);
+
+ let suffix = itemName.slice(itemName.lastIndexOf("."));
+ let id = itemName.replace(suffix, "");
+ let list = this._blockLists.find(el => el.id === id);
+ if (!list) {
+ list = { id };
+ this._blockLists.push(list);
+ }
+ list.selected = this._getActiveList() === id;
+
+ // Get the property name from the suffix (e.g. ".name" -> "name").
+ let prop = suffix.slice(1);
+ list[prop] = value;
+
+ return list;
+ },
+
+ _updateTree: function () {
+ this._tree = document.getElementById("blocklistsTree");
+ this._view._rowCount = this._blockLists.length;
+ this._tree.view = this._view;
+ },
+
+ _getActiveList: function () {
+ let trackingTable = Services.prefs.getCharPref(TRACKING_TABLE_PREF);
+ return trackingTable.includes(CONTENT_LIST_ID) ? CONTENT_LIST_ID : BASE_LIST_ID;
+ }
+};
+
+function initWithParams(params) {
+ gBlocklistManager.init(params);
+}
diff --git a/browser/components/preferences/blocklists.xul b/browser/components/preferences/blocklists.xul
new file mode 100644
index 000000000..523c20810
--- /dev/null
+++ b/browser/components/preferences/blocklists.xul
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/blocklists.dtd" >
+
+<window id="BlocklistsDialog" class="windowDialog"
+ windowtype="Browser:Blocklists"
+ title="&window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: &window.width;;"
+ onload="gBlocklistManager.onLoad();"
+ onunload="gBlocklistManager.uninit();"
+ persist="screenX screenY width height"
+ onkeypress="gBlocklistManager.onWindowKeyPress(event);">
+
+ <script src="chrome://global/content/treeUtils.js"/>
+ <script src="chrome://browser/content/preferences/blocklists.js"/>
+
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <keyset>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+ </keyset>
+
+ <vbox class="contentPane largeDialogContainer" flex="1">
+ <description id="blocklistsText" control="url"/>
+ <separator class="thin"/>
+ <tree id="blocklistsTree" flex="1" style="height: 18em;"
+ hidecolumnpicker="true"
+ onselect="gBlocklistManager.onListSelected();">
+ <treecols>
+ <treecol id="selectionCol" label="" flex="1" sortable="false"
+ type="checkbox"/>
+ <treecol id="listCol" label="&treehead.list.label;" flex="80"
+ sortable="false"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ <vbox>
+ <spacer flex="1"/>
+ <hbox class="actionButtons" align="right" flex="1">
+ <button oncommand="close();" icon="close"
+ label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
+ <button id="btnApplyChanges" oncommand="gBlocklistManager.onApplyChanges();" icon="save"
+ label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
+ </hbox>
+ </vbox>
+</window>
diff --git a/browser/components/preferences/colors.xul b/browser/components/preferences/colors.xul
new file mode 100644
index 000000000..f2109aed7
--- /dev/null
+++ b/browser/components/preferences/colors.xul
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+#ifdef XP_MACOSX
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+#endif
+
+<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/colors.dtd" >
+
+<prefwindow id="ColorsDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&colorsDialog.title;"
+ dlgbuttons="accept,cancel,help"
+ ondialoghelp="openPrefsHelp()"
+#ifdef XP_MACOSX
+ style="width: &window.macWidth; !important;">
+#else
+ style="width: &window.width; !important;">
+#endif
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <prefpane id="ColorsDialogPane"
+ helpTopic="prefs-fonts-and-colors">
+
+ <preferences>
+ <preference id="browser.display.document_color_use" name="browser.display.document_color_use" type="int"/>
+ <preference id="browser.anchor_color" name="browser.anchor_color" type="string"/>
+ <preference id="browser.visited_color" name="browser.visited_color" type="string"/>
+ <preference id="browser.underline_anchors" name="browser.underline_anchors" type="bool"/>
+ <preference id="browser.display.foreground_color" name="browser.display.foreground_color" type="string"/>
+ <preference id="browser.display.background_color" name="browser.display.background_color" type="string"/>
+ <preference id="browser.display.use_system_colors" name="browser.display.use_system_colors" type="bool"/>
+ </preferences>
+
+ <hbox>
+ <groupbox flex="1">
+ <caption label="&color;"/>
+ <hbox align="center">
+ <label value="&textColor.label;" accesskey="&textColor.accesskey;" control="foregroundtextmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="foregroundtextmenu" palettename="standard"
+ preference="browser.display.foreground_color"/>
+ </hbox>
+ <hbox align="center" style="margin-top: 5px">
+ <label value="&backgroundColor.label;" accesskey="&backgroundColor.accesskey;" control="backgroundmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="backgroundmenu" palettename="standard"
+ preference="browser.display.background_color"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox align="center">
+ <checkbox id="browserUseSystemColors" label="&useSystemColors.label;" accesskey="&useSystemColors.accesskey;"
+ preference="browser.display.use_system_colors"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox flex="1">
+ <caption label="&links;"/>
+ <hbox align="center">
+ <label value="&linkColor.label;" accesskey="&linkColor.accesskey;" control="unvisitedlinkmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="unvisitedlinkmenu" palettename="standard"
+ preference="browser.anchor_color"/>
+ </hbox>
+ <hbox align="center" style="margin-top: 5px">
+ <label value="&visitedLinkColor.label;" accesskey="&visitedLinkColor.accesskey;" control="visitedlinkmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="visitedlinkmenu" palettename="standard"
+ preference="browser.visited_color"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox align="center">
+ <checkbox id="browserUnderlineAnchors" label="&underlineLinks.label;" accesskey="&underlineLinks.accesskey;"
+ preference="browser.underline_anchors"/>
+ </hbox>
+ </groupbox>
+ </hbox>
+#ifdef XP_WIN
+ <vbox align="start">
+#else
+ <vbox>
+#endif
+ <label accesskey="&overridePageColors.accesskey;"
+ control="useDocumentColors">&overridePageColors.label;</label>
+ <menulist id="useDocumentColors" preference="browser.display.document_color_use">
+ <menupopup>
+ <menuitem label="&overridePageColors.always.label;"
+ value="2" id="documentColorAlways"/>
+ <menuitem label="&overridePageColors.auto.label;"
+ value="0" id="documentColorAutomatic"/>
+ <menuitem label="&overridePageColors.never.label;"
+ value="1" id="documentColorNever"/>
+ </menupopup>
+ </menulist>
+ </vbox>
+ </prefpane>
+</prefwindow>
diff --git a/browser/components/preferences/connection.js b/browser/components/preferences/connection.js
new file mode 100644
index 000000000..f6b395a2d
--- /dev/null
+++ b/browser/components/preferences/connection.js
@@ -0,0 +1,213 @@
+/* -*- 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/. */
+
+var gConnectionsDialog = {
+ beforeAccept: function ()
+ {
+ var proxyTypePref = document.getElementById("network.proxy.type");
+ if (proxyTypePref.value == 2) {
+ this.doAutoconfigURLFixup();
+ return true;
+ }
+
+ if (proxyTypePref.value != 1)
+ return true;
+
+ var httpProxyURLPref = document.getElementById("network.proxy.http");
+ var httpProxyPortPref = document.getElementById("network.proxy.http_port");
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+
+ // If the port is 0 and the proxy server is specified, focus on the port and cancel submission.
+ for (let prefName of ["http", "ssl", "ftp", "socks"]) {
+ let proxyPortPref = document.getElementById("network.proxy." + prefName + "_port");
+ let proxyPref = document.getElementById("network.proxy." + prefName);
+ // Only worry about ports which are currently active. If the share option is on, then ignore
+ // all ports except the HTTP port
+ if (proxyPref.value != "" && proxyPortPref.value == 0 &&
+ (prefName == "http" || !shareProxiesPref.value)) {
+ document.getElementById("networkProxy" + prefName.toUpperCase() + "_Port").focus();
+ return false;
+ }
+ }
+
+ // In the case of a shared proxy preference, backup the current values and update with the HTTP value
+ if (shareProxiesPref.value) {
+ var proxyPrefs = ["ssl", "ftp", "socks"];
+ for (var i = 0; i < proxyPrefs.length; ++i) {
+ var proxyServerURLPref = document.getElementById("network.proxy." + proxyPrefs[i]);
+ var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port");
+ var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]);
+ var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port");
+ backupServerURLPref.value = backupServerURLPref.value || proxyServerURLPref.value;
+ backupPortPref.value = backupPortPref.value || proxyPortPref.value;
+ proxyServerURLPref.value = httpProxyURLPref.value;
+ proxyPortPref.value = httpProxyPortPref.value;
+ }
+ }
+
+ this.sanitizeNoProxiesPref();
+
+ return true;
+ },
+
+ checkForSystemProxy: function ()
+ {
+ if ("@mozilla.org/system-proxy-settings;1" in Components.classes)
+ document.getElementById("systemPref").removeAttribute("hidden");
+ },
+
+ proxyTypeChanged: function ()
+ {
+ var proxyTypePref = document.getElementById("network.proxy.type");
+
+ // Update http
+ var httpProxyURLPref = document.getElementById("network.proxy.http");
+ httpProxyURLPref.disabled = proxyTypePref.value != 1;
+ var httpProxyPortPref = document.getElementById("network.proxy.http_port");
+ httpProxyPortPref.disabled = proxyTypePref.value != 1;
+
+ // Now update the other protocols
+ this.updateProtocolPrefs();
+
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ shareProxiesPref.disabled = proxyTypePref.value != 1;
+ var autologinProxyPref = document.getElementById("signon.autologin.proxy");
+ autologinProxyPref.disabled = proxyTypePref.value == 0;
+ var noProxiesPref = document.getElementById("network.proxy.no_proxies_on");
+ noProxiesPref.disabled = proxyTypePref.value != 1;
+
+ var autoconfigURLPref = document.getElementById("network.proxy.autoconfig_url");
+ autoconfigURLPref.disabled = proxyTypePref.value != 2;
+
+ this.updateReloadButton();
+ },
+
+ updateDNSPref: function ()
+ {
+ var socksVersionPref = document.getElementById("network.proxy.socks_version");
+ var socksDNSPref = document.getElementById("network.proxy.socks_remote_dns");
+ var proxyTypePref = document.getElementById("network.proxy.type");
+ var isDefinitelySocks4 = !socksVersionPref.disabled && socksVersionPref.value == 4;
+ socksDNSPref.disabled = (isDefinitelySocks4 || proxyTypePref.value == 0);
+ return undefined;
+ },
+
+ updateReloadButton: function ()
+ {
+ // Disable the "Reload PAC" button if the selected proxy type is not PAC or
+ // if the current value of the PAC textbox does not match the value stored
+ // in prefs. Likewise, disable the reload button if PAC is not configured
+ // in prefs.
+
+ var typedURL = document.getElementById("networkProxyAutoconfigURL").value;
+ var proxyTypeCur = document.getElementById("network.proxy.type").value;
+
+ var prefs =
+ Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+ var pacURL = prefs.getCharPref("network.proxy.autoconfig_url");
+ var proxyType = prefs.getIntPref("network.proxy.type");
+
+ var disableReloadPref =
+ document.getElementById("pref.advanced.proxies.disable_button.reload");
+ disableReloadPref.disabled =
+ (proxyTypeCur != 2 || proxyType != 2 || typedURL != pacURL);
+ },
+
+ readProxyType: function ()
+ {
+ this.proxyTypeChanged();
+ return undefined;
+ },
+
+ updateProtocolPrefs: function ()
+ {
+ var proxyTypePref = document.getElementById("network.proxy.type");
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ var proxyPrefs = ["ssl", "ftp", "socks"];
+ for (var i = 0; i < proxyPrefs.length; ++i) {
+ var proxyServerURLPref = document.getElementById("network.proxy." + proxyPrefs[i]);
+ var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port");
+
+ // Restore previous per-proxy custom settings, if present.
+ if (!shareProxiesPref.value) {
+ var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]);
+ var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port");
+ if (backupServerURLPref.hasUserValue) {
+ proxyServerURLPref.value = backupServerURLPref.value;
+ backupServerURLPref.reset();
+ }
+ if (backupPortPref.hasUserValue) {
+ proxyPortPref.value = backupPortPref.value;
+ backupPortPref.reset();
+ }
+ }
+
+ proxyServerURLPref.updateElements();
+ proxyPortPref.updateElements();
+ proxyServerURLPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value;
+ proxyPortPref.disabled = proxyServerURLPref.disabled;
+ }
+ var socksVersionPref = document.getElementById("network.proxy.socks_version");
+ socksVersionPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value;
+ this.updateDNSPref();
+ return undefined;
+ },
+
+ readProxyProtocolPref: function (aProtocol, aIsPort)
+ {
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ if (shareProxiesPref.value) {
+ var pref = document.getElementById("network.proxy.http" + (aIsPort ? "_port" : ""));
+ return pref.value;
+ }
+
+ var backupPref = document.getElementById("network.proxy.backup." + aProtocol + (aIsPort ? "_port" : ""));
+ return backupPref.hasUserValue ? backupPref.value : undefined;
+ },
+
+ reloadPAC: function ()
+ {
+ Components.classes["@mozilla.org/network/protocol-proxy-service;1"].
+ getService().reloadPAC();
+ },
+
+ doAutoconfigURLFixup: function ()
+ {
+ var autoURL = document.getElementById("networkProxyAutoconfigURL");
+ var autoURLPref = document.getElementById("network.proxy.autoconfig_url");
+ var URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(Components.interfaces.nsIURIFixup);
+ try {
+ autoURLPref.value = autoURL.value = URIFixup.createFixupURI(autoURL.value, 0).spec;
+ } catch (ex) {}
+ },
+
+ sanitizeNoProxiesPref: function()
+ {
+ var noProxiesPref = document.getElementById("network.proxy.no_proxies_on");
+ // replace substrings of ; and \n with commas if they're neither immediately
+ // preceded nor followed by a valid separator character
+ noProxiesPref.value = noProxiesPref.value.replace(/([^, \n;])[;\n]+(?![,\n;])/g, '$1,');
+ // replace any remaining ; and \n since some may follow commas, etc.
+ noProxiesPref.value = noProxiesPref.value.replace(/[;\n]/g, '');
+ },
+
+ readHTTPProxyServer: function ()
+ {
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ if (shareProxiesPref.value)
+ this.updateProtocolPrefs();
+ return undefined;
+ },
+
+ readHTTPProxyPort: function ()
+ {
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ if (shareProxiesPref.value)
+ this.updateProtocolPrefs();
+ return undefined;
+ }
+};
diff --git a/browser/components/preferences/connection.xul b/browser/components/preferences/connection.xul
new file mode 100644
index 000000000..a3f0d082a
--- /dev/null
+++ b/browser/components/preferences/connection.xul
@@ -0,0 +1,173 @@
+<?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 SYSTEM "chrome://browser/locale/preferences/connection.dtd">
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<prefwindow id="ConnectionsDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&connectionsDialog.title;"
+ dlgbuttons="accept,cancel,help"
+ onbeforeaccept="return gConnectionsDialog.beforeAccept();"
+ onload="gConnectionsDialog.checkForSystemProxy();"
+ ondialoghelp="openPrefsHelp()"
+#ifdef XP_MACOSX
+ style="width: &window.macWidth2; !important;">
+#else
+ style="width: &window.width2; !important;">
+#endif
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <prefpane id="ConnectionsDialogPane"
+ class="largeDialogContainer"
+ helpTopic="prefs-connection-settings">
+
+ <preferences>
+ <preference id="network.proxy.type" name="network.proxy.type" type="int" onchange="gConnectionsDialog.proxyTypeChanged();"/>
+ <preference id="network.proxy.http" name="network.proxy.http" type="string"/>
+ <preference id="network.proxy.http_port" name="network.proxy.http_port" type="int"/>
+ <preference id="network.proxy.ftp" name="network.proxy.ftp" type="string"/>
+ <preference id="network.proxy.ftp_port" name="network.proxy.ftp_port" type="int"/>
+ <preference id="network.proxy.ssl" name="network.proxy.ssl" type="string"/>
+ <preference id="network.proxy.ssl_port" name="network.proxy.ssl_port" type="int"/>
+ <preference id="network.proxy.socks" name="network.proxy.socks" type="string"/>
+ <preference id="network.proxy.socks_port" name="network.proxy.socks_port" type="int"/>
+ <preference id="network.proxy.socks_version" name="network.proxy.socks_version" type="int" onchange="gConnectionsDialog.updateDNSPref();"/>
+ <preference id="network.proxy.socks_remote_dns" name="network.proxy.socks_remote_dns" type="bool"/>
+ <preference id="network.proxy.no_proxies_on" name="network.proxy.no_proxies_on" type="string"/>
+ <preference id="network.proxy.autoconfig_url" name="network.proxy.autoconfig_url" type="string"/>
+ <preference id="network.proxy.share_proxy_settings"
+ name="network.proxy.share_proxy_settings"
+ type="bool"/>
+ <preference id="signon.autologin.proxy"
+ name="signon.autologin.proxy"
+ type="bool"/>
+
+ <preference id="pref.advanced.proxies.disable_button.reload"
+ name="pref.advanced.proxies.disable_button.reload"
+ type="bool"/>
+
+ <preference id="network.proxy.backup.ftp" name="network.proxy.backup.ftp" type="string"/>
+ <preference id="network.proxy.backup.ftp_port" name="network.proxy.backup.ftp_port" type="int"/>
+ <preference id="network.proxy.backup.ssl" name="network.proxy.backup.ssl" type="string"/>
+ <preference id="network.proxy.backup.ssl_port" name="network.proxy.backup.ssl_port" type="int"/>
+ <preference id="network.proxy.backup.socks" name="network.proxy.backup.socks" type="string"/>
+ <preference id="network.proxy.backup.socks_port" name="network.proxy.backup.socks_port" type="int"/>
+ </preferences>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/connection.js"/>
+
+ <stringbundle id="preferencesBundle" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <groupbox>
+ <caption label="&proxyTitle.label;"/>
+
+ <radiogroup id="networkProxyType" preference="network.proxy.type"
+ onsyncfrompreference="return gConnectionsDialog.readProxyType();">
+ <radio value="0" label="&noProxyTypeRadio.label;" accesskey="&noProxyTypeRadio.accesskey;"/>
+ <radio value="4" label="&WPADTypeRadio.label;" accesskey="&WPADTypeRadio.accesskey;"/>
+ <radio value="5" label="&systemTypeRadio.label;" accesskey="&systemTypeRadio.accesskey;" id="systemPref" hidden="true"/>
+ <radio value="1" label="&manualTypeRadio.label;" accesskey="&manualTypeRadio.accesskey;"/>
+ <grid class="indent" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&http.label;" accesskey="&http.accesskey;" control="networkProxyHTTP"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxyHTTP" flex="1"
+ preference="network.proxy.http" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyServer();"/>
+ <label value="&port.label;" accesskey="&HTTPport.accesskey;" control="networkProxyHTTP_Port"/>
+ <textbox id="networkProxyHTTP_Port" type="number" max="65535" size="5"
+ preference="network.proxy.http_port" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyPort();"/>
+ </hbox>
+ </row>
+ <row>
+ <hbox/>
+ <hbox>
+ <checkbox id="shareAllProxies" label="&shareproxy.label;" accesskey="&shareproxy.accesskey;"
+ preference="network.proxy.share_proxy_settings"
+ onsyncfrompreference="return gConnectionsDialog.updateProtocolPrefs();"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&ssl.label;" accesskey="&ssl.accesskey;" control="networkProxySSL"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxySSL" flex="1" preference="network.proxy.ssl"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', false);"/>
+ <label value="&port.label;" accesskey="&SSLport.accesskey;" control="networkProxySSL_Port"/>
+ <textbox id="networkProxySSL_Port" type="number" max="65535" size="5" preference="network.proxy.ssl_port"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', true);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&ftp.label;" accesskey="&ftp.accesskey;" control="networkProxyFTP"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxyFTP" flex="1" preference="network.proxy.ftp"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', false);"/>
+ <label value="&port.label;" accesskey="&FTPport.accesskey;" control="networkProxyFTP_Port"/>
+ <textbox id="networkProxyFTP_Port" type="number" max="65535" size="5" preference="network.proxy.ftp_port"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', true);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&socks.label;" accesskey="&socks.accesskey;" control="networkProxySOCKS"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxySOCKS" flex="1" preference="network.proxy.socks"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', false);"/>
+ <label value="&port.label;" accesskey="&SOCKSport.accesskey;" control="networkProxySOCKS_Port"/>
+ <textbox id="networkProxySOCKS_Port" type="number" max="65535" size="5" preference="network.proxy.socks_port"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', true);"/>
+ </hbox>
+ </row>
+ <row>
+ <spacer/>
+ <box pack="start">
+ <radiogroup id="networkProxySOCKSVersion" orient="horizontal"
+ preference="network.proxy.socks_version">
+ <radio id="networkProxySOCKSVersion4" value="4" label="&socks4.label;" accesskey="&socks4.accesskey;" />
+ <radio id="networkProxySOCKSVersion5" value="5" label="&socks5.label;" accesskey="&socks5.accesskey;" />
+ </radiogroup>
+ </box>
+ </row>
+ <label value="&noproxy.label;" accesskey="&noproxy.accesskey;" control="networkProxyNone"/>
+ <textbox id="networkProxyNone" preference="network.proxy.no_proxies_on" multiline="true" rows="2"/>
+ <label value="&noproxyExplain.label;" control="networkProxyNone"/>
+ </rows>
+ </grid>
+ <radio value="2" label="&autoTypeRadio.label;" accesskey="&autoTypeRadio.accesskey;"/>
+ <hbox class="indent" flex="1" align="center">
+ <textbox id="networkProxyAutoconfigURL" flex="1" preference="network.proxy.autoconfig_url"
+ oninput="gConnectionsDialog.updateReloadButton();"/>
+ <button id="autoReload" icon="refresh"
+ label="&reload.label;" accesskey="&reload.accesskey;"
+ oncommand="gConnectionsDialog.reloadPAC();"
+ preference="pref.advanced.proxies.disable_button.reload"/>
+ </hbox>
+ </radiogroup>
+ </groupbox>
+ <separator class="thin"/>
+ <checkbox id="autologinProxy"
+ label="&autologinproxy.label;"
+ accesskey="&autologinproxy.accesskey;"
+ preference="signon.autologin.proxy"
+ tooltiptext="&autologinproxy.tooltip;"/>
+ <checkbox id="networkProxySOCKSRemoteDNS" preference="network.proxy.socks_remote_dns" label="&socksRemoteDNS.label2;" accesskey="&socksRemoteDNS.accesskey;" />
+ <separator/>
+ </prefpane>
+</prefwindow>
diff --git a/browser/components/preferences/containers.js b/browser/components/preferences/containers.js
new file mode 100644
index 000000000..6ca5853f7
--- /dev/null
+++ b/browser/components/preferences/containers.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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
+
+const containersBundle = Services.strings.createBundle("chrome://browser/locale/preferences/containers.properties");
+
+const HTMLNS = "http://www.w3.org/1999/xhtml";
+
+let gContainersManager = {
+ icons: [
+ "fingerprint",
+ "briefcase",
+ "dollar",
+ "cart",
+ "circle"
+ ],
+
+ colors: [
+ "blue",
+ "turquoise",
+ "green",
+ "yellow",
+ "orange",
+ "red",
+ "pink",
+ "purple"
+ ],
+
+ onLoad() {
+ let params = window.arguments[0] || {};
+ this.init(params);
+ },
+
+ init(aParams) {
+ this.userContextId = aParams.userContextId || null;
+ this.identity = aParams.identity;
+
+ if (aParams.windowTitle) {
+ document.title = aParams.windowTitle;
+ }
+
+ const iconWrapper = document.getElementById("iconWrapper");
+ iconWrapper.appendChild(this.createIconButtons());
+
+ const colorWrapper = document.getElementById("colorWrapper");
+ colorWrapper.appendChild(this.createColorSwatches());
+
+ if (this.identity.name) {
+ const name = document.getElementById("name");
+ name.value = this.identity.name;
+ this.checkForm();
+ }
+
+ this.setLabelsMinWidth();
+
+ // This is to prevent layout jank caused by the svgs and outlines rendering at different times
+ document.getElementById("containers-content").removeAttribute("hidden");
+ },
+
+ setLabelsMinWidth() {
+ const labelMinWidth = containersBundle.GetStringFromName("containers.labelMinWidth");
+ const labels = [
+ document.getElementById("nameLabel"),
+ document.getElementById("iconLabel"),
+ document.getElementById("colorLabel")
+ ];
+ for (let label of labels) {
+ label.style.minWidth = labelMinWidth;
+ }
+ },
+
+ uninit() {
+ },
+
+ // Check if name string as to if the form can be submitted
+ checkForm() {
+ const name = document.getElementById("name");
+ let btnApplyChanges = document.getElementById("btnApplyChanges");
+ if (!name.value) {
+ btnApplyChanges.setAttribute("disabled", true);
+ } else {
+ btnApplyChanges.removeAttribute("disabled");
+ }
+ },
+
+ createIconButtons(defaultIcon) {
+ let radiogroup = document.createElement("radiogroup");
+ radiogroup.setAttribute("id", "icon");
+ radiogroup.className = "icon-buttons";
+
+ for (let icon of this.icons) {
+ let iconSwatch = document.createElement("radio");
+ iconSwatch.id = "iconbutton-" + icon;
+ iconSwatch.name = "icon";
+ iconSwatch.type = "radio";
+ iconSwatch.value = icon;
+
+ if (this.identity.icon && this.identity.icon == icon) {
+ iconSwatch.setAttribute("selected", true);
+ }
+
+ iconSwatch.setAttribute("label",
+ containersBundle.GetStringFromName(`containers.${icon}.label`));
+ let iconElement = document.createElement("hbox");
+ iconElement.className = 'userContext-icon';
+ iconElement.setAttribute("data-identity-icon", icon);
+
+ iconSwatch.appendChild(iconElement);
+ radiogroup.appendChild(iconSwatch);
+ }
+
+ return radiogroup;
+ },
+
+ createColorSwatches(defaultColor) {
+ let radiogroup = document.createElement("radiogroup");
+ radiogroup.setAttribute("id", "color");
+
+ for (let color of this.colors) {
+ let colorSwatch = document.createElement("radio");
+ colorSwatch.id = "colorswatch-" + color;
+ colorSwatch.name = "color";
+ colorSwatch.type = "radio";
+ colorSwatch.value = color;
+
+ if (this.identity.color && this.identity.color == color) {
+ colorSwatch.setAttribute("selected", true);
+ }
+
+ colorSwatch.setAttribute("label",
+ containersBundle.GetStringFromName(`containers.${color}.label`));
+ let iconElement = document.createElement("hbox");
+ iconElement.className = 'userContext-icon';
+ iconElement.setAttribute("data-identity-icon", "circle");
+ iconElement.setAttribute("data-identity-color", color);
+
+ colorSwatch.appendChild(iconElement);
+ radiogroup.appendChild(colorSwatch);
+ }
+ return radiogroup;
+ },
+
+ onApplyChanges() {
+ let icon = document.getElementById("icon").value;
+ let color = document.getElementById("color").value;
+ let name = document.getElementById("name").value;
+
+ if (this.icons.indexOf(icon) == -1) {
+ throw "Internal error. The icon value doesn't match.";
+ }
+
+ if (this.colors.indexOf(color) == -1) {
+ throw "Internal error. The color value doesn't match.";
+ }
+
+ if (this.userContextId) {
+ ContextualIdentityService.update(this.userContextId,
+ name,
+ icon,
+ color);
+ } else {
+ ContextualIdentityService.create(name,
+ icon,
+ color);
+ }
+ window.parent.location.reload()
+ },
+
+ onWindowKeyPress(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
+ window.close();
+ }
+}
diff --git a/browser/components/preferences/containers.xul b/browser/components/preferences/containers.xul
new file mode 100644
index 000000000..62a775fe4
--- /dev/null
+++ b/browser/components/preferences/containers.xul
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/containers.dtd" >
+
+<window id="ContainersDialog" class="windowDialog"
+ windowtype="Browser:Permissions"
+ title="&window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: &window.width;;"
+ onload="gContainersManager.onLoad();"
+ onunload="gContainersManager.uninit();"
+ persist="screenX screenY width height"
+ onkeypress="gContainersManager.onWindowKeyPress(event);">
+
+ <script src="chrome://global/content/treeUtils.js"/>
+ <script src="chrome://browser/content/preferences/containers.js"/>
+
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <keyset>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+ </keyset>
+
+ <vbox class="contentPane largeDialogContainer" flex="1" hidden="true" id="containers-content">
+ <description id="permissionsText" control="url"/>
+ <separator class="thin"/>
+ <hbox align="start">
+ <label id="nameLabel" control="url" value="&name.label;" accesskey="&name.accesskey;"/>
+ <textbox id="name" flex="1" onkeyup="gContainersManager.checkForm();" />
+ </hbox>
+ <hbox align="center" id="iconWrapper">
+ <label id="iconLabel" control="url" value="&icon.label;" accesskey="&icon.accesskey;"/>
+ </hbox>
+ <hbox align="center" id="colorWrapper">
+ <label id="colorLabel" control="url" value="&color.label;" accesskey="&color.accesskey;"/>
+ </hbox>
+ </vbox>
+ <vbox>
+ <hbox class="actionButtons" align="right" flex="1">
+ <button id="btnApplyChanges" disabled="true" oncommand="gContainersManager.onApplyChanges();" icon="save"
+ label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
+ </hbox>
+ </vbox>
+</window>
diff --git a/browser/components/preferences/cookies.js b/browser/components/preferences/cookies.js
new file mode 100644
index 000000000..1042642da
--- /dev/null
+++ b/browser/components/preferences/cookies.js
@@ -0,0 +1,948 @@
+/* -*- 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 nsICookie = Components.interfaces.nsICookie;
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm")
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
+ "resource://gre/modules/ContextualIdentityService.jsm");
+
+var gCookiesWindow = {
+ _cm : Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager),
+ _hosts : {},
+ _hostOrder : [],
+ _tree : null,
+ _bundle : null,
+
+ init: function () {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(this, "cookie-changed", false);
+ os.addObserver(this, "perm-changed", false);
+
+ this._bundle = document.getElementById("bundlePreferences");
+ this._tree = document.getElementById("cookiesList");
+
+ this._populateList(true);
+
+ document.getElementById("filter").focus();
+
+ if (!Services.prefs.getBoolPref("privacy.userContext.enabled")) {
+ document.getElementById("userContextRow").hidden = true;
+ }
+ },
+
+ uninit: function () {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(this, "cookie-changed");
+ os.removeObserver(this, "perm-changed");
+ },
+
+ _populateList: function (aInitialLoad) {
+ this._loadCookies();
+ this._tree.view = this._view;
+ if (aInitialLoad)
+ this.sort("rawHost");
+ if (this._view.rowCount > 0)
+ this._tree.view.selection.select(0);
+
+ if (aInitialLoad) {
+ if ("arguments" in window &&
+ window.arguments[0] &&
+ window.arguments[0].filterString)
+ this.setFilter(window.arguments[0].filterString);
+ }
+ else if (document.getElementById("filter").value != "") {
+ this.filter();
+ }
+
+ this._updateRemoveAllButton();
+
+ this._saveState();
+ },
+
+ _cookieEquals: function (aCookieA, aCookieB, aStrippedHost) {
+ return aCookieA.rawHost == aStrippedHost &&
+ aCookieA.name == aCookieB.name &&
+ aCookieA.path == aCookieB.path &&
+ ChromeUtils.isOriginAttributesEqual(aCookieA.originAttributes,
+ aCookieB.originAttributes);
+ },
+
+ _isPrivateCookie: function (aCookie) {
+ let { userContextId } = aCookie.originAttributes;
+ if (!userContextId) {
+ // Default identity is public.
+ return false;
+ }
+ return !ContextualIdentityService.getIdentityFromId(userContextId).public;
+ },
+
+ observe: function (aCookie, aTopic, aData) {
+ if (aTopic != "cookie-changed")
+ return;
+
+ if (aCookie instanceof Components.interfaces.nsICookie) {
+ if (this._isPrivateCookie(aCookie)) {
+ return;
+ }
+
+ var strippedHost = this._makeStrippedHost(aCookie.host);
+ if (aData == "changed")
+ this._handleCookieChanged(aCookie, strippedHost);
+ else if (aData == "added")
+ this._handleCookieAdded(aCookie, strippedHost);
+ }
+ else if (aData == "cleared") {
+ this._hosts = {};
+ this._hostOrder = [];
+
+ var oldRowCount = this._view._rowCount;
+ this._view._rowCount = 0;
+ this._tree.treeBoxObject.rowCountChanged(0, -oldRowCount);
+ this._view.selection.clearSelection();
+ this._updateRemoveAllButton();
+ }
+ else if (aData == "reload") {
+ // first, clear any existing entries
+ this.observe(aCookie, aTopic, "cleared");
+
+ // then, reload the list
+ this._populateList(false);
+ }
+
+ // We don't yet handle aData == "deleted" - it's a less common case
+ // and is rather complicated as selection tracking is difficult
+ },
+
+ _handleCookieChanged: function (changedCookie, strippedHost) {
+ var rowIndex = 0;
+ var cookieItem = null;
+ if (!this._view._filtered) {
+ for (let host of this._hostOrder) {
+ ++rowIndex;
+ var hostItem = this._hosts[host];
+ if (host == strippedHost) {
+ // Host matches, look for the cookie within this Host collection
+ // and update its data
+ for (let currCookie of hostItem.cookies) {
+ ++rowIndex;
+ if (this._cookieEquals(currCookie, changedCookie, strippedHost)) {
+ currCookie.value = changedCookie.value;
+ currCookie.isSecure = changedCookie.isSecure;
+ currCookie.isDomain = changedCookie.isDomain;
+ currCookie.expires = changedCookie.expires;
+ cookieItem = currCookie;
+ break;
+ }
+ }
+ }
+ else if (hostItem.open)
+ rowIndex += hostItem.cookies.length;
+ }
+ }
+ else {
+ // Just walk the filter list to find the item. It doesn't matter that
+ // we don't update the main Host collection when we do this, because
+ // when the filter is reset the Host collection is rebuilt anyway.
+ for (let currCookie of this._view._filterSet) {
+ if (this._cookieEquals(currCookie, changedCookie, strippedHost)) {
+ currCookie.value = changedCookie.value;
+ currCookie.isSecure = changedCookie.isSecure;
+ currCookie.isDomain = changedCookie.isDomain;
+ currCookie.expires = changedCookie.expires;
+ cookieItem = currCookie;
+ break;
+ }
+ }
+ }
+
+ // Make sure the tree display is up to date...
+ this._tree.treeBoxObject.invalidateRow(rowIndex);
+ // ... and if the cookie is selected, update the displayed metadata too
+ if (cookieItem != null && this._view.selection.currentIndex == rowIndex)
+ this._updateCookieData(cookieItem);
+ },
+
+ _handleCookieAdded: function (changedCookie, strippedHost) {
+ var rowCountImpact = 0;
+ var addedHost = { value: 0 };
+ this._addCookie(strippedHost, changedCookie, addedHost);
+ if (!this._view._filtered) {
+ // The Host collection for this cookie already exists, and it's not open,
+ // so don't increment the rowCountImpact becaues the user is not going to
+ // see the additional rows as they're hidden.
+ if (addedHost.value || this._hosts[strippedHost].open)
+ ++rowCountImpact;
+ }
+ else {
+ // We're in search mode, and the cookie being added matches
+ // the search condition, so add it to the list.
+ var c = this._makeCookieObject(strippedHost, changedCookie);
+ if (this._cookieMatchesFilter(c)) {
+ this._view._filterSet.push(this._makeCookieObject(strippedHost, changedCookie));
+ ++rowCountImpact;
+ }
+ }
+ // Now update the tree display at the end (we could/should re run the sort
+ // if any to get the position correct.)
+ var oldRowCount = this._rowCount;
+ this._view._rowCount += rowCountImpact;
+ this._tree.treeBoxObject.rowCountChanged(oldRowCount - 1, rowCountImpact);
+
+ this._updateRemoveAllButton();
+ },
+
+ _view: {
+ _filtered : false,
+ _filterSet : [],
+ _filterValue: "",
+ _rowCount : 0,
+ _cacheValid : 0,
+ _cacheItems : [],
+ get rowCount() {
+ return this._rowCount;
+ },
+
+ _getItemAtIndex: function (aIndex) {
+ if (this._filtered)
+ return this._filterSet[aIndex];
+
+ var start = 0;
+ var count = 0, hostIndex = 0;
+
+ var cacheIndex = Math.min(this._cacheValid, aIndex);
+ if (cacheIndex > 0) {
+ var cacheItem = this._cacheItems[cacheIndex];
+ start = cacheItem['start'];
+ count = hostIndex = cacheItem['count'];
+ }
+
+ for (let i = start; i < gCookiesWindow._hostOrder.length; ++i) { // var host in gCookiesWindow._hosts) {
+ let currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]];// gCookiesWindow._hosts[host];
+ if (!currHost) continue;
+ if (count == aIndex)
+ return currHost;
+ hostIndex = count;
+
+ var cacheEntry = { 'start' : i, 'count' : count };
+ var cacheStart = count;
+
+ if (currHost.open) {
+ if (count < aIndex && aIndex <= (count + currHost.cookies.length)) {
+ // We are looking for an entry within this host's children,
+ // enumerate them looking for the index.
+ ++count;
+ for (let cookie of currHost.cookies) {
+ if (count == aIndex) {
+ cookie.parentIndex = hostIndex;
+ return cookie;
+ }
+ ++count;
+ }
+ }
+ else {
+ // A host entry was open, but we weren't looking for an index
+ // within that host entry's children, so skip forward over the
+ // entry's children. We need to add one to increment for the
+ // host value too.
+ count += currHost.cookies.length + 1;
+ }
+ }
+ else
+ ++count;
+
+ for (let k = cacheStart; k < count; k++)
+ this._cacheItems[k] = cacheEntry;
+ this._cacheValid = count - 1;
+ }
+ return null;
+ },
+
+ _removeItemAtIndex: function (aIndex, aCount) {
+ let removeCount = aCount === undefined ? 1 : aCount;
+ if (this._filtered) {
+ // remove the cookies from the unfiltered set so that they
+ // don't reappear when the filter is changed. See bug 410863.
+ for (let i = aIndex; i < aIndex + removeCount; ++i) {
+ let item = this._filterSet[i];
+ let parent = gCookiesWindow._hosts[item.rawHost];
+ for (let j = 0; j < parent.cookies.length; ++j) {
+ if (item == parent.cookies[j]) {
+ parent.cookies.splice(j, 1);
+ break;
+ }
+ }
+ }
+ this._filterSet.splice(aIndex, removeCount);
+ return;
+ }
+
+ let item = this._getItemAtIndex(aIndex);
+ if (!item) return;
+ this._invalidateCache(aIndex - 1);
+ if (item.container) {
+ gCookiesWindow._hosts[item.rawHost] = null;
+ } else {
+ let parent = this._getItemAtIndex(item.parentIndex);
+ for (let i = 0; i < parent.cookies.length; ++i) {
+ let cookie = parent.cookies[i];
+ if (item.rawHost == cookie.rawHost &&
+ item.name == cookie.name &&
+ item.path == cookie.path &&
+ ChromeUtils.isOriginAttributesEqual(item.originAttributes,
+ cookie.originAttributes)) {
+ parent.cookies.splice(i, removeCount);
+ }
+ }
+ }
+ },
+
+ _invalidateCache: function (aIndex) {
+ this._cacheValid = Math.min(this._cacheValid, aIndex);
+ },
+
+ getCellText: function (aIndex, aColumn) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item)
+ return "";
+ if (aColumn.id == "domainCol")
+ return item.rawHost;
+ else if (aColumn.id == "nameCol")
+ return item.name;
+ }
+ else if (aColumn.id == "domainCol") {
+ return this._filterSet[aIndex].rawHost;
+ } else if (aColumn.id == "nameCol") {
+ return this._filterSet[aIndex].name;
+ }
+ return "";
+ },
+
+ _selection: null,
+ get selection () { return this._selection; },
+ set selection (val) { this._selection = val; return val; },
+ getRowProperties: function (aIndex) { return ""; },
+ getCellProperties: function (aIndex, aColumn) { return ""; },
+ getColumnProperties: function (aColumn) { return ""; },
+ isContainer: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ return item.container;
+ }
+ return false;
+ },
+ isContainerOpen: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ return item.open;
+ }
+ return false;
+ },
+ isContainerEmpty: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ return item.cookies.length == 0;
+ }
+ return false;
+ },
+ isSeparator: function (aIndex) { return false; },
+ isSorted: function (aIndex) { return false; },
+ canDrop: function (aIndex, aOrientation) { return false; },
+ drop: function (aIndex, aOrientation) {},
+ getParentIndex: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ // If an item has no parent index (i.e. it is at the top level) this
+ // function MUST return -1 otherwise we will go into an infinite loop.
+ // Containers are always top level items in the cookies tree, so make
+ // sure to return the appropriate value here.
+ if (!item || item.container) return -1;
+ return item.parentIndex;
+ }
+ return -1;
+ },
+ hasNextSibling: function (aParentIndex, aIndex) {
+ if (!this._filtered) {
+ // |aParentIndex| appears to be bogus, but we can get the real
+ // parent index by getting the entry for |aIndex| and reading the
+ // parentIndex field.
+ // The index of the last item in this host collection is the
+ // index of the parent + the size of the host collection, and
+ // aIndex has a next sibling if it is less than this value.
+ var item = this._getItemAtIndex(aIndex);
+ if (item) {
+ if (item.container) {
+ for (let i = aIndex + 1; i < this.rowCount; ++i) {
+ var subsequent = this._getItemAtIndex(i);
+ if (subsequent.container)
+ return true;
+ }
+ return false;
+ }
+ var parent = this._getItemAtIndex(item.parentIndex);
+ if (parent && parent.container)
+ return aIndex < item.parentIndex + parent.cookies.length;
+ }
+ }
+ return aIndex < this.rowCount - 1;
+ },
+ hasPreviousSibling: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ var parent = this._getItemAtIndex(item.parentIndex);
+ if (parent && parent.container)
+ return aIndex > item.parentIndex + 1;
+ }
+ return aIndex > 0;
+ },
+ getLevel: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return 0;
+ return item.level;
+ }
+ return 0;
+ },
+ getImageSrc: function (aIndex, aColumn) {},
+ getProgressMode: function (aIndex, aColumn) {},
+ getCellValue: function (aIndex, aColumn) {},
+ setTree: function (aTree) {},
+ toggleOpenState: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return;
+ this._invalidateCache(aIndex);
+ var multiplier = item.open ? -1 : 1;
+ var delta = multiplier * item.cookies.length;
+ this._rowCount += delta;
+ item.open = !item.open;
+ gCookiesWindow._tree.treeBoxObject.rowCountChanged(aIndex + 1, delta);
+ gCookiesWindow._tree.treeBoxObject.invalidateRow(aIndex);
+ }
+ },
+ cycleHeader: function (aColumn) {},
+ selectionChanged: function () {},
+ cycleCell: function (aIndex, aColumn) {},
+ isEditable: function (aIndex, aColumn) {
+ return false;
+ },
+ isSelectable: function (aIndex, aColumn) {
+ return false;
+ },
+ setCellValue: function (aIndex, aColumn, aValue) {},
+ setCellText: function (aIndex, aColumn, aValue) {},
+ performAction: function (aAction) {},
+ performActionOnRow: function (aAction, aIndex) {},
+ performActionOnCell: function (aAction, aindex, aColumn) {}
+ },
+
+ _makeStrippedHost: function (aHost) {
+ var formattedHost = aHost.charAt(0) == "." ? aHost.substring(1, aHost.length) : aHost;
+ return formattedHost.substring(0, 4) == "www." ? formattedHost.substring(4, formattedHost.length) : formattedHost;
+ },
+
+ _addCookie: function (aStrippedHost, aCookie, aHostCount) {
+ if (!(aStrippedHost in this._hosts) || !this._hosts[aStrippedHost]) {
+ this._hosts[aStrippedHost] = { cookies : [],
+ rawHost : aStrippedHost,
+ level : 0,
+ open : false,
+ container : true };
+ this._hostOrder.push(aStrippedHost);
+ ++aHostCount.value;
+ }
+
+ var c = this._makeCookieObject(aStrippedHost, aCookie);
+ this._hosts[aStrippedHost].cookies.push(c);
+ },
+
+ _makeCookieObject: function (aStrippedHost, aCookie) {
+ var c = { name : aCookie.name,
+ value : aCookie.value,
+ isDomain : aCookie.isDomain,
+ host : aCookie.host,
+ rawHost : aStrippedHost,
+ path : aCookie.path,
+ isSecure : aCookie.isSecure,
+ expires : aCookie.expires,
+ level : 1,
+ container : false,
+ originAttributes: aCookie.originAttributes };
+ return c;
+ },
+
+ _loadCookies: function () {
+ var e = this._cm.enumerator;
+ var hostCount = { value: 0 };
+ this._hosts = {};
+ this._hostOrder = [];
+ while (e.hasMoreElements()) {
+ var cookie = e.getNext();
+ if (cookie && cookie instanceof Components.interfaces.nsICookie) {
+ if (this._isPrivateCookie(cookie)) {
+ continue;
+ }
+
+ var strippedHost = this._makeStrippedHost(cookie.host);
+ this._addCookie(strippedHost, cookie, hostCount);
+ }
+ else
+ break;
+ }
+ this._view._rowCount = hostCount.value;
+ },
+
+ formatExpiresString: function (aExpires) {
+ if (aExpires) {
+ var date = new Date(1000 * aExpires);
+ const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Components.interfaces.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric' };
+ return date.toLocaleString(locale, dtOptions);
+ }
+ return this._bundle.getString("expireAtEndOfSession");
+ },
+
+ _getUserContextString: function(aUserContextId) {
+ if (parseInt(aUserContextId) == 0) {
+ return this._bundle.getString("defaultUserContextLabel");
+ }
+
+ return ContextualIdentityService.getUserContextLabel(aUserContextId);
+ },
+
+ _updateCookieData: function (aItem) {
+ var seln = this._view.selection;
+ var ids = ["name", "value", "host", "path", "isSecure", "expires", "userContext"];
+ var properties;
+
+ if (aItem && !aItem.container && seln.count > 0) {
+ properties = { name: aItem.name, value: aItem.value, host: aItem.host,
+ path: aItem.path, expires: this.formatExpiresString(aItem.expires),
+ isDomain: aItem.isDomain ? this._bundle.getString("domainColon")
+ : this._bundle.getString("hostColon"),
+ isSecure: aItem.isSecure ? this._bundle.getString("forSecureOnly")
+ : this._bundle.getString("forAnyConnection"),
+ userContext: this._getUserContextString(aItem.originAttributes.userContextId) };
+ for (let id of ids) {
+ document.getElementById(id).disabled = false;
+ }
+ }
+ else {
+ var noneSelected = this._bundle.getString("noCookieSelected");
+ properties = { name: noneSelected, value: noneSelected, host: noneSelected,
+ path: noneSelected, expires: noneSelected,
+ isSecure: noneSelected, userContext: noneSelected };
+ for (let id of ids) {
+ document.getElementById(id).disabled = true;
+ }
+ }
+ for (let property in properties)
+ document.getElementById(property).value = properties[property];
+ },
+
+ onCookieSelected: function () {
+ var item;
+ var seln = this._tree.view.selection;
+ if (!this._view._filtered)
+ item = this._view._getItemAtIndex(seln.currentIndex);
+ else
+ item = this._view._filterSet[seln.currentIndex];
+
+ this._updateCookieData(item);
+
+ var rangeCount = seln.getRangeCount();
+ var selectedCookieCount = 0;
+ for (let i = 0; i < rangeCount; ++i) {
+ var min = {}; var max = {};
+ seln.getRangeAt(i, min, max);
+ for (let j = min.value; j <= max.value; ++j) {
+ item = this._view._getItemAtIndex(j);
+ if (!item) continue;
+ if (item.container)
+ selectedCookieCount += item.cookies.length;
+ else if (!item.container)
+ ++selectedCookieCount;
+ }
+ }
+
+ let buttonLabel = this._bundle.getString("removeSelectedCookies");
+ let removeSelectedCookies = document.getElementById("removeSelectedCookies");
+ removeSelectedCookies.label = PluralForm.get(selectedCookieCount, buttonLabel)
+ .replace("#1", selectedCookieCount);
+
+ removeSelectedCookies.disabled = !(seln.count > 0);
+ },
+
+ performDeletion: function gCookiesWindow_performDeletion(deleteItems) {
+ var psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ var blockFutureCookies = false;
+ if (psvc.prefHasUserValue("network.cookie.blockFutureCookies"))
+ blockFutureCookies = psvc.getBoolPref("network.cookie.blockFutureCookies");
+ for (let item of deleteItems) {
+ this._cm.remove(item.host, item.name, item.path,
+ blockFutureCookies, item.originAttributes);
+ }
+ },
+
+ deleteCookie: function () {
+ // Selection Notes
+ // - Selection always moves to *NEXT* adjacent item unless item
+ // is last child at a given level in which case it moves to *PREVIOUS*
+ // item
+ //
+ // Selection Cases (Somewhat Complicated)
+ //
+ // 1) Single cookie selected, host has single child
+ // v cnn.com
+ // //// cnn.com ///////////// goksdjf@ ////
+ // > atwola.com
+ //
+ // Before SelectedIndex: 1 Before RowCount: 3
+ // After SelectedIndex: 0 After RowCount: 1
+ //
+ // 2) Host selected, host open
+ // v goats.com ////////////////////////////
+ // goats.com sldkkfjl
+ // goat.scom flksj133
+ // > atwola.com
+ //
+ // Before SelectedIndex: 0 Before RowCount: 4
+ // After SelectedIndex: 0 After RowCount: 1
+ //
+ // 3) Host selected, host closed
+ // > goats.com ////////////////////////////
+ // > atwola.com
+ //
+ // Before SelectedIndex: 0 Before RowCount: 2
+ // After SelectedIndex: 0 After RowCount: 1
+ //
+ // 4) Single cookie selected, host has many children
+ // v goats.com
+ // goats.com sldkkfjl
+ // //// goats.com /////////// flksjl33 ////
+ // > atwola.com
+ //
+ // Before SelectedIndex: 2 Before RowCount: 4
+ // After SelectedIndex: 1 After RowCount: 3
+ //
+ // 5) Single cookie selected, host has many children
+ // v goats.com
+ // //// goats.com /////////// flksjl33 ////
+ // goats.com sldkkfjl
+ // > atwola.com
+ //
+ // Before SelectedIndex: 1 Before RowCount: 4
+ // After SelectedIndex: 1 After RowCount: 3
+ var seln = this._view.selection;
+ var tbo = this._tree.treeBoxObject;
+
+ if (seln.count < 1) return;
+
+ var nextSelected = 0;
+ var rowCountImpact = 0;
+ var deleteItems = [];
+ if (!this._view._filtered) {
+ var ci = seln.currentIndex;
+ nextSelected = ci;
+ var invalidateRow = -1;
+ var item = this._view._getItemAtIndex(ci);
+ if (item.container) {
+ rowCountImpact -= (item.open ? item.cookies.length : 0) + 1;
+ deleteItems = deleteItems.concat(item.cookies);
+ if (!this._view.hasNextSibling(-1, ci))
+ --nextSelected;
+ this._view._removeItemAtIndex(ci);
+ }
+ else {
+ var parent = this._view._getItemAtIndex(item.parentIndex);
+ --rowCountImpact;
+ if (parent.cookies.length == 1) {
+ --rowCountImpact;
+ deleteItems.push(item);
+ if (!this._view.hasNextSibling(-1, ci))
+ --nextSelected;
+ if (!this._view.hasNextSibling(-1, item.parentIndex))
+ --nextSelected;
+ this._view._removeItemAtIndex(item.parentIndex);
+ invalidateRow = item.parentIndex;
+ }
+ else {
+ deleteItems.push(item);
+ if (!this._view.hasNextSibling(-1, ci))
+ --nextSelected;
+ this._view._removeItemAtIndex(ci);
+ }
+ }
+ this._view._rowCount += rowCountImpact;
+ tbo.rowCountChanged(ci, rowCountImpact);
+ if (invalidateRow != -1)
+ tbo.invalidateRow(invalidateRow);
+ }
+ else {
+ var rangeCount = seln.getRangeCount();
+ // Traverse backwards through selections to avoid messing
+ // up the indices when they are deleted.
+ // See bug 388079.
+ for (let i = rangeCount - 1; i >= 0; --i) {
+ var min = {}; var max = {};
+ seln.getRangeAt(i, min, max);
+ nextSelected = min.value;
+ for (let j = min.value; j <= max.value; ++j) {
+ deleteItems.push(this._view._getItemAtIndex(j));
+ if (!this._view.hasNextSibling(-1, max.value))
+ --nextSelected;
+ }
+ var delta = max.value - min.value + 1;
+ this._view._removeItemAtIndex(min.value, delta);
+ rowCountImpact = -1 * delta;
+ this._view._rowCount += rowCountImpact;
+ tbo.rowCountChanged(min.value, rowCountImpact);
+ }
+ }
+
+ this.performDeletion(deleteItems);
+
+ if (nextSelected < 0)
+ seln.clearSelection();
+ else {
+ seln.select(nextSelected);
+ this._tree.focus();
+ }
+ },
+
+ deleteAllCookies: function () {
+ if (this._view._filtered) {
+ var rowCount = this._view.rowCount;
+ var deleteItems = [];
+ for (let index = 0; index < rowCount; index++) {
+ deleteItems.push(this._view._getItemAtIndex(index));
+ }
+ this._view._removeItemAtIndex(0, rowCount);
+ this._view._rowCount = 0;
+ this._tree.treeBoxObject.rowCountChanged(0, -rowCount);
+ this.performDeletion(deleteItems);
+ }
+ else {
+ this._cm.removeAll();
+ }
+ this._updateRemoveAllButton();
+ this.focusFilterBox();
+ },
+
+ onCookieKeyPress: function (aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) {
+ this.deleteCookie();
+ } else if (AppConstants.platform == "macosx" &&
+ aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE) {
+ this.deleteCookie();
+ }
+ },
+
+ _lastSortProperty : "",
+ _lastSortAscending: false,
+ sort: function (aProperty) {
+ var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true;
+ // Sort the Non-Filtered Host Collections
+ if (aProperty == "rawHost") {
+ function sortByHost(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ }
+ this._hostOrder.sort(sortByHost);
+ if (!ascending)
+ this._hostOrder.reverse();
+ }
+
+ function sortByProperty(a, b) {
+ return a[aProperty].toLowerCase().localeCompare(b[aProperty].toLowerCase());
+ }
+ for (let host in this._hosts) {
+ var cookies = this._hosts[host].cookies;
+ cookies.sort(sortByProperty);
+ if (!ascending)
+ cookies.reverse();
+ }
+ // Sort the Filtered List, if in Filtered mode
+ if (this._view._filtered) {
+ this._view._filterSet.sort(sortByProperty);
+ if (!ascending)
+ this._view._filterSet.reverse();
+ }
+
+ // Adjust the Sort Indicator
+ var domainCol = document.getElementById("domainCol");
+ var nameCol = document.getElementById("nameCol");
+ var sortOrderString = ascending ? "ascending" : "descending";
+ if (aProperty == "rawHost") {
+ domainCol.setAttribute("sortDirection", sortOrderString);
+ nameCol.removeAttribute("sortDirection");
+ }
+ else {
+ nameCol.setAttribute("sortDirection", sortOrderString);
+ domainCol.removeAttribute("sortDirection");
+ }
+
+ this._view._invalidateCache(0);
+ this._view.selection.clearSelection();
+ this._view.selection.select(0);
+ this._tree.treeBoxObject.invalidate();
+ this._tree.treeBoxObject.ensureRowIsVisible(0);
+
+ this._lastSortAscending = ascending;
+ this._lastSortProperty = aProperty;
+ },
+
+ clearFilter: function () {
+ // Revert to single-select in the tree
+ this._tree.setAttribute("seltype", "single");
+
+ // Clear the Tree Display
+ this._view._filtered = false;
+ this._view._rowCount = 0;
+ this._tree.treeBoxObject.rowCountChanged(0, -this._view._filterSet.length);
+ this._view._filterSet = [];
+
+ // Just reload the list to make sure deletions are respected
+ this._loadCookies();
+ this._tree.view = this._view;
+
+ // Restore sort order
+ var sortby = this._lastSortProperty;
+ if (sortby == "") {
+ this._lastSortAscending = false;
+ this.sort("rawHost");
+ }
+ else {
+ this._lastSortAscending = !this._lastSortAscending;
+ this.sort(sortby);
+ }
+
+ // Restore open state
+ for (let openIndex of this._openIndices) {
+ this._view.toggleOpenState(openIndex);
+ }
+ this._openIndices = [];
+
+ // Restore selection
+ this._view.selection.clearSelection();
+ for (let range of this._lastSelectedRanges) {
+ this._view.selection.rangedSelect(range.min, range.max, true);
+ }
+ this._lastSelectedRanges = [];
+
+ document.getElementById("cookiesIntro").value = this._bundle.getString("cookiesAll");
+ this._updateRemoveAllButton();
+ },
+
+ _cookieMatchesFilter: function (aCookie) {
+ return aCookie.rawHost.indexOf(this._view._filterValue) != -1 ||
+ aCookie.name.indexOf(this._view._filterValue) != -1 ||
+ aCookie.value.indexOf(this._view._filterValue) != -1;
+ },
+
+ _filterCookies: function (aFilterValue) {
+ this._view._filterValue = aFilterValue;
+ var cookies = [];
+ for (let i = 0; i < gCookiesWindow._hostOrder.length; ++i) { // var host in gCookiesWindow._hosts) {
+ let currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]]; // gCookiesWindow._hosts[host];
+ if (!currHost) continue;
+ for (let cookie of currHost.cookies) {
+ if (this._cookieMatchesFilter(cookie))
+ cookies.push(cookie);
+ }
+ }
+ return cookies;
+ },
+
+ _lastSelectedRanges: [],
+ _openIndices: [],
+ _saveState: function () {
+ // Save selection
+ var seln = this._view.selection;
+ this._lastSelectedRanges = [];
+ var rangeCount = seln.getRangeCount();
+ for (let i = 0; i < rangeCount; ++i) {
+ var min = {}; var max = {};
+ seln.getRangeAt(i, min, max);
+ this._lastSelectedRanges.push({ min: min.value, max: max.value });
+ }
+
+ // Save open states
+ this._openIndices = [];
+ for (let i = 0; i < this._view.rowCount; ++i) {
+ var item = this._view._getItemAtIndex(i);
+ if (item && item.container && item.open)
+ this._openIndices.push(i);
+ }
+ },
+
+ _updateRemoveAllButton: function gCookiesWindow__updateRemoveAllButton() {
+ document.getElementById("removeAllCookies").disabled = this._view._rowCount == 0;
+ },
+
+ filter: function () {
+ var filter = document.getElementById("filter").value;
+ if (filter == "") {
+ gCookiesWindow.clearFilter();
+ return;
+ }
+ var view = gCookiesWindow._view;
+ view._filterSet = gCookiesWindow._filterCookies(filter);
+ if (!view._filtered) {
+ // Save Display Info for the Non-Filtered mode when we first
+ // enter Filtered mode.
+ gCookiesWindow._saveState();
+ view._filtered = true;
+ }
+ // Move to multi-select in the tree
+ gCookiesWindow._tree.setAttribute("seltype", "multiple");
+
+ // Clear the display
+ var oldCount = view._rowCount;
+ view._rowCount = 0;
+ gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, -oldCount);
+ // Set up the filtered display
+ view._rowCount = view._filterSet.length;
+ gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, view.rowCount);
+
+ // if the view is not empty then select the first item
+ if (view.rowCount > 0)
+ view.selection.select(0);
+
+ document.getElementById("cookiesIntro").value = gCookiesWindow._bundle.getString("cookiesFiltered");
+ this._updateRemoveAllButton();
+ },
+
+ setFilter: function (aFilterString) {
+ document.getElementById("filter").value = aFilterString;
+ this.filter();
+ },
+
+ focusFilterBox: function () {
+ var filter = document.getElementById("filter");
+ filter.focus();
+ filter.select();
+ },
+
+ onWindowKeyPress: function (aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
+ window.close();
+ }
+};
diff --git a/browser/components/preferences/cookies.xul b/browser/components/preferences/cookies.xul
new file mode 100644
index 000000000..cda6ea220
--- /dev/null
+++ b/browser/components/preferences/cookies.xul
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/cookies.dtd" >
+
+<window id="CookiesDialog" windowtype="Browser:Cookies"
+ class="windowDialog" title="&window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: &window.width;;"
+ onload="gCookiesWindow.init();"
+ onunload="gCookiesWindow.uninit();"
+ persist="screenX screenY width height"
+ onkeypress="gCookiesWindow.onWindowKeyPress(event);">
+
+ <script src="chrome://browser/content/preferences/cookies.js"/>
+
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <keyset>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+ <key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
+ <key key="&focusSearch2.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
+ </keyset>
+
+ <vbox flex="1" class="contentPane largeDialogContainer">
+ <hbox align="center">
+ <label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
+ <textbox type="search" id="filter" flex="1"
+ aria-controls="cookiesList"
+ oncommand="gCookiesWindow.filter();"/>
+ </hbox>
+ <separator class="thin"/>
+ <label control="cookiesList" id="cookiesIntro" value="&cookiesonsystem.label;"/>
+ <separator class="thin"/>
+ <tree id="cookiesList" flex="1" style="height: 10em;"
+ onkeypress="gCookiesWindow.onCookieKeyPress(event)"
+ onselect="gCookiesWindow.onCookieSelected();"
+ hidecolumnpicker="true" seltype="single">
+ <treecols>
+ <treecol id="domainCol" label="&cookiedomain.label;" flex="2" primary="true"
+ persist="width" onclick="gCookiesWindow.sort('rawHost');"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="nameCol" label="&cookiename.label;" flex="1"
+ persist="width"
+ onclick="gCookiesWindow.sort('name');"/>
+ </treecols>
+ <treechildren id="cookiesChildren"/>
+ </tree>
+ <hbox id="cookieInfoBox">
+ <grid flex="1" id="cookieInfoGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox pack="end"><label id="nameLabel" control="name" value="&props.name.label;"/></hbox>
+ <textbox id="name" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="valueLabel" control="value" value="&props.value.label;"/></hbox>
+ <textbox id="value" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="isDomain" control="host" value="&props.domain.label;"/></hbox>
+ <textbox id="host" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="pathLabel" control="path" value="&props.path.label;"/></hbox>
+ <textbox id="path" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="isSecureLabel" control="isSecure" value="&props.secure.label;"/></hbox>
+ <textbox id="isSecure" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="expiresLabel" control="expires" value="&props.expires.label;"/></hbox>
+ <textbox id="expires" readonly="true" class="plain"/>
+ </row>
+ <row align="center" id="userContextRow">
+ <hbox pack="end"><label id="userContextLabel" control="userContext" value="&props.container.label;"/></hbox>
+ <textbox id="userContext" readonly="true" class="plain"/>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+ </vbox>
+ <hbox align="end">
+ <hbox class="actionButtons" flex="1">
+ <button id="removeSelectedCookies" disabled="true" icon="clear"
+ accesskey="&button.removeSelectedCookies.accesskey;"
+ oncommand="gCookiesWindow.deleteCookie();"/>
+ <button id="removeAllCookies" disabled="true" icon="clear"
+ label="&button.removeAllCookies.label;" accesskey="&button.removeAllCookies.accesskey;"
+ oncommand="gCookiesWindow.deleteAllCookies();"/>
+ <spacer flex="1"/>
+#ifndef XP_MACOSX
+ <button oncommand="close();" icon="close"
+ label="&button.close.label;" accesskey="&button.close.accesskey;"/>
+#endif
+ </hbox>
+ </hbox>
+</window>
diff --git a/browser/components/preferences/donottrack.xul b/browser/components/preferences/donottrack.xul
new file mode 100644
index 000000000..d0631ac4f
--- /dev/null
+++ b/browser/components/preferences/donottrack.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+
+<!DOCTYPE prefwindow [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % doNotTrackDTD SYSTEM "chrome://browser/locale/preferences/donottrack.dtd">
+%brandDTD;
+%doNotTrackDTD;
+]>
+
+<prefwindow id="DoNotTrackDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&window.title;"
+ style="width: &window.width;; height: &window.height;;"
+ dlgbuttons="accept,cancel">
+ <prefpane>
+ <preferences>
+ <preference id="privacy.donottrackheader.enabled"
+ name="privacy.donottrackheader.enabled"
+ type="bool"/>
+ </preferences>
+ <hbox align="center" pack="start">
+ <!-- Work around focus ring not showing properly. -->
+ <spacer style="width: 1em;"/>
+ <checkbox label="&doNotTrackCheckbox2.label;"
+ accesskey="&doNotTrackCheckbox2.accesskey;"
+ preference="privacy.donottrackheader.enabled"/>
+ </hbox>
+ <description flex="1" class="doNotTrackLearnMore">
+ &doNotTrackTPInfo.description;
+ <label class="text-link"
+ value="&doNotTrackLearnMore.label;"
+ href="https://www.mozilla.org/dnt"/>
+ </description>
+ </prefpane>
+</prefwindow>
diff --git a/browser/components/preferences/fonts.js b/browser/components/preferences/fonts.js
new file mode 100644
index 000000000..bf6f43159
--- /dev/null
+++ b/browser/components/preferences/fonts.js
@@ -0,0 +1,105 @@
+/* -*- 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/. */
+
+/* import-globals-from ../../../toolkit/mozapps/preferences/fontbuilder.js */
+
+// browser.display.languageList LOCK ALL when LOCKED
+
+const kDefaultFontType = "font.default.%LANG%";
+const kFontNameFmtSerif = "font.name.serif.%LANG%";
+const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%";
+const kFontNameFmtMonospace = "font.name.monospace.%LANG%";
+const kFontNameListFmtSerif = "font.name-list.serif.%LANG%";
+const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
+const kFontNameListFmtMonospace = "font.name-list.monospace.%LANG%";
+const kFontSizeFmtVariable = "font.size.variable.%LANG%";
+const kFontSizeFmtFixed = "font.size.fixed.%LANG%";
+const kFontMinSizeFmt = "font.minimum-size.%LANG%";
+
+var gFontsDialog = {
+ _selectLanguageGroup: function (aLanguageGroup)
+ {
+ var prefs = [{ format: kDefaultFontType, type: "string", element: "defaultFontType", fonttype: null},
+ { format: kFontNameFmtSerif, type: "fontname", element: "serif", fonttype: "serif" },
+ { format: kFontNameFmtSansSerif, type: "fontname", element: "sans-serif", fonttype: "sans-serif" },
+ { format: kFontNameFmtMonospace, type: "fontname", element: "monospace", fonttype: "monospace" },
+ { format: kFontNameListFmtSerif, type: "unichar", element: null, fonttype: "serif" },
+ { format: kFontNameListFmtSansSerif, type: "unichar", element: null, fonttype: "sans-serif" },
+ { format: kFontNameListFmtMonospace, type: "unichar", element: null, fonttype: "monospace" },
+ { format: kFontSizeFmtVariable, type: "int", element: "sizeVar", fonttype: null },
+ { format: kFontSizeFmtFixed, type: "int", element: "sizeMono", fonttype: null },
+ { format: kFontMinSizeFmt, type: "int", element: "minSize", fonttype: null }];
+ var preferences = document.getElementById("fontPreferences");
+ for (var i = 0; i < prefs.length; ++i) {
+ var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
+ if (!preference) {
+ preference = document.createElement("preference");
+ var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
+ preference.id = name;
+ preference.setAttribute("name", name);
+ preference.setAttribute("type", prefs[i].type);
+ preferences.appendChild(preference);
+ }
+
+ if (!prefs[i].element)
+ continue;
+
+ var element = document.getElementById(prefs[i].element);
+ if (element) {
+ element.setAttribute("preference", preference.id);
+
+ if (prefs[i].fonttype)
+ FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+
+ preference.setElementValue(element);
+ }
+ }
+ },
+
+ readFontLanguageGroup: function ()
+ {
+ var languagePref = document.getElementById("font.language.group");
+ this._selectLanguageGroup(languagePref.value);
+ return undefined;
+ },
+
+ readUseDocumentFonts: function ()
+ {
+ var preference = document.getElementById("browser.display.use_document_fonts");
+ return preference.value == 1;
+ },
+
+ writeUseDocumentFonts: function ()
+ {
+ var useDocumentFonts = document.getElementById("useDocumentFonts");
+ return useDocumentFonts.checked ? 1 : 0;
+ },
+
+ onBeforeAccept: function ()
+ {
+ let preferences = document.querySelectorAll("preference[id*='font.minimum-size']");
+ // It would be good if we could avoid touching languages the pref pages won't use, but
+ // unfortunately the language group APIs (deducing language groups from language codes)
+ // are C++ - only. So we just check all the things the user touched:
+ // Don't care about anything up to 24px, or if this value is the same as set previously:
+ preferences = Array.filter(preferences, prefEl => {
+ return prefEl.value > 24 && prefEl.value != prefEl.valueFromPreferences;
+ });
+ if (!preferences.length) {
+ return true;
+ }
+
+ let strings = document.getElementById("bundlePreferences");
+ let title = strings.getString("veryLargeMinimumFontTitle");
+ let confirmLabel = strings.getString("acceptVeryLargeMinimumFont");
+ let warningMessage = strings.getString("veryLargeMinimumFontWarning");
+ let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
+ let flags = Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL |
+ Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING |
+ Services.prompt.BUTTON_POS_1_DEFAULT;
+ let buttonChosen = Services.prompt.confirmEx(window, title, warningMessage, flags, confirmLabel, null, "", "", {});
+ return buttonChosen == 0;
+ },
+};
diff --git a/browser/components/preferences/fonts.xul b/browser/components/preferences/fonts.xul
new file mode 100644
index 000000000..ed1d1ecc2
--- /dev/null
+++ b/browser/components/preferences/fonts.xul
@@ -0,0 +1,279 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+#ifdef XP_MACOSX
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+#endif
+
+<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/fonts.dtd" >
+
+<prefwindow id="FontsDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&fontsDialog.title;"
+ dlgbuttons="accept,cancel,help"
+ ondialoghelp="openPrefsHelp()"
+ onbeforeaccept="return gFontsDialog.onBeforeAccept();"
+ style="">
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <prefpane id="FontsDialogPane"
+ class="largeDialogContainer"
+ helpTopic="prefs-fonts-and-colors">
+
+ <preferences id="fontPreferences">
+ <preference id="font.language.group" name="font.language.group" type="wstring"/>
+ <preference id="browser.display.use_document_fonts"
+ name="browser.display.use_document_fonts"
+ type="int"/>
+ <preference id="intl.charset.fallback.override" name="intl.charset.fallback.override" type="string"/>
+ </preferences>
+
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ <script type="application/javascript" src="chrome://mozapps/content/preferences/fontbuilder.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/fonts.js"/>
+
+ <!-- Fonts for: [ Language ] -->
+ <groupbox>
+ <caption>
+ <hbox align="center">
+ <label accesskey="&language.accesskey;" control="selectLangs">&language.label;</label>
+ </hbox>
+ <menulist id="selectLangs" preference="font.language.group"
+ onsyncfrompreference="return gFontsDialog.readFontLanguageGroup();">
+ <menupopup>
+ <menuitem value="ar" label="&font.langGroup.arabic;"/>
+ <menuitem value="x-armn" label="&font.langGroup.armenian;"/>
+ <menuitem value="x-beng" label="&font.langGroup.bengali;"/>
+ <menuitem value="zh-CN" label="&font.langGroup.simpl-chinese;"/>
+ <menuitem value="zh-HK" label="&font.langGroup.trad-chinese-hk;"/>
+ <menuitem value="zh-TW" label="&font.langGroup.trad-chinese;"/>
+ <menuitem value="x-cyrillic" label="&font.langGroup.cyrillic;"/>
+ <menuitem value="x-devanagari" label="&font.langGroup.devanagari;"/>
+ <menuitem value="x-ethi" label="&font.langGroup.ethiopic;"/>
+ <menuitem value="x-geor" label="&font.langGroup.georgian;"/>
+ <menuitem value="el" label="&font.langGroup.el;"/>
+ <menuitem value="x-gujr" label="&font.langGroup.gujarati;"/>
+ <menuitem value="x-guru" label="&font.langGroup.gurmukhi;"/>
+ <menuitem value="he" label="&font.langGroup.hebrew;"/>
+ <menuitem value="ja" label="&font.langGroup.japanese;"/>
+ <menuitem value="x-knda" label="&font.langGroup.kannada;"/>
+ <menuitem value="x-khmr" label="&font.langGroup.khmer;"/>
+ <menuitem value="ko" label="&font.langGroup.korean;"/>
+ <menuitem value="x-western" label="&font.langGroup.latin;"/>
+ <menuitem value="x-mlym" label="&font.langGroup.malayalam;"/>
+ <menuitem value="x-math" label="&font.langGroup.math;"/>
+ <menuitem value="x-orya" label="&font.langGroup.odia;"/>
+ <menuitem value="x-sinh" label="&font.langGroup.sinhala;"/>
+ <menuitem value="x-tamil" label="&font.langGroup.tamil;"/>
+ <menuitem value="x-telu" label="&font.langGroup.telugu;"/>
+ <menuitem value="th" label="&font.langGroup.thai;"/>
+ <menuitem value="x-tibt" label="&font.langGroup.tibetan;"/>
+ <menuitem value="x-cans" label="&font.langGroup.canadian;"/>
+ <menuitem value="x-unicode" label="&font.langGroup.other;"/>
+ </menupopup>
+ </menulist>
+ </caption>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row>
+ <separator class="thin"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&proportional.accesskey;" control="defaultFontType">&proportional.label;</label>
+ </hbox>
+ <menulist id="defaultFontType" flex="1" style="width: 0px;">
+ <menupopup>
+ <menuitem value="serif" label="&useDefaultFontSerif.label;"/>
+ <menuitem value="sans-serif" label="&useDefaultFontSansSerif.label;"/>
+ </menupopup>
+ </menulist>
+ <hbox align="center" pack="end">
+ <label value="&size.label;"
+ accesskey="&sizeProportional.accesskey;"
+ control="sizeVar"/>
+ </hbox>
+ <menulist id="sizeVar" delayprefsave="true">
+ <menupopup>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&serif.accesskey;" control="serif">&serif.label;</label>
+ </hbox>
+ <menulist id="serif" flex="1" style="width: 0px;" delayprefsave="true"
+ onsyncfrompreference="return FontBuilder.readFontSelection(this);"/>
+ <spacer/>
+ </row>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&sans-serif.accesskey;" control="sans-serif">&sans-serif.label;</label>
+ </hbox>
+ <menulist id="sans-serif" flex="1" style="width: 0px;" delayprefsave="true"
+ onsyncfrompreference="return FontBuilder.readFontSelection(this);"/>
+ <spacer/>
+ </row>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&monospace.accesskey;" control="monospace">&monospace.label;</label>
+ </hbox>
+ <menulist id="monospace" flex="1" style="width: 0px;" crop="right" delayprefsave="true"
+ onsyncfrompreference="return FontBuilder.readFontSelection(this);"/>
+ <hbox align="center" pack="end">
+ <label value="&size.label;"
+ accesskey="&sizeMonospace.accesskey;"
+ control="sizeMono"/>
+ </hbox>
+ <menulist id="sizeMono" delayprefsave="true">
+ <menupopup>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <spacer flex="1"/>
+ <hbox align="center" pack="end">
+ <label accesskey="&minSize.accesskey;" control="minSize">&minSize.label;</label>
+ <menulist id="minSize">
+ <menupopup>
+ <menuitem value="0" label="&minSize.none;"/>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </hbox>
+ <separator/>
+ <separator class="groove"/>
+ <hbox>
+ <checkbox id="useDocumentFonts"
+ label="&allowPagesToUse.label;" accesskey="&allowPagesToUse.accesskey;"
+ preference="browser.display.use_document_fonts"
+ onsyncfrompreference="return gFontsDialog.readUseDocumentFonts();"
+ onsynctopreference="return gFontsDialog.writeUseDocumentFonts();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Text Encoding -->
+ <groupbox>
+ <caption label="&languages.customize.Fallback2.grouplabel;"/>
+ <description>&languages.customize.Fallback2.desc;</description>
+ <hbox align="center">
+ <label value="&languages.customize.Fallback2.label;"
+ accesskey="&languages.customize.Fallback2.accesskey;"
+ control="DefaultCharsetList"/>
+ <menulist id="DefaultCharsetList" preference="intl.charset.fallback.override">
+ <menupopup>
+ <menuitem label="&languages.customize.Fallback.auto;" value=""/>
+ <menuitem label="&languages.customize.Fallback.arabic;" value="windows-1256"/>
+ <menuitem label="&languages.customize.Fallback.baltic;" value="windows-1257"/>
+ <menuitem label="&languages.customize.Fallback.ceiso;" value="ISO-8859-2"/>
+ <menuitem label="&languages.customize.Fallback.cewindows;" value="windows-1250"/>
+ <menuitem label="&languages.customize.Fallback.simplified;" value="gbk"/>
+ <menuitem label="&languages.customize.Fallback.traditional;" value="Big5"/>
+ <menuitem label="&languages.customize.Fallback.cyrillic;" value="windows-1251"/>
+ <menuitem label="&languages.customize.Fallback.greek;" value="ISO-8859-7"/>
+ <menuitem label="&languages.customize.Fallback.hebrew;" value="windows-1255"/>
+ <menuitem label="&languages.customize.Fallback.japanese;" value="Shift_JIS"/>
+ <menuitem label="&languages.customize.Fallback.korean;" value="EUC-KR"/>
+ <menuitem label="&languages.customize.Fallback.thai;" value="windows-874"/>
+ <menuitem label="&languages.customize.Fallback.turkish;" value="windows-1254"/>
+ <menuitem label="&languages.customize.Fallback.vietnamese;" value="windows-1258"/>
+ <menuitem label="&languages.customize.Fallback.other;" value="windows-1252"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</prefwindow>
diff --git a/browser/components/preferences/handlers.css b/browser/components/preferences/handlers.css
new file mode 100644
index 000000000..6af75a08b
--- /dev/null
+++ b/browser/components/preferences/handlers.css
@@ -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/. */
+
+#handlersView > richlistitem {
+ -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler");
+}
+
+#handlersView > richlistitem[selected="true"] {
+ -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler-selected");
+}
+
+#containersView > richlistitem {
+ -moz-binding: url("chrome://browser/content/preferences/handlers.xml#container");
+}
+
+/**
+ * Make the icons appear.
+ * Note: we display the icon box for every item whether or not it has an icon
+ * so the labels of all the items align vertically.
+ */
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ display: -moz-box;
+ min-width: 16px;
+}
+
+listitem.offlineapp {
+ -moz-binding: url("chrome://browser/content/preferences/handlers.xml#offlineapp");
+}
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ #handlersView > richlistitem,
+ .actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ image-rendering: -moz-crisp-edges;
+ }
+}
diff --git a/browser/components/preferences/handlers.xml b/browser/components/preferences/handlers.xml
new file mode 100644
index 000000000..0c629d759
--- /dev/null
+++ b/browser/components/preferences/handlers.xml
@@ -0,0 +1,105 @@
+<?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/. -->
+<!-- import-globals-from in-content/applications.js -->
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % applicationsDTD SYSTEM "chrome://browser/locale/preferences/applications.dtd">
+ %brandDTD;
+ %applicationsDTD;
+]>
+
+<bindings id="handlerBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="handler-base" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <implementation>
+ <property name="type" readonly="true">
+ <getter>
+ return this.getAttribute("type");
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="handler" extends="chrome://browser/content/preferences/handlers.xml#handler-base">
+ <content>
+ <xul:hbox flex="1" equalsize="always">
+ <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription">
+ <xul:image src="moz-icon://goat?size=16" class="typeIcon"
+ xbl:inherits="src=typeIcon" height="16" width="16"/>
+ <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/>
+ </xul:hbox>
+ <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=actionDescription">
+ <xul:image xbl:inherits="src=actionIcon" height="16" width="16" class="actionIcon"/>
+ <xul:label flex="1" crop="end" xbl:inherits="value=actionDescription"/>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="handler-selected" extends="chrome://browser/content/preferences/handlers.xml#handler-base">
+ <content>
+ <xul:hbox flex="1" equalsize="always">
+ <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription">
+ <xul:image src="moz-icon://goat?size=16" class="typeIcon"
+ xbl:inherits="src=typeIcon" height="16" width="16"/>
+ <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/>
+ </xul:hbox>
+ <xul:hbox flex="1">
+ <xul:menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1"
+ xbl:inherits="tooltiptext=actionDescription"
+ oncommand="gApplicationsPane.onSelectAction(event.originalTarget)">
+ <xul:menupopup/>
+ </xul:menulist>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ gApplicationsPane.rebuildActionsMenu();
+ </constructor>
+ </implementation>
+
+ </binding>
+
+ <binding id="container">
+ <content>
+ <xul:hbox flex="1" equalsize="always">
+ <xul:hbox flex="1" align="center">
+ <xul:hbox xbl:inherits="data-identity-icon=containerIcon,data-identity-color=containerColor" height="24" width="24" class="userContext-icon"/>
+ <xul:label flex="1" crop="end" xbl:inherits="value=containerName"/>
+ </xul:hbox>
+ <xul:hbox flex="1" align="right">
+ <xul:button anonid="preferencesButton"
+ xbl:inherits="value=userContextId"
+ onclick="gContainersPane.onPeferenceClick(event.originalTarget)">
+ Preferences
+ </xul:button>
+ <xul:button anonid="removeButton"
+ xbl:inherits="value=userContextId"
+ onclick="gContainersPane.onRemoveClick(event.originalTarget)">
+ Remove
+ </xul:button>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="offlineapp"
+ extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <children>
+ <xul:listcell xbl:inherits="label=origin"/>
+ <xul:listcell xbl:inherits="label=usage"/>
+ </children>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/browser/components/preferences/in-content/advanced.js b/browser/components/preferences/in-content/advanced.js
new file mode 100644
index 000000000..448a21dae
--- /dev/null
+++ b/browser/components/preferences/in-content/advanced.js
@@ -0,0 +1,770 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Load DownloadUtils module for convertByteUnits
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
+
+var gAdvancedPane = {
+ _inited: false,
+
+ /**
+ * Brings the appropriate tab to the front and initializes various bits of UI.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gAdvancedPane));
+ }
+
+ this._inited = true;
+ var advancedPrefs = document.getElementById("advancedPrefs");
+
+ var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
+ if (preference.value !== null)
+ advancedPrefs.selectedIndex = preference.value;
+
+ if (AppConstants.MOZ_UPDATER) {
+ let onUnload = function () {
+ window.removeEventListener("unload", onUnload, false);
+ Services.prefs.removeObserver("app.update.", this);
+ }.bind(this);
+ window.addEventListener("unload", onUnload, false);
+ Services.prefs.addObserver("app.update.", this, false);
+ this.updateReadPrefs();
+ }
+ this.updateOfflineApps();
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ this.initSubmitCrashes();
+ }
+ this.initTelemetry();
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ this.initSubmitHealthReport();
+ }
+ this.updateOnScreenKeyboardVisibility();
+ this.updateCacheSizeInputField();
+ this.updateActualCacheSize();
+ this.updateActualAppCacheSize();
+
+ setEventListener("layers.acceleration.disabled", "change",
+ gAdvancedPane.updateHardwareAcceleration);
+ setEventListener("advancedPrefs", "select",
+ gAdvancedPane.tabSelectionChanged);
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ setEventListener("submitHealthReportBox", "command",
+ gAdvancedPane.updateSubmitHealthReport);
+ }
+
+ setEventListener("connectionSettings", "command",
+ gAdvancedPane.showConnections);
+ setEventListener("clearCacheButton", "command",
+ gAdvancedPane.clearCache);
+ setEventListener("clearOfflineAppCacheButton", "command",
+ gAdvancedPane.clearOfflineAppCache);
+ setEventListener("offlineNotifyExceptions", "command",
+ gAdvancedPane.showOfflineExceptions);
+ setEventListener("offlineAppsList", "select",
+ gAdvancedPane.offlineAppSelected);
+ let bundlePrefs = document.getElementById("bundlePreferences");
+ document.getElementById("offlineAppsList")
+ .style.height = bundlePrefs.getString("offlineAppsList.height");
+ setEventListener("offlineAppsListRemove", "command",
+ gAdvancedPane.removeOfflineApp);
+ if (AppConstants.MOZ_UPDATER) {
+ setEventListener("updateRadioGroup", "command",
+ gAdvancedPane.updateWritePrefs);
+ setEventListener("showUpdateHistory", "command",
+ gAdvancedPane.showUpdates);
+ }
+ setEventListener("viewCertificatesButton", "command",
+ gAdvancedPane.showCertificates);
+ setEventListener("viewSecurityDevicesButton", "command",
+ gAdvancedPane.showSecurityDevices);
+ setEventListener("cacheSize", "change",
+ gAdvancedPane.updateCacheSizePref);
+
+ if (AppConstants.MOZ_WIDGET_GTK) {
+ // GTK tabbox' allow the scroll wheel to change the selected tab,
+ // but we don't want this behavior for the in-content preferences.
+ let tabsElement = document.getElementById("tabsElement");
+ tabsElement.addEventListener("DOMMouseScroll", event => {
+ event.stopPropagation();
+ }, true);
+ }
+ },
+
+ /**
+ * Stores the identity of the current tab in preferences so that the selected
+ * tab can be persisted between openings of the preferences window.
+ */
+ tabSelectionChanged: function ()
+ {
+ if (!this._inited)
+ return;
+ var advancedPrefs = document.getElementById("advancedPrefs");
+ var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
+
+ // tabSelectionChanged gets called twice due to the selectedIndex being set
+ // by both the selectedItem and selectedPanel callstacks. This guard is used
+ // to prevent double-counting in Telemetry.
+ if (preference.valueFromPreferences != advancedPrefs.selectedIndex) {
+ Services.telemetry
+ .getHistogramById("FX_PREFERENCES_CATEGORY_OPENED")
+ .add(telemetryBucketForCategory("advanced"));
+ }
+
+ preference.valueFromPreferences = advancedPrefs.selectedIndex;
+ },
+
+ // GENERAL TAB
+
+ /*
+ * Preferences:
+ *
+ * accessibility.browsewithcaret
+ * - true enables keyboard navigation and selection within web pages using a
+ * visible caret, false uses normal keyboard navigation with no caret
+ * accessibility.typeaheadfind
+ * - when set to true, typing outside text areas and input boxes will
+ * automatically start searching for what's typed within the current
+ * document; when set to false, no search action happens
+ * ui.osk.enabled
+ * - when set to true, subject to other conditions, we may sometimes invoke
+ * an on-screen keyboard when a text input is focused.
+ * (Currently Windows-only, and depending on prefs, may be Windows-8-only)
+ * general.autoScroll
+ * - when set to true, clicking the scroll wheel on the mouse activates a
+ * mouse mode where moving the mouse down scrolls the document downward with
+ * speed correlated with the distance of the cursor from the original
+ * position at which the click occurred (and likewise with movement upward);
+ * if false, this behavior is disabled
+ * general.smoothScroll
+ * - set to true to enable finer page scrolling than line-by-line on page-up,
+ * page-down, and other such page movements
+ * layout.spellcheckDefault
+ * - an integer:
+ * 0 disables spellchecking
+ * 1 enables spellchecking, but only for multiline text fields
+ * 2 enables spellchecking for all text fields
+ */
+
+ /**
+ * Stores the original value of the spellchecking preference to enable proper
+ * restoration if unchanged (since we're mapping a tristate onto a checkbox).
+ */
+ _storedSpellCheck: 0,
+
+ /**
+ * Returns true if any spellchecking is enabled and false otherwise, caching
+ * the current value to enable proper pref restoration if the checkbox is
+ * never changed.
+ */
+ readCheckSpelling: function ()
+ {
+ var pref = document.getElementById("layout.spellcheckDefault");
+ this._storedSpellCheck = pref.value;
+
+ return (pref.value != 0);
+ },
+
+ /**
+ * Returns the value of the spellchecking preference represented by UI,
+ * preserving the preference's "hidden" value if the preference is
+ * unchanged and represents a value not strictly allowed in UI.
+ */
+ writeCheckSpelling: function ()
+ {
+ var checkbox = document.getElementById("checkSpelling");
+ if (checkbox.checked) {
+ if (this._storedSpellCheck == 2) {
+ return 2;
+ }
+ return 1;
+ }
+ return 0;
+ },
+
+ /**
+ * security.OCSP.enabled is an integer value for legacy reasons.
+ * A value of 1 means OCSP is enabled. Any other value means it is disabled.
+ */
+ readEnableOCSP: function ()
+ {
+ var preference = document.getElementById("security.OCSP.enabled");
+ // This is the case if the preference is the default value.
+ if (preference.value === undefined) {
+ return true;
+ }
+ return preference.value == 1;
+ },
+
+ /**
+ * See documentation for readEnableOCSP.
+ */
+ writeEnableOCSP: function ()
+ {
+ var checkbox = document.getElementById("enableOCSP");
+ return checkbox.checked ? 1 : 0;
+ },
+
+ /**
+ * When the user toggles the layers.acceleration.disabled pref,
+ * sync its new value to the gfx.direct2d.disabled pref too.
+ */
+ updateHardwareAcceleration: function()
+ {
+ if (AppConstants.platform = "win") {
+ var fromPref = document.getElementById("layers.acceleration.disabled");
+ var toPref = document.getElementById("gfx.direct2d.disabled");
+ toPref.value = fromPref.value;
+ }
+ },
+
+ // DATA CHOICES TAB
+
+ /**
+ * Set up or hide the Learn More links for various data collection options
+ */
+ _setupLearnMoreLink: function (pref, element) {
+ // set up the Learn More link with the correct URL
+ let url = Services.prefs.getCharPref(pref);
+ let el = document.getElementById(element);
+
+ if (url) {
+ el.setAttribute("href", url);
+ } else {
+ el.setAttribute("hidden", "true");
+ }
+ },
+
+ /**
+ *
+ */
+ initSubmitCrashes: function ()
+ {
+ this._setupLearnMoreLink("toolkit.crashreporter.infoURL",
+ "crashReporterLearnMore");
+ },
+
+ /**
+ * The preference/checkbox is configured in XUL.
+ *
+ * In all cases, set up the Learn More link sanely.
+ */
+ initTelemetry: function ()
+ {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ this._setupLearnMoreLink("toolkit.telemetry.infoURL", "telemetryLearnMore");
+ }
+ },
+
+ /**
+ * Set the status of the telemetry controls based on the input argument.
+ * @param {Boolean} aEnabled False disables the controls, true enables them.
+ */
+ setTelemetrySectionEnabled: function (aEnabled)
+ {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ // If FHR is disabled, additional data sharing should be disabled as well.
+ let disabled = !aEnabled;
+ document.getElementById("submitTelemetryBox").disabled = disabled;
+ if (disabled) {
+ // If we disable FHR, untick the telemetry checkbox.
+ Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
+ }
+ document.getElementById("telemetryDataDesc").disabled = disabled;
+ }
+ },
+
+ /**
+ * Initialize the health report service reference and checkbox.
+ */
+ initSubmitHealthReport: function () {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ this._setupLearnMoreLink("datareporting.healthreport.infoURL", "FHRLearnMore");
+
+ let checkbox = document.getElementById("submitHealthReportBox");
+
+ if (Services.prefs.prefIsLocked(PREF_UPLOAD_ENABLED)) {
+ checkbox.setAttribute("disabled", "true");
+ return;
+ }
+
+ checkbox.checked = Services.prefs.getBoolPref(PREF_UPLOAD_ENABLED);
+ this.setTelemetrySectionEnabled(checkbox.checked);
+ }
+ },
+
+ /**
+ * Update the health report preference with state from checkbox.
+ */
+ updateSubmitHealthReport: function () {
+ if (AppConstants.MOZ_TELEMETRY_REPORTING) {
+ let checkbox = document.getElementById("submitHealthReportBox");
+ Services.prefs.setBoolPref(PREF_UPLOAD_ENABLED, checkbox.checked);
+ this.setTelemetrySectionEnabled(checkbox.checked);
+ }
+ },
+
+ updateOnScreenKeyboardVisibility() {
+ if (AppConstants.platform == "win") {
+ let minVersion = Services.prefs.getBoolPref("ui.osk.require_win10") ? 10 : 6.2;
+ if (Services.vc.compare(Services.sysinfo.getProperty("version"), minVersion) >= 0) {
+ document.getElementById("useOnScreenKeyboard").hidden = false;
+ }
+ }
+ },
+
+ // NETWORK TAB
+
+ /*
+ * Preferences:
+ *
+ * browser.cache.disk.capacity
+ * - the size of the browser cache in KB
+ * - Only used if browser.cache.disk.smart_size.enabled is disabled
+ */
+
+ /**
+ * Displays a dialog in which proxy settings may be changed.
+ */
+ showConnections: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/connection.xul");
+ },
+
+ // Retrieves the amount of space currently used by disk cache
+ updateActualCacheSize: function ()
+ {
+ var actualSizeLabel = document.getElementById("actualDiskCacheSize");
+ var prefStrBundle = document.getElementById("bundlePreferences");
+
+ // Needs to root the observer since cache service keeps only a weak reference.
+ this.observer = {
+ onNetworkCacheDiskConsumption: function(consumption) {
+ var size = DownloadUtils.convertByteUnits(consumption);
+ // The XBL binding for the string bundle may have been destroyed if
+ // the page was closed before this callback was executed.
+ if (!prefStrBundle.getFormattedString) {
+ return;
+ }
+ actualSizeLabel.value = prefStrBundle.getFormattedString("actualDiskCacheSize", size);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsICacheStorageConsumptionObserver,
+ Components.interfaces.nsISupportsWeakReference
+ ])
+ };
+
+ actualSizeLabel.value = prefStrBundle.getString("actualDiskCacheSizeCalculated");
+
+ try {
+ var cacheService =
+ Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ cacheService.asyncGetDiskConsumption(this.observer);
+ } catch (e) {}
+ },
+
+ // Retrieves the amount of space currently used by offline cache
+ updateActualAppCacheSize: function ()
+ {
+ var visitor = {
+ onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory)
+ {
+ var actualSizeLabel = document.getElementById("actualAppCacheSize");
+ var sizeStrings = DownloadUtils.convertByteUnits(aConsumption);
+ var prefStrBundle = document.getElementById("bundlePreferences");
+ // The XBL binding for the string bundle may have been destroyed if
+ // the page was closed before this callback was executed.
+ if (!prefStrBundle.getFormattedString) {
+ return;
+ }
+ var sizeStr = prefStrBundle.getFormattedString("actualAppCacheSize", sizeStrings);
+ actualSizeLabel.value = sizeStr;
+ }
+ };
+
+ try {
+ var cacheService =
+ Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ var storage = cacheService.appCacheStorage(LoadContextInfo.default, null);
+ storage.asyncVisitStorage(visitor, false);
+ } catch (e) {}
+ },
+
+ updateCacheSizeUI: function (smartSizeEnabled)
+ {
+ document.getElementById("useCacheBefore").disabled = smartSizeEnabled;
+ document.getElementById("cacheSize").disabled = smartSizeEnabled;
+ document.getElementById("useCacheAfter").disabled = smartSizeEnabled;
+ },
+
+ readSmartSizeEnabled: function ()
+ {
+ // The smart_size.enabled preference element is inverted="true", so its
+ // value is the opposite of the actual pref value
+ var disabled = document.getElementById("browser.cache.disk.smart_size.enabled").value;
+ this.updateCacheSizeUI(!disabled);
+ },
+
+ /**
+ * Converts the cache size from units of KB to units of MB and stores it in
+ * the textbox element.
+ */
+ updateCacheSizeInputField()
+ {
+ let cacheSizeElem = document.getElementById("cacheSize");
+ let cachePref = document.getElementById("browser.cache.disk.capacity");
+ cacheSizeElem.value = cachePref.value / 1024;
+ if (cachePref.locked)
+ cacheSizeElem.disabled = true;
+ },
+
+ /**
+ * Updates the cache size preference once user enters a new value.
+ * We intentionally do not set preference="browser.cache.disk.capacity"
+ * onto the textbox directly, as that would update the pref at each keypress
+ * not only after the final value is entered.
+ */
+ updateCacheSizePref()
+ {
+ let cacheSizeElem = document.getElementById("cacheSize");
+ let cachePref = document.getElementById("browser.cache.disk.capacity");
+ // Converts the cache size as specified in UI (in MB) to KB.
+ let intValue = parseInt(cacheSizeElem.value, 10);
+ cachePref.value = isNaN(intValue) ? 0 : intValue * 1024;
+ },
+
+ /**
+ * Clears the cache.
+ */
+ clearCache: function ()
+ {
+ try {
+ var cache = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ cache.clear();
+ } catch (ex) {}
+ this.updateActualCacheSize();
+ },
+
+ /**
+ * Clears the application cache.
+ */
+ clearOfflineAppCache: function ()
+ {
+ Components.utils.import("resource:///modules/offlineAppCache.jsm");
+ OfflineAppCacheHelper.clear();
+
+ this.updateActualAppCacheSize();
+ this.updateOfflineApps();
+ },
+
+ readOfflineNotify: function()
+ {
+ var pref = document.getElementById("browser.offline-apps.notify");
+ var button = document.getElementById("offlineNotifyExceptions");
+ button.disabled = !pref.value;
+ return pref.value;
+ },
+
+ showOfflineExceptions: function()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible : false,
+ sessionVisible : false,
+ allowVisible : false,
+ prefilledHost : "",
+ permissionType : "offline-app",
+ manageCapability : Components.interfaces.nsIPermissionManager.DENY_ACTION,
+ windowTitle : bundlePreferences.getString("offlinepermissionstitle"),
+ introText : bundlePreferences.getString("offlinepermissionstext") };
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ // XXX: duplicated in browser.js
+ _getOfflineAppUsage(perm, groups) {
+ let cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService);
+ if (!groups) {
+ try {
+ groups = cacheService.getGroups();
+ } catch (ex) {
+ return 0;
+ }
+ }
+
+ let usage = 0;
+ for (let group of groups) {
+ let uri = Services.io.newURI(group, null, null);
+ if (perm.matchesURI(uri, true)) {
+ let cache = cacheService.getActiveCache(group);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ /**
+ * Updates the list of offline applications
+ */
+ updateOfflineApps: function ()
+ {
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+
+ var list = document.getElementById("offlineAppsList");
+ while (list.firstChild) {
+ list.removeChild(list.firstChild);
+ }
+
+ var groups;
+ try {
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ groups = cacheService.getGroups();
+ } catch (e) {
+ return;
+ }
+
+ var bundle = document.getElementById("bundlePreferences");
+
+ var enumerator = pm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ var perm = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ if (perm.type == "offline-app" &&
+ perm.capability != Components.interfaces.nsIPermissionManager.DEFAULT_ACTION &&
+ perm.capability != Components.interfaces.nsIPermissionManager.DENY_ACTION) {
+ var row = document.createElement("listitem");
+ row.id = "";
+ row.className = "offlineapp";
+ row.setAttribute("origin", perm.principal.origin);
+ var converted = DownloadUtils.
+ convertByteUnits(this._getOfflineAppUsage(perm, groups));
+ row.setAttribute("usage",
+ bundle.getFormattedString("offlineAppUsage",
+ converted));
+ list.appendChild(row);
+ }
+ }
+ },
+
+ offlineAppSelected: function()
+ {
+ var removeButton = document.getElementById("offlineAppsListRemove");
+ var list = document.getElementById("offlineAppsList");
+ if (list.selectedItem) {
+ removeButton.setAttribute("disabled", "false");
+ } else {
+ removeButton.setAttribute("disabled", "true");
+ }
+ },
+
+ removeOfflineApp: function()
+ {
+ var list = document.getElementById("offlineAppsList");
+ var item = list.selectedItem;
+ var origin = item.getAttribute("origin");
+ var principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
+
+ var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ var flags = prompts.BUTTON_TITLE_IS_STRING * prompts.BUTTON_POS_0 +
+ prompts.BUTTON_TITLE_CANCEL * prompts.BUTTON_POS_1;
+
+ var bundle = document.getElementById("bundlePreferences");
+ var title = bundle.getString("offlineAppRemoveTitle");
+ var prompt = bundle.getFormattedString("offlineAppRemovePrompt", [principal.URI.prePath]);
+ var confirm = bundle.getString("offlineAppRemoveConfirm");
+ var result = prompts.confirmEx(window, title, prompt, flags, confirm,
+ null, null, null, {});
+ if (result != 0)
+ return;
+
+ // get the permission
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+ var perm = pm.getPermissionObject(principal, "offline-app", true);
+ if (perm) {
+ // clear offline cache entries
+ try {
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ var groups = cacheService.getGroups();
+ for (var i = 0; i < groups.length; i++) {
+ var uri = Services.io.newURI(groups[i], null, null);
+ if (perm.matchesURI(uri, true)) {
+ var cache = cacheService.getActiveCache(groups[i]);
+ cache.discard();
+ }
+ }
+ } catch (e) {}
+
+ pm.removePermission(perm);
+ }
+ list.removeChild(item);
+ gAdvancedPane.offlineAppSelected();
+ this.updateActualAppCacheSize();
+ },
+
+ // UPDATE TAB
+
+ /*
+ * Preferences:
+ *
+ * app.update.enabled
+ * - true if updates to the application are enabled, false otherwise
+ * app.update.auto
+ * - true if updates should be automatically downloaded and installed and
+ * false if the user should be asked what he wants to do when an update is
+ * available
+ * extensions.update.enabled
+ * - true if updates to extensions and themes are enabled, false otherwise
+ * browser.search.update
+ * - true if updates to search engines are enabled, false otherwise
+ */
+
+ /**
+ * Selects the item of the radiogroup based on the pref values and locked
+ * states.
+ *
+ * UI state matrix for update preference conditions
+ *
+ * UI Components: Preferences
+ * Radiogroup i = app.update.enabled
+ * ii = app.update.auto
+ *
+ * Disabled states:
+ * Element pref value locked disabled
+ * radiogroup i t/f f false
+ * i t/f *t* *true*
+ * ii t/f f false
+ * ii t/f *t* *true*
+ */
+ updateReadPrefs: function ()
+ {
+ if (AppConstants.MOZ_UPDATER) {
+ var enabledPref = document.getElementById("app.update.enabled");
+ var autoPref = document.getElementById("app.update.auto");
+ var radiogroup = document.getElementById("updateRadioGroup");
+
+ if (!enabledPref.value) // Don't care for autoPref.value in this case.
+ radiogroup.value="manual"; // 3. Never check for updates.
+ else if (autoPref.value) // enabledPref.value && autoPref.value
+ radiogroup.value="auto"; // 1. Automatically install updates
+ else // enabledPref.value && !autoPref.value
+ radiogroup.value="checkOnly"; // 2. Check, but let me choose
+
+ var canCheck = Components.classes["@mozilla.org/updates/update-service;1"].
+ getService(Components.interfaces.nsIApplicationUpdateService).
+ canCheckForUpdates;
+ // canCheck is false if the enabledPref is false and locked,
+ // or the binary platform or OS version is not known.
+ // A locked pref is sufficient to disable the radiogroup.
+ radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
+
+ if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
+ // Check to see if the maintenance service is installed.
+ // If it is don't show the preference at all.
+ var installed;
+ try {
+ var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
+ .createInstance(Components.interfaces.nsIWindowsRegKey);
+ wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
+ "SOFTWARE\\Mozilla\\MaintenanceService",
+ wrk.ACCESS_READ | wrk.WOW64_64);
+ installed = wrk.readIntValue("Installed");
+ wrk.close();
+ } catch (e) {
+ }
+ if (installed != 1) {
+ document.getElementById("useService").hidden = true;
+ }
+ }
+ }
+ },
+
+ /**
+ * Sets the pref values based on the selected item of the radiogroup.
+ */
+ updateWritePrefs: function ()
+ {
+ if (AppConstants.MOZ_UPDATER) {
+ var enabledPref = document.getElementById("app.update.enabled");
+ var autoPref = document.getElementById("app.update.auto");
+ var radiogroup = document.getElementById("updateRadioGroup");
+ switch (radiogroup.value) {
+ case "auto": // 1. Automatically install updates for Desktop only
+ enabledPref.value = true;
+ autoPref.value = true;
+ break;
+ case "checkOnly": // 2. Check, but let me choose
+ enabledPref.value = true;
+ autoPref.value = false;
+ break;
+ case "manual": // 3. Never check for updates.
+ enabledPref.value = false;
+ autoPref.value = false;
+ }
+ }
+ },
+
+ /**
+ * Displays the history of installed updates.
+ */
+ showUpdates: function ()
+ {
+ gSubDialog.open("chrome://mozapps/content/update/history.xul");
+ },
+
+ // ENCRYPTION TAB
+
+ /*
+ * Preferences:
+ *
+ * security.default_personal_cert
+ * - a string:
+ * "Select Automatically" select a certificate automatically when a site
+ * requests one
+ * "Ask Every Time" present a dialog to the user so he can select
+ * the certificate to use on a site which
+ * requests one
+ */
+
+ /**
+ * Displays the user's certificates and associated options.
+ */
+ showCertificates: function ()
+ {
+ gSubDialog.open("chrome://pippki/content/certManager.xul");
+ },
+
+ /**
+ * Displays a dialog from which the user can manage his security devices.
+ */
+ showSecurityDevices: function ()
+ {
+ gSubDialog.open("chrome://pippki/content/device_manager.xul");
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ if (AppConstants.MOZ_UPDATER) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ this.updateReadPrefs();
+ break;
+ }
+ }
+ },
+};
diff --git a/browser/components/preferences/in-content/advanced.xul b/browser/components/preferences/in-content/advanced.xul
new file mode 100644
index 000000000..facaaeaa9
--- /dev/null
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -0,0 +1,421 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!-- Advanced panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/advanced.js"/>
+
+<preferences id="advancedPreferences" hidden="true" data-category="paneAdvanced">
+ <preference id="browser.preferences.advanced.selectedTabIndex"
+ name="browser.preferences.advanced.selectedTabIndex"
+ type="int"/>
+
+ <!-- General tab -->
+ <preference id="accessibility.browsewithcaret"
+ name="accessibility.browsewithcaret"
+ type="bool"/>
+ <preference id="accessibility.typeaheadfind"
+ name="accessibility.typeaheadfind"
+ type="bool"/>
+ <preference id="accessibility.blockautorefresh"
+ name="accessibility.blockautorefresh"
+ type="bool"/>
+#ifdef XP_WIN
+ <preference id="ui.osk.enabled"
+ name="ui.osk.enabled"
+ type="bool"/>
+#endif
+
+ <preference id="general.autoScroll"
+ name="general.autoScroll"
+ type="bool"/>
+ <preference id="general.smoothScroll"
+ name="general.smoothScroll"
+ type="bool"/>
+ <preference id="layers.acceleration.disabled"
+ name="layers.acceleration.disabled"
+ type="bool"
+ inverted="true"/>
+#ifdef XP_WIN
+ <preference id="gfx.direct2d.disabled"
+ name="gfx.direct2d.disabled"
+ type="bool"
+ inverted="true"/>
+#endif
+ <preference id="layout.spellcheckDefault"
+ name="layout.spellcheckDefault"
+ type="int"/>
+
+#ifdef MOZ_TELEMETRY_REPORTING
+ <preference id="toolkit.telemetry.enabled"
+ name="toolkit.telemetry.enabled"
+ type="bool"/>
+#endif
+
+ <!-- Data Choices tab -->
+#ifdef MOZ_CRASHREPORTER
+ <preference id="browser.crashReports.unsubmittedCheck.autoSubmit2"
+ name="browser.crashReports.unsubmittedCheck.autoSubmit2"
+ type="bool"/>
+#endif
+
+ <!-- Network tab -->
+ <preference id="browser.cache.disk.capacity"
+ name="browser.cache.disk.capacity"
+ type="int"/>
+ <preference id="browser.offline-apps.notify"
+ name="browser.offline-apps.notify"
+ type="bool"/>
+
+ <preference id="browser.cache.disk.smart_size.enabled"
+ name="browser.cache.disk.smart_size.enabled"
+ inverted="true"
+ type="bool"/>
+
+ <!-- Update tab -->
+#ifdef MOZ_UPDATER
+ <preference id="app.update.enabled"
+ name="app.update.enabled"
+ type="bool"/>
+ <preference id="app.update.auto"
+ name="app.update.auto"
+ type="bool"/>
+
+ <preference id="app.update.disable_button.showUpdateHistory"
+ name="app.update.disable_button.showUpdateHistory"
+ type="bool"/>
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+ <preference id="app.update.service.enabled"
+ name="app.update.service.enabled"
+ type="bool"/>
+#endif
+#endif
+
+ <preference id="browser.search.update"
+ name="browser.search.update"
+ type="bool"/>
+
+ <!-- Certificates tab -->
+ <preference id="security.default_personal_cert"
+ name="security.default_personal_cert"
+ type="string"/>
+
+ <preference id="security.disable_button.openCertManager"
+ name="security.disable_button.openCertManager"
+ type="bool"/>
+
+ <preference id="security.disable_button.openDeviceManager"
+ name="security.disable_button.openDeviceManager"
+ type="bool"/>
+
+ <preference id="security.OCSP.enabled"
+ name="security.OCSP.enabled"
+ type="int"/>
+</preferences>
+
+#ifdef HAVE_SHELL_SERVICE
+ <stringbundle id="bundleShell" src="chrome://browser/locale/shellservice.properties"/>
+ <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
+#endif
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+<hbox id="header-advanced"
+ class="header"
+ hidden="true"
+ data-category="paneAdvanced">
+ <label class="header-name" flex="1">&paneAdvanced.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<tabbox id="advancedPrefs"
+ handleCtrlTab="false"
+ handleCtrlPageUpDown="false"
+ flex="1"
+ data-category="paneAdvanced"
+ hidden="true">
+
+ <tabs id="tabsElement">
+ <tab id="generalTab" label="&generalTab.label;"/>
+#ifdef MOZ_DATA_REPORTING
+ <tab id="dataChoicesTab" label="&dataChoicesTab.label;"/>
+#endif
+ <tab id="networkTab" label="&networkTab.label;"/>
+ <tab id="updateTab" label="&updateTab.label;"/>
+ <tab id="encryptionTab" label="&certificateTab.label;"/>
+ </tabs>
+
+ <tabpanels flex="1">
+
+ <!-- General -->
+ <tabpanel id="generalPanel" orient="vertical">
+ <!-- Accessibility -->
+ <groupbox id="accessibilityGroup" align="start">
+ <caption><label>&accessibility.label;</label></caption>
+
+#ifdef XP_WIN
+ <checkbox id="useOnScreenKeyboard"
+ hidden="true"
+ label="&useOnScreenKeyboard.label;"
+ accesskey="&useOnScreenKeyboard.accesskey;"
+ preference="ui.osk.enabled"/>
+#endif
+ <checkbox id="useCursorNavigation"
+ label="&useCursorNavigation.label;"
+ accesskey="&useCursorNavigation.accesskey;"
+ preference="accessibility.browsewithcaret"/>
+ <checkbox id="searchStartTyping"
+ label="&searchStartTyping.label;"
+ accesskey="&searchStartTyping.accesskey;"
+ preference="accessibility.typeaheadfind"/>
+ <checkbox id="blockAutoRefresh"
+ label="&blockAutoRefresh.label;"
+ accesskey="&blockAutoRefresh.accesskey;"
+ preference="accessibility.blockautorefresh"/>
+ </groupbox>
+ <!-- Browsing -->
+ <groupbox id="browsingGroup" align="start">
+ <caption><label>&browsing.label;</label></caption>
+
+ <checkbox id="useAutoScroll"
+ label="&useAutoScroll.label;"
+ accesskey="&useAutoScroll.accesskey;"
+ preference="general.autoScroll"/>
+ <checkbox id="useSmoothScrolling"
+ label="&useSmoothScrolling.label;"
+ accesskey="&useSmoothScrolling.accesskey;"
+ preference="general.smoothScroll"/>
+ <checkbox id="allowHWAccel"
+ label="&allowHWAccel.label;"
+ accesskey="&allowHWAccel.accesskey;"
+ preference="layers.acceleration.disabled"/>
+ <checkbox id="checkSpelling"
+ label="&checkSpelling.label;"
+ accesskey="&checkSpelling.accesskey;"
+ onsyncfrompreference="return gAdvancedPane.readCheckSpelling();"
+ onsynctopreference="return gAdvancedPane.writeCheckSpelling();"
+ preference="layout.spellcheckDefault"/>
+ </groupbox>
+ </tabpanel>
+#ifdef MOZ_DATA_REPORTING
+ <!-- Data Choices -->
+ <tabpanel id="dataChoicesPanel" orient="vertical">
+#ifdef MOZ_TELEMETRY_REPORTING
+ <groupbox>
+ <caption>
+ <checkbox id="submitHealthReportBox" label="&enableHealthReport.label;"
+ accesskey="&enableHealthReport.accesskey;"/>
+ </caption>
+ <vbox>
+ <hbox class="indent">
+ <label flex="1">&healthReportDesc.label;</label>
+ <spacer flex="10"/>
+ <label id="FHRLearnMore"
+ class="text-link">&healthReportLearnMore.label;</label>
+ </hbox>
+ <hbox class="indent">
+ <groupbox flex="1">
+ <caption>
+ <checkbox id="submitTelemetryBox" preference="toolkit.telemetry.enabled"
+ label="&enableTelemetryData.label;"
+ accesskey="&enableTelemetryData.accesskey;"/>
+ </caption>
+ <hbox class="indent">
+ <label id="telemetryDataDesc" flex="1">&telemetryDesc.label;</label>
+ <spacer flex="10"/>
+ <label id="telemetryLearnMore"
+ class="text-link">&telemetryLearnMore.label;</label>
+ </hbox>
+ </groupbox>
+ </hbox>
+ </vbox>
+ </groupbox>
+#endif
+#ifdef MOZ_CRASHREPORTER
+ <groupbox>
+ <caption>
+ <checkbox id="automaticallySubmitCrashesBox"
+ preference="browser.crashReports.unsubmittedCheck.autoSubmit2"
+ label="&alwaysSubmitCrashReports.label;"
+ accesskey="&alwaysSubmitCrashReports.accesskey;"/>
+ </caption>
+ <hbox class="indent">
+ <label flex="1">&crashReporterDesc2.label;</label>
+ <spacer flex="10"/>
+ <label id="crashReporterLearnMore"
+ class="text-link">&crashReporterLearnMore.label;</label>
+ </hbox>
+ </groupbox>
+#endif
+ </tabpanel>
+#endif
+
+ <!-- Network -->
+ <tabpanel id="networkPanel" orient="vertical">
+
+ <!-- Connection -->
+ <groupbox id="connectionGroup">
+ <caption><label>&connection.label;</label></caption>
+
+ <hbox align="center">
+ <description flex="1" control="connectionSettings">&connectionDesc.label;</description>
+ <button id="connectionSettings" icon="network" label="&connectionSettings.label;"
+ accesskey="&connectionSettings.accesskey;"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Cache -->
+ <groupbox id="cacheGroup">
+ <caption><label>&httpCache.label;</label></caption>
+
+ <hbox align="center">
+ <label id="actualDiskCacheSize" flex="1"/>
+ <button id="clearCacheButton" icon="clear"
+ label="&clearCacheNow.label;" accesskey="&clearCacheNow.accesskey;"/>
+ </hbox>
+ <hbox>
+ <checkbox preference="browser.cache.disk.smart_size.enabled"
+ id="allowSmartSize"
+ onsyncfrompreference="return gAdvancedPane.readSmartSizeEnabled();"
+ label="&overrideSmartCacheSize.label;"
+ accesskey="&overrideSmartCacheSize.accesskey;"/>
+ </hbox>
+ <hbox align="center" class="indent">
+ <label id="useCacheBefore" control="cacheSize"
+ accesskey="&limitCacheSizeBefore.accesskey;">
+ &limitCacheSizeBefore.label;
+ </label>
+ <textbox id="cacheSize" type="number" size="4" max="1024"
+ aria-labelledby="useCacheBefore cacheSize useCacheAfter"/>
+ <label id="useCacheAfter" flex="1">&limitCacheSizeAfter.label;</label>
+ </hbox>
+ </groupbox>
+
+ <!-- Offline apps -->
+ <groupbox id="offlineGroup">
+ <caption><label>&offlineStorage2.label;</label></caption>
+
+ <hbox align="center">
+ <label id="actualAppCacheSize" flex="1"/>
+ <button id="clearOfflineAppCacheButton" icon="clear"
+ label="&clearOfflineAppCacheNow.label;" accesskey="&clearOfflineAppCacheNow.accesskey;"/>
+ </hbox>
+ <hbox align="center">
+ <checkbox id="offlineNotify"
+ label="&offlineNotify.label;" accesskey="&offlineNotify.accesskey;"
+ preference="browser.offline-apps.notify"
+ onsyncfrompreference="return gAdvancedPane.readOfflineNotify();"/>
+ <spacer flex="1"/>
+ <button id="offlineNotifyExceptions"
+ label="&offlineNotifyExceptions.label;"
+ accesskey="&offlineNotifyExceptions.accesskey;"/>
+ </hbox>
+ <hbox>
+ <vbox flex="1">
+ <label id="offlineAppsListLabel">&offlineAppsList2.label;</label>
+ <listbox id="offlineAppsList"
+ flex="1"
+ aria-labelledby="offlineAppsListLabel">
+ </listbox>
+ </vbox>
+ <vbox pack="end">
+ <button id="offlineAppsListRemove"
+ disabled="true"
+ label="&offlineAppsListRemove.label;"
+ accesskey="&offlineAppsListRemove.accesskey;"/>
+ </vbox>
+ </hbox>
+ </groupbox>
+ </tabpanel>
+
+ <!-- Update -->
+ <tabpanel id="updatePanel" orient="vertical">
+#ifdef MOZ_UPDATER
+ <groupbox id="updateApp" align="start">
+ <caption><label>&updateApp.label;</label></caption>
+ <radiogroup id="updateRadioGroup" align="start">
+ <radio id="autoDesktop"
+ value="auto"
+ label="&updateAuto1.label;"
+ accesskey="&updateAuto1.accesskey;"/>
+ <radio value="checkOnly"
+ label="&updateCheck.label;"
+ accesskey="&updateCheck.accesskey;"/>
+ <radio value="manual"
+ label="&updateManual.label;"
+ accesskey="&updateManual.accesskey;"/>
+ </radiogroup>
+ <separator class="thin"/>
+ <hbox>
+ <button id="showUpdateHistory"
+ label="&updateHistory.label;"
+ accesskey="&updateHistory.accesskey;"
+ preference="app.update.disable_button.showUpdateHistory"/>
+ </hbox>
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+ <checkbox id="useService"
+ label="&useService.label;"
+ accesskey="&useService.accesskey;"
+ preference="app.update.service.enabled"/>
+#endif
+ </groupbox>
+#endif
+ <groupbox id="updateOthers" align="start">
+ <caption><label>&updateOthers.label;</label></caption>
+ <checkbox id="enableSearchUpdate"
+ label="&enableSearchUpdate.label;"
+ accesskey="&enableSearchUpdate.accesskey;"
+ preference="browser.search.update"/>
+ </groupbox>
+ </tabpanel>
+
+ <!-- Certificates -->
+ <tabpanel id="encryptionPanel" orient="vertical">
+ <groupbox id="certSelection" align="start">
+ <caption><label>&certSelection.label;</label></caption>
+ <description id="CertSelectionDesc" control="certSelection">&certSelection.description;</description>
+
+ <!--
+ The values on these radio buttons may look like l12y issues, but
+ they're not - this preference uses *those strings* as its values.
+ I KID YOU NOT.
+ -->
+ <radiogroup id="certSelection"
+ preftype="string"
+ preference="security.default_personal_cert"
+ aria-labelledby="CertSelectionDesc">
+ <radio label="&certs.auto;"
+ accesskey="&certs.auto.accesskey;"
+ value="Select Automatically"/>
+ <radio label="&certs.ask;"
+ accesskey="&certs.ask.accesskey;"
+ value="Ask Every Time"/>
+ </radiogroup>
+ </groupbox>
+ <separator/>
+ <checkbox id="enableOCSP"
+ label="&enableOCSP.label;"
+ accesskey="&enableOCSP.accesskey;"
+ onsyncfrompreference="return gAdvancedPane.readEnableOCSP();"
+ onsynctopreference="return gAdvancedPane.writeEnableOCSP();"
+ preference="security.OCSP.enabled"/>
+ <separator/>
+ <hbox>
+ <button id="viewCertificatesButton"
+ flex="1"
+ label="&viewCerts.label;"
+ accesskey="&viewCerts.accesskey;"
+ preference="security.disable_button.openCertManager"/>
+ <button id="viewSecurityDevicesButton"
+ flex="1"
+ label="&viewSecurityDevices.label;"
+ accesskey="&viewSecurityDevices.accesskey;"
+ preference="security.disable_button.openDeviceManager"/>
+ <hbox flex="10"/>
+ </hbox>
+ </tabpanel>
+ </tabpanels>
+</tabbox>
diff --git a/browser/components/preferences/in-content/applications.js b/browser/components/preferences/in-content/applications.js
new file mode 100644
index 000000000..6f2989657
--- /dev/null
+++ b/browser/components/preferences/in-content/applications.js
@@ -0,0 +1,1900 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 & Enumeration Values
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+Components.utils.import('resource://gre/modules/AppConstants.jsm');
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_PDF = "application/pdf";
+
+const PREF_PDFJS_DISABLED = "pdfjs.disabled";
+const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
+
+const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
+
+// Preferences that affect which entries to show in the list.
+const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
+const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
+ "browser.download.hide_plugins_without_extensions";
+
+/*
+ * Preferences where we store handling information about the feed type.
+ *
+ * browser.feeds.handler
+ * - "bookmarks", "reader" (clarified further using the .default preference),
+ * or "ask" -- indicates the default handler being used to process feeds;
+ * "bookmarks" is obsolete; to specify that the handler is bookmarks,
+ * set browser.feeds.handler.default to "bookmarks";
+ *
+ * browser.feeds.handler.default
+ * - "bookmarks", "client" or "web" -- indicates the chosen feed reader used
+ * to display feeds, either transiently (i.e., when the "use as default"
+ * checkbox is unchecked, corresponds to when browser.feeds.handler=="ask")
+ * or more permanently (i.e., the item displayed in the dropdown in Feeds
+ * preferences)
+ *
+ * browser.feeds.handler.webservice
+ * - the URL of the currently selected web service used to read feeds
+ *
+ * browser.feeds.handlers.application
+ * - nsILocalFile, stores the current client-side feed reading app if one has
+ * been chosen
+ */
+const PREF_FEED_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_FEED_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_FEED_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_FEED_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_FEED_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_FEED_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_FEED_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_FEED_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_FEED_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_FEED_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_FEED_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_FEED_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+// The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify
+// the actions the application can take with content of various types.
+// But since nsIHandlerInfo doesn't support plugins, there's no value
+// identifying the "use plugin" action, so we use this constant instead.
+const kActionUsePlugin = 5;
+
+const ICON_URL_APP = AppConstants.platform == "linux" ?
+ "moz-icon://dummy.exe?size=16" :
+ "chrome://browser/skin/preferences/application.png";
+
+// For CSS. Can be one of "ask", "save", "plugin" or "feed". If absent, the icon URL
+// was set by us to a custom handler icon and CSS should not try to override it.
+const APP_ICON_ATTR_NAME = "appHandlerIcon";
+
+// Utilities
+
+function getFileDisplayName(file) {
+ if (AppConstants.platform == "win") {
+ if (file instanceof Ci.nsILocalFileWin) {
+ try {
+ return file.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+ }
+ if (AppConstants.platform == "macosx") {
+ if (file instanceof Ci.nsILocalFileMac) {
+ try {
+ return file.bundleDisplayName;
+ } catch (e) {}
+ }
+ }
+ return file.leafName;
+}
+
+function getLocalHandlerApp(aFile) {
+ var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ localHandlerApp.name = getFileDisplayName(aFile);
+ localHandlerApp.executable = aFile;
+
+ return localHandlerApp;
+}
+
+/**
+ * An enumeration of items in a JS array.
+ *
+ * FIXME: use ArrayConverter once it lands (bug 380839).
+ *
+ * @constructor
+ */
+function ArrayEnumerator(aItems) {
+ this._index = 0;
+ this._contents = aItems;
+}
+
+ArrayEnumerator.prototype = {
+ _index: 0,
+
+ hasMoreElements: function() {
+ return this._index < this._contents.length;
+ },
+
+ getNext: function() {
+ return this._contents[this._index++];
+ }
+};
+
+function isFeedType(t) {
+ return t == TYPE_MAYBE_FEED || t == TYPE_MAYBE_VIDEO_FEED || t == TYPE_MAYBE_AUDIO_FEED;
+}
+
+// HandlerInfoWrapper
+
+/**
+ * This object wraps nsIHandlerInfo with some additional functionality
+ * the Applications prefpane needs to display and allow modification of
+ * the list of handled types.
+ *
+ * We create an instance of this wrapper for each entry we might display
+ * in the prefpane, and we compose the instances from various sources,
+ * including plugins and the handler service.
+ *
+ * We don't implement all the original nsIHandlerInfo functionality,
+ * just the stuff that the prefpane needs.
+ *
+ * In theory, all of the custom functionality in this wrapper should get
+ * pushed down into nsIHandlerInfo eventually.
+ */
+function HandlerInfoWrapper(aType, aHandlerInfo) {
+ this._type = aType;
+ this.wrappedHandlerInfo = aHandlerInfo;
+}
+
+HandlerInfoWrapper.prototype = {
+ // The wrapped nsIHandlerInfo object. In general, this object is private,
+ // but there are a couple cases where callers access it directly for things
+ // we haven't (yet?) implemented, so we make it a public property.
+ wrappedHandlerInfo: null,
+
+
+ // Convenience Utils
+
+ _handlerSvc: Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService),
+
+ _prefSvc: Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch),
+
+ _categoryMgr: Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager),
+
+ element: function(aID) {
+ return document.getElementById(aID);
+ },
+
+
+ // nsIHandlerInfo
+
+ // The MIME type or protocol scheme.
+ _type: null,
+ get type() {
+ return this._type;
+ },
+
+ get description() {
+ if (this.wrappedHandlerInfo.description)
+ return this.wrappedHandlerInfo.description;
+
+ if (this.primaryExtension) {
+ var extension = this.primaryExtension.toUpperCase();
+ return this.element("bundlePreferences").getFormattedString("fileEnding",
+ [extension]);
+ }
+
+ return this.type;
+ },
+
+ get preferredApplicationHandler() {
+ return this.wrappedHandlerInfo.preferredApplicationHandler;
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue;
+
+ // Make sure the preferred handler is in the set of possible handlers.
+ if (aNewValue)
+ this.addPossibleApplicationHandler(aNewValue)
+ },
+
+ get possibleApplicationHandlers() {
+ return this.wrappedHandlerInfo.possibleApplicationHandlers;
+ },
+
+ addPossibleApplicationHandler: function(aNewHandler) {
+ var possibleApps = this.possibleApplicationHandlers.enumerate();
+ while (possibleApps.hasMoreElements()) {
+ if (possibleApps.getNext().equals(aNewHandler))
+ return;
+ }
+ this.possibleApplicationHandlers.appendElement(aNewHandler, false);
+ },
+
+ removePossibleApplicationHandler: function(aHandler) {
+ var defaultApp = this.preferredApplicationHandler;
+ if (defaultApp && aHandler.equals(defaultApp)) {
+ // If the app we remove was the default app, we must make sure
+ // it won't be used anymore
+ this.alwaysAskBeforeHandling = true;
+ this.preferredApplicationHandler = null;
+ }
+
+ var handlers = this.possibleApplicationHandlers;
+ for (var i = 0; i < handlers.length; ++i) {
+ var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp);
+ if (handler.equals(aHandler)) {
+ handlers.removeElementAt(i);
+ break;
+ }
+ }
+ },
+
+ get hasDefaultHandler() {
+ return this.wrappedHandlerInfo.hasDefaultHandler;
+ },
+
+ get defaultDescription() {
+ return this.wrappedHandlerInfo.defaultDescription;
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ // If we have an enabled plugin, then the action is to use that plugin.
+ if (this.pluginName && !this.isDisabledPluginType)
+ return kActionUsePlugin;
+
+ // If the action is to use a helper app, but we don't have a preferred
+ // handler app, then switch to using the system default, if any; otherwise
+ // fall back to saving to disk, which is the default action in nsMIMEInfo.
+ // Note: "save to disk" is an invalid value for protocol info objects,
+ // but the alwaysAskBeforeHandling getter will detect that situation
+ // and always return true in that case to override this invalid value.
+ if (this.wrappedHandlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
+ !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
+ if (this.wrappedHandlerInfo.hasDefaultHandler)
+ return Ci.nsIHandlerInfo.useSystemDefault;
+ return Ci.nsIHandlerInfo.saveToDisk;
+ }
+
+ return this.wrappedHandlerInfo.preferredAction;
+ },
+
+ set preferredAction(aNewValue) {
+ // If the action is to use the plugin,
+ // we must set the preferred action to "save to disk".
+ // But only if it's not currently the preferred action.
+ if ((aNewValue == kActionUsePlugin) &&
+ (this.preferredAction != Ci.nsIHandlerInfo.saveToDisk)) {
+ aNewValue = Ci.nsIHandlerInfo.saveToDisk;
+ }
+
+ // We don't modify the preferred action if the new action is to use a plugin
+ // because handler info objects don't understand our custom "use plugin"
+ // value. Also, leaving it untouched means that we can automatically revert
+ // to the old setting if the user ever removes the plugin.
+
+ if (aNewValue != kActionUsePlugin)
+ this.wrappedHandlerInfo.preferredAction = aNewValue;
+ },
+
+ get alwaysAskBeforeHandling() {
+ // If this type is handled only by a plugin, we can't trust the value
+ // in the handler info object, since it'll be a default based on the absence
+ // of any user configuration, and the default in that case is to always ask,
+ // even though we never ask for content handled by a plugin, so special case
+ // plugin-handled types by returning false here.
+ if (this.pluginName && this.handledOnlyByPlugin)
+ return false;
+
+ // If this is a protocol type and the preferred action is "save to disk",
+ // which is invalid for such types, then return true here to override that
+ // action. This could happen when the preferred action is to use a helper
+ // app, but the preferredApplicationHandler is invalid, and there isn't
+ // a default handler, so the preferredAction getter returns save to disk
+ // instead.
+ if (!(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
+ this.preferredAction == Ci.nsIHandlerInfo.saveToDisk)
+ return true;
+
+ return this.wrappedHandlerInfo.alwaysAskBeforeHandling;
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue;
+ },
+
+
+ // nsIMIMEInfo
+
+ // The primary file extension associated with this type, if any.
+ //
+ // XXX Plugin objects contain an array of MimeType objects with "suffixes"
+ // properties; if this object has an associated plugin, shouldn't we check
+ // those properties for an extension?
+ get primaryExtension() {
+ try {
+ if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ this.wrappedHandlerInfo.primaryExtension)
+ return this.wrappedHandlerInfo.primaryExtension
+ } catch (ex) {}
+
+ return null;
+ },
+
+
+ // Plugin Handling
+
+ // A plugin that can handle this type, if any.
+ //
+ // Note: just because we have one doesn't mean it *will* handle the type.
+ // That depends on whether or not the type is in the list of types for which
+ // plugin handling is disabled.
+ plugin: null,
+
+ // Whether or not this type is only handled by a plugin or is also handled
+ // by some user-configured action as specified in the handler info object.
+ //
+ // Note: we can't just check if there's a handler info object for this type,
+ // because OS and user configuration is mixed up in the handler info object,
+ // so we always need to retrieve it for the OS info and can't tell whether
+ // it represents only OS-default information or user-configured information.
+ //
+ // FIXME: once handler info records are broken up into OS-provided records
+ // and user-configured records, stop using this boolean flag and simply
+ // check for the presence of a user-configured record to determine whether
+ // or not this type is only handled by a plugin. Filed as bug 395142.
+ handledOnlyByPlugin: undefined,
+
+ get isDisabledPluginType() {
+ return this._getDisabledPluginTypes().indexOf(this.type) != -1;
+ },
+
+ _getDisabledPluginTypes: function() {
+ var types = "";
+
+ if (this._prefSvc.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES))
+ types = this._prefSvc.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
+
+ // Only split if the string isn't empty so we don't end up with an array
+ // containing a single empty string.
+ if (types != "")
+ return types.split(",");
+
+ return [];
+ },
+
+ disablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ if (disabledPluginTypes.indexOf(this.type) == -1)
+ disabledPluginTypes.push(this.type);
+
+ this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ this._categoryMgr.deleteCategoryEntry("Gecko-Content-Viewers",
+ this.type,
+ false);
+ },
+
+ enablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ var type = this.type;
+ disabledPluginTypes = disabledPluginTypes.filter(v => v != type);
+
+ this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ this._categoryMgr.
+ addCategoryEntry("Gecko-Content-Viewers",
+ this.type,
+ "@mozilla.org/content/plugin/document-loader-factory;1",
+ false,
+ true);
+ },
+
+
+ // Storage
+
+ store: function() {
+ this._handlerSvc.store(this.wrappedHandlerInfo);
+ },
+
+
+ // Icons
+
+ get smallIcon() {
+ return this._getIcon(16);
+ },
+
+ _getIcon: function(aSize) {
+ if (this.primaryExtension)
+ return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize;
+
+ if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo)
+ return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type;
+
+ // FIXME: consider returning some generic icon when we can't get a URL for
+ // one (for example in the case of protocol schemes). Filed as bug 395141.
+ return null;
+ }
+
+};
+
+
+// Feed Handler Info
+
+/**
+ * This object implements nsIHandlerInfo for the feed types. It's a separate
+ * object because we currently store handling information for the feed type
+ * in a set of preferences rather than the nsIHandlerService-managed datastore.
+ *
+ * This object inherits from HandlerInfoWrapper in order to get functionality
+ * that isn't special to the feed type.
+ *
+ * XXX Should we inherit from HandlerInfoWrapper? After all, we override
+ * most of that wrapper's properties and methods, and we have to dance around
+ * the fact that the wrapper expects to have a wrappedHandlerInfo, which we
+ * don't provide.
+ */
+
+function FeedHandlerInfo(aMIMEType) {
+ HandlerInfoWrapper.call(this, aMIMEType, null);
+}
+
+FeedHandlerInfo.prototype = {
+ __proto__: HandlerInfoWrapper.prototype,
+
+ // Convenience Utils
+
+ _converterSvc:
+ Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService),
+
+ _shellSvc: AppConstants.HAVE_SHELL_SERVICE ? getShellService() : null,
+
+ // nsIHandlerInfo
+
+ get description() {
+ return this.element("bundlePreferences").getString(this._appPrefLabel);
+ },
+
+ get preferredApplicationHandler() {
+ switch (this.element(this._prefSelectedReader).value) {
+ case "client":
+ var file = this.element(this._prefSelectedApp).value;
+ if (file)
+ return getLocalHandlerApp(file);
+
+ return null;
+
+ case "web":
+ var uri = this.element(this._prefSelectedWeb).value;
+ if (!uri)
+ return null;
+ return this._converterSvc.getWebContentHandlerByURI(this.type, uri);
+
+ case "bookmarks":
+ default:
+ // When the pref is set to bookmarks, we handle feeds internally,
+ // we don't forward them to a local or web handler app, so there is
+ // no preferred handler.
+ return null;
+ }
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ if (aNewValue instanceof Ci.nsILocalHandlerApp) {
+ this.element(this._prefSelectedApp).value = aNewValue.executable;
+ this.element(this._prefSelectedReader).value = "client";
+ }
+ else if (aNewValue instanceof Ci.nsIWebContentHandlerInfo) {
+ this.element(this._prefSelectedWeb).value = aNewValue.uri;
+ this.element(this._prefSelectedReader).value = "web";
+ // Make the web handler be the new "auto handler" for feeds.
+ // Note: we don't have to unregister the auto handler when the user picks
+ // a non-web handler (local app, Live Bookmarks, etc.) because the service
+ // only uses the "auto handler" when the selected reader is a web handler.
+ // We also don't have to unregister it when the user turns on "always ask"
+ // (i.e. preview in browser), since that also overrides the auto handler.
+ this._converterSvc.setAutoHandler(this.type, aNewValue);
+ }
+ },
+
+ _possibleApplicationHandlers: null,
+
+ get possibleApplicationHandlers() {
+ if (this._possibleApplicationHandlers)
+ return this._possibleApplicationHandlers;
+
+ // A minimal implementation of nsIMutableArray. It only supports the two
+ // methods its callers invoke, namely appendElement and nsIArray::enumerate.
+ this._possibleApplicationHandlers = {
+ _inner: [],
+ _removed: [],
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIMutableArray) ||
+ aIID.equals(Ci.nsIArray) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ get length() {
+ return this._inner.length;
+ },
+
+ enumerate: function() {
+ return new ArrayEnumerator(this._inner);
+ },
+
+ appendElement: function(aHandlerApp, aWeak) {
+ this._inner.push(aHandlerApp);
+ },
+
+ removeElementAt: function(aIndex) {
+ this._removed.push(this._inner[aIndex]);
+ this._inner.splice(aIndex, 1);
+ },
+
+ queryElementAt: function(aIndex, aInterface) {
+ return this._inner[aIndex].QueryInterface(aInterface);
+ }
+ };
+
+ // Add the selected local app if it's different from the OS default handler.
+ // Unlike for other types, we can store only one local app at a time for the
+ // feed type, since we store it in a preference that historically stores
+ // only a single path. But we display all the local apps the user chooses
+ // while the prefpane is open, only dropping the list when the user closes
+ // the prefpane, for maximum usability and consistency with other types.
+ var preferredAppFile = this.element(this._prefSelectedApp).value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ let defaultApp = this._defaultApplicationHandler;
+ if (!defaultApp || !defaultApp.equals(preferredApp))
+ this._possibleApplicationHandlers.appendElement(preferredApp, false);
+ }
+
+ // Add the registered web handlers. There can be any number of these.
+ var webHandlers = this._converterSvc.getContentHandlers(this.type);
+ for (let webHandler of webHandlers)
+ this._possibleApplicationHandlers.appendElement(webHandler, false);
+
+ return this._possibleApplicationHandlers;
+ },
+
+ __defaultApplicationHandler: undefined,
+ get _defaultApplicationHandler() {
+ if (typeof this.__defaultApplicationHandler != "undefined")
+ return this.__defaultApplicationHandler;
+
+ var defaultFeedReader = null;
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ try {
+ defaultFeedReader = this._shellSvc.defaultFeedReader;
+ }
+ catch (ex) {
+ // no default reader or _shellSvc is null
+ }
+ }
+
+ if (defaultFeedReader) {
+ let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsIHandlerApp);
+ handlerApp.name = getFileDisplayName(defaultFeedReader);
+ handlerApp.QueryInterface(Ci.nsILocalHandlerApp);
+ handlerApp.executable = defaultFeedReader;
+
+ this.__defaultApplicationHandler = handlerApp;
+ }
+ else {
+ this.__defaultApplicationHandler = null;
+ }
+
+ return this.__defaultApplicationHandler;
+ },
+
+ get hasDefaultHandler() {
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ try {
+ if (this._shellSvc.defaultFeedReader)
+ return true;
+ }
+ catch (ex) {
+ // no default reader or _shellSvc is null
+ }
+ }
+
+ return false;
+ },
+
+ get defaultDescription() {
+ if (this.hasDefaultHandler)
+ return this._defaultApplicationHandler.name;
+
+ // Should we instead return null?
+ return "";
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ switch (this.element(this._prefSelectedAction).value) {
+
+ case "bookmarks":
+ return Ci.nsIHandlerInfo.handleInternally;
+
+ case "reader": {
+ let preferredApp = this.preferredApplicationHandler;
+ let defaultApp = this._defaultApplicationHandler;
+
+ // If we have a valid preferred app, return useSystemDefault if it's
+ // the default app; otherwise return useHelperApp.
+ if (gApplicationsPane.isValidHandlerApp(preferredApp)) {
+ if (defaultApp && defaultApp.equals(preferredApp))
+ return Ci.nsIHandlerInfo.useSystemDefault;
+
+ return Ci.nsIHandlerInfo.useHelperApp;
+ }
+
+ // The pref is set to "reader", but we don't have a valid preferred app.
+ // What do we do now? Not sure this is the best option (perhaps we
+ // should direct the user to the default app, if any), but for now let's
+ // direct the user to live bookmarks.
+ return Ci.nsIHandlerInfo.handleInternally;
+ }
+
+ // If the action is "ask", then alwaysAskBeforeHandling will override
+ // the action, so it doesn't matter what we say it is, it just has to be
+ // something that doesn't cause the controller to hide the type.
+ case "ask":
+ default:
+ return Ci.nsIHandlerInfo.handleInternally;
+ }
+ },
+
+ set preferredAction(aNewValue) {
+ switch (aNewValue) {
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ this.element(this._prefSelectedReader).value = "bookmarks";
+ break;
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ this.element(this._prefSelectedAction).value = "reader";
+ // The controller has already set preferredApplicationHandler
+ // to the new helper app.
+ break;
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ this.element(this._prefSelectedAction).value = "reader";
+ this.preferredApplicationHandler = this._defaultApplicationHandler;
+ break;
+ }
+ },
+
+ get alwaysAskBeforeHandling() {
+ return this.element(this._prefSelectedAction).value == "ask";
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ if (aNewValue == true)
+ this.element(this._prefSelectedAction).value = "ask";
+ else
+ this.element(this._prefSelectedAction).value = "reader";
+ },
+
+ // Whether or not we are currently storing the action selected by the user.
+ // We use this to suppress notification-triggered updates to the list when
+ // we make changes that may spawn such updates, specifically when we change
+ // the action for the feed type, which results in feed preference updates,
+ // which spawn "pref changed" notifications that would otherwise cause us
+ // to rebuild the view unnecessarily.
+ _storingAction: false,
+
+
+ // nsIMIMEInfo
+
+ get primaryExtension() {
+ return "xml";
+ },
+
+
+ // Storage
+
+ // Changes to the preferred action and handler take effect immediately
+ // (we write them out to the preferences right as they happen),
+ // so we when the controller calls store() after modifying the handlers,
+ // the only thing we need to store is the removal of possible handlers
+ // XXX Should we hold off on making the changes until this method gets called?
+ store: function() {
+ for (let app of this._possibleApplicationHandlers._removed) {
+ if (app instanceof Ci.nsILocalHandlerApp) {
+ let pref = this.element(PREF_FEED_SELECTED_APP);
+ var preferredAppFile = pref.value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ if (app.equals(preferredApp))
+ pref.reset();
+ }
+ }
+ else {
+ app.QueryInterface(Ci.nsIWebContentHandlerInfo);
+ this._converterSvc.removeContentHandler(app.contentType, app.uri);
+ }
+ }
+ this._possibleApplicationHandlers._removed = [];
+ },
+
+
+ // Icons
+
+ get smallIcon() {
+ return this._smallIcon;
+ }
+
+};
+
+var feedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_FEED),
+ _prefSelectedApp: PREF_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/feedIcon16.png",
+ _appPrefLabel: "webFeed"
+}
+
+var videoFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_VIDEO_FEED),
+ _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_VIDEO_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/videoFeedIcon16.png",
+ _appPrefLabel: "videoPodcastFeed"
+}
+
+var audioFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_AUDIO_FEED),
+ _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_AUDIO_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/audioFeedIcon16.png",
+ _appPrefLabel: "audioPodcastFeed"
+}
+
+/**
+ * InternalHandlerInfoWrapper provides a basic mechanism to create an internal
+ * mime type handler that can be enabled/disabled in the applications preference
+ * menu.
+ */
+function InternalHandlerInfoWrapper(aMIMEType) {
+ var mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ var handlerInfo = mimeSvc.getFromTypeAndExtension(aMIMEType, null);
+
+ HandlerInfoWrapper.call(this, aMIMEType, handlerInfo);
+}
+
+InternalHandlerInfoWrapper.prototype = {
+ __proto__: HandlerInfoWrapper.prototype,
+
+ // Override store so we so we can notify any code listening for registration
+ // or unregistration of this handler.
+ store: function() {
+ HandlerInfoWrapper.prototype.store.call(this);
+ Services.obs.notifyObservers(null, this._handlerChanged, null);
+ },
+
+ get enabled() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get description() {
+ return this.element("bundlePreferences").getString(this._appPrefLabel);
+ }
+};
+
+var pdfHandlerInfo = {
+ __proto__: new InternalHandlerInfoWrapper(TYPE_PDF),
+ _handlerChanged: TOPIC_PDFJS_HANDLER_CHANGED,
+ _appPrefLabel: "portableDocumentFormat",
+ get enabled() {
+ return !Services.prefs.getBoolPref(PREF_PDFJS_DISABLED);
+ },
+};
+
+
+// Prefpane Controller
+
+var gApplicationsPane = {
+ // The set of types the app knows how to handle. A hash of HandlerInfoWrapper
+ // objects, indexed by type.
+ _handledTypes: {},
+
+ // The list of types we can show, sorted by the sort column/direction.
+ // An array of HandlerInfoWrapper objects. We build this list when we first
+ // load the data and then rebuild it when users change a pref that affects
+ // what types we can show or change the sort column/direction.
+ // Note: this isn't necessarily the list of types we *will* show; if the user
+ // provides a filter string, we'll only show the subset of types in this list
+ // that match that string.
+ _visibleTypes: [],
+
+ // A count of the number of times each visible type description appears.
+ // We use these counts to determine whether or not to annotate descriptions
+ // with their types to distinguish duplicate descriptions from each other.
+ // A hash of integer counts, indexed by string description.
+ _visibleTypeDescriptionCount: {},
+
+
+ // Convenience & Performance Shortcuts
+
+ // These get defined by init().
+ _brandShortName : null,
+ _prefsBundle : null,
+ _list : null,
+ _filter : null,
+
+ _prefSvc : Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch),
+
+ _mimeSvc : Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService),
+
+ _helperAppSvc : Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+ getService(Ci.nsIExternalHelperAppService),
+
+ _handlerSvc : Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService),
+
+ _ioSvc : Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService),
+
+
+ // Initialization & Destruction
+
+ init: function() {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gApplicationsPane));
+ }
+
+ // Initialize shortcuts to some commonly accessed elements & values.
+ this._brandShortName =
+ document.getElementById("bundleBrand").getString("brandShortName");
+ this._prefsBundle = document.getElementById("bundlePreferences");
+ this._list = document.getElementById("handlersView");
+ this._filter = document.getElementById("filter");
+
+ // Observe preferences that influence what we display so we can rebuild
+ // the view when they change.
+ this._prefSvc.addObserver(PREF_SHOW_PLUGINS_IN_LIST, this, false);
+ this._prefSvc.addObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_READER, this, false);
+
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_READER, this, false);
+
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_READER, this, false);
+
+
+ setEventListener("focusSearch1", "command", gApplicationsPane.focusFilterBox);
+ setEventListener("focusSearch2", "command", gApplicationsPane.focusFilterBox);
+ setEventListener("filter", "command", gApplicationsPane.filter);
+ setEventListener("handlersView", "select",
+ gApplicationsPane.onSelectionChanged);
+ setEventListener("typeColumn", "click", gApplicationsPane.sort);
+ setEventListener("actionColumn", "click", gApplicationsPane.sort);
+
+ // Listen for window unload so we can remove our preference observers.
+ window.addEventListener("unload", this, false);
+
+ // Figure out how we should be sorting the list. We persist sort settings
+ // across sessions, so we can't assume the default sort column/direction.
+ // XXX should we be using the XUL sort service instead?
+ if (document.getElementById("actionColumn").hasAttribute("sortDirection")) {
+ this._sortColumn = document.getElementById("actionColumn");
+ // The typeColumn element always has a sortDirection attribute,
+ // either because it was persisted or because the default value
+ // from the xul file was used. If we are sorting on the other
+ // column, we should remove it.
+ document.getElementById("typeColumn").removeAttribute("sortDirection");
+ }
+ else
+ this._sortColumn = document.getElementById("typeColumn");
+
+ // Load the data and build the list of handlers.
+ // By doing this in a timeout, we let the preferences dialog resize itself
+ // to an appropriate size before we add a bunch of items to the list.
+ // Otherwise, if there are many items, and the Applications prefpane
+ // is the one that gets displayed when the user first opens the dialog,
+ // the dialog might stretch too much in an attempt to fit them all in.
+ // XXX Shouldn't we perhaps just set a max-height on the richlistbox?
+ var _delayedPaneLoad = function(self) {
+ self._loadData();
+ self._rebuildVisibleTypes();
+ self._sortVisibleTypes();
+ self._rebuildView();
+
+ // Notify observers that the UI is now ready
+ Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
+ notifyObservers(window, "app-handler-pane-loaded", null);
+ }
+ setTimeout(_delayedPaneLoad, 0, this);
+ },
+
+ destroy: function() {
+ window.removeEventListener("unload", this, false);
+ this._prefSvc.removeObserver(PREF_SHOW_PLUGINS_IN_LIST, this);
+ this._prefSvc.removeObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_READER, this);
+
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_READER, this);
+
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_READER, this);
+ },
+
+
+ // nsISupports
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIObserver) ||
+ aIID.equals(Ci.nsIDOMEventListener ||
+ aIID.equals(Ci.nsISupports)))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ // nsIObserver
+
+ observe: function (aSubject, aTopic, aData) {
+ // Rebuild the list when there are changes to preferences that influence
+ // whether or not to show certain entries in the list.
+ if (aTopic == "nsPref:changed" && !this._storingAction) {
+ // These two prefs alter the list of visible types, so we have to rebuild
+ // that list when they change.
+ if (aData == PREF_SHOW_PLUGINS_IN_LIST ||
+ aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) {
+ this._rebuildVisibleTypes();
+ this._sortVisibleTypes();
+ }
+
+ // All the prefs we observe can affect what we display, so we rebuild
+ // the view when any of them changes.
+ this._rebuildView();
+ }
+ },
+
+
+ // nsIDOMEventListener
+
+ handleEvent: function(aEvent) {
+ if (aEvent.type == "unload") {
+ this.destroy();
+ }
+ },
+
+
+ // Composed Model Construction
+
+ _loadData: function() {
+ this._loadFeedHandler();
+ this._loadInternalHandlers();
+ this._loadPluginHandlers();
+ this._loadApplicationHandlers();
+ },
+
+ _loadFeedHandler: function() {
+ this._handledTypes[TYPE_MAYBE_FEED] = feedHandlerInfo;
+ feedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_VIDEO_FEED] = videoFeedHandlerInfo;
+ videoFeedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_AUDIO_FEED] = audioFeedHandlerInfo;
+ audioFeedHandlerInfo.handledOnlyByPlugin = false;
+ },
+
+ /**
+ * Load higher level internal handlers so they can be turned on/off in the
+ * applications menu.
+ */
+ _loadInternalHandlers: function() {
+ var internalHandlers = [pdfHandlerInfo];
+ for (let internalHandler of internalHandlers) {
+ if (internalHandler.enabled) {
+ this._handledTypes[internalHandler.type] = internalHandler;
+ }
+ }
+ },
+
+ /**
+ * Load the set of handlers defined by plugins.
+ *
+ * Note: if there's more than one plugin for a given MIME type, we assume
+ * the last one is the one that the application will use. That may not be
+ * correct, but it's how we've been doing it for years.
+ *
+ * Perhaps we should instead query navigator.mimeTypes for the set of types
+ * supported by the application and then get the plugin from each MIME type's
+ * enabledPlugin property. But if there's a plugin for a type, we need
+ * to know about it even if it isn't enabled, since we're going to give
+ * the user an option to enable it.
+ *
+ * Also note that enabledPlugin does not get updated when
+ * plugin.disable_full_page_plugin_for_types changes, so even if we could use
+ * enabledPlugin to get the plugin that would be used, we'd still need to
+ * check the pref ourselves to find out if it's enabled.
+ */
+ _loadPluginHandlers: function() {
+ "use strict";
+
+ let mimeTypes = navigator.mimeTypes;
+
+ for (let mimeType of mimeTypes) {
+ let handlerInfoWrapper;
+ if (mimeType.type in this._handledTypes) {
+ handlerInfoWrapper = this._handledTypes[mimeType.type];
+ } else {
+ let wrappedHandlerInfo =
+ this._mimeSvc.getFromTypeAndExtension(mimeType.type, null);
+ handlerInfoWrapper = new HandlerInfoWrapper(mimeType.type, wrappedHandlerInfo);
+ handlerInfoWrapper.handledOnlyByPlugin = true;
+ this._handledTypes[mimeType.type] = handlerInfoWrapper;
+ }
+ handlerInfoWrapper.pluginName = mimeType.enabledPlugin.name;
+ }
+ },
+
+ /**
+ * Load the set of handlers defined by the application datastore.
+ */
+ _loadApplicationHandlers: function() {
+ var wrappedHandlerInfos = this._handlerSvc.enumerate();
+ while (wrappedHandlerInfos.hasMoreElements()) {
+ let wrappedHandlerInfo =
+ wrappedHandlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
+ let type = wrappedHandlerInfo.type;
+
+ let handlerInfoWrapper;
+ if (type in this._handledTypes)
+ handlerInfoWrapper = this._handledTypes[type];
+ else {
+ handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
+ this._handledTypes[type] = handlerInfoWrapper;
+ }
+
+ handlerInfoWrapper.handledOnlyByPlugin = false;
+ }
+ },
+
+
+ // View Construction
+
+ _rebuildVisibleTypes: function() {
+ // Reset the list of visible types and the visible type description counts.
+ this._visibleTypes = [];
+ this._visibleTypeDescriptionCount = {};
+
+ // Get the preferences that help determine what types to show.
+ var showPlugins = this._prefSvc.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST);
+ var hidePluginsWithoutExtensions =
+ this._prefSvc.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS);
+
+ for (let type in this._handledTypes) {
+ let handlerInfo = this._handledTypes[type];
+
+ // Hide plugins without associated extensions if so prefed so we don't
+ // show a whole bunch of obscure types handled by plugins on Mac.
+ // Note: though protocol types don't have extensions, we still show them;
+ // the pref is only meant to be applied to MIME types, since plugins are
+ // only associated with MIME types.
+ // FIXME: should we also check the "suffixes" property of the plugin?
+ // Filed as bug 395135.
+ if (hidePluginsWithoutExtensions && handlerInfo.handledOnlyByPlugin &&
+ handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ !handlerInfo.primaryExtension)
+ continue;
+
+ // Hide types handled only by plugins if so prefed.
+ if (handlerInfo.handledOnlyByPlugin && !showPlugins)
+ continue;
+
+ // We couldn't find any reason to exclude the type, so include it.
+ this._visibleTypes.push(handlerInfo);
+
+ if (handlerInfo.description in this._visibleTypeDescriptionCount)
+ this._visibleTypeDescriptionCount[handlerInfo.description]++;
+ else
+ this._visibleTypeDescriptionCount[handlerInfo.description] = 1;
+ }
+ },
+
+ _rebuildView: function() {
+ // Clear the list of entries.
+ while (this._list.childNodes.length > 1)
+ this._list.removeChild(this._list.lastChild);
+
+ var visibleTypes = this._visibleTypes;
+
+ // If the user is filtering the list, then only show matching types.
+ if (this._filter.value)
+ visibleTypes = visibleTypes.filter(this._matchesFilter, this);
+
+ for (let visibleType of visibleTypes) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("type", visibleType.type);
+ item.setAttribute("typeDescription", this._describeType(visibleType));
+ if (visibleType.smallIcon)
+ item.setAttribute("typeIcon", visibleType.smallIcon);
+ item.setAttribute("actionDescription",
+ this._describePreferredAction(visibleType));
+
+ if (!this._setIconClassForPreferredAction(visibleType, item)) {
+ item.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(visibleType));
+ }
+
+ this._list.appendChild(item);
+ }
+
+ this._selectLastSelectedType();
+ },
+
+ _matchesFilter: function(aType) {
+ var filterValue = this._filter.value.toLowerCase();
+ return this._describeType(aType).toLowerCase().indexOf(filterValue) != -1 ||
+ this._describePreferredAction(aType).toLowerCase().indexOf(filterValue) != -1;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the type represented by the given
+ * handler info object. Normally this is just the description provided by
+ * the info object, but if more than one object presents the same description,
+ * then we annotate the duplicate descriptions with the type itself to help
+ * users distinguish between those types.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type being described
+ * @returns {string} a description of the type
+ */
+ _describeType: function(aHandlerInfo) {
+ if (this._visibleTypeDescriptionCount[aHandlerInfo.description] > 1)
+ return this._prefsBundle.getFormattedString("typeDescriptionWithType",
+ [aHandlerInfo.description,
+ aHandlerInfo.type]);
+
+ return aHandlerInfo.description;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the preferred action to take on
+ * the type represented by the given handler info object.
+ *
+ * XXX Should this be part of the HandlerInfoWrapper interface? It would
+ * violate the separation of model and view, but it might make more sense
+ * nonetheless (f.e. it would make sortTypes easier).
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type whose preferred action
+ * is being described
+ * @returns {string} a description of the action
+ */
+ _describePreferredAction: function(aHandlerInfo) {
+ // alwaysAskBeforeHandling overrides the preferred action, so if that flag
+ // is set, then describe that behavior instead. For most types, this is
+ // the "alwaysAsk" string, but for the feed type we show something special.
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ if (isFeedType(aHandlerInfo.type))
+ return this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ return this._prefsBundle.getString("alwaysAsk");
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ return this._prefsBundle.getString("saveFile");
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ var preferredApp = aHandlerInfo.preferredApplicationHandler;
+ var name;
+ if (preferredApp instanceof Ci.nsILocalHandlerApp)
+ name = getFileDisplayName(preferredApp.executable);
+ else
+ name = preferredApp.name;
+ return this._prefsBundle.getFormattedString("useApp", [name]);
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ // For the feed type, handleInternally means live bookmarks.
+ if (isFeedType(aHandlerInfo.type)) {
+ return this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+ [this._brandShortName]);
+ }
+
+ if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
+ return this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ }
+
+ // For other types, handleInternally looks like either useHelperApp
+ // or useSystemDefault depending on whether or not there's a preferred
+ // handler app.
+ if (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler))
+ return aHandlerInfo.preferredApplicationHandler.name;
+
+ return aHandlerInfo.defaultDescription;
+
+ // XXX Why don't we say the app will handle the type internally?
+ // Is it because the app can't actually do that? But if that's true,
+ // then why would a preferredAction ever get set to this value
+ // in the first place?
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ return this._prefsBundle.getFormattedString("useDefault",
+ [aHandlerInfo.defaultDescription]);
+
+ case kActionUsePlugin:
+ return this._prefsBundle.getFormattedString("usePluginIn",
+ [aHandlerInfo.pluginName,
+ this._brandShortName]);
+ default:
+ throw new Error(`Unexpected preferredAction: ${aHandlerInfo.preferredAction}`);
+ }
+ },
+
+ _selectLastSelectedType: function() {
+ // If the list is disabled by the pref.downloads.disable_button.edit_actions
+ // preference being locked, then don't select the type, as that would cause
+ // it to appear selected, with a different background and an actions menu
+ // that makes it seem like you can choose an action for the type.
+ if (this._list.disabled)
+ return;
+
+ var lastSelectedType = this._list.getAttribute("lastSelectedType");
+ if (!lastSelectedType)
+ return;
+
+ var item = this._list.getElementsByAttribute("type", lastSelectedType)[0];
+ if (!item)
+ return;
+
+ this._list.selectedItem = item;
+ },
+
+ /**
+ * Whether or not the given handler app is valid.
+ *
+ * @param aHandlerApp {nsIHandlerApp} the handler app in question
+ *
+ * @returns {boolean} whether or not it's valid
+ */
+ isValidHandlerApp: function(aHandlerApp) {
+ if (!aHandlerApp)
+ return false;
+
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ return this._isValidHandlerExecutable(aHandlerApp.executable);
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
+ return aHandlerApp.uriTemplate;
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
+ return aHandlerApp.uri;
+
+ return false;
+ },
+
+ _isValidHandlerExecutable: function(aExecutable) {
+ let leafName;
+ if (AppConstants.platform == "win") {
+ leafName = `${AppConstants.MOZ_APP_NAME}.exe`;
+ } else if (AppConstants.platform == "macosx") {
+ leafName = AppConstants.MOZ_MACBUNDLE_NAME;
+ } else {
+ leafName = `${AppConstants.MOZ_APP_NAME}-bin`;
+ }
+ return aExecutable &&
+ aExecutable.exists() &&
+ aExecutable.isExecutable() &&
+// XXXben - we need to compare this with the running instance executable
+// just don't know how to do that via script...
+// XXXmano TBD: can probably add this to nsIShellService
+ aExecutable.leafName != leafName;
+ },
+
+ /**
+ * Rebuild the actions menu for the selected entry. Gets called by
+ * the richlistitem constructor when an entry in the list gets selected.
+ */
+ rebuildActionsMenu: function() {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+ var menu =
+ document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+ var menuPopup = menu.menupopup;
+
+ // Clear out existing items.
+ while (menuPopup.hasChildNodes())
+ menuPopup.removeChild(menuPopup.lastChild);
+
+ // Add the "Preview in Firefox" option for optional internal handlers.
+ if (handlerInfo instanceof InternalHandlerInfoWrapper) {
+ let internalMenuItem = document.createElement("menuitem");
+ internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
+ let label = this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ internalMenuItem.setAttribute("label", label);
+ internalMenuItem.setAttribute("tooltiptext", label);
+ internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ menuPopup.appendChild(internalMenuItem);
+ }
+
+ {
+ var askMenuItem = document.createElement("menuitem");
+ askMenuItem.setAttribute("action", Ci.nsIHandlerInfo.alwaysAsk);
+ let label;
+ if (isFeedType(handlerInfo.type))
+ label = this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ else
+ label = this._prefsBundle.getString("alwaysAsk");
+ askMenuItem.setAttribute("label", label);
+ askMenuItem.setAttribute("tooltiptext", label);
+ askMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ menuPopup.appendChild(askMenuItem);
+ }
+
+ // Create a menu item for saving to disk.
+ // Note: this option isn't available to protocol types, since we don't know
+ // what it means to save a URL having a certain scheme to disk, nor is it
+ // available to feeds, since the feed code doesn't implement the capability.
+ if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
+ !isFeedType(handlerInfo.type)) {
+ var saveMenuItem = document.createElement("menuitem");
+ saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
+ let label = this._prefsBundle.getString("saveFile");
+ saveMenuItem.setAttribute("label", label);
+ saveMenuItem.setAttribute("tooltiptext", label);
+ saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
+ menuPopup.appendChild(saveMenuItem);
+ }
+
+ // If this is the feed type, add a Live Bookmarks item.
+ if (isFeedType(handlerInfo.type)) {
+ let internalMenuItem = document.createElement("menuitem");
+ internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
+ let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+ [this._brandShortName]);
+ internalMenuItem.setAttribute("label", label);
+ internalMenuItem.setAttribute("tooltiptext", label);
+ internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
+ menuPopup.appendChild(internalMenuItem);
+ }
+
+ // Add a separator to distinguish these items from the helper app items
+ // that follow them.
+ let menuItem = document.createElement("menuseparator");
+ menuPopup.appendChild(menuItem);
+
+ // Create a menu item for the OS default application, if any.
+ if (handlerInfo.hasDefaultHandler) {
+ var defaultMenuItem = document.createElement("menuitem");
+ defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault);
+ let label = this._prefsBundle.getFormattedString("useDefault",
+ [handlerInfo.defaultDescription]);
+ defaultMenuItem.setAttribute("label", label);
+ defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
+ defaultMenuItem.setAttribute("image", this._getIconURLForSystemDefault(handlerInfo));
+
+ menuPopup.appendChild(defaultMenuItem);
+ }
+
+ // Create menu items for possible handlers.
+ let preferredApp = handlerInfo.preferredApplicationHandler;
+ let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate();
+ var possibleAppMenuItems = [];
+ while (possibleApps.hasMoreElements()) {
+ let possibleApp = possibleApps.getNext();
+ if (!this.isValidHandlerApp(possibleApp))
+ continue;
+
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
+ let label;
+ if (possibleApp instanceof Ci.nsILocalHandlerApp)
+ label = getFileDisplayName(possibleApp.executable);
+ else
+ label = possibleApp.name;
+ label = this._prefsBundle.getFormattedString("useApp", [label]);
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ menuItem.setAttribute("image", this._getIconURLForHandlerApp(possibleApp));
+
+ // Attach the handler app object to the menu item so we can use it
+ // to make changes to the datastore when the user selects the item.
+ menuItem.handlerApp = possibleApp;
+
+ menuPopup.appendChild(menuItem);
+ possibleAppMenuItems.push(menuItem);
+ }
+
+ // Create a menu item for the plugin.
+ if (handlerInfo.pluginName) {
+ var pluginMenuItem = document.createElement("menuitem");
+ pluginMenuItem.setAttribute("action", kActionUsePlugin);
+ let label = this._prefsBundle.getFormattedString("usePluginIn",
+ [handlerInfo.pluginName,
+ this._brandShortName]);
+ pluginMenuItem.setAttribute("label", label);
+ pluginMenuItem.setAttribute("tooltiptext", label);
+ pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
+ menuPopup.appendChild(pluginMenuItem);
+ }
+
+ // Create a menu item for selecting a local application.
+ let canOpenWithOtherApp = true;
+ if (AppConstants.platform == "win") {
+ // On Windows, selecting an application to open another application
+ // would be meaningless so we special case executables.
+ let executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
+ .getTypeFromExtension("exe");
+ canOpenWithOtherApp = handlerInfo.type != executableType;
+ }
+ if (canOpenWithOtherApp)
+ {
+ let menuItem = document.createElement("menuitem");
+ menuItem.className = "choose-app-item";
+ menuItem.addEventListener("command", function(e) {
+ gApplicationsPane.chooseApp(e);
+ });
+ let label = this._prefsBundle.getString("useOtherApp");
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Create a menu item for managing applications.
+ if (possibleAppMenuItems.length) {
+ let menuItem = document.createElement("menuseparator");
+ menuPopup.appendChild(menuItem);
+ menuItem = document.createElement("menuitem");
+ menuItem.className = "manage-app-item";
+ menuItem.addEventListener("command", function(e) {
+ gApplicationsPane.manageApp(e);
+ });
+ menuItem.setAttribute("label", this._prefsBundle.getString("manageApp"));
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Select the item corresponding to the preferred action. If the always
+ // ask flag is set, it overrides the preferred action. Otherwise we pick
+ // the item identified by the preferred action (when the preferred action
+ // is to use a helper app, we have to pick the specific helper app item).
+ if (handlerInfo.alwaysAskBeforeHandling)
+ menu.selectedItem = askMenuItem;
+ else switch (handlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.handleInternally:
+ menu.selectedItem = internalMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ menu.selectedItem = defaultMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.useHelperApp:
+ if (preferredApp)
+ menu.selectedItem =
+ possibleAppMenuItems.filter(v => v.handlerApp.equals(preferredApp))[0];
+ break;
+ case kActionUsePlugin:
+ menu.selectedItem = pluginMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.saveToDisk:
+ menu.selectedItem = saveMenuItem;
+ break;
+ }
+ },
+
+
+ // Sorting & Filtering
+
+ _sortColumn: null,
+
+ /**
+ * Sort the list when the user clicks on a column header.
+ */
+ sort: function (event) {
+ var column = event.target;
+
+ // If the user clicked on a new sort column, remove the direction indicator
+ // from the old column.
+ if (this._sortColumn && this._sortColumn != column)
+ this._sortColumn.removeAttribute("sortDirection");
+
+ this._sortColumn = column;
+
+ // Set (or switch) the sort direction indicator.
+ if (column.getAttribute("sortDirection") == "ascending")
+ column.setAttribute("sortDirection", "descending");
+ else
+ column.setAttribute("sortDirection", "ascending");
+
+ this._sortVisibleTypes();
+ this._rebuildView();
+ },
+
+ /**
+ * Sort the list of visible types by the current sort column/direction.
+ */
+ _sortVisibleTypes: function() {
+ if (!this._sortColumn)
+ return;
+
+ var t = this;
+
+ function sortByType(a, b) {
+ return t._describeType(a).toLowerCase().
+ localeCompare(t._describeType(b).toLowerCase());
+ }
+
+ function sortByAction(a, b) {
+ return t._describePreferredAction(a).toLowerCase().
+ localeCompare(t._describePreferredAction(b).toLowerCase());
+ }
+
+ switch (this._sortColumn.getAttribute("value")) {
+ case "type":
+ this._visibleTypes.sort(sortByType);
+ break;
+ case "action":
+ this._visibleTypes.sort(sortByAction);
+ break;
+ }
+
+ if (this._sortColumn.getAttribute("sortDirection") == "descending")
+ this._visibleTypes.reverse();
+ },
+
+ /**
+ * Filter the list when the user enters a filter term into the filter field.
+ */
+ filter: function() {
+ this._rebuildView();
+ },
+
+ focusFilterBox: function() {
+ this._filter.focus();
+ this._filter.select();
+ },
+
+
+ // Changes
+
+ onSelectAction: function(aActionItem) {
+ this._storingAction = true;
+
+ try {
+ this._storeAction(aActionItem);
+ }
+ finally {
+ this._storingAction = false;
+ }
+ },
+
+ _storeAction: function(aActionItem) {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ let action = parseInt(aActionItem.getAttribute("action"));
+
+ // Set the plugin state if we're enabling or disabling a plugin.
+ if (action == kActionUsePlugin)
+ handlerInfo.enablePluginType();
+ else if (handlerInfo.pluginName && !handlerInfo.isDisabledPluginType)
+ handlerInfo.disablePluginType();
+
+ // Set the preferred application handler.
+ // We leave the existing preferred app in the list when we set
+ // the preferred action to something other than useHelperApp so that
+ // legacy datastores that don't have the preferred app in the list
+ // of possible apps still include the preferred app in the list of apps
+ // the user can choose to handle the type.
+ if (action == Ci.nsIHandlerInfo.useHelperApp)
+ handlerInfo.preferredApplicationHandler = aActionItem.handlerApp;
+
+ // Set the "always ask" flag.
+ if (action == Ci.nsIHandlerInfo.alwaysAsk)
+ handlerInfo.alwaysAskBeforeHandling = true;
+ else
+ handlerInfo.alwaysAskBeforeHandling = false;
+
+ // Set the preferred action.
+ handlerInfo.preferredAction = action;
+
+ handlerInfo.store();
+
+ // Make sure the handler info object is flagged to indicate that there is
+ // now some user configuration for the type.
+ handlerInfo.handledOnlyByPlugin = false;
+
+ // Update the action label and image to reflect the new preferred action.
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ typeItem.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(handlerInfo));
+ }
+ },
+
+ manageApp: function(aEvent) {
+ // Don't let the normal "on select action" handler get this event,
+ // as we handle it specially ourselves.
+ aEvent.stopPropagation();
+
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ let onComplete = () => {
+ // Rebuild the actions menu so that we revert to the previous selection,
+ // or "Always ask" if the previous default application has been removed
+ this.rebuildActionsMenu();
+
+ // update the richlistitem too. Will be visible when selecting another row
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ typeItem.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(handlerInfo));
+ }
+ };
+
+ gSubDialog.open("chrome://browser/content/preferences/applicationManager.xul",
+ "resizable=no", handlerInfo, onComplete);
+
+ },
+
+ chooseApp: function(aEvent) {
+ // Don't let the normal "on select action" handler get this event,
+ // as we handle it specially ourselves.
+ aEvent.stopPropagation();
+
+ var handlerApp;
+ let chooseAppCallback = function(aHandlerApp) {
+ // Rebuild the actions menu whether the user picked an app or canceled.
+ // If they picked an app, we want to add the app to the menu and select it.
+ // If they canceled, we want to go back to their previous selection.
+ this.rebuildActionsMenu();
+
+ // If the user picked a new app from the menu, select it.
+ if (aHandlerApp) {
+ let typeItem = this._list.selectedItem;
+ let actionsMenu =
+ document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+ let menuItems = actionsMenu.menupopup.childNodes;
+ for (let i = 0; i < menuItems.length; i++) {
+ let menuItem = menuItems[i];
+ if (menuItem.handlerApp && menuItem.handlerApp.equals(aHandlerApp)) {
+ actionsMenu.selectedIndex = i;
+ this.onSelectAction(menuItem);
+ break;
+ }
+ }
+ }
+ }.bind(this);
+
+ if (AppConstants.platform == "win") {
+ var params = {};
+ var handlerInfo = this._handledTypes[this._list.selectedItem.type];
+
+ if (isFeedType(handlerInfo.type)) {
+ // MIME info will be null, create a temp object.
+ params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type,
+ handlerInfo.primaryExtension);
+ } else {
+ params.mimeInfo = handlerInfo.wrappedHandlerInfo;
+ }
+
+ params.title = this._prefsBundle.getString("fpTitleChooseApp");
+ params.description = handlerInfo.description;
+ params.filename = null;
+ params.handlerApp = null;
+
+ let onAppSelected = () => {
+ if (this.isValidHandlerApp(params.handlerApp)) {
+ handlerApp = params.handlerApp;
+
+ // Add the app to the type's list of possible handlers.
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+ }
+
+ chooseAppCallback(handlerApp);
+ };
+
+ gSubDialog.open("chrome://global/content/appPicker.xul",
+ null, params, onAppSelected);
+ } else {
+ let winTitle = this._prefsBundle.getString("fpTitleChooseApp");
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK && fp.file &&
+ this._isValidHandlerExecutable(fp.file)) {
+ handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.name = getFileDisplayName(fp.file);
+ handlerApp.executable = fp.file;
+
+ // Add the app to the type's list of possible handlers.
+ let handlerInfo = this._handledTypes[this._list.selectedItem.type];
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+
+ chooseAppCallback(handlerApp);
+ }
+ }.bind(this);
+
+ // Prompt the user to pick an app. If they pick one, and it's a valid
+ // selection, then add it to the list of possible handlers.
+ fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterApps);
+ fp.open(fpCallback);
+ }
+ },
+
+ // Mark which item in the list was last selected so we can reselect it
+ // when we rebuild the list or when the user returns to the prefpane.
+ onSelectionChanged: function() {
+ if (this._list.selectedItem)
+ this._list.setAttribute("lastSelectedType",
+ this._list.selectedItem.getAttribute("type"));
+ },
+
+ _setIconClassForPreferredAction: function(aHandlerInfo, aElement) {
+ // If this returns true, the attribute that CSS sniffs for was set to something
+ // so you shouldn't manually set an icon URI.
+ // This removes the existing actionIcon attribute if any, even if returning false.
+ aElement.removeAttribute("actionIcon");
+
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ return true;
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "save");
+ return true;
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ if (isFeedType(aHandlerInfo.type)) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "feed");
+ return true;
+ } else if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ return true;
+ }
+ break;
+
+ case kActionUsePlugin:
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "plugin");
+ return true;
+ }
+ aElement.removeAttribute(APP_ICON_ATTR_NAME);
+ return false;
+ },
+
+ _getIconURLForPreferredAction: function(aHandlerInfo) {
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ return this._getIconURLForSystemDefault(aHandlerInfo);
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ let preferredApp = aHandlerInfo.preferredApplicationHandler;
+ if (this.isValidHandlerApp(preferredApp))
+ return this._getIconURLForHandlerApp(preferredApp);
+ // Explicit fall-through
+
+ // This should never happen, but if preferredAction is set to some weird
+ // value, then fall back to the generic application icon.
+ default:
+ return ICON_URL_APP;
+ }
+ },
+
+ _getIconURLForHandlerApp: function(aHandlerApp) {
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ return this._getIconURLForFile(aHandlerApp.executable);
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
+ return this._getIconURLForWebApp(aHandlerApp.uriTemplate);
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
+ return this._getIconURLForWebApp(aHandlerApp.uri)
+
+ // We know nothing about other kinds of handler apps.
+ return "";
+ },
+
+ _getIconURLForFile: function(aFile) {
+ var fph = this._ioSvc.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler);
+ var urlSpec = fph.getURLSpecFromFile(aFile);
+
+ return "moz-icon://" + urlSpec + "?size=16";
+ },
+
+ _getIconURLForWebApp: function(aWebAppURITemplate) {
+ var uri = this._ioSvc.newURI(aWebAppURITemplate, null, null);
+
+ // Unfortunately we can't use the favicon service to get the favicon,
+ // because the service looks in the annotations table 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 web app's URL template,
+ // they'll only visit URLs derived from that template (i.e. with %s
+ // in the template replaced by the URL of the content being handled).
+
+ if (/^https?$/.test(uri.scheme) && this._prefSvc.getBoolPref("browser.chrome.favicons"))
+ return uri.prePath + "/favicon.ico";
+
+ return "";
+ },
+
+ _getIconURLForSystemDefault: function(aHandlerInfo) {
+ // Handler info objects for MIME types on some OSes implement a property bag
+ // interface from which we can get an icon for the default app, so if we're
+ // dealing with a MIME type on one of those OSes, then try to get the icon.
+ if ("wrappedHandlerInfo" in aHandlerInfo) {
+ let wrappedHandlerInfo = aHandlerInfo.wrappedHandlerInfo;
+
+ if (wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ wrappedHandlerInfo instanceof Ci.nsIPropertyBag) {
+ try {
+ let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL");
+ if (url)
+ return url + "?size=16";
+ }
+ catch (ex) {}
+ }
+ }
+
+ // If this isn't a MIME type object on an OS that supports retrieving
+ // the icon, or if we couldn't retrieve the icon for some other reason,
+ // then use a generic icon.
+ return ICON_URL_APP;
+ }
+
+};
diff --git a/browser/components/preferences/in-content/applications.xul b/browser/components/preferences/in-content/applications.xul
new file mode 100644
index 000000000..1d4723493
--- /dev/null
+++ b/browser/components/preferences/in-content/applications.xul
@@ -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/.
+
+<!-- Applications panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/applications.js"/>
+
+<preferences id="feedsPreferences" hidden="true" data-category="paneApplications">
+ <preference id="browser.feeds.handler"
+ name="browser.feeds.handler"
+ type="string"/>
+ <preference id="browser.feeds.handler.default"
+ name="browser.feeds.handler.default"
+ type="string"/>
+ <preference id="browser.feeds.handlers.application"
+ name="browser.feeds.handlers.application"
+ type="file"/>
+ <preference id="browser.feeds.handlers.webservice"
+ name="browser.feeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.videoFeeds.handler"
+ name="browser.videoFeeds.handler"
+ type="string"/>
+ <preference id="browser.videoFeeds.handler.default"
+ name="browser.videoFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.videoFeeds.handlers.application"
+ name="browser.videoFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.videoFeeds.handlers.webservice"
+ name="browser.videoFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.audioFeeds.handler"
+ name="browser.audioFeeds.handler"
+ type="string"/>
+ <preference id="browser.audioFeeds.handler.default"
+ name="browser.audioFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.audioFeeds.handlers.application"
+ name="browser.audioFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.audioFeeds.handlers.webservice"
+ name="browser.audioFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="pref.downloads.disable_button.edit_actions"
+ name="pref.downloads.disable_button.edit_actions"
+ type="bool"/>
+</preferences>
+
+<keyset data-category="paneApplications">
+ <!-- Ctrl+f/k focus the search box in the Applications pane.
+ These <key>s have oncommand attributes because of bug 371900. -->
+ <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
+ <key key="&focusSearch2.key;" modifiers="accel" id="focusSearch2" oncommand=";"/>
+</keyset>
+
+<hbox id="header-applications"
+ class="header"
+ hidden="true"
+ data-category="paneApplications">
+ <label class="header-name" flex="1">&paneApplications.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<vbox id="applicationsContent"
+ data-category="paneApplications"
+ hidden="true"
+ flex="1">
+ <hbox>
+ <textbox id="filter" flex="1"
+ type="search"
+ placeholder="&filter.emptytext;"
+ aria-controls="handlersView"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <richlistbox id="handlersView" orient="vertical" persist="lastSelectedType"
+ preference="pref.downloads.disable_button.edit_actions"
+ flex="1">
+ <listheader equalsize="always">
+ <treecol id="typeColumn" label="&typeColumn.label;" value="type"
+ accesskey="&typeColumn.accesskey;" persist="sortDirection"
+ flex="1" sortDirection="ascending"/>
+ <treecol id="actionColumn" label="&actionColumn2.label;" value="action"
+ accesskey="&actionColumn2.accesskey;" persist="sortDirection"
+ flex="1"/>
+ </listheader>
+ </richlistbox>
+</vbox>
diff --git a/browser/components/preferences/in-content/containers.js b/browser/components/preferences/in-content/containers.js
new file mode 100644
index 000000000..758e45fff
--- /dev/null
+++ b/browser/components/preferences/in-content/containers.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/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
+
+const containersBundle = Services.strings.createBundle("chrome://browser/locale/preferences/containers.properties");
+
+const defaultContainerIcon = "fingerprint";
+const defaultContainerColor = "blue";
+
+let gContainersPane = {
+
+ init() {
+ this._list = document.getElementById("containersView");
+
+ document.getElementById("backContainersLink").addEventListener("click", function () {
+ gotoPref("privacy");
+ });
+
+ this._rebuildView();
+ },
+
+ _rebuildView() {
+ const containers = ContextualIdentityService.getIdentities();
+ while (this._list.firstChild) {
+ this._list.firstChild.remove();
+ }
+ for (let container of containers) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("containerName", ContextualIdentityService.getUserContextLabel(container.userContextId));
+ item.setAttribute("containerIcon", container.icon);
+ item.setAttribute("containerColor", container.color);
+ item.setAttribute("userContextId", container.userContextId);
+
+ this._list.appendChild(item);
+ }
+ },
+
+ onRemoveClick(button) {
+ let userContextId = button.getAttribute("value");
+ ContextualIdentityService.remove(userContextId);
+ this._rebuildView();
+ },
+ onPeferenceClick(button) {
+ this.openPreferenceDialog(button.getAttribute("value"));
+ },
+
+ onAddButtonClick(button) {
+ this.openPreferenceDialog(null);
+ },
+
+ openPreferenceDialog(userContextId) {
+ let identity = {
+ name: "",
+ icon: defaultContainerIcon,
+ color: defaultContainerColor
+ };
+ let title;
+ if (userContextId) {
+ identity = ContextualIdentityService.getIdentityFromId(userContextId);
+ // This is required to get the translation string from defaults
+ identity.name = ContextualIdentityService.getUserContextLabel(identity.userContextId);
+ title = containersBundle.formatStringFromName("containers.updateContainerTitle", [identity.name], 1);
+ }
+
+ const params = { userContextId, identity, windowTitle: title };
+ gSubDialog.open("chrome://browser/content/preferences/containers.xul",
+ null, params);
+ }
+
+};
diff --git a/browser/components/preferences/in-content/containers.xul b/browser/components/preferences/in-content/containers.xul
new file mode 100644
index 000000000..e83bac1c3
--- /dev/null
+++ b/browser/components/preferences/in-content/containers.xul
@@ -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/.
+
+<!-- Containers panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/containers.js"/>
+
+<preferences id="containerPreferences" hidden="true" data-category="paneContainer">
+ <!-- Containers -->
+ <preference id="privacy.userContext.enabled"
+ name="privacy.userContext.enabled"
+ type="bool"/>
+
+</preferences>
+
+<hbox hidden="true"
+ class="container-header-links"
+ data-category="paneContainers">
+ <label class="text-link" id="backContainersLink" value="&backLink.label;" />
+</hbox>
+
+<hbox id="header-containers"
+ class="header"
+ hidden="true"
+ data-category="paneContainers">
+ <label class="header-name" flex="1">&paneContainers.title;</label>
+ <button class="help-button"
+ aria-label="&helpButton.label;"/>
+</hbox>
+
+<!-- Containers -->
+<groupbox id="browserContainersGroup" data-category="paneContainers" hidden="true">
+ <vbox id="browserContainersbox">
+
+ <richlistbox id="containersView" orient="vertical" persist="lastSelectedType"
+ flex="1">
+ <listheader equalsize="always">
+ <treecol id="typeColumn" label="&label.label;" value="type"
+ persist="sortDirection"
+ flex="1" sortDirection="ascending"/>
+ <treecol id="actionColumn" value="action"
+ persist="sortDirection"
+ flex="1"/>
+ </listheader>
+ </richlistbox>
+ </vbox>
+ <vbox>
+ <hbox flex="1">
+ <button onclick="gContainersPane.onAddButtonClick();" accesskey="&addButton.accesskey;" label="&addButton.label;"/>
+ </hbox>
+ </vbox>
+</groupbox>
diff --git a/browser/components/preferences/in-content/content.js b/browser/components/preferences/in-content/content.js
new file mode 100644
index 000000000..5ba334b02
--- /dev/null
+++ b/browser/components/preferences/in-content/content.js
@@ -0,0 +1,294 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function () {
+ try {
+ let alertsService = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+ // This will throw if manualDoNotDisturb isn't implemented.
+ alertsService.manualDoNotDisturb;
+ return alertsService;
+ } catch (ex) {
+ return undefined;
+ }
+});
+
+var gContentPane = {
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gContentPane));
+ }
+
+ // Initializes the fonts dropdowns displayed in this pane.
+ this._rebuildFonts();
+ var menulist = document.getElementById("defaultFont");
+ if (menulist.selectedIndex == -1) {
+ menulist.value = FontBuilder.readFontSelection(menulist);
+ }
+
+ // Show translation preferences if we may:
+ const prefName = "browser.translation.ui.show";
+ if (Services.prefs.getBoolPref(prefName)) {
+ let row = document.getElementById("translationBox");
+ row.removeAttribute("hidden");
+ // Showing attribution only for Bing Translator.
+ Components.utils.import("resource:///modules/translation/Translation.jsm");
+ if (Translation.translationEngine == "bing") {
+ document.getElementById("bingAttribution").removeAttribute("hidden");
+ }
+ }
+
+ if (AlertsServiceDND) {
+ let notificationsDoNotDisturbRow =
+ document.getElementById("notificationsDoNotDisturbRow");
+ notificationsDoNotDisturbRow.removeAttribute("hidden");
+ if (AlertsServiceDND.manualDoNotDisturb) {
+ let notificationsDoNotDisturb =
+ document.getElementById("notificationsDoNotDisturb");
+ notificationsDoNotDisturb.setAttribute("checked", true);
+ }
+ }
+
+ setEventListener("font.language.group", "change",
+ gContentPane._rebuildFonts);
+ setEventListener("notificationsPolicyButton", "command",
+ gContentPane.showNotificationExceptions);
+ setEventListener("popupPolicyButton", "command",
+ gContentPane.showPopupExceptions);
+ setEventListener("advancedFonts", "command",
+ gContentPane.configureFonts);
+ setEventListener("colors", "command",
+ gContentPane.configureColors);
+ setEventListener("chooseLanguage", "command",
+ gContentPane.showLanguages);
+ setEventListener("translationAttributionImage", "click",
+ gContentPane.openTranslationProviderAttribution);
+ setEventListener("translateButton", "command",
+ gContentPane.showTranslationExceptions);
+ setEventListener("notificationsDoNotDisturb", "command",
+ gContentPane.toggleDoNotDisturbNotifications);
+
+ let notificationInfoURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
+ document.getElementById("notificationsPolicyLearnMore").setAttribute("href",
+ notificationInfoURL);
+
+ let drmInfoURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
+ document.getElementById("playDRMContentLink").setAttribute("href", drmInfoURL);
+ let emeUIEnabled = Services.prefs.getBoolPref("browser.eme.ui.enabled");
+ // Force-disable/hide on WinXP:
+ if (navigator.platform.toLowerCase().startsWith("win")) {
+ emeUIEnabled = emeUIEnabled && parseFloat(Services.sysinfo.get("version")) >= 6;
+ }
+ if (!emeUIEnabled) {
+ // Don't want to rely on .hidden for the toplevel groupbox because
+ // of the pane hiding/showing code potentially interfering:
+ document.getElementById("drmGroup").setAttribute("style", "display: none !important");
+ }
+ },
+
+ // UTILITY FUNCTIONS
+
+ /**
+ * Utility function to enable/disable the button specified by aButtonID based
+ * on the value of the Boolean preference specified by aPreferenceID.
+ */
+ updateButtons: function (aButtonID, aPreferenceID)
+ {
+ var button = document.getElementById(aButtonID);
+ var preference = document.getElementById(aPreferenceID);
+ button.disabled = preference.value != true;
+ return undefined;
+ },
+
+ // BEGIN UI CODE
+
+ /*
+ * Preferences:
+ *
+ * dom.disable_open_during_load
+ * - true if popups are blocked by default, false otherwise
+ */
+
+ // NOTIFICATIONS
+
+ /**
+ * Displays the notifications exceptions dialog where specific site notification
+ * preferences can be set.
+ */
+ showNotificationExceptions()
+ {
+ let bundlePreferences = document.getElementById("bundlePreferences");
+ let params = { permissionType: "desktop-notification" };
+ params.windowTitle = bundlePreferences.getString("notificationspermissionstitle");
+ params.introText = bundlePreferences.getString("notificationspermissionstext4");
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ "resizable=yes", params);
+
+ try {
+ Services.telemetry
+ .getHistogramById("WEB_NOTIFICATION_EXCEPTIONS_OPENED").add();
+ } catch (e) {}
+ },
+
+
+ // POP-UPS
+
+ /**
+ * Displays the popup exceptions dialog where specific site popup preferences
+ * can be set.
+ */
+ showPopupExceptions: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible: false, sessionVisible: false, allowVisible: true,
+ prefilledHost: "", permissionType: "popup" }
+ params.windowTitle = bundlePreferences.getString("popuppermissionstitle");
+ params.introText = bundlePreferences.getString("popuppermissionstext");
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ "resizable=yes", params);
+ },
+
+ // FONTS
+
+ /**
+ * Populates the default font list in UI.
+ */
+ _rebuildFonts: function ()
+ {
+ var preferences = document.getElementById("contentPreferences");
+ // Ensure preferences are "visible" to ensure bindings work.
+ preferences.hidden = false;
+ // Force flush:
+ preferences.clientHeight;
+ var langGroupPref = document.getElementById("font.language.group");
+ this._selectDefaultLanguageGroup(langGroupPref.value,
+ this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
+ },
+
+ /**
+ *
+ */
+ _selectDefaultLanguageGroup: function (aLanguageGroup, aIsSerif)
+ {
+ const kFontNameFmtSerif = "font.name.serif.%LANG%";
+ const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%";
+ const kFontNameListFmtSerif = "font.name-list.serif.%LANG%";
+ const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
+ const kFontSizeFmtVariable = "font.size.variable.%LANG%";
+
+ var preferences = document.getElementById("contentPreferences");
+ var prefs = [{ format : aIsSerif ? kFontNameFmtSerif : kFontNameFmtSansSerif,
+ type : "fontname",
+ element : "defaultFont",
+ fonttype : aIsSerif ? "serif" : "sans-serif" },
+ { format : aIsSerif ? kFontNameListFmtSerif : kFontNameListFmtSansSerif,
+ type : "unichar",
+ element : null,
+ fonttype : aIsSerif ? "serif" : "sans-serif" },
+ { format : kFontSizeFmtVariable,
+ type : "int",
+ element : "defaultFontSize",
+ fonttype : null }];
+ for (var i = 0; i < prefs.length; ++i) {
+ var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
+ if (!preference) {
+ preference = document.createElement("preference");
+ var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
+ preference.id = name;
+ preference.setAttribute("name", name);
+ preference.setAttribute("type", prefs[i].type);
+ preferences.appendChild(preference);
+ }
+
+ if (!prefs[i].element)
+ continue;
+
+ var element = document.getElementById(prefs[i].element);
+ if (element) {
+ element.setAttribute("preference", preference.id);
+
+ if (prefs[i].fonttype)
+ FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+
+ preference.setElementValue(element);
+ }
+ }
+ },
+
+ /**
+ * Returns the type of the current default font for the language denoted by
+ * aLanguageGroup.
+ */
+ _readDefaultFontTypeForLanguage: function (aLanguageGroup)
+ {
+ const kDefaultFontType = "font.default.%LANG%";
+ var defaultFontTypePref = kDefaultFontType.replace(/%LANG%/, aLanguageGroup);
+ var preference = document.getElementById(defaultFontTypePref);
+ if (!preference) {
+ preference = document.createElement("preference");
+ preference.id = defaultFontTypePref;
+ preference.setAttribute("name", defaultFontTypePref);
+ preference.setAttribute("type", "string");
+ preference.setAttribute("onchange", "gContentPane._rebuildFonts();");
+ document.getElementById("contentPreferences").appendChild(preference);
+ }
+ return preference.value;
+ },
+
+ /**
+ * Displays the fonts dialog, where web page font names and sizes can be
+ * configured.
+ */
+ configureFonts: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/fonts.xul", "resizable=no");
+ },
+
+ /**
+ * Displays the colors dialog, where default web page/link/etc. colors can be
+ * configured.
+ */
+ configureColors: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/colors.xul", "resizable=no");
+ },
+
+ // LANGUAGES
+
+ /**
+ * Shows a dialog in which the preferred language for web content may be set.
+ */
+ showLanguages: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/languages.xul");
+ },
+
+ /**
+ * Displays the translation exceptions dialog where specific site and language
+ * translation preferences can be set.
+ */
+ showTranslationExceptions: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/translation.xul");
+ },
+
+ openTranslationProviderAttribution: function ()
+ {
+ Components.utils.import("resource:///modules/translation/Translation.jsm");
+ Translation.openProviderAttribution();
+ },
+
+ toggleDoNotDisturbNotifications: function (event)
+ {
+ AlertsServiceDND.manualDoNotDisturb = event.target.checked;
+ },
+};
diff --git a/browser/components/preferences/in-content/content.xul b/browser/components/preferences/in-content/content.xul
new file mode 100644
index 000000000..c646c16a2
--- /dev/null
+++ b/browser/components/preferences/in-content/content.xul
@@ -0,0 +1,209 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!-- Content panel -->
+
+<preferences id="contentPreferences" hidden="true" data-category="paneContent">
+
+ <!-- DRM content -->
+ <preference id="media.eme.enabled"
+ name="media.eme.enabled"
+ type="bool"/>
+
+ <!-- Popups -->
+ <preference id="dom.disable_open_during_load"
+ name="dom.disable_open_during_load"
+ type="bool"/>
+
+ <!-- Fonts -->
+ <preference id="font.language.group"
+ name="font.language.group"
+ type="wstring"/>
+
+ <!-- Languages -->
+ <preference id="browser.translation.detectLanguage"
+ name="browser.translation.detectLanguage"
+ type="bool"/>
+</preferences>
+
+<script type="application/javascript"
+ src="chrome://mozapps/content/preferences/fontbuilder.js"/>
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/content.js"/>
+
+<hbox id="header-content"
+ class="header"
+ hidden="true"
+ data-category="paneContent">
+ <label class="header-name" flex="1">&paneContent.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<groupbox id="drmGroup" data-category="paneContent" hidden="true">
+ <caption><label>&drmContent.label;</label></caption>
+ <grid id="contentGrid2">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="contentRows-2">
+ <row id="playDRMContentRow">
+ <vbox align="start">
+ <checkbox id="playDRMContent" preference="media.eme.enabled"
+ label="&playDRMContent.label;" accesskey="&playDRMContent.accesskey;"/>
+ </vbox>
+ <hbox pack="end" align="center">
+ <label id="playDRMContentLink" class="text-link" value="&playDRMContent.learnMore.label;"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<groupbox id="notificationsGroup" data-category="paneContent" hidden="true">
+ <caption><label>&notificationsPolicy.label;</label></caption>
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row id="notificationsPolicyRow" align="center">
+ <hbox align="start">
+ <label id="notificationsPolicy">&notificationsPolicyDesc3.label;</label>
+ <label id="notificationsPolicyLearnMore"
+ class="text-link"
+ value="&notificationsPolicyLearnMore.label;"/>
+ </hbox>
+ <hbox pack="end">
+ <button id="notificationsPolicyButton" label="&notificationsPolicyButton.label;"
+ accesskey="&notificationsPolicyButton.accesskey;"/>
+ </hbox>
+ </row>
+ <row id="notificationsDoNotDisturbRow" hidden="true">
+ <vbox align="start">
+ <checkbox id="notificationsDoNotDisturb" label="&notificationsDoNotDisturb.label;"
+ accesskey="&notificationsDoNotDisturb.accesskey;"/>
+ <label id="notificationsDoNotDisturbDetails"
+ class="indent"
+ value="&notificationsDoNotDisturbDetails.value;"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<groupbox id="miscGroup" data-category="paneContent" hidden="true">
+ <caption><label>&popups.label;</label></caption>
+ <grid id="contentGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="contentRows-1">
+ <row id="popupPolicyRow">
+ <vbox align="start">
+ <checkbox id="popupPolicy" preference="dom.disable_open_during_load"
+ label="&blockPopups.label;" accesskey="&blockPopups.accesskey;"
+ onsyncfrompreference="return gContentPane.updateButtons('popupPolicyButton',
+ 'dom.disable_open_during_load');"/>
+ </vbox>
+ <hbox pack="end">
+ <button id="popupPolicyButton" label="&popupExceptions.label;"
+ accesskey="&popupExceptions.accesskey;"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<!-- Fonts and Colors -->
+<groupbox id="fontsGroup" data-category="paneContent" hidden="true">
+ <caption><label>&fontsAndColors.label;</label></caption>
+
+ <grid id="fontsGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="fontsRows">
+ <row id="fontRow">
+ <hbox align="center">
+ <label control="defaultFont" accesskey="&defaultFont.accesskey;">&defaultFont.label;</label>
+ <menulist id="defaultFont" delayprefsave="true"/>
+ <label id="defaultFontSizeLabel" control="defaultFontSize" accesskey="&defaultSize.accesskey;">&defaultSize.label;</label>
+ <menulist id="defaultFontSize" delayprefsave="true">
+ <menupopup>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <button id="advancedFonts" icon="select-font"
+ label="&advancedFonts.label;"
+ accesskey="&advancedFonts.accesskey;"/>
+ </row>
+ <row id="colorsRow">
+ <hbox/>
+ <button id="colors" icon="select-color"
+ label="&colors.label;"
+ accesskey="&colors.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
+
+<!-- Languages -->
+<groupbox id="languagesGroup" data-category="paneContent" hidden="true">
+ <caption><label>&languages.label;</label></caption>
+
+ <hbox id="languagesBox" align="center">
+ <description flex="1" control="chooseLanguage">&chooseLanguage.label;</description>
+ <button id="chooseLanguage"
+ label="&chooseButton.label;"
+ accesskey="&chooseButton.accesskey;"/>
+ </hbox>
+
+ <hbox id="translationBox" hidden="true">
+ <hbox align="center" flex="1">
+ <checkbox id="translate" preference="browser.translation.detectLanguage"
+ label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
+ onsyncfrompreference="return gContentPane.updateButtons('translateButton',
+ 'browser.translation.detectLanguage');"/>
+ <hbox id="bingAttribution" hidden="true">
+ <label>&translation.options.attribution.beforeLogo;</label>
+ <separator orient="vertical" class="thin"/>
+ <image id="translationAttributionImage" aria-label="Microsoft Translator"
+ src="chrome://browser/content/microsoft-translator-attribution.png"/>
+ <separator orient="vertical" class="thin"/>
+ <label>&translation.options.attribution.afterLogo;</label>
+ </hbox>
+ </hbox>
+ <button id="translateButton" label="&translateExceptions.label;"
+ accesskey="&translateExceptions.accesskey;"/>
+ </hbox>
+</groupbox>
diff --git a/browser/components/preferences/in-content/jar.mn b/browser/components/preferences/in-content/jar.mn
new file mode 100644
index 000000000..52f536e96
--- /dev/null
+++ b/browser/components/preferences/in-content/jar.mn
@@ -0,0 +1,18 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/preferences/in-content/preferences.js
+* content/browser/preferences/in-content/preferences.xul
+ content/browser/preferences/in-content/subdialogs.js
+
+ content/browser/preferences/in-content/main.js
+ content/browser/preferences/in-content/privacy.js
+ content/browser/preferences/in-content/containers.js
+ content/browser/preferences/in-content/advanced.js
+ content/browser/preferences/in-content/applications.js
+ content/browser/preferences/in-content/content.js
+ content/browser/preferences/in-content/sync.js
+ content/browser/preferences/in-content/security.js
+ content/browser/preferences/in-content/search.js
diff --git a/browser/components/preferences/in-content/main.js b/browser/components/preferences/in-content/main.js
new file mode 100644
index 000000000..4f20ba8c3
--- /dev/null
+++ b/browser/components/preferences/in-content/main.js
@@ -0,0 +1,721 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Downloads.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+Components.utils.import("resource:///modules/ShellService.jsm");
+Components.utils.import("resource:///modules/TransientPrefs.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+if (AppConstants.E10S_TESTING_ONLY) {
+ XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+}
+
+var gMainPane = {
+ /**
+ * Initialization of this.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gMainPane));
+ }
+
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ this.updateSetDefaultBrowser();
+ if (AppConstants.platform == "win") {
+ // In Windows 8 we launch the control panel since it's the only
+ // way to get all file type association prefs. So we don't know
+ // when the user will select the default. We refresh here periodically
+ // in case the default changes. On other Windows OS's defaults can also
+ // be set while the prefs are open.
+ window.setInterval(this.updateSetDefaultBrowser.bind(this), 1000);
+ }
+ }
+
+ // set up the "use current page" label-changing listener
+ this._updateUseCurrentButton();
+ window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false);
+
+ this.updateBrowserStartupLastSession();
+
+ if (AppConstants.platform == "win") {
+ // Functionality for "Show tabs in taskbar" on Windows 7 and up.
+ try {
+ let sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ let ver = parseFloat(sysInfo.getProperty("version"));
+ let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
+ showTabsInTaskbar.hidden = ver < 6.1;
+ } catch (ex) {}
+ }
+
+ // The "closing multiple tabs" and "opening multiple tabs might slow down
+ // &brandShortName;" warnings provide options for not showing these
+ // warnings again. When the user disabled them, we provide checkboxes to
+ // re-enable the warnings.
+ if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnClose"))
+ document.getElementById("warnCloseMultiple").hidden = true;
+ if (!TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnOpen"))
+ document.getElementById("warnOpenMany").hidden = true;
+
+ setEventListener("browser.privatebrowsing.autostart", "change",
+ gMainPane.updateBrowserStartupLastSession);
+ setEventListener("browser.download.dir", "change",
+ gMainPane.displayDownloadDirPref);
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ setEventListener("setDefaultButton", "command",
+ gMainPane.setDefaultBrowser);
+ }
+ setEventListener("useCurrent", "command",
+ gMainPane.setHomePageToCurrent);
+ setEventListener("useBookmark", "command",
+ gMainPane.setHomePageToBookmark);
+ setEventListener("restoreDefaultHomePage", "command",
+ gMainPane.restoreDefaultHomePage);
+ setEventListener("chooseFolder", "command",
+ gMainPane.chooseFolder);
+
+ if (AppConstants.E10S_TESTING_ONLY) {
+ setEventListener("e10sAutoStart", "command",
+ gMainPane.enableE10SChange);
+ let e10sCheckbox = document.getElementById("e10sAutoStart");
+
+ let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+ let e10sTempPref = document.getElementById("e10sTempPref");
+ let e10sForceEnable = document.getElementById("e10sForceEnable");
+
+ let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
+
+ if (preffedOn) {
+ // The checkbox is checked if e10s is preffed on and enabled.
+ e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
+
+ // but if it's force disabled, then the checkbox is disabled.
+ e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
+ }
+ }
+
+ if (AppConstants.MOZ_DEV_EDITION) {
+ let uAppData = OS.Constants.Path.userApplicationDataDir;
+ let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
+
+ setEventListener("separateProfileMode", "command", gMainPane.separateProfileModeChange);
+ let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
+ setEventListener("getStarted", "click", gMainPane.onGetStarted);
+
+ OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
+ () => separateProfileModeCheckbox.checked = true);
+ }
+
+ // Notify observers that the UI is now ready
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(window, "main-pane-loaded", null);
+ },
+
+ enableE10SChange: function ()
+ {
+ if (AppConstants.E10S_TESTING_ONLY) {
+ let e10sCheckbox = document.getElementById("e10sAutoStart");
+ let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+ let e10sTempPref = document.getElementById("e10sTempPref");
+
+ let prefsToChange;
+ if (e10sCheckbox.checked) {
+ // Enabling e10s autostart
+ prefsToChange = [e10sPref];
+ } else {
+ // Disabling e10s autostart
+ prefsToChange = [e10sPref];
+ if (e10sTempPref.value) {
+ prefsToChange.push(e10sTempPref);
+ }
+ }
+
+ let buttonIndex = confirmRestartPrompt(e10sCheckbox.checked, 0,
+ true, false);
+ if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
+ for (let prefToChange of prefsToChange) {
+ prefToChange.value = e10sCheckbox.checked;
+ }
+
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ }
+
+ // Revert the checkbox in case we didn't quit
+ e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
+ }
+ },
+
+ separateProfileModeChange: function ()
+ {
+ if (AppConstants.MOZ_DEV_EDITION) {
+ function quitApp() {
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile);
+ }
+ function revertCheckbox(error) {
+ separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
+ if (error) {
+ Cu.reportError("Failed to toggle separate profile mode: " + error);
+ }
+ }
+ function createOrRemoveSpecialDevEditionFile(onSuccess) {
+ let uAppData = OS.Constants.Path.userApplicationDataDir;
+ let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
+
+ if (separateProfileModeCheckbox.checked) {
+ OS.File.remove(ignoreSeparateProfile).then(onSuccess, revertCheckbox);
+ } else {
+ OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(onSuccess, revertCheckbox);
+ }
+ }
+
+ let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
+ let button_index = confirmRestartPrompt(separateProfileModeCheckbox.checked,
+ 0, false, true);
+ switch (button_index) {
+ case CONFIRM_RESTART_PROMPT_CANCEL:
+ revertCheckbox();
+ return;
+ case CONFIRM_RESTART_PROMPT_RESTART_NOW:
+ const Cc = Components.classes, Ci = Components.interfaces;
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (!cancelQuit.data) {
+ createOrRemoveSpecialDevEditionFile(quitApp);
+ return;
+ }
+
+ // Revert the checkbox in case we didn't quit
+ revertCheckbox();
+ return;
+ case CONFIRM_RESTART_PROMPT_RESTART_LATER:
+ createOrRemoveSpecialDevEditionFile();
+ return;
+ }
+ }
+ },
+
+ onGetStarted: function (aEvent) {
+ if (AppConstants.MOZ_DEV_EDITION) {
+ const Cc = Components.classes, Ci = Components.interfaces;
+ let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ let win = wm.getMostRecentWindow("navigator:browser");
+
+ if (win) {
+ let accountsTab = win.gBrowser.addTab("about:accounts?action=signin&entrypoint=dev-edition-setup");
+ win.gBrowser.selectedTab = accountsTab;
+ }
+ }
+ },
+
+ // HOME PAGE
+
+ /*
+ * Preferences:
+ *
+ * browser.startup.homepage
+ * - the user's home page, as a string; if the home page is a set of tabs,
+ * this will be those URLs separated by the pipe character "|"
+ * browser.startup.page
+ * - what page(s) to show when the user starts the application, as an integer:
+ *
+ * 0: a blank page
+ * 1: the home page (as set by the browser.startup.homepage pref)
+ * 2: the last page the user visited (DEPRECATED)
+ * 3: windows and tabs from the last session (a.k.a. session restore)
+ *
+ * The deprecated option is not exposed in UI; however, if the user has it
+ * selected and doesn't change the UI for this preference, the deprecated
+ * option is preserved.
+ */
+
+ syncFromHomePref: function ()
+ {
+ let homePref = document.getElementById("browser.startup.homepage");
+
+ // If the pref is set to about:home or about:newtab, set the value to ""
+ // to show the placeholder text (about:home title) rather than
+ // exposing those URLs to users.
+ let defaultBranch = Services.prefs.getDefaultBranch("");
+ let defaultValue = defaultBranch.getComplexValue("browser.startup.homepage",
+ Ci.nsIPrefLocalizedString).data;
+ let currentValue = homePref.value.toLowerCase();
+ if (currentValue == "about:home" ||
+ (currentValue == defaultValue && currentValue == "about:newtab")) {
+ return "";
+ }
+
+ // If the pref is actually "", show about:blank. The actual home page
+ // loading code treats them the same, and we don't want the placeholder text
+ // to be shown.
+ if (homePref.value == "")
+ return "about:blank";
+
+ // Otherwise, show the actual pref value.
+ return undefined;
+ },
+
+ syncToHomePref: function (value)
+ {
+ // If the value is "", use about:home.
+ if (value == "")
+ return "about:home";
+
+ // Otherwise, use the actual textbox value.
+ return undefined;
+ },
+
+ /**
+ * Sets the home page to the current displayed page (or frontmost tab, if the
+ * most recent browser window contains multiple tabs), updating preference
+ * window UI to reflect this.
+ */
+ setHomePageToCurrent: function ()
+ {
+ let homePage = document.getElementById("browser.startup.homepage");
+ let tabs = this._getTabsForHomePage();
+ function getTabURI(t) {
+ return t.linkedBrowser.currentURI.spec;
+ }
+
+ // FIXME Bug 244192: using dangerous "|" joiner!
+ if (tabs.length)
+ homePage.value = tabs.map(getTabURI).join("|");
+ },
+
+ /**
+ * Displays a dialog in which the user can select a bookmark to use as home
+ * page. If the user selects a bookmark, that bookmark's name is displayed in
+ * UI and the bookmark's address is stored to the home page preference.
+ */
+ setHomePageToBookmark: function ()
+ {
+ var rv = { urls: null, names: null };
+ gSubDialog.open("chrome://browser/content/preferences/selectBookmark.xul",
+ "resizable=yes, modal=yes", rv,
+ this._setHomePageToBookmarkClosed.bind(this, rv));
+ },
+
+ _setHomePageToBookmarkClosed: function(rv, aEvent) {
+ if (aEvent.detail.button != "accept")
+ return;
+ if (rv.urls && rv.names) {
+ var homePage = document.getElementById("browser.startup.homepage");
+
+ // XXX still using dangerous "|" joiner!
+ homePage.value = rv.urls.join("|");
+ }
+ },
+
+ /**
+ * Switches the "Use Current Page" button between its singular and plural
+ * forms.
+ */
+ _updateUseCurrentButton: function () {
+ let useCurrent = document.getElementById("useCurrent");
+
+
+ let tabs = this._getTabsForHomePage();
+
+ if (tabs.length > 1)
+ useCurrent.label = useCurrent.getAttribute("label2");
+ else
+ useCurrent.label = useCurrent.getAttribute("label1");
+
+ // In this case, the button's disabled state is set by preferences.xml.
+ let prefName = "pref.browser.homepage.disable_button.current_page";
+ if (document.getElementById(prefName).locked)
+ return;
+
+ useCurrent.disabled = !tabs.length
+ },
+
+ _getTabsForHomePage: function ()
+ {
+ var win;
+ var tabs = [];
+
+ const Cc = Components.classes, Ci = Components.interfaces;
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ win = wm.getMostRecentWindow("navigator:browser");
+
+ if (win && win.document.documentElement
+ .getAttribute("windowtype") == "navigator:browser") {
+ // We should only include visible & non-pinned tabs
+
+ tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs);
+ tabs = tabs.filter(this.isNotAboutPreferences);
+ }
+
+ return tabs;
+ },
+
+ /**
+ * Check to see if a tab is not about:preferences
+ */
+ isNotAboutPreferences: function (aElement, aIndex, aArray)
+ {
+ return !aElement.linkedBrowser.currentURI.spec.startsWith("about:preferences");
+ },
+
+ /**
+ * Restores the default home page as the user's home page.
+ */
+ restoreDefaultHomePage: function ()
+ {
+ var homePage = document.getElementById("browser.startup.homepage");
+ homePage.value = homePage.defaultValue;
+ },
+
+ // DOWNLOADS
+
+ /*
+ * Preferences:
+ *
+ * browser.download.useDownloadDir - bool
+ * True - Save files directly to the folder configured via the
+ * browser.download.folderList preference.
+ * False - Always ask the user where to save a file and default to
+ * browser.download.lastDir when displaying a folder picker dialog.
+ * browser.download.dir - local file handle
+ * A local folder the user may have selected for downloaded files to be
+ * saved. Migration of other browser settings may also set this path.
+ * This folder is enabled when folderList equals 2.
+ * browser.download.lastDir - local file handle
+ * May contain the last folder path accessed when the user browsed
+ * via the file save-as dialog. (see contentAreaUtils.js)
+ * browser.download.folderList - int
+ * Indicates the location users wish to save downloaded files too.
+ * It is also used to display special file labels when the default
+ * download location is either the Desktop or the Downloads folder.
+ * Values:
+ * 0 - The desktop is the default download location.
+ * 1 - The system's downloads folder is the default download location.
+ * 2 - The default download location is elsewhere as specified in
+ * browser.download.dir.
+ * browser.download.downloadDir
+ * deprecated.
+ * browser.download.defaultFolder
+ * deprecated.
+ */
+
+ /**
+ * Enables/disables the folder field and Browse button based on whether a
+ * default download directory is being used.
+ */
+ readUseDownloadDir: function ()
+ {
+ var downloadFolder = document.getElementById("downloadFolder");
+ var chooseFolder = document.getElementById("chooseFolder");
+ var preference = document.getElementById("browser.download.useDownloadDir");
+ downloadFolder.disabled = !preference.value || preference.locked;
+ chooseFolder.disabled = !preference.value || preference.locked;
+
+ // don't override the preference's value in UI
+ return undefined;
+ },
+
+ /**
+ * Displays a file picker in which the user can choose the location where
+ * downloads are automatically saved, updating preferences and UI in
+ * response to the choice, if one is made.
+ */
+ chooseFolder()
+ {
+ return this.chooseFolderTask().catch(Components.utils.reportError);
+ },
+ chooseFolderTask: Task.async(function* ()
+ {
+ let bundlePreferences = document.getElementById("bundlePreferences");
+ let title = bundlePreferences.getString("chooseDownloadFolderTitle");
+ let folderListPref = document.getElementById("browser.download.folderList");
+ let currentDirPref = yield this._indexToFolder(folderListPref.value);
+ let defDownloads = yield this._indexToFolder(1);
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(Components.interfaces.nsIFilePicker);
+
+ fp.init(window, title, Components.interfaces.nsIFilePicker.modeGetFolder);
+ fp.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
+ // First try to open what's currently configured
+ if (currentDirPref && currentDirPref.exists()) {
+ fp.displayDirectory = currentDirPref;
+ } // Try the system's download dir
+ else if (defDownloads && defDownloads.exists()) {
+ fp.displayDirectory = defDownloads;
+ } // Fall back to Desktop
+ else {
+ fp.displayDirectory = yield this._indexToFolder(0);
+ }
+
+ let result = yield new Promise(resolve => fp.open(resolve));
+ if (result != Components.interfaces.nsIFilePicker.returnOK) {
+ return;
+ }
+
+ let downloadDirPref = document.getElementById("browser.download.dir");
+ downloadDirPref.value = fp.file;
+ folderListPref.value = yield this._folderToIndex(fp.file);
+ // Note, the real prefs will not be updated yet, so dnld manager's
+ // userDownloadsDirectory may not return the right folder after
+ // this code executes. displayDownloadDirPref will be called on
+ // the assignment above to update the UI.
+ }),
+
+ /**
+ * Initializes the download folder display settings based on the user's
+ * preferences.
+ */
+ displayDownloadDirPref()
+ {
+ this.displayDownloadDirPrefTask().catch(Components.utils.reportError);
+
+ // don't override the preference's value in UI
+ return undefined;
+ },
+
+ displayDownloadDirPrefTask: Task.async(function* ()
+ {
+ var folderListPref = document.getElementById("browser.download.folderList");
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var downloadFolder = document.getElementById("downloadFolder");
+ var currentDirPref = document.getElementById("browser.download.dir");
+
+ // Used in defining the correct path to the folder icon.
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var fph = ios.getProtocolHandler("file")
+ .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+ var iconUrlSpec;
+
+ // Display a 'pretty' label or the path in the UI.
+ if (folderListPref.value == 2) {
+ // Custom path selected and is configured
+ downloadFolder.label = this._getDisplayNameOfFile(currentDirPref.value);
+ iconUrlSpec = fph.getURLSpecFromFile(currentDirPref.value);
+ } else if (folderListPref.value == 1) {
+ // 'Downloads'
+ // In 1.5, this pointed to a folder we created called 'My Downloads'
+ // and was available as an option in the 1.5 drop down. On XP this
+ // was in My Documents, on OSX it was in User Docs. In 2.0, we did
+ // away with the drop down option, although the special label was
+ // still supported for the folder if it existed. Because it was
+ // not exposed it was rarely used.
+ // With 3.0, a new desktop folder - 'Downloads' was introduced for
+ // platforms and versions that don't support a default system downloads
+ // folder. See nsDownloadManager for details.
+ downloadFolder.label = bundlePreferences.getString("downloadsFolderName");
+ iconUrlSpec = fph.getURLSpecFromFile(yield this._indexToFolder(1));
+ } else {
+ // 'Desktop'
+ downloadFolder.label = bundlePreferences.getString("desktopFolderName");
+ iconUrlSpec = fph.getURLSpecFromFile(yield this._getDownloadsFolder("Desktop"));
+ }
+ downloadFolder.image = "moz-icon://" + iconUrlSpec + "?size=16";
+ }),
+
+ /**
+ * Returns the textual path of a folder in readable form.
+ */
+ _getDisplayNameOfFile: function (aFolder)
+ {
+ // TODO: would like to add support for 'Downloads on Macintosh HD'
+ // for OS X users.
+ return aFolder ? aFolder.path : "";
+ },
+
+ /**
+ * Returns the Downloads folder. If aFolder is "Desktop", then the Downloads
+ * folder returned is the desktop folder; otherwise, it is a folder whose name
+ * indicates that it is a download folder and whose path is as determined by
+ * the XPCOM directory service via the download manager's attribute
+ * defaultDownloadsDirectory.
+ *
+ * @throws if aFolder is not "Desktop" or "Downloads"
+ */
+ _getDownloadsFolder: Task.async(function* (aFolder)
+ {
+ switch (aFolder) {
+ case "Desktop":
+ var fileLoc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ return fileLoc.get("Desk", Components.interfaces.nsILocalFile);
+ case "Downloads":
+ let downloadsDir = yield Downloads.getSystemDownloadsDirectory();
+ return new FileUtils.File(downloadsDir);
+ }
+ throw "ASSERTION FAILED: folder type should be 'Desktop' or 'Downloads'";
+ }),
+
+ /**
+ * Determines the type of the given folder.
+ *
+ * @param aFolder
+ * the folder whose type is to be determined
+ * @returns integer
+ * 0 if aFolder is the Desktop or is unspecified,
+ * 1 if aFolder is the Downloads folder,
+ * 2 otherwise
+ */
+ _folderToIndex: Task.async(function* (aFolder)
+ {
+ if (!aFolder || aFolder.equals(yield this._getDownloadsFolder("Desktop")))
+ return 0;
+ else if (aFolder.equals(yield this._getDownloadsFolder("Downloads")))
+ return 1;
+ return 2;
+ }),
+
+ /**
+ * Converts an integer into the corresponding folder.
+ *
+ * @param aIndex
+ * an integer
+ * @returns the Desktop folder if aIndex == 0,
+ * the Downloads folder if aIndex == 1,
+ * the folder stored in browser.download.dir
+ */
+ _indexToFolder: Task.async(function* (aIndex)
+ {
+ switch (aIndex) {
+ case 0:
+ return yield this._getDownloadsFolder("Desktop");
+ case 1:
+ return yield this._getDownloadsFolder("Downloads");
+ }
+ var currentDirPref = document.getElementById("browser.download.dir");
+ return currentDirPref.value;
+ }),
+
+ /**
+ * Hide/show the "Show my windows and tabs from last time" option based
+ * on the value of the browser.privatebrowsing.autostart pref.
+ */
+ updateBrowserStartupLastSession: function()
+ {
+ let pbAutoStartPref = document.getElementById("browser.privatebrowsing.autostart");
+ let startupPref = document.getElementById("browser.startup.page");
+ let menu = document.getElementById("browserStartupPage");
+ let option = document.getElementById("browserStartupLastSession");
+ if (pbAutoStartPref.value) {
+ option.setAttribute("disabled", "true");
+ if (option.selected) {
+ menu.selectedItem = document.getElementById("browserStartupHomePage");
+ }
+ } else {
+ option.removeAttribute("disabled");
+ startupPref.updateElements(); // select the correct index in the startup menulist
+ }
+ },
+
+ // TABS
+
+ /*
+ * Preferences:
+ *
+ * browser.link.open_newwindow - int
+ * Determines where links targeting new windows should open.
+ * Values:
+ * 1 - Open in the current window or tab.
+ * 2 - Open in a new window.
+ * 3 - Open in a new tab in the most recent window.
+ * browser.tabs.loadInBackground - bool
+ * True - Whether browser should switch to a new tab opened from a link.
+ * browser.tabs.warnOnClose - bool
+ * True - If when closing a window with multiple tabs the user is warned and
+ * allowed to cancel the action, false to just close the window.
+ * browser.tabs.warnOnOpen - bool
+ * True - Whether the user should be warned when trying to open a lot of
+ * tabs at once (e.g. a large folder of bookmarks), allowing to
+ * cancel the action.
+ * browser.taskbar.previews.enable - bool
+ * True - Tabs are to be shown in Windows 7 taskbar.
+ * False - Only the window is to be shown in Windows 7 taskbar.
+ */
+
+ /**
+ * Determines where a link which opens a new window will open.
+ *
+ * @returns |true| if such links should be opened in new tabs
+ */
+ readLinkTarget: function() {
+ var openNewWindow = document.getElementById("browser.link.open_newwindow");
+ return openNewWindow.value != 2;
+ },
+
+ /**
+ * Determines where a link which opens a new window will open.
+ *
+ * @returns 2 if such links should be opened in new windows,
+ * 3 if such links should be opened in new tabs
+ */
+ writeLinkTarget: function() {
+ var linkTargeting = document.getElementById("linkTargeting");
+ return linkTargeting.checked ? 3 : 2;
+ },
+ /*
+ * Preferences:
+ *
+ * browser.shell.checkDefault
+ * - true if a default-browser check (and prompt to make it so if necessary)
+ * occurs at startup, false otherwise
+ */
+
+ /**
+ * Show button for setting browser as default browser or information that
+ * browser is already the default browser.
+ */
+ updateSetDefaultBrowser: function()
+ {
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ let shellSvc = getShellService();
+ let defaultBrowserBox = document.getElementById("defaultBrowserBox");
+ if (!shellSvc) {
+ defaultBrowserBox.hidden = true;
+ return;
+ }
+ let setDefaultPane = document.getElementById("setDefaultPane");
+ let isDefault = shellSvc.isDefaultBrowser(false, true);
+ setDefaultPane.selectedIndex = isDefault ? 1 : 0;
+ let alwaysCheck = document.getElementById("alwaysCheckDefault");
+ alwaysCheck.disabled = alwaysCheck.disabled ||
+ isDefault && alwaysCheck.checked;
+ }
+ },
+
+ /**
+ * Set browser as the operating system default browser.
+ */
+ setDefaultBrowser: function()
+ {
+ if (AppConstants.HAVE_SHELL_SERVICE) {
+ let alwaysCheckPref = document.getElementById("browser.shell.checkDefaultBrowser");
+ alwaysCheckPref.value = true;
+
+ let shellSvc = getShellService();
+ if (!shellSvc)
+ return;
+ try {
+ shellSvc.setDefaultBrowser(true, false);
+ } catch (ex) {
+ Cu.reportError(ex);
+ return;
+ }
+
+ let selectedIndex = shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
+ document.getElementById("setDefaultPane").selectedIndex = selectedIndex;
+ }
+ },
+};
diff --git a/browser/components/preferences/in-content/main.xul b/browser/components/preferences/in-content/main.xul
new file mode 100644
index 000000000..526bbc714
--- /dev/null
+++ b/browser/components/preferences/in-content/main.xul
@@ -0,0 +1,301 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/main.js"/>
+
+<preferences id="mainPreferences" hidden="true" data-category="paneGeneral">
+
+#ifdef E10S_TESTING_ONLY
+ <preference id="browser.tabs.remote.autostart"
+ name="browser.tabs.remote.autostart"
+ type="bool"/>
+ <preference id="e10sTempPref"
+ name="browser.tabs.remote.autostart.2"
+ type="bool"/>
+ <preference id="e10sForceEnable"
+ name="browser.tabs.remote.force-enable"
+ type="bool"/>
+#endif
+
+ <!-- Startup -->
+ <preference id="browser.startup.page"
+ name="browser.startup.page"
+ type="int"/>
+ <preference id="browser.startup.homepage"
+ name="browser.startup.homepage"
+ type="wstring"/>
+
+#ifdef HAVE_SHELL_SERVICE
+ <preference id="browser.shell.checkDefaultBrowser"
+ name="browser.shell.checkDefaultBrowser"
+ type="bool"/>
+
+ <preference id="pref.general.disable_button.default_browser"
+ name="pref.general.disable_button.default_browser"
+ type="bool"/>
+#endif
+
+ <preference id="pref.browser.homepage.disable_button.current_page"
+ name="pref.browser.homepage.disable_button.current_page"
+ type="bool"/>
+ <preference id="pref.browser.homepage.disable_button.bookmark_page"
+ name="pref.browser.homepage.disable_button.bookmark_page"
+ type="bool"/>
+ <preference id="pref.browser.homepage.disable_button.restore_default"
+ name="pref.browser.homepage.disable_button.restore_default"
+ type="bool"/>
+
+ <preference id="browser.privatebrowsing.autostart"
+ name="browser.privatebrowsing.autostart"
+ type="bool"/>
+
+ <!-- Downloads -->
+ <preference id="browser.download.useDownloadDir"
+ name="browser.download.useDownloadDir"
+ type="bool"/>
+
+ <preference id="browser.download.folderList"
+ name="browser.download.folderList"
+ type="int"/>
+ <preference id="browser.download.dir"
+ name="browser.download.dir"
+ type="file"/>
+ <!-- Tab preferences
+ Preferences:
+
+ browser.link.open_newwindow
+ 1 opens such links in the most recent window or tab,
+ 2 opens such links in a new window,
+ 3 opens such links in a new tab
+ browser.tabs.loadInBackground
+ - true if display should switch to a new tab which has been opened from a
+ link, false if display shouldn't switch
+ browser.tabs.warnOnClose
+ - true if when closing a window with multiple tabs the user is warned and
+ allowed to cancel the action, false to just close the window
+ browser.tabs.warnOnOpen
+ - true if the user should be warned if he attempts to open a lot of tabs at
+ once (e.g. a large folder of bookmarks), false otherwise
+ browser.taskbar.previews.enable
+ - true if tabs are to be shown in the Windows 7 taskbar
+ -->
+
+ <preference id="browser.link.open_newwindow"
+ name="browser.link.open_newwindow"
+ type="int"/>
+ <preference id="browser.tabs.loadInBackground"
+ name="browser.tabs.loadInBackground"
+ type="bool"
+ inverted="true"/>
+ <preference id="browser.tabs.warnOnClose"
+ name="browser.tabs.warnOnClose"
+ type="bool"/>
+ <preference id="browser.tabs.warnOnOpen"
+ name="browser.tabs.warnOnOpen"
+ type="bool"/>
+ <preference id="browser.sessionstore.restore_on_demand"
+ name="browser.sessionstore.restore_on_demand"
+ type="bool"/>
+#ifdef XP_WIN
+ <preference id="browser.taskbar.previews.enable"
+ name="browser.taskbar.previews.enable"
+ type="bool"/>
+#endif
+ <preference id="browser.ctrlTab.previews"
+ name="browser.ctrlTab.previews"
+ type="bool"/>
+</preferences>
+
+<hbox id="header-general"
+ class="header"
+ hidden="true"
+ data-category="paneGeneral">
+ <label class="header-name" flex="1">&paneGeneral.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<!-- Startup -->
+<groupbox id="startupGroup"
+ data-category="paneGeneral"
+ hidden="true">
+ <caption><label>&startup.label;</label></caption>
+
+#ifdef MOZ_DEV_EDITION
+ <vbox id="separateProfileBox">
+ <checkbox id="separateProfileMode"
+ label="&separateProfileMode.label;"/>
+ <hbox align="center" class="indent">
+ <label id="useFirefoxSync">&useFirefoxSync.label;</label>
+ <label id="getStarted" class="text-link">&getStarted.label;</label>
+ </hbox>
+ </vbox>
+#endif
+
+#ifdef E10S_TESTING_ONLY
+ <checkbox id="e10sAutoStart"
+ label="&e10sEnabled.label;"/>
+#endif
+
+#ifdef HAVE_SHELL_SERVICE
+ <vbox id="defaultBrowserBox">
+ <hbox align="center">
+ <checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
+ label="&alwaysCheckDefault2.label;" accesskey="&alwaysCheckDefault2.accesskey;"/>
+ </hbox>
+ <deck id="setDefaultPane">
+ <hbox align="center" class="indent">
+ <label id="isNotDefaultLabel" flex="1">&isNotDefault.label;</label>
+ <button id="setDefaultButton"
+ label="&setAsMyDefaultBrowser2.label;" accesskey="&setAsMyDefaultBrowser2.accesskey;"
+ preference="pref.general.disable_button.default_browser"/>
+ </hbox>
+ <hbox align="center" class="indent">
+ <label id="isDefaultLabel" flex="1">&isDefault.label;</label>
+ </hbox>
+ </deck>
+ <separator class="thin"/>
+ </vbox>
+#endif
+
+ <html:table id="startupTable">
+ <html:tr>
+ <html:td class="label-cell">
+ <label accesskey="&startupPage.accesskey;"
+ control="browserStartupPage">&startupPage.label;</label>
+ </html:td>
+ <html:td class="content-cell">
+ <menulist id="browserStartupPage"
+ class="content-cell-item"
+ preference="browser.startup.page">
+ <menupopup>
+ <menuitem label="&startupHomePage.label;"
+ value="1"
+ id="browserStartupHomePage"/>
+ <menuitem label="&startupBlankPage.label;"
+ value="0"
+ id="browserStartupBlank"/>
+ <menuitem label="&startupLastSession.label;"
+ value="3"
+ id="browserStartupLastSession"/>
+ </menupopup>
+ </menulist>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:td class="label-cell">
+ <label accesskey="&homepage.accesskey;"
+ control="browserHomePage">&homepage.label;</label>
+ </html:td>
+ <html:td class="content-cell">
+ <textbox id="browserHomePage"
+ class="padded uri-element content-cell-item"
+ type="autocomplete"
+ autocompletesearch="unifiedcomplete"
+ onsyncfrompreference="return gMainPane.syncFromHomePref();"
+ onsynctopreference="return gMainPane.syncToHomePref(this.value);"
+ placeholder="&abouthome.pageTitle;"
+ preference="browser.startup.homepage"/>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:td class="label-cell" />
+ <html:td class="content-cell homepage-buttons">
+ <button id="useCurrent"
+ class="content-cell-item"
+ label=""
+ accesskey="&useCurrentPage.accesskey;"
+ label1="&useCurrentPage.label;"
+ label2="&useMultiple.label;"
+ preference="pref.browser.homepage.disable_button.current_page"/>
+ <button id="useBookmark"
+ class="content-cell-item"
+ label="&chooseBookmark.label;"
+ accesskey="&chooseBookmark.accesskey;"
+ preference="pref.browser.homepage.disable_button.bookmark_page"/>
+ <button id="restoreDefaultHomePage"
+ class="content-cell-item"
+ label="&restoreDefault.label;"
+ accesskey="&restoreDefault.accesskey;"
+ preference="pref.browser.homepage.disable_button.restore_default"/>
+ </html:td>
+ </html:tr>
+ </html:table>
+</groupbox>
+
+<!-- Downloads -->
+<groupbox id="downloadsGroup"
+ data-category="paneGeneral"
+ hidden="true">
+ <caption><label>&downloads.label;</label></caption>
+
+ <radiogroup id="saveWhere"
+ preference="browser.download.useDownloadDir"
+ onsyncfrompreference="return gMainPane.readUseDownloadDir();">
+ <hbox id="saveToRow">
+ <radio id="saveTo"
+ value="true"
+ label="&saveTo.label;"
+ accesskey="&saveTo.accesskey;"
+ aria-labelledby="saveTo downloadFolder"/>
+ <filefield id="downloadFolder"
+ flex="1"
+ preference="browser.download.folderList"
+ preference-editable="true"
+ aria-labelledby="saveTo"
+ onsyncfrompreference="return gMainPane.displayDownloadDirPref();"/>
+ <button id="chooseFolder"
+#ifdef XP_MACOSX
+ accesskey="&chooseFolderMac.accesskey;"
+ label="&chooseFolderMac.label;"
+#else
+ accesskey="&chooseFolderWin.accesskey;"
+ label="&chooseFolderWin.label;"
+#endif
+ />
+ </hbox>
+ <hbox>
+ <radio id="alwaysAsk"
+ value="false"
+ label="&alwaysAsk.label;"
+ accesskey="&alwaysAsk.accesskey;"/>
+ </hbox>
+ </radiogroup>
+</groupbox>
+
+<!-- Tab preferences -->
+<groupbox data-category="paneGeneral"
+ hidden="true" align="start">
+ <caption><label>&tabsGroup.label;</label></caption>
+
+ <checkbox id="ctrlTabRecentlyUsedOrder" label="&ctrlTabRecentlyUsedOrder.label;"
+ accesskey="&ctrlTabRecentlyUsedOrder.accesskey;"
+ preference="browser.ctrlTab.previews"/>
+
+ <checkbox id="linkTargeting" label="&newWindowsAsTabs.label;"
+ accesskey="&newWindowsAsTabs.accesskey;"
+ preference="browser.link.open_newwindow"
+ onsyncfrompreference="return gMainPane.readLinkTarget();"
+ onsynctopreference="return gMainPane.writeLinkTarget();"/>
+
+ <checkbox id="warnCloseMultiple" label="&warnCloseMultipleTabs.label;"
+ accesskey="&warnCloseMultipleTabs.accesskey;"
+ preference="browser.tabs.warnOnClose"/>
+
+ <checkbox id="warnOpenMany" label="&warnOpenManyTabs.label;"
+ accesskey="&warnOpenManyTabs.accesskey;"
+ preference="browser.tabs.warnOnOpen"/>
+
+ <checkbox id="switchToNewTabs" label="&switchToNewTabs.label;"
+ accesskey="&switchToNewTabs.accesskey;"
+ preference="browser.tabs.loadInBackground"/>
+
+#ifdef XP_WIN
+ <checkbox id="showTabsInTaskbar" label="&showTabsInTaskbar.label;"
+ accesskey="&showTabsInTaskbar.accesskey;"
+ preference="browser.taskbar.previews.enable"/>
+#endif
+</groupbox>
diff --git a/browser/components/preferences/in-content/moz.build b/browser/components/preferences/in-content/moz.build
new file mode 100644
index 000000000..08a75bcf7
--- /dev/null
+++ b/browser/components/preferences/in-content/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
+ DEFINES[var] = CONFIG[var]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
+ DEFINES['HAVE_SHELL_SERVICE'] = 1
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/components/preferences/in-content/preferences.js b/browser/components/preferences/in-content/preferences.js
new file mode 100644
index 000000000..e18ab4b04
--- /dev/null
+++ b/browser/components/preferences/in-content/preferences.js
@@ -0,0 +1,315 @@
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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 globals from the files imported by the .xul files.
+/* import-globals-from subdialogs.js */
+/* import-globals-from advanced.js */
+/* import-globals-from main.js */
+/* import-globals-from search.js */
+/* import-globals-from content.js */
+/* import-globals-from privacy.js */
+/* import-globals-from applications.js */
+/* import-globals-from security.js */
+/* import-globals-from sync.js */
+/* import-globals-from ../../../base/content/utilityOverlay.js */
+
+"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/AppConstants.jsm");
+
+var gLastHash = "";
+
+var gCategoryInits = new Map();
+function init_category_if_required(category) {
+ let categoryInfo = gCategoryInits.get(category);
+ if (!categoryInfo) {
+ throw "Unknown in-content prefs category! Can't init " + category;
+ }
+ if (categoryInfo.inited) {
+ return;
+ }
+ categoryInfo.init();
+}
+
+function register_module(categoryName, categoryObject) {
+ gCategoryInits.set(categoryName, {
+ inited: false,
+ init: function() {
+ categoryObject.init();
+ this.inited = true;
+ }
+ });
+}
+
+addEventListener("DOMContentLoaded", function onLoad() {
+ removeEventListener("DOMContentLoaded", onLoad);
+ init_all();
+});
+
+function init_all() {
+ document.documentElement.instantApply = true;
+
+ gSubDialog.init();
+ register_module("paneGeneral", gMainPane);
+ register_module("paneSearch", gSearchPane);
+ register_module("panePrivacy", gPrivacyPane);
+ register_module("paneContainers", gContainersPane);
+ register_module("paneAdvanced", gAdvancedPane);
+ register_module("paneApplications", gApplicationsPane);
+ register_module("paneContent", gContentPane);
+ register_module("paneSync", gSyncPane);
+ register_module("paneSecurity", gSecurityPane);
+
+ let categories = document.getElementById("categories");
+ categories.addEventListener("select", event => gotoPref(event.target.value));
+
+ document.documentElement.addEventListener("keydown", function(event) {
+ if (event.keyCode == KeyEvent.DOM_VK_TAB) {
+ categories.setAttribute("keyboard-navigation", "true");
+ }
+ });
+ categories.addEventListener("mousedown", function() {
+ this.removeAttribute("keyboard-navigation");
+ });
+
+ window.addEventListener("hashchange", onHashChange);
+ gotoPref();
+
+ init_dynamic_padding();
+
+ var initFinished = new CustomEvent("Initialized", {
+ 'bubbles': true,
+ 'cancelable': true
+ });
+ document.dispatchEvent(initFinished);
+
+ categories = categories.querySelectorAll("richlistitem.category");
+ for (let category of categories) {
+ let name = internalPrefCategoryNameToFriendlyName(category.value);
+ let helpSelector = `#header-${name} > .help-button`;
+ let helpButton = document.querySelector(helpSelector);
+ helpButton.setAttribute("href", getHelpLinkURL(category.getAttribute("helpTopic")));
+ }
+
+ // Wait until initialization of all preferences are complete before
+ // notifying observers that the UI is now ready.
+ Services.obs.notifyObservers(window, "advanced-pane-loaded", null);
+}
+
+// Make the space above the categories list shrink on low window heights
+function init_dynamic_padding() {
+ let categories = document.getElementById("categories");
+ let catPadding = Number.parseInt(getComputedStyle(categories)
+ .getPropertyValue('padding-top'));
+ let fullHeight = categories.lastElementChild.getBoundingClientRect().bottom;
+ let mediaRule = `
+ @media (max-height: ${fullHeight}px) {
+ #categories {
+ padding-top: calc(100vh - ${fullHeight - catPadding}px);
+ }
+ }
+ `;
+ let mediaStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'html:style');
+ mediaStyle.setAttribute('type', 'text/css');
+ mediaStyle.appendChild(document.createCDATASection(mediaRule));
+ document.documentElement.appendChild(mediaStyle);
+}
+
+function telemetryBucketForCategory(category) {
+ switch (category) {
+ case "general":
+ case "search":
+ case "content":
+ case "applications":
+ case "privacy":
+ case "security":
+ case "sync":
+ return category;
+ case "advanced":
+ let advancedPaneTabs = document.getElementById("advancedPrefs");
+ switch (advancedPaneTabs.selectedTab.id) {
+ case "generalTab":
+ return "advancedGeneral";
+ case "dataChoicesTab":
+ return "advancedDataChoices";
+ case "networkTab":
+ return "advancedNetwork";
+ case "updateTab":
+ return "advancedUpdates";
+ case "encryptionTab":
+ return "advancedCerts";
+ }
+ // fall-through for unknown.
+ default:
+ return "unknown";
+ }
+}
+
+function onHashChange() {
+ gotoPref();
+}
+
+function gotoPref(aCategory) {
+ let categories = document.getElementById("categories");
+ const kDefaultCategoryInternalName = categories.firstElementChild.value;
+ let hash = document.location.hash;
+ let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
+ category = friendlyPrefCategoryNameToInternalName(category);
+
+ // Updating the hash (below) or changing the selected category
+ // will re-enter gotoPref.
+ if (gLastHash == category)
+ return;
+ let item = categories.querySelector(".category[value=" + category + "]");
+ if (!item) {
+ category = kDefaultCategoryInternalName;
+ item = categories.querySelector(".category[value=" + category + "]");
+ }
+
+ try {
+ init_category_if_required(category);
+ } catch (ex) {
+ Cu.reportError("Error initializing preference category " + category + ": " + ex);
+ throw ex;
+ }
+
+ let friendlyName = internalPrefCategoryNameToFriendlyName(category);
+ if (gLastHash || category != kDefaultCategoryInternalName) {
+ document.location.hash = friendlyName;
+ }
+ // Need to set the gLastHash before setting categories.selectedItem since
+ // the categories 'select' event will re-enter the gotoPref codepath.
+ gLastHash = category;
+ categories.selectedItem = item;
+ window.history.replaceState(category, document.title);
+ search(category, "data-category");
+ let mainContent = document.querySelector(".main-content");
+ mainContent.scrollTop = 0;
+
+ Services.telemetry
+ .getHistogramById("FX_PREFERENCES_CATEGORY_OPENED")
+ .add(telemetryBucketForCategory(friendlyName));
+}
+
+function search(aQuery, aAttribute) {
+ let mainPrefPane = document.getElementById("mainPrefPane");
+ let elements = mainPrefPane.children;
+ for (let element of elements) {
+ let attributeValue = element.getAttribute(aAttribute);
+ element.hidden = (attributeValue != aQuery);
+ }
+
+ let keysets = mainPrefPane.getElementsByTagName("keyset");
+ for (let element of keysets) {
+ let attributeValue = element.getAttribute(aAttribute);
+ if (attributeValue == aQuery)
+ element.removeAttribute("disabled");
+ else
+ element.setAttribute("disabled", true);
+ }
+}
+
+function helpButtonCommand() {
+ let pane = history.state;
+ let categories = document.getElementById("categories");
+ let helpTopic = categories.querySelector(".category[value=" + pane + "]")
+ .getAttribute("helpTopic");
+ openHelpLink(helpTopic);
+}
+
+function friendlyPrefCategoryNameToInternalName(aName) {
+ if (aName.startsWith("pane"))
+ return aName;
+ return "pane" + aName.substring(0, 1).toUpperCase() + aName.substr(1);
+}
+
+// This function is duplicated inside of utilityOverlay.js's openPreferences.
+function internalPrefCategoryNameToFriendlyName(aName) {
+ return (aName || "").replace(/^pane./, function(toReplace) { return toReplace[4].toLowerCase(); });
+}
+
+// Put up a confirm dialog with "ok to restart", "revert without restarting"
+// and "restart later" buttons and returns the index of the button chosen.
+// We can choose not to display the "restart later", or "revert" buttons,
+// altough the later still lets us revert by using the escape key.
+//
+// The constants are useful to interpret the return value of the function.
+const CONFIRM_RESTART_PROMPT_RESTART_NOW = 0;
+const CONFIRM_RESTART_PROMPT_CANCEL = 1;
+const CONFIRM_RESTART_PROMPT_RESTART_LATER = 2;
+function confirmRestartPrompt(aRestartToEnable, aDefaultButtonIndex,
+ aWantRevertAsCancelButton,
+ aWantRestartLaterButton) {
+ let brandName = document.getElementById("bundleBrand").getString("brandShortName");
+ let bundle = document.getElementById("bundlePreferences");
+ let msg = bundle.getFormattedString(aRestartToEnable ?
+ "featureEnableRequiresRestart" :
+ "featureDisableRequiresRestart",
+ [brandName]);
+ let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
+ let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+
+ // Set up the first (index 0) button:
+ let button0Text = bundle.getFormattedString("okToRestartButton", [brandName]);
+ let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+ Services.prompt.BUTTON_TITLE_IS_STRING);
+
+
+ // Set up the second (index 1) button:
+ let button1Text = null;
+ if (aWantRevertAsCancelButton) {
+ button1Text = bundle.getString("revertNoRestartButton");
+ buttonFlags += (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_IS_STRING);
+ } else {
+ buttonFlags += (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_CANCEL);
+ }
+
+ // Set up the third (index 2) button:
+ let button2Text = null;
+ if (aWantRestartLaterButton) {
+ button2Text = bundle.getString("restartLater");
+ buttonFlags += (Services.prompt.BUTTON_POS_2 *
+ Services.prompt.BUTTON_TITLE_IS_STRING);
+ }
+
+ switch (aDefaultButtonIndex) {
+ case 0:
+ buttonFlags += Services.prompt.BUTTON_POS_0_DEFAULT;
+ break;
+ case 1:
+ buttonFlags += Services.prompt.BUTTON_POS_1_DEFAULT;
+ break;
+ case 2:
+ buttonFlags += Services.prompt.BUTTON_POS_2_DEFAULT;
+ break;
+ default:
+ break;
+ }
+
+ let buttonIndex = prompts.confirmEx(window, title, msg, buttonFlags,
+ button0Text, button1Text, button2Text,
+ null, {});
+
+ // If we have the second confirmation dialog for restart, see if the user
+ // cancels out at that point.
+ if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (cancelQuit.data) {
+ buttonIndex = CONFIRM_RESTART_PROMPT_CANCEL;
+ }
+ }
+ return buttonIndex;
+}
diff --git a/browser/components/preferences/in-content/preferences.xul b/browser/components/preferences/in-content/preferences.xul
new file mode 100644
index 000000000..e9664eaf4
--- /dev/null
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -0,0 +1,224 @@
+<?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"?>
+
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://global/skin/in-content/common.css"?>
+<?xml-stylesheet
+ href="chrome://browser/skin/preferences/in-content/preferences.css"?>
+<?xml-stylesheet
+ href="chrome://browser/content/preferences/handlers.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/in-content/search.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/in-content/containers.css"?>
+
+<!DOCTYPE page [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % globalPreferencesDTD SYSTEM "chrome://global/locale/preferences.dtd">
+<!ENTITY % preferencesDTD SYSTEM
+ "chrome://browser/locale/preferences/preferences.dtd">
+<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd">
+<!ENTITY % tabsDTD SYSTEM "chrome://browser/locale/preferences/tabs.dtd">
+<!ENTITY % searchDTD SYSTEM "chrome://browser/locale/preferences/search.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncDTD SYSTEM "chrome://browser/locale/preferences/sync.dtd">
+<!ENTITY % securityDTD SYSTEM
+ "chrome://browser/locale/preferences/security.dtd">
+<!ENTITY % containersDTD SYSTEM
+ "chrome://browser/locale/preferences/containers.dtd">
+<!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
+<!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd">
+<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+<!ENTITY % contentDTD SYSTEM "chrome://browser/locale/preferences/content.dtd">
+<!ENTITY % applicationsDTD SYSTEM
+ "chrome://browser/locale/preferences/applications.dtd">
+<!ENTITY % advancedDTD SYSTEM
+ "chrome://browser/locale/preferences/advanced.dtd">
+%brandDTD;
+%globalPreferencesDTD;
+%preferencesDTD;
+%privacyDTD;
+%tabsDTD;
+%searchDTD;
+%syncBrandDTD;
+%syncDTD;
+%securityDTD;
+%containersDTD;
+%sanitizeDTD;
+%mainDTD;
+%aboutHomeDTD;
+%contentDTD;
+%applicationsDTD;
+%advancedDTD;
+]>
+
+#ifdef XP_WIN
+#define USE_WIN_TITLE_STYLE
+#endif
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ disablefastfind="true"
+#ifdef USE_WIN_TITLE_STYLE
+ title="&prefWindow.titleWin;">
+#else
+ title="&prefWindow.title;">
+#endif
+
+ <html:link rel="shortcut icon"
+ href="chrome://browser/skin/preferences/in-content/favicon.ico"/>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/preferences.js"/>
+ <script src="chrome://browser/content/preferences/in-content/subdialogs.js"/>
+
+ <stringbundle id="bundleBrand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <stringbundleset id="appManagerBundleset">
+ <stringbundle id="appManagerBundle"
+ src="chrome://browser/locale/preferences/applicationManager.properties"/>
+ </stringbundleset>
+
+ <stack flex="1">
+ <hbox flex="1">
+
+ <!-- category list -->
+ <richlistbox id="categories">
+ <richlistitem id="category-general"
+ class="category"
+ value="paneGeneral"
+ helpTopic="prefs-main"
+ tooltiptext="&paneGeneral.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneGeneral.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-search"
+ class="category"
+ value="paneSearch"
+ helpTopic="prefs-search"
+ tooltiptext="&paneSearch.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneSearch.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-content"
+ class="category"
+ value="paneContent"
+ helpTopic="prefs-content"
+ tooltiptext="&paneContent.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneContent.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-application"
+ class="category"
+ value="paneApplications"
+ helpTopic="prefs-applications"
+ tooltiptext="&paneApplications.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneApplications.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-privacy"
+ class="category"
+ value="panePrivacy"
+ helpTopic="prefs-privacy"
+ tooltiptext="&panePrivacy.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&panePrivacy.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-containers"
+ class="category"
+ value="paneContainers"
+ helpTopic="prefs-containers"
+ hidden="true"/>
+
+ <richlistitem id="category-security"
+ class="category"
+ value="paneSecurity"
+ helpTopic="prefs-security"
+ tooltiptext="&paneSecurity.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneSecurity.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-sync"
+ class="category"
+ value="paneSync"
+ helpTopic="prefs-weave"
+ tooltiptext="&paneSync.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneSync.title;</label>
+ </richlistitem>
+
+ <richlistitem id="category-advanced"
+ class="category"
+ value="paneAdvanced"
+ helpTopic="prefs-advanced-general"
+ tooltiptext="&paneAdvanced.title;"
+ align="center">
+ <image class="category-icon"/>
+ <label class="category-name" flex="1">&paneAdvanced.title;</label>
+ </richlistitem>
+ </richlistbox>
+
+ <keyset>
+ <!-- Disable the findbar because it doesn't work properly.
+ Remove this keyset once bug 1094240 ("disablefastfind" attribute
+ broken in e10s mode) is fixed. -->
+ <key key="&focusSearch1.key;" modifiers="accel" id="focusSearch1" oncommand=";"/>
+ </keyset>
+
+ <vbox class="main-content" flex="1">
+ <prefpane id="mainPrefPane">
+#include main.xul
+#include search.xul
+#include privacy.xul
+#include containers.xul
+#include advanced.xul
+#include applications.xul
+#include content.xul
+#include security.xul
+#include sync.xul
+ </prefpane>
+ </vbox>
+
+ </hbox>
+
+ <vbox id="dialogOverlay" align="center" pack="center">
+ <groupbox id="dialogBox"
+ orient="vertical"
+ pack="end"
+ role="dialog"
+ aria-labelledby="dialogTitle">
+ <caption flex="1" align="center">
+ <label id="dialogTitle" flex="1"></label>
+ <button id="dialogClose"
+ class="close-icon"
+ aria-label="&preferencesCloseButton.label;"/>
+ </caption>
+ <browser id="dialogFrame"
+ name="dialogFrame"
+ autoscroll="false"
+ disablehistory="true"/>
+ </groupbox>
+ </vbox>
+ </stack>
+</page>
diff --git a/browser/components/preferences/in-content/privacy.js b/browser/components/preferences/in-content/privacy.js
new file mode 100644
index 000000000..7dfc7de5a
--- /dev/null
+++ b/browser/components/preferences/in-content/privacy.js
@@ -0,0 +1,712 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
+ "resource://gre/modules/ContextualIdentityService.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+var gPrivacyPane = {
+
+ /**
+ * Whether the use has selected the auto-start private browsing mode in the UI.
+ */
+ _autoStartPrivateBrowsing: false,
+
+ /**
+ * Whether the prompt to restart Firefox should appear when changing the autostart pref.
+ */
+ _shouldPromptForRestart: true,
+
+ /**
+ * Show the Tracking Protection UI depending on the
+ * privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link
+ */
+ _initTrackingProtection: function () {
+ if (!Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled")) {
+ return;
+ }
+
+ let link = document.getElementById("trackingProtectionLearnMore");
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection";
+ link.setAttribute("href", url);
+
+ this.trackingProtectionReadPrefs();
+
+ document.getElementById("trackingprotectionbox").hidden = false;
+ document.getElementById("trackingprotectionpbmbox").hidden = true;
+ },
+
+ /**
+ * Linkify the Learn More link of the Private Browsing Mode Tracking
+ * Protection UI.
+ */
+ _initTrackingProtectionPBM: function () {
+ let link = document.getElementById("trackingProtectionPBMLearnMore");
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection-pbm";
+ link.setAttribute("href", url);
+ },
+
+ /**
+ * Initialize autocomplete to ensure prefs are in sync.
+ */
+ _initAutocomplete: function () {
+ Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+ },
+
+ /**
+ * Show the Containers UI depending on the privacy.userContext.ui.enabled pref.
+ */
+ _initBrowserContainers: function () {
+ if (!Services.prefs.getBoolPref("privacy.userContext.ui.enabled")) {
+ return;
+ }
+
+ let link = document.getElementById("browserContainersLearnMore");
+ link.href = Services.urlFormatter.formatURLPref("app.support.baseURL") + "containers";
+
+ document.getElementById("browserContainersbox").hidden = false;
+
+ document.getElementById("browserContainersCheckbox").checked =
+ Services.prefs.getBoolPref("privacy.userContext.enabled");
+ },
+
+ _checkBrowserContainers: function(event) {
+ let checkbox = document.getElementById("browserContainersCheckbox");
+ if (checkbox.checked) {
+ Services.prefs.setBoolPref("privacy.userContext.enabled", true);
+ return;
+ }
+
+ let count = ContextualIdentityService.countContainerTabs();
+ if (count == 0) {
+ Services.prefs.setBoolPref("privacy.userContext.enabled", false);
+ return;
+ }
+
+ let bundlePreferences = document.getElementById("bundlePreferences");
+
+ let title = bundlePreferences.getString("disableContainersAlertTitle");
+ let message = PluralForm.get(count, bundlePreferences.getString("disableContainersMsg"))
+ .replace("#S", count)
+ let okButton = PluralForm.get(count, bundlePreferences.getString("disableContainersOkButton"))
+ .replace("#S", count)
+ let cancelButton = bundlePreferences.getString("disableContainersButton2");
+
+ 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 rv = Services.prompt.confirmEx(window, title, message, buttonFlags,
+ okButton, cancelButton, null, null, {});
+ if (rv == 0) {
+ ContextualIdentityService.closeAllContainerTabs();
+ Services.prefs.setBoolPref("privacy.userContext.enabled", false);
+ return;
+ }
+
+ checkbox.checked = true;
+ },
+
+ /**
+ * Sets up the UI for the number of days of history to keep, and updates the
+ * label of the "Clear Now..." button.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gPrivacyPane));
+ }
+
+ this._updateSanitizeSettingsButton();
+ this.initializeHistoryMode();
+ this.updateHistoryModePane();
+ this.updatePrivacyMicroControls();
+ this.initAutoStartPrivateBrowsingReverter();
+ this._initTrackingProtection();
+ this._initTrackingProtectionPBM();
+ this._initAutocomplete();
+ this._initBrowserContainers();
+
+ setEventListener("privacy.sanitize.sanitizeOnShutdown", "change",
+ gPrivacyPane._updateSanitizeSettingsButton);
+ setEventListener("browser.privatebrowsing.autostart", "change",
+ gPrivacyPane.updatePrivacyMicroControls);
+ setEventListener("historyMode", "command", function () {
+ gPrivacyPane.updateHistoryModePane();
+ gPrivacyPane.updateHistoryModePrefs();
+ gPrivacyPane.updatePrivacyMicroControls();
+ gPrivacyPane.updateAutostart();
+ });
+ setEventListener("historyRememberClear", "click", function () {
+ gPrivacyPane.clearPrivateDataNow(false);
+ return false;
+ });
+ setEventListener("historyRememberCookies", "click", function () {
+ gPrivacyPane.showCookies();
+ return false;
+ });
+ setEventListener("historyDontRememberClear", "click", function () {
+ gPrivacyPane.clearPrivateDataNow(true);
+ return false;
+ });
+ setEventListener("doNotTrackSettings", "click", function () {
+ gPrivacyPane.showDoNotTrackSettings();
+ return false;
+ });
+ setEventListener("privateBrowsingAutoStart", "command",
+ gPrivacyPane.updateAutostart);
+ setEventListener("cookieExceptions", "command",
+ gPrivacyPane.showCookieExceptions);
+ setEventListener("showCookiesButton", "command",
+ gPrivacyPane.showCookies);
+ setEventListener("clearDataSettings", "command",
+ gPrivacyPane.showClearPrivateDataSettings);
+ setEventListener("trackingProtectionRadioGroup", "command",
+ gPrivacyPane.trackingProtectionWritePrefs);
+ setEventListener("trackingProtectionExceptions", "command",
+ gPrivacyPane.showTrackingProtectionExceptions);
+ setEventListener("changeBlockList", "command",
+ gPrivacyPane.showBlockLists);
+ setEventListener("changeBlockListPBM", "command",
+ gPrivacyPane.showBlockLists);
+ setEventListener("browserContainersCheckbox", "command",
+ gPrivacyPane._checkBrowserContainers);
+ setEventListener("browserContainersSettings", "command",
+ gPrivacyPane.showContainerSettings);
+ },
+
+ // TRACKING PROTECTION MODE
+
+ /**
+ * Selects the right item of the Tracking Protection radiogroup.
+ */
+ trackingProtectionReadPrefs() {
+ let enabledPref = document.getElementById("privacy.trackingprotection.enabled");
+ let pbmPref = document.getElementById("privacy.trackingprotection.pbmode.enabled");
+ let radiogroup = document.getElementById("trackingProtectionRadioGroup");
+
+ // Global enable takes precedence over enabled in Private Browsing.
+ if (enabledPref.value) {
+ radiogroup.value = "always";
+ } else if (pbmPref.value) {
+ radiogroup.value = "private";
+ } else {
+ radiogroup.value = "never";
+ }
+ },
+
+ /**
+ * Sets the pref values based on the selected item of the radiogroup.
+ */
+ trackingProtectionWritePrefs() {
+ let enabledPref = document.getElementById("privacy.trackingprotection.enabled");
+ let pbmPref = document.getElementById("privacy.trackingprotection.pbmode.enabled");
+ let radiogroup = document.getElementById("trackingProtectionRadioGroup");
+
+ switch (radiogroup.value) {
+ case "always":
+ enabledPref.value = true;
+ pbmPref.value = true;
+ break;
+ case "private":
+ enabledPref.value = false;
+ pbmPref.value = true;
+ break;
+ case "never":
+ enabledPref.value = false;
+ pbmPref.value = false;
+ break;
+ }
+ },
+
+ // HISTORY MODE
+
+ /**
+ * The list of preferences which affect the initial history mode settings.
+ * If the auto start private browsing mode pref is active, the initial
+ * history mode would be set to "Don't remember anything".
+ * If ALL of these preferences are set to the values that correspond
+ * to keeping some part of history, and the auto-start
+ * private browsing mode is not active, the initial history mode would be
+ * set to "Remember everything".
+ * Otherwise, the initial history mode would be set to "Custom".
+ *
+ * Extensions adding their own preferences can set values here if needed.
+ */
+ prefsForKeepingHistory: {
+ "places.history.enabled": true, // History is enabled
+ "browser.formfill.enable": true, // Form information is saved
+ "network.cookie.cookieBehavior": 0, // All cookies are enabled
+ "network.cookie.lifetimePolicy": 0, // Cookies use supplied lifetime
+ "privacy.sanitize.sanitizeOnShutdown": false, // Private date is NOT cleared on shutdown
+ },
+
+ /**
+ * The list of control IDs which are dependent on the auto-start private
+ * browsing setting, such that in "Custom" mode they would be disabled if
+ * the auto-start private browsing checkbox is checked, and enabled otherwise.
+ *
+ * Extensions adding their own controls can append their IDs to this array if needed.
+ */
+ dependentControls: [
+ "rememberHistory",
+ "rememberForms",
+ "keepUntil",
+ "keepCookiesUntil",
+ "alwaysClear",
+ "clearDataSettings"
+ ],
+
+ /**
+ * Check whether preferences values are set to keep history
+ *
+ * @param aPrefs an array of pref names to check for
+ * @returns boolean true if all of the prefs are set to keep history,
+ * false otherwise
+ */
+ _checkHistoryValues: function(aPrefs) {
+ for (let pref of Object.keys(aPrefs)) {
+ if (document.getElementById(pref).value != aPrefs[pref])
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Initialize the history mode menulist based on the privacy preferences
+ */
+ initializeHistoryMode: function PPP_initializeHistoryMode()
+ {
+ let mode;
+ let getVal = aPref => document.getElementById(aPref).value;
+
+ if (this._checkHistoryValues(this.prefsForKeepingHistory)) {
+ if (getVal("browser.privatebrowsing.autostart"))
+ mode = "dontremember";
+ else
+ mode = "remember";
+ }
+ else
+ mode = "custom";
+
+ document.getElementById("historyMode").value = mode;
+ },
+
+ /**
+ * Update the selected pane based on the history mode menulist
+ */
+ updateHistoryModePane: function PPP_updateHistoryModePane()
+ {
+ let selectedIndex = -1;
+ switch (document.getElementById("historyMode").value) {
+ case "remember":
+ selectedIndex = 0;
+ break;
+ case "dontremember":
+ selectedIndex = 1;
+ break;
+ case "custom":
+ selectedIndex = 2;
+ break;
+ }
+ document.getElementById("historyPane").selectedIndex = selectedIndex;
+ },
+
+ /**
+ * Update the private browsing auto-start pref and the history mode
+ * micro-management prefs based on the history mode menulist
+ */
+ updateHistoryModePrefs: function PPP_updateHistoryModePrefs()
+ {
+ let pref = document.getElementById("browser.privatebrowsing.autostart");
+ switch (document.getElementById("historyMode").value) {
+ case "remember":
+ if (pref.value)
+ pref.value = false;
+
+ // select the remember history option if needed
+ let rememberHistoryCheckbox = document.getElementById("rememberHistory");
+ if (!rememberHistoryCheckbox.checked)
+ rememberHistoryCheckbox.checked = true;
+
+ // select the remember forms history option
+ document.getElementById("browser.formfill.enable").value = true;
+
+ // select the allow cookies option
+ document.getElementById("network.cookie.cookieBehavior").value = 0;
+ // select the cookie lifetime policy option
+ document.getElementById("network.cookie.lifetimePolicy").value = 0;
+
+ // select the clear on close option
+ document.getElementById("privacy.sanitize.sanitizeOnShutdown").value = false;
+ break;
+ case "dontremember":
+ if (!pref.value)
+ pref.value = true;
+ break;
+ }
+ },
+
+ /**
+ * Update the privacy micro-management controls based on the
+ * value of the private browsing auto-start checkbox.
+ */
+ updatePrivacyMicroControls: function PPP_updatePrivacyMicroControls()
+ {
+ if (document.getElementById("historyMode").value == "custom") {
+ let disabled = this._autoStartPrivateBrowsing =
+ document.getElementById("privateBrowsingAutoStart").checked;
+ this.dependentControls.forEach(function (aElement) {
+ let control = document.getElementById(aElement);
+ let preferenceId = control.getAttribute("preference");
+ if (!preferenceId) {
+ let dependentControlId = control.getAttribute("control");
+ if (dependentControlId) {
+ let dependentControl = document.getElementById(dependentControlId);
+ preferenceId = dependentControl.getAttribute("preference");
+ }
+ }
+
+ let preference = preferenceId ? document.getElementById(preferenceId) : {};
+ control.disabled = disabled || preference.locked;
+ });
+
+ // adjust the cookie controls status
+ this.readAcceptCookies();
+ let lifetimePolicy = document.getElementById("network.cookie.lifetimePolicy").value;
+ if (lifetimePolicy != Ci.nsICookieService.ACCEPT_NORMALLY &&
+ lifetimePolicy != Ci.nsICookieService.ACCEPT_SESSION &&
+ lifetimePolicy != Ci.nsICookieService.ACCEPT_FOR_N_DAYS) {
+ lifetimePolicy = Ci.nsICookieService.ACCEPT_NORMALLY;
+ }
+ document.getElementById("keepCookiesUntil").value = disabled ? 2 : lifetimePolicy;
+
+ // adjust the checked state of the sanitizeOnShutdown checkbox
+ document.getElementById("alwaysClear").checked = disabled ? false :
+ document.getElementById("privacy.sanitize.sanitizeOnShutdown").value;
+
+ // adjust the checked state of the remember history checkboxes
+ document.getElementById("rememberHistory").checked = disabled ? false :
+ document.getElementById("places.history.enabled").value;
+ document.getElementById("rememberForms").checked = disabled ? false :
+ document.getElementById("browser.formfill.enable").value;
+
+ if (!disabled) {
+ // adjust the Settings button for sanitizeOnShutdown
+ this._updateSanitizeSettingsButton();
+ }
+ }
+ },
+
+ // PRIVATE BROWSING
+
+ /**
+ * Initialize the starting state for the auto-start private browsing mode pref reverter.
+ */
+ initAutoStartPrivateBrowsingReverter: function PPP_initAutoStartPrivateBrowsingReverter()
+ {
+ let mode = document.getElementById("historyMode");
+ let autoStart = document.getElementById("privateBrowsingAutoStart");
+ this._lastMode = mode.selectedIndex;
+ this._lastCheckState = autoStart.hasAttribute('checked');
+ },
+
+ _lastMode: null,
+ _lastCheckState: null,
+ updateAutostart: function PPP_updateAutostart() {
+ let mode = document.getElementById("historyMode");
+ let autoStart = document.getElementById("privateBrowsingAutoStart");
+ let pref = document.getElementById("browser.privatebrowsing.autostart");
+ if ((mode.value == "custom" && this._lastCheckState == autoStart.checked) ||
+ (mode.value == "remember" && !this._lastCheckState) ||
+ (mode.value == "dontremember" && this._lastCheckState)) {
+ // These are all no-op changes, so we don't need to prompt.
+ this._lastMode = mode.selectedIndex;
+ this._lastCheckState = autoStart.hasAttribute('checked');
+ return;
+ }
+
+ if (!this._shouldPromptForRestart) {
+ // We're performing a revert. Just let it happen.
+ return;
+ }
+
+ let buttonIndex = confirmRestartPrompt(autoStart.checked, 1,
+ true, false);
+ if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
+ pref.value = autoStart.hasAttribute('checked');
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ return;
+ }
+
+ this._shouldPromptForRestart = false;
+
+ if (this._lastCheckState) {
+ autoStart.checked = "checked";
+ } else {
+ autoStart.removeAttribute('checked');
+ }
+ pref.value = autoStart.hasAttribute('checked');
+ mode.selectedIndex = this._lastMode;
+ mode.doCommand();
+
+ this._shouldPromptForRestart = true;
+ },
+
+ /**
+ * Displays fine-grained, per-site preferences for tracking protection.
+ */
+ showTrackingProtectionExceptions() {
+ let bundlePreferences = document.getElementById("bundlePreferences");
+ let params = {
+ permissionType: "trackingprotection",
+ hideStatusColumn: true,
+ windowTitle: bundlePreferences.getString("trackingprotectionpermissionstitle"),
+ introText: bundlePreferences.getString("trackingprotectionpermissionstext"),
+ };
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Displays container panel for customising and adding containers.
+ */
+ showContainerSettings() {
+ gotoPref("containers");
+ },
+
+ /**
+ * Displays the available block lists for tracking protection.
+ */
+ showBlockLists: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ let brandName = document.getElementById("bundleBrand")
+ .getString("brandShortName");
+ var params = { brandShortName: brandName,
+ windowTitle: bundlePreferences.getString("blockliststitle"),
+ introText: bundlePreferences.getString("blockliststext") };
+ gSubDialog.open("chrome://browser/content/preferences/blocklists.xul",
+ null, params);
+ },
+
+ /**
+ * Displays the Do Not Track settings dialog.
+ */
+ showDoNotTrackSettings() {
+ gSubDialog.open("chrome://browser/content/preferences/donottrack.xul",
+ "resizable=no");
+ },
+
+ // HISTORY
+
+ /*
+ * Preferences:
+ *
+ * places.history.enabled
+ * - whether history is enabled or not
+ * browser.formfill.enable
+ * - true if entries in forms and the search bar should be saved, false
+ * otherwise
+ */
+
+ // COOKIES
+
+ /*
+ * Preferences:
+ *
+ * network.cookie.cookieBehavior
+ * - determines how the browser should handle cookies:
+ * 0 means enable all cookies
+ * 1 means reject all third party cookies
+ * 2 means disable all cookies
+ * 3 means reject third party cookies unless at least one is already set for the eTLD
+ * see netwerk/cookie/src/nsCookieService.cpp for details
+ * network.cookie.lifetimePolicy
+ * - determines how long cookies are stored:
+ * 0 means keep cookies until they expire
+ * 2 means keep cookies until the browser is closed
+ */
+
+ /**
+ * Reads the network.cookie.cookieBehavior preference value and
+ * enables/disables the rest of the cookie UI accordingly, returning true
+ * if cookies are enabled.
+ */
+ readAcceptCookies: function ()
+ {
+ var pref = document.getElementById("network.cookie.cookieBehavior");
+ var acceptThirdPartyLabel = document.getElementById("acceptThirdPartyLabel");
+ var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
+ var keepUntil = document.getElementById("keepUntil");
+ var menu = document.getElementById("keepCookiesUntil");
+
+ // enable the rest of the UI for anything other than "disable all cookies"
+ var acceptCookies = (pref.value != 2);
+
+ acceptThirdPartyLabel.disabled = acceptThirdPartyMenu.disabled = !acceptCookies;
+ keepUntil.disabled = menu.disabled = this._autoStartPrivateBrowsing || !acceptCookies;
+
+ return acceptCookies;
+ },
+
+ /**
+ * Enables/disables the "keep until" label and menulist in response to the
+ * "accept cookies" checkbox being checked or unchecked.
+ */
+ writeAcceptCookies: function ()
+ {
+ var accept = document.getElementById("acceptCookies");
+ var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
+
+ // if we're enabling cookies, automatically select 'accept third party always'
+ if (accept.checked)
+ acceptThirdPartyMenu.selectedIndex = 0;
+
+ return accept.checked ? 0 : 2;
+ },
+
+ /**
+ * Converts between network.cookie.cookieBehavior and the third-party cookie UI
+ */
+ readAcceptThirdPartyCookies: function ()
+ {
+ var pref = document.getElementById("network.cookie.cookieBehavior");
+ switch (pref.value)
+ {
+ case 0:
+ return "always";
+ case 1:
+ return "never";
+ case 2:
+ return "never";
+ case 3:
+ return "visited";
+ default:
+ return undefined;
+ }
+ },
+
+ writeAcceptThirdPartyCookies: function ()
+ {
+ var accept = document.getElementById("acceptThirdPartyMenu").selectedItem;
+ switch (accept.value)
+ {
+ case "always":
+ return 0;
+ case "visited":
+ return 3;
+ case "never":
+ return 1;
+ default:
+ return undefined;
+ }
+ },
+
+ /**
+ * Displays fine-grained, per-site preferences for cookies.
+ */
+ showCookieExceptions: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible : true,
+ sessionVisible : true,
+ allowVisible : true,
+ prefilledHost : "",
+ permissionType : "cookie",
+ windowTitle : bundlePreferences.getString("cookiepermissionstitle"),
+ introText : bundlePreferences.getString("cookiepermissionstext") };
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Displays all the user's cookies in a dialog.
+ */
+ showCookies: function (aCategory)
+ {
+ gSubDialog.open("chrome://browser/content/preferences/cookies.xul");
+ },
+
+ // CLEAR PRIVATE DATA
+
+ /*
+ * Preferences:
+ *
+ * privacy.sanitize.sanitizeOnShutdown
+ * - true if the user's private data is cleared on startup according to the
+ * Clear Private Data settings, false otherwise
+ */
+
+ /**
+ * Displays the Clear Private Data settings dialog.
+ */
+ showClearPrivateDataSettings: function ()
+ {
+ gSubDialog.open("chrome://browser/content/preferences/sanitize.xul", "resizable=no");
+ },
+
+
+ /**
+ * Displays a dialog from which individual parts of private data may be
+ * cleared.
+ */
+ clearPrivateDataNow: function (aClearEverything) {
+ var ts = document.getElementById("privacy.sanitize.timeSpan");
+ var timeSpanOrig = ts.value;
+
+ if (aClearEverything) {
+ ts.value = 0;
+ }
+
+ gSubDialog.open("chrome://browser/content/sanitize.xul", "resizable=no", null, () => {
+ // reset the timeSpan pref
+ if (aClearEverything) {
+ ts.value = timeSpanOrig;
+ }
+
+ Services.obs.notifyObservers(null, "clear-private-data", null);
+ });
+ },
+
+ /**
+ * Enables or disables the "Settings..." button depending
+ * on the privacy.sanitize.sanitizeOnShutdown preference value
+ */
+ _updateSanitizeSettingsButton: function () {
+ var settingsButton = document.getElementById("clearDataSettings");
+ var sanitizeOnShutdownPref = document.getElementById("privacy.sanitize.sanitizeOnShutdown");
+
+ settingsButton.disabled = !sanitizeOnShutdownPref.value;
+ },
+
+ // CONTAINERS
+
+ /*
+ * preferences:
+ *
+ * privacy.userContext.enabled
+ * - true if containers is enabled
+ */
+
+ /**
+ * Enables/disables the Settings button used to configure containers
+ */
+ readBrowserContainersCheckbox: function ()
+ {
+ var pref = document.getElementById("privacy.userContext.enabled");
+ var settings = document.getElementById("browserContainersSettings");
+
+ settings.disabled = !pref.value;
+ }
+
+};
diff --git a/browser/components/preferences/in-content/privacy.xul b/browser/components/preferences/in-content/privacy.xul
new file mode 100644
index 000000000..6ac6c88a4
--- /dev/null
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -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/.
+
+<!-- Privacy panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/privacy.js"/>
+
+<preferences id="privacyPreferences" hidden="true" data-category="panePrivacy">
+
+ <!-- Tracking -->
+ <preference id="privacy.trackingprotection.enabled"
+ name="privacy.trackingprotection.enabled"
+ type="bool"/>
+ <preference id="privacy.trackingprotection.pbmode.enabled"
+ name="privacy.trackingprotection.pbmode.enabled"
+ type="bool"/>
+
+ <!-- XXX button prefs -->
+ <preference id="pref.privacy.disable_button.cookie_exceptions"
+ name="pref.privacy.disable_button.cookie_exceptions"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.view_cookies"
+ name="pref.privacy.disable_button.view_cookies"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.change_blocklist"
+ name="pref.privacy.disable_button.change_blocklist"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.tracking_protection_exceptions"
+ name="pref.privacy.disable_button.tracking_protection_exceptions"
+ type="bool"/>
+
+ <!-- Location Bar -->
+ <preference id="browser.urlbar.autocomplete.enabled"
+ name="browser.urlbar.autocomplete.enabled"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.bookmark"
+ name="browser.urlbar.suggest.bookmark"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.history"
+ name="browser.urlbar.suggest.history"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.openpage"
+ name="browser.urlbar.suggest.openpage"
+ type="bool"/>
+
+ <!-- History -->
+ <preference id="places.history.enabled"
+ name="places.history.enabled"
+ type="bool"/>
+ <preference id="browser.formfill.enable"
+ name="browser.formfill.enable"
+ type="bool"/>
+ <!-- Cookies -->
+ <preference id="network.cookie.cookieBehavior"
+ name="network.cookie.cookieBehavior"
+ type="int"/>
+ <preference id="network.cookie.lifetimePolicy"
+ name="network.cookie.lifetimePolicy"
+ type="int"/>
+ <preference id="network.cookie.blockFutureCookies"
+ name="network.cookie.blockFutureCookies"
+ type="bool"/>
+ <!-- Clear Private Data -->
+ <preference id="privacy.sanitize.sanitizeOnShutdown"
+ name="privacy.sanitize.sanitizeOnShutdown"
+ type="bool"/>
+ <preference id="privacy.sanitize.timeSpan"
+ name="privacy.sanitize.timeSpan"
+ type="int"/>
+ <!-- Private Browsing -->
+ <preference id="browser.privatebrowsing.autostart"
+ name="browser.privatebrowsing.autostart"
+ type="bool"/>
+</preferences>
+
+<hbox id="header-privacy"
+ class="header"
+ hidden="true"
+ data-category="panePrivacy">
+ <label class="header-name" flex="1">&panePrivacy.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<!-- Tracking -->
+<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
+ <vbox id="trackingprotectionbox" hidden="true">
+ <hbox align="start">
+ <vbox>
+ <caption><label>&trackingProtectionHeader.label;
+ <label id="trackingProtectionLearnMore" class="text-link"
+ value="&trackingProtectionLearnMore.label;"/>
+ </label></caption>
+ <radiogroup id="trackingProtectionRadioGroup">
+ <radio value="always"
+ label="&trackingProtectionAlways.label;"
+ accesskey="&trackingProtectionAlways.accesskey;"/>
+ <radio value="private"
+ label="&trackingProtectionPrivate.label;"
+ accesskey="&trackingProtectionPrivate.accesskey;"/>
+ <radio value="never"
+ label="&trackingProtectionNever.label;"
+ accesskey="&trackingProtectionNever.accesskey;"/>
+ </radiogroup>
+ </vbox>
+ <spacer flex="1" />
+ <vbox>
+ <button id="trackingProtectionExceptions"
+ label="&trackingProtectionExceptions.label;"
+ accesskey="&trackingProtectionExceptions.accesskey;"
+ preference="pref.privacy.disable_button.tracking_protection_exceptions"/>
+ <button id="changeBlockList"
+ label="&changeBlockList.label;"
+ accesskey="&changeBlockList.accesskey;"
+ preference="pref.privacy.disable_button.change_blocklist"/>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox id="trackingprotectionpbmbox">
+ <caption><label>&tracking.label;</label></caption>
+ <hbox align="center">
+ <checkbox id="trackingProtectionPBM"
+ preference="privacy.trackingprotection.pbmode.enabled"
+ accesskey="&trackingProtectionPBM5.accesskey;"
+ label="&trackingProtectionPBM5.label;" />
+ <label id="trackingProtectionPBMLearnMore"
+ class="text-link"
+ value="&trackingProtectionPBMLearnMore.label;"/>
+ <spacer flex="1" />
+ <button id="changeBlockListPBM"
+ label="&changeBlockList.label;" accesskey="&changeBlockList.accesskey;"
+ preference="pref.privacy.disable_button.change_blocklist"/>
+ </hbox>
+ </vbox>
+ <vbox>
+ <description>&doNotTrack.pre.label;<label
+ class="text-link" id="doNotTrackSettings"
+ >&doNotTrack.settings.label;</label>&doNotTrack.post.label;</description>
+ </vbox>
+</groupbox>
+
+<!-- History -->
+<groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
+ <caption><label>&history.label;</label></caption>
+ <hbox align="center">
+ <label id="historyModeLabel"
+ control="historyMode"
+ accesskey="&historyHeader.pre.accesskey;">&historyHeader.pre.label;
+ </label>
+ <menulist id="historyMode">
+ <menupopup>
+ <menuitem label="&historyHeader.remember.label;" value="remember"/>
+ <menuitem label="&historyHeader.dontremember.label;" value="dontremember"/>
+ <menuitem label="&historyHeader.custom.label;" value="custom"/>
+ </menupopup>
+ </menulist>
+ <label>&historyHeader.post.label;</label>
+ </hbox>
+ <deck id="historyPane">
+ <vbox id="historyRememberPane">
+ <hbox align="center" flex="1">
+ <vbox flex="1">
+ <description>&rememberDescription.label;</description>
+ <separator class="thin"/>
+ <description>&rememberActions.pre.label;<label
+ class="text-link" id="historyRememberClear"
+ >&rememberActions.clearHistory.label;</label>&rememberActions.middle.label;<label
+ class="text-link" id="historyRememberCookies"
+ >&rememberActions.removeCookies.label;</label>&rememberActions.post.label;</description>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox id="historyDontRememberPane">
+ <hbox align="center" flex="1">
+ <vbox flex="1">
+ <description>&dontrememberDescription.label;</description>
+ <separator class="thin"/>
+ <description>&dontrememberActions.pre.label;<label
+ class="text-link" id="historyDontRememberClear"
+ >&dontrememberActions.clearHistory.label;</label>&dontrememberActions.post.label;</description>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox id="historyCustomPane">
+ <separator class="thin"/>
+ <vbox>
+ <vbox align="start">
+ <checkbox id="privateBrowsingAutoStart"
+ label="&privateBrowsingPermanent2.label;"
+ accesskey="&privateBrowsingPermanent2.accesskey;"
+ preference="browser.privatebrowsing.autostart"/>
+ </vbox>
+ <vbox class="indent">
+ <vbox align="start">
+ <checkbox id="rememberHistory"
+ label="&rememberHistory2.label;"
+ accesskey="&rememberHistory2.accesskey;"
+ preference="places.history.enabled"/>
+ <checkbox id="rememberForms"
+ label="&rememberSearchForm.label;"
+ accesskey="&rememberSearchForm.accesskey;"
+ preference="browser.formfill.enable"/>
+ </vbox>
+ <hbox id="cookiesBox">
+ <checkbox id="acceptCookies" label="&acceptCookies.label;"
+ preference="network.cookie.cookieBehavior"
+ accesskey="&acceptCookies.accesskey;"
+ onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
+ onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/>
+ <spacer flex="1" />
+ <button id="cookieExceptions"
+ label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
+ preference="pref.privacy.disable_button.cookie_exceptions"/>
+ </hbox>
+ <hbox id="acceptThirdPartyRow"
+ class="indent"
+ align="center">
+ <label id="acceptThirdPartyLabel" control="acceptThirdPartyMenu"
+ accesskey="&acceptThirdParty.pre.accesskey;">&acceptThirdParty.pre.label;</label>
+ <menulist id="acceptThirdPartyMenu" preference="network.cookie.cookieBehavior"
+ onsyncfrompreference="return gPrivacyPane.readAcceptThirdPartyCookies();"
+ onsynctopreference="return gPrivacyPane.writeAcceptThirdPartyCookies();">
+ <menupopup>
+ <menuitem label="&acceptThirdParty.always.label;" value="always"/>
+ <menuitem label="&acceptThirdParty.visited.label;" value="visited"/>
+ <menuitem label="&acceptThirdParty.never.label;" value="never"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <hbox id="keepRow"
+ class="indent"
+ align="center">
+ <label id="keepUntil"
+ control="keepCookiesUntil"
+ accesskey="&keepUntil.accesskey;">&keepUntil.label;</label>
+ <menulist id="keepCookiesUntil"
+ preference="network.cookie.lifetimePolicy">
+ <menupopup>
+ <menuitem label="&expire.label;" value="0"/>
+ <menuitem label="&close.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ <button id="showCookiesButton"
+ label="&showCookies.label;" accesskey="&showCookies.accesskey;"
+ preference="pref.privacy.disable_button.view_cookies"/>
+ </hbox>
+ <hbox id="clearDataBox"
+ align="center">
+ <checkbox id="alwaysClear"
+ preference="privacy.sanitize.sanitizeOnShutdown"
+ label="&clearOnClose.label;"
+ accesskey="&clearOnClose.accesskey;"/>
+ <spacer flex="1"/>
+ <button id="clearDataSettings" label="&clearOnCloseSettings.label;"
+ accesskey="&clearOnCloseSettings.accesskey;"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+ </deck>
+</groupbox>
+
+<!-- Location Bar -->
+<groupbox id="locationBarGroup"
+ data-category="panePrivacy"
+ hidden="true">
+ <caption><label>&locationBar.label;</label></caption>
+ <label id="locationBarSuggestionLabel">&locbar.suggest.label;</label>
+ <checkbox id="historySuggestion" label="&locbar.history.label;"
+ accesskey="&locbar.history.accesskey;"
+ preference="browser.urlbar.suggest.history"/>
+ <checkbox id="bookmarkSuggestion" label="&locbar.bookmarks.label;"
+ accesskey="&locbar.bookmarks.accesskey;"
+ preference="browser.urlbar.suggest.bookmark"/>
+ <checkbox id="openpageSuggestion" label="&locbar.openpage.label;"
+ accesskey="&locbar.openpage.accesskey;"
+ preference="browser.urlbar.suggest.openpage"/>
+ <label class="text-link" onclick="gotoPref('search')">
+ &suggestionSettings.label;
+ </label>
+</groupbox>
+
+<!-- Containers -->
+<groupbox id="browserContainersGroup" data-category="panePrivacy" hidden="true">
+ <vbox id="browserContainersbox" hidden="true">
+ <caption><label>&browserContainersHeader.label;
+ <label id="browserContainersLearnMore" class="text-link"
+ value="&browserContainersLearnMore.label;"/>
+ </label></caption>
+ <hbox align="start">
+ <vbox>
+ <checkbox id="browserContainersCheckbox"
+ label="&browserContainersEnabled.label;"
+ accesskey="&browserContainersEnabled.accesskey;"
+ preference="privacy.userContext.enabled"
+ onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
+ </vbox>
+ <spacer flex="1"/>
+ <vbox>
+ <button id="browserContainersSettings"
+ label="&browserContainersSettings.label;"
+ accesskey="&browserContainersSettings.accesskey;"/>
+ </vbox>
+ </hbox>
+ </vbox>
+</groupbox>
diff --git a/browser/components/preferences/in-content/search.js b/browser/components/preferences/in-content/search.js
new file mode 100644
index 000000000..55aa2c18c
--- /dev/null
+++ b/browser/components/preferences/in-content/search.js
@@ -0,0 +1,604 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const ENGINE_FLAVOR = "text/x-moz-search-engine";
+
+var gEngineView = null;
+
+var gSearchPane = {
+
+ /**
+ * Initialize autocomplete to ensure prefs are in sync.
+ */
+ _initAutocomplete: function () {
+ Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+ },
+
+ init: function ()
+ {
+ gEngineView = new EngineView(new EngineStore());
+ document.getElementById("engineList").view = gEngineView;
+ this.buildDefaultEngineDropDown();
+
+ let addEnginesLink = document.getElementById("addEngines");
+ let searchEnginesURL = Services.wm.getMostRecentWindow('navigator:browser')
+ .BrowserSearch.searchEnginesURL;
+ addEnginesLink.setAttribute("href", searchEnginesURL);
+
+ window.addEventListener("click", this, false);
+ window.addEventListener("command", this, false);
+ window.addEventListener("dragstart", this, false);
+ window.addEventListener("keypress", this, false);
+ window.addEventListener("select", this, false);
+ window.addEventListener("blur", this, true);
+
+ Services.obs.addObserver(this, "browser-search-engine-modified", false);
+ window.addEventListener("unload", () => {
+ Services.obs.removeObserver(this, "browser-search-engine-modified", false);
+ });
+
+ this._initAutocomplete();
+
+ let suggestsPref =
+ document.getElementById("browser.search.suggest.enabled");
+ suggestsPref.addEventListener("change", () => {
+ this.updateSuggestsCheckbox();
+ });
+ this.updateSuggestsCheckbox();
+ },
+
+ updateSuggestsCheckbox() {
+ let suggestsPref =
+ document.getElementById("browser.search.suggest.enabled");
+ let permanentPB =
+ Services.prefs.getBoolPref("browser.privatebrowsing.autostart");
+ let urlbarSuggests = document.getElementById("urlBarSuggestion");
+ urlbarSuggests.disabled = !suggestsPref.value || permanentPB;
+
+ let urlbarSuggestsPref =
+ document.getElementById("browser.urlbar.suggest.searches");
+ urlbarSuggests.checked = urlbarSuggestsPref.value;
+ if (urlbarSuggests.disabled) {
+ urlbarSuggests.checked = false;
+ }
+
+ let permanentPBLabel =
+ document.getElementById("urlBarSuggestionPermanentPBLabel");
+ permanentPBLabel.hidden = urlbarSuggests.hidden || !permanentPB;
+ },
+
+ buildDefaultEngineDropDown: function() {
+ // This is called each time something affects the list of engines.
+ let list = document.getElementById("defaultEngine");
+ // Set selection to the current default engine.
+ let currentEngine = Services.search.currentEngine.name;
+
+ // If the current engine isn't in the list any more, select the first item.
+ let engines = gEngineView._engineStore._engines;
+ if (!engines.some(e => e.name == currentEngine))
+ currentEngine = engines[0].name;
+
+ // Now clean-up and rebuild the list.
+ list.removeAllItems();
+ gEngineView._engineStore._engines.forEach(e => {
+ let item = list.appendItem(e.name);
+ item.setAttribute("class", "menuitem-iconic searchengine-menuitem menuitem-with-favicon");
+ if (e.iconURI) {
+ item.setAttribute("image", e.iconURI.spec);
+ }
+ item.engine = e;
+ if (e.name == currentEngine)
+ list.selectedItem = item;
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "click":
+ if (aEvent.target.id != "engineChildren" &&
+ !aEvent.target.classList.contains("searchEngineAction")) {
+ let engineList = document.getElementById("engineList");
+ // We don't want to toggle off selection while editing keyword
+ // so proceed only when the input field is hidden.
+ // We need to check that engineList.view is defined here
+ // because the "click" event listener is on <window> and the
+ // view might have been destroyed if the pane has been navigated
+ // away from.
+ if (engineList.inputField.hidden && engineList.view) {
+ let selection = engineList.view.selection;
+ if (selection.count > 0) {
+ selection.toggleSelect(selection.currentIndex);
+ }
+ engineList.blur();
+ }
+ }
+ break;
+ case "command":
+ switch (aEvent.target.id) {
+ case "":
+ if (aEvent.target.parentNode &&
+ aEvent.target.parentNode.parentNode &&
+ aEvent.target.parentNode.parentNode.id == "defaultEngine") {
+ gSearchPane.setDefaultEngine();
+ }
+ break;
+ case "restoreDefaultSearchEngines":
+ gSearchPane.onRestoreDefaults();
+ break;
+ case "removeEngineButton":
+ Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
+ break;
+ }
+ break;
+ case "dragstart":
+ if (aEvent.target.id == "engineChildren") {
+ onDragEngineStart(aEvent);
+ }
+ break;
+ case "keypress":
+ if (aEvent.target.id == "engineList") {
+ gSearchPane.onTreeKeyPress(aEvent);
+ }
+ break;
+ case "select":
+ if (aEvent.target.id == "engineList") {
+ gSearchPane.onTreeSelect();
+ }
+ break;
+ case "blur":
+ if (aEvent.target.id == "engineList" &&
+ aEvent.target.inputField == document.getBindingParent(aEvent.originalTarget)) {
+ gSearchPane.onInputBlur();
+ }
+ break;
+ }
+ },
+
+ observe: function(aEngine, aTopic, aVerb) {
+ if (aTopic == "browser-search-engine-modified") {
+ aEngine.QueryInterface(Components.interfaces.nsISearchEngine);
+ switch (aVerb) {
+ case "engine-added":
+ gEngineView._engineStore.addEngine(aEngine);
+ gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
+ gSearchPane.buildDefaultEngineDropDown();
+ break;
+ case "engine-changed":
+ gEngineView._engineStore.reloadIcons();
+ gEngineView.invalidate();
+ break;
+ case "engine-removed":
+ gSearchPane.remove(aEngine);
+ break;
+ case "engine-current":
+ // If the user is going through the drop down using up/down keys, the
+ // dropdown may still be open (eg. on Windows) when engine-current is
+ // fired, so rebuilding the list unconditionally would get in the way.
+ let selectedEngine =
+ document.getElementById("defaultEngine").selectedItem.engine;
+ if (selectedEngine.name != aEngine.name)
+ gSearchPane.buildDefaultEngineDropDown();
+ break;
+ case "engine-default":
+ // Not relevant
+ break;
+ }
+ }
+ },
+
+ onInputBlur: function(aEvent) {
+ let tree = document.getElementById("engineList");
+ if (!tree.hasAttribute("editing"))
+ return;
+
+ // Accept input unless discarded.
+ let accept = aEvent.charCode != KeyEvent.DOM_VK_ESCAPE;
+ tree.stopEditing(accept);
+ },
+
+ onTreeSelect: function() {
+ document.getElementById("removeEngineButton").disabled =
+ !gEngineView.isEngineSelectedAndRemovable();
+ },
+
+ onTreeKeyPress: function(aEvent) {
+ let index = gEngineView.selectedIndex;
+ let tree = document.getElementById("engineList");
+ if (tree.hasAttribute("editing"))
+ return;
+
+ if (aEvent.charCode == KeyEvent.DOM_VK_SPACE) {
+ // Space toggles the checkbox.
+ let newValue = !gEngineView._engineStore.engines[index].shown;
+ gEngineView.setCellValue(index, tree.columns.getFirstColumn(),
+ newValue.toString());
+ // Prevent page from scrolling on the space key.
+ aEvent.preventDefault();
+ }
+ else {
+ let isMac = Services.appinfo.OS == "Darwin";
+ if ((isMac && aEvent.keyCode == KeyEvent.DOM_VK_RETURN) ||
+ (!isMac && aEvent.keyCode == KeyEvent.DOM_VK_F2)) {
+ tree.startEditing(index, tree.columns.getLastColumn());
+ } else if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
+ (isMac && aEvent.shiftKey &&
+ aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE &&
+ gEngineView.isEngineSelectedAndRemovable())) {
+ // Delete and Shift+Backspace (Mac) removes selected engine.
+ Services.search.removeEngine(gEngineView.selectedEngine.originalEngine);
+ }
+ }
+ },
+
+ onRestoreDefaults: function() {
+ let num = gEngineView._engineStore.restoreDefaultEngines();
+ gEngineView.rowCountChanged(0, num);
+ gEngineView.invalidate();
+ },
+
+ showRestoreDefaults: function(aEnable) {
+ document.getElementById("restoreDefaultSearchEngines").disabled = !aEnable;
+ },
+
+ remove: function(aEngine) {
+ let index = gEngineView._engineStore.removeEngine(aEngine);
+ gEngineView.rowCountChanged(index, -1);
+ gEngineView.invalidate();
+ gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
+ gEngineView.ensureRowIsVisible(gEngineView.currentIndex);
+ document.getElementById("engineList").focus();
+ },
+
+ editKeyword: Task.async(function* (aEngine, aNewKeyword) {
+ let keyword = aNewKeyword.trim();
+ if (keyword) {
+ let eduplicate = false;
+ let dupName = "";
+
+ // Check for duplicates in Places keywords.
+ let bduplicate = !!(yield PlacesUtils.keywords.fetch(keyword));
+
+ // Check for duplicates in changes we haven't committed yet
+ let engines = gEngineView._engineStore.engines;
+ for (let engine of engines) {
+ if (engine.alias == keyword &&
+ engine.name != aEngine.name) {
+ eduplicate = true;
+ dupName = engine.name;
+ break;
+ }
+ }
+
+ // Notify the user if they have chosen an existing engine/bookmark keyword
+ if (eduplicate || bduplicate) {
+ let strings = document.getElementById("engineManagerBundle");
+ let dtitle = strings.getString("duplicateTitle");
+ let bmsg = strings.getString("duplicateBookmarkMsg");
+ let emsg = strings.getFormattedString("duplicateEngineMsg", [dupName]);
+
+ Services.prompt.alert(window, dtitle, eduplicate ? emsg : bmsg);
+ return false;
+ }
+ }
+
+ gEngineView._engineStore.changeEngine(aEngine, "alias", keyword);
+ gEngineView.invalidate();
+ return true;
+ }),
+
+ saveOneClickEnginesList: function () {
+ let hiddenList = [];
+ for (let engine of gEngineView._engineStore.engines) {
+ if (!engine.shown)
+ hiddenList.push(engine.name);
+ }
+ document.getElementById("browser.search.hiddenOneOffs").value =
+ hiddenList.join(",");
+ },
+
+ setDefaultEngine: function () {
+ Services.search.currentEngine =
+ document.getElementById("defaultEngine").selectedItem.engine;
+ }
+};
+
+function onDragEngineStart(event) {
+ var selectedIndex = gEngineView.selectedIndex;
+ var tree = document.getElementById("engineList");
+ var row = { }, col = { }, child = { };
+ tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, child);
+ if (selectedIndex >= 0 && !gEngineView.isCheckBox(row.value, col.value)) {
+ event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString());
+ event.dataTransfer.effectAllowed = "move";
+ }
+}
+
+
+function EngineStore() {
+ let pref = document.getElementById("browser.search.hiddenOneOffs").value;
+ this.hiddenList = pref ? pref.split(",") : [];
+
+ this._engines = Services.search.getVisibleEngines().map(this._cloneEngine, this);
+ this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine, this);
+
+ // check if we need to disable the restore defaults button
+ var someHidden = this._defaultEngines.some(e => e.hidden);
+ gSearchPane.showRestoreDefaults(someHidden);
+}
+EngineStore.prototype = {
+ _engines: null,
+ _defaultEngines: null,
+
+ get engines() {
+ return this._engines;
+ },
+ set engines(val) {
+ this._engines = val;
+ return val;
+ },
+
+ _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
+ return this._engines.indexOf(aEngine);
+ },
+
+ _getEngineByName: function ES_getEngineByName(aName) {
+ return this._engines.find(engine => engine.name == aName);
+ },
+
+ _cloneEngine: function ES_cloneEngine(aEngine) {
+ var clonedObj={};
+ for (var i in aEngine)
+ clonedObj[i] = aEngine[i];
+ clonedObj.originalEngine = aEngine;
+ clonedObj.shown = this.hiddenList.indexOf(clonedObj.name) == -1;
+ return clonedObj;
+ },
+
+ // Callback for Array's some(). A thisObj must be passed to some()
+ _isSameEngine: function ES_isSameEngine(aEngineClone) {
+ return aEngineClone.originalEngine == this.originalEngine;
+ },
+
+ addEngine: function ES_addEngine(aEngine) {
+ this._engines.push(this._cloneEngine(aEngine));
+ },
+
+ moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
+ if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
+ throw new Error("ES_moveEngine: invalid aNewIndex!");
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("ES_moveEngine: invalid engine?");
+
+ if (index == aNewIndex)
+ return; // nothing to do
+
+ // Move the engine in our internal store
+ var removedEngine = this._engines.splice(index, 1)[0];
+ this._engines.splice(aNewIndex, 0, removedEngine);
+
+ Services.search.moveEngine(aEngine.originalEngine, aNewIndex);
+ },
+
+ removeEngine: function ES_removeEngine(aEngine) {
+ if (this._engines.length == 1) {
+ throw new Error("Cannot remove last engine!");
+ }
+
+ let engineName = aEngine.name;
+ let index = this._engines.findIndex(element => element.name == engineName);
+
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ let removedEngine = this._engines.splice(index, 1)[0];
+
+ if (this._defaultEngines.some(this._isSameEngine, removedEngine))
+ gSearchPane.showRestoreDefaults(true);
+ gSearchPane.buildDefaultEngineDropDown();
+ return index;
+ },
+
+ restoreDefaultEngines: function ES_restoreDefaultEngines() {
+ var added = 0;
+
+ for (var i = 0; i < this._defaultEngines.length; ++i) {
+ var e = this._defaultEngines[i];
+
+ // If the engine is already in the list, just move it.
+ if (this._engines.some(this._isSameEngine, e)) {
+ this.moveEngine(this._getEngineByName(e.name), i);
+ } else {
+ // Otherwise, add it back to our internal store
+
+ // The search service removes the alias when an engine is hidden,
+ // so clear any alias we may have cached before unhiding the engine.
+ e.alias = "";
+
+ this._engines.splice(i, 0, e);
+ let engine = e.originalEngine;
+ engine.hidden = false;
+ Services.search.moveEngine(engine, i);
+ added++;
+ }
+ }
+ Services.search.resetToOriginalDefaultEngine();
+ gSearchPane.showRestoreDefaults(false);
+ gSearchPane.buildDefaultEngineDropDown();
+ return added;
+ },
+
+ changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ this._engines[index][aProp] = aNewValue;
+ aEngine.originalEngine[aProp] = aNewValue;
+ },
+
+ reloadIcons: function ES_reloadIcons() {
+ this._engines.forEach(function (e) {
+ e.uri = e.originalEngine.uri;
+ });
+ }
+};
+
+function EngineView(aEngineStore) {
+ this._engineStore = aEngineStore;
+}
+EngineView.prototype = {
+ _engineStore: null,
+ tree: null,
+
+ get lastIndex() {
+ return this.rowCount - 1;
+ },
+ get selectedIndex() {
+ var seln = this.selection;
+ if (seln.getRangeCount() > 0) {
+ var min = {};
+ seln.getRangeAt(0, min, {});
+ return min.value;
+ }
+ return -1;
+ },
+ get selectedEngine() {
+ return this._engineStore.engines[this.selectedIndex];
+ },
+
+ // Helpers
+ rowCountChanged: function (index, count) {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function () {
+ this.tree.invalidate();
+ },
+
+ ensureRowIsVisible: function (index) {
+ this.tree.ensureRowIsVisible(index);
+ },
+
+ getSourceIndexFromDrag: function (dataTransfer) {
+ return parseInt(dataTransfer.getData(ENGINE_FLAVOR));
+ },
+
+ isCheckBox: function(index, column) {
+ return column.id == "engineShown";
+ },
+
+ isEngineSelectedAndRemovable: function() {
+ return this.selectedIndex != -1 && this.lastIndex != 0;
+ },
+
+ // nsITreeView
+ get rowCount() {
+ return this._engineStore.engines.length;
+ },
+
+ getImageSrc: function(index, column) {
+ if (column.id == "engineName") {
+ if (this._engineStore.engines[index].iconURI)
+ return this._engineStore.engines[index].iconURI.spec;
+
+ if (window.devicePixelRatio > 1)
+ return "chrome://browser/skin/search-engine-placeholder@2x.png";
+ return "chrome://browser/skin/search-engine-placeholder.png";
+ }
+
+ return "";
+ },
+
+ getCellText: function(index, column) {
+ if (column.id == "engineName")
+ return this._engineStore.engines[index].name;
+ else if (column.id == "engineKeyword")
+ return this._engineStore.engines[index].alias;
+ return "";
+ },
+
+ setTree: function(tree) {
+ this.tree = tree;
+ },
+
+ canDrop: function(targetIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ return (sourceIndex != -1 &&
+ sourceIndex != targetIndex &&
+ sourceIndex != targetIndex + orientation);
+ },
+
+ drop: function(dropIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ var sourceEngine = this._engineStore.engines[sourceIndex];
+
+ const nsITreeView = Components.interfaces.nsITreeView;
+ if (dropIndex > sourceIndex) {
+ if (orientation == nsITreeView.DROP_BEFORE)
+ dropIndex--;
+ } else if (orientation == nsITreeView.DROP_AFTER) {
+ dropIndex++;
+ }
+
+ this._engineStore.moveEngine(sourceEngine, dropIndex);
+ gSearchPane.showRestoreDefaults(true);
+ gSearchPane.buildDefaultEngineDropDown();
+
+ // Redraw, and adjust selection
+ this.invalidate();
+ this.selection.select(dropIndex);
+ },
+
+ selection: null,
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(index, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isContainerEmpty: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function(index) { return false; },
+ getParentIndex: function(index) { return -1; },
+ hasNextSibling: function(parentIndex, index) { return false; },
+ getLevel: function(index) { return 0; },
+ getProgressMode: function(index, column) { },
+ getCellValue: function(index, column) {
+ if (column.id == "engineShown")
+ return this._engineStore.engines[index].shown;
+ return undefined;
+ },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(column) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(index, column) { return column.id != "engineName"; },
+ isSelectable: function(index, column) { return false; },
+ setCellValue: function(index, column, value) {
+ if (column.id == "engineShown") {
+ this._engineStore.engines[index].shown = value == "true";
+ gEngineView.invalidate();
+ gSearchPane.saveOneClickEnginesList();
+ }
+ },
+ setCellText: function(index, column, value) {
+ if (column.id == "engineKeyword") {
+ gSearchPane.editKeyword(this._engineStore.engines[index], value)
+ .then(valid => {
+ if (!valid)
+ document.getElementById("engineList").startEditing(index, column);
+ });
+ }
+ },
+ performAction: function(action) { },
+ performActionOnRow: function(action, index) { },
+ performActionOnCell: function(action, index, column) { }
+};
diff --git a/browser/components/preferences/in-content/search.xul b/browser/components/preferences/in-content/search.xul
new file mode 100644
index 000000000..95c7acd85
--- /dev/null
+++ b/browser/components/preferences/in-content/search.xul
@@ -0,0 +1,86 @@
+ <preferences id="searchPreferences" hidden="true" data-category="paneSearch">
+
+ <preference id="browser.search.suggest.enabled"
+ name="browser.search.suggest.enabled"
+ type="bool"/>
+
+ <preference id="browser.urlbar.suggest.searches"
+ name="browser.urlbar.suggest.searches"
+ type="bool"/>
+
+ <preference id="browser.search.hiddenOneOffs"
+ name="browser.search.hiddenOneOffs"
+ type="unichar"/>
+
+ </preferences>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/search.js"/>
+
+ <stringbundle id="engineManagerBundle" src="chrome://browser/locale/engineManager.properties"/>
+
+ <hbox id="header-search"
+ class="header"
+ hidden="true"
+ data-category="paneSearch">
+ <label class="header-name" flex="1">&paneSearch.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+ </hbox>
+
+ <!-- Default Search Engine -->
+ <groupbox id="defaultEngineGroup" align="start" data-category="paneSearch">
+ <caption label="&defaultSearchEngine.label;"/>
+ <label>&chooseYourDefaultSearchEngine.label;</label>
+ <menulist id="defaultEngine">
+ <menupopup/>
+ </menulist>
+ <checkbox id="suggestionsInSearchFieldsCheckbox"
+ label="&provideSearchSuggestions.label;"
+ accesskey="&provideSearchSuggestions.accesskey;"
+ preference="browser.search.suggest.enabled"/>
+ <vbox class="indent">
+ <checkbox id="urlBarSuggestion" label="&showURLBarSuggestions.label;"
+ accesskey="&showURLBarSuggestions.accesskey;"
+ preference="browser.urlbar.suggest.searches"/>
+ <hbox id="urlBarSuggestionPermanentPBLabel"
+ align="center" class="indent">
+ <label flex="1">&urlBarSuggestionsPermanentPB.label;</label>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <groupbox id="oneClickSearchProvidersGroup" data-category="paneSearch">
+ <caption label="&oneClickSearchEngines.label;"/>
+ <label>&chooseWhichOneToDisplay.label;</label>
+
+ <tree id="engineList" flex="1" rows="8" hidecolumnpicker="true" editable="true"
+ seltype="single">
+ <treechildren id="engineChildren" flex="1"/>
+ <treecols>
+ <treecol id="engineShown" type="checkbox" editable="true" sortable="false"/>
+ <treecol id="engineName" flex="4" label="&engineNameColumn.label;" sortable="false"/>
+ <treecol id="engineKeyword" flex="1" label="&engineKeywordColumn.label;" editable="true"
+ sortable="false"/>
+ </treecols>
+ </tree>
+
+ <hbox>
+ <button id="restoreDefaultSearchEngines"
+ label="&restoreDefaultSearchEngines.label;"
+ accesskey="&restoreDefaultSearchEngines.accesskey;"
+ />
+ <spacer flex="1"/>
+ <button id="removeEngineButton"
+ class="searchEngineAction"
+ label="&removeEngine.label;"
+ accesskey="&removeEngine.accesskey;"
+ disabled="true"
+ />
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox id="addEnginesBox" pack="start">
+ <label id="addEngines" class="text-link" value="&addMoreSearchEngines.label;"/>
+ </hbox>
+ </groupbox>
diff --git a/browser/components/preferences/in-content/security.js b/browser/components/preferences/in-content/security.js
new file mode 100644
index 000000000..a8ad28c7e
--- /dev/null
+++ b/browser/components/preferences/in-content/security.js
@@ -0,0 +1,302 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var gSecurityPane = {
+ _pane: null,
+
+ /**
+ * Initializes master password UI.
+ */
+ init: function ()
+ {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gSecurityPane));
+ }
+
+ this._pane = document.getElementById("paneSecurity");
+ this._initMasterPasswordUI();
+ this._initSafeBrowsing();
+
+ setEventListener("addonExceptions", "command",
+ gSecurityPane.showAddonExceptions);
+ setEventListener("passwordExceptions", "command",
+ gSecurityPane.showPasswordExceptions);
+ setEventListener("useMasterPassword", "command",
+ gSecurityPane.updateMasterPasswordButton);
+ setEventListener("changeMasterPassword", "command",
+ gSecurityPane.changeMasterPassword);
+ setEventListener("showPasswords", "command",
+ gSecurityPane.showPasswords);
+ },
+
+ // ADD-ONS
+
+ /*
+ * Preferences:
+ *
+ * xpinstall.whitelist.required
+ * - true if a site must be added to a site whitelist before extensions
+ * provided by the site may be installed from it, false if the extension
+ * may be directly installed after a confirmation dialog
+ */
+
+ /**
+ * Enables/disables the add-ons Exceptions button depending on whether
+ * or not add-on installation warnings are displayed.
+ */
+ readWarnAddonInstall: function ()
+ {
+ var warn = document.getElementById("xpinstall.whitelist.required");
+ var exceptions = document.getElementById("addonExceptions");
+
+ exceptions.disabled = !warn.value;
+
+ // don't override the preference value
+ return undefined;
+ },
+
+ /**
+ * Displays the exceptions lists for add-on installation warnings.
+ */
+ showAddonExceptions: function ()
+ {
+ var bundlePrefs = document.getElementById("bundlePreferences");
+
+ var params = this._addonParams;
+ if (!params.windowTitle || !params.introText) {
+ params.windowTitle = bundlePrefs.getString("addons_permissions_title");
+ params.introText = bundlePrefs.getString("addonspermissionstext");
+ }
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Parameters for the add-on install permissions dialog.
+ */
+ _addonParams:
+ {
+ blockVisible: false,
+ sessionVisible: false,
+ allowVisible: true,
+ prefilledHost: "",
+ permissionType: "install"
+ },
+
+ // PASSWORDS
+
+ /*
+ * Preferences:
+ *
+ * signon.rememberSignons
+ * - true if passwords are remembered, false otherwise
+ */
+
+ /**
+ * Enables/disables the Exceptions button used to configure sites where
+ * passwords are never saved. When browser is set to start in Private
+ * Browsing mode, the "Remember passwords" UI is useless, so we disable it.
+ */
+ readSavePasswords: function ()
+ {
+ var pref = document.getElementById("signon.rememberSignons");
+ var excepts = document.getElementById("passwordExceptions");
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.getElementById("savePasswords").disabled = true;
+ excepts.disabled = true;
+ return false;
+ }
+ excepts.disabled = !pref.value;
+ // don't override pref value in UI
+ return undefined;
+ },
+
+ /**
+ * Displays a dialog in which the user can view and modify the list of sites
+ * where passwords are never saved.
+ */
+ showPasswordExceptions: function ()
+ {
+ var bundlePrefs = document.getElementById("bundlePreferences");
+ var params = {
+ blockVisible: true,
+ sessionVisible: false,
+ allowVisible: false,
+ hideStatusColumn: true,
+ prefilledHost: "",
+ permissionType: "login-saving",
+ windowTitle: bundlePrefs.getString("savedLoginsExceptions_title"),
+ introText: bundlePrefs.getString("savedLoginsExceptions_desc")
+ };
+
+ gSubDialog.open("chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Initializes master password UI: the "use master password" checkbox, selects
+ * the master password button to show, and enables/disables it as necessary.
+ * The master password is controlled by various bits of NSS functionality, so
+ * the UI for it can't be controlled by the normal preference bindings.
+ */
+ _initMasterPasswordUI: function ()
+ {
+ var noMP = !LoginHelper.isMasterPasswordSet();
+
+ var button = document.getElementById("changeMasterPassword");
+ button.disabled = noMP;
+
+ var checkbox = document.getElementById("useMasterPassword");
+ checkbox.checked = !noMP;
+ },
+
+ _initSafeBrowsing() {
+ let enableSafeBrowsing = document.getElementById("enableSafeBrowsing");
+ let blockDownloads = document.getElementById("blockDownloads");
+ let blockUncommonUnwanted = document.getElementById("blockUncommonUnwanted");
+
+ let safeBrowsingPhishingPref = document.getElementById("browser.safebrowsing.phishing.enabled");
+ let safeBrowsingMalwarePref = document.getElementById("browser.safebrowsing.malware.enabled");
+
+ let blockDownloadsPref = document.getElementById("browser.safebrowsing.downloads.enabled");
+ let malwareTable = document.getElementById("urlclassifier.malwareTable");
+
+ let blockUnwantedPref = document.getElementById("browser.safebrowsing.downloads.remote.block_potentially_unwanted");
+ let blockUncommonPref = document.getElementById("browser.safebrowsing.downloads.remote.block_uncommon");
+
+ enableSafeBrowsing.addEventListener("command", function() {
+ safeBrowsingPhishingPref.value = enableSafeBrowsing.checked;
+ safeBrowsingMalwarePref.value = enableSafeBrowsing.checked;
+
+ if (enableSafeBrowsing.checked) {
+ blockDownloads.removeAttribute("disabled");
+ if (blockDownloads.checked) {
+ blockUncommonUnwanted.removeAttribute("disabled");
+ }
+ } else {
+ blockDownloads.setAttribute("disabled", "true");
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+ });
+
+ blockDownloads.addEventListener("command", function() {
+ blockDownloadsPref.value = blockDownloads.checked;
+ if (blockDownloads.checked) {
+ blockUncommonUnwanted.removeAttribute("disabled");
+ } else {
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+ });
+
+ blockUncommonUnwanted.addEventListener("command", function() {
+ blockUnwantedPref.value = blockUncommonUnwanted.checked;
+ blockUncommonPref.value = blockUncommonUnwanted.checked;
+
+ let malware = malwareTable.value
+ .split(",")
+ .filter(x => x !== "goog-unwanted-shavar" && x !== "test-unwanted-simple");
+
+ if (blockUncommonUnwanted.checked) {
+ malware.push("goog-unwanted-shavar");
+ malware.push("test-unwanted-simple");
+ }
+
+ // sort alphabetically to keep the pref consistent
+ malware.sort();
+
+ malwareTable.value = malware.join(",");
+ });
+
+ // set initial values
+
+ enableSafeBrowsing.checked = safeBrowsingPhishingPref.value && safeBrowsingMalwarePref.value;
+ if (!enableSafeBrowsing.checked) {
+ blockDownloads.setAttribute("disabled", "true");
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+
+ blockDownloads.checked = blockDownloadsPref.value;
+ if (!blockDownloadsPref.value) {
+ blockUncommonUnwanted.setAttribute("disabled", "true");
+ }
+
+ blockUncommonUnwanted.checked = blockUnwantedPref.value && blockUncommonPref.value;
+ },
+
+ /**
+ * Enables/disables the master password button depending on the state of the
+ * "use master password" checkbox, and prompts for master password removal if
+ * one is set.
+ */
+ updateMasterPasswordButton: function ()
+ {
+ var checkbox = document.getElementById("useMasterPassword");
+ var button = document.getElementById("changeMasterPassword");
+ button.disabled = !checkbox.checked;
+
+ // unchecking the checkbox should try to immediately remove the master
+ // password, because it's impossible to non-destructively remove the master
+ // password used to encrypt all the passwords without providing it (by
+ // design), and it would be extremely odd to pop up that dialog when the
+ // user closes the prefwindow and saves his settings
+ if (!checkbox.checked)
+ this._removeMasterPassword();
+ else
+ this.changeMasterPassword();
+
+ this._initMasterPasswordUI();
+ },
+
+ /**
+ * Displays the "remove master password" dialog to allow the user to remove
+ * the current master password. When the dialog is dismissed, master password
+ * UI is automatically updated.
+ */
+ _removeMasterPassword: function ()
+ {
+ var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+ getService(Ci.nsIPKCS11ModuleDB);
+ if (secmodDB.isFIPSEnabled) {
+ var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ var bundle = document.getElementById("bundlePreferences");
+ promptService.alert(window,
+ bundle.getString("pw_change_failed_title"),
+ bundle.getString("pw_change2empty_in_fips_mode"));
+ this._initMasterPasswordUI();
+ }
+ else {
+ gSubDialog.open("chrome://mozapps/content/preferences/removemp.xul",
+ null, null, this._initMasterPasswordUI.bind(this));
+ }
+ },
+
+ /**
+ * Displays a dialog in which the master password may be changed.
+ */
+ changeMasterPassword: function ()
+ {
+ gSubDialog.open("chrome://mozapps/content/preferences/changemp.xul",
+ "resizable=no", null, this._initMasterPasswordUI.bind(this));
+ },
+
+ /**
+ * Shows the sites where the user has saved passwords and the associated login
+ * information.
+ */
+ showPasswords: function ()
+ {
+ gSubDialog.open("chrome://passwordmgr/content/passwordManager.xul");
+ }
+
+};
diff --git a/browser/components/preferences/in-content/security.xul b/browser/components/preferences/in-content/security.xul
new file mode 100644
index 000000000..a10576c25
--- /dev/null
+++ b/browser/components/preferences/in-content/security.xul
@@ -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/.
+
+<!-- Security panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/security.js"/>
+
+<preferences id="securityPreferences" hidden="true" data-category="paneSecurity">
+ <!-- XXX buttons -->
+ <preference id="pref.privacy.disable_button.view_passwords"
+ name="pref.privacy.disable_button.view_passwords"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.view_passwords_exceptions"
+ name="pref.privacy.disable_button.view_passwords_exceptions"
+ type="bool"/>
+
+ <!-- Add-ons, malware, phishing -->
+ <preference id="xpinstall.whitelist.required"
+ name="xpinstall.whitelist.required"
+ type="bool"/>
+
+ <preference id="browser.safebrowsing.malware.enabled"
+ name="browser.safebrowsing.malware.enabled"
+ type="bool"/>
+ <preference id="browser.safebrowsing.phishing.enabled"
+ name="browser.safebrowsing.phishing.enabled"
+ type="bool"/>
+
+ <preference id="browser.safebrowsing.downloads.enabled"
+ name="browser.safebrowsing.downloads.enabled"
+ type="bool"/>
+
+ <preference id="urlclassifier.malwareTable"
+ name="urlclassifier.malwareTable"
+ type="string"/>
+
+ <preference id="browser.safebrowsing.downloads.remote.block_potentially_unwanted"
+ name="browser.safebrowsing.downloads.remote.block_potentially_unwanted"
+ type="bool"/>
+ <preference id="browser.safebrowsing.downloads.remote.block_uncommon"
+ name="browser.safebrowsing.downloads.remote.block_uncommon"
+ type="bool"/>
+
+ <!-- Passwords -->
+ <preference id="signon.rememberSignons" name="signon.rememberSignons" type="bool"/>
+
+</preferences>
+
+<hbox id="header-security"
+ class="header"
+ hidden="true"
+ data-category="paneSecurity">
+ <label class="header-name" flex="1">&paneSecurity.title;</label>
+ <html:a class="help-button" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<!-- addons, forgery (phishing) UI -->
+<groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true">
+ <caption><label>&general.label;</label></caption>
+
+ <hbox id="addonInstallBox">
+ <checkbox id="warnAddonInstall"
+ label="&warnAddonInstall.label;"
+ accesskey="&warnAddonInstall.accesskey;"
+ preference="xpinstall.whitelist.required"
+ onsyncfrompreference="return gSecurityPane.readWarnAddonInstall();"/>
+ <spacer flex="1"/>
+ <button id="addonExceptions"
+ label="&addonExceptions.label;"
+ accesskey="&addonExceptions.accesskey;"/>
+ </hbox>
+
+ <separator class="thin"/>
+ <vbox align="start">
+ <checkbox id="enableSafeBrowsing"
+ label="&enableSafeBrowsing.label;"
+ accesskey="&enableSafeBrowsing.accesskey;" />
+ <vbox class="indent">
+ <checkbox id="blockDownloads"
+ label="&blockDownloads.label;"
+ accesskey="&blockDownloads.accesskey;" />
+ <checkbox id="blockUncommonUnwanted"
+ label="&blockUncommonUnwanted.label;"
+ accesskey="&blockUncommonUnwanted.accesskey;" />
+ </vbox>
+ </vbox>
+</groupbox>
+
+<!-- Passwords -->
+<groupbox id="passwordsGroup" orient="vertical" data-category="paneSecurity" hidden="true">
+ <caption><label>&logins.label;</label></caption>
+
+ <hbox id="savePasswordsBox">
+ <checkbox id="savePasswords"
+ label="&rememberLogins.label;" accesskey="&rememberLogins.accesskey;"
+ preference="signon.rememberSignons"
+ onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
+ <spacer flex="1"/>
+ <button id="passwordExceptions"
+ label="&passwordExceptions.label;"
+ accesskey="&passwordExceptions.accesskey;"
+ preference="pref.privacy.disable_button.view_passwords_exceptions"/>
+ </hbox>
+ <grid id="passwordGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="passwordRows">
+ <row id="masterPasswordRow">
+ <hbox id="masterPasswordBox">
+ <checkbox id="useMasterPassword"
+ label="&useMasterPassword.label;"
+ accesskey="&useMasterPassword.accesskey;"/>
+ <spacer flex="1"/>
+ </hbox>
+ <button id="changeMasterPassword"
+ label="&changeMasterPassword.label;"
+ accesskey="&changeMasterPassword.accesskey;"/>
+ </row>
+ <row id="showPasswordRow">
+ <hbox id="showPasswordsBox"/>
+ <button id="showPasswords"
+ label="&savedLogins.label;" accesskey="&savedLogins.accesskey;"
+ preference="pref.privacy.disable_button.view_passwords"/>
+ </row>
+ </rows>
+ </grid>
+</groupbox>
diff --git a/browser/components/preferences/in-content/subdialogs.js b/browser/components/preferences/in-content/subdialogs.js
new file mode 100644
index 000000000..bb8d0048f
--- /dev/null
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -0,0 +1,434 @@
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If 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 gSubDialog = {
+ _closingCallback: null,
+ _closingEvent: null,
+ _isClosing: false,
+ _frame: null,
+ _overlay: null,
+ _box: null,
+ _injectedStyleSheets: [
+ "chrome://browser/skin/preferences/preferences.css",
+ "chrome://global/skin/in-content/common.css",
+ "chrome://browser/skin/preferences/in-content/preferences.css",
+ "chrome://browser/skin/preferences/in-content/dialog.css",
+ ],
+ _resizeObserver: null,
+
+ init: function() {
+ this._frame = document.getElementById("dialogFrame");
+ this._overlay = document.getElementById("dialogOverlay");
+ this._box = document.getElementById("dialogBox");
+ this._closeButton = document.getElementById("dialogClose");
+ },
+
+ updateTitle: function(aEvent) {
+ if (aEvent.target != gSubDialog._frame.contentDocument)
+ return;
+ document.getElementById("dialogTitle").textContent = gSubDialog._frame.contentDocument.title;
+ },
+
+ injectXMLStylesheet: function(aStylesheetURL) {
+ let contentStylesheet = this._frame.contentDocument.createProcessingInstruction(
+ 'xml-stylesheet',
+ 'href="' + aStylesheetURL + '" type="text/css"'
+ );
+ this._frame.contentDocument.insertBefore(contentStylesheet,
+ this._frame.contentDocument.documentElement);
+ },
+
+ open: function(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
+ // If we're already open/opening on this URL, do nothing.
+ if (this._openedURL == aURL && !this._isClosing) {
+ return;
+ }
+ // If we're open on some (other) URL or we're closing, open when closing has finished.
+ if (this._openedURL || this._isClosing) {
+ if (!this._isClosing) {
+ this.close();
+ }
+ let args = Array.from(arguments);
+ this._closingPromise.then(() => {
+ this.open.apply(this, args);
+ });
+ return;
+ }
+ this._addDialogEventListeners();
+
+ let features = (aFeatures ? aFeatures + "," : "") + "resizable,dialog=no,centerscreen";
+ let dialog = window.openDialog(aURL, "dialogFrame", features, aParams);
+ if (aClosingCallback) {
+ this._closingCallback = aClosingCallback.bind(dialog);
+ }
+
+ this._closingEvent = null;
+ this._isClosing = false;
+ this._openedURL = aURL;
+
+ features = features.replace(/,/g, "&");
+ let featureParams = new URLSearchParams(features.toLowerCase());
+ this._box.setAttribute("resizable", featureParams.has("resizable") &&
+ featureParams.get("resizable") != "no" &&
+ featureParams.get("resizable") != "0");
+ },
+
+ close: function(aEvent = null) {
+ if (this._isClosing) {
+ return;
+ }
+ this._isClosing = true;
+ this._closingPromise = new Promise(resolve => {
+ this._resolveClosePromise = resolve;
+ });
+
+ if (this._closingCallback) {
+ try {
+ this._closingCallback.call(null, aEvent);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ this._closingCallback = null;
+ }
+
+ this._removeDialogEventListeners();
+
+ this._overlay.style.visibility = "";
+ // Clear the sizing inline styles.
+ this._frame.removeAttribute("style");
+ // Clear the sizing attributes
+ this._box.removeAttribute("width");
+ this._box.removeAttribute("height");
+ this._box.style.removeProperty("min-height");
+ this._box.style.removeProperty("min-width");
+
+ setTimeout(() => {
+ // Unload the dialog after the event listeners run so that the load of about:blank isn't
+ // cancelled by the ESC <key>.
+ let onBlankLoad = e => {
+ if (this._frame.contentWindow.location.href == "about:blank") {
+ this._frame.removeEventListener("load", onBlankLoad);
+ // We're now officially done closing, so update the state to reflect that.
+ delete this._openedURL;
+ this._isClosing = false;
+ this._resolveClosePromise();
+ }
+ };
+ this._frame.addEventListener("load", onBlankLoad);
+ this._frame.loadURI("about:blank");
+ }, 0);
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "command":
+ this._frame.contentWindow.close();
+ break;
+ case "dialogclosing":
+ this._onDialogClosing(aEvent);
+ break;
+ case "DOMTitleChanged":
+ this.updateTitle(aEvent);
+ break;
+ case "DOMFrameContentLoaded":
+ this._onContentLoaded(aEvent);
+ break;
+ case "load":
+ this._onLoad(aEvent);
+ break;
+ case "unload":
+ this._onUnload(aEvent);
+ break;
+ case "keydown":
+ this._onKeyDown(aEvent);
+ break;
+ case "focus":
+ this._onParentWinFocus(aEvent);
+ break;
+ }
+ },
+
+ /* Private methods */
+
+ _onUnload: function(aEvent) {
+ if (aEvent.target.location.href == this._openedURL) {
+ this._frame.contentWindow.close();
+ }
+ },
+
+ _onContentLoaded: function(aEvent) {
+ if (aEvent.target != this._frame || aEvent.target.contentWindow.location == "about:blank") {
+ return;
+ }
+
+ for (let styleSheetURL of this._injectedStyleSheets) {
+ this.injectXMLStylesheet(styleSheetURL);
+ }
+
+ // Provide the ability for the dialog to know that it is being loaded "in-content".
+ this._frame.contentDocument.documentElement.setAttribute("subdialog", "true");
+
+ this._frame.contentWindow.addEventListener("dialogclosing", this);
+
+ let oldResizeBy = this._frame.contentWindow.resizeBy;
+ this._frame.contentWindow.resizeBy = function(resizeByWidth, resizeByHeight) {
+ // Only handle resizeByHeight currently.
+ let frameHeight = gSubDialog._frame.clientHeight;
+ let boxMinHeight = parseFloat(getComputedStyle(gSubDialog._box).minHeight, 10);
+
+ gSubDialog._frame.style.height = (frameHeight + resizeByHeight) + "px";
+ gSubDialog._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
+
+ oldResizeBy.call(gSubDialog._frame.contentWindow, resizeByWidth, resizeByHeight);
+ };
+
+ // Make window.close calls work like dialog closing.
+ let oldClose = this._frame.contentWindow.close;
+ this._frame.contentWindow.close = function() {
+ var closingEvent = gSubDialog._closingEvent;
+ if (!closingEvent) {
+ closingEvent = new CustomEvent("dialogclosing", {
+ bubbles: true,
+ detail: { button: null },
+ });
+
+ gSubDialog._frame.contentWindow.dispatchEvent(closingEvent);
+ }
+
+ gSubDialog.close(closingEvent);
+ oldClose.call(gSubDialog._frame.contentWindow);
+ };
+
+ // XXX: Hack to make focus during the dialog's load functions work. Make the element visible
+ // sooner in DOMContentLoaded but mostly invisible instead of changing visibility just before
+ // the dialog's load event.
+ this._overlay.style.visibility = "visible";
+ this._overlay.style.opacity = "0.01";
+ },
+
+ _onLoad: function(aEvent) {
+ if (aEvent.target.contentWindow.location == "about:blank") {
+ return;
+ }
+
+ // Do this on load to wait for the CSS to load and apply before calculating the size.
+ let docEl = this._frame.contentDocument.documentElement;
+
+ let groupBoxTitle = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-title");
+ let groupBoxTitleHeight = groupBoxTitle.clientHeight +
+ parseFloat(getComputedStyle(groupBoxTitle).borderBottomWidth);
+
+ let groupBoxBody = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-body");
+ // These are deduced from styles which we don't change, so it's safe to get them now:
+ let boxVerticalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingTop);
+ let boxHorizontalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingLeft);
+ let boxHorizontalBorder = 2 * parseFloat(getComputedStyle(this._box).borderLeftWidth);
+ let boxVerticalBorder = 2 * parseFloat(getComputedStyle(this._box).borderTopWidth);
+
+ // The difference between the frame and box shouldn't change, either:
+ let boxRect = this._box.getBoundingClientRect();
+ let frameRect = this._frame.getBoundingClientRect();
+ let frameSizeDifference = (frameRect.top - boxRect.top) + (boxRect.bottom - frameRect.bottom);
+
+ // Then determine and set a bunch of width stuff:
+ let frameMinWidth = docEl.style.width || docEl.scrollWidth + "px";
+ let frameWidth = docEl.getAttribute("width") ? docEl.getAttribute("width") + "px" :
+ frameMinWidth;
+ this._frame.style.width = frameWidth;
+ this._box.style.minWidth = "calc(" +
+ (boxHorizontalBorder + boxHorizontalPadding) +
+ "px + " + frameMinWidth + ")";
+
+ // Now do the same but for the height. We need to do this afterwards because otherwise
+ // XUL assumes we'll optimize for height and gives us "wrong" values which then are no
+ // longer correct after we set the width:
+ let frameMinHeight = docEl.style.height || docEl.scrollHeight + "px";
+ let frameHeight = docEl.getAttribute("height") ? docEl.getAttribute("height") + "px" :
+ frameMinHeight;
+
+ // Now check if the frame height we calculated is possible at this window size,
+ // accounting for titlebar, padding/border and some spacing.
+ let maxHeight = window.innerHeight - frameSizeDifference - 30;
+ // Do this with a frame height in pixels...
+ let comparisonFrameHeight;
+ if (frameHeight.endsWith("em")) {
+ let fontSize = parseFloat(getComputedStyle(this._frame).fontSize);
+ comparisonFrameHeight = parseFloat(frameHeight, 10) * fontSize;
+ } else if (frameHeight.endsWith("px")) {
+ comparisonFrameHeight = parseFloat(frameHeight, 10);
+ } else {
+ Cu.reportError("This dialog (" + this._frame.contentWindow.location.href + ") " +
+ "set a height in non-px-non-em units ('" + frameHeight + "'), " +
+ "which is likely to lead to bad sizing in in-content preferences. " +
+ "Please consider changing this.");
+ comparisonFrameHeight = parseFloat(frameHeight);
+ }
+
+ if (comparisonFrameHeight > maxHeight) {
+ // If the height is bigger than that of the window, we should let the contents scroll:
+ frameHeight = maxHeight + "px";
+ frameMinHeight = maxHeight + "px";
+ let containers = this._frame.contentDocument.querySelectorAll('.largeDialogContainer');
+ for (let container of containers) {
+ container.classList.add("doScroll");
+ }
+ }
+
+ this._frame.style.height = frameHeight;
+ this._box.style.minHeight = "calc(" +
+ (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
+ "px + " + frameMinHeight + ")";
+
+ this._overlay.style.visibility = "visible";
+ this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
+
+ if (this._box.getAttribute("resizable") == "true") {
+ this._resizeObserver = new MutationObserver(this._onResize);
+ this._resizeObserver.observe(this._box, {attributes: true});
+ }
+
+ this._trapFocus();
+ },
+
+ _onResize: function(mutations) {
+ let frame = gSubDialog._frame;
+ // The width and height styles are needed for the initial
+ // layout of the frame, but afterward they need to be removed
+ // or their presence will restrict the contents of the <browser>
+ // from resizing to a smaller size.
+ frame.style.removeProperty("width");
+ frame.style.removeProperty("height");
+
+ let docEl = frame.contentDocument.documentElement;
+ let persistedAttributes = docEl.getAttribute("persist");
+ if (!persistedAttributes ||
+ (!persistedAttributes.includes("width") &&
+ !persistedAttributes.includes("height"))) {
+ return;
+ }
+
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "width") {
+ docEl.setAttribute("width", docEl.scrollWidth);
+ } else if (mutation.attributeName == "height") {
+ docEl.setAttribute("height", docEl.scrollHeight);
+ }
+ }
+ },
+
+ _onDialogClosing: function(aEvent) {
+ this._frame.contentWindow.removeEventListener("dialogclosing", this);
+ this._closingEvent = aEvent;
+ },
+
+ _onKeyDown: function(aEvent) {
+ if (aEvent.currentTarget == window && aEvent.keyCode == aEvent.DOM_VK_ESCAPE &&
+ !aEvent.defaultPrevented) {
+ this.close(aEvent);
+ return;
+ }
+ if (aEvent.keyCode != aEvent.DOM_VK_TAB ||
+ aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey) {
+ return;
+ }
+
+ let fm = Services.focus;
+
+ function isLastFocusableElement(el) {
+ // XXXgijs unfortunately there is no way to get the last focusable element without asking
+ // the focus manager to move focus to it.
+ let rv = el == fm.moveFocus(gSubDialog._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
+ fm.setFocus(el, 0);
+ return rv;
+ }
+
+ let forward = !aEvent.shiftKey;
+ // check if focus is leaving the frame (incl. the close button):
+ if ((aEvent.target == this._closeButton && !forward) ||
+ (isLastFocusableElement(aEvent.originalTarget) && forward)) {
+ aEvent.preventDefault();
+ aEvent.stopImmediatePropagation();
+ let parentWin = this._getBrowser().ownerGlobal;
+ if (forward) {
+ fm.moveFocus(parentWin, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
+ } else {
+ // Somehow, moving back 'past' the opening doc is not trivial. Cheat by doing it in 2 steps:
+ fm.moveFocus(window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
+ fm.moveFocus(parentWin, null, fm.MOVEFOCUS_BACKWARD, fm.FLAG_BYKEY);
+ }
+ }
+ },
+
+ _onParentWinFocus: function(aEvent) {
+ // Explicitly check for the focus target of |window| to avoid triggering this when the window
+ // is refocused
+ if (aEvent.target != this._closeButton && aEvent.target != window) {
+ this._closeButton.focus();
+ }
+ },
+
+ _addDialogEventListeners: function() {
+ // Make the close button work.
+ this._closeButton.addEventListener("command", this);
+
+ // DOMTitleChanged isn't fired on the frame, only on the chromeEventHandler
+ let chromeBrowser = this._getBrowser();
+ chromeBrowser.addEventListener("DOMTitleChanged", this, true);
+
+ // Similarly DOMFrameContentLoaded only fires on the top window
+ window.addEventListener("DOMFrameContentLoaded", this, true);
+
+ // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
+ // otherwise there is a flicker of the stylesheet applying.
+ this._frame.addEventListener("load", this);
+
+ chromeBrowser.addEventListener("unload", this, true);
+ // Ensure we get <esc> keypresses even if nothing in the subdialog is focusable
+ // (happens on OS X when only text inputs and lists are focusable, and
+ // the subdialog only has checkboxes/radiobuttons/buttons)
+ window.addEventListener("keydown", this, true);
+ },
+
+ _removeDialogEventListeners: function() {
+ let chromeBrowser = this._getBrowser();
+ chromeBrowser.removeEventListener("DOMTitleChanged", this, true);
+ chromeBrowser.removeEventListener("unload", this, true);
+
+ this._closeButton.removeEventListener("command", this);
+
+ window.removeEventListener("DOMFrameContentLoaded", this, true);
+ this._frame.removeEventListener("load", this);
+ this._frame.contentWindow.removeEventListener("dialogclosing", this);
+ window.removeEventListener("keydown", this, true);
+ if (this._resizeObserver) {
+ this._resizeObserver.disconnect();
+ this._resizeObserver = null;
+ }
+ this._untrapFocus();
+ },
+
+ _trapFocus: function() {
+ let fm = Services.focus;
+ fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_FIRST, 0);
+ this._frame.contentDocument.addEventListener("keydown", this, true);
+ this._closeButton.addEventListener("keydown", this);
+
+ window.addEventListener("focus", this, true);
+ },
+
+ _untrapFocus: function() {
+ this._frame.contentDocument.removeEventListener("keydown", this, true);
+ this._closeButton.removeEventListener("keydown", this);
+ window.removeEventListener("focus", this);
+ },
+
+ _getBrowser: function() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ },
+};
diff --git a/browser/components/preferences/in-content/sync.js b/browser/components/preferences/in-content/sync.js
new file mode 100644
index 000000000..27f7cd48c
--- /dev/null
+++ b/browser/components/preferences/in-content/sync.js
@@ -0,0 +1,673 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
+ return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+
+const PAGE_NO_ACCOUNT = 0;
+const PAGE_HAS_ACCOUNT = 1;
+const PAGE_NEEDS_UPDATE = 2;
+const FXA_PAGE_LOGGED_OUT = 3;
+const FXA_PAGE_LOGGED_IN = 4;
+
+// Indexes into the "login status" deck.
+// We are in a successful verified state - everything should work!
+const FXA_LOGIN_VERIFIED = 0;
+// We have logged in to an unverified account.
+const FXA_LOGIN_UNVERIFIED = 1;
+// We are logged in locally, but the server rejected our credentials.
+const FXA_LOGIN_FAILED = 2;
+
+var gSyncPane = {
+ prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
+ "engine.tabs", "engine.history"],
+
+ get page() {
+ return document.getElementById("weavePrefsDeck").selectedIndex;
+ },
+
+ set page(val) {
+ document.getElementById("weavePrefsDeck").selectedIndex = val;
+ },
+
+ get _usingCustomServer() {
+ return Weave.Svc.Prefs.isSet("serverURL");
+ },
+
+ needsUpdate: function () {
+ this.page = PAGE_NEEDS_UPDATE;
+ let label = document.getElementById("loginError");
+ label.textContent = Weave.Utils.getErrorString(Weave.Status.login);
+ label.className = "error";
+ },
+
+ init: function () {
+ this._setupEventListeners();
+
+ // If the Service hasn't finished initializing, wait for it.
+ let xps = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+
+ if (xps.ready) {
+ this._init();
+ return;
+ }
+
+ // it may take some time before we can determine what provider to use
+ // and the state of that provider, so show the "please wait" page.
+ this._showLoadPage(xps);
+
+ let onUnload = function () {
+ window.removeEventListener("unload", onUnload, false);
+ try {
+ Services.obs.removeObserver(onReady, "weave:service:ready");
+ } catch (e) {}
+ };
+
+ let onReady = function () {
+ Services.obs.removeObserver(onReady, "weave:service:ready");
+ window.removeEventListener("unload", onUnload, false);
+ this._init();
+ }.bind(this);
+
+ Services.obs.addObserver(onReady, "weave:service:ready", false);
+ window.addEventListener("unload", onUnload, false);
+
+ xps.ensureLoaded();
+ },
+
+ _showLoadPage: function (xps) {
+ let username;
+ try {
+ username = Services.prefs.getCharPref("services.sync.username");
+ } catch (e) {}
+ if (!username) {
+ this.page = FXA_PAGE_LOGGED_OUT;
+ } else if (xps.fxAccountsEnabled) {
+ // Use cached values while we wait for the up-to-date values
+ let cachedComputerName;
+ try {
+ cachedComputerName = Services.prefs.getCharPref("services.sync.client.name");
+ }
+ catch (e) {
+ cachedComputerName = "";
+ }
+ document.getElementById("fxaEmailAddress1").textContent = username;
+ this._populateComputerName(cachedComputerName);
+ this.page = FXA_PAGE_LOGGED_IN;
+ } else { // Old Sync
+ this.page = PAGE_HAS_ACCOUNT;
+ }
+ },
+
+ _init: function () {
+ let topics = ["weave:service:login:error",
+ "weave:service:login:finish",
+ "weave:service:start-over:finish",
+ "weave:service:setup-complete",
+ "weave:service:logout:finish",
+ FxAccountsCommon.ONVERIFIED_NOTIFICATION,
+ FxAccountsCommon.ONLOGIN_NOTIFICATION,
+ FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION,
+ ];
+ // Add the observers now and remove them on unload
+ // XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
+ // of `this`. Fix in a followup. (bug 583347)
+ topics.forEach(function (topic) {
+ Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this);
+ }, this);
+
+ window.addEventListener("unload", function() {
+ topics.forEach(function (topic) {
+ Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
+ }, gSyncPane);
+ }, false);
+
+ XPCOMUtils.defineLazyGetter(this, '_stringBundle', () => {
+ return Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
+ });
+
+ XPCOMUtils.defineLazyGetter(this, '_accountsStringBundle', () => {
+ return Services.strings.createBundle("chrome://browser/locale/accounts.properties");
+ });
+
+ let url = Services.prefs.getCharPref("identity.mobilepromo.android") + "sync-preferences";
+ document.getElementById("fxaMobilePromo-android").setAttribute("href", url);
+ document.getElementById("fxaMobilePromo-android-hasFxaAccount").setAttribute("href", url);
+ url = Services.prefs.getCharPref("identity.mobilepromo.ios") + "sync-preferences";
+ document.getElementById("fxaMobilePromo-ios").setAttribute("href", url);
+ document.getElementById("fxaMobilePromo-ios-hasFxaAccount").setAttribute("href", url);
+
+ document.getElementById("tosPP-small-ToS").setAttribute("href", gSyncUtils.tosURL);
+ document.getElementById("tosPP-normal-ToS").setAttribute("href", gSyncUtils.tosURL);
+ document.getElementById("tosPP-small-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
+ document.getElementById("tosPP-normal-PP").setAttribute("href", gSyncUtils.privacyPolicyURL);
+
+ fxAccounts.promiseAccountsManageURI(this._getEntryPoint()).then(url => {
+ document.getElementById("verifiedManage").setAttribute("href", url);
+ });
+
+ this.updateWeavePrefs();
+
+ this._initProfileImageUI();
+ },
+
+ _toggleComputerNameControls: function(editMode) {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ textbox.disabled = !editMode;
+ document.getElementById("fxaChangeDeviceName").hidden = editMode;
+ document.getElementById("fxaCancelChangeDeviceName").hidden = !editMode;
+ document.getElementById("fxaSaveChangeDeviceName").hidden = !editMode;
+ },
+
+ _focusComputerNameTextbox: function() {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ let valLength = textbox.value.length;
+ textbox.focus();
+ textbox.setSelectionRange(valLength, valLength);
+ },
+
+ _blurComputerNameTextbox: function() {
+ document.getElementById("fxaSyncComputerName").blur();
+ },
+
+ _focusAfterComputerNameTextbox: function() {
+ // Focus the most appropriate element that's *not* the "computer name" box.
+ Services.focus.moveFocus(window,
+ document.getElementById("fxaSyncComputerName"),
+ Services.focus.MOVEFOCUS_FORWARD, 0);
+ },
+
+ _updateComputerNameValue: function(save) {
+ if (save) {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ Weave.Service.clientsEngine.localName = textbox.value;
+ }
+ this._populateComputerName(Weave.Service.clientsEngine.localName);
+ },
+
+ _setupEventListeners: function() {
+ function setEventListener(aId, aEventType, aCallback)
+ {
+ document.getElementById(aId)
+ .addEventListener(aEventType, aCallback.bind(gSyncPane));
+ }
+
+ setEventListener("noAccountSetup", "click", function (aEvent) {
+ aEvent.stopPropagation();
+ gSyncPane.openSetup(null);
+ });
+ setEventListener("noAccountPair", "click", function (aEvent) {
+ aEvent.stopPropagation();
+ gSyncPane.openSetup('pair');
+ });
+ setEventListener("syncChangePassword", "command",
+ () => gSyncUtils.changePassword());
+ setEventListener("syncResetPassphrase", "command",
+ () => gSyncUtils.resetPassphrase());
+ setEventListener("syncReset", "command", gSyncPane.resetSync);
+ setEventListener("syncAddDeviceLabel", "click", function () {
+ gSyncPane.openAddDevice();
+ return false;
+ });
+ setEventListener("syncEnginesList", "select", function () {
+ if (this.selectedCount)
+ this.clearSelection();
+ });
+ setEventListener("syncComputerName", "change", function (e) {
+ gSyncUtils.changeName(e.target);
+ });
+ setEventListener("fxaChangeDeviceName", "command", function () {
+ this._toggleComputerNameControls(true);
+ this._focusComputerNameTextbox();
+ });
+ setEventListener("fxaCancelChangeDeviceName", "command", function () {
+ // We explicitly blur the textbox because of bug 75324, then after
+ // changing the state of the buttons, force focus to whatever the focus
+ // manager thinks should be next (which on the mac, depends on an OSX
+ // keyboard access preference)
+ this._blurComputerNameTextbox();
+ this._toggleComputerNameControls(false);
+ this._updateComputerNameValue(false);
+ this._focusAfterComputerNameTextbox();
+ });
+ setEventListener("fxaSaveChangeDeviceName", "command", function () {
+ // Work around bug 75324 - see above.
+ this._blurComputerNameTextbox();
+ this._toggleComputerNameControls(false);
+ this._updateComputerNameValue(true);
+ this._focusAfterComputerNameTextbox();
+ });
+ setEventListener("unlinkDevice", "click", function () {
+ gSyncPane.startOver(true);
+ return false;
+ });
+ setEventListener("loginErrorUpdatePass", "click", function () {
+ gSyncPane.updatePass();
+ return false;
+ });
+ setEventListener("loginErrorResetPass", "click", function () {
+ gSyncPane.resetPass();
+ return false;
+ });
+ setEventListener("loginErrorStartOver", "click", function () {
+ gSyncPane.startOver(true);
+ return false;
+ });
+ setEventListener("noFxaSignUp", "command", function () {
+ gSyncPane.signUp();
+ return false;
+ });
+ setEventListener("noFxaSignIn", "command", function () {
+ gSyncPane.signIn();
+ return false;
+ });
+ setEventListener("fxaUnlinkButton", "command", function () {
+ gSyncPane.unlinkFirefoxAccount(true);
+ });
+ setEventListener("verifyFxaAccount", "command",
+ gSyncPane.verifyFirefoxAccount);
+ setEventListener("unverifiedUnlinkFxaAccount", "command", function () {
+ /* no warning as account can't have previously synced */
+ gSyncPane.unlinkFirefoxAccount(false);
+ });
+ setEventListener("rejectReSignIn", "command",
+ gSyncPane.reSignIn);
+ setEventListener("rejectUnlinkFxaAccount", "command", function () {
+ gSyncPane.unlinkFirefoxAccount(true);
+ });
+ setEventListener("fxaSyncComputerName", "keypress", function (e) {
+ if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
+ document.getElementById("fxaSaveChangeDeviceName").click();
+ } else if (e.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+ document.getElementById("fxaCancelChangeDeviceName").click();
+ }
+ });
+ },
+
+ _initProfileImageUI: function () {
+ try {
+ if (Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled")) {
+ document.getElementById("fxaProfileImage").hidden = false;
+ }
+ } catch (e) { }
+ },
+
+ updateWeavePrefs: function () {
+ let service = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ // service.fxAccountsEnabled is false iff sync is already configured for
+ // the legacy provider.
+ if (service.fxAccountsEnabled) {
+ let displayNameLabel = document.getElementById("fxaDisplayName");
+ let fxaEmailAddress1Label = document.getElementById("fxaEmailAddress1");
+ fxaEmailAddress1Label.hidden = false;
+ displayNameLabel.hidden = true;
+
+ let profileInfoEnabled;
+ try {
+ profileInfoEnabled = Services.prefs.getBoolPref("identity.fxaccounts.profile_image.enabled");
+ } catch (ex) {}
+
+ // determine the fxa status...
+ this._showLoadPage(service);
+
+ fxAccounts.getSignedInUser().then(data => {
+ if (!data) {
+ this.page = FXA_PAGE_LOGGED_OUT;
+ return false;
+ }
+ this.page = FXA_PAGE_LOGGED_IN;
+ // We are logged in locally, but maybe we are in a state where the
+ // server rejected our credentials (eg, password changed on the server)
+ let fxaLoginStatus = document.getElementById("fxaLoginStatus");
+ let syncReady;
+ // Not Verfied implies login error state, so check that first.
+ if (!data.verified) {
+ fxaLoginStatus.selectedIndex = FXA_LOGIN_UNVERIFIED;
+ syncReady = false;
+ // So we think we are logged in, so login problems are next.
+ // (Although if the Sync identity manager is still initializing, we
+ // ignore login errors and assume all will eventually be good.)
+ // LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
+ // All other login failures are assumed to be transient and should go
+ // away by themselves, so aren't reflected here.
+ } else if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+ fxaLoginStatus.selectedIndex = FXA_LOGIN_FAILED;
+ syncReady = false;
+ // Else we must be golden (or in an error state we expect to magically
+ // resolve itself)
+ } else {
+ fxaLoginStatus.selectedIndex = FXA_LOGIN_VERIFIED;
+ syncReady = true;
+ }
+ fxaEmailAddress1Label.textContent = data.email;
+ document.getElementById("fxaEmailAddress2").textContent = data.email;
+ document.getElementById("fxaEmailAddress3").textContent = data.email;
+ this._populateComputerName(Weave.Service.clientsEngine.localName);
+ let engines = document.getElementById("fxaSyncEngines")
+ for (let checkbox of engines.querySelectorAll("checkbox")) {
+ checkbox.disabled = !syncReady;
+ }
+ document.getElementById("fxaChangeDeviceName").disabled = !syncReady;
+
+ // Clear the profile image (if any) of the previously logged in account.
+ document.getElementById("fxaProfileImage").style.removeProperty("list-style-image");
+
+ // If the account is verified the next promise in the chain will
+ // fetch profile data.
+ return data.verified;
+ }).then(isVerified => {
+ if (isVerified) {
+ return fxAccounts.getSignedInUserProfile();
+ }
+ return null;
+ }).then(data => {
+ let fxaLoginStatus = document.getElementById("fxaLoginStatus");
+ if (data && profileInfoEnabled) {
+ if (data.displayName) {
+ fxaLoginStatus.setAttribute("hasName", true);
+ displayNameLabel.hidden = false;
+ displayNameLabel.textContent = data.displayName;
+ } else {
+ fxaLoginStatus.removeAttribute("hasName");
+ }
+ if (data.avatar) {
+ let bgImage = "url(\"" + data.avatar + "\")";
+ let profileImageElement = document.getElementById("fxaProfileImage");
+ profileImageElement.style.listStyleImage = bgImage;
+
+ let img = new Image();
+ img.onerror = () => {
+ // Clear the image if it has trouble loading. Since this callback is asynchronous
+ // we check to make sure the image is still the same before we clear it.
+ if (profileImageElement.style.listStyleImage === bgImage) {
+ profileImageElement.style.removeProperty("list-style-image");
+ }
+ };
+ img.src = data.avatar;
+ }
+ } else {
+ fxaLoginStatus.removeAttribute("hasName");
+ }
+ }, err => {
+ FxAccountsCommon.log.error(err);
+ }).catch(err => {
+ // If we get here something's really busted
+ Cu.reportError(String(err));
+ });
+
+ // If fxAccountEnabled is false and we are in a "not configured" state,
+ // then fxAccounts is probably fully disabled rather than just unconfigured,
+ // so handle this case. This block can be removed once we remove support
+ // for fxAccounts being disabled.
+ } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
+ Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
+ this.page = PAGE_NO_ACCOUNT;
+ // else: sync was previously configured for the legacy provider, so we
+ // make the "old" panels available.
+ } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
+ Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+ this.needsUpdate();
+ } else {
+ this.page = PAGE_HAS_ACCOUNT;
+ document.getElementById("accountName").textContent = Weave.Service.identity.account;
+ document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
+ document.getElementById("tosPP-normal").hidden = this._usingCustomServer;
+ }
+ },
+
+ startOver: function (showDialog) {
+ if (showDialog) {
+ let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
+ Services.prompt.BUTTON_POS_1_DEFAULT;
+ let buttonChoice =
+ Services.prompt.confirmEx(window,
+ this._stringBundle.GetStringFromName("syncUnlink.title"),
+ this._stringBundle.GetStringFromName("syncUnlink.label"),
+ flags,
+ this._stringBundle.GetStringFromName("syncUnlinkConfirm.label"),
+ null, null, null, {});
+
+ // If the user selects cancel, just bail
+ if (buttonChoice == 1)
+ return;
+ }
+
+ Weave.Service.startOver();
+ this.updateWeavePrefs();
+ },
+
+ updatePass: function () {
+ if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
+ gSyncUtils.changePassword();
+ else
+ gSyncUtils.updatePassphrase();
+ },
+
+ resetPass: function () {
+ if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
+ gSyncUtils.resetPassword();
+ else
+ gSyncUtils.resetPassphrase();
+ },
+
+ _getEntryPoint: function () {
+ let params = new URLSearchParams(document.URL.split("#")[0].split("?")[1] || "");
+ return params.get("entrypoint") || "preferences";
+ },
+
+ _openAboutAccounts: function(action) {
+ let entryPoint = this._getEntryPoint();
+ let params = new URLSearchParams();
+ if (action) {
+ params.set("action", action);
+ }
+ params.set("entrypoint", entryPoint);
+
+ this.replaceTabWithUrl("about:accounts?" + params);
+ },
+
+ /**
+ * Invoke the Sync setup wizard.
+ *
+ * @param wizardType
+ * Indicates type of wizard to launch:
+ * null -- regular set up wizard
+ * "pair" -- pair a device first
+ * "reset" -- reset sync
+ */
+ openSetup: function (wizardType) {
+ let service = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+
+ if (service.fxAccountsEnabled) {
+ this._openAboutAccounts();
+ } else {
+ let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+ if (win)
+ win.focus();
+ else {
+ window.openDialog("chrome://browser/content/sync/setup.xul",
+ "weaveSetup", "centerscreen,chrome,resizable=no",
+ wizardType);
+ }
+ }
+ },
+
+ openContentInBrowser: function(url, options) {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ if (!win) {
+ // no window to use, so use _openLink to create a new one. We don't
+ // always use that as it prefers to open a new window rather than use
+ // an existing one.
+ gSyncUtils._openLink(url);
+ return;
+ }
+ win.switchToTabHavingURI(url, true, options);
+ },
+
+ // Replace the current tab with the specified URL.
+ replaceTabWithUrl(url) {
+ // Get the <browser> element hosting us.
+ let browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ // And tell it to load our URL.
+ browser.loadURI(url);
+ },
+
+ signUp: function() {
+ this._openAboutAccounts("signup");
+ },
+
+ signIn: function() {
+ this._openAboutAccounts("signin");
+ },
+
+ reSignIn: function() {
+ this._openAboutAccounts("reauth");
+ },
+
+
+ clickOrSpaceOrEnterPressed: function(event) {
+ // Note: charCode is deprecated, but 'char' not yet implemented.
+ // Replace charCode with char when implemented, see Bug 680830
+ return ((event.type == "click" && event.button == 0) ||
+ (event.type == "keypress" &&
+ (event.charCode == KeyEvent.DOM_VK_SPACE || event.keyCode == KeyEvent.DOM_VK_RETURN)));
+ },
+
+ openChangeProfileImage: function(event) {
+ if (this.clickOrSpaceOrEnterPressed(event)) {
+ fxAccounts.promiseAccountsChangeProfileURI(this._getEntryPoint(), "avatar")
+ .then(url => {
+ this.openContentInBrowser(url, {
+ replaceQueryString: true
+ });
+ });
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ }
+ },
+
+ openManageFirefoxAccount: function(event) {
+ if (this.clickOrSpaceOrEnterPressed(event)) {
+ this.manageFirefoxAccount();
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ }
+ },
+
+ manageFirefoxAccount: function() {
+ fxAccounts.promiseAccountsManageURI(this._getEntryPoint())
+ .then(url => {
+ this.openContentInBrowser(url, {
+ replaceQueryString: true
+ });
+ });
+ },
+
+ verifyFirefoxAccount: function() {
+ let showVerifyNotification = (data) => {
+ let isError = !data;
+ let maybeNot = isError ? "Not" : "";
+ let sb = this._accountsStringBundle;
+ let title = sb.GetStringFromName("verification" + maybeNot + "SentTitle");
+ let email = !isError && data ? data.email : "";
+ let body = sb.formatStringFromName("verification" + maybeNot + "SentBody", [email], 1);
+ new Notification(title, { body })
+ }
+
+ let onError = () => {
+ showVerifyNotification();
+ };
+
+ let onSuccess = data => {
+ if (data) {
+ showVerifyNotification(data);
+ } else {
+ onError();
+ }
+ };
+
+ fxAccounts.resendVerificationEmail()
+ .then(fxAccounts.getSignedInUser, onError)
+ .then(onSuccess, onError);
+ },
+
+ openOldSyncSupportPage: function() {
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "old-sync";
+ this.openContentInBrowser(url);
+ },
+
+ unlinkFirefoxAccount: function(confirm) {
+ if (confirm) {
+ // We use a string bundle shared with aboutAccounts.
+ let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+ let disconnectLabel = sb.GetStringFromName("disconnect.label");
+ let title = sb.GetStringFromName("disconnect.verify.title");
+ let body = sb.GetStringFromName("disconnect.verify.bodyHeading") +
+ "\n\n" +
+ sb.GetStringFromName("disconnect.verify.bodyText");
+ let ps = Services.prompt;
+ let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
+ (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
+ ps.BUTTON_POS_1_DEFAULT;
+
+ let factory = Cc["@mozilla.org/prompter;1"]
+ .getService(Ci.nsIPromptFactory);
+ let prompt = factory.getPrompt(window, Ci.nsIPrompt);
+ let bag = prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
+ bag.setPropertyAsBool("allowTabModal", true);
+
+ let pressed = prompt.confirmEx(title, body, buttonFlags,
+ disconnectLabel, null, null, null, {});
+
+ if (pressed != 0) { // 0 is the "continue" button
+ return;
+ }
+ }
+ fxAccounts.signOut().then(() => {
+ this.updateWeavePrefs();
+ });
+ },
+
+ openAddDevice: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return;
+
+ let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+ if (win)
+ win.focus();
+ else
+ window.openDialog("chrome://browser/content/sync/addDevice.xul",
+ "syncAddDevice", "centerscreen,chrome,resizable=no");
+ },
+
+ resetSync: function () {
+ this.openSetup("reset");
+ },
+
+ _populateComputerName(value) {
+ let textbox = document.getElementById("fxaSyncComputerName");
+ if (!textbox.hasAttribute("placeholder")) {
+ textbox.setAttribute("placeholder",
+ Weave.Utils.getDefaultDeviceName());
+ }
+ textbox.value = value;
+ },
+};
diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul
new file mode 100644
index 000000000..f1aebf2aa
--- /dev/null
+++ b/browser/components/preferences/in-content/sync.xul
@@ -0,0 +1,359 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!-- Sync panel -->
+
+<preferences id="syncEnginePrefs" hidden="true" data-category="paneSync">
+ <preference id="engine.addons"
+ name="services.sync.engine.addons"
+ type="bool"/>
+ <preference id="engine.bookmarks"
+ name="services.sync.engine.bookmarks"
+ type="bool"/>
+ <preference id="engine.history"
+ name="services.sync.engine.history"
+ type="bool"/>
+ <preference id="engine.tabs"
+ name="services.sync.engine.tabs"
+ type="bool"/>
+ <preference id="engine.prefs"
+ name="services.sync.engine.prefs"
+ type="bool"/>
+ <preference id="engine.passwords"
+ name="services.sync.engine.passwords"
+ type="bool"/>
+</preferences>
+
+<script type="application/javascript"
+ src="chrome://browser/content/preferences/in-content/sync.js"/>
+<script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+
+<hbox id="header-sync"
+ class="header"
+ hidden="true"
+ data-category="paneSync">
+ <label class="header-name" flex="1">&paneSync.title;</label>
+ <html:a class="help-button text-link" target="_blank" aria-label="&helpButton.label;"></html:a>
+</hbox>
+
+<deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
+ <!-- These panels are for the "legacy" sync provider -->
+ <vbox id="noAccount" align="center">
+ <spacer flex="1"/>
+ <description id="syncDesc">
+ &weaveDesc.label;
+ </description>
+ <separator/>
+ <label id="noAccountSetup" class="text-link">
+ &setupButton.label;
+ </label>
+ <vbox id="pairDevice">
+ <separator/>
+ <label id="noAccountPair" class="text-link">
+ &pairDevice.label;
+ </label>
+ </vbox>
+ <spacer flex="3"/>
+ </vbox>
+
+ <vbox id="hasAccount">
+ <groupbox class="syncGroupBox">
+ <!-- label is set to account name -->
+ <caption id="accountCaption" align="center">
+ <image id="accountCaptionImage"/>
+ <label id="accountName"/>
+ </caption>
+
+ <hbox>
+ <button type="menu"
+ label="&manageAccount.label;"
+ accesskey="&manageAccount.accesskey;">
+ <menupopup>
+ <menuitem id="syncChangePassword" label="&changePassword2.label;"/>
+ <menuitem id="syncResetPassphrase" label="&myRecoveryKey.label;"/>
+ <menuseparator/>
+ <menuitem id="syncReset" label="&resetSync2.label;"/>
+ </menupopup>
+ </button>
+ </hbox>
+
+ <hbox>
+ <label id="syncAddDeviceLabel"
+ class="text-link">
+ &pairDevice.label;
+ </label>
+ </hbox>
+
+ <vbox>
+ <label>&syncMy.label;</label>
+ <richlistbox id="syncEnginesList"
+ orient="vertical">
+ <richlistitem>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ preference="engine.addons"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ preference="engine.bookmarks"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ preference="engine.passwords"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ preference="engine.prefs"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ preference="engine.history"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ preference="engine.tabs"/>
+ </richlistitem>
+ </richlistbox>
+ </vbox>
+ </groupbox>
+
+ <groupbox class="syncGroupBox">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label control="syncComputerName">
+ &syncDeviceName.label;
+ </label>
+ <textbox id="syncComputerName"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <label id="unlinkDevice" class="text-link">
+ &unlinkDevice.label;
+ </label>
+ </hbox>
+ </groupbox>
+ <vbox id="tosPP-normal">
+ <label id="tosPP-normal-ToS" class="text-link">
+ &prefs.tosLink.label;
+ </label>
+ <label id="tosPP-normal-PP" class="text-link">
+ &prefs.ppLink.label;
+ </label>
+ </vbox>
+ </vbox>
+
+ <vbox id="needsUpdate" align="center" pack="center">
+ <hbox>
+ <label id="loginError"/>
+ <label id="loginErrorUpdatePass" class="text-link">
+ &updatePass.label;
+ </label>
+ <label id="loginErrorResetPass" class="text-link">
+ &resetPass.label;
+ </label>
+ </hbox>
+ <label id="loginErrorStartOver" class="text-link">
+ &unlinkDevice.label;
+ </label>
+ </vbox>
+
+ <!-- These panels are for the Firefox Accounts identity provider -->
+ <vbox id="noFxaAccount">
+ <hbox>
+ <vbox id="fxaContentWrapper">
+ <groupbox id="noFxaGroup">
+ <vbox>
+ <label id="noFxaCaption">&signedOut.caption;</label>
+ <description id="noFxaDescription" flex="1">&signedOut.description;</description>
+ <hbox class="fxaAccountBox">
+ <vbox>
+ <image class="fxaFirefoxLogo"/>
+ </vbox>
+ <vbox flex="1">
+ <label id="signedOutAccountBoxTitle">&signedOut.accountBox.title;</label>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="noFxaSignUp" label="&signedOut.accountBox.create;" accesskey="&signedOut.accountBox.create.accesskey;"></button>
+ <button id="noFxaSignIn" label="&signedOut.accountBox.signin;" accesskey="&signedOut.accountBox.signin.accesskey;"></button>
+ </hbox>
+ </vbox>
+ </hbox>
+ </vbox>
+ </groupbox>
+ </vbox>
+ <vbox>
+ <image class="fxaSyncIllustration"/>
+ </vbox>
+ </hbox>
+ <label class="fxaMobilePromo">
+ &mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
+ --><label id="fxaMobilePromo-android"
+ class="androidLink text-link"><!--
+ -->&mobilePromo3.androidLink;</label><!--
+ -->&mobilePromo3.iOSBefore;<!--
+ --><label id="fxaMobilePromo-ios"
+ class="iOSLink text-link"><!--
+ -->&mobilePromo3.iOSLink;</label><!--
+ -->&mobilePromo3.end;
+ </label>
+ </vbox>
+
+ <vbox id="hasFxaAccount">
+ <hbox>
+ <vbox id="fxaContentWrapper">
+ <groupbox id="fxaGroup">
+ <caption><label>&syncBrand.fxAccount.label;</label></caption>
+ <deck id="fxaLoginStatus">
+
+ <!-- logged in and verified and all is good -->
+ <hbox id="fxaLoginVerified" class="fxaAccountBox">
+ <vbox align="center" pack="center">
+ <image id="fxaProfileImage" class="actionable"
+ role="button"
+ onclick="gSyncPane.openChangeProfileImage(event);" hidden="true"
+ onkeypress="gSyncPane.openChangeProfileImage(event);"
+ tooltiptext="&profilePicture.tooltip;"/>
+ </vbox>
+ <vbox flex="1" pack="center">
+ <label id="fxaDisplayName" hidden="true"/>
+ <label id="fxaEmailAddress1"/>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="fxaUnlinkButton" label="&disconnect.label;" accesskey="&disconnect.accesskey;"/>
+ <html:a id="verifiedManage" target="_blank"
+ accesskey="&verifiedManage.accesskey;"
+ onkeypress="gSyncPane.openManageFirefoxAccount(event);"><!--
+ -->&verifiedManage.label;</html:a>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- logged in to an unverified account -->
+ <hbox id="fxaLoginUnverified" class="fxaAccountBox">
+ <vbox>
+ <image id="fxaProfileImage"/>
+ </vbox>
+ <vbox flex="1">
+ <hbox>
+ <vbox><image id="fxaLoginRejectedWarning"/></vbox>
+ <description flex="1">
+ &signedInUnverified.beforename.label;
+ <label id="fxaEmailAddress2"/>
+ &signedInUnverified.aftername.label;
+ </description>
+ </hbox>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="verifyFxaAccount" accesskey="&verify.accesskey;">&verify.label;</button>
+ <button id="unverifiedUnlinkFxaAccount" accesskey="&forget.accesskey;">&forget.label;</button>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- logged in locally but server rejected credentials -->
+ <hbox id="fxaLoginRejected" class="fxaAccountBox">
+ <vbox>
+ <image id="fxaProfileImage"/>
+ </vbox>
+ <vbox flex="1">
+ <hbox>
+ <vbox><image id="fxaLoginRejectedWarning"/></vbox>
+ <description flex="1">
+ &signedInLoginFailure.beforename.label;
+ <label id="fxaEmailAddress3"/>
+ &signedInLoginFailure.aftername.label;
+ </description>
+ </hbox>
+ <hbox class="fxaAccountBoxButtons">
+ <button id="rejectReSignIn" accessky="&signIn.accesskey;">&signIn.label;</button>
+ <button id="rejectUnlinkFxaAccount" accesskey="&forget.accesskey;">&forget.label;</button>
+ </hbox>
+ </vbox>
+ </hbox>
+ </deck>
+ </groupbox>
+ <groupbox id="syncOptions">
+ <caption><label>&signedIn.engines.label;</label></caption>
+ <hbox id="fxaSyncEngines">
+ <vbox align="start" flex="1">
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ preference="engine.tabs"/>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ preference="engine.bookmarks"/>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ preference="engine.passwords"/>
+ </vbox>
+ <vbox align="start" flex="1">
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ preference="engine.history"/>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ preference="engine.addons"/>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ preference="engine.prefs"/>
+ </vbox>
+ <spacer/>
+ </hbox>
+ </groupbox>
+ </vbox>
+ <vbox>
+ <image class="fxaSyncIllustration"/>
+ </vbox>
+ </hbox>
+ <groupbox>
+ <caption>
+ <label control="fxaSyncComputerName">
+ &fxaSyncDeviceName.label;
+ </label>
+ </caption>
+ <hbox id="fxaDeviceName">
+ <textbox id="fxaSyncComputerName" disabled="true"/>
+ <hbox>
+ <button id="fxaChangeDeviceName"
+ label="&changeSyncDeviceName.label;"
+ accesskey="&changeSyncDeviceName.accesskey;"/>
+ <button id="fxaCancelChangeDeviceName"
+ label="&cancelChangeSyncDeviceName.label;"
+ accesskey="&cancelChangeSyncDeviceName.accesskey;"
+ hidden="true"/>
+ <button id="fxaSaveChangeDeviceName"
+ label="&saveChangeSyncDeviceName.label;"
+ accesskey="&saveChangeSyncDeviceName.accesskey;"
+ hidden="true"/>
+ </hbox>
+ </hbox>
+ </groupbox>
+ <label class="fxaMobilePromo">
+ &mobilePromo3.start;<!-- We put these comments to avoid inserting white spaces
+ --><label class="androidLink text-link" id="fxaMobilePromo-android-hasFxaAccount"><!--
+ -->&mobilePromo3.androidLink;</label><!--
+ -->&mobilePromo3.iOSBefore;<!--
+ --><label class="iOSLink text-link" id="fxaMobilePromo-ios-hasFxaAccount"><!--
+ -->&mobilePromo3.iOSLink;</label><!--
+ -->&mobilePromo3.end;
+ </label>
+ <vbox id="tosPP-small" align="start">
+ <label id="tosPP-small-ToS" class="text-link">
+ &prefs.tosLink.label;
+ </label>
+ <label id="tosPP-small-PP" class="text-link">
+ &fxaPrivacyNotice.link.label;
+ </label>
+ </vbox>
+ </vbox>
+</deck>
diff --git a/browser/components/preferences/in-content/tests/.eslintrc.js b/browser/components/preferences/in-content/tests/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/preferences/in-content/tests/browser.ini b/browser/components/preferences/in-content/tests/browser.ini
new file mode 100644
index 000000000..6cba02599
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -0,0 +1,43 @@
+[DEFAULT]
+support-files =
+ head.js
+ privacypane_tests_perwindow.js
+
+[browser_advanced_update.js]
+[browser_basic_rebuild_fonts_test.js]
+[browser_bug410900.js]
+[browser_bug705422.js]
+[browser_bug731866.js]
+[browser_bug795764_cachedisabled.js]
+[browser_bug1018066_resetScrollPosition.js]
+[browser_bug1020245_openPreferences_to_paneContent.js]
+[browser_bug1184989_prevent_scrolling_when_preferences_flipped.js]
+support-files =
+ browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
+[browser_change_app_handler.js]
+skip-if = os != "win" # This test tests the windows-specific app selection dialog, so can't run on non-Windows
+[browser_connection.js]
+[browser_connection_bug388287.js]
+[browser_cookies_exceptions.js]
+[browser_defaultbrowser_alwayscheck.js]
+[browser_healthreport.js]
+skip-if = true || !healthreport # Bug 1185403 for the "true"
+[browser_homepages_filter_aboutpreferences.js]
+[browser_notifications_do_not_disturb.js]
+[browser_permissions_urlFieldHidden.js]
+[browser_proxy_backup.js]
+[browser_privacypane_1.js]
+[browser_privacypane_3.js]
+[browser_privacypane_4.js]
+[browser_privacypane_5.js]
+[browser_privacypane_8.js]
+[browser_sanitizeOnShutdown_prefLocked.js]
+[browser_searchsuggestions.js]
+[browser_security.js]
+[browser_subdialogs.js]
+support-files =
+ subdialog.xul
+ subdialog2.xul
+[browser_telemetry.js]
+# Skip this test on Android as FHR and Telemetry are separate systems there.
+skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
diff --git a/browser/components/preferences/in-content/tests/browser_advanced_update.js b/browser/components/preferences/in-content/tests/browser_advanced_update.js
new file mode 100644
index 000000000..e9d0e8652
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_advanced_update.js
@@ -0,0 +1,158 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+const mockUpdateManager = {
+ contractId: "@mozilla.org/updates/update-manager;1",
+
+ _mockClassId: uuidGenerator.generateUUID(),
+
+ _originalClassId: "",
+
+ _originalFactory: null,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager]),
+
+ createInstance: function(outer, iiD) {
+ if (outer) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(iiD);
+ },
+
+ register: function () {
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ if (!registrar.isCIDRegistered(this._mockClassId)) {
+ this._originalClassId = registrar.contractIDToCID(this.contractId);
+ this._originalFactory = Cm.getClassObject(Cc[this.contractId], Ci.nsIFactory);
+ registrar.unregisterFactory(this._originalClassId, this._originalFactory);
+ registrar.registerFactory(this._mockClassId, "Unregister after testing", this.contractId, this);
+ }
+ },
+
+ unregister: function () {
+ let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(this._mockClassId, this);
+ registrar.registerFactory(this._originalClassId, "", this.contractId, this._originalFactory);
+ },
+
+ get updateCount() {
+ return this._updates.length;
+ },
+
+ getUpdateAt: function (index) {
+ return this._updates[index];
+ },
+
+ _updates: [
+ {
+ name: "Firefox Developer Edition 49.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20160728004010",
+ type: "minor",
+ installDate: 1469763105156,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/"
+ },
+ {
+ name: "Firefox Developer Edition 43.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20150929004011",
+ type: "minor",
+ installDate: 1443585886224,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/"
+ },
+ {
+ name: "Firefox Developer Edition 42.0a2",
+ statusText: "The Update was successfully installed",
+ buildID: "20150920004018",
+ type: "major",
+ installDate: 1442818147544,
+ detailsURL: "https://www.mozilla.org/firefox/aurora/"
+ }
+ ]
+};
+
+function resetPreferences() {
+ Services.prefs.clearUserPref("browser.search.update");
+}
+
+function formatInstallDate(sec) {
+ var date = new Date(sec);
+ const locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+ const dtOptions = { year: 'numeric', month: 'long', day: 'numeric',
+ hour: 'numeric', minute: 'numeric', second: 'numeric' };
+ return date.toLocaleString(locale, dtOptions);
+}
+
+registerCleanupFunction(resetPreferences);
+
+add_task(function*() {
+ yield openPreferencesViaOpenPreferencesAPI("advanced", "updateTab", { leaveOpen: true });
+ resetPreferences();
+ Services.prefs.setBoolPref("browser.search.update", false);
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let enableSearchUpdate = doc.getElementById("enableSearchUpdate");
+ is_element_visible(enableSearchUpdate, "Check search update preference is visible");
+
+ // Ensure that the update pref dialog reflects the actual pref value.
+ ok(!enableSearchUpdate.checked, "Ensure search updates are disabled");
+ Services.prefs.setBoolPref("browser.search.update", true);
+ ok(enableSearchUpdate.checked, "Ensure search updates are enabled");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ mockUpdateManager.register();
+
+ yield openPreferencesViaOpenPreferencesAPI("advanced", "updateTab", { leaveOpen: true });
+ let doc = gBrowser.selectedBrowser.contentDocument;
+
+ let showBtn = doc.getElementById("showUpdateHistory");
+ let dialogOverlay = doc.getElementById("dialogOverlay");
+
+ // Test the dialog window opens
+ is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
+ showBtn.doCommand();
+ yield promiseLoadSubDialog("chrome://mozapps/content/update/history.xul");
+ is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
+
+ let dialogFrame = doc.getElementById("dialogFrame");
+ let frameDoc = dialogFrame.contentDocument;
+ let updates = frameDoc.querySelectorAll("update");
+
+ // Test the update history numbers are correct
+ is(updates.length, mockUpdateManager.updateCount, "The update count is incorrect.");
+
+ // Test the updates are displayed correctly
+ let update = null;
+ let updateData = null;
+ for (let i = 0; i < updates.length; ++i) {
+ update = updates[i];
+ updateData = mockUpdateManager.getUpdateAt(i);
+
+ is(update.name, updateData.name + " (" + updateData.buildID + ")", "Wrong update name");
+ is(update.type, updateData.type == "major" ? "New Version" : "Security Update", "Wrong update type");
+ is(update.installDate, formatInstallDate(updateData.installDate), "Wrong update installDate");
+ is(update.detailsURL, updateData.detailsURL, "Wrong update detailsURL");
+ is(update.status, updateData.statusText, "Wrong update status");
+ }
+
+ // Test the dialog window closes
+ let closeBtn = doc.getElementById("dialogClose");
+ closeBtn.doCommand();
+ is(dialogOverlay.style.visibility, "", "The dialog should be invisible");
+
+ mockUpdateManager.unregister();
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
new file mode 100644
index 000000000..32c1bd726
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_basic_rebuild_fonts_test.js
@@ -0,0 +1,76 @@
+Services.prefs.setBoolPref("browser.preferences.instantApply", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+});
+
+add_task(function*() {
+ yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+ let doc = gBrowser.contentDocument;
+ var langGroup = Services.prefs.getComplexValue("font.language.group", Ci.nsIPrefLocalizedString).data
+ is(doc.getElementById("font.language.group").value, langGroup,
+ "Language group should be set correctly.");
+
+ let defaultFontType = Services.prefs.getCharPref("font.default." + langGroup);
+ let fontFamily = Services.prefs.getCharPref("font.name." + defaultFontType + "." + langGroup);
+ let fontFamilyField = doc.getElementById("defaultFont");
+ is(fontFamilyField.value, fontFamily, "Font family should be set correctly.");
+
+ let defaultFontSize = Services.prefs.getIntPref("font.size.variable." + langGroup);
+ let fontSizeField = doc.getElementById("defaultFontSize");
+ is(fontSizeField.value, defaultFontSize, "Font size should be set correctly.");
+
+ doc.getElementById("advancedFonts").click();
+ let win = yield promiseLoadSubDialog("chrome://browser/content/preferences/fonts.xul");
+ doc = win.document;
+
+ // Simulate a dumb font backend.
+ win.FontBuilder._enumerator = {
+ _list: ["MockedFont1", "MockedFont2", "MockedFont3"],
+ EnumerateFonts: function(lang, type, list) {
+ return this._list;
+ },
+ EnumerateAllFonts: function() {
+ return this._list;
+ },
+ getDefaultFont: function() { return null; },
+ getStandardFamilyName: function(name) { return name; },
+ };
+ win.FontBuilder._allFonts = null;
+ win.FontBuilder._langGroupSupported = false;
+
+ let langGroupElement = doc.getElementById("font.language.group");
+ let selectLangsField = doc.getElementById("selectLangs");
+ let serifField = doc.getElementById("serif");
+ let armenian = "x-armn";
+ let western = "x-western";
+
+ langGroupElement.value = armenian;
+ selectLangsField.value = armenian;
+ is(serifField.value, "", "Font family should not be set.");
+
+ langGroupElement.value = western;
+ selectLangsField.value = western;
+
+ // Simulate a font backend supporting language-specific enumeration.
+ // NB: FontBuilder has cached the return value from EnumerateAllFonts(),
+ // so _allFonts will always have 3 elements regardless of subsequent
+ // _list changes.
+ win.FontBuilder._enumerator._list = ["MockedFont2"];
+
+ langGroupElement.value = armenian;
+ selectLangsField.value = armenian;
+ is(serifField.value, "MockedFont2", "Font family should be set.");
+
+ langGroupElement.value = western;
+ selectLangsField.value = western;
+
+ // Simulate a system that has no fonts for the specified language.
+ win.FontBuilder._enumerator._list = [];
+
+ langGroupElement.value = armenian;
+ selectLangsField.value = armenian;
+ is(serifField.value, "", "Font family should not be set.");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
new file mode 100644
index 000000000..9d938fdd4
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1018066_resetScrollPosition.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var originalWindowHeight;
+registerCleanupFunction(function() {
+ window.resizeTo(window.outerWidth, originalWindowHeight);
+ while (gBrowser.tabs[1])
+ gBrowser.removeTab(gBrowser.tabs[1]);
+});
+
+add_task(function*() {
+ originalWindowHeight = window.outerHeight;
+ window.resizeTo(window.outerWidth, 300);
+ let prefs = yield openPreferencesViaOpenPreferencesAPI("paneApplications", undefined, {leaveOpen: true});
+ is(prefs.selectedPane, "paneApplications", "Applications pane was selected");
+ let mainContent = gBrowser.contentDocument.querySelector(".main-content");
+ mainContent.scrollTop = 50;
+ is(mainContent.scrollTop, 50, "main-content should be scrolled 50 pixels");
+
+ gBrowser.contentWindow.gotoPref("paneGeneral");
+ is(mainContent.scrollTop, 0,
+ "Switching to a different category should reset the scroll position");
+});
+
diff --git a/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js b/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
new file mode 100644
index 000000000..bc2c6d800
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Services.prefs.setBoolPref("browser.preferences.instantApply", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+});
+
+add_task(function*() {
+ let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent");
+ is(prefs.selectedPane, "paneContent", "Content pane was selected");
+ prefs = yield openPreferencesViaOpenPreferencesAPI("advanced", "updateTab");
+ is(prefs.selectedPane, "paneAdvanced", "Advanced pane was selected");
+ is(prefs.selectedAdvancedTab, "updateTab", "The update tab within the advanced prefs should be selected");
+ prefs = yield openPreferencesViaHash("privacy");
+ is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected when hash is 'privacy'");
+ prefs = yield openPreferencesViaOpenPreferencesAPI("nonexistant-category");
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected by default when a nonexistant-category is requested");
+ prefs = yield openPreferencesViaHash("nonexistant-category");
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected when hash is a nonexistant-category");
+ prefs = yield openPreferencesViaHash();
+ is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
+});
+
+function openPreferencesViaHash(aPane) {
+ let deferred = Promise.defer();
+ gBrowser.selectedTab = gBrowser.addTab("about:preferences" + (aPane ? "#" + aPane : ""));
+ let newTabBrowser = gBrowser.selectedBrowser;
+
+ newTabBrowser.addEventListener("Initialized", function PrefInit() {
+ newTabBrowser.removeEventListener("Initialized", PrefInit, true);
+ newTabBrowser.contentWindow.addEventListener("load", function prefLoad() {
+ newTabBrowser.contentWindow.removeEventListener("load", prefLoad);
+ let win = gBrowser.contentWindow;
+ let selectedPane = win.history.state;
+ gBrowser.removeCurrentTab();
+ deferred.resolve({selectedPane: selectedPane});
+ });
+ }, true);
+
+ return deferred.promise;
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js
new file mode 100644
index 000000000..0972b2de4
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.js
@@ -0,0 +1,92 @@
+const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+add_task(function* () {
+ waitForExplicitFinish();
+
+ const tabURL = getRootDirectory(gTestPath) + "browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul";
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: tabURL }, function* (browser) {
+ let doc = browser.contentDocument;
+ let container = doc.getElementById("container");
+
+ // Test button
+ let button = doc.getElementById("button");
+ button.focus();
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(container, "button");
+
+ // Test checkbox
+ let checkbox = doc.getElementById("checkbox");
+ checkbox.focus();
+ EventUtils.synthesizeKey(" ", {});
+ ok(checkbox.checked, "Checkbox is checked");
+ yield checkPageScrolling(container, "checkbox");
+
+ // Test listbox
+ let listbox = doc.getElementById("listbox");
+ let listitem = doc.getElementById("listitem");
+ listbox.focus();
+ EventUtils.synthesizeKey(" ", {});
+ ok(listitem.selected, "Listitem is selected");
+ yield checkPageScrolling(container, "listbox");
+
+ // Test radio
+ let radiogroup = doc.getElementById("radiogroup");
+ radiogroup.focus();
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(container, "radio");
+ });
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:preferences#search" }, function* (browser) {
+ let doc = browser.contentDocument;
+ let container = doc.getElementsByClassName("main-content")[0];
+
+ // Test search
+ let engineList = doc.getElementById("engineList");
+ engineList.focus();
+ EventUtils.synthesizeKey(" ", {});
+ is(engineList.view.selection.currentIndex, 0, "Search engineList is selected");
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(container, "search engineList");
+ });
+
+ // Test session restore
+ const CRASH_URL = "about:mozilla";
+ const CRASH_FAVICON = "chrome://branding/content/icon32.png";
+ const CRASH_SHENTRY = {url: CRASH_URL};
+ const CRASH_TAB = {entries: [CRASH_SHENTRY], image: CRASH_FAVICON};
+ const CRASH_STATE = {windows: [{tabs: [CRASH_TAB]}]};
+
+ const TAB_URL = "about:sessionrestore";
+ const TAB_FORMDATA = {url: TAB_URL, id: {sessionData: CRASH_STATE}};
+ const TAB_SHENTRY = {url: TAB_URL};
+ const TAB_STATE = {entries: [TAB_SHENTRY], formdata: TAB_FORMDATA};
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // Fake a post-crash tab
+ ss.setTabState(tab, JSON.stringify(TAB_STATE));
+
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ let doc = tab.linkedBrowser.contentDocument;
+
+ // Make body scrollable
+ doc.body.style.height = (doc.body.clientHeight + 100) + "px";
+
+ let tabList = doc.getElementById("tabList");
+ tabList.focus();
+ EventUtils.synthesizeKey(" ", {});
+ yield checkPageScrolling(doc.documentElement, "session restore");
+
+ gBrowser.removeCurrentTab();
+ finish();
+});
+
+function checkPageScrolling(container, type) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ is(container.scrollTop, 0, "Page should not scroll when " + type + " flipped");
+ resolve();
+ }, 0);
+ });
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
new file mode 100644
index 000000000..59b644c8f
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+ XUL Widget Test for Bug 1184989
+ -->
+<page title="Bug 1184989 Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<vbox id="container" style="height: 200px; overflow: auto;">
+ <vbox style="height: 500px;">
+ <hbox>
+ <button id="button" label="button" />
+ </hbox>
+
+ <hbox>
+ <checkbox id="checkbox" label="checkbox" />
+ </hbox>
+
+ <hbox style="height: 50px;">
+ <listbox id="listbox">
+ <listitem id="listitem" label="listitem" />
+ <listitem label="listitem" />
+ </listbox>
+ </hbox>
+
+ <hbox>
+ <radiogroup id="radiogroup">
+ <radio id="radio" label="radio" />
+ </radiogroup>
+ </hbox>
+ </vbox>
+</vbox>
+
+</page>
diff --git a/browser/components/preferences/in-content/tests/browser_bug410900.js b/browser/components/preferences/in-content/tests/browser_bug410900.js
new file mode 100644
index 000000000..5b100966d
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug410900.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // Setup a phony handler to ensure the app pane will be populated.
+ var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handler.name = "App pane alive test";
+ handler.uriTemplate = "http://test.mozilla.org/%s";
+
+ var extps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ var info = extps.getProtocolHandlerInfo("apppanetest");
+ info.possibleApplicationHandlers.appendElement(handler, false);
+
+ var hserv = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hserv.store(info);
+
+ openPreferencesViaOpenPreferencesAPI("applications", null, {leaveOpen: true}).then(
+ () => runTest(gBrowser.selectedBrowser.contentWindow)
+ );
+}
+
+function runTest(win) {
+ var rbox = win.document.getElementById("handlersView");
+ ok(rbox, "handlersView is present");
+
+ var items = rbox && rbox.getElementsByTagName("richlistitem");
+ ok(items && items.length > 0, "App handler list populated");
+
+ var handlerAdded = false;
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].getAttribute('type') == "apppanetest")
+ handlerAdded = true;
+ }
+ ok(handlerAdded, "apppanetest protocol handler was successfully added");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug705422.js b/browser/components/preferences/in-content/tests/browser_bug705422.js
new file mode 100644
index 000000000..24732083b
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug705422.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+ // Allow all cookies, then actually set up the test
+ SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, initTest);
+}
+
+function initTest() {
+ const searchTerm = "example";
+ const dummyTerm = "elpmaxe";
+
+ var cm = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager);
+
+ // delete all cookies (might be left over from other tests)
+ cm.removeAll();
+
+ // data for cookies
+ var vals = [[searchTerm+".com", dummyTerm, dummyTerm], // match
+ [searchTerm+".org", dummyTerm, dummyTerm], // match
+ [dummyTerm+".com", searchTerm, dummyTerm], // match
+ [dummyTerm+".edu", searchTerm+dummyTerm, dummyTerm], // match
+ [dummyTerm+".net", dummyTerm, searchTerm], // match
+ [dummyTerm+".org", dummyTerm, searchTerm+dummyTerm], // match
+ [dummyTerm+".int", dummyTerm, dummyTerm]]; // no match
+
+ // matches must correspond to above data
+ const matches = 6;
+
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var cookieSvc = Components.classes["@mozilla.org/cookieService;1"]
+ .getService(Components.interfaces.nsICookieService);
+ var v;
+ // inject cookies
+ for (v in vals) {
+ let [host, name, value] = vals[v];
+ var cookieUri = ios.newURI("http://"+host, null, null);
+ cookieSvc.setCookieString(cookieUri, null, name+"="+value+";", null);
+ }
+
+ // open cookie manager
+ var cmd = window.openDialog("chrome://browser/content/preferences/cookies.xul",
+ "Browser:Cookies", "", {});
+
+ // when it has loaded, run actual tests
+ cmd.addEventListener("load", function() { executeSoon(function() { runTest(cmd, searchTerm, vals.length, matches); }); }, false);
+}
+
+function isDisabled(win, expectation) {
+ var disabled = win.document.getElementById("removeAllCookies").disabled;
+ is(disabled, expectation, "Remove all cookies button has correct state: "+(expectation?"disabled":"enabled"));
+}
+
+function runTest(win, searchTerm, cookies, matches) {
+ var cm = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager);
+
+
+ // number of cookies should match injected cookies
+ var injectedCookies = 0,
+ injectedEnumerator = cm.enumerator;
+ while (injectedEnumerator.hasMoreElements()) {
+ injectedCookies++;
+ injectedEnumerator.getNext();
+ }
+ is(injectedCookies, cookies, "Number of cookies match injected cookies");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+ // filter cookies and count matches
+ win.gCookiesWindow.setFilter(searchTerm);
+ is(win.gCookiesWindow._view.rowCount, matches, "Correct number of cookies shown after filter is applied");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+
+ // select first cookie and delete
+ var tree = win.document.getElementById("cookiesList");
+ var deleteButton = win.document.getElementById("removeSelectedCookies");
+ var rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "cell");
+ EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {}, win);
+ EventUtils.synthesizeMouseAtCenter(deleteButton, {}, win);
+
+ // count cookies should be matches-1
+ is(win.gCookiesWindow._view.rowCount, matches-1, "Deleted selected cookie");
+
+ // select two adjacent cells and delete
+ EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {}, win);
+ var eventObj = {};
+ if (navigator.platform.indexOf("Mac") >= 0)
+ eventObj.metaKey = true;
+ else
+ eventObj.ctrlKey = true;
+ rect = tree.treeBoxObject.getCoordsForCellItem(1, tree.columns[0], "cell");
+ EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, eventObj, win);
+ EventUtils.synthesizeMouseAtCenter(deleteButton, {}, win);
+
+ // count cookies should be matches-3
+ is(win.gCookiesWindow._view.rowCount, matches-3, "Deleted selected two adjacent cookies");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+ // delete all cookies and count
+ var deleteAllButton = win.document.getElementById("removeAllCookies");
+ EventUtils.synthesizeMouseAtCenter(deleteAllButton, {}, win);
+ is(win.gCookiesWindow._view.rowCount, 0, "Deleted all matching cookies");
+
+ // "delete all cookies" should be disabled
+ isDisabled(win, true);
+
+ // clear filter and count should be cookies-matches
+ win.gCookiesWindow.setFilter("");
+ is(win.gCookiesWindow._view.rowCount, cookies-matches, "Unmatched cookies remain");
+
+ // "delete all cookies" should be enabled
+ isDisabled(win, false);
+
+ // delete all cookies and count should be 0
+ EventUtils.synthesizeMouseAtCenter(deleteAllButton, {}, win);
+ is(win.gCookiesWindow._view.rowCount, 0, "Deleted all cookies");
+
+ // check that datastore is also at 0
+ var remainingCookies = 0,
+ remainingEnumerator = cm.enumerator;
+ while (remainingEnumerator.hasMoreElements()) {
+ remainingCookies++;
+ remainingEnumerator.getNext();
+ }
+ is(remainingCookies, 0, "Zero cookies remain");
+
+ // "delete all cookies" should be disabled
+ isDisabled(win, true);
+
+ // clean up
+ win.close();
+ finish();
+}
+
diff --git a/browser/components/preferences/in-content/tests/browser_bug731866.js b/browser/components/preferences/in-content/tests/browser_bug731866.js
new file mode 100644
index 000000000..c1031d412
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug731866.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ open_preferences(runTest);
+}
+
+var gElements;
+
+function checkElements(expectedPane) {
+ for (let element of gElements) {
+ // keyset and preferences elements fail is_element_visible checks because they are never visible.
+ // special-case the drmGroup item because its visibility depends on pref + OS version
+ if (element.nodeName == "keyset" ||
+ element.nodeName == "preferences" ||
+ element.id === "drmGroup") {
+ continue;
+ }
+ let attributeValue = element.getAttribute("data-category");
+ let suffix = " (id=" + element.id + ")";
+ if (attributeValue == "pane" + expectedPane) {
+ is_element_visible(element, expectedPane + " elements should be visible" + suffix);
+ } else {
+ is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
+ }
+ }
+}
+
+function runTest(win) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+
+ let tab = win.document;
+ gElements = tab.getElementById("mainPrefPane").children;
+
+ let panes = [
+ "General", "Search", "Content", "Applications",
+ "Privacy", "Security", "Sync", "Advanced",
+ ];
+
+ for (let pane of panes) {
+ win.gotoPref("pane" + pane);
+ checkElements(pane);
+ }
+
+ gBrowser.removeCurrentTab();
+ win.close();
+ finish();
+}
diff --git a/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
new file mode 100644
index 000000000..21f92db8d
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ let prefs = [
+ "browser.cache.offline.enable",
+ "browser.cache.disk.enable",
+ "browser.cache.memory.enable",
+ ];
+
+ registerCleanupFunction(function() {
+ for (let pref of prefs) {
+ Services.prefs.clearUserPref(pref);
+ }
+ });
+
+ for (let pref of prefs) {
+ Services.prefs.setBoolPref(pref, false);
+ }
+
+ open_preferences(runTest);
+}
+
+function runTest(win) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+
+ let tab = win.document;
+ let elements = tab.getElementById("mainPrefPane").children;
+
+ // Test if advanced pane is opened correctly
+ win.gotoPref("paneAdvanced");
+ for (let element of elements) {
+ if (element.nodeName == "preferences") {
+ continue;
+ }
+ let attributeValue = element.getAttribute("data-category");
+ if (attributeValue == "paneAdvanced") {
+ is_element_visible(element, "Advanced elements should be visible");
+ } else {
+ is_element_hidden(element, "Non-Advanced elements should be hidden");
+ }
+ }
+
+ gBrowser.removeCurrentTab();
+ win.close();
+ finish();
+}
diff --git a/browser/components/preferences/in-content/tests/browser_change_app_handler.js b/browser/components/preferences/in-content/tests/browser_change_app_handler.js
new file mode 100644
index 000000000..f66cdfd37
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_change_app_handler.js
@@ -0,0 +1,98 @@
+var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+
+SimpleTest.requestCompleteLog();
+
+function setupFakeHandler() {
+ let info = gMimeSvc.getFromTypeAndExtension("text/plain", "foo.txt");
+ ok(info.possibleLocalHandlers.length, "Should have at least one known handler");
+ let handler = info.possibleLocalHandlers.queryElementAt(0, Ci.nsILocalHandlerApp);
+
+ let infoToModify = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ infoToModify.possibleApplicationHandlers.appendElement(handler, false);
+
+ gHandlerSvc.store(infoToModify);
+}
+
+add_task(function*() {
+ setupFakeHandler();
+ yield openPreferencesViaOpenPreferencesAPI("applications", null, {leaveOpen: true});
+ info("Preferences page opened on the applications pane.");
+ let win = gBrowser.selectedBrowser.contentWindow;
+
+ let container = win.document.getElementById("handlersView");
+ let ourItem = container.querySelector("richlistitem[type='text/x-test-handler']");
+ ok(ourItem, "handlersView is present");
+ ourItem.scrollIntoView();
+ container.selectItem(ourItem);
+ ok(ourItem.selected, "Should be able to select our item.");
+
+ let list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu"));
+ info("Got list after item was selected");
+
+ let chooseItem = list.firstChild.querySelector(".choose-app-item");
+ let dialogLoadedPromise = promiseLoadSubDialog("chrome://global/content/appPicker.xul");
+ let cmdEvent = win.document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
+ chooseItem.dispatchEvent(cmdEvent);
+
+ let dialog = yield dialogLoadedPromise;
+ info("Dialog loaded");
+
+ let dialogDoc = dialog.document;
+ let dialogList = dialogDoc.getElementById("app-picker-listbox");
+ dialogList.selectItem(dialogList.firstChild);
+ let selectedApp = dialogList.firstChild.handlerApp;
+ dialogDoc.documentElement.acceptDialog();
+
+ // Verify results are correct in mime service:
+ let mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ ok(mimeInfo.preferredApplicationHandler.equals(selectedApp), "App should be set as preferred.");
+
+ // Check that we display this result:
+ list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu"));
+ info("Got list after item was selected");
+ ok(list.selectedItem, "Should have a selected item");
+ ok(mimeInfo.preferredApplicationHandler.equals(list.selectedItem.handlerApp),
+ "App should be visible as preferred item.");
+
+
+ // Now try to 'manage' this list:
+ dialogLoadedPromise = promiseLoadSubDialog("chrome://browser/content/preferences/applicationManager.xul");
+
+ let manageItem = list.firstChild.querySelector(".manage-app-item");
+ cmdEvent = win.document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, win, 0, false, false, false, false, null);
+ manageItem.dispatchEvent(cmdEvent);
+
+ dialog = yield dialogLoadedPromise;
+ info("Dialog loaded the second time");
+
+ dialogDoc = dialog.document;
+ dialogList = dialogDoc.getElementById("appList");
+ let itemToRemove = dialogList.querySelector('listitem[label="' + selectedApp.name + '"]');
+ dialogList.selectItem(itemToRemove);
+ let itemsBefore = dialogList.children.length;
+ dialogDoc.getElementById("remove").click();
+ ok(!itemToRemove.parentNode, "Item got removed from DOM");
+ is(dialogList.children.length, itemsBefore - 1, "Item got removed");
+ dialogDoc.documentElement.acceptDialog();
+
+ // Verify results are correct in mime service:
+ mimeInfo = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ ok(!mimeInfo.preferredApplicationHandler, "App should no longer be set as preferred.");
+
+ // Check that we display this result:
+ list = yield waitForCondition(() => win.document.getAnonymousElementByAttribute(ourItem, "class", "actionsMenu"));
+ ok(list.selectedItem, "Should have a selected item");
+ ok(!list.selectedItem.handlerApp,
+ "No app should be visible as preferred item.");
+
+ gBrowser.removeCurrentTab();
+});
+
+registerCleanupFunction(function() {
+ let infoToModify = gMimeSvc.getFromTypeAndExtension("text/x-test-handler", null);
+ gHandlerSvc.remove(infoToModify);
+});
+
diff --git a/browser/components/preferences/in-content/tests/browser_connection.js b/browser/components/preferences/in-content/tests/browser_connection.js
new file mode 100644
index 000000000..50438aed1
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_connection.js
@@ -0,0 +1,99 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent 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/Services.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // network.proxy.type needs to be backed up and restored because mochitest
+ // changes this setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function() {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("network.proxy.no_proxies_on");
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+ });
+
+ let connectionURL = "chrome://browser/content/preferences/connection.xul";
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ so it has to be opened as a sub dialog of the main pref tab.
+ Open the main tab here.
+ */
+ open_preferences(Task.async(function* tabOpened(aContentWindow) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+ let dialog = yield openAndLoadSubDialog(connectionURL);
+ let dialogClosingPromise = waitForEvent(dialog.document.documentElement, "dialogclosing");
+
+ ok(dialog, "connection window opened");
+ runConnectionTests(dialog);
+ dialog.document.documentElement.acceptDialog();
+
+ let dialogClosingEvent = yield dialogClosingPromise;
+ ok(dialogClosingEvent, "connection window closed");
+ // runConnectionTests will have changed this pref - make sure it was
+ // sanitized correctly when the dialog was accepted
+ is(Services.prefs.getCharPref("network.proxy.no_proxies_on"),
+ ".a.com,.b.com,.c.com", "no_proxies_on pref has correct value");
+ gBrowser.removeCurrentTab();
+ finish();
+ }));
+}
+
+// run a bunch of tests on the window containing connection.xul
+function runConnectionTests(win) {
+ let doc = win.document;
+ let networkProxyNone = doc.getElementById("networkProxyNone");
+ let networkProxyNonePref = doc.getElementById("network.proxy.no_proxies_on");
+ let networkProxyTypePref = doc.getElementById("network.proxy.type");
+
+ // make sure the networkProxyNone textbox is formatted properly
+ is(networkProxyNone.getAttribute("multiline"), "true",
+ "networkProxyNone textbox is multiline");
+ is(networkProxyNone.getAttribute("rows"), "2",
+ "networkProxyNone textbox has two rows");
+
+ // check if sanitizing the given input for the no_proxies_on pref results in
+ // expected string
+ function testSanitize(input, expected, errorMessage) {
+ networkProxyNonePref.value = input;
+ win.gConnectionsDialog.sanitizeNoProxiesPref();
+ is(networkProxyNonePref.value, expected, errorMessage);
+ }
+
+ // change this pref so proxy exceptions are actually configurable
+ networkProxyTypePref.value = 1;
+ is(networkProxyNone.disabled, false, "networkProxyNone textbox is enabled");
+
+ testSanitize(".a.com", ".a.com",
+ "sanitize doesn't mess up single filter");
+ testSanitize(".a.com, .b.com, .c.com", ".a.com, .b.com, .c.com",
+ "sanitize doesn't mess up multiple comma/space sep filters");
+ testSanitize(".a.com\n.b.com", ".a.com,.b.com",
+ "sanitize turns line break into comma");
+ testSanitize(".a.com,\n.b.com", ".a.com,.b.com",
+ "sanitize doesn't add duplicate comma after comma");
+ testSanitize(".a.com\n,.b.com", ".a.com,.b.com",
+ "sanitize doesn't add duplicate comma before comma");
+ testSanitize(".a.com,\n,.b.com", ".a.com,,.b.com",
+ "sanitize doesn't add duplicate comma surrounded by commas");
+ testSanitize(".a.com, \n.b.com", ".a.com, .b.com",
+ "sanitize doesn't add comma after comma/space");
+ testSanitize(".a.com\n .b.com", ".a.com, .b.com",
+ "sanitize adds comma before space");
+ testSanitize(".a.com\n\n\n;;\n;\n.b.com", ".a.com,.b.com",
+ "sanitize only adds one comma per substring of bad chars");
+ testSanitize(".a.com,,.b.com", ".a.com,,.b.com",
+ "duplicate commas from user are untouched");
+ testSanitize(".a.com\n.b.com\n.c.com,\n.d.com,\n.e.com",
+ ".a.com,.b.com,.c.com,.d.com,.e.com",
+ "sanitize replaces things globally");
+
+ // will check that this was sanitized properly after window closes
+ networkProxyNonePref.value = ".a.com;.b.com\n.c.com";
+}
diff --git a/browser/components/preferences/in-content/tests/browser_connection_bug388287.js b/browser/components/preferences/in-content/tests/browser_connection_bug388287.js
new file mode 100644
index 000000000..5a348876e
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_connection_bug388287.js
@@ -0,0 +1,125 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ const connectionURL = "chrome://browser/content/preferences/connection.xul";
+ let closeable = false;
+ let finalTest = false;
+
+ // The changed preferences need to be backed up and restored because this mochitest
+ // changes them setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function() {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
+ for (let proxyType of ["http", "ssl", "ftp", "socks"]) {
+ Services.prefs.clearUserPref("network.proxy." + proxyType);
+ Services.prefs.clearUserPref("network.proxy." + proxyType + "_port");
+ if (proxyType == "http") {
+ continue;
+ }
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType + "_port");
+ }
+ });
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ so it has to be opened as a sub dialog of the main pref tab.
+ Open the main tab here.
+ */
+ open_preferences(Task.async(function* tabOpened(aContentWindow) {
+ let dialog, dialogClosingPromise;
+ let doc, proxyTypePref, sharePref, httpPref, httpPortPref, ftpPref, ftpPortPref;
+
+ // Convenient function to reset the variables for the new window
+ function* setDoc() {
+ if (closeable) {
+ let dialogClosingEvent = yield dialogClosingPromise;
+ ok(dialogClosingEvent, "Connection dialog closed");
+ }
+
+ if (finalTest) {
+ gBrowser.removeCurrentTab();
+ finish();
+ return;
+ }
+
+ dialog = yield openAndLoadSubDialog(connectionURL);
+ dialogClosingPromise = waitForEvent(dialog.document.documentElement, "dialogclosing");
+
+ doc = dialog.document;
+ proxyTypePref = doc.getElementById("network.proxy.type");
+ sharePref = doc.getElementById("network.proxy.share_proxy_settings");
+ httpPref = doc.getElementById("network.proxy.http");
+ httpPortPref = doc.getElementById("network.proxy.http_port");
+ ftpPref = doc.getElementById("network.proxy.ftp");
+ ftpPortPref = doc.getElementById("network.proxy.ftp_port");
+ }
+
+ // This batch of tests should not close the dialog
+ yield setDoc();
+
+ // Testing HTTP port 0 with share on
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ httpPref.value = "localhost";
+ httpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // Testing HTTP port 0 + FTP port 80 with share off
+ sharePref.value = false;
+ ftpPref.value = "localhost";
+ ftpPortPref.value = 80;
+ doc.documentElement.acceptDialog();
+
+ // Testing HTTP port 80 + FTP port 0 with share off
+ httpPortPref.value = 80;
+ ftpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // From now on, the dialog should close since we are giving it legitimate inputs.
+ // The test will timeout if the onbeforeaccept kicks in erroneously.
+ closeable = true;
+
+ // Both ports 80, share on
+ httpPortPref.value = 80;
+ ftpPortPref.value = 80;
+ doc.documentElement.acceptDialog();
+
+ // HTTP 80, FTP 0, with share on
+ yield setDoc();
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ ftpPref.value = "localhost";
+ httpPref.value = "localhost";
+ httpPortPref.value = 80;
+ ftpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // HTTP host empty, port 0 with share on
+ yield setDoc();
+ proxyTypePref.value = 1;
+ sharePref.value = true;
+ httpPref.value = "";
+ httpPortPref.value = 0;
+ doc.documentElement.acceptDialog();
+
+ // HTTP 0, but in no proxy mode
+ yield setDoc();
+ proxyTypePref.value = 0;
+ sharePref.value = true;
+ httpPref.value = "localhost";
+ httpPortPref.value = 0;
+
+ // This is the final test, don't spawn another connection window
+ finalTest = true;
+ doc.documentElement.acceptDialog();
+ yield setDoc();
+ }));
+}
diff --git a/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js b/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js
new file mode 100644
index 000000000..89313d736
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_cookies_exceptions.js
@@ -0,0 +1,348 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(3);
+ testRunner.runTests();
+}
+
+var testRunner = {
+
+ tests:
+ [
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnAllow.doCommand();
+ is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
+ is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission text should be set correctly");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnBlock.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
+ "permission should change to deny in UI");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnAllow.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission should revert back to allow");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "test.com";
+ params.btnRemove.doCommand();
+ is(params.tree.view.rowCount, 0, "exception should be removed");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://test.com", data: "deleted" }],
+ },
+ {
+ expectPermObservancesDuringTestFunction: true,
+ test: function(params) {
+ let uri = params.ioService.newURI("http://test.com", null, null);
+ params.pm.add(uri, "popup", Ci.nsIPermissionManager.DENY_ACTION);
+ is(params.tree.view.rowCount, 0, "adding unrelated permission should not change display");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "popup", origin: "http://test.com", data: "added",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ cleanUp: function(params) {
+ let uri = params.ioService.newURI("http://test.com", null, null);
+ params.pm.remove(uri, "popup");
+ },
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
+ is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission text should be set correctly");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnBlock.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
+ "permission should change to deny in UI");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission should revert back to allow");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "https://test.com:12345";
+ params.btnRemove.doCommand();
+ is(params.tree.view.rowCount, 0, "exception should be removed");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "https://test.com:12345", data: "deleted" }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
+ is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission text should be set correctly");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnBlock.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
+ "permission should change to deny in UI");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.DENY_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnAllow.doCommand();
+ is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
+ "origin name should be set correctly");
+ is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
+ "permission should revert back to allow");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "changed",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ },
+ {
+ test: function(params) {
+ params.url.value = "localhost:12345";
+ params.btnRemove.doCommand();
+ is(params.tree.view.rowCount, 0, "exception should be removed");
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://localhost:12345", data: "deleted" }],
+ },
+ {
+ expectPermObservancesDuringTestFunction: true,
+ test(params) {
+ for (let URL of ["http://a", "http://z", "http://b"]) {
+ let URI = params.ioService.newURI(URL, null, null);
+ params.pm.add(URI, "cookie", Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+
+ is(params.tree.view.rowCount, 3, "Three permissions should be present");
+ is(params.tree.view.getCellText(0, params.nameCol), "http://a",
+ "site should be sorted. 'a' should be first");
+ is(params.tree.view.getCellText(1, params.nameCol), "http://b",
+ "site should be sorted. 'b' should be second");
+ is(params.tree.view.getCellText(2, params.nameCol), "http://z",
+ "site should be sorted. 'z' should be third");
+
+ // Sort descending then check results in cleanup since sorting isn't synchronous.
+ EventUtils.synthesizeMouseAtCenter(params.doc.getElementById("siteCol"), {},
+ params.doc.defaultView);
+ params.btnApplyChanges.doCommand();
+ },
+ observances: [{ type: "cookie", origin: "http://a", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION },
+ { type: "cookie", origin: "http://z", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION },
+ { type: "cookie", origin: "http://b", data: "added",
+ capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
+ cleanUp(params) {
+ is(params.tree.view.getCellText(0, params.nameCol), "http://z",
+ "site should be sorted. 'z' should be first");
+ is(params.tree.view.getCellText(1, params.nameCol), "http://b",
+ "site should be sorted. 'b' should be second");
+ is(params.tree.view.getCellText(2, params.nameCol), "http://a",
+ "site should be sorted. 'a' should be third");
+
+ for (let URL of ["http://a", "http://z", "http://b"]) {
+ let uri = params.ioService.newURI(URL, null, null);
+ params.pm.remove(uri, "cookie");
+ }
+ },
+ },
+ ],
+
+ _currentTest: -1,
+
+ runTests: function() {
+ this._currentTest++;
+
+ info("Running test #" + (this._currentTest + 1) + "\n");
+ let that = this;
+ let p = this.runCurrentTest(this._currentTest + 1);
+ p.then(function() {
+ if (that._currentTest == that.tests.length - 1) {
+ finish();
+ }
+ else {
+ that.runTests();
+ }
+ });
+ },
+
+ runCurrentTest: function(testNumber) {
+ return new Promise(function(resolve, reject) {
+
+ let helperFunctions = {
+ windowLoad: function(win) {
+ let doc = win.document;
+ let params = {
+ doc,
+ tree: doc.getElementById("permissionsTree"),
+ nameCol: doc.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(0),
+ statusCol: doc.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(1),
+ url: doc.getElementById("url"),
+ btnAllow: doc.getElementById("btnAllow"),
+ btnBlock: doc.getElementById("btnBlock"),
+ btnApplyChanges: doc.getElementById("btnApplyChanges"),
+ btnRemove: doc.getElementById("removePermission"),
+ pm: Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager),
+ ioService: Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService),
+ allowText: win.gPermissionManager._getCapabilityString(
+ Ci.nsIPermissionManager.ALLOW_ACTION),
+ denyText: win.gPermissionManager._getCapabilityString(
+ Ci.nsIPermissionManager.DENY_ACTION),
+ allow: Ci.nsIPermissionManager.ALLOW_ACTION,
+ deny: Ci.nsIPermissionManager.DENY_ACTION,
+ };
+
+ let permObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "perm-changed")
+ return;
+
+ if (testRunner.tests[testRunner._currentTest].observances.length == 0) {
+ // Should fail here as we are not expecting a notification, but we don't.
+ // See bug 1063410.
+ return;
+ }
+
+ let permission = aSubject.QueryInterface(Ci.nsIPermission);
+ let expected = testRunner.tests[testRunner._currentTest].observances.shift();
+
+ is(aData, expected.data, "type of message should be the same");
+ for (let prop of ["type", "capability"]) {
+ if (expected[prop])
+ is(permission[prop], expected[prop],
+ "property: \"" + prop + "\" should be equal");
+ }
+
+ if (expected.origin) {
+ is(permission.principal.origin, expected.origin,
+ "property: \"origin\" should be equal");
+ }
+
+ os.removeObserver(permObserver, "perm-changed");
+
+ let test = testRunner.tests[testRunner._currentTest];
+ if (!test.expectPermObservancesDuringTestFunction) {
+ if (test.cleanUp) {
+ test.cleanUp(params);
+ }
+
+ gBrowser.removeCurrentTab();
+ resolve();
+ }
+ },
+ };
+
+ let os = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+ os.addObserver(permObserver, "perm-changed", false);
+
+ if (testRunner._currentTest == 0) {
+ is(params.tree.view.rowCount, 0, "no cookie exceptions");
+ }
+
+ try {
+ let test = testRunner.tests[testRunner._currentTest];
+ test.test(params);
+ if (test.expectPermObservancesDuringTestFunction) {
+ if (test.cleanUp) {
+ test.cleanUp(params);
+ }
+
+ gBrowser.removeCurrentTab();
+ resolve();
+ }
+ } catch (ex) {
+ ok(false, "exception while running test #" +
+ testNumber + ": " + ex);
+ }
+ },
+ };
+
+ openPreferencesViaOpenPreferencesAPI("panePrivacy", null, {leaveOpen: true}).then(function() {
+ let doc = gBrowser.contentDocument;
+ let historyMode = doc.getElementById("historyMode");
+ historyMode.value = "custom";
+ historyMode.doCommand();
+ doc.getElementById("cookieExceptions").doCommand();
+
+ let subDialogURL = "chrome://browser/content/preferences/permissions.xul";
+ promiseLoadSubDialog(subDialogURL).then(function(win) {
+ helperFunctions.windowLoad(win);
+ });
+ });
+ });
+ },
+};
diff --git a/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js b/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js
new file mode 100644
index 000000000..b30b6d9e2
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_defaultbrowser_alwayscheck.js
@@ -0,0 +1,103 @@
+"use strict";
+
+const CHECK_DEFAULT_INITIAL = Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser");
+
+add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+ yield test_with_mock_shellservice({isDefault: false}, function*() {
+ let setDefaultPane = content.document.getElementById("setDefaultPane");
+ Assert.equal(setDefaultPane.selectedIndex, "0",
+ "The 'make default' pane should be visible when not default");
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(!alwaysCheck.checked, "Always Check is unchecked by default");
+ Assert.ok(!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "alwaysCheck pref should be false by default in test runs");
+
+ let setDefaultButton = content.document.getElementById("setDefaultButton");
+ setDefaultButton.click();
+ content.window.gMainPane.updateSetDefaultBrowser();
+
+ yield ContentTaskUtils.waitForCondition(() => alwaysCheck.checked,
+ "'Always Check' checkbox should get checked after clicking the 'Set Default' button");
+
+ Assert.ok(alwaysCheck.checked,
+ "Clicking 'Make Default' checks the 'Always Check' checkbox");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "Checking the checkbox should set the pref to true");
+ Assert.ok(alwaysCheck.disabled,
+ "'Always Check' checkbox is locked with default browser and alwaysCheck=true");
+ Assert.equal(setDefaultPane.selectedIndex, "1",
+ "The 'make default' pane should not be visible when default");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "checkDefaultBrowser pref is now enabled");
+ });
+
+ gBrowser.removeCurrentTab();
+ Services.prefs.clearUserPref("browser.shell.checkDefaultBrowser");
+});
+
+add_task(function* clicking_make_default_checks_alwaysCheck_checkbox() {
+ Services.prefs.lockPref("browser.shell.checkDefaultBrowser");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+
+ yield test_with_mock_shellservice({isDefault: false}, function*() {
+ let setDefaultPane = content.document.getElementById("setDefaultPane");
+ Assert.equal(setDefaultPane.selectedIndex, "0",
+ "The 'make default' pane should be visible when not default");
+ let alwaysCheck = content.document.getElementById("alwaysCheckDefault");
+ Assert.ok(alwaysCheck.disabled, "Always Check is disabled when locked");
+ Assert.ok(alwaysCheck.checked,
+ "Always Check is checked because defaultPref is true and pref is locked");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "alwaysCheck pref should ship with 'true' by default");
+
+ let setDefaultButton = content.document.getElementById("setDefaultButton");
+ setDefaultButton.click();
+ content.window.gMainPane.updateSetDefaultBrowser();
+
+ yield ContentTaskUtils.waitForCondition(() => setDefaultPane.selectedIndex == "1",
+ "Browser is now default");
+
+ Assert.ok(alwaysCheck.checked,
+ "'Always Check' is still checked because it's locked");
+ Assert.ok(alwaysCheck.disabled,
+ "'Always Check is disabled because it's locked");
+ Assert.ok(Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser"),
+ "The pref is locked and so doesn't get changed");
+ });
+
+ Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+ gBrowser.removeCurrentTab();
+});
+
+registerCleanupFunction(function() {
+ Services.prefs.unlockPref("browser.shell.checkDefaultBrowser");
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
+});
+
+function* test_with_mock_shellservice(options, testFn) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, options, function*(options) {
+ let doc = content.document;
+ let win = doc.defaultView;
+ win.oldShellService = win.getShellService();
+ let mockShellService = {
+ _isDefault: false,
+ isDefaultBrowser() {
+ return this._isDefault;
+ },
+ setDefaultBrowser() {
+ this._isDefault = true;
+ },
+ };
+ win.getShellService = function() {
+ return mockShellService;
+ }
+ mockShellService._isDefault = options.isDefault;
+ win.gMainPane.updateSetDefaultBrowser();
+ });
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, testFn);
+
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", CHECK_DEFAULT_INITIAL);
+}
diff --git a/browser/components/preferences/in-content/tests/browser_healthreport.js b/browser/components/preferences/in-content/tests/browser_healthreport.js
new file mode 100644
index 000000000..bbfae9707
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_healthreport.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
+
+function runPaneTest(fn) {
+ open_preferences((win) => {
+ let doc = win.document;
+ win.gotoPref("paneAdvanced");
+ let advancedPrefs = doc.getElementById("advancedPrefs");
+ let tab = doc.getElementById("dataChoicesTab");
+ advancedPrefs.selectedTab = tab;
+ fn(win, doc);
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+ resetPreferences();
+ registerCleanupFunction(resetPreferences);
+ runPaneTest(testBasic);
+}
+
+function testBasic(win, doc) {
+ is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), true,
+ "Health Report upload enabled on app first run.");
+
+ let checkbox = doc.getElementById("submitHealthReportBox");
+ ok(checkbox);
+ is(checkbox.checked, true, "Health Report checkbox is checked on app first run.");
+
+ checkbox.checked = false;
+ checkbox.doCommand();
+ is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), false,
+ "Unchecking checkbox opts out of FHR upload.");
+
+ checkbox.checked = true;
+ checkbox.doCommand();
+ is(Services.prefs.getBoolPref(FHR_UPLOAD_ENABLED), true,
+ "Checking checkbox allows FHR upload.");
+
+ win.close();
+ Services.prefs.lockPref(FHR_UPLOAD_ENABLED);
+ runPaneTest(testUploadDisabled);
+}
+
+function testUploadDisabled(win, doc) {
+ ok(Services.prefs.prefIsLocked(FHR_UPLOAD_ENABLED), "Upload enabled flag is locked.");
+ let checkbox = doc.getElementById("submitHealthReportBox");
+ is(checkbox.getAttribute("disabled"), "true", "Checkbox is disabled if upload flag is locked.");
+ Services.prefs.unlockPref(FHR_UPLOAD_ENABLED);
+
+ win.close();
+ finish();
+}
+
+function resetPreferences() {
+ Services.prefs.clearUserPref(FHR_UPLOAD_ENABLED);
+}
+
diff --git a/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
new file mode 100644
index 000000000..366454fcc
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_homepages_filter_aboutpreferences.js
@@ -0,0 +1,20 @@
+add_task(function*() {
+ is(gBrowser.currentURI.spec, "about:blank", "Test starts with about:blank open");
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home");
+ yield openPreferencesViaOpenPreferencesAPI("paneGeneral", null, {leaveOpen: true});
+ let doc = gBrowser.contentDocument;
+ is(gBrowser.currentURI.spec, "about:preferences#general",
+ "#general should be in the URI for about:preferences");
+ let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
+
+ let useCurrent = doc.getElementById("useCurrent");
+ useCurrent.click();
+
+ is(gBrowser.tabs.length, 3, "Three tabs should be open");
+ is(Services.prefs.getCharPref("browser.startup.homepage"), "about:blank|about:home",
+ "about:blank and about:home should be the only homepages set");
+
+ Services.prefs.setCharPref("browser.startup.homepage", oldHomepagePref);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js b/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js
new file mode 100644
index 000000000..68f9653f6
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_notifications_do_not_disturb.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+registerCleanupFunction(function() {
+ while (gBrowser.tabs[1])
+ gBrowser.removeTab(gBrowser.tabs[1]);
+});
+
+add_task(function*() {
+ let prefs = yield openPreferencesViaOpenPreferencesAPI("paneContent", undefined, {leaveOpen: true});
+ is(prefs.selectedPane, "paneContent", "Content pane was selected");
+
+ let doc = gBrowser.contentDocument;
+ let notificationsDoNotDisturbRow = doc.getElementById("notificationsDoNotDisturbRow");
+ if (notificationsDoNotDisturbRow.hidden) {
+ todo(false, "Do not disturb is not available on this platform");
+ return;
+ }
+
+ let alertService;
+ try {
+ alertService = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+ } catch (ex) {
+ ok(true, "Do not disturb is not available on this platform: " + ex.message);
+ return;
+ }
+
+ let checkbox = doc.getElementById("notificationsDoNotDisturb");
+ ok(!checkbox.checked, "Checkbox should not be checked by default");
+ ok(!alertService.manualDoNotDisturb, "Do not disturb should be off by default");
+
+ let checkboxChanged = waitForEvent(checkbox, "command")
+ checkbox.click();
+ yield checkboxChanged;
+ ok(alertService.manualDoNotDisturb, "Do not disturb should be enabled when checked");
+
+ checkboxChanged = waitForEvent(checkbox, "command")
+ checkbox.click();
+ yield checkboxChanged;
+ ok(!alertService.manualDoNotDisturb, "Do not disturb should be disabled when unchecked");
+});
diff --git a/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js b/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js
new file mode 100644
index 000000000..d9253735a
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_permissions_urlFieldHidden.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const PERMISSIONS_URL = "chrome://browser/content/preferences/permissions.xul";
+
+add_task(function* urlFieldVisibleForPopupPermissions(finish) {
+ yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ let popupPolicyCheckbox = doc.getElementById("popupPolicy");
+ ok(!popupPolicyCheckbox.checked, "popupPolicyCheckbox should be unchecked by default");
+ popupPolicyCheckbox.click();
+ let popupPolicyButton = doc.getElementById("popupPolicyButton");
+ ok(popupPolicyButton, "popupPolicyButton found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ popupPolicyButton.click();
+ let dialog = yield dialogPromise;
+ ok(dialog, "dialog loaded");
+
+ let urlLabel = dialog.document.getElementById("urlLabel");
+ ok(!urlLabel.hidden, "urlLabel should be visible when one of block/session/allow visible");
+ let url = dialog.document.getElementById("url");
+ ok(!url.hidden, "url should be visible when one of block/session/allow visible");
+
+ popupPolicyCheckbox.click();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* urlFieldHiddenForNotificationPermissions() {
+ yield openPreferencesViaOpenPreferencesAPI("paneContent", null, {leaveOpen: true});
+ let win = gBrowser.selectedBrowser.contentWindow;
+ let doc = win.document;
+ let notificationsPolicyButton = doc.getElementById("notificationsPolicyButton");
+ ok(notificationsPolicyButton, "notificationsPolicyButton found");
+ let dialogPromise = promiseLoadSubDialog(PERMISSIONS_URL);
+ notificationsPolicyButton.click();
+ let dialog = yield dialogPromise;
+ ok(dialog, "dialog loaded");
+
+ let urlLabel = dialog.document.getElementById("urlLabel");
+ ok(urlLabel.hidden, "urlLabel should be hidden as requested");
+ let url = dialog.document.getElementById("url");
+ ok(url.hidden, "url should be hidden as requested");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_1.js b/browser/components/preferences/in-content/tests/browser_privacypane_1.js
new file mode 100644
index 000000000..0df60c6ac
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_1.js
@@ -0,0 +1,18 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ test_pane_visibility,
+ test_dependent_elements,
+ test_dependent_cookie_elements,
+ test_dependent_clearonclose_elements,
+ test_dependent_prefs,
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_3.js b/browser/components/preferences/in-content/tests/browser_privacypane_3.js
new file mode 100644
index 000000000..8fe6f0825
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_3.js
@@ -0,0 +1,17 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ test_custom_retention("rememberHistory", "remember"),
+ test_custom_retention("rememberHistory", "custom"),
+ test_custom_retention("rememberForms", "remember"),
+ test_custom_retention("rememberForms", "custom"),
+ test_historymode_retention("remember", "remember"),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_4.js b/browser/components/preferences/in-content/tests/browser_privacypane_4.js
new file mode 100644
index 000000000..b7ef3deda
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_4.js
@@ -0,0 +1,25 @@
+requestLongerTimeout(2);
+
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+run_test_subset([
+ test_custom_retention("acceptCookies", "remember"),
+ test_custom_retention("acceptCookies", "custom"),
+ test_custom_retention("acceptThirdPartyMenu", "remember", "visited"),
+ test_custom_retention("acceptThirdPartyMenu", "custom", "always"),
+ test_custom_retention("keepCookiesUntil", "remember", 1),
+ test_custom_retention("keepCookiesUntil", "custom", 2),
+ test_custom_retention("keepCookiesUntil", "custom", 0),
+ test_custom_retention("alwaysClear", "remember"),
+ test_custom_retention("alwaysClear", "custom"),
+ test_historymode_retention("remember", "remember"),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_5.js b/browser/components/preferences/in-content/tests/browser_privacypane_5.js
new file mode 100644
index 000000000..a07530010
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_5.js
@@ -0,0 +1,17 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ test_locbar_suggestion_retention("history", true),
+ test_locbar_suggestion_retention("bookmark", true),
+ test_locbar_suggestion_retention("openpage", false),
+ test_locbar_suggestion_retention("history", true),
+ test_locbar_suggestion_retention("history", false),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_privacypane_8.js b/browser/components/preferences/in-content/tests/browser_privacypane_8.js
new file mode 100644
index 000000000..756b19a2f
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_8.js
@@ -0,0 +1,26 @@
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+let rootDir = getRootDirectory(gTestPath);
+let jar = getJar(rootDir);
+if (jar) {
+ let tmpdir = extractJarToTmp(jar);
+ rootDir = "file://" + tmpdir.path + '/';
+}
+loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
+
+run_test_subset([
+ // history mode should be initialized to remember
+ test_historymode_retention("remember", undefined),
+
+ // history mode should remain remember; toggle acceptCookies checkbox
+ test_custom_retention("acceptCookies", "remember"),
+
+ // history mode should now be custom; set history mode to dontremember
+ test_historymode_retention("dontremember", "custom"),
+
+ // history mode should remain custom; set history mode to remember
+ test_historymode_retention("remember", "custom"),
+
+ // history mode should now be remember
+ test_historymode_retention("remember", "remember"),
+]);
diff --git a/browser/components/preferences/in-content/tests/browser_proxy_backup.js b/browser/components/preferences/in-content/tests/browser_proxy_backup.js
new file mode 100644
index 000000000..3ad24c7ec
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_proxy_backup.js
@@ -0,0 +1,65 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent 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/Services.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // network.proxy.type needs to be backed up and restored because mochitest
+ // changes this setting from the default
+ let oldNetworkProxyType = Services.prefs.getIntPref("network.proxy.type");
+ registerCleanupFunction(function() {
+ Services.prefs.setIntPref("network.proxy.type", oldNetworkProxyType);
+ Services.prefs.clearUserPref("browser.preferences.instantApply");
+ Services.prefs.clearUserPref("network.proxy.share_proxy_settings");
+ for (let proxyType of ["http", "ssl", "ftp", "socks"]) {
+ Services.prefs.clearUserPref("network.proxy." + proxyType);
+ Services.prefs.clearUserPref("network.proxy." + proxyType + "_port");
+ if (proxyType == "http") {
+ continue;
+ }
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType);
+ Services.prefs.clearUserPref("network.proxy.backup." + proxyType + "_port");
+ }
+ });
+
+ let connectionURL = "chrome://browser/content/preferences/connection.xul";
+
+ // Set a shared proxy and a SOCKS backup
+ Services.prefs.setIntPref("network.proxy.type", 1);
+ Services.prefs.setBoolPref("network.proxy.share_proxy_settings", true);
+ Services.prefs.setCharPref("network.proxy.http", "example.com");
+ Services.prefs.setIntPref("network.proxy.http_port", 1200);
+ Services.prefs.setCharPref("network.proxy.socks", "example.com");
+ Services.prefs.setIntPref("network.proxy.socks_port", 1200);
+ Services.prefs.setCharPref("network.proxy.backup.socks", "127.0.0.1");
+ Services.prefs.setIntPref("network.proxy.backup.socks_port", 9050);
+
+ /*
+ The connection dialog alone won't save onaccept since it uses type="child",
+ so it has to be opened as a sub dialog of the main pref tab.
+ Open the main tab here.
+ */
+ open_preferences(Task.async(function* tabOpened(aContentWindow) {
+ is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
+ let dialog = yield openAndLoadSubDialog(connectionURL);
+ let dialogClosingPromise = waitForEvent(dialog.document.documentElement, "dialogclosing");
+
+ ok(dialog, "connection window opened");
+ dialog.document.documentElement.acceptDialog();
+
+ let dialogClosingEvent = yield dialogClosingPromise;
+ ok(dialogClosingEvent, "connection window closed");
+
+ // The SOCKS backup should not be replaced by the shared value
+ is(Services.prefs.getCharPref("network.proxy.backup.socks"), "127.0.0.1", "Shared proxy backup shouldn't be replaced");
+ is(Services.prefs.getIntPref("network.proxy.backup.socks_port"), 9050, "Shared proxy port backup shouldn't be replaced");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }));
+}
diff --git a/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js b/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js
new file mode 100644
index 000000000..6b587e036
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_sanitizeOnShutdown_prefLocked.js
@@ -0,0 +1,37 @@
+"use strict";
+
+function switchToCustomHistoryMode(doc) {
+ // Select the last item in the menulist.
+ let menulist = doc.getElementById("historyMode");
+ menulist.focus();
+ EventUtils.sendKey("UP");
+}
+
+function testPrefStateMatchesLockedState() {
+ let win = gBrowser.contentWindow;
+ let doc = win.document;
+ switchToCustomHistoryMode(doc);
+
+ let checkbox = doc.getElementById("alwaysClear");
+ let preference = doc.getElementById("privacy.sanitize.sanitizeOnShutdown");
+ is(checkbox.disabled, preference.locked, "Always Clear checkbox should be enabled when preference is not locked.");
+
+ gBrowser.removeCurrentTab();
+}
+
+add_task(function setup() {
+ registerCleanupFunction(function resetPreferences() {
+ Services.prefs.unlockPref("privacy.sanitize.sanitizeOnShutdown");
+ });
+});
+
+add_task(function* test_preference_enabled_when_unlocked() {
+ yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
+ testPrefStateMatchesLockedState();
+});
+
+add_task(function* test_preference_disabled_when_locked() {
+ Services.prefs.lockPref("privacy.sanitize.sanitizeOnShutdown");
+ yield openPreferencesViaOpenPreferencesAPI("panePrivacy", undefined, {leaveOpen: true});
+ testPrefStateMatchesLockedState();
+});
diff --git a/browser/components/preferences/in-content/tests/browser_searchsuggestions.js b/browser/components/preferences/in-content/tests/browser_searchsuggestions.js
new file mode 100644
index 000000000..0185a23b9
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_searchsuggestions.js
@@ -0,0 +1,43 @@
+var original = Services.prefs.getBoolPref("browser.search.suggest.enabled");
+
+registerCleanupFunction(() => {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", original);
+});
+
+// Open with suggestions enabled
+add_task(function*() {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
+
+ yield openPreferencesViaOpenPreferencesAPI("search", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let urlbarBox = doc.getElementById("urlBarSuggestion");
+ ok(!urlbarBox.disabled, "Checkbox should be enabled");
+
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
+
+ ok(urlbarBox.disabled, "Checkbox should be disabled");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Open with suggestions disabled
+add_task(function*() {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
+
+ yield openPreferencesViaOpenPreferencesAPI("search", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let urlbarBox = doc.getElementById("urlBarSuggestion");
+ ok(urlbarBox.disabled, "Checkbox should be disabled");
+
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
+
+ ok(!urlbarBox.disabled, "Checkbox should be enabled");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", original);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_security.js b/browser/components/preferences/in-content/tests/browser_security.js
new file mode 100644
index 000000000..e6eb2a91d
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_security.js
@@ -0,0 +1,130 @@
+const PREFS = [
+ "browser.safebrowsing.phishing.enabled",
+ "browser.safebrowsing.malware.enabled",
+
+ "browser.safebrowsing.downloads.enabled",
+
+ "browser.safebrowsing.downloads.remote.block_potentially_unwanted",
+ "browser.safebrowsing.downloads.remote.block_uncommon"
+];
+
+let originals = PREFS.map(pref => [pref, Services.prefs.getBoolPref(pref)])
+let originalMalwareTable = Services.prefs.getCharPref("urlclassifier.malwareTable");
+registerCleanupFunction(function() {
+ originals.forEach(([pref, val]) => Services.prefs.setBoolPref(pref, val))
+ Services.prefs.setCharPref("urlclassifier.malwareTable", originalMalwareTable);
+});
+
+// test the safebrowsing preference
+add_task(function*() {
+ function* checkPrefSwitch(val1, val2) {
+ Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", val1);
+ Services.prefs.setBoolPref("browser.safebrowsing.malware.enabled", val2);
+
+ yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("enableSafeBrowsing");
+ let blockDownloads = doc.getElementById("blockDownloads");
+ let blockUncommon = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(checked, val1 && val2, "safebrowsing preference is initialized correctly");
+ // should be disabled when checked is false (= pref is turned off)
+ is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
+ is(blockUncommon.hasAttribute("disabled"), !checked, "block uncommon checkbox is set correctly");
+
+ // click the checkbox
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
+
+ // check that both settings are now turned on or off
+ is(Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled"), !checked,
+ "safebrowsing.enabled is set correctly");
+ is(Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled"), !checked,
+ "safebrowsing.malware.enabled is set correctly");
+
+ // check if the other checkboxes have updated
+ checked = checkbox.checked;
+ is(blockDownloads.hasAttribute("disabled"), !checked, "block downloads checkbox is set correctly");
+ is(blockUncommon.hasAttribute("disabled"), !checked || !blockDownloads.checked, "block uncommon checkbox is set correctly");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ yield checkPrefSwitch(true, true);
+ yield checkPrefSwitch(false, true);
+ yield checkPrefSwitch(true, false);
+ yield checkPrefSwitch(false, false);
+});
+
+// test the download protection preference
+add_task(function*() {
+ function* checkPrefSwitch(val) {
+ Services.prefs.setBoolPref("browser.safebrowsing.downloads.enabled", val);
+
+ yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("blockDownloads");
+ let blockUncommon = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(checked, val, "downloads preference is initialized correctly");
+ // should be disabled when val is false (= pref is turned off)
+ is(blockUncommon.hasAttribute("disabled"), !val, "block uncommon checkbox is set correctly");
+
+ // click the checkbox
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
+
+ // check that setting is now turned on or off
+ is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled"), !checked,
+ "safebrowsing.downloads preference is set correctly");
+
+ // check if the uncommon warning checkbox has updated
+ is(blockUncommon.hasAttribute("disabled"), val, "block uncommon checkbox is set correctly");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ yield checkPrefSwitch(true);
+ yield checkPrefSwitch(false);
+});
+
+// test the unwanted/uncommon software warning preference
+add_task(function*() {
+ function* checkPrefSwitch(val1, val2) {
+ Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", val1);
+ Services.prefs.setBoolPref("browser.safebrowsing.downloads.remote.block_uncommon", val2);
+
+ yield openPreferencesViaOpenPreferencesAPI("security", undefined, { leaveOpen: true });
+
+ let doc = gBrowser.selectedBrowser.contentDocument;
+ let checkbox = doc.getElementById("blockUncommonUnwanted");
+ let checked = checkbox.checked;
+ is(checked, val1 && val2, "unwanted/uncommon preference is initialized correctly");
+
+ // click the checkbox
+ EventUtils.synthesizeMouseAtCenter(checkbox, {}, gBrowser.selectedBrowser.contentWindow);
+
+ // check that both settings are now turned on or off
+ is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.remote.block_potentially_unwanted"), !checked,
+ "block_potentially_unwanted is set correctly");
+ is(Services.prefs.getBoolPref("browser.safebrowsing.downloads.remote.block_uncommon"), !checked,
+ "block_uncommon is set correctly");
+
+ // when the preference is on, the malware table should include these ids
+ let malwareTable = Services.prefs.getCharPref("urlclassifier.malwareTable").split(",");
+ is(malwareTable.includes("goog-unwanted-shavar"), !checked,
+ "malware table doesn't include goog-unwanted-shavar");
+ is(malwareTable.includes("test-unwanted-simple"), !checked,
+ "malware table doesn't include test-unwanted-simple");
+ let sortedMalware = malwareTable.slice(0);
+ sortedMalware.sort();
+ Assert.deepEqual(malwareTable, sortedMalware, "malware table has been sorted");
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ }
+
+ yield* checkPrefSwitch(true, true);
+ yield* checkPrefSwitch(false, true);
+ yield* checkPrefSwitch(true, false);
+ yield* checkPrefSwitch(false, false);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_subdialogs.js b/browser/components/preferences/in-content/tests/browser_subdialogs.js
new file mode 100644
index 000000000..ff0c1f8ae
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -0,0 +1,293 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for the sub-dialog infrastructure, not for actual sub-dialog functionality.
+ */
+
+const gDialogURL = getRootDirectory(gTestPath) + "subdialog.xul";
+const gDialogURL2 = getRootDirectory(gTestPath) + "subdialog2.xul";
+
+function* open_subdialog_and_test_generic_start_state(browser, domcontentloadedFn, url = gDialogURL) {
+ let domcontentloadedFnStr = domcontentloadedFn ?
+ "(" + domcontentloadedFn.toString() + ")()" :
+ "";
+ return ContentTask.spawn(browser, {url, domcontentloadedFnStr}, function*(args) {
+ let {url, domcontentloadedFnStr} = args;
+ let rv = { acceptCount: 0 };
+ let win = content.window;
+ let subdialog = win.gSubDialog;
+ subdialog.open(url, null, rv);
+
+ info("waiting for subdialog DOMFrameContentLoaded");
+ yield ContentTaskUtils.waitForEvent(win, "DOMFrameContentLoaded", true);
+ let result;
+ if (domcontentloadedFnStr) {
+ result = eval(domcontentloadedFnStr);
+ }
+
+ info("waiting for subdialog load");
+ yield ContentTaskUtils.waitForEvent(subdialog._frame, "load");
+ info("subdialog window is loaded");
+
+ let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
+ for (let styleSheet of subdialog._frame.contentDocument.styleSheets) {
+ let index = expectedStyleSheetURLs.indexOf(styleSheet.href);
+ if (index >= 0) {
+ expectedStyleSheetURLs.splice(index, 1);
+ }
+ }
+
+ Assert.ok(!!subdialog._frame.contentWindow, "The dialog should be non-null");
+ Assert.notEqual(subdialog._frame.contentWindow.location.toString(), "about:blank",
+ "Subdialog URL should not be about:blank");
+ Assert.equal(win.getComputedStyle(subdialog._overlay, "").visibility, "visible",
+ "Overlay should be visible");
+ Assert.equal(expectedStyleSheetURLs.length, 0,
+ "No stylesheets that were expected are missing");
+ return result;
+ });
+}
+
+function* close_subdialog_and_test_generic_end_state(browser, closingFn, closingButton, acceptCount, options) {
+ let dialogclosingPromise = ContentTask.spawn(browser, {closingButton, acceptCount}, function*(expectations) {
+ let win = content.window;
+ let subdialog = win.gSubDialog;
+ let frame = subdialog._frame;
+ info("waiting for dialogclosing");
+ let closingEvent =
+ yield ContentTaskUtils.waitForEvent(frame.contentWindow, "dialogclosing");
+ let closingButton = closingEvent.detail.button;
+ let actualAcceptCount = frame.contentWindow.arguments &&
+ frame.contentWindow.arguments[0].acceptCount;
+
+ info("waiting for about:blank load");
+ yield ContentTaskUtils.waitForEvent(frame, "load");
+
+ Assert.notEqual(win.getComputedStyle(subdialog._overlay, "").visibility, "visible",
+ "overlay is not visible");
+ Assert.equal(frame.getAttribute("style"), "", "inline styles should be cleared");
+ Assert.equal(frame.contentWindow.location.href.toString(), "about:blank",
+ "sub-dialog should be unloaded");
+ Assert.equal(closingButton, expectations.closingButton,
+ "closing event should indicate button was '" + expectations.closingButton + "'");
+ Assert.equal(actualAcceptCount, expectations.acceptCount,
+ "should be 1 if accepted, 0 if canceled, undefined if closed w/out button");
+ });
+
+ if (options && options.runClosingFnOutsideOfContentTask) {
+ yield closingFn();
+ } else {
+ ContentTask.spawn(browser, null, closingFn);
+ }
+
+ yield dialogclosingPromise;
+}
+
+let tab;
+
+add_task(function* test_initialize() {
+ tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences");
+});
+
+add_task(function* check_titlebar_focus_returnval_titlechanges_accepting() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ let domtitlechangedPromise = BrowserTestUtils.waitForEvent(tab.linkedBrowser, "DOMTitleChanged");
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let dialog = content.window.gSubDialog._frame.contentWindow;
+ let dialogTitleElement = content.document.getElementById("dialogTitle");
+ Assert.equal(dialogTitleElement.textContent, "Sample sub-dialog",
+ "Title should be correct initially");
+ Assert.equal(dialog.document.activeElement.value, "Default text",
+ "Textbox with correct text is focused");
+ dialog.document.title = "Updated title";
+ });
+
+ info("waiting for DOMTitleChanged event");
+ yield domtitlechangedPromise;
+
+ ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let dialogTitleElement = content.document.getElementById("dialogTitle");
+ Assert.equal(dialogTitleElement.textContent, "Updated title",
+ "subdialog should have updated title");
+ });
+
+ // Accept the dialog
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+ "accept", 1);
+});
+
+add_task(function* check_canceling_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.cancelDialog(); },
+ "cancel", 0);
+});
+
+add_task(function* check_reopening_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ info("opening another dialog which will close the first");
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, "", gDialogURL2);
+ info("closing as normal");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+ "accept", 1);
+});
+
+add_task(function* check_opening_while_closing() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ info("closing");
+ content.window.gSubDialog.close();
+ info("reopening immediately after calling .close()");
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentDocument.documentElement.acceptDialog(); },
+ "accept", 1);
+
+});
+
+add_task(function* window_close_on_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* click_close_button_on_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { return BrowserTestUtils.synthesizeMouseAtCenter("#dialogClose", {}, tab.linkedBrowser); },
+ null, 0, {runClosingFnOutsideOfContentTask: true});
+});
+
+add_task(function* back_navigation_on_subdialog_should_close_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.goBack(); },
+ null, undefined);
+});
+
+add_task(function* back_navigation_on_browser_tab_should_close_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { tab.linkedBrowser.goBack(); },
+ null, undefined, {runClosingFnOutsideOfContentTask: true});
+});
+
+add_task(function* escape_should_close_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ info("canceling the dialog");
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { return BrowserTestUtils.synthesizeKey("VK_ESCAPE", {}, tab.linkedBrowser); },
+ "cancel", 0, {runClosingFnOutsideOfContentTask: true});
+});
+
+add_task(function* correct_width_and_height_should_be_used_for_dialog() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser);
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let frameStyle = content.window.gSubDialog._frame.style;
+ Assert.equal(frameStyle.width, "32em",
+ "Width should be set on the frame from the dialog");
+ Assert.equal(frameStyle.height, "5em",
+ "Height should be set on the frame from the dialog");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* wrapped_text_in_dialog_should_have_expected_scrollHeight() {
+ let oldHeight = yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._frame;
+ let doc = frame.contentDocument;
+ let oldHeight = doc.documentElement.scrollHeight;
+ doc.documentElement.style.removeProperty("height");
+ doc.getElementById("desc").textContent = `
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi
+ architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas
+ sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione
+ voluptatem sequi nesciunt.`
+ return oldHeight;
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, oldHeight, function*(oldHeight) {
+ let frame = content.window.gSubDialog._frame;
+ let docEl = frame.contentDocument.documentElement;
+ Assert.equal(frame.style.width, "32em",
+ "Width should be set on the frame from the dialog");
+ Assert.ok(docEl.scrollHeight > oldHeight,
+ "Content height increased (from " + oldHeight + " to " + docEl.scrollHeight + ").");
+ Assert.equal(frame.style.height, docEl.scrollHeight + "px",
+ "Height on the frame should be higher now");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* dialog_too_tall_should_get_reduced_in_height() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._frame;
+ frame.contentDocument.documentElement.style.height = '100000px';
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let frame = content.window.gSubDialog._frame;
+ Assert.equal(frame.style.width, "32em", "Width should be set on the frame from the dialog");
+ Assert.ok(parseInt(frame.style.height, 10) < content.window.innerHeight,
+ "Height on the frame should be smaller than window's innerHeight");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* scrollWidth_and_scrollHeight_from_subdialog_should_size_the_browser() {
+ yield open_subdialog_and_test_generic_start_state(tab.linkedBrowser, function domcontentloadedFn() {
+ let frame = content.window.gSubDialog._frame;
+ frame.contentDocument.documentElement.style.removeProperty("height");
+ frame.contentDocument.documentElement.style.removeProperty("width");
+ });
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function*() {
+ let frame = content.window.gSubDialog._frame;
+ Assert.ok(frame.style.width.endsWith("px"),
+ "Width (" + frame.style.width + ") should be set to a px value of the scrollWidth from the dialog");
+ Assert.ok(frame.style.height.endsWith("px"),
+ "Height (" + frame.style.height + ") should be set to a px value of the scrollHeight from the dialog");
+ });
+
+ yield close_subdialog_and_test_generic_end_state(tab.linkedBrowser,
+ function() { content.window.gSubDialog._frame.contentWindow.window.close(); },
+ null, 0);
+});
+
+add_task(function* test_shutdown() {
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/preferences/in-content/tests/browser_telemetry.js b/browser/components/preferences/in-content/tests/browser_telemetry.js
new file mode 100644
index 000000000..d8139d87a
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_telemetry.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
+
+function runPaneTest(fn) {
+ open_preferences((win) => {
+ let doc = win.document;
+ win.gotoPref("paneAdvanced");
+ let advancedPrefs = doc.getElementById("advancedPrefs");
+ let tab = doc.getElementById("dataChoicesTab");
+ advancedPrefs.selectedTab = tab;
+ fn(win, doc);
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+ resetPreferences();
+ registerCleanupFunction(resetPreferences);
+ runPaneTest(testTelemetryState);
+}
+
+function testTelemetryState(win, doc) {
+ let fhrCheckbox = doc.getElementById("submitHealthReportBox");
+ Assert.ok(fhrCheckbox.checked, "Health Report checkbox is checked on app first run.");
+
+ let telmetryCheckbox = doc.getElementById("submitTelemetryBox");
+ Assert.ok(!telmetryCheckbox.disabled,
+ "Telemetry checkbox must be enabled if FHR is checked.");
+ Assert.ok(Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED),
+ "Telemetry must be enabled if the checkbox is ticked.");
+
+ // Uncheck the FHR checkbox and make sure that Telemetry checkbox gets disabled.
+ fhrCheckbox.click();
+
+ Assert.ok(telmetryCheckbox.disabled,
+ "Telemetry checkbox must be disabled if FHR is unchecked.");
+ Assert.ok(!Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED),
+ "Telemetry must be disabled if the checkbox is unticked.");
+
+ win.close();
+ finish();
+}
+
+function resetPreferences() {
+ Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
+ Services.prefs.clearUserPref(PREF_TELEMETRY_ENABLED);
+}
+
diff --git a/browser/components/preferences/in-content/tests/head.js b/browser/components/preferences/in-content/tests/head.js
new file mode 100644
index 000000000..0ed811e94
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/head.js
@@ -0,0 +1,165 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Promise.jsm");
+
+const kDefaultWait = 2000;
+
+function is_hidden(aElement) {
+ var style = aElement.ownerGlobal.getComputedStyle(aElement);
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+
+ // Hiding a parent element will hide all its children
+ if (aElement.parentNode != aElement.ownerDocument)
+ return is_hidden(aElement.parentNode);
+
+ return false;
+}
+
+function is_element_visible(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(!is_hidden(aElement), aMsg);
+}
+
+function is_element_hidden(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(is_hidden(aElement), aMsg);
+}
+
+function open_preferences(aCallback) {
+ gBrowser.selectedTab = gBrowser.addTab("about:preferences");
+ let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ newTabBrowser.addEventListener("Initialized", function () {
+ newTabBrowser.removeEventListener("Initialized", arguments.callee, true);
+ aCallback(gBrowser.contentWindow);
+ }, true);
+}
+
+function openAndLoadSubDialog(aURL, aFeatures = null, aParams = null, aClosingCallback = null) {
+ let promise = promiseLoadSubDialog(aURL);
+ content.gSubDialog.open(aURL, aFeatures, aParams, aClosingCallback);
+ return promise;
+}
+
+function promiseLoadSubDialog(aURL) {
+ return new Promise((resolve, reject) => {
+ content.gSubDialog._frame.addEventListener("load", function load(aEvent) {
+ if (aEvent.target.contentWindow.location == "about:blank")
+ return;
+ content.gSubDialog._frame.removeEventListener("load", load);
+
+ is(content.gSubDialog._frame.contentWindow.location.toString(), aURL,
+ "Check the proper URL is loaded");
+
+ // Check visibility
+ is_element_visible(content.gSubDialog._overlay, "Overlay is visible");
+
+ // Check that stylesheets were injected
+ let expectedStyleSheetURLs = content.gSubDialog._injectedStyleSheets.slice(0);
+ for (let styleSheet of content.gSubDialog._frame.contentDocument.styleSheets) {
+ let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
+ if (i >= 0) {
+ info("found " + styleSheet.href);
+ expectedStyleSheetURLs.splice(i, 1);
+ }
+ }
+ is(expectedStyleSheetURLs.length, 0, "All expectedStyleSheetURLs should have been found");
+
+ resolve(content.gSubDialog._frame.contentWindow);
+ });
+ });
+}
+
+/**
+ * Waits a specified number of miliseconds for a specified event to be
+ * fired on a specified element.
+ *
+ * Usage:
+ * let receivedEvent = waitForEvent(element, "eventName");
+ * // Do some processing here that will cause the event to be fired
+ * // ...
+ * // Now yield until the Promise is fulfilled
+ * yield receivedEvent;
+ * if (receivedEvent && !(receivedEvent instanceof Error)) {
+ * receivedEvent.msg == "eventName";
+ * // ...
+ * }
+ *
+ * @param aSubject the element that should receive the event
+ * @param aEventName the event to wait for
+ * @param aTimeoutMs the number of miliseconds to wait before giving up
+ * @returns a Promise that resolves to the received event, or to an Error
+ */
+function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) {
+ let eventDeferred = Promise.defer();
+ let timeoutMs = aTimeoutMs || kDefaultWait;
+ let stack = new Error().stack;
+ let timerID = setTimeout(function wfe_canceller() {
+ aSubject.removeEventListener(aEventName, listener);
+ eventDeferred.reject(new Error(aEventName + " event timeout at " + stack));
+ }, timeoutMs);
+
+ var listener = function (aEvent) {
+ if (aTarget && aTarget !== aEvent.target)
+ return;
+
+ // stop the timeout clock and resume
+ clearTimeout(timerID);
+ eventDeferred.resolve(aEvent);
+ };
+
+ function cleanup(aEventOrError) {
+ // unhook listener in case of success or failure
+ aSubject.removeEventListener(aEventName, listener);
+ return aEventOrError;
+ }
+ aSubject.addEventListener(aEventName, listener, false);
+ return eventDeferred.promise.then(cleanup, cleanup);
+}
+
+function openPreferencesViaOpenPreferencesAPI(aPane, aAdvancedTab, aOptions) {
+ let deferred = Promise.defer();
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ openPreferences(aPane, aAdvancedTab ? {advancedTab: aAdvancedTab} : undefined);
+ let newTabBrowser = gBrowser.selectedBrowser;
+
+ newTabBrowser.addEventListener("Initialized", function PrefInit() {
+ newTabBrowser.removeEventListener("Initialized", PrefInit, true);
+ newTabBrowser.contentWindow.addEventListener("load", function prefLoad() {
+ newTabBrowser.contentWindow.removeEventListener("load", prefLoad);
+ let win = gBrowser.contentWindow;
+ let selectedPane = win.history.state;
+ let doc = win.document;
+ let selectedAdvancedTab = aAdvancedTab && doc.getElementById("advancedPrefs").selectedTab.id;
+ if (!aOptions || !aOptions.leaveOpen)
+ gBrowser.removeCurrentTab();
+ deferred.resolve({selectedPane: selectedPane, selectedAdvancedTab: selectedAdvancedTab});
+ });
+ }, true);
+
+ return deferred.promise;
+}
+
+function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
+ return new Promise((resolve, reject) => {
+ function tryNow() {
+ tries++;
+ let rv = aConditionFn();
+ if (rv) {
+ resolve(rv);
+ } else if (tries < aMaxTries) {
+ tryAgain();
+ } else {
+ reject("Condition timed out: " + aConditionFn.toSource());
+ }
+ }
+ function tryAgain() {
+ setTimeout(tryNow, aCheckInterval);
+ }
+ let tries = 0;
+ tryAgain();
+ });
+}
diff --git a/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
new file mode 100644
index 000000000..53c6d7d8a
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/privacypane_tests_perwindow.js
@@ -0,0 +1,330 @@
+function* runTestOnPrivacyPrefPane(testFunc) {
+ info("runTestOnPrivacyPrefPane entered");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:preferences", true, true);
+ let browser = tab.linkedBrowser;
+ info("loaded about:preferences");
+ browser.contentWindow.gotoPref("panePrivacy");
+ info("viewing privacy pane, executing testFunc");
+ testFunc(browser.contentWindow);
+ yield BrowserTestUtils.removeTab(tab);
+}
+
+function controlChanged(element) {
+ element.doCommand();
+}
+
+// We can only test the panes that don't trigger a preference update
+function test_pane_visibility(win) {
+ let modes = {
+ "remember": "historyRememberPane",
+ "custom": "historyCustomPane"
+ };
+
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let historypane = win.document.getElementById("historyPane");
+ ok(historypane, "history mode pane should exist");
+
+ for (let mode in modes) {
+ historymode.value = mode;
+ controlChanged(historymode);
+ is(historypane.selectedPanel, win.document.getElementById(modes[mode]),
+ "The correct pane should be selected for the " + mode + " mode");
+ is_element_visible(historypane.selectedPanel,
+ "Correct pane should be visible for the " + mode + " mode");
+ }
+}
+
+function test_dependent_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let controls = [
+ win.document.getElementById("rememberHistory"),
+ win.document.getElementById("rememberForms"),
+ win.document.getElementById("keepUntil"),
+ win.document.getElementById("keepCookiesUntil"),
+ win.document.getElementById("alwaysClear"),
+ ];
+ controls.forEach(function(control) {
+ ok(control, "the dependent controls should exist");
+ });
+ let independents = [
+ win.document.getElementById("acceptCookies"),
+ win.document.getElementById("acceptThirdPartyLabel"),
+ win.document.getElementById("acceptThirdPartyMenu")
+ ];
+ independents.forEach(function(control) {
+ ok(control, "the independent controls should exist");
+ });
+ let cookieexceptions = win.document.getElementById("cookieExceptions");
+ ok(cookieexceptions, "the cookie exceptions button should exist");
+ let keepuntil = win.document.getElementById("keepCookiesUntil");
+ ok(keepuntil, "the keep cookies until menulist should exist");
+ let alwaysclear = win.document.getElementById("alwaysClear");
+ ok(alwaysclear, "the clear data on close checkbox should exist");
+ let rememberhistory = win.document.getElementById("rememberHistory");
+ ok(rememberhistory, "the remember history checkbox should exist");
+ let rememberforms = win.document.getElementById("rememberForms");
+ ok(rememberforms, "the remember forms checkbox should exist");
+ let alwaysclearsettings = win.document.getElementById("clearDataSettings");
+ ok(alwaysclearsettings, "the clear data settings button should exist");
+
+ function expect_disabled(disabled) {
+ controls.forEach(function(control) {
+ is(control.disabled, disabled,
+ control.getAttribute("id") + " should " + (disabled ? "" : "not ") + "be disabled");
+ });
+ is(keepuntil.value, disabled ? 2 : 0,
+ "the keep cookies until menulist value should be as expected");
+ if (disabled) {
+ ok(!alwaysclear.checked,
+ "the clear data on close checkbox value should be as expected");
+ ok(!rememberhistory.checked,
+ "the remember history checkbox value should be as expected");
+ ok(!rememberforms.checked,
+ "the remember forms checkbox value should be as expected");
+ }
+ }
+ function check_independents(expected) {
+ independents.forEach(function(control) {
+ is(control.disabled, expected,
+ control.getAttribute("id") + " should " + (expected ? "" : "not ") + "be disabled");
+ });
+
+ ok(!cookieexceptions.disabled,
+ "the cookie exceptions button should never be disabled");
+ ok(alwaysclearsettings.disabled,
+ "the clear data settings button should always be disabled");
+ }
+
+ // controls should only change in custom mode
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_disabled(false);
+ check_independents(false);
+
+ // setting the mode to custom shouldn't change anything
+ historymode.value = "custom";
+ controlChanged(historymode);
+ expect_disabled(false);
+ check_independents(false);
+}
+
+function test_dependent_cookie_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let controls = [
+ win.document.getElementById("acceptThirdPartyLabel"),
+ win.document.getElementById("acceptThirdPartyMenu"),
+ win.document.getElementById("keepUntil"),
+ win.document.getElementById("keepCookiesUntil"),
+ ];
+ controls.forEach(function(control) {
+ ok(control, "the dependent cookie controls should exist");
+ });
+ let acceptcookies = win.document.getElementById("acceptCookies");
+ ok(acceptcookies, "the accept cookies checkbox should exist");
+
+ function expect_disabled(disabled) {
+ controls.forEach(function(control) {
+ is(control.disabled, disabled,
+ control.getAttribute("id") + " should " + (disabled ? "" : "not ") + "be disabled");
+ });
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+ pbautostart.checked = false;
+ controlChanged(pbautostart);
+ expect_disabled(false);
+
+ acceptcookies.checked = false;
+ controlChanged(acceptcookies);
+ expect_disabled(true);
+
+ acceptcookies.checked = true;
+ controlChanged(acceptcookies);
+ expect_disabled(false);
+
+ let accessthirdparty = controls.shift();
+ acceptcookies.checked = false;
+ controlChanged(acceptcookies);
+ expect_disabled(true);
+ ok(accessthirdparty.disabled, "access third party button should be disabled");
+
+ pbautostart.checked = false;
+ controlChanged(pbautostart);
+ expect_disabled(true);
+ ok(accessthirdparty.disabled, "access third party button should be disabled");
+
+ acceptcookies.checked = true;
+ controlChanged(acceptcookies);
+ expect_disabled(false);
+ ok(!accessthirdparty.disabled, "access third party button should be enabled");
+}
+
+function test_dependent_clearonclose_elements(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let pbautostart = win.document.getElementById("privateBrowsingAutoStart");
+ ok(pbautostart, "the private browsing auto-start checkbox should exist");
+ let alwaysclear = win.document.getElementById("alwaysClear");
+ ok(alwaysclear, "the clear data on close checkbox should exist");
+ let alwaysclearsettings = win.document.getElementById("clearDataSettings");
+ ok(alwaysclearsettings, "the clear data settings button should exist");
+
+ function expect_disabled(disabled) {
+ is(alwaysclearsettings.disabled, disabled,
+ "the clear data settings should " + (disabled ? "" : "not ") + "be disabled");
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+ pbautostart.checked = false;
+ controlChanged(pbautostart);
+ alwaysclear.checked = false;
+ controlChanged(alwaysclear);
+ expect_disabled(true);
+
+ alwaysclear.checked = true;
+ controlChanged(alwaysclear);
+ expect_disabled(false);
+
+ alwaysclear.checked = false;
+ controlChanged(alwaysclear);
+ expect_disabled(true);
+}
+
+function test_dependent_prefs(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+ let controls = [
+ win.document.getElementById("rememberHistory"),
+ win.document.getElementById("rememberForms"),
+ win.document.getElementById("acceptCookies")
+ ];
+ controls.forEach(function(control) {
+ ok(control, "the micro-management controls should exist");
+ });
+
+ let thirdPartyCookieMenu = win.document.getElementById("acceptThirdPartyMenu");
+ ok(thirdPartyCookieMenu, "the third-party cookie control should exist");
+
+ function expect_checked(checked) {
+ controls.forEach(function(control) {
+ is(control.checked, checked,
+ control.getAttribute("id") + " should " + (checked ? "not " : "") + "be checked");
+ });
+
+ is(thirdPartyCookieMenu.value == "always" || thirdPartyCookieMenu.value == "visited", checked, "third-party cookies should " + (checked ? "not " : "") + "be limited");
+ }
+
+ // controls should be checked in remember mode
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_checked(true);
+
+ // even if they're unchecked in custom mode
+ historymode.value = "custom";
+ controlChanged(historymode);
+ thirdPartyCookieMenu.value = "never";
+ controlChanged(thirdPartyCookieMenu);
+ controls.forEach(function(control) {
+ control.checked = false;
+ controlChanged(control);
+ });
+ expect_checked(false);
+ historymode.value = "remember";
+ controlChanged(historymode);
+ expect_checked(true);
+}
+
+function test_historymode_retention(mode, expect) {
+ return function test_historymode_retention_fn(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+
+ if ((historymode.value == "remember" && mode == "dontremember") ||
+ (historymode.value == "dontremember" && mode == "remember") ||
+ (historymode.value == "custom" && mode == "dontremember")) {
+ return;
+ }
+
+ if (expect !== undefined) {
+ is(historymode.value, expect,
+ "history mode is expected to remain " + expect);
+ }
+
+ historymode.value = mode;
+ controlChanged(historymode);
+ };
+}
+
+function test_custom_retention(controlToChange, expect, valueIncrement) {
+ return function test_custom_retention_fn(win) {
+ let historymode = win.document.getElementById("historyMode");
+ ok(historymode, "history mode menulist should exist");
+
+ if (expect !== undefined) {
+ is(historymode.value, expect,
+ "history mode is expected to remain " + expect);
+ }
+
+ historymode.value = "custom";
+ controlChanged(historymode);
+
+ controlToChange = win.document.getElementById(controlToChange);
+ ok(controlToChange, "the control to change should exist");
+ switch (controlToChange.localName) {
+ case "checkbox":
+ controlToChange.checked = !controlToChange.checked;
+ break;
+ case "textbox":
+ controlToChange.value = parseInt(controlToChange.value) + valueIncrement;
+ break;
+ case "menulist":
+ controlToChange.value = valueIncrement;
+ break;
+ }
+ controlChanged(controlToChange);
+ };
+}
+
+function test_locbar_suggestion_retention(suggestion, autocomplete) {
+ return function(win) {
+ let elem = win.document.getElementById(suggestion + "Suggestion");
+ ok(elem, "Suggest " + suggestion + " checkbox should exist.");
+ elem.click();
+
+ is(Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled"), autocomplete,
+ "browser.urlbar.autocomplete.enabled pref should be " + autocomplete);
+ };
+}
+
+const gPrefCache = new Map();
+
+function cache_preferences(win) {
+ let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
+ for (let pref of prefs)
+ gPrefCache.set(pref.name, pref.value);
+}
+
+function reset_preferences(win) {
+ let prefs = win.document.querySelectorAll("#privacyPreferences > preference");
+ for (let pref of prefs)
+ pref.value = gPrefCache.get(pref.name);
+}
+
+function run_test_subset(subset) {
+ info("subset: " + Array.from(subset, x => x.name).join(",") + "\n");
+ SpecialPowers.pushPrefEnv({"set": [["browser.preferences.instantApply", true]]});
+
+ let tests = [cache_preferences, ...subset, reset_preferences];
+ for (let test of tests) {
+ add_task(runTestOnPrivacyPrefPane.bind(undefined, test));
+ }
+}
diff --git a/browser/components/preferences/in-content/tests/subdialog.xul b/browser/components/preferences/in-content/tests/subdialog.xul
new file mode 100644
index 000000000..48d297b73
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/subdialog.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<dialog id="subDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Sample sub-dialog" style="width: 32em; height: 5em;"
+ onload="document.getElementById('textbox').focus();"
+ ondialogaccept="acceptSubdialog();">
+ <script>
+ function acceptSubdialog() {
+ window.arguments[0].acceptCount++;
+ }
+ </script>
+
+ <description id="desc">A sample sub-dialog for testing</description>
+
+ <textbox id="textbox" value="Default text" />
+
+ <separator class="thin"/>
+
+ <button oncommand="close();" icon="close" label="Close" />
+
+</dialog>
diff --git a/browser/components/preferences/in-content/tests/subdialog2.xul b/browser/components/preferences/in-content/tests/subdialog2.xul
new file mode 100644
index 000000000..89803c250
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/subdialog2.xul
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<dialog id="subDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Sample sub-dialog #2" style="width: 32em; height: 5em;"
+ onload="document.getElementById('textbox').focus();"
+ ondialogaccept="acceptSubdialog();">
+ <script>
+ function acceptSubdialog() {
+ window.arguments[0].acceptCount++;
+ }
+ </script>
+
+ <description id="desc">A sample sub-dialog for testing</description>
+
+ <textbox id="textbox" value="Default text" />
+
+ <separator class="thin"/>
+
+ <button oncommand="close();" icon="close" label="Close" />
+
+</dialog>
diff --git a/browser/components/preferences/jar.mn b/browser/components/preferences/jar.mn
new file mode 100644
index 000000000..c0d34da7f
--- /dev/null
+++ b/browser/components/preferences/jar.mn
@@ -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/.
+
+browser.jar:
+ content/browser/preferences/applicationManager.xul
+ content/browser/preferences/applicationManager.js
+ content/browser/preferences/blocklists.xul
+ content/browser/preferences/blocklists.js
+* content/browser/preferences/colors.xul
+* content/browser/preferences/cookies.xul
+ content/browser/preferences/cookies.js
+* content/browser/preferences/connection.xul
+ content/browser/preferences/connection.js
+ content/browser/preferences/donottrack.xul
+* content/browser/preferences/fonts.xul
+ content/browser/preferences/fonts.js
+ content/browser/preferences/handlers.xml
+ content/browser/preferences/handlers.css
+* content/browser/preferences/languages.xul
+ content/browser/preferences/languages.js
+ content/browser/preferences/permissions.xul
+ content/browser/preferences/containers.xul
+ content/browser/preferences/containers.js
+ content/browser/preferences/permissions.js
+ content/browser/preferences/sanitize.xul
+ content/browser/preferences/sanitize.js
+ content/browser/preferences/selectBookmark.xul
+ content/browser/preferences/selectBookmark.js
+ content/browser/preferences/translation.xul
+ content/browser/preferences/translation.js
diff --git a/browser/components/preferences/languages.js b/browser/components/preferences/languages.js
new file mode 100644
index 000000000..16ca257f7
--- /dev/null
+++ b/browser/components/preferences/languages.js
@@ -0,0 +1,312 @@
+/* -*- 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/. */
+
+var gLanguagesDialog = {
+
+ _availableLanguagesList : [],
+ _acceptLanguages : { },
+
+ _selectedItemID : null,
+
+ init: function ()
+ {
+ if (!this._availableLanguagesList.length)
+ this._loadAvailableLanguages();
+ },
+
+ // Ugly hack used to trigger extra reflow in order to work around XUL bug 1194844;
+ // see bug 1194346.
+ forceReflow: function ()
+ {
+ this._activeLanguages.style.fontKerning = "none";
+ setTimeout("gLanguagesDialog._activeLanguages.style.removeProperty('font-kerning')", 0);
+ },
+
+ get _activeLanguages()
+ {
+ return document.getElementById("activeLanguages");
+ },
+
+ get _availableLanguages()
+ {
+ return document.getElementById("availableLanguages");
+ },
+
+ _loadAvailableLanguages: function ()
+ {
+ // This is a parser for: resource://gre/res/language.properties
+ // The file is formatted like so:
+ // ab[-cd].accept=true|false
+ // ab = language
+ // cd = region
+ var bundleAccepted = document.getElementById("bundleAccepted");
+ var bundleRegions = document.getElementById("bundleRegions");
+ var bundleLanguages = document.getElementById("bundleLanguages");
+ var bundlePreferences = document.getElementById("bundlePreferences");
+
+ function LanguageInfo(aName, aABCD, aIsVisible)
+ {
+ this.name = aName;
+ this.abcd = aABCD;
+ this.isVisible = aIsVisible;
+ }
+
+ // 1) Read the available languages out of language.properties
+ var strings = bundleAccepted.strings;
+ while (strings.hasMoreElements()) {
+ var currString = strings.getNext();
+ if (!(currString instanceof Components.interfaces.nsIPropertyElement))
+ break;
+
+ var property = currString.key.split("."); // ab[-cd].accept
+ if (property[1] == "accept") {
+ var abCD = property[0];
+ var abCDPairs = abCD.split("-"); // ab[-cd]
+ var useABCDFormat = abCDPairs.length > 1;
+ var ab = useABCDFormat ? abCDPairs[0] : abCD;
+ var cd = useABCDFormat ? abCDPairs[1] : "";
+ if (ab) {
+ var language = "";
+ try {
+ language = bundleLanguages.getString(ab);
+ }
+ catch (e) { continue; }
+
+ var region = "";
+ if (useABCDFormat) {
+ try {
+ region = bundleRegions.getString(cd);
+ }
+ catch (e) { continue; }
+ }
+
+ var name = "";
+ if (useABCDFormat)
+ name = bundlePreferences.getFormattedString("languageRegionCodeFormat",
+ [language, region, abCD]);
+ else
+ name = bundlePreferences.getFormattedString("languageCodeFormat",
+ [language, abCD]);
+
+ if (name && abCD) {
+ var isVisible = currString.value == "true" &&
+ (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD]);
+ var li = new LanguageInfo(name, abCD, isVisible);
+ this._availableLanguagesList.push(li);
+ }
+ }
+ }
+ }
+ this._buildAvailableLanguageList();
+ },
+
+ _buildAvailableLanguageList: function ()
+ {
+ var availableLanguagesPopup = document.getElementById("availableLanguagesPopup");
+ while (availableLanguagesPopup.hasChildNodes())
+ availableLanguagesPopup.removeChild(availableLanguagesPopup.firstChild);
+
+ // Sort the list of languages by name
+ this._availableLanguagesList.sort(function (a, b) {
+ return a.name.localeCompare(b.name);
+ });
+
+ // Load the UI with the data
+ for (var i = 0; i < this._availableLanguagesList.length; ++i) {
+ var abCD = this._availableLanguagesList[i].abcd;
+ if (this._availableLanguagesList[i].isVisible &&
+ (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD])) {
+ var menuitem = document.createElement("menuitem");
+ menuitem.id = this._availableLanguagesList[i].abcd;
+ availableLanguagesPopup.appendChild(menuitem);
+ menuitem.setAttribute("label", this._availableLanguagesList[i].name);
+ }
+ }
+ },
+
+ readAcceptLanguages: function ()
+ {
+ while (this._activeLanguages.hasChildNodes())
+ this._activeLanguages.removeChild(this._activeLanguages.firstChild);
+
+ var selectedIndex = 0;
+ var preference = document.getElementById("intl.accept_languages");
+ if (preference.value == "")
+ return undefined;
+ var languages = preference.value.toLowerCase().split(/\s*,\s*/);
+ for (var i = 0; i < languages.length; ++i) {
+ var name = this._getLanguageName(languages[i]);
+ if (!name)
+ name = "[" + languages[i] + "]";
+ var listitem = document.createElement("listitem");
+ listitem.id = languages[i];
+ if (languages[i] == this._selectedItemID)
+ selectedIndex = i;
+ this._activeLanguages.appendChild(listitem);
+ listitem.setAttribute("label", name);
+
+ // Hash this language as an "Active" language so we don't
+ // show it in the list that can be added.
+ this._acceptLanguages[languages[i]] = true;
+ }
+
+ if (this._activeLanguages.childNodes.length > 0) {
+ this._activeLanguages.ensureIndexIsVisible(selectedIndex);
+ this._activeLanguages.selectedIndex = selectedIndex;
+ }
+
+ return undefined;
+ },
+
+ writeAcceptLanguages: function ()
+ {
+ return undefined;
+ },
+
+ onAvailableLanguageSelect: function ()
+ {
+ var addButton = document.getElementById("addButton");
+ addButton.disabled = false;
+
+ this._availableLanguages.removeAttribute("accesskey");
+ },
+
+ addLanguage: function ()
+ {
+ var selectedID = this._availableLanguages.selectedItem.id;
+ var preference = document.getElementById("intl.accept_languages");
+ var arrayOfPrefs = preference.value.toLowerCase().split(/\s*,\s*/);
+ for (var i = 0; i < arrayOfPrefs.length; ++i ) {
+ if (arrayOfPrefs[i] == selectedID)
+ return;
+ }
+
+ this._selectedItemID = selectedID;
+
+ if (preference.value == "")
+ preference.value = selectedID;
+ else {
+ arrayOfPrefs.unshift(selectedID);
+ preference.value = arrayOfPrefs.join(",");
+ }
+
+ this._acceptLanguages[selectedID] = true;
+ this._availableLanguages.selectedItem = null;
+
+ // Rebuild the available list with the added item removed...
+ this._buildAvailableLanguageList();
+
+ this._availableLanguages.setAttribute("label", this._availableLanguages.getAttribute("label2"));
+ },
+
+ removeLanguage: function ()
+ {
+ // Build the new preference value string.
+ var languagesArray = [];
+ for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
+ var item = this._activeLanguages.childNodes[i];
+ if (!item.selected)
+ languagesArray.push(item.id);
+ else
+ this._acceptLanguages[item.id] = false;
+ }
+ var string = languagesArray.join(",");
+
+ // Get the item to select after the remove operation completes.
+ var selection = this._activeLanguages.selectedItems;
+ var lastSelected = selection[selection.length-1];
+ var selectItem = lastSelected.nextSibling || lastSelected.previousSibling;
+ selectItem = selectItem ? selectItem.id : null;
+
+ this._selectedItemID = selectItem;
+
+ // Update the preference and force a UI rebuild
+ var preference = document.getElementById("intl.accept_languages");
+ preference.value = string;
+
+ this._buildAvailableLanguageList();
+ },
+
+ _getLanguageName: function (aABCD)
+ {
+ if (!this._availableLanguagesList.length)
+ this._loadAvailableLanguages();
+ for (var i = 0; i < this._availableLanguagesList.length; ++i) {
+ if (aABCD == this._availableLanguagesList[i].abcd)
+ return this._availableLanguagesList[i].name;
+ }
+ return "";
+ },
+
+ moveUp: function ()
+ {
+ var selectedItem = this._activeLanguages.selectedItems[0];
+ var previousItem = selectedItem.previousSibling;
+
+ var string = "";
+ for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
+ var item = this._activeLanguages.childNodes[i];
+ string += (i == 0 ? "" : ",");
+ if (item.id == previousItem.id)
+ string += selectedItem.id;
+ else if (item.id == selectedItem.id)
+ string += previousItem.id;
+ else
+ string += item.id;
+ }
+
+ this._selectedItemID = selectedItem.id;
+
+ // Update the preference and force a UI rebuild
+ var preference = document.getElementById("intl.accept_languages");
+ preference.value = string;
+ },
+
+ moveDown: function ()
+ {
+ var selectedItem = this._activeLanguages.selectedItems[0];
+ var nextItem = selectedItem.nextSibling;
+
+ var string = "";
+ for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
+ var item = this._activeLanguages.childNodes[i];
+ string += (i == 0 ? "" : ",");
+ if (item.id == nextItem.id)
+ string += selectedItem.id;
+ else if (item.id == selectedItem.id)
+ string += nextItem.id;
+ else
+ string += item.id;
+ }
+
+ this._selectedItemID = selectedItem.id;
+
+ // Update the preference and force a UI rebuild
+ var preference = document.getElementById("intl.accept_languages");
+ preference.value = string;
+ },
+
+ onLanguageSelect: function ()
+ {
+ var upButton = document.getElementById("up");
+ var downButton = document.getElementById("down");
+ var removeButton = document.getElementById("remove");
+ switch (this._activeLanguages.selectedCount) {
+ case 0:
+ upButton.disabled = downButton.disabled = removeButton.disabled = true;
+ break;
+ case 1:
+ upButton.disabled = this._activeLanguages.selectedIndex == 0;
+ downButton.disabled = this._activeLanguages.selectedIndex == this._activeLanguages.childNodes.length - 1;
+ removeButton.disabled = false;
+ break;
+ default:
+ upButton.disabled = true;
+ downButton.disabled = true;
+ removeButton.disabled = false;
+ }
+ }
+};
+
diff --git a/browser/components/preferences/languages.xul b/browser/components/preferences/languages.xul
new file mode 100644
index 000000000..7c877a456
--- /dev/null
+++ b/browser/components/preferences/languages.xul
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/languages.dtd">
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+#ifdef XP_MACOSX
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+#endif
+
+<prefwindow id="LanguagesDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&languages.customize.Header;"
+ dlgbuttons="accept,cancel,help"
+ ondialoghelp="openPrefsHelp()"
+ style="width: &window.width;"
+# hack around XUL bug 1194844 by triggering extra reflow (see bug 1194346):
+ onfocus="gLanguagesDialog.forceReflow()"
+ onresize="gLanguagesDialog.forceReflow()">
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <prefpane id="LanguagesDialogPane"
+ class="largeDialogContainer"
+ onpaneload="gLanguagesDialog.init();"
+ helpTopic="prefs-languages">
+
+ <preferences>
+ <preference id="intl.accept_languages" name="intl.accept_languages" type="wstring"/>
+ <preference id="pref.browser.language.disable_button.up"
+ name="pref.browser.language.disable_button.up"
+ type="bool"/>
+ <preference id="pref.browser.language.disable_button.down"
+ name="pref.browser.language.disable_button.down"
+ type="bool"/>
+ <preference id="pref.browser.language.disable_button.remove"
+ name="pref.browser.language.disable_button.remove"
+ type="bool"/>
+ </preferences>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/languages.js"/>
+
+ <stringbundleset id="languageSet">
+ <stringbundle id="bundleRegions" src="chrome://global/locale/regionNames.properties"/>
+ <stringbundle id="bundleLanguages" src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ <stringbundle id="bundleAccepted" src="resource://gre/res/language.properties"/>
+ </stringbundleset>
+
+ <description>&languages.customize.description;</description>
+ <grid flex="1">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row flex="1">
+ <listbox id="activeLanguages" flex="1" rows="6"
+ seltype="multiple" onselect="gLanguagesDialog.onLanguageSelect();"
+ preference="intl.accept_languages"
+ onsyncfrompreference="return gLanguagesDialog.readAcceptLanguages();"
+ onsynctopreference="return gLanguagesDialog.writeAcceptLanguages();"/>
+ <vbox>
+ <button id="up" class="up" oncommand="gLanguagesDialog.moveUp();" disabled="true"
+ label="&languages.customize.moveUp.label;"
+ accesskey="&languages.customize.moveUp.accesskey;"
+ preference="pref.browser.language.disable_button.up"/>
+ <button id="down" class="down" oncommand="gLanguagesDialog.moveDown();" disabled="true"
+ label="&languages.customize.moveDown.label;"
+ accesskey="&languages.customize.moveDown.accesskey;"
+ preference="pref.browser.language.disable_button.down"/>
+ <button id="remove" oncommand="gLanguagesDialog.removeLanguage();" disabled="true"
+ label="&languages.customize.deleteButton.label;"
+ accesskey="&languages.customize.deleteButton.accesskey;"
+ preference="pref.browser.language.disable_button.remove"/>
+ </vbox>
+ </row>
+ <row>
+ <separator class="thin"/>
+ </row>
+ <row>
+ <menulist id="availableLanguages" oncommand="gLanguagesDialog.onAvailableLanguageSelect();"
+ label="&languages.customize.selectLanguage.label;"
+ label2="&languages.customize.selectLanguage.label;">
+ <menupopup id="availableLanguagesPopup"/>
+ </menulist>
+ <button id="addButton" oncommand="gLanguagesDialog.addLanguage();" disabled="true"
+ label="&languages.customize.addButton.label;"
+ accesskey="&languages.customize.addButton.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <separator/>
+ </prefpane>
+</prefwindow>
+
diff --git a/browser/components/preferences/moz.build b/browser/components/preferences/moz.build
new file mode 100644
index 000000000..5415399a0
--- /dev/null
+++ b/browser/components/preferences/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['in-content']
+
+BROWSER_CHROME_MANIFESTS += [
+ 'in-content/tests/browser.ini',
+]
+
+for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
+ DEFINES[var] = CONFIG[var]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
+ DEFINES['HAVE_SHELL_SERVICE'] = 1
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Preferences')
diff --git a/browser/components/preferences/permissions.js b/browser/components/preferences/permissions.js
new file mode 100644
index 000000000..4944f79a4
--- /dev/null
+++ b/browser/components/preferences/permissions.js
@@ -0,0 +1,462 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+const nsICookiePermission = Components.interfaces.nsICookiePermission;
+
+const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions";
+
+function Permission(principal, type, capability)
+{
+ this.principal = principal;
+ this.origin = principal.origin;
+ this.type = type;
+ this.capability = capability;
+}
+
+var gPermissionManager = {
+ _type : "",
+ _permissions : [],
+ _permissionsToAdd : new Map(),
+ _permissionsToDelete : new Map(),
+ _bundle : null,
+ _tree : null,
+ _observerRemoved : false,
+
+ _view: {
+ _rowCount: 0,
+ get rowCount()
+ {
+ return this._rowCount;
+ },
+ getCellText: function (aRow, aColumn)
+ {
+ if (aColumn.id == "siteCol")
+ return gPermissionManager._permissions[aRow].origin;
+ else if (aColumn.id == "statusCol")
+ return gPermissionManager._permissions[aRow].capability;
+ return "";
+ },
+
+ isSeparator: function(aIndex) { return false; },
+ isSorted: function() { return false; },
+ isContainer: function(aIndex) { return false; },
+ setTree: function(aTree) {},
+ getImageSrc: function(aRow, aColumn) {},
+ getProgressMode: function(aRow, aColumn) {},
+ getCellValue: function(aRow, aColumn) {},
+ cycleHeader: function(column) {},
+ getRowProperties: function(row) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ getCellProperties: function(row, column) {
+ if (column.element.getAttribute("id") == "siteCol")
+ return "ltr";
+
+ return "";
+ }
+ },
+
+ _getCapabilityString: function (aCapability)
+ {
+ var stringKey = null;
+ switch (aCapability) {
+ case nsIPermissionManager.ALLOW_ACTION:
+ stringKey = "can";
+ break;
+ case nsIPermissionManager.DENY_ACTION:
+ stringKey = "cannot";
+ break;
+ case nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY:
+ stringKey = "canAccessFirstParty";
+ break;
+ case nsICookiePermission.ACCESS_SESSION:
+ stringKey = "canSession";
+ break;
+ }
+ return this._bundle.getString(stringKey);
+ },
+
+ addPermission: function (aCapability)
+ {
+ var textbox = document.getElementById("url");
+ var input_url = textbox.value.replace(/^\s*/, ""); // trim any leading space
+ let principal;
+ try {
+ // The origin accessor on the principal object will throw if the
+ // principal doesn't have a canonical origin representation. This will
+ // help catch cases where the URI parser parsed something like
+ // `localhost:8080` as having the scheme `localhost`, rather than being
+ // an invalid URI. A canonical origin representation is required by the
+ // permission manager for storage, so this won't prevent any valid
+ // permissions from being entered by the user.
+ let uri;
+ try {
+ uri = Services.io.newURI(input_url, null, null);
+ principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ // If we have ended up with an unknown scheme, the following will throw.
+ principal.origin;
+ } catch (ex) {
+ uri = Services.io.newURI("http://" + input_url, null, null);
+ principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+ // If we have ended up with an unknown scheme, the following will throw.
+ principal.origin;
+ }
+ } catch (ex) {
+ var message = this._bundle.getString("invalidURI");
+ var title = this._bundle.getString("invalidURITitle");
+ Services.prompt.alert(window, title, message);
+ return;
+ }
+
+ var capabilityString = this._getCapabilityString(aCapability);
+
+ // check whether the permission already exists, if not, add it
+ let permissionExists = false;
+ let capabilityExists = false;
+ for (var i = 0; i < this._permissions.length; ++i) {
+ if (this._permissions[i].principal.equals(principal)) {
+ permissionExists = true;
+ capabilityExists = this._permissions[i].capability == capabilityString;
+ if (!capabilityExists) {
+ this._permissions[i].capability = capabilityString;
+ }
+ break;
+ }
+ }
+
+ let permissionParams = {principal: principal, type: this._type, capability: aCapability};
+ if (!permissionExists) {
+ this._permissionsToAdd.set(principal.origin, permissionParams);
+ this._addPermission(permissionParams);
+ }
+ else if (!capabilityExists) {
+ this._permissionsToAdd.set(principal.origin, permissionParams);
+ this._handleCapabilityChange();
+ }
+
+ textbox.value = "";
+ textbox.focus();
+
+ // covers a case where the site exists already, so the buttons don't disable
+ this.onHostInput(textbox);
+
+ // enable "remove all" button as needed
+ document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
+ },
+
+ _removePermission: function(aPermission)
+ {
+ this._removePermissionFromList(aPermission.principal);
+
+ // If this permission was added during this session, let's remove
+ // it from the pending adds list to prevent calls to the
+ // permission manager.
+ let isNewPermission = this._permissionsToAdd.delete(aPermission.principal.origin);
+
+ if (!isNewPermission) {
+ this._permissionsToDelete.set(aPermission.principal.origin, aPermission);
+ }
+
+ },
+
+ _handleCapabilityChange: function ()
+ {
+ // Re-do the sort, if the status changed from Block to Allow
+ // or vice versa, since if we're sorted on status, we may no
+ // longer be in order.
+ if (this._lastPermissionSortColumn == "statusCol") {
+ this._resortPermissions();
+ }
+ this._tree.treeBoxObject.invalidate();
+ },
+
+ _addPermission: function(aPermission)
+ {
+ this._addPermissionToList(aPermission);
+ ++this._view._rowCount;
+ this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);
+ // Re-do the sort, since we inserted this new item at the end.
+ this._resortPermissions();
+ },
+
+ _resortPermissions: function()
+ {
+ gTreeUtils.sort(this._tree, this._view, this._permissions,
+ this._lastPermissionSortColumn,
+ this._permissionsComparator,
+ this._lastPermissionSortColumn,
+ !this._lastPermissionSortAscending); // keep sort direction
+ },
+
+ onHostInput: function (aSiteField)
+ {
+ document.getElementById("btnSession").disabled = !aSiteField.value;
+ document.getElementById("btnBlock").disabled = !aSiteField.value;
+ document.getElementById("btnAllow").disabled = !aSiteField.value;
+ },
+
+ onWindowKeyPress: function (aEvent)
+ {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
+ window.close();
+ },
+
+ onHostKeyPress: function (aEvent)
+ {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
+ document.getElementById("btnAllow").click();
+ },
+
+ onLoad: function ()
+ {
+ this._bundle = document.getElementById("bundlePreferences");
+ var params = window.arguments[0];
+ this.init(params);
+ },
+
+ init: function (aParams)
+ {
+ if (this._type) {
+ // reusing an open dialog, clear the old observer
+ this.uninit();
+ }
+
+ this._type = aParams.permissionType;
+ this._manageCapability = aParams.manageCapability;
+
+ var permissionsText = document.getElementById("permissionsText");
+ while (permissionsText.hasChildNodes())
+ permissionsText.removeChild(permissionsText.firstChild);
+ permissionsText.appendChild(document.createTextNode(aParams.introText));
+
+ document.title = aParams.windowTitle;
+
+ document.getElementById("btnBlock").hidden = !aParams.blockVisible;
+ document.getElementById("btnSession").hidden = !aParams.sessionVisible;
+ document.getElementById("btnAllow").hidden = !aParams.allowVisible;
+
+ var urlFieldVisible = (aParams.blockVisible || aParams.sessionVisible || aParams.allowVisible);
+
+ var urlField = document.getElementById("url");
+ urlField.value = aParams.prefilledHost;
+ urlField.hidden = !urlFieldVisible;
+
+ this.onHostInput(urlField);
+
+ var urlLabel = document.getElementById("urlLabel");
+ urlLabel.hidden = !urlFieldVisible;
+
+ if (aParams.hideStatusColumn) {
+ document.getElementById("statusCol").hidden = true;
+ }
+
+ let treecols = document.getElementsByTagName("treecols")[0];
+ treecols.addEventListener("click", event => {
+ if (event.target.nodeName != "treecol" || event.button != 0) {
+ return;
+ }
+
+ let sortField = event.target.getAttribute("data-field-name");
+ if (!sortField) {
+ return;
+ }
+
+ gPermissionManager.onPermissionSort(sortField);
+ });
+
+ Services.obs.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type);
+ Services.obs.addObserver(this, "perm-changed", false);
+
+ this._loadPermissions();
+
+ urlField.focus();
+ },
+
+ uninit: function ()
+ {
+ if (!this._observerRemoved) {
+ Services.obs.removeObserver(this, "perm-changed");
+
+ this._observerRemoved = true;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+
+ // Ignore unrelated permission types.
+ if (permission.type != this._type)
+ return;
+
+ if (aData == "added") {
+ this._addPermission(permission);
+ }
+ else if (aData == "changed") {
+ for (var i = 0; i < this._permissions.length; ++i) {
+ if (permission.matches(this._permissions[i].principal, true)) {
+ this._permissions[i].capability = this._getCapabilityString(permission.capability);
+ break;
+ }
+ }
+ this._handleCapabilityChange();
+ }
+ else if (aData == "deleted") {
+ this._removePermissionFromList(permission.principal);
+ }
+ }
+ },
+
+ onPermissionSelected: function ()
+ {
+ var hasSelection = this._tree.view.selection.count > 0;
+ var hasRows = this._tree.view.rowCount > 0;
+ document.getElementById("removePermission").disabled = !hasRows || !hasSelection;
+ document.getElementById("removeAllPermissions").disabled = !hasRows;
+ },
+
+ onPermissionDeleted: function ()
+ {
+ if (!this._view.rowCount)
+ return;
+ var removedPermissions = [];
+ gTreeUtils.deleteSelectedItems(this._tree, this._view, this._permissions, removedPermissions);
+ for (var i = 0; i < removedPermissions.length; ++i) {
+ var p = removedPermissions[i];
+ this._removePermission(p);
+ }
+ document.getElementById("removePermission").disabled = !this._permissions.length;
+ document.getElementById("removeAllPermissions").disabled = !this._permissions.length;
+ },
+
+ onAllPermissionsDeleted: function ()
+ {
+ if (!this._view.rowCount)
+ return;
+ var removedPermissions = [];
+ gTreeUtils.deleteAll(this._tree, this._view, this._permissions, removedPermissions);
+ for (var i = 0; i < removedPermissions.length; ++i) {
+ var p = removedPermissions[i];
+ this._removePermission(p);
+ }
+ document.getElementById("removePermission").disabled = true;
+ document.getElementById("removeAllPermissions").disabled = true;
+ },
+
+ onPermissionKeyPress: function (aEvent)
+ {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) {
+ this.onPermissionDeleted();
+ } else if (AppConstants.platform == "macosx" &&
+ aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE) {
+ this.onPermissionDeleted();
+ }
+ },
+
+ _lastPermissionSortColumn: "",
+ _lastPermissionSortAscending: false,
+ _permissionsComparator : function (a, b)
+ {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ },
+
+
+ onPermissionSort: function (aColumn)
+ {
+ this._lastPermissionSortAscending = gTreeUtils.sort(this._tree,
+ this._view,
+ this._permissions,
+ aColumn,
+ this._permissionsComparator,
+ this._lastPermissionSortColumn,
+ this._lastPermissionSortAscending);
+ this._lastPermissionSortColumn = aColumn;
+ },
+
+ onApplyChanges: function()
+ {
+ // Stop observing permission changes since we are about
+ // to write out the pending adds/deletes and don't need
+ // to update the UI
+ this.uninit();
+
+ for (let permissionParams of this._permissionsToAdd.values()) {
+ Services.perms.addFromPrincipal(permissionParams.principal, permissionParams.type, permissionParams.capability);
+ }
+
+ for (let p of this._permissionsToDelete.values()) {
+ Services.perms.removeFromPrincipal(p.principal, p.type);
+ }
+
+ window.close();
+ },
+
+ _loadPermissions: function ()
+ {
+ this._tree = document.getElementById("permissionsTree");
+ this._permissions = [];
+
+ // load permissions into a table
+ var enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ var nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ this._addPermissionToList(nextPermission);
+ }
+
+ this._view._rowCount = this._permissions.length;
+
+ // sort and display the table
+ this._tree.view = this._view;
+ this.onPermissionSort("origin");
+
+ // disable "remove all" button if there are none
+ document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
+ },
+
+ _addPermissionToList: function (aPermission)
+ {
+ if (aPermission.type == this._type &&
+ (!this._manageCapability ||
+ (aPermission.capability == this._manageCapability))) {
+
+ var principal = aPermission.principal;
+ var capabilityString = this._getCapabilityString(aPermission.capability);
+ var p = new Permission(principal,
+ aPermission.type,
+ capabilityString);
+ this._permissions.push(p);
+ }
+ },
+
+ _removePermissionFromList: function (aPrincipal)
+ {
+ for (let i = 0; i < this._permissions.length; ++i) {
+ if (this._permissions[i].principal.equals(aPrincipal)) {
+ this._permissions.splice(i, 1);
+ this._view._rowCount--;
+ this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1);
+ this._tree.treeBoxObject.invalidate();
+ break;
+ }
+ }
+ },
+
+ setOrigin: function (aOrigin)
+ {
+ document.getElementById("url").value = aOrigin;
+ }
+};
+
+function setOrigin(aOrigin)
+{
+ gPermissionManager.setOrigin(aOrigin);
+}
+
+function initWithParams(aParams)
+{
+ gPermissionManager.init(aParams);
+}
diff --git a/browser/components/preferences/permissions.xul b/browser/components/preferences/permissions.xul
new file mode 100644
index 000000000..7a7040864
--- /dev/null
+++ b/browser/components/preferences/permissions.xul
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/permissions.dtd" >
+
+<window id="PermissionsDialog" class="windowDialog"
+ windowtype="Browser:Permissions"
+ title="&window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: &window.width;;"
+ onload="gPermissionManager.onLoad();"
+ onunload="gPermissionManager.uninit();"
+ persist="screenX screenY width height"
+ onkeypress="gPermissionManager.onWindowKeyPress(event);">
+
+ <script src="chrome://global/content/treeUtils.js"/>
+ <script src="chrome://browser/content/preferences/permissions.js"/>
+
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <keyset>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+ </keyset>
+
+ <vbox class="contentPane largeDialogContainer" flex="1">
+ <description id="permissionsText" control="url"/>
+ <separator class="thin"/>
+ <label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/>
+ <hbox align="start">
+ <textbox id="url" flex="1"
+ oninput="gPermissionManager.onHostInput(event.target);"
+ onkeypress="gPermissionManager.onHostKeyPress(event);"/>
+ </hbox>
+ <hbox pack="end">
+ <button id="btnBlock" disabled="true" label="&block.label;" accesskey="&block.accesskey;"
+ oncommand="gPermissionManager.addPermission(nsIPermissionManager.DENY_ACTION);"/>
+ <button id="btnSession" disabled="true" label="&session.label;" accesskey="&session.accesskey;"
+ oncommand="gPermissionManager.addPermission(nsICookiePermission.ACCESS_SESSION);"/>
+ <button id="btnAllow" disabled="true" label="&allow.label;" default="true" accesskey="&allow.accesskey;"
+ oncommand="gPermissionManager.addPermission(nsIPermissionManager.ALLOW_ACTION);"/>
+ </hbox>
+ <separator class="thin"/>
+ <tree id="permissionsTree" flex="1" style="height: 18em;"
+ hidecolumnpicker="true"
+ onkeypress="gPermissionManager.onPermissionKeyPress(event)"
+ onselect="gPermissionManager.onPermissionSelected();">
+ <treecols>
+ <treecol id="siteCol" label="&treehead.sitename.label;" flex="3"
+ data-field-name="origin" persist="width"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="statusCol" label="&treehead.status.label;" flex="1"
+ data-field-name="capability" persist="width"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ <vbox>
+ <hbox class="actionButtons" align="left" flex="1">
+ <button id="removePermission" disabled="true"
+ accesskey="&removepermission.accesskey;"
+ icon="remove" label="&removepermission.label;"
+ oncommand="gPermissionManager.onPermissionDeleted();"/>
+ <button id="removeAllPermissions"
+ icon="clear" label="&removeallpermissions.label;"
+ accesskey="&removeallpermissions.accesskey;"
+ oncommand="gPermissionManager.onAllPermissionsDeleted();"/>
+ </hbox>
+ <spacer flex="1"/>
+ <hbox class="actionButtons" align="right" flex="1">
+ <button oncommand="close();" icon="close"
+ label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
+ <button id="btnApplyChanges" oncommand="gPermissionManager.onApplyChanges();" icon="save"
+ label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
+ </hbox>
+ </vbox>
+</window>
diff --git a/browser/components/preferences/sanitize.js b/browser/components/preferences/sanitize.js
new file mode 100644
index 000000000..3504bffd6
--- /dev/null
+++ b/browser/components/preferences/sanitize.js
@@ -0,0 +1,21 @@
+/* -*- 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/. */
+
+var gSanitizeDialog = Object.freeze({
+ init: function() {
+ let customWidthElements = document.getElementsByAttribute("dialogWidth", "*");
+ let isInSubdialog = document.documentElement.hasAttribute("subdialog");
+ for (let element of customWidthElements) {
+ element.style.width = element.getAttribute(isInSubdialog ? "subdialogWidth" : "dialogWidth");
+ }
+ this.onClearHistoryChanged();
+ },
+
+ onClearHistoryChanged: function () {
+ let downloadsPref = document.getElementById("privacy.clearOnShutdown.downloads");
+ let historyPref = document.getElementById("privacy.clearOnShutdown.history");
+ downloadsPref.value = historyPref.value;
+ }
+});
diff --git a/browser/components/preferences/sanitize.xul b/browser/components/preferences/sanitize.xul
new file mode 100644
index 000000000..ca020ba44
--- /dev/null
+++ b/browser/components/preferences/sanitize.xul
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+
+<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
+ %brandDTD;
+ %sanitizeDTD;
+]>
+
+<prefwindow id="SanitizeDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ dlgbuttons="accept,cancel,help"
+ ondialoghelp="openPrefsHelp()"
+ dialogWidth="&sanitizePrefs2.modal.width;"
+ subdialogWidth="&sanitizePrefs2.inContent.dialog.width;"
+ title="&sanitizePrefs2.title;"
+ onload="gSanitizeDialog.init();">
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/sanitize.js"/>
+
+ <prefpane id="SanitizeDialogPane"
+ helpTopic="prefs-clear-private-data">
+
+ <preferences>
+ <preference id="privacy.clearOnShutdown.history" name="privacy.clearOnShutdown.history" type="bool"
+ onchange="return gSanitizeDialog.onClearHistoryChanged();"/>
+ <preference id="privacy.clearOnShutdown.formdata" name="privacy.clearOnShutdown.formdata" type="bool"/>
+ <preference id="privacy.clearOnShutdown.downloads" name="privacy.clearOnShutdown.downloads" type="bool"/>
+ <preference id="privacy.clearOnShutdown.cookies" name="privacy.clearOnShutdown.cookies" type="bool"/>
+ <preference id="privacy.clearOnShutdown.cache" name="privacy.clearOnShutdown.cache" type="bool"/>
+ <preference id="privacy.clearOnShutdown.offlineApps" name="privacy.clearOnShutdown.offlineApps" type="bool"/>
+ <preference id="privacy.clearOnShutdown.sessions" name="privacy.clearOnShutdown.sessions" type="bool"/>
+ <preference id="privacy.clearOnShutdown.siteSettings" name="privacy.clearOnShutdown.siteSettings" type="bool"/>
+ </preferences>
+
+ <description>&clearDataSettings2.label;</description>
+
+ <groupbox orient="horizontal">
+ <caption label="&historySection.label;"/>
+ <grid flex="1">
+ <columns>
+ <column dialogWidth="&sanitizePrefs2.column.width;"
+ subdialogWidth="&sanitizePrefs2.inContent.column.width;"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <checkbox label="&itemHistoryAndDownloads.label;"
+ accesskey="&itemHistoryAndDownloads.accesskey;"
+ preference="privacy.clearOnShutdown.history"/>
+ <checkbox label="&itemCookies.label;"
+ accesskey="&itemCookies.accesskey;"
+ preference="privacy.clearOnShutdown.cookies"/>
+ </row>
+ <row>
+ <checkbox label="&itemActiveLogins.label;"
+ accesskey="&itemActiveLogins.accesskey;"
+ preference="privacy.clearOnShutdown.sessions"/>
+ <checkbox label="&itemCache.label;"
+ accesskey="&itemCache.accesskey;"
+ preference="privacy.clearOnShutdown.cache"/>
+ </row>
+ <row>
+ <checkbox label="&itemFormSearchHistory.label;"
+ accesskey="&itemFormSearchHistory.accesskey;"
+ preference="privacy.clearOnShutdown.formdata"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ <groupbox orient="horizontal">
+ <caption label="&dataSection.label;"/>
+ <grid flex="1">
+ <columns>
+ <column dialogWidth="&sanitizePrefs2.column.width;"
+ subdialogWidth="&sanitizePrefs2.inContent.column.width;"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <checkbox label="&itemSitePreferences.label;"
+ accesskey="&itemSitePreferences.accesskey;"
+ preference="privacy.clearOnShutdown.siteSettings"/>
+ <checkbox label="&itemOfflineApps.label;"
+ accesskey="&itemOfflineApps.accesskey;"
+ preference="privacy.clearOnShutdown.offlineApps"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </prefpane>
+</prefwindow>
diff --git a/browser/components/preferences/selectBookmark.js b/browser/components/preferences/selectBookmark.js
new file mode 100644
index 000000000..ae9b6b1c8
--- /dev/null
+++ b/browser/components/preferences/selectBookmark.js
@@ -0,0 +1,83 @@
+//* -*- 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/. */
+
+/**
+ * SelectBookmarkDialog controls the user interface for the "Use Bookmark for
+ * Home Page" dialog.
+ *
+ * The caller (gMainPane.setHomePageToBookmark in main.js) invokes this dialog
+ * with a single argument - a reference to an object with a .urls property and
+ * a .names property. This dialog is responsible for updating the contents of
+ * the .urls property with an array of URLs to use as home pages and for
+ * updating the .names property with an array of names for those URLs before it
+ * closes.
+ */
+var SelectBookmarkDialog = {
+ init: function SBD_init() {
+ document.getElementById("bookmarks").place =
+ "place:queryType=1&folder=" + PlacesUIUtils.allBookmarksFolderId;
+
+ // Initial update of the OK button.
+ this.selectionChanged();
+ },
+
+ /**
+ * Update the disabled state of the OK button as the user changes the
+ * selection within the view.
+ */
+ selectionChanged: function SBD_selectionChanged() {
+ var accept = document.documentElement.getButton("accept");
+ var bookmarks = document.getElementById("bookmarks");
+ var disableAcceptButton = true;
+ if (bookmarks.hasSelection) {
+ if (!PlacesUtils.nodeIsSeparator(bookmarks.selectedNode))
+ disableAcceptButton = false;
+ }
+ accept.disabled = disableAcceptButton;
+ },
+
+ onItemDblClick: function SBD_onItemDblClick() {
+ var bookmarks = document.getElementById("bookmarks");
+ var selectedNode = bookmarks.selectedNode;
+ if (selectedNode && PlacesUtils.nodeIsURI(selectedNode)) {
+ /**
+ * The user has double clicked on a tree row that is a link. Take this to
+ * mean that they want that link to be their homepage, and close the dialog.
+ */
+ document.documentElement.getButton("accept").click();
+ }
+ },
+
+ /**
+ * User accepts their selection. Set all the selected URLs or the contents
+ * of the selected folder as the list of homepages.
+ */
+ accept: function SBD_accept() {
+ var bookmarks = document.getElementById("bookmarks");
+ NS_ASSERT(bookmarks.hasSelection,
+ "Should not be able to accept dialog if there is no selected URL!");
+ var urls = [];
+ var names = [];
+ var selectedNode = bookmarks.selectedNode;
+ if (PlacesUtils.nodeIsFolder(selectedNode)) {
+ var contents = PlacesUtils.getFolderContents(selectedNode.itemId).root;
+ var cc = contents.childCount;
+ for (var i = 0; i < cc; ++i) {
+ var node = contents.getChild(i);
+ if (PlacesUtils.nodeIsURI(node)) {
+ urls.push(node.uri);
+ names.push(node.title);
+ }
+ }
+ contents.containerOpen = false;
+ }
+ else {
+ urls.push(selectedNode.uri);
+ names.push(selectedNode.title);
+ }
+ window.arguments[0].urls = urls;
+ window.arguments[0].names = names;
+ }
+};
diff --git a/browser/components/preferences/selectBookmark.xul b/browser/components/preferences/selectBookmark.xul
new file mode 100644
index 000000000..5547534b6
--- /dev/null
+++ b/browser/components/preferences/selectBookmark.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/selectBookmark.dtd">
+
+<dialog id="selectBookmarkDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&selectBookmark.title;" style="width: 32em;"
+ persist="screenX screenY width height" screenX="24" screenY="24"
+ onload="SelectBookmarkDialog.init();"
+ ondialogaccept="SelectBookmarkDialog.accept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/selectBookmark.js"/>
+
+ <description>&selectBookmark.label;</description>
+
+ <separator class="thin"/>
+
+ <tree id="bookmarks" flex="1" type="places"
+ style="height: 15em;"
+ hidecolumnpicker="true"
+ seltype="single"
+ ondblclick="SelectBookmarkDialog.onItemDblClick();"
+ onselect="SelectBookmarkDialog.selectionChanged();">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="bookmarksChildren" flex="1"/>
+ </tree>
+
+ <separator class="thin"/>
+
+</dialog>
diff --git a/browser/components/preferences/translation.js b/browser/components/preferences/translation.js
new file mode 100644
index 000000000..cd570db0e
--- /dev/null
+++ b/browser/components/preferences/translation.js
@@ -0,0 +1,255 @@
+/* -*- 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/. */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gLangBundle", () =>
+ Services.strings.createBundle("chrome://global/locale/languageNames.properties"));
+
+const kPermissionType = "translate";
+const kLanguagesPref = "browser.translation.neverForLanguages";
+
+function Tree(aId, aData)
+{
+ this._data = aData;
+ this._tree = document.getElementById(aId);
+ this._tree.view = this;
+}
+
+Tree.prototype = {
+ get boxObject() {
+ return this._tree.treeBoxObject;
+ },
+ get isEmpty() {
+ return !this._data.length;
+ },
+ get hasSelection() {
+ return this.selection.count > 0;
+ },
+ getSelectedItems: function() {
+ let result = [];
+
+ let rc = this.selection.getRangeCount();
+ for (let i = 0; i < rc; ++i) {
+ let min = {}, max = {};
+ this.selection.getRangeAt(i, min, max);
+ for (let j = min.value; j <= max.value; ++j)
+ result.push(this._data[j]);
+ }
+
+ return result;
+ },
+
+ // nsITreeView implementation
+ get rowCount() {
+ return this._data.length;
+ },
+ getCellText: function (aRow, aColumn) {
+ return this._data[aRow];
+ },
+ isSeparator: function(aIndex) {
+ return false;
+ },
+ isSorted: function() {
+ return false;
+ },
+ isContainer: function(aIndex) {
+ return false;
+ },
+ setTree: function(aTree) {},
+ getImageSrc: function(aRow, aColumn) {},
+ getProgressMode: function(aRow, aColumn) {},
+ getCellValue: function(aRow, aColumn) {},
+ cycleHeader: function(column) {},
+ getRowProperties: function(row) {
+ return "";
+ },
+ getColumnProperties: function(column) {
+ return "";
+ },
+ getCellProperties: function(row, column) {
+ return "";
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView])
+};
+
+function Lang(aCode)
+{
+ this.langCode = aCode;
+ this._label = gLangBundle.GetStringFromName(aCode);
+}
+
+Lang.prototype = {
+ toString: function() {
+ return this._label;
+ }
+}
+
+var gTranslationExceptions = {
+ onLoad: function() {
+ if (this._siteTree) {
+ // Re-using an open dialog, clear the old observers.
+ this.uninit();
+ }
+
+ // Load site permissions into an array.
+ this._sites = [];
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+
+ if (perm.type == kPermissionType &&
+ perm.capability == Services.perms.DENY_ACTION) {
+ this._sites.push(perm.principal.origin);
+ }
+ }
+ Services.obs.addObserver(this, "perm-changed", false);
+ this._sites.sort();
+
+ this._siteTree = new Tree("sitesTree", this._sites);
+ this.onSiteSelected();
+
+ this._langs = this.getLanguageExceptions();
+ Services.prefs.addObserver(kLanguagesPref, this, false);
+ this._langTree = new Tree("languagesTree", this._langs);
+ this.onLanguageSelected();
+ },
+
+ // Get the list of languages we don't translate as an array.
+ getLanguageExceptions: function() {
+ let langs = Services.prefs.getCharPref(kLanguagesPref);
+ if (!langs)
+ return [];
+
+ let result = langs.split(",").map(code => new Lang(code));
+ result.sort();
+
+ return result;
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "perm-changed") {
+ if (aData == "cleared") {
+ if (!this._sites.length)
+ return;
+ let removed = this._sites.splice(0, this._sites.length);
+ this._siteTree.boxObject.rowCountChanged(0, - removed.length);
+ }
+ else {
+ let perm = aSubject.QueryInterface(Ci.nsIPermission);
+ if (perm.type != kPermissionType)
+ return;
+
+ if (aData == "added") {
+ if (perm.capability != Services.perms.DENY_ACTION)
+ return;
+ this._sites.push(perm.principal.origin);
+ this._sites.sort();
+ let boxObject = this._siteTree.boxObject;
+ boxObject.rowCountChanged(0, 1);
+ boxObject.invalidate();
+ }
+ else if (aData == "deleted") {
+ let index = this._sites.indexOf(perm.principal.origin);
+ if (index == -1)
+ return;
+ this._sites.splice(index, 1);
+ this._siteTree.boxObject.rowCountChanged(index, -1);
+ this.onSiteSelected();
+ return;
+ }
+ }
+ this.onSiteSelected();
+ }
+ else if (aTopic == "nsPref:changed") {
+ this._langs = this.getLanguageExceptions();
+ let change = this._langs.length - this._langTree.rowCount;
+ this._langTree._data = this._langs;
+ let boxObject = this._langTree.boxObject;
+ if (change)
+ boxObject.rowCountChanged(0, change);
+ boxObject.invalidate();
+ this.onLanguageSelected();
+ }
+ },
+
+ _handleButtonDisabling: function(aTree, aIdPart) {
+ let empty = aTree.isEmpty;
+ document.getElementById("removeAll" + aIdPart + "s").disabled = empty;
+ document.getElementById("remove" + aIdPart).disabled =
+ empty || !aTree.hasSelection;
+ },
+
+ onLanguageSelected: function() {
+ this._handleButtonDisabling(this._langTree, "Language");
+ },
+
+ onSiteSelected: function() {
+ this._handleButtonDisabling(this._siteTree, "Site");
+ },
+
+ onLanguageDeleted: function() {
+ let langs = Services.prefs.getCharPref(kLanguagesPref);
+ if (!langs)
+ return;
+
+ let removed = this._langTree.getSelectedItems().map(l => l.langCode);
+
+ langs = langs.split(",").filter(l => removed.indexOf(l) == -1);
+ Services.prefs.setCharPref(kLanguagesPref, langs.join(","));
+ },
+
+ onAllLanguagesDeleted: function() {
+ Services.prefs.setCharPref(kLanguagesPref, "");
+ },
+
+ onSiteDeleted: function() {
+ let removedSites = this._siteTree.getSelectedItems();
+ for (let origin of removedSites) {
+ let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
+ Services.perms.removeFromPrincipal(principal, kPermissionType);
+ }
+ },
+
+ onAllSitesDeleted: function() {
+ if (this._siteTree.isEmpty)
+ return;
+
+ let removedSites = this._sites.splice(0, this._sites.length);
+ this._siteTree.boxObject.rowCountChanged(0, -removedSites.length);
+
+ for (let origin of removedSites) {
+ let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
+ Services.perms.removeFromPrincipal(principal, kPermissionType);
+ }
+
+ this.onSiteSelected();
+ },
+
+ onSiteKeyPress: function(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE)
+ this.onSiteDeleted();
+ },
+
+ onLanguageKeyPress: function(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE)
+ this.onLanguageDeleted();
+ },
+
+ onWindowKeyPress: function(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
+ window.close();
+ },
+
+ uninit: function() {
+ Services.obs.removeObserver(this, "perm-changed");
+ Services.prefs.removeObserver(kLanguagesPref, this);
+ }
+};
diff --git a/browser/components/preferences/translation.xul b/browser/components/preferences/translation.xul
new file mode 100644
index 000000000..b5dfd1b9b
--- /dev/null
+++ b/browser/components/preferences/translation.xul
@@ -0,0 +1,88 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/translation.dtd">
+
+<window id="TranslationDialog" class="windowDialog"
+ windowtype="Browser:TranslationExceptions"
+ title="&window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: &window.width;;"
+ onload="gTranslationExceptions.onLoad();"
+ onunload="gTranslationExceptions.uninit();"
+ persist="screenX screenY width height"
+ onkeypress="gTranslationExceptions.onWindowKeyPress(event);">
+
+ <script src="chrome://browser/content/preferences/translation.js"/>
+
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <keyset>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+ </keyset>
+
+ <vbox class="largeDialogContainer">
+ <vbox class="contentPane" flex="1">
+ <label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
+ <separator class="thin"/>
+ <tree id="languagesTree" flex="1" style="height: 12em;"
+ hidecolumnpicker="true"
+ onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
+ onselect="gTranslationExceptions.onLanguageSelected();">
+ <treecols>
+ <treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ <hbox align="end">
+ <hbox class="actionButtons" flex="1">
+ <button id="removeLanguage" disabled="true"
+ accesskey="&removeLanguage.accesskey;"
+ icon="remove" label="&removeLanguage.label;"
+ oncommand="gTranslationExceptions.onLanguageDeleted();"/>
+ <button id="removeAllLanguages"
+ icon="clear" label="&removeAllLanguages.label;"
+ accesskey="&removeAllLanguages.accesskey;"
+ oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
+ <spacer flex="1"/>
+ </hbox>
+ </hbox>
+ <separator/>
+ <vbox class="contentPane" flex="1">
+ <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
+ <separator class="thin"/>
+ <tree id="sitesTree" flex="1" style="height: 12em;"
+ hidecolumnpicker="true"
+ onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
+ onselect="gTranslationExceptions.onSiteSelected();">
+ <treecols>
+ <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ </vbox>
+ <hbox align="end">
+ <hbox class="actionButtons" flex="1">
+ <button id="removeSite" disabled="true"
+ accesskey="&removeSite.accesskey;"
+ icon="remove" label="&removeSite.label;"
+ oncommand="gTranslationExceptions.onSiteDeleted();"/>
+ <button id="removeAllSites"
+ icon="clear" label="&removeAllSites.label;"
+ accesskey="&removeAllSites.accesskey;"
+ oncommand="gTranslationExceptions.onAllSitesDeleted();"/>
+ <spacer flex="1"/>
+ <button oncommand="close();" icon="close"
+ label="&button.close.label;" accesskey="&button.close.accesskey;"/>
+ </hbox>
+ </hbox>
+</window>
diff --git a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.css b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.css
new file mode 100644
index 000000000..29d7a843d
--- /dev/null
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.css
@@ -0,0 +1,10 @@
+html.private .showNormal,
+html.normal .showPrivate,
+body[tpEnabled] .showTpDisabled,
+body:not([tpEnabled]) .showTpEnabled {
+ display: none !important;
+}
+
+.hide {
+ display: none;
+}
diff --git a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
new file mode 100644
index 000000000..31ce96347
--- /dev/null
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const FAVICON_QUESTION = "chrome://global/skin/icons/question-32.png";
+const FAVICON_PRIVACY = "chrome://browser/skin/privatebrowsing/favicon.svg";
+
+var stringBundle = Services.strings.createBundle(
+ "chrome://browser/locale/aboutPrivateBrowsing.properties");
+
+var prefBranch = Services.prefs.getBranch("privacy.trackingprotection.");
+var prefObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ observe: function () {
+ let tpSubHeader = document.getElementById("tpSubHeader");
+ let tpToggle = document.getElementById("tpToggle");
+ let tpButton = document.getElementById("tpButton");
+ let title = document.getElementById("title");
+ let titleTracking = document.getElementById("titleTracking");
+ let globalTrackingEnabled = prefBranch.getBoolPref("enabled");
+ let trackingEnabled = globalTrackingEnabled ||
+ prefBranch.getBoolPref("pbmode.enabled");
+
+ tpButton.classList.toggle("hide", globalTrackingEnabled);
+ tpToggle.checked = trackingEnabled;
+ title.classList.toggle("hide", trackingEnabled);
+ titleTracking.classList.toggle("hide", !trackingEnabled);
+ tpSubHeader.classList.toggle("tp-off", !trackingEnabled);
+ }
+};
+prefBranch.addObserver("pbmode.enabled", prefObserver, true);
+prefBranch.addObserver("enabled", prefObserver, true);
+
+function setFavIcon(url) {
+ document.getElementById("favicon").setAttribute("href", url);
+}
+
+document.addEventListener("DOMContentLoaded", function () {
+ if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
+ document.documentElement.classList.remove("private");
+ document.documentElement.classList.add("normal");
+ document.title = stringBundle.GetStringFromName("title.normal");
+ document.getElementById("favicon")
+ .setAttribute("href", FAVICON_QUESTION);
+ document.getElementById("startPrivateBrowsing")
+ .addEventListener("command", openPrivateWindow);
+ return;
+ }
+
+ let tpToggle = document.getElementById("tpToggle");
+ document.getElementById("tpButton").addEventListener('click', () => {
+ tpToggle.click();
+ });
+
+ document.title = stringBundle.GetStringFromName("title.head");
+ document.getElementById("favicon")
+ .setAttribute("href", FAVICON_PRIVACY);
+ tpToggle.addEventListener("change", toggleTrackingProtection);
+ document.getElementById("startTour")
+ .addEventListener("click", dontShowIntroPanelAgain);
+
+ let formatURLPref = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Ci.nsIURLFormatter).formatURLPref;
+ document.getElementById("startTour").setAttribute("href",
+ formatURLPref("privacy.trackingprotection.introURL"));
+ document.getElementById("learnMore").setAttribute("href",
+ formatURLPref("app.support.baseURL") + "private-browsing");
+
+ // Update state that depends on preferences.
+ prefObserver.observe();
+}, false);
+
+function openPrivateWindow() {
+ // Ask chrome to open a private window
+ document.dispatchEvent(
+ new CustomEvent("AboutPrivateBrowsingOpenWindow", {bubbles:true}));
+}
+
+function toggleTrackingProtection() {
+ // Ask chrome to enable tracking protection
+ document.dispatchEvent(
+ new CustomEvent("AboutPrivateBrowsingToggleTrackingProtection",
+ {bubbles: true}));
+}
+
+function dontShowIntroPanelAgain() {
+ // Ask chrome to disable the doorhanger
+ document.dispatchEvent(
+ new CustomEvent("AboutPrivateBrowsingDontShowIntroPanelAgain",
+ {bubbles: true}));
+}
diff --git a/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
new file mode 100644
index 000000000..fb5c4ac8e
--- /dev/null
+++ b/browser/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
@@ -0,0 +1,85 @@
+<?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 % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+ %browserDTD;
+ <!ENTITY % aboutPrivateBrowsingDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd">
+ %aboutPrivateBrowsingDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml" class="private">
+ <head>
+ <link id="favicon" rel="icon" type="image/png"/>
+ <link rel="stylesheet" href="chrome://browser/content/aboutPrivateBrowsing.css" type="text/css" media="all"/>
+ <link rel="stylesheet" href="chrome://browser/skin/privatebrowsing/aboutPrivateBrowsing.css" type="text/css" media="all"/>
+ <script type="application/javascript;version=1.7" src="chrome://browser/content/aboutPrivateBrowsing.js"></script>
+ </head>
+
+ <body dir="&locale.dir;">
+ <p class="showNormal">&aboutPrivateBrowsing.notPrivate;</p>
+ <button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="startPrivateBrowsing"
+ class="showNormal"
+ label="&privatebrowsingpage.openPrivateWindow.label;"
+ accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"/>
+ <div class="showPrivate about-content-container">
+ <h1 class="title">
+ <span id="title">&privateBrowsing.title;</span>
+ <span id="titleTracking">&privateBrowsing.title.tracking;</span>
+ </h1>
+ <section class="section-main">
+ <p>&aboutPrivateBrowsing.info.notsaved.before;<strong>&aboutPrivateBrowsing.info.notsaved.emphasize;</strong>&aboutPrivateBrowsing.info.notsaved.after;</p>
+ <div class="list-row">
+ <ul>
+ <li>&aboutPrivateBrowsing.info.visited;</li>
+ <li>&aboutPrivateBrowsing.info.cookies;</li>
+ <li>&aboutPrivateBrowsing.info.searches;</li>
+ <li>&aboutPrivateBrowsing.info.temporaryFiles;</li>
+ </ul>
+ </div>
+ <p>&aboutPrivateBrowsing.info.saved.before;<strong>&aboutPrivateBrowsing.info.saved.emphasize;</strong>&aboutPrivateBrowsing.info.saved.after2;</p>
+ <div class="list-row">
+ <ul>
+ <li>&aboutPrivateBrowsing.info.bookmarks;</li>
+ <li>&aboutPrivateBrowsing.info.downloads;</li>
+ </ul>
+ </div>
+ <p>
+ &aboutPrivateBrowsing.note.before;
+ <strong>&aboutPrivateBrowsing.note.emphasize;</strong>
+ &aboutPrivateBrowsing.note.after;
+ </p>
+ </section>
+
+ <h2 id="tpSubHeader" class="about-subheader">
+ <span class="tpTitle">&trackingProtection.title;</span>
+ <input id="tpToggle" class="toggle toggle-input" type="checkbox"/>
+ <span id="tpButton" class="toggle-btn"></span>
+ </h2>
+
+ <section class="section-main">
+ <p>&trackingProtection.description2;</p>
+ <p>
+ <a id="startTour" class="button">&trackingProtection.startTour1;</a>
+ </p>
+ </section>
+
+ <section class="section-main">
+ <p class="about-info">&aboutPrivateBrowsing.learnMore2;
+ <a id="learnMore" target="_blank">&aboutPrivateBrowsing.learnMore2.title;</a>.
+ </p>
+ </section>
+
+ </div>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/jar.mn b/browser/components/privatebrowsing/jar.mn
new file mode 100644
index 000000000..a98d65163
--- /dev/null
+++ b/browser/components/privatebrowsing/jar.mn
@@ -0,0 +1,8 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/aboutPrivateBrowsing.css (content/aboutPrivateBrowsing.css)
+ content/browser/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
+ content/browser/aboutPrivateBrowsing.js (content/aboutPrivateBrowsing.js)
diff --git a/browser/components/privatebrowsing/moz.build b/browser/components/privatebrowsing/moz.build
new file mode 100644
index 000000000..486737a7f
--- /dev/null
+++ b/browser/components/privatebrowsing/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser/browser.ini',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Private Browsing')
diff --git a/browser/components/privatebrowsing/test/browser/.eslintrc.js b/browser/components/privatebrowsing/test/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/privatebrowsing/test/browser/browser.ini b/browser/components/privatebrowsing/test/browser/browser.ini
new file mode 100644
index 000000000..5efca4c0e
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser.ini
@@ -0,0 +1,54 @@
+[DEFAULT]
+tags = openwindow
+support-files =
+ browser_privatebrowsing_concurrent_page.html
+ browser_privatebrowsing_geoprompt_page.html
+ browser_privatebrowsing_localStorage_before_after_page.html
+ browser_privatebrowsing_localStorage_before_after_page2.html
+ browser_privatebrowsing_localStorage_page1.html
+ browser_privatebrowsing_localStorage_page2.html
+ browser_privatebrowsing_placesTitleNoUpdate.html
+ browser_privatebrowsing_protocolhandler_page.html
+ browser_privatebrowsing_windowtitle_page.html
+ head.js
+ popup.html
+ title.sjs
+ empty_file.html
+ file_favicon.html
+ file_favicon.png
+ file_favicon.png^headers^
+
+[browser_privatebrowsing_DownloadLastDirWithCPS.js]
+[browser_privatebrowsing_about.js]
+tags = trackingprotection
+[browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js]
+[browser_privatebrowsing_aboutSessionRestore.js]
+[browser_privatebrowsing_cache.js]
+[browser_privatebrowsing_certexceptionsui.js]
+[browser_privatebrowsing_concurrent.js]
+[browser_privatebrowsing_context_and_chromeFlags.js]
+[browser_privatebrowsing_crh.js]
+[browser_privatebrowsing_downloadLastDir.js]
+[browser_privatebrowsing_downloadLastDir_c.js]
+[browser_privatebrowsing_downloadLastDir_toggle.js]
+[browser_privatebrowsing_favicon.js]
+[browser_privatebrowsing_geoprompt.js]
+[browser_privatebrowsing_lastpbcontextexited.js]
+[browser_privatebrowsing_localStorage.js]
+[browser_privatebrowsing_localStorage_before_after.js]
+[browser_privatebrowsing_noSessionRestoreMenuOption.js]
+[browser_privatebrowsing_nonbrowser.js]
+[browser_privatebrowsing_opendir.js]
+[browser_privatebrowsing_placesTitleNoUpdate.js]
+[browser_privatebrowsing_placestitle.js]
+[browser_privatebrowsing_popupblocker.js]
+[browser_privatebrowsing_protocolhandler.js]
+[browser_privatebrowsing_sidebar.js]
+[browser_privatebrowsing_theming.js]
+[browser_privatebrowsing_ui.js]
+[browser_privatebrowsing_urlbarfocus.js]
+[browser_privatebrowsing_windowtitle.js]
+[browser_privatebrowsing_zoom.js]
+[browser_privatebrowsing_zoomrestore.js]
+[browser_privatebrowsing_newtab_from_popup.js]
+[browser_privatebrowsing_blobUrl.js]
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
new file mode 100644
index 000000000..bcd19b192
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_DownloadLastDirWithCPS.js
@@ -0,0 +1,282 @@
+/* -*- 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 gTests;
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+ gTests = runTest();
+ gTests.next();
+}
+
+/*
+ * ================
+ * Helper functions
+ * ================
+ */
+
+function moveAlong(aResult) {
+ try {
+ gTests.send(aResult);
+ } catch (x if x instanceof StopIteration) {
+ finish();
+ }
+}
+
+function createWindow(aOptions) {
+ whenNewWindowLoaded(aOptions, function(win) {
+ moveAlong(win);
+ });
+}
+
+function getFile(downloadLastDir, aURI) {
+ downloadLastDir.getFileAsync(aURI, function(result) {
+ moveAlong(result);
+ });
+}
+
+function setFile(downloadLastDir, aURI, aValue) {
+ downloadLastDir.setFile(aURI, aValue);
+ executeSoon(moveAlong);
+}
+
+function clearHistoryAndWait() {
+ clearHistory();
+ executeSoon(() => executeSoon(moveAlong));
+}
+
+/*
+ * ===================
+ * Function with tests
+ * ===================
+ */
+
+function runTest() {
+ let FileUtils =
+ Cu.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils;
+ let DownloadLastDir =
+ Cu.import("resource://gre/modules/DownloadLastDir.jsm", {}).DownloadLastDir;
+
+ let tmpDir = FileUtils.getDir("TmpD", [], true);
+ let dir1 = newDirectory();
+ let dir2 = newDirectory();
+ let dir3 = newDirectory();
+
+ let uri1 = Services.io.newURI("http://test1.com/", null, null);
+ let uri2 = Services.io.newURI("http://test2.com/", null, null);
+ let uri3 = Services.io.newURI("http://test3.com/", null, null);
+ let uri4 = Services.io.newURI("http://test4.com/", null, null);
+
+ // cleanup functions registration
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir.savePerSite");
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ [dir1, dir2, dir3].forEach(dir => dir.remove(true));
+ win.close();
+ pbWin.close();
+ });
+
+ function checkDownloadLastDir(gDownloadLastDir, aLastDir) {
+ is(gDownloadLastDir.file.path, aLastDir.path,
+ "gDownloadLastDir should point to the expected last directory");
+ getFile(gDownloadLastDir, uri1);
+ }
+
+ function checkDownloadLastDirNull(gDownloadLastDir) {
+ is(gDownloadLastDir.file, null, "gDownloadLastDir should be null");
+ getFile(gDownloadLastDir, uri1);
+ }
+
+ /*
+ * ================================
+ * Create a regular and a PB window
+ * ================================
+ */
+
+ let win = yield createWindow({private: false});
+ let pbWin = yield createWindow({private: true});
+
+ let downloadLastDir = new DownloadLastDir(win);
+ let pbDownloadLastDir = new DownloadLastDir(pbWin);
+
+ /*
+ * ==================
+ * Beginning of tests
+ * ==================
+ */
+
+ is(typeof downloadLastDir, "object",
+ "downloadLastDir should be a valid object");
+ is(downloadLastDir.file, null,
+ "LastDir pref should be null to start with");
+
+ // set up last dir
+ yield setFile(downloadLastDir, null, tmpDir);
+ is(downloadLastDir.file.path, tmpDir.path,
+ "LastDir should point to the tmpDir");
+ isnot(downloadLastDir.file, tmpDir,
+ "downloadLastDir.file should not be pointing to tmpDir");
+
+ // set uri1 to dir1, all should now return dir1
+ // also check that a new object is returned
+ yield setFile(downloadLastDir, uri1, dir1);
+ is(downloadLastDir.file.path, dir1.path,
+ "downloadLastDir should return dir1");
+ isnot(downloadLastDir.file, dir1,
+ "downloadLastDir.file should not return dir1");
+ is((yield getFile(downloadLastDir, uri1)).path, dir1.path,
+ "uri1 should return dir1"); // set in CPS
+ isnot((yield getFile(downloadLastDir, uri1)), dir1,
+ "getFile on uri1 should not return dir1");
+ is((yield getFile(downloadLastDir, uri2)).path, dir1.path,
+ "uri2 should return dir1"); // fallback
+ isnot((yield getFile(downloadLastDir, uri2)), dir1,
+ "getFile on uri2 should not return dir1");
+ is((yield getFile(downloadLastDir, uri3)).path, dir1.path,
+ "uri3 should return dir1"); // fallback
+ isnot((yield getFile(downloadLastDir, uri3)), dir1,
+ "getFile on uri3 should not return dir1");
+ is((yield getFile(downloadLastDir, uri4)).path, dir1.path,
+ "uri4 should return dir1"); // fallback
+ isnot((yield getFile(downloadLastDir, uri4)), dir1,
+ "getFile on uri4 should not return dir1");
+
+ // set uri2 to dir2, all except uri1 should now return dir2
+ yield setFile(downloadLastDir, uri2, dir2);
+ is(downloadLastDir.file.path, dir2.path,
+ "downloadLastDir should point to dir2");
+ is((yield getFile(downloadLastDir, uri1)).path, dir1.path,
+ "uri1 should return dir1"); // set in CPS
+ is((yield getFile(downloadLastDir, uri2)).path, dir2.path,
+ "uri2 should return dir2"); // set in CPS
+ is((yield getFile(downloadLastDir, uri3)).path, dir2.path,
+ "uri3 should return dir2"); // fallback
+ is((yield getFile(downloadLastDir, uri4)).path, dir2.path,
+ "uri4 should return dir2"); // fallback
+
+ // set uri3 to dir3, all except uri1 and uri2 should now return dir3
+ yield setFile(downloadLastDir, uri3, dir3);
+ is(downloadLastDir.file.path, dir3.path,
+ "downloadLastDir should point to dir3");
+ is((yield getFile(downloadLastDir, uri1)).path, dir1.path,
+ "uri1 should return dir1"); // set in CPS
+ is((yield getFile(downloadLastDir, uri2)).path, dir2.path,
+ "uri2 should return dir2"); // set in CPS
+ is((yield getFile(downloadLastDir, uri3)).path, dir3.path,
+ "uri3 should return dir3"); // set in CPS
+ is((yield getFile(downloadLastDir, uri4)).path, dir3.path,
+ "uri4 should return dir4"); // fallback
+
+ // set uri1 to dir2, all except uri3 should now return dir2
+ yield setFile(downloadLastDir, uri1, dir2);
+ is(downloadLastDir.file.path, dir2.path,
+ "downloadLastDir should point to dir2");
+ is((yield getFile(downloadLastDir, uri1)).path, dir2.path,
+ "uri1 should return dir2"); // set in CPS
+ is((yield getFile(downloadLastDir, uri2)).path, dir2.path,
+ "uri2 should return dir2"); // set in CPS
+ is((yield getFile(downloadLastDir, uri3)).path, dir3.path,
+ "uri3 should return dir3"); // set in CPS
+ is((yield getFile(downloadLastDir, uri4)).path, dir2.path,
+ "uri4 should return dir2"); // fallback
+
+ yield clearHistoryAndWait();
+
+ // check clearHistory removes all data
+ is(downloadLastDir.file, null, "clearHistory removes all data");
+ //is(Services.contentPrefs.hasPref(uri1, "browser.download.lastDir", null),
+ // false, "LastDir preference should be absent");
+ is((yield getFile(downloadLastDir, uri1)), null, "uri1 should point to null");
+ is((yield getFile(downloadLastDir, uri2)), null, "uri2 should point to null");
+ is((yield getFile(downloadLastDir, uri3)), null, "uri3 should point to null");
+ is((yield getFile(downloadLastDir, uri4)), null, "uri4 should point to null");
+
+ yield setFile(downloadLastDir, null, tmpDir);
+
+ // check data set outside PB mode is remembered
+ is((yield checkDownloadLastDir(pbDownloadLastDir, tmpDir)).path, tmpDir.path, "uri1 should return the expected last directory");
+ is((yield checkDownloadLastDir(downloadLastDir, tmpDir)).path, tmpDir.path, "uri1 should return the expected last directory");
+ yield clearHistoryAndWait();
+
+ yield setFile(downloadLastDir, uri1, dir1);
+
+ // check data set using CPS outside PB mode is remembered
+ is((yield checkDownloadLastDir(pbDownloadLastDir, dir1)).path, dir1.path, "uri1 should return the expected last directory");
+ is((yield checkDownloadLastDir(downloadLastDir, dir1)).path, dir1.path, "uri1 should return the expected last directory");
+ yield clearHistoryAndWait();
+
+ // check data set inside PB mode is forgotten
+ yield setFile(pbDownloadLastDir, null, tmpDir);
+
+ is((yield checkDownloadLastDir(pbDownloadLastDir, tmpDir)).path, tmpDir.path, "uri1 should return the expected last directory");
+ is((yield checkDownloadLastDirNull(downloadLastDir)), null, "uri1 should return the expected last directory");
+
+ yield clearHistoryAndWait();
+
+ // check data set using CPS inside PB mode is forgotten
+ yield setFile(pbDownloadLastDir, uri1, dir1);
+
+ is((yield checkDownloadLastDir(pbDownloadLastDir, dir1)).path, dir1.path, "uri1 should return the expected last directory");
+ is((yield checkDownloadLastDirNull(downloadLastDir)), null, "uri1 should return the expected last directory");
+
+ // check data set outside PB mode but changed inside is remembered correctly
+ yield setFile(downloadLastDir, uri1, dir1);
+ yield setFile(pbDownloadLastDir, uri1, dir2);
+ is((yield checkDownloadLastDir(pbDownloadLastDir, dir2)).path, dir2.path, "uri1 should return the expected last directory");
+ is((yield checkDownloadLastDir(downloadLastDir, dir1)).path, dir1.path, "uri1 should return the expected last directory");
+
+ /*
+ * ====================
+ * Create new PB window
+ * ====================
+ */
+
+ // check that the last dir store got cleared in a new PB window
+ pbWin.close();
+ // And give it time to close
+ executeSoon(moveAlong);
+ yield;
+ pbWin = yield createWindow({private: true});
+ pbDownloadLastDir = new DownloadLastDir(pbWin);
+
+ is((yield checkDownloadLastDir(pbDownloadLastDir, dir1)).path, dir1.path, "uri1 should return the expected last directory");
+
+ yield clearHistoryAndWait();
+
+ // check clearHistory inside PB mode clears data outside PB mode
+ yield setFile(pbDownloadLastDir, uri1, dir2);
+
+ yield clearHistoryAndWait();
+
+ is((yield checkDownloadLastDirNull(downloadLastDir)), null, "uri1 should return the expected last directory");
+ is((yield checkDownloadLastDirNull(pbDownloadLastDir)), null, "uri1 should return the expected last directory");
+
+ // check that disabling CPS works
+ Services.prefs.setBoolPref("browser.download.lastDir.savePerSite", false);
+
+ yield setFile(downloadLastDir, uri1, dir1);
+ is(downloadLastDir.file.path, dir1.path, "LastDir should be set to dir1");
+ is((yield getFile(downloadLastDir, uri1)).path, dir1.path, "uri1 should return dir1");
+ is((yield getFile(downloadLastDir, uri2)).path, dir1.path, "uri2 should return dir1");
+ is((yield getFile(downloadLastDir, uri3)).path, dir1.path, "uri3 should return dir1");
+ is((yield getFile(downloadLastDir, uri4)).path, dir1.path, "uri4 should return dir1");
+
+ downloadLastDir.setFile(uri2, dir2);
+ is(downloadLastDir.file.path, dir2.path, "LastDir should be set to dir2");
+ is((yield getFile(downloadLastDir, uri1)).path, dir2.path, "uri1 should return dir2");
+ is((yield getFile(downloadLastDir, uri2)).path, dir2.path, "uri2 should return dir2");
+ is((yield getFile(downloadLastDir, uri3)).path, dir2.path, "uri3 should return dir2");
+ is((yield getFile(downloadLastDir, uri4)).path, dir2.path, "uri4 should return dir2");
+
+ Services.prefs.clearUserPref("browser.download.lastDir.savePerSite");
+
+ // check that passing null to setFile clears the stored value
+ yield setFile(downloadLastDir, uri3, dir3);
+ is((yield getFile(downloadLastDir, uri3)).path, dir3.path, "LastDir should be set to dir3");
+ yield setFile(downloadLastDir, uri3, null);
+ is((yield getFile(downloadLastDir, uri3)), null, "uri3 should return null");
+
+ yield clearHistoryAndWait();
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.js
new file mode 100644
index 000000000..e00f3f67a
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_about.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/. */
+
+/**
+ * Opens a new private window and loads "about:privatebrowsing" there.
+ */
+function* openAboutPrivateBrowsing() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let tab = win.gBrowser.selectedBrowser;
+ tab.loadURI("about:privatebrowsing");
+ yield BrowserTestUtils.browserLoaded(tab);
+ return { win, tab };
+}
+
+/**
+ * Clicks the given link and checks this opens a new tab with the given URI.
+ */
+function* testLinkOpensTab({ win, tab, elementId, expectedUrl }) {
+ let newTabPromise = BrowserTestUtils.waitForNewTab(win.gBrowser, expectedUrl);
+ yield ContentTask.spawn(tab, { elementId }, function* ({ elementId }) {
+ content.document.getElementById(elementId).click();
+ });
+ let newTab = yield newTabPromise;
+ ok(true, `Clicking ${elementId} opened ${expectedUrl} in a new tab.`);
+ yield BrowserTestUtils.removeTab(newTab);
+}
+
+/**
+ * Clicks the given link and checks this opens the given URI in the same tab.
+ *
+ * This function does not return to the previous page.
+ */
+function* testLinkOpensUrl({ win, tab, elementId, expectedUrl }) {
+ let loadedPromise = BrowserTestUtils.browserLoaded(tab);
+ yield ContentTask.spawn(tab, { elementId }, function* ({ elementId }) {
+ content.document.getElementById(elementId).click();
+ });
+ yield loadedPromise;
+ is(tab.currentURI.spec, expectedUrl,
+ `Clicking ${elementId} opened ${expectedUrl} in the same tab.`);
+}
+
+/**
+ * Tests the links in "about:privatebrowsing".
+ */
+add_task(function* test_links() {
+ // Use full version and change the remote URLs to prevent network access.
+ Services.prefs.setCharPref("app.support.baseURL", "https://example.com/");
+ Services.prefs.setCharPref("privacy.trackingprotection.introURL",
+ "https://example.com/tour");
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.trackingprotection.introURL");
+ Services.prefs.clearUserPref("app.support.baseURL");
+ });
+
+ let { win, tab } = yield openAboutPrivateBrowsing();
+
+ yield testLinkOpensTab({ win, tab,
+ elementId: "learnMore",
+ expectedUrl: "https://example.com/private-browsing",
+ });
+
+ yield testLinkOpensUrl({ win, tab,
+ elementId: "startTour",
+ expectedUrl: "https://example.com/tour",
+ });
+
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Tests the action to disable and re-enable Tracking Protection in
+ * "about:privatebrowsing".
+ */
+add_task(function* test_toggleTrackingProtection() {
+ // Use tour version but disable Tracking Protection.
+ Services.prefs.setBoolPref("privacy.trackingprotection.pbmode.enabled",
+ true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("privacy.trackingprotection.pbmode.enabled");
+ });
+
+ let { win, tab } = yield openAboutPrivateBrowsing();
+
+ // Set up the observer for the preference change before triggering the action.
+ let prefBranch =
+ Services.prefs.getBranch("privacy.trackingprotection.pbmode.");
+ let waitForPrefChanged = () => new Promise(resolve => {
+ let prefObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+ observe: function () {
+ prefBranch.removeObserver("enabled", prefObserver);
+ resolve();
+ },
+ };
+ prefBranch.addObserver("enabled", prefObserver, false);
+ });
+
+ let promisePrefChanged = waitForPrefChanged();
+ yield ContentTask.spawn(tab, {}, function* () {
+ content.document.getElementById("tpButton").click();
+ });
+ yield promisePrefChanged;
+ ok(!prefBranch.getBoolPref("enabled"), "Tracking Protection is disabled.");
+
+ promisePrefChanged = waitForPrefChanged();
+ yield ContentTask.spawn(tab, {}, function* () {
+ content.document.getElementById("tpButton").click();
+ });
+ yield promisePrefChanged;
+ ok(prefBranch.getBoolPref("enabled"), "Tracking Protection is enabled.");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutHomeButtonAfterWindowClose.js
new file mode 100644
index 000000000..6f52f7719
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutHomeButtonAfterWindowClose.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/. */
+
+// This test checks that the Session Restore "Restore Previous Session"
+// button on about:home is disabled in private mode
+add_task(function* test_no_sessionrestore_button() {
+ // Opening, then closing, a private window shouldn't create session data.
+ (yield BrowserTestUtils.openNewBrowserWindow({private: true})).close();
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let tab = win.gBrowser.addTab("about:home");
+ let browser = tab.linkedBrowser;
+
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ let button = content.document.getElementById("restorePreviousSession");
+ Assert.equal(content.getComputedStyle(button).display, "none",
+ "The Session Restore about:home button should be disabled");
+ });
+
+ win.close();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.js
new file mode 100644
index 000000000..5f6a91836
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_aboutSessionRestore.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/. */
+
+// This test checks that the session restore button from about:sessionrestore
+// is disabled in private mode
+add_task(function* testNoSessionRestoreButton() {
+ // Opening, then closing, a private window shouldn't create session data.
+ (yield BrowserTestUtils.openNewBrowserWindow({private: true})).close();
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let tab = win.gBrowser.addTab("about:sessionrestore");
+ let browser = tab.linkedBrowser;
+
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield ContentTask.spawn(browser, null, function* () {
+ Assert.ok(content.document.getElementById("errorTryAgain").disabled,
+ "The Restore about:sessionrestore button should be disabled");
+ });
+
+ win.close();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js
new file mode 100644
index 000000000..2ceb1032b
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_blobUrl.js
@@ -0,0 +1,45 @@
+"use strict";
+
+// Here we want to test that blob URLs are not available between private and
+// non-private browsing.
+
+const BASE_URI = "http://mochi.test:8888/browser/browser/components/"
+ + "privatebrowsing/test/browser/empty_file.html";
+
+add_task(function* test() {
+ info("Creating a normal window...");
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let tab = win.gBrowser.selectedBrowser;
+ tab.loadURI(BASE_URI);
+ yield BrowserTestUtils.browserLoaded(tab);
+
+ let blobURL;
+
+ info("Creating a blob URL...");
+ yield ContentTask.spawn(tab, null, function() {
+ return Promise.resolve(content.window.URL.createObjectURL(new content.window.Blob([123])));
+ }).then(newURL => { blobURL = newURL });
+
+ info("Blob URL: " + blobURL);
+
+ info("Creating a private window...");
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let privateTab = privateWin.gBrowser.selectedBrowser;
+ privateTab.loadURI(BASE_URI);
+ yield BrowserTestUtils.browserLoaded(privateTab);
+
+ yield ContentTask.spawn(privateTab, blobURL, function(url) {
+ return new Promise(resolve => {
+ var xhr = new content.window.XMLHttpRequest();
+ xhr.onerror = function() { resolve("SendErrored"); }
+ xhr.onload = function() { resolve("SendLoaded"); }
+ xhr.open("GET", url);
+ xhr.send();
+ });
+ }).then(status => {
+ is(status, "SendErrored", "Using a blob URI from one user context id in another should not work");
+ });
+
+ yield BrowserTestUtils.closeWindow(win);
+ yield BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
new file mode 100644
index 000000000..4990f6d3b
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Check about:cache after private browsing
+// This test covers MozTrap test 6047
+// bug 880621
+
+var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
+
+var tmp = {};
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+var Sanitizer = tmp.Sanitizer;
+
+function test() {
+
+ waitForExplicitFinish();
+
+ sanitizeCache();
+
+ let nrEntriesR1 = getStorageEntryCount("regular", function(nrEntriesR1) {
+ is(nrEntriesR1, 0, "Disk cache reports 0KB and has no entries");
+
+ get_cache_for_private_window();
+ });
+}
+
+function cleanup() {
+ let prefs = Services.prefs.getBranch("privacy.cpd.");
+
+ prefs.clearUserPref("history");
+ prefs.clearUserPref("downloads");
+ prefs.clearUserPref("cache");
+ prefs.clearUserPref("cookies");
+ prefs.clearUserPref("formdata");
+ prefs.clearUserPref("offlineApps");
+ prefs.clearUserPref("passwords");
+ prefs.clearUserPref("sessions");
+ prefs.clearUserPref("siteSettings");
+}
+
+function sanitizeCache() {
+
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+
+ let prefs = gPrefService.getBranch(s.prefDomain);
+ prefs.setBoolPref("history", false);
+ prefs.setBoolPref("downloads", false);
+ prefs.setBoolPref("cache", true);
+ prefs.setBoolPref("cookies", false);
+ prefs.setBoolPref("formdata", false);
+ prefs.setBoolPref("offlineApps", false);
+ prefs.setBoolPref("passwords", false);
+ prefs.setBoolPref("sessions", false);
+ prefs.setBoolPref("siteSettings", false);
+
+ s.sanitize();
+}
+
+function get_cache_service() {
+ return Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+}
+
+function getStorageEntryCount(device, goon) {
+ var cs = get_cache_service();
+
+ var storage;
+ switch (device) {
+ case "private":
+ storage = cs.diskCacheStorage(LoadContextInfo.private, false);
+ break;
+ case "regular":
+ storage = cs.diskCacheStorage(LoadContextInfo.default, false);
+ break;
+ default:
+ throw "Unknown device " + device + " at getStorageEntryCount";
+ }
+
+ var visitor = {
+ entryCount: 0,
+ onCacheStorageInfo: function (aEntryCount, aConsumption) {
+ },
+ onCacheEntryInfo: function(uri)
+ {
+ var urispec = uri.asciiSpec;
+ info(device + ":" + urispec + "\n");
+ if (urispec.match(/^http:\/\/example.org\//))
+ ++this.entryCount;
+ },
+ onCacheEntryVisitCompleted: function()
+ {
+ goon(this.entryCount);
+ }
+ };
+
+ storage.asyncVisitStorage(visitor, true);
+}
+
+function get_cache_for_private_window () {
+ let win = whenNewWindowLoaded({private: true}, function() {
+
+ executeSoon(function() {
+
+ ok(true, "The private window got loaded");
+
+ let tab = win.gBrowser.addTab("http://example.org");
+ win.gBrowser.selectedTab = tab;
+ let newTabBrowser = win.gBrowser.getBrowserForTab(tab);
+
+ newTabBrowser.addEventListener("load", function eventHandler() {
+ newTabBrowser.removeEventListener("load", eventHandler, true);
+
+ executeSoon(function() {
+
+ getStorageEntryCount("private", function(nrEntriesP) {
+ ok(nrEntriesP >= 1, "Memory cache reports some entries from example.org domain");
+
+ getStorageEntryCount("regular", function(nrEntriesR2) {
+ is(nrEntriesR2, 0, "Disk cache reports 0KB and has no entries");
+
+ cleanup();
+
+ win.close();
+ finish();
+ });
+ });
+ });
+ }, true);
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.js
new file mode 100644
index 000000000..519f43475
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_certexceptionsui.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/. */
+
+// This test makes sure that certificate exceptions UI behaves correctly
+// in private browsing windows, based on whether it's opened from the prefs
+// window or from the SSL error page (see bug 461627).
+
+function test() {
+ const EXCEPTIONS_DLG_URL = 'chrome://pippki/content/exceptionDialog.xul';
+ const EXCEPTIONS_DLG_FEATURES = 'chrome,centerscreen';
+ const INVALID_CERT_LOCATION = 'https://nocert.example.com/';
+ waitForExplicitFinish();
+
+ // open a private browsing window
+ var pbWin = OpenBrowserWindow({private: true});
+ pbWin.addEventListener("load", function onLoad() {
+ pbWin.removeEventListener("load", onLoad, false);
+ doTest();
+ }, false);
+
+ // Test the certificate exceptions dialog
+ function doTest() {
+ let params = {
+ exceptionAdded : false,
+ location: INVALID_CERT_LOCATION,
+ prefetchCert: true,
+ };
+ function testCheckbox() {
+ win.removeEventListener("load", testCheckbox, false);
+ Services.obs.addObserver(function onCertUI(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(onCertUI, "cert-exception-ui-ready");
+ ok(win.gCert, "The certificate information should be available now");
+
+ let checkbox = win.document.getElementById("permanent");
+ ok(checkbox.hasAttribute("disabled"),
+ "the permanent checkbox should be disabled when handling the private browsing mode");
+ ok(!checkbox.hasAttribute("checked"),
+ "the permanent checkbox should not be checked when handling the private browsing mode");
+ win.close();
+ cleanup();
+ }, "cert-exception-ui-ready", false);
+ }
+ var win = pbWin.openDialog(EXCEPTIONS_DLG_URL, "", EXCEPTIONS_DLG_FEATURES, params);
+ win.addEventListener("load", testCheckbox, false);
+ }
+
+ function cleanup() {
+ // close the private browsing window
+ pbWin.close();
+ finish();
+ }
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
new file mode 100644
index 000000000..b73bbf219
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent.js
@@ -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/. */
+
+// Test opening two tabs that share a localStorage, but keep one in private mode.
+// Ensure that values from one don't leak into the other, and that values from
+// earlier private storage sessions aren't visible later.
+
+// Step 1: create new tab, load a page that sets test=value in non-private storage
+// Step 2: create a new tab, load a page that sets test2=value2 in private storage
+// Step 3: load a page in the tab from step 1 that checks the value of test2 is value2 and the total count in non-private storage is 1
+// Step 4: load a page in the tab from step 2 that checks the value of test is value and the total count in private storage is 1
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+add_task(function test() {
+ let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html';
+
+ function getElts(browser) {
+ return browser.contentTitle.split('|');
+ };
+
+ // Step 1
+ let non_private_browser = gBrowser.selectedBrowser;
+ non_private_browser.loadURI(prefix + '?action=set&name=test&value=value&initial=true');
+ yield BrowserTestUtils.browserLoaded(non_private_browser);
+
+
+ // Step 2
+ let private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
+ let private_browser = private_window.getBrowser().selectedBrowser;
+ private_browser.loadURI(prefix + '?action=set&name=test2&value=value2');
+ yield BrowserTestUtils.browserLoaded(private_browser);
+
+
+ // Step 3
+ non_private_browser.loadURI(prefix + '?action=get&name=test2');
+ yield BrowserTestUtils.browserLoaded(non_private_browser);
+ let elts = yield getElts(non_private_browser);
+ isnot(elts[0], 'value2', "public window shouldn't see private storage");
+ is(elts[1], '1', "public window should only see public items");
+
+
+ // Step 4
+ private_browser.loadURI(prefix + '?action=get&name=test');
+ yield BrowserTestUtils.browserLoaded(private_browser);
+ elts = yield getElts(private_browser);
+ isnot(elts[0], 'value', "private window shouldn't see public storage");
+ is(elts[1], '1', "private window should only see private items");
+
+
+ // Reopen the private window again, without privateBrowsing, which should clear the
+ // the private storage.
+ private_window.close();
+ private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : false });
+ private_browser = null;
+ yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
+ private_browser = private_window.getBrowser().selectedBrowser;
+
+ private_browser.loadURI(prefix + '?action=get&name=test2');
+ yield BrowserTestUtils.browserLoaded(private_browser);
+ elts = yield getElts(private_browser);
+ isnot(elts[0], 'value2', "public window shouldn't see cleared private storage");
+ is(elts[1], '1', "public window should only see public items");
+
+
+ // Making it private again should clear the storage and it shouldn't
+ // be able to see the old private storage as well.
+ private_window.close();
+ private_window = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
+ private_browser = null;
+ yield new Promise(resolve => Cu.schedulePreciseGC(resolve));
+ private_browser = private_window.getBrowser().selectedBrowser;
+
+ private_browser.loadURI(prefix + '?action=set&name=test3&value=value3');
+ yield BrowserTestUtils.browserLoaded(private_browser);
+ elts = yield getElts(private_browser);
+ is(elts[1], '1', "private window should only see new private items");
+
+ // Cleanup.
+ non_private_browser.loadURI(prefix + '?final=true');
+ yield BrowserTestUtils.browserLoaded(non_private_browser);
+ private_window.close();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html
new file mode 100644
index 000000000..db35b114d
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_concurrent_page.html
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ var oGetVars = {};
+
+ if (window.location.search.length > 1) {
+ for (var aItKey, nKeyId = 0, aCouples = window.location.search.substr(1).split("&");
+ nKeyId < aCouples.length;
+ nKeyId++) {
+ aItKey = aCouples[nKeyId].split("=");
+ oGetVars[unescape(aItKey[0])] = aItKey.length > 1 ? unescape(aItKey[1]) : "";
+ }
+ }
+
+ if (oGetVars.initial == 'true') {
+ localStorage.clear();
+ }
+
+ if (oGetVars.action == 'set') {
+ localStorage.setItem(oGetVars.name, oGetVars.value);
+ document.title = localStorage.getItem(oGetVars.name) + "|" + localStorage.length;
+ } else if (oGetVars.action == 'get') {
+ document.title = localStorage.getItem(oGetVars.name) + "|" + localStorage.length;
+ }
+
+ if (oGetVars.final == 'true') {
+ localStorage.clear();
+ }
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
new file mode 100644
index 000000000..30f7ee025
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_context_and_chromeFlags.js
@@ -0,0 +1,60 @@
+"use strict";
+
+/**
+ * Given some window in the parent process, ensure that
+ * the nsIXULWindow has the CHROME_PRIVATE_WINDOW chromeFlag,
+ * and that the usePrivateBrowsing property is set to true on
+ * both the window's nsILoadContext, as well as on the initial
+ * browser's content docShell nsILoadContext.
+ *
+ * @param win (nsIDOMWindow)
+ * An nsIDOMWindow in the parent process.
+ * @return Promise
+ */
+function assertWindowIsPrivate(win) {
+ let winDocShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ let chromeFlags = winDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .chromeFlags;
+
+ if (!win.gBrowser.selectedBrowser.hasContentOpener) {
+ Assert.ok(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ "Should have the private window chrome flag");
+ }
+
+ let loadContext = winDocShell.QueryInterface(Ci.nsILoadContext);
+ Assert.ok(loadContext.usePrivateBrowsing,
+ "The parent window should be using private browsing");
+
+ return ContentTask.spawn(win.gBrowser.selectedBrowser, null, function*() {
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ Assert.ok(loadContext.usePrivateBrowsing,
+ "Content docShell should be using private browsing");
+ });
+}
+
+/**
+ * Tests that chromeFlags bits and the nsILoadContext.usePrivateBrowsing
+ * attribute are properly set when opening a new private browsing
+ * window.
+ */
+add_task(function* test_context_and_chromeFlags() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ yield assertWindowIsPrivate(win);
+
+ let browser = win.gBrowser.selectedBrowser;
+
+ let newWinPromise = BrowserTestUtils.waitForNewWindow();
+ yield ContentTask.spawn(browser, null, function*() {
+ content.open("http://example.com", "_blank", "width=100,height=100");
+ });
+
+ let win2 = yield newWinPromise;
+ yield assertWindowIsPrivate(win2);
+
+ yield BrowserTestUtils.closeWindow(win2);
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.js
new file mode 100644
index 000000000..cd316d1fb
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_crh.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/. */
+
+// This test makes sure that the Clear Recent History menu item and command
+// is disabled inside the private browsing mode.
+
+add_task(function test() {
+ function checkDisableOption(aPrivateMode, aWindow) {
+ let crhCommand = aWindow.document.getElementById("Tools:Sanitize");
+ ok(crhCommand, "The clear recent history command should exist");
+
+ is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aPrivateMode,
+ "PrivateBrowsingUtils should report the correct per-window private browsing status");
+ is(crhCommand.hasAttribute("disabled"), aPrivateMode,
+ "Clear Recent History command should be disabled according to the private browsing mode");
+ };
+
+ let testURI = "http://mochi.test:8888/";
+
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let privateBrowser = privateWin.gBrowser.selectedBrowser;
+ privateBrowser.loadURI(testURI);
+ yield BrowserTestUtils.browserLoaded(privateBrowser);
+
+ info("Test on private window");
+ checkDisableOption(true, privateWin);
+
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let browser = win.gBrowser.selectedBrowser;
+ browser.loadURI(testURI);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ info("Test on public window");
+ checkDisableOption(false, win);
+
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(privateWin);
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
new file mode 100644
index 000000000..81b2943ee
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir.js
@@ -0,0 +1,93 @@
+/* -*- 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 test() {
+ waitForExplicitFinish();
+
+ let FileUtils =
+ Cu.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils;
+ let DownloadLastDir =
+ Cu.import("resource://gre/modules/DownloadLastDir.jsm", {}).DownloadLastDir;
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ let launcher = {
+ source: Services.io.newURI("http://test1.com/file", null, null)
+ };
+
+ MockFilePicker.init(window);
+ MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
+
+ let prefs = Services.prefs.getBranch("browser.download.");
+ let launcherDialog =
+ Cc["@mozilla.org/helperapplauncherdialog;1"].
+ getService(Ci.nsIHelperAppLauncherDialog);
+ let tmpDir = FileUtils.getDir("TmpD", [], true);
+ let dir1 = newDirectory();
+ let dir2 = newDirectory();
+ let dir3 = newDirectory();
+ let file1 = newFileInDirectory(dir1);
+ let file2 = newFileInDirectory(dir2);
+ let file3 = newFileInDirectory(dir3);
+
+ // cleanup functions registration
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ [dir1, dir2, dir3].forEach(dir => dir.remove(true));
+ MockFilePicker.cleanup();
+ });
+ prefs.setComplexValue("lastDir", Ci.nsIFile, tmpDir);
+
+ function testOnWindow(aPrivate, aCallback) {
+ whenNewWindowLoaded({private: aPrivate}, function(win) {
+ let gDownloadLastDir = new DownloadLastDir(win);
+ aCallback(win, gDownloadLastDir);
+ gDownloadLastDir.cleanupPrivateFile();
+ });
+ }
+
+ function testDownloadDir(aWin, gDownloadLastDir, aFile, aDisplayDir, aLastDir,
+ aGlobalLastDir, aCallback) {
+ // Check lastDir preference.
+ is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aDisplayDir.path,
+ "LastDir should be the expected display dir");
+ // Check gDownloadLastDir value.
+ is(gDownloadLastDir.file.path, aDisplayDir.path,
+ "gDownloadLastDir should be the expected display dir");
+
+ MockFilePicker.returnFiles = [aFile];
+ MockFilePicker.displayDirectory = null;
+
+ launcher.saveDestinationAvailable = function (file) {
+ ok(!!file, "promptForSaveToFile correctly returned a file");
+
+ // File picker should start with expected display dir.
+ is(MockFilePicker.displayDirectory.path, aDisplayDir.path,
+ "File picker should start with browser.download.lastDir");
+ // browser.download.lastDir should be modified on not private windows
+ is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aLastDir.path,
+ "LastDir should be the expected last dir");
+ // gDownloadLastDir should be usable outside of private windows
+ is(gDownloadLastDir.file.path, aGlobalLastDir.path,
+ "gDownloadLastDir should be the expected global last dir");
+
+ launcher.saveDestinationAvailable = null;
+ aWin.close();
+ aCallback();
+ };
+
+ launcherDialog.promptForSaveToFileAsync(launcher, aWin, null, null, null);
+ }
+
+ testOnWindow(false, function(win, downloadDir) {
+ testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function() {
+ testOnWindow(true, function(win, downloadDir) {
+ testDownloadDir(win, downloadDir, file2, dir1, dir1, dir2, function() {
+ testOnWindow(false, function(win, downloadDir) {
+ testDownloadDir(win, downloadDir, file3, dir1, dir3, dir3, finish);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
new file mode 100644
index 000000000..5a04d1999
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
@@ -0,0 +1,95 @@
+/* -*- 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 test() {
+ waitForExplicitFinish();
+
+ let FileUtils =
+ Cu.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils;
+ let DownloadLastDir =
+ Cu.import("resource://gre/modules/DownloadLastDir.jsm", {}).DownloadLastDir;
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+
+ MockFilePicker.init(window);
+ MockFilePicker.returnValue = Ci.nsIFilePicker.returnOK;
+
+ let validateFileNameToRestore = validateFileName;
+ let prefs = Services.prefs.getBranch("browser.download.");
+ let tmpDir = FileUtils.getDir("TmpD", [], true);
+ let dir1 = newDirectory();
+ let dir2 = newDirectory();
+ let dir3 = newDirectory();
+ let file1 = newFileInDirectory(dir1);
+ let file2 = newFileInDirectory(dir2);
+ let file3 = newFileInDirectory(dir3);
+
+ // cleanup function registration
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ [dir1, dir2, dir3].forEach(dir => dir.remove(true));
+ MockFilePicker.cleanup();
+ validateFileName = validateFileNameToRestore;
+ });
+
+ // Overwrite validateFileName to validate everything
+ validateFileName = foo => foo;
+
+ let params = {
+ fileInfo: new FileInfo("test.txt", "test.txt", "test", "txt", "http://mozilla.org/test.txt"),
+ contentType: "text/plain",
+ saveMode: SAVEMODE_FILEONLY,
+ saveAsType: kSaveAsType_Complete,
+ file: null
+ };
+
+ prefs.setComplexValue("lastDir", Ci.nsIFile, tmpDir);
+
+ function testOnWindow(aPrivate, aCallback) {
+ whenNewWindowLoaded({private: aPrivate}, function(win) {
+ let gDownloadLastDir = new DownloadLastDir(win);
+ aCallback(win, gDownloadLastDir);
+ });
+ }
+
+ function testDownloadDir(aWin, gDownloadLastDir, aFile, aDisplayDir, aLastDir,
+ aGlobalLastDir, aCallback) {
+ // Check lastDir preference.
+ is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aDisplayDir.path,
+ "LastDir should be the expected display dir");
+ // Check gDownloadLastDir value.
+ is(gDownloadLastDir.file.path, aDisplayDir.path,
+ "gDownloadLastDir should be the expected display dir");
+
+ MockFilePicker.returnFiles = [aFile];
+ MockFilePicker.displayDirectory = null;
+ aWin.promiseTargetFile(params).then(function() {
+ // File picker should start with expected display dir.
+ is(MockFilePicker.displayDirectory.path, aDisplayDir.path,
+ "File picker should start with browser.download.lastDir");
+ // browser.download.lastDir should be modified on not private windows
+ is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aLastDir.path,
+ "LastDir should be the expected last dir");
+ // gDownloadLastDir should be usable outside of private windows
+ is(gDownloadLastDir.file.path, aGlobalLastDir.path,
+ "gDownloadLastDir should be the expected global last dir");
+
+ gDownloadLastDir.cleanupPrivateFile();
+ aWin.close();
+ aCallback();
+ }).then(null, function() { ok(false); });
+ }
+
+ testOnWindow(false, function(win, downloadDir) {
+ testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function() {
+ testOnWindow(true, function(win, downloadDir) {
+ testDownloadDir(win, downloadDir, file2, dir1, dir1, dir2, function() {
+ testOnWindow(false, function(win, downloadDir) {
+ testDownloadDir(win, downloadDir, file3, dir1, dir3, dir3, finish);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js
new file mode 100644
index 000000000..b192c08f7
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_toggle.js
@@ -0,0 +1,105 @@
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/DownloadLastDir.jsm");
+
+/**
+ * Tests how the browser remembers the last download folder
+ * from download to download, with a particular emphasis
+ * on how it behaves when private browsing windows open.
+ */
+add_task(function* test_downloads_last_dir_toggle() {
+ let tmpDir = FileUtils.getDir("TmpD", [], true);
+ let dir1 = newDirectory();
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.download.lastDir");
+ dir1.remove(true);
+ });
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let gDownloadLastDir = new DownloadLastDir(win);
+ is(typeof gDownloadLastDir, "object",
+ "gDownloadLastDir should be a valid object");
+ is(gDownloadLastDir.file, null,
+ "gDownloadLastDir.file should be null to start with");
+
+ gDownloadLastDir.file = tmpDir;
+ is(gDownloadLastDir.file.path, tmpDir.path,
+ "LastDir should point to the temporary directory");
+ isnot(gDownloadLastDir.file, tmpDir,
+ "gDownloadLastDir.file should not be pointing to the tmpDir");
+
+ gDownloadLastDir.file = 1; // not an nsIFile
+ is(gDownloadLastDir.file, null, "gDownloadLastDir.file should be null");
+
+ gDownloadLastDir.file = tmpDir;
+ clearHistory();
+ is(gDownloadLastDir.file, null, "gDownloadLastDir.file should be null");
+
+ gDownloadLastDir.file = tmpDir;
+ yield BrowserTestUtils.closeWindow(win);
+
+ info("Opening the first private window");
+ yield testHelper({ private: true, expectedDir: tmpDir });
+ info("Opening a non-private window");
+ yield testHelper({ private: false, expectedDir: tmpDir });
+ info("Opening a private window and setting download directory");
+ yield testHelper({ private: true, setDir: dir1, expectedDir: dir1 });
+ info("Opening a non-private window and checking download directory");
+ yield testHelper({ private: false, expectedDir: tmpDir });
+ info("Opening private window and clearing history");
+ yield testHelper({ private: true, clearHistory: true, expectedDir: null });
+ info("Opening a non-private window and checking download directory");
+ yield testHelper({ private: true, expectedDir: null });
+});
+
+/**
+ * Opens a new window and performs some test actions on it based
+ * on the options object that have been passed in.
+ *
+ * @param options (Object)
+ * An object with the following properties:
+ *
+ * clearHistory (bool, optional):
+ * Whether or not to simulate clearing session history.
+ * Defaults to false.
+ *
+ * setDir (nsIFile, optional):
+ * An nsIFile for setting the last download directory.
+ * If not set, the load download directory is not changed.
+ *
+ * expectedDir (nsIFile, expectedDir):
+ * An nsIFile for what we expect the last download directory
+ * should be. The nsIFile is not compared directly - only
+ * paths are compared. If expectedDir is not set, then the
+ * last download directory is expected to be null.
+ *
+ * @returns Promise
+ */
+function testHelper(options) {
+ return new Task.spawn(function() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow(options);
+ let gDownloadLastDir = new DownloadLastDir(win);
+
+ if (options.clearHistory) {
+ clearHistory();
+ }
+
+ if (options.setDir) {
+ gDownloadLastDir.file = options.setDir;
+ }
+
+ let expectedDir = options.expectedDir;
+
+ if (expectedDir) {
+ is(gDownloadLastDir.file.path, expectedDir.path,
+ "gDownloadLastDir should point to the expected last directory");
+ isnot(gDownloadLastDir.file, expectedDir,
+ "gDownloadLastDir.file should not be pointing to the last directory");
+ } else {
+ is(gDownloadLastDir.file, null, "gDownloadLastDir should be null");
+ }
+
+ gDownloadLastDir.cleanupPrivateFile();
+ yield BrowserTestUtils.closeWindow(win);
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
new file mode 100644
index 000000000..86f714082
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
@@ -0,0 +1,293 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test make sure that the favicon of the private browsing is isolated.
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+const TEST_SITE = "http://mochi.test:8888";
+const TEST_CACHE_SITE = "http://www.example.com";
+const TEST_DIRECTORY = "/browser/browser/components/privatebrowsing/test/browser/";
+
+const TEST_PAGE = TEST_SITE + TEST_DIRECTORY + "file_favicon.html";
+const TEST_CACHE_PAGE = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.html";
+const FAVICON_URI = TEST_SITE + TEST_DIRECTORY + "file_favicon.png";
+const FAVICON_CACHE_URI = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.png";
+
+let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+
+function clearAllImageCaches() {
+ let tools = SpecialPowers.Cc["@mozilla.org/image/tools;1"]
+ .getService(SpecialPowers.Ci.imgITools);
+ let imageCache = tools.getImgCacheForDocument(window.document);
+ imageCache.clearCache(true); // true=chrome
+ imageCache.clearCache(false); // false=content
+}
+
+function clearAllPlacesFavicons() {
+ let faviconService = Cc["@mozilla.org/browser/favicon-service;1"]
+ .getService(Ci.nsIFaviconService);
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "places-favicons-expired") {
+ resolve();
+ Services.obs.removeObserver(observer, "places-favicons-expired", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "places-favicons-expired", false);
+ faviconService.expireAllFavicons();
+ });
+}
+
+function observeFavicon(aIsPrivate, aExpectedCookie, aPageURI) {
+ let faviconReqXUL = false;
+ let faviconReqPlaces = false;
+ let attr = {};
+
+ if (aIsPrivate) {
+ attr.privateBrowsingId = 1;
+ }
+
+ let expectedPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(aPageURI, attr);
+
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ // Make sure that the topic is 'http-on-modify-request'.
+ if (aTopic === "http-on-modify-request") {
+ // We check the privateBrowsingId for the originAttributes of the loading
+ // channel. All requests for the favicon should contain the correct
+ // privateBrowsingId. There are two requests for a favicon loading, one
+ // from the Places library and one from the XUL image. The difference
+ // of them is the loading principal. The Places will use the content
+ // principal and the XUL image will use the system principal.
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let reqLoadInfo = httpChannel.loadInfo;
+ let loadingPrincipal = reqLoadInfo.loadingPrincipal;
+ let triggeringPrincipal = reqLoadInfo.triggeringPrincipal;
+
+ // Make sure this is a favicon request.
+ if (httpChannel.URI.spec !== FAVICON_URI) {
+ return;
+ }
+
+ // Check the privateBrowsingId.
+ if (aIsPrivate) {
+ is(reqLoadInfo.originAttributes.privateBrowsingId, 1, "The loadInfo has correct privateBrowsingId");
+ } else {
+ is(reqLoadInfo.originAttributes.privateBrowsingId, 0, "The loadInfo has correct privateBrowsingId");
+ }
+
+ if (loadingPrincipal.equals(systemPrincipal)) {
+ faviconReqXUL = true;
+ ok(triggeringPrincipal.equals(expectedPrincipal),
+ "The triggeringPrincipal of favicon loading from XUL should be the content principal.");
+ } else {
+ faviconReqPlaces = true;
+ ok(loadingPrincipal.equals(expectedPrincipal),
+ "The loadingPrincipal of favicon loading from Places should be the content prinicpal");
+ }
+
+ let faviconCookie = httpChannel.getRequestHeader("cookie");
+
+ is(faviconCookie, aExpectedCookie, "The cookie of the favicon loading is correct.");
+ } else {
+ ok(false, "Received unexpected topic: ", aTopic);
+ }
+
+ if (faviconReqXUL && faviconReqPlaces) {
+ resolve();
+ Services.obs.removeObserver(observer, "http-on-modify-request", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+ });
+}
+
+function waitOnFaviconResponse(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "http-on-examine-response" ||
+ aTopic === "http-on-examine-cached-response") {
+
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let loadInfo = httpChannel.loadInfo;
+
+ if (httpChannel.URI.spec !== aFaviconURL) {
+ return;
+ }
+
+ let result = {
+ topic: aTopic,
+ privateBrowsingId: loadInfo.originAttributes.privateBrowsingId
+ };
+
+ resolve(result);
+ Services.obs.removeObserver(observer, "http-on-examine-response", false);
+ Services.obs.removeObserver(observer, "http-on-examine-cached-response", false);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+ Services.obs.addObserver(observer, "http-on-examine-cached-response", false);
+ });
+}
+
+function waitOnFaviconLoaded(aFaviconURL) {
+ return new Promise(resolve => {
+ let observer = {
+ onPageChanged(uri, attr, value, id) {
+
+ if (attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
+ value === aFaviconURL) {
+ resolve();
+ PlacesUtils.history.removeObserver(observer, false);
+ }
+ },
+ };
+
+ PlacesUtils.history.addObserver(observer, false);
+ });
+}
+
+function* assignCookies(aBrowser, aURL, aCookieValue){
+ let tabInfo = yield openTab(aBrowser, aURL);
+
+ yield ContentTask.spawn(tabInfo.browser, aCookieValue, function* (value) {
+ content.document.cookie = value;
+ });
+
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+}
+
+function* openTab(aBrowser, aURL) {
+ let tab = aBrowser.addTab(aURL);
+
+ // Select tab and make sure its browser is focused.
+ aBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = aBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return {tab, browser};
+}
+
+// A clean up function to prevent affecting other tests.
+registerCleanupFunction(() => {
+ // Clear all cookies.
+ let cookieMgr = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ cookieMgr.removeAll();
+
+ // Clear all image caches and network caches.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+});
+
+add_task(function* test_favicon_privateBrowsing() {
+ // Clear all image caches before running the test.
+ clearAllImageCaches();
+
+ // Clear all favicons in Places.
+ yield clearAllPlacesFavicons();
+
+ // Create a private browsing window.
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let pageURI = makeURI(TEST_PAGE);
+
+ // Generate two random cookies for non-private window and private window
+ // respectively.
+ let cookies = [];
+ cookies.push(Math.random().toString());
+ cookies.push(Math.random().toString());
+
+ // Open a tab in private window and add a cookie into it.
+ yield assignCookies(privateWindow.gBrowser, TEST_SITE, cookies[0]);
+
+ // Open a tab in non-private window and add a cookie into it.
+ yield assignCookies(gBrowser, TEST_SITE, cookies[1]);
+
+ // Add the observer earlier in case we don't capture events in time.
+ let promiseObserveFavicon = observeFavicon(true, cookies[0], pageURI);
+
+ // Open a tab for the private window.
+ let tabInfo = yield openTab(privateWindow.gBrowser, TEST_PAGE);
+
+ // Waiting until favicon requests are all made.
+ yield promiseObserveFavicon;
+
+ // Close the tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+
+ // Add the observer earlier in case we don't capture events in time.
+ promiseObserveFavicon = observeFavicon(false, cookies[1], pageURI);
+
+ // Open a tab for the non-private window.
+ tabInfo = yield openTab(gBrowser, TEST_PAGE);
+
+ // Waiting until favicon requests are all made.
+ yield promiseObserveFavicon;
+
+ // Close the tab.
+ yield BrowserTestUtils.removeTab(tabInfo.tab);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+});
+
+add_task(function* test_favicon_cache_privateBrowsing() {
+ // Clear all image cahces and network cache before running the test.
+ clearAllImageCaches();
+
+ let networkCache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ networkCache.clear();
+
+ // Clear all favicons in Places.
+ yield clearAllPlacesFavicons();
+
+ // Add an observer for making sure the favicon has been loaded and cached.
+ let promiseFaviconLoaded = waitOnFaviconLoaded(FAVICON_CACHE_URI);
+
+ // Open a tab for the non-private window.
+ let tabInfoNonPrivate = yield openTab(gBrowser, TEST_CACHE_PAGE);
+
+ let response = yield waitOnFaviconResponse(FAVICON_CACHE_URI);
+
+ yield promiseFaviconLoaded;
+
+ // Check that the favicon response has come from the network and it has the
+ // correct privateBrowsingId.
+ is(response.topic, "http-on-examine-response", "The favicon image should be loaded through network.");
+ is(response.privateBrowsingId, 0, "We should observe the network response for the non-private tab.");
+
+ // Create a private browsing window.
+ let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ // Open a tab for the private window.
+ let tabInfoPrivate = yield openTab(privateWindow.gBrowser, TEST_CACHE_PAGE);
+
+ // Wait for the favicon response of the private tab.
+ response = yield waitOnFaviconResponse(FAVICON_CACHE_URI);
+
+ // Make sure the favicon is loaded through the network and its privateBrowsingId is correct.
+ is(response.topic, "http-on-examine-response", "The favicon image should be loaded through the network again.");
+ is(response.privateBrowsingId, 1, "We should observe the network response for the private tab.");
+
+ yield BrowserTestUtils.removeTab(tabInfoPrivate.tab);
+ yield BrowserTestUtils.removeTab(tabInfoNonPrivate.tab);
+ yield BrowserTestUtils.closeWindow(privateWindow);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.js
new file mode 100644
index 000000000..3a078ffc1
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt.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/. */
+
+// This test makes sure that the geolocation prompt does not show a remember
+// control inside the private browsing mode.
+
+add_task(function* test() {
+ const testPageURL = "http://mochi.test:8888/browser/" +
+ "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html";
+
+ function checkGeolocation(aPrivateMode, aWindow) {
+ return Task.spawn(function* () {
+ aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testPageURL);
+ yield BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser);
+
+ let notification = aWindow.PopupNotifications.getNotification("geolocation");
+
+ // Wait until the notification is available.
+ while (!notification){
+ yield new Promise(resolve => { executeSoon(resolve); });
+ let notification = aWindow.PopupNotifications.getNotification("geolocation");
+ }
+
+ if (aPrivateMode) {
+ // Make sure the notification is correctly displayed without a remember control
+ is(notification.secondaryActions.length, 0, "Secondary actions shouldn't exist (always/never remember)");
+ } else {
+ ok(notification.secondaryActions.length > 1, "Secondary actions should exist (always/never remember)");
+ }
+ notification.remove();
+
+ aWindow.gBrowser.removeCurrentTab();
+ });
+ };
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let browser = win.gBrowser.selectedBrowser;
+ browser.loadURI(testPageURL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield checkGeolocation(false, win);
+
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let privateBrowser = privateWin.gBrowser.selectedBrowser;
+ privateBrowser.loadURI(testPageURL);
+ yield BrowserTestUtils.browserLoaded(privateBrowser);
+
+ yield checkGeolocation(true, privateWin);
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(win);
+ yield BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html
new file mode 100644
index 000000000..36d5e3cec
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_geoprompt_page.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Geolocation invoker</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.geolocation.getCurrentPosition(function (pos) {
+ // ignore
+ });
+ </script>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js
new file mode 100644
index 000000000..dbe8ed060
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_lastpbcontextexited.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // We need to open a new window for this so that its docshell would get destroyed
+ // when clearing the PB mode flag.
+ function runTest(aCloseWindow, aCallback) {
+ let newWin = OpenBrowserWindow({private: true});
+ SimpleTest.waitForFocus(function() {
+ let expectedExiting = true;
+ let expectedExited = false;
+ let observerExiting = {
+ observe: function(aSubject, aTopic, aData) {
+ is(aTopic, "last-pb-context-exiting", "Correct topic should be dispatched (exiting)");
+ is(expectedExiting, true, "notification not expected yet (exiting)");
+ expectedExited = true;
+ Services.obs.removeObserver(observerExiting, "last-pb-context-exiting");
+ }
+ };
+ let observerExited = {
+ observe: function(aSubject, aTopic, aData) {
+ is(aTopic, "last-pb-context-exited", "Correct topic should be dispatched (exited)");
+ is(expectedExited, true, "notification not expected yet (exited)");
+ Services.obs.removeObserver(observerExited, "last-pb-context-exited");
+ aCallback();
+ }
+ };
+ Services.obs.addObserver(observerExiting, "last-pb-context-exiting", false);
+ Services.obs.addObserver(observerExited, "last-pb-context-exited", false);
+ expectedExiting = true;
+ aCloseWindow(newWin);
+ newWin = null;
+ SpecialPowers.forceGC();
+ }, newWin);
+ }
+
+ waitForExplicitFinish();
+
+ runTest(function(newWin) {
+ // Simulate pressing the window close button
+ newWin.document.getElementById("cmd_closeWindow").doCommand();
+ }, function () {
+ runTest(function(newWin) {
+ // Simulate closing the last tab
+ newWin.document.getElementById("cmd_close").doCommand();
+ }, finish);
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.js
new file mode 100644
index 000000000..acccb5e2d
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage.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/. */
+
+ add_task(function test() {
+ requestLongerTimeout(2);
+ const page1 = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
+ 'browser_privatebrowsing_localStorage_page1.html'
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+ let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(page1);
+ let browser = win.gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ browser.loadURI(
+ 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/' +
+ 'browser_privatebrowsing_localStorage_page2.html');
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ is(browser.contentTitle, '2', "localStorage should contain 2 items");
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(win);
+ });
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.js
new file mode 100644
index 000000000..3bcb6e5c9
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after.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/. */
+
+// Ensure that a storage instance used by both private and public sessions at different times does not
+// allow any data to leak due to cached values.
+
+// Step 1: Load browser_privatebrowsing_localStorage_before_after_page.html in a private tab, causing a storage
+// item to exist. Close the tab.
+// Step 2: Load the same page in a non-private tab, ensuring that the storage instance reports only one item
+// existing.
+
+add_task(function test() {
+ let testURI = "about:blank";
+ let prefix = 'http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/';
+
+ // Step 1.
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let privateBrowser = privateWin.gBrowser.addTab(
+ prefix + 'browser_privatebrowsing_localStorage_before_after_page.html').linkedBrowser;
+ yield BrowserTestUtils.browserLoaded(privateBrowser);
+
+ is(privateBrowser.contentTitle, '1', "localStorage should contain 1 item");
+
+ // Step 2.
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let browser = win.gBrowser.addTab(
+ prefix + 'browser_privatebrowsing_localStorage_before_after_page2.html').linkedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ is(browser.contentTitle, 'null|0', 'localStorage should contain 0 items');
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(privateWin);
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html
new file mode 100644
index 000000000..143fea4e7
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page.html
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ localStorage.clear();
+ localStorage.setItem('zzztest', 'zzzvalue');
+ document.title = localStorage.length;
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html
new file mode 100644
index 000000000..9a7e2da63
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_before_after_page2.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ document.title = localStorage.getItem('zzztest', 'zzzvalue') + '|' + localStorage.length;
+ localStorage.clear();
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html
new file mode 100644
index 000000000..3e79a01bf
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page1.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ localStorage.clear();
+ localStorage.setItem('test1', 'value1');
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html
new file mode 100644
index 000000000..8c9b28fd8
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_localStorage_page2.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+ localStorage.setItem('test2', 'value2');
+ document.title = localStorage.length;
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js
new file mode 100644
index 000000000..b09ec0368
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_newtab_from_popup.js
@@ -0,0 +1,61 @@
+/**
+ * Tests that a popup window in private browsing window opens
+ * new tab links in the original private browsing window as
+ * new tabs.
+ *
+ * This is a regression test for bug 1202634.
+ */
+
+// We're able to sidestep some quote-escaping issues when
+// nesting data URI's by encoding the second data URI in
+// base64.
+const POPUP_BODY_BASE64 = btoa(`<a href="http://example.com/" target="_blank"
+ id="second">
+ Now click this
+ </a>`);
+const POPUP_LINK = `data:text/html;charset=utf-8;base64,${POPUP_BODY_BASE64}`;
+const WINDOW_BODY = `data:text/html,
+ <a href="%23" id="first"
+ onclick="window.open('${POPUP_LINK}', '_blank',
+ 'width=630,height=500')">
+ First click this.
+ </a>`;
+
+add_task(function* test_private_popup_window_opens_private_tabs() {
+ let privWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ // Sanity check - this browser better be private.
+ ok(PrivateBrowsingUtils.isWindowPrivate(privWin),
+ "Opened a private browsing window.");
+
+ // First, open a private browsing window, and load our
+ // testing page.
+ let privBrowser = privWin.gBrowser.selectedBrowser;
+ yield BrowserTestUtils.loadURI(privBrowser, WINDOW_BODY);
+ yield BrowserTestUtils.browserLoaded(privBrowser);
+
+ // Next, click on the link in the testing page, and ensure
+ // that a private popup window is opened.
+ let openedPromise = BrowserTestUtils.waitForNewWindow(true, POPUP_LINK);
+
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#first", {}, privBrowser);
+ let popupWin = yield openedPromise;
+ ok(PrivateBrowsingUtils.isWindowPrivate(popupWin),
+ "Popup window was private.");
+
+ // Now click on the link in the popup, and ensure that a new
+ // tab is opened in the original private browsing window.
+ let newTabPromise = BrowserTestUtils.waitForNewTab(privWin.gBrowser);
+ let popupBrowser = popupWin.gBrowser.selectedBrowser;
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#second", {}, popupBrowser);
+ let newPrivTab = yield newTabPromise;
+
+ // Ensure that the newly created tab's browser is private.
+ ok(PrivateBrowsingUtils.isBrowserPrivate(newPrivTab.linkedBrowser),
+ "Newly opened tab should be private.");
+
+ // Clean up
+ yield BrowserTestUtils.removeTab(newPrivTab);
+ yield BrowserTestUtils.closeWindow(popupWin);
+ yield BrowserTestUtils.closeWindow(privWin);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js
new file mode 100644
index 000000000..ae6e8a6a3
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_noSessionRestoreMenuOption.js
@@ -0,0 +1,23 @@
+"use strict";
+
+/**
+ * Tests that if we open a tab within a private browsing window, and then
+ * close that private browsing window, that subsequent private browsing
+ * windows do not allow the command for restoring the last session.
+ */
+add_task(function* test_no_session_restore_menu_option() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ ok(true, "The first private window got loaded");
+ win.gBrowser.addTab("about:mozilla");
+ yield BrowserTestUtils.closeWindow(win);
+
+ win = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let srCommand = win.document.getElementById("Browser:RestoreLastSession");
+ ok(srCommand, "The Session Restore command should exist");
+ is(PrivateBrowsingUtils.isWindowPrivate(win), true,
+ "PrivateBrowsingUtils should report the correct per-window private browsing status");
+ is(srCommand.hasAttribute("disabled"), true,
+ "The Session Restore command should be disabled in private browsing mode");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js
new file mode 100644
index 000000000..d2a69dd4e
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_nonbrowser.js
@@ -0,0 +1,19 @@
+"use strict";
+
+/**
+ * Tests that we fire the last-pb-context-exited observer notification
+ * when the last private browsing window closes, even if a chrome window
+ * was opened from that private browsing window.
+ */
+add_task(function* () {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ let chromeWin = win.open("chrome://browser/content/places/places.xul", "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
+ yield BrowserTestUtils.waitForEvent(chromeWin, "load");
+ let obsPromise = TestUtils.topicObserved("last-pb-context-exited");
+ yield BrowserTestUtils.closeWindow(win);
+ yield obsPromise;
+ Assert.ok(true, "Got the last-pb-context-exited notification");
+ chromeWin.close();
+});
+
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.js
new file mode 100644
index 000000000..0b1369b11
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_opendir.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/. */
+
+// This test makes sure that the last open directory used inside the private
+// browsing mode is not remembered after leaving that mode.
+
+var windowsToClose = [];
+function testOnWindow(options, callback) {
+ var win = OpenBrowserWindow(options);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ windowsToClose.push(win);
+ callback(win);
+ }, false);
+}
+
+registerCleanupFunction(function() {
+ windowsToClose.forEach(function(win) {
+ win.close();
+ });
+});
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+ let ds = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ let dir1 = ds.get("ProfD", Ci.nsIFile);
+ let dir2 = ds.get("TmpD", Ci.nsIFile);
+ let file = dir2.clone();
+ file.append("pbtest.file");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ const kPrefName = "browser.open.lastDir";
+
+ function setupCleanSlate(win) {
+ win.gLastOpenDirectory.reset();
+ gPrefService.clearUserPref(kPrefName);
+ }
+
+ setupCleanSlate(window);
+
+ // open one regular and one private window
+ testOnWindow(undefined, function(nonPrivateWindow) {
+ setupCleanSlate(nonPrivateWindow);
+ testOnWindow({private: true}, function(privateWindow) {
+ setupCleanSlate(privateWindow);
+
+ // Test 1: general workflow test
+
+ // initial checks
+ ok(!nonPrivateWindow.gLastOpenDirectory.path,
+ "Last open directory path should be initially empty");
+ nonPrivateWindow.gLastOpenDirectory.path = dir2;
+ is(nonPrivateWindow.gLastOpenDirectory.path.path, dir2.path,
+ "The path should be successfully set");
+ nonPrivateWindow.gLastOpenDirectory.path = null;
+ is(nonPrivateWindow.gLastOpenDirectory.path.path, dir2.path,
+ "The path should be not change when assigning it to null");
+ nonPrivateWindow.gLastOpenDirectory.path = dir1;
+ is(nonPrivateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "The path should be successfully outside of the private browsing mode");
+
+ // test the private window
+ is(privateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "The path should not change when entering the private browsing mode");
+ privateWindow.gLastOpenDirectory.path = dir2;
+ is(privateWindow.gLastOpenDirectory.path.path, dir2.path,
+ "The path should successfully change inside the private browsing mode");
+
+ // test the non-private window
+ is(nonPrivateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "The path should be reset to the same path as before entering the private browsing mode");
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 2: the user first tries to open a file inside the private browsing mode
+
+ // test the private window
+ ok(!privateWindow.gLastOpenDirectory.path,
+ "No original path should exist inside the private browsing mode");
+ privateWindow.gLastOpenDirectory.path = dir1;
+ is(privateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "The path should be successfully set inside the private browsing mode");
+ // test the non-private window
+ ok(!nonPrivateWindow.gLastOpenDirectory.path,
+ "The path set inside the private browsing mode should not leak when leaving that mode");
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 3: the last open directory is set from a previous session, it should be used
+ // in normal mode
+
+ gPrefService.setComplexValue(kPrefName, Ci.nsILocalFile, dir1);
+ is(nonPrivateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "The pref set from last session should take effect outside the private browsing mode");
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 4: the last open directory is set from a previous session, it should be used
+ // in private browsing mode mode
+
+ gPrefService.setComplexValue(kPrefName, Ci.nsILocalFile, dir1);
+ // test the private window
+ is(privateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "The pref set from last session should take effect inside the private browsing mode");
+ // test the non-private window
+ is(nonPrivateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "The pref set from last session should remain in effect after leaving the private browsing mode");
+
+ setupCleanSlate(nonPrivateWindow);
+ setupCleanSlate(privateWindow);
+
+ // Test 5: setting the path to a file shouldn't work
+
+ nonPrivateWindow.gLastOpenDirectory.path = file;
+ ok(!nonPrivateWindow.gLastOpenDirectory.path,
+ "Setting the path to a file shouldn't work when it's originally null");
+ nonPrivateWindow.gLastOpenDirectory.path = dir1;
+ nonPrivateWindow.gLastOpenDirectory.path = file;
+ is(nonPrivateWindow.gLastOpenDirectory.path.path, dir1.path,
+ "Setting the path to a file shouldn't work when it's not originally null");
+
+ // cleanup
+ file.remove(false);
+ finish();
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html
new file mode 100644
index 000000000..f5bb3212f
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Title 1</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
new file mode 100644
index 000000000..32436b3cc
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test to make sure that the visited page titles do not get updated inside the
+// private browsing mode.
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+add_task(function* test() {
+ const TEST_URL = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.html"
+ const TEST_URI = Services.io.newURI(TEST_URL, null, null);
+ const TITLE_1 = "Title 1";
+ const TITLE_2 = "Title 2";
+
+ function waitForTitleChanged() {
+ return new Promise(resolve => {
+ let historyObserver = {
+ onTitleChanged: function(uri, pageTitle) {
+ PlacesUtils.history.removeObserver(historyObserver, false);
+ resolve({uri: uri, pageTitle: pageTitle});
+ },
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onVisit: function () {},
+ onDeleteURI: function () {},
+ onClearHistory: function () {},
+ onPageChanged: function () {},
+ onDeleteVisits: function() {},
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
+ };
+
+ PlacesUtils.history.addObserver(historyObserver, false);
+ });
+ };
+
+ yield PlacesTestUtils.clearHistory();
+
+ let tabToClose = gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
+ yield waitForTitleChanged();
+ is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_1, "The title matches the orignal title after first visit");
+
+ let place = {
+ uri: TEST_URI,
+ title: TITLE_2,
+ visits: [{
+ visitDate: Date.now() * 1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ }]
+ };
+ PlacesUtils.asyncHistory.updatePlaces(place, {
+ handleError: () => ok(false, "Unexpected error in adding visit."),
+ handleResult: function () { },
+ handleCompletion: function () {}
+ });
+
+ yield waitForTitleChanged();
+ is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title matches the updated title after updating visit");
+
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private:true});
+ yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.addTab(TEST_URL).linkedBrowser);
+
+ is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title remains the same after visiting in private window");
+ yield PlacesTestUtils.clearHistory();
+
+ // Cleanup
+ BrowserTestUtils.closeWindow(privateWin);
+ gBrowser.removeTab(tabToClose);
+});
+
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
new file mode 100644
index 000000000..a70019976
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.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/. */
+
+// This test makes sure that the title of existing history entries does not
+// change inside a private window.
+
+add_task(function* test() {
+ const TEST_URL = "http://mochi.test:8888/browser/browser/components/" +
+ "privatebrowsing/test/browser/title.sjs";
+ let cm = Services.cookies;
+
+ function cleanup() {
+ // delete all cookies
+ cm.removeAll();
+ // delete all history items
+ return PlacesTestUtils.clearHistory();
+ }
+
+ yield cleanup();
+
+ let deferredFirst = PromiseUtils.defer();
+ let deferredSecond = PromiseUtils.defer();
+ let deferredThird = PromiseUtils.defer();
+
+ let testNumber = 0;
+ let historyObserver = {
+ onTitleChanged: function(aURI, aPageTitle) {
+ if (aURI.spec != TEST_URL)
+ return;
+ switch (++testNumber) {
+ case 1:
+ // The first time that the page is loaded
+ deferredFirst.resolve(aPageTitle);
+ break;
+ case 2:
+ // The second time that the page is loaded
+ deferredSecond.resolve(aPageTitle);
+ break;
+ case 3:
+ // After clean up
+ deferredThird.resolve(aPageTitle);
+ break;
+ default:
+ // Checks that opening the page in a private window should not fire a
+ // title change.
+ ok(false, "Title changed. Unexpected pass: " + testNumber);
+ }
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onVisit: function () {},
+ onDeleteURI: function () {},
+ onClearHistory: function () {},
+ onPageChanged: function () {},
+ onDeleteVisits: function() {},
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
+ };
+ PlacesUtils.history.addObserver(historyObserver, false);
+
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
+ let aPageTitle = yield deferredFirst.promise;
+ // The first time that the page is loaded
+ is(aPageTitle, "No Cookie",
+ "The page should be loaded without any cookie for the first time");
+
+ win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
+ aPageTitle = yield deferredSecond.promise;
+ // The second time that the page is loaded
+ is(aPageTitle, "Cookie",
+ "The page should be loaded with a cookie for the second time");
+
+ yield cleanup();
+
+ win.gBrowser.selectedTab = win.gBrowser.addTab(TEST_URL);
+ aPageTitle = yield deferredThird.promise;
+ // After clean up
+ is(aPageTitle, "No Cookie",
+ "The page should be loaded without any cookie again");
+
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+
+ let private_tab = win2.gBrowser.addTab(TEST_URL);
+ win2.gBrowser.selectedTab = private_tab;
+ yield BrowserTestUtils.browserLoaded(private_tab.linkedBrowser);
+
+ // Cleanup
+ yield cleanup();
+ PlacesUtils.history.removeObserver(historyObserver);
+ yield BrowserTestUtils.closeWindow(win);
+ yield BrowserTestUtils.closeWindow(win2);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.js
new file mode 100644
index 000000000..71d85f296
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_popupblocker.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/. */
+
+// This test makes sure that private browsing mode disables the remember option
+// for the popup blocker menu.
+add_task(function* test() {
+ let testURI = "http://mochi.test:8888/browser/browser/components/privatebrowsing/test/browser/popup.html";
+ let oldPopupPolicy = gPrefService.getBoolPref("dom.disable_open_during_load");
+ gPrefService.setBoolPref("dom.disable_open_during_load", true);
+
+ registerCleanupFunction(() => {
+ gPrefService.setBoolPref("dom.disable_open_during_load", oldPopupPolicy);
+ });
+
+ function testPopupBlockerMenuItem(aExpectedDisabled, aWindow, aCallback) {
+
+ aWindow.gBrowser.addEventListener("DOMUpdatePageReport", function() {
+ aWindow.gBrowser.removeEventListener("DOMUpdatePageReport", arguments.callee, false);
+
+ executeSoon(function() {
+ let notification = aWindow.gBrowser.getNotificationBox().getNotificationWithValue("popup-blocked");
+ ok(notification, "The notification box should be displayed");
+
+ function checkMenuItem(callback) {
+ dump("CMI: in\n");
+ aWindow.document.addEventListener("popupshown", function(event) {
+ dump("CMI: popupshown\n");
+ aWindow.document.removeEventListener("popupshown", arguments.callee, false);
+
+ if (aExpectedDisabled)
+ is(aWindow.document.getElementById("blockedPopupAllowSite").getAttribute("disabled"), "true",
+ "The allow popups menu item should be disabled");
+
+ event.originalTarget.hidePopup();
+ dump("CMI: calling back\n");
+ callback();
+ dump("CMI: called back\n");
+ }, false);
+ dump("CMI: out\n");
+ }
+
+ checkMenuItem(function() {
+ aCallback();
+ });
+ notification.querySelector("button").doCommand();
+ });
+
+ }, false);
+
+ aWindow.gBrowser.selectedBrowser.loadURI(testURI);
+ }
+
+ let win1 = yield BrowserTestUtils.openNewBrowserWindow();
+ yield new Promise(resolve => waitForFocus(resolve, win1));
+ yield new Promise(resolve => testPopupBlockerMenuItem(false, win1, resolve));
+
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield new Promise(resolve => waitForFocus(resolve, win2));
+ yield new Promise(resolve => testPopupBlockerMenuItem(true, win2, resolve));
+
+ let win3 = yield BrowserTestUtils.openNewBrowserWindow();
+ yield new Promise(resolve => waitForFocus(resolve, win3));
+ yield new Promise(resolve => testPopupBlockerMenuItem(false, win3, resolve));
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(win1);
+ yield BrowserTestUtils.closeWindow(win2);
+ yield BrowserTestUtils.closeWindow(win3);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.js
new file mode 100644
index 000000000..fe69a2234
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler.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/. */
+
+// This test makes sure that the web pages can't register protocol handlers
+// inside the private browsing mode.
+
+add_task(function* test() {
+ let notificationValue = "Protocol Registration: testprotocol";
+ let testURI = "http://example.com/browser/" +
+ "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html";
+
+ let doTest = Task.async(function* (aIsPrivateMode, aWindow) {
+ let tab = aWindow.gBrowser.selectedTab = aWindow.gBrowser.addTab(testURI);
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ let promiseFinished = PromiseUtils.defer();
+ setTimeout(function() {
+ let notificationBox = aWindow.gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue(notificationValue);
+
+ if (aIsPrivateMode) {
+ // Make sure the notification is correctly displayed without a remember control
+ ok(!notification, "Notification box should not be displayed inside of private browsing mode");
+ } else {
+ // Make sure the notification is correctly displayed with a remember control
+ ok(notification, "Notification box should be displaying outside of private browsing mode");
+ }
+
+ promiseFinished.resolve();
+ }, 100); // remember control is added in a setTimeout(0) call
+
+ yield promiseFinished.promise;
+ });
+
+ // test first when not on private mode
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield doTest(false, win);
+
+ // then test when on private mode
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield doTest(true, privateWin);
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(win);
+ yield BrowserTestUtils.closeWindow(privateWin);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html
new file mode 100644
index 000000000..74f846d54
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_protocolhandler_page.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Protocol registrar page</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ navigator.registerProtocolHandler("testprotocol",
+ "https://example.com/foobar?uri=%s",
+ "Test Protocol");
+ </script>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
new file mode 100644
index 000000000..dbd74029d
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_sidebar.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure that Sidebars do not migrate across windows with
+// different privacy states
+
+// See Bug 885054: https://bugzilla.mozilla.org/show_bug.cgi?id=885054
+
+function test() {
+ waitForExplicitFinish();
+
+ let { utils: Cu } = Components;
+
+ let { Promise: { defer } } = Cu.import("resource://gre/modules/Promise.jsm", {});
+
+ // opens a sidebar
+ function openSidebar(win) {
+ let { promise, resolve } = defer();
+ let doc = win.document;
+
+ let sidebarID = 'viewBookmarksSidebar';
+
+ let sidebar = doc.getElementById('sidebar');
+
+ let sidebarurl = doc.getElementById(sidebarID).getAttribute('sidebarurl');
+
+ sidebar.addEventListener('load', function onSidebarLoad() {
+ if (sidebar.contentWindow.location.href != sidebarurl)
+ return;
+ sidebar.removeEventListener('load', onSidebarLoad, true);
+
+ resolve(win);
+ }, true);
+
+ win.SidebarUI.show(sidebarID);
+
+ return promise;
+ }
+
+ let windowCache = [];
+ function cacheWindow(w) {
+ windowCache.push(w);
+ return w;
+ }
+ function closeCachedWindows () {
+ windowCache.forEach(w => w.close());
+ }
+
+ // Part 1: NON PRIVATE WINDOW -> PRIVATE WINDOW
+ openWindow(window, {}, 1).
+ then(cacheWindow).
+ then(openSidebar).
+ then(win => openWindow(win, { private: true })).
+ then(cacheWindow).
+ then(function({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(sidebarBox.hidden, true, 'Opening a private window from reg window does not open the sidebar');
+ }).
+ // Part 2: NON PRIVATE WINDOW -> NON PRIVATE WINDOW
+ then(() => openWindow(window)).
+ then(cacheWindow).
+ then(openSidebar).
+ then(win => openWindow(win)).
+ then(cacheWindow).
+ then(function({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(sidebarBox.hidden, false, 'Opening a reg window from reg window does open the sidebar');
+ }).
+ // Part 3: PRIVATE WINDOW -> NON PRIVATE WINDOW
+ then(() => openWindow(window, { private: true })).
+ then(cacheWindow).
+ then(openSidebar).
+ then(win => openWindow(win)).
+ then(cacheWindow).
+ then(function({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(sidebarBox.hidden, true, 'Opening a reg window from a private window does not open the sidebar');
+ }).
+ // Part 4: PRIVATE WINDOW -> PRIVATE WINDOW
+ then(() => openWindow(window, { private: true })).
+ then(cacheWindow).
+ then(openSidebar).
+ then(win => openWindow(win, { private: true })).
+ then(cacheWindow).
+ then(function({ document }) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ is(sidebarBox.hidden, false, 'Opening a private window from private window does open the sidebar');
+ }).
+ then(closeCachedWindows).
+ then(finish);
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.js
new file mode 100644
index 000000000..e2b8593d6
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_theming.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/. */
+
+// This test makes sure that privatebrowsingmode attribute of the window is correctly
+// adjusted based on whether the window is a private window.
+
+var windowsToClose = [];
+function testOnWindow(options, callback) {
+ var win = OpenBrowserWindow(options);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ windowsToClose.push(win);
+ executeSoon(() => callback(win));
+ }, false);
+}
+
+registerCleanupFunction(function() {
+ windowsToClose.forEach(function(win) {
+ win.close();
+ });
+});
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+
+ ok(!document.documentElement.hasAttribute("privatebrowsingmode"),
+ "privatebrowsingmode should not be present in normal mode");
+
+ // open a private window
+ testOnWindow({private: true}, function(win) {
+ is(win.document.documentElement.getAttribute("privatebrowsingmode"), "temporary",
+ "privatebrowsingmode should be \"temporary\" inside the private browsing mode");
+
+ finish();
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
new file mode 100644
index 000000000..cbd2c60f8
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure that the gPrivateBrowsingUI object, the Private Browsing
+// menu item and its XUL <command> element work correctly.
+
+function test() {
+ // initialization
+ waitForExplicitFinish();
+ let windowsToClose = [];
+ let testURI = "about:blank";
+ let pbMenuItem;
+ let cmd;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+
+ ok(aWindow.gPrivateBrowsingUI, "The gPrivateBrowsingUI object exists");
+
+ pbMenuItem = aWindow.document.getElementById("menu_newPrivateWindow");
+ ok(pbMenuItem, "The Private Browsing menu item exists");
+
+ cmd = aWindow.document.getElementById("Tools:PrivateBrowsing");
+ isnot(cmd, null, "XUL command object for the private browsing service exists");
+
+ is(pbMenuItem.getAttribute("label"), "New Private Window",
+ "The Private Browsing menu item should read \"New Private Window\"");
+ is(PrivateBrowsingUtils.isWindowPrivate(aWindow), aIsPrivateMode,
+ "PrivateBrowsingUtils should report the correct per-window private browsing status (privateBrowsing should be " +
+ aIsPrivateMode + ")");
+
+ aCallback();
+ }, true);
+
+ aWindow.gBrowser.selectedBrowser.loadURI(testURI);
+ };
+
+ function openPrivateBrowsingModeByUI(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ aSubject.addEventListener("load", function() {
+ aSubject.removeEventListener("load", arguments.callee);
+ Services.obs.removeObserver(observer, "domwindowopened");
+ windowsToClose.push(aSubject);
+ aCallback(aSubject);
+ }, false);
+ }, "domwindowopened", false);
+
+ cmd = aWindow.document.getElementById("Tools:PrivateBrowsing");
+ var func = new Function("", cmd.getAttribute("oncommand"));
+ func.call(cmd);
+ };
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function(aWin) {
+ windowsToClose.push(aWin);
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(() => aCallback(aWin));
+ });
+ };
+
+ // this function is called after calling finish() on the test.
+ registerCleanupFunction(function() {
+ windowsToClose.forEach(function(aWin) {
+ aWin.close();
+ });
+ });
+
+ // test first when not on private mode
+ testOnWindow({}, function(aWin) {
+ doTest(false, aWin, function() {
+ // then test when on private mode, opening a new private window from the
+ // user interface.
+ openPrivateBrowsingModeByUI(aWin, function(aPrivateWin) {
+ doTest(true, aPrivateWin, finish);
+ });
+ });
+ });
+}
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.js
new file mode 100644
index 000000000..2be701bcd
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_urlbarfocus.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/. */
+
+// This test makes sure that the URL bar is focused when entering the private window.
+
+"use strict";
+Components.utils.import("resource://gre/modules/Promise.jsm", this);
+let aboutNewTabService = Components.classes["@mozilla.org/browser/aboutnewtab-service;1"]
+ .getService(Components.interfaces.nsIAboutNewTabService);
+
+function checkUrlbarFocus(win) {
+ let urlbar = win.gURLBar;
+ is(win.document.activeElement, urlbar.inputField, "URL Bar should be focused");
+ is(urlbar.value, "", "URL Bar should be empty");
+}
+
+function openNewPrivateWindow() {
+ let deferred = Promise.defer();
+ whenNewWindowLoaded({private: true}, win => {
+ executeSoon(() => deferred.resolve(win));
+ });
+ return deferred.promise;
+}
+
+add_task(function* () {
+ let win = yield openNewPrivateWindow();
+ checkUrlbarFocus(win);
+ win.close();
+});
+
+add_task(function* () {
+ aboutNewTabService.newTabURL = "about:blank";
+ registerCleanupFunction(() => {
+ aboutNewTabService.resetNewTabURL();
+ });
+
+ let win = yield openNewPrivateWindow();
+ checkUrlbarFocus(win);
+ win.close();
+
+ aboutNewTabService.resetNewTabURL();
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.js
new file mode 100644
index 000000000..aca8d0c7b
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle.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/. */
+
+// This test makes sure that the window title changes correctly while switching
+// from and to private browsing mode.
+
+add_task(function test() {
+ const testPageURL = "http://mochi.test:8888/browser/" +
+ "browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html";
+ requestLongerTimeout(2);
+
+ // initialization of expected titles
+ let test_title = "Test title";
+ let app_name = document.documentElement.getAttribute("title");
+ const isOSX = ("nsILocalFileMac" in Ci);
+ let page_with_title;
+ let page_without_title;
+ let about_pb_title;
+ let pb_page_with_title;
+ let pb_page_without_title;
+ let pb_about_pb_title;
+ if (isOSX) {
+ page_with_title = test_title;
+ page_without_title = app_name;
+ about_pb_title = "Open a private window?";
+ pb_page_with_title = test_title + " - (Private Browsing)";
+ pb_page_without_title = app_name + " - (Private Browsing)";
+ pb_about_pb_title = "Private Browsing - (Private Browsing)";
+ }
+ else {
+ page_with_title = test_title + " - " + app_name;
+ page_without_title = app_name;
+ about_pb_title = "Open a private window?" + " - " + app_name;
+ pb_page_with_title = test_title + " - " + app_name + " (Private Browsing)";
+ pb_page_without_title = app_name + " (Private Browsing)";
+ pb_about_pb_title = "Private Browsing - " + app_name + " (Private Browsing)";
+ }
+
+ function* testTabTitle(aWindow, url, insidePB, expected_title) {
+ let tab = (yield BrowserTestUtils.openNewForegroundTab(aWindow.gBrowser));
+ yield BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ yield BrowserTestUtils.waitForCondition(() => {
+ return aWindow.document.title === expected_title;
+ }, `Window title should be ${expected_title}, got ${aWindow.document.title}`);
+
+ is(aWindow.document.title, expected_title, "The window title for " + url +
+ " is correct (" + (insidePB ? "inside" : "outside") +
+ " private browsing mode)");
+
+ let win = aWindow.gBrowser.replaceTabWithWindow(tab);
+ yield BrowserTestUtils.waitForEvent(win, "load", false);
+
+ yield BrowserTestUtils.waitForCondition(() => {
+ return win.document.title === expected_title;
+ }, `Window title should be ${expected_title}, got ${aWindow.document.title}`);
+
+ is(win.document.title, expected_title, "The window title for " + url +
+ " detached tab is correct (" + (insidePB ? "inside" : "outside") +
+ " private browsing mode)");
+
+ yield Promise.all([ BrowserTestUtils.closeWindow(win),
+ BrowserTestUtils.closeWindow(aWindow) ]);
+ }
+
+ function openWin(private) {
+ return BrowserTestUtils.openNewBrowserWindow({ private });
+ }
+ yield Task.spawn(testTabTitle((yield openWin(false)), "about:blank", false, page_without_title));
+ yield Task.spawn(testTabTitle((yield openWin(false)), testPageURL, false, page_with_title));
+ yield Task.spawn(testTabTitle((yield openWin(false)), "about:privatebrowsing", false, about_pb_title));
+ yield Task.spawn(testTabTitle((yield openWin(true)), "about:blank", true, pb_page_without_title));
+ yield Task.spawn(testTabTitle((yield openWin(true)), testPageURL, true, pb_page_with_title));
+ yield Task.spawn(testTabTitle((yield openWin(true)), "about:privatebrowsing", true, pb_about_pb_title));
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html
new file mode 100644
index 000000000..760bde7d1
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_windowtitle_page.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <title>Test title</title>
+ </head>
+ <body>
+ Test page for the window title test
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.js
new file mode 100644
index 000000000..f5afcbd61
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoom.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/. */
+
+// This test makes sure that private browsing turns off doesn't cause zoom
+// settings to be reset on tab switch (bug 464962)
+
+add_task(function* test() {
+ let win = (yield BrowserTestUtils.openNewBrowserWindow({ private: true }));
+ let tabAbout = (yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:"));
+ let tabMozilla = (yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:"));
+
+ let mozillaZoom = win.ZoomManager.zoom;
+
+ // change the zoom on the mozilla page
+ win.FullZoom.enlarge();
+ // make sure the zoom level has been changed
+ isnot(win.ZoomManager.zoom, mozillaZoom, "Zoom level can be changed");
+ mozillaZoom = win.ZoomManager.zoom;
+
+ // switch to about: tab
+ yield BrowserTestUtils.switchTab(win.gBrowser, tabAbout);
+
+ // switch back to mozilla tab
+ yield BrowserTestUtils.switchTab(win.gBrowser, tabMozilla);
+
+ // make sure the zoom level has not changed
+ is(win.ZoomManager.zoom, mozillaZoom,
+ "Entering private browsing should not reset the zoom on a tab");
+
+ // cleanup
+ win.FullZoom.reset();
+ yield Promise.all([ BrowserTestUtils.removeTab(tabMozilla),
+ BrowserTestUtils.removeTab(tabAbout) ]);
+
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
new file mode 100644
index 000000000..b67bfc229
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_zoomrestore.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure that about:privatebrowsing does not appear zoomed in
+// if there is already a zoom site pref for about:blank (bug 487656).
+
+add_task(function* test() {
+ // initialization
+ let windowsToClose = [];
+ let windowsToReset = [];
+
+ function promiseLocationChange() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function onLocationChange(subj, topic, data) {
+ Services.obs.removeObserver(onLocationChange, topic);
+ resolve();
+ }, "browser-fullZoom:location-change", false);
+ });
+ }
+
+ function promiseTestReady(aIsZoomedWindow, aWindow) {
+ // Need to wait on two things, the ordering of which is not guaranteed:
+ // (1) the page load, and (2) FullZoom's update to the new page's zoom
+ // level. FullZoom broadcasts "browser-fullZoom:location-change" when its
+ // update is done. (See bug 856366 for details.)
+
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ return BrowserTestUtils.loadURI(browser, "about:blank").then(() => {
+ return Promise.all([ BrowserTestUtils.browserLoaded(browser),
+ promiseLocationChange() ]);
+ }).then(() => doTest(aIsZoomedWindow, aWindow));
+ }
+
+ function doTest(aIsZoomedWindow, aWindow) {
+ if (aIsZoomedWindow) {
+ is(aWindow.ZoomManager.zoom, 1,
+ "Zoom level for freshly loaded about:blank should be 1");
+ // change the zoom on the blank page
+ aWindow.FullZoom.enlarge();
+ isnot(aWindow.ZoomManager.zoom, 1, "Zoom level for about:blank should be changed");
+ return;
+ }
+
+ // make sure the zoom level is set to 1
+ is(aWindow.ZoomManager.zoom, 1, "Zoom level for about:privatebrowsing should be reset");
+ }
+
+ function testOnWindow(options, callback) {
+ return BrowserTestUtils.openNewBrowserWindow(options).then((win) => {
+ windowsToClose.push(win);
+ windowsToReset.push(win);
+ return win;
+ });
+ }
+
+ yield testOnWindow({}).then(win => promiseTestReady(true, win));
+ yield testOnWindow({private: true}).then(win => promiseTestReady(false, win));
+
+ // cleanup
+ windowsToReset.forEach((win) => win.FullZoom.reset());
+ yield Promise.all(windowsToClose.map(win => BrowserTestUtils.closeWindow(win)));
+});
diff --git a/browser/components/privatebrowsing/test/browser/empty_file.html b/browser/components/privatebrowsing/test/browser/empty_file.html
new file mode 100644
index 000000000..42682b474
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/empty_file.html
@@ -0,0 +1 @@
+<html><body></body></html> \ No newline at end of file
diff --git a/browser/components/privatebrowsing/test/browser/file_favicon.html b/browser/components/privatebrowsing/test/browser/file_favicon.html
new file mode 100644
index 000000000..b571134e1
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/file_favicon.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Favicon Test for originAttributes</title>
+ <link rel="icon" type="image/png" href="file_favicon.png" />
+ </head>
+ <body>
+ Favicon!!
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/components/privatebrowsing/test/browser/file_favicon.png b/browser/components/privatebrowsing/test/browser/file_favicon.png
new file mode 100644
index 000000000..5535363c9
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/file_favicon.png
Binary files differ
diff --git a/browser/components/privatebrowsing/test/browser/file_favicon.png^headers^ b/browser/components/privatebrowsing/test/browser/file_favicon.png^headers^
new file mode 100644
index 000000000..9e23c73b7
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/file_favicon.png^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/browser/components/privatebrowsing/test/browser/head.js b/browser/components/privatebrowsing/test/browser/head.js
new file mode 100644
index 000000000..c822ba8d1
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/head.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var {PromiseUtils} = Cu.import("resource://gre/modules/PromiseUtils.jsm", {});
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+function whenNewWindowLoaded(aOptions, aCallback) {
+ let win = OpenBrowserWindow(aOptions);
+ let focused = SimpleTest.promiseFocus(win);
+ let startupFinished = TestUtils.topicObserved("browser-delayed-startup-finished",
+ subject => subject == win).then(() => win);
+ Promise.all([focused, startupFinished])
+ .then(results => executeSoon(() => aCallback(results[1])));
+
+ return win;
+}
+
+function openWindow(aParent, aOptions, a3) {
+ let { Promise: { defer } } = Components.utils.import("resource://gre/modules/Promise.jsm", {});
+ let { promise, resolve } = defer();
+
+ let win = aParent.OpenBrowserWindow(aOptions);
+
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ resolve(win);
+ }, false);
+
+ return promise;
+}
+
+function newDirectory() {
+ let FileUtils =
+ Cu.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils;
+ let tmpDir = FileUtils.getDir("TmpD", [], true);
+ let dir = tmpDir.clone();
+ dir.append("testdir");
+ dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ return dir;
+}
+
+function newFileInDirectory(aDir) {
+ let FileUtils =
+ Cu.import("resource://gre/modules/FileUtils.jsm", {}).FileUtils;
+ let file = aDir.clone();
+ file.append("testfile");
+ file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_FILE);
+ return file;
+}
+
+function clearHistory() {
+ // simulate clearing the private data
+ Services.obs.notifyObservers(null, "browser:purge-session-history", "");
+}
+
+function _initTest() {
+ // Don't use about:home as the homepage for new windows
+ Services.prefs.setIntPref("browser.startup.page", 0);
+ registerCleanupFunction(() => Services.prefs.clearUserPref("browser.startup.page"));
+}
+
+_initTest();
diff --git a/browser/components/privatebrowsing/test/browser/popup.html b/browser/components/privatebrowsing/test/browser/popup.html
new file mode 100644
index 000000000..68bbbfa26
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/popup.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Page creating a popup</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ window.open("data:text/plain,test", "testwin");
+ </script>
+ </body>
+</html>
diff --git a/browser/components/privatebrowsing/test/browser/title.sjs b/browser/components/privatebrowsing/test/browser/title.sjs
new file mode 100644
index 000000000..568e235be
--- /dev/null
+++ b/browser/components/privatebrowsing/test/browser/title.sjs
@@ -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 provides the tests with a page with different titles based on whether
+// a cookie is present or not.
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html", false);
+
+ var cookie = "name=value";
+ var title = "No Cookie";
+ if (request.hasHeader("Cookie") && request.getHeader("Cookie") == cookie)
+ title = "Cookie";
+ else
+ response.setHeader("Set-Cookie", cookie, false);
+
+ response.write("<html><head><title>");
+ response.write(title);
+ response.write("</title><body>test page</body></html>");
+}
diff --git a/browser/components/safebrowsing/content/test/.eslintrc.js b/browser/components/safebrowsing/content/test/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/safebrowsing/content/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/safebrowsing/content/test/browser.ini b/browser/components/safebrowsing/content/test/browser.ini
new file mode 100644
index 000000000..1ce19118e
--- /dev/null
+++ b/browser/components/safebrowsing/content/test/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_bug400731.js]
+[browser_bug415846.js]
+# Disabled on Mac because of its bizarre special-and-unique snowflake of a help menu.
+skip-if = os == "mac" || e10s # e10s: Bug 1248632
+[browser_whitelisted.js]
diff --git a/browser/components/safebrowsing/content/test/browser_bug400731.js b/browser/components/safebrowsing/content/test/browser_bug400731.js
new file mode 100644
index 000000000..fac187753
--- /dev/null
+++ b/browser/components/safebrowsing/content/test/browser_bug400731.js
@@ -0,0 +1,58 @@
+/* Check presence of the "Ignore this warning" button */
+
+function onDOMContentLoaded(callback) {
+ function complete({ data }) {
+ mm.removeMessageListener("Test:DOMContentLoaded", complete);
+ callback(data);
+ }
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.addMessageListener("Test:DOMContentLoaded", complete);
+
+ function contentScript() {
+ let listener = function () {
+ removeEventListener("DOMContentLoaded", listener);
+
+ let button = content.document.getElementById("ignoreWarningButton");
+
+ sendAsyncMessage("Test:DOMContentLoaded", { buttonPresent: !!button });
+ };
+ addEventListener("DOMContentLoaded", listener);
+ }
+ mm.loadFrameScript("data:,(" + contentScript.toString() + ")();", true);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab("http://www.itisatrap.org/firefox/its-an-attack.html");
+ onDOMContentLoaded(testMalware);
+}
+
+function testMalware(data) {
+ ok(data.buttonPresent, "Ignore warning button should be present for malware");
+
+ Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", false);
+
+ // Now launch the unwanted software test
+ onDOMContentLoaded(testUnwanted);
+ gBrowser.loadURI("http://www.itisatrap.org/firefox/unwanted.html");
+}
+
+function testUnwanted(data) {
+ // Confirm that "Ignore this warning" is visible - bug 422410
+ ok(!data.buttonPresent, "Ignore warning button should be missing for unwanted software");
+
+ Services.prefs.setBoolPref("browser.safebrowsing.allowOverride", true);
+
+ // Now launch the phishing test
+ onDOMContentLoaded(testPhishing);
+ gBrowser.loadURI("http://www.itisatrap.org/firefox/its-a-trap.html");
+}
+
+function testPhishing(data) {
+ ok(data.buttonPresent, "Ignore warning button should be present for phishing");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/components/safebrowsing/content/test/browser_bug415846.js b/browser/components/safebrowsing/content/test/browser_bug415846.js
new file mode 100644
index 000000000..fc2e3472f
--- /dev/null
+++ b/browser/components/safebrowsing/content/test/browser_bug415846.js
@@ -0,0 +1,86 @@
+/* Check for the correct behaviour of the report web forgery/not a web forgery
+menu items.
+
+Mac makes this astonishingly painful to test since their help menu is special magic,
+but we can at least test it on the other platforms.*/
+
+const NORMAL_PAGE = "http://example.com";
+const PHISH_PAGE = "http://www.itisatrap.org/firefox/its-a-trap.html";
+
+/**
+ * Opens a new tab and browses to some URL, tests for the existence
+ * of the phishing menu items, and then runs a test function to check
+ * the state of the menu once opened. This function will take care of
+ * opening and closing the menu.
+ *
+ * @param url (string)
+ * The URL to browse the tab to.
+ * @param testFn (function)
+ * The function to run once the menu has been opened. This
+ * function will be passed the "reportMenu" and "errorMenu"
+ * DOM nodes as arguments, in that order. This function
+ * should not yield anything.
+ * @returns Promise
+ */
+function check_menu_at_page(url, testFn) {
+ return BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank",
+ }, function*(browser) {
+ // We don't get load events when the DocShell redirects to error
+ // pages, but we do get DOMContentLoaded, so we'll wait for that.
+ let dclPromise = ContentTask.spawn(browser, null, function*() {
+ yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", false);
+ });
+ browser.loadURI(url);
+ yield dclPromise;
+
+ let menu = document.getElementById("menu_HelpPopup");
+ ok(menu, "Help menu should exist");
+
+ let reportMenu =
+ document.getElementById("menu_HelpPopup_reportPhishingtoolmenu");
+ ok(reportMenu, "Report phishing menu item should exist");
+
+ let errorMenu =
+ document.getElementById("menu_HelpPopup_reportPhishingErrortoolmenu");
+ ok(errorMenu, "Report phishing error menu item should exist");
+
+ let menuOpen = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ menu.openPopup(null, "", 0, 0, false, null);
+ yield menuOpen;
+
+ testFn(reportMenu, errorMenu);
+
+ let menuClose = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.hidePopup();
+ yield menuClose;
+ });
+}
+
+/**
+ * Tests that we show the "Report this page" menu item at a normal
+ * page.
+ */
+add_task(function*() {
+ yield check_menu_at_page(NORMAL_PAGE, (reportMenu, errorMenu) => {
+ ok(!reportMenu.hidden,
+ "Report phishing menu should be visible on normal sites");
+ ok(errorMenu.hidden,
+ "Report error menu item should be hidden on normal sites");
+ });
+});
+
+/**
+ * Tests that we show the "Report this page is okay" menu item at
+ * a reported attack site.
+ */
+add_task(function*() {
+ yield check_menu_at_page(PHISH_PAGE, (reportMenu, errorMenu) => {
+ ok(reportMenu.hidden,
+ "Report phishing menu should be hidden on phishing sites");
+ ok(!errorMenu.hidden,
+ "Report error menu item should be visible on phishing sites");
+ });
+});
+
diff --git a/browser/components/safebrowsing/content/test/browser_whitelisted.js b/browser/components/safebrowsing/content/test/browser_whitelisted.js
new file mode 100644
index 000000000..afb647a81
--- /dev/null
+++ b/browser/components/safebrowsing/content/test/browser_whitelisted.js
@@ -0,0 +1,41 @@
+/* Ensure that hostnames in the whitelisted pref are not blocked. */
+
+const PREF_WHITELISTED_HOSTNAMES = "urlclassifier.skipHostnames";
+const TEST_PAGE = "http://www.itisatrap.org/firefox/its-an-attack.html";
+var tabbrowser = null;
+
+registerCleanupFunction(function() {
+ tabbrowser = null;
+ Services.prefs.clearUserPref(PREF_WHITELISTED_HOSTNAMES);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function testBlockedPage(window) {
+ info("Non-whitelisted pages must be blocked");
+ ok(true, "about:blocked was shown");
+}
+
+function testWhitelistedPage(window) {
+ info("Whitelisted pages must be skipped");
+ var getmeout_button = window.document.getElementById("getMeOutButton");
+ var ignorewarning_button = window.document.getElementById("ignoreWarningButton");
+ ok(!getmeout_button, "GetMeOut button not present");
+ ok(!ignorewarning_button, "IgnoreWarning button not present");
+}
+
+add_task(function* testNormalBrowsing() {
+ tabbrowser = gBrowser;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+ info("Load a test page that's whitelisted");
+ Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, "example.com,www.ItIsaTrap.org,example.net");
+ yield promiseTabLoadEvent(tab, TEST_PAGE, "load");
+ testWhitelistedPage(tab.ownerGlobal);
+
+ info("Load a test page that's no longer whitelisted");
+ Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, "");
+ yield promiseTabLoadEvent(tab, TEST_PAGE, "AboutBlockedLoaded");
+ testBlockedPage(tab.ownerGlobal);
+});
diff --git a/browser/components/safebrowsing/content/test/head.js b/browser/components/safebrowsing/content/test/head.js
new file mode 100644
index 000000000..90eef0a3f
--- /dev/null
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -0,0 +1,55 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @param [optional] event
+ * The load event type to wait for. Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url, eventType="load")
+{
+ info(`Wait tab event: ${eventType}`);
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded;
+ if (eventType === "load") {
+ loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+ } else {
+ // No need to use handle.
+ loaded =
+ BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, eventType,
+ true, undefined, true);
+ }
+
+ if (url)
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+
+ return loaded;
+}
+
+Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
+Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
+Services.prefs.setCharPref("urlclassifier.blockedTable", "test-block-simple");
+SafeBrowsing.init();
diff --git a/browser/components/search/content/search.xml b/browser/components/search/content/search.xml
new file mode 100644
index 000000000..5c67bc649
--- /dev/null
+++ b/browser/components/search/content/search.xml
@@ -0,0 +1,2090 @@
+<?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 % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd" >
+%searchBarDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<bindings id="SearchBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="searchbar">
+ <resources>
+ <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
+ <stylesheet src="chrome://browser/skin/searchbar.css"/>
+ </resources>
+ <content>
+ <xul:stringbundle src="chrome://browser/locale/search.properties"
+ anonid="searchbar-stringbundle"/>
+ <!--
+ There is a dependency between "maxrows" attribute and
+ "SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing
+ one of them requires changing the other one.
+ -->
+ <xul:textbox class="searchbar-textbox"
+ anonid="searchbar-textbox"
+ type="autocomplete"
+ inputtype="search"
+ flex="1"
+ autocompletepopup="PopupSearchAutoComplete"
+ autocompletesearch="search-autocomplete"
+ autocompletesearchparam="searchbar-history"
+ maxrows="10"
+ completeselectedindex="true"
+ minresultsforpopup="0"
+ xbl:inherits="disabled,disableautocomplete,searchengine,src,newlines">
+ <!--
+ Empty <box> to properly position the icon within the autocomplete
+ binding's anonymous children (the autocomplete binding positions <box>
+ children differently)
+ -->
+ <xul:box>
+ <xul:hbox class="searchbar-search-button-container">
+ <xul:image class="searchbar-search-button"
+ anonid="searchbar-search-button"
+ xbl:inherits="addengines"
+ tooltiptext="&searchEndCap.label;"/>
+ </xul:hbox>
+ </xul:box>
+ <xul:hbox class="search-go-container">
+ <xul:image class="search-go-button" hidden="true"
+ anonid="search-go-button"
+ onclick="handleSearchCommand(event);"
+ tooltiptext="&searchEndCap.label;"/>
+ </xul:hbox>
+ </xul:textbox>
+ </content>
+
+ <implementation implements="nsIObserver">
+ <constructor><![CDATA[
+ if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
+ return;
+ // Make sure we rebuild the popup in onpopupshowing
+ this._needToBuildPopup = true;
+
+ Services.obs.addObserver(this, "browser-search-engine-modified", false);
+
+ this._initialized = true;
+
+ Services.search.init((function search_init_cb(aStatus) {
+ // Bail out if the binding's been destroyed
+ if (!this._initialized)
+ return;
+
+ if (Components.isSuccessCode(aStatus)) {
+ // Refresh the display (updating icon, etc)
+ this.updateDisplay();
+ BrowserSearch.updateOpenSearchBadge();
+ } else {
+ Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
+ }
+ }).bind(this));
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this.destroy();
+ ]]></destructor>
+
+ <method name="destroy">
+ <body><![CDATA[
+ if (this._initialized) {
+ this._initialized = false;
+
+ Services.obs.removeObserver(this, "browser-search-engine-modified");
+ }
+
+ // Make sure to break the cycle from _textbox to us. Otherwise we leak
+ // the world. But make sure it's actually pointing to us.
+ // Also make sure the textbox has ever been constructed, otherwise the
+ // _textbox getter will cause the textbox constructor to run, add an
+ // observer, and leak the world too.
+ if (this._textboxInitialized && this._textbox.mController.input == this)
+ this._textbox.mController.input = null;
+ ]]></body>
+ </method>
+
+ <field name="_ignoreFocus">false</field>
+ <field name="_clickClosedPopup">false</field>
+ <field name="_stringBundle">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-stringbundle");</field>
+ <field name="_textboxInitialized">false</field>
+ <field name="_textbox">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-textbox");</field>
+ <field name="_engines">null</field>
+ <field name="FormHistory" readonly="true">
+ (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
+ </field>
+
+ <property name="engines" readonly="true">
+ <getter><![CDATA[
+ if (!this._engines)
+ this._engines = Services.search.getVisibleEngines();
+ return this._engines;
+ ]]></getter>
+ </property>
+
+ <property name="currentEngine">
+ <setter><![CDATA[
+ Services.search.currentEngine = val;
+ return val;
+ ]]></setter>
+ <getter><![CDATA[
+ var currentEngine = Services.search.currentEngine;
+ // Return a dummy engine if there is no currentEngine
+ return currentEngine || {name: "", uri: null};
+ ]]></getter>
+ </property>
+
+ <!-- textbox is used by sanitize.js to clear the undo history when
+ clearing form information. -->
+ <property name="textbox" readonly="true"
+ onget="return this._textbox;"/>
+
+ <property name="value" onget="return this._textbox.value;"
+ onset="return this._textbox.value = val;"/>
+
+ <method name="focus">
+ <body><![CDATA[
+ this._textbox.focus();
+ ]]></body>
+ </method>
+
+ <method name="select">
+ <body><![CDATA[
+ this._textbox.select();
+ ]]></body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aEngine"/>
+ <parameter name="aTopic"/>
+ <parameter name="aVerb"/>
+ <body><![CDATA[
+ if (aTopic == "browser-search-engine-modified") {
+ switch (aVerb) {
+ case "engine-removed":
+ this.offerNewEngine(aEngine);
+ break;
+ case "engine-added":
+ this.hideNewEngine(aEngine);
+ break;
+ case "engine-changed":
+ // An engine was removed (or hidden) or added, or an icon was
+ // changed. Do nothing special.
+ }
+
+ // Make sure the engine list is refetched next time it's needed
+ this._engines = null;
+
+ // Update the popup header and update the display after any modification.
+ this._textbox.popup.updateHeader();
+ this.updateDisplay();
+ }
+ ]]></body>
+ </method>
+
+ <!-- There are two seaprate lists of search engines, whose uses intersect
+ in this file. The search service (nsIBrowserSearchService and
+ nsSearchService.js) maintains a list of Engine objects which is used to
+ populate the searchbox list of available engines and to perform queries.
+ That list is accessed here via this.SearchService, and it's that sort of
+ Engine that is passed to this binding's observer as aEngine.
+
+ In addition, browser.js fills two lists of autodetected search engines
+ (browser.engines and browser.hiddenEngines) as properties of
+ mCurrentBrowser. Those lists contain unnamed JS objects of the form
+ { uri:, title:, icon: }, and that's what the searchbar uses to determine
+ whether to show any "Add <EngineName>" menu items in the drop-down.
+
+ The two types of engines are currently related by their identifying
+ titles (the Engine object's 'name'), although that may change; see bug
+ 335102. -->
+
+ <!-- If the engine that was just removed from the searchbox list was
+ autodetected on this page, move it to each browser's active list so it
+ will be offered to be added again. -->
+ <method name="offerNewEngine">
+ <parameter name="aEngine"/>
+ <body><![CDATA[
+ for (let browser of gBrowser.browsers) {
+ if (browser.hiddenEngines) {
+ // XXX This will need to be changed when engines are identified by
+ // URL rather than title; see bug 335102.
+ var removeTitle = aEngine.wrappedJSObject.name;
+ for (var i = 0; i < browser.hiddenEngines.length; i++) {
+ if (browser.hiddenEngines[i].title == removeTitle) {
+ if (!browser.engines)
+ browser.engines = [];
+ browser.engines.push(browser.hiddenEngines[i]);
+ browser.hiddenEngines.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+ BrowserSearch.updateOpenSearchBadge();
+ ]]></body>
+ </method>
+
+ <!-- If the engine that was just added to the searchbox list was
+ autodetected on this page, move it to each browser's hidden list so it is
+ no longer offered to be added. -->
+ <method name="hideNewEngine">
+ <parameter name="aEngine"/>
+ <body><![CDATA[
+ for (let browser of gBrowser.browsers) {
+ if (browser.engines) {
+ // XXX This will need to be changed when engines are identified by
+ // URL rather than title; see bug 335102.
+ var removeTitle = aEngine.wrappedJSObject.name;
+ for (var i = 0; i < browser.engines.length; i++) {
+ if (browser.engines[i].title == removeTitle) {
+ if (!browser.hiddenEngines)
+ browser.hiddenEngines = [];
+ browser.hiddenEngines.push(browser.engines[i]);
+ browser.engines.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+ BrowserSearch.updateOpenSearchBadge();
+ ]]></body>
+ </method>
+
+ <method name="setIcon">
+ <parameter name="element"/>
+ <parameter name="uri"/>
+ <body><![CDATA[
+ element.setAttribute("src", uri);
+ ]]></body>
+ </method>
+
+ <method name="updateDisplay">
+ <body><![CDATA[
+ var uri = this.currentEngine.iconURI;
+ this.setIcon(this, uri ? uri.spec : "");
+
+ var name = this.currentEngine.name;
+ var text = this._stringBundle.getFormattedString("searchtip", [name]);
+
+ this._textbox.placeholder = this._stringBundle.getString("searchPlaceholder");
+ this._textbox.label = text;
+ this._textbox.tooltipText = text;
+ ]]></body>
+ </method>
+
+ <method name="updateGoButtonVisibility">
+ <body><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "search-go-button")
+ .hidden = !this._textbox.value;
+ ]]></body>
+ </method>
+
+ <method name="openSuggestionsPanel">
+ <parameter name="aShowOnlySettingsIfEmpty"/>
+ <body><![CDATA[
+ if (this._textbox.open)
+ return;
+
+ this._textbox.showHistoryPopup();
+
+ if (this._textbox.value) {
+ // showHistoryPopup does a startSearch("") call, ensure the
+ // controller handles the text from the input box instead:
+ this._textbox.mController.handleText();
+ }
+ else if (aShowOnlySettingsIfEmpty) {
+ this.setAttribute("showonlysettings", "true");
+ }
+ ]]></body>
+ </method>
+
+ <method name="selectEngine">
+ <parameter name="aEvent"/>
+ <parameter name="isNextEngine"/>
+ <body><![CDATA[
+ // Find the new index
+ var newIndex = this.engines.indexOf(this.currentEngine);
+ newIndex += isNextEngine ? 1 : -1;
+
+ if (newIndex >= 0 && newIndex < this.engines.length) {
+ this.currentEngine = this.engines[newIndex];
+ }
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+
+ this.openSuggestionsPanel();
+ ]]></body>
+ </method>
+
+ <method name="handleSearchCommand">
+ <parameter name="aEvent"/>
+ <parameter name="aEngine"/>
+ <parameter name="aForceNewTab"/>
+ <body><![CDATA[
+ var where = "current";
+ let params;
+
+ // Open ctrl/cmd clicks on one-off buttons in a new background tab.
+ if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") {
+ if (aEvent.button == 2)
+ return;
+ where = whereToOpenLink(aEvent, false, true);
+ }
+ else if (aForceNewTab) {
+ where = "tab";
+ if (Services.prefs.getBoolPref("browser.tabs.loadInBackground"))
+ where += "-background";
+ }
+ else {
+ var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
+ if (((aEvent instanceof KeyboardEvent) && aEvent.altKey) ^ newTabPref)
+ where = "tab";
+ if ((aEvent instanceof MouseEvent) &&
+ (aEvent.button == 1 || aEvent.getModifierState("Accel"))) {
+ where = "tab";
+ params = {
+ inBackground: true,
+ };
+ }
+ }
+
+ this.handleSearchCommandWhere(aEvent, aEngine, where, params);
+ ]]></body>
+ </method>
+
+ <method name="handleSearchCommandWhere">
+ <parameter name="aEvent"/>
+ <parameter name="aEngine"/>
+ <parameter name="aWhere"/>
+ <parameter name="aParams"/>
+ <body><![CDATA[
+ var textBox = this._textbox;
+ var textValue = textBox.value;
+
+ let selection = this.telemetrySearchDetails;
+ let oneOffRecorded = false;
+
+ if (!selection || (selection.index == -1)) {
+ oneOffRecorded = this.textbox.popup.oneOffButtons
+ .maybeRecordTelemetry(aEvent, aWhere, aParams);
+ if (!oneOffRecorded) {
+ let source = "unknown";
+ let type = "unknown";
+ let target = aEvent.originalTarget;
+ if (aEvent instanceof KeyboardEvent) {
+ type = "key";
+ } else if (aEvent instanceof MouseEvent) {
+ type = "mouse";
+ if (target.classList.contains("search-panel-header") ||
+ target.parentNode.classList.contains("search-panel-header")) {
+ source = "header";
+ }
+ } else if (aEvent instanceof XULCommandEvent) {
+ if (target.getAttribute("anonid") == "paste-and-search") {
+ source = "paste";
+ }
+ }
+ if (!aEngine) {
+ aEngine = this.currentEngine;
+ }
+ BrowserSearch.recordOneoffSearchInTelemetry(aEngine, source, type,
+ aWhere);
+ }
+ }
+
+ // This is a one-off search only if oneOffRecorded is true.
+ this.doSearch(textValue, aWhere, aEngine, aParams, oneOffRecorded);
+
+ if (aWhere == "tab" && aParams && aParams.inBackground)
+ this.focus();
+ ]]></body>
+ </method>
+
+ <method name="doSearch">
+ <parameter name="aData"/>
+ <parameter name="aWhere"/>
+ <parameter name="aEngine"/>
+ <parameter name="aParams"/>
+ <parameter name="aOneOff"/>
+ <body><![CDATA[
+ var textBox = this._textbox;
+
+ // Save the current value in the form history
+ if (aData && !PrivateBrowsingUtils.isWindowPrivate(window) && this.FormHistory.enabled) {
+ this.FormHistory.update(
+ { op : "bump",
+ fieldname : textBox.getAttribute("autocompletesearchparam"),
+ value : aData },
+ { handleError : function(aError) {
+ Components.utils.reportError("Saving search to form history failed: " + aError.message);
+ }});
+ }
+
+ let engine = aEngine || this.currentEngine;
+ var submission = engine.getSubmission(aData, null, "searchbar");
+ let telemetrySearchDetails = this.telemetrySearchDetails;
+ this.telemetrySearchDetails = null;
+ if (telemetrySearchDetails && telemetrySearchDetails.index == -1) {
+ telemetrySearchDetails = null;
+ }
+ // If we hit here, we come either from a one-off, a plain search or a suggestion.
+ const details = {
+ isOneOff: aOneOff,
+ isSuggestion: (!aOneOff && telemetrySearchDetails),
+ selection: telemetrySearchDetails
+ };
+ BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details);
+ // null parameter below specifies HTML response for search
+ let params = {
+ postData: submission.postData,
+ };
+ if (aParams) {
+ for (let key in aParams) {
+ params[key] = aParams[key];
+ }
+ }
+ openUILinkIn(submission.uri.spec, aWhere, params);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command"><![CDATA[
+ const target = event.originalTarget;
+ if (target.engine) {
+ this.currentEngine = target.engine;
+ } else if (target.classList.contains("addengine-item")) {
+ // Select the installed engine if the installation succeeds
+ var installCallback = {
+ onSuccess: engine => this.currentEngine = engine
+ }
+ Services.search.addEngine(target.getAttribute("uri"), null,
+ target.getAttribute("src"), false,
+ installCallback);
+ }
+ else
+ return;
+
+ this.focus();
+ this.select();
+ ]]></handler>
+
+ <handler event="DOMMouseScroll"
+ phase="capturing"
+ modifiers="accel"
+ action="this.selectEngine(event, (event.detail > 0));"/>
+
+ <handler event="input" action="this.updateGoButtonVisibility();"/>
+ <handler event="drop" action="this.updateGoButtonVisibility();"/>
+
+ <handler event="blur">
+ <![CDATA[
+ // If the input field is still focused then a different window has
+ // received focus, ignore the next focus event.
+ this._ignoreFocus = (document.activeElement == this._textbox.inputField);
+ ]]></handler>
+
+ <handler event="focus">
+ <![CDATA[
+ // Speculatively connect to the current engine's search URI (and
+ // suggest URI, if different) to reduce request latency
+ this.currentEngine.speculativeConnect({window: window});
+
+ if (this._ignoreFocus) {
+ // This window has been re-focused, don't show the suggestions
+ this._ignoreFocus = false;
+ return;
+ }
+
+ // Don't open the suggestions if there is no text in the textbox.
+ if (!this._textbox.value)
+ return;
+
+ // Don't open the suggestions if the mouse was used to focus the
+ // textbox, that will be taken care of in the click handler.
+ if (Services.focus.getLastFocusMethod(window) & Services.focus.FLAG_BYMOUSE)
+ return;
+
+ this.openSuggestionsPanel();
+ ]]></handler>
+
+ <handler event="mousedown" phase="capturing">
+ <![CDATA[
+ if (event.originalTarget.getAttribute("anonid") == "searchbar-search-button") {
+ this._clickClosedPopup = this._textbox.popup._isHiding;
+ }
+ ]]></handler>
+
+ <handler event="click" button="0">
+ <![CDATA[
+ // Ignore clicks on the search go button.
+ if (event.originalTarget.getAttribute("anonid") == "search-go-button") {
+ return;
+ }
+
+ let isIconClick = event.originalTarget.getAttribute("anonid") == "searchbar-search-button";
+
+ // Ignore clicks on the icon if they were made to close the popup
+ if (isIconClick && this._clickClosedPopup) {
+ return;
+ }
+
+ // Open the suggestions whenever clicking on the search icon or if there
+ // is text in the textbox.
+ if (isIconClick || this._textbox.value) {
+ this.openSuggestionsPanel(true);
+ }
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="searchbar-textbox"
+ extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+ <implementation implements="nsIObserver">
+ <constructor><![CDATA[
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ if (document.getBindingParent(this).parentNode.parentNode.localName ==
+ "toolbarpaletteitem")
+ return;
+
+ // Initialize fields
+ this._stringBundle = document.getBindingParent(this)._stringBundle;
+ this._suggestEnabled =
+ Services.prefs.getBoolPref("browser.search.suggest.enabled");
+
+ if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll"))
+ this.setAttribute("clickSelectsAll", true);
+
+ // Add items to context menu and attach controller to handle them
+ var textBox = document.getAnonymousElementByAttribute(this,
+ "anonid", "textbox-input-box");
+ var cxmenu = document.getAnonymousElementByAttribute(textBox,
+ "anonid", "input-box-contextmenu");
+ var pasteAndSearch;
+ cxmenu.addEventListener("popupshowing", function() {
+ BrowserSearch.searchBar._textbox.closePopup();
+ if (!pasteAndSearch)
+ return;
+ var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+ var enabled = controller.isCommandEnabled("cmd_paste");
+ if (enabled)
+ pasteAndSearch.removeAttribute("disabled");
+ else
+ pasteAndSearch.setAttribute("disabled", "true");
+ }, false);
+
+ var element, label, akey;
+
+ element = document.createElementNS(kXULNS, "menuseparator");
+ cxmenu.appendChild(element);
+
+ this.setAttribute("aria-owns", this.popup.id);
+
+ var insertLocation = cxmenu.firstChild;
+ while (insertLocation.nextSibling &&
+ insertLocation.getAttribute("cmd") != "cmd_paste")
+ insertLocation = insertLocation.nextSibling;
+ if (insertLocation) {
+ element = document.createElementNS(kXULNS, "menuitem");
+ label = this._stringBundle.getString("cmd_pasteAndSearch");
+ element.setAttribute("label", label);
+ element.setAttribute("anonid", "paste-and-search");
+ element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
+ cxmenu.insertBefore(element, insertLocation.nextSibling);
+ pasteAndSearch = element;
+ }
+
+ element = document.createElementNS(kXULNS, "menuitem");
+ label = this._stringBundle.getString("cmd_clearHistory");
+ akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
+ element.setAttribute("label", label);
+ element.setAttribute("accesskey", akey);
+ element.setAttribute("cmd", "cmd_clearhistory");
+ cxmenu.appendChild(element);
+
+ element = document.createElementNS(kXULNS, "menuitem");
+ label = this._stringBundle.getString("cmd_showSuggestions");
+ akey = this._stringBundle.getString("cmd_showSuggestions_accesskey");
+ element.setAttribute("anonid", "toggle-suggest-item");
+ element.setAttribute("label", label);
+ element.setAttribute("accesskey", akey);
+ element.setAttribute("cmd", "cmd_togglesuggest");
+ element.setAttribute("type", "checkbox");
+ element.setAttribute("checked", this._suggestEnabled);
+ element.setAttribute("autocheck", "false");
+ this._suggestMenuItem = element;
+ cxmenu.appendChild(element);
+
+ this.addEventListener("keypress", aEvent => {
+ if (navigator.platform.startsWith("Mac") && aEvent.keyCode == KeyEvent.VK_F4)
+ this.openSearch()
+ }, true);
+
+ this.controllers.appendController(this.searchbarController);
+ document.getBindingParent(this)._textboxInitialized = true;
+
+ // Add observer for suggest preference
+ Services.prefs.addObserver("browser.search.suggest.enabled", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ Services.prefs.removeObserver("browser.search.suggest.enabled", this);
+
+ // Because XBL and the customize toolbar code interacts poorly,
+ // there may not be anything to remove here
+ try {
+ this.controllers.removeController(this.searchbarController);
+ } catch (ex) { }
+ ]]></destructor>
+
+ <field name="_stringBundle"/>
+ <field name="_suggestMenuItem"/>
+ <field name="_suggestEnabled"/>
+
+ <!--
+ This overrides the searchParam property in autocomplete.xml. We're
+ hijacking this property as a vehicle for delivering the privacy
+ information about the window into the guts of nsSearchSuggestions.
+
+ Note that the setter is the same as the parent. We were not sure whether
+ we can override just the getter. If that proves to be the case, the setter
+ can be removed.
+ -->
+ <property name="searchParam"
+ onget="return this.getAttribute('autocompletesearchparam') +
+ (PrivateBrowsingUtils.isWindowPrivate(window) ? '|private' : '');"
+ onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
+
+ <!-- This is implemented so that when textbox.value is set directly (e.g.,
+ by tests), the one-off query is updated. -->
+ <method name="onBeforeValueSet">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this.popup.oneOffButtons.query = aValue;
+ return aValue;
+ ]]></body>
+ </method>
+
+ <!--
+ This method overrides the autocomplete binding's openPopup (essentially
+ duplicating the logic from the autocomplete popup binding's
+ openAutocompletePopup method), modifying it so that the popup is aligned with
+ the inner textbox, but sized to not extend beyond the search bar border.
+ -->
+ <method name="openPopup">
+ <body><![CDATA[
+ var popup = this.popup;
+ if (!popup.mPopupOpen) {
+ // Initially the panel used for the searchbar (PopupSearchAutoComplete
+ // in browser.xul) is hidden to avoid impacting startup / new
+ // window performance. The base binding's openPopup would normally
+ // call the overriden openAutocompletePopup in urlbarBindings.xml's
+ // browser-autocomplete-result-popup binding to unhide the popup,
+ // but since we're overriding openPopup we need to unhide the panel
+ // ourselves.
+ popup.hidden = false;
+
+ // Don't roll up on mouse click in the anchor for the search UI.
+ if (popup.id == "PopupSearchAutoComplete") {
+ popup.setAttribute("norolluponanchor", "true");
+ }
+
+ popup.mInput = this;
+ popup.view = this.controller.QueryInterface(Ci.nsITreeView);
+ popup.invalidate();
+
+ popup.showCommentColumn = this.showCommentColumn;
+ popup.showImageColumn = this.showImageColumn;
+
+ document.popupNode = null;
+
+ const isRTL = getComputedStyle(this, "").direction == "rtl";
+
+ var outerRect = this.getBoundingClientRect();
+ var innerRect = this.inputField.getBoundingClientRect();
+ let width = isRTL ?
+ innerRect.right - outerRect.left :
+ outerRect.right - innerRect.left;
+ popup.setAttribute("width", width > 100 ? width : 100);
+
+ var yOffset = outerRect.bottom - innerRect.bottom;
+ popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aTopic == "nsPref:changed") {
+ this._suggestEnabled =
+ Services.prefs.getBoolPref("browser.search.suggest.enabled");
+ this._suggestMenuItem.setAttribute("checked", this._suggestEnabled);
+ }
+ ]]></body>
+ </method>
+
+ <method name="openSearch">
+ <body>
+ <![CDATA[
+ if (!this.popupOpen) {
+ document.getBindingParent(this).openSuggestionsPanel();
+ return false;
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <!-- override |onTextEntered| in autocomplete.xml -->
+ <method name="onTextEntered">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let engine;
+ let oneOff = this.selectedButton;
+ if (oneOff) {
+ if (!oneOff.engine) {
+ oneOff.doCommand();
+ return;
+ }
+ engine = oneOff.engine;
+ }
+ if (this._selectionDetails &&
+ this._selectionDetails.currentIndex != -1) {
+ BrowserSearch.searchBar.telemetrySearchDetails = this._selectionDetails;
+ this._selectionDetails = null;
+ }
+ document.getBindingParent(this).handleSearchCommand(aEvent, engine);
+ ]]></body>
+ </method>
+
+ <property name="selectedButton">
+ <getter><![CDATA[
+ return this.popup.oneOffButtons.selectedButton;
+ ]]></getter>
+ <setter><![CDATA[
+ return this.popup.oneOffButtons.selectedButton = val;
+ ]]></setter>
+ </property>
+
+ <method name="handleKeyboardNavigation">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let popup = this.popup;
+ if (!popup.popupOpen)
+ return;
+
+ // accel + up/down changes the default engine and shouldn't affect
+ // the selection on the one-off buttons.
+ if (aEvent.getModifierState("Accel"))
+ return;
+
+ let suggestions =
+ document.getAnonymousElementByAttribute(popup, "anonid", "tree");
+ let suggestionsHidden =
+ suggestions.getAttribute("collapsed") == "true";
+ let numItems = suggestionsHidden ? 0 : this.popup.view.rowCount;
+ this.popup.oneOffButtons.handleKeyPress(aEvent, numItems, true);
+ ]]></body>
+ </method>
+
+ <!-- nsIController -->
+ <field name="searchbarController" readonly="true"><![CDATA[({
+ _self: this,
+ supportsCommand: function(aCommand) {
+ return aCommand == "cmd_clearhistory" ||
+ aCommand == "cmd_togglesuggest";
+ },
+
+ isCommandEnabled: function(aCommand) {
+ return true;
+ },
+
+ doCommand: function (aCommand) {
+ switch (aCommand) {
+ case "cmd_clearhistory":
+ var param = this._self.getAttribute("autocompletesearchparam");
+
+ BrowserSearch.searchBar.FormHistory.update({ op : "remove", fieldname : param }, null);
+ this._self.value = "";
+ break;
+ case "cmd_togglesuggest":
+ // The pref observer will update _suggestEnabled and the menu
+ // checkmark.
+ Services.prefs.setBoolPref("browser.search.suggest.enabled",
+ !this._self._suggestEnabled);
+ break;
+ default:
+ // do nothing with unrecognized command
+ }
+ }
+ })]]></field>
+ </implementation>
+
+ <handlers>
+ <handler event="input"><![CDATA[
+ this.popup.removeAttribute("showonlysettings");
+ ]]></handler>
+
+ <handler event="keypress" phase="capturing"
+ action="return this.handleKeyboardNavigation(event);"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="accel"
+ phase="capturing"
+ action="document.getBindingParent(this).selectEngine(event, false);"/>
+
+ <handler event="keypress" keycode="VK_DOWN" modifiers="accel"
+ phase="capturing"
+ action="document.getBindingParent(this).selectEngine(event, true);"/>
+
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt"
+ phase="capturing"
+ action="return this.openSearch();"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="alt"
+ phase="capturing"
+ action="return this.openSearch();"/>
+
+ <handler event="dragover">
+ <![CDATA[
+ var types = event.dataTransfer.types;
+ if (types.includes("text/plain") || types.includes("text/x-moz-text-internal"))
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="drop">
+ <![CDATA[
+ var dataTransfer = event.dataTransfer;
+ var data = dataTransfer.getData("text/plain");
+ if (!data)
+ data = dataTransfer.getData("text/x-moz-text-internal");
+ if (data) {
+ event.preventDefault();
+ this.value = data;
+ document.getBindingParent(this).openSuggestionsPanel();
+ }
+ ]]>
+ </handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="browser-search-autocomplete-result-popup" extends="chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup">
+ <resources>
+ <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
+ <stylesheet src="chrome://browser/skin/searchbar.css"/>
+ </resources>
+ <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+ <xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings"
+ class="search-panel-header search-panel-current-engine">
+ <xul:image class="searchbar-engine-image" xbl:inherits="src"/>
+ <xul:label anonid="searchbar-engine-name" flex="1" crop="end"
+ role="presentation"/>
+ </xul:hbox>
+ <xul:tree anonid="tree" flex="1"
+ class="autocomplete-tree plain search-panel-tree"
+ hidecolumnpicker="true" 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>
+ <xul:vbox anonid="search-one-off-buttons" class="search-one-offs"/>
+ </content>
+ <implementation>
+ <!-- Popup rollup is triggered by native events before the mousedown event
+ reaches the DOM. The will be set to true by the popuphiding event and
+ false after the mousedown event has been triggered to detect what
+ caused rollup. -->
+ <field name="_isHiding">false</field>
+ <field name="_bundle">null</field>
+ <property name="bundle" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._bundle) {
+ const kBundleURI = "chrome://browser/locale/search.properties";
+ this._bundle = Services.strings.createBundle(kBundleURI);
+ }
+ return this._bundle;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="oneOffButtons" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "search-one-off-buttons");
+ </field>
+
+ <method name="updateHeader">
+ <body><![CDATA[
+ let currentEngine = Services.search.currentEngine;
+ let uri = currentEngine.iconURI;
+ if (uri) {
+ this.setAttribute("src", uri.spec);
+ }
+ else {
+ // If the default has just been changed to a provider without icon,
+ // avoid showing the icon of the previous default provider.
+ this.removeAttribute("src");
+ }
+
+ let headerText = this.bundle.formatStringFromName("searchHeader",
+ [currentEngine.name], 1);
+ document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine-name")
+ .setAttribute("value", headerText);
+ document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine")
+ .engine = currentEngine;
+ ]]></body>
+ </method>
+
+ <!-- This is called when a one-off is clicked and when "search in new tab"
+ is selected from a one-off context menu. -->
+ <method name="handleOneOffSearch">
+ <parameter name="event"/>
+ <parameter name="engine"/>
+ <parameter name="where"/>
+ <parameter name="params"/>
+ <body><![CDATA[
+ let searchbar = document.getElementById("searchbar");
+ searchbar.handleSearchCommandWhere(event, engine, where, params);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing"><![CDATA[
+ if (!this.oneOffButtons.popup) {
+ // The panel width only spans to the textbox size, but we also want it
+ // to include the magnifier icon's width.
+ let ltr = getComputedStyle(this).direction == "ltr";
+ let magnifierWidth = parseInt(getComputedStyle(this)[
+ ltr ? "marginLeft" : "marginRight"
+ ]) * -1;
+ // Ensure the panel is wide enough to fit at least 3 engines.
+ let minWidth = Math.max(
+ parseInt(this.width) + magnifierWidth,
+ this.oneOffButtons.buttonWidth * 3
+ );
+ this.style.minWidth = minWidth + "px";
+
+ // Set the origin before assigning the popup, as the assignment does
+ // a rebuild and would miss the origin.
+ this.oneOffButtons.telemetryOrigin = "searchbar";
+ // Set popup after setting the minWidth since it builds the buttons.
+ this.oneOffButtons.popup = this;
+ this.oneOffButtons.textbox = this.input;
+ }
+
+ // First handle deciding if we are showing the reduced version of the
+ // popup containing only the preferences button. We do this if the
+ // glass icon has been clicked if the text field is empty.
+ let searchbar = document.getElementById("searchbar");
+ let tree = document.getAnonymousElementByAttribute(this, "anonid",
+ "tree")
+ if (searchbar.hasAttribute("showonlysettings")) {
+ searchbar.removeAttribute("showonlysettings");
+ this.setAttribute("showonlysettings", "true");
+
+ // Setting this with an xbl-inherited attribute gets overridden the
+ // second time the user clicks the glass icon for some reason...
+ tree.collapsed = true;
+ }
+ else {
+ this.removeAttribute("showonlysettings");
+ // Uncollapse as long as we have a tree with a view which has >= 1 row.
+ // The autocomplete binding itself will take care of uncollapsing later,
+ // if we currently have no rows but end up having some in the future
+ // when the search string changes
+ tree.collapsed = !tree.view || !tree.view.rowCount;
+ }
+
+ // Show the current default engine in the top header of the panel.
+ this.updateHeader();
+ ]]></handler>
+
+ <handler event="popuphiding"><![CDATA[
+ this._isHiding = true;
+ setTimeout(() => {
+ this._isHiding = false;
+ }, 0);
+ ]]></handler>
+
+ <!-- This handles clicks on the topmost "Foo Search" header in the
+ popup (hbox[anonid="searchbar-engine"]). -->
+ <handler event="click"><![CDATA[
+ if (event.button == 2) {
+ // Ignore right clicks.
+ return;
+ }
+ let button = event.originalTarget;
+ let engine = button.parentNode.engine;
+ if (!engine) {
+ return;
+ }
+ this.oneOffButtons.handleSearchCommand(event, engine);
+ ]]></handler>
+ </handlers>
+
+ </binding>
+
+ <!-- Used for additional open search providers in the search panel. -->
+ <binding id="addengine-icon" extends="xul:box">
+ <content>
+ <xul:image class="addengine-icon" xbl:inherits="src"/>
+ <xul:image class="addengine-badge"/>
+ </content>
+ </binding>
+
+ <binding id="search-one-offs">
+ <content context="_child">
+ <xul:deck anonid="search-panel-one-offs-header"
+ selectedIndex="0"
+ class="search-panel-header search-panel-current-input">
+ <xul:label anonid="searchbar-oneoffheader-search"
+ value="&searchWithHeader.label;"/>
+ <xul:hbox anonid="search-panel-searchforwith"
+ class="search-panel-current-input">
+ <xul:label anonid="searchbar-oneoffheader-before"
+ value="&searchFor.label;"/>
+ <xul:label anonid="searchbar-oneoffheader-searchtext"
+ class="search-panel-input-value"
+ flex="1"
+ crop="end"/>
+ <xul:label anonid="searchbar-oneoffheader-after"
+ flex="10000"
+ value="&searchWith.label;"/>
+ </xul:hbox>
+ <xul:hbox anonid="search-panel-searchonengine"
+ class="search-panel-current-input">
+ <xul:label anonid="searchbar-oneoffheader-beforeengine"
+ value="&search.label;"/>
+ <xul:label anonid="searchbar-oneoffheader-engine"
+ class="search-panel-input-value"
+ flex="1"
+ crop="end"/>
+ <xul:label anonid="searchbar-oneoffheader-afterengine"
+ flex="10000"
+ value="&searchAfter.label;"/>
+ </xul:hbox>
+ </xul:deck>
+ <xul:description anonid="search-panel-one-offs"
+ role="group"
+ class="search-panel-one-offs"
+ xbl:inherits="compact">
+ <xul:button anonid="search-settings-compact"
+ oncommand="showSettings();"
+ class="searchbar-engine-one-off-item search-setting-button-compact"
+ tooltiptext="&changeSearchSettings.tooltip;"
+ xbl:inherits="compact"/>
+ </xul:description>
+ <xul:vbox anonid="add-engines"/>
+ <xul:button anonid="search-settings"
+ oncommand="showSettings();"
+ class="search-setting-button search-panel-header"
+ label="&changeSearchSettings.button;"
+ xbl:inherits="compact"/>
+ <xul:menupopup anonid="search-one-offs-context-menu">
+ <xul:menuitem anonid="search-one-offs-context-open-in-new-tab"
+ label="&searchInNewTab.label;"
+ accesskey="&searchInNewTab.accesskey;"/>
+ <xul:menuitem anonid="search-one-offs-context-set-default"
+ label="&searchSetAsDefault.label;"
+ accesskey="&searchSetAsDefault.accesskey;"/>
+ </xul:menupopup>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+
+ <!-- Width in pixels of the one-off buttons. 49px is the min-width of
+ each search engine button, adapt this const when changing the css.
+ It's actually 48px + 1px of right border. -->
+ <property name="buttonWidth" readonly="true" onget="return 49;"/>
+
+ <field name="_popup">null</field>
+
+ <!-- The popup that contains the one-offs. This is required, so it should
+ never be null or undefined, except possibly before the one-offs are
+ used. -->
+ <property name="popup">
+ <getter><![CDATA[
+ return this._popup;
+ ]]></getter>
+ <setter><![CDATA[
+ if (this._popup == val) {
+ return val;
+ }
+
+ let events = [
+ "popupshowing",
+ "popuphidden",
+ ];
+ if (this._popup) {
+ for (let event of events) {
+ this._popup.removeEventListener(event, this);
+ }
+ }
+ if (val) {
+ for (let event of events) {
+ val.addEventListener(event, this);
+ }
+ }
+ this._popup = val;
+
+ // If the popup is already open, rebuild the one-offs now. The
+ // popup may be opening, so check that the state is not closed
+ // instead of checking popupOpen.
+ if (val && val.state != "closed") {
+ this._rebuild();
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <field name="_textbox">null</field>
+
+ <!-- The textbox associated with the one-offs. Set this to a textbox to
+ automatically keep the related one-offs UI up to date. Otherwise you
+ can leave it null/undefined, and in that case you should update the
+ query property manually. -->
+ <property name="textbox">
+ <getter><![CDATA[
+ return this._textbox;
+ ]]></getter>
+ <setter><![CDATA[
+ if (this._textbox == val) {
+ return val;
+ }
+ if (this._textbox) {
+ this._textbox.removeEventListener("input", this);
+ }
+ if (val) {
+ val.addEventListener("input", this);
+ }
+ return this._textbox = val;
+ ]]></setter>
+ </property>
+
+ <!-- Set this to a string that identifies your one-offs consumer. It'll
+ be appended to telemetry recorded with maybeRecordTelemetry(). -->
+ <field name="telemetryOrigin">""</field>
+
+ <field name="_query">""</field>
+
+ <!-- The query string currently shown in the one-offs. If the textbox
+ property is non-null, then this is automatically updated on
+ input. -->
+ <property name="query">
+ <getter><![CDATA[
+ return this._query;
+ ]]></getter>
+ <setter><![CDATA[
+ this._query = val;
+ if (this.popup && this.popup.popupOpen) {
+ this._updateAfterQueryChanged();
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <field name="_selectedButton">null</field>
+
+ <!-- The selected one-off, a xul:button, including the add-engine button
+ and the search-settings button. Null if no one-off is selected. -->
+ <property name="selectedButton">
+ <getter><![CDATA[
+ return this._selectedButton;
+ ]]></getter>
+ <setter><![CDATA[
+ this._changeVisuallySelectedButton(val, true);
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- The index of the selected one-off, including the add-engine button
+ and the search-settings button. -1 if no one-off is selected. -->
+ <property name="selectedButtonIndex">
+ <getter><![CDATA[
+ let buttons = this.getSelectableButtons(true);
+ for (let i = 0; i < buttons.length; i++) {
+ if (buttons[i] == this._selectedButton) {
+ return i;
+ }
+ }
+ return -1;
+ ]]></getter>
+ <setter><![CDATA[
+ let buttons = this.getSelectableButtons(true);
+ this.selectedButton = buttons[val];
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- The visually selected one-off is the same as the selected one-off
+ unless a one-off is moused over. In that case, the visually selected
+ one-off is the moused-over one-off, which may be different from the
+ selected one-off. The visually selected one-off is always the one
+ that is visually highlighted. Includes the add-engine button and the
+ search-settings button. A xul:button. -->
+ <property name="visuallySelectedButton" readonly="true">
+ <getter><![CDATA[
+ return this.getSelectableButtons(true).find(button => {
+ return button.getAttribute("selected") == "true";
+ });
+ ]]></getter>
+ </property>
+
+ <property name="compact" readonly="true">
+ <getter><![CDATA[
+ return this.getAttribute("compact") == "true";
+ ]]></getter>
+ </property>
+
+ <property name="settingsButton" readonly="true">
+ <getter><![CDATA[
+ let id = this.compact ? "search-settings-compact" : "search-settings";
+ return document.getAnonymousElementByAttribute(this, "anonid", id);
+ ]]></getter>
+ </property>
+
+ <field name="_bundle">null</field>
+
+ <property name="bundle" readonly="true">
+ <getter><![CDATA[
+ if (!this._bundle) {
+ const kBundleURI = "chrome://browser/locale/search.properties";
+ this._bundle = Services.strings.createBundle(kBundleURI);
+ }
+ return this._bundle;
+ ]]></getter>
+ </property>
+
+ <!-- When a context menu is opened on a one-off button, this is set to the
+ engine of that button for use with the context menu actions. -->
+ <field name="_contextEngine">null</field>
+
+ <constructor><![CDATA[
+ // Prevent popup events from the context menu from reaching the autocomplete
+ // binding (or other listeners).
+ let menu = document.getAnonymousElementByAttribute(this, "anonid", "search-one-offs-context-menu");
+ let listener = aEvent => aEvent.stopPropagation();
+ menu.addEventListener("popupshowing", listener);
+ menu.addEventListener("popuphiding", listener);
+ menu.addEventListener("popupshown", aEvent => {
+ this._ignoreMouseEvents = true;
+ aEvent.stopPropagation();
+ });
+ menu.addEventListener("popuphidden", aEvent => {
+ this._ignoreMouseEvents = false;
+ aEvent.stopPropagation();
+ });
+ ]]></constructor>
+
+ <!-- This handles events outside the one-off buttons, like on the popup
+ and textbox. -->
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ switch (event.type) {
+ case "input":
+ // Allow the consumer's input to override its value property with
+ // a oneOffSearchQuery property. That way if the value is not
+ // actually what the user typed (e.g., it's autofilled, or it's a
+ // mozaction URI), the consumer has some way of providing it.
+ this.query = event.target.oneOffSearchQuery || event.target.value;
+ break;
+ case "popupshowing":
+ this._rebuild();
+ break;
+ case "popuphidden":
+ Services.tm.mainThread.dispatch(() => {
+ this.selectedButton = null;
+ this._contextEngine = null;
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="showSettings">
+ <body><![CDATA[
+ BrowserUITelemetry.countSearchSettingsEvent(this.telemetryOrigin);
+ openPreferences("paneSearch");
+ // If the preference tab was already selected, the panel doesn't
+ // close itself automatically.
+ this.popup.hidePopup();
+ ]]></body>
+ </method>
+
+ <!-- Updates the parts of the UI that show the query string. -->
+ <method name="_updateAfterQueryChanged">
+ <body><![CDATA[
+ let headerSearchText =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "searchbar-oneoffheader-searchtext");
+ let headerPanel =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "search-panel-one-offs-header");
+ let list = document.getAnonymousElementByAttribute(this, "anonid",
+ "search-panel-one-offs");
+ headerSearchText.setAttribute("value", this.query);
+ let groupText;
+ let isOneOffSelected =
+ this.selectedButton &&
+ this.selectedButton.classList.contains("searchbar-engine-one-off-item");
+ // Typing de-selects the settings or opensearch buttons at the bottom
+ // of the search panel, as typing shows the user intends to search.
+ if (this.selectedButton && !isOneOffSelected)
+ this.selectedButton = null;
+ if (this.query) {
+ groupText = headerSearchText.previousSibling.value +
+ '"' + headerSearchText.value + '"' +
+ headerSearchText.nextSibling.value;
+ if (!isOneOffSelected)
+ headerPanel.selectedIndex = 1;
+ }
+ else {
+ let noSearchHeader =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "searchbar-oneoffheader-search");
+ groupText = noSearchHeader.value;
+ if (!isOneOffSelected)
+ headerPanel.selectedIndex = 0;
+ }
+ list.setAttribute("aria-label", groupText);
+ ]]></body>
+ </method>
+
+ <!-- Builds all the UI. -->
+ <method name="_rebuild">
+ <body><![CDATA[
+ // Update the 'Search for <keywords> with:" header.
+ this._updateAfterQueryChanged();
+
+ let list = document.getAnonymousElementByAttribute(this, "anonid",
+ "search-panel-one-offs");
+
+ // Handle opensearch items. This needs to be done before building the
+ // list of one off providers, as that code will return early if all the
+ // alternative engines are hidden.
+ let addEngineList =
+ document.getAnonymousElementByAttribute(this, "anonid", "add-engines");
+ while (addEngineList.firstChild)
+ addEngineList.firstChild.remove();
+
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ // Add a button for each engine that the page in the selected browser
+ // offers. But not when the one-offs are compact. Compact one-offs
+ // are shown in the urlbar, and the add-engine buttons span the width
+ // of the popup, so if we added all the engines that a site offers, it
+ // could effectively break the urlbar popup by offering a ton of
+ // engines. We should probably make a smaller version of the buttons
+ // for compact one-offs.
+ if (!this.compact) {
+ for (let engine of gBrowser.selectedBrowser.engines || []) {
+ let button = document.createElementNS(kXULNS, "button");
+ let label = this.bundle.formatStringFromName("cmd_addFoundEngine",
+ [engine.title], 1);
+ button.id = this.telemetryOrigin + "-add-engine-" +
+ engine.title.replace(/ /g, '-');
+ button.setAttribute("class", "addengine-item");
+ button.setAttribute("label", label);
+ button.setAttribute("pack", "start");
+
+ button.setAttribute("crop", "end");
+ button.setAttribute("tooltiptext", engine.uri);
+ button.setAttribute("uri", engine.uri);
+ if (engine.icon) {
+ button.setAttribute("image", engine.icon);
+ }
+ button.setAttribute("title", engine.title);
+ addEngineList.appendChild(button);
+ }
+ }
+
+ let settingsButton =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "search-settings-compact");
+ // Finally, build the list of one-off buttons.
+ while (list.firstChild != settingsButton)
+ list.firstChild.remove();
+ // Remove the trailing empty text node introduced by the binding's
+ // content markup above.
+ if (settingsButton.nextSibling)
+ settingsButton.nextSibling.remove();
+
+ let Preferences =
+ Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+ let pref = Preferences.get("browser.search.hiddenOneOffs");
+ let hiddenList = pref ? pref.split(",") : [];
+
+ let currentEngineName = Services.search.currentEngine.name;
+ let includeCurrentEngine = this.getAttribute("includecurrentengine");
+ let engines = Services.search.getVisibleEngines().filter(e => {
+ return (includeCurrentEngine || e.name != currentEngineName) &&
+ !hiddenList.includes(e.name);
+ });
+
+ let header = document.getAnonymousElementByAttribute(this, "anonid",
+ "search-panel-one-offs-header")
+ // header is a xul:deck so collapsed doesn't work on it, see bug 589569.
+ header.hidden = list.collapsed = !engines.length;
+
+ if (!engines.length)
+ return;
+
+ let panelWidth = parseInt(this.popup.clientWidth);
+ // The + 1 is because the last button doesn't have a right border.
+ let enginesPerRow = Math.floor((panelWidth + 1) / this.buttonWidth);
+ let buttonWidth = Math.floor(panelWidth / enginesPerRow);
+ // There will be an emtpy area of:
+ // panelWidth - enginesPerRow * buttonWidth px
+ // at the end of each row.
+
+ // If the <description> tag with the list of search engines doesn't have
+ // a fixed height, the panel will be sized incorrectly, causing the bottom
+ // of the suggestion <tree> to be hidden.
+ let oneOffCount = engines.length;
+ if (this.compact)
+ ++oneOffCount;
+ let rowCount = Math.ceil(oneOffCount / enginesPerRow);
+ let height = rowCount * 33; // 32px per row, 1px border.
+ list.setAttribute("height", height + "px");
+
+ // Ensure we can refer to the settings buttons by ID:
+ let settingsEl = document.getAnonymousElementByAttribute(this, "anonid", "search-settings");
+ settingsEl.id = this.telemetryOrigin + "-anon-search-settings";
+ let compactSettingsEl = document.getAnonymousElementByAttribute(this, "anonid", "search-settings-compact");
+ compactSettingsEl.id = this.telemetryOrigin +
+ "-anon-search-settings-compact";
+
+ let dummyItems = enginesPerRow - (oneOffCount % enginesPerRow || enginesPerRow);
+ for (let i = 0; i < engines.length; ++i) {
+ let engine = engines[i];
+ let button = document.createElementNS(kXULNS, "button");
+ button.id = this._buttonIDForEngine(engine);
+ let uri = "chrome://browser/skin/search-engine-placeholder.png";
+ if (engine.iconURI) {
+ uri = engine.iconURI.spec;
+ }
+ button.setAttribute("image", uri);
+ button.setAttribute("class", "searchbar-engine-one-off-item");
+ button.setAttribute("tooltiptext", engine.name);
+ button.setAttribute("width", buttonWidth);
+ button.engine = engine;
+
+ if ((i + 1) % enginesPerRow == 0)
+ button.classList.add("last-of-row");
+
+ if (i + 1 == engines.length)
+ button.classList.add("last-engine");
+
+ if (i >= oneOffCount + dummyItems - enginesPerRow)
+ button.classList.add("last-row");
+
+ list.insertBefore(button, settingsButton);
+ }
+
+ let hasDummyItems = !!dummyItems;
+ while (dummyItems) {
+ let button = document.createElementNS(kXULNS, "button");
+ button.setAttribute("class", "searchbar-engine-one-off-item dummy last-row");
+ button.setAttribute("width", buttonWidth);
+
+ if (!--dummyItems)
+ button.classList.add("last-of-row");
+
+ list.insertBefore(button, settingsButton);
+ }
+
+ if (this.compact) {
+ this.settingsButton.setAttribute("width", buttonWidth);
+ if (rowCount == 1 && hasDummyItems) {
+ // When there's only one row, make the compact settings button
+ // hug the right edge of the panel. It may not due to the panel's
+ // width not being an integral multiple of the button width. (See
+ // the "There will be an emtpy area" comment above.) Increase the
+ // width of the last dummy item by the remainder.
+ //
+ // There's one weird thing to guard against. When layout pixels
+ // aren't an integral multiple of device pixels, the calculated
+ // remainder can end up being ~1px too big, at least on Windows,
+ // which pushes the settings button to a new row. The remainder
+ // is integral, not a fraction, so that's not the problem. To
+ // work around that, unscale the remainder, floor it, scale it
+ // back, and then floor that.
+ let scale = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .screenPixelsPerCSSPixel;
+ let remainder = panelWidth - (enginesPerRow * buttonWidth);
+ remainder = Math.floor(Math.floor(remainder * scale) / scale);
+ let width = remainder + buttonWidth;
+ let lastDummyItem = this.settingsButton.previousSibling;
+ lastDummyItem.setAttribute("width", width);
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_buttonIDForEngine">
+ <parameter name="engine"/>
+ <body><![CDATA[
+ return this.telemetryOrigin + "-engine-one-off-item-" +
+ engine.name.replace(/ /g, '-');
+ ]]></body>
+ </method>
+
+ <method name="_buttonForEngine">
+ <parameter name="engine"/>
+ <body><![CDATA[
+ return document.getElementById(this._buttonIDForEngine(engine));
+ ]]></body>
+ </method>
+
+ <method name="_changeVisuallySelectedButton">
+ <parameter name="val"/>
+ <parameter name="aUpdateLogicallySelectedButton"/>
+ <body><![CDATA[
+ let visuallySelectedButton = this.visuallySelectedButton;
+ if (visuallySelectedButton)
+ visuallySelectedButton.removeAttribute("selected");
+
+ let header =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "search-panel-one-offs-header");
+ // Avoid selecting dummy buttons.
+ if (val && !val.classList.contains("dummy")) {
+ val.setAttribute("selected", "true");
+ if (val.classList.contains("searchbar-engine-one-off-item") &&
+ val.engine) {
+ let headerEngineText =
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "searchbar-oneoffheader-engine");
+ header.selectedIndex = 2;
+ headerEngineText.value = val.engine.name;
+ }
+ else {
+ header.selectedIndex = this.query ? 1 : 0;
+ }
+ if (this.textbox) {
+ this.textbox.setAttribute("aria-activedescendant", val.id);
+ }
+ } else {
+ val = null;
+ header.selectedIndex = this.query ? 1 : 0;
+ if (this.textbox) {
+ this.textbox.removeAttribute("aria-activedescendant");
+ }
+ }
+
+ if (aUpdateLogicallySelectedButton) {
+ this._selectedButton = val;
+ if (val && !val.engine) {
+ // If the button doesn't have an engine, then clear the popup's
+ // selection to indicate that pressing Return while the button is
+ // selected will do the button's command, not search.
+ this.popup.selectedIndex = -1;
+ }
+ let event = document.createEvent("Events");
+ event.initEvent("SelectedOneOffButtonChanged", true, false);
+ this.dispatchEvent(event);
+ }
+ ]]></body>
+ </method>
+
+ <method name="getSelectableButtons">
+ <parameter name="aIncludeNonEngineButtons"/>
+ <body><![CDATA[
+ let buttons = [];
+ let oneOff = document.getAnonymousElementByAttribute(this, "anonid",
+ "search-panel-one-offs");
+ for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
+ // oneOff may be a text node since the list xul:description contains
+ // whitespace and the compact settings button. See the markup
+ // above. _rebuild removes text nodes, but it may not have been
+ // called yet (because e.g. the popup hasn't been opened yet).
+ if (oneOff.nodeType == Node.ELEMENT_NODE) {
+ if (oneOff.classList.contains("dummy") ||
+ oneOff.classList.contains("search-setting-button-compact"))
+ break;
+ buttons.push(oneOff);
+ }
+ }
+
+ if (!aIncludeNonEngineButtons)
+ return buttons;
+
+ let addEngine =
+ document.getAnonymousElementByAttribute(this, "anonid", "add-engines");
+ for (addEngine = addEngine.firstChild; addEngine; addEngine = addEngine.nextSibling)
+ buttons.push(addEngine);
+
+ buttons.push(this.settingsButton);
+ return buttons;
+ ]]></body>
+ </method>
+
+ <method name="handleSearchCommand">
+ <parameter name="aEvent"/>
+ <parameter name="aEngine"/>
+ <parameter name="aForceNewTab"/>
+ <body><![CDATA[
+ let where = "current";
+ let params;
+
+ // Open ctrl/cmd clicks on one-off buttons in a new background tab.
+ if (aForceNewTab) {
+ where = "tab";
+ if (Services.prefs.getBoolPref("browser.tabs.loadInBackground")) {
+ params = {
+ inBackground: true,
+ };
+ }
+ }
+ else {
+ var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
+ if (((aEvent instanceof KeyboardEvent) && aEvent.altKey) ^ newTabPref)
+ where = "tab";
+ if ((aEvent instanceof MouseEvent) &&
+ (aEvent.button == 1 || aEvent.getModifierState("Accel"))) {
+ where = "tab";
+ params = {
+ inBackground: true,
+ };
+ }
+ }
+
+ this.popup.handleOneOffSearch(aEvent, aEngine, where, params);
+ ]]></body>
+ </method>
+
+ <!--
+ Increments or decrements the index of the currently selected one-off.
+
+ @param aForward
+ If true, the index is incremented, and if false, the index is
+ decremented.
+ @param aWrapAround
+ This has a couple of effects, depending on whether there is
+ currently a selection.
+ (1) If true and the last one-off is currently selected,
+ incrementing the index will cause the selection to be cleared and
+ this method to return true. Calling advanceSelection again after
+ that (again with aForward=true) will select the first one-off.
+ Likewise if decrementing the index when the first one-off is
+ selected, except in the opposite direction of course.
+ (2) If true and there currently is no selection, decrementing the
+ index will cause the last one-off to become selected and this
+ method to return true. Only the aForward=false case is affected
+ because it is always the case that if aForward=true and there
+ currently is no selection, the first one-off becomes selected and
+ this method returns true.
+ @param aCycleEngines
+ If true, only engine buttons are included.
+ @return True if the selection can continue to advance after this method
+ returns and false if not.
+ -->
+ <method name="advanceSelection">
+ <parameter name="aForward"/>
+ <parameter name="aWrapAround"/>
+ <parameter name="aCycleEngines"/>
+ <body><![CDATA[
+ let selectedButton = this.selectedButton;
+ let buttons = this.getSelectableButtons(aCycleEngines);
+
+ if (selectedButton) {
+ // cycle through one-off buttons.
+ let index = buttons.indexOf(selectedButton);
+ if (aForward)
+ ++index;
+ else
+ --index;
+
+ if (index >= 0 && index < buttons.length)
+ this.selectedButton = buttons[index];
+ else
+ this.selectedButton = null;
+
+ if (this.selectedButton || aWrapAround)
+ return true;
+
+ return false;
+ }
+
+ // If no selection, select the first button or ...
+ if (aForward) {
+ this.selectedButton = buttons[0];
+ return true;
+ }
+
+ if (!aForward && aWrapAround) {
+ // the last button.
+ this.selectedButton = buttons[buttons.length - 1];
+ return true;
+ }
+
+ return false;
+ ]]></body>
+ </method>
+
+ <!--
+ This handles key presses specific to the one-off buttons like Tab and
+ Alt-Up/Down, and Up/Down keys within the buttons. Since one-off buttons
+ are always used in conjunction with a list of some sort (in this.popup),
+ it also handles Up/Down keys that cross the boundaries between list
+ items and the one-off buttons.
+
+ @param event
+ The key event.
+ @param numListItems
+ The number of items in the list. The reason that this is a
+ parameter at all is that the list may contain items at the end
+ that should be ignored, depending on the consumer. That's true
+ for the urlbar for example.
+ @param allowEmptySelection
+ Pass true if it's OK that neither the list nor the one-off
+ buttons contains a selection. Pass false if either the list or
+ the one-off buttons (or both) should always contain a selection.
+ @param textboxUserValue
+ When the last list item is selected and the user presses Down,
+ the first one-off becomes selected and the textbox value is
+ restored to the value that the user typed. Pass that value here.
+ However, if you pass true for allowEmptySelection, you don't need
+ to pass anything for this parameter. (Pass undefined or null.)
+ @return True if this method handled the keypress and false if not. If
+ false, then you should let the autocomplete controller handle
+ the keypress. The value of event.defaultPrevented will be the
+ same as this return value.
+ -->
+ <method name="handleKeyPress">
+ <parameter name="event"/>
+ <parameter name="numListItems"/>
+ <parameter name="allowEmptySelection"/>
+ <parameter name="textboxUserValue"/>
+ <body><![CDATA[
+ if (!this.popup) {
+ return false;
+ }
+
+ let stopEvent = false;
+
+ // Tab cycles through the one-offs and moves the focus out at the end.
+ // But only if non-Shift modifiers aren't also pressed, to avoid
+ // clobbering other shortcuts.
+ if (event.keyCode == KeyEvent.DOM_VK_TAB &&
+ !event.altKey &&
+ !event.ctrlKey &&
+ !event.metaKey &&
+ this.getAttribute("disabletab") != "true") {
+ stopEvent = this.advanceSelection(!event.shiftKey, false, true);
+ }
+
+ // Alt + up/down is very similar to (shift +) tab but differs in that
+ // it loops through the list, whereas tab will move the focus out.
+ else if (event.altKey &&
+ (event.keyCode == KeyEvent.DOM_VK_DOWN ||
+ event.keyCode == KeyEvent.DOM_VK_UP)) {
+ stopEvent =
+ this.advanceSelection(event.keyCode == KeyEvent.DOM_VK_DOWN,
+ true, false);
+ }
+
+ else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP) {
+ if (numListItems > 0) {
+ if (this.popup.selectedIndex > 0) {
+ // The autocomplete controller should handle this case.
+ } else if (this.popup.selectedIndex == 0) {
+ if (!allowEmptySelection) {
+ // Wrap around the selection to the last one-off.
+ this.selectedButton = null;
+ this.popup.selectedIndex = -1;
+ // Call advanceSelection after setting selectedIndex so that
+ // screen readers see the newly selected one-off. Both trigger
+ // accessibility events.
+ this.advanceSelection(false, true, true);
+ stopEvent = true;
+ }
+ } else {
+ let firstButtonSelected =
+ this.selectedButton &&
+ this.selectedButton == this.getSelectableButtons(true)[0];
+ if (firstButtonSelected) {
+ this.selectedButton = null;
+ } else {
+ stopEvent = this.advanceSelection(false, true, true);
+ }
+ }
+ } else {
+ stopEvent = this.advanceSelection(false, true, true);
+ }
+ }
+
+ else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
+ if (numListItems > 0) {
+ if (this.popup.selectedIndex >= 0 &&
+ this.popup.selectedIndex < numListItems - 1) {
+ // The autocomplete controller should handle this case.
+ } else if (this.popup.selectedIndex == numListItems - 1) {
+ this.selectedButton = null;
+ if (!allowEmptySelection) {
+ this.popup.selectedIndex = -1;
+ stopEvent = true;
+ }
+ if (this.textbox && typeof(textboxUserValue) == "string") {
+ this.textbox.value = textboxUserValue;
+ }
+ // Call advanceSelection after setting selectedIndex so that
+ // screen readers see the newly selected one-off. Both trigger
+ // accessibility events.
+ this.advanceSelection(true, true, true);
+ } else {
+ let buttons = this.getSelectableButtons(true);
+ let lastButtonSelected =
+ this.selectedButton &&
+ this.selectedButton == buttons[buttons.length - 1];
+ if (lastButtonSelected) {
+ this.selectedButton = null;
+ stopEvent = allowEmptySelection;
+ } else if (this.selectedButton) {
+ stopEvent = this.advanceSelection(true, true, true);
+ } else {
+ // The autocomplete controller should handle this case.
+ }
+ }
+ } else {
+ stopEvent = this.advanceSelection(true, true, true);
+ }
+ }
+
+ if (stopEvent) {
+ event.preventDefault();
+ event.stopPropagation();
+ return true;
+ }
+ return false;
+ ]]></body>
+ </method>
+
+ <!--
+ If the given event is related to the one-offs, this method records
+ one-off telemetry for it. this.telemetryOrigin will be appended to the
+ computed source, so make sure you set that first.
+
+ @param aEvent
+ An event, like a click on a one-off button.
+ @param aOpenUILinkWhere
+ The "where" passed to openUILink.
+ @param aOpenUILinkParams
+ The "params" passed to openUILink.
+ @return True if telemetry was recorded and false if not.
+ -->
+ <method name="maybeRecordTelemetry">
+ <parameter name="aEvent"/>
+ <parameter name="aOpenUILinkWhere"/>
+ <parameter name="aOpenUILinkParams"/>
+ <body><![CDATA[
+ if (!aEvent) {
+ return false;
+ }
+
+ let source = null;
+ let type = "unknown";
+ let engine = null;
+ let target = aEvent.originalTarget;
+
+ if (aEvent instanceof KeyboardEvent) {
+ type = "key";
+ if (this.selectedButton) {
+ source = "oneoff";
+ engine = this.selectedButton.engine;
+ }
+ } else if (aEvent instanceof MouseEvent) {
+ type = "mouse";
+ if (target.classList.contains("searchbar-engine-one-off-item")) {
+ source = "oneoff";
+ engine = target.engine;
+ }
+ } else if ((aEvent instanceof XULCommandEvent) &&
+ target.getAttribute("anonid") ==
+ "search-one-offs-context-open-in-new-tab") {
+ source = "oneoff-context";
+ engine = this._contextEngine;
+ }
+
+ if (!source) {
+ return false;
+ }
+
+ if (this.telemetryOrigin) {
+ source += "-" + this.telemetryOrigin;
+ }
+
+ let tabBackground = aOpenUILinkWhere == "tab" &&
+ aOpenUILinkParams &&
+ aOpenUILinkParams.inBackground;
+ let where = tabBackground ? "tab-background" : aOpenUILinkWhere;
+ BrowserSearch.recordOneoffSearchInTelemetry(engine, source, type,
+ where);
+ return true;
+ ]]></body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+
+ <handler event="mousedown"><![CDATA[
+ // Required to receive click events from the buttons on Linux.
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="mousemove"><![CDATA[
+ let target = event.originalTarget;
+ if (target.localName != "button")
+ return;
+
+ // Ignore mouse events when the context menu is open.
+ if (this._ignoreMouseEvents)
+ return;
+
+ if ((target.classList.contains("searchbar-engine-one-off-item") &&
+ !target.classList.contains("dummy")) ||
+ target.classList.contains("addengine-item") ||
+ target.classList.contains("search-setting-button")) {
+ this._changeVisuallySelectedButton(target);
+ }
+ ]]></handler>
+
+ <handler event="mouseout"><![CDATA[
+ let target = event.originalTarget;
+ if (target.localName != "button") {
+ return;
+ }
+
+ // Don't deselect the current button if the context menu is open.
+ if (this._ignoreMouseEvents)
+ return;
+
+ // Unfortunately this will fire before mouseover hits another item.
+ // If this button is selected, we replace that selection only if
+ // we're not moving to a different one-off item:
+ if (target.getAttribute("selected") == "true" &&
+ (!event.relatedTarget ||
+ !event.relatedTarget.classList.contains("searchbar-engine-one-off-item") ||
+ event.relatedTarget.classList.contains("dummy"))) {
+ this._changeVisuallySelectedButton(this.selectedButton);
+ }
+ ]]></handler>
+
+ <handler event="click"><![CDATA[
+ if (event.button == 2)
+ return; // ignore right clicks.
+
+ let button = event.originalTarget;
+ let engine = button.engine;
+
+ if (!engine)
+ return;
+
+ // Select the clicked button so that consumers can easily tell which
+ // button was acted on.
+ this.selectedButton = button;
+ this.handleSearchCommand(event, engine);
+ ]]></handler>
+
+ <handler event="command"><![CDATA[
+ let target = event.originalTarget;
+ if (target.classList.contains("addengine-item")) {
+ // On success, hide the panel and tell event listeners to reshow it to
+ // show the new engine.
+ let installCallback = {
+ onSuccess: engine => {
+ this._rebuild();
+ },
+ onError: function(errorCode) {
+ if (errorCode != Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE) {
+ // Download error is shown by the search service
+ return;
+ }
+ const kSearchBundleURI = "chrome://global/locale/search/search.properties";
+ let searchBundle = Services.strings.createBundle(kSearchBundleURI);
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandName = brandBundle.getString("brandShortName");
+ let title = searchBundle.GetStringFromName("error_invalid_engine_title");
+ let text = searchBundle.formatStringFromName("error_duplicate_engine_msg",
+ [brandName, target.getAttribute("uri")], 2);
+ Services.prompt.QueryInterface(Ci.nsIPromptFactory);
+ let prompt = Services.prompt.getPrompt(gBrowser.contentWindow, Ci.nsIPrompt);
+ prompt.QueryInterface(Ci.nsIWritablePropertyBag2);
+ prompt.setPropertyAsBool("allowTabModal", true);
+ prompt.alert(title, text);
+ }
+ }
+ Services.search.addEngine(target.getAttribute("uri"), null,
+ target.getAttribute("image"), false,
+ installCallback);
+ }
+ let anonid = target.getAttribute("anonid");
+ if (anonid == "search-one-offs-context-open-in-new-tab") {
+ // Select the context-clicked button so that consumers can easily
+ // tell which button was acted on.
+ this.selectedButton = this._buttonForEngine(this._contextEngine);
+ this.handleSearchCommand(event, this._contextEngine, true);
+ }
+ if (anonid == "search-one-offs-context-set-default") {
+ let currentEngine = Services.search.currentEngine;
+
+ if (!this.getAttribute("includecurrentengine")) {
+ // Make the target button of the context menu reflect the current
+ // search engine first. Doing this as opposed to rebuilding all the
+ // one-off buttons avoids flicker.
+ let button = this._buttonForEngine(this._contextEngine);
+ button.id = this._buttonIDForEngine(currentEngine);
+ let uri = "chrome://browser/skin/search-engine-placeholder.png";
+ if (currentEngine.iconURI)
+ uri = currentEngine.iconURI.spec;
+ button.setAttribute("image", uri);
+ button.setAttribute("tooltiptext", currentEngine.name);
+ button.engine = currentEngine;
+ }
+
+ Services.search.currentEngine = this._contextEngine;
+ }
+ ]]></handler>
+
+ <handler event="contextmenu"><![CDATA[
+ let target = event.originalTarget;
+ // Prevent the context menu from appearing except on the one off buttons.
+ if (!target.classList.contains("searchbar-engine-one-off-item") ||
+ target.classList.contains("dummy")) {
+ event.preventDefault();
+ return;
+ }
+ document.getAnonymousElementByAttribute(this, "anonid", "search-one-offs-context-set-default")
+ .setAttribute("disabled", target.engine == Services.search.currentEngine);
+
+ this._contextEngine = target.engine;
+ ]]></handler>
+ </handlers>
+
+ </binding>
+
+</bindings>
diff --git a/browser/components/search/content/searchReset.js b/browser/components/search/content/searchReset.js
new file mode 100644
index 000000000..b541d41da
--- /dev/null
+++ b/browser/components/search/content/searchReset.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";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const TELEMETRY_RESULT_ENUM = {
+ RESTORED_DEFAULT: 0,
+ KEPT_CURRENT: 1,
+ CHANGED_ENGINE: 2,
+ CLOSED_PAGE: 3,
+ OPENED_SETTINGS: 4
+};
+
+window.onload = function() {
+ let defaultEngine = document.getElementById("defaultEngine");
+ let originalDefault = Services.search.originalDefaultEngine;
+ defaultEngine.textContent = originalDefault.name;
+ defaultEngine.style.backgroundImage =
+ 'url("' + originalDefault.iconURI.spec + '")';
+
+ document.getElementById("searchResetChangeEngine").focus();
+ window.addEventListener("unload", recordPageClosed);
+ document.getElementById("linkSettingsPage")
+ .addEventListener("click", openingSettings);
+};
+
+function doSearch() {
+ let queryString = "";
+ let purpose = "";
+ let params = window.location.href.match(/^about:searchreset\?([^#]*)/);
+ if (params) {
+ params = params[1].split("&");
+ for (let param of params) {
+ if (param.startsWith("data="))
+ queryString = decodeURIComponent(param.slice(5));
+ else if (param.startsWith("purpose="))
+ purpose = param.slice(8);
+ }
+ }
+
+ let engine = Services.search.currentEngine;
+ let submission = engine.getSubmission(queryString, null, purpose);
+
+ window.removeEventListener("unload", recordPageClosed);
+
+ let win = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ win.openUILinkIn(submission.uri.spec, "current", false, submission.postData);
+}
+
+function openingSettings() {
+ record(TELEMETRY_RESULT_ENUM.OPENED_SETTINGS);
+ window.removeEventListener("unload", recordPageClosed);
+}
+
+function record(result) {
+ Services.telemetry.getHistogramById("SEARCH_RESET_RESULT").add(result);
+}
+
+function keepCurrentEngine() {
+ // Calling the currentEngine setter will force a correct loadPathHash to be
+ // written for this engine, so that we don't prompt the user again.
+ Services.search.currentEngine = Services.search.currentEngine;
+ record(TELEMETRY_RESULT_ENUM.KEPT_CURRENT);
+ doSearch();
+}
+
+function changeSearchEngine() {
+ let engine = Services.search.originalDefaultEngine;
+ if (engine.hidden)
+ engine.hidden = false;
+ Services.search.currentEngine = engine;
+
+ record(TELEMETRY_RESULT_ENUM.RESTORED_DEFAULT);
+
+ doSearch();
+}
+
+function recordPageClosed() {
+ record(TELEMETRY_RESULT_ENUM.CLOSED_PAGE);
+}
diff --git a/browser/components/search/content/searchReset.xhtml b/browser/components/search/content/searchReset.xhtml
new file mode 100644
index 000000000..b851dd383
--- /dev/null
+++ b/browser/components/search/content/searchReset.xhtml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % searchresetDTD SYSTEM "chrome://browser/locale/aboutSearchReset.dtd">
+ %searchresetDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <head>
+ <title>&searchreset.tabtitle;</title>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://global/skin/in-content/info-pages.css"/>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/skin/searchReset.css"/>
+ <link rel="icon" type="image/png"
+ href="chrome://browser/skin/favicon-search-16.svg"/>
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://browser/content/search/searchReset.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <div class="container">
+ <div class="title">
+ <h1 class="title-text">&searchreset.pageTitle;</h1>
+ </div>
+
+ <div class="description">
+ <p>&searchreset.pageInfo1;</p>
+ <p>&searchreset.selector.label;<span id="defaultEngine"/></p>
+
+ <p>&searchreset.beforelink.pageInfo2;<a id="linkSettingsPage" href="about:preferences#search">&searchreset.link.pageInfo2;</a>&searchreset.afterlink.pageInfo2;</p>
+ </div>
+
+ <div class="button-container">
+ <xul:button id="searchResetKeepCurrent"
+ label="&searchreset.noChangeButton;"
+ accesskey="&searchreset.noChangeButton.access;"
+ oncommand="keepCurrentEngine();"/>
+ <xul:button class="primary"
+ id="searchResetChangeEngine"
+ label="&searchreset.changeEngineButton;"
+ accesskey="&searchreset.changeEngineButton.access;"
+ oncommand="changeSearchEngine();"/>
+ </div>
+ </div>
+
+ </body>
+</html>
diff --git a/browser/components/search/content/searchbarBindings.css b/browser/components/search/content/searchbarBindings.css
new file mode 100644
index 000000000..0429e8811
--- /dev/null
+++ b/browser/components/search/content/searchbarBindings.css
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.searchbar-textbox {
+ -moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox");
+}
+
+.search-one-offs {
+ -moz-binding: url("chrome://browser/content/search/search.xml#search-one-offs");
+}
+
+.search-setting-button[compact=true],
+.search-setting-button-compact:not([compact=true]) {
+ display: none;
+}
diff --git a/browser/components/search/jar.mn b/browser/components/search/jar.mn
new file mode 100644
index 000000000..089ec4bb9
--- /dev/null
+++ b/browser/components/search/jar.mn
@@ -0,0 +1,9 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/search/search.xml (content/search.xml)
+ content/browser/search/searchbarBindings.css (content/searchbarBindings.css)
+ content/browser/search/searchReset.xhtml (content/searchReset.xhtml)
+ content/browser/search/searchReset.js (content/searchReset.js)
diff --git a/browser/components/search/moz.build b/browser/components/search/moz.build
new file mode 100644
index 000000000..618cd7657
--- /dev/null
+++ b/browser/components/search/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser.ini',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Search')
diff --git a/browser/components/search/test/.eslintrc.js b/browser/components/search/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/search/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/search/test/426329.xml b/browser/components/search/test/426329.xml
new file mode 100644
index 000000000..e4545cc77
--- /dev/null
+++ b/browser/components/search/test/426329.xml
@@ -0,0 +1,11 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
+ xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Bug 426329</ShortName>
+ <Description>426329 Search</Description>
+ <InputEncoding>utf-8</InputEncoding>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+ <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/test.html">
+ <Param name="test" value="{searchTerms}"/>
+ </Url>
+ <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/test.html</moz:SearchForm>
+</OpenSearchDescription>
diff --git a/browser/components/search/test/483086-1.xml b/browser/components/search/test/483086-1.xml
new file mode 100644
index 000000000..9dbba4886
--- /dev/null
+++ b/browser/components/search/test/483086-1.xml
@@ -0,0 +1,10 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
+ xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>483086a</ShortName>
+ <Description>Bug 483086 Test 1</Description>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+ <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+ <Param name="test" value="{searchTerms}"/>
+ </Url>
+ <moz:SearchForm>foo://example.com</moz:SearchForm>
+</OpenSearchDescription>
diff --git a/browser/components/search/test/483086-2.xml b/browser/components/search/test/483086-2.xml
new file mode 100644
index 000000000..f130b9068
--- /dev/null
+++ b/browser/components/search/test/483086-2.xml
@@ -0,0 +1,10 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
+ xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>483086b</ShortName>
+ <Description>Bug 483086 Test 2</Description>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+ <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+ <Param name="test" value="{searchTerms}"/>
+ </Url>
+ <moz:SearchForm>http://example.com</moz:SearchForm>
+</OpenSearchDescription>
diff --git a/browser/components/search/test/browser.ini b/browser/components/search/test/browser.ini
new file mode 100644
index 000000000..f1070264d
--- /dev/null
+++ b/browser/components/search/test/browser.ini
@@ -0,0 +1,44 @@
+[DEFAULT]
+support-files =
+ 426329.xml
+ 483086-1.xml
+ 483086-2.xml
+ head.js
+ opensearch.html
+ test.html
+ testEngine.xml
+ testEngine_diacritics.xml
+ testEngine_dupe.xml
+ testEngine_mozsearch.xml
+ webapi.html
+
+[browser_426329.js]
+[browser_483086.js]
+[browser_addEngine.js]
+[browser_amazon.js]
+[browser_amazon_behavior.js]
+[browser_bing.js]
+[browser_bing_behavior.js]
+[browser_contextmenu.js]
+[browser_contextSearchTabPosition.js]
+skip-if = os == "mac" # bug 967013
+[browser_google.js]
+[browser_google_codes.js]
+[browser_google_behavior.js]
+[browser_healthreport.js]
+[browser_hiddenOneOffs_cleanup.js]
+[browser_hiddenOneOffs_diacritics.js]
+[browser_oneOffContextMenu.js]
+[browser_oneOffContextMenu_setDefault.js]
+[browser_oneOffHeader.js]
+[browser_private_search_perwindowpb.js]
+[browser_yahoo.js]
+[browser_yahoo_behavior.js]
+[browser_abouthome_behavior.js]
+skip-if = true # Bug ??????, Bug 1100301 - leaks windows until shutdown when --run-by-dir
+[browser_aboutSearchReset.js]
+[browser_searchbar_openpopup.js]
+skip-if = os == "linux" # Linux has different focus behaviours.
+[browser_searchbar_keyboard_navigation.js]
+[browser_searchbar_smallpanel_keyboard_navigation.js]
+[browser_webapi.js]
diff --git a/browser/components/search/test/browser_426329.js b/browser/components/search/test/browser_426329.js
new file mode 100644
index 000000000..d9cbd3f7a
--- /dev/null
+++ b/browser/components/search/test/browser_426329.js
@@ -0,0 +1,250 @@
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+function expectedURL(aSearchTerms) {
+ const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html";
+ var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
+ getService(Ci.nsITextToSubURI);
+ var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
+ return ENGINE_HTML_BASE + "?test=" + searchArg;
+}
+
+function simulateClick(aEvent, aTarget) {
+ var event = document.createEvent("MouseEvent");
+ var ctrlKeyArg = aEvent.ctrlKey || false;
+ var altKeyArg = aEvent.altKey || false;
+ var shiftKeyArg = aEvent.shiftKey || false;
+ var metaKeyArg = aEvent.metaKey || false;
+ var buttonArg = aEvent.button || 0;
+ event.initMouseEvent("click", true, true, window,
+ 0, 0, 0, 0, 0,
+ ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
+ buttonArg, null);
+ aTarget.dispatchEvent(event);
+}
+
+// modified from toolkit/components/satchel/test/test_form_autocomplete.html
+function checkMenuEntries(expectedValues) {
+ var actualValues = getMenuEntries();
+ is(actualValues.length, expectedValues.length, "Checking length of expected menu");
+ for (var i = 0; i < expectedValues.length; i++)
+ is(actualValues[i], expectedValues[i], "Checking menu entry #" + i);
+}
+
+function getMenuEntries() {
+ var entries = [];
+ var autocompleteMenu = searchBar.textbox.popup;
+ // Could perhaps pull values directly from the controller, but it seems
+ // more reliable to test the values that are actually in the tree?
+ var column = autocompleteMenu.tree.columns[0];
+ var numRows = autocompleteMenu.tree.view.rowCount;
+ for (var i = 0; i < numRows; i++) {
+ entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
+ }
+ return entries;
+}
+
+function countEntries(name, value) {
+ return new Promise(resolve => {
+ let count = 0;
+ let obj = name && value ? {fieldname: name, value: value} : {};
+ FormHistory.count(obj,
+ { handleResult: function(result) { count = result; },
+ handleError: function(error) { throw error; },
+ handleCompletion: function(reason) {
+ if (!reason) {
+ resolve(count);
+ }
+ }
+ });
+ });
+}
+
+var searchBar;
+var searchButton;
+var searchEntries = ["test"];
+function promiseSetEngine() {
+ return new Promise(resolve => {
+ var ss = Services.search;
+
+ function observer(aSub, aTopic, aData) {
+ switch (aData) {
+ case "engine-added":
+ var engine = ss.getEngineByName("Bug 426329");
+ ok(engine, "Engine was added.");
+ ss.currentEngine = engine;
+ break;
+ case "engine-current":
+ ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
+ searchBar = BrowserSearch.searchBar;
+ searchButton = document.getAnonymousElementByAttribute(searchBar,
+ "anonid", "search-go-button");
+ ok(searchButton, "got search-go-button");
+ searchBar.value = "test";
+
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+ resolve();
+ break;
+ }
+ }
+
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+ ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
+ null, "data:image/x-icon,%00", false);
+ });
+}
+
+function promiseRemoveEngine() {
+ return new Promise(resolve => {
+ var ss = Services.search;
+
+ function observer(aSub, aTopic, aData) {
+ if (aData == "engine-removed") {
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+ resolve();
+ }
+ }
+
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+ var engine = ss.getEngineByName("Bug 426329");
+ ss.removeEngine(engine);
+ });
+}
+
+
+var preSelectedBrowser;
+var preTabNo;
+function* prepareTest() {
+ preSelectedBrowser = gBrowser.selectedBrowser;
+ preTabNo = gBrowser.tabs.length;
+ searchBar = BrowserSearch.searchBar;
+
+ yield SimpleTest.promiseFocus();
+
+ if (document.activeElement == searchBar)
+ return;
+
+ let focusPromise = BrowserTestUtils.waitForEvent(searchBar, "focus");
+ gURLBar.focus();
+ searchBar.focus();
+ yield focusPromise;
+}
+
+add_task(function* testSetupEngine() {
+ yield promiseSetEngine();
+});
+
+add_task(function* testReturn() {
+ yield* prepareTest();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ is(gBrowser.tabs.length, preTabNo, "Return key did not open new tab");
+ is(gBrowser.currentURI.spec, expectedURL(searchBar.value), "testReturn opened correct search page");
+});
+
+add_task(function* testAltReturn() {
+ yield* prepareTest();
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ EventUtils.synthesizeKey("VK_RETURN", { altKey: true });
+ });
+
+ is(gBrowser.tabs.length, preTabNo + 1, "Alt+Return key added new tab");
+ is(gBrowser.currentURI.spec, expectedURL(searchBar.value), "testAltReturn opened correct search page");
+});
+
+// Shift key has no effect for now, so skip it
+add_task(function* testShiftAltReturn() {
+ return;
+ /*
+ yield* prepareTest();
+
+ let url = expectedURL(searchBar.value);
+
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
+ yield newTabPromise;
+
+ is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
+ is(gBrowser.currentURI.spec, url, "testShiftAltReturn opened correct search page");
+ */
+});
+
+add_task(function* testLeftClick() {
+ yield* prepareTest();
+ simulateClick({ button: 0 }, searchButton);
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
+ is(gBrowser.currentURI.spec, expectedURL(searchBar.value), "testLeftClick opened correct search page");
+});
+
+add_task(function* testMiddleClick() {
+ yield* prepareTest();
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ simulateClick({ button: 1 }, searchButton);
+ });
+ is(gBrowser.tabs.length, preTabNo + 1, "MiddleClick added new tab");
+ is(gBrowser.currentURI.spec, expectedURL(searchBar.value), "testMiddleClick opened correct search page");
+});
+
+add_task(function* testShiftMiddleClick() {
+ yield* prepareTest();
+
+ let url = expectedURL(searchBar.value);
+
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url);
+ simulateClick({ button: 1, shiftKey: true }, searchButton);
+ let newTab = yield newTabPromise;
+
+ is(gBrowser.tabs.length, preTabNo + 1, "Shift+MiddleClick added new tab");
+ is(newTab.linkedBrowser.currentURI.spec, url, "testShiftMiddleClick opened correct search page");
+});
+
+add_task(function* testRightClick() {
+ preTabNo = gBrowser.tabs.length;
+ gBrowser.selectedBrowser.loadURI("about:blank");
+ yield new Promise(resolve => {
+ setTimeout(function() {
+ is(gBrowser.tabs.length, preTabNo, "RightClick did not open new tab");
+ is(gBrowser.currentURI.spec, "about:blank", "RightClick did nothing");
+ resolve();
+ }, 5000);
+ simulateClick({ button: 2 }, searchButton);
+ });
+ // The click in the searchbox focuses it, which opens the suggestion
+ // panel. Clean up after ourselves.
+ searchBar.textbox.popup.hidePopup();
+});
+
+add_task(function* testSearchHistory() {
+ var textbox = searchBar._textbox;
+ for (var i = 0; i < searchEntries.length; i++) {
+ let count = yield countEntries(textbox.getAttribute("autocompletesearchparam"), searchEntries[i]);
+ ok(count > 0, "form history entry '" + searchEntries[i] + "' should exist");
+ }
+});
+
+add_task(function* testAutocomplete() {
+ var popup = searchBar.textbox.popup;
+ let popupShownPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ searchBar.textbox.showHistoryPopup();
+ yield popupShownPromise;
+ checkMenuEntries(searchEntries);
+});
+
+add_task(function* testClearHistory() {
+ let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
+ ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
+ controller.doCommand("cmd_clearhistory");
+ let count = yield countEntries();
+ ok(count == 0, "History cleared");
+});
+
+add_task(function* asyncCleanup() {
+ searchBar.value = "";
+ while (gBrowser.tabs.length != 1) {
+ gBrowser.removeTab(gBrowser.tabs[0], {animate: false});
+ }
+ gBrowser.selectedBrowser.loadURI("about:blank");
+ yield promiseRemoveEngine();
+});
diff --git a/browser/components/search/test/browser_483086.js b/browser/components/search/test/browser_483086.js
new file mode 100644
index 000000000..208add867
--- /dev/null
+++ b/browser/components/search/test/browser_483086.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+var gSS = Services.search;
+
+function test() {
+ waitForExplicitFinish();
+
+ function observer(aSubject, aTopic, aData) {
+ switch (aData) {
+ case "engine-added":
+ let engine = gSS.getEngineByName("483086a");
+ ok(engine, "Test engine 1 installed");
+ isnot(engine.searchForm, "foo://example.com",
+ "Invalid SearchForm URL dropped");
+ gSS.removeEngine(engine);
+ break;
+ case "engine-removed":
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+ test2();
+ break;
+ }
+ }
+
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+ gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-1.xml",
+ null, "data:image/x-icon;%00", false);
+}
+
+function test2() {
+ function observer(aSubject, aTopic, aData) {
+ switch (aData) {
+ case "engine-added":
+ let engine = gSS.getEngineByName("483086b");
+ ok(engine, "Test engine 2 installed");
+ is(engine.searchForm, "http://example.com", "SearchForm is correct");
+ gSS.removeEngine(engine);
+ break;
+ case "engine-removed":
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+ finish();
+ break;
+ }
+ }
+
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+ gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-2.xml",
+ null, "data:image/x-icon;%00", false);
+}
diff --git a/browser/components/search/test/browser_aboutSearchReset.js b/browser/components/search/test/browser_aboutSearchReset.js
new file mode 100644
index 000000000..64376d6da
--- /dev/null
+++ b/browser/components/search/test/browser_aboutSearchReset.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TELEMETRY_RESULT_ENUM = {
+ RESTORED_DEFAULT: 0,
+ KEPT_CURRENT: 1,
+ CHANGED_ENGINE: 2,
+ CLOSED_PAGE: 3,
+ OPENED_SETTINGS: 4
+};
+
+const kSearchStr = "a search";
+const kSearchPurpose = "searchbar";
+
+const kTestEngine = "testEngine.xml";
+
+function checkTelemetryRecords(expectedValue) {
+ let histogram = Services.telemetry.getHistogramById("SEARCH_RESET_RESULT");
+ let snapshot = histogram.snapshot();
+ // The probe is declared with 5 values, but we get 6 back from .counts
+ let expectedCounts = [0, 0, 0, 0, 0, 0];
+ if (expectedValue != null) {
+ expectedCounts[expectedValue] = 1;
+ }
+ Assert.deepEqual(snapshot.counts, expectedCounts,
+ "histogram has expected content");
+ histogram.clear();
+}
+
+function promiseStoppedLoad(expectedURL) {
+ return new Promise(resolve => {
+ let browser = gBrowser.selectedBrowser;
+ let original = browser.loadURIWithFlags;
+ browser.loadURIWithFlags = function(URI) {
+ if (URI == expectedURL) {
+ browser.loadURIWithFlags = original;
+ ok(true, "loaded expected url: " + URI);
+ resolve();
+ return;
+ }
+
+ original.apply(browser, arguments);
+ };
+ });
+}
+
+var gTests = [
+
+{
+ desc: "Test the 'Keep Current Settings' button.",
+ run: function* () {
+ let engine = yield promiseNewEngine(kTestEngine, {setAsCurrent: true});
+
+ let expectedURL = engine.
+ getSubmission(kSearchStr, null, kSearchPurpose).
+ uri.spec;
+
+ let rawEngine = engine.wrappedJSObject;
+ let initialHash = rawEngine.getAttr("loadPathHash");
+ rawEngine.setAttr("loadPathHash", "broken");
+
+ let loadPromise = promiseStoppedLoad(expectedURL);
+ gBrowser.contentDocument.getElementById("searchResetKeepCurrent").click();
+ yield loadPromise;
+
+ is(engine, Services.search.currentEngine,
+ "the custom engine is still default");
+ is(rawEngine.getAttr("loadPathHash"), initialHash,
+ "the loadPathHash has been fixed");
+
+ checkTelemetryRecords(TELEMETRY_RESULT_ENUM.KEPT_CURRENT);
+ }
+},
+
+{
+ desc: "Test the 'Restore Search Defaults' button.",
+ run: function* () {
+ let currentEngine = Services.search.currentEngine;
+ let originalEngine = Services.search.originalDefaultEngine;
+ let doc = gBrowser.contentDocument;
+ let defaultEngineSpan = doc.getElementById("defaultEngine");
+ is(defaultEngineSpan.textContent, originalEngine.name,
+ "the name of the original default engine is displayed");
+
+ let expectedURL = originalEngine.
+ getSubmission(kSearchStr, null, kSearchPurpose).
+ uri.spec;
+ let loadPromise = promiseStoppedLoad(expectedURL);
+ let button = doc.getElementById("searchResetChangeEngine");
+ is(doc.activeElement, button,
+ "the 'Change Search Engine' button is focused");
+ button.click();
+ yield loadPromise;
+
+ is(originalEngine, Services.search.currentEngine,
+ "the default engine is back to the original one");
+
+ checkTelemetryRecords(TELEMETRY_RESULT_ENUM.RESTORED_DEFAULT);
+ Services.search.currentEngine = currentEngine;
+ }
+},
+
+{
+ desc: "Click the settings link.",
+ run: function* () {
+ let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser,
+ false,
+ "about:preferences#search")
+ gBrowser.contentDocument.getElementById("linkSettingsPage").click();
+ yield loadPromise;
+
+ checkTelemetryRecords(TELEMETRY_RESULT_ENUM.OPENED_SETTINGS);
+ }
+},
+
+{
+ desc: "Load another page without clicking any of the buttons.",
+ run: function* () {
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "about:mozilla");
+
+ checkTelemetryRecords(TELEMETRY_RESULT_ENUM.CLOSED_PAGE);
+ }
+},
+
+];
+
+function test()
+{
+ waitForExplicitFinish();
+ Task.spawn(function* () {
+ let oldCanRecord = Services.telemetry.canRecordExtended;
+ Services.telemetry.canRecordExtended = true;
+ checkTelemetryRecords();
+
+ for (let test of gTests) {
+ info(test.desc);
+
+ // Create a tab to run the test.
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // Start loading about:searchreset and wait for it to complete.
+ let url = "about:searchreset?data=" + encodeURIComponent(kSearchStr) +
+ "&purpose=" + kSearchPurpose;
+ yield promiseTabLoadEvent(tab, url);
+
+ info("Running test");
+ yield test.run();
+
+ info("Cleanup");
+ gBrowser.removeCurrentTab();
+ }
+
+ Services.telemetry.canRecordExtended = oldCanRecord;
+ }).then(finish, ex => {
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
diff --git a/browser/components/search/test/browser_abouthome_behavior.js b/browser/components/search/test/browser_abouthome_behavior.js
new file mode 100644
index 000000000..3291b41f4
--- /dev/null
+++ b/browser/components/search/test/browser_abouthome_behavior.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test home page search for all plugin URLs
+ */
+
+"use strict";
+
+function test() {
+ // Bug 992270: Ignore uncaught about:home exceptions (related to snippets from IndexedDB)
+ ignoreAllUncaughtExceptions(true);
+
+ let previouslySelectedEngine = Services.search.currentEngine;
+
+ function replaceUrl(base) {
+ return base;
+ }
+
+ let gMutationObserver = null;
+
+ function verify_about_home_search(engine_name) {
+ let engine = Services.search.getEngineByName(engine_name);
+ ok(engine, engine_name + " is installed");
+
+ Services.search.currentEngine = engine;
+
+ // load about:home, but remove the listener first so it doesn't
+ // get in the way
+ gBrowser.removeProgressListener(listener);
+ gBrowser.loadURI("about:home");
+ info("Waiting for about:home load");
+ tab.linkedBrowser.addEventListener("load", function load(event) {
+ if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ tab.linkedBrowser.removeEventListener("load", load, true);
+
+ // Observe page setup
+ let doc = gBrowser.contentDocument;
+ gMutationObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "searchEngineName") {
+ // Re-add the listener, and perform a search
+ gBrowser.addProgressListener(listener);
+ gMutationObserver.disconnect()
+ gMutationObserver = null;
+ executeSoon(function() {
+ doc.getElementById("searchText").value = "foo";
+ doc.getElementById("searchSubmit").click();
+ });
+ }
+ }
+ });
+ gMutationObserver.observe(doc.documentElement, { attributes: true });
+ }, true);
+ }
+ waitForExplicitFinish();
+
+ let gCurrTest;
+ let gTests = [
+ {
+ name: "Search with Bing from about:home",
+ searchURL: replaceUrl("http://www.bing.com/search?q=foo&pc=MOZI&form=MOZSPG"),
+ run: function () {
+ verify_about_home_search("Bing");
+ }
+ },
+ {
+ name: "Search with Yahoo from about:home",
+ searchURL: replaceUrl("https://search.yahoo.com/search?p=foo&ei=UTF-8&fr=moz35"),
+ run: function () {
+ verify_about_home_search("Yahoo");
+ }
+ },
+ {
+ name: "Search with Google from about:home",
+ searchURL: replaceUrl("https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8"),
+ run: function () {
+ verify_about_home_search("Google");
+ }
+ },
+ {
+ name: "Search with Amazon.com from about:home",
+ searchURL: replaceUrl("https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search"),
+ run: function () {
+ verify_about_home_search("Amazon.com");
+ }
+ }
+ ];
+
+ function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ info("Running : " + gCurrTest.name);
+ executeSoon(gCurrTest.run);
+ } else {
+ // Make sure we listen again for uncaught exceptions in the next test or cleanup.
+ executeSoon(finish);
+ }
+ }
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onStateChange(webProgress, req, flags, status) {
+ info("onStateChange");
+ // Only care about top-level document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart) || !webProgress.isTopLevel)
+ return;
+
+ if (req.originalURI.spec == "about:blank")
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+
+ executeSoon(nextTest);
+ }
+ }
+
+ registerCleanupFunction(function () {
+ Services.search.currentEngine = previouslySelectedEngine;
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ if (gMutationObserver)
+ gMutationObserver.disconnect();
+ });
+
+ tab.linkedBrowser.addEventListener("load", function load() {
+ tab.linkedBrowser.removeEventListener("load", load, true);
+ gBrowser.addProgressListener(listener);
+ nextTest();
+ }, true);
+}
diff --git a/browser/components/search/test/browser_addEngine.js b/browser/components/search/test/browser_addEngine.js
new file mode 100644
index 000000000..b971ea5f7
--- /dev/null
+++ b/browser/components/search/test/browser_addEngine.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/. */
+var gSS = Services.search;
+
+function observer(aSubject, aTopic, aData) {
+ if (!gCurrentTest) {
+ info("Observer called with no test active");
+ return;
+ }
+
+ let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + aData + " for " + engine.name);
+ let method;
+ switch (aData) {
+ case "engine-added":
+ if (gCurrentTest.added)
+ method = "added"
+ break;
+ case "engine-current":
+ if (gCurrentTest.current)
+ method = "current";
+ break;
+ case "engine-removed":
+ if (gCurrentTest.removed)
+ method = "removed";
+ break;
+ }
+
+ if (method)
+ gCurrentTest[method](engine);
+}
+
+function checkEngine(checkObj, engineObj) {
+ info("Checking engine");
+ for (var prop in checkObj)
+ is(checkObj[prop], engineObj[prop], prop + " is correct");
+}
+
+var gTests = [
+ {
+ name: "opensearch install",
+ engine: {
+ name: "Foo",
+ alias: null,
+ description: "Foo Search",
+ searchForm: "http://mochi.test:8888/browser/browser/components/search/test/"
+ },
+ run: function () {
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+
+ gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
+ null, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC",
+ false);
+ },
+ added: function (engine) {
+ ok(engine, "engine was added.");
+
+ checkEngine(this.engine, engine);
+
+ let engineFromSS = gSS.getEngineByName(this.engine.name);
+ is(engine, engineFromSS, "engine is obtainable via getEngineByName");
+
+ let aEngine = gSS.getEngineByAlias("fooalias");
+ ok(!aEngine, "Alias was not parsed from engine description");
+
+ gSS.currentEngine = engine;
+ },
+ current: function (engine) {
+ let currentEngine = gSS.currentEngine;
+ is(engine, currentEngine, "engine is current");
+ is(engine.name, this.engine.name, "current engine was changed successfully");
+
+ gSS.removeEngine(engine);
+ },
+ removed: function (engine) {
+ // Remove the observer before calling the currentEngine getter,
+ // as that getter will set the currentEngine to the original default
+ // which will trigger a notification causing the test to loop over all
+ // engines.
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+
+ let currentEngine = gSS.currentEngine;
+ ok(currentEngine, "An engine is present.");
+ isnot(currentEngine.name, this.engine.name, "Current engine reset after removal");
+
+ nextTest();
+ }
+ }
+];
+
+var gCurrentTest = null;
+function nextTest() {
+ if (gTests.length) {
+ gCurrentTest = gTests.shift();
+ info("Running " + gCurrentTest.name);
+ gCurrentTest.run();
+ } else
+ executeSoon(finish);
+}
+
+function test() {
+ waitForExplicitFinish();
+ nextTest();
+}
diff --git a/browser/components/search/test/browser_amazon.js b/browser/components/search/test/browser_amazon.js
new file mode 100644
index 000000000..965a3dcf8
--- /dev/null
+++ b/browser/components/search/test/browser_amazon.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Amazon search plugin URLs
+ */
+
+"use strict";
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+function test() {
+ let engine = Services.search.getEngineByName("Amazon.com");
+ ok(engine, "Amazon.com");
+
+ let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&ie=UTF-8&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base, "Check search URL for 'foo'");
+
+ // Check search suggestion URL.
+ url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
+ is(url, "https://completion.amazon.com/search/complete?q=foo&search-alias=aps&mkt=1", "Check search suggestion URL for 'foo'");
+
+ // Check all other engine properties.
+ const EXPECTED_ENGINE = {
+ name: "Amazon.com",
+ alias: null,
+ description: "Amazon.com Search",
+ searchForm: "https://www.amazon.com/exec/obidos/external-search/?field-keywords=&ie=UTF-8&mode=blended&tag=mozilla-20&sourceid=Mozilla-search",
+ hidden: false,
+ wrappedJSObject: {
+ queryCharset: "UTF-8",
+ "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==",
+ _urls : [
+ {
+ type: "application/x-suggestions+json",
+ method: "GET",
+ template: "https://completion.amazon.com/search/complete?q={searchTerms}&search-alias=aps&mkt=1",
+ params: "",
+ },
+ {
+ type: "text/html",
+ method: "GET",
+ template: "https://www.amazon.com/exec/obidos/external-search/",
+ params: [
+ {
+ name: "field-keywords",
+ value: "{searchTerms}",
+ purpose: undefined,
+ },
+ {
+ name: "ie",
+ value: "{inputEncoding}",
+ purpose: undefined,
+ },
+ {
+ name: "mode",
+ value: "blended",
+ purpose: undefined,
+ },
+ {
+ name: "tag",
+ value: "mozilla-20",
+ purpose: undefined,
+ },
+ {
+ name: "sourceid",
+ value: "Mozilla-search",
+ purpose: undefined,
+ },
+ ],
+ mozparams: {},
+ },
+ ],
+ },
+ };
+
+ isSubObjectOf(EXPECTED_ENGINE, engine, "Amazon");
+}
diff --git a/browser/components/search/test/browser_amazon_behavior.js b/browser/components/search/test/browser_amazon_behavior.js
new file mode 100644
index 000000000..22d16581a
--- /dev/null
+++ b/browser/components/search/test/browser_amazon_behavior.js
@@ -0,0 +1,166 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Amazon search plugin URLs
+ */
+
+"use strict";
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+
+function test() {
+ let engine = Services.search.getEngineByName("Amazon.com");
+ ok(engine, "Amazon is installed");
+
+ let previouslySelectedEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ engine.alias = "a";
+
+ let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&ie=UTF-8&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base, "Check search URL for 'foo'");
+
+ waitForExplicitFinish();
+
+ var gCurrTest;
+ var gTests = [
+ {
+ name: "context menu search",
+ searchURL: base,
+ run: function () {
+ // Simulate a contextmenu search
+ // FIXME: This is a bit "low-level"...
+ BrowserSearch.loadSearch("foo", false, "contextmenu");
+ }
+ },
+ {
+ name: "keyword search",
+ searchURL: base,
+ run: function () {
+ gURLBar.value = "? foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "keyword search",
+ searchURL: base,
+ run: function () {
+ gURLBar.value = "a foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "search bar search",
+ searchURL: base,
+ run: function () {
+ let sb = BrowserSearch.searchBar;
+ sb.focus();
+ sb.value = "foo";
+ registerCleanupFunction(function () {
+ sb.value = "";
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "new tab search",
+ searchURL: base,
+ run: function () {
+ function doSearch(doc) {
+ // Re-add the listener, and perform a search
+ gBrowser.addProgressListener(listener);
+ doc.getElementById("newtab-search-text").value = "foo";
+ doc.getElementById("newtab-search-submit").click();
+ }
+
+ // load about:newtab, but remove the listener first so it doesn't
+ // get in the way
+ gBrowser.removeProgressListener(listener);
+ gBrowser.loadURI("about:newtab");
+ info("Waiting for about:newtab load");
+ tab.linkedBrowser.addEventListener("load", function load(event) {
+ if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ tab.linkedBrowser.removeEventListener("load", load, true);
+
+ // Observe page setup
+ let win = gBrowser.contentWindow;
+ if (win.gSearch.currentEngineName ==
+ Services.search.currentEngine.name) {
+ doSearch(win.document);
+ }
+ else {
+ info("Waiting for newtab search init");
+ win.addEventListener("ContentSearchService", function done(event) {
+ info("Got newtab search event " + event.detail.type);
+ if (event.detail.type == "State") {
+ win.removeEventListener("ContentSearchService", done);
+ // Let gSearch respond to the event before continuing.
+ executeSoon(() => doSearch(win.document));
+ }
+ });
+ }
+ }, true);
+ }
+ }
+ ];
+
+ function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ info("Running : " + gCurrTest.name);
+ executeSoon(gCurrTest.run);
+ } else {
+ finish();
+ }
+ }
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onStateChange(webProgress, req, flags, status) {
+ info("onStateChange");
+ // Only care about top-level document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart) || !webProgress.isTopLevel)
+ return;
+
+ if (req.originalURI.spec == "about:blank")
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+
+ executeSoon(nextTest);
+ }
+ }
+
+ registerCleanupFunction(function () {
+ engine.alias = undefined;
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ Services.search.currentEngine = previouslySelectedEngine;
+ });
+
+ tab.linkedBrowser.addEventListener("load", function load() {
+ tab.linkedBrowser.removeEventListener("load", load, true);
+ gBrowser.addProgressListener(listener);
+ nextTest();
+ }, true);
+}
diff --git a/browser/components/search/test/browser_bing.js b/browser/components/search/test/browser_bing.js
new file mode 100644
index 000000000..3a41ae0ac
--- /dev/null
+++ b/browser/components/search/test/browser_bing.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Bing search plugin URLs
+ */
+
+"use strict";
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+function test() {
+ let engine = Services.search.getEngineByName("Bing");
+ ok(engine, "Bing");
+
+ let base = "https://www.bing.com/search?q=foo&pc=MOZI";
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base + "&form=MOZSBR", "Check search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
+ is(url, base + "&form=MOZCON", "Check context menu search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "keyword").uri.spec;
+ is(url, base + "&form=MOZLBR", "Check keyword search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "searchbar").uri.spec;
+ is(url, base + "&form=MOZSBR", "Check search bar search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "homepage").uri.spec;
+ is(url, base + "&form=MOZSPG", "Check homepage search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "newtab").uri.spec;
+ is(url, base + "&form=MOZTSB", "Check newtab search URL for 'foo'");
+
+ // Check search suggestion URL.
+ url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
+ is(url, "https://www.bing.com/osjson.aspx?query=foo&form=OSDJAS&language=" + getLocale(), "Check search suggestion URL for 'foo'");
+
+ // Check all other engine properties.
+ const EXPECTED_ENGINE = {
+ name: "Bing",
+ alias: null,
+ description: "Bing. Search by Microsoft.",
+ searchForm: "https://www.bing.com/search?q=&pc=MOZI&form=MOZSBR",
+ hidden: false,
+ wrappedJSObject: {
+ queryCharset: "UTF-8",
+ "_iconURL": "data:image/x-icon;base64,AAABAAIAICAAAAEACACoCAAAJgAAABAQAAABAAgAaAUAAM4IAAAoAAAAIAAAAEAAAAABAAgAAAAAAIAEAAAAAAAAAAAAAAABAAAAAAAAhIQMAI+PIwCXlzEAn59BAKioUwCurl8AtbVuAL6+fwDJyZMA0tKlANjYsgDe3r4A5eXMAOzs2QDy8uUA+fnzAP///wAAGi8AAC1QAAA/cAAAUZAAAGOwAAB2zwAAiPAAEZj/ADGm/wBRs/8AccH/AJHP/wCx3f8A0ev/AP///wAAAAAAACwvAABLUAAAaHAAAIaQAAClsAAAw88AAOHwABHv/wAx8f8AUfP/AHH1/wCR9/8Asfn/ANH7/wD///8AAAAAAAAvIQAAUDcAAHBMAACQYwAAsHkAAM+PAADwpgAR/7QAMf++AFH/yABx/9MAkf/cALH/5QDR//AA////AAAAAAAALw4AAFAYAABwIgAAkCwAALA2AADPQAAA8EoAEf9bADH/cQBR/4cAcf+dAJH/sgCx/8kA0f/fAP///wAAAAAAAi8AAARQAAAGcAAACJAAAAqwAAALzwAADvAAACD/EgA9/zEAW/9RAHn/cQCY/5EAtf+xANT/0QD///8AAAAAABQvAAAiUAAAMHAAAD2QAABMsAAAWc8AAGfwAAB4/xEAiv8xAJz/UQCu/3EAwP+RANL/sQDk/9EA////AAAAAAAmLwAAQFAAAFpwAAB0kAAAjrAAAKnPAADC8AAA0f8RANj/MQDe/1EA4/9xAOn/kQDv/7EA9v/RAP///wAAAAAALyYAAFBBAABwWwAAkHQAALCOAADPqQAA8MMAAP/SEQD/2DEA/91RAP/kcQD/6pEA//CxAP/20QD///8AAAAAAC8UAABQIgAAcDAAAJA+AACwTQAAz1sAAPBpAAD/eREA/4oxAP+dUQD/r3EA/8GRAP/SsQD/5dEA////AAAAAAAvAwAAUAQAAHAGAACQCQAAsAoAAM8MAADwDgAA/yASAP8+MQD/XFEA/3pxAP+XkQD/trEA/9TRAP///wAAAAAALwAOAFAAFwBwACEAkAArALAANgDPAEAA8ABJAP8RWgD/MXAA/1GGAP9xnAD/kbIA/7HIAP/R3wD///8AAAAAAC8AIABQADYAcABMAJAAYgCwAHgAzwCOAPAApAD/EbMA/zG+AP9RxwD/cdEA/5HcAP+x5QD/0fAA////AAAAAAAsAC8ASwBQAGkAcACHAJAApQCwAMQAzwDhAPAA8BH/APIx/wD0Uf8A9nH/APeR/wD5sf8A+9H/AP///wAAAAAAGwAvAC0AUAA/AHAAUgCQAGMAsAB2AM8AiADwAJkR/wCmMf8AtFH/AMJx/wDPkf8A3LH/AOvR/wD///8AAAAAAAgALwAOAFAAFQBwABsAkAAhALAAJgDPACwA8AA+Ef8AWDH/AHFR/wCMcf8AppH/AL+x/wDa0f8A////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBggCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAsQEA0HAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwkPEBAQEBALBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMEBAQEBAQEBAPCQMAAAAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAQEBAQEBAQDQgBAAAAAAAAAAAAAAAAAAAAAAABDRAQEBAQEBAQEBAQEA0GAQAAAAAAAAAAAAAAAAAAAAENEBAQDgcLEBAQEBAQEBALBAAAAAAAAAAAAAAAAAAAAQ0QEBANAQEHDRAQEBAQEBAOCAAAAAAAAAAAAAAAAAABDRAQEA4BAAACCA4QEBAQEBANAQAAAAAAAAAAAAAAAAENEBAQDgEAAAAABAsQEBAQEA0BAAAAAAAAAAAAAAAAAQ0QEBAOAQAAAAABBw4QEBAQDQEAAAAAAAAAAAAAAAABDRAQEA4BAAABBw0QEBAQEBANAQAAAAAAAAAAAAAAAAENEBAQDgEAAAcQEBAQEBAQEA0BAAAAAAAAAAAAAAAAAQ0QEBAOAQABCxAQEBAQEBANBwAAAAAAAAAAAAAAAAABDRAQEA4BAAQPEBAQEA0IBAEAAAAAAAAAAAAAAAAAAAENEBAQDgEACRAQDQkGAQAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAOAQILCgYCAAAAAAAAAAAAAAAAAAAAAAAAAAABDRAQEA4BAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENEBAQDgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRAQEA4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENEBAQDgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRAQEA0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENEA0IBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQcFAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAQAAAAIAAAAAEACAAAAAAAQAEAAAAAAAAAAAAAAAEAAAAAAACEhAAAjIwAAJKSFgCZmSgAqqpRALKyYgC+vnsAxsaKAM3NmQDU1KgA4ODBAOjozwDw8OEA+fnyAP///wD///8AAAAAAAAaLwAALVAAAD9wAABRkAAAY7AAAHbPAACI8AARmP8AMab/AFGz/wBxwf8Akc//ALHd/wDR6/8A////AAAAAAAALC8AAEtQAABocAAAhpAAAKWwAADDzwAA4fAAEe//ADHx/wBR8/8AcfX/AJH3/wCx+f8A0fv/AP///wAAAAAAAC8hAABQNwAAcEwAAJBjAACweQAAz48AAPCmABH/tAAx/74AUf/IAHH/0wCR/9wAsf/lANH/8AD///8AAAAAAAAvDgAAUBgAAHAiAACQLAAAsDYAAM9AAADwSgAR/1sAMf9xAFH/hwBx/50Akf+yALH/yQDR/98A////AAAAAAACLwAABFAAAAZwAAAIkAAACrAAAAvPAAAO8AAAIP8SAD3/MQBb/1EAef9xAJj/kQC1/7EA1P/RAP///wAAAAAAFC8AACJQAAAwcAAAPZAAAEywAABZzwAAZ/AAAHj/EQCK/zEAnP9RAK7/cQDA/5EA0v+xAOT/0QD///8AAAAAACYvAABAUAAAWnAAAHSQAACOsAAAqc8AAMLwAADR/xEA2P8xAN7/UQDj/3EA6f+RAO//sQD2/9EA////AAAAAAAvJgAAUEEAAHBbAACQdAAAsI4AAM+pAADwwwAA/9IRAP/YMQD/3VEA/+RxAP/qkQD/8LEA//bRAP///wAAAAAALxQAAFAiAABwMAAAkD4AALBNAADPWwAA8GkAAP95EQD/ijEA/51RAP+vcQD/wZEA/9KxAP/l0QD///8AAAAAAC8DAABQBAAAcAYAAJAJAACwCgAAzwwAAPAOAAD/IBIA/z4xAP9cUQD/enEA/5eRAP+2sQD/1NEA////AAAAAAAvAA4AUAAXAHAAIQCQACsAsAA2AM8AQADwAEkA/xFaAP8xcAD/UYYA/3GcAP+RsgD/scgA/9HfAP///wAAAAAALwAgAFAANgBwAEwAkABiALAAeADPAI4A8ACkAP8RswD/Mb4A/1HHAP9x0QD/kdwA/7HlAP/R8AD///8AAAAAACwALwBLAFAAaQBwAIcAkAClALAAxADPAOEA8ADwEf8A8jH/APRR/wD2cf8A95H/APmx/wD70f8A////AAAAAAAbAC8ALQBQAD8AcABSAJAAYwCwAHYAzwCIAPAAmRH/AKYx/wC0Uf8AwnH/AM+R/wDcsf8A69H/AP///wAAAAAACAAvAA4AUAAVAHAAGwCQACEAsAAmAM8ALADwAD4R/wBYMf8AcVH/AIxx/wCmkf8Av7H/ANrR/wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMAAAAAAAAAAAAAAAACBgwMBwIAAAAAAAAAAAAABg4ODg4MBgIAAAAAAAAAAAYODQoODg4KBAAAAAAAAAAGDgwDBQsODg4FAAAAAAAABg4MAgADCg4OBgAAAAAAAAYODAICCg4ODgYAAAAAAAAGDgwCBg4OCwcCAAAAAAAABg4MAwkIBAIAAAAAAAAAAAYODAICAAAAAAAAAAAAAAAGDgwCAAAAAAAAAAAAAAAABg4MAgAAAAAAAAAAAAAAAAYMCAEAAAAAAAAAAAAAAAACAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
+ _urls : [
+ {
+ type: "application/x-suggestions+json",
+ method: "GET",
+ template: "https://www.bing.com/osjson.aspx",
+ params: [
+ {
+ name: "query",
+ value: "{searchTerms}",
+ purpose: undefined,
+ },
+ {
+ name: "form",
+ value: "OSDJAS",
+ purpose: undefined,
+ },
+ {
+ name: "language",
+ value: "{moz:locale}",
+ purpose: undefined,
+ },
+ ],
+ },
+ {
+ type: "text/html",
+ method: "GET",
+ template: "https://www.bing.com/search",
+ params: [
+ {
+ name: "q",
+ value: "{searchTerms}",
+ purpose: undefined,
+ },
+ {
+ name: "pc",
+ value: "MOZI",
+ purpose: undefined,
+ },
+ {
+ name: "form",
+ value: "MOZCON",
+ purpose: "contextmenu",
+ },
+ {
+ name: "form",
+ value: "MOZSBR",
+ purpose: "searchbar",
+ },
+ {
+ name: "form",
+ value: "MOZSPG",
+ purpose: "homepage",
+ },
+ {
+ name: "form",
+ value: "MOZLBR",
+ purpose:"keyword",
+ },
+ {
+ name: "form",
+ value: "MOZTSB",
+ purpose: "newtab",
+ },
+ ],
+ mozparams: {},
+ },
+ ],
+ },
+ };
+
+ isSubObjectOf(EXPECTED_ENGINE, engine, "Bing");
+}
diff --git a/browser/components/search/test/browser_bing_behavior.js b/browser/components/search/test/browser_bing_behavior.js
new file mode 100644
index 000000000..bc9b187ec
--- /dev/null
+++ b/browser/components/search/test/browser_bing_behavior.js
@@ -0,0 +1,166 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Bing search plugin URLs
+ */
+
+"use strict";
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+
+function test() {
+ let engine = Services.search.getEngineByName("Bing");
+ ok(engine, "Bing is installed");
+
+ let previouslySelectedEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ engine.alias = "b";
+
+ let base = "https://www.bing.com/search?q=foo&pc=MOZI";
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base + "&form=MOZSBR", "Check search URL for 'foo'");
+
+ waitForExplicitFinish();
+
+ var gCurrTest;
+ var gTests = [
+ {
+ name: "context menu search",
+ searchURL: base + "&form=MOZCON",
+ run: function () {
+ // Simulate a contextmenu search
+ // FIXME: This is a bit "low-level"...
+ BrowserSearch.loadSearch("foo", false, "contextmenu");
+ }
+ },
+ {
+ name: "keyword search",
+ searchURL: base + "&form=MOZLBR",
+ run: function () {
+ gURLBar.value = "? foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "keyword search with alias",
+ searchURL: base + "&form=MOZLBR",
+ run: function () {
+ gURLBar.value = "b foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "search bar search",
+ searchURL: base + "&form=MOZSBR",
+ run: function () {
+ let sb = BrowserSearch.searchBar;
+ sb.focus();
+ sb.value = "foo";
+ registerCleanupFunction(function () {
+ sb.value = "";
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "new tab search",
+ searchURL: base + "&form=MOZTSB",
+ run: function () {
+ function doSearch(doc) {
+ // Re-add the listener, and perform a search
+ gBrowser.addProgressListener(listener);
+ doc.getElementById("newtab-search-text").value = "foo";
+ doc.getElementById("newtab-search-submit").click();
+ }
+
+ // load about:newtab, but remove the listener first so it doesn't
+ // get in the way
+ gBrowser.removeProgressListener(listener);
+ gBrowser.loadURI("about:newtab");
+ info("Waiting for about:newtab load");
+ tab.linkedBrowser.addEventListener("load", function load(event) {
+ if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ tab.linkedBrowser.removeEventListener("load", load, true);
+
+ // Observe page setup
+ let win = gBrowser.contentWindow;
+ if (win.gSearch.currentEngineName ==
+ Services.search.currentEngine.name) {
+ doSearch(win.document);
+ }
+ else {
+ info("Waiting for newtab search init");
+ win.addEventListener("ContentSearchService", function done(event) {
+ info("Got newtab search event " + event.detail.type);
+ if (event.detail.type == "State") {
+ win.removeEventListener("ContentSearchService", done);
+ // Let gSearch respond to the event before continuing.
+ executeSoon(() => doSearch(win.document));
+ }
+ });
+ }
+ }, true);
+ }
+ }
+ ];
+
+ function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ info("Running : " + gCurrTest.name);
+ executeSoon(gCurrTest.run);
+ } else {
+ finish();
+ }
+ }
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onStateChange(webProgress, req, flags, status) {
+ info("onStateChange");
+ // Only care about top-level document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart) || !webProgress.isTopLevel)
+ return;
+
+ if (req.originalURI.spec == "about:blank")
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+
+ executeSoon(nextTest);
+ }
+ }
+
+ registerCleanupFunction(function () {
+ engine.alias = undefined;
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ Services.search.currentEngine = previouslySelectedEngine;
+ });
+
+ tab.linkedBrowser.addEventListener("load", function load() {
+ tab.linkedBrowser.removeEventListener("load", load, true);
+ gBrowser.addProgressListener(listener);
+ nextTest();
+ }, true);
+}
diff --git a/browser/components/search/test/browser_contextSearchTabPosition.js b/browser/components/search/test/browser_contextSearchTabPosition.js
new file mode 100644
index 000000000..21a8c1130
--- /dev/null
+++ b/browser/components/search/test/browser_contextSearchTabPosition.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/. */
+
+add_task(function* test() {
+ yield SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]});
+ let engine = yield promiseNewEngine("testEngine.xml");
+ let histogramKey = "other-" + engine.name + ".contextmenu";
+ let numSearchesBefore = 0;
+
+ try {
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ if (histogramKey in hs) {
+ numSearchesBefore = hs[histogramKey].sum;
+ }
+ } catch (ex) {
+ // No searches performed yet, not a problem, |numSearchesBefore| is 0.
+ }
+
+ let tabs = [];
+ let tabsLoadedDeferred = new Deferred();
+
+ function tabAdded(event) {
+ let tab = event.target;
+ tabs.push(tab);
+
+ // We wait for the blank tab and the two context searches tabs to open.
+ if (tabs.length == 3) {
+ tabsLoadedDeferred.resolve();
+ }
+ }
+
+ let container = gBrowser.tabContainer;
+ container.addEventListener("TabOpen", tabAdded, false);
+
+ gBrowser.addTab("about:blank");
+ BrowserSearch.loadSearchFromContext("mozilla");
+ BrowserSearch.loadSearchFromContext("firefox");
+
+ // Wait for all the tabs to open.
+ yield tabsLoadedDeferred.promise;
+
+ is(tabs[0], gBrowser.tabs[3], "blank tab has been pushed to the end");
+ is(tabs[1], gBrowser.tabs[1], "first search tab opens next to the current tab");
+ is(tabs[2], gBrowser.tabs[2], "second search tab opens next to the first search tab");
+
+ container.removeEventListener("TabOpen", tabAdded, false);
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+
+ // Make sure that the context searches are correctly recorded.
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ Assert.ok(histogramKey in hs, "The histogram must contain the correct key");
+ Assert.equal(hs[histogramKey].sum, numSearchesBefore + 2,
+ "The histogram must contain the correct search count");
+});
+
+function Deferred() {
+ this.promise = new Promise((resolve, reject) => {
+ this.resolve = resolve;
+ this.reject = reject;
+ });
+}
diff --git a/browser/components/search/test/browser_contextmenu.js b/browser/components/search/test/browser_contextmenu.js
new file mode 100644
index 000000000..c485242b4
--- /dev/null
+++ b/browser/components/search/test/browser_contextmenu.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * * http://creativecommons.org/publicdomain/zero/1.0/ */
+/*
+ * Test searching for the selected text using the context menu
+ */
+
+add_task(function* () {
+ const ss = Services.search;
+ const ENGINE_NAME = "Foo";
+ var contextMenu;
+
+ // We want select events to be fired.
+ yield new Promise(resolve => SpecialPowers.pushPrefEnv({"set": [["dom.select_events.enabled", true]]}, resolve));
+
+ let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ let originalValue = envService.get("XPCSHELL_TEST_PROFILE_DIR");
+ envService.set("XPCSHELL_TEST_PROFILE_DIR", "1");
+
+ let url = "chrome://mochitests/content/browser/browser/components/search/test/";
+ let resProt = Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ let originalSubstitution = resProt.getSubstitution("search-plugins");
+ resProt.setSubstitution("search-plugins",
+ Services.io.newURI(url, null, null));
+
+ let searchDonePromise;
+ yield new Promise(resolve => {
+ function observer(aSub, aTopic, aData) {
+ switch (aData) {
+ case "engine-added":
+ var engine = ss.getEngineByName(ENGINE_NAME);
+ ok(engine, "Engine was added.");
+ ss.currentEngine = engine;
+ envService.set("XPCSHELL_TEST_PROFILE_DIR", originalValue);
+ resProt.setSubstitution("search-plugins", originalSubstitution);
+ break;
+ case "engine-current":
+ is(ss.currentEngine.name, ENGINE_NAME, "currentEngine set");
+ resolve();
+ break;
+ case "engine-removed":
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+ if (searchDonePromise) {
+ searchDonePromise();
+ }
+ break;
+ }
+ }
+
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+ ss.addEngine("resource://search-plugins/testEngine_mozsearch.xml",
+ null, "data:image/x-icon,%00", false);
+ });
+
+ contextMenu = document.getElementById("contentAreaContextMenu");
+ ok(contextMenu, "Got context menu XUL");
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/plain;charset=utf8,test%20search");
+
+ yield ContentTask.spawn(tab.linkedBrowser, "", function*() {
+ return new Promise(resolve => {
+ content.document.addEventListener("selectionchange", function selectionChanged() {
+ content.document.removeEventListener("selectionchange", selectionChanged);
+ resolve();
+ });
+ content.document.getSelection().selectAllChildren(content.document.body);
+ });
+ });
+
+ var eventDetails = { type: "contextmenu", button: 2 };
+
+ let popupPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ BrowserTestUtils.synthesizeMouseAtCenter("body", eventDetails, gBrowser.selectedBrowser);
+ yield popupPromise;
+
+ info("checkContextMenu");
+ var searchItem = contextMenu.getElementsByAttribute("id", "context-searchselect")[0];
+ ok(searchItem, "Got search context menu item");
+ is(searchItem.label, 'Search ' + ENGINE_NAME + ' for \u201ctest search\u201d', "Check context menu label");
+ is(searchItem.disabled, false, "Check that search context menu item is enabled");
+
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
+ searchItem.click();
+ });
+
+ is(gBrowser.currentURI.spec,
+ "http://mochi.test:8888/browser/browser/components/search/test/?test=test+search&ie=utf-8&channel=contextsearch",
+ "Checking context menu search URL");
+
+ contextMenu.hidePopup();
+
+ // Remove the tab opened by the search
+ gBrowser.removeCurrentTab();
+
+ yield new Promise(resolve => {
+ searchDonePromise = resolve;
+ ss.removeEngine(ss.currentEngine);
+ });
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/search/test/browser_google.js b/browser/components/search/test/browser_google.js
new file mode 100644
index 000000000..2b0cabea7
--- /dev/null
+++ b/browser/components/search/test/browser_google.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Google search plugin URLs
+ */
+
+"use strict";
+
+function test() {
+ let engine = Services.search.getEngineByName("Google");
+ ok(engine, "Google");
+
+ let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&client=firefox-b";
+ let keywordBase = base + "-ab";
+
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base, "Check search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
+ is(url, base, "Check context menu search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "keyword").uri.spec;
+ is(url, keywordBase, "Check keyword search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "searchbar").uri.spec;
+ is(url, base, "Check search bar search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "homepage").uri.spec;
+ is(url, base, "Check homepage search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "newtab").uri.spec;
+ is(url, base, "Check newtab search URL for 'foo'");
+
+ // Check search suggestion URL.
+ url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
+ is(url, "https://www.google.com/complete/search?client=firefox&q=foo", "Check search suggestion URL for 'foo'");
+
+ // Check result parsing and alternate domains.
+ let alternateBase = base.replace("www.google.com", "www.google.fr");
+ is(Services.search.parseSubmissionURL(base).terms, "foo",
+ "Check result parsing");
+ is(Services.search.parseSubmissionURL(alternateBase).terms, "foo",
+ "Check alternate domain");
+
+ // Check all other engine properties.
+ const EXPECTED_ENGINE = {
+ name: "Google",
+ alias: null,
+ description: "Google Search",
+ searchForm: "https://www.google.com/search?q=&ie=utf-8&oe=utf-8&client=firefox-b",
+ hidden: false,
+ wrappedJSObject: {
+ queryCharset: "UTF-8",
+ "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///zD9/f2W/f392P39/fn9/f35/f391/39/ZT+/v4uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Cf39/Zn///////////////////////////////////////////39/ZX///8IAAAAAAAAAAAAAAAA/v7+Cf39/cH/////+v35/7TZp/92ul3/WKs6/1iqOv9yuFn/rNWd//j79v///////f39v////wgAAAAAAAAAAP39/Zn/////7PXp/3G3WP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP+Or1j//vDo///////9/f2VAAAAAP///zD/////+vz5/3G3V/9TqDT/WKo6/6LQkf/U6cz/1urO/6rUm/+Zo0r/8IZB//adZ////v7///////7+/i79/f2Y/////4nWzf9Lqkj/Vqo4/9Xqzv///////////////////////ebY//SHRv/0hUL//NjD///////9/f2U/f392v////8sxPH/Ebzt/43RsP/////////////////////////////////4roL/9IVC//i1jf///////f391/39/fr/////Cr37/wW8+/+16/7/////////////////9IVC//SFQv/0hUL/9IVC//SFQv/3pnX///////39/fn9/f36/////wu++/8FvPv/tuz+//////////////////SFQv/0hUL/9IVC//SFQv/0hUL/96p7///////9/f35/f392/////81yfz/CrL5/2uk9v///////////////////////////////////////////////////////f392P39/Zn/////ks/7/zdS7P84Rur/0NT6///////////////////////9/f////////////////////////39/Zb+/v4y//////n5/v9WYu3/NUPq/ztJ6/+VnPT/z9L6/9HU+v+WnfT/Ul7t/+Hj/P////////////////////8wAAAAAP39/Z3/////6Or9/1hj7v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9sdvD////////////9/f2YAAAAAAAAAAD///8K/f39w//////5+f7/paz2/11p7v88Suv/Okfq/1pm7v+iqfX/+fn+///////9/f3B/v7+CQAAAAAAAAAAAAAAAP///wr9/f2d///////////////////////////////////////////9/f2Z/v7+CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f2Z/f392/39/fr9/f36/f392v39/Zj///8wAAAAAAAAAAAAAAAAAAAAAPAPAADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/g3+/v5X/f39mf39/cj9/f3q/f39+f39/fn9/f3q/f39yP39/Zn+/v5W////DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f2c/f399f/////////////////////////////////////////////////////9/f31/f39mv7+/iMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/gn9/f2K/f39+////////////////////////////////////////////////////////////////////////////f39+v39/Yf///8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4k/f390v////////////////////////////////////////////////////////////////////////////////////////////////39/dD///8iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////MP39/er//////////////////////////+r05v+v16H/gsBs/2WxSf9Wqjj/Vqk3/2OwRv99vWX/pdKV/97u2P////////////////////////////39/ej+/v4vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f3q/////////////////////+v15/+Pxnv/VKk2/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/36+Z//d7tf///////////////////////39/ej///8iAAAAAAAAAAAAAAAAAAAAAAAAAAD///8K/f390//////////////////////E4bn/XKw+/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1apN/+x0pv///////////////////////39/dD///8IAAAAAAAAAAAAAAAAAAAAAP39/Yv/////////////////////sdij/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/YKU1/8qOPv/5wZ////////////////////////39/YcAAAAAAAAAAAAAAAD+/v4l/f39+////////////////8Lgt/9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9utlT/n86N/7faqv+426v/pdKV/3u8ZP9UqDX/U6g0/3egN//jiUH/9IVC//SFQv/82MP//////////////////f39+v7+/iMAAAAAAAAAAP39/Z3////////////////q9Ob/W6w+/1OoNP9TqDT/U6g0/1OoNP9nskz/zOXC/////////////////////////////////+Dv2v+osWP/8YVC//SFQv/0hUL/9IVC//WQVP/++fb//////////////////f39mgAAAAD+/v4O/f399v///////////////4LHj/9TqDT/U6g0/1OoNP9TqDT/dblc//L58P/////////////////////////////////////////////8+v/3p3f/9IVC//SFQv/0hUL/9IVC//rIqf/////////////////9/f31////DP7+/ln////////////////f9v7/Cbz2/zOwhv9TqDT/U6g0/2KwRv/v9+z///////////////////////////////////////////////////////738//1kFT/9IVC//SFQv/0hUL/9plg///////////////////////+/v5W/f39nP///////////////4jf/f8FvPv/Bbz7/yG1s/9QqDz/vN2w//////////////////////////////////////////////////////////////////rHqP/0hUL/9IVC//SFQv/0hUL//vDn//////////////////39/Zn9/f3L////////////////R878/wW8+/8FvPv/Bbz7/y7C5P/7/fr//////////////////////////////////////////////////////////////////ere//SFQv/0hUL/9IVC//SFQv/718H//////////////////f39yP39/ez///////////////8cwvv/Bbz7/wW8+/8FvPv/WNL8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//rIqv/////////////////9/f3q/f39+v///////////////we9+/8FvPv/Bbz7/wW8+/993P3///////////////////////////////////////SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/+cGf//////////////////39/fn9/f36////////////////B737/wW8+/8FvPv/Bbz7/33c/f//////////////////////////////////////9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/6xaX//////////////////f39+f39/e3///////////////8cwvv/Bbz7/wW8+/8FvPv/WdP8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//vVv//////////////////9/f3q/f39y////////////////0bN/P8FvPv/Bbz7/wW8+/8hrvn/+/v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////39/cj9/f2c////////////////ht/9/wW8+/8FvPv/FZP1/zRJ6/+zuPf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////f39mf7+/lr////////////////d9v7/B7n7/yB38f81Q+r/NUPq/0hV7P/u8P3////////////////////////////////////////////////////////////////////////////////////////////////////////////+/v5X////D/39/ff///////////////9tkPT/NUPq/zVD6v81Q+r/NUPq/2Fs7//y8v7////////////////////////////////////////////09f7//////////////////////////////////////////////////f399f7+/g0AAAAA/f39n////////////////+Tm/P89Suv/NUPq/zVD6v81Q+r/NUPq/1Bc7f/IzPn/////////////////////////////////x8v5/0xY7P+MlPP////////////////////////////////////////////9/f2cAAAAAAAAAAD+/v4n/f39/P///////////////7W69/81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9ZZe7/k5v0/6609/+vtff/lJv0/1pm7v81Q+r/NUPq/zVD6v+GjvL//v7//////////////////////////////f39+/7+/iQAAAAAAAAAAAAAAAD9/f2N/////////////////////6Cn9f81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v+BivL////////////////////////////9/f2KAAAAAAAAAAAAAAAAAAAAAP7+/gv9/f3V/////////////////////7W69/8+S+v/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/P0zr/7q/+P///////////////////////f390v7+/gkAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3r/////////////////////+Xn/P94gfH/NkTq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NkTq/3Z/8f/l5/z///////////////////////39/er+/v4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f3r///////////////////////////k5vz/nqX1/2p08P9IVez/OEbq/zdF6v9GU+z/aHLv/5qh9f/i5Pz////////////////////////////9/f3q////MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3V/////////////////////////////////////////////////////////////////////////////////////////////////f390v7+/iQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wr9/f2N/f39/P///////////////////////////////////////////////////////////////////////////f39+/39/Yv+/v4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4n/f39n/39/ff//////////////////////////////////////////////////////f399v39/Z3+/v4lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Dv7+/lr9/f2c/f39y/39/e39/f36/f39+v39/ez9/f3L/f39nP7+/ln+/v4OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AAAAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8AAP//wAP/",
+ _urls : [
+ {
+ type: "application/x-suggestions+json",
+ method: "GET",
+ template: "https://www.google.com/complete/search?client=firefox&q={searchTerms}",
+ params: "",
+ },
+ {
+ type: "text/html",
+ method: "GET",
+ template: "https://www.google.com/search",
+ params: [
+ {
+ "name": "q",
+ "value": "{searchTerms}",
+ "purpose": undefined,
+ },
+ {
+ "name": "ie",
+ "value": "utf-8",
+ "purpose": undefined,
+ },
+ {
+ "name": "oe",
+ "value": "utf-8",
+ "purpose": undefined,
+ },
+ {
+ "name": "client",
+ "value": "firefox-b-ab",
+ "purpose": "keyword",
+ },
+ {
+ "name": "client",
+ "value": "firefox-b",
+ "purpose": "searchbar",
+ },
+ ],
+ mozparams: {
+ },
+ },
+ ],
+ },
+ };
+
+ isSubObjectOf(EXPECTED_ENGINE, engine, "Google");
+}
diff --git a/browser/components/search/test/browser_google_behavior.js b/browser/components/search/test/browser_google_behavior.js
new file mode 100644
index 000000000..55405bb29
--- /dev/null
+++ b/browser/components/search/test/browser_google_behavior.js
@@ -0,0 +1,165 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Google search plugin URLs
+ */
+
+"use strict";
+
+function test() {
+ let engine = Services.search.getEngineByName("Google");
+ ok(engine, "Google is installed");
+
+ let previouslySelectedEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ engine.alias = "g";
+
+ let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&client=firefox-b";
+ let keywordBase = base + "-ab";
+
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base, "Check search URL for 'foo'");
+
+ waitForExplicitFinish();
+
+ var gCurrTest;
+ var gTests = [
+ {
+ name: "context menu search",
+ searchURL: base,
+ run: function () {
+ // Simulate a contextmenu search
+ // FIXME: This is a bit "low-level"...
+ BrowserSearch.loadSearch("foo", false, "contextmenu");
+ }
+ },
+ {
+ name: "keyword search",
+ searchURL: keywordBase,
+ run: function () {
+ gURLBar.value = "? foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "keyword search",
+ searchURL: keywordBase,
+ run: function () {
+ gURLBar.value = "g foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "search bar search",
+ searchURL: base,
+ run: function () {
+ let sb = BrowserSearch.searchBar;
+ sb.focus();
+ sb.value = "foo";
+ registerCleanupFunction(function () {
+ sb.value = "";
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "new tab search",
+ searchURL: base,
+ run: function () {
+ function doSearch(doc) {
+ // Re-add the listener, and perform a search
+ gBrowser.addProgressListener(listener);
+ doc.getElementById("newtab-search-text").value = "foo";
+ doc.getElementById("newtab-search-submit").click();
+ }
+
+ // load about:newtab, but remove the listener first so it doesn't
+ // get in the way
+ gBrowser.removeProgressListener(listener);
+ gBrowser.loadURI("about:newtab");
+ info("Waiting for about:newtab load");
+ tab.linkedBrowser.addEventListener("load", function load(event) {
+ if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ tab.linkedBrowser.removeEventListener("load", load, true);
+
+ // Observe page setup
+ let win = gBrowser.contentWindow;
+ if (win.gSearch.currentEngineName ==
+ Services.search.currentEngine.name) {
+ doSearch(win.document);
+ }
+ else {
+ info("Waiting for newtab search init");
+ win.addEventListener("ContentSearchService", function done(event) {
+ info("Got newtab search event " + event.detail.type);
+ if (event.detail.type == "State") {
+ win.removeEventListener("ContentSearchService", done);
+ // Let gSearch respond to the event before continuing.
+ executeSoon(() => doSearch(win.document));
+ }
+ });
+ }
+ }, true);
+ }
+ }
+ ];
+
+ function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ info("Running : " + gCurrTest.name);
+ executeSoon(gCurrTest.run);
+ } else {
+ finish();
+ }
+ }
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onStateChange(webProgress, req, flags, status) {
+ info("onStateChange");
+ // Only care about top-level document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart) || !webProgress.isTopLevel)
+ return;
+
+ if (req.originalURI.spec == "about:blank")
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+
+ executeSoon(nextTest);
+ }
+ }
+
+ registerCleanupFunction(function () {
+ engine.alias = undefined;
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ Services.search.currentEngine = previouslySelectedEngine;
+ });
+
+ tab.linkedBrowser.addEventListener("load", function load() {
+ tab.linkedBrowser.removeEventListener("load", load, true);
+ gBrowser.addProgressListener(listener);
+ nextTest();
+ }, true);
+}
diff --git a/browser/components/search/test/browser_google_codes.js b/browser/components/search/test/browser_google_codes.js
new file mode 100644
index 000000000..e166b6868
--- /dev/null
+++ b/browser/components/search/test/browser_google_codes.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kUrlPref = "geoSpecificDefaults.url";
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+var originalGeoURL;
+
+/**
+ * Clean the profile of any cache file left from a previous run.
+ * Returns a boolean indicating if the cache file existed.
+ */
+function removeCacheFile()
+{
+ const CACHE_FILENAME = "search.json.mozlz4";
+
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(CACHE_FILENAME);
+ if (file.exists()) {
+ file.remove(false);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Returns a promise that is resolved when an observer notification from the
+ * search service fires with the specified data.
+ *
+ * @param aExpectedData
+ * The value the observer notification sends that causes us to resolve
+ * the promise.
+ */
+function waitForSearchNotification(aExpectedData, aCallback) {
+ const SEARCH_SERVICE_TOPIC = "browser-search-service";
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ if (aData != aExpectedData)
+ return;
+
+ Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
+ aCallback();
+ }, SEARCH_SERVICE_TOPIC, false);
+}
+
+function asyncInit() {
+ return new Promise(resolve => {
+ Services.search.init(function() {
+ ok(Services.search.isInitialized, "search service should be initialized");
+ resolve();
+ });
+ });
+}
+
+function asyncReInit() {
+ const kLocalePref = "general.useragent.locale";
+
+ let promise = new Promise(resolve => {
+ waitForSearchNotification("reinit-complete", resolve);
+ });
+
+ Services.search.QueryInterface(Ci.nsIObserver)
+ .observe(null, "nsPref:changed", kLocalePref);
+
+ return promise;
+}
+
+let gEngineCount;
+
+add_task(function* preparation() {
+ // ContentSearch is interferring with our async re-initializations of the
+ // search service: once _initServicePromise has resolved, it will access
+ // the search service, thus causing unpredictable behavior due to
+ // synchronous initializations of the service.
+ let originalContentSearchPromise = ContentSearch._initServicePromise;
+ ContentSearch._initServicePromise = new Promise(resolve => {
+ registerCleanupFunction(() => {
+ ContentSearch._initServicePromise = originalContentSearchPromise;
+ resolve();
+ });
+ });
+
+ yield asyncInit();
+ gEngineCount = Services.search.getVisibleEngines().length;
+
+ waitForSearchNotification("uninit-complete", () => {
+ // Verify search service is not initialized
+ is(Services.search.isInitialized, false, "Search service should NOT be initialized");
+
+ removeCacheFile();
+
+ // Geo specific defaults won't be fetched if there's no country code.
+ Services.prefs.setCharPref("browser.search.geoip.url",
+ 'data:application/json,{"country_code": "US"}');
+
+ Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true);
+
+ // Make the new Google the only engine
+ originalGeoURL = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + kUrlPref);
+ let geoUrl = 'data:application/json,{"interval": 31536000, "settings": {"searchDefault": "Google", "visibleDefaultEngines": ["google"]}}';
+ Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, geoUrl);
+ });
+
+ yield asyncReInit();
+
+ yield new Promise(resolve => {
+ waitForSearchNotification("write-cache-to-disk-complete", resolve);
+ });
+});
+
+add_task(function* tests() {
+ let engines = Services.search.getEngines();
+ is(Services.search.currentEngine.name, "Google", "Search engine should be Google");
+ is(engines.length, 1, "There should only be one engine");
+
+ let engine = Services.search.getEngineByName("Google");
+ ok(engine, "Google");
+
+ let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&client=firefox-b";
+
+ // Keyword uses a slightly different code
+ let keywordBase = base + "-ab";
+
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
+ is(url, base, "Check context menu search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "keyword").uri.spec;
+ is(url, keywordBase, "Check keyword search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "searchbar").uri.spec;
+ is(url, base, "Check search bar search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "homepage").uri.spec;
+ is(url, base, "Check homepage search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "newtab").uri.spec;
+ is(url, base, "Check newtab search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "system").uri.spec;
+ is(url, base, "Check system search URL for 'foo'");
+});
+
+
+add_task(function* cleanup() {
+ waitForSearchNotification("uninit-complete", () => {
+ // Verify search service is not initialized
+ is(Services.search.isInitialized, false,
+ "Search service should NOT be initialized");
+ removeCacheFile();
+
+ Services.prefs.clearUserPref("browser.search.geoip.url");
+
+ // We can't clear the pref because it's set to false by testing/profiles/prefs_general.js
+ Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
+
+ Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, originalGeoURL);
+ });
+
+ yield asyncReInit();
+ is(gEngineCount, Services.search.getVisibleEngines().length,
+ "correct engine count after cleanup");
+});
diff --git a/browser/components/search/test/browser_healthreport.js b/browser/components/search/test/browser_healthreport.js
new file mode 100644
index 000000000..c68ad174c
--- /dev/null
+++ b/browser/components/search/test/browser_healthreport.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+
+function test() {
+ waitForExplicitFinish();
+ resetPreferences();
+
+ function testTelemetry() {
+ // Find the right bucket for the "Foo" engine.
+ let engine = Services.search.getEngineByName("Foo");
+ let histogramKey = (engine.identifier || "other-Foo") + ".searchbar";
+ let numSearchesBefore = 0;
+ try {
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ if (histogramKey in hs) {
+ numSearchesBefore = hs[histogramKey].sum;
+ }
+ } catch (ex) {
+ // No searches performed yet, not a problem, |numSearchesBefore| is 0.
+ }
+
+ // Now perform a search and ensure the count is incremented.
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ let searchBar = BrowserSearch.searchBar;
+
+ searchBar.value = "firefox health report";
+ searchBar.focus();
+
+ function afterSearch() {
+ searchBar.value = "";
+ gBrowser.removeTab(tab);
+
+ // Make sure that the context searches are correctly recorded.
+ let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+ Assert.ok(histogramKey in hs, "The histogram must contain the correct key");
+ Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
+ "Performing a search increments the related SEARCH_COUNTS key by 1.");
+
+ let engine = Services.search.getEngineByName("Foo");
+ Services.search.removeEngine(engine);
+ }
+
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ executeSoon(() => executeSoon(afterSearch));
+ }
+
+ function observer(subject, topic, data) {
+ switch (data) {
+ case "engine-added":
+ let engine = Services.search.getEngineByName("Foo");
+ ok(engine, "Engine was added.");
+ Services.search.currentEngine = engine;
+ break;
+
+ case "engine-current":
+ is(Services.search.currentEngine.name, "Foo", "Current engine is Foo");
+ testTelemetry();
+ break;
+
+ case "engine-removed":
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+ finish();
+ break;
+ }
+ }
+
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+ SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]}).then(function() {
+ Services.search.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
+ null, "data:image/x-icon,%00", false);
+ });
+}
+
+function resetPreferences() {
+ Preferences.resetBranch("datareporting.policy.");
+ Preferences.set("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
+}
diff --git a/browser/components/search/test/browser_hiddenOneOffs_cleanup.js b/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
new file mode 100644
index 000000000..9a584feb6
--- /dev/null
+++ b/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 testPref = "Foo,FooDupe";
+
+function promiseNewEngine(basename) {
+ return new Promise((resolve, reject) => {
+ info("Waiting for engine to be added: " + basename);
+ Services.search.init({
+ onInitComplete: function() {
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ }
+ });
+ }
+ });
+ });
+}
+
+add_task(function* test_remove() {
+ yield promiseNewEngine("testEngine_dupe.xml");
+ yield promiseNewEngine("testEngine.xml");
+ Services.prefs.setCharPref("browser.search.hiddenOneOffs", testPref);
+
+ info("Removing testEngine_dupe.xml");
+ Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
+
+ let hiddenOneOffs =
+ Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
+
+ is(hiddenOneOffs.length, 1,
+ "hiddenOneOffs has the correct engine count post removal.");
+ is(hiddenOneOffs.some(x => x == "FooDupe"), false,
+ "Removed Engine is not in hiddenOneOffs after removal");
+ is(hiddenOneOffs.some(x => x == "Foo"), true,
+ "Current hidden engine is not affected by removal.");
+
+ info("Removing testEngine.xml");
+ Services.search.removeEngine(Services.search.getEngineByName("Foo"));
+
+ is(Services.prefs.getCharPref("browser.search.hiddenOneOffs"), "",
+ "hiddenOneOffs is empty after removing all hidden engines.");
+});
+
+add_task(function* test_add() {
+ yield promiseNewEngine("testEngine.xml");
+ info("setting prefs to " + testPref);
+ Services.prefs.setCharPref("browser.search.hiddenOneOffs", testPref);
+ yield promiseNewEngine("testEngine_dupe.xml");
+
+ let hiddenOneOffs =
+ Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
+
+ is(hiddenOneOffs.length, 1,
+ "hiddenOneOffs has the correct number of hidden engines present post add.");
+ is(hiddenOneOffs.some(x => x == "FooDupe"), false,
+ "Added engine is not present in hidden list.");
+ is(hiddenOneOffs.some(x => x == "Foo"), true,
+ "Adding an engine does not remove engines from hidden list.");
+});
+
+add_task(function* test_diacritics() {
+ const diacritic_engine = "Foo \u2661";
+ let Preferences =
+ Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+
+ Preferences.set("browser.search.hiddenOneOffs", diacritic_engine);
+ yield promiseNewEngine("testEngine_diacritics.xml");
+
+ let hiddenOneOffs =
+ Preferences.get("browser.search.hiddenOneOffs").split(",");
+ is(hiddenOneOffs.some(x => x == diacritic_engine), false,
+ "Observer cleans up added hidden engines that include a diacritic.");
+
+ Preferences.set("browser.search.hiddenOneOffs", diacritic_engine);
+
+ info("Removing testEngine_diacritics.xml");
+ Services.search.removeEngine(Services.search.getEngineByName(diacritic_engine));
+
+ hiddenOneOffs =
+ Preferences.get("browser.search.hiddenOneOffs").split(",");
+ is(hiddenOneOffs.some(x => x == diacritic_engine), false,
+ "Observer cleans up removed hidden engines that include a diacritic.");
+});
+
+registerCleanupFunction(() => {
+ info("Removing testEngine.xml");
+ Services.search.removeEngine(Services.search.getEngineByName("Foo"));
+ info("Removing testEngine_dupe.xml");
+ Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
+ Services.prefs.clearUserPref("browser.search.hiddenOneOffs");
+});
diff --git a/browser/components/search/test/browser_hiddenOneOffs_diacritics.js b/browser/components/search/test/browser_hiddenOneOffs_diacritics.js
new file mode 100644
index 000000000..db24c7192
--- /dev/null
+++ b/browser/components/search/test/browser_hiddenOneOffs_diacritics.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+// Tests that keyboard navigation in the search panel works as designed.
+
+const searchbar = document.getElementById("searchbar");
+const textbox = searchbar._textbox;
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid",
+ "searchbar-search-button");
+
+const diacritic_engine = "Foo \u2661";
+
+var Preferences =
+ Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+
+add_task(function* init() {
+ let currentEngine = Services.search.currentEngine;
+ yield promiseNewEngine("testEngine_diacritics.xml", {setAsCurrent: false});
+ registerCleanupFunction(() => {
+ Services.search.currentEngine = currentEngine;
+ Services.prefs.clearUserPref("browser.search.hiddenOneOffs");
+ });
+});
+
+add_task(function* test_hidden() {
+ Preferences.set("browser.search.hiddenOneOffs", diacritic_engine);
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ yield promise;
+
+ ok(!getOneOffs().some(x => x.getAttribute("tooltiptext") == diacritic_engine),
+ "Search engines with diacritics are hidden when added to hiddenOneOffs preference.");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ info("Closing search panel");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+});
+
+add_task(function* test_shown() {
+ Preferences.set("browser.search.hiddenOneOffs", "");
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ SimpleTest.executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ });
+ yield promise;
+
+ ok(getOneOffs().some(x => x.getAttribute("tooltiptext") == diacritic_engine),
+ "Search engines with diacritics are shown when removed from hiddenOneOffs preference.");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+});
diff --git a/browser/components/search/test/browser_oneOffContextMenu.js b/browser/components/search/test/browser_oneOffContextMenu.js
new file mode 100644
index 000000000..69207923b
--- /dev/null
+++ b/browser/components/search/test/browser_oneOffContextMenu.js
@@ -0,0 +1,105 @@
+"use strict";
+
+const TEST_ENGINE_NAME = "Foo";
+const TEST_ENGINE_BASENAME = "testEngine.xml";
+
+const searchbar = document.getElementById("searchbar");
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const searchIcon = document.getAnonymousElementByAttribute(
+ searchbar, "anonid", "searchbar-search-button"
+);
+const oneOffBinding = document.getAnonymousElementByAttribute(
+ searchPopup, "anonid", "search-one-off-buttons"
+);
+const contextMenu = document.getAnonymousElementByAttribute(
+ oneOffBinding, "anonid", "search-one-offs-context-menu"
+);
+const oneOffButtons = document.getAnonymousElementByAttribute(
+ oneOffBinding, "anonid", "search-panel-one-offs"
+);
+const searchInNewTabMenuItem = document.getAnonymousElementByAttribute(
+ oneOffBinding, "anonid", "search-one-offs-context-open-in-new-tab"
+);
+
+add_task(function* init() {
+ yield promiseNewEngine(TEST_ENGINE_BASENAME, {
+ setAsCurrent: false,
+ });
+});
+
+add_task(function* extendedTelemetryDisabled() {
+ yield SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", false]]});
+ yield doTest();
+ checkTelemetry("other");
+});
+
+add_task(function* extendedTelemetryEnabled() {
+ yield SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]});
+ yield doTest();
+ checkTelemetry("other-" + TEST_ENGINE_NAME);
+});
+
+function* doTest() {
+ // Open the popup.
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ yield promise;
+
+ // Get the one-off button for the test engine.
+ let oneOffButton;
+ for (let node of oneOffButtons.childNodes) {
+ if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
+ oneOffButton = node;
+ break;
+ }
+ }
+ Assert.notEqual(oneOffButton, undefined,
+ "One-off for test engine should exist");
+
+ // Open the context menu on the one-off.
+ promise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(oneOffButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ yield promise;
+
+ // Click the Search in New Tab menu item.
+ promise = BrowserTestUtils.waitForNewTab(gBrowser);
+ EventUtils.synthesizeMouseAtCenter(searchInNewTabMenuItem, {});
+ let tab = yield promise;
+
+ // By default the search will open in the background and the popup will stay open:
+ promise = promiseEvent(searchPopup, "popuphidden");
+ info("Closing search panel");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+
+ // Check the loaded tab.
+ Assert.equal(tab.linkedBrowser.currentURI.spec,
+ "http://mochi.test:8888/browser/browser/components/search/test/",
+ "Expected search tab should have loaded");
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Move the cursor out of the panel area to avoid messing with other tests.
+ yield EventUtils.synthesizeNativeMouseMove(searchbar);
+}
+
+function checkTelemetry(expectedEngineName) {
+ let propertyPath = [
+ "countableEvents",
+ "__DEFAULT__",
+ "search-oneoff",
+ expectedEngineName + ".oneoff-context-searchbar",
+ "unknown",
+ "tab-background",
+ ];
+ let telem = BrowserUITelemetry.getToolbarMeasures();
+ for (let prop of propertyPath) {
+ Assert.ok(prop in telem, "Property " + prop + " should be in the telemetry");
+ telem = telem[prop];
+ }
+ Assert.equal(telem, 1, "Click count");
+}
diff --git a/browser/components/search/test/browser_oneOffContextMenu_setDefault.js b/browser/components/search/test/browser_oneOffContextMenu_setDefault.js
new file mode 100644
index 000000000..ff49cb0c6
--- /dev/null
+++ b/browser/components/search/test/browser_oneOffContextMenu_setDefault.js
@@ -0,0 +1,195 @@
+"use strict";
+
+const TEST_ENGINE_NAME = "Foo";
+const TEST_ENGINE_BASENAME = "testEngine.xml";
+const SEARCHBAR_BASE_ID = "searchbar-engine-one-off-item-";
+const URLBAR_BASE_ID = "urlbar-engine-one-off-item-";
+const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
+
+const searchbar = document.getElementById("searchbar");
+const urlbar = document.getElementById("urlbar");
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const urlbarPopup = document.getElementById("PopupAutoCompleteRichResult");
+const searchIcon = document.getAnonymousElementByAttribute(
+ searchbar, "anonid", "searchbar-search-button"
+);
+const searchOneOffBinding = document.getAnonymousElementByAttribute(
+ searchPopup, "anonid", "search-one-off-buttons"
+);
+const urlBarOneOffBinding = document.getAnonymousElementByAttribute(
+ urlbarPopup, "anonid", "one-off-search-buttons"
+);
+
+let originalEngine = Services.search.currentEngine;
+
+function resetEngine() {
+ Services.search.currentEngine = originalEngine;
+}
+
+registerCleanupFunction(resetEngine);
+
+add_task(function* init() {
+ yield promiseNewEngine(TEST_ENGINE_BASENAME, {
+ setAsCurrent: false,
+ });
+});
+
+add_task(function* test_searchBarChangeEngine() {
+ let oneOffButton = yield openPopupAndGetEngineButton(true, searchPopup,
+ searchOneOffBinding,
+ SEARCHBAR_BASE_ID);
+
+ const setDefaultEngineMenuItem = document.getAnonymousElementByAttribute(
+ searchOneOffBinding, "anonid", "search-one-offs-context-set-default"
+ );
+
+ // Click the set default engine menu item.
+ let promise = promiseCurrentEngineChanged();
+ EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {});
+
+ // This also checks the engine correctly changed.
+ yield promise;
+
+ Assert.equal(oneOffButton.id, SEARCHBAR_BASE_ID + originalEngine.name,
+ "Should now have the original engine's id for the button");
+ Assert.equal(oneOffButton.getAttribute("tooltiptext"), originalEngine.name,
+ "Should now have the original engine's name for the tooltip");
+ Assert.equal(oneOffButton.image, originalEngine.iconURI.spec,
+ "Should now have the original engine's uri for the image");
+
+ yield promiseClosePopup(searchPopup);
+});
+
+add_task(function* test_urlBarChangeEngine() {
+ Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
+ registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
+ });
+
+ // Ensure the engine is reset.
+ resetEngine();
+
+ let oneOffButton = yield openPopupAndGetEngineButton(false, urlbarPopup,
+ urlBarOneOffBinding,
+ URLBAR_BASE_ID);
+
+ const setDefaultEngineMenuItem = document.getAnonymousElementByAttribute(
+ urlBarOneOffBinding, "anonid", "search-one-offs-context-set-default"
+ );
+
+ // Click the set default engine menu item.
+ let promise = promiseCurrentEngineChanged();
+ EventUtils.synthesizeMouseAtCenter(setDefaultEngineMenuItem, {});
+
+ // This also checks the engine correctly changed.
+ yield promise;
+
+ let currentEngine = Services.search.currentEngine;
+
+ // For the urlbar, we should keep the new engine's icon.
+ Assert.equal(oneOffButton.id, URLBAR_BASE_ID + currentEngine.name,
+ "Should now have the original engine's id for the button");
+ Assert.equal(oneOffButton.getAttribute("tooltiptext"), currentEngine.name,
+ "Should now have the original engine's name for the tooltip");
+ Assert.equal(oneOffButton.image, currentEngine.iconURI.spec,
+ "Should now have the original engine's uri for the image");
+
+ yield promiseClosePopup(urlbarPopup);
+});
+
+/**
+ * Promises that an engine change has happened for the current engine, which
+ * has resulted in the test engine now being the current engine.
+ *
+ * @return {Promise} Resolved once the test engine is set as the current engine.
+ */
+function promiseCurrentEngineChanged() {
+ return new Promise(resolve => {
+ function observer(aSub, aTopic, aData) {
+ if (aData == "engine-current") {
+ Assert.ok(Services.search.currentEngine.name, TEST_ENGINE_NAME, "currentEngine set");
+ Services.obs.removeObserver(observer, "browser-search-engine-modified");
+ resolve();
+ }
+ }
+
+ Services.obs.addObserver(observer, "browser-search-engine-modified", false);
+ });
+}
+
+/**
+ * Opens the specified urlbar/search popup and gets the test engine from the
+ * one-off buttons.
+ *
+ * @param {Boolean} isSearch true if the search popup should be opened; false
+ * for the urlbar popup.
+ * @param {Object} popup The expected popup.
+ * @param {Object} oneOffBinding The expected one-off-binding for the popup.
+ * @param {String} baseId The expected string for the id of the current
+ * engine button, without the engine name.
+ * @return {Object} Returns an object that represents the one off button for the
+ * test engine.
+ */
+function* openPopupAndGetEngineButton(isSearch, popup, oneOffBinding, baseId) {
+ // Open the popup.
+ let promise = promiseEvent(popup, "popupshown");
+ info("Opening panel");
+
+ // We have to open the popups in differnt ways.
+ if (isSearch) {
+ // Use the search icon to avoid hitting the network.
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ } else {
+ // There's no history at this stage, so we need to press a key.
+ urlbar.focus();
+ EventUtils.synthesizeKey("a", {});
+ }
+ yield promise;
+
+ const contextMenu = document.getAnonymousElementByAttribute(
+ oneOffBinding, "anonid", "search-one-offs-context-menu"
+ );
+ const oneOffButtons = document.getAnonymousElementByAttribute(
+ oneOffBinding, "anonid", "search-panel-one-offs"
+ );
+
+ // Get the one-off button for the test engine.
+ let oneOffButton;
+ for (let node of oneOffButtons.childNodes) {
+ if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
+ oneOffButton = node;
+ break;
+ }
+ }
+ Assert.notEqual(oneOffButton, undefined,
+ "One-off for test engine should exist");
+ Assert.equal(oneOffButton.getAttribute("tooltiptext"), TEST_ENGINE_NAME,
+ "One-off should have the tooltip set to the engine name");
+ Assert.equal(oneOffButton.id, baseId + TEST_ENGINE_NAME,
+ "Should have the correct id");
+
+ // Open the context menu on the one-off.
+ promise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(oneOffButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ yield promise;
+
+ return oneOffButton;
+}
+
+/**
+ * Closes the popup and moves the mouse away from it.
+ *
+ * @param {Button} popup The popup to close.
+ */
+function* promiseClosePopup(popup) {
+ // close the panel using the escape key.
+ let promise = promiseEvent(popup, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+
+ // Move the cursor out of the panel area to avoid messing with other tests.
+ yield EventUtils.synthesizeNativeMouseMove(popup);
+}
diff --git a/browser/components/search/test/browser_oneOffHeader.js b/browser/components/search/test/browser_oneOffHeader.js
new file mode 100644
index 000000000..3a209bf56
--- /dev/null
+++ b/browser/components/search/test/browser_oneOffHeader.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/. */
+// Tests that keyboard navigation in the search panel works as designed.
+
+const isMac = ("nsILocalFileMac" in Ci);
+
+const searchbar = document.getElementById("searchbar");
+const textbox = searchbar._textbox;
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid",
+ "searchbar-search-button");
+
+const oneOffsContainer =
+ document.getAnonymousElementByAttribute(searchPopup, "anonid",
+ "search-one-off-buttons");
+const searchSettings =
+ document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
+ "search-settings");
+var header =
+ document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
+ "search-panel-one-offs-header");
+function getHeaderText() {
+ let headerChild = header.selectedPanel;
+ while (headerChild.hasChildNodes()) {
+ headerChild = headerChild.firstChild;
+ }
+ let headerStrings = [];
+ for (let label = headerChild; label; label = label.nextSibling) {
+ headerStrings.push(label.value);
+ }
+ return headerStrings.join("");
+}
+
+const msg = isMac ? 5 : 1;
+const utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+const scale = utils.screenPixelsPerCSSPixel;
+function* synthesizeNativeMouseMove(aElement) {
+ let rect = aElement.getBoundingClientRect();
+ let win = aElement.ownerGlobal;
+ let x = win.mozInnerScreenX + (rect.left + rect.right) / 2;
+ let y = win.mozInnerScreenY + (rect.top + rect.bottom) / 2;
+
+ // Wait for the mouseup event to occur before continuing.
+ return new Promise((resolve, reject) => {
+ function eventOccurred(e)
+ {
+ aElement.removeEventListener("mouseover", eventOccurred, true);
+ resolve();
+ }
+
+ aElement.addEventListener("mouseover", eventOccurred, true);
+
+ utils.sendNativeMouseEvent(x * scale, y * scale, msg, 0, null);
+ });
+}
+
+
+add_task(function* init() {
+ yield promiseNewEngine("testEngine.xml");
+});
+
+add_task(function* test_notext() {
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ yield promise;
+
+ is(header.getAttribute("selectedIndex"), 0,
+ "Header has the correct index selected with no search terms.");
+
+ is(getHeaderText(), "Search with:",
+ "Search header string is correct when no search terms have been entered");
+
+ yield synthesizeNativeMouseMove(searchSettings);
+ is(header.getAttribute("selectedIndex"), 0,
+ "Header has the correct index when no search terms have been entered and the Change Search Settings button is selected.");
+ is(getHeaderText(), "Search with:",
+ "Header has the correct text when no search terms have been entered and the Change Search Settings button is selected.");
+
+ let buttons = getOneOffs();
+ yield synthesizeNativeMouseMove(buttons[0]);
+ is(header.getAttribute("selectedIndex"), 2,
+ "Header has the correct index selected when a search engine has been selected");
+ is(getHeaderText(), "Search " + buttons[0].engine.name,
+ "Is the header text correct when a search engine is selected and no terms have been entered.");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ info("Closing search panel");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+});
+
+add_task(function* test_text() {
+ textbox.value = "foo";
+ registerCleanupFunction(() => {
+ textbox.value = "";
+ });
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ SimpleTest.executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ });
+ yield promise;
+
+ is(header.getAttribute("selectedIndex"), 1,
+ "Header has the correct index selected with a search term.");
+ is(getHeaderText(), "Search for foo with:",
+ "Search header string is correct when a search term has been entered");
+
+ let buttons = getOneOffs();
+ yield synthesizeNativeMouseMove(buttons[0]);
+ is(header.getAttribute("selectedIndex"), 2,
+ "Header has the correct index selected when a search engine has been selected");
+ is(getHeaderText(), "Search " + buttons[0].engine.name,
+ "Is the header text correct when search terms are entered after a search engine has been selected.");
+
+ yield synthesizeNativeMouseMove(searchSettings);
+ is(header.getAttribute("selectedIndex"), 1,
+ "Header has the correct index selected when search terms have been entered and the Change Search Settings button is selected.");
+ is(getHeaderText(), "Search for foo with:",
+ "Header has the correct text when search terms have been entered and the Change Search Settings button is selected.");
+
+ // Click the "Foo Search" header at the top of the popup and make sure it
+ // loads the search results.
+ let searchbarEngine =
+ document.getAnonymousElementByAttribute(searchPopup, "anonid",
+ "searchbar-engine");
+
+ yield synthesizeNativeMouseMove(searchbarEngine);
+ SimpleTest.executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(searchbarEngine, {});
+ });
+
+ let url = Services.search.currentEngine.getSubmission(textbox.value).uri.spec;
+ yield promiseTabLoadEvent(gBrowser.selectedTab, url);
+
+ // Move the cursor out of the panel area to avoid messing with other tests.
+ yield synthesizeNativeMouseMove(searchbar);
+});
diff --git a/browser/components/search/test/browser_private_search_perwindowpb.js b/browser/components/search/test/browser_private_search_perwindowpb.js
new file mode 100644
index 000000000..c0410371b
--- /dev/null
+++ b/browser/components/search/test/browser_private_search_perwindowpb.js
@@ -0,0 +1,76 @@
+// This test performs a search in a public window, then a different
+// search in a private window, and then checks in the public window
+// whether there is an autocomplete entry for the private search.
+
+add_task(function* () {
+ // Don't use about:home as the homepage for new windows
+ Services.prefs.setIntPref("browser.startup.page", 0);
+ registerCleanupFunction(() => Services.prefs.clearUserPref("browser.startup.page"));
+
+ let windowsToClose = [];
+
+ function performSearch(aWin, aIsPrivate) {
+ let searchBar = aWin.BrowserSearch.searchBar;
+ ok(searchBar, "got search bar");
+
+ let loadPromise = BrowserTestUtils.browserLoaded(aWin.gBrowser.selectedBrowser);
+
+ searchBar.value = aIsPrivate ? "private test" : "public test";
+ searchBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {}, aWin);
+
+ return loadPromise;
+ }
+
+ function* testOnWindow(aIsPrivate) {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ private: aIsPrivate });
+ yield SimpleTest.promiseFocus(win);
+ windowsToClose.push(win);
+ return win;
+ }
+
+ yield promiseNewEngine("426329.xml", { iconURL: "data:image/x-icon,%00" });
+
+ let newWindow = yield* testOnWindow(false);
+ yield performSearch(newWindow, false);
+
+ newWindow = yield* testOnWindow(true);
+ yield performSearch(newWindow, true);
+
+ newWindow = yield* testOnWindow(false);
+
+ let searchBar = newWindow.BrowserSearch.searchBar;
+ searchBar.value = "p";
+ searchBar.focus();
+
+ let popup = searchBar.textbox.popup;
+ let popupPromise = BrowserTestUtils.waitForEvent(popup, "popupshown");
+ searchBar.textbox.showHistoryPopup();
+ yield popupPromise;
+
+ let entries = getMenuEntries(searchBar);
+ for (let i = 0; i < entries.length; i++) {
+ isnot(entries[i], "private test",
+ "shouldn't see private autocomplete entries");
+ }
+
+ searchBar.textbox.toggleHistoryPopup();
+ searchBar.value = "";
+
+ windowsToClose.forEach(function(win) {
+ win.close();
+ });
+});
+
+function getMenuEntries(searchBar) {
+ let entries = [];
+ let autocompleteMenu = searchBar.textbox.popup;
+ // Could perhaps pull values directly from the controller, but it seems
+ // more reliable to test the values that are actually in the tree?
+ let column = autocompleteMenu.tree.columns[0];
+ let numRows = autocompleteMenu.tree.view.rowCount;
+ for (let i = 0; i < numRows; i++) {
+ entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
+ }
+ return entries;
+}
diff --git a/browser/components/search/test/browser_searchbar_keyboard_navigation.js b/browser/components/search/test/browser_searchbar_keyboard_navigation.js
new file mode 100644
index 000000000..d395dfdc2
--- /dev/null
+++ b/browser/components/search/test/browser_searchbar_keyboard_navigation.js
@@ -0,0 +1,425 @@
+// Tests that keyboard navigation in the search panel works as designed.
+
+const searchbar = document.getElementById("searchbar");
+const textbox = searchbar._textbox;
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const oneOffsContainer =
+ document.getAnonymousElementByAttribute(searchPopup, "anonid",
+ "search-one-off-buttons");
+
+const kValues = ["foo1", "foo2", "foo3"];
+const kUserValue = "foo";
+
+function getOpenSearchItems() {
+ let os = [];
+
+ let addEngineList =
+ document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
+ "add-engines");
+ for (let item = addEngineList.firstChild; item; item = item.nextSibling)
+ os.push(item);
+
+ return os;
+}
+
+add_task(function* init() {
+ yield promiseNewEngine("testEngine.xml");
+
+ // First cleanup the form history in case other tests left things there.
+ yield new Promise((resolve, reject) => {
+ info("cleanup the search history");
+ searchbar.FormHistory.update({op: "remove", fieldname: "searchbar-history"},
+ {handleCompletion: resolve,
+ handleError: reject});
+ });
+
+ yield new Promise((resolve, reject) => {
+ info("adding search history values: " + kValues);
+ let ops = kValues.map(value => { return {op: "add",
+ fieldname: "searchbar-history",
+ value: value}
+ });
+ searchbar.FormHistory.update(ops, {
+ handleCompletion: function() {
+ registerCleanupFunction(() => {
+ info("removing search history values: " + kValues);
+ let ops =
+ kValues.map(value => { return {op: "remove",
+ fieldname: "searchbar-history",
+ value: value}
+ });
+ searchbar.FormHistory.update(ops);
+ });
+ resolve();
+ },
+ handleError: reject
+ });
+ });
+
+ textbox.value = kUserValue;
+ registerCleanupFunction(() => { textbox.value = ""; });
+});
+
+
+add_task(function* test_arrows() {
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ searchbar.focus();
+ yield promise;
+ is(textbox.mController.searchString, kUserValue, "The search string should be 'foo'");
+
+ // Check the initial state of the panel before sending keyboard events.
+ is(searchPopup.view.rowCount, kValues.length, "There should be 3 suggestions");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+
+ // The tests will be less meaningful if the first, second, last, and
+ // before-last one-off buttons aren't different. We should always have more
+ // than 4 default engines, but it's safer to check this assumption.
+ let oneOffs = getOneOffs();
+ ok(oneOffs.length >= 4, "we have at least 4 one-off buttons displayed")
+
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // The down arrow should first go through the suggestions.
+ for (let i = 0; i < kValues.length; ++i) {
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(searchPopup.selectedIndex, i,
+ "the suggestion at index " + i + " should be selected");
+ is(textbox.value, kValues[i],
+ "the textfield value should be " + kValues[i]);
+ }
+
+ // Pressing down again should remove suggestion selection and change the text
+ // field value back to what the user typed, and select the first one-off.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue,
+ "the textfield value should be back to initial value");
+
+ // now cycle through the one-off items, the first one is already selected.
+ for (let i = 0; i < oneOffs.length; ++i) {
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ }
+
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ // We should now be back to the initial situation.
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ info("now test the up arrow key");
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // cycle through the one-off items, the first one is already selected.
+ for (let i = oneOffs.length; i; --i) {
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton, oneOffs[i - 1],
+ "the one-off button #" + i + " should be selected");
+ }
+
+ // Another press on up should clear the one-off selection and select the
+ // last suggestion.
+ EventUtils.synthesizeKey("VK_UP", {});
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ for (let i = kValues.length - 1; i >= 0; --i) {
+ is(searchPopup.selectedIndex, i,
+ "the suggestion at index " + i + " should be selected");
+ is(textbox.value, kValues[i],
+ "the textfield value should be " + kValues[i]);
+ EventUtils.synthesizeKey("VK_UP", {});
+ }
+
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue,
+ "the textfield value should be back to initial value");
+});
+
+add_task(function* test_typing_clears_button_selection() {
+ is(Services.focus.focusedElement, textbox.inputField,
+ "the search bar should be focused"); // from the previous test.
+ ok(!textbox.selectedButton, "no button should be selected");
+
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // Type a character.
+ EventUtils.synthesizeKey("a", {});
+ ok(!textbox.selectedButton, "the settings item should be de-selected");
+
+ // Remove the character.
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+});
+
+add_task(function* test_tab() {
+ is(Services.focus.focusedElement, textbox.inputField,
+ "the search bar should be focused"); // from the previous test.
+
+ let oneOffs = getOneOffs();
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // Pressing tab should select the first one-off without selecting suggestions.
+ // now cycle through the one-off items, the first one is already selected.
+ for (let i = 0; i < oneOffs.length; ++i) {
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ }
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue, "the textfield value should be unmodified");
+
+ // One more <tab> selects the settings button.
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // Pressing tab again should close the panel...
+ let promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ yield promise;
+
+ // ... and move the focus out of the searchbox.
+ isnot(Services.focus.focusedElement, textbox.inputField,
+ "the search bar no longer be focused");
+});
+
+add_task(function* test_shift_tab() {
+ // First reopen the panel.
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ searchbar.focus();
+ yield promise;
+
+ let oneOffs = getOneOffs();
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // Press up once to select the last button.
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // Press up again to select the last one-off button.
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ // Pressing shift+tab should cycle through the one-off items.
+ for (let i = oneOffs.length - 1; i >= 0; --i) {
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ if (i)
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
+ }
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue, "the textfield value should be unmodified");
+
+ // Pressing shift+tab again should close the panel...
+ promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
+ yield promise;
+
+ // ... and move the focus out of the searchbox.
+ isnot(Services.focus.focusedElement, textbox.inputField,
+ "the search bar no longer be focused");
+});
+
+add_task(function* test_alt_down() {
+ // First refocus the panel.
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ searchbar.focus();
+ yield promise;
+
+ // close the panel using the escape key.
+ promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+
+ // check that alt+down opens the panel...
+ promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
+ yield promise;
+
+ // ... and does nothing else.
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue, "the textfield value should be unmodified");
+
+ // Pressing alt+down should select the first one-off without selecting suggestions
+ // and cycle through the one-off items.
+ let oneOffs = getOneOffs();
+ for (let i = 0; i < oneOffs.length; ++i) {
+ EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ }
+
+ // One more alt+down keypress and nothing should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // another one and the first one-off should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should be selected");
+});
+
+add_task(function* test_alt_up() {
+ // close the panel using the escape key.
+ let promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+
+ // check that alt+up opens the panel...
+ promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ yield promise;
+
+ // ... and does nothing else.
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue, "the textfield value should be unmodified");
+
+ // Pressing alt+up should select the last one-off without selecting suggestions
+ // and cycle up through the one-off items.
+ let oneOffs = getOneOffs();
+ for (let i = oneOffs.length - 1; i >= 0; --i) {
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ }
+
+ // One more alt+down keypress and nothing should be selected.
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // another one and the last one-off should be selected.
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ is(textbox.selectedButton, oneOffs[oneOffs.length - 1],
+ "the last one-off button should be selected");
+
+ // Cleanup for the next test.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(!textbox.selectedButton, "no one-off should be selected anymore");
+});
+
+add_task(function* test_tab_and_arrows() {
+ // Check the initial state is as expected.
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue, "the textfield value should be unmodified");
+
+ // After pressing down, the first sugggestion should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(searchPopup.selectedIndex, 0, "first suggestion should be selected");
+ is(textbox.value, kValues[0], "the textfield value should have changed");
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // After pressing tab, the first one-off should be selected,
+ // and the first suggestion still selected.
+ let oneOffs = getOneOffs();
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should be selected");
+ is(searchPopup.selectedIndex, 0, "first suggestion should still be selected");
+
+ // After pressing down, the second suggestion should be selected,
+ // and the first one-off still selected.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should still be selected");
+ is(searchPopup.selectedIndex, 1, "second suggestion should be selected");
+
+ // After pressing up, the first suggestion should be selected again,
+ // and the first one-off still selected.
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should still be selected");
+ is(searchPopup.selectedIndex, 0, "second suggestion should be selected again");
+
+ // After pressing up again, we should have no suggestion selected anymore,
+ // the textfield value back to the user-typed value, and still the first one-off
+ // selected.
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, kUserValue,
+ "the textfield value should be back to user typed value");
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should still be selected");
+
+ // Now pressing down should select the second one-off.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton, oneOffs[1],
+ "the second one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "there should still be no selected suggestion");
+
+ // Finally close the panel.
+ let promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+});
+
+add_task(function* test_open_search() {
+ let rootDir = getRootDirectory(gTestPath);
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, rootDir + "opensearch.html");
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ searchbar.focus();
+ yield promise;
+
+ let engines = getOpenSearchItems();
+ is(engines.length, 2, "the opensearch.html page exposes 2 engines")
+
+ // Check that there's initially no selection.
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ ok(!textbox.selectedButton, "no button should be selected");
+
+ // Pressing up once selects the setting button...
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // ...and then pressing up selects open search engines.
+ for (let i = engines.length; i; --i) {
+ EventUtils.synthesizeKey("VK_UP", {});
+ let selectedButton = textbox.selectedButton;
+ is(selectedButton, engines[i - 1],
+ "the engine #" + i + " should be selected");
+ ok(selectedButton.classList.contains("addengine-item"),
+ "the button is themed as an engine item");
+ }
+
+ // Pressing up again should select the last one-off button.
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton, getOneOffs().pop(),
+ "the last one-off button should be selected");
+
+ info("now check that the down key navigates open search items as expected");
+ for (let i = 0; i < engines.length; ++i) {
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton, engines[i],
+ "the engine #" + (i + 1) + " should be selected");
+ }
+
+ // Pressing down on the last engine item selects the settings button.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/search/test/browser_searchbar_openpopup.js b/browser/components/search/test/browser_searchbar_openpopup.js
new file mode 100644
index 000000000..befc8f142
--- /dev/null
+++ b/browser/components/search/test/browser_searchbar_openpopup.js
@@ -0,0 +1,521 @@
+// Tests that the suggestion popup appears at the right times in response to
+// focus and user events (mouse, keyboard, drop).
+
+// Instead of loading EventUtils.js into the test scope in browser-test.js for all tests,
+// we only need EventUtils.js for a few files which is why we are using loadSubScript.
+var EventUtils = {};
+this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+const searchbar = document.getElementById("searchbar");
+const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid", "searchbar-search-button");
+const goButton = document.getAnonymousElementByAttribute(searchbar, "anonid", "search-go-button");
+const textbox = searchbar._textbox;
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const kValues = ["long text", "long text 2", "long text 3"];
+
+const isWindows = Services.appinfo.OS == "WINNT";
+const mouseDown = isWindows ? 2 : 1;
+const mouseUp = isWindows ? 4 : 2;
+const utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+const scale = utils.screenPixelsPerCSSPixel;
+
+function* synthesizeNativeMouseClick(aElement) {
+ let rect = aElement.getBoundingClientRect();
+ let win = aElement.ownerGlobal;
+ let x = win.mozInnerScreenX + (rect.left + rect.right) / 2;
+ let y = win.mozInnerScreenY + (rect.top + rect.bottom) / 2;
+
+ // Wait for the mouseup event to occur before continuing.
+ return new Promise((resolve, reject) => {
+ function eventOccurred(e)
+ {
+ aElement.removeEventListener("mouseup", eventOccurred, true);
+ resolve();
+ }
+
+ aElement.addEventListener("mouseup", eventOccurred, true);
+
+ utils.sendNativeMouseEvent(x * scale, y * scale, mouseDown, 0, null);
+ utils.sendNativeMouseEvent(x * scale, y * scale, mouseUp, 0, null);
+ });
+}
+
+add_task(function* init() {
+ yield promiseNewEngine("testEngine.xml");
+
+ // First cleanup the form history in case other tests left things there.
+ yield new Promise((resolve, reject) => {
+ info("cleanup the search history");
+ searchbar.FormHistory.update({op: "remove", fieldname: "searchbar-history"},
+ {handleCompletion: resolve,
+ handleError: reject});
+ });
+
+ yield new Promise((resolve, reject) => {
+ info("adding search history values: " + kValues);
+ let ops = kValues.map(value => { return {op: "add",
+ fieldname: "searchbar-history",
+ value: value}
+ });
+ searchbar.FormHistory.update(ops, {
+ handleCompletion: function() {
+ registerCleanupFunction(() => {
+ info("removing search history values: " + kValues);
+ let ops =
+ kValues.map(value => { return {op: "remove",
+ fieldname: "searchbar-history",
+ value: value}
+ });
+ searchbar.FormHistory.update(ops);
+ });
+ resolve();
+ },
+ handleError: reject
+ });
+ });
+});
+
+// Adds a task that shouldn't show the search suggestions popup.
+function add_no_popup_task(task) {
+ add_task(function*() {
+ let sawPopup = false;
+ function listener() {
+ sawPopup = true;
+ }
+
+ info("Entering test " + task.name);
+ searchPopup.addEventListener("popupshowing", listener, false);
+ yield Task.spawn(task);
+ searchPopup.removeEventListener("popupshowing", listener, false);
+ ok(!sawPopup, "Shouldn't have seen the suggestions popup");
+ info("Leaving test " + task.name);
+ });
+}
+
+// Simulates the full set of events for a context click
+function context_click(target) {
+ for (let event of ["mousedown", "contextmenu", "mouseup"])
+ EventUtils.synthesizeMouseAtCenter(target, { type: event, button: 2 });
+}
+
+// Right clicking the icon should not open the popup.
+add_no_popup_task(function* open_icon_context() {
+ gURLBar.focus();
+ let toolbarPopup = document.getElementById("toolbar-context-menu");
+
+ let promise = promiseEvent(toolbarPopup, "popupshown");
+ context_click(searchIcon);
+ yield promise;
+
+ promise = promiseEvent(toolbarPopup, "popuphidden");
+ toolbarPopup.hidePopup();
+ yield promise;
+});
+
+// With no text in the search box left clicking the icon should open the popup.
+// Clicking the icon again should hide the popup and not show it again.
+add_task(function* open_empty() {
+ gURLBar.focus();
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Clicking icon");
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ yield promise;
+ is(searchPopup.getAttribute("showonlysettings"), "true", "Should only show the settings");
+ is(textbox.mController.searchString, "", "Should be an empty search string");
+
+ // By giving the textbox some text any next attempt to open the search popup
+ // from the click handler will try to search for this text.
+ textbox.value = "foo";
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+
+ info("Hiding popup");
+ yield synthesizeNativeMouseClick(searchIcon);
+ yield promise;
+
+ is(textbox.mController.searchString, "", "Should not have started to search for the new text");
+
+ // Cancel the search if it started.
+ if (textbox.mController.searchString != "") {
+ textbox.mController.stopSearch();
+ }
+
+ textbox.value = "";
+});
+
+// With no text in the search box left clicking it should not open the popup.
+add_no_popup_task(function* click_doesnt_open_popup() {
+ gURLBar.focus();
+
+ EventUtils.synthesizeMouseAtCenter(textbox, {});
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 0, "Should have selected all of the text");
+});
+
+// Left clicking in a non-empty search box when unfocused should focus it and open the popup.
+add_task(function* click_opens_popup() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(textbox, {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+
+ textbox.value = "";
+});
+
+// Right clicking in a non-empty search box when unfocused should open the edit context menu.
+add_no_popup_task(function* right_click_doesnt_open_popup() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let contextPopup = document.getAnonymousElementByAttribute(textbox.inputField.parentNode, "anonid", "input-box-contextmenu");
+ let promise = promiseEvent(contextPopup, "popupshown");
+ context_click(textbox);
+ yield promise;
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(contextPopup, "popuphidden");
+ contextPopup.hidePopup();
+ yield promise;
+
+ textbox.value = "";
+});
+
+// Moving focus away from the search box should close the popup
+add_task(function* focus_change_closes_popup() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(textbox, {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ let promise2 = promiseEvent(searchbar, "blur");
+ EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+ yield promise;
+ yield promise2;
+
+ textbox.value = "";
+});
+
+// Moving focus away from the search box should close the small popup
+add_task(function* focus_change_closes_small_popup() {
+ gURLBar.focus();
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ // For some reason sending the mouse event immediately doesn't open the popup.
+ SimpleTest.executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ });
+ yield promise;
+ is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ let promise2 = promiseEvent(searchbar, "blur");
+ EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+ yield promise;
+ yield promise2;
+});
+
+// Pressing escape should close the popup.
+add_task(function* escape_closes_popup() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(textbox, {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+
+ textbox.value = "";
+});
+
+// Pressing contextmenu should close the popup.
+add_task(function* contextmenu_closes_popup() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(textbox, {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+
+ // synthesizeKey does not work with VK_CONTEXT_MENU (bug 1127368)
+ EventUtils.synthesizeMouseAtCenter(textbox, { type: "contextmenu", button: null });
+
+ yield promise;
+
+ let contextPopup =
+ document.getAnonymousElementByAttribute(textbox.inputField.parentNode,
+ "anonid", "input-box-contextmenu");
+ promise = promiseEvent(contextPopup, "popuphidden");
+ contextPopup.hidePopup();
+ yield promise;
+
+ textbox.value = "";
+});
+
+// Tabbing to the search box should open the popup if it contains text.
+add_task(function* tab_opens_popup() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+
+ textbox.value = "";
+});
+
+// Tabbing to the search box should not open the popup if it doesn't contain text.
+add_no_popup_task(function* tab_doesnt_open_popup() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ EventUtils.synthesizeKey("VK_TAB", {});
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ textbox.value = "";
+});
+
+// Switching back to the window when the search box has focus from mouse should not open the popup.
+add_task(function* refocus_window_doesnt_open_popup_mouse() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(searchbar, {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ let newWin = OpenBrowserWindow();
+ yield new Promise(resolve => waitForFocus(resolve, newWin));
+ yield promise;
+
+ function listener() {
+ ok(false, "Should not have shown the popup.");
+ }
+ searchPopup.addEventListener("popupshowing", listener, false);
+
+ promise = promiseEvent(searchbar, "focus");
+ newWin.close();
+ yield promise;
+
+ // Wait a few ticks to allow any focus handlers to show the popup if they are going to.
+ yield new Promise(resolve => executeSoon(resolve));
+ yield new Promise(resolve => executeSoon(resolve));
+ yield new Promise(resolve => executeSoon(resolve));
+
+ searchPopup.removeEventListener("popupshowing", listener, false);
+ textbox.value = "";
+});
+
+// Switching back to the window when the search box has focus from keyboard should not open the popup.
+add_task(function* refocus_window_doesnt_open_popup_keyboard() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ let newWin = OpenBrowserWindow();
+ yield new Promise(resolve => waitForFocus(resolve, newWin));
+ yield promise;
+
+ function listener() {
+ ok(false, "Should not have shown the popup.");
+ }
+ searchPopup.addEventListener("popupshowing", listener, false);
+
+ promise = promiseEvent(searchbar, "focus");
+ newWin.close();
+ yield promise;
+
+ // Wait a few ticks to allow any focus handlers to show the popup if they are going to.
+ yield new Promise(resolve => executeSoon(resolve));
+ yield new Promise(resolve => executeSoon(resolve));
+ yield new Promise(resolve => executeSoon(resolve));
+
+ searchPopup.removeEventListener("popupshowing", listener, false);
+ textbox.value = "";
+});
+
+// Clicking the search go button shouldn't open the popup
+add_no_popup_task(function* search_go_doesnt_open_popup() {
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gURLBar.focus();
+ textbox.value = "foo";
+ searchbar.updateGoButtonVisibility();
+
+ let promise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ EventUtils.synthesizeMouseAtCenter(goButton, {});
+ yield promise;
+
+ textbox.value = "";
+ gBrowser.removeCurrentTab();
+});
+
+// Clicks outside the search popup should close the popup but not consume the click.
+add_task(function* dont_consume_clicks() {
+ gURLBar.focus();
+ textbox.value = "foo";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(textbox, {});
+ yield promise;
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ is(textbox.selectionStart, 0, "Should have selected all of the text");
+ is(textbox.selectionEnd, 3, "Should have selected all of the text");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ yield synthesizeNativeMouseClick(gURLBar);
+ yield promise;
+
+ is(Services.focus.focusedElement, gURLBar.inputField, "Should have focused the URL bar");
+
+ textbox.value = "";
+});
+
+// Dropping text to the searchbar should open the popup
+add_task(function* drop_opens_popup() {
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeDrop(searchIcon, textbox.inputField, [[ {type: "text/plain", data: "foo" } ]], "move", window);
+ yield promise;
+
+ isnot(searchPopup.getAttribute("showonlysettings"), "true", "Should show the full popup");
+ is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
+ promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+
+ textbox.value = "";
+});
+
+// Moving the caret using the cursor keys should not close the popup.
+add_task(function* dont_rollup_oncaretmove() {
+ gURLBar.focus();
+ textbox.value = "long text";
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(textbox, {});
+ yield promise;
+
+ // Deselect the text
+ EventUtils.synthesizeKey("VK_RIGHT", {});
+ is(textbox.selectionStart, 9, "Should have moved the caret (selectionStart after deselect right)");
+ is(textbox.selectionEnd, 9, "Should have moved the caret (selectionEnd after deselect right)");
+ is(searchPopup.state, "open", "Popup should still be open");
+
+ EventUtils.synthesizeKey("VK_LEFT", {});
+ is(textbox.selectionStart, 8, "Should have moved the caret (selectionStart after left)");
+ is(textbox.selectionEnd, 8, "Should have moved the caret (selectionEnd after left)");
+ is(searchPopup.state, "open", "Popup should still be open");
+
+ EventUtils.synthesizeKey("VK_RIGHT", {});
+ is(textbox.selectionStart, 9, "Should have moved the caret (selectionStart after right)");
+ is(textbox.selectionEnd, 9, "Should have moved the caret (selectionEnd after right)");
+ is(searchPopup.state, "open", "Popup should still be open");
+
+ // Ensure caret movement works while a suggestion is selected.
+ is(textbox.popup.selectedIndex, -1, "No selected item in list");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.popup.selectedIndex, 0, "Selected item in list");
+ is(textbox.selectionStart, 9, "Should have moved the caret to the end (selectionStart after selection)");
+ is(textbox.selectionEnd, 9, "Should have moved the caret to the end (selectionEnd after selection)");
+
+ EventUtils.synthesizeKey("VK_LEFT", {});
+ is(textbox.selectionStart, 8, "Should have moved the caret again (selectionStart after left)");
+ is(textbox.selectionEnd, 8, "Should have moved the caret again (selectionEnd after left)");
+ is(searchPopup.state, "open", "Popup should still be open");
+
+ EventUtils.synthesizeKey("VK_LEFT", {});
+ is(textbox.selectionStart, 7, "Should have moved the caret (selectionStart after left)");
+ is(textbox.selectionEnd, 7, "Should have moved the caret (selectionEnd after left)");
+ is(searchPopup.state, "open", "Popup should still be open");
+
+ EventUtils.synthesizeKey("VK_RIGHT", {});
+ is(textbox.selectionStart, 8, "Should have moved the caret (selectionStart after right)");
+ is(textbox.selectionEnd, 8, "Should have moved the caret (selectionEnd after right)");
+ is(searchPopup.state, "open", "Popup should still be open");
+
+ if (navigator.platform.indexOf("Mac") == -1) {
+ EventUtils.synthesizeKey("VK_HOME", {});
+ is(textbox.selectionStart, 0, "Should have moved the caret (selectionStart after home)");
+ is(textbox.selectionEnd, 0, "Should have moved the caret (selectionEnd after home)");
+ is(searchPopup.state, "open", "Popup should still be open");
+ }
+
+ // Close the popup again
+ promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ yield promise;
+
+ textbox.value = "";
+});
diff --git a/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js b/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js
new file mode 100644
index 000000000..37ca32cf2
--- /dev/null
+++ b/browser/components/search/test/browser_searchbar_smallpanel_keyboard_navigation.js
@@ -0,0 +1,354 @@
+// Tests that keyboard navigation in the search panel works as designed.
+
+const searchbar = document.getElementById("searchbar");
+const textbox = searchbar._textbox;
+const searchPopup = document.getElementById("PopupSearchAutoComplete");
+const oneOffsContainer =
+ document.getAnonymousElementByAttribute(searchPopup, "anonid",
+ "search-one-off-buttons");
+const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid",
+ "searchbar-search-button");
+
+const kValues = ["foo1", "foo2", "foo3"];
+
+function getOpenSearchItems() {
+ let os = [];
+
+ let addEngineList =
+ document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
+ "add-engines");
+ for (let item = addEngineList.firstChild; item; item = item.nextSibling)
+ os.push(item);
+
+ return os;
+}
+
+add_task(function* init() {
+ yield promiseNewEngine("testEngine.xml");
+
+ // First cleanup the form history in case other tests left things there.
+ yield new Promise((resolve, reject) => {
+ info("cleanup the search history");
+ searchbar.FormHistory.update({op: "remove", fieldname: "searchbar-history"},
+ {handleCompletion: resolve,
+ handleError: reject});
+ });
+
+ yield new Promise((resolve, reject) => {
+ info("adding search history values: " + kValues);
+ let ops = kValues.map(value => { return {op: "add",
+ fieldname: "searchbar-history",
+ value: value}
+ });
+ searchbar.FormHistory.update(ops, {
+ handleCompletion: function() {
+ registerCleanupFunction(() => {
+ info("removing search history values: " + kValues);
+ let ops =
+ kValues.map(value => { return {op: "remove",
+ fieldname: "searchbar-history",
+ value: value}
+ });
+ searchbar.FormHistory.update(ops);
+ });
+ resolve();
+ },
+ handleError: reject
+ });
+ });
+});
+
+
+add_task(function* test_arrows() {
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ yield promise;
+info("textbox.mController.searchString = " + textbox.mController.searchString);
+ is(textbox.mController.searchString, "", "The search string should be empty");
+
+ // Check the initial state of the panel before sending keyboard events.
+ is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
+ // Having suggestions populated (but hidden) is important, because if there
+ // are none we can't ensure the keyboard events don't reach them.
+ is(searchPopup.view.rowCount, kValues.length, "There should be 3 suggestions");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+
+ // The tests will be less meaningful if the first, second, last, and
+ // before-last one-off buttons aren't different. We should always have more
+ // than 4 default engines, but it's safer to check this assumption.
+ let oneOffs = getOneOffs();
+ ok(oneOffs.length >= 4, "we have at least 4 one-off buttons displayed")
+
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // Pressing should select the first one-off.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, "", "the textfield value should be unmodified");
+
+ // now cycle through the one-off items, the first one is already selected.
+ for (let i = 0; i < oneOffs.length; ++i) {
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ }
+
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ // We should now be back to the initial situation.
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ info("now test the up arrow key");
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // cycle through the one-off items, the first one is already selected.
+ for (let i = oneOffs.length; i; --i) {
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton, oneOffs[i - 1],
+ "the one-off button #" + i + " should be selected");
+ }
+
+ // Another press on up should clear the one-off selection.
+ EventUtils.synthesizeKey("VK_UP", {});
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, "", "the textfield value should be unmodified");
+});
+
+add_task(function* test_tab() {
+ is(Services.focus.focusedElement, textbox.inputField,
+ "the search bar should be focused"); // from the previous test.
+
+ let oneOffs = getOneOffs();
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // Pressing tab should select the first one-off without selecting suggestions.
+ // now cycle through the one-off items, the first one is already selected.
+ for (let i = 0; i < oneOffs.length; ++i) {
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ }
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, "", "the textfield value should be unmodified");
+
+ // One more <tab> selects the settings button.
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // Pressing tab again should close the panel...
+ let promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ yield promise;
+
+ // ... and move the focus out of the searchbox.
+ isnot(Services.focus.focusedElement, textbox.inputField,
+ "the search bar no longer be focused");
+});
+
+add_task(function* test_shift_tab() {
+ // First reopen the panel.
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ SimpleTest.executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ });
+ yield promise;
+
+ let oneOffs = getOneOffs();
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
+
+ // Press up once to select the last button.
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // Press up again to select the last one-off button.
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ // Pressing shift+tab should cycle through the one-off items.
+ for (let i = oneOffs.length - 1; i >= 0; --i) {
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ if (i)
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
+ }
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, "", "the textfield value should be unmodified");
+
+ // Pressing shift+tab again should close the panel...
+ promise = promiseEvent(searchPopup, "popuphidden");
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
+ yield promise;
+
+ // ... and move the focus out of the searchbox.
+ isnot(Services.focus.focusedElement, textbox.inputField,
+ "the search bar no longer be focused");
+});
+
+add_task(function* test_alt_down() {
+ // First reopen the panel.
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ SimpleTest.executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ });
+ yield promise;
+
+ // and check it's in a correct initial state.
+ is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, "", "the textfield value should be unmodified");
+
+ // Pressing alt+down should select the first one-off without selecting suggestions
+ // and cycle through the one-off items.
+ let oneOffs = getOneOffs();
+ for (let i = 0; i < oneOffs.length; ++i) {
+ EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ }
+
+ // One more alt+down keypress and nothing should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // another one and the first one-off should be selected.
+ EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should be selected");
+
+ // Clear the selection with an alt+up keypress
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+});
+
+add_task(function* test_alt_up() {
+ // Check the initial state of the panel
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, "", "the textfield value should be unmodified");
+
+ // Pressing alt+up should select the last one-off without selecting suggestions
+ // and cycle up through the one-off items.
+ let oneOffs = getOneOffs();
+ for (let i = oneOffs.length - 1; i >= 0; --i) {
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ is(textbox.selectedButton, oneOffs[i],
+ "the one-off button #" + (i + 1) + " should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ }
+
+ // One more alt+down keypress and nothing should be selected.
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+
+ // another one and the last one-off should be selected.
+ EventUtils.synthesizeKey("VK_UP", {altKey: true});
+ is(textbox.selectedButton, oneOffs[oneOffs.length - 1],
+ "the last one-off button should be selected");
+
+ // Cleanup for the next test.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ ok(!textbox.selectedButton, "no one-off should be selected anymore");
+});
+
+add_task(function* test_tab_and_arrows() {
+ // Check the initial state is as expected.
+ ok(!textbox.selectedButton, "no one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ is(textbox.value, "", "the textfield value should be unmodified");
+
+ // After pressing down, the first one-off should be selected.
+ let oneOffs = getOneOffs();
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+
+ // After pressing tab, the second one-off should be selected.
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(textbox.selectedButton, oneOffs[1],
+ "the second one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+
+ // After pressing up, the first one-off should be selected again.
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton, oneOffs[0],
+ "the first one-off button should be selected");
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+
+ // Finally close the panel.
+ let promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+});
+
+add_task(function* test_open_search() {
+ let rootDir = getRootDirectory(gTestPath);
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, rootDir + "opensearch.html");
+
+ let promise = promiseEvent(searchPopup, "popupshown");
+ info("Opening search panel");
+ EventUtils.synthesizeMouseAtCenter(searchIcon, {});
+ yield promise;
+ is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
+
+ let engines = getOpenSearchItems();
+ is(engines.length, 2, "the opensearch.html page exposes 2 engines")
+
+ // Check that there's initially no selection.
+ is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
+ ok(!textbox.selectedButton, "no button should be selected");
+
+ // Pressing up once selects the setting button...
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ // ...and then pressing up selects open search engines.
+ for (let i = engines.length; i; --i) {
+ EventUtils.synthesizeKey("VK_UP", {});
+ let selectedButton = textbox.selectedButton;
+ is(selectedButton, engines[i - 1],
+ "the engine #" + i + " should be selected");
+ ok(selectedButton.classList.contains("addengine-item"),
+ "the button is themed as an engine item");
+ }
+
+ // Pressing up again should select the last one-off button.
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(textbox.selectedButton, getOneOffs().pop(),
+ "the last one-off button should be selected");
+
+ info("now check that the down key navigates open search items as expected");
+ for (let i = 0; i < engines.length; ++i) {
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton, engines[i],
+ "the engine #" + (i + 1) + " should be selected");
+ }
+
+ // Pressing down on the last engine item selects the settings button.
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
+ "the settings item should be selected");
+
+ promise = promiseEvent(searchPopup, "popuphidden");
+ searchPopup.hidePopup();
+ yield promise;
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/search/test/browser_webapi.js b/browser/components/search/test/browser_webapi.js
new file mode 100644
index 000000000..d8161ffbe
--- /dev/null
+++ b/browser/components/search/test/browser_webapi.js
@@ -0,0 +1,92 @@
+var ROOT = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
+const searchBundle = Services.strings.createBundle("chrome://global/locale/search/search.properties");
+const brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+const brandName = brandBundle.GetStringFromName("brandShortName");
+
+function getString(key, ...params) {
+ return searchBundle.formatStringFromName(key, params, params.length);
+}
+
+function AddSearchProvider(...args) {
+ return gBrowser.addTab(ROOT + "webapi.html?" + encodeURIComponent(JSON.stringify(args)));
+}
+
+function promiseDialogOpened() {
+ return new Promise((resolve, reject) => {
+ Services.wm.addListener({
+ onOpenWindow: function(xulWin) {
+ Services.wm.removeListener(this);
+
+ let win = xulWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ waitForFocus(() => {
+ if (win.location == "chrome://global/content/commonDialog.xul")
+ resolve(win)
+ else
+ reject();
+ }, win);
+ }
+ });
+ });
+}
+
+add_task(function* test_working() {
+ gBrowser.selectedTab = AddSearchProvider(ROOT + "testEngine.xml");
+
+ let dialog = yield promiseDialogOpened();
+ is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
+ is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
+ "Should have seen the right install message");
+ dialog.document.documentElement.cancelDialog();
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* test_HTTP() {
+ gBrowser.selectedTab = AddSearchProvider(ROOT.replace("http:", "HTTP:") + "testEngine.xml");
+
+ let dialog = yield promiseDialogOpened();
+ is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
+ is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
+ "Should have seen the right install message");
+ dialog.document.documentElement.cancelDialog();
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* test_relative() {
+ gBrowser.selectedTab = AddSearchProvider("testEngine.xml");
+
+ let dialog = yield promiseDialogOpened();
+ is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
+ is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
+ "Should have seen the right install message");
+ dialog.document.documentElement.cancelDialog();
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* test_invalid() {
+ gBrowser.selectedTab = AddSearchProvider("z://foobar");
+
+ let dialog = yield promiseDialogOpened();
+ is(dialog.args.promptType, "alert", "Should see the alert dialog.");
+ is(dialog.args.text, getString("error_invalid_engine_msg", brandName),
+ "Should have seen the right error message")
+ dialog.document.documentElement.acceptDialog();
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function* test_missing() {
+ let url = ROOT + "foobar.xml";
+ gBrowser.selectedTab = AddSearchProvider(url);
+
+ let dialog = yield promiseDialogOpened();
+ is(dialog.args.promptType, "alert", "Should see the alert dialog.");
+ is(dialog.args.text, getString("error_loading_engine_msg2", brandName, url),
+ "Should have seen the right error message")
+ dialog.document.documentElement.acceptDialog();
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/search/test/browser_yahoo.js b/browser/components/search/test/browser_yahoo.js
new file mode 100644
index 000000000..f45b47d0c
--- /dev/null
+++ b/browser/components/search/test/browser_yahoo.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Yahoo search plugin URLs
+ */
+
+"use strict";
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+function test() {
+ let engine = Services.search.getEngineByName("Yahoo");
+ ok(engine, "Yahoo");
+
+ let base = "https://search.yahoo.com/yhs/search?p=foo&ei=UTF-8&hspart=mozilla";
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base + "&hsimp=yhs-001", "Check search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "searchbar").uri.spec;
+ is(url, base + "&hsimp=yhs-001", "Check search bar search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "keyword").uri.spec;
+ is(url, base + "&hsimp=yhs-002", "Check keyword search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "homepage").uri.spec;
+ is(url, base + "&hsimp=yhs-003", "Check homepage search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "newtab").uri.spec;
+ is(url, base + "&hsimp=yhs-004", "Check newtab search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
+ is(url, base + "&hsimp=yhs-005", "Check context menu search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "system").uri.spec;
+ is(url, base + "&hsimp=yhs-007", "Check system search URL for 'foo'");
+ url = engine.getSubmission("foo", null, "invalid").uri.spec;
+ is(url, base + "&hsimp=yhs-001", "Check invalid URL for 'foo'");
+
+ // Check search suggestion URL.
+ url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
+ is(url, "https://search.yahoo.com/sugg/ff?output=fxjson&appid=ffd&command=foo", "Check search suggestion URL for 'foo'");
+
+ // Check all other engine properties.
+ const EXPECTED_ENGINE = {
+ name: "Yahoo",
+ alias: null,
+ description: "Yahoo Search",
+ searchForm: "https://search.yahoo.com/yhs/search?p=&ei=UTF-8&hspart=mozilla&hsimp=yhs-001",
+ hidden: false,
+ wrappedJSObject: {
+ queryCharset: "UTF-8",
+ "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC",
+ _urls : [
+ {
+ type: "application/x-suggestions+json",
+ method: "GET",
+ template: "https://search.yahoo.com/sugg/ff",
+ params: [
+ {
+ name: "output",
+ value: "fxjson",
+ purpose: undefined,
+ },
+ {
+ name: "appid",
+ value: "ffd",
+ purpose: undefined,
+ },
+ {
+ name: "command",
+ value: "{searchTerms}",
+ purpose: undefined,
+ },
+ ],
+ },
+ {
+ type: "text/html",
+ method: "GET",
+ template: "https://search.yahoo.com/yhs/search",
+ params: [
+ {
+ name: "p",
+ value: "{searchTerms}",
+ purpose: undefined,
+ },
+ {
+ name: "ei",
+ value: "UTF-8",
+ purpose: undefined,
+ },
+ {
+ name: "hspart",
+ value: "mozilla",
+ purpose: undefined,
+ },
+ {
+ name: "hsimp",
+ value: "yhs-001",
+ purpose: "searchbar",
+ },
+ {
+ name: "hsimp",
+ value: "yhs-002",
+ purpose: "keyword",
+ },
+ {
+ name: "hsimp",
+ value: "yhs-003",
+ purpose: "homepage",
+ },
+ {
+ name: "hsimp",
+ value: "yhs-004",
+ purpose: "newtab",
+ },
+ {
+ name: "hsimp",
+ value: "yhs-005",
+ purpose: "contextmenu",
+ },
+ {
+ name: "hsimp",
+ value: "yhs-007",
+ purpose: "system",
+ },
+ ],
+ mozparams: {},
+ },
+ ],
+ },
+ };
+
+ isSubObjectOf(EXPECTED_ENGINE, engine, "Yahoo");
+}
diff --git a/browser/components/search/test/browser_yahoo_behavior.js b/browser/components/search/test/browser_yahoo_behavior.js
new file mode 100644
index 000000000..5b2d61422
--- /dev/null
+++ b/browser/components/search/test/browser_yahoo_behavior.js
@@ -0,0 +1,166 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Test Yahoo search plugin URLs
+ */
+
+"use strict";
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+
+function test() {
+ let engine = Services.search.getEngineByName("Yahoo");
+ ok(engine, "Yahoo is installed");
+
+ let previouslySelectedEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+ engine.alias = "y";
+
+ let base = "https://search.yahoo.com/yhs/search?p=foo&ei=UTF-8&hspart=mozilla";
+ let url;
+
+ // Test search URLs (including purposes).
+ url = engine.getSubmission("foo").uri.spec;
+ is(url, base + "&hsimp=yhs-001", "Check search URL for 'foo'");
+
+ waitForExplicitFinish();
+
+ var gCurrTest;
+ var gTests = [
+ {
+ name: "context menu search",
+ searchURL: base + "&hsimp=yhs-005",
+ run: function () {
+ // Simulate a contextmenu search
+ // FIXME: This is a bit "low-level"...
+ BrowserSearch.loadSearch("foo", false, "contextmenu");
+ }
+ },
+ {
+ name: "keyword search",
+ searchURL: base + "&hsimp=yhs-002",
+ run: function () {
+ gURLBar.value = "? foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "keyword search with alias",
+ searchURL: base + "&hsimp=yhs-002",
+ run: function () {
+ gURLBar.value = "y foo";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "search bar search",
+ searchURL: base + "&hsimp=yhs-001",
+ run: function () {
+ let sb = BrowserSearch.searchBar;
+ sb.focus();
+ sb.value = "foo";
+ registerCleanupFunction(function () {
+ sb.value = "";
+ });
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }
+ },
+ {
+ name: "new tab search",
+ searchURL: base + "&hsimp=yhs-004",
+ run: function () {
+ function doSearch(doc) {
+ // Re-add the listener, and perform a search
+ gBrowser.addProgressListener(listener);
+ doc.getElementById("newtab-search-text").value = "foo";
+ doc.getElementById("newtab-search-submit").click();
+ }
+
+ // load about:newtab, but remove the listener first so it doesn't
+ // get in the way
+ gBrowser.removeProgressListener(listener);
+ gBrowser.loadURI("about:newtab");
+ info("Waiting for about:newtab load");
+ tab.linkedBrowser.addEventListener("load", function load(event) {
+ if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ tab.linkedBrowser.removeEventListener("load", load, true);
+
+ // Observe page setup
+ let win = gBrowser.contentWindow;
+ if (win.gSearch.currentEngineName ==
+ Services.search.currentEngine.name) {
+ doSearch(win.document);
+ }
+ else {
+ info("Waiting for newtab search init");
+ win.addEventListener("ContentSearchService", function done(event) {
+ info("Got newtab search event " + event.detail.type);
+ if (event.detail.type == "State") {
+ win.removeEventListener("ContentSearchService", done);
+ // Let gSearch respond to the event before continuing.
+ executeSoon(() => doSearch(win.document));
+ }
+ });
+ }
+ }, true);
+ }
+ }
+ ];
+
+ function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ info("Running : " + gCurrTest.name);
+ executeSoon(gCurrTest.run);
+ } else {
+ finish();
+ }
+ }
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onStateChange(webProgress, req, flags, status) {
+ info("onStateChange");
+ // Only care about top-level document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart) || !webProgress.isTopLevel)
+ return;
+
+ if (req.originalURI.spec == "about:blank")
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+
+ executeSoon(nextTest);
+ }
+ }
+
+ registerCleanupFunction(function () {
+ engine.alias = undefined;
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ Services.search.currentEngine = previouslySelectedEngine;
+ });
+
+ tab.linkedBrowser.addEventListener("load", function load() {
+ tab.linkedBrowser.removeEventListener("load", load, true);
+ gBrowser.addProgressListener(listener);
+ nextTest();
+ }, true);
+}
diff --git a/browser/components/search/test/head.js b/browser/components/search/test/head.js
new file mode 100644
index 000000000..de27b5e1e
--- /dev/null
+++ b/browser/components/search/test/head.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://gre/modules/Promise.jsm");
+
+/**
+ * Recursively compare two objects and check that every property of expectedObj has the same value
+ * on actualObj.
+ */
+function isSubObjectOf(expectedObj, actualObj, name) {
+ for (let prop in expectedObj) {
+ if (typeof expectedObj[prop] == 'function')
+ continue;
+ if (expectedObj[prop] instanceof Object) {
+ is(actualObj[prop].length, expectedObj[prop].length, name + "[" + prop + "]");
+ isSubObjectOf(expectedObj[prop], actualObj[prop], name + "[" + prop + "]");
+ } else {
+ is(actualObj[prop], expectedObj[prop], name + "[" + prop + "]");
+ }
+ }
+}
+
+function getLocale() {
+ const localePref = "general.useragent.locale";
+ return getLocalizedPref(localePref, Services.prefs.getCharPref(localePref));
+}
+
+/**
+ * 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) {
+ try {
+ return Services.prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
+ } catch (ex) {
+ return aDefault;
+ }
+}
+
+function promiseEvent(aTarget, aEventName, aPreventDefault) {
+ function cancelEvent(event) {
+ if (aPreventDefault) {
+ event.preventDefault();
+ }
+
+ return true;
+ }
+
+ return BrowserTestUtils.waitForEvent(aTarget, aEventName, false, cancelEvent);
+}
+
+/**
+ * Adds a new search engine to the search service and confirms it completes.
+ *
+ * @param {String} basename The file to load that contains the search engine
+ * details.
+ * @param {Object} [options] Options for the test:
+ * - {String} [iconURL] The icon to use for the search engine.
+ * - {Boolean} [setAsCurrent] Whether to set the new engine to be the
+ * current engine or not.
+ * - {String} [testPath] Used to override the current test path if this
+ * file is used from a different directory.
+ * @returns {Promise} The promise is resolved once the engine is added, or
+ * rejected if the addition failed.
+ */
+function promiseNewEngine(basename, options = {}) {
+ return new Promise((resolve, reject) => {
+ // Default the setAsCurrent option to true.
+ let setAsCurrent =
+ options.setAsCurrent == undefined ? true : options.setAsCurrent;
+ info("Waiting for engine to be added: " + basename);
+ Services.search.init({
+ onInitComplete: function() {
+ let url = getRootDirectory(options.testPath || gTestPath) + basename;
+ let current = Services.search.currentEngine;
+ Services.search.addEngine(url, null, options.iconURL || "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ if (setAsCurrent) {
+ Services.search.currentEngine = engine;
+ }
+ registerCleanupFunction(() => {
+ if (setAsCurrent) {
+ Services.search.currentEngine = current;
+ }
+ Services.search.removeEngine(engine);
+ info("Search engine removed: " + basename);
+ });
+ resolve(engine);
+ },
+ onError: function (errCode) {
+ ok(false, "addEngine failed with error code " + errCode);
+ reject();
+ }
+ });
+ }
+ });
+ });
+}
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url)
+{
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url)
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+
+ return loaded;
+}
+
+// Get an array of the one-off buttons.
+function getOneOffs() {
+ let oneOffs = [];
+ let searchPopup = document.getElementById("PopupSearchAutoComplete");
+ let oneOffsContainer =
+ document.getAnonymousElementByAttribute(searchPopup, "anonid",
+ "search-one-off-buttons");
+ let oneOff =
+ document.getAnonymousElementByAttribute(oneOffsContainer, "anonid",
+ "search-panel-one-offs");
+ for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
+ if (oneOff.nodeType == Node.ELEMENT_NODE) {
+ if (oneOff.classList.contains("dummy") ||
+ oneOff.classList.contains("search-setting-button-compact"))
+ break;
+ oneOffs.push(oneOff);
+ }
+ }
+ return oneOffs;
+}
diff --git a/browser/components/search/test/opensearch.html b/browser/components/search/test/opensearch.html
new file mode 100644
index 000000000..f4c0cc98e
--- /dev/null
+++ b/browser/components/search/test/opensearch.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<link rel="search" type="application/opensearchdescription+xml" title="engine1" href="http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml">
+<link rel="search" type="application/opensearchdescription+xml" title="engine2" href="http://mochi.test:8888/browser/browser/components/search/test/testEngine_mozsearch.xml">
+</head>
+<body></body>
+</html>
diff --git a/browser/components/search/test/test.html b/browser/components/search/test/test.html
new file mode 100644
index 000000000..a39bece4f
--- /dev/null
+++ b/browser/components/search/test/test.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>Bug 426329</title>
+</head>
+<body></body>
+</html>
diff --git a/browser/components/search/test/testEngine.xml b/browser/components/search/test/testEngine.xml
new file mode 100644
index 000000000..21ddc4b9a
--- /dev/null
+++ b/browser/components/search/test/testEngine.xml
@@ -0,0 +1,12 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
+ xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Foo</ShortName>
+ <Description>Foo Search</Description>
+ <InputEncoding>utf-8</InputEncoding>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+ <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+ <Param name="test" value="{searchTerms}"/>
+ </Url>
+ <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
+ <moz:Alias>fooalias</moz:Alias>
+</OpenSearchDescription>
diff --git a/browser/components/search/test/testEngine_diacritics.xml b/browser/components/search/test/testEngine_diacritics.xml
new file mode 100644
index 000000000..0744921eb
--- /dev/null
+++ b/browser/components/search/test/testEngine_diacritics.xml
@@ -0,0 +1,12 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
+ xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Foo &#9825;</ShortName>
+ <Description>Engine whose ShortName contains non-BMP Unicode characters</Description>
+ <InputEncoding>utf-8</InputEncoding>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+ <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+ <Param name="test" value="{searchTerms}"/>
+ </Url>
+ <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
+ <moz:Alias>diacriticalias</moz:Alias>
+</OpenSearchDescription>
diff --git a/browser/components/search/test/testEngine_dupe.xml b/browser/components/search/test/testEngine_dupe.xml
new file mode 100644
index 000000000..d2db580c4
--- /dev/null
+++ b/browser/components/search/test/testEngine_dupe.xml
@@ -0,0 +1,12 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
+ xmlns:moz="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>FooDupe</ShortName>
+ <Description>Second Engine Search</Description>
+ <InputEncoding>utf-8</InputEncoding>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+ <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?search">
+ <Param name="test" value="{searchTerms}"/>
+ </Url>
+ <moz:SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</moz:SearchForm>
+ <moz:Alias>secondalias</moz:Alias>
+</OpenSearchDescription>
diff --git a/browser/components/search/test/testEngine_mozsearch.xml b/browser/components/search/test/testEngine_mozsearch.xml
new file mode 100644
index 000000000..9b4c02a0c
--- /dev/null
+++ b/browser/components/search/test/testEngine_mozsearch.xml
@@ -0,0 +1,14 @@
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Foo</ShortName>
+ <Description>Foo Search</Description>
+ <InputEncoding>utf-8</InputEncoding>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC</Image>
+ <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/?suggestions&amp;locale={moz:locale}&amp;test={searchTerms}"/>
+ <Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/components/search/test/">
+ <Param name="test" value="{searchTerms}"/>
+ <Param name="ie" value="utf-8"/>
+ <MozParam name="channel" condition="purpose" purpose="keyword" value="keywordsearch"/>
+ <MozParam name="channel" condition="purpose" purpose="contextmenu" value="contextsearch"/>
+ </Url>
+ <SearchForm>http://mochi.test:8888/browser/browser/components/search/test/</SearchForm>
+</SearchPlugin>
diff --git a/browser/components/search/test/webapi.html b/browser/components/search/test/webapi.html
new file mode 100644
index 000000000..1ef38b895
--- /dev/null
+++ b/browser/components/search/test/webapi.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script>
+function installEngine() {
+ var query = window.location.search.substring(1);
+ var args = JSON.parse(decodeURIComponent(query));
+
+ window.external.AddSearchProvider(...args);
+}
+</script>
+</head>
+<body onload="installEngine()">
+</body>
+</html>
diff --git a/browser/components/selfsupport/SelfSupportService.js b/browser/components/selfsupport/SelfSupportService.js
new file mode 100644
index 000000000..26148c6ff
--- /dev/null
+++ b/browser/components/selfsupport/SelfSupportService.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";
+
+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");
+
+const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
+
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryArchive",
+ "resource://gre/modules/TelemetryArchive.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
+ "resource://gre/modules/TelemetryEnvironment.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
+ "resource://gre/modules/TelemetryController.jsm");
+
+function MozSelfSupportInterface() {
+}
+
+MozSelfSupportInterface.prototype = {
+ classDescription: "MozSelfSupport",
+ classID: Components.ID("{d30aae8b-f352-4de3-b936-bb9d875df0bb}"),
+ contractID: "@mozilla.org/mozselfsupport;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]),
+
+ _window: null,
+
+ init: function (window) {
+ this._window = window;
+ },
+
+ get healthReportDataSubmissionEnabled() {
+ return Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
+ },
+
+ set healthReportDataSubmissionEnabled(enabled) {
+ Preferences.set(PREF_FHR_UPLOAD_ENABLED, enabled);
+ },
+
+ resetPref: function(name) {
+ Services.prefs.clearUserPref(name);
+ },
+
+ resetSearchEngines: function() {
+ Services.search.restoreDefaultEngines();
+ Services.search.resetToOriginalDefaultEngine();
+ },
+
+ getTelemetryPingList: function() {
+ return this._wrapPromise(TelemetryArchive.promiseArchivedPingList());
+ },
+
+ getTelemetryPing: function(pingId) {
+ return this._wrapPromise(TelemetryArchive.promiseArchivedPingById(pingId));
+ },
+
+ getCurrentTelemetryEnvironment: function() {
+ const current = TelemetryEnvironment.currentEnvironment;
+ return new this._window.Promise(resolve => resolve(current));
+ },
+
+ getCurrentTelemetrySubsessionPing: function() {
+ const current = TelemetryController.getCurrentPingData(true);
+ return new this._window.Promise(resolve => resolve(current));
+ },
+
+ _wrapPromise: function(promise) {
+ return new this._window.Promise(
+ (resolve, reject) => promise.then(resolve, reject));
+ },
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozSelfSupportInterface]);
diff --git a/browser/components/selfsupport/SelfSupportService.manifest b/browser/components/selfsupport/SelfSupportService.manifest
new file mode 100644
index 000000000..0e87857e7
--- /dev/null
+++ b/browser/components/selfsupport/SelfSupportService.manifest
@@ -0,0 +1,2 @@
+component {d30aae8b-f352-4de3-b936-bb9d875df0bb} SelfSupportService.js
+contract @mozilla.org/mozselfsupport;1 {d30aae8b-f352-4de3-b936-bb9d875df0bb}
diff --git a/browser/components/selfsupport/moz.build b/browser/components/selfsupport/moz.build
new file mode 100644
index 000000000..daa59ac97
--- /dev/null
+++ b/browser/components/selfsupport/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_COMPONENTS += [
+ 'SelfSupportService.js',
+ 'SelfSupportService.manifest',
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser.ini',
+]
diff --git a/browser/components/selfsupport/test/.eslintrc.js b/browser/components/selfsupport/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/selfsupport/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/selfsupport/test/browser.ini b/browser/components/selfsupport/test/browser.ini
new file mode 100644
index 000000000..ba56857b3
--- /dev/null
+++ b/browser/components/selfsupport/test/browser.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[browser_selfsupportAPI.js]
diff --git a/browser/components/selfsupport/test/browser_selfsupportAPI.js b/browser/components/selfsupport/test/browser_selfsupportAPI.js
new file mode 100644
index 000000000..2a54d4ae6
--- /dev/null
+++ b/browser/components/selfsupport/test/browser_selfsupportAPI.js
@@ -0,0 +1,88 @@
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+function test_resetPref() {
+ const prefNewName = "browser.newpref.fake";
+ Assert.ok(!Preferences.has(prefNewName), "pref should not exist");
+
+ const prefExistingName = "extensions.hotfix.id";
+ Assert.ok(Preferences.has(prefExistingName), "pref should exist");
+ Assert.ok(!Preferences.isSet(prefExistingName), "pref should not be user-set");
+ let prefExistingOriginalValue = Preferences.get(prefExistingName);
+
+ registerCleanupFunction(function() {
+ Preferences.set(prefExistingName, prefExistingOriginalValue);
+ Services.prefs.deleteBranch(prefNewName);
+ });
+
+ // 1. do nothing on an inexistent pref
+ MozSelfSupport.resetPref(prefNewName);
+ Assert.ok(!Preferences.has(prefNewName), "pref should still not exist");
+
+ // 2. creation of a new pref
+ Preferences.set(prefNewName, 10);
+ Assert.ok(Preferences.has(prefNewName), "pref should exist");
+ Assert.equal(Preferences.get(prefNewName), 10, "pref value should be 10");
+
+ MozSelfSupport.resetPref(prefNewName);
+ Assert.ok(!Preferences.has(prefNewName), "pref should not exist any more");
+
+ // 3. do nothing on an unchanged existing pref
+ MozSelfSupport.resetPref(prefExistingName);
+ Assert.ok(Preferences.has(prefExistingName), "pref should still exist");
+ Assert.equal(Preferences.get(prefExistingName), prefExistingOriginalValue, "pref value should be the same as original");
+
+ // 4. change the value of an existing pref
+ Preferences.set(prefExistingName, "anyone@mozilla.org");
+ Assert.ok(Preferences.has(prefExistingName), "pref should exist");
+ Assert.equal(Preferences.get(prefExistingName), "anyone@mozilla.org", "pref value should have changed");
+
+ MozSelfSupport.resetPref(prefExistingName);
+ Assert.ok(Preferences.has(prefExistingName), "pref should still exist");
+ Assert.equal(Preferences.get(prefExistingName), prefExistingOriginalValue, "pref value should be the same as original");
+
+ // 5. delete an existing pref
+ // deleteBranch is implemented in such a way that
+ // clearUserPref can't undo its action
+ // see discussion in bug 1075160
+}
+
+function test_resetSearchEngines()
+{
+ const defaultEngineOriginal = Services.search.defaultEngine;
+ const visibleEnginesOriginal = Services.search.getVisibleEngines();
+
+ // 1. do nothing on unchanged search configuration
+ MozSelfSupport.resetSearchEngines();
+ Assert.equal(Services.search.defaultEngine, defaultEngineOriginal, "default engine should be reset");
+ Assert.deepEqual(Services.search.getVisibleEngines(), visibleEnginesOriginal,
+ "default visible engines set should be reset");
+
+ // 2. change the default search engine
+ const defaultEngineNew = visibleEnginesOriginal[3];
+ Assert.notEqual(defaultEngineOriginal, defaultEngineNew, "new default engine should be different from original");
+ Services.search.defaultEngine = defaultEngineNew;
+ Assert.equal(Services.search.defaultEngine, defaultEngineNew, "default engine should be set to new");
+ MozSelfSupport.resetSearchEngines();
+ Assert.equal(Services.search.defaultEngine, defaultEngineOriginal, "default engine should be reset");
+ Assert.deepEqual(Services.search.getVisibleEngines(), visibleEnginesOriginal,
+ "default visible engines set should be reset");
+
+ // 3. remove an engine
+ const engineRemoved = visibleEnginesOriginal[2];
+ Services.search.removeEngine(engineRemoved);
+ Assert.ok(Services.search.getVisibleEngines().indexOf(engineRemoved) == -1,
+ "removed engine should not be visible any more");
+ MozSelfSupport.resetSearchEngines();
+ Assert.equal(Services.search.defaultEngine, defaultEngineOriginal, "default engine should be reset");
+ Assert.deepEqual(Services.search.getVisibleEngines(), visibleEnginesOriginal,
+ "default visible engines set should be reset");
+
+ // 4. add an angine
+ // we don't remove user-added engines as they are only used if selected
+}
+
+function test()
+{
+ test_resetPref();
+ test_resetSearchEngines();
+}
diff --git a/browser/components/sessionstore/ContentRestore.jsm b/browser/components/sessionstore/ContentRestore.jsm
new file mode 100644
index 000000000..976016770
--- /dev/null
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -0,0 +1,431 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If 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 = ["ContentRestore"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
+ "resource:///modules/sessionstore/DocShellCapabilities.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormData",
+ "resource://gre/modules/FormData.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
+ "resource:///modules/sessionstore/PageStyle.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
+ "resource://gre/modules/ScrollPosition.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
+ "resource:///modules/sessionstore/SessionHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
+ "resource:///modules/sessionstore/SessionStorage.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Utils",
+ "resource://gre/modules/sessionstore/Utils.jsm");
+
+/**
+ * This module implements the content side of session restoration. The chrome
+ * side is handled by SessionStore.jsm. The functions in this module are called
+ * by content-sessionStore.js based on messages received from SessionStore.jsm
+ * (or, in one case, based on a "load" event). Each tab has its own
+ * ContentRestore instance, constructed by content-sessionStore.js.
+ *
+ * In a typical restore, content-sessionStore.js will call the following based
+ * on messages and events it receives:
+ *
+ * restoreHistory(tabData, loadArguments, callbacks)
+ * Restores the tab's history and session cookies.
+ * restoreTabContent(loadArguments, finishCallback)
+ * Starts loading the data for the current page to restore.
+ * restoreDocument()
+ * Restore form and scroll data.
+ *
+ * When the page has been loaded from the network, we call finishCallback. It
+ * should send a message to SessionStore.jsm, which may cause other tabs to be
+ * restored.
+ *
+ * When the page has finished loading, a "load" event will trigger in
+ * content-sessionStore.js, which will call restoreDocument. At that point,
+ * form data is restored and the restore is complete.
+ *
+ * At any time, SessionStore.jsm can cancel the ongoing restore by sending a
+ * reset message, which causes resetRestore to be called. At that point it's
+ * legal to begin another restore.
+ */
+function ContentRestore(chromeGlobal) {
+ let internal = new ContentRestoreInternal(chromeGlobal);
+ let external = {};
+
+ let EXPORTED_METHODS = ["restoreHistory",
+ "restoreTabContent",
+ "restoreDocument",
+ "resetRestore"
+ ];
+
+ for (let method of EXPORTED_METHODS) {
+ external[method] = internal[method].bind(internal);
+ }
+
+ return Object.freeze(external);
+}
+
+function ContentRestoreInternal(chromeGlobal) {
+ this.chromeGlobal = chromeGlobal;
+
+ // The following fields are only valid during certain phases of the restore
+ // process.
+
+ // The tabData for the restore. Set in restoreHistory and removed in
+ // restoreTabContent.
+ this._tabData = null;
+
+ // Contains {entry, pageStyle, scrollPositions, formdata}, where entry is a
+ // single entry from the tabData.entries array. Set in
+ // restoreTabContent and removed in restoreDocument.
+ this._restoringDocument = null;
+
+ // This listener is used to detect reloads on restoring tabs. Set in
+ // restoreHistory and removed in restoreTabContent.
+ this._historyListener = null;
+
+ // This listener detects when a pending tab starts loading (when not
+ // initiated by sessionstore) and when a restoring tab has finished loading
+ // data from the network. Set in restoreHistory() and restoreTabContent(),
+ // removed in resetRestore().
+ this._progressListener = null;
+}
+
+/**
+ * The API for the ContentRestore module. Methods listed in EXPORTED_METHODS are
+ * public.
+ */
+ContentRestoreInternal.prototype = {
+
+ get docShell() {
+ return this.chromeGlobal.docShell;
+ },
+
+ /**
+ * Starts the process of restoring a tab. The tabData to be restored is passed
+ * in here and used throughout the restoration. The epoch (which must be
+ * non-zero) is passed through to all the callbacks. If a load in the tab
+ * is started while it is pending, the appropriate callbacks are called.
+ */
+ restoreHistory(tabData, loadArguments, callbacks) {
+ this._tabData = tabData;
+
+ // In case about:blank isn't done yet.
+ let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
+
+ // Make sure currentURI is set so that switch-to-tab works before the tab is
+ // restored. We'll reset this to about:blank when we try to restore the tab
+ // to ensure that docshell doeesn't get confused. Don't bother doing this if
+ // we're restoring immediately due to a process switch. It just causes the
+ // URL bar to be temporarily blank.
+ let activeIndex = tabData.index - 1;
+ let activePageData = tabData.entries[activeIndex] || {};
+ let uri = activePageData.url || null;
+ if (uri && !loadArguments) {
+ webNavigation.setCurrentURI(Utils.makeURI(uri));
+ }
+
+ SessionHistory.restore(this.docShell, tabData);
+
+ // Add a listener to watch for reloads.
+ let listener = new HistoryListener(this.docShell, () => {
+ // On reload, restore tab contents.
+ this.restoreTabContent(null, false, callbacks.onLoadFinished);
+ });
+
+ webNavigation.sessionHistory.addSHistoryListener(listener);
+ this._historyListener = listener;
+
+ // Make sure to reset the capabilities and attributes in case this tab gets
+ // reused.
+ let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
+ DocShellCapabilities.restore(this.docShell, disallow);
+
+ if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
+ SessionStorage.restore(this.docShell, tabData.storage);
+ delete tabData.storage;
+ }
+
+ // Add a progress listener to correctly handle browser.loadURI()
+ // calls from foreign code.
+ this._progressListener = new ProgressListener(this.docShell, {
+ onStartRequest: () => {
+ // Some code called browser.loadURI() on a pending tab. It's safe to
+ // assume we don't care about restoring scroll or form data.
+ this._tabData = null;
+
+ // Listen for the tab to finish loading.
+ this.restoreTabContentStarted(callbacks.onLoadFinished);
+
+ // Notify the parent.
+ callbacks.onLoadStarted();
+ }
+ });
+ },
+
+ /**
+ * Start loading the current page. When the data has finished loading from the
+ * network, finishCallback is called. Returns true if the load was successful.
+ */
+ restoreTabContent: function (loadArguments, isRemotenessUpdate, finishCallback) {
+ let tabData = this._tabData;
+ this._tabData = null;
+
+ let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
+ let history = webNavigation.sessionHistory;
+
+ // Listen for the tab to finish loading.
+ this.restoreTabContentStarted(finishCallback);
+
+ // Reset the current URI to about:blank. We changed it above for
+ // switch-to-tab, but now it must go back to the correct value before the
+ // load happens. Don't bother doing this if we're restoring immediately
+ // due to a process switch.
+ if (!isRemotenessUpdate) {
+ webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
+ }
+
+ try {
+ if (loadArguments) {
+ // A load has been redirected to a new process so get history into the
+ // same state it was before the load started then trigger the load.
+ let referrer = loadArguments.referrer ?
+ Utils.makeURI(loadArguments.referrer) : null;
+ let referrerPolicy = ('referrerPolicy' in loadArguments
+ ? loadArguments.referrerPolicy
+ : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ let postData = loadArguments.postData ?
+ Utils.makeInputStream(loadArguments.postData) : null;
+
+ if (loadArguments.userContextId) {
+ webNavigation.setOriginAttributesBeforeLoading({ userContextId: loadArguments.userContextId });
+ }
+
+ webNavigation.loadURIWithOptions(loadArguments.uri, loadArguments.flags,
+ referrer, referrerPolicy, postData,
+ null, null);
+ } else if (tabData.userTypedValue && tabData.userTypedClear) {
+ // If the user typed a URL into the URL bar and hit enter right before
+ // we crashed, we want to start loading that page again. A non-zero
+ // userTypedClear value means that the load had started.
+ // Load userTypedValue and fix up the URL if it's partial/broken.
+ webNavigation.loadURI(tabData.userTypedValue,
+ Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
+ null, null, null);
+ } else if (tabData.entries.length) {
+ // Stash away the data we need for restoreDocument.
+ let activeIndex = tabData.index - 1;
+ this._restoringDocument = {entry: tabData.entries[activeIndex] || {},
+ formdata: tabData.formdata || {},
+ pageStyle: tabData.pageStyle || {},
+ scrollPositions: tabData.scroll || {}};
+
+ // In order to work around certain issues in session history, we need to
+ // force session history to update its internal index and call reload
+ // instead of gotoIndex. See bug 597315.
+ history.reloadCurrentEntry();
+ } else {
+ // If there's nothing to restore, we should still blank the page.
+ webNavigation.loadURI("about:blank",
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
+ null, null, null);
+ }
+
+ return true;
+ } catch (ex if ex instanceof Ci.nsIException) {
+ // Ignore page load errors, but return false to signal that the load never
+ // happened.
+ return false;
+ }
+ },
+
+ /**
+ * To be called after restoreHistory(). Removes all listeners needed for
+ * pending tabs and makes sure to notify when the tab finished loading.
+ */
+ restoreTabContentStarted(finishCallback) {
+ // The reload listener is no longer needed.
+ this._historyListener.uninstall();
+ this._historyListener = null;
+
+ // Remove the old progress listener.
+ this._progressListener.uninstall();
+
+ // We're about to start a load. This listener will be called when the load
+ // has finished getting everything from the network.
+ this._progressListener = new ProgressListener(this.docShell, {
+ onStopRequest: () => {
+ // Call resetRestore() to reset the state back to normal. The data
+ // needed for restoreDocument() (which hasn't happened yet) will
+ // remain in _restoringDocument.
+ this.resetRestore();
+
+ finishCallback();
+ }
+ });
+ },
+
+ /**
+ * Finish restoring the tab by filling in form data and setting the scroll
+ * position. The restore is complete when this function exits. It should be
+ * called when the "load" event fires for the restoring tab.
+ */
+ restoreDocument: function () {
+ if (!this._restoringDocument) {
+ return;
+ }
+ let {entry, pageStyle, formdata, scrollPositions} = this._restoringDocument;
+ this._restoringDocument = null;
+
+ let window = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+
+ PageStyle.restoreTree(this.docShell, pageStyle);
+ FormData.restoreTree(window, formdata);
+ ScrollPosition.restoreTree(window, scrollPositions);
+ },
+
+ /**
+ * Cancel an ongoing restore. This function can be called any time between
+ * restoreHistory and restoreDocument.
+ *
+ * This function is called externally (if a restore is canceled) and
+ * internally (when the loads for a restore have finished). In the latter
+ * case, it's called before restoreDocument, so it cannot clear
+ * _restoringDocument.
+ */
+ resetRestore: function () {
+ this._tabData = null;
+
+ if (this._historyListener) {
+ this._historyListener.uninstall();
+ }
+ this._historyListener = null;
+
+ if (this._progressListener) {
+ this._progressListener.uninstall();
+ }
+ this._progressListener = null;
+ }
+};
+
+/*
+ * This listener detects when a page being restored is reloaded. It triggers a
+ * callback and cancels the reload. The callback will send a message to
+ * SessionStore.jsm so that it can restore the content immediately.
+ */
+function HistoryListener(docShell, callback) {
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNavigation.sessionHistory.addSHistoryListener(this);
+
+ this.webNavigation = webNavigation;
+ this.callback = callback;
+}
+HistoryListener.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISHistoryListener,
+ Ci.nsISupportsWeakReference
+ ]),
+
+ uninstall: function () {
+ let shistory = this.webNavigation.sessionHistory;
+ if (shistory) {
+ shistory.removeSHistoryListener(this);
+ }
+ },
+
+ OnHistoryGoBack: function(backURI) { return true; },
+ OnHistoryGoForward: function(forwardURI) { return true; },
+ OnHistoryGotoIndex: function(index, gotoURI) { return true; },
+ OnHistoryPurge: function(numEntries) { return true; },
+ OnHistoryReplaceEntry: function(index) {},
+
+ // This will be called for a pending tab when loadURI(uri) is called where
+ // the given |uri| only differs in the fragment.
+ OnHistoryNewEntry(newURI) {
+ let currentURI = this.webNavigation.currentURI;
+
+ // Ignore new SHistory entries with the same URI as those do not indicate
+ // a navigation inside a document by changing the #hash part of the URL.
+ // We usually hit this when purging session history for browsers.
+ if (currentURI && (currentURI.spec == newURI.spec)) {
+ return;
+ }
+
+ // Reset the tab's URL to what it's actually showing. Without this loadURI()
+ // would use the current document and change the displayed URL only.
+ this.webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
+
+ // Kick off a new load so that we navigate away from about:blank to the
+ // new URL that was passed to loadURI(). The new load will cause a
+ // STATE_START notification to be sent and the ProgressListener will then
+ // notify the parent and do the rest.
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ this.webNavigation.loadURI(newURI.spec, flags, null, null, null);
+ },
+
+ OnHistoryReload(reloadURI, reloadFlags) {
+ this.callback();
+
+ // Cancel the load.
+ return false;
+ },
+}
+
+/**
+ * This class informs SessionStore.jsm whenever the network requests for a
+ * restoring page have completely finished. We only restore three tabs
+ * simultaneously, so this is the signal for SessionStore.jsm to kick off
+ * another restore (if there are more to do).
+ *
+ * The progress listener is also used to be notified when a load not initiated
+ * by sessionstore starts. Pending tabs will then need to be marked as no
+ * longer pending.
+ */
+function ProgressListener(docShell, callbacks) {
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+
+ this.webProgress = webProgress;
+ this.callbacks = callbacks;
+}
+
+ProgressListener.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference
+ ]),
+
+ uninstall: function() {
+ this.webProgress.removeProgressListener(this);
+ },
+
+ onStateChange: function(webProgress, request, stateFlags, status) {
+ let {STATE_IS_WINDOW, STATE_STOP, STATE_START} = Ci.nsIWebProgressListener;
+ if (!webProgress.isTopLevel || !(stateFlags & STATE_IS_WINDOW)) {
+ return;
+ }
+
+ if (stateFlags & STATE_START && this.callbacks.onStartRequest) {
+ this.callbacks.onStartRequest();
+ }
+
+ if (stateFlags & STATE_STOP && this.callbacks.onStopRequest) {
+ this.callbacks.onStopRequest();
+ }
+ },
+
+ onLocationChange: function() {},
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+};
diff --git a/browser/components/sessionstore/DocShellCapabilities.jsm b/browser/components/sessionstore/DocShellCapabilities.jsm
new file mode 100644
index 000000000..098aae86f
--- /dev/null
+++ b/browser/components/sessionstore/DocShellCapabilities.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["DocShellCapabilities"];
+
+/**
+ * The external API exported by this module.
+ */
+this.DocShellCapabilities = Object.freeze({
+ collect: function (docShell) {
+ return DocShellCapabilitiesInternal.collect(docShell);
+ },
+
+ restore: function (docShell, disallow) {
+ return DocShellCapabilitiesInternal.restore(docShell, disallow);
+ },
+});
+
+/**
+ * Internal functionality to save and restore the docShell.allow* properties.
+ */
+var DocShellCapabilitiesInternal = {
+ // List of docShell capabilities to (re)store. These are automatically
+ // retrieved from a given docShell if not already collected before.
+ // This is made so they're automatically in sync with all nsIDocShell.allow*
+ // properties.
+ caps: null,
+
+ allCapabilities: function (docShell) {
+ if (!this.caps) {
+ let keys = Object.keys(docShell);
+ this.caps = keys.filter(k => k.startsWith("allow")).map(k => k.slice(5));
+ }
+ return this.caps;
+ },
+
+ collect: function (docShell) {
+ let caps = this.allCapabilities(docShell);
+ return caps.filter(cap => !docShell["allow" + cap]);
+ },
+
+ restore: function (docShell, disallow) {
+ let caps = this.allCapabilities(docShell);
+ for (let cap of caps)
+ docShell["allow" + cap] = !disallow.has(cap);
+ },
+};
diff --git a/browser/components/sessionstore/FrameTree.jsm b/browser/components/sessionstore/FrameTree.jsm
new file mode 100644
index 000000000..e8ed12a8f
--- /dev/null
+++ b/browser/components/sessionstore/FrameTree.jsm
@@ -0,0 +1,254 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["FrameTree"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+const EXPORTED_METHODS = ["addObserver", "contains", "map", "forEach"];
+
+/**
+ * A FrameTree represents all frames that were reachable when the document
+ * was loaded. We use this information to ignore frames when collecting
+ * sessionstore data as we can't currently restore anything for frames that
+ * have been created dynamically after or at the load event.
+ *
+ * @constructor
+ */
+function FrameTree(chromeGlobal) {
+ let internal = new FrameTreeInternal(chromeGlobal);
+ let external = {};
+
+ for (let method of EXPORTED_METHODS) {
+ external[method] = internal[method].bind(internal);
+ }
+
+ return Object.freeze(external);
+}
+
+/**
+ * The internal frame tree API that the public one points to.
+ *
+ * @constructor
+ */
+function FrameTreeInternal(chromeGlobal) {
+ // A WeakMap that uses frames (DOMWindows) as keys and their initial indices
+ // in their parents' child lists as values. Suppose we have a root frame with
+ // three subframes i.e. a page with three iframes. The WeakMap would have
+ // four entries and look as follows:
+ //
+ // root -> 0
+ // subframe1 -> 0
+ // subframe2 -> 1
+ // subframe3 -> 2
+ //
+ // Should one of the subframes disappear we will stop collecting data for it
+ // as |this._frames.has(frame) == false|. All other subframes will maintain
+ // their initial indices to ensure we can restore frame data appropriately.
+ this._frames = new WeakMap();
+
+ // The Set of observers that will be notified when the frame changes.
+ this._observers = new Set();
+
+ // The chrome global we use to retrieve the current DOMWindow.
+ this._chromeGlobal = chromeGlobal;
+
+ // Register a web progress listener to be notified about new page loads.
+ let docShell = chromeGlobal.docShell;
+ let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
+ let webProgress = ifreq.getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+}
+
+FrameTreeInternal.prototype = {
+
+ // Returns the docShell's current global.
+ get content() {
+ return this._chromeGlobal.content;
+ },
+
+ /**
+ * Adds a given observer |obs| to the set of observers that will be notified
+ * when the frame tree is reset (when a new document starts loading) or
+ * recollected (when a document finishes loading).
+ *
+ * @param obs (object)
+ */
+ addObserver: function (obs) {
+ this._observers.add(obs);
+ },
+
+ /**
+ * Notifies all observers that implement the given |method|.
+ *
+ * @param method (string)
+ */
+ notifyObservers: function (method) {
+ for (let obs of this._observers) {
+ if (obs.hasOwnProperty(method)) {
+ obs[method]();
+ }
+ }
+ },
+
+ /**
+ * Checks whether a given |frame| is contained in the collected frame tree.
+ * If it is not, this indicates that we should not collect data for it.
+ *
+ * @param frame (nsIDOMWindow)
+ * @return bool
+ */
+ contains: function (frame) {
+ return this._frames.has(frame);
+ },
+
+ /**
+ * Recursively applies the given function |cb| to the stored frame tree. Use
+ * this method to collect sessionstore data for all reachable frames stored
+ * in the frame tree.
+ *
+ * If a given function |cb| returns a value, it must be an object. It may
+ * however return "null" to indicate that there is no data to be stored for
+ * the given frame.
+ *
+ * The object returned by |cb| cannot have any property named "children" as
+ * that is used to store information about subframes in the tree returned
+ * by |map()| and might be overridden.
+ *
+ * @param cb (function)
+ * @return object
+ */
+ map: function (cb) {
+ let frames = this._frames;
+
+ function walk(frame) {
+ let obj = cb(frame) || {};
+
+ if (frames.has(frame)) {
+ let children = [];
+
+ Array.forEach(frame.frames, subframe => {
+ // Don't collect any data if the frame is not contained in the
+ // initial frame tree. It's a dynamic frame added later.
+ if (!frames.has(subframe)) {
+ return;
+ }
+
+ // Retrieve the frame's original position in its parent's child list.
+ let index = frames.get(subframe);
+
+ // Recursively collect data for the current subframe.
+ let result = walk(subframe, cb);
+ if (result && Object.keys(result).length) {
+ children[index] = result;
+ }
+ });
+
+ if (children.length) {
+ obj.children = children;
+ }
+ }
+
+ return Object.keys(obj).length ? obj : null;
+ }
+
+ return walk(this.content);
+ },
+
+ /**
+ * Applies the given function |cb| to all frames stored in the tree. Use this
+ * method if |map()| doesn't suit your needs and you want more control over
+ * how data is collected.
+ *
+ * @param cb (function)
+ * This callback receives the current frame as the only argument.
+ */
+ forEach: function (cb) {
+ let frames = this._frames;
+
+ function walk(frame) {
+ cb(frame);
+
+ if (!frames.has(frame)) {
+ return;
+ }
+
+ Array.forEach(frame.frames, subframe => {
+ if (frames.has(subframe)) {
+ cb(subframe);
+ }
+ });
+ }
+
+ walk(this.content);
+ },
+
+ /**
+ * Stores a given |frame| and its children in the frame tree.
+ *
+ * @param frame (nsIDOMWindow)
+ * @param index (int)
+ * The index in the given frame's parent's child list.
+ */
+ collect: function (frame, index = 0) {
+ // Mark the given frame as contained in the frame tree.
+ this._frames.set(frame, index);
+
+ // Mark the given frame's subframes as contained in the tree.
+ Array.forEach(frame.frames, this.collect, this);
+ },
+
+ /**
+ * @see nsIWebProgressListener.onStateChange
+ *
+ * We want to be notified about:
+ * - new documents that start loading to clear the current frame tree;
+ * - completed document loads to recollect reachable frames.
+ */
+ onStateChange: function (webProgress, request, stateFlags, status) {
+ // Ignore state changes for subframes because we're only interested in the
+ // top-document starting or stopping its load. We thus only care about any
+ // changes to the root of the frame tree, not to any of its nodes/leafs.
+ if (!webProgress.isTopLevel || webProgress.DOMWindow != this.content) {
+ return;
+ }
+
+ // onStateChange will be fired when loading the initial about:blank URI for
+ // a browser, which we don't actually care about. This is particularly for
+ // the case of unrestored background tabs, where the content has not yet
+ // been restored: we don't want to accidentally send any updates to the
+ // parent when the about:blank placeholder page has loaded.
+ if (!this._chromeGlobal.docShell.hasLoadedNonBlankURI) {
+ return;
+ }
+
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
+ // Clear the list of frames until we can recollect it.
+ this._frames = new WeakMap();
+
+ // Notify observers that the frame tree has been reset.
+ this.notifyObservers("onFrameTreeReset");
+ } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ // The document and its resources have finished loading.
+ this.collect(webProgress.DOMWindow);
+
+ // Notify observers that the frame tree has been reset.
+ this.notifyObservers("onFrameTreeCollected");
+ }
+ },
+
+ // Unused nsIWebProgressListener methods.
+ onLocationChange: function () {},
+ onProgressChange: function () {},
+ onSecurityChange: function () {},
+ onStatusChange: function () {},
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference])
+};
diff --git a/browser/components/sessionstore/GlobalState.jsm b/browser/components/sessionstore/GlobalState.jsm
new file mode 100644
index 000000000..ac2d7c81b
--- /dev/null
+++ b/browser/components/sessionstore/GlobalState.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["GlobalState"];
+
+const EXPORTED_METHODS = ["getState", "clear", "get", "set", "delete", "setFromState"];
+/**
+ * Module that contains global session data.
+ */
+function GlobalState() {
+ let internal = new GlobalStateInternal();
+ let external = {};
+ for (let method of EXPORTED_METHODS) {
+ external[method] = internal[method].bind(internal);
+ }
+ return Object.freeze(external);
+}
+
+function GlobalStateInternal() {
+ // Storage for global state.
+ this.state = {};
+}
+
+GlobalStateInternal.prototype = {
+ /**
+ * Get all value from the global state.
+ */
+ getState: function() {
+ return this.state;
+ },
+
+ /**
+ * Clear all currently stored global state.
+ */
+ clear: function() {
+ this.state = {};
+ },
+
+ /**
+ * Retrieve a value from the global state.
+ *
+ * @param aKey
+ * A key the value is stored under.
+ * @return The value stored at aKey, or an empty string if no value is set.
+ */
+ get: function(aKey) {
+ return this.state[aKey] || "";
+ },
+
+ /**
+ * Set a global value.
+ *
+ * @param aKey
+ * A key to store the value under.
+ */
+ set: function(aKey, aStringValue) {
+ this.state[aKey] = aStringValue;
+ },
+
+ /**
+ * Delete a global value.
+ *
+ * @param aKey
+ * A key to delete the value for.
+ */
+ delete: function(aKey) {
+ delete this.state[aKey];
+ },
+
+ /**
+ * Set the current global state from a state object. Any previous global
+ * state will be removed, even if the new state does not contain a matching
+ * key.
+ *
+ * @param aState
+ * A state object to extract global state from to be set.
+ */
+ setFromState: function (aState) {
+ this.state = (aState && aState.global) || {};
+ }
+};
diff --git a/browser/components/sessionstore/PageStyle.jsm b/browser/components/sessionstore/PageStyle.jsm
new file mode 100644
index 000000000..0424ef6b1
--- /dev/null
+++ b/browser/components/sessionstore/PageStyle.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["PageStyle"];
+
+const Ci = Components.interfaces;
+
+/**
+ * The external API exported by this module.
+ */
+this.PageStyle = Object.freeze({
+ collect: function (docShell, frameTree) {
+ return PageStyleInternal.collect(docShell, frameTree);
+ },
+
+ restoreTree: function (docShell, data) {
+ PageStyleInternal.restoreTree(docShell, data);
+ }
+});
+
+// Signifies that author style level is disabled for the page.
+const NO_STYLE = "_nostyle";
+
+var PageStyleInternal = {
+ /**
+ * Collects the selected style sheet sets for all reachable frames.
+ */
+ collect: function (docShell, frameTree) {
+ let result = frameTree.map(({document: doc}) => {
+ let style;
+
+ if (doc) {
+ // http://dev.w3.org/csswg/cssom/#persisting-the-selected-css-style-sheet-set
+ style = doc.selectedStyleSheetSet || doc.lastStyleSheetSet;
+ }
+
+ return style ? {pageStyle: style} : null;
+ });
+
+ let markupDocumentViewer =
+ docShell.contentViewer;
+
+ if (markupDocumentViewer.authorStyleDisabled) {
+ result = result || {};
+ result.disabled = true;
+ }
+
+ return result && Object.keys(result).length ? result : null;
+ },
+
+ /**
+ * Restores pageStyle data for the current frame hierarchy starting at the
+ * |docShell's| current DOMWindow using the given pageStyle |data|.
+ *
+ * Warning: If the current frame hierarchy doesn't match that of the given
+ * |data| object we will silently discard data for unreachable frames. We may
+ * as well assign page styles to the wrong frames if some were reordered or
+ * removed.
+ *
+ * @param docShell (nsIDocShell)
+ * @param data (object)
+ * {
+ * disabled: true, // when true, author styles will be disabled
+ * pageStyle: "Dusk",
+ * children: [
+ * null,
+ * {pageStyle: "Mozilla", children: [ ... ]}
+ * ]
+ * }
+ */
+ restoreTree: function (docShell, data) {
+ let disabled = data.disabled || false;
+ let markupDocumentViewer =
+ docShell.contentViewer;
+ markupDocumentViewer.authorStyleDisabled = disabled;
+
+ function restoreFrame(root, data) {
+ if (data.hasOwnProperty("pageStyle")) {
+ root.document.selectedStyleSheetSet = data.pageStyle;
+ }
+
+ if (!data.hasOwnProperty("children")) {
+ return;
+ }
+
+ let frames = root.frames;
+ data.children.forEach((child, index) => {
+ if (child && index < frames.length) {
+ restoreFrame(frames[index], child);
+ }
+ });
+ }
+
+ let ifreq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
+ restoreFrame(ifreq.getInterface(Ci.nsIDOMWindow), data);
+ }
+};
diff --git a/browser/components/sessionstore/PrivacyFilter.jsm b/browser/components/sessionstore/PrivacyFilter.jsm
new file mode 100644
index 000000000..88713b402
--- /dev/null
+++ b/browser/components/sessionstore/PrivacyFilter.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["PrivacyFilter"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
+ "resource:///modules/sessionstore/PrivacyLevel.jsm");
+
+/**
+ * A module that provides methods to filter various kinds of data collected
+ * from a tab by the current privacy level as set by the user.
+ */
+this.PrivacyFilter = Object.freeze({
+ /**
+ * Filters the given (serialized) session storage |data| according to the
+ * current privacy level and returns a new object containing only data that
+ * we're allowed to store.
+ *
+ * @param data The session storage data as collected from a tab.
+ * @return object
+ */
+ filterSessionStorageData: function (data) {
+ let retval = {};
+
+ for (let host of Object.keys(data)) {
+ if (PrivacyLevel.check(host)) {
+ retval[host] = data[host];
+ }
+ }
+
+ return Object.keys(retval).length ? retval : null;
+ },
+
+ /**
+ * Filters the given (serialized) form |data| according to the current
+ * privacy level and returns a new object containing only data that we're
+ * allowed to store.
+ *
+ * @param data The form data as collected from a tab.
+ * @return object
+ */
+ filterFormData: function (data) {
+ // If the given form data object has an associated URL that we are not
+ // allowed to store data for, bail out. We explicitly discard data for any
+ // children as well even if storing data for those frames would be allowed.
+ if (data.url && !PrivacyLevel.check(data.url)) {
+ return;
+ }
+
+ let retval = {};
+
+ for (let key of Object.keys(data)) {
+ if (key === "children") {
+ let recurse = child => this.filterFormData(child);
+ let children = data.children.map(recurse).filter(child => child);
+
+ if (children.length) {
+ retval.children = children;
+ }
+ // Only copy keys other than "children" if we have a valid URL in
+ // data.url and we thus passed the privacy level check.
+ } else if (data.url) {
+ retval[key] = data[key];
+ }
+ }
+
+ return Object.keys(retval).length ? retval : null;
+ },
+
+ /**
+ * Removes any private windows and tabs from a given browser state object.
+ *
+ * @param browserState (object)
+ * The browser state for which we remove any private windows and tabs.
+ * The given object will be modified.
+ */
+ filterPrivateWindowsAndTabs: function (browserState) {
+ // Remove private opened windows.
+ for (let i = browserState.windows.length - 1; i >= 0; i--) {
+ let win = browserState.windows[i];
+
+ if (win.isPrivate) {
+ browserState.windows.splice(i, 1);
+
+ if (browserState.selectedWindow >= i) {
+ browserState.selectedWindow--;
+ }
+ } else {
+ // Remove private tabs from all open non-private windows.
+ this.filterPrivateTabs(win);
+ }
+ }
+
+ // Remove private closed windows.
+ browserState._closedWindows =
+ browserState._closedWindows.filter(win => !win.isPrivate);
+
+ // Remove private tabs from all remaining closed windows.
+ browserState._closedWindows.forEach(win => this.filterPrivateTabs(win));
+ },
+
+ /**
+ * Removes open private tabs from a given window state object.
+ *
+ * @param winState (object)
+ * The window state for which we remove any private tabs.
+ * The given object will be modified.
+ */
+ filterPrivateTabs: function (winState) {
+ // Remove open private tabs.
+ for (let i = winState.tabs.length - 1; i >= 0 ; i--) {
+ let tab = winState.tabs[i];
+
+ if (tab.isPrivate) {
+ winState.tabs.splice(i, 1);
+
+ if (winState.selected >= i) {
+ winState.selected--;
+ }
+ }
+ }
+
+ // Note that closed private tabs are only stored for private windows.
+ // There is no need to call this function for private windows as the
+ // whole window state should just be discarded so we explicitly don't
+ // try to remove closed private tabs as an optimization.
+ }
+});
diff --git a/browser/components/sessionstore/PrivacyLevel.jsm b/browser/components/sessionstore/PrivacyLevel.jsm
new file mode 100644
index 000000000..135f1f959
--- /dev/null
+++ b/browser/components/sessionstore/PrivacyLevel.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["PrivacyLevel"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF = "browser.sessionstore.privacy_level";
+
+// The following constants represent the different possible privacy levels that
+// can be set by the user and that we need to consider when collecting text
+// data, and cookies.
+//
+// Collect data from all sites (http and https).
+const PRIVACY_NONE = 0;
+// Collect data from unencrypted sites (http), only.
+const PRIVACY_ENCRYPTED = 1;
+// Collect no data.
+const PRIVACY_FULL = 2;
+
+/**
+ * The external API as exposed by this module.
+ */
+var PrivacyLevel = Object.freeze({
+ /**
+ * Returns whether the current privacy level allows saving data for the given
+ * |url|.
+ *
+ * @param url The URL we want to save data for.
+ * @return bool
+ */
+ check: function (url) {
+ return PrivacyLevel.canSave({ isHttps: url.startsWith("https:") });
+ },
+
+ /**
+ * Checks whether we're allowed to save data for a specific site.
+ *
+ * @param {isHttps: boolean}
+ * An object that must have one property: 'isHttps'.
+ * 'isHttps' tells whether the site us secure communication (HTTPS).
+ * @return {bool} Whether we can save data for the specified site.
+ */
+ canSave: function ({isHttps}) {
+ let level = Services.prefs.getIntPref(PREF);
+
+ // Never save any data when full privacy is requested.
+ if (level == PRIVACY_FULL) {
+ return false;
+ }
+
+ // Don't save data for encrypted sites when requested.
+ if (isHttps && level == PRIVACY_ENCRYPTED) {
+ return false;
+ }
+
+ return true;
+ }
+});
diff --git a/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm b/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm
new file mode 100644
index 000000000..ac5731160
--- /dev/null
+++ b/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm
@@ -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/. */
+
+this.EXPORTED_SYMBOLS = ["RecentlyClosedTabsAndWindowsMenuUtils"];
+
+const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+
+var navigatorBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+
+this.RecentlyClosedTabsAndWindowsMenuUtils = {
+
+ /**
+ * Builds up a document fragment of UI items for the recently closed tabs.
+ * @param aWindow
+ * The window that the tabs were closed in.
+ * @param aTagName
+ * The tag name that will be used when creating the UI items.
+ * @param aPrefixRestoreAll (defaults to false)
+ * Whether the 'restore all tabs' item is suffixed or prefixed to the list.
+ * If suffixed (the default) a separator will be inserted before it.
+ * @param aRestoreAllLabel (defaults to "menuRestoreAllTabs.label")
+ * Which localizable string to use for the 'restore all tabs' item.
+ * @returns A document fragment with UI items for each recently closed tab.
+ */
+ getTabsFragment: function(aWindow, aTagName, aPrefixRestoreAll=false,
+ aRestoreAllLabel="menuRestoreAllTabs.label") {
+ let doc = aWindow.document;
+ let fragment = doc.createDocumentFragment();
+ if (SessionStore.getClosedTabCount(aWindow) != 0) {
+ let closedTabs = SessionStore.getClosedTabData(aWindow, false);
+ for (let i = 0; i < closedTabs.length; i++) {
+ createEntry(aTagName, false, i, closedTabs[i], doc,
+ closedTabs[i].title, fragment);
+ }
+
+ createRestoreAllEntry(doc, fragment, aPrefixRestoreAll, false,
+ aRestoreAllLabel, closedTabs.length, aTagName)
+ }
+ return fragment;
+ },
+
+ /**
+ * Builds up a document fragment of UI items for the recently closed windows.
+ * @param aWindow
+ * A window that can be used to create the elements and document fragment.
+ * @param aTagName
+ * The tag name that will be used when creating the UI items.
+ * @param aPrefixRestoreAll (defaults to false)
+ * Whether the 'restore all windows' item is suffixed or prefixed to the list.
+ * If suffixed (the default) a separator will be inserted before it.
+ * @param aRestoreAllLabel (defaults to "menuRestoreAllWindows.label")
+ * Which localizable string to use for the 'restore all windows' item.
+ * @returns A document fragment with UI items for each recently closed window.
+ */
+ getWindowsFragment: function(aWindow, aTagName, aPrefixRestoreAll=false,
+ aRestoreAllLabel="menuRestoreAllWindows.label") {
+ let closedWindowData = SessionStore.getClosedWindowData(false);
+ let doc = aWindow.document;
+ let fragment = doc.createDocumentFragment();
+ if (closedWindowData.length != 0) {
+ let menuLabelString = navigatorBundle.GetStringFromName("menuUndoCloseWindowLabel");
+ let menuLabelStringSingleTab =
+ navigatorBundle.GetStringFromName("menuUndoCloseWindowSingleTabLabel");
+
+ for (let i = 0; i < closedWindowData.length; i++) {
+ let undoItem = closedWindowData[i];
+ let otherTabsCount = undoItem.tabs.length - 1;
+ let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
+ : PluralForm.get(otherTabsCount, menuLabelString);
+ let menuLabel = label.replace("#1", undoItem.title)
+ .replace("#2", otherTabsCount);
+ let selectedTab = undoItem.tabs[undoItem.selected - 1];
+
+ createEntry(aTagName, true, i, selectedTab, doc, menuLabel,
+ fragment);
+ }
+
+ createRestoreAllEntry(doc, fragment, aPrefixRestoreAll, true,
+ aRestoreAllLabel, closedWindowData.length,
+ aTagName);
+ }
+ return fragment;
+ },
+
+
+ /**
+ * Re-open a closed tab and put it to the end of the tab strip.
+ * Used for a middle click.
+ * @param aEvent
+ * The event when the user clicks the menu item
+ */
+ _undoCloseMiddleClick: function(aEvent) {
+ if (aEvent.button != 1)
+ return;
+
+ aEvent.view.undoCloseTab(aEvent.originalTarget.getAttribute("value"));
+ aEvent.view.gBrowser.moveTabToEnd();
+ },
+};
+
+function setImage(aItem, aElement) {
+ let iconURL = aItem.image;
+ // don't initiate a connection just to fetch a favicon (see bug 467828)
+ if (/^https?:/.test(iconURL))
+ iconURL = "moz-anno:favicon:" + iconURL;
+
+ aElement.setAttribute("image", iconURL);
+}
+
+/**
+ * Create a UI entry for a recently closed tab or window.
+ * @param aTagName
+ * the tag name that will be used when creating the UI entry
+ * @param aIsWindowsFragment
+ * whether or not this entry will represent a closed window
+ * @param aIndex
+ * the index of the closed tab
+ * @param aClosedTab
+ * the closed tab
+ * @param aDocument
+ * a document that can be used to create the entry
+ * @param aMenuLabel
+ * the label the created entry will have
+ * @param aFragment
+ * the fragment the created entry will be in
+ */
+function createEntry(aTagName, aIsWindowsFragment, aIndex, aClosedTab,
+ aDocument, aMenuLabel, aFragment) {
+ let element = aDocument.createElementNS(kNSXUL, aTagName);
+
+ element.setAttribute("label", aMenuLabel);
+ if (aClosedTab.image) {
+ setImage(aClosedTab, element);
+ }
+ if (!aIsWindowsFragment) {
+ element.setAttribute("value", aIndex);
+ }
+
+ if (aTagName == "menuitem") {
+ element.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+ }
+
+ element.setAttribute("oncommand", "undoClose" + (aIsWindowsFragment ? "Window" : "Tab") +
+ "(" + aIndex + ");");
+
+ // Set the targetURI attribute so it will be shown in tooltip.
+ // SessionStore uses one-based indexes, so we need to normalize them.
+ let tabData;
+ tabData = aIsWindowsFragment ? aClosedTab
+ : aClosedTab.state;
+ let activeIndex = (tabData.index || tabData.entries.length) - 1;
+ if (activeIndex >= 0 && tabData.entries[activeIndex]) {
+ element.setAttribute("targetURI", tabData.entries[activeIndex].url);
+ }
+
+ if (!aIsWindowsFragment) {
+ element.addEventListener("click", RecentlyClosedTabsAndWindowsMenuUtils._undoCloseMiddleClick, false);
+ }
+ if (aIndex == 0) {
+ element.setAttribute("key", "key_undoClose" + (aIsWindowsFragment? "Window" : "Tab"));
+ }
+
+ aFragment.appendChild(element);
+}
+
+/**
+ * Create an entry to restore all closed windows or tabs.
+ * @param aDocument
+ * a document that can be used to create the entry
+ * @param aFragment
+ * the fragment the created entry will be in
+ * @param aPrefixRestoreAll
+ * whether the 'restore all windows' item is suffixed or prefixed to the list
+ * If suffixed a separator will be inserted before it.
+ * @param aIsWindowsFragment
+ * whether or not this entry will represent a closed window
+ * @param aRestoreAllLabel
+ * which localizable string to use for the entry
+ * @param aEntryCount
+ * the number of elements to be restored by this entry
+ * @param aTagName
+ * the tag name that will be used when creating the UI entry
+ */
+function createRestoreAllEntry(aDocument, aFragment, aPrefixRestoreAll,
+ aIsWindowsFragment, aRestoreAllLabel,
+ aEntryCount, aTagName) {
+ let restoreAllElements = aDocument.createElementNS(kNSXUL, aTagName);
+ restoreAllElements.classList.add("restoreallitem");
+ restoreAllElements.setAttribute("label", navigatorBundle.GetStringFromName(aRestoreAllLabel));
+ restoreAllElements.setAttribute("oncommand",
+ "for (var i = 0; i < " + aEntryCount + "; i++) undoClose" +
+ (aIsWindowsFragment? "Window" : "Tab") + "();");
+ if (aPrefixRestoreAll) {
+ aFragment.insertBefore(restoreAllElements, aFragment.firstChild);
+ } else {
+ aFragment.appendChild(aDocument.createElementNS(kNSXUL, "menuseparator"));
+ aFragment.appendChild(restoreAllElements);
+ }
+} \ No newline at end of file
diff --git a/browser/components/sessionstore/RunState.jsm b/browser/components/sessionstore/RunState.jsm
new file mode 100644
index 000000000..3cdf47718
--- /dev/null
+++ b/browser/components/sessionstore/RunState.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["RunState"];
+
+const STATE_STOPPED = 0;
+const STATE_RUNNING = 1;
+const STATE_QUITTING = 2;
+const STATE_CLOSING = 3;
+const STATE_CLOSED = 4;
+
+// We're initially stopped.
+var state = STATE_STOPPED;
+
+/**
+ * This module keeps track of SessionStore's current run state. We will
+ * always start out at STATE_STOPPED. After the session was read from disk and
+ * the initial browser window has loaded we switch to STATE_RUNNING. On the
+ * first notice that a browser shutdown was granted we switch to STATE_QUITTING.
+ */
+this.RunState = Object.freeze({
+ // If we're stopped then SessionStore hasn't been initialized yet. As soon
+ // as the session is read from disk and the initial browser window has loaded
+ // the run state will change to STATE_RUNNING.
+ get isStopped() {
+ return state == STATE_STOPPED;
+ },
+
+ // STATE_RUNNING is our default mode of operation that we'll spend most of
+ // the time in. After the session was read from disk and the first browser
+ // window has loaded we remain running until the browser quits.
+ get isRunning() {
+ return state == STATE_RUNNING;
+ },
+
+ // We will enter STATE_QUITTING as soon as we receive notice that a browser
+ // shutdown was granted. SessionStore will use this information to prevent
+ // us from collecting partial information while the browser is shutting down
+ // as well as to allow a last single write to disk and block all writes after
+ // that.
+ get isQuitting() {
+ return state >= STATE_QUITTING;
+ },
+
+ // We will enter STATE_CLOSING as soon as SessionStore is uninitialized.
+ // The SessionFile module will know that a last write will happen in this
+ // state and it can do some necessary cleanup.
+ get isClosing() {
+ return state == STATE_CLOSING;
+ },
+
+ // We will enter STATE_CLOSED as soon as SessionFile has written to disk for
+ // the last time before shutdown and will not accept any further writes.
+ get isClosed() {
+ return state == STATE_CLOSED;
+ },
+
+ // Switch the run state to STATE_RUNNING. This must be called after the
+ // session was read from, the initial browser window has loaded and we're
+ // now ready to restore session data.
+ setRunning() {
+ if (this.isStopped) {
+ state = STATE_RUNNING;
+ }
+ },
+
+ // Switch the run state to STATE_CLOSING. This must be called *before* the
+ // last SessionFile.write() call so that SessionFile knows we're closing and
+ // can do some last cleanups and write a proper sessionstore.js file.
+ setClosing() {
+ if (this.isQuitting) {
+ state = STATE_CLOSING;
+ }
+ },
+
+ // Switch the run state to STATE_CLOSED. This must be called by SessionFile
+ // after the last write to disk was accepted and no further writes will be
+ // allowed. Any writes after this stage will cause exceptions.
+ setClosed() {
+ if (this.isClosing) {
+ state = STATE_CLOSED;
+ }
+ },
+
+ // Switch the run state to STATE_QUITTING. This should be called once we're
+ // certain that the browser is going away and before we start collecting the
+ // final window states to save in the session file.
+ setQuitting() {
+ if (this.isRunning) {
+ state = STATE_QUITTING;
+ }
+ },
+});
diff --git a/browser/components/sessionstore/SessionCookies.jsm b/browser/components/sessionstore/SessionCookies.jsm
new file mode 100644
index 000000000..b99ab927b
--- /dev/null
+++ b/browser/components/sessionstore/SessionCookies.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["SessionCookies"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "Utils",
+ "resource://gre/modules/sessionstore/Utils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
+ "resource:///modules/sessionstore/PrivacyLevel.jsm");
+
+// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
+const MAX_EXPIRY = Math.pow(2, 62);
+
+/**
+ * The external API implemented by the SessionCookies module.
+ */
+this.SessionCookies = Object.freeze({
+ update: function (windows) {
+ SessionCookiesInternal.update(windows);
+ },
+
+ getHostsForWindow: function (window, checkPrivacy = false) {
+ return SessionCookiesInternal.getHostsForWindow(window, checkPrivacy);
+ },
+
+ restore(cookies) {
+ SessionCookiesInternal.restore(cookies);
+ }
+});
+
+/**
+ * The internal API.
+ */
+var SessionCookiesInternal = {
+ /**
+ * Stores whether we're initialized, yet.
+ */
+ _initialized: false,
+
+ /**
+ * Retrieve the list of all hosts contained in the given windows' session
+ * history entries (per window) and collect the associated cookies for those
+ * hosts, if any. The given state object is being modified.
+ *
+ * @param windows
+ * Array of window state objects.
+ * [{ tabs: [...], cookies: [...] }, ...]
+ */
+ update: function (windows) {
+ this._ensureInitialized();
+
+ for (let window of windows) {
+ let cookies = [];
+
+ // Collect all hosts for the current window.
+ let hosts = this.getHostsForWindow(window, true);
+
+ for (let host of Object.keys(hosts)) {
+ let isPinned = hosts[host];
+
+ for (let cookie of CookieStore.getCookiesForHost(host)) {
+ // _getCookiesForHost() will only return hosts with the right privacy
+ // rules, so there is no need to do anything special with this call
+ // to PrivacyLevel.canSave().
+ if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned: isPinned})) {
+ cookies.push(cookie);
+ }
+ }
+ }
+
+ // Don't include/keep empty cookie sections.
+ if (cookies.length) {
+ window.cookies = cookies;
+ } else if ("cookies" in window) {
+ delete window.cookies;
+ }
+ }
+ },
+
+ /**
+ * Returns a map of all hosts for a given window that we might want to
+ * collect cookies for.
+ *
+ * @param window
+ * A window state object containing tabs with history entries.
+ * @param checkPrivacy (bool)
+ * Whether to check the privacy level for each host.
+ * @return {object} A map of hosts for a given window state object. The keys
+ * will be hosts, the values are boolean and determine
+ * whether we will use the deferred privacy level when
+ * checking how much data to save on quitting.
+ */
+ getHostsForWindow: function (window, checkPrivacy = false) {
+ let hosts = {};
+
+ for (let tab of window.tabs) {
+ for (let entry of tab.entries) {
+ this._extractHostsFromEntry(entry, hosts, checkPrivacy, tab.pinned);
+ }
+ }
+
+ return hosts;
+ },
+
+ /**
+ * Restores a given list of session cookies.
+ */
+ restore(cookies) {
+
+ for (let cookie of cookies) {
+ let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
+ let cookieObj = {
+ host: cookie.host,
+ path: cookie.path || "",
+ name: cookie.name || ""
+ };
+ if (!Services.cookies.cookieExists(cookieObj, cookie.originAttributes || {})) {
+ Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
+ cookie.value, !!cookie.secure, !!cookie.httponly,
+ /* isSession = */ true, expiry, cookie.originAttributes || {});
+ }
+ }
+ },
+
+ /**
+ * Handles observers notifications that are sent whenever cookies are added,
+ * changed, or removed. Ensures that the storage is updated accordingly.
+ */
+ observe: function (subject, topic, data) {
+ switch (data) {
+ case "added":
+ case "changed":
+ this._updateCookie(subject);
+ break;
+ case "deleted":
+ this._removeCookie(subject);
+ break;
+ case "cleared":
+ CookieStore.clear();
+ break;
+ case "batch-deleted":
+ this._removeCookies(subject);
+ break;
+ case "reload":
+ CookieStore.clear();
+ this._reloadCookies();
+ break;
+ default:
+ throw new Error("Unhandled cookie-changed notification.");
+ }
+ },
+
+ /**
+ * If called for the first time in a session, iterates all cookies in the
+ * cookies service and puts them into the store if they're session cookies.
+ */
+ _ensureInitialized: function () {
+ if (!this._initialized) {
+ this._reloadCookies();
+ this._initialized = true;
+ Services.obs.addObserver(this, "cookie-changed", false);
+ }
+ },
+
+ /**
+ * Fill a given map with hosts found in the given entry's session history and
+ * any child entries.
+ *
+ * @param entry
+ * the history entry, serialized
+ * @param hosts
+ * the hash that will be used to store hosts eg, { hostname: true }
+ * @param checkPrivacy
+ * should we check the privacy level for https
+ * @param isPinned
+ * is the entry we're evaluating for a pinned tab; used only if
+ * checkPrivacy
+ */
+ _extractHostsFromEntry: function (entry, hosts, checkPrivacy, isPinned) {
+ let host = entry._host;
+ let scheme = entry._scheme;
+
+ // If host & scheme aren't defined, then we are likely here in the startup
+ // process via _splitCookiesFromWindow. In that case, we'll turn entry.url
+ // into an nsIURI and get host/scheme from that. This will throw for about:
+ // urls in which case we don't need to do anything.
+ if (!host && !scheme) {
+ try {
+ let uri = Utils.makeURI(entry.url);
+ host = uri.host;
+ scheme = uri.scheme;
+ this._extractHostsFromHostScheme(host, scheme, hosts, checkPrivacy, isPinned);
+ }
+ catch (ex) { }
+ }
+
+ if (entry.children) {
+ for (let child of entry.children) {
+ this._extractHostsFromEntry(child, hosts, checkPrivacy, isPinned);
+ }
+ }
+ },
+
+ /**
+ * Add a given host to a given map of hosts if the privacy level allows
+ * saving cookie data for it.
+ *
+ * @param host
+ * the host of a uri (usually via nsIURI.host)
+ * @param scheme
+ * the scheme of a uri (usually via nsIURI.scheme)
+ * @param hosts
+ * the hash that will be used to store hosts eg, { hostname: true }
+ * @param checkPrivacy
+ * should we check the privacy level for https
+ * @param isPinned
+ * is the entry we're evaluating for a pinned tab; used only if
+ * checkPrivacy
+ */
+ _extractHostsFromHostScheme:
+ function (host, scheme, hosts, checkPrivacy, isPinned) {
+ // host and scheme may not be set (for about: urls for example), in which
+ // case testing scheme will be sufficient.
+ if (/https?/.test(scheme) && !hosts[host] &&
+ (!checkPrivacy ||
+ PrivacyLevel.canSave({isHttps: scheme == "https", isPinned: isPinned}))) {
+ // By setting this to true or false, we can determine when looking at
+ // the host in update() if we should check for privacy.
+ hosts[host] = isPinned;
+ } else if (scheme == "file") {
+ hosts[host] = true;
+ }
+ },
+
+ /**
+ * Updates or adds a given cookie to the store.
+ */
+ _updateCookie: function (cookie) {
+ cookie.QueryInterface(Ci.nsICookie2);
+
+ if (cookie.isSession) {
+ CookieStore.set(cookie);
+ } else {
+ CookieStore.delete(cookie);
+ }
+ },
+
+ /**
+ * Removes a given cookie from the store.
+ */
+ _removeCookie: function (cookie) {
+ cookie.QueryInterface(Ci.nsICookie2);
+
+ if (cookie.isSession) {
+ CookieStore.delete(cookie);
+ }
+ },
+
+ /**
+ * Removes a given list of cookies from the store.
+ */
+ _removeCookies: function (cookies) {
+ for (let i = 0; i < cookies.length; i++) {
+ this._removeCookie(cookies.queryElementAt(i, Ci.nsICookie2));
+ }
+ },
+
+ /**
+ * Iterates all cookies in the cookies service and puts them into the store
+ * if they're session cookies.
+ */
+ _reloadCookies: function () {
+ let iter = Services.cookies.enumerator;
+ while (iter.hasMoreElements()) {
+ this._updateCookie(iter.getNext());
+ }
+ }
+};
+
+/**
+ * Generates all possible subdomains for a given host and prepends a leading
+ * dot to all variants.
+ *
+ * See http://tools.ietf.org/html/rfc6265#section-5.1.3
+ * http://en.wikipedia.org/wiki/HTTP_cookie#Domain_and_Path
+ *
+ * All cookies belonging to a web page will be internally represented by a
+ * nsICookie object. nsICookie.host will be the request host if no domain
+ * parameter was given when setting the cookie. If a specific domain was given
+ * then nsICookie.host will contain that specific domain and prepend a leading
+ * dot to it.
+ *
+ * We thus generate all possible subdomains for a given domain and prepend a
+ * leading dot to them as that is the value that was used as the map key when
+ * the cookie was set.
+ */
+function* getPossibleSubdomainVariants(host) {
+ // Try given domain with a leading dot (.www.example.com).
+ yield "." + host;
+
+ // Stop if there are only two parts left (e.g. example.com was given).
+ let parts = host.split(".");
+ if (parts.length < 3) {
+ return;
+ }
+
+ // Remove the first subdomain (www.example.com -> example.com).
+ let rest = parts.slice(1).join(".");
+
+ // Try possible parent subdomains.
+ yield* getPossibleSubdomainVariants(rest);
+}
+
+/**
+ * The internal cookie storage that keeps track of every active session cookie.
+ * These are stored using maps per host, path, and cookie name.
+ */
+var CookieStore = {
+ /**
+ * The internal structure holding all known cookies.
+ *
+ * Host =>
+ * Path =>
+ * Name => {path: "/", name: "sessionid", secure: true}
+ *
+ * Maps are used for storage but the data structure is equivalent to this:
+ *
+ * this._hosts = {
+ * "www.mozilla.org": {
+ * "/": {
+ * "username": {name: "username", value: "my_name_is", etc...},
+ * "sessionid": {name: "sessionid", value: "1fdb3a", etc...}
+ * }
+ * },
+ * "tbpl.mozilla.org": {
+ * "/path": {
+ * "cookiename": {name: "cookiename", value: "value", etc...}
+ * }
+ * },
+ * ".example.com": {
+ * "/path": {
+ * "cookiename": {name: "cookiename", value: "value", etc...}
+ * }
+ * }
+ * };
+ */
+ _hosts: new Map(),
+
+ /**
+ * Returns the list of stored session cookies for a given host.
+ *
+ * @param host
+ * A string containing the host name we want to get cookies for.
+ */
+ getCookiesForHost: function (host) {
+ let cookies = [];
+
+ let appendCookiesForHost = host => {
+ if (!this._hosts.has(host)) {
+ return;
+ }
+
+ for (let pathToNamesMap of this._hosts.get(host).values()) {
+ for (let nameToCookiesMap of pathToNamesMap.values()) {
+ cookies.push(...nameToCookiesMap.values());
+ }
+ }
+ }
+
+ // Try to find cookies for the given host, e.g. <www.example.com>.
+ // The full hostname will be in the map if the Set-Cookie header did not
+ // have a domain= attribute, i.e. the cookie will only be stored for the
+ // request domain. Also, try to find cookies for subdomains, e.g.
+ // <.example.com>. We will find those variants with a leading dot in the
+ // map if the Set-Cookie header had a domain= attribute, i.e. the cookie
+ // will be stored for a parent domain and we send it for any subdomain.
+ for (let variant of [host, ...getPossibleSubdomainVariants(host)]) {
+ appendCookiesForHost(variant);
+ }
+
+ return cookies;
+ },
+
+ /**
+ * Stores a given cookie.
+ *
+ * @param cookie
+ * The nsICookie2 object to add to the storage.
+ */
+ set: function (cookie) {
+ let jscookie = {host: cookie.host, value: cookie.value};
+
+ // Only add properties with non-default values to save a few bytes.
+ if (cookie.path) {
+ jscookie.path = cookie.path;
+ }
+
+ if (cookie.name) {
+ jscookie.name = cookie.name;
+ }
+
+ if (cookie.isSecure) {
+ jscookie.secure = true;
+ }
+
+ if (cookie.isHttpOnly) {
+ jscookie.httponly = true;
+ }
+
+ if (cookie.expiry < MAX_EXPIRY) {
+ jscookie.expiry = cookie.expiry;
+ }
+
+ if (cookie.originAttributes) {
+ jscookie.originAttributes = cookie.originAttributes;
+ }
+
+ this._ensureMap(cookie).set(cookie.name, jscookie);
+ },
+
+ /**
+ * Removes a given cookie.
+ *
+ * @param cookie
+ * The nsICookie2 object to be removed from storage.
+ */
+ delete: function (cookie) {
+ this._ensureMap(cookie).delete(cookie.name);
+ },
+
+ /**
+ * Removes all cookies.
+ */
+ clear: function () {
+ this._hosts.clear();
+ },
+
+ /**
+ * Creates all maps necessary to store a given cookie.
+ *
+ * @param cookie
+ * The nsICookie2 object to create maps for.
+ *
+ * @return The newly created Map instance mapping cookie names to
+ * internal jscookies, in the given path of the given host.
+ */
+ _ensureMap: function (cookie) {
+ if (!this._hosts.has(cookie.host)) {
+ this._hosts.set(cookie.host, new Map());
+ }
+
+ let originAttributesMap = this._hosts.get(cookie.host);
+ // If cookie.originAttributes is null, originAttributes will be an empty string.
+ let originAttributes = ChromeUtils.originAttributesToSuffix(cookie.originAttributes);
+ if (!originAttributesMap.has(originAttributes)) {
+ originAttributesMap.set(originAttributes, new Map());
+ }
+
+ let pathToNamesMap = originAttributesMap.get(originAttributes);
+
+ if (!pathToNamesMap.has(cookie.path)) {
+ pathToNamesMap.set(cookie.path, new Map());
+ }
+
+ return pathToNamesMap.get(cookie.path);
+ }
+};
diff --git a/browser/components/sessionstore/SessionFile.jsm b/browser/components/sessionstore/SessionFile.jsm
new file mode 100644
index 000000000..80c4e7790
--- /dev/null
+++ b/browser/components/sessionstore/SessionFile.jsm
@@ -0,0 +1,399 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["SessionFile"];
+
+/**
+ * Implementation of all the disk I/O required by the session store.
+ * This is a private API, meant to be used only by the session store.
+ * It will change. Do not use it for any other purpose.
+ *
+ * Note that this module implicitly depends on one of two things:
+ * 1. either the asynchronous file I/O system enqueues its requests
+ * and never attempts to simultaneously execute two I/O requests on
+ * the files used by this module from two distinct threads; or
+ * 2. the clients of this API are well-behaved and do not place
+ * concurrent requests to the files used by this module.
+ *
+ * Otherwise, we could encounter bugs, especially under Windows,
+ * e.g. if a request attempts to write sessionstore.js while
+ * another attempts to copy that file.
+ *
+ * This implementation uses OS.File, which guarantees property 1.
+ */
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RunState",
+ "resource:///modules/sessionstore/RunState.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+ "resource://gre/modules/TelemetryStopwatch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
+ "@mozilla.org/base/telemetry;1", "nsITelemetry");
+XPCOMUtils.defineLazyServiceGetter(this, "sessionStartup",
+ "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionWorker",
+ "resource:///modules/sessionstore/SessionWorker.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+
+const PREF_UPGRADE_BACKUP = "browser.sessionstore.upgradeBackup.latestBuildID";
+const PREF_MAX_UPGRADE_BACKUPS = "browser.sessionstore.upgradeBackup.maxUpgradeBackups";
+
+const PREF_MAX_SERIALIZE_BACK = "browser.sessionstore.max_serialize_back";
+const PREF_MAX_SERIALIZE_FWD = "browser.sessionstore.max_serialize_forward";
+
+this.SessionFile = {
+ /**
+ * Read the contents of the session file, asynchronously.
+ */
+ read: function () {
+ return SessionFileInternal.read();
+ },
+ /**
+ * Write the contents of the session file, asynchronously.
+ */
+ write: function (aData) {
+ return SessionFileInternal.write(aData);
+ },
+ /**
+ * Wipe the contents of the session file, asynchronously.
+ */
+ wipe: function () {
+ return SessionFileInternal.wipe();
+ },
+
+ /**
+ * Return the paths to the files used to store, backup, etc.
+ * the state of the file.
+ */
+ get Paths() {
+ return SessionFileInternal.Paths;
+ }
+};
+
+Object.freeze(SessionFile);
+
+var Path = OS.Path;
+var profileDir = OS.Constants.Path.profileDir;
+
+var SessionFileInternal = {
+ Paths: Object.freeze({
+ // The path to the latest version of sessionstore written during a clean
+ // shutdown. After startup, it is renamed `cleanBackup`.
+ clean: Path.join(profileDir, "sessionstore.js"),
+
+ // The path at which we store the previous version of `clean`. Updated
+ // whenever we successfully load from `clean`.
+ cleanBackup: Path.join(profileDir, "sessionstore-backups", "previous.js"),
+
+ // The directory containing all sessionstore backups.
+ backups: Path.join(profileDir, "sessionstore-backups"),
+
+ // The path to the latest version of the sessionstore written
+ // during runtime. Generally, this file contains more
+ // privacy-sensitive information than |clean|, and this file is
+ // therefore removed during clean shutdown. This file is designed to protect
+ // against crashes / sudden shutdown.
+ recovery: Path.join(profileDir, "sessionstore-backups", "recovery.js"),
+
+ // The path to the previous version of the sessionstore written
+ // during runtime (e.g. 15 seconds before recovery). In case of a
+ // clean shutdown, this file is removed. Generally, this file
+ // contains more privacy-sensitive information than |clean|, and
+ // this file is therefore removed during clean shutdown. This
+ // file is designed to protect against crashes that are nasty
+ // enough to corrupt |recovery|.
+ recoveryBackup: Path.join(profileDir, "sessionstore-backups", "recovery.bak"),
+
+ // The path to a backup created during an upgrade of Firefox.
+ // Having this backup protects the user essentially from bugs in
+ // Firefox or add-ons, especially for users of Nightly. This file
+ // does not contain any information more sensitive than |clean|.
+ upgradeBackupPrefix: Path.join(profileDir, "sessionstore-backups", "upgrade.js-"),
+
+ // The path to the backup of the version of the session store used
+ // during the latest upgrade of Firefox. During load/recovery,
+ // this file should be used if both |path|, |backupPath| and
+ // |latestStartPath| are absent/incorrect. May be "" if no
+ // upgrade backup has ever been performed. This file does not
+ // contain any information more sensitive than |clean|.
+ get upgradeBackup() {
+ let latestBackupID = SessionFileInternal.latestUpgradeBackupID;
+ if (!latestBackupID) {
+ return "";
+ }
+ return this.upgradeBackupPrefix + latestBackupID;
+ },
+
+ // The path to a backup created during an upgrade of Firefox.
+ // Having this backup protects the user essentially from bugs in
+ // Firefox, especially for users of Nightly.
+ get nextUpgradeBackup() {
+ return this.upgradeBackupPrefix + Services.appinfo.platformBuildID;
+ },
+
+ /**
+ * The order in which to search for a valid sessionstore file.
+ */
+ get loadOrder() {
+ // If `clean` exists and has been written without corruption during
+ // the latest shutdown, we need to use it.
+ //
+ // Otherwise, `recovery` and `recoveryBackup` represent the most
+ // recent state of the session store.
+ //
+ // Finally, if nothing works, fall back to the last known state
+ // that can be loaded (`cleanBackup`) or, if available, to the
+ // backup performed during the latest upgrade.
+ let order = ["clean",
+ "recovery",
+ "recoveryBackup",
+ "cleanBackup"];
+ if (SessionFileInternal.latestUpgradeBackupID) {
+ // We have an upgradeBackup
+ order.push("upgradeBackup");
+ }
+ return order;
+ },
+ }),
+
+ // Number of attempted calls to `write`.
+ // Note that we may have _attempts > _successes + _failures,
+ // if attempts never complete.
+ // Used for error reporting.
+ _attempts: 0,
+
+ // Number of successful calls to `write`.
+ // Used for error reporting.
+ _successes: 0,
+
+ // Number of failed calls to `write`.
+ // Used for error reporting.
+ _failures: 0,
+
+ // Resolved once initialization is complete.
+ // The promise never rejects.
+ _deferredInitialized: PromiseUtils.defer(),
+
+ // `true` once we have started initialization, i.e. once something
+ // has been scheduled that will eventually resolve `_deferredInitialized`.
+ _initializationStarted: false,
+
+ // The ID of the latest version of Gecko for which we have an upgrade backup
+ // or |undefined| if no upgrade backup was ever written.
+ get latestUpgradeBackupID() {
+ try {
+ return Services.prefs.getCharPref(PREF_UPGRADE_BACKUP);
+ } catch (ex) {
+ return undefined;
+ }
+ },
+
+ // Find the correct session file, read it and setup the worker.
+ read: Task.async(function* () {
+ this._initializationStarted = true;
+
+ let result;
+ let noFilesFound = true;
+ // Attempt to load by order of priority from the various backups
+ for (let key of this.Paths.loadOrder) {
+ let corrupted = false;
+ let exists = true;
+ try {
+ let path = this.Paths[key];
+ let startMs = Date.now();
+
+ let source = yield OS.File.read(path, { encoding: "utf-8" });
+ let parsed = JSON.parse(source);
+
+ if (!SessionStore.isFormatVersionCompatible(parsed.version || ["sessionrestore", 0] /*fallback for old versions*/)) {
+ // Skip sessionstore files that we don't understand.
+ Cu.reportError("Cannot extract data from Session Restore file " + path + ". Wrong format/version: " + JSON.stringify(parsed.version) + ".");
+ continue;
+ }
+ result = {
+ origin: key,
+ source: source,
+ parsed: parsed
+ };
+ Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
+ add(false);
+ Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS").
+ add(Date.now() - startMs);
+ break;
+ } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
+ exists = false;
+ } catch (ex if ex instanceof OS.File.Error) {
+ // The file might be inaccessible due to wrong permissions
+ // or similar failures. We'll just count it as "corrupted".
+ console.error("Could not read session file ", ex, ex.stack);
+ corrupted = true;
+ } catch (ex if ex instanceof SyntaxError) {
+ console.error("Corrupt session file (invalid JSON found) ", ex, ex.stack);
+ // File is corrupted, try next file
+ corrupted = true;
+ } finally {
+ if (exists) {
+ noFilesFound = false;
+ Telemetry.getHistogramById("FX_SESSION_RESTORE_CORRUPT_FILE").
+ add(corrupted);
+ }
+ }
+ }
+
+ // All files are corrupted if files found but none could deliver a result.
+ let allCorrupt = !noFilesFound && !result;
+ Telemetry.getHistogramById("FX_SESSION_RESTORE_ALL_FILES_CORRUPT").
+ add(allCorrupt);
+
+ if (!result) {
+ // If everything fails, start with an empty session.
+ result = {
+ origin: "empty",
+ source: "",
+ parsed: null
+ };
+ }
+
+ result.noFilesFound = noFilesFound;
+
+ // Initialize the worker (in the background) to let it handle backups and also
+ // as a workaround for bug 964531.
+ let promiseInitialized = SessionWorker.post("init", [result.origin, this.Paths, {
+ maxUpgradeBackups: Preferences.get(PREF_MAX_UPGRADE_BACKUPS, 3),
+ maxSerializeBack: Preferences.get(PREF_MAX_SERIALIZE_BACK, 10),
+ maxSerializeForward: Preferences.get(PREF_MAX_SERIALIZE_FWD, -1)
+ }]);
+
+ promiseInitialized.catch(err => {
+ // Ensure that we report errors but that they do not stop us.
+ Promise.reject(err);
+ }).then(() => this._deferredInitialized.resolve());
+
+ return result;
+ }),
+
+ // Post a message to the worker, making sure that it has been initialized
+ // first.
+ _postToWorker: Task.async(function*(...args) {
+ if (!this._initializationStarted) {
+ // Initializing the worker is somewhat complex, as proper handling of
+ // backups requires us to first read and check the session. Consequently,
+ // the only way to initialize the worker is to first call `this.read()`.
+
+ // The call to `this.read()` causes background initialization of the worker.
+ // Initialization will be complete once `this._deferredInitialized.promise`
+ // resolves.
+ this.read();
+ }
+ yield this._deferredInitialized.promise;
+ return SessionWorker.post(...args)
+ }),
+
+ write: function (aData) {
+ if (RunState.isClosed) {
+ return Promise.reject(new Error("SessionFile is closed"));
+ }
+
+ let isFinalWrite = false;
+ if (RunState.isClosing) {
+ // If shutdown has started, we will want to stop receiving
+ // write instructions.
+ isFinalWrite = true;
+ RunState.setClosed();
+ }
+
+ let performShutdownCleanup = isFinalWrite &&
+ !sessionStartup.isAutomaticRestoreEnabled();
+
+ this._attempts++;
+ let options = {isFinalWrite, performShutdownCleanup};
+ let promise = this._postToWorker("write", [aData, options]);
+
+ // Wait until the write is done.
+ promise = promise.then(msg => {
+ // Record how long the write took.
+ this._recordTelemetry(msg.telemetry);
+ this._successes++;
+ if (msg.result.upgradeBackup) {
+ // We have just completed a backup-on-upgrade, store the information
+ // in preferences.
+ Services.prefs.setCharPref(PREF_UPGRADE_BACKUP,
+ Services.appinfo.platformBuildID);
+ }
+ }, err => {
+ // Catch and report any errors.
+ console.error("Could not write session state file ", err, err.stack);
+ this._failures++;
+ // By not doing anything special here we ensure that |promise| cannot
+ // be rejected anymore. The shutdown/cleanup code at the end of the
+ // function will thus always be executed.
+ });
+
+ // Ensure that we can write sessionstore.js cleanly before the profile
+ // becomes unaccessible.
+ AsyncShutdown.profileBeforeChange.addBlocker(
+ "SessionFile: Finish writing Session Restore data",
+ promise,
+ {
+ fetchState: () => ({
+ options,
+ attempts: this._attempts,
+ successes: this._successes,
+ failures: this._failures,
+ })
+ });
+
+ // This code will always be executed because |promise| can't fail anymore.
+ // We ensured that by having a reject handler that reports the failure but
+ // doesn't forward the rejection.
+ return promise.then(() => {
+ // Remove the blocker, no matter if writing failed or not.
+ AsyncShutdown.profileBeforeChange.removeBlocker(promise);
+
+ if (isFinalWrite) {
+ Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", "");
+ }
+ });
+ },
+
+ wipe: function () {
+ return this._postToWorker("wipe");
+ },
+
+ _recordTelemetry: function(telemetry) {
+ for (let id of Object.keys(telemetry)){
+ let value = telemetry[id];
+ let samples = [];
+ if (Array.isArray(value)) {
+ samples.push(...value);
+ } else {
+ samples.push(value);
+ }
+ let histogram = Telemetry.getHistogramById(id);
+ for (let sample of samples) {
+ histogram.add(sample);
+ }
+ }
+ }
+};
diff --git a/browser/components/sessionstore/SessionHistory.jsm b/browser/components/sessionstore/SessionHistory.jsm
new file mode 100644
index 000000000..aa9c10379
--- /dev/null
+++ b/browser/components/sessionstore/SessionHistory.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["SessionHistory"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Utils",
+ "resource://gre/modules/sessionstore/Utils.jsm");
+
+function debug(msg) {
+ Services.console.logStringMessage("SessionHistory: " + msg);
+}
+
+/**
+ * The external API exported by this module.
+ */
+this.SessionHistory = Object.freeze({
+ isEmpty: function (docShell) {
+ return SessionHistoryInternal.isEmpty(docShell);
+ },
+
+ collect: function (docShell) {
+ return SessionHistoryInternal.collect(docShell);
+ },
+
+ restore: function (docShell, tabData) {
+ SessionHistoryInternal.restore(docShell, tabData);
+ }
+});
+
+/**
+ * The internal API for the SessionHistory module.
+ */
+var SessionHistoryInternal = {
+ /**
+ * Returns whether the given docShell's session history is empty.
+ *
+ * @param docShell
+ * The docShell that owns the session history.
+ */
+ isEmpty: function (docShell) {
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let history = webNavigation.sessionHistory;
+ if (!webNavigation.currentURI) {
+ return true;
+ }
+ let uri = webNavigation.currentURI.spec;
+ return uri == "about:blank" && history.count == 0;
+ },
+
+ /**
+ * Collects session history data for a given docShell.
+ *
+ * @param docShell
+ * The docShell that owns the session history.
+ */
+ collect: function (docShell) {
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
+
+ let data = {entries: [], userContextId: loadContext.originAttributes.userContextId };
+
+ if (history && history.count > 0) {
+ // Loop over the transaction linked list directly so we can get the
+ // persist property for each transaction.
+ for (let txn = history.rootTransaction; txn; txn = txn.next) {
+ let entry = this.serializeEntry(txn.sHEntry);
+ entry.persist = txn.persist;
+ data.entries.push(entry);
+ }
+
+ // Ensure the index isn't out of bounds if an exception was thrown above.
+ data.index = Math.min(history.index + 1, data.entries.length);
+ }
+
+ // If either the session history isn't available yet or doesn't have any
+ // valid entries, make sure we at least include the current page.
+ if (data.entries.length == 0) {
+ let uri = webNavigation.currentURI.spec;
+ let body = webNavigation.document.body;
+ // We landed here because the history is inaccessible or there are no
+ // history entries. In that case we should at least record the docShell's
+ // current URL as a single history entry. If the URL is not about:blank
+ // or it's a blank tab that was modified (like a custom newtab page),
+ // record it. For about:blank we explicitly want an empty array without
+ // an 'index' property to denote that there are no history entries.
+ if (uri != "about:blank" || (body && body.hasChildNodes())) {
+ data.entries.push({ url: uri });
+ data.index = 1;
+ }
+ }
+
+ return data;
+ },
+
+ /**
+ * Get an object that is a serialized representation of a History entry.
+ *
+ * @param shEntry
+ * nsISHEntry instance
+ * @return object
+ */
+ serializeEntry: function (shEntry) {
+ let entry = { url: shEntry.URI.spec };
+
+ // Save some bytes and don't include the title property
+ // if that's identical to the current entry's URL.
+ if (shEntry.title && shEntry.title != entry.url) {
+ entry.title = shEntry.title;
+ }
+ if (shEntry.isSubFrame) {
+ entry.subframe = true;
+ }
+
+ entry.charset = shEntry.URI.originCharset;
+
+ let cacheKey = shEntry.cacheKey;
+ if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
+ cacheKey.data != 0) {
+ // XXXbz would be better to have cache keys implement
+ // nsISerializable or something.
+ entry.cacheKey = cacheKey.data;
+ }
+ entry.ID = shEntry.ID;
+ entry.docshellID = shEntry.docshellID;
+
+ // We will include the property only if it's truthy to save a couple of
+ // bytes when the resulting object is stringified and saved to disk.
+ if (shEntry.referrerURI) {
+ entry.referrer = shEntry.referrerURI.spec;
+ entry.referrerPolicy = shEntry.referrerPolicy;
+ }
+
+ if (shEntry.originalURI) {
+ entry.originalURI = shEntry.originalURI.spec;
+ }
+
+ if (shEntry.loadReplace) {
+ entry.loadReplace = shEntry.loadReplace;
+ }
+
+ if (shEntry.srcdocData)
+ entry.srcdocData = shEntry.srcdocData;
+
+ if (shEntry.isSrcdocEntry)
+ entry.isSrcdocEntry = shEntry.isSrcdocEntry;
+
+ if (shEntry.baseURI)
+ entry.baseURI = shEntry.baseURI.spec;
+
+ if (shEntry.contentType)
+ entry.contentType = shEntry.contentType;
+
+ if (shEntry.scrollRestorationIsManual) {
+ entry.scrollRestorationIsManual = true;
+ } else {
+ let x = {}, y = {};
+ shEntry.getScrollPosition(x, y);
+ if (x.value != 0 || y.value != 0)
+ entry.scroll = x.value + "," + y.value;
+ }
+
+ // Collect triggeringPrincipal data for the current history entry.
+ // Please note that before Bug 1297338 there was no concept of a
+ // principalToInherit. To remain backward/forward compatible we
+ // serialize the principalToInherit as triggeringPrincipal_b64.
+ // Once principalToInherit is well established (within FF55)
+ // we can update this code, remove triggeringPrincipal_b64 and
+ // just keep triggeringPrincipal_base64 as well as
+ // principalToInherit_base64; see Bug 1301666.
+ if (shEntry.principalToInherit) {
+ try {
+ let principalToInherit = Utils.serializePrincipal(shEntry.principalToInherit);
+ if (principalToInherit) {
+ entry.triggeringPrincipal_b64 = principalToInherit;
+ entry.principalToInherit_base64 = principalToInherit;
+ }
+ } catch (e) {
+ debug(e);
+ }
+ }
+
+ if (shEntry.triggeringPrincipal) {
+ try {
+ let triggeringPrincipal = Utils.serializePrincipal(shEntry.triggeringPrincipal);
+ if (triggeringPrincipal) {
+ entry.triggeringPrincipal_base64 = triggeringPrincipal;
+ }
+ } catch (e) {
+ debug(e);
+ }
+ }
+
+ entry.docIdentifier = shEntry.BFCacheEntry.ID;
+
+ if (shEntry.stateData != null) {
+ entry.structuredCloneState = shEntry.stateData.getDataAsBase64();
+ entry.structuredCloneVersion = shEntry.stateData.formatVersion;
+ }
+
+ if (!(shEntry instanceof Ci.nsISHContainer)) {
+ return entry;
+ }
+
+ if (shEntry.childCount > 0 && !shEntry.hasDynamicallyAddedChild()) {
+ let children = [];
+ for (let i = 0; i < shEntry.childCount; i++) {
+ let child = shEntry.GetChildAt(i);
+
+ if (child) {
+ // Don't try to restore framesets containing wyciwyg URLs.
+ // (cf. bug 424689 and bug 450595)
+ if (child.URI.schemeIs("wyciwyg")) {
+ children.length = 0;
+ break;
+ }
+
+ children.push(this.serializeEntry(child));
+ }
+ }
+
+ if (children.length) {
+ entry.children = children;
+ }
+ }
+
+ return entry;
+ },
+
+ /**
+ * Restores session history data for a given docShell.
+ *
+ * @param docShell
+ * The docShell that owns the session history.
+ * @param tabData
+ * The tabdata including all history entries.
+ */
+ restore: function (docShell, tabData) {
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let history = webNavigation.sessionHistory;
+ if (history.count > 0) {
+ history.PurgeHistory(history.count);
+ }
+ history.QueryInterface(Ci.nsISHistoryInternal);
+
+ let idMap = { used: {} };
+ let docIdentMap = {};
+ for (let i = 0; i < tabData.entries.length; i++) {
+ let entry = tabData.entries[i];
+ //XXXzpao Wallpaper patch for bug 514751
+ if (!entry.url)
+ continue;
+ let persist = "persist" in entry ? entry.persist : true;
+ history.addEntry(this.deserializeEntry(entry, idMap, docIdentMap), persist);
+ }
+
+ // Select the right history entry.
+ let index = tabData.index - 1;
+ if (index < history.count && history.index != index) {
+ history.getEntryAtIndex(index, true);
+ }
+ },
+
+ /**
+ * Expands serialized history data into a session-history-entry instance.
+ *
+ * @param entry
+ * Object containing serialized history data for a URL
+ * @param idMap
+ * Hash for ensuring unique frame IDs
+ * @param docIdentMap
+ * Hash to ensure reuse of BFCache entries
+ * @returns nsISHEntry
+ */
+ deserializeEntry: function (entry, idMap, docIdentMap) {
+
+ var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
+ createInstance(Ci.nsISHEntry);
+
+ shEntry.setURI(Utils.makeURI(entry.url, entry.charset));
+ shEntry.setTitle(entry.title || entry.url);
+ if (entry.subframe)
+ shEntry.setIsSubFrame(entry.subframe || false);
+ shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
+ if (entry.contentType)
+ shEntry.contentType = entry.contentType;
+ if (entry.referrer) {
+ shEntry.referrerURI = Utils.makeURI(entry.referrer);
+ shEntry.referrerPolicy = entry.referrerPolicy;
+ }
+ if (entry.originalURI) {
+ shEntry.originalURI = Utils.makeURI(entry.originalURI);
+ }
+ if (entry.loadReplace) {
+ shEntry.loadReplace = entry.loadReplace;
+ }
+ if (entry.isSrcdocEntry)
+ shEntry.srcdocData = entry.srcdocData;
+ if (entry.baseURI)
+ shEntry.baseURI = Utils.makeURI(entry.baseURI);
+
+ if (entry.cacheKey) {
+ var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ cacheKey.data = entry.cacheKey;
+ shEntry.cacheKey = cacheKey;
+ }
+
+ if (entry.ID) {
+ // get a new unique ID for this frame (since the one from the last
+ // start might already be in use)
+ var id = idMap[entry.ID] || 0;
+ if (!id) {
+ for (id = Date.now(); id in idMap.used; id++);
+ idMap[entry.ID] = id;
+ idMap.used[id] = true;
+ }
+ shEntry.ID = id;
+ }
+
+ if (entry.docshellID)
+ shEntry.docshellID = entry.docshellID;
+
+ if (entry.structuredCloneState && entry.structuredCloneVersion) {
+ shEntry.stateData =
+ Cc["@mozilla.org/docshell/structured-clone-container;1"].
+ createInstance(Ci.nsIStructuredCloneContainer);
+
+ shEntry.stateData.initFromBase64(entry.structuredCloneState,
+ entry.structuredCloneVersion);
+ }
+
+ if (entry.scrollRestorationIsManual) {
+ shEntry.scrollRestorationIsManual = true;
+ } else if (entry.scroll) {
+ var scrollPos = (entry.scroll || "0,0").split(",");
+ scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
+ shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
+ }
+
+ let childDocIdents = {};
+ if (entry.docIdentifier) {
+ // If we have a serialized document identifier, try to find an SHEntry
+ // which matches that doc identifier and adopt that SHEntry's
+ // BFCacheEntry. If we don't find a match, insert shEntry as the match
+ // for the document identifier.
+ let matchingEntry = docIdentMap[entry.docIdentifier];
+ if (!matchingEntry) {
+ matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
+ docIdentMap[entry.docIdentifier] = matchingEntry;
+ }
+ else {
+ shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
+ childDocIdents = matchingEntry.childDocIdents;
+ }
+ }
+
+ // The field entry.owner_b64 got renamed to entry.triggeringPricipal_b64 in
+ // Bug 1286472. To remain backward compatible we still have to support that
+ // field for a few cycles before we can remove it within Bug 1289785.
+ if (entry.owner_b64) {
+ entry.triggeringPricipal_b64 = entry.owner_b64;
+ delete entry.owner_b64;
+ }
+
+ // Before introducing the concept of principalToInherit we only had
+ // a triggeringPrincipal within every entry which basically is the
+ // equivalent of the new principalToInherit. To avoid compatibility
+ // issues, we first check if the entry has entries for
+ // triggeringPrincipal_base64 and principalToInherit_base64. If not
+ // we fall back to using the principalToInherit (which is stored
+ // as triggeringPrincipal_b64) as the triggeringPrincipal and
+ // the principalToInherit.
+ // FF55 will remove the triggeringPrincipal_b64, see Bug 1301666.
+ if (entry.triggeringPrincipal_base64 || entry.principalToInherit_base64) {
+ if (entry.triggeringPrincipal_base64) {
+ shEntry.triggeringPrincipal =
+ Utils.deserializePrincipal(entry.triggeringPrincipal_base64);
+ }
+ if (entry.principalToInherit_base64) {
+ shEntry.principalToInherit =
+ Utils.deserializePrincipal(entry.principalToInherit_base64);
+ }
+ } else if (entry.triggeringPrincipal_b64) {
+ shEntry.triggeringPrincipal = Utils.deserializePrincipal(entry.triggeringPrincipal_b64);
+ shEntry.principalToInherit = shEntry.triggeringPrincipal;
+ }
+
+ if (entry.children && shEntry instanceof Ci.nsISHContainer) {
+ for (var i = 0; i < entry.children.length; i++) {
+ //XXXzpao Wallpaper patch for bug 514751
+ if (!entry.children[i].url)
+ continue;
+
+ // We're getting sessionrestore.js files with a cycle in the
+ // doc-identifier graph, likely due to bug 698656. (That is, we have
+ // an entry where doc identifier A is an ancestor of doc identifier B,
+ // and another entry where doc identifier B is an ancestor of A.)
+ //
+ // If we were to respect these doc identifiers, we'd create a cycle in
+ // the SHEntries themselves, which causes the docshell to loop forever
+ // when it looks for the root SHEntry.
+ //
+ // So as a hack to fix this, we restrict the scope of a doc identifier
+ // to be a node's siblings and cousins, and pass childDocIdents, not
+ // aDocIdents, to _deserializeHistoryEntry. That is, we say that two
+ // SHEntries with the same doc identifier have the same document iff
+ // they have the same parent or their parents have the same document.
+
+ shEntry.AddChild(this.deserializeEntry(entry.children[i], idMap,
+ childDocIdents), i);
+ }
+ }
+
+ return shEntry;
+ },
+
+};
diff --git a/browser/components/sessionstore/SessionMigration.jsm b/browser/components/sessionstore/SessionMigration.jsm
new file mode 100644
index 000000000..ff339eba9
--- /dev/null
+++ b/browser/components/sessionstore/SessionMigration.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["SessionMigration"];
+
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+// An encoder to UTF-8.
+XPCOMUtils.defineLazyGetter(this, "gEncoder", function () {
+ return new TextEncoder();
+});
+
+// A decoder.
+XPCOMUtils.defineLazyGetter(this, "gDecoder", function () {
+ return new TextDecoder();
+});
+
+var SessionMigrationInternal = {
+ /**
+ * Convert the original session restore state into a minimal state. It will
+ * only contain:
+ * - open windows
+ * - with tabs
+ * - with history entries with only title, url
+ * - with pinned state
+ * - with tab group info (hidden + group id)
+ * - with selected tab info
+ * - with selected window info
+ *
+ * The complete state is then wrapped into the "about:welcomeback" page as
+ * form field info to be restored when restoring the state.
+ */
+ convertState: function(aStateObj) {
+ let state = {
+ selectedWindow: aStateObj.selectedWindow,
+ _closedWindows: []
+ };
+ state.windows = aStateObj.windows.map(function(oldWin) {
+ var win = {extData: {}};
+ win.tabs = oldWin.tabs.map(function(oldTab) {
+ var tab = {};
+ // Keep only titles and urls for history entries
+ tab.entries = oldTab.entries.map(function(entry) {
+ return {url: entry.url, title: entry.title};
+ });
+ tab.index = oldTab.index;
+ tab.hidden = oldTab.hidden;
+ tab.pinned = oldTab.pinned;
+ return tab;
+ });
+ win.selected = oldWin.selected;
+ win._closedTabs = [];
+ return win;
+ });
+ let url = "about:welcomeback";
+ let formdata = {id: {sessionData: state}, url};
+ return {windows: [{tabs: [{entries: [{url}], formdata}]}]};
+ },
+ /**
+ * Asynchronously read session restore state (JSON) from a path
+ */
+ readState: function(aPath) {
+ return Task.spawn(function() {
+ let bytes = yield OS.File.read(aPath);
+ let text = gDecoder.decode(bytes);
+ let state = JSON.parse(text);
+ throw new Task.Result(state);
+ });
+ },
+ /**
+ * Asynchronously write session restore state as JSON to a path
+ */
+ writeState: function(aPath, aState) {
+ let bytes = gEncoder.encode(JSON.stringify(aState));
+ return OS.File.writeAtomic(aPath, bytes, {tmpPath: aPath + ".tmp"});
+ }
+}
+
+var SessionMigration = {
+ /**
+ * Migrate a limited set of session data from one path to another.
+ */
+ migrate: function(aFromPath, aToPath) {
+ return Task.spawn(function() {
+ let inState = yield SessionMigrationInternal.readState(aFromPath);
+ let outState = SessionMigrationInternal.convertState(inState);
+ // Unfortunately, we can't use SessionStore's own SessionFile to
+ // write out the data because it has a dependency on the profile dir
+ // being known. When the migration runs, there is no guarantee that
+ // that's true.
+ yield SessionMigrationInternal.writeState(aToPath, outState);
+ });
+ }
+};
diff --git a/browser/components/sessionstore/SessionSaver.jsm b/browser/components/sessionstore/SessionSaver.jsm
new file mode 100644
index 000000000..d672f8877
--- /dev/null
+++ b/browser/components/sessionstore/SessionSaver.jsm
@@ -0,0 +1,264 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["SessionSaver"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Timer.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
+ "resource:///modules/sessionstore/PrivacyFilter.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RunState",
+ "resource:///modules/sessionstore/RunState.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
+ "resource:///modules/sessionstore/SessionFile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+// Minimal interval between two save operations (in milliseconds).
+XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
+ const PREF = "browser.sessionstore.interval";
+
+ // Observer that updates the cached value when the preference changes.
+ Services.prefs.addObserver(PREF, () => {
+ this.gInterval = Services.prefs.getIntPref(PREF);
+
+ // Cancel any pending runs and call runDelayed() with
+ // zero to apply the newly configured interval.
+ SessionSaverInternal.cancel();
+ SessionSaverInternal.runDelayed(0);
+ }, false);
+
+ return Services.prefs.getIntPref(PREF);
+});
+
+// Notify observers about a given topic with a given subject.
+function notify(subject, topic) {
+ Services.obs.notifyObservers(subject, topic, "");
+}
+
+// TelemetryStopwatch helper functions.
+function stopWatch(method) {
+ return function (...histograms) {
+ for (let hist of histograms) {
+ TelemetryStopwatch[method]("FX_SESSION_RESTORE_" + hist);
+ }
+ };
+}
+
+var stopWatchStart = stopWatch("start");
+var stopWatchCancel = stopWatch("cancel");
+var stopWatchFinish = stopWatch("finish");
+
+/**
+ * The external API implemented by the SessionSaver module.
+ */
+this.SessionSaver = Object.freeze({
+ /**
+ * Immediately saves the current session to disk.
+ */
+ run: function () {
+ return SessionSaverInternal.run();
+ },
+
+ /**
+ * Saves the current session to disk delayed by a given amount of time. Should
+ * another delayed run be scheduled already, we will ignore the given delay
+ * and state saving may occur a little earlier.
+ */
+ runDelayed: function () {
+ SessionSaverInternal.runDelayed();
+ },
+
+ /**
+ * Sets the last save time to the current time. This will cause us to wait for
+ * at least the configured interval when runDelayed() is called next.
+ */
+ updateLastSaveTime: function () {
+ SessionSaverInternal.updateLastSaveTime();
+ },
+
+ /**
+ * Cancels all pending session saves.
+ */
+ cancel: function () {
+ SessionSaverInternal.cancel();
+ }
+});
+
+/**
+ * The internal API.
+ */
+var SessionSaverInternal = {
+ /**
+ * The timeout ID referencing an active timer for a delayed save. When no
+ * save is pending, this is null.
+ */
+ _timeoutID: null,
+
+ /**
+ * A timestamp that keeps track of when we saved the session last. We will
+ * this to determine the correct interval between delayed saves to not deceed
+ * the configured session write interval.
+ */
+ _lastSaveTime: 0,
+
+ /**
+ * Immediately saves the current session to disk.
+ */
+ run: function () {
+ return this._saveState(true /* force-update all windows */);
+ },
+
+ /**
+ * Saves the current session to disk delayed by a given amount of time. Should
+ * another delayed run be scheduled already, we will ignore the given delay
+ * and state saving may occur a little earlier.
+ *
+ * @param delay (optional)
+ * The minimum delay in milliseconds to wait for until we collect and
+ * save the current session.
+ */
+ runDelayed: function (delay = 2000) {
+ // Bail out if there's a pending run.
+ if (this._timeoutID) {
+ return;
+ }
+
+ // Interval until the next disk operation is allowed.
+ delay = Math.max(this._lastSaveTime + gInterval - Date.now(), delay, 0);
+
+ // Schedule a state save.
+ this._timeoutID = setTimeout(() => this._saveStateAsync(), delay);
+ },
+
+ /**
+ * Sets the last save time to the current time. This will cause us to wait for
+ * at least the configured interval when runDelayed() is called next.
+ */
+ updateLastSaveTime: function () {
+ this._lastSaveTime = Date.now();
+ },
+
+ /**
+ * Cancels all pending session saves.
+ */
+ cancel: function () {
+ clearTimeout(this._timeoutID);
+ this._timeoutID = null;
+ },
+
+ /**
+ * Saves the current session state. Collects data and writes to disk.
+ *
+ * @param forceUpdateAllWindows (optional)
+ * Forces us to recollect data for all windows and will bypass and
+ * update the corresponding caches.
+ */
+ _saveState: function (forceUpdateAllWindows = false) {
+ // Cancel any pending timeouts.
+ this.cancel();
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Don't save (or even collect) anything in permanent private
+ // browsing mode
+
+ this.updateLastSaveTime();
+ return Promise.resolve();
+ }
+
+ stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
+ let state = SessionStore.getCurrentState(forceUpdateAllWindows);
+ PrivacyFilter.filterPrivateWindowsAndTabs(state);
+
+ // Make sure that we keep the previous session if we started with a single
+ // private window and no non-private windows have been opened, yet.
+ if (state.deferredInitialState) {
+ state.windows = state.deferredInitialState.windows || [];
+ delete state.deferredInitialState;
+ }
+
+ if (AppConstants.platform != "macosx") {
+ // We want to restore closed windows that are marked with _shouldRestore.
+ // We're doing this here because we want to control this only when saving
+ // the file.
+ while (state._closedWindows.length) {
+ let i = state._closedWindows.length - 1;
+
+ if (!state._closedWindows[i]._shouldRestore) {
+ // We only need to go until _shouldRestore
+ // is falsy since we're going in reverse.
+ break;
+ }
+
+ delete state._closedWindows[i]._shouldRestore;
+ state.windows.unshift(state._closedWindows.pop());
+ }
+ }
+
+ // Clear all cookies on clean shutdown according to user preferences
+ if (RunState.isClosing) {
+ let expireCookies = Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
+ Services.cookies.QueryInterface(Ci.nsICookieService).ACCEPT_SESSION;
+ let sanitizeCookies = Services.prefs.getBoolPref("privacy.sanitize.sanitizeOnShutdown") &&
+ Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies");
+ let restart = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
+ // Don't clear cookies when restarting
+ if ((expireCookies || sanitizeCookies) && !restart) {
+ for (let window of state.windows) {
+ delete window.cookies;
+ }
+ }
+ }
+
+ stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
+ return this._writeState(state);
+ },
+
+ /**
+ * Saves the current session state. Collects data asynchronously and calls
+ * _saveState() to collect data again (with a cache hit rate of hopefully
+ * 100%) and write to disk afterwards.
+ */
+ _saveStateAsync: function () {
+ // Allow scheduling delayed saves again.
+ this._timeoutID = null;
+
+ // Write to disk.
+ this._saveState();
+ },
+
+ /**
+ * Write the given state object to disk.
+ */
+ _writeState: function (state) {
+ // We update the time stamp before writing so that we don't write again
+ // too soon, if saving is requested before the write completes. Without
+ // this update we may save repeatedly if actions cause a runDelayed
+ // before writing has completed. See Bug 902280
+ this.updateLastSaveTime();
+
+ // Write (atomically) to a session file, using a tmp file. Once the session
+ // file is successfully updated, save the time stamp of the last save and
+ // notify the observers.
+ return SessionFile.write(state).then(() => {
+ this.updateLastSaveTime();
+ notify(null, "sessionstore-state-write-complete");
+ }, console.error);
+ },
+};
diff --git a/browser/components/sessionstore/SessionStorage.jsm b/browser/components/sessionstore/SessionStorage.jsm
new file mode 100644
index 000000000..705139ebf
--- /dev/null
+++ b/browser/components/sessionstore/SessionStorage.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["SessionStorage"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+
+// Returns the principal for a given |frame| contained in a given |docShell|.
+function getPrincipalForFrame(docShell, frame) {
+ let ssm = Services.scriptSecurityManager;
+ let uri = frame.document.documentURIObject;
+ return ssm.getDocShellCodebasePrincipal(uri, docShell);
+}
+
+this.SessionStorage = Object.freeze({
+ /**
+ * Updates all sessionStorage "super cookies"
+ * @param docShell
+ * That tab's docshell (containing the sessionStorage)
+ * @param frameTree
+ * The docShell's FrameTree instance.
+ * @return Returns a nested object that will have hosts as keys and per-host
+ * session storage data as strings. For example:
+ * {"example.com": {"key": "value", "my_number": "123"}}
+ */
+ collect: function (docShell, frameTree) {
+ return SessionStorageInternal.collect(docShell, frameTree);
+ },
+
+ /**
+ * Restores all sessionStorage "super cookies".
+ * @param aDocShell
+ * A tab's docshell (containing the sessionStorage)
+ * @param aStorageData
+ * A nested object with storage data to be restored that has hosts as
+ * keys and per-host session storage data as strings. For example:
+ * {"example.com": {"key": "value", "my_number": "123"}}
+ */
+ restore: function (aDocShell, aStorageData) {
+ SessionStorageInternal.restore(aDocShell, aStorageData);
+ },
+});
+
+var SessionStorageInternal = {
+ /**
+ * Reads all session storage data from the given docShell.
+ * @param docShell
+ * A tab's docshell (containing the sessionStorage)
+ * @param frameTree
+ * The docShell's FrameTree instance.
+ * @return Returns a nested object that will have hosts as keys and per-host
+ * session storage data as strings. For example:
+ * {"example.com": {"key": "value", "my_number": "123"}}
+ */
+ collect: function (docShell, frameTree) {
+ let data = {};
+ let visitedOrigins = new Set();
+
+ frameTree.forEach(frame => {
+ let principal = getPrincipalForFrame(docShell, frame);
+ if (!principal) {
+ return;
+ }
+
+ // Get the origin of the current history entry
+ // and use that as a key for the per-principal storage data.
+ let origin = principal.origin;
+ if (visitedOrigins.has(origin)) {
+ // Don't read a host twice.
+ return;
+ }
+
+ // Mark the current origin as visited.
+ visitedOrigins.add(origin);
+
+ let originData = this._readEntry(principal, docShell);
+ if (Object.keys(originData).length) {
+ data[origin] = originData;
+ }
+ });
+
+ return Object.keys(data).length ? data : null;
+ },
+
+ /**
+ * Writes session storage data to the given tab.
+ * @param aDocShell
+ * A tab's docshell (containing the sessionStorage)
+ * @param aStorageData
+ * A nested object with storage data to be restored that has hosts as
+ * keys and per-host session storage data as strings. For example:
+ * {"example.com": {"key": "value", "my_number": "123"}}
+ */
+ restore: function (aDocShell, aStorageData) {
+ for (let origin of Object.keys(aStorageData)) {
+ let data = aStorageData[origin];
+
+ let principal;
+
+ try {
+ let attrs = aDocShell.getOriginAttributes();
+ let originURI = Services.io.newURI(origin, null, null);
+ principal = Services.scriptSecurityManager.createCodebasePrincipal(originURI, attrs);
+ } catch (e) {
+ console.error(e);
+ continue;
+ }
+
+ let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
+ let window = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+
+ // There is no need to pass documentURI, it's only used to fill documentURI property of
+ // domstorage event, which in this case has no consumer. Prevention of events in case
+ // of missing documentURI will be solved in a followup bug to bug 600307.
+ let storage = storageManager.createStorage(window, principal, "", aDocShell.usePrivateBrowsing);
+
+ for (let key of Object.keys(data)) {
+ try {
+ storage.setItem(key, data[key]);
+ } catch (e) {
+ // throws e.g. for URIs that can't have sessionStorage
+ console.error(e);
+ }
+ }
+ }
+ },
+
+ /**
+ * Reads an entry in the session storage data contained in a tab's history.
+ * @param aURI
+ * That history entry uri
+ * @param aDocShell
+ * A tab's docshell (containing the sessionStorage)
+ */
+ _readEntry: function (aPrincipal, aDocShell) {
+ let hostData = {};
+ let storage;
+
+ let window = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+
+ try {
+ let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
+ storage = storageManager.getStorage(window, aPrincipal);
+ storage.length; // XXX: Bug 1232955 - storage.length can throw, catch that failure
+ } catch (e) {
+ // sessionStorage might throw if it's turned off, see bug 458954
+ storage = null;
+ }
+
+ if (storage && storage.length) {
+ for (let i = 0; i < storage.length; i++) {
+ try {
+ let key = storage.key(i);
+ hostData[key] = storage.getItem(key);
+ } catch (e) {
+ // This currently throws for secured items (cf. bug 442048).
+ }
+ }
+ }
+
+ return hostData;
+ }
+};
diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm
new file mode 100644
index 000000000..2f44b2af3
--- /dev/null
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -0,0 +1,4719 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["SessionStore"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+// Current version of the format used by Session Restore.
+const FORMAT_VERSION = 1;
+
+const TAB_STATE_NEEDS_RESTORE = 1;
+const TAB_STATE_RESTORING = 2;
+const TAB_STATE_WILL_RESTORE = 3;
+
+// A new window has just been restored. At this stage, tabs are generally
+// not restored.
+const NOTIFY_SINGLE_WINDOW_RESTORED = "sessionstore-single-window-restored";
+const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
+const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
+const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
+const NOTIFY_RESTORING_ON_STARTUP = "sessionstore-restoring-on-startup";
+const NOTIFY_INITIATING_MANUAL_RESTORE = "sessionstore-initiating-manual-restore";
+
+const NOTIFY_TAB_RESTORED = "sessionstore-debug-tab-restored"; // WARNING: debug-only
+
+// Maximum number of tabs to restore simultaneously. Previously controlled by
+// the browser.sessionstore.max_concurrent_tabs pref.
+const MAX_CONCURRENT_TAB_RESTORES = 3;
+
+// Amount (in CSS px) by which we allow window edges to be off-screen
+// when restoring a window, before we override the saved position to
+// pull the window back within the available screen area.
+const SCREEN_EDGE_SLOP = 8;
+
+// global notifications observed
+const OBSERVING = [
+ "browser-window-before-show", "domwindowclosed",
+ "quit-application-granted", "browser-lastwindow-close-granted",
+ "quit-application", "browser:purge-session-history",
+ "browser:purge-domain-data",
+ "idle-daily",
+];
+
+// XUL Window properties to (re)store
+// Restored in restoreDimensions()
+const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
+
+// Hideable window features to (re)store
+// Restored in restoreWindowFeatures()
+const WINDOW_HIDEABLE_FEATURES = [
+ "menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars"
+];
+
+// Messages that will be received via the Frame Message Manager.
+const MESSAGES = [
+ // The content script sends us data that has been invalidated and needs to
+ // be saved to disk.
+ "SessionStore:update",
+
+ // The restoreHistory code has run. This is a good time to run SSTabRestoring.
+ "SessionStore:restoreHistoryComplete",
+
+ // The load for the restoring tab has begun. We update the URL bar at this
+ // time; if we did it before, the load would overwrite it.
+ "SessionStore:restoreTabContentStarted",
+
+ // All network loads for a restoring tab are done, so we should
+ // consider restoring another tab in the queue. The document has
+ // been restored, and forms have been filled. We trigger
+ // SSTabRestored at this time.
+ "SessionStore:restoreTabContentComplete",
+
+ // A crashed tab was revived by navigating to a different page. Remove its
+ // browser from the list of crashed browsers to stop ignoring its messages.
+ "SessionStore:crashedTabRevived",
+
+ // The content script encountered an error.
+ "SessionStore:error",
+];
+
+// The list of messages we accept from <xul:browser>s that have no tab
+// assigned, or whose windows have gone away. Those are for example the
+// ones that preload about:newtab pages, or from browsers where the window
+// has just been closed.
+const NOTAB_MESSAGES = new Set([
+ // For a description see above.
+ "SessionStore:crashedTabRevived",
+
+ // For a description see above.
+ "SessionStore:update",
+
+ // For a description see above.
+ "SessionStore:error",
+]);
+
+// The list of messages we accept without an "epoch" parameter.
+// See getCurrentEpoch() and friends to find out what an "epoch" is.
+const NOEPOCH_MESSAGES = new Set([
+ // For a description see above.
+ "SessionStore:crashedTabRevived",
+
+ // For a description see above.
+ "SessionStore:error",
+]);
+
+// The list of messages we want to receive even during the short period after a
+// frame has been removed from the DOM and before its frame script has finished
+// unloading.
+const CLOSED_MESSAGES = new Set([
+ // For a description see above.
+ "SessionStore:crashedTabRevived",
+
+ // For a description see above.
+ "SessionStore:update",
+
+ // For a description see above.
+ "SessionStore:error",
+]);
+
+// These are tab events that we listen to.
+const TAB_EVENTS = [
+ "TabOpen", "TabBrowserInserted", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
+ "TabUnpinned"
+];
+
+const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.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/TelemetryStopwatch.jsm", this);
+Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/debug.js", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
+ "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
+XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
+ "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
+XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
+ "@mozilla.org/base/telemetry;1", "nsITelemetry");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
+ "resource:///modules/sessionstore/GlobalState.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
+ "resource:///modules/sessionstore/PrivacyFilter.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RunState",
+ "resource:///modules/sessionstore/RunState.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
+ "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
+ "resource:///modules/sessionstore/SessionSaver.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
+ "resource:///modules/sessionstore/SessionCookies.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
+ "resource:///modules/sessionstore/SessionFile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
+ "resource:///modules/sessionstore/TabAttributes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler",
+ "resource:///modules/ContentCrashHandlers.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabState",
+ "resource:///modules/sessionstore/TabState.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
+ "resource:///modules/sessionstore/TabStateCache.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher",
+ "resource:///modules/sessionstore/TabStateFlusher.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Utils",
+ "resource://gre/modules/sessionstore/Utils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
+ "resource://gre/modules/ViewSourceBrowser.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+
+/**
+ * |true| if we are in debug mode, |false| otherwise.
+ * Debug mode is controlled by preference browser.sessionstore.debug
+ */
+var gDebuggingEnabled = false;
+function debug(aMsg) {
+ if (gDebuggingEnabled) {
+ aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
+ Services.console.logStringMessage(aMsg);
+ }
+}
+
+this.SessionStore = {
+ get promiseInitialized() {
+ return SessionStoreInternal.promiseInitialized;
+ },
+
+ get canRestoreLastSession() {
+ return SessionStoreInternal.canRestoreLastSession;
+ },
+
+ set canRestoreLastSession(val) {
+ SessionStoreInternal.canRestoreLastSession = val;
+ },
+
+ get lastClosedObjectType() {
+ return SessionStoreInternal.lastClosedObjectType;
+ },
+
+ init: function ss_init() {
+ SessionStoreInternal.init();
+ },
+
+ getBrowserState: function ss_getBrowserState() {
+ return SessionStoreInternal.getBrowserState();
+ },
+
+ setBrowserState: function ss_setBrowserState(aState) {
+ SessionStoreInternal.setBrowserState(aState);
+ },
+
+ getWindowState: function ss_getWindowState(aWindow) {
+ return SessionStoreInternal.getWindowState(aWindow);
+ },
+
+ setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
+ SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
+ },
+
+ getTabState: function ss_getTabState(aTab) {
+ return SessionStoreInternal.getTabState(aTab);
+ },
+
+ setTabState: function ss_setTabState(aTab, aState) {
+ SessionStoreInternal.setTabState(aTab, aState);
+ },
+
+ duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta = 0) {
+ return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
+ },
+
+ getClosedTabCount: function ss_getClosedTabCount(aWindow) {
+ return SessionStoreInternal.getClosedTabCount(aWindow);
+ },
+
+ getClosedTabData: function ss_getClosedTabData(aWindow, aAsString = true) {
+ return SessionStoreInternal.getClosedTabData(aWindow, aAsString);
+ },
+
+ undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
+ return SessionStoreInternal.undoCloseTab(aWindow, aIndex);
+ },
+
+ forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
+ return SessionStoreInternal.forgetClosedTab(aWindow, aIndex);
+ },
+
+ getClosedWindowCount: function ss_getClosedWindowCount() {
+ return SessionStoreInternal.getClosedWindowCount();
+ },
+
+ getClosedWindowData: function ss_getClosedWindowData(aAsString = true) {
+ return SessionStoreInternal.getClosedWindowData(aAsString);
+ },
+
+ undoCloseWindow: function ss_undoCloseWindow(aIndex) {
+ return SessionStoreInternal.undoCloseWindow(aIndex);
+ },
+
+ forgetClosedWindow: function ss_forgetClosedWindow(aIndex) {
+ return SessionStoreInternal.forgetClosedWindow(aIndex);
+ },
+
+ getWindowValue: function ss_getWindowValue(aWindow, aKey) {
+ return SessionStoreInternal.getWindowValue(aWindow, aKey);
+ },
+
+ setWindowValue: function ss_setWindowValue(aWindow, aKey, aStringValue) {
+ SessionStoreInternal.setWindowValue(aWindow, aKey, aStringValue);
+ },
+
+ deleteWindowValue: function ss_deleteWindowValue(aWindow, aKey) {
+ SessionStoreInternal.deleteWindowValue(aWindow, aKey);
+ },
+
+ getTabValue: function ss_getTabValue(aTab, aKey) {
+ return SessionStoreInternal.getTabValue(aTab, aKey);
+ },
+
+ setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
+ SessionStoreInternal.setTabValue(aTab, aKey, aStringValue);
+ },
+
+ deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
+ SessionStoreInternal.deleteTabValue(aTab, aKey);
+ },
+
+ getGlobalValue: function ss_getGlobalValue(aKey) {
+ return SessionStoreInternal.getGlobalValue(aKey);
+ },
+
+ setGlobalValue: function ss_setGlobalValue(aKey, aStringValue) {
+ SessionStoreInternal.setGlobalValue(aKey, aStringValue);
+ },
+
+ deleteGlobalValue: function ss_deleteGlobalValue(aKey) {
+ SessionStoreInternal.deleteGlobalValue(aKey);
+ },
+
+ persistTabAttribute: function ss_persistTabAttribute(aName) {
+ SessionStoreInternal.persistTabAttribute(aName);
+ },
+
+ restoreLastSession: function ss_restoreLastSession() {
+ SessionStoreInternal.restoreLastSession();
+ },
+
+ getCurrentState: function (aUpdateAll) {
+ return SessionStoreInternal.getCurrentState(aUpdateAll);
+ },
+
+ reviveCrashedTab(aTab) {
+ return SessionStoreInternal.reviveCrashedTab(aTab);
+ },
+
+ reviveAllCrashedTabs() {
+ return SessionStoreInternal.reviveAllCrashedTabs();
+ },
+
+ navigateAndRestore(tab, loadArguments, historyIndex) {
+ return SessionStoreInternal.navigateAndRestore(tab, loadArguments, historyIndex);
+ },
+
+ getSessionHistory(tab, updatedCallback) {
+ return SessionStoreInternal.getSessionHistory(tab, updatedCallback);
+ },
+
+ undoCloseById(aClosedId) {
+ return SessionStoreInternal.undoCloseById(aClosedId);
+ },
+
+ /**
+ * Determines whether the passed version number is compatible with
+ * the current version number of the SessionStore.
+ *
+ * @param version The format and version of the file, as an array, e.g.
+ * ["sessionrestore", 1]
+ */
+ isFormatVersionCompatible(version) {
+ if (!version) {
+ return false;
+ }
+ if (!Array.isArray(version)) {
+ // Improper format.
+ return false;
+ }
+ if (version[0] != "sessionrestore") {
+ // Not a Session Restore file.
+ return false;
+ }
+ let number = Number.parseFloat(version[1]);
+ if (Number.isNaN(number)) {
+ return false;
+ }
+ return number <= FORMAT_VERSION;
+ },
+};
+
+// Freeze the SessionStore object. We don't want anyone to modify it.
+Object.freeze(SessionStore);
+
+var SessionStoreInternal = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIDOMEventListener,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ]),
+
+ _globalState: new GlobalState(),
+
+ // A counter to be used to generate a unique ID for each closed tab or window.
+ _nextClosedId: 0,
+
+ // During the initial restore and setBrowserState calls tracks the number of
+ // windows yet to be restored
+ _restoreCount: -1,
+
+ // For each <browser> element, records the current epoch.
+ _browserEpochs: new WeakMap(),
+
+ // Any browsers that fires the oop-browser-crashed event gets stored in
+ // here - that way we know which browsers to ignore messages from (until
+ // they get restored).
+ _crashedBrowsers: new WeakSet(),
+
+ // A map (xul:browser -> nsIFrameLoader) that maps a browser to the last
+ // associated frameLoader we heard about.
+ _lastKnownFrameLoader: new WeakMap(),
+
+ // A map (xul:browser -> object) that maps a browser associated with a
+ // recently closed tab to all its necessary state information we need to
+ // properly handle final update message.
+ _closedTabs: new WeakMap(),
+
+ // A map (xul:browser -> object) that maps a browser associated with a
+ // recently closed tab due to a window closure to the tab state information
+ // that is being stored in _closedWindows for that tab.
+ _closedWindowTabs: new WeakMap(),
+
+ // A set of window data that has the potential to be saved in the _closedWindows
+ // array for the session. We will remove window data from this set whenever
+ // forgetClosedWindow is called for the window, or when session history is
+ // purged, so that we don't accidentally save that data after the flush has
+ // completed. Closed tabs use a more complicated mechanism for this particular
+ // problem. When forgetClosedTab is called, the browser is removed from the
+ // _closedTabs map, so its data is not recorded. In the purge history case,
+ // the closedTabs array per window is overwritten so that once the flush is
+ // complete, the tab would only ever add itself to an array that SessionStore
+ // no longer cares about. Bug 1230636 has been filed to make the tab case
+ // work more like the window case, which is more explicit, and easier to
+ // reason about.
+ _saveableClosedWindowData: new WeakSet(),
+
+ // A map (xul:browser -> object) that maps a browser that is switching
+ // remoteness via navigateAndRestore, to the loadArguments that were
+ // most recently passed when calling navigateAndRestore.
+ _remotenessChangingBrowsers: new WeakMap(),
+
+ // whether a setBrowserState call is in progress
+ _browserSetState: false,
+
+ // time in milliseconds when the session was started (saved across sessions),
+ // defaults to now if no session was restored or timestamp doesn't exist
+ _sessionStartTime: Date.now(),
+
+ // states for all currently opened windows
+ _windows: {},
+
+ // counter for creating unique window IDs
+ _nextWindowID: 0,
+
+ // states for all recently closed windows
+ _closedWindows: [],
+
+ // collection of session states yet to be restored
+ _statesToRestore: {},
+
+ // counts the number of crashes since the last clean start
+ _recentCrashes: 0,
+
+ // whether the last window was closed and should be restored
+ _restoreLastWindow: false,
+
+ // number of tabs currently restoring
+ _tabsRestoringCount: 0,
+
+ // When starting Firefox with a single private window, this is the place
+ // where we keep the session we actually wanted to restore in case the user
+ // decides to later open a non-private window as well.
+ _deferredInitialState: null,
+
+ // A promise resolved once initialization is complete
+ _deferredInitialized: (function () {
+ let deferred = {};
+
+ deferred.promise = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+
+ return deferred;
+ })(),
+
+ // Whether session has been initialized
+ _sessionInitialized: false,
+
+ // Promise that is resolved when we're ready to initialize
+ // and restore the session.
+ _promiseReadyForInitialization: null,
+
+ // Keep busy state counters per window.
+ _windowBusyStates: new WeakMap(),
+
+ /**
+ * A promise fulfilled once initialization is complete.
+ */
+ get promiseInitialized() {
+ return this._deferredInitialized.promise;
+ },
+
+ get canRestoreLastSession() {
+ return LastSession.canRestore;
+ },
+
+ set canRestoreLastSession(val) {
+ // Cheat a bit; only allow false.
+ if (!val) {
+ LastSession.clear();
+ }
+ },
+
+ /**
+ * Returns a string describing the last closed object, either "tab" or "window".
+ *
+ * This was added to support the sessions.restore WebExtensions API.
+ */
+ get lastClosedObjectType() {
+ if (this._closedWindows.length) {
+ // Since there are closed windows, we need to check if there's a closed tab
+ // in one of the currently open windows that was closed after the
+ // last-closed window.
+ let tabTimestamps = [];
+ let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ while (windowsEnum.hasMoreElements()) {
+ let window = windowsEnum.getNext();
+ let windowState = this._windows[window.__SSi];
+ if (windowState && windowState._closedTabs[0]) {
+ tabTimestamps.push(windowState._closedTabs[0].closedAt);
+ }
+ }
+ if (!tabTimestamps.length ||
+ (tabTimestamps.sort((a, b) => b - a)[0] < this._closedWindows[0].closedAt)) {
+ return "window";
+ }
+ }
+ return "tab";
+ },
+
+ /**
+ * Initialize the sessionstore service.
+ */
+ init: function () {
+ if (this._initialized) {
+ throw new Error("SessionStore.init() must only be called once!");
+ }
+
+ TelemetryTimestamps.add("sessionRestoreInitialized");
+ OBSERVING.forEach(function(aTopic) {
+ Services.obs.addObserver(this, aTopic, true);
+ }, this);
+
+ this._initPrefs();
+ this._initialized = true;
+ },
+
+ /**
+ * Initialize the session using the state provided by SessionStartup
+ */
+ initSession: function () {
+ TelemetryStopwatch.start("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
+ let state;
+ let ss = gSessionStartup;
+
+ if (ss.doRestore() ||
+ ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
+ state = ss.state;
+ }
+
+ if (state) {
+ try {
+ // If we're doing a DEFERRED session, then we want to pull pinned tabs
+ // out so they can be restored.
+ if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
+ let [iniState, remainingState] = this._prepDataForDeferredRestore(state);
+ // If we have a iniState with windows, that means that we have windows
+ // with app tabs to restore.
+ if (iniState.windows.length)
+ state = iniState;
+ else
+ state = null;
+
+ if (remainingState.windows.length) {
+ LastSession.setState(remainingState);
+ }
+ }
+ else {
+ // Get the last deferred session in case the user still wants to
+ // restore it
+ LastSession.setState(state.lastSessionState);
+
+ if (ss.previousSessionCrashed) {
+ this._recentCrashes = (state.session &&
+ state.session.recentCrashes || 0) + 1;
+
+ if (this._needsRestorePage(state, this._recentCrashes)) {
+ // replace the crashed session with a restore-page-only session
+ let url = "about:sessionrestore";
+ let formdata = {id: {sessionData: state}, url};
+ state = { windows: [{ tabs: [{ entries: [{url}], formdata }] }] };
+ } else if (this._hasSingleTabWithURL(state.windows,
+ "about:welcomeback")) {
+ // On a single about:welcomeback URL that crashed, replace about:welcomeback
+ // with about:sessionrestore, to make clear to the user that we crashed.
+ state.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
+ }
+ }
+
+ // Update the session start time using the restored session state.
+ this._updateSessionStartTime(state);
+
+ // make sure that at least the first window doesn't have anything hidden
+ delete state.windows[0].hidden;
+ // Since nothing is hidden in the first window, it cannot be a popup
+ delete state.windows[0].isPopup;
+ // We don't want to minimize and then open a window at startup.
+ if (state.windows[0].sizemode == "minimized")
+ state.windows[0].sizemode = "normal";
+ // clear any lastSessionWindowID attributes since those don't matter
+ // during normal restore
+ state.windows.forEach(function(aWindow) {
+ delete aWindow.__lastSessionWindowID;
+ });
+ }
+ }
+ catch (ex) { debug("The session file is invalid: " + ex); }
+ }
+
+ // at this point, we've as good as resumed the session, so we can
+ // clear the resume_session_once flag, if it's set
+ if (!RunState.isQuitting &&
+ this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
+
+ TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_INIT_SESSION_MS");
+ return state;
+ },
+
+ _initPrefs : function() {
+ this._prefBranch = Services.prefs.getBranch("browser.");
+
+ gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
+
+ Services.prefs.addObserver("browser.sessionstore.debug", () => {
+ gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
+ }, false);
+
+ this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
+ this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
+
+ this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
+ this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
+ },
+
+ /**
+ * Called on application shutdown, after notifications:
+ * quit-application-granted, quit-application
+ */
+ _uninit: function ssi_uninit() {
+ if (!this._initialized) {
+ throw new Error("SessionStore is not initialized.");
+ }
+
+ // Prepare to close the session file and write the last state.
+ RunState.setClosing();
+
+ // save all data for session resuming
+ if (this._sessionInitialized) {
+ SessionSaver.run();
+ }
+
+ // clear out priority queue in case it's still holding refs
+ TabRestoreQueue.reset();
+
+ // Make sure to cancel pending saves.
+ SessionSaver.cancel();
+ },
+
+ /**
+ * Handle notifications
+ */
+ observe: function ssi_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "browser-window-before-show": // catch new windows
+ this.onBeforeBrowserWindowShown(aSubject);
+ break;
+ case "domwindowclosed": // catch closed windows
+ this.onClose(aSubject);
+ break;
+ case "quit-application-granted":
+ let syncShutdown = aData == "syncShutdown";
+ this.onQuitApplicationGranted(syncShutdown);
+ break;
+ case "browser-lastwindow-close-granted":
+ this.onLastWindowCloseGranted();
+ break;
+ case "quit-application":
+ this.onQuitApplication(aData);
+ break;
+ case "browser:purge-session-history": // catch sanitization
+ this.onPurgeSessionHistory();
+ break;
+ case "browser:purge-domain-data":
+ this.onPurgeDomainData(aData);
+ break;
+ case "nsPref:changed": // catch pref changes
+ this.onPrefChange(aData);
+ break;
+ case "idle-daily":
+ this.onIdleDaily();
+ break;
+ }
+ },
+
+ /**
+ * This method handles incoming messages sent by the session store content
+ * script via the Frame Message Manager or Parent Process Message Manager,
+ * and thus enables communication with OOP tabs.
+ */
+ receiveMessage(aMessage) {
+ // If we got here, that means we're dealing with a frame message
+ // manager message, so the target will be a <xul:browser>.
+ var browser = aMessage.target;
+ let win = browser.ownerGlobal;
+ let tab = win ? win.gBrowser.getTabForBrowser(browser) : null;
+
+ // Ensure we receive only specific messages from <xul:browser>s that
+ // have no tab or window assigned, e.g. the ones that preload
+ // about:newtab pages, or windows that have closed.
+ if (!tab && !NOTAB_MESSAGES.has(aMessage.name)) {
+ throw new Error(`received unexpected message '${aMessage.name}' ` +
+ `from a browser that has no tab or window`);
+ }
+
+ let data = aMessage.data || {};
+ let hasEpoch = data.hasOwnProperty("epoch");
+
+ // Most messages sent by frame scripts require to pass an epoch.
+ if (!hasEpoch && !NOEPOCH_MESSAGES.has(aMessage.name)) {
+ throw new Error(`received message '${aMessage.name}' without an epoch`);
+ }
+
+ // Ignore messages from previous epochs.
+ if (hasEpoch && !this.isCurrentEpoch(browser, data.epoch)) {
+ return;
+ }
+
+ switch (aMessage.name) {
+ case "SessionStore:update":
+ // |browser.frameLoader| might be empty if the browser was already
+ // destroyed and its tab removed. In that case we still have the last
+ // frameLoader we know about to compare.
+ let frameLoader = browser.frameLoader ||
+ this._lastKnownFrameLoader.get(browser.permanentKey);
+
+ // If the message isn't targeting the latest frameLoader discard it.
+ if (frameLoader != aMessage.targetFrameLoader) {
+ return;
+ }
+
+ if (aMessage.data.isFinal) {
+ // If this the final message we need to resolve all pending flush
+ // requests for the given browser as they might have been sent too
+ // late and will never respond. If they have been sent shortly after
+ // switching a browser's remoteness there isn't too much data to skip.
+ TabStateFlusher.resolveAll(browser);
+ } else if (aMessage.data.flushID) {
+ // This is an update kicked off by an async flush request. Notify the
+ // TabStateFlusher so that it can finish the request and notify its
+ // consumer that's waiting for the flush to be done.
+ TabStateFlusher.resolve(browser, aMessage.data.flushID);
+ }
+
+ // Ignore messages from <browser> elements that have crashed
+ // and not yet been revived.
+ if (this._crashedBrowsers.has(browser.permanentKey)) {
+ return;
+ }
+
+ // Record telemetry measurements done in the child and update the tab's
+ // cached state. Mark the window as dirty and trigger a delayed write.
+ this.recordTelemetry(aMessage.data.telemetry);
+ TabState.update(browser, aMessage.data);
+ this.saveStateDelayed(win);
+
+ // Handle any updates sent by the child after the tab was closed. This
+ // might be the final update as sent by the "unload" handler but also
+ // any async update message that was sent before the child unloaded.
+ if (this._closedTabs.has(browser.permanentKey)) {
+ let {closedTabs, tabData} = this._closedTabs.get(browser.permanentKey);
+
+ // Update the closed tab's state. This will be reflected in its
+ // window's list of closed tabs as that refers to the same object.
+ TabState.copyFromCache(browser, tabData.state);
+
+ // Is this the tab's final message?
+ if (aMessage.data.isFinal) {
+ // We expect no further updates.
+ this._closedTabs.delete(browser.permanentKey);
+ // The tab state no longer needs this reference.
+ delete tabData.permanentKey;
+
+ // Determine whether the tab state is worth saving.
+ let shouldSave = this._shouldSaveTabState(tabData.state);
+ let index = closedTabs.indexOf(tabData);
+
+ if (shouldSave && index == -1) {
+ // If the tab state is worth saving and we didn't push it onto
+ // the list of closed tabs when it was closed (because we deemed
+ // the state not worth saving) then add it to the window's list
+ // of closed tabs now.
+ this.saveClosedTabData(closedTabs, tabData);
+ } else if (!shouldSave && index > -1) {
+ // Remove from the list of closed tabs. The update messages sent
+ // after the tab was closed changed enough state so that we no
+ // longer consider its data interesting enough to keep around.
+ this.removeClosedTabData(closedTabs, index);
+ }
+ }
+ }
+ break;
+ case "SessionStore:restoreHistoryComplete":
+ // Notify the tabbrowser that the tab chrome has been restored.
+ let tabData = TabState.collect(tab);
+
+ // wall-paper fix for bug 439675: make sure that the URL to be loaded
+ // is always visible in the address bar if no other value is present
+ let activePageData = tabData.entries[tabData.index - 1] || null;
+ let uri = activePageData ? activePageData.url || null : null;
+ // NB: we won't set initial URIs (about:home, about:newtab, etc.) here
+ // because their load will not normally trigger a location bar clearing
+ // when they finish loading (to avoid race conditions where we then
+ // clear user input instead), so we shouldn't set them here either.
+ // They also don't fall under the issues in bug 439675 where user input
+ // needs to be preserved if the load doesn't succeed.
+ // We also don't do this for remoteness updates, where it should not
+ // be necessary.
+ if (!browser.userTypedValue && uri && !data.isRemotenessUpdate &&
+ !win.gInitialPages.includes(uri)) {
+ browser.userTypedValue = uri;
+ }
+
+ // If the page has a title, set it.
+ if (activePageData) {
+ if (activePageData.title) {
+ tab.label = activePageData.title;
+ tab.crop = "end";
+ } else if (activePageData.url != "about:blank") {
+ tab.label = activePageData.url;
+ tab.crop = "center";
+ }
+ } else if (tab.hasAttribute("customizemode")) {
+ win.gCustomizeMode.setTab(tab);
+ }
+
+ // Restore the tab icon.
+ if ("image" in tabData) {
+ // Use the serialized contentPrincipal with the new icon load.
+ let loadingPrincipal = Utils.deserializePrincipal(tabData.iconLoadingPrincipal);
+ win.gBrowser.setIcon(tab, tabData.image, loadingPrincipal);
+ TabStateCache.update(browser, { image: null, iconLoadingPrincipal: null });
+ }
+
+ let event = win.document.createEvent("Events");
+ event.initEvent("SSTabRestoring", true, false);
+ tab.dispatchEvent(event);
+ break;
+ case "SessionStore:restoreTabContentStarted":
+ if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+ // If a load not initiated by sessionstore was started in a
+ // previously pending tab. Mark the tab as no longer pending.
+ this.markTabAsRestoring(tab);
+ } else if (!data.isRemotenessUpdate) {
+ // If the user was typing into the URL bar when we crashed, but hadn't hit
+ // enter yet, then we just need to write that value to the URL bar without
+ // loading anything. This must happen after the load, as the load will clear
+ // userTypedValue.
+ let tabData = TabState.collect(tab);
+ if (tabData.userTypedValue && !tabData.userTypedClear && !browser.userTypedValue) {
+ browser.userTypedValue = tabData.userTypedValue;
+ win.URLBarSetURI();
+ }
+
+ // Remove state we don't need any longer.
+ TabStateCache.update(browser, {
+ userTypedValue: null, userTypedClear: null
+ });
+ }
+ break;
+ case "SessionStore:restoreTabContentComplete":
+ // This callback is used exclusively by tests that want to
+ // monitor the progress of network loads.
+ if (gDebuggingEnabled) {
+ Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
+ }
+
+ SessionStoreInternal._resetLocalTabRestoringState(tab);
+ SessionStoreInternal.restoreNextTab();
+
+ this._sendTabRestoredNotification(tab, data.isRemotenessUpdate);
+ break;
+ case "SessionStore:crashedTabRevived":
+ // The browser was revived by navigating to a different page
+ // manually, so we remove it from the ignored browser set.
+ this._crashedBrowsers.delete(browser.permanentKey);
+ break;
+ case "SessionStore:error":
+ this.reportInternalError(data);
+ TabStateFlusher.resolveAll(browser, false, "Received error from the content process");
+ break;
+ default:
+ throw new Error(`received unknown message '${aMessage.name}'`);
+ break;
+ }
+ },
+
+ /**
+ * Record telemetry measurements stored in an object.
+ * @param telemetry
+ * {histogramID: value, ...} An object mapping histogramIDs to the
+ * value to be recorded for that ID,
+ */
+ recordTelemetry: function (telemetry) {
+ for (let histogramId in telemetry){
+ Telemetry.getHistogramById(histogramId).add(telemetry[histogramId]);
+ }
+ },
+
+ /* ........ Window Event Handlers .............. */
+
+ /**
+ * Implement nsIDOMEventListener for handling various window and tab events
+ */
+ handleEvent: function ssi_handleEvent(aEvent) {
+ let win = aEvent.currentTarget.ownerGlobal;
+ let target = aEvent.originalTarget;
+ switch (aEvent.type) {
+ case "TabOpen":
+ this.onTabAdd(win);
+ break;
+ case "TabBrowserInserted":
+ this.onTabBrowserInserted(win, target);
+ break;
+ case "TabClose":
+ // `adoptedBy` will be set if the tab was closed because it is being
+ // moved to a new window.
+ if (!aEvent.detail.adoptedBy)
+ this.onTabClose(win, target);
+ this.onTabRemove(win, target);
+ break;
+ case "TabSelect":
+ this.onTabSelect(win);
+ break;
+ case "TabShow":
+ this.onTabShow(win, target);
+ break;
+ case "TabHide":
+ this.onTabHide(win, target);
+ break;
+ case "TabPinned":
+ case "TabUnpinned":
+ case "SwapDocShells":
+ this.saveStateDelayed(win);
+ break;
+ case "oop-browser-crashed":
+ this.onBrowserCrashed(target);
+ break;
+ case "XULFrameLoaderCreated":
+ if (target.namespaceURI == NS_XUL &&
+ target.localName == "browser" &&
+ target.frameLoader &&
+ target.permanentKey) {
+ this._lastKnownFrameLoader.set(target.permanentKey, target.frameLoader);
+ this.resetEpoch(target);
+ }
+ break;
+ default:
+ throw new Error(`unhandled event ${aEvent.type}?`);
+ }
+ this._clearRestoringWindows();
+ },
+
+ /**
+ * Generate a unique window identifier
+ * @return string
+ * A unique string to identify a window
+ */
+ _generateWindowID: function ssi_generateWindowID() {
+ return "window" + (this._nextWindowID++);
+ },
+
+ /**
+ * Registers and tracks a given window.
+ *
+ * @param aWindow
+ * Window reference
+ */
+ onLoad(aWindow) {
+ // return if window has already been initialized
+ if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
+ return;
+
+ // ignore windows opened while shutting down
+ if (RunState.isQuitting)
+ return;
+
+ // Assign the window a unique identifier we can use to reference
+ // internal data about the window.
+ aWindow.__SSi = this._generateWindowID();
+
+ let mm = aWindow.getGroupMessageManager("browsers");
+ MESSAGES.forEach(msg => {
+ let listenWhenClosed = CLOSED_MESSAGES.has(msg);
+ mm.addMessageListener(msg, this, listenWhenClosed);
+ });
+
+ // Load the frame script after registering listeners.
+ mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
+
+ // and create its data object
+ this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false };
+
+ if (PrivateBrowsingUtils.isWindowPrivate(aWindow))
+ this._windows[aWindow.__SSi].isPrivate = true;
+ if (!this._isWindowLoaded(aWindow))
+ this._windows[aWindow.__SSi]._restoring = true;
+ if (!aWindow.toolbar.visible)
+ this._windows[aWindow.__SSi].isPopup = true;
+
+ let tabbrowser = aWindow.gBrowser;
+
+ // add tab change listeners to all already existing tabs
+ for (let i = 0; i < tabbrowser.tabs.length; i++) {
+ this.onTabBrowserInserted(aWindow, tabbrowser.tabs[i]);
+ }
+ // notification of tab add/remove/selection/show/hide
+ TAB_EVENTS.forEach(function(aEvent) {
+ tabbrowser.tabContainer.addEventListener(aEvent, this, true);
+ }, this);
+
+ // Keep track of a browser's latest frameLoader.
+ aWindow.gBrowser.addEventListener("XULFrameLoaderCreated", this);
+ },
+
+ /**
+ * Initializes a given window.
+ *
+ * Windows are registered as soon as they are created but we need to wait for
+ * the session file to load, and the initial window's delayed startup to
+ * finish before initializing a window, i.e. restoring data into it.
+ *
+ * @param aWindow
+ * Window reference
+ * @param aInitialState
+ * The initial state to be loaded after startup (optional)
+ */
+ initializeWindow(aWindow, aInitialState = null) {
+ let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
+
+ // perform additional initialization when the first window is loading
+ if (RunState.isStopped) {
+ RunState.setRunning();
+
+ // restore a crashed session resp. resume the last session if requested
+ if (aInitialState) {
+ // Don't write to disk right after startup. Set the last time we wrote
+ // to disk to NOW() to enforce a full interval before the next write.
+ SessionSaver.updateLastSaveTime();
+
+ if (isPrivateWindow) {
+ // We're starting with a single private window. Save the state we
+ // actually wanted to restore so that we can do it later in case
+ // the user opens another, non-private window.
+ this._deferredInitialState = gSessionStartup.state;
+
+ // Nothing to restore now, notify observers things are complete.
+ Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
+ } else {
+ TelemetryTimestamps.add("sessionRestoreRestoring");
+ this._restoreCount = aInitialState.windows ? aInitialState.windows.length : 0;
+
+ // global data must be restored before restoreWindow is called so that
+ // it happens before observers are notified
+ this._globalState.setFromState(aInitialState);
+
+ let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
+ let options = {firstWindow: true, overwriteTabs: overwrite};
+ this.restoreWindows(aWindow, aInitialState, options);
+ }
+ }
+ else {
+ // Nothing to restore, notify observers things are complete.
+ Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
+ }
+ }
+ // this window was opened by _openWindowWithState
+ else if (!this._isWindowLoaded(aWindow)) {
+ let state = this._statesToRestore[aWindow.__SS_restoreID];
+ let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
+ this.restoreWindow(aWindow, state.windows[0], options);
+ }
+ // The user opened another, non-private window after starting up with
+ // a single private one. Let's restore the session we actually wanted to
+ // restore at startup.
+ else if (this._deferredInitialState && !isPrivateWindow &&
+ aWindow.toolbar.visible) {
+
+ // global data must be restored before restoreWindow is called so that
+ // it happens before observers are notified
+ this._globalState.setFromState(this._deferredInitialState);
+
+ this._restoreCount = this._deferredInitialState.windows ?
+ this._deferredInitialState.windows.length : 0;
+ this.restoreWindows(aWindow, this._deferredInitialState, {firstWindow: true});
+ this._deferredInitialState = null;
+ }
+ else if (this._restoreLastWindow && aWindow.toolbar.visible &&
+ this._closedWindows.length && !isPrivateWindow) {
+
+ // default to the most-recently closed window
+ // don't use popup windows
+ let closedWindowState = null;
+ let closedWindowIndex;
+ for (let i = 0; i < this._closedWindows.length; i++) {
+ // Take the first non-popup, point our object at it, and break out.
+ if (!this._closedWindows[i].isPopup) {
+ closedWindowState = this._closedWindows[i];
+ closedWindowIndex = i;
+ break;
+ }
+ }
+
+ if (closedWindowState) {
+ let newWindowState;
+ if (AppConstants.platform == "macosx" || !this._doResumeSession()) {
+ // We want to split the window up into pinned tabs and unpinned tabs.
+ // Pinned tabs should be restored. If there are any remaining tabs,
+ // they should be added back to _closedWindows.
+ // We'll cheat a little bit and reuse _prepDataForDeferredRestore
+ // even though it wasn't built exactly for this.
+ let [appTabsState, normalTabsState] =
+ this._prepDataForDeferredRestore({ windows: [closedWindowState] });
+
+ // These are our pinned tabs, which we should restore
+ if (appTabsState.windows.length) {
+ newWindowState = appTabsState.windows[0];
+ delete newWindowState.__lastSessionWindowID;
+ }
+
+ // In case there were no unpinned tabs, remove the window from _closedWindows
+ if (!normalTabsState.windows.length) {
+ this._closedWindows.splice(closedWindowIndex, 1);
+ }
+ // Or update _closedWindows with the modified state
+ else {
+ delete normalTabsState.windows[0].__lastSessionWindowID;
+ this._closedWindows[closedWindowIndex] = normalTabsState.windows[0];
+ }
+ }
+ else {
+ // If we're just restoring the window, make sure it gets removed from
+ // _closedWindows.
+ this._closedWindows.splice(closedWindowIndex, 1);
+ newWindowState = closedWindowState;
+ delete newWindowState.hidden;
+ }
+
+ if (newWindowState) {
+ // Ensure that the window state isn't hidden
+ this._restoreCount = 1;
+ let state = { windows: [newWindowState] };
+ let options = {overwriteTabs: this._isCmdLineEmpty(aWindow, state)};
+ this.restoreWindow(aWindow, newWindowState, options);
+ }
+ }
+ // we actually restored the session just now.
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
+ }
+ if (this._restoreLastWindow && aWindow.toolbar.visible) {
+ // always reset (if not a popup window)
+ // we don't want to restore a window directly after, for example,
+ // undoCloseWindow was executed.
+ this._restoreLastWindow = false;
+ }
+ },
+
+ /**
+ * Called right before a new browser window is shown.
+ * @param aWindow
+ * Window reference
+ */
+ onBeforeBrowserWindowShown: function (aWindow) {
+ // Register the window.
+ this.onLoad(aWindow);
+
+ // Just call initializeWindow() directly if we're initialized already.
+ if (this._sessionInitialized) {
+ this.initializeWindow(aWindow);
+ return;
+ }
+
+ // The very first window that is opened creates a promise that is then
+ // re-used by all subsequent windows. The promise will be used to tell
+ // when we're ready for initialization.
+ if (!this._promiseReadyForInitialization) {
+ // Wait for the given window's delayed startup to be finished.
+ let promise = new Promise(resolve => {
+ Services.obs.addObserver(function obs(subject, topic) {
+ if (aWindow == subject) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }
+ }, "browser-delayed-startup-finished", false);
+ });
+
+ // We are ready for initialization as soon as the session file has been
+ // read from disk and the initial window's delayed startup has finished.
+ this._promiseReadyForInitialization =
+ Promise.all([promise, gSessionStartup.onceInitialized]);
+ }
+
+ // We can't call this.onLoad since initialization
+ // hasn't completed, so we'll wait until it is done.
+ // Even if additional windows are opened and wait
+ // for initialization as well, the first opened
+ // window should execute first, and this.onLoad
+ // will be called with the initialState.
+ this._promiseReadyForInitialization.then(() => {
+ if (aWindow.closed) {
+ return;
+ }
+
+ if (this._sessionInitialized) {
+ this.initializeWindow(aWindow);
+ } else {
+ let initialState = this.initSession();
+ this._sessionInitialized = true;
+
+ if (initialState) {
+ Services.obs.notifyObservers(null, NOTIFY_RESTORING_ON_STARTUP, "");
+ }
+ TelemetryStopwatch.start("FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS");
+ this.initializeWindow(aWindow, initialState);
+ TelemetryStopwatch.finish("FX_SESSION_RESTORE_STARTUP_ONLOAD_INITIAL_WINDOW_MS");
+
+ // Let everyone know we're done.
+ this._deferredInitialized.resolve();
+ }
+ }, console.error);
+ },
+
+ /**
+ * On window close...
+ * - remove event listeners from tabs
+ * - save all window data
+ * @param aWindow
+ * Window reference
+ */
+ onClose: function ssi_onClose(aWindow) {
+ // this window was about to be restored - conserve its original data, if any
+ let isFullyLoaded = this._isWindowLoaded(aWindow);
+ if (!isFullyLoaded) {
+ if (!aWindow.__SSi) {
+ aWindow.__SSi = this._generateWindowID();
+ }
+
+ this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID];
+ delete this._statesToRestore[aWindow.__SS_restoreID];
+ delete aWindow.__SS_restoreID;
+ }
+
+ // ignore windows not tracked by SessionStore
+ if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
+ return;
+ }
+
+ // notify that the session store will stop tracking this window so that
+ // extensions can store any data about this window in session store before
+ // that's not possible anymore
+ let event = aWindow.document.createEvent("Events");
+ event.initEvent("SSWindowClosing", true, false);
+ aWindow.dispatchEvent(event);
+
+ if (this.windowToFocus && this.windowToFocus == aWindow) {
+ delete this.windowToFocus;
+ }
+
+ var tabbrowser = aWindow.gBrowser;
+
+ let browsers = Array.from(tabbrowser.browsers);
+
+ TAB_EVENTS.forEach(function(aEvent) {
+ tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
+ }, this);
+
+ aWindow.gBrowser.removeEventListener("XULFrameLoaderCreated", this);
+
+ let winData = this._windows[aWindow.__SSi];
+
+ // Collect window data only when *not* closed during shutdown.
+ if (RunState.isRunning) {
+ // Grab the most recent window data. The tab data will be updated
+ // once we finish flushing all of the messages from the tabs.
+ let tabMap = this._collectWindowData(aWindow);
+
+ for (let [tab, tabData] of tabMap) {
+ let permanentKey = tab.linkedBrowser.permanentKey;
+ this._closedWindowTabs.set(permanentKey, tabData);
+ }
+
+ if (isFullyLoaded) {
+ winData.title = tabbrowser.selectedBrowser.contentTitle || tabbrowser.selectedTab.label;
+ winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
+ tabbrowser.selectedTab);
+ SessionCookies.update([winData]);
+ }
+
+ if (AppConstants.platform != "macosx") {
+ // Until we decide otherwise elsewhere, this window is part of a series
+ // of closing windows to quit.
+ winData._shouldRestore = true;
+ }
+
+ // Store the window's close date to figure out when each individual tab
+ // was closed. This timestamp should allow re-arranging data based on how
+ // recently something was closed.
+ winData.closedAt = Date.now();
+
+ // we don't want to save the busy state
+ delete winData.busy;
+
+ // When closing windows one after the other until Firefox quits, we
+ // will move those closed in series back to the "open windows" bucket
+ // before writing to disk. If however there is only a single window
+ // with tabs we deem not worth saving then we might end up with a
+ // random closed or even a pop-up window re-opened. To prevent that
+ // we explicitly allow saving an "empty" window state.
+ let isLastWindow =
+ Object.keys(this._windows).length == 1 &&
+ !this._closedWindows.some(win => win._shouldRestore || false);
+
+ // clear this window from the list, since it has definitely been closed.
+ delete this._windows[aWindow.__SSi];
+
+ // This window has the potential to be saved in the _closedWindows
+ // array (maybeSaveClosedWindows gets the final call on that).
+ this._saveableClosedWindowData.add(winData);
+
+ // Now we have to figure out if this window is worth saving in the _closedWindows
+ // Object.
+ //
+ // We're about to flush the tabs from this window, but it's possible that we
+ // might never hear back from the content process(es) in time before the user
+ // chooses to restore the closed window. So we do the following:
+ //
+ // 1) Use the tab state cache to determine synchronously if the window is
+ // worth stashing in _closedWindows.
+ // 2) Flush the window.
+ // 3) When the flush is complete, revisit our decision to store the window
+ // in _closedWindows, and add/remove as necessary.
+ if (!winData.isPrivate) {
+ // Remove any open private tabs the window may contain.
+ PrivacyFilter.filterPrivateTabs(winData);
+ this.maybeSaveClosedWindow(winData, isLastWindow);
+ }
+
+ TabStateFlusher.flushWindow(aWindow).then(() => {
+ // At this point, aWindow is closed! You should probably not try to
+ // access any DOM elements from aWindow within this callback unless
+ // you're holding on to them in the closure.
+
+ for (let browser of browsers) {
+ if (this._closedWindowTabs.has(browser.permanentKey)) {
+ let tabData = this._closedWindowTabs.get(browser.permanentKey);
+ TabState.copyFromCache(browser, tabData);
+ this._closedWindowTabs.delete(browser.permanentKey);
+ }
+ }
+
+ // Save non-private windows if they have at
+ // least one saveable tab or are the last window.
+ if (!winData.isPrivate) {
+ // It's possible that a tab switched its privacy state at some point
+ // before our flush, so we need to filter again.
+ PrivacyFilter.filterPrivateTabs(winData);
+ this.maybeSaveClosedWindow(winData, isLastWindow);
+ }
+
+ // Update the tabs data now that we've got the most
+ // recent information.
+ this.cleanUpWindow(aWindow, winData, browsers);
+
+ // save the state without this window to disk
+ this.saveStateDelayed();
+ });
+ } else {
+ this.cleanUpWindow(aWindow, winData, browsers);
+ }
+
+ for (let i = 0; i < tabbrowser.tabs.length; i++) {
+ this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
+ }
+ },
+
+ /**
+ * Clean up the message listeners on a window that has finally
+ * gone away. Call this once you're sure you don't want to hear
+ * from any of this windows tabs from here forward.
+ *
+ * @param aWindow
+ * The browser window we're cleaning up.
+ * @param winData
+ * The data for the window that we should hold in the
+ * DyingWindowCache in case anybody is still holding a
+ * reference to it.
+ */
+ cleanUpWindow(aWindow, winData, browsers) {
+ // Any leftover TabStateFlusher Promises need to be resolved now,
+ // since we're about to remove the message listeners.
+ for (let browser of browsers) {
+ TabStateFlusher.resolveAll(browser);
+ }
+
+ // Cache the window state until it is completely gone.
+ DyingWindowCache.set(aWindow, winData);
+
+ let mm = aWindow.getGroupMessageManager("browsers");
+ MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
+
+ this._saveableClosedWindowData.delete(winData);
+ delete aWindow.__SSi;
+ },
+
+ /**
+ * Decides whether or not a closed window should be put into the
+ * _closedWindows Object. This might be called multiple times per
+ * window, and will do the right thing of moving the window data
+ * in or out of _closedWindows if the winData indicates that our
+ * need for saving it has changed.
+ *
+ * @param winData
+ * The data for the closed window that we might save.
+ * @param isLastWindow
+ * Whether or not the window being closed is the last
+ * browser window. Callers of this function should pass
+ * in the value of SessionStoreInternal.atLastWindow for
+ * this argument, and pass in the same value if they happen
+ * to call this method again asynchronously (for example, after
+ * a window flush).
+ */
+ maybeSaveClosedWindow(winData, isLastWindow) {
+ // Make sure SessionStore is still running, and make sure that we
+ // haven't chosen to forget this window.
+ if (RunState.isRunning && this._saveableClosedWindowData.has(winData)) {
+ // Determine whether the window has any tabs worth saving.
+ let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
+
+ // Note that we might already have this window stored in
+ // _closedWindows from a previous call to this function.
+ let winIndex = this._closedWindows.indexOf(winData);
+ let alreadyStored = (winIndex != -1);
+ let shouldStore = (hasSaveableTabs || isLastWindow);
+
+ if (shouldStore && !alreadyStored) {
+ let index = this._closedWindows.findIndex(win => {
+ return win.closedAt < winData.closedAt;
+ });
+
+ // If we found no tab closed before our
+ // tab then just append it to the list.
+ if (index == -1) {
+ index = this._closedWindows.length;
+ }
+
+ // About to save the closed window, add a unique ID.
+ winData.closedId = this._nextClosedId++;
+
+ // Insert tabData at the right position.
+ this._closedWindows.splice(index, 0, winData);
+ this._capClosedWindows();
+ } else if (!shouldStore && alreadyStored) {
+ this._closedWindows.splice(winIndex, 1);
+ }
+ }
+ },
+
+ /**
+ * On quit application granted
+ */
+ onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown=false) {
+ // Collect an initial snapshot of window data before we do the flush
+ this._forEachBrowserWindow((win) => {
+ this._collectWindowData(win);
+ });
+
+ // Now add an AsyncShutdown blocker that'll spin the event loop
+ // until the windows have all been flushed.
+
+ // This progress object will track the state of async window flushing
+ // and will help us debug things that go wrong with our AsyncShutdown
+ // blocker.
+ let progress = { total: -1, current: -1 };
+
+ // We're going down! Switch state so that we treat closing windows and
+ // tabs correctly.
+ RunState.setQuitting();
+
+ if (!syncShutdown) {
+ // We've got some time to shut down, so let's do this properly.
+ // To prevent blocker from breaking the 60 sec limit(which will cause a
+ // crash) of async shutdown during flushing all windows, we resolve the
+ // promise passed to blocker once:
+ // 1. the flushing exceed 50 sec, or
+ // 2. 'oop-frameloader-crashed' or 'ipc:content-shutdown' is observed.
+ // Thus, Firefox still can open the last session on next startup.
+ AsyncShutdown.quitApplicationGranted.addBlocker(
+ "SessionStore: flushing all windows",
+ () => {
+ var promises = [];
+ promises.push(this.flushAllWindowsAsync(progress));
+ promises.push(this.looseTimer(50000));
+
+ var promiseOFC = new Promise(resolve => {
+ Services.obs.addObserver(function obs(subject, topic) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }, "oop-frameloader-crashed", false);
+ });
+ promises.push(promiseOFC);
+
+ var promiseICS = new Promise(resolve => {
+ Services.obs.addObserver(function obs(subject, topic) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }, "ipc:content-shutdown", false);
+ });
+ promises.push(promiseICS);
+
+ return Promise.race(promises);
+ },
+ () => progress);
+ } else {
+ // We have to shut down NOW, which means we only get to save whatever
+ // we already had cached.
+ }
+ },
+
+ /**
+ * An async Task that iterates all open browser windows and flushes
+ * any outstanding messages from their tabs. This will also close
+ * all of the currently open windows while we wait for the flushes
+ * to complete.
+ *
+ * @param progress (Object)
+ * Optional progress object that will be updated as async
+ * window flushing progresses. flushAllWindowsSync will
+ * write to the following properties:
+ *
+ * total (int):
+ * The total number of windows to be flushed.
+ * current (int):
+ * The current window that we're waiting for a flush on.
+ *
+ * @return Promise
+ */
+ flushAllWindowsAsync: Task.async(function*(progress={}) {
+ let windowPromises = new Map();
+ // We collect flush promises and close each window immediately so that
+ // the user can't start changing any window state while we're waiting
+ // for the flushes to finish.
+ this._forEachBrowserWindow((win) => {
+ windowPromises.set(win, TabStateFlusher.flushWindow(win));
+
+ // We have to wait for these messages to come up from
+ // each window and each browser. In the meantime, hide
+ // the windows to improve perceived shutdown speed.
+ let baseWin = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Ci.nsIBaseWindow);
+ baseWin.visibility = false;
+ });
+
+ progress.total = windowPromises.size;
+ progress.current = 0;
+
+ // We'll iterate through the Promise array, yielding each one, so as to
+ // provide useful progress information to AsyncShutdown.
+ for (let [win, promise] of windowPromises) {
+ yield promise;
+ this._collectWindowData(win);
+ progress.current++;
+ };
+
+ // We must cache this because _getMostRecentBrowserWindow will always
+ // return null by the time quit-application occurs.
+ var activeWindow = this._getMostRecentBrowserWindow();
+ if (activeWindow)
+ this.activeWindowSSiCache = activeWindow.__SSi || "";
+ DirtyWindows.clear();
+ }),
+
+ /**
+ * On last browser window close
+ */
+ onLastWindowCloseGranted: function ssi_onLastWindowCloseGranted() {
+ // last browser window is quitting.
+ // remember to restore the last window when another browser window is opened
+ // do not account for pref(resume_session_once) at this point, as it might be
+ // set by another observer getting this notice after us
+ this._restoreLastWindow = true;
+ },
+
+ /**
+ * On quitting application
+ * @param aData
+ * String type of quitting
+ */
+ onQuitApplication: function ssi_onQuitApplication(aData) {
+ if (aData == "restart") {
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
+ // The browser:purge-session-history notification fires after the
+ // quit-application notification so unregister the
+ // browser:purge-session-history notification to prevent clearing
+ // session data on disk on a restart. It is also unnecessary to
+ // perform any other sanitization processing on a restart as the
+ // browser is about to exit anyway.
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ }
+
+ if (aData != "restart") {
+ // Throw away the previous session on shutdown
+ LastSession.clear();
+ }
+
+ this._uninit();
+ },
+
+ /**
+ * On purge of session history
+ */
+ onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
+ SessionFile.wipe();
+ // If the browser is shutting down, simply return after clearing the
+ // session data on disk as this notification fires after the
+ // quit-application notification so the browser is about to exit.
+ if (RunState.isQuitting)
+ return;
+ LastSession.clear();
+
+ let openWindows = {};
+ // Collect open windows.
+ this._forEachBrowserWindow(({__SSi: id}) => openWindows[id] = true);
+
+ // also clear all data about closed tabs and windows
+ for (let ix in this._windows) {
+ if (ix in openWindows) {
+ this._windows[ix]._closedTabs = [];
+ } else {
+ delete this._windows[ix];
+ }
+ }
+ // also clear all data about closed windows
+ this._closedWindows = [];
+ // give the tabbrowsers a chance to clear their histories first
+ var win = this._getMostRecentBrowserWindow();
+ if (win) {
+ win.setTimeout(() => SessionSaver.run(), 0);
+ } else if (RunState.isRunning) {
+ SessionSaver.run();
+ }
+
+ this._clearRestoringWindows();
+ this._saveableClosedWindowData = new WeakSet();
+ },
+
+ /**
+ * On purge of domain data
+ * @param aData
+ * String domain data
+ */
+ onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
+ // does a session history entry contain a url for the given domain?
+ function containsDomain(aEntry) {
+ if (Utils.hasRootDomain(aEntry.url, aData)) {
+ return true;
+ }
+ return aEntry.children && aEntry.children.some(containsDomain, this);
+ }
+ // remove all closed tabs containing a reference to the given domain
+ for (let ix in this._windows) {
+ let closedTabs = this._windows[ix]._closedTabs;
+ for (let i = closedTabs.length - 1; i >= 0; i--) {
+ if (closedTabs[i].state.entries.some(containsDomain, this))
+ closedTabs.splice(i, 1);
+ }
+ }
+ // remove all open & closed tabs containing a reference to the given
+ // domain in closed windows
+ for (let ix = this._closedWindows.length - 1; ix >= 0; ix--) {
+ let closedTabs = this._closedWindows[ix]._closedTabs;
+ let openTabs = this._closedWindows[ix].tabs;
+ let openTabCount = openTabs.length;
+ for (let i = closedTabs.length - 1; i >= 0; i--)
+ if (closedTabs[i].state.entries.some(containsDomain, this))
+ closedTabs.splice(i, 1);
+ for (let j = openTabs.length - 1; j >= 0; j--) {
+ if (openTabs[j].entries.some(containsDomain, this)) {
+ openTabs.splice(j, 1);
+ if (this._closedWindows[ix].selected > j)
+ this._closedWindows[ix].selected--;
+ }
+ }
+ if (openTabs.length == 0) {
+ this._closedWindows.splice(ix, 1);
+ }
+ else if (openTabs.length != openTabCount) {
+ // Adjust the window's title if we removed an open tab
+ let selectedTab = openTabs[this._closedWindows[ix].selected - 1];
+ // some duplication from restoreHistory - make sure we get the correct title
+ let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
+ if (activeIndex >= selectedTab.entries.length)
+ activeIndex = selectedTab.entries.length - 1;
+ this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
+ }
+ }
+
+ if (RunState.isRunning) {
+ SessionSaver.run();
+ }
+
+ this._clearRestoringWindows();
+ },
+
+ /**
+ * On preference change
+ * @param aData
+ * String preference changed
+ */
+ onPrefChange: function ssi_onPrefChange(aData) {
+ switch (aData) {
+ // if the user decreases the max number of closed tabs they want
+ // preserved update our internal states to match that max
+ case "sessionstore.max_tabs_undo":
+ this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
+ for (let ix in this._windows) {
+ this._windows[ix]._closedTabs.splice(this._max_tabs_undo, this._windows[ix]._closedTabs.length);
+ }
+ break;
+ case "sessionstore.max_windows_undo":
+ this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
+ this._capClosedWindows();
+ break;
+ }
+ },
+
+ /**
+ * save state when new tab is added
+ * @param aWindow
+ * Window reference
+ */
+ onTabAdd: function ssi_onTabAdd(aWindow) {
+ this.saveStateDelayed(aWindow);
+ },
+
+ /**
+ * set up listeners for a new tab
+ * @param aWindow
+ * Window reference
+ * @param aTab
+ * Tab reference
+ */
+ onTabBrowserInserted: function ssi_onTabBrowserInserted(aWindow, aTab) {
+ let browser = aTab.linkedBrowser;
+ browser.addEventListener("SwapDocShells", this);
+ browser.addEventListener("oop-browser-crashed", this);
+
+ if (browser.frameLoader) {
+ this._lastKnownFrameLoader.set(browser.permanentKey, browser.frameLoader);
+ }
+ },
+
+ /**
+ * remove listeners for a tab
+ * @param aWindow
+ * Window reference
+ * @param aTab
+ * Tab reference
+ * @param aNoNotification
+ * bool Do not save state if we're updating an existing tab
+ */
+ onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
+ let browser = aTab.linkedBrowser;
+ browser.removeEventListener("SwapDocShells", this);
+ browser.removeEventListener("oop-browser-crashed", this);
+
+ // If this tab was in the middle of restoring or still needs to be restored,
+ // we need to reset that state. If the tab was restoring, we will attempt to
+ // restore the next tab.
+ let previousState = browser.__SS_restoreState;
+ if (previousState) {
+ this._resetTabRestoringState(aTab);
+ if (previousState == TAB_STATE_RESTORING)
+ this.restoreNextTab();
+ }
+
+ if (!aNoNotification) {
+ this.saveStateDelayed(aWindow);
+ }
+ },
+
+ /**
+ * When a tab closes, collect its properties
+ * @param aWindow
+ * Window reference
+ * @param aTab
+ * Tab reference
+ */
+ onTabClose: function ssi_onTabClose(aWindow, aTab) {
+ // notify the tabbrowser that the tab state will be retrieved for the last time
+ // (so that extension authors can easily set data on soon-to-be-closed tabs)
+ var event = aWindow.document.createEvent("Events");
+ event.initEvent("SSTabClosing", true, false);
+ aTab.dispatchEvent(event);
+
+ // don't update our internal state if we don't have to
+ if (this._max_tabs_undo == 0) {
+ return;
+ }
+
+ // Get the latest data for this tab (generally, from the cache)
+ let tabState = TabState.collect(aTab);
+
+ // Don't save private tabs
+ let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
+ if (!isPrivateWindow && tabState.isPrivate) {
+ return;
+ }
+
+ // Store closed-tab data for undo.
+ let tabbrowser = aWindow.gBrowser;
+ let tabTitle = this._replaceLoadingTitle(aTab.label, tabbrowser, aTab);
+ let {permanentKey} = aTab.linkedBrowser;
+
+ let tabData = {
+ permanentKey,
+ state: tabState,
+ title: tabTitle,
+ image: tabbrowser.getIcon(aTab),
+ iconLoadingPrincipal: Utils.serializePrincipal(aTab.linkedBrowser.contentPrincipal),
+ pos: aTab._tPos,
+ closedAt: Date.now()
+ };
+
+ let closedTabs = this._windows[aWindow.__SSi]._closedTabs;
+
+ // Determine whether the tab contains any information worth saving. Note
+ // that there might be pending state changes queued in the child that
+ // didn't reach the parent yet. If a tab is emptied before closing then we
+ // might still remove it from the list of closed tabs later.
+ if (this._shouldSaveTabState(tabState)) {
+ // Save the tab state, for now. We might push a valid tab out
+ // of the list but those cases should be extremely rare and
+ // do probably never occur when using the browser normally.
+ // (Tests or add-ons might do weird things though.)
+ this.saveClosedTabData(closedTabs, tabData);
+ }
+
+ // Remember the closed tab to properly handle any last updates included in
+ // the final "update" message sent by the frame script's unload handler.
+ this._closedTabs.set(permanentKey, {closedTabs, tabData});
+ },
+
+ /**
+ * Insert a given |tabData| object into the list of |closedTabs|. We will
+ * determine the right insertion point based on the .closedAt properties of
+ * all tabs already in the list. The list will be truncated to contain a
+ * maximum of |this._max_tabs_undo| entries.
+ *
+ * @param closedTabs (array)
+ * The list of closed tabs for a window.
+ * @param tabData (object)
+ * The tabData to be inserted.
+ */
+ saveClosedTabData(closedTabs, tabData) {
+ // Find the index of the first tab in the list
+ // of closed tabs that was closed before our tab.
+ let index = closedTabs.findIndex(tab => {
+ return tab.closedAt < tabData.closedAt;
+ });
+
+ // If we found no tab closed before our
+ // tab then just append it to the list.
+ if (index == -1) {
+ index = closedTabs.length;
+ }
+
+ // About to save the closed tab, add a unique ID.
+ tabData.closedId = this._nextClosedId++;
+
+ // Insert tabData at the right position.
+ closedTabs.splice(index, 0, tabData);
+
+ // Truncate the list of closed tabs, if needed.
+ if (closedTabs.length > this._max_tabs_undo) {
+ closedTabs.splice(this._max_tabs_undo, closedTabs.length);
+ }
+ },
+
+ /**
+ * Remove the closed tab data at |index| from the list of |closedTabs|. If
+ * the tab's final message is still pending we will simply discard it when
+ * it arrives so that the tab doesn't reappear in the list.
+ *
+ * @param closedTabs (array)
+ * The list of closed tabs for a window.
+ * @param index (uint)
+ * The index of the tab to remove.
+ */
+ removeClosedTabData(closedTabs, index) {
+ // Remove the given index from the list.
+ let [closedTab] = closedTabs.splice(index, 1);
+
+ // If the closed tab's state still has a .permanentKey property then we
+ // haven't seen its final update message yet. Remove it from the map of
+ // closed tabs so that we will simply discard its last messages and will
+ // not add it back to the list of closed tabs again.
+ if (closedTab.permanentKey) {
+ this._closedTabs.delete(closedTab.permanentKey);
+ this._closedWindowTabs.delete(closedTab.permanentKey);
+ delete closedTab.permanentKey;
+ }
+
+ return closedTab;
+ },
+
+ /**
+ * When a tab is selected, save session data
+ * @param aWindow
+ * Window reference
+ */
+ onTabSelect: function ssi_onTabSelect(aWindow) {
+ if (RunState.isRunning) {
+ this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
+
+ let tab = aWindow.gBrowser.selectedTab;
+ let browser = tab.linkedBrowser;
+
+ if (browser.__SS_restoreState &&
+ browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+ // If __SS_restoreState is still on the browser and it is
+ // TAB_STATE_NEEDS_RESTORE, then then we haven't restored
+ // this tab yet.
+ //
+ // It's possible that this tab was recently revived, and that
+ // we've deferred showing the tab crashed page for it (if the
+ // tab crashed in the background). If so, we need to re-enter
+ // the crashed state, since we'll be showing the tab crashed
+ // page.
+ if (TabCrashHandler.willShowCrashedTab(browser)) {
+ this.enterCrashedState(browser);
+ } else {
+ this.restoreTabContent(tab);
+ }
+ }
+ }
+ },
+
+ onTabShow: function ssi_onTabShow(aWindow, aTab) {
+ // If the tab hasn't been restored yet, move it into the right bucket
+ if (aTab.linkedBrowser.__SS_restoreState &&
+ aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+ TabRestoreQueue.hiddenToVisible(aTab);
+
+ // let's kick off tab restoration again to ensure this tab gets restored
+ // with "restore_hidden_tabs" == false (now that it has become visible)
+ this.restoreNextTab();
+ }
+
+ // Default delay of 2 seconds gives enough time to catch multiple TabShow
+ // events. This used to be due to changing groups in 'tab groups'. We
+ // might be able to get rid of this now?
+ this.saveStateDelayed(aWindow);
+ },
+
+ onTabHide: function ssi_onTabHide(aWindow, aTab) {
+ // If the tab hasn't been restored yet, move it into the right bucket
+ if (aTab.linkedBrowser.__SS_restoreState &&
+ aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+ TabRestoreQueue.visibleToHidden(aTab);
+ }
+
+ // Default delay of 2 seconds gives enough time to catch multiple TabHide
+ // events. This used to be due to changing groups in 'tab groups'. We
+ // might be able to get rid of this now?
+ this.saveStateDelayed(aWindow);
+ },
+
+ /**
+ * Handler for the event that is fired when a <xul:browser> crashes.
+ *
+ * @param aWindow
+ * The window that the crashed browser belongs to.
+ * @param aBrowser
+ * The <xul:browser> that is now in the crashed state.
+ */
+ onBrowserCrashed: function(aBrowser) {
+ NS_ASSERT(aBrowser.isRemoteBrowser,
+ "Only remote browsers should be able to crash");
+
+ this.enterCrashedState(aBrowser);
+ // The browser crashed so we might never receive flush responses.
+ // Resolve all pending flush requests for the crashed browser.
+ TabStateFlusher.resolveAll(aBrowser);
+ },
+
+ /**
+ * Called when a browser is showing or is about to show the tab
+ * crashed page. This method causes SessionStore to ignore the
+ * tab until it's restored.
+ *
+ * @param browser
+ * The <xul:browser> that is about to show the crashed page.
+ */
+ enterCrashedState(browser) {
+ this._crashedBrowsers.add(browser.permanentKey);
+
+ let win = browser.ownerGlobal;
+
+ // If we hadn't yet restored, or were still in the midst of
+ // restoring this browser at the time of the crash, we need
+ // to reset its state so that we can try to restore it again
+ // when the user revives the tab from the crash.
+ if (browser.__SS_restoreState) {
+ let tab = win.gBrowser.getTabForBrowser(browser);
+ this._resetLocalTabRestoringState(tab);
+ }
+ },
+
+ // Clean up data that has been closed a long time ago.
+ // Do not reschedule a save. This will wait for the next regular
+ // save.
+ onIdleDaily: function() {
+ // Remove old closed windows
+ this._cleanupOldData([this._closedWindows]);
+
+ // Remove closed tabs of closed windows
+ this._cleanupOldData(this._closedWindows.map((winData) => winData._closedTabs));
+
+ // Remove closed tabs of open windows
+ this._cleanupOldData(Object.keys(this._windows).map((key) => this._windows[key]._closedTabs));
+ },
+
+ // Remove "old" data from an array
+ _cleanupOldData: function(targets) {
+ const TIME_TO_LIVE = this._prefBranch.getIntPref("sessionstore.cleanup.forget_closed_after");
+ const now = Date.now();
+
+ for (let array of targets) {
+ for (let i = array.length - 1; i >= 0; --i) {
+ let data = array[i];
+ // Make sure that we have a timestamp to tell us when the target
+ // has been closed. If we don't have a timestamp, default to a
+ // safe timestamp: just now.
+ data.closedAt = data.closedAt || now;
+ if (now - data.closedAt > TIME_TO_LIVE) {
+ array.splice(i, 1);
+ }
+ }
+ }
+ },
+
+ /* ........ nsISessionStore API .............. */
+
+ getBrowserState: function ssi_getBrowserState() {
+ let state = this.getCurrentState();
+
+ // Don't include the last session state in getBrowserState().
+ delete state.lastSessionState;
+
+ // Don't include any deferred initial state.
+ delete state.deferredInitialState;
+
+ return JSON.stringify(state);
+ },
+
+ setBrowserState: function ssi_setBrowserState(aState) {
+ this._handleClosedWindows();
+
+ try {
+ var state = JSON.parse(aState);
+ }
+ catch (ex) { /* invalid state object - don't restore anything */ }
+ if (!state) {
+ throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
+ }
+ if (!state.windows) {
+ throw Components.Exception("No windows", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ this._browserSetState = true;
+
+ // Make sure the priority queue is emptied out
+ this._resetRestoringState();
+
+ var window = this._getMostRecentBrowserWindow();
+ if (!window) {
+ this._restoreCount = 1;
+ this._openWindowWithState(state);
+ return;
+ }
+
+ // close all other browser windows
+ this._forEachBrowserWindow(function(aWindow) {
+ if (aWindow != window) {
+ aWindow.close();
+ this.onClose(aWindow);
+ }
+ });
+
+ // make sure closed window data isn't kept
+ this._closedWindows = [];
+
+ // determine how many windows are meant to be restored
+ this._restoreCount = state.windows ? state.windows.length : 0;
+
+ // global data must be restored before restoreWindow is called so that
+ // it happens before observers are notified
+ this._globalState.setFromState(state);
+
+ // restore to the given state
+ this.restoreWindows(window, state, {overwriteTabs: true});
+ },
+
+ getWindowState: function ssi_getWindowState(aWindow) {
+ if ("__SSi" in aWindow) {
+ return JSON.stringify(this._getWindowState(aWindow));
+ }
+
+ if (DyingWindowCache.has(aWindow)) {
+ let data = DyingWindowCache.get(aWindow);
+ return JSON.stringify({ windows: [data] });
+ }
+
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ },
+
+ setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
+ if (!aWindow.__SSi) {
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ this.restoreWindows(aWindow, aState, {overwriteTabs: aOverwrite});
+ },
+
+ getTabState: function ssi_getTabState(aTab) {
+ if (!aTab.ownerGlobal.__SSi) {
+ throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ let tabState = TabState.collect(aTab);
+
+ return JSON.stringify(tabState);
+ },
+
+ setTabState(aTab, aState) {
+ // Remove the tab state from the cache.
+ // Note that we cannot simply replace the contents of the cache
+ // as |aState| can be an incomplete state that will be completed
+ // by |restoreTabs|.
+ let tabState = JSON.parse(aState);
+ if (!tabState) {
+ throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
+ }
+ if (typeof tabState != "object") {
+ throw Components.Exception("Not an object", Cr.NS_ERROR_INVALID_ARG);
+ }
+ if (!("entries" in tabState)) {
+ throw Components.Exception("Invalid state object: no entries", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ let window = aTab.ownerGlobal;
+ if (!("__SSi" in window)) {
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ if (aTab.linkedBrowser.__SS_restoreState) {
+ this._resetTabRestoringState(aTab);
+ }
+
+ this.restoreTab(aTab, tabState);
+ },
+
+ duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0, aRestoreImmediately = true) {
+ if (!aTab.ownerGlobal.__SSi) {
+ throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+ if (!aWindow.gBrowser) {
+ throw Components.Exception("Invalid window object: no gBrowser", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // Create a new tab.
+ let userContextId = aTab.getAttribute("usercontextid");
+ let newTab = aTab == aWindow.gBrowser.selectedTab ?
+ aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab, userContextId}) :
+ aWindow.gBrowser.addTab(null, {userContextId});
+
+ // Set tab title to "Connecting..." and start the throbber to pretend we're
+ // doing something while actually waiting for data from the frame script.
+ aWindow.gBrowser.setTabTitleLoading(newTab);
+ newTab.setAttribute("busy", "true");
+
+ // Collect state before flushing.
+ let tabState = TabState.clone(aTab);
+
+ // Flush to get the latest tab state to duplicate.
+ let browser = aTab.linkedBrowser;
+ TabStateFlusher.flush(browser).then(() => {
+ // The new tab might have been closed in the meantime.
+ if (newTab.closing || !newTab.linkedBrowser) {
+ return;
+ }
+
+ let window = newTab.ownerGlobal;
+
+ // The tab or its window might be gone.
+ if (!window || !window.__SSi) {
+ return;
+ }
+
+ // Update state with flushed data. We can't use TabState.clone() here as
+ // the tab to duplicate may have already been closed. In that case we
+ // only have access to the <xul:browser>.
+ let options = {includePrivateData: true};
+ TabState.copyFromCache(browser, tabState, options);
+
+ tabState.index += aDelta;
+ tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
+ tabState.pinned = false;
+
+ // Restore the state into the new tab.
+ this.restoreTab(newTab, tabState, {
+ restoreImmediately: aRestoreImmediately
+ });
+ });
+
+ return newTab;
+ },
+
+ getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
+ if ("__SSi" in aWindow) {
+ return this._windows[aWindow.__SSi]._closedTabs.length;
+ }
+
+ if (!DyingWindowCache.has(aWindow)) {
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ return DyingWindowCache.get(aWindow)._closedTabs.length;
+ },
+
+ getClosedTabData: function ssi_getClosedTabData(aWindow, aAsString = true) {
+ if ("__SSi" in aWindow) {
+ return aAsString ?
+ JSON.stringify(this._windows[aWindow.__SSi]._closedTabs) :
+ Cu.cloneInto(this._windows[aWindow.__SSi]._closedTabs, {});
+ }
+
+ if (!DyingWindowCache.has(aWindow)) {
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ let data = DyingWindowCache.get(aWindow);
+ return aAsString ? JSON.stringify(data._closedTabs) : Cu.cloneInto(data._closedTabs, {});
+ },
+
+ undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
+ if (!aWindow.__SSi) {
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
+
+ // default to the most-recently closed tab
+ aIndex = aIndex || 0;
+ if (!(aIndex in closedTabs)) {
+ throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // fetch the data of closed tab, while removing it from the array
+ let {state, pos} = this.removeClosedTabData(closedTabs, aIndex);
+
+ // create a new tab
+ let tabbrowser = aWindow.gBrowser;
+ let tab = tabbrowser.selectedTab = tabbrowser.addTab(null, state);
+
+ // restore tab content
+ this.restoreTab(tab, state);
+
+ // restore the tab's position
+ tabbrowser.moveTabTo(tab, pos);
+
+ // focus the tab's content area (bug 342432)
+ tab.linkedBrowser.focus();
+
+ return tab;
+ },
+
+ forgetClosedTab: function ssi_forgetClosedTab(aWindow, aIndex) {
+ if (!aWindow.__SSi) {
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
+
+ // default to the most-recently closed tab
+ aIndex = aIndex || 0;
+ if (!(aIndex in closedTabs)) {
+ throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // remove closed tab from the array
+ this.removeClosedTabData(closedTabs, aIndex);
+ },
+
+ getClosedWindowCount: function ssi_getClosedWindowCount() {
+ return this._closedWindows.length;
+ },
+
+ getClosedWindowData: function ssi_getClosedWindowData(aAsString = true) {
+ return aAsString ? JSON.stringify(this._closedWindows) : Cu.cloneInto(this._closedWindows, {});
+ },
+
+ undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
+ if (!(aIndex in this._closedWindows)) {
+ throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // reopen the window
+ let state = { windows: this._closedWindows.splice(aIndex, 1) };
+ delete state.windows[0].closedAt; // Window is now open.
+
+ let window = this._openWindowWithState(state);
+ this.windowToFocus = window;
+ return window;
+ },
+
+ forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
+ // default to the most-recently closed window
+ aIndex = aIndex || 0;
+ if (!(aIndex in this._closedWindows)) {
+ throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // remove closed window from the array
+ let winData = this._closedWindows[aIndex];
+ this._closedWindows.splice(aIndex, 1);
+ this._saveableClosedWindowData.delete(winData);
+ },
+
+ getWindowValue: function ssi_getWindowValue(aWindow, aKey) {
+ if ("__SSi" in aWindow) {
+ var data = this._windows[aWindow.__SSi].extData || {};
+ return data[aKey] || "";
+ }
+
+ if (DyingWindowCache.has(aWindow)) {
+ let data = DyingWindowCache.get(aWindow).extData || {};
+ return data[aKey] || "";
+ }
+
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ },
+
+ setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) {
+ if (typeof aStringValue != "string") {
+ throw new TypeError("setWindowValue only accepts string values");
+ }
+
+ if (!("__SSi" in aWindow)) {
+ throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
+ }
+ if (!this._windows[aWindow.__SSi].extData) {
+ this._windows[aWindow.__SSi].extData = {};
+ }
+ this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
+ this.saveStateDelayed(aWindow);
+ },
+
+ deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
+ if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
+ this._windows[aWindow.__SSi].extData[aKey])
+ delete this._windows[aWindow.__SSi].extData[aKey];
+ this.saveStateDelayed(aWindow);
+ },
+
+ getTabValue: function ssi_getTabValue(aTab, aKey) {
+ return (aTab.__SS_extdata || {})[aKey] || "";
+ },
+
+ setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
+ if (typeof aStringValue != "string") {
+ throw new TypeError("setTabValue only accepts string values");
+ }
+
+ // If the tab hasn't been restored, then set the data there, otherwise we
+ // could lose newly added data.
+ if (!aTab.__SS_extdata) {
+ aTab.__SS_extdata = {};
+ }
+
+ aTab.__SS_extdata[aKey] = aStringValue;
+ this.saveStateDelayed(aTab.ownerGlobal);
+ },
+
+ deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
+ if (aTab.__SS_extdata && aKey in aTab.__SS_extdata) {
+ delete aTab.__SS_extdata[aKey];
+ this.saveStateDelayed(aTab.ownerGlobal);
+ }
+ },
+
+ getGlobalValue: function ssi_getGlobalValue(aKey) {
+ return this._globalState.get(aKey);
+ },
+
+ setGlobalValue: function ssi_setGlobalValue(aKey, aStringValue) {
+ if (typeof aStringValue != "string") {
+ throw new TypeError("setGlobalValue only accepts string values");
+ }
+
+ this._globalState.set(aKey, aStringValue);
+ this.saveStateDelayed();
+ },
+
+ deleteGlobalValue: function ssi_deleteGlobalValue(aKey) {
+ this._globalState.delete(aKey);
+ this.saveStateDelayed();
+ },
+
+ persistTabAttribute: function ssi_persistTabAttribute(aName) {
+ if (TabAttributes.persist(aName)) {
+ this.saveStateDelayed();
+ }
+ },
+
+
+ /**
+ * Undoes the closing of a tab or window which corresponds
+ * to the closedId passed in.
+ *
+ * @param aClosedId
+ * The closedId of the tab or window
+ *
+ * @returns a tab or window object
+ */
+ undoCloseById(aClosedId) {
+ // Check for a window first.
+ for (let i = 0, l = this._closedWindows.length; i < l; i++) {
+ if (this._closedWindows[i].closedId == aClosedId) {
+ return this.undoCloseWindow(i);
+ }
+ }
+
+ // Check for a tab.
+ let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ while (windowsEnum.hasMoreElements()) {
+ let window = windowsEnum.getNext();
+ let windowState = this._windows[window.__SSi];
+ if (windowState) {
+ for (let j = 0, l = windowState._closedTabs.length; j < l; j++) {
+ if (windowState._closedTabs[j].closedId == aClosedId) {
+ return this.undoCloseTab(window, j);
+ }
+ }
+ }
+ }
+
+ // Neither a tab nor a window was found, return undefined and let the caller decide what to do about it.
+ return undefined;
+ },
+
+ /**
+ * Restores the session state stored in LastSession. This will attempt
+ * to merge data into the current session. If a window was opened at startup
+ * with pinned tab(s), then the remaining data from the previous session for
+ * that window will be opened into that window. Otherwise new windows will
+ * be opened.
+ */
+ restoreLastSession: function ssi_restoreLastSession() {
+ // Use the public getter since it also checks PB mode
+ if (!this.canRestoreLastSession) {
+ throw Components.Exception("Last session can not be restored");
+ }
+
+ Services.obs.notifyObservers(null, NOTIFY_INITIATING_MANUAL_RESTORE, "");
+
+ // First collect each window with its id...
+ let windows = {};
+ this._forEachBrowserWindow(function(aWindow) {
+ if (aWindow.__SS_lastSessionWindowID)
+ windows[aWindow.__SS_lastSessionWindowID] = aWindow;
+ });
+
+ let lastSessionState = LastSession.getState();
+
+ // This shouldn't ever be the case...
+ if (!lastSessionState.windows.length) {
+ throw Components.Exception("lastSessionState has no windows", Cr.NS_ERROR_UNEXPECTED);
+ }
+
+ // We're technically doing a restore, so set things up so we send the
+ // notification when we're done. We want to send "sessionstore-browser-state-restored".
+ this._restoreCount = lastSessionState.windows.length;
+ this._browserSetState = true;
+
+ // We want to re-use the last opened window instead of opening a new one in
+ // the case where it's "empty" and not associated with a window in the session.
+ // We will do more processing via _prepWindowToRestoreInto if we need to use
+ // the lastWindow.
+ let lastWindow = this._getMostRecentBrowserWindow();
+ let canUseLastWindow = lastWindow &&
+ !lastWindow.__SS_lastSessionWindowID;
+
+ // global data must be restored before restoreWindow is called so that
+ // it happens before observers are notified
+ this._globalState.setFromState(lastSessionState);
+
+ // Restore into windows or open new ones as needed.
+ for (let i = 0; i < lastSessionState.windows.length; i++) {
+ let winState = lastSessionState.windows[i];
+ let lastSessionWindowID = winState.__lastSessionWindowID;
+ // delete lastSessionWindowID so we don't add that to the window again
+ delete winState.__lastSessionWindowID;
+
+ // See if we can use an open window. First try one that is associated with
+ // the state we're trying to restore and then fallback to the last selected
+ // window.
+ let windowToUse = windows[lastSessionWindowID];
+ if (!windowToUse && canUseLastWindow) {
+ windowToUse = lastWindow;
+ canUseLastWindow = false;
+ }
+
+ let [canUseWindow, canOverwriteTabs] = this._prepWindowToRestoreInto(windowToUse);
+
+ // If there's a window already open that we can restore into, use that
+ if (canUseWindow) {
+ // Since we're not overwriting existing tabs, we want to merge _closedTabs,
+ // putting existing ones first. Then make sure we're respecting the max pref.
+ if (winState._closedTabs && winState._closedTabs.length) {
+ let curWinState = this._windows[windowToUse.__SSi];
+ curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
+ curWinState._closedTabs.splice(this._max_tabs_undo, curWinState._closedTabs.length);
+ }
+
+ // Restore into that window - pretend it's a followup since we'll already
+ // have a focused window.
+ //XXXzpao This is going to merge extData together (taking what was in
+ // winState over what is in the window already.
+ let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true};
+ this.restoreWindow(windowToUse, winState, options);
+ }
+ else {
+ this._openWindowWithState({ windows: [winState] });
+ }
+ }
+
+ // Merge closed windows from this session with ones from last session
+ if (lastSessionState._closedWindows) {
+ this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
+ this._capClosedWindows();
+ }
+
+ if (lastSessionState.scratchpads) {
+ ScratchpadManager.restoreSession(lastSessionState.scratchpads);
+ }
+
+ // Set data that persists between sessions
+ this._recentCrashes = lastSessionState.session &&
+ lastSessionState.session.recentCrashes || 0;
+
+ // Update the session start time using the restored session state.
+ this._updateSessionStartTime(lastSessionState);
+
+ LastSession.clear();
+ },
+
+ /**
+ * Revive a crashed tab and restore its state from before it crashed.
+ *
+ * @param aTab
+ * A <xul:tab> linked to a crashed browser. This is a no-op if the
+ * browser hasn't actually crashed, or is not associated with a tab.
+ * This function will also throw if the browser happens to be remote.
+ */
+ reviveCrashedTab(aTab) {
+ if (!aTab) {
+ throw new Error("SessionStore.reviveCrashedTab expected a tab, but got null.");
+ }
+
+ let browser = aTab.linkedBrowser;
+ if (!this._crashedBrowsers.has(browser.permanentKey)) {
+ return;
+ }
+
+ // Sanity check - the browser to be revived should not be remote
+ // at this point.
+ if (browser.isRemoteBrowser) {
+ throw new Error("SessionStore.reviveCrashedTab: " +
+ "Somehow a crashed browser is still remote.")
+ }
+
+ // We put the browser at about:blank in case the user is
+ // restoring tabs on demand. This way, the user won't see
+ // a flash of the about:tabcrashed page after selecting
+ // the revived tab.
+ aTab.removeAttribute("crashed");
+ browser.loadURI("about:blank", null, null);
+
+ let data = TabState.collect(aTab);
+ this.restoreTab(aTab, data, {
+ forceOnDemand: true,
+ });
+ },
+
+ /**
+ * Revive all crashed tabs and reset the crashed tabs count to 0.
+ */
+ reviveAllCrashedTabs() {
+ let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ while (windowsEnum.hasMoreElements()) {
+ let window = windowsEnum.getNext();
+ for (let tab of window.gBrowser.tabs) {
+ this.reviveCrashedTab(tab);
+ }
+ }
+ },
+
+ /**
+ * Navigate the given |tab| by first collecting its current state and then
+ * either changing only the index of the currently shown history entry,
+ * or restoring the exact same state again and passing the new URL to load
+ * in |loadArguments|. Use this method to seamlessly switch between pages
+ * loaded in the parent and pages loaded in the child process.
+ *
+ * This method might be called multiple times before it has finished
+ * flushing the browser tab. If that occurs, the loadArguments from
+ * the most recent call to navigateAndRestore will be used once the
+ * flush has finished.
+ */
+ navigateAndRestore(tab, loadArguments, historyIndex) {
+ let window = tab.ownerGlobal;
+ NS_ASSERT(window.__SSi, "tab's window must be tracked");
+ let browser = tab.linkedBrowser;
+
+ // Were we already waiting for a flush from a previous call to
+ // navigateAndRestore on this tab?
+ let alreadyRestoring =
+ this._remotenessChangingBrowsers.has(browser.permanentKey);
+
+ // Stash the most recent loadArguments in this WeakMap so that
+ // we know to use it when the TabStateFlusher.flush resolves.
+ this._remotenessChangingBrowsers.set(browser.permanentKey, loadArguments);
+
+ if (alreadyRestoring) {
+ // This tab was already being restored to run in the
+ // correct process. We're done here.
+ return;
+ }
+
+ // Set tab title to "Connecting..." and start the throbber to pretend we're
+ // doing something while actually waiting for data from the frame script.
+ window.gBrowser.setTabTitleLoading(tab);
+ tab.setAttribute("busy", "true");
+
+ // Flush to get the latest tab state.
+ TabStateFlusher.flush(browser).then(() => {
+ // loadArguments might have been overwritten by multiple calls
+ // to navigateAndRestore while we waited for the tab to flush,
+ // so we use the most recently stored one.
+ let recentLoadArguments =
+ this._remotenessChangingBrowsers.get(browser.permanentKey);
+ this._remotenessChangingBrowsers.delete(browser.permanentKey);
+
+ // The tab might have been closed/gone in the meantime.
+ if (tab.closing || !tab.linkedBrowser) {
+ return;
+ }
+
+ let window = tab.ownerGlobal;
+
+ // The tab or its window might be gone.
+ if (!window || !window.__SSi || window.closed) {
+ return;
+ }
+
+ let tabState = TabState.clone(tab);
+ let options = {
+ restoreImmediately: true,
+ // We want to make sure that this information is passed to restoreTab
+ // whether or not a historyIndex is passed in. Thus, we extract it from
+ // the loadArguments.
+ reloadInFreshProcess: !!recentLoadArguments.reloadInFreshProcess,
+ };
+
+ if (historyIndex >= 0) {
+ tabState.index = historyIndex + 1;
+ tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
+ } else {
+ options.loadArguments = recentLoadArguments;
+ }
+
+ // Need to reset restoring tabs.
+ if (tab.linkedBrowser.__SS_restoreState) {
+ this._resetLocalTabRestoringState(tab);
+ }
+
+ // Restore the state into the tab.
+ this.restoreTab(tab, tabState, options);
+ });
+
+ tab.linkedBrowser.__SS_restoreState = TAB_STATE_WILL_RESTORE;
+ },
+
+ /**
+ * Retrieves the latest session history information for a tab. The cached data
+ * is returned immediately, but a callback may be provided that supplies
+ * up-to-date data when or if it is available. The callback is passed a single
+ * argument with data in the same format as the return value.
+ *
+ * @param tab tab to retrieve the session history for
+ * @param updatedCallback function to call with updated data as the single argument
+ * @returns a object containing 'index' specifying the current index, and an
+ * array 'entries' containing an object for each history item.
+ */
+ getSessionHistory(tab, updatedCallback) {
+ if (updatedCallback) {
+ TabStateFlusher.flush(tab.linkedBrowser).then(() => {
+ let sessionHistory = this.getSessionHistory(tab);
+ if (sessionHistory) {
+ updatedCallback(sessionHistory);
+ }
+ });
+ }
+
+ // Don't continue if the tab was closed before TabStateFlusher.flush resolves.
+ if (tab.linkedBrowser) {
+ let tabState = TabState.collect(tab);
+ return { index: tabState.index - 1, entries: tabState.entries }
+ }
+ },
+
+ /**
+ * See if aWindow is usable for use when restoring a previous session via
+ * restoreLastSession. If usable, prepare it for use.
+ *
+ * @param aWindow
+ * the window to inspect & prepare
+ * @returns [canUseWindow, canOverwriteTabs]
+ * canUseWindow: can the window be used to restore into
+ * canOverwriteTabs: all of the current tabs are home pages and we
+ * can overwrite them
+ */
+ _prepWindowToRestoreInto: function ssi_prepWindowToRestoreInto(aWindow) {
+ if (!aWindow)
+ return [false, false];
+
+ // We might be able to overwrite the existing tabs instead of just adding
+ // the previous session's tabs to the end. This will be set if possible.
+ let canOverwriteTabs = false;
+
+ // Look at the open tabs in comparison to home pages. If all the tabs are
+ // home pages then we'll end up overwriting all of them. Otherwise we'll
+ // just close the tabs that match home pages. Tabs with the about:blank
+ // URI will always be overwritten.
+ let homePages = ["about:blank"];
+ let removableTabs = [];
+ let tabbrowser = aWindow.gBrowser;
+ let normalTabsLen = tabbrowser.tabs.length - tabbrowser._numPinnedTabs;
+ let startupPref = this._prefBranch.getIntPref("startup.page");
+ if (startupPref == 1)
+ homePages = homePages.concat(aWindow.gHomeButton.getHomePage().split("|"));
+
+ for (let i = tabbrowser._numPinnedTabs; i < tabbrowser.tabs.length; i++) {
+ let tab = tabbrowser.tabs[i];
+ if (homePages.indexOf(tab.linkedBrowser.currentURI.spec) != -1) {
+ removableTabs.push(tab);
+ }
+ }
+
+ if (tabbrowser.tabs.length == removableTabs.length) {
+ canOverwriteTabs = true;
+ }
+ else {
+ // If we're not overwriting all of the tabs, then close the home tabs.
+ for (let i = removableTabs.length - 1; i >= 0; i--) {
+ tabbrowser.removeTab(removableTabs.pop(), { animate: false });
+ }
+ }
+
+ return [true, canOverwriteTabs];
+ },
+
+ /* ........ Saving Functionality .............. */
+
+ /**
+ * Store window dimensions, visibility, sidebar
+ * @param aWindow
+ * Window reference
+ */
+ _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
+ var winData = this._windows[aWindow.__SSi];
+
+ WINDOW_ATTRIBUTES.forEach(function(aAttr) {
+ winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
+ }, this);
+
+ var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
+ return aWindow[aItem] && !aWindow[aItem].visible;
+ });
+ if (hidden.length != 0)
+ winData.hidden = hidden.join(",");
+ else if (winData.hidden)
+ delete winData.hidden;
+
+ var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
+ if (sidebar)
+ winData.sidebar = sidebar;
+ else if (winData.sidebar)
+ delete winData.sidebar;
+ },
+
+ /**
+ * gather session data as object
+ * @param aUpdateAll
+ * Bool update all windows
+ * @returns object
+ */
+ getCurrentState: function (aUpdateAll) {
+ this._handleClosedWindows();
+
+ var activeWindow = this._getMostRecentBrowserWindow();
+
+ TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
+ if (RunState.isRunning) {
+ // update the data for all windows with activities since the last save operation
+ this._forEachBrowserWindow(function(aWindow) {
+ if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
+ return;
+ if (aUpdateAll || DirtyWindows.has(aWindow) || aWindow == activeWindow) {
+ this._collectWindowData(aWindow);
+ }
+ else { // always update the window features (whose change alone never triggers a save operation)
+ this._updateWindowFeatures(aWindow);
+ }
+ });
+ DirtyWindows.clear();
+ }
+ TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
+
+ // An array that at the end will hold all current window data.
+ var total = [];
+ // The ids of all windows contained in 'total' in the same order.
+ var ids = [];
+ // The number of window that are _not_ popups.
+ var nonPopupCount = 0;
+ var ix;
+
+ // collect the data for all windows
+ for (ix in this._windows) {
+ if (this._windows[ix]._restoring) // window data is still in _statesToRestore
+ continue;
+ total.push(this._windows[ix]);
+ ids.push(ix);
+ if (!this._windows[ix].isPopup)
+ nonPopupCount++;
+ }
+
+ TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
+ SessionCookies.update(total);
+ TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
+
+ // collect the data for all windows yet to be restored
+ for (ix in this._statesToRestore) {
+ for (let winData of this._statesToRestore[ix].windows) {
+ total.push(winData);
+ if (!winData.isPopup)
+ nonPopupCount++;
+ }
+ }
+
+ // shallow copy this._closedWindows to preserve current state
+ let lastClosedWindowsCopy = this._closedWindows.slice();
+
+ if (AppConstants.platform != "macosx") {
+ // If no non-popup browser window remains open, return the state of the last
+ // closed window(s). We only want to do this when we're actually "ending"
+ // the session.
+ //XXXzpao We should do this for _restoreLastWindow == true, but that has
+ // its own check for popups. c.f. bug 597619
+ if (nonPopupCount == 0 && lastClosedWindowsCopy.length > 0 &&
+ RunState.isQuitting) {
+ // prepend the last non-popup browser window, so that if the user loads more tabs
+ // at startup we don't accidentally add them to a popup window
+ do {
+ total.unshift(lastClosedWindowsCopy.shift())
+ } while (total[0].isPopup && lastClosedWindowsCopy.length > 0)
+ }
+ }
+
+ if (activeWindow) {
+ this.activeWindowSSiCache = activeWindow.__SSi || "";
+ }
+ ix = ids.indexOf(this.activeWindowSSiCache);
+ // We don't want to restore focus to a minimized window or a window which had all its
+ // tabs stripped out (doesn't exist).
+ if (ix != -1 && total[ix] && total[ix].sizemode == "minimized")
+ ix = -1;
+
+ let session = {
+ lastUpdate: Date.now(),
+ startTime: this._sessionStartTime,
+ recentCrashes: this._recentCrashes
+ };
+
+ let state = {
+ version: ["sessionrestore", FORMAT_VERSION],
+ windows: total,
+ selectedWindow: ix + 1,
+ _closedWindows: lastClosedWindowsCopy,
+ session: session,
+ global: this._globalState.getState()
+ };
+
+ if (Cu.isModuleLoaded("resource://devtools/client/scratchpad/scratchpad-manager.jsm")) {
+ // get open Scratchpad window states too
+ let scratchpads = ScratchpadManager.getSessionState();
+ if (scratchpads && scratchpads.length) {
+ state.scratchpads = scratchpads;
+ }
+ }
+
+ // Persist the last session if we deferred restoring it
+ if (LastSession.canRestore) {
+ state.lastSessionState = LastSession.getState();
+ }
+
+ // If we were called by the SessionSaver and started with only a private
+ // window we want to pass the deferred initial state to not lose the
+ // previous session.
+ if (this._deferredInitialState) {
+ state.deferredInitialState = this._deferredInitialState;
+ }
+
+ return state;
+ },
+
+ /**
+ * serialize session data for a window
+ * @param aWindow
+ * Window reference
+ * @returns string
+ */
+ _getWindowState: function ssi_getWindowState(aWindow) {
+ if (!this._isWindowLoaded(aWindow))
+ return this._statesToRestore[aWindow.__SS_restoreID];
+
+ if (RunState.isRunning) {
+ this._collectWindowData(aWindow);
+ }
+
+ let windows = [this._windows[aWindow.__SSi]];
+ SessionCookies.update(windows);
+
+ return { windows: windows };
+ },
+
+ /**
+ * Gathers data about a window and its tabs, and updates its
+ * entry in this._windows.
+ *
+ * @param aWindow
+ * Window references.
+ * @returns a Map mapping the browser tabs from aWindow to the tab
+ * entry that was put into the window data in this._windows.
+ */
+ _collectWindowData: function ssi_collectWindowData(aWindow) {
+ let tabMap = new Map();
+
+ if (!this._isWindowLoaded(aWindow))
+ return tabMap;
+
+ let tabbrowser = aWindow.gBrowser;
+ let tabs = tabbrowser.tabs;
+ let winData = this._windows[aWindow.__SSi];
+ let tabsData = winData.tabs = [];
+
+ // update the internal state data for this window
+ for (let tab of tabs) {
+ let tabData = TabState.collect(tab);
+ tabMap.set(tab, tabData);
+ tabsData.push(tabData);
+ }
+ winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
+
+ this._updateWindowFeatures(aWindow);
+
+ // Make sure we keep __SS_lastSessionWindowID around for cases like entering
+ // or leaving PB mode.
+ if (aWindow.__SS_lastSessionWindowID)
+ this._windows[aWindow.__SSi].__lastSessionWindowID =
+ aWindow.__SS_lastSessionWindowID;
+
+ DirtyWindows.remove(aWindow);
+ return tabMap;
+ },
+
+ /* ........ Restoring Functionality .............. */
+
+ /**
+ * restore features to a single window
+ * @param aWindow
+ * Window reference to the window to use for restoration
+ * @param winData
+ * JS object
+ * @param aOptions
+ * {overwriteTabs: true} to overwrite existing tabs w/ new ones
+ * {isFollowUp: true} if this is not the restoration of the 1st window
+ * {firstWindow: true} if this is the first non-private window we're
+ * restoring in this session, that might open an
+ * external link as well
+ */
+ restoreWindow: function ssi_restoreWindow(aWindow, winData, aOptions = {}) {
+ let overwriteTabs = aOptions && aOptions.overwriteTabs;
+ let isFollowUp = aOptions && aOptions.isFollowUp;
+ let firstWindow = aOptions && aOptions.firstWindow;
+
+ if (isFollowUp) {
+ this.windowToFocus = aWindow;
+ }
+
+ // initialize window if necessary
+ if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
+ this.onLoad(aWindow);
+
+ TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
+
+ // We're not returning from this before we end up calling restoreTabs
+ // for this window, so make sure we send the SSWindowStateBusy event.
+ this._setWindowStateBusy(aWindow);
+
+ if (!winData.tabs) {
+ winData.tabs = [];
+ }
+
+ // don't restore a single blank tab when we've had an external
+ // URL passed in for loading at startup (cf. bug 357419)
+ else if (firstWindow && !overwriteTabs && winData.tabs.length == 1 &&
+ (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
+ winData.tabs = [];
+ }
+
+ var tabbrowser = aWindow.gBrowser;
+ var openTabCount = overwriteTabs ? tabbrowser.browsers.length : -1;
+ var newTabCount = winData.tabs.length;
+ var tabs = [];
+
+ // disable smooth scrolling while adding, moving, removing and selecting tabs
+ var tabstrip = tabbrowser.tabContainer.mTabstrip;
+ var smoothScroll = tabstrip.smoothScroll;
+ tabstrip.smoothScroll = false;
+
+ // unpin all tabs to ensure they are not reordered in the next loop
+ if (overwriteTabs) {
+ for (let t = tabbrowser._numPinnedTabs - 1; t > -1; t--)
+ tabbrowser.unpinTab(tabbrowser.tabs[t]);
+ }
+
+ // We need to keep track of the initially open tabs so that they
+ // can be moved to the end of the restored tabs.
+ let initialTabs = [];
+ if (!overwriteTabs && firstWindow) {
+ initialTabs = Array.slice(tabbrowser.tabs);
+ }
+
+ // make sure that the selected tab won't be closed in order to
+ // prevent unnecessary flickering
+ if (overwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
+ tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
+
+ let numVisibleTabs = 0;
+
+ for (var t = 0; t < newTabCount; t++) {
+ // When trying to restore into existing tab, we also take the userContextId
+ // into account if present.
+ let userContextId = winData.tabs[t].userContextId;
+ let reuseExisting = t < openTabCount &&
+ (tabbrowser.tabs[t].getAttribute("usercontextid") == (userContextId || ""));
+ // If the tab is pinned, then we'll be loading it right away, and
+ // there's no need to cause a remoteness flip by loading it initially
+ // non-remote.
+ let forceNotRemote = !winData.tabs[t].pinned;
+ let tab = reuseExisting ? tabbrowser.tabs[t] :
+ tabbrowser.addTab("about:blank",
+ {skipAnimation: true,
+ forceNotRemote,
+ userContextId});
+
+ // If we inserted a new tab because the userContextId didn't match with the
+ // open tab, even though `t < openTabCount`, we need to remove that open tab
+ // and put the newly added tab in its place.
+ if (!reuseExisting && t < openTabCount) {
+ tabbrowser.removeTab(tabbrowser.tabs[t]);
+ tabbrowser.moveTabTo(tab, t);
+ }
+
+ tabs.push(tab);
+
+ if (winData.tabs[t].pinned)
+ tabbrowser.pinTab(tabs[t]);
+
+ if (winData.tabs[t].hidden) {
+ tabbrowser.hideTab(tabs[t]);
+ }
+ else {
+ tabbrowser.showTab(tabs[t]);
+ numVisibleTabs++;
+ }
+
+ if (!!winData.tabs[t].muted != tabs[t].linkedBrowser.audioMuted) {
+ tabs[t].toggleMuteAudio(winData.tabs[t].muteReason);
+ }
+ }
+
+ if (!overwriteTabs && firstWindow) {
+ // Move the originally open tabs to the end
+ let endPosition = tabbrowser.tabs.length - 1;
+ for (let i = 0; i < initialTabs.length; i++) {
+ tabbrowser.moveTabTo(initialTabs[i], endPosition);
+ }
+ }
+
+ // if all tabs to be restored are hidden, make the first one visible
+ if (!numVisibleTabs && winData.tabs.length) {
+ winData.tabs[0].hidden = false;
+ tabbrowser.showTab(tabs[0]);
+ }
+
+ // If overwriting tabs, we want to reset each tab's "restoring" state. Since
+ // we're overwriting those tabs, they should no longer be restoring. The
+ // tabs will be rebuilt and marked if they need to be restored after loading
+ // state (in restoreTabs).
+ if (overwriteTabs) {
+ for (let i = 0; i < tabbrowser.tabs.length; i++) {
+ let tab = tabbrowser.tabs[i];
+ if (tabbrowser.browsers[i].__SS_restoreState)
+ this._resetTabRestoringState(tab);
+ }
+ }
+
+ // We want to correlate the window with data from the last session, so
+ // assign another id if we have one. Otherwise clear so we don't do
+ // anything with it.
+ delete aWindow.__SS_lastSessionWindowID;
+ if (winData.__lastSessionWindowID)
+ aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
+
+ // when overwriting tabs, remove all superflous ones
+ if (overwriteTabs && newTabCount < openTabCount) {
+ Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
+ .forEach(tabbrowser.removeTab, tabbrowser);
+ }
+
+ if (overwriteTabs) {
+ this.restoreWindowFeatures(aWindow, winData);
+ delete this._windows[aWindow.__SSi].extData;
+ }
+ if (winData.cookies) {
+ SessionCookies.restore(winData.cookies);
+ }
+ if (winData.extData) {
+ if (!this._windows[aWindow.__SSi].extData) {
+ this._windows[aWindow.__SSi].extData = {};
+ }
+ for (var key in winData.extData) {
+ this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
+ }
+ }
+
+ let newClosedTabsData = winData._closedTabs || [];
+
+ if (overwriteTabs || firstWindow) {
+ // Overwrite existing closed tabs data when overwriteTabs=true
+ // or we're the first window to be restored.
+ this._windows[aWindow.__SSi]._closedTabs = newClosedTabsData;
+ } else if (this._max_tabs_undo > 0) {
+ // If we merge tabs, we also want to merge closed tabs data. We'll assume
+ // the restored tabs were closed more recently and append the current list
+ // of closed tabs to the new one...
+ newClosedTabsData =
+ newClosedTabsData.concat(this._windows[aWindow.__SSi]._closedTabs);
+
+ // ... and make sure that we don't exceed the max number of closed tabs
+ // we can restore.
+ this._windows[aWindow.__SSi]._closedTabs =
+ newClosedTabsData.slice(0, this._max_tabs_undo);
+ }
+
+ // Restore tabs, if any.
+ if (winData.tabs.length) {
+ this.restoreTabs(aWindow, tabs, winData.tabs,
+ (overwriteTabs ? (parseInt(winData.selected || "1")) : 0));
+ }
+
+ // set smoothScroll back to the original value
+ tabstrip.smoothScroll = smoothScroll;
+
+ TelemetryStopwatch.finish("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
+
+ this._setWindowStateReady(aWindow);
+
+ this._sendWindowRestoredNotification(aWindow);
+
+ Services.obs.notifyObservers(aWindow, NOTIFY_SINGLE_WINDOW_RESTORED, "");
+
+ this._sendRestoreCompletedNotifications();
+ },
+
+ /**
+ * Restore multiple windows using the provided state.
+ * @param aWindow
+ * Window reference to the first window to use for restoration.
+ * Additionally required windows will be opened.
+ * @param aState
+ * JS object or JSON string
+ * @param aOptions
+ * {overwriteTabs: true} to overwrite existing tabs w/ new ones
+ * {isFollowUp: true} if this is not the restoration of the 1st window
+ * {firstWindow: true} if this is the first non-private window we're
+ * restoring in this session, that might open an
+ * external link as well
+ */
+ restoreWindows: function ssi_restoreWindows(aWindow, aState, aOptions = {}) {
+ let isFollowUp = aOptions && aOptions.isFollowUp;
+
+ if (isFollowUp) {
+ this.windowToFocus = aWindow;
+ }
+
+ // initialize window if necessary
+ if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
+ this.onLoad(aWindow);
+
+ let root;
+ try {
+ root = (typeof aState == "string") ? JSON.parse(aState) : aState;
+ }
+ catch (ex) { // invalid state object - don't restore anything
+ debug(ex);
+ this._sendRestoreCompletedNotifications();
+ return;
+ }
+
+ // Restore closed windows if any.
+ if (root._closedWindows) {
+ this._closedWindows = root._closedWindows;
+ }
+
+ // We're done here if there are no windows.
+ if (!root.windows || !root.windows.length) {
+ this._sendRestoreCompletedNotifications();
+ return;
+ }
+
+ if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
+ root.selectedWindow = 0;
+ }
+
+ // open new windows for all further window entries of a multi-window session
+ // (unless they don't contain any tab data)
+ let winData;
+ for (var w = 1; w < root.windows.length; w++) {
+ winData = root.windows[w];
+ if (winData && winData.tabs && winData.tabs[0]) {
+ var window = this._openWindowWithState({ windows: [winData] });
+ if (w == root.selectedWindow - 1) {
+ this.windowToFocus = window;
+ }
+ }
+ }
+
+ this.restoreWindow(aWindow, root.windows[0], aOptions);
+
+ if (aState.scratchpads) {
+ ScratchpadManager.restoreSession(aState.scratchpads);
+ }
+ },
+
+ /**
+ * Manage history restoration for a window
+ * @param aWindow
+ * Window to restore the tabs into
+ * @param aTabs
+ * Array of tab references
+ * @param aTabData
+ * Array of tab data
+ * @param aSelectTab
+ * Index of the tab to select. This is a 1-based index where "1"
+ * indicates the first tab should be selected, and "0" indicates that
+ * the currently selected tab will not be changed.
+ */
+ restoreTabs(aWindow, aTabs, aTabData, aSelectTab) {
+ var tabbrowser = aWindow.gBrowser;
+
+ if (!this._isWindowLoaded(aWindow)) {
+ // from now on, the data will come from the actual window
+ delete this._statesToRestore[aWindow.__SS_restoreID];
+ delete aWindow.__SS_restoreID;
+ delete this._windows[aWindow.__SSi]._restoring;
+ }
+
+ let numTabsToRestore = aTabs.length;
+ let numTabsInWindow = tabbrowser.tabs.length;
+ let tabsDataArray = this._windows[aWindow.__SSi].tabs;
+
+ // Update the window state in case we shut down without being notified.
+ // Individual tab states will be taken care of by restoreTab() below.
+ if (numTabsInWindow == numTabsToRestore) {
+ // Remove all previous tab data.
+ tabsDataArray.length = 0;
+ } else {
+ // Remove all previous tab data except tabs that should not be overriden.
+ tabsDataArray.splice(numTabsInWindow - numTabsToRestore);
+ }
+
+ // Let the tab data array have the right number of slots.
+ tabsDataArray.length = numTabsInWindow;
+
+ // If provided, set the selected tab.
+ if (aSelectTab > 0 && aSelectTab <= aTabs.length) {
+ tabbrowser.selectedTab = aTabs[aSelectTab - 1];
+
+ // Update the window state in case we shut down without being notified.
+ this._windows[aWindow.__SSi].selected = aSelectTab;
+ }
+
+ // Restore all tabs.
+ for (let t = 0; t < aTabs.length; t++) {
+ this.restoreTab(aTabs[t], aTabData[t]);
+ }
+ },
+
+ // Restores the given tab state for a given tab.
+ restoreTab(tab, tabData, options = {}) {
+ NS_ASSERT(!tab.linkedBrowser.__SS_restoreState,
+ "must reset tab before calling restoreTab()");
+
+ let restoreImmediately = options.restoreImmediately;
+ let loadArguments = options.loadArguments;
+ let browser = tab.linkedBrowser;
+ let window = tab.ownerGlobal;
+ let tabbrowser = window.gBrowser;
+ let forceOnDemand = options.forceOnDemand;
+ let reloadInFreshProcess = options.reloadInFreshProcess;
+
+ let willRestoreImmediately = restoreImmediately ||
+ tabbrowser.selectedBrowser == browser ||
+ loadArguments;
+
+ if (!willRestoreImmediately && !forceOnDemand) {
+ TabRestoreQueue.add(tab);
+ }
+
+ this._maybeUpdateBrowserRemoteness({ tabbrowser, tab,
+ willRestoreImmediately });
+
+ // Increase the busy state counter before modifying the tab.
+ this._setWindowStateBusy(window);
+
+ // It's important to set the window state to dirty so that
+ // we collect their data for the first time when saving state.
+ DirtyWindows.add(window);
+
+ // In case we didn't collect/receive data for any tabs yet we'll have to
+ // fill the array with at least empty tabData objects until |_tPos| or
+ // we'll end up with |null| entries.
+ for (let otherTab of Array.slice(tabbrowser.tabs, 0, tab._tPos)) {
+ let emptyState = {entries: [], lastAccessed: otherTab.lastAccessed};
+ this._windows[window.__SSi].tabs.push(emptyState);
+ }
+
+ // Update the tab state in case we shut down without being notified.
+ this._windows[window.__SSi].tabs[tab._tPos] = tabData;
+
+ // Prepare the tab so that it can be properly restored. We'll pin/unpin
+ // and show/hide tabs as necessary. We'll also attach a copy of the tab's
+ // data in case we close it before it's been restored.
+ if (tabData.pinned) {
+ tabbrowser.pinTab(tab);
+ } else {
+ tabbrowser.unpinTab(tab);
+ }
+
+ if (tabData.hidden) {
+ tabbrowser.hideTab(tab);
+ } else {
+ tabbrowser.showTab(tab);
+ }
+
+ if (!!tabData.muted != browser.audioMuted) {
+ tab.toggleMuteAudio(tabData.muteReason);
+ }
+
+ if (tabData.lastAccessed) {
+ tab.updateLastAccessed(tabData.lastAccessed);
+ }
+
+ if ("attributes" in tabData) {
+ // Ensure that we persist tab attributes restored from previous sessions.
+ Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
+ }
+
+ if (!tabData.entries) {
+ tabData.entries = [];
+ }
+ if (tabData.extData) {
+ tab.__SS_extdata = Cu.cloneInto(tabData.extData, {});
+ } else {
+ delete tab.__SS_extdata;
+ }
+
+ // Tab is now open.
+ delete tabData.closedAt;
+
+ // Ensure the index is in bounds.
+ let activeIndex = (tabData.index || tabData.entries.length) - 1;
+ activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
+ activeIndex = Math.max(activeIndex, 0);
+
+ // Save the index in case we updated it above.
+ tabData.index = activeIndex + 1;
+
+ // Start a new epoch to discard all frame script messages relating to a
+ // previous epoch. All async messages that are still on their way to chrome
+ // will be ignored and don't override any tab data set when restoring.
+ let epoch = this.startNextEpoch(browser);
+
+ // keep the data around to prevent dataloss in case
+ // a tab gets closed before it's been properly restored
+ browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
+ browser.setAttribute("pending", "true");
+ tab.setAttribute("pending", "true");
+
+ // If we're restoring this tab, it certainly shouldn't be in
+ // the ignored set anymore.
+ this._crashedBrowsers.delete(browser.permanentKey);
+
+ // Update the persistent tab state cache with |tabData| information.
+ TabStateCache.update(browser, {
+ history: {entries: tabData.entries, index: tabData.index},
+ scroll: tabData.scroll || null,
+ storage: tabData.storage || null,
+ formdata: tabData.formdata || null,
+ disallow: tabData.disallow || null,
+ pageStyle: tabData.pageStyle || null,
+
+ // This information is only needed until the tab has finished restoring.
+ // When that's done it will be removed from the cache and we always
+ // collect it in TabState._collectBaseTabData().
+ image: tabData.image || "",
+ iconLoadingPrincipal: tabData.iconLoadingPrincipal || null,
+ userTypedValue: tabData.userTypedValue || "",
+ userTypedClear: tabData.userTypedClear || 0
+ });
+
+ browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
+ {tabData: tabData, epoch: epoch, loadArguments});
+
+ // Restore tab attributes.
+ if ("attributes" in tabData) {
+ TabAttributes.set(tab, tabData.attributes);
+ }
+
+ // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
+ // it ensures each window will have its selected tab loaded.
+ if (willRestoreImmediately) {
+ this.restoreTabContent(tab, loadArguments, reloadInFreshProcess);
+ } else if (!forceOnDemand) {
+ this.restoreNextTab();
+ }
+
+ // Decrease the busy state counter after we're done.
+ this._setWindowStateReady(window);
+ },
+
+ /**
+ * Kicks off restoring the given tab.
+ *
+ * @param aTab
+ * the tab to restore
+ * @param aLoadArguments
+ * optional load arguments used for loadURI()
+ * @param aReloadInFreshProcess
+ * true if we want to reload into a fresh process
+ */
+ restoreTabContent: function (aTab, aLoadArguments = null, aReloadInFreshProcess = false) {
+ if (aTab.hasAttribute("customizemode") && !aLoadArguments) {
+ return;
+ }
+
+ let browser = aTab.linkedBrowser;
+ let window = aTab.ownerGlobal;
+ let tabbrowser = window.gBrowser;
+ let tabData = TabState.clone(aTab);
+ let activeIndex = tabData.index - 1;
+ let activePageData = tabData.entries[activeIndex] || null;
+ let uri = activePageData ? activePageData.url || null : null;
+ if (aLoadArguments) {
+ uri = aLoadArguments.uri;
+ if (aLoadArguments.userContextId) {
+ browser.setAttribute("usercontextid", aLoadArguments.userContextId);
+ }
+ }
+
+ // We have to mark this tab as restoring first, otherwise
+ // the "pending" attribute will be applied to the linked
+ // browser, which removes it from the display list. We cannot
+ // flip the remoteness of any browser that is not being displayed.
+ this.markTabAsRestoring(aTab);
+
+ let isRemotenessUpdate = false;
+ if (aReloadInFreshProcess) {
+ isRemotenessUpdate = tabbrowser.switchBrowserIntoFreshProcess(browser);
+ } else {
+ isRemotenessUpdate = tabbrowser.updateBrowserRemotenessByURL(browser, uri);
+ }
+
+ if (isRemotenessUpdate) {
+ // We updated the remoteness, so we need to send the history down again.
+ //
+ // Start a new epoch to discard all frame script messages relating to a
+ // previous epoch. All async messages that are still on their way to chrome
+ // will be ignored and don't override any tab data set when restoring.
+ let epoch = this.startNextEpoch(browser);
+
+ browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory", {
+ tabData: tabData,
+ epoch: epoch,
+ loadArguments: aLoadArguments,
+ isRemotenessUpdate,
+ });
+
+ }
+
+ // If the restored browser wants to show view source content, start up a
+ // view source browser that will load the required frame script.
+ if (uri && ViewSourceBrowser.isViewSource(uri)) {
+ new ViewSourceBrowser(browser);
+ }
+
+ browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
+ {loadArguments: aLoadArguments, isRemotenessUpdate});
+ },
+
+ /**
+ * Marks a given pending tab as restoring.
+ *
+ * @param aTab
+ * the pending tab to mark as restoring
+ */
+ markTabAsRestoring(aTab) {
+ let browser = aTab.linkedBrowser;
+ if (browser.__SS_restoreState != TAB_STATE_NEEDS_RESTORE) {
+ throw new Error("Given tab is not pending.");
+ }
+
+ // Make sure that this tab is removed from the priority queue.
+ TabRestoreQueue.remove(aTab);
+
+ // Increase our internal count.
+ this._tabsRestoringCount++;
+
+ // Set this tab's state to restoring
+ browser.__SS_restoreState = TAB_STATE_RESTORING;
+ browser.removeAttribute("pending");
+ aTab.removeAttribute("pending");
+ },
+
+ /**
+ * This _attempts_ to restore the next available tab. If the restore fails,
+ * then we will attempt the next one.
+ * There are conditions where this won't do anything:
+ * if we're in the process of quitting
+ * if there are no tabs to restore
+ * if we have already reached the limit for number of tabs to restore
+ */
+ restoreNextTab: function ssi_restoreNextTab() {
+ // If we call in here while quitting, we don't actually want to do anything
+ if (RunState.isQuitting)
+ return;
+
+ // Don't exceed the maximum number of concurrent tab restores.
+ if (this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
+ return;
+
+ let tab = TabRestoreQueue.shift();
+ if (tab) {
+ this.restoreTabContent(tab);
+ }
+ },
+
+ /**
+ * Restore visibility and dimension features to a window
+ * @param aWindow
+ * Window reference
+ * @param aWinData
+ * Object containing session data for the window
+ */
+ restoreWindowFeatures: function ssi_restoreWindowFeatures(aWindow, aWinData) {
+ var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
+ WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
+ aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
+ });
+
+ if (aWinData.isPopup) {
+ this._windows[aWindow.__SSi].isPopup = true;
+ if (aWindow.gURLBar) {
+ aWindow.gURLBar.readOnly = true;
+ aWindow.gURLBar.setAttribute("enablehistory", "false");
+ }
+ }
+ else {
+ delete this._windows[aWindow.__SSi].isPopup;
+ if (aWindow.gURLBar) {
+ aWindow.gURLBar.readOnly = false;
+ aWindow.gURLBar.setAttribute("enablehistory", "true");
+ }
+ }
+
+ var _this = this;
+ aWindow.setTimeout(function() {
+ _this.restoreDimensions.apply(_this, [aWindow,
+ +(aWinData.width || 0),
+ +(aWinData.height || 0),
+ "screenX" in aWinData ? +aWinData.screenX : NaN,
+ "screenY" in aWinData ? +aWinData.screenY : NaN,
+ aWinData.sizemode || "", aWinData.sidebar || ""]);
+ }, 0);
+ },
+
+ /**
+ * Restore a window's dimensions
+ * @param aWidth
+ * Window width
+ * @param aHeight
+ * Window height
+ * @param aLeft
+ * Window left
+ * @param aTop
+ * Window top
+ * @param aSizeMode
+ * Window size mode (eg: maximized)
+ * @param aSidebar
+ * Sidebar command
+ */
+ restoreDimensions: function ssi_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
+ var win = aWindow;
+ var _this = this;
+ function win_(aName) { return _this._getWindowDimension(win, aName); }
+
+ // find available space on the screen where this window is being placed
+ let screen = gScreenManager.screenForRect(aLeft, aTop, aWidth, aHeight);
+ if (screen) {
+ let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
+ screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth, screenHeight);
+ // screenX/Y are based on the origin of the screen's desktop-pixel coordinate space
+ let screenLeftCss = screenLeft.value;
+ let screenTopCss = screenTop.value;
+ // convert screen's device pixel dimensions to CSS px dimensions
+ screen.GetAvailRect(screenLeft, screenTop, screenWidth, screenHeight);
+ let cssToDevScale = screen.defaultCSSScaleFactor;
+ let screenRightCss = screenLeftCss + screenWidth.value / cssToDevScale;
+ let screenBottomCss = screenTopCss + screenHeight.value / cssToDevScale;
+
+ // Pull the window within the screen's bounds (allowing a little slop
+ // for windows that may be deliberately placed with their border off-screen
+ // as when Win10 "snaps" a window to the left/right edge -- bug 1276516).
+ // First, ensure the left edge is large enough...
+ if (aLeft < screenLeftCss - SCREEN_EDGE_SLOP) {
+ aLeft = screenLeftCss;
+ }
+ // Then check the resulting right edge, and reduce it if necessary.
+ let right = aLeft + aWidth;
+ if (right > screenRightCss + SCREEN_EDGE_SLOP) {
+ right = screenRightCss;
+ // See if we can move the left edge leftwards to maintain width.
+ if (aLeft > screenLeftCss) {
+ aLeft = Math.max(right - aWidth, screenLeftCss);
+ }
+ }
+ // Finally, update aWidth to account for the adjusted left and right edges.
+ aWidth = right - aLeft;
+
+ // And do the same in the vertical dimension.
+ if (aTop < screenTopCss - SCREEN_EDGE_SLOP) {
+ aTop = screenTopCss;
+ }
+ let bottom = aTop + aHeight;
+ if (bottom > screenBottomCss + SCREEN_EDGE_SLOP) {
+ bottom = screenBottomCss;
+ if (aTop > screenTopCss) {
+ aTop = Math.max(bottom - aHeight, screenTopCss);
+ }
+ }
+ aHeight = bottom - aTop;
+ }
+
+ // only modify those aspects which aren't correct yet
+ if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
+ aWindow.moveTo(aLeft, aTop);
+ }
+ if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
+ // Don't resize the window if it's currently maximized and we would
+ // maximize it again shortly after.
+ if (aSizeMode != "maximized" || win_("sizemode") != "maximized") {
+ aWindow.resizeTo(aWidth, aHeight);
+ }
+ }
+ if (aSizeMode && win_("sizemode") != aSizeMode)
+ {
+ switch (aSizeMode)
+ {
+ case "maximized":
+ aWindow.maximize();
+ break;
+ case "minimized":
+ aWindow.minimize();
+ break;
+ case "normal":
+ aWindow.restore();
+ break;
+ }
+ }
+ var sidebar = aWindow.document.getElementById("sidebar-box");
+ if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
+ aWindow.SidebarUI.show(aSidebar);
+ }
+ // since resizing/moving a window brings it to the foreground,
+ // we might want to re-focus the last focused window
+ if (this.windowToFocus) {
+ this.windowToFocus.focus();
+ }
+ },
+
+ /* ........ Disk Access .............. */
+
+ /**
+ * Save the current session state to disk, after a delay.
+ *
+ * @param aWindow (optional)
+ * Will mark the given window as dirty so that we will recollect its
+ * data before we start writing.
+ */
+ saveStateDelayed: function (aWindow = null) {
+ if (aWindow) {
+ DirtyWindows.add(aWindow);
+ }
+
+ SessionSaver.runDelayed();
+ },
+
+ /* ........ Auxiliary Functions .............. */
+
+ /**
+ * Determines whether or not a tab that is being restored needs
+ * to have its remoteness flipped first.
+ *
+ * @param (object) with the following properties:
+ *
+ * tabbrowser (<xul:tabbrowser>):
+ * The tabbrowser that the browser belongs to.
+ *
+ * tab (<xul:tab>):
+ * The tab being restored
+ *
+ * willRestoreImmediately (bool):
+ * true if the tab is going to have its content
+ * restored immediately by the caller.
+ *
+ */
+ _maybeUpdateBrowserRemoteness({ tabbrowser, tab,
+ willRestoreImmediately }) {
+ // If the browser we're attempting to restore happens to be
+ // remote, we need to flip it back to non-remote if it's going
+ // to go into the pending background tab state. This is to make
+ // sure that a background tab can't crash if it hasn't yet
+ // been restored.
+ //
+ // Normally, when a window is restored, the tabs that SessionStore
+ // inserts are non-remote - but the initial browser is, by default,
+ // remote, so this check and flip covers this case. The other case
+ // is when window state is overwriting the state of an existing
+ // window with some remote tabs.
+ let browser = tab.linkedBrowser;
+
+ // There are two ways that a tab might start restoring its content
+ // very soon - either the caller is going to restore the content
+ // immediately, or the TabRestoreQueue is set up so that the tab
+ // content is going to be restored in the very near future. In
+ // either case, we don't want to flip remoteness, since the browser
+ // will soon be loading content.
+ let willRestore = willRestoreImmediately ||
+ TabRestoreQueue.willRestoreSoon(tab);
+
+ if (browser.isRemoteBrowser && !willRestore) {
+ tabbrowser.updateBrowserRemoteness(browser, false);
+ }
+ },
+
+ /**
+ * Update the session start time and send a telemetry measurement
+ * for the number of days elapsed since the session was started.
+ *
+ * @param state
+ * The session state.
+ */
+ _updateSessionStartTime: function ssi_updateSessionStartTime(state) {
+ // Attempt to load the session start time from the session state
+ if (state.session && state.session.startTime) {
+ this._sessionStartTime = state.session.startTime;
+ }
+ },
+
+ /**
+ * call a callback for all currently opened browser windows
+ * (might miss the most recent one)
+ * @param aFunc
+ * Callback each window is passed to
+ */
+ _forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) {
+ var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+
+ while (windowsEnum.hasMoreElements()) {
+ var window = windowsEnum.getNext();
+ if (window.__SSi && !window.closed) {
+ aFunc.call(this, window);
+ }
+ }
+ },
+
+ /**
+ * Returns most recent window
+ * @returns Window reference
+ */
+ _getMostRecentBrowserWindow: function ssi_getMostRecentBrowserWindow() {
+ return RecentWindow.getMostRecentBrowserWindow({ allowPopups: true });
+ },
+
+ /**
+ * Calls onClose for windows that are determined to be closed but aren't
+ * destroyed yet, which would otherwise cause getBrowserState and
+ * setBrowserState to treat them as open windows.
+ */
+ _handleClosedWindows: function ssi_handleClosedWindows() {
+ var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+
+ while (windowsEnum.hasMoreElements()) {
+ var window = windowsEnum.getNext();
+ if (window.closed) {
+ this.onClose(window);
+ }
+ }
+ },
+
+ /**
+ * open a new browser window for a given session state
+ * called when restoring a multi-window session
+ * @param aState
+ * Object containing session data
+ */
+ _openWindowWithState: function ssi_openWindowWithState(aState) {
+ var argString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ argString.data = "";
+
+ // Build feature string
+ let features = "chrome,dialog=no,macsuppressanimation,all";
+ let winState = aState.windows[0];
+ WINDOW_ATTRIBUTES.forEach(function(aFeature) {
+ // Use !isNaN as an easy way to ignore sizemode and check for numbers
+ if (aFeature in winState && !isNaN(winState[aFeature]))
+ features += "," + aFeature + "=" + winState[aFeature];
+ });
+
+ if (winState.isPrivate) {
+ features += ",private";
+ }
+
+ var window =
+ Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
+ "_blank", features, argString);
+
+ do {
+ var ID = "window" + Math.random();
+ } while (ID in this._statesToRestore);
+ this._statesToRestore[(window.__SS_restoreID = ID)] = aState;
+
+ return window;
+ },
+
+ /**
+ * Whether or not to resume session, if not recovering from a crash.
+ * @returns bool
+ */
+ _doResumeSession: function ssi_doResumeSession() {
+ return this._prefBranch.getIntPref("startup.page") == 3 ||
+ this._prefBranch.getBoolPref("sessionstore.resume_session_once");
+ },
+
+ /**
+ * whether the user wants to load any other page at startup
+ * (except the homepage) - needed for determining whether to overwrite the current tabs
+ * C.f.: nsBrowserContentHandler's defaultArgs implementation.
+ * @returns bool
+ */
+ _isCmdLineEmpty: function ssi_isCmdLineEmpty(aWindow, aState) {
+ var pinnedOnly = aState.windows &&
+ aState.windows.every(win =>
+ win.tabs.every(tab => tab.pinned));
+
+ let hasFirstArgument = aWindow.arguments && aWindow.arguments[0];
+ if (!pinnedOnly) {
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"].
+ getService(Ci.nsIBrowserHandler).defaultArgs;
+ if (aWindow.arguments &&
+ aWindow.arguments[0] &&
+ aWindow.arguments[0] == defaultArgs)
+ hasFirstArgument = false;
+ }
+
+ return !hasFirstArgument;
+ },
+
+ /**
+ * on popup windows, the XULWindow's attributes seem not to be set correctly
+ * we use thus JSDOMWindow attributes for sizemode and normal window attributes
+ * (and hope for reasonable values when maximized/minimized - since then
+ * outerWidth/outerHeight aren't the dimensions of the restored window)
+ * @param aWindow
+ * Window reference
+ * @param aAttribute
+ * String sizemode | width | height | other window attribute
+ * @returns string
+ */
+ _getWindowDimension: function ssi_getWindowDimension(aWindow, aAttribute) {
+ if (aAttribute == "sizemode") {
+ switch (aWindow.windowState) {
+ case aWindow.STATE_FULLSCREEN:
+ case aWindow.STATE_MAXIMIZED:
+ return "maximized";
+ case aWindow.STATE_MINIMIZED:
+ return "minimized";
+ default:
+ return "normal";
+ }
+ }
+
+ var dimension;
+ switch (aAttribute) {
+ case "width":
+ dimension = aWindow.outerWidth;
+ break;
+ case "height":
+ dimension = aWindow.outerHeight;
+ break;
+ default:
+ dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
+ break;
+ }
+
+ if (aWindow.windowState == aWindow.STATE_NORMAL) {
+ return dimension;
+ }
+ return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
+ },
+
+ /**
+ * @param aState is a session state
+ * @param aRecentCrashes is the number of consecutive crashes
+ * @returns whether a restore page will be needed for the session state
+ */
+ _needsRestorePage: function ssi_needsRestorePage(aState, aRecentCrashes) {
+ const SIX_HOURS_IN_MS = 6 * 60 * 60 * 1000;
+
+ // don't display the page when there's nothing to restore
+ let winData = aState.windows || null;
+ if (!winData || winData.length == 0)
+ return false;
+
+ // don't wrap a single about:sessionrestore page
+ if (this._hasSingleTabWithURL(winData, "about:sessionrestore") ||
+ this._hasSingleTabWithURL(winData, "about:welcomeback")) {
+ return false;
+ }
+
+ // don't automatically restore in Safe Mode
+ if (Services.appinfo.inSafeMode)
+ return true;
+
+ let max_resumed_crashes =
+ this._prefBranch.getIntPref("sessionstore.max_resumed_crashes");
+ let sessionAge = aState.session && aState.session.lastUpdate &&
+ (Date.now() - aState.session.lastUpdate);
+
+ return max_resumed_crashes != -1 &&
+ (aRecentCrashes > max_resumed_crashes ||
+ sessionAge && sessionAge >= SIX_HOURS_IN_MS);
+ },
+
+ /**
+ * @param aWinData is the set of windows in session state
+ * @param aURL is the single URL we're looking for
+ * @returns whether the window data contains only the single URL passed
+ */
+ _hasSingleTabWithURL: function(aWinData, aURL) {
+ if (aWinData &&
+ aWinData.length == 1 &&
+ aWinData[0].tabs &&
+ aWinData[0].tabs.length == 1 &&
+ aWinData[0].tabs[0].entries &&
+ aWinData[0].tabs[0].entries.length == 1) {
+ return aURL == aWinData[0].tabs[0].entries[0].url;
+ }
+ return false;
+ },
+
+ /**
+ * Determine if the tab state we're passed is something we should save. This
+ * is used when closing a tab or closing a window with a single tab
+ *
+ * @param aTabState
+ * The current tab state
+ * @returns boolean
+ */
+ _shouldSaveTabState: function ssi_shouldSaveTabState(aTabState) {
+ // If the tab has only a transient about: history entry, no other
+ // session history, and no userTypedValue, then we don't actually want to
+ // store this tab's data.
+ return aTabState.entries.length &&
+ !(aTabState.entries.length == 1 &&
+ (aTabState.entries[0].url == "about:blank" ||
+ aTabState.entries[0].url == "about:newtab" ||
+ aTabState.entries[0].url == "about:privatebrowsing") &&
+ !aTabState.userTypedValue);
+ },
+
+ /**
+ * This is going to take a state as provided at startup (via
+ * nsISessionStartup.state) and split it into 2 parts. The first part
+ * (defaultState) will be a state that should still be restored at startup,
+ * while the second part (state) is a state that should be saved for later.
+ * defaultState will be comprised of windows with only pinned tabs, extracted
+ * from state. It will contain the cookies that go along with the history
+ * entries in those tabs. It will also contain window position information.
+ *
+ * defaultState will be restored at startup. state will be passed into
+ * LastSession and will be kept in case the user explicitly wants
+ * to restore the previous session (publicly exposed as restoreLastSession).
+ *
+ * @param state
+ * The state, presumably from nsISessionStartup.state
+ * @returns [defaultState, state]
+ */
+ _prepDataForDeferredRestore: function ssi_prepDataForDeferredRestore(state) {
+ // Make sure that we don't modify the global state as provided by
+ // nsSessionStartup.state.
+ state = Cu.cloneInto(state, {});
+
+ let defaultState = { windows: [], selectedWindow: 1 };
+
+ state.selectedWindow = state.selectedWindow || 1;
+
+ // Look at each window, remove pinned tabs, adjust selectedindex,
+ // remove window if necessary.
+ for (let wIndex = 0; wIndex < state.windows.length;) {
+ let window = state.windows[wIndex];
+ window.selected = window.selected || 1;
+ // We're going to put the state of the window into this object
+ let pinnedWindowState = { tabs: [], cookies: []};
+ for (let tIndex = 0; tIndex < window.tabs.length;) {
+ if (window.tabs[tIndex].pinned) {
+ // Adjust window.selected
+ if (tIndex + 1 < window.selected)
+ window.selected -= 1;
+ else if (tIndex + 1 == window.selected)
+ pinnedWindowState.selected = pinnedWindowState.tabs.length + 2;
+ // + 2 because the tab isn't actually in the array yet
+
+ // Now add the pinned tab to our window
+ pinnedWindowState.tabs =
+ pinnedWindowState.tabs.concat(window.tabs.splice(tIndex, 1));
+ // We don't want to increment tIndex here.
+ continue;
+ }
+ tIndex++;
+ }
+
+ // At this point the window in the state object has been modified (or not)
+ // We want to build the rest of this new window object if we have pinnedTabs.
+ if (pinnedWindowState.tabs.length) {
+ // First get the other attributes off the window
+ WINDOW_ATTRIBUTES.forEach(function(attr) {
+ if (attr in window) {
+ pinnedWindowState[attr] = window[attr];
+ delete window[attr];
+ }
+ });
+ // We're just copying position data into the pinned window.
+ // Not copying over:
+ // - _closedTabs
+ // - extData
+ // - isPopup
+ // - hidden
+
+ // Assign a unique ID to correlate the window to be opened with the
+ // remaining data
+ window.__lastSessionWindowID = pinnedWindowState.__lastSessionWindowID
+ = "" + Date.now() + Math.random();
+
+ // Extract the cookies that belong with each pinned tab
+ this._splitCookiesFromWindow(window, pinnedWindowState);
+
+ // Actually add this window to our defaultState
+ defaultState.windows.push(pinnedWindowState);
+ // Remove the window from the state if it doesn't have any tabs
+ if (!window.tabs.length) {
+ if (wIndex + 1 <= state.selectedWindow)
+ state.selectedWindow -= 1;
+ else if (wIndex + 1 == state.selectedWindow)
+ defaultState.selectedIndex = defaultState.windows.length + 1;
+
+ state.windows.splice(wIndex, 1);
+ // We don't want to increment wIndex here.
+ continue;
+ }
+
+
+ }
+ wIndex++;
+ }
+
+ return [defaultState, state];
+ },
+
+ /**
+ * Splits out the cookies from aWinState into aTargetWinState based on the
+ * tabs that are in aTargetWinState.
+ * This alters the state of aWinState and aTargetWinState.
+ */
+ _splitCookiesFromWindow:
+ function ssi_splitCookiesFromWindow(aWinState, aTargetWinState) {
+ if (!aWinState.cookies || !aWinState.cookies.length)
+ return;
+
+ // Get the hosts for history entries in aTargetWinState
+ let cookieHosts = SessionCookies.getHostsForWindow(aTargetWinState);
+
+ // By creating a regex we reduce overhead and there is only one loop pass
+ // through either array (cookieHosts and aWinState.cookies).
+ let hosts = Object.keys(cookieHosts).join("|").replace(/\./g, "\\.");
+ // If we don't actually have any hosts, then we don't want to do anything.
+ if (!hosts.length)
+ return;
+ let cookieRegex = new RegExp(".*(" + hosts + ")");
+ for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
+ if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
+ aTargetWinState.cookies =
+ aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
+ continue;
+ }
+ cIndex++;
+ }
+ },
+
+ _sendRestoreCompletedNotifications: function ssi_sendRestoreCompletedNotifications() {
+ // not all windows restored, yet
+ if (this._restoreCount > 1) {
+ this._restoreCount--;
+ return;
+ }
+
+ // observers were already notified
+ if (this._restoreCount == -1)
+ return;
+
+ // This was the last window restored at startup, notify observers.
+ Services.obs.notifyObservers(null,
+ this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
+ "");
+
+ this._browserSetState = false;
+ this._restoreCount = -1;
+ },
+
+ /**
+ * Set the given window's busy state
+ * @param aWindow the window
+ * @param aValue the window's busy state
+ */
+ _setWindowStateBusyValue:
+ function ssi_changeWindowStateBusyValue(aWindow, aValue) {
+
+ this._windows[aWindow.__SSi].busy = aValue;
+
+ // Keep the to-be-restored state in sync because that is returned by
+ // getWindowState() as long as the window isn't loaded, yet.
+ if (!this._isWindowLoaded(aWindow)) {
+ let stateToRestore = this._statesToRestore[aWindow.__SS_restoreID].windows[0];
+ stateToRestore.busy = aValue;
+ }
+ },
+
+ /**
+ * Set the given window's state to 'not busy'.
+ * @param aWindow the window
+ */
+ _setWindowStateReady: function ssi_setWindowStateReady(aWindow) {
+ let newCount = (this._windowBusyStates.get(aWindow) || 0) - 1;
+ if (newCount < 0) {
+ throw new Error("Invalid window busy state (less than zero).");
+ }
+ this._windowBusyStates.set(aWindow, newCount);
+
+ if (newCount == 0) {
+ this._setWindowStateBusyValue(aWindow, false);
+ this._sendWindowStateEvent(aWindow, "Ready");
+ }
+ },
+
+ /**
+ * Set the given window's state to 'busy'.
+ * @param aWindow the window
+ */
+ _setWindowStateBusy: function ssi_setWindowStateBusy(aWindow) {
+ let newCount = (this._windowBusyStates.get(aWindow) || 0) + 1;
+ this._windowBusyStates.set(aWindow, newCount);
+
+ if (newCount == 1) {
+ this._setWindowStateBusyValue(aWindow, true);
+ this._sendWindowStateEvent(aWindow, "Busy");
+ }
+ },
+
+ /**
+ * Dispatch an SSWindowState_____ event for the given window.
+ * @param aWindow the window
+ * @param aType the type of event, SSWindowState will be prepended to this string
+ */
+ _sendWindowStateEvent: function ssi_sendWindowStateEvent(aWindow, aType) {
+ let event = aWindow.document.createEvent("Events");
+ event.initEvent("SSWindowState" + aType, true, false);
+ aWindow.dispatchEvent(event);
+ },
+
+ /**
+ * Dispatch the SSWindowRestored event for the given window.
+ * @param aWindow
+ * The window which has been restored
+ */
+ _sendWindowRestoredNotification(aWindow) {
+ let event = aWindow.document.createEvent("Events");
+ event.initEvent("SSWindowRestored", true, false);
+ aWindow.dispatchEvent(event);
+ },
+
+ /**
+ * Dispatch the SSTabRestored event for the given tab.
+ * @param aTab
+ * The tab which has been restored
+ * @param aIsRemotenessUpdate
+ * True if this tab was restored due to flip from running from
+ * out-of-main-process to in-main-process or vice-versa.
+ */
+ _sendTabRestoredNotification(aTab, aIsRemotenessUpdate) {
+ let event = aTab.ownerDocument.createEvent("CustomEvent");
+ event.initCustomEvent("SSTabRestored", true, false, {
+ isRemotenessUpdate: aIsRemotenessUpdate,
+ });
+ aTab.dispatchEvent(event);
+ },
+
+ /**
+ * @param aWindow
+ * Window reference
+ * @returns whether this window's data is still cached in _statesToRestore
+ * because it's not fully loaded yet
+ */
+ _isWindowLoaded: function ssi_isWindowLoaded(aWindow) {
+ return !aWindow.__SS_restoreID;
+ },
+
+ /**
+ * Replace "Loading..." with the tab label (with minimal side-effects)
+ * @param aString is the string the title is stored in
+ * @param aTabbrowser is a tabbrowser object, containing aTab
+ * @param aTab is the tab whose title we're updating & using
+ *
+ * @returns aString that has been updated with the new title
+ */
+ _replaceLoadingTitle : function ssi_replaceLoadingTitle(aString, aTabbrowser, aTab) {
+ if (aString == aTabbrowser.mStringBundle.getString("tabs.connecting")) {
+ aTabbrowser.setTabTitle(aTab);
+ [aString, aTab.label] = [aTab.label, aString];
+ }
+ return aString;
+ },
+
+ /**
+ * Resize this._closedWindows to the value of the pref, except in the case
+ * where we don't have any non-popup windows on Windows and Linux. Then we must
+ * resize such that we have at least one non-popup window.
+ */
+ _capClosedWindows : function ssi_capClosedWindows() {
+ if (this._closedWindows.length <= this._max_windows_undo)
+ return;
+ let spliceTo = this._max_windows_undo;
+ if (AppConstants.platform != "macosx") {
+ let normalWindowIndex = 0;
+ // try to find a non-popup window in this._closedWindows
+ while (normalWindowIndex < this._closedWindows.length &&
+ !!this._closedWindows[normalWindowIndex].isPopup)
+ normalWindowIndex++;
+ if (normalWindowIndex >= this._max_windows_undo)
+ spliceTo = normalWindowIndex + 1;
+ }
+ this._closedWindows.splice(spliceTo, this._closedWindows.length);
+ },
+
+ /**
+ * Clears the set of windows that are "resurrected" before writing to disk to
+ * make closing windows one after the other until shutdown work as expected.
+ *
+ * This function should only be called when we are sure that there has been
+ * a user action that indicates the browser is actively being used and all
+ * windows that have been closed before are not part of a series of closing
+ * windows.
+ */
+ _clearRestoringWindows: function ssi_clearRestoringWindows() {
+ for (let i = 0; i < this._closedWindows.length; i++) {
+ delete this._closedWindows[i]._shouldRestore;
+ }
+ },
+
+ /**
+ * Reset state to prepare for a new session state to be restored.
+ */
+ _resetRestoringState: function ssi_initRestoringState() {
+ TabRestoreQueue.reset();
+ this._tabsRestoringCount = 0;
+ },
+
+ /**
+ * Reset the restoring state for a particular tab. This will be called when
+ * removing a tab or when a tab needs to be reset (it's being overwritten).
+ *
+ * @param aTab
+ * The tab that will be "reset"
+ */
+ _resetLocalTabRestoringState: function (aTab) {
+ NS_ASSERT(aTab.linkedBrowser.__SS_restoreState,
+ "given tab is not restoring");
+
+ let browser = aTab.linkedBrowser;
+
+ // Keep the tab's previous state for later in this method
+ let previousState = browser.__SS_restoreState;
+
+ // The browser is no longer in any sort of restoring state.
+ delete browser.__SS_restoreState;
+
+ aTab.removeAttribute("pending");
+ browser.removeAttribute("pending");
+
+ if (previousState == TAB_STATE_RESTORING) {
+ if (this._tabsRestoringCount)
+ this._tabsRestoringCount--;
+ } else if (previousState == TAB_STATE_NEEDS_RESTORE) {
+ // Make sure that the tab is removed from the list of tabs to restore.
+ // Again, this is normally done in restoreTabContent, but that isn't being called
+ // for this tab.
+ TabRestoreQueue.remove(aTab);
+ }
+ },
+
+ _resetTabRestoringState: function (tab) {
+ NS_ASSERT(tab.linkedBrowser.__SS_restoreState,
+ "given tab is not restoring");
+
+ let browser = tab.linkedBrowser;
+ browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
+ this._resetLocalTabRestoringState(tab);
+ },
+
+ /**
+ * Each fresh tab starts out with epoch=0. This function can be used to
+ * start a next epoch by incrementing the current value. It will enables us
+ * to ignore stale messages sent from previous epochs. The function returns
+ * the new epoch ID for the given |browser|.
+ */
+ startNextEpoch(browser) {
+ let next = this.getCurrentEpoch(browser) + 1;
+ this._browserEpochs.set(browser.permanentKey, next);
+ return next;
+ },
+
+ /**
+ * Returns the current epoch for the given <browser>. If we haven't assigned
+ * a new epoch this will default to zero for new tabs.
+ */
+ getCurrentEpoch(browser) {
+ return this._browserEpochs.get(browser.permanentKey) || 0;
+ },
+
+ /**
+ * Each time a <browser> element is restored, we increment its "epoch". To
+ * check if a message from content-sessionStore.js is out of date, we can
+ * compare the epoch received with the message to the <browser> element's
+ * epoch. This function does that, and returns true if |epoch| is up-to-date
+ * with respect to |browser|.
+ */
+ isCurrentEpoch: function (browser, epoch) {
+ return this.getCurrentEpoch(browser) == epoch;
+ },
+
+ /**
+ * Resets the epoch for a given <browser>. We need to this every time we
+ * receive a hint that a new docShell has been loaded into the browser as
+ * the frame script starts out with epoch=0.
+ */
+ resetEpoch(browser) {
+ this._browserEpochs.delete(browser.permanentKey);
+ },
+
+ /**
+ * Handle an error report from a content process.
+ */
+ reportInternalError(data) {
+ // For the moment, we only report errors through Telemetry.
+ if (data.telemetry) {
+ for (let key of Object.keys(data.telemetry)) {
+ let histogram = Telemetry.getHistogramById(key);
+ histogram.add(data.telemetry[key]);
+ }
+ }
+ },
+
+ /**
+ * 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 Promise
+ */
+ looseTimer(delay) {
+ let DELAY_BEAT = 1000;
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ let beats = Math.ceil(delay / DELAY_BEAT);
+ let promise = new Promise(resolve => {
+ timer.initWithCallback(function() {
+ if (beats <= 0) {
+ 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.
+ promise.then(() => timer.cancel(), () => timer.cancel());
+ return promise;
+ }
+};
+
+/**
+ * Priority queue that keeps track of a list of tabs to restore and returns
+ * the tab we should restore next, based on priority rules. We decide between
+ * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
+ * restored with restore_hidden_tabs=true.
+ */
+var TabRestoreQueue = {
+ // The separate buckets used to store tabs.
+ tabs: {priority: [], visible: [], hidden: []},
+
+ // Preferences used by the TabRestoreQueue to determine which tabs
+ // are restored automatically and which tabs will be on-demand.
+ prefs: {
+ // Lazy getter that returns whether tabs are restored on demand.
+ get restoreOnDemand() {
+ let updateValue = () => {
+ let value = Services.prefs.getBoolPref(PREF);
+ let definition = {value: value, configurable: true};
+ Object.defineProperty(this, "restoreOnDemand", definition);
+ return value;
+ }
+
+ const PREF = "browser.sessionstore.restore_on_demand";
+ Services.prefs.addObserver(PREF, updateValue, false);
+ return updateValue();
+ },
+
+ // Lazy getter that returns whether pinned tabs are restored on demand.
+ get restorePinnedTabsOnDemand() {
+ let updateValue = () => {
+ let value = Services.prefs.getBoolPref(PREF);
+ let definition = {value: value, configurable: true};
+ Object.defineProperty(this, "restorePinnedTabsOnDemand", definition);
+ return value;
+ }
+
+ const PREF = "browser.sessionstore.restore_pinned_tabs_on_demand";
+ Services.prefs.addObserver(PREF, updateValue, false);
+ return updateValue();
+ },
+
+ // Lazy getter that returns whether we should restore hidden tabs.
+ get restoreHiddenTabs() {
+ let updateValue = () => {
+ let value = Services.prefs.getBoolPref(PREF);
+ let definition = {value: value, configurable: true};
+ Object.defineProperty(this, "restoreHiddenTabs", definition);
+ return value;
+ }
+
+ const PREF = "browser.sessionstore.restore_hidden_tabs";
+ Services.prefs.addObserver(PREF, updateValue, false);
+ return updateValue();
+ }
+ },
+
+ // Resets the queue and removes all tabs.
+ reset: function () {
+ this.tabs = {priority: [], visible: [], hidden: []};
+ },
+
+ // Adds a tab to the queue and determines its priority bucket.
+ add: function (tab) {
+ let {priority, hidden, visible} = this.tabs;
+
+ if (tab.pinned) {
+ priority.push(tab);
+ } else if (tab.hidden) {
+ hidden.push(tab);
+ } else {
+ visible.push(tab);
+ }
+ },
+
+ // Removes a given tab from the queue, if it's in there.
+ remove: function (tab) {
+ let {priority, hidden, visible} = this.tabs;
+
+ // We'll always check priority first since we don't
+ // have an indicator if a tab will be there or not.
+ let set = priority;
+ let index = set.indexOf(tab);
+
+ if (index == -1) {
+ set = tab.hidden ? hidden : visible;
+ index = set.indexOf(tab);
+ }
+
+ if (index > -1) {
+ set.splice(index, 1);
+ }
+ },
+
+ // Returns and removes the tab with the highest priority.
+ shift: function () {
+ let set;
+ let {priority, hidden, visible} = this.tabs;
+
+ let {restoreOnDemand, restorePinnedTabsOnDemand} = this.prefs;
+ let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
+ if (restorePinned && priority.length) {
+ set = priority;
+ } else if (!restoreOnDemand) {
+ if (visible.length) {
+ set = visible;
+ } else if (this.prefs.restoreHiddenTabs && hidden.length) {
+ set = hidden;
+ }
+ }
+
+ return set && set.shift();
+ },
+
+ // Moves a given tab from the 'hidden' to the 'visible' bucket.
+ hiddenToVisible: function (tab) {
+ let {hidden, visible} = this.tabs;
+ let index = hidden.indexOf(tab);
+
+ if (index > -1) {
+ hidden.splice(index, 1);
+ visible.push(tab);
+ }
+ },
+
+ // Moves a given tab from the 'visible' to the 'hidden' bucket.
+ visibleToHidden: function (tab) {
+ let {visible, hidden} = this.tabs;
+ let index = visible.indexOf(tab);
+
+ if (index > -1) {
+ visible.splice(index, 1);
+ hidden.push(tab);
+ }
+ },
+
+ /**
+ * Returns true if the passed tab is in one of the sets that we're
+ * restoring content in automatically.
+ *
+ * @param tab (<xul:tab>)
+ * The tab to check
+ * @returns bool
+ */
+ willRestoreSoon: function (tab) {
+ let { priority, hidden, visible } = this.tabs;
+ let { restoreOnDemand, restorePinnedTabsOnDemand,
+ restoreHiddenTabs } = this.prefs;
+ let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
+ let candidateSet = [];
+
+ if (restorePinned && priority.length)
+ candidateSet.push(...priority);
+
+ if (!restoreOnDemand) {
+ if (visible.length)
+ candidateSet.push(...visible);
+
+ if (restoreHiddenTabs && hidden.length)
+ candidateSet.push(...hidden);
+ }
+
+ return candidateSet.indexOf(tab) > -1;
+ },
+};
+
+// A map storing a closed window's state data until it goes aways (is GC'ed).
+// This ensures that API clients can still read (but not write) states of
+// windows they still hold a reference to but we don't.
+var DyingWindowCache = {
+ _data: new WeakMap(),
+
+ has: function (window) {
+ return this._data.has(window);
+ },
+
+ get: function (window) {
+ return this._data.get(window);
+ },
+
+ set: function (window, data) {
+ this._data.set(window, data);
+ },
+
+ remove: function (window) {
+ this._data.delete(window);
+ }
+};
+
+// A weak set of dirty windows. We use it to determine which windows we need to
+// recollect data for when getCurrentState() is called.
+var DirtyWindows = {
+ _data: new WeakMap(),
+
+ has: function (window) {
+ return this._data.has(window);
+ },
+
+ add: function (window) {
+ return this._data.set(window, true);
+ },
+
+ remove: function (window) {
+ this._data.delete(window);
+ },
+
+ clear: function (window) {
+ this._data = new WeakMap();
+ }
+};
+
+// The state from the previous session (after restoring pinned tabs). This
+// state is persisted and passed through to the next session during an app
+// restart to make the third party add-on warning not trash the deferred
+// session
+var LastSession = {
+ _state: null,
+
+ get canRestore() {
+ return !!this._state;
+ },
+
+ getState: function () {
+ return this._state;
+ },
+
+ setState: function (state) {
+ this._state = state;
+ },
+
+ clear: function () {
+ if (this._state) {
+ this._state = null;
+ Services.obs.notifyObservers(null, NOTIFY_LAST_SESSION_CLEARED, null);
+ }
+ }
+};
diff --git a/browser/components/sessionstore/SessionWorker.js b/browser/components/sessionstore/SessionWorker.js
new file mode 100644
index 000000000..7d802a7df
--- /dev/null
+++ b/browser/components/sessionstore/SessionWorker.js
@@ -0,0 +1,381 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 to handle I/O for Session Store.
+ */
+
+"use strict";
+
+importScripts("resource://gre/modules/osfile.jsm");
+
+var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
+
+var File = OS.File;
+var Encoder = new TextEncoder();
+var Decoder = new TextDecoder();
+
+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();
+};
+
+self.addEventListener("message", msg => worker.handleMessage(msg));
+
+// The various possible states
+
+/**
+ * We just started (we haven't written anything to disk yet) from
+ * `Paths.clean`. The backup directory may not exist.
+ */
+const STATE_CLEAN = "clean";
+/**
+ * We know that `Paths.recovery` is good, either because we just read
+ * it (we haven't written anything to disk yet) or because have
+ * already written once to `Paths.recovery` during this session.
+ * `Paths.clean` is absent or invalid. The backup directory exists.
+ */
+const STATE_RECOVERY = "recovery";
+/**
+ * We just started from `Paths.recoverBackupy` (we haven't written
+ * anything to disk yet). Both `Paths.clean` and `Paths.recovery` are
+ * absent or invalid. The backup directory exists.
+ */
+const STATE_RECOVERY_BACKUP = "recoveryBackup";
+/**
+ * We just started from `Paths.upgradeBackup` (we haven't written
+ * anything to disk yet). Both `Paths.clean`, `Paths.recovery` and
+ * `Paths.recoveryBackup` are absent or invalid. The backup directory
+ * exists.
+ */
+const STATE_UPGRADE_BACKUP = "upgradeBackup";
+/**
+ * We just started without a valid session store file (we haven't
+ * written anything to disk yet). The backup directory may not exist.
+ */
+const STATE_EMPTY = "empty";
+
+var Agent = {
+ // Path to the files used by the SessionWorker
+ Paths: null,
+
+ /**
+ * The current state of the worker, as one of the following strings:
+ * - "permanent", once the first write has been completed;
+ * - "empty", before the first write has been completed,
+ * if we have started without any sessionstore;
+ * - one of "clean", "recovery", "recoveryBackup", "cleanBackup",
+ * "upgradeBackup", before the first write has been completed, if
+ * we have started by loading the corresponding file.
+ */
+ state: null,
+
+ /**
+ * Number of old upgrade backups that are being kept
+ */
+ maxUpgradeBackups: null,
+
+ /**
+ * Initialize (or reinitialize) the worker
+ *
+ * @param {string} origin Which of sessionstore.js or its backups
+ * was used. One of the `STATE_*` constants defined above.
+ * @param {object} paths The paths at which to find the various files.
+ * @param {object} prefs The preferences the worker needs to known.
+ */
+ init(origin, paths, prefs = {}) {
+ if (!(origin in paths || origin == STATE_EMPTY)) {
+ throw new TypeError("Invalid origin: " + origin);
+ }
+
+ // Check that all required preference values were passed.
+ for (let pref of ["maxUpgradeBackups", "maxSerializeBack", "maxSerializeForward"]) {
+ if (!prefs.hasOwnProperty(pref)) {
+ throw new TypeError(`Missing preference value for ${pref}`);
+ }
+ }
+
+ this.state = origin;
+ this.Paths = paths;
+ this.maxUpgradeBackups = prefs.maxUpgradeBackups;
+ this.maxSerializeBack = prefs.maxSerializeBack;
+ this.maxSerializeForward = prefs.maxSerializeForward;
+ this.upgradeBackupNeeded = paths.nextUpgradeBackup != paths.upgradeBackup;
+ return {result: true};
+ },
+
+ /**
+ * Write the session to disk.
+ * Write the session to disk, performing any necessary backup
+ * along the way.
+ *
+ * @param {object} state The state to write to disk.
+ * @param {object} options
+ * - performShutdownCleanup If |true|, we should
+ * perform shutdown-time cleanup to ensure that private data
+ * is not left lying around;
+ * - isFinalWrite If |true|, write to Paths.clean instead of
+ * Paths.recovery
+ */
+ write: function (state, options = {}) {
+ let exn;
+ let telemetry = {};
+
+ // Cap the number of backward and forward shistory entries on shutdown.
+ if (options.isFinalWrite) {
+ for (let window of state.windows) {
+ for (let tab of window.tabs) {
+ let lower = 0;
+ let upper = tab.entries.length;
+
+ if (this.maxSerializeBack > -1) {
+ lower = Math.max(lower, tab.index - this.maxSerializeBack - 1);
+ }
+ if (this.maxSerializeForward > -1) {
+ upper = Math.min(upper, tab.index + this.maxSerializeForward);
+ }
+
+ tab.entries = tab.entries.slice(lower, upper);
+ tab.index -= lower;
+ }
+ }
+ }
+
+ let stateString = JSON.stringify(state);
+ let data = Encoder.encode(stateString);
+
+ try {
+
+ if (this.state == STATE_CLEAN || this.state == STATE_EMPTY) {
+ // The backups directory may not exist yet. In all other cases,
+ // we have either already read from or already written to this
+ // directory, so we are satisfied that it exists.
+ File.makeDir(this.Paths.backups);
+ }
+
+ if (this.state == STATE_CLEAN) {
+ // Move $Path.clean out of the way, to avoid any ambiguity as
+ // to which file is more recent.
+ File.move(this.Paths.clean, this.Paths.cleanBackup);
+ }
+
+ let startWriteMs = Date.now();
+
+ if (options.isFinalWrite) {
+ // We are shutting down. At this stage, we know that
+ // $Paths.clean is either absent or corrupted. If it was
+ // originally present and valid, it has been moved to
+ // $Paths.cleanBackup a long time ago. We can therefore write
+ // with the guarantees that we erase no important data.
+ File.writeAtomic(this.Paths.clean, data, {
+ tmpPath: this.Paths.clean + ".tmp"
+ });
+ } else if (this.state == STATE_RECOVERY) {
+ // At this stage, either $Paths.recovery was written >= 15
+ // seconds ago during this session or we have just started
+ // from $Paths.recovery left from the previous session. Either
+ // way, $Paths.recovery is good. We can move $Path.backup to
+ // $Path.recoveryBackup without erasing a good file with a bad
+ // file.
+ File.writeAtomic(this.Paths.recovery, data, {
+ tmpPath: this.Paths.recovery + ".tmp",
+ backupTo: this.Paths.recoveryBackup
+ });
+ } else {
+ // In other cases, either $Path.recovery is not necessary, or
+ // it doesn't exist or it has been corrupted. Regardless,
+ // don't backup $Path.recovery.
+ File.writeAtomic(this.Paths.recovery, data, {
+ tmpPath: this.Paths.recovery + ".tmp"
+ });
+ }
+
+ telemetry.FX_SESSION_RESTORE_WRITE_FILE_MS = Date.now() - startWriteMs;
+ telemetry.FX_SESSION_RESTORE_FILE_SIZE_BYTES = data.byteLength;
+
+ } catch (ex) {
+ // Don't throw immediately
+ exn = exn || ex;
+ }
+
+ // If necessary, perform an upgrade backup
+ let upgradeBackupComplete = false;
+ if (this.upgradeBackupNeeded
+ && (this.state == STATE_CLEAN || this.state == STATE_UPGRADE_BACKUP)) {
+ try {
+ // If we loaded from `clean`, the file has since then been renamed to `cleanBackup`.
+ let path = this.state == STATE_CLEAN ? this.Paths.cleanBackup : this.Paths.upgradeBackup;
+ File.copy(path, this.Paths.nextUpgradeBackup);
+ this.upgradeBackupNeeded = false;
+ upgradeBackupComplete = true;
+ } catch (ex) {
+ // Don't throw immediately
+ exn = exn || ex;
+ }
+
+ // Find all backups
+ let iterator;
+ let backups = []; // array that will contain the paths to all upgrade backup
+ let upgradeBackupPrefix = this.Paths.upgradeBackupPrefix; // access for forEach callback
+
+ try {
+ iterator = new File.DirectoryIterator(this.Paths.backups);
+ iterator.forEach(function (file) {
+ if (file.path.startsWith(upgradeBackupPrefix)) {
+ backups.push(file.path);
+ }
+ }, this);
+ } catch (ex) {
+ // Don't throw immediately
+ exn = exn || ex;
+ } finally {
+ if (iterator) {
+ iterator.close();
+ }
+ }
+
+ // If too many backups exist, delete them
+ if (backups.length > this.maxUpgradeBackups) {
+ // Use alphanumerical sort since dates are in YYYYMMDDHHMMSS format
+ backups.sort().forEach((file, i) => {
+ // remove backup file if it is among the first (n-maxUpgradeBackups) files
+ if (i < backups.length - this.maxUpgradeBackups) {
+ File.remove(file);
+ }
+ });
+ }
+ }
+
+ if (options.performShutdownCleanup && !exn) {
+
+ // During shutdown, if auto-restore is disabled, we need to
+ // remove possibly sensitive data that has been stored purely
+ // for crash recovery. Note that this slightly decreases our
+ // ability to recover from OS-level/hardware-level issue.
+
+ // If an exception was raised, we assume that we still need
+ // these files.
+ File.remove(this.Paths.recoveryBackup);
+ File.remove(this.Paths.recovery);
+ }
+
+ this.state = STATE_RECOVERY;
+
+ if (exn) {
+ throw exn;
+ }
+
+ return {
+ result: {
+ upgradeBackup: upgradeBackupComplete
+ },
+ telemetry: telemetry,
+ };
+ },
+
+ /**
+ * Wipes all files holding session data from disk.
+ */
+ wipe: function () {
+
+ // Don't stop immediately in case of error.
+ let exn = null;
+
+ // Erase main session state file
+ try {
+ File.remove(this.Paths.clean);
+ } catch (ex) {
+ // Don't stop immediately.
+ exn = exn || ex;
+ }
+
+ // Wipe the Session Restore directory
+ try {
+ this._wipeFromDir(this.Paths.backups, null);
+ } catch (ex) {
+ exn = exn || ex;
+ }
+
+ try {
+ File.removeDir(this.Paths.backups);
+ } catch (ex) {
+ exn = exn || ex;
+ }
+
+ // Wipe legacy Ression Restore files from the profile directory
+ try {
+ this._wipeFromDir(OS.Constants.Path.profileDir, "sessionstore.bak");
+ } catch (ex) {
+ exn = exn || ex;
+ }
+
+
+ this.state = STATE_EMPTY;
+ if (exn) {
+ throw exn;
+ }
+
+ return { result: true };
+ },
+
+ /**
+ * Wipe a number of files from a directory.
+ *
+ * @param {string} path The directory.
+ * @param {string|null} prefix If provided, only remove files whose
+ * name starts with a specific prefix.
+ */
+ _wipeFromDir: function(path, prefix) {
+ // Sanity check
+ if (typeof prefix == "undefined" || prefix == "") {
+ throw new TypeError();
+ }
+
+ let exn = null;
+
+ let iterator = new File.DirectoryIterator(path);
+ try {
+ if (!iterator.exists()) {
+ return;
+ }
+ for (let entry in iterator) {
+ if (entry.isDir) {
+ continue;
+ }
+ if (!prefix || entry.name.startsWith(prefix)) {
+ try {
+ File.remove(entry.path);
+ } catch (ex) {
+ // Don't stop immediately
+ exn = exn || ex;
+ }
+ }
+ }
+
+ if (exn) {
+ throw exn;
+ }
+ } finally {
+ iterator.close();
+ }
+ },
+};
+
+function isNoSuchFileEx(aReason) {
+ return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
+}
+
+/**
+ * Estimate the number of bytes that a data structure will use on disk
+ * once serialized.
+ */
+function getByteLength(str) {
+ return Encoder.encode(JSON.stringify(str)).byteLength;
+}
diff --git a/browser/components/sessionstore/SessionWorker.jsm b/browser/components/sessionstore/SessionWorker.jsm
new file mode 100644
index 000000000..b26e531ac
--- /dev/null
+++ b/browser/components/sessionstore/SessionWorker.jsm
@@ -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";
+
+/**
+ * Interface to a dedicated thread handling I/O
+ */
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+this.EXPORTED_SYMBOLS = ["SessionWorker"];
+
+this.SessionWorker = new BasePromiseWorker("resource:///modules/sessionstore/SessionWorker.js");
+// As the Session Worker performs I/O, we can receive instances of
+// OS.File.Error, so we need to install a decoder.
+this.SessionWorker.ExceptionHandlers["OS.File.Error"] = OS.File.Error.fromMsg;
+
diff --git a/browser/components/sessionstore/StartupPerformance.jsm b/browser/components/sessionstore/StartupPerformance.jsm
new file mode 100644
index 000000000..d1b77a237
--- /dev/null
+++ b/browser/components/sessionstore/StartupPerformance.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["StartupPerformance"];
+
+const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+ "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
+ "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+const COLLECT_RESULTS_AFTER_MS = 10000;
+
+const OBSERVED_TOPICS = ["sessionstore-restoring-on-startup", "sessionstore-initiating-manual-restore"];
+
+this.StartupPerformance = {
+ /**
+ * Once we have finished restoring initial tabs, we broadcast on this topic.
+ */
+ RESTORED_TOPIC: "sessionstore-finished-restoring-initial-tabs",
+
+ // Instant at which we have started restoration (notification "sessionstore-restoring-on-startup")
+ _startTimeStamp: null,
+
+ // Latest instant at which we have finished restoring a tab (DOM event "SSTabRestored")
+ _latestRestoredTimeStamp: null,
+
+ // A promise resolved once we have finished restoring all the startup tabs.
+ _promiseFinished: null,
+
+ // Function `resolve()` for `_promiseFinished`.
+ _resolveFinished: null,
+
+ // A timer
+ _deadlineTimer: null,
+
+ // `true` once the timer has fired
+ _hasFired: false,
+
+ // `true` once we are restored
+ _isRestored: false,
+
+ // Statistics on the session we need to restore.
+ _totalNumberOfEagerTabs: 0,
+ _totalNumberOfTabs: 0,
+ _totalNumberOfWindows: 0,
+
+ init: function() {
+ for (let topic of OBSERVED_TOPICS) {
+ Services.obs.addObserver(this, topic, false);
+ }
+ },
+
+ /**
+ * Return the timestamp at which we finished restoring the latest tab.
+ *
+ * This information is not really interesting until we have finished restoring
+ * tabs.
+ */
+ get latestRestoredTimeStamp() {
+ return this._latestRestoredTimeStamp;
+ },
+
+ /**
+ * `true` once we have finished restoring startup tabs.
+ */
+ get isRestored() {
+ return this._isRestored;
+ },
+
+ // Called when restoration starts.
+ // Record the start timestamp, setup the timer and `this._promiseFinished`.
+ // Behavior is unspecified if there was already an ongoing measure.
+ _onRestorationStarts: function(isAutoRestore) {
+ this._latestRestoredTimeStamp = this._startTimeStamp = Date.now();
+ this._totalNumberOfEagerTabs = 0;
+ this._totalNumberOfTabs = 0;
+ this._totalNumberOfWindows = 0;
+
+ // While we may restore several sessions in a single run of the browser,
+ // that's a very unusual case, and not really worth measuring, so let's
+ // stop listening for further restorations.
+
+ for (let topic of OBSERVED_TOPICS) {
+ Services.obs.removeObserver(this, topic);
+ }
+
+ Services.obs.addObserver(this, "sessionstore-single-window-restored", false);
+ this._promiseFinished = new Promise(resolve => {
+ this._resolveFinished = resolve;
+ });
+ this._promiseFinished.then(() => {
+ try {
+ this._isRestored = true;
+ Services.obs.notifyObservers(null, this.RESTORED_TOPIC, "");
+
+ if (this._latestRestoredTimeStamp == this._startTimeStamp) {
+ // Apparently, we haven't restored any tab.
+ return;
+ }
+
+ // Once we are done restoring tabs, update Telemetry.
+ let histogramName = isAutoRestore ?
+ "FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS" :
+ "FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS";
+ let histogram = Services.telemetry.getHistogramById(histogramName);
+ let delta = this._latestRestoredTimeStamp - this._startTimeStamp;
+ histogram.add(delta);
+
+ Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_EAGER_TABS_RESTORED").add(this._totalNumberOfEagerTabs);
+ Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED").add(this._totalNumberOfTabs);
+ Services.telemetry.getHistogramById("FX_SESSION_RESTORE_NUMBER_OF_WINDOWS_RESTORED").add(this._totalNumberOfWindows);
+
+ // Reset
+ this._startTimeStamp = null;
+ } catch (ex) {
+ console.error("StartupPerformance: error after resolving promise", ex);
+ }
+ });
+ },
+
+ _startTimer: function() {
+ if (this._hasFired) {
+ return;
+ }
+ if (this._deadlineTimer) {
+ clearTimeout(this._deadlineTimer);
+ }
+ this._deadlineTimer = setTimeout(() => {
+ try {
+ this._resolveFinished();
+ } catch (ex) {
+ console.error("StartupPerformance: Error in timeout handler", ex);
+ } finally {
+ // Clean up.
+ this._deadlineTimer = null;
+ this._hasFired = true;
+ this._resolveFinished = null;
+ Services.obs.removeObserver(this, "sessionstore-single-window-restored");
+ }
+ }, COLLECT_RESULTS_AFTER_MS);
+ },
+
+ observe: function(subject, topic, details) {
+ try {
+ switch (topic) {
+ case "sessionstore-restoring-on-startup":
+ this._onRestorationStarts(true);
+ break;
+ case "sessionstore-initiating-manual-restore":
+ this._onRestorationStarts(false);
+ break;
+ case "sessionstore-single-window-restored": {
+ // Session Restore has just opened a window with (initially empty) tabs.
+ // Some of these tabs will be restored eagerly, while others will be
+ // restored on demand. The process becomes usable only when all windows
+ // have finished restored their eager tabs.
+ //
+ // While it would be possible to track the restoration of each tab
+ // from within SessionRestore to determine exactly when the process
+ // becomes usable, experience shows that this is too invasive. Rather,
+ // we employ the following heuristic:
+ // - we maintain a timer of `COLLECT_RESULTS_AFTER_MS` that we expect
+ // will be triggered only once all tabs have been restored;
+ // - whenever we restore a new window (hence a bunch of eager tabs),
+ // we postpone the timer to ensure that the new eager tabs have
+ // `COLLECT_RESULTS_AFTER_MS` to be restored;
+ // - whenever a tab is restored, we update
+ // `this._latestRestoredTimeStamp`;
+ // - after `COLLECT_RESULTS_AFTER_MS`, we collect the final version
+ // of `this._latestRestoredTimeStamp`, and use it to determine the
+ // entire duration of the collection.
+ //
+ // Note that this heuristic may be inaccurate if a user clicks
+ // immediately on a restore-on-demand tab before the end of
+ // `COLLECT_RESULTS_AFTER_MS`. We assume that this will not
+ // affect too much the results.
+ //
+ // Reset the delay, to give the tabs a little (more) time to restore.
+ this._startTimer();
+
+ this._totalNumberOfWindows += 1;
+
+ // Observe the restoration of all tabs. We assume that all tabs of this
+ // window will have been restored before `COLLECT_RESULTS_AFTER_MS`.
+ // The last call to `observer` will let us determine how long it took
+ // to reach that point.
+ let win = subject;
+
+ let observer = (event) => {
+ // We don't care about tab restorations that are due to
+ // a browser flipping from out-of-main-process to in-main-process
+ // or vice-versa. We only care about restorations that are due
+ // to the user switching to a lazily restored tab, or for tabs
+ // that are restoring eagerly.
+ if (!event.detail.isRemotenessUpdate) {
+ this._latestRestoredTimeStamp = Date.now();
+ this._totalNumberOfEagerTabs += 1;
+ }
+ };
+ win.gBrowser.tabContainer.addEventListener("SSTabRestored", observer);
+ this._totalNumberOfTabs += win.gBrowser.tabContainer.itemCount;
+
+ // Once we have finished collecting the results, clean up the observers.
+ this._promiseFinished.then(() => {
+ if (!win.gBrowser.tabContainer) {
+ // May be undefined during shutdown and/or some tests.
+ return;
+ }
+ win.gBrowser.tabContainer.removeEventListener("SSTabRestored", observer);
+ });
+ }
+ break;
+ default:
+ throw new Error(`Unexpected topic ${topic}`);
+ }
+ } catch (ex) {
+ console.error("StartupPerformance error", ex, ex.stack);
+ throw ex;
+ }
+ }
+};
diff --git a/browser/components/sessionstore/TabAttributes.jsm b/browser/components/sessionstore/TabAttributes.jsm
new file mode 100644
index 000000000..8a29680f4
--- /dev/null
+++ b/browser/components/sessionstore/TabAttributes.jsm
@@ -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.EXPORTED_SYMBOLS = ["TabAttributes"];
+
+// We never want to directly read or write these attributes.
+// 'image' should not be accessed directly but handled by using the
+// gBrowser.getIcon()/setIcon() methods.
+// 'muted' should not be accessed directly but handled by using the
+// tab.linkedBrowser.audioMuted/toggleMuteAudio methods.
+// 'pending' is used internal by sessionstore and managed accordingly.
+// 'iconLoadingPrincipal' is same as 'image' that it should be handled by
+// using the gBrowser.getIcon()/setIcon() methods.
+const ATTRIBUTES_TO_SKIP = new Set(["image", "muted", "pending", "iconLoadingPrincipal"]);
+
+// A set of tab attributes to persist. We will read a given list of tab
+// attributes when collecting tab data and will re-set those attributes when
+// the given tab data is restored to a new tab.
+this.TabAttributes = Object.freeze({
+ persist: function (name) {
+ return TabAttributesInternal.persist(name);
+ },
+
+ get: function (tab) {
+ return TabAttributesInternal.get(tab);
+ },
+
+ set: function (tab, data = {}) {
+ TabAttributesInternal.set(tab, data);
+ }
+});
+
+var TabAttributesInternal = {
+ _attrs: new Set(),
+
+ persist: function (name) {
+ if (this._attrs.has(name) || ATTRIBUTES_TO_SKIP.has(name)) {
+ return false;
+ }
+
+ this._attrs.add(name);
+ return true;
+ },
+
+ get: function (tab) {
+ let data = {};
+
+ for (let name of this._attrs) {
+ if (tab.hasAttribute(name)) {
+ data[name] = tab.getAttribute(name);
+ }
+ }
+
+ return data;
+ },
+
+ set: function (tab, data = {}) {
+ // Clear attributes.
+ for (let name of this._attrs) {
+ tab.removeAttribute(name);
+ }
+
+ // Set attributes.
+ for (let name in data) {
+ if (!ATTRIBUTES_TO_SKIP.has(name)) {
+ tab.setAttribute(name, data[name]);
+ }
+ }
+ }
+};
+
diff --git a/browser/components/sessionstore/TabState.jsm b/browser/components/sessionstore/TabState.jsm
new file mode 100644
index 000000000..f22c52fe3
--- /dev/null
+++ b/browser/components/sessionstore/TabState.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["TabState"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
+ "resource:///modules/sessionstore/PrivacyFilter.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
+ "resource:///modules/sessionstore/TabStateCache.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
+ "resource:///modules/sessionstore/TabAttributes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Utils",
+ "resource://gre/modules/sessionstore/Utils.jsm");
+
+/**
+ * Module that contains tab state collection methods.
+ */
+this.TabState = Object.freeze({
+ update: function (browser, data) {
+ TabStateInternal.update(browser, data);
+ },
+
+ collect: function (tab) {
+ return TabStateInternal.collect(tab);
+ },
+
+ clone: function (tab) {
+ return TabStateInternal.clone(tab);
+ },
+
+ copyFromCache(browser, tabData, options) {
+ TabStateInternal.copyFromCache(browser, tabData, options);
+ },
+});
+
+var TabStateInternal = {
+ /**
+ * Processes a data update sent by the content script.
+ */
+ update: function (browser, {data}) {
+ TabStateCache.update(browser, data);
+ },
+
+ /**
+ * Collect data related to a single tab, synchronously.
+ *
+ * @param tab
+ * tabbrowser tab
+ *
+ * @returns {TabData} An object with the data for this tab. If the
+ * tab has not been invalidated since the last call to
+ * collect(aTab), the same object is returned.
+ */
+ collect: function (tab) {
+ return this._collectBaseTabData(tab);
+ },
+
+ /**
+ * Collect data related to a single tab, including private data.
+ * Use with caution.
+ *
+ * @param tab
+ * tabbrowser tab
+ *
+ * @returns {object} An object with the data for this tab. This data is never
+ * cached, it will always be read from the tab and thus be
+ * up-to-date.
+ */
+ clone: function (tab) {
+ return this._collectBaseTabData(tab, {includePrivateData: true});
+ },
+
+ /**
+ * Collects basic tab data for a given tab.
+ *
+ * @param tab
+ * tabbrowser tab
+ * @param options (object)
+ * {includePrivateData: true} to always include private data
+ *
+ * @returns {object} An object with the basic data for this tab.
+ */
+ _collectBaseTabData: function (tab, options) {
+ let tabData = { entries: [], lastAccessed: tab.lastAccessed };
+ let browser = tab.linkedBrowser;
+
+ if (tab.pinned) {
+ tabData.pinned = true;
+ }
+
+ tabData.hidden = tab.hidden;
+
+ if (browser.audioMuted) {
+ tabData.muted = true;
+ tabData.muteReason = tab.muteReason;
+ }
+
+ // Save tab attributes.
+ tabData.attributes = TabAttributes.get(tab);
+
+ if (tab.__SS_extdata) {
+ tabData.extData = tab.__SS_extdata;
+ }
+
+ // Copy data from the tab state cache only if the tab has fully finished
+ // restoring. We don't want to overwrite data contained in __SS_data.
+ this.copyFromCache(browser, tabData, options);
+
+ // After copyFromCache() was called we check for properties that are kept
+ // in the cache only while the tab is pending or restoring. Once that
+ // happened those properties will be removed from the cache and will
+ // be read from the tab/browser every time we collect data.
+
+ // Store the tab icon.
+ if (!("image" in tabData)) {
+ let tabbrowser = tab.ownerGlobal.gBrowser;
+ tabData.image = tabbrowser.getIcon(tab);
+ }
+
+ // Store the serialized contentPrincipal of this tab to use for the icon.
+ if (!("iconLoadingPrincipal" in tabData)) {
+ tabData.iconLoadingPrincipal = Utils.serializePrincipal(browser.contentPrincipal);
+ }
+
+ // If there is a userTypedValue set, then either the user has typed something
+ // in the URL bar, or a new tab was opened with a URI to load.
+ // If so, we also track whether we were still in the process of loading something.
+ if (!("userTypedValue" in tabData) && browser.userTypedValue) {
+ tabData.userTypedValue = browser.userTypedValue;
+ // We always used to keep track of the loading state as an integer, where
+ // '0' indicated the user had typed since the last load (or no load was
+ // ongoing), and any positive value indicated we had started a load since
+ // the last time the user typed in the URL bar. Mimic this to keep the
+ // session store representation in sync, even though we now represent this
+ // more explicitly:
+ tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping() ? 1 : 0;
+ }
+
+ return tabData;
+ },
+
+ /**
+ * Copy data for the given |browser| from the cache to |tabData|.
+ *
+ * @param browser (xul:browser)
+ * The browser belonging to the given |tabData| object.
+ * @param tabData (object)
+ * The tab data belonging to the given |tab|.
+ * @param options (object)
+ * {includePrivateData: true} to always include private data
+ */
+ copyFromCache(browser, tabData, options = {}) {
+ let data = TabStateCache.get(browser);
+ if (!data) {
+ return;
+ }
+
+ // The caller may explicitly request to omit privacy checks.
+ let includePrivateData = options && options.includePrivateData;
+ let isPinned = !!tabData.pinned;
+
+ for (let key of Object.keys(data)) {
+ let value = data[key];
+
+ // Filter sensitive data according to the current privacy level.
+ if (!includePrivateData) {
+ if (key === "storage") {
+ value = PrivacyFilter.filterSessionStorageData(value);
+ } else if (key === "formdata") {
+ value = PrivacyFilter.filterFormData(value);
+ }
+ }
+
+ if (key === "history") {
+ tabData.entries = value.entries;
+
+ if (value.hasOwnProperty("userContextId")) {
+ tabData.userContextId = value.userContextId;
+ }
+
+ if (value.hasOwnProperty("index")) {
+ tabData.index = value.index;
+ }
+ } else {
+ tabData[key] = value;
+ }
+ }
+ }
+};
diff --git a/browser/components/sessionstore/TabStateCache.jsm b/browser/components/sessionstore/TabStateCache.jsm
new file mode 100644
index 000000000..9bed315a0
--- /dev/null
+++ b/browser/components/sessionstore/TabStateCache.jsm
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["TabStateCache"];
+
+/**
+ * A cache for tabs data.
+ *
+ * This cache implements a weak map from tabs (as XUL elements)
+ * to tab data (as objects).
+ *
+ * Note that we should never cache private data, as:
+ * - that data is used very seldom by SessionStore;
+ * - caching private data in addition to public data is memory consuming.
+ */
+this.TabStateCache = Object.freeze({
+ /**
+ * Retrieves cached data for a given |tab| or associated |browser|.
+ *
+ * @param browserOrTab (xul:tab or xul:browser)
+ * The tab or browser to retrieve cached data for.
+ * @return (object)
+ * The cached data stored for the given |tab|
+ * or associated |browser|.
+ */
+ get: function (browserOrTab) {
+ return TabStateCacheInternal.get(browserOrTab);
+ },
+
+ /**
+ * Updates cached data for a given |tab| or associated |browser|.
+ *
+ * @param browserOrTab (xul:tab or xul:browser)
+ * The tab or browser belonging to the given tab data.
+ * @param newData (object)
+ * The new data to be stored for the given |tab|
+ * or associated |browser|.
+ */
+ update: function (browserOrTab, newData) {
+ TabStateCacheInternal.update(browserOrTab, newData);
+ }
+});
+
+var TabStateCacheInternal = {
+ _data: new WeakMap(),
+
+ /**
+ * Retrieves cached data for a given |tab| or associated |browser|.
+ *
+ * @param browserOrTab (xul:tab or xul:browser)
+ * The tab or browser to retrieve cached data for.
+ * @return (object)
+ * The cached data stored for the given |tab|
+ * or associated |browser|.
+ */
+ get: function (browserOrTab) {
+ return this._data.get(browserOrTab.permanentKey);
+ },
+
+ /**
+ * Helper function used by update (see below). For message size
+ * optimization sometimes we don't update the whole session storage
+ * only the values that have been changed.
+ *
+ * @param data (object)
+ * The cached data where we want to update the changes.
+ * @param change (object)
+ * The actual changed values per domain.
+ */
+ updatePartialStorageChange: function (data, change) {
+ if (!data.storage) {
+ data.storage = {};
+ }
+
+ let storage = data.storage;
+ for (let domain of Object.keys(change)) {
+ for (let key of Object.keys(change[domain])) {
+ let value = change[domain][key];
+ if (value === null) {
+ if (storage[domain] && storage[domain][key]) {
+ delete storage[domain][key];
+ }
+ } else {
+ if (!storage[domain]) {
+ storage[domain] = {};
+ }
+ storage[domain][key] = value;
+ }
+ }
+ }
+ },
+
+ /**
+ * Helper function used by update (see below). For message size
+ * optimization sometimes we don't update the whole browser history
+ * only the current index and the tail of the history from a certain
+ * index (specified by change.fromIdx)
+ *
+ * @param data (object)
+ * The cached data where we want to update the changes.
+ * @param change (object)
+ * Object containing the tail of the history array, and
+ * some additional metadata.
+ */
+ updatePartialHistoryChange: function (data, change) {
+ const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
+
+ if (!data.history) {
+ data.history = { entries: [] };
+ }
+
+ let history = data.history;
+ for (let key of Object.keys(change)) {
+ if (key == "entries") {
+ if (change.fromIdx != kLastIndex) {
+ history.entries.splice(change.fromIdx + 1);
+ while (change.entries.length) {
+ history.entries.push(change.entries.shift());
+ }
+ }
+ } else if (key != "fromIndex") {
+ history[key] = change[key];
+ }
+ }
+ },
+
+ /**
+ * Updates cached data for a given |tab| or associated |browser|.
+ *
+ * @param browserOrTab (xul:tab or xul:browser)
+ * The tab or browser belonging to the given tab data.
+ * @param newData (object)
+ * The new data to be stored for the given |tab|
+ * or associated |browser|.
+ */
+ update: function (browserOrTab, newData) {
+ let data = this._data.get(browserOrTab.permanentKey) || {};
+
+ for (let key of Object.keys(newData)) {
+ if (key == "storagechange") {
+ this.updatePartialStorageChange(data, newData.storagechange);
+ continue;
+ }
+
+ if (key == "historychange") {
+ this.updatePartialHistoryChange(data, newData.historychange);
+ continue;
+ }
+
+ let value = newData[key];
+ if (value === null) {
+ delete data[key];
+ } else {
+ data[key] = value;
+ }
+ }
+
+ this._data.set(browserOrTab.permanentKey, data);
+ }
+};
diff --git a/browser/components/sessionstore/TabStateFlusher.jsm b/browser/components/sessionstore/TabStateFlusher.jsm
new file mode 100644
index 000000000..6397efe9d
--- /dev/null
+++ b/browser/components/sessionstore/TabStateFlusher.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["TabStateFlusher"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Promise.jsm", this);
+
+/**
+ * A module that enables async flushes. Updates from frame scripts are
+ * throttled to be sent only once per second. If an action wants a tab's latest
+ * state without waiting for a second then it can request an async flush and
+ * wait until the frame scripts reported back. At this point the parent has the
+ * latest data and the action can continue.
+ */
+this.TabStateFlusher = Object.freeze({
+ /**
+ * Requests an async flush for the given browser. Returns a promise that will
+ * resolve when we heard back from the content process and the parent has
+ * all the latest data.
+ */
+ flush(browser) {
+ return TabStateFlusherInternal.flush(browser);
+ },
+
+ /**
+ * Requests an async flush for all browsers of a given window. Returns a Promise
+ * that will resolve when we've heard back from all browsers.
+ */
+ flushWindow(window) {
+ return TabStateFlusherInternal.flushWindow(window);
+ },
+
+ /**
+ * Resolves the flush request with the given flush ID.
+ *
+ * @param browser (<xul:browser>)
+ * The browser for which the flush is being resolved.
+ * @param flushID (int)
+ * The ID of the flush that was sent to the browser.
+ * @param success (bool, optional)
+ * Whether or not the flush succeeded.
+ * @param message (string, optional)
+ * An error message that will be sent to the Console in the
+ * event that a flush failed.
+ */
+ resolve(browser, flushID, success=true, message="") {
+ TabStateFlusherInternal.resolve(browser, flushID, success, message);
+ },
+
+ /**
+ * Resolves all active flush requests for a given browser. This should be
+ * used when the content process crashed or the final update message was
+ * seen. In those cases we can't guarantee to ever hear back from the frame
+ * script so we just resolve all requests instead of discarding them.
+ *
+ * @param browser (<xul:browser>)
+ * The browser for which all flushes are being resolved.
+ * @param success (bool, optional)
+ * Whether or not the flushes succeeded.
+ * @param message (string, optional)
+ * An error message that will be sent to the Console in the
+ * event that the flushes failed.
+ */
+ resolveAll(browser, success=true, message="") {
+ TabStateFlusherInternal.resolveAll(browser, success, message);
+ }
+});
+
+var TabStateFlusherInternal = {
+ // Stores the last request ID.
+ _lastRequestID: 0,
+
+ // A map storing all active requests per browser.
+ _requests: new WeakMap(),
+
+ /**
+ * Requests an async flush for the given browser. Returns a promise that will
+ * resolve when we heard back from the content process and the parent has
+ * all the latest data.
+ */
+ flush(browser) {
+ let id = ++this._lastRequestID;
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage("SessionStore:flush", {id});
+
+ // Retrieve active requests for given browser.
+ let permanentKey = browser.permanentKey;
+ let perBrowserRequests = this._requests.get(permanentKey) || new Map();
+
+ return new Promise(resolve => {
+ // Store resolve() so that we can resolve the promise later.
+ perBrowserRequests.set(id, resolve);
+
+ // Update the flush requests stored per browser.
+ this._requests.set(permanentKey, perBrowserRequests);
+ });
+ },
+
+ /**
+ * Requests an async flush for all browsers of a given window. Returns a Promise
+ * that will resolve when we've heard back from all browsers.
+ */
+ flushWindow(window) {
+ let browsers = window.gBrowser.browsers;
+ let promises = browsers.map((browser) => this.flush(browser));
+ return Promise.all(promises);
+ },
+
+ /**
+ * Resolves the flush request with the given flush ID.
+ *
+ * @param browser (<xul:browser>)
+ * The browser for which the flush is being resolved.
+ * @param flushID (int)
+ * The ID of the flush that was sent to the browser.
+ * @param success (bool, optional)
+ * Whether or not the flush succeeded.
+ * @param message (string, optional)
+ * An error message that will be sent to the Console in the
+ * event that a flush failed.
+ */
+ resolve(browser, flushID, success=true, message="") {
+ // Nothing to do if there are no pending flushes for the given browser.
+ if (!this._requests.has(browser.permanentKey)) {
+ return;
+ }
+
+ // Retrieve active requests for given browser.
+ let perBrowserRequests = this._requests.get(browser.permanentKey);
+ if (!perBrowserRequests.has(flushID)) {
+ return;
+ }
+
+ if (!success) {
+ Cu.reportError("Failed to flush browser: " + message);
+ }
+
+ // Resolve the request with the given id.
+ let resolve = perBrowserRequests.get(flushID);
+ perBrowserRequests.delete(flushID);
+ resolve(success);
+ },
+
+ /**
+ * Resolves all active flush requests for a given browser. This should be
+ * used when the content process crashed or the final update message was
+ * seen. In those cases we can't guarantee to ever hear back from the frame
+ * script so we just resolve all requests instead of discarding them.
+ *
+ * @param browser (<xul:browser>)
+ * The browser for which all flushes are being resolved.
+ * @param success (bool, optional)
+ * Whether or not the flushes succeeded.
+ * @param message (string, optional)
+ * An error message that will be sent to the Console in the
+ * event that the flushes failed.
+ */
+ resolveAll(browser, success=true, message="") {
+ // Nothing to do if there are no pending flushes for the given browser.
+ if (!this._requests.has(browser.permanentKey)) {
+ return;
+ }
+
+ // Retrieve active requests for given browser.
+ let perBrowserRequests = this._requests.get(browser.permanentKey);
+
+ if (!success) {
+ Cu.reportError("Failed to flush browser: " + message);
+ }
+
+ // Resolve all requests.
+ for (let resolve of perBrowserRequests.values()) {
+ resolve(success);
+ }
+
+ // Clear active requests.
+ perBrowserRequests.clear();
+ }
+};
diff --git a/browser/components/sessionstore/content/aboutSessionRestore.js b/browser/components/sessionstore/content/aboutSessionRestore.js
new file mode 100644
index 000000000..cc8d2da0b
--- /dev/null
+++ b/browser/components/sessionstore/content/aboutSessionRestore.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/. */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+var gStateObject;
+var gTreeData;
+
+// Page initialization
+
+window.onload = function() {
+ // pages used by this script may have a link that needs to be updated to
+ // the in-product link.
+ let anchor = document.getElementById("linkMoreTroubleshooting");
+ if (anchor) {
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ anchor.setAttribute("href", baseURL + "troubleshooting");
+ }
+
+ // wire up click handlers for the radio buttons if they exist.
+ for (let radioId of ["radioRestoreAll", "radioRestoreChoose"]) {
+ let button = document.getElementById(radioId);
+ if (button) {
+ button.addEventListener("click", updateTabListVisibility);
+ }
+ }
+
+ // the crashed session state is kept inside a textbox so that SessionStore picks it up
+ // (for when the tab is closed or the session crashes right again)
+ var sessionData = document.getElementById("sessionData");
+ if (!sessionData.value) {
+ document.getElementById("errorTryAgain").disabled = true;
+ return;
+ }
+
+ gStateObject = JSON.parse(sessionData.value);
+
+ // make sure the data is tracked to be restored in case of a subsequent crash
+ var event = document.createEvent("UIEvents");
+ event.initUIEvent("input", true, true, window, 0);
+ sessionData.dispatchEvent(event);
+
+ initTreeView();
+
+ document.getElementById("errorTryAgain").focus();
+};
+
+function isTreeViewVisible() {
+ let tabList = document.querySelector(".tree-container");
+ return tabList.hasAttribute("available");
+}
+
+function initTreeView() {
+ // If we aren't visible we initialize as we are made visible (and it's OK
+ // to initialize multiple times)
+ if (!isTreeViewVisible()) {
+ return;
+ }
+ var tabList = document.getElementById("tabList");
+ var winLabel = tabList.getAttribute("_window_label");
+
+ gTreeData = [];
+ gStateObject.windows.forEach(function(aWinData, aIx) {
+ var winState = {
+ label: winLabel.replace("%S", (aIx + 1)),
+ open: true,
+ checked: true,
+ ix: aIx
+ };
+ winState.tabs = aWinData.tabs.map(function(aTabData) {
+ var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" };
+ var iconURL = aTabData.image || null;
+ // don't initiate a connection just to fetch a favicon (see bug 462863)
+ if (/^https?:/.test(iconURL))
+ iconURL = "moz-anno:favicon:" + iconURL;
+ return {
+ label: entry.title || entry.url,
+ checked: true,
+ src: iconURL,
+ parent: winState
+ };
+ });
+ gTreeData.push(winState);
+ for (let tab of winState.tabs)
+ gTreeData.push(tab);
+ }, this);
+
+ tabList.view = treeView;
+ tabList.view.selection.select(0);
+}
+
+// User actions
+function updateTabListVisibility() {
+ let tabList = document.querySelector(".tree-container");
+ let container = document.querySelector(".container");
+ if (document.getElementById("radioRestoreChoose").checked) {
+ tabList.setAttribute("available", "true");
+ container.classList.add("restore-chosen");
+ } else {
+ tabList.removeAttribute("available");
+ container.classList.remove("restore-chosen");
+ }
+ initTreeView();
+}
+
+function restoreSession() {
+ Services.obs.notifyObservers(null, "sessionstore-initiating-manual-restore", "");
+ document.getElementById("errorTryAgain").disabled = true;
+
+ if (isTreeViewVisible()) {
+ if (!gTreeData.some(aItem => aItem.checked)) {
+ // This should only be possible when we have no "cancel" button, and thus
+ // the "Restore session" button always remains enabled. In that case and
+ // when nothing is selected, we just want a new session.
+ startNewSession();
+ return;
+ }
+
+ // remove all unselected tabs from the state before restoring it
+ var ix = gStateObject.windows.length - 1;
+ for (var t = gTreeData.length - 1; t >= 0; t--) {
+ if (treeView.isContainer(t)) {
+ if (gTreeData[t].checked === 0)
+ // this window will be restored partially
+ gStateObject.windows[ix].tabs =
+ gStateObject.windows[ix].tabs.filter((aTabData, aIx) =>
+ gTreeData[t].tabs[aIx].checked);
+ else if (!gTreeData[t].checked)
+ // this window won't be restored at all
+ gStateObject.windows.splice(ix, 1);
+ ix--;
+ }
+ }
+ }
+ var stateString = JSON.stringify(gStateObject);
+
+ var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ var top = getBrowserWindow();
+
+ // if there's only this page open, reuse the window for restoring the session
+ if (top.gBrowser.tabs.length == 1) {
+ ss.setWindowState(top, stateString, true);
+ return;
+ }
+
+ // restore the session into a new window and close the current tab
+ var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all");
+
+ var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ obs.addObserver(function observe(win, topic) {
+ if (win != newWindow) {
+ return;
+ }
+
+ obs.removeObserver(observe, topic);
+ ss.setWindowState(newWindow, stateString, true);
+
+ var tabbrowser = top.gBrowser;
+ var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
+ tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
+ }, "browser-delayed-startup-finished", false);
+}
+
+function startNewSession() {
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ if (prefBranch.getIntPref("browser.startup.page") == 0)
+ getBrowserWindow().gBrowser.loadURI("about:blank");
+ else
+ getBrowserWindow().BrowserHome();
+}
+
+function onListClick(aEvent) {
+ // don't react to right-clicks
+ if (aEvent.button == 2)
+ return;
+
+ var cell = treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY);
+ if (cell.col) {
+ // Restore this specific tab in the same window for middle/double/accel clicking
+ // on a tab's title.
+ let accelKey = AppConstants.platform == "macosx" ?
+ aEvent.metaKey :
+ aEvent.ctrlKey;
+ if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) &&
+ cell.col.id == "title" &&
+ !treeView.isContainer(cell.row)) {
+ restoreSingleTab(cell.row, aEvent.shiftKey);
+ aEvent.stopPropagation();
+ }
+ else if (cell.col.id == "restore")
+ toggleRowChecked(cell.row);
+ }
+}
+
+function onListKeyDown(aEvent) {
+ switch (aEvent.keyCode)
+ {
+ case KeyEvent.DOM_VK_SPACE:
+ toggleRowChecked(document.getElementById("tabList").currentIndex);
+ // Prevent page from scrolling on the space key.
+ aEvent.preventDefault();
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ var ix = document.getElementById("tabList").currentIndex;
+ if (aEvent.ctrlKey && !treeView.isContainer(ix))
+ restoreSingleTab(ix, aEvent.shiftKey);
+ break;
+ }
+}
+
+// Helper functions
+
+function getBrowserWindow() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+}
+
+function toggleRowChecked(aIx) {
+ function isChecked(aItem) {
+ return aItem.checked;
+ }
+
+ var item = gTreeData[aIx];
+ item.checked = !item.checked;
+ treeView.treeBox.invalidateRow(aIx);
+
+ if (treeView.isContainer(aIx)) {
+ // (un)check all tabs of this window as well
+ for (let tab of item.tabs) {
+ tab.checked = item.checked;
+ treeView.treeBox.invalidateRow(gTreeData.indexOf(tab));
+ }
+ }
+ else {
+ // update the window's checkmark as well (0 means "partially checked")
+ item.parent.checked = item.parent.tabs.every(isChecked) ? true :
+ item.parent.tabs.some(isChecked) ? 0 : false;
+ treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
+ }
+
+ // we only disable the button when there's no cancel button.
+ if (document.getElementById("errorCancel")) {
+ document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
+ }
+}
+
+function restoreSingleTab(aIx, aShifted) {
+ var tabbrowser = getBrowserWindow().gBrowser;
+ var newTab = tabbrowser.addTab();
+ var item = gTreeData[aIx];
+
+ var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ var tabState = gStateObject.windows[item.parent.ix]
+ .tabs[aIx - gTreeData.indexOf(item.parent) - 1];
+ // ensure tab would be visible on the tabstrip.
+ tabState.hidden = false;
+ ss.setTabState(newTab, JSON.stringify(tabState));
+
+ // respect the preference as to whether to select the tab (the Shift key inverses)
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)
+ tabbrowser.selectedTab = newTab;
+}
+
+// Tree controller
+
+var treeView = {
+ treeBox: null,
+ selection: null,
+
+ get rowCount() { return gTreeData.length; },
+ setTree: function(treeBox) { this.treeBox = treeBox; },
+ getCellText: function(idx, column) { return gTreeData[idx].label; },
+ isContainer: function(idx) { return "open" in gTreeData[idx]; },
+ getCellValue: function(idx, column){ return gTreeData[idx].checked; },
+ isContainerOpen: function(idx) { return gTreeData[idx].open; },
+ isContainerEmpty: function(idx) { return false; },
+ isSeparator: function(idx) { return false; },
+ isSorted: function() { return false; },
+ isEditable: function(idx, column) { return false; },
+ canDrop: function(idx, orientation, dt) { return false; },
+ getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; },
+
+ getParentIndex: function(idx) {
+ if (!this.isContainer(idx))
+ for (var t = idx - 1; t >= 0 ; t--)
+ if (this.isContainer(t))
+ return t;
+ return -1;
+ },
+
+ hasNextSibling: function(idx, after) {
+ var thisLevel = this.getLevel(idx);
+ for (var t = after + 1; t < gTreeData.length; t++)
+ if (this.getLevel(t) <= thisLevel)
+ return this.getLevel(t) == thisLevel;
+ return false;
+ },
+
+ toggleOpenState: function(idx) {
+ if (!this.isContainer(idx))
+ return;
+ var item = gTreeData[idx];
+ if (item.open) {
+ // remove this window's tab rows from the view
+ var thisLevel = this.getLevel(idx);
+ for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++);
+ var deletecount = t - idx - 1;
+ gTreeData.splice(idx + 1, deletecount);
+ this.treeBox.rowCountChanged(idx + 1, -deletecount);
+ }
+ else {
+ // add this window's tab rows to the view
+ var toinsert = gTreeData[idx].tabs;
+ for (var i = 0; i < toinsert.length; i++)
+ gTreeData.splice(idx + i + 1, 0, toinsert[i]);
+ this.treeBox.rowCountChanged(idx + 1, toinsert.length);
+ }
+ item.open = !item.open;
+ this.treeBox.invalidateRow(idx);
+ },
+
+ getCellProperties: function(idx, column) {
+ if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0)
+ return "partial";
+ if (column.id == "title")
+ return this.getImageSrc(idx, column) ? "icon" : "noicon";
+
+ return "";
+ },
+
+ getRowProperties: function(idx) {
+ var winState = gTreeData[idx].parent || gTreeData[idx];
+ if (winState.ix % 2 != 0)
+ return "alternate";
+
+ return "";
+ },
+
+ getImageSrc: function(idx, column) {
+ if (column.id == "title")
+ return gTreeData[idx].src || null;
+ return null;
+ },
+
+ getProgressMode : function(idx, column) { },
+ cycleHeader: function(column) { },
+ cycleCell: function(idx, column) { },
+ selectionChanged: function() { },
+ performAction: function(action) { },
+ performActionOnCell: function(action, index, column) { },
+ getColumnProperties: function(column) { return ""; }
+};
diff --git a/browser/components/sessionstore/content/aboutSessionRestore.xhtml b/browser/components/sessionstore/content/aboutSessionRestore.xhtml
new file mode 100644
index 000000000..bcd9084e7
--- /dev/null
+++ b/browser/components/sessionstore/content/aboutSessionRestore.xhtml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+-->
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % restorepageDTD SYSTEM "chrome://browser/locale/aboutSessionRestore.dtd">
+ %restorepageDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <head>
+ <title>&restorepage.tabtitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css" media="all"/>
+ <link rel="stylesheet" href="chrome://browser/skin/aboutSessionRestore.css" type="text/css" media="all"/>
+ <link rel="icon" type="image/png" href="chrome://global/skin/icons/warning-16.png"/>
+
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSessionRestore.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <div class="container restore-chosen">
+
+ <div class="title">
+ <h1 class="title-text">&restorepage.errorTitle;</h1>
+ </div>
+ <div class="description">
+ <p>&restorepage.problemDesc;</p>
+
+ <div id="errorLongDesc">
+ <p>&restorepage.tryThis;</p>
+ <ul>
+ <li>&restorepage.restoreSome;</li>
+ <li>&restorepage.startNew;</li>
+ </ul>
+ </div>
+ </div>
+ <div class="tree-container" available="true">
+ <xul:tree id="tabList" seltype="single" hidecolumnpicker="true"
+ onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
+ _window_label="&restorepage.windowLabel;">
+ <xul:treecols>
+ <xul:treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
+ <xul:splitter class="tree-splitter"/>
+ <xul:treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
+ </xul:treecols>
+ <xul:treechildren flex="1"/>
+ </xul:tree>
+ </div>
+ <div class="button-container">
+#ifdef XP_UNIX
+ <xul:button id="errorCancel"
+ label="&restorepage.closeButton;"
+ accesskey="&restorepage.close.access;"
+ oncommand="startNewSession();"/>
+ <xul:button class="primary"
+ id="errorTryAgain"
+ label="&restorepage.tryagainButton;"
+ accesskey="&restorepage.restore.access;"
+ oncommand="restoreSession();"/>
+#else
+ <xul:button class="primary"
+ id="errorTryAgain"
+ label="&restorepage.tryagainButton;"
+ accesskey="&restorepage.restore.access;"
+ oncommand="restoreSession();"/>
+ <xul:button id="errorCancel"
+ label="&restorepage.closeButton;"
+ accesskey="&restorepage.close.access;"
+ oncommand="startNewSession();"/>
+#endif
+ </div>
+ <!-- holds the session data for when the tab is closed -->
+ <input type="text" id="sessionData" style="display: none;"/>
+ </div>
+
+ </body>
+</html>
diff --git a/browser/components/sessionstore/content/content-sessionStore.js b/browser/components/sessionstore/content/content-sessionStore.js
new file mode 100644
index 000000000..858e35750
--- /dev/null
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -0,0 +1,897 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 debug(msg) {
+ Services.console.logStringMessage("SessionStoreContent: " + msg);
+}
+
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormData",
+ "resource://gre/modules/FormData.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
+ "resource:///modules/sessionstore/DocShellCapabilities.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
+ "resource:///modules/sessionstore/PageStyle.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ScrollPosition",
+ "resource://gre/modules/ScrollPosition.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
+ "resource:///modules/sessionstore/SessionHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
+ "resource:///modules/sessionstore/SessionStorage.jsm");
+
+Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
+var gFrameTree = new FrameTree(this);
+
+Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
+XPCOMUtils.defineLazyGetter(this, 'gContentRestore',
+ () => { return new ContentRestore(this) });
+
+// The current epoch.
+var gCurrentEpoch = 0;
+
+// A bound to the size of data to store for DOM Storage.
+const DOM_STORAGE_MAX_CHARS = 10000000; // 10M characters
+
+// This pref controls whether or not we send updates to the parent on a timeout
+// or not, and should only be used for tests or debugging.
+const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
+
+const kNoIndex = Number.MAX_SAFE_INTEGER;
+const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
+
+/**
+ * Returns a lazy function that will evaluate the given
+ * function |fn| only once and cache its return value.
+ */
+function createLazy(fn) {
+ let cached = false;
+ let cachedValue = null;
+
+ return function lazy() {
+ if (!cached) {
+ cachedValue = fn();
+ cached = true;
+ }
+
+ return cachedValue;
+ };
+}
+
+/**
+ * Listens for and handles content events that we need for the
+ * session store service to be notified of state changes in content.
+ */
+var EventListener = {
+
+ init: function () {
+ addEventListener("load", this, true);
+ },
+
+ handleEvent: function (event) {
+ // Ignore load events from subframes.
+ if (event.target != content.document) {
+ return;
+ }
+
+ if (content.document.documentURI.startsWith("about:reader")) {
+ if (event.type == "load" &&
+ !content.document.body.classList.contains("loaded")) {
+ // Don't restore the scroll position of an about:reader page at this
+ // point; listen for the custom event dispatched from AboutReader.jsm.
+ content.addEventListener("AboutReaderContentReady", this);
+ return;
+ }
+
+ content.removeEventListener("AboutReaderContentReady", this);
+ }
+
+ // Restore the form data and scroll position. If we're not currently
+ // restoring a tab state then this call will simply be a noop.
+ gContentRestore.restoreDocument();
+ }
+};
+
+/**
+ * Listens for and handles messages sent by the session store service.
+ */
+var MessageListener = {
+
+ MESSAGES: [
+ "SessionStore:restoreHistory",
+ "SessionStore:restoreTabContent",
+ "SessionStore:resetRestore",
+ "SessionStore:flush",
+ ],
+
+ init: function () {
+ this.MESSAGES.forEach(m => addMessageListener(m, this));
+ },
+
+ receiveMessage: function ({name, data}) {
+ // The docShell might be gone. Don't process messages,
+ // that will just lead to errors anyway.
+ if (!docShell) {
+ return;
+ }
+
+ // A fresh tab always starts with epoch=0. The parent has the ability to
+ // override that to signal a new era in this tab's life. This enables it
+ // to ignore async messages that were already sent but not yet received
+ // and would otherwise confuse the internal tab state.
+ if (data.epoch && data.epoch != gCurrentEpoch) {
+ gCurrentEpoch = data.epoch;
+ }
+
+ switch (name) {
+ case "SessionStore:restoreHistory":
+ this.restoreHistory(data);
+ break;
+ case "SessionStore:restoreTabContent":
+ this.restoreTabContent(data);
+ break;
+ case "SessionStore:resetRestore":
+ gContentRestore.resetRestore();
+ break;
+ case "SessionStore:flush":
+ this.flush(data);
+ break;
+ default:
+ debug("received unknown message '" + name + "'");
+ break;
+ }
+ },
+
+ restoreHistory({epoch, tabData, loadArguments, isRemotenessUpdate}) {
+ gContentRestore.restoreHistory(tabData, loadArguments, {
+ // Note: The callbacks passed here will only be used when a load starts
+ // that was not initiated by sessionstore itself. This can happen when
+ // some code calls browser.loadURI() or browser.reload() on a pending
+ // browser/tab.
+
+ onLoadStarted() {
+ // Notify the parent that the tab is no longer pending.
+ sendSyncMessage("SessionStore:restoreTabContentStarted", {epoch});
+ },
+
+ onLoadFinished() {
+ // Tell SessionStore.jsm that it may want to restore some more tabs,
+ // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
+ sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch});
+ }
+ });
+
+ // When restoreHistory finishes, we send a synchronous message to
+ // SessionStore.jsm so that it can run SSTabRestoring. Users of
+ // SSTabRestoring seem to get confused if chrome and content are out of
+ // sync about the state of the restore (particularly regarding
+ // docShell.currentURI). Using a synchronous message is the easiest way
+ // to temporarily synchronize them.
+ sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch, isRemotenessUpdate});
+ },
+
+ restoreTabContent({loadArguments, isRemotenessUpdate}) {
+ let epoch = gCurrentEpoch;
+
+ // We need to pass the value of didStartLoad back to SessionStore.jsm.
+ let didStartLoad = gContentRestore.restoreTabContent(loadArguments, isRemotenessUpdate, () => {
+ // Tell SessionStore.jsm that it may want to restore some more tabs,
+ // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
+ sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
+ });
+
+ sendAsyncMessage("SessionStore:restoreTabContentStarted", {epoch, isRemotenessUpdate});
+
+ if (!didStartLoad) {
+ // Pretend that the load succeeded so that event handlers fire correctly.
+ sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
+ }
+ },
+
+ flush({id}) {
+ // Flush the message queue, send the latest updates.
+ MessageQueue.send({flushID: id});
+ }
+};
+
+/**
+ * Listens for changes to the session history. Whenever the user navigates
+ * we will collect URLs and everything belonging to session history.
+ *
+ * Causes a SessionStore:update message to be sent that contains the current
+ * session history.
+ *
+ * Example:
+ * {entries: [{url: "about:mozilla", ...}, ...], index: 1}
+ */
+var SessionHistoryListener = {
+ init: function () {
+ // The frame tree observer is needed to handle initial subframe loads.
+ // It will redundantly invalidate with the SHistoryListener in some cases
+ // but these invalidations are very cheap.
+ gFrameTree.addObserver(this);
+
+ // By adding the SHistoryListener immediately, we will unfortunately be
+ // notified of every history entry as the tab is restored. We don't bother
+ // waiting to add the listener later because these notifications are cheap.
+ // We will likely only collect once since we are batching collection on
+ // a delay.
+ docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.
+ addSHistoryListener(this);
+
+ // Collect data if we start with a non-empty shistory.
+ if (!SessionHistory.isEmpty(docShell)) {
+ this.collect();
+ // When a tab is detached from the window, for the new window there is a
+ // new SessionHistoryListener created. Normally it is empty at this point
+ // but in a test env. the initial about:blank might have a children in which
+ // case we fire off a history message here with about:blank in it. If we
+ // don't do it ASAP then there is going to be a browser swap and the parent
+ // will be all confused by that message.
+ MessageQueue.send();
+ }
+
+ // Listen for page title changes.
+ addEventListener("DOMTitleChanged", this);
+ },
+
+ uninit: function () {
+ let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
+ if (sessionHistory) {
+ sessionHistory.removeSHistoryListener(this);
+ }
+ },
+
+ collect: function () {
+ this._fromIdx = kNoIndex;
+ if (docShell) {
+ MessageQueue.push("history", () => SessionHistory.collect(docShell));
+ }
+ },
+
+ _fromIdx: kNoIndex,
+
+ // History can grow relatively big with the nested elements, so if we don't have to, we
+ // don't want to send the entire history all the time. For a simple optimization
+ // we keep track of the smallest index from after any change has occured and we just send
+ // the elements from that index. If something more complicated happens we just clear it
+ // and send the entire history. We always send the additional info like the current selected
+ // index (so for going back and forth between history entries we set the index to kLastIndex
+ // if nothing else changed send an empty array and the additonal info like the selected index)
+ collectFrom: function (idx) {
+ if (this._fromIdx <= idx) {
+ // If we already know that we need to update history fromn index N we can ignore any changes
+ // tha happened with an element with index larger than N.
+ // Note: initially we use kNoIndex which is MAX_SAFE_INTEGER which means we don't ignore anything
+ // here, and in case of navigation in the history back and forth we use kLastIndex which ignores
+ // only the subsequent navigations, but not any new elements added.
+ return;
+ }
+
+ this._fromIdx = idx;
+ MessageQueue.push("historychange", () => {
+ if (this._fromIdx === kNoIndex) {
+ return null;
+ }
+
+ let history = SessionHistory.collect(docShell);
+ if (kLastIndex == idx) {
+ history.entries = [];
+ } else {
+ history.entries.splice(0, this._fromIdx + 1);
+ }
+
+ history.fromIdx = this._fromIdx;
+
+ this._fromIdx = kNoIndex;
+ return history;
+ });
+ },
+
+ handleEvent(event) {
+ this.collect();
+ },
+
+ onFrameTreeCollected: function () {
+ this.collect();
+ },
+
+ onFrameTreeReset: function () {
+ this.collect();
+ },
+
+ OnHistoryNewEntry: function (newURI, oldIndex) {
+ this.collectFrom(oldIndex);
+ },
+
+ OnHistoryGoBack: function (backURI) {
+ this.collectFrom(kLastIndex);
+ return true;
+ },
+
+ OnHistoryGoForward: function (forwardURI) {
+ this.collectFrom(kLastIndex);
+ return true;
+ },
+
+ OnHistoryGotoIndex: function (index, gotoURI) {
+ this.collectFrom(kLastIndex);
+ return true;
+ },
+
+ OnHistoryPurge: function (numEntries) {
+ this.collect();
+ return true;
+ },
+
+ OnHistoryReload: function (reloadURI, reloadFlags) {
+ this.collect();
+ return true;
+ },
+
+ OnHistoryReplaceEntry: function (index) {
+ this.collect();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISHistoryListener,
+ Ci.nsISupportsWeakReference
+ ])
+};
+
+/**
+ * Listens for scroll position changes. Whenever the user scrolls the top-most
+ * frame we update the scroll position and will restore it when requested.
+ *
+ * Causes a SessionStore:update message to be sent that contains the current
+ * scroll positions as a tree of strings. If no frame of the whole frame tree
+ * is scrolled this will return null so that we don't tack a property onto
+ * the tabData object in the parent process.
+ *
+ * Example:
+ * {scroll: "100,100", children: [null, null, {scroll: "200,200"}]}
+ */
+var ScrollPositionListener = {
+ init: function () {
+ addEventListener("scroll", this);
+ gFrameTree.addObserver(this);
+ },
+
+ handleEvent: function (event) {
+ let frame = event.target.defaultView;
+
+ // Don't collect scroll data for frames created at or after the load event
+ // as SessionStore can't restore scroll data for those.
+ if (gFrameTree.contains(frame)) {
+ MessageQueue.push("scroll", () => this.collect());
+ }
+ },
+
+ onFrameTreeCollected: function () {
+ MessageQueue.push("scroll", () => this.collect());
+ },
+
+ onFrameTreeReset: function () {
+ MessageQueue.push("scroll", () => null);
+ },
+
+ collect: function () {
+ return gFrameTree.map(ScrollPosition.collect);
+ }
+};
+
+/**
+ * Listens for changes to input elements. Whenever the value of an input
+ * element changes we will re-collect data for the current frame tree and send
+ * a message to the parent process.
+ *
+ * Causes a SessionStore:update message to be sent that contains the form data
+ * for all reachable frames.
+ *
+ * Example:
+ * {
+ * formdata: {url: "http://mozilla.org/", id: {input_id: "input value"}},
+ * children: [
+ * null,
+ * {url: "http://sub.mozilla.org/", id: {input_id: "input value 2"}}
+ * ]
+ * }
+ */
+var FormDataListener = {
+ init: function () {
+ addEventListener("input", this, true);
+ addEventListener("change", this, true);
+ gFrameTree.addObserver(this);
+ },
+
+ handleEvent: function (event) {
+ let frame = event.target.ownerGlobal;
+
+ // Don't collect form data for frames created at or after the load event
+ // as SessionStore can't restore form data for those.
+ if (gFrameTree.contains(frame)) {
+ MessageQueue.push("formdata", () => this.collect());
+ }
+ },
+
+ onFrameTreeReset: function () {
+ MessageQueue.push("formdata", () => null);
+ },
+
+ collect: function () {
+ return gFrameTree.map(FormData.collect);
+ }
+};
+
+/**
+ * Listens for changes to the page style. Whenever a different page style is
+ * selected or author styles are enabled/disabled we send a message with the
+ * currently applied style to the chrome process.
+ *
+ * Causes a SessionStore:update message to be sent that contains the currently
+ * selected pageStyle for all reachable frames.
+ *
+ * Example:
+ * {pageStyle: "Dusk", children: [null, {pageStyle: "Mozilla"}]}
+ */
+var PageStyleListener = {
+ init: function () {
+ Services.obs.addObserver(this, "author-style-disabled-changed", false);
+ Services.obs.addObserver(this, "style-sheet-applicable-state-changed", false);
+ gFrameTree.addObserver(this);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "author-style-disabled-changed");
+ Services.obs.removeObserver(this, "style-sheet-applicable-state-changed");
+ },
+
+ observe: function (subject, topic) {
+ let frame = subject.defaultView;
+
+ if (frame && gFrameTree.contains(frame)) {
+ MessageQueue.push("pageStyle", () => this.collect());
+ }
+ },
+
+ collect: function () {
+ return PageStyle.collect(docShell, gFrameTree);
+ },
+
+ onFrameTreeCollected: function () {
+ MessageQueue.push("pageStyle", () => this.collect());
+ },
+
+ onFrameTreeReset: function () {
+ MessageQueue.push("pageStyle", () => null);
+ }
+};
+
+/**
+ * Listens for changes to docShell capabilities. Whenever a new load is started
+ * we need to re-check the list of capabilities and send message when it has
+ * changed.
+ *
+ * Causes a SessionStore:update message to be sent that contains the currently
+ * disabled docShell capabilities (all nsIDocShell.allow* properties set to
+ * false) as a string - i.e. capability names separate by commas.
+ */
+var DocShellCapabilitiesListener = {
+ /**
+ * This field is used to compare the last docShell capabilities to the ones
+ * that have just been collected. If nothing changed we won't send a message.
+ */
+ _latestCapabilities: "",
+
+ init: function () {
+ gFrameTree.addObserver(this);
+ },
+
+ /**
+ * onFrameTreeReset() is called as soon as we start loading a page.
+ */
+ onFrameTreeReset: function() {
+ // The order of docShell capabilities cannot change while we're running
+ // so calling join() without sorting before is totally sufficient.
+ let caps = DocShellCapabilities.collect(docShell).join(",");
+
+ // Send new data only when the capability list changes.
+ if (caps != this._latestCapabilities) {
+ this._latestCapabilities = caps;
+ MessageQueue.push("disallow", () => caps || null);
+ }
+ }
+};
+
+/**
+ * Listens for changes to the DOMSessionStorage. Whenever new keys are added,
+ * existing ones removed or changed, or the storage is cleared we will send a
+ * message to the parent process containing up-to-date sessionStorage data.
+ *
+ * Causes a SessionStore:update message to be sent that contains the current
+ * DOMSessionStorage contents. The data is a nested object using host names
+ * as keys and per-host DOMSessionStorage data as values.
+ */
+var SessionStorageListener = {
+ init: function () {
+ addEventListener("MozSessionStorageChanged", this, true);
+ Services.obs.addObserver(this, "browser:purge-domain-data", false);
+ gFrameTree.addObserver(this);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "browser:purge-domain-data");
+ },
+
+ handleEvent: function (event) {
+ if (gFrameTree.contains(event.target)) {
+ this.collectFromEvent(event);
+ }
+ },
+
+ observe: function () {
+ // Collect data on the next tick so that any other observer
+ // that needs to purge data can do its work first.
+ setTimeout(() => this.collect(), 0);
+ },
+
+ // Before DOM Storage can be written to disk, it needs to be serialized
+ // for sending across frames/processes, then again to be sent across
+ // threads, then again to be put in a buffer for the disk. Each of these
+ // serializations is an opportunity to OOM and (depending on the site of
+ // the OOM), either crash, lose all data for the frame or lose all data
+ // for the application.
+ //
+ // In order to avoid this, compute an estimate of the size of the
+ // object, and block SessionStorage items that are too large. As
+ // we also don't want to cause an OOM here, we use a quick and memory-
+ // efficient approximation: we compute the total sum of string lengths
+ // involved in this object.
+ estimateStorageSize: function(collected) {
+ if (!collected) {
+ return 0;
+ }
+
+ let size = 0;
+ for (let host of Object.keys(collected)) {
+ size += host.length;
+ let perHost = collected[host];
+ for (let key of Object.keys(perHost)) {
+ size += key.length;
+ let perKey = perHost[key];
+ size += perKey.length;
+ }
+ }
+
+ return size;
+ },
+
+ // We don't want to send all the session storage data for all the frames
+ // for every change. So if only a few value changed we send them over as
+ // a "storagechange" event. If however for some reason before we send these
+ // changes we have to send over the entire sessions storage data, we just
+ // reset these changes.
+ _changes: undefined,
+
+ resetChanges: function () {
+ this._changes = undefined;
+ },
+
+ collectFromEvent: function (event) {
+ // TODO: we should take browser.sessionstore.dom_storage_limit into an account here.
+ if (docShell) {
+ let {url, key, newValue} = event;
+ let uri = Services.io.newURI(url, null, null);
+ let domain = uri.prePath;
+ if (!this._changes) {
+ this._changes = {};
+ }
+ if (!this._changes[domain]) {
+ this._changes[domain] = {};
+ }
+ this._changes[domain][key] = newValue;
+
+ MessageQueue.push("storagechange", () => {
+ let tmp = this._changes;
+ // If there were multiple changes we send them merged.
+ // First one will collect all the changes the rest of
+ // these messages will be ignored.
+ this.resetChanges();
+ return tmp;
+ });
+ }
+ },
+
+ collect: function () {
+ if (docShell) {
+ // We need the entire session storage, let's reset the pending individual change
+ // messages.
+ this.resetChanges();
+ MessageQueue.push("storage", () => {
+ let collected = SessionStorage.collect(docShell, gFrameTree);
+
+ if (collected == null) {
+ return collected;
+ }
+
+ let size = this.estimateStorageSize(collected);
+
+ MessageQueue.push("telemetry", () => ({ FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS: size }));
+ if (size > Preferences.get("browser.sessionstore.dom_storage_limit", DOM_STORAGE_MAX_CHARS)) {
+ // Rather than keeping the old storage, which wouldn't match the rest
+ // of the state of the page, empty the storage. DOM storage will be
+ // recollected the next time and stored if it is now small enough.
+ return {};
+ }
+
+ return collected;
+ });
+ }
+ },
+
+ onFrameTreeCollected: function () {
+ this.collect();
+ },
+
+ onFrameTreeReset: function () {
+ this.collect();
+ }
+};
+
+/**
+ * Listen for changes to the privacy status of the tab.
+ * By definition, tabs start in non-private mode.
+ *
+ * Causes a SessionStore:update message to be sent for
+ * field "isPrivate". This message contains
+ * |true| if the tab is now private
+ * |null| if the tab is now public - the field is therefore
+ * not saved.
+ */
+var PrivacyListener = {
+ init: function() {
+ docShell.addWeakPrivacyTransitionObserver(this);
+
+ // Check that value at startup as it might have
+ // been set before the frame script was loaded.
+ if (docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing) {
+ MessageQueue.push("isPrivate", () => true);
+ }
+ },
+
+ // Ci.nsIPrivacyTransitionObserver
+ privateModeChanged: function(enabled) {
+ MessageQueue.push("isPrivate", () => enabled || null);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivacyTransitionObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+/**
+ * A message queue that takes collected data and will take care of sending it
+ * to the chrome process. It allows flushing using synchronous messages and
+ * takes care of any race conditions that might occur because of that. Changes
+ * will be batched if they're pushed in quick succession to avoid a message
+ * flood.
+ */
+var MessageQueue = {
+ /**
+ * A map (string -> lazy fn) holding lazy closures of all queued data
+ * collection routines. These functions will return data collected from the
+ * docShell.
+ */
+ _data: new Map(),
+
+ /**
+ * The delay (in ms) used to delay sending changes after data has been
+ * invalidated.
+ */
+ BATCH_DELAY_MS: 1000,
+
+ /**
+ * The current timeout ID, null if there is no queue data. We use timeouts
+ * to damp a flood of data changes and send lots of changes as one batch.
+ */
+ _timeout: null,
+
+ /**
+ * Whether or not sending batched messages on a timer is disabled. This should
+ * only be used for debugging or testing. If you need to access this value,
+ * you should probably use the timeoutDisabled getter.
+ */
+ _timeoutDisabled: false,
+
+ /**
+ * True if batched messages are not being fired on a timer. This should only
+ * ever be true when debugging or during tests.
+ */
+ get timeoutDisabled() {
+ return this._timeoutDisabled;
+ },
+
+ /**
+ * Disables sending batched messages on a timer. Also cancels any pending
+ * timers.
+ */
+ set timeoutDisabled(val) {
+ this._timeoutDisabled = val;
+
+ if (val && this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+
+ return val;
+ },
+
+ init() {
+ this.timeoutDisabled =
+ Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+
+ Services.prefs.addObserver(TIMEOUT_DISABLED_PREF, this, false);
+ },
+
+ uninit() {
+ Services.prefs.removeObserver(TIMEOUT_DISABLED_PREF, this);
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "nsPref:changed" && data == TIMEOUT_DISABLED_PREF) {
+ this.timeoutDisabled =
+ Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+ }
+ },
+
+ /**
+ * Pushes a given |value| onto the queue. The given |key| represents the type
+ * of data that is stored and can override data that has been queued before
+ * but has not been sent to the parent process, yet.
+ *
+ * @param key (string)
+ * A unique identifier specific to the type of data this is passed.
+ * @param fn (function)
+ * A function that returns the value that will be sent to the parent
+ * process.
+ */
+ push: function (key, fn) {
+ this._data.set(key, createLazy(fn));
+
+ if (!this._timeout && !this._timeoutDisabled) {
+ // Wait a little before sending the message to batch multiple changes.
+ this._timeout = setTimeout(() => this.send(), this.BATCH_DELAY_MS);
+ }
+ },
+
+ /**
+ * Sends queued data to the chrome process.
+ *
+ * @param options (object)
+ * {flushID: 123} to specify that this is a flush
+ * {isFinal: true} to signal this is the final message sent on unload
+ */
+ send: function (options = {}) {
+ // Looks like we have been called off a timeout after the tab has been
+ // closed. The docShell is gone now and we can just return here as there
+ // is nothing to do.
+ if (!docShell) {
+ return;
+ }
+
+ if (this._timeout) {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+
+ let flushID = (options && options.flushID) || 0;
+
+ let durationMs = Date.now();
+
+ let data = {};
+ let telemetry = {};
+ for (let [key, func] of this._data) {
+ let value = func();
+ if (key == "telemetry") {
+ for (let histogramId of Object.keys(value)) {
+ telemetry[histogramId] = value[histogramId];
+ }
+ } else if (value || (key != "storagechange" && key != "historychange")) {
+ data[key] = value;
+ }
+ }
+
+ this._data.clear();
+
+ durationMs = Date.now() - durationMs;
+ telemetry.FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS = durationMs;
+
+ try {
+ // Send all data to the parent process.
+ sendAsyncMessage("SessionStore:update", {
+ data, telemetry, flushID,
+ isFinal: options.isFinal || false,
+ epoch: gCurrentEpoch
+ });
+ } catch (ex if ex && ex.result == Cr.NS_ERROR_OUT_OF_MEMORY) {
+ let telemetry = {
+ FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM: 1
+ };
+ sendAsyncMessage("SessionStore:error", {
+ telemetry
+ });
+ }
+ },
+};
+
+EventListener.init();
+MessageListener.init();
+FormDataListener.init();
+PageStyleListener.init();
+SessionHistoryListener.init();
+SessionStorageListener.init();
+ScrollPositionListener.init();
+DocShellCapabilitiesListener.init();
+PrivacyListener.init();
+MessageQueue.init();
+
+function handleRevivedTab() {
+ if (!content) {
+ removeEventListener("pagehide", handleRevivedTab);
+ return;
+ }
+
+ if (content.document.documentURI.startsWith("about:tabcrashed")) {
+ if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ // Sanity check - we'd better be loading this in a non-remote browser.
+ throw new Error("We seem to be navigating away from about:tabcrashed in " +
+ "a non-remote browser. This should really never happen.");
+ }
+
+ removeEventListener("pagehide", handleRevivedTab);
+
+ // Notify the parent.
+ sendAsyncMessage("SessionStore:crashedTabRevived");
+ }
+}
+
+// If we're browsing from the tab crashed UI to a blacklisted URI that keeps
+// this browser non-remote, we'll handle that in a pagehide event.
+addEventListener("pagehide", handleRevivedTab);
+
+addEventListener("unload", () => {
+ // Upon frameLoader destruction, send a final update message to
+ // the parent and flush all data currently held in the child.
+ MessageQueue.send({isFinal: true});
+
+ // If we're browsing from the tab crashed UI to a URI that causes the tab
+ // to go remote again, we catch this in the unload event handler, because
+ // swapping out the non-remote browser for a remote one in
+ // tabbrowser.xml's updateBrowserRemoteness doesn't cause the pagehide
+ // event to be fired.
+ handleRevivedTab();
+
+ // Remove all registered nsIObservers.
+ PageStyleListener.uninit();
+ SessionStorageListener.uninit();
+ SessionHistoryListener.uninit();
+ MessageQueue.uninit();
+
+ // Remove progress listeners.
+ gContentRestore.resetRestore();
+
+ // We don't need to take care of any gFrameTree observers as the gFrameTree
+ // will die with the content script. The same goes for the privacy transition
+ // observer that will die with the docShell when the tab is closed.
+});
diff --git a/browser/components/sessionstore/jar.mn b/browser/components/sessionstore/jar.mn
new file mode 100644
index 000000000..7e5bc07dc
--- /dev/null
+++ b/browser/components/sessionstore/jar.mn
@@ -0,0 +1,8 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+* content/browser/aboutSessionRestore.xhtml (content/aboutSessionRestore.xhtml)
+ content/browser/aboutSessionRestore.js (content/aboutSessionRestore.js)
+ content/browser/content-sessionStore.js (content/content-sessionStore.js)
diff --git a/browser/components/sessionstore/moz.build b/browser/components/sessionstore/moz.build
new file mode 100644
index 000000000..8a8221c9f
--- /dev/null
+++ b/browser/components/sessionstore/moz.build
@@ -0,0 +1,52 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += [
+ 'nsISessionStartup.idl',
+ 'nsISessionStore.idl',
+]
+
+XPIDL_MODULE = 'sessionstore'
+
+EXTRA_COMPONENTS += [
+ 'nsSessionStartup.js',
+ 'nsSessionStore.js',
+ 'nsSessionStore.manifest',
+]
+
+EXTRA_JS_MODULES.sessionstore = [
+ 'ContentRestore.jsm',
+ 'DocShellCapabilities.jsm',
+ 'FrameTree.jsm',
+ 'GlobalState.jsm',
+ 'PageStyle.jsm',
+ 'PrivacyFilter.jsm',
+ 'PrivacyLevel.jsm',
+ 'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
+ 'RunState.jsm',
+ 'SessionCookies.jsm',
+ 'SessionFile.jsm',
+ 'SessionHistory.jsm',
+ 'SessionMigration.jsm',
+ 'SessionSaver.jsm',
+ 'SessionStorage.jsm',
+ 'SessionStore.jsm',
+ 'SessionWorker.js',
+ 'SessionWorker.jsm',
+ 'StartupPerformance.jsm',
+ 'TabAttributes.jsm',
+ 'TabState.jsm',
+ 'TabStateCache.jsm',
+ 'TabStateFlusher.jsm',
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Session Restore')
diff --git a/browser/components/sessionstore/nsISessionStartup.idl b/browser/components/sessionstore/nsISessionStartup.idl
new file mode 100644
index 000000000..2321ac310
--- /dev/null
+++ b/browser/components/sessionstore/nsISessionStartup.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 "nsISupports.idl"
+
+/**
+ * nsISessionStore keeps track of the current browsing state - i.e.
+ * tab history, cookies, scroll state, form data, and window features
+ * - and allows to restore everything into one window.
+ */
+
+[scriptable, uuid(934697e4-3807-47f8-b6c9-6caa8d83ccd1)]
+interface nsISessionStartup: nsISupports
+{
+ /**
+ * Return a promise that is resolved once initialization
+ * is complete.
+ */
+ readonly attribute jsval onceInitialized;
+
+ // Get session state
+ readonly attribute jsval state;
+
+ /**
+ * Determines whether there is a pending session restore. Should only be
+ * called after initialization has completed.
+ */
+ boolean doRestore();
+
+ /**
+ * Determines whether automatic session restoration is enabled for this
+ * launch of the browser. This does not include crash restoration, and will
+ * return false if restoration will only be caused by a crash.
+ */
+ boolean isAutomaticRestoreEnabled();
+
+ /**
+ * Returns whether we will restore a session that ends up replacing the
+ * homepage. The browser uses this to not start loading the homepage if
+ * we're going to stop its load anyway shortly after.
+ *
+ * This is meant to be an optimization for the average case that loading the
+ * session file finishes before we may want to start loading the default
+ * homepage. Should this be called before the session file has been read it
+ * will just return false.
+ */
+ readonly attribute bool willOverrideHomepage;
+
+ /**
+ * What type of session we're restoring.
+ * NO_SESSION There is no data available from the previous session
+ * RECOVER_SESSION The last session crashed. It will either be restored or
+ * about:sessionrestore will be shown.
+ * RESUME_SESSION The previous session should be restored at startup
+ * DEFER_SESSION The previous session is fine, but it shouldn't be restored
+ * without explicit action (with the exception of pinned tabs)
+ */
+ const unsigned long NO_SESSION = 0;
+ const unsigned long RECOVER_SESSION = 1;
+ const unsigned long RESUME_SESSION = 2;
+ const unsigned long DEFER_SESSION = 3;
+
+ readonly attribute unsigned long sessionType;
+ readonly attribute bool previousSessionCrashed;
+};
diff --git a/browser/components/sessionstore/nsISessionStore.idl b/browser/components/sessionstore/nsISessionStore.idl
new file mode 100644
index 000000000..0d2500ef7
--- /dev/null
+++ b/browser/components/sessionstore/nsISessionStore.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 nsIDOMWindow;
+interface nsIDOMNode;
+
+/**
+ * nsISessionStore keeps track of the current browsing state - i.e.
+ * tab history, cookies, scroll state, form data, and window features
+ * - and allows to restore everything into one browser window.
+ *
+ * The nsISessionStore API operates mostly on browser windows and the tabbrowser
+ * tabs contained in them:
+ *
+ * * "Browser windows" are those DOM windows having loaded
+ * chrome://browser/content/browser.xul . From overlays you can just pass the
+ * global |window| object to the API, though (or |top| from a sidebar).
+ * From elsewhere you can get browser windows through the nsIWindowMediator
+ * by looking for "navigator:browser" windows.
+ *
+ * * "Tabbrowser tabs" are all the child nodes of a browser window's
+ * |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
+ */
+
+[scriptable, uuid(4580f5eb-693d-423d-b0ce-2cb20a962e4d)]
+interface nsISessionStore : nsISupports
+{
+ /**
+ * Is it possible to restore the previous session. Will always be false when
+ * in Private Browsing mode.
+ */
+ attribute boolean canRestoreLastSession;
+
+ /**
+ * Restore the previous session if possible. This will not overwrite the
+ * current session. Instead the previous session will be merged into the
+ * current session. Current windows will be reused if they were windows that
+ * pinned tabs were previously restored into. New windows will be opened as
+ * needed.
+ *
+ * Note: This will throw if there is no previous state to restore. Check with
+ * canRestoreLastSession first to avoid thrown errors.
+ */
+ void restoreLastSession();
+
+ /**
+ * Get the current browsing state.
+ * @returns a JSON string representing the session state.
+ */
+ AString getBrowserState();
+
+ /**
+ * Set the browsing state.
+ * This will immediately restore the state of the whole application to the state
+ * passed in, *replacing* the current session.
+ *
+ * @param aState is a JSON string representing the session state.
+ */
+ void setBrowserState(in AString aState);
+
+ /**
+ * @param aWindow is the browser window whose state is to be returned.
+ *
+ * @returns a JSON string representing a session state with only one window.
+ */
+ AString getWindowState(in nsIDOMWindow aWindow);
+
+ /**
+ * @param aWindow is the browser window whose state is to be set.
+ * @param aState is a JSON string representing a session state.
+ * @param aOverwrite boolean overwrite existing tabs
+ */
+ void setWindowState(in nsIDOMWindow aWindow, in AString aState, in boolean aOverwrite);
+
+ /**
+ * @param aTab is the tabbrowser tab whose state is to be returned.
+ *
+ * @returns a JSON string representing the state of the tab
+ * (note: doesn't contain cookies - if you need them, use getWindowState instead).
+ */
+ AString getTabState(in nsIDOMNode aTab);
+
+ /**
+ * @param aTab is the tabbrowser tab whose state is to be set.
+ * @param aState is a JSON string representing a session state.
+ */
+ void setTabState(in nsIDOMNode aTab, in AString aState);
+
+ /**
+ * Duplicates a given tab as thoroughly as possible.
+ *
+ * @param aWindow is the browser window into which the tab will be duplicated.
+ * @param aTab is the tabbrowser tab to duplicate (can be from a different window).
+ * @param aDelta is the offset to the history entry to load in the duplicated tab.
+ * @returns a reference to the newly created tab.
+ */
+ nsIDOMNode duplicateTab(in nsIDOMWindow aWindow, in nsIDOMNode aTab,
+ [optional] in long aDelta);
+
+ /**
+ * Get the number of restore-able tabs for a browser window
+ */
+ unsigned long getClosedTabCount(in nsIDOMWindow aWindow);
+
+ /**
+ * Get closed tab data
+ *
+ * @param aWindow is the browser window for which to get closed tab data
+ * @returns a JSON string representing the list of closed tabs.
+ */
+ AString getClosedTabData(in nsIDOMWindow aWindow);
+
+ /**
+ * @param aWindow is the browser window to reopen a closed tab in.
+ * @param aIndex is the index of the tab to be restored (FIFO ordered).
+ * @returns a reference to the reopened tab.
+ */
+ nsIDOMNode undoCloseTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
+
+ /**
+ * @param aWindow is the browser window associated with the closed tab.
+ * @param aIndex is the index of the closed tab to be removed (FIFO ordered).
+ */
+ nsIDOMNode forgetClosedTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
+
+ /**
+ * Get the number of restore-able windows
+ */
+ unsigned long getClosedWindowCount();
+
+ /**
+ * Get closed windows data
+ *
+ * @returns a JSON string representing the list of closed windows.
+ */
+ AString getClosedWindowData();
+
+ /**
+ * @param aIndex is the index of the windows to be restored (FIFO ordered).
+ * @returns the nsIDOMWindow object of the reopened window
+ */
+ nsIDOMWindow undoCloseWindow(in unsigned long aIndex);
+
+ /**
+ * @param aIndex is the index of the closed window to be removed (FIFO ordered).
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * when aIndex does not map to a closed window
+ */
+ nsIDOMNode forgetClosedWindow(in unsigned long aIndex);
+
+ /**
+ * @param aWindow is the window to get the value for.
+ * @param aKey is the value's name.
+ *
+ * @returns A string value or an empty string if none is set.
+ */
+ AString getWindowValue(in nsIDOMWindow aWindow, in AString aKey);
+
+ /**
+ * @param aWindow is the browser window to set the value for.
+ * @param aKey is the value's name.
+ * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
+ */
+ void setWindowValue(in nsIDOMWindow aWindow, in AString aKey, in jsval aStringValue);
+
+ /**
+ * @param aWindow is the browser window to get the value for.
+ * @param aKey is the value's name.
+ */
+ void deleteWindowValue(in nsIDOMWindow aWindow, in AString aKey);
+
+ /**
+ * @param aTab is the tabbrowser tab to get the value for.
+ * @param aKey is the value's name.
+ *
+ * @returns A string value or an empty string if none is set.
+ */
+ AString getTabValue(in nsIDOMNode aTab, in AString aKey);
+
+ /**
+ * @param aTab is the tabbrowser tab to set the value for.
+ * @param aKey is the value's name.
+ * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
+ */
+ void setTabValue(in nsIDOMNode aTab, in AString aKey, in jsval aStringValue);
+
+ /**
+ * @param aTab is the tabbrowser tab to get the value for.
+ * @param aKey is the value's name.
+ */
+ void deleteTabValue(in nsIDOMNode aTab, in AString aKey);
+
+ /**
+ * @param aKey is the value's name.
+ *
+ * @returns A string value or an empty string if none is set.
+ */
+ AString getGlobalValue(in AString aKey);
+
+ /**
+ * @param aKey is the value's name.
+ * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
+ */
+ void setGlobalValue(in AString aKey, in jsval aStringValue);
+
+ /**
+ * @param aTab is the browser tab to get the value for.
+ * @param aKey is the value's name.
+ */
+ void deleteGlobalValue(in AString aKey);
+
+ /**
+ * @param aName is the name of the attribute to save/restore for all tabbrowser tabs.
+ */
+ void persistTabAttribute(in AString aName);
+};
diff --git a/browser/components/sessionstore/nsSessionStartup.js b/browser/components/sessionstore/nsSessionStartup.js
new file mode 100644
index 000000000..7593c48ec
--- /dev/null
+++ b/browser/components/sessionstore/nsSessionStartup.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";
+
+/**
+ * Session Storage and Restoration
+ *
+ * Overview
+ * This service reads user's session file at startup, and makes a determination
+ * as to whether the session should be restored. It will restore the session
+ * under the circumstances described below. If the auto-start Private Browsing
+ * mode is active, however, the session is never restored.
+ *
+ * Crash Detection
+ * The CrashMonitor is used to check if the final session state was successfully
+ * written at shutdown of the last session. If we did not reach
+ * 'sessionstore-final-state-write-complete', then it's assumed that the browser
+ * has previously crashed and we should restore the session.
+ *
+ * Forced Restarts
+ * In the event that a restart is required due to application update or extension
+ * installation, set the browser.sessionstore.resume_session_once pref to true,
+ * and the session will be restored the next time the browser starts.
+ *
+ * Always Resume
+ * This service will always resume the session if the integer pref
+ * browser.startup.page is set to 3.
+ */
+
+/* :::::::: Constants and Helpers ::::::::::::::: */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
+ "resource:///modules/sessionstore/SessionFile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "StartupPerformance",
+ "resource:///modules/sessionstore/StartupPerformance.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
+ "resource://gre/modules/CrashMonitor.jsm");
+
+const STATE_RUNNING_STR = "running";
+
+// 'browser.startup.page' preference value to resume the previous session.
+const BROWSER_STARTUP_RESUME_SESSION = 3;
+
+function debug(aMsg) {
+ aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
+ Services.console.logStringMessage(aMsg);
+}
+function warning(aMsg, aException) {
+ let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
+consoleMsg.init(aMsg, aException.fileName, null, aException.lineNumber, 0, Ci.nsIScriptError.warningFlag, "component javascript");
+ Services.console.logMessage(consoleMsg);
+}
+
+var gOnceInitializedDeferred = (function () {
+ let deferred = {};
+
+ deferred.promise = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+
+ return deferred;
+})();
+
+/* :::::::: The Service ::::::::::::::: */
+
+function SessionStartup() {
+}
+
+SessionStartup.prototype = {
+
+ // the state to restore at startup
+ _initialState: null,
+ _sessionType: Ci.nsISessionStartup.NO_SESSION,
+ _initialized: false,
+
+ // Stores whether the previous session crashed.
+ _previousSessionCrashed: null,
+
+/* ........ Global Event Handlers .............. */
+
+ /**
+ * Initialize the component
+ */
+ init: function sss_init() {
+ Services.obs.notifyObservers(null, "sessionstore-init-started", null);
+ StartupPerformance.init();
+
+ // do not need to initialize anything in auto-started private browsing sessions
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ this._initialized = true;
+ gOnceInitializedDeferred.resolve();
+ return;
+ }
+
+ SessionFile.read().then(
+ this._onSessionFileRead.bind(this),
+ console.error
+ );
+ },
+
+ // Wrap a string as a nsISupports
+ _createSupportsString: function ssfi_createSupportsString(aData) {
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = aData;
+ return string;
+ },
+
+ /**
+ * Complete initialization once the Session File has been read
+ *
+ * @param source The Session State string read from disk.
+ * @param parsed The object obtained by parsing |source| as JSON.
+ */
+ _onSessionFileRead: function ({source, parsed, noFilesFound}) {
+ this._initialized = true;
+
+ // Let observers modify the state before it is used
+ let supportsStateString = this._createSupportsString(source);
+ Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
+ let stateString = supportsStateString.data;
+
+ if (stateString != source) {
+ // The session has been modified by an add-on, reparse.
+ try {
+ this._initialState = JSON.parse(stateString);
+ } catch (ex) {
+ // That's not very good, an add-on has rewritten the initial
+ // state to something that won't parse.
+ warning("Observer rewrote the state to something that won't parse", ex);
+ }
+ } else {
+ // No need to reparse
+ this._initialState = parsed;
+ }
+
+ if (this._initialState == null) {
+ // No valid session found.
+ this._sessionType = Ci.nsISessionStartup.NO_SESSION;
+ Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
+ gOnceInitializedDeferred.resolve();
+ return;
+ }
+
+ let shouldResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
+ let shouldResumeSession = shouldResumeSessionOnce ||
+ Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
+
+ // If this is a normal restore then throw away any previous session
+ if (!shouldResumeSessionOnce && this._initialState) {
+ delete this._initialState.lastSessionState;
+ }
+
+ let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
+
+ CrashMonitor.previousCheckpoints.then(checkpoints => {
+ if (checkpoints) {
+ // If the previous session finished writing the final state, we'll
+ // assume there was no crash.
+ this._previousSessionCrashed = !checkpoints["sessionstore-final-state-write-complete"];
+
+ } else {
+ // If the Crash Monitor could not load a checkpoints file it will
+ // provide null. This could occur on the first run after updating to
+ // a version including the Crash Monitor, or if the checkpoints file
+ // was removed, or on first startup with this profile, or after Firefox Reset.
+
+ if (noFilesFound) {
+ // There was no checkpoints file and no sessionstore.js or its backups
+ // so we will assume that this was a fresh profile.
+ this._previousSessionCrashed = false;
+
+ } else {
+ // If this is the first run after an update, sessionstore.js should
+ // still contain the session.state flag to indicate if the session
+ // crashed. If it is not present, we will assume this was not the first
+ // run after update and the checkpoints file was somehow corrupted or
+ // removed by a crash.
+ //
+ // If the session.state flag is present, we will fallback to using it
+ // for crash detection - If the last write of sessionstore.js had it
+ // set to "running", we crashed.
+ let stateFlagPresent = (this._initialState.session &&
+ this._initialState.session.state);
+
+
+ this._previousSessionCrashed = !stateFlagPresent ||
+ (this._initialState.session.state == STATE_RUNNING_STR);
+ }
+ }
+
+ // Report shutdown success via telemetry. Shortcoming here are
+ // being-killed-by-OS-shutdown-logic, shutdown freezing after
+ // session restore was written, etc.
+ Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!this._previousSessionCrashed);
+
+ // set the startup type
+ if (this._previousSessionCrashed && resumeFromCrash)
+ this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
+ else if (!this._previousSessionCrashed && shouldResumeSession)
+ this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
+ else if (this._initialState)
+ this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
+ else
+ this._initialState = null; // reset the state
+
+ Services.obs.addObserver(this, "sessionstore-windows-restored", true);
+
+ if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
+ Services.obs.addObserver(this, "browser:purge-session-history", true);
+
+ // We're ready. Notify everyone else.
+ Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
+ gOnceInitializedDeferred.resolve();
+ });
+ },
+
+ /**
+ * Handle notifications
+ */
+ observe: function sss_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "app-startup":
+ Services.obs.addObserver(this, "final-ui-startup", true);
+ Services.obs.addObserver(this, "quit-application", true);
+ break;
+ case "final-ui-startup":
+ Services.obs.removeObserver(this, "final-ui-startup");
+ Services.obs.removeObserver(this, "quit-application");
+ this.init();
+ break;
+ case "quit-application":
+ // no reason for initializing at this point (cf. bug 409115)
+ Services.obs.removeObserver(this, "final-ui-startup");
+ Services.obs.removeObserver(this, "quit-application");
+ if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ break;
+ case "sessionstore-windows-restored":
+ Services.obs.removeObserver(this, "sessionstore-windows-restored");
+ // free _initialState after nsSessionStore is done with it
+ this._initialState = null;
+ break;
+ case "browser:purge-session-history":
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ // reset all state on sanitization
+ this._sessionType = Ci.nsISessionStartup.NO_SESSION;
+ break;
+ }
+ },
+
+/* ........ Public API ................*/
+
+ get onceInitialized() {
+ return gOnceInitializedDeferred.promise;
+ },
+
+ /**
+ * Get the session state as a jsval
+ */
+ get state() {
+ return this._initialState;
+ },
+
+ /**
+ * Determines whether there is a pending session restore. Should only be
+ * called after initialization has completed.
+ * @returns bool
+ */
+ doRestore: function sss_doRestore() {
+ return this._willRestore();
+ },
+
+ /**
+ * Determines whether automatic session restoration is enabled for this
+ * launch of the browser. This does not include crash restoration. In
+ * particular, if session restore is configured to restore only in case of
+ * crash, this method returns false.
+ * @returns bool
+ */
+ isAutomaticRestoreEnabled: function () {
+ return Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
+ Services.prefs.getIntPref("browser.startup.page") == BROWSER_STARTUP_RESUME_SESSION;
+ },
+
+ /**
+ * Determines whether there is a pending session restore.
+ * @returns bool
+ */
+ _willRestore: function () {
+ return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
+ this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
+ },
+
+ /**
+ * Returns whether we will restore a session that ends up replacing the
+ * homepage. The browser uses this to not start loading the homepage if
+ * we're going to stop its load anyway shortly after.
+ *
+ * This is meant to be an optimization for the average case that loading the
+ * session file finishes before we may want to start loading the default
+ * homepage. Should this be called before the session file has been read it
+ * will just return false.
+ *
+ * @returns bool
+ */
+ get willOverrideHomepage() {
+ if (this._initialState && this._willRestore()) {
+ let windows = this._initialState.windows || null;
+ // If there are valid windows with not only pinned tabs, signal that we
+ // will override the default homepage by restoring a session.
+ return windows && windows.some(w => w.tabs.some(t => !t.pinned));
+ }
+ return false;
+ },
+
+ /**
+ * Get the type of pending session store, if any.
+ */
+ get sessionType() {
+ return this._sessionType;
+ },
+
+ /**
+ * Get whether the previous session crashed.
+ */
+ get previousSessionCrashed() {
+ return this._previousSessionCrashed;
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISessionStartup]),
+ classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);
diff --git a/browser/components/sessionstore/nsSessionStore.js b/browser/components/sessionstore/nsSessionStore.js
new file mode 100644
index 000000000..8d96178ce
--- /dev/null
+++ b/browser/components/sessionstore/nsSessionStore.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";
+
+/**
+ * Session Storage and Restoration
+ *
+ * Overview
+ * This service keeps track of a user's session, storing the various bits
+ * required to return the browser to its current state. The relevant data is
+ * stored in memory, and is periodically saved to disk in a file in the
+ * profile directory. The service is started at first window load, in
+ * delayedStartup, and will restore the session from the data received from
+ * the nsSessionStartup service.
+ */
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
+
+function SessionStoreService() {}
+
+// The SessionStore module's object is frozen. We need to modify our prototype
+// and add some properties so let's just copy the SessionStore object.
+Object.keys(SessionStore).forEach(function (aName) {
+ let desc = Object.getOwnPropertyDescriptor(SessionStore, aName);
+ Object.defineProperty(SessionStoreService.prototype, aName, desc);
+});
+
+SessionStoreService.prototype.classID =
+ Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}");
+SessionStoreService.prototype.QueryInterface =
+ XPCOMUtils.generateQI([Ci.nsISessionStore]);
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStoreService]);
diff --git a/browser/components/sessionstore/nsSessionStore.manifest b/browser/components/sessionstore/nsSessionStore.manifest
new file mode 100644
index 000000000..9b5819c6a
--- /dev/null
+++ b/browser/components/sessionstore/nsSessionStore.manifest
@@ -0,0 +1,15 @@
+# This component must restrict its registration for the app-startup category
+# to the specific list of apps that use it so it doesn't get loaded in xpcshell.
+# Thus we restrict it to these apps:
+#
+# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
+# browser: {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
+# graphene: {d1bfe7d9-c01e-4237-998b-7b5f960a4314}
+
+component {5280606b-2510-4fe0-97ef-9b5a22eafe6b} nsSessionStore.js
+contract @mozilla.org/browser/sessionstore;1 {5280606b-2510-4fe0-97ef-9b5a22eafe6b}
+component {ec7a6c20-e081-11da-8ad9-0800200c9a66} nsSessionStartup.js
+contract @mozilla.org/browser/sessionstartup;1 {ec7a6c20-e081-11da-8ad9-0800200c9a66}
+category app-startup nsSessionStartup service,@mozilla.org/browser/sessionstartup;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} application={d1bfe7d9-c01e-4237-998b-7b5f960a4314}
diff --git a/browser/components/sessionstore/test/.eslintrc.js b/browser/components/sessionstore/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/sessionstore/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/sessionstore/test/browser.ini b/browser/components/sessionstore/test/browser.ini
new file mode 100644
index 000000000..37154a0cc
--- /dev/null
+++ b/browser/components/sessionstore/test/browser.ini
@@ -0,0 +1,242 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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_506482.js is disabled because of frequent failures (bug 538672)
+# browser_526613.js is disabled because of frequent failures (bug 534489)
+# browser_589246.js is disabled for leaking browser windows (bug 752467)
+# browser_580512.js is disabled for leaking browser windows (bug 752467)
+
+[DEFAULT]
+support-files =
+ head.js
+ content.js
+ content-forms.js
+ browser_cookies.sjs
+ browser_formdata_sample.html
+ browser_formdata_xpath_sample.html
+ browser_frametree_sample.html
+ browser_frametree_sample_frameset.html
+ browser_frame_history_index.html
+ browser_frame_history_index2.html
+ browser_frame_history_index_blank.html
+ browser_frame_history_a.html
+ browser_frame_history_b.html
+ browser_frame_history_c.html
+ browser_frame_history_c1.html
+ browser_frame_history_c2.html
+ browser_form_restore_events_sample.html
+ browser_formdata_format_sample.html
+ browser_pageStyle_sample.html
+ browser_pageStyle_sample_nested.html
+ browser_sessionHistory_slow.sjs
+ browser_scrollPositions_sample.html
+ browser_scrollPositions_sample_frameset.html
+ browser_scrollPositions_readerModeArticle.html
+ browser_sessionStorage.html
+ browser_248970_b_sample.html
+ browser_339445_sample.html
+ browser_423132_sample.html
+ browser_447951_sample.html
+ browser_454908_sample.html
+ browser_456342_sample.xhtml
+ browser_463205_sample.html
+ browser_463206_sample.html
+ browser_466937_sample.html
+ browser_485482_sample.html
+ browser_637020_slow.sjs
+ browser_662743_sample.html
+ browser_739531_sample.html
+ browser_911547_sample.html
+ browser_911547_sample.html^headers^
+ restore_redirect_http.html
+ restore_redirect_http.html^headers^
+ restore_redirect_js.html
+ restore_redirect_target.html
+ browser_1234021_page.html
+
+#NB: the following are disabled
+# browser_464620_a.html
+# browser_464620_b.html
+# browser_464620_xd.html
+
+
+#disabled-for-intermittent-failures--bug-766044, browser_459906_empty.html
+#disabled-for-intermittent-failures--bug-766044, browser_459906_sample.html
+#disabled-for-intermittent-failures--bug-765389, browser_461743_sample.html
+
+[browser_aboutPrivateBrowsing.js]
+[browser_aboutSessionRestore.js]
+[browser_async_duplicate_tab.js]
+[browser_async_flushes.js]
+run-if = e10s && crashreporter
+skip-if = debug # bug 1167933
+[browser_async_remove_tab.js]
+run-if = e10s
+skip-if = debug # bug 1211084
+[browser_attributes.js]
+[browser_backup_recovery.js]
+[browser_broadcast.js]
+[browser_capabilities.js]
+[browser_cleaner.js]
+[browser_cookies.js]
+[browser_crashedTabs.js]
+skip-if = !e10s || !crashreporter
+[browser_unrestored_crashedTabs.js]
+skip-if = !e10s || !crashreporter
+[browser_revive_crashed_bg_tabs.js]
+skip-if = !e10s || !crashreporter
+[browser_dying_cache.js]
+[browser_dynamic_frames.js]
+[browser_form_restore_events.js]
+[browser_formdata.js]
+[browser_formdata_cc.js]
+[browser_formdata_format.js]
+[browser_formdata_xpath.js]
+[browser_frametree.js]
+[browser_frame_history.js]
+[browser_global_store.js]
+[browser_history_persist.js]
+[browser_label_and_icon.js]
+[browser_merge_closed_tabs.js]
+[browser_page_title.js]
+[browser_pageStyle.js]
+[browser_pending_tabs.js]
+[browser_privatetabs.js]
+[browser_purge_shistory.js]
+skip-if = e10s # Bug 1271024
+[browser_replace_load.js]
+[browser_restore_redirect.js]
+[browser_restore_cookies_noOriginAttributes.js]
+[browser_scrollPositions.js]
+[browser_scrollPositionsReaderMode.js]
+[browser_sessionHistory.js]
+[browser_sessionStorage.js]
+[browser_sessionStorage_size.js]
+[browser_swapDocShells.js]
+[browser_switch_remoteness.js]
+run-if = e10s
+[browser_upgrade_backup.js]
+[browser_windowRestore_perwindowpb.js]
+[browser_248970_b_perwindowpb.js]
+# Disabled because of leaks.
+# Re-enabling and rewriting this test is tracked in bug 936919.
+skip-if = true
+[browser_339445.js]
+[browser_345898.js]
+[browser_350525.js]
+[browser_354894_perwindowpb.js]
+[browser_367052.js]
+[browser_393716.js]
+[browser_394759_basic.js]
+# Disabled for intermittent failures, bug 944372.
+skip-if = true
+[browser_394759_behavior.js]
+[browser_394759_perwindowpb.js]
+[browser_394759_purge.js]
+[browser_423132.js]
+[browser_447951.js]
+[browser_454908.js]
+[browser_456342.js]
+[browser_461634.js]
+[browser_463205.js]
+[browser_463206.js]
+[browser_464199.js]
+[browser_465215.js]
+[browser_465223.js]
+[browser_466937.js]
+[browser_467409-backslashplosion.js]
+[browser_477657.js]
+[browser_480893.js]
+[browser_485482.js]
+[browser_485563.js]
+[browser_490040.js]
+[browser_491168.js]
+[browser_491577.js]
+[browser_495495.js]
+[browser_500328.js]
+[browser_514751.js]
+[browser_522375.js]
+[browser_522545.js]
+[browser_524745.js]
+[browser_528776.js]
+[browser_579868.js]
+[browser_579879.js]
+skip-if = (os == 'linux' && e10s && (debug||asan)) # Bug 1234404
+[browser_581937.js]
+[browser_586147.js]
+[browser_586068-apptabs.js]
+[browser_586068-apptabs_ondemand.js]
+[browser_586068-browser_state_interrupted.js]
+[browser_586068-cascade.js]
+[browser_586068-multi_window.js]
+[browser_586068-reload.js]
+[browser_586068-select.js]
+[browser_586068-window_state.js]
+[browser_586068-window_state_override.js]
+[browser_588426.js]
+[browser_590268.js]
+[browser_590563.js]
+[browser_595601-restore_hidden.js]
+[browser_597071.js]
+skip-if = true # Needs to be rewritten as Marionette test, bug 995916
+[browser_599909.js]
+[browser_600545.js]
+[browser_601955.js]
+[browser_607016.js]
+[browser_615394-SSWindowState_events.js]
+[browser_618151.js]
+[browser_623779.js]
+[browser_624727.js]
+[browser_628270.js]
+[browser_635418.js]
+[browser_636279.js]
+[browser_637020.js]
+[browser_644409-scratchpads.js]
+[browser_645428.js]
+[browser_659591.js]
+[browser_662743.js]
+[browser_662812.js]
+[browser_665702-state_session.js]
+[browser_682507.js]
+[browser_687710.js]
+[browser_687710_2.js]
+[browser_694378.js]
+[browser_701377.js]
+[browser_705597.js]
+[browser_707862.js]
+[browser_739531.js]
+[browser_739805.js]
+[browser_819510_perwindowpb.js]
+skip-if = (os == 'win' && bits == 64) # Bug 1284312
+
+# Disabled for frequent intermittent failures
+[browser_464620_a.js]
+skip-if = true
+[browser_464620_b.js]
+skip-if = true
+
+# Disabled on OS X:
+[browser_625016.js]
+skip-if = os == "mac"
+
+[browser_911547.js]
+[browser_send_async_message_oom.js]
+[browser_multiple_navigateAndRestore.js]
+run-if = e10s
+[browser_async_window_flushing.js]
+[browser_forget_async_closings.js]
+[browser_newtab_userTypedValue.js]
+[browser_parentProcessRestoreHash.js]
+run-if = e10s
+[browser_sessionStoreContainer.js]
+[browser_windowStateContainer.js]
+[browser_1234021.js]
+[browser_remoteness_flip_on_restore.js]
+run-if = e10s
+[browser_background_tab_crash.js]
+run-if = e10s && crashreporter
+
+# Disabled on debug for frequent intermittent failures:
+[browser_undoCloseById.js]
+skip-if = debug
diff --git a/browser/components/sessionstore/test/browser_1234021.js b/browser/components/sessionstore/test/browser_1234021.js
new file mode 100644
index 000000000..a307d1e01
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_1234021.js
@@ -0,0 +1,18 @@
+"use strict";
+
+const PREF = 'network.cookie.cookieBehavior';
+const PAGE_URL = 'http://mochi.test:8888/browser/' +
+ 'browser/components/sessionstore/test/browser_1234021_page.html';
+const BEHAVIOR_REJECT = 2;
+
+add_task(function* test() {
+ yield pushPrefs([PREF, BEHAVIOR_REJECT]);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser: gBrowser,
+ url: PAGE_URL
+ }, function* handler(aBrowser) {
+ yield TabStateFlusher.flush(aBrowser);
+ ok(true, "Flush didn't time out");
+ });
+});
diff --git a/browser/components/sessionstore/test/browser_1234021_page.html b/browser/components/sessionstore/test/browser_1234021_page.html
new file mode 100644
index 000000000..4a74fbc02
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_1234021_page.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html>
+ <script>
+ sessionStorage
+ </script>
+</html>
diff --git a/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js b/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js
new file mode 100644
index 000000000..f5775cd5b
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_248970_b_perwindowpb.js
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ /** Test (B) for Bug 248970 **/
+ waitForExplicitFinish();
+
+ let windowsToClose = [];
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ let filePath = file.path;
+ let fieldList = {
+ "//input[@name='input']": Date.now().toString(),
+ "//input[@name='spaced 1']": Math.random().toString(),
+ "//input[3]": "three",
+ "//input[@type='checkbox']": true,
+ "//input[@name='uncheck']": false,
+ "//input[@type='radio'][1]": false,
+ "//input[@type='radio'][2]": true,
+ "//input[@type='radio'][3]": false,
+ "//select": 2,
+ "//select[@multiple]": [1, 3],
+ "//textarea[1]": "",
+ "//textarea[2]": "Some text... " + Math.random(),
+ "//textarea[3]": "Some more text\n" + new Date(),
+ "//input[@type='file']": filePath
+ };
+
+ registerCleanupFunction(function* () {
+ for (let win of windowsToClose) {
+ yield BrowserTestUtils.closeWindow(win);
+ }
+ });
+
+ function test(aLambda) {
+ try {
+ return aLambda() || true;
+ } catch(ex) { }
+ return false;
+ }
+
+ function getElementByXPath(aTab, aQuery) {
+ let doc = aTab.linkedBrowser.contentDocument;
+ let xptype = Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
+ return doc.evaluate(aQuery, doc, null, xptype, null).singleNodeValue;
+ }
+
+ function setFormValue(aTab, aQuery, aValue) {
+ let node = getElementByXPath(aTab, aQuery);
+ if (typeof aValue == "string")
+ node.value = aValue;
+ else if (typeof aValue == "boolean")
+ node.checked = aValue;
+ else if (typeof aValue == "number")
+ node.selectedIndex = aValue;
+ else
+ Array.forEach(node.options, (aOpt, aIx) =>
+ (aOpt.selected = aValue.indexOf(aIx) > -1));
+ }
+
+ function compareFormValue(aTab, aQuery, aValue) {
+ let node = getElementByXPath(aTab, aQuery);
+ if (!node)
+ return false;
+ if (node instanceof Ci.nsIDOMHTMLInputElement)
+ return aValue == (node.type == "checkbox" || node.type == "radio" ?
+ node.checked : node.value);
+ if (node instanceof Ci.nsIDOMHTMLTextAreaElement)
+ return aValue == node.value;
+ if (!node.multiple)
+ return aValue == node.selectedIndex;
+ return Array.every(node.options, (aOpt, aIx) =>
+ (aValue.indexOf(aIx) > -1) == aOpt.selected);
+ }
+
+ //////////////////////////////////////////////////////////////////
+ // Test (B) : Session data restoration between windows //
+ //////////////////////////////////////////////////////////////////
+
+ let rootDir = getRootDirectory(gTestPath);
+ const testURL = rootDir + "browser_248970_b_sample.html";
+ const testURL2 = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_248970_b_sample.html";
+
+ whenNewWindowLoaded({ private: false }, function(aWin) {
+ windowsToClose.push(aWin);
+
+ // get closed tab count
+ let count = ss.getClosedTabCount(aWin);
+ let max_tabs_undo =
+ Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
+ ok(0 <= count && count <= max_tabs_undo,
+ "getClosedTabCount should return zero or at most max_tabs_undo");
+
+ // setup a state for tab (A) so we can check later that is restored
+ let key = "key";
+ let value = "Value " + Math.random();
+ let state = { entries: [{ url: testURL }], extData: { key: value } };
+
+ // public session, add new tab: (A)
+ let tab_A = aWin.gBrowser.addTab(testURL);
+ ss.setTabState(tab_A, JSON.stringify(state));
+ promiseBrowserLoaded(tab_A.linkedBrowser).then(() => {
+ // make sure that the next closed tab will increase getClosedTabCount
+ Services.prefs.setIntPref(
+ "browser.sessionstore.max_tabs_undo", max_tabs_undo + 1)
+
+ // populate tab_A with form data
+ for (let i in fieldList)
+ setFormValue(tab_A, i, fieldList[i]);
+
+ // public session, close tab: (A)
+ aWin.gBrowser.removeTab(tab_A);
+
+ // verify that closedTabCount increased
+ ok(ss.getClosedTabCount(aWin) > count,
+ "getClosedTabCount has increased after closing a tab");
+
+ // verify tab: (A), in undo list
+ let tab_A_restored = test(() => ss.undoCloseTab(aWin, 0));
+ ok(tab_A_restored, "a tab is in undo list");
+ promiseTabRestored(tab_A_restored).then(() => {
+ is(testURL, tab_A_restored.linkedBrowser.currentURI.spec,
+ "it's the same tab that we expect");
+ aWin.gBrowser.removeTab(tab_A_restored);
+
+ whenNewWindowLoaded({ private: true }, function(aWin) {
+ windowsToClose.push(aWin);
+
+ // setup a state for tab (B) so we can check that its duplicated
+ // properly
+ let key1 = "key1";
+ let value1 = "Value " + Math.random();
+ let state1 = {
+ entries: [{ url: testURL2 }], extData: { key1: value1 }
+ };
+
+ let tab_B = aWin.gBrowser.addTab(testURL2);
+ promiseTabState(tab_B, state1).then(() => {
+ // populate tab: (B) with different form data
+ for (let item in fieldList)
+ setFormValue(tab_B, item, fieldList[item]);
+
+ // duplicate tab: (B)
+ let tab_C = aWin.gBrowser.duplicateTab(tab_B);
+ promiseTabRestored(tab_C).then(() => {
+ // verify the correctness of the duplicated tab
+ is(ss.getTabValue(tab_C, key1), value1,
+ "tab successfully duplicated - correct state");
+
+ for (let item in fieldList)
+ ok(compareFormValue(tab_C, item, fieldList[item]),
+ "The value for \"" + item + "\" was correctly duplicated");
+
+ // private browsing session, close tab: (C) and (B)
+ aWin.gBrowser.removeTab(tab_C);
+ aWin.gBrowser.removeTab(tab_B);
+
+ finish();
+ });
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_248970_b_sample.html b/browser/components/sessionstore/test/browser_248970_b_sample.html
new file mode 100644
index 000000000..76c3ae1aa
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_248970_b_sample.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<meta charset="utf-8">
+<title>Test for bug 248970</title>
+
+<h3>Text Fields</h3>
+<input type="text" name="input">
+<input type="text" name="spaced 1">
+<input>
+
+<h3>Checkboxes and Radio buttons</h3>
+<input type="checkbox" name="check"> Check 1
+<input type="checkbox" name="uncheck" checked> Check 2
+<p>
+<input type="radio" name="group" value="1"> Radio 1
+<input type="radio" name="group" value="some"> Radio 2
+<input type="radio" name="group" checked> Radio 3
+
+<h3>Selects</h3>
+<select name="any">
+ <option value="1"> Select 1
+ <option value="some"> Select 2
+ <option>Select 3
+</select>
+<select multiple="multiple">
+ <option value=1> Multi-select 1
+ <option value=2> Multi-select 2
+ <option value=3> Multi-select 3
+ <option value=4> Multi-select 4
+</select>
+
+<h3>Text Areas</h3>
+<textarea name="testarea"></textarea>
+<textarea name="sized one" rows="5" cols="25"></textarea>
+<textarea></textarea>
+
+<h3>File Selector</h3>
+<input type="file">
diff --git a/browser/components/sessionstore/test/browser_339445.js b/browser/components/sessionstore/test/browser_339445.js
new file mode 100644
index 000000000..c38a6cb18
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_339445.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/. */
+
+add_task(function* test() {
+ /** Test for Bug 339445 **/
+
+ let testURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_339445_sample.html";
+
+ let tab = gBrowser.addTab(testURL);
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+ let doc = content.document;
+ is(doc.getElementById("storageTestItem").textContent, "PENDING",
+ "sessionStorage value has been set");
+ });
+
+ let tab2 = gBrowser.duplicateTab(tab);
+ yield promiseTabRestored(tab2);
+
+ yield ContentTask.spawn(tab2.linkedBrowser, null, function() {
+ let doc2 = content.document;
+ is(doc2.getElementById("storageTestItem").textContent, "SUCCESS",
+ "sessionStorage value has been duplicated");
+ });
+
+ // clean up
+ yield Promise.all([ BrowserTestUtils.removeTab(tab2),
+ BrowserTestUtils.removeTab(tab) ]);
+});
diff --git a/browser/components/sessionstore/test/browser_339445_sample.html b/browser/components/sessionstore/test/browser_339445_sample.html
new file mode 100644
index 000000000..32656a8d9
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_339445_sample.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<meta charset="utf-8">
+<title>Test for bug 339445</title>
+
+storageTestItem = <span id="storageTestItem">FAIL</span>
+
+<!--
+ storageTestItem's textContent will be one of the following:
+ * FAIL : sessionStorage wasn't available
+ * PENDING : the test value has been initialized on first load
+ * SUCCESS : the test value was correctly retrieved
+-->
+
+<script type="application/javascript">
+ document.getElementById("storageTestItem").textContent =
+ sessionStorage["storageTestItem"] || "PENDING";
+ sessionStorage["storageTestItem"] = "SUCCESS";
+</script>
diff --git a/browser/components/sessionstore/test/browser_345898.js b/browser/components/sessionstore/test/browser_345898.js
new file mode 100644
index 000000000..bd4a46e69
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_345898.js
@@ -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/. */
+
+function test() {
+ /** Test for Bug 345898 **/
+
+ function test(aLambda) {
+ try {
+ aLambda();
+ return false;
+ }
+ catch (ex) {
+ return ex.name == "NS_ERROR_ILLEGAL_VALUE" ||
+ ex.name == "NS_ERROR_FAILURE";
+ }
+ }
+
+ // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
+ ok(test(() => ss.getWindowState({})),
+ "Invalid window for getWindowState throws");
+ ok(test(() => ss.setWindowState({}, "", false)),
+ "Invalid window for setWindowState throws");
+ ok(test(() => ss.getTabState({})),
+ "Invalid tab for getTabState throws");
+ ok(test(() => ss.setTabState({}, "{}")),
+ "Invalid tab state for setTabState throws");
+ ok(test(() => ss.setTabState({}, JSON.stringify({ entries: [] }))),
+ "Invalid tab for setTabState throws");
+ ok(test(() => ss.duplicateTab({}, {})),
+ "Invalid tab for duplicateTab throws");
+ ok(test(() => ss.duplicateTab({}, gBrowser.selectedTab)),
+ "Invalid window for duplicateTab throws");
+ ok(test(() => ss.getClosedTabData({})),
+ "Invalid window for getClosedTabData throws");
+ ok(test(() => ss.undoCloseTab({}, 0)),
+ "Invalid window for undoCloseTab throws");
+ ok(test(() => ss.undoCloseTab(window, -1)),
+ "Invalid index for undoCloseTab throws");
+ ok(test(() => ss.getWindowValue({}, "")),
+ "Invalid window for getWindowValue throws");
+ ok(test(() => ss.setWindowValue({}, "", "")),
+ "Invalid window for setWindowValue throws");
+}
diff --git a/browser/components/sessionstore/test/browser_350525.js b/browser/components/sessionstore/test/browser_350525.js
new file mode 100644
index 000000000..1d87b3754
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_350525.js
@@ -0,0 +1,102 @@
+"use strict";
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+})
+
+add_task(function* () {
+ /** Test for Bug 350525 **/
+
+ function test(aLambda) {
+ try {
+ return aLambda() || true;
+ }
+ catch (ex) { }
+ return false;
+ }
+
+ ////////////////////////////
+ // setWindowValue, et al. //
+ ////////////////////////////
+ let key = "Unique name: " + Date.now();
+ let value = "Unique value: " + Math.random();
+
+ // test adding
+ ok(test(() => ss.setWindowValue(window, key, value)), "set a window value");
+
+ // test retrieving
+ is(ss.getWindowValue(window, key), value, "stored window value matches original");
+
+ // test deleting
+ ok(test(() => ss.deleteWindowValue(window, key)), "delete the window value");
+
+ // value should not exist post-delete
+ is(ss.getWindowValue(window, key), "", "window value was deleted");
+
+ // test deleting a non-existent value
+ ok(test(() => ss.deleteWindowValue(window, key)), "delete non-existent window value");
+
+ /////////////////////////
+ // setTabValue, et al. //
+ /////////////////////////
+ key = "Unique name: " + Math.random();
+ value = "Unique value: " + Date.now();
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.stop();
+
+ // test adding
+ ok(test(() => ss.setTabValue(tab, key, value)), "store a tab value");
+
+ // test retrieving
+ is(ss.getTabValue(tab, key), value, "stored tab value match original");
+
+ // test deleting
+ ok(test(() => ss.deleteTabValue(tab, key)), "delete the tab value");
+
+ // value should not exist post-delete
+ is(ss.getTabValue(tab, key), "", "tab value was deleted");
+
+ // test deleting a non-existent value
+ ok(test(() => ss.deleteTabValue(tab, key)), "delete non-existent tab value");
+
+ // clean up
+ yield promiseRemoveTab(tab);
+
+ /////////////////////////////////////
+ // getClosedTabCount, undoCloseTab //
+ /////////////////////////////////////
+
+ // get closed tab count
+ let count = ss.getClosedTabCount(window);
+ let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo");
+ ok(0 <= count && count <= max_tabs_undo,
+ "getClosedTabCount returns zero or at most max_tabs_undo");
+
+ // create a new tab
+ let testURL = "about:";
+ tab = gBrowser.addTab(testURL);
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ // make sure that the next closed tab will increase getClosedTabCount
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1);
+ registerCleanupFunction(() => gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo"));
+
+ // remove tab
+ yield promiseRemoveTab(tab);
+
+ // getClosedTabCount
+ let newcount = ss.getClosedTabCount(window);
+ ok(newcount > count, "after closing a tab, getClosedTabCount has been incremented");
+
+ // undoCloseTab
+ tab = test(() => ss.undoCloseTab(window, 0));
+ ok(tab, "undoCloseTab doesn't throw")
+
+ yield promiseTabRestored(tab);
+ is(tab.linkedBrowser.currentURI.spec, testURL, "correct tab was reopened");
+
+ // clean up
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_354894_perwindowpb.js b/browser/components/sessionstore/test/browser_354894_perwindowpb.js
new file mode 100644
index 000000000..bf80cd710
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_354894_perwindowpb.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/. */
+
+/**
+ * Checks that restoring the last browser window in session is actually
+ * working.
+ *
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=354894
+ * @note It is implicitly tested that restoring the last window works when
+ * non-browser windows are around. The "Run Tests" window as well as the main
+ * browser window (wherein the test code gets executed) won't be considered
+ * browser windows. To achiveve this said main browser window has it's windowtype
+ * attribute modified so that it's not considered a browser window any longer.
+ * This is crucial, because otherwise there would be two browser windows around,
+ * said main test window and the one opened by the tests, and hence the new
+ * logic wouldn't be executed at all.
+ * @note Mac only tests the new notifications, as restoring the last window is
+ * not enabled on that platform (platform shim; the application is kept running
+ * although there are no windows left)
+ * @note There is a difference when closing a browser window with
+ * BrowserTryToCloseWindow() as opposed to close(). The former will make
+ * nsSessionStore restore a window next time it gets a chance and will post
+ * notifications. The latter won't.
+ */
+
+// Some urls that might be opened in tabs and/or popups
+// Do not use about:blank:
+// That one is reserved for special purposes in the tests
+const TEST_URLS = ["about:mozilla", "about:buildconfig"];
+
+// Number of -request notifications to except
+// remember to adjust when adding new tests
+const NOTIFICATIONS_EXPECTED = 6;
+
+// Window features of popup windows
+const POPUP_FEATURES = "toolbar=no,resizable=no,status=no";
+
+// Window features of browser windows
+const CHROME_FEATURES = "chrome,all,dialog=no";
+
+const IS_MAC = navigator.platform.match(/Mac/);
+
+/**
+ * Returns an Object with two properties:
+ * open (int):
+ * A count of how many non-closed navigator:browser windows there are.
+ * winstates (int):
+ * A count of how many windows there are in the SessionStore state.
+ */
+function getBrowserWindowsCount() {
+ let open = 0;
+ let e = Services.wm.getEnumerator("navigator:browser");
+ while (e.hasMoreElements()) {
+ if (!e.getNext().closed)
+ ++open;
+ }
+
+ let winstates = JSON.parse(ss.getBrowserState()).windows.length;
+
+ return { open, winstates };
+}
+
+add_task(function* setup() {
+ // Make sure we've only got one browser window to start with
+ let { open, winstates } = getBrowserWindowsCount();
+ is(open, 1, "Should only be one open window");
+ is(winstates, 1, "Should only be one window state in SessionStore");
+
+ // This test takes some time to run, and it could timeout randomly.
+ // So we require a longer timeout. See bug 528219.
+ requestLongerTimeout(3);
+
+ // Make the main test window not count as a browser window any longer
+ let oldWinType = document.documentElement.getAttribute("windowtype");
+ document.documentElement.setAttribute("windowtype", "navigator:testrunner");
+
+ registerCleanupFunction(() => {
+ document.documentElement.setAttribute("windowtype", "navigator:browser");
+ });
+});
+
+/**
+ * Sets up one of our tests by setting the right preferences, and
+ * then opening up a browser window preloaded with some tabs.
+ *
+ * @param options (Object)
+ * An object that can contain the following properties:
+ *
+ * private:
+ * Whether or not the opened window should be private.
+ *
+ * denyFirst:
+ * Whether or not the first window that attempts to close
+ * via closeWindowForRestoration should be denied.
+ *
+ * @param testFunction (Function*)
+ * A generator function that yields Promises to be run
+ * once the test has been set up.
+ *
+ * @returns Promise
+ * Resolves once the test has been cleaned up.
+ */
+let setupTest = Task.async(function*(options, testFunction) {
+ yield pushPrefs(["browser.startup.page", 3],
+ ["browser.tabs.warnOnClose", false]);
+
+ // Observe these, and also use to count the number of hits
+ let observing = {
+ "browser-lastwindow-close-requested": 0,
+ "browser-lastwindow-close-granted": 0
+ };
+
+ /**
+ * Helper: Will observe and handle the notifications for us
+ */
+ let hitCount = 0;
+ function observer(aCancel, aTopic, aData) {
+ // count so that we later may compare
+ observing[aTopic]++;
+
+ // handle some tests
+ if (options.denyFirst && ++hitCount == 1) {
+ aCancel.QueryInterface(Ci.nsISupportsPRBool).data = true;
+ }
+ }
+
+ for (let o in observing) {
+ Services.obs.addObserver(observer, o, false);
+ }
+
+ let private = options.private || false;
+ let newWin = yield promiseNewWindowLoaded({ private });
+
+ injectTestTabs(newWin);
+
+ yield testFunction(newWin, observing);
+
+ let count = getBrowserWindowsCount();
+ is(count.open, 0, "Got right number of open windows");
+ is(count.winstates, 1, "Got right number of stored window states");
+
+ for (let o in observing) {
+ Services.obs.removeObserver(observer, o);
+ }
+
+ yield popPrefs();
+});
+
+/**
+ * Loads a TEST_URLS into a browser window.
+ *
+ * @param win (Window)
+ * The browser window to load the tabs in
+ */
+function injectTestTabs(win) {
+ TEST_URLS.forEach(function (url) {
+ win.gBrowser.addTab(url);
+ });
+}
+
+/**
+ * Attempts to close a window via BrowserTryToCloseWindow so that
+ * we get the browser-lastwindow-close-requested and
+ * browser-lastwindow-close-granted observer notifications.
+ *
+ * @param win (Window)
+ * The window to try to close
+ * @returns Promise
+ * Resolves to true if the window closed, or false if the window
+ * was denied the ability to close.
+ */
+function closeWindowForRestoration(win) {
+ return new Promise((resolve) => {
+ let closePromise = BrowserTestUtils.windowClosed(win);
+ win.BrowserTryToCloseWindow();
+ if (!win.closed) {
+ resolve(false);
+ return;
+ }
+
+ closePromise.then(() => {
+ resolve(true);
+ });
+ });
+}
+
+/**
+ * Normal in-session restore
+ *
+ * @note: Non-Mac only
+ *
+ * Should do the following:
+ * 1. Open a new browser window
+ * 2. Add some tabs
+ * 3. Close that window
+ * 4. Opening another window
+ * 5. Checks that state is restored
+ */
+add_task(function* test_open_close_normal() {
+ if (IS_MAC) {
+ return;
+ }
+
+ yield setupTest({ denyFirst: true }, function*(newWin, obs) {
+ let closed = yield closeWindowForRestoration(newWin);
+ ok(!closed, "First close request should have been denied");
+
+ closed = yield closeWindowForRestoration(newWin);
+ ok(closed, "Second close request should be accepted");
+
+ newWin = yield promiseNewWindowLoaded();
+ is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2,
+ "Restored window in-session with otherpopup windows around");
+
+ // Note that this will not result in the the browser-lastwindow-close
+ // notifications firing for this other newWin.
+ yield BrowserTestUtils.closeWindow(newWin);
+
+ // setupTest gave us a window which was denied for closing once, and then
+ // closed.
+ is(obs["browser-lastwindow-close-requested"], 2,
+ "Got expected browser-lastwindow-close-requested notifications");
+ is(obs["browser-lastwindow-close-granted"], 1,
+ "Got expected browser-lastwindow-close-granted notifications");
+ });
+});
+
+/**
+ * PrivateBrowsing in-session restore
+ *
+ * @note: Non-Mac only
+ *
+ * Should do the following:
+ * 1. Open a new browser window A
+ * 2. Add some tabs
+ * 3. Close the window A as the last window
+ * 4. Open a private browsing window B
+ * 5. Make sure that B didn't restore the tabs from A
+ * 6. Close private browsing window B
+ * 7. Open a new window C
+ * 8. Make sure that new window C has restored tabs from A
+ */
+add_task(function* test_open_close_private_browsing() {
+ if (IS_MAC) {
+ return;
+ }
+
+ yield setupTest({}, function*(newWin, obs) {
+ let closed = yield closeWindowForRestoration(newWin);
+ ok(closed, "Should be able to close the window");
+
+ newWin = yield promiseNewWindowLoaded({private: true});
+ is(newWin.gBrowser.browsers.length, 1,
+ "Did not restore in private browing mode");
+
+ closed = yield closeWindowForRestoration(newWin);
+ ok(closed, "Should be able to close the window");
+
+ newWin = yield promiseNewWindowLoaded();
+ is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2,
+ "Restored tabs in a new non-private window");
+
+ // Note that this will not result in the the browser-lastwindow-close
+ // notifications firing for this other newWin.
+ yield BrowserTestUtils.closeWindow(newWin);
+
+ // We closed two windows with closeWindowForRestoration, and both
+ // should have been successful.
+ is(obs["browser-lastwindow-close-requested"], 2,
+ "Got expected browser-lastwindow-close-requested notifications");
+ is(obs["browser-lastwindow-close-granted"], 2,
+ "Got expected browser-lastwindow-close-granted notifications");
+ });
+});
+
+/**
+ * Open some popup windows to check those aren't restored, but the browser
+ * window is.
+ *
+ * @note: Non-Mac only
+ *
+ * Should do the following:
+ * 1. Open a new browser window
+ * 2. Add some tabs
+ * 3. Open some popups
+ * 4. Add another tab to one popup (so that it gets stored) and close it again
+ * 5. Close the browser window
+ * 6. Open another browser window
+ * 7. Make sure that the tabs of the closed browser window, but not the popup,
+ * are restored
+ */
+add_task(function* test_open_close_window_and_popup() {
+ if (IS_MAC) {
+ return;
+ }
+
+ yield setupTest({}, function*(newWin, obs) {
+ let popupPromise = BrowserTestUtils.waitForNewWindow();
+ openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[0]);
+ let popup = yield popupPromise;
+
+ let popup2Promise = BrowserTestUtils.waitForNewWindow();
+ openDialog(location, "popup2", POPUP_FEATURES, TEST_URLS[1]);
+ let popup2 = yield popup2Promise;
+
+ popup2.gBrowser.addTab(TEST_URLS[0]);
+
+ let closed = yield closeWindowForRestoration(newWin);
+ ok(closed, "Should be able to close the window");
+
+ yield BrowserTestUtils.closeWindow(popup2);
+
+ newWin = yield promiseNewWindowLoaded();
+
+ is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2,
+ "Restored window and associated tabs in session");
+
+ yield BrowserTestUtils.closeWindow(popup);
+ yield BrowserTestUtils.closeWindow(newWin);
+
+ // We closed one window with closeWindowForRestoration, and it should
+ // have been successful.
+ is(obs["browser-lastwindow-close-requested"], 1,
+ "Got expected browser-lastwindow-close-requested notifications");
+ is(obs["browser-lastwindow-close-granted"], 1,
+ "Got expected browser-lastwindow-close-granted notifications");
+ });
+});
+
+/**
+ * Open some popup window to check it isn't restored. Instead nothing at all
+ * should be restored
+ *
+ * @note: Non-Mac only
+ *
+ * Should do the following:
+ * 1. Open a popup
+ * 2. Add another tab to the popup (so that it gets stored) and close it again
+ * 3. Open a window
+ * 4. Check that nothing at all is restored
+ * 5. Open two browser windows and close them again
+ * 6. undoCloseWindow() one
+ * 7. Open another browser window
+ * 8. Check that nothing at all is restored
+ */
+add_task(function* test_open_close_only_popup() {
+ if (IS_MAC) {
+ return;
+ }
+
+ yield setupTest({}, function*(newWin, obs) {
+ // We actually don't care about the initial window in this test.
+ yield BrowserTestUtils.closeWindow(newWin);
+
+ // This will cause nsSessionStore to restore a window the next time it
+ // gets a chance.
+ let popupPromise = BrowserTestUtils.waitForNewWindow();
+ openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
+ let popup = yield popupPromise;
+
+ is(popup.gBrowser.browsers.length, 1,
+ "Did not restore the popup window (1)");
+
+ let closed = yield closeWindowForRestoration(popup);
+ ok(closed, "Should be able to close the window");
+
+ popupPromise = BrowserTestUtils.waitForNewWindow();
+ openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]);
+ popup = yield popupPromise;
+
+ popup.gBrowser.addTab(TEST_URLS[0]);
+ is(popup.gBrowser.browsers.length, 2,
+ "Did not restore to the popup window (2)");
+
+ yield BrowserTestUtils.closeWindow(popup);
+
+ newWin = yield promiseNewWindowLoaded();
+ isnot(newWin.gBrowser.browsers.length, 2,
+ "Did not restore the popup window");
+ is(TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec), -1,
+ "Did not restore the popup window (2)");
+ yield BrowserTestUtils.closeWindow(newWin);
+
+ // We closed one popup window with closeWindowForRestoration, and popup
+ // windows should never fire the browser-lastwindow notifications.
+ is(obs["browser-lastwindow-close-requested"], 0,
+ "Got expected browser-lastwindow-close-requested notifications");
+ is(obs["browser-lastwindow-close-granted"], 0,
+ "Got expected browser-lastwindow-close-granted notifications");
+ });
+});
+
+/**
+ * Open some windows and do undoCloseWindow. This should prevent any
+ * restoring later in the test
+ *
+ * @note: Non-Mac only
+ *
+ * Should do the following:
+ * 1. Open two browser windows and close them again
+ * 2. undoCloseWindow() one
+ * 3. Open another browser window
+ * 4. Make sure nothing at all is restored
+ */
+add_task(function* test_open_close_restore_from_popup() {
+ if (IS_MAC) {
+ return;
+ }
+
+ yield setupTest({}, function*(newWin, obs) {
+ let newWin2 = yield promiseNewWindowLoaded();
+ yield injectTestTabs(newWin2);
+
+ let closed = yield closeWindowForRestoration(newWin);
+ ok(closed, "Should be able to close the window");
+ closed = yield closeWindowForRestoration(newWin2);
+ ok(closed, "Should be able to close the window");
+
+ let counts = getBrowserWindowsCount();
+ is(counts.open, 0, "Got right number of open windows");
+ is(counts.winstates, 1, "Got right number of window states");
+
+ newWin = undoCloseWindow(0);
+ yield BrowserTestUtils.waitForEvent(newWin, "load");
+
+ // Make sure we wait until this window is restored.
+ yield BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer,
+ "SSTabRestored");
+
+ newWin2 = yield promiseNewWindowLoaded();
+
+ is(newWin2.gBrowser.browsers.length, 1,
+ "Did not restore, as undoCloseWindow() was last called");
+ is(TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), -1,
+ "Did not restore, as undoCloseWindow() was last called (2)");
+
+ counts = getBrowserWindowsCount();
+ is(counts.open, 2, "Got right number of open windows");
+ is(counts.winstates, 3, "Got right number of window states");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+ yield BrowserTestUtils.closeWindow(newWin2);
+
+ counts = getBrowserWindowsCount();
+ is(counts.open, 0, "Got right number of open windows");
+ is(counts.winstates, 1, "Got right number of window states");
+ });
+});
+
+/**
+ * Test if closing can be denied on Mac.
+ * @note: Mac only
+ */
+add_task(function* test_mac_notifications() {
+ if (!IS_MAC) {
+ return;
+ }
+
+ yield setupTest({ denyFirst: true }, function*(newWin, obs) {
+ let closed = yield closeWindowForRestoration(newWin);
+ ok(!closed, "First close attempt should be denied");
+ closed = yield closeWindowForRestoration(newWin);
+ ok(closed, "Second close attempt should be granted");
+
+ // We tried closing once, and got denied. Then we tried again and
+ // succeeded. That means 2 close requests, and 1 close granted.
+ is(obs["browser-lastwindow-close-requested"], 2,
+ "Got expected browser-lastwindow-close-requested notifications");
+ is(obs["browser-lastwindow-close-granted"], 1,
+ "Got expected browser-lastwindow-close-granted notifications");
+ });
+});
+
diff --git a/browser/components/sessionstore/test/browser_367052.js b/browser/components/sessionstore/test/browser_367052.js
new file mode 100644
index 000000000..3cc89a66c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_367052.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(function* () {
+ // make sure that the next closed tab will increase getClosedTabCount
+ let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo");
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1);
+ registerCleanupFunction(() => gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo"));
+
+ // Empty the list of closed tabs.
+ while (ss.getClosedTabCount(window)) {
+ ss.forgetClosedTab(window, 0);
+ }
+
+ // restore a blank tab
+ let tab = gBrowser.addTab("about:");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ let count = yield promiseSHistoryCount(tab.linkedBrowser);
+ ok(count >= 1, "the new tab does have at least one history entry");
+
+ yield promiseTabState(tab, {entries: []});
+
+ // We may have a different sessionHistory object if the tab
+ // switched from non-remote to remote.
+ count = yield promiseSHistoryCount(tab.linkedBrowser);
+ is(count, 0, "the tab was restored without any history whatsoever");
+
+ yield promiseRemoveTab(tab);
+ is(ss.getClosedTabCount(window), 0,
+ "The closed blank tab wasn't added to Recently Closed Tabs");
+});
+
+function promiseSHistoryCount(browser) {
+ return ContentTask.spawn(browser, null, function* () {
+ return docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.count;
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_393716.js b/browser/components/sessionstore/test/browser_393716.js
new file mode 100644
index 000000000..c59bdcc8b
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_393716.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = "about:config";
+
+/**
+ * Bug 393716 - Basic tests for getTabState(), setTabState(), and duplicateTab().
+ */
+add_task(function test_set_tabstate() {
+ let key = "Unique key: " + Date.now();
+ let value = "Unique value: " + Math.random();
+
+ // create a new tab
+ let tab = gBrowser.addTab(URL);
+ ss.setTabValue(tab, key, value);
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ // get the tab's state
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ let state = ss.getTabState(tab);
+ ok(state, "get the tab's state");
+
+ // verify the tab state's integrity
+ state = JSON.parse(state);
+ ok(state instanceof Object && state.entries instanceof Array && state.entries.length > 0,
+ "state object seems valid");
+ ok(state.entries.length == 1 && state.entries[0].url == URL,
+ "Got the expected state object (test URL)");
+ ok(state.extData && state.extData[key] == value,
+ "Got the expected state object (test manually set tab value)");
+
+ // clean up
+ gBrowser.removeTab(tab);
+});
+
+add_task(function test_set_tabstate_and_duplicate() {
+ let key2 = "key2";
+ let value2 = "Value " + Math.random();
+ let value3 = "Another value: " + Date.now();
+ let state = { entries: [{ url: URL }], extData: { key2: value2 } };
+
+ // create a new tab
+ let tab = gBrowser.addTab();
+ // set the tab's state
+ ss.setTabState(tab, JSON.stringify(state));
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ // verify the correctness of the restored tab
+ ok(ss.getTabValue(tab, key2) == value2 && tab.linkedBrowser.currentURI.spec == URL,
+ "the tab's state was correctly restored");
+
+ // add text data
+ yield setInputValue(tab.linkedBrowser, {id: "textbox", value: value3});
+
+ // duplicate the tab
+ let tab2 = ss.duplicateTab(window, tab);
+ yield promiseTabRestored(tab2);
+
+ // verify the correctness of the duplicated tab
+ ok(ss.getTabValue(tab2, key2) == value2 &&
+ tab2.linkedBrowser.currentURI.spec == URL,
+ "correctly duplicated the tab's state");
+ let textbox = yield getInputValue(tab2.linkedBrowser, {id: "textbox"});
+ is(textbox, value3, "also duplicated text data");
+
+ // clean up
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_394759_basic.js b/browser/components/sessionstore/test/browser_394759_basic.js
new file mode 100644
index 000000000..1b1650e27
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_394759_basic.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const TEST_URL = "data:text/html;charset=utf-8,<input%20id=txt>" +
+ "<input%20type=checkbox%20id=chk>";
+
+Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
+
+/**
+ * This test ensures that closing a window is a reversible action. We will
+ * close the the window, restore it and check that all data has been restored.
+ * This includes window-specific data as well as form data for tabs.
+ */
+function test() {
+ waitForExplicitFinish();
+
+ let uniqueKey = "bug 394759";
+ let uniqueValue = "unik" + Date.now();
+ let uniqueText = "pi != " + Math.random();
+
+ // Clear the list of closed windows.
+ forgetClosedWindows();
+
+ provideWindow(function onTestURLLoaded(newWin) {
+ newWin.gBrowser.addTab().linkedBrowser.stop();
+
+ // Mark the window with some unique data to be restored later on.
+ ss.setWindowValue(newWin, uniqueKey, uniqueValue);
+ let [txt, chk] = newWin.content.document.querySelectorAll("#txt, #chk");
+ txt.value = uniqueText;
+
+ let browser = newWin.gBrowser.selectedBrowser;
+ setInputChecked(browser, {id: "chk", checked: true}).then(() => {
+ BrowserTestUtils.closeWindow(newWin).then(() => {
+ is(ss.getClosedWindowCount(), 1,
+ "The closed window was added to Recently Closed Windows");
+
+ let data = SessionStore.getClosedWindowData(false);
+
+ // Verify that non JSON serialized data is the same as JSON serialized data.
+ is(JSON.stringify(data), ss.getClosedWindowData(),
+ "Non-serialized data is the same as serialized data")
+
+ ok(data[0].title == TEST_URL && JSON.stringify(data[0]).indexOf(uniqueText) > -1,
+ "The closed window data was stored correctly");
+
+ // Reopen the closed window and ensure its integrity.
+ let newWin2 = ss.undoCloseWindow(0);
+
+ ok(newWin2 instanceof ChromeWindow,
+ "undoCloseWindow actually returned a window");
+ is(ss.getClosedWindowCount(), 0,
+ "The reopened window was removed from Recently Closed Windows");
+
+ // SSTabRestored will fire more than once, so we need to make sure we count them.
+ let restoredTabs = 0;
+ let expectedTabs = data[0].tabs.length;
+ newWin2.addEventListener("SSTabRestored", function sstabrestoredListener(aEvent) {
+ ++restoredTabs;
+ info("Restored tab " + restoredTabs + "/" + expectedTabs);
+ if (restoredTabs < expectedTabs) {
+ return;
+ }
+
+ is(restoredTabs, expectedTabs, "Correct number of tabs restored");
+ newWin2.removeEventListener("SSTabRestored", sstabrestoredListener, true);
+
+ is(newWin2.gBrowser.tabs.length, 2,
+ "The window correctly restored 2 tabs");
+ is(newWin2.gBrowser.currentURI.spec, TEST_URL,
+ "The window correctly restored the URL");
+
+ let [txt, chk] = newWin2.content.document.querySelectorAll("#txt, #chk");
+ ok(txt.value == uniqueText && chk.checked,
+ "The window correctly restored the form");
+ is(ss.getWindowValue(newWin2, uniqueKey), uniqueValue,
+ "The window correctly restored the data associated with it");
+
+ // Clean up.
+ BrowserTestUtils.closeWindow(newWin2).then(finish);
+ }, true);
+ });
+ });
+ }, TEST_URL);
+}
+
+function setInputChecked(browser, data) {
+ return sendMessage(browser, "ss-test:setInputChecked", data);
+}
diff --git a/browser/components/sessionstore/test/browser_394759_behavior.js b/browser/components/sessionstore/test/browser_394759_behavior.js
new file mode 100644
index 000000000..aa74dc061
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_394759_behavior.js
@@ -0,0 +1,76 @@
+/**
+ * Test helper function that opens a series of windows, closes them
+ * and then checks the closed window data from SessionStore against
+ * expected results.
+ *
+ * @param windowsToOpen (Array)
+ * An array of Objects, where each object must define a single
+ * property "isPopup" for whether or not the opened window should
+ * be a popup.
+ * @param expectedResults (Array)
+ * An Object with two properies: mac and other, where each points
+ * at yet another Object, with the following properties:
+ *
+ * popup (int):
+ * The number of popup windows we expect to be in the closed window
+ * data.
+ * normal (int):
+ * The number of normal windows we expect to be in the closed window
+ * data.
+ * @returns Promise
+ */
+function testWindows(windowsToOpen, expectedResults) {
+ return Task.spawn(function*() {
+ for (let winData of windowsToOpen) {
+ let features = "chrome,dialog=no," +
+ (winData.isPopup ? "all=no" : "all");
+ let url = "http://example.com/?window=" + windowsToOpen.length;
+
+ let openWindowPromise = BrowserTestUtils.waitForNewWindow(true, url);
+ openDialog(getBrowserURL(), "", features, url);
+ let win = yield openWindowPromise;
+ yield BrowserTestUtils.closeWindow(win);
+ }
+
+ let closedWindowData = JSON.parse(ss.getClosedWindowData());
+ let numPopups = closedWindowData.filter(function(el, i, arr) {
+ return el.isPopup;
+ }).length;
+ let numNormal = ss.getClosedWindowCount() - numPopups;
+ // #ifdef doesn't work in browser-chrome tests, so do a simple regex on platform
+ let oResults = navigator.platform.match(/Mac/) ? expectedResults.mac
+ : expectedResults.other;
+ is(numPopups, oResults.popup,
+ "There were " + oResults.popup + " popup windows to reopen");
+ is(numNormal, oResults.normal,
+ "There were " + oResults.normal + " normal windows to repoen");
+ });
+}
+
+add_task(function* test_closed_window_states() {
+ // This test takes quite some time, and timeouts frequently, so we require
+ // more time to run.
+ // See Bug 518970.
+ requestLongerTimeout(2);
+
+ let windowsToOpen = [{isPopup: false},
+ {isPopup: false},
+ {isPopup: true},
+ {isPopup: true},
+ {isPopup: true}];
+ let expectedResults = {mac: {popup: 3, normal: 0},
+ other: {popup: 3, normal: 1}};
+
+ yield testWindows(windowsToOpen, expectedResults);
+
+
+ let windowsToOpen2 = [{isPopup: false},
+ {isPopup: false},
+ {isPopup: false},
+ {isPopup: false},
+ {isPopup: false}];
+ let expectedResults2 = {mac: {popup: 0, normal: 3},
+ other: {popup: 0, normal: 3}};
+
+ yield testWindows(windowsToOpen2, expectedResults2);
+}); \ No newline at end of file
diff --git a/browser/components/sessionstore/test/browser_394759_perwindowpb.js b/browser/components/sessionstore/test/browser_394759_perwindowpb.js
new file mode 100644
index 000000000..83eec3070
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_394759_perwindowpb.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";
+
+const TESTS = [
+ { url: "about:config",
+ key: "bug 394759 Non-PB",
+ value: "uniq" + r() },
+ { url: "about:mozilla",
+ key: "bug 394759 PB",
+ value: "uniq" + r() },
+];
+
+function promiseTestOpenCloseWindow(aIsPrivate, aTest) {
+ return Task.spawn(function*() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ "private": aIsPrivate });
+ win.gBrowser.selectedBrowser.loadURI(aTest.url);
+ yield promiseBrowserLoaded(win.gBrowser.selectedBrowser);
+ yield Promise.resolve();
+ // Mark the window with some unique data to be restored later on.
+ ss.setWindowValue(win, aTest.key, aTest.value);
+ yield TabStateFlusher.flushWindow(win);
+ // Close.
+ yield BrowserTestUtils.closeWindow(win);
+ });
+}
+
+function promiseTestOnWindow(aIsPrivate, aValue) {
+ return Task.spawn(function*() {
+ let win = yield BrowserTestUtils.openNewBrowserWindow({ "private": aIsPrivate });
+ yield TabStateFlusher.flushWindow(win);
+ let data = JSON.parse(ss.getClosedWindowData())[0];
+ is(ss.getClosedWindowCount(), 1, "Check that the closed window count hasn't changed");
+ ok(JSON.stringify(data).indexOf(aValue) > -1,
+ "Check the closed window data was stored correctly");
+ registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
+ });
+}
+
+add_task(function* init() {
+ forgetClosedWindows();
+ while (ss.getClosedTabCount(window) > 0) {
+ ss.forgetClosedTab(window, 0);
+ }
+});
+
+add_task(function* main() {
+ yield promiseTestOpenCloseWindow(false, TESTS[0]);
+ yield promiseTestOpenCloseWindow(true, TESTS[1]);
+ yield promiseTestOnWindow(false, TESTS[0].value);
+ yield promiseTestOnWindow(true, TESTS[0].value);
+});
+
diff --git a/browser/components/sessionstore/test/browser_394759_purge.js b/browser/components/sessionstore/test/browser_394759_purge.js
new file mode 100644
index 000000000..75144aba1
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_394759_purge.js
@@ -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/. */
+
+Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
+
+function waitForClearHistory(aCallback) {
+ let observer = {
+ observe: function(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(this, "browser:purge-domain-data");
+ setTimeout(aCallback, 0);
+ }
+ };
+ Services.obs.addObserver(observer, "browser:purge-domain-data", false);
+}
+
+function test() {
+ waitForExplicitFinish();
+ // utility functions
+ function countClosedTabsByTitle(aClosedTabList, aTitle) {
+ return aClosedTabList.filter(aData => aData.title == aTitle).length;
+ }
+
+ function countOpenTabsByTitle(aOpenTabList, aTitle) {
+ return aOpenTabList.filter(aData => aData.entries.some(aEntry => aEntry.title == aTitle)).length;
+ }
+
+ // backup old state
+ let oldState = ss.getBrowserState();
+ let oldState_wins = JSON.parse(oldState).windows.length;
+ if (oldState_wins != 1)
+ ok(false, "oldState in test_purge has " + oldState_wins + " windows instead of 1");
+
+ // create a new state for testing
+ const REMEMBER = Date.now(), FORGET = Math.random();
+ let testState = {
+ windows: [ { tabs: [{ entries: [{ url: "http://example.com/" }] }], selected: 1 } ],
+ _closedWindows : [
+ // _closedWindows[0]
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com/", title: REMEMBER }] },
+ { entries: [{ url: "http://mozilla.org/", title: FORGET }] }
+ ],
+ selected: 2,
+ title: "mozilla.org",
+ _closedTabs: []
+ },
+ // _closedWindows[1]
+ {
+ tabs: [
+ { entries: [{ url: "http://mozilla.org/", title: FORGET }] },
+ { entries: [{ url: "http://example.com/", title: REMEMBER }] },
+ { entries: [{ url: "http://example.com/", title: REMEMBER }] },
+ { entries: [{ url: "http://mozilla.org/", title: FORGET }] },
+ { entries: [{ url: "http://example.com/", title: REMEMBER }] }
+ ],
+ selected: 5,
+ _closedTabs: []
+ },
+ // _closedWindows[2]
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com/", title: REMEMBER }] }
+ ],
+ selected: 1,
+ _closedTabs: [
+ {
+ state: {
+ entries: [
+ { url: "http://mozilla.org/", title: FORGET },
+ { url: "http://mozilla.org/again", title: "doesn't matter" }
+ ]
+ },
+ pos: 1,
+ title: FORGET
+ },
+ {
+ state: {
+ entries: [
+ { url: "http://example.com", title: REMEMBER }
+ ]
+ },
+ title: REMEMBER
+ }
+ ]
+ }
+ ]
+ };
+
+ // set browser to test state
+ ss.setBrowserState(JSON.stringify(testState));
+
+ // purge domain & check that we purged correctly for closed windows
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ waitForClearHistory(function() {
+ let closedWindowData = JSON.parse(ss.getClosedWindowData());
+
+ // First set of tests for _closedWindows[0] - tests basics
+ let win = closedWindowData[0];
+ is(win.tabs.length, 1, "1 tab was removed");
+ is(countOpenTabsByTitle(win.tabs, FORGET), 0,
+ "The correct tab was removed");
+ is(countOpenTabsByTitle(win.tabs, REMEMBER), 1,
+ "The correct tab was remembered");
+ is(win.selected, 1, "Selected tab has changed");
+ is(win.title, REMEMBER, "The window title was correctly updated");
+
+ // Test more complicated case
+ win = closedWindowData[1];
+ is(win.tabs.length, 3, "2 tabs were removed");
+ is(countOpenTabsByTitle(win.tabs, FORGET), 0,
+ "The correct tabs were removed");
+ is(countOpenTabsByTitle(win.tabs, REMEMBER), 3,
+ "The correct tabs were remembered");
+ is(win.selected, 3, "Selected tab has changed");
+ is(win.title, REMEMBER, "The window title was correctly updated");
+
+ // Tests handling of _closedTabs
+ win = closedWindowData[2];
+ is(countClosedTabsByTitle(win._closedTabs, REMEMBER), 1,
+ "The correct number of tabs were removed, and the correct ones");
+ is(countClosedTabsByTitle(win._closedTabs, FORGET), 0,
+ "All tabs to be forgotten were indeed removed");
+
+ // restore pre-test state
+ ss.setBrowserState(oldState);
+ finish();
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_423132.js b/browser/components/sessionstore/test/browser_423132.js
new file mode 100644
index 000000000..584002cff
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_423132.js
@@ -0,0 +1,59 @@
+"use strict";
+
+/**
+ * Tests that cookies are stored and restored correctly
+ * by sessionstore (bug 423132).
+ */
+add_task(function*() {
+ const testURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_423132_sample.html";
+
+ Services.cookies.removeAll();
+ // make sure that sessionstore.js can be forced to be created by setting
+ // the interval pref to 0
+ yield SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionstore.interval", 0]]
+ });
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let browser = win.gBrowser.selectedBrowser;
+ browser.loadURI(testURL);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+
+ // get the sessionstore state for the window
+ let state = ss.getWindowState(win);
+
+ // verify our cookie got set during pageload
+ let enumerator = Services.cookies.enumerator;
+ let cookie;
+ let i = 0;
+ while (enumerator.hasMoreElements()) {
+ cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ i++;
+ }
+ Assert.equal(i, 1, "expected one cookie");
+
+ // remove the cookie
+ Services.cookies.removeAll();
+
+ // restore the window state
+ ss.setWindowState(win, state, true);
+
+ // at this point, the cookie should be restored...
+ enumerator = Services.cookies.enumerator;
+ let cookie2;
+ while (enumerator.hasMoreElements()) {
+ cookie2 = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ if (cookie.name == cookie2.name)
+ break;
+ }
+ is(cookie.name, cookie2.name, "cookie name successfully restored");
+ is(cookie.value, cookie2.value, "cookie value successfully restored");
+ is(cookie.path, cookie2.path, "cookie path successfully restored");
+
+ // clean up
+ Services.cookies.removeAll();
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/sessionstore/test/browser_423132_sample.html b/browser/components/sessionstore/test/browser_423132_sample.html
new file mode 100644
index 000000000..6ff7e7aa3
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_423132_sample.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="text/javascript">
+ // generate an enormous random number...
+ var r = Math.floor(Math.random() * Math.pow(2, 62)).toString();
+
+ // ... and use it to set a randomly named cookie
+ document.cookie = r + "=value; path=/ohai";
+ </script>
+<body>
+</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_447951.js b/browser/components/sessionstore/test/browser_447951.js
new file mode 100644
index 000000000..a7b6a5ee8
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_447951.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/. */
+
+function test() {
+ /** Test for Bug 447951 **/
+
+ waitForExplicitFinish();
+ const baseURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_447951_sample.html#";
+
+ // Make sure the functionality added in bug 943339 doesn't affect the results
+ gPrefService.setIntPref("browser.sessionstore.max_serialize_back", -1);
+ gPrefService.setIntPref("browser.sessionstore.max_serialize_forward", -1);
+ registerCleanupFunction(function () {
+ gPrefService.clearUserPref("browser.sessionstore.max_serialize_back");
+ gPrefService.clearUserPref("browser.sessionstore.max_serialize_forward");
+ });
+
+ let tab = gBrowser.addTab();
+ promiseBrowserLoaded(tab.linkedBrowser).then(() => {
+ let tabState = { entries: [] };
+ let max_entries = gPrefService.getIntPref("browser.sessionhistory.max_entries");
+ for (let i = 0; i < max_entries; i++)
+ tabState.entries.push({ url: baseURL + i });
+
+ promiseTabState(tab, tabState).then(() => {
+ return TabStateFlusher.flush(tab.linkedBrowser);
+ }).then(() => {
+ tabState = JSON.parse(ss.getTabState(tab));
+ is(tabState.entries.length, max_entries, "session history filled to the limit");
+ is(tabState.entries[0].url, baseURL + 0, "... but not more");
+
+ // visit yet another anchor (appending it to session history)
+ ContentTask.spawn(tab.linkedBrowser, null, function() {
+ content.window.document.querySelector("a").click();
+ }).then(flushAndCheck);
+
+ function flushAndCheck() {
+ TabStateFlusher.flush(tab.linkedBrowser).then(check);
+ }
+
+ function check() {
+ tabState = JSON.parse(ss.getTabState(tab));
+ if (tab.linkedBrowser.currentURI.spec != baseURL + "end") {
+ // It may take a few passes through the event loop before we
+ // get the right URL.
+ executeSoon(flushAndCheck);
+ return;
+ }
+
+ is(tab.linkedBrowser.currentURI.spec, baseURL + "end",
+ "the new anchor was loaded");
+ is(tabState.entries[tabState.entries.length - 1].url, baseURL + "end",
+ "... and ignored");
+ is(tabState.entries[0].url, baseURL + 1,
+ "... and the first item was removed");
+
+ // clean up
+ gBrowser.removeTab(tab);
+ finish();
+ }
+ });
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_447951_sample.html b/browser/components/sessionstore/test/browser_447951_sample.html
new file mode 100644
index 000000000..00282f25e
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_447951_sample.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testcase for bug 447951</title>
+
+<a href="#end">click me</a>
diff --git a/browser/components/sessionstore/test/browser_454908.js b/browser/components/sessionstore/test/browser_454908.js
new file mode 100644
index 000000000..fb8206e2f
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_454908.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = ROOT + "browser_454908_sample.html";
+const PASS = "pwd-" + Math.random();
+
+/**
+ * Bug 454908 - Don't save/restore values of password fields.
+ */
+add_task(function* test_dont_save_passwords() {
+ // Make sure we do save form data.
+ Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
+
+ // Add a tab with a password field.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Fill in some values.
+ let usernameValue = "User " + Math.random();
+ yield setInputValue(browser, {id: "username", value: usernameValue});
+ yield setInputValue(browser, {id: "passwd", value: PASS});
+
+ // Close and restore the tab.
+ yield promiseRemoveTab(tab);
+ tab = ss.undoCloseTab(window, 0);
+ browser = tab.linkedBrowser;
+ yield promiseTabRestored(tab);
+
+ // Check that password fields aren't saved/restored.
+ let username = yield getInputValue(browser, {id: "username"});
+ is(username, usernameValue, "username was saved/restored");
+ let passwd = yield getInputValue(browser, {id: "passwd"});
+ is(passwd, "", "password wasn't saved/restored");
+
+ // Write to disk and read our file.
+ yield forceSaveState();
+ yield promiseForEachSessionRestoreFile((state, key) =>
+ // Ensure that we have not saved our password.
+ ok(!state.includes(PASS), "password has not been written to file " + key)
+ );
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_454908_sample.html b/browser/components/sessionstore/test/browser_454908_sample.html
new file mode 100644
index 000000000..02f40bf20
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_454908_sample.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<title>Test for bug 454908</title>
+
+<h3>Dummy Login</h3>
+<form>
+<p>Username: <input type="text" id="username">
+<p>Password: <input type="password" id="passwd">
+</form>
diff --git a/browser/components/sessionstore/test/browser_456342.js b/browser/components/sessionstore/test/browser_456342.js
new file mode 100644
index 000000000..d7ed33ee5
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_456342.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = ROOT + "browser_456342_sample.xhtml";
+
+/**
+ * Bug 456342 - Restore values from non-standard input field types.
+ */
+add_task(function test_restore_nonstandard_input_values() {
+ // Add tab with various non-standard input field types.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Fill in form values.
+ let expectedValue = Math.random();
+ yield setFormElementValues(browser, {value: expectedValue});
+
+ // Remove tab and check collected form data.
+ yield promiseRemoveTab(tab);
+ let undoItems = JSON.parse(ss.getClosedTabData(window));
+ let savedFormData = undoItems[0].state.formdata;
+
+ let countGood = 0, countBad = 0;
+ for (let id of Object.keys(savedFormData.id)) {
+ if (savedFormData.id[id] == expectedValue) {
+ countGood++;
+ } else {
+ countBad++;
+ }
+ }
+
+ for (let exp of Object.keys(savedFormData.xpath)) {
+ if (savedFormData.xpath[exp] == expectedValue) {
+ countGood++;
+ } else {
+ countBad++;
+ }
+ }
+
+ is(countGood, 4, "Saved text for non-standard input fields");
+ is(countBad, 0, "Didn't save text for ignored field types");
+});
+
+function setFormElementValues(browser, data) {
+ return sendMessage(browser, "ss-test:setFormElementValues", data);
+}
diff --git a/browser/components/sessionstore/test/browser_456342_sample.xhtml b/browser/components/sessionstore/test/browser_456342_sample.xhtml
new file mode 100644
index 000000000..a08777a8d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_456342_sample.xhtml
@@ -0,0 +1,36 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head><title>Test for bug 456342</title></head>
+
+<body>
+<form>
+<h3>Non-standard &lt;input&gt;s</h3>
+<p>Search <input type="search" id="searchTerm"/></p>
+<p>Image Search: <input type="image search" /></p>
+<p>Autocomplete: <input type="autocomplete" name="fill-in"/></p>
+<p>Mistyped: <input type="txet" name="mistyped"/></p>
+
+<h3>Ignored types</h3>
+<input type="hidden" name="hideme"/>
+<input type="HIDDEN" name="hideme2"/>
+<input type="submit" name="submit"/>
+<input type="reset" name="reset"/>
+<input type="image" name="image"/>
+<input type="button" name="button"/>
+<input type="password" name="password"/>
+<input type="PassWord" name="password2"/>
+<input type="PASSWORD" name="password3"/>
+<input autocomplete="off" name="auto1"/>
+<input type="text" autocomplete="OFF" name="auto2"/>
+<textarea autocomplete="off" name="auto3"/>
+<select autocomplete="off" name="auto4">
+ <option value="1" selected="true"/>
+ <option value="2"/>
+ <option value="3"/>
+</select>
+</form>
+
+</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_459906.js b/browser/components/sessionstore/test/browser_459906.js
new file mode 100644
index 000000000..cadab3e5c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_459906.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/. */
+
+function test() {
+ /** Test for Bug 459906 **/
+
+ waitForExplicitFinish();
+
+ let testURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_459906_sample.html";
+ let uniqueValue = "<b>Unique:</b> " + Date.now();
+
+ var frameCount = 0;
+ let tab = gBrowser.addTab(testURL);
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ // wait for all frames to load completely
+ if (frameCount++ < 2)
+ return;
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let iframes = tab.linkedBrowser.contentWindow.frames;
+ iframes[1].document.body.innerHTML = uniqueValue;
+
+ frameCount = 0;
+ let tab2 = gBrowser.duplicateTab(tab);
+ tab2.linkedBrowser.addEventListener("load", function(aEvent) {
+ // wait for all frames to load (and reload!) completely
+ if (frameCount++ < 2)
+ return;
+ tab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ executeSoon(function() {
+ let iframes = tab2.linkedBrowser.contentWindow.frames;
+ if (iframes[1].document.body.innerHTML !== uniqueValue) {
+ // Poll again the value, since we can't ensure to run
+ // after SessionStore has injected innerHTML value.
+ // See bug 521802.
+ info("Polling for innerHTML value");
+ setTimeout(arguments.callee, 100);
+ return;
+ }
+
+ is(iframes[1].document.body.innerHTML, uniqueValue,
+ "rich textarea's content correctly duplicated");
+
+ let innerDomain = null;
+ try {
+ innerDomain = iframes[0].document.domain;
+ }
+ catch (ex) { /* throws for chrome: documents */ }
+ is(innerDomain, "mochi.test", "XSS exploit prevented!");
+
+ // clean up
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+
+ finish();
+ });
+ }, true);
+ }, true);
+}
diff --git a/browser/components/sessionstore/test/browser_459906_empty.html b/browser/components/sessionstore/test/browser_459906_empty.html
new file mode 100644
index 000000000..e01aaa339
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_459906_empty.html
@@ -0,0 +1,3 @@
+<title>Cross Domain File for bug 459906</title>
+
+cheers from localhost
diff --git a/browser/components/sessionstore/test/browser_459906_sample.html b/browser/components/sessionstore/test/browser_459906_sample.html
new file mode 100644
index 000000000..39b789776
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_459906_sample.html
@@ -0,0 +1,41 @@
+<!-- Testcase originally by David Bloom <bloom@google.com> -->
+
+<!DOCTYPE html>
+<title>Test for bug 459906</title>
+
+<body>
+<iframe src="data:text/html;charset=utf-8,not_on_localhost"></iframe>
+<iframe></iframe>
+
+<script type="application/javascript">
+ var loadCount = 0;
+ frames[0].addEventListener("DOMContentLoaded", handleLoad, false);
+ frames[1].addEventListener("DOMContentLoaded", handleLoad, false);
+ function handleLoad() {
+ if (++loadCount < 2)
+ return;
+ frames[0].removeEventListener("DOMContentLoaded", handleLoad, false);
+ frames[1].removeEventListener("DOMContentLoaded", handleLoad, false);
+ frames[0].document.designMode = "on";
+ frames[0].document.__defineGetter__("designMode", function() {
+ // inject a cross domain file ...
+ var documentInjected = false;
+ document.getElementsByTagName("iframe")[0].onload =
+ function() { documentInjected = true; };
+ frames[0].location = "browser_459906_empty.html";
+
+ // ... and ensure that it has time to load
+ for (var c = 0; !documentInjected && c < 20; c++) {
+ var r = new XMLHttpRequest();
+ r.open("GET", location.href, false);
+ r.overrideMimeType("text/plain");
+ r.send(null);
+ }
+
+ return "on";
+ });
+
+ frames[1].document.designMode = "on";
+ };
+</script>
+</body>
diff --git a/browser/components/sessionstore/test/browser_461634.js b/browser/components/sessionstore/test/browser_461634.js
new file mode 100644
index 000000000..01d3a4b0d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_461634.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/. */
+
+Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
+
+function test() {
+ /** Test for Bug 461634 **/
+
+ waitForExplicitFinish();
+
+ const REMEMBER = Date.now(), FORGET = Math.random();
+ let test_state = { windows: [{ "tabs": [{ "entries": [] }], _closedTabs: [
+ { state: { entries: [{ url: "http://www.example.net/" }] }, title: FORGET },
+ { state: { entries: [{ url: "http://www.example.net/" }] }, title: REMEMBER },
+ { state: { entries: [{ url: "http://www.example.net/" }] }, title: FORGET },
+ { state: { entries: [{ url: "http://www.example.net/" }] }, title: REMEMBER },
+ ] }] };
+ let remember_count = 2;
+
+ function countByTitle(aClosedTabList, aTitle) {
+ return aClosedTabList.filter(aData => aData.title == aTitle).length;
+ }
+
+ function testForError(aFunction) {
+ try {
+ aFunction();
+ return false;
+ }
+ catch (ex) {
+ return ex.name == "NS_ERROR_ILLEGAL_VALUE";
+ }
+ }
+
+ // Open a window and add the above closed tab list.
+ let newWin = openDialog(location, "", "chrome,all,dialog=no");
+ promiseWindowLoaded(newWin).then(() => {
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo",
+ test_state.windows[0]._closedTabs.length);
+ ss.setWindowState(newWin, JSON.stringify(test_state), true);
+
+ let closedTabs = SessionStore.getClosedTabData(newWin, false);
+
+ // Verify that non JSON serialized data is the same as JSON serialized data.
+ is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
+ "Non-serialized data is the same as serialized data")
+
+ is(closedTabs.length, test_state.windows[0]._closedTabs.length,
+ "Closed tab list has the expected length");
+ is(countByTitle(closedTabs, FORGET),
+ test_state.windows[0]._closedTabs.length - remember_count,
+ "The correct amout of tabs are to be forgotten");
+ is(countByTitle(closedTabs, REMEMBER), remember_count,
+ "Everything is set up");
+
+ // All of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE.
+ ok(testForError(() => ss.forgetClosedTab({}, 0)),
+ "Invalid window for forgetClosedTab throws");
+ ok(testForError(() => ss.forgetClosedTab(newWin, -1)),
+ "Invalid tab for forgetClosedTab throws");
+ ok(testForError(() => ss.forgetClosedTab(newWin, test_state.windows[0]._closedTabs.length + 1)),
+ "Invalid tab for forgetClosedTab throws");
+
+ // Remove third tab, then first tab.
+ ss.forgetClosedTab(newWin, 2);
+ ss.forgetClosedTab(newWin, null);
+
+ closedTabs = SessionStore.getClosedTabData(newWin, false);
+
+ // Verify that non JSON serialized data is the same as JSON serialized data.
+ is(JSON.stringify(closedTabs), SessionStore.getClosedTabData(newWin),
+ "Non-serialized data is the same as serialized data")
+
+ is(closedTabs.length, remember_count,
+ "The correct amout of tabs was removed");
+ is(countByTitle(closedTabs, FORGET), 0,
+ "All tabs specifically forgotten were indeed removed");
+ is(countByTitle(closedTabs, REMEMBER), remember_count,
+ "... and tabs not specifically forgetten weren't");
+
+ // Clean up.
+ gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
+ BrowserTestUtils.closeWindow(newWin).then(finish);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_461743.js b/browser/components/sessionstore/test/browser_461743.js
new file mode 100644
index 000000000..263fff5f2
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_461743.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/. */
+
+function test() {
+ /** Test for Bug 461743 **/
+
+ waitForExplicitFinish();
+
+ let testURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_461743_sample.html";
+
+ let frameCount = 0;
+ let tab = gBrowser.addTab(testURL);
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ // Wait for all frames to load completely.
+ if (frameCount++ < 2)
+ return;
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ let tab2 = gBrowser.duplicateTab(tab);
+ tab2.linkedBrowser.addEventListener("461743", function(aEvent) {
+ tab2.linkedBrowser.removeEventListener("461743", arguments.callee, true);
+ is(aEvent.data, "done", "XSS injection was attempted");
+
+ executeSoon(function() {
+ let iframes = tab2.linkedBrowser.contentWindow.frames;
+ let innerHTML = iframes[1].document.body.innerHTML;
+ isnot(innerHTML, Components.utils.reportError.toString(),
+ "chrome access denied!");
+
+ // Clean up.
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+
+ finish();
+ });
+ }, true, true);
+ }, true);
+}
diff --git a/browser/components/sessionstore/test/browser_461743_sample.html b/browser/components/sessionstore/test/browser_461743_sample.html
new file mode 100644
index 000000000..80c9e612a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_461743_sample.html
@@ -0,0 +1,56 @@
+<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
+
+<!DOCTYPE html>
+<title>Test for bug 461743</title>
+
+<body>
+<iframe src="data:text/html;charset=utf-8,empty"></iframe>
+<iframe></iframe>
+
+<script type="application/javascript">
+ var chromeUrl = "chrome://global/content/mozilla.xhtml";
+ var exploitUrl = "javascript:try { document.body.innerHTML = Components.utils.reportError; } catch (ex) { }";
+
+ var loadCount = 0;
+ frames[0].addEventListener("DOMContentLoaded", handleLoad, false);
+ frames[1].addEventListener("DOMContentLoaded", handleLoad, false);
+ function handleLoad() {
+ if (++loadCount < 2)
+ return;
+ frames[0].removeEventListener("DOMContentLoaded", handleLoad, false);
+ frames[1].removeEventListener("DOMContentLoaded", handleLoad, false);
+
+ var flip = 0;
+ MutationEvent.prototype.toString = function() {
+ return flip++ == 0 ? chromeUrl : exploitUrl;
+ };
+
+ var href = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(frames[1].location), "href").get;
+ var loadChrome = { handleEvent: href };
+ var loadExploit = { handleEvent: href };
+
+ function delay() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", location.href, false);
+ xhr.send(null);
+ }
+ function done() {
+ var event = new MessageEvent('461743', { bubbles: true, cancelable: false,
+ data: "done", origin: location.href,
+ source: window });
+ document.dispatchEvent(event);
+ frames[0].document.removeEventListener("DOMNodeInserted", loadChrome, true);
+ frames[0].document.removeEventListener("DOMNodeInserted", delay, true);
+ frames[0].document.removeEventListener("DOMNodeInserted", loadExploit, true);
+ frames[0].document.removeEventListener("DOMNodeInserted", done, true);
+ }
+
+ frames[0].document.addEventListener("DOMNodeInserted", loadChrome, true);
+ frames[0].document.addEventListener("DOMNodeInserted", delay, true);
+ frames[0].document.addEventListener("DOMNodeInserted", loadExploit, true);
+ frames[0].document.addEventListener("DOMNodeInserted", done, true);
+
+ frames[0].document.designMode = "on";
+ };
+</script>
+</body>
diff --git a/browser/components/sessionstore/test/browser_463205.js b/browser/components/sessionstore/test/browser_463205.js
new file mode 100644
index 000000000..ad3f22794
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_463205.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = ROOT + "browser_463205_sample.html";
+
+/**
+ * Bug 463205 - Check URLs before restoring form data to make sure a malicious
+ * website can't modify frame URLs and make us inject form data into the wrong
+ * web pages.
+ */
+add_task(function test_check_urls_before_restoring() {
+ // Add a blank tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Restore form data with a valid URL.
+ yield promiseTabState(tab, getState(URL));
+
+ let value = yield getInputValue(browser, {id: "text"});
+ is(value, "foobar", "value was restored");
+
+ // Restore form data with an invalid URL.
+ yield promiseTabState(tab, getState("http://example.com/"));
+
+ value = yield getInputValue(browser, {id: "text"});
+ is(value, "", "value was not restored");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+function getState(url) {
+ return JSON.stringify({
+ entries: [{url: URL}],
+ formdata: {url: url, id: {text: "foobar"}}
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_463205_sample.html b/browser/components/sessionstore/test/browser_463205_sample.html
new file mode 100644
index 000000000..6591401b6
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_463205_sample.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>bug 463205</title>
+
+<body>
+ <input type="text" id="text" />
+</body>
diff --git a/browser/components/sessionstore/test/browser_463206.js b/browser/components/sessionstore/test/browser_463206.js
new file mode 100644
index 000000000..99ee8373e
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_463206.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";
+
+const TEST_URL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_463206_sample.html";
+
+add_task(function* () {
+ // Add a new tab.
+ let tab = gBrowser.addTab(TEST_URL);
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // "Type in" some random values.
+ yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
+ function typeText(aTextField, aValue) {
+ aTextField.value = aValue;
+
+ let event = aTextField.ownerDocument.createEvent("UIEvents");
+ event.initUIEvent("input", true, true, aTextField.ownerGlobal, 0);
+ aTextField.dispatchEvent(event);
+ }
+
+ typeText(content.document.getElementById("out1"), Date.now());
+ typeText(content.document.getElementsByName("1|#out2")[0], Math.random());
+ typeText(content.frames[0].frames[1].document.getElementById("in1"), new Date());
+ });
+
+ // Duplicate the tab.
+ let tab2 = gBrowser.duplicateTab(tab);
+ yield promiseTabRestored(tab2);
+
+ // Query a few values from the top and its child frames.
+ yield ContentTask.spawn(tab2.linkedBrowser, null, function* () {
+ Assert.notEqual(content.document.getElementById("out1").value,
+ content.frames[1].document.getElementById("out1").value,
+ "text isn't reused for frames");
+ Assert.notEqual(content.document.getElementsByName("1|#out2")[0].value,
+ "", "text containing | and # is correctly restored");
+ Assert.equal(content.frames[1].document.getElementById("out2").value,
+ "", "id prefixes can't be faked");
+ // Disabled for now, Bug 588077
+ //Assert.equal(content.frames[0].frames[1].document.getElementById("in1").value,
+ // "", "id prefixes aren't mixed up");
+ Assert.equal(content.frames[1].frames[0].document.getElementById("in1").value,
+ "", "id prefixes aren't mixed up");
+ });
+
+ // Cleanup.
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_463206_sample.html b/browser/components/sessionstore/test/browser_463206_sample.html
new file mode 100644
index 000000000..0d31f2906
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_463206_sample.html
@@ -0,0 +1,11 @@
+<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
+
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for bug 463206</title>
+
+<iframe src="data:text/html;charset=utf-8,<iframe></iframe><iframe%20src='data:text/html;charset=utf-8,<input%2520id=%2522in1%2522>'></iframe>"></iframe>
+<iframe src="data:text/html;charset=utf-8,<input%20id='out1'><input%20id='out2'><iframe%20src='data:text/html;charset=utf-8,<input%2520id=%2522in1%2522>'>"></iframe>
+
+<input id="out1">
+<input name="1|#out2">
diff --git a/browser/components/sessionstore/test/browser_464199.js b/browser/components/sessionstore/test/browser_464199.js
new file mode 100644
index 000000000..36f07832c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_464199.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/ForgetAboutSite.jsm");
+
+function waitForClearHistory(aCallback) {
+ let observer = {
+ observe: function(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(this, "browser:purge-domain-data");
+ setTimeout(aCallback, 0);
+ }
+ };
+ Services.obs.addObserver(observer, "browser:purge-domain-data", false);
+}
+
+function test() {
+ /** Test for Bug 464199 **/
+
+ waitForExplicitFinish();
+
+ const REMEMBER = Date.now(), FORGET = Math.random();
+ let test_state = { windows: [{ "tabs": [{ "entries": [] }], _closedTabs: [
+ { state: { entries: [{ url: "http://www.example.net/" }] }, title: FORGET },
+ { state: { entries: [{ url: "http://www.example.org/" }] }, title: REMEMBER },
+ { state: { entries: [{ url: "http://www.example.net/" },
+ { url: "http://www.example.org/" }] }, title: FORGET },
+ { state: { entries: [{ url: "http://example.net/" }] }, title: FORGET },
+ { state: { entries: [{ url: "http://sub.example.net/" }] }, title: FORGET },
+ { state: { entries: [{ url: "http://www.example.net:8080/" }] }, title: FORGET },
+ { state: { entries: [{ url: "about:license" }] }, title: REMEMBER },
+ { state: { entries: [{ url: "http://www.example.org/frameset",
+ children: [
+ { url: "http://www.example.org/frame" },
+ { url: "http://www.example.org:8080/frame2" }
+ ] }] }, title: REMEMBER },
+ { state: { entries: [{ url: "http://www.example.org/frameset",
+ children: [
+ { url: "http://www.example.org/frame" },
+ { url: "http://www.example.net/frame" }
+ ] }] }, title: FORGET },
+ { state: { entries: [{ url: "http://www.example.org/form",
+ formdata: { id: { "url": "http://www.example.net/" } }
+ }] }, title: REMEMBER },
+ { state: { entries: [{ url: "http://www.example.org/form" }],
+ extData: { "setTabValue": "http://example.net:80" } }, title: REMEMBER }
+ ] }] };
+ let remember_count = 5;
+
+ function countByTitle(aClosedTabList, aTitle) {
+ return aClosedTabList.filter(aData => aData.title == aTitle).length;
+ }
+
+ // open a window and add the above closed tab list
+ let newWin = openDialog(location, "", "chrome,all,dialog=no");
+ promiseWindowLoaded(newWin).then(() => {
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo",
+ test_state.windows[0]._closedTabs.length);
+ ss.setWindowState(newWin, JSON.stringify(test_state), true);
+
+ let closedTabs = JSON.parse(ss.getClosedTabData(newWin));
+ is(closedTabs.length, test_state.windows[0]._closedTabs.length,
+ "Closed tab list has the expected length");
+ is(countByTitle(closedTabs, FORGET),
+ test_state.windows[0]._closedTabs.length - remember_count,
+ "The correct amout of tabs are to be forgotten");
+ is(countByTitle(closedTabs, REMEMBER), remember_count,
+ "Everything is set up.");
+
+ ForgetAboutSite.removeDataFromDomain("example.net");
+ waitForClearHistory(function() {
+ closedTabs = JSON.parse(ss.getClosedTabData(newWin));
+ is(closedTabs.length, remember_count,
+ "The correct amout of tabs was removed");
+ is(countByTitle(closedTabs, FORGET), 0,
+ "All tabs to be forgotten were indeed removed");
+ is(countByTitle(closedTabs, REMEMBER), remember_count,
+ "... and tabs to be remembered weren't.");
+
+ // clean up
+ gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
+ BrowserTestUtils.closeWindow(newWin).then(finish);
+ });
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_464620_a.html b/browser/components/sessionstore/test/browser_464620_a.html
new file mode 100644
index 000000000..1f03c92c7
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_464620_a.html
@@ -0,0 +1,54 @@
+<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
+
+<title>Test for bug 464620 (injection on input)</title>
+
+<iframe></iframe>
+<iframe onload="setup()"></iframe>
+
+<script>
+ var targetUrl = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_464620_xd.html";
+ var firstPass;
+
+ function setup() {
+ if (firstPass !== undefined)
+ return;
+ firstPass = frames[1].location.href == "about:blank";
+ if (firstPass) {
+ frames[0].location = 'data:text/html;charset=utf-8,<body onload="if (parent.firstPass) parent.step();"><input id="x" oninput="parent.xss()">XXX</body>';
+ }
+ frames[1].location = targetUrl;
+ }
+
+ function step() {
+ var x = frames[0].document.getElementById("x");
+ if (x.value == "")
+ x.value = "ready";
+ x.style.display = "none";
+ frames[0].document.designMode = "on";
+ }
+
+ function xss() {
+ step();
+
+ var documentInjected = false;
+ document.getElementsByTagName("iframe")[0].onload =
+ function() { documentInjected = true; };
+ frames[0].location = targetUrl;
+
+ for (var c = 0; !documentInjected && c < 20; c++) {
+ var r = new XMLHttpRequest();
+ r.open("GET", location.href, false);
+ r.overrideMimeType("text/plain");
+ r.send(null);
+ }
+ document.getElementById("state").textContent = "done";
+
+ var event = new MessageEvent('464620_a', { bubbles: true, cancelable: false,
+ data: "done", origin: location.href,
+ source: window });
+ document.dispatchEvent(event);
+ }
+</script>
+
+<p id="state">pending</p>
diff --git a/browser/components/sessionstore/test/browser_464620_a.js b/browser/components/sessionstore/test/browser_464620_a.js
new file mode 100644
index 000000000..9756fa703
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_464620_a.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/. */
+
+function test() {
+ /** Test for Bug 464620 (injection on input) **/
+
+ waitForExplicitFinish();
+
+ let testURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_464620_a.html";
+
+ var frameCount = 0;
+ let tab = gBrowser.addTab(testURL);
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ // wait for all frames to load completely
+ if (frameCount++ < 4)
+ return;
+ this.removeEventListener("load", arguments.callee, true);
+
+ executeSoon(function() {
+ frameCount = 0;
+ let tab2 = gBrowser.duplicateTab(tab);
+ tab2.linkedBrowser.addEventListener("464620_a", function(aEvent) {
+ tab2.linkedBrowser.removeEventListener("464620_a", arguments.callee, true);
+ is(aEvent.data, "done", "XSS injection was attempted");
+
+ // let form restoration complete and take into account the
+ // setTimeout(..., 0) in sss_restoreDocument_proxy
+ executeSoon(function() {
+ setTimeout(function() {
+ let win = tab2.linkedBrowser.contentWindow;
+ isnot(win.frames[0].document.location, testURL,
+ "cross domain document was loaded");
+ ok(!/XXX/.test(win.frames[0].document.body.innerHTML),
+ "no content was injected");
+
+ // clean up
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+
+ finish();
+ }, 0);
+ });
+ }, true, true);
+ });
+ }, true);
+}
diff --git a/browser/components/sessionstore/test/browser_464620_b.html b/browser/components/sessionstore/test/browser_464620_b.html
new file mode 100644
index 000000000..8c86d2152
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_464620_b.html
@@ -0,0 +1,58 @@
+<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
+
+<title>Test for bug 464620 (injection on DOM node insertion)</title>
+
+<iframe></iframe>
+<iframe></iframe>
+<iframe onload="setup()"></iframe>
+
+<script>
+ var targetUrl = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_464620_xd.html";
+ var firstPass;
+
+ function setup() {
+ if (firstPass !== undefined)
+ return;
+ firstPass = frames[2].location.href == "about:blank";
+ if (firstPass) {
+ frames[0].location = 'data:text/html;charset=utf-8,<body onload="parent.step()">a</body>';
+ frames[1].location = 'data:text/html;charset=utf-8,<body onload="document.designMode=\'on\';">XXX</body>';
+ }
+ frames[2].location = targetUrl;
+ }
+
+ function step() {
+ frames[0].document.designMode = "on";
+ if (firstPass)
+ return;
+
+ var body = frames[0].document.body;
+ body.addEventListener("DOMNodeInserted", function() {
+ body.removeEventListener("DOMNodeInserted", arguments.callee, true);
+ xss();
+ }, true);
+ }
+
+ function xss() {
+ var documentInjected = false;
+ document.getElementsByTagName("iframe")[1].onload =
+ function() { documentInjected = true; };
+ frames[1].location = targetUrl;
+
+ for (var c = 0; !documentInjected && c < 20; c++) {
+ var r = new XMLHttpRequest();
+ r.open("GET", location.href, false);
+ r.overrideMimeType("text/plain");
+ r.send(null);
+ }
+ document.getElementById("state").textContent = "done";
+
+ var event = new MessageEvent('464620_b', { bubbles: true, cancelable: false,
+ data: "done", origin: location.href,
+ source: window });
+ document.dispatchEvent(event);
+ }
+</script>
+
+<p id="state">pending</p>
diff --git a/browser/components/sessionstore/test/browser_464620_b.js b/browser/components/sessionstore/test/browser_464620_b.js
new file mode 100644
index 000000000..cf23aa460
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_464620_b.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/. */
+
+function test() {
+ /** Test for Bug 464620 (injection on DOM node insertion) **/
+
+ waitForExplicitFinish();
+
+ let testURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_464620_b.html";
+
+ var frameCount = 0;
+ let tab = gBrowser.addTab(testURL);
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ // wait for all frames to load completely
+ if (frameCount++ < 6)
+ return;
+ this.removeEventListener("load", arguments.callee, true);
+
+ executeSoon(function() {
+ frameCount = 0;
+ let tab2 = gBrowser.duplicateTab(tab);
+ tab2.linkedBrowser.addEventListener("464620_b", function(aEvent) {
+ tab2.linkedBrowser.removeEventListener("464620_b", arguments.callee, true);
+ is(aEvent.data, "done", "XSS injection was attempted");
+
+ // let form restoration complete and take into account the
+ // setTimeout(..., 0) in sss_restoreDocument_proxy
+ executeSoon(function() {
+ setTimeout(function() {
+ let win = tab2.linkedBrowser.contentWindow;
+ isnot(win.frames[1].document.location, testURL,
+ "cross domain document was loaded");
+ ok(!/XXX/.test(win.frames[1].document.body.innerHTML),
+ "no content was injected");
+
+ // clean up
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+
+ finish();
+ }, 0);
+ });
+ }, true, true);
+ });
+ }, true);
+}
diff --git a/browser/components/sessionstore/test/browser_464620_xd.html b/browser/components/sessionstore/test/browser_464620_xd.html
new file mode 100644
index 000000000..9ec51c4c7
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_464620_xd.html
@@ -0,0 +1,5 @@
+<title>Cross Document File for bug 464620</title>
+
+<body onload="document.designMode='on';" bgcolor="red">
+ This document is editable.
+</body>
diff --git a/browser/components/sessionstore/test/browser_465215.js b/browser/components/sessionstore/test/browser_465215.js
new file mode 100644
index 000000000..3a0a7b9c5
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_465215.js
@@ -0,0 +1,28 @@
+"use strict";
+
+var uniqueName = "bug 465215";
+var uniqueValue1 = "as good as unique: " + Date.now();
+var uniqueValue2 = "as good as unique: " + Math.random();
+
+add_task(function* () {
+ // set a unique value on a new, blank tab
+ let tab1 = gBrowser.addTab("about:blank");
+ yield promiseBrowserLoaded(tab1.linkedBrowser);
+ ss.setTabValue(tab1, uniqueName, uniqueValue1);
+
+ // duplicate the tab with that value
+ let tab2 = ss.duplicateTab(window, tab1);
+ yield promiseTabRestored(tab2);
+ is(ss.getTabValue(tab2, uniqueName), uniqueValue1, "tab value was duplicated");
+
+ ss.setTabValue(tab2, uniqueName, uniqueValue2);
+ isnot(ss.getTabValue(tab1, uniqueName), uniqueValue2, "tab values aren't sync'd");
+
+ // overwrite the tab with the value which should remove it
+ yield promiseTabState(tab1, {entries: []});
+ is(ss.getTabValue(tab1, uniqueName), "", "tab value was cleared");
+
+ // clean up
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab1);
+});
diff --git a/browser/components/sessionstore/test/browser_465223.js b/browser/components/sessionstore/test/browser_465223.js
new file mode 100644
index 000000000..04f888b30
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_465223.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ /** Test for Bug 465223 **/
+
+ // test setup
+ waitForExplicitFinish();
+
+ let uniqueKey1 = "bug 465223.1";
+ let uniqueKey2 = "bug 465223.2";
+ let uniqueValue1 = "unik" + Date.now();
+ let uniqueValue2 = "pi != " + Math.random();
+
+ // open a window and set a value on it
+ let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
+ promiseWindowLoaded(newWin).then(() => {
+ ss.setWindowValue(newWin, uniqueKey1, uniqueValue1);
+
+ let newState = { windows: [{ tabs:[{ entries: [] }], extData: {} }] };
+ newState.windows[0].extData[uniqueKey2] = uniqueValue2;
+ ss.setWindowState(newWin, JSON.stringify(newState), false);
+
+ is(newWin.gBrowser.tabs.length, 2,
+ "original tab wasn't overwritten");
+ is(ss.getWindowValue(newWin, uniqueKey1), uniqueValue1,
+ "window value wasn't overwritten when the tabs weren't");
+ is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue2,
+ "new window value was correctly added");
+
+ newState.windows[0].extData[uniqueKey2] = uniqueValue1;
+ ss.setWindowState(newWin, JSON.stringify(newState), true);
+
+ is(newWin.gBrowser.tabs.length, 1,
+ "original tabs were overwritten");
+ is(ss.getWindowValue(newWin, uniqueKey1), "",
+ "window value was cleared");
+ is(ss.getWindowValue(newWin, uniqueKey2), uniqueValue1,
+ "window value was correctly overwritten");
+
+ // clean up
+ BrowserTestUtils.closeWindow(newWin).then(finish);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_466937.js b/browser/components/sessionstore/test/browser_466937.js
new file mode 100644
index 000000000..0a07caa0c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_466937.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = ROOT + "browser_466937_sample.html";
+
+/**
+ * Bug 466937 - Prevent file stealing with sessionstore.
+ */
+add_task(function test_prevent_file_stealing() {
+ // Add a tab with some file input fields.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Generate a path to a 'secret' file.
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append("466937_test.file");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+ let testPath = file.path;
+
+ // Fill in form values.
+ yield setInputValue(browser, {id: "reverse_thief", value: "/home/user/secret2"});
+ yield setInputValue(browser, {id: "bystander", value: testPath});
+
+ // Duplicate and check form values.
+ let tab2 = gBrowser.duplicateTab(tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2);
+
+ let thief = yield getInputValue(browser2, {id: "thief"});
+ is(thief, "", "file path wasn't set to text field value");
+ let reverse_thief = yield getInputValue(browser2, {id: "reverse_thief"});
+ is(reverse_thief, "", "text field value wasn't set to full file path");
+ let bystander = yield getInputValue(browser2, {id: "bystander"});
+ is(bystander, testPath, "normal case: file path was correctly preserved");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(tab2);
+});
diff --git a/browser/components/sessionstore/test/browser_466937_sample.html b/browser/components/sessionstore/test/browser_466937_sample.html
new file mode 100644
index 000000000..1d46c649a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_466937_sample.html
@@ -0,0 +1,22 @@
+<!-- Testcase originally by <moz_bug_r_a4@yahoo.com> -->
+
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for bug 466937</title>
+
+<input id="thief" value="/home/user/secret">
+<input type="file" id="reverse_thief">
+<input type="file" id="bystander">
+
+<script>
+ window.addEventListener("DOMContentLoaded", function() {
+ window.removeEventListener("DOMContentLoaded", arguments.callee, false);
+ if (!document.location.hash) {
+ document.location.hash = "#ready";
+ }
+ else {
+ document.getElementById("thief").type = "file";
+ document.getElementById("reverse_thief").type = "text";
+ }
+ }, false);
+</script>
diff --git a/browser/components/sessionstore/test/browser_467409-backslashplosion.js b/browser/components/sessionstore/test/browser_467409-backslashplosion.js
new file mode 100644
index 000000000..0e990c614
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_467409-backslashplosion.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test Summary:
+// 1. Open about:sessionrestore where formdata is a JS object, not a string
+// 1a. Check that #sessionData on the page is readable after JSON.parse (skipped, checking formdata is sufficient)
+// 1b. Check that there are no backslashes in the formdata
+// 1c. Check that formdata doesn't require JSON.parse
+//
+// 2. Use the current state (currently about:sessionrestore with data) and then open that in a new instance of about:sessionrestore
+// 2a. Check that there are no backslashes in the formdata
+// 2b. Check that formdata doesn't require JSON.parse
+//
+// 3. [backwards compat] Use a stringified state as formdata when opening about:sessionrestore
+// 3a. Make sure there are nodes in the tree on about:sessionrestore (skipped, checking formdata is sufficient)
+// 3b. Check that there are no backslashes in the formdata
+// 3c. Check that formdata doesn't require JSON.parse
+
+const CRASH_STATE = {windows: [{tabs: [{entries: [{url: "about:mozilla" }]}]}]};
+const STATE = createEntries(CRASH_STATE);
+const STATE2 = createEntries({windows: [{tabs: [STATE]}]});
+const STATE3 = createEntries(JSON.stringify(CRASH_STATE));
+
+function createEntries(sessionData) {
+ return {
+ entries: [{url: "about:sessionrestore"}],
+ formdata: {id: {sessionData: sessionData}, url: "about:sessionrestore"}
+ };
+}
+
+add_task(function test_nested_about_sessionrestore() {
+ // Prepare a blank tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // test 1
+ yield promiseTabState(tab, STATE);
+ yield checkState("test1", tab);
+
+ // test 2
+ yield promiseTabState(tab, STATE2);
+ yield checkState("test2", tab);
+
+ // test 3
+ yield promiseTabState(tab, STATE3);
+ yield checkState("test3", tab);
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+function* checkState(prefix, tab) {
+ // Flush and query tab state.
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ let {formdata} = JSON.parse(ss.getTabState(tab));
+
+ ok(formdata.id["sessionData"], prefix + ": we have form data for about:sessionrestore");
+
+ let sessionData_raw = JSON.stringify(formdata.id["sessionData"]);
+ ok(!/\\/.test(sessionData_raw), prefix + ": #sessionData contains no backslashes");
+ info(sessionData_raw);
+
+ let gotError = false;
+ try {
+ JSON.parse(formdata.id["sessionData"]);
+ } catch (e) {
+ info(prefix + ": got error: " + e);
+ gotError = true;
+ }
+ ok(gotError, prefix + ": attempting to JSON.parse form data threw error");
+}
diff --git a/browser/components/sessionstore/test/browser_477657.js b/browser/components/sessionstore/test/browser_477657.js
new file mode 100644
index 000000000..1a8ee3412
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_477657.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/. */
+
+function test() {
+ /** Test for Bug 477657 **/
+ waitForExplicitFinish();
+
+ let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
+ promiseWindowLoaded(newWin).then(() => {
+ let newState = { windows: [{
+ tabs: [{ entries: [] }],
+ _closedTabs: [{
+ state: { entries: [{ url: "about:" }]},
+ title: "About:"
+ }],
+ sizemode: "maximized"
+ }] };
+
+ let uniqueKey = "bug 477657";
+ let uniqueValue = "unik" + Date.now();
+
+ ss.setWindowValue(newWin, uniqueKey, uniqueValue);
+ is(ss.getWindowValue(newWin, uniqueKey), uniqueValue,
+ "window value was set before the window was overwritten");
+ ss.setWindowState(newWin, JSON.stringify(newState), true);
+
+ // use newWin.setTimeout(..., 0) to mirror sss_restoreWindowFeatures
+ newWin.setTimeout(function() {
+ is(ss.getWindowValue(newWin, uniqueKey), "",
+ "window value was implicitly cleared");
+
+ is(newWin.windowState, newWin.STATE_MAXIMIZED,
+ "the window was maximized");
+
+ is(JSON.parse(ss.getClosedTabData(newWin)).length, 1,
+ "the closed tab was added before the window was overwritten");
+ delete newState.windows[0]._closedTabs;
+ delete newState.windows[0].sizemode;
+ ss.setWindowState(newWin, JSON.stringify(newState), true);
+
+ newWin.setTimeout(function() {
+ is(JSON.parse(ss.getClosedTabData(newWin)).length, 0,
+ "closed tabs were implicitly cleared");
+
+ is(newWin.windowState, newWin.STATE_MAXIMIZED,
+ "the window remains maximized");
+ newState.windows[0].sizemode = "normal";
+ ss.setWindowState(newWin, JSON.stringify(newState), true);
+
+ newWin.setTimeout(function() {
+ isnot(newWin.windowState, newWin.STATE_MAXIMIZED,
+ "the window was explicitly unmaximized");
+
+ BrowserTestUtils.closeWindow(newWin).then(finish);
+ }, 0);
+ }, 0);
+ }, 0);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_480893.js b/browser/components/sessionstore/test/browser_480893.js
new file mode 100644
index 000000000..e3ddb39b7
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_480893.js
@@ -0,0 +1,47 @@
+"use strict";
+
+/**
+ * Tests that we get sent to the right page when the user clicks
+ * the "Close" button in about:sessionrestore
+ */
+add_task(function*() {
+ yield SpecialPowers.pushPrefEnv({
+ "set": [
+ ["browser.startup.page", 0],
+ ]
+ });
+
+ let tab = gBrowser.addTab("about:sessionrestore");
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser, false, "about:sessionrestore");
+
+ let doc = browser.contentDocument;
+
+ // Click on the "Close" button after about:sessionrestore is loaded.
+ doc.getElementById("errorCancel").click();
+
+ yield BrowserTestUtils.browserLoaded(browser, false, "about:blank");
+
+ // Test that starting a new session loads the homepage (set to http://mochi.test:8888)
+ // if Firefox is configured to display a homepage at startup (browser.startup.page = 1)
+ let homepage = "http://mochi.test:8888/";
+ yield SpecialPowers.pushPrefEnv({
+ "set": [
+ ["browser.startup.homepage", homepage],
+ ["browser.startup.page", 1],
+ ]
+ });
+
+ browser.loadURI("about:sessionrestore");
+ yield BrowserTestUtils.browserLoaded(browser, false, "about:sessionrestore");
+ doc = browser.contentDocument;
+
+ // Click on the "Close" button after about:sessionrestore is loaded.
+ doc.getElementById("errorCancel").click();
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ is(browser.currentURI.spec, homepage, "loaded page is the homepage");
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_485482.js b/browser/components/sessionstore/test/browser_485482.js
new file mode 100644
index 000000000..68ec9941b
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_485482.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = ROOT + "browser_485482_sample.html";
+
+/**
+ * Bug 485482 - Make sure that we produce valid XPath expressions even for very
+ * weird HTML documents.
+ */
+add_task(function test_xpath_exp_for_strange_documents() {
+ // Load a page with weird tag names.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Fill in some values.
+ let uniqueValue = Math.random();
+ yield setInputValue(browser, {selector: "input[type=text]", value: uniqueValue});
+ yield setInputChecked(browser, {selector: "input[type=checkbox]", checked: true});
+
+ // Duplicate the tab.
+ let tab2 = gBrowser.duplicateTab(tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2);
+
+ // Check that we generated valid XPath expressions to restore form values.
+ let text = yield getInputValue(browser2, {selector: "input[type=text]"});
+ is(text, uniqueValue, "generated XPath expression was valid");
+ let checkbox = yield getInputChecked(browser2, {selector: "input[type=checkbox]"});
+ ok(checkbox, "generated XPath expression was valid");
+
+ // Cleanup.
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_485482_sample.html b/browser/components/sessionstore/test/browser_485482_sample.html
new file mode 100644
index 000000000..c2097b593
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_485482_sample.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Test for bug 485482</title>
+
+<bad=name>
+ <input type="text">
+</bad=name>
+
+<worse=name>
+ <l0c@l+na~e"'>
+ <input type="checkbox" name="check"> Check
+ </l0c@l+na~e"'>
+</worse=name>
diff --git a/browser/components/sessionstore/test/browser_485563.js b/browser/components/sessionstore/test/browser_485563.js
new file mode 100644
index 000000000..f4e6b31cc
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_485563.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/. */
+
+function test() {
+ /** Test for Bug 485563 **/
+
+ waitForExplicitFinish();
+
+ let uniqueValue = Math.random() + "\u2028Second line\u2029Second paragraph\u2027";
+
+ let tab = gBrowser.addTab();
+ promiseBrowserLoaded(tab.linkedBrowser).then(() => {
+ ss.setTabValue(tab, "bug485563", uniqueValue);
+ let tabState = JSON.parse(ss.getTabState(tab));
+ is(tabState.extData["bug485563"], uniqueValue,
+ "unicode line separator wasn't over-encoded");
+ ss.deleteTabValue(tab, "bug485563");
+ ss.setTabState(tab, JSON.stringify(tabState));
+ is(ss.getTabValue(tab, "bug485563"), uniqueValue,
+ "unicode line separator was correctly preserved");
+
+ gBrowser.removeTab(tab);
+ finish();
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_490040.js b/browser/components/sessionstore/test/browser_490040.js
new file mode 100644
index 000000000..bc680c32f
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_490040.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/. */
+
+// Only windows with open tabs are restorable. Windows where a lone tab is
+// detached may have _closedTabs, but is left with just an empty tab.
+const STATES = [{
+ shouldBeAdded: true,
+ windowState: {
+ windows: [{
+ tabs: [{ entries: [{ url: "http://example.com", title: "example.com" }] }],
+ selected: 1,
+ _closedTabs: []
+ }]
+ }
+ }, {
+ shouldBeAdded: false,
+ windowState: {
+ windows: [{
+ tabs: [{ entries: [] }],
+ _closedTabs: []
+ }]
+ }
+ }, {
+ shouldBeAdded: false,
+ windowState: {
+ windows: [{
+ tabs: [{ entries: [] }],
+ _closedTabs: [{ state: { entries: [{ url: "http://example.com", index: 1 }] } }]
+ }]
+ }
+ }, {
+ shouldBeAdded: false,
+ windowState: {
+ windows: [{
+ tabs: [{ entries: [] }],
+ _closedTabs: [],
+ extData: { keyname: "pi != " + Math.random() }
+ }]
+ }
+ }];
+
+add_task(function* test_bug_490040() {
+ for (let state of STATES) {
+ // Ensure we can store the window if needed.
+ let startingClosedWindowCount = ss.getClosedWindowCount();
+ yield pushPrefs(["browser.sessionstore.max_windows_undo",
+ startingClosedWindowCount + 1]);
+
+ let curClosedWindowCount = ss.getClosedWindowCount();
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+
+ ss.setWindowState(win, JSON.stringify(state.windowState), true);
+ if (state.windowState.windows[0].tabs.length) {
+ yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ }
+
+ yield BrowserTestUtils.closeWindow(win);
+
+ is(ss.getClosedWindowCount(),
+ curClosedWindowCount + (state.shouldBeAdded ? 1 : 0),
+ "That window should " + (state.shouldBeAdded ? "" : "not ") +
+ "be restorable");
+ }
+});
diff --git a/browser/components/sessionstore/test/browser_491168.js b/browser/components/sessionstore/test/browser_491168.js
new file mode 100644
index 000000000..ae66afe77
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_491168.js
@@ -0,0 +1,42 @@
+"use strict";
+
+const REFERRER1 = "http://example.org/?" + Date.now();
+const REFERRER2 = "http://example.org/?" + Math.random();
+
+add_task(function* () {
+ function* checkDocumentReferrer(referrer, msg) {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, { referrer, msg }, function* (args) {
+ Assert.equal(content.document.referrer, args.referrer, args.msg);
+ });
+ }
+
+ // Add a new tab.
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Load a new URI with a specific referrer.
+ let referrerURI = Services.io.newURI(REFERRER1, null, null);
+ browser.loadURI("http://example.org", referrerURI, null);
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+ let tabState = JSON.parse(ss.getTabState(tab));
+ is(tabState.entries[0].referrer, REFERRER1,
+ "Referrer retrieved via getTabState matches referrer set via loadURI.");
+
+ tabState.entries[0].referrer = REFERRER2;
+ yield promiseTabState(tab, tabState);
+
+ yield checkDocumentReferrer(REFERRER2,
+ "document.referrer matches referrer set via setTabState.");
+ gBrowser.removeCurrentTab();
+
+ // Restore the closed tab.
+ tab = ss.undoCloseTab(window, 0);
+ yield promiseTabRestored(tab);
+
+ yield checkDocumentReferrer(REFERRER2,
+ "document.referrer is still correct after closing and reopening the tab.");
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/components/sessionstore/test/browser_491577.js b/browser/components/sessionstore/test/browser_491577.js
new file mode 100644
index 000000000..0e088d702
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_491577.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/. */
+
+function test() {
+ /** Test for Bug 491577 **/
+
+ // test setup
+ waitForExplicitFinish();
+
+ const REMEMBER = Date.now(), FORGET = Math.random();
+ let test_state = {
+ windows: [ { tabs: [{ entries: [{ url: "http://example.com/" }] }], selected: 1 } ],
+ _closedWindows : [
+ // _closedWindows[0]
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com/", title: "title" }] },
+ { entries: [{ url: "http://mozilla.org/", title: "title" }] }
+ ],
+ selected: 2,
+ title: FORGET,
+ _closedTabs: []
+ },
+ // _closedWindows[1]
+ {
+ tabs: [
+ { entries: [{ url: "http://mozilla.org/", title: "title" }] },
+ { entries: [{ url: "http://example.com/", title: "title" }] },
+ { entries: [{ url: "http://mozilla.org/", title: "title" }] },
+ ],
+ selected: 3,
+ title: REMEMBER,
+ _closedTabs: []
+ },
+ // _closedWindows[2]
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com/", title: "title" }] }
+ ],
+ selected: 1,
+ title: FORGET,
+ _closedTabs: [
+ {
+ state: {
+ entries: [
+ { url: "http://mozilla.org/", title: "title" },
+ { url: "http://mozilla.org/again", title: "title" }
+ ]
+ },
+ pos: 1,
+ title: "title"
+ },
+ {
+ state: {
+ entries: [
+ { url: "http://example.com", title: "title" }
+ ]
+ },
+ title: "title"
+ }
+ ]
+ }
+ ]
+ };
+ let remember_count = 1;
+
+ function countByTitle(aClosedWindowList, aTitle) {
+ return aClosedWindowList.filter(aData => aData.title == aTitle).length;
+ }
+
+ function testForError(aFunction) {
+ try {
+ aFunction();
+ return false;
+ }
+ catch (ex) {
+ return ex.name == "NS_ERROR_ILLEGAL_VALUE";
+ }
+ }
+
+ // open a window and add the above closed window list
+ let newWin = openDialog(location, "_blank", "chrome,all,dialog=no");
+ promiseWindowLoaded(newWin).then(() => {
+ gPrefService.setIntPref("browser.sessionstore.max_windows_undo",
+ test_state._closedWindows.length);
+ ss.setWindowState(newWin, JSON.stringify(test_state), true);
+
+ let closedWindows = JSON.parse(ss.getClosedWindowData());
+ is(closedWindows.length, test_state._closedWindows.length,
+ "Closed window list has the expected length");
+ is(countByTitle(closedWindows, FORGET),
+ test_state._closedWindows.length - remember_count,
+ "The correct amount of windows are to be forgotten");
+ is(countByTitle(closedWindows, REMEMBER), remember_count,
+ "Everything is set up.");
+
+ // all of the following calls with illegal arguments should throw NS_ERROR_ILLEGAL_VALUE
+ ok(testForError(() => ss.forgetClosedWindow(-1)),
+ "Invalid window for forgetClosedWindow throws");
+ ok(testForError(() => ss.forgetClosedWindow(test_state._closedWindows.length + 1)),
+ "Invalid window for forgetClosedWindow throws");
+
+ // Remove third window, then first window
+ ss.forgetClosedWindow(2);
+ ss.forgetClosedWindow(null);
+
+ closedWindows = JSON.parse(ss.getClosedWindowData());
+ is(closedWindows.length, remember_count,
+ "The correct amount of windows were removed");
+ is(countByTitle(closedWindows, FORGET), 0,
+ "All windows specifically forgotten were indeed removed");
+ is(countByTitle(closedWindows, REMEMBER), remember_count,
+ "... and windows not specifically forgetten weren't.");
+
+ // clean up
+ gPrefService.clearUserPref("browser.sessionstore.max_windows_undo");
+ BrowserTestUtils.closeWindow(newWin).then(finish);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_495495.js b/browser/components/sessionstore/test/browser_495495.js
new file mode 100644
index 000000000..658f81c20
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_495495.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/. */
+
+function test() {
+ /** Test for Bug 495495 **/
+
+ waitForExplicitFinish();
+
+ let newWin = openDialog(location, "_blank", "chrome,all,dialog=no,toolbar=yes");
+ promiseWindowLoaded(newWin).then(() => {
+ let state1 = ss.getWindowState(newWin);
+ BrowserTestUtils.closeWindow(newWin).then(() => {
+
+ newWin = openDialog(location, "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar=no,location,personal,directories,dialog=no");
+ promiseWindowLoaded(newWin).then(() => {
+ let state2 = ss.getWindowState(newWin);
+
+ function testState(state, expected, callback) {
+ let win = openDialog(location, "_blank", "chrome,all,dialog=no");
+ promiseWindowLoaded(win).then(() => {
+
+ is(win.gURLBar.readOnly, false,
+ "URL bar should not be read-only before setting the state");
+ is(win.gURLBar.getAttribute("enablehistory"), "true",
+ "URL bar autocomplete should be enabled before setting the state");
+ ss.setWindowState(win, state, true);
+ is(win.gURLBar.readOnly, expected.readOnly,
+ "URL bar read-only state should be restored correctly");
+ is(win.gURLBar.getAttribute("enablehistory"), expected.enablehistory,
+ "URL bar autocomplete state should be restored correctly");
+
+ BrowserTestUtils.closeWindow(win).then(callback);
+ });
+ }
+
+ BrowserTestUtils.closeWindow(newWin).then(() => {
+ testState(state1, {readOnly: false, enablehistory: "true"}, function() {
+ testState(state2, {readOnly: true, enablehistory: "false"}, finish);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_500328.js b/browser/components/sessionstore/test/browser_500328.js
new file mode 100644
index 000000000..44650ef8b
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_500328.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/. */
+
+let checkState = Task.async(function*(browser) {
+ // Go back and then forward, and make sure that the state objects received
+ // from the popState event are as we expect them to be.
+ //
+ // We also add a node to the document's body when after going back and make
+ // sure it's still there after we go forward -- this is to test that the two
+ // history entries correspond to the same document.
+
+ let deferred = {};
+ deferred.promise = new Promise(resolve => deferred.resolve = resolve);
+
+ let popStateCount = 0;
+
+ browser.addEventListener("popstate", function(aEvent) {
+ if (popStateCount == 0) {
+ popStateCount++;
+
+ ok(aEvent.state, "Event should have a state property.");
+
+ ContentTask.spawn(browser, null, function() {
+ is(content.testState, "foo",
+ "testState after going back");
+ is(JSON.stringify(content.history.state), JSON.stringify({obj1:1}),
+ "first popstate object.");
+
+ // Add a node with id "new-elem" to the document.
+ let doc = content.document;
+ ok(!doc.getElementById("new-elem"),
+ "doc shouldn't contain new-elem before we add it.");
+ let elem = doc.createElement("div");
+ elem.id = "new-elem";
+ doc.body.appendChild(elem);
+ }).then(() => {
+ browser.goForward();
+ });
+ } else if (popStateCount == 1) {
+ popStateCount++;
+ // When content fires a PopStateEvent and we observe it from a chrome event
+ // listener (as we do here, and, thankfully, nowhere else in the tree), the
+ // state object will be a cross-compartment wrapper to an object that was
+ // deserialized in the content scope. And in this case, since RegExps are
+ // not currently Xrayable (see bug 1014991), trying to pull |obj3| (a RegExp)
+ // off of an Xrayed Object won't work. So we need to waive.
+ ContentTask.spawn(browser, aEvent.state, function(state) {
+ Assert.equal(Cu.waiveXrays(state).obj3.toString(),
+ "/^a$/", "second popstate object.");
+
+ // Make sure that the new-elem node is present in the document. If it's
+ // not, then this history entry has a different doc identifier than the
+ // previous entry, which is bad.
+ let doc = content.document;
+ let newElem = doc.getElementById("new-elem");
+ ok(newElem, "doc should contain new-elem.");
+ newElem.parentNode.removeChild(newElem);
+ ok(!doc.getElementById("new-elem"), "new-elem should be removed.");
+ }).then(() => {
+ browser.removeEventListener("popstate", arguments.callee, true);
+ deferred.resolve();
+ });
+ }
+ });
+
+ // Set some state in the page's window. When we go back(), the page should
+ // be retrieved from bfcache, and this state should still be there.
+ yield ContentTask.spawn(browser, null, function() {
+ content.testState = "foo";
+ });
+
+ // Now go back. This should trigger the popstate event handler above.
+ browser.goBack();
+
+ yield deferred.promise;
+});
+
+add_task(function* test() {
+ // Tests session restore functionality of history.pushState and
+ // history.replaceState(). (Bug 500328)
+
+ // We open a new blank window, let it load, and then load in
+ // http://example.com. We need to load the blank window first, otherwise the
+ // docshell gets confused and doesn't have a current history entry.
+ let state;
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ BrowserTestUtils.loadURI(browser, "http://example.com");
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // After these push/replaceState calls, the window should have three
+ // history entries:
+ // testURL (state object: null) <-- oldest
+ // testURL (state object: {obj1:1})
+ // testURL?page2 (state object: {obj3:/^a$/}) <-- newest
+ function contentTest() {
+ let history = content.window.history;
+ history.pushState({obj1:1}, "title-obj1");
+ history.pushState({obj2:2}, "title-obj2", "?page2");
+ history.replaceState({obj3:/^a$/}, "title-obj3");
+ }
+ yield ContentTask.spawn(browser, null, contentTest);
+ yield TabStateFlusher.flush(browser);
+
+ state = ss.getTabState(gBrowser.getTabForBrowser(browser));
+ });
+
+ // Restore the state into a new tab. Things don't work well when we
+ // restore into the old tab, but that's not a real use case anyway.
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
+ let tab2 = gBrowser.getTabForBrowser(browser);
+
+ let tabRestoredPromise = promiseTabRestored(tab2);
+ ss.setTabState(tab2, state, true);
+
+ // Run checkState() once the tab finishes loading its restored state.
+ yield tabRestoredPromise;
+ yield checkState(browser);
+ });
+});
diff --git a/browser/components/sessionstore/test/browser_506482.js b/browser/components/sessionstore/test/browser_506482.js
new file mode 100644
index 000000000..6e5bd83bd
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_506482.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/. */
+
+function test() {
+ /** Test for Bug 506482 **/
+
+ // test setup
+ waitForExplicitFinish();
+
+ // read the sessionstore.js mtime (picked from browser_248970_a.js)
+ let profilePath = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ function getSessionstoreFile() {
+ let sessionStoreJS = profilePath.clone();
+ sessionStoreJS.append("sessionstore.js");
+ return sessionStoreJS;
+ }
+ function getSessionstorejsModificationTime() {
+ let file = getSessionstoreFile();
+ if (file.exists())
+ return file.lastModifiedTime;
+ else
+ return -1;
+ }
+
+ // delete existing sessionstore.js, to make sure we're not reading
+ // the mtime of an old one initially.
+ let sessionStoreJS = getSessionstoreFile();
+ if (sessionStoreJS.exists())
+ sessionStoreJS.remove(false);
+
+ // test content URL
+ const TEST_URL = "data:text/html;charset=utf-8,"
+ + "<body style='width: 100000px; height: 100000px;'><p>top</p></body>"
+
+ // preferences that we use
+ const PREF_INTERVAL = "browser.sessionstore.interval";
+
+ // make sure sessionstore.js is saved ASAP on all events
+ gPrefService.setIntPref(PREF_INTERVAL, 0);
+
+ // get the initial sessionstore.js mtime (-1 if it doesn't exist yet)
+ let mtime0 = getSessionstorejsModificationTime();
+
+ // create and select a first tab
+ let tab = gBrowser.addTab(TEST_URL);
+ promiseBrowserLoaded(tab.linkedBrowser).then(() => {
+ // step1: the above has triggered some saveStateDelayed(), sleep until
+ // it's done, and get the initial sessionstore.js mtime
+ setTimeout(function step1(e) {
+ let mtime1 = getSessionstorejsModificationTime();
+ isnot(mtime1, mtime0, "initial sessionstore.js update");
+
+ // step2: test sessionstore.js is not updated on tab selection
+ // or content scrolling
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.contentWindow.scrollTo(1100, 1200);
+ setTimeout(function step2(e) {
+ let mtime2 = getSessionstorejsModificationTime();
+ is(mtime2, mtime1,
+ "tab selection and scrolling: sessionstore.js not updated");
+
+ // ok, done, cleanup and finish
+ if (gPrefService.prefHasUserValue(PREF_INTERVAL))
+ gPrefService.clearUserPref(PREF_INTERVAL);
+ gBrowser.removeTab(tab);
+ finish();
+ }, 3500); // end of sleep after tab selection and scrolling
+ }, 3500); // end of sleep after initial saveStateDelayed()
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_514751.js b/browser/components/sessionstore/test/browser_514751.js
new file mode 100644
index 000000000..ff80245c4
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_514751.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ /** Test for Bug 514751 (Wallpaper) **/
+
+ waitForExplicitFinish();
+
+ let state = {
+ windows: [{
+ tabs: [{
+ entries: [
+ { url: "about:mozilla", title: "Mozilla" },
+ {}
+ ]
+ }]
+ }]
+ };
+
+ var theWin = openDialog(location, "", "chrome,all,dialog=no");
+ theWin.addEventListener("load", function () {
+ theWin.removeEventListener("load", arguments.callee, false);
+
+ executeSoon(function () {
+ var gotError = false;
+ try {
+ ss.setWindowState(theWin, JSON.stringify(state), true);
+ } catch (e) {
+ if (/NS_ERROR_MALFORMED_URI/.test(e))
+ gotError = true;
+ }
+ ok(!gotError, "Didn't get a malformed URI error.");
+ BrowserTestUtils.closeWindow(theWin).then(finish);
+ });
+ }, false);
+}
+
diff --git a/browser/components/sessionstore/test/browser_522375.js b/browser/components/sessionstore/test/browser_522375.js
new file mode 100644
index 000000000..50b74d6cd
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_522375.js
@@ -0,0 +1,21 @@
+function test() {
+ var startup_info = Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(Components.interfaces.nsIAppStartup).getStartupInfo();
+ // No .process info on mac
+
+ // Check if we encountered a telemetry error for the the process creation
+ // timestamp and turn the first test into a known failure.
+ var telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
+ var snapshot = telemetry.getHistogramById("STARTUP_MEASUREMENT_ERRORS")
+ .snapshot();
+
+ if (snapshot.counts[0] == 0)
+ ok(startup_info.process <= startup_info.main, "process created before main is run " + uneval(startup_info));
+ else
+ todo(false, "An error occurred while recording the process creation timestamp, skipping this test");
+
+ // on linux firstPaint can happen after everything is loaded (especially with remote X)
+ if (startup_info.firstPaint)
+ ok(startup_info.main <= startup_info.firstPaint, "main ran before first paint " + uneval(startup_info));
+
+ ok(startup_info.main < startup_info.sessionRestored, "Session restored after main " + uneval(startup_info));
+}
diff --git a/browser/components/sessionstore/test/browser_522545.js b/browser/components/sessionstore/test/browser_522545.js
new file mode 100644
index 000000000..f4d373166
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_522545.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/. */
+
+function test() {
+ /** Test for Bug 522545 **/
+
+ waitForExplicitFinish();
+ requestLongerTimeout(4);
+
+ // This tests the following use case:
+ // User opens a new tab which gets focus. The user types something into the
+ // address bar, then crashes or quits.
+ function test_newTabFocused() {
+ let state = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:mozilla" }] },
+ { entries: [], userTypedValue: "example.com", userTypedClear: 0 }
+ ],
+ selected: 2
+ }]
+ };
+
+ waitForBrowserState(state, function() {
+ let browser = gBrowser.selectedBrowser;
+ is(browser.currentURI.spec, "about:blank",
+ "No history entries still sets currentURI to about:blank");
+ is(browser.userTypedValue, "example.com",
+ "userTypedValue was correctly restored");
+ ok(!browser.didStartLoadSinceLastUserTyping(),
+ "We still know that no load is ongoing");
+ is(gURLBar.value, "example.com",
+ "Address bar's value correctly restored");
+ // Change tabs to make sure address bar value gets updated
+ gBrowser.selectedTab = gBrowser.tabContainer.getItemAtIndex(0);
+ is(gURLBar.value, "about:mozilla",
+ "Address bar's value correctly updated");
+ runNextTest();
+ });
+ }
+
+ // This tests the following use case:
+ // User opens a new tab which gets focus. The user types something into the
+ // address bar, switches back to the first tab, then crashes or quits.
+ function test_newTabNotFocused() {
+ let state = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:mozilla" }] },
+ { entries: [], userTypedValue: "example.org", userTypedClear: 0 }
+ ],
+ selected: 1
+ }]
+ };
+
+ waitForBrowserState(state, function() {
+ let browser = gBrowser.getBrowserAtIndex(1);
+ is(browser.currentURI.spec, "about:blank",
+ "No history entries still sets currentURI to about:blank");
+ is(browser.userTypedValue, "example.org",
+ "userTypedValue was correctly restored");
+ ok(!browser.didStartLoadSinceLastUserTyping(),
+ "We still know that no load is ongoing");
+ is(gURLBar.value, "about:mozilla",
+ "Address bar's value correctly restored");
+ // Change tabs to make sure address bar value gets updated
+ gBrowser.selectedTab = gBrowser.tabContainer.getItemAtIndex(1);
+ is(gURLBar.value, "example.org",
+ "Address bar's value correctly updated");
+ runNextTest();
+ });
+ }
+
+ // This tests the following use case:
+ // User is in a tab with session history, then types something in the
+ // address bar, then crashes or quits.
+ function test_existingSHEnd_noClear() {
+ let state = {
+ windows: [{
+ tabs: [{
+ entries: [{ url: "about:mozilla" }, { url: "about:config" }],
+ index: 2,
+ userTypedValue: "example.com",
+ userTypedClear: 0
+ }]
+ }]
+ };
+
+ waitForBrowserState(state, function() {
+ let browser = gBrowser.selectedBrowser;
+ is(browser.currentURI.spec, "about:config",
+ "browser.currentURI set to current entry in SH");
+ is(browser.userTypedValue, "example.com",
+ "userTypedValue was correctly restored");
+ ok(!browser.didStartLoadSinceLastUserTyping(),
+ "We still know that no load is ongoing");
+ is(gURLBar.value, "example.com",
+ "Address bar's value correctly restored to userTypedValue");
+ runNextTest();
+ });
+ }
+
+ // This tests the following use case:
+ // User is in a tab with session history, presses back at some point, then
+ // types something in the address bar, then crashes or quits.
+ function test_existingSHMiddle_noClear() {
+ let state = {
+ windows: [{
+ tabs: [{
+ entries: [{ url: "about:mozilla" }, { url: "about:config" }],
+ index: 1,
+ userTypedValue: "example.org",
+ userTypedClear: 0
+ }]
+ }]
+ };
+
+ waitForBrowserState(state, function() {
+ let browser = gBrowser.selectedBrowser;
+ is(browser.currentURI.spec, "about:mozilla",
+ "browser.currentURI set to current entry in SH");
+ is(browser.userTypedValue, "example.org",
+ "userTypedValue was correctly restored");
+ ok(!browser.didStartLoadSinceLastUserTyping(),
+ "We still know that no load is ongoing");
+ is(gURLBar.value, "example.org",
+ "Address bar's value correctly restored to userTypedValue");
+ runNextTest();
+ });
+ }
+
+ // This test simulates lots of tabs opening at once and then quitting/crashing.
+ function test_getBrowserState_lotsOfTabsOpening() {
+ gBrowser.stop();
+
+ let uris = [];
+ for (let i = 0; i < 25; i++)
+ uris.push("http://example.com/" + i);
+
+ // We're waiting for the first location change, which should indicate
+ // one of the tabs has loaded and the others haven't. So one should
+ // be in a non-userTypedValue case, while others should still have
+ // userTypedValue and userTypedClear set.
+ gBrowser.addTabsProgressListener({
+ onLocationChange: function (aBrowser) {
+ if (uris.indexOf(aBrowser.currentURI.spec) > -1) {
+ gBrowser.removeTabsProgressListener(this);
+ firstLocationChange();
+ }
+ }
+ });
+
+ function firstLocationChange() {
+ let state = JSON.parse(ss.getBrowserState());
+ let hasUTV = state.windows[0].tabs.some(function(aTab) {
+ return aTab.userTypedValue && aTab.userTypedClear && !aTab.entries.length;
+ });
+
+ ok(hasUTV, "At least one tab has a userTypedValue with userTypedClear with no loaded URL");
+
+ BrowserTestUtils.waitForMessage(gBrowser.selectedBrowser.messageManager, "SessionStore:update").then(firstLoad);
+ }
+
+ function firstLoad() {
+ let state = JSON.parse(ss.getTabState(gBrowser.selectedTab));
+ let hasSH = !("userTypedValue" in state) && state.entries[0].url;
+ ok(hasSH, "The selected tab has its entry in SH");
+
+ runNextTest();
+ }
+
+ gBrowser.loadTabs(uris);
+ }
+
+ // This simulates setting a userTypedValue and ensures that just typing in the
+ // URL bar doesn't set userTypedClear as well.
+ function test_getBrowserState_userTypedValue() {
+ let state = {
+ windows: [{
+ tabs: [{ entries: [] }]
+ }]
+ };
+
+ waitForBrowserState(state, function() {
+ let browser = gBrowser.selectedBrowser;
+ // Make sure this tab isn't loading and state is clear before we test.
+ is(browser.userTypedValue, null, "userTypedValue is empty to start");
+ ok(!browser.didStartLoadSinceLastUserTyping(),
+ "Initially, no load should be ongoing");
+
+ let inputText = "example.org";
+ gURLBar.focus();
+ gURLBar.value = inputText.slice(0, -1);
+ EventUtils.synthesizeKey(inputText.slice(-1) , {});
+
+ executeSoon(function () {
+ is(browser.userTypedValue, "example.org",
+ "userTypedValue was set when changing URLBar value");
+ ok(!browser.didStartLoadSinceLastUserTyping(),
+ "No load started since changing URLBar value");
+
+ // Now make sure ss gets these values too
+ let newState = JSON.parse(ss.getBrowserState());
+ is(newState.windows[0].tabs[0].userTypedValue, "example.org",
+ "sessionstore got correct userTypedValue");
+ is(newState.windows[0].tabs[0].userTypedClear, 0,
+ "sessionstore got correct userTypedClear");
+ runNextTest();
+ });
+ });
+ }
+
+ // test_getBrowserState_lotsOfTabsOpening tested userTypedClear in a few cases,
+ // but not necessarily any that had legitimate URIs in the state of loading
+ // (eg, "http://example.com"), so this test will cover that case.
+ function test_userTypedClearLoadURI() {
+ let state = {
+ windows: [{
+ tabs: [
+ { entries: [], userTypedValue: "http://example.com", userTypedClear: 2 }
+ ]
+ }]
+ };
+
+ waitForBrowserState(state, function() {
+ let browser = gBrowser.selectedBrowser;
+ is(browser.currentURI.spec, "http://example.com/",
+ "userTypedClear=2 caused userTypedValue to be loaded");
+ is(browser.userTypedValue, null,
+ "userTypedValue was null after loading a URI");
+ ok(!browser.didStartLoadSinceLastUserTyping(),
+ "We should have reset the load state when the tab loaded");
+ is(gURLBar.textValue, gURLBar.trimValue("http://example.com/"),
+ "Address bar's value set after loading URI");
+ runNextTest();
+ });
+ }
+
+
+ let tests = [test_newTabFocused, test_newTabNotFocused,
+ test_existingSHEnd_noClear, test_existingSHMiddle_noClear,
+ test_getBrowserState_lotsOfTabsOpening,
+ test_getBrowserState_userTypedValue, test_userTypedClearLoadURI];
+ let originalState = JSON.parse(ss.getBrowserState());
+ let state = {
+ windows: [{
+ tabs: [{ entries: [{ url: "about:blank" }] }]
+ }]
+ };
+ function runNextTest() {
+ if (tests.length) {
+ waitForBrowserState(state, function() {
+ gBrowser.selectedBrowser.userTypedValue = null;
+ URLBarSetURI();
+ (tests.shift())();
+ });
+ } else {
+ waitForBrowserState(originalState, function() {
+ gBrowser.selectedBrowser.userTypedValue = null;
+ URLBarSetURI();
+ finish();
+ });
+ }
+ }
+
+ // Run the tests!
+ runNextTest();
+}
diff --git a/browser/components/sessionstore/test/browser_524745.js b/browser/components/sessionstore/test/browser_524745.js
new file mode 100644
index 000000000..de53f6c92
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_524745.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/. */
+
+function test() {
+ /** Test for Bug 524745 **/
+
+ let uniqKey = "bug524745";
+ let uniqVal = Date.now().toString();
+
+ waitForExplicitFinish();
+
+ whenNewWindowLoaded({ private: false }, function (window_B) {
+ waitForFocus(function() {
+ // Add identifying information to window_B
+ ss.setWindowValue(window_B, uniqKey, uniqVal);
+ let state = JSON.parse(ss.getBrowserState());
+ let selectedWindow = state.windows[state.selectedWindow - 1];
+ is(selectedWindow.extData && selectedWindow.extData[uniqKey], uniqVal,
+ "selectedWindow is window_B");
+
+ // Now minimize window_B. The selected window shouldn't have the secret data
+ window_B.minimize();
+ waitForFocus(function() {
+ state = JSON.parse(ss.getBrowserState());
+ selectedWindow = state.windows[state.selectedWindow - 1];
+ ok(!selectedWindow.extData || !selectedWindow.extData[uniqKey],
+ "selectedWindow is not window_B after minimizing it");
+
+ // Now minimize the last open window (assumes no other tests left windows open)
+ window.minimize();
+ state = JSON.parse(ss.getBrowserState());
+ is(state.selectedWindow, 0,
+ "selectedWindow should be 0 when all windows are minimized");
+
+ // Cleanup
+ window.restore();
+ BrowserTestUtils.closeWindow(window_B).then(finish);
+ });
+ }, window_B);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_526613.js b/browser/components/sessionstore/test/browser_526613.js
new file mode 100644
index 000000000..7e7fe8059
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_526613.js
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ /** Test for Bug 526613 **/
+
+ // test setup
+ waitForExplicitFinish();
+
+ function browserWindowsCount(expected) {
+ let count = 0;
+ let e = Services.wm.getEnumerator("navigator:browser");
+ while (e.hasMoreElements()) {
+ if (!e.getNext().closed)
+ ++count;
+ }
+ is(count, expected,
+ "number of open browser windows according to nsIWindowMediator");
+ let state = ss.getBrowserState();
+ info(state);
+ is(JSON.parse(state).windows.length, expected,
+ "number of open browser windows according to getBrowserState");
+ }
+
+ browserWindowsCount(1);
+
+ // backup old state
+ let oldState = ss.getBrowserState();
+ // create a new state for testing
+ let testState = {
+ windows: [
+ { tabs: [{ entries: [{ url: "http://example.com/" }] }], selected: 1 },
+ { tabs: [{ entries: [{ url: "about:mozilla" }] }], selected: 1 },
+ ],
+ // make sure the first window is focused, otherwise when restoring the
+ // old state, the first window is closed and the test harness gets unloaded
+ selectedWindow: 1
+ };
+
+ let pass = 1;
+ function observer(aSubject, aTopic, aData) {
+ is(aTopic, "sessionstore-browser-state-restored",
+ "The sessionstore-browser-state-restored notification was observed");
+
+ if (pass++ == 1) {
+ browserWindowsCount(2);
+
+ // let the first window be focused (see above)
+ function pollMostRecentWindow() {
+ if (Services.wm.getMostRecentWindow("navigator:browser") == window) {
+ ss.setBrowserState(oldState);
+ } else {
+ info("waiting for the current window to become active");
+ setTimeout(pollMostRecentWindow, 0);
+ window.focus(); //XXX Why is this needed?
+ }
+ }
+ pollMostRecentWindow();
+ }
+ else {
+ browserWindowsCount(1);
+ ok(!window.closed, "Restoring the old state should have left this window open");
+ Services.obs.removeObserver(observer, "sessionstore-browser-state-restored");
+ finish();
+ }
+ }
+ Services.obs.addObserver(observer, "sessionstore-browser-state-restored", false);
+
+ // set browser to test state
+ ss.setBrowserState(JSON.stringify(testState));
+}
diff --git a/browser/components/sessionstore/test/browser_528776.js b/browser/components/sessionstore/test/browser_528776.js
new file mode 100644
index 000000000..d799c9740
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_528776.js
@@ -0,0 +1,21 @@
+function browserWindowsCount(expected) {
+ var count = 0;
+ var e = Services.wm.getEnumerator("navigator:browser");
+ while (e.hasMoreElements()) {
+ if (!e.getNext().closed)
+ ++count;
+ }
+ is(count, expected,
+ "number of open browser windows according to nsIWindowMediator");
+ is(JSON.parse(ss.getBrowserState()).windows.length, expected,
+ "number of open browser windows according to getBrowserState");
+}
+
+add_task(function() {
+ browserWindowsCount(1);
+
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ browserWindowsCount(2);
+ yield BrowserTestUtils.closeWindow(win);
+ browserWindowsCount(1);
+});
diff --git a/browser/components/sessionstore/test/browser_579868.js b/browser/components/sessionstore/test/browser_579868.js
new file mode 100644
index 000000000..d6c6245d0
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_579868.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab1 = gBrowser.addTab("about:rights");
+ let tab2 = gBrowser.addTab("about:mozilla");
+
+ promiseBrowserLoaded(tab1.linkedBrowser).then(() => {
+ // Tell the session storer that the tab is pinned
+ let newTabState = '{"entries":[{"url":"about:rights"}],"pinned":true,"userTypedValue":"Hello World!"}';
+ ss.setTabState(tab1, newTabState);
+
+ // Undo pinning
+ gBrowser.unpinTab(tab1);
+
+ // Close and restore tab
+ gBrowser.removeTab(tab1);
+ let savedState = JSON.parse(ss.getClosedTabData(window))[0].state;
+ isnot(savedState.pinned, true, "Pinned should not be true");
+ tab1 = ss.undoCloseTab(window, 0);
+
+ isnot(tab1.pinned, true, "Should not be pinned");
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+ finish();
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_579879.js b/browser/components/sessionstore/test/browser_579879.js
new file mode 100644
index 000000000..6886be038
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_579879.js
@@ -0,0 +1,20 @@
+"use strict";
+
+add_task(function* () {
+ let tab1 = gBrowser.addTab("data:text/plain;charset=utf-8,foo");
+ gBrowser.pinTab(tab1);
+
+ yield promiseBrowserLoaded(tab1.linkedBrowser);
+ let tab2 = gBrowser.addTab();
+ gBrowser.pinTab(tab2);
+
+ is(Array.indexOf(gBrowser.tabs, tab1), 0, "pinned tab 1 is at the first position");
+ yield promiseRemoveTab(tab1);
+
+ tab1 = undoCloseTab();
+ ok(tab1.pinned, "pinned tab 1 has been restored as a pinned tab");
+ is(Array.indexOf(gBrowser.tabs, tab1), 0, "pinned tab 1 has been restored to the first position");
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+});
diff --git a/browser/components/sessionstore/test/browser_580512.js b/browser/components/sessionstore/test/browser_580512.js
new file mode 100644
index 000000000..ef048cd37
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_580512.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/. */
+
+const URIS_PINNED = ["about:license", "about:about"];
+const URIS_NORMAL_A = ["about:mozilla"];
+const URIS_NORMAL_B = ["about:buildconfig"];
+
+function test() {
+ waitForExplicitFinish();
+
+ isnot(Services.prefs.getIntPref("browser.startup.page"), 3,
+ "pref to save session must not be set for this test");
+ ok(!Services.prefs.getBoolPref("browser.sessionstore.resume_session_once"),
+ "pref to save session once must not be set for this test");
+
+ document.documentElement.setAttribute("windowtype", "navigator:browsertestdummy");
+
+ openWinWithCb(closeFirstWin, URIS_PINNED.concat(URIS_NORMAL_A));
+}
+
+function closeFirstWin(win) {
+ win.gBrowser.pinTab(win.gBrowser.tabs[0]);
+ win.gBrowser.pinTab(win.gBrowser.tabs[1]);
+
+ let winClosed = BrowserTestUtils.windowClosed(win);
+ // We need to call BrowserTryToCloseWindow in order to trigger
+ // the machinery that chooses whether or not to save the session
+ // for the last window.
+ win.BrowserTryToCloseWindow();
+ ok(win.closed, "window closed");
+
+ winClosed.then(() => {
+ openWinWithCb(checkSecondWin, URIS_NORMAL_B, URIS_PINNED.concat(URIS_NORMAL_B));
+ });
+}
+
+function checkSecondWin(win) {
+ is(win.gBrowser.browsers[0].currentURI.spec, URIS_PINNED[0], "first pinned tab restored");
+ is(win.gBrowser.browsers[1].currentURI.spec, URIS_PINNED[1], "second pinned tab restored");
+ ok(win.gBrowser.tabs[0].pinned, "first pinned tab is still pinned");
+ ok(win.gBrowser.tabs[1].pinned, "second pinned tab is still pinned");
+
+ BrowserTestUtils.closeWindow(win).then(() => {
+ // cleanup
+ document.documentElement.setAttribute("windowtype", "navigator:browser");
+ finish();
+ });
+}
+
+function openWinWithCb(cb, argURIs, expectedURIs) {
+ if (!expectedURIs)
+ expectedURIs = argURIs;
+
+ var win = openDialog(getBrowserURL(), "_blank",
+ "chrome,all,dialog=no", argURIs.join("|"));
+
+ win.addEventListener("load", function () {
+ win.removeEventListener("load", arguments.callee, false);
+ info("the window loaded");
+
+ var expectedLoads = expectedURIs.length;
+
+ win.gBrowser.addTabsProgressListener({
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aRequest &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+ expectedURIs.indexOf(aRequest.QueryInterface(Ci.nsIChannel).originalURI.spec) > -1 &&
+ --expectedLoads <= 0) {
+ win.gBrowser.removeTabsProgressListener(this);
+ info("all tabs loaded");
+ is(win.gBrowser.tabs.length, expectedURIs.length, "didn't load any unexpected tabs");
+ executeSoon(function () {
+ cb(win);
+ });
+ }
+ }
+ });
+ }, false);
+}
diff --git a/browser/components/sessionstore/test/browser_581937.js b/browser/components/sessionstore/test/browser_581937.js
new file mode 100644
index 000000000..74ddaa9d2
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_581937.js
@@ -0,0 +1,19 @@
+// Tests that an about:blank tab with no history will not be saved into
+// session store and thus, it will not show up in Recently Closed Tabs.
+
+"use strict";
+
+add_task(function* () {
+ let tab = gBrowser.addTab("about:blank");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ is(tab.linkedBrowser.currentURI.spec, "about:blank",
+ "we will be removing an about:blank tab");
+
+ let r = `rand-${Math.random()}`;
+ ss.setTabValue(tab, "foobar", r);
+
+ yield promiseRemoveTab(tab);
+ let closedTabData = ss.getClosedTabData(window);
+ ok(!closedTabData.includes(r), "tab not stored in _closedTabs");
+});
diff --git a/browser/components/sessionstore/test/browser_586068-apptabs.js b/browser/components/sessionstore/test/browser_586068-apptabs.js
new file mode 100644
index 000000000..f8727c04f
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-apptabs.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/. */
+
+requestLongerTimeout(2);
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ let state = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() }, pinned: true },
+ { entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() }, pinned: true },
+ { entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() }, pinned: true },
+ { entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
+ ], selected: 5 }] };
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ loadCount++;
+
+ // We'll make sure that the loads we get come from pinned tabs or the
+ // the selected tab.
+
+ // get the tab
+ let tab;
+ for (let i = 0; i < window.gBrowser.tabs.length; i++) {
+ if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
+ tab = window.gBrowser.tabs[i];
+ }
+
+ ok(tab.pinned || tab.selected,
+ "load came from pinned or selected tab");
+
+ // We should get 4 loads: 3 app tabs + 1 normal selected tab
+ if (loadCount < 4)
+ return;
+
+ gProgressListener.unsetCallback();
+ resolve();
+ });
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setBrowserState(JSON.stringify(state));
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-apptabs_ondemand.js b/browser/components/sessionstore/test/browser_586068-apptabs_ondemand.js
new file mode 100644
index 000000000..b58aa649b
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-apptabs_ondemand.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/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+const PREF_RESTORE_PINNED_TABS_ON_DEMAND = "browser.sessionstore.restore_pinned_tabs_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
+ Services.prefs.setBoolPref(PREF_RESTORE_PINNED_TABS_ON_DEMAND, true);
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ Services.prefs.clearUserPref(PREF_RESTORE_PINNED_TABS_ON_DEMAND);
+ });
+
+ let state = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() }, pinned: true },
+ { entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() }, pinned: true },
+ { entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() }, pinned: true },
+ { entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
+ ], selected: 5 }] };
+
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ // get the tab
+ let tab;
+ for (let i = 0; i < window.gBrowser.tabs.length; i++) {
+ if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
+ tab = window.gBrowser.tabs[i];
+ }
+
+ // Check that the load only comes from the selected tab.
+ ok(tab.selected, "load came from selected tab");
+ is(aNeedRestore, 6, "six tabs left to restore");
+ is(aRestoring, 1, "one tab is restoring");
+ is(aRestored, 0, "no tabs have been restored, yet");
+
+ gProgressListener.unsetCallback();
+ resolve();
+ });
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setBrowserState(JSON.stringify(state));
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-browser_state_interrupted.js b/browser/components/sessionstore/test/browser_586068-browser_state_interrupted.js
new file mode 100644
index 000000000..de8f1aba0
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-browser_state_interrupted.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/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+requestLongerTimeout(2);
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ // The first state will be loaded using setBrowserState, followed by the 2nd
+ // state also being loaded using setBrowserState, interrupting the first restore.
+ let state1 = { windows: [
+ {
+ tabs: [
+ { entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#4" }], extData: { "uniq": r() } }
+ ],
+ selected: 1
+ },
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } },
+ ],
+ selected: 3
+ }
+ ] };
+ let state2 = { windows: [
+ {
+ tabs: [
+ { entries: [{ url: "http://example.org#5" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#6" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#7" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#8" }], extData: { "uniq": r() } }
+ ],
+ selected: 3
+ },
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#7" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#8" }], extData: { "uniq": r() } },
+ ],
+ selected: 1
+ }
+ ] };
+
+ // interruptedAfter will be set after the selected tab from each window have loaded.
+ let interruptedAfter = 0;
+ let loadedWindow1 = false;
+ let loadedWindow2 = false;
+ let numTabs = state2.windows[0].tabs.length + state2.windows[1].tabs.length;
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ loadCount++;
+
+ if (aBrowser.currentURI.spec == state1.windows[0].tabs[2].entries[0].url)
+ loadedWindow1 = true;
+ if (aBrowser.currentURI.spec == state1.windows[1].tabs[0].entries[0].url)
+ loadedWindow2 = true;
+
+ if (!interruptedAfter && loadedWindow1 && loadedWindow2) {
+ interruptedAfter = loadCount;
+ ss.setBrowserState(JSON.stringify(state2));
+ return;
+ }
+
+ if (loadCount < numTabs + interruptedAfter)
+ return;
+
+ // We don't actually care about load order in this test, just that they all
+ // do load.
+ is(loadCount, numTabs + interruptedAfter, "all tabs were restored");
+ is(aNeedRestore, 0, "there are no tabs left needing restore");
+
+ // Remove the progress listener.
+ gProgressListener.unsetCallback();
+ resolve();
+ });
+ });
+
+ // We also want to catch the extra windows (there should be 2), so we need to observe domwindowopened
+ Services.ww.registerNotification(function observer(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ Services.ww.unregisterNotification(observer);
+ win.gBrowser.addTabsProgressListener(gProgressListener);
+ });
+ }
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setBrowserState(JSON.stringify(state1));
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseAllButPrimaryWindowClosed();
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-cascade.js b/browser/components/sessionstore/test/browser_586068-cascade.js
new file mode 100644
index 000000000..041aea85c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-cascade.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/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ let state = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com" }], extData: { "uniq": r() } }
+ ] }] };
+
+ let expectedCounts = [
+ [3, 3, 0],
+ [2, 3, 1],
+ [1, 3, 2],
+ [0, 3, 3],
+ [0, 2, 4],
+ [0, 1, 5]
+ ];
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ loadCount++;
+ let expected = expectedCounts[loadCount - 1];
+
+ is(aNeedRestore, expected[0], "load " + loadCount + " - # tabs that need to be restored");
+ is(aRestoring, expected[1], "load " + loadCount + " - # tabs that are restoring");
+ is(aRestored, expected[2], "load " + loadCount + " - # tabs that has been restored");
+
+ if (loadCount == state.windows[0].tabs.length) {
+ gProgressListener.unsetCallback();
+ resolve();
+ }
+ });
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setBrowserState(JSON.stringify(state));
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-multi_window.js b/browser/components/sessionstore/test/browser_586068-multi_window.js
new file mode 100644
index 000000000..03337568e
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-multi_window.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/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ // The first window will be put into the already open window and the second
+ // window will be opened with _openWindowWithState, which is the source of the problem.
+ let state = { windows: [
+ {
+ tabs: [
+ { entries: [{ url: "http://example.org#0" }], extData: { "uniq": r() } }
+ ],
+ selected: 1
+ },
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } }
+ ],
+ selected: 4
+ }
+ ] };
+ let numTabs = state.windows[0].tabs.length + state.windows[1].tabs.length;
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ if (++loadCount == numTabs) {
+ // We don't actually care about load order in this test, just that they all
+ // do load.
+ is(loadCount, numTabs, "all tabs were restored");
+ is(aNeedRestore, 0, "there are no tabs left needing restore");
+
+ gProgressListener.unsetCallback();
+ resolve();
+ }
+ });
+ });
+
+ // We also want to catch the 2nd window, so we need to observe domwindowopened
+ Services.ww.registerNotification(function observer(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ Services.ww.unregisterNotification(observer);
+ win.gBrowser.addTabsProgressListener(gProgressListener);
+ });
+ }
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setBrowserState(JSON.stringify(state));
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseAllButPrimaryWindowClosed();
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-reload.js b/browser/components/sessionstore/test/browser_586068-reload.js
new file mode 100644
index 000000000..630c91f2d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-reload.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/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ let state = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#8" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org/#9" }], extData: { "uniq": r() } },
+ ], selected: 1 }] };
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gBrowser.tabContainer.addEventListener("SSTabRestored", function onRestored(event) {
+ let tab = event.target;
+ let browser = tab.linkedBrowser;
+ let tabData = state.windows[0].tabs[loadCount++];
+
+ // double check that this tab was the right one
+ is(browser.currentURI.spec, tabData.entries[0].url,
+ "load " + loadCount + " - browser loaded correct url");
+ is(ss.getTabValue(tab, "uniq"), tabData.extData.uniq,
+ "load " + loadCount + " - correct tab was restored");
+
+ if (loadCount == state.windows[0].tabs.length) {
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", onRestored);
+ resolve();
+ } else {
+ // reload the next tab
+ gBrowser.browsers[loadCount].reload();
+ }
+ });
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setBrowserState(JSON.stringify(state));
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-select.js b/browser/components/sessionstore/test/browser_586068-select.js
new file mode 100644
index 000000000..433e1754c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-select.js
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ let state = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }
+ ], selected: 1 }] };
+
+ let expectedCounts = [
+ [5, 1, 0],
+ [4, 1, 1],
+ [3, 1, 2],
+ [2, 1, 3],
+ [1, 1, 4],
+ [0, 1, 5]
+ ];
+ let tabOrder = [0, 5, 1, 4, 3, 2];
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ loadCount++;
+ let expected = expectedCounts[loadCount - 1];
+
+ is(aNeedRestore, expected[0], "load " + loadCount + " - # tabs that need to be restored");
+ is(aRestoring, expected[1], "load " + loadCount + " - # tabs that are restoring");
+ is(aRestored, expected[2], "load " + loadCount + " - # tabs that has been restored");
+
+ if (loadCount < state.windows[0].tabs.length) {
+ // double check that this tab was the right one
+ let expectedData = state.windows[0].tabs[tabOrder[loadCount - 1]].extData.uniq;
+ let tab;
+ for (let i = 0; i < window.gBrowser.tabs.length; i++) {
+ if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
+ tab = window.gBrowser.tabs[i];
+ }
+
+ is(ss.getTabValue(tab, "uniq"), expectedData,
+ "load " + loadCount + " - correct tab was restored");
+
+ // select the next tab
+ window.gBrowser.selectTabAtIndex(tabOrder[loadCount]);
+ } else {
+ gProgressListener.unsetCallback();
+ resolve();
+ }
+ });
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setBrowserState(JSON.stringify(state));
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-window_state.js b/browser/components/sessionstore/test/browser_586068-window_state.js
new file mode 100644
index 000000000..6097a70db
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-window_state.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ // We'll use 2 states so that we can make sure calling setWindowState doesn't
+ // wipe out currently restoring data.
+ let state1 = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.com#1" }] },
+ { entries: [{ url: "http://example.com#2" }] },
+ { entries: [{ url: "http://example.com#3" }] },
+ { entries: [{ url: "http://example.com#4" }] },
+ { entries: [{ url: "http://example.com#5" }] },
+ ] }] };
+ let state2 = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org#1" }] },
+ { entries: [{ url: "http://example.org#2" }] },
+ { entries: [{ url: "http://example.org#3" }] },
+ { entries: [{ url: "http://example.org#4" }] },
+ { entries: [{ url: "http://example.org#5" }] }
+ ] }] };
+ let numTabs = state1.windows[0].tabs.length + state2.windows[0].tabs.length;
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ // When loadCount == 2, we'll also restore state2 into the window
+ if (++loadCount == 2) {
+ ss.setWindowState(window, JSON.stringify(state2), false);
+ }
+
+ if (loadCount < numTabs) {
+ return;
+ }
+
+ // We don't actually care about load order in this test, just that they all
+ // do load.
+ is(loadCount, numTabs, "test_setWindowStateNoOverwrite: all tabs were restored");
+ is(aNeedRestore, 0, "there are no tabs left needing restore");
+
+ gProgressListener.unsetCallback();
+ resolve();
+ });
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setWindowState(window, JSON.stringify(state1), true);
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586068-window_state_override.js b/browser/components/sessionstore/test/browser_586068-window_state_override.js
new file mode 100644
index 000000000..731e03307
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586068-window_state_override.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ // We'll use 2 states so that we can make sure calling setWindowState doesn't
+ // wipe out currently restoring data.
+ let state1 = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.com#1" }] },
+ { entries: [{ url: "http://example.com#2" }] },
+ { entries: [{ url: "http://example.com#3" }] },
+ { entries: [{ url: "http://example.com#4" }] },
+ { entries: [{ url: "http://example.com#5" }] },
+ ] }] };
+ let state2 = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org#1" }] },
+ { entries: [{ url: "http://example.org#2" }] },
+ { entries: [{ url: "http://example.org#3" }] },
+ { entries: [{ url: "http://example.org#4" }] },
+ { entries: [{ url: "http://example.org#5" }] }
+ ] }] };
+ let numTabs = 2 + state2.windows[0].tabs.length;
+
+ let loadCount = 0;
+ let promiseRestoringTabs = new Promise(resolve => {
+ gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
+ // When loadCount == 2, we'll also restore state2 into the window
+ if (++loadCount == 2) {
+ executeSoon(() => ss.setWindowState(window, JSON.stringify(state2), true));
+ }
+
+ if (loadCount < numTabs) {
+ return;
+ }
+
+ // We don't actually care about load order in this test, just that they all
+ // do load.
+ is(loadCount, numTabs, "all tabs were restored");
+ is(aNeedRestore, 0, "there are no tabs left needing restore");
+
+ gProgressListener.unsetCallback();
+ resolve();
+ });
+ });
+
+ let backupState = ss.getBrowserState();
+ ss.setWindowState(window, JSON.stringify(state1), true);
+ yield promiseRestoringTabs;
+
+ // Cleanup.
+ yield promiseBrowserState(backupState);
+});
diff --git a/browser/components/sessionstore/test/browser_586147.js b/browser/components/sessionstore/test/browser_586147.js
new file mode 100644
index 000000000..fbfec53c7
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_586147.js
@@ -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/. */
+
+function observeOneRestore(callback) {
+ let topic = "sessionstore-browser-state-restored";
+ Services.obs.addObserver(function onRestore() {
+ Services.obs.removeObserver(onRestore, topic);
+ callback();
+ }, topic, false);
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+ let hiddenTab = gBrowser.addTab();
+
+ is(gBrowser.visibleTabs.length, 2, "should have 2 tabs before hiding");
+ gBrowser.showOnlyTheseTabs([origTab]);
+ is(gBrowser.visibleTabs.length, 1, "only 1 after hiding");
+ ok(hiddenTab.hidden, "sanity check that it's hidden");
+
+ let extraTab = gBrowser.addTab();
+ let state = ss.getBrowserState();
+ let stateObj = JSON.parse(state);
+ let tabs = stateObj.windows[0].tabs;
+ is(tabs.length, 3, "just checking that browser state is correct");
+ ok(!tabs[0].hidden, "first tab is visible");
+ ok(tabs[1].hidden, "second is hidden");
+ ok(!tabs[2].hidden, "third is visible");
+
+ // Make the third tab hidden and then restore the modified state object
+ tabs[2].hidden = true;
+
+ observeOneRestore(function() {
+ let testWindow = Services.wm.getEnumerator("navigator:browser").getNext();
+ is(testWindow.gBrowser.visibleTabs.length, 1, "only restored 1 visible tab");
+ let tabs = testWindow.gBrowser.tabs;
+ ok(!tabs[0].hidden, "first is still visible");
+ ok(tabs[1].hidden, "second tab is still hidden");
+ ok(tabs[2].hidden, "third tab is now hidden");
+
+ // Restore the original state and clean up now that we're done
+ gBrowser.removeTab(hiddenTab);
+ gBrowser.removeTab(extraTab);
+
+ finish();
+ });
+ ss.setBrowserState(JSON.stringify(stateObj));
+}
diff --git a/browser/components/sessionstore/test/browser_588426.js b/browser/components/sessionstore/test/browser_588426.js
new file mode 100644
index 000000000..d2462f2bd
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_588426.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ let state = { windows: [{ tabs: [
+ {entries: [{url: "about:mozilla"}], hidden: true},
+ {entries: [{url: "about:rights"}], hidden: true}
+ ] }] };
+
+ waitForExplicitFinish();
+
+ newWindowWithState(state, function (win) {
+ registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
+
+ is(win.gBrowser.tabs.length, 2, "two tabs were restored");
+ is(win.gBrowser.visibleTabs.length, 1, "one tab is visible");
+
+ let tab = win.gBrowser.visibleTabs[0];
+ is(tab.linkedBrowser.currentURI.spec, "about:mozilla", "visible tab is about:mozilla");
+
+ finish();
+ });
+}
+
+function newWindowWithState(state, callback) {
+ let opts = "chrome,all,dialog=no,height=800,width=800";
+ let win = window.openDialog(getBrowserURL(), "_blank", opts);
+
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+
+ executeSoon(function () {
+ win.addEventListener("SSWindowStateReady", function onReady() {
+ win.removeEventListener("SSWindowStateReady", onReady, false);
+ promiseTabRestored(win.gBrowser.tabs[0]).then(() => callback(win));
+ }, false);
+
+ ss.setWindowState(win, JSON.stringify(state), true);
+ });
+ }, false);
+}
diff --git a/browser/components/sessionstore/test/browser_589246.js b/browser/components/sessionstore/test/browser_589246.js
new file mode 100644
index 000000000..d1f539073
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_589246.js
@@ -0,0 +1,242 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Mirrors WINDOW_ATTRIBUTES IN nsSessionStore.js
+const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
+
+var stateBackup = ss.getBrowserState();
+
+var originalWarnOnClose = gPrefService.getBoolPref("browser.tabs.warnOnClose");
+var originalStartupPage = gPrefService.getIntPref("browser.startup.page");
+var originalWindowType = document.documentElement.getAttribute("windowtype");
+
+var gotLastWindowClosedTopic = false;
+var shouldPinTab = false;
+var shouldOpenTabs = false;
+var shouldCloseTab = false;
+var testNum = 0;
+var afterTestCallback;
+
+// Set state so we know the closed windows content
+var testState = {
+ windows: [
+ { tabs: [{ entries: [{ url: "http://example.org" }] }] }
+ ],
+ _closedWindows: []
+};
+
+// We'll push a set of conditions and callbacks into this array
+// Ideally we would also test win/linux under a complete set of conditions, but
+// the tests for osx mirror the other set of conditions possible on win/linux.
+var tests = [];
+
+// the third & fourth test share a condition check, keep it DRY
+function checkOSX34Generator(num) {
+ return function(aPreviousState, aCurState) {
+ // In here, we should have restored the pinned tab, so only the unpinned tab
+ // should be in aCurState. So let's shape our expectations.
+ let expectedState = JSON.parse(aPreviousState);
+ expectedState[0].tabs.shift();
+ // size attributes are stripped out in _prepDataForDeferredRestore in nsSessionStore.
+ // This isn't the best approach, but neither is comparing JSON strings
+ WINDOW_ATTRIBUTES.forEach(attr => delete expectedState[0][attr]);
+
+ is(aCurState, JSON.stringify(expectedState),
+ "test #" + num + ": closedWindowState is as expected");
+ };
+}
+function checkNoWindowsGenerator(num) {
+ return function(aPreviousState, aCurState) {
+ is(aCurState, "[]", "test #" + num + ": there should be no closedWindowsLeft");
+ };
+}
+
+// The first test has 0 pinned tabs and 1 unpinned tab
+tests.push({
+ pinned: false,
+ extra: false,
+ close: false,
+ checkWinLin: checkNoWindowsGenerator(1),
+ checkOSX: function(aPreviousState, aCurState) {
+ is(aCurState, aPreviousState, "test #1: closed window state is unchanged");
+ }
+});
+
+// The second test has 1 pinned tab and 0 unpinned tabs.
+tests.push({
+ pinned: true,
+ extra: false,
+ close: false,
+ checkWinLin: checkNoWindowsGenerator(2),
+ checkOSX: checkNoWindowsGenerator(2)
+});
+
+// The third test has 1 pinned tab and 2 unpinned tabs.
+tests.push({
+ pinned: true,
+ extra: true,
+ close: false,
+ checkWinLin: checkNoWindowsGenerator(3),
+ checkOSX: checkOSX34Generator(3)
+});
+
+// The fourth test has 1 pinned tab, 2 unpinned tabs, and closes one unpinned tab.
+tests.push({
+ pinned: true,
+ extra: true,
+ close: "one",
+ checkWinLin: checkNoWindowsGenerator(4),
+ checkOSX: checkOSX34Generator(4)
+});
+
+// The fifth test has 1 pinned tab, 2 unpinned tabs, and closes both unpinned tabs.
+tests.push({
+ pinned: true,
+ extra: true,
+ close: "both",
+ checkWinLin: checkNoWindowsGenerator(5),
+ checkOSX: checkNoWindowsGenerator(5)
+});
+
+
+function test() {
+ /** Test for Bug 589246 - Closed window state getting corrupted when closing
+ and reopening last browser window without exiting browser **/
+ waitForExplicitFinish();
+ // windows opening & closing, so extending the timeout
+ requestLongerTimeout(2);
+
+ // We don't want the quit dialog pref
+ gPrefService.setBoolPref("browser.tabs.warnOnClose", false);
+ // Ensure that we would restore the session (important for Windows)
+ gPrefService.setIntPref("browser.startup.page", 3);
+
+ runNextTestOrFinish();
+}
+
+function runNextTestOrFinish() {
+ if (tests.length) {
+ setupForTest(tests.shift())
+ }
+ else {
+ // some state is cleaned up at the end of each test, but not all
+ ["browser.tabs.warnOnClose", "browser.startup.page"].forEach(function(p) {
+ if (gPrefService.prefHasUserValue(p))
+ gPrefService.clearUserPref(p);
+ });
+
+ ss.setBrowserState(stateBackup);
+ executeSoon(finish);
+ }
+}
+
+function setupForTest(aConditions) {
+ // reset some checks
+ gotLastWindowClosedTopic = false;
+ shouldPinTab = aConditions.pinned;
+ shouldOpenTabs = aConditions.extra;
+ shouldCloseTab = aConditions.close;
+ testNum++;
+
+ // set our test callback
+ afterTestCallback = /Mac/.test(navigator.platform) ? aConditions.checkOSX
+ : aConditions.checkWinLin;
+
+ // Add observers
+ Services.obs.addObserver(onLastWindowClosed, "browser-lastwindow-close-granted", false);
+
+ // Set the state
+ Services.obs.addObserver(onStateRestored, "sessionstore-browser-state-restored", false);
+ ss.setBrowserState(JSON.stringify(testState));
+}
+
+function onStateRestored(aSubject, aTopic, aData) {
+ info("test #" + testNum + ": onStateRestored");
+ Services.obs.removeObserver(onStateRestored, "sessionstore-browser-state-restored");
+
+ // change this window's windowtype so that closing a new window will trigger
+ // browser-lastwindow-close-granted.
+ document.documentElement.setAttribute("windowtype", "navigator:testrunner");
+
+ let newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "http://example.com");
+ newWin.addEventListener("load", function(aEvent) {
+ newWin.removeEventListener("load", arguments.callee, false);
+
+ promiseBrowserLoaded(newWin.gBrowser.selectedBrowser).then(() => {
+ // pin this tab
+ if (shouldPinTab)
+ newWin.gBrowser.pinTab(newWin.gBrowser.selectedTab);
+
+ newWin.addEventListener("unload", function () {
+ newWin.removeEventListener("unload", arguments.callee, false);
+ onWindowUnloaded();
+ }, false);
+ // Open a new tab as well. On Windows/Linux this will be restored when the
+ // new window is opened below (in onWindowUnloaded). On OS X we'll just
+ // restore the pinned tabs, leaving the unpinned tab in the closedWindowsData.
+ if (shouldOpenTabs) {
+ let newTab = newWin.gBrowser.addTab("about:config");
+ let newTab2 = newWin.gBrowser.addTab("about:buildconfig");
+
+ newTab.linkedBrowser.addEventListener("load", function() {
+ newTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ if (shouldCloseTab == "one") {
+ newWin.gBrowser.removeTab(newTab2);
+ }
+ else if (shouldCloseTab == "both") {
+ newWin.gBrowser.removeTab(newTab);
+ newWin.gBrowser.removeTab(newTab2);
+ }
+ newWin.BrowserTryToCloseWindow();
+ }, true);
+ }
+ else {
+ newWin.BrowserTryToCloseWindow();
+ }
+ });
+ }, false);
+}
+
+// This will be called before the window is actually closed
+function onLastWindowClosed(aSubject, aTopic, aData) {
+ info("test #" + testNum + ": onLastWindowClosed");
+ Services.obs.removeObserver(onLastWindowClosed, "browser-lastwindow-close-granted");
+ gotLastWindowClosedTopic = true;
+}
+
+// This is the unload event listener on the new window (from onStateRestored).
+// Unload is fired after the window is closed, so sessionstore has already
+// updated _closedWindows (which is important). We'll open a new window here
+// which should actually trigger the bug.
+function onWindowUnloaded() {
+ info("test #" + testNum + ": onWindowClosed");
+ ok(gotLastWindowClosedTopic, "test #" + testNum + ": browser-lastwindow-close-granted was notified prior");
+
+ let previousClosedWindowData = ss.getClosedWindowData();
+
+ // Now we want to open a new window
+ let newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "about:mozilla");
+ newWin.addEventListener("load", function(aEvent) {
+ newWin.removeEventListener("load", arguments.callee, false);
+
+ newWin.gBrowser.selectedBrowser.addEventListener("load", function () {
+ newWin.gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ // Good enough for checking the state
+ afterTestCallback(previousClosedWindowData, ss.getClosedWindowData());
+ afterTestCleanup(newWin);
+ }, true);
+
+ }, false);
+}
+
+function afterTestCleanup(aNewWin) {
+ executeSoon(function() {
+ BrowserTestUtils.closeWindow(aNewWin).then(() => {
+ document.documentElement.setAttribute("windowtype", originalWindowType);
+ runNextTestOrFinish();
+ });
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_590268.js b/browser/components/sessionstore/test/browser_590268.js
new file mode 100644
index 000000000..2b0c2f32d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_590268.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/. */
+
+const NUM_TABS = 12;
+
+var stateBackup = ss.getBrowserState();
+
+function test() {
+ /** Test for Bug 590268 - Provide access to sessionstore tab data sooner **/
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+
+ let startedTest = false;
+
+ // wasLoaded will be used to keep track of tabs that have already had SSTabRestoring
+ // fired for them.
+ let wasLoaded = { };
+ let restoringTabsCount = 0;
+ let restoredTabsCount = 0;
+ let uniq2 = { };
+ let uniq2Count = 0;
+ let state = { windows: [{ tabs: [] }] };
+ // We're going to put a bunch of tabs into this state
+ for (let i = 0; i < NUM_TABS; i++) {
+ let uniq = r();
+ let tabData = {
+ entries: [{ url: "http://example.com/#" + i }],
+ extData: { "uniq": uniq, "baz": "qux" }
+ };
+ state.windows[0].tabs.push(tabData);
+ wasLoaded[uniq] = false;
+ }
+
+
+ function onSSTabRestoring(aEvent) {
+ restoringTabsCount++;
+ let uniq = ss.getTabValue(aEvent.originalTarget, "uniq");
+ wasLoaded[uniq] = true;
+
+ is(ss.getTabValue(aEvent.originalTarget, "foo"), "",
+ "There is no value for 'foo'");
+
+ // On the first SSTabRestoring we're going to run the the real test.
+ // We'll keep this listener around so we can keep marking tabs as restored.
+ if (restoringTabsCount == 1)
+ onFirstSSTabRestoring();
+ else if (restoringTabsCount == NUM_TABS)
+ onLastSSTabRestoring();
+ }
+
+ function onSSTabRestored(aEvent) {
+ if (++restoredTabsCount < NUM_TABS)
+ return;
+ cleanup();
+ }
+
+ function onTabOpen(aEvent) {
+ // To test bug 614708, we'll just set a value on the tab here. This value
+ // would previously cause us to not recognize the values in extData until
+ // much later. So testing "uniq" failed.
+ ss.setTabValue(aEvent.originalTarget, "foo", "bar");
+ }
+
+ // This does the actual testing. SSTabRestoring should be firing on tabs from
+ // left to right, so we're going to start with the rightmost tab.
+ function onFirstSSTabRestoring() {
+ info("onFirstSSTabRestoring...");
+ for (let i = gBrowser.tabs.length - 1; i >= 0; i--) {
+ let tab = gBrowser.tabs[i];
+ let actualUniq = ss.getTabValue(tab, "uniq");
+ let expectedUniq = state.windows[0].tabs[i].extData["uniq"];
+
+ if (wasLoaded[actualUniq]) {
+ info("tab " + i + ": already restored");
+ continue;
+ }
+ is(actualUniq, expectedUniq, "tab " + i + ": extData was correct");
+
+ // Now we're going to set a piece of data back on the tab so it can be read
+ // to test setting a value "early".
+ uniq2[actualUniq] = r();
+ ss.setTabValue(tab, "uniq2", uniq2[actualUniq]);
+
+ // Delete the value we have for "baz". This tests that deleteTabValue
+ // will delete "early access" values (c.f. bug 617175). If this doesn't throw
+ // then the test is successful.
+ try {
+ ss.deleteTabValue(tab, "baz");
+ }
+ catch (e) {
+ ok(false, "no error calling deleteTabValue - " + e);
+ }
+
+ // This will be used in the final comparison to make sure we checked the
+ // same number as we set.
+ uniq2Count++;
+ }
+ }
+
+ function onLastSSTabRestoring() {
+ let checked = 0;
+ for (let i = 0; i < gBrowser.tabs.length; i++) {
+ let tab = gBrowser.tabs[i];
+ let uniq = ss.getTabValue(tab, "uniq");
+
+ // Look to see if we set a uniq2 value for this uniq value
+ if (uniq in uniq2) {
+ is(ss.getTabValue(tab, "uniq2"), uniq2[uniq], "tab " + i + " has correct uniq2 value");
+ checked++;
+ }
+ }
+ ok(uniq2Count > 0, "at least 1 tab properly checked 'early access'");
+ is(checked, uniq2Count, "checked the same number of uniq2 as we set");
+ }
+
+ function cleanup() {
+ // remove the event listener and clean up before finishing
+ gBrowser.tabContainer.removeEventListener("SSTabRestoring", onSSTabRestoring, false);
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true);
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, false);
+ // Put this in an executeSoon because we still haven't called restoreNextTab
+ // in sessionstore for the last tab (we'll call it after this). We end up
+ // trying to restore the tab (since we then add a closed tab to the array).
+ executeSoon(function() {
+ ss.setBrowserState(stateBackup);
+ executeSoon(finish);
+ });
+ }
+
+ // Add the event listeners
+ gBrowser.tabContainer.addEventListener("SSTabRestoring", onSSTabRestoring, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true);
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, false);
+ // Restore state
+ ss.setBrowserState(JSON.stringify(state));
+}
diff --git a/browser/components/sessionstore/test/browser_590563.js b/browser/components/sessionstore/test/browser_590563.js
new file mode 100644
index 000000000..5d1d8f866
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_590563.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ let sessionData = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:mozilla" }], hidden: true },
+ { entries: [{ url: "about:blank" }], hidden: false }
+ ]
+ }]
+ };
+ let url = "about:sessionrestore";
+ let formdata = {id: {sessionData}, url};
+ let state = { windows: [{ tabs: [{ entries: [{url}], formdata }] }] };
+
+ waitForExplicitFinish();
+
+ newWindowWithState(state, function (win) {
+ registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
+
+ is(gBrowser.tabs.length, 1, "The total number of tabs should be 1");
+ is(gBrowser.visibleTabs.length, 1, "The total number of visible tabs should be 1");
+
+ executeSoon(function () {
+ waitForFocus(function () {
+ middleClickTest(win);
+ finish();
+ }, win);
+ });
+ });
+}
+
+function middleClickTest(win) {
+ let browser = win.gBrowser.selectedBrowser;
+ let tree = browser.contentDocument.getElementById("tabList");
+ is(tree.view.rowCount, 3, "There should be three items");
+
+ // click on the first tab item
+ var rect = tree.treeBoxObject.getCoordsForCellItem(1, tree.columns[1], "text");
+ EventUtils.synthesizeMouse(tree.body, rect.x, rect.y, { button: 1 },
+ browser.contentWindow);
+ // click on the second tab item
+ rect = tree.treeBoxObject.getCoordsForCellItem(2, tree.columns[1], "text");
+ EventUtils.synthesizeMouse(tree.body, rect.x, rect.y, { button: 1 },
+ browser.contentWindow);
+
+ is(win.gBrowser.tabs.length, 3,
+ "The total number of tabs should be 3 after restoring 2 tabs by middle click.");
+ is(win.gBrowser.visibleTabs.length, 3,
+ "The total number of visible tabs should be 3 after restoring 2 tabs by middle click");
+}
+
+function newWindowWithState(state, callback) {
+ let opts = "chrome,all,dialog=no,height=800,width=800";
+ let win = window.openDialog(getBrowserURL(), "_blank", opts);
+
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+
+ let tab = win.gBrowser.selectedTab;
+
+ // The form data will be restored before SSTabRestored, so we want to listen
+ // for that on the currently selected tab (it will be reused)
+ tab.addEventListener("SSTabRestored", function onRestored() {
+ tab.removeEventListener("SSTabRestored", onRestored, true);
+ callback(win);
+ }, true);
+
+ executeSoon(function () {
+ ss.setWindowState(win, JSON.stringify(state), true);
+ });
+ }, false);
+}
diff --git a/browser/components/sessionstore/test/browser_595601-restore_hidden.js b/browser/components/sessionstore/test/browser_595601-restore_hidden.js
new file mode 100644
index 000000000..4c2b2d24a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_595601-restore_hidden.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var state = {windows:[{tabs:[
+ {entries:[{url:"http://example.com#1"}]},
+ {entries:[{url:"http://example.com#2"}]},
+ {entries:[{url:"http://example.com#3"}]},
+ {entries:[{url:"http://example.com#4"}]},
+ {entries:[{url:"http://example.com#5"}], hidden: true},
+ {entries:[{url:"http://example.com#6"}], hidden: true},
+ {entries:[{url:"http://example.com#7"}], hidden: true},
+ {entries:[{url:"http://example.com#8"}], hidden: true}
+]}]};
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
+ });
+
+ // First stage: restoreHiddenTabs = true
+ // Second stage: restoreHiddenTabs = false
+ test_loadTabs(true, function () {
+ test_loadTabs(false, finish);
+ });
+}
+
+function test_loadTabs(restoreHiddenTabs, callback) {
+ Services.prefs.setBoolPref("browser.sessionstore.restore_hidden_tabs", restoreHiddenTabs);
+
+ let expectedTabs = restoreHiddenTabs ? 8 : 4;
+ let firstProgress = true;
+
+ newWindowWithState(state, function (win, needsRestore, isRestoring) {
+ if (firstProgress) {
+ firstProgress = false;
+ is(isRestoring, 3, "restoring 3 tabs concurrently");
+ } else {
+ ok(isRestoring < 4, "restoring max. 3 tabs concurrently");
+ }
+
+ // We're explicity checking for (isRestoring == 1) here because the test
+ // progress listener is called before the session store one. So when we're
+ // called with one tab left to restore we know that the last tab has
+ // finished restoring and will soon be handled by the SS listener.
+ let tabsNeedingRestore = win.gBrowser.tabs.length - needsRestore;
+ if (isRestoring == 1 && tabsNeedingRestore == expectedTabs) {
+ is(win.gBrowser.visibleTabs.length, 4, "only 4 visible tabs");
+
+ TabsProgressListener.uninit();
+ executeSoon(callback);
+ }
+ });
+}
+
+var TabsProgressListener = {
+ init: function (win) {
+ this.window = win;
+ Services.obs.addObserver(this, "sessionstore-debug-tab-restored", false);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "sessionstore-debug-tab-restored");
+
+ delete this.window;
+ delete this.callback;
+ },
+
+ setCallback: function (callback) {
+ this.callback = callback;
+ },
+
+ observe: function (browser) {
+ TabsProgressListener.onRestored(browser);
+ },
+
+ onRestored: function (browser) {
+ if (this.callback && browser.__SS_restoreState == TAB_STATE_RESTORING)
+ this.callback.apply(null, [this.window].concat(this.countTabs()));
+ },
+
+ countTabs: function () {
+ let needsRestore = 0, isRestoring = 0;
+
+ for (let i = 0; i < this.window.gBrowser.tabs.length; i++) {
+ let browser = this.window.gBrowser.tabs[i].linkedBrowser;
+ if (browser.__SS_restoreState == TAB_STATE_RESTORING)
+ isRestoring++;
+ else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
+ needsRestore++;
+ }
+
+ return [needsRestore, isRestoring];
+ }
+}
+
+// ----------
+function newWindowWithState(state, callback) {
+ let opts = "chrome,all,dialog=no,height=800,width=800";
+ let win = window.openDialog(getBrowserURL(), "_blank", opts);
+
+ registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
+
+ whenWindowLoaded(win, function onWindowLoaded(aWin) {
+ TabsProgressListener.init(aWin);
+ TabsProgressListener.setCallback(callback);
+
+ ss.setWindowState(aWin, JSON.stringify(state), true);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_597071.js b/browser/components/sessionstore/test/browser_597071.js
new file mode 100644
index 000000000..f8ddaaf54
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_597071.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 597071 - Closed windows should only be resurrected when there is a single
+ * popup window
+ */
+add_task(function test_close_last_nonpopup_window() {
+ // Purge the list of closed windows.
+ forgetClosedWindows();
+
+ let oldState = ss.getWindowState(window);
+
+ let popupState = {windows: [
+ {tabs: [{entries: []}], isPopup: true, hidden: "toolbar"}
+ ]};
+
+ // Set this window to be a popup.
+ ss.setWindowState(window, JSON.stringify(popupState), true);
+
+ // Open a new window with a tab.
+ let win = yield BrowserTestUtils.openNewBrowserWindow({private: false});
+ let tab = win.gBrowser.addTab("http://example.com/");
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Make sure sessionstore sees this window.
+ let state = JSON.parse(ss.getBrowserState());
+ is(state.windows.length, 2, "sessionstore knows about this window");
+
+ // Closed the window and check the closed window count.
+ yield BrowserTestUtils.closeWindow(win);
+ is(ss.getClosedWindowCount(), 1, "correct closed window count");
+
+ // Cleanup.
+ ss.setWindowState(window, oldState, true);
+});
diff --git a/browser/components/sessionstore/test/browser_599909.js b/browser/components/sessionstore/test/browser_599909.js
new file mode 100644
index 000000000..1d2c411fe
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_599909.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/. */
+
+var stateBackup = ss.getBrowserState();
+
+function cleanup() {
+ // Reset the pref
+ try {
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+ } catch (e) {}
+ ss.setBrowserState(stateBackup);
+ executeSoon(finish);
+}
+
+function test() {
+ /** Bug 599909 - to-be-reloaded tabs don't show up in switch-to-tab **/
+ waitForExplicitFinish();
+
+ // Set the pref to true so we know exactly how many tabs should be restoring at
+ // any given time. This guarantees that a finishing load won't start another.
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+ let state = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org/#1" }] },
+ { entries: [{ url: "http://example.org/#2" }] },
+ { entries: [{ url: "http://example.org/#3" }] },
+ { entries: [{ url: "http://example.org/#4" }] }
+ ], selected: 1 }] };
+
+ let tabsForEnsure = {};
+ state.windows[0].tabs.forEach(function(tab) {
+ tabsForEnsure[tab.entries[0].url] = 1;
+ });
+
+ let tabsRestoring = 0;
+ let tabsRestored = 0;
+
+ function handleEvent(aEvent) {
+ if (aEvent.type == "SSTabRestoring")
+ tabsRestoring++;
+ else
+ tabsRestored++;
+
+ if (tabsRestoring < state.windows[0].tabs.length ||
+ tabsRestored < 1)
+ return;
+
+ gBrowser.tabContainer.removeEventListener("SSTabRestoring", handleEvent, true);
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", handleEvent, true);
+ executeSoon(function() {
+ checkAutocompleteResults(tabsForEnsure, cleanup);
+ });
+ }
+
+ // currentURI is set before SSTabRestoring is fired, so we can sucessfully check
+ // after that has fired for all tabs. Since 1 tab will be restored though, we
+ // also need to wait for 1 SSTabRestored since currentURI will be set, unset, then set.
+ gBrowser.tabContainer.addEventListener("SSTabRestoring", handleEvent, true);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", handleEvent, true);
+ ss.setBrowserState(JSON.stringify(state));
+}
+
+// The following was taken from browser/base/content/test/general/browser_tabMatchesInAwesomebar.js
+// so that we could do the same sort of checking.
+var gController = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+
+function checkAutocompleteResults(aExpected, aCallback) {
+ gController.input = {
+ timeout: 10,
+ textValue: "",
+ searches: ["unifiedcomplete"],
+ searchParam: "enable-actions",
+ popupOpen: false,
+ minResultsForPopup: 0,
+ invalidate: function() {},
+ disableAutoComplete: false,
+ completeDefaultIndex: false,
+ get popup() { return this; },
+ onSearchBegin: function() {},
+ onSearchComplete: function ()
+ {
+ info("Found " + gController.matchCount + " matches.");
+ // Check to see the expected uris and titles match up (in any order)
+ for (let i = 0; i < gController.matchCount; i++) {
+ if (gController.getStyleAt(i).includes("heuristic")) {
+ info("Skip heuristic match");
+ continue;
+ }
+ let action = gURLBar.popup.input._parseActionUrl(gController.getValueAt(i));
+ let uri = action.params.url;
+
+ info("Search for '" + uri + "' in open tabs.");
+ ok(uri in aExpected, "Registered open page found in autocomplete.");
+ // Remove the found entry from expected results.
+ delete aExpected[uri];
+ }
+
+ // Make sure there is no reported open page that is not open.
+ for (let entry in aExpected) {
+ ok(false, "'" + entry + "' not found in autocomplete.");
+ }
+
+ executeSoon(aCallback);
+ },
+ setSelectedIndex: function() {},
+ get searchCount() { return this.searches.length; },
+ getSearchAt: function(aIndex) {
+ return this.searches[aIndex];
+ },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteInput,
+ Ci.nsIAutoCompletePopup,
+ ])
+ };
+
+ info("Searching open pages.");
+ gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage"));
+}
diff --git a/browser/components/sessionstore/test/browser_600545.js b/browser/components/sessionstore/test/browser_600545.js
new file mode 100644
index 000000000..6852357c2
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_600545.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/. */
+
+requestLongerTimeout(2);
+
+var stateBackup = JSON.parse(ss.getBrowserState());
+
+function test() {
+ /** Test for Bug 600545 **/
+ waitForExplicitFinish();
+ testBug600545();
+}
+
+function testBug600545() {
+ // Set the pref to false to cause non-app tabs to be stripped out on a save
+ Services.prefs.setBoolPref("browser.sessionstore.resume_from_crash", false);
+ Services.prefs.setIntPref("browser.sessionstore.interval", 2000);
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.sessionstore.resume_from_crash");
+ Services.prefs.clearUserPref("browser.sessionstore.interval");
+ });
+
+ // This tests the following use case: When multiple windows are open
+ // and browser.sessionstore.resume_from_crash preference is false,
+ // tab session data for non-active window is stripped for non-pinned
+ // tabs. This occurs after "sessionstore-state-write-complete"
+ // fires which will only fire in this case if there is at least one
+ // pinned tab.
+ let state = { windows: [
+ {
+ tabs: [
+ { entries: [{ url: "http://example.org#0" }], pinned:true },
+ { entries: [{ url: "http://example.com#1" }] },
+ { entries: [{ url: "http://example.com#2" }] },
+ ],
+ selected: 2
+ },
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com#3" }] },
+ { entries: [{ url: "http://example.com#4" }] },
+ { entries: [{ url: "http://example.com#5" }] },
+ { entries: [{ url: "http://example.com#6" }] }
+ ],
+ selected: 3
+ }
+ ] };
+
+ waitForBrowserState(state, function() {
+ // Need to wait for SessionStore's saveState function to be called
+ // so that non-pinned tabs will be stripped from non-active window
+ waitForSaveState(function () {
+ let expectedNumberOfTabs = getStateTabCount(state);
+ let retrievedState = JSON.parse(ss.getBrowserState());
+ let actualNumberOfTabs = getStateTabCount(retrievedState);
+
+ is(actualNumberOfTabs, expectedNumberOfTabs,
+ "Number of tabs in retreived session data, matches number of tabs set.");
+
+ done();
+ });
+ });
+}
+
+function done() {
+ // Enumerate windows and close everything but our primary window. We can't
+ // use waitForFocus() because apparently it's buggy. See bug 599253.
+ let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ let closeWinPromises = [];
+ while (windowsEnum.hasMoreElements()) {
+ let currentWindow = windowsEnum.getNext();
+ if (currentWindow != window)
+ closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow));
+ }
+
+ Promise.all(closeWinPromises).then(() => {
+ waitForBrowserState(stateBackup, finish);
+ });
+}
+
+// Count up the number of tabs in the state data
+function getStateTabCount(aState) {
+ let tabCount = 0;
+ for (let i in aState.windows)
+ tabCount += aState.windows[i].tabs.length;
+ return tabCount;
+}
diff --git a/browser/components/sessionstore/test/browser_601955.js b/browser/components/sessionstore/test/browser_601955.js
new file mode 100644
index 000000000..797d5d7cc
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_601955.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/. */
+
+// This tests that pinning/unpinning a tab, on its own, eventually triggers a
+// session store.
+
+function test() {
+ waitForExplicitFinish();
+ // We speed up the interval between session saves to ensure that the test
+ // runs quickly.
+ Services.prefs.setIntPref("browser.sessionstore.interval", 2000);
+
+ // Loading a tab causes a save state and this is meant to catch that event.
+ waitForSaveState(testBug601955_1);
+
+ // Assumption: Only one window is open and it has one tab open.
+ gBrowser.addTab("about:mozilla");
+}
+
+function testBug601955_1() {
+ // Because pinned tabs are at the front of |gBrowser.tabs|, pinning tabs
+ // re-arranges the |tabs| array.
+ ok(!gBrowser.tabs[0].pinned, "first tab should not be pinned yet");
+ ok(!gBrowser.tabs[1].pinned, "second tab should not be pinned yet");
+
+ waitForSaveState(testBug601955_2);
+ gBrowser.pinTab(gBrowser.tabs[0]);
+}
+
+function testBug601955_2() {
+ let state = JSON.parse(ss.getBrowserState());
+ ok(state.windows[0].tabs[0].pinned, "first tab should be pinned by now");
+ ok(!state.windows[0].tabs[1].pinned, "second tab should still not be pinned");
+
+ waitForSaveState(testBug601955_3);
+ gBrowser.unpinTab(window.gBrowser.tabs[0]);
+}
+
+function testBug601955_3() {
+ let state = JSON.parse(ss.getBrowserState());
+ ok(!state.windows[0].tabs[0].pinned, "first tab should not be pinned");
+ ok(!state.windows[0].tabs[1].pinned, "second tab should not be pinned");
+
+ done();
+}
+
+function done() {
+ gBrowser.removeTab(window.gBrowser.tabs[1]);
+
+ Services.prefs.clearUserPref("browser.sessionstore.interval");
+
+ executeSoon(finish);
+}
diff --git a/browser/components/sessionstore/test/browser_607016.js b/browser/components/sessionstore/test/browser_607016.js
new file mode 100644
index 000000000..ed4b03b9c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_607016.js
@@ -0,0 +1,98 @@
+"use strict";
+
+var stateBackup = ss.getBrowserState();
+
+add_task(function* () {
+ /** Bug 607016 - If a tab is never restored, attributes (eg. hidden) aren't updated correctly **/
+ ignoreAllUncaughtExceptions();
+
+ // Set the pref to true so we know exactly how many tabs should be restoring at
+ // any given time. This guarantees that a finishing load won't start another.
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+ let state = { windows: [{ tabs: [
+ { entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } }, // overwriting
+ { entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } }, // hiding
+ { entries: [{ url: "http://example.org#4" }], extData: { "uniq": r() } }, // adding
+ { entries: [{ url: "http://example.org#5" }], extData: { "uniq": r() } }, // deleting
+ { entries: [{ url: "http://example.org#6" }] } // creating
+ ], selected: 1 }] };
+
+ function* progressCallback() {
+ let curState = JSON.parse(ss.getBrowserState());
+ for (let i = 0; i < curState.windows[0].tabs.length; i++) {
+ let tabState = state.windows[0].tabs[i];
+ let tabCurState = curState.windows[0].tabs[i];
+ if (tabState.extData) {
+ is(tabCurState.extData["uniq"], tabState.extData["uniq"],
+ "sanity check that tab has correct extData");
+ }
+ else {
+ // We aren't expecting there to be any data on extData, but panorama
+ // may be setting something, so we need to make sure that if we do have
+ // data, we just don't have anything for "uniq".
+ ok(!("extData" in tabCurState) || !("uniq" in tabCurState.extData),
+ "sanity check that tab doesn't have extData or extData doesn't have 'uniq'");
+ }
+ }
+
+ // Now we'll set a new unique value on 1 of the tabs
+ let newUniq = r();
+ ss.setTabValue(gBrowser.tabs[1], "uniq", newUniq);
+ let tabState = JSON.parse(ss.getTabState(gBrowser.tabs[1]));
+ is(tabState.extData.uniq, newUniq,
+ "(overwriting) new data is stored in extData");
+
+ // hide the next tab before closing it
+ gBrowser.hideTab(gBrowser.tabs[2]);
+ tabState = JSON.parse(ss.getTabState(gBrowser.tabs[2]));
+ ok(tabState.hidden, "(hiding) tab data has hidden == true");
+
+ // set data that's not in a conflicting key
+ let stillUniq = r();
+ ss.setTabValue(gBrowser.tabs[3], "stillUniq", stillUniq);
+ tabState = JSON.parse(ss.getTabState(gBrowser.tabs[3]));
+ is(tabState.extData.stillUniq, stillUniq,
+ "(adding) new data is stored in extData");
+
+ // remove the uniq value and make sure it's not there in the closed data
+ ss.deleteTabValue(gBrowser.tabs[4], "uniq");
+ tabState = JSON.parse(ss.getTabState(gBrowser.tabs[4]));
+ // Since Panorama might have put data in, first check if there is extData.
+ // If there is explicitly check that "uniq" isn't in it. Otherwise, we're ok
+ if ("extData" in tabState) {
+ ok(!("uniq" in tabState.extData),
+ "(deleting) uniq not in existing extData");
+ }
+ else {
+ ok(true, "(deleting) no data is stored in extData");
+ }
+
+ // set unique data on the tab that never had any set, make sure that's saved
+ let newUniq2 = r();
+ ss.setTabValue(gBrowser.tabs[5], "uniq", newUniq2);
+ tabState = JSON.parse(ss.getTabState(gBrowser.tabs[5]));
+ is(tabState.extData.uniq, newUniq2,
+ "(creating) new data is stored in extData where there was none");
+
+ while (gBrowser.tabs.length > 1) {
+ yield promiseRemoveTab(gBrowser.tabs[1]);
+ }
+ }
+
+ // Set the test state.
+ ss.setBrowserState(JSON.stringify(state));
+
+ // Wait until the selected tab is restored and all others are pending.
+ yield Promise.all(Array.map(gBrowser.tabs, tab => {
+ return (tab == gBrowser.selectedTab) ?
+ promiseTabRestored(tab) : promiseTabRestoring(tab)
+ }));
+
+ // Kick off the actual tests.
+ yield progressCallback();
+
+ // Cleanup.
+ yield promiseBrowserState(stateBackup);
+});
diff --git a/browser/components/sessionstore/test/browser_615394-SSWindowState_events.js b/browser/components/sessionstore/test/browser_615394-SSWindowState_events.js
new file mode 100644
index 000000000..0b6b8faa6
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_615394-SSWindowState_events.js
@@ -0,0 +1,361 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 stateBackup = JSON.parse(ss.getBrowserState());
+const testState = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:blank" }] },
+ { entries: [{ url: "about:rights" }] }
+ ]
+ }]
+};
+const lameMultiWindowState = { windows: [
+ {
+ tabs: [
+ { entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.org#4" }], extData: { "uniq": r() } }
+ ],
+ selected: 1
+ },
+ {
+ tabs: [
+ { entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } },
+ { entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } },
+ ],
+ selected: 3
+ }
+ ] };
+
+
+function getOuterWindowID(aWindow) {
+ return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+}
+
+function test() {
+ /** Test for Bug 615394 - Session Restore should notify when it is beginning and ending a restore **/
+ waitForExplicitFinish();
+ // Preemptively extend the timeout to prevent [orange]
+ requestLongerTimeout(4);
+ runNextTest();
+}
+
+
+var tests = [
+ test_setTabState,
+ test_duplicateTab,
+ test_undoCloseTab,
+ test_setWindowState,
+ test_setBrowserState,
+ test_undoCloseWindow
+];
+function runNextTest() {
+ // set an empty state & run the next test, or finish
+ if (tests.length) {
+ // Enumerate windows and close everything but our primary window. We can't
+ // use waitForFocus() because apparently it's buggy. See bug 599253.
+ var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ let closeWinPromises = [];
+ while (windowsEnum.hasMoreElements()) {
+ var currentWindow = windowsEnum.getNext();
+ if (currentWindow != window) {
+ closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow));
+ }
+ }
+
+ Promise.all(closeWinPromises).then(() => {
+ let currentTest = tests.shift();
+ info("prepping for " + currentTest.name);
+ waitForBrowserState(testState, currentTest);
+ });
+ }
+ else {
+ waitForBrowserState(stateBackup, finish);
+ }
+}
+
+/** ACTUAL TESTS **/
+
+function test_setTabState() {
+ let tab = gBrowser.tabs[1];
+ let newTabState = JSON.stringify({ entries: [{ url: "http://example.org" }], extData: { foo: "bar" } });
+ let busyEventCount = 0;
+ let readyEventCount = 0;
+
+ function onSSWindowStateBusy(aEvent) {
+ busyEventCount++;
+ }
+
+ function onSSWindowStateReady(aEvent) {
+ readyEventCount++;
+ is(ss.getTabValue(tab, "foo"), "bar");
+ ss.setTabValue(tab, "baz", "qux");
+ }
+
+ function onSSTabRestored(aEvent) {
+ is(busyEventCount, 1);
+ is(readyEventCount, 1);
+ is(ss.getTabValue(tab, "baz"), "qux");
+ is(tab.linkedBrowser.currentURI.spec, "http://example.org/");
+
+ window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, false);
+
+ runNextTest();
+ }
+
+ window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, false);
+ ss.setTabState(tab, newTabState);
+}
+
+
+function test_duplicateTab() {
+ let tab = gBrowser.tabs[1];
+ let busyEventCount = 0;
+ let readyEventCount = 0;
+ let newTab;
+
+ // We'll look to make sure this value is on the duplicated tab
+ ss.setTabValue(tab, "foo", "bar");
+
+ function onSSWindowStateBusy(aEvent) {
+ busyEventCount++;
+ }
+
+ function onSSWindowStateReady(aEvent) {
+ newTab = gBrowser.tabs[2];
+ readyEventCount++;
+ is(ss.getTabValue(newTab, "foo"), "bar");
+ ss.setTabValue(newTab, "baz", "qux");
+ }
+
+ function onSSTabRestored(aEvent) {
+ is(busyEventCount, 1);
+ is(readyEventCount, 1);
+ is(ss.getTabValue(newTab, "baz"), "qux");
+ is(newTab.linkedBrowser.currentURI.spec, "about:rights");
+
+ window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, false);
+
+ runNextTest();
+ }
+
+ window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, false);
+
+ newTab = ss.duplicateTab(window, tab);
+}
+
+
+function test_undoCloseTab() {
+ let tab = gBrowser.tabs[1],
+ busyEventCount = 0,
+ readyEventCount = 0,
+ reopenedTab;
+
+ ss.setTabValue(tab, "foo", "bar");
+
+ function onSSWindowStateBusy(aEvent) {
+ busyEventCount++;
+ }
+
+ function onSSWindowStateReady(aEvent) {
+ reopenedTab = gBrowser.tabs[1];
+ readyEventCount++;
+ is(ss.getTabValue(reopenedTab, "foo"), "bar");
+ ss.setTabValue(reopenedTab, "baz", "qux");
+ }
+
+ function onSSTabRestored(aEvent) {
+ is(busyEventCount, 1);
+ is(readyEventCount, 1);
+ is(ss.getTabValue(reopenedTab, "baz"), "qux");
+ is(reopenedTab.linkedBrowser.currentURI.spec, "about:rights");
+
+ window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, false);
+
+ runNextTest();
+ }
+
+ window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, false);
+
+ gBrowser.removeTab(tab);
+ reopenedTab = ss.undoCloseTab(window, 0);
+}
+
+
+function test_setWindowState() {
+ let testState = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:mozilla" }], extData: { "foo": "bar" } },
+ { entries: [{ url: "http://example.org" }], extData: { "baz": "qux" } }
+ ]
+ }]
+ };
+
+ let busyEventCount = 0,
+ readyEventCount = 0,
+ tabRestoredCount = 0;
+
+ function onSSWindowStateBusy(aEvent) {
+ busyEventCount++;
+ }
+
+ function onSSWindowStateReady(aEvent) {
+ readyEventCount++;
+ is(ss.getTabValue(gBrowser.tabs[0], "foo"), "bar");
+ is(ss.getTabValue(gBrowser.tabs[1], "baz"), "qux");
+ }
+
+ function onSSTabRestored(aEvent) {
+ if (++tabRestoredCount < 2)
+ return;
+
+ is(busyEventCount, 1);
+ is(readyEventCount, 1);
+ is(gBrowser.tabs[0].linkedBrowser.currentURI.spec, "about:mozilla");
+ is(gBrowser.tabs[1].linkedBrowser.currentURI.spec, "http://example.org/");
+
+ window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, false);
+
+ runNextTest();
+ }
+
+ window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, false);
+
+ ss.setWindowState(window, JSON.stringify(testState), true);
+}
+
+
+function test_setBrowserState() {
+ // We'll track events per window so we are sure that they are each happening once
+ // pre window.
+ let windowEvents = {};
+ windowEvents[getOuterWindowID(window)] = { busyEventCount: 0, readyEventCount: 0 };
+
+ // waitForBrowserState does it's own observing for windows, but doesn't attach
+ // the listeners we want here, so do it ourselves.
+ let newWindow;
+ function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ newWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ newWindow.addEventListener("load", function() {
+ newWindow.removeEventListener("load", arguments.callee, false);
+
+ Services.ww.unregisterNotification(windowObserver);
+
+ windowEvents[getOuterWindowID(newWindow)] = { busyEventCount: 0, readyEventCount: 0 };
+
+ newWindow.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ newWindow.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ }, false);
+ }
+ }
+
+ function onSSWindowStateBusy(aEvent) {
+ windowEvents[getOuterWindowID(aEvent.originalTarget)].busyEventCount++;
+ }
+
+ function onSSWindowStateReady(aEvent) {
+ windowEvents[getOuterWindowID(aEvent.originalTarget)].readyEventCount++;
+ }
+
+ window.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ Services.ww.registerNotification(windowObserver);
+
+ waitForBrowserState(lameMultiWindowState, function() {
+ let checkedWindows = 0;
+ for (let id of Object.keys(windowEvents)) {
+ let winEvents = windowEvents[id];
+ is(winEvents.busyEventCount, 1,
+ "[test_setBrowserState] window" + id + " busy event count correct");
+ is(winEvents.readyEventCount, 1,
+ "[test_setBrowserState] window" + id + " ready event count correct");
+ checkedWindows++;
+ }
+ is(checkedWindows, 2,
+ "[test_setBrowserState] checked 2 windows");
+ window.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ window.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ newWindow.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ newWindow.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ runNextTest();
+ });
+}
+
+
+function test_undoCloseWindow() {
+ let newWindow, reopenedWindow;
+
+ function firstWindowObserver(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ newWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ Services.ww.unregisterNotification(firstWindowObserver);
+ }
+ }
+ Services.ww.registerNotification(firstWindowObserver);
+
+ waitForBrowserState(lameMultiWindowState, function() {
+ // Close the window which isn't window
+ BrowserTestUtils.closeWindow(newWindow).then(() => {
+ // Now give it time to close
+ reopenedWindow = ss.undoCloseWindow(0);
+ reopenedWindow.addEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ reopenedWindow.addEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+
+ reopenedWindow.addEventListener("load", function() {
+ reopenedWindow.removeEventListener("load", arguments.callee, false);
+
+ reopenedWindow.gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, false);
+ }, false);
+ });
+ });
+
+ let busyEventCount = 0,
+ readyEventCount = 0,
+ tabRestoredCount = 0;
+ // These will listen to the reopened closed window...
+ function onSSWindowStateBusy(aEvent) {
+ busyEventCount++;
+ }
+
+ function onSSWindowStateReady(aEvent) {
+ readyEventCount++;
+ }
+
+ function onSSTabRestored(aEvent) {
+ if (++tabRestoredCount < 4)
+ return;
+
+ is(busyEventCount, 1);
+ is(readyEventCount, 1);
+
+ reopenedWindow.removeEventListener("SSWindowStateBusy", onSSWindowStateBusy, false);
+ reopenedWindow.removeEventListener("SSWindowStateReady", onSSWindowStateReady, false);
+ reopenedWindow.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, false);
+
+ BrowserTestUtils.closeWindow(reopenedWindow).then(runNextTest);
+ }
+}
diff --git a/browser/components/sessionstore/test/browser_618151.js b/browser/components/sessionstore/test/browser_618151.js
new file mode 100644
index 000000000..bdc268e6c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_618151.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/. */
+
+const stateBackup = ss.getBrowserState();
+const testState = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:blank" }] },
+ { entries: [{ url: "about:mozilla" }] }
+ ]
+ }]
+};
+
+
+function test() {
+ /** Test for Bug 618151 - Overwriting state can lead to unrestored tabs **/
+ waitForExplicitFinish();
+ runNextTest();
+}
+
+// Just a subset of tests from bug 615394 that causes a timeout.
+var tests = [test_setup, test_hang];
+function runNextTest() {
+ // set an empty state & run the next test, or finish
+ if (tests.length) {
+ // Enumerate windows and close everything but our primary window. We can't
+ // use waitForFocus() because apparently it's buggy. See bug 599253.
+ var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ let closeWinPromises = [];
+ while (windowsEnum.hasMoreElements()) {
+ var currentWindow = windowsEnum.getNext();
+ if (currentWindow != window) {
+ closeWinPromises.push(BrowserTestUtils.closeWindow(currentWindow));
+ }
+ }
+
+ Promise.all(closeWinPromises).then(() => {
+ let currentTest = tests.shift();
+ info("running " + currentTest.name);
+ waitForBrowserState(testState, currentTest);
+ });
+ }
+ else {
+ ss.setBrowserState(stateBackup);
+ executeSoon(finish);
+ }
+}
+
+function test_setup() {
+ function onSSTabRestored(aEvent) {
+ gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, false);
+ runNextTest();
+ }
+
+ gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, false);
+ ss.setTabState(gBrowser.tabs[1], JSON.stringify({
+ entries: [{ url: "http://example.org" }],
+ extData: { foo: "bar" } }));
+}
+
+function test_hang() {
+ ok(true, "test didn't time out");
+ runNextTest();
+}
diff --git a/browser/components/sessionstore/test/browser_623779.js b/browser/components/sessionstore/test/browser_623779.js
new file mode 100644
index 000000000..267bccb2d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_623779.js
@@ -0,0 +1,13 @@
+"use strict";
+
+add_task(function* () {
+ gBrowser.pinTab(gBrowser.selectedTab);
+
+ let newTab = gBrowser.duplicateTab(gBrowser.selectedTab);
+ yield promiseTabRestored(newTab);
+
+ ok(!newTab.pinned, "duplicating a pinned tab creates unpinned tab");
+ yield promiseRemoveTab(newTab);
+
+ gBrowser.unpinTab(gBrowser.selectedTab);
+});
diff --git a/browser/components/sessionstore/test/browser_624727.js b/browser/components/sessionstore/test/browser_624727.js
new file mode 100644
index 000000000..85d6ff042
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_624727.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var TEST_STATE = { windows: [{ tabs: [{ url: "about:blank" }] }] };
+
+add_task(function* () {
+ function assertNumberOfTabs(num, msg) {
+ is(gBrowser.tabs.length, num, msg);
+ }
+
+ function assertNumberOfPinnedTabs(num, msg) {
+ is(gBrowser._numPinnedTabs, num, msg);
+ }
+
+ // check prerequisites
+ assertNumberOfTabs(1, "we start off with one tab");
+ assertNumberOfPinnedTabs(0, "no pinned tabs so far");
+
+ // setup
+ gBrowser.addTab("about:blank");
+ assertNumberOfTabs(2, "there are two tabs, now");
+
+ let [tab1, tab2] = gBrowser.tabs;
+ let linkedBrowser = tab1.linkedBrowser;
+ gBrowser.pinTab(tab1);
+ gBrowser.pinTab(tab2);
+ assertNumberOfPinnedTabs(2, "both tabs are now pinned");
+
+ // run the test
+ yield promiseBrowserState(TEST_STATE);
+
+ assertNumberOfTabs(1, "one tab left after setBrowserState()");
+ assertNumberOfPinnedTabs(0, "there are no pinned tabs");
+ is(gBrowser.tabs[0].linkedBrowser, linkedBrowser, "first tab's browser got re-used");
+});
diff --git a/browser/components/sessionstore/test/browser_625016.js b/browser/components/sessionstore/test/browser_625016.js
new file mode 100644
index 000000000..b551fcbb3
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_625016.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function* setup() {
+ /** Test for Bug 625016 - Restore windows closed in succession to quit (non-OSX only) **/
+
+ // We'll test this by opening a new window, waiting for the save
+ // event, then closing that window. We'll observe the
+ // "sessionstore-state-write-complete" notification and check that
+ // the state contains no _closedWindows. We'll then add a new tab
+ // and make sure that the state following that was reset and the
+ // closed window is now in _closedWindows.
+
+ requestLongerTimeout(2);
+
+ yield forceSaveState();
+
+ // We'll clear all closed windows to make sure our state is clean
+ // forgetClosedWindow doesn't trigger a delayed save
+ forgetClosedWindows();
+ is(ss.getClosedWindowCount(), 0, "starting with no closed windows");
+});
+
+add_task(function* new_window() {
+ let newWin;
+ try {
+ newWin = yield promiseNewWindowLoaded();
+ let tab = newWin.gBrowser.addTab("http://example.com/browser_625016.js?" + Math.random());
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ // Double check that we have no closed windows
+ is(ss.getClosedWindowCount(), 0, "no closed windows on first save");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+ newWin = null;
+
+ let state = JSON.parse((yield promiseRecoveryFileContents()));
+ is(state.windows.length, 2,
+ "observe1: 2 windows in data written to disk");
+ is(state._closedWindows.length, 0,
+ "observe1: no closed windows in data written to disk");
+
+ // The API still treats the closed window as closed, so ensure that window is there
+ is(ss.getClosedWindowCount(), 1,
+ "observe1: 1 closed window according to API");
+ } finally {
+ if (newWin) {
+ yield BrowserTestUtils.closeWindow(newWin);
+ }
+ yield forceSaveState();
+ }
+});
+
+// We'll open a tab, which should trigger another state save which would wipe
+// the _shouldRestore attribute from the closed window
+add_task(function* new_tab() {
+ let newTab;
+ try {
+ newTab = gBrowser.addTab("about:mozilla");
+
+ let state = JSON.parse((yield promiseRecoveryFileContents()));
+ is(state.windows.length, 1,
+ "observe2: 1 window in data being written to disk");
+ is(state._closedWindows.length, 1,
+ "observe2: 1 closed window in data being written to disk");
+
+ // The API still treats the closed window as closed, so ensure that window is there
+ is(ss.getClosedWindowCount(), 1,
+ "observe2: 1 closed window according to API");
+ } finally {
+ gBrowser.removeTab(newTab);
+ }
+});
+
+
+add_task(function* done() {
+ // The API still represents the closed window as closed, so we can clear it
+ // with the API, but just to make sure...
+// is(ss.getClosedWindowCount(), 1, "1 closed window according to API");
+ forgetClosedWindows();
+ Services.prefs.clearUserPref("browser.sessionstore.interval");
+});
diff --git a/browser/components/sessionstore/test/browser_628270.js b/browser/components/sessionstore/test/browser_628270.js
new file mode 100644
index 000000000..f552cbfda
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_628270.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ let assertNumberOfTabs = function (num, msg) {
+ is(gBrowser.tabs.length, num, msg);
+ }
+
+ let assertNumberOfVisibleTabs = function (num, msg) {
+ is(gBrowser.visibleTabs.length, num, msg);
+ }
+
+ let assertNumberOfPinnedTabs = function (num, msg) {
+ is(gBrowser._numPinnedTabs, num, msg);
+ }
+
+ waitForExplicitFinish();
+
+ // check prerequisites
+ assertNumberOfTabs(1, "we start off with one tab");
+
+ // setup
+ let tab = gBrowser.addTab("about:mozilla");
+
+ whenTabIsLoaded(tab, function () {
+ // hide the newly created tab
+ assertNumberOfVisibleTabs(2, "there are two visible tabs");
+ gBrowser.showOnlyTheseTabs([gBrowser.tabs[0]]);
+ assertNumberOfVisibleTabs(1, "there is one visible tab");
+ ok(tab.hidden, "newly created tab is now hidden");
+
+ // close and restore hidden tab
+ promiseRemoveTab(tab).then(() => {
+ tab = ss.undoCloseTab(window, 0);
+
+ // check that everything was restored correctly, clean up and finish
+ whenTabIsLoaded(tab, function () {
+ is(tab.linkedBrowser.currentURI.spec, "about:mozilla", "restored tab has correct url");
+
+ gBrowser.removeTab(tab);
+ finish();
+ });
+ });
+ });
+}
+
+function whenTabIsLoaded(tab, callback) {
+ tab.linkedBrowser.addEventListener("load", function onLoad() {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ callback();
+ }, true);
+}
diff --git a/browser/components/sessionstore/test/browser_635418.js b/browser/components/sessionstore/test/browser_635418.js
new file mode 100644
index 000000000..3b21c5b0f
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_635418.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/. */
+
+// This tests that hiding/showing a tab, on its own, eventually triggers a
+// session store.
+
+function test() {
+ waitForExplicitFinish();
+
+ // We speed up the interval between session saves to ensure that the test
+ // runs quickly.
+ Services.prefs.setIntPref("browser.sessionstore.interval", 2000);
+
+ // Loading a tab causes a save state and this is meant to catch that event.
+ waitForSaveState(testBug635418_1);
+
+ // Assumption: Only one window is open and it has one tab open.
+ gBrowser.addTab("about:mozilla");
+}
+
+function testBug635418_1() {
+ ok(!gBrowser.tabs[0].hidden, "first tab should not be hidden");
+ ok(!gBrowser.tabs[1].hidden, "second tab should not be hidden");
+
+ waitForSaveState(testBug635418_2);
+
+ // We can't hide the selected tab, so hide the new one
+ gBrowser.hideTab(gBrowser.tabs[1]);
+}
+
+function testBug635418_2() {
+ let state = JSON.parse(ss.getBrowserState());
+ ok(!state.windows[0].tabs[0].hidden, "first tab should still not be hidden");
+ ok(state.windows[0].tabs[1].hidden, "second tab should be hidden by now");
+
+ waitForSaveState(testBug635418_3);
+ gBrowser.showTab(gBrowser.tabs[1]);
+}
+
+function testBug635418_3() {
+ let state = JSON.parse(ss.getBrowserState());
+ ok(!state.windows[0].tabs[0].hidden, "first tab should still still not be hidden");
+ ok(!state.windows[0].tabs[1].hidden, "second tab should not be hidden again");
+
+ done();
+}
+
+function done() {
+ gBrowser.removeTab(window.gBrowser.tabs[1]);
+
+ Services.prefs.clearUserPref("browser.sessionstore.interval");
+
+ executeSoon(finish);
+}
diff --git a/browser/components/sessionstore/test/browser_636279.js b/browser/components/sessionstore/test/browser_636279.js
new file mode 100644
index 000000000..250995606
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_636279.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var stateBackup = ss.getBrowserState();
+
+var statePinned = {windows:[{tabs:[
+ {entries:[{url:"http://example.com#1"}], pinned: true}
+]}]};
+
+var state = {windows:[{tabs:[
+ {entries:[{url:"http://example.com#1"}]},
+ {entries:[{url:"http://example.com#2"}]},
+ {entries:[{url:"http://example.com#3"}]},
+ {entries:[{url:"http://example.com#4"}]},
+]}]};
+
+function test() {
+ waitForExplicitFinish();
+
+ registerCleanupFunction(function () {
+ TabsProgressListener.uninit();
+ ss.setBrowserState(stateBackup);
+ });
+
+
+ TabsProgressListener.init();
+
+ window.addEventListener("SSWindowStateReady", function onReady() {
+ window.removeEventListener("SSWindowStateReady", onReady, false);
+
+ let firstProgress = true;
+
+ TabsProgressListener.setCallback(function (needsRestore, isRestoring) {
+ if (firstProgress) {
+ firstProgress = false;
+ is(isRestoring, 3, "restoring 3 tabs concurrently");
+ } else {
+ ok(isRestoring <= 3, "restoring max. 2 tabs concurrently");
+ }
+
+ if (0 == needsRestore) {
+ TabsProgressListener.unsetCallback();
+ waitForFocus(finish);
+ }
+ });
+
+ ss.setBrowserState(JSON.stringify(state));
+ }, false);
+
+ ss.setBrowserState(JSON.stringify(statePinned));
+}
+
+function countTabs() {
+ let needsRestore = 0, isRestoring = 0;
+ let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+
+ while (windowsEnum.hasMoreElements()) {
+ let window = windowsEnum.getNext();
+ if (window.closed)
+ continue;
+
+ for (let i = 0; i < window.gBrowser.tabs.length; i++) {
+ let browser = window.gBrowser.tabs[i].linkedBrowser;
+ if (browser.__SS_restoreState == TAB_STATE_RESTORING)
+ isRestoring++;
+ else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
+ needsRestore++;
+ }
+ }
+
+ return [needsRestore, isRestoring];
+}
+
+var TabsProgressListener = {
+ init: function () {
+ Services.obs.addObserver(this, "sessionstore-debug-tab-restored", false);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "sessionstore-debug-tab-restored");
+ this.unsetCallback();
+ },
+
+ setCallback: function (callback) {
+ this.callback = callback;
+ },
+
+ unsetCallback: function () {
+ delete this.callback;
+ },
+
+ observe: function (browser, topic, data) {
+ TabsProgressListener.onRestored(browser);
+ },
+
+ onRestored: function (browser) {
+ if (this.callback && browser.__SS_restoreState == TAB_STATE_RESTORING) {
+ this.callback.apply(null, countTabs());
+ }
+ }
+}
diff --git a/browser/components/sessionstore/test/browser_637020.js b/browser/components/sessionstore/test/browser_637020.js
new file mode 100644
index 000000000..1c1f357d7
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_637020.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URL = "http://mochi.test:8888/browser/browser/components/" +
+ "sessionstore/test/browser_637020_slow.sjs";
+
+const TEST_STATE = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:mozilla" }] },
+ { entries: [{ url: "about:robots" }] }
+ ]
+ }, {
+ tabs: [
+ { entries: [{ url: TEST_URL }] },
+ { entries: [{ url: TEST_URL }] }
+ ]
+ }]
+};
+
+/**
+ * This test ensures that windows that have just been restored will be marked
+ * as dirty, otherwise _getCurrentState() might ignore them when collecting
+ * state for the first time and we'd just save them as empty objects.
+ *
+ * The dirty state acts as a cache to not collect data from all windows all the
+ * time, so at the beginning, each window must be dirty so that we collect
+ * their state at least once.
+ */
+
+add_task(function* test() {
+ // Wait until the new window has been opened.
+ let promiseWindow = new Promise(resolve => {
+ Services.obs.addObserver(function onOpened(subject) {
+ Services.obs.removeObserver(onOpened, "domwindowopened");
+ resolve(subject);
+ }, "domwindowopened", false);
+ });
+
+ // Set the new browser state that will
+ // restore a window with two slowly loading tabs.
+ let backupState = SessionStore.getBrowserState();
+ SessionStore.setBrowserState(JSON.stringify(TEST_STATE));
+ let win = yield promiseWindow;
+
+ // The window has now been opened. Check the state that is returned,
+ // this should come from the cache while the window isn't restored, yet.
+ info("the window has been opened");
+ checkWindows();
+
+ // The history has now been restored and the tabs are loading. The data must
+ // now come from the window, if it's correctly been marked as dirty before.
+ yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+ info("the delayed startup has finished");
+ checkWindows();
+
+ // Cleanup.
+ yield BrowserTestUtils.closeWindow(win);
+ yield promiseBrowserState(backupState);
+});
+
+function checkWindows() {
+ let state = JSON.parse(SessionStore.getBrowserState());
+ is(state.windows[0].tabs.length, 2, "first window has two tabs");
+ is(state.windows[1].tabs.length, 2, "second window has two tabs");
+}
diff --git a/browser/components/sessionstore/test/browser_637020_slow.sjs b/browser/components/sessionstore/test/browser_637020_slow.sjs
new file mode 100644
index 000000000..41da3c2ad
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_637020_slow.sjs
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const DELAY_MS = "2000";
+
+let timer;
+
+function handleRequest(req, resp) {
+ resp.processAsync();
+ resp.setHeader("Cache-Control", "no-cache", false);
+ resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(() => {
+ resp.write("hi");
+ resp.finish();
+ }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/browser/components/sessionstore/test/browser_644409-scratchpads.js b/browser/components/sessionstore/test/browser_644409-scratchpads.js
new file mode 100644
index 000000000..56826801a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_644409-scratchpads.js
@@ -0,0 +1,68 @@
+ /* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const testState = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:blank" }] },
+ ]
+ }],
+ scratchpads: [
+ { text: "text1", executionContext: 1 },
+ { text: "", executionContext: 2, filename: "test.js" }
+ ]
+};
+
+// only finish() when correct number of windows opened
+var restored = [];
+function addState(state) {
+ restored.push(state);
+
+ if (restored.length == testState.scratchpads.length) {
+ ok(statesMatch(restored, testState.scratchpads),
+ "Two scratchpad windows restored");
+
+ Services.ww.unregisterNotification(windowObserver);
+ finish();
+ }
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.ww.registerNotification(windowObserver);
+
+ ss.setBrowserState(JSON.stringify(testState));
+}
+
+function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+
+ if (win.Scratchpad) {
+ win.Scratchpad.addObserver({
+ onReady: function() {
+ win.Scratchpad.removeObserver(this);
+
+ let state = win.Scratchpad.getState();
+ BrowserTestUtils.closeWindow(win).then(() => {
+ addState(state);
+ });
+ },
+ });
+ }
+ }, false);
+ }
+}
+
+function statesMatch(restored, states) {
+ return states.every(function(state) {
+ return restored.some(function(restoredState) {
+ return state.filename == restoredState.filename &&
+ state.text == restoredState.text &&
+ state.executionContext == restoredState.executionContext;
+ })
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_645428.js b/browser/components/sessionstore/test/browser_645428.js
new file mode 100644
index 000000000..124a7aea9
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_645428.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const NOTIFICATION = "sessionstore-browser-state-restored";
+
+function test() {
+ waitForExplicitFinish();
+
+ function observe(subject, topic, data) {
+ if (NOTIFICATION == topic) {
+ finish();
+ ok(true, "TOPIC received");
+ }
+ }
+
+ Services.obs.addObserver(observe, NOTIFICATION, false);
+ registerCleanupFunction(function () {
+ Services.obs.removeObserver(observe, NOTIFICATION);
+ });
+
+ ss.setBrowserState(JSON.stringify({ windows: [] }));
+}
diff --git a/browser/components/sessionstore/test/browser_659591.js b/browser/components/sessionstore/test/browser_659591.js
new file mode 100644
index 000000000..60b1dcd2e
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_659591.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ let eventReceived = false;
+
+ registerCleanupFunction(function () {
+ ok(eventReceived, "SSWindowClosing event received");
+ });
+
+ newWindow(function (win) {
+ win.addEventListener("SSWindowClosing", function onWindowClosing() {
+ win.removeEventListener("SSWindowClosing", onWindowClosing, false);
+ eventReceived = true;
+ }, false);
+
+ BrowserTestUtils.closeWindow(win).then(() => {
+ waitForFocus(finish);
+ });
+ });
+}
+
+function newWindow(callback) {
+ let opts = "chrome,all,dialog=no,height=800,width=800";
+ let win = window.openDialog(getBrowserURL(), "_blank", opts);
+
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ executeSoon(() => callback(win));
+ }, false);
+}
diff --git a/browser/components/sessionstore/test/browser_662743.js b/browser/components/sessionstore/test/browser_662743.js
new file mode 100644
index 000000000..212180213
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_662743.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This tests that session restore component does restore the right <select> option.
+// Session store should not rely only on previous user's selectedIndex, it should
+// check its value as well.
+
+function test() {
+ /** Tests selected options **/
+ requestLongerTimeout(2);
+ waitForExplicitFinish();
+
+ let testTabCount = 0;
+ let formData = [
+ // default case
+ { },
+
+ // new format
+ // index doesn't match value (testing an option in between (two))
+ { id:{ "select_id": {"selectedIndex":0,"value":"val2"} } },
+ // index doesn't match value (testing an invalid value)
+ { id:{ "select_id": {"selectedIndex":4,"value":"val8"} } },
+ // index doesn't match value (testing an invalid index)
+ { id:{ "select_id": {"selectedIndex":8,"value":"val5"} } },
+ // index and value match position zero
+ { id:{ "select_id": {"selectedIndex":0,"value":"val0"} }, xpath: {} },
+ // index doesn't match value (testing the last option (seven))
+ { id:{},"xpath":{ "/xhtml:html/xhtml:body/xhtml:select[@name='select_name']": {"selectedIndex":1,"value":"val7"} } },
+ // index and value match the default option "selectedIndex":3,"value":"val3"
+ { xpath: { "/xhtml:html/xhtml:body/xhtml:select[@name='select_name']" : {"selectedIndex":3,"value":"val3"} } },
+ // index matches default option however it doesn't match value
+ { id:{ "select_id": {"selectedIndex":3,"value":"val4"} } },
+ ];
+
+ let expectedValues = [
+ null, // default value
+ "val2",
+ null, // default value (invalid value)
+ "val5", // value is still valid (even it has an invalid index)
+ "val0",
+ "val7",
+ null,
+ "val4",
+ ];
+ let callback = function() {
+ testTabCount--;
+ if (testTabCount == 0) {
+ finish();
+ }
+ };
+
+ for (let i = 0; i < formData.length; i++) {
+ testTabCount++;
+ testTabRestoreData(formData[i], expectedValues[i], callback);
+ }
+}
+
+function testTabRestoreData(aFormData, aExpectedValue, aCallback) {
+ let testURL =
+ getRootDirectory(gTestPath) + "browser_662743_sample.html";
+ let tab = gBrowser.addTab(testURL);
+
+ aFormData.url = testURL;
+ let tabState = { entries: [{ url: testURL, }], formdata: aFormData };
+
+ promiseBrowserLoaded(tab.linkedBrowser).then(() => {
+ promiseTabState(tab, tabState).then(() => {
+ // Flush to make sure we have the latest form data.
+ return TabStateFlusher.flush(tab.linkedBrowser);
+ }).then(() => {
+ let doc = tab.linkedBrowser.contentDocument;
+ let select = doc.getElementById("select_id");
+ let value = select.options[select.selectedIndex].value;
+ let restoredTabState = JSON.parse(ss.getTabState(tab));
+
+ // If aExpectedValue=null we don't expect any form data to be collected.
+ if (!aExpectedValue) {
+ ok(!restoredTabState.hasOwnProperty("formdata"), "no formdata collected");
+ gBrowser.removeTab(tab);
+ aCallback();
+ return;
+ }
+
+ // test select options values
+ is(value, aExpectedValue,
+ "Select Option by selectedIndex &/or value has been restored correctly");
+
+ let restoredFormData = restoredTabState.formdata;
+ let selectIdFormData = restoredFormData.id.select_id;
+ value = restoredFormData.id.select_id.value;
+
+ // test format
+ ok("id" in restoredFormData || "xpath" in restoredFormData,
+ "FormData format is valid");
+ // test format
+ ok("selectedIndex" in selectIdFormData && "value" in selectIdFormData,
+ "select format is valid");
+ // test set collection values
+ is(value, aExpectedValue,
+ "Collection has been saved correctly");
+
+ // clean up
+ gBrowser.removeTab(tab);
+
+ aCallback();
+ });
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_662743_sample.html b/browser/components/sessionstore/test/browser_662743_sample.html
new file mode 100644
index 000000000..de48fa0c9
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_662743_sample.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Test 662743</title>
+
+<!-- Select events -->
+<h3>Select options</h3>
+<select id="select_id" name="select_name">
+ <option value="val0">Zero</option>
+ <option value="val1">One</option>
+ <option value="val2">Two</option>
+ <option value="val3" selected="selected">Three</option>
+ <option value="val4">Four</option>
+ <option value="val5">Five</option>
+ <option value="val6">Six</option>
+ <option value="val7">Seven</option>
+</select> \ No newline at end of file
diff --git a/browser/components/sessionstore/test/browser_662812.js b/browser/components/sessionstore/test/browser_662812.js
new file mode 100644
index 000000000..1bbaf67dc
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_662812.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ window.addEventListener("SSWindowStateBusy", function onBusy() {
+ window.removeEventListener("SSWindowStateBusy", onBusy, false);
+
+ let state = JSON.parse(ss.getWindowState(window));
+ ok(state.windows[0].busy, "window is busy");
+
+ window.addEventListener("SSWindowStateReady", function onReady() {
+ window.removeEventListener("SSWindowStateReady", onReady, false);
+
+ let state = JSON.parse(ss.getWindowState(window));
+ ok(!state.windows[0].busy, "window is not busy");
+
+ executeSoon(() => {
+ gBrowser.removeTab(gBrowser.tabs[1]);
+ finish();
+ });
+ }, false);
+ }, false);
+
+ // create a new tab
+ let tab = gBrowser.addTab("about:mozilla");
+ let browser = tab.linkedBrowser;
+
+ // close and restore it
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ gBrowser.removeTab(tab);
+ ss.undoCloseTab(window, 0);
+ }, true);
+}
diff --git a/browser/components/sessionstore/test/browser_665702-state_session.js b/browser/components/sessionstore/test/browser_665702-state_session.js
new file mode 100644
index 000000000..524b4969f
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_665702-state_session.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function compareArray(a, b) {
+ if (a.length !== b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function test() {
+ let currentState = JSON.parse(ss.getBrowserState());
+ ok(currentState.session, "session data returned by getBrowserState");
+
+ let keys = Object.keys(currentState.session);
+ let expectedKeys = ["lastUpdate", "startTime", "recentCrashes"];
+ ok(compareArray(keys.sort(), expectedKeys.sort()),
+ "session object from getBrowserState has correct keys");
+}
diff --git a/browser/components/sessionstore/test/browser_682507.js b/browser/components/sessionstore/test/browser_682507.js
new file mode 100644
index 000000000..52b95341b
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_682507.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+ gBrowser.addTab("about:mozilla");
+
+ ss.setTabState(gBrowser.tabs[1], ss.getTabState(gBrowser.tabs[1]));
+ ok(gBrowser.tabs[1].hasAttribute("pending"), "second tab should have 'pending' attribute");
+
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ ok(!gBrowser.tabs[1].hasAttribute("pending"), "second tab should have not 'pending' attribute");
+
+ gBrowser.removeTab(gBrowser.tabs[1]);
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+}
diff --git a/browser/components/sessionstore/test/browser_687710.js b/browser/components/sessionstore/test/browser_687710.js
new file mode 100644
index 000000000..372ecf7ae
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_687710.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that sessionrestore handles cycles in the shentry graph properly.
+//
+// These cycles shouldn't be there in the first place, but they cause hangs
+// when they mysteriously appear (bug 687710). Docshell code assumes this
+// graph is a tree and tires to walk to the root. But if there's a cycle,
+// there is no root, and we loop forever.
+
+var stateBackup = ss.getBrowserState();
+
+var state = {windows:[{tabs:[{entries:[
+ {
+ docIdentifier: 1,
+ url: "http://example.com",
+ children: [
+ {
+ docIdentifier: 2,
+ url: "http://example.com"
+ }
+ ]
+ },
+ {
+ docIdentifier: 2,
+ url: "http://example.com",
+ children: [
+ {
+ docIdentifier: 1,
+ url: "http://example.com"
+ }
+ ]
+ }
+]}]}]}
+
+function test() {
+ registerCleanupFunction(function () {
+ ss.setBrowserState(stateBackup);
+ });
+
+ /* This test fails by hanging. */
+ ss.setBrowserState(JSON.stringify(state));
+ ok(true, "Didn't hang!");
+}
diff --git a/browser/components/sessionstore/test/browser_687710_2.js b/browser/components/sessionstore/test/browser_687710_2.js
new file mode 100644
index 000000000..c22e73750
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_687710_2.js
@@ -0,0 +1,64 @@
+/* eslint-env mozilla/frame-script */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the fix for bug 687710 isn't too aggressive -- shentries which are
+// cousins should be able to share bfcache entries.
+
+var stateBackup = ss.getBrowserState();
+
+var state = {entries:[
+ {
+ docIdentifier: 1,
+ url: "http://example.com?1",
+ children: [{ docIdentifier: 10,
+ url: "http://example.com?10" }]
+ },
+ {
+ docIdentifier: 1,
+ url: "http://example.com?1#a",
+ children: [{ docIdentifier: 10,
+ url: "http://example.com?10#aa" }]
+ }
+]};
+
+add_task(function* test() {
+ let tab = gBrowser.addTab("about:blank");
+ yield promiseTabState(tab, state);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+ function compareEntries(i, j, history) {
+ let e1 = history.getEntryAtIndex(i, false)
+ .QueryInterface(Ci.nsISHEntry)
+ .QueryInterface(Ci.nsISHContainer);
+
+ let e2 = history.getEntryAtIndex(j, false)
+ .QueryInterface(Ci.nsISHEntry)
+ .QueryInterface(Ci.nsISHContainer);
+
+ ok(e1.sharesDocumentWith(e2),
+ `${i} should share doc with ${j}`);
+ is(e1.childCount, e2.childCount,
+ `Child count mismatch (${i}, ${j})`);
+
+ for (let c = 0; c < e1.childCount; c++) {
+ let c1 = e1.GetChildAt(c);
+ let c2 = e2.GetChildAt(c);
+
+ ok(c1.sharesDocumentWith(c2),
+ `Cousins should share documents. (${i}, ${j}, ${c})`);
+ }
+ }
+
+ let history = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISHistory);
+
+ is(history.count, 2, "history.count");
+ for (let i = 0; i < history.count; i++) {
+ for (let j = 0; j < history.count; j++) {
+ compareEntries(i, j, history);
+ }
+ }
+ });
+
+ ss.setBrowserState(stateBackup);
+});
diff --git a/browser/components/sessionstore/test/browser_694378.js b/browser/components/sessionstore/test/browser_694378.js
new file mode 100644
index 000000000..8578428d8
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_694378.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test Summary:
+// 1. call ss.setWindowState with a broken state
+// 1a. ensure that it doesn't throw.
+
+function test() {
+ waitForExplicitFinish();
+
+ let brokenState = {
+ windows: [
+ { tabs: [{ entries: [{ url: "about:mozilla" }] }] }
+ ],
+ selectedWindow: 2
+ };
+ let brokenStateString = JSON.stringify(brokenState);
+
+ let gotError = false;
+ try {
+ ss.setWindowState(window, brokenStateString, true);
+ }
+ catch (ex) {
+ gotError = true;
+ info(ex);
+ }
+
+ ok(!gotError, "ss.setWindowState did not throw an error");
+
+ // Make sure that we reset the state. Use a full state just in case things get crazy.
+ let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
+ waitForBrowserState(blankState, finish);
+}
diff --git a/browser/components/sessionstore/test/browser_701377.js b/browser/components/sessionstore/test/browser_701377.js
new file mode 100644
index 000000000..1bf2625ef
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_701377.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var state = {windows:[{tabs:[
+ {entries:[{url:"http://example.com#1"}]},
+ {entries:[{url:"http://example.com#2"}], hidden: true}
+]}]};
+
+function test() {
+ waitForExplicitFinish();
+
+ newWindowWithState(state, function (aWindow) {
+ let tab = aWindow.gBrowser.tabs[1];
+ ok(tab.hidden, "the second tab is hidden");
+
+ let tabShown = false;
+ let tabShowCallback = () => tabShown = true;
+ tab.addEventListener("TabShow", tabShowCallback, false);
+
+ let tabState = ss.getTabState(tab);
+ ss.setTabState(tab, tabState);
+
+ tab.removeEventListener("TabShow", tabShowCallback, false);
+ ok(tab.hidden && !tabShown, "tab remains hidden");
+
+ finish();
+ });
+}
+
+// ----------
+function newWindowWithState(aState, aCallback) {
+ let opts = "chrome,all,dialog=no,height=800,width=800";
+ let win = window.openDialog(getBrowserURL(), "_blank", opts);
+
+ registerCleanupFunction(() => BrowserTestUtils.closeWindow(win));
+
+ whenWindowLoaded(win, function onWindowLoaded(aWin) {
+ ss.setWindowState(aWin, JSON.stringify(aState), true);
+ executeSoon(() => aCallback(aWin));
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_705597.js b/browser/components/sessionstore/test/browser_705597.js
new file mode 100644
index 000000000..efadcfe88
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_705597.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var tabState = {
+ entries: [{url: "about:robots", children: [{url: "about:mozilla"}]}]
+};
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+
+ Services.prefs.setIntPref("browser.sessionstore.interval", 4000);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.sessionstore.interval");
+ });
+
+ let tab = gBrowser.addTab("about:blank");
+
+ let browser = tab.linkedBrowser;
+
+ promiseTabState(tab, tabState).then(() => {
+ let sessionHistory = browser.sessionHistory;
+ let entry = sessionHistory.getEntryAtIndex(0, false);
+ entry.QueryInterface(Ci.nsISHContainer);
+
+ whenChildCount(entry, 1, function () {
+ whenChildCount(entry, 2, function () {
+ promiseBrowserLoaded(browser).then(() => {
+ return TabStateFlusher.flush(browser);
+ }).then(() => {
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "tab has one history entry");
+ ok(!entries[0].children, "history entry has no subframes");
+
+ // Make sure that we reset the state.
+ let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
+ waitForBrowserState(blankState, finish);
+ });
+
+ // reload the browser to deprecate the subframes
+ browser.reload();
+ });
+
+ // create a dynamic subframe
+ let doc = browser.contentDocument;
+ let iframe = doc.createElement("iframe");
+ doc.body.appendChild(iframe);
+ iframe.setAttribute("src", "about:mozilla");
+ });
+ });
+}
+
+function whenChildCount(aEntry, aChildCount, aCallback) {
+ if (aEntry.childCount == aChildCount)
+ aCallback();
+ else
+ setTimeout(() => whenChildCount(aEntry, aChildCount, aCallback), 100);
+}
diff --git a/browser/components/sessionstore/test/browser_707862.js b/browser/components/sessionstore/test/browser_707862.js
new file mode 100644
index 000000000..e12c44af4
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_707862.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var tabState = {
+ entries: [{url: "about:robots", children: [{url: "about:mozilla"}]}]
+};
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+
+ Services.prefs.setIntPref("browser.sessionstore.interval", 4000);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.sessionstore.interval");
+ });
+
+ let tab = gBrowser.addTab("about:blank");
+
+ let browser = tab.linkedBrowser;
+
+ promiseTabState(tab, tabState).then(() => {
+ let sessionHistory = browser.sessionHistory;
+ let entry = sessionHistory.getEntryAtIndex(0, false);
+ entry.QueryInterface(Ci.nsISHContainer);
+
+ whenChildCount(entry, 1, function () {
+ whenChildCount(entry, 2, function () {
+ promiseBrowserLoaded(browser).then(() => {
+ let sessionHistory = browser.sessionHistory;
+ let entry = sessionHistory.getEntryAtIndex(0, false);
+
+ whenChildCount(entry, 0, function () {
+ // Make sure that we reset the state.
+ let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
+ waitForBrowserState(blankState, finish);
+ });
+ });
+
+ // reload the browser to deprecate the subframes
+ browser.reload();
+ });
+
+ // create a dynamic subframe
+ let doc = browser.contentDocument;
+ let iframe = doc.createElement("iframe");
+ doc.body.appendChild(iframe);
+ iframe.setAttribute("src", "about:mozilla");
+ });
+ });
+
+ // This test relies on the test timing out in order to indicate failure so
+ // let's add a dummy pass.
+ ok(true, "Each test requires at least one pass, fail or todo so here is a pass.");
+}
+
+function whenChildCount(aEntry, aChildCount, aCallback) {
+ if (aEntry.childCount == aChildCount)
+ aCallback();
+ else
+ setTimeout(() => whenChildCount(aEntry, aChildCount, aCallback), 100);
+}
diff --git a/browser/components/sessionstore/test/browser_739531.js b/browser/components/sessionstore/test/browser_739531.js
new file mode 100644
index 000000000..e5927caf6
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_739531.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test ensures that attempts made to save/restore ("duplicate") pages
+// using designmode AND make changes to document structure (remove body)
+// don't result in uncaught errors and a broken browser state.
+
+function test() {
+ waitForExplicitFinish();
+
+ let testURL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_739531_sample.html";
+
+ let loadCount = 0;
+ let tab = gBrowser.addTab(testURL);
+ tab.linkedBrowser.addEventListener("load", function onLoad(aEvent) {
+ // make sure both the page and the frame are loaded
+ if (++loadCount < 2)
+ return;
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+
+ // executeSoon to allow the JS to execute on the page
+ executeSoon(function() {
+
+ let tab2;
+ let caughtError = false;
+ try {
+ tab2 = ss.duplicateTab(window, tab);
+ }
+ catch (e) {
+ caughtError = true;
+ info(e);
+ }
+
+ is(gBrowser.tabs.length, 3, "there should be 3 tabs")
+
+ ok(!caughtError, "duplicateTab didn't throw");
+
+ // if the test fails, we don't want to try to close a tab that doesn't exist
+ if (tab2)
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+
+ finish();
+ });
+ }, true);
+}
diff --git a/browser/components/sessionstore/test/browser_739531_sample.html b/browser/components/sessionstore/test/browser_739531_sample.html
new file mode 100644
index 000000000..ad317ab0c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_739531_sample.html
@@ -0,0 +1,25 @@
+<!-- originally a crash test for bug 713417
+ https://bug713417.bugzilla.mozilla.org/attachment.cgi?id=584240 -->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+
+function boom()
+{
+ var w = document.getElementById("f").contentWindow;
+ var d = w.document;
+ d.designMode = 'on';
+ var r = d.documentElement;
+ d.removeChild(r);
+ document.adoptNode(r);
+}
+
+</script>
+</head>
+<body onload="boom();">
+<iframe src="data:text/html;charset=utf-8,1" id="f"></iframe>
+</body>
+</html>
+
diff --git a/browser/components/sessionstore/test/browser_739805.js b/browser/components/sessionstore/test/browser_739805.js
new file mode 100644
index 000000000..f00871661
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_739805.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var url = "data:text/html;charset=utf-8,<input%20id='foo'>";
+var tabState = {
+ entries: [{ url }], formdata: { id: { "foo": "bar" }, url }
+};
+
+function test() {
+ waitForExplicitFinish();
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+ registerCleanupFunction(function () {
+ if (gBrowser.tabs.length > 1)
+ gBrowser.removeTab(gBrowser.tabs[1]);
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+ });
+
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+
+ promiseBrowserLoaded(browser).then(() => {
+ isnot(gBrowser.selectedTab, tab, "newly created tab is not selected");
+
+ ss.setTabState(tab, JSON.stringify(tabState));
+ is(browser.__SS_restoreState, TAB_STATE_NEEDS_RESTORE, "tab needs restoring");
+
+ let {formdata} = JSON.parse(ss.getTabState(tab));
+ is(formdata && formdata.id["foo"], "bar", "tab state's formdata is valid");
+
+ promiseTabRestored(tab).then(() => {
+ ContentTask.spawn(browser, null, function() {
+ let input = content.document.getElementById("foo");
+ is(input.value, "bar", "formdata has been restored correctly");
+ }).then(() => { finish(); });
+ });
+
+ // Restore the tab by selecting it.
+ gBrowser.selectedTab = tab;
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_819510_perwindowpb.js b/browser/components/sessionstore/test/browser_819510_perwindowpb.js
new file mode 100644
index 000000000..21f916f0d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_819510_perwindowpb.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test opening default mochitest-normal-private-normal-private windows
+// (saving the state with last window being private)
+
+requestLongerTimeout(2);
+
+add_task(function* test_1() {
+ let win = yield promiseNewWindowLoaded();
+ win.gBrowser.addTab("http://www.example.com/1");
+
+ win = yield promiseNewWindowLoaded({private: true});
+ win.gBrowser.addTab("http://www.example.com/2");
+
+ win = yield promiseNewWindowLoaded();
+ win.gBrowser.addTab("http://www.example.com/3");
+
+ win = yield promiseNewWindowLoaded({private: true});
+ win.gBrowser.addTab("http://www.example.com/4");
+
+ let curState = JSON.parse(ss.getBrowserState());
+ is(curState.windows.length, 5, "Browser has opened 5 windows");
+ is(curState.windows[2].isPrivate, true, "Window is private");
+ is(curState.windows[4].isPrivate, true, "Last window is private");
+ is(curState.selectedWindow, 5, "Last window opened is the one selected");
+
+ let state = JSON.parse(yield promiseRecoveryFileContents());
+
+ is(state.windows.length, 3,
+ "sessionstore state: 3 windows in data being written to disk");
+ is(state.selectedWindow, 3,
+ "Selected window is updated to match one of the saved windows");
+ ok(state.windows.every(win => !win.isPrivate),
+ "Saved windows are not private");
+ is(state._closedWindows.length, 0,
+ "sessionstore state: no closed windows in data being written to disk");
+
+ // Cleanup.
+ yield promiseAllButPrimaryWindowClosed();
+ forgetClosedWindows();
+});
+
+// Test opening default mochitest window + 2 private windows
+add_task(function* test_2() {
+ let win = yield promiseNewWindowLoaded({private: true});
+ win.gBrowser.addTab("http://www.example.com/1");
+
+ win = yield promiseNewWindowLoaded({private: true});
+ win.gBrowser.addTab("http://www.example.com/2");
+
+ let curState = JSON.parse(ss.getBrowserState());
+ is(curState.windows.length, 3, "Browser has opened 3 windows");
+ is(curState.windows[1].isPrivate, true, "Window 1 is private");
+ is(curState.windows[2].isPrivate, true, "Window 2 is private");
+ is(curState.selectedWindow, 3, "Last window opened is the one selected");
+
+ let state = JSON.parse(yield promiseRecoveryFileContents());
+
+ is(state.windows.length, 1,
+ "sessionstore state: 1 windows in data being written to disk");
+ is(state.selectedWindow, 1,
+ "Selected window is updated to match one of the saved windows");
+ is(state._closedWindows.length, 0,
+ "sessionstore state: no closed windows in data being written to disk");
+
+ // Cleanup.
+ yield promiseAllButPrimaryWindowClosed();
+ forgetClosedWindows();
+});
+
+// Test opening default-normal-private-normal windows and closing a normal window
+add_task(function* test_3() {
+ let normalWindow = yield promiseNewWindowLoaded();
+ yield promiseTabLoad(normalWindow, "http://www.example.com/");
+
+ let win = yield promiseNewWindowLoaded({private: true});
+ yield promiseTabLoad(win, "http://www.example.com/");
+
+ win = yield promiseNewWindowLoaded();
+ yield promiseTabLoad(win, "http://www.example.com/");
+
+ let curState = JSON.parse(ss.getBrowserState());
+ is(curState.windows.length, 4, "Browser has opened 4 windows");
+ is(curState.windows[2].isPrivate, true, "Window 2 is private");
+ is(curState.selectedWindow, 4, "Last window opened is the one selected");
+
+ yield BrowserTestUtils.closeWindow(normalWindow);
+
+ // Pin and unpin a tab before checking the written state so that
+ // the list of restoring windows gets cleared. Otherwise the
+ // window we just closed would be marked as not closed.
+ let tab = win.gBrowser.tabs[0];
+ win.gBrowser.pinTab(tab);
+ win.gBrowser.unpinTab(tab);
+
+ let state = JSON.parse(yield promiseRecoveryFileContents());
+
+ is(state.windows.length, 2,
+ "sessionstore state: 2 windows in data being written to disk");
+ is(state.selectedWindow, 2,
+ "Selected window is updated to match one of the saved windows");
+ ok(state.windows.every(win => !win.isPrivate),
+ "Saved windows are not private");
+ is(state._closedWindows.length, 1,
+ "sessionstore state: 1 closed window in data being written to disk");
+ ok(state._closedWindows.every(win => !win.isPrivate),
+ "Closed windows are not private");
+
+ // Cleanup.
+ yield promiseAllButPrimaryWindowClosed();
+ forgetClosedWindows();
+});
+
+function* promiseTabLoad(win, url) {
+ let browser = win.gBrowser.selectedBrowser;
+ browser.loadURI(url);
+ yield promiseBrowserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+}
diff --git a/browser/components/sessionstore/test/browser_911547.js b/browser/components/sessionstore/test/browser_911547.js
new file mode 100644
index 000000000..58b2e9ef1
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_911547.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This tests that session restore component does restore the right content
+// security policy with the document.
+// The policy being tested disallows inline scripts
+
+add_task(function* test() {
+ // create a tab that has a CSP
+ let testURL = "http://mochi.test:8888/browser/browser/components/sessionstore/test/browser_911547_sample.html";
+ let tab = gBrowser.selectedTab = gBrowser.addTab(testURL);
+ gBrowser.selectedTab = tab;
+
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // this is a baseline to ensure CSP is active
+ // attempt to inject and run a script via inline (pre-restore, allowed)
+ yield injectInlineScript(browser, `document.getElementById("test_id").value = "fail";`);
+
+ let loadedPromise = promiseBrowserLoaded(browser);
+ yield ContentTask.spawn(browser, null, function() {
+ is(content.document.getElementById("test_id").value, "ok",
+ "CSP should block the inline script that modifies test_id");
+
+ // attempt to click a link to a data: URI (will inherit the CSP of the
+ // origin document) and navigate to the data URI in the link.
+ content.document.getElementById("test_data_link").click();
+ });
+
+ yield loadedPromise;
+
+ yield ContentTask.spawn(browser, null, function() {
+ is(content.document.getElementById("test_id2").value, "ok",
+ "CSP should block the script loaded by the clicked data URI");
+ });
+
+ // close the tab
+ yield promiseRemoveTab(tab);
+
+ // open new tab and recover the state
+ tab = ss.undoCloseTab(window, 0);
+ yield promiseTabRestored(tab);
+ browser = tab.linkedBrowser;
+
+ yield ContentTask.spawn(browser, null, function() {
+ is(content.document.getElementById("test_id2").value, "ok",
+ "CSP should block the script loaded by the clicked data URI after restore");
+ });
+
+ // clean up
+ gBrowser.removeTab(tab);
+});
+
+// injects an inline script element (with a text body)
+function injectInlineScript(browser, scriptText) {
+ return ContentTask.spawn(browser, scriptText, function(text) {
+ let scriptElt = content.document.createElement("script");
+ scriptElt.type = "text/javascript";
+ scriptElt.text = text;
+ content.document.body.appendChild(scriptElt);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_911547_sample.html b/browser/components/sessionstore/test/browser_911547_sample.html
new file mode 100644
index 000000000..ccc201159
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_911547_sample.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test 911547</title>
+ </head>
+<body>
+
+ <!--
+ this element gets modified by an injected script;
+ that script should be blocked by CSP.
+ Inline scripts can modify it, but not data uris.
+ -->
+ <input type="text" id="test_id" value="ok">
+
+ <a id="test_data_link" href="data:text/html;charset=utf-8,<input type='text' id='test_id2' value='ok'/> <script>document.getElementById('test_id2').value = 'fail';</script>">Test Link</a>
+
+</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_911547_sample.html^headers^ b/browser/components/sessionstore/test/browser_911547_sample.html^headers^
new file mode 100644
index 000000000..4623dec30
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_911547_sample.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: script-src 'self'
diff --git a/browser/components/sessionstore/test/browser_aboutPrivateBrowsing.js b/browser/components/sessionstore/test/browser_aboutPrivateBrowsing.js
new file mode 100644
index 000000000..3050bd4c1
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_aboutPrivateBrowsing.js
@@ -0,0 +1,21 @@
+"use strict";
+
+// Tests that an about:privatebrowsing tab with no history will not
+// be saved into session store and thus, it will not show up in
+// Recently Closed Tabs.
+
+add_task(function* () {
+ let tab = gBrowser.addTab("about:privatebrowsing");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ is(gBrowser.browsers[1].currentURI.spec, "about:privatebrowsing",
+ "we will be removing an about:privatebrowsing tab");
+
+ let r = `rand-${Math.random()}`;
+ ss.setTabValue(tab, "foobar", r);
+
+ yield promiseRemoveTab(tab);
+ let closedTabData = ss.getClosedTabData(window);
+ ok(!closedTabData.includes(r), "tab not stored in _closedTabs");
+});
diff --git a/browser/components/sessionstore/test/browser_aboutSessionRestore.js b/browser/components/sessionstore/test/browser_aboutSessionRestore.js
new file mode 100644
index 000000000..8ab91e4df
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_aboutSessionRestore.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const CRASH_URL = "about:mozilla";
+const CRASH_FAVICON = "chrome://branding/content/icon32.png";
+const CRASH_SHENTRY = {url: CRASH_URL};
+const CRASH_TAB = {entries: [CRASH_SHENTRY], image: CRASH_FAVICON};
+const CRASH_STATE = {windows: [{tabs: [CRASH_TAB]}]};
+
+const TAB_URL = "about:sessionrestore";
+const TAB_FORMDATA = {url: TAB_URL, id: {sessionData: CRASH_STATE}};
+const TAB_SHENTRY = {url: TAB_URL};
+const TAB_STATE = {entries: [TAB_SHENTRY], formdata: TAB_FORMDATA};
+
+const FRAME_SCRIPT = "data:," +
+ "content.document.getElementById('errorTryAgain').click()";
+
+add_task(function* () {
+ // Prepare a blank tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Fake a post-crash tab.
+ ss.setTabState(tab, JSON.stringify(TAB_STATE));
+ yield promiseTabRestored(tab);
+
+ ok(gBrowser.tabs.length > 1, "we have more than one tab");
+
+ let view = browser.contentDocument.getElementById("tabList").view;
+ ok(view.isContainer(0), "first entry is the window");
+ is(view.getCellProperties(1, { id: "title" }), "icon",
+ "second entry is the tab and has a favicon");
+
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT, true);
+
+ // Wait until the new window was restored.
+ let win = yield waitForNewWindow();
+ yield BrowserTestUtils.closeWindow(win);
+
+ let [{tabs: [{entries: [{url}]}]}] = JSON.parse(ss.getClosedWindowData());
+ is(url, CRASH_URL, "session was restored correctly");
+ ss.forgetClosedWindow(0);
+});
+
+function waitForNewWindow() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observe(win, topic) {
+ Services.obs.removeObserver(observe, topic);
+ resolve(win);
+ }, "browser-delayed-startup-finished", false);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_async_duplicate_tab.js b/browser/components/sessionstore/test/browser_async_duplicate_tab.js
new file mode 100644
index 000000000..8696a284f
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_async_duplicate_tab.js
@@ -0,0 +1,78 @@
+"use strict";
+
+const URL = "data:text/html;charset=utf-8,<a href=%23>clickme</a>";
+
+add_task(function* test_duplicate() {
+ // Create new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to empty any queued update messages.
+ yield TabStateFlusher.flush(browser);
+
+ // Click the link to navigate, this will add second shistory entry.
+ yield ContentTask.spawn(browser, null, function* () {
+ return new Promise(resolve => {
+ addEventListener("hashchange", function onHashChange() {
+ removeEventListener("hashchange", onHashChange);
+ resolve();
+ });
+
+ // Click the link.
+ content.document.querySelector("a").click();
+ });
+ });
+
+ // Duplicate the tab.
+ let tab2 = ss.duplicateTab(window, tab);
+
+ // Wait until the tab has fully restored.
+ yield promiseTabRestored(tab2);
+ yield TabStateFlusher.flush(tab2.linkedBrowser);
+
+ // There should be two history entries now.
+ let {entries} = JSON.parse(ss.getTabState(tab2));
+ is(entries.length, 2, "there are two shistory entries");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab2);
+ yield promiseRemoveTab(tab);
+});
+
+add_task(function* test_duplicate_remove() {
+ // Create new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to empty any queued update messages.
+ yield TabStateFlusher.flush(browser);
+
+ // Click the link to navigate, this will add second shistory entry.
+ yield ContentTask.spawn(browser, null, function* () {
+ return new Promise(resolve => {
+ addEventListener("hashchange", function onHashChange() {
+ removeEventListener("hashchange", onHashChange);
+ resolve();
+ });
+
+ // Click the link.
+ content.document.querySelector("a").click();
+ });
+ });
+
+ // Duplicate the tab.
+ let tab2 = ss.duplicateTab(window, tab);
+
+ // Before the duplication finished, remove the tab.
+ yield Promise.all([promiseRemoveTab(tab), promiseTabRestored(tab2)]);
+ yield TabStateFlusher.flush(tab2.linkedBrowser);
+
+ // There should be two history entries now.
+ let {entries} = JSON.parse(ss.getTabState(tab2));
+ is(entries.length, 2, "there are two shistory entries");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab2);
+});
diff --git a/browser/components/sessionstore/test/browser_async_flushes.js b/browser/components/sessionstore/test/browser_async_flushes.js
new file mode 100644
index 000000000..a4cbbfbc7
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_async_flushes.js
@@ -0,0 +1,113 @@
+"use strict";
+
+const URL = "data:text/html;charset=utf-8,<a href=%23>clickme</a>";
+
+add_task(function* test_flush() {
+ // Create new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to empty any queued update messages.
+ yield TabStateFlusher.flush(browser);
+
+ // There should be one history entry.
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is a single history entry");
+
+ // Click the link to navigate, this will add second shistory entry.
+ yield ContentTask.spawn(browser, null, function* () {
+ return new Promise(resolve => {
+ addEventListener("hashchange", function onHashChange() {
+ removeEventListener("hashchange", onHashChange);
+ resolve();
+ });
+
+ // Click the link.
+ content.document.querySelector("a").click();
+ });
+ });
+
+ // Flush to empty any queued update messages.
+ yield TabStateFlusher.flush(browser);
+
+ // There should be two history entries now.
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 2, "there are two shistory entries");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_crash() {
+ // Create new tab.
+ let tab = gBrowser.addTab(URL);
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to empty any queued update messages.
+ yield TabStateFlusher.flush(browser);
+
+ // There should be one history entry.
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is a single history entry");
+
+ // Click the link to navigate.
+ yield ContentTask.spawn(browser, null, function* () {
+ return new Promise(resolve => {
+ addEventListener("hashchange", function onHashChange() {
+ removeEventListener("hashchange", onHashChange);
+ resolve();
+ });
+
+ // Click the link.
+ content.document.querySelector("a").click();
+ });
+ });
+
+ // Crash the browser and flush. Both messages are async and will be sent to
+ // the content process. The "crash" message makes it first so that we don't
+ // get a chance to process the flush. The TabStateFlusher however should be
+ // notified so that the flush still completes.
+ let promise1 = BrowserTestUtils.crashBrowser(browser);
+ let promise2 = TabStateFlusher.flush(browser);
+ yield Promise.all([promise1, promise2]);
+
+ // The pending update should be lost.
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 1, "still only one history entry");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_remove() {
+ // Create new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to empty any queued update messages.
+ yield TabStateFlusher.flush(browser);
+
+ // There should be one history entry.
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is a single history entry");
+
+ // Click the link to navigate.
+ yield ContentTask.spawn(browser, null, function* () {
+ return new Promise(resolve => {
+ addEventListener("hashchange", function onHashChange() {
+ removeEventListener("hashchange", onHashChange);
+ resolve();
+ });
+
+ // Click the link.
+ content.document.querySelector("a").click();
+ });
+ });
+
+ // Request a flush and remove the tab. The flush should still complete.
+ yield Promise.all([TabStateFlusher.flush(browser), promiseRemoveTab(tab)]);
+})
diff --git a/browser/components/sessionstore/test/browser_async_remove_tab.js b/browser/components/sessionstore/test/browser_async_remove_tab.js
new file mode 100644
index 000000000..20f3463d0
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_async_remove_tab.js
@@ -0,0 +1,242 @@
+"use strict";
+
+function* createTabWithRandomValue(url) {
+ let tab = gBrowser.addTab(url);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Set a random value.
+ let r = `rand-${Math.random()}`;
+ ss.setTabValue(tab, "foobar", r);
+
+ // Flush to ensure there are no scheduled messages.
+ yield TabStateFlusher.flush(browser);
+
+ return {tab, r};
+}
+
+function isValueInClosedData(rval) {
+ return ss.getClosedTabData(window).includes(rval);
+}
+
+function restoreClosedTabWithValue(rval) {
+ let closedTabData = JSON.parse(ss.getClosedTabData(window));
+ let index = closedTabData.findIndex(function (data) {
+ return (data.state.extData && data.state.extData.foobar) == rval;
+ });
+
+ if (index == -1) {
+ throw new Error("no closed tab found for given rval");
+ }
+
+ return ss.undoCloseTab(window, index);
+}
+
+function promiseNewLocationAndHistoryEntryReplaced(browser, snippet) {
+ return ContentTask.spawn(browser, snippet, function* (snippet) {
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let shistory = webNavigation.sessionHistory;
+
+ // Evaluate the snippet that the changes the location.
+ eval(snippet);
+
+ return new Promise(resolve => {
+ let listener = {
+ OnHistoryReplaceEntry() {
+ shistory.removeSHistoryListener(this);
+ resolve();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISHistoryListener,
+ Ci.nsISupportsWeakReference
+ ])
+ };
+
+ shistory.addSHistoryListener(listener);
+
+ /* Keep the weak shistory listener alive. */
+ addEventListener("unload", function () {
+ try {
+ shistory.removeSHistoryListener(listener);
+ } catch (e) { /* Will most likely fail. */ }
+ });
+ });
+ });
+}
+
+function promiseHistoryEntryReplacedNonRemote(browser) {
+ let {listeners} = promiseHistoryEntryReplacedNonRemote;
+
+ return new Promise(resolve => {
+ let shistory = browser.webNavigation.sessionHistory;
+
+ let listener = {
+ OnHistoryReplaceEntry() {
+ shistory.removeSHistoryListener(this);
+ resolve();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISHistoryListener,
+ Ci.nsISupportsWeakReference
+ ])
+ };
+
+ shistory.addSHistoryListener(listener);
+ listeners.set(browser, listener);
+ });
+}
+promiseHistoryEntryReplacedNonRemote.listeners = new WeakMap();
+
+add_task(function* dont_save_empty_tabs() {
+ let {tab, r} = yield createTabWithRandomValue("about:blank");
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // No tab state worth saving.
+ ok(!isValueInClosedData(r), "closed tab not saved");
+ yield promise;
+
+ // Still no tab state worth saving.
+ ok(!isValueInClosedData(r), "closed tab not saved");
+});
+
+add_task(function* save_worthy_tabs_remote() {
+ let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+ ok(tab.linkedBrowser.isRemoteBrowser, "browser is remote");
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // Tab state deemed worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+ yield promise;
+
+ // Tab state still deemed worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* save_worthy_tabs_nonremote() {
+ let {tab, r} = yield createTabWithRandomValue("about:robots");
+ ok(!tab.linkedBrowser.isRemoteBrowser, "browser is not remote");
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // Tab state deemed worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+ yield promise;
+
+ // Tab state still deemed worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* save_worthy_tabs_remote_final() {
+ let {tab, r} = yield createTabWithRandomValue("about:blank");
+ let browser = tab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "browser is remote");
+
+ // Replace about:blank with a new remote page.
+ let snippet = 'webNavigation.loadURI("https://example.com/", null, null, null, null)';
+ yield promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
+
+ // Remotness shouldn't have changed.
+ ok(browser.isRemoteBrowser, "browser is still remote");
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // No tab state worth saving (that we know about yet).
+ ok(!isValueInClosedData(r), "closed tab not saved");
+ yield promise;
+
+ // Turns out there is a tab state worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* save_worthy_tabs_nonremote_final() {
+ let {tab, r} = yield createTabWithRandomValue("about:blank");
+ let browser = tab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "browser is remote");
+
+ // Replace about:blank with a non-remote entry.
+ yield BrowserTestUtils.loadURI(browser, "about:robots");
+ ok(!browser.isRemoteBrowser, "browser is not remote anymore");
+
+ // Wait until the new entry replaces about:blank.
+ yield promiseHistoryEntryReplacedNonRemote(browser);
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // No tab state worth saving (that we know about yet).
+ ok(!isValueInClosedData(r), "closed tab not saved");
+ yield promise;
+
+ // Turns out there is a tab state worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* dont_save_empty_tabs_final() {
+ let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+ let browser = tab.linkedBrowser;
+
+ // Replace the current page with an about:blank entry.
+ let snippet = 'content.location.replace("about:blank")';
+ yield promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // Tab state deemed worth saving (yet).
+ ok(isValueInClosedData(r), "closed tab saved");
+ yield promise;
+
+ // Turns out we don't want to save the tab state.
+ ok(!isValueInClosedData(r), "closed tab not saved");
+});
+
+add_task(function* undo_worthy_tabs() {
+ let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+ ok(tab.linkedBrowser.isRemoteBrowser, "browser is remote");
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // Tab state deemed worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+
+ // Restore the closed tab before receiving its final message.
+ tab = restoreClosedTabWithValue(r);
+
+ // Wait for the final update message.
+ yield promise;
+
+ // Check we didn't add the tab back to the closed list.
+ ok(!isValueInClosedData(r), "tab no longer closed");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+});
+
+add_task(function* forget_worthy_tabs_remote() {
+ let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+ ok(tab.linkedBrowser.isRemoteBrowser, "browser is remote");
+
+ // Remove the tab before the update arrives.
+ let promise = promiseRemoveTab(tab);
+
+ // Tab state deemed worth saving.
+ ok(isValueInClosedData(r), "closed tab saved");
+
+ // Forget the closed tab.
+ ss.forgetClosedTab(window, 0);
+
+ // Wait for the final update message.
+ yield promise;
+
+ // Check we didn't add the tab back to the closed list.
+ ok(!isValueInClosedData(r), "we forgot about the tab");
+});
diff --git a/browser/components/sessionstore/test/browser_async_window_flushing.js b/browser/components/sessionstore/test/browser_async_window_flushing.js
new file mode 100644
index 000000000..418c055c2
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_async_window_flushing.js
@@ -0,0 +1,178 @@
+"use strict";
+
+const PAGE = "http://example.com/";
+
+/**
+ * Tests that if we initially discard a window as not interesting
+ * to save in the closed windows array, that we revisit that decision
+ * after a window flush has completed.
+ */
+add_task(function* test_add_interesting_window() {
+ // We want to suppress all non-final updates from the browser tabs
+ // so as to eliminate any racy-ness with this test.
+ yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+ // Depending on previous tests, we might already have some closed
+ // windows stored. We'll use its length to determine whether or not
+ // the window was added or not.
+ let initialClosedWindows = ss.getClosedWindowCount();
+
+ // Make sure we can actually store another closed window
+ yield pushPrefs(["browser.sessionstore.max_windows_undo",
+ initialClosedWindows + 1]);
+
+ // Create a new browser window. Since the default window will start
+ // at about:blank, SessionStore should find this tab (and therefore the
+ // whole window) uninteresting, and should not initially put it into
+ // the closed windows array.
+ let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let browser = newWin.gBrowser.selectedBrowser;
+
+ // Send a message that will cause the content to change its location
+ // to someplace more interesting. We've disabled auto updates from
+ // the browser, so the parent won't know about this
+ yield ContentTask.spawn(browser, PAGE, function*(PAGE) {
+ content.location = PAGE;
+ });
+
+ yield promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
+
+ // Clear out the userTypedValue so that the new window looks like
+ // it's really not worth restoring.
+ browser.userTypedValue = null;
+
+ // Once the domWindowClosed Promise resolves, the window should
+ // have closed, and SessionStore's onClose handler should have just
+ // run.
+ let domWindowClosed = BrowserTestUtils.domWindowClosed(newWin);
+
+ // Once this windowClosed Promise resolves, we should have finished
+ // the flush and revisited our decision to put this window into
+ // the closed windows array.
+ let windowClosed = BrowserTestUtils.windowClosed(newWin);
+
+ // Ok, let's close the window.
+ newWin.close();
+
+ yield domWindowClosed;
+ // OnClose has just finished running.
+ let currentClosedWindows = ss.getClosedWindowCount();
+ is(currentClosedWindows, initialClosedWindows,
+ "We should not have added the window to the closed windows array");
+
+ yield windowClosed;
+ // The window flush has finished
+ currentClosedWindows = ss.getClosedWindowCount();
+ is(currentClosedWindows,
+ initialClosedWindows + 1,
+ "We should have added the window to the closed windows array");
+});
+
+/**
+ * Tests that if we initially store a closed window as interesting
+ * to save in the closed windows array, that we revisit that decision
+ * after a window flush has completed, and stop storing a window that
+ * we've deemed no longer interesting.
+ */
+add_task(function* test_remove_uninteresting_window() {
+ // We want to suppress all non-final updates from the browser tabs
+ // so as to eliminate any racy-ness with this test.
+ yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+ // Depending on previous tests, we might already have some closed
+ // windows stored. We'll use its length to determine whether or not
+ // the window was added or not.
+ let initialClosedWindows = ss.getClosedWindowCount();
+
+ // Make sure we can actually store another closed window
+ yield pushPrefs(["browser.sessionstore.max_windows_undo",
+ initialClosedWindows + 1]);
+
+ let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+ // Now browse the initial tab of that window to an interesting
+ // site.
+ let tab = newWin.gBrowser.selectedTab;
+ let browser = tab.linkedBrowser;
+ browser.loadURI(PAGE);
+
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+ yield TabStateFlusher.flush(browser);
+
+ // Send a message that will cause the content to purge its
+ // history entries and make itself seem uninteresting.
+ yield ContentTask.spawn(browser, null, function*() {
+ // Epic hackery to make this browser seem suddenly boring.
+ Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+ docShell.setCurrentURI(BrowserUtils.makeURI("about:blank"));
+
+ let {sessionHistory} = docShell.QueryInterface(Ci.nsIWebNavigation);
+ sessionHistory.PurgeHistory(sessionHistory.count);
+ });
+
+ // Once the domWindowClosed Promise resolves, the window should
+ // have closed, and SessionStore's onClose handler should have just
+ // run.
+ let domWindowClosed = BrowserTestUtils.domWindowClosed(newWin);
+
+ // Once this windowClosed Promise resolves, we should have finished
+ // the flush and revisited our decision to put this window into
+ // the closed windows array.
+ let windowClosed = BrowserTestUtils.windowClosed(newWin);
+
+ // Ok, let's close the window.
+ newWin.close();
+
+ yield domWindowClosed;
+ // OnClose has just finished running.
+ let currentClosedWindows = ss.getClosedWindowCount();
+ is(currentClosedWindows, initialClosedWindows + 1,
+ "We should have added the window to the closed windows array");
+
+ yield windowClosed;
+ // The window flush has finished
+ currentClosedWindows = ss.getClosedWindowCount();
+ is(currentClosedWindows,
+ initialClosedWindows,
+ "We should have removed the window from the closed windows array");
+});
+
+/**
+ * Tests that when we close a window, it is immediately removed from the
+ * _windows array.
+ */
+add_task(function* test_synchronously_remove_window_state() {
+ // Depending on previous tests, we might already have some closed
+ // windows stored. We'll use its length to determine whether or not
+ // the window was added or not.
+ let state = JSON.parse(ss.getBrowserState());
+ ok(state, "Make sure we can get the state");
+ let initialWindows = state.windows.length;
+
+ // Open a new window and send the first tab somewhere
+ // interesting.
+ let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+ let browser = newWin.gBrowser.selectedBrowser;
+ browser.loadURI(PAGE);
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+ yield TabStateFlusher.flush(browser);
+
+ state = JSON.parse(ss.getBrowserState());
+ is(state.windows.length, initialWindows + 1,
+ "The new window to be in the state");
+
+ // Now close the window, and make sure that the window was removed
+ // from the windows list from the SessionState. We're specifically
+ // testing the case where the window is _not_ removed in between
+ // the close-initiated flush request and the flush response.
+ let windowClosed = BrowserTestUtils.windowClosed(newWin);
+ newWin.close();
+
+ state = JSON.parse(ss.getBrowserState());
+ is(state.windows.length, initialWindows,
+ "The new window should have been removed from the state");
+
+ // Wait for our window to go away
+ yield windowClosed;
+});
diff --git a/browser/components/sessionstore/test/browser_attributes.js b/browser/components/sessionstore/test/browser_attributes.js
new file mode 100644
index 000000000..40c7b4e02
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_attributes.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test makes sure that we correctly preserve tab attributes when storing
+ * and restoring tabs. It also ensures that we skip special attributes like
+ * 'image', 'muted' and 'pending' that need to be handled differently or internally.
+ */
+
+const PREF = "browser.sessionstore.restore_on_demand";
+
+add_task(function* test() {
+ Services.prefs.setBoolPref(PREF, true)
+ registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
+ // Add a new tab with a nice icon.
+ let tab = gBrowser.addTab("about:robots");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ // Check that the tab has 'image' and 'iconLoadingPrincipal' attributes.
+ ok(tab.hasAttribute("image"), "tab.image exists");
+ ok(tab.hasAttribute("iconLoadingPrincipal"), "tab.iconLoadingPrincipal exists");
+
+ tab.toggleMuteAudio();
+ // Check that the tab has a 'muted' attribute.
+ ok(tab.hasAttribute("muted"), "tab.muted exists");
+
+ // Make sure we do not persist 'image' or 'muted' attributes.
+ ss.persistTabAttribute("image");
+ ss.persistTabAttribute("muted");
+ ss.persistTabAttribute("iconLoadingPrincipal");
+ let {attributes} = JSON.parse(ss.getTabState(tab));
+ ok(!("image" in attributes), "'image' attribute not saved");
+ ok(!("iconLoadingPrincipal" in attributes), "'iconLoadingPrincipal' attribute not saved");
+ ok(!("muted" in attributes), "'muted' attribute not saved");
+ ok(!("custom" in attributes), "'custom' attribute not saved");
+
+ // Test persisting a custom attribute.
+ tab.setAttribute("custom", "foobar");
+ ss.persistTabAttribute("custom");
+
+ ({attributes} = JSON.parse(ss.getTabState(tab)));
+ is(attributes.custom, "foobar", "'custom' attribute is correct");
+
+ // Make sure we're backwards compatible and restore old 'image' attributes.
+ let state = {
+ entries: [{url: "about:mozilla"}],
+ attributes: {custom: "foobaz"},
+ image: gBrowser.getIcon(tab)
+ };
+
+ // Prepare a pending tab waiting to be restored.
+ let promise = promiseTabRestoring(tab);
+ ss.setTabState(tab, JSON.stringify(state));
+ yield promise;
+
+ ok(tab.hasAttribute("pending"), "tab is pending");
+ is(gBrowser.getIcon(tab), state.image, "tab has correct icon");
+ ok(!state.attributes.image, "'image' attribute not saved");
+
+ // Let the pending tab load.
+ gBrowser.selectedTab = tab;
+ yield promiseTabRestored(tab);
+
+ // Ensure no 'image' or 'pending' attributes are stored.
+ ({attributes} = JSON.parse(ss.getTabState(tab)));
+ ok(!("image" in attributes), "'image' attribute not saved");
+ ok(!("pending" in attributes), "'pending' attribute not saved");
+ is(attributes.custom, "foobaz", "'custom' attribute is correct");
+
+ // Clean up.
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_background_tab_crash.js b/browser/components/sessionstore/test/browser_background_tab_crash.js
new file mode 100644
index 000000000..e804b177e
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_background_tab_crash.js
@@ -0,0 +1,221 @@
+"use strict";
+
+/**
+ * These tests the behaviour of the browser when background tabs crash,
+ * while the foreground tab remains.
+ *
+ * The current behavioural rule is this: if only background tabs crash,
+ * then only the first tab shown of that group should show the tab crash
+ * page, and subsequent ones should restore on demand.
+ */
+
+/**
+ * Makes the current browser tab non-remote, and then sets up two remote
+ * background tabs, ensuring that both belong to the same content process.
+ * Callers should pass in a testing function that will execute (and possibly
+ * yield Promises) taking the created background tabs as arguments. Once
+ * the testing function completes, this function will take care of closing
+ * the opened tabs.
+ *
+ * @param testFn (function)
+ * A Promise-generating function that will be called once the tabs
+ * are opened and ready.
+ * @return Promise
+ * Resolves once the testing function completes and the opened tabs
+ * have been completely closed.
+ */
+function* setupBackgroundTabs(testFn) {
+ const REMOTE_PAGE = "http://www.example.com";
+ const NON_REMOTE_PAGE = "about:robots";
+
+ // Browse the initial tab to a non-remote page, which we'll have in the
+ // foreground.
+ let initialTab = gBrowser.selectedTab;
+ let initialBrowser = initialTab.linkedBrowser;
+ initialBrowser.loadURI(NON_REMOTE_PAGE);
+ yield BrowserTestUtils.browserLoaded(initialBrowser);
+
+ // Open some tabs that should be running in the content process.
+ let tab1 =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, REMOTE_PAGE);
+ let remoteBrowser1 = tab1.linkedBrowser;
+ yield TabStateFlusher.flush(remoteBrowser1);
+
+ let tab2 =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, REMOTE_PAGE);
+ let remoteBrowser2 = tab2.linkedBrowser;
+ yield TabStateFlusher.flush(remoteBrowser2);
+
+ // Quick sanity check - the two browsers should be remote and share the
+ // same childID, or else this test is not going to work.
+ Assert.ok(remoteBrowser1.isRemoteBrowser,
+ "Browser should be remote in order to crash.");
+ Assert.ok(remoteBrowser2.isRemoteBrowser,
+ "Browser should be remote in order to crash.");
+ Assert.equal(remoteBrowser1.frameLoader.childID,
+ remoteBrowser2.frameLoader.childID,
+ "Both remote browsers should share the same content process.");
+
+ // Now switch back to the non-remote browser...
+ yield BrowserTestUtils.switchTab(gBrowser, initialTab);
+
+ yield testFn([tab1, tab2]);
+
+ yield BrowserTestUtils.removeTab(tab1);
+ yield BrowserTestUtils.removeTab(tab2);
+}
+
+/**
+ * Takes some set of background tabs that are assumed to all belong to
+ * the same content process, and crashes them.
+ *
+ * @param tabs (Array(<xul:tab>))
+ * The tabs to crash.
+ * @return Promise
+ * Resolves once the tabs have crashed and entered the pending
+ * background state.
+ */
+function* crashBackgroundTabs(tabs) {
+ Assert.ok(tabs.length > 0, "Need to crash at least one tab.");
+ for (let tab of tabs) {
+ Assert.ok(tab.linkedBrowser.isRemoteBrowser, "tab is remote");
+ }
+
+ let remotenessChangePromises = tabs.map((t) => {
+ return BrowserTestUtils.waitForEvent(t, "TabRemotenessChange");
+ });
+
+ let tabsRevived = tabs.map((t) => {
+ return promiseTabRestoring(t);
+ });
+
+ yield BrowserTestUtils.crashBrowser(tabs[0].linkedBrowser, false);
+ yield Promise.all(remotenessChangePromises);
+ yield Promise.all(tabsRevived);
+
+ // Both background tabs should now be in the pending restore
+ // state.
+ for (let tab of tabs) {
+ Assert.ok(!tab.linkedBrowser.isRemoteBrowser, "tab is not remote");
+ Assert.ok(!tab.linkedBrowser.hasAttribute("crashed"), "tab is not crashed");
+ Assert.ok(tab.linkedBrowser.hasAttribute("pending"), "tab is pending");
+ }
+}
+
+add_task(function* setup() {
+ // We'll simplify by making sure we only ever one content process for this
+ // test.
+ yield SpecialPowers.pushPrefEnv({ set: [[ "dom.ipc.processCount", 1 ]] });
+
+ // On debug builds, crashing tabs results in much thinking, which
+ // slows down the test and results in intermittent test timeouts,
+ // so we'll pump up the expected timeout for this test.
+ requestLongerTimeout(5);
+});
+
+/**
+ * Tests that if a content process crashes taking down only
+ * background tabs, then the first of those tabs that the user
+ * selects will show the tab crash page, but the rest will restore
+ * on demand.
+ */
+add_task(function* test_background_crash_simple() {
+ yield setupBackgroundTabs(function*([tab1, tab2]) {
+ // Let's crash one of those background tabs now...
+ yield crashBackgroundTabs([tab1, tab2]);
+
+ // Selecting the first tab should now send it to the tab crashed page.
+ let tabCrashedPagePromise =
+ BrowserTestUtils.waitForContentEvent(tab1.linkedBrowser,
+ "AboutTabCrashedReady",
+ false, null, true);
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ yield tabCrashedPagePromise;
+
+ // Selecting the second tab should restore it.
+ let tabRestored = promiseTabRestored(tab2);
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ yield tabRestored;
+ });
+});
+
+/**
+ * Tests that if a content process crashes taking down only
+ * background tabs, and the user is configured to send backlogged
+ * crash reports automatically, that the tab crashed page is not
+ * shown.
+ */
+add_task(function* test_background_crash_autosubmit_backlogged() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["browser.crashReports.unsubmittedCheck.autoSubmit2", true]],
+ });
+
+ yield setupBackgroundTabs(function*([tab1, tab2]) {
+ // Let's crash one of those background tabs now...
+ yield crashBackgroundTabs([tab1, tab2]);
+
+ // Selecting the first tab should restore it.
+ let tabRestored = promiseTabRestored(tab1);
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ yield tabRestored;
+
+ // Selecting the second tab should restore it.
+ tabRestored = promiseTabRestored(tab2);
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ yield tabRestored;
+ });
+
+ yield SpecialPowers.popPrefEnv();
+});
+
+/**
+ * Tests that if there are two background tab crashes in a row, that
+ * the two sets of background crashes don't interfere with one another.
+ *
+ * Specifically, if we start with two background tabs (1, 2) which crash,
+ * and we visit 1, 1 should go to the tab crashed page. If we then have
+ * two new background tabs (3, 4) crash, visiting 2 should still restore.
+ * Visiting 4 should show us the tab crashed page, and then visiting 3
+ * should restore.
+ */
+add_task(function* test_background_crash_multiple() {
+ let initialTab = gBrowser.selectedTab;
+
+ yield setupBackgroundTabs(function*([tab1, tab2]) {
+ // Let's crash one of those background tabs now...
+ yield crashBackgroundTabs([tab1, tab2]);
+
+ // Selecting the first tab should now send it to the tab crashed page.
+ let tabCrashedPagePromise =
+ BrowserTestUtils.waitForContentEvent(tab1.linkedBrowser,
+ "AboutTabCrashedReady",
+ false, null, true);
+ yield BrowserTestUtils.switchTab(gBrowser, tab1);
+ yield tabCrashedPagePromise;
+
+ // Now switch back to the original non-remote tab...
+ yield BrowserTestUtils.switchTab(gBrowser, initialTab);
+
+ yield setupBackgroundTabs(function*([tab3, tab4]) {
+ yield crashBackgroundTabs([tab3, tab4]);
+
+ // Selecting the second tab should restore it.
+ let tabRestored = promiseTabRestored(tab2);
+ yield BrowserTestUtils.switchTab(gBrowser, tab2);
+ yield tabRestored;
+
+ // Selecting the fourth tab should now send it to the tab crashed page.
+ let tabCrashedPagePromise =
+ BrowserTestUtils.waitForContentEvent(tab4.linkedBrowser,
+ "AboutTabCrashedReady",
+ false, null, true);
+ yield BrowserTestUtils.switchTab(gBrowser, tab4);
+ yield tabCrashedPagePromise;
+
+ // Selecting the third tab should restore it.
+ tabRestored = promiseTabRestored(tab3);
+ yield BrowserTestUtils.switchTab(gBrowser, tab3);
+ yield tabRestored;
+ });
+ });
+});
diff --git a/browser/components/sessionstore/test/browser_backup_recovery.js b/browser/components/sessionstore/test/browser_backup_recovery.js
new file mode 100644
index 000000000..81f678856
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_backup_recovery.js
@@ -0,0 +1,206 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This tests are for a sessionstore.js atomic backup.
+// Each test will wait for a write to the Session Store
+// before executing.
+
+var OS = Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
+var {File, Constants, Path} = OS;
+
+const PREF_SS_INTERVAL = "browser.sessionstore.interval";
+const Paths = SessionFile.Paths;
+
+// A text decoder.
+var gDecoder = new TextDecoder();
+// Global variables that contain sessionstore.js and sessionstore.bak data for
+// comparison between tests.
+var gSSData;
+var gSSBakData;
+
+function promiseRead(path) {
+ return File.read(path, {encoding: "utf-8"});
+}
+
+add_task(function* init() {
+ // Make sure that we are not racing with SessionSaver's time based
+ // saves.
+ Services.prefs.setIntPref(PREF_SS_INTERVAL, 10000000);
+ registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_SS_INTERVAL));
+});
+
+add_task(function* test_creation() {
+ // Create dummy sessionstore backups
+ let OLD_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak");
+ let OLD_UPGRADE_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.bak-0000000");
+
+ yield File.writeAtomic(OLD_BACKUP, "sessionstore.bak");
+ yield File.writeAtomic(OLD_UPGRADE_BACKUP, "sessionstore upgrade backup");
+
+ yield SessionFile.wipe();
+ yield SessionFile.read(); // Reinitializes SessionFile
+
+ // Ensure none of the sessionstore files and backups exists
+ for (let k of Paths.loadOrder) {
+ ok(!(yield File.exists(Paths[k])), "After wipe " + k + " sessionstore file doesn't exist");
+ }
+ ok(!(yield File.exists(OLD_BACKUP)), "After wipe, old backup doesn't exist");
+ ok(!(yield File.exists(OLD_UPGRADE_BACKUP)), "After wipe, old upgrade backup doesn't exist");
+
+ // Open a new tab, save session, ensure that the correct files exist.
+ let URL_BASE = "http://example.com/?atomic_backup_test_creation=" + Math.random();
+ let URL = URL_BASE + "?first_write";
+ let tab = gBrowser.addTab(URL);
+
+ info("Testing situation after a single write");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ yield SessionSaver.run();
+
+ ok((yield File.exists(Paths.recovery)), "After write, recovery sessionstore file exists again");
+ ok(!(yield File.exists(Paths.recoveryBackup)), "After write, recoveryBackup sessionstore doesn't exist");
+ ok((yield promiseRead(Paths.recovery)).indexOf(URL) != -1, "Recovery sessionstore file contains the required tab");
+ ok(!(yield File.exists(Paths.clean)), "After first write, clean shutdown sessionstore doesn't exist, since we haven't shutdown yet");
+
+ // Open a second tab, save session, ensure that the correct files exist.
+ info("Testing situation after a second write");
+ let URL2 = URL_BASE + "?second_write";
+ tab.linkedBrowser.loadURI(URL2);
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ yield SessionSaver.run();
+
+ ok((yield File.exists(Paths.recovery)), "After second write, recovery sessionstore file still exists");
+ ok((yield promiseRead(Paths.recovery)).indexOf(URL2) != -1, "Recovery sessionstore file contains the latest url");
+ ok((yield File.exists(Paths.recoveryBackup)), "After write, recoveryBackup sessionstore now exists");
+ let backup = yield promiseRead(Paths.recoveryBackup);
+ ok(backup.indexOf(URL2) == -1, "Recovery backup doesn't contain the latest url");
+ ok(backup.indexOf(URL) != -1, "Recovery backup contains the original url");
+ ok(!(yield File.exists(Paths.clean)), "After first write, clean shutdown sessinstore doesn't exist, since we haven't shutdown yet");
+
+ info("Reinitialize, ensure that we haven't leaked sensitive files");
+ yield SessionFile.read(); // Reinitializes SessionFile
+ yield SessionSaver.run();
+ ok(!(yield File.exists(Paths.clean)), "After second write, clean shutdown sessonstore doesn't exist, since we haven't shutdown yet");
+ ok(!(yield File.exists(Paths.upgradeBackup)), "After second write, clean shutdwn sessionstore doesn't exist, since we haven't shutdown yet");
+ ok(!(yield File.exists(Paths.nextUpgradeBackup)), "After second write, clean sutdown sessionstore doesn't exist, since we haven't shutdown yet");
+
+ gBrowser.removeTab(tab);
+ yield SessionFile.wipe();
+});
+
+var promiseSource = Task.async(function*(name) {
+ let URL = "http://example.com/?atomic_backup_test_recovery=" + Math.random() + "&name=" + name;
+ let tab = gBrowser.addTab(URL);
+
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ yield SessionSaver.run();
+ gBrowser.removeTab(tab);
+
+ let SOURCE = yield promiseRead(Paths.recovery);
+ yield SessionFile.wipe();
+ return SOURCE;
+});
+
+add_task(function* test_recovery() {
+ // Remove all files.
+ yield SessionFile.wipe();
+ info("Attempting to recover from the recovery file");
+
+ // Create Paths.recovery, ensure that we can recover from it.
+ let SOURCE = yield promiseSource("Paths.recovery");
+ yield File.makeDir(Paths.backups);
+ yield File.writeAtomic(Paths.recovery, SOURCE);
+ is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
+ yield SessionFile.wipe();
+
+ info("Corrupting recovery file, attempting to recover from recovery backup");
+ SOURCE = yield promiseSource("Paths.recoveryBackup");
+ yield File.makeDir(Paths.backups);
+ yield File.writeAtomic(Paths.recoveryBackup, SOURCE);
+ yield File.writeAtomic(Paths.recovery, "<Invalid JSON>");
+ is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
+ yield SessionFile.wipe();
+});
+
+add_task(function* test_recovery_inaccessible() {
+ // Can't do chmod() on non-UNIX platforms, we need that for this test.
+ if (AppConstants.platform != "macosx" && AppConstants.platform != "linux") {
+ return;
+ }
+
+ info("Making recovery file inaccessible, attempting to recover from recovery backup");
+ let SOURCE_RECOVERY = yield promiseSource("Paths.recovery");
+ let SOURCE = yield promiseSource("Paths.recoveryBackup");
+ yield File.makeDir(Paths.backups);
+ yield File.writeAtomic(Paths.recoveryBackup, SOURCE);
+
+ // Write a valid recovery file but make it inaccessible.
+ yield File.writeAtomic(Paths.recovery, SOURCE_RECOVERY);
+ yield File.setPermissions(Paths.recovery, { unixMode: 0 });
+
+ is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the recovery file");
+ yield File.setPermissions(Paths.recovery, { unixMode: 0o644 });
+});
+
+add_task(function* test_clean() {
+ yield SessionFile.wipe();
+ let SOURCE = yield promiseSource("Paths.clean");
+ yield File.writeAtomic(Paths.clean, SOURCE);
+ yield SessionFile.read();
+ yield SessionSaver.run();
+ is((yield promiseRead(Paths.cleanBackup)), SOURCE, "After first read/write, clean shutdown file has been moved to cleanBackup");
+});
+
+
+/**
+ * Tests loading of sessionstore when format version is known.
+ */
+add_task(function* test_version() {
+ info("Preparing sessionstore");
+ let SOURCE = yield promiseSource("Paths.clean");
+
+ // Check there's a format version number
+ is(JSON.parse(SOURCE).version[0], "sessionrestore", "Found sessionstore format version");
+
+ // Create Paths.clean file
+ yield File.makeDir(Paths.backups);
+ yield File.writeAtomic(Paths.clean, SOURCE);
+
+ info("Attempting to recover from the clean file");
+ // Ensure that we can recover from Paths.recovery
+ is((yield SessionFile.read()).source, SOURCE, "Recovered the correct source from the clean file");
+});
+
+/**
+ * Tests fallback to previous backups if format version is unknown.
+ */
+add_task(function* test_version_fallback() {
+ info("Preparing data, making sure that it has a version number");
+ let SOURCE = yield promiseSource("Paths.clean");
+ let BACKUP_SOURCE = yield promiseSource("Paths.cleanBackup");
+
+ is(JSON.parse(SOURCE).version[0], "sessionrestore", "Found sessionstore format version");
+ is(JSON.parse(BACKUP_SOURCE).version[0], "sessionrestore", "Found backup sessionstore format version");
+
+ yield File.makeDir(Paths.backups);
+
+ info("Modifying format version number to something incorrect, to make sure that we disregard the file.");
+ let parsedSource = JSON.parse(SOURCE);
+ parsedSource.version[0] = "bookmarks";
+ yield File.writeAtomic(Paths.clean, JSON.stringify(parsedSource));
+ yield File.writeAtomic(Paths.cleanBackup, BACKUP_SOURCE);
+ is((yield SessionFile.read()).source, BACKUP_SOURCE, "Recovered the correct source from the backup recovery file");
+
+ info("Modifying format version number to a future version, to make sure that we disregard the file.");
+ parsedSource = JSON.parse(SOURCE);
+ parsedSource.version[1] = Number.MAX_SAFE_INTEGER;
+ yield File.writeAtomic(Paths.clean, JSON.stringify(parsedSource));
+ yield File.writeAtomic(Paths.cleanBackup, BACKUP_SOURCE);
+ is((yield SessionFile.read()).source, BACKUP_SOURCE, "Recovered the correct source from the backup recovery file");
+});
+
+add_task(function* cleanup() {
+ yield SessionFile.wipe();
+});
diff --git a/browser/components/sessionstore/test/browser_broadcast.js b/browser/components/sessionstore/test/browser_broadcast.js
new file mode 100644
index 000000000..95984d6d0
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_broadcast.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const INITIAL_VALUE = "browser_broadcast.js-initial-value-" + Date.now();
+
+/**
+ * This test ensures we won't lose tab data queued in the content script when
+ * closing a tab.
+ */
+add_task(function flush_on_tabclose() {
+ let tab = yield createTabWithStorageData(["http://example.com"]);
+ let browser = tab.linkedBrowser;
+
+ yield modifySessionStorage(browser, {test: "on-tab-close"});
+ yield promiseRemoveTab(tab);
+
+ let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
+ is(storage["http://example.com"].test, "on-tab-close",
+ "sessionStorage data has been flushed on TabClose");
+});
+
+/**
+ * This test ensures we won't lose tab data queued in the content script when
+ * duplicating a tab.
+ */
+add_task(function flush_on_duplicate() {
+ let tab = yield createTabWithStorageData(["http://example.com"]);
+ let browser = tab.linkedBrowser;
+
+ yield modifySessionStorage(browser, {test: "on-duplicate"});
+ let tab2 = ss.duplicateTab(window, tab);
+ yield promiseTabRestored(tab2);
+
+ yield promiseRemoveTab(tab2);
+ let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
+ is(storage["http://example.com"].test, "on-duplicate",
+ "sessionStorage data has been flushed when duplicating tabs");
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * This test ensures we won't lose tab data queued in the content script when
+ * a window is closed.
+ */
+add_task(function flush_on_windowclose() {
+ let win = yield promiseNewWindow();
+ let tab = yield createTabWithStorageData(["http://example.com"], win);
+ let browser = tab.linkedBrowser;
+
+ yield modifySessionStorage(browser, {test: "on-window-close"});
+ yield BrowserTestUtils.closeWindow(win);
+
+ let [{tabs: [_, {storage}]}] = JSON.parse(ss.getClosedWindowData());
+ is(storage["http://example.com"].test, "on-window-close",
+ "sessionStorage data has been flushed when closing a window");
+});
+
+/**
+ * This test ensures that stale tab data is ignored when reusing a tab
+ * (via e.g. setTabState) and does not overwrite the new data.
+ */
+add_task(function flush_on_settabstate() {
+ let tab = yield createTabWithStorageData(["http://example.com"]);
+ let browser = tab.linkedBrowser;
+
+ // Flush to make sure our tab state is up-to-date.
+ yield TabStateFlusher.flush(browser);
+
+ let state = ss.getTabState(tab);
+ yield modifySessionStorage(browser, {test: "on-set-tab-state"});
+
+ // Flush all data contained in the content script but send it using
+ // asynchronous messages.
+ TabStateFlusher.flush(browser);
+
+ yield promiseTabState(tab, state);
+
+ let {storage} = JSON.parse(ss.getTabState(tab));
+ is(storage["http://example.com"].test, INITIAL_VALUE,
+ "sessionStorage data has not been overwritten");
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * This test ensures that we won't lose tab data that has been sent
+ * asynchronously just before closing a tab. Flushing must re-send all data
+ * that hasn't been received by chrome, yet.
+ */
+add_task(function flush_on_tabclose_racy() {
+ let tab = yield createTabWithStorageData(["http://example.com"]);
+ let browser = tab.linkedBrowser;
+
+ // Flush to make sure we start with an empty queue.
+ yield TabStateFlusher.flush(browser);
+
+ yield modifySessionStorage(browser, {test: "on-tab-close-racy"});
+
+ // Flush all data contained in the content script but send it using
+ // asynchronous messages.
+ TabStateFlusher.flush(browser);
+ yield promiseRemoveTab(tab);
+
+ let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
+ is(storage["http://example.com"].test, "on-tab-close-racy",
+ "sessionStorage data has been merged correctly to prevent data loss");
+});
+
+function promiseNewWindow() {
+ let deferred = Promise.defer();
+ whenNewWindowLoaded({private: false}, deferred.resolve);
+ return deferred.promise;
+}
+
+function createTabWithStorageData(urls, win = window) {
+ return Task.spawn(function task() {
+ let tab = win.gBrowser.addTab();
+ let browser = tab.linkedBrowser;
+
+ for (let url of urls) {
+ browser.loadURI(url);
+ yield promiseBrowserLoaded(browser);
+ yield modifySessionStorage(browser, {test: INITIAL_VALUE});
+ }
+
+ throw new Task.Result(tab);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_capabilities.js b/browser/components/sessionstore/test/browser_capabilities.js
new file mode 100644
index 000000000..456e41882
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_capabilities.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * These tests ensures that disabling features by flipping nsIDocShell.allow*
+ * properties are (re)stored as disabled. Disallowed features must be
+ * re-enabled when the tab is re-used by another tab restoration.
+ */
+add_task(function docshell_capabilities() {
+ let tab = yield createTab();
+ let browser = tab.linkedBrowser;
+ let docShell = browser.docShell;
+
+ // Get the list of capabilities for docShells.
+ let flags = Object.keys(docShell).filter(k => k.startsWith("allow"));
+
+ // Check that everything is allowed by default for new tabs.
+ let state = JSON.parse(ss.getTabState(tab));
+ ok(!("disallow" in state), "everything allowed by default");
+ ok(flags.every(f => docShell[f]), "all flags set to true");
+
+ // Flip a couple of allow* flags.
+ docShell.allowImages = false;
+ docShell.allowMetaRedirects = false;
+
+ // Now reload the document to ensure that these capabilities
+ // are taken into account.
+ browser.reload();
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to make sure chrome received all data.
+ yield TabStateFlusher.flush(browser);
+
+ // Check that we correctly save disallowed features.
+ let disallowedState = JSON.parse(ss.getTabState(tab));
+ let disallow = new Set(disallowedState.disallow.split(","));
+ ok(disallow.has("Images"), "images not allowed");
+ ok(disallow.has("MetaRedirects"), "meta redirects not allowed");
+ is(disallow.size, 2, "two capabilities disallowed");
+
+ // Reuse the tab to restore a new, clean state into it.
+ yield promiseTabState(tab, {entries: [{url: "about:robots"}]});
+
+ // Flush to make sure chrome received all data.
+ yield TabStateFlusher.flush(browser);
+
+ // After restoring disallowed features must be available again.
+ state = JSON.parse(ss.getTabState(tab));
+ ok(!("disallow" in state), "everything allowed again");
+ ok(flags.every(f => docShell[f]), "all flags set to true");
+
+ // Restore the state with disallowed features.
+ yield promiseTabState(tab, disallowedState);
+
+ // Check that docShell flags are set.
+ ok(!docShell.allowImages, "images not allowed");
+ ok(!docShell.allowMetaRedirects, "meta redirects not allowed");
+
+ // Check that we correctly restored features as disabled.
+ state = JSON.parse(ss.getTabState(tab));
+ disallow = new Set(state.disallow.split(","));
+ ok(disallow.has("Images"), "images not allowed anymore");
+ ok(disallow.has("MetaRedirects"), "meta redirects not allowed anymore");
+ is(disallow.size, 2, "two capabilities disallowed");
+
+ // Clean up after ourselves.
+ gBrowser.removeTab(tab);
+});
+
+function createTab() {
+ let tab = gBrowser.addTab("about:mozilla");
+ let browser = tab.linkedBrowser;
+ return promiseBrowserLoaded(browser).then(() => tab);
+}
diff --git a/browser/components/sessionstore/test/browser_cleaner.js b/browser/components/sessionstore/test/browser_cleaner.js
new file mode 100644
index 000000000..921d7d3e4
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_cleaner.js
@@ -0,0 +1,157 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+/*
+ * This test ensures that Session Restore eventually forgets about
+ * tabs and windows that have been closed a long time ago.
+ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+
+const LONG_TIME_AGO = 1;
+
+const URL_TAB1 = "http://example.com/browser_cleaner.js?newtab1=" + Math.random();
+const URL_TAB2 = "http://example.com/browser_cleaner.js?newtab2=" + Math.random();
+const URL_NEWWIN = "http://example.com/browser_cleaner.js?newwin=" + Math.random();
+
+function isRecent(stamp) {
+ is(typeof stamp, "number", "This is a timestamp");
+ return Date.now() - stamp <= 60000;
+}
+
+function promiseCleanup () {
+ info("Cleaning up browser");
+
+ return promiseBrowserState(getClosedState());
+};
+
+function getClosedState() {
+ return Cu.cloneInto(CLOSED_STATE, {});
+}
+
+var CLOSED_STATE;
+
+add_task(function* init() {
+ forgetClosedWindows();
+ while (ss.getClosedTabCount(window) > 0) {
+ ss.forgetClosedTab(window, 0);
+ }
+});
+
+add_task(function* test_open_and_close() {
+ let newTab1 = gBrowser.addTab(URL_TAB1);
+ yield promiseBrowserLoaded(newTab1.linkedBrowser);
+
+ let newTab2 = gBrowser.addTab(URL_TAB2);
+ yield promiseBrowserLoaded(newTab2.linkedBrowser);
+
+ let newWin = yield promiseNewWindowLoaded();
+ let tab = newWin.gBrowser.addTab(URL_NEWWIN);
+
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ yield TabStateFlusher.flushWindow(window);
+ yield TabStateFlusher.flushWindow(newWin);
+
+ info("1. Making sure that before closing, we don't have closedAt");
+ // For the moment, no "closedAt"
+ let state = JSON.parse(ss.getBrowserState());
+ is(state.windows[0].closedAt || false, false, "1. Main window doesn't have closedAt");
+ is(state.windows[1].closedAt || false, false, "1. Second window doesn't have closedAt");
+ is(state.windows[0].tabs[0].closedAt || false, false, "1. First tab doesn't have closedAt");
+ is(state.windows[0].tabs[1].closedAt || false, false, "1. Second tab doesn't have closedAt");
+
+ info("2. Making sure that after closing, we have closedAt");
+
+ // Now close stuff, this should add closeAt
+ yield BrowserTestUtils.closeWindow(newWin);
+ yield promiseRemoveTab(newTab1);
+ yield promiseRemoveTab(newTab2);
+
+ state = CLOSED_STATE = JSON.parse(ss.getBrowserState());
+
+ is(state.windows[0].closedAt || false, false, "2. Main window doesn't have closedAt");
+ ok(isRecent(state._closedWindows[0].closedAt), "2. Second window was closed recently");
+ ok(isRecent(state.windows[0]._closedTabs[0].closedAt), "2. First tab was closed recently");
+ ok(isRecent(state.windows[0]._closedTabs[1].closedAt), "2. Second tab was closed recently");
+});
+
+
+add_task(function* test_restore() {
+ info("3. Making sure that after restoring, we don't have closedAt");
+ yield promiseBrowserState(CLOSED_STATE);
+
+ let newWin = ss.undoCloseWindow(0);
+ yield promiseDelayedStartupFinished(newWin);
+
+ let newTab2 = ss.undoCloseTab(window, 0);
+ yield promiseTabRestored(newTab2);
+
+ let newTab1 = ss.undoCloseTab(window, 0);
+ yield promiseTabRestored(newTab1);
+
+ let state = JSON.parse(ss.getBrowserState());
+
+ is(state.windows[0].closedAt || false, false, "3. Main window doesn't have closedAt");
+ is(state.windows[1].closedAt || false, false, "3. Second window doesn't have closedAt");
+ is(state.windows[0].tabs[0].closedAt || false, false, "3. First tab doesn't have closedAt");
+ is(state.windows[0].tabs[1].closedAt || false, false, "3. Second tab doesn't have closedAt");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+ gBrowser.removeTab(newTab1);
+ gBrowser.removeTab(newTab2);
+});
+
+
+add_task(function* test_old_data() {
+ info("4. Removing closedAt from the sessionstore, making sure that it is added upon idle-daily");
+
+ let state = getClosedState();
+ delete state._closedWindows[0].closedAt;
+ delete state.windows[0]._closedTabs[0].closedAt;
+ delete state.windows[0]._closedTabs[1].closedAt;
+ yield promiseBrowserState(state);
+
+ info("Sending idle-daily");
+ Services.obs.notifyObservers(null, "idle-daily", "");
+ info("Sent idle-daily");
+
+ state = JSON.parse(ss.getBrowserState());
+ is(state.windows[0].closedAt || false, false, "4. Main window doesn't have closedAt");
+ ok(isRecent(state._closedWindows[0].closedAt), "4. Second window was closed recently");
+ ok(isRecent(state.windows[0]._closedTabs[0].closedAt), "4. First tab was closed recently");
+ ok(isRecent(state.windows[0]._closedTabs[1].closedAt), "4. Second tab was closed recently");
+ yield promiseCleanup();
+});
+
+
+add_task(function* test_cleanup() {
+
+ info("5. Altering closedAt to an old date, making sure that stuff gets collected, eventually");
+ yield promiseCleanup();
+
+ let state = getClosedState();
+ state._closedWindows[0].closedAt = LONG_TIME_AGO;
+ state.windows[0]._closedTabs[0].closedAt = LONG_TIME_AGO;
+ state.windows[0]._closedTabs[1].closedAt = Date.now();
+ let url = state.windows[0]._closedTabs[1].state.entries[0].url;
+
+ yield promiseBrowserState(state);
+
+ info("Sending idle-daily");
+ Services.obs.notifyObservers(null, "idle-daily", "");
+ info("Sent idle-daily");
+
+ state = JSON.parse(ss.getBrowserState());
+ is(state._closedWindows[0], undefined, "5. Second window was forgotten");
+
+ is(state.windows[0]._closedTabs.length, 1, "5. Only one closed tab left");
+ is(state.windows[0]._closedTabs[0].state.entries[0].url, url, "5. The second tab is still here");
+ yield promiseCleanup();
+});
+
diff --git a/browser/components/sessionstore/test/browser_cookies.js b/browser/components/sessionstore/test/browser_cookies.js
new file mode 100644
index 000000000..cc5b41e4b
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_cookies.js
@@ -0,0 +1,173 @@
+"use strict";
+
+const PATH = "/browser/browser/components/sessionstore/test/";
+
+/**
+ * Remove all cookies to start off a clean slate.
+ */
+add_task(function* test_setup() {
+ requestLongerTimeout(2);
+ Services.cookies.removeAll();
+});
+
+/**
+ * Test multiple scenarios with different Set-Cookie header domain= params.
+ */
+add_task(function* test_run() {
+ // Set-Cookie: foobar=random()
+ // The domain of the cookie should be the request domain (www.example.com).
+ // We should collect data only for the request domain, no parent or subdomains.
+ yield testCookieCollection({
+ host: "http://www.example.com",
+ cookieHost: "www.example.com",
+ cookieURIs: ["http://www.example.com" + PATH],
+ noCookieURIs: ["http://example.com/" + PATH]
+ });
+
+ // Set-Cookie: foobar=random()
+ // The domain of the cookie should be the request domain (example.com).
+ // We should collect data only for the request domain, no parent or subdomains.
+ yield testCookieCollection({
+ host: "http://example.com",
+ cookieHost: "example.com",
+ cookieURIs: ["http://example.com" + PATH],
+ noCookieURIs: ["http://www.example.com/" + PATH]
+ });
+
+ // Set-Cookie: foobar=random(); Domain=example.com
+ // The domain of the cookie should be the given one (.example.com).
+ // We should collect data for the given domain and its subdomains.
+ yield testCookieCollection({
+ host: "http://example.com",
+ domain: "example.com",
+ cookieHost: ".example.com",
+ cookieURIs: ["http://example.com" + PATH, "http://www.example.com/" + PATH],
+ noCookieURIs: ["about:robots"]
+ });
+
+ // Set-Cookie: foobar=random(); Domain=.example.com
+ // The domain of the cookie should be the given one (.example.com).
+ // We should collect data for the given domain and its subdomains.
+ yield testCookieCollection({
+ host: "http://example.com",
+ domain: ".example.com",
+ cookieHost: ".example.com",
+ cookieURIs: ["http://example.com" + PATH, "http://www.example.com/" + PATH],
+ noCookieURIs: ["about:robots"]
+ });
+
+ // Set-Cookie: foobar=random(); Domain=www.example.com
+ // The domain of the cookie should be the given one (.www.example.com).
+ // We should collect data for the given domain and its subdomains.
+ yield testCookieCollection({
+ host: "http://www.example.com",
+ domain: "www.example.com",
+ cookieHost: ".www.example.com",
+ cookieURIs: ["http://www.example.com/" + PATH],
+ noCookieURIs: ["http://example.com"]
+ });
+
+ // Set-Cookie: foobar=random(); Domain=.www.example.com
+ // The domain of the cookie should be the given one (.www.example.com).
+ // We should collect data for the given domain and its subdomains.
+ yield testCookieCollection({
+ host: "http://www.example.com",
+ domain: ".www.example.com",
+ cookieHost: ".www.example.com",
+ cookieURIs: ["http://www.example.com/" + PATH],
+ noCookieURIs: ["http://example.com"]
+ });
+});
+
+/**
+ * Generic test function to check sessionstore's cookie collection module with
+ * different cookie domains given in the Set-Cookie header. See above for some
+ * usage examples.
+ */
+var testCookieCollection = Task.async(function (params) {
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+
+ let urlParams = new URLSearchParams();
+ let value = Math.random();
+ urlParams.append("value", value);
+
+ if (params.domain) {
+ urlParams.append("domain", params.domain);
+ }
+
+ // Construct request URI.
+ let uri = `${params.host}${PATH}browser_cookies.sjs?${urlParams}`;
+
+ // Wait for the browser to load and the cookie to be set.
+ // These two events can probably happen in no particular order,
+ // so let's wait for them in parallel.
+ yield Promise.all([
+ waitForNewCookie(),
+ replaceCurrentURI(browser, uri)
+ ]);
+
+ // Check all URIs for which the cookie should be collected.
+ for (let uri of params.cookieURIs || []) {
+ yield replaceCurrentURI(browser, uri);
+
+ // Check the cookie.
+ let cookie = getCookie();
+ is(cookie.host, params.cookieHost, "cookie host is correct");
+ is(cookie.path, PATH, "cookie path is correct");
+ is(cookie.name, "foobar", "cookie name is correct");
+ is(cookie.value, value, "cookie value is correct");
+ }
+
+ // Check all URIs for which the cookie should NOT be collected.
+ for (let uri of params.noCookieURIs || []) {
+ yield replaceCurrentURI(browser, uri);
+
+ // Cookie should be ignored.
+ ok(!getCookie(), "no cookie collected");
+ }
+
+ // Clean up.
+ gBrowser.removeTab(tab);
+ Services.cookies.removeAll();
+});
+
+/**
+ * Replace the current URI of the given browser by loading a new URI. The
+ * browser's session history will be completely replaced. This function ensures
+ * that the parent process has the lastest shistory data before resolving.
+ */
+var replaceCurrentURI = Task.async(function* (browser, uri) {
+ // Replace the tab's current URI with the parent domain.
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
+ browser.loadURIWithFlags(uri, flags);
+ yield promiseBrowserLoaded(browser);
+
+ // Ensure the tab's session history is up-to-date.
+ yield TabStateFlusher.flush(browser);
+});
+
+/**
+ * Waits for a new "*example.com" cookie to be added.
+ */
+function waitForNewCookie() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subj, topic, data) {
+ let cookie = subj.QueryInterface(Ci.nsICookie2);
+ if (data == "added" && cookie.host.endsWith("example.com")) {
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }
+ }, "cookie-changed", false);
+ });
+}
+
+/**
+ * Retrieves the first cookie in the first window from the current sessionstore
+ * state.
+ */
+function getCookie() {
+ let state = JSON.parse(ss.getWindowState(window));
+ let cookies = state.windows[0].cookies || [];
+ return cookies[0] || null;
+}
diff --git a/browser/components/sessionstore/test/browser_cookies.sjs b/browser/components/sessionstore/test/browser_cookies.sjs
new file mode 100644
index 000000000..bffbd66d9
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_cookies.sjs
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(req, resp) {
+ resp.setStatusLine(req.httpVersion, 200);
+
+ let params = new URLSearchParams(req.queryString);
+ let value = params.get("value");
+
+ let domain = "";
+ if (params.has("domain")) {
+ domain = `; Domain=${params.get("domain")}`;
+ }
+
+ resp.setHeader("Set-Cookie", `foobar=${value}${domain}`);
+ resp.write("<meta charset=utf-8>hi");
+}
diff --git a/browser/components/sessionstore/test/browser_crashedTabs.js b/browser/components/sessionstore/test/browser_crashedTabs.js
new file mode 100644
index 000000000..5841d536a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_crashedTabs.js
@@ -0,0 +1,462 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(10);
+
+const PAGE_1 = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const PAGE_2 = "data:text/html,<html><body>Another%20regular,%20everyday,%20normal%20page.";
+
+// Turn off tab animations for testing and use a single content process
+// for these tests since we want to test tabs within the crashing process here.
+add_task(function* test_initialize() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [
+ [ "dom.ipc.processCount", 1 ],
+ [ "browser.tabs.animate", false]
+ ] });
+});
+
+// Allow tabs to restore on demand so we can test pending states
+Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+
+function clickButton(browser, id) {
+ info("Clicking " + id);
+
+ let frame_script = (id) => {
+ let button = content.document.getElementById(id);
+ button.click();
+ };
+
+ let mm = browser.messageManager;
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")('" + id + "');", false);
+}
+
+/**
+ * Checks the documentURI of the root document of a remote browser
+ * to see if it equals URI. Returns a Promise that resolves if
+ * there is a match, and rejects with an error message if they
+ * do not match.
+ *
+ * @param browser
+ * The remote <xul:browser> to check the root document URI in.
+ * @param URI
+ * A string to match the root document URI against.
+ * @return Promise
+ */
+function promiseContentDocumentURIEquals(browser, URI) {
+ return new Promise((resolve, reject) => {
+ let frame_script = () => {
+ sendAsyncMessage("test:documenturi", {
+ uri: content.document.documentURI,
+ });
+ };
+
+ let mm = browser.messageManager;
+ mm.addMessageListener("test:documenturi", function onMessage(message) {
+ mm.removeMessageListener("test:documenturi", onMessage);
+ let contentURI = message.data.uri;
+ if (contentURI == URI) {
+ resolve();
+ } else {
+ reject(`Content has URI ${contentURI} which does not match ${URI}`);
+ }
+ });
+
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
+ });
+}
+
+/**
+ * Checks the window.history.length of the root window of a remote
+ * browser to see if it equals length. Returns a Promise that resolves
+ * if there is a match, and rejects with an error message if they
+ * do not match.
+ *
+ * @param browser
+ * The remote <xul:browser> to check the root window.history.length
+ * @param length
+ * The expected history length
+ * @return Promise
+ */
+function promiseHistoryLength(browser, length) {
+ return new Promise((resolve, reject) => {
+ let frame_script = () => {
+ sendAsyncMessage("test:historylength", {
+ length: content.history.length,
+ });
+ };
+
+ let mm = browser.messageManager;
+ mm.addMessageListener("test:historylength", function onMessage(message) {
+ mm.removeMessageListener("test:historylength", onMessage);
+ let contentLength = message.data.length;
+ if (contentLength == length) {
+ resolve();
+ } else {
+ reject(`Content has window.history.length ${contentLength} which does ` +
+ `not equal expected ${length}`);
+ }
+ });
+
+ mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false);
+ });
+}
+
+/**
+ * Returns a Promise that resolves when a browser has fired the
+ * AboutTabCrashedReady event.
+ *
+ * @param browser
+ * The remote <xul:browser> that will fire the event.
+ * @return Promise
+ */
+function promiseTabCrashedReady(browser) {
+ return new Promise((resolve) => {
+ browser.addEventListener("AboutTabCrashedReady", function ready(e) {
+ browser.removeEventListener("AboutTabCrashedReady", ready, false, true);
+ resolve();
+ }, false, true);
+ });
+}
+
+/**
+ * Checks that if a tab crashes, that information about the tab crashed
+ * page does not get added to the tab history.
+ */
+add_task(function test_crash_page_not_in_history() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ // Check the tab state and make sure the tab crashed page isn't
+ // mentioned.
+ let {entries} = JSON.parse(ss.getTabState(newTab));
+ is(entries.length, 1, "Should have a single history entry");
+ is(entries[0].url, PAGE_1,
+ "Single entry should be the page we visited before crashing");
+
+ gBrowser.removeTab(newTab);
+});
+
+/**
+ * Checks that if a tab crashes, that when we browse away from that page
+ * to a non-blacklisted site (so the browser becomes remote again), that
+ * we record history for that new visit.
+ */
+add_task(function test_revived_history_from_remote() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ // Browse to a new site that will cause the browser to
+ // become remote again.
+ browser.loadURI(PAGE_2);
+ yield promiseTabRestored(newTab);
+ ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore.");
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield TabStateFlusher.flush(browser);
+
+ // Check the tab state and make sure the tab crashed page isn't
+ // mentioned.
+ let {entries} = JSON.parse(ss.getTabState(newTab));
+ is(entries.length, 2, "Should have two history entries");
+ is(entries[0].url, PAGE_1,
+ "First entry should be the page we visited before crashing");
+ is(entries[1].url, PAGE_2,
+ "Second entry should be the page we visited after crashing");
+
+ gBrowser.removeTab(newTab);
+});
+
+/**
+ * Checks that if a tab crashes, that when we browse away from that page
+ * to a blacklisted site (so the browser stays non-remote), that
+ * we record history for that new visit.
+ */
+add_task(function test_revived_history_from_non_remote() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ // Browse to a new site that will not cause the browser to
+ // become remote again.
+ browser.loadURI("about:mozilla");
+ yield promiseBrowserLoaded(browser);
+ ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore.");
+ ok(!browser.isRemoteBrowser, "Should not be a remote browser");
+ yield TabStateFlusher.flush(browser);
+
+ // Check the tab state and make sure the tab crashed page isn't
+ // mentioned.
+ let {entries} = JSON.parse(ss.getTabState(newTab));
+ is(entries.length, 2, "Should have two history entries");
+ is(entries[0].url, PAGE_1,
+ "First entry should be the page we visited before crashing");
+ is(entries[1].url, "about:mozilla",
+ "Second entry should be the page we visited after crashing");
+
+ gBrowser.removeTab(newTab);
+});
+
+/**
+ * Checks that we can revive a crashed tab back to the page that
+ * it was on when it crashed.
+ */
+add_task(function test_revive_tab_from_session_store() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+
+ let newTab2 = gBrowser.addTab();
+ let browser2 = newTab2.linkedBrowser;
+ ok(browser2.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser2);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_2);
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+ // Background tabs should not be crashed, but should be in the "to be restored"
+ // state.
+ ok(!newTab2.hasAttribute("crashed"), "Second tab should not be crashed.");
+ ok(newTab2.hasAttribute("pending"), "Second tab should be pending.");
+
+ // Use SessionStore to revive the first tab
+ clickButton(browser, "restoreTab");
+ yield promiseTabRestored(newTab);
+ ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore.");
+ ok(newTab2.hasAttribute("pending"), "Second tab should still be pending.");
+
+ // We can't just check browser.currentURI.spec, because from
+ // the outside, a crashed tab has the same URI as the page
+ // it crashed on (much like an about:neterror page). Instead,
+ // we have to use the documentURI on the content.
+ yield promiseContentDocumentURIEquals(browser, PAGE_2);
+
+ // We should also have two entries in the browser history.
+ yield promiseHistoryLength(browser, 2);
+
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(newTab2);
+});
+
+/**
+ * Checks that we can revive multiple crashed tabs back to the pages
+ * that they were on when they crashed.
+ */
+add_task(function test_revive_all_tabs_from_session_store() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+
+ // In order to see a second about:tabcrashed page, we'll need
+ // a second window, since only selected tabs will show
+ // about:tabcrashed.
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+ let newTab2 = win2.gBrowser.addTab(PAGE_1);
+ win2.gBrowser.selectedTab = newTab2;
+ let browser2 = newTab2.linkedBrowser;
+ ok(browser2.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser2);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_2);
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+ yield TabStateFlusher.flush(browser2);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+ // Both tabs should now be crashed.
+ is(newTab.getAttribute("crashed"), "true", "First tab should be crashed");
+ is(newTab2.getAttribute("crashed"), "true", "Second window tab should be crashed");
+
+ // Use SessionStore to revive all the tabs
+ clickButton(browser, "restoreAll");
+ yield promiseTabRestored(newTab);
+ ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore.");
+ ok(!newTab.hasAttribute("pending"), "Tab shouldn't be pending.");
+ ok(!newTab2.hasAttribute("crashed"), "Second tab shouldn't be marked as crashed anymore.");
+ ok(!newTab2.hasAttribute("pending"), "Second tab shouldn't be pending.");
+
+ // We can't just check browser.currentURI.spec, because from
+ // the outside, a crashed tab has the same URI as the page
+ // it crashed on (much like an about:neterror page). Instead,
+ // we have to use the documentURI on the content.
+ yield promiseContentDocumentURIEquals(browser, PAGE_2);
+ yield promiseContentDocumentURIEquals(browser2, PAGE_1);
+
+ // We should also have two entries in the browser history.
+ yield promiseHistoryLength(browser, 2);
+
+ yield BrowserTestUtils.closeWindow(win2);
+ gBrowser.removeTab(newTab);
+});
+
+/**
+ * Checks that about:tabcrashed can close the current tab
+ */
+add_task(function test_close_tab_after_crash() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let promise = promiseEvent(gBrowser.tabContainer, "TabClose");
+
+ // Click the close tab button
+ clickButton(browser, "closeTab");
+ yield promise;
+
+ is(gBrowser.tabs.length, 1, "Should have closed the tab");
+});
+
+
+/**
+ * Checks that "restore all" button is only shown if more than one tab
+ * is showing about:tabcrashed
+ */
+add_task(function* test_hide_restore_all_button() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ let doc = browser.contentDocument;
+ let restoreAllButton = doc.getElementById("restoreAll");
+ let restoreOneButton = doc.getElementById("restoreTab");
+
+ let restoreAllStyles = window.getComputedStyle(restoreAllButton);
+ is(restoreAllStyles.display, "none", "Restore All button should be hidden");
+ ok(restoreOneButton.classList.contains("primary"), "Restore Tab button should have the primary class");
+
+ let newTab2 = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+
+ browser.loadURI(PAGE_2);
+ yield promiseBrowserLoaded(browser);
+
+ // Load up a second window so we can get another tab to show
+ // about:tabcrashed
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+ let newTab3 = win2.gBrowser.addTab(PAGE_2);
+ win2.gBrowser.selectedTab = newTab3;
+ let otherWinBrowser = newTab3.linkedBrowser;
+ yield promiseBrowserLoaded(otherWinBrowser);
+ // We'll need to make sure the second tab's browser has finished
+ // sending its AboutTabCrashedReady event before we know for
+ // sure whether or not we're showing the right Restore buttons.
+ let otherBrowserReady = promiseTabCrashedReady(otherWinBrowser);
+
+ // Crash the first tab.
+ yield BrowserTestUtils.crashBrowser(browser);
+ yield otherBrowserReady;
+
+ doc = browser.contentDocument;
+ restoreAllButton = doc.getElementById("restoreAll");
+ restoreOneButton = doc.getElementById("restoreTab");
+
+ restoreAllStyles = window.getComputedStyle(restoreAllButton);
+ isnot(restoreAllStyles.display, "none", "Restore All button should not be hidden");
+ ok(!(restoreOneButton.classList.contains("primary")), "Restore Tab button should not have the primary class");
+
+ yield BrowserTestUtils.closeWindow(win2);
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(newTab2);
+});
+
+add_task(function* test_aboutcrashedtabzoom() {
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ let browser = newTab.linkedBrowser;
+ ok(browser.isRemoteBrowser, "Should be a remote browser");
+ yield promiseBrowserLoaded(browser);
+
+ browser.loadURI(PAGE_1);
+ yield promiseBrowserLoaded(browser);
+
+ FullZoom.enlarge();
+ let zoomLevel = ZoomManager.getZoomForBrowser(browser);
+ ok(zoomLevel !== 1, "should have enlarged");
+
+ yield TabStateFlusher.flush(browser);
+
+ // Crash the tab
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ ok(ZoomManager.getZoomForBrowser(browser) === 1, "zoom should have reset on crash");
+
+ clickButton(browser, "restoreTab");
+ yield promiseTabRestored(newTab);
+
+ ok(ZoomManager.getZoomForBrowser(browser) === zoomLevel, "zoom should have gone back to enlarged");
+ FullZoom.reset();
+
+ gBrowser.removeTab(newTab);
+});
diff --git a/browser/components/sessionstore/test/browser_dying_cache.js b/browser/components/sessionstore/test/browser_dying_cache.js
new file mode 100644
index 000000000..c573aa5d4
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_dying_cache.js
@@ -0,0 +1,66 @@
+"use strict";
+
+/**
+ * This test ensures that after closing a window we keep its state data around
+ * as long as something keeps a reference to it. It should only be possible to
+ * read data after closing - writing should fail.
+ */
+
+add_task(function* test() {
+ // Open a new window.
+ let win = yield promiseNewWindowLoaded();
+
+ // Load some URL in the current tab.
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
+ win.gBrowser.selectedBrowser.loadURIWithFlags("about:robots", flags);
+ yield promiseBrowserLoaded(win.gBrowser.selectedBrowser);
+
+ // Open a second tab and close the first one.
+ let tab = win.gBrowser.addTab("about:mozilla");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ yield promiseRemoveTab(win.gBrowser.tabs[0]);
+
+ // Make sure our window is still tracked by sessionstore
+ // and the window state is as expected.
+ ok("__SSi" in win, "window is being tracked by sessionstore");
+ ss.setWindowValue(win, "foo", "bar");
+ checkWindowState(win);
+
+ let state = ss.getWindowState(win);
+ let closedTabData = ss.getClosedTabData(win);
+
+ // Close our window.
+ yield BrowserTestUtils.closeWindow(win);
+
+ // SessionStore should no longer track our window
+ // but it should still report the same state.
+ ok(!("__SSi" in win), "sessionstore does no longer track our window");
+ checkWindowState(win);
+
+ // Make sure we're not allowed to modify state data.
+ Assert.throws(() => ss.setWindowState(win, {}),
+ "we're not allowed to modify state data anymore");
+ Assert.throws(() => ss.setWindowValue(win, "foo", "baz"),
+ "we're not allowed to modify state data anymore");
+});
+
+function checkWindowState(window) {
+ let {windows: [{tabs}]} = JSON.parse(ss.getWindowState(window));
+ is(tabs.length, 1, "the window has a single tab");
+ is(tabs[0].entries[0].url, "about:mozilla", "the tab is about:mozilla");
+
+ is(ss.getClosedTabCount(window), 1, "the window has one closed tab");
+ let [{state: {entries: [{url}]}}] = JSON.parse(ss.getClosedTabData(window));
+ is(url, "about:robots", "the closed tab is about:robots");
+
+ is(ss.getWindowValue(window, "foo"), "bar", "correct extData value");
+}
+
+function shouldThrow(f) {
+ try {
+ f();
+ } catch (e) {
+ return true;
+ }
+}
diff --git a/browser/components/sessionstore/test/browser_dynamic_frames.js b/browser/components/sessionstore/test/browser_dynamic_frames.js
new file mode 100644
index 000000000..e4355fee3
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_dynamic_frames.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Ensure that static frames of framesets are serialized but dynamically
+ * inserted iframes are ignored.
+ */
+add_task(function () {
+ // This URL has the following frames:
+ // + data:text/html,A (static)
+ // + data:text/html,B (static)
+ // + data:text/html,C (dynamic iframe)
+ const URL = "data:text/html;charset=utf-8," +
+ "<frameset cols=50%25,50%25><frame src='data:text/html,A'>" +
+ "<frame src='data:text/html,B'></frameset>" +
+ "<script>var i=document.createElement('iframe');" +
+ "i.setAttribute('src', 'data:text/html,C');" +
+ "document.body.appendChild(i);</script>";
+
+ // Add a new tab with two "static" and one "dynamic" frame.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+
+ // Check URLs.
+ ok(entries[0].url.startsWith("data:text/html"), "correct root url");
+ is(entries[0].children[0].url, "data:text/html,A", "correct url for 1st frame");
+ is(entries[0].children[1].url, "data:text/html,B", "correct url for 2nd frame");
+
+ // Check the number of children.
+ is(entries.length, 1, "there is one root entry ...");
+ is(entries[0].children.length, 2, "... with two child entries");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that iframes created by the network parser are serialized but
+ * dynamically inserted iframes are ignored. Navigating a subframe should
+ * create a second root entry that doesn't contain any dynamic children either.
+ */
+add_task(function () {
+ // This URL has the following frames:
+ // + data:text/html,A (static)
+ // + data:text/html,C (dynamic iframe)
+ const URL = "data:text/html;charset=utf-8," +
+ "<iframe name=t src='data:text/html,A'></iframe>" +
+ "<a id=lnk href='data:text/html,B' target=t>clickme</a>" +
+ "<script>var i=document.createElement('iframe');" +
+ "i.setAttribute('src', 'data:text/html,C');" +
+ "document.body.appendChild(i);</script>";
+
+ // Add a new tab with one "static" and one "dynamic" frame.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+
+ // Check URLs.
+ ok(entries[0].url.startsWith("data:text/html"), "correct root url");
+ ok(!entries[0].children, "no children collected");
+
+ // Navigate the subframe.
+ browser.messageManager.sendAsyncMessage("ss-test:click", {id: "lnk"});
+ yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
+
+ yield TabStateFlusher.flush(browser);
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+
+ // Check URLs.
+ ok(entries[0].url.startsWith("data:text/html"), "correct 1st root url");
+ ok(entries[1].url.startsWith("data:text/html"), "correct 2nd root url");
+ ok(!entries.children, "no children collected");
+ ok(!entries[0].children, "no children collected");
+ ok(!entries[1].children, "no children collected");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_forget_async_closings.js b/browser/components/sessionstore/test/browser_forget_async_closings.js
new file mode 100644
index 000000000..c130ec5ad
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_forget_async_closings.js
@@ -0,0 +1,144 @@
+"use strict";
+
+const PAGE = "http://example.com/";
+
+/**
+ * Creates a tab in the current window worth storing in the
+ * closedTabs array, and then closes it. Runs a synchronous
+ * forgetFn passed in that should cause us to forget the tab,
+ * and then ensures that after the tab has sent its final
+ * update message that we didn't accidentally store it in
+ * the closedTabs array.
+ *
+ * @param forgetFn (function)
+ * A synchronous function that should cause the tab
+ * to be forgotten.
+ * @returns Promise
+ */
+let forgetTabHelper = Task.async(function*(forgetFn) {
+ // We want to suppress all non-final updates from the browser tabs
+ // so as to eliminate any racy-ness with this test.
+ yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+ // Forget any previous closed tabs from other tests that may have
+ // run in the same session.
+ Services.obs.notifyObservers(null, "browser:purge-session-history", 0);
+
+ is(ss.getClosedTabCount(window), 0,
+ "We should have 0 closed tabs being stored.");
+
+ // Create a tab worth remembering.
+ let tab = gBrowser.addTab(PAGE);
+ let browser = tab.linkedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+ yield TabStateFlusher.flush(browser);
+
+ // Now close the tab, and immediately choose to forget it.
+ let promise = BrowserTestUtils.removeTab(tab);
+
+ // At this point, the tab will have closed, but the final update
+ // to SessionStore hasn't come up yet. Now do the operation that
+ // should cause us to forget the tab.
+ forgetFn();
+
+ is(ss.getClosedTabCount(window), 0, "Should have forgotten the closed tab");
+
+ // Now wait for the final update to come up.
+ yield promise;
+
+ is(ss.getClosedTabCount(window), 0,
+ "Should not have stored the forgotten closed tab");
+});
+
+/**
+ * Creates a new window worth storing in the closeWIndows array,
+ * and then closes it. Runs a synchronous forgetFn passed in that
+ * should cause us to forget the window, and then ensures that after
+ * the window has sent its final update message that we didn't
+ * accidentally store it in the closedWindows array.
+ *
+ * @param forgetFn (function)
+ * A synchronous function that should cause the window
+ * to be forgotten.
+ * @returns Promise
+ */
+let forgetWinHelper = Task.async(function*(forgetFn) {
+ // We want to suppress all non-final updates from the browser tabs
+ // so as to eliminate any racy-ness with this test.
+ yield pushPrefs(["browser.sessionstore.debug.no_auto_updates", true]);
+
+ // Forget any previous closed windows from other tests that may have
+ // run in the same session.
+ Services.obs.notifyObservers(null, "browser:purge-session-history", 0);
+
+ is(ss.getClosedWindowCount(), 0, "We should have 0 closed windows being stored.");
+
+ let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+ // Create a tab worth remembering.
+ let tab = newWin.gBrowser.selectedTab;
+ let browser = tab.linkedBrowser;
+ browser.loadURI(PAGE);
+ yield BrowserTestUtils.browserLoaded(browser, false, PAGE);
+ yield TabStateFlusher.flush(browser);
+
+ // Now close the window and immediately choose to forget it.
+ let windowClosed = BrowserTestUtils.windowClosed(newWin);
+ let domWindowClosed = BrowserTestUtils.domWindowClosed(newWin);
+
+ newWin.close();
+ yield domWindowClosed;
+
+ // At this point, the window will have closed and the onClose handler
+ // has run, but the final update to SessionStore hasn't come up yet.
+ // Now do the oepration that should cause us to forget the window.
+ forgetFn();
+
+ is(ss.getClosedWindowCount(), 0, "Should have forgotten the closed window");
+
+ // Now wait for the final update to come up.
+ yield windowClosed;
+
+ is(ss.getClosedWindowCount(), 0, "Should not have stored the closed window");
+});
+
+/**
+ * Tests that if we choose to forget a tab while waiting for its
+ * final flush to complete, we don't accidentally store it.
+ */
+add_task(function* test_forget_closed_tab() {
+ yield forgetTabHelper(() => {
+ ss.forgetClosedTab(window, 0);
+ });
+});
+
+/**
+ * Tests that if we choose to forget a tab while waiting for its
+ * final flush to complete, we don't accidentally store it.
+ */
+add_task(function* test_forget_closed_window() {
+ yield forgetWinHelper(() => {
+ ss.forgetClosedWindow(0);
+ });
+});
+
+/**
+ * Tests that if we choose to purge history while waiting for a
+ * final flush of a tab to complete, we don't accidentally store it.
+ */
+add_task(function* test_forget_purged_tab() {
+ yield forgetTabHelper(() => {
+ Services.obs.notifyObservers(null, "browser:purge-session-history", 0);
+ });
+});
+
+/**
+ * Tests that if we choose to purge history while waiting for a
+ * final flush of a window to complete, we don't accidentally
+ * store it.
+ */
+add_task(function* test_forget_purged_window() {
+ yield forgetWinHelper(() => {
+ Services.obs.notifyObservers(null, "browser:purge-session-history", 0);
+ });
+});
diff --git a/browser/components/sessionstore/test/browser_form_restore_events.js b/browser/components/sessionstore/test/browser_form_restore_events.js
new file mode 100644
index 000000000..3fc2e0fd4
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_form_restore_events.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = ROOT + "browser_form_restore_events_sample.html";
+
+/**
+ * Originally a test for Bug 476161, but then expanded to include all input
+ * types in bug 640136.
+ */
+add_task(function () {
+ // Load a page with some form elements.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // text fields
+ yield setInputValue(browser, {id: "modify01", value: Math.random()});
+ yield setInputValue(browser, {id: "modify02", value: Date.now()});
+
+ // textareas
+ yield setInputValue(browser, {id: "modify03", value: Math.random()});
+ yield setInputValue(browser, {id: "modify04", value: Date.now()});
+
+ // file
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ yield setInputValue(browser, {id: "modify05", value: file.path});
+
+ // select
+ yield setSelectedIndex(browser, {id: "modify06", index: 1});
+ yield setMultipleSelected(browser, {id: "modify07", indices: [0,1,2]});
+
+ // checkbox
+ yield setInputChecked(browser, {id: "modify08", checked: true});
+ yield setInputChecked(browser, {id: "modify09", checked: false});
+
+ // radio
+ yield setInputChecked(browser, {id: "modify10", checked: true});
+ yield setInputChecked(browser, {id: "modify11", checked: true});
+
+ // Duplicate the tab and check that restoring form data yields the expected
+ // input and change events for modified form fields.
+ let tab2 = gBrowser.duplicateTab(tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2);
+
+ let inputFired = yield getTextContent(browser2, {id: "inputFired"});
+ inputFired = inputFired.trim().split().sort().join(" ");
+
+ let changeFired = yield getTextContent(browser2, {id: "changeFired"});
+ changeFired = changeFired.trim().split().sort().join(" ");
+
+ is(inputFired, "modify01 modify02 modify03 modify04 modify05",
+ "input events were only dispatched for modified input, textarea fields");
+
+ is(changeFired, "modify06 modify07 modify08 modify09 modify11",
+ "change events were only dispatched for modified select, checkbox, radio fields");
+
+ // Cleanup.
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_form_restore_events_sample.html b/browser/components/sessionstore/test/browser_form_restore_events_sample.html
new file mode 100644
index 000000000..1d46d4040
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_form_restore_events_sample.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for form restore events (originally bug 476161)</title>
+
+<script>
+
+document.addEventListener("input", function(aEvent) {
+ var inputEl = aEvent.originalTarget;
+ var changedEl = document.getElementById("inputFired");
+ changedEl.textContent += " " + inputEl.id;
+}, false);
+
+document.addEventListener("change", function(aEvent) {
+ var inputEl = aEvent.originalTarget;
+ var changedEl = document.getElementById("changeFired");
+ changedEl.textContent += " " + inputEl.id;
+}, false);
+
+</script>
+
+<!-- input events -->
+<h3>Text fields with changed text</h3>
+<input type="text" id="modify1">
+<input type="text" id="modify2" value="preset value">
+<input type="text" id="modify01">
+<input type="text" id="modify02" value="preset value">
+
+<h3>Text fields with unchanged text</h3>
+<input type="text" id="unchanged1">
+<input type="text" id="unchanged2" value="preset value">
+<input type="text" id="unchanged01">
+<input type="text" id="unchanged02" value="preset value">
+
+<h3>Textarea with changed text</h3>
+<textarea id="modify03"></textarea>
+<textarea id="modify04">preset value</textarea>
+
+<h3>Textarea with unchanged text</h3>
+<textarea id="unchanged03"></textarea>
+<textarea id="unchanged04">preset value</textarea>
+
+<h3>file field with changed value</h3>
+<input type="file" id="modify05">
+
+<h3>file field with unchanged value</h3>
+<input type="file" id="unchanged05">
+
+<!-- change events -->
+
+<h3>Select menu with changed selection</h3>
+<select id="modify06">
+ <option value="one">one</option>
+ <option value="two">two</option>
+ <option value="three">three</option>
+</select>
+
+<h3>Select menu with unchanged selection (change event still fires)</h3>
+<select id="unchanged06">
+ <option value="one">one</option>
+ <option value="two" selected>two</option>
+ <option value="three">three</option>
+</select>
+
+<h3>Multiple Select menu with changed selection</h3>
+<select id="modify07" multiple>
+ <option value="one">one</option>
+ <option value="two" selected>two</option>
+ <option value="three">three</option>
+</select>
+
+<h3>Select menu with unchanged selection</h3>
+<select id="unchanged07" multiple>
+ <option value="one">one</option>
+ <option value="two" selected>two</option>
+ <option value="three" selected>three</option>
+</select>
+
+<h3>checkbox with changed value</h3>
+<input type="checkbox" id="modify08">
+<input type="checkbox" id="modify09" checked>
+
+<h3>checkbox with unchanged value</h3>
+<input type="checkbox" id="unchanged08">
+<input type="checkbox" id="unchanged09" checked>
+
+<h3>radio with changed value</h3>
+<input type="radio" id="modify10" name="group">Radio 1</input>
+<input type="radio" id="modify11" name="group">Radio 2</input>
+<input type="radio" id="modify12" name="group" checked>Radio 3</input>
+
+<h3>radio with unchanged value</h3>
+<input type="radio" id="unchanged10" name="group2">Radio 4</input>
+<input type="radio" id="unchanged11" name="group2">Radio 5</input>
+<input type="radio" id="unchanged12" name="group2" checked>Radio 6</input>
+
+<h3>Changed field IDs</h3>
+<div id="changed"></div>
+<div id="inputFired"></div>
+<div id="changeFired"></div>
diff --git a/browser/components/sessionstore/test/browser_formdata.js b/browser/components/sessionstore/test/browser_formdata.js
new file mode 100644
index 000000000..ce1272888
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_formdata.js
@@ -0,0 +1,194 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+/**
+ * This test ensures that form data collection respects the privacy level as
+ * set by the user.
+ */
+add_task(function test_formdata() {
+ const URL = "http://mochi.test:8888/browser/browser/components/" +
+ "sessionstore/test/browser_formdata_sample.html";
+
+ const OUTER_VALUE = "browser_formdata_" + Math.random();
+ const INNER_VALUE = "browser_formdata_" + Math.random();
+
+ // Creates a tab, loads a page with some form fields,
+ // modifies their values and closes the tab.
+ function createAndRemoveTab() {
+ return Task.spawn(function () {
+ // Create a new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Modify form data.
+ yield setInputValue(browser, {id: "txt", value: OUTER_VALUE});
+ yield setInputValue(browser, {id: "txt", value: INNER_VALUE, frame: 0});
+
+ // Remove the tab.
+ yield promiseRemoveTab(tab);
+ });
+ }
+
+ yield createAndRemoveTab();
+ let [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
+ is(formdata.id.txt, OUTER_VALUE, "outer value is correct");
+ is(formdata.children[0].id.txt, INNER_VALUE, "inner value is correct");
+
+ // Disable saving data for encrypted sites.
+ Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1);
+
+ yield createAndRemoveTab();
+ [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
+ is(formdata.id.txt, OUTER_VALUE, "outer value is correct");
+ ok(!formdata.children, "inner value was *not* stored");
+
+ // Disable saving data for any site.
+ Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
+
+ yield createAndRemoveTab();
+ [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
+ ok(!formdata, "form data has *not* been stored");
+
+ // Restore the default privacy level.
+ Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
+});
+
+/**
+ * This test ensures that a malicious website can't trick us into restoring
+ * form data into a wrong website and that we always check the stored URL
+ * before doing so.
+ */
+add_task(function test_url_check() {
+ const URL = "data:text/html;charset=utf-8,<input%20id=input>";
+ const VALUE = "value-" + Math.random();
+
+ // Create a tab with an iframe containing an input field.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Restore a tab state with a given form data url.
+ function restoreStateWithURL(url) {
+ let state = {entries: [{url: URL}], formdata: {id: {input: VALUE}}};
+
+ if (url) {
+ state.formdata.url = url;
+ }
+
+ return promiseTabState(tab, state).then(() => getInputValue(browser, "input"));
+ }
+
+ // Check that the form value is restored with the correct URL.
+ is((yield restoreStateWithURL(URL)), VALUE, "form data restored");
+
+ // Check that the form value is *not* restored with the wrong URL.
+ is((yield restoreStateWithURL(URL + "?")), "", "form data not restored");
+ is((yield restoreStateWithURL()), "", "form data not restored");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * This test ensures that collecting form data works as expected when having
+ * nested frame sets.
+ */
+add_task(function test_nested() {
+ const URL = "data:text/html;charset=utf-8," +
+ "<iframe src='data:text/html;charset=utf-8," +
+ "<input autofocus=true>'/>";
+
+ const FORM_DATA = {
+ children: [{
+ xpath: {"/xhtml:html/xhtml:body/xhtml:input": "M"},
+ url: "data:text/html;charset=utf-8,<input%20autofocus=true>"
+ }]
+ };
+
+ // Create a tab with an iframe containing an input field.
+ let tab = gBrowser.selectedTab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Modify the input field's value.
+ yield sendMessage(browser, "ss-test:sendKeyEvent", {key: "m", frame: 0});
+
+ // Remove the tab and check that we stored form data correctly.
+ yield promiseRemoveTab(tab);
+ let [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
+ is(JSON.stringify(formdata), JSON.stringify(FORM_DATA),
+ "formdata for iframe stored correctly");
+
+ // Restore the closed tab.
+ tab = ss.undoCloseTab(window, 0);
+ browser = tab.linkedBrowser;
+ yield promiseTabRestored(tab);
+
+ // Check that the input field has the right value.
+ yield TabStateFlusher.flush(browser);
+ ({formdata} = JSON.parse(ss.getTabState(tab)));
+ is(JSON.stringify(formdata), JSON.stringify(FORM_DATA),
+ "formdata for iframe restored correctly");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * This test ensures that collecting form data for documents with
+ * designMode=on works as expected.
+ */
+add_task(function test_design_mode() {
+ const URL = "data:text/html;charset=utf-8,<h1>mozilla</h1>" +
+ "<script>document.designMode='on'</script>";
+
+ // Load a tab with an editable document.
+ let tab = gBrowser.selectedTab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Modify the document content.
+ yield sendMessage(browser, "ss-test:sendKeyEvent", {key: "m"});
+
+ // Close and restore the tab.
+ yield promiseRemoveTab(tab);
+ tab = ss.undoCloseTab(window, 0);
+ browser = tab.linkedBrowser;
+ yield promiseTabRestored(tab);
+
+ // Check that the innerHTML value was restored.
+ let html = yield getInnerHTML(browser);
+ let expected = "<h1>Mmozilla</h1><script>document.designMode='on'</script>";
+ is(html, expected, "editable document has been restored correctly");
+
+ // Close and restore the tab.
+ yield promiseRemoveTab(tab);
+ tab = ss.undoCloseTab(window, 0);
+ browser = tab.linkedBrowser;
+ yield promiseTabRestored(tab);
+
+ // Check that the innerHTML value was restored.
+ html = yield getInnerHTML(browser);
+ expected = "<h1>Mmozilla</h1><script>document.designMode='on'</script>";
+ is(html, expected, "editable document has been restored correctly");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+function getInputValue(browser, id) {
+ return sendMessage(browser, "ss-test:getInputValue", {id: id});
+}
+
+function setInputValue(browser, data) {
+ return sendMessage(browser, "ss-test:setInputValue", data);
+}
+
+function getInnerHTML(browser) {
+ return sendMessage(browser, "ss-test:getInnerHTML", {selector: "body"});
+}
diff --git a/browser/components/sessionstore/test/browser_formdata_cc.js b/browser/components/sessionstore/test/browser_formdata_cc.js
new file mode 100644
index 000000000..6e27ca970
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_formdata_cc.js
@@ -0,0 +1,79 @@
+"use strict";
+
+const URL = "http://mochi.test:8888/browser/browser/components/" +
+ "sessionstore/test/browser_formdata_sample.html";
+
+requestLongerTimeout(3);
+
+/**
+ * This test ensures that credit card numbers in form data will not be
+ * collected, while numbers that don't look like credit card numbers will
+ * still be collected.
+ */
+add_task(function* () {
+ const validCCNumbers = [
+ // 15 digits
+ "930771457288760", "474915027480942",
+ "924894781317325", "714816113937185",
+ "790466087343106", "474320195408363",
+ "219211148122351", "633038472250799",
+ "354236732906484", "095347810189325",
+ // 16 digits
+ "3091269135815020", "5471839082338112",
+ "0580828863575793", "5015290610002932",
+ "9465714503078607", "4302068493801686",
+ "2721398408985465", "6160334316984331",
+ "8643619970075142", "0218246069710785"
+ ];
+
+ const invalidCCNumbers = [
+ // 15 digits
+ "526931005800649", "724952425140686",
+ "379761391174135", "030551436468583",
+ "947377014076746", "254848023655752",
+ "226871580283345", "708025346034339",
+ "917585839076788", "918632588027666",
+ // 16 digits
+ "9946177098017064", "4081194386488872",
+ "3095975979578034", "3662215692222536",
+ "6723210018630429", "4411962856225025",
+ "8276996369036686", "4449796938248871",
+ "3350852696538147", "5011802870046957"
+ ];
+
+ // Creates a tab, loads a page with a form field, sets the value of the
+ // field, and then removes the tab to trigger data collection.
+ function* createAndRemoveTab(formValue) {
+ // Create a new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Set form value.
+ yield setInputValue(browser, formValue);
+
+ // Remove the tab.
+ yield promiseRemoveTab(tab);
+ }
+
+ // Test that valid CC numbers are not collected.
+ for (let number of validCCNumbers) {
+ yield createAndRemoveTab(number);
+ let [{state}] = JSON.parse(ss.getClosedTabData(window));
+ ok(!("formdata" in state), "valid CC numbers are not collected");
+ }
+
+ // Test that non-CC numbers are still collected.
+ for (let number of invalidCCNumbers) {
+ yield createAndRemoveTab(number);
+ let [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
+ is(formdata.id.txt, number,
+ "numbers that are not valid CC numbers are still collected");
+ }
+});
+
+function setInputValue(browser, formValue) {
+ return ContentTask.spawn(browser, formValue, function* (formValue) {
+ content.document.getElementById("txt").setUserInput(formValue);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_formdata_format.js b/browser/components/sessionstore/test/browser_formdata_format.js
new file mode 100644
index 000000000..6a1b5975d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_formdata_format.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+ /** Tests formdata format **/
+ waitForExplicitFinish();
+
+ let formData = [
+ { },
+ // old format
+ { "#input1" : "value0" },
+ { "#input1" : "value1", "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value2" },
+ { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value3" },
+ // new format
+ { id: { "input1" : "value4" } },
+ { id: { "input1" : "value5" }, xpath: {} },
+ { id: { "input1" : "value6" }, xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value7" } },
+ { id: {}, xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value8" } },
+ { xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value9" } },
+ // combinations
+ { "#input1" : "value10", id: { "input1" : "value11" } },
+ { "#input1" : "value12", id: { "input1" : "value13" }, xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value14" } },
+ { "#input1" : "value15", xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value16" } },
+ { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value17", id: { "input1" : "value18" } },
+ { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value19", id: { "input1" : "value20" }, xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value21" } },
+ { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value22", xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value23" } },
+ { "#input1" : "value24", "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value25", id: { "input1" : "value26" } },
+ { "#input1" : "value27", "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value28", id: { "input1" : "value29" }, xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value30" } },
+ { "#input1" : "value31", "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value32", xpath: { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value33" } }
+ ]
+ let expectedValues = [
+ [ "" , "" ],
+ // old format
+ [ "value0", "" ],
+ [ "value1", "value2" ],
+ [ "", "value3" ],
+ // new format
+ [ "value4", "" ],
+ [ "value5", "" ],
+ [ "value6", "value7" ],
+ [ "", "value8" ],
+ [ "", "value9" ],
+ // combinations
+ [ "value11", "" ],
+ [ "value13", "value14" ],
+ [ "", "value16" ],
+ [ "value18", "" ],
+ [ "value20", "value21" ],
+ [ "", "value23" ],
+ [ "value26", "" ],
+ [ "value29", "value30" ],
+ [ "", "value33" ]
+ ];
+ let testTabCount = 0;
+ let callback = function() {
+ testTabCount--;
+ if (testTabCount == 0) {
+ finish();
+ }
+ };
+
+ for (let i = 0; i < formData.length; i++) {
+ testTabCount++;
+ testTabRestoreData(formData[i], expectedValues[i], callback);
+ }
+}
+
+function testTabRestoreData(aFormData, aExpectedValue, aCallback) {
+ let URL = ROOT + "browser_formdata_format_sample.html";
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+
+ aFormData.url = URL;
+ let tabState = { entries: [{ url: URL }], formdata: aFormData };
+
+ Task.spawn(function () {
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield promiseTabState(tab, tabState);
+
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ let restoredTabState = JSON.parse(ss.getTabState(tab));
+ let restoredFormData = restoredTabState.formdata;
+
+ if (restoredFormData) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let input1 = doc.getElementById("input1");
+ let input2 = doc.querySelector("input[name=input2]");
+
+ // test format
+ ok("id" in restoredFormData || "xpath" in restoredFormData,
+ "FormData format is valid: " + restoredFormData);
+ // validate that there are no old keys
+ for (let key of Object.keys(restoredFormData)) {
+ if (["id", "xpath", "url"].indexOf(key) === -1) {
+ ok(false, "FormData format is invalid.");
+ }
+ }
+ // test id
+ is(input1.value, aExpectedValue[0],
+ "FormData by 'id' has been restored correctly");
+ // test xpath
+ is(input2.value, aExpectedValue[1],
+ "FormData by 'xpath' has been restored correctly");
+ }
+
+ // clean up
+ gBrowser.removeTab(tab);
+
+ // This test might time out if the task fails.
+ }).then(aCallback);
+}
diff --git a/browser/components/sessionstore/test/browser_formdata_format_sample.html b/browser/components/sessionstore/test/browser_formdata_format_sample.html
new file mode 100644
index 000000000..f991e3657
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_formdata_format_sample.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<title>Test formdata format</title>
+
+<!-- input events -->
+<h3>Input fields</h3>
+<input type="text" id="input1">
+<input type="text" name="input2"> \ No newline at end of file
diff --git a/browser/components/sessionstore/test/browser_formdata_sample.html b/browser/components/sessionstore/test/browser_formdata_sample.html
new file mode 100644
index 000000000..6cbb54fb5
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_formdata_sample.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>browser_formdata_sample.html</title>
+ </head>
+ <body>
+ <input id="txt" />
+
+ <script type="text/javascript;version=1.8">
+ let isOuter = window == window.top;
+
+ if (isOuter) {
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "https://example.com" + location.pathname);
+ document.body.appendChild(iframe);
+ }
+ </script>
+ </body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_formdata_xpath.js b/browser/components/sessionstore/test/browser_formdata_xpath.js
new file mode 100644
index 000000000..d69feb546
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_formdata_xpath.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = ROOT + "browser_formdata_xpath_sample.html";
+
+/**
+ * Bug 346337 - Generic form data restoration tests.
+ */
+add_task(function setup() {
+ // make sure we don't save form data at all (except for tab duplication)
+ Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
+ });
+});
+
+const FILE1 = createFilePath("346337_test1.file");
+const FILE2 = createFilePath("346337_test2.file");
+
+const FIELDS = {
+ "//input[@name='input']": Date.now().toString(),
+ "//input[@name='spaced 1']": Math.random().toString(),
+ "//input[3]": "three",
+ "//input[@type='checkbox']": true,
+ "//input[@name='uncheck']": false,
+ "//input[@type='radio'][1]": false,
+ "//input[@type='radio'][2]": true,
+ "//input[@type='radio'][3]": false,
+ "//select": 2,
+ "//select[@multiple]": [1, 3],
+ "//textarea[1]": "",
+ "//textarea[2]": "Some text... " + Math.random(),
+ "//textarea[3]": "Some more text\n" + new Date(),
+ "//input[@type='file'][1]": [FILE1],
+ "//input[@type='file'][2]": [FILE1, FILE2]
+};
+
+add_task(function test_form_data_restoration() {
+ // Load page with some input fields.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Fill in some values.
+ for (let xpath of Object.keys(FIELDS)) {
+ yield setFormValue(browser, xpath);
+ }
+
+ // Duplicate the tab.
+ let tab2 = gBrowser.duplicateTab(tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2);
+
+ // Check that all form values have been duplicated.
+ for (let xpath of Object.keys(FIELDS)) {
+ let expected = JSON.stringify(FIELDS[xpath]);
+ let actual = JSON.stringify(yield getFormValue(browser2, xpath));
+ is(actual, expected, "The value for \"" + xpath + "\" was correctly restored");
+ }
+
+ // Remove all tabs.
+ yield promiseRemoveTab(tab2);
+ yield promiseRemoveTab(tab);
+
+ // Restore one of the tabs again.
+ tab = ss.undoCloseTab(window, 0);
+ browser = tab.linkedBrowser;
+ yield promiseTabRestored(tab);
+
+ // Check that none of the form values have been restored due to the privacy
+ // level settings.
+ for (let xpath of Object.keys(FIELDS)) {
+ let expected = FIELDS[xpath];
+ if (expected) {
+ let actual = yield getFormValue(browser, xpath, expected);
+ isnot(actual, expected, "The value for \"" + xpath + "\" was correctly discarded");
+ }
+ }
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+});
+
+function createFilePath(leaf) {
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append(leaf);
+ return file.path;
+}
+
+function isArrayOfNumbers(value) {
+ return Array.isArray(value) && value.every(n => typeof(n) === "number");
+}
+
+function isArrayOfStrings(value) {
+ return Array.isArray(value) && value.every(n => typeof(n) === "string");
+}
+
+function getFormValue(browser, xpath) {
+ let value = FIELDS[xpath];
+
+ if (typeof value == "string") {
+ return getInputValue(browser, {xpath: xpath});
+ }
+
+ if (typeof value == "boolean") {
+ return getInputChecked(browser, {xpath: xpath});
+ }
+
+ if (typeof value == "number") {
+ return getSelectedIndex(browser, {xpath: xpath});
+ }
+
+ if (isArrayOfNumbers(value)) {
+ return getMultipleSelected(browser, {xpath: xpath});
+ }
+
+ if (isArrayOfStrings(value)) {
+ return getFileNameArray(browser, {xpath: xpath});
+ }
+
+ throw new Error("unknown input type");
+}
+
+function setFormValue(browser, xpath) {
+ let value = FIELDS[xpath];
+
+ if (typeof value == "string") {
+ return setInputValue(browser, {xpath: xpath, value: value});
+ }
+
+ if (typeof value == "boolean") {
+ return setInputChecked(browser, {xpath: xpath, checked: value});
+ }
+
+ if (typeof value == "number") {
+ return setSelectedIndex(browser, {xpath: xpath, index: value});
+ }
+
+ if (isArrayOfNumbers(value)) {
+ return setMultipleSelected(browser, {xpath: xpath, indices: value});
+ }
+
+ if (isArrayOfStrings(value)) {
+ return setFileNameArray(browser, {xpath: xpath, names: value});
+ }
+
+ throw new Error("unknown input type");
+}
diff --git a/browser/components/sessionstore/test/browser_formdata_xpath_sample.html b/browser/components/sessionstore/test/browser_formdata_xpath_sample.html
new file mode 100644
index 000000000..682162d6a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_formdata_xpath_sample.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<title>Test for bug 346337</title>
+
+<h3>Text Fields</h3>
+<input type="text" name="input">
+<input type="text" name="spaced 1">
+<input>
+
+<h3>Checkboxes and Radio buttons</h3>
+<input type="checkbox" name="check"> Check 1
+<input type="checkbox" name="uncheck" checked> Check 2
+<p>
+<input type="radio" name="group" value="1"> Radio 1
+<input type="radio" name="group" value="some"> Radio 2
+<input type="radio" name="group" checked> Radio 3
+
+<h3>Selects</h3>
+<select name="any">
+ <option value="1"> Select 1
+ <option value="some"> Select 2
+ <option>Select 3
+</select>
+<select multiple="multiple">
+ <option value=1> Multi-select 1
+ <option value=2> Multi-select 2
+ <option value=3> Multi-select 3
+ <option value=4> Multi-select 4
+</select>
+
+<h3>Text Areas</h3>
+<textarea name="testarea"></textarea>
+<textarea name="sized one" rows="5" cols="25"></textarea>
+<textarea></textarea>
+
+<h3>File Selector</h3>
+<input type="file">
+<input type="file" multiple>
diff --git a/browser/components/sessionstore/test/browser_frame_history.js b/browser/components/sessionstore/test/browser_frame_history.js
new file mode 100644
index 000000000..e0d152f77
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ Ensure that frameset history works properly when restoring a tab,
+ provided that the frameset is static.
+ */
+
+// Loading a toplevel frameset
+add_task(function() {
+ let testURL = getRootDirectory(gTestPath) + "browser_frame_history_index.html";
+ let tab = gBrowser.addTab(testURL);
+ gBrowser.selectedTab = tab;
+
+ info("Opening a page with three frames, 4 loads should take place");
+ yield waitForLoadsInBrowser(tab.linkedBrowser, 4);
+
+ let browser_b = tab.linkedBrowser.contentDocument.getElementsByTagName("frame")[1];
+ let document_b = browser_b.contentDocument;
+ let links = document_b.getElementsByTagName("a");
+
+ // We're going to click on the first link, so listen for another load event
+ info("Clicking on link 1, 1 load should take place");
+ let promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
+ EventUtils.sendMouseEvent({type:"click"}, links[0], browser_b.contentWindow);
+ yield promise;
+
+ info("Clicking on link 2, 1 load should take place");
+ promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
+ EventUtils.sendMouseEvent({type:"click"}, links[1], browser_b.contentWindow);
+ yield promise;
+
+ info("Close then un-close page, 4 loads should take place");
+ yield promiseRemoveTab(tab);
+ let newTab = ss.undoCloseTab(window, 0);
+ yield waitForLoadsInBrowser(newTab.linkedBrowser, 4);
+
+ info("Go back in time, 1 load should take place");
+ gBrowser.goBack();
+ yield waitForLoadsInBrowser(newTab.linkedBrowser, 1);
+
+ let expectedURLEnds = ["a.html", "b.html", "c1.html"];
+ let frames = newTab.linkedBrowser.contentDocument.getElementsByTagName("frame");
+ for (let i = 0; i < frames.length; i++) {
+ is(frames[i].contentDocument.location,
+ getRootDirectory(gTestPath) + "browser_frame_history_" + expectedURLEnds[i],
+ "frame " + i + " has the right url");
+ }
+ gBrowser.removeTab(newTab);
+});
+
+// Loading the frameset inside an iframe
+add_task(function() {
+ let testURL = getRootDirectory(gTestPath) + "browser_frame_history_index2.html";
+ let tab = gBrowser.addTab(testURL);
+ gBrowser.selectedTab = tab;
+
+ info("iframe: Opening a page with an iframe containing three frames, 5 loads should take place");
+ yield waitForLoadsInBrowser(tab.linkedBrowser, 5);
+
+ let browser_b = tab.linkedBrowser.contentDocument.
+ getElementById("iframe").contentDocument.
+ getElementsByTagName("frame")[1];
+ let document_b = browser_b.contentDocument;
+ let links = document_b.getElementsByTagName("a");
+
+ // We're going to click on the first link, so listen for another load event
+ info("iframe: Clicking on link 1, 1 load should take place");
+ let promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
+ EventUtils.sendMouseEvent({type:"click"}, links[0], browser_b.contentWindow);
+ yield promise;
+
+ info("iframe: Clicking on link 2, 1 load should take place");
+ promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
+ EventUtils.sendMouseEvent({type:"click"}, links[1], browser_b.contentWindow);
+ yield promise;
+
+ info("iframe: Close then un-close page, 5 loads should take place");
+ yield promiseRemoveTab(tab);
+ let newTab = ss.undoCloseTab(window, 0);
+ yield waitForLoadsInBrowser(newTab.linkedBrowser, 5);
+
+ info("iframe: Go back in time, 1 load should take place");
+ gBrowser.goBack();
+ yield waitForLoadsInBrowser(newTab.linkedBrowser, 1);
+
+ let expectedURLEnds = ["a.html", "b.html", "c1.html"];
+ let frames = newTab.linkedBrowser.contentDocument.
+ getElementById("iframe").contentDocument.
+ getElementsByTagName("frame");
+ for (let i = 0; i < frames.length; i++) {
+ is(frames[i].contentDocument.location,
+ getRootDirectory(gTestPath) + "browser_frame_history_" + expectedURLEnds[i],
+ "frame " + i + " has the right url");
+ }
+ gBrowser.removeTab(newTab);
+});
+
+// Now, test that we don't record history if the iframe is added dynamically
+add_task(function() {
+ // Start with an empty history
+ let blankState = JSON.stringify({
+ windows: [{
+ tabs: [{ entries: [{ url: "about:blank" }] }],
+ _closedTabs: []
+ }],
+ _closedWindows: []
+ });
+ ss.setBrowserState(blankState);
+
+ let testURL = getRootDirectory(gTestPath) + "browser_frame_history_index_blank.html";
+ let tab = gBrowser.addTab(testURL);
+ gBrowser.selectedTab = tab;
+ yield waitForLoadsInBrowser(tab.linkedBrowser, 1);
+
+ info("dynamic: Opening a page with an iframe containing three frames, 4 dynamic loads should take place");
+ let doc = tab.linkedBrowser.contentDocument;
+ let iframe = doc.createElement("iframe");
+ iframe.id = "iframe";
+ iframe.src="browser_frame_history_index.html";
+ doc.body.appendChild(iframe);
+ yield waitForLoadsInBrowser(tab.linkedBrowser, 4);
+
+ let browser_b = tab.linkedBrowser.contentDocument.
+ getElementById("iframe").contentDocument.
+ getElementsByTagName("frame")[1];
+ let document_b = browser_b.contentDocument;
+ let links = document_b.getElementsByTagName("a");
+
+ // We're going to click on the first link, so listen for another load event
+ info("dynamic: Clicking on link 1, 1 load should take place");
+ let promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
+ EventUtils.sendMouseEvent({type:"click"}, links[0], browser_b.contentWindow);
+ yield promise;
+
+ info("dynamic: Clicking on link 2, 1 load should take place");
+ promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
+ EventUtils.sendMouseEvent({type:"click"}, links[1], browser_b.contentWindow);
+ yield promise;
+
+ info("Check in the state that we have not stored this history");
+ let state = ss.getBrowserState();
+ info(JSON.stringify(JSON.parse(state), null, "\t"));
+ is(state.indexOf("c1.html"), -1, "History entry was not stored in the session state");;
+ gBrowser.removeTab(tab);
+});
+
+// helper functions
+function waitForLoadsInBrowser(aBrowser, aLoadCount) {
+ let deferred = Promise.defer();
+ let loadCount = 0;
+ aBrowser.addEventListener("load", function(aEvent) {
+ if (++loadCount < aLoadCount) {
+ info("Got " + loadCount + " loads, waiting until we have " + aLoadCount);
+ return;
+ }
+
+ aBrowser.removeEventListener("load", arguments.callee, true);
+ deferred.resolve();
+ }, true);
+ return deferred.promise;
+}
+
+function timeout(delay, task) {
+ let deferred = Promise.defer();
+ setTimeout(() => deferred.resolve(true), delay);
+ task.then(() => deferred.resolve(false), deferred.reject);
+ return deferred.promise;
+}
diff --git a/browser/components/sessionstore/test/browser_frame_history_a.html b/browser/components/sessionstore/test/browser_frame_history_a.html
new file mode 100755
index 000000000..8e7b35d7a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_a.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ I'm A!
+ </body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_frame_history_b.html b/browser/components/sessionstore/test/browser_frame_history_b.html
new file mode 100755
index 000000000..38b43da21
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_b.html
@@ -0,0 +1,10 @@
+<html>
+ <body>
+ I'm B!<br/>
+ <a target="c" href="browser_frame_history_c1.html">click me first</a><br/>
+ <a target="c" href="browser_frame_history_c2.html">then click me</a><br/>
+ Close this tab.<br/>
+ Restore this tab.<br/>
+ Click back.<br/>
+ </body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_frame_history_c.html b/browser/components/sessionstore/test/browser_frame_history_c.html
new file mode 100755
index 000000000..0efd7d902
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_c.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ I'm C!
+ </body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_frame_history_c1.html b/browser/components/sessionstore/test/browser_frame_history_c1.html
new file mode 100755
index 000000000..b55c1d45a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_c1.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ I'm C1!
+ </body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_frame_history_c2.html b/browser/components/sessionstore/test/browser_frame_history_c2.html
new file mode 100755
index 000000000..aec504141
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_c2.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ I'm C2!
+ </body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_frame_history_index.html b/browser/components/sessionstore/test/browser_frame_history_index.html
new file mode 100644
index 000000000..76eeb4c4d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_index.html
@@ -0,0 +1,10 @@
+<html>
+ <frameset cols="20%,80%">
+ <frameset rows="30%,70%">
+ <frame src="browser_frame_history_a.html"/>
+ <frame src="browser_frame_history_b.html"/>
+ </frameset>
+ <frame src="browser_frame_history_c.html" name="c"/>
+ </frameset>
+</html>
+
diff --git a/browser/components/sessionstore/test/browser_frame_history_index2.html b/browser/components/sessionstore/test/browser_frame_history_index2.html
new file mode 100644
index 000000000..e4dfb4083
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_index2.html
@@ -0,0 +1,4 @@
+<html>
+ <iframe src="browser_frame_history_index.html" id="iframe" />
+</html>
+
diff --git a/browser/components/sessionstore/test/browser_frame_history_index_blank.html b/browser/components/sessionstore/test/browser_frame_history_index_blank.html
new file mode 100644
index 000000000..30fd1f58c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frame_history_index_blank.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ </body>
+</html>
+
diff --git a/browser/components/sessionstore/test/browser_frametree.js b/browser/components/sessionstore/test/browser_frametree.js
new file mode 100644
index 000000000..a342f8c66
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frametree.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = HTTPROOT + "browser_frametree_sample.html";
+const URL_FRAMESET = HTTPROOT + "browser_frametree_sample_frameset.html";
+
+/**
+ * This ensures that loading a page normally, aborting a page load, reloading
+ * a page, navigating using the bfcache, and ignoring frames that were
+ * created dynamically work as expect. We expect the frame tree to be reset
+ * when a page starts loading and we also expect a valid frame tree to exist
+ * when it has stopped loading.
+ */
+add_task(function test_frametree() {
+ const FRAME_TREE_SINGLE = { href: URL };
+ const FRAME_TREE_FRAMESET = {
+ href: URL_FRAMESET,
+ children: [{href: URL}, {href: URL}, {href: URL}]
+ };
+
+ // Create a tab with a single frame.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseNewFrameTree(browser);
+ yield checkFrameTree(browser, FRAME_TREE_SINGLE,
+ "loading a page resets and creates the frame tree correctly");
+
+ // Load the frameset and create two frames dynamically, the first on
+ // DOMContentLoaded and the second on load.
+ yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
+ browser.loadURI(URL_FRAMESET);
+ yield promiseNewFrameTree(browser);
+ yield checkFrameTree(browser, FRAME_TREE_FRAMESET,
+ "dynamic frames created on or after the load event are ignored");
+
+ // Go back to the previous single-frame page. There will be no load event as
+ // the page is still in the bfcache. We thus make sure this type of navigation
+ // resets the frame tree.
+ browser.goBack();
+ yield promiseNewFrameTree(browser);
+ yield checkFrameTree(browser, FRAME_TREE_SINGLE,
+ "loading from bfache resets and creates the frame tree correctly");
+
+ // Load the frameset again but abort the load early.
+ // The frame tree should still be reset and created.
+ browser.loadURI(URL_FRAMESET);
+ executeSoon(() => browser.stop());
+ yield promiseNewFrameTree(browser);
+
+ // Load the frameset and check the tree again.
+ yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
+ browser.loadURI(URL_FRAMESET);
+ yield promiseNewFrameTree(browser);
+ yield checkFrameTree(browser, FRAME_TREE_FRAMESET,
+ "reloading a page resets and creates the frame tree correctly");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * This test ensures that we ignore frames that were created dynamically at or
+ * after the load event. SessionStore can't handle these and will not restore
+ * or collect any data for them.
+ */
+add_task(function test_frametree_dynamic() {
+ // The frame tree as expected. The first two frames are static
+ // and the third one was created on DOMContentLoaded.
+ const FRAME_TREE = {
+ href: URL_FRAMESET,
+ children: [{href: URL}, {href: URL}, {href: URL}]
+ };
+ const FRAME_TREE_REMOVED = {
+ href: URL_FRAMESET,
+ children: [{href: URL}, {href: URL}]
+ };
+
+ // Add an empty tab for a start.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Create dynamic frames on "DOMContentLoaded" and on "load".
+ yield sendMessage(browser, "ss-test:createDynamicFrames", {id: "frames", url: URL});
+ browser.loadURI(URL_FRAMESET);
+ yield promiseNewFrameTree(browser);
+
+ // Check that the frame tree does not contain the frame created on "load".
+ // The two static frames and the one created on DOMContentLoaded must be in
+ // the tree.
+ yield checkFrameTree(browser, FRAME_TREE,
+ "frame tree contains first four frames");
+
+ // Remove the last frame in the frameset.
+ yield sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"});
+ // Check that the frame tree didn't change.
+ yield checkFrameTree(browser, FRAME_TREE,
+ "frame tree contains first four frames");
+
+ // Remove the last frame in the frameset.
+ yield sendMessage(browser, "ss-test:removeLastFrame", {id: "frames"});
+ // Check that the frame tree excludes the removed frame.
+ yield checkFrameTree(browser, FRAME_TREE_REMOVED,
+ "frame tree contains first three frames");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Checks whether the current frame hierarchy of a given |browser| matches the
+ * |expected| frame hierarchy.
+ */
+function checkFrameTree(browser, expected, msg) {
+ return sendMessage(browser, "ss-test:mapFrameTree").then(tree => {
+ is(JSON.stringify(tree), JSON.stringify(expected), msg);
+ });
+}
+
+/**
+ * Returns a promise that will be resolved when the given |browser| has loaded
+ * and we received messages saying that its frame tree has been reset and
+ * recollected.
+ */
+function promiseNewFrameTree(browser) {
+ let reset = promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
+ let collect = promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
+ return Promise.all([reset, collect]);
+}
diff --git a/browser/components/sessionstore/test/browser_frametree_sample.html b/browser/components/sessionstore/test/browser_frametree_sample.html
new file mode 100644
index 000000000..dda129448
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frametree_sample.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>browser_frametree_sample.html</title>
+ </head>
+ <body style='width: 100000px; height: 100000px;'>top</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_frametree_sample_frameset.html b/browser/components/sessionstore/test/browser_frametree_sample_frameset.html
new file mode 100644
index 000000000..e1cd08735
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_frametree_sample_frameset.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>browser_frametree_sample_frameset.html</title>
+ </head>
+ <frameset id="frames" rows="50%, 50%">
+ <frame src="browser_frametree_sample.html">
+ <frame src="browser_frametree_sample.html">
+ </frameset>
+</html>
diff --git a/browser/components/sessionstore/test/browser_global_store.js b/browser/components/sessionstore/test/browser_global_store.js
new file mode 100644
index 000000000..792154830
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_global_store.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the API for saving global session data.
+add_task(function* () {
+ const key1 = "Unique name 1: " + Date.now();
+ const key2 = "Unique name 2: " + Date.now();
+ const value1 = "Unique value 1: " + Math.random();
+ const value2 = "Unique value 2: " + Math.random();
+
+ let global = {};
+ global[key1] = value1;
+
+ const testState = {
+ windows: [
+ {
+ tabs: [
+ { entries: [{ url: "about:blank" }] },
+ ]
+ }
+ ],
+ global: global
+ };
+
+ function testRestoredState() {
+ is(ss.getGlobalValue(key1), value1, "restored state has global value");
+ }
+
+ function testGlobalStore() {
+ is(ss.getGlobalValue(key2), "", "global value initially not set");
+
+ ss.setGlobalValue(key2, value1);
+ is(ss.getGlobalValue(key2), value1, "retreived value matches stored");
+
+ ss.setGlobalValue(key2, value2);
+ is(ss.getGlobalValue(key2), value2, "previously stored value was overwritten");
+
+ ss.deleteGlobalValue(key2);
+ is(ss.getGlobalValue(key2), "", "global value was deleted");
+ }
+
+ yield promiseBrowserState(testState);
+ testRestoredState();
+ testGlobalStore();
+});
diff --git a/browser/components/sessionstore/test/browser_history_persist.js b/browser/components/sessionstore/test/browser_history_persist.js
new file mode 100644
index 000000000..6b9e62abc
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_history_persist.js
@@ -0,0 +1,93 @@
+/* eslint-env mozilla/frame-script */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Ensure that history entries that should not be persisted are restored in the
+ * same state.
+ */
+add_task(function check_history_not_persisted() {
+ // Create an about:blank tab
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Retrieve the tab state.
+ yield TabStateFlusher.flush(browser);
+ let state = JSON.parse(ss.getTabState(tab));
+ ok(!state.entries[0].persist, "Should have collected the persistence state");
+ yield promiseRemoveTab(tab);
+ browser = null;
+
+ // Open a new tab to restore into.
+ tab = gBrowser.addTab("about:blank");
+ browser = tab.linkedBrowser;
+ yield promiseTabState(tab, state);
+
+ yield ContentTask.spawn(browser, null, function() {
+ let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISHistory);
+
+ is(sessionHistory.count, 1, "Should be a single history entry");
+ is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
+ });
+
+ // Load a new URL into the tab, it should replace the about:blank history entry
+ browser.loadURI("about:robots");
+ yield promiseBrowserLoaded(browser);
+ yield ContentTask.spawn(browser, null, function() {
+ let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISHistory);
+ is(sessionHistory.count, 1, "Should be a single history entry");
+ is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:robots", "Should be the right URL");
+ });
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+});
+
+/**
+ * Check that entries default to being persisted when the attribute doesn't
+ * exist
+ */
+add_task(function check_history_default_persisted() {
+ // Create an about:blank tab
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Retrieve the tab state.
+ yield TabStateFlusher.flush(browser);
+ let state = JSON.parse(ss.getTabState(tab));
+ delete state.entries[0].persist;
+ yield promiseRemoveTab(tab);
+ browser = null;
+
+ // Open a new tab to restore into.
+ tab = gBrowser.addTab("about:blank");
+ browser = tab.linkedBrowser;
+ yield promiseTabState(tab, state);
+ yield ContentTask.spawn(browser, null, function() {
+ let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISHistory);
+
+ is(sessionHistory.count, 1, "Should be a single history entry");
+ is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
+ });
+
+ // Load a new URL into the tab, it should replace the about:blank history entry
+ browser.loadURI("about:robots");
+ yield promiseBrowserLoaded(browser);
+ yield ContentTask.spawn(browser, null, function() {
+ let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISHistory);
+ is(sessionHistory.count, 2, "Should be two history entries");
+ is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
+ is(sessionHistory.getEntryAtIndex(1, false).URI.spec, "about:robots", "Should be the right URL");
+ });
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_label_and_icon.js b/browser/components/sessionstore/test/browser_label_and_icon.js
new file mode 100644
index 000000000..db68eb042
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_label_and_icon.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci} = Components;
+
+/**
+ * Make sure that tabs are restored on demand as otherwise the tab will start
+ * loading immediately and we can't check its icon and label.
+ */
+add_task(function setup() {
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+ });
+});
+
+/**
+ * Ensure that a pending tab has label and icon correctly set.
+ */
+add_task(function test_label_and_icon() {
+ // Create a new tab.
+ let tab = gBrowser.addTab("about:robots");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Retrieve the tab state.
+ yield TabStateFlusher.flush(browser);
+ let state = ss.getTabState(tab);
+ yield promiseRemoveTab(tab);
+ browser = null;
+
+ // Open a new tab to restore into.
+ tab = gBrowser.addTab("about:blank");
+ ss.setTabState(tab, state);
+ yield promiseTabRestoring(tab);
+
+ // Check that label and icon are set for the restoring tab.
+ ok(gBrowser.getIcon(tab).startsWith("data:image/png;"), "icon is set");
+ is(tab.label, "Gort! Klaatu barada nikto!", "label is set");
+
+ let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+ let serializedPrincipal = tab.getAttribute("iconLoadingPrincipal");
+ let iconLoadingPrincipal = serhelper.deserializeObject(serializedPrincipal)
+ .QueryInterface(Ci.nsIPrincipal);
+ is(iconLoadingPrincipal.origin, "about:robots", "correct loadingPrincipal used");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_merge_closed_tabs.js b/browser/components/sessionstore/test/browser_merge_closed_tabs.js
new file mode 100644
index 000000000..b26e86f22
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_merge_closed_tabs.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test ensures that closed tabs are merged when restoring
+ * a window state without overwriting tabs.
+ */
+add_task(function () {
+ const initialState = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:blank" }] }
+ ],
+ _closedTabs: [
+ { state: { entries: [{ ID: 1000, url: "about:blank" }]} },
+ { state: { entries: [{ ID: 1001, url: "about:blank" }]} }
+ ]
+ }]
+ }
+
+ const restoreState = {
+ windows: [{
+ tabs: [
+ { entries: [{ url: "about:robots" }] }
+ ],
+ _closedTabs: [
+ { state: { entries: [{ ID: 1002, url: "about:robots" }]} },
+ { state: { entries: [{ ID: 1003, url: "about:robots" }]} },
+ { state: { entries: [{ ID: 1004, url: "about:robots" }]} }
+ ]
+ }]
+ }
+
+ const maxTabsUndo = 4;
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", maxTabsUndo);
+
+ // Open a new window and restore it to an initial state.
+ let win = yield promiseNewWindowLoaded({private: false});
+ SessionStore.setWindowState(win, JSON.stringify(initialState), true);
+ is(SessionStore.getClosedTabCount(win), 2, "2 closed tabs after restoring initial state");
+
+ // Restore the new state but do not overwrite existing tabs (this should
+ // cause the closed tabs to be merged).
+ SessionStore.setWindowState(win, JSON.stringify(restoreState), false);
+
+ // Verify the windows closed tab data is correct.
+ let iClosed = initialState.windows[0]._closedTabs;
+ let rClosed = restoreState.windows[0]._closedTabs;
+ let cData = JSON.parse(SessionStore.getClosedTabData(win));
+
+ is(cData.length, Math.min(iClosed.length + rClosed.length, maxTabsUndo),
+ "Number of closed tabs is correct");
+
+ // When the closed tabs are merged the restored tabs are considered to be
+ // closed more recently.
+ for (let i = 0; i < cData.length; i++) {
+ if (i < rClosed.length) {
+ is(cData[i].state.entries[0].ID, rClosed[i].state.entries[0].ID,
+ "Closed tab entry matches");
+ } else {
+ is(cData[i].state.entries[0].ID, iClosed[i - rClosed.length].state.entries[0].ID,
+ "Closed tab entry matches");
+ }
+ }
+
+ // Clean up.
+ gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+
diff --git a/browser/components/sessionstore/test/browser_multiple_navigateAndRestore.js b/browser/components/sessionstore/test/browser_multiple_navigateAndRestore.js
new file mode 100644
index 000000000..fc958b293
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_multiple_navigateAndRestore.js
@@ -0,0 +1,36 @@
+"use strict";
+
+const PAGE_1 = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const PAGE_2 = "data:text/html,<html><body>Another%20regular,%20everyday,%20normal%20page.";
+
+add_task(function*() {
+ // Load an empty, non-remote tab at about:blank...
+ let tab = gBrowser.addTab("about:blank", {
+ forceNotRemote: true,
+ });
+ gBrowser.selectedTab = tab;
+ let browser = gBrowser.selectedBrowser;
+ ok(!browser.isRemoteBrowser, "Ensure browser is not remote");
+ // Load a remote page, and then another remote page immediately
+ // after.
+ browser.loadURI(PAGE_1);
+ browser.stop();
+ browser.loadURI(PAGE_2);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ ok(browser.isRemoteBrowser, "Should have switched remoteness");
+ yield TabStateFlusher.flush(browser);
+ let state = JSON.parse(ss.getTabState(tab));
+ let entries = state.entries;
+ is(entries.length, 1, "There should only be one entry");
+ is(entries[0].url, PAGE_2, "Should have PAGE_2 as the sole history entry");
+ is(browser.currentURI.spec, PAGE_2, "Should have PAGE_2 as the browser currentURI");
+
+ yield ContentTask.spawn(browser, PAGE_2, function*(PAGE_2) {
+ docShell.QueryInterface(Ci.nsIWebNavigation);
+ Assert.equal(docShell.currentURI.spec, PAGE_2,
+ "Content should have PAGE_2 as the browser currentURI");
+ });
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_newtab_userTypedValue.js b/browser/components/sessionstore/test/browser_newtab_userTypedValue.js
new file mode 100644
index 000000000..66dc93380
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_newtab_userTypedValue.js
@@ -0,0 +1,72 @@
+"use strict";
+
+requestLongerTimeout(4);
+
+/**
+ * Test that when restoring an 'initial page' with session restore, it
+ * produces an empty URL bar, rather than leaving its URL explicitly
+ * there as a 'user typed value'.
+ */
+add_task(function* () {
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:logo");
+ let tabOpenedAndSwitchedTo = BrowserTestUtils.switchTab(win.gBrowser, () => {});
+
+ // This opens about:newtab:
+ win.BrowserOpenTab();
+ let tab = yield tabOpenedAndSwitchedTo;
+ is(win.gURLBar.value, "", "URL bar should be empty");
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+ let state = JSON.parse(SessionStore.getTabState(tab));
+ ok(!state.userTypedValue, "userTypedValue should be undefined on the tab's state");
+ tab = null;
+
+ yield BrowserTestUtils.closeWindow(win);
+
+ ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
+
+ win = SessionStore.undoCloseWindow(0);
+ yield TestUtils.topicObserved("sessionstore-single-window-restored",
+ subject => subject == win);
+ // Don't wait for load here because it's about:newtab and we may have swapped in
+ // a preloaded browser.
+ yield TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+
+ is(win.gURLBar.value, "", "URL bar should be empty");
+ tab = win.gBrowser.selectedTab;
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+ state = JSON.parse(SessionStore.getTabState(tab));
+ ok(!state.userTypedValue, "userTypedValue should be undefined on the tab's state");
+
+ yield BrowserTestUtils.removeTab(tab);
+
+ for (let url of gInitialPages) {
+ if (url == BROWSER_NEW_TAB_URL) {
+ continue; // We tested about:newtab using BrowserOpenTab() above.
+ }
+ info("Testing " + url + " - " + new Date());
+ yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+ yield BrowserTestUtils.closeWindow(win);
+
+ ok(SessionStore.getClosedWindowCount(), "Should have a closed window");
+
+ win = SessionStore.undoCloseWindow(0);
+ yield TestUtils.topicObserved("sessionstore-single-window-restored",
+ subject => subject == win);
+ yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+ yield TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+
+ is(win.gURLBar.value, "", "URL bar should be empty");
+ tab = win.gBrowser.selectedTab;
+ is(tab.linkedBrowser.userTypedValue, null, "userTypedValue should be null");
+ state = JSON.parse(SessionStore.getTabState(tab));
+ ok(!state.userTypedValue, "userTypedValue should be undefined on the tab's state");
+
+ info("Removing tab - " + new Date());
+ yield BrowserTestUtils.removeTab(tab);
+ info("Finished removing tab - " + new Date());
+ }
+ info("Removing window - " + new Date());
+ yield BrowserTestUtils.closeWindow(win);
+ info("Finished removing window - " + new Date());
+});
diff --git a/browser/components/sessionstore/test/browser_pageStyle.js b/browser/components/sessionstore/test/browser_pageStyle.js
new file mode 100644
index 000000000..7abee5d9d
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_pageStyle.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = getRootDirectory(gTestPath) + "browser_pageStyle_sample.html";
+const URL_NESTED = getRootDirectory(gTestPath) + "browser_pageStyle_sample_nested.html";
+
+/**
+ * This test ensures that page style information is correctly persisted.
+ */
+add_task(function page_style() {
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+ let sheets = yield getStyleSheets(browser);
+
+ // Enable all style sheets one by one.
+ for (let [title, disabled] of sheets) {
+ yield enableStyleSheetsForSet(browser, title);
+
+ let tab2 = gBrowser.duplicateTab(tab);
+ yield promiseTabRestored(tab2);
+
+ let sheets = yield getStyleSheets(tab2.linkedBrowser);
+ let enabled = sheets.filter(([title, disabled]) => !disabled);
+
+ if (title.startsWith("fail_")) {
+ ok(!enabled.length, "didn't restore " + title);
+ } else {
+ is(enabled.length, 1, "restored one style sheet");
+ is(enabled[0][0], title, "restored correct sheet");
+ }
+
+ gBrowser.removeTab(tab2);
+ }
+
+ // Disable all styles and verify that this is correctly persisted.
+ yield setAuthorStyleDisabled(browser, true);
+
+ let tab2 = gBrowser.duplicateTab(tab);
+ yield promiseTabRestored(tab2);
+
+ let authorStyleDisabled = yield getAuthorStyleDisabled(tab2.linkedBrowser);
+ ok(authorStyleDisabled, "disabled all stylesheets");
+
+ // Clean up.
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(tab2);
+});
+
+/**
+ * This test ensures that page style notification from nested documents are
+ * received and the page style is persisted correctly.
+ */
+add_task(function nested_page_style() {
+ let tab = gBrowser.addTab(URL_NESTED);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ yield enableSubDocumentStyleSheetsForSet(browser, "alternate");
+ yield promiseRemoveTab(tab);
+
+ let [{state: {pageStyle}}] = JSON.parse(ss.getClosedTabData(window));
+ let expected = JSON.stringify({children: [{pageStyle: "alternate"}]});
+ is(JSON.stringify(pageStyle), expected, "correct pageStyle persisted");
+});
+
+function getStyleSheets(browser) {
+ return sendMessage(browser, "ss-test:getStyleSheets");
+}
+
+function enableStyleSheetsForSet(browser, name) {
+ return sendMessage(browser, "ss-test:enableStyleSheetsForSet", name);
+}
+
+function enableSubDocumentStyleSheetsForSet(browser, name) {
+ return sendMessage(browser, "ss-test:enableSubDocumentStyleSheetsForSet", {
+ id: "iframe", set: name
+ });
+}
+
+function getAuthorStyleDisabled(browser) {
+ return sendMessage(browser, "ss-test:getAuthorStyleDisabled");
+}
+
+function setAuthorStyleDisabled(browser, val) {
+ return sendMessage(browser, "ss-test:setAuthorStyleDisabled", val)
+}
diff --git a/browser/components/sessionstore/test/browser_pageStyle_sample.html b/browser/components/sessionstore/test/browser_pageStyle_sample.html
new file mode 100644
index 000000000..810054049
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_pageStyle_sample.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>pageStyle sample</title>
+
+ <link href="404.css" title="default" rel="stylesheet">
+ <link href="404.css" title="alternate" rel="alternate stylesheet">
+ <link href="404.css" title="altERnate" rel=" styLEsheet altERnate ">
+ <link href="404.css" title="media_empty" rel="alternate stylesheet" media="">
+ <link href="404.css" title="media_all" rel="alternate stylesheet" media="all">
+ <link href="404.css" title="media_ALL" rel="alternate stylesheet" media=" ALL ">
+ <link href="404.css" title="media_screen" rel="alternate stylesheet" media="screen">
+ <link href="404.css" title="media_print_screen" rel="alternate stylesheet" media="print,screen">
+</head>
+<body></body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_pageStyle_sample_nested.html b/browser/components/sessionstore/test/browser_pageStyle_sample_nested.html
new file mode 100644
index 000000000..157609fa6
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_pageStyle_sample_nested.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>pageStyle sample (nested)</title>
+</head>
+<body>
+ <iframe id="iframe" src="browser_pageStyle_sample.html"/>
+</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_page_title.js b/browser/components/sessionstore/test/browser_page_title.js
new file mode 100644
index 000000000..9bbb1ca76
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_page_title.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const URL = "data:text/html,<title>initial title</title>";
+
+add_task(function* () {
+ // Create a new tab.
+ let tab = gBrowser.addTab(URL);
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+
+ // Remove the tab.
+ yield promiseRemoveTab(tab);
+
+ // Check the title.
+ let [{state: {entries}}] = JSON.parse(ss.getClosedTabData(window));
+ is(entries[0].title, "initial title", "correct title");
+});
+
+add_task(function* () {
+ // Create a new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to ensure we collected the initial title.
+ yield TabStateFlusher.flush(browser);
+
+ // Set a new title.
+ yield ContentTask.spawn(browser, null, function* () {
+ return new Promise(resolve => {
+ addEventListener("DOMTitleChanged", function onTitleChanged() {
+ removeEventListener("DOMTitleChanged", onTitleChanged);
+ resolve();
+ });
+
+ content.document.title = "new title";
+ });
+ });
+
+ // Remove the tab.
+ yield promiseRemoveTab(tab);
+
+ // Check the title.
+ let [{state: {entries}}] = JSON.parse(ss.getClosedTabData(window));
+ is(entries[0].title, "new title", "correct title");
+});
diff --git a/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js b/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
new file mode 100644
index 000000000..1deb461c8
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
@@ -0,0 +1,95 @@
+"use strict";
+
+const SELFCHROMEURL =
+ "chrome://mochitests/content/browser/browser/" +
+ "components/sessionstore/test/browser_parentProcessRestoreHash.js";
+
+const Cm = Components.manager;
+
+const TESTCLASSID = "78742c04-3630-448c-9be3-6c5070f062de";
+
+const TESTURL = "about:testpageforsessionrestore#foo";
+
+
+let TestAboutPage = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+ getURIFlags: function(aURI) {
+ // No CAN_ or MUST_LOAD_IN_CHILD means this loads in the parent:
+ return Ci.nsIAboutModule.ALLOW_SCRIPT |
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
+ },
+
+ newChannel: function(aURI, aLoadInfo) {
+ // about: page inception!
+ let newURI = Services.io.newURI(SELFCHROMEURL, null, null);
+ let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
+ aLoadInfo);
+ channel.originalURI = aURI;
+ return channel;
+ },
+
+ createInstance: function(outer, iid) {
+ if (outer != null) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(iid);
+ },
+
+ register: function() {
+ Cm.QueryInterface(Ci.nsIComponentRegistrar).registerFactory(
+ Components.ID(TESTCLASSID), "Only here for a test",
+ "@mozilla.org/network/protocol/about;1?what=testpageforsessionrestore", this);
+ },
+
+ unregister: function() {
+ Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(
+ Components.ID(TESTCLASSID), this);
+ }
+};
+
+
+/**
+ * Test that switching from a remote to a parent process browser
+ * correctly clears the userTypedValue
+ */
+add_task(function* () {
+ TestAboutPage.register();
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/", true, true);
+ ok(tab.linkedBrowser.isRemoteBrowser, "Browser should be remote");
+
+ let resolveLocationChangePromise;
+ let locationChangePromise = new Promise(r => resolveLocationChangePromise = r);
+ let wpl = {
+ onStateChange(wpl, request, state, status) {
+ let location = request.QueryInterface(Ci.nsIChannel).originalURI;
+ // Ignore about:blank loads.
+ let docStop = Ci.nsIWebProgressListener.STATE_STOP |
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+ if (location.spec == "about:blank" || (state & docStop == docStop)) {
+ return;
+ }
+ is(location.spec, TESTURL, "Got the expected URL");
+ resolveLocationChangePromise();
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+
+ gURLBar.value = TESTURL;
+ gURLBar.select();
+ EventUtils.sendKey("return");
+
+ yield locationChangePromise;
+
+ ok(!tab.linkedBrowser.isRemoteBrowser, "Browser should no longer be remote");
+
+ is(gURLBar.textValue, TESTURL, "URL bar visible value should be correct.");
+ is(gURLBar.value, TESTURL, "URL bar value should be correct.");
+ is(gURLBar.getAttribute("pageproxystate"), "valid", "URL bar is in valid page proxy state");
+
+ ok(!tab.linkedBrowser.userTypedValue, "No userTypedValue should be on the browser.");
+
+ yield BrowserTestUtils.removeTab(tab);
+ gBrowser.removeProgressListener(wpl);
+ TestAboutPage.unregister();
+});
diff --git a/browser/components/sessionstore/test/browser_pending_tabs.js b/browser/components/sessionstore/test/browser_pending_tabs.js
new file mode 100644
index 000000000..e734e55c9
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_pending_tabs.js
@@ -0,0 +1,35 @@
+"use strict";
+
+const TAB_STATE = {
+ entries: [{ url: "about:mozilla" }, { url: "about:robots" }],
+ index: 1,
+};
+
+add_task(function* () {
+ // Create a background tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // The tab shouldn't be restored right away.
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+ // Prepare the tab state.
+ let promise = promiseTabRestoring(tab);
+ ss.setTabState(tab, JSON.stringify(TAB_STATE));
+ ok(tab.hasAttribute("pending"), "tab is pending");
+ yield promise;
+
+ // Flush to ensure the parent has all data.
+ yield TabStateFlusher.flush(browser);
+
+ // Check that the shistory index is the one we restored.
+ let tabState = TabState.collect(tab);
+ is(tabState.index, TAB_STATE.index, "correct shistory index");
+
+ // Check we don't collect userTypedValue when we shouldn't.
+ ok(!tabState.userTypedValue, "tab didn't have a userTypedValue");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_privatetabs.js b/browser/components/sessionstore/test/browser_privatetabs.js
new file mode 100644
index 000000000..cc02e56cf
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_privatetabs.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(function cleanup() {
+ info("Forgetting closed tabs");
+ while (ss.getClosedTabCount(window)) {
+ ss.forgetClosedTab(window, 0);
+ }
+});
+
+add_task(function() {
+ let URL_PUBLIC = "http://example.com/public/" + Math.random();
+ let URL_PRIVATE = "http://example.com/private/" + Math.random();
+ let tab1, tab2;
+ try {
+ // Setup a public tab and a private tab
+ info("Setting up public tab");
+ tab1 = gBrowser.addTab(URL_PUBLIC);
+ yield promiseBrowserLoaded(tab1.linkedBrowser);
+
+ info("Setting up private tab");
+ tab2 = gBrowser.addTab();
+ yield promiseBrowserLoaded(tab2.linkedBrowser);
+ yield setUsePrivateBrowsing(tab2.linkedBrowser, true);
+ tab2.linkedBrowser.loadURI(URL_PRIVATE);
+ yield promiseBrowserLoaded(tab2.linkedBrowser);
+
+ info("Flush to make sure chrome received all data.");
+ yield TabStateFlusher.flush(tab1.linkedBrowser);
+ yield TabStateFlusher.flush(tab2.linkedBrowser);
+
+ info("Checking out state");
+ let state = yield promiseRecoveryFileContents();
+
+ info("State: " + state);
+ // Ensure that sessionstore.js only knows about the public tab
+ ok(state.indexOf(URL_PUBLIC) != -1, "State contains public tab");
+ ok(state.indexOf(URL_PRIVATE) == -1, "State does not contain private tab");
+
+ // Ensure that we can close and undo close the public tab but not the private tab
+ gBrowser.removeTab(tab2);
+ tab2 = null;
+
+ gBrowser.removeTab(tab1);
+ tab1 = null;
+
+ tab1 = ss.undoCloseTab(window, 0);
+ ok(true, "Public tab supports undo close");
+
+ is(ss.getClosedTabCount(window), 0, "Private tab does not support undo close");
+
+ } finally {
+ if (tab1) {
+ gBrowser.removeTab(tab1);
+ }
+ if (tab2) {
+ gBrowser.removeTab(tab2);
+ }
+ }
+});
+
+add_task(function () {
+ const FRAME_SCRIPT = "data:," +
+ "docShell.QueryInterface%28Components.interfaces.nsILoadContext%29.usePrivateBrowsing%3Dtrue";
+
+ // Clear the list of closed windows.
+ forgetClosedWindows();
+
+ // Create a new window to attach our frame script to.
+ let win = yield promiseNewWindowLoaded();
+ let mm = win.getGroupMessageManager("browsers");
+ mm.loadFrameScript(FRAME_SCRIPT, true);
+
+ // Create a new tab in the new window that will load the frame script.
+ let tab = win.gBrowser.addTab("about:mozilla");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+
+ // Check that we consider the tab as private.
+ let state = JSON.parse(ss.getTabState(tab));
+ ok(state.isPrivate, "tab considered private");
+
+ // Ensure we don't allow restoring closed private tabs in non-private windows.
+ win.gBrowser.removeTab(tab);
+ is(ss.getClosedTabCount(win), 0, "no tabs to restore");
+
+ // Create a new tab in the new window that will load the frame script.
+ tab = win.gBrowser.addTab("about:mozilla");
+ browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+
+ // Check that we consider the tab as private.
+ state = JSON.parse(ss.getTabState(tab));
+ ok(state.isPrivate, "tab considered private");
+
+ // Check that all private tabs are removed when the non-private
+ // window is closed and we don't save windows without any tabs.
+ yield BrowserTestUtils.closeWindow(win);
+ is(ss.getClosedWindowCount(), 0, "no windows to restore");
+});
+
+add_task(function () {
+ // Clear the list of closed windows.
+ forgetClosedWindows();
+
+ // Create a new window to attach our frame script to.
+ let win = yield promiseNewWindowLoaded({private: true});
+
+ // Create a new tab in the new window that will load the frame script.
+ let tab = win.gBrowser.addTab("about:mozilla");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+
+ // Check that we consider the tab as private.
+ let state = JSON.parse(ss.getTabState(tab));
+ ok(state.isPrivate, "tab considered private");
+
+ // Ensure that closed tabs in a private windows can be restored.
+ win.gBrowser.removeTab(tab);
+ is(ss.getClosedTabCount(win), 1, "there is a single tab to restore");
+
+ // Ensure that closed private windows can never be restored.
+ yield BrowserTestUtils.closeWindow(win);
+ is(ss.getClosedWindowCount(), 0, "no windows to restore");
+});
+
+function setUsePrivateBrowsing(browser, val) {
+ return sendMessage(browser, "ss-test:setUsePrivateBrowsing", val);
+}
+
diff --git a/browser/components/sessionstore/test/browser_purge_shistory.js b/browser/components/sessionstore/test/browser_purge_shistory.js
new file mode 100644
index 000000000..28c6f6f24
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_purge_shistory.js
@@ -0,0 +1,59 @@
+"use strict";
+
+/**
+ * This test checks that pending tabs are treated like fully loaded tabs when
+ * purging session history. Just like for fully loaded tabs we want to remove
+ * every but the current shistory entry.
+ */
+
+const TAB_STATE = {
+ entries: [{url: "about:mozilla"}, {url: "about:robots"}],
+ index: 1,
+};
+
+function checkTabContents(browser) {
+ return ContentTask.spawn(browser, null, function* () {
+ let Ci = Components.interfaces;
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
+ Assert.ok(history && history.count == 1 && content.document.documentURI == "about:mozilla",
+ "expected tab contents found");
+ });
+}
+
+add_task(function* () {
+ // Create a new tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+ yield promiseTabState(tab, TAB_STATE);
+
+ // Create another new tab.
+ let tab2 = gBrowser.addTab("about:blank");
+ let browser2 = tab2.linkedBrowser;
+ yield promiseBrowserLoaded(browser2);
+
+ // The tab shouldn't be restored right away.
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+ // Prepare the tab state.
+ let promise = promiseTabRestoring(tab2);
+ ss.setTabState(tab2, JSON.stringify(TAB_STATE));
+ ok(tab2.hasAttribute("pending"), "tab is pending");
+ yield promise;
+
+ // Purge session history.
+ Services.obs.notifyObservers(null, "browser:purge-session-history", "");
+ yield checkTabContents(browser);
+ ok(tab2.hasAttribute("pending"), "tab is still pending");
+
+ // Kick off tab restoration.
+ gBrowser.selectedTab = tab2;
+ yield promiseTabRestored(tab2);
+ yield checkTabContents(browser2);
+ ok(!tab2.hasAttribute("pending"), "tab is not pending anymore");
+
+ // Cleanup.
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js b/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
new file mode 100644
index 000000000..7dbee03fd
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_remoteness_flip_on_restore.js
@@ -0,0 +1,342 @@
+"use strict";
+
+/**
+ * This set of tests checks that the remoteness is properly
+ * set for each browser in a window when that window has
+ * session state loaded into it.
+ */
+
+/**
+ * Takes a SessionStore window state object for a single
+ * window, sets the selected tab for it, and then returns
+ * the object to be passed to SessionStore.setWindowState.
+ *
+ * @param state (object)
+ * The state to prepare to be sent to a window. This is
+ * state should just be for a single window.
+ * @param selected (int)
+ * The 1-based index of the selected tab. Note that
+ * If this is 0, then the selected tab will not change
+ * from what's already selected in the window that we're
+ * sending state to.
+ * @returns (object)
+ * The JSON encoded string to call
+ * SessionStore.setWindowState with.
+ */
+function prepareState(state, selected) {
+ // We'll create a copy so that we don't accidentally
+ // modify the caller's selected property.
+ let copy = {};
+ Object.assign(copy, state);
+ copy.selected = selected;
+
+ return {
+ windows: [ copy ],
+ };
+}
+
+const SIMPLE_STATE = {
+ tabs: [
+ { entries: [{ url: "http://example.com/", title: "title" }] },
+ { entries: [{ url: "http://example.com/", title: "title" }] },
+ { entries: [{ url: "http://example.com/", title: "title" }] },
+ ],
+ title: "",
+ _closedTabs: [],
+};
+
+const PINNED_STATE = {
+ tabs: [
+ { entries: [{ url: "http://example.com/", title: "title" }], pinned: true },
+ { entries: [{ url: "http://example.com/", title: "title" }], pinned: true },
+ { entries: [{ url: "http://example.com/", title: "title" }] },
+ ],
+ title: "",
+ _closedTabs: [],
+};
+
+/**
+ * This is where most of the action is happening. This function takes
+ * an Array of "test scenario" Objects and runs them. For each scenario, a
+ * window is opened, put into some state, and then a new state is
+ * loaded into that window. We then check to make sure that the
+ * right things have happened in that window wrt remoteness flips.
+ *
+ * The schema for a testing scenario Object is as follows:
+ *
+ * initialRemoteness:
+ * an Array that represents the starting window. Each bool
+ * in the Array represents the window tabs in order. A "true"
+ * indicates that that tab should be remote. "false" if the tab
+ * should be non-remote.
+ *
+ * initialSelectedTab:
+ * The 1-based index of the tab that we want to select for the
+ * restored window. This is 1-based to avoid confusion with the
+ * selectedTab property described down below, though you probably
+ * want to set this to be greater than 0, since the initial window
+ * needs to have a defined initial selected tab. Because of this,
+ * the test will throw if initialSelectedTab is 0.
+ *
+ * stateToRestore:
+ * A JS Object for the state to send down to the window.
+ *
+ * selectedTab:
+ * The 1-based index of the tab that we want to select for the
+ * restored window. Leave this at 0 if you don't want to change
+ * the selection from the initial window state.
+ *
+ * expectedFlips:
+ * an Array that represents the window that we end up with after
+ * restoring state. Each bool in the Array represents the window tabs,
+ * in order. A "true" indicates that the tab should have flipped
+ * its remoteness once. "false" indicates that the tab should never
+ * have flipped remoteness. Note that any tab that flips its remoteness
+ * more than once will cause a test failure.
+ *
+ * expectedRemoteness:
+ * an Array that represents the window that we end up with after
+ * restoring state. Each bool in the Array represents the window
+ * tabs in order. A "true" indicates that the tab be remote, and
+ * a "false" indicates that the tab should be "non-remote". We
+ * need this Array in order to test pinned tabs which will also
+ * be loaded by default, and therefore should end up remote.
+ *
+ */
+function* runScenarios(scenarios) {
+ for (let scenario of scenarios) {
+ // Let's make sure our scenario is sane first.
+ Assert.equal(scenario.expectedFlips.length,
+ scenario.expectedRemoteness.length,
+ "All expected flips and remoteness needs to be supplied");
+ Assert.ok(scenario.initialSelectedTab > 0,
+ "You must define an initially selected tab");
+
+ // First, we need to create the initial conditions, so we
+ // open a new window to put into our starting state...
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let tabbrowser = win.gBrowser;
+ Assert.ok(tabbrowser.selectedBrowser.isRemoteBrowser,
+ "The initial browser should be remote.");
+ // Now put the window into the expected initial state.
+ for (let i = 0; i < scenario.initialRemoteness.length; ++i) {
+ let tab;
+ if (i > 0) {
+ // The window starts with one tab, so we need to create
+ // any of the additional ones required by this test.
+ info("Opening a new tab");
+ tab = yield BrowserTestUtils.openNewForegroundTab(tabbrowser)
+ } else {
+ info("Using the selected tab");
+ tab = tabbrowser.selectedTab;
+ }
+ let browser = tab.linkedBrowser;
+ let remotenessState = scenario.initialRemoteness[i];
+ tabbrowser.updateBrowserRemoteness(browser, remotenessState);
+ }
+
+ // And select the requested tab.
+ let tabToSelect = tabbrowser.tabs[scenario.initialSelectedTab - 1];
+ if (tabbrowser.selectedTab != tabToSelect) {
+ yield BrowserTestUtils.switchTab(tabbrowser, tabToSelect);
+ }
+
+ // Hook up an event listener to make sure that the right
+ // tabs flip remoteness, and only once.
+ let flipListener = {
+ seenBeforeTabs: new Set(),
+ seenAfterTabs: new Set(),
+ handleEvent(e) {
+ let index = Array.from(tabbrowser.tabs).indexOf(e.target);
+ switch (e.type) {
+ case "BeforeTabRemotenessChange":
+ info(`Saw tab at index ${index} before remoteness flip`);
+ if (this.seenBeforeTabs.has(e.target)) {
+ Assert.ok(false, "Saw tab before remoteness flip more than once");
+ }
+ this.seenBeforeTabs.add(e.target);
+ break;
+ case "TabRemotenessChange":
+ info(`Saw tab at index ${index} after remoteness flip`);
+ if (this.seenAfterTabs.has(e.target)) {
+ Assert.ok(false, "Saw tab after remoteness flip more than once");
+ }
+ this.seenAfterTabs.add(e.target);
+ break;
+ }
+ },
+ };
+
+ win.addEventListener("BeforeTabRemotenessChange", flipListener);
+ win.addEventListener("TabRemotenessChange", flipListener);
+
+ // Okay, time to test!
+ let state = prepareState(scenario.stateToRestore,
+ scenario.selectedTab);
+
+ SessionStore.setWindowState(win, state, true);
+
+ win.removeEventListener("BeforeTabRemotenessChange", flipListener);
+ win.removeEventListener("TabRemotenessChange", flipListener);
+
+ // Because we know that scenario.expectedFlips and
+ // scenario.expectedRemoteness have the same length, we
+ // can check that we satisfied both with the same loop.
+ for (let i = 0; i < scenario.expectedFlips.length; ++i) {
+ let expectedToFlip = scenario.expectedFlips[i];
+ let expectedRemoteness = scenario.expectedRemoteness[i];
+ let tab = tabbrowser.tabs[i];
+ if (expectedToFlip) {
+ Assert.ok(flipListener.seenBeforeTabs.has(tab),
+ `We should have seen tab at index ${i} before remoteness flip`);
+ Assert.ok(flipListener.seenAfterTabs.has(tab),
+ `We should have seen tab at index ${i} after remoteness flip`);
+ } else {
+ Assert.ok(!flipListener.seenBeforeTabs.has(tab),
+ `We should not have seen tab at index ${i} before remoteness flip`);
+ Assert.ok(!flipListener.seenAfterTabs.has(tab),
+ `We should not have seen tab at index ${i} after remoteness flip`);
+ }
+
+ Assert.equal(tab.linkedBrowser.isRemoteBrowser, expectedRemoteness,
+ "Should have gotten the expected remoteness " +
+ `for the tab at index ${i}`);
+ }
+
+ yield BrowserTestUtils.closeWindow(win);
+ }
+}
+
+/**
+ * Tests that if we restore state to browser windows with
+ * a variety of initial remoteness states, that we only flip
+ * the remoteness on the necessary tabs. For this particular
+ * set of tests, we assume that tabs are restoring on demand.
+ */
+add_task(function*() {
+ // This test opens and closes windows, which might bog down
+ // a debug build long enough to time out the test, so we
+ // extend the tolerance on timeouts.
+ requestLongerTimeout(5);
+
+ yield SpecialPowers.pushPrefEnv({
+ "set": [["browser.sessionstore.restore_on_demand", true]],
+ });
+
+ const TEST_SCENARIOS = [
+ // Only one tab in the new window, and it's remote. This
+ // is the common case, since this is how restoration occurs
+ // when the restored window is being opened.
+ {
+ initialRemoteness: [true],
+ initialSelectedTab: 1,
+ stateToRestore: SIMPLE_STATE,
+ selectedTab: 3,
+ // The initial tab is remote and should go into
+ // the background state. The second and third tabs
+ // are new and should be initialized non-remote.
+ expectedFlips: [true, false, true],
+ // Only the selected tab should be remote.
+ expectedRemoteness: [false, false, true],
+ },
+
+ // A single remote tab, and this is the one that's going
+ // to be selected once state is restored.
+ {
+ initialRemoteness: [true],
+ initialSelectedTab: 1,
+ stateToRestore: SIMPLE_STATE,
+ selectedTab: 1,
+ // The initial tab is remote and selected, so it should
+ // not flip remoteness. The other two new tabs should
+ // be non-remote by default.
+ expectedFlips: [false, false, false],
+ // Only the selected tab should be remote.
+ expectedRemoteness: [true, false, false],
+ },
+
+ // A single remote tab which starts selected. We set the
+ // selectedTab to 0 which is equivalent to "don't change
+ // the tab selection in the window".
+ {
+ initialRemoteness: [true],
+ initialSelectedTab: 1,
+ stateToRestore: SIMPLE_STATE,
+ selectedTab: 0,
+ // The initial tab is remote and selected, so it should
+ // not flip remoteness. The other two new tabs should
+ // be non-remote by default.
+ expectedFlips: [false, false, false],
+ // Only the selected tab should be remote.
+ expectedRemoteness: [true, false, false],
+ },
+
+ // An initially remote tab, but we're going to load
+ // some pinned tabs now, and the pinned tabs should load
+ // right away.
+ {
+ initialRemoteness: [true],
+ initialSelectedTab: 1,
+ stateToRestore: PINNED_STATE,
+ selectedTab: 3,
+ // The initial tab is pinned and will load right away,
+ // so it should stay remote. The second tab is new
+ // and pinned, so it should start remote and not flip.
+ // The third tab is not pinned, but it is selected,
+ // so it will start non-remote, and then flip remoteness.
+ expectedFlips: [false, false, true],
+ // Both pinned tabs and the selected tabs should all
+ // end up being remote.
+ expectedRemoteness: [true, true, true],
+ },
+
+ // A single non-remote tab.
+ {
+ initialRemoteness: [false],
+ initialSelectedTab: 1,
+ stateToRestore: SIMPLE_STATE,
+ selectedTab: 2,
+ // The initial tab is non-remote and should stay
+ // that way. The second and third tabs are new and
+ // should be initialized non-remote.
+ expectedFlips: [false, true, false],
+ // Only the selected tab should be remote.
+ expectedRemoteness: [false, true, false],
+ },
+
+ // A mixture of remote and non-remote tabs.
+ {
+ initialRemoteness: [true, false, true],
+ initialSelectedTab: 1,
+ stateToRestore: SIMPLE_STATE,
+ selectedTab: 3,
+ // The initial tab is remote and should flip to non-remote
+ // as it is put into the background. The second tab should
+ // stay non-remote, and the third one should stay remote.
+ expectedFlips: [true, false, false],
+ // Only the selected tab should be remote.
+ expectedRemoteness: [false, false, true],
+ },
+
+ // An initially non-remote tab, but we're going to load
+ // some pinned tabs now, and the pinned tabs should load
+ // right away.
+ {
+ initialRemoteness: [false],
+ initialSelectedTab: 1,
+ stateToRestore: PINNED_STATE,
+ selectedTab: 3,
+ // The initial tab is pinned and will load right away,
+ // so it should flip remoteness. The second tab is new
+ // and pinned, so it should start remote and not flip.
+ // The third tab is not pinned, but it is selected,
+ // so it will start non-remote, and then flip remoteness.
+ expectedFlips: [true, false, true],
+ // Both pinned tabs and the selected tabs should all
+ // end up being remote.
+ expectedRemoteness: [true, true, true],
+ },
+ ];
+
+ yield* runScenarios(TEST_SCENARIOS);
+});
diff --git a/browser/components/sessionstore/test/browser_replace_load.js b/browser/components/sessionstore/test/browser_replace_load.js
new file mode 100644
index 000000000..5464a0874
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_replace_load.js
@@ -0,0 +1,52 @@
+"use strict";
+
+const STATE = {
+ entries: [{url: "about:robots"}, {url: "about:mozilla"}],
+ selected: 2
+};
+
+/**
+ * Bug 1100223. Calling browser.loadURI() while a tab is loading causes
+ * sessionstore to override the desired target URL. This test ensures that
+ * calling loadURI() on a pending tab causes the tab to no longer be marked
+ * as pending and correctly finish the instructed load while keeping the
+ * restored history around.
+ */
+add_task(function* () {
+ yield testSwitchToTab("about:mozilla#fooobar", {ignoreFragment: "whenComparingAndReplace"});
+ yield testSwitchToTab("about:mozilla?foo=bar", {replaceQueryString: true});
+});
+
+var testSwitchToTab = Task.async(function* (url, options) {
+ // Create a background tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // The tab shouldn't be restored right away.
+ Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
+
+ // Prepare the tab state.
+ let promise = promiseTabRestoring(tab);
+ ss.setTabState(tab, JSON.stringify(STATE));
+ ok(tab.hasAttribute("pending"), "tab is pending");
+ yield promise;
+
+ // Switch-to-tab with a similar URI.
+ switchToTabHavingURI(url, false, options);
+
+ // Tab should now restore
+ yield promiseTabRestored(tab);
+ is(browser.currentURI.spec, url, "correct URL loaded");
+
+ // Check that we didn't lose any history entries.
+ yield ContentTask.spawn(browser, null, function* () {
+ let Ci = Components.interfaces;
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
+ Assert.equal(history && history.count, 3, "three history entries");
+ });
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js b/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
new file mode 100644
index 000000000..5767c6c0f
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
@@ -0,0 +1,171 @@
+/*
+ * Bug 1267910 - The regression test case for session cookies.
+ */
+
+"use strict";
+
+const TEST_HOST = "www.example.com";
+const COOKIE =
+{
+ name: "test1",
+ value: "yes1",
+ path: "/browser/browser/components/sessionstore/test/"
+};
+const SESSION_DATA = `
+{
+ "version": ["sessionrestore", 1],
+ "windows": [{
+ "tabs": [{
+ "entries": [],
+ "lastAccessed": 1463893009797,
+ "hidden": false,
+ "attributes": {},
+ "image": null
+ }, {
+ "entries": [{
+ "url": "http://www.example.com/browser/browser/components/sessionstore/test/browser_1267910_page.html",
+ \"charset": "UTF-8",
+ "ID": 0,
+ "docshellID": 2,
+ "originalURI": "http://www.example.com/browser/browser/components/sessionstore/test/browser_1267910_page.html",
+ \"docIdentifier": 0,
+ "persist": true
+ }],
+ "lastAccessed": 1463893009321,
+ "hidden": false,
+ "attributes": {},
+ "userContextId": 0,
+ "index": 1,
+ "image": "http://www.example.com/favicon.ico"
+ }],
+ "selected": 1,
+ "_closedTabs": [],
+ "busy": false,
+ "width": 1024,
+ "height": 768,
+ "screenX": 4,
+ "screenY": 23,
+ "sizemode": "normal",
+ "cookies": [{
+ "host": "www.example.com",
+ "value": "yes1",
+ "path": "/browser/browser/components/sessionstore/test/",
+ "name": "test1"
+ }]
+ }],
+ "selectedWindow": 1,
+ "_closedWindows": [],
+ "session": {
+ "lastUpdate": 1463893009801,
+ "startTime": 1463893007134,
+ "recentCrashes": 0
+ },
+ "global": {}
+}`;
+const SESSION_DATA_OA = `
+{
+ "version": ["sessionrestore", 1],
+ "windows": [{
+ "tabs": [{
+ "entries": [],
+ "lastAccessed": 1463893009797,
+ "hidden": false,
+ "attributes": {},
+ "image": null
+ }, {
+ "entries": [{
+ "url": "http://www.example.com/browser/browser/components/sessionstore/test/browser_1267910_page.html",
+ \"charset": "UTF-8",
+ "ID": 0,
+ "docshellID": 2,
+ "originalURI": "http://www.example.com/browser/browser/components/sessionstore/test/browser_1267910_page.html",
+ \"docIdentifier": 0,
+ "persist": true
+ }],
+ "lastAccessed": 1463893009321,
+ "hidden": false,
+ "attributes": {},
+ "userContextId": 0,
+ "index": 1,
+ "image": "http://www.example.com/favicon.ico"
+ }],
+ "selected": 1,
+ "_closedTabs": [],
+ "busy": false,
+ "width": 1024,
+ "height": 768,
+ "screenX": 4,
+ "screenY": 23,
+ "sizemode": "normal",
+ "cookies": [{
+ "host": "www.example.com",
+ "value": "yes1",
+ "path": "/browser/browser/components/sessionstore/test/",
+ "name": "test1",
+ "originAttributes": {
+ "addonId": "",
+ "appId": 0,
+ "inIsolatedMozBrowser": false,
+ "userContextId": 0
+ }
+ }]
+ }],
+ "selectedWindow": 1,
+ "_closedWindows": [],
+ "session": {
+ "lastUpdate": 1463893009801,
+ "startTime": 1463893007134,
+ "recentCrashes": 0
+ },
+ "global": {}
+}`;
+
+add_task(function* run_test() {
+ // Wait until initialization is complete.
+ yield SessionStore.promiseInitialized;
+
+ // Clear cookies.
+ Services.cookies.removeAll();
+
+ // Open a new window.
+ let win = yield promiseNewWindowLoaded();
+
+ // Restore window with session cookies that have no originAttributes.
+ ss.setWindowState(win, SESSION_DATA, true);
+
+ let enumerator = Services.cookies.getCookiesFromHost(TEST_HOST, {});
+ let cookie;
+ let cookieCount = 0;
+ while (enumerator.hasMoreElements()) {
+ cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ cookieCount++;
+ }
+
+ // Check that the cookie is restored successfully.
+ is(cookieCount, 1, "expected one cookie");
+ is(cookie.name, COOKIE.name, "cookie name successfully restored");
+ is(cookie.value, COOKIE.value, "cookie value successfully restored");
+ is(cookie.path, COOKIE.path, "cookie path successfully restored");
+
+ // Clear cookies.
+ Services.cookies.removeAll();
+
+ // Restore window with session cookies that have originAttributes within.
+ ss.setWindowState(win, SESSION_DATA_OA, true);
+
+ enumerator = Services.cookies.getCookiesFromHost(TEST_HOST, {});
+ cookieCount = 0;
+ while (enumerator.hasMoreElements()) {
+ cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ cookieCount++;
+ }
+
+ // Check that the cookie is restored successfully.
+ is(cookieCount, 1, "expected one cookie");
+ is(cookie.name, COOKIE.name, "cookie name successfully restored");
+ is(cookie.value, COOKIE.value, "cookie value successfully restored");
+ is(cookie.path, COOKIE.path, "cookie path successfully restored");
+
+ // Close our window.
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/sessionstore/test/browser_restore_redirect.js b/browser/components/sessionstore/test/browser_restore_redirect.js
new file mode 100644
index 000000000..bea6e9f47
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_restore_redirect.js
@@ -0,0 +1,69 @@
+"use strict";
+
+const BASE = "http://example.com/browser/browser/components/sessionstore/test/";
+const TARGET = BASE + "restore_redirect_target.html";
+
+/**
+ * Ensure that a http redirect leaves a working tab.
+ */
+add_task(function check_http_redirect() {
+ let state = {
+ entries: [{ url: BASE + "restore_redirect_http.html" }]
+ };
+
+ // Open a new tab to restore into.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseTabState(tab, state);
+
+ info("Restored tab");
+
+ yield TabStateFlusher.flush(browser);
+ let data = TabState.collect(tab);
+ is(data.entries.length, 1, "Should be one entry in session history");
+ is(data.entries[0].url, TARGET, "Should be the right session history entry");
+
+ ok(!("__SS_data" in browser), "Temporary restore data should have been cleared");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+});
+
+/**
+ * Ensure that a js redirect leaves a working tab.
+ */
+add_task(function check_js_redirect() {
+ let state = {
+ entries: [{ url: BASE + "restore_redirect_js.html" }]
+ };
+
+ let loadPromise = new Promise(resolve => {
+ function listener(msg) {
+ if (msg.data.url.endsWith("restore_redirect_target.html")) {
+ window.messageManager.removeMessageListener("ss-test:loadEvent", listener);
+ resolve();
+ }
+ }
+
+ window.messageManager.addMessageListener("ss-test:loadEvent", listener);
+ });
+
+ // Open a new tab to restore into.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseTabState(tab, state);
+
+ info("Restored tab");
+
+ yield loadPromise;
+
+ yield TabStateFlusher.flush(browser);
+ let data = TabState.collect(tab);
+ is(data.entries.length, 1, "Should be one entry in session history");
+ is(data.entries[0].url, TARGET, "Should be the right session history entry");
+
+ ok(!("__SS_data" in browser), "Temporary restore data should have been cleared");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_revive_crashed_bg_tabs.js b/browser/components/sessionstore/test/browser_revive_crashed_bg_tabs.js
new file mode 100644
index 000000000..e29cd5e49
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_revive_crashed_bg_tabs.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that even if the user has set their tabs to restore
+ * immediately on session start, that background tabs after a
+ * content process crash restore on demand.
+ */
+
+"use strict";
+
+const PAGE_1 = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+const PAGE_2 = "data:text/html,<html><body>Another%20regular,%20everyday,%20normal%20page.";
+
+add_task(function* setup() {
+ yield pushPrefs(["dom.ipc.processCount", 1],
+ ["browser.tabs.animate", false],
+ ["browser.sessionstore.restore_on_demand", false]);
+});
+
+add_task(function* test_revive_bg_tabs_on_demand() {
+ let newTab1 = gBrowser.addTab(PAGE_1);
+ let browser1 = newTab1.linkedBrowser;
+ gBrowser.selectedTab = newTab1;
+
+ let newTab2 = gBrowser.addTab(PAGE_2);
+ let browser2 = newTab2.linkedBrowser;
+
+ yield BrowserTestUtils.browserLoaded(browser1);
+ yield BrowserTestUtils.browserLoaded(browser2);
+
+ yield TabStateFlusher.flush(browser2);
+
+ // Now crash the selected tab
+ let windowReady = BrowserTestUtils.waitForEvent(window, "SSWindowStateReady");
+ yield BrowserTestUtils.crashBrowser(browser1);
+
+ ok(newTab1.hasAttribute("crashed"), "Selected tab should be crashed");
+ ok(!newTab2.hasAttribute("crashed"), "Background tab should not be crashed");
+
+ // Wait until we've had a chance to restore all tabs immediately
+ yield windowReady;
+
+ // But we should not have restored the background tab
+ ok(newTab2.hasAttribute("pending"), "Background tab should be pending");
+
+ // Now select newTab2 to make sure it restores.
+ let newTab2Restored = promiseTabRestored(newTab2);
+ gBrowser.selectedTab = newTab2;
+ yield newTab2Restored;
+
+ ok(browser2.isRemoteBrowser, "Restored browser should be remote");
+
+ yield BrowserTestUtils.removeTab(newTab1);
+ yield BrowserTestUtils.removeTab(newTab2);
+});
diff --git a/browser/components/sessionstore/test/browser_scrollPositions.js b/browser/components/sessionstore/test/browser_scrollPositions.js
new file mode 100644
index 000000000..865520772
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_scrollPositions.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BASE = "http://example.com/browser/browser/components/sessionstore/test/"
+const URL = BASE + "browser_scrollPositions_sample.html";
+const URL_FRAMESET = BASE + "browser_scrollPositions_sample_frameset.html";
+
+// Randomized set of scroll positions we will use in this test.
+const SCROLL_X = Math.round(100 * (1 + Math.random()));
+const SCROLL_Y = Math.round(200 * (1 + Math.random()));
+const SCROLL_STR = SCROLL_X + "," + SCROLL_Y;
+
+const SCROLL2_X = Math.round(300 * (1 + Math.random()));
+const SCROLL2_Y = Math.round(400 * (1 + Math.random()));
+const SCROLL2_STR = SCROLL2_X + "," + SCROLL2_Y;
+
+requestLongerTimeout(2);
+
+/**
+ * This test ensures that we properly serialize and restore scroll positions
+ * for an average page without any frames.
+ */
+add_task(function test_scroll() {
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Scroll down a little.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL_X, y: SCROLL_Y});
+ yield checkScroll(tab, {scroll: SCROLL_STR}, "scroll is fine");
+
+ // Duplicate and check that the scroll position is restored.
+ let tab2 = ss.duplicateTab(window, tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2);
+
+ let scroll = yield sendMessage(browser2, "ss-test:getScrollPosition");
+ is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}),
+ "scroll position has been duplicated correctly");
+
+ // Check that reloading retains the scroll positions.
+ browser2.reload();
+ yield promiseBrowserLoaded(browser2);
+ yield checkScroll(tab2, {scroll: SCROLL_STR}, "reloading retains scroll positions");
+
+ // Check that a force-reload resets scroll positions.
+ browser2.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ yield promiseBrowserLoaded(browser2);
+ yield checkScroll(tab2, null, "force-reload resets scroll positions");
+
+ // Scroll back to the top and check that the position has been reset. We
+ // expect the scroll position to be "null" here because there is no data to
+ // be stored if the frame is in its default scroll position.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0});
+ yield checkScroll(tab, null, "no scroll stored");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+ yield promiseRemoveTab(tab2);
+});
+
+/**
+ * This tests ensures that we properly serialize and restore scroll positions
+ * for multiple frames of pages with framesets.
+ */
+add_task(function test_scroll_nested() {
+ let tab = gBrowser.addTab(URL_FRAMESET);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Scroll the first child frame down a little.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL_X, y: SCROLL_Y, frame: 0});
+ yield checkScroll(tab, {children: [{scroll: SCROLL_STR}]}, "scroll is fine");
+
+ // Scroll the second child frame down a little.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL2_X, y: SCROLL2_Y, frame: 1});
+ yield checkScroll(tab, {children: [{scroll: SCROLL_STR}, {scroll: SCROLL2_STR}]}, "scroll is fine");
+
+ // Duplicate and check that the scroll position is restored.
+ let tab2 = ss.duplicateTab(window, tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2);
+
+ let scroll = yield sendMessage(browser2, "ss-test:getScrollPosition", {frame: 0});
+ is(JSON.stringify(scroll), JSON.stringify({x: SCROLL_X, y: SCROLL_Y}),
+ "scroll position #1 has been duplicated correctly");
+
+ scroll = yield sendMessage(browser2, "ss-test:getScrollPosition", {frame: 1});
+ is(JSON.stringify(scroll), JSON.stringify({x: SCROLL2_X, y: SCROLL2_Y}),
+ "scroll position #2 has been duplicated correctly");
+
+ // Check that resetting one frame's scroll position removes it from the
+ // serialized value.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0, frame: 0});
+ yield checkScroll(tab, {children: [null, {scroll: SCROLL2_STR}]}, "scroll is fine");
+
+ // Check the resetting all frames' scroll positions nulls the stored value.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: 0, frame: 1});
+ yield checkScroll(tab, null, "no scroll stored");
+
+ // Cleanup.
+ yield promiseRemoveTab(tab);
+ yield promiseRemoveTab(tab2);
+});
+
+/**
+ * Test that scroll positions persist after restoring background tabs in
+ * a restored window (bug 1228518).
+ */
+add_task(function test_scroll_background_tabs() {
+ pushPrefs(["browser.sessionstore.restore_on_demand", true]);
+
+ let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+ let tab = newWin.gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ // Scroll down a little.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: SCROLL_X, y: SCROLL_Y});
+ yield checkScroll(tab, {scroll: SCROLL_STR}, "scroll is fine");
+
+ // Close the window
+ yield BrowserTestUtils.closeWindow(newWin);
+
+ // Now restore the window
+ newWin = ss.undoCloseWindow(0);
+
+ // Make sure to wait for the window to be restored.
+ yield BrowserTestUtils.waitForEvent(newWin, "SSWindowStateReady");
+
+ is(newWin.gBrowser.tabs.length, 2, "There should be two tabs");
+
+ // The second tab should be the one we loaded URL at still
+ tab = newWin.gBrowser.tabs[1];
+ yield promiseTabRestoring(tab);
+
+ ok(tab.hasAttribute("pending"), "Tab should be pending");
+ browser = tab.linkedBrowser;
+
+ // Ensure there are no pending queued messages in the child.
+ yield TabStateFlusher.flush(browser);
+
+ // Now check to see if the background tab remembers where it
+ // should be scrolled to.
+ newWin.gBrowser.selectedTab = tab;
+ yield promiseTabRestored(tab);
+
+ yield checkScroll(tab, {scroll: SCROLL_STR}, "scroll is still fine");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+});
diff --git a/browser/components/sessionstore/test/browser_scrollPositionsReaderMode.js b/browser/components/sessionstore/test/browser_scrollPositionsReaderMode.js
new file mode 100644
index 000000000..735a87634
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_scrollPositionsReaderMode.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BASE = "http://example.com/browser/browser/components/sessionstore/test/"
+const READER_MODE_URL = "about:reader?url=" +
+ encodeURIComponent(BASE + "browser_scrollPositions_readerModeArticle.html");
+
+// Randomized set of scroll positions we will use in this test.
+const SCROLL_READER_MODE_Y = Math.round(400 * (1 + Math.random()));
+const SCROLL_READER_MODE_STR = "0," + SCROLL_READER_MODE_Y;
+
+requestLongerTimeout(2);
+
+/**
+ * Test that scroll positions of about reader page after restoring background
+ * tabs in a restored window (bug 1153393).
+ */
+add_task(function test_scroll_background_about_reader_tabs() {
+ pushPrefs(["browser.sessionstore.restore_on_demand", true]);
+
+ let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+ let tab = newWin.gBrowser.addTab(READER_MODE_URL);
+ let browser = tab.linkedBrowser;
+ yield Promise.all([
+ BrowserTestUtils.browserLoaded(browser),
+ BrowserTestUtils.waitForContentEvent(browser, "AboutReaderContentReady")
+ ]);
+
+ // Scroll down a little.
+ yield sendMessage(browser, "ss-test:setScrollPosition", {x: 0, y: SCROLL_READER_MODE_Y});
+ yield checkScroll(tab, {scroll: SCROLL_READER_MODE_STR}, "scroll is fine");
+
+ // Close the window
+ yield BrowserTestUtils.closeWindow(newWin);
+
+ // Now restore the window
+ newWin = ss.undoCloseWindow(0);
+
+ // Make sure to wait for the window to be restored.
+ yield BrowserTestUtils.waitForEvent(newWin, "SSWindowStateReady");
+
+ is(newWin.gBrowser.tabs.length, 2, "There should be two tabs");
+
+ // The second tab should be the one we loaded URL at still
+ tab = newWin.gBrowser.tabs[1];
+ yield promiseTabRestoring(tab);
+
+ ok(tab.hasAttribute("pending"), "Tab should be pending");
+ browser = tab.linkedBrowser;
+
+ // Ensure there are no pending queued messages in the child.
+ yield TabStateFlusher.flush(browser);
+
+ // Now check to see if the background tab remembers where it
+ // should be scrolled to.
+ newWin.gBrowser.selectedTab = tab;
+ yield Promise.all([
+ promiseTabRestored(tab),
+ BrowserTestUtils.waitForContentEvent(tab.linkedBrowser, "AboutReaderContentReady")
+ ]);
+
+ yield checkScroll(tab, {scroll: SCROLL_READER_MODE_STR}, "scroll is still fine");
+
+ yield BrowserTestUtils.closeWindow(newWin);
+});
diff --git a/browser/components/sessionstore/test/browser_scrollPositions_readerModeArticle.html b/browser/components/sessionstore/test/browser_scrollPositions_readerModeArticle.html
new file mode 100644
index 000000000..55452e043
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_scrollPositions_readerModeArticle.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Article title</title>
+<meta name="description" content="This is the article description." />
+</head>
+<body>
+<header>Site header</header>
+<div>
+<h1>Article title</h1>
+<h2 class="author">by Jane Doe</h2>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+</div>
+</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_scrollPositions_sample.html b/browser/components/sessionstore/test/browser_scrollPositions_sample.html
new file mode 100644
index 000000000..0182783db
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_scrollPositions_sample.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>browser_scrollPositions_sample.html</title>
+ </head>
+ <body style='width: 100000px; height: 100000px;'>top</body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html b/browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html
new file mode 100644
index 000000000..c7e363fa1
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_scrollPositions_sample_frameset.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>browser_scrollPositions_sample_frameset.html</title>
+ </head>
+ <frameset id="frames" rows="50%, 50%">
+ <frame src="browser_scrollPositions_sample.html">
+ <frame src="browser_scrollPositions_sample.html">
+ </frameset>
+</html>
diff --git a/browser/components/sessionstore/test/browser_send_async_message_oom.js b/browser/components/sessionstore/test/browser_send_async_message_oom.js
new file mode 100644
index 000000000..6afd771db
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_send_async_message_oom.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const HISTOGRAM_NAME = "FX_SESSION_RESTORE_SEND_UPDATE_CAUSED_OOM";
+
+/**
+ * Test that an OOM in sendAsyncMessage in a framescript will be reported
+ * to Telemetry.
+ */
+
+add_task(function* init() {
+ Services.telemetry.canRecordExtended = true;
+});
+
+function frameScript() {
+ // Make send[A]syncMessage("SessionStore:update", ...) simulate OOM.
+ // Other operations are unaffected.
+ let mm = docShell.sameTypeRootTreeItem.
+ QueryInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIContentFrameMessageManager);
+
+ let wrap = function(original) {
+ return function(name, ...args) {
+ if (name != "SessionStore:update") {
+ return original(name, ...args);
+ }
+ throw new Components.Exception("Simulated OOM", Cr.NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ mm.sendAsyncMessage = wrap(mm.sendAsyncMessage);
+ mm.sendSyncMessage = wrap(mm.sendSyncMessage);
+}
+
+add_task(function*() {
+ // Capture original state.
+ let snapshot = Services.telemetry.getHistogramById(HISTOGRAM_NAME).snapshot();
+
+ // Open a browser, configure it to cause OOM.
+ let newTab = gBrowser.addTab("about:robots");
+ let browser = newTab.linkedBrowser;
+ yield ContentTask.spawn(browser, null, frameScript);
+
+
+ let promiseReported = new Promise(resolve => {
+ browser.messageManager.addMessageListener("SessionStore:error", resolve);
+ });
+
+ // Attempt to flush. This should fail.
+ let promiseFlushed = TabStateFlusher.flush(browser);
+ promiseFlushed.then((success) => {
+ if (success) {
+ throw new Error("Flush should have failed")
+ }
+ });
+
+ // The frame script should report an error.
+ yield promiseReported;
+
+ // Give us some time to handle that error.
+ yield new Promise(resolve => setTimeout(resolve, 10));
+
+ // By now, Telemetry should have been updated.
+ let snapshot2 = Services.telemetry.getHistogramById(HISTOGRAM_NAME).snapshot();
+ gBrowser.removeTab(newTab);
+
+ Assert.ok(snapshot2.sum > snapshot.sum);
+});
+
+add_task(function* cleanup() {
+ Services.telemetry.canRecordExtended = false;
+});
diff --git a/browser/components/sessionstore/test/browser_sessionHistory.js b/browser/components/sessionstore/test/browser_sessionHistory.js
new file mode 100644
index 000000000..f4523e06a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionHistory.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+/**
+ * Ensure that starting a load invalidates shistory.
+ */
+add_task(function test_load_start() {
+ // Create a new tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Load a new URI.
+ yield BrowserTestUtils.loadURI(browser, "about:mozilla");
+
+ // Remove the tab before it has finished loading.
+ yield promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
+ yield promiseRemoveTab(tab);
+
+ // Undo close the tab.
+ tab = ss.undoCloseTab(window, 0);
+ browser = tab.linkedBrowser;
+ yield promiseTabRestored(tab);
+
+ // Check that the correct URL was restored.
+ is(browser.currentURI.spec, "about:mozilla", "url is correct");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that anchor navigation invalidates shistory.
+ */
+add_task(function test_hashchange() {
+ const URL = "data:text/html;charset=utf-8,<a id=a href=%23>clickme</a>";
+
+ // Create a new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Check that we start with a single shistory entry.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is one shistory entry");
+
+ // Click the link and wait for a hashchange event.
+ browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a"});
+ yield promiseContentMessage(browser, "ss-test:hashchange");
+
+ // Check that we now have two shistory entries.
+ yield TabStateFlusher.flush(browser);
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 2, "there are two shistory entries");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that loading pages from the bfcache invalidates shistory.
+ */
+add_task(function test_pageshow() {
+ const URL = "data:text/html;charset=utf-8,<h1>first</h1>";
+ const URL2 = "data:text/html;charset=utf-8,<h1>second</h1>";
+
+ // Create a new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Create a second shistory entry.
+ browser.loadURI(URL2);
+ yield promiseBrowserLoaded(browser);
+
+ // Go back to the previous url which is loaded from the bfcache.
+ browser.goBack();
+ yield promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
+ is(browser.currentURI.spec, URL, "correct url after going back");
+
+ // Check that loading from bfcache did invalidate shistory.
+ yield TabStateFlusher.flush(browser);
+ let {index} = JSON.parse(ss.getTabState(tab));
+ is(index, 1, "first history entry is selected");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that subframe navigation invalidates shistory.
+ */
+add_task(function test_subframes() {
+ const URL = "data:text/html;charset=utf-8," +
+ "<iframe src=http%3A//example.com/ name=t></iframe>" +
+ "<a id=a1 href=http%3A//example.com/1 target=t>clickme</a>" +
+ "<a id=a2 href=http%3A//example.com/%23 target=t>clickme</a>";
+
+ // Create a new tab.
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Check that we have a single shistory entry.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is one shistory entry");
+ is(entries[0].children.length, 1, "the entry has one child");
+
+ // Navigate the subframe.
+ browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a1"});
+ yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
+
+ // Check shistory.
+ yield TabStateFlusher.flush(browser);
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 2, "there now are two shistory entries");
+ is(entries[1].children.length, 1, "the second entry has one child");
+
+ // Go back in history.
+ browser.goBack();
+ yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
+
+ // Navigate the subframe again.
+ browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a2"});
+ yield promiseContentMessage(browser, "ss-test:hashchange");
+
+ // Check shistory.
+ yield TabStateFlusher.flush(browser);
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 2, "there now are two shistory entries");
+ is(entries[1].children.length, 1, "the second entry has one child");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that navigating from an about page invalidates shistory.
+ */
+add_task(function test_about_page_navigate() {
+ // Create a new tab.
+ let tab = gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Check that we have a single shistory entry.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is one shistory entry");
+ is(entries[0].url, "about:blank", "url is correct");
+
+ browser.loadURI("about:robots");
+ yield promiseBrowserLoaded(browser);
+
+ // Check that we have changed the history entry.
+ yield TabStateFlusher.flush(browser);
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 1, "there is one shistory entry");
+ is(entries[0].url, "about:robots", "url is correct");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that history.pushState and history.replaceState invalidate shistory.
+ */
+add_task(function test_pushstate_replacestate() {
+ // Create a new tab.
+ let tab = gBrowser.addTab("http://example.com/1");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Check that we have a single shistory entry.
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+ is(entries.length, 1, "there is one shistory entry");
+ is(entries[0].url, "http://example.com/1", "url is correct");
+
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.window.history.pushState({}, "", 'test-entry/');
+ });
+
+ // Check that we have added the history entry.
+ yield TabStateFlusher.flush(browser);
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 2, "there is another shistory entry");
+ is(entries[1].url, "http://example.com/test-entry/", "url is correct");
+
+ yield ContentTask.spawn(browser, {}, function* () {
+ content.window.history.replaceState({}, "", "test-entry2/");
+ });
+
+ // Check that we have modified the history entry.
+ yield TabStateFlusher.flush(browser);
+ ({entries} = JSON.parse(ss.getTabState(tab)));
+ is(entries.length, 2, "there is still two shistory entries");
+ is(entries[1].url, "http://example.com/test-entry/test-entry2/", "url is correct");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that slow loading subframes will invalidate shistory.
+ */
+add_task(function test_slow_subframe_load() {
+ const SLOW_URL = "http://mochi.test:8888/browser/browser/components/" +
+ "sessionstore/test/browser_sessionHistory_slow.sjs";
+
+ const URL = "data:text/html;charset=utf-8," +
+ "<frameset cols=50%25,50%25>" +
+ "<frame src='" + SLOW_URL + "'>" +
+ "</frameset>";
+
+ // Add a new tab with a slow loading subframe
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ yield TabStateFlusher.flush(browser);
+ let {entries} = JSON.parse(ss.getTabState(tab));
+
+ // Check the number of children.
+ is(entries.length, 1, "there is one root entry ...");
+ is(entries[0].children.length, 1, "... with one child entries");
+
+ // Check URLs.
+ ok(entries[0].url.startsWith("data:text/html"), "correct root url");
+ is(entries[0].children[0].url, SLOW_URL, "correct url for subframe");
+
+ // Cleanup.
+ gBrowser.removeTab(tab);
+});
diff --git a/browser/components/sessionstore/test/browser_sessionHistory_slow.sjs b/browser/components/sessionstore/test/browser_sessionHistory_slow.sjs
new file mode 100644
index 000000000..41da3c2ad
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionHistory_slow.sjs
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const DELAY_MS = "2000";
+
+let timer;
+
+function handleRequest(req, resp) {
+ resp.processAsync();
+ resp.setHeader("Cache-Control", "no-cache", false);
+ resp.setHeader("Content-Type", "text/html;charset=utf-8", false);
+
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(() => {
+ resp.write("hi");
+ resp.finish();
+ }, DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/browser/components/sessionstore/test/browser_sessionStorage.html b/browser/components/sessionstore/test/browser_sessionStorage.html
new file mode 100644
index 000000000..7e2dccf4a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionStorage.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>browser_sessionStorage.html</title>
+ </head>
+ <body>
+ <script type="text/javascript;version=1.8">
+ let isOuter = window == window.top;
+ let args = window.location.search.slice(1).split("&");
+ let rand = args[0];
+
+ if (isOuter) {
+ let iframe = document.createElement("iframe");
+ let isSecure = args.indexOf("secure") > -1;
+ let scheme = isSecure ? "https" : "http";
+ iframe.setAttribute("src", scheme + "://example.com" + location.pathname + "?" + rand);
+ document.body.appendChild(iframe);
+ }
+
+ if (sessionStorage.length === 0) {
+ sessionStorage.test = (isOuter ? "outer" : "inner") + "-value-" + rand;
+ document.title = sessionStorage.test;
+ }
+ </script>
+ </body>
+</html>
diff --git a/browser/components/sessionstore/test/browser_sessionStorage.js b/browser/components/sessionstore/test/browser_sessionStorage.js
new file mode 100644
index 000000000..b580c5cc2
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionStorage.js
@@ -0,0 +1,188 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const RAND = Math.random();
+const URL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_sessionStorage.html" +
+ "?" + RAND;
+
+const OUTER_VALUE = "outer-value-" + RAND;
+const INNER_VALUE = "inner-value-" + RAND;
+
+/**
+ * This test ensures that setting, modifying and restoring sessionStorage data
+ * works as expected.
+ */
+add_task(function session_storage() {
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to make sure chrome received all data.
+ yield TabStateFlusher.flush(browser);
+
+ let {storage} = JSON.parse(ss.getTabState(tab));
+ is(storage["http://example.com"].test, INNER_VALUE,
+ "sessionStorage data for example.com has been serialized correctly");
+ is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
+ "sessionStorage data for mochi.test has been serialized correctly");
+
+ // Ensure that modifying sessionStore values works for the inner frame only.
+ yield modifySessionStorage(browser, {test: "modified1"}, {frameIndex: 0});
+ yield TabStateFlusher.flush(browser);
+
+ ({storage} = JSON.parse(ss.getTabState(tab)));
+ is(storage["http://example.com"].test, "modified1",
+ "sessionStorage data for example.com has been serialized correctly");
+ is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
+ "sessionStorage data for mochi.test has been serialized correctly");
+
+ // Ensure that modifying sessionStore values works for both frames.
+ yield modifySessionStorage(browser, {test: "modified"});
+ yield modifySessionStorage(browser, {test: "modified2"}, {frameIndex: 0});
+ yield TabStateFlusher.flush(browser);
+
+ ({storage} = JSON.parse(ss.getTabState(tab)));
+ is(storage["http://example.com"].test, "modified2",
+ "sessionStorage data for example.com has been serialized correctly");
+ is(storage["http://mochi.test:8888"].test, "modified",
+ "sessionStorage data for mochi.test has been serialized correctly");
+
+ // Test that duplicating a tab works.
+ let tab2 = gBrowser.duplicateTab(tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2);
+
+ // Flush to make sure chrome received all data.
+ yield TabStateFlusher.flush(browser2);
+
+ ({storage} = JSON.parse(ss.getTabState(tab2)));
+ is(storage["http://example.com"].test, "modified2",
+ "sessionStorage data for example.com has been duplicated correctly");
+ is(storage["http://mochi.test:8888"].test, "modified",
+ "sessionStorage data for mochi.test has been duplicated correctly");
+
+ // Ensure that the content script retains restored data
+ // (by e.g. duplicateTab) and sends it along with new data.
+ yield modifySessionStorage(browser2, {test: "modified3"});
+ yield TabStateFlusher.flush(browser2);
+
+ ({storage} = JSON.parse(ss.getTabState(tab2)));
+ is(storage["http://example.com"].test, "modified2",
+ "sessionStorage data for example.com has been duplicated correctly");
+ is(storage["http://mochi.test:8888"].test, "modified3",
+ "sessionStorage data for mochi.test has been duplicated correctly");
+
+ // Check that loading a new URL discards data.
+ browser2.loadURI("http://mochi.test:8888/");
+ yield promiseBrowserLoaded(browser2);
+ yield TabStateFlusher.flush(browser2);
+
+ ({storage} = JSON.parse(ss.getTabState(tab2)));
+ is(storage["http://mochi.test:8888"].test, "modified3",
+ "navigating retains correct storage data");
+ ok(!storage["http://example.com"], "storage data was discarded");
+
+ // Check that loading a new URL discards data.
+ browser2.loadURI("about:mozilla");
+ yield promiseBrowserLoaded(browser2);
+ yield TabStateFlusher.flush(browser2);
+
+ let state = JSON.parse(ss.getTabState(tab2));
+ ok(!state.hasOwnProperty("storage"), "storage data was discarded");
+
+ // Clean up.
+ yield promiseRemoveTab(tab);
+ yield promiseRemoveTab(tab2);
+});
+
+/**
+ * This test ensures that purging domain data also purges data from the
+ * sessionStorage data collected for tabs.
+ */
+add_task(function purge_domain() {
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Purge data for "mochi.test".
+ yield purgeDomainData(browser, "mochi.test");
+
+ // Flush to make sure chrome received all data.
+ yield TabStateFlusher.flush(browser);
+
+ let {storage} = JSON.parse(ss.getTabState(tab));
+ ok(!storage["http://mochi.test:8888"],
+ "sessionStorage data for mochi.test has been purged");
+ is(storage["http://example.com"].test, INNER_VALUE,
+ "sessionStorage data for example.com has been preserved");
+
+ yield promiseRemoveTab(tab);
+});
+
+/**
+ * This test ensures that collecting sessionStorage data respects the privacy
+ * levels as set by the user.
+ */
+add_task(function respect_privacy_level() {
+ let tab = gBrowser.addTab(URL + "&secure");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield promiseRemoveTab(tab);
+
+ let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
+ is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
+ "http sessionStorage data has been saved");
+ is(storage["https://example.com"].test, INNER_VALUE,
+ "https sessionStorage data has been saved");
+
+ // Disable saving data for encrypted sites.
+ Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1);
+
+ tab = gBrowser.addTab(URL + "&secure");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield promiseRemoveTab(tab);
+
+ [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
+ is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
+ "http sessionStorage data has been saved");
+ ok(!storage["https://example.com"],
+ "https sessionStorage data has *not* been saved");
+
+ // Disable saving data for any site.
+ Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
+
+ // Check that duplicating a tab copies all private data.
+ tab = gBrowser.addTab(URL + "&secure");
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ let tab2 = gBrowser.duplicateTab(tab);
+ yield promiseTabRestored(tab2);
+ yield promiseRemoveTab(tab);
+
+ // With privacy_level=2 the |tab| shouldn't have any sessionStorage data.
+ [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
+ ok(!storage, "sessionStorage data has *not* been saved");
+
+ // Remove all closed tabs before continuing with the next test.
+ // As Date.now() isn't monotonic we might sometimes check
+ // the wrong closedTabData entry.
+ while (ss.getClosedTabCount(window) > 0) {
+ ss.forgetClosedTab(window, 0);
+ }
+
+ // Restore the default privacy level and close the duplicated tab.
+ Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
+ yield promiseRemoveTab(tab2);
+
+ // With privacy_level=0 the duplicated |tab2| should persist all data.
+ [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
+ is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
+ "http sessionStorage data has been saved");
+ is(storage["https://example.com"].test, INNER_VALUE,
+ "https sessionStorage data has been saved");
+});
+
+function purgeDomainData(browser, domain) {
+ return sendMessage(browser, "ss-test:purgeDomainData", domain);
+}
diff --git a/browser/components/sessionstore/test/browser_sessionStorage_size.js b/browser/components/sessionstore/test/browser_sessionStorage_size.js
new file mode 100644
index 000000000..d1d894611
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionStorage_size.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const RAND = Math.random();
+const URL = "http://mochi.test:8888/browser/" +
+ "browser/components/sessionstore/test/browser_sessionStorage.html" +
+ "?" + RAND;
+
+const OUTER_VALUE = "outer-value-" + RAND;
+
+// Test that we record the size of messages.
+add_task(function* test_telemetry() {
+ Services.telemetry.canRecordExtended = true;
+ let histogram = Services.telemetry.getHistogramById("FX_SESSION_RESTORE_DOM_STORAGE_SIZE_ESTIMATE_CHARS");
+ let snap1 = histogram.snapshot();
+
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to make sure chrome received all data.
+ yield TabStateFlusher.flush(browser);
+ let snap2 = histogram.snapshot();
+
+ Assert.ok(snap2.counts[5] > snap1.counts[5]);
+ yield promiseRemoveTab(tab);
+ Services.telemetry.canRecordExtended = false;
+});
+
+// Lower the size limit for DOM Storage content. Check that DOM Storage
+// is not updated, but that other things remain updated.
+add_task(function* test_large_content() {
+ Services.prefs.setIntPref("browser.sessionstore.dom_storage_limit", 5);
+
+ let tab = gBrowser.addTab(URL);
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+
+ // Flush to make sure chrome received all data.
+ yield TabStateFlusher.flush(browser);
+
+ let state = JSON.parse(ss.getTabState(tab));
+ info(JSON.stringify(state, null, "\t"));
+ Assert.equal(state.storage, null, "We have no storage for the tab");
+ Assert.equal(state.entries[0].title, OUTER_VALUE);
+ yield promiseRemoveTab(tab);
+
+ Services.prefs.clearUserPref("browser.sessionstore.dom_storage_limit");
+});
diff --git a/browser/components/sessionstore/test/browser_sessionStoreContainer.js b/browser/components/sessionstore/test/browser_sessionStoreContainer.js
new file mode 100644
index 000000000..1bc9537e2
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionStoreContainer.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(function* () {
+ for (let i = 0; i < 3; ++i) {
+ let tab = gBrowser.addTab("http://example.com/", { userContextId: i });
+ let browser = tab.linkedBrowser;
+
+ yield promiseBrowserLoaded(browser);
+
+ let tab2 = gBrowser.duplicateTab(tab);
+ Assert.equal(tab2.getAttribute("usercontextid"), i);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2)
+
+ yield ContentTask.spawn(browser2, { expectedId: i }, function* (args) {
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ Assert.equal(loadContext.originAttributes.userContextId,
+ args.expectedId, "The docShell has the correct userContextId");
+ });
+
+ yield promiseRemoveTab(tab);
+ yield promiseRemoveTab(tab2);
+ }
+});
+
+add_task(function* () {
+ let tab = gBrowser.addTab("http://example.com/", { userContextId: 1 });
+ let browser = tab.linkedBrowser;
+
+ yield promiseBrowserLoaded(browser);
+
+ gBrowser.selectedTab = tab;
+
+ let tab2 = gBrowser.duplicateTab(tab);
+ let browser2 = tab2.linkedBrowser;
+ yield promiseTabRestored(tab2)
+
+ yield ContentTask.spawn(browser2, { expectedId: 1 }, function* (args) {
+ Assert.equal(docShell.getOriginAttributes().userContextId,
+ args.expectedId,
+ "The docShell has the correct userContextId");
+ });
+
+ yield promiseRemoveTab(tab);
+ yield promiseRemoveTab(tab2);
+});
+
+add_task(function* () {
+ let tab = gBrowser.addTab("http://example.com/", { userContextId: 1 });
+ let browser = tab.linkedBrowser;
+
+ yield promiseBrowserLoaded(browser);
+
+ gBrowser.removeTab(tab);
+
+ let tab2 = ss.undoCloseTab(window, 0);
+ Assert.equal(tab2.getAttribute("usercontextid"), 1);
+ yield promiseTabRestored(tab2);
+ yield ContentTask.spawn(tab2.linkedBrowser, { expectedId: 1 }, function* (args) {
+ Assert.equal(docShell.getOriginAttributes().userContextId,
+ args.expectedId,
+ "The docShell has the correct userContextId");
+ });
+
+ yield promiseRemoveTab(tab2);
+});
+
+// Opens "uri" in a new tab with the provided userContextId and focuses it.
+// Returns the newly opened tab.
+function* openTabInUserContext(userContextId) {
+ // Open the tab in the correct userContextId.
+ let tab = gBrowser.addTab("http://example.com", { userContextId });
+
+ // Select tab and make sure its browser is focused.
+ gBrowser.selectedTab = tab;
+ tab.ownerGlobal.focus();
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+ return { tab, browser };
+}
+
+function waitForNewCookie() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subj, topic, data) {
+ let cookie = subj.QueryInterface(Ci.nsICookie2);
+ if (data == "added") {
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }
+ }, "cookie-changed", false);
+ });
+}
+
+add_task(function* test() {
+ const USER_CONTEXTS = [
+ "default",
+ "personal",
+ "work",
+ ];
+
+ const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ const { TabStateFlusher } = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+ // Make sure userContext is enabled.
+ yield SpecialPowers.pushPrefEnv({
+ "set": [ [ "privacy.userContext.enabled", true ] ]
+ });
+
+ let lastSessionRestore;
+ for (let userContextId of Object.keys(USER_CONTEXTS)) {
+ // Load the page in 3 different contexts and set a cookie
+ // which should only be visible in that context.
+ let cookie = USER_CONTEXTS[userContextId];
+
+ // Open our tab in the given user context.
+ let { tab, browser } = yield* openTabInUserContext(userContextId);
+
+ yield Promise.all([
+ waitForNewCookie(),
+ ContentTask.spawn(browser, cookie, cookie => content.document.cookie = cookie)
+ ]);
+
+ // Ensure the tab's session history is up-to-date.
+ yield TabStateFlusher.flush(browser);
+
+ lastSessionRestore = ss.getWindowState(window);
+
+ // Remove the tab.
+ gBrowser.removeTab(tab);
+ }
+
+ let state = JSON.parse(lastSessionRestore);
+ is(state.windows[0].cookies.length, USER_CONTEXTS.length,
+ "session restore should have each container's cookie");
+});
+
diff --git a/browser/components/sessionstore/test/browser_swapDocShells.js b/browser/components/sessionstore/test/browser_swapDocShells.js
new file mode 100644
index 000000000..839f060e7
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_swapDocShells.js
@@ -0,0 +1,35 @@
+"use strict";
+
+add_task(function* () {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
+ yield promiseBrowserLoaded(gBrowser.selectedBrowser);
+
+ let win = gBrowser.replaceTabWithWindow(tab);
+ yield promiseDelayedStartupFinished(win);
+ yield promiseBrowserHasURL(win.gBrowser.browsers[0], "about:mozilla");
+
+ win.duplicateTabIn(win.gBrowser.selectedTab, "tab");
+ yield promiseTabRestored(win.gBrowser.tabs[1]);
+
+ let browser = win.gBrowser.browsers[1];
+ is(browser.currentURI.spec, "about:mozilla", "tab was duplicated");
+
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+function promiseDelayedStartupFinished(win) {
+ let deferred = Promise.defer();
+ whenDelayedStartupFinished(win, deferred.resolve);
+ return deferred.promise;
+}
+
+function promiseBrowserHasURL(browser, url) {
+ let promise = Promise.resolve();
+
+ if (browser.contentDocument.readyState === "complete" &&
+ browser.currentURI.spec === url) {
+ return promise;
+ }
+
+ return promise.then(() => promiseBrowserHasURL(browser, url));
+}
diff --git a/browser/components/sessionstore/test/browser_switch_remoteness.js b/browser/components/sessionstore/test/browser_switch_remoteness.js
new file mode 100644
index 000000000..9eb8c260a
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_switch_remoteness.js
@@ -0,0 +1,49 @@
+"use strict";
+
+const URL = "http://example.com/browser_switch_remoteness_";
+
+function countHistoryEntries(browser, expected) {
+ return ContentTask.spawn(browser, { expected }, function* (args) {
+ let Ci = Components.interfaces;
+ let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let history = webNavigation.sessionHistory.QueryInterface(Ci.nsISHistoryInternal);
+ Assert.equal(history && history.count, args.expected,
+ "correct number of shistory entries");
+ });
+}
+
+add_task(function* () {
+ // Open a new window.
+ let win = yield promiseNewWindowLoaded();
+
+ // Add a new tab.
+ let tab = win.gBrowser.addTab("about:blank");
+ let browser = tab.linkedBrowser;
+ yield promiseBrowserLoaded(browser);
+ ok(browser.isRemoteBrowser, "browser is remote");
+
+ // Get the maximum number of preceding entries to save.
+ const MAX_BACK = Services.prefs.getIntPref("browser.sessionstore.max_serialize_back");
+ ok(MAX_BACK > -1, "check that the default has a value that caps data");
+
+ // Load more pages than we would save to disk on a clean shutdown.
+ for (let i = 0; i < MAX_BACK + 2; i++) {
+ browser.loadURI(URL + i);
+ yield promiseBrowserLoaded(browser);
+ ok(browser.isRemoteBrowser, "browser is still remote");
+ }
+
+ // Check we have the right number of shistory entries.
+ yield countHistoryEntries(browser, MAX_BACK + 2);
+
+ // Load a non-remote page.
+ browser.loadURI("about:robots");
+ yield promiseTabRestored(tab);
+ ok(!browser.isRemoteBrowser, "browser is not remote anymore");
+
+ // Check that we didn't lose any shistory entries.
+ yield countHistoryEntries(browser, MAX_BACK + 3);
+
+ // Cleanup.
+ yield BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/sessionstore/test/browser_undoCloseById.js b/browser/components/sessionstore/test/browser_undoCloseById.js
new file mode 100644
index 000000000..f2f0f919c
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_undoCloseById.js
@@ -0,0 +1,118 @@
+"use strict";
+
+/**
+ * This test is for the undoCloseById function.
+ */
+
+Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
+
+function openAndCloseTab(window, url) {
+ let tab = window.gBrowser.addTab(url);
+ yield promiseBrowserLoaded(tab.linkedBrowser, true, url);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ yield promiseRemoveTab(tab);
+}
+
+function* openWindow(url) {
+ let win = yield promiseNewWindowLoaded();
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
+ win.gBrowser.selectedBrowser.loadURIWithFlags(url, flags);
+ yield promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, url);
+ return win;
+}
+
+function closeWindow(win) {
+ yield BrowserTestUtils.closeWindow(win);
+ // Wait 20 ms to allow SessionStorage a chance to register the closed window.
+ yield new Promise(resolve => setTimeout(resolve, 20));
+}
+
+add_task(function* test_undoCloseById() {
+ // Clear the lists of closed windows and tabs.
+ forgetClosedWindows();
+ while (SessionStore.getClosedTabCount(window)) {
+ SessionStore.forgetClosedTab(window, 0);
+ }
+
+ // Open a new window.
+ let win = yield openWindow("about:robots");
+
+ // Open and close a tab.
+ yield openAndCloseTab(win, "about:mozilla");
+ is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab");
+
+ // Record the first closedId created.
+ let initialClosedId = SessionStore.getClosedTabData(win, false)[0].closedId;
+
+ // Open and close another window.
+ let win2 = yield openWindow("about:mozilla");
+ yield closeWindow(win2); // closedId == initialClosedId + 1
+ is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window");
+
+ // Open and close another tab in the first window.
+ yield openAndCloseTab(win, "about:robots"); // closedId == initialClosedId + 2
+ is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab");
+
+ // Undo closing the second tab.
+ let tab = SessionStore.undoCloseById(initialClosedId + 2);
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ is(tab.linkedBrowser.currentURI.spec, "about:robots", "The expected tab was re-opened");
+
+ let notTab = SessionStore.undoCloseById(initialClosedId + 2);
+ is(notTab, undefined, "Re-opened tab cannot be unClosed again by closedId");
+
+ // Now the last closed object should be a window again.
+ is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window");
+
+ // Undo closing the first tab.
+ let tab2 = SessionStore.undoCloseById(initialClosedId);
+ yield promiseBrowserLoaded(tab2.linkedBrowser);
+ is(tab2.linkedBrowser.currentURI.spec, "about:mozilla", "The expected tab was re-opened");
+
+ // Close the two tabs we re-opened.
+ yield promiseRemoveTab(tab); // closedId == initialClosedId + 3
+ is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab");
+ yield promiseRemoveTab(tab2); // closedId == initialClosedId + 4
+ is(SessionStore.lastClosedObjectType, "tab", "The last closed object is a tab");
+
+ // Open another new window.
+ let win3 = yield openWindow("about:mozilla");
+
+ // Close both windows.
+ yield closeWindow(win); // closedId == initialClosedId + 5
+ is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window");
+ yield closeWindow(win3); // closedId == initialClosedId + 6
+ is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window");
+
+ // Undo closing the second window.
+ win = SessionStore.undoCloseById(initialClosedId + 6);
+ yield BrowserTestUtils.waitForEvent(win, "load");
+
+ // Make sure we wait until this window is restored.
+ yield BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer,
+ "SSTabRestored");
+
+ is(win.gBrowser.selectedBrowser.currentURI.spec, "about:mozilla", "The expected window was re-opened");
+
+ let notWin = SessionStore.undoCloseById(initialClosedId + 6);
+ is(notWin, undefined, "Re-opened window cannot be unClosed again by closedId");
+
+ // Close the window again.
+ yield closeWindow(win);
+ is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window");
+
+ // Undo closing the first window.
+ win = SessionStore.undoCloseById(initialClosedId + 5);
+
+ yield BrowserTestUtils.waitForEvent(win, "load");
+
+ // Make sure we wait until this window is restored.
+ yield BrowserTestUtils.waitForEvent(win.gBrowser.tabContainer,
+ "SSTabRestored");
+
+ is(win.gBrowser.selectedBrowser.currentURI.spec, "about:robots", "The expected window was re-opened");
+
+ // Close the window again.
+ yield closeWindow(win);
+ is(SessionStore.lastClosedObjectType, "window", "The last closed object is a window");
+});
diff --git a/browser/components/sessionstore/test/browser_unrestored_crashedTabs.js b/browser/components/sessionstore/test/browser_unrestored_crashedTabs.js
new file mode 100644
index 000000000..e46348e59
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_unrestored_crashedTabs.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that if we have tabs that are still in the "click to
+ * restore" state, that if their browsers crash, that we don't
+ * show the crashed state for those tabs (since selecting them
+ * should restore them anyway).
+ */
+
+const PREF = "browser.sessionstore.restore_on_demand";
+const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
+
+add_task(function* test() {
+ yield pushPrefs([PREF, true]);
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: PAGE,
+ }, function*(browser) {
+ yield TabStateFlusher.flush(browser);
+
+ // We'll create a second "pending" tab. This is the one we'll
+ // ensure doesn't go to about:tabcrashed. We start it non-remote
+ // since this is how SessionStore creates all browsers before
+ // they are restored.
+ let unrestoredTab = gBrowser.addTab("about:blank", {
+ skipAnimation: true,
+ forceNotRemote: true,
+ });
+
+ let state = {
+ entries: [{url: PAGE}],
+ };
+
+ ss.setTabState(unrestoredTab, JSON.stringify(state));
+
+ ok(!unrestoredTab.hasAttribute("crashed"), "tab is not crashed");
+ ok(unrestoredTab.hasAttribute("pending"), "tab is pending");
+
+ // Now crash the selected browser.
+ yield BrowserTestUtils.crashBrowser(browser);
+
+ ok(!unrestoredTab.hasAttribute("crashed"), "tab is still not crashed");
+ ok(unrestoredTab.hasAttribute("pending"), "tab is still pending");
+
+ // Selecting the tab should now restore it.
+ gBrowser.selectedTab = unrestoredTab;
+ yield promiseTabRestored(unrestoredTab);
+
+ ok(!unrestoredTab.hasAttribute("crashed"), "tab is still not crashed");
+ ok(!unrestoredTab.hasAttribute("pending"), "tab is no longer pending");
+
+ // The original tab should still be crashed
+ let originalTab = gBrowser.getTabForBrowser(browser);
+ ok(originalTab.hasAttribute("crashed"), "original tab is crashed");
+ ok(!originalTab.isRemoteBrowser, "Should not be remote");
+
+ // We'd better be able to restore it still.
+ gBrowser.selectedTab = originalTab;
+ SessionStore.reviveCrashedTab(originalTab);
+ yield promiseTabRestored(originalTab);
+
+ // Clean up.
+ yield BrowserTestUtils.removeTab(unrestoredTab);
+ });
+});
diff --git a/browser/components/sessionstore/test/browser_upgrade_backup.js b/browser/components/sessionstore/test/browser_upgrade_backup.js
new file mode 100644
index 000000000..768671051
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_upgrade_backup.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/Preferences.jsm", this);
+
+const Paths = SessionFile.Paths;
+const PREF_UPGRADE = "browser.sessionstore.upgradeBackup.latestBuildID";
+const PREF_MAX_UPGRADE_BACKUPS = "browser.sessionstore.upgradeBackup.maxUpgradeBackups";
+
+/**
+ * Prepares tests by retrieving the current platform's build ID, clearing the
+ * build where the last backup was created and creating arbitrary JSON data
+ * for a new backup.
+ */
+var prepareTest = Task.async(function* () {
+ let result = {};
+
+ result.buildID = Services.appinfo.platformBuildID;
+ Services.prefs.setCharPref(PREF_UPGRADE, "");
+ result.contents = JSON.stringify({"browser_upgrade_backup.js": Math.random()});
+
+ return result;
+});
+
+/**
+ * Retrieves all upgrade backups and returns them in an array.
+ */
+var getUpgradeBackups = Task.async(function* () {
+ let iterator;
+ let backups = [];
+ let upgradeBackupPrefix = Paths.upgradeBackupPrefix;
+
+ try {
+ iterator = new OS.File.DirectoryIterator(Paths.backups);
+
+ // iterate over all files in the backup directory
+ yield iterator.forEach(function (file) {
+ // check the upgradeBackupPrefix
+ if (file.path.startsWith(Paths.upgradeBackupPrefix)) {
+ // the file is a backup
+ backups.push(file.path);
+ }
+ }, this);
+ } finally {
+ if (iterator) {
+ iterator.close();
+ }
+ }
+
+ // return results
+ return backups;
+});
+
+add_task(function* init() {
+ // Wait until initialization is complete
+ yield SessionStore.promiseInitialized;
+ yield SessionFile.wipe();
+});
+
+add_task(function* test_upgrade_backup() {
+ let test = yield prepareTest();
+ info("Let's check if we create an upgrade backup");
+ yield OS.File.writeAtomic(Paths.clean, test.contents);
+ yield SessionFile.read(); // First call to read() initializes the SessionWorker
+ yield SessionFile.write(""); // First call to write() triggers the backup
+
+ is(Services.prefs.getCharPref(PREF_UPGRADE), test.buildID, "upgrade backup should be set");
+
+ is((yield OS.File.exists(Paths.upgradeBackup)), true, "upgrade backup file has been created");
+
+ let data = yield OS.File.read(Paths.upgradeBackup);
+ is(test.contents, (new TextDecoder()).decode(data), "upgrade backup contains the expected contents");
+
+ info("Let's check that we don't overwrite this upgrade backup");
+ let newContents = JSON.stringify({"something else entirely": Math.random()});
+ yield OS.File.writeAtomic(Paths.clean, newContents);
+ yield SessionFile.read(); // Reinitialize the SessionWorker
+ yield SessionFile.write(""); // Next call to write() shouldn't trigger the backup
+ data = yield OS.File.read(Paths.upgradeBackup);
+ is(test.contents, (new TextDecoder()).decode(data), "upgrade backup hasn't changed");
+});
+
+add_task(function* test_upgrade_backup_removal() {
+ let test = yield prepareTest();
+ let maxUpgradeBackups = Preferences.get(PREF_MAX_UPGRADE_BACKUPS, 3);
+ info("Let's see if we remove backups if there are too many");
+ yield OS.File.writeAtomic(Paths.clean, test.contents);
+
+ // if the nextUpgradeBackup already exists (from another test), remove it
+ if (OS.File.exists(Paths.nextUpgradeBackup)) {
+ yield OS.File.remove(Paths.nextUpgradeBackup);
+ }
+
+ // create dummy backups
+ yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20080101010101", "");
+ yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20090101010101", "");
+ yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20100101010101", "");
+ yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20110101010101", "");
+ yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20120101010101", "");
+ yield OS.File.writeAtomic(Paths.upgradeBackupPrefix + "20130101010101", "");
+
+ // get currently existing backups
+ let backups = yield getUpgradeBackups();
+
+ // trigger new backup
+ yield SessionFile.read(); // First call to read() initializes the SessionWorker
+ yield SessionFile.write(""); // First call to write() triggers the backup and the cleanup
+
+ // a new backup should have been created (and still exist)
+ is(Services.prefs.getCharPref(PREF_UPGRADE), test.buildID, "upgrade backup should be set");
+ is((yield OS.File.exists(Paths.upgradeBackup)), true, "upgrade backup file has been created");
+
+ // get currently existing backups and check their count
+ let newBackups = yield getUpgradeBackups();
+ is(newBackups.length, maxUpgradeBackups, "expected number of backups are present after removing old backups");
+
+ // find all backups that were created during the last call to `SessionFile.write("");`
+ // ie, filter out all the backups that have already been present before the call
+ newBackups = newBackups.filter(function (backup) {
+ return backups.indexOf(backup) < 0;
+ });
+
+ // check that exactly one new backup was created
+ is(newBackups.length, 1, "one new backup was created that was not removed");
+
+ yield SessionFile.write(""); // Second call to write() should not trigger anything
+
+ backups = yield getUpgradeBackups();
+ is(backups.length, maxUpgradeBackups, "second call to SessionFile.write() didn't create or remove more backups");
+});
+
diff --git a/browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js b/browser/components/sessionstore/test/browser_windowRestore_perwindowpb.js
new file mode 100644
index 000000000..781692909
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_windowRestore_perwindowpb.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/. */
+
+// This test checks that closed private windows can't be restored
+
+function test() {
+ waitForExplicitFinish();
+
+ // Purging the list of closed windows
+ forgetClosedWindows();
+
+ // Load a private window, then close it
+ // and verify it doesn't get remembered for restoring
+ whenNewWindowLoaded({private: true}, function (win) {
+ info("The private window got loaded");
+ win.addEventListener("SSWindowClosing", function onclosing() {
+ win.removeEventListener("SSWindowClosing", onclosing, false);
+ executeSoon(function () {
+ is(ss.getClosedWindowCount(), 0,
+ "The private window should not have been stored");
+ });
+ }, false);
+ BrowserTestUtils.closeWindow(win).then(finish);
+ });
+}
diff --git a/browser/components/sessionstore/test/browser_windowStateContainer.js b/browser/components/sessionstore/test/browser_windowStateContainer.js
new file mode 100644
index 000000000..beb838088
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_windowStateContainer.js
@@ -0,0 +1,122 @@
+"use strict";
+
+requestLongerTimeout(2);
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+add_task(function* () {
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+
+ // Create 4 tabs with different userContextId.
+ for (let userContextId = 1; userContextId < 5; userContextId++) {
+ let tab = win.gBrowser.addTab("http://example.com/", {userContextId});
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ }
+
+ // Move the default tab of window to the end.
+ // We want the 1st tab to have non-default userContextId, so later when we
+ // restore into win2 we can test restore into an existing tab with different
+ // userContextId.
+ win.gBrowser.moveTabTo(win.gBrowser.tabs[0], win.gBrowser.tabs.length - 1);
+
+ let winState = JSON.parse(ss.getWindowState(win));
+
+ for (let i = 0; i < 4; i++) {
+ Assert.equal(winState.windows[0].tabs[i].userContextId, i + 1,
+ "1st Window: tabs[" + i + "].userContextId should exist.");
+ }
+
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ // Create tabs with different userContextId, but this time we create them with
+ // fewer tabs and with different order with win.
+ for (let userContextId = 3; userContextId > 0; userContextId--) {
+ let tab = win2.gBrowser.addTab("http://example.com/", {userContextId});
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+ }
+
+ ss.setWindowState(win2, JSON.stringify(winState), true);
+
+ for (let i = 0; i < 4; i++) {
+ let browser = win2.gBrowser.tabs[i].linkedBrowser;
+ yield ContentTask.spawn(browser, { expectedId: i + 1 }, function* (args) {
+ Assert.equal(docShell.getOriginAttributes().userContextId,
+ args.expectedId,
+ "The docShell has the correct userContextId");
+
+ Assert.equal(content.document.nodePrincipal.originAttributes.userContextId,
+ args.expectedId,
+ "The document has the correct userContextId");
+ });
+ }
+
+ // Test the last tab, which doesn't have userContextId.
+ let browser = win2.gBrowser.tabs[4].linkedBrowser;
+ yield ContentTask.spawn(browser, { expectedId: 0 }, function* (args) {
+ Assert.equal(docShell.getOriginAttributes().userContextId,
+ args.expectedId,
+ "The docShell has the correct userContextId");
+
+ Assert.equal(content.document.nodePrincipal.originAttributes.userContextId,
+ args.expectedId,
+ "The document has the correct userContextId");
+ });
+
+ yield BrowserTestUtils.closeWindow(win);
+ yield BrowserTestUtils.closeWindow(win2);
+});
+
+add_task(function* () {
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ yield TabStateFlusher.flush(win.gBrowser.selectedBrowser);
+
+ let tab = win.gBrowser.addTab("http://example.com/", { userContextId: 1 });
+ yield promiseBrowserLoaded(tab.linkedBrowser);
+ yield TabStateFlusher.flush(tab.linkedBrowser);
+
+ // win should have 1 default tab, and 1 container tab.
+ Assert.equal(win.gBrowser.tabs.length, 2, "win should have 2 tabs");
+
+ let winState = JSON.parse(ss.getWindowState(win));
+
+ for (let i = 0; i < 2; i++) {
+ Assert.equal(winState.windows[0].tabs[i].userContextId, i,
+ "1st Window: tabs[" + i + "].userContextId should be " + i);
+ }
+
+ let win2 = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let tab2 = win2.gBrowser.addTab("http://example.com/", { userContextId : 1 });
+ yield promiseBrowserLoaded(tab2.linkedBrowser);
+ yield TabStateFlusher.flush(tab2.linkedBrowser);
+
+ // Move the first normal tab to end, so the first tab of win2 will be a
+ // container tab.
+ win2.gBrowser.moveTabTo(win2.gBrowser.tabs[0], win2.gBrowser.tabs.length - 1);
+ yield TabStateFlusher.flush(win2.gBrowser.tabs[0].linkedBrowser);
+
+ ss.setWindowState(win2, JSON.stringify(winState), true);
+
+ for (let i = 0; i < 2; i++) {
+ let browser = win2.gBrowser.tabs[i].linkedBrowser;
+ yield ContentTask.spawn(browser, { expectedId: i }, function* (args) {
+ Assert.equal(docShell.getOriginAttributes().userContextId,
+ args.expectedId,
+ "The docShell has the correct userContextId");
+
+ Assert.equal(content.document.nodePrincipal.originAttributes.userContextId,
+ args.expectedId,
+ "The document has the correct userContextId");
+ });
+ }
+
+ yield BrowserTestUtils.closeWindow(win);
+ yield BrowserTestUtils.closeWindow(win2);
+});
+
diff --git a/browser/components/sessionstore/test/content-forms.js b/browser/components/sessionstore/test/content-forms.js
new file mode 100644
index 000000000..da7bc9c08
--- /dev/null
+++ b/browser/components/sessionstore/test/content-forms.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";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+/**
+ * This frame script is only loaded for sessionstore mochitests. It contains
+ * a bunch of utility functions used to test form data collection and
+ * restoration in remote browsers.
+ */
+
+function queryElement(data) {
+ let frame = content;
+ if (data.hasOwnProperty("frame")) {
+ frame = content.frames[data.frame];
+ }
+
+ let doc = frame.document;
+
+ if (data.hasOwnProperty("id")) {
+ return doc.getElementById(data.id);
+ }
+
+ if (data.hasOwnProperty("selector")) {
+ return doc.querySelector(data.selector);
+ }
+
+ if (data.hasOwnProperty("xpath")) {
+ let xptype = Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
+ return doc.evaluate(data.xpath, doc, null, xptype, null).singleNodeValue;
+ }
+
+ throw new Error("couldn't query element");
+}
+
+function dispatchUIEvent(input, type) {
+ let event = input.ownerDocument.createEvent("UIEvents");
+ event.initUIEvent(type, true, true, input.ownerGlobal, 0);
+ input.dispatchEvent(event);
+}
+
+function defineListener(type, cb) {
+ addMessageListener("ss-test:" + type, function ({data}) {
+ sendAsyncMessage("ss-test:" + type, cb(data));
+ });
+}
+
+defineListener("sendKeyEvent", function (data) {
+ let frame = content;
+ if (data.hasOwnProperty("frame")) {
+ frame = content.frames[data.frame];
+ }
+
+ let ifreq = frame.QueryInterface(Ci.nsIInterfaceRequestor);
+ let utils = ifreq.getInterface(Ci.nsIDOMWindowUtils);
+
+ let keyCode = data.key.charCodeAt(0);
+ let charCode = Ci.nsIDOMKeyEvent.DOM_VK_A + keyCode - "a".charCodeAt(0);
+
+ utils.sendKeyEvent("keydown", keyCode, charCode, null);
+ utils.sendKeyEvent("keypress", keyCode, charCode, null);
+ utils.sendKeyEvent("keyup", keyCode, charCode, null);
+});
+
+defineListener("getInnerHTML", function (data) {
+ return queryElement(data).innerHTML;
+});
+
+defineListener("getTextContent", function (data) {
+ return queryElement(data).textContent;
+});
+
+defineListener("getInputValue", function (data) {
+ return queryElement(data).value;
+});
+
+defineListener("setInputValue", function (data) {
+ let input = queryElement(data);
+ input.value = data.value;
+ dispatchUIEvent(input, "input");
+});
+
+defineListener("getInputChecked", function (data) {
+ return queryElement(data).checked;
+});
+
+defineListener("setInputChecked", function (data) {
+ let input = queryElement(data);
+ input.checked = data.checked;
+ dispatchUIEvent(input, "change");
+});
+
+defineListener("getSelectedIndex", function (data) {
+ return queryElement(data).selectedIndex;
+});
+
+defineListener("setSelectedIndex", function (data) {
+ let input = queryElement(data);
+ input.selectedIndex = data.index;
+ dispatchUIEvent(input, "change");
+});
+
+defineListener("getMultipleSelected", function (data) {
+ let input = queryElement(data);
+ return Array.map(input.options, (opt, idx) => idx)
+ .filter(idx => input.options[idx].selected);
+});
+
+defineListener("setMultipleSelected", function (data) {
+ let input = queryElement(data);
+ Array.forEach(input.options, (opt, idx) => opt.selected = data.indices.indexOf(idx) > -1);
+ dispatchUIEvent(input, "change");
+});
+
+defineListener("getFileNameArray", function (data) {
+ return queryElement(data).mozGetFileNameArray();
+});
+
+defineListener("setFileNameArray", function (data) {
+ let input = queryElement(data);
+ input.mozSetFileNameArray(data.names, data.names.length);
+ dispatchUIEvent(input, "input");
+});
+
+defineListener("setFormElementValues", function (data) {
+ for (let elem of content.document.forms[0].elements) {
+ elem.value = data.value;
+ dispatchUIEvent(elem, "input");
+ }
+});
diff --git a/browser/components/sessionstore/test/content.js b/browser/components/sessionstore/test/content.js
new file mode 100644
index 000000000..e815a6783
--- /dev/null
+++ b/browser/components/sessionstore/test/content.js
@@ -0,0 +1,222 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/sessionstore/FrameTree.jsm", this);
+var gFrameTree = new FrameTree(this);
+
+function executeSoon(callback) {
+ Services.tm.mainThread.dispatch(callback, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+}
+
+gFrameTree.addObserver({
+ onFrameTreeReset: function () {
+ sendAsyncMessage("ss-test:onFrameTreeReset");
+ },
+
+ onFrameTreeCollected: function () {
+ sendAsyncMessage("ss-test:onFrameTreeCollected");
+ }
+});
+
+var historyListener = {
+ OnHistoryNewEntry: function () {
+ sendAsyncMessage("ss-test:OnHistoryNewEntry");
+ },
+
+ OnHistoryGoBack: function () {
+ sendAsyncMessage("ss-test:OnHistoryGoBack");
+ return true;
+ },
+
+ OnHistoryGoForward: function () {
+ sendAsyncMessage("ss-test:OnHistoryGoForward");
+ return true;
+ },
+
+ OnHistoryGotoIndex: function () {
+ sendAsyncMessage("ss-test:OnHistoryGotoIndex");
+ return true;
+ },
+
+ OnHistoryPurge: function () {
+ sendAsyncMessage("ss-test:OnHistoryPurge");
+ return true;
+ },
+
+ OnHistoryReload: function () {
+ sendAsyncMessage("ss-test:OnHistoryReload");
+ return true;
+ },
+
+ OnHistoryReplaceEntry: function () {
+ sendAsyncMessage("ss-test:OnHistoryReplaceEntry");
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISHistoryListener,
+ Ci.nsISupportsWeakReference
+ ])
+};
+
+var {sessionHistory} = docShell.QueryInterface(Ci.nsIWebNavigation);
+if (sessionHistory) {
+ sessionHistory.addSHistoryListener(historyListener);
+}
+
+/**
+ * This frame script is only loaded for sessionstore mochitests. It enables us
+ * to modify and query docShell data when running with multiple processes.
+ */
+
+addEventListener("hashchange", function () {
+ sendAsyncMessage("ss-test:hashchange");
+});
+
+addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
+ Services.obs.notifyObservers(null, "browser:purge-domain-data", domain);
+ content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));
+});
+
+addMessageListener("ss-test:getStyleSheets", function (msg) {
+ let sheets = content.document.styleSheets;
+ let titles = Array.map(sheets, ss => [ss.title, ss.disabled]);
+ sendSyncMessage("ss-test:getStyleSheets", titles);
+});
+
+addMessageListener("ss-test:enableStyleSheetsForSet", function (msg) {
+ let sheets = content.document.styleSheets;
+ let change = false;
+ for (let i = 0; i < sheets.length; i++) {
+ if (sheets[i].disabled != (msg.data.indexOf(sheets[i].title) == -1)) {
+ change = true;
+ break;
+ }
+ }
+ function observer() {
+ Services.obs.removeObserver(observer, "style-sheet-applicable-state-changed");
+
+ // It's possible our observer will run before the one in
+ // content-sessionStore.js. Therefore, we run ours a little
+ // later.
+ executeSoon(() => sendAsyncMessage("ss-test:enableStyleSheetsForSet"));
+ }
+ if (change) {
+ // We don't want to reply until content-sessionStore.js has seen
+ // the change.
+ Services.obs.addObserver(observer, "style-sheet-applicable-state-changed", false);
+
+ content.document.enableStyleSheetsForSet(msg.data);
+ } else {
+ sendAsyncMessage("ss-test:enableStyleSheetsForSet");
+ }
+});
+
+addMessageListener("ss-test:enableSubDocumentStyleSheetsForSet", function (msg) {
+ let iframe = content.document.getElementById(msg.data.id);
+ iframe.contentDocument.enableStyleSheetsForSet(msg.data.set);
+ sendAsyncMessage("ss-test:enableSubDocumentStyleSheetsForSet");
+});
+
+addMessageListener("ss-test:getAuthorStyleDisabled", function (msg) {
+ let {authorStyleDisabled} =
+ docShell.contentViewer;
+ sendSyncMessage("ss-test:getAuthorStyleDisabled", authorStyleDisabled);
+});
+
+addMessageListener("ss-test:setAuthorStyleDisabled", function (msg) {
+ let markupDocumentViewer =
+ docShell.contentViewer;
+ markupDocumentViewer.authorStyleDisabled = msg.data;
+ sendSyncMessage("ss-test:setAuthorStyleDisabled");
+});
+
+addMessageListener("ss-test:setUsePrivateBrowsing", function (msg) {
+ let loadContext =
+ docShell.QueryInterface(Ci.nsILoadContext);
+ loadContext.usePrivateBrowsing = msg.data;
+ sendAsyncMessage("ss-test:setUsePrivateBrowsing");
+});
+
+addMessageListener("ss-test:getScrollPosition", function (msg) {
+ let frame = content;
+ if (msg.data.hasOwnProperty("frame")) {
+ frame = content.frames[msg.data.frame];
+ }
+ let {scrollX: x, scrollY: y} = frame;
+ sendAsyncMessage("ss-test:getScrollPosition", {x: x, y: y});
+});
+
+addMessageListener("ss-test:setScrollPosition", function (msg) {
+ let frame = content;
+ let {x, y} = msg.data;
+ if (msg.data.hasOwnProperty("frame")) {
+ frame = content.frames[msg.data.frame];
+ }
+ frame.scrollTo(x, y);
+
+ frame.addEventListener("scroll", function onScroll(event) {
+ if (frame.document == event.target) {
+ frame.removeEventListener("scroll", onScroll);
+ sendAsyncMessage("ss-test:setScrollPosition");
+ }
+ });
+});
+
+addMessageListener("ss-test:createDynamicFrames", function ({data}) {
+ function createIFrame(rows) {
+ let frames = content.document.getElementById(data.id);
+ frames.setAttribute("rows", rows);
+
+ let frame = content.document.createElement("frame");
+ frame.setAttribute("src", data.url);
+ frames.appendChild(frame);
+ }
+
+ addEventListener("DOMContentLoaded", function onContentLoaded(event) {
+ if (content.document == event.target) {
+ removeEventListener("DOMContentLoaded", onContentLoaded, true);
+ // DOMContentLoaded is fired right after we finished parsing the document.
+ createIFrame("33%, 33%, 33%");
+ }
+ }, true);
+
+ addEventListener("load", function onLoad(event) {
+ if (content.document == event.target) {
+ removeEventListener("load", onLoad, true);
+
+ // Creating this frame on the same tick as the load event
+ // means that it must not be included in the frame tree.
+ createIFrame("25%, 25%, 25%, 25%");
+ }
+ }, true);
+
+ sendAsyncMessage("ss-test:createDynamicFrames");
+});
+
+addMessageListener("ss-test:removeLastFrame", function ({data}) {
+ let frames = content.document.getElementById(data.id);
+ frames.lastElementChild.remove();
+ sendAsyncMessage("ss-test:removeLastFrame");
+});
+
+addMessageListener("ss-test:mapFrameTree", function (msg) {
+ let result = gFrameTree.map(frame => ({href: frame.location.href}));
+ sendAsyncMessage("ss-test:mapFrameTree", result);
+});
+
+addMessageListener("ss-test:click", function ({data}) {
+ content.document.getElementById(data.id).click();
+ sendAsyncMessage("ss-test:click");
+});
+
+addEventListener("load", function(event) {
+ let subframe = event.target != content.document;
+ sendAsyncMessage("ss-test:loadEvent", {subframe: subframe, url: event.target.documentURI});
+}, true);
diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js
new file mode 100644
index 000000000..5a8c5dbfc
--- /dev/null
+++ b/browser/components/sessionstore/test/head.js
@@ -0,0 +1,564 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 TAB_STATE_NEEDS_RESTORE = 1;
+const TAB_STATE_RESTORING = 2;
+
+const ROOT = getRootDirectory(gTestPath);
+const HTTPROOT = ROOT.replace("chrome://mochitests/content/", "http://example.com/");
+const FRAME_SCRIPTS = [
+ ROOT + "content.js",
+ ROOT + "content-forms.js"
+];
+
+var mm = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+for (let script of FRAME_SCRIPTS) {
+ mm.loadFrameScript(script, true);
+}
+
+registerCleanupFunction(() => {
+ for (let script of FRAME_SCRIPTS) {
+ mm.removeDelayedFrameScript(script, true);
+ }
+});
+
+const {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+const {SessionStore} = Cu.import("resource:///modules/sessionstore/SessionStore.jsm", {});
+const {SessionSaver} = Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", {});
+const {SessionFile} = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {});
+const {TabState} = Cu.import("resource:///modules/sessionstore/TabState.jsm", {});
+const {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
+
+const ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+// Some tests here assume that all restored tabs are loaded without waiting for
+// the user to bring them to the foreground. We ensure this by resetting the
+// related preference (see the "firefox.js" defaults file for details).
+Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+});
+
+// Obtain access to internals
+Services.prefs.setBoolPref("browser.sessionstore.debug", true);
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.sessionstore.debug");
+});
+
+
+// This kicks off the search service used on about:home and allows the
+// session restore tests to be run standalone without triggering errors.
+Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs;
+
+function provideWindow(aCallback, aURL, aFeatures) {
+ function callbackSoon(aWindow) {
+ executeSoon(function executeCallbackSoon() {
+ aCallback(aWindow);
+ });
+ }
+
+ let win = openDialog(getBrowserURL(), "", aFeatures || "chrome,all,dialog=no", aURL || "about:blank");
+ whenWindowLoaded(win, function onWindowLoaded(aWin) {
+ if (!aURL) {
+ info("Loaded a blank window.");
+ callbackSoon(aWin);
+ return;
+ }
+
+ aWin.gBrowser.selectedBrowser.addEventListener("load", function selectedBrowserLoadListener() {
+ aWin.gBrowser.selectedBrowser.removeEventListener("load", selectedBrowserLoadListener, true);
+ callbackSoon(aWin);
+ }, true);
+ });
+}
+
+// This assumes that tests will at least have some state/entries
+function waitForBrowserState(aState, aSetStateCallback) {
+ if (typeof aState == "string") {
+ aState = JSON.parse(aState);
+ }
+ if (typeof aState != "object") {
+ throw new TypeError("Argument must be an object or a JSON representation of an object");
+ }
+ let windows = [window];
+ let tabsRestored = 0;
+ let expectedTabsRestored = 0;
+ let expectedWindows = aState.windows.length;
+ let windowsOpen = 1;
+ let listening = false;
+ let windowObserving = false;
+ let restoreHiddenTabs = Services.prefs.getBoolPref(
+ "browser.sessionstore.restore_hidden_tabs");
+
+ aState.windows.forEach(function (winState) {
+ winState.tabs.forEach(function (tabState) {
+ if (restoreHiddenTabs || !tabState.hidden)
+ expectedTabsRestored++;
+ });
+ });
+
+ // There must be only hidden tabs and restoreHiddenTabs = false. We still
+ // expect one of them to be restored because it gets shown automatically.
+ if (!expectedTabsRestored)
+ expectedTabsRestored = 1;
+
+ function onSSTabRestored(aEvent) {
+ if (++tabsRestored == expectedTabsRestored) {
+ // Remove the event listener from each window
+ windows.forEach(function(win) {
+ win.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true);
+ });
+ listening = false;
+ info("running " + aSetStateCallback.name);
+ executeSoon(aSetStateCallback);
+ }
+ }
+
+ // Used to add our listener to further windows so we can catch SSTabRestored
+ // coming from them when creating a multi-window state.
+ function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ let newWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ newWindow.addEventListener("load", function() {
+ newWindow.removeEventListener("load", arguments.callee, false);
+
+ if (++windowsOpen == expectedWindows) {
+ Services.ww.unregisterNotification(windowObserver);
+ windowObserving = false;
+ }
+
+ // Track this window so we can remove the progress listener later
+ windows.push(newWindow);
+ // Add the progress listener
+ newWindow.gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true);
+ }, false);
+ }
+ }
+
+ // We only want to register the notification if we expect more than 1 window
+ if (expectedWindows > 1) {
+ registerCleanupFunction(function() {
+ if (windowObserving) {
+ Services.ww.unregisterNotification(windowObserver);
+ }
+ });
+ windowObserving = true;
+ Services.ww.registerNotification(windowObserver);
+ }
+
+ registerCleanupFunction(function() {
+ if (listening) {
+ windows.forEach(function(win) {
+ win.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true);
+ });
+ }
+ });
+ // Add the event listener for this window as well.
+ listening = true;
+ gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true);
+
+ // Ensure setBrowserState() doesn't remove the initial tab.
+ gBrowser.selectedTab = gBrowser.tabs[0];
+
+ // Finally, call setBrowserState
+ ss.setBrowserState(JSON.stringify(aState));
+}
+
+function promiseBrowserState(aState) {
+ return new Promise(resolve => waitForBrowserState(aState, resolve));
+}
+
+function promiseTabState(tab, state) {
+ if (typeof(state) != "string") {
+ state = JSON.stringify(state);
+ }
+
+ let promise = promiseTabRestored(tab);
+ ss.setTabState(tab, state);
+ return promise;
+}
+
+/**
+ * Wait for a content -> chrome message.
+ */
+function promiseContentMessage(browser, name) {
+ let mm = browser.messageManager;
+
+ return new Promise(resolve => {
+ function removeListener() {
+ mm.removeMessageListener(name, listener);
+ }
+
+ function listener(msg) {
+ removeListener();
+ resolve(msg.data);
+ }
+
+ mm.addMessageListener(name, listener);
+ registerCleanupFunction(removeListener);
+ });
+}
+
+function waitForTopic(aTopic, aTimeout, aCallback) {
+ let observing = false;
+ function removeObserver() {
+ if (!observing)
+ return;
+ Services.obs.removeObserver(observer, aTopic);
+ observing = false;
+ }
+
+ let timeout = setTimeout(function () {
+ removeObserver();
+ aCallback(false);
+ }, aTimeout);
+
+ function observer(aSubject, aTopic, aData) {
+ removeObserver();
+ timeout = clearTimeout(timeout);
+ executeSoon(() => aCallback(true));
+ }
+
+ registerCleanupFunction(function() {
+ removeObserver();
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ });
+
+ observing = true;
+ Services.obs.addObserver(observer, aTopic, false);
+}
+
+/**
+ * Wait until session restore has finished collecting its data and is
+ * has written that data ("sessionstore-state-write-complete").
+ *
+ * @param {function} aCallback If sessionstore-state-write-complete is sent
+ * within buffering interval + 100 ms, the callback is passed |true|,
+ * otherwise, it is passed |false|.
+ */
+function waitForSaveState(aCallback) {
+ let timeout = 100 +
+ Services.prefs.getIntPref("browser.sessionstore.interval");
+ return waitForTopic("sessionstore-state-write-complete", timeout, aCallback);
+}
+function promiseSaveState() {
+ return new Promise(resolve => {
+ waitForSaveState(isSuccessful => {
+ if (!isSuccessful) {
+ throw new Error("timeout");
+ }
+
+ resolve();
+ });
+ });
+}
+function forceSaveState() {
+ return SessionSaver.run();
+}
+
+function promiseRecoveryFileContents() {
+ let promise = forceSaveState();
+ return promise.then(function() {
+ return OS.File.read(SessionFile.Paths.recovery, { encoding: "utf-8" });
+ });
+}
+
+var promiseForEachSessionRestoreFile = Task.async(function*(cb) {
+ for (let key of SessionFile.Paths.loadOrder) {
+ let data = "";
+ try {
+ data = yield OS.File.read(SessionFile.Paths[key], { encoding: "utf-8" });
+ } catch (ex) {
+ // Ignore missing files
+ if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
+ throw ex;
+ }
+ }
+ cb(data, key);
+ }
+});
+
+function promiseBrowserLoaded(aBrowser, ignoreSubFrames = true, wantLoad = null) {
+ return BrowserTestUtils.browserLoaded(aBrowser, !ignoreSubFrames, wantLoad);
+}
+
+function whenWindowLoaded(aWindow, aCallback = next) {
+ aWindow.addEventListener("load", function windowLoadListener() {
+ aWindow.removeEventListener("load", windowLoadListener, false);
+ executeSoon(function executeWhenWindowLoaded() {
+ aCallback(aWindow);
+ });
+ }, false);
+}
+function promiseWindowLoaded(aWindow) {
+ return new Promise(resolve => whenWindowLoaded(aWindow, resolve));
+}
+
+var gUniqueCounter = 0;
+function r() {
+ return Date.now() + "-" + (++gUniqueCounter);
+}
+
+function* BrowserWindowIterator() {
+ let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ while (windowsEnum.hasMoreElements()) {
+ let currentWindow = windowsEnum.getNext();
+ if (!currentWindow.closed) {
+ yield currentWindow;
+ }
+ }
+}
+
+var gWebProgressListener = {
+ _callback: null,
+
+ setCallback: function (aCallback) {
+ if (!this._callback) {
+ window.gBrowser.addTabsProgressListener(this);
+ }
+ this._callback = aCallback;
+ },
+
+ unsetCallback: function () {
+ if (this._callback) {
+ this._callback = null;
+ window.gBrowser.removeTabsProgressListener(this);
+ }
+ },
+
+ onStateChange: function (aBrowser, aWebProgress, aRequest,
+ aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+ this._callback(aBrowser);
+ }
+ }
+};
+
+registerCleanupFunction(function () {
+ gWebProgressListener.unsetCallback();
+});
+
+var gProgressListener = {
+ _callback: null,
+
+ setCallback: function (callback) {
+ Services.obs.addObserver(this, "sessionstore-debug-tab-restored", false);
+ this._callback = callback;
+ },
+
+ unsetCallback: function () {
+ if (this._callback) {
+ this._callback = null;
+ Services.obs.removeObserver(this, "sessionstore-debug-tab-restored");
+ }
+ },
+
+ observe: function (browser, topic, data) {
+ gProgressListener.onRestored(browser);
+ },
+
+ onRestored: function (browser) {
+ if (browser.__SS_restoreState == TAB_STATE_RESTORING) {
+ let args = [browser].concat(gProgressListener._countTabs());
+ gProgressListener._callback.apply(gProgressListener, args);
+ }
+ },
+
+ _countTabs: function () {
+ let needsRestore = 0, isRestoring = 0, wasRestored = 0;
+
+ for (let win of BrowserWindowIterator()) {
+ for (let i = 0; i < win.gBrowser.tabs.length; i++) {
+ let browser = win.gBrowser.tabs[i].linkedBrowser;
+ if (!browser.__SS_restoreState)
+ wasRestored++;
+ else if (browser.__SS_restoreState == TAB_STATE_RESTORING)
+ isRestoring++;
+ else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
+ needsRestore++;
+ }
+ }
+ return [needsRestore, isRestoring, wasRestored];
+ }
+};
+
+registerCleanupFunction(function () {
+ gProgressListener.unsetCallback();
+});
+
+// Close all but our primary window.
+function promiseAllButPrimaryWindowClosed() {
+ let windows = [];
+ for (let win of BrowserWindowIterator()) {
+ if (win != window) {
+ windows.push(win);
+ }
+ }
+
+ return Promise.all(windows.map(BrowserTestUtils.closeWindow));
+}
+
+// Forget all closed windows.
+function forgetClosedWindows() {
+ while (ss.getClosedWindowCount() > 0) {
+ ss.forgetClosedWindow(0);
+ }
+}
+
+/**
+ * When opening a new window it is not sufficient to wait for its load event.
+ * We need to use whenDelayedStartupFinshed() here as the browser window's
+ * delayedStartup() routine is executed one tick after the window's load event
+ * has been dispatched. browser-delayed-startup-finished might be deferred even
+ * further if parts of the window's initialization process take more time than
+ * expected (e.g. reading a big session state from disk).
+ */
+function whenNewWindowLoaded(aOptions, aCallback) {
+ let features = "";
+ let url = "about:blank";
+
+ if (aOptions && aOptions.private || false) {
+ features = ",private";
+ url = "about:privatebrowsing";
+ }
+
+ let win = openDialog(getBrowserURL(), "", "chrome,all,dialog=no" + features, url);
+ let delayedStartup = promiseDelayedStartupFinished(win);
+
+ let browserLoaded = new Promise(resolve => {
+ if (url == "about:blank") {
+ resolve();
+ return;
+ }
+
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad);
+ let browser = win.gBrowser.selectedBrowser;
+ promiseBrowserLoaded(browser).then(resolve);
+ });
+ });
+
+ Promise.all([delayedStartup, browserLoaded]).then(() => aCallback(win));
+}
+function promiseNewWindowLoaded(aOptions) {
+ return new Promise(resolve => whenNewWindowLoaded(aOptions, resolve));
+}
+
+/**
+ * This waits for the browser-delayed-startup-finished notification of a given
+ * window. It indicates that the windows has loaded completely and is ready to
+ * be used for testing.
+ */
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ }
+ }, "browser-delayed-startup-finished", false);
+}
+function promiseDelayedStartupFinished(aWindow) {
+ return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve));
+}
+
+function promiseEvent(element, eventType, isCapturing = false) {
+ return new Promise(resolve => {
+ element.addEventListener(eventType, function listener(event) {
+ element.removeEventListener(eventType, listener, isCapturing);
+ resolve(event);
+ }, isCapturing);
+ });
+}
+
+function promiseTabRestored(tab) {
+ return promiseEvent(tab, "SSTabRestored");
+}
+
+function promiseTabRestoring(tab) {
+ return promiseEvent(tab, "SSTabRestoring");
+}
+
+function sendMessage(browser, name, data = {}) {
+ browser.messageManager.sendAsyncMessage(name, data);
+ return promiseContentMessage(browser, name);
+}
+
+// This creates list of functions that we will map to their corresponding
+// ss-test:* messages names. Those will be sent to the frame script and
+// be used to read and modify form data.
+const FORM_HELPERS = [
+ "getTextContent",
+ "getInputValue", "setInputValue",
+ "getInputChecked", "setInputChecked",
+ "getSelectedIndex", "setSelectedIndex",
+ "getMultipleSelected", "setMultipleSelected",
+ "getFileNameArray", "setFileNameArray",
+];
+
+for (let name of FORM_HELPERS) {
+ let msg = "ss-test:" + name;
+ this[name] = (browser, data) => sendMessage(browser, msg, data);
+}
+
+// Removes the given tab immediately and returns a promise that resolves when
+// all pending status updates (messages) of the closing tab have been received.
+function promiseRemoveTab(tab) {
+ return BrowserTestUtils.removeTab(tab);
+}
+
+// Write DOMSessionStorage data to the given browser.
+function modifySessionStorage(browser, data, options = {}) {
+ return ContentTask.spawn(browser, [data, options], function* ([data, options]) {
+ let frame = content;
+ if (options && "frameIndex" in options) {
+ frame = content.frames[options.frameIndex];
+ }
+
+ let keys = new Set(Object.keys(data));
+ let storage = frame.sessionStorage;
+
+ return new Promise(resolve => {
+ addEventListener("MozSessionStorageChanged", function onStorageChanged(event) {
+ if (event.storageArea == storage) {
+ keys.delete(event.key);
+ }
+
+ if (keys.size == 0) {
+ removeEventListener("MozSessionStorageChanged", onStorageChanged, true);
+ resolve();
+ }
+ }, true);
+
+ for (let key of keys) {
+ frame.sessionStorage[key] = data[key];
+ }
+ });
+ });
+}
+
+function pushPrefs(...aPrefs) {
+ return new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
+ });
+}
+
+function popPrefs() {
+ return new Promise(resolve => {
+ SpecialPowers.popPrefEnv(resolve);
+ });
+}
+
+function* checkScroll(tab, expected, msg) {
+ let browser = tab.linkedBrowser;
+ yield TabStateFlusher.flush(browser);
+
+ let scroll = JSON.parse(ss.getTabState(tab)).scroll || null;
+ is(JSON.stringify(scroll), JSON.stringify(expected), msg);
+}
diff --git a/browser/components/sessionstore/test/restore_redirect_http.html b/browser/components/sessionstore/test/restore_redirect_http.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/browser/components/sessionstore/test/restore_redirect_http.html
diff --git a/browser/components/sessionstore/test/restore_redirect_http.html^headers^ b/browser/components/sessionstore/test/restore_redirect_http.html^headers^
new file mode 100644
index 000000000..533bda36f
--- /dev/null
+++ b/browser/components/sessionstore/test/restore_redirect_http.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Moved Temporarily
+Location: restore_redirect_target.html
diff --git a/browser/components/sessionstore/test/restore_redirect_js.html b/browser/components/sessionstore/test/restore_redirect_js.html
new file mode 100644
index 000000000..1f5f0e54c
--- /dev/null
+++ b/browser/components/sessionstore/test/restore_redirect_js.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script>
+var newLocation = window.location.toString().replace("restore_redirect_js.html", "restore_redirect_target.html");
+window.location.replace(newLocation);
+</script>
+</head>
+</html> \ No newline at end of file
diff --git a/browser/components/sessionstore/test/restore_redirect_target.html b/browser/components/sessionstore/test/restore_redirect_target.html
new file mode 100644
index 000000000..6c8b3aae5
--- /dev/null
+++ b/browser/components/sessionstore/test/restore_redirect_target.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<title>Test page</title>
+</head>
+<body>Test page</body>
+</html>
diff --git a/browser/components/sessionstore/test/unit/.eslintrc.js b/browser/components/sessionstore/test/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json
new file mode 100644
index 000000000..928de6a39
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json
@@ -0,0 +1 @@
+{"profile-after-change":true,"final-ui-startup":true,"sessionstore-windows-restored":true,"quit-application-granted":true,"quit-application":true,"sessionstore-final-state-write-complete":true,"profile-change-net-teardown":true,"profile-change-teardown":true,"profile-before-change":true} \ No newline at end of file
diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js
new file mode 100644
index 000000000..a8c3ff2ff
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js
@@ -0,0 +1,3 @@
+{
+ "windows": // invalid json
+}
diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_valid.js b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js
new file mode 100644
index 000000000..f9511f29f
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js
@@ -0,0 +1,3 @@
+{
+ "windows": []
+} \ No newline at end of file
diff --git a/browser/components/sessionstore/test/unit/head.js b/browser/components/sessionstore/test/unit/head.js
new file mode 100644
index 000000000..b62856012
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/head.js
@@ -0,0 +1,32 @@
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// Call a function once initialization of SessionStartup is complete
+function afterSessionStartupInitialization(cb) {
+ do_print("Waiting for session startup initialization");
+ let observer = function() {
+ try {
+ do_print("Session startup initialization observed");
+ Services.obs.removeObserver(observer, "sessionstore-state-finalized");
+ cb();
+ } catch (ex) {
+ do_throw(ex);
+ }
+ };
+
+ // We need the Crash Monitor initialized for sessionstartup to run
+ // successfully.
+ Components.utils.import("resource://gre/modules/CrashMonitor.jsm");
+ CrashMonitor.init();
+
+ // Start sessionstartup initialization.
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsIObserver);
+ Services.obs.addObserver(startup, "final-ui-startup", false);
+ Services.obs.addObserver(startup, "quit-application", false);
+ Services.obs.notifyObservers(null, "final-ui-startup", "");
+ Services.obs.addObserver(observer, "sessionstore-state-finalized", false);
+};
diff --git a/browser/components/sessionstore/test/unit/test_backup_once.js b/browser/components/sessionstore/test/unit/test_backup_once.js
new file mode 100644
index 000000000..fff34ad58
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_backup_once.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+var {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {});
+
+var File = OS.File;
+var Paths;
+var SessionFile;
+
+// We need a XULAppInfo to initialize SessionFile
+Cu.import("resource://testing-common/AppInfo.jsm", this);
+updateAppInfo({
+ name: "SessionRestoreTest",
+ ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+ version: "1",
+ platformVersion: "",
+});
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* init() {
+ // Make sure that we have a profile before initializing SessionFile
+ let profd = do_get_profile();
+ SessionFile = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}).SessionFile;
+ Paths = SessionFile.Paths;
+
+
+ let source = do_get_file("data/sessionstore_valid.js");
+ source.copyTo(profd, "sessionstore.js");
+
+ // Finish initialization of SessionFile
+ yield SessionFile.read();
+});
+
+var pathStore;
+var pathBackup;
+var decoder;
+
+function promise_check_exist(path, shouldExist) {
+ return Task.spawn(function*() {
+ do_print("Ensuring that " + path + (shouldExist?" exists":" does not exist"));
+ if ((yield OS.File.exists(path)) != shouldExist) {
+ throw new Error("File " + path + " should " + (shouldExist?"exist":"not exist"));
+ }
+ });
+}
+
+function promise_check_contents(path, expect) {
+ return Task.spawn(function*() {
+ do_print("Checking whether " + path + " has the right contents");
+ let actual = yield OS.File.read(path, { encoding: "utf-8"});
+ Assert.deepEqual(JSON.parse(actual), expect, `File ${path} contains the expected data.`);
+ });
+}
+
+function generateFileContents(id) {
+ let url = `http://example.com/test_backup_once#${id}_${Math.random()}`;
+ return {windows: [{tabs: [{entries: [{url}], index: 1}]}]}
+}
+
+// Write to the store, and check that it creates:
+// - $Path.recovery with the new data
+// - $Path.nextUpgradeBackup with the old data
+add_task(function* test_first_write_backup() {
+ let initial_content = generateFileContents("initial");
+ let new_content = generateFileContents("test_1");
+
+ do_print("Before the first write, none of the files should exist");
+ yield promise_check_exist(Paths.backups, false);
+
+ yield File.makeDir(Paths.backups);
+ yield File.writeAtomic(Paths.clean, JSON.stringify(initial_content), { encoding: "utf-8" });
+ yield SessionFile.write(new_content);
+
+ do_print("After first write, a few files should have been created");
+ yield promise_check_exist(Paths.backups, true);
+ yield promise_check_exist(Paths.clean, false);
+ yield promise_check_exist(Paths.cleanBackup, true);
+ yield promise_check_exist(Paths.recovery, true);
+ yield promise_check_exist(Paths.recoveryBackup, false);
+ yield promise_check_exist(Paths.nextUpgradeBackup, true);
+
+ yield promise_check_contents(Paths.recovery, new_content);
+ yield promise_check_contents(Paths.nextUpgradeBackup, initial_content);
+});
+
+// Write to the store again, and check that
+// - $Path.clean is not written
+// - $Path.recovery contains the new data
+// - $Path.recoveryBackup contains the previous data
+add_task(function* test_second_write_no_backup() {
+ let new_content = generateFileContents("test_2");
+ let previous_backup_content = yield File.read(Paths.recovery, { encoding: "utf-8" });
+ previous_backup_content = JSON.parse(previous_backup_content);
+
+ yield OS.File.remove(Paths.cleanBackup);
+
+ yield SessionFile.write(new_content);
+
+ yield promise_check_exist(Paths.backups, true);
+ yield promise_check_exist(Paths.clean, false);
+ yield promise_check_exist(Paths.cleanBackup, false);
+ yield promise_check_exist(Paths.recovery, true);
+ yield promise_check_exist(Paths.nextUpgradeBackup, true);
+
+ yield promise_check_contents(Paths.recovery, new_content);
+ yield promise_check_contents(Paths.recoveryBackup, previous_backup_content);
+});
+
+// Make sure that we create $Paths.clean and remove $Paths.recovery*
+// upon shutdown
+add_task(function* test_shutdown() {
+ let output = generateFileContents("test_3");
+
+ yield File.writeAtomic(Paths.recovery, "I should disappear");
+ yield File.writeAtomic(Paths.recoveryBackup, "I should also disappear");
+
+ yield SessionWorker.post("write", [output, { isFinalWrite: true, performShutdownCleanup: true}]);
+
+ do_check_false((yield File.exists(Paths.recovery)));
+ do_check_false((yield File.exists(Paths.recoveryBackup)));
+ yield promise_check_contents(Paths.clean, output);
+});
diff --git a/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js
new file mode 100644
index 000000000..c7d8b03ed
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * The primary purpose of this test is to ensure that
+ * the sessionstore component records information about
+ * corrupted backup files into a histogram.
+ */
+
+"use strict";
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+const Telemetry = Services.telemetry;
+const Path = OS.Path;
+const HistogramId = "FX_SESSION_RESTORE_ALL_FILES_CORRUPT";
+
+// Prepare the session file.
+var profd = do_get_profile();
+Cu.import("resource:///modules/sessionstore/SessionFile.jsm", this);
+
+/**
+ * A utility function for resetting the histogram and the contents
+ * of the backup directory.
+ */
+function reset_session(backups = {}) {
+
+ // Reset the histogram.
+ Telemetry.getHistogramById(HistogramId).clear();
+
+ // Reset the contents of the backups directory
+ OS.File.makeDir(SessionFile.Paths.backups);
+ for (let key of SessionFile.Paths.loadOrder) {
+ if (backups.hasOwnProperty(key)) {
+ OS.File.copy(backups[key], SessionFile.Paths[key]);
+ } else {
+ OS.File.remove(SessionFile.Paths[key]);
+ }
+ }
+}
+
+/**
+ * In order to use FX_SESSION_RESTORE_ALL_FILES_CORRUPT histogram
+ * it has to be registered in "toolkit/components/telemetry/Histograms.json".
+ * This test ensures that the histogram is registered and empty.
+ */
+add_task(function* test_ensure_histogram_exists_and_empty() {
+ let s = Telemetry.getHistogramById(HistogramId).snapshot();
+ Assert.equal(s.sum, 0, "Initially, the sum of probes is 0");
+});
+
+/**
+ * Makes sure that the histogram is negatively updated when no
+ * backup files are present.
+ */
+add_task(function* test_no_files_exist() {
+ // No session files are available to SessionFile.
+ reset_session();
+
+ yield SessionFile.read();
+ // Checking if the histogram is updated negatively
+ let h = Telemetry.getHistogramById(HistogramId);
+ let s = h.snapshot();
+ Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket.");
+ Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket.");
+});
+
+/**
+ * Makes sure that the histogram is negatively updated when at least one
+ * backup file is not corrupted.
+ */
+add_task(function* test_one_file_valid() {
+ // Corrupting some backup files.
+ let invalidSession = "data/sessionstore_invalid.js";
+ let validSession = "data/sessionstore_valid.js";
+ reset_session({
+ clean : invalidSession,
+ cleanBackup: validSession,
+ recovery: invalidSession,
+ recoveryBackup: invalidSession
+ });
+
+ yield SessionFile.read();
+ // Checking if the histogram is updated negatively.
+ let h = Telemetry.getHistogramById(HistogramId);
+ let s = h.snapshot();
+ Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket.");
+ Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket.");
+});
+
+/**
+ * Makes sure that the histogram is positively updated when all
+ * backup files are corrupted.
+ */
+add_task(function* test_all_files_corrupt() {
+ // Corrupting all backup files.
+ let invalidSession = "data/sessionstore_invalid.js";
+ reset_session({
+ clean : invalidSession,
+ cleanBackup: invalidSession,
+ recovery: invalidSession,
+ recoveryBackup: invalidSession
+ });
+
+ yield SessionFile.read();
+ // Checking if the histogram is positively updated.
+ let h = Telemetry.getHistogramById(HistogramId);
+ let s = h.snapshot();
+ Assert.equal(s.counts[1], 1, "One probe for the 'true' bucket.");
+ Assert.equal(s.counts[0], 0, "No probes in the 'false' bucket.");
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js
new file mode 100644
index 000000000..b99e566e9
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js
@@ -0,0 +1,127 @@
+"use strict";
+
+/**
+ * This test ensures that we correctly clean up the session state before
+ * writing to disk a last time on shutdown. For now it only tests that each
+ * tab's shistory is capped to a maximum number of preceding and succeeding
+ * entries.
+ */
+
+const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+const {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {});
+
+const profd = do_get_profile();
+const {SessionFile} = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {});
+const {Paths} = SessionFile;
+
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+const {File} = OS;
+
+const MAX_ENTRIES = 9;
+const URL = "http://example.com/#";
+
+// We need a XULAppInfo to initialize SessionFile
+Cu.import("resource://testing-common/AppInfo.jsm", this);
+updateAppInfo({
+ name: "SessionRestoreTest",
+ ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+ version: "1",
+ platformVersion: "",
+});
+
+add_task(function* setup() {
+ let source = do_get_file("data/sessionstore_valid.js");
+ source.copyTo(profd, "sessionstore.js");
+
+ // Finish SessionFile initialization.
+ yield SessionFile.read();
+
+ // Reset prefs on cleanup.
+ do_register_cleanup(() => {
+ Services.prefs.clearUserPref("browser.sessionstore.max_serialize_back");
+ Services.prefs.clearUserPref("browser.sessionstore.max_serialize_forward");
+ });
+});
+
+function createSessionState(index) {
+ // Generate the tab state entries and set the one-based
+ // tab-state index to the middle session history entry.
+ let tabState = {entries: [], index};
+ for (let i = 0; i < MAX_ENTRIES; i++) {
+ tabState.entries.push({url: URL + i});
+ }
+
+ return {windows: [{tabs: [tabState]}]};
+}
+
+function* setMaxBackForward(back, fwd) {
+ Services.prefs.setIntPref("browser.sessionstore.max_serialize_back", back);
+ Services.prefs.setIntPref("browser.sessionstore.max_serialize_forward", fwd);
+ yield SessionFile.read();
+}
+
+function* writeAndParse(state, path, options = {}) {
+ yield SessionWorker.post("write", [state, options]);
+ return JSON.parse(yield File.read(path, {encoding: "utf-8"}));
+}
+
+add_task(function* test_shistory_cap_none() {
+ let state = createSessionState(5);
+
+ // Don't limit the number of shistory entries.
+ yield setMaxBackForward(-1, -1);
+
+ // Check that no caps are applied.
+ let diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ Assert.deepEqual(state, diskState, "no cap applied");
+});
+
+add_task(function* test_shistory_cap_middle() {
+ let state = createSessionState(5);
+ yield setMaxBackForward(2, 3);
+
+ // Cap is only applied on clean shutdown.
+ let diskState = yield writeAndParse(state, Paths.recovery);
+ Assert.deepEqual(state, diskState, "no cap applied");
+
+ // Check that the right number of shistory entries was discarded
+ // and the shistory index updated accordingly.
+ diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ let tabState = state.windows[0].tabs[0];
+ tabState.entries = tabState.entries.slice(2, 8);
+ tabState.index = 3;
+ Assert.deepEqual(state, diskState, "cap applied");
+});
+
+add_task(function* test_shistory_cap_lower_bound() {
+ let state = createSessionState(1);
+ yield setMaxBackForward(5, 5);
+
+ // Cap is only applied on clean shutdown.
+ let diskState = yield writeAndParse(state, Paths.recovery);
+ Assert.deepEqual(state, diskState, "no cap applied");
+
+ // Check that the right number of shistory entries was discarded.
+ diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ let tabState = state.windows[0].tabs[0];
+ tabState.entries = tabState.entries.slice(0, 6);
+ Assert.deepEqual(state, diskState, "cap applied");
+});
+
+add_task(function* test_shistory_cap_upper_bound() {
+ let state = createSessionState(MAX_ENTRIES);
+ yield setMaxBackForward(5, 5);
+
+ // Cap is only applied on clean shutdown.
+ let diskState = yield writeAndParse(state, Paths.recovery);
+ Assert.deepEqual(state, diskState, "no cap applied");
+
+ // Check that the right number of shistory entries was discarded
+ // and the shistory index updated accordingly.
+ diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+ let tabState = state.windows[0].tabs[0];
+ tabState.entries = tabState.entries.slice(3);
+ tabState.index = 6;
+ Assert.deepEqual(state, diskState, "cap applied");
+});
diff --git a/browser/components/sessionstore/test/unit/test_startup_invalid_session.js b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js
new file mode 100644
index 000000000..9f6df8585
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ let profd = do_get_profile();
+
+ let sourceSession = do_get_file("data/sessionstore_invalid.js");
+ sourceSession.copyTo(profd, "sessionstore.js");
+
+ let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json");
+ sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json");
+
+ do_test_pending();
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+
+ afterSessionStartupInitialization(function cb() {
+ do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION);
+ do_test_finished();
+ });
+}
diff --git a/browser/components/sessionstore/test/unit/test_startup_nosession_async.js b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js
new file mode 100644
index 000000000..5185b02d6
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+// Test nsISessionStartup.sessionType in the following scenario:
+// - no sessionstore.js;
+// - the session store has been loaded, so no need to go
+// through the synchronous fallback
+
+function run_test() {
+ do_get_profile();
+ // Initialize the profile (the session startup uses it)
+
+ do_test_pending();
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+
+ afterSessionStartupInitialization(function cb() {
+ do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION);
+ do_test_finished();
+ });
+} \ No newline at end of file
diff --git a/browser/components/sessionstore/test/unit/test_startup_session_async.js b/browser/components/sessionstore/test/unit/test_startup_session_async.js
new file mode 100644
index 000000000..459acf885
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_startup_session_async.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+// Test nsISessionStartup.sessionType in the following scenario:
+// - valid sessionstore.js;
+// - valid sessionCheckpoints.json with all checkpoints;
+// - the session store has been loaded
+
+function run_test() {
+ let profd = do_get_profile();
+
+ let sourceSession = do_get_file("data/sessionstore_valid.js");
+ sourceSession.copyTo(profd, "sessionstore.js");
+
+ let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json");
+ sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json");
+
+ do_test_pending();
+ let startup = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+
+ afterSessionStartupInitialization(function cb() {
+ do_check_eq(startup.sessionType, Ci.nsISessionStartup.DEFER_SESSION);
+ do_test_finished();
+ });
+}
diff --git a/browser/components/sessionstore/test/unit/xpcshell.ini b/browser/components/sessionstore/test/unit/xpcshell.ini
new file mode 100644
index 000000000..09980f877
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/xpcshell.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+head = head.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ data/sessionCheckpoints_all.json
+ data/sessionstore_invalid.js
+ data/sessionstore_valid.js
+
+[test_backup_once.js]
+[test_histogram_corrupt_files.js]
+[test_shutdown_cleanup.js]
+[test_startup_nosession_async.js]
+[test_startup_session_async.js]
+[test_startup_invalid_session.js]
diff --git a/browser/components/shell/ShellService.jsm b/browser/components/shell/ShellService.jsm
new file mode 100644
index 000000000..2a3400b60
--- /dev/null
+++ b/browser/components/shell/ShellService.jsm
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["ShellService"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+
+/**
+ * Internal functionality to save and restore the docShell.allow* properties.
+ */
+let ShellServiceInternal = {
+ /**
+ * Used to determine whether or not to offer "Set as desktop background"
+ * functionality. Even if shell service is available it is not
+ * guaranteed that it is able to set the background for every desktop
+ * which is especially true for Linux with its many different desktop
+ * environments.
+ */
+ get canSetDesktopBackground() {
+ if (AppConstants.platform == "win" ||
+ AppConstants.platform == "macosx") {
+ return true;
+ }
+
+ if (AppConstants.platform == "linux") {
+ if (this.shellService) {
+ let linuxShellService = this.shellService
+ .QueryInterface(Ci.nsIGNOMEShellService);
+ return linuxShellService.canSetDesktopBackground;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Used to determine whether or not to show a "Set Default Browser"
+ * query dialog. This attribute is true if the application is starting
+ * up and "browser.shell.checkDefaultBrowser" is true, otherwise it
+ * is false.
+ */
+ _checkedThisSession: false,
+ get shouldCheckDefaultBrowser() {
+ // If we've already checked, the browser has been started and this is a
+ // new window open, and we don't want to check again.
+ if (this._checkedThisSession) {
+ return false;
+ }
+
+ if (!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser")) {
+ return false;
+ }
+
+ if (AppConstants.platform == "win") {
+ let optOutValue = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\Firefox",
+ "DefaultBrowserOptOut");
+ WindowsRegistry.removeRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\Firefox",
+ "DefaultBrowserOptOut");
+ if (optOutValue == "True") {
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", false);
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ set shouldCheckDefaultBrowser(shouldCheck) {
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", !!shouldCheck);
+ },
+
+ isDefaultBrowser(startupCheck, forAllTypes) {
+ // If this is the first browser window, maintain internal state that we've
+ // checked this session (so that subsequent window opens don't show the
+ // default browser dialog).
+ if (startupCheck) {
+ this._checkedThisSession = true;
+ }
+ if (this.shellService) {
+ return this.shellService.isDefaultBrowser(startupCheck, forAllTypes);
+ }
+ return false;
+ }
+};
+
+XPCOMUtils.defineLazyServiceGetter(ShellServiceInternal, "shellService",
+ "@mozilla.org/browser/shell-service;1", Ci.nsIShellService);
+
+/**
+ * The external API exported by this module.
+ */
+this.ShellService = new Proxy(ShellServiceInternal, {
+ get(target, name) {
+ if (name in target) {
+ return target[name];
+ }
+ if (target.shellService) {
+ return target.shellService[name];
+ }
+ Services.console.logStringMessage(`${name} not found in ShellService: ${target.shellService}`);
+ return undefined;
+ }
+});
diff --git a/browser/components/shell/content/setDesktopBackground.js b/browser/components/shell/content/setDesktopBackground.js
new file mode 100644
index 000000000..53cc70db0
--- /dev/null
+++ b/browser/components/shell/content/setDesktopBackground.js
@@ -0,0 +1,214 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+var Ci = Components.interfaces;
+
+var gSetBackground = {
+ _position : AppConstants.platform == "macosx" ? "STRETCH" : "",
+ _backgroundColor : AppConstants.platform != "macosx" ? 0 : undefined,
+ _screenWidth : 0,
+ _screenHeight : 0,
+ _image : null,
+ _canvas : null,
+
+ get _shell()
+ {
+ return Components.classes["@mozilla.org/browser/shell-service;1"]
+ .getService(Ci.nsIShellService);
+ },
+
+ load: function ()
+ {
+ this._canvas = document.getElementById("screen");
+ this._screenWidth = screen.width;
+ this._screenHeight = screen.height;
+ if (AppConstants.platform == "macosx") {
+ document.documentElement.getButton("accept").hidden = true;
+ }
+ if (this._screenWidth / this._screenHeight >= 1.6)
+ document.getElementById("monitor").setAttribute("aspectratio", "16:10");
+
+ if (AppConstants.platform == "win") {
+ // Hide fill + fit options if < Win7 since they don't work.
+ var version = Components.classes["@mozilla.org/system-info;1"]
+ .getService(Ci.nsIPropertyBag2)
+ .getProperty("version");
+ var isWindows7OrHigher = (parseFloat(version) >= 6.1);
+ if (!isWindows7OrHigher) {
+ document.getElementById("fillPosition").hidden = true;
+ document.getElementById("fitPosition").hidden = true;
+ }
+ }
+
+ // make sure that the correct dimensions will be used
+ setTimeout(function(self) {
+ self.init(window.arguments[0]);
+ }, 0, this);
+ },
+
+ init: function (aImage)
+ {
+ this._image = aImage;
+
+ // set the size of the coordinate space
+ this._canvas.width = this._canvas.clientWidth;
+ this._canvas.height = this._canvas.clientHeight;
+
+ var ctx = this._canvas.getContext("2d");
+ ctx.scale(this._canvas.clientWidth / this._screenWidth, this._canvas.clientHeight / this._screenHeight);
+
+ if (AppConstants.platform != "macosx") {
+ this._initColor();
+ } else {
+ // Make sure to reset the button state in case the user has already
+ // set an image as their desktop background.
+ var setDesktopBackground = document.getElementById("setDesktopBackground");
+ setDesktopBackground.hidden = false;
+ var bundle = document.getElementById("backgroundBundle");
+ setDesktopBackground.label = bundle.getString("DesktopBackgroundSet");
+ setDesktopBackground.disabled = false;
+
+ document.getElementById("showDesktopPreferences").hidden = true;
+ }
+ this.updatePosition();
+ },
+
+ setDesktopBackground: function ()
+ {
+ if (AppConstants.platform != "macosx") {
+ document.persist("menuPosition", "value");
+ this._shell.desktopBackgroundColor = this._hexStringToLong(this._backgroundColor);
+ } else {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .addObserver(this, "shell:desktop-background-changed", false);
+
+ var bundle = document.getElementById("backgroundBundle");
+ var setDesktopBackground = document.getElementById("setDesktopBackground");
+ setDesktopBackground.disabled = true;
+ setDesktopBackground.label = bundle.getString("DesktopBackgroundDownloading");
+ }
+ this._shell.setDesktopBackground(this._image,
+ Ci.nsIShellService["BACKGROUND_" + this._position]);
+ },
+
+ updatePosition: function ()
+ {
+ var ctx = this._canvas.getContext("2d");
+ ctx.clearRect(0, 0, this._screenWidth, this._screenHeight);
+
+ if (AppConstants.platform != "macosx") {
+ this._position = document.getElementById("menuPosition").value;
+ }
+
+ switch (this._position) {
+ case "TILE":
+ ctx.save();
+ ctx.fillStyle = ctx.createPattern(this._image, "repeat");
+ ctx.fillRect(0, 0, this._screenWidth, this._screenHeight);
+ ctx.restore();
+ break;
+ case "STRETCH":
+ ctx.drawImage(this._image, 0, 0, this._screenWidth, this._screenHeight);
+ break;
+ case "CENTER": {
+ let x = (this._screenWidth - this._image.naturalWidth) / 2;
+ let y = (this._screenHeight - this._image.naturalHeight) / 2;
+ ctx.drawImage(this._image, x, y);
+ break;
+ }
+ case "FILL": {
+ // Try maxing width first, overflow height.
+ let widthRatio = this._screenWidth / this._image.naturalWidth;
+ let width = this._image.naturalWidth * widthRatio;
+ let height = this._image.naturalHeight * widthRatio;
+ if (height < this._screenHeight) {
+ // Height less than screen, max height and overflow width.
+ let heightRatio = this._screenHeight / this._image.naturalHeight;
+ width = this._image.naturalWidth * heightRatio;
+ height = this._image.naturalHeight * heightRatio;
+ }
+ let x = (this._screenWidth - width) / 2;
+ let y = (this._screenHeight - height) / 2;
+ ctx.drawImage(this._image, x, y, width, height);
+ break;
+ }
+ case "FIT": {
+ // Try maxing width first, top and bottom borders.
+ let widthRatio = this._screenWidth / this._image.naturalWidth;
+ let width = this._image.naturalWidth * widthRatio;
+ let height = this._image.naturalHeight * widthRatio;
+ let x = 0;
+ let y = (this._screenHeight - height) / 2;
+ if (height > this._screenHeight) {
+ // Height overflow, maximise height, side borders.
+ let heightRatio = this._screenHeight / this._image.naturalHeight;
+ width = this._image.naturalWidth * heightRatio;
+ height = this._image.naturalHeight * heightRatio;
+ x = (this._screenWidth - width) / 2;
+ y = 0;
+ }
+ ctx.drawImage(this._image, x, y, width, height);
+ break;
+ }
+ }
+ }
+};
+
+if (AppConstants.platform != "macosx") {
+ gSetBackground["_initColor"] = function ()
+ {
+ var color = this._shell.desktopBackgroundColor;
+
+ const rMask = 4294901760;
+ const gMask = 65280;
+ const bMask = 255;
+ var r = (color & rMask) >> 16;
+ var g = (color & gMask) >> 8;
+ var b = (color & bMask);
+ this.updateColor(this._rgbToHex(r, g, b));
+
+ var colorpicker = document.getElementById("desktopColor");
+ colorpicker.color = this._backgroundColor;
+ };
+
+ gSetBackground["updateColor"] = function (aColor)
+ {
+ this._backgroundColor = aColor;
+ this._canvas.style.backgroundColor = aColor;
+ };
+
+ // Converts a color string in the format "#RRGGBB" to an integer.
+ gSetBackground["_hexStringToLong"] = function (aString)
+ {
+ return parseInt(aString.substring(1, 3), 16) << 16 |
+ parseInt(aString.substring(3, 5), 16) << 8 |
+ parseInt(aString.substring(5, 7), 16);
+ };
+
+ gSetBackground["_rgbToHex"] = function (aR, aG, aB)
+ {
+ return "#" + [aR, aG, aB].map(aInt => aInt.toString(16).replace(/^(.)$/, "0$1"))
+ .join("").toUpperCase();
+ };
+} else {
+ gSetBackground["observe"] = function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "shell:desktop-background-changed") {
+ document.getElementById("setDesktopBackground").hidden = true;
+ document.getElementById("showDesktopPreferences").hidden = false;
+
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .removeObserver(this, "shell:desktop-background-changed");
+ }
+ };
+
+ gSetBackground["showDesktopPrefs"] = function()
+ {
+ this._shell.openApplication(Ci.nsIMacShellService.APPLICATION_DESKTOP);
+ };
+}
diff --git a/browser/components/shell/content/setDesktopBackground.xul b/browser/components/shell/content/setDesktopBackground.xul
new file mode 100644
index 000000000..d7d4079e3
--- /dev/null
+++ b/browser/components/shell/content/setDesktopBackground.xul
@@ -0,0 +1,84 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/setDesktopBackground.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/setDesktopBackground.dtd">
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ windowtype="Shell:SetDesktopBackground"
+#ifndef XP_MACOSX
+ buttons="accept,cancel"
+#else
+ buttons="accept"
+#endif
+ buttonlabelaccept="&setDesktopBackground.title;"
+ onload="gSetBackground.load();"
+ ondialogaccept="gSetBackground.setDesktopBackground();"
+ title="&setDesktopBackground.title;"
+ style="width: 30em;">
+
+ <stringbundle id="backgroundBundle"
+ src="chrome://browser/locale/shellservice.properties"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript" src="chrome://browser/content/setDesktopBackground.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+#ifndef XP_MACOSX
+ <hbox align="center">
+ <label value="&position.label;"/>
+ <menulist id="menuPosition"
+ label="&position.label;"
+ oncommand="gSetBackground.updatePosition();">
+ <menupopup>
+ <menuitem label="&center.label;" value="CENTER"/>
+ <menuitem label="&tile.label;" value="TILE"/>
+ <menuitem label="&stretch.label;" value="STRETCH"/>
+ <menuitem label="&fill.label;" value="FILL" id="fillPosition"/>
+ <menuitem label="&fit.label;" value="FIT" id="fitPosition"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ <label value="&color.label;"/>
+ <colorpicker id="desktopColor"
+ type="button"
+ onchange="gSetBackground.updateColor(this.color);"/>
+ </hbox>
+#endif
+ <groupbox align="center">
+ <caption label="&preview.label;"/>
+ <stack>
+ <!-- if width and height are not present, they default to 300x150 and stretch the stack -->
+ <html:canvas id="screen" width="1" height="1"/>
+ <image id="monitor"/>
+ </stack>
+ </groupbox>
+
+#ifdef XP_MACOSX
+ <separator/>
+
+ <hbox align="right">
+ <button id="setDesktopBackground"
+ label="&setDesktopBackground.title;"
+ oncommand="gSetBackground.setDesktopBackground();"/>
+ <button id="showDesktopPreferences"
+ label="&openDesktopPrefs.label;"
+ oncommand="gSetBackground.showDesktopPrefs();"
+ hidden="true"/>
+ </hbox>
+#endif
+
+#ifdef XP_MACOSX
+#include ../../../base/content/browserMountPoints.inc
+#endif
+
+</dialog>
diff --git a/browser/components/shell/jar.mn b/browser/components/shell/jar.mn
new file mode 100644
index 000000000..1f33b5d56
--- /dev/null
+++ b/browser/components/shell/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+* content/browser/setDesktopBackground.xul (content/setDesktopBackground.xul)
+ content/browser/setDesktopBackground.js (content/setDesktopBackground.js)
diff --git a/browser/components/shell/moz.build b/browser/components/shell/moz.build
new file mode 100644
index 000000000..7a605de5f
--- /dev/null
+++ b/browser/components/shell/moz.build
@@ -0,0 +1,62 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += [
+ 'nsIShellService.idl',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ XPIDL_SOURCES += [
+ 'nsIWindowsShellService.idl',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ XPIDL_SOURCES += [
+ 'nsIMacShellService.idl',
+ ]
+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ XPIDL_SOURCES += [
+ 'nsIGNOMEShellService.idl',
+ ]
+
+XPIDL_MODULE = 'shellservice'
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += [
+ 'nsWindowsShellService.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsMacShellService.cpp',
+ ]
+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ SOURCES += [
+ 'nsGNOMEShellService.cpp',
+ ]
+
+if SOURCES:
+ FINAL_LIBRARY = 'browsercomps'
+
+EXTRA_COMPONENTS += [
+ 'nsSetDefaultBrowser.js',
+ 'nsSetDefaultBrowser.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'ShellService.jsm',
+]
+
+for var in ('MOZ_APP_NAME', 'MOZ_APP_VERSION'):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Shell Integration')
diff --git a/browser/components/shell/nsGNOMEShellService.cpp b/browser/components/shell/nsGNOMEShellService.cpp
new file mode 100644
index 000000000..f6c2613d4
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellService.cpp
@@ -0,0 +1,638 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsGNOMEShellService.h"
+#include "nsShellService.h"
+#include "nsIServiceManager.h"
+#include "nsIFile.h"
+#include "nsIProperties.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIPrefService.h"
+#include "prenv.h"
+#include "nsString.h"
+#include "nsIGConfService.h"
+#include "nsIGIOService.h"
+#include "nsIGSettingsService.h"
+#include "nsIStringBundle.h"
+#include "nsIOutputStream.h"
+#include "nsIProcess.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIImageLoadingContent.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "mozilla/Sprintf.h"
+#if defined(MOZ_WIDGET_GTK)
+#include "nsIImageToPixbuf.h"
+#endif
+#include "nsXULAppAPI.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using namespace mozilla;
+
+struct ProtocolAssociation
+{
+ const char *name;
+ bool essential;
+};
+
+struct MimeTypeAssociation
+{
+ const char *mimeType;
+ const char *extensions;
+};
+
+static const ProtocolAssociation appProtocols[] = {
+ { "http", true },
+ { "https", true },
+ { "ftp", false },
+ { "chrome", false }
+};
+
+static const MimeTypeAssociation appTypes[] = {
+ { "text/html", "htm html shtml" },
+ { "application/xhtml+xml", "xhtml xht" }
+};
+
+// GConf registry key constants
+#define DG_BACKGROUND "/desktop/gnome/background"
+
+#define kDesktopImageKey DG_BACKGROUND "/picture_filename"
+#define kDesktopOptionsKey DG_BACKGROUND "/picture_options"
+#define kDesktopDrawBGKey DG_BACKGROUND "/draw_background"
+#define kDesktopColorKey DG_BACKGROUND "/primary_color"
+
+#define kDesktopBGSchema "org.gnome.desktop.background"
+#define kDesktopImageGSKey "picture-uri"
+#define kDesktopOptionGSKey "picture-options"
+#define kDesktopDrawBGGSKey "draw-background"
+#define kDesktopColorGSKey "primary-color"
+
+nsresult
+nsGNOMEShellService::Init()
+{
+ nsresult rv;
+
+ // GConf, GSettings or GIO _must_ be available, or we do not allow
+ // CreateInstance to succeed.
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGIOService> giovfs =
+ do_GetService(NS_GIOSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+
+ if (!gconf && !giovfs && !gsettings)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Check G_BROKEN_FILENAMES. If it's set, then filenames in glib use
+ // the locale encoding. If it's not set, they use UTF-8.
+ mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr;
+
+ if (GetAppPathFromLauncher())
+ return NS_OK;
+
+ nsCOMPtr<nsIProperties> dirSvc
+ (do_GetService("@mozilla.org/file/directory_service;1"));
+ NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIFile> appPath;
+ rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(appPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return appPath->GetNativePath(mAppPath);
+}
+
+NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService)
+
+bool
+nsGNOMEShellService::GetAppPathFromLauncher()
+{
+ gchar *tmp;
+
+ const char *launcher = PR_GetEnv("MOZ_APP_LAUNCHER");
+ if (!launcher)
+ return false;
+
+ if (g_path_is_absolute(launcher)) {
+ mAppPath = launcher;
+ tmp = g_path_get_basename(launcher);
+ gchar *fullpath = g_find_program_in_path(tmp);
+ if (fullpath && mAppPath.Equals(fullpath))
+ mAppIsInPath = true;
+ g_free(fullpath);
+ } else {
+ tmp = g_find_program_in_path(launcher);
+ if (!tmp)
+ return false;
+ mAppPath = tmp;
+ mAppIsInPath = true;
+ }
+
+ g_free(tmp);
+ return true;
+}
+
+bool
+nsGNOMEShellService::KeyMatchesAppName(const char *aKeyValue) const
+{
+
+ gchar *commandPath;
+ if (mUseLocaleFilenames) {
+ gchar *nativePath = g_filename_from_utf8(aKeyValue, -1,
+ nullptr, nullptr, nullptr);
+ if (!nativePath) {
+ NS_ERROR("Error converting path to filesystem encoding");
+ return false;
+ }
+
+ commandPath = g_find_program_in_path(nativePath);
+ g_free(nativePath);
+ } else {
+ commandPath = g_find_program_in_path(aKeyValue);
+ }
+
+ if (!commandPath)
+ return false;
+
+ bool matches = mAppPath.Equals(commandPath);
+ g_free(commandPath);
+ return matches;
+}
+
+bool
+nsGNOMEShellService::CheckHandlerMatchesAppName(const nsACString &handler) const
+{
+ gint argc;
+ gchar **argv;
+ nsAutoCString command(handler);
+
+ // The string will be something of the form: [/path/to/]browser "%s"
+ // We want to remove all of the parameters and get just the binary name.
+
+ if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) {
+ command.Assign(argv[0]);
+ g_strfreev(argv);
+ }
+
+ if (!KeyMatchesAppName(command.get()))
+ return false; // the handler is set to another app
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::IsDefaultBrowser(bool aStartupCheck,
+ bool aForAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ *aIsDefaultBrowser = false;
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+
+ bool enabled;
+ nsAutoCString handler;
+ nsCOMPtr<nsIGIOMimeApp> gioApp;
+
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (!appProtocols[i].essential)
+ continue;
+
+ if (gconf) {
+ handler.Truncate();
+ gconf->GetAppForProtocol(nsDependentCString(appProtocols[i].name),
+ &enabled, handler);
+
+ if (!CheckHandlerMatchesAppName(handler) || !enabled)
+ return NS_OK; // the handler is disabled or set to another app
+ }
+
+ if (giovfs) {
+ handler.Truncate();
+ giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name),
+ getter_AddRefs(gioApp));
+ if (!gioApp)
+ return NS_OK;
+
+ gioApp->GetCommand(handler);
+
+ if (!CheckHandlerMatchesAppName(handler))
+ return NS_OK; // the handler is set to another app
+ }
+ }
+
+ *aIsDefaultBrowser = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes,
+ bool aForAllUsers)
+{
+#ifdef DEBUG
+ if (aForAllUsers)
+ NS_WARNING("Setting the default browser for all users is not yet supported");
+#endif
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (gconf) {
+ nsAutoCString appKeyValue;
+ if (mAppIsInPath) {
+ // mAppPath is in the users path, so use only the basename as the launcher
+ gchar *tmp = g_path_get_basename(mAppPath.get());
+ appKeyValue = tmp;
+ g_free(tmp);
+ } else {
+ appKeyValue = mAppPath;
+ }
+
+ appKeyValue.AppendLiteral(" %s");
+
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (appProtocols[i].essential || aClaimAllTypes) {
+ gconf->SetAppForProtocol(nsDependentCString(appProtocols[i].name),
+ appKeyValue);
+ }
+ }
+ }
+
+ if (giovfs) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(BRAND_PROPERTIES, getter_AddRefs(brandBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString brandShortName;
+ brandBundle->GetStringFromName(u"brandShortName",
+ getter_Copies(brandShortName));
+
+ // use brandShortName as the application id.
+ NS_ConvertUTF16toUTF8 id(brandShortName);
+ nsCOMPtr<nsIGIOMimeApp> appInfo;
+ rv = giovfs->CreateAppFromCommand(mAppPath,
+ id,
+ getter_AddRefs(appInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set handler for the protocols
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (appProtocols[i].essential || aClaimAllTypes) {
+ appInfo->SetAsDefaultForURIScheme(nsDependentCString(appProtocols[i].name));
+ }
+ }
+
+ // set handler for .html and xhtml files and MIME types:
+ if (aClaimAllTypes) {
+ // Add mime types for html, xhtml extension and set app to just created appinfo.
+ for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) {
+ appInfo->SetAsDefaultForMimeType(nsDependentCString(appTypes[i].mimeType));
+ appInfo->SetAsDefaultForFileExtensions(nsDependentCString(appTypes[i].extensions));
+ }
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult)
+{
+ // setting desktop background is currently only supported
+ // for Gnome or desktops using the same GSettings and GConf keys
+ const char* gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID");
+ if (gnomeSession) {
+ *aResult = true;
+ } else {
+ *aResult = false;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+WriteImage(const nsCString& aPath, imgIContainer* aImage)
+{
+#if !defined(MOZ_WIDGET_GTK)
+ return NS_ERROR_NOT_AVAILABLE;
+#else
+ nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
+ do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
+ if (!imgToPixbuf)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ GdkPixbuf* pixbuf = imgToPixbuf->ConvertImageToPixbuf(aImage);
+ if (!pixbuf)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr);
+
+ g_object_unref(pixbuf);
+ return res ? NS_OK : NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDesktopBackground(nsIDOMElement* aElement,
+ int32_t aPosition)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement, &rv);
+ if (!imageContent) return rv;
+
+ // get the image container
+ nsCOMPtr<imgIRequest> request;
+ rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ if (!request) return rv;
+ nsCOMPtr<imgIContainer> container;
+ rv = request->GetImage(getter_AddRefs(container));
+ if (!container) return rv;
+
+ // Set desktop wallpaper filling style
+ nsAutoCString options;
+ if (aPosition == BACKGROUND_TILE)
+ options.AssignLiteral("wallpaper");
+ else if (aPosition == BACKGROUND_STRETCH)
+ options.AssignLiteral("stretched");
+ else if (aPosition == BACKGROUND_FILL)
+ options.AssignLiteral("zoom");
+ else if (aPosition == BACKGROUND_FIT)
+ options.AssignLiteral("scaled");
+ else
+ options.AssignLiteral("centered");
+
+ // Write the background file to the home directory.
+ nsAutoCString filePath(PR_GetEnv("HOME"));
+
+ // get the product brand name from localized strings
+ nsString brandName;
+ nsCID bundleCID = NS_STRINGBUNDLESERVICE_CID;
+ nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(bundleCID));
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(BRAND_PROPERTIES,
+ getter_AddRefs(brandBundle));
+ if (NS_SUCCEEDED(rv) && brandBundle) {
+ rv = brandBundle->GetStringFromName(u"brandShortName",
+ getter_Copies(brandName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // build the file name
+ filePath.Append('/');
+ filePath.Append(NS_ConvertUTF16toUTF8(brandName));
+ filePath.AppendLiteral("_wallpaper.png");
+
+ // write the image to a file in the home dir
+ rv = WriteImage(filePath, container);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try GSettings first. If we don't have GSettings or the right schema, fall back
+ // to using GConf instead. Note that if GSettings works ok, the changes get
+ // mirrored to GConf by the gsettings->gconf bridge in gnome-settings-daemon
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ gsettings->GetCollectionForSchema(
+ NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings));
+ if (background_settings) {
+ gchar *file_uri = g_filename_to_uri(filePath.get(), nullptr, nullptr);
+ if (!file_uri)
+ return NS_ERROR_FAILURE;
+
+ background_settings->SetString(NS_LITERAL_CSTRING(kDesktopOptionGSKey),
+ options);
+
+ background_settings->SetString(NS_LITERAL_CSTRING(kDesktopImageGSKey),
+ nsDependentCString(file_uri));
+ g_free(file_uri);
+ background_settings->SetBoolean(NS_LITERAL_CSTRING(kDesktopDrawBGGSKey),
+ true);
+ return rv;
+ }
+ }
+
+ // if the file was written successfully, set it as the system wallpaper
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+
+ if (gconf) {
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopOptionsKey), options);
+
+ // Set the image to an empty string first to force a refresh
+ // (since we could be writing a new image on top of an existing
+ // Firefox_wallpaper.png and nautilus doesn't monitor the file for changes)
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey),
+ EmptyCString());
+
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey), filePath);
+ gconf->SetBool(NS_LITERAL_CSTRING(kDesktopDrawBGKey), true);
+ }
+
+ return rv;
+}
+
+#define COLOR_16_TO_8_BIT(_c) ((_c) >> 8)
+#define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c))
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t *aColor)
+{
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ nsAutoCString background;
+
+ if (gsettings) {
+ gsettings->GetCollectionForSchema(
+ NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings));
+ if (background_settings) {
+ background_settings->GetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
+ background);
+ }
+ }
+
+ if (!background_settings) {
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ if (gconf)
+ gconf->GetString(NS_LITERAL_CSTRING(kDesktopColorKey), background);
+ }
+
+ if (background.IsEmpty()) {
+ *aColor = 0;
+ return NS_OK;
+ }
+
+ GdkColor color;
+ gboolean success = gdk_color_parse(background.get(), &color);
+
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+ *aColor = COLOR_16_TO_8_BIT(color.red) << 16 |
+ COLOR_16_TO_8_BIT(color.green) << 8 |
+ COLOR_16_TO_8_BIT(color.blue);
+ return NS_OK;
+}
+
+static void
+ColorToCString(uint32_t aColor, nsCString& aResult)
+{
+ // The #rrrrggggbbbb format is used to match gdk_color_to_string()
+ aResult.SetLength(13);
+ char *buf = aResult.BeginWriting();
+ if (!buf)
+ return;
+
+ uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff);
+ uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff);
+ uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff);
+
+ snprintf(buf, 14, "#%04x%04x%04x", red, green, blue);
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor)
+{
+ NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits");
+ nsAutoCString colorString;
+ ColorToCString(aColor, colorString);
+
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ gsettings->GetCollectionForSchema(
+ NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings));
+ if (background_settings) {
+ background_settings->SetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
+ colorString);
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+
+ if (gconf) {
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopColorKey), colorString);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::OpenApplication(int32_t aApplication)
+{
+ nsAutoCString scheme;
+ if (aApplication == APPLICATION_MAIL)
+ scheme.AssignLiteral("mailto");
+ else if (aApplication == APPLICATION_NEWS)
+ scheme.AssignLiteral("news");
+ else
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (giovfs) {
+ nsCOMPtr<nsIGIOMimeApp> gioApp;
+ giovfs->GetAppForURIScheme(scheme, getter_AddRefs(gioApp));
+ if (gioApp)
+ return gioApp->Launch(EmptyCString());
+ }
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ if (!gconf)
+ return NS_ERROR_FAILURE;
+
+ bool enabled;
+ nsAutoCString appCommand;
+ gconf->GetAppForProtocol(scheme, &enabled, appCommand);
+
+ if (!enabled)
+ return NS_ERROR_FAILURE;
+
+ // XXX we don't currently handle launching a terminal window.
+ // If the handler requires a terminal, bail.
+ bool requiresTerminal;
+ gconf->HandlerRequiresTerminal(scheme, &requiresTerminal);
+ if (requiresTerminal)
+ return NS_ERROR_FAILURE;
+
+ // Perform shell argument expansion
+ int argc;
+ char **argv;
+ if (!g_shell_parse_argv(appCommand.get(), &argc, &argv, nullptr))
+ return NS_ERROR_FAILURE;
+
+ char **newArgv = new char*[argc + 1];
+ int newArgc = 0;
+
+ // Run through the list of arguments. Copy all of them to the new
+ // argv except for %s, which we skip.
+ for (int i = 0; i < argc; ++i) {
+ if (strcmp(argv[i], "%s") != 0)
+ newArgv[newArgc++] = argv[i];
+ }
+
+ newArgv[newArgc] = nullptr;
+
+ gboolean err = g_spawn_async(nullptr, newArgv, nullptr, G_SPAWN_SEARCH_PATH,
+ nullptr, nullptr, nullptr, nullptr);
+
+ g_strfreev(argv);
+ delete[] newArgv;
+
+ return err ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process =
+ do_CreateInstance("@mozilla.org/process/util;1", &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = process->Init(aApplication);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const nsCString spec(aURI);
+ const char* specStr = spec.get();
+ return process->Run(false, &specStr, 1);
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetDefaultFeedReader(nsIFile** _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/browser/components/shell/nsGNOMEShellService.h b/browser/components/shell/nsGNOMEShellService.h
new file mode 100644
index 000000000..b3ef1a918
--- /dev/null
+++ b/browser/components/shell/nsGNOMEShellService.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsgnomeshellservice_h____
+#define nsgnomeshellservice_h____
+
+#include "nsIGNOMEShellService.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+class nsGNOMEShellService final : public nsIGNOMEShellService
+{
+public:
+ nsGNOMEShellService() : mAppIsInPath(false) { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIGNOMESHELLSERVICE
+
+ nsresult Init();
+
+private:
+ ~nsGNOMEShellService() {}
+
+ bool KeyMatchesAppName(const char *aKeyValue) const;
+ bool CheckHandlerMatchesAppName(const nsACString& handler) const;
+
+ bool GetAppPathFromLauncher();
+ bool mUseLocaleFilenames;
+ nsCString mAppPath;
+ bool mAppIsInPath;
+};
+
+#endif // nsgnomeshellservice_h____
diff --git a/browser/components/shell/nsIGNOMEShellService.idl b/browser/components/shell/nsIGNOMEShellService.idl
new file mode 100644
index 000000000..842ce5e8a
--- /dev/null
+++ b/browser/components/shell/nsIGNOMEShellService.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(2ce5c803-edcd-443d-98eb-ceba86d02d13)]
+interface nsIGNOMEShellService : nsIShellService
+{
+ /**
+ * Used to determine whether or not to offer "Set as desktop background"
+ * functionality. Even if shell service is available it is not
+ * guaranteed that it is able to set the background for every desktop
+ * which is especially true for Linux with its many different desktop
+ * environments.
+ */
+ readonly attribute boolean canSetDesktopBackground;
+};
+
diff --git a/browser/components/shell/nsIMacShellService.idl b/browser/components/shell/nsIMacShellService.idl
new file mode 100644
index 000000000..6a532bbd0
--- /dev/null
+++ b/browser/components/shell/nsIMacShellService.idl
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(387fdc80-0077-4b60-a0d9-d9e80a83ba64)]
+interface nsIMacShellService : nsIShellService
+{
+ const long APPLICATION_KEYCHAIN_ACCESS = 2;
+ const long APPLICATION_NETWORK = 3;
+ const long APPLICATION_DESKTOP = 4;
+};
+
diff --git a/browser/components/shell/nsIShellService.idl b/browser/components/shell/nsIShellService.idl
new file mode 100644
index 000000000..3e7e94b00
--- /dev/null
+++ b/browser/components/shell/nsIShellService.idl
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIFile;
+
+[scriptable, uuid(2d1a95e4-5bd8-4eeb-b0a8-c1455fd2a357)]
+interface nsIShellService : nsISupports
+{
+ /**
+ * Determines whether or not Firefox is the "Default Browser."
+ * This is simply whether or not Firefox is registered to handle
+ * http links.
+ *
+ * @param aStartupCheck true if this is the check being performed
+ * by the first browser window at startup,
+ * false otherwise.
+ * @param aForAllTypes true if the check should be made for HTTP and HTML.
+ * false if the check should be made for HTTP only.
+ * This parameter may be ignored on some platforms.
+ */
+ boolean isDefaultBrowser(in boolean aStartupCheck,
+ [optional] in boolean aForAllTypes);
+
+ /**
+ * Registers Firefox as the "Default Browser."
+ *
+ * @param aClaimAllTypes Register Firefox as the handler for
+ * additional protocols (ftp, chrome etc)
+ * and web documents (.html, .xhtml etc).
+ * @param aForAllUsers Whether or not Firefox should attempt
+ * to become the default browser for all
+ * users on a multi-user system.
+ */
+ void setDefaultBrowser(in boolean aClaimAllTypes, in boolean aForAllUsers);
+
+ /**
+ * Flags for positioning/sizing of the Desktop Background image.
+ */
+ const long BACKGROUND_TILE = 1;
+ const long BACKGROUND_STRETCH = 2;
+ const long BACKGROUND_CENTER = 3;
+ const long BACKGROUND_FILL = 4;
+ const long BACKGROUND_FIT = 5;
+
+ /**
+ * Sets the desktop background image using either the HTML <IMG>
+ * element supplied or the background image of the element supplied.
+ *
+ * @param aImageElement Either a HTML <IMG> element or an element with
+ * a background image from which to source the
+ * background image.
+ * @param aPosition How to place the image on the desktop
+ */
+ void setDesktopBackground(in nsIDOMElement aElement, in long aPosition);
+
+ /**
+ * Constants identifying applications that can be opened with
+ * openApplication.
+ */
+ const long APPLICATION_MAIL = 0;
+ const long APPLICATION_NEWS = 1;
+
+ /**
+ * Opens the application specified. If more than one application of the
+ * given type is available on the system, the default or "preferred"
+ * application is used.
+ */
+ void openApplication(in long aApplication);
+
+ /**
+ * The desktop background color, visible when no background image is
+ * used, or if the background image is centered and does not fill the
+ * entire screen. A rgb value, where (r << 16 | g << 8 | b)
+ */
+ attribute unsigned long desktopBackgroundColor;
+
+ /**
+ * Opens an application with a specific URI to load.
+ * @param application
+ * The application file (or bundle directory, on OS X)
+ * @param uri
+ * The uri to be loaded by the application
+ */
+ void openApplicationWithURI(in nsIFile aApplication, in ACString aURI);
+
+ /**
+ * The default system handler for web feeds
+ */
+ readonly attribute nsIFile defaultFeedReader;
+};
diff --git a/browser/components/shell/nsIWindowsShellService.idl b/browser/components/shell/nsIWindowsShellService.idl
new file mode 100644
index 000000000..57ed37055
--- /dev/null
+++ b/browser/components/shell/nsIWindowsShellService.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(f8a26b94-49e5-4441-8fbc-315e0b4f22ef)]
+interface nsIWindowsShellService : nsIShellService
+{
+ /**
+ * Provides the shell service an opportunity to do some Win7+ shortcut
+ * maintenance needed on initial startup of the browser.
+ */
+ void shortcutMaintenance();
+};
+
diff --git a/browser/components/shell/nsMacShellService.cpp b/browser/components/shell/nsMacShellService.cpp
new file mode 100644
index 000000000..48db4896b
--- /dev/null
+++ b/browser/components/shell/nsMacShellService.cpp
@@ -0,0 +1,434 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDirectoryServiceDefs.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsILocalFileMac.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsIURL.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsMacShellService.h"
+#include "nsIProperties.h"
+#include "nsServiceManagerUtils.h"
+#include "nsShellService.h"
+#include "nsString.h"
+#include "nsIDocShell.h"
+#include "nsILoadContext.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <ApplicationServices/ApplicationServices.h>
+
+#define NETWORK_PREFPANE NS_LITERAL_CSTRING("/System/Library/PreferencePanes/Network.prefPane")
+#define DESKTOP_PREFPANE NS_LITERAL_CSTRING("/System/Library/PreferencePanes/DesktopScreenEffectsPref.prefPane")
+
+#define SAFARI_BUNDLE_IDENTIFIER "com.apple.Safari"
+
+NS_IMPL_ISUPPORTS(nsMacShellService, nsIMacShellService, nsIShellService, nsIWebProgressListener)
+
+NS_IMETHODIMP
+nsMacShellService::IsDefaultBrowser(bool aStartupCheck,
+ bool aForAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ *aIsDefaultBrowser = false;
+
+ CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
+ if (!firefoxID) {
+ // CFBundleGetIdentifier is expected to return nullptr only if the specified
+ // bundle doesn't have a bundle identifier in its plist. In this case, that
+ // means a failure, since our bundle does have an identifier.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the default http handler's bundle ID (or nullptr if it has not been
+ // explicitly set)
+ CFStringRef defaultBrowserID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("http"));
+ if (defaultBrowserID) {
+ *aIsDefaultBrowser = ::CFStringCompare(firefoxID, defaultBrowserID, 0) == kCFCompareEqualTo;
+ ::CFRelease(defaultBrowserID);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
+{
+ // Note: We don't support aForAllUsers on Mac OS X.
+
+ CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
+ if (!firefoxID) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("http"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("https"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aClaimAllTypes) {
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("ftp"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll, firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDesktopBackground(nsIDOMElement* aElement,
+ int32_t aPosition)
+{
+ // Note: We don't support aPosition on OS X.
+
+ // Get the image URI:
+ nsresult rv;
+ nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement,
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> imageURI;
+ rv = imageContent->GetCurrentURI(getter_AddRefs(imageURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We need the referer URI for nsIWebBrowserPersist::saveURI
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIURI *docURI = content->OwnerDoc()->GetDocumentURI();
+ if (!docURI)
+ return NS_ERROR_FAILURE;
+
+ // Get the desired image file name
+ nsCOMPtr<nsIURL> imageURL(do_QueryInterface(imageURI));
+ if (!imageURL) {
+ // XXXmano (bug 300293): Non-URL images (e.g. the data: protocol) are not
+ // yet supported. What filename should we take here?
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsAutoCString fileName;
+ imageURL->GetFileName(fileName);
+ nsCOMPtr<nsIProperties> fileLocator
+ (do_GetService("@mozilla.org/file/directory_service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current user's "Pictures" folder (That's ~/Pictures):
+ fileLocator->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(mBackgroundFile));
+ if (!mBackgroundFile)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsAutoString fileNameUnicode;
+ CopyUTF8toUTF16(fileName, fileNameUnicode);
+
+ // and add the imgage file name itself:
+ mBackgroundFile->Append(fileNameUnicode);
+
+ // Download the image; the desktop background will be set in OnStateChange()
+ nsCOMPtr<nsIWebBrowserPersist> wbp
+ (do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
+ nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE;
+
+ wbp->SetPersistFlags(flags);
+ wbp->SetProgressListener(this);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ nsCOMPtr<nsISupports> container = content->OwnerDoc()->GetContainer();
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
+ if (docShell) {
+ loadContext = do_QueryInterface(docShell);
+ }
+
+ return wbp->SaveURI(imageURI, nullptr,
+ docURI, content->OwnerDoc()->GetReferrerPolicy(),
+ nullptr, nullptr,
+ mBackgroundFile, loadContext);
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ if (aStateFlags & STATE_STOP) {
+ nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
+ if (os)
+ os->NotifyObservers(nullptr, "shell:desktop-background-changed", nullptr);
+
+ bool exists = false;
+ mBackgroundFile->Exists(&exists);
+ if (!exists)
+ return NS_OK;
+
+ nsAutoCString nativePath;
+ mBackgroundFile->GetNativePath(nativePath);
+
+ AEDesc tAEDesc = { typeNull, nil };
+ OSErr err = noErr;
+ AliasHandle aliasHandle = nil;
+ FSRef pictureRef;
+ OSStatus status;
+
+ // Convert the path into a FSRef
+ status = ::FSPathMakeRef((const UInt8*)nativePath.get(), &pictureRef,
+ nullptr);
+ if (status == noErr) {
+ err = ::FSNewAlias(nil, &pictureRef, &aliasHandle);
+ if (err == noErr && aliasHandle == nil)
+ err = paramErr;
+
+ if (err == noErr) {
+ // We need the descriptor (based on the picture file reference)
+ // for the 'Set Desktop Picture' apple event.
+ char handleState = ::HGetState((Handle)aliasHandle);
+ ::HLock((Handle)aliasHandle);
+ err = ::AECreateDesc(typeAlias, *aliasHandle,
+ GetHandleSize((Handle)aliasHandle), &tAEDesc);
+ // unlock the alias handler
+ ::HSetState((Handle)aliasHandle, handleState);
+ ::DisposeHandle((Handle)aliasHandle);
+ }
+ if (err == noErr) {
+ AppleEvent tAppleEvent;
+ OSType sig = 'MACS';
+ AEBuildError tAEBuildError;
+ // Create a 'Set Desktop Pictue' Apple Event
+ err = ::AEBuildAppleEvent(kAECoreSuite, kAESetData, typeApplSignature,
+ &sig, sizeof(OSType), kAutoGenerateReturnID,
+ kAnyTransactionID, &tAppleEvent, &tAEBuildError,
+ "'----':'obj '{want:type (prop),form:prop" \
+ ",seld:type('dpic'),from:'null'()},data:(@)",
+ &tAEDesc);
+ if (err == noErr) {
+ AppleEvent reply = { typeNull, nil };
+ // Sent the event we built, the reply event isn't necessary
+ err = ::AESend(&tAppleEvent, &reply, kAENoReply, kAENormalPriority,
+ kNoTimeOut, nil, nil);
+ ::AEDisposeDesc(&tAppleEvent);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OpenApplication(int32_t aApplication)
+{
+ nsresult rv = NS_OK;
+ CFURLRef appURL = nil;
+ OSStatus err = noErr;
+
+ switch (aApplication) {
+ case nsIShellService::APPLICATION_MAIL:
+ {
+ CFURLRef tempURL = ::CFURLCreateWithString(kCFAllocatorDefault,
+ CFSTR("mailto:"), nullptr);
+ err = ::LSGetApplicationForURL(tempURL, kLSRolesAll, nullptr, &appURL);
+ ::CFRelease(tempURL);
+ }
+ break;
+ case nsIShellService::APPLICATION_NEWS:
+ {
+ CFURLRef tempURL = ::CFURLCreateWithString(kCFAllocatorDefault,
+ CFSTR("news:"), nullptr);
+ err = ::LSGetApplicationForURL(tempURL, kLSRolesAll, nullptr, &appURL);
+ ::CFRelease(tempURL);
+ }
+ break;
+ case nsIMacShellService::APPLICATION_KEYCHAIN_ACCESS:
+ err = ::LSGetApplicationForInfo('APPL', 'kcmr', nullptr, kLSRolesAll,
+ nullptr, &appURL);
+ break;
+ case nsIMacShellService::APPLICATION_NETWORK:
+ {
+ nsCOMPtr<nsIFile> lf;
+ rv = NS_NewNativeLocalFile(NETWORK_PREFPANE, true, getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ lf->Exists(&exists);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+ return lf->Launch();
+ }
+ case nsIMacShellService::APPLICATION_DESKTOP:
+ {
+ nsCOMPtr<nsIFile> lf;
+ rv = NS_NewNativeLocalFile(DESKTOP_PREFPANE, true, getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ lf->Exists(&exists);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+ return lf->Launch();
+ }
+ }
+
+ if (appURL && err == noErr) {
+ err = ::LSOpenCFURLRef(appURL, nullptr);
+ rv = err != noErr ? NS_ERROR_FAILURE : NS_OK;
+
+ ::CFRelease(appURL);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMacShellService::GetDesktopBackgroundColor(uint32_t *aColor)
+{
+ // This method and |SetDesktopBackgroundColor| has no meaning on Mac OS X.
+ // The mac desktop preferences UI uses pictures for the few solid colors it
+ // supports.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDesktopBackgroundColor(uint32_t aColor)
+{
+ // This method and |GetDesktopBackgroundColor| has no meaning on Mac OS X.
+ // The mac desktop preferences UI uses pictures for the few solid colors it
+ // supports.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI)
+{
+ nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(aApplication));
+ CFURLRef appURL;
+ nsresult rv = lfm->GetCFURL(&appURL);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const nsCString spec(aURI);
+ const UInt8* uriString = (const UInt8*)spec.get();
+ CFURLRef uri = ::CFURLCreateWithBytes(nullptr, uriString, aURI.Length(),
+ kCFStringEncodingUTF8, nullptr);
+ if (!uri)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ CFArrayRef uris = ::CFArrayCreate(nullptr, (const void**)&uri, 1, nullptr);
+ if (!uris) {
+ ::CFRelease(uri);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ LSLaunchURLSpec launchSpec;
+ launchSpec.appURL = appURL;
+ launchSpec.itemURLs = uris;
+ launchSpec.passThruParams = nullptr;
+ launchSpec.launchFlags = kLSLaunchDefaults;
+ launchSpec.asyncRefCon = nullptr;
+
+ OSErr err = ::LSOpenFromURLSpec(&launchSpec, nullptr);
+
+ ::CFRelease(uris);
+ ::CFRelease(uri);
+
+ return err != noErr ? NS_ERROR_FAILURE : NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::GetDefaultFeedReader(nsIFile** _retval)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ *_retval = nullptr;
+
+ CFStringRef defaultHandlerID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("feed"));
+ if (!defaultHandlerID) {
+ defaultHandlerID = ::CFStringCreateWithCString(kCFAllocatorDefault,
+ SAFARI_BUNDLE_IDENTIFIER,
+ kCFStringEncodingASCII);
+ }
+
+ CFURLRef defaultHandlerURL = nullptr;
+ OSStatus status = ::LSFindApplicationForInfo(kLSUnknownCreator,
+ defaultHandlerID,
+ nullptr, // inName
+ nullptr, // outAppRef
+ &defaultHandlerURL);
+
+ if (status == noErr && defaultHandlerURL) {
+ nsCOMPtr<nsILocalFileMac> defaultReader =
+ do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = defaultReader->InitWithCFURL(defaultHandlerURL);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF(*_retval = defaultReader);
+ rv = NS_OK;
+ }
+ }
+
+ ::CFRelease(defaultHandlerURL);
+ }
+
+ ::CFRelease(defaultHandlerID);
+
+ return rv;
+}
diff --git a/browser/components/shell/nsMacShellService.h b/browser/components/shell/nsMacShellService.h
new file mode 100644
index 000000000..db9527809
--- /dev/null
+++ b/browser/components/shell/nsMacShellService.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsmacshellservice_h____
+#define nsmacshellservice_h____
+
+#include "nsIMacShellService.h"
+#include "nsIWebProgressListener.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+class nsMacShellService : public nsIMacShellService,
+ public nsIWebProgressListener
+{
+public:
+ nsMacShellService() {};
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIMACSHELLSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+protected:
+ virtual ~nsMacShellService() {};
+
+private:
+ nsCOMPtr<nsIFile> mBackgroundFile;
+};
+
+#endif // nsmacshellservice_h____
diff --git a/browser/components/shell/nsSetDefaultBrowser.js b/browser/components/shell/nsSetDefaultBrowser.js
new file mode 100644
index 000000000..bb09ab213
--- /dev/null
+++ b/browser/components/shell/nsSetDefaultBrowser.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * --setDefaultBrowser commandline handler
+ * Makes the current executable the "default browser".
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+Components.utils.import("resource:///modules/ShellService.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function nsSetDefaultBrowser() {}
+
+nsSetDefaultBrowser.prototype = {
+ handle: function nsSetDefault_handle(aCmdline) {
+ if (aCmdline.handleFlag("setDefaultBrowser", false)) {
+ ShellService.setDefaultBrowser(true, true);
+ }
+ },
+
+ helpInfo: " --setDefaultBrowser Set this app as the default browser.\n",
+
+ classID: Components.ID("{F57899D0-4E2C-4ac6-9E29-50C736103B0C}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSetDefaultBrowser]);
diff --git a/browser/components/shell/nsSetDefaultBrowser.manifest b/browser/components/shell/nsSetDefaultBrowser.manifest
new file mode 100644
index 000000000..bf3c0f04f
--- /dev/null
+++ b/browser/components/shell/nsSetDefaultBrowser.manifest
@@ -0,0 +1,3 @@
+component {F57899D0-4E2C-4ac6-9E29-50C736103B0C} nsSetDefaultBrowser.js
+contract @mozilla.org/browser/default-browser-clh;1 {F57899D0-4E2C-4ac6-9E29-50C736103B0C}
+category command-line-handler m-setdefaultbrowser @mozilla.org/browser/default-browser-clh;1
diff --git a/browser/components/shell/nsShellService.h b/browser/components/shell/nsShellService.h
new file mode 100644
index 000000000..516a8423a
--- /dev/null
+++ b/browser/components/shell/nsShellService.h
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#define PREF_CHECKDEFAULTBROWSER "browser.shell.checkDefaultBrowser"
+#define PREF_SKIPDEFAULTBROWSERCHECK "browser.shell.skipDefaultBrowserCheck"
+#define PREF_DEFAULTBROWSERCHECKCOUNT "browser.shell.defaultBrowserCheckCount"
+
+#define SHELLSERVICE_PROPERTIES "chrome://browser/locale/shellservice.properties"
+#define BRAND_PROPERTIES "chrome://branding/locale/brand.properties"
+
diff --git a/browser/components/shell/nsWindowsShellService.cpp b/browser/components/shell/nsWindowsShellService.cpp
new file mode 100644
index 000000000..416e00cbc
--- /dev/null
+++ b/browser/components/shell/nsWindowsShellService.cpp
@@ -0,0 +1,1280 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowsShellService.h"
+
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIOutputStream.h"
+#include "nsIPrefService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsShellService.h"
+#include "nsIProcess.h"
+#include "nsICategoryManager.h"
+#include "nsBrowserCompsCID.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIWindowsRegKey.h"
+#include "nsUnicharUtils.h"
+#include "nsIWinTaskbar.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURLFormatter.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "windows.h"
+#include "shellapi.h"
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+#define INITGUID
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WIN8
+// Needed for access to IApplicationActivationManager
+#include <shlobj.h>
+
+#include <mbstring.h>
+#include <shlwapi.h>
+
+#include <lm.h>
+#undef ACCESS_READ
+
+#ifndef MAX_BUF
+#define MAX_BUF 4096
+#endif
+
+#define REG_SUCCEEDED(val) \
+ (val == ERROR_SUCCESS)
+
+#define REG_FAILED(val) \
+ (val != ERROR_SUCCESS)
+
+#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
+
+using mozilla::IsWin8OrLater;
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIWindowsShellService, nsIShellService)
+
+static nsresult
+OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName, HKEY* aKey)
+{
+ const nsString &flatName = PromiseFlatString(aKeyName);
+
+ DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey);
+ switch (res) {
+ case ERROR_SUCCESS:
+ break;
+ case ERROR_ACCESS_DENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ case ERROR_FILE_NOT_FOUND:
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Default Browser Registry Settings
+//
+// The setting of these values are made by an external binary since writing
+// these values may require elevation.
+//
+// - File Extension Mappings
+// -----------------------
+// The following file extensions:
+// .htm .html .shtml .xht .xhtml
+// are mapped like so:
+//
+// HKCU\SOFTWARE\Classes\.<ext>\ (default) REG_SZ FirefoxHTML
+//
+// as aliases to the class:
+//
+// HKCU\SOFTWARE\Classes\FirefoxHTML\
+// DefaultIcon (default) REG_SZ <apppath>,1
+// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
+// shell\open\ddeexec (default) REG_SZ <empty string>
+//
+// - Windows Vista and above Protocol Handler
+//
+// HKCU\SOFTWARE\Classes\FirefoxURL\ (default) REG_SZ <appname> URL
+// EditFlags REG_DWORD 2
+// FriendlyTypeName REG_SZ <appname> URL
+// DefaultIcon (default) REG_SZ <apppath>,1
+// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
+// shell\open\ddeexec (default) REG_SZ <empty string>
+//
+// - Protocol Mappings
+// -----------------
+// The following protocols:
+// HTTP, HTTPS, FTP
+// are mapped like so:
+//
+// HKCU\SOFTWARE\Classes\<protocol>\
+// DefaultIcon (default) REG_SZ <apppath>,1
+// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
+// shell\open\ddeexec (default) REG_SZ <empty string>
+//
+// - Windows Start Menu (XP SP1 and newer)
+// -------------------------------------------------
+// The following keys are set to make Firefox appear in the Start Menu as the
+// browser:
+//
+// HKCU\SOFTWARE\Clients\StartMenuInternet\FIREFOX.EXE\
+// (default) REG_SZ <appname>
+// DefaultIcon (default) REG_SZ <apppath>,0
+// InstallInfo HideIconsCommand REG_SZ <uninstpath> /HideShortcuts
+// InstallInfo IconsVisible REG_DWORD 1
+// InstallInfo ReinstallCommand REG_SZ <uninstpath> /SetAsDefaultAppGlobal
+// InstallInfo ShowIconsCommand REG_SZ <uninstpath> /ShowShortcuts
+// shell\open\command (default) REG_SZ <apppath>
+// shell\properties (default) REG_SZ <appname> &Options
+// shell\properties\command (default) REG_SZ <apppath> -preferences
+// shell\safemode (default) REG_SZ <appname> &Safe Mode
+// shell\safemode\command (default) REG_SZ <apppath> -safe-mode
+//
+
+// The values checked are all default values so the value name is not needed.
+typedef struct {
+ const char* keyName;
+ const char* valueData;
+ const char* oldValueData;
+} SETTING;
+
+#define APP_REG_NAME L"Firefox"
+#define VAL_FILE_ICON "%APPPATH%,1"
+#define VAL_OPEN "\"%APPPATH%\" -osint -url \"%1\""
+#define OLD_VAL_OPEN "\"%APPPATH%\" -requestPending -osint -url \"%1\""
+#define DI "\\DefaultIcon"
+#define SOC "\\shell\\open\\command"
+#define SOD "\\shell\\open\\ddeexec"
+// Used for updating the FTP protocol handler's shell open command under HKCU.
+#define FTP_SOC L"Software\\Classes\\ftp\\shell\\open\\command"
+
+#define MAKE_KEY_NAME1(PREFIX, MID) \
+ PREFIX MID
+
+// The DefaultIcon registry key value should never be used when checking if
+// Firefox is the default browser for file handlers since other applications
+// (e.g. MS Office) may modify the DefaultIcon registry key value to add Icon
+// Handlers. see http://msdn2.microsoft.com/en-us/library/aa969357.aspx for
+// more info. The FTP protocol is not checked so advanced users can set the FTP
+// handler to another application and still have Firefox check if it is the
+// default HTTP and HTTPS handler.
+// *** Do not add additional checks here unless you skip them when aForAllTypes
+// is false below***.
+static SETTING gSettings[] = {
+ // File Handler Class
+ // ***keep this as the first entry because when aForAllTypes is not set below
+ // it will skip over this check.***
+ { MAKE_KEY_NAME1("FirefoxHTML", SOC), VAL_OPEN, OLD_VAL_OPEN },
+
+ // Protocol Handler Class - for Vista and above
+ { MAKE_KEY_NAME1("FirefoxURL", SOC), VAL_OPEN, OLD_VAL_OPEN },
+
+ // Protocol Handlers
+ { MAKE_KEY_NAME1("HTTP", DI), VAL_FILE_ICON },
+ { MAKE_KEY_NAME1("HTTP", SOC), VAL_OPEN, OLD_VAL_OPEN },
+ { MAKE_KEY_NAME1("HTTPS", DI), VAL_FILE_ICON },
+ { MAKE_KEY_NAME1("HTTPS", SOC), VAL_OPEN, OLD_VAL_OPEN }
+};
+
+// The settings to disable DDE are separate from the default browser settings
+// since they are only checked when Firefox is the default browser and if they
+// are incorrect they are fixed without notifying the user.
+static SETTING gDDESettings[] = {
+ // File Handler Class
+ { MAKE_KEY_NAME1("Software\\Classes\\FirefoxHTML", SOD) },
+
+ // Protocol Handler Class - for Vista and above
+ { MAKE_KEY_NAME1("Software\\Classes\\FirefoxURL", SOD) },
+
+ // Protocol Handlers
+ { MAKE_KEY_NAME1("Software\\Classes\\FTP", SOD) },
+ { MAKE_KEY_NAME1("Software\\Classes\\HTTP", SOD) },
+ { MAKE_KEY_NAME1("Software\\Classes\\HTTPS", SOD) }
+};
+
+nsresult
+GetHelperPath(nsAutoString& aPath)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProperties> directoryService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> appHelper;
+ rv = directoryService->Get(XRE_EXECUTABLE_FILE,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(appHelper));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->SetNativeLeafName(NS_LITERAL_CSTRING("uninstall"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->AppendNative(NS_LITERAL_CSTRING("helper.exe"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->GetPath(aPath);
+
+ aPath.Insert(L'"', 0);
+ aPath.Append(L'"');
+ return rv;
+}
+
+nsresult
+LaunchHelper(nsAutoString& aPath)
+{
+ STARTUPINFOW si = {sizeof(si), 0};
+ PROCESS_INFORMATION pi = {0};
+
+ if (!CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::ShortcutMaintenance()
+{
+ nsresult rv;
+
+ // XXX App ids were updated to a constant install path hash,
+ // XXX this code can be removed after a few upgrade cycles.
+
+ // Launch helper.exe so it can update the application user model ids on
+ // shortcuts in the user's taskbar and start menu. This keeps older pinned
+ // shortcuts grouped correctly after major updates. Note, we also do this
+ // through the upgrade installer script, however, this is the only place we
+ // have a chance to trap links created by users who do control the install/
+ // update process of the browser.
+
+ nsCOMPtr<nsIWinTaskbar> taskbarInfo =
+ do_GetService(NS_TASKBAR_CONTRACTID);
+ if (!taskbarInfo) // If we haven't built with win7 sdk features, this fails.
+ return NS_OK;
+
+ // Avoid if this isn't Win7+
+ bool isSupported = false;
+ taskbarInfo->GetAvailable(&isSupported);
+ if (!isSupported)
+ return NS_OK;
+
+ nsAutoString appId;
+ if (NS_FAILED(taskbarInfo->GetDefaultGroupId(appId)))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_NAMED_LITERAL_CSTRING(prefName, "browser.taskbar.lastgroupid");
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsISupportsString> prefString;
+ rv = prefs->GetComplexValue(prefName.get(),
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(prefString));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString version;
+ prefString->GetData(version);
+ if (!version.IsEmpty() && version.Equals(appId)) {
+ // We're all good, get out of here.
+ return NS_OK;
+ }
+ }
+ // Update the version in prefs
+ prefString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ prefString->SetData(appId);
+ rv = prefs->SetComplexValue(prefName.get(),
+ NS_GET_IID(nsISupportsString),
+ prefString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Couldn't set last user model id!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoString appHelperPath;
+ if (NS_FAILED(GetHelperPath(appHelperPath)))
+ return NS_ERROR_UNEXPECTED;
+
+ appHelperPath.AppendLiteral(" /UpdateShortcutAppUserModelIds");
+
+ return LaunchHelper(appHelperPath);
+}
+
+static bool
+IsAARDefault(const RefPtr<IApplicationAssociationRegistration>& pAAR,
+ LPCWSTR aClassName)
+{
+ // Make sure the Prog ID matches what we have
+ LPWSTR registeredApp;
+ bool isProtocol = *aClassName != L'.';
+ ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION;
+ HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE,
+ &registeredApp);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ LPCWSTR progID = isProtocol ? L"FirefoxURL" : L"FirefoxHTML";
+ bool isDefault = !wcsicmp(registeredApp, progID);
+ CoTaskMemFree(registeredApp);
+
+ return isDefault;
+}
+
+static void
+IsDefaultBrowserWin8(bool aCheckAllTypes, bool* aIsDefaultBrowser)
+{
+ RefPtr<IApplicationAssociationRegistration> pAAR;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
+ nullptr,
+ CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistration,
+ getter_AddRefs(pAAR));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ bool res = IsAARDefault(pAAR, L"http");
+ if (*aIsDefaultBrowser) {
+ *aIsDefaultBrowser = res;
+ }
+ res = IsAARDefault(pAAR, L".html");
+ if (*aIsDefaultBrowser && aCheckAllTypes) {
+ *aIsDefaultBrowser = res;
+ }
+}
+
+/*
+ * Query's the AAR for the default status.
+ * This only checks for FirefoxURL and if aCheckAllTypes is set, then
+ * it also checks for FirefoxHTML. Note that those ProgIDs are shared
+ * by all Firefox browsers.
+*/
+bool
+nsWindowsShellService::IsDefaultBrowserVista(bool aCheckAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ RefPtr<IApplicationAssociationRegistration> pAAR;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
+ nullptr,
+ CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistration,
+ getter_AddRefs(pAAR));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (aCheckAllTypes) {
+ BOOL res;
+ hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
+ APP_REG_NAME,
+ &res);
+ *aIsDefaultBrowser = res;
+ } else if (!IsWin8OrLater()) {
+ *aIsDefaultBrowser = IsAARDefault(pAAR, L"http");
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::IsDefaultBrowser(bool aStartupCheck,
+ bool aForAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ // Assume we're the default unless one of the several checks below tell us
+ // otherwise.
+ *aIsDefaultBrowser = true;
+
+ wchar_t exePath[MAX_BUF];
+ if (!::GetModuleFileNameW(0, exePath, MAX_BUF))
+ return NS_ERROR_FAILURE;
+
+ // Convert the path to a long path since GetModuleFileNameW returns the path
+ // that was used to launch Firefox which is not necessarily a long path.
+ if (!::GetLongPathNameW(exePath, exePath, MAX_BUF))
+ return NS_ERROR_FAILURE;
+
+ nsAutoString appLongPath(exePath);
+
+ HKEY theKey;
+ DWORD res;
+ nsresult rv;
+ wchar_t currValue[MAX_BUF];
+
+ SETTING* settings = gSettings;
+ if (!aForAllTypes && IsWin8OrLater()) {
+ // Skip over the file handler check
+ settings++;
+ }
+
+ SETTING* end = gSettings + sizeof(gSettings) / sizeof(SETTING);
+
+ for (; settings < end; ++settings) {
+ NS_ConvertUTF8toUTF16 keyName(settings->keyName);
+ NS_ConvertUTF8toUTF16 valueData(settings->valueData);
+ int32_t offset = valueData.Find("%APPPATH%");
+ valueData.Replace(offset, 9, appLongPath);
+
+ rv = OpenKeyForReading(HKEY_CLASSES_ROOT, keyName, &theKey);
+ if (NS_FAILED(rv)) {
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ ::ZeroMemory(currValue, sizeof(currValue));
+ DWORD len = sizeof currValue;
+ res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr,
+ (LPBYTE)currValue, &len);
+ // Close the key that was opened.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res) ||
+ _wcsicmp(valueData.get(), currValue)) {
+ // Key wasn't set or was set to something other than our registry entry.
+ NS_ConvertUTF8toUTF16 oldValueData(settings->oldValueData);
+ offset = oldValueData.Find("%APPPATH%");
+ oldValueData.Replace(offset, 9, appLongPath);
+ // The current registry value doesn't match the current or the old format.
+ if (_wcsicmp(oldValueData.get(), currValue)) {
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ res = ::RegOpenKeyExW(HKEY_CLASSES_ROOT, keyName.get(),
+ 0, KEY_SET_VALUE, &theKey);
+ if (REG_FAILED(res)) {
+ // If updating the open command fails try to update it using the helper
+ // application when setting Firefox as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ res = ::RegSetValueExW(theKey, L"", 0, REG_SZ,
+ (const BYTE *) valueData.get(),
+ (valueData.Length() + 1) * sizeof(char16_t));
+ // Close the key that was created.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res)) {
+ // If updating the open command fails try to update it using the helper
+ // application when setting Firefox as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+ }
+ }
+
+ // Only check if Firefox is the default browser on Vista and above if the
+ // previous checks show that Firefox is the default browser.
+ if (*aIsDefaultBrowser) {
+ IsDefaultBrowserVista(aForAllTypes, aIsDefaultBrowser);
+ if (IsWin8OrLater()) {
+ IsDefaultBrowserWin8(aForAllTypes, aIsDefaultBrowser);
+ }
+ }
+
+ // To handle the case where DDE isn't disabled due for a user because there
+ // account didn't perform a Firefox update this will check if Firefox is the
+ // default browser and if dde is disabled for each handler
+ // and if it isn't disable it. When Firefox is not the default browser the
+ // helper application will disable dde for each handler.
+ if (*aIsDefaultBrowser && aForAllTypes) {
+ // Check ftp settings
+
+ end = gDDESettings + sizeof(gDDESettings) / sizeof(SETTING);
+
+ for (settings = gDDESettings; settings < end; ++settings) {
+ NS_ConvertUTF8toUTF16 keyName(settings->keyName);
+
+ rv = OpenKeyForReading(HKEY_CURRENT_USER, keyName, &theKey);
+ if (NS_FAILED(rv)) {
+ ::RegCloseKey(theKey);
+ // If disabling DDE fails try to disable it using the helper
+ // application when setting Firefox as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ ::ZeroMemory(currValue, sizeof(currValue));
+ DWORD len = sizeof currValue;
+ res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr,
+ (LPBYTE)currValue, &len);
+ // Close the key that was opened.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res) || char16_t('\0') != *currValue) {
+ // Key wasn't set or was set to something other than our registry entry.
+ // Delete the key along with all of its childrean and then recreate it.
+ ::SHDeleteKeyW(HKEY_CURRENT_USER, keyName.get());
+ res = ::RegCreateKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, nullptr,
+ REG_OPTION_NON_VOLATILE, KEY_SET_VALUE,
+ nullptr, &theKey, nullptr);
+ if (REG_FAILED(res)) {
+ // If disabling DDE fails try to disable it using the helper
+ // application when setting Firefox as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ res = ::RegSetValueExW(theKey, L"", 0, REG_SZ, (const BYTE *) L"",
+ sizeof(char16_t));
+ // Close the key that was created.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res)) {
+ // If disabling DDE fails try to disable it using the helper
+ // application when setting Firefox as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+ }
+ }
+
+ // Update the FTP protocol handler's shell open command if it is the old
+ // format.
+ res = ::RegOpenKeyExW(HKEY_CURRENT_USER, FTP_SOC, 0, KEY_ALL_ACCESS,
+ &theKey);
+ // Don't update the FTP protocol handler's shell open command when opening
+ // its registry key fails under HKCU since it most likely doesn't exist.
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF8toUTF16 oldValueOpen(OLD_VAL_OPEN);
+ int32_t offset = oldValueOpen.Find("%APPPATH%");
+ oldValueOpen.Replace(offset, 9, appLongPath);
+
+ ::ZeroMemory(currValue, sizeof(currValue));
+ DWORD len = sizeof currValue;
+ res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr, (LPBYTE)currValue,
+ &len);
+
+ // Don't update the FTP protocol handler's shell open command when the
+ // current registry value doesn't exist or matches the old format.
+ if (REG_FAILED(res) ||
+ _wcsicmp(oldValueOpen.get(), currValue)) {
+ ::RegCloseKey(theKey);
+ return NS_OK;
+ }
+
+ NS_ConvertUTF8toUTF16 valueData(VAL_OPEN);
+ valueData.Replace(offset, 9, appLongPath);
+ res = ::RegSetValueExW(theKey, L"", 0, REG_SZ,
+ (const BYTE *) valueData.get(),
+ (valueData.Length() + 1) * sizeof(char16_t));
+ // Close the key that was created.
+ ::RegCloseKey(theKey);
+ // If updating the FTP protocol handlers shell open command fails try to
+ // update it using the helper application when setting Firefox as the
+ // default browser.
+ if (REG_FAILED(res)) {
+ *aIsDefaultBrowser = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+DynSHOpenWithDialog(HWND hwndParent, const OPENASINFO *poainfo)
+{
+ // shell32.dll is in the knownDLLs list so will always be loaded from the
+ // system32 directory.
+ static const wchar_t kSehllLibraryName[] = L"shell32.dll";
+ HMODULE shellDLL = ::LoadLibraryW(kSehllLibraryName);
+ if (!shellDLL) {
+ return NS_ERROR_FAILURE;
+ }
+
+ decltype(SHOpenWithDialog)* SHOpenWithDialogFn =
+ (decltype(SHOpenWithDialog)*) GetProcAddress(shellDLL, "SHOpenWithDialog");
+
+ if (!SHOpenWithDialogFn) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ HRESULT hr = SHOpenWithDialogFn(hwndParent, poainfo);
+ if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ FreeLibrary(shellDLL);
+ return rv;
+}
+
+nsresult
+nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI()
+{
+ IApplicationAssociationRegistrationUI* pAARUI;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistrationUI,
+ NULL,
+ CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistrationUI,
+ (void**)&pAARUI);
+ if (SUCCEEDED(hr)) {
+ hr = pAARUI->LaunchAdvancedAssociationUI(APP_REG_NAME);
+ pAARUI->Release();
+ }
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsWindowsShellService::LaunchControlPanelDefaultPrograms()
+{
+ // This Default Programs feature is Win7+ only.
+ if (!IsWin7OrLater()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Build the path control.exe path safely
+ WCHAR controlEXEPath[MAX_PATH + 1] = { '\0' };
+ if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) {
+ return NS_ERROR_FAILURE;
+ }
+ LPCWSTR controlEXE = L"control.exe";
+ if (wcslen(controlEXEPath) + wcslen(controlEXE) >= MAX_PATH) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!PathAppendW(controlEXEPath, controlEXE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WCHAR params[] = L"control.exe /name Microsoft.DefaultPrograms /page "
+ "pageDefaultProgram\\pageAdvancedSettings?pszAppName=" APP_REG_NAME;
+ STARTUPINFOW si = {sizeof(si), 0};
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWDEFAULT;
+ PROCESS_INFORMATION pi = {0};
+ if (!CreateProcessW(controlEXEPath, params, nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ return NS_ERROR_FAILURE;
+ }
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ return NS_OK;
+}
+
+static bool
+IsWindowsLogonConnected()
+{
+ WCHAR userName[UNLEN + 1];
+ DWORD size = ArrayLength(userName);
+ if (!GetUserNameW(userName, &size)) {
+ return false;
+ }
+
+ LPUSER_INFO_24 info;
+ if (NetUserGetInfo(nullptr, userName, 24, (LPBYTE *)&info)
+ != NERR_Success) {
+ return false;
+ }
+ bool connected = info->usri24_internet_identity;
+ NetApiBufferFree(info);
+
+ return connected;
+}
+
+static bool
+SettingsAppBelievesConnected()
+{
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("SOFTWARE\\Microsoft\\Windows\\Shell\\Associations"),
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ uint32_t value;
+ rv = regKey->ReadIntValue(NS_LITERAL_STRING("IsConnectedAtLogon"), &value);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return !!value;
+}
+
+nsresult
+nsWindowsShellService::LaunchModernSettingsDialogDefaultApps()
+{
+ if (!IsWindowsBuildOrLater(14965) &&
+ !IsWindowsLogonConnected() && SettingsAppBelievesConnected()) {
+ // Use the classic Control Panel to work around a bug of older
+ // builds of Windows 10.
+ return LaunchControlPanelDefaultPrograms();
+ }
+
+ IApplicationActivationManager* pActivator;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationActivationManager,
+ nullptr,
+ CLSCTX_INPROC,
+ IID_IApplicationActivationManager,
+ (void**)&pActivator);
+
+ if (SUCCEEDED(hr)) {
+ DWORD pid;
+ hr = pActivator->ActivateApplication(
+ L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
+ if (SUCCEEDED(hr)) {
+ // Do not check error because we could at least open
+ // the "Default apps" setting.
+ pActivator->ActivateApplication(
+ L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults"
+ L"&target=SystemSettings_DefaultApps_Browser", AO_NONE, &pid);
+ }
+ pActivator->Release();
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsWindowsShellService::InvokeHTTPOpenAsVerb()
+{
+ nsCOMPtr<nsIURLFormatter> formatter(
+ do_GetService("@mozilla.org/toolkit/URLFormatterService;1"));
+ if (!formatter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsString urlStr;
+ nsresult rv = formatter->FormatURLPref(
+ NS_LITERAL_STRING("app.support.baseURL"), urlStr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!StringBeginsWith(urlStr, NS_LITERAL_STRING("https://"))) {
+ return NS_ERROR_FAILURE;
+ }
+ urlStr.AppendLiteral("win10-default-browser");
+
+ SHELLEXECUTEINFOW seinfo = { sizeof(SHELLEXECUTEINFOW) };
+ seinfo.lpVerb = L"openas";
+ seinfo.lpFile = urlStr.get();
+ seinfo.nShow = SW_SHOWNORMAL;
+ if (!ShellExecuteExW(&seinfo)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsWindowsShellService::LaunchHTTPHandlerPane()
+{
+ OPENASINFO info;
+ info.pcszFile = L"http";
+ info.pcszClass = nullptr;
+ info.oaifInFlags = OAIF_FORCE_REGISTRATION |
+ OAIF_URL_PROTOCOL |
+ OAIF_REGISTER_EXT;
+ return DynSHOpenWithDialog(nullptr, &info);
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
+{
+ nsAutoString appHelperPath;
+ if (NS_FAILED(GetHelperPath(appHelperPath)))
+ return NS_ERROR_FAILURE;
+
+ if (aForAllUsers) {
+ appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal");
+ } else {
+ appHelperPath.AppendLiteral(" /SetAsDefaultAppUser");
+ }
+
+ nsresult rv = LaunchHelper(appHelperPath);
+ if (NS_SUCCEEDED(rv) && IsWin8OrLater()) {
+ if (aClaimAllTypes) {
+ if (IsWin10OrLater()) {
+ rv = LaunchModernSettingsDialogDefaultApps();
+ } else {
+ rv = LaunchControlPanelDefaultsSelectionUI();
+ }
+ // The above call should never really fail, but just in case
+ // fall back to showing the HTTP association screen only.
+ if (NS_FAILED(rv)) {
+ if (IsWin10OrLater()) {
+ rv = InvokeHTTPOpenAsVerb();
+ } else {
+ rv = LaunchHTTPHandlerPane();
+ }
+ }
+ } else {
+ // Windows 10 blocks attempts to load the
+ // HTTP Handler association dialog.
+ if (IsWin10OrLater()) {
+ rv = LaunchModernSettingsDialogDefaultApps();
+ } else {
+ rv = LaunchHTTPHandlerPane();
+ }
+
+ // The above call should never really fail, but just in case
+ // fall back to showing control panel for all defaults
+ if (NS_FAILED(rv)) {
+ rv = LaunchControlPanelDefaultsSelectionUI();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return rv;
+}
+
+static nsresult
+WriteBitmap(nsIFile* aFile, imgIContainer* aImage)
+{
+ nsresult rv;
+
+ RefPtr<SourceSurface> surface =
+ aImage->GetFrame(imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ // For either of the following formats we want to set the biBitCount member
+ // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap
+ // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored
+ // for the BI_RGB value we use for the biCompression member.
+ MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == SurfaceFormat::B8G8R8X8);
+
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ int32_t bytesPerPixel = 4 * sizeof(uint8_t);
+ uint32_t bytesPerRow = bytesPerPixel * width;
+
+ // initialize these bitmap structs which we will later
+ // serialize directly to the head of the bitmap file
+ BITMAPINFOHEADER bmi;
+ bmi.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.biWidth = width;
+ bmi.biHeight = height;
+ bmi.biPlanes = 1;
+ bmi.biBitCount = (WORD)bytesPerPixel*8;
+ bmi.biCompression = BI_RGB;
+ bmi.biSizeImage = bytesPerRow * height;
+ bmi.biXPelsPerMeter = 0;
+ bmi.biYPelsPerMeter = 0;
+ bmi.biClrUsed = 0;
+ bmi.biClrImportant = 0;
+
+ BITMAPFILEHEADER bf;
+ bf.bfType = 0x4D42; // 'BM'
+ bf.bfReserved1 = 0;
+ bf.bfReserved2 = 0;
+ bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
+ bf.bfSize = bf.bfOffBits + bmi.biSizeImage;
+
+ // get a file output stream
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // write the bitmap headers and rgb pixel data to the file
+ rv = NS_ERROR_FAILURE;
+ if (stream) {
+ uint32_t written;
+ stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written);
+ if (written == sizeof(BITMAPFILEHEADER)) {
+ stream->Write((const char*)&bmi, sizeof(BITMAPINFOHEADER), &written);
+ if (written == sizeof(BITMAPINFOHEADER)) {
+ // write out the image data backwards because the desktop won't
+ // show bitmaps with negative heights for top-to-bottom
+ uint32_t i = map.mStride * height;
+ do {
+ i -= map.mStride;
+ stream->Write(((const char*)map.mData) + i, bytesPerRow, &written);
+ if (written == bytesPerRow) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ } while (i != 0);
+ }
+ }
+
+ stream->Close();
+ }
+
+ dataSurface->Unmap();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDesktopBackground(nsIDOMElement* aElement,
+ int32_t aPosition)
+{
+ nsresult rv;
+
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<nsIDOMHTMLImageElement> imgElement(do_QueryInterface(aElement));
+ if (!imgElement) {
+ // XXX write background loading stuff!
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ else {
+ nsCOMPtr<nsIImageLoadingContent> imageContent =
+ do_QueryInterface(aElement, &rv);
+ if (!imageContent)
+ return rv;
+
+ // get the image container
+ nsCOMPtr<imgIRequest> request;
+ rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ if (!request)
+ return rv;
+ rv = request->GetImage(getter_AddRefs(container));
+ if (!container)
+ return NS_ERROR_FAILURE;
+ }
+
+ // get the file name from localized strings
+ nsCOMPtr<nsIStringBundleService>
+ bundleService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> shellBundle;
+ rv = bundleService->CreateBundle(SHELLSERVICE_PROPERTIES,
+ getter_AddRefs(shellBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // e.g. "Desktop Background.bmp"
+ nsString fileLeafName;
+ rv = shellBundle->GetStringFromName
+ (u"desktopBackgroundLeafNameWin",
+ getter_Copies(fileLeafName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the profile root directory
+ nsCOMPtr<nsIFile> file;
+ rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR,
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // eventually, the path is "%APPDATA%\Mozilla\Firefox\Desktop Background.bmp"
+ rv = file->Append(fileLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ rv = file->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the bitmap to a file in the profile directory
+ rv = WriteBitmap(file, container);
+
+ // if the file was written successfully, set it as the system wallpaper
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Control Panel\\Desktop"),
+ nsIWindowsRegKey::ACCESS_SET_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString tile;
+ nsAutoString style;
+ switch (aPosition) {
+ case BACKGROUND_TILE:
+ style.Assign('0');
+ tile.Assign('1');
+ break;
+ case BACKGROUND_CENTER:
+ style.Assign('0');
+ tile.Assign('0');
+ break;
+ case BACKGROUND_STRETCH:
+ style.Assign('2');
+ tile.Assign('0');
+ break;
+ case BACKGROUND_FILL:
+ style.AssignLiteral("10");
+ tile.Assign('0');
+ break;
+ case BACKGROUND_FIT:
+ style.Assign('6');
+ tile.Assign('0');
+ break;
+ }
+
+ rv = regKey->WriteStringValue(NS_LITERAL_STRING("TileWallpaper"), tile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = regKey->WriteStringValue(NS_LITERAL_STRING("WallpaperStyle"), style);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = regKey->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (PVOID)path.get(),
+ SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::OpenApplication(int32_t aApplication)
+{
+ nsAutoString application;
+ switch (aApplication) {
+ case nsIShellService::APPLICATION_MAIL:
+ application.AssignLiteral("Mail");
+ break;
+ case nsIShellService::APPLICATION_NEWS:
+ application.AssignLiteral("News");
+ break;
+ }
+
+ // The Default Client section of the Windows Registry looks like this:
+ //
+ // Clients\aClient\
+ // e.g. aClient = "Mail"...
+ // \Mail\(default) = Client Subkey Name
+ // \Client Subkey Name
+ // \Client Subkey Name\shell\open\command\
+ // \Client Subkey Name\shell\open\command\(default) = path to exe
+ //
+
+ // Find the default application for this class.
+ HKEY theKey;
+ nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, application, &theKey);
+ if (NS_FAILED(rv))
+ return rv;
+
+ wchar_t buf[MAX_BUF];
+ DWORD type, len = sizeof buf;
+ DWORD res = ::RegQueryValueExW(theKey, EmptyString().get(), 0,
+ &type, (LPBYTE)&buf, &len);
+
+ if (REG_FAILED(res) || !*buf)
+ return NS_OK;
+
+ // Close the key we opened.
+ ::RegCloseKey(theKey);
+
+ // Find the "open" command
+ application.Append('\\');
+ application.Append(buf);
+ application.AppendLiteral("\\shell\\open\\command");
+
+ rv = OpenKeyForReading(HKEY_CLASSES_ROOT, application, &theKey);
+ if (NS_FAILED(rv))
+ return rv;
+
+ ::ZeroMemory(buf, sizeof(buf));
+ len = sizeof buf;
+ res = ::RegQueryValueExW(theKey, EmptyString().get(), 0,
+ &type, (LPBYTE)&buf, &len);
+ if (REG_FAILED(res) || !*buf)
+ return NS_ERROR_FAILURE;
+
+ // Close the key we opened.
+ ::RegCloseKey(theKey);
+
+ // Look for any embedded environment variables and substitute their
+ // values, as |::CreateProcessW| is unable to do this.
+ nsAutoString path(buf);
+ int32_t end = path.Length();
+ int32_t cursor = 0, temp = 0;
+ ::ZeroMemory(buf, sizeof(buf));
+ do {
+ cursor = path.FindChar('%', cursor);
+ if (cursor < 0)
+ break;
+
+ temp = path.FindChar('%', cursor + 1);
+ ++cursor;
+
+ ::ZeroMemory(&buf, sizeof(buf));
+
+ ::GetEnvironmentVariableW(nsAutoString(Substring(path, cursor, temp - cursor)).get(),
+ buf, sizeof(buf));
+
+ // "+ 2" is to subtract the extra characters used to delimit the environment
+ // variable ('%').
+ path.Replace((cursor - 1), temp - cursor + 2, nsDependentString(buf));
+
+ ++cursor;
+ }
+ while (cursor < end);
+
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+
+ ::ZeroMemory(&si, sizeof(STARTUPINFOW));
+ ::ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
+
+ BOOL success = ::CreateProcessW(nullptr, (LPWSTR)path.get(), nullptr,
+ nullptr, FALSE, 0, nullptr, nullptr,
+ &si, &pi);
+ if (!success)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::GetDesktopBackgroundColor(uint32_t* aColor)
+{
+ uint32_t color = ::GetSysColor(COLOR_DESKTOP);
+ *aColor = (GetRValue(color) << 16) | (GetGValue(color) << 8) | GetBValue(color);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDesktopBackgroundColor(uint32_t aColor)
+{
+ int aParameters[2] = { COLOR_BACKGROUND, COLOR_DESKTOP };
+ BYTE r = (aColor >> 16);
+ BYTE g = (aColor << 16) >> 24;
+ BYTE b = (aColor << 24) >> 24;
+ COLORREF colors[2] = { RGB(r,g,b), RGB(r,g,b) };
+
+ ::SetSysColors(sizeof(aParameters) / sizeof(int), aParameters, colors);
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Control Panel\\Colors"),
+ nsIWindowsRegKey::ACCESS_SET_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ wchar_t rgb[12];
+ _snwprintf(rgb, 12, L"%u %u %u", r, g, b);
+
+ rv = regKey->WriteStringValue(NS_LITERAL_STRING("Background"),
+ nsDependentString(rgb));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return regKey->Close();
+}
+
+nsWindowsShellService::nsWindowsShellService()
+{
+}
+
+nsWindowsShellService::~nsWindowsShellService()
+{
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::OpenApplicationWithURI(nsIFile* aApplication,
+ const nsACString& aURI)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process =
+ do_CreateInstance("@mozilla.org/process/util;1", &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = process->Init(aApplication);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const nsCString spec(aURI);
+ const char* specStr = spec.get();
+ return process->Run(false, &specStr, 1);
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::GetDefaultFeedReader(nsIFile** _retval)
+{
+ *_retval = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ NS_LITERAL_STRING("feed\\shell\\open\\command"),
+ nsIWindowsRegKey::ACCESS_READ);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ rv = regKey->ReadStringValue(EmptyString(), path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (path.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ if (path.First() == '"') {
+ // Everything inside the quotes
+ path = Substring(path, 1, path.FindChar('"', 1) - 1);
+ }
+ else {
+ // Everything up to the first space
+ path = Substring(path, 0, path.FindChar(' '));
+ }
+
+ nsCOMPtr<nsIFile> defaultReader =
+ do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = defaultReader->InitWithPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = defaultReader->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*_retval = defaultReader);
+ return NS_OK;
+}
diff --git a/browser/components/shell/nsWindowsShellService.h b/browser/components/shell/nsWindowsShellService.h
new file mode 100644
index 000000000..b9c473a15
--- /dev/null
+++ b/browser/components/shell/nsWindowsShellService.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nswindowsshellservice_h____
+#define nswindowsshellservice_h____
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsIWindowsShellService.h"
+#include "nsITimer.h"
+
+#include <windows.h>
+#include <ole2.h>
+
+class nsWindowsShellService : public nsIWindowsShellService
+{
+ virtual ~nsWindowsShellService();
+
+public:
+ nsWindowsShellService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIWINDOWSSHELLSERVICE
+
+protected:
+ bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser);
+ nsresult LaunchControlPanelDefaultsSelectionUI();
+ nsresult LaunchControlPanelDefaultPrograms();
+ nsresult LaunchModernSettingsDialogDefaultApps();
+ nsresult InvokeHTTPOpenAsVerb();
+ nsresult LaunchHTTPHandlerPane();
+};
+
+#endif // nswindowsshellservice_h____
diff --git a/browser/components/shell/test/.eslintrc.js b/browser/components/shell/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/shell/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/shell/test/browser.ini b/browser/components/shell/test/browser.ini
new file mode 100644
index 000000000..8f18415c0
--- /dev/null
+++ b/browser/components/shell/test/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+skip-if = os != "linux"
+
+[browser_420786.js]
+[browser_633221.js]
+
diff --git a/browser/components/shell/test/browser_420786.js b/browser/components/shell/test/browser_420786.js
new file mode 100644
index 000000000..ae4521890
--- /dev/null
+++ b/browser/components/shell/test/browser_420786.js
@@ -0,0 +1,88 @@
+const DG_BACKGROUND = "/desktop/gnome/background"
+const DG_IMAGE_KEY = DG_BACKGROUND + "/picture_filename";
+const DG_OPTION_KEY = DG_BACKGROUND + "/picture_options";
+const DG_DRAW_BG_KEY = DG_BACKGROUND + "/draw_background";
+
+function onPageLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", onPageLoad, true);
+
+ var bs = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ var brandName = bs.createBundle("chrome://branding/locale/brand.properties").
+ GetStringFromName("brandShortName");
+
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIDirectoryServiceProvider);
+ var homeDir = dirSvc.getFile("Home", {});
+
+ var wpFile = homeDir.clone();
+ wpFile.append(brandName + "_wallpaper.png");
+
+ // Backup the existing wallpaper so that this test doesn't change the user's
+ // settings.
+ var wpFileBackup = homeDir.clone()
+ wpFileBackup.append(brandName + "_wallpaper.png.backup");
+
+ if (wpFileBackup.exists())
+ wpFileBackup.remove(false);
+
+ if (wpFile.exists())
+ wpFile.copyTo(null, wpFileBackup.leafName);
+
+ var shell = Cc["@mozilla.org/browser/shell-service;1"].
+ getService(Ci.nsIShellService);
+ var gconf = Cc["@mozilla.org/gnome-gconf-service;1"].
+ getService(Ci.nsIGConfService);
+
+ var prevImageKey = gconf.getString(DG_IMAGE_KEY);
+ var prevOptionKey = gconf.getString(DG_OPTION_KEY);
+ var prevDrawBgKey = gconf.getBool(DG_DRAW_BG_KEY);
+
+ var image = content.document.images[0];
+
+ function checkWallpaper(position, expectedGConfPosition) {
+ shell.setDesktopBackground(image, position);
+ ok(wpFile.exists(), "Wallpaper was written to disk");
+ is(gconf.getString(DG_IMAGE_KEY), wpFile.path,
+ "Wallpaper file GConf key is correct");
+ is(gconf.getString(DG_OPTION_KEY), expectedGConfPosition,
+ "Wallpaper position GConf key is correct");
+ }
+
+ checkWallpaper(Ci.nsIShellService.BACKGROUND_TILE, "wallpaper");
+ checkWallpaper(Ci.nsIShellService.BACKGROUND_STRETCH, "stretched");
+ checkWallpaper(Ci.nsIShellService.BACKGROUND_CENTER, "centered");
+ checkWallpaper(Ci.nsIShellService.BACKGROUND_FILL, "zoom");
+ checkWallpaper(Ci.nsIShellService.BACKGROUND_FIT, "scaled");
+
+ // Restore GConf and wallpaper
+
+ gconf.setString(DG_IMAGE_KEY, prevImageKey);
+ gconf.setString(DG_OPTION_KEY, prevOptionKey);
+ gconf.setBool(DG_DRAW_BG_KEY, prevDrawBgKey);
+
+ wpFile.remove(false);
+ if (wpFileBackup.exists())
+ wpFileBackup.moveTo(null, wpFile.leafName);
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test() {
+ try {
+ // If GSettings is available, then the GConf tests
+ // will fail
+ Cc["@mozilla.org/gsettings-service;1"].
+ getService(Ci.nsIGSettingsService).
+ getCollectionForSchema("org.gnome.desktop.background");
+ todo(false, "This test doesn't work when GSettings is available");
+ return;
+ } catch (e) { }
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true);
+ content.location = "about:logo";
+
+ waitForExplicitFinish();
+}
diff --git a/browser/components/shell/test/browser_633221.js b/browser/components/shell/test/browser_633221.js
new file mode 100644
index 000000000..7929e8098
--- /dev/null
+++ b/browser/components/shell/test/browser_633221.js
@@ -0,0 +1,7 @@
+Components.utils.import("resource:///modules/ShellService.jsm");
+
+function test() {
+ ShellService.setDefaultBrowser(true, false);
+ ok(ShellService.isDefaultBrowser(true, false), "we got here and are the default browser");
+ ok(ShellService.isDefaultBrowser(true, true), "we got here and are the default browser");
+}
diff --git a/browser/components/shell/test/unit/.eslintrc.js b/browser/components/shell/test/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/shell/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/shell/test/unit/test_421977.js b/browser/components/shell/test/unit/test_421977.js
new file mode 100644
index 000000000..637db4b91
--- /dev/null
+++ b/browser/components/shell/test/unit/test_421977.js
@@ -0,0 +1,123 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+const GCONF_BG_COLOR_KEY = "/desktop/gnome/background/primary_color";
+
+var gShell;
+var gGConf;
+
+/**
+ * Converts from a rgb numerical color valule (r << 16 | g << 8 | b)
+ * into a hex string in #RRGGBB format.
+ */
+function colorToHex(aColor) {
+ const rMask = 4294901760;
+ const gMask = 65280;
+ const bMask = 255;
+
+ var r = (aColor & rMask) >> 16;
+ var g = (aColor & gMask) >> 8;
+ var b = (aColor & bMask);
+
+ return "#" + [r, g, b].map(aInt =>
+ aInt.toString(16).replace(/^(.)$/, "0$1"))
+ .join("").toUpperCase();
+}
+
+/**
+ * Converts a color string in #RRGGBB format to a rgb numerical color value
+ * (r << 16 | g << 8 | b).
+ */
+function hexToColor(aString) {
+ return parseInt(aString.substring(1, 3), 16) << 16 |
+ parseInt(aString.substring(3, 5), 16) << 8 |
+ parseInt(aString.substring(5, 7), 16);
+}
+
+/**
+ * Checks that setting the GConf background key to aGConfColor will
+ * result in the Shell component returning a background color equals
+ * to aExpectedShellColor in #RRGGBB format.
+ */
+function checkGConfToShellColor(aGConfColor, aExpectedShellColor) {
+
+ gGConf.setString(GCONF_BG_COLOR_KEY, aGConfColor);
+ var shellColor = colorToHex(gShell.desktopBackgroundColor);
+
+ do_check_eq(shellColor, aExpectedShellColor);
+}
+
+/**
+ * Checks that setting the background color (in #RRGGBB format) using the Shell
+ * component will result in having a GConf key for the background color set to
+ * aExpectedGConfColor.
+ */
+function checkShellToGConfColor(aShellColor, aExpectedGConfColor) {
+
+ gShell.desktopBackgroundColor = hexToColor(aShellColor);
+ var gconfColor = gGConf.getString(GCONF_BG_COLOR_KEY);
+
+ do_check_eq(gconfColor, aExpectedGConfColor);
+}
+
+function run_test() {
+
+ // This test is Linux specific for now
+ if (!("@mozilla.org/gnome-gconf-service;1" in Cc))
+ return;
+
+ try {
+ // If GSettings is available, then the GConf tests
+ // will fail
+ Cc["@mozilla.org/gsettings-service;1"].
+ getService(Ci.nsIGSettingsService).
+ getCollectionForSchema("org.gnome.desktop.background");
+ return;
+ } catch (e) { }
+
+ gGConf = Cc["@mozilla.org/gnome-gconf-service;1"].
+ getService(Ci.nsIGConfService);
+
+ gShell = Cc["@mozilla.org/browser/shell-service;1"].
+ getService(Ci.nsIShellService);
+
+ // Save the original background color so that we can restore it
+ // after the test.
+ var origGConfColor = gGConf.getString(GCONF_BG_COLOR_KEY);
+
+ try {
+
+ checkGConfToShellColor("#000", "#000000");
+ checkGConfToShellColor("#00f", "#0000FF");
+ checkGConfToShellColor("#b2f", "#BB22FF");
+ checkGConfToShellColor("#fff", "#FFFFFF");
+
+ checkGConfToShellColor("#000000", "#000000");
+ checkGConfToShellColor("#0000ff", "#0000FF");
+ checkGConfToShellColor("#b002f0", "#B002F0");
+ checkGConfToShellColor("#ffffff", "#FFFFFF");
+
+ checkGConfToShellColor("#000000000", "#000000");
+ checkGConfToShellColor("#00f00f00f", "#000000");
+ checkGConfToShellColor("#aaabbbccc", "#AABBCC");
+ checkGConfToShellColor("#fffffffff", "#FFFFFF");
+
+ checkGConfToShellColor("#000000000000", "#000000");
+ checkGConfToShellColor("#000f000f000f", "#000000");
+ checkGConfToShellColor("#00ff00ff00ff", "#000000");
+ checkGConfToShellColor("#aaaabbbbcccc", "#AABBCC");
+ checkGConfToShellColor("#111122223333", "#112233");
+ checkGConfToShellColor("#ffffffffffff", "#FFFFFF");
+
+ checkShellToGConfColor("#000000", "#000000000000");
+ checkShellToGConfColor("#0000FF", "#00000000ffff");
+ checkShellToGConfColor("#FFFFFF", "#ffffffffffff");
+ checkShellToGConfColor("#0A0B0C", "#0a0a0b0b0c0c");
+ checkShellToGConfColor("#A0B0C0", "#a0a0b0b0c0c0");
+ checkShellToGConfColor("#AABBCC", "#aaaabbbbcccc");
+
+ } finally {
+ gGConf.setString(GCONF_BG_COLOR_KEY, origGConfColor);
+ }
+}
diff --git a/browser/components/shell/test/unit/xpcshell.ini b/browser/components/shell/test/unit/xpcshell.ini
new file mode 100644
index 000000000..be00037e0
--- /dev/null
+++ b/browser/components/shell/test/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head =
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_421977.js]
diff --git a/browser/components/syncedtabs/EventEmitter.jsm b/browser/components/syncedtabs/EventEmitter.jsm
new file mode 100644
index 000000000..ec3225f0f
--- /dev/null
+++ b/browser/components/syncedtabs/EventEmitter.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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = [
+ "EventEmitter"
+];
+
+// Simple event emitter abstraction for storage objects to use.
+function EventEmitter () {
+ this._events = new Map();
+}
+
+EventEmitter.prototype = {
+ on(event, listener) {
+ if (this._events.has(event)) {
+ this._events.get(event).add(listener);
+ } else {
+ this._events.set(event, new Set([listener]));
+ }
+ },
+ off(event, listener) {
+ if (!this._events.has(event)) {
+ return;
+ }
+ this._events.get(event).delete(listener);
+ },
+ emit(event, ...args) {
+ if (!this._events.has(event)) {
+ return;
+ }
+ for (let listener of this._events.get(event).values()) {
+ try {
+ listener.apply(this, args);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+};
+
diff --git a/browser/components/syncedtabs/SyncedTabsDeckComponent.js b/browser/components/syncedtabs/SyncedTabsDeckComponent.js
new file mode 100644
index 000000000..c35277795
--- /dev/null
+++ b/browser/components/syncedtabs/SyncedTabsDeckComponent.js
@@ -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/. */
+
+"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:///modules/syncedtabs/SyncedTabsDeckStore.js");
+Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckView.js");
+Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js");
+Cu.import("resource:///modules/syncedtabs/TabListComponent.js");
+Cu.import("resource:///modules/syncedtabs/TabListView.js");
+let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
+
+XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
+ return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+let log = Cu.import("resource://gre/modules/Log.jsm", {})
+ .Log.repository.getLogger("Sync.RemoteTabs");
+
+this.EXPORTED_SYMBOLS = [
+ "SyncedTabsDeckComponent"
+];
+
+/* SyncedTabsDeckComponent
+ * This component instantiates views and storage objects as well as defines
+ * behaviors that will be passed down to the views. This helps keep the views
+ * isolated and easier to test.
+ */
+
+function SyncedTabsDeckComponent({
+ window, SyncedTabs, fxAccounts, deckStore, listStore, listComponent, DeckView, getChromeWindowMock,
+}) {
+ this._window = window;
+ this._SyncedTabs = SyncedTabs;
+ this._fxAccounts = fxAccounts;
+ this._DeckView = DeckView || SyncedTabsDeckView;
+ // used to stub during tests
+ this._getChromeWindow = getChromeWindowMock || getChromeWindow;
+
+ this._deckStore = deckStore || new SyncedTabsDeckStore();
+ this._syncedTabsListStore = listStore || new SyncedTabsListStore(SyncedTabs);
+ this.tabListComponent = listComponent || new TabListComponent({
+ window: this._window,
+ store: this._syncedTabsListStore,
+ View: TabListView,
+ SyncedTabs: SyncedTabs,
+ clipboardHelper: Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper),
+ getChromeWindow: this._getChromeWindow,
+ });
+}
+
+SyncedTabsDeckComponent.prototype = {
+ PANELS: {
+ TABS_CONTAINER: "tabs-container",
+ TABS_FETCHING: "tabs-fetching",
+ NOT_AUTHED_INFO: "notAuthedInfo",
+ SINGLE_DEVICE_INFO: "singleDeviceInfo",
+ TABS_DISABLED: "tabs-disabled",
+ },
+
+ get container() {
+ return this._deckView ? this._deckView.container : null;
+ },
+
+ init() {
+ Services.obs.addObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED, false);
+ Services.obs.addObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION, false);
+
+ // Go ahead and trigger sync
+ this._SyncedTabs.syncTabs()
+ .catch(Cu.reportError);
+
+ this._deckView = new this._DeckView(this._window, this.tabListComponent, {
+ onAndroidClick: event => this.openAndroidLink(event),
+ oniOSClick: event => this.openiOSLink(event),
+ onSyncPrefClick: event => this.openSyncPrefs(event)
+ });
+
+ this._deckStore.on("change", state => this._deckView.render(state));
+ // Trigger the initial rendering of the deck view
+ // Object.values only in nightly
+ this._deckStore.setPanels(Object.keys(this.PANELS).map(k => this.PANELS[k]));
+ // Set the initial panel to display
+ this.updatePanel();
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED);
+ Services.obs.removeObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION);
+ this._deckView.destroy();
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case this._SyncedTabs.TOPIC_TABS_CHANGED:
+ this._syncedTabsListStore.getData();
+ this.updatePanel();
+ break;
+ case FxAccountsCommon.ONLOGIN_NOTIFICATION:
+ this.updatePanel();
+ break;
+ default:
+ break;
+ }
+ },
+
+ // There's no good way to mock fxAccounts in browser tests where it's already
+ // been instantiated, so we have this method for stubbing.
+ _accountStatus() {
+ return this._fxAccounts.accountStatus();
+ },
+
+ getPanelStatus() {
+ return this._accountStatus().then(exists => {
+ if (!exists) {
+ return this.PANELS.NOT_AUTHED_INFO;
+ }
+ if (!this._SyncedTabs.isConfiguredToSyncTabs) {
+ return this.PANELS.TABS_DISABLED;
+ }
+ if (!this._SyncedTabs.hasSyncedThisSession) {
+ return this.PANELS.TABS_FETCHING;
+ }
+ return this._SyncedTabs.getTabClients().then(clients => {
+ if (clients.length) {
+ return this.PANELS.TABS_CONTAINER;
+ }
+ return this.PANELS.SINGLE_DEVICE_INFO;
+ });
+ })
+ .catch(err => {
+ Cu.reportError(err);
+ return this.PANELS.NOT_AUTHED_INFO;
+ });
+ },
+
+ updatePanel() {
+ // return promise for tests
+ return this.getPanelStatus()
+ .then(panelId => this._deckStore.selectPanel(panelId))
+ .catch(Cu.reportError);
+ },
+
+ openAndroidLink(event) {
+ let href = Services.prefs.getCharPref("identity.mobilepromo.android") + "synced-tabs-sidebar";
+ this._openUrl(href, event);
+ },
+
+ openiOSLink(event) {
+ let href = Services.prefs.getCharPref("identity.mobilepromo.ios") + "synced-tabs-sidebar";
+ this._openUrl(href, event);
+ },
+
+ _openUrl(url, event) {
+ this._window.openUILink(url, event);
+ },
+
+ openSyncPrefs() {
+ this._getChromeWindow(this._window).gSyncUI.openSetup(null, "tabs-sidebar");
+ }
+};
+
diff --git a/browser/components/syncedtabs/SyncedTabsDeckStore.js b/browser/components/syncedtabs/SyncedTabsDeckStore.js
new file mode 100644
index 000000000..ede6914c8
--- /dev/null
+++ b/browser/components/syncedtabs/SyncedTabsDeckStore.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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {});
+
+this.EXPORTED_SYMBOLS = [
+ "SyncedTabsDeckStore"
+];
+
+/**
+ * SyncedTabsDeckStore
+ *
+ * This store keeps track of the deck view state, including the panels and which
+ * one is selected. The view listens for change events on the store, which are
+ * triggered whenever the state changes. If it's a small change, the state
+ * will have `isUpdatable` set to true so the view can skip rerendering the whole
+ * DOM.
+ */
+function SyncedTabsDeckStore() {
+ EventEmitter.call(this);
+ this._panels = [];
+}
+
+Object.assign(SyncedTabsDeckStore.prototype, EventEmitter.prototype, {
+ _change(isUpdatable = false) {
+ let panels = this._panels.map(panel => {
+ return {id: panel, selected: panel === this._selectedPanel};
+ });
+ this.emit("change", {panels, isUpdatable: isUpdatable});
+ },
+
+ /**
+ * Sets the selected panelId and triggers a change event.
+ * @param {String} panelId - ID of the panel to select.
+ */
+ selectPanel(panelId) {
+ if (this._panels.indexOf(panelId) === -1 || this._selectedPanel === panelId) {
+ return;
+ }
+ this._selectedPanel = panelId;
+ this._change(true);
+ },
+
+ /**
+ * Update the set of panels in the deck and trigger a change event.
+ * @param {Array} panels - an array of IDs for each panel in the deck.
+ */
+ setPanels(panels) {
+ if (panels === this._panels) {
+ return;
+ }
+ this._panels = panels || [];
+ this._change();
+ }
+});
diff --git a/browser/components/syncedtabs/SyncedTabsDeckView.js b/browser/components/syncedtabs/SyncedTabsDeckView.js
new file mode 100644
index 000000000..e9efff323
--- /dev/null
+++ b/browser/components/syncedtabs/SyncedTabsDeckView.js
@@ -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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
+
+let log = Cu.import("resource://gre/modules/Log.jsm", {})
+ .Log.repository.getLogger("Sync.RemoteTabs");
+
+this.EXPORTED_SYMBOLS = [
+ "SyncedTabsDeckView"
+];
+
+/**
+ * SyncedTabsDeckView
+ *
+ * Instances of SyncedTabsDeckView render DOM nodes from a given state.
+ * No state is kept internaly and the DOM will completely
+ * rerender unless the state flags `isUpdatable`, which helps
+ * make small changes without the overhead of a full rerender.
+ */
+const SyncedTabsDeckView = function (window, tabListComponent, props) {
+ this.props = props;
+
+ this._window = window;
+ this._doc = window.document;
+
+ this._tabListComponent = tabListComponent;
+ this._deckTemplate = this._doc.getElementById("deck-template");
+ this.container = this._doc.createElement("div");
+};
+
+SyncedTabsDeckView.prototype = {
+ render(state) {
+ if (state.isUpdatable) {
+ this.update(state);
+ } else {
+ this.create(state);
+ }
+ },
+
+ create(state) {
+ let deck = this._doc.importNode(this._deckTemplate.content, true).firstElementChild;
+ this._clearChilden();
+
+ let tabListWrapper = this._doc.createElement("div");
+ tabListWrapper.className = "tabs-container sync-state";
+ this._tabListComponent.init();
+ tabListWrapper.appendChild(this._tabListComponent.container);
+ deck.appendChild(tabListWrapper);
+ this.container.appendChild(deck);
+
+ this._generateDevicePromo();
+
+ this._attachListeners();
+ this.update(state);
+ },
+
+ _getBrowserBundle() {
+ return getChromeWindow(this._window).document.getElementById("bundle_browser");
+ },
+
+ _generateDevicePromo() {
+ let bundle = this._getBrowserBundle();
+ let formatArgs = ["android", "ios"].map(os => {
+ let link = this._doc.createElement("a");
+ link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`);
+ link.className = `${os}-link text-link`;
+ link.setAttribute("href", "#");
+ return link.outerHTML;
+ });
+ // Put it all together...
+ let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs);
+ this.container.querySelector(".device-promo").innerHTML = contents;
+ },
+
+ destroy() {
+ this._tabListComponent.uninit();
+ this.container.remove();
+ },
+
+ update(state) {
+ // Note that we may also want to update elements that are outside of the
+ // deck, so use the document to find the class names rather than our
+ // container.
+ for (let panel of state.panels) {
+ if (panel.selected) {
+ Array.prototype.map.call(this._doc.getElementsByClassName(panel.id),
+ item => item.classList.add("selected"));
+ } else {
+ Array.prototype.map.call(this._doc.getElementsByClassName(panel.id),
+ item => item.classList.remove("selected"));
+ }
+ }
+ },
+
+ _clearChilden() {
+ while (this.container.firstChild) {
+ this.container.removeChild(this.container.firstChild);
+ }
+ },
+
+ _attachListeners() {
+ this.container.querySelector(".android-link").addEventListener("click", this.props.onAndroidClick);
+ this.container.querySelector(".ios-link").addEventListener("click", this.props.oniOSClick);
+ let syncPrefLinks = this.container.querySelectorAll(".sync-prefs");
+ for (let link of syncPrefLinks) {
+ link.addEventListener("click", this.props.onSyncPrefClick);
+ }
+ },
+};
+
diff --git a/browser/components/syncedtabs/SyncedTabsListStore.js b/browser/components/syncedtabs/SyncedTabsListStore.js
new file mode 100644
index 000000000..8f03d9a89
--- /dev/null
+++ b/browser/components/syncedtabs/SyncedTabsListStore.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";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {});
+
+this.EXPORTED_SYMBOLS = [
+ "SyncedTabsListStore"
+];
+
+/**
+ * SyncedTabsListStore
+ *
+ * Instances of this store encapsulate all of the state associated with a synced tabs list view.
+ * The state includes the clients, their tabs, the row that is currently selected,
+ * and the filtered query.
+ */
+function SyncedTabsListStore(SyncedTabs) {
+ EventEmitter.call(this);
+ this._SyncedTabs = SyncedTabs;
+ this.data = [];
+ this._closedClients = {};
+ this._selectedRow = [-1, -1];
+ this.filter = "";
+ this.inputFocused = false;
+}
+
+Object.assign(SyncedTabsListStore.prototype, EventEmitter.prototype, {
+ // This internal method triggers the "change" event that views
+ // listen for. It denormalizes the state so that it's easier for
+ // the view to deal with. updateType hints to the view what
+ // actually needs to be rerendered or just updated, and can be
+ // empty (to (re)render everything), "searchbox" (to rerender just the tab list),
+ // or "all" (to skip rendering and just update all attributes of existing nodes).
+ _change(updateType) {
+ let selectedParent = this._selectedRow[0];
+ let selectedChild = this._selectedRow[1];
+ let rowSelected = false;
+ // clone the data so that consumers can't mutate internal storage
+ let data = Cu.cloneInto(this.data, {});
+ let tabCount = 0;
+
+ data.forEach((client, index) => {
+ client.closed = !!this._closedClients[client.id];
+
+ if (rowSelected || selectedParent < 0) {
+ return;
+ }
+ if (this.filter) {
+ if (selectedParent < tabCount + client.tabs.length) {
+ client.tabs[selectedParent - tabCount].selected = true;
+ client.tabs[selectedParent - tabCount].focused = !this.inputFocused;
+ rowSelected = true;
+ } else {
+ tabCount += client.tabs.length;
+ }
+ return;
+ }
+ if (selectedParent === index && selectedChild === -1) {
+ client.selected = true;
+ client.focused = !this.inputFocused;
+ rowSelected = true;
+ } else if (selectedParent === index) {
+ client.tabs[selectedChild].selected = true;
+ client.tabs[selectedChild].focused = !this.inputFocused;
+ rowSelected = true;
+ }
+ });
+
+ // If this were React the view would be smart enough
+ // to not re-render the whole list unless necessary. But it's
+ // not, so updateType is a hint to the view of what actually
+ // needs to be rerendered.
+ this.emit("change", {
+ clients: data,
+ canUpdateAll: updateType === "all",
+ canUpdateInput: updateType === "searchbox",
+ filter: this.filter,
+ inputFocused: this.inputFocused
+ });
+ },
+
+ /**
+ * Moves the row selection from a child to its parent,
+ * which occurs when the parent of a selected row closes.
+ */
+ _selectParentRow() {
+ this._selectedRow[1] = -1;
+ },
+
+ _toggleBranch(id, closed) {
+ this._closedClients[id] = closed;
+ if (this._closedClients[id]) {
+ this._selectParentRow();
+ }
+ this._change("all");
+ },
+
+ _isOpen(client) {
+ return !this._closedClients[client.id];
+ },
+
+ moveSelectionDown() {
+ let branchRow = this._selectedRow[0];
+ let childRow = this._selectedRow[1];
+ let branch = this.data[branchRow];
+
+ if (this.filter) {
+ this.selectRow(branchRow + 1);
+ return;
+ }
+
+ if (branchRow < 0) {
+ this.selectRow(0, -1);
+ } else if ((!branch.tabs.length || childRow >= branch.tabs.length - 1 || !this._isOpen(branch)) && branchRow < this.data.length) {
+ this.selectRow(branchRow + 1, -1);
+ } else if (childRow < branch.tabs.length) {
+ this.selectRow(branchRow, childRow + 1);
+ }
+ },
+
+ moveSelectionUp() {
+ let branchRow = this._selectedRow[0];
+ let childRow = this._selectedRow[1];
+
+ if (this.filter) {
+ this.selectRow(branchRow - 1);
+ return;
+ }
+
+ if (branchRow < 0) {
+ this.selectRow(0, -1);
+ } else if (childRow < 0 && branchRow > 0) {
+ let prevBranch = this.data[branchRow - 1];
+ let newChildRow = this._isOpen(prevBranch) ? prevBranch.tabs.length - 1 : -1;
+ this.selectRow(branchRow - 1, newChildRow);
+ } else if (childRow >= 0) {
+ this.selectRow(branchRow, childRow - 1);
+ }
+ },
+
+ // Selects a row and makes sure the selection is within bounds
+ selectRow(parent, child) {
+ let maxParentRow = this.filter ? this._tabCount() : this.data.length;
+ let parentRow = parent;
+ if (parent <= -1) {
+ parentRow = 0;
+ } else if (parent >= maxParentRow) {
+ return;
+ }
+
+ let childRow = child;
+ if (parentRow === -1 || this.filter || typeof child === "undefined" || child < -1) {
+ childRow = -1;
+ } else if (child >= this.data[parentRow].tabs.length) {
+ childRow = this.data[parentRow].tabs.length - 1;
+ }
+
+ if (this._selectedRow[0] === parentRow && this._selectedRow[1] === childRow) {
+ return;
+ }
+
+ this._selectedRow = [parentRow, childRow];
+ this.inputFocused = false;
+ this._change("all");
+ },
+
+ _tabCount() {
+ return this.data.reduce((prev, curr) => curr.tabs.length + prev, 0);
+ },
+
+ toggleBranch(id) {
+ this._toggleBranch(id, !this._closedClients[id]);
+ },
+
+ closeBranch(id) {
+ this._toggleBranch(id, true);
+ },
+
+ openBranch(id) {
+ this._toggleBranch(id, false);
+ },
+
+ focusInput() {
+ this.inputFocused = true;
+ // A change type of "all" updates rather than rebuilds, which is what we
+ // want here - only the selection/focus has changed.
+ this._change("all");
+ },
+
+ blurInput() {
+ this.inputFocused = false;
+ // A change type of "all" updates rather than rebuilds, which is what we
+ // want here - only the selection/focus has changed.
+ this._change("all");
+ },
+
+ clearFilter() {
+ this.filter = "";
+ this._selectedRow = [-1, -1];
+ return this.getData();
+ },
+
+ // Fetches data from the SyncedTabs module and triggers
+ // and update
+ getData(filter) {
+ let updateType;
+ let hasFilter = typeof filter !== "undefined";
+ if (hasFilter) {
+ this.filter = filter;
+ this._selectedRow = [-1, -1];
+
+ // When a filter is specified we tell the view that only the list
+ // needs to be rerendered so that it doesn't disrupt the input
+ // field's focus.
+ updateType = "searchbox";
+ }
+
+ // return promise for tests
+ return this._SyncedTabs.getTabClients(this.filter)
+ .then(result => {
+ if (!hasFilter) {
+ // Only sort clients and tabs if we're rendering the whole list.
+ this._SyncedTabs.sortTabClientsByLastUsed(result);
+ }
+ this.data = result;
+ this._change(updateType);
+ })
+ .catch(Cu.reportError);
+ }
+});
diff --git a/browser/components/syncedtabs/TabListComponent.js b/browser/components/syncedtabs/TabListComponent.js
new file mode 100644
index 000000000..d3aace8f9
--- /dev/null
+++ b/browser/components/syncedtabs/TabListComponent.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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let log = Cu.import("resource://gre/modules/Log.jsm", {})
+ .Log.repository.getLogger("Sync.RemoteTabs");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
+ "resource:///modules/BrowserUITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
+ "resource:///modules/PlacesUIUtils.jsm");
+
+this.EXPORTED_SYMBOLS = [
+ "TabListComponent"
+];
+
+/**
+ * TabListComponent
+ *
+ * The purpose of this component is to compose the view, state, and actions.
+ * It defines high level actions that act on the state and passes them to the
+ * view for it to trigger during user interaction. It also subscribes the view
+ * to state changes so it can rerender.
+ */
+
+function TabListComponent({window, store, View, SyncedTabs, clipboardHelper,
+ getChromeWindow}) {
+ this._window = window;
+ this._store = store;
+ this._View = View;
+ this._clipboardHelper = clipboardHelper;
+ this._getChromeWindow = getChromeWindow;
+ // used to trigger Sync from context menu
+ this._SyncedTabs = SyncedTabs;
+}
+
+TabListComponent.prototype = {
+ get container() {
+ return this._view.container;
+ },
+
+ init() {
+ log.debug("Initializing TabListComponent");
+
+ this._view = new this._View(this._window, {
+ onSelectRow: (...args) => this.onSelectRow(...args),
+ onOpenTab: (...args) => this.onOpenTab(...args),
+ onOpenTabs: (...args) => this.onOpenTabs(...args),
+ onMoveSelectionDown: (...args) => this.onMoveSelectionDown(...args),
+ onMoveSelectionUp: (...args) => this.onMoveSelectionUp(...args),
+ onToggleBranch: (...args) => this.onToggleBranch(...args),
+ onBookmarkTab: (...args) => this.onBookmarkTab(...args),
+ onCopyTabLocation: (...args) => this.onCopyTabLocation(...args),
+ onSyncRefresh: (...args) => this.onSyncRefresh(...args),
+ onFilter: (...args) => this.onFilter(...args),
+ onClearFilter: (...args) => this.onClearFilter(...args),
+ onFilterFocus: (...args) => this.onFilterFocus(...args),
+ onFilterBlur: (...args) => this.onFilterBlur(...args)
+ });
+
+ this._store.on("change", state => this._view.render(state));
+ this._view.render({clients: []});
+ // get what's already available...
+ this._store.getData();
+ this._store.focusInput();
+ },
+
+ uninit() {
+ this._view.destroy();
+ },
+
+ onFilter(query) {
+ this._store.getData(query);
+ },
+
+ onClearFilter() {
+ this._store.clearFilter();
+ },
+
+ onFilterFocus() {
+ this._store.focusInput();
+ },
+
+ onFilterBlur() {
+ this._store.blurInput();
+ },
+
+ onSelectRow(position) {
+ this._store.selectRow(position[0], position[1]);
+ },
+
+ onMoveSelectionDown() {
+ this._store.moveSelectionDown();
+ },
+
+ onMoveSelectionUp() {
+ this._store.moveSelectionUp();
+ },
+
+ onToggleBranch(id) {
+ this._store.toggleBranch(id);
+ },
+
+ onBookmarkTab(uri, title) {
+ this._window.top.PlacesCommandHook
+ .bookmarkLink(this._window.top.PlacesUtils.bookmarksMenuFolderId, uri, title)
+ .catch(Cu.reportError);
+ },
+
+ onOpenTab(url, where, params) {
+ this._window.openUILinkIn(url, where, params);
+ BrowserUITelemetry.countSyncedTabEvent("open", "sidebar");
+ },
+
+ onOpenTabs(urls, where) {
+ if (!PlacesUIUtils.confirmOpenInTabs(urls.length, this._window)) {
+ return;
+ }
+ if (where == "window") {
+ this._window.openDialog(this._window.getBrowserURL(), "_blank",
+ "chrome,dialog=no,all", urls.join("|"));
+ } else {
+ let loadInBackground = where == "tabshifted" ? true : false;
+ this._getChromeWindow(this._window).gBrowser.loadTabs(urls, loadInBackground, false);
+ }
+ BrowserUITelemetry.countSyncedTabEvent("openmultiple", "sidebar");
+ },
+
+ onCopyTabLocation(url) {
+ this._clipboardHelper.copyString(url);
+ },
+
+ onSyncRefresh() {
+ this._SyncedTabs.syncTabs(true);
+ }
+};
diff --git a/browser/components/syncedtabs/TabListView.js b/browser/components/syncedtabs/TabListView.js
new file mode 100644
index 000000000..dab15101b
--- /dev/null
+++ b/browser/components/syncedtabs/TabListView.js
@@ -0,0 +1,568 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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");
+
+let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
+
+let log = Cu.import("resource://gre/modules/Log.jsm", {})
+ .Log.repository.getLogger("Sync.RemoteTabs");
+
+this.EXPORTED_SYMBOLS = [
+ "TabListView"
+];
+
+function getContextMenu(window) {
+ return getChromeWindow(window).document.getElementById("SyncedTabsSidebarContext");
+}
+
+function getTabsFilterContextMenu(window) {
+ return getChromeWindow(window).document.getElementById("SyncedTabsSidebarTabsFilterContext");
+}
+
+/*
+ * TabListView
+ *
+ * Given a state, this object will render the corresponding DOM.
+ * It maintains no state of it's own. It listens for DOM events
+ * and triggers actions that may cause the state to change and
+ * ultimately the view to rerender.
+ */
+function TabListView(window, props) {
+ this.props = props;
+
+ this._window = window;
+ this._doc = this._window.document;
+
+ this._tabsContainerTemplate = this._doc.getElementById("tabs-container-template");
+ this._clientTemplate = this._doc.getElementById("client-template");
+ this._emptyClientTemplate = this._doc.getElementById("empty-client-template");
+ this._tabTemplate = this._doc.getElementById("tab-template");
+ this.tabsFilter = this._doc.querySelector(".tabsFilter");
+ this.clearFilter = this._doc.querySelector(".textbox-search-clear");
+ this.searchBox = this._doc.querySelector(".search-box");
+ this.searchIcon = this._doc.querySelector(".textbox-search-icon");
+
+ this.container = this._doc.createElement("div");
+
+ this._attachFixedListeners();
+
+ this._setupContextMenu();
+}
+
+TabListView.prototype = {
+ render(state) {
+ // Don't rerender anything; just update attributes, e.g. selection
+ if (state.canUpdateAll) {
+ this._update(state);
+ return;
+ }
+ // Rerender the tab list
+ if (state.canUpdateInput) {
+ this._updateSearchBox(state);
+ this._createList(state);
+ return;
+ }
+ // Create the world anew
+ this._create(state);
+ },
+
+ // Create the initial DOM from templates
+ _create(state) {
+ let wrapper = this._doc.importNode(this._tabsContainerTemplate.content, true).firstElementChild;
+ this._clearChilden();
+ this.container.appendChild(wrapper);
+
+ this.list = this.container.querySelector(".list");
+
+ this._createList(state);
+ this._updateSearchBox(state);
+
+ this._attachListListeners();
+ },
+
+ _createList(state) {
+ this._clearChilden(this.list);
+ for (let client of state.clients) {
+ if (state.filter) {
+ this._renderFilteredClient(client);
+ } else {
+ this._renderClient(client);
+ }
+ }
+ if (this.list.firstChild) {
+ const firstTab = this.list.firstChild.querySelector(".item.tab:first-child .item-title");
+ if (firstTab) {
+ firstTab.setAttribute("tabindex", 2);
+ }
+ }
+ },
+
+ destroy() {
+ this._teardownContextMenu();
+ this.container.remove();
+ },
+
+ _update(state) {
+ this._updateSearchBox(state);
+ for (let client of state.clients) {
+ let clientNode = this._doc.getElementById("item-" + client.id);
+ if (clientNode) {
+ this._updateClient(client, clientNode);
+ }
+
+ client.tabs.forEach((tab, index) => {
+ let tabNode = this._doc.getElementById('tab-' + client.id + '-' + index);
+ this._updateTab(tab, tabNode, index);
+ });
+ }
+ },
+
+ // Client rows are hidden when the list is filtered
+ _renderFilteredClient(client, filter) {
+ client.tabs.forEach((tab, index) => {
+ let node = this._renderTab(client, tab, index);
+ this.list.appendChild(node);
+ });
+ },
+
+ _renderClient(client) {
+ let itemNode = client.tabs.length ?
+ this._createClient(client) :
+ this._createEmptyClient(client);
+
+ this._updateClient(client, itemNode);
+
+ let tabsList = itemNode.querySelector(".item-tabs-list");
+ client.tabs.forEach((tab, index) => {
+ let node = this._renderTab(client, tab, index);
+ tabsList.appendChild(node);
+ });
+
+ this.list.appendChild(itemNode);
+ return itemNode;
+ },
+
+ _renderTab(client, tab, index) {
+ let itemNode = this._createTab(tab);
+ this._updateTab(tab, itemNode, index);
+ return itemNode;
+ },
+
+ _createClient(item) {
+ return this._doc.importNode(this._clientTemplate.content, true).firstElementChild;
+ },
+
+ _createEmptyClient(item) {
+ return this._doc.importNode(this._emptyClientTemplate.content, true).firstElementChild;
+ },
+
+ _createTab(item) {
+ return this._doc.importNode(this._tabTemplate.content, true).firstElementChild;
+ },
+
+ _clearChilden(node) {
+ let parent = node || this.container;
+ while (parent.firstChild) {
+ parent.removeChild(parent.firstChild);
+ }
+ },
+
+ // These listeners are attached only once, when we initialize the view
+ _attachFixedListeners() {
+ this.tabsFilter.addEventListener("input", this.onFilter.bind(this));
+ this.tabsFilter.addEventListener("focus", this.onFilterFocus.bind(this));
+ this.tabsFilter.addEventListener("blur", this.onFilterBlur.bind(this));
+ this.clearFilter.addEventListener("click", this.onClearFilter.bind(this));
+ this.searchIcon.addEventListener("click", this.onFilterFocus.bind(this));
+ },
+
+ // These listeners have to be re-created every time since we re-create the list
+ _attachListListeners() {
+ this.list.addEventListener("click", this.onClick.bind(this));
+ this.list.addEventListener("mouseup", this.onMouseUp.bind(this));
+ this.list.addEventListener("keydown", this.onKeyDown.bind(this));
+ },
+
+ _updateSearchBox(state) {
+ if (state.filter) {
+ this.searchBox.classList.add("filtered");
+ } else {
+ this.searchBox.classList.remove("filtered");
+ }
+ this.tabsFilter.value = state.filter;
+ if (state.inputFocused) {
+ this.searchBox.setAttribute("focused", true);
+ this.tabsFilter.focus();
+ } else {
+ this.searchBox.removeAttribute("focused");
+ }
+ },
+
+ /**
+ * Update the element representing an item, ensuring it's in sync with the
+ * underlying data.
+ * @param {client} item - Item to use as a source.
+ * @param {Element} itemNode - Element to update.
+ */
+ _updateClient(item, itemNode) {
+ itemNode.setAttribute("id", "item-" + item.id);
+ let lastSync = new Date(item.lastModified);
+ let lastSyncTitle = getChromeWindow(this._window).gSyncUI.formatLastSyncDate(lastSync);
+ itemNode.setAttribute("title", lastSyncTitle);
+ if (item.closed) {
+ itemNode.classList.add("closed");
+ } else {
+ itemNode.classList.remove("closed");
+ }
+ if (item.selected) {
+ itemNode.classList.add("selected");
+ } else {
+ itemNode.classList.remove("selected");
+ }
+ if (item.isMobile) {
+ itemNode.classList.add("device-image-mobile");
+ } else {
+ itemNode.classList.add("device-image-desktop");
+ }
+ if (item.focused) {
+ itemNode.focus();
+ }
+ itemNode.dataset.id = item.id;
+ itemNode.querySelector(".item-title").textContent = item.name;
+ },
+
+ /**
+ * Update the element representing a tab, ensuring it's in sync with the
+ * underlying data.
+ * @param {tab} item - Item to use as a source.
+ * @param {Element} itemNode - Element to update.
+ */
+ _updateTab(item, itemNode, index) {
+ itemNode.setAttribute("title", `${item.title}\n${item.url}`);
+ itemNode.setAttribute("id", "tab-" + item.client + '-' + index);
+ if (item.selected) {
+ itemNode.classList.add("selected");
+ } else {
+ itemNode.classList.remove("selected");
+ }
+ if (item.focused) {
+ itemNode.focus();
+ }
+ itemNode.dataset.url = item.url;
+
+ itemNode.querySelector(".item-title").textContent = item.title;
+
+ if (item.icon) {
+ let icon = itemNode.querySelector(".item-icon-container");
+ icon.style.backgroundImage = "url(" + item.icon + ")";
+ }
+ },
+
+ onMouseUp(event) {
+ if (event.which == 2) { // Middle click
+ this.onClick(event);
+ }
+ },
+
+ onClick(event) {
+ let itemNode = this._findParentItemNode(event.target);
+ if (!itemNode) {
+ return;
+ }
+
+ if (itemNode.classList.contains("tab")) {
+ let url = itemNode.dataset.url;
+ if (url) {
+ this.onOpenSelected(url, event);
+ }
+ }
+
+ // Middle click on a client
+ if (itemNode.classList.contains("client")) {
+ let where = getChromeWindow(this._window).whereToOpenLink(event);
+ if (where != "current") {
+ const tabs = itemNode.querySelector(".item-tabs-list").childNodes;
+ const urls = [...tabs].map(tab => tab.dataset.url);
+ this.props.onOpenTabs(urls, where);
+ }
+ }
+
+ if (event.target.classList.contains("item-twisty-container")
+ && event.which != 2) {
+ this.props.onToggleBranch(itemNode.dataset.id);
+ return;
+ }
+
+ let position = this._getSelectionPosition(itemNode);
+ this.props.onSelectRow(position);
+ },
+
+ /**
+ * Handle a keydown event on the list box.
+ * @param {Event} event - Triggering event.
+ */
+ onKeyDown(event) {
+ if (event.keyCode == this._window.KeyEvent.DOM_VK_DOWN) {
+ event.preventDefault();
+ this.props.onMoveSelectionDown();
+ } else if (event.keyCode == this._window.KeyEvent.DOM_VK_UP) {
+ event.preventDefault();
+ this.props.onMoveSelectionUp();
+ } else if (event.keyCode == this._window.KeyEvent.DOM_VK_RETURN) {
+ let selectedNode = this.container.querySelector('.item.selected');
+ if (selectedNode.dataset.url) {
+ this.onOpenSelected(selectedNode.dataset.url, event);
+ } else if (selectedNode) {
+ this.props.onToggleBranch(selectedNode.dataset.id);
+ }
+ }
+ },
+
+ onBookmarkTab() {
+ let item = this._getSelectedTabNode();
+ if (item) {
+ let title = item.querySelector(".item-title").textContent;
+ this.props.onBookmarkTab(item.dataset.url, title);
+ }
+ },
+
+ onCopyTabLocation() {
+ let item = this._getSelectedTabNode();
+ if (item) {
+ this.props.onCopyTabLocation(item.dataset.url);
+ }
+ },
+
+ onOpenSelected(url, event) {
+ let where = getChromeWindow(this._window).whereToOpenLink(event);
+ this.props.onOpenTab(url, where, {});
+ },
+
+ onOpenSelectedFromContextMenu(event) {
+ let item = this._getSelectedTabNode();
+ if (item) {
+ let where = event.target.getAttribute("where");
+ let params = {
+ private: event.target.hasAttribute("private"),
+ };
+ this.props.onOpenTab(item.dataset.url, where, params);
+ }
+ },
+
+ onFilter(event) {
+ let query = event.target.value;
+ if (query) {
+ this.props.onFilter(query);
+ } else {
+ this.props.onClearFilter();
+ }
+ },
+
+ onClearFilter() {
+ this.props.onClearFilter();
+ },
+
+ onFilterFocus() {
+ this.props.onFilterFocus();
+ },
+ onFilterBlur() {
+ this.props.onFilterBlur();
+ },
+
+ _getSelectedTabNode() {
+ let item = this.container.querySelector('.item.selected');
+ if (this._isTab(item) && item.dataset.url) {
+ return item;
+ }
+ return null;
+ },
+
+ // Set up the custom context menu
+ _setupContextMenu() {
+ Services.els.addSystemEventListener(this._window, "contextmenu", this, false);
+ for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
+ let menu = getMenu(this._window);
+ menu.addEventListener("popupshowing", this, true);
+ menu.addEventListener("command", this, true);
+ }
+ },
+
+ _teardownContextMenu() {
+ // Tear down context menu
+ Services.els.removeSystemEventListener(this._window, "contextmenu", this, false);
+ for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
+ let menu = getMenu(this._window);
+ menu.removeEventListener("popupshowing", this, true);
+ menu.removeEventListener("command", this, true);
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "contextmenu":
+ this.handleContextMenu(event);
+ break;
+
+ case "popupshowing": {
+ if (event.target.getAttribute("id") == "SyncedTabsSidebarTabsFilterContext") {
+ this.handleTabsFilterContextMenuShown(event);
+ }
+ break;
+ }
+
+ case "command": {
+ let menu = event.target.closest("menupopup");
+ switch (menu.getAttribute("id")) {
+ case "SyncedTabsSidebarContext":
+ this.handleContentContextMenuCommand(event);
+ break;
+
+ case "SyncedTabsSidebarTabsFilterContext":
+ this.handleTabsFilterContextMenuCommand(event);
+ break;
+ }
+ break;
+ }
+ }
+ },
+
+ handleTabsFilterContextMenuShown(event) {
+ let document = event.target.ownerDocument;
+ let focusedElement = document.commandDispatcher.focusedElement;
+ if (focusedElement != this.tabsFilter) {
+ this.tabsFilter.focus();
+ }
+ for (let item of event.target.children) {
+ if (!item.hasAttribute("cmd")) {
+ continue;
+ }
+ let command = item.getAttribute("cmd");
+ let controller = document.commandDispatcher.getControllerForCommand(command);
+ if (controller.isCommandEnabled(command)) {
+ item.removeAttribute("disabled");
+ } else {
+ item.setAttribute("disabled", "true");
+ }
+ }
+ },
+
+ handleContentContextMenuCommand(event) {
+ let id = event.target.getAttribute("id");
+ switch (id) {
+ case "syncedTabsOpenSelected":
+ case "syncedTabsOpenSelectedInTab":
+ case "syncedTabsOpenSelectedInWindow":
+ case "syncedTabsOpenSelectedInPrivateWindow":
+ this.onOpenSelectedFromContextMenu(event);
+ break;
+ case "syncedTabsBookmarkSelected":
+ this.onBookmarkTab();
+ break;
+ case "syncedTabsCopySelected":
+ this.onCopyTabLocation();
+ break;
+ case "syncedTabsRefresh":
+ case "syncedTabsRefreshFilter":
+ this.props.onSyncRefresh();
+ break;
+ }
+ },
+
+ handleTabsFilterContextMenuCommand(event) {
+ let command = event.target.getAttribute("cmd");
+ let dispatcher = getChromeWindow(this._window).document.commandDispatcher;
+ let controller = dispatcher.focusedElement.controllers.getControllerForCommand(command);
+ controller.doCommand(command);
+ },
+
+ handleContextMenu(event) {
+ let menu;
+
+ if (event.target == this.tabsFilter) {
+ menu = getTabsFilterContextMenu(this._window);
+ } else {
+ let itemNode = this._findParentItemNode(event.target);
+ if (itemNode) {
+ let position = this._getSelectionPosition(itemNode);
+ this.props.onSelectRow(position);
+ }
+ menu = getContextMenu(this._window);
+ this.adjustContextMenu(menu);
+ }
+
+ menu.openPopupAtScreen(event.screenX, event.screenY, true, event);
+ },
+
+ adjustContextMenu(menu) {
+ let item = this.container.querySelector('.item.selected');
+ let showTabOptions = this._isTab(item);
+
+ let el = menu.firstChild;
+
+ while (el) {
+ if (showTabOptions || el.getAttribute("id") === "syncedTabsRefresh") {
+ el.hidden = false;
+ } else {
+ el.hidden = true;
+ }
+
+ el = el.nextSibling;
+ }
+ },
+
+ /**
+ * Find the parent item element, from a given child element.
+ * @param {Element} node - Child element.
+ * @return {Element} Element for the item, or null if not found.
+ */
+ _findParentItemNode(node) {
+ while (node && node !== this.list && node !== this._doc.documentElement &&
+ !node.classList.contains("item")) {
+ node = node.parentNode;
+ }
+
+ if (node !== this.list && node !== this._doc.documentElement) {
+ return node;
+ }
+
+ return null;
+ },
+
+ _findParentBranchNode(node) {
+ while (node && !node.classList.contains("list") && node !== this._doc.documentElement &&
+ !node.parentNode.classList.contains("list")) {
+ node = node.parentNode;
+ }
+
+ if (node !== this.list && node !== this._doc.documentElement) {
+ return node;
+ }
+
+ return null;
+ },
+
+ _getSelectionPosition(itemNode) {
+ let parent = this._findParentBranchNode(itemNode);
+ let parentPosition = this._indexOfNode(parent.parentNode, parent);
+ let childPosition = -1;
+ // if the node is not a client, find its position within the parent
+ if (parent !== itemNode) {
+ childPosition = this._indexOfNode(itemNode.parentNode, itemNode);
+ }
+ return [parentPosition, childPosition];
+ },
+
+ _indexOfNode(parent, child) {
+ return Array.prototype.indexOf.call(parent.childNodes, child);
+ },
+
+ _isTab(item) {
+ return item && item.classList.contains("tab");
+ }
+};
diff --git a/browser/components/syncedtabs/jar.mn b/browser/components/syncedtabs/jar.mn
new file mode 100644
index 000000000..ba2b105a1
--- /dev/null
+++ b/browser/components/syncedtabs/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/syncedtabs/sidebar.xhtml
+ content/browser/syncedtabs/sidebar.js
diff --git a/browser/components/syncedtabs/moz.build b/browser/components/syncedtabs/moz.build
new file mode 100644
index 000000000..93c98e65d
--- /dev/null
+++ b/browser/components/syncedtabs/moz.build
@@ -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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
+
+EXTRA_JS_MODULES.syncedtabs += [
+ 'EventEmitter.jsm',
+ 'SyncedTabsDeckComponent.js',
+ 'SyncedTabsDeckStore.js',
+ 'SyncedTabsDeckView.js',
+ 'SyncedTabsListStore.js',
+ 'TabListComponent.js',
+ 'TabListView.js',
+ 'util.js',
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Synced tabs')
+
diff --git a/browser/components/syncedtabs/sidebar.js b/browser/components/syncedtabs/sidebar.js
new file mode 100644
index 000000000..84df95e9d
--- /dev/null
+++ b/browser/components/syncedtabs/sidebar.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-sync/SyncedTabs.jsm");
+Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+
+this.syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts});
+
+let onLoaded = () => {
+ syncedTabsDeckComponent.init();
+ document.getElementById("template-container").appendChild(syncedTabsDeckComponent.container);
+};
+
+let onUnloaded = () => {
+ removeEventListener("DOMContentLoaded", onLoaded);
+ removeEventListener("unload", onUnloaded);
+ syncedTabsDeckComponent.uninit();
+};
+
+addEventListener("DOMContentLoaded", onLoaded);
+addEventListener("unload", onUnloaded);
diff --git a/browser/components/syncedtabs/sidebar.xhtml b/browser/components/syncedtabs/sidebar.xhtml
new file mode 100644
index 000000000..3efcbea0e
--- /dev/null
+++ b/browser/components/syncedtabs/sidebar.xhtml
@@ -0,0 +1,114 @@
+<?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 PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" [
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+ %browserDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % syncBrandDTD
+ SYSTEM "chrome://browser/locale/syncBrand.dtd">
+ %syncBrandDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <head>
+ <script src="chrome://browser/content/syncedtabs/sidebar.js" type="application/javascript;version=1.8"></script>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/syncedtabs/sidebar.css"/>
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/"/>
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/textbox.css"/>
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/browser.css"/>
+ <title>&syncedTabs.sidebar.label;</title>
+ </head>
+
+ <body dir="&locale.dir;" role="application">
+ <template id="client-template">
+ <div class="item client" role="option" tabindex="-1">
+ <div class="item-title-container">
+ <div class="item-twisty-container"></div>
+ <div class="item-icon-container"></div>
+ <p class="item-title"></p>
+ </div>
+ <div class="item-tabs-list"></div>
+ </div>
+ </template>
+ <template id="empty-client-template">
+ <div class="item empty client" role="option" tabindex="-1">
+ <div class="item-title-container">
+ <div class="item-twisty-container"></div>
+ <div class="item-icon-container"></div>
+ <p class="item-title"></p>
+ </div>
+ <div class="item-tabs-list">
+ <div class="item empty" role="option" tabindex="-1">
+ <div class="item-title-container">
+ <div class="item-icon-container"></div>
+ <p class="item-title">&syncedTabs.sidebar.notabs.label;</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </template>
+ <template id="tab-template">
+ <div class="item tab" role="option" tabindex="-1">
+ <div class="item-title-container">
+ <div class="item-icon-container"></div>
+ <p class="item-title"></p>
+ </div>
+ </div>
+ </template>
+
+ <template id="tabs-container-template">
+ <div class="tabs-container">
+ <div class="list" role="listbox"></div>
+ </div>
+ </template>
+
+ <template id="deck-template">
+ <div class="deck">
+ <div class="tabs-fetching sync-state">
+ <!-- Show intentionally blank panel, see bug 1239845 -->
+ </div>
+ <div class="notAuthedInfo sync-state">
+ <p>&syncedTabs.sidebar.notsignedin.label;</p>
+ <p><a href="#" class="sync-prefs text-link">&fxaSignIn.label;</a></p>
+ </div>
+ <div class="singleDeviceInfo sync-state">
+ <p>&syncedTabs.sidebar.noclients.title;</p>
+ <p>&syncedTabs.sidebar.noclients.subtitle;</p>
+ <p class="device-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"></p>
+ </div>
+ <div class="tabs-disabled sync-state">
+ <p>&syncedTabs.sidebar.tabsnotsyncing.label;</p>
+ <p><a href="#" class="sync-prefs text-link">&syncedTabs.sidebar.openprefs.label;</a></p>
+ </div>
+ </div>
+ </template>
+
+ <div class="content-container">
+ <!-- the non-scrollable header -->
+ <div class="content-header">
+ <div class="sidebar-search-container tabs-container sync-state">
+ <div class="search-box compact">
+ <div class="textbox-input-box">
+ <input type="text" class="tabsFilter textbox-input" tabindex="1"/>
+ <div class="textbox-search-icons">
+ <a class="textbox-search-clear"></a>
+ <a class="textbox-search-icon"></a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- the scrollable content area where our templates are inserted -->
+ <div id="template-container" class="content-scrollable" tabindex="-1">
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/browser/components/syncedtabs/test/browser/.eslintrc.js b/browser/components/syncedtabs/test/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/components/syncedtabs/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/syncedtabs/test/browser/browser.ini b/browser/components/syncedtabs/test/browser/browser.ini
new file mode 100644
index 000000000..02fa364f1
--- /dev/null
+++ b/browser/components/syncedtabs/test/browser/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_sidebar_syncedtabslist.js]
diff --git a/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
new file mode 100644
index 000000000..afbc00282
--- /dev/null
+++ b/browser/components/syncedtabs/test/browser/browser_sidebar_syncedtabslist.js
@@ -0,0 +1,410 @@
+"use strict";
+
+const FIXTURE = [
+ {
+ "id": "7cqCr77ptzX3",
+ "type": "client",
+ "name": "zcarter's Nightly on MacBook-Pro-25",
+ "isMobile": false,
+ "tabs": [
+ {
+ "type": "tab",
+ "title": "Firefox for Android — Mobile Web browser — More ways to customize and protect your privacy — Mozilla",
+ "url": "https://www.mozilla.org/en-US/firefox/android/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=synced-tabs-sidebar",
+ "icon": "chrome://mozapps/skin/places/defaultFavicon.png",
+ "client": "7cqCr77ptzX3",
+ "lastUsed": 1452124677
+ }
+ ]
+ },
+ {
+ "id": "2xU5h-4bkWqA",
+ "type": "client",
+ "name": "laptop",
+ "isMobile": false,
+ "tabs": [
+ {
+ "type": "tab",
+ "title": "Firefox for iOS — Mobile Web browser for your iPhone, iPad and iPod touch — Mozilla",
+ "url": "https://www.mozilla.org/en-US/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=synced-tabs-sidebar",
+ "icon": "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon.dc6635050bf5.ico",
+ "client": "2xU5h-4bkWqA",
+ "lastUsed": 1451519425
+ },
+ {
+ "type": "tab",
+ "title": "Firefox Nightly First Run Page",
+ "url": "https://www.mozilla.org/en-US/firefox/nightly/firstrun/?oldversion=45.0a1",
+ "icon": "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon-nightly.560395bbb2e1.png",
+ "client": "2xU5h-4bkWqA",
+ "lastUsed": 1451519420
+ },
+ {
+ // Should appear first for this client.
+ "type": "tab",
+ "title": "Mozilla Developer Network",
+ "url": "https://developer.mozilla.org/en-US/",
+ "icon": "moz-anno:favicon:https://developer.cdn.mozilla.net/static/img/favicon32.e02854fdcf73.png",
+ "client": "2xU5h-4bkWqA",
+ "lastUsed": 1451519725
+ }
+ ]
+ },
+ {
+ "id": "OL3EJCsdb2JD",
+ "type": "client",
+ "name": "desktop",
+ "isMobile": false,
+ "tabs": []
+ }
+];
+
+let originalSyncedTabsInternal = null;
+
+function* testClean() {
+ let syncedTabsDeckComponent = window.SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
+ let SyncedTabs = window.SidebarUI.browser.contentWindow.SyncedTabs;
+ syncedTabsDeckComponent._accountStatus.restore();
+ SyncedTabs._internal.getTabClients.restore();
+ SyncedTabs._internal = originalSyncedTabsInternal;
+
+ yield new Promise(resolve => {
+ window.SidebarUI.browser.contentWindow.addEventListener("unload", function listener() {
+ window.SidebarUI.browser.contentWindow.removeEventListener("unload", listener);
+ resolve();
+ });
+ SidebarUI.hide();
+ });
+}
+
+add_task(function* testSyncedTabsSidebarList() {
+ yield SidebarUI.show('viewTabsSidebar');
+
+ Assert.equal(SidebarUI.currentID, "viewTabsSidebar", "Sidebar should have SyncedTabs loaded");
+
+ let syncedTabsDeckComponent = SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
+ let SyncedTabs = SidebarUI.browser.contentWindow.SyncedTabs;
+
+ Assert.ok(syncedTabsDeckComponent, "component exists");
+
+ originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: true,
+ hasSyncedThisSession: true,
+ getTabClients() { return Promise.resolve([]) },
+ syncTabs() { return Promise.resolve(); },
+ };
+
+ sinon.stub(syncedTabsDeckComponent, "_accountStatus", () => Promise.resolve(true));
+ sinon.stub(SyncedTabs._internal, "getTabClients", () => Promise.resolve(Cu.cloneInto(FIXTURE, {})));
+
+ yield syncedTabsDeckComponent.updatePanel();
+ // This is a hacky way of waiting for the view to render. The view renders
+ // after the following promise (a different instance of which is triggered
+ // in updatePanel) resolves, so we wait for it here as well
+ yield syncedTabsDeckComponent.tabListComponent._store.getData();
+
+ Assert.ok(SyncedTabs._internal.getTabClients.called, "get clients called");
+
+ let selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+
+
+ Assert.ok(selectedPanel.classList.contains("tabs-container"),
+ "tabs panel is selected");
+
+ Assert.equal(selectedPanel.querySelectorAll(".tab").length, 4,
+ "four tabs listed");
+ Assert.equal(selectedPanel.querySelectorAll(".client").length, 3,
+ "three clients listed");
+ Assert.equal(selectedPanel.querySelectorAll(".client")[2].querySelectorAll(".empty").length, 1,
+ "third client is empty");
+
+ // Verify that the tabs are sorted by last used time.
+ var expectedTabIndices = [[0], [2, 0, 1]];
+ Array.prototype.forEach.call(selectedPanel.querySelectorAll(".client"), (clientNode, i) => {
+ checkItem(clientNode, FIXTURE[i]);
+ Array.prototype.forEach.call(clientNode.querySelectorAll(".tab"), (tabNode, j) => {
+ let tabIndex = expectedTabIndices[i][j];
+ checkItem(tabNode, FIXTURE[i].tabs[tabIndex]);
+ });
+ });
+
+});
+
+add_task(testClean);
+
+add_task(function* testSyncedTabsSidebarFilteredList() {
+ yield SidebarUI.show('viewTabsSidebar');
+ let syncedTabsDeckComponent = window.SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
+ let SyncedTabs = window.SidebarUI.browser.contentWindow.SyncedTabs;
+
+ Assert.ok(syncedTabsDeckComponent, "component exists");
+
+ originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: true,
+ hasSyncedThisSession: true,
+ getTabClients() { return Promise.resolve([]) },
+ syncTabs() { return Promise.resolve(); },
+ };
+
+ sinon.stub(syncedTabsDeckComponent, "_accountStatus", () => Promise.resolve(true));
+ sinon.stub(SyncedTabs._internal, "getTabClients", () => Promise.resolve(Cu.cloneInto(FIXTURE, {})));
+
+ yield syncedTabsDeckComponent.updatePanel();
+ // This is a hacky way of waiting for the view to render. The view renders
+ // after the following promise (a different instance of which is triggered
+ // in updatePanel) resolves, so we wait for it here as well
+ yield syncedTabsDeckComponent.tabListComponent._store.getData();
+
+ let filterInput = syncedTabsDeckComponent._window.document.querySelector(".tabsFilter");
+ filterInput.value = "filter text";
+ filterInput.blur();
+
+ yield syncedTabsDeckComponent.tabListComponent._store.getData("filter text");
+
+ let selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ Assert.ok(selectedPanel.classList.contains("tabs-container"),
+ "tabs panel is selected");
+
+ Assert.equal(selectedPanel.querySelectorAll(".tab").length, 4,
+ "four tabs listed");
+ Assert.equal(selectedPanel.querySelectorAll(".client").length, 0,
+ "no clients are listed");
+
+ Assert.equal(filterInput.value, "filter text",
+ "filter text box has correct value");
+
+ // Tabs should not be sorted when filter is active.
+ let FIXTURE_TABS = FIXTURE.reduce((prev, client) => prev.concat(client.tabs), []);
+
+ Array.prototype.forEach.call(selectedPanel.querySelectorAll(".tab"), (tabNode, i) => {
+ checkItem(tabNode, FIXTURE_TABS[i]);
+ });
+
+ // Removing the filter should resort tabs.
+ FIXTURE_TABS.sort((a, b) => b.lastUsed - a.lastUsed);
+ yield syncedTabsDeckComponent.tabListComponent._store.getData();
+ Array.prototype.forEach.call(selectedPanel.querySelectorAll(".tab"), (tabNode, i) => {
+ checkItem(tabNode, FIXTURE_TABS[i]);
+ });
+});
+
+add_task(testClean);
+
+add_task(function* testSyncedTabsSidebarStatus() {
+ let accountExists = false;
+
+ yield SidebarUI.show('viewTabsSidebar');
+ let syncedTabsDeckComponent = window.SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
+ let SyncedTabs = window.SidebarUI.browser.contentWindow.SyncedTabs;
+
+ originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: false,
+ hasSyncedThisSession: false,
+ getTabClients() {},
+ syncTabs() { return Promise.resolve(); },
+ };
+
+ Assert.ok(syncedTabsDeckComponent, "component exists");
+
+ sinon.spy(syncedTabsDeckComponent, "updatePanel");
+ sinon.spy(syncedTabsDeckComponent, "observe");
+
+ sinon.stub(syncedTabsDeckComponent, "_accountStatus", () => Promise.reject("Test error"));
+ yield syncedTabsDeckComponent.updatePanel();
+
+ let selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ Assert.ok(selectedPanel.classList.contains("notAuthedInfo"),
+ "not-authed panel is selected on auth error");
+
+ syncedTabsDeckComponent._accountStatus.restore();
+ sinon.stub(syncedTabsDeckComponent, "_accountStatus", () => Promise.resolve(accountExists));
+ yield syncedTabsDeckComponent.updatePanel();
+ selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ Assert.ok(selectedPanel.classList.contains("notAuthedInfo"),
+ "not-authed panel is selected");
+
+ accountExists = true;
+ yield syncedTabsDeckComponent.updatePanel();
+ selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ Assert.ok(selectedPanel.classList.contains("tabs-disabled"),
+ "tabs disabled panel is selected");
+
+ SyncedTabs._internal.isConfiguredToSyncTabs = true;
+ yield syncedTabsDeckComponent.updatePanel();
+ selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ Assert.ok(selectedPanel.classList.contains("tabs-fetching"),
+ "tabs fetch panel is selected");
+
+ SyncedTabs._internal.hasSyncedThisSession = true;
+ sinon.stub(SyncedTabs._internal, "getTabClients", () => Promise.resolve([]));
+ yield syncedTabsDeckComponent.updatePanel();
+ selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ Assert.ok(selectedPanel.classList.contains("singleDeviceInfo"),
+ "tabs fetch panel is selected");
+
+ SyncedTabs._internal.getTabClients.restore();
+ sinon.stub(SyncedTabs._internal, "getTabClients", () => Promise.resolve([{id: "mock"}]));
+ yield syncedTabsDeckComponent.updatePanel();
+ selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ Assert.ok(selectedPanel.classList.contains("tabs-container"),
+ "tabs panel is selected");
+});
+
+add_task(testClean);
+
+add_task(function* testSyncedTabsSidebarContextMenu() {
+ yield SidebarUI.show('viewTabsSidebar');
+ let syncedTabsDeckComponent = window.SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
+ let SyncedTabs = window.SidebarUI.browser.contentWindow.SyncedTabs;
+
+ Assert.ok(syncedTabsDeckComponent, "component exists");
+
+ originalSyncedTabsInternal = SyncedTabs._internal;
+ SyncedTabs._internal = {
+ isConfiguredToSyncTabs: true,
+ hasSyncedThisSession: true,
+ getTabClients() { return Promise.resolve([]) },
+ syncTabs() { return Promise.resolve(); },
+ };
+
+ sinon.stub(syncedTabsDeckComponent, "_accountStatus", () => Promise.resolve(true));
+ sinon.stub(SyncedTabs._internal, "getTabClients", () => Promise.resolve(Cu.cloneInto(FIXTURE, {})));
+
+ yield syncedTabsDeckComponent.updatePanel();
+ // This is a hacky way of waiting for the view to render. The view renders
+ // after the following promise (a different instance of which is triggered
+ // in updatePanel) resolves, so we wait for it here as well
+ yield syncedTabsDeckComponent.tabListComponent._store.getData();
+
+ info("Right-clicking the search box should show text-related actions");
+ let filterMenuItems = [
+ "menuitem[cmd=cmd_undo]",
+ "menuseparator",
+ // We don't check whether the commands are enabled due to platform
+ // differences. On OS X and Windows, "cut" and "copy" are always enabled
+ // for HTML inputs; on Linux, they're only enabled if text is selected.
+ "menuitem[cmd=cmd_cut]",
+ "menuitem[cmd=cmd_copy]",
+ "menuitem[cmd=cmd_paste]",
+ "menuitem[cmd=cmd_delete]",
+ "menuseparator",
+ "menuitem[cmd=cmd_selectAll]",
+ "menuseparator",
+ "menuitem#syncedTabsRefreshFilter",
+ ];
+ yield* testContextMenu(syncedTabsDeckComponent,
+ "#SyncedTabsSidebarTabsFilterContext",
+ ".tabsFilter",
+ filterMenuItems);
+
+ info("Right-clicking a tab should show additional actions");
+ let tabMenuItems = [
+ ["menuitem#syncedTabsOpenSelected", { hidden: false }],
+ ["menuitem#syncedTabsOpenSelectedInTab", { hidden: false }],
+ ["menuitem#syncedTabsOpenSelectedInWindow", { hidden: false }],
+ ["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: false }],
+ ["menuseparator", { hidden: false }],
+ ["menuitem#syncedTabsBookmarkSelected", { hidden: false }],
+ ["menuitem#syncedTabsCopySelected", { hidden: false }],
+ ["menuseparator", { hidden: false }],
+ ["menuitem#syncedTabsRefresh", { hidden: false }],
+ ];
+ yield* testContextMenu(syncedTabsDeckComponent,
+ "#SyncedTabsSidebarContext",
+ "#tab-7cqCr77ptzX3-0",
+ tabMenuItems);
+
+ info("Right-clicking a client shouldn't show any actions");
+ let sidebarMenuItems = [
+ ["menuitem#syncedTabsOpenSelected", { hidden: true }],
+ ["menuitem#syncedTabsOpenSelectedInTab", { hidden: true }],
+ ["menuitem#syncedTabsOpenSelectedInWindow", { hidden: true }],
+ ["menuitem#syncedTabsOpenSelectedInPrivateWindow", { hidden: true }],
+ ["menuseparator", { hidden: true }],
+ ["menuitem#syncedTabsBookmarkSelected", { hidden: true }],
+ ["menuitem#syncedTabsCopySelected", { hidden: true }],
+ ["menuseparator", { hidden: true }],
+ ["menuitem#syncedTabsRefresh", { hidden: false }],
+ ];
+ yield* testContextMenu(syncedTabsDeckComponent,
+ "#SyncedTabsSidebarContext",
+ "#item-OL3EJCsdb2JD",
+ sidebarMenuItems);
+});
+
+add_task(testClean);
+
+function checkItem(node, item) {
+ Assert.ok(node.classList.contains("item"),
+ "Node should have .item class");
+ if (item.client) {
+ // tab items
+ Assert.equal(node.querySelector(".item-title").textContent, item.title,
+ "Node's title element's text should match item title");
+ Assert.ok(node.classList.contains("tab"),
+ "Node should have .tab class");
+ Assert.equal(node.dataset.url, item.url,
+ "Node's URL should match item URL");
+ Assert.equal(node.getAttribute("title"), item.title + "\n" + item.url,
+ "Tab node should have correct title attribute");
+ } else {
+ // client items
+ Assert.equal(node.querySelector(".item-title").textContent, item.name,
+ "Node's title element's text should match client name");
+ Assert.ok(node.classList.contains("client"),
+ "Node should have .client class");
+ Assert.equal(node.dataset.id, item.id,
+ "Node's ID should match item ID");
+ }
+}
+
+function* testContextMenu(syncedTabsDeckComponent, contextSelector, triggerSelector, menuSelectors) {
+ let contextMenu = document.querySelector(contextSelector);
+ let triggerElement = syncedTabsDeckComponent._window.document.querySelector(triggerSelector);
+ let isClosed = triggerElement.classList.contains("closed");
+
+ let promisePopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+
+ let chromeWindow = triggerElement.ownerGlobal.top;
+ let rect = triggerElement.getBoundingClientRect();
+ let contentRect = chromeWindow.SidebarUI.browser.getBoundingClientRect();
+ // The offsets in `rect` are relative to the content window, but
+ // `synthesizeMouseAtPoint` calls `nsIDOMWindowUtils.sendMouseEvent`,
+ // which interprets the offsets relative to the containing *chrome* window.
+ // This means we need to account for the width and height of any elements
+ // outside the `browser` element, like `sidebarheader`.
+ let offsetX = contentRect.x + rect.x + (rect.width / 2);
+ let offsetY = contentRect.y + rect.y + (rect.height / 4);
+
+ yield EventUtils.synthesizeMouseAtPoint(offsetX, offsetY, {
+ type: "contextmenu",
+ button: 2,
+ }, chromeWindow);
+ yield promisePopupShown;
+ is(triggerElement.classList.contains("closed"), isClosed,
+ "Showing the context menu shouldn't toggle the tab list");
+ checkChildren(contextMenu, menuSelectors);
+
+ let promisePopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ yield promisePopupHidden;
+}
+
+function checkChildren(node, selectors) {
+ is(node.children.length, selectors.length, "Menu item count doesn't match");
+ for (let index = 0; index < node.children.length; index++) {
+ let child = node.children[index];
+ let [selector, props] = [].concat(selectors[index]);
+ ok(selector, `Node at ${index} should have selector`);
+ ok(child.matches(selector), `Node ${
+ index} should match ${selector}`);
+ if (props) {
+ Object.keys(props).forEach(prop => {
+ is(child[prop], props[prop], `${prop} value at ${index} should match`);
+ });
+ }
+ }
+}
diff --git a/browser/components/syncedtabs/test/browser/head.js b/browser/components/syncedtabs/test/browser/head.js
new file mode 100644
index 000000000..40e36123e
--- /dev/null
+++ b/browser/components/syncedtabs/test/browser/head.js
@@ -0,0 +1,19 @@
+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/Promise.jsm");
+
+
+// Load mocking/stubbing library, sinon
+// docs: http://sinonjs.org/docs/
+/* global sinon */
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
+loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
+
+registerCleanupFunction(function*() {
+ // Cleanup window or the test runner will throw an error
+ delete window.sinon;
+ delete window.setImmediate;
+ delete window.clearImmediate;
+});
diff --git a/browser/components/syncedtabs/test/xpcshell/.eslintrc.js b/browser/components/syncedtabs/test/xpcshell/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/syncedtabs/test/xpcshell/head.js b/browser/components/syncedtabs/test/xpcshell/head.js
new file mode 100644
index 000000000..00055231c
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/head.js
@@ -0,0 +1,29 @@
+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.defineLazyGetter(this, "FxAccountsCommon", function () {
+ return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+Cu.import("resource://gre/modules/Timer.jsm");
+
+do_get_profile(); // fxa needs a profile directory for storage.
+
+// Create a window polyfill so sinon can load
+let window = {
+ document: {},
+ location: {},
+ setTimeout: setTimeout,
+ setInterval: setInterval,
+ clearTimeout: clearTimeout,
+ clearinterval: clearInterval
+};
+let self = window;
+
+// Load mocking/stubbing library, sinon
+// docs: http://sinonjs.org/docs/
+/* global sinon */
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
+loader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
diff --git a/browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js b/browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js
new file mode 100644
index 000000000..bc73ac621
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js
@@ -0,0 +1,35 @@
+"use strict";
+
+let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {});
+
+add_task(function* testSingleListener() {
+ let eventEmitter = new EventEmitter();
+ let spy = sinon.spy();
+
+ eventEmitter.on("click", spy);
+ eventEmitter.emit("click", "foo", "bar");
+ Assert.ok(spy.calledOnce);
+ Assert.ok(spy.calledWith("foo", "bar"));
+
+ eventEmitter.off("click", spy);
+ eventEmitter.emit("click");
+ Assert.ok(spy.calledOnce);
+});
+
+add_task(function* testMultipleListeners() {
+ let eventEmitter = new EventEmitter();
+ let spy1 = sinon.spy();
+ let spy2 = sinon.spy();
+
+ eventEmitter.on("some_event", spy1);
+ eventEmitter.on("some_event", spy2);
+ eventEmitter.emit("some_event");
+ Assert.ok(spy1.calledOnce);
+ Assert.ok(spy2.calledOnce);
+
+ eventEmitter.off("some_event", spy1);
+ eventEmitter.emit("some_event");
+ Assert.ok(spy1.calledOnce);
+ Assert.ok(spy2.calledTwice);
+});
+
diff --git a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
new file mode 100644
index 000000000..3d748b33c
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
@@ -0,0 +1,218 @@
+"use strict";
+
+let { SyncedTabs } = Cu.import("resource://services-sync/SyncedTabs.jsm", {});
+let { SyncedTabsDeckComponent } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js", {});
+let { TabListComponent } = Cu.import("resource:///modules/syncedtabs/TabListComponent.js", {});
+let { SyncedTabsListStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js", {});
+let { SyncedTabsDeckStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckStore.js", {});
+let { TabListView } = Cu.import("resource:///modules/syncedtabs/TabListView.js", {});
+let { DeckView } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckView.js", {});
+
+
+add_task(function* testInitUninit() {
+ let deckStore = new SyncedTabsDeckStore();
+ let listComponent = {};
+
+ let ViewMock = sinon.stub();
+ let view = {render: sinon.spy(), destroy: sinon.spy(), container: {}};
+ ViewMock.returns(view);
+
+ sinon.stub(SyncedTabs, "syncTabs", () => Promise.resolve());
+
+ sinon.spy(deckStore, "on");
+ sinon.stub(deckStore, "setPanels");
+
+ let component = new SyncedTabsDeckComponent({
+ window,
+ deckStore,
+ listComponent,
+ SyncedTabs,
+ DeckView: ViewMock,
+ });
+
+ sinon.stub(component, "updatePanel");
+
+ component.init();
+
+ Assert.ok(SyncedTabs.syncTabs.called);
+ SyncedTabs.syncTabs.restore();
+
+ Assert.ok(ViewMock.calledWithNew(), "view is instantiated");
+ Assert.equal(ViewMock.args[0][0], window);
+ Assert.equal(ViewMock.args[0][1], listComponent);
+ Assert.ok(ViewMock.args[0][2].onAndroidClick,
+ "view is passed onAndroidClick prop");
+ Assert.ok(ViewMock.args[0][2].oniOSClick,
+ "view is passed oniOSClick prop");
+ Assert.ok(ViewMock.args[0][2].onSyncPrefClick,
+ "view is passed onSyncPrefClick prop");
+
+ Assert.equal(component.container, view.container,
+ "component returns view's container");
+
+ Assert.ok(deckStore.on.calledOnce, "listener is added to store");
+ Assert.equal(deckStore.on.args[0][0], "change");
+ // Object.values only in nightly
+ let values = Object.keys(component.PANELS).map(k => component.PANELS[k]);
+ Assert.ok(deckStore.setPanels.calledWith(values),
+ "panels are set on deck store");
+
+ Assert.ok(component.updatePanel.called);
+
+ deckStore.emit("change", "mock state");
+ Assert.ok(view.render.calledWith("mock state"),
+ "view.render is called on state change");
+
+ component.uninit();
+
+ Assert.ok(view.destroy.calledOnce, "view is destroyed on uninit");
+});
+
+
+function waitForObserver() {
+ return new Promise((resolve, reject) => {
+ Services.obs.addObserver((subject, topic) => {
+ resolve();
+ }, SyncedTabs.TOPIC_TABS_CHANGED, false);
+ });
+}
+
+add_task(function* testObserver() {
+ let deckStore = new SyncedTabsDeckStore();
+ let listStore = new SyncedTabsListStore(SyncedTabs);
+ let listComponent = {};
+
+ let ViewMock = sinon.stub();
+ let view = {render: sinon.spy(), destroy: sinon.spy(), container: {}};
+ ViewMock.returns(view);
+
+ sinon.stub(SyncedTabs, "syncTabs", () => Promise.resolve());
+
+ sinon.spy(deckStore, "on");
+ sinon.stub(deckStore, "setPanels");
+
+ sinon.stub(listStore, "getData");
+
+ let component = new SyncedTabsDeckComponent({
+ window,
+ deckStore,
+ listStore,
+ listComponent,
+ SyncedTabs,
+ DeckView: ViewMock,
+ });
+
+ sinon.spy(component, "observe");
+ sinon.stub(component, "updatePanel");
+
+ component.init();
+ SyncedTabs.syncTabs.restore();
+ Assert.ok(component.updatePanel.called, "triggers panel update during init");
+
+ Services.obs.notifyObservers(null, SyncedTabs.TOPIC_TABS_CHANGED, "");
+
+ Assert.ok(component.observe.calledWith(null, SyncedTabs.TOPIC_TABS_CHANGED, ""),
+ "component is notified");
+
+ Assert.ok(listStore.getData.called, "gets list data");
+ Assert.ok(component.updatePanel.calledTwice, "triggers panel update");
+
+ Services.obs.notifyObservers(null, FxAccountsCommon.ONLOGIN_NOTIFICATION, "");
+
+ Assert.ok(component.observe.calledWith(null, FxAccountsCommon.ONLOGIN_NOTIFICATION, ""),
+ "component is notified of login");
+ Assert.equal(component.updatePanel.callCount, 3, "triggers panel update again");
+});
+
+add_task(function* testPanelStatus() {
+ let deckStore = new SyncedTabsDeckStore();
+ let listStore = new SyncedTabsListStore();
+ let listComponent = {};
+ let fxAccounts = {
+ accountStatus() {}
+ };
+ let SyncedTabsMock = {
+ getTabClients() {}
+ };
+
+ sinon.stub(listStore, "getData");
+
+
+ let component = new SyncedTabsDeckComponent({
+ fxAccounts,
+ deckStore,
+ listComponent,
+ SyncedTabs: SyncedTabsMock,
+ });
+
+ let isAuthed = false;
+ sinon.stub(fxAccounts, "accountStatus", () => Promise.resolve(isAuthed));
+ let result = yield component.getPanelStatus();
+ Assert.equal(result, component.PANELS.NOT_AUTHED_INFO);
+
+ isAuthed = true;
+
+ SyncedTabsMock.isConfiguredToSyncTabs = false;
+ result = yield component.getPanelStatus();
+ Assert.equal(result, component.PANELS.TABS_DISABLED);
+
+ SyncedTabsMock.isConfiguredToSyncTabs = true;
+
+ SyncedTabsMock.hasSyncedThisSession = false;
+ result = yield component.getPanelStatus();
+ Assert.equal(result, component.PANELS.TABS_FETCHING);
+
+ SyncedTabsMock.hasSyncedThisSession = true;
+
+ let clients = [];
+ sinon.stub(SyncedTabsMock, "getTabClients", () => Promise.resolve(clients));
+ result = yield component.getPanelStatus();
+ Assert.equal(result, component.PANELS.SINGLE_DEVICE_INFO);
+
+ clients = ["mock-client"];
+ result = yield component.getPanelStatus();
+ Assert.equal(result, component.PANELS.TABS_CONTAINER);
+
+ fxAccounts.accountStatus.restore();
+ sinon.stub(fxAccounts, "accountStatus", () => Promise.reject("err"));
+ result = yield component.getPanelStatus();
+ Assert.equal(result, component.PANELS.NOT_AUTHED_INFO);
+
+ sinon.stub(component, "getPanelStatus", () => Promise.resolve("mock-panelId"));
+ sinon.spy(deckStore, "selectPanel");
+ yield component.updatePanel();
+ Assert.ok(deckStore.selectPanel.calledWith("mock-panelId"));
+});
+
+add_task(function* testActions() {
+ let windowMock = {
+ openUILink() {},
+ };
+ let chromeWindowMock = {
+ gSyncUI: {
+ openSetup() {}
+ }
+ };
+ sinon.spy(windowMock, "openUILink");
+ sinon.spy(chromeWindowMock.gSyncUI, "openSetup");
+
+ let getChromeWindowMock = sinon.stub();
+ getChromeWindowMock.returns(chromeWindowMock);
+
+ let component = new SyncedTabsDeckComponent({
+ window: windowMock,
+ getChromeWindowMock
+ });
+
+ let href = Services.prefs.getCharPref("identity.mobilepromo.android") + "synced-tabs-sidebar";
+ component.openAndroidLink("mock-event");
+ Assert.ok(windowMock.openUILink.calledWith(href, "mock-event"));
+
+ href = Services.prefs.getCharPref("identity.mobilepromo.ios") + "synced-tabs-sidebar";
+ component.openiOSLink("mock-event");
+ Assert.ok(windowMock.openUILink.calledWith(href, "mock-event"));
+
+ component.openSyncPrefs();
+ Assert.ok(getChromeWindowMock.calledWith(windowMock));
+ Assert.ok(chromeWindowMock.gSyncUI.openSetup.called);
+});
diff --git a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js
new file mode 100644
index 000000000..69abb4024
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js
@@ -0,0 +1,64 @@
+"use strict";
+
+let { SyncedTabsDeckStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckStore.js", {});
+
+add_task(function* testSelectUnkownPanel() {
+ let deckStore = new SyncedTabsDeckStore();
+ let spy = sinon.spy();
+
+ deckStore.on("change", spy);
+ deckStore.selectPanel("foo");
+
+ Assert.ok(!spy.called);
+});
+
+add_task(function* testSetPanels() {
+ let deckStore = new SyncedTabsDeckStore();
+ let spy = sinon.spy();
+
+ deckStore.on("change", spy);
+ deckStore.setPanels(["panel1", "panel2"]);
+
+ Assert.ok(spy.calledWith({
+ panels: [
+ { id: "panel1", selected: false },
+ { id: "panel2", selected: false },
+ ],
+ isUpdatable: false
+ }));
+});
+
+add_task(function* testSelectPanel() {
+ let deckStore = new SyncedTabsDeckStore();
+ let spy = sinon.spy();
+
+ deckStore.setPanels(["panel1", "panel2"]);
+
+ deckStore.on("change", spy);
+ deckStore.selectPanel("panel2");
+
+ Assert.ok(spy.calledWith({
+ panels: [
+ { id: "panel1", selected: false },
+ { id: "panel2", selected: true },
+ ],
+ isUpdatable: true
+ }));
+
+ deckStore.selectPanel("panel2");
+ Assert.ok(spy.calledOnce, "doesn't trigger unless panel changes");
+});
+
+add_task(function* testSetPanelsSameArray() {
+ let deckStore = new SyncedTabsDeckStore();
+ let spy = sinon.spy();
+ deckStore.on("change", spy);
+
+ let panels = ["panel1", "panel2"];
+
+ deckStore.setPanels(panels);
+ deckStore.setPanels(panels);
+
+ Assert.ok(spy.calledOnce, "doesn't trigger unless set of panels changes");
+});
+
diff --git a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js
new file mode 100644
index 000000000..51580235f
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js
@@ -0,0 +1,266 @@
+"use strict";
+
+let { SyncedTabs } = Cu.import("resource://services-sync/SyncedTabs.jsm", {});
+let { SyncedTabsListStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js", {});
+
+const FIXTURE = [
+ {
+ "id": "2xU5h-4bkWqA",
+ "type": "client",
+ "name": "laptop",
+ "isMobile": false,
+ "tabs": [
+ {
+ "type": "tab",
+ "title": "Firefox for iOS — Mobile Web browser for your iPhone, iPad and iPod touch — Mozilla",
+ "url": "https://www.mozilla.org/en-US/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=synced-tabs-sidebar",
+ "icon": "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon.dc6635050bf5.ico",
+ "client": "2xU5h-4bkWqA",
+ "lastUsed": 1451519425
+ },
+ {
+ "type": "tab",
+ "title": "Firefox Nightly First Run Page",
+ "url": "https://www.mozilla.org/en-US/firefox/nightly/firstrun/?oldversion=45.0a1",
+ "icon": "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon-nightly.560395bbb2e1.png",
+ "client": "2xU5h-4bkWqA",
+ "lastUsed": 1451519420
+ }
+ ]
+ },
+ {
+ "id": "OL3EJCsdb2JD",
+ "type": "client",
+ "name": "desktop",
+ "isMobile": false,
+ "tabs": []
+ }
+];
+
+add_task(function* testGetDataEmpty() {
+ let store = new SyncedTabsListStore(SyncedTabs);
+ let spy = sinon.spy();
+
+ sinon.stub(SyncedTabs, "getTabClients", () => {
+ return Promise.resolve([]);
+ });
+ store.on("change", spy);
+
+ yield store.getData();
+
+ Assert.ok(SyncedTabs.getTabClients.calledWith(""));
+ Assert.ok(spy.calledWith({
+ clients: [],
+ canUpdateAll: false,
+ canUpdateInput: false,
+ filter: "",
+ inputFocused: false
+ }));
+
+ yield store.getData("filter");
+
+ Assert.ok(SyncedTabs.getTabClients.calledWith("filter"));
+ Assert.ok(spy.calledWith({
+ clients: [],
+ canUpdateAll: false,
+ canUpdateInput: true,
+ filter: "filter",
+ inputFocused: false
+ }));
+
+ SyncedTabs.getTabClients.restore();
+});
+
+add_task(function* testRowSelectionWithoutFilter() {
+ let store = new SyncedTabsListStore(SyncedTabs);
+ let spy = sinon.spy();
+
+ sinon.stub(SyncedTabs, "getTabClients", () => {
+ return Promise.resolve(FIXTURE);
+ });
+
+ yield store.getData();
+ SyncedTabs.getTabClients.restore();
+
+ store.on("change", spy);
+
+ store.selectRow(0, -1);
+ Assert.ok(spy.args[0][0].canUpdateAll, "can update the whole view");
+ Assert.ok(spy.args[0][0].clients[0].selected, "first client is selected");
+
+ store.moveSelectionUp();
+ Assert.ok(spy.calledOnce,
+ "can't move up past first client, no change triggered");
+
+ store.selectRow(0, 0);
+ Assert.ok(spy.args[1][0].clients[0].tabs[0].selected,
+ "first tab of first client is selected");
+
+ store.selectRow(0, 0);
+ Assert.ok(spy.calledTwice, "selecting same row doesn't trigger change");
+
+ store.selectRow(0, 1);
+ Assert.ok(spy.args[2][0].clients[0].tabs[1].selected,
+ "second tab of first client is selected");
+
+ store.selectRow(1);
+ Assert.ok(spy.args[3][0].clients[1].selected, "second client is selected");
+
+ store.moveSelectionDown();
+ Assert.equal(spy.callCount, 4,
+ "can't move selection down past last client, no change triggered");
+
+ store.moveSelectionUp();
+ Assert.equal(spy.callCount, 5,
+ "changed");
+ Assert.ok(spy.args[4][0].clients[0].tabs[FIXTURE[0].tabs.length - 1].selected,
+ "move selection up from client selects last tab of previous client");
+
+ store.moveSelectionUp();
+ Assert.ok(spy.args[5][0].clients[0].tabs[FIXTURE[0].tabs.length - 2].selected,
+ "move selection up from tab selects previous tab of client");
+});
+
+
+add_task(function* testToggleBranches() {
+ let store = new SyncedTabsListStore(SyncedTabs);
+ let spy = sinon.spy();
+
+ sinon.stub(SyncedTabs, "getTabClients", () => {
+ return Promise.resolve(FIXTURE);
+ });
+
+ yield store.getData();
+ SyncedTabs.getTabClients.restore();
+
+ store.selectRow(0);
+ store.on("change", spy);
+
+ let clientId = FIXTURE[0].id;
+ store.closeBranch(clientId);
+ Assert.ok(spy.args[0][0].clients[0].closed, "first client is closed");
+
+ store.openBranch(clientId);
+ Assert.ok(!spy.args[1][0].clients[0].closed, "first client is open");
+
+ store.toggleBranch(clientId);
+ Assert.ok(spy.args[2][0].clients[0].closed, "first client is toggled closed");
+
+ store.moveSelectionDown();
+ Assert.ok(spy.args[3][0].clients[1].selected,
+ "selection skips tabs if client is closed");
+
+ store.moveSelectionUp();
+ Assert.ok(spy.args[4][0].clients[0].selected,
+ "selection skips tabs if client is closed");
+});
+
+
+add_task(function* testRowSelectionWithFilter() {
+ let store = new SyncedTabsListStore(SyncedTabs);
+ let spy = sinon.spy();
+
+ sinon.stub(SyncedTabs, "getTabClients", () => {
+ return Promise.resolve(FIXTURE);
+ });
+
+ yield store.getData("filter");
+ SyncedTabs.getTabClients.restore();
+
+ store.on("change", spy);
+
+ store.selectRow(0);
+ Assert.ok(spy.args[0][0].clients[0].tabs[0].selected, "first tab is selected");
+
+ store.moveSelectionUp();
+ Assert.ok(spy.calledOnce,
+ "can't move up past first tab, no change triggered");
+
+ store.moveSelectionDown();
+ Assert.ok(spy.args[1][0].clients[0].tabs[1].selected,
+ "selection skips tabs if client is closed");
+
+ store.moveSelectionDown();
+ Assert.equal(spy.callCount, 2,
+ "can't move selection down past last tab, no change triggered");
+
+ store.selectRow(1);
+ Assert.equal(spy.callCount, 2,
+ "doesn't trigger change if same row selected");
+
+});
+
+
+add_task(function* testFilterAndClearFilter() {
+ let store = new SyncedTabsListStore(SyncedTabs);
+ let spy = sinon.spy();
+
+ sinon.stub(SyncedTabs, "getTabClients", () => {
+ return Promise.resolve(FIXTURE);
+ });
+ store.on("change", spy);
+
+ yield store.getData("filter");
+
+ Assert.ok(SyncedTabs.getTabClients.calledWith("filter"));
+ Assert.ok(!spy.args[0][0].canUpdateAll, "can't update all");
+ Assert.ok(spy.args[0][0].canUpdateInput, "can update just input");
+
+ store.selectRow(0);
+
+ Assert.equal(spy.args[1][0].filter, "filter");
+ Assert.ok(spy.args[1][0].clients[0].tabs[0].selected,
+ "tab is selected");
+
+ yield store.clearFilter();
+
+ Assert.ok(SyncedTabs.getTabClients.calledWith(""));
+ Assert.ok(!spy.args[2][0].canUpdateAll, "can't update all");
+ Assert.ok(!spy.args[2][0].canUpdateInput, "can't just update input");
+
+ Assert.equal(spy.args[2][0].filter, "");
+ Assert.ok(!spy.args[2][0].clients[0].tabs[0].selected,
+ "tab is no longer selected");
+
+ SyncedTabs.getTabClients.restore();
+});
+
+add_task(function* testFocusBlurInput() {
+ let store = new SyncedTabsListStore(SyncedTabs);
+ let spy = sinon.spy();
+
+ sinon.stub(SyncedTabs, "getTabClients", () => {
+ return Promise.resolve(FIXTURE);
+ });
+ store.on("change", spy);
+
+ yield store.getData();
+ SyncedTabs.getTabClients.restore();
+
+ Assert.ok(!spy.args[0][0].canUpdateAll, "must rerender all");
+
+ store.selectRow(0);
+ Assert.ok(!spy.args[1][0].inputFocused,
+ "input is not focused");
+ Assert.ok(spy.args[1][0].clients[0].selected,
+ "client is selected");
+ Assert.ok(spy.args[1][0].clients[0].focused,
+ "client is focused");
+
+ store.focusInput();
+ Assert.ok(spy.args[2][0].inputFocused,
+ "input is focused");
+ Assert.ok(spy.args[2][0].clients[0].selected,
+ "client is still selected");
+ Assert.ok(!spy.args[2][0].clients[0].focused,
+ "client is no longer focused");
+
+ store.blurInput();
+ Assert.ok(!spy.args[3][0].inputFocused,
+ "input is not focused");
+ Assert.ok(spy.args[3][0].clients[0].selected,
+ "client is selected");
+ Assert.ok(spy.args[3][0].clients[0].focused,
+ "client is focused");
+});
+
diff --git a/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js b/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js
new file mode 100644
index 000000000..0b0665a1b
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js
@@ -0,0 +1,155 @@
+"use strict";
+
+let { SyncedTabs } = Cu.import("resource://services-sync/SyncedTabs.jsm", {});
+let { TabListComponent } = Cu.import("resource:///modules/syncedtabs/TabListComponent.js", {});
+let { SyncedTabsListStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js", {});
+let { View } = Cu.import("resource:///modules/syncedtabs/TabListView.js", {});
+
+const ACTION_METHODS = [
+ "onSelectRow",
+ "onOpenTab",
+ "onOpenTabs",
+ "onMoveSelectionDown",
+ "onMoveSelectionUp",
+ "onToggleBranch",
+ "onBookmarkTab",
+ "onSyncRefresh",
+ "onFilter",
+ "onClearFilter",
+ "onFilterFocus",
+ "onFilterBlur",
+];
+
+add_task(function* testInitUninit() {
+ let store = new SyncedTabsListStore();
+ let ViewMock = sinon.stub();
+ let view = {render() {}, destroy() {}};
+
+ ViewMock.returns(view);
+
+ sinon.spy(view, 'render');
+ sinon.spy(view, 'destroy');
+
+ sinon.spy(store, "on");
+ sinon.stub(store, "getData");
+ sinon.stub(store, "focusInput");
+
+ let component = new TabListComponent({window, store, View: ViewMock, SyncedTabs});
+
+ for (let action of ACTION_METHODS) {
+ sinon.stub(component, action);
+ }
+
+ component.init();
+
+ Assert.ok(ViewMock.calledWithNew(), "view is instantiated");
+ Assert.ok(store.on.calledOnce, "listener is added to store");
+ Assert.equal(store.on.args[0][0], "change");
+ Assert.ok(view.render.calledWith({clients: []}),
+ "render is called on view instance");
+ Assert.ok(store.getData.calledOnce, "store gets initial data");
+ Assert.ok(store.focusInput.calledOnce, "input field is focused");
+
+ for (let method of ACTION_METHODS) {
+ let action = ViewMock.args[0][1][method];
+ Assert.ok(action, method + " action is passed to View");
+ action("foo", "bar");
+ Assert.ok(component[method].calledWith("foo", "bar"),
+ method + " action passed to View triggers the component method with args");
+ }
+
+ store.emit("change", "mock state");
+ Assert.ok(view.render.secondCall.calledWith("mock state"),
+ "view.render is called on state change");
+
+ component.uninit();
+ Assert.ok(view.destroy.calledOnce, "view is destroyed on uninit");
+});
+
+add_task(function* testActions() {
+ let store = new SyncedTabsListStore();
+ let chromeWindowMock = {
+ gBrowser: {
+ loadTabs() {},
+ },
+ };
+ let getChromeWindowMock = sinon.stub();
+ getChromeWindowMock.returns(chromeWindowMock);
+ let clipboardHelperMock = {
+ copyString() {},
+ };
+ let windowMock = {
+ top: {
+ PlacesCommandHook: {
+ bookmarkLink() { return Promise.resolve(); }
+ },
+ PlacesUtils: { bookmarksMenuFolderId: "id" }
+ },
+ getBrowserURL() {},
+ openDialog() {},
+ openUILinkIn() {}
+ };
+ let component = new TabListComponent({
+ window: windowMock, store, View: null, SyncedTabs,
+ clipboardHelper: clipboardHelperMock,
+ getChromeWindow: getChromeWindowMock });
+
+ sinon.stub(store, "getData");
+ component.onFilter("query");
+ Assert.ok(store.getData.calledWith("query"));
+
+ sinon.stub(store, "clearFilter");
+ component.onClearFilter();
+ Assert.ok(store.clearFilter.called);
+
+ sinon.stub(store, "focusInput");
+ component.onFilterFocus();
+ Assert.ok(store.focusInput.called);
+
+ sinon.stub(store, "blurInput");
+ component.onFilterBlur();
+ Assert.ok(store.blurInput.called);
+
+ sinon.stub(store, "selectRow");
+ component.onSelectRow([-1, -1]);
+ Assert.ok(store.selectRow.calledWith(-1, -1));
+
+ sinon.stub(store, "moveSelectionDown");
+ component.onMoveSelectionDown();
+ Assert.ok(store.moveSelectionDown.called);
+
+ sinon.stub(store, "moveSelectionUp");
+ component.onMoveSelectionUp();
+ Assert.ok(store.moveSelectionUp.called);
+
+ sinon.stub(store, "toggleBranch");
+ component.onToggleBranch("foo-id");
+ Assert.ok(store.toggleBranch.calledWith("foo-id"));
+
+ sinon.spy(windowMock.top.PlacesCommandHook, "bookmarkLink");
+ component.onBookmarkTab("uri", "title");
+ Assert.equal(windowMock.top.PlacesCommandHook.bookmarkLink.args[0][1], "uri");
+ Assert.equal(windowMock.top.PlacesCommandHook.bookmarkLink.args[0][2], "title");
+
+ sinon.spy(windowMock, "openUILinkIn");
+ component.onOpenTab("uri", "where", "params");
+ Assert.ok(windowMock.openUILinkIn.calledWith("uri", "where", "params"));
+
+ sinon.spy(chromeWindowMock.gBrowser, "loadTabs");
+ let tabsToOpen = ["uri1", "uri2"];
+ component.onOpenTabs(tabsToOpen, "where");
+ Assert.ok(getChromeWindowMock.calledWith(windowMock));
+ Assert.ok(chromeWindowMock.gBrowser.loadTabs.calledWith(tabsToOpen, false, false));
+ component.onOpenTabs(tabsToOpen, "tabshifted");
+ Assert.ok(chromeWindowMock.gBrowser.loadTabs.calledWith(tabsToOpen, true, false));
+
+ sinon.spy(clipboardHelperMock, "copyString");
+ component.onCopyTabLocation("uri");
+ Assert.ok(clipboardHelperMock.copyString.calledWith("uri"));
+
+ sinon.stub(SyncedTabs, "syncTabs");
+ component.onSyncRefresh();
+ Assert.ok(SyncedTabs.syncTabs.calledWith(true));
+ SyncedTabs.syncTabs.restore();
+});
+
diff --git a/browser/components/syncedtabs/test/xpcshell/xpcshell.ini b/browser/components/syncedtabs/test/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..1cb8dcb7a
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+head = head.js
+tail =
+firefox-appdir = browser
+
+[test_EventEmitter.js]
+[test_SyncedTabsDeckStore.js]
+[test_SyncedTabsListStore.js]
+[test_SyncedTabsDeckComponent.js]
+[test_TabListComponent.js]
diff --git a/browser/components/syncedtabs/util.js b/browser/components/syncedtabs/util.js
new file mode 100644
index 000000000..e09a1a528
--- /dev/null
+++ b/browser/components/syncedtabs/util.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";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = [
+ "getChromeWindow"
+];
+
+// Get the chrome (ie, browser) window hosting this content.
+function getChromeWindow(window) {
+ return window
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .wrappedJSObject;
+}
diff --git a/browser/components/tests/browser/.eslintrc.js b/browser/components/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/tests/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/tests/browser/browser.ini b/browser/components/tests/browser/browser.ini
new file mode 100644
index 000000000..6d00b69fa
--- /dev/null
+++ b/browser/components/tests/browser/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+
+[browser_bug538331.js]
+[browser_contentpermissionprompt.js]
diff --git a/browser/components/tests/browser/browser_bug538331.js b/browser/components/tests/browser/browser_bug538331.js
new file mode 100644
index 000000000..fce3790a0
--- /dev/null
+++ b/browser/components/tests/browser/browser_bug538331.js
@@ -0,0 +1,426 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const PREF_POSTUPDATE = "app.update.postupdate";
+const PREF_MSTONE = "browser.startup.homepage_override.mstone";
+const PREF_OVERRIDE_URL = "startup.homepage_override_url";
+
+const DEFAULT_PREF_URL = "http://pref.example.com/";
+const DEFAULT_UPDATE_URL = "http://example.com/";
+
+const XML_EMPTY = "<?xml version=\"1.0\"?><updates xmlns=" +
+ "\"http://www.mozilla.org/2005/app-update\"></updates>";
+
+const XML_PREFIX = "<updates xmlns=\"http://www.mozilla.org/2005/app-update\"" +
+ "><update appVersion=\"1.0\" buildID=\"20080811053724\" " +
+ "channel=\"nightly\" displayVersion=\"Version 1.0\" " +
+ "extensionVersion=\"1.0\" installDate=\"1238441400314\" " +
+ "isCompleteUpdate=\"true\" name=\"Update Test 1.0\" " +
+ "serviceURL=\"https://example.com/\" showNeverForVersion=" +
+ "\"false\" showPrompt=\"false\" type=" +
+ "\"minor\" version=\"version 1.0\" detailsURL=" +
+ "\"http://example.com/\" previousAppVersion=\"1.0\" " +
+ "statusText=\"The Update was successfully installed\" " +
+ "foregroundDownload=\"true\"";
+
+const XML_SUFFIX = "><patch type=\"complete\" URL=\"http://example.com/\" " +
+ "hashFunction=\"MD5\" hashValue=" +
+ "\"6232cd43a1c77e30191c53a329a3f99d\" size=\"775\" " +
+ "selected=\"true\" state=\"succeeded\"/></update></updates>";
+
+// nsBrowserContentHandler.js defaultArgs tests
+const BCH_TESTS = [
+ {
+ description: "no mstone change and no update",
+ noPostUpdatePref: true,
+ noMstoneChange: true
+ }, {
+ description: "mstone changed and no update",
+ noPostUpdatePref: true,
+ prefURL: DEFAULT_PREF_URL
+ }, {
+ description: "no mstone change and update with 'showURL' for actions",
+ actions: "showURL",
+ noMstoneChange: true
+ }, {
+ description: "update without actions",
+ prefURL: DEFAULT_PREF_URL
+ }, {
+ description: "update with 'showURL' for actions",
+ actions: "showURL",
+ prefURL: DEFAULT_PREF_URL
+ }, {
+ description: "update with 'showURL' for actions and openURL",
+ actions: "showURL",
+ openURL: DEFAULT_UPDATE_URL
+ }, {
+ description: "update with 'showURL showAlert' for actions",
+ actions: "showAlert showURL",
+ prefURL: DEFAULT_PREF_URL
+ }, {
+ description: "update with 'showAlert showURL' for actions and openURL",
+ actions: "showAlert showURL",
+ openURL: DEFAULT_UPDATE_URL
+ }, {
+ description: "update with 'showURL showNotification' for actions",
+ actions: "showURL showNotification",
+ prefURL: DEFAULT_PREF_URL
+ }, {
+ description: "update with 'showNotification showURL' for actions and " +
+ "openURL",
+ actions: "showNotification showURL",
+ openURL: DEFAULT_UPDATE_URL
+ }, {
+ description: "update with 'showAlert showURL showNotification' for actions",
+ actions: "showAlert showURL showNotification",
+ prefURL: DEFAULT_PREF_URL
+ }, {
+ description: "update with 'showNotification showURL showAlert' for " +
+ "actions and openURL",
+ actions: "showNotification showURL showAlert",
+ openURL: DEFAULT_UPDATE_URL
+ }, {
+ description: "update with 'showAlert' for actions",
+ actions: "showAlert"
+ }, {
+ description: "update with 'showAlert showNotification' for actions",
+ actions: "showAlert showNotification"
+ }, {
+ description: "update with 'showNotification' for actions",
+ actions: "showNotification"
+ }, {
+ description: "update with 'showNotification showAlert' for actions",
+ actions: "showNotification showAlert"
+ }, {
+ description: "update with 'silent' for actions",
+ actions: "silent"
+ }, {
+ description: "update with 'silent showURL showAlert showNotification' " +
+ "for actions and openURL",
+ actions: "silent showURL showAlert showNotification"
+ }
+];
+
+var gOriginalMStone;
+var gOriginalOverrideURL;
+
+this.__defineGetter__("gBG", function() {
+ delete this.gBG;
+ return this.gBG = Cc["@mozilla.org/browser/browserglue;1"].
+ getService(Ci.nsIObserver);
+});
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // Reset the startup page pref since it may have been set by other tests
+ // and we will assume it is default.
+ Services.prefs.clearUserPref('browser.startup.page');
+
+ if (gPrefService.prefHasUserValue(PREF_MSTONE)) {
+ gOriginalMStone = gPrefService.getCharPref(PREF_MSTONE);
+ }
+
+ if (gPrefService.prefHasUserValue(PREF_OVERRIDE_URL)) {
+ gOriginalOverrideURL = gPrefService.getCharPref(PREF_OVERRIDE_URL);
+ }
+
+ testDefaultArgs();
+}
+
+var gWindowCatcher = {
+ windowsOpen: 0,
+ finishCalled: false,
+ start: function() {
+ Services.ww.registerNotification(this);
+ },
+
+ finish: function(aFunc) {
+ Services.ww.unregisterNotification(this);
+ this.finishFunc = aFunc;
+ if (this.windowsOpen > 0)
+ return;
+
+ this.finishFunc();
+ },
+
+ closeWindow: function (win) {
+ info("window catcher closing window: " + win.document.documentURI);
+ win.close();
+ this.windowsOpen--;
+ if (this.finishFunc) {
+ this.finish(this.finishFunc);
+ }
+ },
+
+ windowLoad: function (win) {
+ executeSoon(this.closeWindow.bind(this, win));
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic != "domwindowopened")
+ return;
+
+ this.windowsOpen++;
+ let win = subject.QueryInterface(Ci.nsIDOMWindow);
+ info("window catcher caught window opening: " + win.document.documentURI);
+ win.addEventListener("load", function () {
+ win.removeEventListener("load", arguments.callee, false);
+ gWindowCatcher.windowLoad(win);
+ }, false);
+ }
+};
+
+function finish_test()
+{
+ // Reset browser.startup.homepage_override.mstone to the original value or
+ // clear it if it didn't exist.
+ if (gOriginalMStone) {
+ gPrefService.setCharPref(PREF_MSTONE, gOriginalMStone);
+ } else if (gPrefService.prefHasUserValue(PREF_MSTONE)) {
+ gPrefService.clearUserPref(PREF_MSTONE);
+ }
+
+ // Reset startup.homepage_override_url to the original value or clear it if
+ // it didn't exist.
+ if (gOriginalOverrideURL) {
+ gPrefService.setCharPref(PREF_OVERRIDE_URL, gOriginalOverrideURL);
+ } else if (gPrefService.prefHasUserValue(PREF_OVERRIDE_URL)) {
+ gPrefService.clearUserPref(PREF_OVERRIDE_URL);
+ }
+
+ writeUpdatesToXMLFile(XML_EMPTY);
+ reloadUpdateManagerData();
+
+ finish();
+}
+
+// Test the defaultArgs returned by nsBrowserContentHandler after an update
+function testDefaultArgs()
+{
+ // Clear any pre-existing override in defaultArgs that are hanging around.
+ // This will also set the browser.startup.homepage_override.mstone preference
+ // if it isn't already set.
+ Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs;
+
+ let originalMstone = gPrefService.getCharPref(PREF_MSTONE);
+
+ gPrefService.setCharPref(PREF_OVERRIDE_URL, DEFAULT_PREF_URL);
+
+ writeUpdatesToXMLFile(XML_EMPTY);
+ reloadUpdateManagerData();
+
+ for (let i = 0; i < BCH_TESTS.length; i++) {
+ let test = BCH_TESTS[i];
+ ok(true, "Test nsBrowserContentHandler " + (i + 1) + ": " + test.description);
+
+ if (test.actions) {
+ let actionsXML = " actions=\"" + test.actions + "\"";
+ if (test.openURL) {
+ actionsXML += " openURL=\"" + test.openURL + "\"";
+ }
+ writeUpdatesToXMLFile(XML_PREFIX + actionsXML + XML_SUFFIX);
+ } else {
+ writeUpdatesToXMLFile(XML_EMPTY);
+ }
+
+ reloadUpdateManagerData();
+
+ let noOverrideArgs = Cc["@mozilla.org/browser/clh;1"].
+ getService(Ci.nsIBrowserHandler).defaultArgs;
+
+ let overrideArgs = "";
+ if (test.prefURL) {
+ overrideArgs = test.prefURL;
+ } else if (test.openURL) {
+ overrideArgs = test.openURL;
+ }
+
+ if (overrideArgs == "" && noOverrideArgs) {
+ overrideArgs = noOverrideArgs;
+ } else if (noOverrideArgs) {
+ overrideArgs += "|" + noOverrideArgs;
+ }
+
+ if (test.noMstoneChange === undefined) {
+ gPrefService.setCharPref(PREF_MSTONE, "PreviousMilestone");
+ }
+
+ if (test.noPostUpdatePref == undefined) {
+ gPrefService.setBoolPref(PREF_POSTUPDATE, true);
+ }
+
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"].
+ getService(Ci.nsIBrowserHandler).defaultArgs;
+ is(defaultArgs, overrideArgs, "correct value returned by defaultArgs");
+
+ if (test.noMstoneChange === undefined || test.noMstoneChange != true) {
+ let newMstone = gPrefService.getCharPref(PREF_MSTONE);
+ is(originalMstone, newMstone, "preference " + PREF_MSTONE +
+ " should have been updated");
+ }
+
+ if (gPrefService.prefHasUserValue(PREF_POSTUPDATE)) {
+ gPrefService.clearUserPref(PREF_POSTUPDATE);
+ }
+ }
+
+ testShowNotification();
+}
+
+// nsBrowserGlue.js _showUpdateNotification notification tests
+const BG_NOTIFY_TESTS = [
+ {
+ description: "'silent showNotification' actions should not display a notification",
+ actions: "silent showNotification"
+ }, {
+ description: "'showNotification' for actions should display a notification",
+ actions: "showNotification"
+ }, {
+ description: "no actions and empty updates.xml",
+ }, {
+ description: "'showAlert' for actions should not display a notification",
+ actions: "showAlert"
+ }, {
+ // This test MUST be the last test in the array to test opening the url
+ // provided by the updates.xml.
+ description: "'showNotification' for actions with custom notification " +
+ "attributes should display a notification",
+ actions: "showNotification",
+ notificationText: "notification text",
+ notificationURL: DEFAULT_UPDATE_URL,
+ notificationButtonLabel: "button label",
+ notificationButtonAccessKey: "b"
+ }
+];
+
+// Test showing a notification after an update
+// _showUpdateNotification in nsBrowserGlue.js
+function testShowNotification()
+{
+ let notifyBox = document.getElementById("high-priority-global-notificationbox");
+
+ // Catches any windows opened by these tests (e.g. alert windows) and closes
+ // them
+ gWindowCatcher.start();
+
+ for (let i = 0; i < BG_NOTIFY_TESTS.length; i++) {
+ let test = BG_NOTIFY_TESTS[i];
+ ok(true, "Test showNotification " + (i + 1) + ": " + test.description);
+
+ if (test.actions) {
+ let actionsXML = " actions=\"" + test.actions + "\"";
+ if (test.notificationText) {
+ actionsXML += " notificationText=\"" + test.notificationText + "\"";
+ }
+ if (test.notificationURL) {
+ actionsXML += " notificationURL=\"" + test.notificationURL + "\"";
+ }
+ if (test.notificationButtonLabel) {
+ actionsXML += " notificationButtonLabel=\"" + test.notificationButtonLabel + "\"";
+ }
+ if (test.notificationButtonAccessKey) {
+ actionsXML += " notificationButtonAccessKey=\"" + test.notificationButtonAccessKey + "\"";
+ }
+ writeUpdatesToXMLFile(XML_PREFIX + actionsXML + XML_SUFFIX);
+ } else {
+ writeUpdatesToXMLFile(XML_EMPTY);
+ }
+
+ reloadUpdateManagerData();
+ gPrefService.setBoolPref(PREF_POSTUPDATE, true);
+
+ gBG.observe(null, "browser-glue-test", "post-update-notification");
+
+ let updateBox = notifyBox.getNotificationWithValue("post-update-notification");
+ if (test.actions && test.actions.indexOf("showNotification") != -1 &&
+ test.actions.indexOf("silent") == -1) {
+ ok(updateBox, "Update notification box should have been displayed");
+ if (updateBox) {
+ if (test.notificationText) {
+ is(updateBox.label, test.notificationText, "Update notification box " +
+ "should have the label provided by the update");
+ }
+ if (test.notificationButtonLabel) {
+ var button = updateBox.getElementsByTagName("button").item(0);
+ is(button.label, test.notificationButtonLabel, "Update notification " +
+ "box button should have the label provided by the update");
+ if (test.notificationButtonAccessKey) {
+ let accessKey = button.getAttribute("accesskey");
+ is(accessKey, test.notificationButtonAccessKey, "Update " +
+ "notification box button should have the accesskey " +
+ "provided by the update");
+ }
+ }
+ // The last test opens an url and verifies the url from the updates.xml
+ // is correct.
+ if (i == (BG_NOTIFY_TESTS.length - 1)) {
+ // Wait for any windows caught by the windowcatcher to close
+ gWindowCatcher.finish(function () {
+ BrowserTestUtils.waitForNewTab(gBrowser).then(testNotificationURL);
+ button.click();
+ });
+ } else {
+ notifyBox.removeAllNotifications(true);
+ }
+ } else if (i == (BG_NOTIFY_TESTS.length - 1)) {
+ // If updateBox is null the test has already reported errors so bail
+ finish_test();
+ }
+ } else {
+ ok(!updateBox, "Update notification box should not have been displayed");
+ }
+
+ let prefHasUserValue = gPrefService.prefHasUserValue(PREF_POSTUPDATE);
+ is(prefHasUserValue, false, "preference " + PREF_POSTUPDATE +
+ " shouldn't have a user value");
+ }
+}
+
+// Test opening the url provided by the updates.xml in the last test
+function testNotificationURL()
+{
+ ok(true, "Test testNotificationURL: clicking the notification button " +
+ "opened the url specified by the update");
+ let href = gBrowser.currentURI.spec;
+ let expectedURL = BG_NOTIFY_TESTS[BG_NOTIFY_TESTS.length - 1].notificationURL;
+ is(href, expectedURL, "The url opened from the notification should be the " +
+ "url provided by the update");
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish_test();
+}
+
+/* Reloads the update metadata from disk */
+function reloadUpdateManagerData()
+{
+ Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager).
+ QueryInterface(Ci.nsIObserver).observe(null, "um-reload-update-data", "");
+}
+
+
+function writeUpdatesToXMLFile(aText)
+{
+ const PERMS_FILE = 0o644;
+
+ const MODE_WRONLY = 0x02;
+ const MODE_CREATE = 0x08;
+ const MODE_TRUNCATE = 0x20;
+
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("UpdRootD", Ci.nsIFile);
+ file.append("updates.xml");
+ let fos = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ if (!file.exists()) {
+ file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
+ fos.write(aText, aText.length);
+ fos.close();
+}
diff --git a/browser/components/tests/browser/browser_contentpermissionprompt.js b/browser/components/tests/browser/browser_contentpermissionprompt.js
new file mode 100644
index 000000000..054aa22e8
--- /dev/null
+++ b/browser/components/tests/browser/browser_contentpermissionprompt.js
@@ -0,0 +1,166 @@
+/**
+ * These tests test nsBrowserGlue's nsIContentPermissionPrompt
+ * implementation behaviour with various types of
+ * nsIContentPermissionRequests.
+ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Integration.jsm", this);
+
+XPCOMUtils.defineLazyServiceGetter(this, "ContentPermissionPrompt",
+ "@mozilla.org/content-permission/prompt;1",
+ "nsIContentPermissionPrompt");
+
+/**
+ * This is a partial implementation of nsIContentPermissionType.
+ *
+ * @param {string} type
+ * The string defining what type of permission is being requested.
+ * Example: "geo", "desktop-notification".
+ * @return nsIContentPermissionType implementation.
+ */
+function MockContentPermissionType(type) {
+ this.type = type;
+}
+
+MockContentPermissionType.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType]),
+ // We expose the wrappedJSObject so that we can be sure
+ // in some of our tests that we're passing the right
+ // nsIContentPermissionType around.
+ wrappedJSObject: this,
+};
+
+/**
+ * This is a partial implementation of nsIContentPermissionRequest.
+ *
+ * @param {Array<nsIContentPermissionType>} typesArray
+ * The types to assign to this nsIContentPermissionRequest,
+ * in order. You probably want to use MockContentPermissionType.
+ * @return nsIContentPermissionRequest implementation.
+ */
+function MockContentPermissionRequest(typesArray) {
+ this.types = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ for (let type of typesArray) {
+ this.types.appendElement(type, false);
+ }
+}
+
+MockContentPermissionRequest.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
+ // We expose the wrappedJSObject so that we can be sure
+ // in some of our tests that we're passing the right
+ // nsIContentPermissionRequest around.
+ wrappedJSObject: this,
+ // For some of our tests, we want to make sure that the
+ // request is cancelled, so we add some instrumentation here
+ // to check that cancel() is called.
+ cancel() {
+ this.cancelled = true;
+ },
+ cancelled: false,
+};
+
+/**
+ * Tests that if the nsIContentPermissionRequest has an empty
+ * types array, that NS_ERROR_UNEXPECTED is thrown, and the
+ * request is cancelled.
+ */
+add_task(function* test_empty_types() {
+ let mockRequest = new MockContentPermissionRequest([]);
+ Assert.throws(() => { ContentPermissionPrompt.prompt(mockRequest); },
+ /NS_ERROR_UNEXPECTED/,
+ "Should have thrown NS_ERROR_UNEXPECTED.");
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if the nsIContentPermissionRequest has more than
+ * one type, that NS_ERROR_UNEXPECTED is thrown, and the request
+ * is cancelled.
+ */
+add_task(function* test_multiple_types() {
+ let mockRequest = new MockContentPermissionRequest([
+ new MockContentPermissionType("test1"),
+ new MockContentPermissionType("test2"),
+ ]);
+
+ Assert.throws(() => { ContentPermissionPrompt.prompt(mockRequest); },
+ /NS_ERROR_UNEXPECTED/);
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if the nsIContentPermissionRequest has a type that
+ * does not implement nsIContentPermissionType that NS_NOINTERFACE
+ * is thrown, and the request is cancelled.
+ */
+add_task(function* test_not_permission_type() {
+ let mockRequest = new MockContentPermissionRequest([
+ { QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]) },
+ ]);
+
+ Assert.throws(() => { ContentPermissionPrompt.prompt(mockRequest); },
+ /NS_NOINTERFACE/);
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if the nsIContentPermissionRequest is for a type
+ * that is not recognized, that NS_ERROR_FAILURE is thrown and
+ * the request is cancelled.
+ */
+add_task(function* test_unrecognized_type() {
+ let mockRequest = new MockContentPermissionRequest([
+ new MockContentPermissionType("test1"),
+ ]);
+
+ Assert.throws(() => { ContentPermissionPrompt.prompt(mockRequest); },
+ /NS_ERROR_FAILURE/);
+ Assert.ok(mockRequest.cancelled, "Should have cancelled the request.");
+});
+
+/**
+ * Tests that if we meet the minimal requirements for a
+ * nsIContentPermissionRequest, that it will be passed to
+ * ContentPermissionIntegration's createPermissionPrompt
+ * method.
+ */
+add_task(function* test_working_request() {
+ let mockType = new MockContentPermissionType("test-permission-type");
+ let mockRequest = new MockContentPermissionRequest([mockType]);
+
+ // mockPermissionPrompt is what createPermissionPrompt
+ // will return. Returning some kind of object should be
+ // enough to convince nsBrowserGlue that everything went
+ // okay.
+ let didPrompt = false;
+ let mockPermissionPrompt = {
+ prompt() {
+ didPrompt = true;
+ }
+ };
+
+ let integration = (base) => ({
+ createPermissionPrompt(type, request) {
+ Assert.equal(type, "test-permission-type");
+ Assert.ok(Object.is(request.wrappedJSObject, mockRequest.wrappedJSObject));
+ return mockPermissionPrompt;
+ },
+ });
+
+ // Register an integration so that we can capture the
+ // calls into ContentPermissionIntegration.
+ try {
+ Integration.contentPermission.register(integration);
+
+ ContentPermissionPrompt.prompt(mockRequest);
+ Assert.ok(!mockRequest.cancelled,
+ "Should not have cancelled the request.");
+ Assert.ok(didPrompt, "Should have tried to show the prompt");
+ } finally {
+ Integration.contentPermission.unregister(integration);
+ }
+});
diff --git a/browser/components/tests/unit/.eslintrc.js b/browser/components/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..fee088c17
--- /dev/null
+++ b/browser/components/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/tests/unit/data/engine-de-DE.xml b/browser/components/tests/unit/data/engine-de-DE.xml
new file mode 100644
index 000000000..b9fa0a464
--- /dev/null
+++ b/browser/components/tests/unit/data/engine-de-DE.xml
@@ -0,0 +1,8 @@
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Google</ShortName>
+<Description>override-de-DE</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="GET" template="http://searchtest.local">
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/components/tests/unit/distribution.ini b/browser/components/tests/unit/distribution.ini
new file mode 100644
index 000000000..d7d298808
--- /dev/null
+++ b/browser/components/tests/unit/distribution.ini
@@ -0,0 +1,58 @@
+# Distribution Configuration File
+# Test of distribution preferences
+
+[Global]
+id=disttest
+version=1.0
+about=Test distribution file
+about.en-US=Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè
+
+[Preferences]
+distribution.test.string="Test String"
+distribution.test.string.noquotes=Test String
+distribution.test.int=777
+distribution.test.bool.true=true
+distribution.test.bool.false=false
+distribution.test.empty=
+
+distribution.test.pref.locale="%LOCALE%"
+distribution.test.pref.language.reset="Preference Set"
+distribution.test.pref.locale.reset="Preference Set"
+distribution.test.pref.locale.set="Preference Set"
+distribution.test.pref.language.set="Preference Set"
+
+[Preferences-en]
+distribution.test.pref.language.en="en"
+distribution.test.pref.language.reset=
+distribution.test.pref.language.set="Language Set"
+distribution.test.pref.locale.set="Language Set"
+
+[Preferences-en-US]
+distribution.test.pref.locale.en-US="en-US"
+distribution.test.pref.locale.reset=
+distribution.test.pref.locale.set="Locale Set"
+
+
+[Preferences-de]
+distribution.test.pref.language.de="de"
+
+[LocalizablePreferences]
+distribution.test.locale="%LOCALE%"
+distribution.test.language.reset="Preference Set"
+distribution.test.locale.reset="Preference Set"
+distribution.test.locale.set="Preference Set"
+distribution.test.language.set="Preference Set"
+
+[LocalizablePreferences-en]
+distribution.test.language.en="en"
+distribution.test.language.reset=
+distribution.test.language.set="Language Set"
+distribution.test.locale.set="Language Set"
+
+[LocalizablePreferences-en-US]
+distribution.test.locale.en-US="en-US"
+distribution.test.locale.reset=
+distribution.test.locale.set="Locale Set"
+
+[LocalizablePreferences-de]
+distribution.test.language.de="de"
diff --git a/browser/components/tests/unit/head.js b/browser/components/tests/unit/head.js
new file mode 100644
index 000000000..3d4e23452
--- /dev/null
+++ b/browser/components/tests/unit/head.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/. */
+
+const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var gProfD = do_get_profile().QueryInterface(Ci.nsILocalFile);
diff --git a/browser/components/tests/unit/test_browserGlue_migration_loop_cleanup.js b/browser/components/tests/unit/test_browserGlue_migration_loop_cleanup.js
new file mode 100644
index 000000000..a68503db3
--- /dev/null
+++ b/browser/components/tests/unit/test_browserGlue_migration_loop_cleanup.js
@@ -0,0 +1,32 @@
+const UI_VERSION = 41;
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+const TOPICDATA_BROWSERGLUE_TEST = "force-ui-migration";
+
+var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"]
+ .getService(Ci.nsIObserver);
+
+Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
+
+add_task(function* test_check_cleanup_loop_prefs() {
+ Services.prefs.setBoolPref("loop.createdRoom", true);
+ Services.prefs.setBoolPref("loop1.createdRoom", true);
+ Services.prefs.setBoolPref("loo.createdRoom", true);
+
+ // Simulate a migration.
+ gBrowserGlue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_BROWSERGLUE_TEST);
+
+ Assert.throws(() => Services.prefs.getBoolPref("loop.createdRoom"),
+ /NS_ERROR_UNEXPECTED/,
+ "should have cleared old loop preference 'loop.createdRoom'");
+ Assert.ok(Services.prefs.getBoolPref("loop1.createdRoom"),
+ "should have left non-loop pref 'loop1.createdRoom' untouched");
+ Assert.ok(Services.prefs.getBoolPref("loo.createdRoom"),
+ "should have left non-loop pref 'loo.createdRoom' untouched");
+});
+
+do_register_cleanup(() => {
+ Services.prefs.clearUserPref("browser.migration.version");
+ Services.prefs.clearUserPref("loop.createdRoom");
+ Services.prefs.clearUserPref("loop1.createdRoom");
+ Services.prefs.clearUserPref("loo.createdRoom");
+});
diff --git a/browser/components/tests/unit/test_distribution.js b/browser/components/tests/unit/test_distribution.js
new file mode 100644
index 000000000..183ab85d6
--- /dev/null
+++ b/browser/components/tests/unit/test_distribution.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that preferences are properly set by distribution.ini
+ */
+
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+
+// Import common head.
+var commonFile = do_get_file("../../../../toolkit/components/places/tests/head_common.js", false);
+/* import-globals-from ../../../../toolkit/components/places/tests/head_common.js */
+if (commonFile) {
+ let uri = Services.io.newFileURI(commonFile);
+ Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization";
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+
+/**
+ * Copy the engine-distribution.xml engine to a fake distribution
+ * created in the profile, and registered with the directory service.
+ * Create an empty en-US directory to make sure it isn't used.
+ */
+function installDistributionEngine() {
+ const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
+
+ let dir = gProfD.clone();
+ dir.append("distribution");
+ let distDir = dir.clone();
+
+ dir.append("searchplugins");
+ dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+ dir.append("locale");
+ dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ let localeDir = dir.clone();
+
+ dir.append("en-US");
+ dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+ localeDir.append("de-DE");
+ localeDir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+ do_get_file("data/engine-de-DE.xml").copyTo(localeDir, "engine-de-DE.xml");
+
+ Services.dirsvc.registerProvider({
+ getFile: function(aProp, aPersistent) {
+ aPersistent.value = true;
+ if (aProp == XRE_APP_DISTRIBUTION_DIR)
+ return distDir.clone();
+ return null;
+ }
+ });
+}
+
+function run_test() {
+ // Set special pref to load distribution.ini from the profile folder.
+ Services.prefs.setBoolPref("distribution.testing.loadFromProfile", true);
+
+ // Copy distribution.ini file to the profile dir.
+ let distroDir = gProfD.clone();
+ distroDir.leafName = "distribution";
+ let iniFile = distroDir.clone();
+ iniFile.append("distribution.ini");
+ if (iniFile.exists()) {
+ iniFile.remove(false);
+ print("distribution.ini already exists, did some test forget to cleanup?");
+ }
+
+ let testDistributionFile = gTestDir.clone();
+ testDistributionFile.append("distribution.ini");
+ testDistributionFile.copyTo(distroDir, "distribution.ini");
+ Assert.ok(testDistributionFile.exists());
+
+ installDistributionEngine();
+
+ run_next_test();
+}
+
+do_register_cleanup(function () {
+ // Remove the distribution dir, even if the test failed, otherwise all
+ // next tests will use it.
+ let distDir = gProfD.clone();
+ distDir.append("distribution");
+ distDir.remove(true);
+ Assert.ok(!distDir.exists());
+});
+
+add_task(function* () {
+ // Force distribution.
+ let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
+ glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
+
+ var defaultBranch = Services.prefs.getDefaultBranch(null);
+
+ Assert.equal(defaultBranch.getCharPref("distribution.id"), "disttest");
+ Assert.equal(defaultBranch.getCharPref("distribution.version"), "1.0");
+ Assert.equal(defaultBranch.getComplexValue("distribution.about", Ci.nsISupportsString).data, "Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè");
+
+ Assert.equal(defaultBranch.getCharPref("distribution.test.string"), "Test String");
+ Assert.equal(defaultBranch.getCharPref("distribution.test.string.noquotes"), "Test String");
+ Assert.equal(defaultBranch.getIntPref("distribution.test.int"), 777);
+ Assert.equal(defaultBranch.getBoolPref("distribution.test.bool.true"), true);
+ Assert.equal(defaultBranch.getBoolPref("distribution.test.bool.false"), false);
+
+ Assert.throws(() => defaultBranch.getCharPref("distribution.test.empty"));
+ Assert.throws(() => defaultBranch.getIntPref("distribution.test.empty"));
+ Assert.throws(() => defaultBranch.getBoolPref("distribution.test.empty"));
+
+ Assert.equal(defaultBranch.getCharPref("distribution.test.pref.locale"), "en-US");
+ Assert.equal(defaultBranch.getCharPref("distribution.test.pref.language.en"), "en");
+ Assert.equal(defaultBranch.getCharPref("distribution.test.pref.locale.en-US"), "en-US");
+ Assert.throws(() => defaultBranch.getCharPref("distribution.test.pref.language.de"));
+ // This value was never set because of the empty language specific pref
+ Assert.throws(() => defaultBranch.getCharPref("distribution.test.pref.language.reset"));
+ // This value was never set because of the empty locale specific pref
+ Assert.throws(() => defaultBranch.getCharPref("distribution.test.pref.locale.reset"));
+ // This value was overridden by a locale specific setting
+ Assert.equal(defaultBranch.getCharPref("distribution.test.pref.locale.set"), "Locale Set");
+ // This value was overridden by a language specific setting
+ Assert.equal(defaultBranch.getCharPref("distribution.test.pref.language.set"), "Language Set");
+ // Language should not override locale
+ Assert.notEqual(defaultBranch.getCharPref("distribution.test.pref.locale.set"), "Language Set");
+
+ Assert.equal(defaultBranch.getComplexValue("distribution.test.locale", Ci.nsIPrefLocalizedString).data, "en-US");
+ Assert.equal(defaultBranch.getComplexValue("distribution.test.language.en", Ci.nsIPrefLocalizedString).data, "en");
+ Assert.equal(defaultBranch.getComplexValue("distribution.test.locale.en-US", Ci.nsIPrefLocalizedString).data, "en-US");
+ Assert.throws(() => defaultBranch.getComplexValue("distribution.test.language.de", Ci.nsIPrefLocalizedString));
+ // This value was never set because of the empty language specific pref
+ Assert.throws(() => defaultBranch.getComplexValue("distribution.test.language.reset", Ci.nsIPrefLocalizedString));
+ // This value was never set because of the empty locale specific pref
+ Assert.throws(() => defaultBranch.getComplexValue("distribution.test.locale.reset", Ci.nsIPrefLocalizedString));
+ // This value was overridden by a locale specific setting
+ Assert.equal(defaultBranch.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Locale Set");
+ // This value was overridden by a language specific setting
+ Assert.equal(defaultBranch.getComplexValue("distribution.test.language.set", Ci.nsIPrefLocalizedString).data, "Language Set");
+ // Language should not override locale
+ Assert.notEqual(defaultBranch.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Language Set");
+
+ do_test_pending();
+
+ Services.prefs.setCharPref("distribution.searchplugins.defaultLocale", "de-DE");
+
+ Services.search.init(function() {
+ Assert.equal(Services.search.isInitialized, true);
+ var engine = Services.search.getEngineByName("Google");
+ Assert.equal(engine.description, "override-de-DE");
+ do_test_finished();
+ });
+});
diff --git a/browser/components/tests/unit/xpcshell.ini b/browser/components/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..c2f461966
--- /dev/null
+++ b/browser/components/tests/unit/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+head = head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ distribution.ini
+ data/engine-de-DE.xml
+
+[test_distribution.js]
+[test_browserGlue_migration_loop_cleanup.js]
diff --git a/browser/components/translation/BingTranslator.jsm b/browser/components/translation/BingTranslator.jsm
new file mode 100644
index 000000000..fc1cc942a
--- /dev/null
+++ b/browser/components/translation/BingTranslator.jsm
@@ -0,0 +1,449 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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;
+
+this.EXPORTED_SYMBOLS = [ "BingTranslator" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Http.jsm");
+
+// The maximum amount of net data allowed per request on Bing's API.
+const MAX_REQUEST_DATA = 5000; // Documentation says 10000 but anywhere
+ // close to that is refused by the service.
+
+// The maximum number of chunks allowed to be translated in a single
+// request.
+const MAX_REQUEST_CHUNKS = 1000; // Documentation says 2000.
+
+// Self-imposed limit of 15 requests. This means that a page that would need
+// to be broken in more than 15 requests won't be fully translated.
+// The maximum amount of data that we will translate for a single page
+// is MAX_REQUESTS * MAX_REQUEST_DATA.
+const MAX_REQUESTS = 15;
+
+/**
+ * Translates a webpage using Bing's Translation API.
+ *
+ * @param translationDocument The TranslationDocument object that represents
+ * the webpage to be translated
+ * @param sourceLanguage The source language of the document
+ * @param targetLanguage The target language for the translation
+ *
+ * @returns {Promise} A promise that will resolve when the translation
+ * task is finished.
+ */
+this.BingTranslator = function(translationDocument, sourceLanguage, targetLanguage) {
+ this.translationDocument = translationDocument;
+ this.sourceLanguage = sourceLanguage;
+ this.targetLanguage = targetLanguage;
+ this._pendingRequests = 0;
+ this._partialSuccess = false;
+ this._serviceUnavailable = false;
+ this._translatedCharacterCount = 0;
+};
+
+this.BingTranslator.prototype = {
+ /**
+ * Performs the translation, splitting the document into several chunks
+ * respecting the data limits of the API.
+ *
+ * @returns {Promise} A promise that will resolve when the translation
+ * task is finished.
+ */
+ translate: function() {
+ return Task.spawn(function *() {
+ let currentIndex = 0;
+ this._onFinishedDeferred = Promise.defer();
+
+ // Let's split the document into various requests to be sent to
+ // Bing's Translation API.
+ for (let requestCount = 0; requestCount < MAX_REQUESTS; requestCount++) {
+ // Generating the text for each request can be expensive, so
+ // let's take the opportunity of the chunkification process to
+ // allow for the event loop to attend other pending events
+ // before we continue.
+ yield CommonUtils.laterTickResolvingPromise();
+
+ // Determine the data for the next request.
+ let request = this._generateNextTranslationRequest(currentIndex);
+
+ // Create a real request to the server, and put it on the
+ // pending requests list.
+ let bingRequest = new BingRequest(request.data,
+ this.sourceLanguage,
+ this.targetLanguage);
+ this._pendingRequests++;
+ bingRequest.fireRequest().then(this._chunkCompleted.bind(this),
+ this._chunkFailed.bind(this));
+
+ currentIndex = request.lastIndex;
+ if (request.finished) {
+ break;
+ }
+ }
+
+ return this._onFinishedDeferred.promise;
+ }.bind(this));
+ },
+
+ /**
+ * Resets the expiration time of the current token, in order to
+ * force the token manager to ask for a new token during the next request.
+ */
+ _resetToken : function() {
+ // Force the token manager to get update token
+ BingTokenManager._currentExpiryTime = 0;
+ },
+
+ /**
+ * Function called when a request sent to the server completed successfully.
+ * This function handles calling the function to parse the result and the
+ * function to resolve the promise returned by the public `translate()`
+ * method when there's no pending request left.
+ *
+ * @param request The BingRequest sent to the server.
+ */
+ _chunkCompleted: function(bingRequest) {
+ if (this._parseChunkResult(bingRequest)) {
+ this._partialSuccess = true;
+ // Count the number of characters successfully translated.
+ this._translatedCharacterCount += bingRequest.characterCount;
+ }
+
+ this._checkIfFinished();
+ },
+
+ /**
+ * Function called when a request sent to the server has failed.
+ * This function handles deciding if the error is transient or means the
+ * service is unavailable (zero balance on the key or request credentials are
+ * not in an active state) and calling the function to resolve the promise
+ * returned by the public `translate()` method when there's no pending.
+ * request left.
+ *
+ * @param aError [optional] The XHR object of the request that failed.
+ */
+ _chunkFailed: function(aError) {
+ if (aError instanceof Ci.nsIXMLHttpRequest &&
+ [400, 401].indexOf(aError.status) != -1) {
+ let body = aError.responseText;
+ if (body && body.includes("TranslateApiException") &&
+ (body.includes("balance") || body.includes("active state")))
+ this._serviceUnavailable = true;
+ }
+
+ this._checkIfFinished();
+ },
+
+ /**
+ * Function called when a request sent to the server has completed.
+ * This function handles resolving the promise
+ * returned by the public `translate()` method when all chunks are completed.
+ */
+ _checkIfFinished: function() {
+ // Check if all pending requests have been
+ // completed and then resolves the promise.
+ // If at least one chunk was successful, the
+ // promise will be resolved positively which will
+ // display the "Success" state for the infobar. Otherwise,
+ // the "Error" state will appear.
+ if (--this._pendingRequests == 0) {
+ if (this._partialSuccess) {
+ this._onFinishedDeferred.resolve({
+ characterCount: this._translatedCharacterCount
+ });
+ } else {
+ let error = this._serviceUnavailable ? "unavailable" : "failure";
+ this._onFinishedDeferred.reject(error);
+ }
+ }
+ },
+
+ /**
+ * This function parses the result returned by Bing's Http.svc API,
+ * which is a XML file that contains a number of elements. To our
+ * particular interest, the only part of the response that matters
+ * are the <TranslatedText> nodes, which contains the resulting
+ * items that were sent to be translated.
+ *
+ * @param request The request sent to the server.
+ * @returns boolean True if parsing of this chunk was successful.
+ */
+ _parseChunkResult: function(bingRequest) {
+ let results;
+ try {
+ let doc = bingRequest.networkRequest.responseXML;
+ results = doc.querySelectorAll("TranslatedText");
+ } catch (e) {
+ return false;
+ }
+
+ let len = results.length;
+ if (len != bingRequest.translationData.length) {
+ // This should never happen, but if the service returns a different number
+ // of items (from the number of items submitted), we can't use this chunk
+ // because all items would be paired incorrectly.
+ return false;
+ }
+
+ let error = false;
+ for (let i = 0; i < len; i++) {
+ try {
+ let result = results[i].firstChild.nodeValue;
+ let root = bingRequest.translationData[i][0];
+
+ if (root.isSimpleRoot) {
+ // Workaround for Bing's service problem in which "&" chars in
+ // plain-text TranslationItems are double-escaped.
+ result = result.replace(/&amp;/g, "&");
+ }
+
+ root.parseResult(result);
+ } catch (e) { error = true; }
+ }
+
+ return !error;
+ },
+
+ /**
+ * This function will determine what is the data to be used for
+ * the Nth request we are generating, based on the input params.
+ *
+ * @param startIndex What is the index, in the roots list, that the
+ * chunk should start.
+ */
+ _generateNextTranslationRequest: function(startIndex) {
+ let currentDataSize = 0;
+ let currentChunks = 0;
+ let output = [];
+ let rootsList = this.translationDocument.roots;
+
+ for (let i = startIndex; i < rootsList.length; i++) {
+ let root = rootsList[i];
+ let text = this.translationDocument.generateTextForItem(root);
+ if (!text) {
+ continue;
+ }
+
+ text = escapeXML(text);
+ let newCurSize = currentDataSize + text.length;
+ let newChunks = currentChunks + 1;
+
+ if (newCurSize > MAX_REQUEST_DATA ||
+ newChunks > MAX_REQUEST_CHUNKS) {
+
+ // If we've reached the API limits, let's stop accumulating data
+ // for this request and return. We return information useful for
+ // the caller to pass back on the next call, so that the function
+ // can keep working from where it stopped.
+ return {
+ data: output,
+ finished: false,
+ lastIndex: i
+ };
+ }
+
+ currentDataSize = newCurSize;
+ currentChunks = newChunks;
+ output.push([root, text]);
+ }
+
+ return {
+ data: output,
+ finished: true,
+ lastIndex: 0
+ };
+ }
+};
+
+/**
+ * Represents a request (for 1 chunk) sent off to Bing's service.
+ *
+ * @params translationData The data to be used for this translation,
+ * generated by the generateNextTranslationRequest...
+ * function.
+ * @param sourceLanguage The source language of the document.
+ * @param targetLanguage The target language for the translation.
+ *
+ */
+function BingRequest(translationData, sourceLanguage, targetLanguage) {
+ this.translationData = translationData;
+ this.sourceLanguage = sourceLanguage;
+ this.targetLanguage = targetLanguage;
+ this.characterCount = 0;
+}
+
+BingRequest.prototype = {
+ /**
+ * Initiates the request
+ */
+ fireRequest: function() {
+ return Task.spawn(function *() {
+ // Prepare authentication.
+ let token = yield BingTokenManager.getToken();
+ let auth = "Bearer " + token;
+
+ // Prepare URL.
+ let url = getUrlParam("https://api.microsofttranslator.com/v2/Http.svc/TranslateArray",
+ "browser.translation.bing.translateArrayURL");
+
+ // Prepare request headers.
+ let headers = [["Content-type", "text/xml"], ["Authorization", auth]];
+
+ // Prepare the request body.
+ let requestString =
+ '<TranslateArrayRequest>' +
+ '<AppId/>' +
+ '<From>' + this.sourceLanguage + '</From>' +
+ '<Options>' +
+ '<ContentType xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2">text/html</ContentType>' +
+ '<ReservedFlags xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" />' +
+ '</Options>' +
+ '<Texts xmlns:s="http://schemas.microsoft.com/2003/10/Serialization/Arrays">';
+
+ for (let [, text] of this.translationData) {
+ requestString += '<s:string>' + text + '</s:string>';
+ this.characterCount += text.length;
+ }
+
+ requestString += '</Texts>' +
+ '<To>' + this.targetLanguage + '</To>' +
+ '</TranslateArrayRequest>';
+
+ // Set up request options.
+ let deferred = Promise.defer();
+ let options = {
+ onLoad: (function(responseText, xhr) {
+ deferred.resolve(this);
+ }).bind(this),
+ onError: function(e, responseText, xhr) {
+ deferred.reject(xhr);
+ },
+ postData: requestString,
+ headers: headers
+ };
+
+ // Fire the request.
+ let request = httpRequest(url, options);
+
+ // Override the response MIME type.
+ request.overrideMimeType("text/xml");
+ this.networkRequest = request;
+ return deferred.promise;
+ }.bind(this));
+ }
+};
+
+/**
+ * Authentication Token manager for the API
+ */
+var BingTokenManager = {
+ _currentToken: null,
+ _currentExpiryTime: 0,
+ _pendingRequest: null,
+
+ /**
+ * Get a valid, non-expired token to be used for the API calls.
+ *
+ * @returns {Promise} A promise that resolves with the token
+ * string once it is obtained. The token returned
+ * can be the same one used in the past if it is still
+ * valid.
+ */
+ getToken: function() {
+ if (this._pendingRequest) {
+ return this._pendingRequest;
+ }
+
+ let remainingMs = this._currentExpiryTime - new Date();
+ // Our existing token is still good for more than a minute, let's use it.
+ if (remainingMs > 60 * 1000) {
+ return Promise.resolve(this._currentToken);
+ }
+
+ return this._getNewToken();
+ },
+
+ /**
+ * Generates a new token from the server.
+ *
+ * @returns {Promise} A promise that resolves with the token
+ * string once it is obtained.
+ */
+ _getNewToken: function() {
+ let url = getUrlParam("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13",
+ "browser.translation.bing.authURL");
+ let params = [
+ ["grant_type", "client_credentials"],
+ ["scope", "http://api.microsofttranslator.com"],
+ ["client_id",
+ getUrlParam("%BING_API_CLIENTID%", "browser.translation.bing.clientIdOverride")],
+ ["client_secret",
+ getUrlParam("%BING_API_KEY%", "browser.translation.bing.apiKeyOverride")]
+ ];
+
+ let deferred = Promise.defer();
+ let options = {
+ onLoad: function(responseText, xhr) {
+ BingTokenManager._pendingRequest = null;
+ try {
+ let json = JSON.parse(responseText);
+
+ if (json.error) {
+ deferred.reject(json.error);
+ return;
+ }
+
+ let token = json.access_token;
+ let expires_in = json.expires_in;
+ BingTokenManager._currentToken = token;
+ BingTokenManager._currentExpiryTime = new Date(Date.now() + expires_in * 1000);
+ deferred.resolve(token);
+ } catch (e) {
+ deferred.reject(e);
+ }
+ },
+ onError: function(e, responseText, xhr) {
+ BingTokenManager._pendingRequest = null;
+ deferred.reject(e);
+ },
+ postData: params
+ };
+
+ this._pendingRequest = deferred.promise;
+ httpRequest(url, options);
+
+ return deferred.promise;
+ }
+};
+
+/**
+ * Escape a string to be valid XML content.
+ */
+function escapeXML(aStr) {
+ return aStr.toString()
+ .replace(/&/g, "&amp;")
+ .replace(/\"/g, "&quot;")
+ .replace(/\'/g, "&apos;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;");
+}
+
+/**
+ * Fetch an auth token (clientID or client secret), which may be overridden by
+ * a pref if it's set.
+ */
+function getUrlParam(paramValue, prefName) {
+ if (Services.prefs.getPrefType(prefName))
+ paramValue = Services.prefs.getCharPref(prefName);
+ paramValue = Services.urlFormatter.formatURL(paramValue);
+ return paramValue;
+}
diff --git a/browser/components/translation/LanguageDetector.jsm b/browser/components/translation/LanguageDetector.jsm
new file mode 100644
index 000000000..a65d6eda1
--- /dev/null
+++ b/browser/components/translation/LanguageDetector.jsm
@@ -0,0 +1,143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["LanguageDetector"];
+
+Components.utils.import("resource://gre/modules/Timer.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Since Emscripten can handle heap growth, but not heap shrinkage, we
+// need to refresh the worker after we've processed a particularly large
+// string in order to prevent unnecessary resident memory growth.
+//
+// These values define the cut-off string length and the idle timeout
+// (in milliseconds) before destroying a worker. Once a string of the
+// maximum size has been processed, the worker is marked for
+// destruction, and is terminated as soon as it has been idle for the
+// given timeout.
+//
+// 1.5MB. This is the approximate string length that forces heap growth
+// for a 2MB heap.
+var LARGE_STRING = 1.5 * 1024 * 1024;
+var IDLE_TIMEOUT = 10 * 1000;
+
+const WORKER_URL = "resource:///modules/translation/cld-worker.js";
+
+var workerManager = {
+ detectionQueue: [],
+
+ detectLanguage(aParams) {
+ return this.workerReady.then(worker => {
+ return new Promise(resolve => {
+ this.detectionQueue.push({resolve});
+ worker.postMessage(aParams);
+ });
+ }).then(result => {
+ // We have our asynchronous result from the worker.
+ //
+ // Determine if our input was large enough to trigger heap growth,
+ // or if we're already waiting to destroy the worker when it's
+ // idle. If so, schedule termination after the idle timeout.
+ if (aParams.text.length >= LARGE_STRING || this._idleTimeout != null)
+ this.flushWorker();
+
+ return result;
+ })
+ },
+
+ _worker: null,
+ _workerReadyPromise: null,
+
+ get workerReady() {
+ if (!this._workerReadyPromise)
+ this._workerReadyPromise = new Promise(resolve => {
+ let worker = new Worker(WORKER_URL);
+ worker.onmessage = (aMsg) => {
+ if (aMsg.data == "ready")
+ resolve(worker);
+ else
+ this.detectionQueue.shift().resolve(aMsg.data);
+ };
+ this._worker = worker;
+ });
+
+ return this._workerReadyPromise;
+ },
+
+ // Holds the ID of the current pending idle cleanup setTimeout.
+ _idleTimeout: null,
+
+ // Schedule the current worker to be terminated after the idle timeout.
+ flushWorker() {
+ if (this._idleTimeout != null)
+ clearTimeout(this._idleTimeout);
+
+ this._idleTimeout = setTimeout(this._flushWorker.bind(this), IDLE_TIMEOUT);
+ },
+
+ // Immediately terminate the worker, as long as there no pending
+ // results. Otherwise, reschedule termination until after the next
+ // idle timeout.
+ _flushWorker() {
+ if (this.detectionQueue.length)
+ this.flushWorker();
+ else {
+ if (this._worker)
+ this._worker.terminate();
+
+ this._worker = null;
+ this._workerReadyPromise = null;
+ this._idleTimeout = null;
+ }
+ },
+};
+
+this.LanguageDetector = {
+ /**
+ * Detect the language of a given string.
+ *
+ * The argument may be either a string containing the text to analyze,
+ * or an object with the following properties:
+ *
+ * - 'text' The text to analyze.
+ *
+ * - 'isHTML' (optional) A boolean, indicating whether the text
+ * should be analyzed as HTML rather than plain text.
+ *
+ * - 'language' (optional) A string indicating the expected language.
+ * For text extracted from HTTP documents, this is expected to
+ * come from the Content-Language header.
+ *
+ * - 'tld' (optional) A string indicating the top-level domain of the
+ * document the text was extracted from.
+ *
+ * - 'encoding' (optional) A string describing the encoding of the
+ * document the string was extracted from. Note that, regardless
+ * of the value of this property, the 'text' property must be a
+ * UTF-16 JavaScript string.
+ *
+ * @returns {Promise<Object>}
+ * @resolves When detection is finished, with a object containing
+ * these fields:
+ * - 'language' (string with a language code)
+ * - 'confident' (boolean) Whether the detector is confident of the
+ * result.
+ * - 'languages' (array) An array of up to three elements, containing
+ * the most prevalent languages detected. It contains a
+ * 'languageCode' property, containing the ISO language code of
+ * the language, and a 'percent' property, describing the
+ * approximate percentage of the input which is in that language.
+ * For text of an unknown language, the result may contain an
+ * entry with the languge code 'un', indicating the percent of
+ * the text which is unknown.
+ */
+ detectLanguage: function(aParams) {
+ if (typeof aParams == "string")
+ aParams = { text: aParams };
+
+ return workerManager.detectLanguage(aParams);
+ },
+};
diff --git a/browser/components/translation/Translation.jsm b/browser/components/translation/Translation.jsm
new file mode 100644
index 000000000..15a847c13
--- /dev/null
+++ b/browser/components/translation/Translation.jsm
@@ -0,0 +1,446 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = [
+ "Translation",
+ "TranslationTelemetry",
+];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const TRANSLATION_PREF_SHOWUI = "browser.translation.ui.show";
+const TRANSLATION_PREF_DETECT_LANG = "browser.translation.detectLanguage";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm", this);
+
+this.Translation = {
+ STATE_OFFER: 0,
+ STATE_TRANSLATING: 1,
+ STATE_TRANSLATED: 2,
+ STATE_ERROR: 3,
+ STATE_UNAVAILABLE: 4,
+
+ serviceUnavailable: false,
+
+ supportedSourceLanguages: ["bg", "cs", "de", "en", "es", "fr", "ja", "ko", "nl", "no", "pl", "pt", "ru", "tr", "vi", "zh"],
+ supportedTargetLanguages: ["bg", "cs", "de", "en", "es", "fr", "ja", "ko", "nl", "no", "pl", "pt", "ru", "tr", "vi", "zh"],
+
+ _defaultTargetLanguage: "",
+ get defaultTargetLanguage() {
+ if (!this._defaultTargetLanguage) {
+ this._defaultTargetLanguage = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global")
+ .split("-")[0];
+ }
+ return this._defaultTargetLanguage;
+ },
+
+ documentStateReceived: function(aBrowser, aData) {
+ if (aData.state == this.STATE_OFFER) {
+ if (aData.detectedLanguage == this.defaultTargetLanguage) {
+ // Detected language is the same as the user's locale.
+ return;
+ }
+
+ if (this.supportedSourceLanguages.indexOf(aData.detectedLanguage) == -1) {
+ // Detected language is not part of the supported languages.
+ TranslationTelemetry.recordMissedTranslationOpportunity(aData.detectedLanguage);
+ return;
+ }
+
+ TranslationTelemetry.recordTranslationOpportunity(aData.detectedLanguage);
+ }
+
+ if (!Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI))
+ return;
+
+ if (!aBrowser.translationUI)
+ aBrowser.translationUI = new TranslationUI(aBrowser);
+ let trUI = aBrowser.translationUI;
+
+ // Set all values before showing a new translation infobar.
+ trUI._state = Translation.serviceUnavailable ? Translation.STATE_UNAVAILABLE
+ : aData.state;
+ trUI.detectedLanguage = aData.detectedLanguage;
+ trUI.translatedFrom = aData.translatedFrom;
+ trUI.translatedTo = aData.translatedTo;
+ trUI.originalShown = aData.originalShown;
+
+ trUI.showURLBarIcon();
+
+ if (trUI.shouldShowInfoBar(aBrowser.currentURI))
+ trUI.showTranslationInfoBar();
+ },
+
+ openProviderAttribution: function() {
+ let attribution = this.supportedEngines[this.translationEngine];
+ Cu.import("resource:///modules/RecentWindow.jsm");
+ RecentWindow.getMostRecentBrowserWindow().openUILinkIn(attribution, "tab");
+ },
+
+ /**
+ * The list of translation engines and their attributions.
+ */
+ supportedEngines: {
+ "bing" : "http://aka.ms/MicrosoftTranslatorAttribution",
+ "yandex" : "http://translate.yandex.com/"
+ },
+
+ /**
+ * Fallback engine (currently Bing Translator) if the preferences seem
+ * confusing.
+ */
+ get defaultEngine() {
+ return this.supportedEngines.keys[0];
+ },
+
+ /**
+ * Returns the name of the preferred translation engine.
+ */
+ get translationEngine() {
+ let engine = Services.prefs.getCharPref("browser.translation.engine");
+ return Object.keys(this.supportedEngines).indexOf(engine) == -1 ? this.defaultEngine : engine;
+ },
+};
+
+/* TranslationUI objects keep the information related to translation for
+ * a specific browser. This object is passed to the translation
+ * infobar so that it can initialize itself. The properties exposed to
+ * the infobar are:
+ * - detectedLanguage, code of the language detected on the web page.
+ * - state, the state in which the infobar should be displayed
+ * - translatedFrom, if already translated, source language code.
+ * - translatedTo, if already translated, target language code.
+ * - translate, method starting the translation of the current page.
+ * - showOriginalContent, method showing the original page content.
+ * - showTranslatedContent, method showing the translation for an
+ * already translated page whose original content is shown.
+ * - originalShown, boolean indicating if the original or translated
+ * version of the page is shown.
+ */
+function TranslationUI(aBrowser) {
+ this.browser = aBrowser;
+}
+
+TranslationUI.prototype = {
+ get browser() {
+ return this._browser;
+ },
+ set browser(aBrowser) {
+ if (this._browser)
+ this._browser.messageManager.removeMessageListener("Translation:Finished", this);
+ aBrowser.messageManager.addMessageListener("Translation:Finished", this);
+ this._browser = aBrowser;
+ },
+ translate: function(aFrom, aTo) {
+ if (aFrom == aTo ||
+ (this.state == Translation.STATE_TRANSLATED &&
+ this.translatedFrom == aFrom && this.translatedTo == aTo)) {
+ // Nothing to do.
+ return;
+ }
+
+ if (this.state == Translation.STATE_OFFER) {
+ if (this.detectedLanguage != aFrom)
+ TranslationTelemetry.recordDetectedLanguageChange(true);
+ } else {
+ if (this.translatedFrom != aFrom)
+ TranslationTelemetry.recordDetectedLanguageChange(false);
+ if (this.translatedTo != aTo)
+ TranslationTelemetry.recordTargetLanguageChange();
+ }
+
+ this.state = Translation.STATE_TRANSLATING;
+ this.translatedFrom = aFrom;
+ this.translatedTo = aTo;
+
+ this.browser.messageManager.sendAsyncMessage(
+ "Translation:TranslateDocument",
+ { from: aFrom, to: aTo }
+ );
+ },
+
+ showURLBarIcon: function() {
+ let chromeWin = this.browser.ownerGlobal;
+ let PopupNotifications = chromeWin.PopupNotifications;
+ let removeId = this.originalShown ? "translated" : "translate";
+ let notification =
+ PopupNotifications.getNotification(removeId, this.browser);
+ if (notification)
+ PopupNotifications.remove(notification);
+
+ let callback = (aTopic, aNewBrowser) => {
+ if (aTopic == "swapping") {
+ let infoBarVisible =
+ this.notificationBox.getNotificationWithValue("translation");
+ aNewBrowser.translationUI = this;
+ this.browser = aNewBrowser;
+ if (infoBarVisible)
+ this.showTranslationInfoBar();
+ return true;
+ }
+
+ if (aTopic != "showing")
+ return false;
+ let notification = this.notificationBox.getNotificationWithValue("translation");
+ if (notification)
+ notification.close();
+ else
+ this.showTranslationInfoBar();
+ return true;
+ };
+
+ let addId = this.originalShown ? "translate" : "translated";
+ PopupNotifications.show(this.browser, addId, null,
+ addId + "-notification-icon", null, null,
+ {dismissed: true, eventCallback: callback});
+ },
+
+ _state: 0,
+ get state() {
+ return this._state;
+ },
+ set state(val) {
+ let notif = this.notificationBox.getNotificationWithValue("translation");
+ if (notif)
+ notif.state = val;
+ this._state = val;
+ },
+
+ originalShown: true,
+ showOriginalContent: function() {
+ this.originalShown = true;
+ this.showURLBarIcon();
+ this.browser.messageManager.sendAsyncMessage("Translation:ShowOriginal");
+ TranslationTelemetry.recordShowOriginalContent();
+ },
+
+ showTranslatedContent: function() {
+ this.originalShown = false;
+ this.showURLBarIcon();
+ this.browser.messageManager.sendAsyncMessage("Translation:ShowTranslation");
+ },
+
+ get notificationBox() {
+ return this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser);
+ },
+
+ showTranslationInfoBar: function() {
+ let notificationBox = this.notificationBox;
+ let notif = notificationBox.appendNotification("", "translation", null,
+ notificationBox.PRIORITY_INFO_HIGH);
+ notif.init(this);
+ return notif;
+ },
+
+ shouldShowInfoBar: function(aURI) {
+ // Never show the infobar automatically while the translation
+ // service is temporarily unavailable.
+ if (Translation.serviceUnavailable)
+ return false;
+
+ // Check if we should never show the infobar for this language.
+ let neverForLangs =
+ Services.prefs.getCharPref("browser.translation.neverForLanguages");
+ if (neverForLangs.split(",").indexOf(this.detectedLanguage) != -1) {
+ TranslationTelemetry.recordAutoRejectedTranslationOffer();
+ return false;
+ }
+
+ // or if we should never show the infobar for this domain.
+ let perms = Services.perms;
+ if (perms.testExactPermission(aURI, "translate") == perms.DENY_ACTION) {
+ TranslationTelemetry.recordAutoRejectedTranslationOffer();
+ return false;
+ }
+
+ return true;
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Translation:Finished":
+ if (msg.data.success) {
+ this.originalShown = false;
+ this.state = Translation.STATE_TRANSLATED;
+ this.showURLBarIcon();
+
+ // Record the number of characters translated.
+ TranslationTelemetry.recordTranslation(msg.data.from, msg.data.to,
+ msg.data.characterCount);
+ } else if (msg.data.unavailable) {
+ Translation.serviceUnavailable = true;
+ this.state = Translation.STATE_UNAVAILABLE;
+ } else {
+ this.state = Translation.STATE_ERROR;
+ }
+ break;
+ }
+ },
+
+ infobarClosed: function() {
+ if (this.state == Translation.STATE_OFFER)
+ TranslationTelemetry.recordDeniedTranslationOffer();
+ }
+};
+
+/**
+ * Uses telemetry histograms for collecting statistics on the usage of the
+ * translation component.
+ *
+ * NOTE: Metrics are only recorded if the user enabled the telemetry option.
+ */
+this.TranslationTelemetry = {
+
+ init: function () {
+ // Constructing histograms.
+ const plain = (id) => Services.telemetry.getHistogramById(id);
+ const keyed = (id) => Services.telemetry.getKeyedHistogramById(id);
+ this.HISTOGRAMS = {
+ OPPORTUNITIES : () => plain("TRANSLATION_OPPORTUNITIES"),
+ OPPORTUNITIES_BY_LANG : () => keyed("TRANSLATION_OPPORTUNITIES_BY_LANGUAGE"),
+ PAGES : () => plain("TRANSLATED_PAGES"),
+ PAGES_BY_LANG : () => keyed("TRANSLATED_PAGES_BY_LANGUAGE"),
+ CHARACTERS : () => plain("TRANSLATED_CHARACTERS"),
+ DENIED : () => plain("DENIED_TRANSLATION_OFFERS"),
+ AUTO_REJECTED : () => plain("AUTO_REJECTED_TRANSLATION_OFFERS"),
+ SHOW_ORIGINAL : () => plain("REQUESTS_OF_ORIGINAL_CONTENT"),
+ TARGET_CHANGES : () => plain("CHANGES_OF_TARGET_LANGUAGE"),
+ DETECTION_CHANGES : () => plain("CHANGES_OF_DETECTED_LANGUAGE"),
+ SHOW_UI : () => plain("SHOULD_TRANSLATION_UI_APPEAR"),
+ DETECT_LANG : () => plain("SHOULD_AUTO_DETECT_LANGUAGE"),
+ };
+
+ // Capturing the values of flags at the startup.
+ this.recordPreferences();
+ },
+
+ /**
+ * Record a translation opportunity in the health report.
+ * @param language
+ * The language of the page.
+ */
+ recordTranslationOpportunity: function (language) {
+ return this._recordOpportunity(language, true);
+ },
+
+ /**
+ * Record a missed translation opportunity in the health report.
+ * A missed opportunity is when the language detected is not part
+ * of the supported languages.
+ * @param language
+ * The language of the page.
+ */
+ recordMissedTranslationOpportunity: function (language) {
+ return this._recordOpportunity(language, false);
+ },
+
+ /**
+ * Record an automatically rejected translation offer in the health
+ * report. A translation offer is automatically rejected when a user
+ * has previously clicked "Never translate this language" or "Never
+ * translate this site", which results in the infobar not being shown for
+ * the translation opportunity.
+ *
+ * These translation opportunities should still be recorded in addition to
+ * recording the automatic rejection of the offer.
+ */
+ recordAutoRejectedTranslationOffer: function () {
+ if (!this._canRecord) return;
+ this.HISTOGRAMS.AUTO_REJECTED().add();
+ },
+
+ /**
+ * Record a translation in the health report.
+ * @param langFrom
+ * The language of the page.
+ * @param langTo
+ * The language translated to
+ * @param numCharacters
+ * The number of characters that were translated
+ */
+ recordTranslation: function (langFrom, langTo, numCharacters) {
+ if (!this._canRecord) return;
+ this.HISTOGRAMS.PAGES().add();
+ this.HISTOGRAMS.PAGES_BY_LANG().add(langFrom + " -> " + langTo);
+ this.HISTOGRAMS.CHARACTERS().add(numCharacters);
+ },
+
+ /**
+ * Record a change of the detected language in the health report. This should
+ * only be called when actually executing a translation, not every time the
+ * user changes in the language in the UI.
+ *
+ * @param beforeFirstTranslation
+ * A boolean indicating if we are recording a change of detected
+ * language before translating the page for the first time. If we
+ * have already translated the page from the detected language and
+ * the user has manually adjusted the detected language false should
+ * be passed.
+ */
+ recordDetectedLanguageChange: function (beforeFirstTranslation) {
+ if (!this._canRecord) return;
+ this.HISTOGRAMS.DETECTION_CHANGES().add(beforeFirstTranslation);
+ },
+
+ /**
+ * Record a change of the target language in the health report. This should
+ * only be called when actually executing a translation, not every time the
+ * user changes in the language in the UI.
+ */
+ recordTargetLanguageChange: function () {
+ if (!this._canRecord) return;
+ this.HISTOGRAMS.TARGET_CHANGES().add();
+ },
+
+ /**
+ * Record a denied translation offer.
+ */
+ recordDeniedTranslationOffer: function () {
+ if (!this._canRecord) return;
+ this.HISTOGRAMS.DENIED().add();
+ },
+
+ /**
+ * Record a "Show Original" command use.
+ */
+ recordShowOriginalContent: function () {
+ if (!this._canRecord) return;
+ this.HISTOGRAMS.SHOW_ORIGINAL().add();
+ },
+
+ /**
+ * Record the state of translation preferences.
+ */
+ recordPreferences: function () {
+ if (!this._canRecord) return;
+ if (Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI)) {
+ this.HISTOGRAMS.SHOW_UI().add(1);
+ }
+ if (Services.prefs.getBoolPref(TRANSLATION_PREF_DETECT_LANG)) {
+ this.HISTOGRAMS.DETECT_LANG().add(1);
+ }
+ },
+
+ _recordOpportunity: function(language, success) {
+ if (!this._canRecord) return;
+ this.HISTOGRAMS.OPPORTUNITIES().add(success);
+ this.HISTOGRAMS.OPPORTUNITIES_BY_LANG().add(language, success);
+ },
+
+ /**
+ * A shortcut for reading the telemetry preference.
+ *
+ */
+ _canRecord: function () {
+ return Services.prefs.getBoolPref("toolkit.telemetry.enabled");
+ }
+};
+
+this.TranslationTelemetry.init();
diff --git a/browser/components/translation/TranslationContentHandler.jsm b/browser/components/translation/TranslationContentHandler.jsm
new file mode 100644
index 000000000..3b0d59ddd
--- /dev/null
+++ b/browser/components/translation/TranslationContentHandler.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 = [ "TranslationContentHandler" ];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
+ "resource:///modules/translation/LanguageDetector.jsm");
+
+const STATE_OFFER = 0;
+const STATE_TRANSLATED = 2;
+const STATE_ERROR = 3;
+
+this.TranslationContentHandler = function(global, docShell) {
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+
+ global.addEventListener("pageshow", this);
+
+ global.addMessageListener("Translation:TranslateDocument", this);
+ global.addMessageListener("Translation:ShowTranslation", this);
+ global.addMessageListener("Translation:ShowOriginal", this);
+ this.global = global;
+}
+
+TranslationContentHandler.prototype = {
+ handleEvent: function(aEvent) {
+ // We are only listening to pageshow events.
+ let target = aEvent.target;
+
+ // Only handle top-level frames.
+ let win = target.defaultView;
+ if (win.parent !== win)
+ return;
+
+ let content = this.global.content;
+ if (!content.detectedLanguage)
+ return;
+
+ let data = {};
+ let trDoc = content.translationDocument;
+ if (trDoc) {
+ data.state = trDoc.translationError ? STATE_ERROR : STATE_TRANSLATED;
+ data.translatedFrom = trDoc.translatedFrom;
+ data.translatedTo = trDoc.translatedTo;
+ data.originalShown = trDoc.originalShown;
+ } else {
+ data.state = STATE_OFFER;
+ data.originalShown = true;
+ }
+ data.detectedLanguage = content.detectedLanguage;
+
+ this.global.sendAsyncMessage("Translation:DocumentState", data);
+ },
+
+ /* nsIWebProgressListener implementation */
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (!aWebProgress.isTopLevel ||
+ !(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
+ !this.global.content)
+ return;
+
+ let url = aRequest.name;
+ if (!url.startsWith("http://") && !url.startsWith("https://"))
+ return;
+
+ let content = this.global.content;
+ if (content.detectedLanguage)
+ return;
+
+ // Grab a 60k sample of text from the page.
+ let encoder = Cc["@mozilla.org/layout/documentEncoder;1?type=text/plain"]
+ .createInstance(Ci.nsIDocumentEncoder);
+ encoder.init(content.document, "text/plain", encoder.SkipInvisibleContent);
+ let string = encoder.encodeToStringWithMaxLength(60 * 1024);
+
+ // Language detection isn't reliable on very short strings.
+ if (string.length < 100)
+ return;
+
+ LanguageDetector.detectLanguage(string).then(result => {
+ // Bail if we're not confident.
+ if (!result.confident) {
+ return;
+ }
+
+ // The window might be gone by now.
+ if (Cu.isDeadWrapper(content)) {
+ return;
+ }
+
+ content.detectedLanguage = result.language;
+
+ let data = {
+ state: STATE_OFFER,
+ originalShown: true,
+ detectedLanguage: result.language
+ };
+ this.global.sendAsyncMessage("Translation:DocumentState", data);
+ });
+ },
+
+ // Unused methods.
+ onProgressChange: function() {},
+ onLocationChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Translation:TranslateDocument":
+ {
+ Cu.import("resource:///modules/translation/TranslationDocument.jsm");
+
+ // If a TranslationDocument already exists for this document, it should
+ // be used instead of creating a new one so that we can use the original
+ // content of the page for the new translation instead of the newly
+ // translated text.
+ let translationDocument = this.global.content.translationDocument ||
+ new TranslationDocument(this.global.content.document);
+
+ let preferredEngine = Services.prefs.getCharPref("browser.translation.engine");
+ let translator = null;
+ if (preferredEngine == "yandex") {
+ Cu.import("resource:///modules/translation/YandexTranslator.jsm");
+ translator = new YandexTranslator(translationDocument,
+ msg.data.from,
+ msg.data.to);
+ } else {
+ Cu.import("resource:///modules/translation/BingTranslator.jsm");
+ translator = new BingTranslator(translationDocument,
+ msg.data.from,
+ msg.data.to);
+ }
+
+ this.global.content.translationDocument = translationDocument;
+ translationDocument.translatedFrom = msg.data.from;
+ translationDocument.translatedTo = msg.data.to;
+ translationDocument.translationError = false;
+
+ translator.translate().then(
+ result => {
+ this.global.sendAsyncMessage("Translation:Finished", {
+ characterCount: result.characterCount,
+ from: msg.data.from,
+ to: msg.data.to,
+ success: true
+ });
+ translationDocument.showTranslation();
+ },
+ error => {
+ translationDocument.translationError = true;
+ let data = {success: false};
+ if (error == "unavailable")
+ data.unavailable = true;
+ this.global.sendAsyncMessage("Translation:Finished", data);
+ }
+ );
+ break;
+ }
+
+ case "Translation:ShowOriginal":
+ this.global.content.translationDocument.showOriginal();
+ break;
+
+ case "Translation:ShowTranslation":
+ this.global.content.translationDocument.showTranslation();
+ break;
+ }
+ }
+};
diff --git a/browser/components/translation/TranslationDocument.jsm b/browser/components/translation/TranslationDocument.jsm
new file mode 100644
index 000000000..058d07a49
--- /dev/null
+++ b/browser/components/translation/TranslationDocument.jsm
@@ -0,0 +1,683 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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;
+
+this.EXPORTED_SYMBOLS = [ "TranslationDocument" ];
+
+const SHOW_ELEMENT = Ci.nsIDOMNodeFilter.SHOW_ELEMENT;
+const SHOW_TEXT = Ci.nsIDOMNodeFilter.SHOW_TEXT;
+const TEXT_NODE = Ci.nsIDOMNode.TEXT_NODE;
+
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Task.jsm");
+
+/**
+ * This class represents a document that is being translated,
+ * and it is responsible for parsing the document,
+ * generating the data structures translation (the list of
+ * translation items and roots), and managing the original
+ * and translated texts on the translation items.
+ *
+ * @param document The document to be translated
+ */
+this.TranslationDocument = function(document) {
+ this.itemsMap = new Map();
+ this.roots = [];
+ this._init(document);
+};
+
+this.TranslationDocument.prototype = {
+ translatedFrom: "",
+ translatedTo: "",
+ translationError: false,
+ originalShown: true,
+
+ /**
+ * Initializes the object and populates
+ * the roots lists.
+ *
+ * @param document The document to be translated
+ */
+ _init: function(document) {
+ let window = document.defaultView;
+ let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ // Get all the translation nodes in the document's body:
+ // a translation node is a node from the document which
+ // contains useful content for translation, and therefore
+ // must be included in the translation process.
+ let nodeList = winUtils.getTranslationNodes(document.body);
+
+ let length = nodeList.length;
+
+ for (let i = 0; i < length; i++) {
+ let node = nodeList.item(i);
+ let isRoot = nodeList.isTranslationRootAtIndex(i);
+
+ // Create a TranslationItem object for this node.
+ // This function will also add it to the this.roots array.
+ this._createItemForNode(node, i, isRoot);
+ }
+
+ // At first all roots are stored in the roots list, and only after
+ // the process has finished we're able to determine which roots are
+ // simple, and which ones are not.
+
+ // A simple root is defined by a root with no children items, which
+ // basically represents an element from a page with only text content
+ // inside.
+
+ // This distinction is useful for optimization purposes: we treat a
+ // simple root as plain-text in the translation process and with that
+ // we are able to reduce their data payload sent to the translation service.
+
+ for (let root of this.roots) {
+ if (root.children.length == 0 &&
+ root.nodeRef.childElementCount == 0) {
+ root.isSimpleRoot = true;
+ }
+ }
+ },
+
+ /**
+ * Creates a TranslationItem object, which should be called
+ * for each node returned by getTranslationNodes.
+ *
+ * @param node The DOM node for this item.
+ * @param id A unique, numeric id for this item.
+ * @parem isRoot A boolean saying whether this item is a root.
+ *
+ * @returns A TranslationItem object.
+ */
+ _createItemForNode: function(node, id, isRoot) {
+ if (this.itemsMap.has(node)) {
+ return this.itemsMap.get(node);
+ }
+
+ let item = new TranslationItem(node, id, isRoot);
+
+ if (isRoot) {
+ // Root items do not have a parent item.
+ this.roots.push(item);
+ } else {
+ let parentItem = this.itemsMap.get(node.parentNode);
+ if (parentItem) {
+ parentItem.children.push(item);
+ }
+ }
+
+ this.itemsMap.set(node, item);
+ return item;
+ },
+
+ /**
+ * Generate the text string that represents a TranslationItem object.
+ * Besides generating the string, it's also stored in the "original"
+ * field of the TranslationItem object, which needs to be stored for
+ * later to be used in the "Show Original" functionality.
+ * If this function had already been called for the given item (determined
+ * by the presence of the "original" array in the item), the text will
+ * be regenerated from the "original" data instead of from the related
+ * DOM nodes (because the nodes might contain translated data).
+ *
+ * @param item A TranslationItem object
+ *
+ * @returns A string representation of the TranslationItem.
+ */
+ generateTextForItem: function(item) {
+ if (item.original) {
+ return regenerateTextFromOriginalHelper(item);
+ }
+
+ if (item.isSimpleRoot) {
+ let text = item.nodeRef.firstChild.nodeValue.trim();
+ item.original = [text];
+ return text;
+ }
+
+ let str = "";
+ item.original = [];
+ let wasLastItemPlaceholder = false;
+
+ for (let child of item.nodeRef.childNodes) {
+ if (child.nodeType == TEXT_NODE) {
+ let x = child.nodeValue.trim();
+ if (x != "") {
+ item.original.push(x);
+ str += x;
+ wasLastItemPlaceholder = false;
+ }
+ continue;
+ }
+
+ let objInMap = this.itemsMap.get(child);
+ if (objInMap && !objInMap.isRoot) {
+ // If this childNode is present in the itemsMap, it means
+ // it's a translation node: it has useful content for translation.
+ // In this case, we need to stringify this node.
+ // However, if this item is a root, we should skip it here in this
+ // object's child list (and just add a placeholder for it), because
+ // it will be stringfied separately for being a root.
+ item.original.push(objInMap);
+ str += this.generateTextForItem(objInMap);
+ wasLastItemPlaceholder = false;
+ } else if (!wasLastItemPlaceholder) {
+ // Otherwise, if this node doesn't contain any useful content,
+ // or if it is a root itself, we can replace it with a placeholder node.
+ // We can't simply eliminate this node from our string representation
+ // because that could change the HTML structure (e.g., it would
+ // probably merge two separate text nodes).
+ // It's not necessary to add more than one placeholder in sequence;
+ // we can optimize them away.
+ item.original.push(TranslationItem_NodePlaceholder);
+ str += '<br>';
+ wasLastItemPlaceholder = true;
+ }
+ }
+
+ return generateTranslationHtmlForItem(item, str);
+ },
+
+ /**
+ * Changes the document to display its translated
+ * content.
+ */
+ showTranslation: function() {
+ this.originalShown = false;
+ this._swapDocumentContent("translation");
+ },
+
+ /**
+ * Changes the document to display its original
+ * content.
+ */
+ showOriginal: function() {
+ this.originalShown = true;
+ this._swapDocumentContent("original");
+ },
+
+ /**
+ * Swap the document with the resulting translation,
+ * or back with the original content.
+ *
+ * @param target A string that is either "translation"
+ * or "original".
+ */
+ _swapDocumentContent: function(target) {
+ Task.spawn(function *() {
+ // Let the event loop breath on every 100 nodes
+ // that are replaced.
+ const YIELD_INTERVAL = 100;
+ let count = YIELD_INTERVAL;
+
+ for (let root of this.roots) {
+ root.swapText(target);
+ if (count-- == 0) {
+ count = YIELD_INTERVAL;
+ yield CommonUtils.laterTickResolvingPromise();
+ }
+ }
+ }.bind(this));
+ }
+};
+
+/**
+ * This class represents an item for translation. It's basically our
+ * wrapper class around a node returned by getTranslationNode, with
+ * more data and structural information on it.
+ *
+ * At the end of the translation process, besides the properties below,
+ * a TranslationItem will contain two other properties: one called "original"
+ * and one called "translation". They are twin objects, one which reflect
+ * the structure of that node in its original state, and the other in its
+ * translated state.
+ *
+ * The "original" array is generated in the generateTextForItem function,
+ * and the "translation" array is generated when the translation results
+ * are parsed.
+ *
+ * They are both arrays, which contain a mix of strings and references to
+ * child TranslationItems. The references in both arrays point to the * same *
+ * TranslationItem object, but they might appear in different orders between the
+ * "original" and "translation" arrays.
+ *
+ * An example:
+ *
+ * English: <div id="n1">Welcome to <b id="n2">Mozilla's</b> website</div>
+ * Portuguese: <div id="n1">Bem vindo a pagina <b id="n2">da Mozilla</b></div>
+ *
+ * TranslationItem n1 = {
+ * id: 1,
+ * original: ["Welcome to", ptr to n2, "website"]
+ * translation: ["Bem vindo a pagina", ptr to n2]
+ * }
+ *
+ * TranslationItem n2 = {
+ * id: 2,
+ * original: ["Mozilla's"],
+ * translation: ["da Mozilla"]
+ * }
+ */
+function TranslationItem(node, id, isRoot) {
+ this.nodeRef = node;
+ this.id = id;
+ this.isRoot = isRoot;
+ this.children = [];
+}
+
+TranslationItem.prototype = {
+ isRoot: false,
+ isSimpleRoot: false,
+
+ toString: function() {
+ let rootType = "";
+ if (this.isRoot) {
+ if (this.isSimpleRoot) {
+ rootType = " (simple root)";
+ }
+ else {
+ rootType = " (non simple root)";
+ }
+ }
+ return "[object TranslationItem: <" + this.nodeRef.localName + ">"
+ + rootType + "]";
+ },
+
+ /**
+ * This function will parse the result of the translation of one translation
+ * item. If this item was a simple root, all we sent was a plain-text version
+ * of it, so the result is also straightforward text.
+ *
+ * For non-simple roots, we sent a simplified HTML representation of that
+ * node, and we'll first parse that into an HTML doc and then call the
+ * parseResultNode helper function to parse it.
+ *
+ * While parsing, the result is stored in the "translation" field of the
+ * TranslationItem, which will be used to display the final translation when
+ * all items are finished. It remains stored too to allow back-and-forth
+ * switching between the "Show Original" and "Show Translation" functions.
+ *
+ * @param result A string with the textual result received from the server,
+ * which can be plain-text or a serialized HTML doc.
+ */
+ parseResult: function(result) {
+ if (this.isSimpleRoot) {
+ this.translation = [result];
+ return;
+ }
+
+ let domParser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+
+ let doc = domParser.parseFromString(result, "text/html");
+ parseResultNode(this, doc.body.firstChild);
+ },
+
+ /**
+ * This function finds a child TranslationItem
+ * with the given id.
+ * @param id The id to look for, in the format "n#"
+ * @returns A TranslationItem with the given id, or null if
+ * it was not found.
+ */
+ getChildById: function(id) {
+ for (let child of this.children) {
+ if (("n" + child.id) == id) {
+ return child;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Swap the text of this TranslationItem between
+ * its original and translated states.
+ *
+ * @param target A string that is either "translation"
+ * or "original".
+ */
+ swapText: function(target) {
+ swapTextForItem(this, target);
+ }
+};
+
+/**
+ * This object represents a placeholder item for translation. It's similar to
+ * the TranslationItem class, but it represents nodes that have no meaningful
+ * content for translation. These nodes will be replaced by "<br>" in a
+ * translation request. It's necessary to keep them to use it as a mark
+ * for correct positioning and spliting of text nodes.
+ */
+const TranslationItem_NodePlaceholder = {
+ toString: function() {
+ return "[object TranslationItem_NodePlaceholder]";
+ }
+};
+
+/**
+ * Generate the outer HTML representation for a given item.
+ *
+ * @param item A TranslationItem object.
+ * param content The inner content for this item.
+ * @returns string The outer HTML needed for translation
+ * of this item.
+ */
+function generateTranslationHtmlForItem(item, content) {
+ let localName = item.isRoot ? "div" : "b";
+ return '<' + localName + ' id=n' + item.id + '>' +
+ content +
+ "</" + localName + ">";
+}
+
+ /**
+ * Regenerate the text string that represents a TranslationItem object,
+ * with data from its "original" array. The array must have already
+ * been created by TranslationDocument.generateTextForItem().
+ *
+ * @param item A TranslationItem object
+ *
+ * @returns A string representation of the TranslationItem.
+ */
+function regenerateTextFromOriginalHelper(item) {
+ if (item.isSimpleRoot) {
+ return item.original[0];
+ }
+
+ let str = "";
+ for (let child of item.original) {
+ if (child instanceof TranslationItem) {
+ str += regenerateTextFromOriginalHelper(child);
+ } else if (child === TranslationItem_NodePlaceholder) {
+ str += "<br>";
+ } else {
+ str += child;
+ }
+ }
+
+ return generateTranslationHtmlForItem(item, str);
+}
+
+/**
+ * Helper function to parse a HTML doc result.
+ * How it works:
+ *
+ * An example result string is:
+ *
+ * <div id="n1">Hello <b id="n2">World</b> of Mozilla.</div>
+ *
+ * For an element node, we look at its id and find the corresponding
+ * TranslationItem that was associated with this node, and then we
+ * walk down it repeating the process.
+ *
+ * For text nodes we simply add it as a string.
+ */
+function parseResultNode(item, node) {
+ item.translation = [];
+ for (let child of node.childNodes) {
+ if (child.nodeType == TEXT_NODE) {
+ item.translation.push(child.nodeValue);
+ } else if (child.localName == "br") {
+ item.translation.push(TranslationItem_NodePlaceholder);
+ } else {
+ let translationItemChild = item.getChildById(child.id);
+
+ if (translationItemChild) {
+ item.translation.push(translationItemChild);
+ parseResultNode(translationItemChild, child);
+ }
+ }
+ }
+}
+
+/**
+ * Helper function to swap the text of a TranslationItem
+ * between its original and translated states.
+ * How it works:
+ *
+ * The function iterates through the target array (either the `original` or
+ * `translation` array from the TranslationItem), while also keeping a pointer
+ * to a current position in the child nodes from the actual DOM node that we
+ * are modifying. This pointer is moved forward after each item of the array
+ * is translated. If, at any given time, the pointer doesn't match the expected
+ * node that was supposed to be seen, it means that the original and translated
+ * contents have a different ordering, and thus we need to adjust that.
+ *
+ * A full example of the reordering process, swapping from Original to
+ * Translation:
+ *
+ * Original (en): <div>I <em>miss</em> <b>you</b></div>
+ *
+ * Translation (fr): <div><b>Tu</b> me <em>manques</em></div>
+ *
+ * Step 1:
+ * pointer points to firstChild of the DOM node, textnode "I "
+ * first item in item.translation is [object TranslationItem <b>]
+ *
+ * pointer does not match the expected element, <b>. So let's move <b> to the
+ * pointer position.
+ *
+ * Current state of the DOM:
+ * <div><b>you</b>I <em>miss</em> </div>
+ *
+ * Step 2:
+ * pointer moves forward to nextSibling, textnode "I " again.
+ * second item in item.translation is the string " me "
+ *
+ * pointer points to a text node, and we were expecting a text node. Match!
+ * just replace the text content.
+ *
+ * Current state of the DOM:
+ * <div><b>you</b> me <em>miss</em> </div>
+ *
+ * Step 3:
+ * pointer moves forward to nextSibling, <em>miss</em>
+ * third item in item.translation is [object TranslationItem <em>]
+ *
+ * pointer points to the expected node. Match! Nothing to do.
+ *
+ * Step 4:
+ * all items in this item.translation were transformed. The remaining
+ * text nodes are cleared to "", and domNode.normalize() removes them.
+ *
+ * Current state of the DOM:
+ * <div><b>you</b> me <em>miss</em></div>
+ *
+ * Further steps:
+ * After that, the function will visit the child items (from the visitStack),
+ * and the text inside the <b> and <em> nodes will be swapped as well,
+ * yielding the final result:
+ *
+ * <div><b>Tu</b> me <em>manques</em></div>
+ *
+ *
+ * @param item A TranslationItem object
+ * @param target A string that is either "translation"
+ * or "original".
+ */
+function swapTextForItem(item, target) {
+ // visitStack is the stack of items that we still need to visit.
+ // Let's start the process by adding the root item.
+ let visitStack = [ item ];
+
+ while (visitStack.length > 0) {
+ let curItem = visitStack.shift();
+
+ let domNode = curItem.nodeRef;
+ if (!domNode) {
+ // Skipping this item due to a missing node.
+ continue;
+ }
+
+ if (!curItem[target]) {
+ // Translation not found for this item. This could be due to
+ // an error in the server response. For example, if a translation
+ // was broken in various chunks, and one of the chunks failed,
+ // the items from that chunk will be missing its "translation"
+ // field.
+ continue;
+ }
+
+ domNode.normalize();
+
+ // curNode points to the child nodes of the DOM node that we are
+ // modifying. During most of the process, while the target array is
+ // being iterated (in the for loop below), it should walk together with
+ // the array and be pointing to the correct node that needs to modified.
+ // If it's not pointing to it, that means some sort of node reordering
+ // will be necessary to produce the correct translation.
+ // Note that text nodes don't need to be reordered, as we can just replace
+ // the content of one text node with another.
+ //
+ // curNode starts in the firstChild...
+ let curNode = domNode.firstChild;
+
+ // ... actually, let's make curNode start at the first useful node (either
+ // a non-blank text node or something else). This is not strictly necessary,
+ // as the reordering algorithm would correctly handle this case. However,
+ // this better aligns the resulting translation with the DOM content of the
+ // page, avoiding cases that would need to be unecessarily reordered.
+ //
+ // An example of how this helps:
+ //
+ // ---- Original: <div> <b>Hello </b> world.</div>
+ // ^textnode 1 ^item 1 ^textnode 2
+ //
+ // - Translation: <div><b>Hallo </b> Welt.</div>
+ //
+ // Transformation process without this optimization:
+ // 1 - start pointer at textnode 1
+ // 2 - move item 1 to first position inside the <div>
+ //
+ // Node now looks like: <div><b>Hello </b>[ ][ world.]</div>
+ // textnode 1^ ^textnode 2
+ //
+ // 3 - replace textnode 1 with " Welt."
+ // 4 - clear remaining text nodes (in this case, textnode 2)
+ //
+ // Transformation process with this optimization:
+ // 1 - start pointer at item 1
+ // 2 - item 1 is already in position
+ // 3 - replace textnode 2 with " Welt."
+ //
+ // which completely avoids any node reordering, and requires only one
+ // text change instead of two (while also leaving the page closer to
+ // its original state).
+ while (curNode &&
+ curNode.nodeType == TEXT_NODE &&
+ curNode.nodeValue.trim() == "") {
+ curNode = curNode.nextSibling;
+ }
+
+ // Now let's walk through all items in the `target` array of the
+ // TranslationItem. This means either the TranslationItem.original or
+ // TranslationItem.translation array.
+ for (let targetItem of curItem[target]) {
+
+ if (targetItem instanceof TranslationItem) {
+ // If the array element is another TranslationItem object, let's
+ // add it to the stack to be visited.
+ visitStack.push(targetItem);
+
+ let targetNode = targetItem.nodeRef;
+
+ // If the node is not in the expected position, let's reorder
+ // it into position...
+ if (curNode != targetNode &&
+ // ...unless the page has reparented this node under a totally
+ // different node (or removed it). In this case, all bets are off
+ // on being able to do anything correctly, so it's better not to
+ // bring back the node to this parent.
+ targetNode.parentNode == domNode) {
+
+ // We don't need to null-check curNode because insertBefore(..., null)
+ // does what we need in that case: reorder this node to the end
+ // of child nodes.
+ domNode.insertBefore(targetNode, curNode);
+ curNode = targetNode;
+ }
+
+ // Move pointer forward. Since we do not add empty text nodes to the
+ // list of translation items, we must skip them here too while
+ // traversing the DOM in order to get better alignment between the
+ // text nodes and the translation items.
+ if (curNode) {
+ curNode = getNextSiblingSkippingEmptyTextNodes(curNode);
+ }
+
+ } else if (targetItem === TranslationItem_NodePlaceholder) {
+ // If the current item is a placeholder node, we need to move
+ // our pointer "past" it, jumping from one side of a block of
+ // elements + empty text nodes to the other side. Even if
+ // non-placeholder elements exists inside the jumped block,
+ // they will be pulled correctly later in the process when the
+ // targetItem for those nodes are handled.
+
+ while (curNode &&
+ (curNode.nodeType != TEXT_NODE ||
+ curNode.nodeValue.trim() == "")) {
+ curNode = curNode.nextSibling;
+ }
+
+ } else {
+ // Finally, if it's a text item, we just need to find the next
+ // text node to use. Text nodes don't need to be reordered, so
+ // the first one found can be used.
+ while (curNode && curNode.nodeType != TEXT_NODE) {
+ curNode = curNode.nextSibling;
+ }
+
+ // If none was found and we reached the end of the child nodes,
+ // let's create a new one.
+ if (!curNode) {
+ // We don't know if the original content had a space or not,
+ // so the best bet is to create the text node with " " which
+ // will add one space at the beginning and one at the end.
+ curNode = domNode.appendChild(domNode.ownerDocument.createTextNode(" "));
+ }
+
+ // A trailing and a leading space must be preserved because
+ // they are meaningful in HTML.
+ let preSpace = /^\s/.test(curNode.nodeValue) ? " " : "";
+ let endSpace = /\s$/.test(curNode.nodeValue) ? " " : "";
+
+ curNode.nodeValue = preSpace + targetItem + endSpace;
+ curNode = getNextSiblingSkippingEmptyTextNodes(curNode);
+ }
+ }
+
+ // The translated version of a node might have less text nodes than its
+ // original version. If that's the case, let's clear the remaining nodes.
+ if (curNode) {
+ clearRemainingNonEmptyTextNodesFromElement(curNode);
+ }
+
+ // And remove any garbage "" nodes left after clearing.
+ domNode.normalize();
+ }
+}
+
+function getNextSiblingSkippingEmptyTextNodes(startSibling) {
+ let item = startSibling.nextSibling;
+ while (item &&
+ item.nodeType == TEXT_NODE &&
+ item.nodeValue.trim() == "") {
+ item = item.nextSibling;
+ }
+ return item;
+}
+
+function clearRemainingNonEmptyTextNodesFromElement(startSibling) {
+ let item = startSibling;
+ while (item) {
+ if (item.nodeType == TEXT_NODE &&
+ item.nodeValue != "") {
+ item.nodeValue = "";
+ }
+ item = item.nextSibling;
+ }
+}
diff --git a/browser/components/translation/YandexTranslator.jsm b/browser/components/translation/YandexTranslator.jsm
new file mode 100644
index 000000000..ab92e0962
--- /dev/null
+++ b/browser/components/translation/YandexTranslator.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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = [ "YandexTranslator" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Http.jsm");
+
+// The maximum amount of net data allowed per request on Bing's API.
+const MAX_REQUEST_DATA = 5000; // Documentation says 10000 but anywhere
+ // close to that is refused by the service.
+
+// The maximum number of chunks allowed to be translated in a single
+// request.
+const MAX_REQUEST_CHUNKS = 1000; // Documentation says 2000.
+
+// Self-imposed limit of 15 requests. This means that a page that would need
+// to be broken in more than 15 requests won't be fully translated.
+// The maximum amount of data that we will translate for a single page
+// is MAX_REQUESTS * MAX_REQUEST_DATA.
+const MAX_REQUESTS = 15;
+
+const YANDEX_RETURN_CODE_OK = 200;
+
+const YANDEX_ERR_KEY_INVALID = 401; // Invalid API key
+const YANDEX_ERR_KEY_BLOCKED = 402; // This API key has been blocked
+const YANDEX_ERR_DAILY_REQ_LIMIT_EXCEEDED = 403; // Daily limit for requests reached
+const YANDEX_ERR_DAILY_CHAR_LIMIT_EXCEEDED = 404; // Daily limit of chars reached
+const YANDEX_ERR_TEXT_TOO_LONG = 413; // The text size exceeds the maximum
+const YANDEX_ERR_UNPROCESSABLE_TEXT = 422; // The text could not be translated
+const YANDEX_ERR_LANG_NOT_SUPPORTED = 501; // The specified translation direction is not supported
+
+// Errors that should activate the service unavailable handling
+const YANDEX_PERMANENT_ERRORS = [
+ YANDEX_ERR_KEY_INVALID,
+ YANDEX_ERR_KEY_BLOCKED,
+ YANDEX_ERR_DAILY_REQ_LIMIT_EXCEEDED,
+ YANDEX_ERR_DAILY_CHAR_LIMIT_EXCEEDED,
+];
+
+/**
+ * Translates a webpage using Yandex's Translation API.
+ *
+ * @param translationDocument The TranslationDocument object that represents
+ * the webpage to be translated
+ * @param sourceLanguage The source language of the document
+ * @param targetLanguage The target language for the translation
+ *
+ * @returns {Promise} A promise that will resolve when the translation
+ * task is finished.
+ */
+this.YandexTranslator = function(translationDocument, sourceLanguage, targetLanguage) {
+ this.translationDocument = translationDocument;
+ this.sourceLanguage = sourceLanguage;
+ this.targetLanguage = targetLanguage;
+ this._pendingRequests = 0;
+ this._partialSuccess = false;
+ this._serviceUnavailable = false;
+ this._translatedCharacterCount = 0;
+};
+
+this.YandexTranslator.prototype = {
+ /**
+ * Performs the translation, splitting the document into several chunks
+ * respecting the data limits of the API.
+ *
+ * @returns {Promise} A promise that will resolve when the translation
+ * task is finished.
+ */
+ translate: function() {
+ return Task.spawn(function *() {
+ let currentIndex = 0;
+ this._onFinishedDeferred = Promise.defer();
+
+ // Let's split the document into various requests to be sent to
+ // Yandex's Translation API.
+ for (let requestCount = 0; requestCount < MAX_REQUESTS; requestCount++) {
+ // Generating the text for each request can be expensive, so
+ // let's take the opportunity of the chunkification process to
+ // allow for the event loop to attend other pending events
+ // before we continue.
+ yield CommonUtils.laterTickResolvingPromise();
+
+ // Determine the data for the next request.
+ let request = this._generateNextTranslationRequest(currentIndex);
+
+ // Create a real request to the server, and put it on the
+ // pending requests list.
+ let yandexRequest = new YandexRequest(request.data,
+ this.sourceLanguage,
+ this.targetLanguage);
+ this._pendingRequests++;
+ yandexRequest.fireRequest().then(this._chunkCompleted.bind(this),
+ this._chunkFailed.bind(this));
+
+ currentIndex = request.lastIndex;
+ if (request.finished) {
+ break;
+ }
+ }
+
+ return this._onFinishedDeferred.promise;
+ }.bind(this));
+ },
+
+ /**
+ * Function called when a request sent to the server completed successfully.
+ * This function handles calling the function to parse the result and the
+ * function to resolve the promise returned by the public `translate()`
+ * method when there are no pending requests left.
+ *
+ * @param request The YandexRequest sent to the server
+ */
+ _chunkCompleted: function(yandexRequest) {
+ if (this._parseChunkResult(yandexRequest)) {
+ this._partialSuccess = true;
+ // Count the number of characters successfully translated.
+ this._translatedCharacterCount += yandexRequest.characterCount;
+ }
+
+ this._checkIfFinished();
+ },
+
+ /**
+ * Function called when a request sent to the server has failed.
+ * This function handles deciding if the error is transient or means the
+ * service is unavailable (zero balance on the key or request credentials are
+ * not in an active state) and calling the function to resolve the promise
+ * returned by the public `translate()` method when there are no pending
+ * requests left.
+ *
+ * @param aError [optional] The XHR object of the request that failed.
+ */
+ _chunkFailed: function(aError) {
+ if (aError instanceof Ci.nsIXMLHttpRequest) {
+ let body = aError.responseText;
+ let json = { code: 0 };
+ try {
+ json = JSON.parse(body);
+ } catch (e) {}
+
+ if (json.code && YANDEX_PERMANENT_ERRORS.indexOf(json.code) != -1)
+ this._serviceUnavailable = true;
+ }
+
+ this._checkIfFinished();
+ },
+
+ /**
+ * Function called when a request sent to the server has completed.
+ * This function handles resolving the promise
+ * returned by the public `translate()` method when all chunks are completed.
+ */
+ _checkIfFinished: function() {
+ // Check if all pending requests have been
+ // completed and then resolves the promise.
+ // If at least one chunk was successful, the
+ // promise will be resolved positively which will
+ // display the "Success" state for the infobar. Otherwise,
+ // the "Error" state will appear.
+ if (--this._pendingRequests == 0) {
+ if (this._partialSuccess) {
+ this._onFinishedDeferred.resolve({
+ characterCount: this._translatedCharacterCount
+ });
+ } else {
+ let error = this._serviceUnavailable ? "unavailable" : "failure";
+ this._onFinishedDeferred.reject(error);
+ }
+ }
+ },
+
+ /**
+ * This function parses the result returned by Yandex's Translation API,
+ * which returns a JSON result that contains a number of elements. The
+ * API is documented here:
+ * http://api.yandex.com/translate/doc/dg/reference/translate.xml
+ *
+ * @param request The request sent to the server.
+ * @returns boolean True if parsing of this chunk was successful.
+ */
+ _parseChunkResult: function(yandexRequest) {
+ let results;
+ try {
+ let result = JSON.parse(yandexRequest.networkRequest.responseText);
+ if (result.code != 200) {
+ Services.console.logStringMessage("YandexTranslator: Result is " + result.code);
+ return false;
+ }
+ results = result.text
+ } catch (e) {
+ return false;
+ }
+
+ let len = results.length;
+ if (len != yandexRequest.translationData.length) {
+ // This should never happen, but if the service returns a different number
+ // of items (from the number of items submitted), we can't use this chunk
+ // because all items would be paired incorrectly.
+ return false;
+ }
+
+ let error = false;
+ for (let i = 0; i < len; i++) {
+ try {
+ let result = results[i];
+ let root = yandexRequest.translationData[i][0];
+ root.parseResult(result);
+ } catch (e) { error = true; }
+ }
+
+ return !error;
+ },
+
+ /**
+ * This function will determine what is the data to be used for
+ * the Nth request we are generating, based on the input params.
+ *
+ * @param startIndex What is the index, in the roots list, that the
+ * chunk should start.
+ */
+ _generateNextTranslationRequest: function(startIndex) {
+ let currentDataSize = 0;
+ let currentChunks = 0;
+ let output = [];
+ let rootsList = this.translationDocument.roots;
+
+ for (let i = startIndex; i < rootsList.length; i++) {
+ let root = rootsList[i];
+ let text = this.translationDocument.generateTextForItem(root);
+ if (!text) {
+ continue;
+ }
+
+ let newCurSize = currentDataSize + text.length;
+ let newChunks = currentChunks + 1;
+
+ if (newCurSize > MAX_REQUEST_DATA ||
+ newChunks > MAX_REQUEST_CHUNKS) {
+
+ // If we've reached the API limits, let's stop accumulating data
+ // for this request and return. We return information useful for
+ // the caller to pass back on the next call, so that the function
+ // can keep working from where it stopped.
+ return {
+ data: output,
+ finished: false,
+ lastIndex: i
+ };
+ }
+
+ currentDataSize = newCurSize;
+ currentChunks = newChunks;
+ output.push([root, text]);
+ }
+
+ return {
+ data: output,
+ finished: true,
+ lastIndex: 0
+ };
+ }
+};
+
+/**
+ * Represents a request (for 1 chunk) sent off to Yandex's service.
+ *
+ * @params translationData The data to be used for this translation,
+ * generated by the generateNextTranslationRequest...
+ * function.
+ * @param sourceLanguage The source language of the document.
+ * @param targetLanguage The target language for the translation.
+ *
+ */
+function YandexRequest(translationData, sourceLanguage, targetLanguage) {
+ this.translationData = translationData;
+ this.sourceLanguage = sourceLanguage;
+ this.targetLanguage = targetLanguage;
+ this.characterCount = 0;
+}
+
+YandexRequest.prototype = {
+ /**
+ * Initiates the request
+ */
+ fireRequest: function() {
+ return Task.spawn(function *() {
+ // Prepare URL.
+ let url = getUrlParam("https://translate.yandex.net/api/v1.5/tr.json/translate",
+ "browser.translation.yandex.translateURLOverride");
+
+ // Prepare the request body.
+ let apiKey = getUrlParam("%YANDEX_API_KEY%", "browser.translation.yandex.apiKeyOverride");
+ let params = [
+ ["key", apiKey],
+ ["format", "html"],
+ ["lang", this.sourceLanguage + "-" + this.targetLanguage],
+ ];
+
+ for (let [, text] of this.translationData) {
+ params.push(["text", text]);
+ this.characterCount += text.length;
+ }
+
+ // Set up request options.
+ let deferred = Promise.defer();
+ let options = {
+ onLoad: (function(responseText, xhr) {
+ deferred.resolve(this);
+ }).bind(this),
+ onError: function(e, responseText, xhr) {
+ deferred.reject(xhr);
+ },
+ postData: params
+ };
+
+ // Fire the request.
+ this.networkRequest = httpRequest(url, options);
+
+ return deferred.promise;
+ }.bind(this));
+ }
+};
+
+/**
+ * Fetch an auth token (clientID or client secret), which may be overridden by
+ * a pref if it's set.
+ */
+function getUrlParam(paramValue, prefName) {
+ if (Services.prefs.getPrefType(prefName))
+ paramValue = Services.prefs.getCharPref(prefName);
+ paramValue = Services.urlFormatter.formatURL(paramValue);
+ return paramValue;
+}
diff --git a/browser/components/translation/cld2/Makefile b/browser/components/translation/cld2/Makefile
new file mode 100644
index 000000000..080a7be3d
--- /dev/null
+++ b/browser/components/translation/cld2/Makefile
@@ -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/.
+
+PYTHON2 ?= python2
+
+EMSCRIPTEN_ROOT := $(shell if which emcc >/dev/null 2>&1; \
+ then dirname `which emcc`; \
+ else echo /usr/lib/emscripten; \
+ fi)
+
+EMCC ?= $(EMSCRIPTEN_ROOT)/emcc
+
+WEBIDL ?= $(PYTHON2) $(EMSCRIPTEN_ROOT)/tools/webidl_binder.py
+
+# A 2MB heap is to analyze most web pages. For the outliers, we need to either
+# allow for heap growth, or allocate an unreasonable amount of memory at the
+# outset.
+# Unfortunately, once the heap has been enlarged, there is no shrinking, so
+# analyzing one 20MB web page gives us a 30-40MB heap for the life of the
+# worker.
+FLAGS=-s -O3 -s INLINING_LIMIT=1 -s NO_FILESYSTEM=1 -s NO_EXIT_RUNTIME=1 -s INVOKE_RUN=0 \
+ -s TOTAL_STACK=8192 -s TOTAL_MEMORY=2097152 -s ALLOW_MEMORY_GROWTH=1 \
+ --llvm-lto 1 --memory-init-file 1 --closure 1
+
+export EMCC_CLOSURE_ARGS = --language_in ECMASCRIPT6 --language_out ES5_STRICT
+
+SOURCES= \
+ internal/cldutil.cc \
+ internal/cldutil_shared.cc \
+ internal/compact_lang_det.cc \
+ internal/compact_lang_det_hint_code.cc \
+ internal/compact_lang_det_impl.cc \
+ internal/debug_empty.cc \
+ internal/fixunicodevalue.cc \
+ internal/generated_entities.cc \
+ internal/generated_language.cc \
+ internal/generated_ulscript.cc \
+ internal/getonescriptspan.cc \
+ internal/lang_script.cc \
+ internal/offsetmap.cc \
+ internal/scoreonescriptspan.cc \
+ internal/tote.cc \
+ internal/utf8statetable.cc \
+ internal/cld_generated_cjk_uni_prop_80.cc \
+ internal/cld2_generated_cjk_compatible.cc \
+ internal/cld_generated_cjk_delta_bi_4.cc \
+ internal/generated_distinct_bi_0.cc \
+ internal/cld2_generated_quadchrome0122_16.cc \
+ internal/cld2_generated_deltaoctachrome0122.cc \
+ internal/cld2_generated_distinctoctachrome0122.cc \
+ internal/cld_generated_score_quad_octa_0122_2.cc \
+ cldapp.cc \
+ $(NULL)
+
+OBJECTS=$(SOURCES:.cc=.o)
+
+default: all
+
+%.o: %.cc Makefile
+ $(EMCC) -Os -I. -o $@ $<
+
+cldapp.o: cld.cpp
+
+%.cpp %.js: %.idl
+ $(WEBIDL) $< $*
+
+all: cld-worker.js
+
+cld-worker.js: $(OBJECTS) post.js cld.js
+ $(EMCC) $(FLAGS) -I. -o cld-worker.js $(OBJECTS) --post-js cld.js --post-js post.js
+
+clean:
+ rm -f $(OBJECTS) cld.cpp cld.js before.js
diff --git a/browser/components/translation/cld2/cld-worker.js b/browser/components/translation/cld2/cld-worker.js
new file mode 100644
index 000000000..b06021bfe
--- /dev/null
+++ b/browser/components/translation/cld2/cld-worker.js
@@ -0,0 +1,86 @@
+'use strict';var c;c||(c=eval("(function() { try { return Module || {} } catch(e) { return {} } })()"));var aa={},g;for(g in c)c.hasOwnProperty(g)&&(aa[g]=c[g]);var ba=!1,k=!1,m=!1,ca=!1;
+if(c.ENVIRONMENT)if("WEB"===c.ENVIRONMENT)ba=!0;else if("WORKER"===c.ENVIRONMENT)k=!0;else if("NODE"===c.ENVIRONMENT)m=!0;else if("SHELL"===c.ENVIRONMENT)ca=!0;else throw Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.");else ba="object"===typeof window,k="function"===typeof importScripts,m="object"===typeof process&&"function"===typeof require&&!ba&&!k,ca=!ba&&!m&&!k;
+if(m){c.print||(c.print=console.log);c.printErr||(c.printErr=console.warn);var da,ea;c.read=function(a,b){da||(da=require("fs"));ea||(ea=require("path"));a=ea.normalize(a);var d=da.readFileSync(a);d||a==ea.resolve(a)||(a=path.join(__dirname,"..","src",a),d=da.readFileSync(a));d&&!b&&(d=d.toString());return d};c.readBinary=function(a){a=c.read(a,!0);a.buffer||(a=new Uint8Array(a));assert(a.buffer);return a};c.load=function(a){fa(read(a))};c.thisProgram||(c.thisProgram=1<process.argv.length?process.argv[1].replace(/\\/g,
+"/"):"unknown-program");c.arguments=process.argv.slice(2);"undefined"!==typeof module&&(module.exports=c);process.on("uncaughtException",function(a){if(!(a instanceof n))throw a;});c.inspect=function(){return"[Emscripten Module object]"}}else if(ca)c.print||(c.print=print),"undefined"!=typeof printErr&&(c.printErr=printErr),c.read="undefined"!=typeof read?read:function(){throw"no read() available (jsc?)";},c.readBinary=function(a){if("function"===typeof readbuffer)return new Uint8Array(readbuffer(a));
+a=read(a,"binary");assert("object"===typeof a);return a},"undefined"!=typeof scriptArgs?c.arguments=scriptArgs:"undefined"!=typeof arguments&&(c.arguments=arguments),eval("if (typeof gc === 'function' && gc.toString().indexOf('[native code]') > 0) var gc = undefined");else if(ba||k)c.read=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},c.readAsync=function(a,b,d){var e=new XMLHttpRequest;e.open("GET",a,!0);e.responseType="arraybuffer";e.onload=function(){200==
+e.status||0==e.status&&e.response?b(e.response):d()};e.onerror=d;e.send(null)},"undefined"!=typeof arguments&&(c.arguments=arguments),"undefined"!==typeof console?(c.print||(c.print=function(a){console.log(a)}),c.printErr||(c.printErr=function(a){console.warn(a)})):c.print||(c.print=function(){}),k&&(c.load=importScripts),"undefined"===typeof c.setWindowTitle&&(c.setWindowTitle=function(a){document.title=a});else throw"Unknown runtime environment. Where are we?";function fa(a){eval.call(null,a)}
+!c.load&&c.read&&(c.load=function(a){fa(c.read(a))});c.print||(c.print=function(){});c.printErr||(c.printErr=c.print);c.arguments||(c.arguments=[]);c.thisProgram||(c.thisProgram="./this.program");c.print=c.print;c.u=c.printErr;c.preRun=[];c.postRun=[];for(g in aa)aa.hasOwnProperty(g)&&(c[g]=aa[g]);
+var aa=void 0,t={V:function(a){tempRet0=a},R:function(){return tempRet0},w:function(){return p},o:function(a){p=a},H:function(a){switch(a){case "i1":case "i8":return 1;case "i16":return 2;case "i32":return 4;case "i64":return 8;case "float":return 4;case "double":return 8;default:return"*"===a[a.length-1]?t.q:"i"===a[0]?(a=parseInt(a.substr(1)),assert(0===a%8),a/8):0}},O:function(a){return Math.max(t.H(a),t.q)},W:16,la:function(a,b){"double"===b||"i64"===b?a&7&&(assert(4===(a&7)),a+=4):assert(0===
+(a&3));return a},ea:function(a,b,d){return d||"i64"!=a&&"double"!=a?a?Math.min(b||(a?t.O(a):0),t.q):Math.min(b,8):8},h:function(a,b,d){return d&&d.length?(d.splice||(d=Array.prototype.slice.call(d)),d.splice(0,0,b),c["dynCall_"+a].apply(null,d)):c["dynCall_"+a].call(null,b)},l:[],K:function(a){for(var b=0;b<t.l.length;b++)if(!t.l[b])return t.l[b]=a,2*(1+b);throw"Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.";},U:function(a){t.l[(a-2)/2]=null},k:function(a){t.k.v||
+(t.k.v={});t.k.v[a]||(t.k.v[a]=1,c.u(a))},s:{},ga:function(a,b){assert(b);t.s[b]||(t.s[b]={});var d=t.s[b];d[a]||(d[a]=function(){return t.h(b,a,arguments)});return d[a]},fa:function(){throw"You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work";},n:function(a){var b=p;p=p+a|0;p=p+15&-16;return b},A:function(a){var b=u;u=u+a|0;u=u+15&-16;return b},d:function(a){var b=v;v=v+a|0;v=v+15&-16;return v>=w&&!ga()?(v=b,0):b},F:function(a,
+b){return Math.ceil(a/(b?b:16))*(b?b:16)},ka:function(a,b,d){return d?+(a>>>0)+4294967296*+(b>>>0):+(a>>>0)+4294967296*+(b|0)},C:8,q:4,X:0};c.Runtime=t;t.addFunction=t.K;t.removeFunction=t.U;var ia=!1;function assert(a,b){a||y("Assertion failed: "+b)}function ja(a){var b=c["_"+a];if(!b)try{b=eval("_"+a)}catch(d){}assert(b,"Cannot call unknown function "+a+" (perhaps LLVM optimizations or closure removed it?)");return b}var ka,la;
+(function(){function a(a){a=a.toString().match(f).slice(1);return{arguments:a[0],body:a[1],returnValue:a[2]}}function b(){if(!l){l={};for(var b in d)d.hasOwnProperty(b)&&(l[b]=a(d[b]))}}var d={stackSave:function(){t.w()},stackRestore:function(){t.o()},arrayToC:function(a){var b=t.n(a.length);ma(a,b);return b},stringToC:function(a){var b=0;null!==a&&void 0!==a&&0!==a&&(b=t.n((a.length<<2)+1),na(a,b));return b}},e={string:d.stringToC,array:d.arrayToC};la=function(a,b,d,f,l){a=ja(a);var O=[],P=0;if(f)for(var x=
+0;x<f.length;x++){var ha=e[d[x]];ha?(0===P&&(P=t.w()),O[x]=ha(f[x])):O[x]=f[x]}d=a.apply(null,O);"string"===b&&(d=z(d));if(0!==P){if(l&&l.async){EmterpreterAsync.Y.push(function(){t.o(P)});return}t.o(P)}return d};var f=/^function\s*[a-zA-Z$_0-9]*\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/,l=null;ka=function(d,e,f){f=f||[];var A=ja(d);d=f.every(function(a){return"number"===a});var X="string"!==e;if(X&&d)return A;var O=f.map(function(a,b){return"$"+b});e="(function("+O.join(",")+
+") {";var P=f.length;if(!d){b();e+="var stack = "+l.stackSave.body+";";for(var x=0;x<P;x++){var ha=O[x],Y=f[x];"number"!==Y&&(Y=l[Y+"ToC"],e+="var "+Y.arguments+" = "+ha+";",e+=Y.body+";",e+=ha+"=("+Y.returnValue+");")}}f=a(function(){return A}).returnValue;e+="var ret = "+f+"("+O.join(",")+");";X||(f=a(function(){return z}).returnValue,e+="ret = "+f+"(ret);");d||(b(),e+=l.stackRestore.body.replace("()","(stack)")+";");return eval(e+"return ret})")}})();c.ccall=la;c.cwrap=ka;
+function oa(a,b,d){d=d||"i8";"*"===d.charAt(d.length-1)&&(d="i32");switch(d){case "i1":B[a>>0]=b;break;case "i8":B[a>>0]=b;break;case "i16":pa[a>>1]=b;break;case "i32":C[a>>2]=b;break;case "i64":tempI64=[b>>>0,(tempDouble=b,1<=+qa(tempDouble)?0<tempDouble?(ra(+sa(tempDouble/4294967296),4294967295)|0)>>>0:~~+ta((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)];C[a>>2]=tempI64[0];C[a+4>>2]=tempI64[1];break;case "float":ua[a>>2]=b;break;case "double":va[a>>3]=b;break;default:y("invalid type for setValue: "+
+d)}}c.setValue=oa;function wa(a,b){b=b||"i8";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":return B[a>>0];case "i8":return B[a>>0];case "i16":return pa[a>>1];case "i32":return C[a>>2];case "i64":return C[a>>2];case "float":return ua[a>>2];case "double":return va[a>>3];default:y("invalid type for setValue: "+b)}return null}c.getValue=wa;c.ALLOC_NORMAL=0;c.ALLOC_STACK=1;c.ALLOC_STATIC=2;c.ALLOC_DYNAMIC=3;c.ALLOC_NONE=4;
+function xa(a,b,d,e){var f,l;"number"===typeof a?(f=!0,l=a):(f=!1,l=a.length);var h="string"===typeof b?b:null;d=4==d?e:["function"===typeof D?D:t.A,t.n,t.A,t.d][void 0===d?2:d](Math.max(l,h?1:b.length));if(f){e=d;assert(0==(d&3));for(a=d+(l&-4);e<a;e+=4)C[e>>2]=0;for(a=d+l;e<a;)B[e++>>0]=0;return d}if("i8"===h)return a.subarray||a.slice?E.set(a,d):E.set(new Uint8Array(a),d),d;e=0;for(var q,r;e<l;){var A=a[e];"function"===typeof A&&(A=t.ha(A));f=h||b[e];0===f?e++:("i64"==f&&(f="i32"),oa(d+e,A,f),
+r!==f&&(q=t.H(f),r=f),e+=q)}return d}c.allocate=xa;c.getMemory=function(a){return ya?"undefined"!==typeof F&&!F.b||!za?t.d(a):D(a):t.A(a)};function z(a,b){if(0===b||!a)return"";for(var d=0,e,f=0;;){e=E[a+f>>0];d|=e;if(0==e&&!b)break;f++;if(b&&f==b)break}b||(b=f);e="";if(128>d){for(;0<b;)d=String.fromCharCode.apply(String,E.subarray(a,a+Math.min(b,1024))),e=e?e+d:d,a+=1024,b-=1024;return e}return c.UTF8ToString(a)}c.Pointer_stringify=z;
+c.AsciiToString=function(a){for(var b="";;){var d=B[a++>>0];if(!d)return b;b+=String.fromCharCode(d)}};c.stringToAscii=function(a,b){return Aa(a,b,!1)};
+function Ba(a,b){for(var d,e,f,l,h,q,r="";;){d=a[b++];if(!d)return r;d&128?(e=a[b++]&63,192==(d&224)?r+=String.fromCharCode((d&31)<<6|e):(f=a[b++]&63,224==(d&240)?d=(d&15)<<12|e<<6|f:(l=a[b++]&63,240==(d&248)?d=(d&7)<<18|e<<12|f<<6|l:(h=a[b++]&63,248==(d&252)?d=(d&3)<<24|e<<18|f<<12|l<<6|h:(q=a[b++]&63,d=(d&1)<<30|e<<24|f<<18|l<<12|h<<6|q))),65536>d?r+=String.fromCharCode(d):(d-=65536,r+=String.fromCharCode(55296|d>>10,56320|d&1023)))):r+=String.fromCharCode(d)}}c.UTF8ArrayToString=Ba;
+c.UTF8ToString=function(a){return Ba(E,a)};
+function Ca(a,b,d,e){if(!(0<e))return 0;var f=d;e=d+e-1;for(var l=0;l<a.length;++l){var h=a.charCodeAt(l);55296<=h&&57343>=h&&(h=65536+((h&1023)<<10)|a.charCodeAt(++l)&1023);if(127>=h){if(d>=e)break;b[d++]=h}else{if(2047>=h){if(d+1>=e)break;b[d++]=192|h>>6}else{if(65535>=h){if(d+2>=e)break;b[d++]=224|h>>12}else{if(2097151>=h){if(d+3>=e)break;b[d++]=240|h>>18}else{if(67108863>=h){if(d+4>=e)break;b[d++]=248|h>>24}else{if(d+5>=e)break;b[d++]=252|h>>30;b[d++]=128|h>>24&63}b[d++]=128|h>>18&63}b[d++]=128|
+h>>12&63}b[d++]=128|h>>6&63}b[d++]=128|h&63}}b[d]=0;return d-f}c.stringToUTF8Array=Ca;c.stringToUTF8=function(a,b,d){return Ca(a,E,b,d)};function Da(a){for(var b=0,d=0;d<a.length;++d){var e=a.charCodeAt(d);55296<=e&&57343>=e&&(e=65536+((e&1023)<<10)|a.charCodeAt(++d)&1023);127>=e?++b:b=2047>=e?b+2:65535>=e?b+3:2097151>=e?b+4:67108863>=e?b+5:b+6}return b}c.lengthBytesUTF8=Da;
+function Ea(){return Fa().replace(/__Z[\w\d_]+/g,function(a){var b;a:{if(c.___cxa_demangle)try{var d=D(a.length);na(a.substr(1),d);var e=D(4),f=c.___cxa_demangle(d,0,0,e);if(0===wa(e,"i32")&&f){b=z(f);break a}}catch(l){b=a;break a}finally{d&&Ga(d),e&&Ga(e),f&&Ga(f)}t.k("warning: build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling");b=a}return a===b?a:a+" ["+b+"]"})}
+function Fa(){var a=Error();if(!a.stack){try{throw Error(0);}catch(b){a=b}if(!a.stack)return"(no stack trace available)"}return a.stack.toString()}c.stackTrace=function(){return Ea()};function Ha(a){0<a%4096&&(a+=4096-a%4096);return a}var buffer,B,E,pa,Ia,C,Ja,ua,va;
+function Ka(){c.HEAP8=B=new Int8Array(buffer);c.HEAP16=pa=new Int16Array(buffer);c.HEAP32=C=new Int32Array(buffer);c.HEAPU8=E=new Uint8Array(buffer);c.HEAPU16=Ia=new Uint16Array(buffer);c.HEAPU32=Ja=new Uint32Array(buffer);c.HEAPF32=ua=new Float32Array(buffer);c.HEAPF64=va=new Float64Array(buffer)}var La=0,u=0,ya=!1,Ma=0,p=0,Na=0,v=0;
+c.reallocBuffer||(c.reallocBuffer=function(a){var b;try{if(ArrayBuffer.b)b=ArrayBuffer.b(buffer,a);else{var d=B;b=new ArrayBuffer(a);(new Int8Array(b)).set(d)}}catch(e){return!1}return Oa(b)?b:!1});function ga(){var a=Math.pow(2,31);if(v>=a)return!1;for(;w<=v;)if(w<a/2)w=Ha(2*w);else{var b=w;w=Ha((3*w+a)/4);if(w<=b)return!1}w=Math.max(w,16777216);if(w>=a)return!1;a=c.reallocBuffer(w);if(!a)return!1;c.buffer=buffer=a;Ka();return!0}var Pa;
+try{Pa=Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype,"byteLength").get),Pa(new ArrayBuffer(4))}catch(Qa){Pa=function(a){return a.byteLength}}for(var Ra=c.TOTAL_STACK||8192,w=c.TOTAL_MEMORY||2097152,G=65536;G<w||G<2*Ra;)G=16777216>G?2*G:G+16777216;G=Math.max(G,16777216);G!==w&&(w=G);c.buffer?buffer=c.buffer:buffer=new ArrayBuffer(w);Ka();C[0]=255;if(255!==E[0]||0!==E[3])throw"Typed arrays 2 must be run on a little-endian system";c.HEAP=void 0;c.buffer=buffer;
+c.HEAP8=B;c.HEAP16=pa;c.HEAP32=C;c.HEAPU8=E;c.HEAPU16=Ia;c.HEAPU32=Ja;c.HEAPF32=ua;c.HEAPF64=va;function H(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b();else{var d=b.da;"number"===typeof d?void 0===b.r?t.h("v",d):t.h("vi",d,[b.r]):d(void 0===b.r?null:b.r)}}}var Sa=[],Ta=[],Ua=[],I=[],Va=[],za=!1;function Wa(a){Sa.unshift(a)}c.addOnPreRun=Wa;c.addOnInit=function(a){Ta.unshift(a)};function Xa(a){Ua.unshift(a)}c.addOnPreMain=Xa;c.addOnExit=function(a){I.unshift(a)};
+function Ya(a){Va.unshift(a)}c.addOnPostRun=Ya;function Za(a,b,d){d=Array(0<d?d:Da(a)+1);a=Ca(a,d,0,d.length);b&&(d.length=a);return d}c.intArrayFromString=Za;c.intArrayToString=function(a){for(var b=[],d=0;d<a.length;d++){var e=a[d];255<e&&(e&=255);b.push(String.fromCharCode(e))}return b.join("")};function na(a,b,d){a=Za(a,d);for(d=0;d<a.length;)B[b+d>>0]=a[d],d+=1}c.writeStringToMemory=na;function ma(a,b){for(var d=0;d<a.length;d++)B[b++>>0]=a[d]}c.writeArrayToMemory=ma;
+function Aa(a,b,d){for(var e=0;e<a.length;++e)B[b++>>0]=a.charCodeAt(e);d||(B[b>>0]=0)}c.writeAsciiToMemory=Aa;Math.imul&&-5===Math.imul(4294967295,5)||(Math.imul=function(a,b){var d=a&65535,e=b&65535;return d*e+((a>>>16)*e+d*(b>>>16)<<16)|0});Math.ia=Math.imul;Math.clz32||(Math.clz32=function(a){a=a>>>0;for(var b=0;32>b;b++)if(a&1<<31-b)return b;return 32});Math.$=Math.clz32;var qa=Math.abs,ta=Math.ceil,sa=Math.floor,ra=Math.min,J=0,$a=null,ab=null;
+function bb(){J++;c.monitorRunDependencies&&c.monitorRunDependencies(J)}c.addRunDependency=bb;function cb(){J--;c.monitorRunDependencies&&c.monitorRunDependencies(J);if(0==J&&(null!==$a&&(clearInterval($a),$a=null),ab)){var a=ab;ab=null;a()}}c.removeRunDependency=cb;c.preloadedImages={};c.preloadedAudios={};var K=null,db=[function(a,b){throw"Array index "+a+" out of bounds: [0,"+b+")";}],La=8,u=La+1097872;Ta.push();var K="cld-worker.js.mem",eb=u,u=u+16;c._i64Add=fb;c._i64Subtract=gb;
+function hb(a){c.___errno_location&&(C[c.___errno_location()>>2]=a);return a}function ib(){return!!ib.b}var jb=0,kb=[],L={};function lb(a){if(!a||L[a])return a;for(var b in L)if(L[b].D===a)return b;return a}
+function mb(){var a=jb;if(!a)return(M.setTempRet0(0),0)|0;var b=L[a],d=b.type;if(!d)return(M.setTempRet0(0),a)|0;var e=Array.prototype.slice.call(arguments);c.___cxa_is_pointer_type(d);mb.buffer||(mb.buffer=D(4));C[mb.buffer>>2]=a;for(var a=mb.buffer,f=0;f<e.length;f++)if(e[f]&&c.___cxa_can_catch(e[f],d,a))return a=C[a>>2],b.D=a,(M.setTempRet0(e[f]),a)|0;a=C[a>>2];return(M.setTempRet0(d),a)|0}c._memset=nb;function ob(a,b){I.push(function(){t.h("vi",a,[b])});ob.level=I.length}c._bitshift64Lshr=pb;
+c._bitshift64Shl=qb;function rb(a,b){rb.b||(rb.b={});a in rb.b||(t.h("v",b),rb.b[a]=1)}c._memcpy=sb;var tb=0;function N(){tb+=4;return C[tb-4>>2]}var ub={},vb={};function F(a){F.b||(v=Ha(v),F.b=!0,assert(t.d),F.f=t.d,t.d=function(){y("cannot dynamically allocate, sbrk now has control")});var b=v;return 0==a||F.f(a)?b:4294967295}c._memmove=wb;var xb=1;
+function Q(a,b){tb=b;try{var d=N(),e=N(),f=N(),l=0;Q.buffer||(Q.b=[null,[],[]],Q.g=function(a,b){var d=Q.b[a];assert(d);0===b||10===b?((1===a?c.print:c.printErr)(Ba(d,0)),d.length=0):d.push(b)});for(var h=0;h<f;h++){for(var q=C[e+8*h>>2],r=C[e+(8*h+4)>>2],A=0;A<r;A++)Q.g(d,E[q+A]);l+=r}return l}catch(X){return"undefined"!==typeof FS&&X instanceof FS.B||y(X),-X.G}}function D(a){return t.d(a+8)+8&4294967288}c._malloc=D;
+I.push(function(){var a=c._fflush;a&&a(0);if(a=Q.g){var b=Q.b;b[1].length&&a(1,10);b[2].length&&a(2,10)}});
+var Ma=p=t.F(u),ya=!0,Na=Ma+Ra,v=t.F(Na),yb=xa([8,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,
+0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0],"i8",3);c.L={Math:Math,Int8Array:Int8Array,Int16Array:Int16Array,Int32Array:Int32Array,Uint8Array:Uint8Array,Uint16Array:Uint16Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array,NaN:NaN,Infinity:Infinity,byteLength:Pa};
+c.M={abort:y,assert:assert,invoke_iiii:function(a,b,d,e){try{return c.dynCall_iiii(a,b,d,e)}catch(f){if("number"!==typeof f&&"longjmp"!==f)throw f;M.setThrew(1,0)}},invoke_viiiii:function(a,b,d,e,f,l){try{c.dynCall_viiiii(a,b,d,e,f,l)}catch(h){if("number"!==typeof h&&"longjmp"!==h)throw h;M.setThrew(1,0)}},invoke_vi:function(a,b){try{c.dynCall_vi(a,b)}catch(d){if("number"!==typeof d&&"longjmp"!==d)throw d;M.setThrew(1,0)}},invoke_ii:function(a,b){try{return c.dynCall_ii(a,b)}catch(d){if("number"!==
+typeof d&&"longjmp"!==d)throw d;M.setThrew(1,0)}},invoke_v:function(a){try{c.dynCall_v(a)}catch(b){if("number"!==typeof b&&"longjmp"!==b)throw b;M.setThrew(1,0)}},invoke_viiiiii:function(a,b,d,e,f,l,h){try{c.dynCall_viiiiii(a,b,d,e,f,l,h)}catch(q){if("number"!==typeof q&&"longjmp"!==q)throw q;M.setThrew(1,0)}},invoke_viiii:function(a,b,d,e,f){try{c.dynCall_viiii(a,b,d,e,f)}catch(l){if("number"!==typeof l&&"longjmp"!==l)throw l;M.setThrew(1,0)}},_pthread_cleanup_pop:function(){assert(ob.level==I.length,
+"cannot pop if something else added meanwhile!");I.pop();ob.level=I.length},___syscall6:function(a,b){tb=b;try{var d=ub.Q();FS.close(d);return 0}catch(e){return"undefined"!==typeof FS&&e instanceof FS.B||y(e),-e.G}},___gxx_personality_v0:function(){},___assert_fail:function(a,b,d,e){ia=!0;throw"Assertion failed: "+z(a)+", at: "+[b?z(b):"unknown filename",d,e?z(e):"unknown function"]+" at "+Ea();},___cxa_allocate_exception:function(a){return D(a)},___cxa_find_matching_catch:mb,___setErrNo:hb,_sbrk:F,
+___cxa_begin_catch:function(a){ib.b--;kb.push(a);var b=lb(a);b&&L[b].I++;return a},_emscripten_memcpy_big:function(a,b,d){E.set(E.subarray(b,b+d),a);return a},___resumeException:function(a){jb||(jb=a);var b=lb(a);b&&(L[b].I=0);throw a+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch.";},__ZSt18uncaught_exceptionv:ib,_sysconf:function(a){switch(a){case 30:return 4096;case 85:return G/4096;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;
+case 79:return 0;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;
+case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===typeof navigator?navigator.hardwareConcurrency||1:1}hb(22);return-1},
+_pthread_getspecific:function(a){return vb[a]||0},_pthread_self:function(){return 0},_pthread_once:rb,_pthread_key_create:function(a){if(0==a)return 22;C[a>>2]=xb;vb[xb]=0;xb++;return 0},_emscripten_asm_const_iii:function(a,b,d){return db[a](b,d)},_pthread_setspecific:function(a,b){if(!(a in vb))return 22;vb[a]=b;return 0},___cxa_throw:function(a,b,d){L[a]={a:a,D:a,type:b,aa:d,I:0};jb=a;"uncaught_exception"in ib?ib.b++:ib.b=1;throw a+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch.";
+},_abort:function(){c.abort()},_pthread_cleanup_push:ob,_time:function(a){var b=Date.now()/1E3|0;a&&(C[a>>2]=b);return b},___syscall140:function(a,b){tb=b;try{var d=ub.Q(),e=N(),f=N(),l=N(),h=N();assert(0===e);FS.ja(d,f,h);C[l>>2]=d.position;d.T&&0===f&&0===h&&(d.T=null);return 0}catch(q){return"undefined"!==typeof FS&&q instanceof FS.B||y(q),-q.G}},___syscall146:Q,STACKTOP:p,STACK_MAX:Na,tempDoublePtr:eb,ABORT:ia,cttz_i8:yb};// EMSCRIPTEN_START_ASM
+
+var M=(function(global,env,buffer) {
+"almost asm";var a=global.Int8Array;var b=global.Int16Array;var c=global.Int32Array;var d=global.Uint8Array;var e=global.Uint16Array;var f=global.Uint32Array;var g=global.Float32Array;var h=global.Float64Array;var i=new a(buffer);var j=new b(buffer);var k=new c(buffer);var l=new d(buffer);var m=new e(buffer);var n=new f(buffer);var o=new g(buffer);var p=new h(buffer);var q=global.byteLength;var r=env.STACKTOP|0;var s=env.STACK_MAX|0;var t=env.tempDoublePtr|0;var u=env.ABORT|0;var v=env.cttz_i8|0;var w=0;var x=0;var y=0;var z=0;var A=global.NaN,B=global.Infinity;var C=0,D=0,E=0,F=0,G=0.0,H=0,I=0,J=0,K=0.0;var L=0;var M=0;var N=0;var O=0;var P=0;var Q=0;var R=0;var S=0;var T=0;var U=0;var V=global.Math.floor;var W=global.Math.abs;var X=global.Math.sqrt;var Y=global.Math.pow;var Z=global.Math.cos;var _=global.Math.sin;var $=global.Math.tan;var aa=global.Math.acos;var ba=global.Math.asin;var ca=global.Math.atan;var da=global.Math.atan2;var ea=global.Math.exp;var fa=global.Math.log;var ga=global.Math.ceil;var ha=global.Math.imul;var ia=global.Math.min;var ja=global.Math.clz32;var ka=env.abort;var la=env.assert;var ma=env.invoke_iiii;var na=env.invoke_viiiii;var oa=env.invoke_vi;var pa=env.invoke_ii;var qa=env.invoke_v;var ra=env.invoke_viiiiii;var sa=env.invoke_viiii;var ta=env._pthread_cleanup_pop;var ua=env.___syscall6;var va=env.___gxx_personality_v0;var wa=env.___assert_fail;var xa=env.___cxa_allocate_exception;var ya=env.___cxa_find_matching_catch;var za=env.___setErrNo;var Aa=env._sbrk;var Ba=env.___cxa_begin_catch;var Ca=env._emscripten_memcpy_big;var Da=env.___resumeException;var Ea=env.__ZSt18uncaught_exceptionv;var Fa=env._sysconf;var Ga=env._pthread_getspecific;var Ha=env._pthread_self;var Ia=env._pthread_once;var Ja=env._pthread_key_create;var Ka=env._emscripten_asm_const_iii;var La=env._pthread_setspecific;var Ma=env.___cxa_throw;var Na=env._abort;var Oa=env._pthread_cleanup_push;var Pa=env._time;var Qa=env.___syscall140;var Ra=env.___syscall146;var Sa=0.0;function Ta(newBuffer){if(q(newBuffer)&16777215||q(newBuffer)<=16777215||q(newBuffer)>2147483648)return false;i=new a(newBuffer);j=new b(newBuffer);k=new c(newBuffer);l=new d(newBuffer);m=new e(newBuffer);n=new f(newBuffer);o=new g(newBuffer);p=new h(newBuffer);buffer=newBuffer;return true}
+// EMSCRIPTEN_START_FUNCS
+function $a(a){a=a|0;var b=0;b=r;r=r+a|0;r=r+15&-16;return b|0}function ab(){return r|0}function bb(a){a=a|0;r=a}function cb(a,b){a=a|0;b=b|0;r=a;s=b}function db(a,b){a=a|0;b=b|0;if(!w){w=a;x=b}}function eb(a){a=a|0;i[t>>0]=i[a>>0];i[t+1>>0]=i[a+1>>0];i[t+2>>0]=i[a+2>>0];i[t+3>>0]=i[a+3>>0]}function fb(a){a=a|0;i[t>>0]=i[a>>0];i[t+1>>0]=i[a+1>>0];i[t+2>>0]=i[a+2>>0];i[t+3>>0]=i[a+3>>0];i[t+4>>0]=i[a+4>>0];i[t+5>>0]=i[a+5>>0];i[t+6>>0]=i[a+6>>0];i[t+7>>0]=i[a+7>>0]}function gb(a){a=a|0;L=a}function hb(){return L|0}function ib(a,b){a=a|0;b=b|0;var c=0,d=0;d=980497+(a<<3&2040)|0;c=a>>>8;if(c&255|0)nd(b,c&255,l[d+5>>0]|0);c=a>>>16;if(c&255|0)nd(b,c&255,l[d+6>>0]|0);c=a>>>24;if(c|0)nd(b,c&255,l[d+7>>0]|0);return}function jb(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,m=0,n=0,o=0;o=r;r=r+16|0;n=o+4|0;m=o;f=a+b|0;g=a+c|0;j=e+8|0;h=k[e+4>>2]|0;b=k[j>>2]|0;c=(i[f>>0]|0)==32?f+1|0:f;do{if(c>>>0>=g>>>0)break;k[n>>2]=c;f=l[1009576+(l[c>>0]|0)>>0]|0;k[m>>2]=f;c=c+f|0;f=ud(d,n,m)|0;if(f<<24>>24){k[e+32+(b<<3)>>2]=c-a;k[e+32+(b<<3)+4>>2]=f&255;b=b+1|0}}while((b|0)<(h|0));k[j>>2]=b;n=c-a|0;k[e+32+(b<<3)>>2]=n;k[e+32+(k[j>>2]<<3)+4>>2]=0;r=o;return n|0}function kb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;q=a+c|0;z=f+12|0;u=k[f+4>>2]|0;A=f+16|0;v=u+-1|0;w=d+16|0;r=d+12|0;y=a;s=e+16|0;t=e+12|0;h=k[z>>2]|0;g=k[A>>2]|0;p=a+b|0;while(1){if(p>>>0>=q>>>0){c=p;break}b=l[1009576+(l[p>>0]|0)>>0]|0;c=p+b|0;b=(l[1009576+(l[c>>0]|0)>>0]|0)+b|0;do if(b>>>0>5){o=qb(p,b)|0;a=k[d>>2]|0;m=k[w>>2]|0;n=(o>>>12)+o|0;i=(k[r>>2]|0)+-1&n;j=m&o;b=k[a+(i<<4)>>2]|0;if((b^j)&m){b=k[a+(i<<4)+4>>2]|0;if((b^j)&m){b=k[a+(i<<4)+8>>2]|0;if((b^j)&m){b=k[a+(i<<4)+12>>2]|0;if(!((b^j)&m))x=8}else x=8}else x=8}else x=8;if((x|0)==8){x=0;if(b){k[f+8040+(h<<3)>>2]=p-y;k[f+8040+(h<<3)+4>>2]=b&~m;h=h+1|0}}j=k[e>>2]|0;m=k[s>>2]|0;a=(k[t>>2]|0)+-1&n;i=m&o;b=k[j+(a<<4)>>2]|0;if((b^i)&m){b=k[j+(a<<4)+4>>2]|0;if((b^i)&m){b=k[j+(a<<4)+8>>2]|0;if((b^i)&m){b=k[j+(a<<4)+12>>2]|0;if((b^i)&m)break}}}if(b){k[f+16048+(g<<3)>>2]=p-y;k[f+16048+(g<<3)+4>>2]=b&~m;g=g+1|0}}while(0);if((g|0)<(v|0)&(h|0)<(u|0))p=c;else break}k[z>>2]=h;k[A>>2]=g;z=c-y|0;k[f+8040+(h<<3)>>2]=z;k[f+8040+(h<<3)+4>>2]=0;k[f+16048+(k[A>>2]<<3)>>2]=z;k[f+16048+(k[A>>2]<<3)+4>>2]=0;return}function lb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0;D=r;r=r+16|0;C=D;t=a+b|0;u=a+c|0;B=f+8|0;b=k[B>>2]|0;A=k[f+4>>2]|0;v=C;k[v>>2]=0;k[v+4>>2]=0;v=C+4|0;w=d+16|0;x=d+12|0;y=e+12|0;z=e+16|0;g=0;c=(i[t>>0]|0)==32?t+1|0:t;do{if(c>>>0>=u>>>0)break;s=c+(l[979972+(l[c>>0]|0)>>0]|0)|0;s=s+(l[979972+(l[s>>0]|0)>>0]|0)|0;t=s+(l[979972+(l[s>>0]|0)>>0]|0)|0;t=t+(l[979972+(l[t>>0]|0)>>0]|0)|0;q=c;p=sb(c,t-q|0)|0;do if(!((p|0)==(k[C>>2]|0)?1:(p|0)==(k[v>>2]|0))){j=k[d>>2]|0;h=k[w>>2]|0;o=(p>>>12)+p|0;m=(k[x>>2]|0)+-1&o;n=h&p;c=k[j+(m<<4)>>2]|0;if((c^n)&h){c=k[j+(m<<4)+4>>2]|0;if((c^n)&h){c=k[j+(m<<4)+8>>2]|0;if((c^n)&h){c=k[j+(m<<4)+12>>2]|0;if(!((c^n)&h))m=8;else m=9}else m=8}else m=8}else m=8;if((m|0)==8){m=0;if(!c)m=9;else j=0}if((m|0)==9){c=k[y>>2]|0;if(!c)break;n=k[e>>2]|0;h=k[z>>2]|0;j=c+-1&o;m=h&p;c=k[n+(j<<4)>>2]|0;if((c^m)&h){c=k[n+(j<<4)+4>>2]|0;if((c^m)&h){c=k[n+(j<<4)+8>>2]|0;if((c^m)&h){c=k[n+(j<<4)+12>>2]|0;if((c^m)&h)break}}}if(!c)break;else j=-2147483648}k[C+(g<<2)>>2]=p;k[f+32+(b<<3)>>2]=q-a;k[f+32+(b<<3)+4>>2]=c&~h|j;b=b+1|0;g=g&1^1}while(0);c=(i[t>>0]|0)==32?t:s;if(c>>>0<u>>>0)c=c+(l[980228+(l[c>>0]|0)>>0]|0)|0;else c=u}while((b|0)<(A|0));k[B>>2]=b;e=c-a|0;k[f+32+(b<<3)>>2]=e;k[f+32+(k[B>>2]<<3)+4>>2]=0;r=D;return e|0}function mb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0;J=r;r=r+16|0;F=J;u=a+b|0;A=a+(c+1)|0;H=f+12|0;g=k[H>>2]|0;B=k[f+4>>2]|0;I=f+16|0;b=k[I>>2]|0;C=B+-1|0;k[F>>2]=0;k[F+4>>2]=0;k[F+8>>2]=0;k[F+12>>2]=0;u=(i[u>>0]|0)==32?u+1|0:u;x=F+8|0;y=e+16|0;z=e+12|0;E=a;v=d+16|0;w=d+12|0;n=0;a=g;g=0;q=u;D=u;j=u;while(1){if(D>>>0>=A>>>0){c=D;break}c=i[D>>0]|0;if(c<<24>>24==32){t=u;p=ub(u,j-t|0)|0;s=L;n=F;o=x;do if(!(((p|0)==(k[n>>2]|0)?(s|0)==(k[n+4>>2]|0):0)|((p|0)==(k[o>>2]|0)?(s|0)==(k[o+4>>2]|0):0))){h=F+(g<<3)|0;k[h>>2]=p;k[h+4>>2]=s;g=1-g|0;h=F+(g<<3)|0;c=k[h>>2]|0;h=k[h+4>>2]|0;do if(!((c|0)==0&(h|0)==0|(c|0)==(p|0)&(h|0)==(s|0))){c=vb(c,h,p,s)|0;j=L;m=k[e>>2]|0;o=k[y>>2]|0;n=k[z>>2]|0;h=rf(c|0,j|0,12)|0;h=of(h|0,L|0,c|0,j|0)|0;h=n+-1&h;j=rf(c|0,j|0,4)|0;j=o&j;c=k[m+(h<<4)>>2]|0;if((c^j)&o){c=k[m+(h<<4)+4>>2]|0;if((c^j)&o){c=k[m+(h<<4)+8>>2]|0;if((c^j)&o){c=k[m+(h<<4)+12>>2]|0;if((c^j)&o){c=n;break}}}}if(!c)c=n;else{k[f+16048+(b<<3)>>2]=q-E;k[f+16048+(b<<3)+4>>2]=c&~o;c=n;b=b+1|0}}else{c=k[z>>2]|0;o=k[y>>2]|0;m=k[e>>2]|0}while(0);q=rf(p|0,s|0,12)|0;q=of(q|0,L|0,p|0,s|0)|0;j=c+-1&q;p=rf(p|0,s|0,4)|0;h=o&p;c=k[m+(j<<4)>>2]|0;if((c^h)&o){c=k[m+(j<<4)+4>>2]|0;if((c^h)&o){c=k[m+(j<<4)+8>>2]|0;if((c^h)&o){c=k[m+(j<<4)+12>>2]|0;if(!((c^h)&o))G=17}else G=17}else G=17}else G=17;if((G|0)==17){G=0;if(c){k[f+16048+(b<<3)>>2]=t-E;k[f+16048+(b<<3)+4>>2]=c&~o;b=b+1|0}}m=k[d>>2]|0;n=k[v>>2]|0;h=(k[w>>2]|0)+-1&q;j=n&p;c=k[m+(h<<4)>>2]|0;if((c^j)&n){c=k[m+(h<<4)+4>>2]|0;if((c^j)&n){c=k[m+(h<<4)+8>>2]|0;if((c^j)&n){c=k[m+(h<<4)+12>>2]|0;if((c^j)&n)break}}}if(c){k[f+8040+(a<<3)>>2]=t-E;k[f+8040+(a<<3)+4>>2]=c&~n;a=a+1|0}}while(0);m=D+1|0;c=i[D>>0]|0;o=0;h=u;j=m}else{o=n+1|0;h=q;m=u}c=D+(l[1009576+(c&255)>>0]|0)|0;if((b|0)<(C|0)&(a|0)<(B|0)){n=o;q=h;D=c;j=(o|0)<9?c:j;u=m}else break}k[H>>2]=a;k[I>>2]=b;H=c-E|0;k[f+8040+(a<<3)>>2]=H;k[f+8040+(a<<3)+4>>2]=0;k[f+16048+(k[I>>2]<<3)>>2]=H;k[f+16048+(k[I>>2]<<3)+4>>2]=0;r=J;return}function nb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=(c|0)<8?c*12|0:100;e=c*5>>3;e=(e|0)<3?3:(e|0)>16?16:e;c=a-b|0;if((c|0)<(e|0))if((c|0)<1)d=0;else{b=(c*100|0)/(e|0)|0;d=(d|0)<(b|0)?d:b}return d|0}function ob(a,b){a=a|0;b=b|0;var c=0.0;if(b)if(a){if((b|0)>(a|0))c=+(b|0)/+(a|0);else c=+(a|0)/+(b|0);if(!(c<=1.5))if(c>4.0)a=0;else a=~~((4.0-c)*100.0/2.5);else a=100}else a=0;else a=100;return a|0}function pb(a,b){a=a|0;b=b|0;a=((Hc(a)|0)&255)<<8;return a|(l[980484+b>>0]|0)|0}function qb(a,b){a=a|0;b=b|0;var c=0;do if(b){c=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;if((b|0)<5){c=k[120+((b&3)<<2)>>2]&c;c=c>>>3^c;break}else{a=a+4|0;a=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=(a<<18^a)+(c>>>3^c)|0;break}}else c=0;while(0);return c|0}function rb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;do if((b|0)>=5){d=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;d=d>>>3^d;e=a+4|0;e=l[e>>0]|l[e+1>>0]<<8|l[e+2>>0]<<16|l[e+3>>0]<<24;if((b|0)<9){a=k[120+((b&3)<<2)>>2]&e;d=(a<<4^a)+(d^c)|0;break}else{a=a+8|0;a=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);d=(d^c)+(e<<4^e)+(a<<2^a)|0;break}}else{d=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);d=d^c^d>>>3}while(0);return d|0}function sb(a,b){a=a|0;b=b|0;var c=0;if(!b)b=0;else{c=(i[a+-1>>0]|0)==32?17476:0;b=rb(a,b,(i[a+b>>0]|0)==32?c|1145307136:c)|0}return b|0}function tb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0;f=(i[a+-1>>0]|0)==32;e=f?c|17476:c;f=f?d:d;d=(i[a+b>>0]|0)==32;e=d?e|1145307136:e;f=d?f:f;switch(b+-1>>2|0){case 0:{b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=b;d=0;b=(rf(b|0,0,3)|0)^b;a=L;break}case 1:{h=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;j=rf(h|0,0,3)|0;g=L;c=a+4|0;b=k[120+((b&3)<<2)>>2]&(l[c>>0]|l[c+1>>0]<<8|l[c+2>>0]<<16|l[c+3>>0]<<24);c=of(b|0,0,h|0,0)|0;d=L;b=of((sf(b|0,0,4)|0)^b|0,L|0,j^h|0,g|0)|0;a=L;break}case 2:{j=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;g=rf(j|0,0,3)|0;h=L;m=a+4|0;m=l[m>>0]|l[m+1>>0]<<8|l[m+2>>0]<<16|l[m+3>>0]<<24;d=of(m|0,0,j|0,0)|0;c=L;h=of((sf(m|0,0,4)|0)^m|0,L|0,g^j|0,h|0)|0;j=L;a=a+8|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(h|0,j|0,(sf(b|0,0,2)|0)^b|0,L|0)|0;a=L;break}case 3:{m=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;j=rf(m|0,0,3)|0;h=L;g=a+4|0;g=l[g>>0]|l[g+1>>0]<<8|l[g+2>>0]<<16|l[g+3>>0]<<24;c=of(g|0,0,m|0,0)|0;d=L;h=of((sf(g|0,0,4)|0)^g|0,L|0,j^m|0,h|0)|0;m=L;j=a+8|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(sf(j|0,0,2)|0)^j|0,L|0)|0;m=L;a=a+12|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(j|0,m|0,(rf(b|0,0,8)|0)^b|0,L|0)|0;a=L;break}case 4:{m=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;h=rf(m|0,0,3)|0;j=L;g=a+4|0;g=l[g>>0]|l[g+1>>0]<<8|l[g+2>>0]<<16|l[g+3>>0]<<24;d=of(g|0,0,m|0,0)|0;c=L;j=of((sf(g|0,0,4)|0)^g|0,L|0,h^m|0,j|0)|0;m=L;h=a+8|0;h=l[h>>0]|l[h+1>>0]<<8|l[h+2>>0]<<16|l[h+3>>0]<<24;c=of(d|0,c|0,h|0,0)|0;d=L;h=of(j|0,m|0,(sf(h|0,0,2)|0)^h|0,L|0)|0;m=L;j=a+12|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(rf(j|0,0,8)|0)^j|0,L|0)|0;m=L;a=a+16|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(j|0,m|0,(rf(b|0,0,4)|0)^b|0,L|0)|0;a=L;break}default:{m=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;j=rf(m|0,0,3)|0;h=L;g=a+4|0;g=l[g>>0]|l[g+1>>0]<<8|l[g+2>>0]<<16|l[g+3>>0]<<24;c=of(g|0,0,m|0,0)|0;d=L;h=of((sf(g|0,0,4)|0)^g|0,L|0,j^m|0,h|0)|0;m=L;j=a+8|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(sf(j|0,0,2)|0)^j|0,L|0)|0;m=L;h=a+12|0;h=l[h>>0]|l[h+1>>0]<<8|l[h+2>>0]<<16|l[h+3>>0]<<24;c=of(d|0,c|0,h|0,0)|0;d=L;h=of(j|0,m|0,(rf(h|0,0,8)|0)^h|0,L|0)|0;m=L;j=a+16|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(rf(j|0,0,4)|0)^j|0,L|0)|0;m=L;a=a+20|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(j|0,m|0,(rf(b|0,0,6)|0)^b|0,L|0)|0;a=L}}j=rf(c|0,d|0,17)|0;j=of(j|0,L|0,c|0,d|0)|0;m=L;h=rf(j|0,m|0,9)|0;m=of(h|0,L|0,j|0,m|0)|0;m=of(0,m&255|0,b^e|0,a^f|0)|0;return m|0}function ub(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;if(!b){b=0;a=0}else{c=(i[a+-1>>0]|0)==32;e=c?17476:0;c=c?0:0;d=(i[a+b>>0]|0)==32;a=tb(a,b,d?e|1145307136:e,d?c:c)|0;b=L}L=b;return a|0}function vb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=rf(a|0,b|0,13)|0;e=L;b=sf(a|0,b|0,51)|0;d=of(f|b|0,e|L|0,c|0,d|0)|0;return d|0}function wb(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,i=0;h=r;r=r+48|0;i=h+24|0;k[i>>2]=0;k[i+4>>2]=1097857;k[i+8>>2]=23;k[i+12>>2]=26;f=kc(a,b,c,i,0,d,e,h,f,g)|0;r=h;return ((f|0)==26?0:f)|0}function xb(a,b,c,d,e,f,g,h,i){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;return kc(a,b,c,d,0,e,f,g,h,i)|0}function yb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;a:do if(a<<16>>16){d=a&1023;e=k[b>>2]|0;do if((e|0)>0){c=0;while(1){f=b+4+(c<<1)|0;g=j[f>>1]|0;c=c+1|0;if((g&1023|0)==(d|0)){c=5;break}if((c|0)>=(e|0)){c=6;break}}if((c|0)==5){b=g<<16>>16>>10;a=a<<16>>16>>10;j[f>>1]=((b|0)>=(a|0)?b:a)<<10|d;break a}else if((c|0)==6)if((e|0)>13)break a;else break}while(0);k[b>>2]=e+1;j[b+4+(e<<1)>>1]=a}while(0);return}function zb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;a:do if(a<<16>>16){d=a&1023;e=k[b>>2]|0;do if((e|0)>0){c=0;while(1){f=b+4+(c<<1)|0;g=m[f>>1]|0;c=c+1|0;if((g&1023|0)==(d|0)){c=5;break}if((c|0)>=(e|0)){c=6;break}}if((c|0)==5){j[f>>1]=g+2048&64512|d;break a}else if((c|0)==6)if((e|0)>13)break a;else break}while(0);k[b>>2]=e+1;j[b+4+(e<<1)>>1]=a}while(0);return}function Ab(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0;if((k[a>>2]|0)>4){g=0;do{f=j[a+4+(g<<1)>>1]|0;d=f<<16>>16>>10;d=(d|0)>-1?d:0-d|0;a:do if((g|0)>0){e=g;while(1){b=e+-1|0;c=j[a+4+(b<<1)>>1]|0;h=c<<16>>16>>10;if((((h|0)>-1?h:0-h|0)|0)>=(d|0)){b=e;break a}j[a+4+(e<<1)>>1]=c;if((e|0)>1)e=b;else break}}else b=g;while(0);j[a+4+(b<<1)>>1]=f;g=g+1|0}while((g|0)<(k[a>>2]|0));k[a>>2]=4}return}function Bb(a){a=a|0;var b=0,c=0,d=0;d=i[a>>0]|0;b=(d&1)==0;d=b?(d&255)>>>1:k[a+4>>2]|0;if((d|0)>0){a=b?a+1|0:k[a+8>>2]|0;b=0;c=0;do{b=((i[a+c>>0]|0)==44&1)+b|0;c=c+1|0}while((c|0)!=(d|0))}else b=0;return b|0}function Cb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;g=c;c=0;a:while(1){if((c|0)<(g|0))f=c;else{c=0;break}while(1){d=f+g>>1;c=b+(d*12|0)|0;e=_d(k[c>>2]|0,a)|0;if((e|0)>=0)break;c=d+1|0;if((c|0)<(g|0))f=c;else{c=0;break a}}if((e|0)>0){g=d;c=f}else break}return c|0}function Db(a){a=a|0;var b=0,c=0,d=0,e=0,f=0;f=181;b=0;a:while(1){if((b|0)<(f|0))e=b;else{b=0;break}while(1){c=e+f>>1;b=5776+(c<<3)|0;d=_d(k[b>>2]|0,a)|0;if((d|0)>=0)break;b=c+1|0;if((b|0)<(f|0))e=b;else{b=0;break a}}if((d|0)>0){f=c;b=e}else break}return b|0}function Eb(a){a=a|0;Ba(a|0)|0;cf()}function Fb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=c+-3|0;a:do if((d|0)>(b|0))do{e=a+b|0;e=l[e>>0]|l[e+1>>0]<<8|l[e+2>>0]<<16|l[e+3>>0]<<24;if((e^1010580540)+-16843009&(e&-2139062144^-2139062144)|0)break a;b=b+4|0}while((b|0)<(d|0));while(0);b:do if((b|0)<(c|0))while(1){if((i[a+b>>0]|0)==60)break b;b=b+1|0;if((b|0)>=(c|0)){b=-1;break}}else b=-1;while(0);return b|0}function Gb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;a:do if((b|0)<(c|0))while(1){b:do switch(i[a+b>>0]|0){case 61:break a;case 34:{e=b+1|0;if((e|0)<(c|0)){d=b;b=e;while(1){switch(i[a+b>>0]|0){case 34:break b;case 92:{d=d+2|0;break}default:d=b}b=d+1|0;if((b|0)>=(c|0))break b}}else b=e;break}case 39:{e=b+1|0;if((e|0)<(c|0)){d=b;b=e;while(1){switch(i[a+b>>0]|0){case 39:break b;case 92:{d=d+2|0;break}default:d=b}b=d+1|0;if((b|0)>=(c|0))break b}}else b=e;break}default:{}}while(0);b=b+1|0;if((b|0)>=(c|0)){b=-1;break a}}else b=-1;while(0);return b|0}function Hb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;g=Vd(d)|0;a:do if((c-b|0)>=(g|0)){f=g+b|0;while(1){if((c|0)<=(f|0))break;e=c+-1|0;if((i[a+e>>0]|0)==32)c=e;else break}c=c-g|0;if((c|0)>=(b|0)){c=a+c|0;if((g|0)>0){e=0;while(1){if((i[c+e>>0]|32|0)!=(i[d+e>>0]|0)){c=0;break a}e=e+1|0;if((e|0)>=(g|0)){c=1;break}}}else c=1}else c=0}else c=0;while(0);return c|0}function Ib(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;e=Vd(d)|0;a:do if((c-b|0)>=(e|0)){c=c-e|0;b:do if((c|0)>(b|0))while(1){switch(i[a+b>>0]|0){case 39:case 34:case 32:break;default:break b}b=b+1|0;if((b|0)>=(c|0))break b}while(0);b=a+b|0;if((e|0)>0){c=0;while(1){if((i[b+c>>0]|32|0)!=(i[d+c>>0]|0)){b=0;break a}c=c+1|0;if((c|0)>=(e|0)){b=1;break}}}else b=1}else b=0;while(0);return b|0}function Jb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;if((c|0)<(d|0)){f=1;do{e=l[b+c>>0]|0;g=(l[984554+e>>0]|0)>>>(f*3|0);f=g&3;do if(g&4|0)if(!f){Ce(a,1,i[984810+e>>0]|0);break}else{Ce(a,1,44);break}while(0);c=c+1|0}while((c|0)!=(d|0));if(!f)Ce(a,1,44)}return}function Kb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;a:do if((c|0)<(d|0)){b:while(1){switch(i[b+c>>0]|0){case 39:case 34:break b;case 32:break;default:{f=5;break a}}c=c+1|0;if((c|0)>=(d|0)){f=5;break a}}if((c|0)>=0){e=c+1|0;c:do if((e|0)<(d|0)){c=e;d:while(1){switch(i[b+c>>0]|0){case 39:case 34:break d;case 62:{f=8;break d}case 61:{f=9;break d}case 60:{f=10;break d}case 38:{f=11;break d}default:{}}c=c+1|0;if((c|0)>=(d|0))break c}if((f|0)==8)c=c+-1|0;else if((f|0)==9)c=c+-1|0;else if((f|0)==10)c=c+-1|0;else if((f|0)==11)c=c+-1|0;if((c|0)>=0){Jb(a,b,e,c);break a}}while(0);ze(a,1097857,0)}else f=5}else f=5;while(0);if((f|0)==5)ze(a,1097857,0);return}function Lb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,l=0,m=0,n=0,o=0;o=r;r=r+32|0;n=o;d=i[a>>0]|0;l=a+4|0;e=k[l>>2]|0;c=(d&1)==0?(d&255)>>>1:e;if((c|0?(Bb(a)|0)<=4:0)?(c|0)>0:0){g=a+8|0;h=a+1|0;f=0;do{c=Ie(a,f)|0;if((c|0)==-1){if(!(d&1))e=(d&255)>>>1}else e=c;c=e-f|0;do if((c|0)<17){tf(n|0,((d&1)==0?h:k[g>>2]|0)+f|0,c|0)|0;i[n+c>>0]=0;c=Cb(n,136,213)|0;if(c|0){yb(j[c+8>>1]|0,b);yb(j[c+10>>1]|0,b);break}c=me(n,45)|0;if(c|0)i[c>>0]=0;if((Vd(n)|0)<4?(m=Cb(n,2692,257)|0,m|0):0){yb(j[m+8>>1]|0,b);yb(j[m+10>>1]|0,b)}}while(0);f=e+1|0;d=i[a>>0]|0;e=k[l>>2]|0}while((f|0)<(((d&1)==0?(d&255)>>>1:e)|0))}r=o;return}function Mb(a,b){a=a|0;b=b|0;var c=0,d=0;c=r;r=r+16|0;d=c;Jb(d,a,0,Vd(a)|0);Lb(d,b);Ae(d);r=c;return}function Nb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;e=r;r=r+16|0;d=e;c=Vd(a)|0;if((c|0)<=3){ne(d,a);i[d+3>>0]=0;if((c|0)>0){a=0;do{f=d+a|0;i[f>>0]=l[f>>0]|0|32;a=a+1|0}while((a|0)!=(c|0))}a=Db(d)|0;if(a|0){zb(j[a+4>>1]|0,b);zb(j[a+6>>1]|0,b)}}r=e;return}function Ob(a,b){a=a|0;b=b|0;switch(a|0){case 62:case 48:case 46:case 45:case 14:{zb(4112,b);break}case 47:case 20:case 13:{zb(4165,b);break}case 12:case 21:case 11:case 10:{zb(4104,b);break}case 44:case 16:{zb(4105,b);break}default:{}}return}function Pb(a,b){a=a|0;b=b|0;zb(a+8192&65535,b);return}function Qb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0;f=r;r=r+80|0;e=f;d=f+8|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;if((k[b>>2]|0)>0){c=0;do{g=j[b+4+(c<<1)>>1]|0;h=Ec(g&1023)|0;k[e>>2]=h;k[e+4>>2]=g<<16>>16>>10;ue(d,985273,e);Fe(a,d);c=c+1|0}while((c|0)<(k[b>>2]|0))}r=f;return}function Rb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0;D=r;r=r+16|0;C=D;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;y=(c|0)<8192?c:8192;a:do if((y|0)>0){s=C+4|0;t=a+1|0;u=C+8|0;v=C+1|0;w=a+8|0;x=a+4|0;c=0;do{c=Fb(b,c,y)|0;if((c|0)<0)break a;d=c+1|0;if((d|0)<(y|0))c=d;else break a;b:while(1){switch(i[b+c>>0]|0){case 62:break b;case 60:{z=6;break b}case 38:{z=7;break b}default:{}}c=c+1|0;if((c|0)>=(y|0))break a}if((z|0)==6){z=0;c=c+-1|0}else if((z|0)==7){z=0;c=c+-1|0}if((c|0)<0)break a;if((((((!(Ib(b,d,c,985280)|0)?!(Ib(b,d,c,985284)|0):0)?!(Ib(b,d,c,985290)|0):0)?!(Ib(b,d,c,985298)|0):0)?!(Ib(b,d,c,985304)|0):0)?!(Ib(b,d,c,985309)|0):0)?(A=Ib(b,d,c,985312)|0,B=Gb(b,d,c)|0,(B|0)>-1):0){g=B;f=0;e=d;while(1){do if(A){if(Hb(b,e,g,985318)|0?Ib(b,g+1|0,c,985330)|0:0){z=25;break}if(Hb(b,e,g,985348)|0){d=g+1|0;if(!(Ib(b,d,c,985354)|0)?!(f|(Ib(b,d,c,985367)|0)):0){d=0;z=26}else z=25}else z=24}else z=24;while(0);if((z|0)==24)if(f)z=25;else{d=0;z=26}if((z|0)==25)if(Hb(b,e,g,985377)|0){d=1;z=29}else{d=1;z=26}if((z|0)==26){z=0;if(!(Hb(b,e,g,985386)|0)?!(Hb(b,e,g,985392)|0):0)e=g+1|0;else z=29}if((z|0)==29){z=0;p=g+1|0;Kb(C,b,p,c);q=i[C>>0]|0;f=(q&1)==0;q=f?(q&255)>>>1:k[s>>2]|0;c:do if(q|0){e=i[a>>0]|0;if(!(e&1)){e=(e&255)>>>1;g=t}else{e=k[x>>2]|0;g=k[w>>2]|0}l=f?v:k[u>>2]|0;d:do if(e>>>0>=q>>>0){m=g+e|0;n=l+q|0;o=g;if((e|0)<(q|0))break;h=m+(1-q)|0;if((h|0)==(g|0))break;j=i[l>>0]|0;e=g;e:while(1){if((i[e>>0]|0)==j<<24>>24){f=e;g=l;do{g=g+1|0;if((g|0)==(n|0))break e;f=f+1|0}while((i[f>>0]|0)==(i[g>>0]|0))}e=e+1|0;if((e|0)==(h|0))break d}if(!((e|0)==(m|0)|(e-o|0)==-1))break c}while(0);Ee(a,l,q)}while(0);Ae(C);e=p}g=Gb(b,e,c)|0;if((g|0)<=-1)break;else f=d}}c=c+1|0}while((c|0)<(y|0))}while(0);c=i[a>>0]|0;d=(c&1)==0;if(d)e=(c&255)>>>1;else e=k[a+4>>2]|0;if(e>>>0>1){if(d)c=(c&255)>>>1;else c=k[a+4>>2]|0;He(a,c+-1|0)}r=D;return}function Sb(a,b){a=a|0;b=b|0;var c=0;c=(b|0)<32?b:32;a:do if((c|0)>0){b=0;while(1){if((i[a+~b>>0]|0)==32)break a;b=b+1|0;if((b|0)>=(c|0)){b=0;break}}while(1){if((i[a+(0-b)>>0]&-64)<<24>>24!=-128)break a;b=b+1|0;if((b|0)>=(c|0)){b=0;break}}}else b=0;while(0);return b|0}function Tb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;d=(b|0)<32?b:32;c=0;while(1){if((c|0)>=(d|0)){e=3;break}b=c+1|0;if((i[a+c>>0]|0)==32)break;else c=b}a:do if((e|0)==3)if((d|0)>0){b=0;while(1){if((i[a+b>>0]&-64)<<24>>24!=-128)break a;b=b+1|0;if((b|0)>=(d|0)){b=0;break}}}else b=0;while(0);return b|0}function Ub(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0;j=a+b|0;e=k[c>>2]|0;if((b|0)>0){b=0;h=a;do{f=i[h>>0]|0;g=f&255;do if((f&255)>=192){if((g&224|0)==192){g=l[h+1>>0]|0|g<<8;a=2;break}a=i[h+1>>0]|0;f=i[h+2>>0]|0;if((g&240|0)==224){g=(a&255)<<8|g<<16|f&255;a=3;break}else{g=(a&255)<<16|g<<24|(f&255)<<8|(l[h+3>>0]|0);a=4;break}}else a=1;while(0);h=h+a|0;m=d+(e<<2)|0;f=k[m>>2]|0;k[m>>2]=g;b=((g|0)==(f|0)?a:0)+b|0;e=(g^e<<4)&4095}while(h>>>0<j>>>0)}else b=0;k[c>>2]=e;return b|0}function Vb(a,b){a=a|0;b=b|0;var c=0,d=0;c=b&-4;if((c|0)>0){d=0;b=0;do{b=((i[a+d>>0]|0)==32&1)+b+((i[a+(d|1)>>0]|0)==32&1)+((i[a+(d|2)>>0]|0)==32&1)+((i[a+(d|3)>>0]|0)==32&1)|0;d=d+4|0}while((d|0)<(c|0))}else b=0;return b|0}function Wb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;s=a+b|0;e=k[c>>2]|0;if((b|0)>0){q=a;p=0;r=a;m=a;n=0;while(1){g=i[r>>0]|0;j=g&255;f=q+1|0;i[q>>0]=g;do if(g<<24>>24!=32)if((g&255)>=192){if((j&224|0)==192){h=r+1|0;i[f>>0]=i[h>>0]|0;j=l[h>>0]|0|j<<8;f=q+2|0;h=p;o=2;break}h=r+1|0;i[f>>0]=i[h>>0]|0;g=r+2|0;f=q+3|0;i[q+2>>0]=i[g>>0]|0;if((j&240|0)==224){j=(l[h>>0]|0)<<8|j<<16|(l[g>>0]|0);h=p;o=3;break}else{o=r+3|0;i[f>>0]=i[o>>0]|0;j=(l[h>>0]|0)<<16|j<<24|(l[g>>0]|0)<<8|(l[o>>0]|0);f=q+4|0;h=p;o=4;break}}else{h=p;o=1}else{m=(p<<1|0)>(n|0)?m:f;f=m;h=0;o=1;n=0}while(0);r=r+o|0;q=d+(e<<2)|0;g=k[q>>2]|0;k[q>>2]=j;e=(j^e<<4)&4095;if(r>>>0>=s>>>0)break;else{q=f;p=((j|0)==(g|0)?o:0)+h|0;n=o+n|0}}}else f=a;k[c>>2]=e;e=f-a|0;if((e|0)>=(b+-3|0)){if((e|0)<(b|0))i[f>>0]=32}else{i[f>>0]=32;i[f+1>>0]=32;i[f+2>>0]=32;i[f+3>>0]=0}return e|0}function Xb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0;o=r;r=r+16|0;m=o;h=a+b|0;k[m>>2]=0;n=jf(16384)|0;qf(n|0,0,16384)|0;if((b|0)>0){j=h;c=a;l=a+1|0;d=a;e=0;f=a;do{g=j-f|0;g=(g|0)>48?48:g;while(1)if((i[f+g>>0]&-64)<<24>>24==-128)g=g+1|0;else break;p=Vb(f,g)|0;if((p|0)<12&(Ub(f,g,m,n)|0)<19){if(e){p=Tb(f,g)|0;e=g-p|0;f=f+p|0}else e=g;if((e|0)>0){uf(d|0,f|0,e|0)|0;d=d+e|0;g=e;e=0}else{g=e;e=0}}else if(!e){d=d+(0-(Sb(d,d-c|0)|0))|0;if((d|0)==(a|0)){i[a>>0]=32;d=l;e=1}else e=1}else e=1;f=f+g|0}while(f>>>0<h>>>0)}else{c=a;d=a}c=d-c|0;if((c|0)>=(b+-3|0)){if((c|0)<(b|0))i[d>>0]=32}else{i[d>>0]=32;i[d+1>>0]=32;i[d+2>>0]=32;i[d+3>>0]=0}kf(n);r=o;return c|0}function Yb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;e=r;r=r+16|0;d=e;if((b|0)<256)b=0;else{k[d>>2]=0;c=jf(16384)|0;qf(c|0,0,16384)|0;if((Vb(a,256)|0)<64?(Ub(a,256,d,c)|0)<171:0)b=0;else b=1;kf(c)}r=e;return b|0}function Zb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;y=r;r=r+48|0;x=y+32|0;t=y+16|0;s=y;q=0;do{d=j[a+568+(q<<1)>>1]|0;n=d&65535;do if(((((d<<16>>16!=-1?(o=k[a+616+(q<<2)>>2]|0,o|0):0)?(p=(k[a+808+(q<<2)>>2]|0)/(o|0)|0,(d&65535)<165&(p|0)<41):0)?(i=k[7224+(n<<2)>>2]|0,(i|0)!=26):0)?(l=rd(a,i&65535)|0,(l|0)>=0):0)?(m=k[a+616+(l<<2)>>2]|0,m|0):0){d=(k[a+808+(l<<2)>>2]|0)/(m|0)|0;e=(d|0)<(p|0);if(!e?!((n|0)<(i|0)&(d|0)==(p|0)):0){f=q;g=0;h=l}else{f=l;g=1;h=q}z=e?p:d;e=m+o|0;z=ha((z|0)>41?z:41,e)|0;j[a+568+(f<<1)>>1]=-1;k[a+712+(f<<2)>>2]=0;k[a+808+(f<<2)>>2]=0;k[a+712+(h<<2)>>2]=e;k[a+808+(h<<2)>>2]=z;if(!(c|(e|0)>9&b^1))if(g){h=Ec(i)|0;z=Ec(n)|0;k[s>>2]=h;k[s+4>>2]=d;k[s+8>>2]=m;k[s+12>>2]=z;pe(941016,985398,s);break}else{n=Ec(n)|0;z=Ec(i)|0;k[t>>2]=n;k[t+4>>2]=p;k[t+8>>2]=o;k[t+12>>2]=z;pe(941016,985398,t);break}}while(0);q=q+1|0}while((q|0)!=24);f=0;do{d=a+568+(f<<1)|0;z=j[d>>1]|0;e=z&65535;if(((z<<16>>16!=-1?(u=k[a+616+(f<<2)>>2]|0,v=a+808+(f<<2)|0,u|0):0)?(w=(k[v>>2]|0)/(u|0)|0,(w|0)<=40):0)?(j[d>>1]=-1,k[a+712+(f<<2)>>2]=0,k[v>>2]=0,!(c|(u|0)>9&b^1)):0){z=Ec(e)|0;k[x>>2]=z;k[x+4>>2]=w;k[x+8>>2]=u;pe(941016,985425,x)}f=f+1|0}while((f|0)!=24);r=y;return}function _b(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=c+616+(b<<2)|0;k[e>>2]=(k[c+616+(a<<2)>>2]|0)+(k[e>>2]|0);e=c+712+(b<<2)|0;d=c+712+(a<<2)|0;k[e>>2]=(k[d>>2]|0)+(k[e>>2]|0);e=c+808+(b<<2)|0;b=c+808+(a<<2)|0;k[e>>2]=(k[b>>2]|0)+(k[e>>2]|0);j[c+568+(a<<1)>>1]=-1;k[d>>2]=0;k[b>>2]=0;return}function $b(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,n=0,o=0;j=r;r=r+16|0;i=j;f=b^1|c;g=0;do{d=m[a+568+(g<<1)>>1]|0;b=Fc(d)|0;a:do if(b|0){h=g;do{h=h+1|0;if((h|0)>=24)break a;e=m[a+568+(h<<1)>>1]|0}while((Fc(e)|0)!=(b|0));b=(k[a+616+(g<<2)>>2]|0)<(k[a+616+(h<<2)>>2]|0);c=b?g:h;if(!f){l=k[a+616+(c<<2)>>2]|0;n=(k[a+808+(c<<2)>>2]|0)/((l|0?l:1)|0)|0;o=Ec(b?d:e)|0;e=Ec(b?e:d)|0;k[i>>2]=o;k[i+4>>2]=n;k[i+8>>2]=l;k[i+12>>2]=e;pe(941016,985446,i)}_b(c,b?h:g,a)}while(0);g=g+1|0}while((g|0)!=24);r=j;return}function ac(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,l=0,m=0;l=r;r=r+48|0;h=l+32|0;j=l+24|0;g=l+16|0;f=l;se(985487,34,1,941016);e=k[a>>2]|0;if((e|0)!=26){m=Dc(e)|0;e=i[d>>0]|0?1097857:985485;d=k[b>>2]|0;k[f>>2]=m;k[f+4>>2]=e;k[f+8>>2]=d;pe(941016,985522,f)}e=k[a+4>>2]|0;if((e|0)!=26){f=Dc(e)|0;m=k[b+4>>2]|0;k[g>>2]=f;k[g+4>>2]=m;pe(941016,985535,g)}e=k[a+8>>2]|0;if((e|0)!=26){g=Dc(e)|0;m=k[b+8>>2]|0;k[j>>2]=g;k[j+4>>2]=m;pe(941016,985535,j)}k[h>>2]=k[c>>2];pe(941016,985546,h);se(1017206,5,1,941016);r=l;return}function bc(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var l=0.0,m=0,n=0,o=0,q=0,r=0,s=0,t=0,u=0,v=0;k[c>>2]=0;n=c+4|0;k[n>>2]=0;r=c+8|0;k[r>>2]=0;k[d>>2]=26;m=d+4|0;k[m>>2]=26;q=d+8|0;k[q>>2]=26;k[e>>2]=0;u=e+4|0;k[u>>2]=0;v=e+8|0;k[v>>2]=0;o=f+8|0;s=f+16|0;k[f>>2]=0;k[f+4>>2]=0;k[f+8>>2]=0;k[f+12>>2]=0;k[f+16>>2]=0;k[f+20>>2]=0;k[g>>2]=b;i[h>>0]=0;t=j[a+568>>1]|0;switch(t<<16>>16){case 26:case -1:{f=0;break}default:{k[d>>2]=t&65535;d=k[a+616>>2]|0;k[c>>2]=(k[a+808>>2]|0)/((d|0?d:1)|0)|0;if((d|0)<1)l=0.0;else l=+((k[a+712>>2]<<10|0)/(d|0)|0|0);p[f>>3]=l;f=d}}d=j[a+570>>1]|0;switch(d<<16>>16){case 26:case -1:{c=0;break}default:{k[m>>2]=d&65535;d=k[a+620>>2]|0;k[n>>2]=(k[a+812>>2]|0)/((d|0?d:1)|0)|0;if((d|0)<1)l=0.0;else l=+((k[a+716>>2]<<10|0)/(d|0)|0|0);p[o>>3]=l;c=d}}d=j[a+572>>1]|0;switch(d<<16>>16){case 26:case -1:{d=0;break}default:{k[q>>2]=d&65535;d=k[a+624>>2]|0;k[r>>2]=(k[a+816>>2]|0)/((d|0?d:1)|0)|0;if((d|0)<1)l=0.0;else l=+((k[a+720>>2]<<10|0)/(d|0)|0|0);p[s>>3]=l}}c=c+f|0;d=d+c|0;if((d|0)>(b|0)){k[g>>2]=d;b=d}r=(b|0)<1?1:b;f=(f*100|0)/(r|0)|0;k[e>>2]=f;s=(c*100|0)/(r|0)|0;c=((d*100|0)/(r|0)|0)-s|0;k[v>>2]=c;d=s-f|0;k[u>>2]=d;if((d|0)<(c|0)){d=d+1|0;k[u>>2]=d;k[v>>2]=c+-1}if((f|0)<(d|0)){k[e>>2]=f+1;k[u>>2]=d+-1}k[g>>2]=b;switch(t<<16>>16){case 26:case -1:{d=0;break}default:{d=k[a+616>>2]|0;d=((k[a+808>>2]|0)/((d|0?d:1)|0)|0|0)>40&1}}i[h>>0]=d;i[h>>0]=(100-(k[e>>2]|0)-(k[u>>2]|0)-(k[v>>2]|0)|0)>20?0:d;return}function cc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;y=r;r=r+32|0;w=y+8|0;v=y;q=y+12|0;k[q>>2]=k[1971];k[q+4>>2]=k[1972];k[q+8>>2]=k[1973];h=k[c>>2]|0;k[d>>2]=k[b>>2];i[e>>0]=(h|0)>1&1;j=0;m=0;l=0;p=3;while(1){if((k[b+(m<<2)>>2]|0)==25){o=(k[c+(m<<2)>>2]|0)+l|0;n=m+1|0;if((n|0)<3){h=m;j=n;while(1){k[q+(h<<2)>>2]=k[q+(j<<2)>>2];h=j+1|0;if((h|0)==3)break;else{u=j;j=h;h=u}}j=k[q>>2]|0}l=p+-1|0;h=((k[c>>2]|0)*100|0)/(101-o|0)|0;k[d>>2]=k[b+(j<<2)>>2];if((k[c+(j<<2)>>2]|0)<2){i[e>>0]=0;m=n;s=j;t=o;u=l}else{m=n;s=j;t=o;u=l}}else{m=m+1|0;s=j;t=l;u=p}if((m|0)==3)break;else{j=s;l=t;p=u}}o=k[q+4>>2]|0;p=k[c+(o<<2)>>2]|0;n=ha(p,a)|0;o=b+(o<<2)|0;l=k[b+(s<<2)>>2]|0;m=(l|0)==0;a:do if(m){j=k[o>>2]|0;switch(j|0){case 26:case 0:{x=24;break a}default:{}}if((n|0)>1499&(p|0)>16){h=(p*100|0)/(101-t-(k[c+(s<<2)>>2]|0)|0)|0;k[d>>2]=j;if((p|0)<2)i[e>>0]=0}else x=23}else{b=l+-4|0;if(b>>>0<11?(1035>>>(b&2047)&1)!=0:0){j=k[o>>2]|0;if(j>>>0<15){if(16561>>>(j&32767)&1){x=23;break}}else if((j|0)==26){x=23;break}if((n|0)>1499&(p|0)>19){h=(p*100|0)/(101-t-(k[c+(s<<2)>>2]|0)|0)|0;k[d>>2]=j;if((p|0)<2)i[e>>0]=0}else x=23}else x=23}while(0);if((x|0)==23){j=k[o>>2]|0;x=24}do if((x|0)==24){if(!j){if(m)break;h=((k[c+(s<<2)>>2]|0)*100|0)/(101-t-p|0)|0;break}x=j+-4|0;if(x>>>0<11?(1035>>>(x&2047)&1)!=0:0){if(l>>>0<15?16561>>>(l&32767)&1:0)break;h=((k[c+(s<<2)>>2]|0)*100|0)/(101-t-p|0)|0}}while(0);if((h|0)<26){if(!(f^1|g)){x=Ec(k[d>>2]|0)|0;k[v>>2]=x;k[v+4>>2]=h;pe(941016,985557,v)}k[d>>2]=26;i[e>>0]=0}if((h|0)<51)i[e>>0]=0;if((100-(k[c>>2]|0)-(k[c+4>>2]|0)-(k[c+8>>2]|0)|0)>20)i[e>>0]=0;if(!u){if(!(f^1|g)){x=Ec(k[d>>2]|0)|0;k[w>>2]=x;pe(941016,985593,w)}k[d>>2]=26;i[e>>0]=0}r=y;return}function dc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;if(Jc(a)|0){d=c+16|0;e=k[d>>2]|0;k[c+20+(e<<2)>>2]=b;k[d>>2]=e+1&3}if(Kc(a)|0){e=c+36|0;d=k[e>>2]|0;k[c+40+(d<<2)>>2]=b;k[e>>2]=d+1&3}return}function ec(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;d=pb(b,1)|0;if(Jc(a)|0?Jc(b)|0:0){e=c+56|0;f=k[e>>2]|0;k[c+60+(f<<2)>>2]=d;k[e>>2]=f+1&3}if(Kc(a)|0?Kc(b)|0:0){f=c+76|0;e=k[f>>2]|0;k[c+80+(e<<2)>>2]=d;k[f>>2]=e+1&3}return}function fc(a,b){a=a|0;b=b|0;var c=0,d=0;switch(a|0){case 16:{ec(16,69,b);break}case 69:{ec(69,16,b);break}default:{c=Fc(a)|0;if(c|0){d=0;do{if(!((d|0)==(a|0)|(c|0)!=(Fc(d)|0)))ec(a,d,b);d=d+1|0}while((d|0)!=512)}}}return}function gc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,l=0,n=0,o=0,p=0;p=r;r=r+64|0;h=p+8|0;g=p;n=p+32|0;o=p+16|0;l=p+12|0;k[n>>2]=0;if(!c){Rb(o,a,b);Lb(o,n);if(i[e+5>>0]|0?(c=i[o>>0]|0,f=(c&1)==0,(f?(c&255)>>>1:k[o+4>>2]|0)|0):0){c=k[e>>2]|0;k[g>>2]=f?o+1|0:k[o+8>>2]|0;pe(c,985624,g)}Ae(o)}if(d|0){f=k[d>>2]|0;if(f|0?i[f>>0]|0:0)Mb(f,n);f=k[d+4>>2]|0;if(f|0?i[f>>0]|0:0)Nb(f,n);f=k[d+8>>2]|0;if((f|0)!=23)Ob(f,n);f=k[d+12>>2]|0;if((f|0)!=26)Pb(f,n)}Ab(n);if(i[e+5>>0]|0){Qb(o,n);d=i[o>>0]|0;f=(d&1)==0;if((f?(d&255)>>>1:k[o+4>>2]|0)|0){d=k[e>>2]|0;k[h>>2]=f?o+1|0:k[o+8>>2]|0;pe(d,985648,h)}Ae(o)}d=k[n>>2]|0;c=(d|0)>0;if(c){b=0;do{a=j[n+4+(b<<1)>>1]|0;f=a&1023;a=a<<16>>16>>10;if((a|0)>0)dc(f,pb(f,a)|0,e);b=b+1|0}while((b|0)<(d|0))}k[l>>2]=0;hc(o,l);g=k[o>>2]|0;if(c){b=g+40|0;f=0;while(1){l=m[n+4+(f<<1)>>1]&1023;h=g+((Fc(l)|0)<<2)|0;k[h>>2]=(k[h>>2]|0)+1;switch(l|0){case 16:{k[b>>2]=(k[b>>2]|0)+1;break}case 69:{k[b>>2]=(k[b>>2]|0)+1;break}default:{}}f=f+1|0;if((f|0)==(d|0)){a=0;break}}do{l=j[n+4+(a<<1)>>1]|0;f=l&1023;a:do if((l<<16>>16>>10|0)>0){l=Fc(f)|0;if((l|0)>0?(k[g+(l<<2)>>2]|0)==1:0)fc(f,e);switch(f|0){case 16:case 69:break;default:break a}if((k[b>>2]|0)==1)fc(f,e)}while(0);a=a+1|0}while((a|0)!=(d|0))}b=g;if(g|0){f=o+4|0;a=k[f>>2]|0;if((a|0)!=(g|0))k[f>>2]=a+(~((a+-4-b|0)>>>2)<<2);Qe(g)}r=p;return}function hc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;k[a>>2]=0;e=a+4|0;k[e>>2]=0;k[a+8>>2]=0;ic(a);c=k[e>>2]|0;a=11;d=c;while(1){k[d>>2]=k[b>>2];a=a+-1|0;if(!a)break;else d=d+4|0}k[e>>2]=c+44;return}function ic(a){a=a|0;var b=0;b=gf(44)|0;k[a+4>>2]=b;k[a>>2]=b;k[a+8>>2]=b+44;return}function jc(a){a=a|0;var b=0,c=0,d=0;c=k[a>>2]|0;d=c;if(c|0){a=a+4|0;b=k[a>>2]|0;if((b|0)!=(c|0))k[a>>2]=b+(~((b+-4-d|0)>>>2)<<2);Qe(c)}return}function kc(a,b,c,d,e,f,g,h,j,l){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;l=l|0;var m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;U=r;r=r+3488|0;S=U+3112|0;O=U+3104|0;F=U+3096|0;E=U+3088|0;R=U+3080|0;L=U+3072|0;I=U+3064|0;H=U+3056|0;J=U+3040|0;C=U+3032|0;p=U+3024|0;o=U+3016|0;D=U+2112|0;m=U+3468|0;n=U+3308|0;z=U+3320|0;q=U;T=U+3168|0;A=U+3144|0;y=U+3136|0;G=U+3124|0;Q=U+3120|0;k[f>>2]=26;K=f+4|0;k[K>>2]=26;M=f+8|0;k[M>>2]=26;k[g>>2]=0;N=g+4|0;k[N>>2]=0;P=g+8|0;k[P>>2]=0;k[h>>2]=0;k[h+4>>2]=0;k[h+8>>2]=0;k[h+12>>2]=0;k[h+16>>2]=0;k[h+20>>2]=0;k[j>>2]=0;i[l>>0]=0;if(e&8192|0){ze(D,a,b);if(!(e&512)){lc(n);B=(i[n>>0]&1)==0?n+1|0:k[n+8>>2]|0;k[p>>2]=b;k[p+4>>2]=B;pe(941016,985693,p);Ae(n)}else{mc(m);B=(i[m>>0]&1)==0?m+1|0:k[m+8>>2]|0;k[o>>2]=b;k[o+4>>2]=B;pe(941016,985674,o);Ae(m)}Ae(D)}if(!b)m=26;else{pd(D);k[z>>2]=941016;i[z+4>>0]=e>>>8&1;w=e>>>9&1;i[z+5>>0]=w;x=z+6|0;i[x>>0]=e>>>10&1;i[z+7>>0]=e>>>11&1;k[z+12>>2]=26;t=z+8|0;k[t>>2]=0;k[z+140>>2]=7896;o=z+144|0;k[o>>2]=0;m=z+16|0;n=m+120|0;do{k[m>>2]=0;m=m+4|0}while((m|0)<(n|0));u=e>>>12;gc(a,b,c,d,z);kd(q);kd(q+528|0);kd(q+1056|0);kd(q+1584|0);uc(T,a,b,c);k[o>>2]=T;n=A+4|0;o=A+12|0;k[A>>2]=0;k[A+4>>2]=0;k[A+8>>2]=0;k[A+12>>2]=0;k[A+16>>2]=26;k[y>>2]=0;s=jf(16384)|0;p=(e&4|0)!=0;if(p)qf(s|0,0,16384)|0;q=(e&2|0)==0;v=(e&1|0)!=0;B=0;while(1){if(!(Ac(T,A)|0)){o=21;break}m=k[n>>2]|0;if(q){if(!(v|(m|0)<2049)?Yb(k[A>>2]|0,m)|0:0){o=15;break}}else{m=Xb(k[A>>2]|0,m)|0;k[n>>2]=m}if(p){m=Wb(k[A>>2]|0,m,y,s)|0;k[n>>2]=m}k[t>>2]=k[o>>2];jd(A,z,D);B=m+B|0}if((o|0)==15){if(w|0){k[C>>2]=B;pe(941016,985708,C)}kf(s);m=kc(a,b,c,d,e|2,f,g,h,j,l)|0}else if((o|0)==21){kf(s);n=(w|0)!=0;m=u&1;if(n&(m|0)==0){if(!(i[x>>0]|0))se(1017206,5,1,941016);td(D)}p=(m|0)!=0;$b(D,n,p);sd(D);bc(D,B,G,f,g,h,j,l);do if(!(v|(B|0)<257)){if(i[l>>0]|0){m=k[g>>2]|0;if((m|0)>69){o=29;break}if(((k[N>>2]|0)+m|0)>92){o=29;break}}if(!(p|n^1))ac(f,g,j,l);if((B|0)<256){if(n){k[E>>2]=B;pe(941016,985862,E)}m=kc(a,b,c,d,e|93,f,g,h,j,l)|0;break}else{if(n){k[F>>2]=B;pe(941016,985937,F)}m=kc(a,b,c,d,e|13,f,g,h,j,l)|0;break}}else o=29;while(0);if((o|0)==29){Zb(D,n,p);sd(D);bc(D,B,G,f,g,h,j,l);cc(B,f,g,Q,l,n,p);m=n^1;if(!(p|m)){o=0;do{n=k[f+(o<<2)>>2]|0;if((n|0)!=26){j=Ec(n)|0;E=k[G+(o<<2)>>2]|0;F=k[g+(o<<2)>>2]|0;k[J>>2]=j;k[J+4>>2]=E;k[J+8>>2]=F;pe(941016,985771,J)}o=o+1|0}while((o|0)!=3);k[H>>2]=B;pe(941016,985761,H);H=Dc(k[Q>>2]|0)|0;J=i[l>>0]|0?32:42;k[I>>2]=H;k[I+4>>2]=J;pe(941016,985785,I);se(985793,9,1,941016)}if(m|p^1)m=k[Q>>2]|0;else{se(985803,37,1,941016);m=k[f>>2]|0;if((m|0)!=26){J=Ec(m)|0;g=k[g>>2]|0;k[L>>2]=J;k[L+4>>2]=g;pe(941016,985841,L)}m=k[K>>2]|0;if((m|0)!=26){L=Ec(m)|0;N=k[N>>2]|0;k[O>>2]=L;k[O+4>>2]=N;pe(941016,985841,O)}m=k[M>>2]|0;if((m|0)!=26){O=Ec(m)|0;P=k[P>>2]|0;k[S>>2]=O;k[S+4>>2]=P;pe(941016,985841,S)}m=k[Q>>2]|0;Q=Dc(m)|0;S=i[l>>0]|0?32:42;k[R>>2]=Q;k[R+4>>2]=S;pe(941016,985785,R);se(1017206,5,1,941016)}}}vc(T)}r=U;return m|0}function lc(a){a=a|0;ze(a,1097857,0);return}function mc(a){a=a|0;ze(a,1097857,0);return}function nc(a){a=a|0;a:do if(a>>>0>=256){if(a>>>0>=55296){switch(a&-16|0){case 64992:case 64976:{a=65533;break a}default:{}}if((a&65534|0)==65534)a=65533;else a=(a+-57344|0)>>>0<1056768?a:65533}}else a=k[7932+(a<<2)>>2]|0;while(0);return a|0}function oc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;a:do if((b|0)>0){g=0;c=0;d=0;while(1){f=i[a+g>>0]|0;if((f&-64)<<24>>24!=-128){e=(d|0)>7|((c|0)>24?1:((l[993817+((f&255)>>>4)>>0]|0)+g|0)>(b|0));if(e)break a;else d=(e&1^1)+d|0}switch(f<<24>>24){case 60:{f=1097824+c|0;i[f>>0]=38;i[f+1>>0]=108;i[f+2>>0]=116;i[f+3>>0]=59;c=c+4|0;break}case 62:{f=1097824+c|0;i[f>>0]=38;i[f+1>>0]=103;i[f+2>>0]=116;i[f+3>>0]=59;c=c+4|0;break}case 38:{f=1097824+c|0;i[f>>0]=i[993833]|0;i[f+1>>0]=i[993834]|0;i[f+2>>0]=i[993835]|0;i[f+3>>0]=i[993836]|0;i[f+4>>0]=i[993837]|0;c=c+5|0;break}case 39:{f=1097824+c|0;i[f>>0]=i[993839]|0;i[f+1>>0]=i[993840]|0;i[f+2>>0]=i[993841]|0;i[f+3>>0]=i[993842]|0;i[f+4>>0]=i[993843]|0;i[f+5>>0]=i[993844]|0;c=c+6|0;break}case 34:{f=1097824+c|0;i[f>>0]=i[993846]|0;i[f+1>>0]=i[993847]|0;i[f+2>>0]=i[993848]|0;i[f+3>>0]=i[993849]|0;i[f+4>>0]=i[993850]|0;i[f+5>>0]=i[993851]|0;c=c+6|0;break}default:{i[1097824+c>>0]=f;c=c+1|0}}g=g+1|0;if((g|0)>=(b|0))break a}}else c=0;while(0);i[1097824+c>>0]=0;return}function pc(a,b){a=a|0;b=b|0;do if(b>>>0>=128){if(b>>>0<2048){i[a>>0]=b>>>6|192;i[a+1>>0]=b&63|128;b=2;break}b=b>>>0>1114111?65533:b;if(b>>>0<65536){i[a>>0]=b>>>12|224;i[a+1>>0]=b>>>6&63|128;i[a+2>>0]=b&63|128;b=3;break}else{i[a>>0]=b>>>18|240;i[a+1>>0]=b>>>12&63|128;i[a+2>>0]=b>>>6&63|128;i[a+3>>0]=b&63|128;b=4;break}}else{i[a>>0]=b;b=1}while(0);return b|0}function qc(a,b){a=a|0;b=b|0;var c=0,d=0;d=r;r=r+16|0;c=d;if((b|0)>15)b=-1;else{tf(c|0,a|0,b|0)|0;i[c+b>>0]=0;b=Lc(c)|0;if((b|0)>-1)b=k[8956+(b<<3)+4>>2]|0;else b=-1}r=d;return b|0}function rc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0;j=a+b|0;a:do if((b|0)!=0?(i[a>>0]|0)==38:0){k[c>>2]=1;f=a+1|0;d=i[f>>0]|0;if(d<<24>>24==35){if((b|0)<4){d=-1;break}d=a+2|0;b:do switch(i[d>>0]|0){case 88:case 120:{d=a+3|0;do{if((i[d>>0]|0)!=48)break;d=d+1|0}while(d>>>0<j>>>0);if((d|0)==(j|0)){d=-1;break a}f=i[d>>0]|0;if((f+-48&255)>=10)switch(f<<24>>24){case 65:case 66:case 67:case 68:case 69:case 70:case 97:case 98:case 99:case 100:case 101:case 102:break;default:{d=-1;break a}}c:do if(d>>>0<j>>>0){e=f;b=d;while(1){if((e+-48&255)>=10)switch(e<<24>>24){case 65:case 66:case 67:case 68:case 69:case 70:case 97:case 98:case 99:case 100:case 101:case 102:break;default:{h=b;break c}}b=b+1|0;if(b>>>0>=j>>>0){h=b;break c}e=i[b>>0]|0}}else h=d;while(0);b=h;g=b-d|0;if((g|0)>=8?!((g|0)==8&f<<24>>24<56):0){d=65533;break b}d:do if(d>>>0<h>>>0){g=f;e=0;while(1){f=e<<4;e=g<<24>>24;do if((g+-48&255)>=10)if((g+-97&255)<6){e=e+-87|0;break}else{e=(g+-65&255)<6?e+-55|0:0;break}else e=e+-48|0;while(0);f=e+f|0;e=d+1|0;if((e|0)==(h|0)){d=f;break d}d=e;g=i[e>>0]|0;e=f}}else d=0;while(0);d=nc(d)|0;break}case 48:{while(1){d=d+1|0;if(d>>>0>=j>>>0){l=32;break b}if((i[d>>0]|0)!=48){l=32;break}}break}default:l=32}while(0);do if((l|0)==32){if((d|0)==(j|0)){d=-1;break a}f=i[d>>0]|0;if((f+-48&255)>=10){d=-1;break a}e:do if(d>>>0<j>>>0){b=d;while(1){b=b+1|0;if(b>>>0>=j>>>0){g=b;break e}if(((i[b>>0]|0)+-48&255)>=10){g=b;break}}}else g=d;while(0);b=g;e=b-d|0;if((e|0)>=9){if((e|0)!=10){d=65533;break}if((Yd(d,993853,10)|0)>=1){d=65533;break}}if(d>>>0<g>>>0){e=(f<<24>>24)+-48|0;d=d+1|0;if((d|0)==(g|0))d=e;else{f=d;d=e;do{d=(d*10|0)+-48+(i[f>>0]|0)|0;f=f+1|0}while((f|0)!=(g|0))}}else d=0;d=nc(d)|0}while(0);if((d|0)==-1|b>>>0>j>>>0){d=-1;break}}else{g=f;if((b|0)>1?(d+-48&255)<10|((d&-33)+-65&255)<26:0){d=f;do{d=d+1|0;if(d>>>0>=j>>>0)break;h=i[d>>0]|0}while((h+-48&255)<10|((h&-33)+-65&255)<26);b=d;e=d}else{b=g;e=f}d=qc(f,b-g|0)|0;if((d|0)<0){d=-1;break}if((d|0)>255){if(e>>>0>=j>>>0){d=-1;break}if((i[e>>0]|0)!=59){d=-1;break}}}e=b;if(e>>>0<j>>>0?(i[e>>0]|0)==59:0)b=e+1|0;k[c>>2]=b-a}else l=3;while(0);if((l|0)==3){k[c>>2]=0;d=-1}return d|0}function sc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;b=rc(a,b,d)|0;if((b|0)>0)b=pc(c,b)|0;else{k[d>>2]=1;b=0}k[e>>2]=b;return}function tc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;f=a+b|0;a:do if((b|0)>0){d=a;e=1008264;while(1){e=l[e+(l[1009064+(l[d>>0]|0)>>0]|0)>>0]|0;if((e|0)<=(c|0))break;d=d+1|0;if(d>>>0<f>>>0)e=1008264+(e*20|0)|0;else break a}switch(e|0){case 0:case 2:{b=d-a|0;break a}default:{}}b=d-a|0;while(1){d=b+-1|0;if((b|0)<=1)break a;if((i[a+d>>0]|0)==60)break;else b=d}}while(0);return b|0}function uc(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;k[a>>2]=b;k[a+4>>2]=b;k[a+8>>2]=b+c;k[a+12>>2]=c;i[a+16>>0]=d&1;i[a+28>>0]=1;i[a+29>>0]=1;k[a+32>>2]=1;c=a+36|0;Nc(c);d=a+88|0;Nc(d);b=jf(40960)|0;k[a+20>>2]=b;b=jf(61440)|0;k[a+24>>2]=b;Oc(c);Oc(d);return}function vc(a){a=a|0;var b=0;b=k[a+20>>2]|0;if(b|0)kf(b);b=k[a+24>>2]|0;if(b|0)kf(b);Pc(a+88|0);Pc(a+36|0);return}function wc(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0;u=r;r=r+32|0;o=u+12|0;s=u+8|0;q=u+4|0;n=u;p=u+16|0;k[q>>2]=0;a:do if((c|0)>0){m=a+16|0;j=a+32|0;a=0;e=0;while(1){wd(b+e|0,c-e|0,o);e=(k[o>>2]|0)+e|0;if((e|0)>=(c|0)){e=c;a=0;break a}g=b+e|0;h=i[g>>0]|0;f=h&255;b:do if(((h&-32)<<24>>24==32?(i[1009320+f>>0]|0)!=0:0)?(i[m>>0]|0)==0:0)switch(h<<24>>24){case 60:{a=tc(g,c-e|0,k[j>>2]|0)|0;k[q>>2]=a;break b}case 62:{k[q>>2]=1;a=1;break b}case 38:{sc(g,c-e|0,p,q,n);k[o>>2]=l[1009576+(l[p>>0]|0)>>0];k[s>>2]=p;a=(vd(s,o)|0)&255;t=12;break b}default:break b}else t=11;while(0);if((t|0)==11){a=l[1009576+f>>0]|0;k[q>>2]=a;k[o>>2]=a;k[s>>2]=g;a=(vd(s,o)|0)&255;t=12}if((t|0)==12){t=0;if(a|0)break a;a=k[q>>2]|0}e=a+e|0;if((e|0)>=(c|0)){a=0;break}}}else{e=0;a=0}while(0);k[d>>2]=a;r=u;return e|0}function xc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0;w=r;r=r+16|0;p=w+4|0;o=w;t=a+20|0;m=k[t>>2]|0;k[b>>2]=m;u=b+4|0;k[u>>2]=0;v=a+4|0;k[b+8>>2]=(k[v>>2]|0)-(k[a>>2]|0);k[b+12>>2]=0;k[b+16>>2]=26;l=b+20|0;i[l>>0]=0;s=a+12|0;n=k[s>>2]|0;n=(n+-40928|0)>>>0<40928?(n|0)/2|0:40896;i[m>>0]=32;i[(k[t>>2]|0)+1>>0]=0;b=k[s>>2]|0;if((b|0)<1)b=0;else{m=a+16|0;j=a+32|0;c=0;g=b;h=0;a=1;b=0;while(1){d=k[v>>2]|0;e=d+b|0;f=i[e>>0]|0;f=f<<24>>24==13?10:f;if(((f&-32)<<24>>24==32?(i[1009320+(f&255)>>0]|0)!=0:0)?(i[m>>0]|0)==0:0){a:do switch(f<<24>>24){case 60:{b:do if((b|0)<(g+-3|0))switch(i[d+(b+1)>>0]|32|0){case 112:{d=(i[d+(b+2)>>0]|0)<64?10:32;break b}case 98:{if((i[d+(b+2)>>0]|32|0)!=114){d=32;break b}d=(i[d+(b+3)>>0]|0)<64?10:32;break b}case 116:{if((i[d+(b+2)>>0]|32|0)!=114){d=32;break b}d=(i[d+(b+3)>>0]|0)<64?10:32;break b}default:{d=32;break b}}else d=32;while(0);c=(tc(e,g-b|0,k[j>>2]|0)|0)+1|0;k[p>>2]=c;if(h)switch(d<<24>>24){case 10:case 32:{e=d;d=1;break a}default:{}}i[(k[t>>2]|0)+a>>0]=d;e=d;d=d<<24>>24==32|d<<24>>24==10;a=a+1|0;break}case 62:{k[p>>2]=1;i[(k[t>>2]|0)+a>>0]=62;c=1;e=62;d=h;a=a+1|0;break}case 38:{sc(e,g-b|0,(k[t>>2]|0)+a|0,p,o);c=k[p>>2]|0;e=38;d=h;a=(k[o>>2]|0)+a|0;break}default:{e=f;d=h}}while(0);h=c;b=c+b|0}else{if(h)switch(f<<24>>24){case 10:case 32:{d=1;break}default:q=22}else q=22;if((q|0)==22){q=0;i[(k[t>>2]|0)+a>>0]=f;d=f<<24>>24==32|f<<24>>24==10;a=a+1|0}h=c;e=f;b=b+1|0}if(!((a|0)<(n|0)|e<<24>>24!=10&e<<24>>24!=32)){q=25;break}if((a|0)>40927){q=28;break}g=k[s>>2]|0;if((g|0)<=(b|0))break;else{c=h;h=d}}if((q|0)==25)i[l>>0]=1;else if((q|0)==28)i[l>>0]=1;d=k[v>>2]|0;c:do if((b|0)>0){c=b;while(1){if((i[d+c>>0]&-64)<<24>>24!=-128){b=c;break c}b=c+-1|0;a=a+-1|0;if((c|0)>1)c=b;else break}}while(0);k[v>>2]=d+b;k[s>>2]=(k[s>>2]|0)-b;i[(k[t>>2]|0)+a>>0]=32;i[(k[t>>2]|0)+(a+1)>>0]=32;i[(k[t>>2]|0)+(a+2)>>0]=32;i[(k[t>>2]|0)+(a+3)>>0]=0;k[u>>2]=a;b=1}r=w;return b|0}function yc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;C=r;r=r+32|0;s=C+16|0;v=C+12|0;c=C+8|0;u=C+4|0;t=C;if(!(i[a+28>>0]|0))b=xc(a,b)|0;else{z=a+20|0;y=k[z>>2]|0;k[b>>2]=y;A=b+4|0;k[A>>2]=0;B=a+4|0;o=b+8|0;k[o>>2]=(k[B>>2]|0)-(k[a>>2]|0);d=b+12|0;k[d>>2]=0;k[b+16>>2]=26;p=b+20|0;i[p>>0]=0;x=a+12|0;q=k[x>>2]|0;q=(q+-40928|0)>>>0<40928?(q|0)/2|0:40896;k[u>>2]=0;k[t>>2]=0;i[y>>0]=32;i[(k[z>>2]|0)+1>>0]=0;y=a+36|0;Oc(y);Vc(y,k[o>>2]|0);b=wc(a,k[B>>2]|0,k[x>>2]|0,c)|0;k[B>>2]=(k[B>>2]|0)+b;k[x>>2]=(k[x>>2]|0)-b;if((b|0)==1)Tc(y,1);else{Vc(y,b);Uc(y,1)}b=k[x>>2]|0;if((b|0)<1){Rc(y);b=0}else{o=k[c>>2]|0;k[d>>2]=o;m=a+16|0;n=a+29|0;j=a+32|0;d=b;c=1;a=0;b=0;do{a:do if((d|0)>(b|0)){g=a;while(1){f=(k[B>>2]|0)+b|0;a=i[f>>0]|0;e=a&255;b:do if(((a&-32)<<24>>24==32?(i[1009320+e>>0]|0)!=0:0)?(i[m>>0]|0)==0:0){switch(a<<24>>24){case 62:case 60:{h=c;c=0;break a}case 38:break;default:break b}sc(f,d-b|0,(k[z>>2]|0)+c|0,u,t);g=(k[z>>2]|0)+c|0;k[s>>2]=l[1009576+(l[g>>0]|0)>>0];k[v>>2]=g;g=(vd(v,s)|0)&255}else w=15;while(0);if((w|0)==15){w=0;a=l[1009576+e>>0]|0;k[t>>2]=a;k[u>>2]=a;e=(k[z>>2]|0)+c|0;if((b|0)<(d+-3|0)){h=l[f>>0]|l[f+1>>0]<<8|l[f+2>>0]<<16|l[f+3>>0]<<24;i[e>>0]=h;i[e+1>>0]=h>>8;i[e+2>>0]=h>>16;i[e+3>>0]=h>>24}else tf(e|0,f|0,a|0)|0;g=(k[B>>2]|0)+b|0;k[s>>2]=l[1009576+(l[g>>0]|0)>>0];k[v>>2]=g;g=(vd(v,s)|0)&255}if((g|0)!=40&(g|0)!=(o|0)){if(!g){h=c;c=0;break a}d=k[u>>2]|0;h=(k[B>>2]|0)+b+d|0;k[s>>2]=l[1009576+(l[h>>0]|0)>>0];k[v>>2]=h;h=vd(v,s)|0;if(!(h<<24>>24==0|(h&255|0)==(o|0))?(i[n>>0]|0)!=0:0){h=c;c=g;break a}}else d=k[u>>2]|0;b=d+b|0;a=k[t>>2]|0;c=a+c|0;do if((d|0)!=(a|0))if((d|0)<(a|0)){Tc(y,d);Uc(y,a-d|0);break}else{Tc(y,a);Vc(y,d-a|0);break}else Tc(y,d);while(0);if((c|0)>40927)break;d=k[x>>2]|0;if((d|0)<=(b|0)){h=c;c=g;break a}}i[p>>0]=1;h=c;c=g}else{h=c;c=a}while(0);d=k[x>>2]|0;c:do if((d|0)>(b|0))while(1){wd((k[B>>2]|0)+b|0,d-b|0,s);d=k[s>>2]|0;k[u>>2]=d;b=d+b|0;Vc(y,d);d=k[x>>2]|0;if((d|0)<=(b|0)){a=c;break c}a=(k[B>>2]|0)+b|0;e=i[a>>0]|0;f=e&255;d:do if(((e&-32)<<24>>24==32?(i[1009320+f>>0]|0)!=0:0)?(i[m>>0]|0)==0:0)switch(e<<24>>24){case 60:{c=tc(a,d-b|0,k[j>>2]|0)|0;k[u>>2]=c;break d}case 62:{k[u>>2]=1;c=1;break d}case 38:{sc(a,d-b|0,(k[z>>2]|0)+h|0,u,t);c=(k[z>>2]|0)+h|0;k[s>>2]=l[1009576+(l[c>>0]|0)>>0];k[v>>2]=c;c=(vd(v,s)|0)&255;w=42;break d}default:{w=42;break d}}else w=41;while(0);if((w|0)==41){c=l[1009576+f>>0]|0;k[u>>2]=c;k[s>>2]=c;k[v>>2]=a;c=(vd(v,s)|0)&255;w=42}if((w|0)==42){w=0;if(c|0){a=c;break c}c=k[u>>2]|0}b=c+b|0;Vc(y,c);d=k[x>>2]|0;if((d|0)<=(b|0)){a=0;break}else c=0}else a=c;while(0);c=h+1|0;i[(k[z>>2]|0)+h>>0]=32;Uc(y,1);if((a|0)!=40&(a|0)!=(o|0))break;if((c|0)>=(q|0)){w=48;break}d=k[x>>2]|0}while((b|0)<(d|0));if((w|0)==48)i[p>>0]=1;e=k[x>>2]|0;e:do if((b|0)>0){a=(b|0)<(e|0);d=b;while(1){if(!a){b=d;break e}if((i[(k[B>>2]|0)+d>>0]&-64)<<24>>24!=-128){b=d;break e}b=d+-1|0;c=c+-1|0;if((d|0)>1)d=b;else break}}while(0);k[B>>2]=(k[B>>2]|0)+b;k[x>>2]=e-b;i[(k[z>>2]|0)+c>>0]=32;i[(k[z>>2]|0)+(c+1)>>0]=32;i[(k[z>>2]|0)+(c+2)>>0]=32;i[(k[z>>2]|0)+(c+3)>>0]=0;Uc(y,4);Rc(y);k[A>>2]=c;b=1}}r=C;return b|0}function zc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;c=r;r=r+16|0;g=c+4|0;d=a+88|0;Oc(d);e=b+4|0;f=a+24|0;xd(k[b>>2]|0,(k[e>>2]|0)+3|0,k[f>>2]|0,61440,(i[a+16>>0]|0)!=0,c+8|0,g,c,d);a=k[g>>2]|0;i[(k[f>>2]|0)+a>>0]=0;k[b>>2]=k[f>>2];k[e>>2]=a+-3;Rc(d);r=c;return}function Ac(a,b){a=a|0;b=b|0;var c=0;c=yc(a,b)|0;zc(a,b);return c|0}function Bc(a){a=a|0;a=(a|0)<0?0:a;return k[15988+(((a|0)>101?0:a)<<2)>>2]|0}function Cc(a){a=a|0;a=(a|0)<0?0:a;return k[16396+(((a|0)>101?0:a)<<2)>>2]|0}function Dc(a){a=a|0;a=(a|0)<0?26:a;return k[11076+(((a|0)>613?26:a)<<2)>>2]|0}function Ec(a){a=a|0;a=(a|0)<0?26:a;return k[13532+(((a|0)>613?26:a)<<2)>>2]|0}function Fc(a){a=a|0;do switch(a|0){case 40:case 38:{a=1;break}case 105:{a=2;break}case 135:{a=2;break}case 17:{a=3;break}case 68:{a=3;break}case 84:{a=4;break}case 83:{a=4;break}case 78:{a=5;break}case 28:{a=5;break}case 29:{a=5;break}case 160:{a=5;break}case 35:{a=6;break}case 64:{a=6;break}case 51:{a=6;break}case 43:{a=6;break}case 10:{a=7;break}case 80:{a=7;break}case 1:{a=7;break}case 31:{a=8;break}case 14:{a=8;break}case 12:{a=8;break}case 143:{a=9;break}case 147:{a=9;break}default:a=0}while(0);return a|0}function Gc(a){a=a|0;if(a>>>0>101)a=26;else a=k[16804+(a<<2)>>2]|0;return a|0}function Hc(a){a=a|0;if((a|0)<512)a=i[992810+a>>0]|0;else a=0;return a|0}function Ic(a,b){a=a|0;b=b|0;do if(a>>>0<=101){if((k[16396+(a<<2)>>2]|0)>>>0<2){b=k[16804+(a<<2)>>2]|0;break}b=b&255;if((a|0)==1){b=m[941344+(b<<1)>>1]|0;break}else{b=m[941856+(b<<1)>>1]|0;break}}else b=26;while(0);return b|0}function Jc(a){a=a|0;if((a|0)<512)a=(m[941344+((l[992810+a>>0]|0)<<1)>>1]|0|0)==(a|0);else a=0;return a|0}function Kc(a){a=a|0;if((a|0)<512)a=(m[941856+((l[992810+a>>0]|0)<<1)>>1]|0|0)==(a|0);else a=0;return a|0}function Lc(a){a=a|0;var b=0,c=0,d=0,e=0;c=0;e=265;a:while(1)while(1){if((c|0)>=(e|0)){b=-1;break a}b=c+e>>1;d=_d(a,k[8956+(b<<3)>>2]|0)|0;if((d|0)<0){e=b;continue a}if((d|0)>0)c=b+1|0;else break a}return b|0}function Mc(a){a=a|0;switch(a|0){case 1:{a=0;break}case 3:{a=1;break}case 6:{a=2;break}default:a=3}return a|0}function Nc(a){a=a|0;var b=0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=1;a=a+16|0;b=a+36|0;do{k[a>>2]=0;a=a+4|0}while((a|0)<(b|0));return}function Oc(a){a=a|0;var b=0;if(!(i[a>>0]&1)){i[a+1>>0]=0;i[a>>0]=0}else{i[k[a+8>>2]>>0]=0;k[a+4>>2]=0}k[a+12>>2]=1;a=a+16|0;b=a+36|0;do{k[a>>2]=0;a=a+4|0}while((a|0)<(b|0));return}function Pc(a){a=a|0;Ae(a);return}function Qc(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,j=0;j=a+16|0;g=k[j>>2]|0;do if(g|0){h=a+12|0;b=k[h>>2]|0;if((b|0)==1){e=i[a>>0]|0;f=(e&1)==0;if(f)c=(e&255)>>>1;else c=k[a+4>>2]|0;if(c|0){if(f){c=(e&255)>>>1;d=a+1|0}else{c=k[a+4>>2]|0;d=k[a+8>>2]|0}d=i[d+(c+-1)>>0]|0;if((d&-64)<<24>>24==64?(g+(d&63)|0)>>>0<64:0){if(f){c=(e&255)>>>1;b=a+1|0}else{c=k[a+4>>2]|0;b=k[a+8>>2]|0}a=b+(c+-1)|0;i[a>>0]=(l[a>>0]|0)+g;k[j>>2]=0;break}}}if(g>>>0>63){c=g;d=0;e=30;while(1){b=c>>>e&63;if(d|(b|0)!=0){Ge(a,b&255);b=1}else b=0;if((e|0)<=6)break;c=k[j>>2]|0;d=b;e=e+-6|0}c=k[j>>2]|0;b=k[h>>2]|0}else c=g;Ge(a,(c&63|b<<6)&255);k[j>>2]=0}while(0);return}function Rc(a){a=a|0;Sc(a);a=a+20|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;k[a+16>>2]=0;k[a+20>>2]=0;return}function Sc(a){a=a|0;var b=0,c=0;if(!(k[a+16>>2]|0)){b=i[a>>0]|0;if(!(b&1))b=(b&255)>>>1;else b=k[a+4>>2]|0;if(!b)c=6}else c=6;if((c|0)==6){Tc(a,1);Qc(a)}return}function Tc(a,b){a=a|0;b=b|0;var c=0;do if(b|0){c=a+44|0;k[c>>2]=(k[c>>2]|0)+b;c=a+48|0;k[c>>2]=(k[c>>2]|0)+b;c=a+12|0;if((k[c>>2]|0)==1){a=a+16|0;k[a>>2]=(k[a>>2]|0)+b;break}else{Qc(a);k[c>>2]=1;k[a+16>>2]=b;break}}while(0);return}function Uc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;do if(b|0){d=a+48|0;k[d>>2]=(k[d>>2]|0)+b;d=a+12|0;e=k[d>>2]|0;if((e|0)==2){a=a+16|0;k[a>>2]=(k[a>>2]|0)+b;break}c=a+16|0;if((b|0)==1&(e|0)==3?(k[c>>2]|0)==1:0){k[d>>2]=1;break}Qc(a);k[d>>2]=2;k[c>>2]=b}while(0);return}function Vc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;do if(b|0){d=a+44|0;k[d>>2]=(k[d>>2]|0)+b;d=a+12|0;e=k[d>>2]|0;if((e|0)==3){a=a+16|0;k[a>>2]=(k[a>>2]|0)+b;break}c=a+16|0;if((b|0)==1&(e|0)==2?(k[c>>2]|0)==1:0){k[d>>2]=1;break}Qc(a);k[d>>2]=3;k[c>>2]=b}while(0);return}function Wc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,l=0,n=0,o=0,p=0,q=0,s=0,t=0;t=r;r=r+16|0;l=t;od(f,l);h=k[l>>2]|0;o=Ic(a,h&255)|0;l=k[l+4>>2]|0;q=Ic(a,l&255)|0;n=f+16|0;if((d|0)>0)s=(m[n+(h<<1)>>1]<<10|0)/(d|0)|0;else s=0;p=(Mc(a)|0)+(o<<2)|0;p=j[(k[(k[e+140>>2]|0)+32>>2]|0)+(p<<1)>>1]|0;j[g>>1]=c;j[g+2>>1]=b;j[g+4>>1]=o;j[g+6>>1]=q;c=j[n+(h<<1)>>1]|0;j[g+8>>1]=c;h=j[n+(l<<1)>>1]|0;j[g+10>>1]=h;j[g+12>>1]=d;e=k[f+12>>2]|0;j[g+14>>1]=e;j[g+16>>1]=a;e=(nb(c&65535,h&65535,e&65535)|0)&255;h=g+18|0;i[h>>0]=e;e=Fc(o)|0;if(e|0?(e|0)==(Fc(q)|0):0)i[h>>0]=100;s=(ob(s,p)|0)&255;i[g+19>>0]=s;r=t;return}function Xc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;e=(k[a+8>>2]|0)==1;f=e?a+96|0:a+116|0;d=e?a+16|0:a+36|0;c=k[d+4>>2]|0;if(c|0)ib(c,b);c=k[d+8>>2]|0;if(c|0)ib(c,b);c=k[d+12>>2]|0;if(c|0)ib(c,b);c=k[d+16>>2]|0;if(c|0)ib(c,b);e=e?a+56|0:a+76|0;c=k[f+4>>2]|0;if(c|0)ib(c,b);c=k[f+8>>2]|0;if(c|0)ib(c,b);c=k[f+12>>2]|0;if(c|0)ib(c,b);c=k[f+16>>2]|0;if(c|0)ib(c,b);d=b+16|0;c=k[e+4>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;c=k[e+8>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;c=k[e+12>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;c=k[e+16>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;return}function Yc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,l=0,n=0,o=0,p=0,q=0,s=0,t=0;t=r;r=r+16|0;h=t;s=k[b+56064+(c<<2)>>2]|0;q=k[b+56064+(c+1<<2)>>2]|0;ld(f);k[e+16>>2]=0;k[e+20>>2]=0;if(i[d+7>>0]|0){p=k[d>>2]|0;k[h>>2]=s;k[h+4>>2]=q;pe(p,1017069,h)}k[e>>2]=s;k[e+12>>2]=q-s;if((q|0)>(s|0)){h=d+96|0;l=d+8|0;n=d+116|0;p=s;do{o=k[b+24056+(p<<3)+4>>2]|0;ib(o,f);e=b+24056+(p<<3)+2|0;c=j[e>>1]|0;if((c&65535)<2){md(f);c=j[e>>1]|0}if(c<<16>>16==3){e=(k[l>>2]|0)==1?h:n;c=k[e>>2]|0;k[e+4+(c<<2)>>2]=o;k[e>>2]=c+1&3}p=p+1|0}while((p|0)!=(q|0))}Xc(d,f);p=m[b+24056+(s<<3)>>1]|0;Wc(a,s,p,(m[b+24056+(q<<3)>>1]|0)-p|0,d,f,g);k[d+12>>2]=m[g+4>>1];r=t;return}function Zc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0;x=r;r=r+608|0;w=x+552|0;s=x+528|0;p=x;q=x+576|0;k[w>>2]=0;k[w+4>>2]=0;k[w+8>>2]=0;k[w+12>>2]=0;k[w+16>>2]=0;k[w+20>>2]=0;k[s>>2]=0;k[s+4>>2]=0;k[s+8>>2]=0;k[s+12>>2]=0;k[s+16>>2]=0;k[s+20>>2]=0;g=b+24|0;if((k[g>>2]|0)>0){i=s+12|0;l=s+16|0;m=s+4|0;n=s+20|0;o=s+8|0;t=0;do{kd(p);Yc(a,b,t,c,s,p,q);h=k[d>>2]|0;if((h|0)<50){u=d+4+(h*20|0)|0;f=q;v=u+20|0;do{j[u>>1]=j[f>>1]|0;u=u+2|0;f=f+2|0}while((u|0)<(v|0));h=(k[d>>2]|0)+1|0;k[d>>2]=h};k[w>>2]=k[s>>2];k[w+4>>2]=k[s+4>>2];k[w+8>>2]=k[s+8>>2];k[w+12>>2]=k[s+12>>2];k[w+16>>2]=k[s+16>>2];k[w+20>>2]=k[s+20>>2];k[s>>2]=(k[s>>2]|0)+(k[i>>2]|0);k[m>>2]=(k[m>>2]|0)+(k[l>>2]|0);k[o>>2]=(k[o>>2]|0)+(k[n>>2]|0);t=t+1|0}while((t|0)<(k[g>>2]|0))}else h=k[d>>2]|0;i=k[b+20>>2]|0;f=j[b+24056+(i<<3)>>1]|0;g=d+4+(h*20|0)|0;u=g;v=u+20|0;do{j[u>>1]=0;u=u+2|0}while((u|0)<(v|0));j[g>>1]=f;j[d+4+(h*20|0)+2>>1]=i;k[e>>2]=k[w>>2];k[e+4>>2]=k[w+4>>2];k[e+8>>2]=k[w+8>>2];k[e+12>>2]=k[w+12>>2];k[e+16>>2]=k[w+16>>2];k[e+20>>2]=k[w+20>>2];r=x;return}function _c(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;if((k[a>>2]|0)>0){c=0;do{e=l[a+4+(c*20|0)+18>>0]|0;d=l[a+4+(c*20|0)+19>>0]|0;qd(b,j[a+4+(c*20|0)+4>>1]|0,m[a+4+(c*20|0)+12>>1]|0,m[a+4+(c*20|0)+8>>1]|0,e>>>0<d>>>0?e:d);c=c+1|0}while((c|0)<(k[a>>2]|0))}return}function $c(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0;p=r;r=r+112|0;o=p+96|0;n=p+80|0;l=p+64|0;h=p+48|0;g=p+32|0;i=p+16|0;e=p;t=Bc(k[c>>2]|0)|0;d=c+8|0;s=k[d>>2]|0;j=c+12|0;q=k[j>>2]|0;m=c+16|0;f=k[m>>2]|0;k[e>>2]=t;k[e+4>>2]=s;k[e+8>>2]=q;k[e+12>>2]=f;pe(a,1017096,e);e=c+4|0;a:do if((k[e>>2]|0)>0){f=0;do{if((f|0)<(k[d>>2]|0)){s=k[c+32+(f<<3)>>2]|0;t=k[c+32+(f<<3)+4>>2]|0;oc(b+s|0,6);k[i>>2]=f;k[i+4>>2]=s;k[i+8>>2]=(t|0)<0?(t&2147483647)+2e9|0:t;k[i+12>>2]=1097824;pe(a,1017160,i)}if((f|0)<(k[j>>2]|0)){s=k[c+8040+(f<<3)>>2]|0;t=k[c+8040+(f<<3)+4>>2]|0;oc(b+s|0,12);k[g>>2]=f;k[g+4>>2]=s;k[g+8>>2]=t;k[g+12>>2]=1097824;pe(a,1017175,g)}if((f|0)<(k[m>>2]|0)){s=k[c+16048+(f<<3)>>2]|0;t=k[c+16048+(f<<3)+4>>2]|0;oc(b+s|0,12);k[h>>2]=f;k[h+4>>2]=s;k[h+8>>2]=t;k[h+12>>2]=1097824;pe(a,1017191,h)}if((f|0)<(k[d>>2]|0))se(1017206,5,1,a);if((f|0)>50)break a;f=f+1|0}while((f|0)<(k[e>>2]|0))}while(0);d=k[d>>2]|0;if((d|0)>50){s=k[c+32+(d<<3)>>2]|0;t=k[c+32+(d<<3)+4>>2]|0;oc(b+s|0,6);k[l>>2]=d;k[l+4>>2]=s;k[l+8>>2]=(t|0)<0?(t&2147483647)+2e9|0:t;k[l+12>>2]=1097824;pe(a,1017160,l)}d=k[j>>2]|0;if((d|0)>50){s=k[c+8040+(d<<3)>>2]|0;t=k[c+8040+(d<<3)+4>>2]|0;oc(b+s|0,12);k[n>>2]=d;k[n+4>>2]=s;k[n+8>>2]=t;k[n+12>>2]=1097824;pe(a,1017175,n)}d=k[m>>2]|0;if((d|0)>50){s=k[c+16048+(d<<3)>>2]|0;t=k[c+16048+(d<<3)+4>>2]|0;oc(b+s|0,12);k[o>>2]=d;k[o+4>>2]=s;k[o+8>>2]=t;k[o+12>>2]=1097824;pe(a,1017191,o)}se(1017206,5,1,a);r=p;return}function ad(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,n=0,o=0;l=r;r=r+48|0;j=l+40|0;g=l+16|0;h=l+8|0;d=l;f=c+20|0;k[d>>2]=k[f>>2];pe(a,1017212,d);d=k[f>>2]|0;if((d|0)>=0){e=0;while(1){if(!((e|0)>50&(e|0)<(d+-1|0))){o=m[c+24056+(e<<3)>>1]|0;n=i[1017266+(m[c+24056+(e<<3)+2>>1]|0)>>0]|0;d=k[c+24056+(e<<3)+4>>2]|0;oc(b+o|0,6);k[g>>2]=e;k[g+4>>2]=o;k[g+8>>2]=n;k[g+12>>2]=d;k[g+16>>2]=1097824;pe(a,1017271,g);d=k[f>>2]|0}if((e|0)<(d|0))e=e+1|0;else break}}se(1017206,5,1,a);e=c+24|0;k[h>>2]=k[e>>2];pe(a,1017242,h);if((k[e>>2]|0)>=0){d=0;while(1){o=k[c+56064+(d<<2)>>2]|0;k[j>>2]=d;k[j+4>>2]=o;pe(a,1017294,j);if((d|0)<(k[e>>2]|0))d=d+1|0;else break}}se(1017206,5,1,a);r=l;return}function bd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,l=0,n=0,o=0,p=0;c=r;r=r+48|0;d=c;p=m[b>>1]|0;o=m[b+2>>1]|0;n=Ec(m[b+4>>1]|0)|0;l=m[b+8>>1]|0;i=Ec(m[b+6>>1]|0)|0;h=m[b+10>>1]|0;g=m[b+12>>1]|0;f=m[b+14>>1]|0;e=Bc(m[b+16>>1]|0)|0;b=j[b+18>>1]|0;k[d>>2]=p;k[d+4>>2]=o;k[d+8>>2]=n;k[d+12>>2]=l;k[d+16>>2]=i;k[d+20>>2]=h;k[d+24>>2]=g;k[d+28>>2]=f;k[d+32>>2]=e;k[d+36>>2]=b&255;k[d+40>>2]=(b&65535)>>>8&65535;pe(a,1017302,d);r=c;return}function cd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;e=r;r=r+16|0;d=e+8|0;c=e;k[c>>2]=k[b>>2];pe(a,1017351,c);se(1017382,101,1,a);if((k[b>>2]|0)>=0){c=0;while(1){k[d>>2]=c;pe(a,1017484,d);bd(a,b+4+(c*20|0)|0);if((c|0)<(k[b>>2]|0))c=c+1|0;else break}}se(1017206,5,1,a);r=e;return}function dd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;d=k[a+140>>2]|0;if(b){t=k[d+4>>2]|0;r=0;s=t;b=d+8|0;d=d+12|0}else{r=1;s=k[d+16>>2]|0;t=k[d+20>>2]|0;b=d+24|0;d=d+28|0}e=k[b>>2]|0;b=k[d>>2]|0;o=c+8|0;d=k[o>>2]|0;p=k[c+12>>2]|0;q=k[c+16>>2]|0;j[c+24056>>1]=k[c+28>>2];j[c+24058>>1]=r;n=pb(Gc(k[a+8>>2]|0)|0,1)|0;k[c+24060>>2]=n;if((q|0)>0|((p|0)>0|(d|0)>0)){n=b+4|0;m=e+4|0;g=0;i=0;l=0;b=1;while(1){h=k[c+32+(g<<3)>>2]|0;f=k[c+8040+(i<<3)>>2]|0;a=k[c+16048+(l<<3)>>2]|0;do if((i|0)>=(p|0)|(f|0)>(h|0)|(f|0)>(a|0)){if(!((a|0)>(f|0)|((l|0)>=(q|0)|(a|0)>(h|0)))){f=l+1|0;e=k[(k[n>>2]|0)+(k[c+16048+(l<<3)+4>>2]<<2)>>2]|0;if(!e){a=i;e=f;break}j[c+24056+(b<<3)>>1]=a;j[c+24056+(b<<3)+2>>1]=3;k[c+24056+(b<<3)+4>>2]=e;a=i;e=f;b=b+1|0;break}f=k[c+32+(g<<3)+4>>2]|0;a=f&2147483647;f=(f|0)<0?t:s;g=g+1|0;e=k[f+8>>2]|0;if((a|0)<(e|0)){e=k[(k[f+4>>2]|0)+(a<<2)>>2]|0;if(!e){a=i;e=l;break}j[c+24056+(b<<3)>>1]=h;j[c+24056+(b<<3)+2>>1]=r;k[c+24056+(b<<3)+4>>2]=e;a=i;e=l;b=b+1|0;break}a=a-e+a|0;f=k[f+4>>2]|0;e=k[f+(a<<2)>>2]|0;a=k[f+(a+1<<2)>>2]|0;if(e){j[c+24056+(b<<3)>>1]=h;j[c+24056+(b<<3)+2>>1]=r;k[c+24056+(b<<3)+4>>2]=e;b=b+1|0}if(!a){a=i;e=l}else{j[c+24056+(b<<3)>>1]=h;j[c+24056+(b<<3)+2>>1]=r;k[c+24056+(b<<3)+4>>2]=a;a=i;e=l;b=b+1|0}}else{a=i+1|0;e=k[(k[m>>2]|0)+(k[c+8040+(i<<3)+4>>2]<<2)>>2]|0;if(!e)e=l;else{j[c+24056+(b<<3)>>1]=f;j[c+24056+(b<<3)+2>>1]=2;k[c+24056+(b<<3)+4>>2]=e;e=l;b=b+1|0}}while(0);if((e|0)<(q|0)|((a|0)<(p|0)|(g|0)<(d|0))){i=a;l=e}else break}d=k[o>>2]|0}else b=1;k[c+20>>2]=b;j[c+24056+(b<<3)>>1]=k[c+32+(d<<3)>>2];k[c+24056+(b<<3)+4>>2]=0;return}function ed(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,n=0,o=0;o=b&1^1;i=b?50:20;j=c+20|0;e=k[j>>2]|0;b=k[c+8>>2]|0;if((b|0)>0){l=(i>>>1)+i|0;n=i<<1;h=b;f=0;d=0;b=a;do{if((h|0)>=(l|0))if((h|0)<(n|0))g=h+1>>1;else g=i;else g=h;k[c+56064+(d<<2)>>2]=f;k[c+56268+(d<<2)>>2]=b;d=d+1|0;if((f|0)<(e|0)&(g|0)>0){a=0;b=f;do{a=((m[c+24056+(b<<3)+2>>1]|0|0)==(o|0)&1)+a|0;b=b+1|0}while((b|0)<(e|0)&(a|0)<(g|0));f=b}b=m[c+24056+(f<<3)>>1]|0;h=h-g|0}while((h|0)>0);e=k[j>>2]|0}else{k[c+56064>>2]=0;k[c+56268>>2]=m[c+24056>>1];d=1;b=a}k[c+24>>2]=d;k[c+56064+(d<<2)>>2]=e;k[c+56268+(d<<2)>>2]=b;return}function fd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0;j=r;r=r+1056|0;h=j+24|0;g=c+7|0;if(i[g>>0]|0){se(1017490,12,1,k[c>>2]|0);$c(k[c>>2]|0,k[a>>2]|0,f)}dd(c,e,f);ed(b,e,f);if(i[g>>0]|0){se(1017503,9,1,k[c>>2]|0);ad(k[c>>2]|0,k[a>>2]|0,f)}k[h>>2]=0;Zc(k[a+12>>2]|0,f,c,h,j);if(i[g>>0]|0)cd(k[c>>2]|0,h);_c(h,d);r=j;return}function gd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;qd(d,(Gc(b)|0)&65535,a,a,100);k[c+12>>2]=26;return}function hd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0;p=r;r=r+16|0;o=p;d=gf(56472)|0;k[d+4>>2]=1e3;l=d+8|0;k[d+8040>>2]=0;k[d+8044>>2]=0;k[d+16048>>2]=0;k[d+16052>>2]=0;j[d+24056>>1]=0;k[d+24060>>2]=0;k[d+56064>>2]=0;k[d+56268>>2]=0;k[l>>2]=0;k[l+4>>2]=0;k[l+8>>2]=0;k[l+12>>2]=0;k[l+16>>2]=0;k[l+20>>2]=0;k[l+24>>2]=0;k[l+28>>2]=0;k[d>>2]=k[a+12>>2];e=b+12|0;k[e>>2]=26;k[b+136>>2]=0;f=d+28|0;k[f>>2]=1;g=k[a+4>>2]|0;if((g|0)>1){h=b+7|0;n=b+140|0;m=1;do{if(i[h>>0]|0){q=k[b>>2]|0;k[o>>2]=m;k[o+4>>2]=g;pe(q,1017513,o)}q=m;m=jb(k[a>>2]|0,m,g,k[k[n>>2]>>2]|0,d)|0;s=k[n>>2]|0;kb(k[a>>2]|0,q,m,k[s+8>>2]|0,k[s+12>>2]|0,d);fd(a,q,b,c,1,d);k[l>>2]=0;k[l+4>>2]=0;k[l+8>>2]=0;k[l+12>>2]=0;k[l+16>>2]=0;k[f>>2]=m}while((m|0)<(g|0))}Qe(d);k[e>>2]=26;r=p;return}function id(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,l=0,m=0;d=gf(56472)|0;k[d+4>>2]=1e3;g=d+8|0;k[d+8040>>2]=0;k[d+8044>>2]=0;k[d+16048>>2]=0;k[d+16052>>2]=0;j[d+24056>>1]=0;k[d+24060>>2]=0;k[d+56064>>2]=0;k[d+56268>>2]=0;k[g>>2]=0;k[g+4>>2]=0;k[g+8>>2]=0;k[g+12>>2]=0;k[g+16>>2]=0;k[g+20>>2]=0;k[g+24>>2]=0;k[g+28>>2]=0;k[d>>2]=k[a+12>>2];k[b+12>>2]=26;k[b+136>>2]=0;e=d+28|0;k[e>>2]=1;f=k[a+4>>2]|0;if((f|0)>1){i=b+140|0;h=1;do{m=k[i>>2]|0;l=h;h=lb(k[a>>2]|0,h,f,k[m+16>>2]|0,k[m+20>>2]|0,d)|0;m=k[i>>2]|0;mb(k[a>>2]|0,l,h,k[m+24>>2]|0,k[m+28>>2]|0,d);fd(a,l,b,c,0,d);k[g>>2]=0;k[g+4>>2]=0;k[g+8>>2]=0;k[g+12>>2]=0;k[g+16>>2]=0;k[e>>2]=h}while((h|0)<(f|0))}Qe(d);return}function jd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0;g=r;r=r+32|0;f=g+16|0;e=g;d=g+20|0;if(!(i[b+7>>0]|0))d=a+12|0;else{l=k[b>>2]|0;h=a+12|0;n=Bc(k[h>>2]|0)|0;j=a+4|0;m=k[j>>2]|0;k[e>>2]=n;k[e+4>>2]=m;pe(l,1017545,e);ze(e,k[a>>2]|0,k[j>>2]|0);j=k[b>>2]|0;mc(d);k[f>>2]=(i[d>>0]&1)==0?d+1|0:k[d+8>>2]|0;pe(j,1017576,f);Ae(d);se(1017206,5,1,k[b>>2]|0);Ae(e);d=h}k[b+12>>2]=26;k[b+136>>2]=0;n=Cc(k[d>>2]|0)|0;switch(((n|0)!=3&(i[b+4>>0]|0)!=0?2:n)|0){case 1:case 0:{gd(k[a+4>>2]|0,k[a+12>>2]|0,b,c);break}case 3:{hd(a,b,c);break}case 2:{id(a,b,c);break}default:{}}r=g;return}function kd(a){a=a|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;return}function ld(a){a=a|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;return}function md(a){a=a|0;a=a+12|0;k[a>>2]=(k[a>>2]|0)+1;return}function nd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;b=b&255;d=b>>>2;e=sf(1,0,d|0)|0;f=L;h=a;g=k[h>>2]|0;h=k[h+4>>2]|0;if((g&e|0)==0&(h&f|0)==0){d=a+16+(d<<3)|0;k[d>>2]=0;k[d+4>>2]=0;d=a;k[d>>2]=g|e;k[d+4>>2]=h|f}a=a+16+(b<<1)|0;j[a>>1]=(m[a>>1]|0)+c;return}function od(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,n=0,o=0,p=0,q=0;q=r;r=r+16|0;p=q;k[b>>2]=-1;n=b+4|0;k[n>>2]=-1;o=b+8|0;k[o>>2]=-1;k[p>>2]=-1;k[p+4>>2]=-1;k[p+8>>2]=-1;d=a;c=k[d>>2]|0;d=k[d+4>>2]|0;if(!((c|0)==0&(d|0)==0)){i=a+16|0;j=p+8|0;l=p+4|0;g=0;while(1){if(!((c&1|0)==0&0==0)){h=0;do{e=h+g|0;f=m[i+(e<<1)>>1]|0;if((f|0)>(k[j>>2]|0)){a=k[l>>2]|0;if((f|0)>(a|0)){k[j>>2]=a;k[o>>2]=k[n>>2];a=k[p>>2]|0;if((f|0)>(a|0)){k[l>>2]=a;k[n>>2]=k[b>>2];a=0}else a=1}else a=2;k[p+(a<<2)>>2]=f;k[b+(a<<2)>>2]=e}h=h+1|0}while((h|0)!=4)}c=rf(c|0,d|0,1)|0;d=L;if((c|0)==0&(d|0)==0)break;else g=g+4|0}}r=q;return}function pd(a){a=a|0;var b=0;b=a+536|0;k[b>>2]=0;k[b+4>>2]=0;k[b+8>>2]=0;k[b+12>>2]=0;k[b+16>>2]=0;k[b+20>>2]=0;k[b+24>>2]=0;k[b+28>>2]=0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;k[a+16>>2]=0;k[a+20>>2]=0;a=a+568|0;b=a+48|0;do{k[a>>2]=-1;a=a+4|0}while((a|0)<(b|0));return}function qd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,l=0,m=0;k[a>>2]=(k[a>>2]|0)+1;g=b&65535;l=g&15;m=j[a+568+(l<<1)>>1]|0;do if(m<<16>>16==b<<16>>16){b=a+616+(l<<2)|0;k[b>>2]=(k[b>>2]|0)+c;b=a+712+(l<<2)|0;k[b>>2]=(k[b>>2]|0)+d;d=ha(e,c)|0;a=a+808+(l<<2)|0;k[a>>2]=(k[a>>2]|0)+d}else{f=l^8;i=j[a+568+(f<<1)>>1]|0;if(i<<16>>16==b<<16>>16){b=a+616+(f<<2)|0;k[b>>2]=(k[b>>2]|0)+c;b=a+712+(f<<2)|0;k[b>>2]=(k[b>>2]|0)+d;d=ha(e,c)|0;a=a+808+(f<<2)|0;k[a>>2]=(k[a>>2]|0)+d;break}g=g&7|16;h=j[a+568+(g<<1)>>1]|0;if(h<<16>>16==b<<16>>16){b=a+616+(g<<2)|0;k[b>>2]=(k[b>>2]|0)+c;b=a+712+(g<<2)|0;k[b>>2]=(k[b>>2]|0)+d;d=ha(e,c)|0;a=a+808+(g<<2)|0;k[a>>2]=(k[a>>2]|0)+d;break}if(m<<16>>16!=-1){if(i<<16>>16!=-1)if(h<<16>>16==-1)f=g;else{f=(k[a+616+(f<<2)>>2]|0)<(k[a+616+(l<<2)>>2]|0)?f:l;f=(k[a+616+(g<<2)>>2]|0)<(k[a+616+(f<<2)>>2]|0)?g:f}}else f=l;j[a+568+(f<<1)>>1]=b;k[a+616+(f<<2)>>2]=c;k[a+712+(f<<2)>>2]=d;d=ha(e,c)|0;k[a+808+(f<<2)>>2]=d}while(0);return}function rd(a,b){a=a|0;b=b|0;var c=0,d=0;a:do if(!(k[a+4>>2]|0)){d=b&65535;c=d&15;if((j[a+568+(c<<1)>>1]|0)!=b<<16>>16){c=c^8;if((j[a+568+(c<<1)>>1]|0)!=b<<16>>16){c=d&7|16;c=(j[a+568+(c<<1)>>1]|0)==b<<16>>16?c:-1}}}else{c=0;while(1){if((j[a+568+(c<<1)>>1]|0)==b<<16>>16)break a;c=c+1|0;if((c|0)>=24){c=-1;break}}}while(0);return c|0}function sd(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,l=0;l=0;while(1){g=a+568+(l<<1)|0;if((j[g>>1]|0)==-1)k[a+616+(l<<2)>>2]=-1;b=l;l=l+1|0;if((l|0)>=24)continue;h=a+616+(b<<2)|0;i=a+712+(b<<2)|0;e=a+808+(b<<2)|0;f=l;do{b=a+568+(f<<1)|0;c=a+616+(f<<2)|0;if((j[b>>1]|0)==-1){k[c>>2]=-1;d=-1}else d=k[c>>2]|0;if((k[h>>2]|0)<(d|0)){d=j[g>>1]|0;j[g>>1]=j[b>>1]|0;j[b>>1]=d;d=k[h>>2]|0;k[h>>2]=k[c>>2];k[c>>2]=d;d=k[i>>2]|0;c=a+712+(f<<2)|0;k[i>>2]=k[c>>2];k[c>>2]=d;c=k[e>>2]|0;d=a+808+(f<<2)|0;k[e>>2]=k[d>>2];k[d>>2]=c}f=f+1|0}while((f|0)!=24);if((l|0)==3)break}k[a+4>>2]=1;return}function td(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0;f=r;r=r+32|0;e=f+8|0;d=f;se(1017581,14,1,941016);c=0;do{b=j[a+568+(c<<1)>>1]|0;if(b<<16>>16!=-1){i=Ec(b&65535)|0;h=k[a+616+(c<<2)>>2]|0;g=k[a+712+(c<<2)>>2]|0;b=k[a+808+(c<<2)>>2]|0;k[e>>2]=c;k[e+4>>2]=i;k[e+8>>2]=h;k[e+12>>2]=g;k[e+16>>2]=b;pe(941016,1017620,e)}c=c+1|0}while((c|0)!=24);k[d>>2]=k[a>>2];pe(941016,1017596,d);r=f;return}function ud(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;e=k[c>>2]|0;do if((e|0)>=1){f=k[b>>2]|0;g=(k[a+32>>2]|0)+(k[a>>2]|0)|0;d=k[a+16>>2]|0;h=i[f>>0]|0;a=h&255;if(h<<24>>24>-1){a=i[g+a>>0]|0;k[b>>2]=f+1;k[c>>2]=e+-1;break}if((e|0)>1&(a&224|0)==192){a=i[g+(l[g+a>>0]<<d)+(l[f+1>>0]|0)>>0]|0;k[b>>2]=f+2;k[c>>2]=e+-2;break}if((e|0)>2&(a&240|0)==224){a=g+(l[g+a>>0]<<d+4)|0;a=i[a+(i[a+(l[f+1>>0]|0)>>0]<<d)+(l[f+2>>0]|0)>>0]|0;k[b>>2]=f+3;k[c>>2]=e+-3;break}if((a&248|0)==240&(e|0)>3){a=g+(l[g+(l[g+a>>0]<<d)+(l[f+1>>0]|0)>>0]<<d+4)|0;a=i[a+(i[a+(l[f+2>>0]|0)>>0]<<d)+(l[f+3>>0]|0)>>0]|0;k[b>>2]=f+4;k[c>>2]=e+-4;break}else{k[b>>2]=f+1;k[c>>2]=e+-1;a=0;break}}else a=0;while(0);return a|0}function vd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;c=k[b>>2]|0;do if((c|0)>=1){d=k[a>>2]|0;f=i[d>>0]|0;e=f&255;if(f<<24>>24>-1){f=j[942368+(e<<1)>>1]|0;k[a>>2]=d+1;k[b>>2]=c+-1;c=f&255;break}if((c|0)>1&(e&224|0)==192){f=j[942368+((m[942368+(e<<1)>>1]|0)<<6<<1)+((l[d+1>>0]|0)<<1)>>1]|0;k[a>>2]=d+2;k[b>>2]=c+-2;c=f&255;break}if((c|0)>2&(e&240|0)==224){f=j[942368+((m[942368+((m[942368+(e<<1)>>1]|0)<<6<<1)+((l[d+1>>0]|0)<<1)>>1]|0)<<6<<1)+((l[d+2>>0]|0)<<1)>>1]|0;k[a>>2]=d+3;k[b>>2]=c+-3;c=f&255;break}if((e&248|0)==240&(c|0)>3){f=j[942368+((m[942368+((m[942368+((m[942368+(e<<1)>>1]|0)<<6<<1)+((l[d+1>>0]|0)<<1)>>1]|0)<<6<<1)+((l[d+2>>0]|0)<<1)>>1]|0)<<6<<1)+((l[d+3>>0]|0)<<1)>>1]|0;k[a>>2]=d+4;k[b>>2]=c+-4;c=f&255;break}else{k[a>>2]=d+1;k[b>>2]=c+-1;c=0;break}}else c=0;while(0);return c|0}function wd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,m=0;h=a+b|0;j=h+-7|0;k[c>>2]=0;if(b|0){b=a;a:do{b:do if(b>>>0<j>>>0){g=b;while(1){d=k[g>>2]|0;b=g+4|0;e=k[b>>2]|0;f=g+8|0;if((d+-656877351|d+1145324612|e+-656877351|e+1145324612)&-2139062144|0){if((i[1008008+(l[g+1>>0]|0)>>0]|i[1008008+(d&255)>>0]|i[1008008+(l[g+2>>0]|0)>>0]|i[1008008+(l[g+3>>0]|0)>>0])<<24>>24){b=g;break b}if((i[1008008+(l[g+5>>0]|0)>>0]|i[1008008+(e&255)>>0]|i[1008008+(l[g+6>>0]|0)>>0]|i[1008008+(l[g+7>>0]|0)>>0])<<24>>24)break b}if(f>>>0<j>>>0)g=f;else{b=f;break}}}while(0);if(b>>>0<h>>>0)d=993864;else{d=993864;m=13;break}while(1){f=i[d+(l[b>>0]|0)>>0]|0;e=b+1|0;if((f&255)>239)break;b=993864+((f&255)<<6)|0;if(e>>>0<h>>>0){d=b;b=e}else{d=b;b=e;m=13;break a}}c:do if((d-993864|0)>>>0>=64)do{b=b+-1|0;if(b>>>0<=a>>>0)break c}while((i[b>>0]&-64)<<24>>24==-128);while(0)}while(f<<24>>24==-3);d:do if((m|0)==13)if((d-993864|0)>>>0>=64)do{b=b+-1|0;if(b>>>0<=a>>>0)break d}while((i[b>>0]&-64)<<24>>24==-128);while(0);k[c>>2]=b-a}return}function xd(a,b,c,d,e,f,g,h,j){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;var n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0;H=(j|0)==0;I=(j|0)!=0;E=0;F=0;G=0;while(1){C=a;x=a+b|0;D=c;y=c+d|0;z=x;if((d|0)<(b|0))if(H){r=239;q=0;o=0;n=0}else{Tc(j,0);r=239;q=0;o=0;n=0}else{A=1009832;u=0;B=a;p=D;r=0;o=C;n=0;a:while(1){s=o;if(s>>>0<x>>>0){q=i[s>>0]|0;w=i[A+(q&255)>>0]|0;r=w&255;i[p>>0]=q;o=s+1|0;p=p+1|0;if((w&255)>239)w=q;else{w=B;A=1009832+(r<<6)|0;u=q;B=w;continue}}else w=u;if((r|0)<=239){J=43;break}b:do switch(r|0){case 247:{if(H)q=B;else{Tc(j,-2-B+o|0);Vc(j,2);q=o}i[p+-3>>0]=i[A+((w&255)+64)>>0]|0;A=1009832;u=0;B=q;p=p+-2|0;r=0;n=n+1|0;continue a}case 248:{if(H)q=B;else{Tc(j,o+~B|0);Vc(j,1);q=o}u=w&255;i[p+-3>>0]=i[A+(u+128)>>0]|0;i[p+-2>>0]=i[A+(u+64)>>0]|0;A=1009832;u=0;B=q;p=p+-1|0;r=0;n=n+1|0;continue a}case 246:{if(H)q=B;else{Tc(j,o+~B|0);Vc(j,1);q=o}i[p+-2>>0]=i[A+((w&255)+64)>>0]|0;A=1009832;u=0;B=q;p=p+-1|0;r=0;n=n+1|0;continue a}case 245:{q=w&255;r=p;i[r+-3>>0]=i[A+(q+192)>>0]|0;J=23;break}case 244:{q=w&255;r=p;J=23;break}case 243:{q=w&255;r=p;J=24;break}case 251:{i[p+-1>>0]=i[A+(w&255|256)>>0]|0;w=B;A=1009832;u=0;r=0;n=n+1|0;B=w;continue a}case 250:{q=A;r=w&255;if((q-1009832|0)>>>0<320){r=l[A+(r|512)>>0]<<8;break b}else{r=l[A+(r+128)>>0]<<8;J=29;break b}}case 249:case 252:{r=0;J=29;break}default:{J=10;break a}}while(0);if((J|0)==23){i[r+-2>>0]=i[A+(q+128)>>0]|0;J=24}else if((J|0)==29){J=0;q=A}if((J|0)==24){J=0;i[r+-1>>0]=i[A+(q+64)>>0]|0;w=B;A=1009832;u=0;r=0;n=n+1|0;B=w;continue}s=w&255;r=l[A+((q-1009832|0)>>>0<320?s|256:s+64|0)>>0]|r;s=974880+(r<<2)|0;v=l[s>>0]&127;q=l[974880+(r<<2)+1>>0]|0;if(!((q&128|0)==0|e)){s=r+1|0;q=l[974880+(s<<2)+1>>0]|0;s=974880+(s<<2)|0}u=q&127;r=m[s+2>>1]|0;q=p+(0-v)|0;t=q+u|0;if((y-t|0)<(z-o|0)){r=239;J=39;break}tf(q|0,1016936+r|0,u|0)|0;n=n+1|0;do if(!H){if(u>>>0>v>>>0){Tc(j,o-B|0);Uc(j,u-v|0);p=o;break}if(u>>>0<v>>>0){Tc(j,o-B-v+u|0);Vc(j,v-u|0);p=o}else p=B}else p=B;while(0);if((i[s>>0]|0)>=0){A=1009832;u=0;B=p;p=t;r=0;continue}r=l[1016936+(r+u)>>0]|0;A=1009832+(r<<6)|0;u=w;B=p;p=t}c:do if((J|0)==10)J=39;else if((J|0)==43){J=0;if((A-1009832|0)>>>0<320)r=241;else while(1){q=o+-1|0;o=q;p=p+-1|0;if(q>>>0<=a>>>0){r=240;break c}if((i[q>>0]&-64)<<24>>24!=-128){r=240;break}}}while(0);d:do if((J|0)==39){J=0;o=o+-1|0;p=p+-1|0;if((A-1009832|0)>>>0>=320)do{q=o+-1|0;o=q;p=p+-1|0;if(q>>>0<=a>>>0)break d}while((i[q>>0]&-64)<<24>>24==-128)}while(0);if(I&o>>>0>B>>>0)Tc(j,o-B|0);q=o-C|0;o=p-D|0}F=q+F|0;G=o+G|0;E=n+E|0;if((r|0)!=253)break;else{a=a+q|0;b=b-q|0;c=c+o|0;d=d-o|0}}k[f>>2]=F;k[g>>2]=G;k[h>>2]=E;return}function yd(a){a=a|0;if(a|0)Qe(a);return}function zd(a){a=a|0;return Ec(k[a>>2]|0)|0}function Ad(a){a=a|0;return i[a+4>>0]|0}function Bd(a){a=a|0;if(a|0){Cd(a);Qe(a)}return}function Cd(a){a=a|0;var b=0;b=k[a+4>>2]|0;if(b|0)Qe(b);b=k[a+8>>2]|0;if(b|0)Qe(b);b=k[a+12>>2]|0;if(b|0)Qe(b);return}function Dd(a,b,c){a=a|0;b=b|0;c=c|0;return Ed(b,c)|0}function Ed(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;c=r;r=r+32|0;e=c+16|0;d=c+4|0;f=c+28|0;k[e>>2]=0;k[e+4>>2]=0;k[e+8>>2]=0;k[d>>2]=0;k[d+4>>2]=0;k[d+8>>2]=0;i[f>>0]=0;a=wb(a,Vd(a)|0,b,e,d,c,f)|0;b=gf(20)|0;Fd(b,(i[f>>0]|0)!=0,a,e,d);r=c;return b|0}function Fd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;k[a>>2]=c;i[a+16>>0]=b&1;b=gf(8)|0;c=k[e>>2]&255;k[b>>2]=k[d>>2];i[b+4>>0]=c;k[a+4>>2]=b;b=gf(8)|0;c=k[e+4>>2]&255;k[b>>2]=k[d+4>>2];i[b+4>>0]=c;k[a+8>>2]=b;b=gf(8)|0;e=k[e+8>>2]&255;k[b>>2]=k[d+8>>2];i[b+4>>0]=e;k[a+12>>2]=b;return}function Gd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;return Hd(b,c,d,e,f)|0}function Hd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,l=0;f=r;r=r+80|0;l=f+56|0;h=f+40|0;g=f+28|0;j=f+72|0;k[l>>2]=e;k[l+4>>2]=c;k[l+8>>2]=d;k[l+12>>2]=26;k[h>>2]=0;k[h+4>>2]=0;k[h+8>>2]=0;k[g>>2]=0;k[g+4>>2]=0;k[g+8>>2]=0;i[j>>0]=0;e=xb(a,Vd(a)|0,b,l,h,g,f,f+24|0,j)|0;c=gf(20)|0;Fd(c,(i[j>>0]|0)!=0,e,h,g);r=f;return c|0}function Id(a){a=a|0;return (i[a+16>>0]|0)!=0|0}function Jd(a){a=a|0;return Ec(k[a>>2]|0)|0}function Kd(a,b){a=a|0;b=b|0;if(b>>>0>=3)Ka(0,b|0,3)|0;return k[a+4+(b<<2)>>2]|0}function Ld(a){a=a|0;if(a|0)Qe(a);return}function Md(a){a=a|0;return Ec(k[a>>2]|0)|0}function Nd(a){a=a|0;if(a|0)Qe(a);return}function Od(a){a=a|0;var b=0,c=0;b=r;r=r+16|0;c=b;k[c>>2]=k[a+60>>2];a=Pd(ua(6,c|0)|0)|0;r=b;return a|0}function Pd(a){a=a|0;var b=0;if(a>>>0>4294963200){b=Qd()|0;k[b>>2]=0-a;a=-1}return a|0}function Qd(){var a=0;if(!0)a=1097308;else{a=(Ha()|0)+64|0;a=k[a>>2]|0}return a|0}function Rd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0;p=r;r=r+48|0;m=p+16|0;l=p;i=p+32|0;n=a+28|0;g=k[n>>2]|0;k[i>>2]=g;o=a+20|0;g=(k[o>>2]|0)-g|0;k[i+4>>2]=g;k[i+8>>2]=b;k[i+12>>2]=c;h=a+60|0;j=a+44|0;e=2;b=g+c|0;while(1){if(!0){k[m>>2]=k[h>>2];k[m+4>>2]=i;k[m+8>>2]=e;f=Pd(Ra(146,m|0)|0)|0}else{Oa(8,a|0);k[l>>2]=k[h>>2];k[l+4>>2]=i;k[l+8>>2]=e;f=Pd(Ra(146,l|0)|0)|0;ta(0)}if((b|0)==(f|0)){b=6;break}if((f|0)<0){b=8;break}b=b-f|0;d=k[i+4>>2]|0;if(f>>>0<=d>>>0)if((e|0)==2){k[n>>2]=(k[n>>2]|0)+f;g=d;d=i;e=2}else{g=d;d=i}else{g=k[j>>2]|0;k[n>>2]=g;k[o>>2]=g;g=k[i+12>>2]|0;f=f-d|0;d=i+8|0;e=e+-1|0}k[d>>2]=(k[d>>2]|0)+f;k[d+4>>2]=g-f;i=d}if((b|0)==6){m=k[j>>2]|0;k[a+16>>2]=m+(k[a+48>>2]|0);a=m;k[n>>2]=a;k[o>>2]=a}else if((b|0)==8){k[a+16>>2]=0;k[n>>2]=0;k[o>>2]=0;k[a>>2]=k[a>>2]|32;if((e|0)==2)c=0;else c=c-(k[i+4>>2]|0)|0}r=p;return c|0}function Sd(a){a=a|0;return}function Td(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;e=r;r=r+32|0;f=e;d=e+20|0;k[f>>2]=k[a+60>>2];k[f+4>>2]=0;k[f+8>>2]=b;k[f+12>>2]=d;k[f+16>>2]=c;if((Pd(Qa(140,f|0)|0)|0)<0){k[d>>2]=-1;a=-1}else a=k[d>>2]|0;r=e;return a|0}function Ud(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;d=b&255;a:do if(!d)a=a+(Vd(a)|0)|0;else{if(a&3){c=b&255;do{e=i[a>>0]|0;if(e<<24>>24==0?1:e<<24>>24==c<<24>>24)break a;a=a+1|0}while((a&3|0)!=0)}d=ha(d,16843009)|0;c=k[a>>2]|0;b:do if(!((c&-2139062144^-2139062144)&c+-16843009))do{e=c^d;if((e&-2139062144^-2139062144)&e+-16843009|0)break b;a=a+4|0;c=k[a>>2]|0}while(!((c&-2139062144^-2139062144)&c+-16843009|0));while(0);c=b&255;while(1){e=i[a>>0]|0;if(e<<24>>24==0?1:e<<24>>24==c<<24>>24)break;else a=a+1|0}}while(0);return a|0}function Vd(a){a=a|0;var b=0,c=0,d=0;d=a;a:do if(!(d&3))c=4;else{b=a;a=d;while(1){if(!(i[b>>0]|0))break a;b=b+1|0;a=b;if(!(a&3)){a=b;c=4;break}}}while(0);if((c|0)==4){while(1){b=k[a>>2]|0;if(!((b&-2139062144^-2139062144)&b+-16843009))a=a+4|0;else break}if((b&255)<<24>>24)do a=a+1|0;while((i[a>>0]|0)!=0)}return a-d|0}function Wd(a,b){a=+a;b=b|0;var c=0,d=0,e=0;p[t>>3]=a;c=k[t>>2]|0;d=k[t+4>>2]|0;e=rf(c|0,d|0,52)|0;e=e&2047;switch(e|0){case 0:{if(a!=0.0){a=+Wd(a*18446744073709551616.0,b);c=(k[b>>2]|0)+-64|0}else c=0;k[b>>2]=c;break}case 2047:break;default:{k[b>>2]=e+-1022;k[t>>2]=c;k[t+4>>2]=d&-2146435073|1071644672;a=+p[t>>3]}}return +a}function Xd(a,b){a=+a;b=b|0;return +(+Wd(a,b))}function Yd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;a:do if(!c)c=0;else{while(1){d=i[a>>0]|0;e=i[b>>0]|0;if(d<<24>>24!=e<<24>>24)break;c=c+-1|0;if(!c){c=0;break a}else{a=a+1|0;b=b+1|0}}c=(d&255)-(e&255)|0}while(0);return c|0}function Zd(a){a=a|0;var b=0,c=0;b=0;while(1){if((l[1094098+b>>0]|0)==(a|0)){c=2;break}b=b+1|0;if((b|0)==87){b=87;a=1094186;c=5;break}}if((c|0)==2)if(!b)b=1094186;else{a=1094186;c=5}if((c|0)==5)while(1){do{c=a;a=a+1|0}while((i[c>>0]|0)!=0);b=b+-1|0;if(!b){b=a;break}else c=5}return b|0}function _d(a,b){a=a|0;b=b|0;var c=0,d=0;d=i[a>>0]|0;c=i[b>>0]|0;if(d<<24>>24==0?1:d<<24>>24!=c<<24>>24)b=d;else{do{a=a+1|0;b=b+1|0;d=i[a>>0]|0;c=i[b>>0]|0}while(!(d<<24>>24==0?1:d<<24>>24!=c<<24>>24));b=d}return (b&255)-(c&255)|0}function $d(a,b){a=a|0;b=b|0;if(!a)a=0;else a=ae(a,b)|0;return a|0}function ae(a,b){a=a|0;b=b|0;do if(a){if(b>>>0<128){i[a>>0]=b;a=1;break}if(b>>>0<2048){i[a>>0]=b>>>6|192;i[a+1>>0]=b&63|128;a=2;break}if(b>>>0<55296|(b&-8192|0)==57344){i[a>>0]=b>>>12|224;i[a+1>>0]=b>>>6&63|128;i[a+2>>0]=b&63|128;a=3;break}if((b+-65536|0)>>>0<1048576){i[a>>0]=b>>>18|240;i[a+1>>0]=b>>>12&63|128;i[a+2>>0]=b>>>6&63|128;i[a+3>>0]=b&63|128;a=4;break}else{a=Qd()|0;k[a>>2]=84;a=-1;break}}else a=1;while(0);return a|0}function be(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0;o=r;r=r+128|0;j=o+112|0;l=o;m=l;p=941128;q=m+112|0;do{k[m>>2]=k[p>>2];m=m+4|0;p=p+4|0}while((m|0)<(q|0));if((b+-1|0)>>>0>2147483646)if(!b){e=j;f=1;n=4}else{q=Qd()|0;k[q>>2]=75}else{e=a;f=b;n=4}if((n|0)==4?(q=-2-e|0,q=f>>>0>q>>>0?q:f,k[l+48>>2]=q,h=l+20|0,k[h>>2]=e,k[l+44>>2]=e,p=e+q|0,g=l+16|0,k[g>>2]=p,k[l+28>>2]=p,de(l,c,d)|0,q|0):0){q=k[h>>2]|0;i[q+(((q|0)==(k[g>>2]|0))<<31>>31)>>0]=0}r=o;return}function ce(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=a+20|0;e=k[d>>2]|0;a=(k[a+16>>2]|0)-e|0;a=a>>>0>c>>>0?c:a;tf(e|0,b|0,a|0)|0;k[d>>2]=(k[d>>2]|0)+a;return c|0}function de(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0;q=r;r=r+224|0;m=q+120|0;p=q+80|0;o=q;n=q+136|0;d=p;e=d+40|0;do{k[d>>2]=0;d=d+4|0}while((d|0)<(e|0));k[m>>2]=k[c>>2];if((ee(0,b,m,o,p)|0)<0)c=-1;else{c=k[a>>2]|0;l=c&32;if((i[a+74>>0]|0)<1)k[a>>2]=c&-33;j=a+48|0;if(!(k[j>>2]|0)){d=a+44|0;e=k[d>>2]|0;k[d>>2]=n;f=a+28|0;k[f>>2]=n;g=a+20|0;k[g>>2]=n;k[j>>2]=80;h=a+16|0;k[h>>2]=n+80;c=ee(a,b,m,o,p)|0;if(e){Ua[k[a+36>>2]&7](a,0,0)|0;c=(k[g>>2]|0)==0?-1:c;k[d>>2]=e;k[j>>2]=0;k[h>>2]=0;k[f>>2]=0;k[g>>2]=0}}else c=ee(a,b,m,o,p)|0;p=k[a>>2]|0;k[a>>2]=p|l;c=(p&32|0)==0?c:-1}r=q;return c|0}function ee(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,m=0.0,n=0,o=0,q=0,s=0,u=0,v=0,w=0.0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ia=0;ia=r;r=r+624|0;ca=ia+24|0;ea=ia+16|0;da=ia+588|0;$=ia+576|0;ba=ia;W=ia+536|0;ga=ia+8|0;fa=ia+528|0;M=(a|0)!=0;N=W+40|0;V=N;W=W+39|0;X=ga+4|0;Y=da;Z=0-Y|0;_=$+12|0;$=$+11|0;aa=_;O=aa-Y|0;P=-2-Y|0;Q=aa+2|0;R=ca+288|0;S=da+9|0;T=S;U=da+8|0;f=0;g=0;o=0;x=b;a:while(1){do if((f|0)>-1)if((g|0)>(2147483647-f|0)){f=Qd()|0;k[f>>2]=75;f=-1;break}else{f=g+f|0;break}while(0);b=i[x>>0]|0;if(!(b<<24>>24)){K=244;break}else g=x;b:while(1){switch(b<<24>>24){case 37:{b=g;K=9;break b}case 0:{b=g;break b}default:{}}J=g+1|0;b=i[J>>0]|0;g=J}c:do if((K|0)==9)while(1){K=0;if((i[b+1>>0]|0)!=37)break c;g=g+1|0;b=b+2|0;if((i[b>>0]|0)==37)K=9;else break}while(0);v=g-x|0;if(M?(k[a>>2]&32|0)==0:0)fe(x,v,a)|0;if((g|0)!=(x|0)){g=v;x=b;continue}n=b+1|0;g=i[n>>0]|0;h=(g<<24>>24)+-48|0;if(h>>>0<10){J=(i[b+2>>0]|0)==36;n=J?b+3|0:n;g=i[n>>0]|0;s=J?h:-1;o=J?1:o}else s=-1;b=g<<24>>24;d:do if((b&-32|0)==32){h=0;do{if(!(1<<b+-32&75913))break d;h=1<<(g<<24>>24)+-32|h;n=n+1|0;g=i[n>>0]|0;b=g<<24>>24}while((b&-32|0)==32)}else h=0;while(0);do if(g<<24>>24==42){g=n+1|0;b=(i[g>>0]|0)+-48|0;if(b>>>0<10?(i[n+2>>0]|0)==36:0){k[e+(b<<2)>>2]=10;b=1;n=n+3|0;g=k[d+((i[g>>0]|0)+-48<<3)>>2]|0}else{if(o|0){f=-1;break a}if(!M){u=h;J=0;n=g;I=0;break}b=(k[c>>2]|0)+(4-1)&~(4-1);J=k[b>>2]|0;k[c>>2]=b+4;b=0;n=g;g=J}if((g|0)<0){u=h|8192;J=b;I=0-g|0}else{u=h;J=b;I=g}}else{b=(g<<24>>24)+-48|0;if(b>>>0<10){g=0;do{g=(g*10|0)+b|0;n=n+1|0;b=(i[n>>0]|0)+-48|0}while(b>>>0<10);if((g|0)<0){f=-1;break a}else{u=h;J=o;I=g}}else{u=h;J=o;I=0}}while(0);e:do if((i[n>>0]|0)==46){b=n+1|0;g=i[b>>0]|0;if(g<<24>>24!=42){h=(g<<24>>24)+-48|0;if(h>>>0<10)g=0;else{o=0;break}while(1){g=(g*10|0)+h|0;b=b+1|0;h=(i[b>>0]|0)+-48|0;if(h>>>0>=10){o=g;break e}}}b=n+2|0;g=(i[b>>0]|0)+-48|0;if(g>>>0<10?(i[n+3>>0]|0)==36:0){k[e+(g<<2)>>2]=10;o=k[d+((i[b>>0]|0)+-48<<3)>>2]|0;b=n+4|0;break}if(J|0){f=-1;break a}if(M){H=(k[c>>2]|0)+(4-1)&~(4-1);o=k[H>>2]|0;k[c>>2]=H+4}else o=0}else{o=-1;b=n}while(0);q=0;while(1){g=(i[b>>0]|0)+-65|0;if(g>>>0>57){f=-1;break a}H=b+1|0;g=i[1095990+(q*58|0)+g>>0]|0;h=g&255;if((h+-1|0)>>>0<8){b=H;q=h}else break}if(!(g<<24>>24)){f=-1;break}n=(s|0)>-1;do if(g<<24>>24==19)if(n){f=-1;break a}else K=52;else{if(n){k[e+(s<<2)>>2]=h;F=d+(s<<3)|0;G=k[F+4>>2]|0;K=ba;k[K>>2]=k[F>>2];k[K+4>>2]=G;K=52;break}if(!M){f=0;break a}he(ba,h,c)}while(0);if((K|0)==52?(K=0,!M):0){g=v;o=J;x=H;continue}s=i[b>>0]|0;s=(q|0)!=0&(s&15|0)==3?s&-33:s;h=u&-65537;G=(u&8192|0)==0?u:h;f:do switch(s|0){case 110:switch(q|0){case 0:{k[k[ba>>2]>>2]=f;g=v;o=J;x=H;continue a}case 1:{k[k[ba>>2]>>2]=f;g=v;o=J;x=H;continue a}case 2:{g=k[ba>>2]|0;k[g>>2]=f;k[g+4>>2]=((f|0)<0)<<31>>31;g=v;o=J;x=H;continue a}case 3:{j[k[ba>>2]>>1]=f;g=v;o=J;x=H;continue a}case 4:{i[k[ba>>2]>>0]=f;g=v;o=J;x=H;continue a}case 6:{k[k[ba>>2]>>2]=f;g=v;o=J;x=H;continue a}case 7:{g=k[ba>>2]|0;k[g>>2]=f;k[g+4>>2]=((f|0)<0)<<31>>31;g=v;o=J;x=H;continue a}default:{g=v;o=J;x=H;continue a}}case 112:{q=G|8;o=o>>>0>8?o:8;s=120;K=64;break}case 88:case 120:{q=G;K=64;break}case 111:{h=ba;g=k[h>>2]|0;h=k[h+4>>2]|0;if((g|0)==0&(h|0)==0)b=N;else{b=N;do{b=b+-1|0;i[b>>0]=g&7|48;g=rf(g|0,h|0,3)|0;h=L}while(!((g|0)==0&(h|0)==0))}if(!(G&8)){g=G;q=0;n=1096470;K=77}else{q=V-b|0;g=G;o=(o|0)>(q|0)?o:q+1|0;q=0;n=1096470;K=77}break}case 105:case 100:{g=ba;b=k[g>>2]|0;g=k[g+4>>2]|0;if((g|0)<0){b=pf(0,0,b|0,g|0)|0;g=L;h=ba;k[h>>2]=b;k[h+4>>2]=g;h=1;n=1096470;K=76;break f}if(!(G&2048)){n=G&1;h=n;n=(n|0)==0?1096470:1096472;K=76}else{h=1;n=1096471;K=76}break}case 117:{g=ba;b=k[g>>2]|0;g=k[g+4>>2]|0;h=0;n=1096470;K=76;break}case 99:{i[W>>0]=k[ba>>2];b=W;s=1;v=0;u=1096470;g=N;break}case 109:{g=Qd()|0;g=Zd(k[g>>2]|0)|0;K=82;break}case 115:{g=k[ba>>2]|0;g=g|0?g:1096480;K=82;break}case 67:{k[ga>>2]=k[ba>>2];k[X>>2]=0;k[ba>>2]=ga;b=ga;o=-1;K=86;break}case 83:{b=k[ba>>2]|0;if(!o){ke(a,32,I,0,G);b=0;K=97}else K=86;break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{m=+p[ba>>3];k[ea>>2]=0;p[t>>3]=m;if((k[t+4>>2]|0)>=0)if(!(G&2048)){F=G&1;E=F;F=(F|0)==0?1096488:1096493}else{E=1;F=1096490}else{m=-m;E=1;F=1096487}p[t>>3]=m;D=k[t+4>>2]&2146435072;do if(D>>>0<2146435072|(D|0)==2146435072&0<0){w=+Xd(m,ea)*2.0;g=w!=0.0;if(g)k[ea>>2]=(k[ea>>2]|0)+-1;B=s|32;if((B|0)==97){u=s&32;x=(u|0)==0?F:F+9|0;v=E|2;b=12-o|0;do if(!(o>>>0>11|(b|0)==0)){m=8.0;do{b=b+-1|0;m=m*16.0}while((b|0)!=0);if((i[x>>0]|0)==45){m=-(m+(-w-m));break}else{m=w+m-m;break}}else m=w;while(0);g=k[ea>>2]|0;b=(g|0)<0?0-g|0:g;b=ie(b,((b|0)<0)<<31>>31,_)|0;if((b|0)==(_|0)){i[$>>0]=48;b=$}i[b+-1>>0]=(g>>31&2)+43;q=b+-2|0;i[q>>0]=s+15;n=(o|0)<1;h=(G&8|0)==0;g=da;while(1){F=~~m;b=g+1|0;i[g>>0]=l[1096454+F>>0]|u;m=(m-+(F|0))*16.0;do if((b-Y|0)==1){if(h&(n&m==0.0))break;i[b>>0]=46;b=g+2|0}while(0);if(!(m!=0.0))break;else g=b}h=q;o=(o|0)!=0&(P+b|0)<(o|0)?Q+o-h|0:O-h+b|0;n=o+v|0;ke(a,32,I,n,G);if(!(k[a>>2]&32))fe(x,v,a)|0;ke(a,48,I,n,G^65536);g=b-Y|0;if(!(k[a>>2]&32))fe(da,g,a)|0;b=aa-h|0;ke(a,48,o-(g+b)|0,0,0);if(!(k[a>>2]&32))fe(q,b,a)|0;ke(a,32,I,n,G^8192);b=(n|0)<(I|0)?I:n;break}b=(o|0)<0?6:o;if(g){g=(k[ea>>2]|0)+-28|0;k[ea>>2]=g;m=w*268435456.0}else{m=w;g=k[ea>>2]|0}D=(g|0)<0?ca:R;C=D;h=D;do{A=~~m>>>0;k[h>>2]=A;h=h+4|0;m=(m-+(A>>>0))*1.0e9}while(m!=0.0);g=k[ea>>2]|0;if((g|0)>0){n=D;o=h;while(1){q=(g|0)>29?29:g;g=o+-4|0;do if(g>>>0>=n>>>0){h=0;do{z=sf(k[g>>2]|0,0,q|0)|0;z=of(z|0,L|0,h|0,0)|0;A=L;y=Cf(z|0,A|0,1e9,0)|0;k[g>>2]=y;h=Bf(z|0,A|0,1e9,0)|0;g=g+-4|0}while(g>>>0>=n>>>0);if(!h)break;n=n+-4|0;k[n>>2]=h}while(0);h=o;while(1){if(h>>>0<=n>>>0)break;g=h+-4|0;if(!(k[g>>2]|0))h=g;else break}g=(k[ea>>2]|0)-q|0;k[ea>>2]=g;if((g|0)>0)o=h;else break}}else n=D;if((g|0)<0){x=((b+25|0)/9|0)+1|0;y=(B|0)==102;do{v=0-g|0;v=(v|0)>9?9:v;do if(n>>>0<h>>>0){g=(1<<v)+-1|0;o=1e9>>>v;u=0;q=n;do{A=k[q>>2]|0;k[q>>2]=(A>>>v)+u;u=ha(A&g,o)|0;q=q+4|0}while(q>>>0<h>>>0);g=(k[n>>2]|0)==0?n+4|0:n;if(!u){n=g;g=h;break}k[h>>2]=u;n=g;g=h+4|0}else{n=(k[n>>2]|0)==0?n+4|0:n;g=h}while(0);h=y?D:n;h=(g-h>>2|0)>(x|0)?h+(x<<2)|0:g;g=(k[ea>>2]|0)+v|0;k[ea>>2]=g}while((g|0)<0);x=n;y=h}else{x=n;y=h}do if(x>>>0<y>>>0){g=(C-x>>2)*9|0;n=k[x>>2]|0;if(n>>>0<10)break;else h=10;do{h=h*10|0;g=g+1|0}while(n>>>0>=h>>>0)}else g=0;while(0);z=(B|0)==103;A=(b|0)!=0;h=b-((B|0)!=102?g:0)+((A&z)<<31>>31)|0;if((h|0)<(((y-C>>2)*9|0)+-9|0)){o=h+9216|0;h=D+4+(((o|0)/9|0)+-1024<<2)|0;o=((o|0)%9|0)+1|0;if((o|0)<9){n=10;do{n=n*10|0;o=o+1|0}while((o|0)!=9)}else n=10;u=k[h>>2]|0;v=(u>>>0)%(n>>>0)|0;o=(h+4|0)==(y|0);do if(o&(v|0)==0)n=x;else{w=(((u>>>0)/(n>>>0)|0)&1|0)==0?9007199254740992.0:9007199254740994.0;q=(n|0)/2|0;if(v>>>0<q>>>0)m=.5;else m=o&(v|0)==(q|0)?1.0:1.5;do if(E){if((i[F>>0]|0)!=45)break;w=-w;m=-m}while(0);o=u-v|0;k[h>>2]=o;if(!(w+m!=w)){n=x;break}B=o+n|0;k[h>>2]=B;if(B>>>0>999999999){g=x;while(1){n=h+-4|0;k[h>>2]=0;if(n>>>0<g>>>0){g=g+-4|0;k[g>>2]=0}B=(k[n>>2]|0)+1|0;k[n>>2]=B;if(B>>>0>999999999)h=n;else{q=g;h=n;break}}}else q=x;g=(C-q>>2)*9|0;o=k[q>>2]|0;if(o>>>0<10){n=q;break}else n=10;do{n=n*10|0;g=g+1|0}while(o>>>0>=n>>>0);n=q}while(0);h=h+4|0;x=n;h=y>>>0>h>>>0?h:y}else h=y;v=0-g|0;B=h;while(1){if(B>>>0<=x>>>0){y=0;break}h=B+-4|0;if(!(k[h>>2]|0))B=h;else{y=1;break}}do if(z){b=(A&1^1)+b|0;if((b|0)>(g|0)&(g|0)>-5){s=s+-1|0;b=b+-1-g|0}else{s=s+-2|0;b=b+-1|0}h=G&8;if(h|0)break;do if(y){h=k[B+-4>>2]|0;if(!h){n=9;break}if(!((h>>>0)%10|0)){o=10;n=0}else{n=0;break}do{o=o*10|0;n=n+1|0}while(!((h>>>0)%(o>>>0)|0|0))}else n=9;while(0);h=((B-C>>2)*9|0)+-9|0;if((s|32|0)==102){h=h-n|0;h=(h|0)<0?0:h;b=(b|0)<(h|0)?b:h;h=0;break}else{h=h+g-n|0;h=(h|0)<0?0:h;b=(b|0)<(h|0)?b:h;h=0;break}}else h=G&8;while(0);u=b|h;o=(u|0)!=0&1;q=(s|32|0)==102;if(q){g=(g|0)>0?g:0;s=0}else{n=(g|0)<0?v:g;n=ie(n,((n|0)<0)<<31>>31,_)|0;if((aa-n|0)<2)do{n=n+-1|0;i[n>>0]=48}while((aa-n|0)<2);i[n+-1>>0]=(g>>31&2)+43;C=n+-2|0;i[C>>0]=s;g=aa-C|0;s=C}v=E+1+b+o+g|0;ke(a,32,I,v,G);if(!(k[a>>2]&32))fe(F,E,a)|0;ke(a,48,I,v,G^65536);do if(q){n=x>>>0>D>>>0?D:x;h=n;do{g=ie(k[h>>2]|0,0,S)|0;do if((h|0)==(n|0)){if((g|0)!=(S|0))break;i[U>>0]=48;g=U}else{if(g>>>0<=da>>>0)break;qf(da|0,48,g-Y|0)|0;do g=g+-1|0;while(g>>>0>da>>>0)}while(0);if(!(k[a>>2]&32))fe(g,T-g|0,a)|0;h=h+4|0}while(h>>>0<=D>>>0);do if(u|0){if(k[a>>2]&32|0)break;fe(1096522,1,a)|0}while(0);if((b|0)>0&h>>>0<B>>>0)while(1){g=ie(k[h>>2]|0,0,S)|0;if(g>>>0>da>>>0){qf(da|0,48,g-Y|0)|0;do g=g+-1|0;while(g>>>0>da>>>0)}if(!(k[a>>2]&32))fe(g,(b|0)>9?9:b,a)|0;h=h+4|0;g=b+-9|0;if(!((b|0)>9&h>>>0<B>>>0)){b=g;break}else b=g}ke(a,48,b+9|0,9,0)}else{q=y?B:x+4|0;if((b|0)>-1){o=(h|0)==0;n=x;do{g=ie(k[n>>2]|0,0,S)|0;if((g|0)==(S|0)){i[U>>0]=48;g=U}do if((n|0)==(x|0)){h=g+1|0;if(!(k[a>>2]&32))fe(g,1,a)|0;if(o&(b|0)<1){g=h;break}if(k[a>>2]&32|0){g=h;break}fe(1096522,1,a)|0;g=h}else{if(g>>>0<=da>>>0)break;qf(da|0,48,g+Z|0)|0;do g=g+-1|0;while(g>>>0>da>>>0)}while(0);h=T-g|0;if(!(k[a>>2]&32))fe(g,(b|0)>(h|0)?h:b,a)|0;b=b-h|0;n=n+4|0}while(n>>>0<q>>>0&(b|0)>-1)}ke(a,48,b+18|0,18,0);if(k[a>>2]&32|0)break;fe(s,aa-s|0,a)|0}while(0);ke(a,32,I,v,G^8192);b=(v|0)<(I|0)?I:v}else{q=(s&32|0)!=0;o=m!=m|0.0!=0.0;g=o?0:E;n=g+3|0;ke(a,32,I,n,h);b=k[a>>2]|0;if(!(b&32)){fe(F,g,a)|0;b=k[a>>2]|0}if(!(b&32))fe(o?(q?1096514:1096518):q?1096506:1096510,3,a)|0;ke(a,32,I,n,G^8192);b=(n|0)<(I|0)?I:n}while(0);g=b;o=J;x=H;continue a}default:{b=x;h=G;s=o;v=0;u=1096470;g=N}}while(0);g:do if((K|0)==64){h=ba;g=k[h>>2]|0;h=k[h+4>>2]|0;n=s&32;if(!((g|0)==0&(h|0)==0)){b=N;do{b=b+-1|0;i[b>>0]=l[1096454+(g&15)>>0]|n;g=rf(g|0,h|0,4)|0;h=L}while(!((g|0)==0&(h|0)==0));K=ba;if((q&8|0)==0|(k[K>>2]|0)==0&(k[K+4>>2]|0)==0){g=q;q=0;n=1096470;K=77}else{g=q;q=2;n=1096470+(s>>4)|0;K=77}}else{b=N;g=q;q=0;n=1096470;K=77}}else if((K|0)==76){b=ie(b,g,N)|0;g=G;q=h;K=77}else if((K|0)==82){K=0;G=je(g,0,o)|0;F=(G|0)==0;b=g;s=F?o:G-g|0;v=0;u=1096470;g=F?g+o|0:G}else if((K|0)==86){K=0;h=0;g=0;q=b;while(1){n=k[q>>2]|0;if(!n)break;g=$d(fa,n)|0;if((g|0)<0|g>>>0>(o-h|0)>>>0)break;h=g+h|0;if(o>>>0>h>>>0)q=q+4|0;else break}if((g|0)<0){f=-1;break a}ke(a,32,I,h,G);if(!h){b=0;K=97}else{n=0;while(1){g=k[b>>2]|0;if(!g){b=h;K=97;break g}g=$d(fa,g)|0;n=g+n|0;if((n|0)>(h|0)){b=h;K=97;break g}if(!(k[a>>2]&32))fe(fa,g,a)|0;if(n>>>0>=h>>>0){b=h;K=97;break}else b=b+4|0}}}while(0);if((K|0)==97){K=0;ke(a,32,I,b,G^8192);g=(I|0)>(b|0)?I:b;o=J;x=H;continue}if((K|0)==77){K=0;h=(o|0)>-1?g&-65537:g;g=ba;g=(k[g>>2]|0)!=0|(k[g+4>>2]|0)!=0;if((o|0)!=0|g){s=(g&1^1)+(V-b)|0;s=(o|0)>(s|0)?o:s;v=q;u=n;g=N}else{b=N;s=0;v=q;u=n;g=N}}q=g-b|0;n=(s|0)<(q|0)?q:s;o=v+n|0;g=(I|0)<(o|0)?o:I;ke(a,32,g,o,h);if(!(k[a>>2]&32))fe(u,v,a)|0;ke(a,48,g,o,h^65536);ke(a,48,n,q,0);if(!(k[a>>2]&32))fe(b,q,a)|0;ke(a,32,g,o,h^8192);o=J;x=H}h:do if((K|0)==244)if(!a)if(!o)f=0;else{f=1;while(1){b=k[e+(f<<2)>>2]|0;if(!b){b=0;break}he(d+(f<<3)|0,b,c);f=f+1|0;if((f|0)>=10){f=1;break h}}while(1){f=f+1|0;if(b|0){f=-1;break h}if((f|0)>=10){f=1;break h}b=k[e+(f<<2)>>2]|0}}while(0);r=ia;return f|0}function fe(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=c+16|0;e=k[d>>2]|0;if(!e)if(!(ge(c)|0)){e=k[d>>2]|0;f=5}else d=0;else f=5;a:do if((f|0)==5){g=c+20|0;d=k[g>>2]|0;f=d;if((e-d|0)>>>0<b>>>0){d=Ua[k[c+36>>2]&7](c,a,b)|0;break}b:do if((i[c+75>>0]|0)>-1){d=b;while(1){if(!d){e=f;d=0;break b}e=d+-1|0;if((i[a+e>>0]|0)==10)break;else d=e}if((Ua[k[c+36>>2]&7](c,a,d)|0)>>>0<d>>>0)break a;b=b-d|0;a=a+d|0;e=k[g>>2]|0}else{e=f;d=0}while(0);tf(e|0,a|0,b|0)|0;k[g>>2]=(k[g>>2]|0)+b;d=d+b|0}while(0);return d|0}function ge(a){a=a|0;var b=0,c=0;b=a+74|0;c=i[b>>0]|0;i[b>>0]=c+255|c;b=k[a>>2]|0;if(!(b&8)){k[a+8>>2]=0;k[a+4>>2]=0;b=k[a+44>>2]|0;k[a+28>>2]=b;k[a+20>>2]=b;k[a+16>>2]=b+(k[a+48>>2]|0);b=0}else{k[a>>2]=b|32;b=-1}return b|0}function he(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0.0;a:do if(b>>>0<=20)do switch(b|0){case 9:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;k[a>>2]=b;break a}case 10:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;d=a;k[d>>2]=b;k[d+4>>2]=((b|0)<0)<<31>>31;break a}case 11:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;d=a;k[d>>2]=b;k[d+4>>2]=0;break a}case 12:{d=(k[c>>2]|0)+(8-1)&~(8-1);b=d;e=k[b>>2]|0;b=k[b+4>>2]|0;k[c>>2]=d+8;d=a;k[d>>2]=e;k[d+4>>2]=b;break a}case 13:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;d=(d&65535)<<16>>16;e=a;k[e>>2]=d;k[e+4>>2]=((d|0)<0)<<31>>31;break a}case 14:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;e=a;k[e>>2]=d&65535;k[e+4>>2]=0;break a}case 15:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;d=(d&255)<<24>>24;e=a;k[e>>2]=d;k[e+4>>2]=((d|0)<0)<<31>>31;break a}case 16:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;e=a;k[e>>2]=d&255;k[e+4>>2]=0;break a}case 17:{e=(k[c>>2]|0)+(8-1)&~(8-1);f=+p[e>>3];k[c>>2]=e+8;p[a>>3]=f;break a}case 18:{e=(k[c>>2]|0)+(8-1)&~(8-1);f=+p[e>>3];k[c>>2]=e+8;p[a>>3]=f;break a}default:break a}while(0);while(0);return}function ie(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if(b>>>0>0|(b|0)==0&a>>>0>4294967295)while(1){d=Cf(a|0,b|0,10,0)|0;c=c+-1|0;i[c>>0]=d|48;d=a;a=Bf(a|0,b|0,10,0)|0;if(!(b>>>0>9|(b|0)==9&d>>>0>4294967295))break;else b=L}if(a)while(1){c=c+-1|0;i[c>>0]=(a>>>0)%10|0|48;if(a>>>0<10)break;else a=(a>>>0)/10|0}return c|0}function je(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;f=b&255;d=(c|0)!=0;a:do if(d&(a&3|0)!=0){e=b&255;while(1){if((i[a>>0]|0)==e<<24>>24)break a;a=a+1|0;c=c+-1|0;d=(c|0)!=0;if(!(d&(a&3|0)!=0)){g=5;break}}}else g=5;while(0);b:do if((g|0)==5)if(d){e=b&255;if((i[a>>0]|0)!=e<<24>>24){d=ha(f,16843009)|0;c:do if(c>>>0>3)while(1){f=k[a>>2]^d;if((f&-2139062144^-2139062144)&f+-16843009|0)break;a=a+4|0;c=c+-4|0;if(c>>>0<=3){g=11;break c}}else g=11;while(0);if((g|0)==11)if(!c){c=0;break}while(1){if((i[a>>0]|0)==e<<24>>24)break b;a=a+1|0;c=c+-1|0;if(!c){c=0;break}}}}else c=0;while(0);return (c|0?a:0)|0}function ke(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;g=r;r=r+256|0;f=g;do if((c|0)>(d|0)&(e&73728|0)==0){e=c-d|0;qf(f|0,b|0,(e>>>0>256?256:e)|0)|0;d=k[a>>2]|0;c=(d&32|0)==0;if(e>>>0>255){b=e;do{if(c){fe(f,256,a)|0;d=k[a>>2]|0}b=b+-256|0;c=(d&32|0)==0}while(b>>>0>255);if(c)e=e&255;else break}else if(!c)break;fe(f,e,a)|0}while(0);r=g;return}function le(){return 0}function me(a,b){a=a|0;b=b|0;a=Ud(a,b)|0;return ((i[a>>0]|0)==(b&255)<<24>>24?a:0)|0}function ne(a,b){a=a|0;b=b|0;oe(a,b,4);return}function oe(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;e=b;a:do if(!((e^a)&3)){d=(c|0)!=0;if(d&(e&3|0)!=0)do{e=i[b>>0]|0;i[a>>0]=e;if(!(e<<24>>24))break a;c=c+-1|0;b=b+1|0;a=a+1|0;d=(c|0)!=0}while(d&(b&3|0)!=0);if(d){if(i[b>>0]|0){b:do if(c>>>0>3)do{d=k[b>>2]|0;if((d&-2139062144^-2139062144)&d+-16843009|0)break b;k[a>>2]=d;c=c+-4|0;b=b+4|0;a=a+4|0}while(c>>>0>3);while(0);f=11}}else c=0}else f=11;while(0);c:do if((f|0)==11)if(!c)c=0;else while(1){f=i[b>>0]|0;i[a>>0]=f;if(!(f<<24>>24))break c;c=c+-1|0;a=a+1|0;if(!c){c=0;break}else b=b+1|0}while(0);qf(a|0,0,c|0)|0;return}function pe(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=r;r=r+16|0;e=d;k[e>>2]=c;de(a,b,e)|0;r=d;return}function qe(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0;j=r;r=r+16|0;h=j;g=b&255;i[h>>0]=g;d=a+16|0;e=k[d>>2]|0;if(!e)if(!(ge(a)|0)){e=k[d>>2]|0;f=4}else c=-1;else f=4;do if((f|0)==4){d=a+20|0;f=k[d>>2]|0;if(f>>>0<e>>>0?(c=b&255,(c|0)!=(i[a+75>>0]|0)):0){k[d>>2]=f+1;i[f>>0]=g;break}if((Ua[k[a+36>>2]&7](a,h,1)|0)==1)c=l[h>>0]|0;else c=-1}while(0);r=j;return c|0}function re(){var a=0,b=0,c=0;do if((k[235273]|0)>=0?(le()|0)!=0:0){if((i[941091]|0)!=10?(a=k[235259]|0,a>>>0<(k[235258]|0)>>>0):0){k[235259]=a+1;i[a>>0]=10;break}qe(941016,10)|0}else c=3;while(0);do if((c|0)==3){if((i[941091]|0)!=10?(b=k[235259]|0,b>>>0<(k[235258]|0)>>>0):0){k[235259]=b+1;i[b>>0]=10;break}qe(941016,10)|0}while(0);return}function se(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;fe(a,ha(c,b)|0,d)|0;return}function te(a,b,c){a=a|0;b=b|0;c=c|0;be(a,2147483647,b,c);return}function ue(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=r;r=r+16|0;e=d;k[e>>2]=c;te(a,b,e);r=d;return}function ve(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0;do if(a>>>0<245){o=a>>>0<11?16:a+11&-8;a=o>>>3;i=k[274328]|0;b=i>>>a;if(b&3|0){b=(b&1^1)+a|0;c=1097352+(b<<1<<2)|0;d=c+8|0;e=k[d>>2]|0;f=e+8|0;g=k[f>>2]|0;do if((c|0)!=(g|0)){if(g>>>0<(k[274332]|0)>>>0)Na();a=g+12|0;if((k[a>>2]|0)==(e|0)){k[a>>2]=c;k[d>>2]=g;break}else Na()}else k[274328]=i&~(1<<b);while(0);G=b<<3;k[e+4>>2]=G|3;G=e+G+4|0;k[G>>2]=k[G>>2]|1;G=f;return G|0}g=k[274330]|0;if(o>>>0>g>>>0){if(b|0){c=2<<a;c=b<<a&(c|0-c);c=(c&0-c)+-1|0;h=c>>>12&16;c=c>>>h;e=c>>>5&8;c=c>>>e;f=c>>>2&4;c=c>>>f;d=c>>>1&2;c=c>>>d;b=c>>>1&1;b=(e|h|f|d|b)+(c>>>b)|0;c=1097352+(b<<1<<2)|0;d=c+8|0;f=k[d>>2]|0;h=f+8|0;e=k[h>>2]|0;do if((c|0)!=(e|0)){if(e>>>0<(k[274332]|0)>>>0)Na();a=e+12|0;if((k[a>>2]|0)==(f|0)){k[a>>2]=c;k[d>>2]=e;j=k[274330]|0;break}else Na()}else{k[274328]=i&~(1<<b);j=g}while(0);g=(b<<3)-o|0;k[f+4>>2]=o|3;d=f+o|0;k[d+4>>2]=g|1;k[d+g>>2]=g;if(j|0){e=k[274333]|0;b=j>>>3;c=1097352+(b<<1<<2)|0;a=k[274328]|0;b=1<<b;if(a&b){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{l=a;m=b}}else{k[274328]=a|b;l=c+8|0;m=c}k[l>>2]=e;k[m+12>>2]=e;k[e+8>>2]=m;k[e+12>>2]=c}k[274330]=g;k[274333]=d;G=h;return G|0}a=k[274329]|0;if(a){h=(a&0-a)+-1|0;F=h>>>12&16;h=h>>>F;E=h>>>5&8;h=h>>>E;G=h>>>2&4;h=h>>>G;b=h>>>1&2;h=h>>>b;i=h>>>1&1;i=k[1097616+((E|F|G|b|i)+(h>>>i)<<2)>>2]|0;h=(k[i+4>>2]&-8)-o|0;b=i;while(1){a=k[b+16>>2]|0;if(!a){a=k[b+20>>2]|0;if(!a)break}b=(k[a+4>>2]&-8)-o|0;G=b>>>0<h>>>0;h=G?b:h;b=a;i=G?a:i}e=k[274332]|0;if(i>>>0<e>>>0)Na();g=i+o|0;if(i>>>0>=g>>>0)Na();f=k[i+24>>2]|0;c=k[i+12>>2]|0;do if((c|0)==(i|0)){b=i+20|0;a=k[b>>2]|0;if(!a){b=i+16|0;a=k[b>>2]|0;if(!a){n=0;break}}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<e>>>0)Na();else{k[b>>2]=0;n=a;break}}else{d=k[i+8>>2]|0;if(d>>>0<e>>>0)Na();a=d+12|0;if((k[a>>2]|0)!=(i|0))Na();b=c+8|0;if((k[b>>2]|0)==(i|0)){k[a>>2]=c;k[b>>2]=d;n=c;break}else Na()}while(0);do if(f|0){a=k[i+28>>2]|0;b=1097616+(a<<2)|0;if((i|0)==(k[b>>2]|0)){k[b>>2]=n;if(!n){k[274329]=k[274329]&~(1<<a);break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(i|0))k[a>>2]=n;else k[f+20>>2]=n;if(!n)break}b=k[274332]|0;if(n>>>0<b>>>0)Na();k[n+24>>2]=f;a=k[i+16>>2]|0;do if(a|0)if(a>>>0<b>>>0)Na();else{k[n+16>>2]=a;k[a+24>>2]=n;break}while(0);a=k[i+20>>2]|0;if(a|0)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[n+20>>2]=a;k[a+24>>2]=n;break}}while(0);if(h>>>0<16){G=h+o|0;k[i+4>>2]=G|3;G=i+G+4|0;k[G>>2]=k[G>>2]|1}else{k[i+4>>2]=o|3;k[g+4>>2]=h|1;k[g+h>>2]=h;a=k[274330]|0;if(a|0){d=k[274333]|0;b=a>>>3;c=1097352+(b<<1<<2)|0;a=k[274328]|0;b=1<<b;if(a&b){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{p=a;q=b}}else{k[274328]=a|b;p=c+8|0;q=c}k[p>>2]=d;k[q+12>>2]=d;k[d+8>>2]=q;k[d+12>>2]=c}k[274330]=h;k[274333]=g}G=i+8|0;return G|0}}}else if(a>>>0<=4294967231){a=a+11|0;o=a&-8;j=k[274329]|0;if(j){c=0-o|0;a=a>>>8;if(a)if(o>>>0>16777215)i=31;else{q=(a+1048320|0)>>>16&8;z=a<<q;p=(z+520192|0)>>>16&4;z=z<<p;i=(z+245760|0)>>>16&2;i=14-(p|q|i)+(z<<i>>>15)|0;i=o>>>(i+7|0)&1|i<<1}else i=0;b=k[1097616+(i<<2)>>2]|0;a:do if(!b){a=0;b=0;z=86}else{e=c;a=0;g=o<<((i|0)==31?0:25-(i>>>1)|0);h=b;b=0;while(1){d=k[h+4>>2]&-8;c=d-o|0;if(c>>>0<e>>>0)if((d|0)==(o|0)){a=h;b=h;z=90;break a}else b=h;else c=e;d=k[h+20>>2]|0;h=k[h+16+(g>>>31<<2)>>2]|0;a=(d|0)==0|(d|0)==(h|0)?a:d;d=(h|0)==0;if(d){z=86;break}else{e=c;g=g<<(d&1^1)}}}while(0);if((z|0)==86){if((a|0)==0&(b|0)==0){a=2<<i;a=j&(a|0-a);if(!a)break;q=(a&0-a)+-1|0;m=q>>>12&16;q=q>>>m;l=q>>>5&8;q=q>>>l;n=q>>>2&4;q=q>>>n;p=q>>>1&2;q=q>>>p;a=q>>>1&1;a=k[1097616+((l|m|n|p|a)+(q>>>a)<<2)>>2]|0}if(!a){h=c;i=b}else z=90}if((z|0)==90)while(1){z=0;q=(k[a+4>>2]&-8)-o|0;d=q>>>0<c>>>0;c=d?q:c;b=d?a:b;d=k[a+16>>2]|0;if(d|0){a=d;z=90;continue}a=k[a+20>>2]|0;if(!a){h=c;i=b;break}else z=90}if((i|0)!=0?h>>>0<((k[274330]|0)-o|0)>>>0:0){e=k[274332]|0;if(i>>>0<e>>>0)Na();g=i+o|0;if(i>>>0>=g>>>0)Na();f=k[i+24>>2]|0;c=k[i+12>>2]|0;do if((c|0)==(i|0)){b=i+20|0;a=k[b>>2]|0;if(!a){b=i+16|0;a=k[b>>2]|0;if(!a){s=0;break}}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<e>>>0)Na();else{k[b>>2]=0;s=a;break}}else{d=k[i+8>>2]|0;if(d>>>0<e>>>0)Na();a=d+12|0;if((k[a>>2]|0)!=(i|0))Na();b=c+8|0;if((k[b>>2]|0)==(i|0)){k[a>>2]=c;k[b>>2]=d;s=c;break}else Na()}while(0);do if(f|0){a=k[i+28>>2]|0;b=1097616+(a<<2)|0;if((i|0)==(k[b>>2]|0)){k[b>>2]=s;if(!s){k[274329]=k[274329]&~(1<<a);break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(i|0))k[a>>2]=s;else k[f+20>>2]=s;if(!s)break}b=k[274332]|0;if(s>>>0<b>>>0)Na();k[s+24>>2]=f;a=k[i+16>>2]|0;do if(a|0)if(a>>>0<b>>>0)Na();else{k[s+16>>2]=a;k[a+24>>2]=s;break}while(0);a=k[i+20>>2]|0;if(a|0)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[s+20>>2]=a;k[a+24>>2]=s;break}}while(0);do if(h>>>0>=16){k[i+4>>2]=o|3;k[g+4>>2]=h|1;k[g+h>>2]=h;a=h>>>3;if(h>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;if(b&a){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{t=a;v=b}}else{k[274328]=b|a;t=c+8|0;v=c}k[t>>2]=g;k[v+12>>2]=g;k[g+8>>2]=v;k[g+12>>2]=c;break}a=h>>>8;if(a)if(h>>>0>16777215)c=31;else{F=(a+1048320|0)>>>16&8;G=a<<F;E=(G+520192|0)>>>16&4;G=G<<E;c=(G+245760|0)>>>16&2;c=14-(E|F|c)+(G<<c>>>15)|0;c=h>>>(c+7|0)&1|c<<1}else c=0;d=1097616+(c<<2)|0;k[g+28>>2]=c;a=g+16|0;k[a+4>>2]=0;k[a>>2]=0;a=k[274329]|0;b=1<<c;if(!(a&b)){k[274329]=a|b;k[d>>2]=g;k[g+24>>2]=d;k[g+12>>2]=g;k[g+8>>2]=g;break}c=h<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(h|0)){z=148;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){z=145;break}else{c=c<<1;d=a}}if((z|0)==145)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=g;k[g+24>>2]=d;k[g+12>>2]=g;k[g+8>>2]=g;break}else if((z|0)==148){a=d+8|0;b=k[a>>2]|0;G=k[274332]|0;if(b>>>0>=G>>>0&d>>>0>=G>>>0){k[b+12>>2]=g;k[a>>2]=g;k[g+8>>2]=b;k[g+12>>2]=d;k[g+24>>2]=0;break}else Na()}}else{G=h+o|0;k[i+4>>2]=G|3;G=i+G+4|0;k[G>>2]=k[G>>2]|1}while(0);G=i+8|0;return G|0}}}else o=-1;while(0);c=k[274330]|0;if(c>>>0>=o>>>0){a=c-o|0;b=k[274333]|0;if(a>>>0>15){G=b+o|0;k[274333]=G;k[274330]=a;k[G+4>>2]=a|1;k[G+a>>2]=a;k[b+4>>2]=o|3}else{k[274330]=0;k[274333]=0;k[b+4>>2]=c|3;G=b+c+4|0;k[G>>2]=k[G>>2]|1}G=b+8|0;return G|0}a=k[274331]|0;if(a>>>0>o>>>0){E=a-o|0;k[274331]=E;G=k[274334]|0;F=G+o|0;k[274334]=F;k[F+4>>2]=E|1;k[G+4>>2]=o|3;G=G+8|0;return G|0}do if(!(k[274446]|0)){a=Fa(30)|0;if(!(a+-1&a)){k[274448]=a;k[274447]=a;k[274449]=-1;k[274450]=-1;k[274451]=0;k[274439]=0;v=(Pa(0)|0)&-16^1431655768;k[274446]=v;break}else Na()}while(0);g=o+48|0;d=k[274448]|0;h=o+47|0;c=d+h|0;d=0-d|0;i=c&d;if(i>>>0<=o>>>0){G=0;return G|0}a=k[274438]|0;if(a|0?(t=k[274436]|0,v=t+i|0,v>>>0<=t>>>0|v>>>0>a>>>0):0){G=0;return G|0}b:do if(!(k[274439]&4)){b=k[274334]|0;c:do if(b){e=1097760;while(1){a=k[e>>2]|0;if(a>>>0<=b>>>0?(r=e+4|0,(a+(k[r>>2]|0)|0)>>>0>b>>>0):0)break;a=k[e+8>>2]|0;if(!a){z=173;break c}else e=a}a=c-(k[274331]|0)&d;if(a>>>0<2147483647){b=Aa(a|0)|0;if((b|0)==((k[e>>2]|0)+(k[r>>2]|0)|0)){if((b|0)!=(-1|0)){g=b;f=a;z=193;break b}}else z=183}}else z=173;while(0);do if((z|0)==173?(u=Aa(0)|0,(u|0)!=(-1|0)):0){a=u;b=k[274447]|0;c=b+-1|0;if(!(c&a))a=i;else a=i-a+(c+a&0-b)|0;b=k[274436]|0;c=b+a|0;if(a>>>0>o>>>0&a>>>0<2147483647){v=k[274438]|0;if(v|0?c>>>0<=b>>>0|c>>>0>v>>>0:0)break;b=Aa(a|0)|0;if((b|0)==(u|0)){g=u;f=a;z=193;break b}else z=183}}while(0);d:do if((z|0)==183){c=0-a|0;do if(g>>>0>a>>>0&(a>>>0<2147483647&(b|0)!=(-1|0))?(w=k[274448]|0,w=h-a+w&0-w,w>>>0<2147483647):0)if((Aa(w|0)|0)==(-1|0)){Aa(c|0)|0;break d}else{a=w+a|0;break}while(0);if((b|0)!=(-1|0)){g=b;f=a;z=193;break b}}while(0);k[274439]=k[274439]|4;z=190}else z=190;while(0);if((((z|0)==190?i>>>0<2147483647:0)?(x=Aa(i|0)|0,y=Aa(0)|0,x>>>0<y>>>0&((x|0)!=(-1|0)&(y|0)!=(-1|0))):0)?(f=y-x|0,f>>>0>(o+40|0)>>>0):0){g=x;z=193}if((z|0)==193){a=(k[274436]|0)+f|0;k[274436]=a;if(a>>>0>(k[274437]|0)>>>0)k[274437]=a;j=k[274334]|0;do if(j){e=1097760;while(1){a=k[e>>2]|0;b=e+4|0;c=k[b>>2]|0;if((g|0)==(a+c|0)){z=203;break}d=k[e+8>>2]|0;if(!d)break;else e=d}if(((z|0)==203?(k[e+12>>2]&8|0)==0:0)?j>>>0<g>>>0&j>>>0>=a>>>0:0){k[b>>2]=c+f;G=j+8|0;G=(G&7|0)==0?0:0-G&7;F=j+G|0;G=f-G+(k[274331]|0)|0;k[274334]=F;k[274331]=G;k[F+4>>2]=G|1;k[F+G+4>>2]=40;k[274335]=k[274450];break}a=k[274332]|0;if(g>>>0<a>>>0){k[274332]=g;h=g}else h=a;b=g+f|0;a=1097760;while(1){if((k[a>>2]|0)==(b|0)){z=211;break}a=k[a+8>>2]|0;if(!a){b=1097760;break}}if((z|0)==211)if(!(k[a+12>>2]&8)){k[a>>2]=g;m=a+4|0;k[m>>2]=(k[m>>2]|0)+f;m=g+8|0;m=g+((m&7|0)==0?0:0-m&7)|0;a=b+8|0;a=b+((a&7|0)==0?0:0-a&7)|0;l=m+o|0;i=a-m-o|0;k[m+4>>2]=o|3;do if((a|0)!=(j|0)){if((a|0)==(k[274333]|0)){G=(k[274330]|0)+i|0;k[274330]=G;k[274333]=l;k[l+4>>2]=G|1;k[l+G>>2]=G;break}b=k[a+4>>2]|0;if((b&3|0)==1){g=b&-8;e=b>>>3;e:do if(b>>>0>=256){f=k[a+24>>2]|0;d=k[a+12>>2]|0;do if((d|0)==(a|0)){d=a+16|0;c=d+4|0;b=k[c>>2]|0;if(!b){b=k[d>>2]|0;if(!b){E=0;break}else c=d}while(1){d=b+20|0;e=k[d>>2]|0;if(e|0){b=e;c=d;continue}d=b+16|0;e=k[d>>2]|0;if(!e)break;else{b=e;c=d}}if(c>>>0<h>>>0)Na();else{k[c>>2]=0;E=b;break}}else{e=k[a+8>>2]|0;if(e>>>0<h>>>0)Na();b=e+12|0;if((k[b>>2]|0)!=(a|0))Na();c=d+8|0;if((k[c>>2]|0)==(a|0)){k[b>>2]=d;k[c>>2]=e;E=d;break}else Na()}while(0);if(!f)break;b=k[a+28>>2]|0;c=1097616+(b<<2)|0;do if((a|0)!=(k[c>>2]|0)){if(f>>>0<(k[274332]|0)>>>0)Na();b=f+16|0;if((k[b>>2]|0)==(a|0))k[b>>2]=E;else k[f+20>>2]=E;if(!E)break e}else{k[c>>2]=E;if(E|0)break;k[274329]=k[274329]&~(1<<b);break e}while(0);d=k[274332]|0;if(E>>>0<d>>>0)Na();k[E+24>>2]=f;b=a+16|0;c=k[b>>2]|0;do if(c|0)if(c>>>0<d>>>0)Na();else{k[E+16>>2]=c;k[c+24>>2]=E;break}while(0);b=k[b+4>>2]|0;if(!b)break;if(b>>>0<(k[274332]|0)>>>0)Na();else{k[E+20>>2]=b;k[b+24>>2]=E;break}}else{c=k[a+8>>2]|0;d=k[a+12>>2]|0;b=1097352+(e<<1<<2)|0;do if((c|0)!=(b|0)){if(c>>>0<h>>>0)Na();if((k[c+12>>2]|0)==(a|0))break;Na()}while(0);if((d|0)==(c|0)){k[274328]=k[274328]&~(1<<e);break}do if((d|0)==(b|0))B=d+8|0;else{if(d>>>0<h>>>0)Na();b=d+8|0;if((k[b>>2]|0)==(a|0)){B=b;break}Na()}while(0);k[c+12>>2]=d;k[B>>2]=c}while(0);a=a+g|0;e=g+i|0}else e=i;a=a+4|0;k[a>>2]=k[a>>2]&-2;k[l+4>>2]=e|1;k[l+e>>2]=e;a=e>>>3;if(e>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;do if(!(b&a)){k[274328]=b|a;F=c+8|0;G=c}else{a=c+8|0;b=k[a>>2]|0;if(b>>>0>=(k[274332]|0)>>>0){F=a;G=b;break}Na()}while(0);k[F>>2]=l;k[G+12>>2]=l;k[l+8>>2]=G;k[l+12>>2]=c;break}a=e>>>8;do if(!a)c=0;else{if(e>>>0>16777215){c=31;break}F=(a+1048320|0)>>>16&8;G=a<<F;E=(G+520192|0)>>>16&4;G=G<<E;c=(G+245760|0)>>>16&2;c=14-(E|F|c)+(G<<c>>>15)|0;c=e>>>(c+7|0)&1|c<<1}while(0);d=1097616+(c<<2)|0;k[l+28>>2]=c;a=l+16|0;k[a+4>>2]=0;k[a>>2]=0;a=k[274329]|0;b=1<<c;if(!(a&b)){k[274329]=a|b;k[d>>2]=l;k[l+24>>2]=d;k[l+12>>2]=l;k[l+8>>2]=l;break}c=e<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(e|0)){z=281;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){z=278;break}else{c=c<<1;d=a}}if((z|0)==278)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=l;k[l+24>>2]=d;k[l+12>>2]=l;k[l+8>>2]=l;break}else if((z|0)==281){a=d+8|0;b=k[a>>2]|0;G=k[274332]|0;if(b>>>0>=G>>>0&d>>>0>=G>>>0){k[b+12>>2]=l;k[a>>2]=l;k[l+8>>2]=b;k[l+12>>2]=d;k[l+24>>2]=0;break}else Na()}}else{G=(k[274331]|0)+i|0;k[274331]=G;k[274334]=l;k[l+4>>2]=G|1}while(0);G=m+8|0;return G|0}else b=1097760;while(1){a=k[b>>2]|0;if(a>>>0<=j>>>0?(A=a+(k[b+4>>2]|0)|0,A>>>0>j>>>0):0)break;b=k[b+8>>2]|0}e=A+-47|0;b=e+8|0;b=e+((b&7|0)==0?0:0-b&7)|0;e=j+16|0;b=b>>>0<e>>>0?j:b;a=b+8|0;c=g+8|0;c=(c&7|0)==0?0:0-c&7;G=g+c|0;c=f+-40-c|0;k[274334]=G;k[274331]=c;k[G+4>>2]=c|1;k[G+c+4>>2]=40;k[274335]=k[274450];c=b+4|0;k[c>>2]=27;k[a>>2]=k[274440];k[a+4>>2]=k[274441];k[a+8>>2]=k[274442];k[a+12>>2]=k[274443];k[274440]=g;k[274441]=f;k[274443]=0;k[274442]=a;a=b+24|0;do{a=a+4|0;k[a>>2]=7}while((a+4|0)>>>0<A>>>0);if((b|0)!=(j|0)){f=b-j|0;k[c>>2]=k[c>>2]&-2;k[j+4>>2]=f|1;k[b>>2]=f;a=f>>>3;if(f>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;if(b&a){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{C=a;D=b}}else{k[274328]=b|a;C=c+8|0;D=c}k[C>>2]=j;k[D+12>>2]=j;k[j+8>>2]=D;k[j+12>>2]=c;break}a=f>>>8;if(a)if(f>>>0>16777215)c=31;else{F=(a+1048320|0)>>>16&8;G=a<<F;E=(G+520192|0)>>>16&4;G=G<<E;c=(G+245760|0)>>>16&2;c=14-(E|F|c)+(G<<c>>>15)|0;c=f>>>(c+7|0)&1|c<<1}else c=0;d=1097616+(c<<2)|0;k[j+28>>2]=c;k[j+20>>2]=0;k[e>>2]=0;a=k[274329]|0;b=1<<c;if(!(a&b)){k[274329]=a|b;k[d>>2]=j;k[j+24>>2]=d;k[j+12>>2]=j;k[j+8>>2]=j;break}c=f<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(f|0)){z=307;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){z=304;break}else{c=c<<1;d=a}}if((z|0)==304)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=j;k[j+24>>2]=d;k[j+12>>2]=j;k[j+8>>2]=j;break}else if((z|0)==307){a=d+8|0;b=k[a>>2]|0;G=k[274332]|0;if(b>>>0>=G>>>0&d>>>0>=G>>>0){k[b+12>>2]=j;k[a>>2]=j;k[j+8>>2]=b;k[j+12>>2]=d;k[j+24>>2]=0;break}else Na()}}}else{G=k[274332]|0;if((G|0)==0|g>>>0<G>>>0)k[274332]=g;k[274440]=g;k[274441]=f;k[274443]=0;k[274337]=k[274446];k[274336]=-1;a=0;do{G=1097352+(a<<1<<2)|0;k[G+12>>2]=G;k[G+8>>2]=G;a=a+1|0}while((a|0)!=32);G=g+8|0;G=(G&7|0)==0?0:0-G&7;F=g+G|0;G=f+-40-G|0;k[274334]=F;k[274331]=G;k[F+4>>2]=G|1;k[F+G+4>>2]=40;k[274335]=k[274450]}while(0);a=k[274331]|0;if(a>>>0>o>>>0){E=a-o|0;k[274331]=E;G=k[274334]|0;F=G+o|0;k[274334]=F;k[F+4>>2]=E|1;k[G+4>>2]=o|3;G=G+8|0;return G|0}}G=Qd()|0;k[G>>2]=12;G=0;return G|0}
+function we(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0;if(!a)return;c=a+-8|0;g=k[274332]|0;if(c>>>0<g>>>0)Na();a=k[a+-4>>2]|0;b=a&3;if((b|0)==1)Na();d=a&-8;m=c+d|0;do if(!(a&1)){a=k[c>>2]|0;if(!b)return;j=c+(0-a)|0;i=a+d|0;if(j>>>0<g>>>0)Na();if((j|0)==(k[274333]|0)){a=m+4|0;b=k[a>>2]|0;if((b&3|0)!=3){q=j;e=i;break}k[274330]=i;k[a>>2]=b&-2;k[j+4>>2]=i|1;k[j+i>>2]=i;return}d=a>>>3;if(a>>>0<256){b=k[j+8>>2]|0;c=k[j+12>>2]|0;a=1097352+(d<<1<<2)|0;if((b|0)!=(a|0)){if(b>>>0<g>>>0)Na();if((k[b+12>>2]|0)!=(j|0))Na()}if((c|0)==(b|0)){k[274328]=k[274328]&~(1<<d);q=j;e=i;break}if((c|0)!=(a|0)){if(c>>>0<g>>>0)Na();a=c+8|0;if((k[a>>2]|0)==(j|0))f=a;else Na()}else f=c+8|0;k[b+12>>2]=c;k[f>>2]=b;q=j;e=i;break}f=k[j+24>>2]|0;c=k[j+12>>2]|0;do if((c|0)==(j|0)){c=j+16|0;b=c+4|0;a=k[b>>2]|0;if(!a){a=k[c>>2]|0;if(!a){h=0;break}else b=c}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<g>>>0)Na();else{k[b>>2]=0;h=a;break}}else{d=k[j+8>>2]|0;if(d>>>0<g>>>0)Na();a=d+12|0;if((k[a>>2]|0)!=(j|0))Na();b=c+8|0;if((k[b>>2]|0)==(j|0)){k[a>>2]=c;k[b>>2]=d;h=c;break}else Na()}while(0);if(f){a=k[j+28>>2]|0;b=1097616+(a<<2)|0;if((j|0)==(k[b>>2]|0)){k[b>>2]=h;if(!h){k[274329]=k[274329]&~(1<<a);q=j;e=i;break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(j|0))k[a>>2]=h;else k[f+20>>2]=h;if(!h){q=j;e=i;break}}c=k[274332]|0;if(h>>>0<c>>>0)Na();k[h+24>>2]=f;a=j+16|0;b=k[a>>2]|0;do if(b|0)if(b>>>0<c>>>0)Na();else{k[h+16>>2]=b;k[b+24>>2]=h;break}while(0);a=k[a+4>>2]|0;if(a)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[h+20>>2]=a;k[a+24>>2]=h;q=j;e=i;break}else{q=j;e=i}}else{q=j;e=i}}else{q=c;e=d}while(0);if(q>>>0>=m>>>0)Na();a=m+4|0;b=k[a>>2]|0;if(!(b&1))Na();if(!(b&2)){if((m|0)==(k[274334]|0)){p=(k[274331]|0)+e|0;k[274331]=p;k[274334]=q;k[q+4>>2]=p|1;if((q|0)!=(k[274333]|0))return;k[274333]=0;k[274330]=0;return}if((m|0)==(k[274333]|0)){p=(k[274330]|0)+e|0;k[274330]=p;k[274333]=q;k[q+4>>2]=p|1;k[q+p>>2]=p;return}e=(b&-8)+e|0;d=b>>>3;do if(b>>>0>=256){f=k[m+24>>2]|0;a=k[m+12>>2]|0;do if((a|0)==(m|0)){c=m+16|0;b=c+4|0;a=k[b>>2]|0;if(!a){a=k[c>>2]|0;if(!a){n=0;break}else b=c}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=0;n=a;break}}else{b=k[m+8>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();c=b+12|0;if((k[c>>2]|0)!=(m|0))Na();d=a+8|0;if((k[d>>2]|0)==(m|0)){k[c>>2]=a;k[d>>2]=b;n=a;break}else Na()}while(0);if(f|0){a=k[m+28>>2]|0;b=1097616+(a<<2)|0;if((m|0)==(k[b>>2]|0)){k[b>>2]=n;if(!n){k[274329]=k[274329]&~(1<<a);break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(m|0))k[a>>2]=n;else k[f+20>>2]=n;if(!n)break}c=k[274332]|0;if(n>>>0<c>>>0)Na();k[n+24>>2]=f;a=m+16|0;b=k[a>>2]|0;do if(b|0)if(b>>>0<c>>>0)Na();else{k[n+16>>2]=b;k[b+24>>2]=n;break}while(0);a=k[a+4>>2]|0;if(a|0)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[n+20>>2]=a;k[a+24>>2]=n;break}}}else{b=k[m+8>>2]|0;c=k[m+12>>2]|0;a=1097352+(d<<1<<2)|0;if((b|0)!=(a|0)){if(b>>>0<(k[274332]|0)>>>0)Na();if((k[b+12>>2]|0)!=(m|0))Na()}if((c|0)==(b|0)){k[274328]=k[274328]&~(1<<d);break}if((c|0)!=(a|0)){if(c>>>0<(k[274332]|0)>>>0)Na();a=c+8|0;if((k[a>>2]|0)==(m|0))l=a;else Na()}else l=c+8|0;k[b+12>>2]=c;k[l>>2]=b}while(0);k[q+4>>2]=e|1;k[q+e>>2]=e;if((q|0)==(k[274333]|0)){k[274330]=e;return}}else{k[a>>2]=b&-2;k[q+4>>2]=e|1;k[q+e>>2]=e}a=e>>>3;if(e>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;if(b&a){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{o=a;p=b}}else{k[274328]=b|a;o=c+8|0;p=c}k[o>>2]=q;k[p+12>>2]=q;k[q+8>>2]=p;k[q+12>>2]=c;return}a=e>>>8;if(a)if(e>>>0>16777215)c=31;else{o=(a+1048320|0)>>>16&8;p=a<<o;n=(p+520192|0)>>>16&4;p=p<<n;c=(p+245760|0)>>>16&2;c=14-(n|o|c)+(p<<c>>>15)|0;c=e>>>(c+7|0)&1|c<<1}else c=0;d=1097616+(c<<2)|0;k[q+28>>2]=c;k[q+20>>2]=0;k[q+16>>2]=0;a=k[274329]|0;b=1<<c;do if(a&b){c=e<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(e|0)){a=130;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){a=127;break}else{c=c<<1;d=a}}if((a|0)==127)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=q;k[q+24>>2]=d;k[q+12>>2]=q;k[q+8>>2]=q;break}else if((a|0)==130){a=d+8|0;b=k[a>>2]|0;p=k[274332]|0;if(b>>>0>=p>>>0&d>>>0>=p>>>0){k[b+12>>2]=q;k[a>>2]=q;k[q+8>>2]=b;k[q+12>>2]=d;k[q+24>>2]=0;break}else Na()}}else{k[274329]=a|b;k[d>>2]=q;k[q+24>>2]=d;k[q+12>>2]=q;k[q+8>>2]=q}while(0);q=(k[274336]|0)+-1|0;k[274336]=q;if(!q)a=1097768;else return;while(1){a=k[a>>2]|0;if(!a)break;else a=a+8|0}k[274336]=-1;return}function xe(){wa(1096524,1096553,1164,1096636)}function ye(){wa(1096657,1096553,1175,1096686)}function ze(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;if(c>>>0>4294967279)xe();if(c>>>0<11){i[a>>0]=c<<1;a=a+1|0}else{e=c+16&-16;d=gf(e)|0;k[a+8>>2]=d;k[a>>2]=e|1;k[a+4>>2]=c;a=d}tf(a|0,b|0,c|0)|0;i[a+c>>0]=0;return}function Ae(a){a=a|0;if(i[a>>0]&1)Qe(k[a+8>>2]|0);return}function Be(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,l=0,m=0;if((-18-b|0)>>>0<c>>>0)xe();if(!(i[a>>0]&1))m=a+1|0;else m=k[a+8>>2]|0;if(b>>>0<2147483623){j=c+b|0;l=b<<1;j=j>>>0<l>>>0?l:j;j=j>>>0<11?11:j+16&-16}else j=-17;l=gf(j)|0;if(e|0)tf(l|0,m|0,e|0)|0;if(g|0)tf(l+e|0,h|0,g|0)|0;c=d-f|0;if((c|0)!=(e|0))tf(l+e+g|0,m+e+f|0,c-e|0)|0;if((b|0)!=10)Qe(m);k[a+8>>2]=l;k[a>>2]=j|1;b=c+g|0;k[a+4>>2]=b;i[l+b>>0]=0;return}function Ce(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;if(b|0){d=i[a>>0]|0;if(!(d&1))e=10;else{d=k[a>>2]|0;e=(d&-2)+-1|0;d=d&255}if(!(d&1))f=(d&255)>>>1;else f=k[a+4>>2]|0;if((e-f|0)>>>0<b>>>0){De(a,e,b-e+f|0,f,f);d=i[a>>0]|0}if(!(d&1))e=a+1|0;else e=k[a+8>>2]|0;qf(e+f|0,c|0,b|0)|0;d=f+b|0;if(!(i[a>>0]&1))i[a>>0]=d<<1;else k[a+4>>2]=d;i[e+d>>0]=0}return}function De(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;if((-17-b|0)>>>0<c>>>0)xe();if(!(i[a>>0]&1))g=a+1|0;else g=k[a+8>>2]|0;if(b>>>0<2147483623){c=c+b|0;f=b<<1;c=c>>>0<f>>>0?f:c;c=c>>>0<11?11:c+16&-16}else c=-17;f=gf(c)|0;if(e|0)tf(f|0,g|0,e|0)|0;if((d|0)!=(e|0))tf(f+e|0,g+e|0,d-e|0)|0;if((b|0)!=10)Qe(g);k[a+8>>2]=f;k[a>>2]=c|1;return}function Ee(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;d=i[a>>0]|0;if(!(d&1))f=10;else{d=k[a>>2]|0;f=(d&-2)+-1|0;d=d&255}e=(d&1)==0;if(e)d=(d&255)>>>1;else d=k[a+4>>2]|0;if((f-d|0)>>>0>=c>>>0){if(c|0){if(e)e=a+1|0;else e=k[a+8>>2]|0;tf(e+d|0,b|0,c|0)|0;d=d+c|0;if(!(i[a>>0]&1))i[a>>0]=d<<1;else k[a+4>>2]=d;i[e+d>>0]=0}}else Be(a,f,c-f+d|0,d,d,0,c,b);return}function Fe(a,b){a=a|0;b=b|0;Ee(a,b,Vd(b)|0);return}function Ge(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;c=i[a>>0]|0;d=(c&1)!=0;if(d){e=(k[a>>2]&-2)+-1|0;f=k[a+4>>2]|0}else{e=10;f=(c&255)>>>1}if((f|0)==(e|0)){De(a,e,1,e,e);if(!(i[a>>0]&1))d=7;else d=8}else if(d)d=8;else d=7;if((d|0)==7){i[a>>0]=(f<<1)+2;c=a+1|0}else if((d|0)==8){c=k[a+8>>2]|0;k[a+4>>2]=f+1}a=c+f|0;i[a>>0]=b;i[a+1>>0]=0;return}function He(a,b){a=a|0;b=b|0;var c=0,d=0;c=i[a>>0]|0;d=(c&1)==0;if(d)c=(c&255)>>>1;else c=k[a+4>>2]|0;if(c>>>0<b>>>0)ye();if(d){i[a>>0]=b<<1;c=a+1|0}else{c=k[a+8>>2]|0;k[a+4>>2]=b}i[c+b>>0]=0;return}function Ie(a,b){a=a|0;b=b|0;var c=0,d=0;c=i[a>>0]|0;if(!(c&1)){d=(c&255)>>>1;c=a+1|0}else{d=k[a+4>>2]|0;c=k[a+8>>2]|0}if(d>>>0>b>>>0){b=je(c+b|0,44,d-b|0)|0;c=(b|0)==0?-1:b-c|0}else c=-1;return c|0}function Je(){var a=0,b=0,c=0,d=0,e=0,f=0,g=0,h=0;e=r;r=r+48|0;g=e+32|0;c=e+24|0;h=e+16|0;f=e;e=e+36|0;a=Ke()|0;if(a|0?(d=k[a>>2]|0,d|0):0){a=d+48|0;b=k[a>>2]|0;a=k[a+4>>2]|0;if(!((b&-256|0)==1126902528&(a|0)==1129074247)){k[c>>2]=1096989;Ne(1097084,c)}if((b|0)==1126902529&(a|0)==1129074247)a=k[d+44>>2]|0;else a=d+80|0;k[e>>2]=a;d=k[d>>2]|0;a=k[d+4>>2]|0;if(Te(8,d,e)|0){h=k[e>>2]|0;h=Xa[k[(k[h>>2]|0)+8>>2]&3](h)|0;k[f>>2]=1096989;k[f+4>>2]=a;k[f+8>>2]=h;Ne(1096998,f)}else{k[h>>2]=1096989;k[h+4>>2]=a;Ne(1097043,h)}}Ne(1097122,g)}function Ke(){var a=0,b=0;a=r;r=r+16|0;if(!(Ia(1097808,2)|0)){b=Ga(k[274453]|0)|0;r=a;return b|0}else Ne(1096810,a);return 0}function Le(){var a=0;a=r;r=r+16|0;if(!(Ja(1097812,9)|0)){r=a;return}else Ne(1096760,a)}function Me(a){a=a|0;var b=0;b=r;r=r+16|0;we(a);if(!(La(k[274453]|0,0)|0)){r=b;return}else Ne(1096707,b)}function Ne(a,b){a=a|0;b=b|0;var c=0;c=r;r=r+16|0;k[c>>2]=b;de(941016,a,c)|0;re();Na()}function Oe(a){a=a|0;return}function Pe(a){a=a|0;Qe(a);return}function Qe(a){a=a|0;we(a);return}function Re(a){a=a|0;return}function Se(a){a=a|0;return}function Te(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;g=r;r=r+64|0;f=g;if((a|0)!=(b|0))if((b|0)!=0?(e=Ue(b,16)|0,(e|0)!=0):0){b=f;d=b+56|0;do{k[b>>2]=0;b=b+4|0}while((b|0)<(d|0));k[f>>2]=e;k[f+8>>2]=a;k[f+12>>2]=-1;k[f+48>>2]=1;_a[k[(k[e>>2]|0)+28>>2]&3](e,f,k[c>>2]|0,1);if((k[f+24>>2]|0)==1){k[c>>2]=k[f+16>>2];b=1}else b=0}else b=0;else b=1;r=g;return b|0}function Ue(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0;s=r;r=r+64|0;q=s;p=k[a>>2]|0;o=a+(k[p+-8>>2]|0)|0;p=k[p+-4>>2]|0;k[q>>2]=b;k[q+4>>2]=a;k[q+8>>2]=48;h=q+12|0;l=q+16|0;a=q+20|0;c=q+24|0;d=q+28|0;e=q+32|0;f=q+40|0;g=(p|0)==(b|0);m=h;n=m+40|0;do{k[m>>2]=0;m=m+4|0}while((m|0)<(n|0));j[h+40>>1]=0;i[h+42>>0]=0;a:do if(g){k[q+48>>2]=1;Za[k[(k[b>>2]|0)+20>>2]&3](b,q,o,o,1,0);a=(k[c>>2]|0)==1?o:0}else{Va[k[(k[p>>2]|0)+24>>2]&3](p,q,o,1,0);switch(k[q+36>>2]|0){case 0:{a=(k[f>>2]|0)==1&(k[d>>2]|0)==1&(k[e>>2]|0)==1?k[a>>2]|0:0;break a}case 1:break;default:{a=0;break a}}if((k[c>>2]|0)!=1?!((k[f>>2]|0)==0&(k[d>>2]|0)==1&(k[e>>2]|0)==1):0){a=0;break}a=k[l>>2]|0}while(0);r=s;return a|0}function Ve(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if((a|0)==(k[b+8>>2]|0))We(b,c,d,e);else{a=k[a+8>>2]|0;Za[k[(k[a>>2]|0)+20>>2]&3](a,b,c,d,e,f)}return}function We(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;i[a+53>>0]=1;do if((k[a+4>>2]|0)==(c|0)){i[a+52>>0]=1;c=a+16|0;e=k[c>>2]|0;if(!e){k[c>>2]=b;k[a+24>>2]=d;k[a+36>>2]=1;if(!((d|0)==1?(k[a+48>>2]|0)==1:0))break;i[a+54>>0]=1;break}if((e|0)!=(b|0)){d=a+36|0;k[d>>2]=(k[d>>2]|0)+1;i[a+54>>0]=1;break}e=a+24|0;c=k[e>>2]|0;if((c|0)==2){k[e>>2]=d;c=d}if((c|0)==1?(k[a+48>>2]|0)==1:0)i[a+54>>0]=1}while(0);return}function Xe(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0;do if((a|0)==(k[b+8>>2]|0)){if((k[b+4>>2]|0)==(c|0)?(f=b+28|0,(k[f>>2]|0)!=1):0)k[f>>2]=d}else{if((a|0)!=(k[b>>2]|0)){h=k[a+8>>2]|0;Va[k[(k[h>>2]|0)+24>>2]&3](h,b,c,d,e);break}if((k[b+16>>2]|0)!=(c|0)?(h=b+20|0,(k[h>>2]|0)!=(c|0)):0){k[b+32>>2]=d;g=b+44|0;if((k[g>>2]|0)==4)break;f=b+52|0;i[f>>0]=0;d=b+53|0;i[d>>0]=0;a=k[a+8>>2]|0;Za[k[(k[a>>2]|0)+20>>2]&3](a,b,c,c,1,e);if(i[d>>0]|0)if(!(i[f>>0]|0)){f=1;d=13}else d=17;else{f=0;d=13}do if((d|0)==13){k[h>>2]=c;c=b+40|0;k[c>>2]=(k[c>>2]|0)+1;if((k[b+36>>2]|0)==1?(k[b+24>>2]|0)==2:0){i[b+54>>0]=1;if(f){d=17;break}else{f=4;break}}if(f)d=17;else f=4}while(0);if((d|0)==17)f=3;k[g>>2]=f;break}if((d|0)==1)k[b+32>>2]=1}while(0);return}function Ye(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if((a|0)==(k[b+8>>2]|0))Ze(b,c,d);else{a=k[a+8>>2]|0;_a[k[(k[a>>2]|0)+28>>2]&3](a,b,c,d)}return}function Ze(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=a+16|0;e=k[d>>2]|0;do if(e){if((e|0)!=(b|0)){c=a+36|0;k[c>>2]=(k[c>>2]|0)+1;k[a+24>>2]=2;i[a+54>>0]=1;break}d=a+24|0;if((k[d>>2]|0)==2)k[d>>2]=c}else{k[d>>2]=b;k[a+24>>2]=c;k[a+36>>2]=1}while(0);return}function _e(a){a=a|0;Qe(a);return}function $e(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if((a|0)==(k[b+8>>2]|0))We(b,c,d,e);return}function af(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;do if((a|0)==(k[b+8>>2]|0)){if((k[b+4>>2]|0)==(c|0)?(g=b+28|0,(k[g>>2]|0)!=1):0)k[g>>2]=d}else if((a|0)==(k[b>>2]|0)){if((k[b+16>>2]|0)!=(c|0)?(f=b+20|0,(k[f>>2]|0)!=(c|0)):0){k[b+32>>2]=d;k[f>>2]=c;e=b+40|0;k[e>>2]=(k[e>>2]|0)+1;if((k[b+36>>2]|0)==1?(k[b+24>>2]|0)==2:0)i[b+54>>0]=1;k[b+44>>2]=4;break}if((d|0)==1)k[b+32>>2]=1}while(0);return}function bf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if((a|0)==(k[b+8>>2]|0))Ze(b,c,d);return}function cf(){var a=0,b=0,c=0,d=0;c=r;r=r+16|0;d=c+8|0;a=Ke()|0;if((a|0?(b=k[a>>2]|0,b|0):0)?(a=b+48|0,(k[a>>2]&-256|0)==1126902528?(k[a+4>>2]|0)==1129074247:0):0){Ya[k[b+12>>2]&3]();Ne(1097134,c)}c=k[235310]|0;k[235310]=c+0;Ya[c&3]();Ne(1097134,d)}function df(a){a=a|0;return}function ef(a){a=a|0;Qe(a);return}function ff(a){a=a|0;return 1097187}function gf(a){a=a|0;var b=0,c=0;b=(a|0)==0?1:a;while(1){c=ve(b)|0;if(c|0){a=6;break}a=hf()|0;if(!a){a=5;break}Ya[a&3]()}if((a|0)==5){c=xa(4)|0;k[c>>2]=941332;Ma(c|0,72,6)}else if((a|0)==6)return c|0;return 0}function hf(){var a=0;a=k[274454]|0;k[274454]=a+0;return a|0}function jf(a){a=a|0;return gf(a)|0}function kf(a){a=a|0;Qe(a);return}function lf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=r;r=r+16|0;d=e;k[d>>2]=k[c>>2];a=Ua[k[(k[a>>2]|0)+16>>2]&7](a,b,d)|0;if(a)k[c>>2]=k[d>>2];r=e;return a&1|0}function mf(a){a=a|0;if(!a)a=0;else a=(Ue(a,104)|0)!=0;return a&1|0}function nf(){}function of(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;c=a+c>>>0;return (L=b+d+(c>>>0<a>>>0|0)>>>0,c|0)|0}function pf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;d=b-d-(c>>>0>a>>>0|0)>>>0;return (L=d,a-c>>>0|0)|0}function qf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=a+c|0;if((c|0)>=20){b=b&255;f=a&3;g=b|b<<8|b<<16|b<<24;e=d&~3;if(f){f=a+4-f|0;while((a|0)<(f|0)){i[a>>0]=b;a=a+1|0}}while((a|0)<(e|0)){k[a>>2]=g;a=a+4|0}}while((a|0)<(d|0)){i[a>>0]=b;a=a+1|0}return a-c|0}function rf(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b>>>c;return a>>>c|(b&(1<<c)-1)<<32-c}L=0;return b>>>c-32|0}function sf(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b<<c|(a&(1<<c)-1<<32-c)>>>32-c;return a<<c}L=a<<c-32;return 0}function tf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if((c|0)>=4096)return Ca(a|0,b|0,c|0)|0;d=a|0;if((a&3)==(b&3)){while(a&3){if(!c)return d|0;i[a>>0]=i[b>>0]|0;a=a+1|0;b=b+1|0;c=c-1|0}while((c|0)>=4){k[a>>2]=k[b>>2];a=a+4|0;b=b+4|0;c=c-4|0}}while((c|0)>0){i[a>>0]=i[b>>0]|0;a=a+1|0;b=b+1|0;c=c-1|0}return d|0}function uf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if((b|0)<(a|0)&(a|0)<(b+c|0)){d=a;b=b+c|0;a=a+c|0;while((c|0)>0){a=a-1|0;b=b-1|0;c=c-1|0;i[a>>0]=i[b>>0]|0}a=d}else tf(a,b,c)|0;return a|0}function vf(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b>>c;return a>>>c|(b&(1<<c)-1)<<32-c}L=(b|0)<0?-1:0;return b>>c-32|0}function wf(a){a=a|0;var b=0;b=i[v+(a&255)>>0]|0;if((b|0)<8)return b|0;b=i[v+(a>>8&255)>>0]|0;if((b|0)<8)return b+8|0;b=i[v+(a>>16&255)>>0]|0;if((b|0)<8)return b+16|0;return (i[v+(a>>>24)>>0]|0)+24|0}function xf(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;f=a&65535;e=b&65535;c=ha(e,f)|0;d=a>>>16;a=(c>>>16)+(ha(e,d)|0)|0;e=b>>>16;b=ha(e,f)|0;return (L=(a>>>16)+(ha(e,d)|0)+(((a&65535)+b|0)>>>16)|0,a+b<<16|c&65535|0)|0}function yf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;j=b>>31|((b|0)<0?-1:0)<<1;i=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;f=d>>31|((d|0)<0?-1:0)<<1;e=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;h=pf(j^a|0,i^b|0,j|0,i|0)|0;g=L;a=f^j;b=e^i;return pf((Df(h,g,pf(f^c|0,e^d|0,f|0,e|0)|0,L,0)|0)^a|0,L^b|0,a|0,b|0)|0}function zf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=r;r=r+16|0;h=e|0;g=b>>31|((b|0)<0?-1:0)<<1;f=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;j=d>>31|((d|0)<0?-1:0)<<1;i=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;a=pf(g^a|0,f^b|0,g|0,f|0)|0;b=L;Df(a,b,pf(j^c|0,i^d|0,j|0,i|0)|0,L,h)|0;d=pf(k[h>>2]^g|0,k[h+4>>2]^f|0,g|0,f|0)|0;c=L;r=e;return (L=c,d)|0}function Af(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;e=a;f=c;c=xf(e,f)|0;a=L;return (L=(ha(b,f)|0)+(ha(d,e)|0)+a|a&0,c|0|0)|0}function Bf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return Df(a,b,c,d,0)|0}function Cf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=r;r=r+16|0;e=f|0;Df(a,b,c,d,e)|0;r=f;return (L=k[e+4>>2]|0,k[e>>2]|0)|0}function Df(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0;l=a;i=b;j=i;g=c;n=d;h=n;if(!j){f=(e|0)!=0;if(!h){if(f){k[e>>2]=(l>>>0)%(g>>>0);k[e+4>>2]=0}n=0;e=(l>>>0)/(g>>>0)>>>0;return (L=n,e)|0}else{if(!f){n=0;e=0;return (L=n,e)|0}k[e>>2]=a|0;k[e+4>>2]=b&0;n=0;e=0;return (L=n,e)|0}}f=(h|0)==0;do if(g){if(!f){f=(ja(h|0)|0)-(ja(j|0)|0)|0;if(f>>>0<=31){m=f+1|0;h=31-f|0;b=f-31>>31;g=m;a=l>>>(m>>>0)&b|j<<h;b=j>>>(m>>>0)&b;f=0;h=l<<h;break}if(!e){n=0;e=0;return (L=n,e)|0}k[e>>2]=a|0;k[e+4>>2]=i|b&0;n=0;e=0;return (L=n,e)|0}f=g-1|0;if(f&g|0){h=(ja(g|0)|0)+33-(ja(j|0)|0)|0;p=64-h|0;m=32-h|0;i=m>>31;o=h-32|0;b=o>>31;g=h;a=m-1>>31&j>>>(o>>>0)|(j<<m|l>>>(h>>>0))&b;b=b&j>>>(h>>>0);f=l<<p&i;h=(j<<p|l>>>(o>>>0))&i|l<<m&h-33>>31;break}if(e|0){k[e>>2]=f&l;k[e+4>>2]=0}if((g|0)==1){o=i|b&0;p=a|0|0;return (L=o,p)|0}else{p=wf(g|0)|0;o=j>>>(p>>>0)|0;p=j<<32-p|l>>>(p>>>0)|0;return (L=o,p)|0}}else{if(f){if(e|0){k[e>>2]=(j>>>0)%(g>>>0);k[e+4>>2]=0}o=0;p=(j>>>0)/(g>>>0)>>>0;return (L=o,p)|0}if(!l){if(e|0){k[e>>2]=0;k[e+4>>2]=(j>>>0)%(h>>>0)}o=0;p=(j>>>0)/(h>>>0)>>>0;return (L=o,p)|0}f=h-1|0;if(!(f&h)){if(e|0){k[e>>2]=a|0;k[e+4>>2]=f&j|b&0}o=0;p=j>>>((wf(h|0)|0)>>>0);return (L=o,p)|0}f=(ja(h|0)|0)-(ja(j|0)|0)|0;if(f>>>0<=30){b=f+1|0;h=31-f|0;g=b;a=j<<h|l>>>(b>>>0);b=j>>>(b>>>0);f=0;h=l<<h;break}if(!e){o=0;p=0;return (L=o,p)|0}k[e>>2]=a|0;k[e+4>>2]=i|b&0;o=0;p=0;return (L=o,p)|0}while(0);if(!g){j=h;i=0;h=0}else{m=c|0|0;l=n|d&0;j=of(m|0,l|0,-1,-1)|0;c=L;i=h;h=0;do{d=i;i=f>>>31|i<<1;f=h|f<<1;d=a<<1|d>>>31|0;n=a>>>31|b<<1|0;pf(j|0,c|0,d|0,n|0)|0;p=L;o=p>>31|((p|0)<0?-1:0)<<1;h=o&1;a=pf(d|0,n|0,o&m|0,(((p|0)<0?-1:0)>>31|((p|0)<0?-1:0)<<1)&l|0)|0;b=L;g=g-1|0}while((g|0)!=0);j=i;i=0}g=0;if(e|0){k[e>>2]=a;k[e+4>>2]=b}o=(f|0)>>>31|(j|g)<<1|(g<<1|f>>>31)&0|i;p=(f<<1|0>>>31)&-2|h;return (L=o,p)|0}function Ef(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return Ua[a&7](b|0,c|0,d|0)|0}function Ff(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;Va[a&3](b|0,c|0,d|0,e|0,f|0)}function Gf(a,b){a=a|0;b=b|0;Wa[a&15](b|0)}function Hf(a,b){a=a|0;b=b|0;return Xa[a&3](b|0)|0}function If(a){a=a|0;Ya[a&3]()}function Jf(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;Za[a&3](b|0,c|0,d|0,e|0,f|0,g|0)}function Kf(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;_a[a&3](b|0,c|0,d|0,e|0)}function Lf(a,b,c){a=a|0;b=b|0;c=c|0;ka(0);return 0}function Mf(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;ka(1)}function Nf(a){a=a|0;ka(2)}function Of(a){a=a|0;ka(3);return 0}function Pf(){ka(4)}function Qf(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;ka(5)}function Rf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;ka(6)}
+
+// EMSCRIPTEN_END_FUNCS
+var Ua=[Lf,Rd,Td,ce,Te,Lf,Lf,Lf];var Va=[Mf,af,Xe,Mf];var Wa=[Nf,Oe,_e,Re,Se,Pe,df,ef,Sd,Me,Nf,Nf,Nf,Nf,Nf,Nf];var Xa=[Of,Od,ff,Of];var Ya=[Pf,Je,Le,Pf];var Za=[Qf,$e,Ve,Qf];var _a=[Rf,bf,Ye,Rf];return{_emscripten_bind_LanguageInfo_getLanguageCode_0:Jd,_bitshift64Lshr:rf,_bitshift64Shl:sf,_malloc:ve,___cxa_is_pointer_type:mf,_emscripten_bind_LanguageGuess_getPercent_0:Ad,_emscripten_bind_VoidPtr___destroy___0:Nd,_memset:qf,_memcpy:tf,_emscripten_bind_LanguageInfo_getIsReliable_0:Id,_i64Subtract:pf,_emscripten_bind_LanguageInfo___destroy___0:Bd,_i64Add:of,_emscripten_bind_LanguageInfo_get_languages_1:Kd,_emscripten_bind_Language_getLanguageCode_0:Md,_emscripten_bind_LanguageGuess___destroy___0:yd,_emscripten_bind_Language___destroy___0:Ld,___cxa_can_catch:lf,_free:we,_emscripten_bind_LanguageInfo_detectLanguage_5:Gd,_memmove:uf,_emscripten_bind_LanguageInfo_detectLanguage_2:Dd,_emscripten_bind_LanguageGuess_getLanguageCode_0:zd,runPostSets:nf,_emscripten_replace_memory:Ta,stackAlloc:$a,stackSave:ab,stackRestore:bb,establishStackSpace:cb,setThrew:db,setTempRet0:gb,getTempRet0:hb,dynCall_iiii:Ef,dynCall_viiiii:Ff,dynCall_vi:Gf,dynCall_ii:Hf,dynCall_v:If,dynCall_viiiiii:Jf,dynCall_viiii:Kf}})
+
+
+// EMSCRIPTEN_END_ASM
+(c.L,c.M,buffer),zb=c._emscripten_bind_LanguageInfo_getLanguageCode_0=M._emscripten_bind_LanguageInfo_getLanguageCode_0,pb=c._bitshift64Lshr=M._bitshift64Lshr,qb=c._bitshift64Shl=M._bitshift64Shl,Ab=c._emscripten_bind_LanguageGuess_getLanguageCode_0=M._emscripten_bind_LanguageGuess_getLanguageCode_0;c.___cxa_is_pointer_type=M.___cxa_is_pointer_type;
+var Bb=c._emscripten_bind_LanguageGuess_getPercent_0=M._emscripten_bind_LanguageGuess_getPercent_0,Cb=c._emscripten_bind_VoidPtr___destroy___0=M._emscripten_bind_VoidPtr___destroy___0,nb=c._memset=M._memset,sb=c._memcpy=M._memcpy,Db=c._emscripten_bind_LanguageInfo_getIsReliable_0=M._emscripten_bind_LanguageInfo_getIsReliable_0,gb=c._i64Subtract=M._i64Subtract,Eb=c._emscripten_bind_LanguageInfo___destroy___0=M._emscripten_bind_LanguageInfo___destroy___0,fb=c._i64Add=M._i64Add,Fb=c._emscripten_bind_LanguageInfo_get_languages_1=
+M._emscripten_bind_LanguageInfo_get_languages_1,Gb=c._emscripten_bind_Language_getLanguageCode_0=M._emscripten_bind_Language_getLanguageCode_0,Hb=c._emscripten_bind_LanguageGuess___destroy___0=M._emscripten_bind_LanguageGuess___destroy___0,Ib=c._emscripten_bind_Language___destroy___0=M._emscripten_bind_Language___destroy___0;c.___cxa_can_catch=M.___cxa_can_catch;var Ga=c._free=M._free;c.runPostSets=M.runPostSets;
+var Jb=c._emscripten_bind_LanguageInfo_detectLanguage_5=M._emscripten_bind_LanguageInfo_detectLanguage_5,wb=c._memmove=M._memmove,Kb=c._emscripten_bind_LanguageInfo_detectLanguage_2=M._emscripten_bind_LanguageInfo_detectLanguage_2,D=c._malloc=M._malloc,Oa=c._emscripten_replace_memory=M._emscripten_replace_memory;c.dynCall_iiii=M.dynCall_iiii;c.dynCall_viiiii=M.dynCall_viiiii;c.dynCall_vi=M.dynCall_vi;c.dynCall_ii=M.dynCall_ii;c.dynCall_v=M.dynCall_v;c.dynCall_viiiiii=M.dynCall_viiiiii;
+c.dynCall_viiii=M.dynCall_viiii;t.n=M.stackAlloc;t.w=M.stackSave;t.o=M.stackRestore;t.ba=M.establishStackSpace;t.V=M.setTempRet0;t.R=M.getTempRet0;
+if(K)if("function"===typeof c.locateFile?K=c.locateFile(K):c.memoryInitializerPrefixURL&&(K=c.memoryInitializerPrefixURL+K),m||ca){var Lb=c.readBinary(K);E.set(Lb,t.C)}else{var Nb=function(){c.readAsync(K,Mb,function(){throw"could not load memory initializer "+K;})};bb();var Mb=function(a){a.byteLength&&(a=new Uint8Array(a));E.set(a,t.C);c.memoryInitializerRequest&&delete c.memoryInitializerRequest.response;cb()};if(c.memoryInitializerRequest){var Ob=function(){var a=c.memoryInitializerRequest;200!==
+a.status&&0!==a.status?(console.warn("a problem seems to have happened with Module.memoryInitializerRequest, status: "+a.status+", retrying "+K),Nb()):Mb(a.response)};c.memoryInitializerRequest.response?setTimeout(Ob,0):c.memoryInitializerRequest.addEventListener("load",Ob)}else Nb()}function n(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}n.prototype=Error();n.prototype.constructor=n;var Pb=null,ab=function Qb(){c.calledRun||Rb();c.calledRun||(ab=Qb)};
+c.callMain=c.Z=function(a){function b(){for(var a=0;3>a;a++)e.push(0)}a=a||[];za||(za=!0,H(Ta));var d=a.length+1,e=[xa(Za(c.thisProgram),"i8",0)];b();for(var f=0;f<d-1;f+=1)e.push(xa(Za(a[f]),"i8",0)),b();e.push(0);e=xa(e,"i32",0);try{var l=c._main(d,e,0);Sb(l,!0)}catch(h){if(!(h instanceof n))if("SimulateInfiniteLoop"==h)c.noExitRuntime=!0;else throw h&&"object"===typeof h&&h.stack&&c.u("exception thrown: "+[h,h.stack]),h;}finally{}};
+function Rb(a){function b(){if(!c.calledRun&&(c.calledRun=!0,!ia)){za||(za=!0,H(Ta));H(Ua);if(c.onRuntimeInitialized)c.onRuntimeInitialized();c._main&&Tb&&c.callMain(a);if(c.postRun)for("function"==typeof c.postRun&&(c.postRun=[c.postRun]);c.postRun.length;)Ya(c.postRun.shift());H(Va)}}a=a||c.arguments;null===Pb&&(Pb=Date.now());if(!(0<J)){if(c.preRun)for("function"==typeof c.preRun&&(c.preRun=[c.preRun]);c.preRun.length;)Wa(c.preRun.shift());H(Sa);0<J||c.calledRun||(c.setStatus?(c.setStatus("Running..."),
+setTimeout(function(){setTimeout(function(){c.setStatus("")},1);b()},1)):b())}}c.run=c.run=Rb;function Sb(a,b){if(!b||!c.noExitRuntime){if(!c.noExitRuntime&&(ia=!0,p=void 0,H(I),c.onExit))c.onExit(a);m?process.exit(a):ca&&"function"===typeof quit&&quit(a);throw new n(a);}}c.exit=c.exit=Sb;var Ub=[];
+function y(a){void 0!==a?(c.print(a),c.u(a),a=JSON.stringify(a)):a="";ia=!0;var b="abort("+a+") at "+Ea()+"\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.";Ub&&Ub.forEach(function(d){b=d(b,a)});throw b;}c.abort=c.abort=y;if(c.preInit)for("function"==typeof c.preInit&&(c.preInit=[c.preInit]);0<c.preInit.length;)c.preInit.pop()();var Tb=!1;c.noInitialRun&&(Tb=!1);c.noExitRuntime=!0;Rb();function R(){}R.prototype=Object.create(R.prototype);
+R.prototype.constructor=R;R.prototype.c=R;R.e={};c.WrapperObject=R;function Vb(a){return(a||R).e}c.getCache=Vb;function S(a,b){var d=Vb(b),e=d[a];if(e)return e;e=Object.create((b||R).prototype);e.a=a;return d[a]=e}c.wrapPointer=S;c.castObject=function(a,b){return S(a.a,b)};c.NULL=S(0);c.destroy=function(a){if(!a.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";a.__destroy__();delete Vb(a.c)[a.a]};c.compare=function(a,b){return a.a===b.a};c.getPointer=function(a){return a.a};
+c.getClass=function(a){return a.c};
+var T={buffer:0,size:0,j:0,p:[],i:0,t:function(){if(this.i){for(var a=0;a<this.p.length;a++)c._free(this.p[a]);this.p.length=0;c._free(this.buffer);this.buffer=0;this.size+=this.i;this.i=0}this.buffer||(this.size+=128,this.buffer=c._malloc(this.size),assert(this.buffer));this.j=0},f:function(a,b){assert(this.buffer);var d=b.BYTES_PER_ELEMENT,e=a.length*d,e=e+7&-8,f;this.j+e>=this.size?(assert(0<e),this.i+=e,f=c._malloc(e),this.p.push(f)):(f=this.buffer+this.j,this.j+=e);e=f;switch(d){case 2:e>>=1;
+break;case 4:e>>=2;break;case 8:e>>=3}for(d=0;d<a.length;d++)b[e+d]=a[d];return f}};function Wb(a){return"string"===typeof a?T.f(Za(a),B):a}function U(){throw"cannot construct a Language, no constructor in IDL";}U.prototype=Object.create(R.prototype);U.prototype.constructor=U;U.prototype.c=U;U.e={};c.Language=U;U.prototype.getLanguageCode=U.prototype.m=function(){return z(Gb(this.a))};U.prototype.__destroy__=function(){Ib(this.a)};
+function V(){throw"cannot construct a LanguageGuess, no constructor in IDL";}V.prototype=Object.create(U.prototype);V.prototype.constructor=V;V.prototype.c=V;V.e={};c.LanguageGuess=V;V.prototype.getPercent=V.prototype.P=function(){return Bb(this.a)};V.prototype.getLanguageCode=V.prototype.m=function(){return z(Ab(this.a))};V.prototype.__destroy__=function(){Hb(this.a)};function W(){throw"cannot construct a LanguageInfo, no constructor in IDL";}W.prototype=Object.create(U.prototype);
+W.prototype.constructor=W;W.prototype.c=W;W.e={};c.LanguageInfo=W;
+W.prototype.detectLanguage=W.prototype.b=function(a,b,d,e,f){var l=this.a;T.t();a&&"object"===typeof a?a=a.a:a=Wb(a);b&&"object"===typeof b&&(b=b.a);d&&"object"===typeof d?d=d.a:d=Wb(d);e&&"object"===typeof e&&(e=e.a);f&&"object"===typeof f?f=f.a:f=Wb(f);return void 0===d?S(Kb(l,a,b),W):void 0===e?S(_emscripten_bind_LanguageInfo_detectLanguage_3(l,a,b,d),W):void 0===f?S(_emscripten_bind_LanguageInfo_detectLanguage_4(l,a,b,d,e),W):S(Jb(l,a,b,d,e,f),W)};W.prototype.getIsReliable=W.prototype.N=function(){return!!Db(this.a)};
+W.prototype.getLanguageCode=W.prototype.m=function(){return z(zb(this.a))};W.prototype.get_languages=W.prototype.S=function(a){var b=this.a;a&&"object"===typeof a&&(a=a.a);return S(Fb(b,a),V)};W.prototype.__destroy__=function(){Eb(this.a)};function Z(){throw"cannot construct a VoidPtr, no constructor in IDL";}Z.prototype=Object.create(R.prototype);Z.prototype.constructor=Z;Z.prototype.c=Z;Z.e={};c.VoidPtr=Z;Z.prototype.__destroy__=function(){Cb(this.a)};(function(){function a(){}c.calledRun||Xa(a)})();
+W.g=W.prototype.b;T.f=T.f.bind(T);T.t=T.t.bind(T);
+for(var Xb={ISO_8859_1:0,ISO_8859_2:1,ISO_8859_3:2,ISO_8859_4:3,ISO_8859_5:4,ISO_8859_6:5,ISO_8859_7:6,ISO_8859_8:7,ISO_8859_9:8,ISO_8859_10:9,JAPANESE_EUC_JP:10,EUC_JP:10,JAPANESE_SHIFT_JIS:11,SHIFT_JIS:11,JAPANESE_JIS:12,JIS:12,CHINESE_BIG5:13,BIG5:13,CHINESE_GB:14,CHINESE_EUC_CN:15,EUC_CN:15,KOREAN_EUC_KR:16,EUC_KR:16,UNICODE_UNUSED:17,CHINESE_EUC_DEC:18,EUC_DEC:18,CHINESE_CNS:19,CNS:19,CHINESE_BIG5_CP950:20,BIG5_CP950:20,JAPANESE_CP932:21,CP932:21,UTF8:22,UNKNOWN_ENCODING:23,ASCII_7BIT:24,RUSSIAN_KOI8_R:25,
+KOI8_R:25,RUSSIAN_CP1251:26,CP1251:26,MSFT_CP1252:27,CP1252:27,RUSSIAN_KOI8_RU:28,KOI8_RU:28,MSFT_CP1250:29,CP1250:29,ISO_8859_15:30,MSFT_CP1254:31,CP1254:31,MSFT_CP1257:32,CP1257:32,ISO_8859_11:33,MSFT_CP874:34,CP874:34,MSFT_CP1256:35,CP1256:35,MSFT_CP1255:36,CP1255:36,ISO_8859_8_I:37,HEBREW_VISUAL:38,CZECH_CP852:39,CP852:39,CZECH_CSN_369103:40,CSN_369103:40,MSFT_CP1253:41,CP1253:41,RUSSIAN_CP866:42,CP866:42,ISO_8859_13:43,ISO_2022_KR:44,GBK:45,GB18030:46,BIG5_HKSCS:47,ISO_2022_CN:48,TSCII:49,TAMIL_MONO:50,
+TAMIL_BI:51,JAGRAN:52,MACINTOSH_ROMAN:53,UTF7:54,BHASKAR:55,HTCHANAKYA:56,UTF16BE:57,UTF16LE:58,UTF32BE:59,UTF32LE:60,BINARYENC:61,HZ_GB_2312:62,UTF8UTF8:63,TAM_ELANGO:64,TAM_LTTMBARANI:65,TAM_SHREE:66,TAM_TBOOMIS:67,TAM_TMNEWS:68,TAM_WEBTAMIL:69,KDDI_SHIFT_JIS:70,DOCOMO_SHIFT_JIS:71,SOFTBANK_SHIFT_JIS:72,KDDI_ISO_2022_JP:73,ISO_2022_JP:73,SOFTBANK_ISO_2022_JP:74},Yb=function(a){if(a.J)return a.J();if(!(a instanceof Array)&&"string"!=typeof a)throw Error();var b=0;return{next:function(){return b==
+a.length?{done:!0}:{done:!1,value:a[b++]}}}}(Object.keys(Xb)),Zb=Yb.next();!Zb.done;Zb=Yb.next()){var $b=Zb.value;$b.includes("_")&&(Xb[$b.replace(/_/g,"")]=Xb[$b])}
+Xa(function(){onmessage=function(a){a=a.data;var b=void 0;if(void 0==a.tld&&void 0==a.encoding&&void 0==a.language)b=W.g(a.text,!a.isHTML);else var d=String(a.encoding).toUpperCase().replace(/[_-]/g,""),e=void 0,e=Xb.hasOwnProperty(d)?Xb[d]:Xb.UNKNOWN_ENCODING,b=W.g(a.text,!a.isHTML,a.tld||null,e,a.language||null);postMessage({language:b.m(),confident:b.N(),languages:Array(3).fill(0).map(function(a,d){var e=b.S(d);return{languageCode:e.m(),percent:e.P()}}).filter(function(a){return"un"!=a.languageCode||
+0<a.percent})});c.destroy(b)};postMessage("ready")});
+
diff --git a/browser/components/translation/cld2/cld-worker.js.mem b/browser/components/translation/cld2/cld-worker.js.mem
new file mode 100644
index 000000000..c07ed5d53
--- /dev/null
+++ b/browser/components/translation/cld2/cld-worker.js.mem
Binary files differ
diff --git a/browser/components/translation/cld2/cld.idl b/browser/components/translation/cld2/cld.idl
new file mode 100644
index 000000000..e426dd303
--- /dev/null
+++ b/browser/components/translation/cld2/cld.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/. */
+
+// Note: This is a variant of WebIDL, but its semantics differ from our
+// internal WebIDL implementation. Some particular differences that are
+// relevent here include:
+//
+// - Attribute declarations refer directly to member variables of the
+// underlying class, and are not forwarded to explicit getter methods.
+//
+// - Attribute declarations also do not create getters on the JavaScript
+// wrapper object, but instead generate "get_foo()" and "set_foo()"
+// methods, which must be called in order to access the value. In the case
+// of array attributes, the callers must also pass the index they wish to
+// access.
+//
+// - Method overloading is fairly crude. Only explicitly declared variants
+// are supported, and selection is based entirely on index of the first
+// parameter whose value is undefined.
+//
+// - DOMString attributes are nullable by default. Null values are not
+// converted to empty strings, and non-null values are converted to
+// null-terminated, UTF-8 byte arrays.
+
+interface Language {
+ [Const] DOMString getLanguageCode();
+};
+
+interface LanguageGuess {
+ byte getPercent();
+};
+
+interface LanguageInfo {
+ static LanguageInfo detectLanguage(DOMString buffer, boolean isPlainText);
+
+ static LanguageInfo detectLanguage(DOMString buffer, boolean isPlainText,
+ DOMString? tldHint, long encodingHint,
+ DOMString? languageHint);
+
+ boolean getIsReliable();
+
+ [BoundsChecked,Const] readonly attribute LanguageGuess[] languages;
+};
+
+LanguageGuess implements Language;
+LanguageInfo implements Language;
diff --git a/browser/components/translation/cld2/cldapp.cc b/browser/components/translation/cld2/cldapp.cc
new file mode 100644
index 000000000..4750cc54b
--- /dev/null
+++ b/browser/components/translation/cld2/cldapp.cc
@@ -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/. */
+
+#include "public/compact_lang_det.h"
+
+#define MAX_RESULTS 3
+
+class Language {
+public:
+ Language(CLD2::Language lang) : mLang(lang) {}
+
+ const char* getLanguageCode() const
+ {
+ return CLD2::LanguageCode(mLang);
+ }
+
+private:
+ const CLD2::Language mLang;
+};
+
+class LanguageGuess : public Language {
+public:
+ LanguageGuess(CLD2::Language lang, char percent) :
+ Language(lang), mPercent(percent) {}
+
+ char getPercent() const
+ {
+ return mPercent;
+ }
+
+private:
+ const char mPercent;
+};
+
+
+class LanguageInfo : public Language {
+public:
+ static LanguageInfo* detectLanguage(const char* buffer, bool isPlainText)
+ {
+ CLD2::Language languages[MAX_RESULTS] = {};
+ int percentages[MAX_RESULTS] = {};
+ bool isReliable = false;
+
+ // This is ignored.
+ int textBytes;
+
+ CLD2::Language bestGuess = DetectLanguageSummary(
+ buffer, strlen(buffer), isPlainText,
+ languages, percentages, &textBytes,
+ &isReliable);
+
+ return new LanguageInfo(isReliable, bestGuess, languages, percentages);
+ }
+
+ static LanguageInfo* detectLanguage(const char* buffer, bool isPlainText,
+ const char* tldHint, int encodingHint,
+ const char* languageHint)
+ {
+ CLD2::CLDHints hints = {languageHint, tldHint, encodingHint, CLD2::UNKNOWN_LANGUAGE};
+
+ CLD2::Language languages[MAX_RESULTS] = {};
+ int percentages[MAX_RESULTS] = {};
+ bool isReliable = false;
+
+ // These are ignored.
+ double scores[MAX_RESULTS];
+ int textBytes;
+
+ CLD2::Language bestGuess = ExtDetectLanguageSummary(
+ buffer, strlen(buffer), isPlainText,
+ &hints, 0,
+ languages, percentages, scores,
+ nullptr, &textBytes, &isReliable);
+
+ return new LanguageInfo(isReliable, bestGuess, languages, percentages);
+ }
+
+ ~LanguageInfo()
+ {
+ for (int i = 0; i < MAX_RESULTS; i++) {
+ delete languages[i];
+ }
+ }
+
+ bool getIsReliable() const
+ {
+ return mIsReliable;
+ }
+
+ const LanguageGuess* languages[MAX_RESULTS];
+
+private:
+ LanguageInfo(bool isReliable, CLD2::Language bestGuess,
+ CLD2::Language languageIDs[MAX_RESULTS],
+ int percentages[MAX_RESULTS]) :
+ Language(bestGuess), mIsReliable(isReliable)
+ {
+ for (int i = 0; i < MAX_RESULTS; i++) {
+ languages[i] = new LanguageGuess(languageIDs[i], percentages[i]);
+ }
+ }
+
+ const bool mIsReliable;
+};
+
+#include "cld.cpp"
diff --git a/browser/components/translation/cld2/internal/LICENSE b/browser/components/translation/cld2/internal/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/browser/components/translation/cld2/internal/LICENSE
@@ -0,0 +1,202 @@
+
+ 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
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/browser/components/translation/cld2/internal/cld2_dynamic_data.h b/browser/components/translation/cld2/internal/cld2_dynamic_data.h
new file mode 100644
index 000000000..693d35b38
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld2_dynamic_data.h
@@ -0,0 +1,216 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef CLD2_INTERNAL_CLD2_DYNAMIC_DATA_H_
+#define CLD2_INTERNAL_CLD2_DYNAMIC_DATA_H_
+
+#include "integral_types.h"
+#include "cld2tablesummary.h"
+#include "utf8statetable.h"
+#include "scoreonescriptspan.h"
+
+/*
+ There are two primary parts to a CLD2 dynamic data file:
+ 1. A header, wherein trivial data, block lengths and block offsets are kept
+ 2. A data block, wherein the large binary blocks are kept
+
+ By reading the header, an application can determine the offsets and lengths of
+ all the data blocks for all tables. Offsets in the header are expressed
+ relative to the first byte of the file, inclusive of the header itself; thus,
+ any offset whose value is less than the length of the header is invalid.
+
+ Any offset whose value is zero indicates a field that is null in the
+ underlying CLD2 data; a real example of this is the fast_state field of the
+ UTF8PropObj, which may be null.
+
+ The size of the header can be precalculated by calling calculateHeaderSize(),
+ which will indicate the exact size of the header for a data file that contains
+ a given number of CLD2TableSummary objects.
+
+ Notes on endianness:
+ The data format is only suitable for little-endian machines. For big-endian
+ systems, a tedious transformation would need to be made first to reverse the
+ byte order of significant portions of the binary - not just the lengths, but
+ also some of the underlying table data.
+
+ Note on 32/64 bit:
+ The data format is agnostic to 32/64 bit pointers. All the offsets within the
+ data blob itself are 32-bit values relative to the start of the file, and the
+ file should certainly never be gigabytes in size!
+ When the file is ultimately read by the loading code and mmap()'d, new
+ pointers are generated at whatever size the system uses, initialized to the
+ start of the mmap, and incremented by the 32-bit offset. This should be safe
+ regardless of 32- or 64-bit architectures.
+
+ --------------------------------------------------------------------
+ FIELD
+ --------------------------------------------------------------------
+ DATA_FILE_MARKER (no null terminator)
+ total file size (sanity check, uint32)
+ --------------------------------------------------------------------
+ UTF8PropObj: const uint32 state0
+ UTF8PropObj: const uint32 state0_size
+ UTF8PropObj: const uint32 total_size
+ UTF8PropObj: const int max_expand
+ UTF8PropObj: const int entry_shift (coerced to 32 bits)
+ UTF8PropObj: const int bytes_per_entry (coerced to 32 bits)
+ UTF8PropObj: const uint32 losub
+ UTF8PropObj: const uint32 hiadd
+ offset of UTF8PropObj: const uint8* state_table
+ length of UTF8PropObj: const uint8* state_table
+ offset of UTF8PropObj: const RemapEntry* remap_base (4-byte struct)
+ length of UTF8PropObj: const RemapEntry* remap_base (4-byte struct)
+ offset of UTF8PropObj: const uint8* remap_string
+ length of UTF8PropObj: const uint8* remap_string
+ offset of UTF8PropObj: const uint8* fast_state
+ length of UTF8PropObj: const uint8* fast_state
+ --------------------------------------------------------------------
+ start of const short kAvgDeltaOctaScore[]
+ length of const short kAvgDeltaOctaScore[]
+ --------------------------------------------------------------------
+ number of CLD2TableSummary objects encoded (n)
+ [Table 1]: CLD2TableSummary: uint32 kCLDTableSizeOne
+ [Table 1]: CLD2TableSummary: uint32 kCLDTableSize
+ [Table 1]: CLD2TableSummary: uint32 kCLDTableKeyMask
+ [Table 1]: CLD2TableSummary: uint32 kCLDTableBuildDate
+ [Table 1]: offset of CLD2TableSummary: const IndirectProbBucket4* kCLDTable
+ [Table 1]: length of CLD2TableSummary: const IndirectProbBucket4* kCLDTable
+ [Table 1]: offset of CLD2TableSummary: const uint32* kCLDTableInd
+ [Table 1]: length of CLD2TableSummary: const uint32* kCLDTableInd
+ [Table 1]: offset of CLD2TableSummary: const char* kRecognizedLangScripts
+ [Table 1]: length of CLD2TableSummary: const char* kRecognizedLangScripts + 1
+ .
+ .
+ .
+ [Table n]: CLD2TableSummary: uint32 kCLDTableSizeOne
+ [Table n]: CLD2TableSummary: uint32 kCLDTableSize
+ [Table n]: CLD2TableSummary: uint32 kCLDTableKeyMask
+ [Table n]: CLD2TableSummary: uint32 kCLDTableBuildDate
+ [Table n]: offset of CLD2TableSummary: const IndirectProbBucket4* kCLDTable
+ [Table n]: length of CLD2TableSummary: const IndirectProbBucket4* kCLDTable
+ [Table n]: offset of CLD2TableSummary: const uint32* kCLDTableInd
+ [Table n]: length of CLD2TableSummary: const uint32* kCLDTableInd
+ [Table n]: offset of CLD2TableSummary: const char* kRecognizedLangScripts
+ [Table n]: length of CLD2TableSummary: const char* kRecognizedLangScripts + 1
+ --------------------------------------------------------------------
+
+
+ Immediately after the header fields comes the data block. The data block has
+ the following content, in this order (note that padding is applied in order to
+ keep lookups word-aligned):
+
+ UTF8PropObj: const uint8* state_table
+ UTF8PropObj: const RemapEntry* remap_base (4-byte struct)
+ UTF8PropObj: const uint8* remap_string
+ UTF8PropObj: const uint8* fast_state
+ const short kAvgDeltaOctaScore[]
+ [Table 1]: CLD2TableSummary: const IndirectProbBucket4* kCLDTable
+ [Table 1]: CLD2TableSummary: const uint32* kCLDTableInd
+ [Table 1]: CLD2TableSummary: const char* kRecognizedLangScripts (with null terminator)
+ .
+ .
+ .
+ [Table n]: CLD2TableSummary: const IndirectProbBucket4* kCLDTable
+ [Table n]: CLD2TableSummary: const uint32* kCLDTableInd
+ [Table n]: CLD2TableSummary: const char* kRecognizedLangScripts (with null terminator)
+
+
+ It is STRONGLY recommended that the chunks within the data block be kept
+ 128-bit aligned for efficiency reasons, although the code will work without
+ such alignment: the main lookup tables have randomly-accessed groups of four
+ 4-byte entries, and these must be 16-byte aligned to avoid the performance
+ cost of multiple cache misses per group.
+*/
+namespace CLD2DynamicData {
+
+static const char* DATA_FILE_MARKER = "cld2_data_file00";
+static const int DATA_FILE_MARKER_LENGTH = 16; // Keep aligned to 128 bits
+
+// Nicer version of memcmp that shows the offset at which bytes differ
+bool mem_compare(const void* data1, const void* data2, const int length);
+
+// Enable or disable debugging; 0 to disable, 1 to enable
+void setDebug(int debug);
+
+// Lower-level structure for individual tables. There are n table headers in
+// a given file header.
+typedef struct {
+ CLD2::uint32 kCLDTableSizeOne;
+ CLD2::uint32 kCLDTableSize;
+ CLD2::uint32 kCLDTableKeyMask;
+ CLD2::uint32 kCLDTableBuildDate;
+ CLD2::uint32 startOf_kCLDTable;
+ CLD2::uint32 lengthOf_kCLDTable;
+ CLD2::uint32 startOf_kCLDTableInd;
+ CLD2::uint32 lengthOf_kCLDTableInd;
+ CLD2::uint32 startOf_kRecognizedLangScripts;
+ CLD2::uint32 lengthOf_kRecognizedLangScripts;
+} TableHeader;
+
+
+// Top-level structure for a CLD2 Data File Header.
+// Contains all the primitive fields for the header as well as an array of
+// headers for the individual tables.
+typedef struct {
+ // Marker fields help recognize and verify the data file
+ char sanityString[DATA_FILE_MARKER_LENGTH];
+ CLD2::uint32 totalFileSizeBytes;
+
+ // UTF8 primitives
+ CLD2::uint32 utf8PropObj_state0;
+ CLD2::uint32 utf8PropObj_state0_size;
+ CLD2::uint32 utf8PropObj_total_size;
+ CLD2::uint32 utf8PropObj_max_expand;
+ CLD2::uint32 utf8PropObj_entry_shift;
+ CLD2::uint32 utf8PropObj_bytes_per_entry;
+ CLD2::uint32 utf8PropObj_losub;
+ CLD2::uint32 utf8PropObj_hiadd;
+ CLD2::uint32 startOf_utf8PropObj_state_table;
+ CLD2::uint32 lengthOf_utf8PropObj_state_table;
+ CLD2::uint32 startOf_utf8PropObj_remap_base;
+ CLD2::uint32 lengthOf_utf8PropObj_remap_base;
+ CLD2::uint32 startOf_utf8PropObj_remap_string;
+ CLD2::uint32 lengthOf_utf8PropObj_remap_string;
+ CLD2::uint32 startOf_utf8PropObj_fast_state;
+ CLD2::uint32 lengthOf_utf8PropObj_fast_state;
+
+ // Average delta-octa-score bits
+ CLD2::uint32 startOf_kAvgDeltaOctaScore;
+ CLD2::uint32 lengthOf_kAvgDeltaOctaScore;
+
+ // Table bits
+ CLD2::uint32 numTablesEncoded;
+ TableHeader* tableHeaders;
+} FileHeader;
+
+// Calculate the exact size of a header that encodes the specified number of
+// tables. This can be used to reserve space within the data file,
+// calculate offsets, and so on.
+CLD2::uint32 calculateHeaderSize(CLD2::uint32 numTables);
+
+// Dump a given header to stdout as a human-readable string.
+void dumpHeader(FileHeader* header);
+
+// Verify that a given pair of scoring tables match precisely
+// If there is a problem, returns an error message; otherwise, the empty string.
+bool verify(const CLD2::ScoringTables* realData, const CLD2::ScoringTables* loadedData);
+
+// Return true iff the program is running in little-endian mode.
+bool isLittleEndian();
+
+// Return true iff the core size assumptions are ok on this platform.
+bool coreAssumptionsOk();
+
+} // End namespace CLD2DynamicData
+#endif // CLD2_INTERNAL_CLD2_DYNAMIC_DATA_H_
diff --git a/browser/components/translation/cld2/internal/cld2_dynamic_data_loader.h b/browser/components/translation/cld2/internal/cld2_dynamic_data_loader.h
new file mode 100644
index 000000000..9beba0fb3
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld2_dynamic_data_loader.h
@@ -0,0 +1,52 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#ifndef CLD2_INTERNAL_CLD2_DYNAMIC_DATA_LOADER_H_
+#define CLD2_INTERNAL_CLD2_DYNAMIC_DATA_LOADER_H_
+
+#include "scoreonescriptspan.h"
+#include "cld2_dynamic_data.h"
+
+namespace CLD2DynamicDataLoader {
+
+// Read a header from the specified file and return it.
+// The header returned is dynamically allocated; you must 'delete' the array
+// of TableHeaders as well as the returned FileHeader* when done.
+CLD2DynamicData::FileHeader* loadHeader(const char* fileName);
+
+// Load data directly into a ScoringTables structure using a private, read-only
+// mmap and return the newly-allocated structure.
+// The out-parameter "mmapAddressOut" is a pointer to a void*; the starting
+// address of the mmap()'d block will be written here.
+// The out-parameter "mmapLengthOut" is a pointer to an int; the length of the
+// mmap()'d block will be written here.
+// It is up to the caller to delete
+CLD2::ScoringTables* loadDataFile(const char* fileName,
+ void** mmapAddressOut, int* mmapLengthOut);
+
+// Given pointers to the data from a previous invocation of loadDataFile,
+// unloads the data safely - freeing and deleting any malloc'd/new'd objects.
+// When this method returns, the mmap has been deleted, as have all the scoring
+// tables; the pointers passed in are all zeroed, such that:
+// *scoringTables == NULL
+// *mmapAddress == NULL
+// mmapLength == NULL
+// This is the only safe way to unload data that was previously loaded, as there
+// is an unfortunate mixture of new and malloc involved in building the
+// in-memory represtation of the data.
+void unloadData(CLD2::ScoringTables** scoringTables,
+ void** mmapAddress, int* mmapLength);
+
+} // End namespace CLD2DynamicDataExtractor
+#endif // CLD2_INTERNAL_CLD2_DYNAMIC_DATA_EXTRACTOR_H_
diff --git a/browser/components/translation/cld2/internal/cld2_generated_cjk_compatible.cc b/browser/components/translation/cld2/internal/cld2_generated_cjk_compatible.cc
new file mode 100644
index 000000000..d08b66c5e
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld2_generated_cjk_compatible.cc
@@ -0,0 +1,298 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// CJK compatible CLD2 scoring lookup table
+//
+#include "cld2tablesummary.h"
+
+namespace CLD2 {
+
+static const uint32 kCompatTableBuildDate = 20130128; // yyyymmdd
+static const uint32 kCompatTableSize = 1; // Total Bucket count
+static const uint32 kCompatTableKeyMask = 0xffffff00; // Mask hash key
+static const char* const kCompatTableRecognizedLangScripts =
+ "zh-Hans zh-Hant ja-Hani ko-Hani vi-Hani za-Hani ";
+
+// Empty table
+static const IndirectProbBucket4 kCompatTable[kCompatTableSize] = {
+ // key[4], words[4] in UTF-8
+ // value[4]
+ { {0x00000000,0x00000000,0x00000000,0x00000000}}, // [000]
+};
+
+// These are back-derived CTJKVZ probabilities from the table
+// kTargetCTJKVZProbs in cldutil.cc
+// This is all part of using one-byte mappings for CJK but wanting to
+// convert them to normal langprob values to share the scoring code.
+static const uint32 kCompatTableSizeOne = 0; // One-langprob count
+static const uint32 kCompatTableIndSize = 239 * 2; // Largest subscript
+static const uint32 kCompatTableInd[kCompatTableIndSize] = {
+ // [0000]
+ 0x00000000, 0x00000000, // [0] zh.0 zhT.0 ja.0 ko.0 vi.0 za.0
+ 0x00006142, 0x00000000, // [1] zh.0 zhT.0 ja.0 ko.0 vi.0 za.12
+ 0x00002d42, 0x00000000, // [2] zh.0 zhT.0 ja.0 ko.0 vi.12 za.0
+ 0x00000342, 0x00000000, // [3] zh.0 zhT.0 ja.0 ko.12 vi.0 za.0
+ 0x00000242, 0x00000000, // [4] zh.0 zhT.0 ja.12 ko.0 vi.0 za.0
+ 0x00001d42, 0x00000000, // [5] zh.0 zhT.12 ja.0 ko.0 vi.0 za.0
+ 0x00000542, 0x00000000, // [6] zh.12 zhT.0 ja.0 ko.0 vi.0 za.0
+ 0x2d00051f, 0x00000000, // [7] zh.8 zhT.0 ja.0 ko.0 vi.4 za.0
+ 0x0300051f, 0x00000000, // [8] zh.8 zhT.0 ja.0 ko.4 vi.0 za.0
+ 0x0200051f, 0x00000000, // [9] zh.8 zhT.0 ja.4 ko.0 vi.0 za.0
+ 0x1d00051f, 0x00000000, // [10] zh.8 zhT.4 ja.0 ko.0 vi.0 za.0
+ 0x031d05ea, 0x00000000, // [11] zh.8 zhT.2 ja.0 ko.2 vi.0 za.0
+ 0x0000611c, 0x00000000, // [12] zh.0 zhT.0 ja.0 ko.0 vi.0 za.8
+ 0x1d00021f, 0x00000000, // [13] zh.0 zhT.4 ja.8 ko.0 vi.0 za.0
+ 0x0500611f, 0x00000000, // [14] zh.4 zhT.0 ja.0 ko.0 vi.0 za.8
+ 0x0000021c, 0x00000000, // [15] zh.0 zhT.0 ja.8 ko.0 vi.0 za.0
+ 0x021d05ea, 0x00000000, // [16] zh.8 zhT.2 ja.2 ko.0 vi.0 za.0
+ 0x02001d1f, 0x00000000, // [17] zh.0 zhT.8 ja.4 ko.0 vi.0 za.0
+ 0x6100051f, 0x00000000, // [18] zh.8 zhT.0 ja.0 ko.0 vi.0 za.4
+ 0x02001d1d, 0x00000000, // [19] zh.0 zhT.8 ja.2 ko.0 vi.0 za.0
+ 0x05001d1f, 0x00000000, // [20] zh.4 zhT.8 ja.0 ko.0 vi.0 za.0
+ 0x03051dea, 0x00000000, // [21] zh.2 zhT.8 ja.0 ko.2 vi.0 za.0
+ 0x051d02ea, 0x00000000, // [22] zh.2 zhT.2 ja.8 ko.0 vi.0 za.0
+ 0x00001d1c, 0x00000000, // [23] zh.0 zhT.8 ja.0 ko.0 vi.0 za.0
+ 0x1d00021d, 0x00000000, // [24] zh.0 zhT.2 ja.8 ko.0 vi.0 za.0
+ 0x02051dea, 0x00000000, // [25] zh.2 zhT.8 ja.2 ko.0 vi.0 za.0
+ 0x0000051c, 0x00000000, // [26] zh.8 zhT.0 ja.0 ko.0 vi.0 za.0
+ 0x05001d1d, 0x00000000, // [27] zh.2 zhT.8 ja.0 ko.0 vi.0 za.0
+ 0x1d00051d, 0x00000000, // [28] zh.8 zhT.2 ja.0 ko.0 vi.0 za.0
+ 0x2d021ded, 0x00000000, // [29] zh.0 zhT.6 ja.2 ko.0 vi.2 za.0
+ 0x05002d10, 0x00000000, // [30] zh.2 zhT.0 ja.0 ko.0 vi.6 za.0
+ 0x05002d12, 0x00000000, // [31] zh.4 zhT.0 ja.0 ko.0 vi.6 za.0
+ 0x2d051dec, 0x00000000, // [32] zh.4 zhT.6 ja.0 ko.0 vi.4 za.0
+ 0x02051d10, 0x00002d01, // [33] zh.4 zhT.6 ja.2 ko.0 vi.2 za.0
+ 0x02051dec, 0x00002d01, // [34] zh.4 zhT.6 ja.4 ko.0 vi.2 za.0
+ 0x1d050212, 0x00000000, // [35] zh.5 zhT.4 ja.6 ko.0 vi.0 za.0
+ 0x2d000512, 0x00000000, // [36] zh.6 zhT.0 ja.0 ko.0 vi.4 za.0
+ 0x022d0510, 0x00000000, // [37] zh.6 zhT.0 ja.2 ko.0 vi.4 za.0
+ 0x2d0205ec, 0x00000000, // [38] zh.6 zhT.0 ja.4 ko.0 vi.4 za.0
+ 0x1d2d0510, 0x00000000, // [39] zh.6 zhT.2 ja.0 ko.0 vi.4 za.0
+ 0x022d0510, 0x00001d01, // [40] zh.6 zhT.2 ja.2 ko.0 vi.4 za.0
+ 0x1d020510, 0x00002d01, // [41] zh.6 zhT.2 ja.4 ko.0 vi.2 za.0
+ 0x2d1d0510, 0x00000000, // [42] zh.6 zhT.4 ja.0 ko.0 vi.2 za.0
+ 0x021d0510, 0x00002d01, // [43] zh.6 zhT.4 ja.2 ko.0 vi.2 za.0
+ 0x03000210, 0x00000000, // [44] zh.0 zhT.0 ja.6 ko.2 vi.0 za.0
+ 0x61021ded, 0x00000000, // [45] zh.0 zhT.6 ja.2 ko.0 vi.0 za.2
+ 0x021d61ed, 0x00000501, // [46] zh.2 zhT.2 ja.2 ko.0 vi.0 za.6
+ 0x05030210, 0x00001d01, // [47] zh.2 zhT.2 ja.6 ko.4 vi.0 za.0
+ 0x051d6110, 0x00000000, // [48] zh.2 zhT.4 ja.0 ko.0 vi.0 za.6
+ 0x05031d10, 0x00000000, // [49] zh.2 zhT.6 ja.0 ko.4 vi.0 za.0
+ 0x02031d10, 0x00000501, // [50] zh.2 zhT.6 ja.2 ko.4 vi.0 za.0
+ 0x03021dec, 0x00000501, // [51] zh.2 zhT.6 ja.4 ko.4 vi.0 za.0
+ 0x02056110, 0x00000000, // [52] zh.4 zhT.0 ja.2 ko.0 vi.0 za.6
+ 0x1d050210, 0x00000301, // [53] zh.4 zhT.2 ja.6 ko.2 vi.0 za.0
+ 0x051d61ec, 0x00000201, // [54] zh.4 zhT.4 ja.2 ko.0 vi.0 za.6
+ 0x02051dec, 0x00006101, // [55] zh.4 zhT.6 ja.4 ko.0 vi.0 za.2
+ 0x610205ed, 0x00000000, // [56] zh.6 zhT.0 ja.2 ko.0 vi.0 za.2
+ 0x611d05ed, 0x00000000, // [57] zh.6 zhT.2 ja.0 ko.0 vi.0 za.2
+ 0x02610510, 0x00001d01, // [58] zh.6 zhT.2 ja.2 ko.0 vi.0 za.4
+ 0x1d020510, 0x00006101, // [59] zh.6 zhT.2 ja.4 ko.0 vi.0 za.2
+ 0x61051dec, 0x00000201, // [60] zh.4 zhT.6 ja.2 ko.0 vi.0 za.4
+ 0x611d05ec, 0x00000201, // [61] zh.6 zhT.4 ja.2 ko.0 vi.0 za.4
+ 0x05006110, 0x00000000, // [62] zh.2 zhT.0 ja.0 ko.0 vi.0 za.6
+ 0x031d05ed, 0x00000000, // [63] zh.6 zhT.2 ja.0 ko.2 vi.0 za.0
+ 0x051d61ed, 0x00000000, // [64] zh.2 zhT.2 ja.0 ko.0 vi.0 za.6
+ 0x1d0205eb, 0x00000000, // [65] zh.6 zhT.2 ja.6 ko.0 vi.0 za.0
+ 0x021d0510, 0x00006101, // [66] zh.6 zhT.4 ja.2 ko.0 vi.0 za.2
+ 0x021d0510, 0x00000301, // [67] zh.6 zhT.4 ja.2 ko.2 vi.0 za.0
+ 0x02051dec, 0x00000301, // [68] zh.4 zhT.6 ja.4 ko.2 vi.0 za.0
+ 0x02610510, 0x00000000, // [69] zh.6 zhT.0 ja.2 ko.0 vi.0 za.4
+ 0x61020510, 0x00000000, // [70] zh.6 zhT.0 ja.4 ko.0 vi.0 za.2
+ 0x02000514, 0x00000000, // [71] zh.6 zhT.0 ja.6 ko.0 vi.0 za.0
+ 0x021d05ed, 0x00000000, // [72] zh.6 zhT.2 ja.2 ko.0 vi.0 za.0
+ 0x611d0510, 0x00000000, // [73] zh.6 zhT.4 ja.0 ko.0 vi.0 za.2
+ 0x1d020512, 0x00000000, // [74] zh.6 zhT.4 ja.5 ko.0 vi.0 za.0
+ 0x03001d10, 0x00000000, // [75] zh.0 zhT.6 ja.0 ko.2 vi.0 za.0
+ 0x03021ded, 0x00000000, // [76] zh.0 zhT.6 ja.2 ko.2 vi.0 za.0
+ 0x03051ded, 0x00000000, // [77] zh.2 zhT.6 ja.0 ko.2 vi.0 za.0
+ 0x02051ded, 0x00000301, // [78] zh.2 zhT.6 ja.2 ko.2 vi.0 za.0
+ 0x1d056110, 0x00000000, // [79] zh.4 zhT.2 ja.0 ko.0 vi.0 za.6
+ 0x611d05ec, 0x00000000, // [80] zh.6 zhT.4 ja.0 ko.0 vi.0 za.4
+ 0x031d0510, 0x00000000, // [81] zh.6 zhT.4 ja.0 ko.2 vi.0 za.0
+ 0x031d05eb, 0x00000000, // [82] zh.6 zhT.6 ja.0 ko.2 vi.0 za.0
+ 0x610205ec, 0x00000000, // [83] zh.6 zhT.0 ja.4 ko.0 vi.0 za.4
+ 0x1d610510, 0x00000000, // [84] zh.6 zhT.2 ja.0 ko.0 vi.0 za.4
+ 0x021d05eb, 0x00000301, // [85] zh.6 zhT.6 ja.2 ko.2 vi.0 za.0
+ 0x61051d10, 0x00000000, // [86] zh.4 zhT.6 ja.0 ko.0 vi.0 za.2
+ 0x05021deb, 0x00000000, // [87] zh.2 zhT.6 ja.6 ko.0 vi.0 za.0
+ 0x051d0212, 0x00000000, // [88] zh.4 zhT.5 ja.6 ko.0 vi.0 za.0
+ 0x03051d10, 0x00000000, // [89] zh.4 zhT.6 ja.0 ko.2 vi.0 za.0
+ 0x1d6105eb, 0x00000000, // [90] zh.6 zhT.2 ja.0 ko.0 vi.0 za.6
+ 0x03021d10, 0x00000000, // [91] zh.0 zhT.6 ja.4 ko.2 vi.0 za.0
+ 0x05000212, 0x00000000, // [92] zh.4 zhT.0 ja.6 ko.0 vi.0 za.0
+ 0x05021d10, 0x00000301, // [93] zh.2 zhT.6 ja.4 ko.2 vi.0 za.0
+ 0x61051dec, 0x00000000, // [94] zh.4 zhT.6 ja.0 ko.0 vi.0 za.4
+ 0x021d05ed, 0x00000000, // [95] zh.6 zhT.2 ja.2 ko.0 vi.0 za.0
+ 0x02051d10, 0x00000301, // [96] zh.4 zhT.6 ja.2 ko.2 vi.0 za.0
+ 0x05021d12, 0x00000000, // [97] zh.4 zhT.6 ja.5 ko.0 vi.0 za.0
+ 0x02000510, 0x00000000, // [98] zh.6 zhT.0 ja.2 ko.0 vi.0 za.0
+ 0x021d05ec, 0x00000000, // [99] zh.6 zhT.4 ja.4 ko.0 vi.0 za.0
+ 0x1d050210, 0x00000000, // [100] zh.4 zhT.2 ja.6 ko.0 vi.0 za.0
+ 0x05000210, 0x00000000, // [101] zh.2 zhT.0 ja.6 ko.0 vi.0 za.0
+ 0x051d61ec, 0x00000000, // [102] zh.4 zhT.4 ja.0 ko.0 vi.0 za.6
+ 0x051d02ec, 0x00000000, // [103] zh.4 zhT.4 ja.6 ko.0 vi.0 za.0
+ 0x02051d10, 0x00006101, // [104] zh.4 zhT.6 ja.2 ko.0 vi.0 za.2
+ 0x051d02ed, 0x00000000, // [105] zh.2 zhT.2 ja.6 ko.0 vi.0 za.0
+ 0x051d0210, 0x00000000, // [106] zh.2 zhT.4 ja.6 ko.0 vi.0 za.0
+ 0x02001d14, 0x00000000, // [107] zh.0 zhT.6 ja.6 ko.0 vi.0 za.0
+ 0x1d020510, 0x00000000, // [108] zh.6 zhT.2 ja.4 ko.0 vi.0 za.0
+ 0x1d000212, 0x00000000, // [109] zh.0 zhT.4 ja.6 ko.0 vi.0 za.0
+ 0x05006112, 0x00000000, // [110] zh.4 zhT.0 ja.0 ko.0 vi.0 za.6
+ 0x02051dec, 0x00000000, // [111] zh.4 zhT.6 ja.4 ko.0 vi.0 za.0
+ 0x61000514, 0x00000000, // [112] zh.6 zhT.0 ja.0 ko.0 vi.0 za.6
+ 0x61000510, 0x00000000, // [113] zh.6 zhT.0 ja.0 ko.0 vi.0 za.2
+ 0x02000512, 0x00000000, // [114] zh.6 zhT.0 ja.4 ko.0 vi.0 za.0
+ 0x021d0512, 0x00000000, // [115] zh.6 zhT.5 ja.4 ko.0 vi.0 za.0
+ 0x1d000210, 0x00000000, // [116] zh.0 zhT.2 ja.6 ko.0 vi.0 za.0
+ 0x0000020f, 0x00000000, // [117] zh.0 zhT.0 ja.6 ko.0 vi.0 za.0
+ 0x021d05eb, 0x00000000, // [118] zh.6 zhT.6 ja.2 ko.0 vi.0 za.0
+ 0x05021d10, 0x00000000, // [119] zh.2 zhT.6 ja.4 ko.0 vi.0 za.0
+ 0x021d0510, 0x00000000, // [120] zh.6 zhT.4 ja.2 ko.0 vi.0 za.0
+ 0x02051ded, 0x00000000, // [121] zh.2 zhT.6 ja.2 ko.0 vi.0 za.0
+ 0x05001d10, 0x00000000, // [122] zh.2 zhT.6 ja.0 ko.0 vi.0 za.0
+ 0x61000512, 0x00000000, // [123] zh.6 zhT.0 ja.0 ko.0 vi.0 za.4
+ 0x1d000512, 0x00000000, // [124] zh.6 zhT.4 ja.0 ko.0 vi.0 za.0
+ 0x1d000514, 0x00000000, // [125] zh.6 zhT.6 ja.0 ko.0 vi.0 za.0
+ 0x02051d12, 0x00000000, // [126] zh.5 zhT.6 ja.4 ko.0 vi.0 za.0
+ 0x00001d0f, 0x00000000, // [127] zh.0 zhT.6 ja.0 ko.0 vi.0 za.0
+ 0x1d000510, 0x00000000, // [128] zh.6 zhT.2 ja.0 ko.0 vi.0 za.0
+ 0x02001d10, 0x00000000, // [129] zh.0 zhT.6 ja.2 ko.0 vi.0 za.0
+ 0x02051d10, 0x00000000, // [130] zh.4 zhT.6 ja.2 ko.0 vi.0 za.0
+ 0x02001d12, 0x00000000, // [131] zh.0 zhT.6 ja.4 ko.0 vi.0 za.0
+ 0x05001d12, 0x00000000, // [132] zh.4 zhT.6 ja.0 ko.0 vi.0 za.0
+ 0x0000050f, 0x00000000, // [133] zh.6 zhT.0 ja.0 ko.0 vi.0 za.0
+ 0x021d0513, 0x00000000, // [134] zh.6 zhT.6 ja.5 ko.0 vi.0 za.0
+ 0x1d020513, 0x00000000, // [135] zh.6 zhT.5 ja.6 ko.0 vi.0 za.0
+ 0x05021d13, 0x00000000, // [136] zh.5 zhT.6 ja.6 ko.0 vi.0 za.0
+ 0x051d02af, 0x00000000, // [137] zh.5 zhT.5 ja.6 ko.0 vi.0 za.0
+ 0x02051daf, 0x00000000, // [138] zh.5 zhT.6 ja.5 ko.0 vi.0 za.0
+ 0x021d05af, 0x00000000, // [139] zh.6 zhT.5 ja.5 ko.0 vi.0 za.0
+ 0x021d0514, 0x00000000, // [140] zh.6 zhT.6 ja.6 ko.0 vi.0 za.0
+ 0x1d000513, 0x00000000, // [141] zh.6 zhT.5 ja.0 ko.0 vi.0 za.0
+ 0x02000513, 0x00000000, // [142] zh.6 zhT.0 ja.5 ko.0 vi.0 za.0
+ 0x02001d13, 0x00000000, // [143] zh.0 zhT.6 ja.5 ko.0 vi.0 za.0
+ 0x05001d13, 0x00000000, // [144] zh.5 zhT.6 ja.0 ko.0 vi.0 za.0
+ 0x05000213, 0x00000000, // [145] zh.5 zhT.0 ja.6 ko.0 vi.0 za.0
+ 0x1d000213, 0x00000000, // [146] zh.0 zhT.5 ja.6 ko.0 vi.0 za.0
+ 0x00002d06, 0x00000000, // [147] zh.0 zhT.0 ja.0 ko.0 vi.4 za.0
+ 0x00000306, 0x00000000, // [148] zh.0 zhT.0 ja.0 ko.4 vi.0 za.0
+ 0x051d2dee, 0x00000000, // [149] zh.2 zhT.2 ja.0 ko.0 vi.4 za.0
+ 0x021d2dee, 0x00000501, // [150] zh.2 zhT.2 ja.2 ko.0 vi.4 za.0
+ 0x2d051dee, 0x00000000, // [151] zh.2 zhT.4 ja.0 ko.0 vi.2 za.0
+ 0x02051dee, 0x00002d01, // [152] zh.2 zhT.4 ja.2 ko.0 vi.2 za.0
+ 0x05021d55, 0x00002d01, // [153] zh.2 zhT.4 ja.4 ko.0 vi.2 za.0
+ 0x022d0555, 0x00000000, // [154] zh.4 zhT.0 ja.2 ko.0 vi.4 za.0
+ 0x2d020555, 0x00000000, // [155] zh.4 zhT.0 ja.4 ko.0 vi.2 za.0
+ 0x2d1d05ee, 0x00000000, // [156] zh.4 zhT.2 ja.0 ko.0 vi.2 za.0
+ 0x021d05ee, 0x00002d01, // [157] zh.4 zhT.2 ja.2 ko.0 vi.2 za.0
+ 0x2d1d0555, 0x00000000, // [158] zh.4 zhT.4 ja.0 ko.0 vi.2 za.0
+ 0x021d0555, 0x00002d01, // [159] zh.4 zhT.4 ja.2 ko.0 vi.2 za.0
+ 0x021d0509, 0x00002d01, // [160] zh.4 zhT.4 ja.4 ko.0 vi.2 za.0
+ 0x1d0203ee, 0x00000000, // [161] zh.0 zhT.2 ja.2 ko.4 vi.0 za.0
+ 0x051d02ee, 0x00000301, // [162] zh.2 zhT.2 ja.4 ko.2 vi.0 za.0
+ 0x05021d55, 0x00006101, // [163] zh.2 zhT.4 ja.4 ko.0 vi.0 za.2
+ 0x05021d55, 0x00000301, // [164] zh.2 zhT.4 ja.4 ko.2 vi.0 za.0
+ 0x61020555, 0x00000000, // [165] zh.4 zhT.0 ja.4 ko.0 vi.0 za.2
+ 0x61020509, 0x00000000, // [166] zh.4 zhT.0 ja.4 ko.0 vi.0 za.4
+ 0x02030555, 0x00001d01, // [167] zh.4 zhT.2 ja.2 ko.4 vi.0 za.0
+ 0x031d0555, 0x00000000, // [168] zh.4 zhT.4 ja.0 ko.2 vi.0 za.0
+ 0x051d03ee, 0x00000000, // [169] zh.2 zhT.2 ja.0 ko.4 vi.0 za.0
+ 0x02051dee, 0x00000301, // [170] zh.2 zhT.4 ja.2 ko.2 vi.0 za.0
+ 0x021d0555, 0x00000301, // [171] zh.4 zhT.4 ja.2 ko.2 vi.0 za.0
+ 0x02000509, 0x00000000, // [172] zh.4 zhT.0 ja.4 ko.0 vi.0 za.0
+ 0x021d0509, 0x00006106, // [173] zh.4 zhT.4 ja.4 ko.0 vi.0 za.4
+ 0x03001d07, 0x00000000, // [174] zh.0 zhT.4 ja.0 ko.2 vi.0 za.0
+ 0x03021dee, 0x00000000, // [175] zh.0 zhT.4 ja.2 ko.2 vi.0 za.0
+ 0x610205ee, 0x00000000, // [176] zh.4 zhT.0 ja.2 ko.0 vi.0 za.2
+ 0x1d610555, 0x00000000, // [177] zh.4 zhT.2 ja.0 ko.0 vi.0 za.4
+ 0x021d61ee, 0x00000501, // [178] zh.2 zhT.2 ja.2 ko.0 vi.0 za.4
+ 0x03000507, 0x00000000, // [179] zh.4 zhT.0 ja.0 ko.2 vi.0 za.0
+ 0x021d0509, 0x00006101, // [180] zh.4 zhT.4 ja.4 ko.0 vi.0 za.2
+ 0x61000509, 0x00000000, // [181] zh.4 zhT.0 ja.0 ko.0 vi.0 za.4
+ 0x02610555, 0x00000000, // [182] zh.4 zhT.0 ja.2 ko.0 vi.0 za.4
+ 0x611d05ee, 0x00000000, // [183] zh.4 zhT.2 ja.0 ko.0 vi.0 za.2
+ 0x021d05ee, 0x00006101, // [184] zh.4 zhT.2 ja.2 ko.0 vi.0 za.2
+ 0x03051dee, 0x00000000, // [185] zh.2 zhT.4 ja.0 ko.2 vi.0 za.0
+ 0x051d61ee, 0x00000000, // [186] zh.2 zhT.2 ja.0 ko.0 vi.0 za.4
+ 0x05611d55, 0x00000000, // [187] zh.2 zhT.4 ja.0 ko.0 vi.0 za.4
+ 0x02611d55, 0x00000501, // [188] zh.2 zhT.4 ja.2 ko.0 vi.0 za.4
+ 0x1d020555, 0x00000000, // [189] zh.4 zhT.2 ja.4 ko.0 vi.0 za.0
+ 0x05000207, 0x00000000, // [190] zh.2 zhT.0 ja.4 ko.0 vi.0 za.0
+ 0x02000507, 0x00000000, // [191] zh.4 zhT.0 ja.2 ko.0 vi.0 za.0
+ 0x611d0509, 0x00000000, // [192] zh.4 zhT.4 ja.0 ko.0 vi.0 za.4
+ 0x611d0509, 0x00000201, // [193] zh.4 zhT.4 ja.2 ko.0 vi.0 za.4
+ 0x02001d09, 0x00000000, // [194] zh.0 zhT.4 ja.4 ko.0 vi.0 za.0
+ 0x611d0555, 0x00000000, // [195] zh.4 zhT.4 ja.0 ko.0 vi.0 za.2
+ 0x61051dee, 0x00000000, // [196] zh.2 zhT.4 ja.0 ko.0 vi.0 za.2
+ 0x051d02ee, 0x00000000, // [197] zh.2 zhT.2 ja.4 ko.0 vi.0 za.0
+ 0x1d000207, 0x00000000, // [198] zh.0 zhT.2 ja.4 ko.0 vi.0 za.0
+ 0x021d05ee, 0x00000000, // [199] zh.4 zhT.2 ja.2 ko.0 vi.0 za.0
+ 0x02051dee, 0x00006101, // [200] zh.2 zhT.4 ja.2 ko.0 vi.0 za.2
+ 0x021d0509, 0x00000000, // [201] zh.4 zhT.4 ja.4 ko.0 vi.0 za.0
+ 0x05021d55, 0x00000000, // [202] zh.2 zhT.4 ja.4 ko.0 vi.0 za.0
+ 0x00000206, 0x00000000, // [203] zh.0 zhT.0 ja.4 ko.0 vi.0 za.0
+ 0x02001d07, 0x00000000, // [204] zh.0 zhT.4 ja.2 ko.0 vi.0 za.0
+ 0x021d0555, 0x00006101, // [205] zh.4 zhT.4 ja.2 ko.0 vi.0 za.2
+ 0x02051dee, 0x00000000, // [206] zh.2 zhT.4 ja.2 ko.0 vi.0 za.0
+ 0x1d000507, 0x00000000, // [207] zh.4 zhT.2 ja.0 ko.0 vi.0 za.0
+ 0x1d000509, 0x00000000, // [208] zh.4 zhT.4 ja.0 ko.0 vi.0 za.0
+ 0x021d0555, 0x00000000, // [209] zh.4 zhT.4 ja.2 ko.0 vi.0 za.0
+ 0x05001d07, 0x00000000, // [210] zh.2 zhT.4 ja.0 ko.0 vi.0 za.0
+ 0x00001d06, 0x00000000, // [211] zh.0 zhT.4 ja.0 ko.0 vi.0 za.0
+ 0x00000506, 0x00000000, // [212] zh.4 zhT.0 ja.0 ko.0 vi.0 za.0
+ 0x2d000309, 0x00000000, // [213] zh.0 zhT.0 ja.0 ko.4 vi.4 za.0
+ 0x2d000209, 0x00000000, // [214] zh.0 zhT.0 ja.4 ko.0 vi.4 za.0
+ 0x03000209, 0x00000000, // [215] zh.0 zhT.0 ja.4 ko.4 vi.0 za.0
+ 0x2d001d09, 0x00000000, // [216] zh.0 zhT.4 ja.0 ko.0 vi.4 za.0
+ 0x03001d09, 0x00000000, // [217] zh.0 zhT.4 ja.0 ko.4 vi.0 za.0
+ 0x2d000509, 0x00000000, // [218] zh.4 zhT.0 ja.0 ko.0 vi.4 za.0
+ 0x03000509, 0x00000000, // [219] zh.4 zhT.0 ja.0 ko.4 vi.0 za.0
+ 0x00000501, 0x00000000, // [220] zh.2 zhT.0 ja.0 ko.0 vi.0 za.0
+ 0x00001d01, 0x00000000, // [221] zh.0 zhT.2 ja.0 ko.0 vi.0 za.0
+ 0x2d031d02, 0x00000000, // [222] zh.0 zhT.2 ja.0 ko.2 vi.2 za.0
+ 0x2d021d02, 0x00000000, // [223] zh.0 zhT.2 ja.2 ko.0 vi.2 za.0
+ 0x2d030502, 0x00000000, // [224] zh.2 zhT.0 ja.0 ko.2 vi.2 za.0
+ 0x2d020502, 0x00000000, // [225] zh.2 zhT.0 ja.2 ko.0 vi.2 za.0
+ 0x03020502, 0x00000000, // [226] zh.2 zhT.0 ja.2 ko.2 vi.0 za.0
+ 0x2d1d0502, 0x00000000, // [227] zh.2 zhT.2 ja.0 ko.0 vi.2 za.0
+ 0x021d0502, 0x00000301, // [228] zh.2 zhT.2 ja.2 ko.2 vi.0 za.0
+ 0x031d0502, 0x00000000, // [229] zh.2 zhT.2 ja.0 ko.2 vi.0 za.0
+ 0x1d000502, 0x00000000, // [230] zh.2 zhT.2 ja.0 ko.0 vi.0 za.0
+ 0x00000201, 0x00000000, // [231] zh.0 zhT.0 ja.2 ko.0 vi.0 za.0
+ 0x02001d02, 0x00000000, // [232] zh.0 zhT.2 ja.2 ko.0 vi.0 za.0
+ 0x021d0502, 0x00000000, // [233] zh.2 zhT.2 ja.2 ko.0 vi.0 za.0
+ 0x00000301, 0x00000000, // [234] zh.0 zhT.0 ja.0 ko.2 vi.0 za.0
+ 0x02000502, 0x00000000, // [235] zh.2 zhT.0 ja.2 ko.0 vi.0 za.0
+ 0x03001d02, 0x00000000, // [236] zh.0 zhT.2 ja.0 ko.2 vi.0 za.0
+ 0x03000202, 0x00000000, // [237] zh.0 zhT.0 ja.2 ko.2 vi.0 za.0
+ 0x03021d02, 0x00000000, // [238] zh.0 zhT.2 ja.2 ko.2 vi.0 za.0
+};
+
+extern const CLD2TableSummary kCjkCompat_obj = {
+ kCompatTable,
+ kCompatTableInd,
+ kCompatTableSizeOne,
+ kCompatTableSize,
+ kCompatTableKeyMask,
+ kCompatTableBuildDate,
+ kCompatTableRecognizedLangScripts,
+};
+
+} // End namespace CLD2
+
+// End of generated tables
+
+
diff --git a/browser/components/translation/cld2/internal/cld2_generated_deltaoctachrome0122.cc b/browser/components/translation/cld2/internal/cld2_generated_deltaoctachrome0122.cc
new file mode 100644
index 000000000..035e8fb26
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld2_generated_deltaoctachrome0122.cc
@@ -0,0 +1,4601 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Created by postproc-cld2 4.0 on 2014-02-02 09:39:20
+// From command line:
+// --cld2 --cc --just_read_raw --delta_octa
+// --wrt=cld2_generated_quadchrome0122_2.bin --standard --minchars=5
+// --mincount=2 --max_items_per_langscript=300 --flatmap --rr_alloc
+// --freq_alloc --boostcloseweakerpercent=00 --indirectbits=12 --thresh=224
+// --v25 --kentries=16 --tablename=DeltaOctaChrome0122
+// --remap=xxx-Latn=>ut-Latn tw-Latn=>ak-Latn nd-Latn=>nr-Latn
+// blu-Latn=>hmn-Latn nn-Latn=>no-Latn --include=af-Latn ar-Arab be-Cyrl
+// bg-Cyrl bs-Latn ca-Latn cs-Latn cy-Latn da-Latn de-Latn el-Grek
+// en-Latn es-Latn et-Latn fa-Arab fi-Latn fr-Latn ga-Latn gd-Latn
+// hi-Deva hr-Latn hu-Latn id-Latn is-Latn it-Latn iw-Hebr ja-Hani
+// ko-Hani lg-Latn lt-Latn lv-Latn mk-Cyrl ms-Latn nl-Latn no-Latn
+// pl-Latn pt-Latn ro-Latn ro-Cyrl ru-Cyrl rw-Latn sh-Cyrl sh-Latn sk-Latn
+// sl-Latn sr-Cyrl sv-Latn sw-Latn th-Thai tl-Latn tr-Latn uk-Cyrl
+// vi-Latn yi-Hebr zh-Hani zh-TW zhT-Hani sq-Latn az-Latn eu-Latn
+// bn-Beng gl-Latn ht-Latn mt-Latn sr-Latn ur-Arab bh-Deva mr-Deva
+// ne-Deva lg-Latn rw-Latn gd-Latn ut-Latn ut-Deva ceb-Latn blu-Latn
+// hmn-Latn jw-Latn so-Latn ig-Latn ha-Latn yo-Latn zu-Latn --ko_english
+// --force_to_lang_soft --nosoft_cram2 --nomsidlevel --shapeflatprob
+// --langpriorpercent=10 --skipnuc --noshapeforcetop --noshapeeventop
+// --noshapesteep2 --spread=15 --nodoubleclose --langcounts --writebin
+// --list_items=120 /hdb1/cld2/probs/p90_raw_octagrams_2014.utf8
+//
+// CLD2_pslangs
+//
+// See compact_lang_det.cc for usage
+//
+#include "cld2tablesummary.h"
+namespace CLD2 {
+
+static const uint32 kDeltaOctaChrome0122BuildDate = 20140202; // yyyymmdd
+
+
+// Of 22890 offered items into 16384 table entries:
+// 15078 filled (65%), 4 merged (0%), 7808 dropped (34%)
+
+// Nil-grams: 19 languages
+// GREEK MALAYALAM TELUGU TAMIL GUJARATI THAI KANNADA PUNJABI
+// GEORGIAN SINHALESE ARMENIAN LAOTHIAN KHMER DHIVEHI CHEROKEE
+// SYRIAC LIMBU ORIYA INUKTITUT
+
+// Uni-grams: 4 languages
+// Japanese Korean Chinese ChineseT
+
+// Words/Quads: 71 languages in range ENGLISH..HMONG:
+// ENGLISH DANISH DUTCH FINNISH FRENCH GERMAN HEBREW ITALIAN
+// NORWEGIAN POLISH PORTUGUESE RUSSIAN SPANISH SWEDISH CZECH
+// ICELANDIC LATVIAN LITHUANIAN ROMANIAN HUNGARIAN ESTONIAN
+// Unknown BULGARIAN CROATIAN SERBIAN IRISH GALICIAN TAGALOG
+// TURKISH UKRAINIAN HINDI MACEDONIAN BENGALI INDONESIAN MALAY
+// WELSH NEPALI ALBANIAN BELARUSIAN JAVANESE URDU BIHARI ARABIC
+// CATALAN BASQUE SCOTS_GAELIC SWAHILI SLOVENIAN MARATHI MALTESE
+// VIETNAMESE SLOVAK AZERBAIJANI PERSIAN BOSNIAN ZULU YIDDISH
+// SOMALI MONGOLIAN AFRIKAANS YORUBA HAUSA HAITIAN_CREOLE
+// KINYARWANDA GANDA IGBO CEBUANO HMONG
+
+// TopLanguage TokenCount
+// ENGLISH 187
+// DANISH 199
+// DUTCH 198
+// FINNISH 214
+// FRENCH 189
+// GERMAN 189
+// HEBREW 199
+// ITALIAN 198
+// Japanese 200
+// Korean 193
+// NORWEGIAN 204
+// POLISH 199
+// PORTUGUESE 220
+// RUSSIAN 205
+// SPANISH 187
+// SWEDISH 188
+// Chinese 196
+// CZECH 381
+// ICELANDIC 195
+// LATVIAN 203
+// LITHUANIAN 201
+// ROMANIAN 376
+// HUNGARIAN 201
+// ESTONIAN 193
+// Unknown 5
+// BULGARIAN 187
+// CROATIAN 357
+// SERBIAN 405
+// IRISH 201
+// GALICIAN 195
+// TAGALOG 191
+// TURKISH 200
+// UKRAINIAN 208
+// HINDI 398
+// MACEDONIAN 200
+// BENGALI 203
+// INDONESIAN 384
+// MALAY 349
+// WELSH 212
+// NEPALI 192
+// ALBANIAN 188
+// BELARUSIAN 198
+// JAVANESE 193
+// URDU 190
+// BIHARI 292
+// ARABIC 186
+// CATALAN 195
+// BASQUE 196
+// SCOTS_GAELIC 190
+// SWAHILI 197
+// SLOVENIAN 207
+// MARATHI 199
+// MALTESE 196
+// VIETNAMESE 195
+// SLOVAK 349
+// AZERBAIJANI 189
+// PERSIAN 188
+// BOSNIAN 192
+// ZULU 204
+// YIDDISH 197
+// SOMALI 195
+// MONGOLIAN 4
+// AFRIKAANS 200
+// YORUBA 205
+// HAUSA 195
+// HAITIAN_CREOLE 192
+// KINYARWANDA 194
+// GANDA 194
+// IGBO 183
+// CEBUANO 188
+// HMONG 189
+
+
+
+// Recognized language-script combinations [73]:
+static const char* const kDeltaOctaChrome0122RecognizedLangScripts =
+ "af-Latn ar-Arab az-Latn be-Cyrl bg-Cyrl bh-Deva bn-Beng bs-Latn "
+ "ca-Latn ceb-Latn cs-Latn cy-Latn da-Latn de-Latn en-Latn es-Latn "
+ "et-Latn eu-Latn fa-Arab fi-Latn fr-Latn ga-Latn gd-Latn gl-Latn "
+ "ha-Latn hi-Deva hmn-Latn hr-Latn ht-Latn hu-Latn id-Latn ig-Latn "
+ "is-Latn it-Latn iw-Hebr ja-Hani jw-Latn ko-Hani lg-Latn lt-Latn "
+ "lv-Latn mk-Cyrl mn-Latn mr-Deva ms-Latn mt-Latn ne-Deva nl-Latn "
+ "no-Latn pl-Latn pt-Latn ro-Cyrl ro-Latn ru-Cyrl rw-Latn sk-Latn "
+ "sl-Latn so-Latn sq-Latn sr-Cyrl sr-Latn sv-Latn sw-Latn tl-Latn "
+ "tr-Latn uk-Cyrl un-Latn ur-Arab vi-Latn yi-Hebr yo-Latn zh-Hani "
+ "zu-Latn ";
+
+static const uint32 kDeltaOctaChrome0122Size = 4096; // Bucket count
+static const uint32 kDeltaOctaChrome0122KeyMask = 0xfffff000; // Mask hash key
+
+static const IndirectProbBucket4 kDeltaOctaChrome0122[kDeltaOctaChrome0122Size] = {
+ // hash_indirect[4], tokens[4] in UTF-8
+ {{0x1682b002,0x53576003,0x9df80004,0x527fe005}}, // [000] _उमराव_, _þremur_, _געעפנט_, _vatni_,
+ {{0x67c95006,0x63dd7007,0x00000000,0x00000000}}, // _zastosow, _abawon_, --, --,
+ {{0x3a4ba008,0xc3877009,0x29edc00a,0x9e1ac00b}}, // _בלילה_, _siaran_, _finitura_, _kecelaka,
+ {{0x627ef00c,0x5292500d,0x2249700e,0x23f8500f}}, // _ikinci_, _intara_, _agamba_, _palun_,
+ {{0x72d9f010,0x9b7dd011,0xb2ca4012,0xf2da5013}}, // _kuten_, _prenumer, _galleri_, _meteen_,
+ {{0x7e716014,0x83eb9015,0x94f30016,0x00000000}}, // _karaoké_, _nesta_, _shembull_, --,
+ {{0xc1833017,0xee040004,0xc1758018,0x52b15019}}, // _плате_, _ערנסטע_, _מועצת_, _mostrará_,
+ {{0xb290001a,0x0200201b,0x00000000,0x00000000}}, // _asiat_, _zikir_, --, --,
+ {{0xf2b1d01c,0x8a3e501d,0x2387401e,0x82d9801f}}, // _bendova_, _haritası_, _cierto_, _ogres_,
+ {{0x2d5dc020,0xd10aa021,0x72902022,0x53f82023}}, // _북마크하기_, _gruodžio_, _cikar_, _pakub_,
+ {{0xf2919024,0xf6d2c025,0xdb615026,0x0364b027}}, // _insan_, _ताहिक_, _सनसनी_, _podrazum,
+ {{0x53866028,0x3061e029,0x625ad02a,0x47bb502b}}, // _exora_, _विकसक_, _faller_, _objektyv,
+ {{0xbc67202c,0xd292502d,0x6b9a202e,0xf248902f}}, // _другие_, _antara_, _ekonomic, _esame_,
+ {{0xbceb3030,0xeccea031,0x39fe0032,0xe2484033}}, // _मदनमोहन_, _县级以上地方人民, _novament, _gamme_,
+ {{0xf1c92034,0x6e059035,0xd3ebe036,0x3d370037}}, // _доста_, _kendaraa, _detto_, _شکلات_,
+ {{0x627ef038,0xe3a7e039,0x53ebe03a,0x0d69203b}}, // _nyingi_, _مجموعی_, _letto_, _kulturës_,
+ {{0xf27ed03c,0x8849b03d,0xb3eb903e,0xcc7d703f}}, // [010] _ocena_, _コメント記入欄を, _besta_, _versija_,
+ {{0x32d9f040,0x6219e041,0xaf3a5042,0x8ceba043}}, // _guten_, _podnikán, _hellenic_, _pestsawg_,
+ {{0xd3eb9044,0x841a6045,0x437b1046,0x48739025}}, // _desta_, _правилам, _gyfaill_, _बेंगलुरु_,
+ {{0xa200c03e,0x92019047,0x5f914048,0x9e72c049}}, // _undir_, _ansin_, _phẩm_, _डार्क_,
+ {{0xba53b04a,0x32da904b,0x0e59804c,0x2e42a04d}}, // _menerima_, _amplify_, _pembinaa, _カタログ情報を_,
+ {{0x03ea604e,0xe25a6023,0x0ad2004f,0xa7125050}}, // _multe_, _mulle_, _gratuite, _construç,
+ {{0xf2918051,0xb2495052,0x5dd4e053,0x6394e054}}, // _kiran_, _iqembu_, _verkocht_, _naiste_,
+ {{0x02018055,0x52240056,0x5c09c057,0x85e3604d}}, // _jirin_, _olika_, _sempena_, _広告掲載について_,
+ {{0x43eb903e,0x00000000,0x00000000,0x00000000}}, // _besti_, --, --, --,
+ {{0xa25b9049,0x32245058,0x00000000,0x00000000}}, // _jesli_, _kelke_, --, --,
+ {{0x15011059,0xfa647054,0xa99c802c,0x52d7105a}}, // _espesyal_, _lääne_, _последст, _murongo_,
+ {{0x62d9f05b,0x54b8205c,0xc98cb05d,0x13f8305e}}, // _zuten_, _लेटेस्ट_, _abahinda_, _kumuha_,
+ {{0xb25a605f,0xdb76d060,0xa247b061,0x0845c062}}, // _julle_, _àyọkà_, _bendruom, _главна_,
+ {{0x73ea6063,0x63258041,0xf3219064,0xde482065}}, // _ngota_, _tiscali_, _risya_, _البطولات_,
+ {{0xa8c4e054,0x5d54c066,0x32feb02d,0x553c0067}}, // _arvestad, _आवागमन_, _matahari_, _तब्बल_,
+ {{0xd2935068,0x6201c03a,0x92912069,0x115d9018}}, // _가능합니다_, _invia_, _hnyav_, _situatio,
+ {{0xccf0e06a,0x9ec0002e,0x04cbf06b,0x394e706c}}, // [020] _ملاحظہ_, _nebezpeč, _चदरिया_, _abayọri_,
+ {{0x527ff06d,0x4291200c,0xe2c6406e,0xc200503b}}, // _jaunu_, _siyah_, _abalaye_, _cilit_,
+ {{0xc80a706f,0xe60be070,0x995be070,0x78e62071}}, // _президен, _معاہدے_, _معاہدہ_, _ефтин_,
+ {{0xeebc1072,0x10485073,0xfc6c1040,0xdb7c1056}}, // _aktuelle_, _naturaln, _aktuell_, _aktuella_,
+ {{0xf2919074,0x22f29075,0x9fd60076,0x00000000}}, // _kisan_, _lleihau_, _pojemnoś, --,
+ {{0xa27ee077,0xf30ac078,0x03eaf015,0x00000000}}, // _nainen_, _abanidij, _manter_, --,
+ {{0x267a9079,0x04ddd07a,0xa822907b,0xcf53d07c}}, // _अभिप्राय_, _французы_, _অতঃপর_, _домашни_,
+ {{0xf8a7d07d,0x9320007e,0x4f94607f,0xf2d87035}}, // _फोटोफिचर_, _abiye_, _апелулуй_, _panen_,
+ {{0xce352080,0x53ba1081,0xa230b065,0xd006406c}}, // _skladova, _виноград, _الآراء_, _adékúnlé_,
+ {{0xc6e80082,0x2291900c,0xd2901083,0xc2fce084}}, // _контроль_, _nisan_, _wahai_, _tengok_,
+ {{0x83eb9062,0x19ba3085,0x8290303c,0x0cd72065}}, // _vesti_, _privacid, _ramach_, _لعلاج_,
+ {{0x049ea07b,0x6a6cc086,0x325ab087,0x2e559088}}, // _সোশ্যাল_, _abaminis, _çelik_, _katangia,
+ {{0x2201c089,0x0b6ca08a,0xe2a6908b,0x00000000}}, // _envia_, _pantalla_, _životné_, --,
+ {{0xc3f9f08c,0xe248f08d,0x637fe08e,0xf94b708f}}, // _mutum_, _ehhovisi_, _domača_, _総合ポイント_,
+ {{0x72d9f090,0x225ae033,0x425ad091,0x142b7092}}, // _lutem_, _taille_, _ddolen_, _podarilo_,
+ {{0x83947093,0x117db094,0x2efe9020,0xa2005095}}, // _mense_, _זיווג_, _이용하시기_, _bilis_,
+ {{0x22a69096,0xea977097,0x8d30c098,0x891d004d}}, // [030] _životní_, _veličinu_, _vlastnic, _ご利用ガイド_,
+ {{0x97a6b099,0xd8b2c031,0x248fd08b,0x0c764060}}, // _protivni, _查看详细资料_, _zatvoriť_, _aforiti_,
+ {{0x427f709a,0x1b59709b,0x2b61e003,0xa2a7f09c}}, // _cuando_, _tentunya_, _regluger, _akuba_,
+ {{0xf8a4109d,0xef1f204d,0x15d5909e,0xa2905074}}, // _प्रश्नोत, _お気に入りに登録, _batangiy, _tilas_,
+ {{0x32cad09f,0x82018060,0x232120a0,0xf25a90a1}}, // _reeds_, _ririn_, _ysbyty_, _atali_,
+ {{0x617510a2,0x5e6770a3,0x2bd1e0a4,0x15e8c0a5}}, // _pročitaj_, _autorizē, _विषयक_, _rahatsız_,
+ {{0xd39490a6,0xb3eb90a7,0x82ca703d,0x529170a8}}, // _edasi_, _mestu_, _hunde_, _praksis_,
+ {{0xe22490a9,0x9b5530aa,0xf2d990ab,0xb296c0ac}}, // _svaku_, _firmalar, _museu_, _strateji_,
+ {{0xe3f8d0ad,0x729020a4,0x07b9e03b,0x9ce9e04d}}, // _budur_, _pakai_, _komuniki, _インタビュ_,
+ {{0x82d800ae,0x9ae1d0af,0xd698102c,0x232630b0}}, // _remonts_, _modelars, _контроля_, _bynciau_,
+ {{0x307af0b1,0x37253004,0x340c60b2,0xef4af0b3}}, // _besplatn, _אנטשולדי, _ngoặt_, _besplata,
+ {{0x3e5d40b4,0xb2d9a0b5,0x588880b6,0xcf5a70b7}}, // _treballa, _srpen_, _тексту_, _абсолвир,
+ {{0x47876049,0xd29030b8,0xa28cc065,0x75a9301b}}, // _सर्विस_, _wajah_, _الثمن_, _diusahak,
+ {{0x262d102b,0xc2d8f0b9,0xdd2b30ba,0xf38700bb}}, // _лінгвіст, _erger_, _acontece_, _tubrog_,
+ {{0xb8b740bc,0x72905056,0x42c5c0bd,0x93870076}}, // _kostumer_, _bilar_, _deklare_, _dobrym_,
+ {{0xdc1650a2,0xf29150be,0x920250bc,0x57ec202c}}, // _sigurnos, _theatr_, _altima_, _устанавл,
+ {{0xd25ad0bf,0xa3cef02c,0x5db4b084,0xcd910037}}, // [040] _atele_, _voivat_, _meriwaya, _ملاحظه_,
+ {{0x23ebe0c0,0xb943f0c1,0x526e001c,0xe05af070}}, // _sette_, _počasie_, _tipove_, _آرکائیو_,
+ {{0xaf2120c2,0x1108d0c3,0x4c16b076,0xa27eb0a2}}, // _esperien, _абеба_, _ofertę_, _jednim_,
+ {{0xa9e700c4,0xdb0c7089,0x8ae1f011,0x73f96058}}, // _ospraved, _possible, _kostnade, _liguri_,
+ {{0xb291e0c5,0x4c6aa0c6,0x22419006,0x22b61018}}, // _antaa_, _गुल्मी_, _मनाली_, _בסטנדרט_,
+ {{0x620070bd,0x43ce7007,0xa50160c7,0xf1d280c8}}, // _minis_, _abanirẹ_, _materjal_, _neznámý_,
+ {{0x7b5db0c9,0x5291500c,0xe2d990ca,0xe17bf0cb}}, // _navijačk, _sigara_, _muset_, _پھانسی_,
+ {{0x571200cc,0x6e46d091,0x5d2e4031,0xf7e730cd}}, // _החרדית_, _stratega, _承担一切因您的行, _रिचर्ड_,
+ {{0x23948029,0xf2d80048,0x839600ce,0x0c0730cf}}, // _langsung_, _phien_, _besser_, _partiyas,
+ {{0x6c569072,0x23ea009f,0xd6882082,0x921390d0}}, // _lettere_, _buite_, _контролю_, _mosha_,
+ {{0xb239f07f,0x5201a060,0x127ef0d1,0xc89700d2}}, // _termeni_, _pipin_, _seinem_, _verkeerd,
+ {{0x129030d3,0x425b70d4,0x39ca30d5,0xa17c30d6}}, // _cumann_, _ingliz_, _जबर्दस्त_, _रस्सा_,
+ {{0x03eb903e,0x386b30d7,0x220200d8,0x327ed0d9}}, // _bestu_, _aconsegu, _musisi_, _ocene_,
+ {{0xf248d089,0x90279004,0xb29070da,0xc2d98029}}, // _premi_, _טעקעס_, _dinas_, _surel_,
+ {{0x86e710db,0x995e10dc,0x84862017,0x7842802c}}, // _circunci, _transmet, _stručnja, _teollisu,
+ {{0x0ab0c0dd,0x7486c0de,0x4c7630df,0x129110e0}}, // _संस्कृति_, _निशानी_, _दुल्हन_, _nicaea_,
+ {{0xc7f41055,0x69aaa0e1,0xb7b1301c,0x82d570e2}}, // [050] _islaamig, _दिग्विजय_, _izostavi, _seromba_,
+ {{0xb387f0e3,0xda5f2060,0x7b5520e4,0xf7cad0e5}}, // _nzuri_, _àròko_, _अप्लिकेश, _vanskeli,
+ {{0x5d47d06a,0x5d85d020,0x9e9e30e6,0xbb661008}}, // _دراصل_, _카테고리의_, _erstellt_, _הסטטוס_,
+ {{0xe341707e,0x6b051049,0xdea3b056,0x205e0018}}, // _diferan_, _अनुभाग_, _intresse_, _וורדפרס_,
+ {{0xe6a220e7,0x53a3a0e8,0x32da70e9,0x03f99084}}, // _институц, _hoppa_, _noregi_, _kusut_,
+ {{0x6a7ae04d,0xfc8b80ea,0x00000000,0x00000000}}, // _エグザイル_, _저작권정책_, --, --,
+ {{0xe2a710eb,0x5e96c070,0xf9cda033,0xcc1970ec}}, // _часова_, _برسوں_, _interdit, _निश्चिन्,
+ {{0x23eae0ed,0x43f8f037,0x0200f078,0xe3f960ee}}, // _contoh_, _gugur_, _adiitu_, _siguri_,
+ {{0x389b2047,0x2c6050e8,0xefaf50ef,0x3b0a308f}}, // _مراسيل_, _platser_, _csillago, _下記のボタンを押,
+ {{0xbb62a0ca,0x9d5f9030,0x829020a9,0x171da0f0}}, // _भयानक_, _टर्निंग_, _takav_, _actuació,
+ {{0x8212b0c2,0xa29040f1,0x7b6c6091,0x1c6220f2}}, // _anche_, _ramai_, _pharetra_, _liputan_,
+ {{0x025b70f3,0x13076057,0x12b3708d,0x4213204f}}, // _avalia_, _bimbang_, _ubudala_, _fiches_,
+ {{0xa2905037,0x220ec045,0x727ef0f4,0xa02d604d}}, // _salah_, _хабар_, _ikindi_, _コミュニティ_,
+ {{0x63f8a01d,0xf36f20f5,0x820020f6,0xb3a260f7}}, // _kabul_, _hangtot_, _dakit_, _loopt_,
+ {{0x02003055,0x881b706c,0x00000000,0x00000000}}, // _kamida_, _ajialàrà_, --, --,
+ {{0x8290200c,0x740f60f8,0xec6a8076,0x522400f9}}, // _fakat_, _horretan_, _systemy_, _klike_,
+ {{0x43f450fa,0x93eaf0fb,0xa8db9031,0xe2001024}}, // [060] _akutte_, _baitku_, _胶南市城建局_, _sahil_,
+ {{0xb2d02008,0xcae1507b,0x6fb2b018,0x79c89070}}, // _המגזין_, _হেফাজত_, _ויטמינים_, _automata_,
+ {{0x12d800bb,0x00000000,0x00000000,0x00000000}}, // _thiel_, --, --, --,
+ {{0x598ae0a2,0x234fc0fc,0xc47ef07b,0x4af36056}}, // _sviđa_, _estekak_, _এইচএসসি_, _konstate,
+ {{0xbe1590fd,0xfaa9f0fe,0x00000000,0x00000000}}, // _активног, _মুন্সীগঞ, --, --,
+ {{0xf5a5e0ff,0x46948069,0x42084047,0x6dbbe0a2}}, // _niektorý, _cawmseej_, _جوالات_, _podršku_,
+ {{0xab9e003c,0xd4ecb03d,0xcbf540b4,0x4291d0a0}}, // _गुरुवार_, _ファッション_, _espectac, _enwau_,
+ {{0x92cf00f9,0x00000000,0x00000000,0x00000000}}, // _volonte_, --, --, --,
+ {{0xe3ebe056,0x5883c033,0x346ed100,0x7378d101}}, // _detta_, _您现在的位置_, _храме_, _kimanin_,
+ {{0x8291e0bd,0x427f0037,0xca8150ad,0x33788102}}, // _antan_, _nyanyi_, _assosias, _gunakan_,
+ {{0x74862103,0x62909104,0x02020105,0x22494106}}, // _परमाणु_, _pakati_, _musiqi_, _vremea_,
+ {{0x4eafb03d,0x5202503f,0xfe523067,0x183cb107}}, // _知的財産本部_, _notika_, _dikataka, _прочитав_,
+ {{0xf2d51108,0xd38c2071,0xe20070e9,0x6200a035}}, // _sellest_, _лутер_, _vinir_, _bibit_,
+ {{0x0c766109,0xf379210a,0x89cff10b,0xe3f980a4}}, // _første_, _jabatan_, _stretnut, _buruk_,
+ {{0xf201410c,0xb157010d,0x524f80e7,0x92d830c4}}, // _breith_, _उपलब्धता_, _оставите_, _odmenu_,
+ {{0x26ce310e,0xa25950c2,0x92ea710f,0xac770076}}, // _detaljni, _particol, _petikan_, _damskie_,
+ {{0x22360110,0x9bccb021,0xd5ab00cb,0xefd05061}}, // [070] _ujiji_, _studijų_, _گھنٹوں_, _maždaug_,
+ {{0xd2caf0a6,0x03f880aa,0x7f5f30b9,0x00000000}}, // _kuidas_, _hukuka_, _hospitaa, --,
+ {{0xb3f98037,0x421c5084,0x4347d0c4,0x5f28e084}}, // _buruh_, _tauhid_, _balenia_, _cintaila,
+ {{0x42d9f081,0x89787111,0x1e5d8112,0xfed9603c}}, // _etter_, _velikost, _traballa, _kulinarn,
+ {{0xd7c2d08a,0xb2cac0ce,0xd290b113,0x2291e0bd}}, // _বাইরে_, _melden_, _licas_, _mitan_,
+ {{0x5d7f808d,0x42b600d4,0xf64060fe,0xe9cf2068}}, // _ngamunye_, _muscat_, _টিপস্_, _momentee,
+ {{0xc2a6c114,0xaf91f115,0x337950aa,0x0291e116}}, // _member_, _condenar_, _antalya_, _xitaa_,
+ {{0xa3ea0117,0x89c9f07f,0x03f98037,0x537d40f9}}, // _muita_, _germania_, _guruh_, _etranje_,
+ {{0x960bc118,0x02902119,0xa1c3c106,0x59a34047}}, // _вчера_, _arkay_, _полония_, _limistéa,
+ {{0x53fa703b,0xf3874038,0x72904046,0x2200411a}}, // _forumi_, _sheria_, _camau_, _mamit_,
+ {{0xa584f020,0xf2ca003a,0x6481611b,0xdf7b4065}}, // _브라우저입니다_, _guida_, _दरवाजा_, _مفاتيح_,
+ {{0xf2cae11c,0x334ed11d,0x62018110,0x6d3ef04d}}, // _tindak_, _posebej_, _miria_, _初めての方は_,
+ {{0xcc618045,0x0851811e,0x637f8113,0xaa9770b3}}, // _sverige_, _sveriges_, _hahaaaa_, _veličina_,
+ {{0x63ebe03e,0x9d56011f,0x23003038,0x00000000}}, // _setti_, _लैंगिक_, _kijamii_, --,
+ {{0x299910b7,0x048910b7,0x568130d5,0xdb775120}}, // _интерн_, _интерв_, _जिहाद_, _explicac,
+ {{0xa2d98121,0xa3ea605e,0xdd2600c3,0xcd7f50a4}}, // _arren_, _multo_, _europene_, _विश्रांत,
+ {{0x22902122,0x622670b6,0x3eb800cd,0x0394010c}}, // [080] _zakaj_, _verkar_, _असाधारण_, _coise_,
+ {{0x92004123,0x3e70d06b,0x53eaf003,0x23ea7124}}, // _damit_, _गिद्ध_, _vantar_, _runta_,
+ {{0x8e33507b,0x48c7b125,0x5f5db106,0xd3eb8126}}, // _উচ্চশিক্, _मणिपुर_, _raspunde_, _kerti_,
+ {{0x222490a2,0x027ec0bd,0x9e83c0d3,0xd200905d}}, // _svaki_, _anonse_, _عائشة_, _wakiri_,
+ {{0x4290a08c,0x83ea7127,0x3e0f2128,0x8b1fc055}}, // _dibar_, _xunta_, _comentea, _ramadaan_,
+ {{0x0d5fd129,0x59c1712a,0x92b400ce,0xb2240081}}, // _विक्रमाद, _filefact, _klick_, _klikk_,
+ {{0xa248905d,0xc200b08c,0x019760c5,0x8344a0cc}}, // _maama_, _hadisi_, _телефоны_, _מאַטעריא,
+ {{0x4386611e,0x42c850cb,0xfa43912b,0x8f80212c}}, // _gjort_, _mindenki_, _konkrétn, _prochain,
+ {{0x77e7912d,0x4f4a404f,0x130ff12e,0x63b06044}}, // _स्वर्ण_, _personna, _bolesť_, _elevado_,
+ {{0x0a35012f,0xbba31086,0x4e1c70a9,0x00000000}}, // _straitéi, _imihango_, _prekršaj, --,
+ {{0x71862130,0x526c5131,0x2096d132,0x6f6f303f}}, // _економск, _selon_, _چالاکی_, _komandas_,
+ {{0xf6de503b,0x91d8c133,0xf7e3d01a,0x00000000}}, // _futbolli, _ainglibh_, _позиции_, --,
+ {{0x1f9c604d,0x1631a134,0xe3870090,0x34622135}}, // _このブログをリン, _economai, _librin_, _stredisk,
+ {{0x225a908d,0x8b61c025,0xf75d9008,0x02b400ce}}, // _ngale_, _समझने_, _גבינת_, _blick_,
+ {{0x7200b136,0xda00a065,0x556ff137,0x19040138}}, // _raditi_, _النغمات_, _posvetio_, _comataid,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x62249139,0x3b25b13a,0x4f65b099,0x05bc513b}}, // [090] _kwake_, _priprema_, _pripreme_, _国家外汇管理局_,
+ {{0x1200c03b,0x23207074,0xdc77303c,0x00000000}}, // _midis_, _sanyi_, _konsole_, --,
+ {{0x551140db,0xbc53b13c,0x93ea613d,0x6e959008}}, // _cobrando_, _реализов, _grote_, _עוררו_,
+ {{0x427f413e,0x81c3c0c5,0xd2cae0f5,0xb0429076}}, // _svensk_, _помощи_, _xvideo_, _pomiędzy_,
+ {{0x02b4e13f,0x1ce050c3,0x8c339018,0xf7c1b0fe}}, // _vincom_, _унижений_, _גאליס_, _অপহরণ_,
+ {{0xa37d40ff,0x93800092,0xe2903140,0xb2905141}}, // _zdravie_, _vopred_, _sajam_, _walau_,
+ {{0xf239f142,0xe2904143,0xc200505e,0x0200c038}}, // _termini_, _samal_, _galit_, _halisi_,
+ {{0x5ec3a093,0x9c02c034,0x69c40054,0x1559c021}}, // _christus_, _пълен_, _moderaat, _знакам_,
+ {{0x48d480ca,0x800ee144,0x2ff2407b,0xb2a77046}}, // _चर्चित_, _potvrden, _থাকছে_, _arabeg_,
+ {{0x5018c140,0x295410cc,0x52c20074,0x935f304f}}, // _preporuč, _גרופּע_, _italiya_, _origine_,
+ {{0x22360145,0xc27a804d,0xfc66a146,0xffd31147}}, // _dvije_, _氏名又は名称_, _hurtigt_, _relihiyo,
+ {{0xc413c034,0x425ad0d7,0x82cae148,0x526c4149}}, // _човека_, _taller_, _leiden_, _nemoc_,
+ {{0xa25a914a,0x0290414b,0x62027016,0xbc1a1002}}, // _suala_, _tamam_, _arriti_, _energias,
+ {{0x2060508f,0x9a27f14c,0x00000000,0x00000000}}, // _このカテゴリから_, _बाईबिल_, --, --,
+ {{0x7e3ff088,0x6a65c13b,0x4ad8e072,0xb2fd00bc}}, // _pananamp, _有难题就提问_, _umiddelb, _bhagat_,
+ {{0xf3940117,0x8c5d20f6,0x6e03f14d,0x00000000}}, // _olisi_, _bertako_, _формату_, --,
+ {{0xe27e014e,0x22907100,0x6af7b0d4,0xe8caa061}}, // [0a0] _aming_, _manau_, _konsumat, _статусе_,
+ {{0x72330011,0x728e30a9,0x9638714f,0xeaf7b128}}, // _exempel_, _svakako_, _mwenyeki, _consumat,
+ {{0x9290b150,0xb201e151,0x92005152,0xd6bad153}}, // _indawo_, _sitio_, _kalim_, _javascri,
+ {{0xc39f311c,0xe2fc7154,0x5b89006e,0x12fc00e9}}, // _اصناف_, _lengo_, _aisinipo_, _leigu_,
+ {{0xf0cce07b,0x0395709f,0x88d52008,0x38751155}}, // _হাদীস_, _laaste_, _לאנדרואי, _ahikọrọ_,
+ {{0x1940d156,0x827f4134,0x6318d157,0x85759158}}, // _počinje_, _brenin_, _pabcuam_, _agologaà,
+ {{0xa248603b,0xf992e011,0xd0a9e03a,0x42a6914f}}, // _akoma_, _verklige, _пропуска, _ajabu_,
+ {{0xce627114,0xb3dc0159,0x135f115a,0xba709147}}, // _practice_, _afiwe_, _jongste_, _magagawa_,
+ {{0xda7b000b,0xa907b15b,0x7942512c,0x00000000}}, // _औरंगाबाद_, _redakteu, _quartier_, --,
+ {{0xd68c615c,0xf39400d3,0x9a5f8019,0xf230115d}}, // _सलवार_, _toisc_, _pregunta_, _khampha_,
+ {{0xa15520a2,0xe683b143,0x32249074,0xa6b40024}}, // _mogućnos, _सोनार_, _alaka_, _abdullay,
+ {{0xfecc515e,0x225a615b,0x2b7480ea,0xb2fc515f}}, // _papildus_, _bulle_, _개인정보보호_, _papildu_,
+ {{0xd291f160,0x72da505a,0x111c10c5,0x827f700e}}, // _anuas_, _gutera_, _основани, _mpanga_,
+ {{0x0a83507b,0x4c23103d,0x9d2310c0,0x00000000}}, // _স্বেচ্ছা, _mulighed_, _mulighet_, --,
+ {{0x123c6068,0x53e73161,0x42e30162,0xc2fcb163}}, // _감사합니다_, _måtte_, _संदीप_, _odraziti_,
+ {{0x12907105,0xefe9e0a4,0xd2fc2052,0x58c6e164}}, // _manat_, _बाजारात_, _comitis_, _innehold,
+ {{0x425a00dc,0x3f47707b,0xd413206a,0x22639165}}, // [0b0] _prill_, _একান্ত_, _والدہ_, _definiti_,
+ {{0x6c51912a,0x368c7166,0x6f38d167,0x52dc214d}}, // _taktika_, _तलवार_, _देखिन्छ_, _партнера_,
+ {{0x1c7af168,0x72e8711d,0xfbd8c0c5,0x0b0af169}}, // _vlasnik_, _higiena_, _сначала_, _vlasnika_,
+ {{0xa26c1164,0xdc80c062,0x5aea8034,0xb290c160}}, // _behov_, _девет_, _trattame, _ualach_,
+ {{0x125bf091,0x739490c3,0xf838112d,0xeb54616a}}, // _teulu_, _clasa_, _कुरुक्षे, _लालीगुरा,
+ {{0x9067416b,0x31588113,0x7447e16c,0x00000000}}, // _झांसी_, _javouhey_, _анексязэ_, --,
+ {{0x42dab008,0x62da6076,0x9d6a108b,0x49cbf11d}}, // _october_, _strefa_, _atomique_, _namestit,
+ {{0x4c7120de,0x0a81616d,0x53209104,0xf318e147}}, // _बाल्टी_, _impormas, _abaye_, _salitang_,
+ {{0x966d316e,0x820020ca,0xca0f716f,0x8200d0c3}}, // _الجسم_, _komise_, _zapadnoe, _cheie_,
+ {{0xee73603c,0x337e4170,0xed60b13b,0x13dcb00b}}, // _पार्क_, _corgoň_, _香港紫金碳雕加盟, _jadwal_,
+ {{0x8d8e4171,0x027e0150,0x425a607c,0x6c5c607b}}, // _encontra, _emine_, _sulle_, _সমগ্র_,
+ {{0x52f3a172,0xc79e2173,0x37dd3122,0x43eaf037}}, // _ukristu_, _трэба_, _obravnav, _banter_,
+ {{0x02d8e0b4,0x62fc7174,0xd0dda004,0x6dda9140}}, // _manera_, _tengo_, _חשובע_, _naglašav,
+ {{0x5e68c175,0x2c748176,0xc3552177,0x8394e08f}}, // _menciona, _रोल्पा_, _calendr_, _uanset_,
+ {{0xe7d0c15d,0x62b67178,0x4f445179,0x9200a00c}}, // _congdong, _tercer_, _энциклоп, _tabii_,
+ {{0xca830056,0xb2e3003b,0xfed3003b,0x7489017a}}, // _familjen_, _familje_, _familjes_, _присъеди,
+ {{0x8a176054,0xa2902099,0xee6ac081,0x7a920004}}, // [0c0] _हार्डकोर_, _iskaz_, _фонду_, _אַדרעס_,
+ {{0x2f198175,0x62904100,0xa3bc3065,0x2c28403f}}, // _располаг, _namai_, _ولكنه_, _plastmas,
+ {{0x327f7175,0xc2d9f0ca,0xb248917b,0x9301215d}}, // _quando_, _autem_, _shami_, _qmobile_,
+ {{0xd201e124,0x54866026,0x8e68317c,0x7290e102}}, // _intii_, _दोबारा_, _अनुभूति_, _sifat_,
+ {{0x95f9501a,0x77573173,0x22ef4134,0x00000000}}, // _напряжен, _негры_, _diffyg_, --,
+ {{0x03f4017d,0x6201f17e,0x9316f007,0x937a0087}}, // _gister_, _vizita_, _adabori_, _buradan_,
+ {{0x725a417f,0xba08e0c5,0xb2468035,0x7c75f180}}, // _jumla_, _salasana_, _चिखली_, _स्प्रे_,
+ {{0x36313181,0x42905037,0xaad0211c,0xd6ab3182}}, // _お問い合わせ_, _malah_, _برزیل_, _езеро_,
+ {{0x285f0183,0x52240184,0x949c90a7,0x023b90a2}}, // _empresas_, _aliko_, _naročilu_, _ostvaril,
+ {{0x8ad710d4,0x5da78185,0x7cf710d4,0x52eb7153}}, // _letterat, _हॉटशॉट्स_, _letterar, _medical_,
+ {{0x7290b0bb,0x90b15044,0xa473213b,0x56915044}}, // _ascas_, _gráfico_, _网站或个人未经本, _gráfica_,
+ {{0x3c59f185,0x4cd2b186,0x0290e187,0x9d9b0042}}, // _listrik_, _पोटेन्शि, _janari_, _taylorma,
+ {{0x839540ff,0xb290e0e6,0xe3f45188,0xb7b65011}}, // _presne_, _danach_, _nyttig_, _utrustni,
+ {{0xe6975072,0x02018035,0xd3a23189,0xf3f9803f}}, // _offentli, _kirim_, _tampak_, _kurus_,
+ {{0xcb765089,0xd379018a,0x4dbbf18b,0x4da2018c}}, // _declarac, _gagasan_, _argentín, _prensipa,
+ {{0x0300500e,0xe30e4174,0x43ea715b,0x62161018}}, // _kulaani_, _estamos_, _punte_, _אספנות_,
+ {{0x92d800f5,0xa290c037,0x801f50f8,0xb04c318d}}, // [0d0] _chieu_, _wadah_, _handiago, _politisk_,
+ {{0x524850e1,0x42025120,0x5e923050,0x725a018e}}, // _filmy_, _antiga_, _потреба_, _quill_,
+ {{0x5f453105,0xef2bc07b,0x930f518f,0x09ec4190}}, // _formalaş, _কল্পনা_, _fasahar_, _kabineta_,
+ {{0x8108a065,0x224890dc,0x00000000,0x00000000}}, // _مذكرات_, _dhamo_, --, --,
+ {{0x02d80191,0x42da610b,0x1810812f,0x99e2c065}}, // _okien_, _streda_, _ardchaig, _شاهين_,
+ {{0x9292012b,0xd3001074,0x5291801c,0x625a606e}}, // _strany_, _makamai_, _biram_, _agolo_,
+ {{0xd2d5202e,0x5b552011,0x82fc7192,0xc2910088}}, // _kontakty_, _kontakta_, _penge_, _maaari_,
+ {{0x825a6074,0x6db3a173,0x2061c02a,0x29d510b7}}, // _kulla_, _negalite_, _стати_, _аджитатэ_,
+ {{0x12919193,0x9e4ac194,0x36832195,0x1c35307b}}, // _nisam_, _статей_, _послуги_, _সহায়তা_,
+ {{0x36f78196,0x17ada0d4,0xf486c017,0x95258197}}, // _परिवहन_, _direttiv, _смена_, _जोड़्न_,
+ {{0xdf1c90b7,0x18c5312f,0x92e90088,0x00000000}}, // _формулат, _spreagad, _pagitan_, --,
+ {{0xa2d80048,0xd9801098,0xd31be190,0x00000000}}, // _thieu_, _भूकम्प_, _vasarā_, --,
+ {{0x32b2802c,0x5387f0e3,0xd700f0d3,0xe39520aa}}, // _voidaan_, _uhuru_, _struchtú, _neyse_,
+ {{0x1b07d045,0x62631065,0xd3f47198,0xf3a35199}}, // _тиждень_, _تهنئة_, _unutar_, _riepas_,
+ {{0xe7dec113,0x620110bd,0xb2da602e,0x1c6b400f}}, // _epiphani, _vizit_, _hotely_, _कुर्मी_,
+ {{0x93e5d19a,0x1bd27143,0x53ea905a,0x4dd2d01a}}, // _către_, _बांये_, _itatu_, _стиле_,
+ {{0xf92d7020,0xa18800a3,0x6e38e095,0xd0f9204d}}, // [0e0] _자바스크립트를_, _festivāl, _karapata, _クショップ_,
+ {{0x13877065,0x234c7008,0xe27eb0a2,0x7c85e069}}, // _tharla_, _several_, _jednoj_, _nujtxeeg_,
+ {{0xf6527030,0xc25a903a,0xae30c19b,0xf378403c}}, // _बाँधे_, _quale_, _langgana, _badania_,
+ {{0x22fcd19c,0x02d88158,0x42e8e19d,0x8290c05d}}, // _seega_, _kekere_, _pilihan_, _balaga_,
+ {{0x6290719e,0xe3e5519f,0x1c6731a0,0xd2f76158}}, // _kanak_, _mətni_, _kultuur_, _akiidah_,
+ {{0xd290214e,0xf3f8f133,0xea8cd057,0x9c673013}}, // _lakas_, _dhiubh_, _sayyidat, _cultuur_,
+ {{0xc2de5105,0x7324f160,0x50062070,0x73eaf1a1}}, // _dilində_, _nascadh_, _ٹریفک_, _hantar_,
+ {{0x899d519b,0xeaf350a2,0xc8b47004,0x00000000}}, // _ويرايش_, _ugostite, _באָריס_, --,
+ {{0xa33a51a2,0xe3f8d0bb,0xd3ea917b,0xb290a18f}}, // _وردپرس_, _rieux_, _atatu_, _dabam_,
+ {{0x2a75c0d5,0x2248d1a3,0x085981a4,0xb9b7702a}}, // _उन्नाव_, _czemu_, _upozorav, _класифік,
+ {{0x92d801a5,0x3ceed01a,0xf2da60fc,0x7771f0ca}}, // _thiet_, _среди_, _berean_, _ब्रह्माण,
+ {{0x72ca70de,0x5b7bd060,0x63f890d8,0x00000000}}, // _kunda_, _adesanya_, _sekutu_, --,
+ {{0xb2b6a0b9,0x2c2431a6,0x69ead1a7,0x5d1b61a8}}, // _verdere_, _फ्रेवुअर, _vergange, _porodice_,
+ {{0x92ca71a9,0x65b85031,0x397e21aa,0x05d0b19b}}, // _munda_, _构成犯罪的_, _групе_, _پیشخوان_,
+ {{0xc73f00c5,0x727eb1ab,0xb26c20a2,0xb0e41094}}, // _оставить_, _niente_, _nekoj_, _באריכות_,
+ {{0xc2caf1ac,0xf6e7603d,0xa212b033,0x96213134}}, // _bandar_, _人が役に立つと評, _fiche_, _ewropeai,
+ {{0xf3a381ad,0xb39491ae,0x00000000,0x00000000}}, // [0f0] _corpo_, _clase_, --, --,
+ {{0x0387718d,0x726c205f,0x4ca431af,0x737841b0}}, // _starte_, _gekom_, _интервен, _zadania_,
+ {{0xd20110f4,0xc2902084,0x89b8e06f,0x82d880b9}}, // _kabiri_, _zakat_, _татуиров, _sekere_,
+ {{0xca0f81b1,0xd513c07b,0x2c12403b,0x0b22403b}}, // _hahahaaa_, _নোয়াখাল, _kompani_, _kompania_,
+ {{0x43806077,0x7c27b0cc,0xa24a01b2,0x2b077003}}, // _suurin_, _נאַציאָנ, _kasmas_, _samskipt,
+ {{0x33ead1b3,0x07ed61b4,0x2b6fa03b,0x629031b5}}, // _frete_, _kapanlag, _momentin_, _samada_,
+ {{0x2927e060,0x127f71b6,0xb20011b7,0x2476b070}}, // _ọpọlọpọ_, _slanje_, _zahir_, _hirtelen_,
+ {{0xff3021b8,0x00000000,0x00000000,0x00000000}}, // _редакции_, --, --, --,
+ {{0xce481171,0x3f9b11b9,0xa3eb71ba,0x73a35173}}, // _capacida, _हस्तकला_, _reatha_, _liepos_,
+ {{0xb18621bb,0xf341b15d,0xb2aad19b,0x9140c090}}, // _пальма_, _pokezoo_, _پروتئین_, _pretendo,
+ {{0xd1c371a4,0xa6abf035,0xfb354004,0x13487004}}, // _postupak_, _सणासुदीच, _האלבערשט, _מערדער_,
+ {{0x01e6b0cd,0xf34fa03b,0xb2b9e164,0x2eb58020}}, // _टिहरी_, _komenti_, _endelig_, _gebracht_,
+ {{0x3ff011bc,0xb3173043,0x022200ef,0x3f9ec1bd}}, // _мерки_, _concile_, _استفتائا, _محظوظ_,
+ {{0xb2d83033,0x489e401a,0x8290205e,0x00000000}}, // _samedi_, _закладки_, _wakas_, --,
+ {{0xd290c093,0x727ee1be,0x4642b04e,0x92919102}}, // _nadat_, _tannin_, _местоиме, _kisah_,
+ {{0x2c6ad0e1,0x1051301a,0xa2ca71bf,0x0a86803c}}, // _किन्तु_, _истории_, _fundi_, _europejs,
+ {{0x690a5055,0x12908154,0xe3ead18c,0x3a8cb0d1}}, // [100] _shabakad, _jakaya_, _arete_, _springen_,
+ {{0xac754024,0xf3f8d0bd,0x302b9082,0xb2cad02a}}, // _deputat_, _abouti_, _школах_, _brede_,
+ {{0x825b71c0,0xe3dcd1b2,0x62cae1bf,0x480730fe}}, // _italya_, _brower_, _mendon_, _সাইবার_,
+ {{0xf2cad0b9,0xb6d240b0,0x1212d064,0xce7341c1}}, // _vrede_, _morgannw, _zhohir_, _तटस्थ_,
+ {{0xe3f471c2,0x827e6069,0xc5d99071,0x2400a065}}, // _nostra_, _hmong_, _инверс_, _الهجرة_,
+ {{0x13ead0f9,0x4e7141c3,0x125a919e,0x69ae5006}}, // _trete_, _अगस्ट_, _kuala_, _handlowe,
+ {{0xc29550ec,0x0291c039,0x22aad065,0x00000000}}, // _probleem, _divat_, _مسألة_, --,
+ {{0xc3ea60ca,0xa2ca91c4,0xe3ea90e6,0x62e971c5}}, // _proto_, _stadt_, _statt_, _dirinya_,
+ {{0x027ee076,0xa29021c6,0x569da054,0xa081d132}}, // _opinia_, _zakar_, _direktii, _مضطرب_,
+ {{0xf29f7037,0xbf282089,0x1638e0c5,0xf27e61c7}}, // _sarjana_, _expressa, _perintei, _omong_,
+ {{0x4f5f50b2,0x94b631c8,0x6e6d302b,0xfa151073}}, // _uppppppp, _खिचडी_, _паміж_, _redakcja_,
+ {{0x717721c9,0x65be2175,0x927e61ca,0xc26c5149}}, // _место_, _друго_, _imong_, _celou_,
+ {{0x63f431cb,0xf2fc910c,0x39c491cc,0x1e6f1150}}, // _posté_, _beaga_, _pemerint, _ongcwele_,
+ {{0xa3442027,0x0fac612f,0xa550703a,0xec90a0a6}}, // _prodaju_, _مغامرات_, _società_, _क्षोभ_,
+ {{0x78a3e04d,0xb7c61035,0x62c9416e,0x2386d078}}, // _メロディアス_, _हळूहळू_, _míonna_, _akoran_,
+ {{0x6290c136,0xa236d1cd,0x82ca7054,0x6129a1ce}}, // _nadam_, _projet_, _tundi_, _hangsúly,
+ {{0x3eb721cf,0x825a60b5,0x0291507f,0x7cd370fe}}, // [110] _consulte_, _stoly_, _legate_, _ঝুঁকি_,
+ {{0x23f981d0,0x05df41d1,0x336e9073,0x929021d2}}, // _marun_, _telenotí, _पक्षियों_, _yakar_,
+ {{0xc26c40a2,0xfc18f013,0x8bdc91d3,0xde461004}}, // _nemoj_, _verander, _सैंया_, _עיפריל_,
+ {{0xff3ca0d6,0x6290202b,0xde71b029,0x11214163}}, // _perusaha, _vakar_, _टुर्स_, _grožđa_,
+ {{0xeff79004,0x12247104,0x5c6911d4,0xc2498179}}, // _מעניו_, _sonke_, _cerrado_, _varma_,
+ {{0xf8433020,0xb20200e2,0xa6261020,0x913ac1d5}}, // _되었습니다_, _kusini_, _미디어다음_, _abịdịị_,
+ {{0x929081d6,0xf20040a4,0x4bb12171,0xe2e861d7}}, // _makana_, _kamis_, _conjunto_, _airfon_,
+ {{0x701d10ae,0xf290c0ae,0x125b10f9,0x00000000}}, // _nacionāl, _gadam_, _biblik_, --,
+ {{0x6070807b,0xb2d98068,0x6ccc2070,0xd70d4175}}, // _একুশে_, _jaren_, _سیدنا_, _experiên,
+ {{0x13ea71a9,0x02902084,0x6dc2004a,0xd2ca70f8}}, // _muntu_, _pakar_, _meningka, _mundu_,
+ {{0x8386e051,0xdda24077,0xa2fd300a,0x8c6161d8}}, // _shirin_, _kirosana, _pilastri_, _gestuur_,
+ {{0x19bf004d,0x23e86175,0x98b8606c,0xd212c119}}, // _に選ばれた回答_, _decoraçã, _agadagod, _indha_,
+ {{0x929040f6,0x63eb9006,0xcecb8119,0x9d265099}}, // _hamar_, _testy_, _komishan, _istočne_,
+ {{0x3c6160b9,0x56341008,0xb2cae1d9,0x00000000}}, // _bestuur_, _אנימציה_, _landes_, --,
+ {{0x92d98074,0x91f9d0c3,0x625a6045,0xeae2f1aa}}, // _haren_, _министра, _fullt_, _umetnost,
+ {{0x02d8c175,0x2c2cf070,0x9bda6004,0x2062c067}}, // _menores_, _bruttó_, _פֿענצטער_, _फारसे_,
+ {{0xd7b611da,0x9956a072,0x0ccf8031,0x7c01e0cb}}, // [120] _trasferi, _millione, _如要投诉或提出意, _sokszor_,
+ {{0x63f471db,0x52905147,0x42d471dc,0x1379f08d}}, // _vostra_, _balat_, _gerddi_, _umlando_,
+ {{0x1ab6a031,0x8541807b,0x17aa80bb,0xaf26c19e}}, // _添加到搜藏_, _সাংস্কৃত, _kastosli, _penterna,
+ {{0x411b60cb,0x82d8b034,0xc66790c5,0x2200c044}}, // _سرپرستی_, _vedere_, _наверх_, _galiza_,
+ {{0x8290503b,0x82d981dd,0x13eae03b,0x60b590a4}}, // _falas_, _garen_, _tenton_, _गुरुजी_,
+ {{0x51660094,0x51001008,0x0e4ca185,0x76d2d1de}}, // _חילוקי_, _רמקולים_, _pelayana, _तारिख_,
+ {{0x6c4b102d,0x6d874007,0x13949008,0x3ae5400d}}, // _tentang_, _kerekere_, _flash_, _itsembab,
+ {{0x52904101,0xa290518e,0xe2eb2082,0x3da47098}}, // _damar_, _halas_, _abonner_, _nesouhla,
+ {{0x01192017,0x52925099,0xad84d1df,0xb200917f}}, // _технике_, _litara_, _genesise_, _majina_,
+ {{0xa2254159,0xbf2c6017,0x00000000,0x00000000}}, // _akekoo_, _bijeljin, --, --,
+ {{0x82d98013,0xf23b71e0,0x82005173,0x82d59031}}, // _waren_, _nollaig_, _dalis_, _凡本网注明_,
+ {{0x37eaa0a4,0x2edc5120,0xf61f41e1,0x99d221e2}}, // _व्यक्ती_, _racistes_, _exposici, _erregist,
+ {{0x1b1831e3,0x774ed0c3,0x12010124,0x02d6f1e4}}, // _linganis, _граве_, _nabiga_, _achlais_,
+ {{0x5200c124,0x82d840aa,0x529040de,0x8d35c0cb}}, // _kaliya_, _ahmet_, _samas_, _انٹیلی_,
+ {{0x7e4de020,0x025a903a,0x1b9a2163,0xced6d1e5}}, // _대학보고서_, _quali_, _ispisano_, _regierun,
+ {{0xe2fe6089,0x8b7451e6,0x5200d0b9,0x15015147}}, // _visites_, _регламен, _koning_, _materyal_,
+ {{0x429050fb,0xd2cae0e6,0xc291e102,0xb93a2035}}, // [130] _balas_, _beiden_, _hitam_, _अमेरिकेच,
+ {{0x739471e7,0x4affa1e8,0x735ca1af,0x622461be}}, // _konsa_, _zabranje, _партнери_, _oloko_,
+ {{0x73eae1e7,0x143ec01a,0xe2d8d0c2,0x00000000}}, // _tenten_, _равно_, _genere_, --,
+ {{0x36d710ab,0x75ea3003,0x729051ad,0xd200b0c0}}, // _descobri, _heiminum_, _falar_, _fleire_,
+ {{0x42904051,0x82005091,0x72908154,0xe27e9035}}, // _samar_, _talis_, _makala_, _emang_,
+ {{0x1c76416b,0x4a93d04e,0x4290f05e,0x1307710c}}, // _बॉम्बे_, _фонетик_, _dagat_, _aghaigh_,
+ {{0xa7c520cc,0x699880a9,0xbbcfd0a4,0xdc49d04d}}, // _אנטיסעמי, _lakše_, _श्रीराम_, _みんなへの一言_,
+ {{0x82908154,0x73f981b5,0x2ae320db,0x2200c12a}}, // _nakala_, _darul_, _populare, _eloise_,
+ {{0x925ac0e8,0x62a6c037,0x63eaf04f,0xbb40e136}}, // _mellan_, _kembar_, _faites_, _trgovačk,
+ {{0x22d9f1e9,0x8638c01a,0xe20270bc,0x19cd30f1}}, // _gazeta_, _направле, _bisita_, _seumpama_,
+ {{0x41351006,0x0455112a,0x742921c6,0x4201e057}}, // _produkty_, _produkto_, _tempatan_, _intip_,
+ {{0xac3201ea,0x5263c025,0xc200b122,0x00000000}}, // _kombinir, _आरुषि_, _bodite_, --,
+ {{0xb291e076,0xe8557126,0xf3b110b5,0x56053004}}, // _witam_, _rendkívü, _personál, _אוישוויץ_,
+ {{0x8ff97030,0x22cae0e6,0x22a771eb,0xa201e12f}}, // _विलायती_, _senden_, _ансамбля_, _titim_,
+ {{0x63a5a1bb,0x93eae0f9,0x1db940b5,0x82d9f1c4}}, // _garantij, _kontni_, _interiér_, _gutes_,
+ {{0x6c1241ec,0x33eae0d1,0xcf358003,0xc26c70b5}}, // _kompeni_, _seiten_, _meistara, _jenom_,
+ {{0xedbd11ed,0xeae640c2,0xe5ebf0b7,0x6394f1ad}}, // [140] _दुर्योधन, _strument, _докторат, _coisas_,
+ {{0x281921b8,0x2db7d1ee,0xfff810a2,0xaa19d175}}, // _компания_, _activida, _ovogodiš, _намалува,
+ {{0x18b6b033,0x92fca1ef,0x67eee04d,0xbc3ba1f0}}, // _partager_, _medidas_, _ブログトップ_, _उत्साहित_,
+ {{0x222b41f1,0x7daa7105,0x52905056,0x00000000}}, // _zoradiť_, _sistemlə, _talar_, --,
+ {{0xc394d122,0x7f5751f2,0x5be5b100,0x9c5b5054}}, // _prosim_, _जिनगी_, _kalendor, _albumid_,
+ {{0xd882d07b,0x743c20fe,0x7771c1f3,0x00000000}}, // _বাড়ছে_, _তামিম_, _व्याभिचा, --,
+ {{0xc3e361f4,0xf290c101,0x0ea6b065,0xc2d81056}}, // _सत्येन्द, _kadai_, _مانشستر_, _nyhet_,
+ {{0x8e177031,0x536f21b2,0x03869072,0x030cf0ae}}, // _中华人民共和国商, _mongkut_, _svare_, _nevarat_,
+ {{0xa387f00d,0x0a90112d,0xa4769062,0x1290c154}}, // _ukuri_, _मधुमेह_, _процеса_, _madai_,
+ {{0x42018060,0x1681612d,0x329261f5,0x00000000}}, // _iiris_, _ज्ञात_, _kirara_, --,
+ {{0xbbe53061,0xd2d9f037,0x629031d4,0x43662025}}, // _aksesuar, _muter_, _humano_, _मुहैया_,
+ {{0x926c7011,0x833010ca,0x030e51f6,0x6a09202c}}, // _genom_, _व्याख्या_, _trebala_, _говорить_,
+ {{0x5d8f516d,0xf6645054,0x72bf3039,0xd2cad05d}}, // _kailanga, _miljardi, _لمحات_, _gudda_,
+ {{0x29e23173,0x737b8084,0x13959195,0x4200c099}}, // _istorija_, _semalam_, _desse_, _volite_,
+ {{0x729070a2,0x03eb9033,0x68a3201a,0x06c8a0ea}}, // _danas_, _juste_, _гораздо_, _반송비용은_,
+ {{0xec078187,0x02ade118,0xc30c2183,0xebe40056}}, // _oneshot_, _всъщност_, _semanas_, _underbar,
+ {{0x700d808e,0xff9a31f7,0x024981f8,0x32917105}}, // [150] _življenj, _भक्तिमय_, _varme_, _rabitə_,
+ {{0xeae531f9,0x26af3021,0x00000000,0x00000000}}, // _siguient, _ісуса_, --, --,
+ {{0x538d1185,0xa28d1114,0x8290c119,0x62d84047}}, // _properti_, _property_, _kalana_, _rannóg_,
+ {{0x62ca91ec,0x1bd12021,0xae729049,0x7fa731fa}}, // _agadi_, _klientų_, _युद्घ_, _aumentar_,
+ {{0x729070aa,0xa85d91fb,0x443100fe,0x1290c1fc}}, // _sanat_, _רשעים_, _টিকিট_, _dalana_,
+ {{0xf20180f9,0x03738099,0xb7b6b07b,0x00000000}}, // _diris_, _zadatke_, _বিকেলে_, --,
+ {{0x6d5a612f,0x49a980b7,0xde17c1fd,0x00000000}}, // _leathnú_, _арэтацим, _nimenoma, --,
+ {{0xc37ca0a2,0x553ce1ac,0x82fcd1b2,0xc477a0c5}}, // _pitanje_, _sekirany, _langid_, _проекта_,
+ {{0xd06c8031,0x83ea0047,0x2ee3d0d5,0xf461d068}}, // _第二十四条_, _frith_, _पृथ्वीरा, _medewerk,
+ {{0x62b521fe,0x1fe7b01a,0xdc6970f9,0xd291919e}}, // _prodeje_, _править_, _zantray_, _risau_,
+ {{0x1b5511ff,0x1200c09c,0xf4ad60e6,0x925ae109}}, // _kinderen_, _balina_, _vollstän, _spille_,
+ {{0xec1930f8,0x00000000,0x00000000,0x00000000}}, // _gainditu_, --, --, --,
+ {{0xc29070ee,0xa2b4e19e,0x52918178,0x675380da}}, // _janar_, _kancil_, _mirar_, _पारिभाषि,
+ {{0x114631f4,0x8378e067,0x121bb0ca,0x423c9043}}, // _नालंदा_, _silakan_, _austráli, _vammeej_,
+ {{0x925a2200,0xe3167185,0xdd8da041,0xee516173}}, // _dakle_, _pribadi_, _televize_, _patarima,
+ {{0xbc651201,0x01f97020,0xae9510d7,0xe5751202}}, // _contact_, _스크랩하기_, _contacte_, _contacto_,
+ {{0x2290805a,0xe3ea203b,0x23f911e3,0x03b85086}}, // [160] _bakaba_, _fakte_, _sicula_, _yabwiye_,
+ {{0xc4fdb203,0xfbc8c1a6,0xd2a65204,0xd25a9205}}, // _позволяв, _कालीकोट_, _seljaci_, _ugali_,
+ {{0x8d19801a,0x32fe616e,0x12d9a0a0,0xca9730ce}}, // _различны, _airgid_, _capel_, _zumindes,
+ {{0x23875116,0x87899100,0x629071d7,0x8b1fc1b7}}, // _weerar_, _землях_, _sanas_, _bilangan_,
+ {{0x85a7907b,0xf387f086,0xc7df112d,0x3290702d}}, // _বুঝলাম_, _nkuru_, _छुटकारा_, _panas_,
+ {{0x837c7013,0x120180f9,0x81775104,0x2e3bc02a}}, // _locatie_, _viris_, _kulandel, _основног,
+ {{0xd320917f,0xe2018206,0xd2240163,0xd7cae0c5}}, // _mbaya_, _muziki_, _sliku_, _opiskeli,
+ {{0xa1c640c4,0x2a143173,0xb200a16d,0xa29120d8}}, // _otvorená_, _negalima_, _labis_, _hayam_,
+ {{0x83ea0002,0xf2908119,0x1f865065,0x00000000}}, // _eriti_, _arkaan_, _لاجهزة_, --,
+ {{0x0b5ed207,0x22ca919f,0x42907051,0xf2d990e6}}, // _храна_, _orada_, _ranar_, _essen_,
+ {{0xa3eb7208,0xa2ca9163,0xea5390c9,0x52907051}}, // _beatha_, _grada_, _kolegama_, _sanar_,
+ {{0x5752d008,0x4fa9303d,0xb2907051,0x4344b09c}}, // _קייטרינג_, _インテリア_, _yanar_, _mirembe_,
+ {{0x42ca9209,0x83ea920a,0x00000000,0x00000000}}, // _arada_, _arata_, --, --,
+ {{0x22a6320b,0x4cf50144,0xfdf5002e,0x438cd117}}, // _timbul_, _ostatnýc, _ostatníc, _нужен_,
+ {{0xf636a041,0x82a8807b,0x3200c0fa,0x9e7d613b}}, // _त्यहाँ_, _উবুন্টু_, _talina_, _arrivée_,
+ {{0x62d9f1ee,0x9f1e315b,0xea5be05e,0x00000000}}, // _usted_, _handelin, _pangarap_, --,
+ {{0x9290b154,0x5ba670cd,0x522670e9,0xffa0a0b7}}, // [170] _badala_, _व्यतीत_, _merkir_, _аменажар,
+ {{0xc68e1008,0x92e990aa,0x0f69119e,0x4290a074}}, // _שוקולד_, _haziran_, _sepatutn, _gabas_,
+ {{0xc4e3e07b,0x4310e20c,0xb7b5f1c4,0x22ba720d}}, // _পোশাক_, _detaily_, _startsei, _kendini_,
+ {{0xf29190ae,0x73ead144,0x65278136,0xd2da5128}}, // _visas_, _preto_, _napravio_, _cateva_,
+ {{0x996e71a7,0x8f24002d,0x8d6b4090,0x00000000}}, // _donnerst, _sederhan, _martesë_, --,
+ {{0xa2d6c061,0xc344912c,0x8ff691d5,0x33ea01ea}}, // _stilius_, _vitesse_, _divaịsị_, _priti_,
+ {{0x8c683036,0x42eab115,0xe2d9e148,0x1290a0ae}}, // _доставка_, _prensas_, _laten_, _dabas_,
+ {{0xa168b0c3,0x0395420e,0x919b4056,0x581b60ea}}, // _програму, _nyeste_, _speciell, _포카고수가될려면_,
+ {{0x8cf2c047,0xe2e820e8,0xb7ff9008,0xb706d0ea}}, // _فاطمة_, _sociala_, _ומידע_, _오스트리아_,
+ {{0x22d870c6,0x1394e179,0x00000000,0x00000000}}, // _oknem_, _toista_, --, --,
+ {{0x5201820f,0xc386c09f,0x4290a119,0xfdf8b020}}, // _mirip_, _storie_, _habar_, _빌라연립다세대_,
+ {{0x09a41017,0xb2fce1a1,0xfac81210,0xc37a714e}}, // _април_, _kongsi_, _dozvolje, _katawan_,
+ {{0x92cae090,0x43ea01be,0x92fc7154,0xec7e9088}}, // _vendin_, _esite_, _mengi_, _apostol_,
+ {{0xf2bb91b2,0x92247052,0x62d9e211,0x72b470ca}}, // _sagesse_, _konke_, _daten_, _konce_,
+ {{0x18597171,0xd34a90a2,0x3e2f01e1,0xb2da606c}}, // _retratos_, _između_, _células_, _aarewa_,
+ {{0x42cad080,0xf386d03a,0xa47ac045,0xd1704132}}, // _vtedy_, _avere_, _промисло, _مشتقات_,
+ {{0xf4d770cc,0x3290a212,0xd290c0fa,0xcc73e0ad}}, // [180] _אנציקלאפ, _gabar_, _balala_, _pensiya_,
+ {{0x827a413d,0x7fc231bd,0x1e57d070,0x51f88028}}, // _persoonl, _ناموس_, _کرپشن_, _huzaifah_,
+ {{0x5c6e3031,0xe2919011,0x98bed09f,0xb03ff178}}, // _我来说两句_, _visar_, _ingestuu, _seguimen,
+ {{0x42d9e0f8,0x326cf078,0x72905213,0x42786086}}, // _baten_, _jehofa_, _eblas_, _ubundi_,
+ {{0xd6b75025,0x549c90a7,0xdc520214,0xeb0ea142}}, // _obserwow, _naročila_, _amatala_, _epistola_,
+ {{0x0290a215,0xf2366122,0xd394e077,0x512d2175}}, // _dabar_, _svojo_, _poista_, _расте_,
+ {{0xb291a1bf,0x02d9e147,0x9dff504a,0x6da1b008}}, // _sipas_, _mateo_, _kandunga, _principa,
+ {{0xbb4690eb,0xf47370e7,0x229180f6,0xf3f1b0bc}}, // _privatna_, _partizan_, _garai_, _layering_,
+ {{0x0d86f04d,0x6b7d3185,0x628d60a0,0x02d8c060}}, // _明日の日経平均を, _terutama_, _archebu_, _aileto_,
+ {{0x93a39060,0xf15390c5,0xaaf5e018,0xf1696102}}, // _egypti_, _модель_, _attribut, _perintah_,
+ {{0xb3f4002e,0x5e5251c5,0x6a604143,0x36201054}}, // _postup_, _sedangka, _koolitus, _planeeri,
+ {{0xedb9707f,0xf580d03c,0x425a00f6,0xe290a11c}}, // _califica, _matematy, _saila_, _babar_,
+ {{0xd2259216,0xa2fe5217,0x243930ea,0x00000000}}, // _adabaghị_, _inrikes_, _인천광역시_, --,
+ {{0xe317a134,0xb2e681c4,0x1a82c0c3,0x8856b049}}, // _merched_, _bringen_, _алуминиу_, _मातृभूमि_,
+ {{0xd217d218,0x42d8d075,0xa3802060,0x69cf10c4}}, // _прогноз_, _teledu_, _aburú_, _nabehnut,
+ {{0xe25bf1be,0xe8311065,0x52ab1057,0x7224106a}}, // _afulu_, _الظروف_, _gendang_, _leginkáb,
+ {{0x237901a5,0xbbf451a3,0xdecdc190,0x92fdc098}}, // [190] _megafun_, _akceptac, _ministrs_, _ministr_,
+ {{0x53eb9219,0xc52de02c,0x226d8205,0xe290b1e7}}, // _cesty_, _позволяе, _meron_, _frekans_,
+ {{0xc2d9e20d,0xd06e1008,0x729260b4,0xff2a8170}}, // _zaten_, _מוצקין_, _usuari_, _rastisla,
+ {{0x3290221a,0x31b02070,0xa772f035,0x85292062}}, // _lakay_, _پچھلا_, _प्राण्या, _наступил,
+ {{0xa25a503a,0xfd86b178,0x68361008,0x0a93d182}}, // _dalle_, _setembre_, _מסמכים_, _музиката_,
+ {{0x163f01f1,0x704dc02a,0xb37a6084,0x7200c102}}, // _komentár_, _проти_, _lawatan_, _gadis_,
+ {{0xc3940047,0xa202604a,0xc3bb21ee,0x2ba920f6}}, // _poist_, _sering_, _través_, _merkatar,
+ {{0x2317021b,0x3c5c10b9,0x3291b18e,0x3af2221c}}, // _amazwi_, _bestaan_, _movado_, _imoralit,
+ {{0x9290c02e,0xf53b1031,0x22c1d1ea,0xf2fc621d}}, // _zadat_, _内容读取中_, _okolica_, _stoga_,
+ {{0x01b641da,0xcd448100,0xd38720ac,0x5290f0ff}}, // _permessi_, _венесуэл, _idarə_, _oznamy_,
+ {{0x5290c18e,0x9bf6b0cd,0x494e921e,0xc2e8e051}}, // _talaba_, _बर्खास्त_, _učlanjen, _dalilin_,
+ {{0x2709c036,0x00000000,0x00000000,0x00000000}}, // _религия_, --, --, --,
+ {{0xe04410cc,0xc25a005d,0x22c5a10c,0x29e76018}}, // _פרידמאן_, _mailo_, _bremner_, _performe,
+ {{0xf291c190,0x0d787048,0xec7ea0d4,0x5387404f}}, // _divas_, _quăng_, _kunsill_, _guerre_,
+ {{0x7907c0d3,0x5225f0f8,0x7c1d20f6,0x32919175}}, // _تعتبر_, _eduki_, _uztailar, _essas_,
+ {{0x1eb14153,0x63f1421f,0x730e604d,0x557740f8}}, // _importan, _importaz, _悩みを聞いて_, _instalaz,
+ {{0x5cdae0ff,0x92a691e3,0x42cad220,0x7586f18d}}, // [1a0] _katalóg_, _ndaba_, _valdes_, _overrask,
+ {{0xf39540e8,0x83869206,0x49e81018,0x0200a024}}, // _flesta_, _idara_, _העירייה_, _sabiq_,
+ {{0x9fe08185,0xb26c41cf,0x6248d154,0x00000000}}, // _संयुक्ता_, _temos_, _maombi_, --,
+ {{0x99ecc031,0x9f5bf136,0xe2df908e,0x937a61a1}}, // _魔兽野怪介绍_, _ponašanj, _nogomet_, _jawatan_,
+ {{0x95f2e031,0x36839117,0xd9695171,0xde1ac189}}, // _获取免费代码_, _войдите_, _similare, _diberika,
+ {{0x8aca409d,0x330d21c6,0x3c9e0018,0x62da7221}}, // _warmińsk, _pelayar_, _כוסיות_, _musela_,
+ {{0x9290a0ef,0xcfffd16c,0xa2c1c01c,0xe087913b}}, // _abban_, _аджария_, _stolova_, _并自负法律责任_,
+ {{0x08abd068,0x43ea6011,0x1e916008,0xb2905070}}, // _자기소개서_, _trots_, _abstract_, _ablak_,
+ {{0xe9e3700a,0x23fa615e,0x12637142,0x9241f049}}, // _limitata_, _datums_, _limitati_, _बहाली_,
+ {{0x3987307b,0x6290a095,0x6178813c,0x00000000}}, // _সমিতির_, _sabay_, _контроле_, --,
+ {{0x2290c1d7,0xac4b102d,0x8225f1ff,0xd23440bb}}, // _eadar_, _penting_, _leuke_, _khammee_,
+ {{0x8bd6a020,0x02927100,0x027f7045,0x59e3e1e6}}, // _광고제휴문의_, _visada_, _spania_, _редагува,
+ {{0x92cbf035,0xbd295113,0x1c91005e,0xa25a0075}}, // _studi_, _antipope_, _magulang_, _naill_,
+ {{0x0f391098,0xf3fa61be,0x2291f222,0x32da001d}}, // _milionů_, _burula_, _nhuan_, _liseli_,
+ {{0x43c05223,0xf3ea9033,0x5a2ab07b,0x7290c224}}, // _प्रस्तुत_, _santé_, _গুলিতে_, _zadar_,
+ {{0x437b90b7,0x83cf5225,0xdff9a0a4,0x34efd0ea}}, // _контрибу, _elever_, _स्मारके_, _관련사이트_,
+ {{0x32ca71e7,0x93eb9211,0x14ba416a,0xa3011022}}, // [1b0] _mande_, _erste_, _प्रेषित_, _bayanai_,
+ {{0x0201e0c3,0x368e213b,0x00000000,0x00000000}}, // _citit_, _查看该会员企业网, --, --,
+ {{0x0225903e,0x73ff021f,0x968240de,0x00000000}}, // _elska_, _sproporz, _भुलाई_, --,
+ {{0x314540d5,0x2c1db0fc,0x161180c1,0x99482076}}, // _अहिंसा_, _ullamcor, _klientom_, _सांसदों_,
+ {{0xc22ac175,0x37cf901a,0x2291a0bd,0x7c627101}}, // _добар_, _varsinki, _espas_, _mutumin_,
+ {{0x5ef3100f,0x01dcb02c,0x67ccb020,0x42fd4165}}, // _अधिका_, _картинки_, _노튼주니어_, _separati_,
+ {{0x73a41210,0x3da900a4,0x73f13166,0x7f641061}}, // _finansij, _fasilita, _श्रीदेवी_, _finansin,
+ {{0x5291e226,0xf30e5061,0x00000000,0x00000000}}, // _zitat_, _paramos_, --, --,
+ {{0x22d91052,0xd38ba02c,0x87e0c035,0x834aa19b}}, // _uncedo_, _станет_, _सावरकर_, _پروجکشن_,
+ {{0xced16020,0xb2b47221,0x02b4913f,0x1e396056}}, // _상품가치가_, _konci_, _hoach_, _relatera,
+ {{0xf843e044,0xbc53e227,0xe291d19e,0x5699c0a4}}, // _últimos_, _último_, _diwar_, _नागरिकां,
+ {{0x1b7be1b4,0xa2903117,0x661530b4,0x00000000}}, // _pangeran_, _samaan_, _ampliaci, --,
+ {{0xfc4460d7,0xfa0170e9,0x4c177100,0x00000000}}, // _serveis_, _allavega_, _buhalter, --,
+ {{0x5f02d228,0x9d85e05d,0xc5bbc116,0xe2ca709f}}, // _सिद्दीकी_, _makerere_, _bakaarah, _hande_,
+ {{0x23f8c0a9,0x336be1c5,0x1efe6061,0xdbffb01b}}, // _odluke_, _lengkap_, _elektros_, _pengajar,
+ {{0xe291e024,0x43ea7121,0x573ad100,0xf291119b}}, // _sitat_, _puntu_, _мозгу_, _wacana_,
+ {{0xd291e03f,0xc290e121,0x726c51b3,0xe290c19e}}, // [1c0] _citas_, _nafar_, _pelos_, _qadar_,
+ {{0x7201e10c,0x9561d229,0x73ea3210,0x626c7044}}, // _litir_, _mielestä, _sajta_, _nenos_,
+ {{0xcb865099,0xad265099,0x72e37153,0xa1ec101c}}, // _dovoljno_, _dovoljne_, _believe_, _najgorih_,
+ {{0x3ea18068,0xa2eca203,0x2b3580c5,0x04b300cb}}, // _부동산써브_, _notizie_, _tavallis, _jellegű_,
+ {{0x73eb9152,0x82cad130,0xc290f039,0xcc820008}}, // _gusto_, _sreda_, _magas_, _מקרקעין_,
+ {{0xa6b641a2,0xc25a012f,0xf202018c,0x620150b9}}, // _ماهواره_, _gaile_, _depite_, _poging_,
+ {{0x526c7183,0xc2f4c113,0x541d70b9,0xaa029037}}, // _menos_, _hopital_, _verstaan_, _serangga_,
+ {{0x72f0d037,0xf3a4222a,0x42cb8188,0xce542008}}, // _istilah_, _financij, _burde_, _financia,
+ {{0xeeb4e120,0xe290322b,0x1b5b7165,0x92db722c}}, // _diverses_, _komast_, _komunita_, _komunity_,
+ {{0xe2cfc1a6,0x2a240117,0xa290d1a7,0x1ad300b5}}, // _kancelář, _интеллек, _monate_, _dostanet,
+ {{0xe57521ef,0x0010e160,0x42d9f039,0x9c07b0b0}}, // _contrato_, _margaíoc, _isten_, _cyrsiau_,
+ {{0xba7b222d,0x5290122e,0xf290319e,0x59787054}}, // _prošle_, _nchai_, _kilang_, _valimist,
+ {{0xbcdca008,0x45a90041,0x5b607038,0x02637142}}, // _סמארטפון_, _formulář_, _septemba_, _nominati_,
+ {{0x50d1012f,0x8317b130,0x3eac30a2,0x00000000}}, // _الأغاني_, _тренера_, _donošenj, --,
+ {{0x627e922f,0x8408012f,0xf0ce20bc,0xc2b4b18c}}, // _umano_, _اونلاين_, _ribonucl, _cheche_,
+ {{0xd24941ae,0x20f52167,0x438740bd,0x33eaf0de}}, // _tiempo_, _अप्रिल_, _amerik_, _viitsi_,
+ {{0xef1a9030,0x1d3d3098,0xfa0000c4,0xc26cb0b0}}, // [1d0] _ओरिजनल_, _politice_, _dražba_, _ofcom_,
+ {{0x5be16191,0x6609e16f,0x70d9e193,0x23565100}}, // _navigáci, _zaboravi, _zaboravl, _adresas_,
+ {{0x7a8901be,0x2e70d0a4,0x8a01205e,0x8c69d057}}, // _karibian_, _व्यक्तीम, _mahalaga_, _jibrail_,
+ {{0xfb7e603b,0x629030a4,0x33eaf01f,0x91c6913b}}, // _profilin_, _bilang_, _saites_, _保持共产党员先进,
+ {{0x22fcd0a6,0x623ab1be,0xc857b017,0x929251e7}}, // _keegi_, _abamuru_, _културно, _estati_,
+ {{0x73940179,0x537a5230,0xcc43a025,0x32b4004f}}, // _voisi_, _izjavio_, _पनडुब्बी_, _voici_,
+ {{0x827e908d,0xd975b0dc,0x1ae84093,0xc9c84068}}, // _amane_, _kuriozit, _verdiens, _verdiene,
+ {{0xa3ea0190,0x6951f143,0xd2925142,0x93f400ab}}, // _saite_, _kvalitee, _istati_, _pistes_,
+ {{0x6c87b118,0x03ea90ed,0x5459c0db,0xf9840231}}, // _времето_, _suatu_, _objetivo_, _medijima_,
+ {{0x03dc1157,0xd29080de,0x00000000,0x00000000}}, // _nthwv_, _sakala_, --, --,
+ {{0xb26cd043,0x22d8d1b1,0x2c5b5113,0x720031c0}}, // _proofs_, _nkees_, _nomtswv_, _ailing_,
+ {{0x2290f195,0x72919232,0x73ac1039,0x53445008}}, // _dagar_, _yasal_, _tippek_, _license_,
+ {{0x2291e00c,0xf7c2f07b,0x22e090a2,0x5290f233}}, // _kitap_, _মিসরে_, _poznato_, _vagas_,
+ {{0xc65b9088,0x754d70e8,0x23ea7089,0xa7e3102a}}, // _halimbaw, _verksamh, _punts_, _чотири_,
+ {{0xf291d037,0xbc6821ae,0xb2903234,0x92ab913b}}, // _sawah_, _febrero_, _संयोजन_, _并不代表本站及其,
+ {{0x9f5bb235,0xb9d73054,0x00000000,0x00000000}}, // _mahadsan, _kustutad, --, --,
+ {{0xf3f8b057,0xdc530122,0x6145c0e1,0x93874082}}, // [1e0] _tudung_, _obstaja_, _हुकूमत_, _sterke_,
+ {{0x04721102,0xb2b46236,0x86360004,0xcb8fc06f}}, // _disebabk, _blocs_, _ייִוואָ_, _смолян_,
+ {{0xf548c025,0x1365713a,0x627f4036,0xe3a290e6}}, // _चाहेंगे_, _drugima_, _eventi_, _knapp_,
+ {{0xad19a1b2,0xa37950a4,0x02ea0237,0x5012f144}}, // _crosswor, _matanya_, _barisan_, _predpiso,
+ {{0x03ea9047,0xb83b8025,0x930fe0c2,0x10f67006}}, // _brath_, _अरबपति_, _bambino_, _आक्रोश_,
+ {{0xd89990b7,0x170ba008,0x730c61d7,0x00000000}}, // _универ_, _ההבדל_, _adabroc_, --,
+ {{0x9c67c034,0x52ea011c,0x120180de,0x42d901ab}}, // _телефона_, _warisan_, _parim_, _libero_,
+ {{0x2dd2d01a,0x2f383238,0xdc1da040,0x12018024}}, // _стали_, _दायित्व_, _aktualis, _fiziki_,
+ {{0xc26450c0,0x255830c2,0x32fc4098,0x7290122e}}, // _skulle_, _понякога_, _designu_, _nphau_,
+ {{0x837950f1,0xff37b198,0x798680cd,0x2c37a203}}, // _katanya_, _pristupa, _ताजमहल_, _димитър_,
+ {{0xd7c2f07b,0xd368a0d2,0xc2cad134,0x5740512c}}, // _মিশরে_, _vergeet_, _credu_, _accompag,
+ {{0xc5ce40ca,0xe200b239,0x6290c0a2,0x1e4070c8}}, // _प्रदर्शन, _kleine_, _dolaze_, _usnadnit_,
+ {{0x525a9080,0xaf399004,0x033f9018,0x475db23a}}, // _hrali_, _סקרין_, _במתחם_, _कुख्यात_,
+ {{0x729180a4,0x32eb10f9,0x02919142,0x00000000}}, // _jarak_, _difisil_, _qasam_, --,
+ {{0xe9a9c23b,0x11f5d1bb,0x72916074,0xb3dc900d}}, // _विद्यालय_, _субота_, _magana_, _ntawe_,
+ {{0x5fe9b0a6,0x134c323c,0x0927c025,0x00000000}}, // _मुहावरा_, _मासूम_, _आंकड़े_, --,
+ {{0x32008074,0xca15b116,0x3e7a804d,0x6359e0d7}}, // [1f0] _hakika_, _islaamka_, _アイコンの説明_, _situació_,
+ {{0xc25a9165,0x00000000,0x00000000,0x00000000}}, // _orali_, --, --, --,
+ {{0x7e4f310c,0x8291a089,0x4650c0ea,0xa2d87076}}, // _cheangla, _espai_, _전자상거래_, _rynek_,
+ {{0x21037126,0xc593a173,0x729180ed,0x00000000}}, // _munkahel, _ліпеня_, _marah_, --,
+ {{0xc864023d,0x00e1f076,0xe608a0c8,0x00000000}}, // _fernánde, _ewentual, _zdravím_, --,
+ {{0x9fd68045,0x92b4e203,0x83ac0021,0x4ecc502a}}, // _кваліфік, _clicca_, _grupę_, _основном,
+ {{0x47ad4175,0xc2ca0074,0xaf2c023e,0xd2009113}}, // _природни_, _maida_, _hantarka, _npaim_,
+ {{0xb2e8223f,0xf3ea0179,0x00000000,0x00000000}}, // _içinde_, _laita_, --, --,
+ {{0xd91b80cc,0xd39491a4,0x329120a4,0x3d89f0c3}}, // _בפֿרט_, _vlast_, _layar_, _техниче_,
+ {{0x2f63f0ea,0xefeeb024,0x88682018,0xd20101ea}}, // _참고하시기_, _partlayı, _מתאימים_, _dobite_,
+ {{0x4b2d3240,0xe96b50e6,0x00000000,0x00000000}}, // _sambutan_, _meinunge, --, --,
+ {{0x925a50e6,0xd29070e9,0x8f4ae061,0xc3007082}}, // _hallo_, _sambandi_, _laisvala, _samband_,
+ {{0x4291f183,0x43ea01dd,0x14fdc1a2,0xc25a518f}}, // _estas_, _baita_, _مجددا_, _kallo_,
+ {{0x824980e1,0x1b02807a,0x1f0721c9,0xf200b0a1}}, // _firmy_, _розуму_, _disponív, _hadija_,
+ {{0xd57fc0ef,0xb200b241,0x00000000,0x00000000}}, // _مصاحف_, _radila_, --, --,
+ {{0x7201919d,0x2ebc0242,0x22c3f017,0xf1c950de}}, // _kasih_, _opportun, _користи_, _पद्मविभू,
+ {{0x42ca0154,0xcff4d02b,0x8dd4d070,0x443e102f}}, // [200] _faida_, _techniko, _technika, _фауны_,
+ {{0xc27e900d,0xc2ca71df,0x225b5045,0x0b7c113b}}, // _imana_, _zanda_, _spelar_, _如果您没能找到需,
+ {{0x3ddcc1bd,0xf3a9505e,0x9291a187,0x00000000}}, // _ملحوظ_, _madaling_, _mapak_, --,
+ {{0x12911209,0x2225700e,0xbb0d818e,0x1deb5233}}, // _yazar_, _abakol_, _kissimme, _municípi,
+ {{0xd3f47090,0x4c53e243,0x9290f119,0xb4b15175}}, // _pastaj_, _राकेश_, _tagay_, _descriçã,
+ {{0x99fca09c,0x82927244,0xa387f086,0x450a81d7}}, // _palament, _disana_, _ijuru_, _agallamh,
+ {{0xf4a900f8,0x73f0a035,0x4393a094,0x3201919e}}, // _pasahitz, _ग्रॅम_, _ברוגז_, _tasik_,
+ {{0x626e11c0,0xdc664024,0xd5b27098,0x00000000}}, // _riport_, _tutulub_, _pronájmu_, --,
+ {{0x230c4245,0x6200c246,0x00000000,0x00000000}}, // _impamvu_, _halima_, --, --,
+ {{0x52ca7074,0xa51e10ac,0x3b7e1076,0x7d8b1154}}, // _sanda_, _ekologiy, _ekologic, _halmasha,
+ {{0xb386603e,0x1643b02b,0x3202505d,0x00000000}}, // _hvort_, _susitiki, _mitima_, --,
+ {{0xf8852108,0xf3eb90c5,0x9236c143,0x0c48004d}}, // _श्रीवास्, _musta_, _hiljem_, _エレクトロニクス_,
+ {{0x3dd22045,0x7201e247,0x4517b029,0x03ea9100}}, // _доступ_, _matiu_, _समृद्ध_, _esate_,
+ {{0xe25a4109,0x46052140,0x5682219b,0x5f02f098}}, // _gamle_, _prodajem_, _همانطور_, _लुम्विनी_,
+ {{0x5200c07a,0x93eb9248,0x32ff4122,0x13b9c061}}, // _galima_, _gusta_, _maribor_, _ліній_,
+ {{0x88409070,0xf291f1e4,0xdf4eb18c,0x62d9614f}}, // _سیکرٹری_, _astar_, _respekte_, _wageni_,
+ {{0xd166303c,0x5291c105,0x331531ce,0xb37a119e}}, // [210] _प्रतिबंध_, _davam_, _تواضع_, _pasaran_,
+ {{0x00a610cc,0x920260f9,0xfd8750d7,0x42fc31b2}}, // _עסקנים_, _merite_, _desembre_, _melikas_,
+ {{0xa3ee109d,0x5e227025,0xd2919173,0xe407e036}}, // _zakończo, _आमतौर_, _pasak_, _градове_,
+ {{0x63ac7187,0x239470d1,0xa35be00d,0x42afb044}}, // _turpis_, _sonst_, _kangura_, _módulo_,
+ {{0x0291a19e,0x00000000,0x00000000,0x00000000}}, // _tapak_, --, --, --,
+ {{0x720090e0,0xb1660094,0xd047f025,0xc2f0d10f}}, // _ncaim_, _איסורים_, _मुसलिम_, _fatimah_,
+ {{0x13eb9037,0xe8b59020,0x02369249,0xbe07208b}}, // _gusti_, _전체평가보기_, _miljö_, _dospelýc,
+ {{0x02ab10b9,0xa2e0e105,0x1d97a065,0xa2d94124}}, // _leiding_, _cənubi_, _القهوة_, _sheega_,
+ {{0xb8345008,0x3290112c,0x82c21088,0x1561a24a}}, // _מהתגובות_, _achat_, _bibliya_, _proviamo_,
+ {{0xe9a5f10b,0xcd23618c,0x6bef2172,0x374ab219}}, // _diskusia_, _konsantr, _racecour, _upozorni,
+ {{0x1593a24b,0x8c90b12d,0xe2fd804a,0x9492e102}}, // _prilikom_, _क्रोम_, _pergi_, _tinggalk,
+ {{0xe2ca919c,0x9394724c,0x73949076,0x89ea612f}}, // _saada_, _innse_, _klasy_, _مراهقات_,
+ {{0x42fd80ad,0xd9de10d4,0xdb6e21fd,0x7c6c10ea}}, // _vergi_, _multimid, _kartalla_, _actueel_,
+ {{0x33ebf05d,0x94730084,0x6f207244,0x820250d4}}, // _butto_, _pastikan_, _mendukun, _antika_,
+ {{0xbce621b1,0xa37a21c6,0x52916116,0x82b400c2}}, // _cwjvuamc, _paparan_, _lagama_, _unico_,
+ {{0x6291f0f5,0x42d9d24d,0x1e62217e,0xe00350ea}}, // _lazada_, _skynyrd_, _imobilia, _인기검색어_,
+ {{0x4fe87035,0xf394d24e,0xc32af0a2,0x325a2147}}, // [220] _वाचायला_, _pulsuz_, _prikuplj, _bakla_,
+ {{0x15144115,0x5df5312e,0xe980504e,0x3880504e}}, // _resumido_, _pridajte_, _articole, _articolu,
+ {{0x0c57424f,0x4e9741bf,0xa26e60ec,0x11e7b1af}}, // _postime_, _postimet_, _parool_, _наградат,
+ {{0x59a3f034,0xfc0450e9,0x23967068,0xa1b72017}}, // _лекарств, _vinstri_, _cursus_, _високим_,
+ {{0x721c717f,0x5feda018,0x416910a0,0xb2ca50fd}}, // _rushwa_, _מסביב_, _pennaeth_, _valde_,
+ {{0xe2ca017f,0x803400cc,0x6e16509d,0x66264128}}, // _dhidi_, _קאמאנס_, _जागरूकता_, _sistemul_,
+ {{0xb25a5054,0x877f50ea,0xb2e83133,0xc291e250}}, // _talle_, _서울대학교_, _fàilte_, _tatau_,
+ {{0x3200e241,0xca02f1cc,0x00000000,0x00000000}}, // _danima_, _tetangga_, --, --,
+ {{0x13dc0184,0x6d30e038,0x7161d185,0x9201a099}}, // _ariwe_, _hatimaye_, _pengelol, _ispit_,
+ {{0x32ca7251,0x62925006,0xf3797141,0x6b5280fd}}, // _cando_, _latach_, _rasanya_, _почуття_,
+ {{0x47fae04d,0x995ce122,0xb37f8252,0x5da2d19b}}, // _コメントありがと, _nadomest, _inkasta_, _پاشنه_,
+ {{0xc38af208,0x22f0f1d6,0xd2da5086,0x620261d6}}, // _mòran_, _achapụ_, _gatera_, _birila_,
+ {{0x138690ca,0x22aba18e,0x69c4b0cb,0x0da0808f}}, // _tvaru_, _beading_, _گنجائش_, _アルファグランデ,
+ {{0xa357f048,0x8290e086,0x12925038,0xe2b491e8}}, // _xenforo_, _kanama_, _hatari_, _inace_,
+ {{0xa201f0a2,0x4a05f175,0x12da5122,0x214b5076}}, // _obzira_, _embalage, _katera_, _stronach_,
+ {{0x3fe91253,0x56b970b9,0x00000000,0x00000000}}, // _फ्रान्स_, _benaderi, --, --,
+ {{0x327e0254,0x4231704c,0xa855d04d,0x910740a9}}, // [230] _seinn_, _bermula_, _ああっ名無しさま, _pronađi_,
+ {{0x937a60f1,0xa25a504f,0x3c64a041,0x00000000}}, // _kawasan_, _salle_, _situace_, --,
+ {{0x22494077,0xa386d1ec,0x54901025,0x5b6c70d5}}, // _hieman_, _edere_, _चम्मच_, _वासना_,
+ {{0xe3f461a8,0x1453112b,0x94d6c14c,0x00000000}}, // _ujutru_, _službou_, _बुढिया_, --,
+ {{0x7493f020,0xd7e270bb,0x9f3dd23a,0xeb0de13b}}, // _블로거뉴스_, _cinplaim_, _क्षितिज_, _违反本法规定_,
+ {{0x814dc017,0x23949255,0x4ecda0dc,0x82cad0aa}}, // _избори_, _vlasy_, _komision, _kredi_,
+ {{0x82da5256,0x8224911c,0x93ea3070,0x00000000}}, // _batera_, _anake_, _fajta_, --,
+ {{0xfe9ba257,0x03cf418c,0xec743037,0x00000000}}, // _seguinte_, _preval_, _pelukis_, --,
+ {{0x0675c13b,0x00000000,0x00000000,0x00000000}}, // _经典艺术片_, --, --, --,
+ {{0x46d61258,0x722b91cd,0xa2e20202,0xbe1b9178}}, // _discussi, _exemple_, _calidad_, _exemples_,
+ {{0x23ead090,0x641211bd,0x2291e035,0x00000000}}, // _rreth_, _محققین_, _batam_, --,
+ {{0x1db8e070,0x9be5b259,0x81cc212d,0x47e8c003}}, // _valamint_, _randamac, _धरोहर_, _alvarleg,
+ {{0xdc6f7221,0x3c76325a,0xeb2200ed,0xb30cb149}}, // _heureka_, _मुद्रा_, _walaupun_, _poradit_,
+ {{0x5a57101a,0x438060bd,0x44769179,0xd3fa501f}}, // _цитата_, _sourit_, _процесс_, _satura_,
+ {{0x9089c13c,0x143ec175,0xc39601b2,0x6b17c1b8}}, // _режима_, _разни_, _lossis_, _раздел_,
+ {{0x829d8080,0x8c59d093,0x6378109e,0x5c6210b3}}, // _všade_, _vertrek_, _rubanda_, _izgubio_,
+ {{0xa272a25b,0x0ebca006,0x43ead075,0x327e025c}}, // [240] _tűnik_, _publiczn, _treth_, _reino_,
+ {{0xada8d0b4,0x776e8153,0x925a4056,0x9b67f1d7}}, // _activita, _universi, _gamla_, _innealan_,
+ {{0x2b81b1f1,0xb201e0f1,0xd307810c,0x7c6ea25d}}, // _sledovať_, _yatim_, _agbaidh_, _styrkja_,
+ {{0x82aa315d,0xe292725e,0x00000000,0x00000000}}, // _vneconom, _rurali_, --, --,
+ {{0x46658025,0x00000000,0x00000000,0x00000000}}, // _आईएसआई_, --, --, --,
+ {{0x76e40038,0x92fdf038,0x3afdf164,0x959db024}}, // _kimataif, _ndugu_, _prosjekt, _lisenziy,
+ {{0x4dc3a02e,0x9394425f,0x5215a16a,0xf3869011}}, // _podobné_, _komst_, _oddělení_, _svart_,
+ {{0x6641407b,0x01e19002,0xb2d5100f,0x6dbb804d}}, // _যথেষ্ট_, _मदारी_, _millest_, _アニメ感想_,
+ {{0x8c00b178,0x9290f083,0x0379b022,0x931c008e}}, // _passant_, _pahala_, _bayanin_, _pohištvo_,
+ {{0x038691be,0x23a8e205,0x22027206,0x07b2d260}}, // _gwara_, _malaking_, _hariri_, _штаба_,
+ {{0x6bcf51b0,0xa2ca711a,0xe0b6d030,0x22cad122}}, // _श्रीनगर_, _tande_, _नेचुरल_, _uredi_,
+ {{0xf2fc906e,0x99c290de,0x9a51f0a4,0x9ed60169}}, // _agaga_, _kindlast, _व्यंगचित, _kažem_,
+ {{0xfbdff191,0xb27e0143,0xc291f0a9,0xe01fa02b}}, // _aplikáci, _teine_, _brzaka_, _investuo,
+ {{0x82ca518b,0x82918261,0xa290c055,0x13ead0e7}}, // _halda_, _barat_, _salaan_, _preti_,
+ {{0x42018246,0xfea280e9,0x13ac70ae,0x22918246}}, // _maris_, _landsins_, _grupas_, _maras_,
+ {{0x227e9152,0xe2018056,0x4202503a,0xed5b712f}}, // _ilang_, _varit_, _ultimo_, _aistriú_,
+ {{0x450fb0f1,0xb278e00d,0x1154f0a2,0x13f96088}}, // [250] _komersil_, _miliyoni_, _budućnos, _siguro_,
+ {{0xe2018037,0x9d72c262,0xb4b9f263,0x41b9f089}}, // _garis_, _राष्ट्रध, _मंगेशकर_, _direcció_,
+ {{0x520271ec,0x2c30707b,0xd200c19e,0xe68c5166}}, // _kariri_, _ভাইয়ের_, _talian_, _संभाल_,
+ {{0xb968a1c4,0x029101e2,0x9127c036,0x23478264}}, // _informie, _zabala_, _наложи_, _começa_,
+ {{0x021391df,0x72ea0083,0x3dab5003,0x00000000}}, // _cishe_, _kiriman_, _menninga, --,
+ {{0xd3ea7058,0xb15c212b,0x426df265,0xa291a069}}, // _sante_, _oficiáln, _मनीषा_, _ntxawm_,
+ {{0x52dc50a8,0x489b404d,0xfd6b403b,0xc6fa60ea}}, // _søndag_, _レストラン_, _partisë_, _실시간환전게임_,
+ {{0xc18ac12f,0xa27e0211,0x525ad078,0x52c2702e}}, // _وكذلك_, _seine_, _isele_, _kvalita_,
+ {{0x625a5034,0x6e6da0a9,0xbc98022b,0xb2480122}}, // _dalla_, _proizvođ, _spurning_, _njimi_,
+ {{0xbb617266,0x69d310a4,0xf2024044,0x122500a2}}, // _bystrica_, _pokoknya_, _ética_, _ovakav_,
+ {{0x1c483162,0x73ebf048,0x0ff5f04d,0x00000000}}, // _अभिनंदन_, _btttt_, _ランキング一覧_, --,
+ {{0x56820166,0xa201900c,0x72fd40f2,0x00000000}}, // _विराट_, _basit_, _adegan_, --,
+ {{0x2291e05e,0x229190a3,0x00000000,0x00000000}}, // _tatak_, _masas_, --, --,
+ {{0x41bd8077,0x66e40008,0x7cb2407b,0xf95ed013}}, // _развития_, _בנתניה_, _আইনগত_, _inclusie,
+ {{0x03d7103d,0xe1b2b04d,0xe98400a9,0x42d221df}}, // _参考になった_, _名無しさん_, _godinama_, _empeleni_,
+ {{0x92366098,0x526c2099,0xa3805177,0xc2918037}}, // _svoji_, _mukom_, _catrin_, _waras_,
+ {{0x118d60a8,0x62a6a119,0x7291e00c,0x32d8f165}}, // [260] _forbehol, _eebbe_, _yatak_, _rekords_,
+ {{0xf2761008,0x1394d038,0x3201e0aa,0xaf69b122}}, // _תיאטרון_, _agosti_, _fatih_, _porodniš,
+ {{0x96b00008,0x32925174,0xf4682018,0xbd2f20fe}}, // _סמסונג_, _estaba_, _התהליך_, _তৎকালীন_,
+ {{0x0291a220,0xf291814b,0x11389037,0xbc77120e}}, // _lapas_, _karar_, _ابراهیم_, _venstre_,
+ {{0x2a10709e,0x4225b0e0,0x00000000,0x00000000}}, // _kambanda_, _xavkom_, --, --,
+ {{0xe7937077,0xe5dfc065,0xaa719074,0x5d392054}}, // _moottori, _بحاجة_, _larabawa_, _mobiilne_,
+ {{0xfeaa805a,0x53b9b222,0x00000000,0x00000000}}, // _baturage_, _pdaviet_, --, --,
+ {{0xe3ebf0fa,0xb236011a,0x63ea90de,0x927e0267}}, // _kutta_, _blije_, _saate_, _owino_,
+ {{0xc278b0b6,0xc22400d4,0xd396512b,0xd341c178}}, // _закарпат, _unika_, _zkuste_, _adreça_,
+ {{0x03ebf01a,0x59997020,0x83ebf0b2,0xf9d0314d}}, // _mutta_, _미디어로그_, _stttt_, _желим_,
+ {{0x1291a268,0xe29180aa,0x7306400f,0x92019018}}, // _mapas_, _zarar_, _सुचित्रा_, _basis_,
+ {{0xe730e0c4,0xc27e0267,0x5b38a1a2,0x62ca7221}}, // _novostav, _bwino_, _زولبیا_, _bundy_,
+ {{0x529181ac,0x5841d163,0x0501d163,0xc47bd0ea}}, // _paras_, _početku_, _početka_, _제휴사이트_,
+ {{0xe2b4c221,0xb2e80249,0x22cae134,0x127ee057}}, // _plocha_, _länder_, _beidio_, _zainal_,
+ {{0x2a868060,0x32fe4269,0x22d5d089,0x2f5d31e8}}, // _nítorí_, _haridus_, _benefici_, _opravdan,
+ {{0x83ebf03e,0x3f212034,0xfc5e30b9,0x1477c215}}, // _stutt_, _acquista, _kasteel_, _пакетах_,
+ {{0xa316d063,0x22ab70b6,0x83a291e7,0x7290b17a}}, // [270] _ndezi_, _melding_, _chape_, _andare_,
+ {{0xa2ca705e,0xa27ee031,0x646db26a,0x1c8fd16f}}, // _handa_, _bonnes_, _aprendiz, _opadanju_,
+ {{0x625aa26b,0x83fa607a,0x0f86f16a,0xd345403c}}, // _table_, _forumo_, _डडेलधुरा_, _terenie_,
+ {{0x569c2077,0x93eaf1df,0x77d8108d,0x2e7791d3}}, // _достаточ, _faiths_, _quarterf, _कुच्छ_,
+ {{0x1f19e1b3,0x8639402e,0xb6a0f037,0x53fa409c}}, // _permitir_, _materiál_, _الزامی_, _dawudi_,
+ {{0x9d0a126c,0x07b61072,0xf1dbe159,0x00000000}}, // _segretar, _levering, _akọsilẹ_, --,
+ {{0xb2918124,0x6386d26d,0x734290d5,0x9753a0cd}}, // _warar_, _hvert_, _सरपंच_, _युधिष्ठि,
+ {{0x6867e021,0x1d5ac218,0x9c52801a,0x3eb4f216}}, // _органам_, _кожен_, _hintaan_, _alakụba_,
+ {{0xa291816d,0x5f48b0b9,0x9303c0ce,0x86f1f26e}}, // _harap_, _kunstena, _gekauft_, _portuguê,
+ {{0xe348c0c7,0x72e8026f,0x8c70f113,0x63ea7119}}, // _offerti_, _händer_, _kajsiab_, _aanta_,
+ {{0x734630b9,0xd34040eb,0xf9bdf13b,0x835421bb}}, // _kinders_, _nedelja_, _中国气象局_, _numeris_,
+ {{0x53eb8061,0xb683513b,0x3200200d,0x620180d4}}, // _kurti_, _福星上战场_, _komine_, _parir_,
+ {{0x3e260008,0x93ea7119,0x5dfb904e,0x00000000}}, // _משפחתי_, _danta_, _diferite_, --,
+ {{0x924bb0de,0x14a2c01a,0x00000000,0x00000000}}, // _जानेला_, _ткани_, --, --,
+ {{0x325bf0a6,0xa2b5c0a2,0xe59fb05e,0xda0010da}}, // _juuli_, _novca_, _pangunah, _kayaknya_,
+ {{0x436ef231,0x270e2018,0xbc59f122,0x00000000}}, // _njegovu_, _בוודאי_, _lastnik_, --,
+ {{0x4ab47270,0xb29ee053,0x437b2271,0x927e0089}}, // [280] _खिचड़ी_, _miljoen_, _impacto_, _feina_,
+ {{0x42d9812e,0x94b280c2,0xffa541c6,0x83958036}}, // _okrem_, _organizz, _disember_, _forse_,
+ {{0x426d90a4,0x0dd620f6,0x00000000,0x00000000}}, // _besok_, _deskriba, --, --,
+ {{0x218c60de,0x637861e7,0x00000000,0x00000000}}, // _सर्वर_, _ouganda_, --, --,
+ {{0x03ced043,0x4394e08d,0x226c60b9,0xa291c105}}, // _clovis_, _elisha_, _brood_, _banklar_,
+ {{0x227f00c9,0x5386d22b,0xd00a817a,0xc6f640da}}, // _hranom_, _hvers_, _списание_, _पुरवठा_,
+ {{0xa2b5718c,0x63bff0c2,0x956ff0c2,0x125bc158}}, // _chache_, _servizi_, _servizio_, _agalú_,
+ {{0xa2d980ca,0x42139272,0x02fc724a,0x52fc0081}}, // _okres_, _kisha_, _tunga_, _origo_,
+ {{0xcb7bd076,0x8097d175,0x827e722b,0x6b993122}}, // _दर्शकों_, _промена_, _renna_, _socialno_,
+ {{0xa3410126,0x3386d1be,0xd3896024,0xa3cf2222}}, // _بنياد_, _meere_, _məruz_, _jzcviw_,
+ {{0x49118084,0x3302d084,0x0c754248,0x00000000}}, // _assalamu, _jikalau_, _algunas_, --,
+ {{0xeebb21b2,0x32cac119,0x714d61ea,0xfacde273}}, // _excommun, _cadde_, _obvestil, _посещени,
+ {{0x1760111c,0x5386d274,0x448101a3,0x639540b3}}, // _دلخواه_, _tvorba_, _मिलाकर_, _umesto_,
+ {{0xcb0f2242,0x627851be,0x012a2215,0x93bc51bd}}, // _corporat, _agunye_, _эстрадны, _حقانیت_,
+ {{0x9489f13b,0xd30c9275,0x1b2f812c,0xa200d147}}, // _新用户注册_, _setakat_, _considér, _sining_,
+ {{0xfff791fb,0xa2139119,0xefaa703e,0x2a31c07b}}, // _תענית_, _bisha_, _nemendur_, _পিডিএফ_,
+ {{0x191e1004,0x730c325c,0x00000000,0x00000000}}, // [290] _זונטיק_, _relatos_, --, --,
+ {{0xe331713b,0x2300b107,0x00000000,0x00000000}}, // _国家粮食局_, _имигрант, --, --,
+ {{0x3b1d2065,0xd37fc156,0x3c6a8122,0x52d9d134}}, // _النجم_, _zemalja_, _forumov_, _hywel_,
+ {{0x785be15e,0xd2b5f1a6,0x00000000,0x00000000}}, // _nedrīkst_, _kluci_, --, --,
+ {{0x58635276,0xd3ead0d1,0x0317e163,0x8290c124}}, // _dicampur_, _montag_, _potiskuj, _kulamo_,
+ {{0x12f1e09d,0x225bf23f,0x022490a2,0x468530e7}}, // _pomocą_, _mutlu_, _onako_, _русије_,
+ {{0x6579304d,0x00000000,0x00000000,0x00000000}}, // _酢酸ビニル共重合, --, --, --,
+ {{0xadc5a018,0xa8e7f1ab,0x7863c050,0x4e96c0cb}}, // _christia, _затвори_, _позади_, _قرضوں_,
+ {{0x8c67f0c5,0x3cdd107b,0x02ca6134,0xc377c19b}}, // _minulla_, _হালকা_, _chodi_, _قرائت_,
+ {{0x5efee277,0x04fd1065,0xfb27300c,0xc24a7080}}, // _nóvember_, _بجودة_, _cumartes, _desmod_,
+ {{0x12e971da,0xcfb8604d,0x4ed0e098,0x00000000}}, // _tariffa_, _産学連携組織_, _policejn, --,
+ {{0x63f8c278,0xb2c3a1e2,0xe5acd100,0xd2b6d0ad}}, // _deluje_, _mailako_, _племя_, _müdiri_,
+ {{0x6f502048,0x62fc7036,0xbdbcf144,0xe2d8e15b}}, // _sohanews_, _lungo_, _intenzív, _meneer_,
+ {{0xd201807f,0x22026074,0x7a49e13b,0x00000000}}, // _scrie_, _jarida_, _个国家中排名第_, --,
+ {{0x32fd4039,0x00000000,0x00000000,0x00000000}}, // _idegen_, --, --, --,
+ {{0x3559e071,0x1ebba1aa,0x00000000,0x00000000}}, // _проблеме, _sportske_, --, --,
+ {{0x3dd6005c,0x05f421ab,0x49233025,0x0758416a}}, // [2a0] _केजरीवाल_, _случва_, _सुरजन_, _समर्थित_,
+ {{0xecd73037,0x99df507b,0x79f95098,0x0dbbb092}}, // _اعلام_, _চারুকলা_, _firemní_, _objektív,
+ {{0xc155d025,0xf46e803d,0x62d8c279,0x00000000}}, // _इसीलिए_, _frederik, _lyder_, --,
+ {{0xa2786147,0xa291d095,0xc3ea91d7,0x00000000}}, // _ngunit_, _bawat_, _chath_, --,
+ {{0xca5a6119,0x00000000,0x00000000,0x00000000}}, // _hargeisa_, --, --, --,
+ {{0x230e727a,0x2c692099,0x4c52e003,0x3b97c017}}, // _arabera_, _zapravo_, _boltinn_, _семинара_,
+ {{0x72ca90b9,0x4290d038,0x00000000,0x00000000}}, // _skade_, _mkoani_, --, --,
+ {{0x01a3a07b,0x3c9c603c,0xc81c6105,0x9b7d327b}}, // _তাপমাত্র, _मुल्यांक, _çalanşik_, _wasukuma_,
+ {{0x67cc61f8,0x84ead031,0xa56ad031,0x429181ee}}, // _personli, _第二十二条_, _第二十五条_, _obras_,
+ {{0xd5d53017,0x2387427c,0xb24a61c4,0x034101da}}, // _туризма_, _ndersa_, _firmen_, _koperta_,
+ {{0x02a690a1,0x4386906c,0xc68e40cf,0xdd368034}}, // _alaba_, _alara_, _teleradi, _opinione_,
+ {{0x5977f26d,0x521391c0,0xa2da00bd,0xf292627d}}, // _menneske, _tishi_, _lapolis_, _oprava_,
+ {{0x1255a1ce,0xc1b03070,0x0c6e1206,0x72d1f057}}, // _problémá, _پچھلے_, _gharama_, _tentulah_,
+ {{0xe2fc9211,0xcf25b0c2,0x2f1160c2,0x4248f150}}, // _frage_, _selezion, _contatta, _ibhokisi_,
+ {{0x53993249,0x8e0d011c,0xc2781185,0x13a8d1eb}}, // _måste_, _فیلتر_, _pengirim, _армянска,
+ {{0xefd710c8,0xc2b720de,0xb6dc406b,0xcef11069}}, // _protože_, _क्रिएटिव_, _बैरिन_, _loojceeb_,
+ {{0xdda47255,0xe378108d,0x022ac100,0x038750d1}}, // [2b0] _reagovat_, _cabanga_, _домам_, _zuerst_,
+ {{0xbc6af076,0xd257327e,0xe3ce5109,0x327e0123}}, // _dostawy_, _føler_, _selve_, _meint_,
+ {{0x33a22131,0xa212d1b1,0x8200c038,0x8e1c1120}}, // _compte_, _ehehe_, _kuliko_, _valencia,
+ {{0xa291c03f,0xc201c1d6,0x43860077,0x038d121f}}, // _savas_, _savis_, _koira_, _proposti_,
+ {{0xa306520b,0x6b3c904d,0xa36e30f5,0xb3e920de}}, // _menarik_, _解決済みの質問_, _songoku_, _वेबसाइटन_,
+ {{0xd291e1c0,0x499401dd,0xe2fc7147,0x00000000}}, // _gatas_, _emakumee, _tungo_, --,
+ {{0xc48531f4,0x5c64304f,0x4200c02d,0x22e29035}}, // _देहाती_, _contenu_, _kalian_, _तिकीट_,
+ {{0xaa162136,0xb3062241,0x52926275,0xd3f83104}}, // _podataka_, _podatak_, _korang_, _amaziko_,
+ {{0x1936e061,0x62fcf0c0,0x5386f236,0xeffab026}}, // _struktūr, _ganger_, _negre_, _मुहावरे_,
+ {{0x02d960de,0x212851d5,0x00000000,0x00000000}}, // _sageli_, _bụrụ_, --, --,
+ {{0xb43ec01a,0x22a07084,0x5ee3627d,0x94861025}}, // _давно_, _serbuk_, _spokojen, _मोटापा_,
+ {{0xd26e60ae,0xe290c055,0x00000000,0x00000000}}, // _autors_, _galaan_, --, --,
+ {{0x03ac71c2,0x38ff00ea,0xd3ea917f,0x92ca918f}}, // _respon_, _프로그램의_, _chati_, _chadi_,
+ {{0xe7cd307b,0x786380bb,0xf89990b7,0xf200b0f4}}, // _তাঁরা_, _haistias_, _универс_, _radiyo_,
+ {{0xe8aae11c,0x65dd40ea,0x9bede035,0x20e3a144}}, // _آکادمی_, _처갓집양념통닭_, _diperkir, _skontrol,
+ {{0x62aa312f,0xbdc3927f,0x22fc7206,0x9e539185}}, // _وسهلا_, _peringka, _bunge_, _perangka,
+ {{0x8291e088,0xd200c18e,0x5200c038,0xc25ac05e}}, // [2c0] _batas_, _dalian_, _malipo_, _madla_,
+ {{0x82cac056,0x48fdb0e9,0x82ca9136,0xf0e2d045}}, // _ladda_, _skipulag, _ikada_, _sannsynl,
+ {{0x72018171,0x8fae0122,0xe1481170,0x00000000}}, // _abrir_, _odgovoro, _kartónov, --,
+ {{0xf34391a6,0x19f0a02c,0x52d740f7,0x9f207035}}, // _युकून_, _kunnolla_, _beslist_, _pendukun,
+ {{0x7e0370eb,0xf70a209d,0xdaf60280,0xcc1520de}}, // _uključen_, _zdecydow, _emprende, _विक्रान्,
+ {{0x73ea6153,0xdb5bf097,0xd373004f,0xee9bf241}}, // _photo_, _pretraga_, _domaine_, _pretrage_,
+ {{0x5eae0004,0xc27e706a,0xc2da60c3,0x59d37281}}, // _אוצרות_, _benne_, _cerere_, _kononnya_,
+ {{0x4f1171e1,0xe3f43087,0x6f5100ea,0x864ca0df}}, // _rendemen, _kartı_, _등록하시면_, _मर्डर_,
+ {{0x624a704f,0xedb4b084,0xcb84004d,0x7394d0ea}}, // _permet_, _diriwaya, _ザ操作端末監視シ, _moest_,
+ {{0x98065282,0xa3c87153,0x723880b2,0xdd7a9178}}, // _décembre_, _server_, _teamobi_, _diumenge_,
+ {{0xc032116a,0x225a905e,0x925a003e,0xc2fe6073}}, // _रौतहट_, _akala_, _skila_, _drugie_,
+ {{0x52dc6194,0x7f0fd0c1,0x920031ae,0xb2e39058}}, // _måndag_, _opatrova, _camino_, _latitid_,
+ {{0xacd2003c,0x0632a187,0x338ce13c,0x4db17061}}, // _विशेषाधि, _nabigazi, _музеј_, _marijamp,
+ {{0x46b2f045,0x238cd107,0xdc1e2033,0x62da5119}}, // _редактор, _музеи_, _compris_, _siteka_,
+ {{0x6fcc6128,0xf2d8c0d4,0x00000000,0x00000000}}, // _mobilier_, _mument_, --, --,
+ {{0x73aa1283,0x358a1032,0xc56cc003,0x4c4e3065}}, // _maraming_, _проблеми, _framleið, _بمعنى_,
+ {{0xe29251e3,0x8e9050bc,0x13a22284,0x364920ea}}, // [2d0] _altare_, _cordless_, _compre_, _촛불문화제_,
+ {{0xeecb40a9,0x52baa046,0xf0db5065,0xbee2020f}}, // _priznaje_, _garedig_, _طرابلس_, _दुर्दैवा,
+ {{0x8200c05d,0x4eff8069,0xebca1175,0xf37a50a2}}, // _waliyo_, _gaojmoob_, _продавни, _objavio_,
+ {{0xa429c20b,0x2c91405c,0xca662130,0x62ca41a2}}, // _pimpinan_, _कंडोम_, _вируса_, _evolusi_,
+ {{0x93ce01e7,0x29c870f6,0x3e24d035,0x327ec0af}}, // _swivi_, _animalia_, _पृथ्वीतल, _redna_,
+ {{0xd386d1d5,0xb34340a4,0xc25a40fd,0xc3b33017}}, // _nwere_, _dikenal_, _оригінал, _турци_,
+ {{0x02d8809f,0x62e800d1,0x6c77f031,0x8291e18f}}, // _beheer_, _männer_, _presque_, _satar_,
+ {{0x2e71d0d5,0xf757c017,0x79c4a0c7,0x1aae60f9}}, // _लॉन्च_, _jedinstv, _rilevant, _eleksyon_,
+ {{0xfc35e07b,0xe292515a,0xea1280a7,0x5e397285}}, // _ইলিয়াস_, _betaal_, _zdravlje, _pelabura,
+ {{0x62b400d7,0x2b59d036,0x5fcff0ea,0x00000000}}, // _inici_, _distanza_, _마이페이지_, --,
+ {{0xe292617f,0x8c80f070,0xff1e10e8,0xa8450089}}, // _baraza_, _سالگرہ_, _landstin, _últimes_,
+ {{0x56230091,0x07868218,0xa2924154,0x824c60ef}}, // _naturiol_, _ansvarli, _hawana_, _normális_,
+ {{0xfa4ba008,0xa2f04024,0x726d9120,0xc7170286}}, // _פלילי_, _colibri_, _mesos_, _stoličky_,
+ {{0x82018008,0xa2a05287,0x82910036,0xe394008d}}, // _april_, _futbal_, _sabato_, _mnisi_,
+ {{0x32da50f5,0xa386d075,0xc08cc13c,0xcd9390fe}}, // _aptech_, _clerc_, _славе_, _যোগাযো,
+ {{0x76df3136,0x5a1f3241,0xcf63c288,0x00000000}}, // _najbolji, _najbolje, _समस्याएं_, --,
+ {{0x0b630091,0xe099a008,0xbce800d5,0xcea4d194}}, // [2e0] _materion_, _לחסוך_, _अनामिका_, _будучи_,
+ {{0x1f1500e8,0x9815d0c3,0x98cec0cb,0x00000000}}, // _fortfara, _буката_, _ديکھيے_, --,
+ {{0x9b630046,0xc3ec600a,0x00000000,0x00000000}}, // _faterion_, _espressi_, --, --,
+ {{0x7b09d04d,0xf6be2081,0x0f31a18c,0x22da60b9}}, // _サインイン_, _показник, _konfidan, _bereid_,
+ {{0x6a7c6158,0x62bc2018,0x42f050a1,0x3e0d2020}}, // _ìjápọ̀_, _מליסינג_, _kilindi_, _landscha,
+ {{0x6c26a098,0x52da401f,0x00000000,0x00000000}}, // _titulů_, _metodes_, --, --,
+ {{0x74a31020,0xa305a0bd,0x43f8f246,0xe2d440af}}, // _대구광역시_, _almanak_, _uygur_, _podobna_,
+ {{0xc2245056,0x8201415d,0x00000000,0x00000000}}, // _vilka_, _pleiku_, --, --,
+ {{0x43b26289,0x2fb29071,0xe212b013,0xc39571ea}}, // _porque_, _музикант_, _nacht_, _glasba_,
+ {{0x1249f28a,0xe5324082,0x803bd107,0x32b520cb}}, // _nyuma_, _централь, _станал_, _vicces_,
+ {{0xf236c035,0xbf3880ac,0x72fcf005,0xf5016044}}, // _produksi_, _resursla, _langar_, _forestal_,
+ {{0x0f63c1a3,0xb26d8011,0x023fc0ea,0xb3eaf0ed}}, // _समस्याओं_, _beror_, _사용합니다_, _mantap_,
+ {{0x22be61bd,0x5c76d025,0xd29241c0,0x00000000}}, // _majdnem_, _छेड़छाड़_, _mawala_, --,
+ {{0x5547c07b,0xec4a7134,0x56e8a13b,0xe60cf216}}, // _ষড়যন্ত্র_, _pentref_, _国家工商行政管理, _ayọcha_,
+ {{0xa85ef233,0x00000000,0x00000000,0x00000000}}, // _forneced, --, --, --,
+ {{0x6f5d812d,0xcb55c025,0xe25a910c,0x2c18f045}}, // _महंगा_, _एप्लीकेश, _chall_, _arranger,
+ {{0x22e760bd,0xf39581ab,0xd27f40d4,0x22b4004e}}, // [2f0] _trennen_, _corso_, _emenda_, _unici_,
+ {{0xff58e031,0xc201e068,0x32918116,0x00000000}}, // _notammen, _actie_, _haray_, --,
+ {{0x95ae01da,0x127e70e9,0x73ebf05d,0x555d8052}}, // _preparaz, _kenna_, _lutti_, _uvavanyo_,
+ {{0xb014804d,0x43f8906e,0x0e52216c,0x7292708e}}, // _このブログの更新, _kukuru_, _адолесче, _hkrati_,
+ {{0x82918119,0x5b0b504d,0xc2fe4008,0x6c6b223a}}, // _maray_, _アプワイザ_, _various_, _टोक्यो_,
+ {{0xfc530024,0x62d61118,0x87b39004,0x43a2e09f}}, // _dostuna_, _negozio_, _שאנסן_, _klippe_,
+ {{0xb1c3d0c5,0xe386012f,0x00000000,0x00000000}}, // _говорит_, _boird_, --, --,
+ {{0xb386d056,0x72016005,0x229181c0,0xa7e14143}}, // _flera_, _daginn_, _garay_, _परार्थ_,
+ {{0x2c763026,0xef516020,0xc73b2031,0x0af7c034}}, // _मंत्री_, _클릭하시면_, _第二十九条_, _галерия_,
+ {{0x2effc0cb,0x5be281aa,0xc31a401a,0xfa5c80fe}}, // _مماثل_, _херцегов, _правообл, _হাঙ্গরিক,
+ {{0x527ed1f8,0x56f6a035,0x00000000,0x00000000}}, // _kroner_, _सुरवात_, --, --,
+ {{0x9386d24b,0xc26c4147,0xc66730ca,0x425af047}}, // _utorak_, _lumot_, _राजस्व_, _eagla_,
+ {{0xa27e7056,0xc711e076,0x9d37f050,0xb2c1c00f}}, // _denna_, _भद्दी_, _личност_, _millega_,
+ {{0x22a5f021,0x9292620b,0x8ecbe11c,0x8ceab025}}, // _станісла, _asrama_, _درگیری_, _पाठमाला_,
+ {{0x827e0134,0xa303014e,0x12fcd09a,0xc27e70a1}}, // _meini_, _malapit_, _luego_, _fenna_,
+ {{0x33436175,0x6349f01a,0xb26d9056,0x926e60b5}}, // _governo_, _resepti_, _resor_, _motory_,
+ {{0x8226703e,0x890b10ea,0xe0e9d04d,0xcfb92271}}, // [300] _virkar_, _감사드립니다_, _とりあえず_, _métodos_,
+ {{0x425db215,0x826c003e,0x468c628b,0x00000000}}, // _sprendim, _arion_, _दुबार_, --,
+ {{0x326c31be,0xec66b1c4,0x172b003b,0x486f00bf}}, // _timoti_, _besteht_, _ekspertë, _abẹlẹ_,
+ {{0xa26c20b3,0x00000000,0x00000000,0x00000000}}, // _rukom_, --, --, --,
+ {{0xd692e28c,0xa24a71ac,0x6f6c6131,0xcc7c528d}}, // _navegaci, _permit_, _vacances_, _guardar_,
+ {{0xf6a2601c,0xc01d003d,0x1290f063,0x8c0580c7}}, // _pogledaj_, _もっと見る_, _achaba_, _massimi_,
+ {{0x79f56076,0x4291503c,0x1c19b1eb,0x00000000}}, // _tagów_, _badań_, _tekstą_, --,
+ {{0x834010f6,0xcc0470a2,0x5c774024,0x96d3428e}}, // _taberna_, _rođen_, _tutulur_, _मौखिक_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa2d9513e,0x326e6165,0x3686c0b9,0x00000000}}, // _offert_, _deroga_, _hinderni, --,
+ {{0xc3811088,0x12d450a7,0xd2cb8051,0x8471a0cd}}, // _kanyang_, _ljudje_, _yarda_, _मशीनी_,
+ {{0xcd9d700f,0x326fa052,0x7a0b0037,0xf2ebd213}}, // _tunnista, _abaningi_, _surakart, _aibidil_,
+ {{0x07534082,0xa27f00f9,0xbb35a1bc,0x96372021}}, // _barnehag, _blanch_, _máquina_, _сарказм_,
+ {{0xe5e80105,0x221d21aa,0xb2247061,0xb9774077}}, // _silahlı_, _датум_, _tinka_, _valitett,
+ {{0x53f470c2,0xc245a28f,0xd8ff40b3,0x00000000}}, // _nostro_, _adultgui_, _raspolag, --,
+ {{0xb2f180e2,0x00000000,0x00000000,0x00000000}}, // _misingi_, --, --, --,
+ {{0xf3415040,0x8b8c3290,0x525b41ab,0x02480104}}, // [310] _anderen_, _राजमा_, _quella_, _ndima_,
+ {{0x01660008,0xb3eb80d8,0xa20c90aa,0x5291612f}}, // _איתותים_, _sarta_, _herhangi_, _tagann_,
+ {{0x625b8120,0x8b187070,0xc035f0c4,0xb035f11d}}, // _parla_, _ingatlan_, _svetovej_, _svetovan,
+ {{0xcc1670de,0x5394d122,0xcb7d30b5,0x9291508d}}, // _मृत्युन्, _prosti_, _finanční_, _angase_,
+ {{0x427e7039,0x3bd76186,0xc25b80e9,0x438691b2}}, // _venni_, _जटायू_, _varla_, _xmary_,
+ {{0xbdbf80cc,0x642e7291,0x81a4c006,0x500b904d}}, // _פּרעזידע, _प्रतिभाग, _dokonać_, _質問投稿日時_,
+ {{0x7567e0cc,0x227e7039,0x493f00ac,0xe0e1007b}}, // _באַציִונ, _tenni_, _olimpiya_, _হেফাজতের_,
+ {{0xe2cae039,0x320101e8,0x72fd60f7,0x00000000}}, // _minden_, _dobije_, _leggen_, --,
+ {{0x429110fb,0x190a7175,0x124a6008,0x0f81f1a3}}, // _bacaan_, _последен_, _normal_, _धक्के_,
+ {{0xa3eb9292,0x27c71293,0xd2915052,0xcc52c19c}}, // _casta_, _начини_, _ungase_, _वारेन_,
+ {{0x53949206,0x327f4148,0xb8542294,0x00000000}}, // _siasa_, _dienst_, _קרדיטים_, --,
+ {{0xa3658245,0xf9aac0c3,0x9fd780c1,0x2341c03b}}, // _tangazo_, _ловим_, _pretože_, _komento_,
+ {{0x127b71a6,0x0639303d,0xb2ea1074,0x0397c192}}, // _komentář, _ブログ内検索_, _tarihin_, _medarbej,
+ {{0x130da26b,0x025a9045,0xe764c00b,0xb9183071}}, // _מייפל_, _skall_, _पौष्टिक_, _депута_,
+ {{0xdc173099,0x28713293,0xd795d071,0xe290805d}}, // _ukupnom_, _останат_, _лумина_, _kakano_,
+ {{0x55da61e6,0x42000119,0xa27f7052,0x00000000}}, // _проблемн, _amiin_, _amanga_, --,
+ {{0xfeac106c,0x938ff00d,0x0249418c,0xf2bc604f}}, // [320] _aaarére_, _hanyuma_, _chemen_, _médias_,
+ {{0x0f4680ac,0x3c7de059,0x12804078,0x800f3018}}, // _konfrans_, _proseso_, _ajakaye_, _קוקסינלי,
+ {{0x6e68c276,0xb39e10a2,0xf3fa11e9,0x12cad247}}, // _pencerna, _stvaranj, _popull_, _chedo_,
+ {{0x7200d02b,0x839d80f6,0xab0d80f6,0x0248d061}}, // _toliau_, _proposam, _proposat, _tuomet_,
+ {{0xc2cae02c,0x82e180de,0xcd033065,0xd3eae0e6}}, // _niiden_, _चौबीस_, _أدناه_, _hinten_,
+ {{0xc030c1e8,0xecf741dd,0x9fbff215,0xda749132}}, // _prethodn, _sistemar, _асфальта_, _وضوابط_,
+ {{0xb51e41e3,0xe320b202,0xc14ec047,0x957c200c}}, // _enhliziy, _precios_, _dealramh_, _bankası_,
+ {{0x9b5c4295,0xb2d90011,0x43940172,0x6cd34098}}, // _političk, _arbete_, _azise_, _kvalitě_,
+ {{0xc27f021b,0x62b47296,0x13eb8116,0xe2fc50d1}}, // _abantu_, _cinco_, _karto_, _richtig_,
+ {{0x53ebe211,0x32925037,0x3c60f0f7,0x52025099}}, // _hatte_, _istana_, _minuten_, _istina_,
+ {{0x1519f175,0xedbce00d,0xb26cf297,0x597a61e6}}, // _рецепта_, _athanase_, _tugon_, _продемон,
+ {{0x12494211,0x726de11d,0x025a6122,0xce43114e}}, // _themen_, _letos_, _okoli_, _kalagaya,
+ {{0x6e53218a,0xd2a6608d,0x7b83e030,0x00000000}}, // _sumbanga, _hlobo_, _दुसमन_, --,
+ {{0xb33c207a,0xd2da70af,0x9c922078,0x1d28c02a}}, // _слёзы_, _besede_, _adahunṣe_, _андрій_,
+ {{0x5f25b1b3,0x4a6a21de,0x93ced24a,0xc29590c8}}, // _selecion, _प्रशस्त_, _reeve_, _स्लोली_,
+ {{0x83ead036,0x101fa1c9,0xc1dcd0a2,0x530ca075}}, // _mentre_, _endereço_, _istovrem, _geraint_,
+ {{0xd2918297,0x19f9e054,0xfc7ca1c0,0x1f2bb065}}, // [330] _siyang_, _tavalise, _seksyon_, _انتهاء_,
+ {{0xbe19f09b,0x030de084,0x4399c27e,0xee96d132}}, // _menemuka, _sekadar_, _læste_, _خرطوم_,
+ {{0x82cae016,0xdf4db0cc,0x2212b154,0x5236c298}}, // _vendet_, _מיזרח_, _machi_, _biljke_,
+ {{0xf386d299,0xd26dc1c4,0x22903055,0xa2243155}}, // _flere_, _bevor_, _samayn_, _dimkpa_,
+ {{0xfdbca14e,0xe40490a4,0x1b114232,0x3f80b192}}, // _pilipina, _जोरदार_, _kampanya_, _placerin,
+ {{0x2aeb30f6,0xcf91f074,0x02926101,0x00000000}}, // _horregat, _rundunar_, _laraba_, --,
+ {{0x5c05a0b5,0x5ea6a082,0xa2fc7104,0xc3ea60e3}}, // _rubriky_, _fortsatt_, _zungu_, _vyote_,
+ {{0x53ebf033,0xbd8d500d,0xd097e1ab,0x5690709f}}, // _haute_, _demokara, _франция_, _landbouw,
+ {{0x02f7c179,0xdc7510d2,0xcdd041b2,0x6be2b032}}, // _никогда_, _ernstig_, _motherna, _величест,
+ {{0xf35f3047,0xef41c098,0x4fd2b0a6,0xf3bfc071}}, // _taighde_, _prosince_, _सारगर्भि, _аменда_,
+ {{0xb63361a4,0x22d4c1a4,0xa2240106,0x54d6e098}}, // _stanovni, _priliku_, _антирусе, _बिनिमय_,
+ {{0x5340a0c2,0xd1ad70a5,0x00000000,0x00000000}}, // _diventa_, _kazandır, --, --,
+ {{0xc2267225,0x426d8099,0xb0b180b5,0xcc3af098}}, // _virker_, _otrov_, _optimáln, _समायोजन_,
+ {{0x0151307f,0xfb5c41a4,0x427e71c4,0xf4df013b}}, // _струс_, _političa, _kennt_, _法定代表人_,
+ {{0xa2cb4247,0xf2fce185,0xf519018c,0x026321b8}}, // _midday_, _pengen_, _eternulo_, _вложения_,
+ {{0x7c5e6080,0x5c7c729a,0xcdae612b,0x12a60004}}, // _portál_, _glasova_, _portálu_, _פיעסעס_,
+ {{0x150bf097,0xc0480047,0xa6f80047,0xb2f7d0ca}}, // [340] _postavio_, _موضوعي_, _موضوعك_, _उजुरी_,
+ {{0xe5b1910b,0x10951024,0x8c0d71fd,0xf301e074}}, // _technoló, _hadisələ, _tampere_, _matakai_,
+ {{0xc77f5031,0x127e022f,0x7bfd51bb,0x38a3b065}}, // _请输入上图中的验, _ating_, _balandži, _بينهما_,
+ {{0xd3eb81a3,0x22ecb008,0xdf1e0004,0x7e579182}}, // _warto_, _למכשירים_, _רוסישער_, _металург_,
+ {{0x7305c037,0x96d6204d,0x9292114f,0xe248e008}}, // _اوراق_, _に示すように_, _mapato_, _animal_,
+ {{0xabf511a6,0x03eb91a4,0xd26cc25b,0x7349e084}}, // _produktů_, _zasto_, _komoly_, _bererti_,
+ {{0xdb43c0cb,0x246140f8,0x32926135,0x328c117f}}, // _اُردو_, _baldintz, _sprava_, _uhakika_,
+ {{0xf3eb812c,0x827e022b,0xef4e007f,0xd3cec156}}, // _carte_, _beint_, _persoane_, _jedva_,
+ {{0x01013017,0x38ab7031,0x6bfb7031,0x49db7031}}, // _свети_, _第二十一条_, _第二十三条_, _第二十七条_,
+ {{0xaad7129b,0xe2eb1034,0x8490e1d3,0xef4251da}}, // _litterat, _periodo_, _गनीमत_, _trasport_,
+ {{0x828d4036,0x2dafd0a2,0x8225501f,0x00000000}}, // _appartam, _smatraju_, _liekas_, --,
+ {{0xd25ad0c4,0x1386d1cf,0x1a0801a7,0x6e7161d5}}, // _biely_, _acordo_, _verbesse, _bọros_,
+ {{0x39d0a143,0xc386615a,0xdfe82025,0x6a02a116}}, // _investee, _moord_, _रिलायंस_, _magalada_,
+ {{0x42fba0c2,0x5291f08d,0x0b6d3099,0x7b6e7065}}, // _потребит, _bazama_, _razmiric, _postála_,
+ {{0x325b6111,0xfdd0c0f3,0xab61418d,0x00000000}}, // _anglii_, _острови_, _billiger, --,
+ {{0xd386609f,0x026d8006,0xf26891e3,0x00000000}}, // _noord_, _stron_, _okuningi_, --,
+ {{0xe2075271,0x025b8100,0x803301c9,0x632470cb}}, // [350] _democrát, _varle_, _postagen, _nézni_,
+ {{0xc905800b,0xe3eae070,0xa178c0bd,0xaf602221}}, // _sekaligu, _mintha_, _rechèch_, _divadeln,
+ {{0xc2cb829c,0x7ad630ca,0x0d0a1108,0x22da5086}}, // _tarde_, _कच्ची_, _तत्वाधान_, _intego_,
+ {{0xb2b47047,0xc2eb5087,0x9049f0b9,0x97e6c267}}, // _rince_, _orjinal_, _weerstan, _abapolii,
+ {{0x8c00d11c,0x02ec0030,0x1a8cc194,0x326d613a}}, // _دخالت_, _भाषीय_, _retnings, _dogodi_,
+ {{0xeb42c175,0x539661df,0x0c0bd092,0x7989e27e}}, // _екипи_, _omusha_, _najbližš, _forholde,
+ {{0xcc77405a,0x06d2b041,0x5db42065,0x30443025}}, // _cyumweru_, _धादिङ_, _موقوف_, _ग़ालिब_,
+ {{0x8290c17d,0xa26c7099,0x5be0e29d,0x00000000}}, // _eiland_, _punom_, _entender, --,
+ {{0xac5c2008,0xa283f0c4,0xf380013b,0x5425414d}}, // _ספונטני_, _funkcia_, _常州组织工作_, _dezinfik,
+ {{0x427ee13e,0x627f709e,0x03656034,0x3e3b2100}}, // _kvinna_, _imanza_, _съвремен, _remontas_,
+ {{0x927f711a,0x538691d4,0x690cc02c,0xc2cbf136}}, // _chante_, _claro_, _покупате, _grudi_,
+ {{0x3321f247,0xad59a12f,0xd9dd81a4,0x2d70a24d}}, // _abuyi_, _sainmhín, _zemljama_, _achmhasa,
+ {{0x8532e03d,0xf27ed11e,0xb4a40018,0xd2df829e}}, // _この回答へのお礼_, _kronor_, _באשדוד_, _misirdə_,
+ {{0xfa8a9229,0xd202529f,0x0ebd0204,0x03ea614f}}, // _августа_, _artigo_, _apsolutn, _nyota_,
+ {{0xe291e0bd,0x53eac048,0xdb6e3017,0x83e5d0d3}}, // _batay_, _tamtay_, _лиценцом_, _shocrú_,
+ {{0x73f4618d,0x855320b7,0x00000000,0x00000000}}, // _morten_, _андрееск, --, --,
+ {{0xfb0ae2a0,0xe255d035,0xe08ae2a1,0x0759316a}}, // [360] _conselle, _यथादृश्य_, _concello, _मुस्लीम_,
+ {{0x2d86504a,0xd2d8e202,0x9349e0c5,0x00000000}}, // _permaina, _quiere_, _hotelli_, --,
+ {{0x626de03d,0xe5374076,0x00000000,0x00000000}}, // _netop_, _कस्बे_, --, --,
+ {{0xe31a1147,0x4aea6081,0x16039045,0x4394d0c3}}, // _sariling_, _fortelle, _поверх_, _piesa_,
+ {{0xf26d00a9,0x82caf076,0xae0061ff,0xea54204d}}, // _subota_, _nigdy_, _donderda, _検索オプション_,
+ {{0xd2ce5054,0xf27f408d,0xd300c03a,0x2a09a286}}, // _reklaam_, _kwenza_, _infatti_, _nezamest,
+ {{0x437801c0,0x53f94105,0x02d462a2,0xae3b10ed}}, // _subasta_, _ordusu_, _jorden_, _peratura,
+ {{0x8394d15e,0x42926056,0xdd055143,0x5ba53013}}, // _tiesa_, _totalt_, _populaar, _praktisc,
+ {{0xd75c0004,0xb2b511bf,0x5a4a30c6,0xb2fcd1ef}}, // _ספינקא_, _vendeve_, _परिषदको_, _grego_,
+ {{0x101a20a2,0xf63eb0a5,0x00000000,0x00000000}}, // _životinj, _hamileli, --, --,
+ {{0xac4d7242,0xe8dce084,0xb49e60d4,0x7ae2e082}}, // _privacy_, _bergantu, _apparenz, _fortsett,
+ {{0xe24800a9,0x0c7e018f,0x02027160,0x00000000}}, // _svima_, _harshen_, _scrios_, --,
+ {{0xe386c081,0x00f2b263,0x4249412c,0x00000000}}, // _aldri_, _सजावट_, _chemin_, --,
+ {{0x9c499071,0x00000000,0x00000000,0x00000000}}, // _реколта_, --, --, --,
+ {{0xdc5bf0a2,0xad3e1008,0x99817175,0xb2cbf2a3}}, // _postovi_, _שוטרים_, _equipame, _poslovi_,
+ {{0x225ac0dc,0x22d051be,0xa075409d,0x2458f190}}, // _fillon_, _achorom_, _उन्मुक्त_, _ikdienā_,
+ {{0xa22a815b,0xd84381cd,0xff9451e6,0x00000000}}, // [370] _niemand_, _communau, _величезн, --,
+ {{0x71541008,0x92ddc02a,0x228e1018,0x3ee0c1ab}}, // _בניגוד_, _трохи_, _בודפשט_, _тегло_,
+ {{0xe33d3132,0xe7e3c036,0x2afe80a4,0xfc60f1c7}}, // _حیران_, _полиция_, _aparteme, _monumen_,
+ {{0x8394e179,0xf2fc705d,0xdf3ab0d4,0x52fcf0fa}}, // _toisen_, _kungi_, _sfortuna, _mugga_,
+ {{0xd35bf17c,0xa96f106e,0xa2fc500c,0xf9667163}}, // _उद्देश्य_, _abanigbe, _izinsiz_, _natoplje,
+ {{0x72d9a1e7,0xbf79f170,0x1ad6a01c,0x5476b0f7}}, // _revele_, _ježiš_, _čekajući_, _gisteren_,
+ {{0x43cff150,0x42f12122,0xf104919b,0x00000000}}, // _emuva_, _očitno_, _ارزانتری, --,
+ {{0x9dafd020,0x53954092,0xf5d07190,0x0ad2f0d1}}, // _가맹점가입안내_, _miesta_, _komentēt_, _gestalte,
+ {{0x6201102e,0x4ceb3166,0x43fa602d,0xe87ba1ab}}, // _mobilu_, _मुहम्मद_, _burung_, _известна_,
+ {{0xb1e3c166,0xc26c4241,0x62cae1b5,0x79685049}}, // _उतारा_, _samog_, _pandai_, _jednocze,
+ {{0x32b491a4,0xdaf720c3,0xdfb59215,0x3394b2a4}}, // _znaci_, _материй_, _блогеры_, _pjesme_,
+ {{0x8033d175,0x53f470f7,0xce98c173,0x6386820b}}, // _помогне_, _kosten_, _маршрута, _mahram_,
+ {{0xc25b40ff,0x43a0b044,0x92c5c036,0x52d9a258}}, // _nielen_, _máximo_, _чувствит, _types_,
+ {{0x250b91b3,0xd233c03d,0xa27e7126,0xb3ebf071}}, // _saturado_, _ショッピング_, _lenni_, _cauta_,
+ {{0xc2b4d191,0xe224d100,0xdc1c0008,0x2c62b20e}}, // _nieco_, _nieko_, _מפתחים_, _forrige_,
+ {{0x1f697276,0x9758e0d6,0x00000000,0x00000000}}, // _sembaran, _चमत्कार_, --, --,
+ {{0xe1fd60d3,0xb2911118,0x626ab198,0xfc124038}}, // [380] _ballstái, _locale_, _učiniti_, _kampeni_,
+ {{0x9047c017,0x52f0d058,0x83ea904a,0xba6e4092}}, // _законом_, _viginti_, _nyata_, _profesia_,
+ {{0xb3947206,0x09c7c061,0xe20da2a5,0x7cd94007}}, // _jinsi_, _тамаша_, _ויקהל_, _akikanju_,
+ {{0xe27f70cb,0x3afa221d,0x15268175,0xd486500f}}, // _tegnap_, _razumije, _стабилно, _मुनादी_,
+ {{0x327ee10c,0x23a37064,0x42bf1037,0x66828098}}, // _bainne_, _suapan_, _اموال_, _तामाङ_,
+ {{0xe25e201f,0xf3949047,0xb9b8a17a,0xa081f082}}, // _ventspil, _miasa_, _ситуация, _інноваці,
+ {{0xc3d12286,0xa3fb6011,0x631650ad,0x7b1fc19d}}, // _história_, _människo, _halbuki_, _kematian_,
+ {{0x03eb91ee,0x22d5a29e,0x739e10a2,0xe2a07100}}, // _hasta_, _millət_, _otvaranj, _klubas_,
+ {{0x33eb92a6,0x8ab42030,0x43dc42a7,0xb2d8e208}}, // _kasta_, _चौकड़ी_, _kamwe_, _aineol_,
+ {{0x12cac056,0xb30a10e8,0x818d1106,0x0ac980cd}}, // _bilden_, _fotboll_, _алеӂе_, _रूपरेखा_,
+ {{0x0386024c,0x1416a031,0x43947024,0x02b4707f}}, // _toirt_, _依法追究刑事责任_, _cinsi_, _cinci_,
+ {{0x4394902d,0xf0fe10cc,0x42d9a26d,0x3906b01a}}, // _biasa_, _צוטאגס_, _typer_, _альтерна,
+ {{0x73c70047,0x1a4350ea,0x00000000,0x00000000}}, // _corparái, _찾아오시는길_, --, --,
+ {{0x7b0350c5,0x6290c27b,0x9a968190,0xfc0c0053}}, // _prosentt, _nalabo_, _novembrī_, _webshop_,
+ {{0x162420a8,0xc200e0db,0x62da517b,0xeda2d070}}, // _økonomis, _único_, _inteko_, _فائنل_,
+ {{0xc7b52214,0x00000000,0x00000000,0x00000000}}, // _blastosi, --, --, --,
+ {{0x7d76d06a,0xef6f2035,0x8fd0b0c9,0xf3305068}}, // [390] _فرانس_, _कलादालन_, _postiže_, _makkelij,
+ {{0xb27e80ca,0x00000000,0x00000000,0x00000000}}, // _denně_, --, --, --,
+ {{0x84873238,0x22e85122,0xc39570a4,0x573e8045}}, // _स्टाइल_, _naslednj, _alasan_, _востаннє_,
+ {{0x89e3c04e,0x14d4d1f4,0x226d80c5,0x12eb9099}}, // _повара_, _कोचिंग_, _euron_, _velikom_,
+ {{0x00f1112f,0xf4173123,0x00000000,0x00000000}}, // _الطعام_, _menschen_, --, --,
+ {{0x8d0f211c,0xb146f089,0x1cfdc017,0x916f3065}}, // _بارسلونا_, _ingresso, _прича_, _نماذج_,
+ {{0x7387019d,0x0ce4f098,0xb290f24d,0x737a2147}}, // _syarat_, _podobně_, _triall_, _basahin_,
+ {{0xf27f714f,0xfd81003b,0x33a01018,0xea0a6065}}, // _mwanza_, _realizua, _מצליחה_, _معاينة_,
+ {{0xa2e9e2a8,0xc7601020,0x9e75311f,0x8248000d}}, // _peligro_, _크리에이티브_, _दिग्दर्श, _ndimi_,
+ {{0xcceec017,0x28c351e6,0x3200a0a2,0x93d94006}}, // _америке_, _генераль, _knjigu_, _tematów_,
+ {{0x70723022,0x12139074,0x86aed088,0x2c4a3006}}, // _sakamako, _dashi_, _nagustuh, _भावनाओं_,
+ {{0x8a636062,0xd184124d,0xa106e133,0xd2026147}}, // _интереса, _sheasamh_, _mechthil, _narito_,
+ {{0x027f705f,0x00000000,0x00000000,0x00000000}}, // _maande_, --, --, --,
+ {{0xd27f72a7,0x1f628073,0x637902a9,0x00000000}}, // _kwanza_, _संस्थाएं_, _basanga_, --,
+ {{0x9bc7d0b6,0xb9e7d0c5,0x2e976084,0x7c5c104f}}, // _продаж_, _продам_, _permohon, _gestion_,
+ {{0x7200b2aa,0x3209d025,0x43dd905a,0xb2f312ab}}, // _ridire_, _परिजनों_, _ruswa_, _bonitet_,
+ {{0x4a3b21bc,0x3395403a,0x03957057,0x1a8fd051}}, // [3a0] _студии_, _questi_, _ulasan_, _ministan_,
+ {{0x6197817a,0x637fd03e,0xeeb43190,0x437e600a}}, // _групата_, _einasta_, _rezerves_, _professj,
+ {{0xf20802ac,0xb3017186,0xc8caa2ad,0x8c0550e1}}, // _informác, _स्निग्धा_, _статуса_, _choroby_,
+ {{0xb2cbb0ac,0x9290b194,0xe4d7c173,0x2753e017}}, // _haqda_, _vidare_, _балконы_, _рехабили,
+ {{0x08fdd08a,0x73f9011c,0xf7beb031,0xc29092ae}}, // _ফলাফল_, _gabung_, _违者本网将依法追, _omaan_,
+ {{0xec527184,0x29cd32af,0x128f11df,0x825a922b}}, // _amatora_, _ढाँचा_, _nochris_, _skalt_,
+ {{0xe27f70f4,0xdfa112b0,0x93b1128c,0x14f8c13b}}, // _mwanya_, _servizos_, _servizo_, _第四十二条_,
+ {{0xcf0b0271,0xc3e5a03f,0x9c7250ff,0xd2a652b1}}, // _promover_, _oktobrī_, _popradu_, _volba_,
+ {{0xf2e8122e,0x637081be,0x3c537241,0xf542c0b7}}, // _jacinta_, _anyanwụ_, _nastupa_, _бэрбэтес,
+ {{0x50b5f025,0x5f628049,0x526df0de,0x00000000}}, // _चाकुओं_, _संस्थाओं_, _autod_, --,
+ {{0x62d462b2,0xb77b812f,0x668d6108,0x96cfb21f}}, // _soudan_, _tromchúi, _बनवास_, _sigurtà_,
+ {{0xb201807f,0xb25a9090,0x73431035,0x00000000}}, // _scris_, _fjale_, _तिथून_, --,
+ {{0x5e529020,0xd2fc9181,0x15ed70be,0xe2b4d0ca}}, // _써브를시작페이지, _fragt_, _gorchymy, _dnech_,
+ {{0x8a715136,0xb2f151a4,0x6b190141,0xa794125e}}, // _članova_, _članovi_, _pasangan_, _mistoqsi,
+ {{0x93947194,0x174c3065,0xefc00031,0xdf211036}}, // _minst_, _العفو_, _提问者对于答案的, _dipenden,
+ {{0x72cae0f7,0x8394d0c3,0x2c4cb041,0xc26c2051}}, // _vinden_, _piese_, _partneři_, _makon_,
+ {{0x32fc70e5,0x32cff031,0x426e60c2,0x348590de}}, // [3b0] _mange_, _只显示最新_, _parole_, _दिवाना_,
+ {{0x62dab2b3,0x97280045,0xa5d62008,0x5224b0dc}}, // _oktobar_, _продукці, _היועצים_, _dicka_,
+ {{0x525bf1c9,0x62bb10c4,0xac77316d,0x884d22b4}}, // _paulo_, _balenie_, _mensahe_, _destinos_,
+ {{0xc37a7013,0x727ef01a,0x26ce6076,0x00000000}}, // _betalen_, _kunnes_, _zapaleni, --,
+ {{0xa3781116,0x2956a01a,0x00000000,0x00000000}}, // _gacanta_, _sellaine, --, --,
+ {{0xb9403070,0x080b90ca,0x32fc9232,0xab0ae004}}, // _اپريل_, _समुद्री_, _irtibat_, _צוואנציג_,
+ {{0x54dac229,0x7245812b,0x2d363126,0x6387f031}}, // _можно_, _rámci_, _افروز_, _heure_,
+ {{0x17a200fc,0x59d20153,0x468120ca,0xb477c061}}, // _interesg, _interest, _ज्यान_, _манера_,
+ {{0x286d72b5,0x02025112,0x2ebd7090,0x62a7c107}}, // _personas_, _estivo_, _personat_, _мадона_,
+ {{0x734c40ff,0xebf51061,0xa84440bf,0xd6ec2065}}, // _francúzs, _produktų_, _ahàmù_, _وتصميم_,
+ {{0xd26c201c,0xb9d7a0de,0xdc72c061,0x1503716f}}, // _nakon_, _प्रयास_, _sportas_, _zastupao_,
+ {{0x327e016e,0x23f4611a,0x42fc705d,0x6464d173}}, // _duine_, _mouton_, _nange_, _дзень_,
+ {{0x2280b0f9,0x2fc4c1f4,0x8fa0a19b,0x04852111}}, // _diskite_, _अधकचरा_, _رجیستری_, _हैरानी_,
+ {{0x02cbf0f8,0x7defd0ea,0xf394a22e,0x00000000}}, // _gaude_, _하이파이브_, _tibsi_, --,
+ {{0xc9b14255,0xbe714144,0xef2400a2,0x16db31da}}, // _ostatní_, _ostatné_, _došao_, _densità_,
+ {{0xcc93c047,0x517e01b8,0xf975d0de,0x00000000}}, // _ساعات_, _поколени, _एसोसियेश, --,
+ {{0xe2da600c,0xc250e12c,0xe8d0e12c,0x2458213b}}, // [3c0] _derece_, _directem, _directeu, _国家文物局_,
+ {{0xf84c2020,0x950a416f,0xafcbc045,0xc72c02b6}}, // _있었습니다_, _protivio_, _свободи_, _hemmelig,
+ {{0xf604807b,0x44ce4017,0x60e4d23a,0x37b61143}}, // _সাবস্ক্র, _написани_, _आईसीसी_, _unustasi,
+ {{0x99fa41bf,0x03806054,0xedd9b173,0xedbfc2b7}}, // _pershend, _suurem_, _схемах_, _marufuku_,
+ {{0xbe0200f9,0x5b1c00ea,0x1c5321d7,0xee7c6196}}, // _enteresa, _적극적으로_, _chatain_, _पॉर्न_,
+ {{0xc61a7031,0xd19b528d,0x48d740cb,0xa2026051}}, // _评论内容只代表网, _mercadol, _ناگزیر_, _yarima_,
+ {{0x161fc116,0x5290b143,0x0493e045,0xd395000a}}, // _muslimii, _endale_, _стратегі, _prassi_,
+ {{0xcb9090cc,0xb4e800cc,0x222541ff,0x5fbd7078}}, // _סטודענטן_, _טערמין_, _boeken_, _abaniṣer,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x927ed2b8,0x92786086,0x794e408f,0x4ae630fe}}, // _kwenu_, _umunsi_, _質問した人からの, _সমবায়_,
+ {{0xfbf1f108,0xd6d07176,0x8fe142b9,0x3248d154}}, // _postitus, _जैविक_, _maskiner_, _chombo_,
+ {{0xa1ff7215,0x6c381144,0xf35f10b5,0xffa15089}}, // _papildom, _generáci, _objekty_, _intentar_,
+ {{0xbcd32020,0xf291513b,0xff6092ba,0x435691cb}}, // _부담하셔야_, _enfant_, _יאָאַכים_, _revenir_,
+ {{0xd2365122,0xbb89d2bb,0x8ae6b01b,0x1a179018}}, // _voljo_, _noviembr, _parlimen_, _calculat,
+ {{0x1301f091,0x72017047,0x2e4e41e4,0x00000000}}, // _paratoi_, _cnaipe_, _afragana, --,
+ {{0xe38661d6,0x7f060031,0xd00b307b,0xc2331271}}, // _dooro_, _采煤工作面_, _চাঁপাইনব, _formado_,
+ {{0x8152113b,0x7294f267,0x2a2a6211,0x00000000}}, // [3d0] _已经本网协议授权, _ababazzi_, _maschine, --,
+ {{0xf3c32047,0x52da52bc,0x326c20dc,0x3b8b0106}}, // _عُمان_, _hotell_, _takon_, _асочиаци,
+ {{0x32c381e8,0x23cf80a6,0x8bf7c0c3,0xdd5ac100}}, // _ukoliko_, _terve_, _билете_, _болей_,
+ {{0x72fc61a3,0x6950f104,0x427e60f7,0xa253a02b}}, // _drogi_, _abalande, _stond_, _klientam,
+ {{0xe33cc034,0x1a739198,0x226c22bd,0x1fd39018}}, // _близо_, _obično_, _sakon_, _פנסיה_,
+ {{0x88db1134,0xd6015008,0x9f4a22be,0x364cb2bf}}, // _gwleidyd, _commerci, _रामधारी_, _मण्डल_,
+ {{0x09b732c0,0x72fce2c1,0x127e91c7,0x00000000}}, // _polasaí_, _stigla_, _etang_, --,
+ {{0x0e553234,0xa68c615c,0x73ced1f5,0xd2da6153}}, // _महत्वपूर, _दुलार_, _leevi_, _reports_,
+ {{0x67a8207f,0x0c70a026,0x1201f2b2,0xe2947170}}, // _februari, _मुर्गा_, _muziko_, _funkcii_,
+ {{0x93fac034,0xc3a290bf,0x726dc0eb,0xaf83c0cd}}, // _вторник_, _ipapo_, _prvog_, _लश्कर_,
+ {{0x658ed105,0xdc7102c2,0x00000000,0x00000000}}, // _akademiy, _मान्छे_, --, --,
+ {{0x525a01b1,0xa292705d,0x330cc057,0x83eb7089}}, // _exile_, _masaka_, _isyarat_, _teatre_,
+ {{0x199900a9,0x726d1136,0x79f2c1d7,0x9ec5c050}}, // _traži_, _guzom_, _acarsaid, _славни_,
+ {{0x89e5508d,0xeae2f0a9,0xb41fe01c,0x888fe29e}}, // _indodana_, _pretpost, _psiholoz, _psixolog,
+ {{0xbef08061,0xed8860d8,0x1a6700ea,0x5c6bc26d}}, // _elektrin, _penerbit_, _드리겠습니다_, _akkurat_,
+ {{0x020801ef,0xf2a69206,0xac615016,0x521c61b2}}, // _informát, _klabu_, _situata_, _nathis_,
+ {{0x626ca205,0x437d4093,0xb224028f,0xeb564119}}, // [3e0] _lubos_, _japanse_, _chike_, _khatarta_,
+ {{0x126d8248,0xce1b6276,0xfbf130cb,0xb2fa005d}}, // _otros_, _singkata, _بھگدڑ_, _abahaya_,
+ {{0x729b713f,0x406da008,0xacbec061,0xba34d04d}}, // _linkhay_, _הסיבה_, _самец_, _をご覧ください_,
+ {{0x03fc11da,0x2d03803e,0x6c2be2ad,0xec6c1008}}, // _naturali_, _bolungar, _ставили_, _natural_,
+ {{0x63eae2c3,0x738d606a,0xe27c213f,0x82e06193}}, // _lantai_, _kulturál, _lênin_, _jednako_,
+ {{0x00e1a018,0xdaecd018,0x00000000,0x00000000}}, // _לדאוג_, _accessib, --, --,
+ {{0x125af159,0x22da7056,0xa395406c,0xa3ebe267}}, // _lailai_, _adress_, _elesin_, _batta_,
+ {{0x5faa5031,0x73cf01c9,0xf13ba018,0xfdebe14d}}, // _字母不区分大小写_, _gravar_, _ללחוץ_, _стварно_,
+ {{0x87d0d07a,0x726cb2a3,0x33545070,0x414581fb}}, // _снегу_, _radova_, _بہتیرن_, _בושות_,
+ {{0x32cbf03f,0xaf945159,0x00000000,0x00000000}}, // _nauda_, _adéye_, --, --,
+ {{0x77fd0047,0x4b2ad13c,0xb051d05e,0x62d8d21b}}, // _اتفاقية_, _сцене_, _panginoo, _kunene_,
+ {{0x52fc91b2,0x43e58076,0xcff79018,0xb26c503b}}, // _iragi_, _wątek_, _בעתיד_, _kalon_,
+ {{0x74d690ca,0xfb2b21ce,0x7f8a6004,0x00000000}}, // _मिडिया_, _paradics, _פֿעסטיוו, --,
+ {{0x62d85039,0x8373a0af,0x6076112f,0x00000000}}, // _meleg_, _domače_, _seapáini, --,
+ {{0x23869056,0x62bbd0b9,0x5dd2d01a,0x25042133}}, // _klart_, _bekende_, _стало_, _ciallach,
+ {{0x02fd7047,0xc3f1a03c,0x5b26c229,0x3631e0f8}}, // _leagan_, _मध्यस्थत, _одном_, _aplikazi,
+ {{0xee5180a4,0x803731ab,0xc30eb056,0x219c1018}}, // [3f0] _काव्यप्र, _ristoran, _betalar_, _ולפגוש_,
+ {{0x13875049,0x665d4025,0x6202611c,0x36839002}}, // _oferty_, _समाधि_, _miring_, _दालान_,
+ {{0x32b1e101,0x0292718f,0x32926119,0x32fca1bc}}, // _bindiga_, _fasaha_, _yaraan_, _pedidos_,
+ {{0x0c835065,0x022501e7,0xcf5bd057,0x6379509e}}, // _بصراحة_, _shakur_, _bahagian, _batanga_,
+ {{0x62fd710c,0xa3ebf022,0x1b26c13c,0xe3dc01d2}}, // _beagan_, _bauta_, _одбор_, _kaiwa_,
+ {{0x427e912f,0x87aa90fe,0x8394208d,0x00000000}}, // _seans_, _খোমেনী_, _unksz_, --,
+ {{0x11cc303b,0x6f1fa2c4,0x0ff31006,0x7212d1b2}}, // _policisë_, _vurderin, _कलाकारों_, _neohom_,
+ {{0x3aa1c09d,0xeeefc0eb,0x00000000,0x00000000}}, // _licytuje, _одобри_, --, --,
+ {{0x726c50a0,0xa378614e,0x23dd9069,0x1019b0cb}}, // _galon_, _maganda_, _ntsws_, _پروٹیز_,
+ {{0xcf33c08a,0x43b9a2c5,0xb9572179,0x6417f0fc}}, // _presenta, _serveru_, _edelline, _horrekin_,
+ {{0xa2926088,0x62b63106,0x9a4f4052,0x00000000}}, // _paraan_, _антериор_, _haleluya_, --,
+ {{0x84b3a11c,0xe394700d,0x240e72c6,0x4c8e7062}}, // _تمرینات_, _minsi_, _zadržana_, _zadržava_,
+ {{0xc26c5116,0x32ca718b,0x785e104d,0x2da11054}}, // _kaloo_, _myndi_, _産学連携本部_, _fotograa,
+ {{0xf30a5230,0x206d8140,0x0224c247,0x00000000}}, // _najnovij, _propisan, _chokwa_, --,
+ {{0xe48741f4,0x526d8229,0x08dd903d,0xb201a0b3}}, // _नाजायज_, _euroa_, _ベストアンサ_, _novine_,
+ {{0x69bce0c2,0xe540912f,0xd1868170,0x43806269}}, // _коментир, _النجاح_, _otvoriť_, _suurim_,
+
+ {{0x534550a9,0x53a1d02c,0x147d92c7,0x00000000}}, // [400] _spadaju_, _kaupunki_, _नमुना_, --,
+ {{0xd48520de,0xc2fc7051,0x910310a2,0xb98310a2}}, // _संपादक_, _zanga_, _građana_, _građani_,
+ {{0x1097e077,0x03eb9006,0x2677203d,0xac5280f9}}, // _времени_, _listy_, _高等専門学校_, _haitian_,
+ {{0x039501f1,0xa0ce70eb,0x0830f028,0x127e0008}}, // _časti_, _sadržaja_, _termaktu, _bring_,
+ {{0xa72c12c8,0x05b8f13b,0xd3795074,0x00000000}}, // _miloševi, _元以下罚款_, _kudaden_, --,
+ {{0xa145219c,0xf9836143,0xfd165008,0x1200d131}}, // _materjal, _konkreet, _insuranc, _milieu_,
+ {{0xc27e60f5,0x2224017f,0xf378605d,0x11bc1117}}, // _huong_, _usiku_, _baganda_, _täynnä_,
+ {{0xd2cb4068,0x5d00c0fe,0xf4a68196,0x00000000}}, // _bieden_, _পরাজিত_, _सौराष्ट्, --,
+ {{0x4394d0ff,0x1a897185,0x7a5d219d,0xf00ed0a2}}, // _miest_, _jaringan_, _diterima_, _ostvaren,
+ {{0x7c69f2b2,0x3948f06f,0x03c87054,0xeb739099}}, // _burundi_, _комплект_, _tervis_, _uslugama_,
+ {{0xa2b581ab,0x175d00da,0x5236d1c6,0x427ef099}}, // _circa_, _कार्ड्स_, _projek_, _skinem_,
+ {{0x6e4242af,0x49c192c9,0xd25ae0c5,0xa2fc724a}}, // _संस्थाहर, _साँचो_, _meille_, _xanga_,
+ {{0x6c5a5083,0x4f859018,0x227e616d,0x72d301a1}}, // _menteri_, _מורשה_, _nuong_, _mestilah_,
+ {{0x434d525a,0x00000000,0x00000000,0x00000000}}, // _महसूस_, --, --, --,
+ {{0x742a601a,0x620140c4,0x253b919d,0xd06db067}}, // _правильн, _vidiet_, _menyatak, _फारसा_,
+ {{0x627e614e,0x7b19719e,0xc189705e,0x72926037}}, // _buong_, _barangan_, _barangay_, _pirang_,
+ {{0x72d820c3,0xb7bbb008,0x8226d065,0x13788105}}, // [410] _camere_, _בפועל_, _مراكز_, _kazanda_,
+ {{0x12d94116,0x6c4b1102,0xe394d03f,0xe2bbe137}}, // _sheego_, _bintang_, _viesu_, _nijedne_,
+ {{0xc2d85039,0xc394017f,0x00000000,0x00000000}}, // _veled_, _raisi_, --, --,
+ {{0xf26df0c5,0x2c621024,0xd3940077,0xb22401a2}}, // _auton_, _forumun_, _saisi_, _saiki_,
+ {{0xae38f0e6,0x64872290,0x52025008,0xb27e60b2}}, // _veransta, _निभावत_, _entire_, _guong_,
+ {{0x225a3156,0x1121b06e,0x00000000,0x00000000}}, // _zemlju_, _akilápá_, --, --,
+ {{0xe225504d,0xc27e0089,0xc92c30bf,0x4d5ab1c1}}, // _でご注文いただく, _quina_, _aimuṣiṣẹ_, _फर्निचर_,
+ {{0x93a92008,0x427ee25d,0x32ee92ca,0x22026119}}, // _private_, _seinna_, _agafe_, _arrimo_,
+ {{0xc27e6048,0x608c0065,0x73fe113b,0x5290f241}}, // _xuong_, _بيقولوا_, _日一周时政要闻_, _nemaš_,
+ {{0xa59c9031,0xde9b1042,0xb7aa60b9,0x927f012c}}, // _修改删除记录_, _bindings_, _letterli, _grands_,
+ {{0x585c9077,0x598e1175,0x00000000,0x00000000}}, // _maalisku, _нации_, --, --,
+ {{0x23eaf2b4,0x90b2d1f9,0x459ef03d,0xaae3e175}}, // _editar_, _পল্লী_, _クレジットカ_, _интереси,
+ {{0xf22b41f1,0x5313109a,0x72d80036,0xe27e92cb}}, // _upraviť_, _cambiar_, _красавиц, _saknar_,
+ {{0x32365136,0x7f16515b,0xbcaba2cc,0xa46161dd}}, // _bolji_, _maatskap, _menyadar, _hondartz,
+ {{0xb2001158,0x24f8520d,0x1ee7b13f,0x32ebb1a1}}, // _lehin_, _otomobil_, _vnsharin, _fizikal_,
+ {{0x5ac05054,0x4290a163,0x62efe1b4,0xd3797142}}, // _pakkumis, _izjavu_, _menimpa_, _ukrajna_,
+ {{0x63dc2172,0x00000000,0x00000000,0x00000000}}, // [420] _bakwa_, --, --, --,
+ {{0x73f46146,0xc9c4b128,0x0b2f40c5,0x5b643024}}, // _nettet_, _rezervat, _varastos, _fondunun_,
+ {{0xb24a52cd,0x8e95a004,0xa575b050,0x00000000}}, // _resolusi_, _צופרי_, _utilizaç, --,
+ {{0x52fce1c5,0xcea1f2ce,0xfde7f1bc,0x6829e208}}, // _tinggi_, _posljedn, _личните_, _thàinig_,
+ {{0x32cb7072,0x41b3d17a,0x53f871d9,0xb2fc50de}}, // _stadig_, _доволен_, _genug_, _valge_,
+ {{0xa27e91b1,0x405782cf,0x33807002,0xd7af70a0}}, // _muang_, _inostran, _korras_, _gynullei,
+ {{0x217d82d0,0x5ae53128,0x2c7d60e9,0x00000000}}, // _דיעות_, _sigurant, _pressan_, --,
+ {{0xd341e049,0x13c1302c,0xf365905e,0x00000000}}, // _allegro_, _сведения_, _pangako_, --,
+ {{0x876131aa,0x922410b5,0xc30df02b,0x9d1bd13b}}, // _двери_, _regionál, _nekilnoj, _peinture_,
+ {{0x32d8715e,0xf27e00b3,0x244240ea,0xf27e70a1}}, // _tvnet_, _brine_, _사회복지과_, _munne_,
+ {{0xa9803190,0x675ce1c6,0xfee03190,0xdba03061}}, // _konkursa_, _kakitang, _konkurss_, _konkurso_,
+ {{0x44303268,0x12001187,0x13203236,0xd3aac2d1}}, // _concurso_, _behin_, _concurs_, _dozvole_,
+ {{0xec68d056,0x9c361177,0xcad730f8,0x91c6b1ea}}, // _augusti_, _grantiau_, _bistarat, _postopek_,
+ {{0x3d88204d,0xeeed5269,0x00000000,0x00000000}}, // _質問者のみ_, _salvesta, --, --,
+ {{0x026d90c4,0xa57cc04d,0xad1372d2,0x058071a1}}, // _kusov_, _ジに対するお問い, _kunstner, _televisy,
+ {{0x59b96032,0x927f4206,0x52d870c5,0xb246c02b}}, // _растител, _kwenda_, _menee_, _аднак_,
+ {{0x92fc71df,0x9c7d6077,0xad60714c,0x9290c1e2}}, // [430] _yango_, _viestin_, _इन्स्टाल_, _halako_,
+ {{0xf201e039,0xf3648147,0x4cec1017,0xa27ed0d2}}, // _akció_, _tingnan_, _компаниј, _weens_,
+ {{0xfbf8308a,0x1f71c031,0xfc91e23b,0x1b8a125c}}, // _নেটওয়ার্, _concours_, _अवरोध_, _fronteir,
+ {{0x8d3c3096,0x3c6541be,0x00000000,0x00000000}}, // _velikost_, _buruzie_, --, --,
+ {{0x803dc0ea,0x626ff12e,0xe7e1f108,0x674de08f}}, // _mogelijk_, _tvrdošín_, _अर्धांगि, _マンスナイプ_,
+ {{0x026c207e,0x801db24a,0x16973055,0xd387726b}}, // _lemond_, _tradisyo, _bartamah, _charge_,
+ {{0x227f700d,0x259b31dd,0xae1ce085,0x0d127004}}, // _ibanze_, _horrelak, _pergunta, _אַביסל_,
+ {{0xd27e7045,0x6d22b006,0xab08504d,0x0aaae0de}}, // _kunne_, _zagranic, _アルバイト_, _पहिलहीं_,
+ {{0xa2ad112f,0x627872d3,0x214c31a5,0x05b350b7}}, // _مليار_, _barnet_, _cinephil, _бэрбэтеш,
+ {{0x7200c2d4,0x826d8002,0x93878002,0x6366f175}}, // _online_, _eurot_, _korra_, _реалност_,
+ {{0xbdc5e18c,0x5290500c,0x4b68413f,0x68e2f0c3}}, // _elektora, _emlak_, _enlefzin_, _депутаци,
+ {{0x85845144,0xc0f0707b,0x748602d5,0xc9d8517f}}, // _fotogalé, _নিখোঁজ_, _ड्रामा_, _kutekele,
+ {{0x6303f20d,0xf2ebc0fd,0xe30da0b2,0xf17360c4}}, // _teknoloj, _kvinner_, _pokézoo_, _pomníky_,
+ {{0x32e5e2c6,0xc6d380c3,0xd67271b2,0x00000000}}, // _hronika_, _пенализа, _waldensi, --,
+ {{0x3c6a82d6,0xb2f45047,0x3200211d,0x3d98e179}}, // _चिन्ता_, _chuntas_, _cekin_, _начинающ,
+ {{0xe343b12f,0x02a06193,0x9f239271,0x78c391d4}}, // _بتوقيت_, _ljubav_, _conectan, _conectad,
+ {{0xabfa52d7,0x6af91119,0x5e73c13b,0x36c6425a}}, // [440] _vietcomb, _carruurt, _构建社会主义和谐, _अज्ञानता_,
+ {{0x9b5a725a,0x00000000,0x00000000,0x00000000}}, // _मिर्जापु, --, --, --,
+ {{0x237b9147,0x3271b0b0,0xd3484035,0x00000000}}, // _malalim_, _mabinogi_, _seleksi_, --,
+ {{0xd2fc7181,0x02a0a0ea,0x00000000,0x00000000}}, // _gange_, _궁금합니다_, --, --,
+ {{0xe27e90f5,0xfe70b108,0xa4b5b006,0x02ee90dc}}, // _quang_, _कुम्भ_, _poradnik, _stafi_,
+ {{0x7e2631bb,0x2277d03c,0x85f63221,0xcefdc065}}, // _сайтам_, _maksymal, _veteriná, _الرغم_,
+ {{0x82fc70a1,0xd2da6060,0x89d9303e,0x4394203f}}, // _bange_, _kereke_, _seinasta_, _maksu_,
+ {{0x468c10cb,0xae56b12c,0x89727017,0x52b4d03c}}, // _mindenki, _dimanche_, _редакциј, _niech_,
+ {{0x5f4e004f,0x869790fd,0x8349e1d6,0x0c7390cf}}, // _personne_, _видань_, _adreesi_, _kadrlar_,
+ {{0xde1dd07b,0xd2fc705d,0xf27e90ed,0x32f30185}}, // _দাপ্তরিক_, _wange_, _ruang_, _kalimat_,
+ {{0x2d7dc00b,0xf3a3f076,0x5c60526c,0x952480ea}}, // _bermanfa, _grupy_, _sigurta_, _롯데캐슬비치_,
+ {{0xec5b80b5,0x33ce90fa,0x81652017,0x73e2d02a}}, // _dostali_, _agava_, _одсто_, _відразу_,
+ {{0x69fe60f8,0x052c5223,0x6e169036,0x905e603b}}, // _ekonomia_, _प्राथमिक, _значение_, _ekonomik_,
+ {{0x9b73e106,0x02fc705d,0x72eb2046,0x709260fe}}, // _ротару_, _zange_, _digidol_, _ইউসুফ_,
+ {{0xb60de025,0x16d5c055,0x2588d033,0xf2fc71f5}}, // _बांदा_, _saraakii, _activité, _yange_,
+ {{0xdf0120de,0xa477a0c5,0xdf7f4136,0x322582b6}}, // _अंग्रेजी_, _проекты_, _aktivnoš, _virke_,
+ {{0x8381b140,0xec55b294,0x00000000,0x00000000}}, // [450] _verziju_, _instead_, --, --,
+ {{0x13ead230,0xb2f520d3,0x22902037,0x4200203e}}, // _protiv_, _laistigh_, _tekan_, _tekin_,
+ {{0xa2c94091,0x62a0a0ea,0x061f200e,0x1c3040fe}}, // _pellach_, _제공합니다_, _abaneesi, _পেঁয়াজ_,
+ {{0x4e452102,0x029020a4,0x220020f6,0x0ad232d8}}, // _pelangga, _rekan_, _rekin_, _šampiona_,
+ {{0xc2137084,0x5601503d,0x40760190,0x3200201a}}, // _arahan_, _kommerci, _aksesuār, _sekin_,
+ {{0xeceb8291,0x0e964033,0x1486a2d9,0xb2efc144}}, // _अधिकतर_, _gratuite_, _ल्याउन_, _komisia_,
+ {{0x628611fb,0x5f46311a,0xd2d76136,0x00000000}}, // _לשונות_, _aristide_, _odvojio_, --,
+ {{0x13946153,0x0367c07b,0xc2fa01f5,0x1349e142}}, // _those_, _দুঃখিত_, _swahaba_, _stretti_,
+ {{0x794f702e,0xef77a19b,0x2d57c017,0x735431e1}}, // _samostat, _کیبورد_, _назван_, _galegas_,
+ {{0xa3be61c7,0xb17f2175,0x4db5404a,0x1ba960a4}}, // _privasi_, _ученицит, _meningga, _karyawan_,
+ {{0x8b8b816c,0x9c27a0c3,0xa2b44170,0x11455059}}, // _timisoar, _примиря_, _ramci_, _espesyal,
+ {{0x8a69f034,0xeb4210b5,0x63797099,0x7bd1607f}}, // _финансов, _doporuču, _boravka_, _conducer,
+ {{0xa3eb9041,0x02d55080,0xd7c0613b,0x00000000}}, // _byste_, _levoča_, _抗震救灾特别专题_, --,
+ {{0x740012ae,0x1ec3f0ca,0x52d94055,0x4256d07b}}, // _песни_, _राजकुमार, _sheeko_, _ডিগ্রী_,
+ {{0xd431e006,0xd2904101,0xa301e0e1,0x9e51e1a3}}, // _ostatnio_, _neman_, _ostatni_, _ostatnie_,
+ {{0x202ed100,0x8212b24c,0xbc27a175,0xf5d2e03b}}, // _назву_, _eadhon_, _примери_, _studentë,
+ {{0x9303014e,0xac765100,0xaeec4031,0x751cf166}}, // [460] _salamat_, _forumas_, _编译或摘编自其它, _प्रणब_,
+ {{0x52e96252,0x7a896252,0x49f96252,0x00000000}}, // _arrinta_, _arrintan_, _arrintaa_, --,
+ {{0x52926252,0xa2b470ef,0x00000000,0x00000000}}, // _karaan_, _sincs_, --, --,
+ {{0x41ecd076,0x22fce19b,0xf2d90218,0x00000000}}, // _हाथरस_, _nangis_, _arbeid_, --,
+ {{0x129020a2,0xa3cf0171,0x434c2286,0x00000000}}, // _rekao_, _suaves_, _cestovné_, --,
+ {{0x99fa30ae,0x02d8e208,0x2e389043,0xe2005134}}, // _starptau, _juneau_, _irenaeus_, _melin_,
+ {{0xa13f30f5,0x85aa5037,0xdd2f013b,0xf87b8094}}, // _kemulato, _pengaruh, _arrière_, _קלונימוס_,
+ {{0x04363274,0x56ae807b,0x6ac80004,0x00000000}}, // _praktick, _বেসামরিক_, _בערלין_, --,
+ {{0xf87cc045,0x3c493236,0x00000000,0x00000000}}, // _поставле, _circulac, --, --,
+ {{0xb349c0d4,0xc2ac402a,0x868cc002,0x4c0fb24c}}, // _livelli_, _контроль, _नादान_, _uaigneac,
+ {{0x239590a6,0xad73a017,0x3b8091c7,0x225a9007}}, // _sisse_, _познато_, _دورتموند_, _ajala_,
+ {{0xbb343229,0x5394b0b9,0xe3d5b02b,0xa290505e}}, // _интересн, _meeste_, _platinti_, _kelan_,
+ {{0xfb1c209a,0x5db792da,0x72d551c0,0xee96d19b}}, // _respuest, _заводы_, _istorya_, _برروی_,
+ {{0x1d032037,0x52b1d2db,0x82d89046,0x8aa5c124}}, // _خدمات_, _टेबुल_, _adael_, _gurigiis,
+ {{0x4401a215,0xb55dc117,0x3c7622dc,0x1952618d}}, // _аспекты_, _вполне_, _मुल्ये_, _billigst,
+ {{0x427e905e,0x39e4a03d,0xe2b182dd,0x52c53222}}, // _ngang_, _ありがとうござい, _pridala_, _ionline_,
+ {{0x484ed227,0x324bd0e1,0xfc124154,0x929042de}}, // [470] _publicid, _ट्रेलर_, _kampuni_, _zeman_,
+ {{0x71a41004,0x54e2107b,0xa2fe725d,0x7c76805c}}, // _שניאור_, _নওগাঁ_, _margar_, _नवग्रह_,
+ {{0xd17db008,0xed732210,0x00000000,0x00000000}}, // _ריהוט_, _posmatra, --, --,
+ {{0x9387e282,0x4224d1df,0x7c14313b,0xc26cc0bd}}, // _votre_, _bheka_, _没有相关文章_, _padon_,
+ {{0x71f3c0c3,0xc2d912df,0x18c40025,0x42005124}}, // _войник_, _emberi_, _ओलंपिक_, _celin_,
+ {{0x329040a4,0xe9c2f08e,0xe2fc704e,0x3713b04d}}, // _teman_, _vrednost, _langa_, _アビシニアン_,
+ {{0x0305a0d8,0xe15790de,0x73162043,0x935b8113}}, // _dimasak_, _मल्लाह_, _xubntiag_, _zeemzeeg_,
+ {{0x92fc725d,0xe9c9b136,0x72005075,0x00000000}}, // _ganga_, _odgovara_, _felin_, --,
+ {{0x82005119,0x60361184,0x4b8931ae,0x93eb427b}}, // _gelin_, _guverino, _diciembr, _lwethu_,
+ {{0x727e615a,0xaf0a5208,0xe387c0b5,0x903c0018}}, // _grond_, _mhàin_, _stará_, _selectio,
+ {{0xd62130a8,0x6290c037,0x80e0c117,0xe59ee144}}, // _befolkni, _melati_, _вновь_, _manipulá,
+ {{0xcb444062,0xdb3130cb,0xbefd50ea,0x8c25418d}}, // _интересо, _جنگجو_, _확인하시기_, _kombiner,
+ {{0x4290c239,0x98a7a175,0xff21e013,0x727f7267}}, // _omdat_, _правец_, _rotterda, _abange_,
+ {{0x52bb108f,0x89eba13b,0x4f619082,0xd96f726e}}, // _seneste_, _由县级以上人民政, _століття_, _acontece,
+ {{0xa29270a2,0x620050a0,0xa288712c,0x6c18e11e}}, // _moraju_, _delio_, _fichier_, _garanter,
+ {{0xde435153,0x2f4350e6,0x0248d0a2,0xb3485185}}, // _password_, _passwort_, _svemu_, _koneksi_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [480] --, --, --, --,
+ {{0x779d9181,0x9c5e304f,0xe4ec30fe,0x00000000}}, // _トラックバック_, _portail_, _যাকাত_, --,
+ {{0x97b59070,0x7c54a134,0x00000000,0x00000000}}, // _amelynek_, _gostwng_, --, --,
+ {{0x727e6195,0xe36ab031,0x14d77008,0x7b7b8102}}, // _trong_, _文化东路街道办事, _אנציקלופ, _biasanya_,
+ {{0x62fc9119,0xaa7b2098,0x00000000,0x00000000}}, // _kaaga_, _opište_, --, --,
+ {{0x0c5371c5,0x35a9510b,0x125ad0dc,0x92d800b9}}, // _pertama_, _formulár_, _sjell_, _kliek_,
+ {{0xbc71e002,0xec6a5143,0x286860de,0x7290e119}}, // _पिज्जा_, _गुड्डू_, _ekspress_, _zenawi_,
+ {{0x3785d2e0,0x0f60d0b5,0x4b23d055,0x42b4d1ec}}, // _प्राकृति, _displeje_, _isbahays, _theca_,
+ {{0x77e72017,0x86d72062,0x32f73065,0x367f6187}}, // _архива_, _архиве_, _indiach_, _venenati,
+ {{0x33086078,0xcefd2025,0x2301e0d8,0x027ed1df}}, // _adebayo_, _पेशकश_, _piranti_, _ngena_,
+ {{0xa740c031,0x91e730a4,0x5f18d082,0x42247051}}, // _科学技术部_, _चेहर्_, _публічно, _yanki_,
+ {{0x69ec811c,0x69f7b0b5,0x83dd00f1,0x82da70c3}}, // _فدراسیون_, _dodavate, _piawai_, _curent_,
+ {{0x6947e175,0xd1e370a6,0x00000000,0x00000000}}, // _предлог_, _पीआरओ_, --, --,
+ {{0x15ae40a4,0xb3f89122,0x66e2f0c6,0x00000000}}, // _पाहता_, _nakupu_, _životníh, --,
+ {{0x427e61fd,0x920070d7,0xb38a22cb,0x5686510c}}, // _huono_, _tenia_, _körde_, _acadamai,
+ {{0x8d9841a3,0xd236316f,0x5162d076,0x730c2268}}, // _momencie_, _reljef_, _zabronio, _romanos_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [490] --, --, --, --,
+ {{0xf9b6c07b,0x1f2e3202,0xe224714b,0xfee500a4}}, // _শতাধিক_, _comunida, _sanki_, _राष्ट्रा,
+ {{0xc3a91008,0xbde91153,0xff991153,0x5f1610b9}}, // _provide_, _provided_, _provides_, _predikan,
+ {{0x72fcd054,0x32fae1a3,0xdae7f056,0xcf27f056}}, // _isegi_, _informuj, _intresse, _intressa,
+ {{0x72903084,0xd98e1010,0x48eec065,0x77840263}}, // _semasa_, _наших_, _تصنيف_, _प्रासंगि,
+ {{0x233d12e1,0xa25ad2e2,0x9ad68195,0x72ad9140}}, // _دیدار_, _heller_, _сайтах_, _nekadašn,
+ {{0x4f051026,0xb7bbf0a2,0x9ccc1062,0xbc75710c}}, // _प्रेमचंद_, _međutim_, _служи_, _adhradh_,
+ {{0xc58020f3,0x41c30241,0x81e6c070,0xaf2202e3}}, // _informaç, _sastanak_, _تباہی_, _realizar_,
+ {{0x03f471c4,0xe27ee0bd,0x3290e06e,0xf84fd0a0}}, // _ersten_, _mennen_, _abiamo_, _ymholiad,
+ {{0xe2cab161,0x89eed27b,0x0e5f612f,0x72480074}}, // _fredag_, _bacteriu, _المؤمنين_, _ilimi_,
+ {{0x12d841a7,0xb27ee040,0x727f717f,0xd3f8c07c}}, // _immer_, _meinen_, _uwanja_, _salute_,
+ {{0xb29d60b9,0x9395925e,0x33868204,0x12d8c0bd}}, // _verkoop_, _fissi_, _sakrij_, _malere_,
+ {{0x283240ea,0x23eae134,0x9313c07b,0x00000000}}, // _잊으셨나요_, _menter_, _হলিউড_, --,
+ {{0x3e7352e4,0xa7c0d07b,0x20ec12c0,0xd27ef1b2}}, // _दोस्त_, _আবারো_, _speisial, _bannor_,
+ {{0x83948095,0x9f4c303d,0x770b9047,0x03c5811c}}, // _hanggang_, _ガイドライン_, _custaimé, _جادویی_,
+ {{0x48c53171,0x4f3a109f,0x826a10ac,0x7f24023c}}, // _reservad, _amerikaa, _amerikal, _गुलजार_,
+ {{0xf21ee0fe,0x3d306229,0x00000000,0x00000000}}, // [4a0] _ক্রমানুস, _lappeenr, --, --,
+ {{0xfae5f008,0xad0fa008,0x261c7229,0xf50cd1a3}}, // _characte, _עמותת_, _mielenki, _automaty,
+ {{0xb3fd6031,0xd2307007,0x72493078,0x6e57312e}}, // _国家发展和改革委, _abameta_, _abamì_, _nenašiel_,
+ {{0x7d1360dc,0x19805013,0x4fdca16a,0x739421c0}}, // _konsider, _artikele, _velikono, _laksi_,
+ {{0x23eae077,0x9ecb917f,0xbeb14090,0x43008160}}, // _tuntuu_, _kulingan, _evropian, _tapaidh_,
+ {{0x7340128c,0x23031090,0x02cae192,0x42d8c267}}, // _licenza_, _allahut_, _fundet_, _muleke_,
+ {{0xc27ee0f7,0x75eb30ea,0xb9489098,0x00000000}}, // _kennen_, _공정거래위원회_, _हालसम्म_, --,
+ {{0x027e915d,0x573eb17e,0x2249411a,0x93eac069}}, // _trang_, _спидомет, _premye_, _lwmtus_,
+ {{0xa3949124,0x927ee1e5,0x59f5705c,0x127e90da}}, // _taasi_, _keinen_, _डेस्कटॉप_, _urang_,
+ {{0xdc1c30c4,0x9395903e,0xc8fe1134,0xb045c068}}, // _nitrians, _vissi_, _dadansod, _overigen,
+ {{0xc2cae192,0x32489038,0xf39401aa,0x0394c065}}, // _kender_, _alama_, _upisa_, _diosca_,
+ {{0xa516411c,0xf37961e4,0xeb42127d,0x1316d0dc}}, // _بازنگری_, _furasta_, _doporuče, _poezi_,
+ {{0x53b811fe,0xae88b047,0xb26c7091,0xcc5c62c7}}, // _provozu_, _أوباما_, _hynod_, _निषेध_,
+ {{0x22fce19e,0x90c0512a,0x3c6ff1ab,0x51112076}}, // _mangsa_, _summersl, _storico_, _białysto,
+ {{0x797650f3,0x7c5052e5,0x9f6ea0a4,0xf29081df}}, // _atendime, _कालेज_, _अहमदनगर_, _kakade_,
+ {{0xa2caf0e6,0x06ba1237,0x9bb3c049,0x455d2065}}, // _leider_, _barangsi, _शास्त्रो, _جزائرية_,
+ {{0x324802b8,0x44b181bb,0x3ecf706f,0xa3993249}}, // [4b0] _ulimi_, _асфальт_, _posizion, _läser_,
+ {{0x19add06a,0xf3ebf2c3,0x99233025,0x050680b7}}, // _تجربہ_, _ikuti_, _रेंजर_, _алфабету,
+ {{0xe88060fc,0xa3f461a0,0xc0f0603b,0xcfe06147}}, // _artikulu, _vertel_, _artikull, _artikulo,
+ {{0x86e6004b,0xcb61c1f4,0x4175c070,0x6395e024}}, // _kompaniy, _भेजनी_, _تذکرہ_, _vitse_,
+ {{0xae99212f,0x322ac02f,0x8212e052,0x329112aa}}, // _وبعدين_, _мовах_, _elihle_, _albais_,
+ {{0x929072e6,0xdbcc0008,0x8ef0d0a4,0x9c66b22b}}, // _kenal_, _והעמקים_, _संकल्पना_, _birtist_,
+ {{0x32fe6045,0x53d0a01f,0x00000000,0x00000000}}, // _bergen_, _olimpisk, --, --,
+ {{0x52a740f5,0xab051078,0xeac8a127,0x00000000}}, // _byebye_, _afasegbe, _rosalía_, --,
+ {{0x7c6d8154,0xd9f6c070,0x73860267,0x72489189}}, // _shirika_, _توہین_, _siiro_, _ulama_,
+ {{0xe2e6f190,0xe33f207b,0xf2c941c9,0x32a7f100}}, // _biznesa_, _মজুমদার_, _suporte_, _klubo_,
+ {{0x620e1173,0x42da5216,0x91c3c02a,0x00000000}}, // _качан_, _butere_, _догори_, --,
+ {{0x72fce04a,0xeec5c081,0xfa931017,0x00000000}}, // _bangsa_, _словник_, _корени_, --,
+ {{0x25042163,0x0c15c011,0xa7ece11c,0x33807023}}, // _početna_, _sveriges, _pekalong, _suurus_,
+ {{0x1721d136,0xc000911c,0xb26d8075,0x6307d099}}, // _početkom_, _علیزاده_, _barod_, _boravak_,
+ {{0xf30dc01b,0xd363619d,0x6dad8003,0x623ba247}}, // _membina_, _langkah_, _bandarís, _atumatu_,
+ {{0x9b6e3182,0xc2da70cb,0x00000000,0x00000000}}, // _емоции_, _keress_, --, --,
+ {{0xe38781dd,0x33ebf154,0x00000000,0x00000000}}, // [4c0] _horri_, _ukuta_, --, --,
+ {{0xd3dc900d,0xe310b043,0x65ee128c,0x8e33b061}}, // _ikawa_, _chuckle_, _produció, _reklamos_,
+ {{0xc2c94177,0x89fce074,0x2bb4c0c8,0x00000000}}, // _bellach_, _makarant, _सुन्नुहो, --,
+ {{0x626df048,0x627e70de,0x4c3ea105,0xb3f4709f}}, // _truoc_, _tunne_, _genişlən, _gestel_,
+ {{0xb395903e,0xf3949068,0x4f2360db,0xc3f4027d}}, // _misst_, _naast_, _comentan, _dostat_,
+ {{0x4b28b100,0x12d8c13d,0xa20030a4,0x00000000}}, // _транспар, _groene_, _komisi_, --,
+ {{0xa2d51039,0x8ef71006,0x4c7d721f,0x663d726c}}, // _mellett_, _कलाको_, _persuna_, _persunal_,
+ {{0xfd8c21a7,0xa9d1519b,0xe954f1ab,0xf26d81ef}}, // _geregelt_, _بفرمایید_, _известен_, _muros_,
+ {{0x64eba02a,0x0341624a,0x887002e7,0x00000000}}, // _контракт, _direkta_, _músicas_, --,
+ {{0x43207154,0x0292505d,0x00000000,0x00000000}}, // _wenye_, _matayo_, --, --,
+ {{0xe4b38094,0x282e901a,0xbef6f022,0x7e006061}}, // _הארבע_, _условиях_, _yarjejen, _bendrada,
+ {{0x1f03607b,0x93659018,0x18a3c02c,0x9f3980bc}}, // _লোকজন_, _לוקחת_, _команды_, _muenster_,
+ {{0x8dc42106,0xd3737036,0x6624828c,0x6f78320f}}, // _agricole_, _domande_, _tecnolox, _blackpan,
+ {{0x2dffe04a,0xd69ba0b2,0x12fc3089,0x73207206}}, // _perempua, _chướng_, _milions_, _zenye_,
+ {{0xaaba2175,0xff321031,0x6320717f,0xfa17113b}}, // _предизви, _国土资源部_, _yenye_, _徐州监察分局_,
+ {{0x35105099,0xfd784004,0xd460d13b,0xfd0a5013}}, // _pokušao_, _פאסאזשיר, _香艳的寻宝之旅_, _europese_,
+ {{0x8ae1f008,0x22a6a1d9,0xfce88175,0xb581f187}}, // [4d0] _institut, _hobby_, _достигне_, _instituz,
+ {{0x42d7c08e,0x427f509a,0x00000000,0x00000000}}, // _kuponko_, _buenas_, --, --,
+ {{0xf85cb093,0x72a64206,0x02583140,0x8517523e}}, // _verander_, _simba_, _engleski_, _amalanny,
+ {{0x327e6037,0x73b022e8,0x8683400f,0x5dab5003}}, // _ngono_, _webstrán, _एतबार_, _minninga,
+ {{0x72d9000c,0x50dd20cb,0xe2e9a179,0x022ba17d}}, // _habere_, _ریسرچ_, _arkisto_, _vermoed_,
+ {{0x6f1e311c,0x1d85c045,0xb4fb40d5,0x00000000}}, // _شاکردوست_, _словом_, _गिलानी_, --,
+ {{0xe2da70ff,0x6cd99094,0x8cf2f128,0x22bc716a}}, // _stredu_, _החסיד_, _partener, _ध्रुव_,
+ {{0xd3da20e7,0xa3c890fe,0x15ec1018,0xc0abe203}}, // _правилни, _এলোমেলো_, _ללקוחות_, _станция_,
+ {{0xee29d03e,0xc38bc2e9,0xfb25307a,0x00000000}}, // _bygginga, _færre_, _пушкіна_, --,
+ {{0xce4d9008,0xb27e912c,0x8510a19f,0xb8dfb0a0}}, // _הייטק_, _quand_, _səlahiyy, _ryngwlad,
+ {{0x6386c194,0x611ac173,0x0c7b4132,0x82d521a5}}, // _gjorde_, _бомбы_, _برخاست_, _robocon_,
+ {{0x2386a09d,0x09472175,0x33ebe0c0,0x00000000}}, // _dobry_, _писател_, _nytta_, --,
+ {{0x5290c0a2,0x2c54a147,0xdb67c20a,0x00000000}}, // _jedan_, _gustong_, _бабеле_, --,
+ {{0x22b73045,0x829020a4,0x768e61ad,0x25148126}}, // _другому_, _dekat_, _profissi, _egyáltal,
+ {{0x95bd9017,0xa1b9a018,0x1302c077,0x13875044}}, // _министра_, _החלקה_, _lokakuu_, _cifras_,
+ {{0x1b495035,0xb66cd1af,0x6d8c7204,0x427e905a}}, // _संस्कृती, _бунга_, _točno_, _itanu_,
+ {{0x7e4291c0,0x9290c0e8,0x3221b03b,0x22c02190}}, // [4e0] _breakout_, _nedan_, _diskutim, _vēlies_,
+ {{0x02e0216e,0x861d1068,0x4c03d190,0xc635d1ab}}, // _deireadh_, _개인정보보호정책_, _forumā_, _класичес,
+ {{0xfa7e91cd,0xa29130aa,0xcd4ad19b,0xb2013232}}, // _connaiss, _linkler_, _اسکنر_, _linkleri_,
+ {{0x854e21bc,0xa290c212,0x62d460ac,0x2aa910c3}}, // _лични_, _allaah_, _burdan_, _перете_,
+ {{0x6299b03d,0x00000000,0x00000000,0x00000000}}, // _アウトドア_, --, --, --,
+ {{0xe70ba094,0x32d5d1a4,0xf30d1055,0x03fa6231}}, // _ההגדה_, _razliku_, _samatar_, _struje_,
+ {{0x4b7cc12b,0x1387e04f,0xf320c088,0x32009134}}, // _jednoduc, _notre_, _medyo_, _owain_,
+ {{0xc236525f,0xdfe1512f,0x12bd5035,0x2200200c}}, // _vilja_, _ollmhór_, _माणुस_, _tekil_,
+ {{0x834c20c8,0x549752ea,0xff23804c,0xe1415039}}, // _cestovní_, _अयोग्यता_, _cemerlan, _مشاروتی_,
+ {{0x984340e7,0xd27fe0a7,0xd20021a4,0x6d99c1a3}}, // _централн, _letni_, _nekim_, _bezpośre,
+ {{0x96577156,0x437c5104,0x2394d2a9,0x725ad192}}, // _poljopri, _nabanye_, _mbese_, _skolen_,
+ {{0x927e7045,0x52b470f9,0x323fd0b9,0xbc1650a2}}, // _kunna_, _manch_, _vermaak_, _mogucnos,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc2ba504a,0x89d5d002,0x4867504d,0x652c101e}}, // _sendiri_, _उत्पात_, _mulighed, _términos_,
+ {{0xc291c2eb,0x03796147,0x00000000,0x00000000}}, // _vrijeme_, _matanda_, --, --,
+ {{0x3248d143,0x1145124a,0x33eae2ec,0x37a0e1f9}}, // _olema_, _materyal, _centar_, _শিপলু_,
+ {{0xab1820d3,0x5c5c92ed,0x527e7259,0x2627d008}}, // [4f0] _هههههههه, _बिशेष_, _gunna_, _definiti,
+ {{0x1ca66036,0x0200c0de,0xf75fa02a,0x19699190}}, // _журналис, _valige_, _дозволяє_, _autortie,
+ {{0x826cc11d,0xcc1720ca,0xa25a9206,0x526dd0c8}}, // _zalogi_, _पूर्वाञ्, _ajali_, _neboť_,
+ {{0x432091e3,0x1b0b003d,0x93fa40d8,0x322540aa}}, // _kwayo_, _アドバイス_, _patung_, _şekli_,
+ {{0x98fce2ee,0xbdae00e0,0xb2f4c098,0x66c5e0fe}}, // _levantad, _kasnasda, _emailem_, _দাবীতে_,
+ {{0x6c777029,0xd25a927c,0xad57901a,0x00000000}}, // _पेक्षा_, _djali_, _муниципа, --,
+ {{0x89f1e003,0x644570a4,0x13417140,0x00000000}}, // _endilega_, _प्रयोगशा, _spremna_, --,
+ {{0x1a897087,0xc27e91c0,0x4cb72031,0x12fe7044}}, // _üniversi, _ngano_, _您当前的位置_, _cargas_,
+ {{0x32cae11d,0x139402b9,0xa2fc00fa,0x2860c215}}, // _vendar_, _spise_, _oyiga_, _юнака_,
+ {{0xd290c011,0x727ed241,0xb31730f7,0x1b1c420e}}, // _redan_, _krene_, _functie_, _debatten_,
+ {{0xb9d2e143,0xa9995077,0x62ef40b0,0xe290c008}}, // _tallinna_, _професси, _gwefan_, _sedan_,
+ {{0xb320909c,0x5288214b,0xc8da0080,0x2c7e01c0}}, // _bwayo_, _hastalığ, _mojsejov, _bersyon_,
+ {{0xb3508053,0x4bad80c6,0xf2e760b3,0x49aa3065}}, // _profiel_, _क्यूबेक_, _putnika_, _تأكيد_,
+ {{0xd29040a0,0x4bfcf1a0,0x820091dd,0x00000000}}, // _gemau_, _vergader, _dakizu_, --,
+ {{0x325bf03e,0x49eab020,0x19fd002e,0x0a906091}}, // _skulu_, _알려드립니다_, _interiér, _comisiwn_,
+ {{0xc1b1e07b,0x62da61b4,0xc27e706e,0xb2002070}}, // _জানেন_, _bareng_, _funni_, _nekik_,
+ {{0x0840c1a2,0x92d8a054,0x91452153,0xab65810b}}, // [500] _مخصوص_, _umbes_, _material, _januára_,
+ {{0xc681900b,0x925a90bf,0x00000000,0x00000000}}, // _ग्लास_, _ajolú_, --, --,
+ {{0x42e111f4,0xc2d8b11d,0xa3f47192,0xb852a089}}, // _भेजीं_, _vedeti_, _bestil_, _entrades_,
+ {{0xcbd21243,0x927ed202,0x62fdf099,0xa1f8c065}}, // _कृपया_, _bueno_, _drugu_, _متصفح_,
+ {{0xcc610154,0x1a03a119,0x23683093,0x53eae1c4}}, // _masuala_, _darajada_, _vingers_, _dritte_,
+ {{0x82d8a06a,0x739a60a7,0x93519128,0x13e89020}}, // _ember_, _podatkov_, _conform_, _태터데스크_,
+ {{0x47fcb033,0xdc1ce03d,0x52d66122,0x83f40194}}, // _只看该作者_, _サイトマップ_, _podobno_, _kostet_,
+ {{0x542d60cd,0xb200f25f,0x00000000,0x00000000}}, // _दबदबा_, _megin_, --, --,
+ {{0x03f471c2,0xddff5102,0x48534089,0x63cf30b2}}, // _nostre_, _mendenga, _empreses_, _ttxvn_,
+ {{0x5386d191,0x00000000,0x00000000,0x00000000}}, // _miere_, --, --, --,
+ {{0x0200c1be,0x92cbf1a4,0x93f20088,0xd200800c}}, // _redio_, _ljude_, _parehong_, _makine_,
+ {{0x9681a1f3,0xcf5d41ce,0x5c634036,0x00000000}}, // _क्रास_, _magasabb_, _imprese_, --,
+ {{0xfc0542de,0x13949119,0x7e9880ea,0xaf7fb0b3}}, // _chorvats, _kaasi_, _선거법위반행위_, _potrošač,
+ {{0x2e50202e,0xc24db054,0x8b95a05d,0xcc5ca128}}, // _informač, _फिरला_, _katikkir, _centrul_,
+ {{0x6a7c805d,0x55a122ef,0x62f640f9,0xbfd21009}}, // _makanisa_, _सोचते_, _prensip_, _miniatur_,
+ {{0xb27e90ed,0x0290f055,0x758a01f1,0x3c7690a8}}, // _orang_, _degan_, _registrá, _forslag_,
+ {{0xc285804d,0x00000000,0x00000000,0x00000000}}, // [510] _回答は役に立ちま, --, --, --,
+ {{0x5290201b,0xc386d1d8,0xbcb950a0,0x00000000}}, // _mekah_, _diere_, _ymwadiad_, --,
+ {{0xd34170e6,0xc3869206,0xe3a7b2da,0xc27e911d}}, // _eigenen_, _ziara_, _франсуа_, _hrano_,
+ {{0x2f2db0e8,0x6b35c184,0x7dc45020,0x00000000}}, // _utrednin, _kurashis, _maximale_, --,
+ {{0xa26dc136,0xa25b7205,0x8290904a,0x5e68504d}}, // _prvoj_, _ilalim_, _lokasi_, _総合ランキング_,
+ {{0xd582f088,0x1394b2e2,0x317e007b,0x7739b11c}}, // _kategory, _fleste_, _কথাবার্ত, _سبزیجات_,
+ {{0x37cc7036,0x72da619b,0xea97a0cc,0x92e79036}}, // _пострада, _sareng_, _דאטנאביע, _gennaio_,
+ {{0x47e340de,0xa38f51da,0xe26d80bc,0x9da63293}}, // _त्रिपाठी_, _rapporti_, _karon_, _стопанск,
+ {{0x53877063,0xa26da200,0xc62ed13b,0x2706e2f0}}, // _ilaria_, _ispod_, _必须保留本网注明, _štruktúr,
+ {{0x128d2113,0xe2f1e1ab,0xe394906e,0xa2ff90d3}}, // _thaksin_, _diritti_, _abaso_, _radharc_,
+ {{0x235870ff,0x997e409f,0x33f8e0ab,0xd304e16f}}, // _potrebné_, _afrikane, _minuts_, _izbacim_,
+ {{0x62d8716a,0x7d0840cc,0xc18ec07f,0x038672a3}}, // _konec_, _באזונדער, _лимба_, _blizini_,
+ {{0xe153c13b,0x05b760b2,0xf960105d,0x00000000}}, // _企业国有产权转让, _finalsty, _abangere, --,
+ {{0xe22500a2,0xf2fe7044,0x00000000,0x00000000}}, // _svakom_, _cargos_, --, --,
+ {{0x0682f008,0x2637602c,0xe9c2f091,0x581b31bb}}, // _categori, _пластико, _categore, _стремно_,
+ {{0x9290c027,0x1b7f2101,0x52fce13d,0xb386f1be}}, // _nalaze_, _bangaren_, _dingen_, _ahaziri_,
+ {{0x62007061,0x7031f2f1,0xec619076,0x072a602a}}, // [520] _meniu_, _बतकही_, _oferuje_, _приватни,
+ {{0xa280c219,0x02b28040,0x6394e18d,0x8c9090de}}, // _novinky_, _sondern_, _prisen_, _त्यों_,
+ {{0x7967a004,0xd200d03e,0x9ccb904d,0x18ad3034}}, // _מגילה_, _tveir_, _円以上国内配送料_, _малък_,
+ {{0x2eead037,0x00000000,0x00000000,0x00000000}}, // _پستون_, --, --, --,
+ {{0xf648a04e,0xd2d9915b,0xedf7c050,0x00000000}}, // _никарагу, _besef_, _филмови_, --,
+ {{0x83f471db,0x26d611bd,0x653c904d,0x4f38d1c0}}, // _vostre_, _یاسمین_, _希望小売価格_, _sovracca,
+ {{0x5c50c0ca,0x2ee3e0aa,0x1e398029,0x20e7e0d1}}, // _ट्रेड_, _programı, _penawara, _offiziel,
+ {{0xd27e722b,0x185da094,0x5200e158,0x426df02f}}, // _kunnu_, _משכיל_, _abiire_, _sezono_,
+ {{0x3db8e18e,0xde237079,0x88e79293,0x9320b013}}, // _filament_, _संग्रहाल, _надвор_, _precies_,
+ {{0xe4d5e28b,0x2200f269,0x0cbaa04f,0x00000000}}, // _गोपिका_, _tegin_, _magnifiq, --,
+ {{0x26dff017,0x7ae99175,0x00000000,0x00000000}}, // _израелск, _немаат_, --, --,
+ {{0x73eae008,0xf26e51d0,0x7248d054,0x92c530b9}}, // _center_, _nitori_, _oleme_, _heilige_,
+ {{0x76e46017,0x42d9f01d,0x1f44422b,0x7c6f6174}}, // _многобро, _gazete_, _búskapar, _carrera_,
+ {{0xc9d4d2f2,0x95727004,0xa27f7051,0x02926095}}, // _umiestne, _אַרמיי_, _kwanan_, _sarado_,
+ {{0x123691e8,0x2d00707b,0x6c69e03b,0xc39591d7}}, // _znaju_, _মিউজিক_, _titulli_, _ahssa_,
+ {{0x520070d6,0x0b2720a4,0xbbfb91ae,0x5b03912c}}, // _menit_, _विश्वकरं, _septiemb, _entretie,
+ {{0xe3f47049,0x42e2d166,0xe9e870cd,0x05268193}}, // [530] _jestem_, _स्वीट_, _कोतवाली_, _završio_,
+ {{0x048510de,0x23f47056,0x02d980b9,0x00000000}}, // _शिवाला_, _bostad_, _gered_, --,
+ {{0xb24890a4,0x327f90cf,0x002d70fd,0x0186927b}}, // _alami_, _qaynar_, _фотоальб, _bakankul,
+ {{0xe290319d,0x93eaf1bf,0x00000000,0x00000000}}, // _sejak_, _pritet_, --, --,
+ {{0x826c12f3,0x82908205,0xfd136153,0x9378d185}}, // _रक्षा_, _dekada_, _consider, _kemarin_,
+ {{0xf072b037,0xc047f1bc,0xc2a682f4,0x9290d0d4}}, // _امارات_, _мислите_, _provozní_, _ideat_,
+ {{0xb200321b,0x52904037,0x13796116,0x0a6fa12f}}, // _komiti_, _lemah_, _carabta_, _prionsab,
+ {{0x42e320c4,0xc7aa71fc,0x126cd065,0xbe5f0192}}, // _peniaze_, _avstrali, _cheol_, _specielt_,
+ {{0x62bd5186,0xb287f14b,0x728c3080,0xfa0fe035}}, // _मानुस_, _hastalık, _balkón_, _manajeme,
+ {{0xc2bb8034,0xec5740c9,0x526df01a,0xad78c034}}, // _offerte_, _nastaje_, _autot_, _енергия_,
+ {{0x6580e0f9,0x2f3dd143,0x097ad040,0x2c31e143}}, // _federasy, _कृतित्व_, _enthalte, _artiklid_,
+ {{0x43f9813d,0xbc673161,0x93874009,0x53f8f089}}, // _terug_, _omkring_, _ngerti_, _algun_,
+ {{0x7163a26b,0x12d85177,0x93949099,0x747d8009}}, // _דניאל_, _coleg_, _spasa_, _interwik,
+ {{0x1c53f0f3,0xd38b327e,0xe2d830a2,0x327e0143}}, // _contato_, _først_, _kojem_, _naine_,
+ {{0xbce27050,0x02a640c3,0x7435e1fd,0x9248d09c}}, // _информир, _limba_, _функции_, _aleme_,
+ {{0x74ef4045,0xf3f85142,0x0290c091,0x127ee1d9}}, // _спортивн, _illum_, _addas_, _seinen_,
+ {{0xd200c153,0x252021e4,0x7b0e7077,0x027ed092}}, // [540] _media_, _beannach, _viestike, _stenu_,
+ {{0x6c7e015a,0x4e54e081,0xeb10010b,0x025b50d1}}, // _persoon_, _економіч, _prostrie, _stellt_,
+ {{0xc9f8f21d,0x326da078,0x47dfb01c,0x8f628190}}, // _milijuna_, _aapon_, _ponašaju_, _katalogs_,
+ {{0x1a152265,0x503c9013,0x3836902f,0xe1767108}}, // _कामोत्ते, _namelijk_, _кадрах_, _प्रतिघात_,
+ {{0xde0ea175,0xf2eee046,0xd29271ee,0x9b260153}}, // _verdinha, _benfro_, _pasado_, _companie,
+ {{0xb3869056,0x53eb2044,0xf290419e,0x924990dc}}, // _snart_, _lector_, _semak_, _mesme_,
+ {{0x7c50c07b,0xe3099008,0x419991a3,0xedad8003}}, // _ছোট্ট_, _problem_, _problemy_, _bandarík,
+ {{0x92484045,0xa1acc16a,0xc39ca0a9,0x075330e6}}, // _komme_, _pondělí_, _zakašnje, _grundlag,
+ {{0xe5c0b0a4,0x32d851c4,0xe2fd20a7,0x963f1134}}, // _ब्राह्मण, _allem_, _kolikor_, _bersonol_,
+ {{0x956730a2,0x3c52d143,0xb2bb805f,0x00000000}}, // _obavezno_, _स्लेट_, _effekte_, --,
+ {{0xf24802a7,0x5290e208,0x8a886221,0xd3f46037}}, // _elimu_, _chiall_, _moderáto, _urutan_,
+ {{0xe2d850a7,0x49e17070,0xb3f40089,0x0b2611bb}}, // _poleg_, _میگزین_, _costat_, _ядром_,
+ {{0x8dcfa2f5,0xc2d980b5,0x0290c090,0x489a804d}}, // _ज़्यादा_, _tablety_, _islame_, _したがって_,
+ {{0x02d8b13c,0x12255064,0xb290501b,0xa596310f}}, // _организу, _nafkah_, _belah_, _isteriny,
+ {{0x320070aa,0x0bf2d039,0xaceee079,0x18c2d039}}, // _benim_, _kattints, _क्रमवार_, _kattintv,
+ {{0x53eae15e,0xd2c6b143,0x04fb600f,0x10821094}}, // _centrs_, _oluline_, _किसानी_, _לחבירו_,
+ {{0xe43f3020,0xe394d134,0x4e463268,0xbc6e11d6}}, // [550] _대전광역시_, _asesu_, _humanida, _akaraka_,
+ {{0x323a41b0,0x5631c01a,0x1f6d31d5,0xf34850d4}}, // _विशालकाय_, _erinomai, _atịkụl_, _lineari_,
+ {{0x93f4011e,0x268230c3,0x0200b044,0x52007120}}, // _postat_, _accesori, _medida_, _tenim_,
+ {{0x32ca01ec,0x25b7b0c2,0xedb510a4,0xad47b1ee}}, // _odide_, _disposiz, _श्रीकांत_, _disposic,
+ {{0xcbee10b6,0xc329e269,0x725a20a8,0x4843828c}}, // _часом_, _राजपूतन_, _fortælle, _contidos_,
+ {{0x92a13020,0xb27ed1ee,0xef47d0a6,0x00000000}}, // _불가합니다_, _buena_, _maksimaa, --,
+ {{0x27c0e018,0xc921112b,0x00000000,0x00000000}}, // _classifi, _neobsahu, --, --,
+ {{0xd47f90d4,0xdefd21ab,0xbef2c13c,0xf27ff1df}}, // _batterij, _графични_, _хвала_, _efuna_,
+ {{0x763f1091,0xe9e130d5,0xe20051b5,0xea14c2c4}}, // _personol_, _कस्टम_, _pelik_, _forbruge,
+ {{0xf2e641e8,0x6c0361cc,0x0354a271,0xabad10fe}}, // _radnika_, _gampang_, _poderes_, _মন্ডল_,
+ {{0xd3ea0058,0x59c2c089,0xcc19b229,0xc1d02065}}, // _edite_, _divendre, _процессо, _ملايين_,
+ {{0x2202619f,0xfe725243,0xd2a7106f,0x96db0065}}, // _turizm_, _आरम्भ_, _парола_, _يااااااا,
+ {{0x415ac0c9,0xd57dc020,0xf7a45045,0x37bac2c1}}, // _prirodno, _엔터테인먼트_, _тематичн, _prirodni,
+ {{0xf25a0159,0x5cd2107b,0xc3ced191,0x9200c0aa}}, // _idile_, _বৈঠকে_, _drevo_, _haline_,
+ {{0x5f2400f7,0x03869128,0xb63e1017,0x26dca129}}, // _nederlan, _ziare_, _министри_, _मिसिर_,
+ {{0x90373266,0x9f2e1175,0x22131069,0x3f06c065}}, // _cestovan, _модифици, _tobhau_, _وعطور_,
+ {{0xc6640143,0xefe7b175,0x5c7f6008,0xf2cae0d2}}, // [560] _ग्रसित_, _правата_, _january_, _rondom_,
+ {{0xf2d85041,0xa1b8910b,0x443020f3,0x6386d0ab}}, // _kolem_, _recenzií_, _processo_, _riera_,
+ {{0xb3f85208,0x948641b9,0x127e0102,0x0387f175}}, // _colum_, _सुपारी_, _asing_, _couro_,
+ {{0xaaf85033,0x73eae10f,0xb4ae21ac,0x00000000}}, // _actuelle, _gentar_, _jawatank, --,
+ {{0x33ce20a2,0xdd2d10c3,0x00000000,0x00000000}}, // _kakve_, _similare_, --, --,
+ {{0x43ead254,0xa29101ec,0xedc0d03f,0x72a7f0ae}}, // _tiotal_, _lebara_, _komisija, _klubs_,
+ {{0x72e750dc,0x320070ca,0x00000000,0x00000000}}, // _gjenden_, _denik_, --, --,
+ {{0xc17730b6,0xc14752f6,0x1290201f,0x725ad194}}, // _центр_, _desenvol, _nekas_, _kveld_,
+ {{0x472280b7,0x0290c0c3,0xf9efb033,0x00000000}}, // _анӂелику, _dolari_, _injurieu, --,
+ {{0x6f5dd1a5,0x30763070,0x3d6f9208,0x99d4d07b}}, // _vinaphon, _مذاہب_, _seòladh_, _বিভ্রান্,
+ {{0x42d882f7,0x1557715d,0x2394e0d3,0x89f37179}}, // _hehehh_, _ninjasch, _coiste_, _kansaned,
+ {{0x6a7210b5,0xcbd6f13b,0x951cc1bf,0xfa89019e}}, // _značka_, _市场参考价_, _zakonish, _perisian_,
+ {{0xb2cad15b,0x029020bb,0x8394f143,0x1200c07f}}, // _vandag_, _mekas_, _siiski_, _mediu_,
+ {{0x03310065,0x00000000,0x00000000,0x00000000}}, // _اجنبيه_, --, --, --,
+ {{0x478412e0,0x53f140d4,0x926c61b1,0x295140d4}}, // _प्रस्तुत, _esportaz, _nkoos_, _esportat,
+ {{0x5f3fa2cc,0x85ff22cc,0x00000000,0x00000000}}, // _tangeran, _विरोधात_, --, --,
+ {{0x62ec20c4,0x1b65a21e,0x6878b045,0x6cd560ea}}, // [570] _rastliny_, _coolinar, _продовжу, _페이지까지_,
+ {{0xd13140c3,0x12dc11d6,0x9951b179,0xcf1140da}}, // _produsel, _achoghi_, _kiellett, _produsen,
+ {{0x99b141ff,0xc0114248,0xabaa1018,0xd290711d}}, // _producte, _producto, _architec, _cenah_,
+ {{0xac03602d,0xa3eaf03e,0x00000000,0x00000000}}, // _samping_, _leitar_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf290c25e,0x729101c0,0x12318017,0x00000000}}, // _solari_, _babaye_, _септембр, --,
+ {{0x129031f5,0x768302f8,0xb94e713b,0x82573108}}, // _tamale_, _पोशाक_, _广州市白云山农产, _virtuaal,
+ {{0x329010fc,0x2c776153,0xb27f40e9,0xeebdd1ff}}, // _behar_, _popular_, _prenta_, _evenemen,
+ {{0xf912e07a,0x00000000,0x00000000,0x00000000}}, // _этапу_, --, --, --,
+ {{0x12fd92f7,0x215f212f,0x6a3e519b,0x6200c0bd}}, // _ehehehe_, _coinnigh_, _جزییات_, _galile_,
+ {{0x07d7c2f9,0xe963d04d,0x52902102,0xc229907b}}, // _nadaljev, _あなたにおすすめ, _bekas_, _মুহম্মদ_,
+ {{0x26b85045,0xedd040bb,0xd4b90067,0xf26de037}}, // _глобальн, _fatherna, _दिनेशदा_, _katon_,
+ {{0x6c6d3137,0x62d82194,0xe25b7051,0x82907134}}, // _porukom_, _boken_, _akalla_, _menai_,
+ {{0x7877a02b,0xc195c07b,0xf2d8c1dd,0x3e2000fe}}, // _правах_, _নীড়পাতা_, _kodea_, _সজিপ্র_,
+ {{0x220031b2,0x524a7133,0x9e9ec0f3,0x226df2c1}}, // _mejis_, _seumas_, _табла_, _istoj_,
+ {{0xac53d0ca,0x7594a062,0x4e1a423a,0x03ead0c2}}, // _विदेह_, _студенат, _सर्बिया_, _avete_,
+ {{0x178ac0e7,0x94ac70c2,0xe3bfd099,0xc0d5d04d}}, // [580] _приватно, _консулта, _zatvori_, _このサイトを評価,
+ {{0x2c5d606f,0x70b220de,0x91259229,0x2b82e070}}, // _компания, _प्रजातंत, _mielestä_, _منظرنامہ_,
+ {{0xa9d181b2,0x117d216f,0x438061e8,0x825ad005}}, // _muitimed, _odrastal, _jutros_, _fellur_,
+ {{0x2cf612ae,0xd2a7f144,0xfc612037,0xa37940c5}}, // _адрес_, _kluby_, _sebutan_, _tavalla_,
+ {{0x0386b0ab,0x00000000,0x00000000,0x00000000}}, // _podria_, --, --, --,
+ {{0x199cc070,0x7849e082,0xd35ae13b,0x62e00078}}, // _رجسٹر_, _призначе, _从这里开始_, _aguntan_,
+ {{0xb387f1cd,0x7db9b2f2,0x1b499108,0x75120004}}, // _jours_, _trenčian, _रास्ट्री, _מחלוקה_,
+ {{0xc387e071,0xe98081a3,0xc3200184,0x00000000}}, // _intre_, _नियमों_, _agiye_, --,
+ {{0x027f41f9,0x2adf303b,0xc141207b,0x226af1ab}}, // _cuenta_, _komunite, _বেড়েছে_, _операция_,
+ {{0xde72e2fa,0xac32525b,0x08621002,0x4694f16a}}, // _एकत्र_, _szeptemb, _राजगद्दी_, _kadeřnic,
+ {{0xa3c870a2,0xd2b58074,0x8cf7a0a6,0x327e0074}}, // _sasvim_, _barci_, _हमशक्ल_, _raina_,
+ {{0xa2018064,0x5679d02e,0xdaaa80ea,0xf2d9e070}}, // _ceria_, _neustále_, _구체적으로_, _beteg_,
+ {{0x330cb041,0x6ae43153,0x0200c0aa,0x5f243210}}, // _upravit_, _septembe, _dedim_, _septemba,
+ {{0x6fc67173,0x8a8672fb,0x225ad21b,0xb6aa0018}}, // _istorijo, _istorijs, _uvele_, _הדרדסים_,
+ {{0x4387f1cd,0x135f32a2,0x00000000,0x00000000}}, // _cours_, _pengene_, --, --,
+ {{0x52da71ab,0x23f45056,0x636cf0a9,0xa27e90bd}}, // _essere_, _natten_, _tragovi_, _grann_,
+ {{0xb2dac040,0xec0362fc,0x4387e11a,0x12da505a}}, // [590] _können_, _kampung_, _antre_, _gatete_,
+ {{0x12018007,0x0b63b04d,0x825ad2fd,0x1ef6313c}}, // _merin_, _アクセス解析_, _felles_, _настала_,
+ {{0xd248426c,0x8ea2a04d,0x0c2a5065,0xe26de18f}}, // _somma_, _最新記事一覧_, _معالجة_, _zaton_,
+ {{0x9290403c,0xe281c03a,0x6a0fa080,0xef3c1094}}, // _temat_, _possibil, _manažmen, _מלאכות_,
+ {{0xd056716a,0x122b30c3,0x5e1b301e,0xa3430038}}, // _अर्घाखाँ, _animale_, _animales_, _kitendo_,
+ {{0x62903202,0xe3869120,0xb2b480f5,0xf37920d0}}, // _dejar_, _diari_, _chicky_, _elbasan_,
+ {{0x92fd60f7,0xd2bc01a0,0xf290419e,0x13d95049}}, // _liggen_, _verdien_, _kemas_, _polaków_,
+ {{0x9c1872fe,0xf2018007,0x3d3da197,0xec535021}}, // _despois_, _kerin_, _ordinace_, _būtent_,
+ {{0x4c50419c,0x4c6f110c,0xcd9f00a2,0x429630c4}}, // _कैसेट_, _fearann_, _većina_, _funkciu_,
+ {{0x44ec70b7,0xd3430063,0xebd450eb,0xdc6160a1}}, // _контрола, _nitendo_, _генералн, _mutumba_,
+ {{0x63954036,0xcb774044,0x00000000,0x00000000}}, // _questo_, _explotac, --, --,
+ {{0x2b67400f,0xf9f920c2,0xb386e0c5,0x040d70fc}}, // _कारने_, _alessand, _koiran_, _gainditz,
+ {{0x92730054,0xec0ba27e,0xc195c1e6,0x00000000}}, // _politsei_, _egypten_, _планом_, --,
+ {{0x5f4e0036,0x0045e23a,0xa2c490b9,0x00000000}}, // _versione_, _चाइल्ड_, _skuldig_, --,
+ {{0x7d23e282,0xab05a043,0x83806133,0xe0f5b0b7}}, // _plusieur, _grignion_, _beurla_, _субститу,
+ {{0x5237907b,0x8ad8d0c8,0xf249525c,0xc3eae18a}}, // _মানিকগঞ্, _vánoční_, _poemas_, _gentur_,
+ {{0x0d5d7060,0x43187069,0x3290211c,0xb2d460c5}}, // [5a0] _lọ́nà_, _hwjchim_, _cekap_, _kauden_,
+ {{0x0be0e100,0x4fc2c1bd,0xc3f33047,0x00000000}}, // _interjer, _تابوت_, _اطلاق_, --,
+ {{0xe27e616d,0xfafa504c,0xe27f7024,0xda3c2173}}, // _taong_, _pengurus, _fransa_, _сухое_,
+ {{0xe2d8710f,0xefb1e0ea,0xdae4205a,0xc386d0d1}}, // _konek_, _주절거리기_, _ingengab, _tiere_,
+ {{0xef6fc151,0xb2e60158,0x939542ff,0x0fea4300}}, // _palabras_, _adanida_, _flestu_, _dubrovač,
+ {{0x528c70cc,0xb27f2048,0xe7560070,0xf20190aa}}, // _בליץבריו, _quynh_, _ماہنامہ_, _kesin_,
+ {{0x53430154,0x16b691eb,0xfecef0a7,0x0ec7e018}}, // _vitendo_, _канікул_, _varnostn, _efficien,
+ {{0x92004232,0x9f21f0db,0x23fa70aa,0xf3ea920a}}, // _demir_, _utilizar_, _kurulu_, _odata_,
+ {{0x925a00e9,0xf290509b,0x92cae08f,0x1200502b}}, // _deild_, _kelas_, _hendes_, _kelis_,
+ {{0xaa1440e2,0xe29051c5,0xd290c2a3,0xa201814b}}, // _mahakama_, _jelas_, _sedam_, _verin_,
+ {{0x52a601ec,0x128c316e,0x9cf75049,0x82fce239}}, // _shiba_, _الصمت_, _लेखकों_, _gingen_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x92d85046,0x23957267,0x00000000,0x00000000}}, // _dolen_, _abassa_, --, --,
+ {{0x5394b301,0xf27e9088,0xa316b1d5,0xe25a0003}}, // _često_, _laang_, _afrịka_, _heild_,
+ {{0xeded8302,0x0202605a,0xa290c0a7,0xeb094303}}, // _विद्यालय, _harimo_, _sedaj_, _composte,
+ {{0x324a729f,0xf3f47077,0xe3ec20c9,0x22918185}}, // _termos_, _lasten_, _sastavni_, _peran_,
+ {{0xcc50400f,0x527e70b9,0x671da202,0x080a9045}}, // [5b0] _कैरेट_, _manne_, _situació, _стоять_,
+ {{0xd27e90f7,0x425a0143,0x72005229,0x92004304}}, // _maand_, _meile_, _pelit_, _temir_,
+ {{0xc09a403c,0xe815201f,0x4f8c9143,0xc8d6c14c}}, // _प्रयोगपृ, _sacensīb, _हक्का_, _कनपटी_,
+ {{0x77c95049,0x0e70f004,0x2ee3d0ca,0xf38740a0}}, // _dostosow, _לאָנדאָן_, _पूर्वाधा, _hyfryd_,
+ {{0xa2905035,0xf2ddf099,0xec5370db,0x00000000}}, // _gelar_, _otrovni_, _certeza_, --,
+ {{0xe29160fb,0x0a15e161,0x5291a04a,0xe1ac4304}}, // _negara_, _forbinde, _depan_, _adlandır,
+ {{0x7f7390a7,0xcb92a008,0xff471120,0x882c7036}}, // _posamezn, _אינדקסים_, _comprova, _доставка,
+ {{0x449192dc,0x522580f7,0x50f390cc,0x8e2cc175}}, // _धर्मे_, _markt_, _קאשוי_, _млада_,
+ {{0x4edc6019,0x5737a0f3,0x1c34f024,0x1a93c0f3}}, // _sociales_, _таблети_, _bannerlə, _монети_,
+ {{0xab6130e1,0x72905045,0x07833077,0x920050b0}}, // _तुमने_, _delar_, _harvinai, _delir_,
+ {{0x1c0ae20e,0x72918175,0x1394d178,0xf27e001a}}, // _kroppen_, _geral_, _aposta_, _paino_,
+ {{0x8290c249,0x0b343005,0xcb1fc252,0xe3ea01a0}}, // _ibland_, _sambands, _balaaran_, _feite_,
+ {{0x3378804d,0xa2e120f9,0x262b1041,0x525a0054}}, // _ひとりごと_, _pozitif_, _खेलहरु_, _neile_,
+ {{0x924a71db,0x22919185,0x00000000,0x00000000}}, // _termes_, _pesan_, --, --,
+ {{0x73eab277,0x8d7c9020,0x3736d045,0xf25a0081}}, // _þetta_, _블로그에서_, _stilling, _heile_,
+ {{0x6507d1ff,0xfb132305,0xa295e28f,0x4928f0a3}}, // _allemaal_, _hinanden_, _ajukari_, _institūc,
+ {{0xf2014005,0xcc6f0274,0xa7737263,0x0a5b70a2}}, // [5c0] _fleiri_, _doprava_, _प्रसिद्ध, _večeras_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd2d85081,0xc3eae048,0x825b71dc,0x54eac031}}, // _elles_, _toitim_, _chalon_, _第三十二条_,
+ {{0x429190bd,0x998790de,0x090eb04d,0xa27a80aa}}, // _resan_, _स्तम्भ_, _はじめまして_, _kaliteli_,
+ {{0x9a23b0ae,0x86d10098,0x02fcf2b6,0x00000000}}, // _darbinie, _symbolem_, _bygge_, --,
+ {{0xf8c86017,0xdd23010b,0x0ea2f08a,0x00000000}}, // _финансиј, _položiek_, _contiene_, --,
+ {{0x3eaa703d,0xf422a020,0xa132a10b,0xe2318019}}, // _この回答内容が不, _개인정보를_, _reklamné_, _primero_,
+ {{0xe2007128,0x62d84057,0x731bd0ae,0xb2c5b04d}}, // _venit_, _comel_, _otrajā_, _rusland_,
+ {{0x52d990a8,0x420160f6,0x42d8e089,0x1c75c074}}, // _olsen_, _begira_, _diners_, _manufar_,
+ {{0xb0a92077,0xdc6bb184,0x6b1fc116,0x00000000}}, // _подарить_, _burundu_, _salaysan_, --,
+ {{0xe132a041,0x14867306,0x535f3154,0x00000000}}, // _reklamní_, _तरकारी_, _pengine_, --,
+ {{0x039490c3,0x7387f058,0xa200d241,0xb58160f6}}, // _acasa_, _pouri_, _splitu_, _pentsatz,
+ {{0x63c870b4,0x9278600c,0x1999d02e,0x52ee91df}}, // _servei_, _okundu_, _neexistu, _asafa_,
+ {{0x229011d6,0x9e954018,0x52ff4232,0xedb5117a}}, // _aghaa_, _התלמידים_, _veriyor_, _stagione_,
+ {{0x82a63037,0xc22670d4,0x2682c190,0x22e85087}}, // _tambah_, _avukat_, _novembri, _mutfak_,
+ {{0x0f240193,0xe2d8b0aa,0x227f7086,0x00000000}}, // _zašto_, _nedeni_, _utanga_, --,
+ {{0x7a5da080,0xe21a102e,0xf394d0c2,0xe13240cb}}, // [5d0] _takúto_, _centráln, _spese_, _وجوہات_,
+ {{0xde9640a4,0xd386005a,0x23be0307,0x9bed7175}}, // _राष्ट्रव, _nzira_, _živali_, _fevereir,
+ {{0xe3ebf0b7,0x67433082,0x3980b308,0x00000000}}, // _ajuta_, _курси_, _zaslužan_, --,
+ {{0xd12d2173,0x001fd1e4,0xd10c80a2,0x07bb21bf}}, // _тарту_, _greensto, _slučajev, _gjermani,
+ {{0x22cad072,0xcc009044,0x08709044,0x6d7fb0a4}}, // _mandag_, _básica_, _básicas_, _ज्योतिषा,
+ {{0x82907199,0xa38b51e5,0xe2d87187,0x12907056}}, // _cenas_, _würde_, _honen_, _menar_,
+ {{0x9d44503d,0xc69c21ab,0xb407717a,0xb249f1e3}}, // _最近の記事_, _достатъч, _autorizz, _udumo_,
+ {{0x1c7512c7,0xe2d84190,0x6c7691f8,0xb0e8e05f}}, // _रात्री_, _domes_, _tirsdag_, _christel,
+ {{0xe531a156,0xac5b30c5,0x5f1e402a,0x2405d1da}}, // _najčitan, _minulle_, _проектів_, _armonizz,
+ {{0xc2d85309,0x127ff1a2,0x72d8418a,0x2dda61be}}, // _eller_, _agung_, _nomer_, _adresị_,
+ {{0x4546c07f,0x2ae21070,0x7366a04a,0xa3f851b2}}, // _брашов_, _تھریڈ_, _tinggal_, _lolus_,
+ {{0x2290c0a4,0xbf12e074,0x80d72018,0x29c52108}}, // _celana_, _littatta, _וירטואלי, _matemaat,
+ {{0xd7cc20bc,0x941e405a,0xc1e0a076,0xfb441025}}, // _dysplasi, _kiganiro_, _हंगरी_, _औरपढ़े_,
+ {{0x6343810b,0xdf7ab2db,0xbcdea069,0x829071ea}}, // _členom_, _दुखदायी_, _hiavtxwv_, _denar_,
+ {{0x226de1fd,0x3dcef020,0xd27f703e,0xc2fda22b}}, // _katoa_, _베스트셀러_, _standa_, _tveimur_,
+ {{0x8c5e3040,0xf8e680b7,0x5ee0f11f,0x9ba600de}}, // _erstmal_, _арэтат_, _शास्त्रा, _अमित्र_,
+ {{0x726f50a1,0xe2cbf150,0x2fa30018,0xd35bd04b}}, // [5e0] _piripiri_, _ujuda_, _director_, _pulgada_,
+ {{0x76eb5181,0xedc410c3,0x794d403c,0x1ba4103a}}, // _ランキング_, _articole_, _चित्रकूट_, _articolo_,
+ {{0x67a9f06a,0x9ba4c14c,0x5c5ba069,0xe20271dd}}, // _harmadik_, _संयुक्ता, _lubtsev_, _hasiko_,
+ {{0x629071c5,0x2a57d04e,0xf0b3f0a2,0x00000000}}, // _benar_, _каменка_, _američke_, --,
+ {{0x9f2d6044,0xd49c007b,0x8cd39117,0x33cc5018}}, // _advertin, _চিহ্নিত_, _голову_, _הפרשנויו,
+ {{0x223760d1,0x9c5fd0f9,0x00000000,0x00000000}}, // _völlig_, _septanm_, --, --,
+ {{0xbc1da008,0xefd72100,0x3f9930b2,0x533b8130}}, // _לשאול_, _svetainė, _xuất_, _могућнос,
+ {{0x40075181,0xf8c5c04d,0x00000000,0x00000000}}, // _甲信越地区_, _の検索結果_, --, --,
+ {{0x7762b293,0x422571df,0x5c757269,0x00000000}}, // _легендат, _abakwa_, _रहस्यन_, --,
+ {{0x2e592033,0xb27e9089,0x02d8c175,0xf2ee0104}}, // _推荐给朋友_, _quant_, _podem_, _isifo_,
+ {{0x5d9ad117,0x2743d065,0x34a3b004,0xa71fb159}}, // _статьи_, _chlúdach_, _רעפרעזענ, _akọtun_,
+ {{0x0cb2507b,0x92fce02d,0x00000000,0x00000000}}, // _মুরগী_, _dingin_, --, --,
+ {{0x8fba9013,0x2170201c,0x5bf77004,0xe26cc1ca}}, // _augustus_, _različit_, _מסתּמא_, _pulong_,
+ {{0xd4f4b1c9,0x92ca91cf,0xc2d841d4,0x38cca0f7}}, // _acompanh, _idade_, _comer_, _begeleid,
+ {{0xe6d6012f,0x7c7e0045,0xe3e351df,0xa89551ea}}, // _لايمكنك_, _versjon_, _umpristi_, _posvetov,
+ {{0x8291d064,0xe2d85075,0xb27ef08d,0xfd2de0bd}}, // _dewan_, _solet_, _iningi_, _rekipere_,
+ {{0x8290c1a4,0xbc64d157,0xf27c9092,0x7ae301ab}}, // [5f0] _dolazi_, _pivtxwv_, _rezervác, _ristrutt,
+ {{0x527e916d,0xd3eb730a,0x799e2039,0x9c23012b}}, // _isang_, _quatre_, _البرادعی_, _poprvé_,
+ {{0xb7661008,0xf2cad1d0,0x33f850dc,0x56b980e6}}, // _ושומרת_, _adeda_, _folur_, _benachri,
+ {{0x72b58008,0x7b5df18a,0x9b0150d1,0x00000000}}, // _march_, _norwegia_, _sonstige, --,
+ {{0xc27e0173,0xc291d1cc,0xece800ea,0xc213425e}}, // _kaina_, _hewan_, _은평뉴타운_, _ftehim_,
+ {{0xe181d291,0xb84a10f3,0x2d91e019,0xf5e5d1af}}, // _सर्वे_, _фотки_, _cantidad_, _гледаат_,
+ {{0x29adc11c,0x12ef7128,0x7d1fa26f,0x2387e036}}, // _مجتبی_, _trafic_, _beroende_, _entro_,
+ {{0x227ed02c,0x025b608e,0x32cae175,0x73eae202}}, // _vuonna_, _poglej_, _vendas_, _ventas_,
+ {{0x52da60fd,0xc2fcf011,0xbcd720fe,0x00000000}}, // _barend_, _bygga_, _টপিকঃ_, --,
+ {{0x5386d08b,0x3316f0f6,0x00000000,0x00000000}}, // _mieru_, _baizik_, --, --,
+ {{0x635aa008,0x69ebe0cd,0xa228c08b,0x53fa21d1}}, // _perfect_, _मारवाडी_, _portfóli, _paquet_,
+ {{0xd305a13a,0x82ca60dc,0x926d312f,0xddfd9170}}, // _članak_, _ndodh_, _macosx_, _modelova,
+ {{0xf7a80024,0x027ef126,0x92d840c4,0x62d8f1e9}}, // _korrupsi, _akinek_, _pomer_, _vogel_,
+ {{0x4f2e1017,0xb2d9a17d,0x92fce147,0xdac1119b}}, // _мисли_, _amper_, _tingin_, _سنندج_,
+ {{0x13f851d7,0x527e0074,0x5f21230b,0x8225f150}}, // _solus_, _daina_, _diperken, _usuku_,
+ {{0x996d40d7,0x1b88f072,0xc5ea1194,0x326c91a0}}, // _visionat, _eksister, _формі_, _tekort_,
+ {{0xfa1070ad,0x62905106,0x00000000,0x00000000}}, // [600] _assamble, _aflat_, --, --,
+ {{0xf26dc22d,0x216c9153,0x7395e051,0x00000000}}, // _izvor_, _activity_, _watsi_, --,
+ {{0xaae84241,0xc3ff723e,0xcc01a0e9,0x58b530cb}}, // _izloženi_, _berzina_, _flestir_, _گوجرانوا,
+ {{0x02fce05e,0xf679c017,0xcc5d9082,0x00000000}}, // _linggo_, _региону_, _sentrum_, --,
+ {{0x7ed4005f,0xfbaa602c,0x1f2450c6,0x43cee1c0}}, // _inligtin, _прикольн, _znáte_, _ativan_,
+ {{0xa27ff146,0x924a7033,0x5348c035,0xd291a0a0}}, // _grund_, _permis_, _infeksi_, _cwpan_,
+ {{0x9386630c,0x9eb301cf,0xd1c93106,0x534301cf}}, // _ahora_, _direitos_, _поате_, _direito_,
+ {{0x42918215,0x9fee613b,0xdc9631bb,0xf20fd1ba}}, // _gerai_, _国家体育总局_, _занята_, _محارم_,
+ {{0xc3492142,0xc863a008,0x23160304,0x220191be}}, // _diversi_, _ברלין_, _faizi_, _kesia_,
+ {{0x3136d12f,0xdc5740b5,0x00000000,0x00000000}}, // _مراقب_, _dostane_, --, --,
+ {{0x628381be,0xfaf4c1c2,0xdfeaf1a3,0xaf6a300e}}, // _mashini_, _estrange, _विभागों_, _ababawan,
+ {{0x227f21a5,0x37ed4098,0x42ca5188,0xef2a21fd}}, // _huynh_, _fotbalov, _melde_, _банковск,
+ {{0x2d43d045,0xe2d980a2,0x4316808d,0x41ed4041}}, // _автомобі, _pored_, _amabili_, _formulář,
+ {{0xce173050,0x3177201a,0xa2926090,0x7c213036}}, // _значи_, _месте_, _tirane_, _академия_,
+ {{0x43893117,0x6140e30d,0x73ab5080,0x79fa40f7}}, // _рождения_, _तिरंगा_, _stavieb_, _vacature,
+ {{0xa27ff0f5,0xa2647232,0x92a68064,0xb0a2b08f}}, // _trung_, _teslim_, _wahbah_, _コメント日時_,
+ {{0x22eba0e9,0x1a748160,0xee5041b5,0x725ad217}}, // [610] _stendur_, _ranganna_, _pedalama, _fallet_,
+ {{0x45fdb30e,0x4dc4107f,0xa248d1ad,0x2dc40004}}, // _direcció, _ultimele_, _filmes_, _מאַשין_,
+ {{0x7f3fa0a9,0x2ffa301a,0xd290302d,0x873b113b}}, // _osiguran, _работник, _remaja_, _第三十九条_,
+ {{0xbfb2c12f,0x2d1880c5,0xb24c50cd,0x4200b1f9}}, // _ندعوك_, _нельзя_, _हिटलर_, _decir_,
+ {{0x52df1003,0xbd1fc00c,0x426db00d,0x23eae044}}, // _menning_, _beslenme_, _segiteri_, _centos_,
+ {{0x3d5a4100,0xe61e3089,0x00000000,0x00000000}}, // _раздавал, _laboraci, --, --,
+ {{0x52fe611c,0x035f1052,0x00000000,0x00000000}}, // _dirilis_, _bangase_, --, --,
+ {{0xa343811d,0xd87ae0c8,0xa19c6065,0xd39480c5}}, // _namenom_, _बर्दिया_, _فتافيت_, _saksan_,
+ {{0xb22ac02b,0x1728802e,0x8e8ac070,0xf3ce20a2}}, // _годам_, _publikov, _چودھری_, _kakva_,
+ {{0x8806026b,0x7201e1be,0x425a5143,0xcc1d5050}}, // _אינטרנט_, _retin_, _kelle_, _проектот_,
+ {{0x43966053,0xf386a036,0xa394e100,0x1389509c}}, // _eerste_, _messaggi_, _maisto_, _okuyita_,
+ {{0xc2cae309,0x6225f30f,0x5ad741dd,0x0c54b0bc}}, // _landet_, _lauku_, _partekat, _flatbed_,
+ {{0xa8535310,0x829160d7,0x1f81f0f3,0x75f9a180}}, // _dossiers_, _vegada_, _германиј, _फेलोशिप_,
+ {{0x360860c5,0x12cae0dc,0x949ac0ea,0x425ad033}}, // _normaali, _vendos_, _전자상거래등에서, _belles_,
+ {{0x12906283,0xd22500a2,0x00000000,0x00000000}}, // _tulad_, _svakoj_, --, --,
+ {{0xeab150c4,0xf87e0094,0xb29080c5,0xc354a10f}}, // _telefónu_, _קונטרס_, _mukava_, _tarekat_,
+ {{0x6529011a,0x71abd13b,0xd2f420c3,0x47b39004}}, // [620] _definisy, _位读者读过此文_, _ultimul_, _גאַסט_,
+ {{0x6759c09d,0x8487d2ad,0xa8ec8173,0x9b07d1af}}, // _निर्मित_, _лидера_, _законных_, _лидери_,
+ {{0x5257407b,0x9290403f,0x32d8c1d9,0xf56e1018}}, // _আশঙ্কা_, _nemaz_, _boden_, _טוקבקים_,
+ {{0x32926024,0x1f696039,0xabd1618d,0x2abac0e8}}, // _israil_, _hatalmas_, _kundeser, _gravidit,
+ {{0x7386a036,0x3c5420bb,0x52d1b009,0x00000000}}, // _libri_, _lintawd_, _kelopak_, --,
+ {{0xf3696233,0xd200f0f7,0xe3869254,0x2053d128}}, // _imagens_, _manier_, _mhara_, _reprezin,
+ {{0xd1d4b1be,0x9b7781a3,0xe387e0c4,0x5f5bb277}}, // _elsebeth_, _podobnyc, _nitre_, _innanlan,
+ {{0x0e10f311,0x4292004f,0x3a08402b,0xd3871035}}, // _रामरक्षा_, _espace_, _буржуазн, _pabrik_,
+ {{0x626e500d,0xe641c0b7,0x0485e007,0x00000000}}, // _matora_, _атурэ_, _akirikó_, --,
+ {{0xd2ca917f,0x82d9e040,0xb5a6d143,0x232071df}}, // _idadi_, _alten_, _कसरती_, _munye_,
+ {{0xa25ae047,0x7d9bd0b5,0xd249026c,0xc2024037}}, // _buille_, _autoseda, _stampi_, _akting_,
+ {{0x42da6237,0x5fe7e117,0x36a660de,0x1bebe0de}}, // _kereta_, _пароль_, _प्रयासरत_, _मद्रासी_,
+ {{0x805330c6,0x525ad0de,0xc0e3b008,0x82f0e057}}, // _sportovn, _selles_, _מרחבי_, _berilah_,
+ {{0x3b72504d,0x2b43a092,0x4c7830b9,0xa7c3d179}}, // _地域共同研究セン, _futbalis, _verskil_, _любого_,
+ {{0xc15bf031,0xd2eee192,0x39c27054,0xcc75a1b2}}, // _月参加工作_, _kaffe_, _kindlust, _puasyog_,
+ {{0x747ec0fd,0x71d2615d,0xca7d5221,0x46231312}}, // _ринок_, _lifebuoy_, _kapacita_, _अण्णा_,
+ {{0xe292617f,0x2199d130,0xb20070b9,0x19bf3218}}, // [630] _kitabu_, _манипула, _junie_, _meldinge,
+ {{0x32da511d,0x3ac02122,0x37eab04d,0xad71e100}}, // _katere_, _različne_, _クラシック_, _premjera,
+ {{0x4bda5175,0x12d8c0b4,0xc2d8c153,0x725a905d}}, // _родители_, _poden_, _model_, _adali_,
+ {{0xa3f461c4,0xb3eae12c,0x00000000,0x00000000}}, // _garten_, _ventes_, --, --,
+ {{0x827d0255,0x6ff9c09d,0x235f3154,0x43869047}}, // _materiál, _स्टेडियम_, _mengine_, _chara_,
+ {{0xa20070c3,0x9758d182,0xee1070b2,0xb301f09e}}, // _iunie_, _финансис, _thằng_, _murambi_,
+ {{0xb37fc106,0x227e61df,0x2b7c90ea,0x2290c1c6}}, // _romania_, _usona_, _필요하다고_, _sedar_,
+ {{0xc2ca70f0,0xba6740ac,0x4652d1fc,0xfccaf15e}}, // _dende_, _mütləq_, _braziliy, _spēļu_,
+ {{0xa2902074,0xf1cbf0f6,0x0f35e054,0x6c75f1d7}}, // _dukan_, _besterik_, _suurenda, _acarach_,
+ {{0x127e4154,0xe26db216,0xd313a0e9,0xcc5fc01c}}, // _namna_, _adword_, _umleið_, _sestrom_,
+ {{0x62ca7143,0x3ea5b0f6,0x7f3c50f1,0x7c6822b2}}, // _nende_, _bestalde_, _dikurnia, _cyprien_,
+ {{0x24b630de,0xd202526b,0xdbf04100,0xa88a10ea}}, // _कुंडी_, _action_, _baltarus, _민주노동당_,
+ {{0x02ca7313,0x0200d0a2,0xe27e4111,0x43ea72b9}}, // _hende_, _ranije_, _kamna_, _hente_,
+ {{0xd2da01e3,0xf27e0184,0x4dd40018,0xadf0c19b}}, // _impela_, _nyine_, _לאשכול_, _انبوه_,
+ {{0x5f320314,0xbfeda004,0x53bbd070,0x424981d7}}, // _preferan, _כסליו_, _اسٹاف_, _gorma_,
+ {{0xcd9a30c5,0x82d960bb,0x1200c29c,0xbb0b0236}}, // _скорее_, _ligers_, _pedir_, _resposte,
+ {{0x42cae18d,0xd257d0fe,0x8bb27264,0x306300f3}}, // [640] _sendes_, _ইনস্টল_, _conexão_, _инфограф,
+ {{0x7344b086,0xa3f47068,0x93790074,0x00000000}}, // _mirenge_, _gasten_, _disamba_, --,
+ {{0x2d80a291,0xadd100b4,0x8ba4813a,0x9292617f}}, // _मुख्यमंत, _projecte_, _radionic, _vitabu_,
+ {{0x370ac02b,0x00000000,0x00000000,0x00000000}}, // _эсперант, --, --, --,
+ {{0x0200f134,0xbb19e315,0x3c61412a,0x82d8a1ea}}, // _megis_, _relasyon_, _gautier_, _dober_,
+ {{0x414ec12f,0x42d8c164,0x31dec065,0x00000000}}, // _agallamh_, _alder_, _agallaim, --,
+ {{0xa27e70fa,0x4150e0aa,0xb7caf008,0xdbf5a018}}, // _banne_, _hareketl, _respecti, _דוגמא_,
+ {{0x23caa316,0x7307410a,0x629180b9,0x7c0dd02a}}, // _zamanı_, _wilayah_, _veral_, _взагалі_,
+ {{0x7394e11e,0x0308b031,0x9290104a,0x23f160f7}}, // _ganska_, _股权登记日_, _tuhan_, _zaterdag_,
+ {{0xe61210cc,0xdbe01181,0xf6b0b25b,0x51fe5213}}, // _אַזעלכע_, _accepter, _kizáróla, _organach_,
+ {{0x125ac068,0xc3f400a0,0x42e670b3,0xa3f8b2e8}}, // _jullie_, _costau_, _stanova_, _pocut_,
+ {{0x126c80a2,0xa26c6069,0x00000000,0x00000000}}, // _nekome_, _xyoos_, --, --,
+ {{0x29ed9056,0xcc1e904d,0x3d2d92c4,0xf378e208}}, // _redigera_, _リストマニア_, _redigere_, _allaban_,
+ {{0x538601e4,0xabe2602a,0x4eb3b036,0xb3eb0134}}, // _gairm_, _створити_, _potrebbe_, _statws_,
+ {{0x7c64907b,0xc4138007,0x13455016,0x00000000}}, // _অপেশাদার_, _adisokan_, _sigurisë_, --,
+ {{0xa1f1c031,0x71f74198,0x440080b2,0xf787a025}}, // _订阅该问题_, _zemljišt, _nguời_, _तत्वों_,
+ {{0xf34ef1ce,0x0aec326c,0x52f16008,0x89463045}}, // [650] _emberek_, _attraent, _saturday_, _сайтів_,
+ {{0x5431507b,0x82d8c120,0x1c5f204d,0x19d7e07b}}, // _আংশিক_, _podeu_, _ゲストさん_, _পাশ্চাত্,
+ {{0x7200817f,0x00000000,0x00000000,0x00000000}}, // _hekima_, --, --, --,
+ {{0xb315d12a,0x2651b174,0xb2d8b268,0xe51a00ea}}, // _burbank_, _educació, _voces_, _모르겠지만_,
+ {{0x7394e17f,0x72a13144,0xc68230e1,0x1394b299}}, // _maisha_, _pravidlá_, _इंकार_, _eneste_,
+ {{0x32001235,0x92902250,0xeaea70aa,0x23eaf0ad}}, // _muhim_, _sukan_, _sistemle, _dmitri_,
+ {{0x36dcb030,0x927f1099,0xe3e782df,0xdb7b4008}}, // _बंदिश_, _kazne_, _látni_, _demonstr,
+ {{0xd27f40dc,0x5a376044,0xa4d76044,0x5b03a16e}}, // _brenda_, _técnica_, _técnico_, _lárionad_,
+ {{0x7d5ac118,0x69ef801a,0x72fd00d7,0x00000000}}, // _номер_, _известны, _socials_, --,
+ {{0xd290b1aa,0x11b03070,0x52cad068,0x990a306c}}, // _dodaci_, _منسلک_, _handig_, _alabapad,
+ {{0xf2e9003b,0x6977d317,0x430e630f,0x90d4d21e}}, // _berisha_, _entidade, _vasaras_, _poručuju_,
+ {{0xe3329069,0x8f4bf033,0x16269036,0x6201e259}}, // _kojxwb_, _consomma, _алтернат, _netia_,
+ {{0x81f3a008,0x8031a0da,0x4861b24c,0x00000000}}, // _אריאל_, _मलाही_, _donnchad, --,
+ {{0xefdf9300,0xd294e103,0xaa48800e,0x0cb200fe}}, // _najvažni, _ज्यादातर_, _yeremiya_, _ইনস্টিটি,
+ {{0xa84f808b,0x946571fd,0x829030c9,0x00000000}}, // _mimoriad, _страхова, _rujan_, --,
+ {{0x311da04e,0xc9e2c19b,0x5b6031ab,0x00000000}}, // _проблеме_, _باشيد_, _обичам_, --,
+ {{0xd27e0147,0x1354a1c4,0x4985001c,0xf29000fc}}, // [660] _aking_, _anderes_, _navikama_, _agian_,
+ {{0x329090f7,0x9ad51081,0x5a4a8122,0xc50300f9}}, // _staan_, _postadre, _katerega_, _refleksy,
+ {{0x08ab6031,0x2bfb6031,0x19db6031,0x2c5b803b}}, // _第三十一条_, _第三十三条_, _第三十七条_, _postimi_,
+ {{0x284cd020,0xa29040a4,0x25c33081,0xcc5d1100}}, // _않았습니다_, _cuman_, _огляд_, _portalo_,
+ {{0x8200b0e6,0xc2248013,0x52019016,0xf2d980b6}}, // _medien_, _pakken_, _besim_, _noreg_,
+ {{0x704720ca,0x262240c4,0xb70e0008,0x5c5c21c4}}, // _पर्याप्त_, _narodeni, _יונדאי_, _gestern_,
+ {{0xf6590215,0x9206112f,0x124400ca,0xf5a39050}}, // _настольк, _ناروتو_, _उत्तीर्ण_, _помеѓу_,
+ {{0x38283318,0xc39602a3,0x3e1a2237,0x7c620276}}, // _центра_, _srpska_, _dikemuka, _terusan_,
+ {{0x5200f005,0xd27e70f5,0x2b3eb060,0x3af33037}}, // _segir_, _gunny_, _wẹẹbu_, _عبدالمال,
+ {{0x4def7039,0x929190dc,0xbcc4100a,0x85bd414d}}, // _اجتماعات_, _kesaj_, _artikolu_, _контролн,
+ {{0x84273154,0xa5ecc045,0xa3a25199,0x00000000}}, // _matatizo_, _повернут, _telpa_, --,
+ {{0x03f8e017,0x72267037,0x1eb16319,0xa39590d1}}, // _ponuda_, _naskah_, _externas_, _passt_,
+ {{0xa2918037,0x57bbe034,0x93692011,0xa30f600d}}, // _kerak_, _останали, _bloggar_, _ukuboza_,
+ {{0x82786106,0x53d520c2,0x02926208,0x65752034}}, // _atunci_, _contatti_, _caraid_, _contatto_,
+ {{0x71e2c076,0x86d81221,0x427f7057,0x92d87077}}, // _करारा_, _univerzá, _duanya_, _monet_,
+ {{0x7da6f099,0x9290b156,0x62d8c28c,0xc046716a}}, // _ispitiva, _podaci_, _podes_, _लेखलाई_,
+ {{0xb7816149,0x6395f1ec,0xc2cad16d,0x00000000}}, // [670] _व्यापारि, _asusu_, _pwede_, --,
+ {{0xf27e6142,0xc225f076,0x0290f0db,0x2527c0b7}}, // _skond_, _nauki_, _pegar_, _ведяу_,
+ {{0x5201920d,0xd3963175,0x0c80c050,0xbbdbb19b}}, // _resim_, _ивановск, _велес_, _فرآیند_,
+ {{0xd2d46031,0x93eb0153,0x41ede180,0x8249731a}}, // _jardin_, _status_, _मकबरा_, _nyampe_,
+ {{0x237d42b2,0x22906116,0xd1d200b2,0xd173f215}}, // _rusange_, _kulan_, _đôrêmon_, _бочках_,
+ {{0xa3fa7008,0x44fb809d,0x0b041008,0x64b6000a}}, // _forums_, _आडवाणी_, _ברגשות_, _kumpanij,
+ {{0x7038e26c,0xb635b1ce,0x472d21a4,0x00000000}}, // _dimensjo, _garanciá, _uostalom_, --,
+ {{0xaef1b215,0x32786105,0x1069d10b,0x1eb7b13b}}, // _paskutin, _olunan_, _prepojen, _中华人民共和国行,
+ {{0x894730a2,0x2dbd4133,0xc2f19122,0xa341c1cc}}, // _uključuj, _legalese_, _samostoj, _शशांक_,
+ {{0xbfe3412f,0xe2d960b9,0xe09d4065,0xd27f709e}}, // _مماثلة_, _engels_, _مقارنة_, _aranga_,
+ {{0xd386931b,0xbbefb221,0x92ff4178,0x42d8b0d9}}, // _whare_, _pardubic, _arribar_, _vedeli_,
+ {{0x3bd2012d,0x1b973202,0x6a48f18e,0xa1f9b24b}}, // _अनुपम_, _relación_, _lisensya_, _vanzemal,
+ {{0xba828031,0x6380602c,0x086531bc,0x3ad00185}}, // _市政府主要部门_, _verran_, _луѓето_, _keamanan_,
+ {{0x7b27111c,0x5dc9b197,0x82fe500e,0x00000000}}, // _المپیک_, _तथ्यांक_, _abahehe_, --,
+ {{0x68868076,0x327ff143,0x58701008,0x42d97214}}, // _मूवीरिव्, _juuni_, _standard, _akaebe_,
+ {{0x831cb31c,0x2413d100,0xe394f241,0x00000000}}, // _sábado_, _пожелаю_, _blisku_, --,
+ {{0x0d85331d,0x5ecac10c,0xd500f1dc,0x82b4d12b}}, // [680] _permulaa, _leantain, _carmarth, _plochu_,
+ {{0xb543f024,0x2db7a11c,0xd38780ee,0xf201a034}}, // _komissiy, _دیپلمات_, _birra_, _страници_,
+ {{0x425b00bb,0x92903087,0x93866033,0x57f37071}}, // _kablig_, _numara_, _abord_, _версурил,
+ {{0x523670f1,0x6290206a,0x8beda232,0x00000000}}, // _janji_, _kukac_, _gerektir, --,
+ {{0x4394e04f,0xe93fe093,0x7a473204,0xd255c035}}, // _maison_, _verminde, _uključen, _दिशेने_,
+ {{0x727f0052,0x4645f020,0x227ba149,0xa9f0916f}}, // _phansi_, _소프트웨어_, _kalendář, _pronasla_,
+ {{0x83a24015,0xa27e9206,0xe589b220,0x420061ae}}, // _tempo_, _maana_, _pasaulē_, _julio_,
+ {{0x40d33034,0xdfd83175,0xf3ea0282,0x63531276}}, // _гласа_, _детство_, _boite_, _دهقان_,
+ {{0xd34451a6,0x02ca8237,0x220060d3,0xf2262060}}, // _recenze_, _dialami_, _troid_, _abuké_,
+ {{0xb61c2134,0x269b20b7,0x83879074,0x00000000}}, // _statudol_, _акциун_, _misra_, --,
+ {{0xf27ec099,0xb320005a,0x3c2970ed,0xe2d830c2}}, // _radno_, _ariyo_, _pembayar, _numeri_,
+ {{0xd4e300fe,0x00000000,0x00000000,0x00000000}}, // _বৈশাখ_, --, --, --,
+ {{0xf27e616d,0xff61f316,0xb2ff519f,0x427ef18e}}, // _iyong_, _çünki_, _biridir_, _tagna_,
+ {{0xa1e2d0de,0x6290317b,0x8200321f,0xc875700e}}, // _पसारे_, _kumara_, _nomini_, _abandalu,
+ {{0x525ad0b9,0x2cfb8035,0x93f470dc,0x82d8801f}}, // _beeld_, _प्रमाणे_, _rastin_, _hokeja_,
+ {{0x22e7615d,0x0401f258,0x82d91092,0x00000000}}, // _diendan_, _solution_, _mozem_, --,
+ {{0x7291c20d,0x725a918f,0x3e7500cb,0x7421f31e}}, // [690] _devam_, _kwale_, _انڈونیشی, _politikk,
+ {{0xd300413a,0xd3869124,0xc3a8d147,0x0fb4c117}}, // _članci_, _gaari_, _dumating_, _платье_,
+ {{0x20061008,0x347d41f4,0xb236711c,0x00000000}}, // _מסיבות_, _निरभर_, _panji_, --,
+ {{0xbcc8c0d3,0x4f1fb034,0xd2924189,0x82dab0e8}}, // _يتعلق_, _partecip, _dewasa_, _datorer_,
+ {{0xe58b40d4,0xdb90014f,0x8f641050,0x24074054}}, // _approvaz, _muungano_, _економиј, _हरिदास_,
+ {{0x7b19c07c,0x3290f1be,0x62000254,0x92388179}}, // _знаете_, _aghara_, _agiis_, _краснода,
+ {{0xb30e41c6,0x62fd0190,0x3341d11d,0xc34231cf}}, // _peratus_, _akcijas_, _namesto_, _sucesso_,
+ {{0x92901254,0x825ac0b9,0x6e869168,0x5c3870d5}}, // _athar_, _willem_, _životne_, _अनुपालन_,
+ {{0xb8dc422b,0x67e9a03d,0x837b9286,0xc287322c}}, // _spurning, _ダイエット_, _skladom_, _funkcie_,
+ {{0x8e1b705f,0x3b6c121f,0x00000000,0x00000000}}, // _wetenska, _castagna_, --, --,
+ {{0x4670831f,0x8c6a2024,0x520071ee,0x63f95019}}, // _perjanji, _sonuncu_, _junio_, _alguna_,
+ {{0xa7a90033,0x3320719b,0x62fe4310,0xabd1312b}}, // _entrepri, _dunyo_, _petites_, _mezináro,
+ {{0x6167613f,0x1908326f,0x857120ea,0x325a81b5}}, // _tienganh_, _populära_, _결과입니다_, _tahlil_,
+ {{0x1cbcd1f9,0xc29090b9,0xd2fce081,0xc3106073}}, // _নবাগত_, _graad_, _lenger_, _marzec_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x3dc0e190,0xcce471c0,0x00000000,0x00000000}}, // _policija, _traveloc, --, --,
+ {{0x3386e246,0xcdebb1a3,0x525ad320,0x3ed830b7}}, // [6a0] _amirul_, _najbliżs, _tallet_, _аннсее_,
+ {{0x6e43400d,0x36b0c0c5,0xa394e12c,0xeeb1308f}}, // _ambasade_, _страницу_, _saison_, _danskere_,
+ {{0xd9bec065,0x00000000,0x00000000,0x00000000}}, // _تحذير_, --, --, --,
+ {{0x222b2071,0xde7240de,0xba885077,0x00000000}}, // _trimite_, _तुर्क_, _kokonais, --,
+ {{0xdc74a081,0x698381ba,0x96d5704d,0xeceec045}}, // _forumet_, _creidmhe, _月末日まで_, _америки_,
+ {{0xac764171,0x831740c2,0x3045e25a,0x82912208}}, // _futuros_, _prezzi_, _जंगलों_, _bucais_,
+ {{0x321c6069,0x4fc230a9,0x97a5e321,0x72d9e170}}, // _asthiv_, _previše_, _parroqui, _bojnice_,
+ {{0x13878322,0x0fcbc045,0x00000000,0x00000000}}, // _mazrui_, _свободу_, --, --,
+ {{0xe27f7235,0x42925140,0x034052e2,0x2398001a}}, // _maanta_, _metara_, _verdens_, _присутст,
+ {{0x22020185,0x730d3064,0xfd8631bb,0x6c00b323}}, // _posisi_, _pelacur_, _настрой_, _egestas_,
+ {{0x5c4490ac,0x52a6a05d,0xfd5cd19b,0x62ef308c}}, // _istirahə, _babbi_, _تجسمی_, _kimiyya_,
+ {{0x537d407f,0x837ff01a,0x9a25b116,0x89fd4100}}, // _departe_, _tahansa_, _shacbiga_, _pagamint,
+ {{0xe8f13047,0xf664a070,0x52d8700c,0x00000000}}, // _انتظر_, _مہنگائی_, _annem_, --,
+ {{0x0cf931cc,0x841f10de,0xe0d5c03b,0x00000000}}, // _घडामोडी_, _versioon_, _prezanto, --,
+ {{0x13993002,0xb8d5c194,0x223f6080,0x1da3603b}}, // _hästi_, _страхува, _prokurát, _jugoslla,
+ {{0x767cf1e6,0xd27e9095,0x75056034,0x711af197}}, // _проблему_, _nyang_, _acquisto_, _जन्मदिवस_,
+ {{0x83100324,0x42ca0042,0x83ea001a,0xfc0740c4}}, // [6b0] _político, _noida_, _noita_, _vlastnu_,
+ {{0x178ba028,0xe92880f3,0x66d3e082,0x1396725d}}, // _termasuk_, _центар_, _студенті, _fyrsti_,
+ {{0xa2d99039,0xa27ed047,0x429b2047,0x3d5570b3}}, // _sosem_, _daonra_, _وكمان_, _pokazuju_,
+ {{0x65765050,0x00000000,0x00000000,0x00000000}}, // _publicaç, --, --, --,
+ {{0x1a104250,0x33f400e8,0xa33d60c8,0x72cd71b9}}, // _malaysia_, _kostar_, _zdravé_, _रिपब्लिक,
+ {{0x2b11b12f,0xb565a0e1,0xc2e30033,0x73443003}}, // _أمريكا_, _उद्धृत_, _famille_, _landinu_,
+ {{0x8316419d,0x8300e084,0xebabd08b,0xee140020}}, // _kembali_, _ceramah_, _fungovať_, _inderdaa,
+ {{0x79b88045,0xd6488194,0xf512711d,0x1f5f41ab}}, // _центру_, _центрі_, _aktualno_, _citazion,
+ {{0xa386d155,0xa09ca07b,0x0c607047,0xdc66c17b}}, // _chere_, _বিপুল_, _costais_, _protais_,
+ {{0xd2eba082,0x8526a0fe,0x6ae42122,0x2aca20cd}}, // _trenger_, _রায়হান_, _lastnost, _डायरिया_,
+ {{0xa847d071,0xcf746185,0x767c0008,0xc9c51112}}, // _бавария_, _प्रकाशचि, _ההשקעות_, _potestat,
+ {{0xbf7d31f9,0xa6a8212f,0x346e21c5,0x43ead128}}, // _সাংগঠনিক_, _وبحمده_, _pendidik, _aveti_,
+ {{0x0ecc301a,0x2d222090,0x00000000,0x00000000}}, // _avainsan, _pozitive_, --, --,
+ {{0xbcbf20a4,0x326e1218,0x7f23800a,0xd2026090}}, // _क्षेत्रा, _napoli_, _dilettan, _derisa_,
+ {{0x7387002b,0xdd8100a1,0xa7bd6068,0xba1280a7}}, // _svarbu_, _josephat_, _bevestig, _preberit,
+ {{0x6c7bf035,0xc201e03b,0xb3f7f04d,0x1eb0615d}}, // _शुद्धलेख, _ketij_, _質問した人_, _sacomban,
+ {{0xbe62f017,0x87920008,0xc39e11ea,0x00000000}}, // [6c0] _регистру, _statisti, _izvajanj, --,
+ {{0xe38690f7,0xf959e0a7,0x0168c0c3,0x72ba9298}}, // _maart_, _zaposlit, _imaginil, _gradski_,
+ {{0xa2ee9101,0xb2e0d12d,0x23cf82e9,0x00000000}}, // _shafi_, _कोचीन_, _farve_, --,
+ {{0xaae91036,0xc291e19e,0x75a9120a,0x62d5f01c}}, // _прокурат, _letak_, _терито_, _sklopio_,
+ {{0x12903189,0x32a64154,0x1eea7056,0x8ac6525c}}, // _pemain_, _kambi_, _reaktion, _previame,
+ {{0x70743220,0xf2e62027,0x3292508e,0xdbcaa238}}, // _komentār, _pogledaj, _boljše_, _स्वीकृत_,
+ {{0x828d52a6,0x00000000,0x00000000,0x00000000}}, // _muqaawam, --, --, --,
+ {{0x3301e190,0x92d92325,0x8f9a30ea,0x00000000}}, // _parasti_, _colocar_, _지식포인트_, --,
+ {{0x838601e4,0x9ad51153,0x3d051178,0x3431703a}}, // _cairt_, _instruct, _instrucc, _successo_,
+ {{0x03967326,0x020191fc,0xa2d8200c,0xe2a06222}}, // _tussen_, _etsin_, _anket_, _hotboy_,
+ {{0x8c293024,0x0225f00d,0x44b9f0c6,0x5d1ba034}}, // _versiyas, _ibuka_, _inspiruj, _prendere_,
+ {{0xd8c710ea,0x825b0091,0x00000000,0x00000000}}, // _안녕하십니까_, _tabled_, --, --,
+ {{0x027e61ca,0xc2d820f6,0xe322004d,0xa24841d2}}, // _akong_, _mikel_, _ブログジャンル_, _cimma_,
+ {{0x5d959008,0xd9d990ae,0x087980a0,0x2e3990ae}}, // _פורטל_, _dienesta_, _chyhoedd, _dienests_,
+ {{0x127ee077,0xb2da6058,0x93eae05a,0xfc38007f}}, // _kuinka_, _teroris_, _uwitwa_, _executar,
+ {{0x8bd58045,0xc38690f7,0xc343e061,0x36d3b02b}}, // _soldatar, _kaart_, _privalom, _глядзіце_,
+ {{0x0962202a,0x76165177,0x8261a12f,0x59565177}}, // [6d0] _коштів_, _poblogai, _píosa_, _poblogae,
+ {{0xf251f0e7,0xe2918100,0x204c921e,0x17cf204d}}, // _странице_, _geras_, _izbornik_, _サイト内検索_,
+ {{0x8f5c3070,0x6201e185,0x22c6d031,0x1166d031}}, // _ingatlan, _detik_, _第三十六条_, _第三十八条_,
+ {{0xe2d840f9,0x7200c0bd,0x53200245,0xe2908074}}, // _limen_, _aplike_, _iriya_, _bukata_,
+ {{0xa378d19d,0x229180a4,0x32e9f09c,0x72c47004}}, // _semakin_, _keras_, _bulijjo_, _באשיצן_,
+ {{0x6248d0ae,0x8cdd007b,0xbda9d024,0x195dc173}}, // _filmas_, _বাগেরহাট_, _stansiya, _vieninte,
+ {{0x787390cf,0xb2918056,0x75230020,0x73f980ca}}, // _istifadə_, _deras_, _선택하세요_, _korun_,
+ {{0xb6652035,0x7200617f,0x2b53d02a,0x00000000}}, // _वर्णिलेल, _kulia_, _телебаче, --,
+ {{0xb201e1ea,0xd3513047,0x0fed9018,0x837b0271}}, // _letih_, _meiriceá_, _לסייע_, _estando_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa387e0ff,0x5224e078,0x43ce912a,0x134b0070}}, // _nitra_, _kankan_, _chavo_, _baleset_,
+ {{0x9b27815a,0x3366a067,0x8962002e,0x67a58327}}, // _johannes, _tanggal_, _podnikat, _komentov,
+ {{0x163f31a6,0x7fe81025,0x92918037,0xb2918009}}, // _komentář_, _स्थानों_, _beras_, _serat_,
+ {{0x02d98013,0xf291e19b,0xa2018142,0xe290c092}}, // _horen_, _betah_, _perit_, _pomaly_,
+ {{0xefae12da,0x0290d328,0xf2674054,0xb3f8c06a}}, // _ласка_, _mudan_, _alkoholi_, _indul_,
+ {{0x72925136,0x7298c07b,0xa24a7100,0x00000000}}, // _ostaje_, _অলাভজনক_, _formos_, --,
+ {{0x86fa218a,0xb430b015,0x96405302,0x23a24034}}, // [6e0] _فرانسه_, _desconto_, _पारिश्रम, _tempi_,
+ {{0x1f6fa0cc,0xc2489245,0x0d1ee020,0xc33f016a}}, // _המדרש_, _inama_, _애니메이션_, _विजयादशम,
+ {{0xe2d980e6,0x83f40113,0xf2d8c0ef,0xac39b04d}}, // _foren_, _lostus_, _hideg_, _ショップへ_,
+ {{0x898e10c3,0x2291a028,0x425a51a0,0x63ea506f}}, // _парис_, _lepas_, _volle_, _volte_,
+ {{0x6a14105a,0xaae7310b,0xc8887004,0x00000000}}, // _bigatuma_, _videoalb, _שײַכות_, --,
+ {{0x50e73053,0x680d01fd,0xa2eff00d,0xb0a3c218}}, // _gemiddel, _ситуации_, _ngufu_, _колонки_,
+ {{0x20d33218,0x7290d0f8,0xdcd6317a,0xf085f0c3}}, // _класу_, _dudan_, _закрила_, _spectaco,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc290601b,0x534152ae,0x00c29122,0xd3fa50f8}}, // _julai_, _оформлен, _priljubl, _lotura_,
+ {{0x165d40c3,0xe8fd4175,0x14a70045,0x78733329}}, // _постамен, _поставен, _благоуст, _प्रकारको_,
+ {{0x1881632a,0xdb15e0d4,0xb2919240,0x13200086}}, // _महाविद्य, _kompetit, _sesat_, _uriya_,
+ {{0x7394b092,0x92025189,0x3860532b,0x520190a4}}, // _mieste_, _ketiga_, _बालेश्वर_, _mesir_,
+ {{0xad4960f6,0x4386d155,0x72ee00fa,0xd9a00197}}, // _vvрvv_, _obere_, _ekifo_, _लक्ष्मीप,
+ {{0x8fd23036,0x72d8501a,0xafd2f197,0xc320019b}}, // _покупка_, _eilen_, _dostateč, _priya_,
+ {{0xaaabb020,0xfc67d132,0x361f1098,0x00000000}}, // _비밀번호를_, _شرعاً_, _अर्थतन्त, --,
+ {{0xaa05a1d6,0x6265407f,0x73e5328b,0x7379b0b3}}, // _satalait, _anunturi_, _इण्डस्ट्, _pokazao_,
+ {{0x127f532c,0x071de277,0x00000000,0x00000000}}, // [6f0] _apenas_, _ærumeiða, --, --,
+ {{0xa2d870c5,0x0ad010cb,0xc651313b,0xa682d32d}}, // _ennen_, _ترغیب_, _无标题文档_, _आजकाल_,
+ {{0x22480318,0x22d9a068,0x82ebc046,0x85389036}}, // _snimi_, _kopen_, _prinder_, _настаняв,
+ {{0x52d85090,0xe2d940a0,0xf14e4047,0x2acce1e6}}, // _cilen_, _fideos_, _gaillimh_, _кондитер,
+ {{0x67a260ff,0xe2d87081,0x703260c4,0x1f5500aa}}, // _spotrebi, _innen_, _spotrebn, _kampanya,
+ {{0xe20180ad,0xe2909147,0x0200d0e6,0x00000000}}, // _verir_, _itaas_, _freie_, --,
+ {{0xd2fce056,0x6485c0e1,0x232141cb,0x98a5d176}}, // _pengar_, _सेनानी_, _inscrit_, _इच्छुक_,
+ {{0xf320932e,0x06bec12f,0x52d8421c,0x427ff0b6}}, // _ngayo_, _مصرّح_, _simen_, _grunn_,
+ {{0x0c70b0c2,0x4950b2e2,0x4860b0b4,0x7d3620cb}}, // _persone_, _personer_, _persones_, _مفہوم_,
+ {{0xe2d94201,0x03a290e3,0x6c1cd100,0xdeb7c276}}, // _videos_, _iwapo_, _участку_, _مراتب_,
+ {{0x4340e0a4,0xed559158,0x92f3a21f,0x7470e2a8}}, // _telepon_, _gbẹ̀yìn_, _artiklu_, _telepono_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc3ea02f6,0xa2d9c140,0x4602f0de,0x00000000}}, // _noite_, _covek_, _स्वागताध, --,
+ {{0x129090d4,0x62d87218,0x2e8790b5,0x225740b2}}, // _lokali_, _annen_, _kalkulač, _chuẩn_,
+ {{0xa8d8d025,0x1d9341df,0x4376301a,0x42d990c3}}, // _माध्यमिक_, _jikelele_, _авторски, _atentie_,
+ {{0x2342a153,0x2d92a034,0xa29001d7,0x436c70bc}}, // _general_, _generale_, _grian_, _tingali_,
+ {{0x73f4018d,0x00000000,0x00000000,0x00000000}}, // [700] _koster_, --, --, --,
+ {{0x7c5b8099,0x9db4c071,0x6ae640fe,0x0c65b0ed}}, // _postoji_, _аустрия_, _দোকানে_, _bertemu_,
+ {{0x5290d133,0xb7a3e145,0x9879e04d,0x9b45e0ea}}, // _rudan_, _području_, _ジャンル別_, _행정정보공개_,
+ {{0xf25a6269,0x5342a11a,0x9ca1b0fe,0x72d2a154}}, // _poole_, _jeneral_, _প্র্রোফা, _jenerali_,
+ {{0x9139826c,0x2df480b7,0xeccd600c,0x525b0074}}, // _identità_, _економис, _şeklinde_, _adalci_,
+ {{0x3150813b,0xa615a094,0x2386f1bb,0x4fe030fe}}, // _东营市蓝天塑胶有, _סודאן_, _skirti_, _প্রমুখ_,
+ {{0x62902122,0x69226143,0xe2d8e106,0x1665332f}}, // _tukaj_, _ओहिजा_, _cineva_, _कांस्य_,
+ {{0x92909020,0x9f286300,0xb2485061,0xadec206c}}, // _staat_, _također_, _filmo_, _adéyinká_,
+ {{0x43f871fd,0x3c67b088,0xd3f87077,0xb2ba8038}}, // _minun_, _sobrang_, _sinua_, _maadili_,
+ {{0x82d9e0a7,0x32cad096,0xbb0cc03d,0xc394d0b9}}, // _potem_, _prodej_, _でお届けします_, _minste_,
+ {{0x837af1d7,0x527f1099,0x0b60f324,0x5c0c706c}}, // _tamaill_, _kazna_, _permanec, _adarọ_,
+ {{0xf2ad0037,0xc2d9e0c6,0x6212b11a,0x6f2bf22b}}, // _شلوار_, _fotek_, _seche_, _sameigin,
+ {{0x3212b315,0x4de7a1af,0x00000000,0x00000000}}, // _peche_, _нивните_, --, --,
+ {{0xe2efe048,0x525a5056,0x1200d15d,0xefdd0160}}, // _ketnooi_, _kolla_, _mudim_, _máistir_,
+ {{0x62c501f8,0xfaf2c047,0xa7e61094,0x00000000}}, // _billige_, _beartait, _גילגול_, --,
+ {{0x62e21299,0x93ea5120,0xc225f159,0x1d0b82de}}, // _gjennom_, _molta_, _abuku_, _zaplatit_,
+ {{0x6827a144,0xe913f043,0xc303f1ff,0x0de7a02e}}, // [710] _rodinný_, _anacletu, _bedankt_, _rodinné_,
+ {{0x9201c220,0x0387917f,0x038c50eb,0xfc7de06c}}, // _nevis_, _misri_, _формирањ, _ajosepo_,
+ {{0xa382d06a,0xf683c11b,0x52d980d7,0xb8ccd015}}, // _مشاھدہ_, _श्वास_, _hores_, _necessid,
+ {{0x42f2b068,0x08f00070,0x12d8300e,0x725ad143}}, // _archief_, _کلومیٹر_, _kimera_, _koolis_,
+ {{0x4c5f012b,0x86cef072,0x5a8ca100,0x0fdd119b}}, // _systém_, _behandli, _galeriją_, _لاریجانی_,
+ {{0x82fda2e9,0xd27f802d,0x938f10c6,0x7ac740a2}}, // _forhold_, _warna_, _cestován, _zaštite_,
+ {{0x553641ab,0x00000000,0x00000000,0x00000000}}, // _страхотн, --, --, --,
+ {{0xcfdbf065,0xd200b156,0x33f8f1df,0xafe8b0ec}}, // _múinteoi, _godini_, _ongum_, _हीरालाल_,
+ {{0xee56c24b,0xda16c24b,0x265db132,0x13a250ea}}, // _zdravlje_, _zdravlja_, _اسنوکر_, _helpt_,
+ {{0xcfc4212d,0xd2ca51ec,0x9c73f0bc,0x4b0c5072}}, // _ब्रिटेन_, _folda_, _opisina_, _presseme,
+ {{0x2224e070,0xd17b6095,0x502bc032,0x00000000}}, // _minket_, _samantal, _учете_, --,
+ {{0x374232c7,0xa2d870f8,0x1224e192,0x00000000}}, // _साहित्या, _ginen_, _linket_, --,
+ {{0x248060ca,0x92ecb033,0x02cae013,0xb47f10b2}}, // _विभाजन_, _musique_, _minder_, _truớc_,
+ {{0x12a660e1,0x1306211d,0xd5430156,0x73a920b3}}, // _osoby_, _dodatek_, _pronađit, _imovine_,
+ {{0x52d8f039,0x6617903c,0x030e70f5,0x5c0b02a4}}, // _engem_, _प्रत्याश, _secafer_, _obiteljs,
+ {{0xaf28e085,0x330832a8,0xb9c5306f,0x9941119f}}, // _devolver_, _trabaho_, _riservat, _populyar_,
+ {{0xdad701f9,0x6291c220,0xe4cea2b0,0x7ef0e17c}}, // [720] _opinión_, _nevar_, _concello_, _विस्थापि,
+ {{0x32d872aa,0x573140c5,0xddb42065,0xdaa0a0cb}}, // _innes_, _страницы_, _شوفوا_, _ریٹائرڈ_,
+ {{0x62365054,0xd149a330,0xa200602d,0x725a6119}}, // _palju_, _מחיצה_, _kulit_, _loola_,
+ {{0xe2001122,0xc2d8702a,0xa16480ff,0x4c60e2cc}}, // _arhiv_, _annet_, _tvnoviny_, _lulusan_,
+ {{0xa292620b,0x9d9e30c4,0x1c767042,0xab61112f}}, // _berada_, _nainštal, _macular_, _teoranta_,
+ {{0x32d9827e,0x825af041,0xa1c5513b,0x8c76b128}}, // _vores_, _mailem_, _中华人民共和国国, _tuturor_,
+ {{0x1200e0fc,0x3829e1e6,0x2ec15132,0xea02400e}}, // _benito_, _четвер_, _الرضوان_, _ababatta_,
+ {{0xa2ca6267,0x143da073,0xb2b4005a,0x00000000}}, // _booda_, _अत्याधुन, _imico_, --,
+ {{0x22d8c0f7,0x736e3304,0xff73e13b,0x32b401ab}}, // _alleen_, _redaktə_, _编译或摘编的目的, _amico_,
+ {{0x43ead13f,0x75a2e331,0xf3eae0e6,0x5386910c}}, // _chotot_, _यावत्_, _hinter_, _opara_,
+ {{0x17034185,0xe2cae0e6,0xf7a2811a,0x1362c332}}, // _ज्ञानकोश, _kinder_, _kontribi, _drugiej_,
+ {{0xc290b239,0xe26470a5,0x43877065,0x502b024a}}, // _gedaan_, _meslek_, _tiarna_, _koleksyo,
+ {{0x5f495333,0xfede2004,0x00000000,0x00000000}}, // _existuje_, _אומזיסט_, --, --,
+ {{0x7290f119,0xf20020a8,0xa3f871fd,0xa238827b}}, // _sugan_, _arkiv_, _sinun_, _dlamini_,
+ {{0xb975a0a2,0xe4769082,0x0f02c0c3,0x5f1e902a}}, // _koristit, _процесі_, _аколо_, _основних_,
+ {{0x0e69312f,0x024830e6,0x96fd70c5,0x326e6114}}, // _سيدتي_, _kommen_, _проблемы_, _strong_,
+ {{0x07f311a6,0xe68e313b,0x3ea3701f,0xf72681eb}}, // [730] _स्थानहरू_, _国家税务总局_, _studijas_, _камшот_,
+ {{0x1e2c4030,0x7f7dc20a,0x4b0c2070,0x9c93319b}}, // _बहुआयामी_, _измаил_, _لاشیں_, _قاچاق_,
+ {{0x9305016e,0xd25ad1d5,0x4d8db091,0xd15011df}}, // _béarla_, _elele_, _cwsmeria, _impendul,
+ {{0x73f470aa,0xe2901119,0x76038133,0x0a16616c}}, // _destek_, _mahad_, _chomatai, _адолесче_,
+ {{0x4212b334,0xd71fb2d6,0x6060d160,0xf7ebb32b}}, // _fecha_, _उद्देश्य, _substain, _पिचकारी_,
+ {{0x30e8e0fd,0x2eb14020,0x1290c124,0xb3eae1b2}}, // _artikkel, _단독다가구_, _helaan_, _mentsi_,
+ {{0x82d8a0bd,0xe20091b2,0x03bd0018,0x8d8db091}}, // _kiben_, _nraim_, _showing_, _gwsmeria,
+ {{0x53870080,0x0a7d8032,0xf1943190,0x00000000}}, // _modrý_, _полуврем, _komentēt, --,
+ {{0x627ff061,0x13f9a136,0x519e802a,0x432af1d5}}, // _kaune_, _poput_, _поведінк, _akpụkpọ_,
+ {{0xf486c01a,0xc2ca41fc,0x726d8241,0xfa1721ab}}, // _имени_, _hillari_, _skroz_, _бельо_,
+ {{0x348a5122,0x2d32a072,0x5c6b7291,0xa39dc179}}, // _apartmaj, _positivt_, _जिन्दा_, _касается_,
+ {{0xd5a37335,0xbbdf22fc,0xa1b2f021,0x9bedf136}}, // _kliknutí, _kementer, _поршня_, _ograniče,
+ {{0xd2cad0a7,0x9386911e,0x6e8b5170,0x5d3201ca}}, // _glede_, _spara_, _neskutoč, _coenzyme_,
+ {{0x925aa089,0xd2fd911d,0x00000000,0x00000000}}, // _poble_, _najnovej, --, --,
+ {{0xa6b7d081,0x92b390fc,0xd5f7c07b,0x3d11c160}}, // _категорі, _munduko_, _ছাড়াই_, _اختار_,
+ {{0xe201802b,0x3395422e,0xe2cb80d2,0x3e6c0070}}, // _kurie_, _fresno_, _derde_, _شیڈول_,
+ {{0x92d8c188,0x400c6210,0x9f64204d,0xa6e2619b}}, // [740] _inden_, _privredn, _最新コメント_, _خودارضای,
+ {{0xb875702a,0x96412066,0xb3a371c5,0x00000000}}, // _студента, _ऑर्डर_, _ucapan_, --,
+ {{0xe39d20b7,0x0290404e,0xb2d9e1c7,0xfc72b085}}, // _критерий_, _numai_, _boten_, _ofertas_,
+ {{0x93ac6164,0xd7c48336,0x9010c047,0x4d880004}}, // _gruppe_, _चव्हाण_, _انتقل_, _פעלקער_,
+ {{0x227ef0e3,0x77bfc337,0xef9e3065,0x00000000}}, // _msingi_, _cokolvek_, _لحقوق_, --,
+ {{0x126cd193,0xb3eb910b,0x6c1d311c,0xd12bb338}}, // _pomoć_, _meste_, _nusantar, _servicio,
+ {{0x32abc0a8,0xc3101055,0x4edc301a,0xa46672de}}, // _håber_, _ogyahay_, _спорта_, _hyperinz,
+ {{0xa3a24104,0x00000000,0x00000000,0x00000000}}, // _lempi_, --, --, --,
+ {{0x224891ab,0xc2d99033,0x02004106,0x00000000}}, // _siamo_, _poser_, _lumii_, --,
+ {{0x12d8c181,0x426e70b5,0x7249818f,0x42d8c175}}, // _anden_, _oproti_, _bormi_, _boleto_,
+ {{0xd39660a4,0x08c10039,0x4200d092,0x00000000}}, // _urusan_, _viselked, _ludia_, --,
+ {{0x61661004,0x920010ac,0x11525203,0x8320b202}}, // _ליקוטי_, _vahid_, _станишев_, _efectos_,
+ {{0xd2003339,0xaf6f3268,0xb75991fd,0x83f40050}}, // _asmira_, _demandas_, _преступл, _postar_,
+ {{0xe2cb9044,0x23eb9015,0x3c071045,0xb40241fd}}, // _desde_, _deste_, _prosent_, _сексуаль,
+ {{0xdef12182,0xb871206f,0x2ef5711c,0xbee31024}}, // _остави_, _остава_, _panjenen, _valideyn,
+ {{0xc2918041,0x8c601018,0x8f5010a0,0x00000000}}, // _stran_, _המקצוע_, _ymosodia, --,
+ {{0xc3eb90b6,0x719812a3,0x12907037,0x4db92147}}, // [750] _neste_, _specijal, _lunak_, _sapagkat_,
+ {{0x56e6d2b2,0x5f247098,0xeb6c4236,0x00000000}}, // _barayagw, _pište_, _terrassa_, --,
+ {{0x8ae9021f,0x759b3158,0x566d319b,0x82c2806a}}, // _intrapre, _aburumak, _گلکسی_, _mailben_,
+ {{0x39cd903d,0x220180bd,0x6aff20c2,0x22ddc017}}, // _コメントを書く_, _afrik_, _истината_, _košarkaš,
+ {{0x73eae044,0x925420fe,0x00000000,0x00000000}}, // _contos_, _টেক্সট_, --, --,
+ {{0x867a1243,0x1fe9419f,0x92d8411d,0xe0719106}}, // _डेनमार्क_, _müsbət_, _izmed_, _аменинца,
+ {{0x038bb1b1,0xf9f6733a,0xbe19a017,0x1303026c}}, // _menyuam_, _atrás_, _посетили_, _relatat_,
+ {{0xa3eae039,0x8939904d,0x7376c1ab,0x332c6061}}, // _fontos_, _タイからの投稿_, _христо_, _privatum,
+ {{0xb98b40c2,0x42ca5299,0x92009254,0x2551213b}}, // _richiest, _holde_, _craic_, _违反上述声明者_,
+ {{0x63eb933b,0xcb69f02c,0x7341319b,0x00000000}}, // _odsto_, _paremmin_, _جنجال_, --,
+ {{0x02a60214,0x06d4807b,0x5da9d0b3,0x3730d00b}}, // _ibibi_, _আক্রমণ_, _postupku_, _berlangg,
+ {{0x8e272062,0x1b040250,0x00000000,0x00000000}}, // _интереса_, _persekut, --, --,
+ {{0x61db604e,0xe2cad239,0x06c4c13b,0x025a4224}}, // _спортзал_, _goede_, _查看用户评论_, _znanosti_,
+ {{0xfe3da008,0x86a88117,0x8ebb8099,0x0edec0cf}}, // _נשלחה_, _значит_, _rukometn, _milyonla,
+ {{0xf98bf07a,0x024940aa,0x9554c13b,0x43082061}}, // _штрафу_, _eleman_, _小时内解决_, _jubiliej,
+ {{0x6484b0df,0xb211804d,0x00000000,0x00000000}}, // _रोजाना_, _おすすめ商品を見, --, --,
+ {{0x9c7510a4,0xb28cf080,0xc0f02008,0xe36c7037}}, // [760] _शिक्षण_, _archív_, _הצדדים_, _ningali_,
+ {{0xf2906102,0xcfa42070,0x3f46030b,0x00000000}}, // _mulai_, _ٹویٹر_, _persemba, --,
+ {{0x7386905a,0x93eae037,0x7f64d19d,0xfa70d192}}, // _ibara_, _mentah_, _dibandin, _berlings,
+ {{0xb386033c,0x32d9c024,0x9e4f5175,0x598050f8}}, // _abiri_, _sovet_, _впечатли, _ostirale,
+ {{0xb25e104b,0x4e54e106,0xf37ff04d,0x62d8c040}}, // _huntsvil, _економич, _この質問に対する, _allein_,
+ {{0xcc85a008,0x625b40a0,0x147b8090,0x2e9b8090}}, // _רואים_, _apelio_, _sistemin_, _sistemit_,
+ {{0x1c4b8134,0xb38691d6,0x626c6069,0x9290800d}}, // _cartref_, _mbara_, _txoos_, _bihari_,
+ {{0xc3f8b0fc,0x6f8a204d,0x52d8c0a5,0xa26e133d}}, // _moduan_, _アメブロランキン, _giden_, _gepost_,
+ {{0xa290906e,0xf29030de,0x3977626c,0x00000000}}, // _aikasi_, _jumala_, _manifatt, --,
+ {{0x65bd2056,0x7ae86144,0x5c7530e8,0xaa7d10a4}}, // _erfarenh, _založený_, _minuter_, _sumatera_,
+ {{0xb2a08020,0xde520190,0xe1e121a3,0xd096f19b}}, // _생각합니다_, _bibliotē, _oprogram, _داداشی_,
+ {{0xf38691be,0xeb7d60d1,0xd2cbe06e,0x1316900d}}, // _abara_, _abonnier, _aguleri_, _abaza_,
+ {{0x8f5c00d5,0x6d30c1ae,0x42d8c03d,0x93209087}}, // _सिंगल_, _clasific, _viden_, _araya_,
+ {{0xe585902b,0xf2d8a0bc,0x727f00a1,0x00000000}}, // _плазмы_, _tobey_, _maanyi_, --,
+ {{0x028d61c6,0xa63a90a4,0x638f1039,0x138780a0}}, // _perkara_, _प्रश्नां, _rengeteg_, _gwyrdd_,
+ {{0x027f417f,0xa2ca71cd,0x72d8c03d,0x98c0203d}}, // _kwenye_, _monde_, _andet_, _続きを読む_,
+ {{0xc2d830c2,0xbec72050,0x9386633e,0x00000000}}, // [770] _almeno_, _impostos_, _oboru_, --,
+ {{0x0290d091,0x569a0050,0x227f4038,0x02f690a4}}, // _ardal_, _нормално_, _mwenye_, _कोशिंबीर_,
+ {{0xa2d8f072,0xb25ad1c2,0x12fe608c,0x8411a0fc}}, // _ingen_, _millor_, _jirgin_, _gainerak,
+ {{0x73f870c5,0x3c51e1b4,0x4683f02a,0xc2da10d4}}, // _minua_, _tantara_, _січень_, _appell_,
+ {{0xe02a32ae,0xf2b54128,0x6f9940ca,0xb2646014}}, // _физическ, _precum_, _उत्तेजक_, _haulwm_,
+ {{0x8290c040,0x0daee1b2,0x32e1106e,0xb386c0bd}}, // _damals_, _tiffanie_, _ajunilo_, _florid_,
+ {{0x22d8c082,0x731b933f,0xe3eb50ae,0x331f0036}}, // _tiden_, _názor_, _lietus_, _mercato_,
+ {{0x52d8c134,0x22918266,0x12ca7202,0x02004252}}, // _fideo_, _teraz_, _donde_, _kamid_,
+ {{0x4290c05d,0x72f540a2,0xec1f50dc,0x6104c340}}, // _tulaga_, _obzirom_, _perandor, _mobiarmy_,
+ {{0x12d8c299,0xb2d0602e,0x4c6a80de,0x73ea71cf}}, // _siden_, _včera_, _चित्ते_, _fonte_,
+ {{0xfd76206a,0x127f8029,0xd477e173,0xe30e4044}}, // _پرِنٹ_, _karna_, _зарэгіст, _citados_,
+ {{0x22d8f134,0xf290205e,0xd290205e,0xd3e75036}}, // _angen_, _sukat_, _bukas_, _applicaz,
+ {{0x938062cc,0xde735176,0x327f8055,0x4248d10c}}, // _aturan_, _सशक्त_, _marna_, _chomas_,
+ {{0x52d8c153,0xc3ea718c,0x0fb51062,0x8673b0ea}}, // _video_, _konte_, _играчи_, _북구문화예술회관_,
+ {{0xa2cb8099,0x4389a078,0xa2ab92df,0xac881173}}, // _tvrdi_, _abiyamo_, _gábor_, _катэгоры,
+ {{0x5f33a036,0x3a1100c6,0x00000000,0x00000000}}, // _следните_, _nezapome, --, --,
+ {{0xdfc20156,0xe200e2b2,0xd3f0b0c5,0x00000000}}, // [780] _najviše_, _kuniga_, _viestisi_, --,
+ {{0xf9b050dc,0xf27e621f,0x71044065,0xb9bec033}}, // _menjëher, _skont_, _مناقشة_, _effectue,
+ {{0xfd70e237,0x57e54045,0x227e9119,0xd2b4006f}}, // _perminta, _актуальн, _iyana_, _amici_,
+ {{0x4f28e1dd,0x1a08609f,0x2316e11a,0x7f5b8102}}, // _kontrola, _belangst, _prizon_, _pelaksan,
+ {{0xa27f81d7,0xf29040de,0x00000000,0x00000000}}, // _darna_, _urmas_, --, --,
+ {{0x52d8c1a0,0xb31f129c,0x374ed01a,0xd28f11c0}}, // _ander_, _mercado_, _крови_, _merkado_,
+ {{0x627e911a,0x02009323,0x62d8c03c,0x72d802d7}}, // _chans_, _orain_, _wideo_, _chiec_,
+ {{0x3c012025,0x22a78121,0x00000000,0x00000000}}, // _हरमोहिंद, _garbi_, --, --,
+ {{0xb163403d,0xeb82b07b,0xfb27c204,0x22a7c0a2}}, // _ペタを残す_, _পড়তে_, _nesreća_, _nesreći_,
+ {{0xb649907a,0x1386d0a0,0xa4166060,0x00000000}}, // _медаль_, _storio_, _ajorehin_, --,
+ {{0x7ab1404d,0xffb5b002,0xd2cad122,0xa8f051b2}}, // _お気に入りブログ_, _postitus_, _sledi_, _niagthau,
+ {{0xfa0ba134,0x720ec061,0x003c1019,0x1b51f0a4}}, // _asiantae, _палац_, _seleccio, _tindakan_,
+ {{0x4e72500b,0xf2a78190,0x5af4e185,0x9d8b40b3}}, // _जोक्स_, _darbi_, _investas, _kilometa,
+ {{0x7e33611d,0xd291207c,0x9b1340fe,0x72ab91ce}}, // _pridržan, _locali_, _পর্বে_, _tábor_,
+ {{0xe2902083,0x7997813b,0x27590025,0xce4811ec}}, // _tukar_, _突发公共卫生事件, _मूल्यों_, _aịsaịa_,
+ {{0x42d8f045,0x0a1531df,0x4200e328,0x721470b2}}, // _eigen_, _abrahama_, _dunida_, _hướng_,
+ {{0x11d0e1a6,0xad90e1c9,0x7b0c311c,0x3292513e}}, // [790] _telefony_, _telefone_, _ناحیه_, _betala_,
+ {{0x0c76c09d,0xd29021c6,0xf9696271,0xde27f07b}}, // _यात्री_, _sukar_, _militare, _হোমপেজ_,
+ {{0x1bc89025,0xd36f102b,0x00000000,0x00000000}}, // _परपीड़न_, _adresą_, --, --,
+ {{0x99f4a098,0x0204101a,0x11e59017,0x9ebb9004}}, // _povlečen, _valikoim, _будућнос, _תּמיד_,
+ {{0xb3870328,0x428e9047,0x222ac195,0x00000000}}, // _sharci_, _aithint_, _показ_, --,
+ {{0x63ea9011,0xb0bda04d,0x00000000,0x00000000}}, // _plats_, _コメント投稿_, --, --,
+ {{0xf36f20d6,0x858740f6,0xd0f5b10b,0x29bc81d5}}, // _tingkat_, _bestelak, _najstarš, _adọnye_,
+ {{0x1c025341,0x13ea5034,0xcbb5600c,0x00000000}}, // _persoas_, _molti_, _projeler, --,
+ {{0x02d8f11e,0xe2da624e,0x87d4219b,0xe201808c}}, // _inget_, _koreya_, _بخشنامه_, _burin_,
+ {{0x3ee9d1e5,0xc2907020,0x1f79d242,0x18e81008}}, // _funktion, _vanaf_, _function, _התפרסמו_,
+ {{0x027ff215,0x620250d4,0xf276814f,0x820261f5}}, // _kauno_, _attiva_, _upinzani_, _terina_,
+ {{0xf27ff190,0x92904128,0xc40e5036,0x83eca13c}}, // _jauno_, _numar_, _residenz, _братства_,
+ {{0x02901077,0x02918055,0x7db6810c,0x72d8c078}}, // _rahaa_, _furan_, _acarsaid_, _ailera_,
+ {{0x42d9a0f5,0x12027061,0x72d851c6,0xd29071dc}}, // _gopet_, _kurios_, _filem_, _arnat_,
+ {{0xf2a76060,0x28192045,0xa3ae2249,0x86f161a3}}, // _sugbon_, _компанія_, _köpte_, _kontaktó,
+ {{0x030d41ef,0xa9e3d017,0xfb6201e6,0x3fbbe011}}, // _lugares_, _пожара_, _publiser, _fungerar_,
+ {{0xf2904047,0xd297c17b,0xf24a11a3,0x48145190}}, // [7a0] _cumas_, _matariki_, _firmę_, _kolektīv,
+ {{0x8201807a,0x83ea922e,0xb0d1807b,0xe36f2189}}, // _kurio_, _fajtim_, _কাউন্সিল, _singkat_,
+ {{0xc3a24089,0xe06422c7,0x368371f9,0x00000000}}, // _temps_, _अक्षरे_, _producci, --,
+ {{0x33ebe0e5,0x52e65218,0x59e47030,0x094db068}}, // _dette_, _svenska_, _शीर्षस्थ_, _formulie,
+ {{0xf2ed40c3,0x21862106,0xdaa3d0b7,0x82016008}}, // _aprilie_, _економик, _индепенд, _region_,
+ {{0x9295f20d,0x849d1154,0x78e372c6,0x82ca004f}}, // _merkezi_, _matangaz, _prisustv, _poids_,
+ {{0x52919074,0xc9481008,0xc200611c,0xf660b18f}}, // _kusan_, _הגילאים_, _nulis_, _karkashi,
+ {{0x03ea60fa,0xb686a070,0x12006033,0xe25a6143}}, // _kooti_, _جدوجہد_, _trois_, _kooli_,
+ {{0x93030057,0x348c60e7,0x43210205,0x00000000}}, // _gelagat_, _naprednj, _maayos_, --,
+ {{0x1d7ba0cc,0xdab0b020,0x2cf76061,0xdf69f1ab}}, // _תלמיד_, _알려주세요_, _генералы_, _произвеж,
+ {{0x05f93215,0x72480022,0xbadda0f6,0x82fe40c6}}, // _ролях_, _shima_, _sustatze, _registr_,
+ {{0xe359d207,0xb9d9a004,0x73869039,0xf20010bc}}, // _разновид, _אינדרויס, _ipari_, _bahin_,
+ {{0xce49f19e,0x72ecb031,0x025a1052,0x530300cb}}, // _antaraba, _mariage_, _enhle_, _feladat_,
+ {{0xc62a1050,0xd2f4e158,0x8387a1ea,0x5265d293}}, // _организи, _ìyàwó_, _zapri_, _агенции_,
+ {{0xc29190f6,0xd2bd700e,0x631d6120,0x5f9ed19b}}, // _busan_, _liddell_, _tercera_, _محکوم_,
+ {{0x94ba9194,0xe2904119,0x7b6a9082,0xad10704d}}, // _статті_, _cumar_, _стаття_, _商品レビュ_,
+ {{0x7b62111e,0x5d0210d7,0xa2002024,0xaefdb031}}, // [7b0] _publicer, _publicat_, _lakin_, _上的问答吗_,
+ {{0x2873f1ef,0x3c637264,0x8dc53065,0xf7d0c100}}, // _cancelar_, _outubro_, _طبخات_, _многа_,
+ {{0x406d726c,0xb68cc0df,0x4ec8a07a,0xf4058098}}, // _temporan, _बौछार_, _трансвес, _sdružení_,
+ {{0x41cba045,0xcb1f90f1,0x86a82208,0x9e9ec1aa}}, // _консульт, _bahagian_, _bharrach, _дакле_,
+ {{0x5200201a,0xf27c3015,0x92cae188,0xd3eae18d}}, // _takia_, _necessár, _vinder_, _vinter_,
+ {{0xee9ec2da,0xa6ad9106,0x30f76342,0x0291b212}}, // _файла_, _autentif, _तर्रार_, _jawaab_,
+ {{0x4fc7c118,0x42b54202,0xdc7c8077,0x933c7157}}, // _дизайн_, _precio_, _naisten_, _losxij_,
+ {{0xe80a3034,0x0201416a,0xa641932b,0x00000000}}, // _струва_, _rodiny_, _मण्डी_, --,
+ {{0x34844020,0xbf5c214e,0x49c511da,0x8d8c0145}}, // _주변정보는_, _magandan, _intestat, _jučer_,
+ {{0xac07317b,0xe37b0233,0x2d9a40db,0x39cf4013}}, // _tharciss, _espaço_, _velocida, _selectee,
+ {{0x52d7d02c,0xdba6c070,0x00000000,0x00000000}}, // _машину_, _سازشیں_, --, --,
+ {{0xc2009031,0xa2d8f211,0x7697e343,0xd2027029}}, // _frais_, _enger_, _शिवरायां, _karier_,
+ {{0x42902074,0x4301a1a0,0xc2f7331d,0x4301017f}}, // _hakan_, _verband_, _madinah_, _sayansi_,
+ {{0x275aa223,0x2ee9e04d,0x6ae7907b,0x58412065}}, // _संस्करण_, _ありがとう_, _উদরাজী_, _موريتاني,
+ {{0x73a9718d,0xe4e4a234,0x438741f9,0x3388418e}}, // _erfaring_, _प्रभावशा, _cuerpo_, _classnam,
+ {{0xa2bc60bc,0x03a8e028,0x12e310b9,0x8c01c051}}, // _kredito_, _dilarang_, _polisie_, _bangaror,
+ {{0xc386d158,0xd0fed0a2,0x62fd625f,0x4db4e12f}}, // [7c0] _apere_, _društven, _byggja_, _saineola,
+ {{0xd8f5f1af,0x00000000,0x00000000,0x00000000}}, // _студенти, --, --, --,
+ {{0xf3ea7015,0x826c20a2,0x73eae037,0xf29250bd}}, // _conta_, _nekog_, _pinter_, _pataje_,
+ {{0x23209150,0xe9870344,0x00000000,0x00000000}}, // _isaya_, _देशमुख_, --, --,
+ {{0xc2fc60a2,0xa4964158,0xe3f8703e,0x98626345}}, // _ovoga_, _ògbufò_, _hinum_, _zapomenu,
+ {{0x9c67f02d,0x3fe82029,0x52902074,0xbdfce1fd}}, // _manusia_, _स्थानिक_, _yakan_, _ситуация_,
+ {{0xd274c047,0xc7956286,0xb307102d,0x00000000}}, // _سوالف_, _história, _majalah_, --,
+ {{0xc0513100,0x5a0300a2,0x7dadb105,0x72925282}}, // _хвост_, _pronađen, _mütəxəss, _états_,
+ {{0x13f800b6,0x5290917d,0x0290d105,0xa3f8a035}}, // _агентств, _praat_, _ordan_, _gedung_,
+ {{0xce819068,0x3d0cb1b8,0xd3ea70c3,0xb290c12c}}, // _서울특별시_, _комплект, _ponta_, _jamais_,
+ {{0xe25ad06a,0xc290a328,0x1edc9178,0x0386c143}}, // _amely_, _nabad_, _artistes_, _noored_,
+ {{0x0386900b,0xa2009177,0xc2cb10a2,0x2f5d9094}}, // _acara_, _trais_, _mozda_, _היתכן_,
+ {{0x5c5b81a3,0xa2903276,0x38c2804d,0xfb6a0007}}, // _postaci_, _najan_, _ドラマティック_, _aileesun_,
+ {{0xe200b163,0xf38051e3,0xf290e005,0x7201a24b}}, // _jedino_, _matric_, _einari_, _kupio_,
+ {{0xf22551b2,0x77a3e145,0x457511a3,0x035951bf}}, // _amekas_, _područja_, _dostarcz, _trafiku_,
+ {{0x625ad0e8,0x505a1175,0x00000000,0x00000000}}, // _gillar_, _променли, --, --,
+ {{0xe7956044,0xea8c405d,0x6aa4d02a,0x89cb1232}}, // [7d0] _históric, _chairmen_, _севастоп, _belediye,
+ {{0xa2d6a091,0xb2480139,0x00000000,0x00000000}}, // _priodol_, _nzima_, --, --,
+ {{0x62b4325e,0x43877175,0xd3f8f346,0x5200d0b9}}, // _entità_, _quarto_, _sigue_, _brein_,
+ {{0x329040c5,0xb053c16c,0x97b3512a,0xa290412c}}, // _samaa_, _солиде_, _geospati, _maman_,
+ {{0x16e20017,0x9290410a,0x852ef17f,0x9291b347}}, // _резултат, _laman_, _mwendesh, _hrvata_,
+ {{0xa4d65030,0x5b1fc04c,0xb003603f,0x00000000}}, // _हाजिरी_, _kelantan_, _konsultā, --,
+ {{0x627f0267,0x9471e057,0x31c5519b,0xf93212d3}}, // _mwangu_, _hantaran_, _اضطراب_, _rasmusse,
+ {{0xa200d22b,0x1ce0503d,0x02fc6347,0x73596031}}, // _grein_, _削除用パスワ_, _svoga_, _compléme,
+ {{0xed3b013b,0x42904154,0xf547e0c3,0x03806020}}, // _国家知识产权局_, _tamaa_, _белорушь_, _sturen_,
+ {{0x42ca7036,0x2312216a,0xacf7118e,0x61a8803f}}, // _mondo_, _प्रमाणपत, _southwar, _maksimāl,
+ {{0x729040d8,0xc4fec1ab,0x82b6630c,0x00000000}}, // _jaman_, _временно_, _marcha_, --,
+ {{0xfff3e11d,0x8bdd3129,0x5290f128,0xd2862208}}, // _nepremič, _बछिया_, _rugam_, _dunkeld_,
+ {{0xe4b470f8,0x2cb7b13c,0x5c8011bc,0xd3cba018}}, // _didaktik, _границе_, _лесен_, _לפגוע_,
+ {{0x02948052,0x139571dd,0x1291c024,0xd29160f7}}, // _ofakazi_, _osasun_, _arvad_, _gegaan_,
+ {{0xb290414e,0xd2004297,0xe281b038,0xa290c12a}}, // _naman_, _namin_, _jamhuri_, _delano_,
+ {{0x22fce1d6,0x4a442004,0x4ef5a07b,0xded75097}}, // _fingal_, _טראפיק_, _আরাফাত_, _bonitetn,
+ {{0x2291e0bd,0x508a2218,0x48995175,0x8723c065}}, // [7e0] _detay_, _чиновник, _ориентац, _جهازك_,
+ {{0xc2caf12e,0xd2918348,0x64a7e0d7,0xf2ea5264}}, // _pridaj_, _otras_, _organitz, _região_,
+ {{0xa38660d7,0x0a8fd2cd,0x93bfe0ea,0x4201a11d}}, // _acord_, _penipuan_, _맨마지막글_, _kupil_,
+ {{0x42bfc2df,0xf2f23027,0xd2cb9266,0xb2d870cb}}, // _شمشاد_, _putovanj, _dokonca_, _kinek_,
+ {{0x06678025,0x00000000,0x00000000,0x00000000}}, // _बेकसूर_, --, --, --,
+ {{0x20d7a0cc,0xf4128295,0x1cb281a4,0x1dffd02d}}, // _הגאון_, _sarajevo_, _sarajevu_, _ketentua,
+ {{0x4290422b,0x4d41719d,0x2be231ab,0x02e950f9}}, // _gaman_, _membantu_, _inserisc, _andidan_,
+ {{0xcbddc1f7,0xa046416a,0xcc64d0cb,0xceb3b13c}}, // _बप्पा_, _zeměděls, _اکیڈمی_, _smernice_,
+ {{0x74b821cc,0x62373062,0xd7a55165,0x00000000}}, // _विशेषतः_, _filmova_, _umoristi, --,
+ {{0xb9d7a1b0,0xaa77a0d5,0xc033902a,0x4290e18a}}, // _प्रयाग_, _प्रभाग_, _демотива, _manawa_,
+ {{0x1cd7d0a9,0x4236d0a2,0x2ceed032,0x3734a0ff}}, // _pretraži, _svojoj_, _среде_, _rosnička_,
+ {{0x8290500c,0x00372117,0x0d05e008,0x37a5e089}}, // _kalan_, _группа_, _introduc, _introdui,
+ {{0x72905028,0xd3f9b1b2,0xf2fcd187,0x337fd029}}, // _jalan_, _nique_, _blogak_, _belanda_,
+ {{0x320050dc,0x1df62008,0xb3341004,0x027ef055}}, // _dalin_, _מיליארד_, _סאציאלע_, _idinku_,
+ {{0x4394600e,0x7c625098,0xb2c43035,0xd292610c}}, // _amosi_, _rubrice_, _televisi_, _afraga_,
+ {{0x5886d349,0x82a770b2,0xe9eb70fc,0x32027100}}, // _प्रस्ताव, _muaban_, _langilee, _kurias_,
+ {{0x7025318a,0xb3877084,0x359f90ed,0x9da6916f}}, // [7f0] _عوارض_, _luaran_, _pengetah, _navikava,
+ {{0xfd173070,0x28b6d19b,0x00000000,0x00000000}}, // _علاقوں_, _شرايط_, --, --,
+ {{0x72005246,0x738070fc,0xc387a0bd,0x00000000}}, // _halin_, _mauris_, _espri_, --,
+ {{0x0d94515e,0xa6dea19f,0x738692a9,0xe290d128}}, // _finanšu_, _məsuliyy, _abari_, _vreau_,
+ {{0x0ff1d243,0x1ed410ae,0x16e60105,0x3c02b044}}, // _नियुक्ति_, _palielin, _kampaniy, _básico_,
+ {{0x320041be,0x59f36031,0x127ff06d,0x491e20de}}, // _tamin_, _constitu, _jauna_, _पत्रिकाक,
+ {{0xe291d088,0xe998e061,0x32903241,0x4f054230}}, // _buwan_, _каментар, _nemamo_, _mišković_,
+ {{0x1e539107,0x329050aa,0x5ea6421f,0x02ec620e}}, // _медицина, _falan_, _interess_, _teknisk_,
+ {{0x925af0a8,0x62005119,0x56bd613c,0xdc1d20ad}}, // _nogle_, _galin_, _операциј, _kitablar,
+ {{0x8a04c0d4,0x6a25a00e,0x6752505c,0x835fe221}}, // _dibattit, _abachaga_, _सेलिब्रि, _legendy_,
+ {{0x8201133c,0x83e0706a,0xfa57707b,0x00000000}}, // _bibiri_, _héten_, _করুনঃ_, --,
+ {{0x62905024,0xc33da070,0xd26c5017,0xbebb204f}}, // _yalan_, _ہتھیار_, _belog_, _tourisme_,
+ {{0x227ef086,0x826c50f5,0xd5f3a032,0x7cd7c047}}, // _ibindi_, _tabindex_, _моменти_, _إعداد_,
+ {{0x1770b34a,0xe3f8211c,0x2306b23e,0x43b67144}}, // _क्लासीफा, _tikus_, _samalah_, _dievča_,
+ {{0xe03cd03a,0xdfa4f153,0xb351d031,0xdc76732b}}, // _recensio, _november_, _其版权属于商务部, _सिल्वर_,
+ {{0x0c66b192,0x53a0c273,0x07f24098,0xd756d17c}}, // _bestemt_, _удостове, _नजिस्क्य, _परिषद्_,
+
+ {{0x4ffad035,0x529080de,0xa25521ee,0x927f005d}}, // [800] _परवानगी_, _tahaks_, _directam, _abangi_,
+ {{0x2069802c,0x32a7818f,0x9a48d1a5,0xca3dd19b}}, // _напомина, _harbi_, _freeship_, _مانکن_,
+ {{0xe25a600f,0x230c31be,0xa200b33a,0x62a7818f}}, // _poolt_, _nazaret_, _pedido_, _karbi_,
+ {{0xbc446181,0x0200225e,0x72f42018,0xcf0a00f3}}, // _プライバシ_, _żmien_, _באחריות_, _симптоми,
+ {{0x437970ca,0x4b40b020,0x220050f6,0xf200d10c}}, // _poradna_, _남겨주세요_, _balio_, _greis_,
+ {{0x63f83206,0x53e54142,0x00000000,0x00000000}}, // _kamusi_, _imputati_, --, --,
+ {{0xd2480024,0xa2e9f0dc,0x2ead8050,0x00000000}}, // _daimi_, _gjendet_, _комисија, --,
+ {{0x348680de,0x96b11016,0x3200518b,0x125ad0de}}, // _बाजारी_, _sigurish, _talin_, _milles_,
+ {{0xa3bbb05a,0xdc00d100,0xa36c7061,0x6d70f02c}}, // _indwara_, _maistas_, _lengvai_, _varmista,
+ {{0xa320718f,0x42907154,0x838690a8,0xd57fe19e}}, // _sanya_, _sanaa_, _vcard_, _peruntuk,
+ {{0x22d8c122,0x4f239004,0x9f735282,0xaa42908f}}, // _videl_, _דעפארטמע, _parcours_, _プラズマ処理室内,
+ {{0x6290f064,0xc20190c4,0x227f401e,0x017d9018}}, // _bahawa_, _musia_, _tienen_, _מיתוג_,
+ {{0x6422503a,0xe291d18f,0x82912209,0x23ead091}}, // _indirizz, _ruwan_, _ticari_, _doeth_,
+ {{0xe68a70e7,0xb1772017,0xf3f83269,0x28c751fc}}, // _организо, _месту_, _samuti_, _stadionu,
+ {{0xdff2c170,0xb320734b,0x21cc9033,0x13f8f120}}, // _presvedč, _tanya_, _activité_, _sigui_,
+ {{0xc3f82090,0xde91312f,0xa200d065,0x7616a0d0}}, // _sikur_, _ينبغي_, _breis_, _ballkani,
+ {{0x42b4003f,0x82d8d093,0x8870105e,0x115a8082}}, // [810] _teica_, _dieet_, _pakiramd, _situasjo,
+ {{0xc0741004,0x127ff067,0x1dfad0af,0x09bad1ea}}, // _גניבות_, _hyung_, _povezave_, _povezava_,
+ {{0xd3949063,0x1d5fb2a5,0x49fe417a,0x00000000}}, // _amasi_, _במזיד_, _verament, --,
+ {{0x92d8c153,0x22927133,0xa290d10c,0x00000000}}, // _under_, _turais_, _treas_, --,
+ {{0x7a9740de,0xc225706e,0x44695032,0x0e26802f}}, // _सम्मान_, _apakan_, _пристигн, _халяву_,
+ {{0x42903133,0x627f02b2,0x183b603d,0x2aa760cd}}, // _camara_, _abandi_, _こんにちは_, _डिप्लोमा_,
+ {{0x920032c3,0x1248d247,0x77ca5100,0x3d09103c}}, // _wajib_, _asompi_, _verslini, _उपलब्धि_,
+ {{0x027f41a9,0x65af3260,0x7200517f,0x4305201c}}, // _mwenge_, _споразум, _walio_, _ostalom_,
+ {{0x92d98187,0x1290724d,0xb2ec6153,0x9287c276}}, // _diren_, _canan_, _february_, _نفرات_,
+ {{0xaa083070,0xa3c1f045,0x82e95247,0x960ad042}}, // _تصدیق_, _географі, _aganihu_, _algonqui,
+ {{0x7387e190,0xe1483179,0x2249705e,0x00000000}}, // _katru_, _kaverill, _alamin_, --,
+ {{0xb387d12b,0x53eb90d9,0xff041144,0x6230506e}}, // _dobrý_, _boste_, _chladnič, _ademola_,
+ {{0x1aefb134,0x4cbd4018,0x00000000,0x00000000}}, // _hystyrie, _disabled_, --, --,
+ {{0x33eb804f,0xda226172,0x1f07006c,0xd290f039}}, // _porte_, _kwicande, _abẹla_, _magad_,
+ {{0x4deb31ab,0x4c025100,0x8f53d1bc,0x9c35a332}}, // _странно_, _verslas_, _домашна_, _ostatnic,
+ {{0x52cb5040,0xa1c7311c,0x024830e8,0xd949d1f4}}, // _wieder_, _هاستینگ_, _kommun_, _पारसनाथ_,
+ {{0x2ba4f011,0xf291f024,0xe00820cc,0xa420e0ea}}, // [820] _diskuter, _tutan_, _געגנטן_, _개인정보의_,
+ {{0x5759d025,0x4dbd2119,0xb2d85090,0xf95651fd}}, // _निर्यात_, _arrintaa, _cilet_, _студенто,
+ {{0x7386d158,0x13ebf0e8,0x52cae272,0x00000000}}, // _apero_, _sluta_, _bindur_, --,
+ {{0x1f22907b,0x9637c1ab,0xc2d98232,0x00000000}}, // _কাঁচা_, _файлове_, _giren_, --,
+ {{0x4a39216c,0xa31b60a5,0x82e56171,0x5b5cb27d}}, // _веселие_, _müzik_, _mínimo_, _jedinečn,
+ {{0x92cae072,0x62eb6008,0xb2009221,0xd3178017}}, // _findes_, _changes_, _mikiny_, _ubrzo_,
+ {{0x525a5134,0xa00521af,0xad49f170,0xa726e082}}, // _colli_, _огромна_, _filtráci, _дисциплі,
+ {{0x6387009d,0x4292604e,0x6204116a,0x3b0c3036}}, // _dobrze_, _durata_, _probíhá_, _rapprese,
+ {{0x095630c3,0x2ee9d0ee,0x42b281dd,0x7320700c}}, // _минуте_, _funksion, _munduan_, _banyo_,
+ {{0x3479f057,0x527ff06e,0x7f2170ea,0x53eb9096}}, // _diletakk, _ikuna_, _홈페이지를_, _roste_,
+ {{0x1e55e0fc,0x627ff074,0x4290d34c,0x395f606c}}, // _espainia, _shuni_, _mudar_, _afonrere,
+ {{0x125b9230,0x229071c0,0xfa963166,0xa888e0e7}}, // _posle_, _tanan_, _नर्मदा_, _фејсбук_,
+ {{0x025a9053,0xb32091ca,0x23eb50b9,0x24180187}}, // _zoals_, _maayo_, _pieter_, _nabigatz,
+ {{0xf2d981e2,0xb9ef40d3,0x1200f121,0x5f5d4133}}, // _ziren_, _استعادة_, _nahita_, _disarman,
+ {{0x339b103d,0xbb7450b7,0x320072bd,0x5f6a500e}}, // _こんばんは_, _реглемен, _sanin_, _ababaman,
+ {{0x92eff05d,0x52fc70e6,0x3c716126,0x1d20c05d}}, // _okufa_, _menge_, _المختوم_, _abasibir,
+ {{0xa2d84072,0xd290a124,0x0ee3b34d,0x42fc7081}}, // [830] _timer_, _rabaa_, _महत्वाका, _lenge_,
+ {{0x85be71aa,0x55040106,0xfcdf602b,0x9129f0d7}}, // _формиран, _produsul_, _аспектах_, _formació_,
+ {{0x4290a05e,0xc2d850e8,0x7eacc2f9,0x226c8298}}, // _laban_, _filer_, _dovoljen, _sinoć_,
+ {{0xf6a9024d,0x529051da,0x00000000,0x00000000}}, // _litreach, _talab_, --, --,
+ {{0x32489154,0xb2d87211,0x12fcd14a,0x6a76f100}}, // _chama_, _ihnen_, _ndege_, _частку_,
+ {{0xf630b12f,0xed86b08e,0x00000000,0x00000000}}, // _الأبيض_, _vrednost_, --, --,
+ {{0x6386d05d,0x1200a154,0x3afbc1a1,0x02da707e}}, // _mberi_, _tabia_, _peperiks, _jeremi_,
+ {{0xa35571ad,0x89b96106,0xe43aa04d,0x00000000}}, // _cabelos_, _алеӂерил, _相棒探しから_, --,
+ {{0x29243004,0xbd86c036,0xa2907199,0xf94e9163}}, // _אָנװײַז_, _награди_, _manam_, _maglovit,
+ {{0xa2cb4144,0xc290a18f,0x840490b9,0xb4c7b34e}}, // _predaj_, _daban_, _intussen_, _buscando_,
+ {{0xb485211f,0x839fc047,0x99a41117,0x923b413b}}, // _अर्थात_, _اصدار_, _опции_, _控制面板首页_,
+ {{0xe3543084,0xc201c229,0xb29021c4,0x82cb8122}}, // _selepas_, _kuvia_, _jemand_, _morda_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe2907095,0xa249913a,0x2f5d301b,0xe5f66178}}, // _banal_, _bismo_, _merancan, _esperanç,
+ {{0x6ad07032,0x82d8526d,0x00000000,0x00000000}}, // _инфекции_, _biler_, --, --,
+ {{0x4b6121f9,0x5d8740a2,0xf24110cb,0x46a5b076}}, // _también_, _međunaro, _منقطع_, _energety,
+ {{0xcc9ec031,0xd69a5304,0xd24991eb,0x344f3004}}, // [840] _新农村建设_, _sakinlər, _eismo_, _אומשולדי,
+ {{0x7202602d,0x7815107b,0x3ed78034,0x03eb8011}}, // _terima_, _প্রখ্যাত_, _другата_, _borta_,
+ {{0xb2f3019a,0x525ae133,0x84b3f05d,0xc23b21a0}}, // _proiect_, _chille_, _abachwez, _klimaat_,
+ {{0xb2fcd0a2,0xc3f99078,0x4c7361ae,0x72906209}}, // _svega_, _sisun_, _podrás_, _aslan_,
+ {{0xfee2d173,0x72907235,0x6316403a,0xe30c3057}}, // _атаку_, _sanad_, _bambini_, _pelapis_,
+ {{0x33f47089,0x31ed110b,0x1c1d81fb,0xc26ce210}}, // _sortir_, _formulár, _רשעות_, _osnovu_,
+ {{0xd56400cc,0x46cc211a,0x2c2c2108,0x00000000}}, // _לאנדאן_, _ekspresy, _ekspress, --,
+ {{0x0e3931a1,0x3e4300ad,0x42918190,0xb7f13175}}, // _kebangsa, _almaniya, _kuram_, _скара_,
+ {{0x6b280008,0xc2918208,0x22024172,0x92d8b008}}, // _התייעץ_, _curam_, _ulwimi_, _modern_,
+ {{0x0fa98126,0x2becd13f,0xa30310cb,0xa2d8e099}}, // _تبلیغی_, _superthr, _مشرکین_, _kineza_,
+ {{0xfb8d102a,0x12d870b5,0x0394704d,0xf45f20a4}}, // _artistar, _ihned_, _件を表示していま, _अध्यात्म,
+ {{0x1f612293,0x08712107,0xb6293047,0x827ee09c}}, // _остане_, _остана_, _traidisi, _ayinza_,
+ {{0x3105d1f9,0x83e2506a,0x00000000,0x00000000}}, // _পাশাপাশি_, _természe, --, --,
+ {{0x37a300bb,0xe29201e8,0x0ead8017,0x00000000}}, // _cerulari, _otpada_, _доминира, --,
+ {{0x63ac6295,0x22e8e074,0x024c81f2,0x22007083}}, // _ukupno_, _miliyan_, _होखला_, _isnin_,
+ {{0xdf7cf03c,0x03eb90d4,0x2019e17e,0xd290605e}}, // _पुस्तकें_, _bosta_, _хиротони, _kulay_,
+ {{0x9290a124,0x93eb902c,0x5286c22e,0x8291605a}}, // [850] _qaban_, _josta_, _sibhawm_, _bigari_,
+ {{0xb373f10b,0xefe71118,0x32bd7208,0x48db2053}}, // _zadajte_, _заявка_, _riddell_, _leerling,
+ {{0xca1c0100,0x54d7d0ec,0xbe917018,0x84013221}}, // _маленькі_, _वेटिकन_, _district_, _infolink,
+ {{0x92fe4116,0xe44ba158,0xa248c23e,0x939da061}}, // _weeraray_, _àdéhùn_, _salmah_, _palangoj,
+ {{0x23967089,0xbeab70ab,0x00000000,0x00000000}}, // _passen_, _dimecres_, --, --,
+ {{0xc200a1d2,0x6290c058,0xf2e3209f,0x02918144}}, // _rabin_, _madan_, _maniere_, _juraj_,
+ {{0x5290c18c,0xa51090fc,0x805e615b,0xa394002a}}, // _ladan_, _hurrengo_, _moontlik_, _reise_,
+ {{0x4303a0a2,0x9067a34f,0x3d3c211c,0x65fa70fe}}, // _pitanju_, _आकासे_, _میدهد_, _হবিগঞ্জ_,
+ {{0x92a6516f,0x7201b233,0x3c773072,0x00000000}}, // _daljini_, _devido_, _kristne_, --,
+ {{0xf99970a9,0xb25520c2,0xec684065,0x23f401a0}}, // _traže_, _direttam, _patrún_, _suster_,
+ {{0x81440116,0xab399017,0x35864055,0xb94090fe}}, // _federaal, _договоре_, _raadinay, _রংধনু_,
+ {{0x82ca6214,0x7784f0a8,0x4290c051,0x5f5d301b}}, // _koodu_, _frivilli, _kadan_, _perancan,
+ {{0xe292521f,0x327ed12a,0xd2e11183,0x1ed11350}}, // _totali_, _bennie_, _anuncio_, _anuncios_,
+ {{0xd2d9e18d,0xf200c1d2,0xbefc91d5,0x5c620029}}, // _enten_, _dadin_, _agụiyi_, _jurusan_,
+ {{0x7cce12b6,0x1394e177,0x00000000,0x00000000}}, // _tyskland_, _lansio_, --, --,
+ {{0x6366d130,0x0965b07b,0xe34521c4,0xf7c710c3}}, // _ponedelj, _অদ্ভুত_, _handelt_, _машиний_,
+ {{0x107c212f,0x0087b036,0x5b1f219e,0x4da01056}}, // [860] _ملعقة_, _природат, _kemaluan_, _avancera,
+ {{0x4c0521d6,0x2085e0b5,0x058fa008,0xa27e900d}}, // _lakselv_, _komplexn, _אמצעי_, _ijana_,
+ {{0x3200c074,0x52ba2351,0xf2d9119f,0x42026293}}, // _hadin_, _ponekad_, _gizem_, _equipe_,
+ {{0x6ee410b9,0x3a941056,0x7ad7c022,0xdeda90b7}}, // _artikels_, _artikeln_, _mummunan_, _абундент,
+ {{0xa12d2036,0xbaf50090,0x42caf044,0x92266119}}, // _части_, _evropian_, _unidas_, _markan_,
+ {{0x885b3070,0x5200c05b,0xe202606e,0xdc60e0f2}}, // _ellenére_, _milioi_, _afrifa_, _puluhan_,
+ {{0x77b1f0f7,0x6ec1627b,0x00000000,0x00000000}}, // _vertrouw, _komntwan, --, --,
+ {{0x926cb0c9,0x58cdd159,0xd3432065,0xf63a3259}}, // _redova_, _adékò_, _نافذة_, _praitici,
+ {{0x854d6173,0x1291c066,0x46b2e054,0xe15401f6}}, // _візуальн, _नियोजन_, _चहेता_, _vlastito,
+ {{0x5615a094,0x6d9bd199,0x12fdb018,0x00000000}}, // _טומאה_, _fotorada, _ratings_, --,
+ {{0xadc47061,0x6200a19e,0x62d830ae,0xdbf5a008}}, // _konkursa, _habib_, _valodas_, _אובמה_,
+ {{0xa344907f,0x60ddd07c,0xb5c4d07b,0x099950a2}}, // _citeste_, _положите, _ময়মনসিংহ_, _svakodne,
+ {{0x12918189,0xa7b3611a,0xec5d913b,0x439501ae}}, // _murah_, _konstiti, _功成名就闯大都_, _clases_,
+ {{0x1387719a,0xc3ea7121,0xc386e0da,0x7ea7013b}}, // _foarte_, _kontu_, _cairan_, _掘进工作面_,
+ {{0xc7541036,0x1290a16d,0x4290b04a,0xb200b177}}, // _независи, _babae_, _macam_, _pacio_,
+ {{0xe7e4b0de,0x53207185,0xf985132a,0x5201f05f}}, // _negatiiv, _nanya_, _अभिमान_, _stuit_,
+ {{0x03207064,0x82f1e11c,0x07a8200a,0xbd7e8070}}, // [870] _ianya_, _variasi_, _approssi, _kézműves_,
+ {{0xf320704a,0x22b082c9,0x527ed1a9,0xe3ead05f}}, // _hanya_, _कानुन_, _gyonna_, _toets_,
+ {{0x23207088,0xcba4212a,0x4e4ff095,0xc29071c0}}, // _kanya_, _artikulo_, _pinakama, _kanaa_,
+ {{0xf2fce34b,0x533d0037,0xf6b950f7,0x53a2d04f}}, // _sangat_, _ایلام_, _belangri, _propos_,
+ {{0x32d9e02c,0xf3eba2f7,0x7b07d017,0xb271d01f}}, // _miten_, _sixtus_, _патријар, _jānis_,
+ {{0x22d9e081,0x96f9c043,0xeb6720d1,0xa203d195}}, // _liten_, _saibnkaw, _momentan_, _процедур_,
+ {{0x7201f009,0x0c6f70f8,0xd2e20297,0xaf64c1a3}}, // _putih_, _horrela_, _spoiled_, _negatywn,
+ {{0xe224003e,0x21f591d5,0xc387f054,0x8cbec061}}, // _leika_, _afranịọs_, _lauri_, _капец_,
+ {{0x1290b1dd,0x7a01801a,0xce20319b,0x92b7301a}}, // _bidali_, _marrasku, _آلودگی_, _другими_,
+ {{0x06377116,0xf6d8b0a4,0x37866077,0x4f3fe144}}, // _barnaami, _mahasisw, _turvalli, _nakoniec_,
+ {{0xac52815d,0x03407092,0x1449904d,0x258ec1e3}}, // _denthan_, _nedeľa_, _ドを忘れた_, _isatifik,
+ {{0x820040a5,0x4fe3211c,0xe347c011,0x8037d13b}}, // _izmir_, _رایتل_, _handlar_, _在这种情况下_,
+ {{0x8d38e106,0x534d913b,0xa2d9e0dc,0x00000000}}, // _milioane_, _中央电视台_, _diten_, --,
+ {{0x0495e11d,0x3273012f,0xd2d8a128,0xea2de0c5}}, // _potrebuj, _dúnta_, _liber_, _победите,
+ {{0x16dc2243,0x12498051,0xd3028352,0x00000000}}, // _पारित_, _girma_, _izdanju_, --,
+ {{0x425bf315,0xb20180dc,0xb2ac711a,0x4b6bf121}}, // _boule_, _arrin_, _popilè_, _justizia_,
+ {{0xf290c124,0xdb9b5068,0x4301e0da,0x00000000}}, // [880] _hadal_, _technisc, _garansi_, --,
+ {{0xc9f341f1,0x93a2c068,0x8387f159,0xa07700b0}}, // _prostred, _helpen_, _aburo_, _carcharo,
+ {{0x5151011c,0x51a100cb,0x07f29099,0xdbd2904d}}, // _اشتغال_, _اشتعال_, _prebaciv, _コレクション_,
+ {{0x748e9241,0xdfc49025,0x63eb702e,0x00000000}}, // _napravlj, _पॉजिटिव_, _platba_, --,
+ {{0x420031e3,0xc39660c5,0xd6016063,0x32903073}}, // _simila_, _varsin_, _atilanti, _tematu_,
+ {{0x529000c3,0x3290c241,0xa3f47120,0x2e70e00f}}, // _chiar_, _izlazi_, _partit_, _झुण्ड_,
+ {{0x72d9e012,0x00000000,0x00000000,0x00000000}}, // _intet_, --, --, --,
+ {{0x6248d211,0x22b65315,0x8606b2b9,0x28bbe04d}}, // _thema_, _mouche_, _sammenli, _キャラクタ_,
+ {{0x7200c0bc,0xc625d1a3,0x62900047,0x00000000}}, // _balita_, _pomorski, _thiar_, --,
+ {{0x92919035,0x724991ae,0xcc7142d5,0xd2f741a1}}, // _rusak_, _misma_, _मोर्चे_, _kalimah_,
+ {{0x3200c267,0x72d76198,0x9b69c01a,0xb200c077}}, // _kulima_, _navodno_, _договора_, _valita_,
+ {{0x825a500f,0x2343107f,0x82d8311d,0xbc6f502e}}, // _mille_, _сорока_, _člena_, _ostrava_,
+ {{0x725a5353,0x0c36f04d,0x397ec20d,0xe25ad106}}, // _lille_, _この質問の閲覧数_, _kelimele, _unele_,
+ {{0x3e56c215,0xddbd3236,0x22d9c0dc,0x74d200fe}}, // _проста_, _superfíc, _enver_, _নিরপেক্ষ_,
+ {{0x225ac0ff,0x8290b06c,0x9486023a,0x32cbf11a}}, // _okolie_, _kadara_, _प्रातः_, _soude_,
+ {{0x0200f25f,0x622000de,0x8bdf20e8,0xd4b93035}}, // _dagin_, _viljandi_, _semester, _प्रेमात_,
+ {{0x946ee117,0x0723e01c,0x94203262,0x1290a1d1}}, // [890] _время_, _kulturom_, _उपलब्_, _tabac_,
+ {{0x13ebf098,0x0c5bf03b,0x930db008,0x3df1b041}}, // _historii_, _histori_, _ויופי_, _sledovat_,
+ {{0xc57e4091,0xa200f1c0,0x3814d01f,0xe3940143}}, // _benderfy, _nagin_, _kafejnīc, _reisi_,
+ {{0x5f79f1ec,0x5b1f40ea,0xc2c9602b,0x00000000}}, // _atapịagh, _효과적으로_, _гальштук, --,
+ {{0x425ba0f5,0x047a70bb,0xd57e3259,0x36dad0b7}}, // _amply_, _pontmain_, _reiligiú, _ачестора_,
+ {{0x607c212f,0x017d90cc,0x604dc02a,0x23f400c2}}, // _الفقه_, _עיבור_, _проте_, _nostri_,
+ {{0x1ccc1037,0x3a12e1cd,0x537f8086,0xe27f7104}}, // _ایرنا_, _inscrive, _muhanga_, _nyanga_,
+ {{0x8200c088,0x7805207b,0x9824a060,0xe5a1f03b}}, // _salita_, _জালবাজ_, _fẹ̀ẹ́_, _politikë,
+ {{0x97b74098,0xe29250b2,0x1b7b319b,0x2d20c1fd}}, // _poplatek_, _vitalk_, _اتوکد_, _известно,
+ {{0x0ef150f6,0xba11f1ce,0x92d9e01a,0xf96720ca}}, // _kaмep_, _garancia_, _siten_, _prosinec_,
+ {{0x337b90c8,0x629e716e,0x48ef60ea,0xc2ba9046}}, // _skladem_, _céard_, _각종계약서_, _academi_,
+ {{0x28b6e16e,0x82122205,0x22fcc0bd,0xe91b70b2}}, // _عرايس_, _mukha_, _malgre_, _giảm_,
+ {{0x12d85276,0x3c604033,0x3348606f,0x7bf5a018}}, // _dalem_, _lettres_, _clienti_, _גוטמן_,
+ {{0xd980e119,0x93eaf02c,0x42cad03f,0x1200f106}}, // _arrimaha_, _kiitos_, _bildes_, _schimb_,
+ {{0xe2d9e354,0x8ff82020,0xa388f231,0xf695c092}}, // _antes_, _클린중개업소_, _unutrašn, _prezentá,
+ {{0x1f0d1185,0x8200d1e5,0x635f902e,0x8290d1ee}}, // _संस्कृती_, _preis_, _recepty_, _crear_,
+ {{0xc346d173,0xa290e142,0x615a9036,0xdc035271}}, // [8a0] _vandens_, _finali_, _difficil, _físico_,
+ {{0x368fe091,0x561c5076,0xf2d71086,0x226cf154}}, // _gwreiddi, _podlaski, _mirongo_, _yehova_,
+ {{0xa57e4134,0xd29711c5,0x03410035,0x3c7440a0}}, // _penderfy, _panjang_, _जाणून_, _mesurau_,
+ {{0x43f8524d,0x37f68273,0xf320f00e,0x42918057}}, // _calum_, _текстове_, _nagyo_, _izzati_,
+ {{0xe291f039,0xa1e1e0cb,0xae1200d1,0xf290a1be}}, // _mutat_, _کمزوری_, _vereinba, _dabaa_,
+ {{0x9fd30099,0x32d8c04f,0x229090b9,0xfab7b01b}}, // _postojeć, _aider_, _schatz_, _keikhlas,
+ {{0x4298012e,0x42cae063,0x7386d080,0x9386a2e9}}, // _vďaka_, _windos_, _vcera_, _bidrag_,
+ {{0x32918039,0xa394000d,0x5200b355,0x822401df}}, // _marad_, _afise_, _hacia_, _afike_,
+ {{0x1591d068,0x9c626356,0x038062e1,0x92d8d119}}, // _안녕하세요_, _escribe_, _aktris_, _sheeg_,
+ {{0xebf5a008,0x12bb90b9,0xac6122c4,0x8aad619e}}, // _עולמי_, _engelse_, _består_, _berkesan_,
+ {{0x79d22282,0xe818e045,0x41e33202,0x31efb19b}}, // _enregist, _договору_, _autoprom, _گیگابایت_,
+ {{0x22d842d7,0xa77d3065,0xbe96d19b,0x8c5bc090}}, // _gamek_, _الحزب_, _آرزوی_, _sikurse_,
+ {{0x6229102c,0xa0d7c07f,0x526e4009,0xefac11ab}}, // _показать_, _биколор_, _atribusi_, _promozio,
+ {{0xc3a0103d,0xd0f9e357,0x6239302b,0xdf2d6264}}, // _ログインして投票, _forhandl, _розум_, _através_,
+ {{0x2b824068,0x33bef231,0x22d8b057,0x7394f07a}}, // _standaar, _staviti_, _paderi_, _keisti_,
+ {{0xccc9c017,0x327f4034,0x8378d074,0x7af040ea}}, // _многим_, _utente_, _lamarin_, _구매평가를_,
+ {{0x22fce067,0xc44ac173,0x7d1e213b,0xf290f02d}}, // [8b0] _banget_, _джона_, _明星大哉问_, _gagal_,
+ {{0xe2d8509d,0xe2912051,0x2e99711a,0x439401c4}}, // _dalej_, _kayan_, _kontinye_, _meist_,
+ {{0xa3f8c1cc,0xc711b0de,0x77a37151,0xc387e128}}, // _tidur_, _जबरदस्ती_, _protecci, _patru_,
+ {{0x1ef1e0de,0x5b91e025,0x7290c1e2,0x928a40a4}}, // _विश्वामि, _zamieszc, _delako_, _पर्यटणस्,
+ {{0xf2efd044,0x53940143,0x42d88252,0xcd403065}}, // _galicia_, _neist_, _arkeen_, _انتهت_,
+ {{0x017d90cc,0x52d8c161,0x6381a07b,0xe30d207f}}, // _חילוק_, _sider_, _দাঁড়িয়, _sambata_,
+ {{0xac764358,0x9348d122,0x6290f0ef,0xbe47e252}}, // _estudos_, _katerih_, _magam_, _farabada,
+ {{0xed8df2a3,0xd2911209,0x385f20bb,0x27c961a3}}, // _odgovara, _yazan_, _missnoob_, _postanow,
+ {{0xf2ca717d,0xae96b1d4,0x02911295,0x0a861158}}, // _einde_, _bastante_, _kazao_, _adanikan_,
+ {{0x529122bd,0x02ca7146,0xa91b70b2,0x42e5c020}}, // _bayan_, _finde_, _hoàn_, _통신판매업_,
+ {{0x0310706a,0xbd96a143,0x1bdf828a,0xd3930070}}, // _hiszen_, _tallinna, _ruhenger, _سینکڑوں_,
+ {{0xac0480c4,0xc7c4b062,0xfaee112c,0xf933e19b}}, // _uhasili_, _околност, _quotidie, _زارعی_,
+ {{0xe052812b,0xa2eeb200,0x723181be,0x64b84098}}, // _poznámka_, _štimac_, _akomako_, _एभरेष्ट_,
+ {{0x841dd11c,0x4bee1100,0x3d182234,0xadd102bf}}, // _سالگی_, _каток_, _कारबाही_, _एकाएक_,
+ {{0x421340aa,0xfa4480b7,0x33a2a04d,0x86514065}}, // _şehir_, _алэтурь_, _ご利用案内_, _العثور_,
+ {{0x01682008,0x7e55d173,0x27274241,0xa1dc10fd}}, // _כתוצאה_, _думаем_, _poslodav, _клубу_,
+ {{0xb202617a,0x00000000,0x00000000,0x00000000}}, // [8c0] _aprile_, --, --, --,
+ {{0x62d98038,0xfaed00bc,0x4c09e118,0xe431f0fe}}, // _gazeti_, _carleton_, _propria_, _জড়িত_,
+ {{0x98372071,0xc9d9a1dd,0xe290c154,0x8227b061}}, // _паралел_, _esperime, _silaha_, _крайняя_,
+ {{0x1e18e03f,0xf206e08f,0x00000000,0x00000000}}, // _galerija, _クリックで拡大_, --, --,
+ {{0x929b408b,0x1c23a094,0x5289c1bd,0x82904048}}, // _izbový_, _קראון_, _سقراط_, _mimax_,
+ {{0x12918359,0x80492036,0xd21240b2,0x08c8904d}}, // _aurat_, _периода_, _bumha_, _と投票しています_,
+ {{0x92b4700c,0x126c20c5,0x26f76030,0x4291819e}}, // _bence_, _alkoi_, _दयावान_, _surau_,
+ {{0xb291c002,0x32a71222,0x825af1b1,0x431c61d7}}, // _arvan_, _cacbon_, _yogli_, _anabarr_,
+ {{0x821c00e6,0x8a142040,0x7e542036,0xb70540fe}}, // _bisher_, _finanzie, _finanzia, _অস্বীকার_,
+ {{0xa2d98041,0x8d3cd031,0xa20110ac,0xb290317f}}, // _firem_, _发表评论于_, _fazil_, _jamani_,
+ {{0xc9919091,0xd2011175,0xc25b235a,0x6e52b037}}, // _technole, _vazio_, _ciclos_, _perbanya,
+ {{0xe712f02a,0x28f291ab,0xb482f0c5,0x00000000}}, // _форумі_, _интервют, _форумы_, --,
+ {{0x3d9ca1a3,0xd26af0b7,0x78dc207c,0x00000000}}, // _kolekcje_, _апариция_, _кутии_, --,
+ {{0xc201802b,0xa291803f,0x3d5f9008,0x3468302c}}, // _kuris_, _kuras_, _המקיף_, _планируе,
+ {{0x7460720b,0x82250037,0xb20180a3,0x8ed5c082}}, // _menerusk, _anakku_, _juris_, _kopierin,
+ {{0x2290f1c5,0x02905055,0x12e31041,0x308cc100}}, // _bahasa_, _dilay_, _policie_, _слове_,
+ {{0x0361a0eb,0xc290f051,0x0dff5328,0x2aa840ea}}, // [8d0] _основу_, _mahara_, _bandhiga, _지속적으로_,
+ {{0xfc7140de,0x37a6b076,0x127f7086,0xf9e850c5}}, // _मार्गी_, _spotkani, _nyanza_, _kustannu,
+ {{0x3b09012f,0xeb0b304d,0xb2d830b5,0x63f871c0}}, // _تذكرني_, _一部例外あり_, _objem_, _tanum_,
+ {{0x130da1d6,0x47a6b175,0x7237f35b,0x4f46916a}}, // _freiburg_, _текстови_, _abuja_, _समाजशास्,
+ {{0x420191d6,0x7628b153,0x5201a025,0xc46bc179}}, // _kasie_, _navigati, _mapie_, _скидки_,
+ {{0xc35880ab,0x736ce160,0x329181c5,0x1f7b41d0}}, // _pàgina_, _margadh_, _surat_, _afẹde_,
+ {{0x170e10cc,0xd51b9008,0x527ee010,0x9291214b}}, // _אוודאי_, _ההצעה_, _mennyt_, _hayal_,
+ {{0x68464085,0xa2a6c113,0x2785f03f,0xe2246184}}, // _realizad, _coobme_, _normatīv, _bwoko_,
+ {{0x9f165025,0x0299e0c1,0x417d303b,0x92914047}}, // _शादीशुदा_, _svojím_, _parandal, _theach_,
+ {{0x3291810c,0xdf2d8076,0x90dd70cb,0xd2da406a}}, // _turas_, _serdeczn, _فیڈرل_, _motoros_,
+ {{0xb20201f5,0x6925a036,0xbe232046,0x8b33c0d1}}, // _misiri_, _главата_, _gwefanna, _gegenübe,
+ {{0xa2010038,0xbae811cc,0x794c0004,0x4e38f12f}}, // _kabisa_, _सर्वत्र_, _באזונדער_, _carachta,
+ {{0xb0f62008,0x74459041,0xfc78e19f,0x1d6a4045}}, // _הבחירות_, _गण्डकी_, _fərqli_, _показати_,
+ {{0xaec7a19b,0x7319220f,0x00000000,0x00000000}}, // _میدهند_, _dibilang_, --, --,
+ {{0xe2245087,0x51c6b008,0x02c6b134,0xa202107f}}, // _belki_, _category_, _categori_, _copiii_,
+ {{0xb2903139,0x73fe506a,0x19d94008,0x02aa10a5}}, // _zamani_, _amerikai_, _governme, _kapsamın,
+ {{0x3ec211b2,0xf3eae0da,0xc755e044,0xf200c2d7}}, // [8e0] _vientian, _lintas_, _católica_, _eclick_,
+ {{0x43eb530f,0xe59f100f,0x14017065,0x78a42018}}, // _vietas_, _करुणानिध, _المؤلف_, _אנגליה_,
+ {{0x827ec092,0x361f1251,0xde46e039,0x4200c18e}}, // _fronte_, _resoluci, _karbanta, _haligi_,
+ {{0x5d07b0cc,0xbb60104d,0xc7b9d1cc,0xcb14d004}}, // _ארטאדאקס, _ショッピングカ_, _memungki, _אָרטאָדא,
+ {{0x1f3f600f,0x6dc82030,0x92fe50e8,0x00000000}}, // _ट्विस्ट_, _सत्यदेव_, _utrikes_, --,
+ {{0x111b0020,0x4856419f,0x30b12035,0xc2d8b035}}, // _개인정보취급방침_, _əslində_, _बिरुटे_, _macet_,
+ {{0x92f3a004,0x10d33117,0x4ed65083,0xe7b4807b}}, // _הרבני_, _класс_, _dilindun, _নিষেধাজ্,
+ {{0xa3f8f20a,0xe2b201be,0x29ab5077,0x791b70b2}}, // _sigur_, _abadaba_, _остальны, _toán_,
+ {{0xbd9dc163,0xe2d8c35c,0x973890bc,0xe3fdc1a3}}, // _jednosta, _galega_, _unforgiv, _jednostk,
+ {{0xa9176031,0xb25a50dc,0xc2027124,0x9f6ad00f}}, // _地方商务之窗_, _tilla_, _nasiib_, _बिंदिया_,
+ {{0x93ba725b,0x63ea9214,0x4290c07e,0x6a19c080}}, // _privát_, _anata_, _maladi_, _matúš_,
+ {{0x126c50a4,0x898d4175,0x366ef13b,0xfd0d41e6}}, // _alloh_, _популарн, _中国经典经济现代, _популяри,
+ {{0x02919028,0x029101be,0xf201109d,0xf2b66025}}, // _pusat_, _dabara_, _razie_, _marcin_,
+ {{0xb2e852ad,0x4394f147,0x34ab2221,0x99585092}}, // _poslednj, _moises_, _keramick, _posledne,
+ {{0xe201b0f5,0x3200b10b,0x07c3c07b,0xc2003154}}, // _davita_, _akcia_, _স্মরণ_, _kamili_,
+ {{0xc358935d,0xb3f8219d,0x32900084,0x02fc935e}}, // _página_, _takut_, _skian_, _klage_,
+ {{0x63ea0142,0xd3687081,0x2ef0707b,0xfed78084}}, // [8f0] _uniti_, _наприкла, _মাহফিল_, _hakikatn,
+ {{0x4d3921a5,0x5290c091,0xce31a09d,0x395120c5}}, // _mobifone_, _gydag_, _प्रक्षेप, _automaat,
+ {{0xa25b70f1,0xd201f0b9,0xb873c175,0x8313c1cf}}, // _amalan_, _bruin_, _procurar_, _procura_,
+ {{0xf2cae070,0x5f0ef0b2,0x02249154,0xa3ebf17d}}, // _mondja_, _nhếch_, _kwako_, _foute_,
+ {{0x8db4311c,0x5c530116,0xf65a8070,0x7edc21aa}}, // _کوروش_, _kastaba_, _اہلسنت_, _албански_,
+ {{0x0d4390cc,0x8dfcc045,0x121a91a3,0x12e772c3}}, // _סטעיט_, _учитель_, _एकदिवसीय_, _bernama_,
+ {{0x33a9214e,0x43a2c037,0xc3f8a35f,0xfee1019b}}, // _maaaring_, _campur_, _qabul_, _جداگانه_,
+ {{0xab0172bb,0x620090bb,0xc5831017,0x0291f090}}, // _posición_, _khaim_, _москви_, _gruan_,
+ {{0x9201e0bd,0xe2d8b0c3,0x339501fe,0xa312816a}}, // _natif_, _facem_, _časem_, _křišťálo,
+ {{0xf3f9117b,0xe5a3a16c,0xc34b606a,0x5a14e267}}, // _zaburi_, _елементе_, _médiaajá, _ababuuza_,
+ {{0xd7b611da,0x726ca360,0x4ceec017,0x00000000}}, // _apprendi, _ambos_, _америку_, --,
+ {{0x0526e015,0x9ca3e004,0x8681a0d5,0x12f05154}}, // _carrinho_, _ארגינעלע_, _जयराम_, _bilioni_,
+ {{0x7cc511a2,0xc2fce19d,0xa2c9c18e,0x2163804d}}, // _اریکسون_, _tengah_, _hotlink_, _円以上ご注文頂き,
+ {{0x2290e0f1,0xb27ef040,0x92f041da,0x4290c074}}, // _hanafi_, _bringt_, _delitti_, _filato_,
+ {{0x8c7cb010,0x527f4355,0x62d82018,0x03eb90c2}}, // _tekstin_, _fuente_, _takes_, _costo_,
+ {{0x9c9631bb,0x92d9c225,0x75f3a14d,0xf7ef51d3}}, // _заняла_, _livet_, _довести_, _चहलकदमी_,
+ {{0x62b352c0,0x9b32e04d,0xc0c1d0c5,0x420271ae}}, // [900] _cumarsái, _ジの先頭へ_, _основе_, _varias_,
+ {{0xee0ba008,0x9637f006,0x3847f1a3,0x934d2122}}, // _שליטה_, _reklamow, _reklamod, _katerem_,
+ {{0xf200c1da,0xf224717f,0x43eb91af,0x227e6068}}, // _validi_, _benki_, _gosto_, _avond_,
+ {{0xbfeba008,0x72646006,0x148502e0,0xb852413b}}, // _בהמשך_, _anuluj_, _स्नातक_, _中文科技期刊数据,
+ {{0xee91604f,0x82b31133,0x8b8a21af,0x272e1076}}, // _histoire_, _meadhan_, _мојот_, _zapamięt,
+ {{0x584d2173,0xe3add190,0x8319c047,0x6cfd20a7}}, // _sistemos_, _tāpat_, _إيران_, _sistemov_,
+ {{0x12f0c17f,0xd39490d3,0xffeda0ca,0x127f4346}}, // _hadithi_, _measa_, _ग्राफिक_, _siendo_,
+ {{0x23eb5061,0xc3949047,0x6300931d,0xb25a5044}}, // _vietos_, _leasa_, _separuh_, _fillo_,
+ {{0xf460f039,0x627f7184,0x60afe0fd,0x0af17020}}, // _berendez, _afande_, _контракт_, _opdracht,
+ {{0x56aee04d,0x8dc380cc,0x63f470f3,0x03eb00ed}}, // _ジのトップへ_, _מארטש_, _curtir_, _diatas_,
+ {{0x6307b25d,0x6ad7300f,0xfaf5718e,0x1d60113b}}, // _komandi_, _postitat, _moroccan_, _澄城县人民政府网,
+ {{0x225ad249,0x53f84100,0xc8de825a,0x12d84018}}, // _fyller_, _namus_, _विस्फोटक_, _names_,
+ {{0xe212b0d1,0x10f3b004,0x62fd208d,0xe8563118}}, // _suche_, _באזוך_, _ujohane_, _загуби_,
+ {{0xe3eb80d7,0x33793101,0xe847a065,0xcbdd634d}}, // _morts_, _labarin_, _اللواء_, _धनिया_,
+ {{0x122b8355,0x487d804e,0x727ff1cc,0x1058112a}}, // _siempre_, _комемора, _ujung_, _escalato,
+ {{0x3faa407b,0x02c660bb,0xe387f06c,0x73eb8120}}, // _পড়াশোনা_, _kasmoos_, _aburu_, _forts_,
+ {{0x427ef1b0,0xe2e09117,0x65e95082,0xade740ea}}, // [910] _opinii_, _profiili_, _зростанн, _부동산써브의_,
+ {{0x820180fc,0x02247126,0xc4ce0175,0xbf0b90a2}}, // _saria_, _senki_, _криминал_, _premijer_,
+ {{0xe20181d0,0x22d9025b,0xc273a084,0x0fbc724b}}, // _larin_, _videó_, _peribadi_, _englesko,
+ {{0x06368361,0x06c73020,0xa2d41004,0xb2504067}}, // _vikipedi, _고객님께서_, _אנטקעגן_, _पायेंग_,
+ {{0x16368361,0x87e620ca,0xb83f609a,0x135540a4}}, // _wikipedi, _अवार्ड_, _después_, _disebut_,
+ {{0x12009014,0x412a61fd,0x2afa5188,0xa475310f}}, // _phais_, _пропусти, _branchen_, _disertak,
+ {{0xa465126c,0x47a7304f,0x1e5361cd,0x03788051}}, // _integraz, _partenai, _inscrire_, _kananan_,
+ {{0x13f8418f,0x13a8e283,0xd2d84294,0xb3dc1157}}, // _jamus_, _talagang_, _james_, _plhws_,
+ {{0xe2fca056,0xd9aac045,0xe2d95006,0x00000000}}, // _skrivet_, _годин_, _okolicy_, --,
+ {{0xb7834228,0x7e76f16a,0x42918040,0xc2489007}}, // _धारावाहि, _klikněte_, _daran_, _abamo_,
+ {{0xd25a524c,0xae19e261,0xc84c11cd,0x83877116}}, // _cille_, _selengka, _certains_, _sharaf_,
+ {{0xf380600f,0x4e76607b,0x33ea90c3,0x72a05128}}, // _korral_, _ময়মনসিং, _piata_, _fotbal_,
+ {{0x13f820a7,0xf2caa002,0x02018205,0x97b4c31d}}, // _nakup_, _विचारणीय_, _narin_, _menerang,
+ {{0x320e10b7,0xfcc08117,0x00000000,0x00000000}}, // _натал_, _одноклас, --, --,
+ {{0xa201808c,0x225a5133,0xbc1d20a5,0xad0721ea}}, // _harin_, _gille_, _kitaplar, _neprimer,
+ {{0x6c34f0c3,0x42018074,0x8a535045,0x55a7119b}}, // _descoper, _barin_, _сертифік, _شکنجه_,
+ {{0xc291906e,0x32918138,0x73870056,0xb2d890a0}}, // [920] _lasan_, _caran_, _klarar_, _sbaen_,
+ {{0x92b650bd,0x038aa07b,0x6ece913b,0xa2ca5082}}, // _touche_, _আক্রান্ত_, _上海联放贸易有限, _bilde_,
+ {{0xf615a004,0xee29b04d,0x43f9e1e2,0x32ca0269}}, // _מוצאי_, _ジャンルランキン, _ditut_, _liidu_,
+ {{0xb290c230,0x820181d2,0x92dab024,0x31eb104d}}, // _nalazi_, _farin_, _fotolar_, _マタニティ_,
+ {{0xb2019175,0x59d0a0b9,0x92018362,0x72918252}}, // _assim_, _advertee, _garin_, _garan_,
+ {{0xb9d2217e,0x22926209,0xf25a50dc,0x82d85081}}, // _inregist, _burada_, _tille_, _talet_,
+ {{0xc807a153,0x22027202,0x8291f12b,0xb27e907f}}, // _מתאים_, _varios_, _zuzana_, _avand_,
+ {{0x8a80d2f2,0xf290d24d,0x92918074,0x8d86b120}}, // _tehotens, _shean_, _yaran_, _entendre_,
+ {{0x2c4a2025,0x6e521057,0x59e852c1,0xbde8e0ea}}, // _योजनाओं_, _dinafika, _postignu, _커뮤니케이션_,
+ {{0xc5d3d130,0x8242e09d,0x6db6c025,0x6291813e}}, // _елемента_, _चावला_, _रामशलाका_, _varan_,
+ {{0x531731df,0x0f54013b,0x00000000,0x00000000}}, // _kancane_, _建设项目环境影响, --, --,
+ {{0xc291f0c5,0xf6dd715c,0x12ba70ef,0x8348b0fe}}, // _ostaa_, _नीतिश_, _mondani_, _যানবাহন_,
+ {{0x191320ed,0xa3f8805d,0xd5901154,0xc3f9e221}}, // _pembangu, _yakuna_, _kupunguz, _titul_,
+ {{0x63833068,0xa200e05f,0x7200119d,0x8c85b004}}, // _자유게시판_, _geniet_, _akhir_, _מוזיי_,
+ {{0xf8af41bd,0x23ea71e3,0xb291a057,0xd201a010}}, // _استطاعت_, _zinto_, _lapan_, _lapin_,
+ {{0x2201814e,0x0291818c,0x93957241,0x5340e0e6}}, // _parin_, _paran_, _opasno_, _gelesen_,
+ {{0xb29051c5,0xe25a51b8,0xc2025039,0x6cb3717f}}, // [930] _iklan_, _sille_, _attila_, _jumatatu_,
+ {{0xb2b50071,0x4b2570f8,0x72e590d4,0xb50c728c}}, // _produse_, _uniberts, _teknika_, _febreiro_,
+ {{0xa52bb05a,0xde950008,0x02a2a12f,0xc4550248}}, // _munyakaz, _products_, _بمنطقة_, _producto_,
+ {{0xa291a035,0x3c554060,0xc201a18e,0x498b5220}}, // _kapan_, _ṣugbọn_, _kapin_, _preču_,
+ {{0x99e212cc,0x13f400f8,0x7bdfc0fd,0x927ee20e}}, // _कट्टा_, _laster_, _modeller, _venner_,
+ {{0xfb14c118,0x426c5126,0xe4354055,0x620df0c3}}, // _избор_, _dolog_, _hambalyo_, _апринса_,
+ {{0x0378a102,0x8394f2e2,0x4ef8e017,0x72fcd0b2}}, // _makanan_, _priser_, _министар, _ringme_,
+ {{0x926c22c6,0x227fb1aa,0xd2eff0b3,0xc2247053}}, // _tokom_, _оставили_, _nedostaj, _denkt_,
+ {{0x72919101,0xeea210bc,0x03f85185,0x42d9c0c3}}, // _wasan_, _assurant_, _jalur_, _анений_,
+ {{0x93f8c0a9,0x0a6f0098,0xf429713b,0x4ecda143}}, // _usluga_, _véčka_, _国家烟草专卖局_, _komisjon,
+ {{0xb2ca71be,0x32483109,0x637f601a,0xaae7c056}}, // _windo_, _kommet_, _избранно, _styrelse,
+ {{0x1d5d9010,0x32480264,0xac6200d8,0xa6f2717a}}, // _популярн, _acima_, _turunan_, _интериор,
+ {{0x9200c2a3,0x9c0c80ea,0x26a5d21f,0x09ca313b}}, // _molimo_, _무이자할부_, _konformi, _国家环境保护总局_,
+ {{0xedc3a0cc,0x40f3a004,0x00000000,0x00000000}}, // _דריקט_, _דאטום_, --, --,
+ {{0xf2b4d149,0x131831f5,0x8741013b,0x82fcd128}}, // _trochu_, _paschal_, _政治交接学习教育, _alege_,
+ {{0xf386e0d3,0x8300f057,0xcb8b8272,0x73967045}}, // _foirne_, _risalah_, _familjar, _visste_,
+ {{0xd3a2c0ed,0x2af76122,0x5d8df02b,0x2347c1ab}}, // [940] _hampir_, _zasebnos, _autobusa, _allerta_,
+ {{0xeda8d185,0x3c5b8099,0x00000000,0x00000000}}, // _aktivita, _sastoji_, --, --,
+ {{0xf3949008,0x5b1db0ea,0xe20de071,0x825b413f}}, // _least_, _세계적으로_, _апринс_, _dielac_,
+ {{0x2d748194,0x52009024,0xa20180a0,0x46de10ea}}, // _авторизу, _sakini_, _wario_, _워디안이나_,
+ {{0x152e0008,0x9290d0e8,0xc200b038,0xee4c31e6}}, // _לונדון_, _senare_, _madini_, _списки_,
+ {{0x13a2405d,0x357391bb,0x42fa303c,0x13423036}}, // _kumpi_, _педагога, _kuchnia_, _accesso_,
+ {{0x7d2c3036,0xd386006a,0xdcdf9070,0x147720ca}}, // _списък_, _amire_, _negatív_, _अग्नी_,
+ {{0x02ca704e,0x5ddc2070,0x43ea7090,0x4f4170ef}}, // _vinde_, _حلقوں_, _vinte_, _المحجوب_,
+ {{0xa3940254,0xc2ece276,0x3b60f033,0x262eb203}}, // _ceist_, _alkitab_, _permettr, _операция,
+ {{0x5fed2031,0x92c8a19a,0x63b4b1a3,0x016920a0}}, // _百度知道投诉吧_, _aproape_, _atrakcyj, _bresenno,
+ {{0xd26c30a2,0x04c071ef,0xba207324,0xa3eb50ae}}, // _kojoj_, _públicos_, _públicas_, _lietas_,
+ {{0xf291a009,0xb5960020,0x4c1d205a,0x929191c0}}, // _papan_, _클릭하세요_, _intambar, _kasal_,
+ {{0x1fc381cd,0x026e111a,0x23a26185,0x7f653363}}, // _modifier_, _repons_, _eropa_, _रामचन्द्,
+ {{0x33f882b8,0x67134182,0x6292405e,0x6e3cd037}}, // _nakuba_, _сексуалн, _kawawa_, _اشتون_,
+ {{0x82d87122,0xed1c81c0,0x53f982d7,0xf2df1059}}, // _danes_, _follicle_, _virut_, _whining_,
+ {{0xe25a910c,0x4708a008,0x2291f2b0,0x15a72270}}, // _ciall_, _ומולטימד, _rutas_, _झपकते_,
+ {{0x325a5056,0xb2918252,0xa344b06f,0xf2018034}}, // [950] _lilla_, _garab_, _firenze_, _varie_,
+ {{0xb1ed0364,0x4200b365,0xbb88e1c4,0x02f3f178}}, // _धरहरा_, _ledige_, _aktivier, _dedicat_,
+ {{0x92d8c116,0x4201a1c0,0xd25a50e8,0xa71cc12d}}, // _kalena_, _sapin_, _gilla_, _पारदर्शी_,
+ {{0xf9fb10f6,0x327ef211,0xa2d430f5,0x9213f063}}, // _digitala_, _seiner_, _tailieu_, _afuha_,
+ {{0x0200c27b,0x12919050,0xedb8a088,0x00000000}}, // _malini_, _casal_, _pakinaba, --,
+ {{0x401d10bd,0x82918039,0x927f5013,0xa968d018}}, // _kondisyo, _darab_, _brengt_, _estimate,
+ {{0xe486f1f2,0xc148834f,0x6b98d041,0xd0e3b13c}}, // _भिखारी_, _टाइपराइट, _प्युठान_, _протесту_,
+ {{0x44399175,0x73076009,0xd2fc1153,0x5ca781b8}}, // _програма, _lambang_, _actually_, _редакция_,
+ {{0xf611c126,0xc683c00f,0x07b01008,0x11dd3098}}, // _probléma_, _रूमाल_, _המלחמה_, _aplikací_,
+ {{0x1394d078,0xe16920a0,0x92e95189,0x77c8c135}}, // _geesi_, _presenno, _artinya_, _poisteni,
+ {{0x4361418a,0xf6b9a1ea,0x4e4a5366,0x00000000}}, // _lingvoj_, _nekateri, _netačne_, --,
+ {{0x224bd186,0xca168149,0x1224f11a,0x18d9e0c5}}, // _जिलेबी_, _osobnost, _efikas_, _сервис_,
+ {{0x8d42d194,0xd3a69044,0x4290d1d7,0x62b0b008}}, // _інформац, _máxima_, _sheas_, _ואירועים_,
+ {{0x18d2c171,0x125a50d4,0xcdee50bc,0x327ee056}}, // _sobreviv, _billi_, _bridgewa, _kunnat_,
+ {{0x327f4171,0xf2d912c1,0xa683a025,0xc3ebf01a}}, // _frente_, _kazem_, _रैदास_, _nouto_,
+ {{0xf0ddf224,0x9290516d,0x727eb033,0x7aa7c070}}, // _branitel, _aklat_, _agence_, _لوڈشیڈنگ_,
+ {{0x0477a050,0x51b3c0cb,0xd355001e,0xbe73f08f}}, // [960] _проекти_, _وائرس_, _recetas_, _ルアドレスを入力,
+ {{0xcaaa40c5,0x326c210f,0xe8fb81dd,0xe80c7135}}, // _компании_, _pokok_, _hamarkad, _pozitívn,
+ {{0xb20061e4,0x72926087,0xf290e14e,0x8290b1c0}}, // _chois_, _buraya_, _pinaka_, _madali_,
+ {{0xf26c1077,0xa29260d7,0x34a8a068,0x00000000}}, // _johon_, _agrada_, _maximaal_, --,
+ {{0xbd9d9167,0x13e8302b,0x0b37f265,0x43874151}}, // _महत्त्वप, _голубева_, _बर्बाद_, _acerca_,
+ {{0xb3ead30f,0x02da527b,0x227ed1d7,0x8be74143}}, // _vieta_, _intela_, _tional_, _kategoor,
+ {{0x634f7014,0x884150bb,0x98c55035,0x6cbc3107}}, // _heheheh_, _callixtu, _जवळपास_, _опасен_,
+ {{0x9b65b07f,0xf977b00a,0x4845b0db,0xfc560002}}, // _utilizar, _indirett, _utilizad, _कइलें_,
+ {{0xe6818076,0x139501a4,0xe35850f5,0x084e5065}}, // _करनाल_, _vlasti_, _webgame_, _لأجهزة_,
+ {{0x1c6060bd,0x95dbc020,0x22a7f08d,0x7e29505e}}, // _distans_, _마지막으로_, _ububi_, _manggaga,
+ {{0xfee6e12a,0x582f5332,0x6290c0cf,0xb291b2cd}}, // _machinin, _skomentu, _aydan_, _maqam_,
+ {{0xc527e0c9,0x329262c1,0x585dd143,0x00000000}}, // _potrudio_, _zgrada_, _teenistu, --,
+ {{0x312d217e,0x6900b054,0x00000000,0x00000000}}, // _парте_, _अतिरिक्त, --, --,
+ {{0xc2da519e,0xd291d0f1,0x5395021f,0xd2ca50ec}}, // _isteri_, _kawan_, _klassi_, _pildi_,
+ {{0x1ecda0aa,0xdf6e1094,0x02f71098,0x252d0036}}, // _gerektiğ, _יוליוס_, _novinek_, _composto_,
+ {{0x32ca50e9,0x42d800d1,0xd2019037,0x1d7a718d}}, // _vildi_, _spiel_, _arsip_, _kommende_,
+ {{0x23ea7010,0x0b11a12f,0x14858367,0x00000000}}, // [970] _hinta_, _cásanna_, _विनाशक_, --,
+ {{0xdaba5179,0xbdc39261,0xc3a8a037,0x8d11802a}}, // _компаний_, _terimaka, _sakadang_, _юстиції_,
+ {{0x95e0d10b,0x24d01008,0xd30432b1,0x1feee0cb}}, // _literatú, _במקביל_, _porazil_, _بسلسلہ_,
+ {{0x43f9903f,0xf3a1311c,0x73ea704a,0x338712f4}}, // _visus_, _منحصر_, _minta_, _dobrou_,
+ {{0xc224f2cb,0xcebac07c,0x9eb6c179,0x2e398069}}, // _funkar_, _топло_, _профиля_, _vivncaus_,
+ {{0x23f461cd,0xe291e074,0xb33d7070,0x0201e1cb}}, // _partie_, _matan_, _دیہات_, _matin_,
+ {{0x52ff50dc,0xc2786304,0x722e00b5,0x956de034}}, // _arritur_, _chunki_, _pravidel, _часовете_,
+ {{0xbafd90b2,0xf07ec036,0x326c8224,0x3efe80e6}}, // _nghếch_, _правилно_, _tokova_, _privatsp,
+ {{0x62ca7015,0xc9f660cb,0xd68e70be,0x13fc603d}}, // _ainda_, _során_, _proffesi, _リクエスチョン_,
+ {{0xe3f1503a,0x82b4e063,0x7b815089,0x3e152002}}, // _associaz, _adicha_, _associac, _kuressaa,
+ {{0x52d8a20d,0x82da51df,0x5200c18f,0x53eb00f9}}, // _haber_, _esteri_, _dalili_, _pratik_,
+ {{0xd14a7040,0xa29b0105,0x13f9a120,0x32b66091}}, // _kostenlo, _ekspertl, _tipus_, _parcio_,
+ {{0xfabdf064,0xf3ead03f,0x12cad1ea,0x356010ea}}, // _pinjaman_, _lieto_, _spodaj_, _사람입니다_,
+ {{0x0c06e0ff,0xdefdd036,0x90705007,0xc20091b1}}, // _potreby_, _политиче, _metalelo, _khais_,
+ {{0x32cbf2de,0x537380b3,0xd290e267,0x79dfe054}}, // _soudu_, _izdanje_, _kanani_, _struktuu,
+ {{0x726f108d,0xda32612d,0x7fa4c070,0x7cf2c06a}}, // _eziningi_, _फर्जी_, _ٹوئٹر_, _فاطمہ_,
+ {{0xce14a12e,0x842c5255,0x1201e14e,0xd41c51a3}}, // [980] _zamestna, _technick, _natin_, _technicz,
+ {{0x02ca7086,0x92ebd160,0xd200318f,0x0c0440ac}}, // _bindi_, _muintir_, _namiji_, _prosesi_,
+ {{0xf2d99225,0x2f27712f,0xb26d8122,0x8dfbe0f5}}, // _viser_, _لانجري_, _evrov_, _nukeviet_,
+ {{0x5201e237,0x9766a0ea,0xf89aa0cb,0x79e820b5}}, // _batin_, _마찬가지로_, _سرفراز_, _dobrodru,
+ {{0x756912ae,0xf26c711e,0x6a0a00f6,0xfc37107b}}, // _настройк, _honom_, _estatist, _সংঘটিত_,
+ {{0xf34441be,0x00000000,0x00000000,0x00000000}}, // _dabeere_, --, --, --,
+ {{0xd2d9a033,0x98dbf128,0x427f7038,0xf50a924a}}, // _favoris_, _dimensiu, _chanzo_, _sigurado_,
+ {{0x7291e074,0xa58830ff,0xb25a813f,0xa2016068}}, // _fatan_, _popradsk, _daklak_, _begint_,
+ {{0x2758d0ca,0xec051070,0x82ca705a,0x00000000}}, // _मुस्ताङ_, _گناہوں_, _zindi_, --,
+ {{0xd26e1089,0x2394d0f7,0x0c751368,0xc1ca511c}}, // _suport_, _meest_, _रात्रि_, _مراقبت_,
+ {{0xa962d031,0x42e8e056,0xd2b37237,0x5650b031}}, // _商务部网站版权与, _polisen_, _perdana_, _胶南市教育体育局_,
+ {{0xa2d830dc,0xcc6a3080,0x9ae5607b,0xf3f47173}}, // _koment_, _odvtedy_, _শ্রাবণ_, _turtas_,
+ {{0x9202505d,0x83eae0c2,0xd290e075,0xd291e147}}, // _mutima_, _contro_, _gyfan_, _katao_,
+ {{0x32d8b341,0x83031133,0xc9e2f1d8,0x5754604d}}, // _facer_, _malairt_, _portefeu, _最新から表示_,
+ {{0x0d8680d7,0x527332bc,0x8291e074,0xa2d6300f}}, // _novembre_, _många_, _watan_, _selleks_,
+ {{0x020061cb,0x33f4602c,0x0d2e90fe,0x996610da}}, // _choix_, _kertoa_, _সারাদিন_, _नविनतम_,
+ {{0x7611c0b5,0xd40531f7,0x9394e164,0xa22750a2}}, // [990] _problémy_, _आनंदित_, _avisen_, _građansk,
+ {{0xb2ca50f8,0x616e307b,0xcee4d036,0xfee040de}}, // _bildu_, _গাইবান্ধ, _чувства_, _चित्रकला,
+ {{0xf386d063,0xfa45c0b4,0x826c2074,0x937570fe}}, // _emere_, _finestra_, _rokon_, _ফায়ারফক্,
+ {{0x737301cd,0x213da017,0xfe6841af,0xb291d14e}}, // _semaine_, _спортист, _дискусиј, _kawal_,
+ {{0xecdf3031,0x62fc9072,0xdbf0e1a3,0x195c81b8}}, // _欢迎批评指正_, _slags_, _internec, _списку_,
+ {{0x73ead191,0xc2927276,0xc342b186,0x99cd1192}}, // _tieto_, _busana_, _भुजंग_, _myndighe,
+ {{0xe49cc100,0xe20200a4,0xb2927038,0xe91320ed}}, // _плане_, _disini_, _miradi_, _membangu,
+ {{0xc8b8907f,0xbc7f413b,0x437f805a,0x00000000}}, // _кэутаре_, _返回黑龙江主站_, _muhanda_, --,
+ {{0x91c3310b,0x9bea4063,0xae627063,0x925ad2ac}}, // _autorské_, _achọrọ_, _agakọta_, _ciele_,
+ {{0xf3eae18c,0x2eb4d122,0x72ebd190,0x4b242124}}, // _kontan_, _samodejn, _bizness_, _munaasib,
+ {{0x3da3c061,0x32d8c120,0x02026174,0x6637219a}}, // _nemokama, _dades_, _arriba_, _татиана_,
+ {{0x1ed101c9,0x0378e14e,0xbf240193,0x7265309c}}, // _reunidos_, _malaman_, _višak_, _yerusaal,
+ {{0xac7581f7,0xb25a91b1,0x03eae21c,0xa1c1731a}}, // _सदस्या_, _ciali_, _lontan_, _terendah_,
+ {{0xd3a8207f,0xd603919f,0x15953217,0xd60190b5}}, // _privind_, _bələdiyy, _klassisk, _cyklisti,
+ {{0x3d04c19b,0x42d99175,0x9c0880e7,0x00000000}}, // _توجهی_, _esses_, _традициј, --,
+ {{0xf6b36215,0x825ad0c4,0xe1bc80fe,0xb2d8e246}}, // _pristaty, _biele_, _মেঘের_, _manema_,
+ {{0x16af302b,0xab9280aa,0x363800fe,0x28bfe1ae}}, // [9a0] _рсфср_, _ingilizc, _এস্কিমো_, _enfermed,
+ {{0xb27e0153,0x91cae021,0xf3ead144,0xc201e19e}}, // _being_, _parašė_, _viete_, _katil_,
+ {{0x83f4512a,0x66831369,0x024df36a,0x03f472da}}, // _rattle_, _ब्याज_, _मनाला_, _kartus_,
+ {{0x6f5940e9,0x82ea7024,0x00000000,0x00000000}}, // _hlýtur_, _putinin_, --, --,
+ {{0x3f6b2130,0x4efb2034,0xd291e18e,0xd2025034}}, // _стране_, _страни_, _matam_, _attivo_,
+ {{0xa47fe1d5,0xebdc70ea,0x3ddee04d,0xa77bb118}}, // _ajụkarị_, _한국과학기술연구, _昭和つっても幅広, _стоката_,
+ {{0x0249910a,0xff3f9037,0xfad59128,0xd332f089}}, // _rasmi_, _pengaran, _cantitat, _baixos_,
+ {{0x12e16076,0xe26c10b2,0x6200b1be,0xd355d325}}, // _दसवीं_, _sohot_, _midian_, _confira_,
+ {{0xeda811dd,0xaef7f1ab,0x67f82047,0x72e8e147}}, // _donostia, _фирмата_, _لبرنامج_, _maliban_,
+ {{0xc2c40084,0x02ca70f6,0x64a5f018,0x0c0bb010}}, // _alawiyah_, _findu_, _clinical_, _egyptin_,
+ {{0x52ca01d2,0x430f6119,0x0291d0ed,0x19f6b221}}, // _shida_, _berbera_, _jawab_, _ustanove,
+ {{0xc48510e1,0xb295c0d4,0xe2a6d36b,0x0202600c}}, // _विधायक_, _dipartim, _osobno_, _harita_,
+ {{0x32920051,0x927860ac,0xec9142cc,0x3c76c32f}}, // _misali_, _olundu_, _श्लोक_, _मरम्मत_,
+ {{0x12d4510b,0xeff1111e,0x3c1cc105,0x648140c6}}, // _ľudia_, _miljoner_, _bazarlar, _भाषाको_,
+ {{0x72925175,0x5a00e064,0xd20271a4,0x0f172348}}, // _estava_, _kitabnya_, _desilo_, _ciudadan,
+ {{0x9fc0d061,0xffdd3065,0x82b4912f,0x725ad0c3}}, // _literatū, _العبد_, _dtaca_, _piele_,
+ {{0xddcb3037,0x00000000,0x00000000,0x00000000}}, // [9b0] _اثبات_, --, --, --,
+ {{0x427e01d9,0x90b52030,0xc6b2c070,0xdd87a2b1}}, // _meine_, _दुगुना_, _تمہاری_, _objednat_,
+ {{0xd290818a,0xa4a900b7,0x12fcf217,0x22d9e27c}}, // _tukang_, _протекци, _logga_, _vitet_,
+ {{0x0bf0d098,0xae72f143,0x9f4511d9,0x00000000}}, // _mistrovs, _शुद्र_, _sonstige_, --,
+ {{0x0201619d,0x026ca134,0x4351e16c,0x4ac1009d}}, // _begitu_, _pobol_, _амендаме, _wciąż_,
+ {{0x6f240193,0x9255c1f3,0x03ea7267,0x12012111}}, // _nešto_, _विशेषः_, _bintu_, _kabiny_,
+ {{0xfcc89060,0x12f2f0ac,0x365131cc,0x3dfe6176}}, // _mìíràn_, _radiosu_, _गोंधळ_, _हार्डवेय,
+ {{0x92369122,0x42d980a4,0xa3f9e173,0x87aba1dd}}, // _imajo_, _maret_, _kitus_, _populazi,
+ {{0x8ed9f061,0x3b11414c,0x00000000,0x00000000}}, // _aplinkos_, _पाखण्ड_, --, --,
+ {{0xe4dac0c0,0x42d8c17d,0x0c574099,0x526ca2c1}}, // _можна_, _vader_, _nestaje_, _tobom_,
+ {{0x726e0278,0x7201403b,0xa98b40a2,0xb41cc241}}, // _lepota_, _mediat_, _treće_, _pregazio_,
+ {{0xd792807c,0xb3eb80ca,0xb3188088,0xa3d4d14b}}, // _интересу, _dorty_, _ganitong_, _kararı_,
+ {{0x438602c0,0xcaeeb0e8,0x43451033,0x821ba0b5}}, // _imirt_, _fastighe, _américai, _virtuáln,
+ {{0xce0cc03d,0x3c528141,0x107d1175,0x12f3a1a7}}, // _ドを入力してくだ, _bantuan_, _природен_, _beginnt_,
+ {{0x7b9e40f9,0x27b8416a,0xc575335c,0xf1b5e050}}, // _prezidan_, _položek_, _contexto_, _стандард,
+ {{0x227e00d1,0x82c74037,0xe2d8c05e,0x32578039}}, // _keine_, _ادارات_, _pader_, _nálam_,
+ {{0xd3c7210b,0x438661dd,0x134bc087,0x5344a127}}, // [9c0] _hospodár, _umore_, _birkaç_, _ourense_,
+ {{0xf201c0b4,0x2758e025,0xc2ea4170,0x7f2642d9}}, // _havia_, _मुद्दों_, _marián_, _समाजमा_,
+ {{0x137a8091,0x429302f9,0x026c530c,0x12489276}}, // _allanol_, _sporočil_, _ellos_, _upami_,
+ {{0x74695082,0xfb8231da,0xf047202c,0x322bb1be}}, // _християн, _umanitar, _часовой_, _akamere_,
+ {{0x12db40a2,0x42352165,0x426ca241,0x00000000}}, // _kasnije_, _evidenti_, _sobom_, --,
+ {{0x0200c091,0xf80da2ad,0xb25b936c,0x0c3f203e}}, // _miliwn_, _командов, _misle_, _stephens,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf291f089,0xadf69082,0x9aa590ca,0x5321f177}}, // _estan_, _ресурсів_, _विक्रेता_, _estyn_,
+ {{0x23f9e0dc,0x0fb61170,0x42d9805f,0xa440f039}}, // _ditur_, _zosilňov, _darem_, _الحرمین_,
+ {{0x337c9088,0xe2911194,0xac1730cd,0xe24af106}}, // _detalye_, _debatt_, _इंस्टीट्, _асигурар,
+ {{0x5696500c,0x1c5a6165,0x651351ab,0xce35d03c}}, // _adresini, _evitati_, _енергийн, _opracowa,
+ {{0x874c01b8,0x4316d36d,0xc9f20124,0x00000000}}, // _растения_, _trebali_, _culimada_, --,
+ {{0xa386d061,0x123750d4,0x3a9660de,0x7201121b}}, // _sporto_, _plejer_, _उम्मेद_, _kabili_,
+ {{0xba83b0f7,0x62c0e1e8,0x674ed06f,0xf2d861b1}}, // _beginnen_, _poštovan, _счетовод, _nplej_,
+ {{0x23f9e0d6,0xe2d9e114,0xbf8752a3,0x03f12065}}, // _situs_, _sites_, _mašine_, _فضفضة_,
+ {{0x5bc27020,0x020dc017,0x2c7f0122,0x945e1018}}, // _크게보기를_, _спомен_, _varstvo_, _נוסעים_,
+ {{0x0c2a602e,0x12fc61aa,0x72b850f8,0xa6838180}}, // [9d0] _vlastnos, _ulogu_, _handiak_, _वनवास_,
+ {{0x5290d259,0x28392036,0x2225805f,0x333f9018}}, // _theas_, _последна_, _werke_, _המסחר_,
+ {{0x7718501a,0x63f8f02d,0x5205a00a,0x42bca0f9}}, // _контакты_, _bagus_, _marittim, _mondyal_,
+ {{0xe3f0200a,0x425ad144,0x626c705d,0x63f9919e}}, // _informaz, _biela_, _nonon_, _kasut_,
+ {{0x943ed0ea,0x5d82a1aa,0x5290c205,0x29416236}}, // _광주광역시_, _najčešće_, _kulang_, _estudiar_,
+ {{0x7c01c267,0xc3ea507e,0x13f9e003,0x00000000}}, // _vengador, _kilti_, _situr_, --,
+ {{0x21c632ca,0xe290c1c0,0x42d8f03d,0xf3a3f2b7}}, // _onemscom, _dulang_, _tages_, _mfupi_,
+ {{0x227e6003,0x12d9f144,0xad9ac118,0x2f2fb0bb}}, // _svona_, _kvality_, _статии_, _nestoria,
+ {{0xc48072be,0x62d980de,0x02cae0b9,0x4e0cb0c5}}, // _बहराइच_, _varem_, _londen_, _laadukka,
+ {{0xa291f241,0x926c4035,0xb3f980e6,0x837880a4}}, // _ostao_, _nomor_, _warum_, _tanaman_,
+ {{0x7e71336e,0xb2efb277,0x37540302,0x3c6b81dd}}, // _चित्र_, _þeirra_, _पारिवारि, _elementu_,
+ {{0xf255436f,0x826cc241,0x4c55a328,0x4200c147}}, // _विधेयक_, _vodom_, _darteed_, _huling_,
+ {{0xbe72f12d,0x96374054,0xa3738144,0x3e3d20fe}}, // _शीघ्र_, _politsei, _dodanie_, _দুইটা_,
+ {{0x1317001c,0x5b0301ea,0x123c2068,0x324c202c}}, // _stazom_, _priprava_, _normaal_, _normaali_,
+ {{0x7f2521cb,0xf34351f9,0xe906e0a8,0x00000000}}, // _utiliser_, _diseño_, _forbered, --,
+ {{0xc394904b,0xf3f460c3,0x3e2da004,0xd6a3012f}}, // _yeast_, _martie_, _השקפה_, _aistrith,
+ {{0x12fd0171,0xc3940038,0xb2cae117,0xa967a018}}, // [9e0] _nocivas_, _ofisi_, _joiden_, _הגדלת_,
+ {{0xc2fc6242,0xa394811c,0xcdf98265,0x4203b084}}, // _blogs_, _langkung_, _आत्मकथा_, _nauzubil,
+ {{0x29ec90d4,0x83ac70b2,0xa291f246,0xc5f2c025}}, // _attivita_, _cmspro_, _bazara_, _चट्ठा_,
+ {{0x6d5ac175,0xa3ebf058,0x4aeb9077,0x2c969025}}, // _совет_, _foutu_, _kokonaan_, _जगहों_,
+ {{0xa3192088,0xc2cae0ea,0xd7def263,0x00000000}}, // _kabilang_, _honden_, _गायकवाड_, --,
+ {{0x1303c0e6,0x62efd14f,0xeb5970d7,0x00000000}}, // _bekannt_, _kulinda_, _muntanya_, --,
+ {{0xe212b0bd,0x05bd4216,0x02d98143,0xb91e110b}}, // _mache_, _meridiem_, _parem_, _trenčín_,
+ {{0xc84d20b5,0x71c4813b,0xf3087164,0x22ca5051}}, // _naposled, _人在草木中_, _klubben_, _wallahi_,
+ {{0x8200d18a,0x4d35d021,0x94996119,0x7e186165}}, // _kuning_, _славян_, _islamark, _irregola,
+ {{0xeb72d034,0x520c82c0,0x2b74629f,0x4378818a}}, // _стана_, _dóigh_, _esquerda_, _sanajan_,
+ {{0xd3a2404b,0x93044122,0x43053149,0x587ba018}}, // _kampo_, _pozabil_, _obrazem_, _הלבשה_,
+ {{0x52e920f8,0xc10281b9,0x25982039,0x7aa41049}}, // _hainbat_, _प्रश्_, _impressz, _जुर्माना_,
+ {{0xf26c4360,0xa290c0a4,0x3cee21b8,0x00000000}}, // _somos_, _pulang_, _груди_, --,
+ {{0xa4ba0020,0x1ff100cd,0x4979f0e8,0xaf6d50ce}}, // _중국국제전화카드_, _तेलंगाना_, _gymnasie, _insbeson,
+ {{0x84fa9162,0xc20080a6,0x8365b025,0xa2490206}}, // _गंगाधर_, _arhiiv_, _मुरैना_, _dhambi_,
+ {{0x59977349,0x702e20c2,0xec932039,0x43e2b24e}}, // _प्रशिक्ष, _condizio, _تاوان_, _enerjisi_,
+ {{0x31ab7036,0x4bcb7036,0x6212b058,0x12d9e040}}, // [9f0] _commenti_, _commento_, _fache_, _datei_,
+ {{0x0a074179,0xa8078173,0xc212d0c4,0x00000000}}, // _tilantee, _круглых_, _brehu_, --,
+ {{0x2291800c,0x729270cb,0xf3a2d0b6,0x4f9e11ab}}, // _biraz_, _اشتیاق_, _drept_, _родители,
+ {{0x475a3033,0xab87d370,0xe96731b2,0x025ac12a}}, // _非煤矿生产许可证_, _ingilter, _immitate, _bellow_,
+ {{0x9301206f,0x4c7d80a7,0x3c4b10d1,0xb2e3b034}}, // _страната_, _prosimo_, _eintrag_, _regione_,
+ {{0x39e321f5,0x12d83052,0x5a26d0cb,0x00000000}}, // _minisita_, _tumelo_, _شاہراہ_, --,
+ {{0x626c51c0,0x2ba7904e,0x4986107a,0x56d1d12d}}, // _kolor_, _електора, _paskelbt, _अंकित_,
+ {{0xff6bc036,0xe17d9008,0x89ded32a,0x7090b0c5}}, // _историче, _בישול_, _न्यूजलेट, _netistä_,
+ {{0x463a301a,0xb22491c0,0x63eae19d,0xf26c5174}}, // _промышле, _atake_, _cantik_, _dolor_,
+ {{0x92ca717b,0xa340407c,0x8ae61138,0x00000000}}, // _kindi_, _offerta_, _acetonae, --,
+ {{0x53ead0ae,0x6a04b1ac,0x00000000,0x00000000}}, // _lietu_, _megabait, --, --,
+ {{0xf2d8c1d6,0x634c7191,0x1177508d,0xd212b0e1}}, // _illene_, _povedal_, _zilandel, _ruchu_,
+ {{0x53320089,0xa5a3e040,0x6a10307f,0x52d9f1cf}}, // _baixa_, _versandk, _consiliu, _estes_,
+ {{0xf291f0f1,0x937110fe,0x784d80ea,0xdeb63045}}, // _skuad_, _চুয়াডাঙ্, _들었습니다_, _системі_,
+ {{0x01f7c037,0x226c7134,0xe2b40086,0x2c62d19f}}, // _معارف_, _annog_, _igice_, _əfsanə_,
+ {{0xc26c528c,0x22fc602e,0x00000000,0x00000000}}, // _polos_, _blogy_, --, --,
+ {{0x63f81024,0x8d37c037,0x78fd313b,0xba1a5174}}, // [a00] _uchun_, _معاصر_, _您所在的位置_, _descuent,
+ {{0x49a96082,0xd394d0a0,0x8528402a,0xf4730205}}, // _професій, _broses_, _футбольн, _listahan_,
+ {{0xb1579194,0x569761d9,0xc389b068,0xe6d2b0de}}, // _рамках_, _eigentli, _vergelij, _नाहिए_,
+ {{0xa9c530a4,0x00000000,0x00000000,0x00000000}}, // _kesempat, --, --, --,
+ {{0xb529e18c,0xc23d50ea,0x6629e09f,0x027f0269}}, // _navigasy, _필요합니다_, _navigasi, _omanik_,
+ {{0x827ef06e,0x52259121,0x00000000,0x00000000}}, // _aginju_, _neska_, --, --,
+ {{0x9f4eb120,0x2354a0aa,0x82d9f0aa,0xf9b61144}}, // _respecte_, _hareket_, _ister_, _predplat,
+ {{0xe98b5047,0x93871016,0xb793713b,0x22927154}}, // _modhanna_, _librat_, _国家旅游局_, _jirani_,
+ {{0x0d7860b2,0xf4789035,0x90b591d3,0x9dc4e179}}, // _giăng_, _सानिकास्, _सतगुरु_, _matkusta,
+ {{0xc9e0a020,0x83d86176,0x450140ef,0xeab6f0ea}}, // _청소년보호정책_, _सेलिब्रे, _billenty, _올려주세요_,
+ {{0x538691be,0x129ad22e,0x0c738037,0x12a69119}}, // _amara_, _faajtim_, _raksasa_, _amaba_,
+ {{0xf3a7201a,0xec672117,0x00000000,0x00000000}}, // _другой_, _другое_, --, --,
+ {{0xde3d9008,0x7ef510c4,0x02fcc18b,0x9212701a}}, // _משפחת_, _telefónn, _blogga_, _vanha_,
+ {{0xdd116020,0xb37f800d,0x8f965222,0x00000000}}, // _아파트분양권_, _mahanga_, _quần_, --,
+ {{0x47572182,0xf387f128,0x937541ab,0x00000000}}, // _метри_, _scurt_, _appuntam, --,
+ {{0x6e7350e1,0x036da1a4,0x6db7a065,0x8074403f}}, // _रुद्र_, _potencij, _اليهود_, _kalendār,
+ {{0x3efc6030,0x9342013f,0x9710d0ea,0x06f780df}}, // [a10] _नजाकत_, _doremon_, _고객센터의_, _नौजवान_,
+ {{0xf3ead30f,0xa2bf2037,0x43030047,0xaeb64198}}, // _vietu_, _سمنان_, _raidió_, _gradonač,
+ {{0xfbc4e117,0xf316900d,0xb2259098,0xd3869074}}, // _очень_, _amazi_, _deska_, _amari_,
+ {{0xe3ea01be,0xdc46d11c,0xa2ca0002,0x861a3047}}, // _saiti_, _مردمی_, _saidi_, _ترانيم_,
+ {{0x08b4c0ea,0xbcf2c065,0x00000000,0x00000000}}, // _사진갤러리_, _ناعمه_, --, --,
+ {{0x6f1e100c,0xa20220a7,0x2c1d2105,0xfd76319b}}, // _kendisin, _izboljša, _tatarlar, _ترینر_,
+ {{0x4f6d4220,0x3237e1df,0x1f618014,0x12f55008}}, // _pasaules_, _ngomhla_, _xeebxeeb_, _maximum_,
+ {{0x74fa5010,0x2aa2a0b2,0x6192a371,0x92911252}}, // _программ, _techcomb, _autonómi, _sabahi_,
+ {{0x130de163,0xe2d94116,0xfe15a033,0x00000000}}, // _osobama_, _sheegi_, _premiers_, --,
+ {{0x998e11c9,0x224a60a8,0xe2c5c145,0x30c3906a}}, // _начин_, _dermed_, _primjer_, _بالمقابل_,
+ {{0xa4e201f9,0x923e925b,0x99250033,0x9a350033}}, // _মোছাঃ_, _termék_, _caractèr, _caractér,
+ {{0xe3ebe023,0x6290c070,0x8557a004,0x5cc91194}}, // _mitte_, _valami_, _מתפלל_, _трансляц,
+ {{0xc0e42008,0x2200d064,0x1ee60018,0x6eb51056}}, // _מאיימות_, _hanief_, _חיפשתם_, _konstigt_,
+ {{0x72da61dd,0x306262db,0x2f72c045,0x53f8916f}}, // _direla_, _बतासे_, _доброго_, _pljuje_,
+ {{0xf3eb9011,0x5c913025,0x792120fe,0x00000000}}, // _sista_, _मैचों_, _চট্রগ্রা, --,
+ {{0x627e9104,0xb3a240a4,0x187a7050,0x229fe1cf}}, // _zwane_, _sampe_, _затворен, _projeto_,
+ {{0xa273a1ef,0x281fa0ea,0xf79100ea,0x81d05055}}, // [a20] _aínda_, _광화문연가_, _테마감상평_, _sheegnay_,
+ {{0x92d911a3,0x227e6033,0x9987704d,0xcfe3a1e6}}, // _reklamy_, _avons_, _厚生労働省_, _неможлив,
+ {{0x23940112,0x49ebd2d3,0x548180da,0x00000000}}, // _crise_, _undersøg, _प्राजु_, --,
+ {{0x8fe0007b,0xe1ffe0ed,0x4c75e2f4,0x72e77070}}, // _প্রমাণ_, _menambah_, _formátu_, _کالعدم_,
+ {{0x7b06e0cb,0x4290c1df,0x00000000,0x00000000}}, // _fórumban_, _uhlala_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x52bb1025,0x9a1f113b,0xec67a147,0xcd12301a}}, // _kolejne_, _热门关键字_, _maaring_, _добрый_,
+ {{0x3aa2702b,0x750a6045,0x3f30d221,0xa27e90da}}, // _priklaus, _програмн, _satelitn, _teknis_,
+ {{0x4a841004,0x2ad172c1,0x338741ab,0x00000000}}, // _ארגינעל_, _normalan_, _aperto_, --,
+ {{0xb290c147,0x22d9b1e8,0x02c1e078,0x00000000}}, // _malaki_, _saveza_, _akilapa_, --,
+ {{0xc20422a7,0x2efd71ed,0x119720b5,0xd3f460b9}}, // _serikali_, _धडाका_, _koncerty_, _watter_,
+ {{0x6ed5d30f,0x09f59184,0x5ee27143,0x37b5c071}}, // _papildin, _kaminuza_, _रिक्शावा, _адеся_,
+ {{0x253462ec,0x23ea203b,0xa26ca11c,0x32cf10bd}}, // _različit, _fakti_, _bobot_, _denonse_,
+ {{0xf2b40138,0x12ca003f,0x00000000,0x00000000}}, // _trice_, _naidu_, --, --,
+ {{0x03ea00fb,0xf9cfb15e,0x9cd9e02f,0xb7b5f1e2}}, // _iaitu_, _tirdznie, _героях_, _ibarretx,
+ {{0xc75de00d,0xa7b420cc,0xaa1fa0c5,0xeaeeb11e}}, // _bizimung, _נאכנישט_, _основные_, _hastighe,
+ {{0x627e71bd,0x9dfa804d,0x72da50a4,0xf57f3065}}, // [a30] _lenne_, _このレビュ_, _materi_, _أصبحت_,
+ {{0x83ced335,0xaf34618e,0x92f49099,0x9342f098}}, // _clovek_, _adironda, _nepovolj, _kulturní_,
+ {{0x927ed104,0x32fc0054,0x00000000,0x00000000}}, // _edene_, _liige_, --, --,
+ {{0x0b5a812f,0x6379a185,0xd25a5056,0x2c77d0d4}}, // _مُساهمات, _layanan_, _tills_, _entrati_,
+ {{0x325a91e4,0xf224912a,0xe200c041,0x4224d09f}}, // _chala_, _ataki_, _velice_, _reeks_,
+ {{0xfc624044,0x52fd7248,0xb2026037,0x9c6270ad}}, // _aparece_, _imagen_, _carita_, _tutulan_,
+ {{0xc27e9033,0x622550e9,0x52da511d,0x327860e8}}, // _avant_, _frekar_, _kateri_, _barnen_,
+ {{0xe27e7309,0x9611f0f7,0x9fd5d09d,0xcde5804d}}, // _denne_, _kwalitei, _prezyden, _指定しない_,
+ {{0x7c67c039,0x32fe6061,0xcb03e1c4,0xee5262a7}}, // _دربار_, _visiems_, _versende, _madaraka,
+ {{0xaf01b241,0x795fe0fa,0x65b1504d,0x72d9913b}}, // _novorođe, _atendere, _に関する質問_, _assez_,
+ {{0x7daf60f9,0x727ef086,0x23ea2003,0x9290c119}}, // _ansiklop, _iminsi_, _vakti_, _filayo_,
+ {{0x8248d007,0x29d9b0d5,0x02926074,0x537a00ac}}, // _abemi_, _सातवाहन_, _karasa_, _haradan_,
+ {{0x200a8033,0x62fe720f,0x227e7045,0x10d99010}}, // _回复此发言_, _target_, _henne_, _заболева,
+ {{0xf2369295,0xaa73d221,0x2852033a,0x3237e142}}, // _imaju_, _lokalita_, _segundos_, _animata_,
+ {{0xce38301b,0xae430088,0x00000000,0x00000000}}, // _pengajia, _kamataya, --, --,
+ {{0xd394d13e,0xbec44086,0x6af090b5,0xdc68419b}}, // _kanske_, _banyarwa, _minulost, _سودمند_,
+ {{0x03ea00ed,0x951cf045,0xf032f0b5,0xf1b311bd}}, // [a40] _yaitu_, _доступни, _postaven, _ماورا_,
+ {{0x6394d313,0x13dd8208,0x4290c06a,0xd2fd822b}}, // _danske_, _borwa_, _valaki_, _borga_,
+ {{0x3f6b109a,0x63210271,0x9f9fd178,0x7f01d04d}}, // _usuarios_, _década_, _qualsevo, _スピリチュアル_,
+ {{0x8e1c9086,0xaeba6044,0xf7b5d163,0xa212e1d0}}, // _interaha, _petróleo_, _šestoric, _afihan_,
+ {{0x8291f15d,0x225a017f,0x954b7039,0x327e9074}}, // _thuan_, _asili_, _könnyű_, _kwana_,
+ {{0xeba8f025,0x721c5052,0x16359372,0xe3ea702d}}, // _स्कूलों_, _okuhle_, _económic, _pintu_,
+ {{0xf2ca7068,0x3f966008,0x92fc90a2,0x82b4d1a7}}, // _vindt_, _חובבניות_, _snage_, _manche_,
+ {{0x15322002,0x22d8d0b9,0x3ffc6049,0xf485d0d5}}, // _नब्बे_, _speel_, _अधिकारों_, _रिटायर_,
+ {{0xf1013017,0x570ae132,0x13ac5099,0x2e7250cd}}, // _свете_, _وزيراعظم_, _ukupne_, _लिफ्ट_,
+ {{0x934271b2,0x163e3080,0xb20251dd,0xe2224035}}, // _lateran_, _štiavnic, _betiko_, _नावनोंदण,
+ {{0x43a230ed,0xa2cae008,0xa1c7319b,0x714e1034}}, // _tempat_, _london_, _جاستین_, _protezio,
+ {{0x1ef1213c,0x7147d045,0x32d890c4,0x0f612130}}, // _остали_, _написав_, _pokecu_, _остале_,
+ {{0xa27e900d,0xdc1e7036,0x1ae381af,0x03eae00b}}, // _bwana_, _развитие_, _интензив, _nonton_,
+ {{0x5290c203,0xd2b280fb,0x81a472be,0xc349e0c5}}, // _milano_, _panduan_, _नागालैंड_, _parempi_,
+ {{0x59d22221,0xed3ab07b,0x904990cc,0x336f60c9}}, // _zaregist, _হেফাজতে_, _אַלײן_, _tehnički,
+ {{0xf355b173,0xf2498056,0xad2f004d,0x33f400ef}}, // _moterys_, _varmt_, _回答順に表示_, _postai_,
+ {{0xbe1a7373,0x7205b184,0xe27f70a0,0x7065b082}}, // [a50] _materija, _politiki_, _sianel_, _politikk_,
+ {{0xae181031,0x48b9a036,0xf2e90076,0x427fa0a2}}, // _凡本站及其子站注, _последни_, _posiada_, _javnog_,
+ {{0x3b00512f,0x3dd03034,0x68090098,0x00000000}}, // _للإيجار_, _остров_, _आम्दानी_, --,
+ {{0x23f981c5,0xdd1b41a3,0x326471cd,0x7236425f}}, // _harus_, _katowice_, _parler_, _semja_,
+ {{0xca8b30c8,0x1b1fb19e,0xf6d0f0ca,0x8c6f00d4}}, // _sociální_, _cadangan_, _आंशिक_, _approva_,
+ {{0x1d928328,0x825ad1b0,0x00000000,0x00000000}}, // _puntland_, _mieli_, --, --,
+ {{0x995f1008,0xe137d170,0x2e10e045,0x00000000}}, // _resource, _obidvoch_, _adressea, --,
+ {{0x73eb9049,0x627b712f,0x8a0400d3,0xca1ed13b}}, // _posty_, _ginearál, _cumasait, _亿元人民币_,
+ {{0x12d9e0f8,0x77e5b0d5,0x124971e2,0xa170d04d}}, // _batek_, _अतिरेक_, _eraman_, _ビジネスラボラト,
+ {{0xb3720008,0xfb7a22b4,0x22497003,0x388410ea}}, // _related_, _presenza_, _framan_, _회원님들께서는_,
+ {{0x03eb924c,0xf3f9e020,0xa21291c5,0x52249078}}, // _aiste_, _플래닛으로_, _usaha_, _atako_,
+ {{0x23eb9230,0xd12d20c5,0xdb1fc19d,0x3352513b}}, // _biste_, _часто_, _kalangan_, _知识堂首页_,
+ {{0x62027187,0x5fe7b175,0xcb83d0fc,0xcd9f7108}}, // _musika_, _правите_, _europako_, _demokraa,
+ {{0x0e36c12e,0x1202605d,0x9f0d214c,0x227292b7}}, // _spracova, _yeriko_, _संस्कारी_, _mafisadi_,
+ {{0x6945a04f,0xc0dc0004,0x951e1004,0xf24c0018}}, // _utilisat, _ספרדים_, _עוועניו_, _ספרדית_,
+ {{0xf3ea20ed,0x4efcc025,0x15742018,0x931141cb}}, // _waktu_, _बैठकर_, _האגודה_, _piscine_,
+ {{0x1d03f04d,0x2660f231,0x5970f0cb,0x570220ea}}, // [a60] _リスト作成者_, _službeni, _دیوبند_, _허브차입니다_,
+ {{0x6a07000d,0xfc67c0b9,0xbf07a017,0x5c46c065}}, // _ambasade, _kantoor_, _провери_, _وربما_,
+ {{0xbeaf9037,0x53f9920f,0xb2ca71ff,0xab1fc057}}, // _gelomban, _kasus_, _sinds_, _halangan_,
+ {{0xc26cc115,0x67db712f,0xa1f8c047,0xc394d32e}}, // _todos_, _كروشيه_, _وقالت_, _efesu_,
+ {{0x8e5360ed,0x6201718f,0xab810122,0x00000000}}, // _mencinta, _shaida_, _zanimivo_, --,
+ {{0x1b09324a,0x72365003,0xd719e10b,0xd75da004}}, // _kompyute, _selja_, _komuniká, _רבונו_,
+ {{0x325ab0be,0x22ec232b,0x073bc1ea,0x00000000}}, // _ledled_, _परवीन_, _podobneg, --,
+ {{0xa5bd2037,0xa9eb915d,0xa69b20b7,0x73154045}}, // _ditambah, _skygarde, _акциунь_, _включенн,
+ {{0xbca42118,0xd2011018,0xa23651ea,0x72f4c128}}, // _английск, _social_, _velja_, _posibil_,
+ {{0x204fd1c9,0x1769a094,0x02e000e6,0x3ffed0ea}}, // _односно_, _רחמנא_, _stunden_, _프로그래밍_,
+ {{0x33eb9195,0xd755d0de,0xa2927100,0x5c29627c}}, // _siste_, _positiiv, _vasara_, _kombetar,
+ {{0x4a0a10f6,0x92d99153,0x326ce021,0x27241018}}, // _arrakast, _cases_, _šioje_, _סטודנט_,
+ {{0x22925099,0x9c612160,0x5c25f040,0x027861ea}}, // _ostaci_, _feirste_, _verbrauc, _zurnal_,
+ {{0x73ea02c0,0x627c70b9,0x0af61056,0xb8c61072}}, // _maith_, _verskill, _leverans, _leverand,
+ {{0xc2b1d036,0x17e7e099,0xdaa4b328,0xaa812006}}, // _vendita_, _bogatstv, _ingiriis, _natomias,
+ {{0x13f9a091,0xe3cef06a,0xb46ed017,0xa23b706a}}, // _hapus_, _privit_, _храму_, _komment_,
+ {{0x82d8b148,0xb40d103d,0x126e50f6,0x438771a7}}, // [a70] _andere_, _最近のトラックバ, _aktore_, _sparen_,
+ {{0xd312112c,0x42921035,0xcfdd1061,0x952e903b}}, // _membres_, _sepatu_, _medicino, _princesh,
+ {{0xf25a50d4,0x00000000,0x00000000,0x00000000}}, // _talli_, --, --, --,
+ {{0x92b58134,0x9ecb80f5,0x13e78126,0xd292531e}}, // _merch_, _thoitran, _látom_, _avtale_,
+ {{0xec61600e,0x798bf0eb,0xaf63d03f,0xde8490b7}}, // _kutunga_, _страну_, _vakances_, _алиятул_,
+ {{0xdd84a146,0x71b5b191,0x126c51a1,0x9344a305}}, // _oprettet_, _najčítan, _dilog_, _oprette_,
+ {{0x7c00b0a9,0xf8d281bb,0x4eded1bb,0x425ae013}}, // _razgovar, _добрых_, _dienomis_, _veilig_,
+ {{0xcb465079,0x929200f9,0xf156303c,0xe036c302}}, // _विद्यापी, _repare_, _aktualno, _गवाही_,
+ {{0xb43f2020,0x62b52013,0xa292018c,0x9eb6c0c2}}, // _부산광역시_, _succes_, _separe_, _профила_,
+ {{0x3be741da,0x6074a107,0x13ebe2cb,0x620ec017}}, // _direttor, _англичан, _titta_, _талас_,
+ {{0xe49b203c,0x70ca407b,0x9340e254,0x72927037}}, // _ogranicz, _মৌলভীবাজ, _ghleann_, _pusaka_,
+ {{0xf0fc9035,0xa25a6070,0x00000000,0x00000000}}, // _सुचवा_, _zsolt_, --, --,
+ {{0xc932a050,0x7feb5196,0x73ead214,0x52e641dd}}, // _реализир, _नामांकन_, _cheta_, _prentsa_,
+ {{0x426d2051,0xb19c2039,0x9291f22e,0x9c99c128}}, // _goyon_, _الملک_, _phuas_, _interviu_,
+ {{0x0d62c126,0x18dcd175,0x83eb527c,0x36b730a8}}, // _ردعمل_, _купив_, _vjeter_, _behøver_,
+ {{0xab0f4015,0x452870bc,0x8e70a118,0x328ae025}}, // _resposta_, _aplikasy, _статията_, _कॉलोनी_,
+ {{0xa3860015,0xcd8a0045,0x827f0084,0x32f450dc}}, // [a80] _feira_, _allerede_, _amanah_, _pranuar_,
+ {{0xe36c80c3,0x1c5740de,0xd3eb8003,0x73a2e046}}, // _imagini_, _lastele_, _birta_, _teipio_,
+ {{0x427f0047,0x93eae010,0x4f8c61a6,0x528bd100}}, // _leanas_, _eniten_, _तस्कर_, _pakankam,
+ {{0xc7a2719e,0xb2b471a1,0x0200c122,0xf7a93258}}, // _pentadbi, _punca_, _obliki_, _appropri,
+ {{0xa2b58033,0xe3f8206f,0xa941f03b,0xc2258003}}, // _merci_, _comune_, _realitet, _merki_,
+ {{0x70e48166,0xa2b4e219,0xd28d632e,0xc27e00b9}}, // _स्वीडन_, _plochy_, _mabhuku_, _klink_,
+ {{0xb46ed06f,0x5683f0cd,0x26aeb031,0xa88e2017}}, // _време_, _प्याज_, _民族自治地方的自, _холандск,
+ {{0x32d8c119,0x5bb75070,0xf777023c,0x81e3712b}}, // _galeen_, _مداخلت_, _सत्संग_, _parfémy_,
+ {{0x8feff25a,0x43bb21bd,0x12c78080,0x00000000}}, // _व्यापार_, _اسقاط_, _pádom_, --,
+ {{0xc3167273,0x5386905a,0xdfa4503d,0x41473045}}, // _senza_, _imari_, _accepter_, _галина_,
+ {{0x2b9e5249,0xfe1f804d,0xa2c1610b,0x3212b0f9}}, // _fortsätt, _してください_, _kombinác, _rache_,
+ {{0xb2fd60e9,0x59b74064,0x82130304,0x4212b374}}, // _liggur_, _berdekat, _shahri_, _sache_,
+ {{0x85b9e203,0xecc9b0e0,0x8698f04d,0xc2d8b0b3}}, // _заболява, _hauvcaug_, _メルちゃん_, _budemo_,
+ {{0x3394927b,0x0316732e,0x22b5503f,0x98c75040}}, // _ngase_, _wenza_, _preces_, _angemeld,
+ {{0x3202004e,0x12920246,0xa7b630ed,0x00000000}}, // _masini_, _masani_, _keterang, --,
+ {{0x1097d16c,0xebca9215,0x7c6730d8,0x6f3310fe}}, // _транси_, _светлым_, _ضربات_, _ষড়যন্ত্,
+ {{0xd2ce30c5,0x03ea7078,0xc255f143,0x63eb900a}}, // [a90] _нормальн, _ranti_, _होखेला_, _jista_,
+ {{0xc2c3108c,0xf2c8b170,0x72027133,0x62c7513b}}, // _kwallon_, _narocne_, _heriot_, _任何单位和个人不,
+ {{0x0316001f,0xc22590da,0x00000000,0x00000000}}, // _reizi_, _meski_, --, --,
+ {{0x7e14a1fc,0xb0df9208,0xf93700fe,0xd7e721d3}}, // _telekana, _teicneòl, _ডুপ্লিকে, _एसआरके_,
+ {{0x625a909c,0x7340a0ea,0x828e115b,0x00000000}}, // _baali_, _bekeken_, _miskien_, --,
+ {{0x1eb6c1bc,0xdc754342,0xfc52e265,0xc3ac0299}}, // _профил_, _तृप्ति_, _लादेन_, _toppen_,
+ {{0xfcc7725b,0x596b903d,0xd7dbd21e,0x2248b01a}}, // _hosszú_, _関連法人等_, _oglašava, _olemme_,
+ {{0x2f96c100,0x1478227b,0x8bee102a,0xe3dcd16c}}, // _рабства_, _isifundo_, _нашої_, _ачестор_,
+ {{0xf394e013,0x044300a4,0x392cf32b,0x00000000}}, // _mensen_, _प्रयत्ना, _परिजन_, --,
+ {{0xadbb4286,0x00000000,0x00000000,0x00000000}}, // _poznania, --, --, --,
+ {{0x53ead020,0x0200c061,0x2202720d,0x325ad181}}, // _niets_, _dydis_, _tarifi_, _niels_,
+ {{0x6201a1e7,0x338690c3,0x72a630c2,0x5eac2225}}, // _revize_, _seara_, _sembra_, _eksterne_,
+ {{0x0e33a375,0x93869201,0x7af002f2,0xf5a830f8}}, // _גרופע_, _learn_, _prirodze, _klaserak,
+ {{0x86aa8020,0x3af7d04e,0xa9c511c0,0x52ca9055}}, // _최근검색매물_, _ражения_, _vallarta_, _caadi_,
+ {{0x62cad068,0x1f23910c,0x002e22ad,0x00000000}}, // _biedt_, _achterca, _важности_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x5bd9c045,0xac69606a,0x62a6400c,0x00000000}}, // [aa0] _реклами_, _mintegy_, _pembe_, --,
+ {{0xb27e906e,0x04071035,0x03f45053,0x9ce3b13b}}, // _ilana_, _तांदूळ_, _zetten_, _国家海洋局_,
+ {{0xf2d9c089,0x525ad0ae,0xe25a9322,0x404fd173}}, // _haver_, _skolas_, _mbale_, _адносна_,
+ {{0xe94840a4,0x9b348031,0xa3ea914b,0x72feb056}}, // _वेबसमूह_, _评价已经被关闭_, _saati_, _butiker_,
+ {{0x96ec7117,0x73ea9159,0xc2d9c017,0x034200aa}}, // _абсолютн, _paati_, _savet_, _nereden_,
+ {{0x02f1a07b,0xb6b3a045,0x99e6724a,0xf962c0ea}}, // _অবরোধ_, _бензинов, _craftsme, _같은지역출신_,
+ {{0x82927133,0x37ebe349,0xe1fe2376,0xf5a6302e}}, // _obrach_, _महाकाली_, _realizác, _plastick,
+ {{0x3c6100f9,0x7555f00c,0x503be069,0x7ac720eb}}, // _otorite_, _cumhuriy, _povthawj_, _zahvalje,
+ {{0x19f871a6,0x616751bd,0x4224e07a,0x52d98120}}, // _recenzí_, _ناپسند_, _veikla_, _obres_,
+ {{0x220261e7,0x18acf036,0x00000000,0x00000000}}, // _verite_, _доставчи, --, --,
+ {{0xd26cc0ff,0x8c3fc108,0x12d8d113,0x00000000}}, // _bodov_, _चम्पारण_, _tineeg_, --,
+ {{0xf7a5f092,0xc32c816c,0xee361281,0x52cf0076}}, // _objektov, _валуриле_, _kongsika, _kolorze_,
+ {{0x326dc156,0xfc66c199,0xb25a003e,0x027e6018}}, // _novog_, _protams_, _spila_, _along_,
+ {{0xf292527c,0xcdd7d175,0x907d2293,0xb2b120cd}}, // _fetare_, _превоз_, _барса_, _काबुल_,
+ {{0xbd05a047,0x3e3a105e,0xedf9c02c,0x69f0a078}}, // _suntasac, _karanasa, _многое_, _akintola_,
+ {{0x3b9e316f,0xc2d4605b,0xf316618f,0x00000000}}, // _podnosio_, _zeuden_, _asibiti_, --,
+ {{0xb5ae61bc,0x109b404d,0x3d9b21b2,0x00000000}}, // [ab0] _дозволен, _オリジナル_, _phonsava, --,
+ {{0x33ebe1c4,0x691a0036,0xd908d19b,0x1028e12a}}, // _bitte_, _последно_, _تغذيه_, _nandrolo,
+ {{0xf94cf062,0xa1b3c07b,0x726cb2b7,0x03f47190}}, // _принципу_, _একসেস_, _dodoma_, _kartes_,
+ {{0x429272f9,0x97baf0a9,0xc4fb2108,0xb4a90035}}, // _zaradi_, _nekretni, _गिरामी_, _nasional_,
+ {{0x693d312f,0xa4001037,0x3b6620c4,0x83ebe020}}, // _الآخر_, _gabungan_, _spotreba_, _witte_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x4295c1b2,0x12f450a3,0x9bffa037,0x5264611a}}, // _majkauv_, _stundas_, _pengacar, _berlen_,
+ {{0xe2fc7093,0x9c7f0068,0x280cc293,0x4cbec02b}}, // _dinge_, _meestal_, _млеко_, _манер_,
+ {{0x72d462a2,0x134921e8,0x00000000,0x00000000}}, // _verden_, _dovesti_, --, --,
+ {{0x39207030,0xfc74b1bf,0x5163e0aa,0x018b3031}}, // _शिवजी_, _forumit_, _programl, _日时政要闻_,
+ {{0x23a232c3,0xbc8100a7,0xb529d293,0x34c970fe}}, // _simpan_, _storitev_, _реакции_, _ফিডব্যাক_,
+ {{0xb9fda1e8,0xcd3da1e8,0x00000000,0x00000000}}, // _jedinica_, _jedinice_, --, --,
+ {{0x72e5e037,0xfba961ea,0x00000000,0x00000000}}, // _تبصره_, _psihiatr, --, --,
+ {{0x327f405d,0xc9ff411c,0x0f4c0004,0x00000000}}, // _esente_, _دستبند_, _אפשטאם_, --,
+ {{0x62f45035,0x799b8086,0x7d86704e,0x73567071}}, // _standar_, _shakisha_, _necesare_, _necesar_,
+ {{0x2d32712b,0xfe92302a,0x9a77c0a4,0x8c874217}}, // _pronájem_, _порталу_, _भन्नाट_, _varukorg_,
+ {{0x9f21a004,0xb7c3316a,0x43ebe045,0xa637c02a}}, // [ac0] _קדושת_, _नजोडिएको_, _sitte_, _файлів_,
+ {{0x739400a0,0x2b96015d,0x000d316f,0x23eae192}}, // _prism_, _quangcao_, _nerazjaš, _syntes_,
+ {{0xb3a230ed,0xe25b7275,0x2400f048,0x22d9e008}}, // _sampai_, _soalan_, _nguội_, _later_,
+ {{0x026cc0bd,0x42926057,0xc2d9e1dc,0x82fc6377}}, // _vodou_, _diraja_, _fater_, _onoga_,
+ {{0x73f470e6,0x82279004,0xb651503e,0xc60321d3}}, // _besten_, _יארהונדע, _skrifað_, _आबिदा_,
+ {{0x263fe0ca,0xdd6e9215,0x00000000,0x00000000}}, // _kalendář_, _асфальту_, --, --,
+ {{0xa78770de,0xbaea7020,0xd7aa7020,0xf31671e3}}, // _तस्वीर_, _bestelle, _bestelli, _benze_,
+ {{0xe2f0b018,0xc75cf173,0x00000000,0x00000000}}, // _טריילרים_, _разведку_, --, --,
+ {{0xda58910b,0x7e1a701b,0xf3489041,0xed98910b}}, // _recenzia_, _pengelua, _recenzi_, _recenzie_,
+ {{0xd2d891f6,0x02d8b0c3,0x2d230071,0x23091268}}, // _dijete_, _acces_, _calitate_, _carbono_,
+ {{0x226d102e,0x239d10a7,0xe415507b,0xb37f3154}}, // _pozor_, _nadaljnj, _উদাহরণ_, _kamanda_,
+ {{0x80fe126b,0xe200d136,0x7bba7078,0xb3003100}}, // _אוגוסט_, _veliku_, _abawọn_, _kadangi_,
+ {{0xcc5d206b,0x9c1e2378,0xf3eae13f,0xb353423a}}, // _परदेस_, _compras_, _dantri_, _बहुचर्चि,
+ {{0x0e82113b,0x61c9217a,0x133cc017,0xbaa7416a}}, // _图等稿件均为转载, _кости_, _близу_, _potrubí_,
+ {{0x7262d037,0x4eab8006,0xb4c3611c,0xe8668047}}, // _سابقه_, _zostanie_, _همواره_, _beartais_,
+ {{0x57929177,0xbbd872fe,0x78387180,0x00000000}}, // _manteisi, _símbolos_, _स्प्रिंग_, --,
+ {{0x927e912a,0x537970d3,0x617ed16e,0xe2903205}}, // [ad0] _alang_, _cosanta_, _أماكن_, _nalang_,
+ {{0x3b72514f,0x7386d216,0x6290d0da,0xa3966142}}, // _biashara_, _imeri_, _pelaku_, _flussi_,
+ {{0xd778d0cb,0xd2c51039,0x7bb9d0a4,0x82918084}}, // _یونانی_, _kellene_, _व्यक्तीं, _razali_,
+ {{0x93cf70e8,0x1a64125b,0xeb31119b,0xbe73300f}}, // _gravid_, _hőmérsék, _منیجر_, _मुण्ड_,
+ {{0x726de0de,0xe25a9051,0xd27e9037,0x9968f045}}, // _fotod_, _asali_, _elang_, _pasiente,
+ {{0x12ca5003,0x43785074,0x63961070,0x7e8460a7}}, // _haldi_, _inganta_, _مہینے_, _različni,
+ {{0x625a5101,0x52da5215,0xb807b07b,0x0d6e8175}}, // _kalli_, _europos_, _সমাবেশ_, _моментал,
+ {{0xe1f5516a,0xbc5b81ea,0x626dc241,0x097160a6}}, // _ओखलढुंगा_, _postani_, _novoj_, _सांझी_,
+ {{0x439481ca,0x9200305e,0xaf83730d,0x825a502c}}, // _teksto_, _galing_, _हकीकत_, _malli_,
+ {{0xe365f1c0,0x639dc037,0xa68cd288,0xc2d9e1d9}}, // _abogado_, _penyair_, _फरमान_, _vater_,
+ {{0xd2d9e258,0x52b370a2,0x128be0df,0x23a29058}}, // _water_, _ljudima_, _अनहोनी_, _chapo_,
+ {{0xf2fce2d7,0xed57c1eb,0x227e0018,0x32d4b022}}, // _bongda_, _тайвань_, _doing_, _dokokin_,
+ {{0x53ebe056,0x26f0e108,0x3ba8b096,0x8029107a}}, // _hitta_, _परिवेश_, _न्यूनतम_, _героем_,
+ {{0x72d98024,0x9230006e,0x937a5068,0x0c6990ea}}, // _aprel_, _aramada_, _volledig_, _게시판으로_,
+ {{0x02466113,0xcc3ac0c2,0xd7b66057,0x72d81113}}, // _nîmes_, _съвсем_, _peperang, _nqhes_,
+ {{0x0394605e,0xa3f9e0dc,0x5fbf218d,0xbd4631eb}}, // _bulsa_, _patur_, _fungerer_, _крутым_,
+ {{0x73564173,0x00473265,0x725be25f,0x7e4bb2d7}}, // [ae0] _moteris_, _रिजल्ट_, _litla_, _vinagame_,
+ {{0xa37f300c,0x3fa02210,0x106572c4,0xb26d503c}}, // _zamanda_, _decembar_, _danskern, _pogoda_,
+ {{0xe291f0f5,0xbe29505e,0x42bca04e,0xfa7f4056}}, // _thuat_, _panganga, _mondial_, _varandra_,
+ {{0x220032cc,0x126d80a0,0xf355d033,0x5386d00e}}, // _paling_, _goron_, _acheter_, _seera_,
+ {{0x20ea219b,0x00000000,0x00000000,0x00000000}}, // _فراوان_, --, --, --,
+ {{0x327ee0b9,0x88dad173,0x330e0267,0x22da408d}}, // _kennis_, _skirting, _ababala_, _efanele_,
+ {{0xe276010b,0xfc5a726c,0xb889613b,0x00000000}}, // _starostl, _mentali_, _您当前位置_, --,
+ {{0x03ebe370,0x22908136,0x4cb7b1af,0xac2bd175}}, // _ditta_, _nikako_, _граници_, _ставил_,
+ {{0x47d8723b,0xd6c9f0a2,0x92b4733a,0x1225f045}}, // _अधिकांश_, _uglavnom_, _nunca_, _становле,
+ {{0x826d9134,0x028bd0cd,0xbd9b91fc,0xb8d4a1c0}}, // _noson_, _कुपोषण_, _toplanma, _bulutang,
+ {{0x5200304a,0x53c5c171,0x547841ae,0x0c5530a3}}, // _saling_, _estará_, _comercio_, _meitene_,
+ {{0x234172e1,0xa394708c,0x1c77f241,0x5d4bb0e2}}, // _peserta_, _kunsa_, _unesite_, _binadamu_,
+ {{0x2e46e002,0xda9a5269,0x00000000,0x00000000}}, // _पोस्टकार, _tehnilis, --, --,
+ {{0x82b47128,0xa2247039,0x4c75f2be,0x6045b1ab}}, // _munca_, _munka_, _रिक्शा_, _soluzion,
+ {{0x9c537099,0xe1c92034,0x72c37379,0x7444d00b}}, // _postova_, _госта_, _poslova_, _terjangk,
+ {{0x680d4238,0x322b0320,0x4acb037a,0x6f0b0164}}, // _मोटरसाइक, _kommune_, _kommunen_, _kommuner_,
+ {{0xe2dc10d4,0x12cb9076,0xb3ce930c,0xb22b004f}}, // [af0] _notevoli_, _schowka_, _clave_, _commune_,
+ {{0xd291f1ba,0x52345008,0x52fe613a,0xf201f1ff}}, // _thuas_, _example_, _drugoj_, _thuis_,
+ {{0xc39460b4,0x8d82918d,0x6502902a,0x741d3070}}, // _agost_, _generelt_, _generell_, _ٹارگٹ_,
+ {{0x13578118,0x4316908d,0x93f40014,0xd8462094}}, // _profilo_, _kwazo_, _hastas_, _משכילים_,
+ {{0x8c72d061,0x00000000,0x00000000,0x00000000}}, // _skirtas_, --, --, --,
+ {{0x2f8141a6,0x32246078,0x09f6000e,0xbb1cc089}}, // _पृष्ठलाई_, _iroko_, _locative, _cinquant,
+ {{0xf2fc00a6,0xef6d3048,0x41b3807b,0xed1e42ae}}, // _liiga_, _đềmới_, _বাজেট_, _поздравл,
+ {{0xf6397202,0xc7d99082,0x7b471204,0x00000000}}, // _aplicaci, _надзвича, _kolačiće_, --,
+ {{0x164df04d,0xa2014299,0x8b26c182,0x6090b02c}}, // _この商品について, _medier_, _однос_, _netissä_,
+ {{0x120fc037,0x22ca70f8,0xd2fd5264,0x63ea7119}}, // _محترم_, _handi_, _chegar_, _hanti_,
+ {{0x52ca7184,0x93ea71df,0xb31671e3,0x52fc90a2}}, // _kandi_, _kanti_, _benza_, _snaga_,
+ {{0xd1e73335,0x1db3207f,0x00000000,0x00000000}}, // _kliknutí_, _sanatate_, --, --,
+ {{0x72ca7102,0x1da660c9,0xf200b097,0x3291e25e}}, // _mandi_, _pozitiva, _budite_, _iktar_,
+ {{0x62ca7003,0xc386d05d,0xb7e70054,0xdc7d6036}}, // _landi_, _leero_, _तवारीख_, _последва,
+ {{0x2224d1bf,0x55785041,0x62560035,0x9e39c132}}, // _greke_, _poslední, _थोडेसे_, _متعصب_,
+ {{0xe2e3d090,0x425ad060,0xefa04209,0xf8d9219b}}, // _prandaj_, _abele_, _videolar_, _تمرکز_,
+ {{0x32d4702b,0x1c0ca241,0x49c6f0de,0x871cf175}}, // [b00] _vardas_, _austrijs, _खींचे_, _констати,
+ {{0x205130c3,0x2de5237b,0x7b211065,0x9355c0e2}}, // _историк_, _hoặc_, _البطولة_, _profesa_,
+ {{0x49f621a2,0x17aa7192,0x99f2c16e,0xcb8740d4}}, // _توصیه_, _bestilli, _mhargaid, _camiller,
+ {{0x9af6c18d,0x0510102b,0xa2d50165,0xb525502a}}, // _oplevels, _ферму_, _apposta_, _зверненн,
+ {{0x63f4715f,0xb386d1e4,0xd3a2e0e9,0x25166163}}, // _tautas_, _storas_, _skipta_, _prozorsk,
+ {{0x6c379020,0x127e712c,0x33949084,0xb291e25e}}, // _연구회참여_, _bonne_, _kuasa_, _aktar_,
+ {{0x777f7302,0x329e116f,0xbd014144,0x8e3a2055}}, // _क्रान्ति, _krajnja_, _okuliare_, _tababara,
+ {{0xa394c1a3,0x72fce37c,0x52d3d057,0x82fc711e}}, // _polski_, _lingua_, _seronok_, _ringa_,
+ {{0x92fc70a1,0x3f6951b3,0xed6f9071,0xffbec090}}, // _singa_, _intactas_, _адмисэ_, _koalicio,
+ {{0xb744e11c,0x626e616a,0x8292500d,0xe02ca24a}}, // _جنجالی_, _metody_, _satani_, _direksyo,
+ {{0x4200c17f,0x417da018,0xb3dc61a3,0x00000000}}, // _kilimo_, _סיבוב_, _znowu_, --,
+ {{0x127e6078,0x2c31a20a,0x00000000,0x00000000}}, // _oloni_, _аспирь_, --, --,
+ {{0x88731031,0x129260e6,0x025a905d,0xe8cd9271}}, // _您想在自己的网站, _gerade_, _kaali_, _intensid,
+ {{0x0f1da0ea,0x00000000,0x00000000,0x00000000}}, // _무엇보다도_, --, --, --,
+ {{0x91db3173,0x62927154,0x00000000,0x00000000}}, // _строгая_, _barani_, --, --,
+ {{0xfbe8505c,0x7c91121b,0x0badb018,0x00000000}}, // _खजुराहो_, _igauteng_, _ביומד_, --,
+ {{0xa25aa158,0xb301b099,0xb248d1bd,0x00000000}}, // [b10] _abilé_, _odabere_, _filmet_, --,
+ {{0x791b7048,0x827f704a,0x1c627203,0x5b2011e7}}, // _giáo_, _adanya_, _seguito_, _arjantin_,
+ {{0x8386c37d,0x92f6c03b,0xa512f0b7,0x660bc1bb}}, // _bedre_, _artikuj_, _универсу, _учора_,
+ {{0xa2615215,0x00000000,0x00000000,0x00000000}}, // _зверобой_, --, --, --,
+ {{0x1006c004,0x2bc990cb,0x227e71d9,0x00000000}}, // _עסטרײַך_, _ٹیوٹوریل, _sonne_, --,
+ {{0x20670004,0x93eb7099,0x00000000,0x00000000}}, // _אידעאָלא, _slatko_, --, --,
+ {{0x7c37d0cf,0x2dffe0dc,0xda0d30fe,0xd26d91ef}}, // _istifadə, _arrestua, _ভয়াবহ_, _nosos_,
+ {{0x23e0025b,0xf3730202,0xa2da7106,0x361d4100}}, // _péter_, _delante_, _corect_, _разметку_,
+ {{0x43f8e05b,0x526c6069,0x925b4036,0xd7157047}}, // _minutu_, _hnoos_, _quelli_, _meiriceá,
+ {{0x326cf039,0xe3ea905d,0x42924051,0x00000000}}, // _angol_, _abato_, _jawabi_, --,
+ {{0xad292006,0x5c092214,0xaddde210,0x8975c0b2}}, // _komputer, _komputa_, _projekat_, _musiccit,
+ {{0x52d9e37e,0x239d6173,0x3d7860b2,0x27e69176}}, // _obter_, _заменены_, _khăng_, _हेमराज_,
+ {{0x7e01c03c,0x3637b047,0x966420ea,0x00000000}}, // _rejestra, _chiclipé, _공휴일제외_, --,
+ {{0xa2a6c1b4,0x6213e1c0,0x539400a0,0xeaca1065}}, // _sumber_, _lathe_, _grist_, _ومنتديات_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x020270bd,0x1d86c13c,0xfc18e164,0x5b6b6054}}, // _deside_, _награду_, _detaljer, _renoveer,
+ {{0x8386d158,0x568cd166,0x426d10f5,0x0db75133}}, // [b20] _beere_, _फरहान_, _socola_, _ministea,
+ {{0x84868025,0xc3944254,0x6e51702c,0x82fc7100}}, // _नीलामी_, _ormsa_, _viranoma, _dingo_,
+ {{0x43954116,0x727e6119,0x906da018,0xe2eb4249}}, // _geeska_, _loona_, _מסיבה_, _avsnitt_,
+ {{0xa386e047,0x9b9950d5,0x3f9b5047,0xf200e018}}, // _cairde_, _ध्रुवीय_, _minicíoc, _senior_,
+ {{0x550380c3,0xdc4771d5,0xa0f3e0c9,0x93a3f0d7}}, // _претекст_, _akuwkwo_, _županija_, _grups_,
+ {{0x526e108e,0xb22a2003,0x325be128,0x00000000}}, // _šport_, _heimild_, _titlu_, --,
+ {{0x93f470a0,0xb27ee0b6,0xab93f319,0x92c9308f}}, // _destun_, _finner_, _dividido_, _在庫がございます_,
+ {{0xfcdc902c,0x00000000,0x00000000,0x00000000}}, // _остально, --, --, --,
+ {{0x93806009,0xa9113241,0x76d130c9,0xe0f3e36d}}, // _ukuran_, _pojavlju, _pojavlji, _županije_,
+ {{0xd26c8007,0xe74731c9,0xb23f7039,0xea0b7070}}, // _kokoro_, _милион_, _munkatár, _kilométe,
+ {{0x13574368,0xdf69d100,0xa2cb0246,0x4909d03c}}, // _ज्ञानकोष_, _komandos_, _amadou_, _makijaż_,
+ {{0x33940039,0x00000000,0x00000000,0x00000000}}, // _friss_, --, --, --,
+ {{0x73954328,0x73ea9007,0xfab70036,0xb4d100a4}}, // _meesha_, _apata_, _германия_, _मालिका_,
+ {{0x0a4a719a,0x826c712c,0x8d2d4222,0x00000000}}, // _прелимин, _sinon_, _agriviet_, --,
+ {{0xcb98e268,0x00000000,0x00000000,0x00000000}}, // _enriquec, --, --, --,
+ {{0x02927283,0xd542c036,0x627a11fd,0x09c0f224}}, // _marami_, _невероят, _трениров, _prometna_,
+ {{0x06d5d031,0xe22600a4,0xf641c100,0x327f0072}}, // [b30] _与本站立场无关_, _kaskus_, _шторм_, _fransk_,
+ {{0x93eae239,0x74a811aa,0xac71b164,0x52008114}}, // _buiten_, _izuzetno_, _startet_, _taking_,
+ {{0xc3954116,0x02d5616d,0x03807033,0x5345212c}}, // _beesha_, _tatoeba_, _heures_, _pendant_,
+ {{0x13960192,0x83f4712c,0x4ebac1ab,0x52a6319a}}, // _masser_, _cartes_, _рокли_, _limbaj_,
+ {{0xd91b2054,0xf681e32a,0x420080a4,0x00000000}}, // _आदर्शवाद_, _देसाई_, _saking_, --,
+ {{0xb2ca717a,0x8b03f179,0x00000000,0x00000000}}, // _разочаро, _haastatt, --, --,
+ {{0x52da5187,0x72000119,0x92fcd0af,0xf7a730b5}}, // _artean_, _idiin_, _enega_, _integrov,
+ {{0xc9c641c9,0xa7bef031,0x4be2908d,0x853b7039}}, // _apresent, _安徽企业网络服务, _inombolo_, _افلاطون_,
+ {{0x20b8c2ad,0x93f470a0,0x938dc0d8,0x1241511c}}, // _ислам_, _testun_, _اشخاص_, _سیمبین_,
+ {{0xd27e7267,0x0a967190,0x6769e041,0x00000000}}, // _bonna_, _decembrī_, _सदस्यले_, --,
+ {{0x302e1034,0x679971b8,0xb25a50b4,0xf266f17a}}, // _produzio, _противоп, _valls_, _immobili_,
+ {{0x5c66d173,0xf27e7036,0xf3090061,0x7290a19d}}, // _centras_, _donna_, _futbolo_, _kadang_,
+ {{0x72e711ff,0x6affd12f,0xf2a741f6,0x00000000}}, // _maanden_, _cróitis_, _trebam_, --,
+ {{0xd9d9007b,0x1c72a120,0x725680cd,0x43940269}}, // _সংযুক্ত_, _ofertes_, _सुहागरात_, _naise_,
+ {{0x3fe8d311,0x79cd9035,0x521391df,0x927ed299}}, // _सोयाबीन_, _नदिचा_, _basho_, _alene_,
+ {{0x039581bf,0x4d120065,0x327eb1e8,0x00000000}}, // _kurse_, _himreoir, _njenog_, --,
+ {{0xb7aa311c,0x11b4b0c5,0xe290c19e,0xe6d9f057}}, // [b40] _فرانسوی_, _varmasti_, _jumaat_, _perpuluh,
+ {{0xc6cca0d3,0xacf7401d,0x45e60106,0x0c258222}}, // _ollainni, _hastalar, _procesul_, _funstarb,
+ {{0x64e22125,0x52f4602d,0x00000000,0x00000000}}, // _अय्यर_, _dikirim_, --, --,
+ {{0xc2eca0b5,0x4ee63050,0x0200a05e,0xf7b3037c}}, // _kontroly_, _билтен_, _bading_, _titulaci,
+ {{0x3ac88071,0x13869047,0xfefb334b,0x00000000}}, // _ресурсе_, _neart_, _perjalan, --,
+ {{0x1ffc302c,0x25d65128,0x129261f5,0xd3ea700a}}, // _способ_, _probabil_, _pirato_, _santu_,
+ {{0xdff8f061,0x24463031,0xb2901055,0x62ca71c6}}, // _французс, _信息产业部_, _odhan_, _pandu_,
+ {{0x702eb0d7,0x9e29505e,0x93860160,0x00000000}}, // _moderado, _nanganga, _beirt_, --,
+ {{0xb3ac6142,0x6c14f039,0x5200a0a4,0x00000000}}, // _gruppi_, _verseny_, _gading_, --,
+ {{0x82895150,0x2a5e3056,0x4df711fd,0xe22461be}}, // _endaweni_, _engelska_, _интервью_, _broka_,
+ {{0xf27e616d,0xa39472ff,0xe0382077,0xcc8011ab}}, // _noong_, _hérna_, _обувь_, _черен_,
+ {{0x9386924d,0x3a1050d8,0xee68c033,0x1647e05d}}, // _feart_, _irlandia_, _concerna, _abahingi,
+ {{0xdeaca1c9,0x1602c178,0x1c1d12f0,0xef3f00cf}}, // _controle_, _confianç, _štandard, _kilometr_,
+ {{0xfb97f25b,0x279ed07f,0x37513173,0x0ecd60b5}}, // _regisztr, _араба_, _сваёй_, _investič,
+ {{0x6d76c1a2,0x524a20a6,0x00000000,0x00000000}}, // _برهنه_, _कामेडी_, --, --,
+ {{0x12551078,0xcad2e0e6,0x883860ea,0x00000000}}, // _ajagunnà_, _vertrete, _쇼핑몰에서_, --,
+ {{0x6e7cf098,0xf26dc1cf,0x149992ad,0x9ae4a1c4}}, // [b50] _बिश्व_, _novos_, _половине_, _telefonb,
+ {{0x5ce21094,0x2d55a1af,0x616d2008,0xf2469065}}, // _הקיצור_, _планира_, _military_, _gailísis_,
+ {{0xbf7d1065,0x16d35037,0xf2baf1c0,0xf737a203}}, // _الفعاليا, _majapahi, _sleeved_, _заплата_,
+ {{0x07b3615e,0xfbe741ab,0x81bc60fe,0x00000000}}, // _situācij, _ulterior, _সোহেল_, --,
+ {{0xe7c9301a,0x9c6161be,0xdb063047,0x00000000}}, // _varsinai, _burukwa_, _clasaice, --,
+ {{0x9c5a7078,0x0c859008,0xfd325055,0x3386905a}}, // _britani_, _רוסיה_, _dayniile_, _bwari_,
+ {{0xd6edb1a3,0x138dd126,0x020ec045,0x4f03d1d5}}, // _finansow, _اشراق_, _назар_, _akụnụba_,
+ {{0x4cd7a1b2,0x335e10b7,0x3c0f20ea,0x63ea902c}}, // _catholiq, _паузе_, _오피스텔분양권_, _saatu_,
+ {{0x00e5602b,0x0dad0175,0x137d8159,0x22494035}}, // _скандаль, _quantida, _adékòmí_, _taemin_,
+ {{0x3aad9123,0x1b3d9008,0x53f94070,0x00000000}}, // _funktion_, _function_, _indult_, --,
+ {{0x2854311c,0x52ebc04d,0x63a290b6,0xa387704f}}, // _موزيک_, _kvinder_, _skapa_, _charte_,
+ {{0x42cae105,0xc3940007,0x85dab082,0xac7160cd}}, // _bundan_, _irisi_, _зазначен, _भुट्टो_,
+ {{0x5eab2378,0xa34040b4,0x00000000,0x00000000}}, // _clientes_, _defensa_, --, --,
+ {{0x05eee098,0xa22b20e7,0xe2b46175,0xb33fe2de}}, // _příští_, _promene_, _troca_, _sedací_,
+ {{0xb2d9b18e,0x9248e0c9,0x23ef42a4,0x1c5f42a4}}, // _covert_, _primio_, _obitelji_, _obitelj_,
+ {{0x0e35006e,0x4dc8c02a,0xc9ec2018,0x2f925098}}, // _adigunja, _центром_, _להאמין_, _receptů_,
+ {{0xd65d407f,0xdff42008,0xf280a0a5,0x00000000}}, // [b60] _контамин, _הנחיות_, _herkese_, --,
+ {{0x2f38f1f4,0x8b421144,0x00000000,0x00000000}}, // _होशियार_, _odporuči, --, --,
+ {{0x42ca9051,0x44b3f004,0x4a8d50ea,0x00000000}}, // _ibada_, _באָבע_, _전문가들은_, --,
+ {{0x47e90342,0x82ca0060,0x69f96232,0x37074050}}, // _अदाकारी_, _abidi_, _forumumu, _instalaç,
+ {{0xd3ebe03e,0x00000000,0x00000000,0x00000000}}, // _hitti_, --, --, --,
+ {{0xf300c278,0xc2d4703f,0x8da9d06a,0xa30ac2ad}}, // _podatki_, _naudas_, _rendelés_, _открили_,
+ {{0x1c9cb175,0x7291807a,0x2629e0cc,0x2d2cc076}}, // _помогнет, _vyras_, _ליבהאָבע, _podobnie_,
+ {{0x527f4267,0x30e0e002,0x12f05037,0x6bccb036}}, // _mwenzi_, _स्वीटी_, _kelinci_, _полицият,
+ {{0x94f31034,0x594ed323,0xc2881008,0x222430a2}}, // _местопол, _publizit, _עתידיים_, _majke_,
+ {{0x5c32c0aa,0x0290224b,0xe62651ba,0xf7c7d02c}}, // _olabilir, _kamate_, _túdarás_, _saksalai,
+ {{0x82926235,0x32d8e2b7,0x7198a0c3,0x00000000}}, // _jiraan_, _maneno_, _провизор, --,
+ {{0x7308724b,0xd1414037,0x03ea91be,0x6292510f}}, // _trebalo_, _مشارکت_, _abata_, _hutang_,
+ {{0x9d0060bc,0x3bca9215,0x7c32c0aa,0x627e90b2}}, // _groundbr, _светлых_, _alabilir, _loang_,
+ {{0x064922e1,0xb27e237f,0x72249099,0x73eaa0ed}}, // _نتيجه_, _rokna_, _braka_, _sabtu_,
+ {{0x63940120,0x6b69d119,0x4df4919b,0xa824615e}}, // _crisi_, _tartanka_, _منچستر_, _grīdas_,
+ {{0xd3ead0bd,0xf2ca91d0,0x72bf0037,0x00000000}}, // _montre_, _ipade_, _امواج_, --,
+ {{0x1d056061,0x829262c3,0x1213f1b2,0x0f352380}}, // [b70] _populiar, _kurang_, _nsthe_, _muassasa,
+ {{0x2a0c1004,0x113bb004,0xb37fc267,0xfe6c1070}}, // _בפירוש_, _אלזוז_, _talanta_, _پیشوا_,
+ {{0x32026008,0x22ac1008,0x471541c9,0x6ed0c133}}, // _during_, _והקריות_, _seguranç, _nàiseant,
+ {{0xb4a82284,0xc5d690e4,0x03374008,0x32b4d092}}, // _opcional_, _परिपुर्ण_, _סטטיסטיק, _preco_,
+ {{0xdb09818b,0x5c63030c,0x9853030c,0x7316d1e8}}, // _lýsingar_, _estudio_, _estudios_, _prozor_,
+ {{0x1b09302b,0xb2f300ed,0xd2d21143,0x4688e05f}}, // _kompiute, _dilihat_, _paremini_, _wonderli,
+ {{0xdc5ce12d,0x4c75f144,0x5ae5125c,0x52b4d208}}, // _नोबेल_, _napriek_, _pertence, _stocha_,
+ {{0xccd1d175,0xdcbec062,0x92fcd0c5,0xc7a080b2}}, // _особено_, _кажем_, _blogin_, _eximbank_,
+ {{0xdb752140,0x03f5224b,0x4342d32b,0xa2bf3047}}, // _tekstova_, _tekstovi_, _बंदूक_, _حمدان_,
+ {{0xd4f96080,0xd20260e9,0x5cef31a7,0x02cb4122}}, // _peňazí_, _netinu_, _hintergr, _preden_,
+ {{0x33dd200e,0xaf16c12f,0x51434065,0x7f1360a0}}, // _anywa_, _leideann, _ممارسة_, _ffotogra,
+ {{0x137a1037,0xa2fc708c,0x14044098,0x00000000}}, // _masakan_, _dinga_, _semináře_, --,
+ {{0xcaf720c3,0xa3cee1fe,0x2986a07b,0xec7d7054}}, // _наречия_, _provoz_, _মুজতবা_, _muusika_,
+ {{0x32b49006,0x7394903f,0x7c01c17d,0x00000000}}, // _praca_, _prasa_, _luister_, --,
+ {{0x33ead1cd,0x227ed134,0x7290419f,0x2c762002}}, // _contre_, _eleni_, _idman_, _सिक्सर_,
+ {{0xc25ac03d,0x4401b048,0x730971a3,0x7e2ec229}}, // _mellem_, _kilobook, _aplikacj, _поверхно,
+ {{0xb9c581bb,0xd2fc9064,0x1c630134,0xaac3d2f8}}, // [b80] _сказочны, _niaga_, _astudio_, _समापन_,
+ {{0x02fc705d,0xc2d12098,0x738072b4,0x77aa6213}}, // _bingi_, _autorem_, _terras_, _partiali,
+ {{0xfa08a33c,0xc984f179,0x76d1a122,0x6e53a037}}, // _bakabona_, _tarkaste, _dobavlji, _masaraka,
+ {{0x2e6de291,0x72ea000d,0x12900046,0x239670d2}}, // _अधिकारिय, _ingingo_, _leiaf_, _passie_,
+ {{0x925ad045,0x43958120,0x7b21c16c,0x00000000}}, // _spela_, _cursa_, _атенуаря_, --,
+ {{0xc3cff07f,0xd56ff338,0x2883d0ea,0x91661018}}, // _servicii_, _servicio_, _인근지하철_, _כיסויים_,
+ {{0x65aa01bc,0xbceed175,0xc62d6045,0x7eb30044}}, // _promoçõe, _вреди_, _trondhei, _dereitos_,
+ {{0x40ad507b,0xdceed0b7,0x37500065,0xa6d320d5}}, // _ধ্বংস_, _креде_, _الطويل_, _दूषित_,
+ {{0x5d1e200d,0xd3ea9106,0x0b7871e4,0x00000000}}, // _genocide_, _spate_, _catriona_, --,
+ {{0x626de174,0x0d8451ea,0x08c0c144,0x72a64052}}, // _votos_, _izberite_, _predsedu, _qembu_,
+ {{0x529251bc,0xd2009069,0xe38000a2,0xc3df204d}}, // _citado_, _txais_, _ispred_, _この質問内容が不,
+ {{0x9b4010e7,0x32240074,0x9739c106,0xc2d8c082}}, // _енглески_, _saika_, _impotriv, _bilete_,
+ {{0x8373713b,0x66534173,0xbc51e025,0x4bf4101f}}, // _demande_, _ментальн, _यादें_, _amatpers,
+ {{0xbf263020,0x9cd99330,0x00000000,0x00000000}}, // _홈페이지에_, _תחיית_, --, --,
+ {{0x526cf091,0x00000000,0x00000000,0x00000000}}, // _digon_, --, --, --,
+ {{0x02fc7143,0xee7c00de,0x00000000,0x00000000}}, // _ringi_, _तुम्ह_, --, --,
+ {{0x5c6490ab,0x621391bf,0xb10e1018,0x4ead90d6}}, // [b90] _octubre_, _basha_, _מופצות_, _lintasme_,
+ {{0x226ce131,0xaf4c404d,0x0767f12b,0x226cd32e}}, // _infos_, _ウェブライタ_, _podložky_, _ndlovu_,
+ {{0x927ff0a4,0x96981008,0xa769119b,0x14641276}}, // _jeung_, _מעצבים_, _گذاشتم_, _ائتلاف_,
+ {{0x52925299,0x08645050,0x42da51dd,0x640d2065}}, // _betale_, _универзи, _ostean_, _النحو_,
+ {{0x9263f007,0x929dd1ea,0xf39572d7,0xd237803e}}, // _abdulsal, _svojega_, _trasua_, _verja_,
+ {{0x72da40f5,0xb30fe00b,0x4394d018,0x47360018}}, // _fullbox_, _menatap_, _guest_, _פיננסים_,
+ {{0xb27e0258,0xe9b1f241,0x62da5121,0x4256c025}}, // _point_, _dvadeset, _astean_, _अनदेखी_,
+ {{0x214ec216,0x684a1061,0xe200d039,0x1c779013}}, // _sisterho, _сотка_, _ideig_, _kwestie_,
+ {{0xf395417a,0x23866011,0x33a3a06f,0x7764711f}}, // _questa_, _stora_, _mappa_, _अस्तित्व,
+ {{0xb09f204d,0x00000000,0x00000000,0x00000000}}, // _イベント会場_, --, --, --,
+ {{0x05e19031,0x74074039,0x1be3604a,0x1e47e128}}, // _网站或个人转载使, _feliratk, _sementar, _требуе_,
+ {{0xb2d56143,0x125b221b,0xdaf56077,0x52cae056}}, // _euroopa_, _eccles_, _euroopan_, _handla_,
+ {{0x52b571b2,0x35add036,0xa2f3c02c,0x92927239}}, // _deacon_, _работата_, _lukijat_, _straat_,
+ {{0x568ee177,0xd5f4a381,0x037ec0b7,0xed50e0e1}}, // _ceredigi, _zamestná, _абордязэ_, _वेस्टइंड,
+ {{0x73f460c5,0xf87d202b,0xb5d9d13c,0x127f7047}}, // _kautta_, _кампанія_, _анализе_, _amanna_,
+ {{0x9bab41a2,0x5d6de034,0xa3ea322e,0xbf2981da}}, // _فرکانس_, _поддържа, _namtha_, _fratella,
+ {{0x89f29036,0xa394230f,0x3c67d0c5,0x00000000}}, // [ba0] _великобр, _jūras_, _minusta_, --,
+ {{0xa39580bc,0x6f242116,0x58bc2018,0xe847d1ab}}, // _kurso_, _mustaqba, _מפגשים_, _развива_,
+ {{0x5c4b11b4,0x22bb80a5,0xa5b602d7,0x64f91160}}, // _sunting_, _nedenle_, _keangnam_, _موبايلي_,
+ {{0xb2ab1068,0xa2d8b26d,0x9f23931d,0xc9b8e25d}}, // _vandaag_, _videre_, _menentan, _handbolt,
+ {{0x5679d118,0x18cb613b,0xdc70a018,0xd27e0033}}, // _региона_, _北京新发地_, _outside_, _soins_,
+ {{0x103fc105,0x92c5100f,0xbc6161e2,0xe25b60e9}}, // _sistemin, _selline_, _dituela_, _reglum_,
+ {{0x34c1f19d,0x92027052,0x0614913b,0x595cb188}}, // _memberik, _sesine_, _经络锻炼法_, _naalakke,
+ {{0x027e51ce,0x915b9107,0x8396623d,0x02ca900e}}, // _volna_, _консумир, _persoa_, _abadu_,
+ {{0xd0270070,0x6afa60ea,0xf26d107e,0x00000000}}, // _باغیوں_, _이르기까지_, _dizon_, --,
+ {{0xd34550ff,0x4e59501a,0x0e5000f7,0x89f65002}}, // _nosenie_, _минималь, _vakantie_, _नागेन्द्,
+ {{0x2395837e,0x52df11c4,0x12d4220b,0x2c4ec282}}, // _curso_, _sonntag_, _maqdis_, _cheveux_,
+ {{0x27cad1ab,0x3395f119,0x5292513d,0x62f520cf}}, // _consigli, _muuse_, _totaal_, _ittiham_,
+ {{0xac605144,0x622c111c,0xb7e0d382,0x12a6d383}}, // _augusta_, _سیکلت_, _बिमर्श_, _osobnu_,
+ {{0xc29c4069,0x323fe0ea,0x00000000,0x00000000}}, // _txujlub_, _모집합니다_, --, --,
+ {{0x3387f1be,0x55785092,0x429090a2,0x875c113b}}, // _amuru_, _posledný, _najave_, _政府信息公开_,
+ {{0x5317d0a9,0x63942143,0x427e60a4,0x0d29e173}}, // _ljubavi_, _saksa_, _yoona_, _песнях_,
+ {{0x422470bd,0x475a032a,0x1224222b,0xceb410a3}}, // [bb0] _manke_, _पिक्सेल_, _pakka_, _vardarbī,
+ {{0xe3bb3037,0xc2da509e,0x52de413c,0x33860245}}, // _اسباب_, _mateka_, _нормално, _ngira_,
+ {{0x62e5e187,0xffe80035,0xaf3ca16c,0x9605a07a}}, // _gainera_, _अर्थातच_, _колорату, _буклет_,
+ {{0x8838f1ab,0x8d7860b2,0x3efc6054,0x7649a14d}}, // _проектир, _phăng_, _युवकन_, _тендера_,
+ {{0x7386007f,0x2ed040d3,0x0bb59111,0xf083b091}}, // _stiri_, _cúiseann, _नियुक्ति, _derbynio,
+ {{0x2c062008,0x2d60311c,0x950a6128,0x00000000}}, // _השגויים_, _ماهیانه_, _momentul_, --,
+ {{0x944ee02e,0x724941ae,0x00000000,0x00000000}}, // _souvisej, _premio_, --, --,
+ {{0xaf96b055,0x15319025,0x00000000,0x00000000}}, // _wardheer_, _चम्बल_, --, --,
+ {{0x6fcbc045,0xd2ff4081,0x5166e031,0x52c6e031}}, // _свобода_, _skriver_, _第二十八条_, _第二十六条_,
+ {{0x52da5154,0xdc75e05e,0x06a2a003,0xe06260d5}}, // _wateja_, _sekular_, _tilfinni, _पारसी_,
+ {{0xffa5b113,0x33eba02b,0xd3869102,0x417ec02f}}, // _koomtxoo, _tapti_, _utara_, _дамбы_,
+ {{0x8292703e,0x63860086,0xfc3080b9,0xf9d8a00c}}, // _gerast_, _agira_, _spesifis, _inceleme,
+ {{0x152b7070,0xaa4da0d4,0xec9361f3,0xd5fda0b5}}, // _لڑکیوں_, _alleanza_, _निरोग_, _potenciá,
+ {{0xb38070ab,0x22a6d144,0x1af6c070,0xa2ff7113}}, // _terres_, _osobou_, _böngésző, _sibhlub_,
+ {{0x2212e0c5,0x8212d35b,0x63877047,0x4224707a}}, // _siihen_, _shehu_, _bearta_, _sunku_,
+ {{0x317e01b8,0x40c7c0b7,0x1f6dd069,0x8053c19b}}, // _положени, _физиче_, _toobfaab_, _تاکسی_,
+ {{0xc224209c,0x0e923062,0x0b04f0c5,0x5be78208}}, // [bc0] _makko_, _потребу_, _viestist, _alpenhor,
+ {{0x025b1124,0x2a05c0c3,0xb2420038,0xa26c70cb}}, // _shalay_, _ближний_, _nyumbani_, _finom_,
+ {{0x03eb8054,0x6960d05c,0x1ddc2070,0x7b9161a3}}, // _tartu_, _फिटनेस_, _فلموں_, _materiał_,
+ {{0x82da7109,0x87a11021,0x00000000,0x00000000}}, // _årene_, _suaugusi, --, --,
+ {{0x2248c195,0x23dd0082,0x00000000,0x00000000}}, // _kommer_, _протягом_, --, --,
+ {{0x5e54e1ab,0x79aa20e7,0x03947135,0x00000000}}, // _икономич, _којих_, _séria_, --,
+ {{0x85f32228,0xe804f293,0x63121114,0x3cbdd003}}, // _अतिथि_, _австрали, _members_, _formaður_,
+ {{0xac650181,0xb9f600cb,0xfb1af0d9,0xb39420c5}}, // _最近のコメント_, _japán_, _preprost, _jakso_,
+ {{0xbeffc19f,0x628cd065,0x00000000,0x00000000}}, // _forumçu_, _كلامك_, --, --,
+ {{0x62b55011,0x53a3f16d,0x62ff611c,0xc27ed02a}}, // _precis_, _tsupa_, _pesisir_, _poeng_,
+ {{0x12914091,0x00000000,0x00000000,0x00000000}}, // _bedair_, --, --, --,
+ {{0xac5300b5,0xb6e60065,0x00000000,0x00000000}}, // _dostala_, _ههههههه_, --, --,
+ {{0x7d85d194,0xb39600d1,0xb96e503b,0xec613241}}, // _словами_, _wasser_, _aksident, _prirode_,
+ {{0x653241bb,0xc1c400cc,0x4ec4313f,0x284a20f3}}, // _нейтраль, _מאנהעטן_, _tienphon, _паралелн,
+ {{0xf4d6a06b,0x02861008,0x8383915d,0xa2b66384}}, // _नगरिया_, _רשלנות_, _petrotim, _marche_,
+ {{0xf1bc507b,0x00000000,0x00000000,0x00000000}}, // _হোটেল_, --, --, --,
+ {{0xcf116108,0x00000000,0x00000000,0x00000000}}, // [bd0] _suitseta, --, --, --,
+ {{0xaf938354,0x237a7074,0x2c138025,0xd36bd003}}, // _vendedor_, _matakan_, _औरब्लॉग्, _byrjað_,
+ {{0x138781cb,0x6b315065,0x00000000,0x00000000}}, // _terre_, _الشديد_, --, --,
+ {{0x2394012f,0xf9061020,0xf9c7c100,0x7200c0af}}, // _naisc_, _부탁드립니다_, _багата_, _avdio_,
+ {{0x52b46144,0x339500ff,0x2202603e,0x71a7206a}}, // _troch_, _vlasov_, _skrifa_, _ہڑتال_,
+ {{0x9b97400d,0xa837e020,0x826ce080,0x134c20b5}}, // _komisiyo_, _하겠습니다_, _omnoho_, _dostupný_,
+ {{0x72904193,0x03fad045,0x00000000,0x00000000}}, // _odmah_, _старший_, --, --,
+ {{0x895cc136,0x826ce385,0xdecdd03f,0x92f00154}}, // _iskorist, _ponovo_, _oktobris_, _mshindi_,
+ {{0xd76e3175,0x2993704d,0x00000000,0x00000000}}, // _подготве, _ソフトウェア_, --, --,
+ {{0x4396026d,0x63a3f0d4,0x22eb6089,0x40f5316a}}, // _passer_, _grupp_, _plantes_, _अप्रील_,
+ {{0xdeb820cc,0xa71dc1f7,0xf2914091,0x920251ea}}, // _פעברואר_, _भद्दा_, _pedair_, _prvič_,
+ {{0xac75e111,0xc7b710a8,0x62cae246,0x77cb7136}}, // _कास्की_, _forsikri, _kundin_, _vlasteli,
+ {{0x0380635a,0x3cde10cc,0xbe9650ae,0x3c22219a}}, // _letras_, _צופרידן_, _čempionā, _înregist,
+ {{0x1b87c106,0x8e404270,0xc2a7803b,0x29c71025}}, // _библио_, _प्रशस्ति_, _serbe_, _प्रोमीता_,
+ {{0xfda2c034,0xf224202c,0x82432130,0x6a123061}}, // _контрол_, _pakko_, _мудрости_, _istoriją_,
+ {{0x0f5c70a4,0x52b12080,0x89dfb039,0x02ea7141}}, // _प्रगत_, _musieť_, _hangulat, _latihan_,
+ {{0xe165404b,0x43eb71f9,0x7333e0f5,0x00000000}}, // [be0] _motorway_, _cuatro_, _matxa_, --,
+ {{0xcc49633a,0x06e8b036,0xffb96044,0xff65a056}}, // _título_, _гарантир, _títulos_, _betalnin,
+ {{0x11983062,0xd592416f,0x00000000,0x00000000}}, // _потребна_, _dvostruk, --, --,
+ {{0xa3d5b1da,0x034200aa,0x6943507b,0x3081909d}}, // _trattati_, _gereken_, _পুরনো_, _opubliko,
+ {{0x03ce9136,0x726de0dc,0xac12b154,0x00c95065}}, // _glavu_, _fotot_, _utaratib, _وبعدها_,
+ {{0x0396608c,0x52d9006e,0x88895273,0x85a0c07b}}, // _agusta_, _bibere_, _застрахо, _পাবলিক_,
+ {{0x07b33044,0xf224e15f,0xd5933044,0x4876e076}}, // _próximos_, _bankas_, _próximas_, _gimnazju,
+ {{0xd35f3274,0xc34271e2,0x1d0dd033,0xd55c3386}}, // _funguje_, _batekin_, _populair, _prašume_,
+ {{0xdff45041,0x02bb7142,0x00000000,0x00000000}}, // _नुवाकोट_, _komunità_, --, --,
+ {{0x73869259,0x625ac225,0xc320d2a8,0x6efbb098}}, // _fearr_, _mellom_, _ideya_, _prevence_,
+ {{0xd69b2106,0x5bee301a,0x5c51d035,0xe7296352}}, // _акциуне_, _таком_, _tentara_, _maslinov,
+ {{0x9da791ac,0x13869128,0x52d91133,0x52442024}}, // _kerosaka, _afara_, _aicese_, _mömin_,
+ {{0x33a3f18b,0x127e614e,0xcf33e380,0x80574070}}, // _kaupa_, _anong_, _persatua, _feladato,
+ {{0x649cd270,0xfc267031,0xdf6d11d5,0xa27ee10c}}, // _रश्मि_, _加入收藏夹_, _ahụhụ_, _ainnir_,
+ {{0x225a9038,0xdf90212f,0xa87d217b,0xa265b00c}}, // _mbali_, _تنفيذ_, _artichau, _maalesef_,
+ {{0xeee65076,0x32015037,0xc3eb7037,0xdb5971cc}}, // _मास्टरमा, _daging_, _muatan_, _nantinya_,
+ {{0x0b765202,0x4417509f,0xcb6c026b,0x32c65128}}, // [bf0] _publicac, _politiek, _communic, _publice_,
+ {{0x956db387,0xe38702ff,0x2387312b,0xf2cab013}}, // _formació, _þarna_, _staré_, _hadden_,
+ {{0x52da002a,0xceace017,0xec3101a3,0xe2fcf26f}}, // _årets_, _користит, _osobowyc, _ligga_,
+ {{0x3732402e,0x625a909c,0x1358e02e,0x00000000}}, // _informov, _abali_, _letenky_, --,
+ {{0x237961f5,0xa386d256,0xe3eb0084,0x94d1c03c}}, // _kitanda_, _atera_, _umatku_, _पालिका_,
+ {{0x9f22e2d7,0x96bcd13b,0xa394f225,0xc2258037}}, // _vietinba, _见习魔法师_, _spiser_, _murka_,
+ {{0x827e9081,0x725b701b,0x16869388,0x5341402a}}, // _blant_, _jualan_, _गोराई_, _studier_,
+ {{0x423f9008,0x12fc9097,0x42483299,0xfdbc9097}}, // _במשקל_, _ostaviti_, _gammel_, _ostavite_,
+ {{0x336ce16c,0x52d59295,0x53cf511d,0xe2b4913f}}, // _виктория_, _odnosno_, _prevoz_, _trach_,
+ {{0x83ebf02b,0xe2da60f8,0xd2a781a1,0x9394003f}}, // _gauti_, _sarean_, _herba_, _gaisa_,
+ {{0xd097e130,0x2237819d,0xdd0812d7,0xb7aa3144}}, // _времена_, _kerja_, _vietstoc, _anatómia,
+ {{0xb2fc700f,0x489dd031,0x53090120,0xa680e0be}}, // _mingi_, _中国人民银行_, _treball_, _ieuencti,
+ {{0x45d0a03f,0x2c07212f,0xc200c042,0x522422b6}}, // _augustā_, _malartú_, _salive_, _pakke_,
+ {{0x8f891175,0x9ab150ff,0xac28e174,0x1498e0f6}}, // _детали_, _telefón_, _establec, _establez,
+ {{0xe98e1034,0x72243136,0x34f6413b,0x6b3c20fe}}, // _мария_, _bajke_, _一年赚一生工资_, _কীবোর্ড_,
+ {{0x92d9e089,0x038781e2,0x42000004,0x4394c0b0}}, // _actes_, _gerra_, _עמעצער_, _ymosod_,
+
+ {{0xe200c02b,0x154a42ad,0x20dd219b,0xc06c81ab}}, // [c00] _galite_, _половини_, _تیشرت_, _налягане_,
+ {{0x2637803c,0x83eb8119,0xe531e143,0x00000000}}, // _प्रतीक्ष, _harti_, _कम्बल_, --,
+ {{0x21cc90c2,0xb47360da,0x6d6d00f3,0xd6dfa036}}, // _attività_, _साधनपेटी_, _бактерии_, _descrizi,
+ {{0x2200c11a,0x31442008,0x1c379218,0xdf483008}}, // _kalite_, _בטוויטר_, _району_, _possible_,
+ {{0x672ba031,0x92cb812c,0xd3eb804e,0x43574244}}, // _经销假冒伪劣商品, _mardi_, _marti_, _beredar_,
+ {{0xda0aa380,0x318621ab,0x3bca9140,0x00000000}}, // _manakala_, _икономик, _preminuo_, --,
+ {{0x034061a9,0xfaf460cb,0x7276917f,0x00000000}}, // _bagenda_, _ترقیاتی_, _mbinguni_, --,
+ {{0x2ae7a14d,0x8b9511ab,0x224892b8,0x32d762c1}}, // _медицине_, _столицат, _omama_, _govorio_,
+ {{0x425d20ea,0xa204c26c,0x8f432082,0xb2fc010c}}, // _자연스럽게_, _kapitali_, _досвід_, _uaigh_,
+ {{0x603b90bb,0xc2cad32e,0xb2914036,0x031b230f}}, // _hautxawj_, _nceda_, _creare_, _novadā_,
+ {{0x6200c039,0x9d8c70eb,0x480d501a,0x50a3f15e}}, // _addig_, _počeo_, _понедель, _datubāzē_,
+ {{0xf349e21f,0x43ebf14a,0x940ef259,0x00000000}}, // _esterni_, _sauti_, _teorainn_, --,
+ {{0xe3db70eb,0x4e920144,0xf775332b,0x00000000}}, // _početak_, _poškoden, _इन्सान_, --,
+ {{0xe2bf804d,0x00000000,0x00000000,0x00000000}}, // _ザ操作端末_, --, --, --,
+ {{0x04b5b0a7,0x92008224,0xc4c3a09f,0x52b471d1}}, // _zdravnik, _linić_, _verbruik, _tanca_,
+ {{0xc23b812a,0x41ce5045,0x6035b1d9,0xbdffc008}}, // _fremont_, _положенн, _seitenan, _understa,
+ {{0x239541fd,0x88095020,0xc394d0f8,0x8d8f5232}}, // [c10] _viesti_, _삼성패밀리세이브_, _prest_, _belediye_,
+ {{0x852f317f,0x5651503e,0x12918205,0x5da781b7}}, // _mwandish, _skrifaði_, _kayang_, _motosika,
+ {{0x7ac490cb,0x0315101b,0x00000000,0x00000000}}, // _ارتکاب_, _kencing_, --, --,
+ {{0xcfef6108,0x39b96017,0xb177401a,0xc2b401e7}}, // _एकमात्र_, _материја, _kalastel, _chich_,
+ {{0x5e4380d8,0xac3390cc,0x740b7142,0xa68d6025}}, // _madaniya, _זאליס_, _immigraz, _भड़ास_,
+ {{0x05049031,0x6bc130ea,0x5899b032,0x5c57f04d}}, // _除权除息日_, _세금계산서_, _моментов_, _強い味方が必要_,
+ {{0xa9022020,0x0290f0f5,0xd2ee50ad,0x14fda0ea}}, // _저작권침해_, _fshare_, _verilib_, _상품입니다_,
+ {{0xc1880190,0x00000000,0x00000000,0x00000000}}, // _restorān, --, --, --,
+ {{0xa39591cd,0xb2fc0215,0x8c34e0ea,0x4006c050}}, // _aussi_, _taigi_, _민주주의법학연구, _енергија_,
+ {{0x9b11b0d8,0x1fea71cc,0x9adb007b,0x92cae0f7}}, // _امريکا_, _अर्थजगत_, _কলকাতা_, _landen_,
+ {{0xcc5381d4,0x720e10c5,0x0386d216,0x00000000}}, // _sentido_, _начал_, _chorom_, --,
+ {{0x3248f106,0xf2249119,0x327f92c1,0x22d8f177}}, // _primul_, _saaka_, _bosne_, _amgen_,
+ {{0x0f950223,0xa2f00108,0x73878003,0x00000000}}, // _अज्ञात_, _वक्री_, _verri_, --,
+ {{0xc344b170,0xeea3829c,0x69e5004d,0x384c9175}}, // _stredne_, _vendidos_, _テクノロジ_, _поставув,
+ {{0xe320032e,0x26228287,0x0676b0ef,0xf27c60e6}}, // _eliya_, _organizá, _دانشور_, _herstell,
+ {{0xa4019058,0x8d0b20f8,0x4d9781a3,0x8682d067}}, // _ameriken_, _horretar, _motoryza, _अहवाल_,
+ {{0x9969e06a,0xa3866091,0xd4d1c12d,0x6e0cb16a}}, // [c20] _مظاہرہ_, _stori_, _डिजिटल_, _vlastně_,
+ {{0xc66f61a3,0x9d6f3065,0xd34c20b5,0x2386d09f}}, // _pozdrawi, _أحكام_, _dostupné_, _sterf_,
+ {{0xa3945119,0x42cb5043,0x15a9f13b,0x7dbf9036}}, // _balse_, _dogdig_, _大兴少年宫_, _областта_,
+ {{0xf2cfd0c8,0x627ed168,0xe7fa41d6,0x5ad9c08f}}, // _dokonce_, _poena_, _anabigag, _トラックバックの_,
+ {{0xe39450b9,0x186d217a,0x00000000,0x00000000}}, // _valse_, _биология_, --, --,
+ {{0x67566185,0x32ca507f,0xe2b40116,0x72cbf03f}}, // _प्रमाणात_, _conditii_, _dhici_, _naudu_,
+ {{0xd2b400f5,0x9c51d1c6,0xbde6f0d8,0x00000000}}, // _thich_, _tentera_, _prajurit_, --,
+ {{0x938662f6,0xb023910b,0x038d2070,0x4b4961d5}}, // _agora_, _pondelok_, _اشیاء_, _aịkọn_,
+ {{0xf200612c,0x3aebc10f,0x3ed6a025,0x42cbf143}}, // _avoir_, _golongan_, _लैपटॉप_, _kaudu_,
+ {{0xcfea4025,0xbba481a7,0x00000000,0x00000000}}, // _पुरालेख_, _lediglic, --, --,
+ {{0x11703020,0xce734389,0xf37970e6,0x6bf1e1ac}}, // _책임한계와_, _विप्र_, _angaben_, _berterus,
+ {{0x07aad117,0x32360013,0x024830b6,0x50b7e179}}, // _любви_, _vrije_, _gammal_, _парень_,
+ {{0xa27e6119,0xd2f462aa,0x9a60f004,0xa751838a}}, // _gooni_, _airneis_, _אָננעמען_, _विकिपिडि,
+ {{0x13413065,0x5cf0b065,0x123601ea,0x7fa5113b}}, // _أنحاء_, _البصرة_, _trije_, _网络文化经营许可,
+ {{0x02b4d177,0x39f8f060,0x01e020c3,0x36e8c24a}}, // _drech_, _ainidena_, _бэтая_, _emicrani,
+ {{0xdc7661d6,0x22f53241,0x787380f9,0x00000000}}, // _agirisi_, _turizam_, _disponib_, --,
+ {{0xf3f82007,0x22b4d134,0xc9675025,0x54769082}}, // [c30] _lekun_, _frech_, _वाहनों_, _процесу_,
+ {{0x852951bb,0x21661094,0x5da9d076,0x7ae58217}}, // _брутальн, _חידושי_, _zapomnia, _spelaren_,
+ {{0x92da507f,0xb98590ff,0x02b5320d,0xac6f5007}}, // _altele_, _polievka_, _kendine_, _ajurawa_,
+ {{0xb2a7c036,0xec7c81c4,0xc197c229,0xf30d2037}}, // _района_, _meisten_, _районе_, _pembawa_,
+ {{0x038690f4,0x2386d216,0xc26370bf,0x00000000}}, // _atari_, _etere_, _adebísí_, --,
+ {{0x1b09f267,0x73f46147,0x00000000,0x00000000}}, // _abaprote, _martsa_, --, --,
+ {{0x9591408e,0xa20091cb,0x82a6d05d,0x02a600f6}}, // _prispevk, _avait_, _ntebe_, _uribe_,
+ {{0xf486b363,0x3aca31b8,0x02d872b6,0x8e0f310c}}, // _प्रायः_, _китайски, _emnet_, _aimeirea,
+ {{0xb528621f,0x82da50a7,0x2c05f03b,0x00000000}}, // _libertà_, _katero_, _personi_, --,
+ {{0xd23601f6,0x6158a082,0x4248002b,0xc91af0b2}}, // _prije_, _оголошен, _seimo_, _chịu_,
+ {{0x8549b108,0x7c78e147,0x0413f1d7,0x104e4246}}, // _स्पेशली_, _patuloy_, _ceistean_, _ahlussun,
+ {{0x72f0a278,0x3383c07b,0x7225802c,0x29fd2106}}, // _delovanj, _নিয়োগ_, _turku_, _duminica_,
+ {{0x026d81dd,0x10a47004,0x00000000,0x00000000}}, // _kirol_, _לאַגער_, --, --,
+ {{0x59b7300a,0x0f24f0c3,0x11792376,0x00000000}}, // _bandiera_, _админист_, _španiels, --,
+ {{0x65417065,0x402ec1ab,0x3d0310d7,0x227f71df}}, // _المؤتمر_, _tradizio, _explicat_, _ilanga_,
+ {{0x12d88157,0x56d5a119,0x2687d23a,0xf453938b}}, // _heheeh_, _faransii, _खुराक_, _अनौपचारि,
+ {{0x8337c034,0xad341018,0x3053c175,0xcb7fe099}}, // [c40] _николай_, _באיטליה_, _ромите_, _dopunska_,
+ {{0x71fc90c3,0xb2a69150,0x65a9b04d,0x2fc7b065}}, // _порядков, _ngaba_, _よろしくお願いし, _ولكنها_,
+ {{0x4387f0ab,0xd292710c,0xafbd113b,0x00000000}}, // _veure_, _òrain_, _年中国经济现代化, --,
+ {{0x938690b2,0x81dc7004,0x4c9810b4,0xdff00062}}, // _muare_, _ספּאָרט_, _objectiu_, _чешки_,
+ {{0x18dcd0b7,0x00000000,0x00000000,0x00000000}}, // _аузит_, --, --, --,
+ {{0xba0530aa,0x9200904f,0x7ff700c4,0x6d839173}}, // _saklıdır_, _avais_, _smokovec_, _войнах_,
+ {{0xe97a219b,0xf5c1e1e4,0x536b4116,0x00000000}}, // _شقایق_, _artaigil_, _gargaar_, --,
+ {{0x6477a082,0x226c20bd,0x00000000,0x00000000}}, // _проекту_, _lakou_, --, --,
+ {{0x62d82053,0x11f1a1a2,0x1c5b9088,0xc1cc6190}}, // _weken_, _ریمیکس_, _katulad_, _finansēj,
+ {{0xc2fe72d7,0xd3ead247,0x8200d02b,0x8fe8a0ca}}, // _exciter_, _protin_, _kelias_, _सीताराम_,
+ {{0x5429a119,0x12f460e9,0x73877100,0x03eae108}}, // _inkastoo_, _hvenær_, _tvarka_, _kontot_,
+ {{0x35cac1a5,0x238691d6,0xdceff056,0xe5c8d1df}}, // _canbannh, _agara_, _ansvarig_, _intaneth,
+ {{0x8a11f189,0xd2d8312b,0x2cf2822b,0xb758038c}}, // _pemberit, _nejen_, _myndband_, _विस्वास_,
+ {{0x17a7510b,0x67d800a2,0x00000000,0x00000000}}, // _opatreni, _događaj_, --, --,
+ {{0xe2da51dd,0x927e122b,0x73eb8192,0xd1660094}}, // _batean_, _einstakl, _marts_, _עירובין_,
+ {{0x0aa830ea,0x4017d062,0x00000000,0x00000000}}, // _전체적으로_, _наводно_, --, --,
+ {{0x993bd166,0x736f1076,0x4b005065,0x4cc520b7}}, // [c50] _सिद्धांत_, _akceptuj, _للايجار_, _активитэ,
+ {{0x32d8205f,0x8c07532f,0x203fb03b,0x5290b0aa}}, // _teken_, _कामसूत्र_, _historin, _bedava_,
+ {{0x0867a25b,0x92add039,0xe11a1008,0xf75a1008}}, // _április_, _الشان_, _לדיוור_, _לכיוון_,
+ {{0x02b2f06a,0x57a4313b,0xa8cc2143,0xb9dc20a3}}, // _rendben_, _胶南市乡镇及街道, _klientid, _klientie,
+ {{0x225a6144,0xb2cb7316,0xdb9401cb,0x0e72213b}}, // _spolu_, _aradan_, _similair, _遵守中华人民共和,
+ {{0x425a607f,0x837900c4,0x60e97166,0x4c5c72d7}}, // _acolo_, _zdravia_, _अधीक्षक_, _vietbao_,
+ {{0x42bde111,0x9394711a,0x8ca222ba,0xcfde1004}}, // _prodám_, _panse_, _יקותיאל_, _לובלין_,
+ {{0x3c67d11c,0xd257025b,0xcd2c7025,0xe1e5b03b}}, // _ارزان_, _múlva_, _ponownie_, _politikë_,
+ {{0x296481fd,0x2c6fc121,0x589a20ea,0x62e0c034}}, // _известно_, _loturak_, _포토갤러리_, _tecnico_,
+ {{0x21257020,0x4816f215,0xa1271082,0x4f648045}}, // _사업자등록번호_, _pareigūn, _заходи_, _utdannin,
+ {{0xa248c033,0xc2002024,0x9ac4804d,0xd354a061}}, // _sommes_, _ilkin_, _質問をみる_, _didelis_,
+ {{0x1340126c,0x8863a008,0x030300cb,0x42d84058}}, // _tabella_, _טרייד_, _valamit_, _demen_,
+ {{0xc3eb9190,0xa2d9a118,0xb60fa0da,0x73fa709e}}, // _pastu_, _invece_, _श्रावणात, _rusumo_,
+ {{0x498b0163,0xdd47b041,0x3b17b128,0xc21e0008}}, // _znači_, _dispozic, _dispozit, _שופינג_,
+ {{0xe2d841ff,0xec7690b9,0x99436033,0x23d8b0fe}}, // _nemen_, _verslag_, _imprimer_, _বিএডিসি_,
+ {{0x62fcd086,0xdeb9d013,0xa316d245,0x00000000}}, // _nzego_, _situatie_, _ageze_, --,
+ {{0x82d84323,0xac6f709e,0x22cae0f8,0xcf25b0f7}}, // [c60] _hemen_, _afurika_, _handia_, _telefoon,
+ {{0xb1e1a0ff,0x4201430e,0x02d921df,0xe0537068}}, // _niektoré_, _medios_, _inceku_, _consumen,
+ {{0xc3f46259,0x12fc908d,0x670fb259,0x3218b050}}, // _curtha_, _izaga_, _institiú, _потребни_,
+ {{0x69e240cd,0xc8606124,0xeaf85040,0x643fe0c3}}, // _पट्टी_, _banaadir_, _aktuelle, _ефектив_,
+ {{0xf2d830c4,0x247c30aa,0xe5bae098,0x00000000}}, // _odmena_, _genellik, _přičemž_, --,
+ {{0x4303a2a8,0x00000000,0x00000000,0x00000000}}, // _magamit_, --, --, --,
+ {{0x49cc11ce,0x5be75089,0xa81dd07b,0xc387e03e}}, // _rendelke, _assessor, _বগুড়া_, _betra_,
+ {{0x0386d24a,0xc3f47035,0x00000000,0x00000000}}, // _peoria_, _sastra_, --, --,
+ {{0xab56d083,0xd27e01df,0xbac71193,0x42027148}}, // _sentiasa_, _unina_, _prilično_, _risico_,
+ {{0xfc6bd036,0xdff5438d,0x727ed1df,0xa225224a}}, // _означава_, _proteína, _mnene_, _hickey_,
+ {{0x432f51fc,0xaaf230d9,0xf2078160,0x6069b12b}}, // _təyin_, _priredit, _priontái, _podprsen,
+ {{0x2dc41008,0x5386d1a3,0xd2495002,0x29dfa124}}, // _באפריל_, _ofert_, _olemas_, _booliska_,
+ {{0xa2d8525b,0xa3eb8008,0xe2927037,0x82d821be}}, // _jelen_, _parts_, _ibrani_, _bekee_,
+ {{0x42d850f7,0x525c60c2,0x95a391aa,0xe2d4630c}}, // _delen_, _località_, _победу_, _verdad_,
+ {{0x63877219,0x4387e03e,0x5dc530d8,0xc1de6047}}, // _zdarma_, _betri_, _suriname_, _chinntiú_,
+ {{0x27db2270,0x54fbd096,0x5b5a7090,0xd349f0c4}}, // _टीआरपी_, _कैलाली_, _mendimin_, _stretli_,
+ {{0xb2908084,0x434370ea,0xa3fa70fc,0x00000000}}, // [c70] _baharu_, _머니투데이_, _kasuan_, --,
+ {{0x240ef031,0xc27f10ff,0x4cd6c173,0xc2cae246}}, // _比上年增长_, _mozno_, _забрала_, _sandan_,
+ {{0x6000d220,0xd90e20cc,0x92cae1ac,0x6c2d5205}}, // _informāc, _וואלוטע_, _pandan_, _espiritu_,
+ {{0xb24802ff,0x670dc130,0x6301836d,0xf26d918b}}, // _heima_, _кадра_, _trebate_, _vandamál,
+ {{0x93411039,0x026c211e,0xa25bf17f,0x143ec1bb}}, // _انوار_, _bakom_, _kauli_, _тайне_,
+ {{0xe6cca2c0,0xc26dd1d2,0x00000000,0x00000000}}, // _polainni, _ciwon_, --, --,
+ {{0xa2d1a144,0x18683132,0xc9f5b06a,0x00000000}}, // _človek_, _حيثيت_, _kevés_, --,
+ {{0x88480087,0x2a31512d,0x7c6831d4,0xb9483319}}, // _ağustos_, _सब्जी_, _escrito_, _escritor_,
+ {{0x72d8500c,0x92fcd0b0,0xc23fe12b,0x00000000}}, // _gelen_, _blogio_, _elektrár, --,
+ {{0x1a7212ae,0x727ed0c4,0x69aac20a,0xc6e6c175}}, // _акции_, _ikonku_, _комис_, _публикат,
+ {{0xc24862c8,0xa3eb90ae,0x7394714e,0x896601a6}}, // _veoma_, _pasts_, _bansa_, _जीवनको_,
+ {{0x6200c126,0xbe6dc100,0x3d8a71bc,0xa302c007}}, // _pedig_, _заменена_, _вицепрем, _akinkanj,
+ {{0x647550fb,0xc388000a,0x23657215,0x328380ee}}, // _disediak, _bulgarij, _mergina_, _nishani_,
+ {{0xeae1f276,0x17fc10b7,0x0bea0036,0xe2e8e0aa}}, // _pahlawan_, _алягэ_, _incontro_, _bilinen_,
+ {{0xb290402c,0xad8040d5,0x62d840b9,0xd3e0220a}}, // _ilman_, _सुस्वागत, _hemel_, _медалион_,
+ {{0xb926c182,0xb3875277,0x11a4d140,0x12fc7003}}, // _причини_, _áfram_, _nikolić_, _gangi_,
+ {{0x8ec18047,0x438ff154,0x32f081f5,0x8263c154}}, // [c80] _bronntan, _wanyama_, _abaingi_, _katoliki_,
+ {{0x152d627b,0x945e7144,0x16250035,0xd2b36022}}, // _ezintath, _prievidz, _प्राजक्त, _saudiya_,
+ {{0xc2fc71f5,0x78d31030,0x8dd6c091,0x3d8aa05d}}, // _bangi_, _विंटर_, _gwahania, _abamaaka,
+ {{0x7ad3b13b,0x53f45056,0x126e01a3,0x00000000}}, // _正在载入用户签名, _mitten_, _europy_, --,
+ {{0x8292730a,0x93949143,0x0915e1a7,0x27ae4065}}, // _durant_, _kaasa_, _verbindu, _مكافحة_,
+ {{0xa36401df,0xf26c211d,0x62926031,0x821c5052}}, // _amagama_, _takoj_, _autant_, _omuhle_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x953602ba,0x89786118,0x02c4112c,0xc87880cd}}, // _מברסלב_, _amminist, _vendredi_, _दर्दनाक_,
+ {{0x9213216a,0x82a6338e,0x630cb0d3,0xdc48d15c}}, // _archiv_, _membro_, _cosaint_, _वैमनस्य_,
+ {{0x5c6f7155,0x0b094089,0x4bffe227,0x5a474154}}, // _aburula_, _comparte, _ofensivo_, _karemera_,
+ {{0x03eb8008,0xccd340d2,0x72055154,0x00000000}}, // _party_, _verkeerd_, _mahitaji_, --,
+ {{0xd23dd017,0xc26dd1cc,0x42e0612b,0xfe4b1119}}, // _празника_, _siwon_, _jednoho_, _barakaca,
+ {{0xa2f4c11d,0xa300e04c,0x12d8009f,0x425ac068}}, // _napisal_, _pegawai_, _twiet_, _vallen_,
+ {{0xdfea41f4,0xe570619b,0xe12d10e7,0xbb2a31d6}}, // _बरदाश्त_, _میهمان_, _сајта_, _anabatab,
+ {{0x37a3f03d,0x2d021071,0x72c43275,0x47c79049}}, // _チェックリスト_, _declarat_, _menerusi_, _समूहों_,
+ {{0x622670da,0x626cb044,0x2ea14045,0x00000000}}, // _parkir_, _ricos_, _fullsten, --,
+ {{0xc2fc7038,0xb2df430b,0xf39d10b3,0x3320412a}}, // [c90] _rangi_, _melebihi_, _izdavanj, _emmys_,
+ {{0x52d870e6,0x57ab11c7,0x0dc4c034,0x02f4c018}}, // _denen_, _ketrampi, _capitale_, _capital_,
+ {{0x527e6222,0x4aea70e6,0xee392021,0x2f399018}}, // _giong_, _erstelle, _нацыянал, _תקציר_,
+ {{0x4386d086,0xfb8ac107,0x5349f00a,0x4225901c}}, // _agera_, _помош_, _insetti_, _muski_,
+ {{0x235660e8,0x93ea8090,0x9c6ef38f,0x00000000}}, // _arbetar_, _faktin_, _imorgon_, --,
+ {{0x0bb27186,0x04692050,0x827f0184,0x00000000}}, // _म्युनिसि, _conforto_, _afandi_, --,
+ {{0x627e6069,0x0facb085,0x1cf81008,0xd94cb153}}, // _xiong_, _favorito, _מתאימה_, _favorite,
+ {{0xbc89f1d6,0xc20b637b,0xc2fd129e,0x1c0ac02c}}, // _akapịbag, _vườn_, _pomidor_, _позже_,
+ {{0x593b9128,0x03966134,0x725ad096,0x89a3e02e}}, // _asemenea_, _berson_, _oceli_, _rodinnéh,
+ {{0x32905075,0xf2a71092,0x42cf1045,0x00000000}}, // _allan_, _stavebné_, _annonse_, --,
+ {{0x02b490f5,0x93ea8194,0x027f7086,0x01843088}}, // _khach_, _viktig_, _nyange_, _kinakail,
+ {{0x368ce108,0x6212d0ca,0x22489128,0x027e0119}}, // _बैताल_, _tenhle_, _seama_, _miino_,
+ {{0xb38690ed,0x00000000,0x00000000,0x00000000}}, // _suara_, --, --, --,
+ {{0x6abe825e,0xac47b2aa,0xe1e29175,0x8c601091}}, // _provvist, _hunchbac, _властите_, _cynradd_,
+ {{0xea158360,0x026f127b,0x0a17c231,0xeb1ff31d}}, // _automáti, _eminingi_, _inicijat, _penafian_,
+ {{0x440f2133,0x92a6005e,0xb394d153,0x722ad215}}, // _fearainn_, _tribo_, _press_, _позах_,
+ {{0x6e73e1f0,0x32fc705d,0x2290d214,0xd0d09065}}, // [ca0] _बौद्ध_, _mangu_, _manase_, _للنساء_,
+ {{0x726e502c,0xb6d2b076,0xfbed31ea,0xc2da7160}}, // _kotona_, _यूजिक_, _rezervir, _nasedo_,
+ {{0xaffd9071,0x27f4d020,0x5422405d,0x185140fe}}, // _военкома, _사업계획서_, _ababiito_, _পারভেজ_,
+ {{0x02f4a1e8,0x60e620cb,0xb270b056,0x00000000}}, // _velikoj_, _باشندوں_, _områden_, --,
+ {{0xb2018282,0xb4fac035,0x7585c05a,0xc9c7d182}}, // _avril_, _खादाडी_, _ipererez, _прилеп_,
+ {{0xca222100,0x5429223e,0x023660aa,0x03166038}}, // _строй_, _himpitan_, _proje_, _ngozi_,
+ {{0x1bd80390,0xa94cc068,0x696b4068,0xb7014183}}, // _अमेरिकन_, _favoriet, _groninge, _américa_,
+ {{0xf26c3102,0x00000000,0x00000000,0x00000000}}, // _tolong_, --, --, --,
+ {{0xe3966114,0x72fc91be,0x7a15e0e6,0xdc6de10c}}, // _person_, _taagi_, _verbunde, _llorona_,
+ {{0x2ff90302,0x9304f110,0xf8d8f175,0xffd5416f}}, // _अर्थात्_, _whooping_, _помогнат_, _upozoren,
+ {{0x5200b036,0xc8e692be,0x5280e008,0xd356b391}}, // _ordine_, _न्यूयार्, _variety_, _obtener_,
+ {{0xf7ac0251,0xe27ed134,0x52d87089,0x7e07d13c}}, // _colectiv, _poeni_, _tenen_, _правна_,
+ {{0xf1852293,0x82026061,0xdf19f0b3,0x7639005f}}, // _огромен_, _turite_, _navijača_, _besighei,
+ {{0x33fa7392,0xef272208,0x163e8025,0x42cab124}}, // _poruku_, _agartasa, _आईएएनएस_, _aaddan_,
+ {{0x775662ed,0xe5362021,0x13ea6047,0x52e68124}}, // _प्रमाणित_, _ілюстрац, _scoth_, _arintan_,
+ {{0x629ec1ab,0x143350fe,0xb292408d,0x32d8721b}}, // _финал_, _লিখিত_, _ncwadi_, _denel_,
+ {{0xd529d393,0x12fc726d,0x58d391b2,0x020c9036}}, // [cb0] _obasanjo_, _langt_, _attrativ, _комбинир,
+ {{0xd202f29a,0xbaf862aa,0x0c7450cd,0xc2ebc347}}, // _siromašn, _ierusale, _वैष्णव_, _mobitel_,
+ {{0x62d8720d,0xe869a175,0xb25ab055,0x100ff0cb}}, // _genel_, _книгата_, _fadlan_, _ٹوئنٹی_,
+ {{0x3181807b,0x5439c2b0,0x22bbf008,0x57535035}}, // _মুরুব্বী_, _traballo_, _powered_, _युटिलिटि,
+ {{0x8bb9e020,0xd22b3143,0x9a093070,0x93329017}}, // _스팸블로그로_, _esimene_, _درجنوں_, _културна_,
+ {{0x0394e0a2,0x726cc056,0x2eb441a6,0x00000000}}, // _zaista_, _sidor_, _posunout_, --,
+ {{0xe27e915d,0xe9de32b5,0xd2a691c0,0xff839035}}, // _giang_, _contenid, _grabe_, _एक्का_,
+ {{0x116dc221,0xd36c7154,0x6047005c,0x00000000}}, // _monitory_, _bungeni_, _रॉयल्स_, --,
+ {{0xa386d07f,0x726c11b2,0x10604061,0x7c5851a0}}, // _oferi_, _ibhom_, _prisimin, _kritiek_,
+ {{0x227e7195,0x82a7d100,0xf2d8a202,0x299330cb}}, // _finne_, _работай_, _deben_, _سپیکر_,
+ {{0x32366117,0xa2fc7154,0x1a310070,0x32925154}}, // _sulje_, _tangu_, _قیصرانی_, _butare_,
+ {{0xd2bb61bf,0x1dfb603b,0xa386903f,0xd2484011}}, // _komente_, _komentet_, _starp_, _hemma_,
+ {{0x427e7054,0xd59a7007,0x52a6c074,0x8f58430c}}, // _hinne_, _abawọle_, _lambar_, _propieda,
+ {{0x0a30313b,0x4069415b,0x63ea01e7,0x7df8e0cd}}, // _的所有文字_, _kampioen, _imite_, _उन्मूलन_,
+ {{0x82d4d0fb,0xf28ad021,0x8bd8a0b7,0xc0c7c13c}}, // _memohon_, _мінет_, _транснис, _физике_,
+ {{0x524bf1cc,0x0e5f404d,0xbc56a034,0xb6e0f13b}}, // _कुठेही_, _この記事へのトラ, _partire_, _多种方式看新闻_,
+ {{0xdc6090fa,0x6295e247,0xf3958394,0x5c50c19b}}, // [cc0] _nnyumba_, _abukari_, _darse_, _زنگنه_,
+ {{0xc2710024,0x569641ea,0x536641ea,0xeb0951df}}, // _deputatl, _spremeni, _spremenj, _colporte,
+ {{0x52f91017,0x0354a1d4,0x3395c069,0xb2249055}}, // _потребно_, _podemos_, _huvsi_, _shaki_,
+ {{0x6c36e037,0x33eb019e,0x884371bb,0x00000000}}, // _تعامل_, _amatur_, _portalas_, --,
+ {{0x42d9710c,0x00000000,0x00000000,0x00000000}}, // _ghaelg_, --, --, --,
+ {{0xa204104d,0xf224c116,0x81260018,0x32a6d044}}, // _住所又は居所_, _aadka_, _ניצחון_, _probas_,
+ {{0xc6d111ff,0x439660a4,0xa3ced156,0x037b3205}}, // _probleem_, _persen_, _čovek_, _papaano_,
+ {{0x6944b18d,0x968d82be,0xf344a21b,0x63415034}}, // _herunder_, _परवाह_, _wetende_, _potenza_,
+ {{0x63f47395,0xb20111be,0xf3806087,0xca46f0b7}}, // _postao_, _lezie_, _olursa_, _алеӂеря_,
+ {{0xd31690ca,0xd9858066,0x52da60b9,0xa71fb149}}, // _praze_, _टाईम्स_, _morele_, _pěstován,
+ {{0x6b2b70ad,0x326d81df,0x11120396,0x52bb7068}}, // _komandas, _inyoka_, _družina_, _komende_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x0dfae118,0x06aa4008,0xa4864108,0x726de01a}}, // _осъществ, _פסיכומטר, _सुधारे_, _antoi_,
+ {{0x047c00d3,0xb24891df,0x89a68106,0xa39540f8}}, // _cartlann_, _kwami_, _прусия_, _tresna_,
+ {{0x9c7170de,0x73ce611d,0x3647009f,0xe213f155}}, // _डाक्टर_, _znova_, _verklari, _ahuhu_,
+ {{0xff460076,0xb7a2e0ea,0xcaeb8164,0x666440de}}, // _zakresie_, _테마스토리_, _bestemme, _viljandi,
+ {{0x1fa2206e,0x625bf1be,0x876c309d,0x67e1f099}}, // [cd0] _iṣowo_, _obula_, _wyjątkow, _pripremi,
+ {{0x1f15f22c,0x02a740ac,0x6313d047,0x00000000}}, // _sledovan, _məclisin_, _saibhir_, --,
+ {{0xdd3bd0a4,0x6ef1a201,0x00000000,0x00000000}}, // _व्यक्तीच, _marketin, --, --,
+ {{0xbfeb40ca,0x62cbf0bf,0x826d80a0,0xf187a004}}, // _विभाजित_, _abuda_, _sirol_, _אָנערקענ,
+ {{0xd2027154,0x5202600e,0x4d4710ea,0xeb2120cb}}, // _aprili_, _kiriko_, _전문가서비스_, _keretébe,
+ {{0xee8f6347,0x92d4630a,0x5e7cb0de,0x739401e3}}, // _promijen, _perdre_, _बाक्स_, _uyise_,
+ {{0x82d8c149,0xeb6310f5,0x901a1031,0x7307f277}}, // _leden_, _doreamon_, _服务已开通_, _endaði_,
+ {{0x2df02070,0xa95a6100,0x053150a2,0xab41d13a}}, // _ونڈوز_, _намаганн, _pročitat, _katoličk,
+ {{0x7ae52342,0x00000000,0x00000000,0x00000000}}, // _कृष्णदेव_, --, --, --,
+ {{0xe280b1a6,0x728c111c,0xa772c13c,0xb15b7068}}, // _diskuse_, _آلوما_, _свега_, _originel,
+ {{0x52bb6015,0xb4b2600f,0xa22ac100,0x9c2a5034}}, // _somente_, _पिंडी_, _богам_, _compless,
+ {{0x02484070,0x16992117,0xb2e860c4,0x00000000}}, // _semmi_, _профилак, _sofinka_, --,
+ {{0x51f62035,0xf3a2e09f,0x12d8a124,0xa27f41c6}}, // _टोमॅटो_, _egipte_, _hebel_, _agensi_,
+ {{0x8292402d,0x5ee86031,0xd84a12da,0xcb614065}}, // _datang_, _城乡规划法_, _горка_, _جلابيات_,
+ {{0xb8475397,0x525ae0f8,0x027f5190,0xb26c705e}}, // _duplicad, _mailan_, _sienas_, _manok_,
+ {{0x8e3251b2,0xa31960eb,0xdb1fc208,0x00000000}}, // _amelikas_, _христово, _ammannan_, --,
+ {{0xa2d8c20d,0x217da004,0x563ce1b2,0x425a90b5}}, // [ce0] _neden_, _פיגור_, _arianism_, _obaly_,
+ {{0x327f502f,0x85ee3003,0xd2f450a2,0x1d85b00f}}, // _vienas_, _landslið, _zapošlja, _बायोग्रा,
+ {{0x727ff153,0x12e560cb,0x22f22003,0xe48d50ea}}, // _found_, _اعتکاف_, _ákveðið_, _인테리어소품_,
+ {{0x7b7340e4,0x352f41e1,0x4fdb60ea,0x00000000}}, // _अप्रकाशि, _disposto_, _소비자보호에_, --,
+ {{0xc2cae06e,0x52009133,0xc20260aa,0x2e0bc0da}}, // _dandan_, _amais_, _suriye_, _खरोखरच_,
+ {{0xe290a177,0x6032f02e,0xc3567387,0x5f943021}}, // _alban_, _nastaven, _acceder_, _плоская_,
+ {{0x9386e113,0x52ea7241,0x620090a0,0x9292703f}}, // _leiria_, _poginuo_, _blaid_, _atrast_,
+ {{0x23f8512c,0x9db141ee,0xbc7f5233,0x7096919b}}, // _celui_, _lenguaje_, _versão_, _پاشایی_,
+ {{0x37158004,0x1e761004,0x5a15d100,0xb3172018}}, // _שושנת_, _מספריהם_, _куплена_, _planning_,
+ {{0x9e1bc202,0xa9dbc089,0x737fd055,0x00000000}}, // _pregunta, _pregunte, _kulanka_, --,
+ {{0x3c604024,0x6c723018,0x12d772d7,0x9201b0ae}}, // _abdulla_, _stories_, _autopro_, _diviem_,
+ {{0xf2f50190,0x7200a04b,0x627ed2d7,0x527940fe}}, // _pavisam_, _ambit_, _mieng_, _নীড়পাতা_,
+ {{0x97f27144,0xd3bab11c,0x696ca2c0,0x1e84302a}}, // _možnosť_, _هرمزگان_, _eilimint, _власне_,
+ {{0xa96c50c3,0xb9d8f099,0xeefbd13c,0xf7414018}}, // _societat, _izložba_, _ствари_, _ואביזרים_,
+ {{0xe3877081,0x3d94a0cb,0x5c61614f,0x00000000}}, // _starta_, _keresett_, _matunda_, --,
+ {{0x2b4c220d,0x926c205e,0xd27f8022,0xd26c1119}}, // _numarası_, _takot_, _borno_, _kahor_,
+ {{0xf27f711a,0x269b2062,0x5759f143,0x00000000}}, // [cf0] _chanje_, _активне_, _भिक्षुक_, --,
+ {{0x96823092,0xd342a047,0x02926037,0xf7b5b0b3}}, // _decembri, _inneall_, _marang_, _najstari,
+ {{0xb3eb8221,0x22eef21b,0x906cf031,0x47e3d02c}}, // _karty_, _phantsi_, _第三十四条_, _универса,
+ {{0xf6c3a0ad,0x7a03a042,0x2402e106,0xbc433239}}, // _portalı_, _paralyze, _стема_, _netwerk_,
+ {{0x422ab0c6,0x73eb71dd,0x53f870f6,0x926e7347}}, // _někdo_, _esaten_, _menua_, _europi_,
+ {{0xbd81506a,0x5395f02c,0x0c76c054,0x00000000}}, // _hallotta, _kuusi_, _नृत्यं_, --,
+ {{0x42009134,0xa32090bf,0x4abca017,0x37cbf0da}}, // _plaid_, _alaye_, _министар_, _लोकमान्य_,
+ {{0xf27f5061,0xf3169052,0x02d8c1ea,0x026c3039}}, // _dienos_, _ngazo_, _teden_, _lajos_,
+ {{0x80e64025,0x83052037,0xdf6900cd,0xf202619b}}, // _आईबीएन_, _mataram_, _लाभदायक_, _jaring_,
+ {{0x22fc700c,0x2200b08c,0xe2d8c0f7,0x185d907f}}, // _hangi_, _addini_, _reden_, _минерале_,
+ {{0x5fc39153,0x6dd6e01a,0xe26dc2c6,0x9aaa72a5}}, // _register_, _perjanta, _nivou_, _מדרבנן_,
+ {{0x1807c070,0x3248003e,0xe2fce0dc,0x00000000}}, // _لکھتے_, _heimi_, _mungon_, --,
+ {{0x72fc705d,0x43fec21f,0xe2905091,0x083e101a}}, // _mangi_, _kwalifik, _allai_, _диски_,
+ {{0x62fc709c,0x22fce089,0x9f0711eb,0xd681612c}}, // _langi_, _origen_, _маразм_, _calendri,
+ {{0xeb6cb0c3,0x18674303,0x1f128190,0x63ced092}}, // _министру_, _solicitu, _organizā, _slovan_,
+ {{0x529262c3,0x22027141,0x7c5370c9,0xbc115090}}, // _barang_, _masing_, _sastava_, _sarandë_,
+ {{0xa3b75017,0x93f8f090,0x00000000,0x00000000}}, // [d00] _zatvora_, _punuar_, --, --,
+ {{0x126c9250,0xfd2c911a,0xcaa9c2be,0x616c90b5}}, // _aktiviti_, _aktivite_, _रामलीला_, _aktivity_,
+ {{0xd2cf9144,0x127ed069,0x42c4a018,0x00000000}}, // _kancelár, _xieng_, _england_, --,
+ {{0xf27ed1b2,0x13035043,0x5fb3d07f,0x3c6ba070}}, // _vieng_, _nyablab_, _антилеӂи, _magukat_,
+ {{0xb290703c,0x420b004b,0xad6bb1d3,0x08fd7035}}, // _ponad_, _emphysem, _प्रभुजी_, _urbanind,
+ {{0xae72223c,0x02027147,0x00000000,0x00000000}}, // _तुच्छ_, _kasing_, --, --,
+ {{0xd2dd2122,0x00000000,0x00000000,0x00000000}}, // _katerimi_, --, --, --,
+ {{0xac74d34e,0xe3869153,0xa19d9117,0x4c34d173}}, // _minutos_, _start_, _наиболее_, _цяпер_,
+ {{0xb200d0b9,0xb27e7108,0x92d8f045,0x0386007e}}, // _klein_, _sinna_, _legen_, _asire_,
+ {{0x3373c01f,0x33294082,0xbc3a30b4,0xf3f47091}}, // _nekustam, _запропон, _elaborac, _ofsted_,
+ {{0xb3ce612b,0xbc7380b3,0x72ab10f2,0x05b8c2de}}, // _znovu_, _emisija_, _kandang_, _bankovní,
+ {{0xe27e718b,0x5adb80e6,0x32cae091,0xc354a171}}, // _vinna_, _bestimmt, _ganddo_, _modelos_,
+ {{0x0386b1db,0x0b7aa19f,0x32a781d8,0xabdd6049}}, // _sucre_, _uğrunda_, _verby_, _दतिया_,
+ {{0xd27ed0f5,0x828d7074,0x087fb073,0xeee82117}}, // _tieng_, _turkiya_, _शब्दयोग_, _метров_,
+ {{0xa35e9063,0x678530ca,0xb122d276,0x00000000}}, // _trafiki_, _आह्वान_, _مدافع_, --,
+ {{0x3292605e,0x925ca11c,0x87e60398,0xb27ed15d}}, // _parang_, _اتوبوس_, _अनार्य_, _rieng_,
+ {{0x03646025,0x10d7709d,0xaf7ab041,0xde85816b}}, // [d10] _ग्रैंड_, _motocykl, _upozorně, _दिव्यदृष,
+ {{0x9386d1f5,0xe38691d7,0x972c2399,0xe4edf03c}}, // _ngeri_, _fuara_, _responsá, _मध्या_,
+ {{0xc25b417a,0x52db9071,0xcdf2c1eb,0xe2ab1147}}, // _quello_, _контакту, _джорджа_, _bandang_,
+ {{0xe394e055,0x12cfd062,0x326de1e7,0x00000000}}, // _amisom_, _takodje_, _ditou_, --,
+ {{0xd39580aa,0x21332092,0x9386d1cf,0x510a801a}}, // _varsa_, _kvalitné_, _quero_, _означает_,
+ {{0x4c9e12d0,0xf2fe4044,0x93f472b1,0xcf57417b}}, // _תוויות_, _motivos_, _postel_, _damascen,
+ {{0x52ff514b,0x1d8761ab,0x00000000,0x00000000}}, // _verilir_, _inserire_, --, --,
+ {{0xc3a8a2c3,0x4c55b13d,0x3ecba19d,0x00000000}}, // _sekarang_, _afstand_, _menyampa, --,
+ {{0x753a9024,0xfd027089,0xa290e0e6,0x3eb04276}}, // _agentliy, _qualitat_, _hinaus_, _البلاغه_,
+ {{0xe2f041a9,0xd349e00a,0x0a9040c5,0x62d66210}}, // _poliisi_, _diretti_, _poliisin_, _redovno_,
+ {{0x831691e2,0xbd687004,0x7e1882d8,0x09aac179}}, // _arazo_, _דערזען_, _posetila, _логин_,
+ {{0xbf71f037,0x92d8c122,0xf83e5065,0x037a0074}}, // _perancis_, _telesa_, _القحطاني_, _jiragen_,
+ {{0x5dc6d19b,0xc238202c,0x22fc31b1,0x00000000}}, // _ناوبری_, _блондинк, _ualicas_, --,
+ {{0x23ead0d3,0x32927102,0xeb31919b,0x00000000}}, // _contae_, _pasang_, _سمینار_, --,
+ {{0x2956006a,0x00000000,0x00000000,0x00000000}}, // _ٹورنامنٹ_, --, --, --,
+ {{0x527f501e,0xddbd50bd,0x1c5592d7,0x8e974279}}, // _tienes_, _kapasite_, _wattpad_, _systemet_,
+ {{0x4b0cc03d,0xe7d0c0a4,0x23f45068,0x5263c11d}}, // [d20] _友達に紹介_, _lingkung, _zitten_, _evropski_,
+ {{0xceee0175,0xc94890cd,0x00000000,0x00000000}}, // _компании, _जालस्थल_, --, --,
+ {{0x325620e7,0x0e46b271,0x00000000,0x00000000}}, // _становни, _modalida, --, --,
+ {{0xe2e3d2aa,0x22d85076,0x8b50502c,0xc26e11ea}}, // _lehrbuch_, _celem_, _postissa_, _kuponi_,
+ {{0xa2cae0e6,0x039461e3,0x27937054,0x5e643019}}, // _findet_, _nkosi_, _kultuuri, _públicam,
+ {{0x129040b9,0xbe67f173,0x37e5503e,0xdb0270a0}}, // _almal_, _шахматы_, _samstarf, _lawrlwyt,
+ {{0x375fa143,0xac51c16f,0x82d8c27f,0x02fcc2b4}}, // _विद्याक_, _spotova_, _selesa_, _blogue_,
+ {{0x90c7603d,0xd27ee055,0xf20180b9,0xd26c514e}}, // _一覧を見る_, _diinta_, _berig_, _halos_,
+ {{0xb3f45077,0x637b71b3,0xb26c435d,0x9aeb7008}}, // _sitten_, _intacto_, _vamos_, _instrume,
+ {{0x3b0410cc,0x2a8bb081,0xbbb79070,0xeed741a3}}, // _נאכאמאל_, _trenings, _کیلنڈر_, _delikatn,
+ {{0x6fe940de,0x826fa1e3,0x9aec00f8,0xd073907e}}, // _संचालित_, _amaningi_, _kolektib, _sweatsho,
+ {{0x73169015,0xc2fe6171,0x2b6841b7,0x069d30ea}}, // _prazo_, _visitas_, _palestin_, _해피선데이_,
+ {{0x6291b086,0x52d85070,0x7234a060,0xfda74141}}, // _ndwara_, _velem_, _ajumose_, _kelihata,
+ {{0x83946086,0x73f86058,0x53234134,0xfb8fe213}}, // _cyose_, _gwoup_, _mercher_, _glengarr,
+ {{0xded080b2,0x78d4b132,0xcba9300c,0x00000000}}, // _cuối_, _انحطاط_, _markalar, --,
+ {{0x1ae6316a,0xffdfb008,0x5a1b4080,0x00000000}}, // _यद्यपि_, _מצאתי_, _absolútn, --,
+ {{0xec09b11d,0x12d84232,0x32b58268,0x726c5042}}, // [d30] _podpora_, _demek_, _barco_, _balos_,
+ {{0x827e3053,0x932070aa,0x00000000,0x00000000}}, // _bijna_, _konya_, --, --,
+ {{0xe387e32e,0x5eb7b126,0x068a115e,0x993320cb}}, // _petru_, _البلاغ_, _akadēmij, _شافعی_,
+ {{0x211b511c,0xcaea700c,0x5a28d19b,0x86b120cd}}, // _سرپرست_, _destekle, _نتايج_, _डकैती_,
+ {{0x73946245,0xbb593136,0x05793136,0xb3b31017}}, // _byose_, _iskustva_, _iskustvo_, _културни_,
+ {{0xcc66d387,0xc395807a,0x52d4600c,0x2a6c303e}}, // _centros_, _garso_, _birden_, _heimilis,
+ {{0x3200e158,0x0387b213,0x59c62018,0x00000000}}, // _abiiko_, _lawrie_, _ריאליטי_, --,
+ {{0x9150a076,0x3291f064,0x77b0a076,0x4395f1a7}}, // _podstawo, _aduan_, _podstawi, _hause_,
+ {{0xf2d850c4,0xc26c3089,0x7c741004,0xe6b84076}}, // _celej_, _rajoy_, _באריכטן_, _raportuj_,
+ {{0xce741004,0x0d1a024d,0x2b15808b,0x00000000}}, // _חנוכּה_, _cudromac, _bielizeň_, --,
+ {{0x2e4c4113,0xa386023f,0xbc70d1b9,0xffc8b05c}}, // _haaaaaaa, _daire_, _हेक्टर_, _समलैंगिक_,
+ {{0x426cd08e,0x2bebe386,0x4b97706a,0x83f4712e}}, // _pomoč_, _komentir, _ماہرین_, _dostal_,
+ {{0xc38601cb,0x0dd40008,0x7c74a13b,0x7d2601e6}}, // _faire_, _האשכול_, _查看评论信息_, _редакції_,
+ {{0xbc47c215,0xf20030a2,0xa6e7c062,0xb2e7c1b8}}, // _разгон_, _kojim_, _разлог_, _разное_,
+ {{0x129101f5,0x1ff4709d,0x527f6089,0x00000000}}, // _embala_, _ब्राजील_, _signes_, --,
+ {{0xa248d065,0x05c7711d,0xbc1d7140,0x03878090}}, // _seomra_, _oblačila_, _doprinos, _kurre_,
+ {{0x8c068219,0x7f6ea0b2,0xa696207b,0x309320fe}}, // [d40] _dopravy_, _giềng_, _বরগুনা_, _শিমুল_,
+ {{0x9fd99036,0x00000000,0x00000000,0x00000000}}, // _принтира, --, --, --,
+ {{0x62f20144,0xd8a3c218,0x03eb818e,0x00000000}}, // _nasleduj, _команду_, _payton_, --,
+ {{0x137c6044,0x1b78e189,0x77a612d7,0xa2d4d060}}, // _alcalde_, _berusaha_, _vietgiai, _aimokan_,
+ {{0x71773100,0x6e075208,0x32b69050,0x8291b0bd}}, // _белта_, _meadhana, _планирањ, _lavant_,
+ {{0x02f7631a,0x0f24c0da,0x00000000,0x00000000}}, // _menikah_, _जवळजवळ_, --, --,
+ {{0xdd66c0e7,0x231c70fe,0x7743d01a,0x92e9f07a}}, // _критику_, _বলিউড_, _новинки_, _француза,
+ {{0xa9dff0cb,0x2a37607b,0xa40dc065,0xa2497122}}, // _augusztu, _চালিয়ে_, _الضحك_, _znamke_,
+ {{0x2f45d2a4,0x12980018,0x227e91ab,0x83c410fe}}, // _najljepš, _הגירסה_, _siano_, _ব্যতিক্র,
+ {{0x154d8080,0xe27f8316,0xc54e0035,0xd23660a2}}, // _prevádzk, _biznes_, _कुठेतरी_, _broja_,
+ {{0xe33c4020,0xd30ca05e,0x35ab007b,0x4d11d037}}, // _사람들에게_, _madalas_, _উন্মুক্ত_, _اخراج_,
+ {{0x9c2c801a,0xb290e163,0xdf16213b,0x00000000}}, // _полицейс, _sinaju_, _买卖通档案_, --,
+ {{0x62a6c16d,0xa7bc51f8,0x42ee6134,0xb5059008}}, // _tomboy_, _forretni, _economi_, _טורבו_,
+ {{0xa36bb070,0xffd952ac,0xad76319b,0x3ffa4291}}, // _بھرپور_, _dostatoč, _هرچند_, _बिरादरी_,
+ {{0x22ab1185,0x5e62d1ee,0x322591c0,0x627ec055}}, // _bandung_, _descarga, _pasko_, _cidna_,
+ {{0xdccda175,0xd08e1330,0x35fa7031,0x8290a0d3}}, // _читатели_, _פוסקים_, _劳动和社会保障部_, _tobac_,
+ {{0x42f62133,0xd378b051,0x3af3604f,0x82da7008}}, // [d50] _briseadh_, _kokarin_, _consulte, _direct_,
+ {{0x027f50a3,0xbb4fd2d1,0xacf97030,0x2b541008}}, // _dienas_, _premijer, _अभिमानी_, _הרישוי_,
+ {{0x7cc1712f,0x69a69031,0x62ad3071,0xa26cc1df}}, // _اليدوية_, _尊重网上道德_, _сигур_, _kolose_,
+ {{0xe27e71e0,0xd380108e,0x4c9250d5,0xbf69b24b}}, // _rinne_, _naprej_, _संभोग_, _nezavisn,
+ {{0x7291225b,0xe8c6305a,0xf98110cb,0xf27ec39a}}, // _olyan_, _abaturag, _مرتکب_, _endnu_,
+ {{0xa2005105,0x5248510a,0xea5d212f,0x7d30a06e}}, // _illik_, _helmi_, _tapúla_, _akinyele_,
+ {{0xad8911ea,0x82bbd0b9,0x2c6e112f,0x00000000}}, // _primerja, _geheime_, _buartha_, --,
+ {{0x56410193,0x834520cb,0x727f91e8,0xc200c1dd}}, // _zanimlji, _mindent_, _bosni_, _polita_,
+ {{0x27fea031,0xb2c4a18d,0x742cf39b,0x3f128190}}, // _的所有作品_, _normalt_, _बीरबल_, _organizē,
+ {{0x62d811bf,0xcbb47144,0xe35152f5,0xaf5891b8}}, // _behet_, _reagovať_, _हथेली_, _проголос,
+ {{0x71fa303d,0x5f640013,0x43f9603b,0x5248f19b}}, // _詳細はこちら_, _belastin, _sigurt_, _dekorasi_,
+ {{0x87e0205c,0xe2e880b5,0x2c610189,0x00000000}}, // _फेवरेट_, _podniku_, _berusia_, --,
+ {{0xe2cad070,0xd366a037,0xb3340018,0x2f0ef222}}, // _mindig_, _tunggal_, _באריאל_, _chạnh_,
+ {{0x923171df,0x00000000,0x00000000,0x00000000}}, // _thumela_, --, --, --,
+ {{0xbb2dc19b,0xac5322be,0x2cec41eb,0x529261e8}}, // _tambahan_, _पैकेज_, _колыбель, _zgrade_,
+ {{0x9f3f4088,0x0eb45190,0xe034506a,0x85a1d035}}, // _kristiya, _receptes_, _receptek_, _सरबते_,
+ {{0xc5a8c24b,0xa20030a2,0x2af4c239,0x0386b0c3}}, // [d60] _preporuk, _kojih_, _hieronde, _andrei_,
+ {{0x23860247,0x52f7407b,0xa354a008,0xe38691e2}}, // _oriri_, _কখনোই_, _effects_, _ugari_,
+ {{0xc39460d3,0xbc6a21c0,0x81123191,0x02a83276}}, // _phost_, _darrell_, _pravdepo, _متوقف_,
+ {{0x327ed03f,0x251650da,0xbb8e5018,0x27a351bc}}, // _piena_, _शब्दात_, _אסיאתיות_, _внатрешн,
+ {{0xd2eb406e,0x1fb230ef,0xcac03017,0x6e1b6074}}, // _mahidol_, _مدفون_, _обично_, _danganta,
+ {{0x927ed100,0xdf6ea048,0x31c37097,0x62b64007}}, // _viena_, _kiểng_, _nastavak_, _adadale_,
+ {{0x92fc909c,0xb2cae0d3,0x127f00e9,0x00000000}}, // _mbaga_, _maidin_, _standi_, --,
+ {{0x3f0ef37b,0x0aaf30ea,0xec78228c,0xa34ce025}}, // _thạnh_, _정상적으로_, _persoal_, _साइंस_,
+ {{0xb074703d,0xf3dc9110,0xe2fc61a9,0xa2b6735d}}, // _redaktio, _obawa_, _mbogo_, _marcas_,
+ {{0x561ee27b,0xd2905055,0x88d63004,0xbccc0037}}, // _njengesi, _kulankii_, _סבֿיבֿה_, _ایلنا_,
+ {{0x4290716e,0xe8d0517c,0xb22590de,0x126cc03a}}, // _ionad_, _पश्चात_, _raske_, _colore_,
+ {{0x1840d047,0xd3eb7119,0xc26d8070,0xcf162061}}, // _بخصوص_, _haatan_, _piros_, _застой_,
+ {{0x9225918c,0x42fdf10c,0x13b0a037,0xaa34104d}}, // _paske_, _diugh_, _القاعده_, _マンション_,
+ {{0x62bf0039,0xda77006c,0xf0f6f175,0x32805076}}, // _اموات_, _adegbola_, _единство_, _brakuje_,
+ {{0xa5ee2341,0x53f8c00b,0x7f20e0b5,0x4e3941c9}}, // _condició, _solusi_, _zateplen, _esportes_,
+ {{0xeb3690a9,0xf290206e,0xc7e7e23c,0x3582f10d}}, // _isključi, _lokan_, _सिगरेट_, _प्रदेशाध,
+ {{0x6d8ac194,0xf761c117,0x7564212f,0x130761a2}}, // [d70] _ввести_, _вверх_, _thiomáin, _tembang_,
+ {{0xc395410b,0x5d0ea056,0x1b6390c6,0x8c001018}}, // _miesto_, _otroligt_, _एकजना_, _שמישהו_,
+ {{0x68645034,0xe8d96020,0x7fe801a3,0xf3f470e8}}, // _универси, _해외부동산_, _विचारों_, _listan_,
+ {{0x226d9173,0x727ee117,0x17556122,0xc2d8f17e}}, // _visos_, _toinen_, _učinkovi, _legea_,
+ {{0x427e7054,0xada6c1af,0x6d1ae0fe,0x00000000}}, // _linna_, _планинат, _মালয়েশিয়, --,
+ {{0x880800e0,0xdeab9011,0xe5187118,0x1e390165}}, // _dabqhuas_, _rekommen, _картинка, _separata,
+ {{0x0201c247,0x5349f099,0x12bf1065,0x69bc81d5}}, // _devid_, _spremni_, _دمياط_, _apịnye_,
+ {{0x4dbe113f,0x8e327185,0x5e7c109d,0x2d1c10b7}}, // _hdvietna, _kualitas_, _मौर्य_, _алфел_,
+ {{0xf7cba318,0xb27e00bc,0xc84740d7,0x86d21025}}, // _skupštin, _niini_, _captures_, _श॑शित_,
+ {{0x02cae053,0x1d8970f1,0x4df4202a,0xab9cc249}}, // _zonder_, _seumpama, _екскурсі, _lantmäte,
+ {{0x937b01df,0x527862b7,0x00000000,0x00000000}}, // _intando_, _ubunge_, --, --,
+ {{0x91456008,0xdeb56280,0xf26da0db,0xe62d8160}}, // _especial, _especies_, _tipos_, _sainchei,
+ {{0x601f30a7,0x4a59802b,0x731110b7,0xfd0760ea}}, // _najboljš, _nereikia_, _абсолв_, _쇼음악중심_,
+ {{0xf24750da,0x93169044,0xf310509d,0xe27e7081}}, // _जंगलं_, _praza_, _pracuje_, _finna_,
+ {{0x79da8047,0x9e41e142,0xb80a200d,0xfffa2143}}, // _upalenia_, _garantit_, _electrog, _elektroo,
+ {{0x92efc0c7,0xe2005041,0x3291407a,0x3e1070b2}}, // _familja_, _kolik_, _vidaus_, _trắng_,
+ {{0x7c7cd215,0xdbddd39c,0x6200106c,0x8fae1175}}, // [d80] _вылет_, _बतिया_, _rohin_, _новогоди,
+ {{0x026c80d4,0x21862082,0x92cae15a,0x9236908b}}, // _iskola_, _економік, _sonder_, _traja_,
+ {{0xd3177037,0x82a64055,0xa3b26033,0x33074276}}, // _penyanyi_, _dambe_, _marque_, _dilatih_,
+ {{0xc5a1016e,0x904511ca,0xcc01c033,0xa2904074}}, // _الاضحى_, _estudyan, _laisser_, _komai_,
+ {{0x320270bb,0x83ac1126,0xc8c84005,0x63a82025}}, // _assise_, _éppen_, _flutning, _युटिलिटी,
+ {{0x3696712c,0xf5628020,0x223660a2,0x137920a4}}, // _accessoi, _때문입니다_, _broju_, _lebaran_,
+ {{0xd2558144,0x63ea009c,0x00000000,0x00000000}}, // _problémo, _emiti_, --, --,
+ {{0xa386d0c2,0x63869063,0x8d99d1aa,0x00000000}}, // _storia_, _agaru_, _победила_, --,
+ {{0xcc5c507b,0x93ee0036,0x31695077,0xceb0a0db}}, // _তীব্র_, _dettagli_, _itsensä_, _diversas_,
+ {{0xe68b8041,0x8eb0e07e,0x2864e056,0x00000000}}, // _kancelář_, _tolerans_, _reinfeld, --,
+ {{0x5378512f,0x7c1410fc,0xe202618e,0x72cea35e}}, // _مواليد_, _segurtas, _purine_, _utrolig_,
+ {{0xa716704d,0x727ed100,0x720050c6,0xdb6de0ea}}, // _固定リンク_, _vieno_, _tolik_, _우리나라의_,
+ {{0xf9624239,0x23ead047,0x2248d020,0x9fc3d1fd}}, // _retourne, _moltar_, _neemt_, _подарки_,
+ {{0x9a063031,0xb3f8222b,0xee472324,0x8cf2831b}}, // _下一篇文章_, _tekur_, _localida, _bridgend_,
+ {{0x9f52b0a9,0xc9503016,0x7b803346,0x7c5bf070}}, // _držač_, _negociat, _negociac, _tartani_,
+ {{0x0f2c11a7,0x298ec071,0x62e8e1fc,0x0379e1a3}}, // _allerdin, _паник_, _polisin_, _telewizj,
+ {{0x72902078,0x39c6e049,0x5fec6092,0x00000000}}, // [d90] _tokan_, _कराची_, _dobrovoľ, --,
+ {{0x127ed100,0xf9aa1057,0x030c3147,0x3a34304d}}, // _pieno_, _mendakwa_, _malakas_, _ミッション_,
+ {{0xd25ad078,0x02490091,0x1290407e,0xe26cc061}}, // _emele_, _dramor_, _moman_, _mados_,
+ {{0x346520d4,0xab03f020,0x13171267,0xff3a200c}}, // _referenz, _일반적으로_, _buzzi_, _profilin,
+ {{0xe34981da,0xa2d8a23f,0x9863c034,0xd290a12f}}, // _aspetti_, _bebek_, _покажи_, _albam_,
+ {{0x1290909f,0x826cc01f,0x45f20153,0x9c3430cb}}, // _plaas_, _gados_, _permissi, _آرڈیننس_,
+ {{0xc24981b5,0xd3860208,0x4c5fc148,0x52a3528c}}, // _terma_, _nairn_, _systeem_, _procedem,
+ {{0xe2d980a4,0xb83e1017,0x279310c3,0xa5879070}}, // _keren_, _битка_, _софией_, _horoszkó,
+ {{0x1200403e,0x9507d0ea,0x12843174,0xf29040f9}}, // _komin_, _helemaal_, _archivo_, _koman_,
+ {{0x1e70f0ca,0x1055c0ae,0x72d980e6,0xf6c4104d}}, // _चित्त_, _sagatavo, _deren_, _おすすめ度_,
+ {{0x526cc1a2,0xa2004051,0xb195c100,0x5b27d069}}, // _dados_, _domin_, _членам_, _nawbbbbb,
+ {{0x2386d122,0x284b003d,0xc290013f,0xc386d1e2}}, // _skoraj_, _sommerhu, _uniad_, _ageri_,
+ {{0xd587702e,0x13a2c13b,0x21703054,0xce41f0f8}}, // _autorský, _propre_, _एहीसे_, _zalantza,
+ {{0x137b0175,0x1655d045,0x62ceb170,0x0225f100}}, // _citando_, _рейтингу_, _moravčík_, _lauko_,
+ {{0x4589e18c,0xff39e098,0xd3f84037,0x6a7de163}}, // _televizy, _televizn, _semut_, _oprašta_,
+ {{0xe26ce18f,0xf30c20b9,0x52d980ca,0x62840004}}, // _manoma_, _onlangs_, _beren_, _קאנגרעס_,
+ {{0xf2d991c6,0xbebcf2c0,0xaaf52134,0x0200d12c}}, // [da0] _lesen_, _chosaint_, _astudiae, _plein_,
+ {{0xd27ed036,0x398b40eb,0xc2251252,0x82e3021c}}, // _viene_, _uveče_, _saakay_, _delivre_,
+ {{0x92d840d7,0x13043088,0xf0b4c19b,0x0f81800f}}, // _temes_, _marahil_, _انیشتین_, _मक्की_,
+ {{0xd962739d,0xb27ed1ae,0xcc00b1b2,0xf2489205}}, // _व्यवस्था, _tiene_, _ameskas_, _itama_,
+ {{0x727e0114,0xc04ec0b7,0x00000000,0x00000000}}, // _china_, _афлэрий_, --, --,
+ {{0x8557d13c,0xf2d84190,0x626d10b5,0xe04b518c}}, // _приказ_, _zemes_, _sobotu_, _reprezan,
+ {{0x917fe11c,0x026e70c9,0xe0e8f192,0x2b1c727d}}, // _روشهای_, _parovi_, _oprindel, _označené_,
+ {{0x2b95724e,0xc8eed065,0x00000000,0x00000000}}, // _sevindir, _تصحيح_, --, --,
+ {{0xa3782047,0x9200c013,0xb290d24d,0x659941b8}}, // _بينما_, _nodig_, _solais_, _настроен,
+ {{0x8c18e0c2,0x92d980aa,0xcf393025,0xe2d8c144}}, // _caratter, _veren_, _बेटियों_, _sedem_,
+ {{0x7d89402a,0x12005051,0x03a2906c,0x12d99190}}, // _харчуван, _kolin_, _adapa_, _nesen_,
+ {{0xd601312f,0x298a819f,0xbdb1809e,0x5646a0ea}}, // _هندسة_, _müraciət_, _intambwe_, _터치스크린에_,
+ {{0x6d44f25a,0x7c17d045,0xd68b20fc,0x4f07110b}}, // _अतिरिक्त_, _правил_, _lizentzi, _dispozíc,
+ {{0x72d981cd,0x0c536125,0xee351248,0xaeb0b178}}, // _prendre_, _मैसेज_, _millones_, _dissabte_,
+ {{0x034980d4,0xe2c4a12f,0x3394612e,0x92d89177}}, // _esperti_, _aisling_, _akosi_, _afael_,
+ {{0x62cad26d,0xd6920073,0x927e01df,0x9a8fc1c5}}, // _mindre_, _statysty, _thina_, _demikian_,
+ {{0x0d6f31cb,0x99e93043,0xd27f010c,0x604d10b5}}, // [db0] _commenta, _mainidea_, _chanas_, _mikrovln,
+ {{0x541bc194,0xa872c2ad,0x98e2c130,0x633d1098}}, // _скільки_, _могући_, _могуће_, _dolarů_,
+ {{0xb2a6d256,0x00000000,0x00000000,0x00000000}}, // _greba_, --, --, --,
+ {{0x53f462d4,0x7af3c136,0x5273c136,0x511b519b}}, // _portal_, _napravit, _napravil, _پرسرعت_,
+ {{0x0386e18f,0xdddc3065,0x4498e1d6,0x00000000}}, // _afirka_, _الضوء_, _databeez, --,
+ {{0x77e2719f,0x04d1a03c,0xf16671ab,0x348190ec}}, // _respubli, _प्रतापगढ, _футболис, _नानाजी_,
+ {{0x8ecf91a4,0x53dcb006,0x42903050,0x650551cf}}, // _socijaln, _kosmetyk, _lojas_, _enquanto_,
+ {{0x13ead071,0x826de061,0xfb2f80c3,0x6527a117}}, // _dintre_, _kitos_, _историче_, _кажется_,
+ {{0x926c8230,0xb236707e,0x00000000,0x00000000}}, // _uskoro_, _danje_, --, --,
+ {{0x2256f290,0x5a93c175,0xe3f8d008,0x82d8526d}}, // _मानेला_, _можеби_, _groups_, _deler_,
+ {{0xb24890f1,0x8ed5b078,0xf2bb81aa,0xd344b05a}}, // _utama_, _merindin, _nedelje_, _murenge_,
+ {{0x03f8c0a9,0x33bdc163,0x8d6000cb,0x79a5e27d}}, // _odluka_, _drevnih_, _قادیانی_, _široký_,
+ {{0x0378830b,0x5bdd907b,0x5c28c12f,0x8c77a1c4}}, // _kenalan_, _ব্যথা_, _سيدنا_, _versand_,
+ {{0x33167245,0x32d991b4,0x22d8d1e1,0x724a7087}}, // _hanze_, _pesen_, _idiomas_, _vermek_,
+ {{0xbe2bd32b,0xdb99007c,0xae416082,0x5823119b}}, // _बर्बादी_, _заплатит, _relativt_, _رطوبت_,
+ {{0xe68c70ca,0x92905165,0x16e8b017,0xb201403e}}, // _बिहान_, _allat_, _карактер, _fleira_,
+ {{0x84d770cc,0x011090b5,0x026de03f,0x038770e9}}, // [dc0] _ענציקלאפ, _techniky_, _citos_, _starfa_,
+ {{0x031d72cd,0xa869515e,0xcb822034,0x75eda190}}, // _percuma_, _procedūr, _късно_, _personām_,
+ {{0x0da2b04b,0xb2902051,0x9e8381d6,0xa386b13f}}, // _terraced_, _dokar_, _akawanye_, _androi_,
+ {{0x03a63118,0xf163319b,0x341910c3,0xecd3b07a}}, // _хиляди_, _بامزه_, _престижи, _сценах_,
+ {{0x626da18a,0x7d22e175,0x32f23017,0xc9fb6039}}, // _navigasi_, _pagsegur, _letovanj, _ellentét,
+ {{0x275fa0cd,0x0a6b4221,0xbc242070,0x12b2e046}}, // _विख्यात_, _domény_, _کروڑوں_, _atodlen_,
+ {{0x0ea740dc,0xf366a05e,0x5615e187,0x00000000}}, // _mysliman, _hinggil_, _balorazi, --,
+ {{0xb23600f7,0x946e2031,0xf122d0a9,0x6347f002}}, // _prijs_, _国家税务总局关于, _privatno, _venemaa_,
+ {{0x47e760a4,0x6f39720b,0x023f706a,0x23a20060}}, // _स्वरूप_, _terutama, _kalkulát, _adipo_,
+ {{0x227e9340,0x3264710b,0x17f33002,0xd2fe62bc}}, // _khang_, _poslal_, _त्रिशंकु_, _morgon_,
+ {{0x7c48139e,0x4507b017,0x62d9f09f,0x8af9f013}}, // _अधिनियम_, _кредита_, _dienste_, _diensten_,
+ {{0xb200b0a2,0xd2979008,0x40ebb21b,0xce07d045}}, // _jedini_, _התקנת_, _olunabil, _символи_,
+ {{0x72fcc020,0xc200d035,0x42a69245,0x31e9b013}}, // _hoogte_, _miliar_, _asaba_, _assortim,
+ {{0xd27ea0a2,0xa118f031,0x6c89b192,0x5e721167}}, // _jednog_, _您的汇款已收到_, _samtidig_, _वृद्ध_,
+ {{0x1642f03d,0xacb7b14d,0xe0944031,0x02907003}}, // _最近の記事一覧_, _границу_, _互联网上网服务营, _konan_,
+ {{0x42d9602d,0xc386d008,0xa34fb20e,0xed8fb217}}, // _segera_, _sports_, _kraftig_, _kraftigt_,
+ {{0xfb77a021,0x10e0c117,0xdc6270a9,0xd3877175}}, // [dd0] _органы_, _снова_, _preuzeo_, _quarta_,
+ {{0xa905a1dd,0x631d9004,0xa394e120,0x00000000}}, // _eztabaid, _פינחס_, _consum_, --,
+ {{0x6c499045,0xc39491d6,0x99d070f8,0xd2f040a1}}, // _ремонту_, _akasi_, _telebist, _malinzi_,
+ {{0x22005136,0xa2d8c140,0xa7bbe050,0xaa33e13b}}, // _molim_, _jelena_, _останати, _请作者在两周内速,
+ {{0x34652142,0x09c52153,0x83076037,0x1ff761a2}}, // _internaz, _internat, _kembang_, _بازسازی_,
+ {{0x127b42ac,0x7290522b,0x9200503e,0x727ed100}}, // _komentár, _allar_, _allir_, _diena_,
+ {{0x41fe1153,0x3ece1033,0x626da070,0x50fc303c}}, // _environm, _environn, _napot_, _szerokoś,
+ {{0xeb369099,0x038710cc,0xd278617f,0x3044b02c}}, // _zaključa, _נייַעס_, _mbunge_, _вконтакт,
+ {{0xc2d87089,0x6fe3d070,0x4a1470a1,0x7a6bc179}}, // _gener_, _لاپتہ_, _okwagala_, _свежие_,
+ {{0xee2c81cc,0x33ac6090,0x4d0b20aa,0xd2005134}}, // _pengemba, _trupin_, _metrekar, _ellir_,
+ {{0x72eaa1d7,0xf2fcd038,0x61ef712b,0x315da120}}, // _daniell_, _mbegu_, _brigády_, _actuacio,
+ {{0x9032005d,0x00000000,0x00000000,0x00000000}}, // _abatagen, --, --, --,
+ {{0xfbf38120,0x441311af,0x72e21108,0x752980b9}}, // _celebrac, _посета_, _valitud_, _koninkry,
+ {{0x6320a09d,0xf20041c4,0x7329e232,0x00000000}}, // _podczas_, _somit_, _psikoloj, --,
+ {{0x927b4032,0xc2904133,0x72018069,0xf29260f8}}, // _comentár, _comas_, _hnyiab_, _estatu_,
+ {{0xd2caf12e,0x2646e060,0x369512dd,0x1c3e31af}}, // _pridal_, _ṣàtúnṣe_, _oddeleni, _затворен_,
+ {{0xf32062a9,0x630d21fc,0xd2011099,0xbd15302a}}, // [de0] _aloys_, _balalar_, _odbili_, _футболу_,
+ {{0x2d0931fd,0x28a92282,0x00000000,0x00000000}}, // _конце_, _stratégi, --, --,
+ {{0xfae30002,0xd897112f,0xb48821a3,0x9319a25b}}, // _postimee, _قريبا_, _gwarancj, _nemcsak_,
+ {{0x72d870a4,0x0055c148,0xcd8bd204,0x44dbe229}}, // _bener_, _verantwo, _nogometa, _останови,
+ {{0x1eb0909e,0x420181be,0x00abe02c,0x00000000}}, // _umurenge_, _deziri_, _станции_, --,
+ {{0x5eac21c0,0x79e6f01a,0x1f26f01a,0x1c00309e}}, // _cysteine_, _harrastu, _harrasta, _uhagarar,
+ {{0xe3dd80a0,0x323f902e,0xf9d9e055,0x514c0018}}, // _glywed_, _čokoládo, _muslimka_, _והצפון_,
+ {{0x92d87248,0xedb6e208,0x6716c0a4,0x6fc35143}}, // _tener_, _barrachd_, _गर्दी_, _horoskoo,
+ {{0xe9ec51e2,0x82ba81f8,0x82ea702c,0x4b5530ac}}, // _donostia_, _kodeord_, _entinen_, _normalar,
+ {{0x9aaee353,0x23f8a061,0x525b00de,0x260260c2}}, // _forvente, _nebus_, _avalik_, _bellissi,
+ {{0x5378a19e,0xb9ae015e,0x2248f0c3,0x00000000}}, // _bekalan_, _privātum, _primar_, --,
+ {{0x0395f074,0xb27ed01a,0xaa0ff031,0xde3980a4}}, // _hausa_, _pieni_, _没收违法所得_, _penasara,
+ {{0xc24990b3,0x3395f03f,0xe26d1144,0x00000000}}, // _pesme_, _kausa_, _bazos_, --,
+ {{0xe24830d4,0xf929411c,0x127ed100,0x3c891017}}, // _nemmen_, _آلومینیو, _vieni_, _различит,
+ {{0x89a7907b,0x7739c1db,0x1f33e05a,0x00000000}}, // _বগুড়া_, _esportiv, _akarenga, --,
+ {{0xd29051b5,0xf27ed0d4,0xda1620c9,0x615e0005}}, // _solat_, _tieni_, _zadataka_, _morgunbl,
+ {{0x3c694098,0xc2904183,0x92b961be,0x45c81045}}, // [df0] _kultury_, _tomar_, _adreesị_, _благодій,
+ {{0x03860158,0xe2d85048,0x02eca1df,0xc2a6004b}}, // _naira_, _telex_, _korinte_, _naiba_,
+ {{0xa73350b5,0x9a3240fe,0xa20200c2,0x00000000}}, // _absolvov, _ভিজিটর_, _capire_, --,
+ {{0x7c6bc103,0x00000000,0x00000000,0x00000000}}, // _चित्रण_, --, --, --,
+ {{0x17b3b0de,0x92a6012a,0x827e61e3,0xd409e263}}, // _प्रधानम्_, _kaiba_, _izono_, _बाबासाहे,
+ {{0x7a7f0020,0xd387519b,0x3d06029d,0xb6b6d00f}}, // _입주가능일_, _باردار_, _aparecer, _कठौती_,
+ {{0x925b739f,0xa7f951ab,0xc290c1a3,0xc5103116}}, // _maalin_, _протести, _podaj_, _boosaaso_,
+ {{0xc5a7907b,0xabdd339c,0x52da70b9,0x5373a0a2}}, // _ভৌগলিক_, _बढ़िया_, _bereik_, _domaće_,
+ {{0xcfc23070,0xf2d9c008,0x8c07b190,0x13c581ab}}, // _ناموں_, _level_, _personu_, _турнира_,
+ {{0x9481c0de,0x4a3de1b9,0xb27e7143,0xd27b40b5}}, // _बाबाजी_, _मज्जा_, _kinni_, _momentál,
+ {{0x739fc047,0xa1c531a3,0x9c76402d,0x627ef03e}}, // _إصدار_, _sklepach_, _ditulis_, _erindi_,
+ {{0x7c1ad0de,0xd27e7005,0xf2ba7147,0xb373c09e}}, // _चुनौती_, _minni_, _sandali_, _ruhande_,
+ {{0x2fc42004,0x1386c174,0x726df346,0xb2009069}}, // _מנהיגים_, _madre_, _estos_, _hlais_,
+ {{0xd2d8c039,0x98fe40ea,0x59e790bf,0x00000000}}, // _elleni_, _프로그램을_, _adegboye, --,
+ {{0x9be9023b,0xc29050d3,0xac868031,0xa248c0b9}}, // _अमेरिकी_, _solas_, _中签号公布_, _jammer_,
+ {{0x2387f2e1,0xd5f87311,0x6c1bf057,0xc1c47018}}, // _putra_, _रातोरात_, _didaftar, _האנציקלו,
+ {{0x03160150,0x5fe510ad,0x13f860bb,0x00000000}}, // [e00] _usizo_, _dostları, _lulug_, --,
+ {{0xb25a9099,0xb27ed220,0x329073a0,0xa108c128}}, // _imala_, _dienu_, _ionas_, _asistenţ,
+ {{0x226c10d1,0x46d740d9,0x75b1a008,0xd9620052}}, // _schon_, _sporočil, _בדואר_, _igenerat,
+ {{0x19e53085,0x6248c126,0xd318d057,0xb38780dc}}, // _resultad, _filmek_, _gemilang_, _burri_,
+ {{0x994e8179,0x4318f144,0xf52d5047,0xaf77b0da}}, // _mielipit, _zavrieť_, _daonlath, _होईपर्यं,
+ {{0x4249717f,0x4f16416e,0x527f000e,0x4ad8602c}}, // _kwamba_, _راسلنا_, _erangi_, _основной_,
+ {{0xe491607b,0x6e2fa173,0x7be3319b,0xd02e0046}}, // _এন্ড্রয়, _autorius_, _دامپزشکی_, _arbenigo,
+ {{0xf2bf6087,0x9386a19b,0x26213025,0xb200f08b}}, // _yardım_, _پایدار_, _logowani, _meniny_,
+ {{0x2e726389,0xb59bd3a1,0x0312b00c,0x428c8013}}, // _इंद्र_, _dibutuhk, _serbest_, _klikken_,
+ {{0x6200a136,0xe3436273,0xc27ed01f,0xc2836070}}, // _dobio_, _livello_, _ikonas_, _miskolc_,
+ {{0x9f1161ef,0x325a9074,0x1b1e506f,0xe1316271}}, // _contrata, _amala_, _понеделн, _contrato,
+ {{0xf329c0ea,0x972e40e8,0x92eb1061,0x674cc170}}, // _개인정보관리책임, _exklusiv, _turizmo_, _poziadav,
+ {{0xb2909068,0x427f0034,0x00000000,0x00000000}}, // _klaar_, _grandi_, --, --,
+ {{0xa2d9e24f,0xc8de5036,0x15d2f070,0x700a00eb}}, // _veten_, _приказки_, _újdonság, _викимеди,
+ {{0x826d10ab,0xb2d9e0ea,0x72ca015e,0x3ace2025}}, // _escola_, _weten_, _veida_, _चेयरमैन_,
+ {{0x9394d090,0xd2da6008,0x15c1a105,0x82011214}}, // _pjese_, _thread_, _sevirəm_, _dozie_,
+ {{0xa27ed148,0xf6b8d100,0x73f40217,0x6274319b}}, // [e10] _dient_, _разраста, _testar_, _توسلی_,
+ {{0xd27ed15f,0x2c7d6175,0x8d0210ea,0x00000000}}, // _vienu_, _последна, _알고싶어요_, --,
+ {{0x3290a119,0xffc7902f,0x9c6a1229,0x12fca154}}, // _toban_, _ранняя_, _helmikuu_, _harakati_,
+ {{0x84a44080,0xa1b99008,0xb290710c,0xe355a047}}, // _neobmedz, _מחלקת_, _annad_, _referer_,
+ {{0x62d8b202,0x03559221,0xcc13f017,0x6b9cf2b2}}, // _veces_, _plastové_, _културно_, _nyandiko_,
+ {{0xe5183021,0x45a08035,0x00000000,0x00000000}}, // _славянск, _वाढते_, --, --,
+ {{0x1d3f604e,0xf3f8805d,0x32cae037,0x3200a122}}, // _juridice_, _kukuwa_, _gandum_, _dobil_,
+ {{0xa37960a2,0xac5bf007,0x127ef143,0x00000000}}, // _pitanja_, _abuteni_, _prindi_, --,
+ {{0x03f87134,0xae96a153,0x42732117,0xc136a098}}, // _munud_, _students_, _tänne_, _studenty_,
+ {{0x3734003d,0xea8ff0b3,0xa2927208,0x029190f5}}, // _お役立ち度_, _odličan_, _parant_, _sosad_,
+ {{0x23167074,0xd200c1fd,0x4290722b,0x00bfc0cf}}, // _manzo_, _kodin_, _konar_, _elementl,
+ {{0xf86d2047,0xb386d0d2,0x00000000,0x00000000}}, // _scriosad, _ouers_, --, --,
+ {{0xd29070b4,0x427eb192,0xdf571215,0xe2d80018}}, // _donar_, _odense_, _merginos_, _tried_,
+ {{0x9290b018,0x87c74278,0xba07412e,0x29459039}}, // _local_, _pripravi, _pripravu, _pillanat,
+ {{0x489fa045,0xf4b111e3,0x3c1da2d0,0x925ad104}}, // _компанії_, _umnyango_, _משכון_, _amele_,
+ {{0x6cfa61aa,0x2ff1f1d0,0x96cec00c,0x441d21e2}}, // _придружи, _adúláwò_, _bulabili, _horietak,
+ {{0x2386906e,0xbe466271,0xc7bde0ae,0x4d4e5154}}, // [e20] _taara_, _finalida, _patiesīb, _taratibu_,
+ {{0xa200c333,0xf26e1044,0x927e901a,0x8290c055}}, // _hodin_, _época_, _ihana_, _hodan_,
+ {{0x975be06b,0xd3648190,0xd29072c0,0x00000000}}, // _मुस्कान_, _atkritum, _sonas_, --,
+ {{0x126e61dd,0xf54a9036,0x0c637147,0x020131ca}}, // _kirola_, _обстанов, _patungo_, _moxie_,
+ {{0x5b611037,0xb3ead015,0x7c75f349,0xea38013b}}, // _kesenian_, _voltar_, _मुक्तक_, _国家邮政局_,
+ {{0x425b0154,0xa2014061,0xb2ca0215,0x00000000}}, // _maalum_, _sveiki_, _leido_, --,
+ {{0x02cf810b,0x22499035,0xc3c08070,0x52cbb031}}, // _podobne_, _resmi_, _رینجرز_, _对最佳答案的评论_,
+ {{0x8a06d031,0x9378d00c,0x020261da,0x00000000}}, // _上一篇文章_, _tamamen_, _ferita_, --,
+ {{0x2396602b,0xd290311d,0xd3b26044,0xa2d8c1c4}}, // _verslo_, _pomaga_, _parque_, _jeder_,
+ {{0x0eae11ac,0x69dc4048,0x8983d0c3,0x92907054}}, // _syarikat_, _bikervie, _модерне_, _annab_,
+ {{0x438770b3,0x9c193092,0x3292600d,0x526cd05d}}, // _udario_, _polovicu_, _batatu_, _abanyazi_,
+ {{0x72d801ee,0x66e9704d,0x1b94d025,0x201230cb}}, // _quien_, _プレゼント_, _wrzesień_, _سپاہی_,
+ {{0x1eead11c,0xff53d293,0x0eee704d,0x80fa1076}}, // _خسروی_, _домашно_, _すべて表示_, _amerykań,
+ {{0x6038525d,0xf08ae02b,0xb347b264,0x409e804d}}, // _vestmann, _psicholo, _madeira_, _ポイント獲得_,
+ {{0x1b7bf028,0xbc56b1f6,0x1b962107,0x92c6b1e8}}, // _bangunan_, _postove_, _научни_, _poslove_,
+ {{0xe93f3077,0xb5ef3077,0xcf8f3077,0xb2df306a}}, // _kommentt, _kommenti, _kommento, _kommentj,
+ {{0x4503d126,0x9397408f,0xc9f74056,0x96c7402a}}, // [e30] _خارجہ_, _samarbej, _samarbet, _samarbei,
+ {{0x127e0177,0x146f4037,0x93f9e0bb,0xf202603b}}, // _maint_, _استعداد_, _metub_, _burime_,
+ {{0xb9f2b011,0x13860101,0xdfc5c1af,0x00000000}}, // _startsid, _shiri_, _идеја_, --,
+ {{0xb3ea0394,0x847da01a,0x1c6c307f,0x00000000}}, // _xeito_, _животные_, _forumul_, --,
+ {{0x1c6ba09c,0x52ca002f,0x007170bb,0x2adf50d1}}, // _kitundu_, _veido_, _norapamo, _eintrage,
+ {{0x66276086,0x49676086,0x52911099,0x12026061}}, // _minisiti, _minisite, _mozak_, _turime_,
+ {{0x8a9bb316,0x0a0f61f6,0x1e4f6163,0x200610cc}}, // _jurnalis, _stranica_, _stranice_, _כינעזער_,
+ {{0x42012059,0x8035a208,0x731000ef,0x00000000}}, // _orbitz_, _santayan, _észak_, --,
+ {{0x427ef034,0x3ed4e173,0xd2ef803c,0x82f0f0b2}}, // _quindi_, _socialin, _पदार्थों_, _giaitri_,
+ {{0xa9dcc03c,0x12eb01ab,0xd3eae0c3,0x023640a4}}, // _zabezpie, _diritto_, _suntem_, _namja_,
+ {{0x22d7c109,0xb18e50dc,0xdf21000b,0x2290c0d9}}, // _spiller_, _parasysh_, _kesulita, _dodal_,
+ {{0x8290a185,0x00000000,0x00000000,0x00000000}}, // _sobat_, --, --, --,
+ {{0x125a8219,0xf386426c,0xb69872d7,0x00000000}}, // _okolí_, _kamra_, _careerli, --,
+ {{0xe27e62d7,0x8afd803b,0xd2fca0a0,0x636d8190}}, // _phong_, _intervis, _wariant_, _intervij,
+ {{0xda4d4065,0xa41a008b,0xfe1541e4,0x3ed6511d}}, // _متّصلاً_, _prezeraj, _aigeanna, _jezikovn,
+ {{0x02d8f134,0xf201e012,0xc2a64022,0x125ac0f8}}, // _neges_, _altid_, _lamba_, _nullam_,
+ {{0xa7b87073,0x8af870c3,0x2c73a15b,0x83ea30e8}}, // [e40] _regulami, _regulame, _sterker_, _rejta_,
+ {{0xb7dab065,0x76bc2065,0x803903a2,0x00000000}}, // _وعندما_, _لتحميل_, _profesyo, --,
+ {{0xcc22f080,0x5aba0062,0x2a5050bf,0x00000000}}, // _skupiny_, _proverit, _agbède_, --,
+ {{0xc067211c,0xc2f3a094,0x526c302c,0x85649190}}, // _رفسنجانی_, _דרבנן_, _samoin_, _informē_,
+ {{0xf378b246,0x7290a2ec,0x52247025,0x740d03a3}}, // _lokacin_, _dobar_, _rynku_, _francouz,
+ {{0xa2a6406e,0xf6cf9206,0x5fe0c0fe,0x0f264002}}, // _jamba_, _kubadili, _নাজমুল_, _perekonn,
+ {{0xa3ea900e,0x5340e2fa,0x72a670f5,0x7b678143}}, // _amatu_, _ग्रूप_, _banbe_, _अइसने_,
+ {{0x42902163,0x78d7d0c3,0x0ac882ad,0xef7f8018}}, // _nikad_, _низация_, _ресурса_, _נמשכת_,
+ {{0x139cc03d,0x62ca00f7,0x55b68194,0xc829419f}}, // _回答した人_, _beide_, _партії_, _sərbəst_,
+ {{0x98bb1225,0x29da51e2,0xf3869055,0xd25ad11d}}, // _minutter_, _ekologia_, _saaro_, _imela_,
+ {{0x51111092,0xb2cb7158,0x51a310c4,0x8cd86203}}, // _projekty_, _ibadan_, _svadobné_, _служител,
+ {{0x12489102,0x49f94098,0xb79da0c2,0x00000000}}, // _agama_, _externí_, _costruzi, --,
+ {{0xb25a00de,0x611700ae,0x8e511084,0x3b764047}}, // _teile_, _publicēt, _kezalima, _sealadac,
+ {{0x7290d1e4,0x031ab13b,0xa2d98232,0x8ff3a0b5}}, // _sleat_, _按作者搜索_, _yerel_, _netradič,
+ {{0x7290a1d7,0x7a899175,0xa26d8098,0x00000000}}, // _tobar_, _продажба, _parou_, --,
+ {{0x32fe618b,0x530da0f9,0x116af090,0x72915075}}, // _morgun_, _lavalas_, _respekto, _enfawr_,
+ {{0xd522703c,0x9387f19c,0x53eb7037,0xc26c6069}}, // [e50] _प्रतिबंध, _suure_, _obatan_, _npoos_,
+ {{0x02c36239,0x437a63a4,0xbe29c175,0x00000000}}, // _stellen_, _zadarmo_, _немало_, --,
+ {{0x442b211c,0x4eb3225c,0xdefdd065,0x00000000}}, // _مسنجر_, _aspectos_, _الشغل_, --,
+ {{0x0b88002e,0xaa88310c,0x1290323e,0x7c5af128}}, // _nemocnic, _schottis, _anjal_, _alaturi_,
+ {{0x7207926b,0x2386d216,0x51434065,0xd3f821b7}}, // _רעננה_, _esere_, _لمعرفة_, _rukun_,
+ {{0xa321a1c0,0xe477a02c,0x00000000,0x00000000}}, // _kopya_, _проекте_, --, --,
+ {{0xe2362194,0x62a69070,0x00000000,0x00000000}}, // _mykje_, _csaba_, --, --,
+ {{0xc3ea01c4,0xf290f0a2,0x0290c061,0x203c5017}}, // _seite_, _mogao_, _kodas_, _користећ,
+ {{0x725a012f,0xe3a2906c,0x326de1c0,0x0a577128}}, // _peile_, _adapo_, _batok_, _biserica_,
+ {{0xc3fc5039,0xa4f8f081,0x2d43a1af,0xa9ff0286}}, // _aktuális_, _наступни, _можност_, _poznania_,
+ {{0x9c80c061,0xc2025327,0xbd19f098,0x53f4618d}}, // _немец_, _fotiek_, _dekorace_, _hurtig_,
+ {{0x42025199,0x4200c134,0x7749119b,0x904da17f}}, // _notiek_, _nodir_, _صبحانه_, _matumain,
+ {{0x3f12e03d,0x00000000,0x00000000,0x00000000}}, // _studeren, --, --, --,
+ {{0x13ea5199,0x5c76c102,0x6e9a607c,0x98acd04d}}, // _zelta_, _operasi_, _силиконо, _最新の日記_,
+ {{0x225ad1d5,0x12cae10f,0x926df3a5,0x129030d4}}, // _emeli_, _hendak_, _estou_, _armati_,
+ {{0x931320d2,0x5060400f,0x00000000,0x00000000}}, // _probeer_, _kristiin, --, --,
+ {{0xc291c0a9,0xceb860b9,0xbe2d902b,0x03966366}}, // [e60] _novac_, _januarie_, _японец_, _turska_,
+ {{0x435e2042,0x3ccdb0c5,0xa5c07133,0x00000000}}, // _lungsod_, _последне, _ionadail_, --,
+ {{0x9342c28c,0x22d9a090,0xc2447065,0xef21c1db}}, // _facendo_, _jepet_, _grafaicí_, _millorar_,
+ {{0x9290c175,0xc2d8e0d1,0x237a0009,0x00000000}}, // _todas_, _offen_, _seratan_, --,
+ {{0x0ee2900b,0xb4e8b062,0xf027a0cc,0x73f400a4}}, // _महत्त्वा, _планиран, _נעבעך_, _justru_,
+ {{0xbea150d4,0xafe89025,0x526da06a,0x92d920c3}}, // _fallimen, _हज़ारों_, _napos_, _printre_,
+ {{0x123a2122,0x8104807b,0xf35f5025,0x00000000}}, // _primeru_, _কাছাকাছি_, _kolejny_, --,
+ {{0xd290f0b9,0x22025304,0xcb9c2160,0x53544056}}, // _nogal_, _imtina_, _digiteac, _arbetet_,
+ {{0xf3ac50bd,0xd2d8113f,0xd3ea53a6,0xadbf3102}}, // _souple_, _nghen_, _velta_, _semangat_,
+ {{0xc25a5134,0x770d3173,0x7ae6707b,0xb2c551df}}, // _wella_, _жанры_, _ঈদগাঁও_, _ingelosi_,
+ {{0xa1deb0fd,0x706d6045,0xe2d8f1d9,0xd3eae089}}, // _понеділо, _kompetan, _spielt_, _contes_,
+ {{0x8eb6c0c5,0xa27e900d,0x5d33b26d,0x52d8f116}}, // _профиль_, _azana_, _menneske_, _laheyd_,
+ {{0x52bf11a2,0x90edb01a,0x00000000,0x00000000}}, // _املاک_, _monipuol, --, --,
+ {{0x024a71db,0xe98d9094,0xc943a02f,0x5792f19b}}, // _formes_, _וישלח_, _ансамбле, _اصحابی_,
+ {{0x824a71c2,0xf2d94190,0xd200b0a7,0x1b5a718f}}, // _normes_, _rudens_, _nuditi_, _dandalin_,
+ {{0xd38b825b,0xa2787047,0x29d080a0,0xb7951374}}, // _három_, _karnar_, _ieithydd, _testberi,
+ {{0x6201507e,0x93ce91ad,0x9741e0e7,0x92ee905d}}, // [e70] _defini_, _chave_, _основне_, _abanaki_,
+ {{0xcc3960a4,0x7290c24e,0xcbc7d045,0x7ea49222}}, // _प्रयत्न_, _onlara_, _продажу_, _giftcode_,
+ {{0xcdbc1008,0x1316605d,0x23eb0002,0xe52fa203}}, // _בלייזר_, _akoze_, _alates_, _prossimo_,
+ {{0x8e8a50de,0x33eb40b7,0xe31741c0,0x00984004}}, // _विपत्ति_, _pretul_, _namatay_, _אטמאספער,
+ {{0x29edc18e,0xe20cb0a2,0x4c0811cd,0x4316700d}}, // _disulfid, _uživo_, _fourniss, _manza_,
+ {{0xc42cf00f,0x43f831a1,0xa43c60fe,0x32d9821e}}, // _मेंबर_, _sujud_, _হানিফ_, _cerek_,
+ {{0xd2fce19e,0xc8f4502a,0x12d86134,0x9e35e05e}}, // _tangan_, _телефонн, _droed_, _karaniwa,
+ {{0x13ea003e,0x8f3503a7,0x66b500ac,0xd2ca0269}}, // _leita_, _reportar_, _reportaj_, _leida_,
+ {{0x83a2226b,0x183ce04d,0x839520b2,0x6e7d51cc}}, // _simple_, _産学官連携リンク, _đcsvn_, _मिक्स_,
+ {{0x6637a142,0xd274126b,0x3367a153,0x62feb0ad}}, // _programm_, _כניקראו_, _program_, _intihar_,
+ {{0xd3bbc047,0x234a1075,0x4c7eb065,0xcfcd1126}}, // _فستان_, _telerau_, _taistil_, _المعز_,
+ {{0xaae5113b,0x366ff16f,0x00000000,0x00000000}}, // _由工商行政管理部, _voljenom_, --, --,
+ {{0x7c50c12f,0xc3a23064,0xe25a501a,0xb26da297}}, // _عندنا_, _tempoh_, _kello_, _tapos_,
+ {{0x12e3d0bd,0x727c1062,0x037c81f5,0x4bfbe034}}, // _avantaj_, _играч_, _kitaawe_, _dimentic,
+ {{0x86bc6143,0x625a003e,0xe27f7346,0x42c94208}}, // _सत्तर_, _deila_, _blanco_, _tulloch_,
+ {{0x527e006e,0x93874024,0xfdbf90c4,0x60d1f04e}}, // _laini_, _enerji_, _rajecké_, _instituţ,
+ {{0xd15c7039,0xa3ea03a8,0xa96740f8,0x00000000}}, // [e80] _speciáli, _feita_, _kalitate, --,
+ {{0x32a6212c,0x215c7096,0x6c6e8055,0x92d8c0e2}}, // _membre_, _speciáln, _hiiraan_, _kuleta_,
+ {{0xd653d302,0x6d0cd02f,0x125bf0de,0xa2d83056}}, // _उपाधि_, _выйду_, _लिमिटेशन_, _numera_,
+ {{0x49d0d165,0xbf980004,0x9a9f10e7,0x5e9e30e6}}, // _interatt, _צענזור_, _адресе_, _gestellt_,
+ {{0xbf2a7134,0xb2c8a16f,0x00000000,0x00000000}}, // _dystiola, _oproste_, --, --,
+ {{0xa290500c,0xe26ce159,0x22c9f02e,0x92e021e3}}, // _kolay_, _abiola_, _nakonec_, _efanayo_,
+ {{0x826c9099,0xf4806166,0xb3869247,0x02901179}}, // _školi_, _ब्राउन_, _maara_, _onhan_,
+ {{0x6c7c8031,0xb236d122,0x42486326,0x00000000}}, // _贴子相关图片_, _svojem_, _drome_, --,
+ {{0x34d663a9,0x437fd119,0x2fb40041,0x82d8e031}}, // _देखिये_, _dalalka_, _říjen_, _effet_,
+ {{0x53869155,0x63ea9373,0x7afa7143,0x21c7919a}}, // _gaara_, _imati_, _tingimus, _фидель_,
+ {{0x82480245,0x00000000,0x00000000,0x00000000}}, // _arimo_, --, --, --,
+ {{0xb290002c,0x7386c310,0x0dda9122,0x13ce70ab}}, // _liian_, _cadre_, _soglašat, _canvi_,
+ {{0xb7b860f7,0xe3ea7019,0xa2ca7175,0xd387f1b8}}, // _natuurli, _venta_, _venda_, _suuri_,
+ {{0x92d83109,0x6e38207f,0x2290e133,0x80d33017}}, // _igjen_, _localita, _winans_, _гласи_,
+ {{0x468333aa,0x82d80048,0x6a6e0105,0x5413d017}}, // _विशाल_, _trien_, _fotosess, _повезао_,
+ {{0x015b108e,0xcafb1144,0x52c51002,0x60778004}}, // _podrobno, _podrobne, _milline_, _מערבֿ_,
+ {{0xf3f87087,0x42f76170,0x1428d17f,0xa201b1a0}}, // [e90] _bunun_, _pezinok_, _jumatano_, _rivier_,
+ {{0x4cfde077,0x30a621af,0x4ab04111,0x030d4025}}, // _последни, _ексклузи, _tradiční_, _posiadaj,
+ {{0xf4e0211c,0xe9b8426c,0x00000000,0x00000000}}, // _بلامانع_, _tendenza_, --, --,
+ {{0xb27fe0a2,0x9c1720b2,0x148db122,0xd36570bc}}, // _bitno_, _catphcm_, _kirurgij, _virgina_,
+ {{0xcecf70c8,0x553cb170,0x00000000,0x00000000}}, // _sociální, _polyfunk, --, --,
+ {{0x026ca219,0x00000000,0x00000000,0x00000000}}, // _školy_, --, --, --,
+ {{0x9b8ac082,0xf2ca00de,0xfdc0e2c1,0x4970e381}}, // _зобов_, _veidi_, _policaja, _policajt,
+ {{0xc2916100,0x54d743ab,0x4fb8c021,0x49d0c02c}}, // _negali_, _मामिला_, _збоку_, _tekemist,
+ {{0x127ed033,0x9f03b0a4,0x00000000,0x00000000}}, // _liens_, _निर्मिती_, --, --,
+ {{0x13004122,0xee994092,0x03f45075,0x6873c3ac}}, // _članki_, _uvítací_, _gutted_, _anonymou,
+ {{0x8484117c,0x53f86119,0xdd18318e,0x56f6832f}}, // _आँखामा_, _kulul_, _glasswar, _राजवंश_,
+ {{0x724800c2,0x99b160e6,0xa3a20024,0x227ea23e}}, // _primo_, _mindeste, _klipi_, _lajnah_,
+ {{0xeeee719a,0xe2001179,0x526dc07a,0x268d73ad}}, // _dezvolta, _mihin_, _kavos_, _बिकास_,
+ {{0x1b58421f,0x1299618a,0x973e1068,0x72d990b9}}, // _sentenza_, _paragraf_, _volledig, _resep_,
+ {{0x23ea0086,0x5f9f816a,0x1809b13b,0x00000000}}, // _ifite_, _रक्तदान_, _查看该公司所有供, --,
+ {{0x495be21e,0x7b4150d5,0x13957038,0x1200e1da}}, // _znanstve, _रामगोपाल_, _ghasia_, _muniti_,
+ {{0x80b0906a,0x6ab9004d,0xc27ed12c,0x53ea9054}}, // [ea0] _digitáli, _陽気なギャングが, _vient_, _teata_,
+ {{0x32362218,0x7a9d61b2,0x62d8c068,0x23f47182}}, // _ikkje_, _albigens, _kamers_, _listum_,
+ {{0xcae5d03c,0xa25a51cb,0x2303010f,0xdc1e1120}}, // _उद्यान_, _belle_, _pelawat_, _comptes_,
+ {{0xecd73037,0x910ff04d,0x03874068,0x00000000}}, // _اعمال_, _個人情報保護方針_, _overal_, --,
+ {{0xf20090a2,0xc26e6133,0xe84fd0a0,0xf895d207}}, // _kojima_, _baroda_, _etholiad, _странств,
+ {{0xbc7f217a,0x1ee320e4,0xc3f90061,0x0668f036}}, // _possono_, _निष्प्रा, _nebuvo_, _присъств,
+ {{0x62fcf16e,0x4d9d61fd,0xb386605a,0xed8c7278}}, // _éigin_, _sunnunta, _akora_, _točke_,
+ {{0x47853066,0x927ed1a0,0x87f851be,0x00000000}}, // _आस्वाद_, _diens_, _chawapụt, --,
+ {{0x2265d034,0xe78881eb,0x249df04d,0x19b732c1}}, // _агенция_, _калейдос, _ルディングス_, _studenat,
+ {{0xd0b0902e,0x92003250,0xa3f8d0b9,0x2b6bf233}}, // _digitáln, _enjin_, _steun_, _gostaria_,
+ {{0x87ffb108,0x568a51dd,0xf4b5c1bf,0x7a3cd1eb}}, // _ब्रह्मान, _federazi, _parashik, _чужое_,
+ {{0xb2e310aa,0xd2e962f9,0xb315a069,0x32cae0b9}}, // _halinde_, _kariera_, _pebcaug_, _wandel_,
+ {{0xd2003063,0x429071d7,0x9f486153,0x57b6510a}}, // _injin_, _annam_, _response_, _seseteng,
+ {{0x26b52351,0x851e20ea,0x435ef033,0xb25b2033}}, // _milanovi, _수령일로부터_, _recevez_, _inclus_,
+ {{0x34297099,0xf43b204d,0x3d897140,0x00000000}}, // _opravdao_, _ルマガジン_, _školovan, --,
+ {{0x82c3e341,0xaad0301f,0x00000000,0x00000000}}, // _gallego_, _konkursā_, --, --,
+ {{0xda55e1e8,0x818230da,0xecfed1f5,0xec0ed00e}}, // [eb0] _učenika_, _आचेवर_, _abatakir, _abatakis,
+ {{0xc25a5108,0xd98ba03d,0xf3f460e6,0xd394e33d}}, // _selle_, _ルアドレス_, _wetter_, _meisje_,
+ {{0x0c05f1da,0x17a3e041,0x81fa90c9,0xc3169063}}, // _tessuti_, _निशानहरु_, _trenucim, _ahazi_,
+ {{0xce18506c,0x5213400c,0x00000000,0x00000000}}, // _adódo_, _şehit_, --, --,
+ {{0x6a08e0cb,0x1e9ec215,0xf99d2060,0x63ea003e}}, // _تصنیف_, _файле_, _ọ̀rọ̀_, _neitt_,
+ {{0x03f251f8,0xe3ea0184,0xeff0b0f9,0x1f2470a2}}, // _levering_, _ufite_, _diskisyo, _našeg_,
+ {{0xa3866214,0x225e20f8,0xc7a20018,0xab6db197}}, // _choro_, _kontseil, _וקנבסים_, _kosmetic,
+ {{0xb27ed190,0xc7fcc1bb,0x8202618c,0x2c1c229d}}, // _viens_, _флага_, _dirije_, _realizou_,
+ {{0x838781bf,0xdd81203b,0xd378600e,0x876960ca}}, // _marre_, _publikua, _bagamba_, _राज्यको_,
+ {{0xde9a0020,0xe290206e,0x66d871b1,0x838601ec}}, // _브라우저로_, _nikan_, _ceebtoom_, _ikiri_,
+ {{0x113c110b,0xb2ca015e,0xe1987004,0x72595382}}, // _posledné_, _veidu_, _טײַערע_, _त्रिभुवन_,
+ {{0xc27b60f1,0xd3f401d1,0x3b26006e,0xc93c2279}}, // _persekol, _festes_, _agabageb, _sommerfe,
+ {{0xa3ea72f2,0x92d9e24f,0x92d4733d,0x12cad122}}, // _tento_, _vetem_, _verder_, _prodam_,
+ {{0x7640702c,0x02b6b1ad,0x9d3c6265,0xfc5c2054}}, // _sosiaali, _verdade_, _ब्वॉय_, _बेमेल_,
+ {{0x513c11a6,0xc9da803a,0xe25a00fd,0x97af80b7}}, // _poslední_, _preferit, _heilt_, _апаратул,
+ {{0xebe40087,0x09edb087,0xaacb70b6,0xe0b1d1ec}}, // _videolar, _peygambe, _sommaren_, _amerịka_,
+ {{0xf316101f,0xa2ef923e,0xb2a7800c,0xd386c047}}, // [ec0] _nedaudz_, _nisfu_, _darbe_, _madra_,
+ {{0xc3958003,0x6394e07f,0xe25a9134,0xbfab3065}}, // _fyrst_, _exista_, _deall_, _وأنتم_,
+ {{0xd3869074,0x5001e0ae,0xd2b4e1ae,0xd2a78123}}, // _shari_, _fotogrāf, _inicio_, _farbe_,
+ {{0x93f8d0dc,0x2ebdd0c3,0x53ca6070,0x0d0b20c3}}, // _kaluar_, _evenimen, _پوزیشن_, _intrebar,
+ {{0xb9d22130,0x689420c4,0x2061208e,0xcd3ea041}}, // _neregist, _prevodov, _enostavn, _navigace_,
+ {{0xdc6f1133,0x0db7407b,0x4813a008,0xc290217f}}, // _clarion_, _মেহেরপুর_, _קראתי_, _kikao_,
+ {{0x92ca720d,0xb29030f9,0x7278603e,0x00000000}}, // _bende_, _kijan_, _stunda_, --,
+ {{0x23869216,0x629191a1,0x43ea7127,0x00000000}}, // _okara_, _rosak_, _xente_, --,
+ {{0x23ea704f,0x93051039,0x727e012f,0x7eb0b0af}}, // _vente_, _رومان_, _caint_, _težave_,
+ {{0xc26de056,0xc75ec149,0x62af0099,0x00000000}}, // _dator_, _चार्ल्स_, _dosadašn, --,
+ {{0x172721a2,0x02a690f4,0x43869063,0x52cb3111}}, // _عروسی_, _akaba_, _akara_, _mladé_,
+ {{0xf20270a4,0x9fd85098,0xabe2e04d,0xbb2a400e}}, // _berisi_, _odstraně, _が参考になった_, _ababodab,
+ {{0xdb0c10cc,0x49d221ff,0x7e96307a,0x8303408d}}, // _אלימלך_, _geregist, _čempiona, _labantu_,
+ {{0xb25a909c,0x00000000,0x00000000,0x00000000}}, // _kwali_, --, --, --,
+ {{0x32fdf2a9,0x030e4246,0x025a50a6,0x00000000}}, // _mbuga_, _maganar_, _kella_, --,
+ {{0x2432b07b,0xd3f470d7,0x53a8e102,0x2a36907b}}, // _জিনিস_, _instal_, _belakang_, _পিসিতে_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [ed0] --, --, --, --,
+ {{0x0e7ab03d,0xa7e1a0ea,0x625ad1ea,0x33f9815b}}, // _チェックリストに, _판매자에게_, _imeli_, _gerus_,
+ {{0x829110b4,0x52903138,0x4ceb9018,0x913c1092}}, // _encara_, _almain_, _להסיר_, _posledná_,
+ {{0x33f860bd,0x6e139179,0x00000000,0x00000000}}, // _atout_, _ollenkaa, --, --,
+ {{0x32d98070,0xf27e905d,0x00000000,0x00000000}}, // _keres_, _asani_, --, --,
+ {{0x9fab3041,0x22d9803d,0x00000000,0x00000000}}, // _nemovito, _jeres_, --, --,
+ {{0xc2d980e5,0xa303408d,0xf29020ad,0x124922d7}}, // _deres_, _nabantu_, _inkar_, _hocmai_,
+ {{0x4290c22b,0xeea2f08d,0xb862d078,0x00000000}}, // _undan_, _uhulumen, _akintund, --,
+ {{0xe2004024,0x522900f3,0x00000000,0x00000000}}, // _kimin_, _размислу, --, --,
+ {{0xb290c02c,0x00000000,0x00000000,0x00000000}}, // _ollaan_, --, --, --,
+ {{0x135bd086,0xae5272c1,0x9c6e01d6,0x82ca519e}}, // _kongera_, _ostavlja, _azaraya_, _felda_,
+ {{0x22ca520d,0x23dce084,0xaf72302c,0x2197a008}}, // _geldi_, _haiwan_, _вокруг_, _לתואר_,
+ {{0x52d99183,0x52d9e19e,0x2600e0de,0x626e00b7}}, // _meses_, _tetek_, _शायदे_, _napoca_,
+ {{0x427a12a7,0x730c2264,0x0c22e081,0xe201a0ca}}, // _karibuni_, _poderá_, _ініціати, _kopii_,
+ {{0xf25a514b,0x00000000,0x00000000,0x00000000}}, // _belli_, --, --, --,
+ {{0x02fce06e,0xf85ca26d,0xbc0f81ec,0xe4a940ea}}, // _gangan_, _detaljer_, _agamnihu_, _통신판매업신고_,
+ {{0xe2498153,0x03f98028,0x358b9175,0xa225f1be}}, // [ee0] _terms_, _terus_, _достигну, _akuko_,
+ {{0x0c5bf017,0x93b39143,0x82d6f0c4,0x0dff703b}}, // _ekstovi_, _प्रबुद्ध, _nemohol_, _rezervua,
+ {{0xd291820e,0x9cb26007,0x00000000,0x00000000}}, // _foran_, _alaarẹ_, --, --,
+ {{0x0c77b05d,0x00000000,0x00000000,0x00000000}}, // _abasese_, --, --, --,
+ {{0xf27e9039,0x2252f188,0x0cc2d070,0x00eec0a0}}, // _arany_, _bestyrel, _ہلاکتیں_, _amlinell,
+ {{0x627e0380,0x32b201e3,0xf2005037,0xd5b2004d}}, // _sains_, _abadala_, _lilin_, _詳しくはこちら_,
+ {{0x2290e021,0xbd9c60bb,0xc2b670d7,0x0c7fa333}}, // _šiame_, _ameliska, _mercat_, _tabulky_,
+ {{0xfc00b100,0xf4b00064,0x325a906e,0x43f10144}}, // _miestas_, _diharamk, _adalu_, _poštovné,
+ {{0x12d9e1ea,0x738fd11c,0x3d0a307b,0xd98811d9}}, // _petek_, _senyawa_, _নরসিংদী_, _richtige,
+ {{0x938aa005,0x2d3c8175,0x00000000,0x00000000}}, // _aðrir_, _новинарс, --, --,
+ {{0x39f31062,0xa2d991c0,0x0c56a090,0x4f4da2a5}}, // _инвестит, _beses_, _vertete_, _היברו_,
+ {{0xf26e51ab,0xb2d98271,0xbc41f13b,0xc2d8d045}}, // _motore_, _seres_, _县级以上人民政府, _huden_,
+ {{0xa291e153,0x23f8202d,0x32d99082,0xf149f14d}}, // _total_, _hukum_, _leser_, _цариград,
+ {{0x0d1b23ae,0x0c69323d,0x02e8a276,0x23ab10c9}}, // _गुणवत्ता_, _emprego_, _lukisan_, _upravnog_,
+ {{0x64d52129,0x83ea0047,0xf22aa0c6,0x327ed1c4}}, // _साहित्_, _leith_, _někde_, _abend_,
+ {{0xd47511c1,0x1c631175,0x58531175,0x32f0f047}}, // _अपरिहार्, _assunto_, _assuntos_, _eisiach_,
+ {{0x10f3b008,0x86a1f08d,0x00000000,0x00000000}}, // [ef0] _לאהוב_, _amathath, --, --,
+ {{0xc2ca703e,0x07bbb008,0xc290e29f,0xdf641018}}, // _henda_, _לפועל_, _finais_, _וריכוז_,
+ {{0x0b57f191,0x72e25035,0x02908077,0x2a0c20b7}}, // _kultúra_, _जाणीव_, _alkaen_, _raspunsu,
+ {{0x6200503b,0x125a927b,0xc9f61070,0x8d46c050}}, // _cilin_, _kwalo_, _توسیع_, _интервју_,
+ {{0xd29070c0,0x965e1017,0x42d9a03b,0x00000000}}, // _innan_, _фирме_, _neper_, --,
+ {{0xcb79d215,0x853170ea,0x818691df,0xf74e70ea}}, // _легенды_, _불가능합니다_, _nakakhul, _국가보안법_,
+ {{0x9ae070c2,0x799ad263,0xd3160184,0xfc394089}}, // _contenut, _सदस्यनाम_, _myiza_, _inscripc,
+ {{0x3e06c0cb,0x429043af,0xa743e0a4,0x000c108f}}, // _mindenna, _siman_, _नवनिर्मा, _お問い合せ_,
+ {{0xf290c1c0,0xaabf2149,0x928c2222,0xcfbda092}}, // _andam_, _slavnost, _rơixuống_, _poplatko,
+ {{0x21f7c0cb,0x347d9253,0xb665e176,0x00000000}}, // _معترف_, _प्रभा_, _आवासीय_, --,
+ {{0x9c645154,0x626470a2,0x93ea71c0,0x57cd00fe}}, // _wabunge_, _mislio_, _benta_, _চক্রবর্ত,
+ {{0xe2d9b0af,0x6837e0ea,0xec2590a1,0x73993069}}, // _seveda_, _되겠습니다_, _astagfir, _samxusa_,
+ {{0x72ca70f6,0x5ed961ee,0x379140d3,0xe13592bc}}, // _denda_, _noticias_, _صلاحيات_, _sundsval,
+ {{0xdd80d0a4,0x57891107,0x00000000,0x00000000}}, // _माध्यमात, _месото_, --, --,
+ {{0x1307d19e,0x79e6f02c,0x00000000,0x00000000}}, // _sarawak_, _verrattu, --, --,
+ {{0x23ead17d,0x73a2c2bc,0x93ea00de,0x00000000}}, // _groter_, _kompis_, _leiti_, --,
+ {{0xdbd83166,0xd2a1a130,0x28533082,0x2e333017}}, // [f00] _श्रीमद्_, _менталит, _площа_, _плоча_,
+ {{0x82cad071,0xbfe970ca,0x7b19301a,0xbdbe41c0}}, // _produs_, _संभावित_, _tamperee, _catalase_,
+ {{0xd583a232,0x129dc0cb,0x327e017f,0x9667f02b}}, // _fenerbah, _مشائخ_, _mbinu_, _pastebėj,
+ {{0x90b8711c,0x27e7e0d5,0x850c813b,0x16b92045}}, // _ماجراجوی, _देखरेख_, _胶南市农业机械管, _системам,
+ {{0xf075d0ae,0x068dc12d,0x468c91ed,0x1a6fc3b0}}, // _materiāl, _निवास_, _बोलाव_, _आर्काइवक_,
+ {{0x87de91da,0x121c4212,0xe63b513b,0x8643a004}}, // _kumpanni, _jowhar_, _小仓优子性感泳装, _סאדאם_,
+ {{0x62909116,0xc969b0c5,0xc665a102,0x861830d3}}, // _inaan_, _ajankoht, _peristiw, _saighdiú,
+ {{0x2eb99215,0xd3c75098,0x564b80b3,0x2e55319b}}, // _анонсы_, _hospodář, _njihovom_, _زودتر_,
+ {{0xf28710d3,0x4f39e1b7,0x00000000,0x00000000}}, // _أفلام_, _memulaka, --, --,
+ {{0xc3940268,0x1b60b030,0xafe952f1,0xe9e3f100}}, // _aviso_, _सोहनी_, _सासाराम_, _валютных_,
+ {{0x62905316,0xa2890013,0x23cef0c5,0x03f820a5}}, // _onlar_, _appartem, _olivat_, _hukuk_,
+ {{0xc4c0735c,0x62007047,0xa2d9a090,0x03f8d2cc}}, // _público_, _inniu_, _teper_, _judul_,
+ {{0x324971e4,0x4172127d,0xdd321128,0x00000000}}, // _agamsa_, _miliardy_, _miliarde_, --,
+ {{0x829190a2,0x52b4e119,0x2c52a025,0x06368193}}, // _posao_, _dhicin_, _ड्रइङ_, _završila_,
+ {{0x12da50d4,0x9237e03c,0x82a7f0aa,0x00000000}}, // _poteri_, _chomika_, _grubu_, --,
+ {{0x02b3e264,0x87e7a3b1,0x22918160,0xa2f1f024}}, // _produto_, _बाथरूम_, _foras_, _tarixli_,
+ {{0x5ee9b0cc,0xd27e9088,0xf68ca2af,0x2dbe4114}}, // [f10] _בחדרי_, _ibang_, _प्राय_, _database_,
+ {{0x3394f1df,0xe71f1045,0x1386e18f,0x823781b8}}, // _kristu_, _спортсме, _shirya_, _sarja_,
+ {{0x42918183,0x72d370b9,0x53a2d143,0x2394e12f}}, // _horas_, _jehovah_, _hoopis_, _briste_,
+ {{0xf478721b,0x670cd050,0x1e4ef1df,0x00000000}}, // _lemfundo_, _divergên, _tshabala, --,
+ {{0x538690d3,0x1249f09c,0xbc689128,0xa20191b2}}, // _bharr_, _atuma_, _acordul_, _losis_,
+ {{0xd25ad17f,0x0291810c,0xfc93e1cc,0xf6ae3024}}, // _kweli_, _doras_, _स्कोर_, _britaniy,
+ {{0xb3f8f08c,0x5e5a300e,0x00000000,0x00000000}}, // _bugun_, _ababataa, --, --,
+ {{0xc38f1092,0x5b4200d1,0x62d9a1a0,0x00000000}}, // _obstaráv, _mädchen_, _peper_, --,
+ {{0xd756a0cb,0x9291019d,0x00000000,0x00000000}}, // _طاقتور_, _dibawa_, --, --,
+ {{0xa2d820b9,0x47e4814c,0x0aaeb004,0x00000000}}, // _erken_, _कामरूप_, _דובראַוו, --,
+ {{0xd301e07f,0xb501e099,0x12b400c3,0x934a0231}}, // _detalii_, _protekao_, _adica_, _izmedju_,
+ {{0xd200c153,0xc69b1177,0x3448a293,0x00000000}}, // _india_, _daleithi, _критикув, --,
+ {{0x0307e084,0xa2018120,0xa16e5039,0x00000000}}, // _jugalah_, _morir_, _استفادہ_, --,
+ {{0x62d9e190,0xf2030190,0x48b99130,0xf2d801db}}, // _sezonas_, _automašī, _последњи_, _trieu_,
+ {{0x32d9617b,0xb4843176,0xf2fce04a,0xa2ef5177}}, // _ingeri_, _इजरायल_, _bangun_, _hoffwn_,
+ {{0x0feda0cc,0x630b70a7,0x729b72a3,0xad5b7241}}, // _מסביר_, _najbolj_, _najbolji_, _najbolju_,
+ {{0x4cc480e9,0x00000000,0x00000000,0x00000000}}, // [f20] _hamingju_, --, --, --,
+ {{0xc2f230a7,0x5fa46153,0x4200710c,0x9fee9096}}, // _potovanj, _december_, _innis_, _चुनावमा_,
+ {{0x69df50b4,0x00000000,0x00000000,0x00000000}}, // _contingu, --, --, --,
+ {{0xbc765201,0xb290713e,0x938071d7,0x1c7610ac}}, // _results_, _annat_, _thuras_, _fikrini_,
+ {{0x2248901b,0x528b612b,0x6200c01a,0xc2fe6051}}, // _suami_, _pomocí_, _tulisi_, _kabilar_,
+ {{0xbae96093,0x41772100,0x89ed9126,0xe362902c}}, // _gasteboe, _нешта_, _rendelés, _одноврем,
+ {{0xfdbc4381,0xfb93b08f,0x1ce40018,0x00000000}}, // _označte_, _その他のタグ_, _קרמיקה_, --,
+ {{0x425a4064,0x1358405d,0x02d9f1fd,0x00000000}}, // _semlm_, _abagabe_, _etten_, --,
+ {{0x42fc0115,0x5278219a,0x82007177,0x92da634c}}, // _amigo_, _anunţ_, _cinio_, _parede_,
+ {{0x5876820d,0x0115a082,0x786cf0b9,0x7df940a4}}, // _olabilir_, _власност, _minstens_, _अनुमोदन_,
+ {{0xf8a1703d,0xf2919202,0xfc26e013,0x24fd9175}}, // _お気に入り_, _cosas_, _verbeter, _законите_,
+ {{0xf874807a,0xa3f8e1e2,0xd27ee0de,0xaefe2018}}, // _курсах_, _genuen_, _tunnen_, _רוטשילד_,
+ {{0x7248d1d5,0x520271d8,0xac09312a,0xe2d80222}}, // _abụchagh, _musiek_, _lampara_, _triet_,
+ {{0xa2d922d7,0x98c7c04d,0x1b479025,0xd5eab20d}}, // _luyen_, _知恵袋に投稿され, _लिक्खाड़_, _amacıyla_,
+ {{0x32ca70f6,0x094e3296,0x835d105e,0x52cad002}}, // _kendu_, _escolare, _tanging_, _reede_,
+ {{0xc8e8a04d,0xdc5a718f,0x4e8f4279,0x62d831b8}}, // _選択してください_, _tuntubi_, _kommunen, _arjen_,
+ {{0xd86a904f,0xaea38077,0x62907153,0x1316d069}}, // [f30] _politiqu, _значител, _final_, _moozoo_,
+ {{0x83878171,0x34047020,0x6f40f020,0x6fb971a2}}, // _carro_, _전체매물정보_, _인도네시아_, _فوتسال_,
+ {{0xa3f9e061,0x86cb801a,0xf197c0c5,0x3c9d802c}}, // _metus_, _samanlai, _наконец_, _последов,
+ {{0x6c59e1dd,0xd2d9c0b4,0x72cb018d,0x3b9e1008}}, // _besteak_, _seves_, _grader_, _קומדיה_,
+ {{0xd25ae003,0x22d922d7,0x92d90217,0xa212b09a}}, // _heilsa_, _duyen_, _arbeta_, _coche_,
+ {{0xd9daa2cc,0x2623a043,0x0200b05d,0x830cd091}}, // _ternyata_, _daimntaw, _endiga_, _anhapus_,
+ {{0x426e6178,0x00000000,0x00000000,0x00000000}}, // _aprova_, --, --, --,
+ {{0x95e2f1ee,0x0a7e02b2,0xf29272aa,0x02007128}}, // _categorí, _byandits, _dorais_, _minim_,
+ {{0xf9ffa041,0xc2cb4144,0x00000000,0x00000000}}, // _moderní_, _predam_, --, --,
+ {{0x9290718b,0xb5e2f177,0x00000000,0x00000000}}, // _annar_, _categorï, --, --,
+ {{0x03ea702d,0x328c4173,0x5200e27b,0xb7929112}}, // _tentu_, _niekada_, _nonina_, _contrasi,
+ {{0x12004037,0xf2e7706e,0x53204154,0x00000000}}, // _kimia_, _adunola_, _kimya_, --,
+ {{0x92e8e0a4,0x62d8d1b2,0xd9ab10fd,0x6eb9615b}}, // _tulisan_, _ntees_, _оксана_, _navorsin,
+ {{0x32480136,0x4d93d2db,0x4233c179,0xb200c02d}}, // _naime_, _लोकोक्ति, _hyppää_, _kuliah_,
+ {{0x8053c035,0xe290c105,0x02d9e194,0x331a807b}}, // _presiden, _ondan_, _heter_, _সিকিউরিট,
+ {{0xb301c076,0x5d3d3041,0xab98035c,0x727d3098}}, // _ज़िम्मेद, _aplikace_, _ficheiro_, _aplikaci_,
+ {{0x7387f068,0xa1b9e100,0xd2d280d4,0x43b99017}}, // [f40] _buurt_, _молодёжь_, _illegali_, _zatvoru_,
+ {{0x9202009d,0xff3a0061,0x224a6222,0x00000000}}, // _lipiec_, _profesin, _bcumnw_, --,
+ {{0xe2fcc3b2,0x227e600d,0x038602a9,0xe38cd1ab}}, // _google_, _mbona_, _akiri_, _шумен_,
+ {{0x355c9045,0xf5a97194,0x62d922d7,0xf2d9812f}}, // _постанов, _радянськ, _xuyen_, _dtreo_,
+ {{0x91c930c3,0x36e8d0a2,0xc326513b,0xa27e70a0}}, // _болта_, _istraživ, _图片和音视频稿件_, _rannu_,
+ {{0x5cce40ea,0x00000000,0x00000000,0x00000000}}, // _이메일주소_, --, --, --,
+ {{0x1f3e8116,0xf465325e,0xb7e60004,0x927f12a3}}, // _baarlama, _osservaz, _ליצנות_, _kaznu_,
+ {{0x527e600d,0x9730d11c,0x53271037,0x2e39d07f}}, // _abona_, _ویتامین_, _صاحبان_, _рэзбой_,
+ {{0x16d1a166,0xb2919089,0x237fd055,0xbef2e056}}, // _रितिक_, _posar_, _filayaa_, _beskrivn,
+ {{0x97597389,0x438691d6,0x160340c3,0xbc619076}}, // _साक्ष्य_, _ikari_, _тестамен, _ofercie_,
+ {{0x229071ce,0x32915154,0x1a572021,0x519d504d}}, // _annak_, _ingawa_, _іншых_, _名様にプレゼント_,
+ {{0x3aed40d4,0x02486208,0x8d561018,0x182c1018}}, // _introdot, _droma_, _בינתיים_, _להישאר_,
+ {{0xa3fc1008,0x725ad022,0x32eab0db,0x1481c13b}}, // _להפסיק_, _bullar_, _eventos_, _劳动的财政贡献率,
+ {{0x62fe423d,0x7386b07f,0x4315807a,0x02f181ab}}, // _artigos_, _oferte_, _ансамблі_, _инсталир,
+ {{0xe25ad1be,0x4d35a062,0xf2904037,0xd9a0c05d}}, // _meela_, _планете_, _timah_, _abakozie,
+ {{0xa2d9e0f7,0x62da600d,0x73d510d4,0x026e727c}}, // _beter_, _gereza_, _protetti_, _kosove_,
+ {{0xf23780e8,0x4ff82020,0xc291c015,0x6c1db008}}, // [f50] _varje_, _담당중개업소_, _novas_, _כשהוא_,
+ {{0x3fd68154,0xe6c5019b,0x725ad119,0x622670e8}}, // _changamo, _نويسد_, _geela_, _risken_,
+ {{0xb202009d,0x825af18c,0xe27f40a2,0x00000000}}, // _musisz_, _regle_, _krenuo_, --,
+ {{0xf53fe1aa,0xd290b1e3,0x925af1db,0xcf39727d}}, // _адресу_, _indaba_, _segle_, _finanční,
+ {{0x2c755079,0x025a9038,0x8e4a0037,0x2c0691a3}}, // _दोस्ती_, _awali_, _peradaba, _imprezy_,
+ {{0x620070d3,0x4c2c0208,0xa27e61df,0xb502b036}}, // _minic_, _theangac, _ubona_, _ingresso_,
+ {{0xc08a4082,0xf3f9e18b,0x1248d28a,0x825a5370}}, // _кримінал, _setur_, _igomba_, _kelli_,
+ {{0x82da63b3,0x0a1461dd,0xd3193065,0x00000000}}, // _parece_, _pribatut, _شيئاً_, --,
+ {{0x72b52044,0xa229a014,0x427e6191,0x62def08b}}, // _índice_, _kuamuag_, _uplne_, _autorov_,
+ {{0x127f02e9,0x330c30ae,0x7b29d264,0x72d92018}}, // _blandt_, _izlases_, _justiça_, _accept_,
+ {{0xe20ec1c9,0xae05919e,0x52d8c1a0,0xd9b3a16a}}, // _намаз_, _kenderaa, _namens_, _nesprávn,
+ {{0x771341ed,0xca0d60db,0x0200519b,0x8135a0f8}}, // _जर्दा_, _computad, _silih_, _mendebal,
+ {{0x9711403c,0xd2005102,0x34a30105,0xbb18105e}}, // _गर्दन_, _pilih_, _futbolçu, _kabataan_,
+ {{0x5396606a,0xb2a661b4,0x22d96013,0x00000000}}, // _persze_, _nyoba_, _ergens_, --,
+ {{0x2ece509d,0x6f3b0037,0xd9ec5174,0xad444194}}, // _कार्यवाह, _keluraha, _colombia_, _унікальн,
+ {{0x96e94029,0xc3f9e074,0x32d9e201,0xc7240018}}, // _इतिहासाच, _petur_, _often_, _סטנדרט_,
+ {{0x3b8b9295,0xce530009,0x3b6ac0b9,0x632070ef}}, // [f60] _ministar, _pelatiha, _pretoria_, _annyi_,
+ {{0x935d40a4,0x92fce1c0,0x0b02c0ac,0x3c615105}}, // _सल्ला_, _hangin_, _konfrans, _postlar_,
+ {{0xedb79021,0xc2fce074,0x00000000,0x00000000}}, // _законы_, _kangin_, --, --,
+ {{0x9845b044,0xc25a3039,0x00000000,0x00000000}}, // _utilidad, _állam_, --, --,
+ {{0xc33d211c,0x325a9038,0xc3eb006e,0xd9f7a086}}, // _میراث_, _swali_, _abetí_, _bibiliya_,
+ {{0x2f1d608e,0xc5327017,0x25abf004,0xc224d26f}}, // _nakupova, _септемба, _עקאָנאָמ, _ifokus_,
+ {{0x1315b240,0x74293037,0x627ed12f,0xd48552cc}}, // _memberi_, _campuran_, _daonna_, _शेजारी_,
+ {{0x4290c051,0xe212b154,0x0c64323e,0xd29030d4}}, // _gidan_, _kocha_, _bintulu_, _rumani_,
+ {{0x027e914e,0xa431507b,0xc2787061,0x1c586353}}, // _upang_, _আপনিও_, _kaunas_, _faktisk_,
+ {{0x2f993048,0x307cb07b,0xedb2d0f2,0x01df803c}}, // _nhất_, _ফটোশপ_, _keingina, _poinform,
+ {{0x429040fb,0x52d9e1f8,0x2a73b1bb,0xb6faf039}}, // _limau_, _aften_, _публічна_, _افزائی_,
+ {{0xe27e900d,0xc38c2017,0x238e9203,0x12cb5089}}, // _abana_, _путем_, _consigli_, _quedar_,
+ {{0x6e68c3b4,0x1f20c223,0xbc6321a3,0xd1dc1094}}, // _adiciona, _स्वीकृति_, _dostęp_, _סלוצקי_,
+ {{0x1201f0d2,0x7200a247,0x34e3407b,0xd248d184}}, // _sluit_, _bibie_, _ইকবাল_, _ugomba_,
+ {{0xa9093166,0x1230705a,0x513c110b,0x9ae891dd}}, // _विवेकानं, _nyamara_, _posledný_, _datorren_,
+ {{0xbd0d7099,0x7200f22b,0xe34fd0f8,0x5290f03e}}, // _najmanje_, _engin_, _direnak_, _engan_,
+ {{0x9db43037,0xf3eb3044,0xcc1d300c,0x83a342ff}}, // [f70] _موضوع_, _texto_, _kurallar, _sleppa_,
+ {{0x7290c1dd,0x2291d1d8,0x20f4404d,0xe387f224}}, // _zidan_, _sowat_, _そういえば_, _istre_,
+ {{0xb200f04a,0x67bca040,0x4291e0dc,0x00000000}}, // _ingin_, _angezeig, _votat_, --,
+ {{0x035fa254,0x77a1f15a,0x131c119b,0xb9d0c0c3}}, // _airgead_, _omstandi, _ضیغمی_, _векий_,
+ {{0x2b98e283,0x3c5280f8,0x0481b2be,0x11216241}}, // _pilipino_, _kontuan_, _पिताजी_, _raspolož,
+ {{0x72a7c036,0x07d7c173,0x3273b0e9,0xc201007f}}, // _такова_, _тэкст_, _mínir_, _mobila_,
+ {{0x325a0034,0x22903154,0x7367b0d1,0xf30d8086}}, // _utile_, _kamati_, _langsam_, _kwibuka_,
+ {{0x8943813d,0x6200c158,0x53ead0de,0x53d4019f}}, // _mailadre, _fidio_, _rootsi_, _yuxarı_,
+ {{0xae37b0fc,0x0499308a,0x5599303b,0x125a0036}}, // _bidalita, _personaj, _personaz, _stile_,
+ {{0xe27ee047,0x90c7a1bc,0x0290c081,0xcf30012f}}, // _pointe_, _рамките_, _sidan_, _الزفاف_,
+ {{0x72ca701d,0x938691ba,0xd37381a6,0x527ef142}}, // _kendi_, _thart_, _redakce_, _magni_,
+ {{0x801e2173,0x7a0c107b,0x5f06c107,0x3200f02d}}, // _komentuo, _সুখবর_, _кратка_, _angin_,
+ {{0xe200c01d,0xc75831f7,0xab7bf05e,0xa810f11c}}, // _indir_, _मिष्ठान_, _pangalan_, _جایگزین_,
+ {{0x039461d6,0x604dc017,0x82ca70f9,0x73f40044}}, // _idosa_, _врата_, _lendi_, _destas_,
+ {{0x6b1d112f,0x43ea9047,0x7200c002,0x62fcd044}}, // _الفجر_, _leath_, _andis_, _únicos_,
+ {{0x427bc221,0xa2a78101,0x00000000,0x00000000}}, // _objednáv, _garba_, --, --,
+ {{0x33f8f276,0x00000000,0x00000000,0x00000000}}, // [f80] _puguh_, --, --, --,
+ {{0xf3ead1d7,0xa89ea06a,0x372ba02a,0x00000000}}, // _cultur_, _katalógu, _столиці_, --,
+ {{0x7fd52006,0x82139144,0x1273b277,0xef0ce0fe}}, // _materiał, _najnovši, _sínar_, _ব্লজব_,
+ {{0x9d97601a,0x4bdde03c,0x07a6c082,0x5752c08d}}, // _applikaa, _बलिया_, _handling, _ezinjeng,
+ {{0x72a78220,0x987602d0,0x4942701a,0xbeb32346}}, // _darba_, _משניות_, _sellaise, _derechos_,
+ {{0x0e1ed0a4,0xee2e0057,0x00000000,0x00000000}}, // _व्यक्तिप, _bergamba, --, --,
+ {{0x3dd9319e,0x92a7808b,0x9773913b,0xc7e3d036}}, // _berkenaa, _farba_, _并不意味着赞同其, _позиция_,
+ {{0xeb0a4128,0x49912163,0xa290f044,0xe200c23e}}, // _persoane, _klijenta_, _ligan_, _aidil_,
+ {{0xd369c03e,0xc38751bd,0x00000000,0x00000000}}, // _tenglar_, _پاسداری_, --, --,
+ {{0xe2903138,0xd5d2f017,0x00000000,0x00000000}}, // _comann_, _железниц, --, --,
+ {{0x52927221,0xf2d820a1,0x027f818b,0x0c774192}}, // _strach_, _emmere_, _barni_, _websted_,
+ {{0x632091cc,0x1d561008,0x929070a2,0xec7d501a}}, // _biaya_, _זיכרון_, _iznad_, _miesten_,
+ {{0x3aefd080,0x68d2d0da,0x0605a02b,0x00000000}}, // _opíšte_, _पहाटे_, _буклеты_, --,
+ {{0x92d97080,0x3a03304d,0x00000000,0x00000000}}, // _videá_, _このブログの読者_, --, --,
+ {{0x92c7c27e,0xca0510da,0x8a376044,0xd4d76044}}, // _sådan_, _चिन्हावर_, _técnicas_, _técnicos_,
+ {{0x645b7100,0x72901106,0x235d111c,0x00000000}}, // _установа, _mihai_, _nanging_, --,
+ {{0x6ccc102a,0xb290327d,0x00550175,0x00000000}}, // [f90] _служб_, _nijak_, _passagen, --,
+ {{0xd3eb8064,0x6898a25a,0xf685204e,0x6a11f034}}, // _serta_, _न्यूजीलै, _аурите_, _garanzia_,
+ {{0x62cb8035,0x8197c14d,0x2290c0ed,0x72d85177}}, // _perda_, _законе_, _indah_, _caled_,
+ {{0x02d981dd,0x1f5991b2,0x329070bb,0x4387a0ef}}, // _euren_, _bliajyob_, _vinai_, _napra_,
+ {{0x8730d0cb,0x03eb802a,0x00000000,0x00000000}}, // _عیسائی_, _verta_, --, --,
+ {{0xe2bd1215,0x9387e0f0,0x82015246,0x00000000}}, // _спроба_, _catro_, _ingiza_, --,
+ {{0x42249193,0xc3cef13e,0xbe83d1b8,0xaf5d6102}}, // _ovako_, _blivit_, _подарок_, _sepanjan,
+ {{0x43878060,0x33eb0084,0xf2cb0084,0x95b9212f}}, // _abiyamọ_, _khatam_, _khadam_, _ballstát_,
+ {{0xd291b03f,0x62fc9283,0x22905009,0xf290f02d}}, // _novada_, _umaga_, _kilat_, _ingat_,
+ {{0x53787154,0xeaf7c062,0x00000000,0x00000000}}, // _angalia_, _памети_, --, --,
+ {{0xb2d9f01a,0x346a90bf,0x6321a0c4,0x8841c171}}, // _ettei_, _akademio_, _dopyt_, _últimas_,
+ {{0xa2d84073,0x1395403a,0x43f0c00a,0xa2b41192}}, // _numer_, _queste_, _minoranz, _sundhed_,
+ {{0x2d04c12f,0x3a116133,0x8f7a5222,0x6343c130}}, // _زوجها_, _thuairme, _chồng_, _повода_,
+ {{0xfb038195,0xa5ba3031,0x527f8276,0xecaa0199}}, // _телекана, _月加入中国共产党_, _warni_, _asociāci,
+ {{0x2c21a375,0xf30b911c,0x1292700e,0x25a0b125}}, // _גדליה_, _یکدیگر_, _merali_, _सोचती_,
+ {{0x7f39a008,0x5254e1bd,0x9224e1d6,0xb200c190}}, // _תקציב_, _főleg_, _adikwa_, _paliek_,
+ {{0x4fef80a4,0x62b66089,0x22cb5332,0x707d3293}}, // [fa0] _विमानतळ_, _cercle_, _kredyt_, _шанси_,
+ {{0x03f821c5,0x5ad681af,0x4290312a,0x42bb50ac}}, // _cukup_, _листата_, _himaya_, _həcmi_,
+ {{0xa248002b,0xa617401a,0x5e59101b,0xf74e0111}}, // _kaimo_, _englanni, _berbinca, _rodičovs,
+ {{0xdb3b9017,0xe8b1d1f6,0x2225409f,0x27cb902a}}, // _складу_, _predsjed, _hoekom_, _складі_,
+ {{0x51778013,0xbb6440d1,0x6387e3b5,0xe2da700c}}, // _behandel, _arbeiten_, _patro_, _mesela_,
+ {{0x33f880a1,0x1f3360bd,0xb332d18e,0x22d7c1ee}}, // _yakuwa_, _konsista, _flexo_, _laboral_,
+ {{0x92d9e153,0x429081a4,0xf486d002,0x82b4f029}}, // _after_, _nikada_, _त्राहि_, _आत्मविश्,
+ {{0xb30cc209,0x12da10a0,0x628c2136,0x806172b9}}, // _siyaset_, _popeth_, _njihovu_, _praktisk_,
+ {{0x5b1ed031,0x3b8de0d4,0xc13a31fd,0x84eb60e9}}, // _点击此处查看原文_, _fornitur, _mennessä_, _gestabók_,
+ {{0xa3ea6038,0x4200520d,0x822490a2,0xd686b210}}, // _mtoto_, _bilim_, _svako_, _prodavni,
+ {{0xd2d9e27e,0x5be9a16e,0x73538006,0x70d560a4}}, // _efter_, _جرينتش_, _produkcj, _bergerak_,
+ {{0xf387e07f,0x42d8d1dd,0xe3f860a4,0xb3f9810f}}, // _catre_, _ordea_, _mulus_, _nurul_,
+ {{0xf2d981f8,0x3dd7a102,0x2200f252,0x00000000}}, // _turen_, _perhatia, _digil_, --,
+ {{0xffcd312f,0x2e38720d,0x02a2623b,0x7e91e041}}, // _البعض_, _galatasa, _उच्चस्तर, _editovat_,
+ {{0x720071c6,0x829040f1,0xe00521af,0x84cb91ab}}, // _minit_, _jimak_, _огромни_, _контрапр,
+ {{0xa2d82148,0xf507b0c5,0xf2a63185,0x37d0c017}}, // _maken_, _средств_, _tombol_, _многи_,
+ {{0x53eb91f1,0x7c7040ff,0xe2909038,0x80d040c4}}, // [fb0] _mesto_, _vlastne_, _askari_, _vlastnej_,
+ {{0xd23510c7,0xf45510c2,0xb3eb8036,0x44a1b00c}}, // _prodotti_, _prodotto_, _certo_, _firması_,
+ {{0x217cc068,0x2bdf81b3,0x3e59104c,0x0359f2eb}}, // _해피캠퍼스_, _coreógra, _perbinca, _razdoblj,
+ {{0xf2d6f0c4,0x62918025,0x59678070,0xc96740a6}}, // _pomohol_, _coraz_, _tekintet, _हरिनाम_,
+ {{0x30b0112b,0x42b40086,0xce4f012a,0xb2b4e314}}, // _minimáln, _kwica_, _makalala, _afiche_,
+ {{0x02786150,0xe4b5a05d,0xfa19813b,0xe4b2800a}}, // _umuntu_, _kutandik, _点击查看更多_, _legalizz,
+ {{0xf5c7108a,0x97798031,0xaa739204,0x33eb9229}}, // _তেহরান_, _青岛市公安局_, _slično_, _kesto_,
+ {{0xd2926116,0x52009328,0x334212c1,0x41dc102a}}, // _intaas_, _jijiga_, _poseban_, _клуби_,
+ {{0xf3eb7133,0x7c7ea0bc,0xf25a0018,0x43dc91be}}, // _leatsa_, _opisyal_, _build_, _amawo_,
+ {{0xdc603232,0xd2005102,0xe37b2248,0x00000000}}, // _amatör_, _milik_, _horario_, --,
+ {{0xa9e64136,0x96d601dd,0x78bae0ea,0xb2d64136}}, // _otvorena_, _desberdi, _동영상갤러리_, _otvoren_,
+ {{0x63eb91e8,0x9290f25d,0x00000000,0x00000000}}, // _nesto_, _engar_, --, --,
+ {{0x226e607f,0xb3ea9203,0xd26fb0ed,0xc57140ea}}, // _parola_, _stata_, _pengalam, _공간입니다_,
+ {{0x373e00c5,0x6e39504f,0x9a0ab2d7,0xc27ed0a0}}, // _подготов, _localisa, _banbient, _ffonio_,
+ {{0x62d99221,0xdb0c0237,0x9a42f011,0xb2b2f0e6}}, // _musel_, _keistime, _bilderna_, _bildern_,
+ {{0xf200c1ec,0x5378d0a4,0xb48663b6,0x4045f0b2}}, // _midia_, _lumayan_, _गंगाजी_, _kilobook_,
+ {{0x83eb834c,0x725a0031,0x4386d159,0xf041a2c1}}, // [fc0] _perto_, _huile_, _akero_, _okruženj,
+ {{0xc5bbf0b7,0xef4d0179,0x8f03e025,0x83f96245}}, // _акчепте_, _omistaja, _मुफ़्त_, _kugura_,
+ {{0x73ef61cc,0xf726d025,0x3290b0a1,0xb8f8b0f8}}, // _जन्मकुंड, _काफ़ी_, _endala_, _askatasu,
+ {{0xa290e366,0x7c615024,0x42d750ca,0xf2ed7018}}, // _dinara_, _dostlar_, _třech_, _approach_,
+ {{0x62272020,0x223dd069,0xe07a413c,0x93870161}}, // _전원주택펜션_, _huamhoj_, _временск, _svarer_,
+ {{0x7248d379,0xd201124e,0x127e9089,0xa2deb1ea}}, // _prema_, _sizin_, _abans_, _kuponov_,
+ {{0xc03fb0b9,0x229ee099,0x343ee099,0xa200518a}}, // _vertalin, _slobodni_, _slobodno_, _milih_,
+ {{0x220051c6,0xab2e40ca,0xe20260de,0x8baf1108}}, // _bilik_, _poradens, _aprill_, _षडयंत्र_,
+ {{0xa2fde061,0x22dfa33d,0x7f5d40e1,0x32005009}}, // _muzikos_, _maandag_, _दूंगा_, _cilik_,
+ {{0x430f3230,0x52d811c0,0x127ff0b2,0x92495089}}, // _roditelj, _kahel_, _phung_, _premis_,
+ {{0xed03504d,0x0d314065,0x675ba070,0xbe3b10da}}, // _最終更新日_, _رووووووو, _ہتھیاروں_, _kesadara,
+ {{0x7294227b,0x5db71183,0xddb41002,0x6f5d4125}}, // _israyeli_, _habilida, _munitsip, _दूँगा_,
+ {{0x3ef4d01a,0x42d8a07a,0xad444065,0x42007133}}, // _становит, _vadovas_, _وخلفيات_, _ainim_,
+ {{0x527ff2d7,0xe38730dc,0x67ed410b,0x42d8307f}}, // _thung_, _milionë_, _futbalov, _nimeni_,
+ {{0xf1e1a0a4,0x508d9114,0x03009004,0xc2cb81c0}}, // _ठाकरे_, _השיער_, _ראטעווען_, _berde_,
+ {{0xe38ce07a,0x43ac705f,0x00000000,0x00000000}}, // _музея_, _respek_, --, --,
+ {{0x5e9230e7,0xa741d082,0x00000000,0x00000000}}, // [fd0] _портала_, _основні_, --, --,
+ {{0x4ea2f0da,0x53f82159,0x7c68316c,0x4987e3ad}}, // _parlemen, _takun_, _петрика_, _तालमेल_,
+ {{0x327f7118,0xe31692a9,0x50da5045,0xa2905102}}, // _quanto_, _akazi_, _написанн, _nilai_,
+ {{0x62484056,0xa4ee1194,0x52da714d,0x70f3a263}}, // _samma_, _підготов, _meseca_, _भागवत_,
+ {{0x7e9f00de,0xe3f84037,0xc54a703d,0x02d820c0}}, // _विशेषज्ञ_, _lamun_, _アクセサリ_, _saken_,
+ {{0xd5fa6243,0xab0030fe,0x4201c0c2,0x00000000}}, // _पुरोहित_, _পিয়াল_, _invio_, --,
+ {{0x625ad06e,0x238a9130,0x1067b063,0xa200b21b}}, // _meeli_, _француск, _akwarado, _indima_,
+ {{0x729001d7,0x00000000,0x00000000,0x00000000}}, // _chiad_, --, --, --,
+ {{0xf5ac1071,0x42b440ef,0x323451ec,0x33a45018}}, // _глума_, _vendég_, _amamihe_, _ההצטרפות_,
+ {{0x830e01c0,0x930370d1,0x03b6202a,0xd27f709e}}, // _sapatos_, _gemacht_, _значення_, _asanga_,
+ {{0x1290a028,0x0201c31c,0x7cd230fe,0xbe31c014}}, // _sedang_, _envio_, _ভাইকে_, _vimlicas_,
+ {{0x3e6c2126,0xe2912037,0x6a12b1b6,0xdf2d5008}}, // _فیروز_, _anyar_, _izaberit, _conferen,
+ {{0x575fe3b7,0xc08da008,0xd9f7b16a,0x83f400dc}}, // _विस्फोट_, _לבדוק_, _vydavate, _rastet_,
+ {{0xc2ea0084,0xff3890f5,0xaf202047,0x93eb51e3}}, // _beriman_, _kienthuc_, _موبايلات_, _upetru_,
+ {{0xc2d840e6,0x42ea824a,0x4bc43191,0xa93592cc}}, // _namen_, _helical_, _dekoráci, _एक्स्प्ल,
+ {{0x01b3c12f,0x33eae142,0x00000000,0x00000000}}, // _شاغرة_, _editja_, --, --,
+ {{0x8c3aa036,0xa26e4147,0x439403b8,0x00000000}}, // [fe0] _историят, _nitong_, _ovisi_, --,
+ {{0xedff5250,0x2c7581f3,0x856d622c,0xa2d42260}}, // _rundinga, _सदस्यो_, _samosprá, _večernje_,
+ {{0x7611804b,0x24d5b1f3,0xde72c1a6,0x5bdfc23f}}, // _kritikal_, _काशिका_, _रिक्त_, _nedenler,
+ {{0x5cc1712f,0xe290a110,0x6e7c10c6,0x7c639266}}, // _النجوم_, _hajati_, _मूख्य_, _milujem_,
+ {{0x92907041,0x7df16024,0x615ba031,0xb3f0b0bd}}, // _jinak_, _hindista, _添加到收藏夹_, _maketing_,
+ {{0x402e1153,0x3ac3b076,0x2a3cd0b7,0xcc57427d}}, // _conditio, _बागपत_, _бужор_, _postele_,
+ {{0x992070c8,0x8de073b9,0x72d8d2b9,0x525a90de}}, // _mobilní_, _mobilné_, _ordet_, _sealt_,
+ {{0xc84c7004,0xd784f161,0x00000000,0x00000000}}, // _ספֿרים_, _privatli, --, --,
+ {{0x0297e0e2,0xaae8a18c,0xee4f8241,0x62e8e0bd}}, // _marafiki_, _retounen_, _malograđ, _filipin_,
+ {{0x419580ae,0x12480052,0x0a1440f7,0xf25b00f6}}, // _rezultāt, _isimo_, _vakantie, _aralar_,
+ {{0x5c1d6128,0x03eb904f,0xe387e314,0x5e1d5065}}, // _complet_, _reste_, _fatra_, _وقضايا_,
+ {{0x1795a042,0xbf3b90d6,0x8378e037,0x322540ca}}, // _centerpi, _kerusaka, _kalawan_, _čekat_,
+ {{0xe3eb904e,0xd5e940aa,0x5c1fc056,0x00000000}}, // _peste_, _yabancı_, _slipper_, --,
+ {{0x428750c6,0x6c75626f,0x82ada080,0xe314b12a}}, // _miliony_, _därmed_, _zohnať_, _lambeth_,
+ {{0xd23cd020,0x9d9df0c5,0x01c930b7,0xbc5ac128}}, // _환영합니다_, _maananta, _тоате_, _preturi_,
+ {{0x4dc051da,0x227f7184,0xdebf4173,0x92927268}}, // _intitola, _usanga_, _bendrovė, _árabe_,
+ {{0x4278701a,0x50e3b018,0xd37a02c3,0xdc67b05e}}, // [ff0] _kaunis_, _הרחבה_, _gerakan_, _libreng_,
+ {{0x97db52f8,0x1ae97122,0x5a8ca0e6,0x0851113b}}, // _प्रकृति_, _sprememb, _optionen_, _衣锦荣归回乡路_,
+ {{0xd386d214,0x817db008,0x7489f1ab,0x2dad80e9}}, // _ejere_, _ליווי_, _опасност_, _landspít,
+ {{0x95f3a273,0xce9c1094,0xd2926191,0x32fcd100}}, // _момента_, _והארות_, _porada_, _blogas_,
+ {{0x3e7380a4,0xc9f6111c,0xc1dea140,0x6394609e}}, // _दृश्य_, _توزیع_, _povratak_, _bwose_,
+ {{0x22d843ba,0xec5be00a,0x825af1ab,0x79dc7082}}, // _tamen_, _partiti_, _negli_, _кіровогр,
+ {{0x360680de,0x13207154,0xaa58a0db,0x00000000}}, // _चाँदी_, _ninyi_, _pergunta_, --,
+ {{0x5828e0b2,0x52d760b3,0x7ffaa19b,0x00000000}}, // _liệu_, _osvojio_, _پرفروش_, --,
+ {{0x53f84074,0x12d841ff,0x32a6204f,0xbc1cc1e3}}, // _samun_, _samen_, _semble_, _yobukris,
+ {{0x63ea9024,0xf5d15006,0x9290e00d,0x57e770e1}}, // _teatr_, _produktó, _munani_, _लेटर्स_,
+ {{0x5fcca2e2,0x12901070,0x126401b2,0x32002037}}, // _artikler_, _alkalomm, _heslis_, _mikir_,
+ {{0x0378e18e,0x82d96056,0x42fce2cc,0x6814c008}}, // _bulanan_, _dagens_, _minggu_, _specific_,
+ {{0x63f8a19e,0x52d8d0bb,0xb200709d,0x73eb8285}}, // _kubur_, _nrees_, _linii_, _merta_,
+ {{0x9be4121b,0xf26630b7,0x00000000,0x00000000}}, // _safesear, _ансамблу_, --, --,
+ {{0x191f30d3,0x5248017f,0x020e11ab,0x9386d0bf}}, // _aistriúc, _daima_, _масаж_, _ajere_,
+ {{0xf379618f,0x9200e21b,0x00000000,0x00000000}}, // _faransa_, _kunini_, --, --,
+
+ };
+ // table_hash = 57ce-808c, unused_entries = 1306 (7.97%)
+
+static const uint32 kDeltaOctaChrome0122SizeOne = 955; // One-langprob count
+static const uint32 kDeltaOctaChrome0122IndSize = 955; // Largest subscript
+static const uint32 kDeltaOctaChrome0122Ind[kDeltaOctaChrome0122IndSize] = {
+ // [0000]
+ 0x00000000, 0x00000000, 0x00001324, 0x00000e1c, // -- -- bh.un.un_900 is.un.un_800
+ 0x0000240f, 0x00000e0f, 0x00000924, 0x00004a2d, // yi.un.un_600 is.un.un_600 pl.un.un_900 yo.un.un_A00
+ 0x00000115, 0x00002124, 0x00002a2d, 0x00001c03, // iw.un.un_700 jw.un.un_900 mt.un.un_A00 id.un.un_300
+ 0x00001b42, 0x00005542, 0x0000642d, 0x0000132d, // tr.un.un_C00 rw.un.un_C00 lg.un.un_A00 et.un.un_A00
+ // [0010]
+ 0x0000042d, 0x00000c24, 0x08000235, 0x00000342, // fi.un.un_A00 sv.un.un_900 da.no.un_A90 nl.un.un_C00
+ 0x00006e24, 0x19000a36, 0x00002037, 0x00001742, // hmn.un.un_900 pt.gl.un_AA0 sq.un.un_B00 sr.un.un_C00
+ 0x0000010f, 0x00000b24, 0x00000442, 0x00001e01, // iw.un.un_600 es.un.un_900 fi.un.un_C00 ms.un.un_200
+ 0x1700160e, 0x00001b2d, 0x19000b1b, 0x00000f15, // hr.sr.un_550 tr.un.un_A00 es.gl.un_770 lv.un.un_700
+ // [0020]
+ 0x00000315, 0x00001001, 0x0000520f, 0x0000130a, // ko.un.un_700 lt.un.un_200 ha.un.un_600 et.un.un_500
+ 0x00003142, 0x0000090f, 0x13000902, 0x16001709, // az.un.un_C00 hi.un.un_600 hi.bh.un_220 sr.hr.un_440
+ 0x1c001e09, 0x00001c37, 0x00000815, 0x00001015, // ms.id.un_440 mr.un.un_B00 no.un.un_700 lt.un.un_700
+ 0x00000415, 0x1e001c36, 0x2d000d23, 0x00001006, // ru.un.un_700 id.ms.un_AA0 cs.sk.un_880 lt.un.un_400
+ // [0030]
+ 0x09001309, 0x00000515, 0x00000a37, 0x0000051c, // bh.hi.un_440 zh.un.un_700 pt.un.un_B00 fr.un.un_800
+ 0x0000071c, 0x00001c15, 0x00000742, 0x00002142, // bg.un.un_800 id.un.un_700 it.un.un_C00 fa.un.un_C00
+ 0x0000281c, 0x00001242, 0x0000072d, 0x0000201c, // sw.un.un_800 ur.un.un_C00 it.un.un_A00 sq.un.un_800
+ 0x00000942, 0x0000021c, 0x00000e42, 0x00000f42, // pl.un.un_C00 ja.un.un_800 is.un.un_C00 lv.un.un_C00
+ // [0040]
+ 0x00000624, 0x00000d1c, 0x00006b0a, 0x00006e2d, // de.un.un_900 cs.un.un_800 ceb.un.un_500 hmn.un.un_A00
+ 0x00001942, 0x00000842, 0x00001f0f, 0x00001842, // gl.un.un_C00 uk.un.un_C00 cy.un.un_600 ga.un.un_C00
+ 0x00002b0f, 0x0000092d, 0x1e001c2c, 0x00006b15, // vi.un.un_600 hi.un.un_A00 id.ms.un_990 ceb.un.un_700
+ 0x00001e03, 0x00000215, 0x00001137, 0x00000524, // ms.un.un_300 ja.un.un_700 ro.un.un_B00 fr.un.un_900
+ // [0050]
+ 0x00000a1c, 0x0000521c, 0x00003524, 0x00000301, // pt.un.un_800 ha.un.un_800 zu.un.un_900 nl.un.un_200
+ 0x00001315, 0x00003b15, 0x00000c42, 0x1c001e14, // et.un.un_700 so.un.un_700 sv.un.un_C00 ms.id.un_660
+ 0x0000532d, 0x00006b03, 0x00005515, 0x00002501, // ht.un.un_A00 ceb.un.un_300 rw.un.un_700 eu.un.un_200
+ 0x1c000912, 0x00006442, 0x00001a42, 0x00003f24, // hi.mr.un_640 lg.un.un_C00 tl.un.un_C00 af.un.un_900
+ // [0060]
+ 0x00004a24, 0x0000101c, 0x00001737, 0x0000682d, // yo.un.un_900 lt.un.un_800 sr.un.un_B00 ig.un.un_A00
+ 0x1c001e02, 0x0000180f, 0x09001c13, 0x00001c0a, // ms.id.un_220 ar.un.un_600 mr.hi.un_650 mr.un.un_500
+ 0x0000031c, 0x00006e01, 0x0000121c, 0x09001314, // ko.un.un_800 hmn.un.un_200 ur.un.un_800 bh.hi.un_660
+ 0x00004a0a, 0x00000f0a, 0x00004a42, 0x00000706, // yo.un.un_500 lv.un.un_500 yo.un.un_C00 bg.un.un_400
+ // [0070]
+ 0x00001215, 0x0000112d, 0x08000236, 0x00000937, // ur.un.un_700 ro.un.un_A00 da.no.un_AA0 pl.un.un_B00
+ 0x00005242, 0x00001f24, 0x00000915, 0x00000424, // ha.un.un_C00 cy.un.un_900 pl.un.un_700 fi.un.un_900
+ 0x00004a37, 0x09001c1b, 0x0000100f, 0x00000b15, // yo.un.un_B00 mr.hi.un_770 be.un.un_600 bn.un.un_700
+ 0x00000703, 0x1c000d22, 0x0000530f, 0x00001124, // bg.un.un_300 ne.mr.un_870 ht.un.un_600 ro.un.un_900
+ // [0080]
+ 0x0d002d02, 0x00000824, 0x0000081c, 0x1c001e22, // sk.cs.un_220 uk.un.un_900 uk.un.un_800 ms.id.un_870
+ 0x1c001e1b, 0x0b000a2c, 0x0000551c, 0x00001b01, // ms.id.un_770 pt.es.un_990 rw.un.un_800 tr.un.un_200
+ 0x00001a24, 0x00002342, 0x00000b1c, 0x00002d0f, // tl.un.un_900 ca.un.un_C00 es.un.un_800 sk.un.un_600
+ 0x0000522d, 0x00003515, 0x00002901, 0x0000020f, // ha.un.un_A00 zu.un.un_700 sl.un.un_200 ja.un.un_600
+ // [0090]
+ 0x00002015, 0x00001f1c, 0x00002d15, 0x00003f2d, // sq.un.un_700 cy.un.un_800 sk.un.un_700 af.un.un_A00
+ 0x0000240a, 0x00001a37, 0x00000d06, 0x16001722, // yi.un.un_500 tl.un.un_B00 cs.un.un_400 sr.hr.un_870
+ 0x00000d15, 0x17001636, 0x19000b02, 0x1e001c07, // cs.un.un_700 hr.sr.un_AA0 es.gl.un_220 id.ms.un_420
+ 0x00006415, 0x00000901, 0x0000550f, 0x00003f1c, // lg.un.un_700 hi.un.un_200 rw.un.un_600 af.un.un_800
+ // [00a0]
+ 0x00001f15, 0x0000641c, 0x00003242, 0x00000f37, // cy.un.un_700 lg.un.un_800 bs.un.un_C00 lv.un.un_B00
+ 0x00001c42, 0x00001b0f, 0x00001301, 0x00002924, // mr.un.un_C00 tr.un.un_600 et.un.un_200 sl.un.un_900
+ 0x08000202, 0x17001602, 0x00001b1c, 0x00002301, // da.no.un_220 hr.sr.un_220 tr.un.un_800 ca.un.un_200
+ 0x00003115, 0x00003137, 0x00000f24, 0x0000290f, // az.un.un_700 az.un.un_B00 lv.un.un_900 sl.un.un_600
+ // [00b0]
+ 0x00001f2d, 0x1600172b, 0x00002b0a, 0x1600171b, // cy.un.un_A00 sr.hr.un_980 vi.un.un_500 sr.hr.un_770
+ 0x0000232d, 0x2d000d1b, 0x00000806, 0x0000110f, // ca.un.un_A00 cs.sk.un_770 uk.un.un_400 ro.un.un_600
+ 0x1e001c0d, 0x00003f42, 0x19000a07, 0x00006e37, // id.ms.un_540 af.un.un_C00 pt.gl.un_420 hmn.un.un_B00
+ 0x00006b24, 0x00005342, 0x00001f01, 0x00004a06, // ceb.un.un_900 ht.un.un_C00 cy.un.un_200 yo.un.un_400
+ // [00c0]
+ 0x0000080a, 0x00002d0a, 0x00000724, 0x00001142, // no.un.un_500 sk.un.un_500 it.un.un_900 ro.un.un_C00
+ 0x0d002d14, 0x0000041c, 0x00000d0f, 0x00002a06, // sk.cs.un_660 fi.un.un_800 ne.un.un_600 mt.un.un_400
+ 0x00000d0a, 0x17001614, 0x00000d42, 0x0000120f, // cs.un.un_500 hr.sr.un_660 cs.un.un_C00 ur.un.un_600
+ 0x00002415, 0x0d00090e, 0x00000601, 0x0000310f, // yi.un.un_700 hi.ne.un_550 de.un.un_200 az.un.un_600
+ // [00d0]
+ 0x0000200a, 0x00000615, 0x00003f01, 0x0000181c, // sq.un.un_500 de.un.un_700 af.un.un_200 ga.un.un_800
+ 0x00002a42, 0x1c00090e, 0x00001c24, 0x00002324, // mt.un.un_C00 hi.mr.un_550 mr.un.un_900 ca.un.un_900
+ 0x0000212d, 0x00002937, 0x00001c0f, 0x0b000a23, // jw.un.un_A00 sl.un.un_B00 id.un.un_600 pt.es.un_880
+ 0x00002042, 0x0900132b, 0x00001342, 0x13000912, // sq.un.un_C00 bh.hi.un_980 bh.un.un_C00 hi.bh.un_640
+ // [00e0]
+ 0x00006e0a, 0x00000903, 0x00002837, 0x00002801, // hmn.un.un_500 hi.un.un_300 sw.un.un_B00 sw.un.un_200
+ 0x1c001323, 0x0200080e, 0x00000642, 0x0000171c, // bh.mr.un_880 no.da.un_550 de.un.un_C00 sr.un.un_800
+ 0x00000c1c, 0x00000e15, 0x0000030f, 0x00001703, // sv.un.un_800 is.un.un_700 ko.un.un_600 sr.un.un_300
+ 0x00001303, 0x1e001c02, 0x00002006, 0x00001237, // bh.un.un_300 id.ms.un_220 sq.un.un_400 hu.un.un_B00
+ // [00f0]
+ 0x0b00192a, 0x1c001e23, 0x1e001c35, 0x00000a2d, // gl.es.un_970 ms.id.un_880 id.ms.un_A90 pt.un.un_A00
+ 0x00005506, 0x00002b42, 0x00002542, 0x00000324, // rw.un.un_400 vi.un.un_C00 eu.un.un_C00 nl.un.un_900
+ 0x0000251c, 0x00005315, 0x0000640f, 0x1c001e2c, // eu.un.un_800 ht.un.un_700 lg.un.un_600 ms.id.un_990
+ 0x00002524, 0x0000080f, 0x00000b0f, 0x0d002d1b, // eu.un.un_900 uk.un.un_600 bn.un.un_600 sk.cs.un_770
+ // [0100]
+ 0x00001042, 0x00005224, 0x1e001c23, 0x0d00092c, // be.un.un_C00 ha.un.un_900 id.ms.un_880 hi.ne.un_990
+ 0x0000352d, 0x0000311c, 0x00001115, 0x00000a0f, // zu.un.un_A00 az.un.un_800 ro.un.un_700 mk.un.un_600
+ 0x00001337, 0x02000802, 0x00001e06, 0x00002d1c, // et.un.un_B00 no.da.un_220 ms.un.un_400 sk.un.un_800
+ 0x00002742, 0x1c000936, 0x1600172a, 0x1c001e34, // gd.un.un_C00 hi.mr.un_AA0 sr.hr.un_970 ms.id.un_A80
+ // [0110]
+ 0x0000640a, 0x00000d03, 0x0000191c, 0x00006e0f, // lg.un.un_500 cs.un.un_300 gl.un.un_800 hmn.un.un_600
+ 0x00000142, 0x0b000a09, 0x00003b1c, 0x00000401, // en.un.un_C00 pt.es.un_440 so.un.un_800 fi.un.un_200
+ 0x00000701, 0x00003b42, 0x00005324, 0x09001c1a, // bg.un.un_200 so.un.un_C00 ht.un.un_900 mr.hi.un_760
+ 0x00002115, 0x00002942, 0x00000c2d, 0x0d001c1b, // jw.un.un_700 sl.un.un_C00 sv.un.un_A00 mr.ne.un_770
+ // [0120]
+ 0x0000231c, 0x00002537, 0x0000291c, 0x00000606, // ca.un.un_800 eu.un.un_B00 sl.un.un_800 de.un.un_400
+ 0x00003b0f, 0x13000909, 0x00001224, 0x0b001902, // so.un.un_600 hi.bh.un_440 hu.un.un_900 gl.es.un_220
+ 0x0000111c, 0x09001323, 0x00006b2d, 0x2d000d02, // ro.un.un_800 bh.hi.un_880 ceb.un.un_A00 cs.sk.un_220
+ 0x00000542, 0x0d000914, 0x0d002d23, 0x00001815, // fr.un.un_C00 hi.ne.un_660 sk.cs.un_880 ga.un.un_700
+ // [0130]
+ 0x00001724, 0x00000506, 0x00001206, 0x0000271c, // sr.un.un_900 fr.un.un_400 ur.un.un_400 gd.un.un_800
+ 0x00001f42, 0x00002d37, 0x17001623, 0x0000162d, // cy.un.un_C00 sk.un.un_B00 hr.sr.un_880 hr.un.un_A00
+ 0x00002715, 0x00002806, 0x17001635, 0x0000050f, // gd.un.un_700 sw.un.un_400 hr.sr.un_A90 zh.un.un_600
+ 0x00001715, 0x00000306, 0x00000c37, 0x00002b37, // sr.un.un_700 nl.un.un_400 sv.un.un_B00 vi.un.un_B00
+ // [0140]
+ 0x16001736, 0x1e001c1b, 0x00002a24, 0x0000131c, // sr.hr.un_AA0 id.ms.un_770 mt.un.un_900 et.un.un_800
+ 0x00002d42, 0x32001602, 0x0000022d, 0x00001a1c, // sk.un.un_C00 hr.bs.un_220 da.un.un_A00 tl.un.un_800
+ 0x00000337, 0x00000d37, 0x00002803, 0x00001b37, // nl.un.un_B00 cs.un.un_B00 sw.un.un_300 tr.un.un_B00
+ 0x09001308, 0x0000170f, 0x00001a2d, 0x0000280f, // bh.hi.un_430 sr.un.un_600 tl.un.un_A00 sw.un.un_600
+ // [0150]
+ 0x00003501, 0x19000b05, 0x00006b0f, 0x0000011c, // zu.un.un_200 es.gl.un_330 ceb.un.un_600 en.un.un_800
+ 0x00002842, 0x00006806, 0x16001702, 0x00006e03, // sw.un.un_C00 ig.un.un_400 sr.hr.un_220 hmn.un.un_300
+ 0x00004a1c, 0x00004a0f, 0x00003f06, 0x00003f15, // yo.un.un_800 yo.un.un_600 af.un.un_400 af.un.un_700
+ 0x09001302, 0x00002b24, 0x00000f01, 0x00000f03, // bh.hi.un_220 vi.un.un_900 lv.un.un_200 lv.un.un_300
+ // [0160]
+ 0x00001837, 0x08000205, 0x09001c02, 0x1700162c, // ga.un.un_B00 da.no.un_330 mr.hi.un_220 hr.sr.un_990
+ 0x0200081b, 0x00002a0f, 0x1c000914, 0x09000d2c, // no.da.un_770 mt.un.un_600 hi.mr.un_660 ne.hi.un_990
+ 0x16001734, 0x00003237, 0x00000d01, 0x1c000902, // sr.hr.un_A80 bs.un.un_B00 ne.un.un_200 hi.mr.un_220
+ 0x0000110a, 0x00001a01, 0x00001801, 0x17001609, // ro.un.un_500 tl.un.un_200 ar.un.un_200 hr.sr.un_440
+ // [0170]
+ 0x0d002d0e, 0x0b000a36, 0x00003506, 0x00001024, // sk.cs.un_550 pt.es.un_AA0 zu.un.un_400 be.un.un_900
+ 0x00000b42, 0x00000a42, 0x09000d13, 0x00001f37, // es.un.un_C00 pt.un.un_C00 ne.hi.un_650 cy.un.un_B00
+ 0x00002315, 0x0000040f, 0x00000737, 0x00005537, // ca.un.un_700 ru.un.un_600 bg.un.un_B00 rw.un.un_B00
+ 0x09000d14, 0x00003f03, 0x00001103, 0x00002815, // ne.hi.un_660 af.un.un_300 ro.un.un_300 sw.un.un_700
+ // [0180]
+ 0x1c000909, 0x00000224, 0x00000a06, 0x0a000b36, // hi.mr.un_440 ja.un.un_900 mk.un.un_400 es.pt.un_AA0
+ 0x00005524, 0x00001c1c, 0x1c001309, 0x0000252d, // rw.un.un_900 mr.un.un_800 bh.mr.un_440 eu.un.un_A00
+ 0x00000237, 0x1e001c04, 0x0000211c, 0x00000e2d, // da.un.un_B00 id.ms.un_320 jw.un.un_800 is.un.un_A00
+ 0x0000531c, 0x08000223, 0x00006b37, 0x00005215, // ht.un.un_800 da.no.un_880 ceb.un.un_B00 ha.un.un_700
+ // [0190]
+ 0x00000f1c, 0x00002d01, 0x00000242, 0x17001605, // lv.un.un_800 sk.un.un_200 da.un.un_C00 hr.sr.un_330
+ 0x00000801, 0x0000082d, 0x1c000923, 0x00000d2d, // uk.un.un_200 uk.un.un_A00 hi.mr.un_880 ne.un.un_A00
+ 0x17001622, 0x00000f0f, 0x00001101, 0x0000210f, // hr.sr.un_870 lv.un.un_600 ro.un.un_200 jw.un.un_600
+ 0x00001306, 0x1e001c05, 0x00001e42, 0x00003101, // et.un.un_400 id.ms.un_330 ms.un.un_C00 az.un.un_200
+ // [01a0]
+ 0x00003f37, 0x00001e15, 0x00002101, 0x0000091c, // af.un.un_B00 ms.un.un_700 fa.un.un_200 pl.un.un_800
+ 0x16003223, 0x00002b15, 0x00000d24, 0x00000637, // bs.hr.un_880 vi.un.un_700 ne.un.un_900 de.un.un_B00
+ 0x3200172c, 0x00006401, 0x00001701, 0x00000715, // sr.bs.un_990 lg.un.un_200 sr.un.un_200 it.un.un_700
+ 0x00001e0f, 0x19000a23, 0x19000b23, 0x00000a15, // ms.un.un_600 pt.gl.un_880 es.gl.un_880 mk.un.un_700
+ // [01b0]
+ 0x00000906, 0x00006e15, 0x00006e42, 0x0b000a1b, // pl.un.un_400 hmn.un.un_700 hmn.un.un_C00 pt.es.un_770
+ 0x00002106, 0x00001e2d, 0x16001721, 0x00001e37, // jw.un.un_400 ms.un.un_A00 sr.hr.un_860 ms.un.un_B00
+ 0x00000403, 0x1c000d14, 0x00001806, 0x00001037, // ru.un.un_300 ne.mr.un_660 ga.un.un_400 be.un.un_B00
+ 0x00000a01, 0x0000120a, 0x00006842, 0x00002024, // mk.un.un_200 ur.un.un_500 ig.un.un_C00 sq.un.un_900
+ // [01c0]
+ 0x00006b42, 0x1c000d0e, 0x00002306, 0x1c000d35, // ceb.un.un_C00 ne.mr.un_550 ca.un.un_400 ne.mr.un_A90
+ 0x0000061c, 0x1e001c09, 0x00001e1c, 0x0000210a, // de.un.un_800 id.ms.un_440 ms.un.un_800 jw.un.un_500
+ 0x0d001c12, 0x00000a24, 0x00006b01, 0x00000537, // mr.ne.un_640 mk.un.un_900 ceb.un.un_200 fr.un.un_B00
+ 0x00001c01, 0x0000052d, 0x0000122d, 0x19000a2c, // id.un.un_200 fr.un.un_A00 hu.un.un_A00 pt.gl.un_990
+ // [01d0]
+ 0x00004a15, 0x0000230f, 0x00005201, 0x0900130c, // yo.un.un_700 ca.un.un_600 ha.un.un_200 bh.hi.un_530
+ 0x0a000b23, 0x00006801, 0x00006837, 0x0000272d, // es.pt.un_880 ig.un.un_200 ig.un.un_B00 gd.un.un_A00
+ 0x00003f0f, 0x0000060f, 0x00002a37, 0x00002303, // af.un.un_600 de.un.un_600 mt.un.un_B00 ca.un.un_300
+ 0x00001f0a, 0x00002515, 0x1c000d19, 0x00003542, // cy.un.un_500 eu.un.un_700 ne.mr.un_750 zu.un.un_C00
+ // [01e0]
+ 0x00001803, 0x0000192d, 0x0000250f, 0x0000351c, // ga.un.un_300 gl.un.un_A00 eu.un.un_600 zu.un.un_800
+ 0x00002737, 0x0000060a, 0x00000837, 0x00005337, // gd.un.un_B00 de.un.un_500 uk.un.un_B00 ht.un.un_B00
+ 0x16003236, 0x00002001, 0x00002915, 0x0000100a, // bs.hr.un_AA0 sq.un.un_200 sl.un.un_700 be.un.un_500
+ 0x00006815, 0x1c001336, 0x19000b36, 0x0a001923, // ig.un.un_700 bh.mr.un_AA0 es.gl.un_AA0 gl.pt.un_880
+ // [01f0]
+ 0x09000d36, 0x00002d24, 0x09001313, 0x09001335, // ne.hi.un_AA0 sk.un.un_900 bh.hi.un_650 bh.hi.un_A90
+ 0x0900130e, 0x00006424, 0x32001636, 0x09001336, // bh.hi.un_550 lg.un.un_900 hr.bs.un_AA0 bh.hi.un_AA0
+ 0x0800022c, 0x00000b01, 0x0b000a22, 0x00002437, // da.no.un_990 es.un.un_200 pt.es.un_870 yi.un.un_B00
+ 0x0000312d, 0x00000437, 0x2d000d13, 0x0000032d, // az.un.un_A00 fi.un.un_B00 cs.sk.un_650 nl.un.un_A00
+ // [0200]
+ 0x17001604, 0x00000124, 0x19000b2c, 0x0000070f, // hr.sr.un_320 en.un.un_900 es.gl.un_990 bg.un.un_600
+ 0x00001601, 0x00001a15, 0x00002824, 0x00000a0a, // hr.un.un_200 tl.un.un_700 sw.un.un_900 mk.un.un_500
+ 0x00002724, 0x00001b03, 0x00001106, 0x1c001e05, // gd.un.un_900 tr.un.un_300 ro.un.un_400 ms.id.un_330
+ 0x0d002d35, 0x00001b24, 0x08000209, 0x00001c2d, // sk.cs.un_A90 tr.un.un_900 da.no.un_440 id.un.un_A00
+ // [0210]
+ 0x32001723, 0x0000062d, 0x00003b01, 0x00002706, // sr.bs.un_880 de.un.un_A00 so.un.un_200 gd.un.un_400
+ 0x0000681c, 0x0000102d, 0x0000680f, 0x00000c15, // ig.un.un_800 lt.un.un_A00 ig.un.un_600 sv.un.un_700
+ 0x00000803, 0x2d000d05, 0x00005306, 0x00003537, // uk.un.un_300 cs.sk.un_330 ht.un.un_400 zu.un.un_B00
+ 0x0000530a, 0x32001623, 0x00001642, 0x00002a1c, // ht.un.un_500 hr.bs.un_880 hr.un.un_C00 mt.un.un_800
+ // [0220]
+ 0x00000f2d, 0x2d000d36, 0x00002b06, 0x09000d08, // lv.un.un_A00 cs.sk.un_AA0 vi.un.un_400 ne.hi.un_430
+ 0x0000161c, 0x02000805, 0x1000062c, 0x0a000b2c, // hr.un.un_800 no.da.un_330 de.lt.un_990 es.pt.un_990
+ 0x09001304, 0x0000040a, 0x32001635, 0x00000e24, // bh.hi.un_320 fi.un.un_500 hr.bs.un_A90 is.un.un_900
+ 0x0d002d1a, 0x16001707, 0x00006e1c, 0x00001a0a, // sk.cs.un_760 sr.hr.un_420 hmn.un.un_800 tl.un.un_500
+ // [0230]
+ 0x1600172c, 0x16003222, 0x00001b15, 0x19000a1b, // sr.hr.un_990 bs.hr.un_870 tr.un.un_700 pt.gl.un_770
+ 0x09000d02, 0x00003b2d, 0x00002337, 0x1c001e35, // ne.hi.un_220 so.un.un_A00 ca.un.un_B00 ms.id.un_A90
+ 0x09000d04, 0x00000303, 0x0d000909, 0x09000d23, // ne.hi.un_320 nl.un.un_300 hi.ne.un_440 ne.hi.un_880
+ 0x1c000913, 0x00001901, 0x1c001e0e, 0x00001b0a, // hi.mr.un_650 gl.un.un_200 ms.id.un_550 tr.un.un_500
+ // [0240]
+ 0x1c001e07, 0x1700161b, 0x00000106, 0x0d000902, // ms.id.un_420 hr.sr.un_770 en.un.un_400 hi.ne.un_220
+ 0x1e001c34, 0x00005501, 0x00005237, 0x00006824, // id.ms.un_A80 rw.un.un_200 ha.un.un_B00 ig.un.un_900
+ 0x19000b34, 0x00000c01, 0x00006b1c, 0x16001735, // es.gl.un_A80 sv.un.un_200 ceb.un.un_800 sr.hr.un_A90
+ 0x00002703, 0x0000270f, 0x00003103, 0x0000202d, // gd.un.un_300 gd.un.un_600 az.un.un_300 sq.un.un_A00
+ // [0250]
+ 0x00001e24, 0x0b001935, 0x00003b37, 0x1c000d02, // ms.un.un_900 gl.es.un_A90 so.un.un_B00 ne.mr.un_220
+ 0x00002701, 0x2d000d2c, 0x00002503, 0x0a001904, // gd.un.un_200 cs.sk.un_990 eu.un.un_300 gl.pt.un_320
+ 0x00000103, 0x0000182d, 0x0d00091b, 0x00001201, // en.un.un_300 ga.un.un_A00 hi.ne.un_770 hu.un.un_200
+ 0x0a001935, 0x00000e37, 0x00002a01, 0x00000e06, // gl.pt.un_A90 is.un.un_B00 mt.un.un_200 is.un.un_400
+ // [0260]
+ 0x0000170a, 0x1e001c2b, 0x1c000934, 0x09001c14, // sr.un.un_500 id.ms.un_980 hi.mr.un_A80 mr.hi.un_660
+ 0x19000a02, 0x0d000913, 0x00002d06, 0x00006437, // pt.gl.un_220 hi.ne.un_650 sk.un.un_400 lg.un.un_B00
+ 0x0a001936, 0x0000130f, 0x0a000b22, 0x00000101, // gl.pt.un_AA0 et.un.un_600 es.pt.un_870 en.un.un_200
+ 0x00002a15, 0x02000823, 0x19000a09, 0x00000c0f, // mt.un.un_700 no.da.un_880 pt.gl.un_440 sv.un.un_600
+ // [0270]
+ 0x0900130d, 0x0a00191b, 0x00002003, 0x0000070a, // bh.hi.un_540 gl.pt.un_770 sq.un.un_300 bg.un.un_500
+ 0x2d000d09, 0x00001e0a, 0x00002137, 0x00000e01, // cs.sk.un_440 ms.un.un_500 jw.un.un_B00 is.un.un_200
+ 0x00002903, 0x0800021b, 0x0000250a, 0x0000350f, // sl.un.un_300 da.no.un_770 eu.un.un_500 zu.un.un_600
+ 0x0000200f, 0x2d000d14, 0x00000201, 0x1c001e2a, // sq.un.un_600 cs.sk.un_660 da.un.un_200 ms.id.un_970
+ // [0280]
+ 0x0b001922, 0x1c001e13, 0x00000501, 0x00001a0f, // gl.es.un_870 ms.id.un_650 fr.un.un_200 tl.un.un_600
+ 0x0b000a2a, 0x1c001e19, 0x00002d03, 0x0d002d21, // pt.es.un_970 ms.id.un_750 sk.un.un_300 sk.cs.un_860
+ 0x1300091b, 0x0a000b08, 0x0000550a, 0x1c001335, // hi.bh.un_770 es.pt.un_430 rw.un.un_500 bh.mr.un_A90
+ 0x00001924, 0x0a000b34, 0x09000d0e, 0x0000680a, // gl.un.un_900 es.pt.un_A80 ne.hi.un_550 ig.un.un_500
+ // [0290]
+ 0x1c001314, 0x13000936, 0x0900183f, 0x00000a03, // bh.mr.un_660 hi.bh.un_AA0 ga.pl.un_B90 mk.un.un_300
+ 0x0000010a, 0x1600322c, 0x0a001934, 0x00001a06, // iw.un.un_500 bs.hr.un_990 gl.pt.un_A80 tl.un.un_400
+ 0x16001705, 0x02000809, 0x00001637, 0x02000834, // sr.hr.un_330 no.da.un_440 hr.un.un_B00 no.da.un_A80
+ 0x0b000a35, 0x0a00191a, 0x0000310a, 0x0a001908, // pt.es.un_A90 gl.pt.un_760 az.un.un_500 gl.pt.un_430
+ // [02a0]
+ 0x00001906, 0x0b00191b, 0x02000836, 0x16001723, // gl.un.un_400 gl.es.un_770 no.da.un_AA0 sr.hr.un_880
+ 0x3200162c, 0x00002406, 0x00003b0a, 0x0000282d, // hr.bs.un_990 yi.un.un_400 so.un.un_500 sw.un.un_A00
+ 0x00006b06, 0x00005503, 0x0000270a, 0x1600171a, // ceb.un.un_400 rw.un.un_300 gd.un.un_500 sr.hr.un_760
+ 0x00002d2d, 0x0000172d, 0x00000406, 0x09000d1a, // sk.un.un_A00 sr.un.un_A00 fi.un.un_400 ne.hi.un_760
+ // [02b0]
+ 0x0b00192c, 0x2d000d34, 0x0000552d, 0x3200170e, // gl.es.un_990 cs.sk.un_A80 rw.un.un_A00 sr.bs.un_550
+ 0x00001937, 0x00000b2d, 0x08000204, 0x0000280a, // gl.un.un_B00 es.un.un_A00 da.no.un_320 sw.un.un_500
+ 0x00003503, 0x08000208, 0x00002401, 0x19000b04, // zu.un.un_300 da.no.un_430 yi.un.un_200 es.gl.un_320
+ 0x00000c06, 0x00005206, 0x13000914, 0x0d00091a, // sv.un.un_400 ha.un.un_400 hi.bh.un_660 hi.ne.un_760
+ // [02c0]
+ 0x00001824, 0x1600321b, 0x09000d2a, 0x1e001c08, // ga.un.un_900 bs.hr.un_770 ne.hi.un_970 id.ms.un_430
+ 0x0000020a, 0x2d000d21, 0x32001736, 0x0d001c14, // da.un.un_500 cs.sk.un_860 sr.bs.un_AA0 mr.ne.un_660
+ 0x32001705, 0x13000d21, 0x00006803, 0x00000c0a, // sr.bs.un_330 ne.bh.un_860 ig.un.un_300 sv.un.un_500
+ 0x00001c06, 0x1c001e36, 0x32001605, 0x32001709, // id.un.un_400 ms.id.un_AA0 hr.bs.un_330 sr.bs.un_440
+ // [02d0]
+ 0x0000242d, 0x16001708, 0x0800020e, 0x08000234, // yi.un.un_A00 sr.hr.un_430 da.no.un_550 da.no.un_A80
+ 0x00001515, 0x1c00092c, 0x13000d05, 0x00002b2d, // un.un.un_700 hi.mr.un_990 ne.bh.un_330 vi.un.un_A00
+ 0x3200171a, 0x09000d21, 0x00001003, 0x0d00130e, // sr.bs.un_760 ne.hi.un_860 be.un.un_300 bh.ne.un_550
+ 0x1c001321, 0x0d002d2b, 0x2d000d19, 0x00001203, // bh.mr.un_860 sk.cs.un_980 cs.sk.un_750 hu.un.un_300
+ // [02e0]
+ 0x09000d05, 0x00002103, 0x0200082c, 0x0a000b2b, // ne.hi.un_330 fa.un.un_300 no.da.un_990 es.pt.un_980
+ 0x1300092c, 0x1300091a, 0x1e001c0c, 0x19000a21, // hi.bh.un_990 hi.bh.un_760 id.ms.un_530 pt.gl.un_860
+ 0x0d002d36, 0x00000203, 0x0d001309, 0x32001609, // sk.cs.un_AA0 da.un.un_300 bh.ne.un_440 hr.bs.un_440
+ 0x1600322b, 0x13000d2c, 0x0b000a14, 0x13000919, // bs.hr.un_980 ne.bh.un_990 pt.es.un_660 hi.bh.un_750
+ // [02f0]
+ 0x0d002d12, 0x09001312, 0x0d002d2c, 0x09001307, // sk.cs.un_640 bh.hi.un_640 sk.cs.un_990 bh.hi.un_420
+ 0x2d000d1a, 0x13000923, 0x19000a05, 0x00006e06, // cs.sk.un_760 hi.bh.un_880 pt.gl.un_330 hmn.un.un_400
+ 0x0d000905, 0x0000292d, 0x13001c1b, 0x32001735, // hi.ne.un_330 sl.un.un_A00 mr.bh.un_770 sr.bs.un_A90
+ 0x1c001e08, 0x02000813, 0x0a001914, 0x00000e0a, // ms.id.un_430 no.da.un_650 gl.pt.un_660 is.un.un_500
+ // [0300]
+ 0x00001603, 0x291617a0, 0x09000d1b, 0x0b001936, // hr.un.un_300 sr.hr.sl_322 ne.hi.un_770 gl.es.un_AA0
+ 0x00003124, 0x0800022a, 0x13000d04, 0x0000290a, // az.un.un_900 da.no.un_970 ne.bh.un_320 sl.un.un_500
+ 0x16001719, 0x02000814, 0x0000230a, 0x1c001e04, // sr.hr.un_750 no.da.un_660 ca.un.un_500 ms.id.un_320
+ 0x19000b09, 0x13000913, 0x19000b0e, 0x00000f06, // es.gl.un_440 hi.bh.un_650 es.gl.un_550 lv.un.un_400
+ // [0310]
+ 0x00000503, 0x1c001302, 0x09001c12, 0x0800020c, // fr.un.un_300 bh.mr.un_220 mr.hi.un_640 da.no.un_530
+ 0x00005301, 0x00005303, 0x00003106, 0x0a00192a, // ht.un.un_200 ht.un.un_300 az.un.un_400 gl.pt.un_970
+ 0x00001706, 0x0a001921, 0x1e001c21, 0x00001f06, // sr.un.un_400 gl.pt.un_860 id.ms.un_860 cy.un.un_400
+ 0x0b000a04, 0x1c001e1a, 0x0200081a, 0x1c001e0c, // pt.es.un_320 ms.id.un_760 no.da.un_760 ms.id.un_530
+ // [0320]
+ 0x02000835, 0x0b001904, 0x00006406, 0x00002506, // no.da.un_A90 gl.es.un_320 lg.un.un_400 eu.un.un_400
+ 0x0a001922, 0x0b000a34, 0x00003f0a, 0x0d002d22, // gl.pt.un_870 pt.es.un_A80 af.un.un_500 sk.cs.un_870
+ 0x00003b24, 0x09000d2b, 0x09001c04, 0x1300090e, // so.un.un_900 ne.hi.un_980 mr.hi.un_320 hi.bh.un_550
+ 0x0b000a0c, 0x0d001c35, 0x0000350a, 0x0d000912, // pt.es.un_530 mr.ne.un_A90 zu.un.un_500 hi.ne.un_640
+ // [0330]
+ 0x00002424, 0x09000d07, 0x0000090a, 0x2d000d04, // yi.un.un_900 ne.hi.un_420 pl.un.un_500 cs.sk.un_320
+ 0x0a000b07, 0x0d002d05, 0x09001c19, 0x0d002d09, // es.pt.un_420 sk.cs.un_330 mr.hi.un_750 sk.cs.un_440
+ 0x19000b2b, 0x0d002d34, 0x0b000a05, 0x3200172a, // es.gl.un_980 sk.cs.un_A80 pt.es.un_330 sr.bs.un_970
+ 0x00006403, 0x0000030a, 0x2d000d0e, 0x2d000d08, // lg.un.un_300 nl.un.un_500 cs.sk.un_550 cs.sk.un_430
+ // [0340]
+ 0x00002b1c, 0x0b001934, 0x0900131b, 0x09001c21, // vi.un.un_800 gl.es.un_A80 bh.hi.un_770 mr.hi.un_860
+ 0x09001c23, 0x2d000d07, 0x19000b35, 0x3200161b, // mr.hi.un_880 cs.sk.un_420 es.gl.un_A90 hr.bs.un_770
+ 0x19000b14, 0x1c000d1b, 0x09001c07, 0x1e001c0e, // es.gl.un_660 ne.mr.un_770 mr.hi.un_420 id.ms.un_550
+ 0x19000a34, 0x0d000936, 0x0a000b04, 0x0d001314, // pt.gl.un_A80 hi.ne.un_AA0 es.pt.un_320 bh.ne.un_660
+ // [0350]
+ 0x19000b2a, 0x17001634, 0x1700161a, 0x0800020d, // es.gl.un_970 hr.sr.un_A80 hr.sr.un_760 da.no.un_540
+ 0x0a000b09, 0x19000b08, 0x19000b07, 0x08000213, // es.pt.un_440 es.gl.un_430 es.gl.un_420 da.no.un_650
+ 0x0a001902, 0x1e1c1bd9, 0x0a001905, 0x00005203, // gl.pt.un_220 tr.id.ms_B87 gl.pt.un_330 ha.un.un_300
+ 0x0a00192c, 0x0b000a02, 0x02000808, 0x1c001e2b, // gl.pt.un_990 pt.es.un_220 no.da.un_430 ms.id.un_980
+ // [0360]
+ 0x0a000b02, 0x00001542, 0x0000520a, 0x09000d19, // es.pt.un_220 un.un.un_C00 ha.un.un_500 ne.hi.un_750
+ 0x0d001323, 0x00000206, 0x3200171b, 0x09001322, // bh.ne.un_880 da.un.un_400 sr.bs.un_770 bh.hi.un_870
+ 0x0d000923, 0x0d000904, 0x13001c14, 0x32001621, // hi.ne.un_880 hi.ne.un_320 mr.bh.un_660 hr.bs.un_860
+ 0x16001714, 0x32001614, 0x09001c05, 0x0d000919, // sr.hr.un_660 hr.bs.un_660 mr.hi.un_330 hi.ne.un_750
+ // [0370]
+ 0x00002a03, 0x0b001905, 0x00000b03, 0x1700162b, // mt.un.un_300 gl.es.un_330 es.un.un_300 hr.sr.un_980
+ 0x00000603, 0x0000241c, 0x0d002d13, 0x16003205, // de.un.un_300 yi.un.un_800 sk.cs.un_650 bs.hr.un_330
+ 0x0b000a08, 0x16003235, 0x0200082b, 0x00002b01, // pt.es.un_430 bs.hr.un_A90 no.da.un_980 vi.un.un_200
+ 0x00001915, 0x08000207, 0x0a00192b, 0x00002a0a, // gl.un.un_700 da.no.un_420 gl.pt.un_980 mt.un.un_500
+ // [0380]
+ 0x1c001e21, 0x0d002d19, 0x13000d14, 0x3200162a, // ms.id.un_860 sk.cs.un_750 ne.bh.un_660 hr.bs.un_970
+ 0x0000050a, 0x1600170d, 0x00001624, 0x0b00192b, // fr.un.un_500 sr.hr.un_540 hr.un.un_900 gl.es.un_980
+ 0x1c001334, 0x09001319, 0x1c000d23, 0x1c000d13, // bh.mr.un_A80 bh.hi.un_750 ne.mr.un_880 ne.mr.un_650
+ 0x0d00130d, 0x0a001909, 0x19000a35, 0x00000c03, // bh.ne.un_540 gl.pt.un_440 pt.gl.un_A90 sv.un.un_300
+ // [0390]
+ 0x0d001c1a, 0x19000b21, 0x00001606, 0x00004a01, // mr.ne.un_760 es.gl.un_860 hr.un.un_400 yo.un.un_200
+ 0x0b001923, 0x1600320d, 0x00002906, 0x0a000b1b, // gl.es.un_880 bs.hr.un_540 sl.un.un_400 es.pt.un_770
+ 0x1c00131b, 0x19000a22, 0x08000214, 0x13000904, // bh.mr.un_770 pt.gl.un_870 da.no.un_660 hi.bh.un_320
+ 0x0900132c, 0x1c000d08, 0x1c00091b, 0x00003b03, // bh.hi.un_990 ne.mr.un_430 hi.mr.un_770 so.un.un_300
+ // [03a0]
+ 0x0000180a, 0x1e001c1a, 0x00001b06, 0x2d000d35, // ga.un.un_500 id.ms.un_760 tr.un.un_400 cs.sk.un_A90
+ 0x0d002d0d, 0x19000a14, 0x00000e03, 0x0a000b2a, // sk.cs.un_540 pt.gl.un_660 is.un.un_300 es.pt.un_970
+ 0x19000a08, 0x13000934, 0x09001c09, 0x13000d23, // pt.gl.un_430 hi.bh.un_A80 mr.hi.un_440 ne.bh.un_880
+ 0x0000012d, 0x13000d1b, 0x1c000904, 0x00003b06, // en.un.un_A00 ne.bh.un_770 hi.mr.un_320 so.un.un_400
+ // [03b0] --- double_langprob_start=03bb ---
+ 0x0d00131b, 0x1c000905, 0x00001524, 0x0a000b0d, // bh.ne.un_770 hi.mr.un_330 un.un.un_900 es.pt.un_540
+ 0x19000a0e, 0x2d000d2b, 0x0d001312, 0x09001305, // pt.gl.un_550 cs.sk.un_980 bh.ne.un_640 bh.hi.un_330
+ 0x32001604, 0x0d002d07, 0x0000190a, // hr.bs.un_320 sk.cs.un_420 gl.un.un_500
+ //
+ };
+
+// COMPILE_ASSERT(955 <= 4096, k_indirectbits_too_small);
+
+extern const CLD2TableSummary kDeltaOcta_obj = {
+ kDeltaOctaChrome0122,
+ kDeltaOctaChrome0122Ind,
+ kDeltaOctaChrome0122SizeOne,
+ kDeltaOctaChrome0122Size,
+ kDeltaOctaChrome0122KeyMask,
+ kDeltaOctaChrome0122BuildDate,
+ kDeltaOctaChrome0122RecognizedLangScripts,
+};
+
+static const uint32 kDeltaOctaChrome0122_2Size = 0; // Bucket count
+static const uint32 kDeltaOctaChrome0122_2KeyMask = 0xffffffff; // Mask hash key
+
+static const IndirectProbBucket4 kDeltaOctaChrome0122_2[kDeltaOctaChrome0122_2Size] = {
+ // hash_indirect[4], tokens[4] in UTF-8
+ };
+ // table_hash = ffff-ffff, unused_entries = 0 (0.00%)
+
+static const uint32 kDeltaOctaChrome0122_2SizeOne = 2; // One-langprob count
+static const uint32 kDeltaOctaChrome0122_2IndSize = 2; // Largest subscript
+static const uint32 kDeltaOctaChrome0122_2Ind[kDeltaOctaChrome0122_2IndSize] = {
+ // [0000] --- double_langprob_start=0002 ---
+ 0x00000000, 0x00000000, // -- --
+ //
+ };
+
+extern const CLD2TableSummary kDeltaOcta_obj2 = {
+ kDeltaOctaChrome0122_2,
+ kDeltaOctaChrome0122_2Ind,
+ kDeltaOctaChrome0122_2SizeOne,
+ kDeltaOctaChrome0122_2Size,
+ kDeltaOctaChrome0122_2KeyMask,
+ kDeltaOctaChrome0122BuildDate,
+ kDeltaOctaChrome0122RecognizedLangScripts,
+};
+
+} // End namespace CLD2
+
+// End of generated tables
diff --git a/browser/components/translation/cld2/internal/cld2_generated_distinctoctachrome0122.cc b/browser/components/translation/cld2/internal/cld2_generated_distinctoctachrome0122.cc
new file mode 100644
index 000000000..e8171caa3
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld2_generated_distinctoctachrome0122.cc
@@ -0,0 +1,2208 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Created by postproc-cld2 4.0 on 2014-02-02 09:48:19
+// From command line:
+// --cld2 --cc --just_read_raw --distinct_octa --standard --minchars=2
+// --mincount=2 --max_items_per_langscript=300 --flatmap --rr_alloc
+// --freq_alloc --boostcloseweakerpercent=00 --indirectbits=12 --thresh=224
+// --v25 --kentries=8 --tablename=DistinctOctaChrome0122
+// --remap=xxx-Latn=>ut-Latn tw-Latn=>ak-Latn nd-Latn=>nr-Latn
+// blu-Latn=>hmn-Latn nn-Latn=>no-Latn --include=af-Latn ar-Arab be-Cyrl
+// bg-Cyrl bs-Latn ca-Latn cs-Latn cy-Latn da-Latn de-Latn el-Grek
+// en-Latn es-Latn et-Latn fa-Arab fi-Latn fr-Latn ga-Latn gd-Latn
+// hi-Deva hr-Latn hu-Latn id-Latn is-Latn it-Latn iw-Hebr ja-Hani
+// ko-Hani lg-Latn lt-Latn lv-Latn mk-Cyrl ms-Latn nl-Latn no-Latn
+// pl-Latn pt-Latn ro-Latn ro-Cyrl ru-Cyrl rw-Latn sh-Cyrl sh-Latn sk-Latn
+// sl-Latn sr-Cyrl sv-Latn sw-Latn th-Thai tl-Latn tr-Latn uk-Cyrl
+// vi-Latn yi-Hebr zh-Hani zh-TW zhT-Hani sq-Latn az-Latn eu-Latn
+// bn-Beng gl-Latn ht-Latn mt-Latn sr-Latn ur-Arab bh-Deva mr-Deva
+// ne-Deva lg-Latn rw-Latn gd-Latn ut-Latn ut-Deva ceb-Latn blu-Latn
+// hmn-Latn jw-Latn so-Latn ig-Latn ha-Latn yo-Latn zu-Latn --ko_english
+// --force_to_lang_soft --nosoft_cram2 --nomsidlevel --shapeflatprob
+// --langpriorpercent=10 --skipnuc --noshapeforcetop --noshapeeventop
+// --noshapesteep2 --spread=15 --nodoubleclose --langcounts --writebin
+// --list_items=120 /tmp/xocta_octa2.utf8
+//
+// CLD2_pslangs
+//
+// See compact_lang_det.cc for usage
+//
+#include "cld2tablesummary.h"
+namespace CLD2 {
+
+static const uint32 kDistinctOctaChrome0122BuildDate = 20140202; // yyyymmdd
+
+
+// Of 4748 offered items into 8192 table entries:
+// 4447 filled (93%), 0 merged (0%), 301 dropped (6%)
+
+// Nil-grams: 19 languages
+// GREEK MALAYALAM TELUGU TAMIL GUJARATI THAI KANNADA PUNJABI
+// GEORGIAN SINHALESE ARMENIAN LAOTHIAN KHMER DHIVEHI CHEROKEE
+// SYRIAC LIMBU ORIYA INUKTITUT
+
+// Uni-grams: 4 languages
+// Japanese Korean Chinese ChineseT
+
+// Words/Quads: 18 languages in range DANISH..KINYARWANDA:
+// DANISH NORWEGIAN PORTUGUESE SPANISH CZECH CROATIAN SERBIAN
+// GALICIAN HINDI INDONESIAN MALAY NEPALI BIHARI MARATHI SLOVAK
+// BOSNIAN ZULU KINYARWANDA
+
+// TopLanguage TokenCount
+// DANISH 281
+// NORWEGIAN 281
+// PORTUGUESE 272
+// SPANISH 282
+// CZECH 280
+// CROATIAN 95
+// SERBIAN 504
+// GALICIAN 284
+// HINDI 280
+// INDONESIAN 279
+// MALAY 276
+// NEPALI 48
+// BIHARI 279
+// MARATHI 26
+// SLOVAK 281
+// BOSNIAN 150
+// ZULU 274
+// KINYARWANDA 275
+
+
+
+// Recognized language-script combinations [19]:
+static const char* const kDistinctOctaChrome0122RecognizedLangScripts =
+ "bh-Deva bs-Latn cs-Latn da-Latn es-Latn gl-Latn hi-Deva hr-Latn "
+ "id-Latn mr-Deva ms-Latn ne-Deva no-Latn pt-Latn rw-Latn sk-Latn "
+ "sr-Cyrl sr-Latn zu-Latn ";
+
+static const uint32 kDistinctOctaChrome0122Size = 2048; // Bucket count
+static const uint32 kDistinctOctaChrome0122KeyMask = 0xfffff800; // Mask hash key
+
+static const IndirectProbBucket4 kDistinctOctaChrome0122[kDistinctOctaChrome0122Size] = {
+ // hash_indirect[4], tokens[4] in UTF-8
+ {{0x53098802,0x4ae00803,0xb964a804,0x00000000}}, // [000] _añadir_, _kabupate, _použití_, --,
+ {{0x52907805,0x34427806,0xdfa54007,0x72918008}}, // _kuna_, _jen_, _desember_, _surah_,
+ {{0x93877009,0x4722700a,0x0442180b,0x00000000}}, // _bošnjačk, _बाड़े_, _secara__terus_, --,
+ {{0x5442780c,0xd4c0580d,0x00000000,0x00000000}}, // _len_, _http__gl_, --, --,
+ {{0x4a4e300e,0x2475980f,0xd2902010,0x00000000}}, // _के__नाम_, _dana__apr_, _inka_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x127ee811,0x82122011,0x4a967010,0x52b15012}}, // _izinto_, _lokhu_, _tanzania_, _mostrará_,
+ {{0x6291e813,0x0200c814,0x00000000,0x00000000}}, // _antal_, _nadie_, --, --,
+ {{0xb4427815,0x524b6816,0xb2ca9010,0x00000000}}, // _ben_, _tinjau__ahli_, _stade_, --,
+ {{0x1495e00c,0x626de806,0x00000000,0x00000000}}, // _sa__mi_, _tyto_, --, --,
+ {{0x73eb900c,0x5bfea017,0xdb93900f,0x00000000}}, // _mesta_, _sua__opinião_, _pjesama__tekstova_, --,
+ {{0x6290f816,0x00000000,0x00000000,0x00000000}}, // _bahawa_, --, --, --,
+ {{0x8317f811,0x7ec8e018,0x8a3c9019,0x00000000}}, // _ukuze_, _регистру_се_, _लॉग_, --,
+ {{0x04427811,0x39fe081a,0x00000000,0x00000000}}, // _gen_, _novament, --, --,
+ {{0x8442681b,0xe47a581c,0xdcca0818,0x6e05981b}}, // _klo_, _hvad__er_, _јануар__децембар_, _kendaraa,
+ {{0x0242101d,0x00000000,0x00000000,0x00000000}}, // _मामले_, --, --, --,
+ {{0x8291e81e,0x9a3e480e,0x00000000,0x00000000}}, // [010] _dotaz_, _भीम_, --, --,
+ {{0x8290100d,0x00000000,0x00000000,0x00000000}}, // _unha_, --, --, --,
+ {{0x36fee01d,0xa2f0b81c,0x1e73c019,0x00000000}}, // _करना_, _søg_, _सिर्फ_, --,
+ {{0x6d7f3018,0x6c02a019,0x00000000,0x00000000}}, // _јун_, _लॉग__इन_, --, --,
+ {{0x12248006,0x00000000,0x00000000,0x00000000}}, // _velké_, --, --, --,
+ {{0x627f0010,0xeb7c3011,0x92f85816,0x00000000}}, // _abandi_, _kusukela_, _dalam__laman_, --,
+ {{0x12f6d005,0x1213a011,0x0a84500e,0x00000000}}, // _pošalji__osobnu_, _kepha_, _भवन__में_, --,
+ {{0xc442780d,0x92c7d817,0x00000000,0x00000000}}, // _sen_, _comentár_comentár, --, --,
+ {{0x23945807,0xe46e7807,0x00000000,0x00000000}}, // _helse_, _seg__til_, --, --,
+ {{0xe451e01f,0xe594f011,0x13551009,0x0ac3a00e}}, // _poruke__na_, _unkulunk, _postignu_dogovor_, _जवाब__में_,
+ {{0xf236d818,0x36f9481d,0x03ea681c,0x00000000}}, // _tanjug_, _सकता_, _flot_, --,
+ {{0x7274f807,0x5a4bd81d,0x00000000,0x00000000}}, // _forskjel, _की__मौत_, --, --,
+ {{0x3d838820,0x00000000,0x00000000,0x00000000}}, // _reči_, --, --, --,
+ {{0x83ea0017,0x8237e01f,0x00000000,0x00000000}}, // _muito_, _promena_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x844c880d,0x00000000,0x00000000,0x00000000}}, // _xestión_, --, --, --,
+ {{0xe364c818,0x5bd00017,0xb3f85814,0xd254f821}}, // [020] _ekstovi__blogu_, _opinião_, _salud_, _no__artigo_,
+ {{0x92d81010,0x3225000b,0x92d58817,0xe44d6007}}, // _gihe_, _adakah_, _conosco_, _dersom__du_,
+ {{0xbb88f822,0x6c90581d,0xe68a7018,0x00000000}}, // _पर_, _लोगों_, _презимен, --,
+ {{0xa3a5a007,0x725af818,0x00000000,0x00000000}}, // _innlegg__svar_, _đilas_, --, --,
+ {{0xe4765812,0x0395c80c,0x0c14601d,0xd4b7d816}}, // _lugar__es_, _časť_, _दिया__है_, _versi__ke_,
+ {{0x45852016,0x92cae81c,0x00000000,0x00000000}}, // _pengguna_komersil_, _findes_, --, --,
+ {{0x827ee01c,0x00000000,0x00000000,0x00000000}}, // _gennem_, --, --, --,
+ {{0x32fd5823,0xb442a011,0x00000000,0x00000000}}, // _dana__tjedna_, _heb_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe2902011,0x02d8a011,0xf290a010,0x2eba5007}}, // _lika_, _kube_, _kuba_, _kjøpe_,
+ {{0x83eb9018,0x00000000,0x00000000,0x00000000}}, // _vesti_, --, --, --,
+ {{0x125b901f,0x12a61810,0x00000000,0x00000000}}, // _posle_, _ntushobo_kubona_, --, --,
+ {{0x8458780c,0x0b011810,0x00000000,0x00000000}}, // _izbový__byt_, _ukoreshe, --, --,
+ {{0x967cb024,0xb01cb006,0x00000000,0x00000000}}, // _se__registri, _se__registro, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb3414011,0x00000000,0x00000000,0x00000000}}, // _ukwenza_, --, --, --,
+ {{0x8c86200e,0x09f43006,0x42fc7807,0x00000000}}, // [030] _के__माध्यम_, _mají_, _lenge_, --,
+ {{0xebad3025,0x00000000,0x00000000,0x00000000}}, // _कार्यक्र_के_, --, --, --,
+ {{0x427f7814,0x1a3e701d,0xc3d9081c,0x7290a016}}, // _cuando_, _मैं_, _at__være_, _cuba_,
+ {{0xe46f581b,0x00000000,0x00000000,0x00000000}}, // _jawa__timur_, --, --, --,
+ {{0xebed9825,0x00000000,0x00000000,0x00000000}}, // _संस्कृति__के_, --, --, --,
+ {{0xed86b01f,0x00000000,0x00000000,0x00000000}}, // _vrednost_, --, --, --,
+ {{0xb3eb901f,0x00000000,0x00000000,0x00000000}}, // _mestu_, --, --, --,
+ {{0x3b88101d,0x44426810,0x00000000,0x00000000}}, // _यह_, _bwo_, --, --,
+ {{0x8a33e825,0x99f5b006,0x00000000,0x00000000}}, // _जाई_, _navíc_, --, --,
+ {{0xa2cb480c,0x62aab81c,0xd351b826,0x00000000}}, // _predaj_, _købe_, _synes__ikke_, --,
+ {{0xa2e6c00d,0x35dd7018,0x3303981c,0x00000000}}, // _da__súa_, _подаци__други_, _bruge__vores_, --,
+ {{0x6a3d581d,0xe3543016,0x0a69380e,0xe495f81c}}, // _रहा_, _selepas_, _पाकिस्ता_में_, _op__til_,
+ {{0x6ee2e818,0x00000000,0x00000000,0x00000000}}, // _он_, --, --, --,
+ {{0x52902011,0x00000000,0x00000000,0x00000000}}, // _sika_, --, --, --,
+ {{0x3556c01d,0x4a3d2819,0xbb21c80c,0x00000000}}, // _असहमत__बढ़िया_, _हॉट_, _komplime, --,
+ {{0xd49d500d,0xf2a7f811,0xebed6025,0x00000000}}, // _páxinas__que_, _ihubo_, _मध्यप्रद_के_, --,
+ {{0x2bf8180e,0x846a8018,0x00000000,0x00000000}}, // [040] _में__हम_, _povezane__vesti_, --, --,
+ {{0xf00bc018,0xf29bd80c,0x23ebe807,0x72b4680c}}, // _prijavi__komentar_, _chybu__upraviť_, _sette_, _dvoch_,
+ {{0x2dd77016,0x6a32980e,0x42d19807,0x00000000}}, // _janji__perniaga, _गौर_, _på__grunn_, --,
+ {{0x447c5820,0xec0a9825,0x00000000,0x00000000}}, // _html__kod_, _कहानी__के_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x64b3680d,0x31ed180c,0x00000000,0x00000000}}, // _concello__de_, _formulár, --, --,
+ {{0x32013018,0xc226701b,0x42249010,0x00000000}}, // _свиђа_, _terbaru__kali_, _mwaka_, --,
+ {{0x22d9800c,0x7cdeb011,0x00000000,0x00000000}}, // _hore_, _elikhulu_, --, --,
+ {{0x42918010,0x4f240806,0xba3e3818,0xd9fc5811}}, // _kora_, _našem_, _sreda__neregist, _iminyaka_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x02004811,0x72785811,0x00000000,0x00000000}}, // _kimi_, _omunye_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf442c80c,0xfed52818,0x74426810,0x248e601c}}, // _ked_, _код_, _uwo_, _af__og_,
+ {{0xd490a007,0x6d950818,0x19711806,0x00000000}}, // _av__den_, _slanje__lične_, _stažení_, --,
+ {{0xd218a01d,0xb9ee6810,0x00000000,0x00000000}}, // _पृष्ठ__हमारे_, _kubikora_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x9442b00c,0x34aea007,0x00000000,0x00000000}}, // [050] _vec_, _mer__enn_, --, --,
+ {{0x7d8c000c,0x66d2880e,0x00000000,0x00000000}}, // _počas_, _लोगिन_, --, --,
+ {{0x4c4f201d,0x00000000,0x00000000,0x00000000}}, // _उसने_, --, --, --,
+ {{0xbc0d300e,0x64429010,0xe2d90010,0x00000000}}, // _देर__तक_, _kwa_, _imbere_, --,
+ {{0x2bf6280e,0x00000000,0x00000000,0x00000000}}, // _में__जब_, --, --, --,
+ {{0xc3f8c804,0xd9f4300c,0x5b1d200d,0x22d99010}}, // _budu_, _majú_, _calquera_, _hose_,
+ {{0xd2c71008,0xc3954017,0xfafb5006,0x00000000}}, // _mana__mana_, _acesso_, _žádné_, --,
+ {{0x9290c814,0x00000000,0x00000000,0x00000000}}, // _duda_, --, --, --,
+ {{0x72d99010,0xbbf56025,0x00000000,0x00000000}}, // _mose_, _अच्__छा_, --, --,
+ {{0xb2d6f006,0xaa3e581d,0x00000000,0x00000000}}, // _přes_, _कविता__कोश_, --, --,
+ {{0xa2904808,0xca00f81b,0x00000000,0x00000000}}, // _ramai_, _surabaya_, --, --,
+ {{0x7291900d,0xd4429010,0x025b7817,0x00000000}}, // _nosa_, _bwa_, _avalia_, --,
+ {{0x29da001f,0x7f238817,0x00000000,0x00000000}}, // _subotica_, _semelhan, --, --,
+ {{0xb26c8806,0x6b7fc01f,0x5b194816,0x00000000}}, // _nahoru_, _posledic, _kewangan_, --,
+ {{0xc2d99010,0x00000000,0x00000000,0x00000000}}, // _bose_, --, --, --,
+ {{0xb3991814,0x6366d818,0x7bbb901d,0x0469a018}}, // _más_, _ponedelj, _हैं__तो_, _dodaci__gratis_,
+ {{0x0ba27819,0x226d2010,0x00000000,0x00000000}}, // [060] _रहा__है_, _icyo_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x84867025,0x5ebed012,0xbf7b081c,0xebea200e}}, // _तिवारी_, _mostrará__públicam, _denne__tråd_, _मन__के_,
+ {{0x5201a010,0x326cc81f,0x048f6827,0x00000000}}, // _kopi_, _delovi_, _ja__som_, --,
+ {{0xb38b5807,0xba390018,0x00000000,0x00000000}}, // _våre_, _četvrtak__neregist, --, --,
+ {{0x427f4004,0x00000000,0x00000000,0x00000000}}, // _jednou_, --, --, --,
+ {{0x7395001a,0xb4b16009,0x72907828,0x00000000}}, // _brasil_, _postovi__dan_, _anna_, --,
+ {{0xc2e5681b,0x3328a81c,0x00000000,0x00000000}}, // _rp__kamar_, _du__vores_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x92a67011,0x6c4ed81d,0x6200a00b,0x00000000}}, // _kanjani_, _जैसे_, _habib_, --,
+ {{0x42f62023,0xd424701b,0x00000000,0x00000000}}, // _dan__dana_, _ketentua_serta_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x60d7c818,0x00000000,0x00000000,0x00000000}}, // _neregist_korisnik_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x1b262811,0x92ad0018,0x00000000,0x00000000}}, // _ukuphila_, _нису_, --, --,
+ {{0x03207816,0x20fe200e,0x00000000,0x00000000}}, // [070] _ianya_, _के__गांव_, --, --,
+ {{0x2f247006,0x00000000,0x00000000,0x00000000}}, // _část_, --, --, --,
+ {{0xa9f36024,0xc48e601c,0x34bba016,0x9cf75025}}, // _prosvjed, _af__en_, _sama__ada_, _नामकरण_,
+ {{0x62905816,0x1e5d800d,0x34c58029,0xb296b810}}, // _sila_, _traballa, _trabalho_, _amakuru_,
+ {{0x9248d817,0x00000000,0x00000000,0x00000000}}, // _quem_, --, --, --,
+ {{0x6201901b,0x22d9e807,0xc344c01e,0xb46ab017}}, // _musik_, _liten_, _inzerce_, _com__uma_,
+ {{0x12c20003,0x00000000,0x00000000,0x00000000}}, // _soalnya_, --, --, --,
+ {{0x52396818,0x62df780d,0x00000000,0x00000000}}, // _коју_, _na__pola_, --, --,
+ {{0x032c181c,0x13df300d,0x2ac14004,0x00000000}}, // _så__meget_, _esta__páxina_, _lepší_, --,
+ {{0x72178811,0x00000000,0x00000000,0x00000000}}, // _ngokuvam, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xefa7281b,0xc4a9d823,0xf2e1a02a,0x93966007}}, // _komentar_, _napisao__la_, _kolovoz_, _norske_,
+ {{0xf2127815,0xda6af00e,0x32fc0815,0x00000000}}, // _cunha_, _कुछ__दिन_, _debes__estar_, --,
+ {{0x4442d80d,0x0495e027,0x0318201c,0x00000000}}, // _lle_, _potrebuj, _at__finde_, --,
+ {{0xe26cf811,0x00000000,0x00000000,0x00000000}}, // _ukholo_, --, --, --,
+ {{0x82f7301b,0x00000000,0x00000000,0x00000000}}, // _dan__bisa_, --, --, --,
+ {{0x2442f807,0x00000000,0x00000000,0x00000000}}, // [080] _meg_, --, --, --,
+ {{0x2e9a5818,0x7a3d301d,0xf229c806,0xc9f3480c}}, // _што__је_, _हुआ_, _díky_, _prostred,
+ {{0x12907811,0xa442d807,0x92e2701f,0xde292018}}, // _mina_, _ble_, _odgovori__prijavi_, _дан_,
+ {{0xe475f80f,0x00000000,0x00000000,0x00000000}}, // _dana__okt_, --, --, --,
+ {{0x83ea780d,0xff185818,0xc442d806,0x00000000}}, // _xunta_, _да__би_, _dle_, --,
+ {{0x22907811,0x920ee806,0xd442d817,0xb2240007}}, // _nina_, _při_, _ele_, _klikk_,
+ {{0xc2613817,0x4285e818,0x7290f80d,0x42126017}}, // _não_, _na__nadji_, _auga_, _julho_,
+ {{0xca331022,0x50dd7811,0x00000000,0x00000000}}, // _टेक_, _izindlel, --, --,
+ {{0x9442f807,0x27a1382b,0x00000000,0x00000000}}, // _deg_, _अन्तर्रा, --, --,
+ {{0xd20ee806,0x00000000,0x00000000,0x00000000}}, // _tři_, --, --, --,
+ {{0x52fe601c,0x00000000,0x00000000,0x00000000}}, // _brugte_, --, --, --,
+ {{0x4471780d,0x4ae9c818,0x4a3dc822,0x00000000}}, // _que__lle_, _политика_, _तुम_, --,
+ {{0xe4956010,0x0a4d480e,0x6290b029,0xd25ab807}}, // _ni__na_, _के__देश_, _ficar_, _hjelpe_,
+ {{0x95c0580f,0x54625029,0x00000000,0x00000000}}, // _uz__obavezno_, _não__se_, --, --,
+ {{0xa7803821,0x00000000,0x00000000,0x00000000}}, // _subvenci, --, --, --,
+ {{0xe2907810,0x00000000,0x00000000,0x00000000}}, // _zina_, --, --, --,
+ {{0xf2007811,0xc278c00d,0x00000000,0x00000000}}, // [090] _yini_, _ler__máis_, --, --,
+ {{0x6b69f00d,0x4a4c1819,0x83ead826,0x00000000}}, // _ligazóns_, _हो__गया_, _slet_, --,
+ {{0xa38ab81c,0x74ba5002,0x7dc2b81b,0x326cf811}}, // _børn_, _como__si_, _kebijaka, _jehova_,
+ {{0x9a3d301d,0x04780007,0xd4717817,0x647dd00d}}, // _हुई_, _blir__det_, _que__ele_, _xullo__de_,
+ {{0x9f78780c,0x00000000,0x00000000,0x00000000}}, // _všetky__práva_, --, --, --,
+ {{0x8442f807,0xd28da023,0xc28fe029,0x83eaf01b}}, // _seg_, _nja__naslov_, _que__não_, _kantor_,
+ {{0x0a3d301d,0xadde7018,0x325a600d,0x00000000}}, // _हुए_, _да__су_, _xullo_, --,
+ {{0xfebce820,0xd2132014,0x00000000,0x00000000}}, // _verovatn, _muchas_, --, --,
+ {{0x12613817,0x48d4882c,0x42be201f,0x9e10800d}}, // _são_, _चर्चित_, _pre__dana_, _igual__pódense_,
+ {{0xc2d91011,0x7bdce80e,0x00000000,0x00000000}}, // _kuze_, _पलायन_, --, --,
+ {{0x337b9006,0xbcd0301c,0x4464d010,0xfc66a01c}}, // _skladem_, _flexbloc, _bbc__bbc_, _hurtigt_,
+ {{0x4a88b81d,0x1f49d818,0x00000000,0x00000000}}, // _दुनिया__खेल_, _производ, --, --,
+ {{0x7e72e818,0x30734011,0x00000000,0x00000000}}, // _се_, _bakajeho, --, --,
+ {{0xb406f818,0xa2566006,0x00000000,0x00000000}}, // _profil__poruke_, _více__hodin_, --, --,
+ {{0x19aa2018,0xdaf3b81c,0x00000000,0x00000000}}, // _нови_, _yderlige, --, --,
+ {{0xa1f7980c,0x00000000,0x00000000,0x00000000}}, // _najlepši, --, --, --,
+ {{0xa075a80d,0x00000000,0x00000000,0x00000000}}, // [0a0] _navegaci_ferramen, --, --, --,
+ {{0x2443100c,0x74420010,0x226cf810,0x00000000}}, // _cez_, _iti_, _yehova_, --,
+ {{0x9290b011,0x00000000,0x00000000,0x00000000}}, // _indawo_, --, --, --,
+ {{0xb2d92011,0x22502018,0xc49f7012,0x00000000}}, // _kuye_, _нема_, _escribe__una_, --,
+ {{0xaac7f806,0xb491e017,0x46e0600b,0x00000000}}, // _kteří_, _em__um_, _linkedin__perjanji, --,
+ {{0x3c4ee01d,0x3213e811,0x227ee807,0x00000000}}, // _करें_, _wethu_, _funnet_, --,
+ {{0xefb10018,0x0b8f8022,0x4298080c,0x53af3007}}, // _pošaljit_komentar_, _था_, _vďaka_, _løpet_,
+ {{0x14b78007,0x6be1f80d,0x00000000,0x00000000}}, // _samarbei_med_, _ligazón__informac, --, --,
+ {{0xa6f8402d,0xa4677005,0x5290a016,0x00000000}}, // _शंका_, _prije__minuta_, _sabah_, --,
+ {{0x8d704818,0x9a5f8014,0x6326681c,0x6213e811}}, // _reputaci_moć_, _pregunta_, _at__vide_, _zethu_,
+ {{0xd399601c,0xf4420010,0x7288a011,0xc200c810}}, // _læs_, _ati_, _ekhasini_, _indi_,
+ {{0xf2d75006,0x00000000,0x00000000,0x00000000}}, // _třeba_, --, --, --,
+ {{0x5291e82e,0x00000000,0x00000000,0x00000000}}, // _kota_, --, --, --,
+ {{0x3b940018,0x5307e81a,0x42cb5002,0x0496c018}}, // _komentar_pošaljit, _seu__nome_, _puedes_, _preporuč_ne_,
+ {{0x340b0005,0x02fcf807,0x00000000,0x00000000}}, // _osobnu__poruku_, _legge_, --, --,
+ {{0xf606382f,0x28c5882c,0x58c6e007,0x00000000}}, // _प्रतिक्र, _उदयपुर_, _innehold, --,
+ {{0x22fce81b,0x2200a010,0x0290a010,0x2be7a00e}}, // [0b0] _banget_, _bibi_, _biba_, _से__हम_,
+ {{0xce35000d,0xbeed0017,0x00000000,0x00000000}}, // _mulleres_, _mulheres_, --, --,
+ {{0x1c60b808,0x99224825,0x00000000,0x00000000}}, // _rujukan_, _इतिहासका, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf2efd80d,0x1452d01f,0x9290301b,0x00000000}}, // _galicia_, _strana__od_, _pajak_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6290c807,0x00000000,0x00000000,0x00000000}}, // _enda_, --, --, --,
+ {{0x433fb80d,0xd496f80d,0xca33601d,0x4389c81c}}, // _dispoñib_baixo_, _xa__que_, _किए_, _søgning_,
+ {{0x02ca781c,0xeb8e8022,0x72d9201d,0x00000000}}, // _finde_, _वे_, _मूवी_, --,
+ {{0x4477980d,0x00000000,0x00000000,0x00000000}}, // _informac_da_, --, --, --,
+ {{0x1c398004,0x78d0101d,0x00000000,0x00000000}}, // _kombinac, _बच्चों_, --, --,
+ {{0x62fc7802,0xa2eeb82a,0x02d8e014,0x00000000}}, // _tengo_, _štimac_, _manera_, --,
+ {{0x5e68c01a,0x00000000,0x00000000,0x00000000}}, // _menciona, --, --, --,
+ {{0x53ea6803,0x12d8b010,0xd47fa80d,0x00000000}}, // _quote_, _bice_, _unha__das_, --,
+ {{0xa2ef801f,0x3b48700c,0x22d8a011,0x00000000}}, // _pogledaj__javni_, _musíte__prihlási, _sibe_, --,
+ {{0xe2a7f811,0x92d83010,0xcb03d018,0x56b1a019}}, // [0c0] _ukuba_, _kamena_, _можете_, _मस्ती_,
+ {{0x5229601c,0x00000000,0x00000000,0x00000000}}, // _væk_, --, --, --,
+ {{0x327f7817,0x4290b029,0x00000000,0x00000000}}, // _quando_, _fica_, --, --,
+ {{0x02905808,0x00000000,0x00000000,0x00000000}}, // _ialah_, --, --, --,
+ {{0x8442000d,0x929b400c,0x00000000,0x00000000}}, // _moi_, _izbový_, --, --,
+ {{0xa283d811,0x00000000,0x00000000,0x00000000}}, // _ngokuba_, --, --, --,
+ {{0x1273601c,0x00000000,0x00000000,0x00000000}}, // _mænd_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x2eb3f818,0x00000000,0x00000000,0x00000000}}, // _која__је_, --, --, --,
+ {{0x93f98008,0xf373c00c,0xf2c9e010,0x00000000}}, // _turut_, _žiadne_, _vyacu__umva_, --,
+ {{0x3c59f01b,0x00000000,0x00000000,0x00000000}}, // _listrik_, --, --, --,
+ {{0x5497e014,0xfc922818,0x00000000,0x00000000}}, // _si__no_, _овде_, --, --,
+ {{0x2309c017,0x3b8ea830,0x0201801b,0xd3a2382e}}, // _também_, _लग_, _kirim_, _tampak_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb4444017,0x2cd6c018,0x1d46c018,0xc2d8c813}}, // _em_, _српски_, _српске_, _lide_,
+ {{0x72a8781c,0x00000000,0x00000000,0x00000000}}, // [0d0] _en__lille_, --, --, --,
+ {{0x2212b014,0x00000000,0x00000000,0x00000000}}, // _mucho_, --, --, --,
+ {{0x8a885803,0x02c4b80d,0x9632e00d,0x00000000}}, // _kegiatan_, _ademais_, _coñeceme, --,
+ {{0x16d1300a,0x00000000,0x00000000,0x00000000}}, // _खातिर_, --, --, --,
+ {{0x0cb7280e,0x7417c011,0x00000000,0x00000000}}, // _में__कहीं_, _kudingek, --, --,
+ {{0xf4912813,0x54421011,0x00000000,0x00000000}}, // _at__der_, _joh_, --, --,
+ {{0x82fc781c,0xebc07825,0x12ca1016,0x00000000}}, // _penge_, _शक्ति__के_, _mohd_, --,
+ {{0xe2d88002,0x00000000,0x00000000,0x00000000}}, // _mejores_, --, --, --,
+ {{0xda564010,0xe489d007,0x00000000,0x00000000}}, // _ubufasha__buboneka_, _finner__du_, --, --,
+ {{0x07281818,0xff6ca817,0x449fd81c,0x00000000}}, // _због_, _detalhes_, _indlæg__af_, --,
+ {{0x84444016,0x13a2001f,0x00000000,0x00000000}}, // _rm_, _gripa_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb201801f,0xb303b00d,0x00000000,0x00000000}}, // _kurir_, _desde__túa_, --, --,
+ {{0x44b08006,0x6c4e081d,0x00000000,0x00000000}}, // _můžete_, _कहते_, --, --,
+ {{0xb3232018,0x4bb3f01c,0x0abc8825,0x00000000}}, // _odgovora__poslednj, _se__billeder, _अभाव__में_, --,
+ {{0x6c6e582e,0x28464017,0x272e800f,0xf3eb9007}}, // _amerika_, _qualidad, _zoran__bibanovi, _posta_,
+ {{0xf3eae803,0x00000000,0x00000000,0x00000000}}, // [0e0] _lintas_, --, --, --,
+ {{0xb4444017,0xc201f01c,0x00000000,0x00000000}}, // _um_, _butik_, --, --,
+ {{0xff8bf017,0x4edc5818,0xc386d814,0x00000000}}, // _além_, _више__од_, _ayer_, --,
+ {{0x13157008,0x00000000,0x00000000,0x00000000}}, // _yang__boleh_, --, --, --,
+ {{0x62907816,0x4495c014,0x00000000,0x00000000}}, // _kanak_, _no__hay_, --, --,
+ {{0x83f8f811,0x00000000,0x00000000,0x00000000}}, // _angu_, --, --, --,
+ {{0x74422003,0x62d8c813,0xfbdba015,0xfc60b802}}, // _kok_, _vide_, _alumnado_, _este__artículo_,
+ {{0xde5d780c,0x00000000,0x00000000,0x00000000}}, // _nehnuteľ, --, --, --,
+ {{0xe2d8c00d,0xb212780d,0x3ee2900d,0x349e2006}}, // _galega_, _nunha_, _adiciona_consulte_, _od__kč_,
+ {{0xc3f8f811,0x00000000,0x00000000,0x00000000}}, // _engu_, --, --, --,
+ {{0x0b6b681c,0x00000000,0x00000000,0x00000000}}, // _annoncer, --, --, --,
+ {{0x42bb101f,0x00000000,0x00000000,0x00000000}}, // _mesec__dana_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb2e8501f,0x42315012,0x00000000,0x00000000}}, // _poslednj, _la__autoprom, --, --,
+ {{0x9f8bf006,0x2c75b006,0xa303b006,0x00000000}}, // _své_, _hlavní_, _opravdu_, --,
+ {{0x4497e01c,0x00000000,0x00000000,0x00000000}}, // _ud__af_, --, --, --,
+ {{0x48c7d825,0xf440d023,0x00000000,0x00000000}}, // [0f0] _भोजपुर_, _lokacija__sarajevo_, --, --,
+ {{0xc4aa080c,0x00000000,0x00000000,0x00000000}}, // _prísluše, --, --, --,
+ {{0xf371f00b,0xa25b7808,0xf50e7825,0xd2011010}}, // _lagi__dengan_, _amalan_, _भी__ज्यादा_, _kabiri_,
+ {{0xd2ca781b,0x9291a817,0x8bc0c01d,0x00000000}}, // _bunda_, _título__comentár, _करने__का_, --,
+ {{0x74ba9007,0xc698b00c,0x00000000,0x00000000}}, // _logg__inn_, _košíka_, --, --,
+ {{0x0212780d,0x641e681c,0x33ead817,0x00000000}}, // _dunha_, _er__blevet_, _frete_, --,
+ {{0x127f7020,0x227ed807,0xdc763018,0x00000000}}, // _slanje_, _kjent_, _децембар_, --,
+ {{0x12ca7811,0xf33df815,0x00000000,0x00000000}}, // _funda_, _en__galicia_, --, --,
+ {{0xfe8f1818,0x00000000,0x00000000,0x00000000}}, // _или_, --, --, --,
+ {{0x34801806,0x248a601c,0xc4b22007,0x00000000}}, // _cena__kč_, _logget__ind_, _vil__bli_, --,
+ {{0x5c7c4014,0xe3afb823,0x00000000,0x00000000}}, // _nuestra_, _ispisa__čpp_, --, --,
+ {{0xc7518831,0x32d82003,0x0526e817,0x08c7301c}}, // _विकिपीडि, _paket_, _carrinho_, _indehold,
+ {{0x527f4011,0xf9f47806,0x00000000,0x00000000}}, // _izenzo_, _nyní_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb4c05007,0x02e28017,0x544e5006,0x527f4014}}, // _slik__at_, _sobre__produto_, _přihlási_se_, _fuente_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf4051016,0x00000000,0x00000000,0x00000000}}, // [100] _profil__penuh_, --, --, --,
+ {{0x7444401c,0xd2d8f81c,0x0399b817,0x00000000}}, // _ud_, _lige_, _mês_, --,
+ {{0x04422007,0x8292781b,0xf347980c,0x00000000}}, // _tok_, _desain_, _týždeň_, --,
+ {{0x04b3581e,0x00000000,0x00000000,0x00000000}}, // _idnes__cz_, --, --, --,
+ {{0x627ff806,0x0a6e981d,0x00000000,0x00000000}}, // _seznam_, _करते__हुए_, --, --,
+ {{0x02247811,0x125a9016,0x00000000,0x00000000}}, // _bonke_, _kuala_, --, --,
+ {{0xd48cc018,0x3c4f081d,0xb266c025,0x00000000}}, // _učlanjen__pol_, _इससे_, _के__निदेशक_, --,
+ {{0x22d9f817,0xc3ea6806,0x785ca003,0xc2e78811}}, // _avalia__este_, _proto_, _karakter_, _iqiniso_,
+ {{0x75b6c00e,0x52247811,0x5ab2d816,0xa2902016}}, // _कि__भारत_, _wonke_, _berkongs, _zakar_,
+ {{0x1496e006,0x92018010,0x82d98010,0x9330a81d}}, // _se__mi_, _kuri_, _kure_, _ज्योतिष__धर्मयात्,
+ {{0x84438007,0x3712a01d,0x5cc2f01e,0x00000000}}, // _mer_, _हो__सकता_, _český_, --,
+ {{0xb2018010,0x42f4080d,0x00000000,0x00000000}}, // _muri_, _entrada__debes_, --, --,
+ {{0x82247811,0x2496e010,0xe495e00c,0x00000000}}, // _zonke_, _se__ni_, _sa__na_, --,
+ {{0x7ba6a80f,0x72247811,0x12d92011,0xe2b5d006}}, // _komentar_čitanja_, _yonke_, _enye_, _najdete_,
+ {{0x5c264823,0x128cd811,0x122b8014,0x00000000}}, // _postovi__pridruže, _kakhulu_, _siempre_, --,
+ {{0x64779809,0xc442680c,0x1e3b2816,0x6cb0181f}}, // _informac_iz_, _kto_, _kepakara, _kuhinjsk_aparati_,
+ {{0x42d82007,0x00000000,0x00000000,0x00000000}}, // [110] _saker_, --, --, --,
+ {{0x02018010,0x00000000,0x00000000,0x00000000}}, // _buri_, --, --, --,
+ {{0xf4424817,0x7d6d3018,0xc8fc8806,0xc273a816}}, // _bom_, _шта_, _odpovědě, _peribadi_,
+ {{0x6c48d01d,0xff3ca803,0x24425824,0x2d260806}}, // _होने_, _perusaha, _rtl_, _hodnocen_produktu_,
+ {{0x1442480c,0x9f7c281e,0xe292001b,0x5c691012}}, // _dom_, _všechna__práva_, _jepang_, _cerrado_,
+ {{0x3669080d,0x1cd8a816,0x42d8f81c,0x2633981b}}, // _da__foundati, _perminta_rujukan_, _sige_, _maksimal_,
+ {{0xf3f81016,0x2442681b,0x64439016,0x9e25d81b}}, // _mahu_, _ato_, _kes_, _konfirma,
+ {{0x8300a803,0x3e0d1818,0x85fe481c,0x00000000}}, // _tidak__bisa_, _сви_, _tilmeldi, --,
+ {{0x030d3807,0xe46f781c,0x62366827,0x00000000}}, // _hele__saken_, _sig__til_, _tvoje_, --,
+ {{0x74439007,0x1c719032,0xeeb91818,0xb2019010}}, // _les_, _पार्टी_, _био_, _musi_,
+ {{0xae19e81b,0x821cb006,0x0859e816,0x00000000}}, // _selengka, _během_, _selangor_, --,
+ {{0xd2b70818,0x00000000,0x00000000,0x00000000}}, // _paročist_dodaci_, --, --, --,
+ {{0x4e72e818,0x047e1818,0xbec76808,0xd29e1818}}, // _не_, _било_, _menyerta, _била_,
+ {{0xe26c7816,0x00000000,0x00000000,0x00000000}}, // _umno_, --, --, --,
+ {{0xd493d81c,0x00000000,0x00000000,0x00000000}}, // _på__vej_, --, --, --,
+ {{0x32ca9008,0x825b1009,0xec5c780e,0x00000000}}, // _tiada_, _tuzla_, _संदेह_, --,
+ {{0x12902016,0x00000000,0x00000000,0x00000000}}, // [120] _cakap_, --, --, --,
+ {{0x89992006,0xa2ca5807,0x00000000,0x00000000}}, // _když_, _bilde_, --, --,
+ {{0x8fc7402c,0xad838820,0xe3eb8007,0xd2d5481f}}, // _समाचार_, _reč_, _vert_, _devojka_,
+ {{0x3494f00f,0x52366827,0x4e292018,0x2200c00d}}, // _pridruže_vrh_, _svoje_, _сам_, _galiza_,
+ {{0xf442580c,0xb2019017,0x00000000,0x00000000}}, // _bol_, _assim_, --, --,
+ {{0xc290481b,0xe48f6002,0xf7bc101f,0xe399b006}}, // _kamar_, _de__tu_, _kragujev, _místo_,
+ {{0x0c09b825,0x00000000,0x00000000,0x00000000}}, // _फरवरी__को_, --, --, --,
+ {{0x895eb014,0x14451005,0x82bef809,0xc2c92812}}, // _cualquie, _mlađim__od_, _se__bilo_, _escribe__como_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc200202e,0x92002810,0xbde6c00c,0x9d67281d}}, // _laki_, _komite_, _nájdete_, _अपना__ब्लॉग_,
+ {{0x32b20011,0x00000000,0x00000000,0x00000000}}, // _amadoda_, --, --, --,
+ {{0x0e292818,0x00000000,0x00000000,0x00000000}}, // _сад_, --, --, --,
+ {{0xc4444007,0x84b8501b,0x00000000,0x00000000}}, // _av_, _bisa__di_, --, --,
+ {{0x04619802,0x00000000,0x00000000,0x00000000}}, // _gracias__por_, --, --, --,
+ {{0xd2d8f807,0xbeb1380c,0x00000000,0x00000000}}, // _laget_, _porovnan, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x3b88080a,0xb290582e,0x40ae3018,0x582e3018}}, // [130] _बा_, _iklan_, _срба_, _срби_,
+ {{0xb2cad814,0x32d8c80d,0xb466700d,0xb50c700d}}, // _puede_, _orde_, _decembro_, _febreiro_,
+ {{0x32d01023,0xc4550814,0xf443900d,0xc3a6b814}}, // _redanje__autor_, _producto_, _tes_, _la__página_,
+ {{0x13b0e017,0xa291a01b,0x00000000,0x00000000}}, // _clique_, _kapan_, --, --,
+ {{0x72f8901b,0x12c50816,0xe27e901b,0x5ba07819}}, // _kamar__mandi_, _berhubun_lihat_, _emang_, _है__या_,
+ {{0xeb979025,0x00000000,0x00000000,0x00000000}}, // _नाम__के_, --, --, --,
+ {{0xb4a37018,0xe31e600d,0x3291801b,0x00000000}}, // _društvo__pre_, _traído__desde_, _saran_, --,
+ {{0xa3e2201c,0x2be9200e,0x9386d807,0x00000000}}, // _indlæg__svar_, _जब__हम_, _uker_, --,
+ {{0xeb98100e,0x84429010,0x00000000,0x00000000}}, // _समय__के_, _nta_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb442900d,0x32026810,0xab4f9002,0x74292816}}, // _ata_, _afrika_, _la__posición_, _tempatan_,
+ {{0x33f40006,0xa9f66815,0x00000000,0x00000000}}, // _jestli_, _verán_, --, --,
+ {{0x54b2600d,0x2d20980c,0x00000000,0x00000000}}, // _non__se_, _vyhľadáv, --, --,
+ {{0x4e737018,0x00000000,0x00000000,0x00000000}}, // _јер_, --, --, --,
+ {{0xf2d8201b,0xebdb4025,0x00000000,0x00000000}}, // _pake_, _बातचीत__के_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x12d83010,0x02903014,0x00000000,0x00000000}}, // [140] _baje_, _baja_, --, --,
+ {{0xb0b2800c,0x53e9701b,0x125b080c,0x00000000}}, // _práva__vyhraden, _menambah__wawasan_, _ďalej_, --,
+ {{0x6a3da825,0x97ddf020,0x8320201e,0x630f9011}}, // _धार_, _mihajlov, _taky_, _imibuzo_,
+ {{0x6bdd101d,0x00000000,0x00000000,0x00000000}}, // _यदि__आप_, --, --, --,
+ {{0x0ca6b80e,0xc3940017,0x00000000,0x00000000}}, // _से__कहीं_, _coisa_, --, --,
+ {{0xc442780d,0xae82c80e,0x0e426018,0x00000000}}, // _non_, _के__राज्य_, _је__за_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe290c023,0x00000000,0x00000000,0x00000000}}, // _islama_, --, --, --,
+ {{0x39ebc018,0x00000000,0x00000000,0x00000000}}, // _коментар_који_, --, --, --,
+ {{0x7278180b,0x00000000,0x00000000,0x00000000}}, // _nama__cari_, --, --, --,
+ {{0x64325005,0x1bf1200e,0xa282500f,0x02735807}}, // _profil__korisnik, _में__ना_, _pregled__budžetsk, _sånn_,
+ {{0xc20e2818,0x00000000,0x00000000,0x00000000}}, // _када_, --, --, --,
+ {{0x33f88811,0x737b8008,0x00000000,0x00000000}}, // _nakuba_, _semalam_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x1290302e,0xf2004811,0x00000000,0x00000000}}, // _saja_, _nami_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [150] --, --, --, --,
+ {{0xeae53802,0x9291c810,0x00000000,0x00000000}}, // _siguient, _kuva_, --, --,
+ {{0x22010820,0x025a1011,0x538d181b,0x00000000}}, // _srbija_, _kahle_, _properti_, --,
+ {{0xc471200d,0x00000000,0x00000000,0x00000000}}, // _que__non_, --, --, --,
+ {{0x0aa1b00e,0x7a70881d,0xf4919817,0x00000000}}, // _इतिहास__में_, _कहते__हैं_, _em__seu_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfae6f816,0x43d2600f,0x12122011,0x553ce808}}, // _ahli__linkedin_, _za__stranke_, _lakhe_, _sekirany,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x92e95803,0x74bcd006,0x6259f825,0x00000000}}, // _artinya_, _jsem__si_, _यक्ष_, --,
+ {{0xb2d98010,0xa2004811,0x00000000,0x00000000}}, // _aires_, _yami_, --, --,
+ {{0xa442900d,0xa465f00f,0x947ec807,0x00000000}}, // _coa_, _partije__bih_, _registre_deg_, --,
+ {{0x7ed4e818,0xbefb202a,0xb633981d,0x00000000}}, // _по_, _natjecan, _व्रत__त्योहार_, --,
+ {{0xd467e80d,0xb9a7f80f,0x0c742811,0x8378e02e}}, // _aínda__que_, _zabranje_koristit, _ngenxa__yokuthi_, _silakan_,
+ {{0xe200580c,0xc290580c,0xe316701b,0x00000000}}, // _mali_, _mala_, _pribadi_, --,
+ {{0xf0ddf824,0xd036c027,0x00000000,0x00000000}}, // _branitel, _ubytovan, --, --,
+ {{0x22d8e802,0x64bd5021,0x00000000,0x00000000}}, // [160] _quiero_, _logo__de_, --, --,
+ {{0x42004811,0xd2a6502a,0x92d83007,0x00000000}}, // _sami_, _seljaci_, _skjer_, --,
+ {{0xb2b2200b,0x4ab7f01d,0x00000000,0x00000000}}, // _cari__orang_, _दिया__गया_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x73cef81c,0x3200581b,0xa38b5807,0x00000000}}, // _bliver_, _bali_, _vårt_, --,
+ {{0x329fd80c,0x82004811,0x00000000,0x00000000}}, // _dňa_, _wami_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf3299812,0x00000000,0x00000000,0x00000000}}, // _quieres__recibir_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf2dff007,0x00000000,0x00000000,0x00000000}}, // _trening_, --, --, --,
+ {{0xee644818,0x00000000,0x00000000,0x00000000}}, // _тако__да_, --, --, --,
+ {{0x75c63821,0x553ea018,0x3b30d00e,0x00000000}}, // _de__santiago_, _devojčic, _पर__चर्चा_, --,
+ {{0x0bc3881d,0x00000000,0x00000000,0x00000000}}, // _रही__है_, --, --, --,
+ {{0xc2da5816,0x1c2bb80e,0x4bcec819,0x1c013006}}, // _isteri_, _जा__सके_, _नही_, _prostor_,
+ {{0xb38b6007,0x00000000,0x00000000,0x00000000}}, // _vært_, --, --, --,
+ {{0xc4926007,0x0c4f081d,0x53ebe807,0x62d9f002}}, // _en__av_, _इसके_, _lett_, _usted_,
+ {{0xc4429011,0xa2247811,0x00000000,0x00000000}}, // [170] _ifa_, _lonke_, --, --,
+ {{0x1eb31016,0x73ebe807,0x02bbf01b,0x00000000}}, // _perminta_kepakara, _nett_, _mandi__dalam_, --,
+ {{0xa28a0016,0x00000000,0x00000000,0x00000000}}, // _rundinga_usaha_, --, --, --,
+ {{0x73ead80c,0x42127817,0x62924808,0x00000000}}, // _preto_, _minha_, _petang_, --,
+ {{0x2d7c9818,0x2d47c806,0x32127817,0x00000000}}, // _не__може_, _nejoblíb, _linha_, --,
+ {{0xfc10a00e,0xe44c2810,0x00000000,0x00000000}}, // _लाख__से_, _mu__gihugu_, --, --,
+ {{0x6284081b,0x00000000,0x00000000,0x00000000}}, // _kyuhyun_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x62ca7829,0x5442b004,0x2eff3018,0x7213f016}}, // _ainda_, _moc_, _доступан__под_, _perkhidm,
+ {{0xd39cd017,0x6450d814,0x00000000,0x00000000}}, // _informaç_sobre_, _nombre__de_, --, --,
+ {{0x9c4f1022,0x92a6e81b,0x52018003,0x72df1019}}, // _उनके_, _di__follow_, _mirip_, _उनकी_,
+ {{0xb2fce816,0x09a41818,0xb63a3814,0x9492b80c}}, // _kongsi_, _април_, _habitaci, _komentár_tak_,
+ {{0xa31a700c,0x00000000,0x00000000,0x00000000}}, // _na__predaj_, --, --, --,
+ {{0x33dcc811,0x4212901e,0x92247811,0x9fb66806}}, // _kodwa_, _praha_, _konke_, _právě_,
+ {{0xec360809,0x00000000,0x00000000,0x00000000}}, // _se__komentar, --, --, --,
+ {{0x726f1811,0xb442a029,0x00000000,0x00000000}}, // _eziningi_, _sob_, --, --,
+ {{0xc2d87810,0xce14a00c,0x03e68022,0x7e0d2018}}, // [180] _kane_, _zamestna, _उत्तराखं, _два_,
+ {{0xfccf9006,0x00000000,0x00000000,0x00000000}}, // _zpět_, --, --, --,
+ {{0xd290780b,0xe443e81c,0x644c5002,0x64c10015}}, // _mana_, _ret_, _cuenta__de_, _parroqui_de_,
+ {{0x5200580b,0x5422a010,0x7290a003,0x00000000}}, // _ahli_, _hanze__imbuga_, _kabar_, --,
+ {{0x62490011,0xca6f401d,0xf26c0017,0x00000000}}, // _phambi_, _विज्ञापन__दें_, _meio_, --,
+ {{0x2eb4b818,0x02007811,0x341f7810,0x1478c00c}}, // _који__је_, _nani_, _rupapuro_, _nie__sú_,
+ {{0x2443e807,0x00000000,0x00000000,0x00000000}}, // _vet_, --, --, --,
+ {{0x39aa7818,0xbb46981f,0x3d5a7818,0x222a7818}}, // _који_, _privatna_, _које_, _која_,
+ {{0x52d8b014,0x32018010,0x32d87810,0x22907810}}, // _hacer_, _kiri_, _bane_, _bana_,
+ {{0x42d87810,0x148f6014,0x00000000,0x00000000}}, // _cane_, _de__mi_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x1c4f201d,0x00000000,0x00000000,0x00000000}}, // _उसके_, --, --, --,
+ {{0x32d84806,0x0379a808,0x00000000,0x00000000}}, // _jsme_, _bayaran_, --, --,
+ {{0xec7ac033,0x226c3014,0x32d8b00d,0x00000000}}, // _परिवर्तन_, _mejor_, _facer_, --,
+ {{0x6442c813,0x00000000,0x00000000,0x00000000}}, // _mod_, --, --, --,
+ {{0xb7ad580d,0x04b6081b,0x00000000,0x00000000}}, // _obxectiv, _tokobagu_com_, --, --,
+ {{0x4a3df80e,0xa26dc810,0xa2018010,0x02d9e81d}}, // [190] _तार_, _ministri_, _biri_, _पूरी_,
+ {{0x6491e017,0xfbe5c80c,0xd473e817,0x00000000}}, // _em__de_, _priestor, _acho__que_, --,
+ {{0xf442d80c,0x82887018,0xdc4e6034,0x6b530803}}, // _ste_, _preporuk_odgovori_, _खाने_, _yang__tersedia_,
+ {{0xf2e67010,0xf79ee018,0x02d9b81f,0x93fa7807}}, // _cyangwa_, _треба_, _saveti_, _seg__selv_,
+ {{0x31e6d80e,0x4247500c,0x00000000,0x00000000}}, // _चौधरी_, _nahlásiť_, --, --,
+ {{0x163f080c,0x63949007,0x73ead80c,0xd448181e}}, // _komentár_, _plass_, _tieto_, _dobrý__den_,
+ {{0x0443f821,0x4b88700e,0x00000000,0x00000000}}, // _teu_, _भा_, --, --,
+ {{0xaa3d780e,0x23170011,0x00000000,0x00000000}}, // _दर्जा_, _amazwi_, --, --,
+ {{0xca685023,0x32919010,0x9290c806,0x1442d807}}, // _pridruže_lokacija_, _misa_, _zadat_, _ute_,
+ {{0x4d2e6818,0x02f1980d,0x00000000,0x00000000}}, // _član__učlanjen_, _unha__marca_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x1399f816,0xd379e817,0x3cb81806,0x00000000}}, // _kerjaya__tawaran_, _tamanho_, _odpovědi_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x2e917818,0x6768300e,0x00000000,0x00000000}}, // _страница__је_, _में__उनका_, --, --,
+ {{0x8291901b,0x5499e006,0x5491e01c,0xa2018010}}, // _bisa_, _to__se_, _at__se_, _riri_,
+ {{0x7442d807,0x1e8c5025,0x14138016,0x00000000}}, // _noe_, _के__पुत्र_, _kristian_, --,
+ {{0x8a3e081d,0x00000000,0x00000000,0x00000000}}, // [1a0] _बार_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x54a8d82a,0x72904809,0x2ef4d018,0x00000000}}, // _prijavi__se_, _namaz_, _које__је_, --,
+ {{0xd26e1829,0xc4957835,0x1e292018,0x937a6016}}, // _depois_, _om__nye_, _нам_, _jawatan_,
+ {{0x3f6b2818,0x00000000,0x00000000,0x00000000}}, // _стране_, --, --, --,
+ {{0x323a600c,0x00000000,0x00000000,0x00000000}}, // _môj_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x22d9c81c,0x025b1023,0x2290a010,0xd355d817}}, // _giver_, _tuzli_, _haba_, _confira_,
+ {{0xf8673012,0xbc773002,0x7200a011,0x00000000}}, // _mensajes_, _mensaje_, _kabi_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x52b4001f,0xd313601c,0xb4422013,0xf3c6180d}}, // _blic_, _skal__have_, _luk_, _debe__darse_,
+ {{0x6290a011,0xe1a32023,0x00000000,0x00000000}}, // _laba_, _na__vlastito, --, --,
+ {{0x0267b023,0x00000000,0x00000000,0x00000000}}, // _sallalla_alejhi_, --, --, --,
+ {{0x5eced818,0x00000000,0x00000000,0x00000000}}, // _преко_, --, --, --,
+ {{0x7290c824,0xec12780e,0x72925017,0x67d0c018}}, // _zadar_, _बाबा__के_, _estava_, _много_,
+ {{0xee625818,0x326c1016,0x00000000,0x00000000}}, // _lepota__zdravlje_, _johor_, --, --,
+ {{0x83f86806,0x3aea182a,0xd2d8a011,0xf22bf810}}, // [1b0] _jsou_, _vjerojat, _babe_, _css__mugihe_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x3e017018,0xb491e014,0x00000000,0x00000000}}, // _izveštaj, _el__el_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x32d8b002,0x00000000,0x00000000,0x00000000}}, // _hace_, --, --, --,
+ {{0xe315e017,0x3da90003,0xe7b5481c,0x00000000}}, // _usuários_, _fasilita, _regering, --,
+ {{0x12d8d036,0xcc328811,0x42d9801b,0x29f5c80c}}, // _होती_, _ngaphamb, _maret_, _prvý_,
+ {{0x8290a80b,0x54132016,0x841bb018,0x00000000}}, // _undang_, _lihat__siapa_, _sve__vesti_, --,
+ {{0x04920807,0xb9e70025,0x00000000,0x00000000}}, // _av__det_, _के__प्रयास_, --, --,
+ {{0x726e081f,0x568f481b,0x00000000,0x00000000}}, // _lepota_, _spotligh, --, --,
+ {{0x830f7016,0x00000000,0x00000000,0x00000000}}, // _berbeza_, --, --, --,
+ {{0x7e72e818,0x00000000,0x00000000,0x00000000}}, // _те_, --, --, --,
+ {{0xc349301b,0x00000000,0x00000000,0x00000000}}, // _info__terbaru_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe2bed80e,0xe2aed834,0x44bbd81c,0xd290a011}}, // _कहां_, _कहाँ_, _brug__af_, _saba_,
+ {{0xfaf2200c,0x5344a815,0xa2d9a01b,0x0edbe817}}, // [1c0] _prihláse, _ourense_, _tipe_, _conteúdo_,
+ {{0x1290d016,0x7291f814,0x0a8b700e,0x00000000}}, // _melayu_, _agua_, _विश्व__में_, --,
+ {{0x4f370823,0x43f9e81b,0xb388f80d,0xa2ad781c}}, // _ukupno__mijenjan, _fitur_, _páxina__outras_, _besked__dato_,
+ {{0xe2fc681f,0x1290a011,0x7327481c,0x22d8b010}}, // _blogu_, _waba_, _at__levere_, _gace_,
+ {{0x82cad818,0x00000000,0x00000000,0x00000000}}, // _sreda_, --, --, --,
+ {{0xa442d811,0x00000000,0x00000000,0x00000000}}, // _efe_, --, --, --,
+ {{0x2f3e2806,0x725ae805,0xc442f81c,0x00000000}}, // _méně__než_, _rofl_, _bog_, --,
+ {{0xe4444014,0x7669a037,0x52d8f81c,0x00000000}}, // _le_, _सम्पादन__इतिहास_, _taget_, --,
+ {{0xa2481016,0x00000000,0x00000000,0x00000000}}, // _negara__lagi_, --, --, --,
+ {{0xc3216814,0x3a3df022,0xcdb94818,0xf3710008}}, // _gracias_, _साथ_, _последњи__пут_, _atas__dengan_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfbac300e,0xddbab81d,0x84808810,0xd452a007}}, // _माध्यम__से_, _आपत्तिजन, _kuri__uru_, _har__blitt_,
+ {{0xe468701c,0x00000000,0x00000000,0x00000000}}, // _tilføj__til_, --, --, --,
+ {{0x42dea023,0x5eb6881e,0x0e94e018,0xef4e2818}}, // _la__dana_, _zobrazit_, _јун__мај_, _политичк,
+ {{0x23f9e81b,0x2495100f,0xbf87501f,0x1e19d018}}, // _situs_, _pridruže_maj_, _mašine_, _београд_,
+ {{0xd2494014,0x00000000,0x00000000,0x00000000}}, // _tiempo_, --, --, --,
+ {{0x42ca3807,0x43c93806,0x00000000,0x00000000}}, // [1d0] _veldig_, _pro__vás_, --, --,
+ {{0xebf6f025,0x00000000,0x00000000,0x00000000}}, // _हिंदी__के_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6b802006,0x9470481f,0x6290381b,0x00000000}}, // _informac, _privatna__poruka_, _bilang_, --,
+ {{0xb4444010,0x00000000,0x00000000,0x00000000}}, // _ye_, --, --, --,
+ {{0xe1eb3006,0x92122011,0x00000000,0x00000000}}, // _obchodní_, _zakho_, --, --,
+ {{0x92ca7016,0x82122011,0xba3e281d,0x00000000}}, // _komersil__laman_, _yakho_, _मां_, --,
+ {{0x9342380d,0x00000000,0x00000000,0x00000000}}, // _goberno_, --, --, --,
+ {{0xd224d820,0x00000000,0x00000000,0x00000000}}, // _uvek_, --, --, --,
+ {{0x3cc4181d,0x926c481b,0x00000000,0x00000000}}, // _क्लिक__करें_, _nomor_, --, --,
+ {{0xd369a016,0x0ff7e029,0x00000000,0x00000000}}, // _usaha__niaga_, _de__favorito, --, --,
+ {{0x32d8f807,0x8920d825,0x00000000,0x00000000}}, // _dager_, _रामजी_, --, --,
+ {{0x4eb8e818,0x2e28e818,0x00000000,0x00000000}}, // _ли_, _ла_, --, --,
+ {{0x1326981c,0x00000000,0x00000000,0x00000000}}, // _at__tage_, --, --, --,
+ {{0xc6f4b80e,0xbc682014,0x5870d818,0x00000000}}, // _में__सरकार_, _febrero_, _pevačica_, --,
+ {{0x94444010,0x22fc7810,0x217dd00e,0x2200b006}}, // _we_, _kongo_, _भरोसा_, _chci_,
+ {{0x24b19823,0x00000000,0x00000000,0x00000000}}, // [1e0] _stare__svi_, --, --, --,
+ {{0xb4432014,0x0aa6400e,0x62122011,0x00000000}}, // _hoy_, _में__देश_, _wakho_, --,
+ {{0x226cb01b,0xb0ec0018,0x00000000,0x00000000}}, // _cocok_, _на__навигаци, --, --,
+ {{0x487d5003,0xaac70806,0x33f8c81f,0x8f686812}}, // _महाराष्ट, _zboží_, _sadu_, _informac_reportar_,
+ {{0x82678818,0x644c000a,0x00000000,0x00000000}}, // _dan__sati_, _साहित्य__अकादमी_, --, --,
+ {{0xee426018,0x32d8f81c,0x7a421025,0x0444401b}}, // _је__да_, _tager_, _के__गठन_, _jl_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xebc0a01d,0xb994c80c,0x34af600c,0xebf65825}}, // _करने__के_, _môže_, _že__sa_, _संगीत__के_,
+ {{0x1d1a500c,0xa2fc7810,0xe394901b,0x5320c806}}, // _ďalšie_, _congo_, _sukses_, _tady_,
+ {{0x784b8812,0xb3080816,0x9297a010,0xf2ca581c}}, // _por__proporci, _terakhir__pada_, _umukuru_, _fuld_,
+ {{0xae1ff029,0xa65ab818,0xc4840810,0x00000000}}, // _após_, _се__пријави_, _hagufi__aho_, --,
+ {{0x65b8a011,0x00000000,0x00000000,0x00000000}}, // _ibhayibh, --, --, --,
+ {{0x62122011,0x9ab6a012,0x00000000,0x00000000}}, // _bakhe_, _tu__pregunta_, --, --,
+ {{0x9bb9301d,0x00000000,0x00000000,0x00000000}}, // _हैं__कि_, --, --, --,
+ {{0x94499018,0x00000000,0x00000000,0x00000000}}, // _na__poruka_, --, --, --,
+ {{0xf2d91017,0xc2122011,0x42d9e807,0x6200e810}}, // _fazer_, _wakhe_, _lite_, _hafi_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [1f0] --, --, --, --,
+ {{0xd4444015,0x6e83400c,0x00000000,0x00000000}}, // _gl_, _spoločno, --, --,
+ {{0x9430781f,0x72fa181c,0x33aa4011,0x00000000}}, // _broj__poruka_, _tilmeldt__indlæg_, _udavide_, --,
+ {{0xf2122011,0x926c1006,0x82d9a818,0xa444581b}}, // _zakhe_, _mohou_, _uspeh_, _gambar__rp_,
+ {{0x426c5829,0xe2122011,0x382e3018,0xc338a004}}, // _pelo_, _yakhe_, _први_, _zdraví_,
+ {{0x2dc5601b,0x00000000,0x00000000,0x00000000}}, // _semoga__bermanfa, --, --, --,
+ {{0xec11900e,0x5484e00d,0x3994d80c,0x82fc5807}}, // _आदि__के_, _de__traballo_, _môžu_, _velg_,
+ {{0x32ffc805,0x00000000,0x00000000,0x00000000}}, // _sakrij__citiraj_, --, --, --,
+ {{0x826c580c,0x2b9f2025,0x25b8a011,0x00000000}}, // _telo_, _के__स्_, _ebhayibh, --,
+ {{0xcabc700a,0x64432002,0x33f9e82e,0x00000000}}, // _राजा__भोज_, _soy_, _gitu_, --,
+ {{0xa31fa01b,0x023ab81c,0x00000000,0x00000000}}, // _aja__yang_, _høj_, --, --,
+ {{0x12aab81c,0x82122011,0x00000000,0x00000000}}, // _køb_, _sakhe_, --, --,
+ {{0xfeb7d017,0x3e72e818,0x00000000,0x00000000}}, // _possível_, _ме_, --, --,
+ {{0xb4b65807,0x1f072817,0x00000000,0x00000000}}, // _etter__at_, _disponív, --, --,
+ {{0x2c3e8805,0x00000000,0x00000000,0x00000000}}, // _se__komentir, --, --, --,
+ {{0xc26c580d,0x00000000,0x00000000,0x00000000}}, // _polos_, --, --, --,
+ {{0x03ab5816,0x52d9c81c,0x00000000,0x00000000}}, // [200] _berkongs_kenalan_, _lavet_, --, --,
+ {{0xc27e9010,0x92d8f807,0xec4e080a,0x00000000}}, // _imana_, _lage_, _काहे_, --,
+ {{0x82441814,0x226c780c,0x00000000,0x00000000}}, // _cómo_, _meno_, --, --,
+ {{0xecb6a805,0x2bccc00a,0xa70f3825,0x00000000}}, // _korisnik_nađi_, _एगो_, _मनाया__जाता_, --,
+ {{0x52de6038,0xb4b15017,0x5f9c7812,0x00000000}}, // _खाली_, _descriçã, _ofensivo__publicar_, --,
+ {{0x64444010,0xd2e9c00d,0xb7764006,0x529f9011}}, // _kw_, _as__súas_, _zkušenos, _kunjalo_,
+ {{0x3e426018,0x48d30818,0x00000000,0x00000000}}, // _је__на_, _је__последњи_, --, --,
+ {{0x84444010,0xce834006,0x9de2f00d,0xe39ab006}}, // _mw_, _společno, _pódense_, _města_,
+ {{0xb9f4300d,0x12d8f81c,0x138ab81c,0x00000000}}, // _tamén_, _dage_, _gør_, --,
+ {{0x82cac807,0x4498a818,0x00000000,0x00000000}}, // _hadde_, _moć__re_, --, --,
+ {{0x4442780d,0x00000000,0x00000000,0x00000000}}, // _cun_, --, --, --,
+ {{0xc4ab2007,0x00000000,0x00000000,0x00000000}}, // _kan__bli_, --, --, --,
+ {{0xe496e006,0xd3874806,0xc37e280d,0x00000000}}, // _se__na_, _kterou_, _de__imaxes_, --,
+ {{0xd4444010,0x82b8b00d,0x133a180f,0x00000000}}, // _bw_, _inc__unha_, _opće__diskusij, --,
+ {{0x93eb9014,0x00000000,0x00000000,0x00000000}}, // _gusta_, --, --, --,
+ {{0xd2d92022,0x00000000,0x00000000,0x00000000}}, // _मोदी_, --, --, --,
+ {{0xef9a6014,0xe291a02e,0x00000000,0x00000000}}, // [210] _las__palabras_, _bapak_, --, --,
+ {{0x4225e82a,0x822f8818,0x64926007,0x7355d807}}, // _netko_, _pre__sati_, _av__de_, _nyheter_,
+ {{0x631b7805,0x2444401b,0x00000000,0x00000000}}, // _crna__kronika_, _gw_, --, --,
+ {{0x0bd9781d,0x5236680c,0x00000000,0x00000000}}, // _गया__है_, _svoju_, --, --,
+ {{0x3af2200c,0xe422f81b,0x12be900c,0x00000000}}, // _prihlási, _email__untuk_, _zatiaľ_, --,
+ {{0x9bd1280f,0xe461701c,0x00000000,0x00000000}}, // _finansir_političk, _tilbage__til_, --, --,
+ {{0xd30fd816,0x00000000,0x00000000,0x00000000}}, // _carian__nama_, --, --, --,
+ {{0xed937024,0x6192a821,0x00000000,0x00000000}}, // _listopad_, _autonómi, --, --,
+ {{0x5053c818,0x4163c818,0x32011010,0x00000000}}, // _године_, _година_, _kazi_, --,
+ {{0x6b8fe022,0x4c74d012,0x998e1818,0xe9a5f00c}}, // _थे_, _lenguaje__ofensivo_, _начин_, _diskusia_,
+ {{0x026c200c,0xb045880c,0x12d8f81c,0x42d91010}}, // _rokov_, _vyhraden, _tage_, _maze_,
+ {{0xd4444010,0x94b7201b,0x1c26e006,0x00000000}}, // _rw_, _topik__apa_, _prostě_, --,
+ {{0x1248d806,0x825a9014,0x5d4fb006,0x0b9c881d}}, // _jsem_, _cual_, _včetně_, _की__है_,
+ {{0x53eab810,0x6273a814,0x00000000,0x00000000}}, // _ndetse_, _línea_, --, --,
+ {{0x26f8a01d,0xa37a2808,0x00000000,0x00000000}}, // _होगा_, _paparan_, --, --,
+ {{0xb3a2481b,0x8334e01b,0x00000000,0x00000000}}, // _sampe_, _pukul__melalui_, --, --,
+ {{0xa273a80d,0xa2011010,0x92d91011,0x025aa016}}, // [220] _aínda_, _bazi_, _baze_, _majlis_,
+ {{0xf4444010,0xae732018,0x4448b820,0x047bd807}}, // _tw_, _без_, _možete__da_, _noen__som_,
+ {{0xa396700b,0x49ebd01c,0x326c4839,0x7496e010}}, // _kursus_, _undersøg, _domov_, _ku__wa_,
+ {{0xa6ffc03a,0x44993029,0x00000000,0x00000000}}, // _अर्थ_, _um__dos_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfe701803,0x00000000,0x00000000,0x00000000}}, // _pencaria, --, --, --,
+ {{0x4ec27818,0x1ebdc006,0x42d92011,0xb46c6017}}, // _свиђа__ми_, _hodnocen, _naye_, _com__um_,
+ {{0xfe3bf00d,0x3420901c,0xd4b7481c,0xffc0901c}}, // _xuño_, _hjælper_, _find__vej_, _hjælpe_,
+ {{0x32ca780d,0x54422007,0x00000000,0x00000000}}, // _cando_, _lik_, --, --,
+ {{0xd4429029,0xe41ba01c,0x82d92011,0x00000000}}, // _sua_, _at__blive_, _baye_, --,
+ {{0xb442101b,0xb26ca00c,0xf257c01c,0xc2a6c016}}, // _sih_, _lebo_, _vælge_, _nombor_,
+ {{0x93f9e806,0x625a9017,0x00000000,0x00000000}}, // _datum_, _qual_, --, --,
+ {{0xd26ca006,0xb522c017,0xebbe3825,0xb2c9e816}}, // _nebo_, _nome__opcional_, _युद्ध__के_, _dengan__resolusi_,
+ {{0x03ea0016,0x00000000,0x00000000,0x00000000}}, // _iaitu_, --, --, --,
+ {{0xe0ecd006,0x00000000,0x00000000,0x00000000}}, // _hodnocen_hvězdičk, --, --, --,
+ {{0x42317808,0x00000000,0x00000000,0x00000000}}, // [230] _bermula_, --, --, --,
+ {{0x3386d00c,0x00000000,0x00000000,0x00000000}}, // _ktorej_, --, --, --,
+ {{0xf442201c,0xe4836018,0xecab480d,0x00000000}}, // _fik_, _vest__na_, _da__coruña_, --,
+ {{0xe3f4681f,0x0442201c,0xb386e80d,0x25ff6819}}, // _ujutru_, _gik_, _venres_, _क्योंकि_,
+ {{0x6a3c503b,0x8481c027,0x00000000,0x00000000}}, // _आता_, _modrykon_sk_, --, --,
+ {{0x52fd7802,0x7ea98818,0x00000000,0x00000000}}, // _imagen_, _одјавите__се_, --, --,
+ {{0x09645006,0x72de6025,0xf00a0818,0x00000000}}, // _fotogale, _खाती_, _користећ_свој_, --,
+ {{0x6ac77806,0x0c4e3825,0x826c9011,0x00000000}}, // _další_, _चाहे_, _inkosi_, --,
+ {{0x826ca011,0xd4907829,0xd2b05012,0x137a6016}}, // _yebo_, _em__que_, _opinión__otras_, _rawatan_,
+ {{0xddae3816,0x33f47807,0x54343018,0xad32000d}}, // _mengikut_, _nesten_, _očistite__dezinfik, _calidade_,
+ {{0xe38c3818,0x00000000,0x00000000,0x00000000}}, // _буде_, --, --, --,
+ {{0x227ff80c,0x00000000,0x00000000,0x00000000}}, // _zoznam_, --, --, --,
+ {{0x537a602e,0x227e7828,0xdb88180e,0x00000000}}, // _wawasan_, _henne_, _मय_, --,
+ {{0x1bf7181d,0x00000000,0x00000000,0x00000000}}, // _में__एक_, --, --, --,
+ {{0xce38300b,0x00000000,0x00000000,0x00000000}}, // _pengajia, --, --, --,
+ {{0xd6ffd81d,0x00000000,0x00000000,0x00000000}}, // _इसका_, --, --, --,
+ {{0x03ea001b,0xb712a80d,0x00000000,0x00000000}}, // [240] _yaitu_, _dispoñib, --, --,
+ {{0x6394d813,0xc4919812,0x8324380c,0x00000000}}, // _danske_, _posición__en_, _ak__chcete_, --,
+ {{0x14bd080d,0x2b81b80c,0x1300000d,0x00000000}}, // _proxecto_, _sledovať_, _citar__esta_, --,
+ {{0x7419f802,0xcc6e7811,0x00000000,0x00000000}}, // _de__nuevo_, _ngaphezu__kwalokho_, --, --,
+ {{0xc3959017,0x0a83d025,0x0bcda01d,0xc2070817}}, // _nossa_, _काम__में_, _मित्र__को_, _código__abaixo_,
+ {{0x3afdf807,0xf2970018,0x8386d81c,0x00000000}}, // _prosjekt, _nadji__info_, _ejer_, --,
+ {{0x6706380e,0x00000000,0x00000000,0x00000000}}, // _आर्थिक__विकास_, --, --, --,
+ {{0x52d8281c,0x00000000,0x00000000,0x00000000}}, // _anmeld_, --, --, --,
+ {{0x8d3fb818,0x00000000,0x00000000,0x00000000}}, // _децембар__новембар_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xab88201d,0x12c56815,0x00000000,0x00000000}}, // _नए_, _comentar__esta_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf34ba807,0xfbdff80c,0x00000000,0x00000000}}, // _ikke__tillatt_, _aplikáci, --, --,
+ {{0x8291802e,0x00000000,0x00000000,0x00000000}}, // _barat_, --, --, --,
+ {{0x42d8d01d,0x00000000,0x00000000,0x00000000}}, // _होगी_, --, --, --,
+ {{0x0200d00b,0xc47b700d,0x8308981b,0x53ced81b}}, // _beliau_, _organiza_sen_, _jawa__barat_, _trovit_,
+ {{0x450fb016,0xf3eb8035,0xe497201b,0x00000000}}, // [250] _komersil_, _bort_, _ya__gan_, --,
+ {{0x4c48d00a,0x00000000,0x00000000,0x00000000}}, // _होखे_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x23f9802e,0xe496e014,0x00000000,0x00000000}}, // _harus_, _no__es_, --, --,
+ {{0x02139011,0x00000000,0x00000000,0x00000000}}, // _cishe_, --, --, --,
+ {{0xf492a826,0x00000000,0x00000000,0x00000000}}, // _er__der_, --, --, --,
+ {{0x612d0018,0xb443900b,0xe0bbd806,0x04424829}}, // _март_, _kos_, _hvězdičk, _fim_,
+ {{0x7338581c,0x537a6016,0x00000000,0x00000000}}, // _tilmeldi_indlæg_, _jawapan_, --, --,
+ {{0x6cb39814,0x00000000,0x00000000,0x00000000}}, // _de__ingreso_, --, --, --,
+ {{0x2338201c,0x82711803,0x00000000,0x00000000}}, // _læs__mere_, _mulai__dari_, --, --,
+ {{0xcc4e883c,0xa2de8822,0x00000000,0x00000000}}, // _जाते_, _जाती_, --, --,
+ {{0x92b81815,0x42d81011,0x00000000,0x00000000}}, // _de__vigo_, _akhe_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa290f816,0x9a475017,0x00000000,0x00000000}}, // _sahaja_, _se__você_, --, --,
+ {{0x17c0d818,0x2443900d,0x00000000,0x00000000}}, // _poruke__reputaci, _bos_, --, --,
+ {{0x0f31f00b,0x00000000,0x00000000,0x00000000}}, // _seterusn, --, --, --,
+ {{0x2a1e7018,0x6c84b825,0xb291b80c,0x00000000}}, // [260] _бити_, _के__समर्थन_, _tovaru_, --,
+ {{0x8354180c,0x64420010,0xd4424829,0xf0319006}}, // _všetky_, _iri_, _sim_, _předchoz,
+ {{0xf46ee00e,0x6fd99817,0x53f9901b,0x6b00b818}}, // _मध्य__प्रदेश_, _em__até_, _kasus_, _септемба_август_,
+ {{0x7b882819,0x4b7cd80d,0x33a3a007,0xc394d811}}, // _ने_, _recoñece, _hopp_, _efesu_,
+ {{0x04916827,0x00000000,0x00000000,0x00000000}}, // _by__som_, --, --, --,
+ {{0xd2905806,0x8290d023,0x33205806,0xbc57e80a}}, // _byla_, _allahu_, _byly_, _के__साथे_,
+ {{0x9e438817,0x43960017,0x00000000,0x00000000}}, // _atualiza, _nossos_, --, --,
+ {{0x2442d814,0xe3ce001c,0x2234b016,0x22aef023}}, // _fue_, _blive_, _dari__lebih_, _ve__sellem_,
+ {{0x2ec86018,0x3442d81b,0xf427c00d,0xb3cfb802}}, // _то__је_, _gue_, _todo__texto_, _leer__más_,
+ {{0x9200d01c,0x00000000,0x00000000,0x00000000}}, // _muligt_, --, --, --,
+ {{0xe4420010,0x9bb16823,0xd37fc021,0xd4779816}}, // _ari_, _za__političk, _comarca_, _pukul__ptg_,
+ {{0x34426814,0x7fc8e821,0x00000000,0x00000000}}, // _uno_, _obrigato, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x0bc6e01d,0x94300018,0x28a49825,0x00000000}}, // _होता__है_, _poslednj_poruka_, _कौन__बनेगा_, --,
+ {{0x1f945806,0x72fe7028,0xeb99c80e,0x1201b00c}}, // _jméno_, _norges_, _परिषद__के_, _neviem_,
+ {{0x54992020,0x00000000,0x00000000,0x00000000}}, // _pridruži_se_, --, --, --,
+ {{0xdb88483d,0x8442681c,0x22ab7028,0x00000000}}, // [270] _भए_, _mio_, _melding_, --,
+ {{0x2dacd816,0x00000000,0x00000000,0x00000000}}, // _perniaga, --, --, --,
+ {{0x20eec00e,0xd2ca7810,0xc4906035,0x00000000}}, // _गांव_, _kanda_, _ha__en_, --,
+ {{0x0a8f3025,0xb1beb01d,0xd2925007,0x00000000}}, // _बिहार__में_, _टाइल_, _avtale_, --,
+ {{0x6e1d3007,0x1a3e280e,0x00000000,0x00000000}}, // _spørsmål_, _माई_, --, --,
+ {{0x12f5981f,0xdd84a01c,0x416af006,0xb8eaf006}}, // _miliona__evra_, _oprettet_, _nabídka_, _nabídky_,
+ {{0xb990b821,0x2e2a1018,0x00000000,0x00000000}}, // _para__imprimir_, _национал, --, --,
+ {{0x6236e816,0x53807002,0x00000000,0x00000000}}, // _tinjau_, _cerrar_, --, --,
+ {{0xa35e101f,0x92fd5002,0x534b9814,0x00000000}}, // _beograd_, _juegos_, _la__imagen_, --,
+ {{0x018c680e,0xe2a11010,0x4a41f025,0x00000000}}, // _सरोवर_, _bbc__izindi_, _के__गीत_, --,
+ {{0xd340481f,0x5092a025,0x00000000,0x00000000}}, // _nedelja_, _शिवराज__सिंह_, --, --,
+ {{0x2e72e818,0xe213f011,0xd442000c,0x44aa601c}}, // _је_, _futhi_, _pri_, _del__af_,
+ {{0x2e56e818,0xed18201c,0x00000000,0x00000000}}, // _из_, _længere_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x1442000c,0xc3449007,0x00000000,0x00000000}}, // _tri_, _blant__annet_, --, --,
+ {{0x6b6e5818,0x24420010,0xf3a3a007,0x3a3df00e}}, // [280] _се__прокомен, _uri_, _topp_, _देस_,
+ {{0xffa54016,0x42d9800c,0x00000000,0x00000000}}, // _disember_, _okrem_, --, --,
+ {{0xd2fd8028,0xe5b3f806,0x8b645006,0xc2ca7810}}, // _norge_, _nejlepší_, _oblečení_, _bandi_,
+ {{0xe6c34818,0x00000000,0x00000000,0x00000000}}, // _порекло__презимен, --, --, --,
+ {{0xd4426829,0xf3005816,0x00000000,0x00000000}}, // _rio_, _peluang__kerjaya_, --, --,
+ {{0x9442780d,0xb4976819,0x00000000,0x00000000}}, // _nin_, _जम्मू_, --, --,
+ {{0x72fd8011,0xb46ed818,0xda6a101f,0x00000000}}, // _isithomb_ekhasini_, _време_, _sledeća_, --,
+ {{0xde737818,0x00000000,0x00000000,0x00000000}}, // _већ_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x24465805,0x00000000,0x00000000,0x00000000}}, // _cijena__kn_, --, --, --,
+ {{0xe4895809,0xb727f80d,0x326cf817,0x82240010}}, // _natrag__na_, _está__dispoñib, _jogos_, _ariko_,
+ {{0x04427807,0x72eb9807,0x73960017,0x00000000}}, // _ein_, _kjenner_, _possui_, --,
+ {{0x6395401f,0xe48f6027,0xc4422007,0xe41ae01b}}, // _umesto_, _aj__na_, _nrk_, _jakarta__timur_,
+ {{0x22018010,0x7020380d,0x03167811,0x00000000}}, // _nari_, _votar__comentar_, _wenza_, --,
+ {{0x52bac806,0xd4add011,0xd212e017,0x93eaf819}}, // _něco_, _khona__ke_, _nenhum_, _राजस्थान_,
+ {{0x546be006,0x27e6580e,0x00000000,0x00000000}}, // _jak__se_, _शिवराज_, --, --,
+ {{0x62018010,0xc2d15017,0x00000000,0x00000000}}, // [290] _bari_, _produto__não_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd327701c,0xebc3d00e,0xc225901b,0x64c2d814}}, // _at__have_, _डॉलर__के_, _meski_, _ciudad__de_,
+ {{0x93fb781c,0xd48a5011,0x00000000,0x00000000}}, // _sig__selv_, _ngakho__ke_, --, --,
+ {{0xd2d86823,0xeae8200e,0x00000000,0x00000000}}, // _opštinam, _आश्वासन_, --, --,
+ {{0xefe6c018,0xd3ea902e,0xb2018010,0x00000000}}, // _против_, _liat_, _gari_, --,
+ {{0x6dea2018,0x1327701c,0x00000000,0x00000000}}, // _јул__јун_, _at__lave_, --, --,
+ {{0x69fd381a,0xa334800b,0xe2018010,0x81b5c818}}, // _pagament, _profesio_yang_, _zari_, _чланак_,
+ {{0xb4702018,0x9443e807,0xd2018010,0x00000000}}, // _pre__dan_, _ett_, _yari_, --,
+ {{0xb2e31810,0x00000000,0x00000000,0x00000000}}, // _buenos__aires_, --, --, --,
+ {{0xe301081c,0x2a63501d,0x00000000,0x00000000}}, // _tilbage_, _में__विज्ञापन_, --, --,
+ {{0x1442600c,0xc48eb80d,0x5eac2028,0x00000000}}, // _čo_, _de__ás_, _eksterne_, --,
+ {{0xbdc66818,0x54b63017,0x00000000,0x00000000}}, // _lak__jednosta, _cadastre__se_, --, --,
+ {{0x998aa006,0x068d401d,0x00000000,0x00000000}}, // _době_, _बदलाव_, --, --,
+ {{0x6f8bf01a,0xaa3d4019,0xd2fe701c,0xe2267007}}, // _até_, _लिए_, _bruger_, _bruker_,
+ {{0x3442d811,0x00000000,0x00000000,0x00000000}}, // _nge_, --, --, --,
+ {{0x2291801b,0xa26cf81b,0x53cba003,0x00000000}}, // [2a0] _para_, _bogor_, _di__jakarta_, --,
+ {{0x86128806,0x44c4280d,0x00000000,0x00000000}}, // _příspěvk, _rexistra_da_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x2bf0000e,0x2c57c81d,0x4499b00d,0x92918010}}, // _ना__हो_, _के__लिये_, _un__dos_, _wara_,
+ {{0xfbd3780e,0xc2d9c81f,0xe020980f,0x00000000}}, // _भारत__से_, _savet_, _opštinam_izdvajan, --,
+ {{0xc44f6023,0x34432014,0x8cbff023,0x43781823}}, // _postao__la_, _muy_, _mjesec__mjeseca_, _godina__redanje_,
+ {{0x5a3e1033,0x86f2581d,0x32d9c81c,0x00000000}}, // _मगर_, _धर्म__संसार_, _laver_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x19f87806,0x0ec4580d,0x7335c812,0x00000000}}, // _recenzí_, _teñen_, _dato__erróneo_, --,
+ {{0xc305681b,0x905c781d,0x32503818,0x00000000}}, // _tidur__kamar_, _समाचार__व्यापार_, _рекао_, --,
+ {{0x2442c81c,0xa290e80c,0x00000000,0x00000000}}, // _ind_, _priamo_, --, --,
+ {{0xf468d81b,0x00000000,0x00000000,0x00000000}}, // _topic__kamu_, --, --, --,
+ {{0x23089017,0x3291a016,0xb44e182e,0x00000000}}, // _aqui__para_, _bapa_, _syarat__dan_, --,
+ {{0xe29c980d,0xb6503814,0x00000000,0x00000000}}, // _dúas_, _ubicació, --, --,
+ {{0xe443e807,0x850b380e,0xc2781803,0x00000000}}, // _mot_, _के__किनारे_, _pengirim, --,
+ {{0x638b5007,0xefd71006,0x322ee018,0x00000000}}, // _vår_, _protože_, _pre__godinu_, --,
+ {{0xe3781011,0x00000000,0x00000000,0x00000000}}, // [2b0] _cabanga_, --, --, --,
+ {{0x1b5ec80e,0x00000000,0x00000000,0x00000000}}, // _कमान_, --, --, --,
+ {{0xca3df01d,0xe305a00c,0x00000000,0x00000000}}, // _दें_, _sk__pridala_, --, --,
+ {{0xada66818,0x00000000,0x00000000,0x00000000}}, // _што__су_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc3ea7810,0xcd7f0018,0xe473201c,0x00000000}}, // _bantu_, _пут_, _accepter_du_, --,
+ {{0x73fa6024,0xc27fb80d,0xa442d81c,0xb242d81d}}, // _udruge_, _obter__máis_, _uge_, _पिछले_,
+ {{0x6692f025,0x2442d810,0x23ec1821,0x62fcf007}}, // _के__विकास_, _ine_, _editar__editar_, _ganger_,
+ {{0x83f8c016,0x47721818,0xa2cad804,0x00000000}}, // _laluan_, _овог_, _hned_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x3442b004,0xa9827006,0x0318f00f,0xab1e7017}}, // _nic_, _možnost_, _vlastito_siteu_, _pesquisa_,
+ {{0x1291a010,0xf28ca012,0x00000000,0x00000000}}, // _papa_, _lugar__real_, --, --,
+ {{0x2300a00b,0x1ee3281d,0x00000000,0x00000000}}, // _linkedin__anda_, _दें__मित्र_, --, --,
+ {{0x1450e020,0x0bc4401d,0x904d0018,0x799b8810}}, // _poruka__od_, _जाती__है_, _фотограф, _shakisha_,
+ {{0xc2926016,0x00000000,0x00000000,0x00000000}}, // _borang_, --, --, --,
+ {{0xae47e80c,0x00000000,0x00000000,0x00000000}}, // [2c0] _spravoda, --, --, --,
+ {{0xa442d81b,0x00000000,0x00000000,0x00000000}}, // _ane_, --, --, --,
+ {{0xdb8c983c,0xd29c980d,0xe25b7816,0x842fd016}}, // _ऑफ_, _súas_, _soalan_, _kata__laluan_,
+ {{0x33958010,0x00000000,0x00000000,0x00000000}}, // _morsi_, --, --, --,
+ {{0xf2f2c005,0x7e037020,0xd442d806,0xc192b80c}}, // _profil__pogledaj, _uključen_, _dne_, _zmazať__topovať_,
+ {{0xb409b802,0x00000000,0x00000000,0x00000000}}, // _ver__más_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00b7a81d,0x00000000,0x00000000,0x00000000}}, // _टीवी__गुदगुदी_, --, --, --,
+ {{0xe2359808,0xed9c080c,0x2ffea007,0x00000000}}, // _pukul__pagi_, _odpoveda, _diskusjo, --,
+ {{0xcca96825,0x00000000,0x00000000,0x00000000}}, // _तक__पहुंच_, --, --, --,
+ {{0x5306c805,0x0316b811,0xfe0d2818,0x7019980c}}, // _milijuna__kuna_, _kuze__kube_, _ове_, _podmienk_používan,
+ {{0xc3940017,0x00000000,0x00000000,0x00000000}}, // _dois_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd2d9c81c,0xf3eac81c,0xebbc800e,0x3fbc500d}}, // _have_, _lidt_, _वर्ग__के_, _cambios__relacion,
+ {{0x8205a82e,0x13a2281a,0xab91e817,0x00000000}}, // [2d0] _memiliki_, _compre_, _gravar__registro_, --,
+ {{0x1b8c9825,0x72faf816,0x549fb007,0x4443f817}}, // _गो_, _lebih__juta_, _spørsmål__om_, _sou_,
+ {{0xc356682e,0x00000000,0x00000000,0x00000000}}, // _sebesar_, --, --, --,
+ {{0x12d9c826,0x00000000,0x00000000,0x00000000}}, // _lave_, --, --, --,
+ {{0xea3df03e,0x7443f81a,0x99eac811,0x8c11c810}}, // _देत_, _vou_, _izindaba_, _bishya__ibiganir,
+ {{0x3224d811,0xd2d34016,0x00000000,0x00000000}}, // _ngeke_, _dikenali_, --, --,
+ {{0xe32d0812,0x00000000,0x00000000,0x00000000}}, // _cerrar__sesión_, --, --, --,
+ {{0xa7bb8809,0xc4b53828,0x00000000,0x00000000}}, // _poslanik_, _index__php_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x82f99006,0xc708280a,0x00000000,0x00000000}}, // _přidat_, _के__राजा_, --, --,
+ {{0xfc27a014,0xf2f9a80b,0x00000000,0x00000000}}, // _artículo_, _dalam__masa_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x5068e81f,0xee28e818,0x00000000,0x00000000}}, // _ne__preporuč, _га_, --, --,
+ {{0x53054017,0xa317401f,0x00000000,0x00000000}}, // _menciona_citado_, _zvezda_, --, --,
+ {{0xda3e380a,0x00000000,0x00000000,0x00000000}}, // _फेर_, --, --, --,
+ {{0x4442d80c,0xe291d803,0x32d1c816,0xe2c49006}}, // _nie_, _jawa_, _bertulis__adalah_, _jméno__heslo_,
+ {{0x935f600b,0x00000000,0x00000000,0x00000000}}, // [2e0] _lihat__profil_, --, --, --,
+ {{0x11752025,0xf323e010,0x00000000,0x00000000}}, // _अल्पसंख्, _software__cyangwa_, --, --,
+ {{0x12d99017,0x02919017,0x026d800d,0x00000000}}, // _esse_, _essa_, _foron_, --,
+ {{0x32d9d810,0x00000000,0x00000000,0x00000000}}, // _nawe_, --, --, --,
+ {{0xc0536817,0x00000000,0x00000000,0x00000000}}, // _mensagen, --, --, --,
+ {{0x0b8cb022,0xebce9825,0x00000000,0x00000000}}, // _को_, _अध्ययन__के_, --, --,
+ {{0x127c2006,0x326c2010,0x72027016,0x526c8810}}, // _méně_, _koko_, _tarikh_, _gukora_,
+ {{0x04426806,0x3dce7818,0x00000000,0x00000000}}, // _pro_, _није_, --, --,
+ {{0x485f6817,0x0c6f681a,0x00000000,0x00000000}}, // _estrelas_, _estrela_, --, --,
+ {{0xc26de005,0x1249f810,0x00000000,0x00000000}}, // _diskutan_broj_, _nyuma_, --, --,
+ {{0xfe3bf00d,0xf236c01b,0x00000000,0x00000000}}, // _liña_, _produksi_, --, --,
+ {{0x92787006,0x64bb581a,0xd469e01f,0xc297b817}}, // _první_, _mais__de_, _gde__su_, _adiciona_como_,
+ {{0x2ed52818,0x64a3d812,0x00000000,0x00000000}}, // _под_, _delante__de_, --, --,
+ {{0x63f89010,0x00000000,0x00000000,0x00000000}}, // _mukuru_, --, --, --,
+ {{0x9c537024,0x447ba00f,0xe2d9d810,0xcaa2500c}}, // _postova_, _komentar_re_, _yawe_, _sa__musíte_,
+ {{0xc49be007,0xe44c6018,0x00000000,0x00000000}}, // _ut__av_, _gratis__na_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [2f0] --, --, --, --,
+ {{0xeb546015,0xdd699817,0x7ad68018,0x5cee3018}}, // _de__composte, _acessóri, _јануар_, _пред_,
+ {{0x52d8c80d,0x00000000,0x00000000,0x00000000}}, // _galego_, --, --, --,
+ {{0xbe1c480d,0x6459980f,0xe498601c,0xe2a69011}}, // _última__modifica, _bih__postao_, _nu__er_, _kwabo_,
+ {{0x62d9e810,0x7201e810,0x72f3981c,0x00000000}}, // _bate_, _bati_, _udgivet_, --,
+ {{0xd419800d,0x00000000,0x00000000,0x00000000}}, // _modifica_desta_, --, --, --,
+ {{0xa442d827,0x63e10816,0x00000000,0x00000000}}, // _tie_, _hb__ogos_, --, --,
+ {{0xd3949011,0x00000000,0x00000000,0x00000000}}, // _ngaso_, --, --, --,
+ {{0x86ff580a,0x00000000,0x00000000,0x00000000}}, // _जाला_, --, --, --,
+ {{0x2eecd818,0x00000000,0x00000000,0x00000000}}, // _ово__је_, --, --, --,
+ {{0x6a8a8818,0x52ca7810,0xa3959017,0xa0189006}}, // _август_, _kandi_, _nosso_, _přísluše,
+ {{0x14b3601c,0xb4429007,0x17a09006,0xb5f9681d}}, // _okt__kl_, _bra_, _podmínky_, _त्योहार_,
+ {{0x2ed4e818,0xf2fcd802,0x00000000,0x00000000}}, // _до_, _juego_, --, --,
+ {{0x12d8b006,0xb9ffc80d,0x2c072807,0x00000000}}, // _akce_, _véxase__tamén_, _uansett_, --,
+ {{0xc2da681b,0x12fcd802,0x2190a010,0x00000000}}, // _karena_, _luego_, _css__ntushobo, --,
+ {{0x9c813812,0x4a56b81d,0x00000000,0x00000000}}, // _opinión__escribe_, _अन्य__खेल_, --, --,
+ {{0x4442f81c,0xcef12018,0x00000000,0x00000000}}, // [300] _mig_, _има_, --, --,
+ {{0xc442000d,0x7355d81c,0xbc4e600a,0x00000000}}, // _hai_, _nyheder_, _कइले_, --,
+ {{0xf4444007,0xd1f79818,0xc26c201b,0xd7537818}}, // _ut_, _ова__страница_, _toko_, _други__језици_,
+ {{0xf4444007,0x9af6c01c,0x00000000,0x00000000}}, // _ho_, _oplevels, --, --,
+ {{0x24444010,0x3ec71014,0x56f9f00e,0x599f1811}}, // _ko_, _nosotros_, _मोटा_, _ngokushe,
+ {{0x64b7e815,0xee08c018,0x00000000,0x00000000}}, // _santiago__de_, _фебруар_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x34444014,0x72fce00d,0x1c96781d,0x00000000}}, // _lo_, _lingua_, _क्यों_, --,
+ {{0xb442f81c,0x7e51901f,0x00000000,0x00000000}}, // _dig_, _je__uključen_, --, --,
+ {{0xe4438023,0xa26de839,0xefe10818,0x7ab81806}}, // _kur_, _potom_, _свој_, _odpověď_,
+ {{0x8c083810,0x00000000,0x00000000,0x00000000}}, // _ibiganir, --, --, --,
+ {{0x94429029,0xc2de081d,0x04438010,0x326fa811}}, // _pra_, _किसी_, _mur_, _abaningi_,
+ {{0x94444010,0xf4b48817,0x0ea18811,0x07534007}}, // _bo_, _estrelas__bom_, _emhlaben, _barnehag,
+ {{0x22cbc81c,0xe2dc102a,0x83236821,0x00000000}}, // _havde_, _dalmacij, _de__nomes_, --,
+ {{0x430d2008,0xa442000d,0xd34c8803,0x00000000}}, // _pelajar_, _fai_, _yang__berbeda_, --,
+ {{0x84be380d,0x3470e00c,0x5165501d,0xc9fff812}}, // _consulte__os_, _tak__sa_, _कॉमेंट__लाइव_, _búsqueda,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [310] --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x1444400c,0xa4be3023,0xc20b981b,0xda63280e}}, // _zo_, _programe__za_, _minggu__hari_, _फेस्टिवल_,
+ {{0x04444010,0x84438039,0x646c601a,0x00000000}}, // _yo_, _eur_, _com__de_, --,
+ {{0xa442f81c,0x48fa2817,0xd28a5018,0x00000000}}, // _sig_, _direitos__reservad, _навигаци_претрага_, --,
+ {{0x16fed819,0x8706081d,0x6bd74012,0x00000000}}, // _किया_, _खबर__संसार_, _tu__informac, --,
+ {{0x55fec820,0xb3134006,0xf3730012,0x82c2a81b}}, // _korišćen, _než__více_, _delante_, _anda__bisa_,
+ {{0x34bb901c,0x92781803,0x00000000,0x00000000}}, // _klik__her_, _mengirim, --, --,
+ {{0x8ef11818,0x2442101b,0x2eefe818,0xf51e4011}}, // _смо_, _nah_, _текст__је_, _inhliziy,
+ {{0xcc7e6014,0x086e6014,0x7307e016,0x00000000}}, // _nuestro_, _nuestros_, _menyerta_juta_, --,
+ {{0x9fd7800c,0x00000000,0x00000000,0x00000000}}, // _pretože_, --, --, --,
+ {{0x127b7806,0xe4627007,0x0397c01c,0x00000000}}, // _komentář, _tilbake__til_, _medarbej, --,
+ {{0xc474602a,0x00000000,0x00000000,0x00000000}}, // _tko__je_, --, --, --,
+ {{0xd444400c,0x54936006,0x00000000,0x00000000}}, // _vo_, _by__se_, --, --,
+ {{0x2ed5981e,0xe4444010,0x526c5811,0x00000000}}, // _než_, _wo_, _lolo_, --,
+ {{0xf328a009,0x83415006,0x5bee7018,0x00000000}}, // [320] _na__jezeru_, _stejně_, _зато_, --,
+ {{0x6caff817,0xebbe6025,0xac617814,0x00000000}}, // _no__cartão_, _केंद्र__के_, _alguien_, --,
+ {{0x198b400c,0x54431017,0x6367c80d,0x00000000}}, // _prečo_, _diz_, _imprimir__caixa_, --,
+ {{0xb2329009,0xd9b93811,0x00000000,0x00000000}}, // _februara__godine_, _kujehova_, --, --,
+ {{0xb26c580c,0xd442101b,0x00000000,0x00000000}}, // _bolo_, _yah_, --, --,
+ {{0xa98b400c,0x526e180c,0x6eab8818,0x00000000}}, // _niečo_, _šport_, _повезао__са_, --,
+ {{0x04422024,0x1386880c,0xb27ee807,0x2f1b980c}}, // _kak_, _ktoré_, _finner_, _vaša_,
+ {{0xf4422006,0x226da020,0xfb248016,0xdb09f812}}, // _jak_, _lepo_, _dilarang__linkedin_, _publicar__opinión_,
+ {{0xc27f0011,0xc290e024,0xe0f3e82a,0x24422008}}, // _abantu_, _dinamo_, _županije_, _mak_,
+ {{0xe2b5d807,0x00000000,0x00000000,0x00000000}}, // _upassend_innlegg_, --, --, --,
+ {{0x4ceed818,0x6478600d,0x942af818,0x0c07e01d}}, // _уреди_, _xunta__de_, _dezinfik_celu_, _लगता__है_,
+ {{0x4444401c,0xc20d200d,0x34422016,0x00000000}}, // _af_, _máis_, _nak_, --,
+ {{0x0cd9381b,0x6e53200b,0x00000000,0x00000000}}, // _melalui__seluler_, _sumbanga, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa26da00f,0x72d9e806,0xd386900c,0x74422028}}, // _depo_, _jste_, _ktorá_, _bak_,
+ {{0x09c0f805,0x101fa017,0x00000000,0x00000000}}, // _prometna_, _endereço_, --, --,
+ {{0xe226001b,0x00000000,0x00000000,0x00000000}}, // [330] _kaskus_, --, --, --,
+ {{0xbe19f803,0x4399c01c,0xac71b835,0x00000000}}, // _menemuka, _læste_, _startet_, --,
+ {{0xb4936014,0x13f0b80d,0x00000000,0x00000000}}, // _es__el_, _da__páxina_, --, --,
+ {{0xc4422003,0x74439002,0x6399c01c,0x13960013}}, // _gak_, _tus_, _næste_, _masser_,
+ {{0xa7605018,0x0a80500e,0x930b9817,0x00000000}}, // _ауторств_делити_, _असल__में_, _um__pouco_, --,
+ {{0xaed56818,0xec5af818,0x60b27806,0x5326e80d}}, // _још_, _izvor__komentar, _práva__vyhrazen, _en__galego_,
+ {{0x230e100b,0xe33b4818,0x5ea6a807,0x130d281b}}, // _apabila_, _tekstova__blogu_, _fortsatt_, _pelapor_,
+ {{0xc9c64029,0x00000000,0x00000000,0x00000000}}, // _apresent, --, --, --,
+ {{0x22d9e80c,0x04423005,0x72979812,0x00000000}}, // _este_, _kaj_, _amigo__pero_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x24423013,0xe4826826,0x00000000,0x00000000}}, // _maj_, _adgang__til_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb201d811,0xa2d9d811,0x226cb011,0x0cca0816}}, // _izwi_, _izwe_, _indoda_, _nama__pertama_,
+ {{0xb6f9b03f,0x54422006,0xd1c5e81d,0xf2fce01b}}, // _पैसा_, _pak_, _सेहत__व्यंजन_, _pengen_,
+ {{0xb3395823,0xa3b89823,0x4c01b81b,0x0355d816}}, // _je__napisao_, _stranica__natrag_, _agustus_, _suka__komen_,
+ {{0xcc802018,0x92da4811,0xe290c016,0x9f945806}}, // [340] _везе_, _ezweni_, _jumaat_, _svého_,
+ {{0x64420010,0x00000000,0x00000000,0x00000000}}, // _isi_, --, --, --,
+ {{0xd27ee811,0xb2cb4816,0x00000000,0x00000000}}, // _ezinye_, _kaedah_, --, --,
+ {{0xc234f816,0x00000000,0x00000000,0x00000000}}, // _buat__kali_, --, --, --,
+ {{0x609eb005,0xb9dbf837,0x00000000,0x00000000}}, // _registri_korisnik_, _दान__सहायता_, --, --,
+ {{0x6301e01b,0x6457b829,0xabf51006,0x84433021}}, // _berarti_, _até__de_, _produktů_, _xix_,
+ {{0x434c200f,0x472d980e,0x73afc807,0x96efe00d}}, // _lud__zbunjen_, _दिखाई__देता_, _håper_, _páxinas__especiai,
+ {{0xe213a011,0xa303f807,0x00000000,0x00000000}}, // _lapho_, _les__hele_, --, --,
+ {{0x2412401b,0x73bcd81b,0x02b4301b,0x71fe2818}}, // _bagi__simpan_, _menerima__syarat_, _jakarta__pusat_, _кроз_,
+ {{0x82895011,0x00000000,0x00000000,0x00000000}}, // _endaweni_, --, --, --,
+ {{0xc2fc7803,0xecbe2818,0x00000000,0x00000000}}, // _dong_, _каже_, --, --,
+ {{0xaa32b019,0x00000000,0x00000000,0x00000000}}, // _कोश_, --, --, --,
+ {{0x8c38201d,0xdb926806,0x00000000,0x00000000}}, // _लखनऊ_, _srovnání_, --, --,
+ {{0xe98a6806,0x9f5c8021,0x00000000,0x00000000}}, // _proč_, _de__contrata, --, --,
+ {{0xe290f811,0x00000000,0x00000000,0x00000000}}, // _johane_, --, --, --,
+ {{0x204ba034,0x14a77807,0x66465007,0x4245a016}}, // _सामग्री_, _kontakt__oss_, _gjennomf, _pandu__arah_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [350] --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x72d8a02a,0xd2d9801c,0xa36a1003,0x00000000}}, // _uvjeti_, _opret_, _iklan__yang_, --,
+ {{0xb442d80c,0x42cad80c,0xdc01e01c,0x00000000}}, // _pre_, _pred_, _næsten_, --,
+ {{0xad9c4018,0x00000000,0x00000000,0x00000000}}, // _могући__су_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x2c74003f,0xa4aa581b,0x00000000,0x00000000}}, // _आवश्यक_, _harga__rp_, --, --,
+ {{0x2fcde80c,0xb4b2181c,0x00000000,0x00000000}}, // _článok_, _til__dig_, --, --,
+ {{0xc2b6e023,0x0dad0017,0x140d380d,0x54254018}}, // _ica__vrijeme_, _quantida, _desta__páxina_, _dezinfik,
+ {{0x3ba5881d,0x00000000,0x00000000,0x00000000}}, // _है__जो_, --, --, --,
+ {{0x5a3cc00a,0x52ebc81c,0x2ba0100c,0x00000000}}, // _एकर_, _kvinder_, _porovnať__sledovať_, --,
+ {{0xe442582e,0x7394900d,0x00000000,0x00000000}}, // _hal_, _coas_, --, --,
+ {{0x6fe79806,0x00000000,0x00000000,0x00000000}}, // _svůj_, --, --, --,
+ {{0xa22b201f,0x00000000,0x00000000,0x00000000}}, // _promene_, --, --, --,
+ {{0x23ef482a,0x34425827,0x1c5f482a,0x84b1b023}}, // _obitelji_, _mal_, _obitelj_, _kur__ana_,
+ {{0x43721017,0x6ca5b80c,0x00000000,0x00000000}}, // _como__amigo_, _upraviť__zmazať_, --, --,
+ {{0x8201601b,0x34b22823,0xfb0ae00d,0xe08ae00d}}, // [360] _bagian_, _posta__nja_, _conselle, _concello,
+ {{0xd471f815,0x223ea026,0xb6dc6018,0x00000000}}, // _nos__que_, _svar__skriv_, _коришћењ, --,
+ {{0x626de81c,0x9412b01b,0x00000000,0x00000000}}, // _netop_, _com__buat_, --, --,
+ {{0x4aea6807,0x00000000,0x00000000,0x00000000}}, // _fortelle, --, --, --,
+ {{0x0bffc01d,0xc4b6e007,0x00000000,0x00000000}}, // _होती__है_, _løpet__av_, --, --,
+ {{0x94425815,0xd3868811,0xb299701b,0x00000000}}, // _cal_, _abazali_, _simpan__iklan_, --,
+ {{0xf236680c,0x53eac007,0x00000000,0x00000000}}, // _svoj_, _alltid_, --, --,
+ {{0x6b8e4019,0xebcb7825,0x00000000,0x00000000}}, // _आप_, _राष्ट्र__के_, --, --,
+ {{0x9467c805,0xc25a9011,0x63ce900f,0x3323a81c}}, // _postove__datum_, _obala_, _slavo_, _af__vores_,
+ {{0xd4425811,0x998ab00c,0x00000000,0x00000000}}, // _gal_, _podľa_, --, --,
+ {{0xae612818,0x626fb816,0x00000000,0x00000000}}, // _где_, _malaysia__tinjau_, --, --,
+ {{0xf4aa2018,0x0236680c,0xb4808810,0x00000000}}, // _din__din_, _tvoj_, _kuri__uyu_, --,
+ {{0xe66c1824,0xe386c807,0x1a421825,0x00000000}}, // _odvjetni, _aldri_, _के__कुल_, --,
+ {{0xa254001d,0x6ba20006,0x00000000,0x00000000}}, // _कॉमेंट_, _případě_, --, --,
+ {{0xdc49e036,0xb2f56006,0x00000000,0x00000000}}, // _पैसे_, _mpix__více_, --, --,
+ {{0x4492601c,0x3a3c400a,0x6303f807,0x00000000}}, // _en__af_, _आपन_, _les__alle_, --,
+ {{0x646ce015,0x911d982a,0x00000000,0x00000000}}, // [370] _foi__de_, _na__sadržaj_, --, --,
+ {{0x34b9b00f,0x00000000,0x00000000,0x00000000}}, // _sarajevo__vrh_, --, --, --,
+ {{0x47154017,0x2afe881b,0x00000000,0x00000000}}, // _seguranç, _aparteme, --, --,
+ {{0x0864e809,0xdfe3d806,0x54426817,0xb4a8281a}}, // _bosne__hercegov, _celý__článek_, _nao_, _opcional_,
+ {{0x23206811,0xa3325003,0x00000000,0x00000000}}, // _umoya_, _informas_yang_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x7e8a9830,0x0a5df00e,0x8a3ee01d,0x00000000}}, // _प्रतिशत_, _कार्यक्र_में_, _फिर_, --,
+ {{0x2379a811,0x026cd81f,0x548a5806,0x5f3a402e}}, // _uthando_, _hteo_, _které__se_, _transfer_,
+ {{0x6c621803,0x00000000,0x00000000,0x00000000}}, // _masukan_, --, --, --,
+ {{0x52ff880f,0xe27f7804,0x00000000,0x00000000}}, // _odobrenj_takođe_, _jedná_, --, --,
+ {{0xec59e01d,0xfed51818,0x00000000,0x00000000}}, // _साल__पहले_, _док_, --, --,
+ {{0x0a3e500e,0xd44cd816,0x00000000,0x00000000}}, // _भजन_, _lompat__ke_, --, --,
+ {{0xc25ac81c,0xc6c5f808,0x00000000,0x00000000}}, // _mellem_, _sebahagi, --, --,
+ {{0x1c53702a,0xd2fc9008,0x92dfa01d,0xe4c3b006}}, // [380] _sustava_, _niaga_, _शादी_, _přejít__na_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd2ca781c,0x927f5007,0x00000000,0x00000000}}, // _mand_, _trener_, --, --,
+ {{0xaea0980b,0x00000000,0x00000000,0x00000000}}, // _linkedin__pengguna, --, --, --,
+ {{0xb29f9011,0x00000000,0x00000000,0x00000000}}, // _kanjalo_, --, --, --,
+ {{0x726cf804,0x00000000,0x00000000,0x00000000}}, // _tohoto_, --, --, --,
+ {{0xeb94c00e,0x23163017,0x7eb3000d,0xc62d6807}}, // _दिवस__के_, _em__estoque_, _dereitos_, _trondhei,
+ {{0x13dc4810,0x00000000,0x00000000,0x00000000}}, // _hamwe_, --, --, --,
+ {{0xe40b8818,0x9442782e,0x00000000,0x00000000}}, // _ujutru__ujutru_, _ban_, --, --,
+ {{0xdf10c01f,0xaf1b980c,0x00000000,0x00000000}}, // _predsedn, _naša_, --, --,
+ {{0x52925029,0x00000000,0x00000000,0x00000000}}, // _citado_, --, --, --,
+ {{0xf26cd006,0xcca9101d,0x5e2ae00c,0x00000000}}, // _budou_, _और__देखें_, _na__základe_, --,
+ {{0xa3ea7807,0x1da3380a,0x00000000,0x00000000}}, // _fant_, _संगोष्ठी_, --, --,
+ {{0xe442781b,0x34439018,0x12f1200e,0x7cc0a81d}}, // _gan_, _sns_, _जरुरत_, _जाने__वाले_,
+ {{0x426cb014,0xd1ff3823,0xdf3bd01b,0xc5591018}}, // _poco_, _sljedeća__prikaži_, _kebutuha, _октобар__септемба,
+ {{0x54900814,0x34baf007,0x00000000,0x00000000}}, // _el__uso_, _inn__på_, --, --,
+ {{0x5c649002,0xaf87500c,0x00000000,0x00000000}}, // [390] _octubre_, _košice_, --, --,
+ {{0xf484883c,0x8d83880c,0x547dd015,0x00000000}}, // _ऑनलाइन_, _nič_, _foto__no_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa9f54017,0xc29b2017,0xafe12812,0x00000000}}, // _preço_, _sem__juros_, _el__vendedor_, --,
+ {{0x44438007,0xb3869809,0xc4426810,0x00000000}}, // _gir_, _bajram_, _aho_, --,
+ {{0x442f1011,0x92d2e80d,0xd38ea01e,0x00000000}}, // _main__menu_, _ás__todo_, _ubytován, --,
+ {{0x427ed81c,0xc2015817,0x14adc017,0x00000000}}, // _uden_, _digite_, _serviços_, --,
+ {{0x6c51f023,0xa4439014,0xc4a4e00c,0x9426181b}}, // _kantona_, _mis_, _čo__je_, _tulisan__baru_,
+ {{0x22cb4014,0x00000000,0x00000000,0x00000000}}, // _pueden_, --, --, --,
+ {{0x43d4581c,0x03eb8016,0x62ca781c,0xb421f818}}, // _mest__læste_, _parti_, _vand_, _ženski__poruke_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd2fe701c,0xe2267007,0x73566816,0x00000000}}, // _bruges_, _brukes_, _sebenar_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe386a027,0xb3871006,0xb466881c,0x00000000}}, // _dobre_, _vybrat_, _tilmeld__dig_, --,
+ {{0x12a6d80c,0x47cea810,0x2446d80d,0x00000000}}, // [3a0] _alebo_, _amashaki, _galego__ir_, --,
+ {{0xc2c91816,0x97580025,0x72111011,0x00000000}}, // _dengan__terma_, _विद्वान_, _sikhathi_, --,
+ {{0xc3870006,0xf208000c,0x6fa4f839,0xc32e8010}}, // _které_, _informác, _používan, _to__friend_,
+ {{0x0492a812,0x54983010,0x22d3a812,0x00000000}}, // _es__por_, _ya__css_, _del__foro_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf450100d,0xec527810,0x00000000,0x00000000}}, // _túa__conta_, _amatora_, --, --,
+ {{0x34b1300f,0xdfa1100d,0x93b1100d,0x00000000}}, // _okt__vrh_, _servizos_, _servizo_, --,
+ {{0x626c2010,0x8396680d,0x53f89011,0x00000000}}, // _kuko_, _persoa_, _enkulu_, --,
+ {{0xbbbdb00e,0xd2cac011,0x00000000,0x00000000}}, // _अंत__तक_, _endlini_, --, --,
+ {{0x03ea9003,0x34429010,0xd3b55818,0x00000000}}, // _saat_, _saa_, _налог__одјавите_, --,
+ {{0x99f3a808,0x83860007,0x83870806,0x93ce001c}}, // _daripada_, _blir_, _která_, _bliv_,
+ {{0x1999a006,0x00000000,0x00000000,0x00000000}}, // _např_, --, --, --,
+ {{0xe23b0807,0x12a6d805,0xd2fc901c,0xac60580c}}, // _innlegg_, _osobnu_, _fragt_, _augusta_,
+ {{0x94426821,0xd3eb9007,0xfc0b2025,0x00000000}}, // _iso_, _sist_, _रुप__से_, --,
+ {{0x48871017,0x93947807,0x8fd0981d,0x00000000}}, // _bom__estrelas_, _minst_, _पर्यटन__समाचार_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x4fe11018,0x00000000,0x00000000,0x00000000}}, // [3b0] _овај_, --, --, --,
+ {{0xb218101d,0x00000000,0x00000000,0x00000000}}, // _स्__टाइल_, --, --, --,
+ {{0x8352880a,0x525bf829,0x3cd6201d,0xe3f88810}}, // _के__संबंध_, _paulo_, _संपर्क__करें_, _gihugu_,
+ {{0x547d501a,0x63492010,0x00000000,0x00000000}}, // _para__se_, _yishingi_ibibera_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xeb8c901d,0x83967817,0x00000000,0x00000000}}, // _गई_, _pessoa_, --, --,
+ {{0x73eb801b,0x26eae81d,0x6c61781d,0xea5ab012}}, // _kartu_, _है__लेकिन_, _को__भेजें_, _reportar__contenid,
+ {{0xadc0c818,0x5ae20029,0x93e3a823,0x6e6c5007}}, // _који__су_, _interess, _tuzlansk, _du__spørsmål_,
+ {{0x14b4600d,0x1f92a818,0xd491f815,0xec7d7814}}, // _foundati_inc_, _pol__muški_, _na__que_, _persona_,
+ {{0x54429010,0x00000000,0x00000000,0x00000000}}, // _aha_, --, --, --,
+ {{0x04a99812,0x00000000,0x00000000,0x00000000}}, // _abajo__por_, --, --, --,
+ {{0x74429011,0x4dc2601f,0x00000000,0x00000000}}, // _cha_, _jednosta_način_, --, --,
+ {{0xaed4e818,0xca0f8006,0x0292100d,0x00000000}}, // _то_, _následuj, _espazo_, --,
+ {{0xe498f012,0x00000000,0x00000000,0x00000000}}, // _categorí_es_, --, --, --,
+ {{0xc9b14806,0x33e51024,0xbe71480c,0x00000000}}, // _ostatní_, _prikaži__sakrij_, _ostatné_, --,
+ {{0x026cf817,0xd690c01d,0xb443f811,0x00000000}}, // _jogo_, _के__खिलाफ_, _ngu_, --,
+ {{0xe3085016,0x3ce9f00c,0x14abe812,0x00000000}}, // [3c0] _mengikut__negara_, _pridala__príspevk, _hablaras__con_, --,
+ {{0x02fcf807,0x00000000,0x00000000,0x00000000}}, // _logg_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x26d29022,0xc200b00f,0x22c89017,0xf345901d}}, // _लेकिन_, _kukić_, _estoque_, _बॉलीवुड__टीवी_,
+ {{0x4ed25818,0x00000000,0x00000000,0x00000000}}, // _да__ће_, --, --, --,
+ {{0x54444010,0x7c36380c,0x00000000,0x00000000}}, // _ku_, _vybrať_, --, --,
+ {{0x44444027,0xc2d38803,0x00000000,0x00000000}}, // _ju_, _dari__berbagai_, --, --,
+ {{0x74444010,0x00000000,0x00000000,0x00000000}}, // _mu_, --, --, --,
+ {{0x9316c80c,0x6200f811,0x00000000,0x00000000}}, // _medzi_, _othile_, --, --,
+ {{0xd33fc013,0xf62c1817,0x24e9781d,0x7291a01b}}, // _der__ikke_, _condiçõe, _नायिका__रोमांस_, _oppa_,
+ {{0x8444401c,0x00859808,0xf4908817,0x00000000}}, // _nu_, _juta__profesio, _comentár_seu_, --,
+ {{0x327f801c,0x19f65017,0x00000000,0x00000000}}, // _gerne_, _então_, --, --,
+ {{0xd442c816,0x72542018,0x0b3c2810,0x00000000}}, // _kad_, _po__ceni_, _neza__ukoreshe, --,
+ {{0xbb89d014,0x232c281c,0xd4426810,0x00000000}}, // _noviembr, _se__mere_, _izo_, --,
+ {{0xa4b4d020,0x0491a007,0x00000000,0x00000000}}, // _noćenja_, _er__ein_, --, --,
+ {{0xb2d8b811,0x00000000,0x00000000,0x00000000}}, // _abafundi_, --, --, --,
+ {{0xbab46806,0x53178006,0x00000000,0x00000000}}, // [3d0] _vlastní_, _verze_, --, --,
+ {{0x943b3802,0xc05fe80e,0x42c2d014,0x00000000}}, // _me__gusta_, _उदास_, _un__poco_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb3f40807,0x801a6020,0x00000000,0x00000000}}, // _har__vært_, _vojvodin, --, --,
+ {{0xbf8bf00c,0x4861a011,0x00000000,0x00000000}}, // _iné_, _izincwad, --, --,
+ {{0xae00e818,0x00000000,0x00000000,0x00000000}}, // _које__су_, --, --, --,
+ {{0x72d5581c,0xfb4a4812,0x99990006,0x00000000}}, // _fået_, _una__respuest, _dobře_, --,
+ {{0x8aa6d011,0x00000000,0x00000000,0x00000000}}, // _zebhayib, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x74ae580f,0x00fd800a,0x00000000,0x00000000}}, // _stranke__po_, _मालवा_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x48163810,0x0abe101d,0x00000000,0x00000000}}, // _mugihe__amashaki, _मामले__में_, --, --,
+ {{0xb4abe81b,0xc399381c,0x726c5811,0x89e55011}}, // _bermanfa_dan_, _måske_, _kulo_, _indodana_,
+ {{0x0443f810,0xec70301d,0x5c6bc028,0x00000000}}, // _onu_, _महीने__पहले_, _akkurat_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [3e0] --, --, --, --,
+ {{0x126d8014,0xf3cd0806,0x00000000,0x00000000}}, // _otros_, _osobní_, --, --,
+ {{0x4def300c,0x00000000,0x00000000,0x00000000}}, // _zaujímav, --, --, --,
+ {{0xebcb780e,0x6467301b,0xceaa381d,0x00000000}}, // _सरकार__के_, _apa__aja_, _हॉट__शॉट्_, --,
+ {{0x422a2006,0x19f4e006,0x434b981c,0x00000000}}, // _přihlási, _deník_, _at__bruge_, --,
+ {{0x0c8fb81d,0xa31e1016,0x00000000,0x00000000}}, // _हमारे__बारे_, _sesiapa__yang_, --, --,
+ {{0x7c48481d,0x00000000,0x00000000,0x00000000}}, // _वाले_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc220d80c,0x00000000,0x00000000,0x00000000}}, // _dámske_, --, --, --,
+ {{0x3c8ad80c,0x82c24811,0x2baba018,0x34924014}}, // _príspevk, _indlela_, _истим__условима_, _es__muy_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x9da79816,0xe4b1f807,0x630ed002,0x00000000}}, // _kerosaka, _opp__til_, _tambien_, --,
+ {{0x348f080c,0xda3c281d,0x00000000,0x00000000}}, // _páči__sa_, _एंड_, --, --,
+ {{0x7c8ad80c,0xe402a00d,0x00000000,0x00000000}}, // _príspevo, _sen__fins_, --, --,
+ {{0xb443e81c,0xa38a8017,0x83ebe807,0x0213e811}}, // _mit_, _fórum_, _mitt_, _bathi_,
+ {{0x73ebe807,0x83eae81c,0x00000000,0x00000000}}, // _litt_, _haft_, --, --,
+ {{0x646d6021,0x00000000,0x00000000,0x00000000}}, // [3f0] _lei__de_, --, --, --,
+ {{0x9a26383a,0x776f6833,0x00000000,0x00000000}}, // _उपलब्ध_, _टिप्पणी_, --, --,
+ {{0xab8cb819,0x00000000,0x00000000,0x00000000}}, // _की_, --, --, --,
+ {{0xf2026816,0x22e7a00d,0x00000000,0x00000000}}, // _carian_, _acceder__para_, --, --,
+ {{0xeca1681d,0x00000000,0x00000000,0x00000000}}, // _से__पहले_, --, --, --,
+ {{0x929c980d,0x00000000,0x00000000,0x00000000}}, // _rúa_, --, --, --,
+ {{0xa29c980d,0x2443e826,0x29bce818,0xf3ebe807}}, // _súa_, _dit_, _коментар, _ditt_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6461d814,0x5427c018,0x00000000,0x00000000}}, // _más__de_, _се__повезао_, --, --,
+ {{0xee9d1818,0x546f2007,0xcf33c002,0x23ebe807}}, // _ако_, _varslet__om_, _presenta, _gitt_,
+ {{0xd2cc181c,0x432cb807,0xa2f4e816,0x00000000}}, // _at__kunne_, _høy_, _melalui__mudah_, --,
+ {{0x147bc02a,0xb29c980d,0x00000000,0x00000000}}, // _komentar__od_, _túa_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe27f7810,0x2a883819,0x6b9db814,0x50e8381d}}, // _rwanda_, _लाइफ_, _más__informac, _लाइव_,
+ {{0x83e3300b,0x00000000,0x00000000,0x00000000}}, // _ke__atas_, --, --, --,
+ {{0x53a6201b,0xd7377806,0xcf01a80c,0x00000000}}, // _dki__jakarta_, _dalších_, _hodnoten_hviezdič, --,
+
+ {{0x826bb02a,0x0c16581d,0x232e0010,0x00000000}}, // [400] _sveučili, _व्यापार__करियर_, _mu__karere_, --,
+ {{0x5213e811,0x02915811,0x539ab006,0x94420010}}, // _wathi_, _ingabe_, _město_, _iyi_,
+ {{0xf2965003,0x9e50d011,0x227e7807,0xd47ba017}}, // _ini__semoga_, _ngaphezu_, _menn_, _para__sua_,
+ {{0xec7b8805,0xeed28818,0x0395080c,0x2460c80d}}, // _nađi_, _је__био_, _časti_, _tamén__editar_,
+ {{0x6c62300c,0x00000000,0x00000000,0x00000000}}, // _pozrite_, --, --, --,
+ {{0xda0aa008,0x00000000,0x00000000,0x00000000}}, // _manakala_, --, --, --,
+ {{0x0b9e881d,0x12e86816,0x1c102018,0x04979018}}, // _गई__है_, _baginda_, _фејсбуку__како_, _је__било_,
+ {{0xf2d82810,0x5546900c,0x00000000,0x00000000}}, // _kamere_, _zobraziť_, --, --,
+ {{0xdc49201d,0xc2d9201d,0x00000000,0x00000000}}, // _बड़े_, _बड़ी_, --, --,
+ {{0x5c5d900e,0xf2ce2809,0x7c69f010,0x00000000}}, // _के__चलते_, _navođenj_izvora_, _burundi_, --,
+ {{0x5236d016,0x00000000,0x00000000,0x00000000}}, // _projek_, --, --, --,
+ {{0x046be807,0xd0fe8025,0x630de810,0x00000000}}, // _hva__som_, _के__अभाव_, _ibibera_, --,
+ {{0xa29cb80c,0x6c5a580b,0x00000000,0x00000000}}, // _zľava_, _menteri_, --, --,
+ {{0xca32b022,0x00000000,0x00000000,0x00000000}}, // _कोई_, --, --, --,
+ {{0x53dc1003,0x60458006,0x00000000,0x00000000}}, // _bahwa_, _vyhrazen, --, --,
+ {{0x84444007,0x63417010,0x00000000,0x00000000}}, // _eg_, _barenga_, --, --,
+ {{0x03f4002a,0x82561814,0x00000000,0x00000000}}, // [410] _sustav_, _sólo_, --, --,
+ {{0x55536811,0x2b5fd818,0x00000000,0x00000000}}, // _isikhath, _условима_, --, --,
+ {{0x92a64810,0x00000000,0x00000000,0x00000000}}, // _zombi_, --, --, --,
+ {{0x92e66006,0xd4444024,0xe408081b,0x00000000}}, // _více__zadat_, _zg_, _serupa__untuk_, --,
+ {{0xbc48f03c,0x00000000,0x00000000,0x00000000}}, // _तुझे_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd25a9011,0x4472d81c,0x00000000,0x00000000}}, // _njalo_, _skrevet__af_, --, --,
+ {{0xb07bd01f,0x69d2d007,0xeda8a818,0x54431002}}, // _bezbedno, _nettsted, _оно__што_, _haz_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xccf4900f,0x0f1b700c,0x00000000,0x00000000}}, // _objavlji_pjesama_, _odporúča, --, --,
+ {{0xa434f018,0xa718b01d,0xb27e7807,0x00000000}}, // _sati__minuta_, _हो__जाता_, _venn_, --,
+ {{0x23eaf815,0x327f9009,0x92abe014,0x52391816}}, // _editar_, _bosne_, _de__años_, _urus__janji_,
+ {{0xf22b400c,0x00000000,0x00000000,0x00000000}}, // _upraviť_, --, --, --,
+ {{0x22a66811,0xb394901a,0x2b226811,0x00000000}}, // _ngoba_, _duas_, _ekuphile, --,
+ {{0xe28c3010,0xf3dd901b,0x847dd028,0x00000000}}, // _igihugu_, _siswa_, _skal__ha_, --,
+ {{0x126d0810,0x4fe00806,0x00000000,0x00000000}}, // _kubona_, _kvůli_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [420] --, --, --, --,
+ {{0x44432014,0x42fdf81c,0x5225f807,0xfb8ff80a}}, // _hay_, _bruge_, _bruke_, _तऽ_,
+ {{0x1e0d2018,0xb24a500b,0x00000000,0x00000000}}, // _све_, _resolusi_, --, --,
+ {{0xb3eab814,0xd442c818,0xb47bd817,0xe4b11814}}, // _lo__más_, _rsd_, _de__desconto_, _puede__ser_,
+ {{0x24444005,0xae43600e,0x34431017,0x225ac823}}, // _kn_, _बांग्लाद, _faz_, _sellem_,
+ {{0xd2ad502a,0x0b16c009,0xce55a009,0xe4926002}}, // _objavlje_prije_, _sarajevs, _islamske_, _en__tu_,
+ {{0x0442d810,0x9c51d816,0x00000000,0x00000000}}, // _ese_, _tentera_, --, --,
+ {{0x54b7080c,0x81e1d81d,0x00000000,0x00000000}}, // _môžete_, _दूसरे_, --, --,
+ {{0xfdaac80d,0x54a3e016,0x8e57d023,0x00000000}}, // _rexistra, _bersetuj, _bosanske_, --,
+ {{0x93043816,0x226ca011,0xa4456015,0x00000000}}, // _mengguna_laman_, _kubo_, _editar__as_, --,
+ {{0xffe82037,0xa3949029,0x3e756018,0xa4218016}}, // _स्थानीय_, _suas_, _скочи__на_, _pelawat__about_,
+ {{0xa455e02a,0x2e847818,0x00000000,0x00000000}}, // _poruku__za_, _рекао__је_, --, --,
+ {{0x31fda806,0x52291816,0x00000000,0x00000000}}, // _vyhledáv, _rujukan__saling_, --, --,
+ {{0xdeb9f007,0xe26d8014,0x00000000,0x00000000}}, // _gjør_, _otro_, --, --,
+ {{0x64abd815,0xc0895012,0xf443100c,0x00000000}}, // _darse__de_, _términos__condicio, _raz_, --,
+ {{0x646da814,0x00000000,0x00000000,0x00000000}}, // _cantidad__de_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [430] --, --, --, --,
+ {{0x5bb76809,0xec49901d,0xdb29f012,0xd2d9901d}}, // _pridruže, _पहले_, _públicam_opinión_, _पहली_,
+ {{0xa2242007,0x63946816,0x1c67c011,0xc3e4c028}}, // _fikk_, _ogos_, _namuhla_, _kl__tekst_,
+ {{0xb2242007,0x8d3c3806,0x746ae00c,0x00000000}}, // _gikk_, _velikost_, _ako__si_, --,
+ {{0x41427818,0x03869010,0x00000000,0x00000000}}, // _је__доступан_, _atari_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x9c898005,0x41a77818,0x3291681b,0x00000000}}, // _broj__postova_, _на__фејсбуку_, _dijual__rumah_, --,
+ {{0x62fb9008,0xf9f43006,0x00000000,0x00000000}}, // _kanak__kanak_, _její_, --, --,
+ {{0xc4439817,0x00000000,0x00000000,0x00000000}}, // _às_, --, --, --,
+ {{0xc4bc5007,0xcbe51025,0x00000000,0x00000000}}, // _bruk__av_, _तक__ही_, --, --,
+ {{0xa25a001c,0x00000000,0x00000000,0x00000000}}, // _spil_, --, --, --,
+ {{0xe291d810,0x00000000,0x00000000,0x00000000}}, // _ukwa_, --, --, --,
+ {{0x6d6d3818,0x00000000,0x00000000,0x00000000}}, // _сте_, --, --, --,
+ {{0x6bee1818,0x5319a011,0x72f1301c,0x00000000}}, // _тако_, _noma__kunjalo_, _bøger_, --,
+ {{0x62e5e005,0x9c8e100a,0xb442d806,0x00000000}}, // _kronika_, _कवनो_, _lze_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x0c49201d,0x24420010,0x22771818,0x1a92101c}}, // [440] _मुझे_, _iki_, _blogu__snimi_, _danmarks__største_,
+ {{0x623e4026,0xf244e039,0xae2e401c,0x2afb880f}}, // _danmark_, _tým_, _danmarks_, _zbunjen__normalan_,
+ {{0x926c3002,0xd3ead80c,0x00000000,0x00000000}}, // _dijo_, _azet_, --, --,
+ {{0xfb81f81d,0xd2fc781c,0x4a74e006,0x00000000}}, // _असहमत_, _gange_, _zprávy_, --,
+ {{0xf4444010,0x2496600f,0x00000000,0x00000000}}, // _ki_, _pridruže_apr_, --, --,
+ {{0xe4444006,0x00000000,0x00000000,0x00000000}}, // _ji_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x34444007,0x74b96807,0x00000000,0x00000000}}, // _oi_, _ikke__noe_, --, --,
+ {{0x24444010,0x9f072017,0x00000000,0x00000000}}, // _ni_, _os__direitos_, --, --,
+ {{0xb326d806,0x34936014,0x942b1803,0x00000000}}, // _hodin__více_, _es__lo_, _rumah__baru_, --,
+ {{0xced6780c,0xda74e039,0x00000000,0x00000000}}, // _veľmi_, _správy_, --, --,
+ {{0x8a11f02e,0x82f1301c,0xce292818,0xd442001b}}, // _pemberit, _søger_, _кад_, _dki_,
+ {{0xd48ca01c,0xe4135023,0xc31fd81f,0xc187580c}}, // _af__den_, _bez__odobrenj, _crvene__zvezde_, _topovať_,
+ {{0xa5996034,0xae9ed816,0x73eb801c,0x347b7007}}, // _सहित_, _terma__pengguna, _marts_, _seg__på_,
+ {{0x94444007,0x6bd5b006,0x00000000,0x00000000}}, // _ei_, _více__informac, --, --,
+ {{0x1be54025,0x04a32006,0x00000000,0x00000000}}, // [450] _से__कर_, _heureka__cz_, --, --,
+ {{0xb4444028,0x147fb023,0xc3ea8018,0x00000000}}, // _gi_, _historij, _zahtev_, --,
+ {{0x330d0817,0x00000000,0x00000000,0x00000000}}, // _recados_, --, --, --,
+ {{0x9e1bb816,0x00000000,0x00000000,0x00000000}}, // _atau__kerosaka, --, --, --,
+ {{0x3e386818,0xa252c812,0x0f9a0817,0x00000000}}, // _bonitetn_izveštaj, _se__admite_, _dúvidas_, --,
+ {{0xa35c900b,0x00000000,0x00000000,0x00000000}}, // _perkara__yang_, --, --, --,
+ {{0x0bd9d01d,0x62d92825,0x00000000,0x00000000}}, // _गया__था_, _बानी_, --, --,
+ {{0x0d98c823,0xf367e003,0x00000000,0x00000000}}, // _sallalla, _bergabun_dengan_, --, --,
+ {{0x7354300d,0x00000000,0x00000000,0x00000000}}, // _galegas_, --, --, --,
+ {{0xebc29819,0xe9d7681c,0x4f648807,0xca1f8012}}, // _ऐसे_, _vores__tjeneste, _utdannin, _hay__algún_,
+ {{0x0aae500e,0x00000000,0x00000000,0x00000000}}, // _खोज__में_, --, --, --,
+ {{0xeb421804,0xa26d900d,0x72902016,0xba69c010}}, // _doporuču, _noso_, _kekal_, _washobor_kubikora_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x537f801b,0xd386d814,0xebca780a,0xf8f4980c}}, // _jakarta_, _leer_, _जाति__के_, _všetkých_,
+ {{0x5e08e80b,0x00000000,0x00000000,0x00000000}}, // _hendakla, --, --, --,
+ {{0xbb54381f,0x5aeff016,0x00000000,0x00000000}}, // _forumu__privatna_, _yang__berkongs, --, --,
+ {{0xeee9302a,0xe4420010,0xcdc7300c,0xfa9e6025}}, // [460] _diskutan, _ibi_, _kliknite_, _प्राचीन__काल_,
+ {{0x546d5012,0x0ab1500e,0x926c4811,0x4497d010}}, // _email__no_, _लेख__में_, _simo_, _za__bbc_,
+ {{0x12fc9011,0xf2c8881f,0x00000000,0x00000000}}, // _izaga_, _devojke_, --, --,
+ {{0x7baa880e,0xf2d90007,0xced59806,0x8fb4e012}}, // _कि__अब_, _arbeid_, _což_, _respuest_reportar_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x44425806,0xee3ce018,0x00000000,0x00000000}}, // _byl_, _се__да_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xbc48900a,0x00000000,0x00000000,0x00000000}}, // _साथे_, --, --, --,
+ {{0xab56d016,0x00000000,0x00000000,0x00000000}}, // _sentiasa_, --, --, --,
+ {{0xc26cc817,0xcb9e900e,0xe2497803,0x00000000}}, // _tudo_, _के__ही_, _nyaman_, --,
+ {{0x16d5e808,0x00000000,0x00000000,0x00000000}}, // _berbandi, --, --, --,
+ {{0xb2b9881d,0xbb2db018,0x00000000,0x00000000}}, // _यहां_, _delatnos, --, --,
+ {{0xfb1c2002,0x0ab95025,0xb47e501c,0x00000000}}, // _respuest, _रुप__में_, _uden__at_, --,
+ {{0x93e4c806,0xc4426810,0xe2d46014,0x94ad1807}}, // _děti_, _iyo_, _verdad_, _gir__deg_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb29d80b,0xe2b1800c,0x00000000,0x00000000}}, // _pengguna_linkedin_, _pridala_, --, --,
+ {{0xe442201c,0xb224900c,0x00000000,0x00000000}}, // [470] _dkk_, _inak_, --, --,
+ {{0xa2bcc00f,0x34677010,0xf479981c,0x00000000}}, // _na__depo_, _niba__bishobok, _hvad__der_, --,
+ {{0xb48db029,0xc4926007,0x4ec8f00c,0x00000000}}, // _de__uma_, _av__en_, _zariaden, --,
+ {{0x9394d814,0x4224d811,0xf229c01c,0x00000000}}, // _pues_, _bheka_, _række_, --,
+ {{0xea333040,0x0ab3800e,0x06f8381d,0x937b702e}}, // _जैन_, _वर्तमान__में_, _लिया_, _padahal_,
+ {{0xe9c2f01f,0x83eb4811,0x42d8481b,0x447fa003}}, // _vrednost, _kwethu_, _temen_, _satu__ini_,
+ {{0x44426810,0x00000000,0x00000000,0x00000000}}, // _ayo_, --, --, --,
+ {{0xdfc70806,0x8ccf8806,0x00000000,0x00000000}}, // _článek_, _dvě_, --, --,
+ {{0x8443f810,0x4b893814,0xc3946814,0x64426810}}, // _uru_, _diciembr, _dios_, _cyo_,
+ {{0xec10780e,0xa402080c,0xc2925807,0x00000000}}, // _विकास__के_, _dovolenk, _antall_, --,
+ {{0xca3dd825,0x00000000,0x00000000,0x00000000}}, // _सकल_, --, --, --,
+ {{0x23290802,0x6429c80b,0x00000000,0x00000000}}, // _es__este_, _kumpulan_, --, --,
+ {{0xefeb9031,0x13cf5014,0x5a329822,0x3ef0e818}}, // _सामाजिक_, _nuevos_, _ऐसा_, _им_,
+ {{0x52bb181c,0x6dce6818,0x00000000,0x00000000}}, // _seneste_, _више_, --, --,
+ {{0x0b8d5819,0x00000000,0x00000000,0x00000000}}, // _और_, --, --, --,
+ {{0x327f5010,0x11a4d020,0xc4616807,0xf26c7810}}, // _buenos_, _nikolić_, _innlegg__del_, _hino_,
+ {{0xabcdd80e,0xa646c818,0x00000000,0x00000000}}, // [480] _सकी_, _пријави_, --, --,
+ {{0xb28cd811,0x00000000,0x00000000,0x00000000}}, // _omkhulu_, --, --, --,
+ {{0xcc50b81d,0x526cf815,0x00000000,0x00000000}}, // _देखें_, _lugo_, --, --,
+ {{0x59f47806,0x00000000,0x00000000,0x00000000}}, // _není_, --, --, --,
+ {{0x7ed25818,0x1adf6805,0xe3441810,0x48470010}}, // _да__се_, _osobama__mlađim_, _amashaki_yawe_, _ukoreshe_amashaki,
+ {{0x44b5e00f,0xc8386817,0xaa280812,0x7e8d6018}}, // _vrh__re_, _comparti_mensagem_, _lugar__está_, _ће__се_,
+ {{0x3e3ce018,0x8c15c825,0xdb8d500a,0x00000000}}, // _се__на_, _स्क्रिप्, _छी_, --,
+ {{0xff01c818,0x7afd980c,0xd26dc80c,0x0f60d01e}}, // _како__би_, _prípade_, _prvom_, _displeje_,
+ {{0x13eb8007,0xd2e3e80c,0x00000000,0x00000000}}, // _kart_, _ktorých_, --, --,
+ {{0x5a3cf80e,0x14926014,0x00000000,0x00000000}}, // _वंश_, _en__mi_, --, --,
+ {{0x6bcde01d,0x00000000,0x00000000,0x00000000}}, // _सभी_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe3947821,0x00000000,0x00000000,0x00000000}}, // _fins_, --, --, --,
+ {{0x9f3b701b,0x00000000,0x00000000,0x00000000}}, // _mengomen, --, --, --,
+ {{0x124dc01b,0x3fcd9818,0x137eb01d,0x00000000}}, // _di__topic_, _од__стране_, _मुक्त__ज्ञानकोष_, --,
+ {{0x739ed823,0x5317f806,0x00000000,0x00000000}}, // _tjedna__mjesec_, _pouze_, --, --,
+ {{0x6225e82a,0xe8477025,0xe4c15018,0x00000000}}, // [490] _nitko_, _मातृभाषा_, _skok__na_, --,
+ {{0x5442901b,0x00000000,0x00000000,0x00000000}}, // _iya_, --, --, --,
+ {{0x02d18817,0xee7bc818,0x00000000,0x00000000}}, // _como__avalia_, _март__фебруар_, --, --,
+ {{0x7ab3881d,0x82bc200d,0x00000000,0x00000000}}, // _जाते__हैं_, _crear__unha_, --, --,
+ {{0x72903016,0x00000000,0x00000000,0x00000000}}, // _semasa_, --, --, --,
+ {{0xe21c9818,0x00000000,0x00000000,0x00000000}}, // _да__бисте_, --, --, --,
+ {{0x8270782a,0x63007805,0xede62018,0x53ea8007}}, // _prije__sati_, _prije__sata_, _као__што_, _riktig_,
+ {{0xc5802017,0x6a61500d,0x335f2011,0x00000000}}, // _informaç, _algúns_, _kungase_, --,
+ {{0x1225b006,0x00000000,0x00000000,0x00000000}}, // _mobilní__telefony_, --, --, --,
+ {{0x554c2810,0x42cf1807,0xe26d1011,0xee6a0018}}, // _mashyash, _annonse_, _kuzo_, _могу__да_,
+ {{0xd4429010,0x79bbb81f,0x00000000,0x00000000}}, // _aya_, _nameštaj_, --, --,
+ {{0x33868824,0xf3e6980c,0xaa601025,0x00000000}}, // _sakrij_, _túto_, _कुछ__कुछ_, --,
+ {{0xfeb8e818,0x9f53980c,0xf4429010,0x0679e818}}, // _би_, _mať_, _cya_, _правосла,
+ {{0x9479d016,0x62572006,0x00000000,0x00000000}}, // _pada__hb_, _dále_, --, --,
+ {{0xb4439016,0x5acdc819,0x00000000,0x00000000}}, // _bas_, _भाजपा_, --, --,
+ {{0xf2bb6811,0xa394c81c,0x00000000,0x00000000}}, // _kumelwe_, _sidst_, --, --,
+ {{0x6c06601d,0x00000000,0x00000000,0x00000000}}, // [4a0] _किसी__भी_, --, --, --,
+ {{0xa26dd811,0x00000000,0x00000000,0x00000000}}, // _lowo_, --, --, --,
+ {{0x39b93811,0x00000000,0x00000000,0x00000000}}, // _kajehova_, --, --, --,
+ {{0x9bf9f811,0xd26d2011,0x4354d014,0x629d500c}}, // _isithomb, _kuyo_, _tenemos_, _zľavy_,
+ {{0xd4438007,0x2c48681d,0xc46a3817,0x00000000}}, // _tar_, _लिये_, _das__às_, --,
+ {{0x7340180d,0x7e89d016,0x02cae81c,0x12257811}}, // _licenza_, _pertanya_pekerjaa, _fundet_, _kwakho_,
+ {{0x2307a812,0x6b413004,0x00000000,0x00000000}}, // _duplicad_este_, _pravidla_, --, --,
+ {{0x49786802,0xf3f96829,0x00000000,0x00000000}}, // _administ, _alguns_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x4b908006,0xfbbbf025,0x3fc0b806,0xe4429010}}, // _prostřed, _क्षेत्र__से_, _stránek_, _rya_,
+ {{0xe3f82006,0x0f2f281c,0xc2cae81c,0xc2cf281c}}, // _pokud_, _annoncer_, _kender_, _annonce_,
+ {{0x2bcde83e,0x2ab0e025,0x31595006,0x00000000}}, // _हवे_, _पांडेय_, _doplňky_, --,
+ {{0x32b4900c,0x53f90810,0x00000000,0x00000000}}, // _viac_, _imbuga_, --, --,
+ {{0xb4444017,0x3b94d80a,0xa74ed818,0x51b8380a}}, // _br_, _रहल__बा_, _права_, _लागल_,
+ {{0xad7ef818,0x77566804,0x79765817,0x00000000}}, // _су_, _प्रमाणित_, _atendime, --,
+ {{0x5b8c880a,0x64996023,0x00000000,0x00000000}}, // _ओह_, _za__bh_, --, --,
+ {{0xd84bf816,0x85536811,0x00000000,0x00000000}}, // [4b0] _bertulis_, _izikhath, --, --,
+ {{0xeb9cd80a,0x613d5018,0x00000000,0x00000000}}, // _भोजपुरी__के_, _април__март_, --, --,
+ {{0xc45f2012,0xd443901b,0x00000000,0x00000000}}, // _cerrado__la_, _tas_, --, --,
+ {{0x5439c00d,0xe8979016,0x00000000,0x00000000}}, // _traballo_, _kali__terakhir_, --, --,
+ {{0x1144f012,0x00000000,0x00000000,0x00000000}}, // _por__inténtel, --, --, --,
+ {{0xd442680c,0xca59000c,0x00000000,0x00000000}}, // _ako_, _inzercia_, --, --,
+ {{0xe9de3002,0x32fe6807,0xf277e01c,0x79807006}}, // _contenid, _bergen_, _en__række_, _péče_,
+ {{0xf20ec818,0x00000000,0x00000000,0x00000000}}, // _данас_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xed019816,0x227e7807,0x7a6e881d,0x144a980d}}, // _maklumat_, _finne_, _होते__हैं_, _páxina__foi_,
+ {{0x734a881f,0x00000000,0x00000000,0x00000000}}, // _nedelju_, --, --, --,
+ {{0xebe3681b,0xaa79380e,0x00000000,0x00000000}}, // _komentar, _काम__करत_, --, --,
+ {{0xc4444039,0x00000000,0x00000000,0x00000000}}, // _sr_, --, --, --,
+ {{0xebfa6818,0x00000000,0x00000000,0x00000000}}, // _najviše__komentar, --, --, --,
+ {{0x9a69e025,0x649cc812,0xf30dc00b,0x00000000}}, // _कुछ__लोग_, _inténtel_de_, _membina_, --,
+ {{0x0c08e01d,0x9212b006,0xe2e60811,0x00000000}}, // _सकता__है_, _bych_, _amandla_, --,
+ {{0x67f6f80e,0x00000000,0x00000000,0x00000000}}, // [4c0] _के__संदर्भ_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe496c00f,0xf4bd501c,0x03b6700d,0x00000000}}, // _pridruže_okt_, _skriv__et_, _outras__linguas_, --,
+ {{0x2395f80d,0x00000000,0x00000000,0x00000000}}, // _dous_, --, --, --,
+ {{0x8c61282a,0xe06f180f,0x00000000,0x00000000}}, // _tisuća_, _budžetsk_izdvajan, --, --,
+ {{0xa3ced020,0x1f4f8811,0xfeb9f007,0x33cf5014}}, // _čovek_, _ngesikha, _kjøp_, _nuevas_,
+ {{0x8434980d,0xf26d8015,0x00000000,0x00000000}}, // _da__xunta_, _muros_, --, --,
+ {{0x63f47809,0x62ab5802,0x00000000,0x00000000}}, // _postao_, _de__año_, --, --,
+ {{0xc333080d,0x92d25802,0xd3169006,0x00000000}}, // _imaxes_, _hace__años_, _praze_, --,
+ {{0x5303f012,0xd5fda825,0x00000000,0x00000000}}, // _se__mostrará_, _पटकथा_, --, --,
+ {{0x14426810,0xeb69400e,0x438ca029,0x00000000}}, // _uko_, _के__बयान_, _de__janeiro_, --,
+ {{0x34ab300f,0x2a330822,0x54bae01c,0x95d4281b}}, // _maj__vrh_, _टीम_, _godt__om_, _membutuh,
+ {{0x33e75807,0xf2c7581c,0xbc10f80e,0x534b2812}}, // _måte_, _måde_, _जहां__तक_, _hace__meses_,
+ {{0x9a3de836,0x927f7811,0xc26d8017,0xebefd00e}}, // _हवा_, _abanye_, _juros_, _सिनेमा__के_,
+ {{0x7602380e,0x8c2b301d,0x00000000,0x00000000}}, // _में__भारत_, _जा__रही_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [4d0] --, --, --, --,
+ {{0x361ee00d,0xeb93900a,0xd257601c,0x427f5002}}, // _foundati, _बात__के_, _vælg_, _buenas_,
+ {{0xf33b380a,0xf2be580c,0x9b9bc01d,0x00000000}}, // _भोजपुरी_, _predám_, _बताया__कि_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa998d80c,0x339c382e,0x00000000,0x00000000}}, // _tiež_, _pada__tanggal_, --, --,
+ {{0x94426810,0x8290c807,0xf4bd5007,0xda76781d}}, // _abo_, _medan_, _skriv__ut_, _हॉट__एंड_,
+ {{0x7bc33041,0x729dc816,0xcb18201b,0xac48981d}}, // _घरी_, _pelbagai_, _sebagian_, _देने_,
+ {{0x93e3f006,0xbd6a081c,0x722a081c,0x6fd88817}}, // _zadat__rozpětí_, _tilmeldt_, _tilmeld_, _comentár_até_,
+ {{0x0487600e,0x00000000,0x00000000,0x00000000}}, // _अंदाजा_, --, --, --,
+ {{0x638bd026,0x64429010,0x02374823,0xe280b006}}, // _været_, _aka_, _alejhi_, _diskuse_,
+ {{0x52bb6817,0x00000000,0x00000000,0x00000000}}, // _somente_, --, --, --,
+ {{0xc201781a,0x238ca00d,0x930fe00b,0x00000000}}, // _abaixo_, _de__xaneiro_, _melihat__profil_, --,
+ {{0x17e25018,0x00000000,0x00000000,0x00000000}}, // _blogu__reputaci, --, --, --,
+ {{0xb442d807,0x82d86842,0xa711f01d,0x64651015}}, // _mye_, _लागी_, _कर__दिया_, _menú__de_,
+ {{0x14427823,0x142c6025,0xb8475802,0x00000000}}, // _ibn_, _साइबर_, _duplicad, --,
+ {{0x6d8c702a,0x5e297018,0xe47a6028,0x00000000}}, // _točno_, _нас_, _tekst__er_, --,
+ {{0x54439010,0x00000000,0x00000000,0x00000000}}, // [4e0] _css_, --, --, --,
+ {{0x8dfe901f,0xb3007016,0x00000000,0x00000000}}, // _neverova, _senarai_, --, --,
+ {{0x0ce1b017,0x48d1b017,0xf442d811,0x00000000}}, // _estrelas__estrela_, _estrelas__estrelas_, _aye_, --,
+ {{0x236c7011,0x00000000,0x00000000,0x00000000}}, // _kungani_, --, --, --,
+ {{0x527c8817,0xa386d814,0x00000000,0x00000000}}, // _são__paulo_, _enero_, --, --,
+ {{0x24aa2013,0x32fb2011,0x00000000,0x00000000}}, // _der__kan_, _kuphela_, --, --,
+ {{0xa78b600d,0x6a4c080e,0x00000000,0x00000000}}, // _fins__política_, _के__रुप_, --, --,
+ {{0x9db14002,0x00000000,0x00000000,0x00000000}}, // _lenguaje_, --, --, --,
+ {{0x96fe2819,0xf29f7016,0xcbf98011,0x00000000}}, // _क्या_, _kerjaya_, _kwalokho_, --,
+ {{0x9290901b,0x7e74b816,0xe26c781e,0x3a3d301d}}, // _bekasi_, _peluang__pekerjaa, _brno_, _हूँ_,
+ {{0x42e04808,0x437c5011,0x00000000,0x00000000}}, // _abdullah_, _nabanye_, --, --,
+ {{0xac4a4019,0xf3ebe807,0xebc3900a,0x00000000}}, // _नहीं_, _hatt_, _भोज__के_, --,
+ {{0x5443e816,0x97f2700c,0x3757201d,0x52824019}}, // _kat_, _možnosť_, _जा__सकता_, _धर्म__दर्शन_,
+ {{0x84232818,0x652c1802,0x4867581c,0x99675807}}, // _najnovij_vesti_, _términos_, _mulighed, _mulighet,
+ {{0x7443e828,0x7f029807,0x00000000,0x00000000}}, // _mat_, _regjerin, --, --,
+ {{0x9410e020,0x00000000,0x00000000,0x00000000}}, // _još__poruka_, --, --, --,
+ {{0x32485804,0xa442900c,0x629f7016,0x98787011}}, // [4f0] _velmi_, _iba_, _berjaya_, _njengoku,
+ {{0xd2249010,0x48cf8017,0x0442d810,0x00000000}}, // _irak_, _produto__estrelas_, _rye_, --,
+ {{0xebb8780e,0x5312f821,0x8212d017,0xec037825}}, // _पाकिस्ता_के_, _para__obter_, _melhor_, _घटना__के_,
+ {{0x12cbf814,0xf224901c,0xb11b4807,0x00000000}}, // _ayuda_, _krak_, _nasjonal, --,
+ {{0x82cea803,0x422ab006,0x00000000,0x00000000}}, // _yang__bisa_, _někdo_, --, --,
+ {{0x128c0818,0x00000000,0x00000000,0x00000000}}, // _profil__slanje_, --, --, --,
+ {{0xdde0c816,0xd3ea901b,0x0bc1401d,0x4a3d301d}}, // _ahli__mengikut_, _obat_, _सकती__है_, _हूं_,
+ {{0x1a5c6816,0x00000000,0x00000000,0x00000000}}, // _di__malaysia_, --, --, --,
+ {{0x1919b00e,0x3442d811,0x00000000,0x00000000}}, // _सिंहासन_, _uye_, --, --,
+ {{0x24429010,0x00000000,0x00000000,0x00000000}}, // _aba_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa394b01c,0x00000000,0x00000000,0x00000000}}, // _bedste_, --, --, --,
+ {{0x7443f803,0xe494e007,0x00000000,0x00000000}}, // _mau_, _nå__er_, --, --,
+ {{0x536d180f,0x00000000,0x00000000,0x00000000}}, // _opštinam_pregled_, --, --, --,
+ {{0xde7cb017,0x830ab01b,0x0ab65025,0x75ad4025}}, // _mensagen_verdinha, _langsung__bisa_, _जगत__में_, _भी__भारत_,
+ {{0x89594840,0x5406e816,0x00000000,0x00000000}}, // _वैयक्तिक_, _kepakara_urus_, --, --,
+ {{0x73ced802,0x27c6581d,0x1ca19817,0x2a60d018}}, // [500] _nuevo_, _उन्हें_, _em__contato_, _неће__бити_,
+ {{0x126c9816,0x32beb80c,0x00000000,0x00000000}}, // _aktiviti_, _všetko_, --, --,
+ {{0xe201f81a,0x6c51181d,0xd2cf900c,0x00000000}}, // _aqui_, _भेजें_, _kancelár, --,
+ {{0xc9696042,0x3200a006,0x927ed814,0xa3ebe807}}, // _साँझ_, _jejich_, _bueno_, _satt_,
+ {{0x5c26c007,0x00000000,0x00000000,0x00000000}}, // _bli__varslet_, --, --, --,
+ {{0xc7d0a007,0x4ed26018,0x00000000,0x00000000}}, // _tilgjeng, _да__не_, --, --,
+ {{0x952d9818,0x4c49401d,0x00000000,0x00000000}}, // _између_, _हमें_, --, --,
+ {{0x2f326018,0x00000000,0x00000000,0x00000000}}, // _делити__под_, --, --, --,
+ {{0xb3ebe807,0x00000000,0x00000000,0x00000000}}, // _tatt_, --, --, --,
+ {{0x7ed2e018,0xb926480c,0x00000000,0x00000000}}, // _би__се_, _odpoveď_, --, --,
+ {{0xc491e002,0x73300012,0x9295e81c,0x00000000}}, // _el__la_, _contenid_erróneo_, _at__komme_, --,
+ {{0x47692025,0x52493806,0x5b8fe00e,0xd144180e}}, // _के__निर्माण_, _přihláše, _तप_, _जासूसी_,
+ {{0x3da7880c,0xc2e2701f,0xdc17b017,0x444ad021}}, // _keď_, _od__prva_, _tópico_, _artigo__da_,
+ {{0x6a9b301d,0x00000000,0x00000000,0x00000000}}, // _जा__रहा_, --, --, --,
+ {{0x635a100d,0x946d2817,0x8493601c,0x00000000}}, // _pódense__aplicar_, _não__há_, _er__nu_, --,
+ {{0x758a080c,0x39b51018,0xa2d4700d,0x64429011}}, // _registrá, _uveče__uveče_, _axudas_, _uba_,
+ {{0x84b36023,0x79cd2810,0x0a396025,0x00000000}}, // [510] _kur__an_, _bishobok_shakisha_, _सांप_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x3ed5801c,0x4a5b301d,0x00000000,0x00000000}}, // _udviklin, _का__नाम_, --, --,
+ {{0x73a87812,0x00000000,0x00000000,0x00000000}}, // _si__quieres_, --, --, --,
+ {{0x8682f835,0x2b88f00a,0xcc8a8818,0x00000000}}, // _kategori, _पऽ_, _уреди__везе_, --,
+ {{0x7a3e3825,0x00000000,0x00000000,0x00000000}}, // _नकद_, --, --, --,
+ {{0x6b5a6818,0x8e2d1818,0x526d1014,0x00000000}}, // _прокомен, _број_, _hizo_, --,
+ {{0x926cf815,0x00000000,0x00000000,0x00000000}}, // _vigo_, --, --, --,
+ {{0x12fa8821,0x0a79b00e,0xc2bb4810,0x00000000}}, // _con__esta_, _नेपाल__में_, _css__niba_, --,
+ {{0xee2ec816,0x72b46806,0x1029e01d,0x7e188018}}, // _tanpa__kebenara, _líbí_, _एंड__स्पाइसी_, _posetila,
+ {{0x02ecf817,0x29c5f818,0x00000000,0x00000000}}, // _este__produto_, _нема__коментар, --, --,
+ {{0xd6dcf80a,0x00000000,0x00000000,0x00000000}}, // _बाकिर_, --, --, --,
+ {{0xc2dc5817,0x00000000,0x00000000,0x00000000}}, // _produto__como_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x527f5002,0x5a5e280d,0x00000000,0x00000000}}, // _tienes_, _traído_, --, --,
+ {{0xe7d0c82e,0x32fdf81c,0x4225f807,0xb2295020}}, // [520] _lingkung, _brugt_, _brukt_, _dve__godine_,
+ {{0x8442d811,0x7245c01e,0x23e7200c,0xeef27011}}, // _ake_, _tím_, _táto_, _kunkulun,
+ {{0x32562018,0xf35bf014,0x00000000,0x00000000}}, // _становни, _ninguna_, --, --,
+ {{0x93660035,0x00000000,0x00000000,0x00000000}}, // _offentli_profil_, --, --, --,
+ {{0x5e643812,0xb48d6817,0x00000000,0x00000000}}, // _públicam, _de__sem_, --, --,
+ {{0x3cc7601d,0x00000000,0x00000000,0x00000000}}, // _संपादित__करें_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb26c2006,0x7f1ff00c,0x527f1016,0x2408981b}}, // _jako_, _oblečeni, _mudah__alih_, _bisa__langsung_,
+ {{0xbac5a009,0xb85ca812,0x61332806,0xcda78806}}, // _iz__sarajeva_, _está__duplicad, _kvalitní_, _teď_,
+ {{0x2a8bb007,0x34d5681d,0x00000000,0x00000000}}, // _trenings, _वीडियो_, --, --,
+ {{0x73e7a029,0x826fa811,0x00000000,0x00000000}}, // _no__brasil_, _amaningi_, --, --,
+ {{0x51dfc025,0x00000000,0x00000000,0x00000000}}, // _आयकर_, --, --, --,
+ {{0xbb03b802,0x00000000,0x00000000,0x00000000}}, // _herramie, --, --, --,
+ {{0x56d2303f,0x00000000,0x00000000,0x00000000}}, // _दैनिक_, --, --, --,
+ {{0x0fbad006,0xba6c0806,0x00000000,0x00000000}}, // _zařízení_, _přímo_, --, --,
+ {{0x5200781b,0xbbfb9814,0x958e981b,0x22d88007}}, // _menit_, _septiemb, _quote__original_, _sakene_,
+ {{0x93cca008,0xfc31480c,0x00000000,0x00000000}}, // [530] _ini__tanpa_, _detské_, --, --,
+ {{0xf292600c,0x00000000,0x00000000,0x00000000}}, // _strane_, --, --, --,
+ {{0x69c37811,0xe387e811,0x00000000,0x00000000}}, // _ngempela_, _petru_, --, --,
+ {{0x33f46024,0x346ae00c,0x48027810,0x4e778018}}, // _tvrtke_, _ako__sa_, _shakisha__amashaki, _један__од_,
+ {{0x826c180e,0x43232821,0x73946810,0x1a213007}}, // _रक्षा_, _século_, _byose_, _les__også_,
+ {{0x49f65817,0xd47f680d,0x4c5c201c,0x00000000}}, // _estão_, _polo__que_, _sætter_, --,
+ {{0x7c39a006,0x523b6807,0xa2fdd816,0x00000000}}, // _सम्पादन_, _tillegg_, _orang__ramai_, --,
+ {{0xd8bb6816,0xfebf0007,0xc442d807,0x00000000}}, // _kebenara_bertulis_, _spesielt_, _uke_, --,
+ {{0xaa336843,0xd27e0010,0x52fd7817,0xd250c818}}, // _करत_, _print_, _imagem_, _један_,
+ {{0xc443f808,0xcc52e00e,0x00000000,0x00000000}}, // _isu_, _अवशेष_, --, --,
+ {{0xa2ebc00d,0xbbc8e81d,0x00000000,0x00000000}}, // _na__súa_, _चंडीगढ़_, --, --,
+ {{0x2bebe82a,0xd3a8d01b,0x00000000,0x00000000}}, // _komentir, _semarang_, --, --,
+ {{0xf67f681b,0x00000000,0x00000000,0x00000000}}, // _peneliti, --, --, --,
+ {{0x1c53f817,0x38dc3818,0x00000000,0x00000000}}, // _contato_, _људи_, --, --,
+ {{0x8bdd381d,0x00000000,0x00000000,0x00000000}}, // _आज__का_, --, --, --,
+ {{0x9b97780e,0x9fe13018,0x00000000,0x00000000}}, // _के__मन_, _своје_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [540] --, --, --, --,
+ {{0x4442d811,0x56152806,0x00000000,0x00000000}}, // _abe_, _příspěve, --, --,
+ {{0xc9f8f02a,0x43728804,0x42242007,0x62f2000c}}, // _milijuna_, _katalog_, _takk_, _nasleduj,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb4444010,0xde0ea817,0x2686880e,0xf2015810}}, // _ka_, _verdinha, _खुदाई_, _mugihe_,
+ {{0xf3f47023,0x740b5808,0x00000000,0x00000000}}, // _postove__stare_, _kuala__lumpur_, --, --,
+ {{0x42010020,0xd96bb806,0xd394d814,0x00000000}}, // _srbije_, _uživatel, _eres_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xebe3200e,0xf3457010,0x00000000,0x00000000}}, // _तक__के_, _share__tweet_, --, --,
+ {{0x154d880c,0x8d492818,0x00000000,0x00000000}}, // _prevádzk, _три_, --, --,
+ {{0x93ced814,0x00000000,0x00000000,0x00000000}}, // _nueva_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x24444010,0xa6f91019,0xc4ae6020,0x00000000}}, // _ba_, _दिया_, _kod__je_, --,
+ {{0x34444010,0xb492201b,0x2332900d,0x00000000}}, // _ca_, _me__dan_, _imaxe_, --,
+ {{0x22ab101b,0xc2d84817,0x00000000,0x00000000}}, // _bandung_, _homem_, --, --,
+ {{0x82131810,0x242a281b,0xaf33602e,0x00000000}}, // _michel_, _rumah__dijual_, _konsulta, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [550] --, --, --, --,
+ {{0xbcb54023,0x00000000,0x00000000,0x00000000}}, // _prikaži__postove_, --, --, --,
+ {{0xe442100c,0x7264001e,0x4ef8081a,0x13034011}}, // _ich_, _poslat_, _os__produtos_, _kubantu_,
+ {{0xc46e600c,0x9be3380e,0xc2370017,0x00000000}}, // _nie__je_, _तक__कि_, _opcional__digite_, --,
+ {{0x94444010,0x282ba00d,0xf27ec81c,0x48438815}}, // _ya_, _recoñece_comparti, _endnu_, _contidos_,
+ {{0x8444400d,0xc2011020,0xe4800012,0xb27ed814}}, // _xa_, _srbiju_, _proporci_tu_, _buena_,
+ {{0xfbeb280e,0x13f31817,0x00000000,0x00000000}}, // _तब__से_, _lista__negra_, --, --,
+ {{0x12f9b811,0x226fc010,0x8442d811,0x00000000}}, // _ujehova_, _imbuga__zayo_, _ube_, --,
+ {{0xc4439815,0xa473400f,0x368c8022,0x00000000}}, // _ás_, _izdvajan_za_, _पंजाब_, --,
+ {{0xcbb4700c,0x00000000,0x00000000,0x00000000}}, // _reagovať_, --, --, --,
+ {{0x33050810,0x00000000,0x00000000,0x00000000}}, // _izindi__mbuga_, --, --, --,
+ {{0x3444400c,0x00000000,0x00000000,0x00000000}}, // _sa_, --, --, --,
+ {{0xd26c5803,0x4e28e818,0x2fdb1811,0x00000000}}, // _kalo_, _па_, _umphosto, --,
+ {{0x6eba1818,0x00000000,0x00000000,0x00000000}}, // _после_, --, --, --,
+ {{0xf26c580c,0x64444014,0x346d581f,0x00000000}}, // _malo_, _va_, _novom__sadu_, --,
+ {{0x74444010,0x4290a01c,0x22303017,0xc26dd811}}, // _wa_, _debat_, _seu__email_, _kuwo_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [560] --, --, --, --,
+ {{0x8309301b,0x02018813,0xf2d85806,0x22e2081c}}, // _kamu__bisa_, _årig_, _kolem_, _igennem_,
+ {{0x84684816,0x00000000,0x00000000,0x00000000}}, // _kehilang_atau_, --, --, --,
+ {{0x0a49d00e,0x00000000,0x00000000,0x00000000}}, // _दिल__में_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x44718010,0x23f86806,0x00000000,0x00000000}}, // _buboneka__bbc_, _svou_, --, --,
+ {{0x9bef380a,0xec78280d,0x725ad807,0x00000000}}, // _बा__कि_, _persoal_, _kveld_, --,
+ {{0x0a6a280e,0x12c3c81f,0x6290101b,0x6baa281d}}, // _क्षेत्र__में_, _milijard_evra_, _sehat_, _यह__भी_,
+ {{0xb2a6a007,0x00000000,0x00000000,0x00000000}}, // _jobb_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x64444023,0xb2dd200b,0x52d80007,0x00000000}}, // _bh_, _al__quran_, _seier_, --,
+ {{0xf3ae9812,0x00000000,0x00000000,0x00000000}}, // _pero__delante_, --, --, --,
+ {{0x33eb581c,0x12d99825,0x00000000,0x00000000}}, // _rigtig_, _पाती_, --, --,
+ {{0xf200c810,0x16f9602c,0x374df80f,0x5f3fa01b}}, // _polisi_, _साझा_, _bih__istraživ, _tangeran,
+ {{0x1b65a005,0x431df812,0xaef3c00e,0x00000000}}, // [570] _coolinar, _no__puedes_, _इलाका_, --,
+ {{0x14703018,0xc395400c,0x00000000,0x00000000}}, // _veoma__lak_, _miesto_, --, --,
+ {{0x3a453018,0x00000000,0x00000000,0x00000000}}, // _izveštaj_, --, --, --,
+ {{0x0866e809,0xc44cc806,0x7254c806,0xc28c6017}}, // _bosni__hercegov, _mě_, _měl_, _mas__não_,
+ {{0x34ab300f,0xb225400c,0xb4ad601c,0x00000000}}, // _apr__vrh_, _niekto_, _ind__at_, --,
+ {{0x0a6ef025,0x00000000,0x00000000,0x00000000}}, // _युद्ध__में_, --, --, --,
+ {{0x7a75081d,0xe47b5812,0x848cf810,0x5eefc80e}}, // _रहे__हैं_, _mapa__es_, _kugira__ngo_, _आतंक_,
+ {{0x6b8e5022,0x8e32781b,0xee3d8806,0x839b080a}}, // _इन_, _kualitas_, _přejít_, _से__संबंध_,
+ {{0x228b280c,0x00000000,0x00000000,0x00000000}}, // _poslať_, --, --, --,
+ {{0xfe731818,0x00000000,0x00000000,0x00000000}}, // _део_, --, --, --,
+ {{0x0a648809,0x00115039,0x00000000,0x00000000}}, // _ov__stranica_, _produkto, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x02b5800c,0x00000000,0x00000000,0x00000000}}, // _marca_, --, --, --,
+ {{0xa1e6081d,0xee325818,0xeb94900e,0x9316900d}}, // _गैलरी_, _може__да_, _रात__के_, _praza_,
+ {{0x3a11e029,0x00000000,0x00000000,0x00000000}}, // _garantia_, --, --, --,
+ {{0x827f4011,0xcc31b81d,0x00000000,0x00000000}}, // _ngenxa_, _ने__बताया_, --, --,
+ {{0x237ea009,0x00000000,0x00000000,0x00000000}}, // [580] _bir__izaberi_, --, --, --,
+ {{0x6b97a825,0x00000000,0x00000000,0x00000000}}, // _के__भी_, --, --, --,
+ {{0xd20e2018,0xeba6c81d,0x026c7814,0x00000000}}, // _дана_, _है__इस_, _mano_, --,
+ {{0x544dd01f,0x00000000,0x00000000,0x00000000}}, // _za__decu_, --, --, --,
+ {{0xc4936014,0x6bd77010,0x00000000,0x00000000}}, // _es__un_, _niba__washobor, --, --,
+ {{0xd379980c,0xa32f3016,0x00000000,0x00000000}}, // _pridať_, _dikenali__sebagai_, --, --,
+ {{0x32e97816,0x4e297818,0x00000000,0x00000000}}, // _sesiapa_, _тај_, --, --,
+ {{0x7ec05818,0x00000000,0x00000000,0x00000000}}, // _су__се_, --, --, --,
+ {{0x027f4014,0x310ff807,0x00000000,0x00000000}}, // _cuenta_, _denne__artikkel, --, --,
+ {{0xde72e82e,0x9bd9b01d,0x426de81e,0xe27f3002}}, // _एकत्र_, _कहा__कि_, _tuto_, _el__primero_,
+ {{0x2d5a2818,0xa3959018,0xb3ea901b,0x00000000}}, // _може_, _komentar__beograd_, _dokter_, --,
+ {{0x52024817,0xcbddd81d,0x4670181d,0x00000000}}, // _ótimo_, _बताया_, _दर्शन__ज्योतिष_, --,
+ {{0x12d85808,0xbcf4300c,0x8af52006,0x44424816}}, // _boleh_, _septembr, _napříkla, _mcm_,
+ {{0x0bc6e01d,0xd290201b,0xc2d8c011,0x00000000}}, // _करता__है_, _lokal_, _ndlela_, --,
+ {{0x2474480f,0x9d59200e,0x00000000,0x00000000}}, // _dana__maj_, _राजभाषा_, --, --,
+ {{0x23ead80c,0x00000000,0x00000000,0x00000000}}, // _svete_, --, --, --,
+ {{0xeb8e681d,0xdc8d1023,0xb26d8010,0xec03600b}}, // [590] _इस_, _svi__postovi_, _biro_, _kampung_,
+ {{0x63e79006,0xcaf6f818,0x00000000,0x00000000}}, // _této_, _mop__paročist, --, --,
+ {{0x4a9fc01d,0x00000000,0x00000000,0x00000000}}, // _सिनेमा__हॉट_, --, --, --,
+ {{0x12cbf80d,0x00000000,0x00000000,0x00000000}}, // _axuda_, --, --, --,
+ {{0x84445023,0x93eaa01b,0x00000000,0x00000000}}, // _alejhi__ve_, _gambar__buat_, --, --,
+ {{0x8d42d018,0x62903002,0xfa3c3806,0xe3eab81c}}, // _информац, _dejar_, _září_, _indtil_,
+ {{0xc2498016,0xb32c9024,0xb47e5012,0xca4b681d}}, // _terma_, _se__putem_, _autoprom_el_, _की__बात_,
+ {{0x9c18700d,0xd438a811,0x00000000,0x00000000}}, // _despois_, _imibhalo_, --, --,
+ {{0x02d84816,0xc27ed807,0x1c773007,0x12843002}}, // _komen_, _uten_, _kanskje_, _archivo_,
+ {{0x83a92816,0x00000000,0x00000000,0x00000000}}, // _sebarang_, --, --, --,
+ {{0x99f4b017,0x526cc829,0x00000000,0x00000000}}, // _você_, _dados_, --, --,
+ {{0x0ed59806,0xc3c1d80f,0x1293481b,0x2f2b2806}}, // _již_, _po__kantonim, _berapa__saja_, _více__než_,
+ {{0xfb8e781d,0x00000000,0x00000000,0x00000000}}, // _उस_, --, --, --,
+ {{0x62fc781b,0x137b0029,0x00000000,0x00000000}}, // _uang_, _citando_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x327ed807,0x00000000,0x00000000,0x00000000}}, // _noen_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [5a0] --, --, --, --,
+ {{0xa26c5807,0x398b401f,0x92d0e817,0x74746027}}, // _oslo_, _uveče_, _um__comentár, _som__si_,
+ {{0xfc06a81d,0x54d4f016,0x8767700e,0xfafa5808}}, // _सहमत__असहमत_, _anda__bersetuj, _में__अपना_, _pengurus,
+ {{0xb27ed814,0xaf53980c,0x00000000,0x00000000}}, // _tiene_, _byť_, --, --,
+ {{0x0fea402a,0x00000000,0x00000000,0x00000000}}, // _dubrovač, --, --, --,
+ {{0x0b302018,0x00000000,0x00000000,0x00000000}}, // _него_, --, --, --,
+ {{0xb9d52830,0xa47a1009,0x92d79807,0xf435c01f}}, // _सहायता_, _reprezen_bih_, _gjelder_, _veličinu__slova_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x9a235017,0x62258011,0x92cae81c,0x00000000}}, // _que__você_, _marku_, _hendes_, --,
+ {{0x0c681811,0x00000000,0x00000000,0x00000000}}, // _yokuthi_, --, --, --,
+ {{0x12e1300c,0xebf5b025,0x00000000,0x00000000}}, // _nahlásiť__chybu_, _प्रणाली__के_, --, --,
+ {{0x926ca011,0xc2dd0016,0x00000000,0x00000000}}, // _labo_, _hb__julai_, --, --,
+ {{0x14426810,0x14bb280f,0x00000000,0x00000000}}, // _ico_, _siteu__bez_, --, --,
+ {{0xb26ca011,0xee351014,0x00000000,0x00000000}}, // _nabo_, _millones_, --, --,
+ {{0x04444004,0xeb09400d,0xee67e816,0x00000000}}, // _cz_, _composte, _tawaran__rundinga, --,
+ {{0x84b4d01b,0x00000000,0x00000000,0x00000000}}, // _topic__di_, --, --, --,
+ {{0x42367811,0xf26ca011,0x00000000,0x00000000}}, // [5b0] _manje_, _babo_, --, --,
+ {{0x52d8c820,0x00000000,0x00000000,0x00000000}}, // _ovde_, --, --, --,
+ {{0xb2ad7814,0x00000000,0x00000000,0x00000000}}, // _que__tiene_, --, --, --,
+ {{0x8343280e,0x3a33d044,0x746d5012,0xc5f73817}}, // _संबंध_, _जना_, _email__si_, _mais__informaç,
+ {{0x03f9a016,0xd1c9e812,0x00000000,0x00000000}}, // _jepun_, _sobre__mercadol, --, --,
+ {{0x7466e011,0x00000000,0x00000000,0x00000000}}, // _ngaphamb_kokuba_, --, --, --,
+ {{0xba3cc82e,0xb32d0016,0x92902011,0x42903017}}, // _शोध_, _komen__kongsi_, _yeka_, _lojas_,
+ {{0x726ca011,0x3ee31818,0x00000000,0x00000000}}, // _zabo_, _они_, --, --,
+ {{0x26ff3045,0x626ca011,0xf236d00c,0x00000000}}, // _अथवा_, _yabo_, _svojho_, --,
+ {{0x3e0d1818,0x5e9d1818,0x048e081c,0x6468e00c}}, // _ово_, _око_, _af__det_, _ale__aj_,
+ {{0xf2bb881f,0x00000000,0x00000000,0x00000000}}, // _nedelje_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa0921815,0x0c6a1018,0x03788008,0x00000000}}, // _de__ferramen, _представ, _kenalan_, --,
+ {{0x43738809,0x2291902e,0xc297881f,0xf451e018}}, // _redanje_, _pesan_, _na__veoma_, _kuću__din_,
+ {{0x22367811,0xd4ace014,0x00000000,0x00000000}}, // _kanje_, _con__su_, --, --,
+ {{0x026ca011,0xf9e60011,0x00000000,0x00000000}}, // _sabo_, _kungenze, --, --,
+ {{0x1aabe806,0xd681601d,0xfee32018,0x00000000}}, // [5c0] _základní_, _खिलाफ_, _она_, --,
+ {{0x83ee2007,0x031d7808,0x2e85a812,0xa92f380e}}, // _det__blir_, _percuma_, _si__hablaras_, _के__इलाज_,
+ {{0x2e737046,0x42903002,0x336b781c,0x00000000}}, // _मार्च_, _deja_, _til__salg_, --,
+ {{0x7eb8f818,0x00000000,0x00000000,0x00000000}}, // _када__се_, --, --, --,
+ {{0x4f185818,0xa373e016,0x00000000,0x00000000}}, // _да__ли_, _maklumat__yang_, --, --,
+ {{0xa290b01b,0xc4635007,0x9414e806,0x00000000}}, // _terkait_, _innlegg__av_, _pro__děti_, --,
+ {{0xe2318802,0x1290c816,0x0bd4180d,0xc394a006}}, // _primero_, _kedai_, _páxina__elemento_, _nejsou_,
+ {{0xa2f1001f,0xe4b46002,0x687e180c,0x00000000}}, // _životne__sredine_, _por__tu_, _prehliad, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x92a6d80c,0x00000000,0x00000000,0x00000000}}, // _treba_, --, --, --,
+ {{0xbc37b01e,0x7e711022,0xd290c00c,0x54dfa81d}}, // _české_, _अगस्त_, _sklade_, _व्यंजन__नायिका_,
+ {{0xa200d02a,0x00000000,0x00000000,0x00000000}}, // _splitu_, --, --, --,
+ {{0x12ffd807,0x00000000,0x00000000,0x00000000}}, // _denne__siden_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6c49281d,0xebbbd80e,0xbd7ef818,0x00000000}}, // _मेरे_, _आदमी__के_, _ту_, --,
+ {{0x32903017,0xc599401d,0x00000000,0x00000000}}, // _seja_, _सेहत_, --, --,
+ {{0xc0a26009,0xa3efc01c,0x00000000,0x00000000}}, // [5d0] _vrh__prethodn, _spørgsmå_svar_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x62903017,0xa2cbf817,0x00000000,0x00000000}}, // _veja_, _ajuda_, --, --,
+ {{0xc7666025,0x00000000,0x00000000,0x00000000}}, // _में__गंगा_, --, --, --,
+ {{0x0af52007,0xd2e23818,0x00000000,0x00000000}}, // _internas, _večernje__novosti_, --, --,
+ {{0x127b480c,0xa37fc810,0x00000000,0x00000000}}, // _komentár, _somalia_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe9d73024,0xc3e75807,0x00000000,0x00000000}}, // _listopad, _gått_, --, --,
+ {{0x949fa017,0x00000000,0x00000000,0x00000000}}, // _cartão__ou_, --, --, --,
+ {{0x5b1fd81b,0x32a5800d,0x00000000,0x00000000}}, // _silahkan_, _de__xullo_, --, --,
+ {{0x4d0ad018,0x7394900b,0x00000000,0x00000000}}, // _učlanjen_, _asas_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x927b4817,0x33a12014,0xe470b810,0x00000000}}, // _comentár, _méxico_, _adakoran_na_, --,
+ {{0xf2005811,0x92c9d817,0x00000000,0x00000000}}, // _leli_, _nenhum__comentár, --, --,
+ {{0x3256980c,0x00000000,0x00000000,0x00000000}}, // [5e0] _júl_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x5c00a007,0x00000000,0x00000000,0x00000000}}, // _varslet_, --, --, --,
+ {{0x72d8781b,0x1280b806,0x00000000,0x00000000}}, // _bener_, _diskuze_, --, --,
+ {{0x932bb010,0x00000000,0x00000000,0x00000000}}, // _mu__gihe_, --, --, --,
+ {{0x93f85820,0xa3010016,0x00000000,0x00000000}}, // _celu_, _telah__berada_, --, --,
+ {{0x15409818,0x64040825,0xa3f8581f,0x2f376018}}, // _original__postavio_, _अकादमी_, _delu_, _од__до_,
+ {{0xba9bb00e,0x92d87814,0xd44a1003,0xa46dd810}}, // _में__काम_, _tener_, _karena__itu_, _mbuga__za_,
+ {{0xf473a835,0x4c807818,0x29fae818,0x00000000}}, // _som__var_, _неће_, _промени__коментар, --,
+ {{0x83f82818,0x7ba8181d,0x02d8c817,0x00000000}}, // _ostale__vesti_, _है__तो_, _podem_, --,
+ {{0xa2d46011,0x00000000,0x00000000,0x00000000}}, // _ukudla_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x14ab2813,0x03f46016,0x52d9901f,0xf3ff7810}}, // _der__har_, _pautan_, _mesec_, _ubuzima_,
+ {{0xd4f4b017,0x00000000,0x00000000,0x00000000}}, // _acompanh, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd2905816,0xec612815,0x20946806,0x8291d80b}}, // _solat_, _coruña_, _nabízí_, _dewan_,
+ {{0x2f6fc017,0x8641e016,0x00000000,0x00000000}}, // [5f0] _palavras_, _perkahwi, --, --,
+ {{0x4d493018,0x02eca011,0x00000000,0x00000000}}, // _пре_, _korinte_, --, --,
+ {{0x4443e81c,0x13ebe807,0x00000000,0x00000000}}, // _nyt_, _nytt_, --, --,
+ {{0xb46c6017,0xf1ba6842,0xccc77817,0x00000000}}, // _com__br_, _पावल_, _ruim__título_, --,
+ {{0x12905817,0xb31d7814,0x63ea9008,0x525ad807}}, // _pela_, _se__puede_, _doktor_, _vanlig_,
+ {{0xb6d7e018,0xb667e018,0x97e7e018,0x2d91e802}}, // _србије_, _србији_, _србија_, _cantidad_,
+ {{0x144e980c,0x00000000,0x00000000,0x00000000}}, // _sú_, --, --, --,
+ {{0xf33af80d,0x34429009,0xf2006028,0x7ef00811}}, // _de__galicia_, _nja_, _mulig_, _ngokuqin,
+ {{0xc224c80c,0x00000000,0x00000000,0x00000000}}, // _celkom_, --, --, --,
+ {{0x6442901b,0xa2d58806,0x00000000,0x00000000}}, // _aja_, _všech_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x8f06b80c,0x244e9814,0x00000000,0x00000000}}, // _súvisiac, _tú_, --, --,
+ {{0x1386c802,0x00000000,0x00000000,0x00000000}}, // _madre_, --, --, --,
+ {{0x53f83008,0x00000000,0x00000000,0x00000000}}, // _semula_, --, --, --,
+ {{0x9be90033,0x8225f811,0x00000000,0x00000000}}, // _अमेरिकी_, _usuku_, --, --,
+ {{0x327e6811,0x9290580d,0xb2005816,0x00000000}}, // _khona_, _polas_, _polis_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [600] --, --, --, --,
+ {{0xd1812818,0x00000000,0x00000000,0x00000000}}, // _страница_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xe278001b,0x6fd9780d,0x00000000,0x00000000}}, // _bisnis_, _estás__comentar_, --, --,
+ {{0x02cee81b,0x00000000,0x00000000,0x00000000}}, // _com__gambar_, --, --, --,
+ {{0xf313e803,0xa27ff81c,0x8a3da00e,0x00000000}}, // _mencoba_, _grund_, _नतीजा_, --,
+ {{0x9eb30017,0x00000000,0x00000000,0x00000000}}, // _direitos_, --, --, --,
+ {{0x4b996825,0x92905814,0x00000000,0x00000000}}, // _पता__चल_, _ella_, --, --,
+ {{0x52d87810,0x0b62581d,0x2e72600e,0x00000000}}, // _bene_, _बनाने_, _इंद्र_, --,
+ {{0xaa3d2843,0x62d8781f,0x72007818,0x42783010}}, // _होत_, _cene_, _ceni_, _mu__mwaka_,
+ {{0xba02f837,0x4ba0780d,0x7d7ef818,0x8f675010}}, // _सहायता__सहायता_, _licenza__recoñece, _му_, _yawe__adakoran,
+ {{0xd394f816,0xeef12818,0x00000000,0x00000000}}, // _rahsia_, _име_, --, --,
+ {{0xebf0f00e,0x1bcc481d,0x29456812,0x00000000}}, // _इतिहास__के_, _आने_, _alquiler_, --,
+ {{0x835e601b,0x346b600c,0x00000000,0x00000000}}, // _pengikut__lainnya_, _aby__sa_, --, --,
+ {{0xc200a01b,0x5d7f2818,0x2224c806,0x5c681811}}, // _mobil_, _јул_, _celkem_, _nokuthi_,
+ {{0x13f87811,0x73f88011,0x00000000,0x00000000}}, // _zenu_, _kokuba_, --, --,
+ {{0xb2907811,0x3bcc681d,0x00000000,0x00000000}}, // [610] _yena_, _इसी_, --, --,
+ {{0x648e601c,0xe24a9017,0x021c8818,0x00000000}}, // _af__de_, _clique__aqui_, _објављен, --,
+ {{0x8756f80e,0x00000000,0x00000000,0x00000000}}, // _से__अपना_, --, --, --,
+ {{0x44ae801c,0x64997021,0xf40c580c,0x00000000}}, // _seneste__nyt_, _condició_de_, _aktuálne_, --,
+ {{0x8c48980e,0x94bee810,0xf27e0010,0x00000000}}, // _देखे_, _mashyash_ya_, _ukine_, --,
+ {{0xe4abe013,0xe5af701e,0x00000000,0x00000000}}, // _der__er_, _obchodní, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x73eb980d,0x00000000,0x00000000,0x00000000}}, // _editar__fonte_, --, --, --,
+ {{0xb443f810,0x00000000,0x00000000,0x00000000}}, // _uyu_, --, --, --,
+ {{0x71cda806,0x2394e811,0xfa4da806,0x32d9c011}}, // _všechny_, _ngisho_, _všechna_, _ezinhle_,
+ {{0x7e4b0811,0x0a5ac80e,0x00000000,0x00000000}}, // _kufanele_, _कि__देश_, --, --,
+ {{0xd3e18017,0x92907811,0x00000000,0x00000000}}, // _opinião__sobre_, _wena_, --, --,
+ {{0xe2ca8003,0x0ae1d807,0x00000000,0x00000000}}, // _apalagi_, _nettside, --, --,
+ {{0xd23b5020,0x726d1011,0x00000000,0x00000000}}, // _još__uvek_, _nazo_, --, --,
+ {{0x6421980f,0x6463d817,0xe3f9a810,0x00000000}}, // _about__postao_, _imagens__de_, _yavuze_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd9e2f010,0xcb88f822,0x00000000,0x00000000}}, // [620] _ruboneka_, _पल_, --, --,
+ {{0xa200c806,0x00000000,0x00000000,0x00000000}}, // _hodin_, --, --, --,
+ {{0xe3eba818,0x00000000,0x00000000,0x00000000}}, // _да__буде_, --, --, --,
+ {{0x7bee2018,0x8b8fe819,0xa4268018,0x5492b010}}, // _само_, _दो_, _pre__minuta_, _na__css_,
+ {{0xb48f6029,0x0ead5009,0x526d2014,0x00000000}}, // _de__um_, _navođenj, _mayo_, --,
+ {{0x344d8006,0xf3696817,0x00000000,0x00000000}}, // _kč_, _imagens_, --, --,
+ {{0x926ba010,0x2249901b,0x00000000,0x00000000}}, // _ibiri__kuri_, _resmi_, --, --,
+ {{0x82fc2007,0xd3bdb00c,0x00000000,0x00000000}}, // _innhold_, _chcete__pridať_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x0eae1016,0xc273d00f,0x00000000,0x00000000}}, // _syarikat_, _slavo__kukić_, --, --,
+ {{0xaa33900a,0x00000000,0x00000000,0x00000000}}, // _अउर_, --, --, --,
+ {{0xabcc6822,0x020e2818,0xee70a818,0x6a96580e}}, // _इसे_, _сада_, _треба__да_, _से__बढ़_,
+ {{0xb42a802a,0x80533006,0x00000000,0x00000000}}, // _sve__poruke_, _sportovn, --, --,
+ {{0xe3e6e006,0xff25782a,0xbf535816,0x00000000}}, // _být_, _profil__pošalji_, _baru__pertanya, --,
+ {{0x55b66810,0x00000000,0x00000000,0x00000000}}, // _amashaki_mashyash, --, --, --,
+ {{0x046de807,0x00000000,0x00000000,0x00000000}}, // _noe__som_, --, --, --,
+ {{0xe443e826,0x7a3dc032,0x96c74007,0xd6f9f825}}, // [630] _okt_, _संग_, _samarbei, _बिया_,
+ {{0x226d2010,0x00000000,0x00000000,0x00000000}}, // _zayo_, --, --, --,
+ {{0x03b5a805,0x12d8c80d,0x626c700c,0x00000000}}, // _lokacija__zagreb_, _poden_, _áno_, --,
+ {{0x0a3e5847,0x8a46901b,0xb3ea000d,0xe8e61018}}, // _बंद_, _buat__pemberit, _xeito_, _библиогр_подаци_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6a951010,0xce538018,0x00000000,0x00000000}}, // _aho__ubufasha_, _везе__ова_, --, --,
+ {{0xebdfa00a,0x00000000,0x00000000,0x00000000}}, // _पी__के_, --, --, --,
+ {{0xc2ca780d,0xbbcc781d,0x6e7a580d,0x6b98a81d}}, // _dende_, _उसे_, _termos__adiciona, _को__भी_,
+ {{0x932a602a,0x00000000,0x00000000,0x00000000}}, // _vidi__profil_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6b15f02e,0x733df81c,0xcc56501d,0x00000000}}, // _tersedia_, _har__fået_, _खोजें_, --,
+ {{0x4e60e818,0x8a87401d,0xc2e70807,0x00000000}}, // _од_, _ने__कहा_, _det__finnes_, --,
+ {{0x22da6010,0x00000000,0x00000000,0x00000000}}, // _karere_, --, --, --,
+ {{0xf400a018,0x1e8c000e,0x00000000,0x00000000}}, // _datum__upisa_, _के__महत्व_, --, --,
+ {{0x268cf80e,0x5442d811,0xf201e81c,0x74b7d008}}, // _बिहार_, _nje_, _altid_, _surah__al_,
+ {{0x12d8b01f,0x0290b01f,0x53f8b020,0x00000000}}, // [640] _dece_, _deca_, _decu_, --,
+ {{0xdd3fe81f,0xeb9d480a,0x00000000,0x00000000}}, // _očistite_, _मालवा__के_, --, --,
+ {{0x1e292018,0x92489011,0x5aba081f,0x00000000}}, // _као_, _igama_, _proverit, --,
+ {{0x134b4024,0xe3ead80c,0x54444010,0x00000000}}, // _nitko__nije_, _sveta_, _iy_, --,
+ {{0xb41dc818,0x00000000,0x00000000,0x00000000}}, // _lične__poruke_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x02e0f010,0x00000000,0x00000000,0x00000000}}, // _ibiganir_vyacu_, --, --, --,
+ {{0xd6e4080d,0x74571008,0x2b2a681d,0x53946807}}, // _ir__navegaci, _kenal__minta_, _गुदगुदी__पर्यटन_, _epost_,
+ {{0xdb82c818,0xc81fc007,0x00000000,0x00000000}}, // _poruke__tekstova_, _er__tilgjeng, --, --,
+ {{0x8a33601d,0xdbe0101c,0x2a38e018,0x7f79f81e}}, // _कहा_, _accepter, _ће__бити_, _možná_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa211e81d,0x7ab0081d,0x821b5811,0xced60039}}, // _फोटो__गैलरी_, _सकते__हैं_, _ngokuthi_, _možno_,
+ {{0x32fd9035,0xa3316816,0xe320c80c,0x00000000}}, // _rediger_, _kerosaka_yang_, _kedy_, --,
+ {{0xb303300d,0x2386a81c,0x86447811,0x635d1003}}, // _comparti_igual_, _aldrig_, _ngokwesi, _bingung_,
+ {{0x8d378820,0x8291800d,0xf4444010,0x32c23013}}, // _moć_, _xeral_, _cy_, _der__skal_,
+ {{0x5a3ab80c,0x00000000,0x00000000,0x00000000}}, // _alebo__zaregist, --, --, --,
+ {{0xb40ab012,0x4200b00c,0xa6ffb80a,0x00000000}}, // [650] _hace__más_, _veci_, _एकरा_, --,
+ {{0x02d8f81c,0x437a6839,0x00000000,0x00000000}}, // _nogen_, _zadarmo_, --, --,
+ {{0xe290c023,0x1200e009,0x00000000,0x00000000}}, // _allaha_, _zenica_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x72a1380c,0xf290c81b,0xc2d9f814,0x00000000}}, // _pravidlá_, _beda_, _mejorar_, --,
+ {{0xcadc7002,0x92902016,0x00000000,0x00000000}}, // _imágenes_, _sukan_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x59f95828,0x00000000,0x00000000,0x00000000}}, // _upassend, --, --, --,
+ {{0x5e13c01f,0x00000000,0x00000000,0x00000000}}, // _vučić_, --, --, --,
+ {{0x027e901b,0xf4760818,0x6270f80c,0x04969815}}, // _agan_, _ceni__din_, _napríkla, _no__teu_,
+ {{0x8248900b,0x82a69010,0x44a81816,0x00000000}}, // _awam_, _usaba_, _sebelum__ini_, --,
+ {{0xe4444010,0x00000000,0x00000000,0x00000000}}, // _ry_, --, --, --,
+ {{0x0e28e818,0x944f1829,0x00000000,0x00000000}}, // _за_, _há_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb44f1817,0xa4b42009,0x6c5d5817,0x247c5009}}, // _já_, _autor__ica_, _cartão_, _onaj__ko_,
+ {{0x24444010,0x1cbf100b,0xe2ff100d,0x93ea5821}}, // _vy_, _dan__peluang_, _comentar__desde_, _celta_,
+ {{0xabc0201d,0xd44f1817,0xbf24500c,0x00000000}}, // [660] _करने__की_, _lá_, _práve_, --,
+ {{0x42ca1012,0x9ad51007,0x00000000,0x00000000}}, // _este__lugar_, _postadre, --, --,
+ {{0x0b8f2819,0x824d1809,0x9223381b,0xe354380d}}, // _है_, _prikaz__ispisa_, _lengkap__topik_, _galegos_,
+ {{0xf2fdf81c,0xa27ed814,0x00000000,0x00000000}}, // _brug_, _buen_, --, --,
+ {{0x2ed25818,0x048d6828,0x73f4001b,0x00000000}}, // _да__је_, _de__som_, _justru_, --,
+ {{0x949cf01c,0x026c5806,0xe4ade009,0xdf3a2817}}, // _at__få_, _bylo_, _staviti__na_, _as__melhores_,
+ {{0xa49cf01c,0x46614006,0x00000000,0x00000000}}, // _at__gå_, _zaměstna, --, --,
+ {{0x54af6006,0x02571806,0x00000000,0x00000000}}, // _že__se_, _dál_, --, --,
+ {{0x7320c806,0x1290c839,0x00000000,0x00000000}}, // _tedy_, _teda_, --, --,
+ {{0x7444401c,0x238e2814,0x00000000,0x00000000}}, // _op_, _de__acuerdo_, --, --,
+ {{0x0f22000f,0xf2918810,0x706d6807,0x00000000}}, // _obavezno__navođenj, _imyaka_, _kompetan, --,
+ {{0x9502f846,0x1eb16821,0x00000000,0x00000000}}, // _अनुभव_, _externas_, --, --,
+ {{0xa3f9580b,0x00000000,0x00000000,0x00000000}}, // _kepada__semua_, --, --, --,
+ {{0x5f4bf003,0xf291c810,0x00000000,0x00000000}}, // _bergabun, _umva_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x62d8c821,0x00000000,0x00000000,0x00000000}}, // _podes_, --, --, --,
+ {{0x7c51c006,0xcf905017,0x00000000,0x00000000}}, // [670] _kultura_, _grátis_, --, --,
+ {{0x649a500d,0x00000000,0x00000000,0x00000000}}, // _máis__de_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x927f7810,0x51f44827,0x00000000,0x00000000}}, // _uganda_, _nevhodné_, --, --,
+ {{0x1291801f,0x83ea300c,0x927ed80d,0xa386d829}}, // _evra_, _tejto_, _quen_, _quer_,
+ {{0x32d98005,0x00000000,0x00000000,0x00000000}}, // _cerek_, --, --, --,
+ {{0x846f2807,0x6bd6300d,0x22d91017,0x00000000}}, // _les__mer_, _máis__informac, _vezes_, --,
+ {{0x8f350814,0x0443f810,0x4395f803,0x00000000}}, // _reportar_, _ubu_, _trus_, --,
+ {{0x6bfa403a,0x22c42815,0x5395f816,0x0bb0101d}}, // _नियम_, _uso__para_, _urus_, _लोगों__को_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa444401b,0x8a4c9825,0xaf5a3017,0x00000000}}, // _rp_, _के__लाभ_, _pesquisa, --,
+ {{0xc3a23816,0x00000000,0x00000000,0x00000000}}, // _tempoh_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x2eea6018,0x00000000,0x00000000,0x00000000}}, // _био__је_, --, --, --,
+ {{0xbc4f5019,0x92df5022,0x23ead81f,0x92f3c802}}, // _अपने_, _अपनी_, _svetu_, _el__nombre_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [680] --, --, --, --,
+ {{0x6212d017,0x00000000,0x00000000,0x00000000}}, // _mulher_, --, --, --,
+ {{0x82e5f003,0x03ac601b,0x927c601c,0x10f9480d}}, // _lainnya_, _maupun_, _forskell, _proxecto,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf2918008,0xb22f880c,0x00000000,0x00000000}}, // _perak_, _pre__deti_, --, --,
+ {{0xea3d280a,0x727f0011,0x00000000,0x00000000}}, // _होई_, _phansi_, --, --,
+ {{0x8b8d6022,0x42006014,0x4e86e018,0x00000000}}, // _का_, _julio_, _се__не_, --,
+ {{0xca0f5811,0x00000000,0x00000000,0x00000000}}, // _ngaphand, --, --, --,
+ {{0x34972006,0xd3445806,0x12b8b00c,0x400ac006}}, // _kč__kč_, _recenze_, _prihlási_alebo_, _několik_,
+ {{0x425a9011,0x34444010,0xb4420011,0x653e400e}}, // _imali_, _nk_, _ami_, _के__करीब_,
+ {{0xc4936014,0x04946010,0x5d12f818,0xdb052012}}, // _es__la_, _li__li_, _их_, _una__opinión_,
+ {{0x6444400c,0x00000000,0x00000000,0x00000000}}, // _ak_, --, --, --,
+ {{0xe3ea7802,0x96599012,0x00000000,0x00000000}}, // _venta_, _otras__categorí, --, --,
+ {{0x92d83007,0x00000000,0x00000000,0x00000000}}, // _igjen_, --, --, --,
+ {{0x9444401c,0x00000000,0x00000000,0x00000000}}, // _dk_, --, --, --,
+ {{0x6e6de013,0x33ab001b,0x00000000,0x00000000}}, // _københav, _daftar__lengkap_, --, --,
+ {{0x52cc201c,0x5c0e780e,0x00000000,0x00000000}}, // [690] _sælger_, _सामने__आई_, --, --,
+ {{0x1496e81c,0x00000000,0x00000000,0x00000000}}, // _os__med_, --, --, --,
+ {{0x84b46028,0x21660010,0xbd600021,0x00000000}}, // _vil__ha_, _ntushobo, _comunida_autónoma_, --,
+ {{0x1a20483f,0x74160810,0x00000000,0x00000000}}, // _फेसबुक_, _rupapuro__byose_, --, --,
+ {{0xb4a69833,0x2304800d,0x00000000,0x00000000}}, // _स्वास्थ्, _poder__votar_, --, --,
+ {{0x92ba581b,0xf2ae7018,0x00000000,0x00000000}}, // _kondisi_, _izvor__tanjug_, --, --,
+ {{0xa4936010,0x7dd7d80d,0xb30e4016,0x00000000}}, // _na__za_, _lingüíst, _peratus_, --,
+ {{0x54d74842,0x00000000,0x00000000,0x00000000}}, // _मामिला_, --, --, --,
+ {{0xa27ed81c,0x226d9010,0xc4605817,0x00000000}}, // _igen_, _maso_, _não__tem_, --,
+ {{0x446e9016,0xaa93c818,0x00000000,0x00000000}}, // _laman__ini_, _подели_, --, --,
+ {{0xfab4680b,0x144f5028,0x72011006,0x52007814}}, // _linkedin_, _nå_, _mezi_, _junio_,
+ {{0x84444027,0x62011011,0x4250001c,0x00000000}}, // _sk_, _lezi_, _at__skrive_, --,
+ {{0x6e28e818,0x8eb8e818,0x00000000,0x00000000}}, // _са_, _си_, --, --,
+ {{0xa9964006,0x62911010,0x00000000,0x00000000}}, // _může_, _neza_, --, --,
+ {{0x5496e014,0x9659002e,0x46f9f822,0x34936027}}, // _no__se_, _di__indonesi, _मेरा_, _by__sa_,
+ {{0xf2921024,0x1e7ea00d,0x48a9f817,0x744f5007}}, // _srpanj_, _unha__organiza, _ótimo__estrelas_, _då_,
+ {{0x4ef27011,0x7300b812,0x525ad035,0x00000000}}, // [6a0] _kankulun, _erróneo_, _tallet_, --,
+ {{0x34746027,0x7e9a5818,0x16ec580e,0x00000000}}, // _som__sa_, _што__се_, _के__मंदिर_, --,
+ {{0xc290c80b,0x547d581c,0x23030016,0x00000000}}, // _lelaki_, _mere__om_, _pelawat_, --,
+ {{0x94444016,0xddf24818,0x6496e002,0x00000000}}, // _hb_, _пут__измењена_, _no__te_, --,
+ {{0xc7c72817,0xdc74a007,0x1b9c2022,0x421d880e}}, // _mensagem_, _forumet_, _की__एक_, _नक्शा_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xec0ab80e,0xaeee6018,0xc2257811,0x97a5e815}}, // _राज्य__के_, _је__то_, _kwakhe_, _parroqui,
+ {{0x62fd2021,0x03011807,0x00000000,0x00000000}}, // _aplicar_, _tilbake_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc2257811,0x22020003,0x0a148818,0x5dca8018}}, // _ngakho_, _posisi_, _мај__април_, _август__јул_,
+ {{0xebbfc00e,0xd26df002,0x00000000,0x00000000}}, // _सिंह__के_, _estoy_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x547a1007,0x00000000,0x00000000,0x00000000}}, // _informas_om_, --, --, --,
+ {{0x26b5202a,0x54b06007,0x00000000,0x00000000}}, // _milanovi, _mer__om_, --, --,
+ {{0x7d5fc80e,0x00000000,0x00000000,0x00000000}}, // _इकाई_, --, --, --,
+ {{0xe3f46811,0x00000000,0x00000000,0x00000000}}, // _ukuthi_, --, --, --,
+ {{0x04665011,0xf2cae81c,0x00000000,0x00000000}}, // [6b0] _umsebenz, _mindst_, --, --,
+ {{0x03eaf80d,0x00000000,0x00000000,0x00000000}}, // _moitas_, --, --, --,
+ {{0x49e0c00b,0x00000000,0x00000000,0x00000000}}, // _menghant, --, --, --,
+ {{0x12001011,0x00000000,0x00000000,0x00000000}}, // _ithi_, --, --, --,
+ {{0x1a104008,0x00000000,0x00000000,0x00000000}}, // _malaysia_, --, --, --,
+ {{0xf2ab301c,0x00000000,0x00000000,0x00000000}}, // _køber_, --, --, --,
+ {{0x07e4a81d,0x00000000,0x00000000,0x00000000}}, // _ख़बरें_, --, --, --,
+ {{0x22d51806,0xd34ab00d,0x7386d81c,0xfe1c600d}}, // _vše_, _esta__cambios_, _uger_, _estar__rexistra,
+ {{0x2292080b,0xdc0f6017,0x3b96a80d,0x00000000}}, // _kepada_, _seja__primeiro_, _especiai_ligazón_, --,
+ {{0xd2eba007,0x526df80c,0x00000000,0x00000000}}, // _trenger_, _predchád, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x085df813,0x4444401e,0xf30b002e,0x00000000}}, // _fotoalbu, _sb_, _serupa__dengan_, --,
+ {{0x92a62814,0x2b8c9822,0x5ed4e818,0x4200202e}}, // _nombre_, _गए_, _ко_, _bikin_,
+ {{0xf226701c,0x00000000,0x00000000,0x00000000}}, // _besked_, --, --, --,
+ {{0x513c1006,0x32a62814,0xab7b901e,0xc40a5016}}, // _poslední_, _hombre_, _rekonstr, _dalam__tempoh_,
+ {{0x1eccc00d,0x00000000,0x00000000,0x00000000}}, // _ligazóns__externas_, --, --, --,
+ {{0xd2001011,0x00000000,0x00000000,0x00000000}}, // [6c0] _ethi_, --, --, --,
+ {{0x7ae5f018,0x0a85f80e,0xcc60d003,0x00000000}}, // _фебруар__јануар_, _साहित्य__में_, _temukan_, --,
+ {{0xea89300c,0xda33d80e,0x00000000,0x00000000}}, // _inzeráto, _जड़_, --, --,
+ {{0x53e7601c,0x00000000,0x00000000,0x00000000}}, // _tæt_, --, --, --,
+ {{0xb9d22018,0x53fa682a,0x00000000,0x00000000}}, // _neregist, _udruga_, --, --,
+ {{0xa2246820,0x00000000,0x00000000,0x00000000}}, // _skok_, --, --, --,
+ {{0x1e65701b,0x00000000,0x00000000,0x00000000}}, // _dan__ketentua, --, --, --,
+ {{0x0e297818,0x43ea780d,0x00000000,0x00000000}}, // _мај_, _xente_, --, --,
+ {{0xec03f825,0x00000000,0x00000000,0x00000000}}, // _कुमार__के_, --, --, --,
+ {{0x147d2020,0x448cd81c,0x00000000,0x00000000}}, // _često__postavlj, _masser__af_, --, --,
+ {{0x0734280e,0xb47bd012,0xb9dec018,0x32bbc016}}, // _दिल्ली__पुलिस_, _real__el_, _utorak__neregist, _ibu__bapa_,
+ {{0x6b8ea01d,0x52d9d803,0x02d66012,0xa291300d}}, // _वह_, _cewek_, _tu__cuenta_, _sexa_,
+ {{0x22742812,0x00000000,0x00000000,0x00000000}}, // _nuevo__email_, --, --, --,
+ {{0x72ddb048,0x00000000,0x00000000,0x00000000}}, // _खोजी_, --, --, --,
+ {{0x33359817,0x00000000,0x00000000,0x00000000}}, // _você__pode_, --, --, --,
+ {{0xbfb61006,0xcdbbb814,0x8f1fb802,0x846c6029}}, // _ještě_, _búsqueda_, _particip, _com__os_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [6d0] --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd2001011,0xa46c6029,0x00000000,0x00000000}}, // _uthi_, _com__as_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x5df5e006,0x00000000,0x00000000,0x00000000}}, // _představ, --, --, --,
+ {{0xb2b9c806,0x726d9017,0x9fab3806,0x22d9801c}}, // _více_, _isso_, _nemovito, _jeres_,
+ {{0xa3034011,0x00000000,0x00000000,0x00000000}}, // _nabantu_, --, --, --,
+ {{0x547cd821,0x00000000,0x00000000,0x00000000}}, // _onde__se_, --, --, --,
+ {{0x9260c014,0xec04f80e,0x2399601c,0x33548014}}, // _años_, _जनता__के_, _læse_, _mujeres_,
+ {{0x3a46600e,0x00000000,0x00000000,0x00000000}}, // _संरक्षित_, --, --, --,
+ {{0x54bcd006,0x00000000,0x00000000,0x00000000}}, // _jsem__se_, --, --, --,
+ {{0x3366a01b,0x146f900d,0xf48be01b,0x73eae821}}, // _tanggal_, _ligan__con_, _lintas__me_, _montes_,
+ {{0x163f3806,0x8e56e818,0x00000000,0x00000000}}, // _komentář_, _уз_, --, --,
+ {{0x6a6ef010,0x00000000,0x00000000,0x00000000}}, // _ubufasha_, --, --, --,
+ {{0xa3e7200c,0x00000000,0x00000000,0x00000000}}, // _ešte_, --, --, --,
+ {{0x84423010,0x84bad81c,0x00000000,0x00000000}}, // _adj_, _lige__nu_, --, --,
+ {{0xb430b817,0xd1e1301d,0xe04d6806,0x00000000}}, // [6e0] _desconto_, _हमारे_, _prohlíže, --,
+ {{0x0c5bf018,0xc317401f,0xc2489010,0x00000000}}, // _ekstovi_, _zvezde_, _inama_, --,
+ {{0x427ed814,0x0da4080f,0x00000000,0x00000000}}, // _bien_, _opšte__opće_, --, --,
+ {{0xb495e027,0x00000000,0x00000000,0x00000000}}, // _sa__to_, --, --, --,
+ {{0x844f8814,0x00000000,0x00000000,0x00000000}}, // _sé_, --, --, --,
+ {{0x64b24014,0x62c7e010,0x00000000,0x00000000}}, // _condicio_de_, _ruboneka__neza_, --, --,
+ {{0xde24601d,0x1e00f803,0x0bb9281d,0x2da8f816}}, // _बढ़िया__आपत्तिजन, _kesehata, _हैं__और_, _kesihata,
+ {{0x6a3cb00e,0x7ba91819,0x00000000,0x00000000}}, // _रुप_, _हो__तो_, --, --,
+ {{0xc2906016,0x00000000,0x00000000,0x00000000}}, // _julai_, --, --, --,
+ {{0x13b1680f,0x7eb3f818,0x5236d00c,0x00000000}}, // _tekstova__ostalog_, _која__се_, _svojom_, --,
+ {{0x22e6701f,0x2386d810,0x525d7016,0xd972c802}}, // _planeta_, _mbere_, _sebagai__ahli_, _opinione,
+ {{0xa426d81b,0xa0473018,0x00000000,0x00000000}}, // _kamar__tidur_, _текст_, --, --,
+ {{0x26f03849,0x7a3d2819,0x43f5d015,0x00000000}}, // _प्रवेश_, _हैं_, _datos__acerca_, --,
+ {{0x9c600805,0x00000000,0x00000000,0x00000000}}, // _profil__postova_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x13a6980d,0x62d8f81c,0x4f96980d,0x00000000}}, // _páxina_, _noget_, _páxinas_, --,
+ {{0x9c8e7805,0x127f501a,0x00000000,0x00000000}}, // [6f0] _korisnik__postova_, _apenas_, --, --,
+ {{0x6386d807,0x0fe80019,0x00000000,0x00000000}}, // _sier_, _संपादित_, --, --,
+ {{0xc29f7003,0x22480018,0xb4903017,0xe2daa00c}}, // _berbagai_, _snimi_, _em__uma_, _doplnky_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x826de810,0xe2d87807,0x99f65806,0x00000000}}, // _bato_, _innen_, _zatím_, --,
+ {{0xc26e5003,0x84954815,0x00000000,0x00000000}}, // _metode_, _século__xix_, --, --,
+ {{0xcb8dd00e,0x6470e00d,0x00000000,0x00000000}}, // _झा_, _servizo__de_, --, --,
+ {{0x3b8ed00a,0xf3209011,0xe2d83029,0x427ff807}}, // _एह_, _ngayo_, _hoje_, _grunn_,
+ {{0x071f580f,0x00000000,0x00000000,0x00000000}}, // _je__objavlji, --, --, --,
+ {{0xd26de810,0xda29b81b,0x245b6010,0x00000000}}, // _gato_, _bisa__mendapat, _yavuze__ko_, --,
+ {{0x4340e81b,0x00000000,0x00000000,0x00000000}}, // _telepon_, --, --, --,
+ {{0x12903017,0xa8ec2018,0x00000000,0x00000000}}, // _loja_, _условима__могући_, --, --,
+ {{0xee28e818,0xe394d811,0x7290200c,0x00000000}}, // _да_, _ujesu_, _roka_, --,
+ {{0x62d87807,0x54a7d812,0xe27ef010,0x00000000}}, // _annen_, _erróneo__no_, _izindi_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc782b837,0x00000000,0x00000000,0x00000000}}, // _नीति__विकिपीडि, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [700] --, --, --, --,
+ {{0x72c4281c,0xfe12e00d,0xff89281d,0xbc743802}}, // _indlæg_, _marca__rexistra, _रोमांस__साहित्य_, _saludos_,
+ {{0xe273f81b,0xb28cb01d,0x00000000,0x00000000}}, // _bisa__menambah_, _उन्होंने_, --, --,
+ {{0x1248d81f,0x32fcf81c,0x00000000,0x00000000}}, // _vreme_, _indhold_, --, --,
+ {{0x5e9fb017,0x5a60b017,0x2344501c,0x3d5ea81d}}, // _verdinha_menciona, _preços_, _arbejde_, _भेजें__अस्वीकरण_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x13096816,0x00000000,0x00000000,0x00000000}}, // _anda__boleh_, --, --, --,
+ {{0x13e7c006,0x3d368811,0xf4857017,0x00000000}}, // _mít_, _izingane_, _faça__seu_, --,
+ {{0xd30b2018,0xf2918010,0xa26de806,0xc9f4c806}}, // _октобар_, _kera_, _tato_, _lidé_,
+ {{0x32cad006,0x67a8480c,0x00000000,0x00000000}}, // _prodej_, _odpovedz_, --, --,
+ {{0x22d9801c,0xeba62025,0x546cd812,0x440c7806}}, // _mere_, _कर__के_, _madre__no_, _aktuální_,
+ {{0xf2dde018,0x7859701b,0x74956010,0x00000000}}, // _petak__izvor_, _tokobagu, _ko__mu_, --,
+ {{0x35be2818,0xf0c4d018,0x00000000,0x00000000}}, // _други_, _додатни__услови_, --, --,
+ {{0xd2dfe00c,0x45986822,0x00000000,0x00000000}}, // _na__sklade_, _व्रत_, --, --,
+ {{0x44426806,0xd369581b,0x858ad811,0x00000000}}, // _kdo_, _inggris_, _ngezinye__izikhath, --,
+ {{0x02b3e829,0x4ea3e81a,0x42904811,0x00000000}}, // _produto_, _produtos_, _noma_, --,
+ {{0x56146018,0xed6c3018,0x00000000,0x00000000}}, // [710] _uslovi__korišćen, _celu__kuću_, --, --,
+ {{0xd467a005,0x3394f011,0x00000000,0x00000000}}, // _tematski__alati_, _kristu_, --, --,
+ {{0x2b8fd81d,0x92d98007,0x7645180e,0x2671700f}}, // _थी_, _dere_, _के__इतिहास_, _opštinsk_službeni,
+ {{0xed6d3018,0x92904815,0x00000000,0x00000000}}, // _што_, _coma_, --, --,
+ {{0xbed58007,0xb4895012,0x93f52811,0x00000000}}, // _utviklin, _admite__el_, _united__states_, --,
+ {{0xaebdb00c,0x3e35700f,0x00000000,0x00000000}}, // _hodnoten, _koristit_programe_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x33f99011,0xd2904810,0x8290f81b,0x64b4180d}}, // _jesu_, _goma_, _nggak_, _conselle_de_,
+ {{0xec36a80c,0x00000000,0x00000000,0x00000000}}, // _kategóri, --, --, --,
+ {{0x74429011,0x22019011,0x12d99007,0xc2369002}}, // _ama_, _lesi_, _lese_, _abajo_,
+ {{0xf863e014,0x00000000,0x00000000,0x00000000}}, // _artículo, --, --, --,
+ {{0xe2905814,0x4317f81c,0x32d9900d,0x92fd6807}}, // _hola_, _at__holde_, _nese_, _legger_,
+ {{0x3e28e818,0x5eb8e818,0x00000000,0x00000000}}, // _на_, _ни_, --, --,
+ {{0x52494011,0x6ba16010,0x48b99818,0x00000000}}, // _ngemva_, _washobor, _последњи_, --,
+ {{0x5c0b082a,0x72d99011,0x00000000,0x00000000}}, // _obiteljs, _bese_, --, --,
+ {{0xcb25780c,0x00000000,0x00000000,0x00000000}}, // _porovnať_, --, --, --,
+ {{0x82904811,0xe4cea00d,0xe9eaa011,0x00000000}}, // [720] _roma_, _concello_, _njengoba_, --,
+ {{0x92904810,0x00000000,0x00000000,0x00000000}}, // _soma_, --, --, --,
+ {{0xdeed0017,0x73f8e00c,0x2c8f600e,0x00000000}}, // _melhores_, _ponuka_, _इंडो_, --,
+ {{0xc2d87807,0x1394e82e,0x00000000,0x00000000}}, // _annet_, _sensor_, --, --,
+ {{0x8290580c,0xadd9980c,0xa200580c,0xf437081b}}, // _bola_, _deň_, _boli_, _di__lintas_,
+ {{0x32d9801c,0xebd7f825,0x3ee9d807,0x00000000}}, // _vores_, _भाषा__के_, _funksjon, --,
+ {{0x332b201c,0x00000000,0x00000000,0x00000000}}, // _om__synes_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x23fa281b,0x12d59806,0x94429003,0x00000000}}, // _dan__langsung_, _než__méně_, _sma_, --,
+ {{0xaa9f2018,0xaa2d9015,0xf2919014,0x436bf81b}}, // _прокомен_подели_, _texto__está_, _cosas_, _sejak__minggu_,
+ {{0xebde7825,0xe4acd80f,0x00000000,0x00000000}}, // _बिहार__के_, _ostalog__na_, --, --,
+ {{0xfa33002c,0x59c2c00c,0x6bae981d,0x00000000}}, // _चयन_, _odpovedz__páči_, _कोई__भी_, --,
+ {{0xa5a99818,0x00000000,0x00000000,0x00000000}}, // _организа, --, --, --,
+ {{0xf21b8011,0x34a4e00c,0x00000000,0x00000000}}, // _phakathi_, _čo__sa_, --, --,
+ {{0xb4429017,0xc2cb4024,0x00000000,0x00000000}}, // _uma_, _tjedan_, --, --,
+ {{0xb48e601c,0x00000000,0x00000000,0x00000000}}, // [730] _af__at_, --, --, --,
+ {{0x3386900c,0x8387801a,0x00000000,0x00000000}}, // _ktorí_, _carro_, --, --,
+ {{0xe4b86018,0xcc57301d,0x00000000,0x00000000}}, // _vesti__na_, _इसमें_, --, --,
+ {{0x9c69e805,0x00000000,0x00000000,0x00000000}}, // _još__postova_, --, --, --,
+ {{0x6290580d,0x4212b014,0xd71fb04a,0x0224d807}}, // _pola_, _fecha_, _उद्देश्य, _sjekk_,
+ {{0xd9daa82e,0x00000000,0x00000000,0x00000000}}, // _ternyata_, --, --, --,
+ {{0x1bee1818,0xcb99001d,0x00000000,0x00000000}}, // _како_, _साथ__ही_, --, --,
+ {{0xeb9ce00e,0x00000000,0x00000000,0x00000000}}, // _प्रदेश__के_, --, --, --,
+ {{0xd4957821,0xb386980c,0xebde100e,0x6468601a}}, // _no__que_, _ktorý_, _हाल__के_, _dia__de_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xc2d6f006,0x9ad29002,0x33f8f80c,0x00000000}}, // _před_, _contrase, _ponuky_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6200f81b,0xf297f015,0x00000000,0x00000000}}, // _bugil_, _dos__novos_, --, --,
+ {{0x02ca9014,0x03eb3029,0x00000000,0x00000000}}, // _edad_, _sexta_, --, --,
+ {{0x63f4701c,0x76d7f81d,0x00000000,0x00000000}}, // _har__været_, _चाहिए_, --, --,
+ {{0xab980015,0x4a32a00a,0x13dde812,0x8c49d00a}}, // _ficheiro_, _गइल_, _tu__madre_, _नइखे_,
+ {{0x22907811,0x92d8c81c,0x00000000,0x00000000}}, // [740] _kona_, _inden_, --, --,
+ {{0x6326a007,0x00000000,0x00000000,0x00000000}}, // _en__annen_, --, --, --,
+ {{0x6c4e101d,0x4a329022,0x7475a807,0xf9f47806}}, // _करने_, _खेल_, _saker__les_, _jiné_,
+ {{0x32907811,0x4236c81f,0x42d98018,0x00000000}}, // _lona_, _nadji_, _ocenite_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xb3eb900c,0x422a2018,0x00000000,0x00000000}}, // _meste_, _тога_, --, --,
+ {{0xebc0a01d,0x32abc81c,0x00000000,0x00000000}}, // _होने__के_, _håber_, --, --,
+ {{0x447a1803,0x1fe13018,0x00000000,0x00000000}}, // _saat__ini_, _своју_, --, --,
+ {{0x92907811,0xe4626035,0x53f83011,0x00000000}}, // _bona_, _bør__du_, _kamuva_, --,
+ {{0x397d4024,0x54429023,0x12d8c81c,0x53f95817}}, // _uvjeti__korišten, _sda_, _anden_, _alguma_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf68cb81d,0x00000000,0x00000000,0x00000000}}, // _प्यार_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x3c071007,0x78c1881b,0xf327101c,0x00000000}}, // _prosent_, _dalam__tokobagu, _procent_, --,
+ {{0xb449d017,0x00000000,0x00000000,0x00000000}}, // _enviar__um_, --, --, --,
+ {{0x1212901b,0xf321e807,0x00000000,0x00000000}}, // _udah_, _betyr_, --, --,
+ {{0x5a32a00e,0xf4548807,0x00000000,0x00000000}}, // [750] _गेल_, _har__hatt_, --, --,
+ {{0x7354d802,0x0275481b,0x027e9010,0x00000000}}, // _quieres_, _pemberit_bagi_, _cyane_, --,
+ {{0xbb197808,0x3b69f00d,0x3beec018,0x00000000}}, // _kerajaan_, _ligazón_, _након_, --,
+ {{0xdb8c6018,0x00000000,0x00000000,0x00000000}}, // _povređen, --, --, --,
+ {{0x4654d012,0xbc0c3025,0x00000000,0x00000000}}, // _tu__direcció, _दूर__तक_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x9ef48016,0x00000000,0x00000000,0x00000000}}, // _saling__berhubun, --, --, --,
+ {{0x5cb53012,0xe3eaf80d,0x00000000,0x00000000}}, // _ha__cerrado_, _moitos_, --, --,
+ {{0x8255b023,0x3265880d,0xc300780d,0x00000000}}, // _kantonal, _aplicar__termos_, _datos__citar_, --,
+ {{0xa493f811,0xca3e400a,0x7a6e881d,0x127f0013}}, // _of__the_, _भोज_, _करते__हैं_, _blandt_,
+ {{0xb2d9c817,0x5c61d01d,0x43860010,0x00000000}}, // _deve_, _कर__सकते_, _ibiri_, --,
+ {{0x92da001f,0xfb96200e,0x226c1017,0x53ea000d}}, // _meseci_, _बात__से_, _acho_, _moito_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x8543e003,0x00000000,0x00000000,0x00000000}}, // _berhasil_, --, --, --,
+ {{0xfde25816,0xd4b9d01f,0xd9ec5002,0x00000000}}, // _perniaga_perminta, _smajliji__su_, _colombia_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xdbe0500e,0x00000000,0x00000000,0x00000000}}, // [760] _से__भर_, --, --, --,
+ {{0xa25ac806,0xf2906003,0xf45f5810,0x42906016}}, // _podle_, _mulai_, _ikigega__cy_, _pulak_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x44d6900e,0xd9f7a010,0x00000000,0x00000000}}, // _मैथिली_, _bibiliya_, --, --,
+ {{0xf3120810,0xc5327818,0x00000000,0x00000000}}, // _byose__cyangwa_, _септемба, --, --,
+ {{0x1315b00b,0xd3ea0007,0x826e180b,0x34398835}}, // _memberi_, _blitt_, _kenal__dari_, _at__dette_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x0c51b01d,0x00000000,0x00000000,0x00000000}}, // _पढ़ें_, --, --, --,
+ {{0x73a2c008,0x00000000,0x00000000,0x00000000}}, // _lumpur_, --, --, --,
+ {{0x2e966018,0x00000000,0x00000000,0x00000000}}, // _он__је_, --, --, --,
+ {{0x43522014,0xfe87180d,0xdbe7a018,0xc39eb01c}}, // _un__amigo_, _de__xuño_, _под__лиценцом_, _betyder_,
+ {{0x42011002,0x00000000,0x00000000,0x00000000}}, // _cocina_, --, --, --,
+ {{0x840b0015,0x00000000,0x00000000,0x00000000}}, // _desde__http_, --, --, --,
+ {{0x028d600b,0x4fc3980d,0xd48db017,0x00000000}}, // _perkara_, _ferramen_páxinas_, _de__sua_, --,
+ {{0x1291e810,0x72d8c81c,0xd464601c,0xeb99e81d}}, // _leta_, _andet_, _synes__godt_, _की__गई_,
+ {{0x94201816,0x00000000,0x00000000,0x00000000}}, // [770] _niaga__baru_, --, --, --,
+ {{0xb442d80c,0x4290a01b,0x00000000,0x00000000}}, // _sme_, _coba_, --, --,
+ {{0xc2ca901c,0x34429007,0xf317f01b,0x00000000}}, // _hvad_, _hva_, _berbagi_, --,
+ {{0x64661809,0x9245e006,0x997e5035,0x00000000}}, // _vrh__postao_, _dům_, _du__kommente, --,
+ {{0xcc213809,0x00000000,0x00000000,0x00000000}}, // _mjeseca__mjeseci_, --, --, --,
+ {{0x22391018,0x00000000,0x00000000,0x00000000}}, // _могу_, --, --, --,
+ {{0xb201e80c,0xa2d9e81f,0xd472101d,0x1291c80c}}, // _deti_, _dete_, _महीने_, _tovar_,
+ {{0x2291800c,0xb7c41029,0x62927810,0x2eb4f813}}, // _teraz_, _brasilei, _yesaya_, _anvendes_,
+ {{0xd442d827,0x00000000,0x00000000,0x00000000}}, // _ide_, --, --, --,
+ {{0x8500a01d,0xc0e7381c,0x00000000,0x00000000}}, // _क्रिकेट__अन्य_, _almindel, --, --,
+ {{0x96311802,0x00000000,0x00000000,0x00000000}}, // _la__categorí, --, --, --,
+ {{0x0b99f81d,0xe442d806,0x00000000,0x00000000}}, // _की__ओर_, _jde_, --, --,
+ {{0xc3de4817,0x92aae817,0x93806003,0xfbbb0825}}, // _avaliaçã, _seu__comentár, _aturan_, _पाकिस्ता_से_,
+ {{0x5ea3b80f,0x739ab00d,0x00000000,0x00000000}}, // _za__djelimič, _véxase_, --, --,
+ {{0xd2926816,0x1c59700b,0xc352701c,0x00000000}}, // _kerana_, _peluang_, _tilføj_, --,
+ {{0x2442d810,0xda649010,0x7019c015,0x00000000}}, // _nde_, _rupapuro__ruboneka_, _poder__comentar_, --,
+ {{0x226d2010,0x99f4d00c,0x00000000,0x00000000}}, // [780] _ibyo_, _budú_, --, --,
+ {{0xe4420007,0x63d8581c,0x00000000,0x00000000}}, // _hei_, _at__gøre_, --, --,
+ {{0x7291e810,0x8213900c,0x00000000,0x00000000}}, // _reta_, _najnovši, --, --,
+ {{0xcbcb681c,0x0bd1e019,0x00000000,0x00000000}}, // _erhvervs, _करियर_, --, --,
+ {{0xcf22f805,0x6e92e017,0x73432014,0x8305701b}}, // _nađi__još_, _amigo__adiciona, _derecho_, _pemberit_saya_,
+ {{0x14b12024,0x52749017,0xfa3d580e,0x00000000}}, // _postova__od_, _estrelas__ótimo_, _रहन_, --,
+ {{0x3dd93816,0x82d83014,0x92a7800c,0xe4603802}}, // _berkenaa, _mujer_, _farba_, _qué__es_,
+ {{0xb442d820,0xa290f80d,0x44420007,0x5b5f6018}}, // _gde_, _ligan_, _nei_, _climatec,
+ {{0x22122011,0x6250581f,0x43418029,0x00000000}}, // _lokho_, _ne__bojim_, _janeiro_, --,
+ {{0x7ed56018,0xe442d806,0x00000000,0x00000000}}, // _ми__се_, _zde_, --, --,
+ {{0xd394b01c,0x42122011,0x31d9a01d,0x00000000}}, // _sidste_, _nokho_, _नज़र_, --,
+ {{0x6320901b,0x0a6e1825,0x00000000,0x00000000}}, // _biaya_, _सत्र__में_, --, --,
+ {{0xe2d8c803,0x42d8b006,0x64b86018,0x00000000}}, // _kode_, _roce_, _vesti__iz_, --,
+ {{0x1326e81c,0x00000000,0x00000000,0x00000000}}, // _en__anden_, --, --, --,
+ {{0x17b33820,0x92c7c01c,0x725a0007,0x645bf017}}, // _reputaci, _sådan_, _feil_, _condiçõe_de_,
+ {{0xc3ea100c,0x225af00c,0x00000000,0x00000000}}, // _tohto_, _mailom_, --, --,
+ {{0x0eba5007,0x11d0e806,0xc2d0e810,0xdc6e1017}}, // [790] _gjøre_, _telefony_, _telefoni_, _escreva_,
+ {{0xaa3cf822,0xc44ef012,0x00000000,0x00000000}}, // _शहर_, _nombre__del_, --, --,
+ {{0x7ec27818,0x00000000,0x00000000,0x00000000}}, // _данас__се_, --, --, --,
+ {{0xf39b781b,0x00000000,0x00000000,0x00000000}}, // _jakarta__selatan_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xa2bf980d,0x22ca901c,0x9387e80d,0x00000000}}, // _que__ligan_, _plads_, _catro_, --,
+ {{0xf36f202e,0xa00d5806,0xca489025,0x00000000}}, // _tingkat_, _proveden, _खास__तौर_, --,
+ {{0x1c02580d,0x9442d81c,0xaee9a811,0xd2e8a01b}}, // _persoas_, _ude_, _ngokuqon, _pikiran_,
+ {{0x2341800d,0x00000000,0x00000000,0x00000000}}, // _xaneiro_, --, --, --,
+ {{0x3b5ca020,0x78b1a018,0xc2480017,0x3ee9d81c}}, // _pošaljit, _под__истим_, _ruim_, _funktion,
+ {{0x927e7807,0xe46de007,0x00000000,0x00000000}}, // _vann_, _hva__er_, --, --,
+ {{0x19ff8006,0x1a9c0025,0x00000000,0x00000000}}, // _hvězdičk_recenzí_, _में__अगर_, --, --,
+ {{0xcd03002e,0x02915826,0xc4420007,0x00000000}}, // _terlihat_, _adgang_, _vei_, --,
+ {{0xc588d00d,0x72d85816,0x00000000,0x00000000}}, // _poboació, _filem_, --, --,
+ {{0xa442101b,0x32cad007,0x66ff381d,0x74444017}}, // _deh_, _bilder_, _आपका_, _vc_,
+ {{0xfb620807,0x00000000,0x00000000,0x00000000}}, // _publiser, --, --, --,
+ {{0x4a32b819,0x86ad5025,0x00000000,0x00000000}}, // [7a0] _गया_, _जन्म__स्थान_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x44af901b,0xf2d4a818,0x9badb81d,0x00000000}}, // _iklan__ini_, _četvrtak__izvor_, _था__कि_, --,
+ {{0x33ebe835,0x2eb8f818,0x34539810,0x52d8d039}}, // _dette_, _када__је_, _uru__rupapuro_, _budem_,
+ {{0xaae6283f,0x42f72012,0xf2d51007,0x00000000}}, // _उद्योग_, _del__lugar_, _tillatt_, --,
+ {{0x2c026817,0x7eb4b818,0x00000000,0x00000000}}, // _pessoas_, _који__се_, --, --,
+ {{0x9bd30807,0x00000000,0x00000000,0x00000000}}, // _oppdater, --, --, --,
+ {{0x52002010,0x32902011,0x00000000,0x00000000}}, // _kuki_, _kuka_, --, --,
+ {{0xf46ed818,0xfbe6301d,0x348c681f,0xb495f807}}, // _према_, _कर__रहे_, _naprednj, _så__mye_,
+ {{0x33218010,0x00000000,0x00000000,0x00000000}}, // _buryo_, --, --, --,
+ {{0x42902011,0xd2d9e81c,0xc4420007,0x3712201d}}, // _luka_, _efter_, _bli_, _कर__सकता_,
+ {{0x64444027,0xf985d81d,0x00000000,0x00000000}}, // _aj_, _जिसमें_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x2387f01b,0x00000000,0x00000000,0x00000000}}, // _istri_, --, --, --,
+ {{0xa3f87806,0x84bd5803,0x3436b007,0x00000000}}, // _minut_, _langsung__di_, _de__siste_, --,
+ {{0x1c90b01d,0x54a32810,0xcecc300c,0x00000000}}, // _दोनों_, _cyangwa__css_, _hviezdič, --,
+ {{0x53eb900c,0x00000000,0x00000000,0x00000000}}, // [7b0] _mesto_, --, --, --,
+ {{0x04ac901b,0x00000000,0x00000000,0x00000000}}, // _berniaga__com_, --, --, --,
+ {{0x63cf481c,0x5429181b,0xb497e014,0x00000000}}, // _blevet_, _original__posted_, _si__el_, --,
+ {{0xcb1f9016,0xb2f5b817,0x5d845007,0x00000000}}, // _bahagian_, _digite__código_, _arbeidet_, --,
+ {{0x63871006,0xc2686017,0x3e5e2018,0x0c5de025}}, // _který_, _estrela__ruim_, _пријави__на_, _के__जाने_,
+ {{0x02786811,0x2ad44020,0x7e1b7003,0x00000000}}, // _umuntu_, _đoković_, _ditemuka, --,
+ {{0x42b54002,0xe0ddd011,0x2df8a818,0x00000000}}, // _precio_, _ngendlel, _коментар_, --,
+ {{0x9f25b017,0xd30f701b,0xf442301c,0x00000000}}, // _qualquer_, _berbeda_, _hej_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x14423039,0x03ea7810,0x00000000,0x00000000}}, // _jej_, _konti_, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xec32f818,0xc28d8811,0x72003010,0x649c300d}}, // _tanjug__komentar, _mathewu_, _muji_, _xuño__de_,
+ {{0x4a33e84b,0xbc86d024,0x8eb8e818,0x00000000}}, // _जात_, _sve__postove_, _ти_, --,
+ {{0x275aa830,0xf35d6818,0x54423027,0x00000000}}, // _संस्करण_, _pol__ženski_, _nej_, --,
+ {{0x03f82011,0x63207811,0x638ab81c,0x73aa300d}}, // _suku_, _kanye_, _gøre_, _baixo__licenza_,
+ {{0xa316d811,0x00000000,0x00000000,0x00000000}}, // _phezu_, --, --, --,
+ {{0xce0d2018,0x00000000,0x00000000,0x00000000}}, // [7c0] _ова_, --, --, --,
+ {{0x02e9600e,0x52016010,0x00000000,0x00000000}}, // _सत्र_, _kugira_, --, --,
+ {{0x2439f81f,0x94432006,0x527f7814,0x00000000}}, // _na__svetu_, _kdy_, _cuanto_, --,
+ {{0xa290e01f,0xf236d00c,0x23209011,0x7ef4d818}}, // _dinara_, _svojim_, _isaya_, _које__се_,
+ {{0x09f4c806,0x825a301c,0x00000000,0x00000000}}, // _lidí_, _fejl_, --, --,
+ {{0x1442d80c,0x648c3817,0x00000000,0x00000000}}, // _dve_, _além__de_, --, --,
+ {{0x5008900d,0x00000000,0x00000000,0x00000000}}, // _relacion_páxinas_, --, --, --,
+ {{0x4a73701d,0x22005816,0xa2900017,0x00000000}}, // _किया__गया_, _bilik_, _criar_, --,
+ {{0x9e13c01f,0xebeda00a,0x00000000,0x00000000}}, // _dačić_, _सब__के_, --, --,
+ {{0x5987880c,0x44abd80c,0x00000000,0x00000000}}, // _páči_, _stránke_, --, --,
+ {{0x5c48f81d,0x31ea781d,0x42d8f81d,0x3496e014}}, // _सकते_, _एक__नज़र_, _सकती_, _no__lo_,
+ {{0xd34e800f,0x72942011,0x00000000,0x00000000}}, // _kantonim_opštinam, _israyeli_, --, --,
+ {{0x33719010,0xf1772818,0x00000000,0x00000000}}, // _ibibera__hanze_, _вести_, --, --,
+ {{0x12907816,0x00000000,0x00000000,0x00000000}}, // _sunat_, --, --, --,
+ {{0x8c4f681d,0x7ca39023,0x9c8f681d,0x62df681d}}, // _आपके_, _političk_partije_, _आपको_, _आपकी_,
+ {{0x64424829,0x00000000,0x00000000,0x00000000}}, // _nem_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [7d0] --, --, --, --,
+ {{0xc3567017,0xf2720816,0x00000000,0x00000000}}, // _receber_, _penuh__carian_, --, --,
+ {{0xa4424817,0x12caa02a,0xd442301c,0xd3ead80c}}, // _bem_, _hajduk_, _vej_, _svet_,
+ {{0x52da781f,0xe2902016,0x00000000,0x00000000}}, // _meseca_, _rakan_, --, --,
+ {{0x92904816,0x02d82007,0xe2004810,0xc46e100d}}, // _laman_, _saken_, _cumi_, _enderezo_,
+ {{0x6bb7100d,0x5b1fc016,0x00000000,0x00000000}}, // _as__ligazóns_, _kelantan_, --, --,
+ {{0xf451a823,0x00000000,0x00000000,0x00000000}}, // _vrijeme__posta_, --, --, --,
+ {{0xeec0d030,0x84765012,0xe27f5810,0x00000000}}, // _विशेष__पृष्ठ_, _lugar__ha_, _ukwa__cumi_, --,
+ {{0xc36e300f,0x00000000,0x00000000,0x00000000}}, // _kantonim, --, --, --,
+ {{0x921f1009,0xe0120823,0x00000000,0x00000000}}, // _mjeseci__godina_, _izdvajan, --, --,
+ {{0x6a485829,0x0e2dd01b,0x00000000,0x00000000}}, // _de__pagament, _dengan__pengikut_, --, --,
+ {{0x4e72e818,0x4f25101c,0x12c5101c,0xc284c017}}, // _ће_, _billeder_, _billede_, _páginas__como_,
+ {{0x03eb9007,0x575fe00e,0x9c3dd00f,0xd34f8018}}, // _beste_, _विस्फोट_, _istraživ_finansir, _новембар__октобар_,
+ {{0x52d85811,0x936f6006,0x6597380e,0x02948011}}, // _kule_, _rozpětí_, _में__करीब_, _ofakazi_,
+ {{0xa2959024,0x54425816,0xc3ead812,0x00000000}}, // _tematski_, _mel_, _cuotas_, --,
+ {{0x62905808,0x00000000,0x00000000,0x00000000}}, // _mula_, --, --, --,
+ {{0x52905811,0x027eb804,0x3c4e501d,0x3481c00c}}, // [7e0] _lula_, _jednom_, _कैसे_, _registro_sa_,
+ {{0xb442481e,0xd2918014,0xedff5016,0xa2d4201f}}, // _sem_, _otras_, _rundinga, _večernje_,
+ {{0x5175e017,0x00000000,0x00000000,0x00000000}}, // _notícias_, --, --, --,
+ {{0xebf64825,0xb496e006,0x00000000,0x00000000}}, // _गांव__के_, _se__to_, --, --,
+ {{0x92907806,0x00000000,0x00000000,0x00000000}}, // _jinak_, --, --, --,
+ {{0xf4128009,0x1cb28009,0xba728009,0x1dffd02e}}, // _sarajevo_, _sarajevu_, _sarajeva_, _ketentua,
+ {{0xc4424817,0x99207806,0x2c4e1019,0x02de101d}}, // _tem_, _mobilní_, _करते_, _करती_,
+ {{0x82901016,0x00000000,0x00000000,0x00000000}}, // _sihat_, --, --, --,
+ {{0xb0739009,0x93e5100c,0xb3266818,0x00000000}}, // _bosansko, _podmienk, _prva__poslednj, --,
+ {{0xb4629816,0x12480011,0x2ed5e018,0x0aa5881d}}, // _kenalan__dan_, _isimo_, _ко__је_, _में__हुए_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xd94f4807,0x7ef0301d,0x00000000,0x00000000}}, // _innlegge, _लड़की_, --, --,
+ {{0x6474a015,0x00000000,0x00000000,0x00000000}}, // _elemento__de_, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x4c6d200d,0x00000000,0x00000000,0x00000000}}, // _ligazóns__última_, --, --, --,
+ {{0x7290480b,0x632c4806,0x00000000,0x00000000}}, // [7f0] _zaman_, _těchto_, --, --,
+ {{0xff298827,0xd442481c,0xb2680802,0x00000000}}, // _bratisla, _alm_, _del__sitio_, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x6a3df822,0x52d9300d,0x00000000,0x00000000}}, // _तरह_, _hoxe_, --, --,
+ {{0x44b66023,0x2f77f017,0x00000000,0x00000000}}, // _mijenjan_put_, _faça_, --, --,
+ {{0x44bb201b,0x00000000,0x00000000,0x00000000}}, // _situs__ini_, --, --, --,
+ {{0xd9d15815,0x00000000,0x00000000,0x00000000}}, // _ponteved, --, --, --,
+ {{0xbe8f1818,0x9a3d580a,0x0d864021,0x00000000}}, // _али_, _रहल_, _interese_, --,
+ {{0xd442681f,0x925af81c,0xcf66601c,0xa2e43812}}, // _deo_, _nogle_, _hjælp_, _algún__dato_,
+ {{0xf36f8816,0x9290e810,0x00000000,0x00000000}}, // _bersetuj_dengan_, _munani_, --, --,
+ {{0xc0abc818,0x00000000,0x00000000,0x00000000}}, // _се__промени_, --, --, --,
+ {{0x12d82007,0xd26c581f,0x2264a82e,0x00000000}}, // _liker_, _belog_, _melalui__email_, --,
+ {{0x39f4201e,0x4eb8e818,0x26a2201c,0x93100010}}, // _také_, _ми_, _spørgsmå, _ukwa__munani_,
+ {{0x8bcd5822,0xbff4181c,0x04976005,0x00000000}}, // _रही_, _oplysnin, _pronađi__sve_, --,
+ {{0x02120010,0x2e320812,0x00000000,0x00000000}}, // _igihe_, _hablaras_, --, --,
+ {{0x14427813,0x2eebd818,0x00000000,0x00000000}}, // _hen_, _нам__је_, --, --,
+
+ };
+ // table_hash = 032c-fba2, unused_entries = 3745 (45.72%)
+
+static const uint32 kDistinctOctaChrome0122SizeOne = 76; // One-langprob count
+static const uint32 kDistinctOctaChrome0122IndSize = 76; // Largest subscript
+static const uint32 kDistinctOctaChrome0122Ind[kDistinctOctaChrome0122IndSize] = {
+ // [0000]
+ 0x00000000, 0x00000000, 0x00000b03, 0x00001c03, // -- -- es.un.un_300 id.un.un_300
+ 0x00000d01, 0x00001606, 0x00000d06, 0x00000806, // cs.un.un_200 hr.un.un_400 cs.un.un_400 no.un.un_400
+ 0x00001e03, 0x00003201, 0x00001306, 0x00001e01, // ms.un.un_300 bs.un.un_200 bh.un.un_400 ms.un.un_200
+ 0x00002d06, 0x00001906, 0x00001301, 0x00003206, // sk.un.un_400 gl.un.un_400 bh.un.un_200 bs.un.un_400
+ // [0010]
+ 0x00005506, 0x00003506, 0x00000b06, 0x00000203, // rw.un.un_400 zu.un.un_400 es.un.un_400 da.un.un_300
+ 0x00000b01, 0x00001903, 0x00001e06, 0x00000a06, // es.un.un_200 gl.un.un_300 ms.un.un_400 pt.un.un_400
+ 0x00001706, 0x00000903, 0x00000a01, 0x00001c06, // sr.un.un_400 hi.un.un_300 pt.un.un_200 id.un.un_400
+ 0x00000206, 0x00000906, 0x00000d03, 0x00001703, // da.un.un_400 hi.un.un_400 cs.un.un_300 sr.un.un_300
+ // [0020]
+ 0x00001701, 0x00001901, 0x00000901, 0x00003203, // sr.un.un_200 gl.un.un_200 hi.un.un_200 bs.un.un_300
+ 0x00001603, 0x00001303, 0x00000201, 0x00002d03, // hr.un.un_300 bh.un.un_300 da.un.un_200 sk.un.un_300
+ 0x00000803, 0x00000a03, 0x00001601, 0x13000d02, // no.un.un_300 pt.un.un_300 hr.un.un_200 ne.bh.un_220
+ 0x09000d09, 0x130d1c02, 0x00001c01, 0x0d001c07, // ne.hi.un_440 mr.ne.bh_222 id.un.un_200 mr.ne.un_420
+ // [0030]
+ 0x09000d08, 0x091c0d07, 0x09000d02, 0x09000d04, // ne.hi.un_430 ne.mr.hi_432 ne.hi.un_220 ne.hi.un_320
+ 0x09000d05, 0x00000801, 0x09001c08, 0x09000d07, // ne.hi.un_330 no.un.un_200 mr.hi.un_430 ne.hi.un_420
+ 0x13001c04, 0x00002d01, 0x091c0d08, 0x09001c07, // mr.bh.un_320 sk.un.un_200 ne.mr.hi_443 mr.hi.un_420
+ 0x09001c09, 0x13000d07, 0x13001c09, 0x091c0da4, // mr.hi.un_440 ne.bh.un_420 mr.bh.un_440 ne.mr.hi_433
+ // [0040] --- double_langprob_start=004c ---
+ 0x09001c02, 0x13001c05, 0x13000d09, 0x13001c08, // mr.hi.un_220 mr.bh.un_330 ne.bh.un_440 mr.bh.un_430
+ 0x13000d08, 0x0d001c08, 0x090d1ca4, 0x09001c05, // ne.bh.un_430 mr.ne.un_430 mr.ne.hi_433 mr.hi.un_330
+ 0x13000d04, 0x091c0d55, 0x13000d05, 0x13001c02, // ne.bh.un_320 ne.mr.hi_442 ne.bh.un_330 mr.bh.un_220
+ //
+ };
+
+// COMPILE_ASSERT(76 <= 2048, k_indirectbits_too_small);
+
+extern const CLD2TableSummary kDistinctOcta_obj = {
+ kDistinctOctaChrome0122,
+ kDistinctOctaChrome0122Ind,
+ kDistinctOctaChrome0122SizeOne,
+ kDistinctOctaChrome0122Size,
+ kDistinctOctaChrome0122KeyMask,
+ kDistinctOctaChrome0122BuildDate,
+ kDistinctOctaChrome0122RecognizedLangScripts,
+};
+
+static const uint32 kDistinctOctaChrome0122_2Size = 0; // Bucket count
+static const uint32 kDistinctOctaChrome0122_2KeyMask = 0xffffffff; // Mask hash key
+
+static const IndirectProbBucket4 kDistinctOctaChrome0122_2[kDistinctOctaChrome0122_2Size] = {
+ // hash_indirect[4], tokens[4] in UTF-8
+ };
+ // table_hash = ffff-ffff, unused_entries = 0 (0.00%)
+
+static const uint32 kDistinctOctaChrome0122_2SizeOne = 2; // One-langprob count
+static const uint32 kDistinctOctaChrome0122_2IndSize = 2; // Largest subscript
+static const uint32 kDistinctOctaChrome0122_2Ind[kDistinctOctaChrome0122_2IndSize] = {
+ // [0000] --- double_langprob_start=0002 ---
+ 0x00000000, 0x00000000, // -- --
+ //
+ };
+
+extern const CLD2TableSummary kDistinctOcta_obj2 = {
+ kDistinctOctaChrome0122_2,
+ kDistinctOctaChrome0122_2Ind,
+ kDistinctOctaChrome0122_2SizeOne,
+ kDistinctOctaChrome0122_2Size,
+ kDistinctOctaChrome0122_2KeyMask,
+ kDistinctOctaChrome0122BuildDate,
+ kDistinctOctaChrome0122RecognizedLangScripts,
+};
+
+} // End namespace CLD2
+
+// End of generated tables
diff --git a/browser/components/translation/cld2/internal/cld2_generated_quadchrome0122_16.cc b/browser/components/translation/cld2/internal/cld2_generated_quadchrome0122_16.cc
new file mode 100644
index 000000000..45018c8e6
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld2_generated_quadchrome0122_16.cc
@@ -0,0 +1,52746 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Created by postproc-cld2 4.0 on 2014-01-31 09:59:13
+// From command line:
+// --nearby_probs=3.4 --extra_entries=be-Cyrl bh-Deva bs-Latn hr-Latn
+// sr-Latn gl-Latn es-Latn id-Latn ms-Latn sv-Latn --cld2 --cc
+// --just_read_raw --quad --standard --minchars=2 --mincount=2
+// --max_items_per_langscript=6000 --flatmap --rr_alloc --freq_alloc
+// --boostcloseweakerpercent=00 --indirectbits=16 --thresh=224 --v25
+// --kentries=160 --tablename=QuadChrome0122_16 --remap=xxx-Latn=>ut-Latn
+// tw-Latn=>ak-Latn nd-Latn=>nr-Latn blu-Latn=>hmn-Latn nn-Latn=>no-Latn
+// --include=af-Latn ar-Arab be-Cyrl bg-Cyrl bs-Latn ca-Latn cs-Latn
+// cy-Latn da-Latn de-Latn el-Grek en-Latn es-Latn et-Latn fa-Arab
+// fi-Latn fr-Latn ga-Latn gd-Latn hi-Deva hr-Latn hu-Latn id-Latn
+// is-Latn it-Latn iw-Hebr ja-Hani ko-Hani lg-Latn lt-Latn lv-Latn
+// mk-Cyrl ms-Latn nl-Latn no-Latn pl-Latn pt-Latn ro-Latn ro-Cyrl
+// ru-Cyrl rw-Latn sh-Cyrl sh-Latn sk-Latn sl-Latn sr-Cyrl sv-Latn
+// sw-Latn th-Thai tl-Latn tr-Latn uk-Cyrl vi-Latn yi-Hebr zh-Hani zh-TW
+// zhT-Hani sq-Latn az-Latn eu-Latn bn-Beng gl-Latn ht-Latn mt-Latn
+// sr-Latn ur-Arab bh-Deva mr-Deva ne-Deva lg-Latn rw-Latn gd-Latn
+// ut-Latn ut-Deva ceb-Latn blu-Latn hmn-Latn jw-Latn so-Latn ig-Latn
+// ha-Latn yo-Latn zu-Latn --ko_english --force_to_lang_soft --nosoft_cram2
+// --nomsidlevel --shapeflatprob --langpriorpercent=10 --skipnuc
+// --noshapeforcetop --noshapeeventop --noshapesteep2 --langcounts
+// --writebin --list_items=120 --max_langs_per_item=3
+// /hdb1/cld2/probs/p90_raw_quadgrams_2014.utf8
+//
+// CLD2_pslangs
+//
+// See compact_lang_det.cc for usage
+//
+#include "cld2tablesummary.h"
+namespace CLD2 {
+
+// For this build date, looking up 'qpdbmrmxyzptlkuuddlrlrbas' gives AZERBAIJANI
+static const uint32 kQuadChrome0122_16BuildDate = 20140131; // yyyymmdd
+
+
+// Of 390154 offered items into 163840 table entries:
+// 163724 filled (41%), 558 merged (0%), 225872 dropped (57%)
+
+// Nil-grams: 19 languages
+// GREEK MALAYALAM TELUGU TAMIL GUJARATI THAI KANNADA PUNJABI
+// GEORGIAN SINHALESE ARMENIAN LAOTHIAN KHMER DHIVEHI CHEROKEE
+// SYRIAC LIMBU ORIYA INUKTITUT
+
+// Uni-grams: 4 languages
+// Japanese Korean Chinese ChineseT
+
+// Words/Quads: 67 languages in range ENGLISH..HMONG:
+// ENGLISH DANISH DUTCH FINNISH FRENCH GERMAN HEBREW ITALIAN
+// NORWEGIAN POLISH PORTUGUESE RUSSIAN SPANISH SWEDISH CZECH
+// ICELANDIC LATVIAN LITHUANIAN ROMANIAN HUNGARIAN ESTONIAN
+// BULGARIAN CROATIAN SERBIAN IRISH GALICIAN TAGALOG TURKISH
+// UKRAINIAN HINDI MACEDONIAN BENGALI INDONESIAN MALAY WELSH
+// NEPALI ALBANIAN BELARUSIAN JAVANESE URDU BIHARI ARABIC CATALAN
+// BASQUE SCOTS_GAELIC SWAHILI SLOVENIAN MARATHI MALTESE
+// VIETNAMESE SLOVAK AZERBAIJANI PERSIAN BOSNIAN ZULU YIDDISH
+// SOMALI MONGOLIAN AFRIKAANS YORUBA HAUSA HAITIAN_CREOLE
+// KINYARWANDA GANDA IGBO CEBUANO HMONG
+
+// TopLanguage TokenCount
+// ENGLISH 2200
+// DANISH 2244
+// DUTCH 2218
+// FINNISH 2205
+// FRENCH 2207
+// GERMAN 2182
+// HEBREW 2225
+// ITALIAN 2152
+// NORWEGIAN 2233
+// POLISH 2213
+// PORTUGUESE 2226
+// RUSSIAN 2139
+// SPANISH 3167
+// SWEDISH 3274
+// CZECH 4498
+// ICELANDIC 2205
+// LATVIAN 2201
+// LITHUANIAN 2202
+// ROMANIAN 4306
+// HUNGARIAN 2209
+// ESTONIAN 2212
+// BULGARIAN 2149
+// CROATIAN 3414
+// SERBIAN 5250
+// IRISH 2247
+// GALICIAN 3148
+// TAGALOG 2091
+// TURKISH 2231
+// UKRAINIAN 2167
+// HINDI 4330
+// MACEDONIAN 2156
+// BENGALI 2231
+// INDONESIAN 4024
+// MALAY 2752
+// WELSH 2214
+// NEPALI 2183
+// ALBANIAN 2210
+// BELARUSIAN 3189
+// JAVANESE 2218
+// URDU 2212
+// BIHARI 3621
+// ARABIC 2184
+// CATALAN 2236
+// BASQUE 2241
+// SCOTS_GAELIC 2255
+// SWAHILI 2232
+// SLOVENIAN 2223
+// MARATHI 2141
+// MALTESE 2245
+// VIETNAMESE 2200
+// SLOVAK 2227
+// AZERBAIJANI 2232
+// PERSIAN 2204
+// BOSNIAN 2783
+// ZULU 2208
+// YIDDISH 2154
+// SOMALI 2233
+// MONGOLIAN 15
+// AFRIKAANS 2208
+// YORUBA 2212
+// HAUSA 2238
+// HAITIAN_CREOLE 2225
+// KINYARWANDA 2199
+// GANDA 2273
+// IGBO 2175
+// CEBUANO 2006
+// HMONG 1978
+
+
+
+// Recognized language-script combinations [69]:
+static const char* const kQuadChrome0122_16RecognizedLangScripts =
+ "af-Latn ar-Arab az-Latn be-Cyrl bg-Cyrl bh-Deva bn-Beng bs-Latn "
+ "ca-Latn ceb-Latn cs-Latn cy-Latn da-Latn de-Latn en-Latn es-Latn "
+ "et-Latn eu-Latn fa-Arab fi-Latn fr-Latn ga-Latn gd-Latn gl-Latn "
+ "ha-Latn hi-Deva hmn-Latn hr-Latn ht-Latn hu-Latn id-Latn ig-Latn "
+ "is-Latn it-Latn iw-Hebr jw-Latn lg-Latn lt-Latn lv-Latn mk-Cyrl "
+ "mn-Latn mr-Deva ms-Latn mt-Latn ne-Deva nl-Latn no-Latn pl-Latn "
+ "pt-Latn ro-Cyrl ro-Latn ru-Cyrl rw-Latn sk-Latn sl-Latn so-Latn "
+ "sq-Latn sr-Cyrl sr-Latn sv-Latn sw-Latn tl-Latn tr-Latn uk-Cyrl "
+ "ur-Arab vi-Latn yi-Hebr yo-Latn zu-Latn ";
+
+static const uint32 kQuadChrome0122_16Size = 32768; // Bucket count
+static const uint32 kQuadChrome0122_16KeyMask = 0xffff8000; // Mask hash key
+
+static const IndirectProbBucket4 kQuadChrome0122_16[kQuadChrome0122_16Size] = {
+ // hash_indirect[4], tokens[4] in UTF-8
+ {{0xe9da0002,0xe2970003,0x7c2e0004,0x20078005}}, // [000] вка_, аат_, ssbr, _huni_,
+ {{0x20078006,0x64438007,0xbddb0008,0x23bf0009}}, // _kuni_, kuni, stèr, täjä_,
+ {{0xddc4000a,0x6563800b,0x52e2800c,0x02e2800d}}, // _križ, denh, _पर्स, _पर्न,
+ {{0x6443800e,0x4916000f,0x63bb8010,0x20078011}}, // duni, _प्रो_, jiun, _muni_,
+ {{0x20078012,0x3ea08013,0x27ed8014,0x63a98015}}, // _luni_, áit_, _dhen_, dhen,
+ {{0x65638016,0x64438017,0x6fdd0018,0xef1f0019}}, // genh, funi, léct, tjük_,
+ {{0x6443801a,0x3ea0801b,0x2451801c,0x2007801d}}, // guni, šit_, _cơm_, _nuni_,
+ {{0x63bb801e,0xef1f0019,0x63a9801f,0x6fdd0020}}, // giun, rjük_, ghen, néct,
+ {{0x36678021,0x65638022,0xa3ab0023,0x2fc68024}}, // _като_, benh, कोट_, mnog_,
+ {{0x2fc68025,0x64438026,0x645e0009,0x20078027}}, // lnog_, buni, _oppi, _buni_,
+ {{0x2b9c0028,0x75288029,0x63bb802a,0x6aad002b}}, // _tích_, _iedz, biun, zzaf,
+ {{0x63a9802c,0x4ea7002d,0x63bb802e,0x2252002f}}, // chen, арга, ciun, ktyk_,
+ {{0x69dc0030,0x645e0031,0xadc38032,0x6b828033}}, // _okre, _appi, _atẹn, _inog,
+ {{0x6e240034,0x75288035,0x2b988036,0x20078037}}, // _dwib, _jedz, _vécu_, _funi_,
+ {{0x75288038,0x89378039,0xddc40024,0x212c8014}}, // _medz, _כרגע_, _friž, madh_,
+ {{0x2fc6803a,0x248d003b,0x212c803c,0xf699003d}}, // jnog_, ćem_, ladh_, _منبع_,
+ {{0x6b82803b,0x2fc68025,0x7afd803e,0xafb8003f}}, // [010] _mnog, dnog_, žstv, _خطوط_,
+ {{0x212c803c,0x63bb802e,0x64438040,0x20078041}}, // nadh_, ziun, yuni, _yuni_,
+ {{0x6b828042,0x69d70043,0x65638044,0x27ed8045}}, // _onog, roxe, venh, _phen_,
+ {{0x212c8046,0x69dc0047,0xa8040048,0xc7c48049}}, // hadh_, _ekre, ізіл, осчи,
+ {{0x44310019,0x75288035,0x63a9804a,0x672b804b}}, // lsz_, _bedz, vhen, ragj,
+ {{0x27ed804c,0x6443804d,0x9474004e,0x26df804f}}, // _when_, tuni, _ادیا, _vyuo_,
+ {{0x63bb8012,0x65638050,0x2fc68024,0x27ed8051}}, // tiun, renh, bnog_, _then_,
+ {{0x2fc68052,0x65638053,0xa3ab0054,0x20078055}}, // cnog_, senh, कोच_, _runi_,
+ {{0xe9d98056,0x63a98057,0x64438058,0x29188059}}, // лки_, rhen, suni, ıran_,
+ {{0x212c803c,0x63bb805a,0x63a9805b,0x2007805c}}, // gadh_, siun, shen, _puni_,
+ {{0x63a9805d,0xa3d7005e,0x6616005f,0x8d748060}}, // phen, ाका_, _styk, _باقا,
+ {{0x44310061,0x66018062,0x6fdd002a,0xd5438032}}, // dsz_, _hilk, xéct, _fẹ̀f,
+ {{0x66018063,0xeab38064,0x7e6d0065,0x67298066}}, // _kilk, _بعض_, _csap, _meej,
+ {{0x2fc68067,0x212c8068,0x67298069,0xb21b006a}}, // znog_, cadh_, _leej, _kræf,
+ {{0x1015806b,0x64a3006c,0xfce6006d,0x7e6d006e}}, // _ابتد, пара, сово, _esap,
+ {{0x69dc006f,0xee3f003e,0x260a8006,0x67298070}}, // _skre, brý_, ादमी_, _neej,
+ {{0x2fc6803a,0x0aea0071,0x3a3f802a,0x28a58072}}, // [020] vnog_, удай_, nrup_, खनवि,
+ {{0xa5bb0073,0x46db8074,0x31710037,0xda0a8075}}, // ssór, _बड़ह, _cazz_, _हंसत_,
+ {{0x2fc6803a,0xaad80076,0x75288029,0x7e7b8077}}, // tnog_, _भुरक, _redz, _arup,
+ {{0xbb860013,0x43860013,0xfdb0800f,0xa3e7800f}}, // _الحي, _الحق, जफ्फ, _मील_,
+ {{0x2fc68025,0x66018078,0x65948009,0x291e8079}}, // rnog_, _bilk, жалу, ybta_,
+ {{0x69dc007a,0x3a3f806a,0xdb16007b,0x8e57007c}}, // _ukre, drup_, ábær, מינג_,
+ {{0x2fc6805c,0x3860807d,0x66018079,0xe644807e}}, // pnog_, _ćiro_, _dilk, ğlığ,
+ {{0x5214807f,0xe6448059,0x79838080,0x53988081}}, // одит, şlığ, _annw, рвия_,
+ {{0x22400082,0x212c8083,0xa92a8084,0x6fc80085}}, // nrik_, tadh_, ліне_, rıcl,
+ {{0x388e8086,0xf96a8087,0x20098088,0x44310035}}, // ləri_, урий_, _čair_, ysz_,
+ {{0x212c803c,0x22400089,0x6729808a,0xdb0d008b}}, // radh_, hrik_, _yeej, knað,
+ {{0x212c808c,0x6601808d,0x212a008e,0x96f9008f}}, // sadh_, _zilk, _lebh_, иент_,
+ {{0x4ad80076,0x212c8090,0x9f588091,0x44310092}}, // _भुलव, padh_, _burú_, wsz_,
+ {{0x44310019,0x22400093,0x66018079,0x6d4a8094}}, // tsz_, drik_, _xilk, ngfa,
+ {{0x69da8095,0x79950063,0xe8d98028,0x69c88096}}, // mote, _rozw, _đủ_, mnde,
+ {{0x7ae28097,0x67d40098,0x22400099,0xdb0d007b}}, // _kyot, _нощу, frik_, gnað,
+ {{0x7995009a,0x44310019,0xddc2809b,0x7e6d009c}}, // [030] _pozw, ssz_, dvož, _usap,
+ {{0x69da809d,0x672f0063,0x69c8809e,0x6441009f}}, // note, lacj, nnde, àlis,
+ {{0xe71980a0,0x439500a1,0x388e8085,0x644100a2}}, // ليات_, _данс, fəri_, álid,
+ {{0x644700a3,0x200a00a4,0xe00b80a5,0x672f0035}}, // muji, _dubi_, _संसद_, nacj,
+ {{0x69da80a6,0x660180a7,0x63ad00a8,0x7e7b8085}}, // kote, _pilk, mhan, _qrup,
+ {{0x63ad00a9,0x69da80aa,0xcfcf00ab,0xd6580039}}, // lhan, jote, রতিন, ציות_,
+ {{0x660180ac,0x645500ad,0x672f0063,0x200a0052}}, // _vilk, ntzi, kacj, _gubi_,
+ {{0x36d580ae,0xeb9980af,0x69c880b0,0x7e7b80b1}}, // _добр, рии_, ende, _trup,
+ {{0x660180b2,0xbfa880b3,0x7e7b80b4,0x69da80b5}}, // _tilk, _утре_, _urup, fote,
+ {{0x499980b6,0xfeb880b7,0x644700b8,0x3b1f80b9}}, // ртия_, _طاقت_, kuji, sbuq_,
+ {{0xa3e900ba,0xe2a88077,0x63ad00bb,0xfbd900bc}}, // यता_, لاین_, khan, भकाम,
+ {{0x69c880bd,0x9986809a,0x14d680be,0xaa5980bf}}, // ande, _ktoś_, _וועל_, _виду_,
+ {{0x63ad00c0,0x69da80c1,0xc8e600c2,0xddc400c3}}, // dhan, bote, _कर्म_, _asiš,
+ {{0x98a580c4,0xa0a580c5,0x7bc980c6,0x63ad00c7}}, // _миле, _малд, lneu, ehan,
+ {{0x09c400c8,0x63ad00c9,0x8b9600ca,0x3b9600a9}}, // ্তমা, fhan, орач, ојат,
+ {{0x63ad00cb,0x7bc980cc,0x2fcd80cd,0x7c3a00ce}}, // ghan, nneu, _njeg_, štro,
+ {{0xa3bb00cf,0x27e000d0,0x200a00d1,0x7c2900d2}}, // [040] _अगर_, _ikin_, _subi_, ćera,
+ {{0x224000d3,0x0f2280d4,0x2b4f808e,0x2b58003d}}, // rrik_, मर्स_, _lcgc_, _گیرد_,
+ {{0xc8ca00d5,0x106a00d6,0x13a800d7,0x7bc980d8}}, // جوان_, рией_, _هندی_, kneu,
+ {{0x68e380d9,0x63ad00da,0xa3ae001b,0x69da80db}}, // _mynd, chan, _कति_, zote,
+ {{0xa3d700dc,0x27e000dd,0x69da80de,0x69c68061}}, // ाकर_, _mkin_, yote, ékek,
+ {{0x672f0063,0x200a00df,0x63a400e0,0x644100e1}}, // zacj, _tubi_, lkin, šlie,
+ {{0x07a680e2,0x6d4a80e3,0x7ae280e4,0x201800e5}}, // _давн, rgfa, _syot, _utri_,
+ {{0x63a400e6,0x7bc980e7,0x81e780ab,0xd32680e8}}, // nkin, gneu, _মূল_, цьки,
+ {{0x298a80e9,0x63a400ea,0xdcfe00eb,0xb21b00ec}}, // асно_, ikin, _kopē, _græd,
+ {{0x69c880ed,0x7ae2804f,0x2a6200ee,0x27e680ef}}, // unde, _vyot, _mpkb_, llon_,
+ {{0x200480f0,0x201880f1,0xdb060019,0x672f009a}}, // _kimi_, ëri_, _elké, tacj,
+ {{0x290780f2,0x63a400f3,0x69da80f4,0x27e680f5}}, // _egna_, jkin, sote, nlon_,
+ {{0x200480f6,0x672f0063,0xd25080f7,0x69da80f8}}, // _mimi_, racj, _أنت_, pote,
+ {{0x7bc080f9,0x200480fa,0x27e680fb,0x63ad00fc}}, // limu, _limi_, hlon_, whan,
+ {{0x63ad00fd,0x27e6803b,0x63a400fe,0x261080ff}}, // than, klon_, fkin, _lào_,
+ {{0x20048100,0x2a620101,0x64550102,0x52150103}}, // _nimi_, _bpkb_, rtzi, здат,
+ {{0x26108104,0xe8108105,0xdb0b8106,0x1dbc8107}}, // [050] _nào_, _ठंडा_, _umgå, ्चात,
+ {{0x63ad0108,0x7bc08109,0xddc2809a,0x6447010a}}, // shan, himu, rtoś, puji,
+ {{0x672d010b,0x660d010c,0x63a4010d,0x7bc0810e}}, // _keaj, _huak, bkin, kimu,
+ {{0x63a40039,0xf093810f,0x261080ff,0x7bc08110}}, // ckin, ונע_, _bào_, jimu,
+ {{0xa3e40105,0x7bc08111,0xa77c00be,0x660500b9}}, // _पीट_, dimu, יראפ, _kihk,
+ {{0x98a5802e,0x5a340112,0x99640113,0x7bc98114}}, // nală_, _інст, _отсл, wneu,
+ {{0xdd8e80f7,0x22490102,0x20048115,0x27e68116}}, // اوى_, luak_, _fimi_, blon_,
+ {{0xd90e0117,0xdb028118,0x7bc08119,0xb21b006a}}, // _لیے_, _eloí, gimu, _træd,
+ {{0x76428036,0xdce3811a,0x2249011b,0x68e6811c}}, // croy, menč, nuak_, əkdi,
+ {{0x661b811d,0x63a4011e,0x2004811f,0xae44803d}}, // _ituk, zkin, _zimi_, _آپلو,
+ {{0x63a40120,0x7bc08121,0x660d0122,0x7bc98123}}, // ykin, bimu, _auak, pneu,
+ {{0xf7720124,0x6d410125,0x2249011b,0x6e298126}}, // راج_, ólan, kuak_, _kweb,
+ {{0xa3ca8127,0xa3c10128,0x290780ee,0x26108129}}, // ोवर_, ्चन_, _tgna_, _xào_,
+ {{0xdd920077,0x427a812a,0x67228024,0x7c29012b}}, // روز_, _לאנג, zboj, ćern,
+ {{0x63a4012c,0x27e6812d,0x69c1812e,0x9479012f}}, // tkin, ylon_, mile, осту_,
+ {{0x69c18130,0x6f098131,0xdce38132,0xfd4c8133}}, // lile, _ngec, jenč, _belụ,
+ {{0xc64a0013,0xeb970134,0x69c18083,0x6e298135}}, // [060] _اجعل_, чит_, oile, _nweb,
+ {{0x20048136,0x63a40137,0xb6bb0051,0x867b012a}}, // _simi_, skin, _לצפי, _פריו,
+ {{0x63a40138,0x036a0139,0xfce6013a,0x661b813b}}, // pkin, _линк_, _хоно, _atuk,
+ {{0xb21b013c,0x69c1813d,0x2249013e,0x2d9a0039}}, // _dræb, hile, buak_, _hope_,
+ {{0x69c1813f,0x672280eb,0x7bc08110,0xddc98140}}, // kile, rboj, vimu, _drež,
+ {{0x6d4e0141,0x26108142,0x6d5c0143,0x76428144}}, // ngba, _vào_, nfra, rroy,
+ {{0x69c18145,0x6e298146,0x27e68147,0x20048148}}, // dile, _eweb, plon_, _timi_,
+ {{0x69de0149,0x7e6400d2,0x65ab0085,0x7642814a}}, // lope, _opip, _müha, proy,
+ {{0xadc38028,0x7bc0814b,0xe944803d,0x9d46814c}}, // _quản, rimu, وردی, _неад,
+ {{0x7bc0814d,0x69c1814e,0x6d4e00f3,0x69de014f}}, // simu, gile, jgba, nope,
+ {{0x7bc08150,0x672d0151,0x290a0135,0xe8df00ff}}, // pimu, _peaj, _mgba_, _trục_,
+ {{0x37ab0152,0xeb978153,0xd36f8154,0x660d00ee}}, // стон_, _них_, _مهم_, _puak,
+ {{0x69c18155,0x6d5c0156,0x69de0157,0xe3b38158}}, // bile, ffra, kope, _אױף_,
+ {{0x72eb8159,0x69c1815a,0xe9448077,0xf9c4815b}}, // _פֿאַ, cile, _آرشی, _تحصی,
+ {{0xd5c88104,0x98a58087,0x7c29012b,0xe7e8015c}}, // yền_, rală_, ćero, _टीका_,
+ {{0x2249011e,0x660d015d,0x290a015e,0x6d4e015f}}, // tuak_, _tuak, _agba_, agba,
+ {{0xb9990160,0xdce38161,0x98a58162,0x644a0163}}, // [070] овах_, venč, pală_, _åfin,
+ {{0x5c070098,0x644a8164,0x22490102,0xc1788110}}, // _няма, kufi, ruak_, ndė_,
+ {{0x3ea60029,0xddc98165,0x661b8166,0x2249011b}}, // _ļoti_, _prež, _stuk, suak_,
+ {{0x69c18167,0x6443807a,0x65770168,0x290a0032}}, // zile, vrni, _haxh, _egba_,
+ {{0x69c18169,0x7bdf016a,0xee3880e8,0xddc98088}}, // yile, moqu, зні_, _vrež,
+ {{0x7bdf00a2,0x050100ab,0xddd6016b,0xfce5816c}}, // loqu, ্রীর_, _kryš, моло,
+ {{0x6458816d,0x200e8041,0x65770168,0x6443816e}}, // gtvi, _nufi_, _maxh, urni,
+ {{0x09b7816f,0x7bcd0170,0x22158171,0x64c50115}}, // ेच्य, nnau, мфор, _ičić,
+ {{0x69c18172,0x6d5c0114,0x661b8173,0xd24e8174}}, // tile, yfra, _utuk, لني_,
+ {{0x7bcd0175,0x23c38176,0x6d410177,0x10a58178}}, // hnau, _bèjè_, ólal, дион,
+ {{0x69c18179,0x5455817a,0xe3c2017b,0xf746017c}}, // rile, _цвет, lmış_, мено,
+ {{0x69c1817d,0xd24e817e,0x7c3a017f,0xe2918180}}, // sile, دنی_, štrk, _لذت_,
+ {{0x65b00181,0xe3c20182,0x6d5c0183,0x69c18184}}, // _lähe, nmış_, tfra, pile,
+ {{0x90c58185,0x7ae60186,0x65ab0074,0x69de0187}}, // _обле, _rykt, _püha, xope,
+ {{0xfce30188,0x6d5c0189,0x7bdf018a,0xa507018b}}, // воро, rfra, foqu, дера_,
+ {{0x8ffa003d,0x320f808e,0x764b818c,0x6441018d}}, // _برتر_, _kugy_, kugy, ália,
+ {{0xd5b20019,0x2fc0018e,0x7c248115,0x3207818f}}, // [080] _لفظ_, _imig_, ćiri, _kiny_,
+ {{0xb1430190,0xa0a60191,0x320f810c,0x65ab0192}}, // кнул, мазд, _mugy_, _hühn,
+ {{0xeb9a0193,0x69de0194,0xb21b006a,0x8b260195}}, // _дин_, rope, _præc, _одне,
+ {{0xfaa60196,0x46ea80b3,0x201c00eb,0xddc98197}}, // _жамо, оден_, īvi_, _ereż,
+ {{0x65b00198,0xfe6f8199,0x05d20035,0x69de019a}}, // _tähd, لدو_, _सदाब, pope,
+ {{0x6458819b,0x443a019c,0x290a019d,0x644a819e}}, // ttvi, _kvp_, _ugba_, tufi,
+ {{0xd90d019f,0x910301a0,0x443a00c3,0x443801a1}}, // _ایم_, _апре, _jvp_, nsr_,
+ {{0xda088104,0x28bd01a2,0x645881a3,0x02a681a4}}, // _hỏi_, ्परि, rtvi, дром,
+ {{0x200e81a5,0x961880ab,0x6f09016b,0x6aa401a6}}, // _sufi_, _ডটকম_, žeck, nyif,
+ {{0x6e2281a7,0xb5aa81a8,0x443800b9,0x67d201a9}}, // mpob, _بارك_, ksr_, vāja,
+ {{0x26ee81aa,0xda0880ff,0xa3ae01ab,0x65ab0192}}, // _जरुर_, _mỏi_, कसा_, _bühn,
+ {{0xdce501ac,0x67d200eb,0x733681ad,0x338381ae}}, // _pohľ, tāja, _آرائ, _аушв,
+ {{0xa3e781af,0x799c01b0,0x7bdf01b1,0x200e80b9}}, // _मीट_, _oorw, voqu, _wufi_,
+ {{0x58d481b2,0x29070110,0xdb0b81b3,0x660881b4}}, // _полт, žnai_, _algè, _jidk,
+ {{0x7bdf00a9,0x66088079,0x9f588144,0xd13981b5}}, // toqu, _midk, _juró_, _ххі_,
+ {{0x26ee81b6,0x628281b7,0x67d200eb,0x53de816f}}, // _जरूर_, _kroo, pāja, मविश,
+ {{0x7bcd01b8,0x050100ab,0xddc981b9,0x799c01ba}}, // [090] rnau, ্রের_, _preż, _borw,
+ {{0x51f801bb,0xaca301bc,0x2a6680ee,0xd13101bd}}, // дную_, _arụg, _mpob_, اما_,
+ {{0x753501be,0x200901bf,0x9f9c808b,0x443a01c0}}, // mazz, _kiai_, tíð_, _gvp_,
+ {{0x753501c1,0x65b001c2,0x6e2d01c3,0xa635804a}}, // lazz, _vähe, _kwab, енді,
+ {{0x59de81c4,0x628281c5,0x6e2d008e,0x799c01c6}}, // नकार, _nroo, _jwab, _forw,
+ {{0x753501b9,0x65b00006,0x9f588144,0xba430084}}, // nazz, _tähe, _buró_, žįst,
+ {{0x76598114,0x628281c7,0x3ea981c8,0x64c50088}}, // stwy, _aroo, šat_, _učić,
+ {{0x6f0d01c9,0x9f5881ca,0x6aca81cb,0xe3c201cc}}, // _ngac, _duró_, ापूर, pmış_,
+ {{0x753501cd,0xb4bc01ce,0x020581cf,0x67f601d0}}, // kazz, _अध्_, нзин, náje,
+ {{0xf9918013,0x628280c9,0x6b9d01d1,0x7c3a017f}}, // ابة_, _droo, _losg, štri,
+ {{0xa3c101d2,0xfc3080f7,0x2011004f,0x39588014}}, // ्चा_, _نحن_, _juzi_, òrsa_,
+ {{0x6e2d01d3,0x201101d4,0x69c501d5,0x248601d6}}, // _bwab, _muzi_, hihe, _čom_,
+ {{0xee3701d7,0x628281d8,0xa68301d9,0x6b8b81da}}, // ння_, _groo, гляд, _ingg,
+ {{0xceb281db,0x753501dc,0x7c2201dd,0x236d81de}}, // נים_, gazz, _čorb, leej_,
+ {{0x77a601df,0xdb0b80e7,0x69c501e0,0x6b9d01e1}}, // _lóxi, _algé, dihe, _bosg,
+ {{0xddcd01e2,0x63a981e3,0x2009001c,0x6b9d01e4}}, // _graž, nken, _giai_, _cosg,
+ {{0x6b6301e5,0xe8df801c,0x799c01e6,0x6b8b80dd}}, // [0a0] ыкта, _khỏi_, _porw, _mngg,
+ {{0x61e181e7,0x753501e8,0x236d81e9,0x63a981ea}}, // noll, cazz, heej_, hken,
+ {{0x63a981eb,0x799c01ec,0x6b9d01e4,0x03a60087}}, // kken, _vorw, _fosg, _зино,
+ {{0x201100d2,0x63a981ed,0x644e01ee,0x61e181ef}}, // _duzi_, jken, mubi, holl,
+ {{0x61e181f0,0x63a9813c,0x99800110,0xdfcf81a8}}, // koll, dken, usių_, حين_,
+ {{0x6b8b81f1,0xdd9080f7,0x63a981f2,0xda088129}}, // _angg, شوب_, eken, _tỏi_,
+ {{0x186701f3,0x316c81ac,0x201101f4,0x61e181f5}}, // _пари_, vedz_, _guzi_, doll,
+ {{0x2a66808e,0x67d201a9,0x7c38800b,0x656e01f6}}, // _ppob_, dājo, rsvr, hebh,
+ {{0x645c01f7,0x644e01f8,0xdd8f81f9,0x6d4101fa}}, // htri, hubi, _گول_, ólak,
+ {{0x6b8b81fb,0x644e01fc,0xbddb01fd,0x6e2d00b4}}, // _engg, kubi, trèm, _rwab,
+ {{0xa3d001fe,0x753501ff,0x2d8c8200,0xd8f880e8}}, // वचा_, vazz, _inde_, ьної_,
+ {{0x645a8201,0x63a98202,0xe7cf00ab,0x657a8203}}, // _iqti, cken, রত্য, _hath,
+ {{0x657a8204,0x2d9e8205,0x61e18206,0xb5fb0207}}, // _kath, _kote_, boll, ntág,
+ {{0x61e18208,0xd12f8077,0xb21b013c,0x2d9e8168}}, // coll, _همه_, _bræn, _jote_,
+ {{0xddcd0209,0x753501cd,0x6b9d020a,0xa4d400e8}}, // _traž, razz, _posg, горі,
+ {{0x657a820b,0x6e2d020c,0x3ea6820d,0x25dc820e}}, // _lath, _twab, kyot_, मकुण,
+ {{0x245a820f,0x2d8c8210,0x75350211,0x21310176}}, // [0b0] hëm_, _onde_, pazz, _pezh_,
+ {{0x657a8212,0xdb0d00f7,0x2d9e8213,0x3eb30214}}, // _nath, mhaí, _note_, ıntı_,
+ {{0xb21b0215,0x644e0216,0x69c50217,0x645c0218}}, // _græn, cubi, rihe, ctri,
+ {{0x44210219,0x201c021a,0x61fe021b,0x69c5021c}}, // _ith_, ívia_, ampl, sihe,
+ {{0x2d9e821d,0xe618821e,0x3ea6821f,0x8b9a0039}}, // _bote_, еді_, gyot_, _חברת,
+ {{0x7bc28220,0xbea28221,0x2d9e8222,0x644800eb}}, // _amou, рашк, _cote_, šdie,
+ {{0x61e18223,0x657a8224,0xeb998225,0xabd58226}}, // voll, _dath, дий_, кциј,
+ {{0x2d8c8227,0x77a60228,0xc1788110,0x657a8229}}, // _ende_, _tóxi, klės_, _eath,
+ {{0x61e1822a,0x853c8084,0xddcd022b,0x98ac81a9}}, // toll, _plėt, _braż, vadā_,
+ {{0x926480f7,0x236d822c,0xb5fb0118,0x777b811b}}, // اديم, seej_, ntád, _haux,
+ {{0xb604000d,0x645c022d,0xe80b8105,0x6b8b80dd}}, // ntář, xtri, _सूखा_, _tngg,
+ {{0x2d9e822e,0x6b8b822f,0x22490230,0x61e18231}}, // _zote_, _ungg, krak_, soll,
+ {{0x2d9e822e,0x61e18232,0x44210233,0x657a8234}}, // _yote_, poll, _ath_, _yath,
+ {{0x645c0235,0xa5bb0236,0x61458237,0x644e0238}}, // ttri, spón, _река, tubi,
+ {{0xb0248104,0x44210239,0xfce6006d,0x22490102}}, // _trườ, _cth_, тово, erak_,
+ {{0x1e86023a,0xf41380ab,0x764f023b,0x2c7c81d0}}, // _алим, _সবার_, bucy, ládá_,
+ {{0xc332804c,0x6d41803a,0x2619023c,0x644e023d}}, // [0c0] יון_, _odla, _यूपी_, subi,
+ {{0xb21b0022,0x3f8d823e,0xa92480e1,0xe9d7023f}}, // _træn, _aneu_, peľň, вку_,
+ {{0x657a8240,0x2d9e8241,0x7e698242,0x7aeb8243}}, // _rath, _rote_, _apep, _lygt,
+ {{0x6d418244,0xdce88038,0x2d9e8245,0x22490246}}, // _adla, _podľ, _sote_, brak_,
+ {{0x2d9e8247,0xa3c10054,0x69c38248,0xeb970249}}, // _pote_, ्चर_, _omne, _рия_,
+ {{0x245a820f,0x2fc68110,0x44210101,0x7c3a024a}}, // tëm_, siog_, _yth_, štru,
+ {{0x6da3824b,0x6aca824c,0x629d8106,0x2d9e824d}}, // рија, ाप्र, äson, _vote_,
+ {{0x2d9e822e,0x6286024e,0x657a805d,0x8fa3824f}}, // _wote_, _krko, _wath, _касе,
+ {{0xe73781a1,0xa6658250,0xcb678251,0x2d9e8252}}, // _шет_, _مطلو, тање_, _tote_,
+ {{0x2d8c8012,0x657a80f7,0x601e80eb,0x62860253}}, // _unde_, _uath, _līme, _mrko,
+ {{0x31370039,0x7f590254,0x22490255,0x1cba8061}}, // כנים_, нанс_, zrak_, _غائب_,
+ {{0x97a70256,0x69c38257,0xdb0d00f7,0x1fa70258}}, // трол, _emne, thaí, трог,
+ {{0xecdb000f,0xdddb8259,0xa2c2025a,0x629601c0}}, // _मुजफ, _hruš, _रेल्, jxyo,
+ {{0xdddb825b,0x765d025c,0xdbd1808b,0x764f018e}}, // _kruš, rtsy, lýðs, rucy,
+ {{0xd5b8025d,0xb4ad025e,0xa3d680c2,0x81d800ab}}, // тся_, कने_, _सदा_, াতা_,
+ {{0x6286012b,0x644a81a8,0x7d080061,0x9cd7825f}}, // _brko, irfi, _sérü, דולה_,
+ {{0x27e90260,0x62848084,0x443c8088,0x777b8261}}, // [0d0] _ikan_, _šioj, hsv_, _raux,
+ {{0x224900ad,0x69c88262,0x645a009f,0x23b180e1}}, // rrak_, nide, àtic, _mája_,
+ {{0x290700f2,0x62860263,0x981401a8,0x4cc10264}}, // äna_, _erko, لبنا, _শুরু,
+ {{0x63b60265,0x69c88266,0x75220267,0x22490268}}, // chyn, hide, _đozi, prak_,
+ {{0x69c88269,0x2509026a,0x3f8181d0,0xadc4026b}}, // kide, _ترکی_, ěhu_, _arẹw,
+ {{0x24858101,0x2b59026c,0x6d55026d,0xf483803d}}, // _prlm_, _hcsc_, ngza, لاعی,
+ {{0x27e9026e,0x61e5026f,0x69c88270,0x25a101d0}}, // _okan_, mohl, dide, _mohl_,
+ {{0x63ad0271,0xb5fb0272,0xdb098073,0xaca3019d}}, // nkan, ntáb, nheç, _abịd,
+ {{0x443e8273,0x1a65826a,0x7e69804f,0xb5fb0118}}, // _evt_, ایتی_, _upep, itáb,
+ {{0x27e90274,0x69c88275,0x63ad0057,0x6d418276}}, // _akan_, gide, hkan, _udla,
+ {{0xcea90158,0xcb558277,0x8fa60278,0x64410279}}, // _צי_, _مناظ, гане, šlij,
+ {{0x63ad027a,0x61e5027b,0x69c8827c,0x2619827d}}, // jkan, hohl, aide, _kèo_,
+ {{0x69c880ad,0xe1ee827e,0x200d8098,0xdddb827f}}, // bide, _гг_, _miei_, _zruš,
+ {{0x27e90280,0x63ad0281,0x237d8282,0x7bc98283}}, // _ekan_, ekan, _hawj_, lieu,
+ {{0x98ac8162,0xbb460284,0x225f8285,0x63b60286}}, // radă_, левк, ltuk_, thyn,
+ {{0x7bc98287,0xc0528051,0x63ad0288,0x62860289}}, // nieu, _הזה_, gkan, _prko,
+ {{0x225f828a,0xe1fa028b,0x63b6028c,0x6abb81ec}}, // [0e0] ntuk_, ега_, rhyn, nzuf,
+ {{0x63ad028d,0x237d822c,0x6e240214,0x7c3e0115}}, // akan, _lawj_, _itib, _tvpr,
+ {{0x11d70013,0xdca6028e,0x9c7c828f,0x70560290}}, // دولة_, _сани, _tyči, انصا,
+ {{0x69c88291,0x63ad0292,0xee3a8293,0x237d8282}}, // zide, ckan, енд_, _nawj_,
+ {{0xdddb8067,0x443e816d,0x56948162,0x6da68294}}, // _sruš, _svt_, ралт, лида,
+ {{0x443e8295,0xdddb8296,0x2816003d,0x644a8088}}, // _pvt_, _pruš, _موسس, trfi,
+ {{0x657e0297,0xb5fb0298,0x66160299,0xdd32029a}}, // _maph, ltác, _luyk, zəşt,
+ {{0x657e029b,0x7bc9829c,0x7f440168,0x69c8829d}}, // _laph, gieu, _ndiq, wide,
+ {{0x69c8829e,0xb5fb029f,0x237d81c0,0x6e2402a0}}, // tide, ntác, _dawj_, _ntib,
+ {{0xd6cf8277,0x657e0234,0x27e682a1,0xb33b02a2}}, // _عقل_, _naph, loon_, _moça,
+ {{0x63ad02a3,0x0c2682a4,0xdddb82a5,0x6b9b82a6}}, // ykan, _смен, _uruš, bjug,
+ {{0x27e682a7,0x7bc980e7,0x7a868019,0x644101a9}}, // noon_, cieu, _مشتم, šlik,
+ {{0xb8eb82a8,0x199482a9,0x78ba8019,0x657e0234}}, // _रे_, _валя, sztv, _baph,
+ {{0x27e682aa,0x2d9c80f1,0x63a28074,0x2a79018e}}, // hoon_, njve_, _hoon, _rssb_,
+ {{0x63a282ab,0xf36702ac,0x63ad02ad,0x27e682ae}}, // _koon, лтан, tkan, koon_,
+ {{0x764b8286,0x25a102af,0x63a282b0,0x67f600e1}}, // yrgy, _wohl_, _joon, pája,
+ {{0x6d5a82b1,0x65ab0074,0x27e681b4,0x38c90019}}, // [0f0] _octa, _lühi, doon_, ھائی_,
+ {{0x63ad02b2,0x63a282b3,0x26188105,0x2bc602b4}}, // skan, _loon, _पढ़ी_, रोबा,
+ {{0x44ce8086,0x27e682b5,0x63ad02b6,0xb5fb02b7}}, // _nə_, foon_, pkan, ptáb,
+ {{0x63a282b8,0xbddb00e7,0x27e682b9,0x61fc01ec}}, // _noon, ssèd, goon_, _ehrl,
+ {{0xd9f68076,0xb5fb02ba,0x6b9b826c,0x657e0234}}, // ुगात_, ctác, vjug, _yaph,
+ {{0xdcfa82bb,0xdca582bc,0x9f42016b,0x3a2900ee}}, // _altı, иали, roké_, mpap_,
+ {{0x63a282bd,0x290502be,0x9f42026f,0x27e681b4}}, // _boon, _àla_, soké_, boon_,
+ {{0x44ce82bf,0xc24582c0,0x225f8061,0x2619827d}}, // _də_, аник, ttuk_, _tèo_,
+ {{0x63a282c1,0x7bc982c2,0x6d4502c3,0x7e6d02c4}}, // _doon, rieu, _odha, _npap,
+ {{0xb5fd803b,0x7bc982be,0x7e7b82c5,0xaca3026b}}, // _opši, sieu, _isup, _abọd,
+ {{0x7e6d02c6,0xe8f682c7,0x63a282c8,0x225f82c9}}, // _apap, иль_, _foon, stuk_,
+ {{0x6d4502ca,0x8e8602cb,0x657e02cc,0x63a281b4}}, // _adha, агме, _saph, _goon,
+ {{0x7c2502cd,0x69c702ce,0x44258088,0x2bb880f7}}, // _athr, _omje, _htl_, _حالة_,
+ {{0x63a282cf,0x44ce811c,0x291f02d0,0x27e682d1}}, // _zoon, _yə_, _şuan_, zoon_,
+ {{0x63a282d2,0x4174803d,0xab5d81b9,0x88838019}}, // _yoon, _دانس, viżj, _ایجن,
+ {{0x032602d3,0x65b001ec,0x657e0234,0x7e7b82d4}}, // рдан, _zähl, _waph, _osup,
+ {{0x3f9202d5,0x6a830087,0x98b102d6,0x442582d7}}, // [100] _inyu_, олта, _dezč_, _ltl_,
+ {{0x27e682d8,0x2a6002d9,0xd94602da,0x628402db}}, // woon_, rtib_, режи, rvio,
+ {{0x7e7b802e,0xe73a82dc,0x27e682dd,0x67f601ac}}, // _asup, вее_, toon_, nájo,
+ {{0xd62a82de,0x753c0035,0xc6a682df,0xb6cb8019}}, // воде_, larz, ирли, ھانے_,
+ {{0x27e682e0,0x63a282e1,0x644182e2,0xfe7282e3}}, // roon_, _roon, _avli, تدا_,
+ {{0xbebb020f,0x27e682e4,0x224002e5,0x798181c0}}, // mbët, soon_, msik_, jdlw,
+ {{0x442582e6,0x63a282e7,0x224002e8,0x7e7b82e9}}, // _ctl_, _poon, lsik_, _esup,
+ {{0x44ce82ea,0xafe382eb,0x7bd602ec,0x67f880e1}}, // _və_, _досл, nnyu, bíja,
+ {{0x753c009a,0x6441807e,0x201802ed,0xe57a82ee}}, // karz, _evli, _huri_, _иза_,
+ {{0x1daa82ef,0x201802f0,0x63a282d8,0x65ab02f1}}, // _कवित, _kuri_, _woon, _tühi,
+ {{0x201802f2,0x63a282f3,0x753c009a,0xdcfa8176}}, // _juri_, _toon, darz, _katč,
+ {{0x201802f4,0xc4778051,0x224002f5,0x65b002af}}, // _muri_, _כתבו_, ksik_, _wähl,
+ {{0x95cb00b3,0x539b82f6,0x6d4502f7,0x442580e5}}, // вува_, _שידו, _pdha, _ztl_,
+ {{0x6d5882f8,0xdb0d0187,0x6703016f,0x753c02f9}}, // ngva, lhaç, रेषक_, garz,
+ {{0x201802fa,0xb21b013c,0x8bc702fb,0x442502fc}}, // _nuri_, _træk, асад, ël_,
+ {{0x69c702fd,0xf77f0187,0x7e6282fe,0x473502ff}}, // _smje, moço_, ktop, _унис,
+ {{0x7e628042,0x20180300,0x2fc90229,0x7c2d8301}}, // [110] jtop, _auri_, _imag_, ćari,
+ {{0xdce181e2,0x201802a0,0x673d0302,0x6d450303}}, // _galė, _buri_, lasj, _udha,
+ {{0x20180304,0x4dda01c6,0x67030305,0x7e7b8306}}, // _curi_, _תחתו, रेरक_, _ssup,
+ {{0x9f058307,0x44258308,0x64550309,0x61e8803e}}, // موضو, _rtl_, muzi, hodl,
+ {{0x6455030a,0x7bcd0110,0xd244002e,0x2d810074}}, // luzi, miau, _мэри, _kahe_,
+ {{0x69c7030b,0x2d83030c,0x7bcd01e2,0x5187030d}}, // _umje, ndje_, liau, _тума,
+ {{0x2018030e,0xf773830f,0x673d0310,0x64550311}}, // _guri_, _وار_, kasj, nuzi,
+ {{0x224d8312,0x7e7b809c,0x7bcd0110,0x3219016b}}, // trek_, _tsup, niau, _kusy_,
+ {{0x20180313,0xa3af0006,0x6aad0314,0x27ed8315}}, // _zuri_, _कवन_, nyaf, _iken_,
+ {{0x224d811e,0x64550316,0x3f800317,0x2d8300f3}}, // rrek_, kuzi, _saiu_, jdje_,
+ {{0xe73a0318,0x600a0319,0x480a031a,0x7bcd031b}}, // лен_, лном_, леон_, kiau,
+ {{0x6455031c,0x25a5831d,0x753c009a,0x224d831e}}, // duzi, _holl_, warz, prek_,
+ {{0xa969831f,0x21698320,0x25a5816d,0x4ac70321}}, // _била_, _били_, _koll_, _रेलव,
+ {{0x2d8302fd,0x63bb8322,0x2fc90162,0x41268323}}, // gdje_, lhun, _emag_, _лошо_,
+ {{0x27ff8324,0x27ed8325,0xa34a0098,0x2d810326}}, // _ohun_, _oken_, лзва_, _dahe_,
+ {{0xb4e80327,0x7bcd0110,0x3f920101,0x20180314}}, // _बडी_, giau, _unyu_, _ruri_,
+ {{0xe2970328,0x17570039,0xb1460329,0x2018032a}}, // [120] бат_, _הספר_, йнал, _suri_,
+ {{0x645c832b,0x2018032c,0x6455032d,0x2240032e}}, // _årig, _puri_, buzi, rsik_,
+ {{0x6c860013,0x27ff832f,0x64550330,0x63bb805d}}, // _الجم, _bhun_, cuzi, khun,
+ {{0x44440331,0x27ff8332,0xb5fb0333,0x7bcd0334}}, // _iv_, _chun_, ntán, ciau,
+ {{0x63bb8335,0x44440336,0x20180337,0xb5fb0118}}, // dhun, _hv_, _wuri_, itán,
+ {{0x6b840338,0x20180339,0x27ed833a,0x3f80833b}}, // ldig, _turi_, _eken_, žiu_,
+ {{0x7e62833c,0x31cd80c8,0x628b833d,0x44440077}}, // stop, রকাশ, _argo, _jv_,
+ {{0x63bb833e,0xd544826b,0x3a26833f,0x2d830340}}, // ghun, _bẹ̀w, _ptop_, zdje_,
+ {{0x44440341,0x67d20341,0xa5bb00f7,0xd13080f7}}, // _lv_, nāju, spói, ومة_,
+ {{0x44440342,0x361b8039,0x2fc90343,0x67240344}}, // _ov_, _עובד, _smag_, _afij,
+ {{0xd90d803d,0x673d00e8,0x2d83026c,0x63bb8345}}, // نیه_, tasj, vdje_, bhun,
+ {{0x63bb8346,0x753a80f3,0xf8b30039,0x61e88347}}, // chun, _hetz, פשר_, sodl,
+ {{0x44440348,0x673d00e8,0x6b840349,0x2d81008e}}, // _av_, rasj, ddig, _pahe_,
+ {{0x753a834a,0x6455034b,0xb21b013c,0x4444034c}}, // _jetz, tuzi, _kræv, _bv_,
+ {{0x7bcd034d,0x63a6034e,0x2d83034f,0x32190350}}, // tiau, _bokn, rdje_, _susy_,
+ {{0x44440351,0x753a8352,0x64550098,0xddc40353}}, // _dv_, _letz, ruzi, _vpiš,
+ {{0x6d488354,0x7bcd01e2,0x6e360355,0x67d201a9}}, // [130] _odda, riau, _gwyb, gāju,
+ {{0x753a82af,0x213e8229,0x6b828356,0x44440357}}, // _netz, nath_, _laog, _fv_,
+ {{0xddc4003a,0x63bb8358,0x6aad0359,0x27ff80ff}}, // _upiš, yhun, syaf, _phun_,
+ {{0x25a58352,0x64c3835a,0x6d48835b,0x6b82835c}}, // _soll_, _वेगळ, _adda, _naog,
+ {{0xb5fd835d,0x25a5835e,0x9599835f,0x2c618035}}, // _opšt, _poll_, итку_, wód_,
+ {{0x4444011e,0x7afd0360,0x2bb18361,0x6b828362}}, // _yv_, _izst, जस्थ, _aaog,
+ {{0x44440363,0x65b00364,0x63bb805d,0x25a581ec}}, // _xv_, _sähk, thun, _voll_,
+ {{0x2c61809a,0x6d488365,0x27ed804a,0x628b81f4}}, // ród_, _edda, _uken_, _prgo,
+ {{0x60250110,0xb21b0366,0x25a58367,0x7d000032}}, // _dėme, _usæd, _toll_, _kárí,
+ {{0x63bb8368,0x628b8369,0x776d036a,0x3eb2804a}}, // shun, _vrgo, _abax, øyte_,
+ {{0xc952036b,0x6b84036c,0xa3e9036d,0x673b836e}}, // ומי_, ydig, मका_, _keuj,
+ {{0x628b836f,0xe3b381bd,0x661b8364,0x64438370}}, // _trgo, _عرض_, _kuuk, msni,
+ {{0x61458371,0x63a60372,0x69cf8373,0xd9458374}}, // _дела, _rokn, éces, _дели,
+ {{0x67d200eb,0xb5fb0375,0x6d410376,0x63a60377}}, // vāju, stán, ólap, _sokn,
+ {{0x8fa60003,0xd5e4019d,0xdb1d016d,0x64438378}}, // _маке, _arị, _omsä, nsni,
+ {{0x44440379,0x67d20029,0x6b84037a,0x333f80e7}}, // _vv_, tāju, udig, naux_,
+ {{0x9f5f8028,0x65b0037b,0x7983837c,0xdce381d0}}, // [140] _thuê_, _lähi, _kanw, teně,
+ {{0xb5fb037d,0x4444037e,0x6b84037f,0xa7fd0380}}, // ntál, _tv_, sdig, ntın,
+ {{0xef1a8381,0xdce18029,0x79838382,0xdb060168}}, // аме_, _dalī, _manw, _kokë,
+ {{0xf1bf0065,0x6d5c0383,0x753a8384,0xbddb0385}}, // _után_, lgra, _setz, nsèn,
+ {{0x25e68076,0x3e640386,0x2b400387,0x644100e1}}, // टवली_, nöt_, maic_, šlit,
+ {{0x27388028,0x67228388,0x6b828014,0xc7b98061}}, // ẩn_, scoj, _saog, _elő_,
+ {{0xe73a8389,0x2636809a,0x68300032,0x6e29838a}}, // _веб_, sło_, _dòdò, _eteb,
+ {{0x66e6838b,0xdee6838c,0x0b8a838d,0x7983838e}}, // _доба, _доби, рски_, _aanw,
+ {{0x7983838f,0x386d0390,0xed5a8391,0xdfcf8174}}, // _banw, _ćeri_, _воз_, _شيك_,
+ {{0x6d48813c,0x6d5c0392,0x3f84804f,0x23b88036}}, // _udda, jgra, _hamu_, _déja_,
+ {{0x3f848393,0x213e8394,0x6d5c0395,0xd7fb0396}}, // _kamu_, rath_, dgra, иум_,
+ {{0xa2cb835a,0x3f848397,0x333f80e7,0x442e8398}}, // _तेव्, _jamu_, caux_, mpf_,
+ {{0xdefb0196,0x64588399,0x22948081,0x79838286}}, // рын_, luvi, _хиля, _fanw,
+ {{0x69ca839a,0x6d5c039b,0x2918039c,0xe2a98180}}, // _umfe, ggra, _ngra_, _دامن_,
+ {{0x5ba7039d,0xe0d480be,0x602181a9,0x3b280133}}, // ораз, _מײַ_, _tēma, _ọyị_,
+ {{0x6d5c0013,0x3f84839e,0xdce180eb,0xdb0f0333}}, // agra, _namu_, _salī, _alcá,
+ {{0x1ddb81fe,0xdce18029,0x2bbc0076,0x201c82f1}}, // [150] _बदलत, _palī, _ईतरा, _huvi_,
+ {{0x69da839f,0x64438370,0x645883a0,0x7afd808b}}, // nnte, ysni, kuvi, ýsti,
+ {{0x65ab02af,0x6e2983a1,0xb5fb03a2,0x3f8483a3}}, // _führ, _steb, ntám, _bamu_,
+ {{0xd47a0158,0xa3af03a4,0xb5fb0019,0x2cac0106}}, // _פארל, _कवि_, ztál, ädde_,
+ {{0x333f82be,0x7afd00eb,0x3f8483a5,0x539a0039}}, // vaux_, _uzst, _damu_, _הירו,
+ {{0x644383a6,0x201c83a7,0xdb0403a8,0xa7fd0085}}, // tsni, _ouvi_, chiñ, xtın,
+ {{0x201c8358,0x333f80e7,0x21ef01ec,0x9f3583a9}}, // _nuvi_, taux_, bühr_, _неві,
+ {{0x69da83aa,0x644383ab,0x79838091,0x3a3903ac}}, // ente, rsni, _sanw, _kwsp_,
+ {{0x644380f2,0x6d5c03ad,0x798382f7,0x333f80e7}}, // ssni, ygra, _panw, raux_,
+ {{0x644381e2,0x3f8483ae,0x65b00074,0x69da83af}}, // psni, _zamu_, _tähi, gnte,
+ {{0xb5fb03b0,0x7c2d83b1,0x6d5c0289,0x798383b2}}, // rtál, ćars, vgra, _vanw,
+ {{0xb5fb03b3,0x2d8583b4,0xbebb0168,0x201c802a}}, // stál, _hale_, rcën, _duvi_,
+ {{0x2d8583b5,0xa2cb03b6,0xcaea03b7,0x19958081}}, // _kale_, _सेल्, _झुंड_, _надя,
+ {{0x2d8583b8,0xda1f0105,0x6d5c03b9,0x2d8783ba}}, // _jale_, _बढ़त_, ugra, ndne_,
+ {{0x9f9203bb,0x64a603bc,0xdb060168,0x224682d4}}, // lší_, чава, _tokë, _zvok_,
+ {{0x422603bd,0x225203be,0xa7fd03bf,0x65618122}}, // здав, tryk_, ntıl, _lclh,
+ {{0x27e003c0,0x6e4600f7,0x9f9203c1,0x6d5c03c2}}, // [160] _ijin_, _عندم, nší_, pgra,
+ {{0x3f8483c3,0x2b4003c4,0xb46603c5,0x2d8581f6}}, // _samu_, saic_, _екол, _nale_,
+ {{0xe1ff00a9,0x764603c6,0x9d1b00be,0x628f01e8}}, // _após_, lsky, _לויט, _erco,
+ {{0x205503c7,0xdc9b03c8,0x3f8683c9,0x6ecd83ca}}, // ьтур, _היטל, udou_, _देहु,
+ {{0x9f9203cb,0x764603cc,0x3f8683cd,0xa3e900d4}}, // jší_, nsky, rdou_, मकर_,
+ {{0x645883ce,0x63b603cf,0xa6db007b,0x2d8583d0}}, // tuvi, lkyn, naða, _cale_,
+ {{0x80d080c8,0x69ce03d1,0x3f8483d2,0xddc98162}}, // _সুন্, _imbe, _tamu_, _speţ,
+ {{0x69dd83d3,0x63a403d4,0x645883d5,0xb21b0366}}, // ésen, njin, ruvi, _græs,
+ {{0x2d8583d6,0x201c83d7,0x7d028032,0x6b8603d8}}, // _fale_, _suvi_, _bírí, _makg,
+ {{0x764603d9,0x69da83da,0x27e00300,0x7d070036}}, // dsky, unte, _ajin_, néré,
+ {{0x69ce00dd,0x261583db,0x69da83dc,0xf99301c6}}, // _mmbe, _फूटी_, rnte, גרת_,
+ {{0x09b903dd,0x6028801b,0x78fb03de,0x2d8583df}}, // _इत्य, _něme, מפיו, _zale_,
+ {{0x69ce03e0,0x443a03e1,0x9f4b0216,0x764603e2}}, // _ombe, _awp_, nocé_, gsky,
+ {{0x7afd83e3,0x9f42016b,0x7d02826b,0x442c83e4}}, // üste, roká_, _gírí, _ktd_,
+ {{0x9f42026f,0x6b8603e5,0x7d0703e6,0x628f00c3}}, // soká_, _bakg, déré, _srco,
+ {{0x27e003e7,0x63a400f1,0xa3bc03e8,0x087783de}}, // _gjin_, gjin, _अतः_, _זעהט_,
+ {{0x3edf0135,0xdcf200eb,0x6b8603e9,0x442c81c6}}, // [170] _kpọọ_, īgāk, _dakg, _ltd_,
+ {{0x6e2d03ea,0xceb200be,0xaca301bc,0xbba983eb}}, // _itab, _סיי_, _asụg, _छक्क,
+ {{0xb21b013c,0x2d8583ec,0x673f03ed,0xa3be8035}}, // _præs, _rale_, _keqj, ीफा_,
+ {{0x69ce03ee,0x64550289,0x2d8583ef,0x256f807e}}, // _embe, mrzi, _sale_, zılı_,
+ {{0xd2468065,0x644883f0,0x442c83f1,0x256f8214}}, // _جن_, _avdi, _atd_, yılı_,
+ {{0xcb120051,0x645501ed,0x9f9203f2,0x68f500e1}}, // _שלך_, orzi, vší_, _tyzd,
+ {{0x2d8583f3,0xac09827e,0x628283f4,0x2d8783f5}}, // _vale_, онка_, _asoo, rdne_,
+ {{0x2d8583f6,0x9f92001b,0x6e2d03f7,0x395e80eb}}, // _wale_, tší_, _otab, egts_,
+ {{0xfe7083f8,0x2d8583f9,0x2a6903fa,0x6e2d02a0}}, // _مدل_, _tale_, ntab_, _ntab,
+ {{0x9f9203fb,0x3ce8809a,0x27f203fc,0xab5b0192}}, // rší_, _चुके_, _skyn_, _flüg,
+ {{0x6d4383fd,0xd37180f7,0xdddb805c,0x6e2d03fe}}, // mana, _لها_, _kruž, _atab,
+ {{0x65b003ff,0x9f920400,0x2a690401,0x7bc08402}}, // _läht, pší_, ktab_, chmu,
+ {{0xb8f48403,0xa6db008b,0x443a0404,0xdcfa8162}}, // _से_, taða, _swp_, _tată,
+ {{0x79870063,0x76460405,0x65b00406,0x273c0028}}, // _najw, rsky, _näht, ận_,
+ {{0xdddb8025,0x6e2d0407,0x76460408,0x69c18409}}, // _oruž, _etab, ssky, mhle,
+ {{0x6d43840a,0x6b89840b,0x6f1b840c,0xeb97040d}}, // hana, ndeg, _nguc, мис_,
+ {{0x59e1824c,0xdb0b840e,0x63ab840f,0x63a680f2}}, // [180] _पदार, _algú, _mogn, ökni,
+ {{0x6d438410,0x69c18234,0x63b60411,0x63a40412}}, // jana, nhle, skyn, sjin,
+ {{0x6d438413,0x657c0414,0x628b0106,0x7bc78084}}, // dana, merh, ågor, _įjun,
+ {{0x645c0415,0xdd8f8416,0x657c0417,0x3fbe80ab}}, // muri, یون_, lerh, েক্ষ,
+ {{0x6d438418,0xdddb8419,0x4cd300ab,0x7988831d}}, // fana, _druž, _দুপু, yddw,
+ {{0x69ce041a,0x657c02af,0x442c83ac,0x63ab8362}}, // _umbe, nerh, _ptd_, _aogn,
+ {{0x645c041b,0x442c841c,0x69c1841d,0x644d81a9}}, // nuri, _qtd_, dhle, ņain,
+ {{0x63ab841e,0xdddb841f,0x6d438420,0x6b898421}}, // _cogn, _gruž, aana, gdeg,
+ {{0x657c0422,0x645c0423,0xe7bd8424,0x52148425}}, // kerh, huri, ्फरप, ндит,
+ {{0x645c0426,0x442c808e,0x69c18427,0x657c0144}}, // kuri, _ttd_, ghle, jerh,
+ {{0x39570051,0x6e20820f,0x79888428,0x63ab8429}}, // ושים_, _humb, rddw, _fogn,
+ {{0x6e20842a,0x628281c5,0xe29a81e2,0x26c681c0}}, // _kumb, _tsoo, _мае_, ozoo_,
+ {{0xdce881e2,0xdde2042b,0x6e20842c,0x26c68144}}, // _padė, _šušk, _jumb, nzoo_,
+ {{0xceb38158,0xe0cf8416,0x645c042d,0x69c1842e}}, // דיש_, ازی_, furi, chle,
+ {{0x67d5042f,0x6e208430,0xb5fb00f7,0xdb060019}}, // _поку, _lumb, ntái, _elkü,
+ {{0x6d438431,0x2a690432,0x2cac0106,0x7c220088}}, // zana, ttab_, ädda_, _čort,
+ {{0x657c0433,0x6e208434,0x6e2d0435,0x65950436}}, // [190] berh, _numb, _utab, _запу,
+ {{0x645c0437,0x6d438438,0x765d0439,0x4905801b}}, // buri, xana, musy, हेको_,
+ {{0xdddb812b,0x2a690006,0x6d43843a,0x765d043b}}, // _pruž, stab_, vana, lusy,
+ {{0x6d43843c,0x2a690242,0x93c90019,0xef1982a6}}, // wana, ptab_, _راجہ_, _każi_,
+ {{0xcb360051,0x65b0043d,0x6e20843e,0x6d43843f}}, // _ראשי_, _täht, _cumb, tana,
+ {{0x63ab8440,0xa2bf0441,0x9f4d8229,0x44210442}}, // _sogn, लैण्, _cheò_, _kuh_,
+ {{0x6d438443,0x442101d6,0x22490444,0x67298197}}, // rana, _juh_, lsak_, _tfej,
+ {{0x6d438445,0x65b002af,0x765d0446,0x26c6822c}}, // sana, _jähr, kusy, bzoo_,
+ {{0x6d438447,0x645c8448,0x6b898449,0x6e20844a}}, // pana, _årin, rdeg, _gumb,
+ {{0x645c044b,0x6d43844c,0x9f4d8362,0x6b89844d}}, // yuri, qana, _gheò_, sdeg,
+ {{0x657c044e,0xe8f9844f,0x6d4180eb,0x67248450}}, // verh, яло_, _iela, žije,
+ {{0xf0920451,0x6d418452,0x22490453,0x657c0454}}, // אנט_, _hela, ksak_, werh,
+ {{0x6d418455,0x657c0456,0x3e6980e1,0xe3c20457}}, // _kela, terh, nút_, nlış_,
+ {{0x6d418458,0x92c18459,0x9aa4845a,0xdd92045b}}, // _jela, ığın, _جمهو, دوس_,
+ {{0x6d41845c,0xfce3045d,0xddcd0087,0x6603045e}}, // _mela, горо, _spaţ, упра,
+ {{0x6d41845f,0xa2d48076,0x645c0460,0x765d0461}}, // _lela, _बेड्, ruri, busy,
+ {{0xf41280be,0x22490462,0x02060463,0xdb060242}}, // [1a0] רפן_, gsak_, езан, _alkò,
+ {{0x6d418464,0x645c0465,0xe889801c,0x442100ee}}, // _nela, puri, _kẻ_, _fuh_,
+ {{0x6e208397,0x65b001ec,0xdddb8035,0x21f40192}}, // _sumb, _fähr, _druż, fähr_,
+ {{0xa2cb0466,0xfaa60467,0xfe468468,0xe6668469}}, // _सेक्, _замо, ендо, етко,
+ {{0xe889801c,0x394580eb,0x98ac0085,0x91b48035}}, // _lẻ_, kals_, ğlıq_, ेसमै,
+ {{0xe5a3846a,0x6d4182ad,0x7bd6046b,0x98ac0085}}, // личи, _cela, liyu, şlıq_,
+ {{0x6d41846c,0x8d1a8065,0xdbe3046d,0x26c681c0}}, // _dela, _ہزار_, _béèn, szoo_,
+ {{0xb5fb00f7,0x6d4182f1,0x51200054,0x8aa6846e}}, // rtái, _eela, _बलुआ_, ерод,
+ {{0xb5fb0013,0x6d41846f,0x66e38470,0x6aa40118}}, // stái, _fela, _бота, nxif,
+ {{0x64a68471,0xb5ca8065,0x39458472,0x7afd8085}}, // _паза, _عوام_, gals_, üsta,
+ {{0x815700be,0x3f890473,0xdb12007b,0x7c218474}}, // עסטע_, _paau_, _ágæt, _gulr,
+ {{0xfa888142,0x6d418102,0x765d0475,0xb5fb0476}}, // _từ_, _zela, tusy, ntáv,
+ {{0x42db00ab,0x7d278106,0x44210477,0x6d418478}}, // _দর্শ, _årså, _suh_, _yela,
+ {{0x39458479,0x661a847a,0x9f4b001b,0x4421047b}}, // cals_, _jitk, mocí_, _puh_,
+ {{0x317e80cd,0x6286047c,0xb3a4847d,0x7bd6008e}}, // metz_, _asko, _खचाख, fiyu,
+ {{0x7bc4047e,0x7bdf047f,0x22490480,0xdcf88088}}, // ghiu, unqu, tsak_, jevč,
+ {{0x66d38065,0x9f4b0388,0x443e82f7,0xd2510481}}, // [1b0] _műkö, nocí_, _lwt_, شنا_,
+ {{0x661a8482,0x44210483,0xd8e70484,0x65b002af}}, // _nitk, _tuh_, нцеп, _währ,
+ {{0x62860485,0x6d470486,0x20090028,0x22490487}}, // _esko, maja, _khai_, ssak_,
+ {{0x6d41845c,0x6d470393,0x200901c0,0x7bc40488}}, // _sela, laja, _jhai_, chiu,
+ {{0x661a8489,0x6b9d048a,0xdb0b80f1,0x69dd8019}}, // _bitk, _insg, _vogë, ések,
+ {{0x6d47048b,0xb90700c8,0xdce88029,0x443e848c}}, // naja, _পর_, _gadī, _bwt_,
+ {{0x63af02a5,0xab5b02af,0x80d080ab,0x644a848d}}, // _kocn, _glüc, _সুস্, gsfi,
+ {{0xe8898028,0x69d701df,0x6d470353,0x6d41848e}}, // _rẻ_, lixe, haja, _wela,
+ {{0x6d41848f,0x39458490,0xe8898028,0x63af0491}}, // _tela, tals_, _sẻ_, _mocn,
+ {{0x6d470492,0x27fd8428,0x798a8428,0x93bc8493}}, // jaja, llwn_, _cafw, _stăt,
+ {{0x39458494,0x6d470495,0x2fc001e9,0xf9920496}}, // rals_, daja, _plig_, ברי_,
+ {{0x39458497,0xe8898028,0x20090498,0x6b8d01ed}}, // sals_, _vẻ_, _chai_, jdag,
+ {{0x63a9830b,0x6b8d03a6,0x93b68051,0xddcd0499}}, // mjen, ddag, _שלנו_, _spaš,
+ {{0xceb2804c,0x6d47049a,0x7bd600b4,0x65638129}}, // סים_, gaja, wiyu, ngnh,
+ {{0xdce880eb,0xddc280eb,0x6b8b849b,0x443e049c}}, // _radī, ntoš, _kagg, èt_,
+ {{0x63a9849d,0x63bb849e,0x6b8d049f,0x087680be}}, // njen, nkun, gdag, _יעצט_,
+ {{0x6b8b84a0,0x6d4704a1,0x3951026c,0x4b2300e8}}, // [1c0] _magg, baja, _bdzs_, рмув,
+ {{0x6d470025,0x63a984a2,0xb65b007c,0x00da04a3}}, // caja, hjen, _קדיש, وبات_,
+ {{0x63bb84a4,0xdce88029,0x63a984a5,0x661a84a6}}, // kkun, _vadī, kjen, _ritk,
+ {{0x6b8b84a7,0x70530077,0x6e24004f,0x7bd600dd}}, // _nagg, شنها, _kuib, qiyu,
+ {{0x63a984a8,0x628604a9,0x661a8364,0x443e84aa}}, // djen, _usko, _pitk, _swt_,
+ {{0xb5fb04ab,0x69c504ac,0xd49a84ad,0x8d6384ae}}, // stáv, chhe, дри_, авље,
+ {{0x6b8b84af,0xb5fb001b,0xcbe684b0,0x2cb80362}}, // _bagg, ptáv, кции, nyrd_,
+ {{0x63a984b1,0x63bb84b2,0x09da8072,0x61fe04b3}}, // gjen, gkun, यच्य, llpl,
+ {{0xd00f8013,0xfbd30051,0x2009031d,0x6b8b84b4}}, // _ذلك_, יתה_, _rhai_, _dagg,
+ {{0xa3f5800d,0x661a837a,0xb4d2016f,0x200900fc}}, // ější_, _uitk, _वधू_, _shai_,
+ {{0x2d9e84b5,0x6d4704b6,0x63a984b7,0xd1388110}}, // _inte_, vaja, bjen, rbą_,
+ {{0x2d8c84b8,0x317e81ec,0x6b8b84b9,0x63bb82af}}, // _hade_, setz_, _gagg, ckun,
+ {{0x6d4704ba,0x2d8c84bb,0x6e2404bc,0xc9aa84bd}}, // taja, _kade_, _cuib, _овде_,
+ {{0x2d8c84be,0xea778039,0xa4d500e8,0x63af01f4}}, // _jade_, _סגור_, гогі, _socn,
+ {{0x6d4704bf,0x63af0052,0x80a384c0,0x6b8d03b2}}, // raja, _pocn, _نمون, tdag,
+ {{0x5f7704c1,0x2d8c84c2,0x6b8b8079,0x2009004f}}, // _شاعر, _lade_, _xagg, _uhai_,
+ {{0x2d9e84c3,0x6d4704c4,0x63af025b,0xf8e284c5}}, // [1d0] _onte_, paja, _vocn, _पशुप,
+ {{0x6b8d03a6,0x63bb80ad,0x68fc04c6,0x80be0264}}, // sdag, zkun, _byrd, ্পন্,
+ {{0x44270193,0x3eb904c7,0xc953810f,0x69d704c8}}, // _în_, lyst_, שמע_, rixe,
+ {{0x2d9e84c9,0x44330183,0xb4c1009a,0x7659831d}}, // _ante_, _itx_, ्छी_, trwy,
+ {{0x6d4504ca,0x6b8b84cb,0x4ea78081,0x6b8084cc}}, // _keha, _ragg, ържа, demg,
+ {{0x6d4504cd,0xf2d20051,0x7bc284ce,0xa93404cf}}, // _jeha, _פעם_, _alou, _верш,
+ {{0x63a984d0,0x7afd04d1,0x6b8b84d2,0x7bc284d3}}, // tjen, _hyst, _pagg, _blou,
+ {{0x2d9e84d4,0xddc284d5,0x7afd04d6,0x7bc284d7}}, // _ente_, rtoš, _kyst, _clou,
+ {{0x3f9f82c1,0x63a984d8,0x7bc2800d,0xee8484d9}}, // _inuu_, rjen, _dlou, _выхо,
+ {{0x63bb84da,0xdcbb8021,0x2d8c8247,0x5fb9835a}}, // skun, _още_, _gade_, _आवडल,
+ {{0x2dd880f7,0xf74604db,0x7afd04dc,0x046784dd}}, // _شبكة_, лено, _lyst, _штам,
+ {{0xdb0f0073,0xc43a84de,0x7ae48289,0x44258087}}, // _você, _נתני, _žite, _iul_,
+ {{0x442584df,0x6d4504e0,0x7afd04e1,0x69d504e2}}, // _hul_, _beha, _nyst, _omze,
+ {{0x88c584e3,0x442584e4,0x2bcf84e5,0x7bc28067}}, // _متعل, _kul_, तोरा, _zlou,
+ {{0x224d84e6,0x7981805d,0x442584e7,0x987980be}}, // lsek_, melw, _jul_, _טאַט,
+ {{0x7afd04e8,0x2d8c04e9,0x44258006,0x7981805d}}, // _byst, žde_, _mul_, lelw,
+ {{0x7afd0114,0xdcfc026f,0x442584ea,0x6d450144}}, // [1e0] _cyst, merč, _lul_, _feha,
+ {{0x6d4504eb,0x7afd04ec,0x3f8204ed,0x798184ee}}, // _geha, _dyst, leku_, nelw,
+ {{0xdb06003e,0x2d8c84ef,0xdce38110,0xb5fb04f0}}, // _doká, _rade_, menė, rtát,
+ {{0xb5fb04f1,0x2d8c84f2,0x6d45011e,0x224d8214}}, // gráf, _sade_, _zeha, ksek_,
+ {{0x2d8c84f3,0x7981805d,0x7f3b00be,0x7afd0114}}, // _pade_, kelw, גענו, _gyst,
+ {{0x7bc2801b,0x442584f4,0xd5b1801c,0x7e7b808e}}, // _slou, _bul_, _có_, _dpup,
+ {{0x442584f5,0x5b2684f6,0x2d8c8214,0x7bc284f7}}, // _cul_, льпа, _vade_, _plou,
+ {{0xb5fb04f8,0x442584f9,0x3f820499,0x539884fa}}, // ntár, _dul_, jeku_, твия_,
+ {{0x2d8c84fb,0xbebb0168,0x224d82c4,0xdb060061}}, // _tade_, ncët, gsek_, _elkö,
+ {{0xc05a8221,0x602180eb,0x442584fc,0x79818114}}, // нім_, _lēmu, _ful_, gelw,
+ {{0x291884fd,0x98be8087,0x442584fe,0x798e04ff}}, // ära_, dată_, _gul_, _mabw,
+ {{0x6d450500,0x6d4a8501,0x644e0502,0xa6db007b}}, // _seha, lafa, dsbi, laði,
+ {{0x6d450503,0x44258504,0x645c011b,0x79818505}}, // _peha, _zul_, erri, belw,
+ {{0x44258506,0xbb1b02be,0x6d4a8507,0xa6db008b}}, // _yul_, _maît, nafa, naði,
+ {{0x6d450508,0x44258509,0xb4c1050a,0x3ebf050b}}, // _veha, _xul_, ्छे_, šut_,
+ {{0xc4c4850c,0x7afd0009,0xa3cf050d,0x246e8085}}, // _ہے_, _pyst, वों_, _cəmi_,
+ {{0x6d4a850e,0x6d450074,0x645c02f9,0x5b7b80be}}, // [1f0] kafa, _teha, arri, ערמא,
+ {{0x7afd050f,0x645a0510,0x98be802e,0xb5fb0061}}, // _vyst, átic, cată_, rtás,
+ {{0x7afd0063,0x2d8302a0,0xb5fb0511,0xd7c880d7}}, // _wyst, meje_, tráf, گونه_,
+ {{0x31e18512,0x645a011f,0x2d830513,0x7afd0514}}, // _पद्ध, štic, leje_, _tyst,
+ {{0x44258515,0x6d4a8516,0xa2d4800c,0x98aa01a9}}, // _sul_, fafa, _बेल्, ējām_,
+ {{0x44258085,0x2d830517,0xf483804e,0x6d4a8518}}, // _pul_, neje_, ماعی, gafa,
+ {{0x63ad0110,0xa7fd007e,0xa2d90519,0x7981851a}}, // ojan, ntıs, _नेत्, velw,
+ {{0x63ad051b,0x4425838e,0x98be8087,0xddc4051c}}, // njan, _vul_, zată_, _spiż,
+ {{0x69da851d,0x6b8f051e,0x6d4a851f,0x2b468176}}, // fite, _macg, bafa, _deoc_,
+ {{0x2d830520,0x44258521,0x27e90522,0x69c8808c}}, // jeje_, _tul_, _ajan_, ghde,
+ {{0xe73a0523,0x2d830524,0x63ad0525,0x66ca8019}}, // кен_, deje_, kjan, _töké,
+ {{0xc6000526,0x7981805d,0xb5fb0527,0x63ad0528}}, // ोग्य_, selw, hrád, jjan,
+ {{0x63ad0529,0x98be8087,0x2bc9052a,0xb5fb052b}}, // djan, tată_, रसना, vtár,
+ {{0x3f82052c,0x69c8852d,0x644e052e,0x7bc980e7}}, // seku_, chde, tsbi, lheu,
+ {{0x98be802e,0x645c052f,0xdd0e02d0,0x8c460530}}, // rată_, urri, _dışa, ребе,
+ {{0xc0528039,0x7bc980e7,0xdb0b8531,0x63ad0532}}, // _וזה_, nheu, _logí, gjan,
+ {{0x6d4a8533,0x661e0534,0x644e0535,0xeab70536}}, // [200] yafa, _tipk, ssbi, айт_,
+ {{0x394782fe,0xb5fb0537,0xf0a98032,0xdebb0538}}, // _jens_, stár, _gbàà_, _סמיל,
+ {{0xa3af00d4,0x64a60539,0x63ad053a,0xdca6053b}}, // _कवच_, _тана, bjan, _тани,
+ {{0x69da853c,0x6d4a8010,0x764400b4,0x6e93053d}}, // zite, wafa, _kwiy, _الیا,
+ {{0xb8fd053e,0xe8e2853f,0x23a98540,0x6d4a8541}}, // _ते_, _पश्च, कानद, tafa,
+ {{0x44440542,0x3947809f,0xb8660019,0x6b840543}}, // _kw_, _nens_, _پاسو, leig,
+ {{0x92e280c8,0x292700e1,0xa6db008b,0x03c68544}}, // _ধরে_, ávať_, raði, исим,
+ {{0x44440545,0x69da8546,0x6d4a8547,0x0fc100ab}}, // _mw_, wite, safa, _উদ্ধ,
+ {{0x444402ec,0x27e68420,0x39478548,0x2d830549}}, // _lw_, mnon_, _bens_, yeje_,
+ {{0x444402ec,0x6b84054a,0x63ad054b,0x4973854c}}, // _ow_, heig, zjan, ольс,
+ {{0x628b854d,0x7644054e,0x3947854f,0x58d880e8}}, // _esgo, _awiy, _dens_, удня_,
+ {{0x69da8550,0x394782d8,0x27e68551,0x6d488102}}, // site, _eens_, nnon_, _heda,
+ {{0x44440552,0x63a28553,0x6d4884bf,0xd6d80554}}, // _aw_, _inon, _keda, атр_,
+ {{0x6d488025,0x44440555,0x7bc60556,0x39478557}}, // _jeda, _bw_, _alku, _gens_,
+ {{0x6d488558,0x2d830559,0xf77f055a,0x63ad055b}}, // _meda, reje_, ança_, tjan,
+ {{0x7c288025,0x6d48855c,0x4444055d,0x628b8355}}, // _mudr, _leda, _dw_, _ysgo,
+ {{0x7c28855e,0x2905055f,0xadf683eb,0x717900e5}}, // [210] _ludr, _šla_, इवान_, лбар_,
+ {{0x6f098063,0x39470560,0x3947802a,0x394c8561}}, // _rzec, úns_, _xens_, nads_,
+ {{0x44440562,0x63ad0563,0x63a28564,0x6b8401a9}}, // _gw_, pjan, _onon, beig,
+ {{0x27e68565,0x2014009f,0xdc9a03de,0x63a283f7}}, // gnon_, _èxit_, _עירל, _nnon,
+ {{0x7c288566,0x69d881eb,0x44440567,0xed598568}}, // _audr, _omve, _zw_, гол_,
+ {{0x63a28569,0x44440355,0x27e6856a,0x5f0a056b}}, // _anon, _yw_, anon_, _सरस्_,
+ {{0x4444056c,0x6d48856d,0xa6db0125,0x0574803d}}, // _xw_, _deda, maðu, تاند,
+ {{0x3947856e,0x69c7056f,0x23690570,0x1dd98571}}, // _sens_, _hlje, ngaj_, _भगवत,
+ {{0xe1f98572,0x248c8573,0xdd2c0110,0xe7308019}}, // лги_, _esdm_, vėži, _آصف_,
+ {{0x6b840352,0x6d488574,0xa6db007b,0x63a28575}}, // zeig, _geda, naðu, _enon,
+ {{0xa3e80576,0x69c702ce,0x7c288577,0x7dc200e1}}, // _बदल_, _mlje, _gudr, _dôsl,
+ {{0x44440578,0x3947837a,0x7dc201ac,0x6d570579}}, // _rw_, _wens_, _pôso, _adxa,
+ {{0x34b2057a,0x7f49857b,0x69c7057c,0xa7fd02d0}}, // ीन्द, _meeq, _olje, ttır,
+ {{0x4444057d,0x6e298264,0x6e21857e,0x28b980f7}}, // _pw_, _mueb, _jilb, _مطبخ_,
+ {{0x44440282,0x6b84057f,0xdb0b8187,0xdb1d0580}}, // _qw_, teig, _fogã, _alsà,
+ {{0xf1bf001c,0x69c1800b,0x44440581,0xfd6901bc}}, // _quán_, lkle, _vw_, _kepụ,
+ {{0x69c70582,0xa7fd0214,0x6e2982d5,0xfaa30530}}, // [220] _blje, ptır, _nueb, пато,
+ {{0x44440583,0x01d70013,0x69c18584,0x6b840585}}, // _tw_, موقع_, nkle, seig,
+ {{0x44440586,0x3f920587,0x6d4e0588,0x291c0589}}, // _uw_, _kayu_, maba, äva_,
+ {{0x6d4e058a,0x7bc6058b,0x78a30338,0x68fc8192}}, // laba, _ulku, ånva, ürdi,
+ {{0x6e21811e,0xa3ab858c,0x2b40058d,0x3f920041}}, // _bilb, खान_, mbic_, _mayu_,
+ {{0x6d4e058e,0x225f858f,0x3f920590,0xe1e78591}}, // naba, druk_, _layu_, _رس_,
+ {{0x2bbf0592,0x6d488593,0xb5fb026f,0x394c80eb}}, // _एकमा, _veda, duál, vads_,
+ {{0x69de0594,0x6d4e0595,0x9b6a8596,0x6d488597}}, // lipe, haba, ушка_, _weda,
+ {{0x6d4e0598,0x6d488599,0x7dcb0059,0x394c859a}}, // kaba, _teda, _hüse, tads_,
+ {{0x69de059b,0x6d4e059c,0x8c0000ab,0xfd6901bc}}, // nipe, jaba, ্দিন_, _depụ,
+ {{0x672d80ce,0x64a3859d,0x3f92059e,0x9f42016b}}, // žaje, _нафа, _bayu_, soký_,
+ {{0x4422059f,0xaad98105,0x69de0087,0x225f85a0}}, // _nik_, _बेवक, hipe, bruk_,
+ {{0x69de05a1,0x645a0091,0x394a05a2,0x3f9202f7}}, // kipe, átin, _oebs_, _dayu_,
+ {{0x6d4e05a3,0xf1bf05a4,0x4422016d,0x30a685a5}}, // gaba, _cuál_, _aik_, _грив,
+ {{0x645a05a6,0x61fa8580,0x69de05a7,0xc05a85a8}}, // štin, motl, dipe, _сіл_,
+ {{0x69c702fd,0x442205a9,0x61fa85aa,0xeda585ab}}, // _slje, _cik_, lotl, _गच्छ,
+ {{0x6d4e05ac,0x442205ad,0x69c70052,0x7c2285ae}}, // [230] baba, _dik_, _plje, _mior,
+ {{0x6d4e005d,0x7c228118,0x6f0d05af,0x61fa85b0}}, // caba, _lior, _izac, notl,
+ {{0x44220022,0x2fc905b1,0x660384e8,0x6d4105b2}}, // _fik_, _klag_, vlnk, ılad,
+ {{0x1da705b3,0x6e2985b4,0xdb0b8013,0x4422013c}}, // खावत, _pueb, _logá, _gik_,
+ {{0x7bdf05b5,0x7bcd05b6,0x61fa85b7,0x6e218176}}, // miqu, mhau, kotl, _pilb,
+ {{0x7bdf05b8,0x628f0197,0xb5fb05b9,0x69de05ba}}, // liqu, _isco, xuál, cipe,
+ {{0xddcc01e2,0x224680ee,0x442200ee,0x7c2285bb}}, // _žiūr, _awok_, _yik_, _bior,
+ {{0x23c905b3,0x7bdf05bc,0x6d4e05bd,0xa91c8038}}, // रसिद, niqu, zaba, teľn,
+ {{0x6e2185be,0xb5fb05bf,0x7c2285c0,0x3e72802e}}, // _tilb, tuál, _dior, cât_,
+ {{0x80be00c8,0x8d778416,0x3f9205c1,0xe9d985c2}}, // ্পর্, _دارا, _sayu_, рко_,
+ {{0x7c228098,0x2fc905c3,0x15fa009a,0x6f0d05c4}}, // _fior, _blag_, ृतसर_, _azac,
+ {{0x7c228021,0xdd0e85c5,0x6d4e05c6,0x2d8785c7}}, // _gior, lışm, waba, lene_,
+ {{0x6d4e05c8,0x7bdf05c9,0xf8a6016f,0x7bcd05ca}}, // taba, diqu, _गप्प, dhau,
+ {{0x442205cb,0x2d8785cc,0xdb0f002a,0xdd0e83bf}}, // _sik_, nene_, _cocí, nışm,
+ {{0x6d4e05cd,0x7bdf05ce,0x628f0098,0x629d05cf}}, // raba, fiqu, _asco, _arso,
+ {{0x7bdf0557,0xb5fb05d0,0x2d8785d1,0x3f868118}}, // giqu, trác, hene_, xeou_,
+ {{0x2d8785d2,0xe9da05d3,0x6d4e05d4,0xb5fb00e1}}, // [240] kene_, ака_, paba, urác,
+ {{0x27ff85d5,0x27ed80b9,0xe16600f7,0x6d4e05d6}}, // _akun_, _ajen_, تدري, qaba,
+ {{0x442205d7,0x2d87835f,0x628f05d8,0x69de05d9}}, // _tik_, dene_, _esco, ripe,
+ {{0xeb9a05da,0x7bdf05db,0xb5fb026f,0x394a05dc}}, // _тим_, ciqu, prác, _webs_,
+ {{0x7c2285dd,0xfe6f85de,0x2d8785df,0x61fa85e0}}, // _rior, ندو_, fene_, yotl,
+ {{0x69de020f,0x2d8785e1,0x443a05e2,0x7c228014}}, // qipe, gene_, _ktp_, _sior,
+ {{0x69dc00f7,0x7c2285e3,0x6fc60118,0x50b5002e}}, // _imre, _pior, _nóco, _есту,
+ {{0x78a105e4,0xa3ba80f7,0x27ed8168,0xe3bf0144}}, // _álva, شاعر_, _gjen_, _tiñe_,
+ {{0x2d8785e5,0xa91c8038,0xf41302f6,0x69dc02f7}}, // bene_, teľo, ופה_, _kmre,
+ {{0x2fc905e6,0x61e881ec,0x20068168,0x7bdf05e7}}, // _slag_, undl, lloi_, ziqu,
+ {{0x09b78307,0xeb0705e8,0x2fc9056c,0x28f885e9}}, // تطيع_, _शर्त_, _plag_, сень_,
+ {{0x93258077,0x32058039,0xea0100ff,0x7bdf05ea}}, // _فرهن, ally_, _đập_, xiqu,
+ {{0x645a005c,0x442c84fe,0x69dc05eb,0xf98f80d5}}, // štil, _hud_, _omre, ربی_,
+ {{0x63a605ec,0x72e980be,0x443a05ed,0x200005ee}}, // _ankn, _פֿײַ, _btp_, _ekii_,
+ {{0x7bdf05ef,0x69ca808e,0x442c8162,0x7bcd05f0}}, // tiqu, _ilfe, _jud_, thau,
+ {{0x5a3505f1,0x798885f2,0x3a3905f3,0x69dc05f4}}, // знат, ledw, _ptsp_, _amre,
+ {{0x7bdf03d3,0x442c85f5,0x7bcd05f6,0x2d9305f7}}, // [250] riqu, _lud_, rhau, _taxe_,
+ {{0x7bdf0557,0x442c85f8,0x7bcd05f9,0xaca301bc}}, // siqu, _oud_, shau, _apụg,
+ {{0x2d8785fa,0x6e2d022c,0xf8ca85fb,0x20ca85fc}}, // vene_, _huab, ानिय, ानिध,
+ {{0x6d5a85fd,0x7e6281e0,0x2d8780d7,0x6e2d05fe}}, // _adta, frop, wene_, _kuab,
+ {{0xf77085ff,0x27ed80f1,0x2d87835f,0x7c3a8600}}, // ضان_, _vjen_, tene_, _attr,
+ {{0x442c8601,0xb2ba01c6,0x6e2d0602,0xa2ba01c6}}, // _bud_, _המשר, _muab, _המשט,
+ {{0x2d878603,0xd8380048,0x27ff81cd,0x7d1c808b}}, // rene_, _мэт_, _tkun_, _úrsl,
+ {{0x5ba98604,0x442c8197,0x2d878605,0x69ca8606}}, // ским_, _dud_, sene_, _alfe,
+ {{0xe9d98607,0x2d878608,0x64488609,0x79950234}}, // йки_, pene_, _ewdi, _kazw,
+ {{0x442c80f7,0x6ca7860a,0x69c5060b,0x2ba981cb}}, // _fud_, _мреж, nkhe, काहा,
+ {{0x6d43860c,0x442c8082,0x2a6901c5,0x6e2d0362}}, // mbna, _gud_, huab_, _auab,
+ {{0x69ca860d,0xe29f007b,0x7649860e,0x628d060f}}, // _elfe, _orð_, _kwey, kwao,
+ {{0xb9028610,0xfaa58611,0x7bcb8612,0x629800eb}}, // _ने_, _хало, _ilgu, āvok,
+ {{0xceb2804c,0x69c50613,0x79950063,0x645a005c}}, // עים_, jkhe, _nazw, štim,
+ {{0x61e18614,0xdca30615,0x64a30616,0x764983f7}}, // mill, нари, нара, _lwey,
+ {{0x61e18617,0x67298618,0x3cde8006,0x69dc0619}}, // lill, _ngej, _कइसे_, _smre,
+ {{0x61e1861a,0x443a008e,0x2737001c,0x37e180ab}}, // [260] oill, _wtp_, _ủng_, যকার,
+ {{0x3ea0061b,0x443a0359,0x6d41061c,0xa96a01a8}}, // _irit_, _ttp_, ılac, إمام_,
+ {{0x7e628372,0x6e3b861d,0x55bb01c6,0x2ec4861e}}, // wrop, _atub, _המיו, वन्त,
+ {{0x442c861f,0x2d980620,0x2d8a0621,0x7e628622}}, // _rud_, ldre_, lebe_, trop,
+ {{0x61e18623,0xdfcf8624,0xc61580c8,0x2a690282}}, // kill, دين_, াদনা_, cuab_,
+ {{0x7bcb8531,0x2d980625,0xb5fb0626,0x61e18627}}, // _algu, ndre_, hrán, jill,
+ {{0xf7468628,0x200683d3,0x62828629,0xb4c6800d}}, // _нево, ploi_, _spoo, उने_,
+ {{0x7dcb062a,0x2d8a062b,0x9f5900e7,0x61e1862c}}, // _müsa, hebe_, posé_, eill,
+ {{0x63b9862d,0x61e1862e,0x60338087,0xdcfa80eb}}, // _down, fill, _răma, _patē,
+ {{0xe3bf062f,0x442c8630,0x61e18631,0x6d5a806a}}, // _miña_, _tud_, gill, _udta,
+ {{0xe3bf062f,0xb5fb0632,0x2d8a0633,0x442c8634}}, // _liña_, frán, debe_, _uud_,
+ {{0x61fe0047,0x61e18635,0x79888636,0x22400637}}, // hopl, aill, sedw, kpik_,
+ {{0x71a68638,0x61fe0639,0xe3bf0264,0x2ba9863a}}, // _надз, kopl, _niña_, कारा,
+ {{0x61e1863b,0x64550065,0x4426863c,0x2d8a063d}}, // cill, tszi, _hio_, gebe_,
+ {{0x4426863e,0x3ea00176,0x48dc016f,0xb5fb03c1}}, // _kio_, _erit_, _गेलो_, brán,
+ {{0xcf2700f7,0x3ea0006a,0x2d98063f,0x6e2d0640}}, // _عربي, _frit_, adre_, _tuab,
+ {{0x44268641,0x61eb8019,0x7c260642,0x2d8a0643}}, // [270] _mio_, _állí, _dikr, bebe_,
+ {{0x2d8a0644,0x61fe00fe,0x44268645,0xeb9f0646}}, // cebe_, gopl, _lio_, _skød_,
+ {{0x7c260647,0x69c50648,0x25d800be,0x2a690069}}, // _fikr, rkhe, _מוזן_, suab_,
+ {{0x61e18649,0xb8d5064a,0x4426864b,0xe29f008b}}, // zill, _जप_, _nio_, _eyða_,
+ {{0x6ff5809a,0xcb9a0039,0x63b9864c,0xf8a9864d}}, // jące, _וסרט, _rown, _छप्प,
+ {{0xd1268013,0x06f4803d,0x6b89864e,0xbea2864f}}, // _لم_, رسنج, weeg, ташк,
+ {{0x44268025,0x6b898650,0x2ba98651,0xdb0603ec}}, // _bio_, teeg, काला, _ankè,
+ {{0x2fc68052,0x5f948652,0x44268653,0x3f8b0654}}, // ckog_, _пист, _cio_, jecu_,
+ {{0x6b898079,0x44268655,0x8d878656,0x60188009}}, // reeg, _dio_, _нужд, _хотя_,
+ {{0x61e180e7,0x26c20084,0x63b982f7,0x5555803d}}, // uill, vyko_, _wown, _آپار,
+ {{0x61e18657,0x7dd00009,0x442683a7,0xb5fb0658}}, // rill, _jäse, _fio_, trán,
+ {{0x61e18659,0x4426865a,0xdee6065b,0x66e6065c}}, // sill, _gio_, доми, дома,
+ {{0x7dd0016d,0xb5fb05e4,0xab0b803d,0x6fc6002a}}, // _läse, rrán, _اتاق_, _hóck,
+ {{0xb21b065d,0x26110105,0x7c26065e,0x61e1865f}}, // _spæn, _दीदी_, _sikr, qill,
+ {{0x2d9801b3,0x3e76006a,0x2d8a00b4,0x3b960073}}, // rdre_, sæt_, rebe_, мјат,
+ {{0x6ff60307,0x3ea00660,0xea01001c,0xe3bf0661}}, // _تستط, _trit_, _đạp_, _piña_,
+ {{0x61fe0662,0x2bbf0663,0x3b070098,0x7c260664}}, // [280] topl, _एकरा, дето_, _vikr,
+ {{0x3cde8665,0xe3bf05e4,0xc98601a8,0x2fcd8162}}, // _कइले_, _viña_, بشري, _aleg_,
+ {{0x7c260666,0x61fe0667,0x8cb503eb,0x2fc68668}}, // _tikr, ropl, _उपयो, tkog_,
+ {{0xe3bf062f,0x672d8669,0x2fcd810c,0x4f0a866a}}, // _tiña_, žajn, _cleg_, онен_,
+ {{0x442680a9,0xa91c80e1,0xa248003d,0xdb0f016a}}, // _rio_, teľk, _ریال_, _tocá,
+ {{0x2fc68289,0x44268010,0x3f8b017f,0x7d1c808b}}, // skog_, _sio_, zecu_, _úrsk,
+ {{0xd49781bb,0x52b8866b,0x6da3866c,0x69ce066d}}, // дры_, ेन्स, тија, _ilbe,
+ {{0x27e000e4,0xcfaa866e,0xc1788196,0x2918066f}}, // _nmin_, _باسم_, ynės_, ągać_,
+ {{0x8cb50670,0x44268671,0x29078672,0xb5fb016a}}, // _उपभो, _vio_, _ayna_, rtáz,
+ {{0x27e00673,0x7c3e003a,0x76428674,0x290781b0}}, // _amin_, _otpr, mpoy, _byna_,
+ {{0x44268675,0xf1bf001c,0x3f8b026c,0xf99301c6}}, // _tio_, _quái_, tecu_, דרת_,
+ {{0x443e8065,0x72c68676,0x62860677,0x29078114}}, // _itt_, _обез, _opko, _dyna_,
+ {{0x441b8158,0x69ce0358,0xa3ab8105,0x3f8b017f}}, // _וואס, _olbe, खार_, recu_,
+ {{0x34950678,0x443e8679,0x27e0067a,0x7dcf8366}}, // нагр, _ktt_, _emin_, _tøse,
+ {{0xe4e400e8,0x628600eb,0xc1788084,0x7d088366}}, // вітн, _apko, snės_, _jyds,
+ {{0x69ce067b,0xcbcd00ab,0xddc9801b,0x7dd0039c}}, // _albe, রচ্ছ, _dveř, _räse,
+ {{0x6feb0063,0x5215067c,0xbbbf036d,0x6b82867d}}, // [290] jęci, едат, _एकीक, _mbog,
+ {{0x443e867e,0x2fcd867f,0x64588680,0x26000072}}, // _ott_, _sleg_, ksvi, ळवणी_,
+ {{0x3cde835a,0x443e8681,0x6d470682,0x764d0683}}, // _केले_, _ntt_, mbja, _hway,
+ {{0x764d0684,0x69ce0685,0x64588686,0x7dd00106}}, // _kway, _elbe, dsvi, _väse,
+ {{0x443e8687,0xb906058c,0x6fc600f7,0x733a00be}}, // _att_, _बे_, _dóch, _מערס,
+ {{0x6b828688,0x6d550689,0x63bd068a,0x764d0247}}, // _abog, naza, _hosn, _mway,
+ {{0x26c9803a,0x6458868b,0x6aa2808e,0x63bd007b}}, // šao_, gsvi, _arof, _kosn,
+ {{0x6d55068c,0x672d068d,0xe3bf04c3,0x61e5068e}}, // haza, _ngaj, _miño_, lihl,
+ {{0x443e868f,0xb5fb0019,0x29078690,0x6aa28691}}, // _ett_, trál, _syna_, _crof,
+ {{0xf1b180f7,0x672d04b9,0x6e288197,0x63bd0692}}, // اءة_, _agaj, _gidb, _losn,
+ {{0xe3bf0693,0x764d0694,0x6aa2826b,0x6d550695}}, // _niño_, _away, _erof, daza,
+ {{0x649a8051,0x6b828324,0x63bd0696,0x64438502}}, // _מסעד, _gbog, _nosn, mpni,
+ {{0x0aeb8077,0xe1ee8098,0x6d55005d,0x34c80697}}, // _براي_, _бг_, faza, रन्द,
+ {{0x7bcf0698,0x200b0699,0x6d55069a,0x6b82812b}}, // _alcu, alci_, gaza, _zbog,
+ {{0xafe6069b,0xb042801c,0xdb86069c,0x63bd069d}}, // _покл, _trưở, нгви, _bosn,
+ {{0x6b9b869e,0x7ae4869f,0x63bd0090,0x27e0008e}}, // ndug, _žiti, _cosn, _umin_,
+ {{0x6d5506a0,0xada606a1,0x7bc98198,0x200206a2}}, // [2a0] baza, _запл, ikeu, moki_,
+ {{0xdb0d0125,0x200206a3,0x7bcf0144,0x673b85ee}}, // rkað, loki_, _elcu, _afuj,
+ {{0x7dcb0457,0xb5fb016b,0x717300f7,0x7bc98198}}, // _müsl, kráj, اهما, kkeu,
+ {{0x2d9a06a4,0x48c300ab,0x601606a5,0x2ca5006a}}, // _hape_, ্পূর, lámb, ælde_,
+ {{0x2d9a06a6,0x490686a7,0x7bc986a8,0xfeb7803d}}, // _kape_, _सुनो_, dkeu, فاوت_,
+ {{0x63ab86a9,0x645886aa,0xab5b01ec,0x200206ab}}, // _angn, tsvi, _flüs, hoki_,
+ {{0x200206ac,0x2d9a06ad,0xa3ab86ae,0xb0de86af}}, // koki_, _mape_, खां_, _फेंग,
+ {{0x645883a6,0x6b9b84a7,0x8cb182f1,0x9f5901ca}}, // rsvi, gdug, _अपरो, tosí_,
+ {{0x6d5506b0,0x60e00110,0x645886b1,0x200206b2}}, // yaza, žymė, ssvi, doki_,
+ {{0x27e686b3,0x7d08867f,0x2d9a06b4,0x6b8d011b}}, // lion_, _tyds, _nape_, zeag,
+ {{0xc05786b5,0x764d06b6,0x5fc486b7,0xdddb80ce}}, // нія_, _sway, _वकाल, _upuš,
+ {{0x6d5506b8,0x6b8286b9,0x27e686ba,0x6b998326}}, // waza, _ubog, nion_, _gawg,
+ {{0xcf938158,0x26c686bb,0x6d5506bc,0x63bd06bd}}, // נטש_, nyoo_, taza, _rosn,
+ {{0x7d0306be,0xbbc902ef,0x2bc906bf,0x764d06c0}}, // _ønsk, रस्क, रस्थ, _vway,
+ {{0x6d5506c1,0x26ec8665,0x63bd06c2,0x5ff50098}}, // raza, जपुर_, _posn, _изку,
+ {{0xe3bf04c3,0x6d5a06c3,0x27e686c4,0x60160118}}, // _viño_, ótar, jion_, táme,
+ {{0x27e686c5,0x6b8d06c6,0x6d5506c7,0x497506c8}}, // [2b0] dion_, reag, paza, клас,
+ {{0x601606c9,0x8cb1809a,0x63bd008e,0x6b8d06ca}}, // ráme, _अपलो, _wosn, seag,
+ {{0xc95380be,0x6fc60187,0x6f0986cb,0x61e506cc}}, // רמע_, _sóci, _syec, rihl,
+ {{0x27e686cd,0x61e5005d,0x60c50009,0x76598428}}, // gion_, sihl, ryhm, tswy,
+ {{0xa3b486ce,0xee3886cf,0x7e7d06d0,0xd35604de}}, // ञान_, ені_, ntsp, _שישי_,
+ {{0xfce581cf,0x442b06d1,0x7bc981ed,0xd5bb86d2}}, // коло, _hic_, wkeu, ясе_,
+ {{0x645a06d3,0x27e68114,0x02c986d4,0x6b9b86d5}}, // štiv, bion_, िन्न, tdug,
+ {{0x27e686d6,0x25ac802a,0x644386d7,0x443306d8}}, // cion_, _endl_, rpni, _mux_,
+ {{0x7dd000f2,0x7bc986d9,0x443306da,0x2a690066}}, // _läsa, rkeu, _lux_, nrab_,
+ {{0x644386db,0x3f8f807b,0xddc28110,0x7bc986dc}}, // ppni, legu_, puoš, skeu,
+ {{0x660386dd,0xa3c306de,0x442b06df,0x200206e0}}, // monk, ्सा_, _oic_, toki_,
+ {{0x442b06e1,0xb4bf8076,0xf77206e2,0x66038234}}, // _nic_, ुनी_, جاد_, lonk,
+ {{0x443306e3,0x200206e4,0x3f9b0609,0x7e7d06e5}}, // _aux_, roki_, _daqu_, gtsp,
+ {{0x008586e6,0x442b06e7,0x7d048214,0x27e686e8}}, // _алко, _aic_, şise, zion_,
+ {{0xdce18214,0xa50706e9,0x601886ea,0xd62706eb}}, // _kalı, вера_, líme, торе_,
+ {{0x27e682be,0x3f8f86ec,0x442b06ed,0x660386ee}}, // xion_, jegu_, _cic_, honk,
+ {{0x442b06ef,0x443302be,0x3ae80077,0x2ebd86f0}}, // [2c0] _dic_, _eux_, ربری_, ्नोत,
+ {{0xb14306f1,0x261683db,0xdcfc00eb,0x7c2b86f2}}, // инул, _पीढी_, ndrī, _ligr,
+ {{0x27e686f3,0xe0d200f7,0xd6a9026a,0x27f7015b}}, // tion_, جزء_, _قدیم_, _سفید_,
+ {{0x7c2b86f4,0x2d858039,0xa3a9064a,0x442b06f5}}, // _nigr, _able_, _गोप_, _gic_,
+ {{0x27e686f6,0xa3ab823c,0x644186f7,0xc32000ab}}, // rion_, खाई_, _atli, _ফ্রি_,
+ {{0x27e686f8,0xd94306f9,0x442b0087,0x614306fa}}, // sion_, _вери, _zic_, _вера,
+ {{0xdce183bf,0x27e686fb,0x2d8e86fc,0x09e306fd}}, // _balı, pion_, refe_, _торн,
+ {{0x442006fe,0x7bd606ff,0xb5fb0700,0xd9ca8701}}, // nmi_, nhyu, nuár, िस्ट,
+ {{0x64418702,0x877b80be,0x60188661,0x3cde8074}}, // _etli, _קאמי, gíme, _कइके_,
+ {{0x79a68703,0x66038704,0xf1a68705,0x2fc00706}}, // трие, conk, трин, _aoig_,
+ {{0x799c0707,0x7bd60708,0x44200709,0x4ce080ab}}, // _marw, khyu, kmi_, _পুরু,
+ {{0x6d58870a,0x7e7d070b,0x4420070c,0x9f4b0020}}, // lava, ttsp, jmi_, vocó_,
+ {{0xb5fb0065,0x2902809a,0x4420070d,0xf40800ab}}, // nság, łka_, dmi_, লগার_,
+ {{0x6d58870e,0x442b00f1,0xdd11026f,0x4420070f}}, // nava, _sic_, _výži, emi_,
+ {{0x61e88710,0x2fd20711,0xf41480be,0x645c0106}}, // midl, _flyg_, ָפּ_, gsri,
+ {{0x6d588712,0xdce18713,0xa3dd009a,0xef670098}}, // hava, _xalı, थों_, _ръко,
+ {{0x799c0714,0x6d588715,0x2009010c,0x2b4900fe}}, // [2d0] _barw, kava, _ikai_, rbac_,
+ {{0x6d588025,0x44200716,0x799c0717,0xb5fb0718}}, // java, ami_, _carw, dság,
+ {{0x2d9100b4,0x48c300ab,0x3c388019,0xd246853d}}, // meze_, ্প্র, _név_, _گن_,
+ {{0x2d910719,0x6603871a,0x4420071b,0x926b0087}}, // leze_, wonk, cmi_, _орга_,
+ {{0x645a003a,0x35a8023c,0xd2468065,0x799c0428}}, // štit, _छोड़, _دن_, _farw,
+ {{0xa3c3035a,0x6d58871c,0x3e7b82be,0x6575071d}}, // ्सव_, gava, rêt_, ngzh,
+ {{0x6603871e,0xfd500135,0xdb0b80e7,0x6b9d0122}}, // ronk, _aghụ, _ingé, _kasg,
+ {{0x7c3e803e,0x6603871f,0x7bcd0720,0x1fa48721}}, // ípra, sonk, nkau, _круг,
+ {{0x6603807a,0x2fc00722,0x6b9d0723,0x2d910724}}, // ponk, _roig_, _masg, keze_,
+ {{0x44200725,0x20090041,0x3c3880e7,0x61e88726}}, // zmi_, _akai_, _fév_, gidl,
+ {{0x7c2b8727,0x44200728,0x7bcd0364,0x80ad8540}}, // _tigr, ymi_, kkau, टमें,
+ {{0x366a0729,0xeb9f006a,0x5275872a,0x7bcd01c0}}, // намо_, _skøn_, _буку, jkau,
+ {{0x6f0d00b4,0x4420072b,0x765d072c,0x61e8872d}}, // _cyac, vmi_, gssy, bidl,
+ {{0x63bb872e,0x4095872f,0x2d9102a0,0x645c0730}}, // ljun, _брит, geze_, tsri,
+ {{0x07a60048,0x44200731,0x6b9d0732,0x6f0d0733}}, // _байн, tmi_, _basg, _eyac,
+ {{0x63bb8734,0x7bcd0735,0x44200736,0x6b9d0114}}, // njun, gkau, umi_, _casg,
+ {{0xc332836b,0xdcfa80eb,0x6d588737,0x60160511}}, // [2e0] מון_, _patī, yava, cáma,
+ {{0xd05a0558,0x64410207,0xddd481ac,0x7bd602a0}}, // ерді_, ílic, _ťažk, shyu,
+ {{0x6d588738,0x63bb808b,0x2a7f82f1,0x6b9d0739}}, // vava, kjun, htub_, _fasg,
+ {{0x4444073a,0x61e8873b,0xb4c2073c,0x799c073d}}, // _it_, zidl, ्नी_, _warw,
+ {{0x6d58873e,0x4444073f,0xa3c30740,0x799c0741}}, // tava, _ht_, ्सर_, _tarw,
+ {{0x270300ff,0xb5fb0742,0x7dd001d6,0x98be8196}}, // ổng_, trái, _mäso, lbtą_,
+ {{0x6d588743,0x44440744,0x61e8827f,0x395a01e0}}, // rava, _jt_, vidl, maps_,
+ {{0x7dc68125,0x2d910745,0xb5fb01a8,0x6f090035}}, // _aðst, zeze_, rrái, żeci,
+ {{0x44440746,0x6d5e84c3,0xb4c2000d,0x0aea80f7}}, // _lt_, ópas, ्नु_, عربي_,
+ {{0x44440747,0xc27b03de,0x765d0163,0x776402f9}}, // _ot_, עריי, vssy, _adix,
+ {{0x20090748,0x28f88749,0x63bb874a,0x26e200ab}}, // _pkai_, тень_, bjun, _গুলো_,
+ {{0x09c1016f,0x2d91074b,0x2006874c,0x765d074d}}, // _शक्य, weze_, nooi_, tssy,
+ {{0x4444004c,0x61e8874e,0xa91c81ac,0x2d91074f}}, // _at_, pidl, teľs, teze_,
+ {{0x7dcb0006,0xb5fb003e,0x0cb680c8,0x23278750}}, // _küsi, hráv, জনীত, _сочи_,
+ {{0x2d910751,0x6b840752,0x6b9d0753,0xd13f809a}}, // reze_, ffig, _pasg, łącz_,
+ {{0x672d8754,0x44440755,0x2d910756,0x261a06a7}}, // žaji, _dt_, seze_, _मीठी_,
+ {{0x44440757,0x99550758,0x7bcd0759,0xf96b075a}}, // [2f0] _et_, икац, rkau, ерей_,
+ {{0x2d9e875b,0x7bcd075c,0x444401c6,0x6b9d075d}}, // _nate_, skau, _ft_, _wasg,
+ {{0x6b9d0014,0x3ea904e8,0x92b50264,0x63bb875e}}, // _tasg, _hrat_, জনে_, yjun,
+ {{0xb4d100a5,0x3ea9075f,0x66070088,0x64450760}}, // वनी_, _krat_, lojk, _ithi,
+ {{0x2d9e8761,0x248c80ee,0x29180762,0xd2440087}}, // _bate_, _apdm_, _ezra_, _гэси,
+ {{0xd6580051,0x22490763,0x6da58764,0x2d9e802e}}, // ניות_, mpak_, рика, _cate_,
+ {{0xeb998765,0x2d9e8766,0x23d58767,0xa3b90768}}, // вий_, _date_, ицир, चाप_,
+ {{0xea010104,0xb5fb01df,0x3ff980be,0x2d9e838a}}, // _đẹp_, nsáb, _ספּע, _eate_,
+ {{0x22490769,0x59a686a7,0xe3b6876a,0xd7f88084}}, // npak_, _कोहर, рбы_, _сур_,
+ {{0x3a2e83a8,0x7c2f076b,0x6ff201a9,0x64450234}}, // _cifp_, _licr, māci, _othi,
+ {{0x7dd000f2,0x7c9586e2,0xf9920039,0x6607076c}}, // _säso, _خلاص, חרי_, dojk,
+ {{0x2249076d,0xf1d98651,0x7c2f0706,0x2d218072}}, // kpak_, योजन, _nicr, मधील_,
+ {{0x6445076e,0xa3b90321,0x2d9e876f,0x6ff200eb}}, // _athi, चान_, _yate_, nāci,
+ {{0x3ea9008e,0x7bc281c0,0x9fb881d0,0xe4da00d7}}, // _drat_, _zoou, včí_, پورت_,
+ {{0xb4c20770,0x3ea9010b,0xd5e4019d,0x69c38771}}, // ्ने_, _erat_, _apị, _ione,
+ {{0x6ff200eb,0xa3a90105,0x2d9e027f,0xb5fb04f0}}, // kāci, _गोद_, žte_, dráu,
+ {{0x442f8772,0x44440773,0x64450774,0x60188775}}, // [300] _mig_, _wt_, _ethi, jíma,
+ {{0x44440776,0x442f8777,0xbbd28778,0xe9d70779}}, // _tt_, _lig_, _तत्क, аку_,
+ {{0x4444077a,0x673b077b,0x69c3877c,0xd91000d5}}, // _ut_, žuje, _mone, سیر_,
+ {{0xc60e83b7,0xddc28029,0x6d41877d,0x53ca0424}}, // ित्य_, droš, _afla, _रविश,
+ {{0x7c24077e,0x9f82077f,0x7ae90669,0xeb970081}}, // mmir, _yóò_, _žetv, _тия_,
+ {{0x2b4d8780,0x6d5c0781,0x69c380b4,0x442f8782}}, // ebec_, nara, _none, _aig_,
+ {{0x442f8783,0x601d02be,0xb5fb026f,0x2bdd016f}}, // _big_, léme, práv, नोका,
+ {{0xa3b905b3,0x442f8784,0x0b8a8139,0x938a8785}}, // चाय_, _cig_, тски_, тска_,
+ {{0x442f8786,0x2a6d8787,0x6d5c0788,0x3c3c0789}}, // _dig_, greb_, kara, _lív_,
+ {{0x6d5c078a,0x4e7b8051,0x64430036,0x442f878b}}, // jara, _באמצ, _énig, _eig_,
+ {{0x6607003b,0x69c3878c,0xb4c203bb,0x3137078d}}, // vojk, _done, ्नो_, ינים_,
+ {{0x6d57078e,0x41a68076,0x3ea9078f,0x0aea8790}}, // _sexa, _कोरस, _prat_, _идей_,
+ {{0xdefb0791,0x6d5c0792,0x69c38793,0x7c240669}}, // тын_, fara, _fone, dmir,
+ {{0x6d5c0794,0x442f8795,0x7c2f031d,0x3ea90796}}, // gara, _zig_, _sicr, _vrat_,
+ {{0x442f8069,0xe4a40256,0x6d570797,0x6ff20029}}, // _yig_, орто, _vexa, zāci,
+ {{0x7c240798,0x3ea90799,0x83350081,0x69c3879a}}, // gmir, _trat_, снах, _zone,
+ {{0x6d5c079b,0x8d8701a1,0x69d5001b,0xba5501a8}}, // [310] bara, _кунд, _plze, صناع,
+ {{0xe29f8125,0xc1788110,0x6ff201a9,0x3958079c}}, // æði_, ngė_, vāci, _cers_,
+ {{0xd1380698,0x224902f7,0x6445079d,0x4395079e}}, // рху_, spak_, _uthi, _ланс,
+ {{0x3958079f,0x224687a0,0x6ff200eb,0x6018807b}}, // _eers_, _otok_, tāci, tíma,
+ {{0x6d4181ec,0x9f42016b,0x60160019,0xbea587a1}}, // _pfla, niká_, zámo, салк,
+ {{0x442f87a2,0xb5fb07a3,0x6ff20029,0x629d07a4}}, // _sig_, crát, rāci, _isso,
+ {{0x27e907a5,0xe3b180f7,0x22468748,0x69c387a6}}, // _oman_, كرة_, _atok_, _rone,
+ {{0x69c387a7,0x442f822c,0x27e907a8,0x2a6d87a9}}, // _sone, _qig_, _nman_, treb_,
+ {{0x69c387aa,0xdcfe01a9,0x36d4804a,0x2b4d87ab}}, // _pone, _papī, _дотр, rbec_,
+ {{0xdef887ac,0x27e90590,0x25a107ad,0x69da8039}}, // _тыс_, _aman_, _nahl_, ghte,
+ {{0x2ed00076,0x442f87ae,0x1bf905e8,0x69c387af}}, // हन्त, _tig_, ्काल_, _vone,
+ {{0x6d5c07b0,0xcdc90051,0x601607b1,0x69c380f3}}, // wara, _אך_, rámo, _wone,
+ {{0x6d5c07b2,0x69c387b3,0x539a8039,0x7bdb8079}}, // tara, _tone, _תינו, mhuu,
+ {{0x69da87b4,0x27e9011e,0x32b881a8,0x601603a8}}, // chte, _eman_, _خدمة_, pámo,
+ {{0x629d07b5,0x04460226,0xb5fb016b,0x25a104dc}}, // _asso, себн, vrát, _dahl_,
+ {{0xe29a07b6,0x9f82007b,0x2b5907b7,0x7dd007b8}}, // лаб_, _góð_, _cesc_, _käsk,
+ {{0x2b400390,0xaaa786ae,0x09360742,0xb5fb07b9}}, // [320] rcic_, कटिक, مراج, trát,
+ {{0x6d5c07ba,0x2a6007bb,0x2d87826c,0x601d07bc}}, // qara, rsib_, jfne_, réme,
+ {{0xfbd207bd,0x395807be,0x629d07bf,0x6f1b85e4}}, // _فتح_, _vers_, _esso, _azuc,
+ {{0xc88507c0,0x25a107c1,0x201f87c2,0x939607c3}}, // daşı_, _zahl_, _chui_, _اجدا,
+ {{0xe5c687c4,0x248c01b9,0x69da87c5,0x201f8362}}, // йско, ħdmu_, yhte, _dhui_,
+ {{0x443a07c6,0x62840106,0x6b96057b,0xe196002e}}, // _kup_, mtio, leyg, _урмэ,
+ {{0x7bc6029b,0x224687c7,0x62840009,0x29c9802a}}, // _koku, _stok_, ltio, _lúas_,
+ {{0xd00a87c8,0x443a0067,0x6b9607c9,0xb33b0315}}, // _себе_, _mup_, neyg, _kaça,
+ {{0x7ae907ca,0x63a407cb,0x628407cc,0x7bc607cd}}, // _þett, idin, ntio, _moku,
+ {{0x7bc607ce,0x27e907cf,0x984a8098,0xb33b07d0}}, // _loku, _sman_, ляма_, _maça,
+ {{0x7dcb07d1,0xe45287d2,0xb5fb07d3,0x8b66803d}}, // _xüsu, _رضا_, krár, _هاشم,
+ {{0x628407d4,0x69da820f,0x7bc6029b,0x6d5a816d}}, // ktio, shte, _noku, _heta,
+ {{0x6d5a87d5,0x63a407d6,0x7f5d002a,0xb4d107d7}}, // _keta, ddin, rasq, वन्_,
+ {{0x27078104,0x29c9862f,0x7c3a87d8,0xd6d800e8}}, // ống_, _dúas_, _kutr, стю_,
+ {{0xdce887d9,0x6d5a87da,0x395e87db,0x69d884a2}}, // _kadı, _meta, mats_, _ilve,
+ {{0x395e87dc,0x6d5a87dd,0x27e907de,0x7c3a811b}}, // lats_, _leta, _uman_, _mutr,
+ {{0x63a2822e,0x7c3a82a5,0x200b07df,0xb33b07e0}}, // [330] _maon, _lutr, moci_, _caça,
+ {{0x7c3a87e1,0x2ba905b3,0x395e87e2,0x63a407e3}}, // _outr, _चोरा, nats_, adin,
+ {{0xceeb8077,0x5335079e,0x443a07e4,0x628407e5}}, // گران_, _мент, _gup_, atio,
+ {{0x61c587e6,0xb33b07e7,0xdea1803d,0x395e87e8}}, // _लक्ष, _faça, ویزی, hats_,
+ {{0x628407e9,0x7c3a86e3,0x6d5a87ea,0x25ed835a}}, // ctio, _autr, _beta, _अगदी_,
+ {{0x313507eb,0x6d5a87ec,0x7bc6029b,0xdcf88110}}, // _февр, _ceta, _zoku, nevė,
+ {{0x7bc6029b,0x628282a3,0x395e87ed,0x7c3a8087}}, // _yoku, _aqoo, dats_, _cutr,
+ {{0x69d887ee,0x69c707ef,0x7c3a87f0,0x6d5a87f1}}, // _alve, _hoje, _dutr, _eeta,
+ {{0x69c7003a,0xe9d987f2,0x64488201,0x200b0052}}, // _koje, ики_, _etdi, doci_,
+ {{0x7dcb0352,0x29c984c3,0x395e87f3,0x7dd0016d}}, // _müss, _rúas_, gats_, _väsk,
+ {{0x29c987f4,0x69c707f5,0x63a407f6,0x645e80e1}}, // _súas_, _moje, ydin, špir,
+ {{0x6d4507f7,0x69d887f8,0x6d5a87f9,0x200b0722}}, // _afha, _elve, _zeta, goci_,
+ {{0x395e87fa,0x6d5a87fb,0xa2948112,0xd9fb016f}}, // bats_, _yeta, _наці, ्वात_,
+ {{0x7dd003ff,0x395e87fc,0x7bc6029b,0x63a40295}}, // _käsi, cats_, _soku, wdin,
+ {{0x7bc607fd,0x63a287fe,0x64a307ff,0x7dcb0800}}, // _poku, _yaon, мара, _rüst,
+ {{0xb06880d5,0x63a40801,0x29c984c3,0x7dcb0074}}, // _اصول_, udin, _túas_, _süst,
+ {{0x63a40802,0x98a583bf,0x7dcb02f1,0x224d8088}}, // [340] rdin, malı_, _püst, npek_,
+ {{0x7bc6029b,0x63a40803,0x3ea006c0,0x69c70804}}, // _woku, sdin, _isit_, _coje,
+ {{0x69c70805,0x62840806,0x7bc60807,0xdbd18214}}, // _doje, stio, _toku, _güçl,
+ {{0x2d980808,0x62840809,0x510c8158,0x98a5880a}}, // lere_, ptio, נהאַ, nalı_,
+ {{0xd5ba8009,0x4e96880b,0x63a2880c,0x601d080d}}, // иск_, تشار, _raon, néma,
+ {{0x2d98080e,0x7c3a81f1,0x3ead809f,0x7e6280b9}}, // nere_, _putr, _dret_, rsop,
+ {{0x395e880f,0xaff58065,0x98a5861c,0xb4d4853f}}, // vats_, _کہنا_, kalı_, हने_,
+ {{0x2d980810,0xdce88201,0x32668811,0x9faf001b}}, // here_, _qadı, отов, tří_,
+ {{0x6d5a8812,0x395e8813,0x2d980814,0xdb1d0019}}, // _teta, tats_, kere_, _kosá,
+ {{0x2d980815,0x670e0816,0xddc2809a,0x63a2804f}}, // jere_, _सड़क_, społ, _waon,
+ {{0x2d980817,0x395e8818,0x63a282b8,0xeb9a8819}}, // dere_, rats_, _taon, _вие_,
+ {{0x395e881a,0xf8ad881b,0xb09b01c6,0x2d98038a}}, // sats_, اکو_, ביטר, eere_,
+ {{0x2d98081c,0x200b081d,0x395e881e,0xa3b9081f}}, // fere_, roci_, pats_, चास_,
+ {{0x2d980820,0xddcf0162,0x61ea8162,0x69d88821}}, // gere_, ducţ, _umfl, _ulve,
+ {{0xfbd30013,0xa1778051,0x69c70822,0xc05a821e}}, // وتر_, _ועוד_, _roje, _тіл_,
+ {{0x44220823,0x69c70824,0xb5fb0825,0x98a58826}}, // _chk_, _soje, rráq, calı_,
+ {{0x2d980827,0x69c70828,0xe1f30829,0x7c22882a}}, // [350] bere_, _poje, وسط_, _mhor,
+ {{0x2d98082b,0x44220006,0xdddb882c,0x3f99082d}}, // cere_, _ehk_, _spuž, lesu_,
+ {{0xe7f78076,0x2d81082e,0x69c7026f,0x7649882f}}, // ंचला_, _iche_, _voje, _stey,
+ {{0x3ead8830,0x320c8831,0x69c7009a,0x4c9a04de}}, // _pret_, hody_, _woje, _פברו,
+ {{0xb3ba0039,0x320c866f,0x00000000,0x00000000}}, // _המרכ, kody_, --, --,
+ {{0x7c228832,0x270a0104,0x27ef8833,0x246580eb}}, // _ahor, ờng_, sign_, tēm_,
+ {{0x98a58059,0xc5f4007c,0x7c228834,0x6b898122}}, // yalı_, ודס_, _bhor, tfeg,
+ {{0x3cfc012a,0x44290835,0x2d9800b4,0x3f99005d}}, // ילונ, nma_, zere_, jesu_,
+ {{0x6aa48836,0x7c228837,0x27ed8838,0xe7af009a}}, // _šifr, _dhor, _imen_, _जोधप,
+ {{0xe9d98139,0xaca38135,0x2d810135,0xba228071}}, // ско_, _ahụm, _nche_, ндык,
+ {{0x2d980839,0x4429083a,0x7c228013,0x224d883b}}, // vere_, kma_, _fhor, spek_,
+ {{0x2d98082e,0x7c22883c,0x4429083d,0x2fc9002a}}, // were_, _ghor, jma_, _coag_,
+ {{0x2d98083e,0x4429083f,0x98a583bf,0x25a58840}}, // tere_, dma_, ralı_, _kall_,
+ {{0x44290841,0x7c22816b,0x7dd00338,0x7ddd810c}}, // ema_, _zhor, _läsv, _hèsb,
+ {{0x2d980842,0x7c298843,0x44220573,0x27ed8844}}, // rere_, omer, _phk_, _omen_,
+ {{0x2d980845,0x44290846,0x4a460847,0x7c298848}}, // sere_, gma_, знав, nmer,
+ {{0x2d980849,0x6d41084a,0xe9da084b,0x236000dd}}, // [360] pere_, ılar, бка_, raij_,
+ {{0x4429084c,0xb33b02be,0x7c29884d,0xdb04084e}}, // ama_, _faço, hmer, ndié,
+ {{0x6d5e00eb,0x7c29884f,0x6d3b01c6,0x63768850}}, // _iepa, kmer, _לתינ, nünü,
+ {{0xb4b800c2,0x7c298851,0xf1ba8129,0x291d0326}}, // चमी_, jmer, _nhơn_, ƙwan_,
+ {{0x6d5e0852,0xa3b90853,0xb4e486a7,0xa7fd02d0}}, // _kepa, चार_, _पेड़_, ksın,
+ {{0x6d5e0854,0x63a60855,0x7c228039,0x25a58856}}, // _jepa, _hakn, _shor, _call_,
+ {{0x25a58698,0x7c228857,0x63a60858,0x320c83fb}}, // _dall_, _phor, _kakn, vody_,
+ {{0x6d5e0859,0x7bd6085a,0x7c29885b,0xdcf881a9}}, // _lepa, nkyu, gmer, levī,
+ {{0x63a6085c,0x320c885d,0x25a5885e,0xdb040036}}, // _makn, tody_, _fall_, udiè,
+ {{0x64a6885f,0x20030025,0x672082fd,0xdca68860}}, // _наза, čkim_, _izmj, _нази,
+ {{0x44290861,0xa3190740,0x7c298862,0x660e0079}}, // yma_, _दर्ज_, bmer, dobk,
+ {{0x7c228863,0x44290864,0x2d810865,0x443e8866}}, // _uhor, xma_, _sche_, _iut_,
+ {{0x6d5e05f8,0xe80d80ba,0xb80d86b7,0x7c3e00e7}}, // _bepa, िकता_, िकतम_, _aupr,
+ {{0x6d5e0867,0x443e8868,0x1a9b80be,0x224b80e1}}, // _cepa, _kut_, _לידע, ícke_,
+ {{0x44290869,0x7c3e002e,0xdcf881a9,0xa3b9086a}}, // tma_, _cupr, devī, चाल_,
+ {{0x4429086b,0x443e886c,0x7dcf80e8,0x63a600b9}}, // uma_, _mut_, _høst, _cakn,
+ {{0x5a3500b3,0x443e886d,0x601d086e,0x69dc0362}}, // [370] днат, _lut_, lémo, _blre,
+ {{0x443e804c,0x6d5e04eb,0x7c29886f,0x2d810870}}, // _out_, _gepa, zmer, _uche_,
+ {{0x7c298428,0xf8dc86ae,0x442902d0,0x9f4f8036}}, // ymer, मनिय, pma_, ligé_,
+ {{0xc10400a0,0x6d488871,0xa7fd02d0,0x60160118}}, // روني, _afda, zsın, támi,
+ {{0xf7708872,0x443e8873,0x6da586f1,0x81bc80eb}}, // طان_, _aut_, дила, rmēj,
+ {{0x443e804c,0x60160874,0xf6508875,0x6b828876}}, // _but_, rámi, ائن_, _acog,
+ {{0x63a60877,0x25a58722,0xdca28098,0x70940878}}, // _yakn, _vall_, ващи, _парф,
+ {{0x7dd00879,0x443e811e,0x601d087a,0x25a5887b}}, // _häst, _dut_, démo, _wall_,
+ {{0x200f887c,0x6449887d,0x91e5887e,0x443e8036}}, // mogi_, _žlič, допе, _eut_,
+ {{0x7c29887f,0x443e8880,0x39e9835f,0x69ca8881}}, // smer, _fut_, ідно_, _cofe,
+ {{0x65638882,0x7bdd02bb,0xdd1c826f,0x764d0883}}, // manh, _olsu, _záži, _atay,
+ {{0x7dd00884,0x637683bf,0x5ec600ab,0x65638885}}, // _läst, rünü, _লেগে, lanh,
+ {{0x6d5e0886,0x7bcb8887,0x7c3e0888,0x799a8889}}, // _pepa, _iogu, _supr, betw,
+ {{0x7dd004b8,0x63a9888a,0x51878791,0x63a6088b}}, // _näst, lden, _худа, _sakn,
+ {{0xa3b90076,0x660e0831,0x7bcb888c,0x764d038a}}, // चां_, robk, _kogu, _etay,
+ {{0x7ae4011b,0x673b888d,0x6d5e088e,0x6563888f}}, // tzit, _nguj, _wepa, hanh,
+ {{0x7bcb803a,0x7dd004b8,0x6d5e0890,0x62898891}}, // [380] _mogu, _bäst, _tepa, nteo,
+ {{0x673b81ca,0x7bcb8892,0x6b9b80f3,0x63a98198}}, // _aguj, _logu, heug, hden,
+ {{0xdb0f023e,0xd91000d7,0x765b8893,0x59c40894}}, // _encà, ایز_, _awuy, लॉगर,
+ {{0xef1a8895,0x63a98896,0x443e8897,0x200f8898}}, // оме_, jden, _rut_, gogi_,
+ {{0x443e831d,0x7dd0016d,0xd765803d,0x60188899}}, // _sut_, _fäst, رنوی, ními,
+ {{0x7dd0089a,0x63a9889b,0x443e869d,0x6b8281e8}}, // _gäst, eden, _put_, _scog,
+ {{0x7dd0089c,0x78450110,0xb4bc052a,0x601d002a}}, // _häss, _tėva, _आपो_, xémo,
+ {{0x69ca889d,0xd7648019,0x7dcf889e,0x7bcb889f}}, // _sofe, _سنئی, _søst, _cogu,
+ {{0x291808a0,0x7bcb88a1,0xa80283bf,0x1eaa803d}}, // _hyra_, _dogu, ğıda, _بازي_,
+ {{0x443e88a2,0x7abb0039,0x7dd008a3,0x601d08a4}}, // _tut_, קציו, _mäss, témo,
+ {{0x2d9c820f,0x7dd002af,0xc0578221,0x443e8074}}, // meve_, _läss, мія_, _uut_,
+ {{0x2d9c88a5,0x6aa28041,0x601d08a6,0x764088a7}}, // leve_, _tsof, rémo, _kumy,
+ {{0x442688a8,0x628988a9,0x7dd008aa,0xa2fd066f}}, // _iho_, cteo, _näss, _एशेज_,
+ {{0x2d9c80f1,0x7dd4809f,0x764088ab,0x7bcb83ed}}, // neve_, _bàsi, _mumy, _zogu,
+ {{0x442688ac,0x200f88ad,0x2bbb01a8,0x16340198}}, // _kho_, yogi_, يارة_, теря,
+ {{0x7bcb81df,0x6aad88ae,0x2ba908af,0x439408b0}}, // _xogu, _šafi, _चोखा, касс,
+ {{0xa3d68424,0x6563833e,0x4e0e8035,0x764d08b1}}, // [390] _हवन_, yanh, ाकाई_, _utay,
+ {{0x2d9c88b2,0x442688b3,0x6b9b82af,0xdbd68074}}, // jeve_, _lho_, zeug, _hääl,
+ {{0x7dd000f2,0xdb0b80f2,0x2d9c88b4,0x63a988b5}}, // _väst, _ingå, deve_, yden,
+ {{0xdb0f03a8,0x442688b6,0xddcd016b,0x656380b9}}, // _encá, _nho_, _staň, wanh,
+ {{0x20030025,0x7c2d0687,0x656388b7,0x7dd00364}}, // čkih_, mmar, tanh, _täst,
+ {{0x442688b8,0x44390051,0x291800f2,0x0ea880e8}}, // _aho_, _his_, _fyra_, ькій_,
+ {{0x443908b9,0x7bcb88ba,0x44268046,0x656388bb}}, // _kis_, _pogu, _bho_, ranh,
+ {{0x442688bc,0x7c2d08bd,0x443908be,0x7e7d08bf}}, // _cho_, nmar, _jis_, kusp,
+ {{0x443908c0,0x66f38894,0x656388c1,0x2a6908c2}}, // _mis_, _अधिक_, panh, nsab_,
+ {{0x443908c3,0x3f9d88c4,0x600808c5,0x628988c6}}, // _lis_, lewu_, zımd, rteo,
+ {{0x628988c7,0x7c2d08c8,0x7bcb88c9,0xdb060168}}, // steo, kmar, _togu, _pakë,
+ {{0xf77208ca,0x44390046,0x64418012,0x656181a3}}, // داد_, _nis_, _iuli, _helh,
+ {{0x442d88cb,0x81d588cc,0x644188cd,0x213c80ee}}, // mme_, _подх, _huli, _fgvh_,
+ {{0x644188ce,0x7c2608cf,0x4439061f,0x3f8401b9}}, // _kuli, _shkr, _ais_, żmu_,
+ {{0x65618003,0x443908d0,0xb8f480c8,0xa3b48054}}, // _melh, _bis_, _সে_, _छोड_,
+ {{0x442d8179,0x644188d1,0x7c2d08d2,0xf0628081}}, // nme_, _muli, gmar, _скъп,
+ {{0x291e0025,0x443908d3,0x160f88d4,0x422608d5}}, // [3a0] _šta_, _dis_, िवार_, едав,
+ {{0x442d88d6,0x443908d7,0x7c2d08d8,0xdb06001b}}, // hme_, _eis_, amar, _jaké,
+ {{0x443908d9,0x644188da,0x442d88db,0x61fe08dc}}, // _fis_, _nuli, kme_, rnpl,
+ {{0x442d88dd,0x7dd00364,0x3f8208de,0xe853003d}}, // jme_, _täss, ngku_, انند,
+ {{0x2d9c820f,0x442688df,0x442d88e0,0x245e001b}}, // teve_, _rho_, dme_, lům_,
+ {{0x443908e1,0x644188e2,0x442d88e3,0x27f2008e}}, // _zis_, _buli, eme_, _lmyn_,
+ {{0x644188e4,0x2d9c80f1,0xd49784d9,0x27e008e5}}, // _culi, reve_, еры_, _olin_,
+ {{0x644188e6,0x442681e9,0x44390069,0x69ce011e}}, // _duli, _qho_, _xis_, _hobe,
+ {{0x65618065,0x69ce08e7,0x7c39831d,0x2d9c8168}}, // _felh, _kobe, _diwr, peve_,
+ {{0x44268051,0x27e008e8,0xef1f08e9,0x442d88ea}}, // _who_, _alin_, ngüe_, ame_,
+ {{0x644188eb,0xdbd68364,0x33d6835f,0x27e008ec}}, // _guli, _pääl, _підт, _blin_,
+ {{0x7e7d04a2,0xa2e688ed,0x7c2d0079,0x442d88ee}}, // tusp, _поед, xmar, cme_,
+ {{0x443908ef,0x0cc906ce,0x644188f0,0xa3d58006}}, // _ris_, रह्म, _zuli, _सकत_,
+ {{0x443901e9,0x7afd88f1,0x644188f2,0x27e008f3}}, // _sis_, úste, _yuli, _elin_,
+ {{0x443908f4,0x7e7d08f5,0xdbd68009,0x644188f6}}, // _pis_, susp, _tääl, _xuli,
+ {{0x44390069,0xa4010264,0x75d8007e,0x69c88106}}, // _qis_, ্ষ্য_, _ağzı, ljde,
+ {{0x443908f7,0x98ac83bf,0x1b0e80ab,0x7f490216}}, // [3b0] _vis_, madı_, াইকে_, ñequ,
+ {{0x443903f8,0x69da88f8,0x98ac861c,0x9f590174}}, // _wis_, nkte, ladı_, insí_,
+ {{0xd36688f9,0x69ce08fa,0xaca30133,0x27e00198}}, // _په_, _dobe, _kpọg, _ylin_,
+ {{0x443908fb,0x7e6d08fc,0x6e3a8362,0x2d83021e}}, // _uis_, _kvap, _aitb, lgje_,
+ {{0xe7e288fd,0x2fcd80c9,0x6e3a88fe,0x2fdf874c}}, // _खतरा_, _voeg_, _bitb, _vlug_,
+ {{0x69ce08ff,0x64418900,0x20048088,0x7d1a8901}}, // _gobe, _puli, _ejmi_, _byts,
+ {{0x442d8902,0x65618903,0x301b80f7,0x6441811c}}, // tme_, _velh, _فترة_, _quli,
+ {{0x7bcf0012,0x64418904,0x2242008e,0x69da8905}}, // _jocu, _vuli, _yukk_, ekte,
+ {{0x628d0906,0x1309835f,0x9f4200f1,0x65618907}}, // ntao, ьний_, shkë_, _telh,
+ {{0x7bcf0012,0x64418908,0xb63587c3,0x63ad0909}}, // _locu, _tuli, _شفاع, hdan,
+ {{0xf8e1090a,0x63ad090b,0x442d890c,0xdb1d00f7}}, // पनिय, kdan, pme_, _cosú,
+ {{0x13a7803d,0x79818114,0x6e3a890d,0x78bc8338}}, // _سنتی_, sglw, _zitb, årva,
+ {{0x63ad090e,0x69c4090f,0x21e401ec,0x3f8c009a}}, // ddan, रामी, _höhe_, ędu_,
+ {{0x7ae98065,0x7e6d012b,0x69da8910,0x827680be}}, // mzet, _dvap, ckte, _יענע_,
+ {{0xa8060098,0x69ce0911,0xc5630912,0x2bc58913}}, // _изкл, _robe, ркск, वापा,
+ {{0x63ad0914,0x7bc98915,0x63ab8916,0x6f16809a}}, // gdan, njeu, _jagn, życi,
+ {{0x69ce0917,0x7bcf0918,0xe7f780d4,0x7ae98919}}, // [3c0] _pobe, _docu, ूचना_, nzet,
+ {{0xdb0d008b,0x63ab891a,0x867b01c6,0x63ad091b}}, // rjað, _lagn, _קריו, adan,
+ {{0xa50a091c,0x3d16835a,0x8fa6891d,0x1d0a091e}}, // _цена_, _पुढे_, каже, _цени_,
+ {{0x4444091f,0x69ce0920,0x63ab8921,0x78b5026c}}, // _iu_, _wobe, _nagn, _drzv,
+ {{0x44440922,0xe7f3809a,0x776403a8,0xe7378923}}, // _hu_, _आगरा_, _meix, вец_,
+ {{0x44440924,0x77640118,0x249a00b9,0x7ae98925}}, // _ku_, _leix, _kppm_, dzet,
+ {{0x44440926,0x63ab8098,0x6f098927,0x629986c0}}, // _ju_, _bagn, _exec, _apwo,
+ {{0x44440928,0x3d16835a,0xb5fb00f7,0x63ab8929}}, // _mu_, _पुणे_, rsái, _cagn,
+ {{0x4444092a,0x249a008e,0xdb028118,0x7ae98061}}, // _lu_, _lppm_, _taoí, gzet,
+ {{0x4444092b,0xbda587bd,0xdb1d0331,0x6e3a837a}}, // _ou_, _محمو, _posú, _uitb,
+ {{0x4444092c,0x601d092d,0x717b010f,0x69da892e}}, // _nu_, lémi, וניס, rkte,
+ {{0x63ab892f,0xb4c08076,0x7ec900f1,0xb5fb0073}}, // _gagn, ुही_, _nëpë, nsáv,
+ {{0x44440930,0x77640931,0x63ad05f5,0xd25080f7}}, // _au_, _deix, vdan, _انت_,
+ {{0x44440932,0x63ab8933,0x76440326,0xceb381c6}}, // _bu_, _zagn, _duiy, ביש_,
+ {{0x44440934,0xa3d68935,0x77640936,0x657a00f7}}, // _cu_, _हवा_, _feix, ótha,
+ {{0x249a00b9,0x600802d0,0x6b840937,0x63a0821e}}, // _dppm_, rımc, ggig, nemn,
+ {{0x44440938,0x63ad0939,0x27e6893a,0x628081e0}}, // [3d0] _eu_, rdan, dhon_, numo,
+ {{0x4444093b,0xa2940160,0x601d093c,0x628d00e5}}, // _fu_, рафі, démi, rtao,
+ {{0x44440782,0xf1ba8104,0x6280893d,0x94d5093e}}, // _gu_, _chơi_, humo, _сонц,
+ {{0xf093893f,0x65650940,0x62808941,0x63a08942}}, // ינע_, _hehh, kumo, jemn,
+ {{0x44440943,0x63ab8944,0x628080eb,0x6008080a}}, // _zu_, _ragn, jumo, nıma,
+ {{0xee38835f,0x7ae98945,0x8d548098,0x63ab8946}}, // вні_, vzet, атъч, _sagn,
+ {{0x44440947,0x63ab8948,0x64450074,0x20020035}}, // _xu_, _pagn, _juhi, ynki_,
+ {{0x64450949,0x27e6894a,0x7ae9894b,0x3f89022c}}, // _muhi, chon_, tzet, _ncau_,
+ {{0x7ae9803b,0x63ab894c,0x23c580d4,0x7764094d}}, // uzet, _vagn, वाबद, _reix,
+ {{0x798502f7,0x7ae9894e,0x7764094f,0x7bdb8950}}, // nghw, rzet, _seix, skuu,
+ {{0x54558951,0x63ab8952,0x6d418953,0x7ae98019}}, // _свет, _tagn, _igla, szet,
+ {{0x44440954,0xdce38955,0x63a0808e,0x92b58250}}, // _ru_, nanč, cemn, _محتا,
+ {{0x644502f1,0xe7370956,0x3ea90957,0x62c78174}}, // _auhi, лею_, _csat_, _جزاك,
+ {{0x44440958,0x64450959,0x2002026c,0x7c3d0362}}, // _pu_, _buhi, snki_, _aisr,
+ {{0x444406e3,0xfce3095a,0x7764095b,0x6565008e}}, // _qu_, боро, _teix, _dehh,
+ {{0x4444095c,0xdce38110,0xfaa6095d,0x81bc81a9}}, // _vu_, janč, лабо, smēt,
+ {{0x6d41895e,0x4444095f,0xfe7200d5,0x032602fb}}, // [3e0] _ogla, _wu_, _ادب_, удан,
+ {{0x44440960,0x6d41845a,0x63a08961,0xf1ba8129}}, // _tu_, _ngla, zemn, _phơi_,
+ {{0x44440962,0x81bc8029,0x27e68963,0x644502a0}}, // _uu_, slēg, thon_, _guhi,
+ {{0x2a6d8964,0x6d418965,0x4b550081,0xbe8a8966}}, // kseb_, _agla, _върт, еске_,
+ {{0x44200967,0x6d5c0968,0xcc3b00be,0x44320969}}, // mli_, obra, _רעכט, mmy_,
+ {{0x4432096a,0x6143096b,0xe9df00f7,0xc6930039}}, // lmy_, _гера, _dtús_, צאו_,
+ {{0xe0c7896c,0x4420096d,0xdb1d03ec,0x63a0896e}}, // _از_, oli_, _ansè, temn,
+ {{0x4420017b,0x02a6896f,0x44320970,0x6e3e0971}}, // nli_, гром, nmy_, _lipb,
+ {{0x060a8972,0xaf068973,0xe20b009a,0x248e81e0}}, // _знак_, _спал, łóż_, stfm_,
+ {{0x44200255,0x2a978013,0x63a08974,0x62808975}}, // hli_, ائية_, semn, rumo,
+ {{0x438680d5,0x683e8110,0x62808976,0x02170039}}, // _ملاق, _būda, sumo, רחים_,
+ {{0x273a8182,0x3f8686c0,0x64450977,0x9f590168}}, // _günü_, ngou_, _ruhi, misë_,
+ {{0xeab284e3,0x64450978,0x9f590168,0x98b8022b}}, // _دعا_, _suhi, lisë_, _ferħ_,
+ {{0x44200979,0x2fc0097a,0x44320035,0x6445097b}}, // eli_, _enig_, emy_, _puhi,
+ {{0x4420097c,0x9f5900f1,0xfaff00f1,0xa2ac897d}}, // fli_, nisë_, _ndër_, जिक्,
+ {{0x4420097e,0x61fa897f,0x81bc80eb,0xdb0f0118}}, // gli_, litl, slēd, _incú,
+ {{0x6d5c0098,0xdce381f4,0x3ea90980,0xe51b83b7}}, // [3f0] bbra, vanč, _usat_, _पुनि_,
+ {{0x44200981,0x6d5c0362,0x61fa8982,0x9f590168}}, // ali_, cbra, nitl, kisë_,
+ {{0x44200983,0xa3d58006,0xdce38984,0xdb04016a}}, // bli_, _सकल_, tanč, rdiá,
+ {{0xb4c3035a,0x44200985,0x673b011f,0x9f5903ed}}, // ्ही_, cli_, žuju, disë_,
+ {{0xd90f0986,0x8d658987,0xdce38988,0xea0080ff}}, // _کیا_, авле, ranč, _giầy_,
+ {{0x3f758104,0x7dd48722,0x443d8989,0xaaa80081}}, // ều_, _bàsq, _siw_, _съюз_,
+ {{0x5ed380c8,0x463c098a,0x7aed047f,0x244a898b}}, // _দেশে, געגע, lzat, _cùm_,
+ {{0x7bcd098c,0x0b45898d,0x22468122,0x6d5c098e}}, // njau, шнин, _buok_, zbra,
+ {{0x7aed098f,0x63af0990,0x11e58991,0x4a458992}}, // nzat, _macn, ижим, јнов,
+ {{0x6d41803a,0x43758077,0xe9d98993,0x44200994}}, // _ugla, _چهار, тко_, zli_,
+ {{0xf1b20451,0x44200995,0x23c58996,0x53c580d4}}, // קסט_, yli_, वादद, वादश,
+ {{0x22958997,0x629d074c,0x6d5c0998,0xb4b18999}}, // _видя, _opso, wbra, टटी_,
+ {{0x228f0019,0x6d5c099a,0x61fa899b,0x4420099c}}, // jük_, tbra, bitl, vli_,
+ {{0x2d87899d,0x4420099e,0x200900b9,0xeaf8853d}}, // ngne_, wli_, _ejai_, _حرمت_,
+ {{0x4420099f,0x64a609a0,0x629d003a,0x6d5c09a1}}, // tli_, шава, _apso, rbra,
+ {{0x35c7800f,0xdb0400a9,0x864604ad,0x2fc0031d}}, // लाड़, ndiç, _книж, _unig_,
+
+ {{0x442009a2,0xade589a3,0x443209a4,0x896609a5}}, // [400] rli_, कसान_, rmy_, икаж,
+ {{0x442009a6,0xd5a409a7,0x63af00f7,0x68e481d0}}, // sli_, _الدی, _eacn, _řidi,
+ {{0x18a609a8,0x161f09a9,0xb5fb09aa,0x68e489ab}}, // раем, _मीटर_, rsát, _ƙidi,
+ {{0x442009ac,0x7bdf09ad,0x244a8014,0x7e6409ae}}, // qli_, ckqu, _rùm_, _kwip,
+ {{0x63a409af,0xe5c689b0,0x614609b1,0x3f8689b2}}, // mein, иско, _вега, rgou_,
+ {{0xd49a8381,0x5d7a0158,0x672981a9,0x63af09b3}}, // ври_, דאַק, _dzej, _zacn,
+ {{0x53e689b4,0xcbe689b5,0x672989b6,0x00000000}}, // ициа, иции, _ezej, --,
+ {{0x9f5908cf,0xdb008065,0x63a409b7,0x7afb0289}}, // risë_, lemé, nein, _žuti,
+ {{0x9f5900f1,0x75e201ac,0xee3a89b8,0x6fb38290}}, // sisë_, _rôzn, _ана_, _بمقا,
+ {{0x63a40352,0xe61f00e7,0x9f5909b9,0x3e328019}}, // hein, ntôt_, pisë_, _بہتر,
+ {{0x7aed09ba,0x9f5909bb,0x3167819d,0x61fa89bc}}, // zzat, lisé_, _denz_, ritl,
+ {{0x228f084a,0x61fa89bd,0x656889be,0x6618816b}}, // yük_, sitl, _hedh, lovk,
+ {{0x61fa89bf,0x644889c0,0xf1c909c1,0x34df09c2}}, // pitl, _hudi, रामन, नन्द,
+ {{0x644889c3,0x656889c4,0x249e89c5,0x752d801b}}, // _kudi, _jedh, _kptm_, řaze,
+ {{0x63a409c6,0x403509c7,0x31578039,0xb4c309c8}}, // fein, _лекс, _דיון_, ्हे_,
+ {{0x81e880c8,0x644889c9,0x2cba09ca,0x7aed09cb}}, // _যদি_, _mudi, _drpd_, tzat,
+ {{0x6e950307,0x7bcd0364,0x4df88105,0x5e9500f7}}, // [410] _الخا, rjau, ्चाई_, _الخط,
+ {{0x201909cc,0x7bcd09cd,0x228f09ce,0xa80281cc}}, // losi_, sjau, rük_, şıla,
+ {{0x63af09cf,0xea008028,0x6618826f,0x6d4509d0}}, // _tacn, _giấy_, dovk, _igha,
+ {{0x64570102,0x63a400e7,0xdce709d1,0x5f7480f7}}, // _itxi, cein, najč, قاهر,
+ {{0xef1f080a,0x2bc5816f,0xa3bc06a7,0x656880d7}}, // zgün_, वासा, _आफत_, _bedh,
+ {{0x75eb0059,0x201909d2,0xa1840150,0x7bc289d3}}, // _düzg, hosi_, _мысл, _anou,
+ {{0x201909d4,0x2d8789d5,0xddc98087,0x6d4509d6}}, // kosi_, rgne_, _aveţ, _mgha,
+ {{0xd77489d7,0x6e45803d,0x3ff980be,0x316781e8}}, // _واقع, _هنرم, _עפּע, _senz_,
+ {{0x6568822e,0x69d509d8,0x201909d9,0x316c89da}}, // _fedh, _koze, dosi_, fadz_,
+ {{0x6d4509db,0x3ead83a7,0x65688077,0x69d509dc}}, // _ngha, _iset_, _gedh, _joze,
+ {{0x27338104,0x645701e9,0x7e6409dd,0x2019047f}}, // _ảnh_, _ntxi, _swip, fosi_,
+ {{0x6d4509de,0xdb0609df,0x0cb3864a,0xfaff067f}}, // _agha, _laká, ंटीम, _reën_,
+ {{0xe9ce89e0,0x64570102,0x6d450609,0x05c30035}}, // _ек_, _atxi, _bgha, शांब,
+ {{0xc5f28158,0x9f590009,0x63a40114,0x644889e1}}, // ִדן_, ensä_, wein, _yudi,
+ {{0x764189e2,0xdcf70201,0x644889e3,0x201909e4}}, // _mily, _baxı, _xudi, bosi_,
+ {{0x6b8989e5,0x6618826f,0x776989e6,0x7ddd88f9}}, // ngeg, zovk, _neex, _dèsi,
+ {{0x63a409e7,0xb5fb03a7,0x69d509e8,0xdb008061}}, // [420] rein, rsár, _boze, temé,
+ {{0xb5fb09e9,0x05aa04d9,0x458609ea,0x3ea002d5}}, // ssár, _свой_, _угов, _ipit_,
+ {{0x656889eb,0x3ead89ec,0x442f89ed,0x6008080a}}, // _redh, _aset_, _ohg_, yıml,
+ {{0x644889ee,0x656e09ef,0x76498144,0x6b89890d}}, // _rudi, labh, _buey, jgeg,
+ {{0x6448803b,0x6b8989f0,0x69c38009,0x764189f1}}, // _sudi, dgeg, _onne, _bily,
+ {{0x2d9187d9,0x644888a4,0x20190135,0x9f5902be}}, // _özel_, _pudi, zosi_, risé_,
+ {{0x270409f2,0xdcf70201,0x645c09f3,0x442f89f4}}, // रपुर_, _yaxı, npri, _bhg_,
+ {{0x69c389f5,0xdb06003e,0x656e09f6,0x65688077}}, // _anne, _zaká, habh, _wedh,
+ {{0x6ff58063,0x75eb0214,0x316c8890,0x600801cc}}, // jący, _yüzd, tadz_, rıml,
+ {{0xc05a89f7,0x64488573,0x76418114,0x69c38144}}, // лім_, _tudi, _gily, _cnne,
+ {{0x644889f8,0x645c0639,0x201909f9,0x656e0706}}, // _uudi, jpri, tosi_, dabh,
+ {{0x69c389fa,0xa3cc0740,0x61fe0214,0x442f82c4}}, // _enne, लाफ_, hipl, _ghg_,
+ {{0x201909fb,0x2d9809fc,0x442480b9,0xdce181a9}}, // rosi_, ffre_, mlm_, _ielā,
+ {{0x6d480024,0xddcd009a,0x10f98019,0x15eb00e8}}, // ždan, _stał, ابیں_, уємо_,
+ {{0xd12f896b,0x69d509fd,0x7c240609,0x645c09fe}}, // _хх_, _roze, flir, gpri,
+ {{0x68e10074,0xdb0f0216,0x61fe09ff,0x98a78196}}, // _üldi, _hacé, eipl, _agnė_,
+ {{0x69d50a00,0x27e90a01,0xdd9100f7,0x645c0a02}}, // [430] _poze, _ilan_, _قوة_, apri,
+ {{0x44248106,0x672d0a03,0xdb098118,0x776981b4}}, // hlm_, _izaj, ndeá, _seex,
+ {{0x463c0158,0x7c240a04,0x69d50a05,0xdb1d0a06}}, // _געזע, blir, _voze, _insí,
+ {{0x75eb07d9,0x76418a07,0xdb0981a8,0x2b4680ff}}, // _düze, _sily, hdeá, _ngoc_,
+ {{0x442480dd,0x62350a08,0xdb2300d7,0xdb060a09}}, // dlm_, _мему, _روسی, _taká,
+ {{0x27180028,0x442f8573,0x27fd8114,0x321a0a0a}}, // ọng_, _shg_, siwn_, copy_,
+ {{0x27e90a0b,0x75eb0179,0x64428a0c,0x2bc58a0d}}, // _olan_, _güze, _bioi, वाला,
+ {{0xa9f385b3,0x8c428a0e,0xa3cc016f,0x5f948193}}, // ेच्छ_, меще, लाय_, _нист,
+ {{0xdd3a8012,0x22430a0f,0x6b898a10,0x6442802a}}, // tăţi, _kijk_, rgeg, _dioi,
+ {{0x6b898a11,0x645c0a12,0x75eb02d0,0xfaff067f}}, // sgeg, ypri, _yüze, _reël_,
+ {{0x27e906c0,0x600a0a13,0xe5c48a14,0x3ea00a15}}, // _blan_, ином_, _эсто, _spit_,
+ {{0xd9a60a16,0x64428081,0x7a30016d,0xdb1d016a}}, // _ऑस्ट, _gioi, mäte, _ansí,
+ {{0xdb0f0a17,0x8db6004a,0x11d58a18,0x51f58a19}}, // _facé, осві, _мікр, _وستر,
+ {{0x645c0a1a,0xcb368039,0x27e90a1b,0x672d069f}}, // tpri, _ואני_, _elan_, _dzaj,
+ {{0x656e0834,0x236001ed,0xdb0b816a,0x2ea681d0}}, // rabh, tbij_, _magí, _गन्त,
+ {{0x27e90a1c,0x656e0a1d,0x645c0a1e,0xf34e8870}}, // _glan_, sabh, rpri, _hụrụ_,
+ {{0xe1fa0a1f,0x23600613,0x3ea00a20,0x53368158}}, // [440] ага_, rbij_, _upit_, ַנען_,
+ {{0x244e0a21,0xf6530039,0x645c0a22,0xf77f0214}}, // _tým_, _קצת_, ppri, rkçe_,
+ {{0x321a0873,0x7c240a23,0x224301ed,0xf34e8135}}, // ropy_, plir, _dijk_, _mụrụ_,
+ {{0xf34e8133,0xdb040174,0x7a300198,0xb09a83de}}, // _lụrụ_, idiú, däte, ייער,
+ {{0x7bc60a24,0xddc98a25,0x2d588a26,0xdce701a9}}, // _inku, _sveš, биль_, majā,
+ {{0xddc9801b,0x624901a1,0x8cb88a27,0x78af0198}}, // _kteř, _džod, ्मलो, äivä,
+ {{0xc0c900a9,0x9cca8a28,0x37e680e8,0x3ea981b9}}, // _луѓе_, рыла_, _довг, ħata_,
+ {{0xd00a8a29,0x68e30257,0x39478a2a,0x7dd00198}}, // _тебе_, ønde, _agns_, _väsy,
+ {{0x77628a2b,0xf34e8a2c,0x661c0a2d,0xc1a68a2e}}, // mbox, _bụrụ_, kork, ојни,
+ {{0x661c0a2f,0x64a68a30,0x68e901d0,0x776288dc}}, // jork, _маза, _ředi, lbox,
+ {{0x27e90220,0xf2df001c,0x7bc60a31,0xc9530039}}, // _plan_, _ngân_, _onku, ומת_,
+ {{0x645a8a32,0x68318a33,0x77628a34,0x6724018e}}, // _itti, rådg, nbox, _byij,
+ {{0xf7738039,0x661c0a35,0x99d381f9,0x63b60428}}, // וקר_, fork, ستیا, ddyn,
+ {{0xdcfa8214,0x2d9e8267,0x661c0a36,0xf34e8133}}, // _hatı, _jbte_, gork, _gụrụ_,
+ {{0xdcfa87d9,0x224b0a37,0x224300f3,0xdb6b02dc}}, // _katı, _suck_, _rijk_, ирал_,
+ {{0x69d88a38,0x27e90a39,0x3eb00009,0xf34e8133}}, // _hove, _ulan_, ältä_, _zụrụ_,
+ {{0x161a8a3a,0x672d0a3b,0x69d88a3c,0x2aab0a3d}}, // [450] धवार_, _uzaj, _kove, штво_,
+ {{0x69d88a3e,0x645a8a3f,0x661c00dd,0x7bc60a40}}, // _jove, _otti, cork, _enku,
+ {{0x69d88039,0xef58819d,0xb4b50a41,0x90990a42}}, // _move, _ịfụn, _эйнш, свет_,
+ {{0x2bb68a43,0x6d488a44,0x224300f3,0x59c9001b}}, // _अफगा, _agda, _wijk_, राहर,
+ {{0x645a8a45,0xf1bc8076,0x68e3006a,0x9e6480e8}}, // _atti, ्ञान, øndb, двід,
+ {{0x76450101,0x7a3001ec,0xb5fb002a,0x764d02d0}}, // _jihy, täte, lpáb, _muay,
+ {{0x69c70a46,0x459c0158,0x5ed380ab,0xdcfa807e}}, // _inje, יסגע, _দেখে, _batı,
+ {{0xf7738a47,0x6e958009,0x7a300a48,0xf1c906ab}}, // _زار_, _нибу, räte, रावन,
+ {{0x81ae0a49,0x69d88613,0x645a8a4a,0xa3cc0935}}, // কার_, _bove, _etti, लात_,
+ {{0x69d88a4b,0x7e698135,0x99158a4c,0x2484819d}}, // _cove, _iwep, зьмі, _ịmee_,
+ {{0x69d88a4d,0x6d55009a,0x6c4980f7,0x661c0a4e}}, // _dove, dcza, _الصف_, vork,
+ {{0x764d0a4f,0x201d8035,0x3ebf8a50,0x69ce8a51}}, // _buay, nowi_, _krut_, öber,
+ {{0x63a98a52,0xdce881d0,0xa3b48074,0xf34e8135}}, // meen, _oddě, _छोट_, _tụrụ_,
+ {{0x63a98a53,0x7e698a54,0x225f859e,0x69d88a55}}, // leen, _mwep, mpuk_, _gove,
+ {{0xdcfa8457,0x8af00085,0xdce701a9,0xdb0f0144}}, // _yatı, hbət, tajā, _oncó,
+ {{0x24518a56,0x69c70a57,0x69d88a58,0x661c0088}}, // _mám_, _anje, _zove, sork,
+ {{0x661c0057,0x764d0a59,0x765b8a5a,0x7e698133}}, // [460] pork, _guay, _ntuy, _nwep,
+ {{0x69d8862f,0x14bd885d,0x63a98a5b,0x42230073}}, // _xove, ्माण, heen, едув,
+ {{0x24518a5c,0x81ae00c8,0x63a98a5d,0x62960a5e}}, // _nám_, কাল_, keen, styo,
+ {{0x20020a5f,0x7bc6029b,0x26c00503,0x63a98a60}}, // niki_, _unku, _krio_, jeen,
+ {{0x1dc78a61,0x63a98a62,0xdb060106,0x64460a63}}, // लांत, deen, _bakå, _iiki,
+ {{0xdcfa82bb,0x68e18a64,0xf1d0801c,0xdb0f03a8}}, // _satı, kyld, _mạch_, _encó,
+ {{0x2451801c,0x645800e7,0x24778087,0x7e698870}}, // _cám_, _évid, găm_, _ewep,
+ {{0x63a98079,0x75eb0201,0x24518a65,0xa6d380ab}}, // geen, _müza, _dám_, _দেওয়,
+ {{0x644607ca,0x20020a66,0x36678a67,0x5a348a68}}, // _miki, diki_, _нато_, мнит,
+ {{0x68318370,0x64460a69,0x68e1813c,0x62840084}}, // råde, _liki, fyld, irio,
+ {{0x20020a6a,0x80df80c8,0x9b46803d,0x63a98a6b}}, // fiki_, _ফেব্, _فناو, been,
+ {{0x64460a6c,0x683f0110,0xf1d0801c,0x7bd985ee}}, // _niki, _kūdi, _bạch_, _gowu,
+ {{0x9f4d816d,0x7c228a6d,0x27e68a6e,0x69d88a6f}}, // _umeå_, _ikor, ikon_, _tove,
+ {{0x644603c3,0x4a7b812a,0x62840a70,0x5558002e}}, // _aiki, _פרוב, drio, заря_,
+ {{0x64460a71,0x2c5580eb,0x20020a72,0x6d550035}}, // _biki, _kāda_, biki_, rcza,
+ {{0x64460a73,0x683f0029,0x671e000f,0x69c702ce}}, // _ciki, _līdz, _युवक_, _snje,
+ {{0x4422013c,0xe802009a,0xf1d080ff,0x5edc8264}}, // [470] _dkk_, _लगता_, _gạch_, _মেসে,
+ {{0xda0c8a74,0x2fc9022c,0x8af00085,0x20b08a75}}, // िचित_, _hnag_, vbət, _जनाध,
+ {{0x6284040e,0x64460a76,0xdb0b8065,0x7c228a77}}, // ario, _fiki, _magá, _okor,
+ {{0xdb0f05a4,0x64460a78,0x3ebf8a79,0x321e8a7a}}, // _hací, _giki, _prut_, hoty_,
+ {{0x44290a7b,0x2451826f,0x4ea781e2,0x9f4b0216}}, // mla_, _sám_, яржа, licó_,
+ {{0x44290a7c,0x20020a7d,0x7c228a7e,0x63a98079}}, // lla_, ziki_, _akor, ween,
+ {{0x58840a7f,0x44290a80,0x63a98a81,0xd5fb819d}}, // _пыта, ola_, teen, _apụ,
+ {{0x24518775,0x69dc8884,0x44290a82,0x7a300a83}}, // _vám_, ören, nla_, mäta,
+ {{0x44290695,0x3ebf845f,0x27ff8a84,0x63a98a85}}, // ila_, _urut_, _imun_, reen,
+ {{0x44290a86,0x13068364,0x81bc80eb,0x7c228a87}}, // hla_, ьный_, blēm, _ekor,
+ {{0x20020a88,0x66038a89,0xf1d0801c,0x2fc90a2a}}, // tiki_, mink, _sạch_, _bnag_,
+ {{0xad1a893f,0x3f8f859c,0x44290a8a,0x9f4b06a5}}, // _ווער, nggu_, jla_, dicó_,
+ {{0x44290a8b,0x7c298179,0x20020a8c,0x62898a8d}}, // dla_, mler, riki_, queo,
+ {{0x66038a8e,0x64460a8f,0x64a60a90,0x20020a91}}, // nink, _siki, мага, siki_,
+ {{0x27ed8a92,0x64460a93,0x80160a94,0xe6430a95}}, // _olen_, _piki, _офиц, терп,
+ {{0x7c2987d9,0xdb008a96,0x26c00a97,0x2d840214}}, // nler, lemá, _trio_, şme_,
+ {{0x41c901fe,0xec098104,0x66038a98,0xdb0f04c3}}, // [480] रांस, yến_, kink, _fací,
+ {{0x64460a99,0xceb40086,0x66038a9a,0x27ed8669}}, // _wiki, _isə_, jink, _alen_,
+ {{0x44290a9b,0x64460a9c,0x7c298a9d,0x66038a9e}}, // bla_, _tiki, kler, dink,
+ {{0x62840a9f,0x44290aa0,0xe8d680be,0xbf9b0129}}, // prio, cla_, ווער_, hiên,
+ {{0x27e68aa1,0x7c298aa2,0x22478aa3,0x66038aa4}}, // rkon_, dler, _oink_, fink,
+ {{0x27e68aa5,0xa3cc009a,0x44200aa6,0x7bdd81ec}}, // skon_, लाव_, loi_, ösun,
+ {{0x7c298aa7,0xdcfe0214,0xbf9b03a7,0x27ed8aa8}}, // fler, _kapı, diên, _flen_,
+ {{0xfbdf0104,0x69dc0aa9,0x7c298aaa,0x44200aab}}, // _liên_, _hore, gler, noi_,
+ {{0xc9868aac,0x34ab0aad,0xdb0b8aae,0x6aa880d4}}, // муни, _चन्द, _pagá, कबेर,
+ {{0x44290aaf,0x645e0289,0x7c298ab0,0xfbdf001c}}, // zla_, _otpi, aler, _niên_,
+ {{0x69dc0ab1,0x442902bb,0x7c298620,0x2247879f}}, // _more, yla_, bler, _dink_,
+ {{0xe3638ab2,0xca488104,0x69dc0ab3,0x7c228ab4}}, // _акци, _lời_, _lore, _ukor,
+ {{0x8c438ab5,0x44290ab6,0xe81f800c,0x3f85017b}}, // _реце, vla_, यकता_, ğlu_,
+ {{0xbf9b03a7,0x69dc021e,0x9f4b0216,0x06e180ab}}, // ciên, _nore, ticó_, _মেডি,
+ {{0x62868ab7,0x44290ab8,0xdb0f05a4,0x76488ab9}}, // škod, tla_, _vací, _midy,
+ {{0x66038aba,0x44200abb,0x44290365,0xdb0d03a7}}, // zink, goi_, ula_, ndaç,
+ {{0x66038abc,0x63a28abd,0xdce7009a,0x5a350abe}}, // [490] yink, _mbon, dają, енат,
+ {{0x69dc0abf,0x44290ac0,0x7c2987d9,0xddc40668}}, // _core, sla_, zler, _zviž,
+ {{0x66038ac1,0x69dc0ac2,0x44200ac3,0x27ed86c0}}, // vink, _dore, boi_, _plen_,
+ {{0x6d5a8ac4,0xdd0d009a,0x66038ac5,0x44290085}}, // _afta, półp, wink, qla_,
+ {{0x69dc0ac6,0x660381e2,0x63ad0ac7,0x36698ac8}}, // _fore, tink, mean, чало_,
+ {{0x63ad0ac9,0x69dc816d,0xed598aca,0x65750168}}, // lean, örel, чок_, nazh,
+ {{0xdcfe0179,0x7c298179,0xa3e38074,0x7bdd0acb}}, // _yapı, tler, _नकद_, _kosu,
+ {{0x66038acc,0x63ad0acd,0x7f85803f,0x7f4d0ace}}, // sink, nean, _سلطن, _ngaq,
+ {{0x69c403b7,0x7c298179,0x3d0605b3,0xdb008acf}}, // राची, rler, _हेने_, temá,
+ {{0x63ad0ad0,0x7c298ad1,0xdb1d016d,0xe1ff009a}}, // hean, sler, _insä, _swój_,
+ {{0x7c298214,0xbf9b0073,0x31710ad2,0xbc6880f7}}, // pler, riên, _mezz_, _يمكن_,
+ {{0x44200ad3,0x6e250ad4,0x60dc8106,0x34bd8ad5}}, // xoi_, _akhb, ärma, ्मीद,
+ {{0x7bcb8ad6,0x63ad0ad7,0x63bb8ad8,0x6d478019}}, // _ingu, dean, mdun, _újab,
+ {{0x63bb8ad9,0xf1d0801c,0x40958ada,0x65750168}}, // ldun, _hạnh_, _зрит, gazh,
+ {{0xdca30adb,0xfbdf0104,0x44200adc,0x32058add}}, // лари, _viên_, toi_, mily_,
+ {{0x63bb8ade,0xc4d28158,0x7bdd0087,0xca4880ff}}, // ndun, נגן_, _cosu, _rời_,
+ {{0x69dc003b,0x629b8a7f,0xfbdf0028,0x44200adf}}, // [4a0] _pore, ntuo, _tiên_, roi_,
+ {{0xf1d08028,0x81e180ab,0x44200ae0,0x171b0039}}, // _lạnh_, নোর_, soi_, _מופע,
+ {{0x69dc0ae1,0x63ad0ae2,0x4420002e,0xdcfe08c5}}, // _vore, bean, poi_, _tapı,
+ {{0x63ad0ae3,0xca4880ff,0x76488122,0x629b8084}}, // cean, _vời_, _sidy, ktuo,
+ {{0x69dc0ae4,0x3f9a809a,0xdce70035,0xfbd3801b}}, // _tore, ępu_, rają, थानम,
+ {{0x7bcb8ae5,0xb90600c8,0x7e628ae6,0x629b8122}}, // _angu, _বই_, rpop, dtuo,
+ {{0x3f6a8ae7,0x987e801b,0x7bdd0ae8,0x7bcb808e}}, // _ниво_, _péče_, _yosu, _bngu,
+ {{0xa2c403b7,0xf1d0801c,0x76488ae9,0x63bb8420}}, // रिप्, _cạnh_, _widy, gdun,
+ {{0xa3e805e8,0x63b98aea,0x7c2084dc,0x3205816b}}, // _भवन_, _dawn, romr, fily_,
+ {{0x7bcb8aeb,0x7c208082,0xa2b18aec,0x2006811b}}, // _engu, somr, _अनर्, lioi_,
+ {{0x6815809a,0x2bb88aed,0xdb1d0106,0x63b98aee}}, // ląda, _आसपा, _inså, _fawn,
+ {{0xc02e8104,0x63a28aef,0x9cd78039,0x20d58196}}, // _điều_, _ubon, _רואה_, džią_,
+ {{0x91e68af0,0x44268af1,0x2d8507a3,0x09e68af2}}, // _поде, _iko_, óleo_, _подн,
+ {{0xa2c40af3,0x92f283bf,0x69ca8af4,0x7bdd0af5}}, // रिन्, ığım, _unfe, _sosu,
+ {{0x63ad0af6,0xe57180be,0xa25b0187,0xb4cb0af7}}, // tean, יַן_, _crôn, लमे_,
+ {{0x657500f1,0x6836002a,0xdb008366,0x44268af8}}, // sazh, táde, remæ, _jko_,
+ {{0x63ad0af9,0x62490a2f,0x75eb007e,0xa7fd02d0}}, // [4b0] rean, _džon, _yüzl, ksız,
+ {{0x63ad0afa,0x99990084,0xafdb0366,0x22b202d6}}, // sean, _rusų_, smød, _dņk_,
+ {{0x63ad0afb,0x78ba01ac,0xa7758afc,0xddc982d0}}, // pean, _štvr, влеч, _ateş,
+ {{0x66070586,0x44268afd,0xe50001a2,0xdb1d0106}}, // lijk, _nko_, ोपरि_, _anså,
+ {{0x7c2d0afe,0x44390aff,0x23690087,0x212b80ff}}, // mlar, _ihs_, mbaj_, ́ch_,
+ {{0x44268b00,0x6da58b01,0xada58b02,0x7c2d0b03}}, // _ako_, тика, такл, llar,
+ {{0x1fa786b5,0x34bd8b04,0x270d009a,0x32058b05}}, // _праг, ्मेद, सपुर_, vily_,
+ {{0x7c2d0b06,0xdb008009,0x629b8b07,0xd6d880ab}}, // nlar, kemä, ttuo, _দেওয,
+ {{0x66070a0f,0x63bb8b08,0x44268b09,0x7c2d011b}}, // kijk, rdun, _dko_, ilar,
+ {{0x3d1a0063,0x7c2d080a,0x44268b0a,0x7d798077}}, // _मुझे_, hlar, _eko_, _آمار_,
+ {{0x7c2d0b0b,0x629b8b0c,0x23e780e8,0xc95204de}}, // klar, stuo, _підв, ימי_,
+ {{0x7bcb8b0d,0x6e238b0e,0x7c2d0457,0x2d81010c}}, // _ungu, lonb, jlar, _adhe_,
+ {{0x443f8b0f,0x7c2d0b10,0x442d8b11,0x26c4001b}}, // mmu_, dlar, mle_, ímo_,
+ {{0x644b8b12,0xb5fb016a,0x2fdf8118,0x3ea90b13}}, // _higi, spán, _loug_, _dpat_,
+ {{0x442d8b14,0x644b8b15,0x7c2d0b16,0x44390b17}}, // ole_, _kigi, flar, _bhs_,
+ {{0x2d810b18,0x442d8b19,0x7c2d0b1a,0x443f8b1b}}, // _edhe_, nle_, glar, nmu_,
+ {{0x442d8219,0x443f8b1c,0xdb0f00f7,0x6e238b1d}}, // [4c0] ile_, imu_, _pacá, konb,
+ {{0x644b8b1e,0xf1aa0077,0x442d8b1f,0x443f8b20}}, // _ligi, باره_, hle_, hmu_,
+ {{0x7c2d0b21,0x443f8886,0x27e00b22,0x442d8b23}}, // blar, kmu_, _koin_, kle_,
+ {{0x443f8b24,0x27e00b25,0x4f0a8b26,0x442d8b27}}, // jmu_, _join_, мнен_, jle_,
+ {{0x442d8b28,0x443f8b29,0x6e238b2a,0x96968098}}, // dle_, dmu_, fonb, треш,
+ {{0x442d8b2b,0x442680b2,0x7c240b2c,0x443f8b2d}}, // ele_, _sko_, loir, emu_,
+ {{0x69ce0b2e,0x644b80b4,0x443902f7,0xeb9f00e8}}, // _inbe, _bigi, _yhs_, _kjøp_,
+ {{0x68e30b2f,0x27e00364,0xeb978b30,0x644b8b31}}, // ände, _noin_, вић_, _cigi,
+ {{0x644b8b32,0xea00801c,0x2d968b33,0x78ad8140}}, // _digi, _nhảy_, _арес, _ćava,
+ {{0x442d8b34,0x7c2d017b,0x644b8b35,0xdb0401df}}, // ale_, zlar, _eigi, ndiñ,
+ {{0x442d8b36,0x44268b37,0x7c2d0b38,0xf9930039}}, // ble_, _tko_, ylar, ברת_,
+ {{0x44268578,0x442d8b39,0x644b8b3a,0x660700f3}}, // _uko_, cle_, _gigi, wijk,
+ {{0xa3cc0b3b,0x7c24062f,0x69ce0b3c,0x4ab6800c}}, // लाई_, doir, _onbe, _अनिव,
+ {{0x44248b3d,0x82778158,0x644b8b3e,0x44390b3f}}, // lom_, _יעדע_, _zigi, _shs_,
+ {{0x7c2d084a,0x66070a0f,0x644b8314,0x2bc4097d}}, // tlar, rijk, _yigi, _लोहा,
+ {{0x69ce0b40,0xe7870b41,0xe41400e8,0xfbdf00ff}}, // _anbe, _руко, адсь, _tiêm_,
+ {{0x7c2d0b42,0x6b828010,0x3b540087,0x64408198}}, // [4d0] rlar, _mdog, ркур, immi,
+ {{0x44248b43,0x2fcd8b44,0x7c2d0b45,0x442d8b46}}, // hom_, _sneg_, slar, zle_,
+ {{0x442d8b47,0x7c2d03bf,0x21a4035f,0x6836027f}}, // yle_, plar, риєм, láda,
+ {{0x7c2d0086,0x44248b48,0xb8db0b49,0x6b828b4a}}, // qlar, jom_, _अन_, _ndog,
+ {{0x442d83a6,0x6e238b4b,0x628b0b4c,0xdd0180d2}}, // vle_, tonb, ágod, _čuđe,
+ {{0x68388b4d,0x6b828b4e,0x68e88106,0x69d80019}}, // víde, _adog, kydd, övet,
+ {{0x442d8b4f,0x443f8b50,0x18b80009,0x644b8b51}}, // tle_, tmu_, _игры_, _pigi,
+ {{0x4424803a,0x442d8b52,0x5f458b53,0x62898b54}}, // gom_, ule_, _منزل, treo,
+ {{0x442d8b55,0x63bd0b56,0x644b8b57,0x11d980f7}}, // rle_, _masn, _vigi, فوظة_,
+ {{0x442d8b58,0x443f8b59,0x62898b5a,0xe0d98b5b}}, // sle_, smu_, rreo, ево_,
+ {{0x442d8b5c,0x77940077,0x27e00072,0x443f859e}}, // ple_, لیغا, _poin_, pmu_,
+ {{0x44248b5d,0xf649010f,0x69da8282,0x672d0b5e}}, // com_, _זצ_, ajte, _byaj,
+ {{0xa9a58b5f,0x7c2400e7,0x27e00198,0x7e7d0b60}}, // _билд, voir, _voin_, gssp,
+ {{0x68e88114,0x7bcf0b61,0x645c841c,0xdb040b62}}, // bydd, _ancu, _éric, leiç,
+ {{0x7c2402be,0x0c7901bb,0x7ae98b63,0xdb008b64}}, // toir, ксты_, lyet, remå,
+ {{0x63bd0b65,0x6d5c0b66,0x62490b67,0xf09281c6}}, // _casn, scra, _džom, _הנה_,
+ {{0x7ae98b68,0x43750b69,0xdb1989c4,0x7c240b6a}}, // [4e0] nyet, руст, _bawé, roir,
+ {{0x7bcf0b6b,0x3c3a8168,0x764b0036,0x7c240b6c}}, // _encu, mëve_, _égyp, soir,
+ {{0x98a68b6d,0x2fd70077,0xdb040b6e,0x3c3a80f1}}, // čiće_, _شوید_, rdiñ, lëve_,
+ {{0x7880003e,0x27e08125,0x61e08b6f,0x63bd0b70}}, // _návš, ðin_, _पक्ष, _gasn,
+ {{0x4a750b71,0x44248b72,0x3c3a80f1,0x7e2a8b73}}, // _выст, vom_, nëve_, ніка_,
+ {{0x63a40b74,0x69ce02af,0xa3cc0b75,0x4424833e}}, // lfin, _unbe, लाक_, wom_,
+ {{0xa1160b76,0x98be83bf,0xdb040187,0xa3cf8a27}}, // _صورت, yatı_, feiç, शां_,
+ {{0xd90d0077,0x6440826c,0x20048b77,0x3c3a80f1}}, // _تیم_, ummi, _ummi_, këve_,
+ {{0x68e88355,0x81bc8029,0x3c3a8168,0x64408b78}}, // wydd, klēt, jëve_, rmmi,
+ {{0x49748b79,0x683881d0,0x3c3a8168,0x24588b7a}}, // шлос, bídc, dëve_, _tém_,
+ {{0x7e640b7b,0x44248b7c,0xea008028,0x48150b7d}}, // _atip, pom_, _chạy_, имос,
+ {{0x44248b7e,0x68e88b7f,0x59d2801b,0x63a400fe}}, // qom_, rydd, ताहर, jfin,
+ {{0x6b7b810f,0x63bd011f,0x5eb880ab,0x62490b80}}, // _טראנ, _rasn, _আপডে, _džoj,
+ {{0xdb070019,0x63bd00eb,0x7e7d0b81,0x6836016b}}, // ámít, _sasn, rssp, ráda,
+ {{0x63bd0b82,0x81da0264,0x683604e8,0x40350b83}}, // _pasn, ়সা_, mádn, _кекс,
+ {{0x59d28b84,0xa3cc0b85,0x75eb0214,0x683603b0}}, // तावर, लाग_, _müzi, ládn,
+ {{0x2ba68b86,0x200b0b87,0x49750b88,0xdb0600e1}}, // [4f0] क्या, mici_, илас, _taký,
+ {{0xdd9103f8,0xddc98b89,0x63a40b8a,0xd91b0198}}, // _بود_, _svež, afin, нье_,
+ {{0x7afb80e5,0xfaf98084,0x155a01c6,0x273b046d}}, // zzut, lbūt_, _בכתב, _fàní_,
+ {{0x200b02a5,0xa29505e9,0x69d80b8b,0x26c90503}}, // nici_, _камі, över, _krao_,
+ {{0x442b026c,0xf8c9826b,0x7c3a0036,0xb5fd8084}}, // _ikc_, _asẹ́_, ître, _atša,
+ {{0x7bc78029,0x644f05a4,0x48770b8c,0x7ae98b8d}}, // ējum, _hici, _مدرس, vyet,
+ {{0x200b0b8e,0xdb1d006a,0x644f0b8f,0x2c5581a9}}, // kici_, _ansø, _kici, _kādi_,
+ {{0x7afb8b90,0x26c9025b,0x73e58ada,0x99908503}}, // tzut, _orao_, роиз, _čaši_,
+ {{0x200b0b91,0x645702f7,0x22158b92,0x5fab80d4}}, // dici_, _luxi, ифор, _टॉयल,
+ {{0x644f0b93,0xe1258615,0x7ae98b94,0x9f5900e5}}, // _lici, импи, ryet, misù_,
+ {{0x7ae98b95,0x200b0b96,0x387e8077,0x63a40b97}}, // syet, fici_, gstr_, yfin,
+ {{0x644f0b98,0x3c3a80f1,0x76438b99,0x200b0b9a}}, // _nici, tëve_, lmny, gici_,
+ {{0xaa678b9b,0x32638019,0xdb0603ec,0x32678237}}, // _стек, _انتہ, _lakò, _стев,
+ {{0x3c3a820f,0xd9458b9c,0x644f0029,0x442b01e0}}, // rëve_, _кели, _aici, _akc_,
+ {{0x442b0267,0x443d8069,0x7643808e,0x61e38b9d}}, // _bkc_, _khw_, imny, _ionl,
+ {{0x3ead803a,0x200b0b9e,0x4aa98b9f,0x61e38019}}, // _opet_, cici_, _कहाव, _honl,
+ {{0x63a40ba0,0x26c904c3,0x02060ba1,0x395600a9}}, // [500] rfin, _grao_, азан, ањет,
+ {{0x69c18ba2,0x61e3810c,0x64570ba3,0x667b00be}}, // ndle, _jonl, _fuxi, _שטיק,
+ {{0x61e38ba4,0x03a60ba5,0x657c0ba6,0x60268ba7}}, // _monl, _кино, marh, аджа,
+ {{0xb8ce023c,0xe6668b02,0x443d831d,0xb8fe0054}}, // _कह_, атко, _nhw_, _तप_,
+ {{0x2fc007e2,0xddd6026f,0xfe468ba8,0xb8660061}}, // _maig_, _zvyš, андо, _چارو,
+ {{0x44320ba9,0x2d980baa,0x200b001b,0x2fc00bab}}, // lly_, ngre_, zici_, _laig_,
+ {{0x443d80dd,0xeb0e81a2,0xa2c40035,0x200b01cc}}, // _bhw_, ाप्त_, रिव्, yici_,
+ {{0x8aa68bac,0x25a9812b,0xe8d70039,0x317801ec}}, // арод, đala_, _דולר_, _herz_,
+ {{0x44320bad,0x78af011f,0x9f490315,0x61e380c3}}, // ily_, čivš, _ilaç_, _bonl,
+ {{0x2c0b89d7,0x44320bae,0x61e38baf,0xc05a81e2}}, // _یعنی_, hly_, _conl, кім_,
+ {{0x245c026f,0x61e38bb0,0x3a370039,0x65770168}}, // _ním_, _donl, שרים_, _rexh,
+ {{0x2fc001c0,0x2a7f8bb1,0xdb1d0176,0x683604e8}}, // _caig_, bsub_, _fasè, sádn,
+ {{0x200b0bb2,0x67010bb3,0x2fc00bb4,0x645701df}}, // rici_, _लेखक_, _daig_, _suxi,
+ {{0x644f0bb5,0xfaf98bb6,0x2b928065,0x442b0bb7}}, // _sici, rbūt_, _ایکس, _skc_,
+ {{0x6ab6890a,0x52b68bb8,0x644f0bb9,0xe7870bba}}, // _अनुर, _अनुस, _pici, _купо,
+ {{0x628d0bbb,0x3b55096b,0x26c9001c,0xd0f781c6}}, // trao, скар, _trao_, _כמות_,
+ {{0xa3a88bbc,0x657c0bbd,0xd2510bbe,0xa2c405ab}}, // [510] ख्य_, barh, سنا_, रिष्,
+ {{0xf9930bbe,0x2ba6800c,0x6838801b,0x644f0114}}, // تبر_, क्ता, vída, _wici,
+ {{0x3ead8353,0x44320bbf,0x644f0bc0,0x2fc0022c}}, // _spet_, bly_, _tici, _yaig_,
+ {{0x442b0bc1,0xa2c40bc2,0x1ab686a7,0x628d0140}}, // _ukc_, रिश्, _अनूठ, prao,
+ {{0x44290bc3,0x683d02be,0x6fd8016f,0x7a3d80e7}}, // moa_, céde, यातू, lète,
+ {{0x44290bc4,0x7aed05ee,0x764389da,0xc5f400fc}}, // loa_, myat, umny, _tuɓe_,
+ {{0x22590bc5,0xfc3f0028,0xa3cc016f,0x764382f7}}, // _husk_, _khí_, लाच_, rmny,
+ {{0x44290bc6,0x5f948bc7,0x24898bc8,0x2a7f8bc9}}, // noa_, _мист, čam_, tsub_,
+ {{0x8d878bca,0xfc3f00f7,0x62808bcb,0x645c8019}}, // _نشان, _mhí_, gsmo, _érin,
+ {{0x44290bcc,0x20190bcd,0x20090bce,0x673b8bcf}}, // hoa_, érin_, _amai_, _izuj,
+ {{0x44290bd0,0x7aed008e,0x69c18bd1,0x62808a53}}, // koa_, hyat, rdle, asmo,
+ {{0xc5e9007c,0xfc3f00ff,0x44290bd2,0xfbdf0129}}, // _אד_, _nhí_, joa_, _kiêu_,
+ {{0x2fc0009f,0xdb098bd3,0x44290bd4,0xf3ff00ab}}, // _vaig_, rdeó, doa_, ্কার_,
+ {{0x61fa8bd5,0xfc3f0693,0x7aed0bd6,0x69dc8bd7}}, // chtl, _ahí_, dyat, öret,
+ {{0x40958307,0xfc3f0307,0x2fc0022c,0x44290bd8}}, // _العر, _bhí_, _taig_, foa_,
+ {{0x4429011e,0x3ce500f2,0xfc3f0028,0x7ff6026a}}, // goa_, älva_, _chí_, _دستا,
+ {{0x44320051,0x9ad381bc,0x68e305ec,0x77640609}}, // [520] rly_, _aịzi, ändn, _ifix,
+ {{0xc3328051,0x245c000d,0x44320bd9,0x51560bda}}, // לון_, _tím_, sly_, стау,
+ {{0x44320bdb,0xef6582bb,0x2c5580eb,0x44290bdc}}, // ply_, ğını_, _kādu_, boa_,
+ {{0xef658059,0xea00801c,0x7c298123,0x22590bdd}}, // şını_, _thầy_, joer, _fusk_,
+ {{0x317802a6,0x673b8035,0x7aed018f,0x661a808e}}, // _terz_, _czuj, cyat, _pjtk,
+ {{0x99d60277,0x21f600f7,0x63b60bde,0x62960bdf}}, // _اتحا, _اكسس, leyn, muyo,
+ {{0xec098028,0x320c82d6,0x62960be0,0x44c780ff}}, // yết_, vidy_, luyo, _cđ_,
+ {{0x8fa38be1,0xc7a38be2,0x62808be3,0x62868be4}}, // _насе, _ниск, tsmo, škom,
+ {{0x6aa40be5,0x62960214,0x200900ee,0x6d43016b}}, // ntif, nuyo, _rmai_, _únav,
+ {{0x660e0be6,0x44290102,0x60ca82f7,0x200900b9}}, // jibk, zoa_, _prfm, _smai_,
+ {{0x7c298be7,0x201903a7,0xe1ff0118,0x44290122}}, // boer, ério_, _otón_, yoa_,
+ {{0x7aed0be8,0x6aa40590,0xf5378039,0x313704de}}, // yyat, ktif, _פנאי_, מנים_,
+ {{0x7336803d,0x657a80d7,0xf1c800d4,0x63b60be9}}, // _ارائ, _keth, रयान, deyn,
+ {{0x329b8bea,0xe1ff0118,0x77640118,0xe7cf8beb}}, // _עבוד, _atón_, _efix, _सोनप,
+ {{0x44290bec,0xf1a98077,0xfc3f0028,0x657a8bed}}, // toa_, _خانه_, _phí_, _meth,
+ {{0x645a8bee,0x657a8bef,0x317e81ec,0x200900b9}}, // _muti, _leth, latz_, _umai_,
+ {{0x44290bf0,0x645a8bf1,0x62960bf2,0x8af78085}}, // [530] roa_, _luti, guyo, _çəti,
+ {{0x645a82be,0x44290bf3,0x7aed0bf4,0xa3a883eb}}, // _outi, soa_, ryat, ख्त_,
+ {{0xe8e00142,0x2bc40bf5,0xfbdf0129,0x3178809a}}, // ười_, _लोका, _siêu_, órzy_,
+ {{0x2d9e8bf6,0x317e8bf7,0x657a8286,0x473583a7}}, // _acte_, hatz_, _aeth, онес,
+ {{0x539a0051,0x657a8bf8,0x7c29838e,0x6aa40bf9}}, // _דירו, _beth, voer, ctif,
+ {{0x645a8bfa,0x25a9812b,0x63ab80b9,0xdc3c81a9}}, // _buti, đalo_, _sbgn, kšēj,
+ {{0x69d50bfb,0x645a802e,0x8ccc0b9f,0x7c29800b}}, // _inze, _cuti, हियो, toer,
+ {{0xfbdf0028,0x69c50bfc,0x7bc28036,0x657a8bfd}}, // _tiêu_, ldhe, _caou, _eeth,
+ {{0x7bc28bfe,0x6fdd016f,0x657a8bff,0x0ae68009}}, // _daou, यानं, _feth, ждый_,
+ {{0x69c50c00,0x200f8c01,0x88858758,0x657a8c02}}, // ndhe, ligi_, олож, _geth,
+ {{0xf9920bea,0x69c50046,0x645a8c03,0x7a300009}}, // ורי_, idhe, _guti, mätt,
+ {{0x7a300c04,0x200f8c05,0x777b80e7,0x26cd8c06}}, // lätt, nigi_, _jeux, _kreo_,
+ {{0xe9d98c07,0x3f890114,0x9f448168,0x69d50c08}}, // _вкл_, _ddau_, _romë_, _onze,
+ {{0x20190c09,0x317e83e4,0xdce8801b,0x63b60079}}, // ansi_, catz_, _nedě, weyn,
+ {{0x2c5580eb,0x7bc28c0a,0x69c50c0b,0x68e30192}}, // _tādu_, _yaou, ddhe, ändl,
+ {{0x69c38c0c,0x69d50352,0x7bc40358,0x62960c0d}}, // _hane, _anze, udiu, tuyo,
+ {{0xb4b683bb,0xe29a0c0e,0x69c38c0f,0x2c5580eb}}, // [540] _छन्_, раз_, _kane, _kāds_,
+ {{0x69c38c10,0xea008104,0x69c50c11,0x62960c12}}, // _jane, _thấy_, gdhe, ruyo,
+ {{0x02b68aed,0xd91009a7,0x657a8c13,0x69c385d8}}, // _अन्न, ریر_, _reth, _mane,
+ {{0x69c3811e,0xd7dd016f,0x657a8c14,0x26cd8118}}, // _lane, यायच, _seth, _breo_,
+ {{0x26cd8c15,0x645a8c16,0x777b80e7,0x7bc28c17}}, // _creo_, _suti, _deux, _raou,
+ {{0x62860c18,0x7c36008b,0x63a98c19,0x7bc28c1a}}, // _ivko, llyr, efen, _saou,
+ {{0x63a98c1b,0x5fca0c1c,0xb5fb0c1d,0xf77f02d0}}, // ffen, ियाल, mpát, yaç_,
+ {{0x7a34809f,0x657a8c1e,0x69c38c1f,0x26cd802a}}, // màti, _weth, _aane, _freo_,
+ {{0x62840c20,0x69c38c21,0x2fc6849c,0x38698c22}}, // nsio, _bane, mdog_, _čara_,
+ {{0x094a8abe,0x645a8c23,0x914a8c24,0x3ebb84de}}, // ични_, _tuti, ична_, _דצמב,
+ {{0x69c38c25,0x645a8364,0x61e302af,0x4254803d}}, // _dane, _uuti, önli, _کنتر,
+ {{0x62840c26,0x2fc68c27,0x317e81ec,0x2ba68c28}}, // ksio, ndog_, satz_, क्शा,
+ {{0x3ea68c29,0x0aba8c2a,0x7a2f8aa2,0xead48791}}, // ntot_, _خطاب_, tøtt, _холь,
+ {{0x81f88c2b,0x62840c2c,0x2ba68c2d,0x200f8c2e}}, // _دفتر_, dsio, क्रा, zigi_,
+ {{0xd37b0c2f,0x64428c30,0x271c8028,0x09ac80ab}}, // ича_, _khoi, ỉnh_, _কোথা,
+ {{0x248d0c31,0x683f00eb,0x6b80804f,0xdb198144}}, // čem_, _lūdz, namg, _hawá,
+ {{0x2bbd8c32,0x34b78c33,0x27e90c34,0x4abc80d4}}, // [550] ्यता, _आन्द, _ioan_, ्टिव,
+ {{0x69c3862f,0x78a50125,0x27e900ff,0xdb00841c}}, // _xane, tthv, _hoan_, lemó,
+ {{0x69c50c35,0x26cd82a5,0x7fd58084,0x7e698c36}}, // rdhe, _sreo_, зілі, _step,
+ {{0x764183ed,0x69c503ed,0x27e90c37,0xdb0b8176}}, // _shly, sdhe, _joan_, _magò,
+ {{0x2bc40076,0x200f8c38,0x7c2d0c39,0x7bda0039}}, // _लोटा, rigi_, moar, _הקרו,
+ {{0x7c2d0c3a,0x26cd8087,0xdd918c3b,0x1dcb0512}}, // loar, _vreo_, روع_, ायित,
+ {{0x7a300c3c,0x6442832f,0x69c38c3d,0x6ab90085}}, // rätt, _bhoi, _rane, şafı,
+ {{0x7a300c3c,0x69c3803d,0x57b48c3e,0x64428c3f}}, // sätt, _sane, _обст, _choi,
+ {{0xd5bb8c40,0x64428c41,0x63a98c42,0x3ebf8573}}, // _все_, _dhoi, rfen, _usut_,
+ {{0xdb0d0c43,0x63a98c44,0x7c2d0c45,0x1306862c}}, // ndañ, sfen, hoar, яный_,
+ {{0xf992093f,0x69c38074,0x6442861f,0x7c2d011e}}, // ערט_, _vane, _fhoi, koar,
+ {{0xb4d78c46,0x69c38c47,0xa3bb8c48,0x64428c49}}, // ामी_, _wane, _خاطر_, _ghoi,
+ {{0x7c2d0c4a,0x27e90c4b,0x64498c4c,0x24980c4d}}, // doar, _doan_, mmei, wurm_,
+ {{0x64498c4e,0x2c5580eb,0x57470a41,0x63af0163}}, // lmei, _tāds_, язям_, _abcn,
+ {{0x2eb88424,0x7981838a,0x33f60c4f,0x8cce8035}}, // _इन्त, malw, пчас, थियो,
+ {{0x62840665,0x644981e4,0x7c2d0c50,0x1e860c51}}, // tsio, nmei, goar, _елим,
+ {{0x2bad835a,0x9f448c52,0x62840110,0x26c00c53}}, // [560] झ्या, _tomé_, usio, _usio_,
+ {{0x3f820500,0x79818c54,0x2bb8825e,0x200d8187}}, // laku_, nalw, _आस्थ, _amei_,
+ {{0x62840c55,0x23bd83dd,0xfbbd824c,0x201c83a7}}, // ssio, ्याद, ्याम, évio_,
+ {{0x3f820c56,0x61fe0c57,0x657e01e0,0x7a3001ec}}, // naku_, shpl, _heph, täts,
+ {{0x5fdd016f,0x78828019,0xb5fb0c58,0x442d8c59}}, // यातल, _kívá, mpár, doe_,
+ {{0x645e0c5a,0x7bc60c5b,0x64428c41,0xe5348c5c}}, // _kupi, _haku, _shoi, мель,
+ {{0x7bc60c5d,0x64428c5e,0x3ea68c5f,0xa2c40aed}}, // _kaku, _phoi, stot_, रिक्,
+ {{0x293700be,0x7bc60558,0x657e0c60,0xdb00802a}}, // _זאלן_, _jaku, _leph, xemó,
+ {{0x7bc60c61,0x3f820c62,0xdb0d008b,0x64a080c3}}, // _maku, daku_, ndað, dšiš,
+ {{0x7bc60590,0x657e0c63,0xeaaf880b,0x39588aa2}}, // _laku, _neph, اعي_, ørs_,
+ {{0x64428c64,0x3f820c65,0x6d5a8bb1,0x5f950c66}}, // _thoi, faku_, _igta, минт,
+ {{0x1ae68c67,0x3f820c68,0x04db8039,0x7c2d0102}}, // _ноем, gaku_, _לקבל, xoar,
+ {{0xa3c00c69,0x7c2d0087,0x79818c6a,0x63a28c6b}}, // ंजन_, voar, balw, _icon,
+ {{0x91e38c6c,0x09e38c6d,0x611484bd,0x7bc602d5}}, // _поче, _почн, _одлу, _aaku,
+ {{0x7c2d0c6e,0x7bc60c6f,0x69c8831d,0x69a18beb}}, // toar, _baku, mdde, _खाती,
+ {{0x645e0c70,0x7bc60c71,0x2c0d016f,0x69c88c72}}, // _dupi, _caku, _सगळं_, ldde,
+ {{0x4a550c73,0x7c2d0102,0x7bc60c74,0x32550c75}}, // [570] дкас, roar, _daku, двар,
+ {{0x7c2d0c76,0x6838800d,0x69c88c77,0xa2bf8c78}}, // soar, bídk, ndde, _वनस्,
+ {{0x7bc60c79,0x2d830c7a,0x7c2d0c7b,0x61ea8144}}, // _faku, maje_, poar, _mofl,
+ {{0x2d9c04b8,0x27e987ca,0x2d830c7c,0x68360187}}, // _över_, ðan_, laje_, tádi,
+ {{0x6fa50c7d,0x63ad0c7e,0x69d880c9,0x62490c7f}}, // _कानू, mfan, _onve, _džor,
+ {{0x2d830c80,0x7bc60c81,0x63ad0c82,0x68360c83}}, // naje_, _zaku, lfan, rádi,
+ {{0x7bc60c84,0xb4d78054,0x645800e7,0x442d8c85}}, // _yaku, ामे_, _évit, toe_,
+ {{0xeb9980af,0x69d88c86,0x2bda8c87,0x63ad0c88}}, // ции_, _anve, भाषा, nfan,
+ {{0x442d8baf,0x69c70c89,0x64498c8a,0x79818c8b}}, // roe_, _kaje, rmei, talw,
+ {{0x63a28c8c,0x64498c8d,0x78a10106,0x442d852a}}, // _econ, smei, _älvs, soe_,
+ {{0x39458c8e,0x3f820c8f,0x02ba03e8,0x61ea8c06}}, // ьног, taku_, _उन्न, _dofl,
+ {{0x645e0c90,0xe73a0c91,0x657e0c92,0x69c70077}}, // _rupi, жем_, _seph, _laje,
+ {{0x7bd981bc,0x63bb8c93,0x63ad0c94,0x7c2d8c95}}, // _inwu, meun, dfan, čard,
+ {{0x2bbd87e6,0x69c70c96,0x2d830c97,0x3f820c98}}, // ्यवा, _naje, gaje_, saku_,
+ {{0x64a30c99,0x7bc60c9a,0xed570c9b,0x6286816b}}, // кара, _paku, дор_, ákov,
+ {{0x7bc98c9c,0xaac900d4,0x04430c9d,0x672401c0}}, // ndeu, रिंक, весн, _txij,
+ {{0x69c70c9e,0x6aa98c9f,0xc1ea0ca0,0x7bc60ca1}}, // [580] _baje, ntef, одаж_, _vaku,
+ {{0x7c290025,0x7bc60ca2,0x69c70ca3,0x26d200b4}}, // čera, _waku, _caje, _iryo_,
+ {{0x106a0ca4,0xb4d7852a,0x7640001b,0x7bd98133}}, // цией_, ामो_, ůmys, _onwu,
+ {{0x63bb8ca5,0x680b066f,0x00000000,0x00000000}}, // jeun, wędz, --, --,
+ {{0xa3e1873c,0x69c88ca6,0x6d418748,0x6b840ca7}}, // दान_, ydde, _azla, maig,
+ {{0x6b840c5e,0x69c70ca8,0x7bd981bc,0x63a28ca9}}, // laig, _gaje, _anwu, _scon,
+ {{0xdb0b8125,0xd839012b,0x7e7b8caa,0x7bc98cab}}, // _sagð, luče_, _ewup, fdeu,
+ {{0x69c70025,0x6b840cac,0x2d830cad,0xd9430cae}}, // _zaje, naig, zaje_, _реси,
+ {{0x7e7b81bc,0x27e6810c,0x83fc8bcf,0x752d861c}}, // _gwup, mjon_, _nuđe, ğaza,
+ {{0x27e68caf,0x6b840cb0,0x765f0359,0x24f68cb1}}, // ljon_, haig, _ruqy, _очер,
+ {{0xdb1d026f,0x2d830cb2,0x69c88114,0x4422008e}}, // _masá, vaje_, rdde, _ojk_,
+ {{0x7e608cb3,0x68e30cb4,0xb8e6809a,0x63bb8118}}, // _kump, ändi, _उन_, ceun,
+ {{0x6b840cb5,0x19948cb6,0xd8390968,0x7c228cb7}}, // daig, _заля, juče_, _ijor,
+ {{0xc1c803bb,0x7c2d807d,0x69d88cb8,0x7e608cb9}}, // रयोग, čare, _unve, _mump,
+ {{0x2d830cba,0x7e608cbb,0x69c70cbc,0x63ad007b}}, // raje_, _lump, _raje, tfan,
+ {{0x2d830cbd,0xeac88028,0x25a98289,0x69c70cbe}}, // saje_, _kỹ_, đali_, _saje,
+ {{0x63ad0cbf,0x7e608cc0,0x79a70cc1,0x4caa80ab}}, // [590] rfan, _nump, дрее, কিটু,
+ {{0xeac88028,0x2fc90cc2,0x7c228cc3,0x63ad0cc4}}, // _mỹ_, _haag_, _ljor, sfan,
+ {{0x6b840110,0x61e7816d,0x69c70cc5,0x63ad0cc6}}, // baig, öjli, _vaje, pfan,
+ {{0x69c703c3,0x66150cc7,0xd839026c,0x6b840cc8}}, // _waje, lizk, buče_, caig,
+ {{0x7e608cc9,0xb4d78cca,0xdb1d0ccb,0x644d0ccc}}, // _cump, ाम्_, _ansö, mmai,
+ {{0x7c228ccd,0x2fc903b2,0x7a2b02af,0xa5948081}}, // _ajor, _laag_, hütz, връщ,
+ {{0x271b0cce,0x63bb85f8,0x97a7846e,0xdce380eb}}, // नपुर_, teun, _орал, manī,
+ {{0x6aa981a3,0xee3f00e1,0x2d810ccf,0x75fd82d6}}, // ttef, dtým_, _lehe_, _dèza,
+ {{0xc6a48cd0,0x7bc98cd1,0x4ea48b02,0x7c228cd2}}, // _архи, rdeu, _арха, _djor,
+ {{0xddc4003b,0xb5fd8cd3,0xdb040cd4,0x63bb8cd5}}, // _otiš, _buše, nfiá, seun,
+ {{0x83fc803b,0x07a60112,0xf1b200be,0x6d418cd6}}, // _suđe, нанн, נסט_, _uzla,
+ {{0x7c228cd7,0x2fc90cd8,0xb5fd8cd9,0x60da0061}}, // _gjor, _caag_, _duše, _átme,
+ {{0x79850748,0x2fc90cda,0x2d810cdb,0xdce18084}}, // kahw, _daag_, _behe_, _pelė,
+ {{0x6a178065,0xd8390024,0x3ea2816d,0x27ff8114}}, // _تبصر, vuče_, _åkte_, _llun_,
+ {{0x62868cdc,0xb5fd811f,0x6b840cdd,0x7c29042b}}, // škot, _guše, taig, čern,
+ {{0x27ed8cde,0x83fc80d2,0x8b950cdf,0x7c3b820d}}, // _noen_, _tuđe, круч, nlur,
+ {{0xdb1d0ce0,0x64598ce1,0x22580084,0x49960162}}, // [5a0] _pasá, _miwi, _pirk_, ешет,
+ {{0xe7ac01c4,0x6b84008c,0x644d0ce2,0x27ff8ce3}}, // ट्रप, saig, amai, _alun_,
+ {{0x644d0ce4,0x7d7b0039,0xdb1d0019,0x6d5e033e}}, // bmai, _מניו, _vasá, _igpa,
+ {{0x4ea68ce5,0x644d09c4,0xc6a68ce6,0x64598ce7}}, // ерка, cmai, ерки, _niwi,
+ {{0x44200ce8,0x27ed81d8,0x44320ce9,0x61f30aa2}}, // mni_, _doen_, moy_, ådlø,
+ {{0x27e68cde,0x443202b8,0xf2df00ff,0xf48700d7}}, // sjon_, loy_, _ngây_, _تایی,
+ {{0x44200cea,0x69dc0ceb,0xd9430cec,0x61430110}}, // oni_, _inre, _бери, _бера,
+ {{0x44200ced,0x2bba8013,0x44320cee,0x7e608cef}}, // nni_, ساعة_, noy_, _tump,
+ {{0x7ae404c3,0xe7e40cf0,0x64598cf1,0x61ee0cf2}}, // nxit, गाना_, _diwi, _kobl,
+ {{0xb5fd8503,0xfce68cf3,0x44320cf4,0x44200cf5}}, // _puše, _позо, hoy_, hni_,
+ {{0x44200cf6,0x7a2b02af,0x4432009c,0x61ee094d}}, // kni_, tütz, koy_, _mobl,
+ {{0x2d8101ec,0x6d5e02c4,0x44320cf7,0x7c3b8cf8}}, // _sehe_, _agpa, joy_, clur,
+ {{0xd6d80cf9,0x2d81026b,0x69dc0cfa,0x44320cfb}}, // нтр_, _pehe_, _onre, doy_,
+ {{0x44200cfc,0x2ee10cfd,0x6b82876d,0x84e4864a}}, // eni_, _पपीत, _heog, _कपाट_,
+ {{0x44200125,0xa4d80cfe,0x765a8cff,0x66150d00}}, // fni_, ндру_, _mity, rizk,
+ {{0x69dc0d01,0x44200d02,0x493b8039,0x44320d03}}, // _anre, gni_, _מגוו, goy_,
+ {{0x61ee0b7f,0x5a3500b3,0x23690d04,0x27ed8d05}}, // [5b0] _bobl, внат, rcaj_, _roen_,
+ {{0x44200d06,0x27ed8d07,0x6b828d08,0x61ee0d09}}, // ani_, _soen_, _leog, _cobl,
+ {{0x44200d0a,0x44320d0b,0x61ee0d0c,0x69ca81bc}}, // bni_, boy_, _dobl, _mafe,
+ {{0x44200042,0x69dc0557,0x69ca8d0d,0x87e48d0e}}, // cni_, _enre, _lafe, люче,
+ {{0x539a0bea,0x6abc8996,0xf770845b,0xe5a58d0f}}, // _קישו, ्टोर, صان_, вили,
+ {{0x0c740077,0x44d101e2,0xdce88029,0x69ca8d10}}, // _جدید, _ką_, _nedē, _nafe,
+ {{0x6b828d11,0xdb04007b,0x27ed8a0f,0x44d10d12}}, // _beog, leið, _toen_, _ją_,
+ {{0xa1580d13,0xbbbd8d14,0x19580d15,0xc4478019}}, // _часу_, ्येक, _часы_, ریشن_,
+ {{0x24808025,0x7c3a00e7,0x7c3b8106,0x64598c53}}, // ćima_, îtri, rlur, _viwi,
+ {{0x44200d16,0x62898d17,0x64598077,0x33f183a8}}, // zni_, rseo, _wiwi, _páxs_,
+ {{0xd7dd053e,0x7bdd0d18,0x34948791,0x7c3b8d19}}, // यांच, _onsu, _бахр, plur,
+ {{0x5b1587eb,0x6b828d1a,0x2d8785d1,0xdb04008b}}, // _имет, _geog, lane_, keið,
+ {{0x6d450d1b,0x76488009,0x25de0d1c,0x2d878162}}, // _azha, _yhdy, खाली_, oane_,
+ {{0x44200d1d,0xa7fd0201,0x7bcb8d1e,0x69ca8135}}, // wni_, qqın, _hagu, _gafe,
+ {{0x6e3c0713,0x443200ab,0x59a68076,0x7bcb8d1f}}, // rlrb, toy_, _खातर, _kagu,
+ {{0x2d878c89,0x6b8284c3,0x44200d20,0x3e660d21}}, // hane_, _xeog, uni_, _tôt_,
+ {{0x61ee0d22,0x2d878d23,0x6b848074,0xdfd20065}}, // [5c0] _pobl, kane_, _õige, _ميں_,
+ {{0x7bcb8d24,0xfe9b04de,0x6d5e0122,0x7ae68106}}, // _lagu, _קיימ, _ugpa, äkti,
+ {{0x2d878d25,0x7e640009,0x4432033e,0x83fc8d26}}, // dane_, _huip, poy_, _luđa,
+ {{0xdb0401fa,0x7bcb8d27,0x59e0000d,0x7e640d28}}, // beið, _nagu, नाहर, _kuip,
+ {{0xa2c1000f,0x779180d5,0x765a8d29,0xe7aa0321}}, // रौद्, صیلا, _pity, _कानप,
+ {{0x63a40d2a,0x2d878d2b,0x61ee00d2,0x68388187}}, // lgin, gane_, _uobl, vídu,
+ {{0x7bcb8d2c,0x6abc82ef,0x69ca8d2d,0xc0e30254}}, // _bagu, ्ट्र, _rafe, _корк,
+ {{0x63a40d2e,0x25ad0d2f,0x83fc8b80,0x69ca8d30}}, // ngin, đeli_, _buđa, _safe,
+ {{0x5a348d31,0xf4130bea,0x67ff00f1,0x7bcb8d32}}, // лнит, יפה_, _bëjn, _dagu,
+ {{0x2d878d33,0x683d0d34,0xbd8a80d7,0x320581c6}}, // cane_, médi, _آنان_, ghly_,
+ {{0x68388187,0xa99b00be,0xbb1b0162,0x61fa8d35}}, // sídu, וביר, _neîn, rktl,
+ {{0x3f848d36,0x7bcb8d37,0x63a401ed,0xc05801e2}}, // _jemu_, _gagu, jgin, кія_,
+ {{0x44d10d38,0x44268d39,0x69ca837a,0x8a0684fa}}, // _są_, _ijo_, _tafe, _изде,
+ {{0x7bc08006,0x27318028,0xb5fd842b,0xdb04008b}}, // lemu, ỡng_, _kuša, veið,
+ {{0x442688cf,0x6d5c81ac,0x20068d3a,0x7bcb8d3b}}, // _kjo_, _úrad, khoi_, _yagu,
+ {{0x2d878d3c,0x7bc08763,0x6aad0d3d,0x2ba689c8}}, // zane_, nemu, ttaf, क्चा,
+ {{0x20190d3e,0x2d878d3f,0x36d50d40,0xab840746}}, // [5d0] misi_, yane_, _сопр, руск,
+ {{0x20190d41,0x44d10d42,0x26c9007d,0xdb04007b}}, // lisi_, _tą_, _isao_, reið,
+ {{0x44268d43,0x7bc08d44,0x21640d45,0x99640d46}}, // _ojo_, kemu, ртуг, ртул,
+ {{0x2d878d47,0xf8b3893f,0x20190d48,0x3f8485f5}}, // wane_, ישע_, nisi_, _cemu_,
+ {{0x4b7a0bea,0x539a0051,0x2d878d49,0x6aa084a8}}, // _ראשו, _אירו, tane_, jumf,
+ {{0xd1268d4a,0x44268d4b,0xa2b880bc,0x20190d4c}}, // _هم_, _ajo_, ्बन्, hisi_,
+ {{0x2d878d4d,0x20190d4e,0x645d0d4f,0x2baa06d4}}, // rane_, kisi_, _kisi, ङ्का,
+ {{0x7bc08d50,0x645d0d51,0x2bda816f,0x3f848d52}}, // gemu, _jisi, भागा, _gemu_,
+ {{0x20190d53,0x2d878d54,0x442682d6,0xe3b68d55}}, // disi_, pane_, _djo_, убы_,
+ {{0x44390d56,0xf99180f7,0x44268d57,0x6e350503}}, // _lks_, حبة_, _ejo_, jozb,
+ {{0x7bcb8d58,0x201906c0,0x63a40d59,0x7a3d823e}}, // _tagu, fisi_, ygin, nèti,
+ {{0x645d0d5a,0x2d858d5b,0xc50c83de,0xbb1b0162}}, // _nisi, _hele_, _קלאָ, _reîn,
+ {{0x443f8d5c,0xd24e803d,0xa3e70d5d,0x63a40850}}, // mlu_, تنی_, पान_, vgin,
+ {{0x443f8d5e,0x64a60d5f,0x2d858d60,0xdca60523}}, // llu_, лага, _jele_, лаги,
+ {{0x69c18d61,0x443f8d62,0x2d858d63,0x5e5680be}}, // mele, olu_, _mele_, טישע_,
+ {{0x69c18d64,0x2d858d65,0x66188503,0xeb9f0aa2}}, // lele, _lele_, zivk, _smør_,
+ {{0x645d0d66,0xe2970d67,0x443f8d68,0x9e5a0d69}}, // [5e0] _disi, лат_, ilu_, _проф_,
+ {{0x69c18d6a,0x645d0d6b,0x44390d6c,0x3f848d6d}}, // nele, _eisi, _eks_, _semu_,
+ {{0x645d0d6e,0x443f8d6f,0x34aa0d70,0x27e00669}}, // _fisi, klu_, _явно_, _knin_,
+ {{0x69c18d71,0x2bab0d72,0x2d8a02ec,0x645d0d73}}, // hele, _छाया, labe_, _gisi,
+ {{0x69c18d74,0xa3d783b7,0x443f8d75,0x60060d76}}, // kele, ायन_, dlu_, _góme,
+ {{0x2d858c6e,0x69c18d77,0xb5fd812b,0x7c240087}}, // _cele_, jele, _puša, lnir,
+ {{0x3f848d78,0x69c18d79,0x7bc08d7a,0x2d858d7b}}, // _temu_, dele, temu, _dele_,
+ {{0x69ce0d7c,0x683d0d7d,0x2d8a0d7e,0x68e30a33}}, // _habe, pédi, habe_, ånde,
+ {{0x69ce0d7f,0x20048d80,0x6b8601a1,0x69c18d81}}, // _kabe, _ilmi_, _mekg, fele,
+ {{0x69c187b3,0x7bc08d82,0x27e00d83,0x69ce0102}}, // gele, semu, _anin_, _jabe,
+ {{0x69ce0d84,0x443f8d85,0x03148d86,0x7bc080dd}}, // _mabe, blu_, _तेरह_, pemu,
+ {{0x31570451,0x2d858052,0x44268d87,0x7c240024}}, // טיין_, _zele_, _ujo_, jnir,
+ {{0xa3e187e6,0xcaf68154,0x26c682f7,0x645d0d88}}, // दार_, _حساب, nwoo_, _risi,
+ {{0x20190d89,0x2d8a0d8a,0x645d0d8b,0x69ce0d8c}}, // sisi_, gabe_, _sisi, _nabe,
+ {{0x44390d8d,0xab5d84b7,0xe4e40d8e,0x02178039}}, // _pks_, reże, бітн, _תחום_,
+ {{0xfbdf0104,0x69a70006,0x33750d8f,0x6b8600b9}}, // _thêm_, _चाही, ргар, _cekg,
+ {{0x52150d90,0x65950d91,0x3f8b0d92,0x2d8a0d93}}, // [5f0] адат, _капу, macu_, babe_,
+ {{0x69ce0510,0x9475026a,0x3b540d94,0x443f8d95}}, // _cabe, _نگرا, скур, zlu_,
+ {{0x69ce0d96,0x2d858205,0x443f8d97,0x44390d98}}, // _dabe, _rele_, ylu_, _tks_,
+ {{0x69c18d99,0x2d858d9a,0x8d74003d,0x3f8b0d9b}}, // zele, _sele_, _پایا, nacu_,
+ {{0x2d858d9c,0x66e58d9d,0xdee58d9e,0x69c18d9f}}, // _pele_, рола, роли, yele,
+ {{0x2bda8da0,0x69c18da1,0x69ce0da2,0x20048085}}, // _मोबा, xele, _gabe, _elmi_,
+ {{0x2d858da3,0x628282a3,0x7e718da4,0x7bcf0da5}}, // _vele_, _awoo, _žepč, _kacu,
+ {{0x69ce0da6,0xd6f70076,0x629b8da7,0x3f8b0da8}}, // _zabe, ुनाथ_, truo, jacu_,
+ {{0x69c18da9,0x2d858daa,0x443f8dab,0x4919016f}}, // tele, _tele_, rlu_, _येतो_,
+ {{0x7c878dac,0x443f81fa,0x2ba7835a,0xd24e80a0}}, // _буде, slu_, _गावा, وني_,
+ {{0x443f8dad,0xd9458d5f,0x61458dae,0x69c18daf}}, // plu_, рени, рена, rele,
+ {{0x20040db0,0xdcfe01a9,0x7bcf0db1,0x78a18db2}}, // ómi_, _nepā, _nacu, rulv,
+ {{0x91e58db3,0x248600e1,0x09e58db4,0xf38c83de}}, // _воле, _ňom_, _волн, _טראָ,
+ {{0x44d58db5,0x7a3002af,0xee370db6,0x216681a1}}, // _kā_, sätz, иня_, _киши_,
+ {{0x2d8a0db7,0x7bcf0db8,0x44d580eb,0x69ce0db9}}, // rabe_, _bacu, _jā_, _rabe,
+ {{0x3ced8d38,0x44d58dba,0x2d8a0dbb,0x31c58dbc}}, // _अपने_, _mā_, sabe_, _वसुध,
+ {{0xfaa60dbd,0x69ce0dbe,0xcb00009a,0x25db82f1}}, // [600] разо, _pabe, लैंड_, _गोभी_,
+ {{0xb21b0bc5,0x69ce04b7,0xe1ff0dbf,0x2d960dc0}}, // _svær, _qabe, _stór_, _трис,
+ {{0x44d58dc1,0x61fe0dc2,0x44c78019,0x661c0dc3}}, // _nā_, rkpl, _nő_, mirk,
+ {{0x69ce0dc4,0x7bcf0dc5,0xb5fd811f,0x661c0dc6}}, // _wabe, _gacu, _bušo, lirk,
+ {{0xdfcf80f7,0x683d00e7,0x7bc40162,0x41a6864a}}, // بين_, cédu, meiu, _खालस,
+ {{0xa61380e8,0x2888004e,0x27350129,0xd0b3011c}}, // оміч, _مصری_, ụng_, hşət,
+ {{0x291e00f2,0x6da38dc7,0xb21b006a,0x7bcf018f}}, // _äta_, ција, _tvær, _yacu,
+ {{0x661c0057,0x3edf01bc,0x6d4884e8,0x07a38191}}, // hirk, _stọọ_, _vzda, _масн,
+ {{0x15f8085d,0xe5a68dc8,0x6da68dc9,0xb5fd826c}}, // ुसार_, _визи, _виза, _kušl,
+ {{0x44c78019,0x38600362,0x7cdc8162,0x99668dca}}, // _fő_, _aiir_, _pără, штил,
+ {{0x7e760dcb,0x6aa401ed,0xddc98dcc,0x88590087}}, // _atyp, huif, _uteš, лиос_,
+ {{0xa3d78054,0xb8868032,0xb5fc8372,0xeb978dcd}}, // ायण_, _atíò, _diġe, _виц_,
+ {{0xc7b38051,0x69a701b6,0x661c0359,0x3f8b0b80}}, // חבר_, _चाली, firk, racu_,
+ {{0x2723801c,0x661c0dce,0x7bcf0dcf,0x2bda8006}}, // ằng_, girk, _sacu, _मोता,
+ {{0x40948dd0,0xedf48996,0xe65400e8,0x7bcf0dd1}}, // _البر, _अवैध_, івсь, _pacu,
+ {{0x3ced0024,0x539800e8,0x41a68dd2,0x672d01c0}}, // ćev_, ився_, _खाँस, _txaj,
+ {{0x99670dd3,0x7bcf0dd4,0x79870101,0x24580009}}, // [610] ател, _vacu, _pejw, рать_,
+ {{0x661c0dd5,0x3d178074,0x2aaf011c,0x7bcf0dd6}}, // cirk, _भइले_, rüb_, _wacu,
+ {{0x7bcf0dd7,0x35f8003d,0x92e700ab,0x61f80106}}, // _tacu, _خريد_, বনে_, övla,
+ {{0x44d58dc1,0x9ad3819d,0x7bc40162,0x4e9581a8}}, // _rā_, _dịji, ceiu, مشتر,
+ {{0xa3ea8dbc,0xd6d9809a,0x6b8d0dd8,0x645a0dd9}}, // मान_, _była_, maag, ïtie,
+ {{0xee38835f,0x44d58dc1,0x6b8d0dda,0x80af80ab}}, // ані_, _pā_, laag, য়িত্,
+ {{0x69c50ddb,0x93fb016f,0x70940ddc,0xcd9801c6}}, // mehe, _एकाच_, _марф, בדות_,
+ {{0x6b8d0ddd,0x69c50dde,0x53c98319,0x44d58dba}}, // naag, lehe, угим_, _vā_,
+ {{0xb5fb0118,0x60060333,0x7d06811c,0x65958162}}, // bpáx, _nóma, _əksi, _газу,
+ {{0x44d58341,0xa2058ddf,0x7ae984c3,0x26cd80e5}}, // _tā_, спод, rxet, _iseo_,
+ {{0x3f890de0,0x661c0de1,0x6b8d0de2,0x629d816b}}, // _beau_, virk, kaag, ásob,
+ {{0x69c50de3,0x661c0de4,0x76438de5,0x7e69804f}}, // hehe, wirk, llny, _kuep,
+ {{0x69c50de6,0x7e618009,0x661c0de7,0xaac60105}}, // kehe, _kilp, tirk, _रैंक,
+ {{0x63a98de8,0x998580f7,0x6006008b,0x3f9e0214}}, // lgen, _الثو, _dóma, ştu_,
+ {{0x61e38de9,0xeb9a0dea,0x63a98deb,0x61fc8dec}}, // _innl, лив_, ogen, örle,
+ {{0x63a98ded,0x644f0dee,0x78a504a8,0x6b8d0def}}, // ngen, _chci, duhv, gaag,
+ {{0x38b18df0,0x661c0df1,0x63b6031d,0x63a98df2}}, // [620] lár_, pirk, rfyn, igen,
+ {{0xe3b203f8,0x69c50df3,0xb5fd826c,0x443d8192}}, // _کرد_, gehe, _bušm, _lkw_,
+ {{0xa3d78df4,0x395c016d,0x6d418df5,0x26cd8020}}, // ाया_, ävs_, _nyla, _aseo_,
+ {{0x6b8d0df6,0x20020009,0xddc281b9,0x7e618362}}, // caag, nkki_, spoż, _ailp,
+ {{0xadec0cce,0xa3e1823c,0x69c50df7,0x248d00eb}}, // ञापन_, दाई_, behe, ņemt_,
+ {{0x44d803bb,0x443d8133,0x64440df8,0x6d418df9}}, // _kč_, _akw_, llii, _byla,
+ {{0x38b18dfa,0x63a98dfb,0xdd99804a,0x7c29016b}}, // jár_, fgen, рші_, červ,
+ {{0x68158063,0x6d41831d,0x63a98dfc,0x83fc805c}}, // ządz, _dyla, ggen, _tuđm,
+ {{0x44d806c0,0xa3e0064a,0xaf068dfd,0x645c80e5}}, // _lč_, _दफन_, _упал, _èrim,
+ {{0x60060dfe,0xd9cf00ab,0xddcd01a1,0x98b88073}}, // _róma, রাথম, _otaš, алот_,
+ {{0x3f8900e7,0xeb9a8dff,0x2cbe82f7,0x26c0016a}}, // _peau_, _бие_, _uptd_, _apio_,
+ {{0xfaa681e2,0x3a3a0711,0x82a68705,0x394281a9}}, // _гадо, hopp_, _гадж, āks_,
+ {{0xab5d809a,0xa3e70e00,0x3ea68e01,0x3f890866}}, // leżn, पास_, nuot_, _veau_,
+ {{0x86270e02,0xddcd009a,0x6b8d0e03,0x9f44826b}}, // сьме, _uważ, waag, _bomú_,
+ {{0x61f50e04,0x6b8d0e05,0x9f448722,0x69c50e06}}, // _rozl, taag, _comú_, vehe,
+ {{0x321e8e07,0x69c5010c,0x27e482c4,0x93790162}}, // lity_, wehe, _nnmn_, рбат_,
+ {{0x7a3d8e08,0x69c502af,0x6b8d0e09,0xfaff0168}}, // [630] mètr, tehe, raag, _afër_,
+ {{0x321e8e0a,0x44d80247,0xddab0e0b,0x32070e0c}}, // nity_, _fč_, _стил_, óny_,
+ {{0x094b03bd,0x201d8e0d,0x6b8d0e0e,0x69c50e0f}}, // учаи_, tiwi_, paag, rehe,
+ {{0x69c50352,0xb8f10e10,0x7e618e11,0xad1a0039}}, // sehe, _वन_, _silp, _כותר,
+ {{0x8d658e12,0x44290e13,0x38b18e14,0x7e698e15}}, // овле, mna_, zár_, _quep,
+ {{0x443b0e16,0xdc998e17,0x20090114,0x201d804f}}, // loq_, атиш_, _llai_, siwi_,
+ {{0xd7aa0e18,0xcc8684fa,0x63a98e19,0xb5fd80d2}}, // _कालच, обще_, tgen, _hušk,
+ {{0x5fd90e1a,0xdb0d0e1b,0x66838065,0x6b808122}}, // _बोलल, leañ, _فیصل, gbmg,
+ {{0xab2989e0,0x7e618e1c,0x23298e1d,0x3ebf8088}}, // ропа_, _tilp, ропи_, _uput_,
+ {{0xb5fd803b,0x44290e1e,0x63a9844e,0x38b18e1f}}, // _mušk, hna_, sgen, tár_,
+ {{0x29038298,0x44290e20,0x443d8573,0x3207816b}}, // ája_, kna_, _tkw_, _vlny_,
+ {{0xa3ea86d4,0x3ea08805,0xb4d58128,0x2ca78e21}}, // माण_, šite_, िटी_, mund_,
+ {{0x7c3b8e22,0x2ca78e23,0x20020009,0x38b18e24}}, // mour, lund_, rkki_, sár_,
+ {{0x27388028,0x321e8e25,0x44d80e26,0x386a81a8}}, // ứng_, city_, _pč_, úirí_,
+ {{0xed5a0dd3,0x44290e27,0x7c298e28,0xdbc7007b}}, // ров_, fna_, oner, töðu,
+ {{0x44290e29,0x683601ac,0xb5fd88ae,0xf1ac0cf0}}, // gna_, hádz, _bušk, _चाहन,
+ {{0xa3ea816f,0xdb118036,0x2ca784dc,0x6b8b8e2a}}, // [640] मात_, éfèr, hund_, _megg,
+ {{0x44290e2b,0x6b8b8e2c,0xb5fd8390,0x44d80e2d}}, // ana_, _legg, _dušk, _tč_,
+ {{0x44290e2e,0xd6d98063,0x63af0364,0x7c3b86c0}}, // bna_, _było_, ännö, kour,
+ {{0x7c3b83d3,0x8c46002e,0x321e84e8,0x2d8e8e2f}}, // jour, _феме, zity_, wafe_,
+ {{0x44200e30,0xa3e7025e,0x7c3b8e31,0x628681d0}}, // mii_, पार_, dour, škoz,
+ {{0xd49a8abe,0x44200e32,0x2ba80e33,0xa6e3007b}}, // ари_, lii_, गलवा, æðis,
+ {{0x6b8b8e34,0x25a087d9,0x1db20740,0x798a8e35}}, // _begg, ğil_, _जानत, _refw,
+ {{0x44200e36,0x25a082d0,0x7c3b8e37,0x2d8e8e38}}, // nii_, şil_, gour, safe_,
+ {{0x24898a20,0x9c8780e1,0x6b8b81b4,0x6ab60035}}, // ćama_, _počú, _degg, ntyf,
+ {{0x44200e39,0x44290e3a,0xdb198247,0x2ca78e3b}}, // hii_, zna_, _pawò, bund_,
+ {{0x44290e3c,0xa3b58076,0x44200079,0x600605e4}}, // yna_, _चॉक_, kii_, _cómo,
+ {{0x44200e3d,0x44290e3e,0xf1ae852a,0x7c29826c}}, // jii_, xna_, ज्जन, cner,
+ {{0x44200962,0x44290e3f,0x77640698,0x6e3c0e40}}, // dii_, vna_, държ, forb,
+ {{0x6b8b8a0f,0x2d8c8e41,0x44290e42,0x60060118}}, // _zegg, _jede_, wna_, _fómo,
+ {{0xa3e7005e,0x69c88e43,0x48c500c8,0x44200e44}}, // पाल_, mede, _এপ্র, fii_,
+ {{0x44200e45,0x69c88e46,0x44290e47,0x7aed0e48}}, // gii_, lede, una_, txat,
+ {{0x4429088b,0x443b022b,0xd839011a,0xf96b0e49}}, // [650] rna_, roq_, juči_, арей_,
+ {{0x7c2d80d2,0x69c88e4a,0xdb1182be,0x527b8158}}, // čari, nede, éfér, ינמא,
+ {{0x44200e4b,0x44290e4c,0x2d910e4d,0x683601ac}}, // bii_, pna_, maze_, vádz,
+ {{0x2d91012b,0x44200e4e,0x69c88e4f,0x98b38052}}, // laze_, cii_, hede, žeći_,
+ {{0x6b8b8e50,0x63ad0e51,0x2d8c8e52,0x3f8f8e53}}, // _regg, mgan, _bede_, ragu_,
+ {{0x69c88e54,0x63ad0e55,0x2ca78e56,0xe61880e8}}, // jede, lgan, tund_, оді_,
+ {{0xa3d784e5,0x7c3b8e57,0x69c88e58,0x2d8c8e59}}, // ायल_, tour, dede, _dede_,
+ {{0x63ad0e5a,0xb5fd8754,0x25db8e5b,0x2ca78e5c}}, // ngan, _muši, _गोरी_, rund_,
+ {{0x2ca78e5d,0x2d910e5e,0x2d8c8e5f,0x6b8b8357}}, // sund_, kaze_, _fede_, _vegg,
+ {{0x44200012,0xddc4003b,0x2d8c8e60,0x6b8b8e61}}, // zii_, _stiž, _gede_, _wegg,
+ {{0x6d450e62,0x442004be,0xb5fd8e63,0x9f448242}}, // _nyha, yii_, _nuši, _anmè_,
+ {{0x442002a3,0x69d50e64,0xdd8f8019,0x6e3c0192}}, // xii_, _laze, _سوچ_, worb,
+ {{0x63bb8710,0x44200e65,0x63ad0e66,0xdb1d016a}}, // mfun, vii_, dgan, _masó,
+ {{0x9c828db7,0x2d8c0e67,0x63bb80e3,0x44200079}}, // ščen, úde_, lfun, wii_,
+ {{0x44200e68,0xe1668077,0xa5070e69,0x63ad0e6a}}, // tii_, _عضوی, пера_, fgan,
+ {{0xc4d2893f,0xbc630e6b,0x6e3c0e6c,0x61e70e6d}}, // עגן_, евск, sorb, _anjl,
+ {{0x44200e6e,0x7e6d0e6f,0x2fc002b8,0xaabd81a2}}, // [660] rii_, _guap, _ibig_, ्बिक,
+ {{0xa3e28e70,0x44200e71,0x2d910e72,0x25db8bb8}}, // _फोन_, sii_, caze_, _गोली_,
+ {{0x44200e73,0x38b50e23,0x2d8c8e74,0x7e7b8e75}}, // pii_, går_, _rede_, _atup,
+ {{0xdb1d0e76,0x83fc82a5,0xde6d001c,0x35b203db}}, // _obsè, _tuđi, _hươn, _जाड़,
+ {{0x69d50003,0xf8a88bb8,0x66fe0540,0x2d8c8e77}}, // _faze, _कम्प, ॉनिक_, _pede_,
+ {{0xd8390e78,0x69d50e79,0x6b840e7a,0x7c3b0da8}}, // ruči_, _gaze, lbig, čure,
+ {{0x2d8c8e7b,0xdb0f02be,0xe81d06a7,0x63bb8e7c}}, // _vede_, _accè, _पगला_, ffun,
+ {{0x8aa68adb,0xde6d001c,0x69c88e7d,0x32058366}}, // прод, _lươn, wede, ekly_,
+ {{0x62960e7e,0x69c88e7f,0x2d8c8e80,0xa3e18e81}}, // nsyo, tede, _tede_, दाज_,
+ {{0xdb0d03a7,0xde6d00ff,0x8afb0e82,0x6faa0305}}, // sfaç, _nươn, צהיי, _काउं,
+ {{0x6aa40e83,0x7e6d0ad4,0x63ad0e84,0xb4c80035}}, // hrif, _suap, ygan, ोटो_,
+ {{0x6aa40e85,0xe45a8e86,0x62960e87,0x4c16826a}}, // krif, _уже_, ksyo, _عباس,
+ {{0xf2068021,0x78a8806a,0x7e6d0299,0xeb9f0646}}, // _няко, sudv, _quap, _fløj_,
+ {{0xa2db0e88,0x7afd8e89,0x6aa4035f,0xde6d00ff}}, // निस्, äste, drif, _cươn,
+ {{0x63ad0e8a,0xde6d0028,0x69d50e8b,0x19bb8039}}, // tgan, _dươn, _raze, _המוב,
+ {{0x6aa4031d,0x6b84009c,0x9c7c812b,0x2d910e8c}}, // frif, gbig, _grče, saze_,
+ {{0x6aa40e8d,0x95cb0e8e,0x2ee00e8f,0xb03803c8}}, // [670] grif, _луна_, _grif_, _מנהג_,
+ {{0x38b50bfa,0xde6d001c,0x20c80e90,0xb0c8052a}}, // tår_, _gươn, रबंध, रबंग,
+ {{0x7e7b8b24,0x6b840e91,0x29070e92,0x7e7c807a}}, // _stup, bbig, ána_, _črpa,
+ {{0x2c718e93,0x290704b7,0x6aa40e94,0x69d50e95}}, // _rád_, ġna_, brif, _waze,
+ {{0xdb1d0b2c,0x38b50bbd,0x7c2d0e96,0x603201a9}}, // _obsé, sår_, mnar, nāma,
+ {{0x6da58e97,0xde6d001c,0x2c71816b,0x38b50106}}, // фика, _xươn, _pád_, pår_,
+ {{0x2489805c,0x442f80dd,0x27e902d6,0xed568073}}, // ćamo_, _pjg_, _onan_, мош_,
+ {{0x7c2d0e98,0xdb0f0e99,0x1b1e80ab,0x67d4806d}}, // nnar, _accé, _পড়ে_, _посу,
+ {{0x63bb8e9a,0x7c2d0e9b,0x7bc98e9c,0xa7fd011c}}, // rfun, inar, reeu, yqır,
+ {{0x27e90e9d,0x3eab0e9e,0x6442811b,0xb5fd8e9f}}, // _anan_, duct_, _ekoi, _aušv,
+ {{0x63bb8ea0,0x07a601d9,0x7c2d007b,0x200d800b}}, // pfun, манн, knar, _klei_,
+ {{0x2ee0031d,0x81b580ab,0xe3b081a8,0xde6d00ff}}, // _prif_, _চোখ_, _مره_, _sươn,
+ {{0xa3ea88d4,0x7c2d0353,0x443f8ea1,0xe28e8ea2}}, // मार_, dnar, mou_, _ша_,
+ {{0x64498ea3,0x200d809f,0x7c2d0ea4,0x27e902d6}}, // llei, _llei_, enar, _enan_,
+ {{0x442d8021,0xe1f08ea5,0x3eb90613,0x9d430ea6}}, // one_, _حسن_, atst_, верд,
+ {{0x443f8ea7,0x7c2d0ea8,0x64498e5d,0x9c7c8ea9}}, // nou_, gnar, nlei, _urče,
+ {{0x442d8eaa,0xe1fa0eab,0xde6d0028,0x64498229}}, // [680] ine_, ога_, _tươn, ilei,
+ {{0x6aa40eac,0x443f8ead,0x2b8c0028,0x6b8402a3}}, // rrif, hou_, ếc_, sbig,
+ {{0x443f8eae,0x442d8558,0x851c8105,0x64498eaf}}, // kou_, kne_, _भेंट_, klei,
+ {{0x442d8eb0,0xa2db05e8,0x798e00c9,0x443f8eb1}}, // jne_, निश्, _webw, jou_,
+ {{0x442d81d6,0x443f8eb2,0xee3a8eb3,0x20068168}}, // dne_, dou_, онд_, rkoi_,
+ {{0xa2db0eb4,0x6449844e,0x6e238129,0xb8140264}}, // निर्, elei, ginb, াচিত_,
+ {{0x443f8eb5,0xa3ea84c5,0x3e740106,0x1dc00eb6}}, // fou_, माल_, _lät_, श्यत,
+ {{0x442d8eb7,0x443f8e67,0x68e3013c,0x64498eb8}}, // gne_, gou_, ænde, glei,
+ {{0x61fc0eb9,0x3e740106,0x3f868eba,0x9ad301bc}}, // _korl, _nät_, mbou_, tịim,
+ {{0x442d8ebb,0x7c240ebc,0x4efb81c6,0x6449862c}}, // ane_, hiir, _והמו, alei,
+ {{0x443f8ebd,0x850684c0,0x442d8ebe,0x25eb8ebf}}, // bou_, _توان, bne_, चारी_,
+ {{0x443f8ec0,0x3f868ec1,0x62808ec2,0x60320ec3}}, // cou_, nbou_, ppmo, tāma,
+ {{0x64408ec4,0x61fc00c9,0x2bdf00d4,0x5bbc8ec5}}, // momi, _oorl, _पोषा, ्जीव,
+ {{0x64408ec6,0x61fc0ec7,0x8a038081,0x603201a9}}, // lomi, _norl, _изче, rāma,
+ {{0x92bb0a49,0x443f001c,0x7c240ec8,0x623381a1}}, // _আছে_, _ưu_, fiir, _шешу,
+ {{0x27e90ec9,0xfc300eca,0x69da8ecb,0x69d88cf7}}, // _unan_, لحق_, ldte, _have,
+ {{0xa3b685e8,0x69d88ecc,0x61fc0ecd,0x776d0118}}, // [690] _जान_, _kave, _borl, _igax,
+ {{0x443f8ece,0x442d8ecf,0x69da8ed0,0x64408ed1}}, // zou_, zne_, ndte, homi,
+ {{0x442d8ed2,0x644081f6,0x69d88ed3,0x7c240ed4}}, // yne_, komi, _mave, biir,
+ {{0x44248025,0x27e98ed5,0x69d88257,0x443f8548}}, // jim_, ñan_, _lave, xou_,
+ {{0x442d8ed6,0x443f8ed7,0x9f4087ca,0x61fc0ed8}}, // vne_, vou_, ðið_, _forl,
+ {{0x7bc2804c,0x442d8063,0x61fc0ed9,0x7cd38085}}, // _abou, wne_, _gorl, _hərə,
+ {{0x443f8eda,0x442d8ed6,0x60060013,0x44248edb}}, // tou_, tne_, _fómh, fim_,
+ {{0x442d8012,0x200d8087,0x61ea8edc,0x61fc03bf}}, // une_, _ulei_, _anfl, _zorl,
+ {{0x442d8edd,0x64498357,0x63a68ede,0x69d88edf}}, // rne_, rlei, ókna, _bave,
+ {{0x443f83a8,0x442d8ee0,0xe1f18013,0x9c828ee1}}, // sou_, sne_, اسة_, ščan,
+ {{0xd8260ab5,0x602607eb,0x38690ee2,0x69d88ee3}}, // едни, една, _biar_, _dave,
+ {{0x64408ee4,0x7ae28ee5,0xa3b68ee6,0x61ea8ee7}}, // comi, _frot, _जाय_, _enfl,
+ {{0x7ae28ee8,0x7bcd0ee9,0x7c240eea,0x69d88eeb}}, // _grot, deau, viir, _fave,
+ {{0x69d88eec,0xa3d78eed,0xb5fd8582,0x2d9e0036}}, // _gave, ायक_, _gušt, ûte_,
+ {{0x7bd98eee,0x1b1d00ab,0xd00a0eef,0xa3b68ef0}}, // _kawu, _ভুলে_, _неке_, _जाम_,
+ {{0x69d88ef1,0xe73a0009,0x7bcd010c,0xb60201d0}}, // _zave, _ней_, geau, _žádo,
+ {{0x7c240ef2,0xf41280be,0x60060ef3,0x7cd38085}}, // [6a0] riir, אפן_, _nómi, _dərə,
+ {{0x44248ef4,0xe9d70ef5,0x7c2402a3,0x7c3b0279}}, // zim_, нку_, siir, čura,
+ {{0xa3ea0381,0x7c240ef6,0x44248ef7,0x61fc01ec}}, // _една_, piir, yim_, _vorl,
+ {{0x44248ef8,0x61fc0051,0xfbae8670,0x7bd98ef9}}, // xim_, _worl, _टाइम, _nawu,
+ {{0xa3b685b3,0x600605e4,0x64408791,0x6d488efa}}, // _जाब_, _cómi, vomi, _syda,
+ {{0x44248efb,0xf2d3007c,0x7ae28168,0x7cd3811c}}, // wim_, _געש_, _rrot, _zərə,
+ {{0x8fa681e2,0x04668adb,0x7c2f00f7,0x9c7c8754}}, // _паве, нтим, _áirí, _brča,
+ {{0x6d488efc,0xb5fd8efd,0x961d0eed,0xccf304de}}, // _vyda, _sušt, _नष्ट_, רכה_,
+ {{0x44248efe,0xb5fd803a,0x64408eff,0x69d88f00}}, // rim_, _pušt, romi, _pave,
+ {{0x38690ad0,0x64408329,0x38b88f01,0x7afd8f02}}, // _siar_, somi, tér_, ästa,
+ {{0x0b8a825d,0x9f448f03,0x290a8019,0x64a38f04}}, // _если_, _tomó_, ába_, _баха,
+ {{0x7ae28f05,0x2c750f06,0x68e38858,0x929d8035}}, // _trot, _råd_, _drnd, syła,
+ {{0x7bcd0f07,0x7ae28f08,0x38b88f09,0x3869008e}}, // veau, _urot, sér_, _viar_,
+ {{0x61ea8176,0x8cd78f0a,0x44220f0b,0x64460c2e}}, // _unfl, _मनमो, _amk_, _akki,
+ {{0x248d003b,0x7bcd0f0c,0x7bd98f0d,0xf8a48f0e}}, // ćemo_, teau, _yawu, _खटिय,
+ {{0x59c600d4,0x60c7008e,0x210301d6,0x6b9601b4}}, // _रॉबर, _rpjm, nčiť_, gayg,
+ {{0x6b848006,0x2bc2816f,0xfbc28f0f,0x7bcd0f10}}, // [6b0] _õigu, व्या, व्यम, reau,
+ {{0x7bcd0f11,0xa2a70f12,0x2bcf0035,0x210301d6}}, // seau, _चिठ्, हजहा, hčiť_,
+ {{0x7cd38086,0x5f958f13,0x7c228f14,0x6b9601b4}}, // _tərə, виет, _omor, bayg,
+ {{0xfbab0009,0x629d8061,0x9f49010c,0x200b01d0}}, // _этой_, ások, _anaé_, nkci_,
+ {{0x62830025,0x644d0f15,0x6aad00e5,0x79a70a2e}}, // ćnos, mlai, quaf, _прве,
+ {{0x62350f16,0x9c7c8024,0x2ab98247,0x62500074}}, // _регу, _srča, vèb_, näol,
+ {{0xda3401bb,0xdce1809a,0x644d0f17,0x7bd98f18}}, // _серы, _oglą, olai, _pawu,
+ {{0x644d0f19,0x8ca286b7,0x69c3808e,0x26c90390}}, // nlai, _गिरो, _sbne, _opao_,
+ {{0x7bdb8f1a,0x69c10f1b,0x68e38bcf,0x961d8ec3}}, // rduu, र्थी, _srnd, _atņe,
+ {{0xaca38870,0x644d0c5e,0x2fcf80e8,0x7c228f1c}}, // _amụm, hlai, legg_, _emor,
+ {{0x9c7c805c,0x76438f1d,0x644d0f1e,0x7bd98f1f}}, // _trča, mony, klai, _tawu,
+ {{0x9c7c8f20,0x645a0084,0x27ff8176,0x69c10f21}}, // _krčn, įtik, _joun_, र्ती,
+ {{0xa3b68f22,0x27ff8f23,0x9aa48f24,0xe9ce8f25}}, // _जात_, _moun_, _ممنو, _кк_,
+ {{0x44220f26,0xed5a0f27,0x7c228f28,0xc5d50a4c}}, // _smk_, дог_, _zmor, лісь,
+ {{0x799702c1,0x44220f29,0xdcfc00eb,0x27ed8f2a}}, // daxw, _pmk_, varē, _onen_,
+ {{0x644d0f2b,0x7ae9005c,0x69c18f2c,0x6b898f2d}}, // glai, _šeta, lfle, nbeg,
+ {{0xe2970f2e,0xee3a0f2f,0xab270320,0x76438f30}}, // [6c0] кат_, _они_, вота_, kony,
+ {{0x644d0f31,0x69c18f32,0x6b89826c,0x175701c6}}, // alai, nfle, hbeg, _בספר_,
+ {{0x76438f33,0x644d0f34,0x27ff8176,0x62840f35}}, // dony, blai, _boun_, spio,
+ {{0x2d980f36,0x644d0f37,0x629b8084,0x6aa9804a}}, // lare_, clai, ksuo, kref,
+ {{0x76438f38,0x44320f39,0x37dc0264,0x2a6a011c}}, // fony, mny_, ধাপর, _tibb_,
+ {{0xa2a70f3a,0x4432096a,0x2d980f3b,0x76438f3c}}, // _चित्, lny_, nare_, gony,
+ {{0x61ee0106,0x44320f3d,0x69dc0f3e,0xb5fc81b9}}, // _inbl, ony_, _iare, _jiġi,
+ {{0x44320f3f,0x64598f40,0x2d980f41,0x69dc0f42}}, // nny_, _chwi, hare_, _hare,
+ {{0x69dc0f43,0x2d980f44,0x51868071,0x76438f45}}, // _kare, kare_, куна, bony,
+ {{0x69dc0f46,0x2d980f47,0x44320f48,0x2bae816f}}, // _jare, jare_, hny_, _टाका,
+ {{0x69dc0f49,0x25db8076,0x2d98088b,0x27ff8247}}, // _mare, _गोटी_, dare_, _youn_,
+ {{0x27ed07f4,0x44320f4a,0x2d930f4b,0x3ea986c2}}, // ñen_, jny_, _sexe_, šate_,
+ {{0x44320f4c,0x34cb0996,0x2d9800e5,0x63a28f4d}}, // dny_, ाब्द, fare_, _idon,
+ {{0x69dc0f4e,0xb8828f4f,0x2d980687,0x62828f50}}, // _nare, üíst, gare_, _itoo,
+ {{0x6d400b16,0x20d38c3b,0x65940f51,0xdd940f52}}, // şman, _نتيج, расу, расы,
+ {{0x76438f53,0x44320f54,0xec340f55,0x644d0f56}}, // zony, gny_, ансь, ulai,
+ {{0x2d980f57,0x644d0f58,0x81d580c8,0x76438f59}}, // [6d0] bare_, rlai, হার_, yony,
+ {{0x2d980f5a,0x44320063,0xaca30135,0x69dc0f5b}}, // care_, any_, _atụg, _care,
+ {{0x69dc0f5c,0x26080063,0x24810748,0x9c7c807a}}, // _dare, _सकती_, _uthm_, _srčn,
+ {{0x63a28f5d,0x3f990f5e,0xa283003d,0xac1900e8}}, // _ndon, nasu_, _نیرو, _чому_,
+ {{0x44290f5f,0x501c0f60,0xf7708bbe,0x1dc000d4}}, // mia_, וואו, زان_, श्वत,
+ {{0x69dc0f61,0x63a28f62,0x6272809a,0x7bdd0f63}}, // _gare, _adon, _młod, _hasu,
+ {{0x7bdd0f64,0x6b898754,0x27ff826b,0x25a983bf}}, // _kasu, tbeg, _toun_, ğal_,
+ {{0x44290f65,0x2d980012,0x69dc0f66,0x6aa98f67}}, // nia_, zare_, _zare, tref,
+ {{0x7bdd0f68,0x69dc0f69,0x76438f6a,0x6113811c}}, // _masu, _yare, pony, mələ,
+ {{0x44290f6b,0x386d8f6c,0x6aa98f6d,0x61138201}}, // hia_, _hier_, rref, lələ,
+ {{0x44290f6e,0x2d980f6f,0x44320063,0x69c18f70}}, // kia_, vare_, yny_, rfle,
+ {{0x7bdd0f71,0x44290f72,0x98160154,0x6aa98f73}}, // _nasu, jia_, ابدا, pref,
+ {{0x66018f74,0x4432003e,0x7ae9005c,0xfaa58f75}}, // _kolk, vny_, _šetn, _сало,
+ {{0x4432009a,0x3e798247,0x61138085,0x98e580f7}}, // wny_, _mèt_, hələ, _مكتو,
+ {{0x2d980f3b,0x61138201,0x3f990f76,0x69dc0f77}}, // rare_, kələ, basu_, _rare,
+ {{0x44290f78,0x69dc0f79,0x2d980f7a,0x44e387d9}}, // gia_, _sare, sare_, _mı_,
+ {{0x79950f7b,0x2d980f7c,0x4432096a,0x3e7986c0}}, // [6e0] _bezw, pare_, rny_, _nèt_,
+ {{0x7c298f7d,0x44290f7e,0x44320f7f,0x613a0f80}}, // hier, aia_, sny_, нчер_,
+ {{0x69dc0f81,0x386d8f82,0x61138085,0x7c298f83}}, // _vare, _bier_, fələ, kier,
+ {{0xe73a8364,0x765a80f1,0xb8dc03a4,0x3e798247}}, // нее_, _shty, _आम_, _bèt_,
+ {{0x69dc0f84,0xadf08f85,0x539b0039,0x386d8f86}}, // _tare, चालन_, _איכו, _dier_,
+ {{0x38bc0f87,0x61460f88,0x3f990f89,0x386d8f8a}}, // pír_, _бега, zasu_, _eier_,
+ {{0x7c298f8b,0x386d8f8c,0xa3bb0f8d,0x7bdd0f8e}}, // fier, _fier_, _घाम_, _yasu,
+ {{0x7c298f8f,0x3e798205,0x79950f90,0x61138201}}, // gier, _fèt_, _zezw, cələ,
+ {{0x66018f91,0x7ae40d7b,0x52148cc1,0xe7378037}}, // _folk, nvit, идит, _бет_,
+ {{0x44290f92,0xddcd0f93,0xa3b68006,0x66018f94}}, // zia_, _staž, _जाव_, _golk,
+ {{0x7c298f95,0x7c260f96,0x41c60670,0x59c60f97}}, // bier, _omkr, र्यस, र्यर,
+ {{0x7c298f98,0xaae68f99,0x386d0106,0x660180b9}}, // cier, _مستو, öer_, _zolk,
+ {{0x44268f9a,0x7bdd0f9b,0x6d4003bf,0x95cb0f9c}}, // _imo_, _rasu, şmal, нува_,
+ {{0x44290f9d,0x442681c5,0x7bdd0f9e,0x2731801c}}, // wia_, _hmo_, _sasu, ạng_,
+ {{0x44290f9f,0x7bdd0fa0,0x3f990fa1,0x317b810f}}, // tia_, _pasu, pasu_, _ארומ,
+ {{0xb4c50006,0x799a8fa2,0x44290fa3,0x6006008b}}, // _एही_, latw, uia_, _dóms,
+ {{0x59c603eb,0xafdb03ba,0x7bdf0fa4,0x7bdd0fa5}}, // [6f0] र्भर, sløs, rdqu, _vasu,
+ {{0x44290fa6,0x7c298fa7,0x386d835f,0x7bdd0fa8}}, // sia_, zier, _sier_, _wasu,
+ {{0x4426877f,0x7bdd0006,0xc7958071,0x44290fa9}}, // _omo_, _tasu, арлы, pia_,
+ {{0xb4fa078d,0x20da807d,0x79950f90,0x2ee903ac}}, // _ספרי, mšić_, _wezw, _kraf_,
+ {{0x6b8d0faa,0x386d84eb,0x7c298fab,0x539a0039}}, // mbag, _vier_, vier, _בירו,
+ {{0x6b8d0fac,0x7c298063,0x61138201,0x69c7056f}}, // lbag, wier, sələ, _ubje,
+ {{0x6601844e,0x7c298fad,0x44390fae,0xb60584be}}, // _volk, tier, _kjs_, _aláì,
+ {{0x3e798205,0x66018faf,0x61138085,0x22458fb0}}, // _tèt_, _wolk, qələ, wolk_,
+ {{0x7c298fb1,0x2bc7016f,0xa3b68fb2,0x799a80b4}}, // rier, ऱ्या, _जार_, fatw,
+ {{0x7c298fb3,0x20da812b,0x5ec880ab,0xb4c50074}}, // sier, kšić_, লিশে, _एहू_,
+ {{0x63b60196,0x2bc2816f,0x3ea00fb4,0xdb09816a}}, // ygyn, व्हा, _svit_, rfeñ,
+ {{0x44268fb5,0xa4fa80be,0x6aad0fb6,0x2ee90428}}, // _gmo_, _בלעט, kraf, _braf_,
+ {{0x636b83bf,0x1f5601bb,0x799a8fb7,0x41c60fb8}}, // _dönü, ртнё, batw, र्डस,
+ {{0x91fc8341,0x81ab00c8,0x6aad0fb9,0x62898fba}}, // rmāc, ক্ত_, draf, mpeo,
+ {{0x3b960fbb,0xe946819f,0x2ee90fbc,0x4439041c}}, // ијат, _پروی, _eraf_, _bjs_,
+ {{0x3ea0812b,0xf8140077,0x6b8d0fbd,0xafdb0aa2}}, // šiti_, _دستگ, gbag, slør,
+ {{0xd4670fbe,0xb22601b5,0xada60fbf,0xa3d686b7}}, // [700] рите_, амал, _капл, ाजत_,
+ {{0x7ae40fc0,0x27e00fc1,0x26060187,0xa3e601a2}}, // rvit, _hain_, _vôos_, _योर_,
+ {{0x27e00590,0x6b9b8fc2,0xdb0d016b,0x2fdf8fc3}}, // _kain_, kaug, čkác, _baug_,
+ {{0x7c260fc4,0x2fdf8069,0xbc7b0039,0xe3b20fc5}}, // _umkr, _caug_, _שנכת, _برد_,
+ {{0x2fdf81e2,0x27e00fc6,0xa3bb0075,0x2bdf0072}}, // _daug_, _main_, _घात_, _पोटा,
+ {{0x27e00fc7,0x7ae480f7,0x7aeb006a,0x44d802d6}}, // _lain_, _áiti, ægte, _kō_,
+ {{0xd49781e2,0x10a30fc8,0x18a30fc9,0x44268fca}}, // ары_, _мирн, _марм, _pmo_,
+ {{0xa6868fcb,0xeca78fcc,0x27e00fcd,0xa3c90fce}}, // _влад, _गिरफ, _nain_, ल्प_,
+ {{0x9c7c85f3,0xf1c60aed,0x41c60af3,0x7cca811c}}, // _krčk, र्थन, र्थस, _qərb,
+ {{0x1db2016f,0x2fdf81c5,0x38780168,0xdb0982d0}}, // _जागत, _zaug_, _kurr_, zgeç,
+ {{0x27e00fcf,0x44d802d6,0x2d9c80f1,0x20048fd0}}, // _bain_, _nō_, mave_, _komi_,
+ {{0xf1c6000d,0x2d9c8fd1,0x7c3b0c18,0x6286012b}}, // र्तन, lave_, čurk, _otko,
+ {{0x27e00fd2,0x799a8c53,0xeafa81f9,0x2c0b8fd3}}, // _dain_, patw, _جرات_, _جعلی_,
+ {{0x2d9c8fd4,0xa3b68fd5,0xa3c9001b,0x6aad00e5}}, // nave_, _जाँ_, ल्न_, vraf,
+ {{0x69ca81c0,0x44390fd6,0xf8da80c2,0x61fc8338}}, // _ibfe, _pjs_, _बनिय, örlu,
+ {{0x20048fd7,0x6aad0fd8,0x25ad0fd9,0xa3b68816}}, // _nomi_, traf, şel_, जलि_,
+ {{0x6b8d01f1,0x9c7c8fda,0x81e000c8,0x2fdf81e9}}, // [710] rbag, _brčk, থান_, _raug_,
+ {{0xd9990f99,0x6aad0fdb,0x98da8770,0xf8da809a}}, // انات_, rraf, _बनाए, _बनाय,
+ {{0x20048915,0x2fdf8282,0x29070162,0x2d9c8fdc}}, // _bomi_, _paug_, âna_, dave_,
+ {{0x6aad002e,0x2ef58fdd,0x628981e0,0x2fdf8069}}, // praf, йзер, ypeo, _qaug_,
+ {{0x20048fde,0x7c2d0fdf,0x6d5a8fe0,0xadc40032}}, // _domi_, miar, _azta, _atẹw,
+ {{0x9c7c803b,0x929d809a,0x2d9c8fe1,0x41c600c2}}, // _grčk, syłk, gave_, र्दस,
+ {{0x2fdf81c5,0x20048fe2,0x6b9b8fe3,0x610a829a}}, // _taug_, _fomi_, taug, nəld,
+ {{0xf77f817b,0x2d828019,0xf5380039,0x3c3a81a9}}, // _üç_, _őket_, ינוי_, tīva_,
+ {{0xa3c90fe4,0x6d5a8102,0x6b9b8fe5,0x3f158fe6}}, // ल्य_, _ezta, raug, _удас,
+ {{0x6b9b81e2,0xe1f98fe7,0x7528009a,0x27e00fe8}}, // saug, уго_, ździ, _sain_,
+ {{0x7c2d0102,0xd24e803d,0x7c360fe9,0x799883ec}}, // kiar, رچه_, ynyr, _devw,
+ {{0xa3c90fea,0x60060feb,0x9f0601a8,0xa3e60035}}, // ल्म_, _cómp, موجو, _यों_,
+ {{0x27e00364,0x91e58381,0x7c2d0fec,0x201e01e2}}, // _vain_, _голе, diar, ėti_,
+ {{0x442d8fed,0xdce38182,0x26c20fee,0x44d80176}}, // lie_, lanı, rtko_, _pō_,
+ {{0x26c20063,0x27e00fef,0x94790ff0,0x60c10106}}, // stko_, _tain_, исту_, _älmh,
+ {{0x442d8ff1,0x2d9c8ff2,0x7c2d0ff3,0xdce383bf}}, // nie_, zave_, giar, nanı,
+ {{0x38a38459,0x63a68ff4,0x7c360084,0xb3440085}}, // [720] lır_, ókni, rnyr, _keçə,
+ {{0x442d882e,0x81ab00c8,0x9c828353,0x20048ff5}}, // hie_, ক্স_, ščin, _somi_,
+ {{0x442d8ff6,0x999a893f,0x8c460ff7,0x38a38182}}, // kie_, רבעט, _гене, nır_,
+ {{0x442d8ff8,0x2d9a0ff9,0x68fc8106,0x6e2e0ffa}}, // jie_, _hepe_, ärdi, libb,
+ {{0x442d8ffb,0x2d9c8ffc,0x3ea20ffd,0x9c7c80cd}}, // die_, tave_, nskt_, _trčk,
+ {{0x6b828081,0x6e2e04b7,0x38a382d0,0x27e08ffe}}, // _sfog, nibb, kır_, žin_,
+ {{0x2d9c8fff,0xdcfc00eb,0x557700be,0x6ce68196}}, // rave_, darī, זעצן_, сіле,
+ {{0x442d9000,0x38a38b06,0x2d9c9001,0x6d5a801b}}, // gie_, dır_, save_, _vzta,
+ {{0xa3bb01b6,0x3866816d,0x34d40054,0x2d9c9002}}, // _घास_, mmor_, दबुद, pave_,
+ {{0x7c2d1003,0x442d9004,0x3860009f,0x38a382d0}}, // ziar, aie_, _ahir_, fır_,
+ {{0x442d9005,0x19589006,0x660509f8,0x2bca835f}}, // bie_, раны_, _rohk, _було_,
+ {{0x442d9007,0x28a99008,0x38601009,0x39570039}}, // cie_, _किरि, _chir_, משים_,
+ {{0x7c2d100a,0x291e900b,0x799e100c,0x6e2e0037}}, // viar, szta_, kapw, fibb,
+ {{0xdb0d03a7,0x6e2e038a,0xe84c01d0,0x38a38380}}, // lgaç, gibb, ětší, bır_,
+ {{0x7c2d100d,0x2d9a02f7,0xe65400e8,0x38600c41}}, // tiar, _depe_, ївсь, _fhir_,
+ {{0x388101e2,0x69c88a53,0x2d9a026b,0x38600362}}, // _nėra_, lfde, _eepe_, _ghir_,
+ {{0x7c2d011e,0xa3b68665,0x99990aac,0x24580009}}, // [730] riar, _जाई_, икат_, сать_,
+ {{0x442d900e,0x6fb50416,0x7c2d100f,0xa3bb0e5b}}, // zie_, _امکا, siar, _घाव_,
+ {{0xdce39010,0xa25b0038,0x9f520242,0x3c590087}}, // yanı, _spôs, _anyè_, _винэ_,
+ {{0x9f558468,0x9c7c9011,0x28ba03b7,0x64498118}}, // овеч, _grči, _उमरि, xoei,
+ {{0x442d9012,0x38a38059,0xdce388c5,0x81ab00ab}}, // vie_, zır_, vanı, ক্ষ_,
+ {{0x442d9013,0x7aed805c,0xd9fd05e8,0x38a39014}}, // wie_, _šato, _उचित_, yır_,
+ {{0x442d9015,0xdce387c0,0xed599016,0x69d7002a}}, // tie_, tanı, шок_, mexe,
+ {{0x7cca8201,0x52a99017,0x3ea2039c,0xe0d200be}}, // _qəra, авим_, yskt_, _קײן_,
+ {{0x28e10076,0x2d83009a,0xdce38059,0x442b1018}}, // फिकि, kcje_, ranı, _mmc_,
+ {{0x442d9019,0x38a385c5,0x88c98009,0x38600079}}, // sie_, tır_, алов_, _shir_,
+ {{0x442d901a,0xdce38380,0x61e50014,0x999e81d0}}, // pie_, panı, idhl, nitř_,
+ {{0xa3b6901b,0xb901901c,0xa2b2835a,0x38a38059}}, // _जाए_, _नई_, _आमच्, rır_,
+ {{0xf77207bd,0x38a38214,0xe29981a1,0xb7da81c6}}, // باد_, sır_, _тал_, בקרי,
+ {{0x63bb901d,0x5fdc101e,0x7ae9901f,0x3ea21020}}, // lgun, _बसवल, mvet, rskt_,
+ {{0xc5f28039,0xfce31021,0xf3870133,0x7c3a1022}}, // ודם_, моро, _ụtọa, étro,
+ {{0x63bb9023,0x3ead1024,0x26df81a1,0x69de9025}}, // ngun, šeta_, _osuo_, _óper,
+ {{0x61e39026,0x9c7c817f,0xa2061027,0x81dc80ab}}, // [740] _kanl, _isče, опад, ডার_,
+ {{0xe1f71028,0x63bb8458,0xe24601a8,0x915f1029}}, // огу_, hgun, آخري, _sắp_,
+ {{0x6026902a,0x9c7c8968,0x7ae9902b,0x7c2b821e}}, // оджа, _trči, hvet, _omgr,
+ {{0xb8f20d38,0x9c7c803e,0x61e3902c,0x2eed902d}}, // _वह_, _urči, _lanl, _bref_,
+ {{0x38668bbd,0xaad8805e,0x4e0e102e,0x6d418106}}, // rmor_, _बैंक, _हवाई_, _axla,
+ {{0x6ce589a3,0xb0a98540,0x7ae9816d,0x0caa8318}}, // किंग_, _किंग, dvet, атии_,
+ {{0x90c3102f,0xa3d686a7,0x7bc99030,0x69ce1031}}, // _обре, ाजल_, ffeu, _ibbe,
+ {{0x61e380c9,0x2fc001c5,0x63bb9032,0x8aa69033}}, // _aanl, _ncig_, ggun, ород,
+ {{0x8af78201,0xada68187,0x61e39034,0x7ae98061}}, // _şərh, _маал, _banl, gvet,
+ {{0x61e39035,0x32188063,0x7ae48267,0x799c0326}}, // _canl, óry_, _šits, _jerw,
+ {{0x61e39036,0x63bb8102,0x2ee01037,0x3207808e}}, // _danl, bgun, _asif_, _dony_,
+ {{0x68e3013c,0x27e69038,0x69ca1039,0x61ed8300}}, // ændi, ndon_, स्ती, _ɗali,
+ {{0x91e68a94,0xb90183bb,0x26c6903a,0x09e68293}}, // _моде, _नै_, ntoo_, _модн,
+ {{0xd3448077,0x63a0903b,0x32078118,0x61e38114}}, // _ویژه_, lamn, _gony_, _ganl,
+ {{0x81c200c8,0x70c90074,0x80c90035,0x27e68122}}, // ্যা_, _रहेल, _रहें, kdon_,
+ {{0x27e48687,0xe3ac80ab,0xb8670fd3,0x3cdd86ae}}, // _namn_, ক্রব, _باتو, _कईसे_,
+ {{0xd7630bca,0x799c103c,0xa3cc8aed,0x69d701df}}, // [750] _تنظی, _berw, श्य_, texe,
+ {{0x3eb9103d,0xaaac103e,0x63a0903f,0x63bb9040}}, // must_, _चिरक, hamn, zgun,
+ {{0x21270142,0x69d71041,0x3eb90074,0x7ae98019}}, // ành_, rexe, lust_, zvet,
+ {{0x69d71042,0x69ce1043,0x60c50428,0x6b8400e5}}, // sexe, _ebbe, rthm, ccig,
+ {{0xd6db823a,0x799c1044,0x69d71045,0xfd4c80ff}}, // рте_, _ferw, pexe, _triề,
+ {{0xc27a0039,0x66088079,0x62728035,0x7ae982d0}}, // _לרשי, _codk, _słon, vvet,
+ {{0x1c0f09f2,0x32078176,0x91fc80eb,0x27e49046}}, // _सवाल_, _rony_, rmāl, _famn_,
+ {{0xd7c9803d,0x61e39047,0x3eb90074,0xa80601df}}, // _دوره_, _sanl, kust_, _mañá,
+ {{0x63bb9048,0x61e39049,0xeab181a8,0x7ae98162}}, // rgun, _panl, رعة_, uvet,
+ {{0x2ea806b7,0x6608904a,0x3eb9103d,0xa925904b}}, // _कटौत, _godk, dust_, здол,
+ {{0x7ae9904c,0x61e3904d,0xf1b200be,0xa3bb853d}}, // svet, _vanl, עסט_, _ناشر_,
+ {{0xb4e78540,0x63a0904e,0x22808247,0x5437853d}}, // यटी_, camn, _fòk_, _برطر,
+ {{0xa3e6104f,0x61e39050,0xe8039051,0xe7371052}}, // _योग_, _tanl, _रचना_, зея_,
+ {{0xa3c91053,0x321e9054,0x3e6086c0,0x59c29055}}, // ल्स_, chty_, pòte_, _शायर,
+ {{0xed5a1056,0x27e69057,0x1828003d,0xdbd9007b}}, // сов_, ydon_, _وقتی_, tæðu,
+ {{0x799c0d38,0x7c3b9058,0xaca3819d,0x6b9d1059}}, // _serw, nnur, _anịm, _cesg,
+ {{0x3cdd8063,0x799c105a,0xc86500f7,0xdbd9007b}}, // [760] _कैसे_, _perw, _تطوي, ræðu,
+ {{0x6aa40057,0x6b84105b,0x09e3105c,0xe4760012}}, // rsif, scig, ноун, _нумэ,
+ {{0x799c105d,0x6b9d002a,0xa8060118,0x1b0400ab}}, // _verw, _fesg, _gañá, _রেখে_,
+ {{0x6281105e,0xdcf5009a,0x7985105f,0x799c1060}}, // _člov, jczę, achw, _werw,
+ {{0x44321061,0x799c1062,0x66088f66,0x27e69063}}, // miy_, _terw, _podk, rdon_,
+ {{0x26c68009,0xd49a8abe,0x98a61064,0x27e485ee}}, // rtoo_, бри_, _ниге, _wamn_,
+ {{0x39a68013,0x26c68079,0x628b8428,0xa3c9064a}}, // hísí_, stoo_, _atgo, ल्व_,
+ {{0xafe39065,0x44201066,0xb60380eb,0x320a008e}}, // _посл, nhi_, īšan, _hoby_,
+ {{0x27f7800d,0x44201067,0x2bce016f,0xa0678a4c}}, // šení_, ihi_, ह्या, _хата_,
+ {{0x59cf016f,0xe8e000ff,0x3eb90074,0x7de580f7}}, // स्पर, _nhịp_, vust_, _يسلم,
+ {{0x69bd8eed,0x44201068,0x69c10f1b,0x3eb900f3}}, // _शारी, khi_, र्टी, wust_,
+ {{0x44201069,0x3eb90006,0x29028289,0x443202ed}}, // jhi_, tust_, ćka_, jiy_,
+ {{0x4420106a,0x2d9e906b,0x6b9d106c,0x79850428}}, // dhi_, _kete_, _resg, ychw,
+ {{0x99ce00c8,0x2d9e906d,0x2b09801b,0x6b9d016a}}, // রযুক, _jete_, िहरु_, _sesg,
+ {{0x2d9e8205,0x61f8837a,0x3eb90074,0x248c8037}}, // _mete_, _invl, sust_, _jtdm_,
+ {{0x4420106e,0x69da906f,0x3eb91070,0x5c741071}}, // ghi_, lete, pust_, елст,
+ {{0x3eb91072,0x3a751073,0x78ba9074,0x320a1075}}, // [770] qust_, длар, lutv, _boby_,
+ {{0x44200079,0xdce70214,0xa3cc8105,0xdcf50035}}, // ahi_, sajı, श्त_, zczę,
+ {{0x02d983bb,0x77610201,0x79851076,0x320a03c1}}, // _भन्न, əlxa, rchw, _doby_,
+ {{0x44201077,0x69da9078,0x798502af,0x98c582a5}}, // chi_, hete, schw, žući_,
+ {{0xa2a700cf,0x69da9079,0x7ae2907a,0x2d9e8102}}, // _चिट्, kete, _nsot, _bete_,
+ {{0x2d9e8052,0x69da907b,0x7bcd0bda,0x8afa01c6}}, // _cete_, jete, lfau, _והשי,
+ {{0x2d9e8eef,0xd0d40698,0x20f08904,0xe81e06a7}}, // _dete_, _поръ, nđić_, _पतला_,
+ {{0x3ea9025d,0xfcaa093f,0x61e70061,0xdb1b907c}}, // _ovat_, קיפּ, _hajl, lgué,
+ {{0x2d9e907d,0x61e70088,0x7aed107e,0x81e000ab}}, // _fete_, _kajl, nvat, থার_,
+ {{0xb33b07d9,0xdb1b907f,0x69da9080,0x44201081}}, // _geçe, ngué, gete, zhi_,
+ {{0x7aed02a5,0x7ae29082,0x61e71083,0x44201084}}, // hvat, _esot, _majl, yhi_,
+ {{0x44201085,0x7aed00d2,0x61e701c0,0xead2077f}}, // xhi_, kvat, _lajl, fẹ́_,
+ {{0x44321086,0x44201087,0x764e1088,0x7aed0bcf}}, // viy_, vhi_, toby, jvat,
+ {{0x61e71089,0x7a200019,0x59cf04c5,0xf1cf108a}}, // _najl, _köte, स्मर, स्मन,
+ {{0x4420108b,0x443202d6,0x442f8079,0x320a00e5}}, // thi_, tiy_, _kmg_, _roby_,
+ {{0x7a20016d,0x68fc908c,0xc1728039,0x4420010b}}, // _möte, årde, _בחו_, uhi_,
+ {{0xfaa3108d,0x4420108e,0x442f908f,0x2eff0192}}, // [780] като, rhi_, _mmg_, äuft_,
+ {{0x44201090,0xe9d71091,0x601f0168,0x7cca829a}}, // shi_, мку_, _vëme, _sərm,
+ {{0xb4661092,0x2d9e9093,0x4420029b,0x25e71094}}, // _окол, _rete_, phi_, _छोटी_,
+ {{0x69da9095,0x9f401096,0x6d40017b,0xb33b0214}}, // zete, _unió_, şmas, _seçe,
+ {{0x61e71097,0xa3c8809a,0x63a41098,0xc7a384bd}}, // _fajl, _लॉग_, main, тичк,
+ {{0x3f9f9099,0x3f86907c,0x7a200106,0xd04c8085}}, // _eeuu_, scou_, _böte, _əhəm,
+ {{0x2d9e80f1,0x69da909a,0x28dd909b,0xf8d20075}}, // _vete_, vete, _फैलि, _सहिय,
+ {{0x2284109c,0x69da909d,0x2d9e909e,0x6b9600e8}}, // _kök_, wete, _wete_, nbyg,
+ {{0x2d9e909f,0x69c38580,0x427490a0,0xe45701c6}}, // _tete_, _bcne, нгис, ויקט_,
+ {{0x61e88713,0xf8d210a1,0x63a410a2,0xdb150036}}, // uddl, _सहाय, hain, ébéc,
+ {{0xd1308307,0x63a410a3,0x291890a4,0x69da90a5}}, // امج_, kain, ára_, rete,
+ {{0xe8df0028,0x7aed026f,0x63a410a6,0x7ae290a7}}, // _cuộc_, yvat, jain, _tsot,
+ {{0x63a410a8,0xd6d9809a,0x6b9610a9,0x78ba90aa}}, // dain, _były_, dbyg, sutv,
+ {{0xead210ab,0x7aed10ac,0x69da90ad,0x2ef20a53}}, // rẹ́_, vvat, qete, _dryf_,
+ {{0x61e70267,0x1bd50098,0xdb008511,0x2edb90a1}}, // _rajl, новя, damé, _यन्त,
+ {{0x63a40219,0x69ca8b6f,0x61e7025b,0x20f08088}}, // gain, त्री, _sajl, rđić_,
+ {{0xeabc80ab,0x61e70282,0x2bd08072,0x2ef210ae}}, // [790] _অনুম, _pajl, थ्या, _gryf_,
+ {{0x6d4510af,0x27e910b0,0xdb0090b1,0x228410b2}}, // _txha, _haan_, ramè, _dök_,
+ {{0x63a410b3,0x7aed037b,0x588401bb,0xd13100f7}}, // bain, svat, выча, دما_,
+ {{0x201910b4,0x3cf48743,0x27e910b5,0x63a410b6}}, // nksi_, ्मने_, _jaan_, cain,
+ {{0x61e710b7,0x0f35804e,0x27e902a1,0x20190198}}, // _tajl, رکرد, _maan_, iksi_,
+ {{0x780c016f,0x27e910b8,0x68e3006a,0x64428198}}, // _हक्क_, _laan_, ænds, _ajoi,
+ {{0xf41f0106,0x25a110b9,0xafdb0163,0x00000000}}, // _klä_, _mehl_, sløy, --,
+ {{0x7cca8085,0x3eb28084,0x27e910ba,0x2d878174}}, // _mərh, šyta_, _naan_, scne_,
+ {{0x7c3a0073,0x9f9d007b,0x316300c3,0xf6b58019}}, // étri, væði_, _hzjz_, _سماج,
+ {{0x63a410bb,0xfdf38424,0x20190009,0x569490bc}}, // zain, _आफिस_, eksi_, _рафт,
+ {{0x6e350052,0x27e910bd,0x09e18264,0x200d8a53}}, // dizb, _baan_, বালা, _koei_,
+ {{0xcdc90051,0x80a0809a,0x59cf10be,0x2b408968}}, // _לך_, _खबरे, स्तर, _žice_,
+ {{0xef1710bf,0x63a410c0,0x27e910c1,0xcc3a80be}}, // емя_, vain, _daan_, _צענט,
+ {{0x63a410c2,0xfce60a13,0x645b90c3,0xc69283de}}, // wain, ного, llui, ראם_,
+ {{0x443f90c4,0x228404b8,0xa3cc90c5,0xa50a0d5f}}, // onu_, _sök_, श्व_, чева_,
+ {{0x443f90c6,0x27e910c7,0x6b898057,0x7dd38457}}, // nnu_, _gaan_, nceg, lısı,
+ {{0x34dc90c8,0x6b9610c9,0x7ae9016b,0x41b610ca}}, // [7a0] _मन्द, rbyg, _šetr, есет,
+ {{0x69c190cb,0x6b9610cc,0x80c900ab,0x8e9701c6}}, // ngle, sbyg, রবন্, _רדיו_,
+ {{0x2d9810cd,0x63a410ce,0xf1c290cf,0x69c80214}}, // mbre_, pain, _शासन, _ödem,
+ {{0xa2b210d0,0x443f883d,0x7afb0bcf,0xcfb20264}}, // ेंद्, jnu_, _šute, ট্রন,
+ {{0x443f90d1,0xc87981cc,0x7cca8085,0xe8df819d}}, // dnu_, _diş_, _tərk, diọm_,
+ {{0x443f90d2,0x69c190d3,0x6aa990d4,0xa3c8864a}}, // enu_, jgle, dsef, _लॉज_,
+ {{0x69c18503,0x443f90d5,0x32430171,0x5fc6016f}}, // dgle, fnu_, _серг, _वाढल,
+ {{0x59c2886a,0x66e690d6,0x6b7b00be,0x25fe816f}}, // _शाहर, _поба, ּרינ, लाही_,
+ {{0xdcea90d7,0xe7d583bf,0x3edf0133,0x27e902d5}}, // rafı, lığı, _kwụọ_, _raan_,
+ {{0x27e910d8,0x69c190d9,0x443f90da,0x9fd000ab}}, // _saan_, ggle, anu_, িযোগ,
+ {{0x7c2410db,0x443f883d,0x27e910dc,0x39588366}}, // khir, bnu_, _paan_, ærs_,
+ {{0x645b90dd,0x69de10de,0x351b8039,0x2d980144}}, // clui, hepe, _מובנ, ebre_,
+ {{0x27e90364,0x69de10df,0x7cca8085,0x81ab0264}}, // _vaan_, kepe, _sərh, ক্ট_,
+ {{0xa3cc83b7,0x27e902a3,0x4992804e,0x63a290e0}}, // श्र_, _waan_, _حیدر, _heon,
+ {{0x644102be,0x63a290e1,0x69de10e2,0x6282810c}}, // élio, _keon, depe, _huoo,
+ {{0xe7d582bb,0x7c2410e3,0x63a290e4,0x61ea90e5}}, // dığı, ghir, _jeon, _hafl,
+ {{0xf1a48b85,0x63a2808e,0x7cca8085,0x61ea8ff4}}, // [7b0] _खजान, _meon, _dəri, _kafl,
+ {{0x63a290e6,0xbbdd90e7,0x443f90e8,0x69de10e9}}, // _leon, _मस्क, znu_, gepe,
+ {{0x443f90ea,0x7c2410eb,0x47bc8264,0xb21b0163}}, // ynu_, bhir, োজনী, _stæs,
+ {{0x7c2410ec,0x61ea90ed,0x443f02f9,0x6e9590ee}}, // chir, _lafl, _ñu_, виду,
+ {{0x443f803a,0xdee590ef,0x44248013,0xb4e10054}}, // vnu_, толи, dhm_, _धनु_,
+ {{0x27e9803b,0xe6188163,0x69de10f0,0xed5987b6}}, // žan_, нді_, cepe, мол_,
+ {{0x443f812b,0x6d5a8035,0x7e7a810c,0x645b90f1}}, // tnu_, _cyta, _ditp, tlui,
+ {{0x2ef587eb,0x201f802e,0x7bdf041c,0x386901b9}}, // _избр, _ului_, nequ, _nhar_,
+ {{0x6b89807d,0x63a290f2,0x645b90f3,0x045680f7}}, // rceg, _deon, rlui, خلية_,
+ {{0x443f82a5,0x645b90f4,0x6aa990f5,0xd24e80a0}}, // snu_, slui, rsef, يني_,
+ {{0x69c190f6,0xd945828b,0xf1c6086a,0x25fe90f7}}, // rgle, тени, र्टन, लारी_,
+ {{0xe73a10f8,0x68fc8106,0x69c190f9,0xdb1b8333}}, // дем_, årda, sgle, lguí,
+ {{0x69c702fd,0x26160074,0x7dd3807e,0x69de10fa}}, // _ocje, _नकदी_, pısı, yepe,
+ {{0xdb1b8228,0xdd92003f,0x628003c9,0x61ea90fb}}, // nguí, شور_, _émoi, _gafl,
+ {{0xed570785,0xada310fc,0x7c2410fd,0x63a290fe}}, // вор_, расл, thir, _yeon,
+ {{0x1c4310ff,0x63a283a8,0x38691100,0x60d69101}}, // ансм, _xeon, _ghar_, _יוצא_,
+ {{0xb33b0073,0xeab10065,0x7c240c0b,0x248601d6}}, // [7c0] _peça, یعے_, rhir, _čomu_,
+ {{0x7c241102,0xb21b1103,0xceb9000d,0x68f5026f}}, // shir, _stær, áře_, _brzd,
+ {{0x291c0ed7,0x69de1104,0xb33b00f1,0x7bdf1105}}, // áva_, repe, _veça, bequ,
+ {{0x6e219106,0x7bc6026c,0xf1c61107,0x7ae602c4}}, // _allb, _ucku, र्जन, _tskt,
+ {{0x44220459,0x63a2826c,0xdfcf80f7,0x3ead9108}}, // _ilk_, _reon, تين_, _cvet_,
+ {{0x6d5a8063,0x93f60201,0xaa461109,0x4a750009}}, // _pyta, _şəhə, _регл, _быст,
+ {{0x63a2910a,0xa3c9000c,0x3ead8214,0x98a3110b}}, // _peon, ल्क_, _evet_, _висе,
+ {{0x61ea831d,0x7bc4110c,0xa78580f7,0x6d5a910d}}, // _safl, ngiu, _مشكو, _vyta,
+ {{0x7c23002e,0x59cf000c,0xdee3910e,0x7ae40197}}, // _înre, स्वर, _коти, nwit,
+ {{0xaca401bc,0x610a8085,0x7e7a910f,0x63a28144}}, // _ahọt, səlm, _uitp, _weon,
+ {{0x859b82f6,0x9f4e026f,0x3ea01110,0x850688ca}}, // _חשבו, čným_, _awit_, _جوان,
+ {{0xc9530051,0x28c48540,0xaca40135,0x7ae41111}}, // ימת_, लंबि, _chọt, kwit,
+ {{0xd5c98074,0x05c98a74,0x7c229112,0x61ea9113}}, // _रामज, _रामब, _ilor, _tafl,
+ {{0xb8f480f7,0x6e940009,0x44221114,0x70d206a7}}, // _مكتب, риру, _alk_, _सहूल,
+ {{0x62728063,0x3869008c,0x7bdf1115,0x1dd206bf}}, // _słow, _thar_, tequ, द्धत,
+ {{0xd10f83b7,0x7bc40098,0xaca40135,0x41c6052a}}, // ाहरण_, ggiu, _ghọt, र्घस,
+ {{0x24580364,0xdb1d06c4,0x0cbf83b7,0x41cf8743}}, // [7d0] тать_, _absò, _एम्म, त्वस,
+ {{0x7c229116,0x200b0503,0x4422038e,0xf7f48077}}, // _llor, ljci_, _elk_, یسند,
+ {{0x3ead9117,0x0cbd80c8,0x7c2290ab,0xe01e8105}}, // _svet_, _আন্ত, _olor, पसंद_,
+ {{0xa2c30540,0x76551118,0x787a801b,0x7ae41119}}, // िंद्, lozy, _důvo, bwit,
+ {{0xa3cc00d4,0x539a0039,0x3c3a80eb,0x03d61101}}, // _शॉट_, _רישו, hīvs_, _גורם_,
+ {{0x7c22911a,0x69c7111b,0x5187111c,0x6b8d111d}}, // _alor, _ucje, _шума, lcag,
+ {{0x6aad111e,0xb4b1009a,0x7c22911f,0x81cc0264}}, // lsaf, _ऑटो_, _blor, _শোক_,
+ {{0x20f30a20,0x30da00be,0x2fc901c0,0x69c51120}}, // _ići_, _אַמע, _ncag_, lghe,
+ {{0x2bd51121,0x32111122,0x2d8101bc,0x0a398190}}, // ड्या, _kozy_, _oghe_, ечны_,
+ {{0x2d810028,0x20f31123,0x7c229124,0xb33b0036}}, // _nghe_, _kći_, _elor, _leço,
+ {{0xe4cb9125,0x07a6035f,0x69c50083,0x1acf00ab}}, // _زبان_, ланн, ighe, রিয়া,
+ {{0x30da00be,0x27ed9126,0x7643808e,0xf3f081a8}}, // ײַטע, _jaen_, lnny, _وأن_,
+ {{0x25a59127,0x27ed9128,0x63a99129,0x9f8a8074}}, // _kell_, _maen_, maen, _tööd_,
+ {{0x27ed912a,0x2aba8039,0x7c22912b,0x63a9912c}}, // _laen_, _נמצא, _zlor, laen,
+ {{0x27ff9010,0x03e680c8,0xdcee080a,0x7643912d}}, // _onun_, কারী_, tabı, inny,
+ {{0x6b8d112e,0x61e1912f,0xf1cf9130,0x63a99131}}, // gcag, lell, त्रन, naen,
+ {{0xab661132,0xe1fa1133,0x764380dd,0x6826016a}}, // [7e0] увал, нга_, knny, _códe,
+ {{0x55e61134,0x25a58698,0xdcee080a,0x787a801b}}, // _сооб, _nell_, sabı, _půvo,
+ {{0x7ae41135,0x61e18081,0x610a8085,0x20120041}}, // swit, iell, vəlk, _koyi_,
+ {{0x61e19136,0x6b8d1137,0xe29a1138,0x27ed9139}}, // hell, ccag, _пам_, _caen_,
+ {{0x61e1913a,0xdd92826a,0x6456113b,0x7e7e113c}}, // kell, موش_, moyi, _kipp,
+ {{0x69d3913d,0x7e7e04b7,0xc1e7019f,0x61e1913e}}, // _बॉली, _jipp, _مکمل_, jell,
+ {{0x61e1913f,0x25a59140,0x62861141,0x63a61142}}, // dell, _dell_, _huko, _kekn,
+ {{0x61ee1143,0x62861144,0xe4f70996,0x76438867}}, // _habl, _kuko, ुमति_, anny,
+ {{0x61ee1145,0x61e19146,0x7c2280f1,0xa3c9000c}}, // _kabl, fell, _vlor, ल्ट_,
+ {{0x628601d3,0x61e19147,0x61ee07df,0xddc89148}}, // _muko, gell, _jabl, _hidž,
+ {{0x5bc68bbe,0x61ee1149,0x64440009,0x63a9820d}}, // _مقال, _mabl, knii, baen,
+ {{0x6456114a,0x0eeb0009,0x61ee114b,0x26c2114c}}, // joyi, ньги_, _labl, fuko_,
+ {{0xcc57810f,0xb33b0036,0xddc88b80,0xe2968073}}, // _גבאי_, _reço, _midž, _среќ,
+ {{0x27ed114d,0x61e1914e,0x7e7e00e5,0xa2c3114f}}, // žen_, cell, _cipp, िंस्,
+ {{0xf9938039,0x69ca8144,0x62861150,0x63a61151}}, // _פרק_, _icfe, _auko, _bekn,
+ {{0x682607f4,0x62861152,0x60df8065,0x3756082e}}, // _póde, _buko, őzmé, ọdụ_,
+ {{0x5a350e17,0x92590003,0x765a8085,0x76439153}}, // [7f0] анат, ваат_, _okty, ynny,
+ {{0x61ee1154,0x27ed81df,0x321e809a,0x6b8d1155}}, // _cabl, _saen_, nkty_, scag,
+ {{0xa3d51156,0xa3e58105,0x69c51157,0x6aad1158}}, // _конч, _फसल_, rghe, ssaf,
+ {{0x61e19159,0x765a915a,0x63a6115b,0x6272809a}}, // zell, _akty, _gekn, _złot,
+ {{0x69d8915c,0x61ee115d,0xddc881e2,0x62860578}}, // _obve, _fabl, _didž, _guko,
+ {{0x4429115e,0xf770915f,0x64599160,0x61e18bf9}}, // lha_, سان_, _skwi, xell,
+ {{0x62861161,0x6b9b8609,0x20f3005c,0x61e19162}}, // _zuko, tbug, _ući_, vell,
+ {{0x442907e1,0x62861163,0x25a58051,0x61e19164}}, // nha_, _yuko, _well_, well,
+ {{0x61e19165,0x62800036,0xcb1200be,0x25a58039}}, // tell, _émot, ַלט_, _tell_,
+ {{0x63a98573,0x60c1861c,0x6abb9166,0x26c21167}}, // saen, tulm, rruf, vuko_,
+ {{0x5d849168,0x645600a4,0xc5fa80be,0x44291169}}, // _المل, yoyi, _שפעט, kha_,
+ {{0x61e1916a,0x4375916b,0x26c200ad,0x60c1817b}}, // sell, _култ, tuko_, rulm,
+ {{0x4429116c,0x60c1916d,0x8c1a8039,0xe7d7916e}}, // dha_, sulm, _שוני, ण्यप,
+ {{0x7c29916f,0x51879170,0x26c21171,0xc9878389}}, // lher, _суда, ruko_, _суди,
+ {{0xed5a1172,0x64a60912,0xe8168076,0x26c21173}}, // тов_, раба, _थकला_, suko_,
+ {{0x44291174,0x1e86013a,0x20000974,0x6286005c}}, // gha_, _клим, _unii_, _puko,
+
+ {{0x7e7e1175,0x64561176,0x6272809a,0x6d5e009a}}, // [800] _tipp, royi, _głos, _wypa,
+ {{0x6286003a,0xb33b0214,0x6d4100f7,0xc6930039}}, // _vuko, _geçm, úlac, _ואז_,
+ {{0x44291177,0x55bb0039,0x61ee007a,0x7c299178}}, // bha_, _במיו, _vabl, kher,
+ {{0x44291179,0x386d82be,0x6286117a,0xfa8880ff}}, // cha_, _cher_, _tuko, _lừa_,
+ {{0x4420117b,0x66f4917c,0x628600e4,0x386d917d}}, // mki_, сплу, _uuko, _dher_,
+ {{0x61ee0362,0x40960785,0x386d82af,0x9c7c8140}}, // _uabl, _врат, _eher_, _isči,
+ {{0xac26917e,0xdb008789,0x29070106,0xd943117f}}, // ифик, damá, änat_, _дери,
+ {{0x20e80059,0x7c299180,0x6aa28247,0x7c3b9181}}, // _işi_, gher, _pwof, giur,
+ {{0x787f00eb,0xf5ea9182,0x5a348081,0xdb0d008b}}, // _būvn, _имал_, йнит, ngað,
+ {{0x44201183,0x7a248362,0x387f8cdb,0x24780176}}, // hki_, _bòta, _ziur_, _dōmi_,
+ {{0x44201184,0x6e3c1185,0x7c298234,0xfa8880ff}}, // kki_, dirb, bher, _dừa_,
+ {{0x7c299186,0xb33b080a,0x44291187,0x44201188}}, // cher, _seçm, xha_, jki_,
+ {{0x22949189,0x4426918a,0x09e180c8,0x4420118b}}, // _التس, _ilo_, বাজা, dki_,
+ {{0x4420118c,0x4429022b,0x44268069,0xaadb83c8}}, // eki_, wha_, _hlo_, _סחור,
+ {{0x4429118d,0x4426918e,0xda05000f,0x6272809a}}, // tha_, _klo_, रासत_, _włos,
+ {{0xe97b8039,0xf21c8105,0x5bb80009,0x717b8039}}, // _בנוש, _पकड़_, ился_, _בנוס,
+ {{0x4429118f,0xb06602f1,0xf7731190,0x32d08129}}, // [810] rha_, svää, _باغ_, này_,
+ {{0x44291191,0x44201192,0x44269193,0x69c8904a}}, // sha_, aki_, _llo_, ngde,
+ {{0xda781194,0x4f58026a,0x44201195,0x6f1d0176}}, // рят_, _مجید_, bki_, lysc,
+ {{0xa3ce023c,0xe8df0104,0xd1268117,0x44268091}}, // _राय_, _quốc_, _ہم_, _nlo_,
+ {{0x63ad1196,0xd9f8815c,0x161b016f,0x44390581}}, // maan, ्ञात_, _नकार_, _ims_,
+ {{0x63ad1197,0x6da28073,0x7c2981c6,0xe5a29198}}, // laan, пиша, wher, пиши,
+ {{0xa3ce1199,0x4426807a,0x7c29919a,0x2907119b}}, // _राम_, _blo_, ther, ånad_,
+ {{0x63ad119c,0x4369919d,0x4426919e,0x1be988ed}}, // naan, лайн_, _clo_, удии_,
+ {{0x7c3b919f,0x7c2991a0,0x44268247,0x44390558}}, // riur, rher, _dlo_, _mms_,
+ {{0x442691a1,0x442011a2,0x7c2991a3,0x63ad11a4}}, // _elo_, zki_, sher, haan,
+ {{0x63ad11a5,0x442691a6,0xfa888028,0x60c500dd}}, // kaan, _flo_, _vừa_, nuhm,
+ {{0x442691a7,0x5c7591a8,0xdb0091a9,0x7bd98314}}, // _glo_, _улет, samá, _ubwu,
+ {{0x44200805,0x63ad11aa,0xceb283de,0x28c9064a}}, // vki_, daan, ּיל_, ांडि,
+ {{0x44200063,0xe29c80be,0x7d1c03ed,0x519581a8}}, // wki_, _ישׂר, tyrs, _الغذ,
+ {{0x442011ab,0x823491ac,0x1d19035f,0x63ad11ad}}, // tki_, _برنا, ають_, faan,
+ {{0x63ad11ae,0x61b7816f,0x3eb28110,0x7bc991af}}, // gaan, _आयुष, šyti_, ngeu,
+ {{0x442011b0,0xe9da11b1,0x7ae991b2,0xd46711b3}}, // [820] rki_, лка_, nwet, сите_,
+ {{0x645d11b4,0x7afd8198,0x27f201b4,0xdb0d01fa}}, // _eksi, ästy, _hayn_, rgað,
+ {{0xf77f03a7,0x63ad11b5,0x5a3a80be,0x442011b6}}, // meça_, baan, נגעה, pki_,
+ {{0x7ae991b7,0x63ad11b8,0xa967047f,0xf77f02df}}, // kwet, caan, щиха_, leça_,
+ {{0x2907016d,0x628f91b9,0x9ad38870,0x61e511ba}}, // änar_, ícol, _dịkw, behl,
+ {{0x442691bb,0x60c50359,0xd0420085,0x7cca8085}}, // _slo_, buhm, rtlə, _bərp,
+ {{0x645d0364,0x261991bc,0xd0420085,0xd4978110}}, // _yksi, _बकरी_, stlə, бры_,
+ {{0x290500ce,0x1e86845e,0x786a91bd,0x9c7c80c3}}, // _šlag_, _глам, býva, _opče,
+ {{0x59cf11be,0x09ac80ab,0x6a700106,0x69c811bf}}, // स्कर, _খাবা, räff, _ödes,
+ {{0x98a787d9,0xb33b0201,0x6d4003bf,0x23ad0084}}, // _aynı_, _seçk, şmay, mųjų_,
+ {{0x69dc11c0,0x63ad11c1,0x4426812b,0x69ce0176}}, // _mbre, yaan, _tlo_, _mcbe,
+ {{0x63ad02c1,0x442691c2,0x438680f7,0xbb8680f7}}, // xaan, _ulo_, _الإق, _الإي,
+ {{0x69dc11c3,0xf1a810a1,0x23ad0084,0x291886ae}}, // _obre, गरान, nųjų_, ärab_,
+ {{0x63ad11c4,0x7afd806a,0x44f880ff,0x41c70f3d}}, // waan, æste, _kĩ_, _लाइस,
+ {{0x43960a2c,0x786a83f2,0x2d910035,0xb425866f}}, // _ọrụ_, zýva, rcze_, łżeń,
+ {{0x682604c3,0x25bf11c5,0xa3cc91c6,0x53990198}}, // _tóda, şul_, श्च_, рвая_,
+ {{0x7b6711c7,0xb33b0086,0x63ad11c8,0xf77f0073}}, // [830] стве, _keçi, raan, beça_,
+ {{0x63ad11c9,0x9c7c80fe,0x9ad3819d,0x69dc0362}}, // saan, _isču, _sịkw, _cbre,
+ {{0x63ad11ca,0x6826026f,0xf96b0196,0x44390198}}, // paan, _módn, урай_, _tms_,
+ {{0x69dc11cb,0x7c6711cc,0x63ad0079,0x160700d4}}, // _ebre, _داخل, qaan, शावर_,
+ {{0x7c2d11cd,0xa3ce0740,0x60c50a84,0x287c00be}}, // mhar, _रात_, suhm, אנאמ,
+ {{0xae038aed,0x7c2d11ce,0x69d80e88,0x313511cf}}, // लाइन_, lhar, न्दी, _дегр,
+ {{0xac0991d0,0x24098171,0xafdb006a,0x661702f9}}, // инка_, инки_, rnøj, _moxk,
+ {{0x7afd025b,0x7c2d11d1,0x601f020f,0xa4b80039}}, // _krst, nhar, _fëmi, צלחה_,
+ {{0xc7b98019,0x7bc980e7,0x20f78162,0x7afb05f3}}, // _idő_, rgeu, _săi_, _šutj,
+ {{0x394591d2,0x1ddb097d,0xaca311d3,0x7ae991d4}}, // жног, म्मत, _atọg, rwet,
+ {{0xa96991d5,0x7c2d11d6,0x216991d7,0x7bcf11d8}}, // рина_, khar, рини_, _occu,
+ {{0xe8df8028,0x3dc989ab,0xe8e0027d,0x20d191d9}}, // hiệm_, ɗawa_, _chộp_, rái_,
+ {{0x7c2d06df,0x442d8068,0x6d4111da,0xc7d681c6}}, // dhar, mhe_, úlan, חורי_,
+ {{0x443f91db,0xb33b07d9,0x442d8073,0x628b91dc}}, // liu_, _geçi, lhe_, _iugo,
+ {{0xf77f11dd,0x27e686ff,0x7afd11de,0x628391df}}, // reça_, yeon_, _arst, _iino,
+ {{0x443f91e0,0x7c2d11e1,0x644991e2,0x442d8073}}, // niu_, ghar, nnei, nhe_,
+ {{0xda0509a3,0x63ab8019,0x644991e3,0x987b03de}}, // [840] रांत_, _megn, inei, _דאקט,
+ {{0x63ab91e4,0x442d8940,0x644991e5,0x628b820c}}, // _legn, hhe_, hnei, _mugo,
+ {{0x7afd11e6,0x7c2d11e7,0x442d91e8,0x443f8110}}, // _erst, bhar, khe_, kiu_,
+ {{0x81e70a49,0x307691e9,0x628391ea,0x68260019}}, // ভাগ_, цузс, _lino, _módo,
+ {{0x442d91eb,0x443f91ec,0x44f88028,0x087700be}}, // dhe_, diu_, _sĩ_, קענט_,
+ {{0x8c1f00c8,0xdcba8098,0x7c240074,0xa0a611ed}}, // নোদন_, ащи_, lkir, _магд,
+ {{0x25ba11ee,0x7a2001ec,0x291c02f1,0x443f91ef}}, // _mdpl_, _nöti, ävad_, fiu_,
+ {{0x09b48a49,0x442d91f0,0xb33b1010,0x443f91f1}}, // জ্ঞা, ghe_, _seçi, giu_,
+ {{0x63ab91f2,0x2d9691f3,0x628b80c3,0x63b90035}}, // _degn, _дрес, _cugo, ówni,
+ {{0x386691f4,0x628b803a,0x291e8110,0xfce691f5}}, // llor_, _dugo, tyta_, _мозо,
+ {{0xf9930051,0x443f91f6,0x69fb010f,0x7c240c56}}, // ורת_, biu_, בליק, kkir,
+ {{0x443f91f7,0x63ab91f8,0x291e91f9,0x628b91fa}}, // ciu_, _gegn, ryta_, _fugo,
+ {{0xe1ef91fb,0xc7b38051,0x38668087,0x628391fc}}, // رسی_, ובר_, ilor_, _fino,
+ {{0x765c11fd,0x7c2d11fe,0x628391ff,0x64409200}}, // gory, whar, _gino, limi,
+ {{0x7c2d1201,0x61e88bbd,0x7c2402a6,0xfe6e0180}}, // thar, medl, fkir, دگي_,
+ {{0x69da8a34,0x62839202,0xdb0080e1,0x38668b67}}, // lfte, _zino, pamä, jlor_,
+ {{0x7c2d1203,0xdceb03bf,0xf77f03a7,0x628b8118}}, // [850] rhar, ınız, meço_, _xugo,
+ {{0x7c2d1204,0x61e8831d,0x64409205,0x443f9206}}, // shar, nedl, himi, ziu_,
+ {{0x64409207,0x7c2d1208,0xf8b1004e,0x442480b9}}, // kimi, phar, عکس_, kkm_,
+ {{0xdce50028,0x443f9209,0x6440920a,0x25ac802a}}, // _nghĩ, xiu_, jimi, _aedl_,
+ {{0x63ab920b,0x6605920c,0x443f920d,0x63a206a5}}, // _regn, зпла, viu_, ñone,
+ {{0x63ab8698,0x61e8920e,0x628b920f,0x7aed1210}}, // _segn, jedl, _rugo, mwat,
+ {{0x442d9211,0x443f9212,0x64409213,0x628b9214}}, // the_, tiu_, fimi, _sugo,
+ {{0x61f51215,0x64409216,0x3f891217,0xa2c2001b}}, // _hazl, gimi, _ngau_, _लिम्,
+ {{0x443f9218,0x63ab8125,0x62839219,0x442d84a9}}, // riu_, _vegn, _pino, rhe_,
+ {{0x442d921a,0x443f921b,0x7c24059f,0x61f50748}}, // she_, siu_, zkir, _jazl,
+ {{0x442d921c,0x63ab921d,0x61f5121e,0x6440921f}}, // phe_, _tegn, _mazl, bimi,
+ {{0x64409220,0x7aed0c56,0xe8df082e,0x628b9221}}, // cimi, kwat, _ahịa_, _tugo,
+ {{0x765c1222,0x62839223,0x249a03ac,0xd5d2852a}}, // tory, _tino, _stpm_, _सामज,
+ {{0x682604c3,0xa3ce000f,0x7aed1224,0xe8df01bc}}, // _tódo, _राह_, dwat, _chịa_,
+ {{0x32078035,0x7afb9225,0x7c241226,0x00000000}}, // _inny_, lvut, tkir, --,
+ {{0x5fc6035a,0x765c1227,0xc4830abe,0x7bcd0041}}, // _वाटल, sory, елск, ggau,
+ {{0x7c241228,0x6e461229,0x6fa20670,0x7aed122a}}, // [860] rkir, _неиз, _क्यू, gwat,
+ {{0x7c240b0c,0x17570051,0x6440922b,0x2fdf8122}}, // skir, _מספר_, zimi, _abug_,
+ {{0x6440922c,0x2eff8359,0xae1b00be,0x7aed008e}}, // yimi, _aruf_, _הויכ, awat,
+ {{0x442b122d,0x4424001c,0x7aed0c2e,0x61e8922e}}, // _glc_, _ôm_, bwat, zedl,
+ {{0x61f507d9,0x6440922f,0x38669230,0xa2bd83dd}}, // _fazl, vimi, rlor_, _शिष्,
+ {{0xe29701a1,0x63a41231,0x442480ee,0x160386a7}}, // _хар_, lbin, wkm_, लागर_,
+ {{0xf8e00076,0x8fa69232,0x443d9233,0x61e89234}}, // _नहिय, _наве, _bmw_, vedl,
+ {{0x7afb01dd,0x60c89235,0x61e89236,0x329883a7}}, // _šuti, vudm, wedl, овиќ_,
+ {{0x63a41237,0x61f51238,0x6ab61239,0x06e40264}}, // ibin, _yazl, nsyf, মিডি,
+ {{0x27e003c3,0x80c0923a,0x3f808110,0xa2bd90be}}, // _abin_, _विदे, žius_, _शिर्,
+ {{0xdca3835f,0x64a38a1f,0x6608813c,0x859b8039}}, // _захи, _заха, _indk, _השבו,
+ {{0x7aed0063,0x35d28105,0x61e8923b,0x3207923c}}, // ywat, _साड़, sedl, _enny_,
+ {{0x7ae2803b,0xf77f0073,0x82fa853d,0x442b09c4}}, // _upot, reço_, _دراز_, _rlc_,
+ {{0x661a8364,0x4b7b81c6,0xa7fb123d,0xe3b08fd3}}, // _jotk, _האדו, doñe, _سرچ_,
+ {{0x7a2d840e,0x601f00f1,0x1c208105,0x61db82f6}}, // _fútb, _sëmu, _बवाल_, _הקוד,
+ {{0x63a4123e,0x7aed123f,0xd2510019,0x442b00e5}}, // gbin, twat, ھنا_, _qlc_,
+ {{0x3b541240,0xa3de8aed,0x7bcd1241,0x35d281ce}}, // [870] ектр, द्य_, rgau, _साढ़,
+ {{0x24868013,0x91fc8029,0x661a807b,0x7c870012}}, // _liom_, klām, _notk, _нуме,
+ {{0x4fd59242,0x21270028,0xb33b0036,0x26cb1243}}, // джет, ánh_, _reçu, luco_,
+ {{0xa2bd9244,0x2eff80b9,0x35d303b7,0xa3de852a}}, // _शिल्, _pruf_, _ताड़, द्म_,
+ {{0xa7fb07f4,0xd6db9245,0x236681c0,0x26cb016a}}, // coñe, сте_, _nyoj_, nuco_,
+ {{0x2f238029,0x443d808e,0x992b1246,0x644d1247}}, // _rīgā_, _smw_, _люба_, lnai,
+ {{0x644d1248,0x3cf78035,0x80db8327,0xb33b03ed}}, // onai, ंटें_, _बहें, _veçu,
+ {{0x62870693,0x13b100ab,0xaca3819d,0x9f448037}}, // _hijo, _ছাড়, _haịm, _lamù_,
+ {{0x661a9249,0xb33b0214,0x2eff80b9,0x644d124a}}, // _fotk, _geçt, _uruf_, inai,
+ {{0x644d0083,0x7afb8074,0x26cb124b,0x2009124c}}, // hnai, rvut, duco_, _anai_,
+ {{0x7643924d,0xdb0b8019,0xd90f803d,0x63a4124e}}, // miny, _megé, ایع_, ybin,
+ {{0xa3ae8a3a,0x7643924f,0x59dd1250,0xeb0d1251}}, // करण_, liny, न्तर, _सपूत_,
+ {{0x629d01c5,0x7a201252,0xd0928201,0x09ac80ab}}, // _ntso, _kött, _müəy, _খারা,
+ {{0xfce600e8,0xdb2680d7,0x62871253,0x3e640061}}, // мого, _توهی, _nijo, jött_,
+ {{0x2101005c,0x91fc80eb,0x63a41254,0x7a201255}}, // nčić_, klāj, tbin, _mött,
+ {{0x27e002f7,0x628f1256,0x644d1257,0x601f0168}}, // _ubin_, _buco, gnai, _dëms,
+ {{0x76439258,0x4b7a8158,0x62871259,0xcb67125a}}, // [880] kiny, ַטעג, _bijo, дате_,
+ {{0x644d125b,0x7643925c,0x62870b80,0x21010267}}, // anai, jiny, _cijo, kčić_,
+ {{0x7643925d,0x628707fc,0x2101011f,0x644d125e}}, // diny, _dijo, jčić_, bnai,
+ {{0xa7fb04c3,0x28c5925f,0x320984b9,0x3b0a0fdd}}, // poñe, _विनि, _ɗaya_, _демо_,
+ {{0x62870216,0x76438234,0x987b03de,0x628f018f}}, // _fijo, finy, קאנט, _guco,
+ {{0xd49a9260,0x59dd1261,0x62871262,0x9d450019}}, // ори_, न्दर, _gijo, _آئین,
+ {{0xf9831263,0x7a200338,0xa9670198,0x21671264}}, // _агро, _dött, _жира_, _жири_,
+ {{0xa8038098,0xb7fb801b,0x628702f9,0x00000000}}, // _изсл, _एफएम_, _zijo, --,
+ {{0x984600eb,0x7a20016d,0x76439265,0x998c8088}}, // _dēļ_, _fött, biny, ridž_,
+ {{0x7a201266,0x76439267,0x210100c3,0x20091268}}, // _gött, ciny, bčić_, _snai_,
+ {{0xa3c00576,0xa3ce016f,0x24868706,0x2d8c81a1}}, // ीला_, रला_, _tiom_, _igde_,
+ {{0x3e640065,0x699e800d,0x53468ada,0x2b40811f}}, // zött_, _ख्री, _охла, _žicu_,
+ {{0xa7741269,0x2ba480bc,0x63af0bda,0x4432126a}}, // ключ, _ग्या, _recn, dhy_,
+ {{0x68fc926b,0x26d90102,0xb8f480ab,0x764e126c}}, // årds, rtso_, _হন_, gnby,
+ {{0x236d026c,0x63af0669,0x628f126d,0x644d126e}}, // _šej_, _pecn, _suco, tnai,
+ {{0x7643926f,0x62871270,0x61f89271,0x20091272}}, // ziny, _sijo, _havl, _unai_,
+ {{0xf4841273,0x76439274,0x61f89275,0x0c840048}}, // [890] турн, yiny, _kavl, тырм,
+ {{0x61f88025,0x644d1276,0x9c13819d,0x61fa9277}}, // _javl, snai, _kọch, ndtl,
+ {{0xa3ae8006,0x64dd9278,0x76438234,0x62871279}}, // करा_, _महेश, viny, _vijo,
+ {{0x4432127a,0x2c27035f,0x210102a5,0x628f01a1}}, // chy_, _цьог, včić_, _tuco,
+ {{0x4429127b,0x28c58bb8,0x7643927c,0xceeb803d}}, // mka_, _विभि, tiny, تران_,
+ {{0x4429127d,0xf7708416,0x321c816b,0x7876041c}}, // lka_, جام_, _novy_, máve,
+ {{0x764389ca,0x4429127e,0x7876127f,0x7a24823e}}, // riny, oka_, láve, _gòti,
+ {{0x44291280,0xa3d70d86,0x76439281,0x21010668}}, // nka_, _साफ_, siny, rčić_,
+ {{0x44291282,0xa3de8744,0x76439283,0x61f89284}}, // ika_, द्ध_, piny, _bavl,
+ {{0x80c08b6f,0xe9d99285,0x3eb9004a,0xa2c2064a}}, // _विशे, чко_, dsst_, _लिस्,
+ {{0x61f89286,0x68461287,0x44291288,0x186a1289}}, // _davl, енна, kka_, зани_,
+ {{0x44f18a8e,0xdb0601ac,0xb4fa81c6,0x7876128a}}, // _iš_, _leká, _מפעי, káve,
+ {{0xc2c88277,0x442f83a8,0xbbbd816f,0x93278f24}}, // _قبول_, _ilg_, ोलीक, _تران,
+ {{0x4429128b,0x7876128c,0x2d8c128d,0xf65200f7}}, // eka_, dáve, øde_, ائح_,
+ {{0xdb04128e,0x442902a3,0x7c29928f,0x1d071290}}, // mbié, fka_, oker, нери_,
+ {{0x44291291,0x1ddf816f,0x44f1816b,0x3c2e01d6}}, // gka_, प्तत, _mš_, _býva_,
+ {{0x7c29838e,0x28c5809a,0x290301b9,0x44f18bda}}, // [8a0] iker, _विडि, _arja_, _lš_,
+ {{0x44291292,0xe1f70638,0x4a431293,0x7a2486c4}}, // aka_, нгу_, гнув, _dòtw,
+ {{0x442902c1,0x7c298d5b,0x4c861294,0x3ead9295}}, // bka_, kker, _плов, _awet_,
+ {{0x44291296,0x7c299297,0x6f041298,0x768b0035}}, // cka_, jker, _kric, _użyc,
+ {{0x68260510,0xdcfc0201,0x5fd18076,0xc8ec9299}}, // _códi, marı, _हारल, _जन्म_,
+ {{0xdcfc0b06,0x80db00ab,0xceb303c8,0x63b60079}}, // ları, ণিজ্, ריג_, layn,
+ {{0x3da5835f,0xa3d704e5,0x442f929a,0xdb1d0176}}, // тріб, _साम_, _blg_, _adsè,
+ {{0x6f040012,0x61f8929b,0x63b60079,0x7c29929c}}, // _oric, _savl, nayn, gker,
+ {{0x44f18267,0x53348087,0xa3ce01d0,0x661e129d}}, // _eš_, тепт, _राई_, _oopk,
+ {{0x4429129e,0x2d8c03f2,0x63b601b4,0xdb0f0580}}, // zka_, ždej_, hayn, _decè,
+ {{0x44290063,0xdcfc0214,0x6f04129f,0x403512a0}}, // yka_, karı, _aric, венс,
+ {{0x2fc080f7,0x7c2992a1,0x442912a2,0x63b601b4}}, // óigh_, cker, xka_, jayn,
+ {{0x61f892a3,0x6d480511,0x63b60079,0x44f1816b}}, // _tavl, údan, dayn, _zš_,
+ {{0x4429009a,0x6f04016d,0x787612a4,0xbe880198}}, // wka_, _dric, váve, ессе_,
+ {{0xf1b38051,0xcfb00264,0x2d8512a5,0xd12f804a}}, // _עסק_, _কারন, äle_, _сх_,
+ {{0x442912a6,0x6f0412a7,0xd6db1294,0x3ea004b7}}, // uka_, _fric, _ето_, _ftit_,
+ {{0x442912a8,0x78ba92a9,0xa3d7058c,0x06e880ab}}, // [8b0] rka_, lstv, _साठ_, পিডি,
+ {{0x787612aa,0x51f80150,0x2296006a,0x7c298102}}, // ráve, нную_, _dæk_, zker,
+ {{0x78ba86d4,0xe8028076,0x7fd5902a,0x63b601b4}}, // nstv, _रोहा_, вілі, bayn,
+ {{0x644812ab,0x3ead809c,0x69d5009a,0x764706c0}}, // édit, _pwet_, _wcze, lijy,
+ {{0x141a098a,0xe8df001c,0xdb0b806a,0x799c0114}}, // _חורב, _thỏa_, _udgø, _ffrw,
+ {{0xf77400be,0xb5c285e9,0x628a92ac,0x442f802a}}, // נקס_, айшл, _nifo, _slg_,
+ {{0x7c2992ad,0xf402002e,0x442f92ae,0x9165003d}}, // tker, _faţă_, _plg_, _شهره,
+ {{0x130991d2,0x5fd891bc,0xd138809a,0x628a8915}}, // дний_, _डायल, dzą_, _aifo,
+ {{0x628a816d,0x56958081,0x3ed981a8,0x95d983a7}}, // _bifo, _разт, _زواج_, _одат_,
+ {{0x7c2992af,0xdcfc0059,0x32d986c0,0x6442811b}}, // sker, zarı, rèy_, _emoi,
+ {{0xa2bd885d,0x07a60554,0x442f92b0,0x2dd880f7}}, // _शिक्, канн, _tlg_, _سبلة_,
+ {{0x6f0412b1,0x63b602a3,0xdcfc0085,0x23c7826b}}, // _pric, xayn, xarı, _dìjà_,
+ {{0xe1ee92b2,0xfc46826f,0x539a82f6,0x628a8032}}, // _кг_, ších_, _חינו, _fifo,
+ {{0xceb2873a,0x63a992b3,0x2c4c801b,0x248b0144}}, // שים_, lben, vědi_, _aicm_,
+ {{0xdcfc1014,0xd1388035,0x78ba8dcc,0xa7fb016a}}, // tarı, czą_, cstv, soña,
+ {{0x2ae585e8,0x51848160,0x21888158,0x63a992b4}}, // _कहाँ_, _суча, _אָפּ, nben,
+ {{0xd4670572,0x787880a9,0x63b612b5,0x3b860048}}, // [8c0] тите_, níve, rayn, тлаг,
+ {{0xa3d70d38,0x2296013c,0xdcfc0214,0x7d0892b6}}, // _साथ_, _væk_, sarı, _jérô,
+ {{0x63a992b7,0xd7fa92b8,0x28c904e5,0x64a612b9}}, // kben, дул_, ांगि, _шана,
+ {{0x63a9911b,0x26cf804f,0x63b60079,0xc95281c6}}, // jben, fugo_, qayn, סמא_,
+ {{0x63a98805,0x78ba826f,0x6378026b,0xfaa6111c}}, // dben, zstv, _dúní, _јаго,
+ {{0x71a30162,0x63a992ba,0x787881d0,0xaca40135}}, // _варз, eben, díve, _nkịt,
+ {{0x628a92bb,0xd94612bc,0xdb0f12bd,0x61fc12be}}, // _rifo, _реви, _recé, _iarl,
+ {{0x61fc12bf,0xdfd280f7,0x7a2d8019,0xab8692c0}}, // _harl, _خير_, _búto, купк,
+ {{0x61fc12c1,0xa3e4001b,0x26dd809a,0x97ea8085}}, // _karl, प्य_, ctwo_, ışdı,
+ {{0x682607a3,0x78ba90e8,0x61fe12c2,0x61fc12c3}}, // _módu, tstv, ndpl, _jarl,
+ {{0x3ea692c4,0xc4c592c5,0x1be212c6,0x02de02f1}}, // lpot_, _متنو, खभाल_, नझुन,
+ {{0xa3ce12c7,0x23ba92c8,0xa3d70fd5,0x6d5a8216}}, // _राग_, _آداب_, _साध_, _ixta,
+ {{0x6f0292c9,0xd138809a,0x682612ca,0x628a92cb}}, // nvoc, szą_, _nódu, _tifo,
+ {{0x78ba89cf,0x6f0292cc,0xa4d50d8e,0x67200338}}, // pstv, ivoc, логі, ämja,
+ {{0xfa778051,0xa3ce12cd,0x97320061,0x69c81238}}, // _שעות_, _राख_, لکیا, _ödey,
+ {{0x7876026f,0x3c31816b,0x54540425,0xc61f92ce}}, // rávc, _káva_, авст, _भव्य_,
+ {{0x2d9e8e1c,0x61fc12cf,0x337500c4,0x06d78264}}, // [8d0] _ofte_, _barl, угар, _দৈনি,
+ {{0x35f5098d,0x61fc12d0,0x63a99123,0x394b003d}}, // _спор, _carl, zben, وشاپ_,
+ {{0x61fc12d1,0xdb0b80ab,0x787603b0,0x27ef83ba}}, // _darl, _regí, láva, tegn_,
+ {{0xdb0b8065,0xdb0612d2,0x61fc0039,0x28c586ae}}, // _segí, _bekä, _earl, _विसि,
+ {{0x6d5a8201,0x787612d3,0x7c2d12d4,0x78788187}}, // _axta, náva, mkar, xíve,
+ {{0xd77480f7,0x26cf92d5,0x78788187,0x7bc292d6}}, // جامع, rugo_, víve, _adou,
+ {{0xeb9992d7,0xd46992d8,0x7e6392d9,0x26dd8035}}, // ний_, нике_, ronp, stwo_,
+ {{0x200d8012,0x7876037d,0x787892da,0xdb0b8187}}, // _unei_, káva, tíve, _negã,
+ {{0xc7b28039,0x7c2d12db,0x05d3025a,0x9d238264}}, // _לבן_, ikar, _तांब, বছেন_,
+ {{0x78760a56,0x96f892dc,0x63a992dd,0x386912de}}, // dáva, _جعفر_, sben, _akar_,
+ {{0x7c2d12df,0x2ba492e0,0x78788187,0x9f4e001b}}, // kkar, _ग्वा, síve, čním_,
+ {{0x1ddf836d,0xddd5001b,0x2c098035,0xdb0a016b}}, // प्रत, mozř, वाओं_, šnéh,
+ {{0x91e592e1,0x28c58bbc,0x09e58abe,0x442d92e2}}, // _боле, _विवि, _болн, mke_,
+ {{0x442d92e3,0xdd920f99,0x386912e4,0xaf7683de}}, // lke_, صور_, _ekar_, טערס_,
+ {{0x442d92e5,0x7c3612e6,0x6e218668,0x09b200ab}}, // oke_, thyr, _molb, _চালা,
+ {{0x442d92e7,0x7c2d12e8,0x044312e9,0x8c4312ea}}, // nke_, gkar, бесн, бесе,
+ {{0x442d92eb,0x9b9300f7,0x248012ec,0x7c3612ed}}, // [8e0] ike_, إلكت, rmim_, rhyr,
+ {{0xf1a903f8,0xe9d70003,0x05d292ee,0x3b8312ef}}, // نامه_, лку_, _साइब, слуг,
+ {{0x442d92f0,0x3ebf8012,0x61fe12f1,0x19b680be}}, // kke_, _avut_, rdpl, רפער_,
+ {{0x442d92f2,0x7c2d12f3,0x6aad81cd,0x61fc12f4}}, // jke_, ckar, _ħafn, _warl,
+ {{0x6e2192f5,0x61fc12f6,0xc7d70039,0x442d92f7}}, // _bolb, _tarl, רוני_, dke_,
+ {{0x442d8b3c,0x6e2192f8,0xccfa84ae,0x442212f9}}, // eke_, _colb, ећи_, _hok_,
+ {{0x6aa292fa,0x442212fb,0xf8c5825e,0x442d84b9}}, // _stof, _kok_, _विषय, fke_,
+ {{0x442212fc,0x25f0809a,0x7bd612fd,0x442d92fe}}, // _jok_, _इसकी_, ngyu, gke_,
+ {{0xa6ca89d7,0x628e12ff,0x5a349300,0xa3d70592}}, // _سوال_, _kibo, инит, _सास_,
+ {{0xa3e40e70,0xa2d501fe,0x2ba480d4,0xd6cf8f99}}, // प्त_, यंत्, _ग्रा, _رقم_,
+ {{0x8d939301,0x44221302,0x2907803b,0xf5938013}}, // _المش, _ook_, _crna_, _المج,
+ {{0x628e1303,0x764a9304,0x4422005f,0x557780be}}, // _libo, lify, _nok_, _געבן_,
+ {{0xa3ce09a3,0x09cc00c8,0x80c09305,0xe80b8beb}}, // _राज_, র্যা, _विके, _सोफा_,
+ {{0xe80b800f,0x7c229306,0x78761307,0x386681b0}}, // _सोना_, _hoor, ráva, hoor_,
+ {{0x16d19308,0x44221309,0x7c2d130a,0x7876130b}}, // _सम्ब, _bok_, tkar, sáva,
+ {{0x09cc00c8,0x44220214,0x26de0087,0x628e130c}}, // র্মা, _cok_, ător_, _aibo,
+ {{0x44220025,0x7c22930d,0x628e130e,0x5b15130f}}, // [8f0] _dok_, _moor, _bibo, шмат,
+ {{0x7c2d00ad,0x442d9310,0x628e1311,0x7c229312}}, // skar, zke_, _cibo, _loor,
+ {{0x645a1313,0x44221314,0x764a9315,0x442d8be7}}, // étic, _fok_, dify, yke_,
+ {{0x7c229316,0xe8d784de,0x6e218f06,0x72d51317}}, // _noor, רואר_, _solb, _комф,
+ {{0x6e219318,0x44c101e2,0x64499319,0x27e9131a}}, // _polb, lė_, viei, _mban_,
+ {{0x09cc0a49,0x80aa8a49,0x7d0882a5,0xa1940071}}, // র্বা, _কিন্, _brds, _гарч,
+ {{0x44c101e2,0x442d931b,0x4422131c,0xa3dc131d}}, // nė_, tke_, _yok_, _ठाम_,
+ {{0x0c258aac,0x63ad131e,0x442d931f,0x6a6b0192}}, // рмин, nban, uke_, rüfu,
+ {{0x2907812b,0x78a49320,0x63ad1321,0x7c229322}}, // _srna_, _čiva, iban, _door,
+ {{0x442d9323,0x44c101e2,0x4a459324,0x27e91325}}, // ske_, kė_, рнов, _aban_,
+ {{0x69dd090a,0x7c2289f8,0xc32380c8,0x8e848013}}, // _पानी, _foor, _বেশি_, _الله,
+ {{0x7bc78110,0xdb0f03a7,0xd71c80ab,0x44c10110}}, // ėjus, _mecâ, _দেয়া_, dė_,
+ {{0x92b78013,0xf77208ca,0x290780dd,0x93948a47}}, // _إحصا, تاد_, _wrna_, _اجما,
+ {{0x44221326,0x63bb9327,0xbb3a8496,0x63ad1328}}, // _sok_, laun, _בעני, eban,
+ {{0x628e1329,0x6142932a,0xdb0f05e4,0x89da8039}}, // _ribo, _меша, _decí, _יחסי,
+ {{0x63ad132b,0x63bb932c,0x6f09932d,0x628e132e}}, // gban, naun, _orec, _sibo,
+ {{0xe9da0a08,0x386680f3,0x6d41046d,0xeb97002e}}, // [900] кка_, voor_, úlar, риу_,
+ {{0x63ad132f,0x63bb8bb1,0x7af60114,0x787881d0}}, // aban, haun, rwyt, díva,
+ {{0xe0da1330,0xf1dd83eb,0x41dd8eb4,0xeaf28592}}, // тве_, _मानन, _मानस, _अन्त_,
+ {{0x35dc0665,0x6f099331,0xa3e7800d,0xdb0f002e}}, // _बाड़, _brec, म्म_, _decâ,
+ {{0x6f09840e,0x27ff8590,0xe7e8035a,0x764a9332}}, // _crec, _daun_, ट्या_, tify,
+ {{0x69dd0076,0x7c229333,0x6f099334,0x7d008019}}, // _पायी, _soor, _drec, járá,
+ {{0x01bb8a49,0x764a9335,0x7c229336,0x38669337}}, // ংলাদ, rify, _poor, poor_,
+ {{0x64561338,0x6f099339,0x27ff8763,0x69d8816f}}, // nnyi, _frec, _gaun_, _माही,
+ {{0x7c22933a,0xd37780d6,0x27e902f7,0xadba81a8}}, // _voor, ичь_, _rban_, _لهذا_,
+ {{0x7c2290f4,0x20120870,0x27e6933b,0x5a34933c}}, // _woor, _anyi_, lfon_, снот,
+ {{0xa3d703a4,0x78760a56,0x63ad133d,0xdca38088}}, // _साल_, rávn, yban, _дахи,
+ {{0x44c10110,0x3e7b8036,0x78a10061,0x63ad133e}}, // vė_, rête_, _élve, xban,
+ {{0x63a2933f,0xdb0f0118,0x200501d0,0xdb060032}}, // _ifon, _pecí, ěli_, _eekú,
+ {{0x20050063,0x20121340,0x44c101e2,0xa3e41053}}, // śli_, _enyi_, tė_, प्स_,
+ {{0x9c470791,0x3a7b83de,0x00000000,0x00000000}}, // ахал, _שטוד, --, --,
+ {{0x8f471317,0x27e91341,0x7055003d,0x1dd30105}}, // _вход, _uban_, _دنبا, _ताकत,
+ {{0x290a01e2,0x63ad1342,0xaa7b00e1,0xfb870e49}}, // [910] _arba_, rban, _opýt, рывн,
+ {{0xe7e80063,0x39a71343,0x44c10110,0x63bb84b9}}, // ट्ठा_, ршав, pė_, zaun,
+ {{0x26141344,0x6f09803b,0x27ff8abf,0x63ad1345}}, // दारी_, _srec, _paun_, pban,
+ {{0xdd1c01d0,0x765a8168,0xe3b81346,0x785201a9}}, // _nářa, _ajty, _ключ_, tāvi,
+ {{0xe5a5917e,0x290a1347,0xd6db82dc,0x8c1a0039}}, // били, _erba_, тте_, _עושי,
+ {{0x644d1348,0x63a29349,0x14b8019f,0x6f0980e1}}, // liai, _afon, _حدیث_, _vrec,
+ {{0x27ff83f8,0x6f098114,0x44f88028,0x63bb934a}}, // _taun_, _wrec, _cũ_, taun,
+ {{0x6f098012,0x644d134b,0xed580039,0x62850706}}, // _trec, niai, יבור_, lmho,
+ {{0x869887eb,0x63bb934c,0x386d934d,0x6f098087}}, // _вкус_, raun, _iker_, _urec,
+ {{0x6ce7835f,0x629b82be,0xe4e7835f,0xe8f9934e}}, // _віде, rquo, _відн, гло_,
+ {{0xa3d71344,0x92d780c8,0x261989f2,0x9f5e000d}}, // सला_, াবে_, पाठी_, ětí_,
+ {{0x6601934f,0x44e19350,0x9b9580f7,0xaac704c5}}, // _halk, mó_, _الإت, _लिंक,
+ {{0x69c71351,0xbb858013,0x64561352,0x44e19353}}, // _odje, _الشي, vnyi, ló_,
+ {{0x69c7020f,0x6da31354,0x66018009,0x7bdd0118}}, // _ndje, зира, _jalk, _acsu,
+ {{0x44e19355,0x64a602df,0x2d8c0106,0x2ba6801b}}, // nó_, саба, äde_, síců_,
+ {{0x7c3b9356,0x69dc1357,0x644d1358,0x62999359}}, // nhur, _scre, giai, _kuwo,
+ {{0x290a003b,0xa3df8105,0x63b98114,0xd3b90a8e}}, // [920] _srba_, _तान_, _mewn, русі_,
+ {{0xbf9b0073,0x26c2135a,0xa3e786ab,0xdb0f0980}}, // ndên, msko_, म्त_, _decà,
+ {{0x44e1935b,0x31ba8158,0x93f9816f,0x787d0019}}, // jó_, רזענ, ्याच_, séve,
+ {{0x387f8af9,0x44e1935c,0xe73a8364,0x644d135d}}, // _chur_, dó_, лее_, ciai,
+ {{0x26c20ed6,0x4432003e,0x7c3b935e,0x2f18935f}}, // nsko_, mky_, dhur, роль_,
+ {{0x443203cb,0x63a29360,0x40961361,0xceb303c8}}, // lky_, _sfon, _грат, ליד_,
+ {{0x660182c1,0x44e19362,0x1619000f,0xdc1180ab}}, // _dalk, gó_, दातर_, িউনস_,
+ {{0x44321363,0x44f88028,0x62999364,0x69c71365}}, // nky_, _vũ_, _buwo, _zdje,
+ {{0x8c1b004c,0x26c2135a,0x66019366,0x32669367}}, // רופי, jsko_, _falk, йтов,
+ {{0x41dd835a,0x8afb00be,0x660181b4,0x11d880f7}}, // _माणस, נהיי, _galk, جودة_,
+ {{0x04968013,0x44e185a4,0xdb0f02ba,0xdfd11368}}, // _الصح, có_, _mecá, ويد_,
+ {{0x4432026f,0xdb0b816d,0x44201369,0x6e250214}}, // jky_, _begä, jji_, _sohb,
+ {{0x44320a56,0x4420136a,0x447b8158,0x7c6580f7}}, // dky_, dji_, _ענדע, _بالل,
+ {{0x7ae4136b,0x4420136c,0x63b9809a,0x66018df6}}, // dtit, eji_, _zewn, _xalk,
+ {{0x90a6936d,0x644d136e,0x4432136f,0x59dd9370}}, // _احتم, tiai, fky_, _मातर,
+ {{0x442000f1,0xb4e90770,0x7c2606cb,0x44269371}}, // gji_, _यही_, _cokr, _joo_,
+ {{0x644d1372,0x2fc9108c,0x442681c5,0x44e18065}}, // [930] riai, _idag_, _moo_, zó_,
+ {{0x442682c1,0x442000f6,0x386d832b,0x644d1373}}, // _loo_, aji_, _sker_, siai,
+ {{0x4432003e,0xda781374,0x6f0d026f,0x7d081375}}, // bky_, сят_, _hrac, ådst,
+ {{0x44200d38,0x44321376,0x6f0d1377,0x44e19378}}, // cji_, cky_, _krac, vó_,
+ {{0x443903d3,0x660188e5,0x9f0580f7,0xe610804e}}, // _ils_, _palk, توشو, کشن_,
+ {{0x44e18117,0xd1268154,0x4ac28072,0x2ba501d0}}, // tó_, _قم_, _शिजव, गुरा,
+ {{0x63b98063,0xe3b18013,0xfe708077,0x66019379}}, // _pewn, ورة_, رده_, _valk,
+ {{0x44e1937a,0x5ac9937b,0x386d80e8,0x6601937c}}, // ró_, алим_, _uker_, _walk,
+ {{0x44e1937d,0xe816816f,0xb4d601ce,0x27ed89bf}}, // só_, णारा_, _सम्_, _iben_,
+ {{0x7c3b937e,0x4432003e,0x4420137f,0x2fc91380}}, // shur, zky_, zji_, _adag_,
+ {{0x3ea9002e,0x9d559381,0x6f0d1382,0x261d015c}}, // _atat_, _بنات, _arac, मानी_,
+ {{0x186a1383,0xa4140264,0x44c581a9,0x69da9384}}, // рами_, _সত্য_, mē_, agte,
+ {{0x44321385,0x28c5825e,0x44c580eb,0x44201386}}, // vky_, _विकि, lē_, vji_,
+ {{0x44391387,0x7ae41388,0x5bb206a7,0x64a380c3}}, // _als_, vtit, जर्व, _gđic,
+ {{0x44321389,0x4426877f,0x27ed8352,0x8c0100c8}}, // tky_, _yoo_, _oben_, _একজন_,
+ {{0xd5dc023c,0x6a7002af,0x5baa07a1,0x7bdb938a}}, // _बावज, häft, ркам_, nguu,
+ {{0x4420138b,0x4432003e,0x4226138c,0x3b0700b3}}, // [940] rji_, rky_, одав, оето_,
+ {{0x4439138d,0x44320a56,0x4420009a,0x6aa480f7}}, // _els_, sky_, sji_, _éife,
+ {{0x7ae4138e,0x10a6138f,0x4432003e,0x644b8370}}, // stit, циен, pky_, _omgi,
+ {{0xa2a2000f,0x7876026f,0x23e10935,0x7ae41390}}, // _कंप्, dávk, _फायद, ptit,
+ {{0x69da806a,0xa25b00e1,0xdb0f0144,0xaac7001b}}, // ygte, _kvôl, _tecá, _लिएक,
+ {{0x2d4e011b,0x44268962,0xa3e79391,0x27ed9392}}, // _eнee_, _soo_, म्स_, _eben_,
+ {{0xd4979006,0x44269393,0x672f009a,0xd5b98a4c}}, // оры_, _poo_, zycj, ссі_,
+ {{0x3cfe8267,0x7a2d9394,0x442682f7,0x68e3023e}}, // _vstv_, _mútu, _qoo_, ànde,
+ {{0x63a41395,0x44269396,0x69da1238,0x6a748580}}, // icin, _voo_, _ötes, làfi,
+ {{0x81d280c8,0x27e000fc,0x2004865f,0x26c08196}}, // হ্ন_, _acin_, _hami_, šios_,
+ {{0x20049397,0x63a41398,0x4a7b0158,0xc27b00be}}, // _kami_, kcin, שריב, שריי,
+ {{0x6f0d1399,0x200480a4,0x63a400d2,0x6e28826c}}, // _prac, _jami_, jcin, _hodb,
+ {{0x61ee139a,0x7bc0939b,0x78ad939c,0xa3e3864a}}, // _obbl, mamu, _čave, _पॉट_,
+ {{0x63a400eb,0x2004939d,0xdef801e2,0xddda8061}}, // ecin, _lami_, цыю_, _kitű,
+ {{0xdd940196,0x63a4026c,0x6594139e,0x6f0d139f}}, // дары, fcin, дару, _wrac,
+ {{0x320d017b,0x6f0d13a0,0x200493a1,0x7bc093a2}}, // _şey_, _trac, _nami_, namu,
+ {{0x6f0d13a3,0x44c581a9,0x97ea81cc,0x2aa08242}}, // [950] _urac, zē_, ışlı, _jòb_,
+ {{0x7bc093a4,0x20048091,0x290e81a1,0xdb198176}}, // hamu, _aami_, _krfa_, _dewè,
+ {{0x31c40987,0x7bc093a5,0x26d913a6,0x200493a7}}, // ьств, kamu, luso_, _bami_,
+ {{0xdb1f0168,0xae190035,0x7bc093a8,0x200493a9}}, // naqë, दावन_, jamu, _cami_,
+ {{0xdefb8364,0x7bc093aa,0x81e700c8,0x63bd0029}}, // рые_, damu, _মোঃ_, _iesn,
+ {{0x46d30076,0xafdb00e8,0x386681a1,0xdcef8b80}}, // _तिनह, rnøy, čora_, šečl,
+ {{0x660513ab,0x20048970,0x19580190,0x6a7013ac}}, // _mahk, _fami_, _дары_, räft,
+ {{0x660513ad,0x629d13ae,0x443993af,0x69ca93b0}}, // _lahk, _kuso, ós_, _adfe,
+ {{0xf7498013,0x768b0063,0x6e28813c,0x629d13b1}}, // _الذي_, _używ, _fodb, _juso,
+ {{0x2bab8eed,0x629d13b2,0x36378077,0x63bd13b3}}, // _ट्रा, _muso, _بررس, _lesn,
+ {{0x216993b4,0x629d13b5,0x60c50168,0x7bc093b6}}, // сини_, _luso, nshm, bamu,
+ {{0xa3d71278,0x2489026c,0x26d90568,0x7bc093b7}}, // _साग_, jmam_, fuso_, camu,
+ {{0x660513b8,0x20040038,0xdb0b8019,0x7876016b}}, // _bahk, ľmi_, _legú, návi,
+ {{0x443f84b9,0x8b058063,0x629513b9,0x3d0813ba}}, // lhu_, _częś, _nizo, हिले_,
+ {{0x69c193bb,0xe3bf0e1b,0x60c500f1,0xc6928039}}, // male, _coño_, jshm, תאם_,
+ {{0x69c1805d,0x629d13bc,0xaac7009a,0x4ac70d86}}, // lale, _buso, _लिखक, _लिखव,
+ {{0x63a413bd,0xe36313be,0x6b6313bf,0x62950019}}, // [960] rcin, екти, екта, _bizo,
+ {{0x69c193c0,0x629504b9,0x7bc080b4,0x63a413c1}}, // nale, _cizo, zamu, scin,
+ {{0x6d438024,0x443f829b,0x200486c0,0x7bcb80d2}}, // jzna, khu_, _pami_, _odgu,
+ {{0x38a18013,0xfc3200f7,0xdb0400f3,0x7bc08079}}, // _mór_, _أحد_, ncië, xamu,
+ {{0xfbd30051,0x69c193c2,0x7bc08006,0x629d02a0}}, // _אתר_, kale, vamu, _guso,
+ {{0xe8df0028,0x69c193c3,0x20048234,0x629513c4}}, // _giữa_, jale, _wami_, _gizo,
+ {{0x69c193c5,0x7bc093c6,0x2ee693c7,0xb5098072}}, // dale, tamu, stof_, विषय_,
+ {{0x7c2413c8,0x629d03ac,0xb0d1016f,0xf8c5824c}}, // njir, _yuso, _हितग, _विजय,
+ {{0x7bc093c9,0x69c193ca,0xdb060061,0x2a6c8163}}, // ramu, fale, _nekü, todb_,
+ {{0x69c193cb,0x7bc093cc,0x7f3b8039,0x62838219}}, // gale, samu, _העמו, _chno,
+ {{0x7bc08ce9,0x443f81f6,0x649581a1,0xf1dd8ad5}}, // pamu, bhu_, _išin, _मारन,
+ {{0xc48693cd,0x3157098a,0x660513ce,0x7d7b80be}}, // _элек, ליין_, _sahk, _פראג,
+ {{0x63bd13cf,0xe3bf01df,0xee870198,0x47340c6e}}, // _resn, _soño_, зыво, еняс,
+ {{0x69c193d0,0x38a18125,0xe3bf002a,0xd2508fd3}}, // cale, _fór_, _poño_, _جنت_,
+ {{0x776401df,0xa15793d1,0x26d913d2,0x7876026f}}, // _exix, _нашу_, suso_, závi,
+ {{0x26d913d3,0x31c6801b,0x629d13d4,0xab5d809a}}, // puso_, bízí_, _puso, saże,
+ {{0xdb0b8c15,0xdb0400e7,0x248913d5,0x60da80dd}}, // [970] _segú, nciè, rmam_, lutm,
+ {{0x60c500f1,0x4ad493ba,0x61fa93d6,0xddc381ac}}, // tshm, _दिनव, netl, konš,
+ {{0x6ca413d7,0x63bd024a,0x787d0036,0x7c380333}}, // _пряж, _tesn, névo, ñará,
+ {{0x69c193d8,0x60c500f1,0x629d0365,0xf770803d}}, // zale, rshm, _tuso, گام_,
+ {{0x261993d9,0x61fa93da,0xe80e10c8,0x69c193db}}, // पाली_, ketl, ियता_, yale,
+ {{0xf7708bbe,0x61fa82fd,0x442b13dc,0x7876016b}}, // دام_, jetl, _hoc_, rávi,
+ {{0x443f93dd,0xc2120039,0x26c681b4,0x60c513de}}, // thu_, _בהם_, asoo_, qshm,
+ {{0xc9520051,0x442b13df,0x7bdf13e0,0xdb1d0168}}, // _סמן_, _joc_, ngqu, _nesë,
+ {{0xe82101fe,0x69c193e1,0x442b13e2,0xa3e505fc}}, // याना_, tale, _moc_, _नान_,
+ {{0x442b0012,0x443f93e3,0x38a193e4,0xa3df93e5}}, // _loc_, shu_, _pór_, _ताव_,
+ {{0x443f93e6,0xa2cf8c2d,0xda1f8072,0x3de30264}}, // phu_, _दिव्, बाबत_, য়াল,
+ {{0x69c193e7,0xcdc90051,0x442b1122,0x64408920}}, // sale, _כך_, _noc_, chmi,
+ {{0xa3d70e5b,0xdb060247,0xdb0401ed,0x09d500ab}}, // _साज_, _lekò, rcië, স্যা,
+ {{0x69c18234,0xf1ba00ff,0x7ae993e8,0x291113e9}}, // qale, ươi_, mtet, _orza_,
+ {{0x6a7d809f,0x443d815d,0x1d070c24,0x442b13ea}}, // lèfo, _klw_, мери_, _boc_,
+ {{0x998381e2,0x26c40019,0x6e21008b,0xdb1d13eb}}, // ųjų_, ámos_, ölbr, _kesè,
+ {{0x7ae993ec,0x7c2b8122,0x290313ed,0xfaff00eb}}, // [980] ntet, _mogr, _asja_, šīnu_,
+ {{0x7c2b840e,0x291113ee,0x7ae993ef,0x6f040234}}, // _logr, _brza_, itet, _isic,
+ {{0x361b0158,0x442b13f0,0xeb9a13f1,0x26c90052}}, // _וויד, _foc_, _мин_, _zvao_,
+ {{0xdfcf8013,0x3ea013f2,0x61fa93f3,0x9f5906c0}}, // ليم_, _kuit_, zetl, fesè_,
+ {{0x61fa93f4,0xbefa826a,0x7ae98019,0x41dd83dd}}, // yetl, تراض_, jtet, _मांस,
+ {{0x768b0d38,0x28db03eb,0x2d9a80e8,0x6f04004f}}, // _użyt, मूहि, øpe_, _msic,
+ {{0xa3e513f5,0x61fa93f6,0x5e570158,0xe821000d}}, // _नाम_, vetl, ויסע_, यामा_,
+ {{0x7bc41149,0x09c6816f,0x442b023e,0xdb1d010c}}, // naiu, लण्य, _xoc_, _besè,
+ {{0xae1e83dd,0xa3df8999,0x68e100f2,0x3ea002be}}, // पादन_, _तार_, _äldr, _nuit_,
+ {{0x64408168,0xa68393f7,0x2fc00580,0xdb1d13f8}}, // shmi, _алуд, _aeig_, _desè,
+ {{0x61fa93f9,0xae220d86,0x6f0413fa,0x443d82df}}, // retl, मानन_, _asic, _flw_,
+ {{0x68e100f2,0x61fa93fb,0xf9930039,0x3ea013fc}}, // _ålde, setl, כרת_, _buit_,
+ {{0x5b36845b,0x69ce01ac,0x320790e4,0x27e480b9}}, // _تعار, _odbe, _fany_, _mcmn_,
+ {{0x3ea013fd,0x6b8293fe,0x32078176,0x683a8580}}, // _duit_, _izog, _gany_, _oïdo,
+ {{0xd3088104,0x2bd00006,0x442b009f,0x24868282}}, // _hệ_, तृभा, _poc_, _khom_,
+ {{0x7a3f80e7,0x3ea013ff,0x69ce1400,0xdb0606c0}}, // _vête, _fuit_, _adbe, _rekò,
+ {{0x52151401,0x661a8cfa,0x7d1c81fa,0x62989402}}, // [990] ндат, _ontk, _ársf, _kivo,
+ {{0x7a3f8036,0x20090cda,0x24868609,0xdb060176}}, // _tête, _haai_, _lhom_, _pekò,
+ {{0x7ae99403,0xd308801c,0xa3df8f97,0xdb1d0061}}, // ztet, _lệ_, _ताल_, _mesé,
+ {{0xa2cf9404,0x7ae99405,0xec350158,0x6448009f}}, // _दिल्, ytet, _נאָר_, èdit,
+ {{0x66089406,0x2ee900b9,0x6b828a03,0x20091407}}, // _badk, _jqaf_, _nzog, _maai_,
+ {{0x62989408,0xa3cd064a,0x7c2b9409,0x2009067f}}, // _nivo, रणव_, _sogr, _laai_,
+ {{0x660882c1,0x6d4704b7,0xdfd58009,0x69c5140a}}, // _dadk, nzja, _повы, mahe,
+ {{0x9b05940b,0xcd980bea,0x2486940c,0x58878e11}}, // езид, ודות_, _chom_, _цыга,
+ {{0xed5881ac,0xfe0f140d,0x6298940e,0x443d8187}}, // _veľa_, ायास_, _bivo, _vlw_,
+ {{0x7ae9940f,0x66089410,0x69c51411,0x3ea01412}}, // rtet, _gadk, nahe, _ruit_,
+ {{0x7ae99413,0x09d500c8,0x61e10125,0x29119414}}, // stet, স্থা, _öllu, íza_,
+ {{0x7a320125,0x7878826f,0x2360816b,0x764380dd}}, // _hætt, tívi, _žije_, lhny,
+ {{0x69c513fa,0x200901b0,0x76551415,0x64859416}}, // kahe, _daai_, fizy, róid,
+ {{0xceb29417,0x09d50a49,0xfc3f062f,0x63a99418}}, // רים_, স্তা, _alí_, lcen,
+ {{0x75f38059,0xa8060457,0x7a32007b,0x69c51419}}, // mızı, _alış, _mætt, dahe,
+ {{0x3ea00998,0x98a701d0,0x2af382f1,0x52aa0081}}, // _tuit_, šně_, _अहाँ_, звам_,
+ {{0x9696141a,0x1e96018b,0xfd658028,0xfc3f00f7}}, // [9a0] _прош, _прор, _chuẩ, _dlí_,
+ {{0x75f38457,0xa3dc141b,0xd94611f3,0x69c5141c}}, // nızı, _ठाक_, нези, gahe,
+ {{0xa3df941d,0x78ab802e,0x261d00c2,0xba3b023e}}, // तला_, _învă, मारी_, _suïc,
+ {{0xe29a141e,0x41e2016f,0x63a9941f,0xa0679420}}, // _нам_, _पावस, jcen, наха_,
+ {{0xe7379421,0x64561422,0x69c51423,0x2ca1026c}}, // нец_, miyi, bahe, _zuhd_,
+ {{0x64561424,0x69c5008e,0x20e0016f,0x63a99425}}, // liyi, cahe, खंदळ_, ecen,
+ {{0x62989426,0x24868069,0xdb1d1427,0x649581f4}}, // _rivo, _phom_, _resé, _išij,
+ {{0xdb0f0118,0x7643836e,0x64561428,0x04638a41}}, // _recú, ahny, niyi, лтым,
+ {{0xeaaf803d,0x81e700ab,0xa01b007b,0x6d4701b9}}, // یعی_, _মোট_, llög, zzja,
+ {{0xd3088028,0xc3330039,0x63a99429,0x20091384}}, // _vệ_, טוח_, acen, _saai_,
+ {{0xa3e881b6,0x7655142a,0xab5d809a,0x63a9942b}}, // _बाप_, vizy, raża, bcen,
+ {{0x236690af,0xfaa6942c,0x80d3142d,0x9cd782f6}}, // _txoj_, _задо, _डिसे, _טובה_,
+ {{0x644402a3,0x64560201,0xe667142e,0x61fe004a}}, // dhii, diyi, етво, kepl,
+ {{0x61fe142f,0x320a01b0,0x614680e7,0x2009067f}}, // jepl, _naby_, _célé, _waai_,
+ {{0xe821101b,0x5fd0816f,0x69c50006,0x61fe1430}}, // यादा_, हणाल, vahe, depl,
+ {{0x60de09ca,0x9f520e1b,0x765506c0,0x64440162}}, // dupm, _cayó_, sizy, ghii,
+ {{0xc2991431,0x69c50a74,0x3a7507b6,0x2fc68bb1}}, // [9b0] дках_, tahe, влар, daog_,
+ {{0xddc71432,0x6467026f,0xbbeb003d,0x61fe1433}}, // bojš, _špič, _کردم_, gepl,
+ {{0x7a321434,0xe61f801c,0x659504bd,0x64561435}}, // _rætt, _đô_, _јану, biyi,
+ {{0x7a32065d,0x44cc800d,0x69c51436,0xd75984a3}}, // _sætt, dě_, sahe, ولات_,
+ {{0xd90f0117,0x320a1437,0x764380dd,0x61fe1438}}, // _گیا_, _faby_, thny, bepl,
+ {{0xee389439,0x7aed0019,0x20098110,0x657a943a}}, // нні_, mtat, žai_, _byth,
+ {{0x25e909f2,0xd90f0117,0x44290110,0x78ad8115}}, // _जानी_, _دیا_, oja_, _čavo,
+ {{0xeb99943b,0x764382f7,0x7aed00e5,0x6d58143c}}, // мий_, shny, otat, _žvak,
+ {{0xc864943d,0xe7db143e,0x248d943f,0x7a320366}}, // _стри, _भाजप, rmem_, _tætt,
+ {{0x44291440,0x63a99441,0x7aed1442,0x6aa281e0}}, // hja_, scen, itat, _duof,
+ {{0x44291443,0xa06a1444,0x7aed0168,0x186a1445}}, // kja_, дана_, htat, дани_,
+ {{0x44291446,0x2d8587d9,0x7c2f09c4,0x7bc29447}}, // jja_, _izle_, _nocr, _geou,
+ {{0xe28e9448,0x44291449,0x7c29944a,0xf092893f}}, // _ва_, dja_, mjer, ינט_,
+ {{0xc0a883f8,0xda110076,0x7c2984fe,0x7c3b944b}}, // بایل_, _डोलत_, ljer, lkur,
+ {{0x442f8bda,0x1fb6144c,0x7aed144d,0x4429144e}}, // _kog_, _испр, etat, fja_,
+ {{0x4429144f,0x442f9450,0x69c39451,0xe8df00ff}}, // gja_, _jog_, _hene, _nhựa_,
+ {{0x614683d3,0x442f822c,0x7aed02b8,0xe29a02df}}, // [9c0] _télé, _mog_, gtat, даа_,
+ {{0x69c39452,0x644402a3,0x44291453,0x4a431454}}, // _jene, shii, aja_, анув,
+ {{0x44291455,0x7c3b9456,0x442f837a,0x2dd900be}}, // bja_, kkur, _oog_, _אַרב,
+ {{0x44290d38,0x44cc800d,0x442f9457,0xdefa8048}}, // cja_, vě_, _nog_, дык_,
+ {{0x7aed1458,0xb8659459,0xab5d809a,0x3aeb804a}}, // ctat, دالو, ważn, jøp_,
+ {{0x09cc00c8,0x649580d2,0x44cc801b,0x98a70087}}, // র্জা, _ušij, tě_, ână_,
+ {{0x77698118,0xe976003d,0xa2bf8035,0x4096945a}}, // _exex, _شهرد, लीस्, _арат,
+ {{0x442f81c5,0xdb0b8073,0x629c145b,0x7c29945c}}, // _cog_, _negó, _hiro, gjer,
+ {{0x629c145d,0xbea68098,0x657a831d,0x3f848289}}, // _kiro, _разк, _wyth, _uzmu_,
+ {{0x69c3945e,0x629c145f,0x44291460,0x656902d0}}, // _cene, _jiro, zja_, şehi,
+ {{0x69c39461,0x7aed1462,0x44291463,0x442f9464}}, // _dene, ztat, yja_, _fog_,
+ {{0x320a8775,0x76588010,0xc1730039,0xe53780be}}, // žby_, livy, יחת_, _שטאט_,
+ {{0x44291465,0x69c39466,0xada2807b,0x2fc480b9}}, // vja_, _fene, _þúsu, _memg_,
+ {{0x442f81e9,0x7c2f1467,0x629c1468,0x6f161469}}, // _zog_, _socr, _niro, _dryc,
+ {{0x442f946a,0x69c8946b,0xd49b146c,0x16df8bb8}}, // _yog_, made, дра_, _नम्ब,
+ {{0x030e146d,0x7aed146e,0x3d08035a,0x629c146f}}, // सिंह_, ttat, हिजे_, _airo,
+ {{0x629c1470,0x7aed1471,0x5b150791,0x34940254}}, // [9d0] _biro, utat, ымат, ратр,
+ {{0x44291472,0x7aed1473,0x69c8888b,0xa3e5035a}}, // sja_, rtat, nade, _नाव_,
+ {{0x7aed1474,0x44291475,0xdb0b809f,0x629c1476}}, // stat, pja_, _segü, _diro,
+ {{0x4429020f,0x629c0029,0x7aed1477,0x69c89478}}, // qja_, _eiro, ptat, hade,
+ {{0x442f9479,0x7c29830b,0x4421801c,0x63ad147a}}, // _rog_, vjer, _đh_, mcan,
+ {{0x629c147b,0x64428364,0x6d580110,0x3ea48bbd}}, // _giro, _aloi, _žvai, _dumt_,
+ {{0x69c89313,0x7c29911b,0x442f947c,0x6442947d}}, // dade, tjer, _pog_, _bloi,
+ {{0x63ad147e,0x69c3947f,0x442f8282,0x13099480}}, // ncan, _sene, _qog_, ений_,
+ {{0x69c39481,0x7c299482,0x628a8013,0x442f9483}}, // _pene, rjer, _bhfo, _vog_,
+ {{0x69c89484,0x42259485,0x64429486,0x6f099487}}, // gade, удов, _eloi, _isec,
+ {{0xa2cf873c,0x569482df,0xe4e4804a,0x6a7d8980}}, // _दिक्, _тафт, _вічн, nèfi,
+ {{0x69c39488,0x64429489,0xb8cb9370,0x5fab81d0}}, // _wene, _gloi, _कं_, _छलफल,
+ {{0x63bb8397,0xa3e880cf,0x69c3948a,0x69c8948b}}, // mbun, _बात_, _tene, bade,
+ {{0xa2d88305,0xf1e2000d,0x63bb948c,0xfce6148d}}, // _मित्, _पाइन, lbun, лого,
+ {{0x629c148e,0xa3e503b6,0xdb1d01d0,0x290780fe}}, // _riro, _नाश_, _desí, _usna_,
+ {{0x7bc9948f,0x6f161490,0x6f0981dd,0x6e3c1491}}, // naeu, _tryc, _osec, skrb,
+ {{0x63bb82af,0x629c1492,0xa3e50327,0xc05301c6}}, // [9e0] ibun, _piro, _नार_, _לזה_,
+ {{0x20020c14,0x629c0609,0x63ad1493,0x09d580ab}}, // leki_, _qiro, acan, ত্রা,
+ {{0x25e91370,0x291e0b67,0xa494003d,0x645a0036}}, // _जाती_, _štab_, _نیست, étis,
+ {{0x63ad0098,0x69c89494,0x61e5032f,0x629c1495}}, // ccan, zade, aghl, _wiro,
+ {{0x68e19496,0x69c89497,0x2ca58362,0xa2cf8c2d}}, // huld, yade, _auld_, _दिग्,
+ {{0x2480020f,0xd252015b,0x69c89498,0x64428635}}, // llim_, _طنز_, xade, _sloi,
+ {{0xa3e89499,0x69c8949a,0x7bc6059c,0x6f09949b}}, // _बाद_, vade, _keku, _esec,
+ {{0x2002061b,0x63bb949c,0x68e1949d,0x7658949e}}, // jeki_, gbun, duld, sivy,
+ {{0x38558698,0x6edb925f,0x38a880e7,0x20020214}}, // _търс, _नियु, _sûr_, deki_,
+ {{0x7bc6149f,0x2ca5813c,0xdd8f94a0,0x27e694a1}}, // _leku, _fuld_, _کوم_, lgon_,
+ {{0x63ad0086,0xe821053e,0x2ca594a2,0x6e9380f7}}, // ycan, याला_, _guld_, _اللا,
+ {{0x27e69341,0x69c894a3,0x7bc614a4,0x64858742}}, // ngon_, sade, _neku, lóin,
+ {{0xd90e8077,0x63ad02a5,0xb8cb86ae,0xa85781c6}}, // زیک_, vcan, _कू_, ריכה_,
+ {{0xdd20800d,0x539b812a,0x291801e4,0x25e8940d}}, // _může, _ניגו, _orra_, च्ची_,
+ {{0x68e183a7,0xdb1d002a,0x7bc614a5,0x63ad14a6}}, // culd, _vesí, _beku, tcan,
+ {{0x20020214,0x63ad0144,0xf1e2001b,0x93b5105c}}, // ceki_, ucan, _पाउन, рбац,
+ {{0x29180019,0x28da800d,0x63a2804f,0x32550f04}}, // [9f0] _arra_, _भिडि, _mgon, авар,
+ {{0x200d80ee,0x61e501c0,0x63bb8162,0x6fc794a7}}, // _saei_, ughl, zbun, ारभू,
+ {{0x78ad817f,0xdfd181a8,0x648594a8,0x63ad14a9}}, // _čavk, قيا_, dóin, pcan,
+ {{0x63a294aa,0x7bc614ab,0xf8c9026b,0x64b181a9}}, // _ngon, _geku, _aréè_, _jāie,
+ {{0x260a016f,0x291814ac,0x61f882d4,0x65c582df}}, // ायची_, _erra_, _obvl, абла,
+ {{0x63a294ad,0xe61890ac,0x200214ae,0xed5994af}}, // _agon, лді_, zeki_, кол_,
+ {{0xce380039,0x7bc60085,0x2905047f,0x51840084}}, // ראות_, _yeku, _èla_, _гута,
+ {{0x201f8012,0x68e194b0,0x7afd0122,0x3dc594b1}}, // _unui_, vuld, _kpst, _welw_,
+ {{0x63bb89af,0x28c282f1,0xa3b483b7,0xc24594b2}}, // rbun, वीसि, झड़_, иник,
+ {{0x69c714b3,0x386914b4,0xdb0414b5,0x63a28102}}, // _jeje, _ajar_, nciá, _egon,
+ {{0x69c714b6,0xa96994b7,0x216994b8,0x7c2d14b9}}, // _meje, тина_, тини_, kjar,
+ {{0x7afd14ba,0x69c714bb,0xe73a14bc,0x20560cc1}}, // _opst, _leje, вем_, ртор,
+ {{0x387e86e3,0x81dd00c8,0x7c2d14bd,0x200214be}}, // _être_, থ্য_, djar, reki_,
+ {{0x7bc614bf,0x443f94c0,0x644994c1,0x69c7001b}}, // _seku, lku_, lhei, _neje,
+ {{0x7afd0029,0x776d0118,0x442d8110,0x7c2d007b}}, // _apst, _exax, oje_, fjar,
+ {{0x644994c2,0x660394c3,0x44f1801c,0xef1a066a}}, // nhei, henk, _mơ_, _ами_,
+ {{0x69c70065,0x645a02be,0x207b00be,0x68fc012b}}, // [a00] _beje, étiq, _באקא, _sprd,
+ {{0xba2314c4,0x601b0364,0x442d94c5,0x7bc614c6}}, // одук, _tämä, hje_, _weku,
+ {{0x443f94c7,0x442d821e,0x69c714c8,0x6449879f}}, // kku_, kje_, _deje, khei,
+ {{0x1ae6025d,0x443f803b,0x644980f3,0x442d94c9}}, // _комм, jku_, jhei, jje_,
+ {{0x27e694ca,0x442d94cb,0x644994cc,0x644614cd}}, // rgon_, dje_, dhei, _ilki,
+ {{0x443f8029,0x69c71024,0x442214ce,0x660394cf}}, // eku_, _geje, _hnk_, genk,
+ {{0x44f18104,0x7ae414d0,0xb4e40540,0x644980f7}}, // _cơ_, luit, _नमो_, fhei,
+ {{0x644994d1,0x442d94d2,0x628194d3,0x443f80ee}}, // ghei, gje_, ello, gku_,
+ {{0x660394d4,0xd5c614d5,0x326694d6,0x765c14d7}}, // benk, वराज, итов, kiry,
+ {{0x442d94d8,0x443f94d9,0x7c2d14da,0x442214db}}, // aje_, aku_, zjar, _lnk_,
+ {{0x850684c0,0x443f94dc,0x442d8353,0x6449851e}}, // _خوان, bku_, bje_, bhei,
+ {{0x644994dd,0x442d8d38,0x386694de,0x443f87df}}, // chei, cje_, nnor_, cku_,
+ {{0x7c22811b,0xfe2214df,0x7c2d14e0,0x95cb0f9c}}, // _inor, मांस_, vjar, кува_,
+ {{0x7ae414e1,0xdbf880c8,0x644614e2,0x30a68ff7}}, // duit, _অফিস_, _alki, _крив,
+ {{0x69c714e3,0x0ed30072,0x44f18129,0xfda70a27}}, // _reje, _तिकड, _xơ_, _खलीफ,
+ {{0xe51785e8,0x69c714e4,0x386914e5,0x660394e6}}, // थिति_, _seje, _ujar_, zenk,
+ {{0x7ae414e7,0x7c2d14e8,0xdb1982af,0x499282e3}}, // [a10] guit, rjar, _gewä, _دیگر,
+ {{0x443f827f,0xdb1d0207,0xe4e700e8,0x442d94e9}}, // zku_, _cesá, _кімн, zje_,
+ {{0x442d94ea,0x69c714eb,0x644981b0,0x3ea900ee}}, // yje_, _veje, yhei, _huat_,
+ {{0x3ea914ec,0x7ae414ed,0x386681e8,0x660394ee}}, // _kuat_, buit, gnor_, wenk,
+ {{0x660394ef,0x443f94f0,0x44f18028,0x442d94f1}}, // tenk, vku_, _sơ_, vje_,
+ {{0xc3320158,0x3ea90812,0x9f568061,0x2fc914f2}}, // _צום_, _muat_, ágát_, _leag_,
+ {{0x442d94f3,0x3ea914f4,0x644994f5,0x58d414f6}}, // tje_, _luat_, thei, _морт,
+ {{0xa2d881ab,0x442d81e2,0x660394f7,0x443f94f8}}, // _मिश्, uje_, senk, uku_,
+ {{0x644994f9,0xa3e8901c,0xa3e50054,0x66038a9e}}, // rhei, _बार_, _नाई_, penk,
+ {{0x442d94fa,0x644994fb,0xa2d894fc,0xaca38135}}, // sje_, shei, _मिर्, _anụm,
+ {{0x2fc90ad0,0x442d94fd,0x6f0d14fe,0x84678098}}, // _beag_, pje_, _asac, _къде,
+ {{0x3ea914ff,0x64409500,0x442d8168,0x66fa8ec3}}, // _buat_, ckmi, qje_, bākā,
+ {{0x44d3009a,0x683f00f1,0xac7480f7,0x02a78cdf}}, // eć_, _mëdh, _بالش, _трем,
+ {{0xed5a1501,0x6d4e0192,0xa3e501a2,0x7c229502}}, // гог_, tzba, _नाउ_, _znor,
+ {{0x50b590f8,0x25e9123a,0xf1ca026f,0x38669503}}, // _услу, _जारी_, ntáž_, ynor_,
+ {{0x7ae41504,0xb603827f,0x47c61505,0x68e50122}}, // tuit, _hráč, _убив, duhd,
+ {{0xab271506,0x23271507,0xe1fa1508,0x44d30035}}, // [a20] бота_, боти_, лга_, ać_,
+ {{0x78a908ae,0x3ea101f4,0x15461509,0x78a0950a}}, // _čevr, _giht_, _геом, _simv,
+ {{0x0ce2950b,0xa3e88128,0x7ae4150c,0x20120300}}, // _মন্ত, _बाल_, suit, _kayi_,
+ {{0xa2d8800c,0xdd2c8110,0x32058061,0xd6cf01f9}}, // _मिल्, _nėšt, hely_, _تقی_,
+ {{0x3866950d,0xa2a20540,0x78bb10d3,0x6ecb150e}}, // rnor_, _कंट्, _čuve, तीपु,
+ {{0x6f1b82af,0x20120041,0xdb1d0366,0x6459950f}}, // _druc, _layi_, _besæ, _amwi,
+ {{0xd132045b,0x6f1b819d,0x48e31510,0xa3cc00d4}}, // _عمر_, _eruc, _норв, _रजा_,
+ {{0xf0639511,0x6f1b9512,0xe8e0001c,0xa2b48993}}, // _експ, _fruc, _chụp_, обич,
+ {{0x25e91513,0x6440807b,0x66118198,0x2fc90037}}, // _जाली_, rkmi, ökkä, _reag_,
+ {{0xd90e84c0,0x64408074,0x683f03ed,0x66fa81a9}}, // فیت_, skmi, _zëdh, rākā,
+ {{0x20121514,0x5d868013,0x5316815b,0x28f8804a}}, // _bayi_, _الال, _گذار, шень_,
+ {{0x96eb0084,0x9f991515,0x6f0d1516,0x673d062c}}, // льга_, ивну_, _psac, tysj,
+ {{0x21270129,0x3c388036,0x32059517,0x6c7b83de}}, // ̣nh_, _févr_, bely_, _גראד,
+ {{0x6d1401fe,0x1bf89518,0xe6671519,0x25a51277}}, // डिंग_, ्जवल_, стмо, øll_,
+ {{0x3211009a,0x4035151a,0x64a3817f,0x20120db1}}, // _razy_, _мекс, _uđit, _fayi_,
+ {{0x0ca8151b,0x20120326,0x6b828c2e,0x6495936f}}, // стри_, _gayi_, _kyog, _ušit,
+ {{0x5a35151c,0xe7cc8035,0x6f0d113b,0x69ca951d}}, // [a30] онат, ारनप, _usac, _kefe,
+ {{0xa3e5146d,0x6b82951e,0x27ed951f,0x69ca9520}}, // _नाक_, _myog, _scen_, _jefe,
+ {{0x201200a4,0x69ca826b,0x66070088,0xa3e89521}}, // _yayi_, _mefe, mejk, बला_,
+ {{0xac190765,0x69ca9522,0x753a0192,0x06ed064a}}, // _тому_, _lefe, ätze, _जमाव_,
+ {{0x644d1523,0x69ca80c9,0xd6db8aac,0x171c00be}}, // mhai, _oefe, уте_, ָווע,
+ {{0xf7708c48,0x644d1524,0x5bb38beb,0x30a71525}}, // ران_, lhai, ीर्व, _грав,
+ {{0xb0660009,0x32059526,0x91fc80eb,0x6b828c56}}, // ntää, vely_, rnāl, _ayog,
+ {{0x69d89527,0x628504b7,0x6b829528,0x69ca85ee}}, // _adve, llho, _byog, _aefe,
+ {{0x52a691e9,0x32058e25,0x69ca9529,0x290e8118}}, // овым_, tely_, _befe, _asfa_,
+ {{0xe8f9952a,0x2012152b,0x6b8282c4,0x5f1d152c}}, // ало_, _sayi_, _dyog, मिन्_,
+ {{0x386d8db7,0x69d80073,0x644d152d,0x69ca952e}}, // _kjer_, óvei, khai, _defe,
+ {{0xdb1d0364,0x76438aa2,0x645980b4,0xa3e50107}}, // _kesä, lkny, _umwi, _नाग_,
+ {{0x644d152f,0x7bcb80eb,0x3205816b,0x7643808e}}, // dhai, _iegu, pely_, okny,
+ {{0x76439530,0x69ca9531,0xe7e607e6,0xceb283de}}, // nkny, _gefe, _काका_, ויך_,
+ {{0x69c19532,0x2d8c0370,0x7bcb8812,0x6da30ae7}}, // mble, åde_, _kegu, дира,
+ {{0x644d1523,0xe3ba067c,0x62851533,0x76a0077f}}, // ghai, рба_, elho, _báyì,
+ {{0xe7ff8b04,0x764380dd,0xd0420085,0x764b0084}}, // [a40] ोजना_, kkny, xslə, _įgyv,
+ {{0x7bcb9534,0xa7fb04c3,0x69c19535,0x25fb8bb8}}, // _legu, riñe, nble, ल्मी_,
+ {{0x644d1523,0x60c70282,0x03a60e97,0x6aa381b9}}, // bhai, _cwjm, _мино, _minf,
+ {{0x644d1536,0xef1a9537,0x6edb83eb,0xa7fb002a}}, // chai, име_, _निशु, piñe,
+ {{0x7c261538,0x2f189539,0x63b40035,0x386d89c4}}, // _inkr, соль_, ągną, _djer_,
+ {{0x644402a3,0x386d813c,0x6aa3953a,0x78a407b8}}, // lkii, _ejer_, _ninf, _liiv,
+ {{0x69ca953b,0x3af4039c,0xdd9381e5,0x69c182c4}}, // _refe, läp_, машы, dble,
+ {{0x6444153c,0xdee6953d,0x69ca953e,0x69c1953f}}, // nkii, _моби, _sefe, eble,
+ {{0x7e7c1540,0x16668098,0x9e669541,0x213e8114}}, // korp, _двам, _двад, wyth_,
+ {{0x7af601e2,0x76439542,0x645f05e7,0x7bcb8091}}, // ityt, ckny, ziqi, _eegu,
+ {{0x7c261543,0x6e371544,0x3a3800b9,0x644d1545}}, // _onkr, _roxb, _morp_, yhai,
+ {{0x69ba83eb,0x6aa39546,0x644d01c5,0x7bcb9547}}, // _श्री, _einf, xhai, _gegu,
+ {{0xdefb0196,0x09d500ab,0x44269548,0x64440079}}, // шын_, স্টা, _ino_, dkii,
+ {{0x44269549,0x3f850029,0x7c26154a,0x46dc154b}}, // _hno_, ālu_, _ankr, _बिरह,
+ {{0x644d154c,0x6607154d,0x7bcb954e,0x3495154f}}, // thai, rejk, _yegu, завр,
+ {{0xe019853e,0x68e89550,0x4426808e,0x8cdc12c6}}, // _नोंद_, mudd, _jno_, _फिरो,
+ {{0xa3e89551,0x68e89552,0x644d1553,0xb0660009}}, // [a50] _बाई_, ludd, rhai, stää,
+ {{0x7c260353,0x09e300ab,0x65950139,0x387f9554}}, // _enkr, ন্না, _напу, _skur_,
+ {{0xdb098013,0x3a38037a,0x68e89555,0xceb981d0}}, // iceá, _dorp_, nudd, _stř_,
+ {{0x26c90010,0x44268365,0x670081bc,0x645f1556}}, // _kwao_, _nno_, _ịkag, qiqi,
+ {{0x7bcb9557,0x645d1558,0x68e89559,0x870780d7}}, // _regu, _imsi, hudd, تبال,
+ {{0x4426955a,0x4439120b,0x68e8955b,0x2cac955c}}, // _ano_, _hos_, kudd, _budd_,
+ {{0x4439155d,0x7bcb955e,0x7643955f,0x6aa38d02}}, // _kos_, _pegu, rkny, _rinf,
+ {{0x44391560,0x387f885c,0x7643808e,0x44268706}}, // _jos_, _ukur_, skny, _cno_,
+ {{0x44391561,0x9f4f8e1b,0xab659562,0x27ff8122}}, // _mos_, regó_, явил, _ibun_,
+ {{0x44391563,0x44269564,0x7e55803f,0x68e88c2e}}, // _los_, _eno_, _عناص, fudd,
+ {{0x2cac9565,0x7bcb9566,0x3ead9567,0x69c19568}}, // _gudd_, _tegu, _huet_, rble,
+ {{0x44d7802e,0xe8df001c,0xbbaa1370,0x69c18192}}, // mă_, _chứa_, _कलेक, sble,
+ {{0x7d1e09cf,0x644b9569,0x44d7802e,0x2fcd81c0}}, // _srps, _ilgi, lă_, _meeg_,
+ {{0x2fcd956a,0x4439156b,0x68e8956c,0x629d801b}}, // _leeg_, _aos_, budd, ůsob,
+ {{0x44d78012,0x7ae9956d,0x44268114,0x78a4156e}}, // nă_, luet, _yno_, _tiiv,
+ {{0x4426040e,0x2fcd90af,0x4439156f,0x5baa1570}}, // ño_, _neeg_, _cos_, скам_,
+ {{0x44391571,0x78bb003b,0xe9da11b1,0x64440079}}, // [a60] _dos_, _čuva, йка_, rkii,
+ {{0xa7fb04c3,0x64441572,0x7c399106,0x27ff8041}}, // miña, skii, _lowr, _abun_,
+ {{0xe821035a,0xe8fa12e1,0x7af61573,0xa3e50f12}}, // याचा_, сле_, styt, _नाच_,
+ {{0xa3ea800c,0x1bea809a,0x44391574,0xafdb0257}}, // _टाइप_, _टाइल_, _gos_, lhør,
+ {{0x9cf880ab,0x7bc41575,0x2fcd9576,0xdfcf80a0}}, // _আছেন_, mbiu, _deeg_, ايه_,
+ {{0x443901c5,0x644b8084,0x44268b81,0x412a1577}}, // _zos_, _algi, _sno_, _лого_,
+ {{0xe7ed035a,0x44390282,0x44d78087,0x69dc00e8}}, // _झाला_, _yos_, gă_, _idre,
+ {{0xb6038029,0x44391578,0x68e302ba,0x69ce1579}}, // ēšan, _xos_, ánde, _hebe,
+ {{0x69ce0393,0x7ae9957a,0x7ae41434,0x66e3957b}}, // _kebe, guet, nrit, _пота,
+ {{0x64aa800d,0x69ce06ec,0x78ad957c,0x25fb80d4}}, // _přid, _jebe, _čavr, ल्दी_,
+ {{0x69ce157d,0x44d78012,0x6e28813c,0x7ae402af}}, // _mebe, că_, _indb, hrit,
+ {{0x4426957e,0x69ce157f,0x68e89580,0x5f1383ca}}, // _uno_, _lebe, rudd, _धनम्_,
+ {{0x69dc0025,0x44391581,0x68e89582,0x7ae98661}}, // _odre, _ros_, sudd, cuet,
+ {{0x8cb109a3,0x69ce1583,0xc6a41584,0x6266845a}}, // _आंदो, _nebe, ерси, _عاشق,
+ {{0x92e900c8,0x44391585,0x7ae40074,0x6fcc816f}}, // _মনে_, _pos_, erit, ारां,
+ {{0x69dc1586,0x7ae41587,0x765e1588,0x69ce011b}}, // _adre, frit, _empy, _aebe,
+ {{0x44391589,0x69ce158a,0x44d78012,0xe3b007bd}}, // [a70] _vos_, _bebe, ză_, ارف_,
+ {{0x19ab03a7,0x4439158b,0x6d5909ab,0x200b158c}}, // стап_, _wos_, _ƙwad, leci_,
+ {{0x69ce158d,0x4439158e,0x3947158f,0x7ae400e5}}, // _debe, _tos_, áns_, arit,
+ {{0x44d78087,0x645d01f6,0x4add8d86,0x2fcd822c}}, // vă_, _umsi, _मिलव, _qeeg_,
+ {{0x3ea58074,0x24891217,0x2fc08333,0x66171590}}, // _pilt_, mlam_, ñiga_, _kaxk,
+ {{0x69ce1591,0x44d78012,0x1dbf0f97,0x6e3a8362}}, // _gebe, tă_, ्रित, _cotb,
+ {{0x7bcf059c,0x78a2826f,0x661702a3,0x7c39809a}}, // _kecu, ňova, _maxk, _powr,
+ {{0x44d78012,0x3ea58613,0x200b120e,0xa01b016d}}, // ră_, _wilt_, jeci_, slöj,
+ {{0x44d7802e,0x4add816f,0x1dbf016f,0x7bcf1592}}, // să_, _मिळव, ्रात, _mecu,
+ {{0xe0d99593,0x39459594,0x2d85809a,0x44d78087}}, // ово_, дног, _tyle_, pă_,
+ {{0x24891595,0x69c501e4,0x7ae401ac,0x7ae98cdb}}, // klam_, ibhe, zrit, suet,
+ {{0xb9220870,0x25c601cb,0x7bcf0087,0xa7fb002a}}, // _akpọ_, वर्ण, _necu, tiña,
+ {{0x6d439596,0xba558081,0x99830196,0x00000000}}, // lyna, _откъ, _kojų_, --,
+ {{0xc87902bb,0xa7fb04c3,0x09e300ab,0xdb218216}}, // duğu_, riña, ন্তা, _ñuño,
+ {{0xbea31597,0x7bcf1598,0x9872016b,0xa7fb03a8}}, // тарк, _becu, láč_, siña,
+ {{0x69ce1599,0xd9f0159a,0xd7ff002e,0x7e63826c}}, // _sebe, _चाहत_, şări_, hinp,
+ {{0x78ae159b,0x64bc0024,0x78bc026c,0x7bcf159c}}, // [a80] _subv, _očig, _strv, _decu,
+ {{0x7ae4159d,0x2489159e,0x1cba003f,0xe8df001c}}, // rrit, alam_, _واجب_, _chữa_,
+ {{0xdb1d01df,0xfbc6159f,0x7bcf15a0,0x2d9615a1}}, // _xesú, _обно, _fecu, _юрис,
+ {{0xab2a153d,0x31368158,0x248915a2,0x7d1c808b}}, // _дома_, ונעם_, clam_, _ársi,
+ {{0x69ce15a3,0x09e300ab,0x394601a9,0xe73795a4}}, // _tebe, ন্ধা, ņos_, мец_,
+ {{0x69dc15a5,0x65948162,0xdcba95a6,0x69ce0192}}, // _udre, налу, ощи_, _uebe,
+ {{0x0ee0835a,0x2df980d7,0x6935128a,0xdb060646}}, // _निवड, سبوک_, _přeš, _afkø,
+ {{0x3218008e,0x25de0d14,0xdb1d0511,0x64aa801b}}, // _hary_, कृती_, _resú, _přib,
+ {{0x41e68d13,0xa3c10074,0xc5e600ab,0x25fb95a7}}, // міна, ुरत_, খ্যা_, ल्ही_,
+ {{0xd25095a8,0xd62695a9,0x6d4395aa,0x2fc680e4}}, // اند_, _تعري, byna, lbog_,
+ {{0x6d4395ab,0xc87f0192,0xa01b0198,0x321801c6}}, // cyna, toß_, llöi, _mary_,
+ {{0xa3e8800d,0xc32a80f7,0xb9068af3,0xd5b1827d}}, // _बाट_, _وكان_, _पि_, _tác_,
+ {{0xa2d88105,0xdca395ac,0xd78b00f7,0x7bcf15ad}}, // _मिट्, _раци, تخدم_, _recu,
+ {{0x200b0025,0x386015ae,0x3eb985a4,0x7bcf079a}}, // seci_, _emir_, _éste_, _secu,
+ {{0x38b701cd,0x248915af,0x6d550037,0x320c95b0}}, // _oħra_, tlam_, uzza, medy_,
+ {{0xa7fb04c3,0xd49815b1,0xfc150071,0x28d815b2}}, // miño, дру_, эмбэ, _डिजि,
+ {{0x7bcf00eb,0x6d43809a,0x2fc6806a,0x32180a7a}}, // [a90] _vecu, zyna, dbog_, _bary_,
+ {{0x321815b3,0x248915b4,0xae0082f1,0xa2b00a27}}, // _cary_, slam_, ल्मन_, _अंर्,
+ {{0xa3c38b6f,0x1bd515b5,0x24890214,0x69c5125b}}, // ्रम_, _поня, plam_, rbhe,
+ {{0x3f8904b9,0x61fe1384,0x2fc695b6,0x64bc0362}}, // _kyau_, afpl, gbog_, _dčid,
+ {{0xe4560159,0xdb028028,0xa3e88fb2,0x320c81ac}}, // _נישט_, _ngoà, _बाज_, kedy_,
+ {{0x2451827f,0x7aed03ac,0x46e082f1,0x645a01a9}}, // _máme_, muat, _निरह, ītie,
+ {{0x7aed0be5,0x82b881bc,0xc98415b7,0x7e6395b8}}, // luat, _rịịl_, _аути, rinp,
+ {{0xe7f9800d,0xdb1d0b40,0xa19415b9,0xa3c10697}}, // _एउटा_, _besø, _расч, ुरि_,
+ {{0x442b06cb,0x7e6395ba,0x2ef48098,0x25a9803d}}, // _mnc_, pinp, _изтр, _فضای_,
+ {{0x442b0122,0x291e0084,0x850f001b,0x4ae2052a}}, // _lnc_, _štai_, ाबाट_, _पिसव,
+ {{0x7aed15bb,0xa3c115bc,0xf8660a13,0x3f8909ab}}, // huat, ुरा_, евно, _ayau_,
+ {{0x7aed15bd,0x66e615be,0x9ac48372,0xdee615bf}}, // kuat, нома, _boċċ, номи,
+ {{0xe801835a,0x7aed15c0,0x7c2b95c1,0x64498192}}, // ळ्या_, juat, _ingr, mkei,
+ {{0x443d8051,0x78a995c2,0x95860088,0x442b15c3}}, // _how_, _liev, елге, _anc_,
+ {{0xa7fb062f,0x628195c4,0xda020a0d,0x26d915c5}}, // ciño, molo, र्फत_, asso_,
+ {{0x7aed022e,0x628195c6,0x78a995c7,0x7c3d00b9}}, // fuat, lolo, _niev, _cosr,
+ {{0x3b070698,0xae008e5b,0xfe720065,0x2c01809a}}, // [aa0] нето_, ल्डन_, _مدد_, व्यू_,
+ {{0x9b9300f7,0x443d8039,0x98d38ad5,0xb77b01c6}}, // الكت, _low_, _दबाए, _מאפש,
+ {{0x26c0120e,0x644995c8,0x61fe016d,0x7c2b95c9}}, // _htio_, kkei, rfpl, _ongr,
+ {{0x672415ca,0x443d8051,0xdfcf8013,0x7aed09ca}}, // _krij, _now_, ميم_, buat,
+ {{0x78a995cb,0x91fc80eb,0x7bd615cc,0x7aed15cd}}, // _diev, onāt, mayu, cuat,
+ {{0x25fb901b,0x7c2b95ce,0x628195cf,0x7bd615d0}}, // ल्ली_, _angr, jolo, layu,
+ {{0xa3e40740,0xafe695d1,0x291e01fa,0x7c3d0748}}, // _नया_, _повл, _éta_, _yosr,
+ {{0x64498352,0x98a395d2,0x78a995d3,0x7bd615d4}}, // gkei, _бисе, _giev, nayu,
+ {{0x0edc01ce,0x7afb90c1,0xdb1981ec,0x68e10106}}, // _बिगड, gtut, _gewü, _åldr,
+ {{0x628195d5,0x27e015d6,0x7bd615d7,0xef178110}}, // golo, _adin_, hayu, _яму_,
+ {{0x694680fe,0xa3b3801b,0x7afb8cdb,0x320c95d8}}, // _očeš, जुङ_, atut, redy_,
+ {{0x44fa88cf,0x672415d9,0x661a826c,0xa7fb05e4}}, // më_, _brij, _hatk, riño,
+ {{0x62818042,0xff0415da,0x44fa80f1,0x672415db}}, // bolo, лярн, lë_, _crij,
+ {{0x661a8364,0x672403e9,0x27e015dc,0x442b047f}}, // _jatk, _drij, _edin_, _snc_,
+ {{0x44fa88cf,0x661a95dd,0x25a500f2,0x7c3d0805}}, // në_, _matk, äll_, _posr,
+ {{0x6d588353,0x27e0017f,0x672415de,0x7aed15df}}, // izva, _gdin_, _frij, tuat,
+ {{0xc6a715e0,0x672415e1,0x44fa80f1,0x6aaa95e2}}, // [ab0] _прои, _grij, hë_, _kiff,
+ {{0x78a98029,0xda7815e3,0x44fa80f1,0x6aaa81b9}}, // _siev, нях_, kë_, _jiff,
+ {{0xdd9115e4,0x78a98029,0x7aed0867,0x44fa820f}}, // _خود_, _piev, suat, jë_,
+ {{0x644f15e5,0x648580f7,0x7aed15e6,0x44fa83ed}}, // _ulci, hóir, puat, dë_,
+ {{0x628195e7,0x661a8267,0xdb070009,0x44fa81b0}}, // yolo, _batk, ämäs, eë_,
+ {{0x443d95e8,0x628195e9,0x99d40154,0x9f4f8077}}, // _sow_, xolo, _متفا, nggé_,
+ {{0x628195ea,0x92a60063,0x648580f7,0xdd918065}}, // volo, _dołą, dóir, گوں_,
+ {{0x628195eb,0x7afb8098,0x38668087,0xed6404e8}}, // wolo, ttut, cior_, loží_,
+ {{0x644995ec,0xdd918065,0x661a8168,0x200f95ed}}, // rkei, دوں_, _fatk, megi_,
+ {{0x7afb95ee,0x644995ef,0x69d704c3,0x200f95f0}}, // rtut, skei, naxe, legi_,
+ {{0x7afb95f1,0x6aaa8806,0x672402fd,0xa3c380c2}}, // stut, _diff, _srij, ्रद_,
+ {{0x672415f2,0x20040a56,0xa3d284c5,0xd5648638}}, // _prij, ými_, वरण_, _ступ,
+ {{0x628195f3,0x6aaa95f4,0x6e3e01a1,0xddd8807a}}, // polo, _fiff, _popb, lovš,
+ {{0x672415f5,0x248d95f6,0x63bb8098,0xfce615f7}}, // _vrij, llem_, lcun, кого,
+ {{0x6b8b95f8,0x7ae995f9,0x7bd615fa,0xe81d15fb}}, // _hygg, lret, tayu, _बोका_,
+ {{0xc9528051,0x27e0015d,0x15ee0035,0x7bc98d35}}, // _כמו_, _udin_, _जाकर_, nbeu,
+ {{0xdca315fc,0x64a30110,0xcb6715fd,0x0b4315fe}}, // [ac0] рати, рата, вате_, антн,
+ {{0x7bd604a7,0x7ae9802a,0x6b8b92f1,0xbea615ff}}, // sayu, iret, _mygg, _шапк,
+ {{0x661a8009,0xfe370039,0xa3c3873c,0x249f9600}}, // _ratk, _סרטי_, ्रा_, kmum_,
+ {{0x69469601,0x628700b9,0xddd8824a,0x44fa8168}}, // _učeš, _ekjo, dovš, vë_,
+ {{0x248d9602,0x92a6009a,0x69ca008b,0x38669603}}, // dlem_, _połą, ðfes, rior_,
+ {{0x44fa88cf,0x7ae98620,0x68e88114,0x629d9412}}, // të_, dret, yrdd, ïsol,
+ {{0x81d780ab,0x38668106,0x7ae99604,0x200f9605}}, // িলা_, pior_, eret, begi_,
+ {{0x44fa88cf,0x6aaa9606,0x43948b69,0x291800a4}}, // rë_, _siff, лайс, _isra_,
+ {{0x44fa88cf,0x661a8359,0x6d47009a,0x648580f7}}, // së_, _tatk, zyja, tóir,
+ {{0x91fc8029,0x5ea68b76,0x3eb985a4,0x60c49607}}, // ciāl, _شمال, _ésta_, _čimb,
+ {{0x67229608,0x248d9609,0x648580f7,0xb6a38162}}, // lvoj, blem_, róir, _тифл,
+ {{0xdb04160a,0x7ae9960b,0x4bda819f,0x648581a8}}, // nció, bret, _آباد_, sóir,
+ {{0x7336845b,0xf09f023e,0x7ae9960c,0xdb040333}}, // _جرائ, rmà_, cret, ició,
+ {{0x52d78039,0x201b00dd,0xceb383c8,0x200f960d}}, // _יודע_, _waqi_, ייש_, zegi_,
+ {{0x4fc70652,0x984f009a,0x3eac8aa2,0x201b0187}}, // _испа, jęć_, _midt_, _taqi_,
+ {{0x3eac8022,0x62870370,0x57f5160e,0x3dd88264}}, // _lidt_, _skjo, _спит, _সাফল,
+ {{0xb275160f,0x3a750162,0x2d8c9610,0x667200d7}}, // [ad0] глаш, глар, _lyde_, شگیر,
+ {{0x6722803a,0x68e302ba,0x7aef809f,0x78ad01c0}}, // dvoj, ándo, àcti, _hiav,
+ {{0x200f9611,0xa3d28076,0xf1a71612,0x2d8c8366}}, // tegi_, वरा_, тран, _nyde_,
+ {{0x25ff05fc,0x69d71613,0x69ca007b,0xa01b1614}}, // श्री_, saxe, ðfer, mlös,
+ {{0x7c2f1615,0x200f9616,0x64aa801b,0xd6db9617}}, // _incr, regi_, _přin, фте_,
+ {{0xe29a8104,0xdb04040e,0x6b8b8bfa,0x644d0364}}, // _như_, ació, _rygg, lkai,
+ {{0x91fc8029,0x62851618,0x58d41619,0x30a7961a}}, // riāl, moho, _вост, _арав,
+ {{0xdb04161b,0x69d5161c,0x6285161d,0xc1720039}}, // cció, _heze, loho, _החל_,
+ {{0x69d50065,0x248d961e,0x6f098754,0xac9680f7}}, // _keze, rlem_, _upec, دنيا_,
+ {{0x7ae9961f,0x3f7a8158,0x69d51620,0x63bb9621}}, // rret, _אָבע, _jeze, scun,
+ {{0x10a600c4,0x644d0009,0x69d51622,0x2ba483db}}, // лион, kkai, _meze, खेबा,
+ {{0xcea9093f,0x69d51623,0x7ae98558,0xf99204de}}, // _זי_, _leze, pret, מרי_,
+ {{0x442f896c,0xe28e9624,0x78ad1625,0x643a81c6}}, // _ing_, _га_, _diav, _רעננ,
+ {{0x69d51626,0xa01b01ec,0x7c2f1627,0x644d0196}}, // _neze, flös, _ancr, ekai,
+ {{0xa5070fbf,0x2d8c013c,0xed5a1628,0xdb0b806a}}, // лера_, æde_, хов_, _afgø,
+ {{0x7c3b8886,0x644d1629,0x442f962a,0x78ad162b}}, // njur, gkai, _jng_, _giav,
+ {{0x69d5162c,0x28d78f97,0x33d5021e,0x2ca002f1}}, // [ae0] _beze, _भौति, ріст, rmid_,
+ {{0x64560578,0x442f820d,0x62851486,0x2ca0162d}}, // shyi, _lng_, goho, smid_,
+ {{0x442f962e,0x69d5162f,0x4fea9630,0x00e61631}}, // _ong_, _deze, емен_, ужен,
+ {{0x6493029a,0x442f85ee,0xf3f98493,0x644d0196}}, // nçil, _nng_, biţi_, ckai,
+ {{0xdb040c15,0x44201632,0x62851633,0xbe8a9634}}, // rció, mdi_, boho, нске_,
+ {{0x442f9635,0x69d51636,0x20d18390,0x67229637}}, // _ang_, _geze, kši_, rvoj,
+ {{0xdb04040e,0x442f862f,0x20d18db7,0x78b502a5}}, // pció, _bng_, jši_, _guzv,
+ {{0x33269638,0x661e0110,0x26c69639,0x44de81b9}}, // _prox_, _lapk, ppoo_, rċ_,
+ {{0x76418065,0x4420163a,0x442f963b,0x78ad047f}}, // _foly, idi_, _dng_, _riav,
+ {{0x7641831d,0x78a2963c,0x644d011e,0x4420163d}}, // _goly, lmov, zkai, hdi_,
+ {{0xd48f963e,0x1994963f,0x2d8c009a,0x7413803d}}, // _гр_, раня, żdej_, _گونا,
+ {{0x4d7b8f60,0x44201640,0x78a28333,0xd0e5800c}}, // _ארבע, jdi_, nmov, _किरण_,
+ {{0xa2b005e8,0xead49641,0x44201642,0xe29a8028}}, // _अंग्, _толь, ddi_, _thư_,
+ {{0x44201643,0x98bc001b,0x2bd2940d,0x64429151}}, // edi_, ávě_, सरवा, _hooi,
+ {{0x69da9644,0x442f8355,0x644d0364,0x78a28353}}, // mate, _yng_, tkai, kmov,
+ {{0x69da9645,0x78a280ce,0x44201646,0xa01b1647}}, // late, jmov, gdi_, rlös,
+ {{0x644d0483,0x644282d8,0x90980c48,0xa01b12d2}}, // [af0] rkai, _mooi, _حضور_, slös,
+ {{0x644d1648,0x629c0c3f,0x44201649,0x7d1a964a}}, // skai, _chro, adi_, _osts,
+ {{0x21270104,0x69d5164b,0x442002a3,0x3915164c}}, // ính_, _veze, bdi_, рмер,
+ {{0x644290f4,0x69d5164d,0x69da964e,0x628500b4}}, // _nooi, _weze, hate, soho,
+ {{0x7bda0496,0xfe7f023e,0x2d9c01d6,0xf6e7964f}}, // _בקרו, buït_, _úver_, уцен,
+ {{0x20d71650,0x7aed1651,0xf6259652,0x4e00864a}}, // _مترج, mrat, рдко, ल्लई_,
+ {{0xe3b18307,0x69da9653,0xa3b91094,0x76419654}}, // يرة_, date, _चला_, _voly,
+ {{0x20d1803b,0x6ee780d5,0x1309835f,0x7bcd1655}}, // vši_, _مسئل, вний_, nbau,
+ {{0x7c3b9656,0x92020054,0x69da8428,0x64429657}}, // rjur, र्वज_, fate, _dooi,
+ {{0x2480803b,0x7aed1658,0x212789da,0xf41f016d}}, // čima_, irat, _prnh_, _skäl_,
+ {{0x44201659,0x6493080a,0xdddc025b,0x27e0808b}}, // ydi_, tçil, jorš, ðing_,
+ {{0x442f965a,0x186a0153,0x20d193cf,0x6729965b}}, // _ung_, тами_, rši_, _krej,
+ {{0x69da965c,0x442001b9,0x7aed026c,0x6c8481a8}}, // bate, vdi_, jrat, _النم,
+ {{0x69da8039,0xfed780ab,0x8cb10035,0x78a284e8}}, // cate, _সমাধ, _आंखो, zmov,
+ {{0xada6165d,0xdb1207ca,0x4420011c,0x6da614b8}}, // равл, _ágús, tdi_, рива,
+ {{0x4420165e,0x3b00022b,0x6729965f,0x2b099660}}, // udi_, ttiq_, _orej, _वहाँ_,
+ {{0x44201661,0x5baa06d2,0xfe7f0722,0x62661662}}, // [b00] rdi_, ткам_, tuït_, аваа,
+ {{0x2bb80076,0x09e61663,0x2243026c,0x91e600bf}}, // _अलवा, рожн, _cojk_, роже,
+ {{0x2cf49664,0x290100f7,0x44201665,0x78a29666}}, // _इमेल_, gtha_, pdi_, tmov,
+ {{0x67299667,0x69da9668,0x44201669,0x64aa928a}}, // _brej, zate, qdi_, _přim,
+ {{0x2bd301fe,0x629c0051,0x78a2826f,0x69da966a}}, // तररा, _thro, rmov, yate,
+ {{0x672988cf,0xa11600d7,0x69da966b,0x2901166c}}, // _drej, _پورت, xate, btha_,
+ {{0x69da966d,0x2508819f,0xafdb0aa2,0x320a0118}}, // vate, ارتی_, ljøe, _ibby_,
+ {{0x69da966e,0x67299554,0x6d4a966f,0x249d9670}}, // wate, _frej, ryfa, _chwm_,
+ {{0x67299671,0x69da9672,0xa2b38198,0xd1ca9673}}, // _grej, tate, обыч, кунд_,
+ {{0x38ba9674,0x6f029675,0x16639676,0x32549677}}, // _për_, mtoc, _двум, свор,
+ {{0xa3d60816,0x6f0d1678,0x672980e1,0x91a980ff}}, // हरा_, _spac, _zrej, _đã_,
+ {{0x69da9679,0x64bc0088,0xa3c38eb6,0x7aed0071}}, // sate, _pčin, ्रः_, yrat,
+ {{0x69da967a,0x98be8196,0xfe7f0980,0x00000000}}, // pate, dytą_, duïr_, --,
+ {{0x7aed07aa,0x9f4002af,0x2d840214,0x69d8007b}}, // vrat, _weiß_, ğmen_, ðvel,
+ {{0x0467167b,0x25fb9513,0x7aed0372,0x24920114}}, // атам, ल्टी_, wrat, flym_,
+ {{0x7aed167c,0x200900b9,0x6f02967d,0x69d8967e}}, // trat, _ubai_, ktoc, _heve,
+ {{0x7bcd167f,0x6f0d0397,0x64aa801b,0x69d89680}}, // [b10] rbau, _upac, _přij, _keve,
+ {{0x62888114,0xe9ab07d2,0x35f50a13,0x290100b9}}, // nodo, ندان_, _упор, ttha_,
+ {{0xdfd180f7,0x69d88722,0x38690506,0x66151681}}, // كيا_, _meve, _kmar_, mezk,
+ {{0x69d89682,0x67299683,0x62889684,0x7aed1685}}, // _leve, _prej, hodo, prat,
+ {{0x53350158,0x29011686,0xdefb8198,0x48dd8074}}, // _האָב_, stha_, тые_, _कौनो_,
+ {{0x9e648cde,0xb6079687,0x69d89688,0xddc3802e}}, // овід, рядк, _neve, dinţ,
+ {{0x7ae280eb,0x62889689,0xd469968a,0x48ab81e5}}, // _avot, dodo, лике_, _атам_,
+ {{0xeb99968b,0x6729968c,0xfd5501bc,0xdd2801a9}}, // лий_, _trej, _slaị, nēša,
+ {{0x69d8968d,0x67298353,0x9f44080a,0x62888114}}, // _beve, _urej, ümü_, fodo,
+ {{0x38690727,0x2fdd822c,0x69d8968e,0x00000000}}, // _amar_, lawg_, _ceve, --,
+ {{0x69d8968f,0xf5958013,0x8d958013,0x6615011e}}, // _deve, _الاج, _الاش, dezk,
+ {{0x20560a14,0xa856038c,0x3d01809a,0x6e219690}}, // стор, стој, mów_, _halb,
+ {{0x6e219691,0x62889692,0x41a7016f,0x443f9693}}, // _kalb, bodo, केतस, mju_,
+ {{0x69d882d8,0x6615011b,0xb6cc8380,0x645b9694}}, // _geve, gezk, _şükü, lhui,
+ {{0x3d018063,0x7bd983ac,0xc69283c8,0x443f8084}}, // nów_, _kewu, פאל_, oju_,
+ {{0x645b9695,0x69d89696,0x78a38cfa,0xd5b18129}}, // nhui, _zeve, ïnvl, _cái_,
+ {{0x7bd98057,0x04431697,0x3aba80be,0x03a30d91}}, // [b20] _mewu, четн, ָמענ, зито,
+ {{0x3d018d38,0x64bc0fda,0x443f8074,0x3f920300}}, // ków_, _učio, hju_, _iyyu_,
+ {{0x24891698,0x499a0a14,0x3d018035,0x645b9699}}, // boam_, утая_, jów_, khui,
+ {{0x7bd989b6,0x3d01809a,0x6e21813c,0x6f02969a}}, // _newu, dów_, _aalb, ttoc,
+ {{0x443f969b,0xf3ff80ab,0x645b969c,0x8bff80ab}}, // dju_, ্যার_, dhui, ্যান_,
+ {{0x4422059e,0x7c24169d,0x443f969e,0x6446169f}}, // _hak_, ldir, eju_, _hoki,
+ {{0x6f0296a0,0x44220988,0x644601e2,0x7bd996a1}}, // stoc, _kak_, _koki, _bewu,
+ {{0x44221308,0x7c24158a,0x644616a2,0xa3a980bc}}, // _jak_, ndir, _joki, _गृह_,
+ {{0x442216a3,0x35fa845b,0x6e2181e4,0x64460110}}, // _mak_, _مراد_, _falb, _moki,
+ {{0xc333004c,0x69d8820f,0x6e2196a4,0x6e9696a5}}, // פות_, _qeve, _galb, _الطا,
+ {{0x443f96a6,0x3d01809a,0x78b88713,0xa3b90006}}, // bju_, ców_, _quvv, _चलल_,
+ {{0x442216a7,0x443f8699,0x645b96a8,0x69de16a9}}, // _nak_, cju_, chui, hape,
+ {{0x7645003e,0x628896aa,0x69d896ab,0x69de16ac}}, // _pohy, podo, _teve, kape,
+ {{0x3dd900c8,0x7aef86a5,0x7c22810c,0xdce500e1}}, // _তাহল, ácte, _haor, _vzhľ,
+ {{0x69de16ad,0x64aa800d,0xe57300f7,0x66150102}}, // dape, _přih, سطس_, rezk,
+ {{0x386916ae,0x7c2416af,0x60c781d0,0x2fdd822c}}, // _umar_, gdir, íjme, xawg_,
+ {{0x7c2296b0,0x644616b1,0x27e903c3,0x442216b2}}, // [b30] _maor, _doki, _idan_, _dak_,
+ {{0x443f84b7,0x7b7400f7,0xb9210870,0x765c16b3}}, // zju_, أطفا, _gasị_, chry,
+ {{0x7c2416b4,0xbebd96b5,0x672d03c1,0xf6520039}}, // bdir, _trūk, _hraj, _חצי_,
+ {{0x442216b6,0x7c228052,0x64bc005c,0x6e2196b7}}, // _gak_, _naor, _očij, _salb,
+ {{0x443f96b8,0x69de16b9,0xe73996ba,0x3d018035}}, // vju_, bape, рел_, wów_,
+ {{0xdb1d04b8,0x3d018063,0x6e2196bb,0xc332010f}}, // _besö, tów_, _qalb, _קום_,
+ {{0x443f96bc,0x442216bd,0xed5996be,0xf53280a9}}, // tju_, _yak_, рой_, дејќ,
+ {{0x3d018063,0x6e2182a3,0x7bdf16bf,0x7c2296c0}}, // rów_, _walb, naqu, _caor,
+ {{0x443f96c1,0x3d018063,0x6e2196c2,0x645b83b2}}, // rju_, sów_, _talb, rhui,
+ {{0xe9d985f1,0x27e916c3,0xda6580e8,0x443f96c4}}, // шко_, _adan_, івни, sju_,
+ {{0x69c196c5,0x26c9008e,0x443f96c6,0xd94596c7}}, // rcle, _atao_, pju_, цени,
+ {{0x672d16c8,0x60cd8267,0x69de16c9,0x1faa05f1}}, // _braj, _čamd, zape, икни_,
+ {{0x442216ca,0x7bdf16cb,0xb69b16cc,0x644616cd}}, // _rak_, daqu, rtân, _roki,
+ {{0x442216ce,0x8aa796cf,0x672d16d0,0xb69b16cc}}, // _sak_, _уред, _draj, stân,
+ {{0x442216d1,0xa3bd835a,0x644601ac,0x69de16d2}}, // _pak_, _आला_, _poki, vape,
+ {{0x69de16d3,0x7c228118,0xdb1d041c,0x7c240037}}, // wape, _xaor, _lesõ, udir,
+ {{0xe9da16d4,0x7c2416d5,0x20050201,0xf41282f6}}, // [b40] ика_, rdir, əli_, ופן_,
+ {{0x442216d6,0x22478a53,0x7c2416d7,0x4996066c}}, // _wak_, _jonk_, sdir, ошет,
+ {{0x69de16d8,0x6da616d9,0x644616da,0x8c4616db}}, // rape, _фина, _toki, _дене,
+ {{0xdd0f0214,0x7c24011c,0x9f4800ff,0x7bdf16dc}}, // lışt, qdir, _điêu_, caqu,
+ {{0x9eaa8ba1,0xcfbc00ab,0x245c0061,0x4ea696dd}}, // авда_, _অজান, _címe_, орка,
+ {{0xe29716de,0x7c22808c,0x412a16df,0x7ae401a8}}, // _мая_, _saor, _кого_, msit,
+ {{0x910315e0,0xf9878f24,0xd90d019f,0x7ae400f7}}, // _опре, _آب_, _میل_, lsit,
+ {{0x7bc416e0,0xfd530091,0x6ee381bc,0x7ae416e1}}, // nciu, _aifọ, _ọber, osit,
+ {{0x69dc01f9,0x6fd08105,0x7ae416e2,0x224796e3}}, // _kere, _ड्यू, nsit, _bonk_,
+ {{0x245181a8,0x20000362,0x442482f7,0x7ae416e4}}, // _lámh_, _acii_, sdm_, isit,
+ {{0x69dc045c,0x224796e5,0x26c902a5,0x7bdf16e6}}, // _mere, _donk_, _stao_, yaqu,
+ {{0x69dc16e7,0x672d16e8,0x7ae416e9,0xe0cf87d2}}, // _lere, _praj, ksit, کزی_,
+ {{0x7648808e,0xfd530032,0x674701e2,0x2005066f}}, // _kody, _fifọ, цэнз, ęli_,
+ {{0x248980e1,0x7bc616ea,0x76488242,0x672d16eb}}, // čame_, _afku, _jody, _vraj,
+ {{0x7bdf00e7,0xa3c80135,0x2bc80133,0x764896ec}}, // taqu, _ọbịa_, _ọjị_, _mody,
+ {{0x672d16ed,0xd6db0364,0x69dc0081,0x67209351}}, // _traj, _кто_, _aere, _osmj,
+ {{0x69dc16ee,0x201916ef,0x7ae400e4,0x7bdf16f0}}, // [b50] _bere, mesi_, gsit, raqu,
+ {{0x201916f1,0x661896f2,0x69dc079a,0x7bc401e8}}, // lesi_, jevk, _cere, aciu,
+ {{0x69dc005f,0x7bdf16f3,0xc0158009,0x7d0d8035}}, // _dere, paqu, змещ, łasz,
+ {{0x765a96f4,0x201916f5,0x78bc16f6,0x7ae416f7}}, // _alty, nesi_, _durv, bsit,
+ {{0x2ca916f8,0x27e0807b,0x69dc16f9,0x7bdd02f9}}, // mmad_, ðina_, _fere, _iesu,
+ {{0x69dc16fa,0x7bdd16fb,0x518716fc,0x2ca916fd}}, // _gere, _hesu, _дуба, lmad_,
+ {{0xdfd596fe,0x7bdd0859,0x69c516ff,0xdb1b81df}}, // _новы, _kesu, mche, rcuí,
+ {{0x69c51700,0x7bdd1701,0x0b458081,0x7d7c00be}}, // lche, _jesu, знин, ינוו,
+ {{0x2bdc0076,0x7bdd1702,0x69dc1703,0x76d580f7}}, // बरता, _mesu, _yere, _رياض,
+ {{0x69dc1704,0x7bdd1705,0x69c51706,0x09af00ab}}, // _xere, _lesu, nche, _চ্যা,
+ {{0x69c51707,0x20191708,0x2c0c0054,0xdcef8084}}, // iche, fesi_, ड्डू_, _įdėt,
+ {{0x2458837d,0x7bdd01e2,0x315600be,0x20191709}}, // _téma_, _nesu, פירן_, gesi_,
+ {{0x2451970a,0x64aa81d0,0x2905970b,0x69cc970c}}, // _námi_, _přiv, stla_, _द्वी,
+ {{0xfe79801b,0x6e25170d,0x69c5170e,0x68e7082c}}, // _svůj_, _cahb, jche, _ovjd,
+ {{0x61e18364,0x2019026e,0x386d970f,0x7bdd1710}}, // mall, besi_, _omer_, _besu,
+ {{0x7ae41711,0x20191712,0x61e19713,0x248d81b0}}, // tsit, cesi_, lall, noem_,
+ {{0x69dc1714,0x7bdd006a,0x7bc40019,0x2d9e026f}}, // [b60] _pere, _desu, rciu, ťte_,
+ {{0x7bc40098,0xba231715,0x27e200dd,0x78bc0612}}, // sciu, ндук, lakn_, _purv,
+ {{0x7ae41716,0x76488110,0xda0204c5,0x7bc41717}}, // ssit, _rody, र्गत_, pciu,
+ {{0x69dc1718,0x61e19719,0x7bdd171a,0x7ae4171b}}, // _were, hall, _gesu, psit,
+ {{0x61e1971c,0x387f816d,0xddcf801b,0x69c5171d}}, // kall, _djur_, _řeše, bche,
+ {{0x69c50098,0x78bc037b,0x61e1971e,0x7bdd09b6}}, // cche, _turv, jall, _zesu,
+ {{0x2019171f,0x20c18104,0x7bdd1720,0x7c261721}}, // yesi_, _nói_, _yesu, _kakr,
+ {{0x321a009a,0x9e66826a,0x7bdd03a8,0xadc30032}}, // lepy_, _کارن, _xesu, _diẹd,
+ {{0x66188038,0x61e19722,0x6720920e,0x20191723}}, // pevk, fall, _usmj, vesi_,
+ {{0x20191724,0x61e19725,0x7aef9726,0xe3bf1727}}, // wesi_, gall, ácta, _maña_,
+ {{0x20191728,0x60cd82a5,0x6e251729,0x69c3007a}}, // tesi_, _čamc, _rahb, žneg,
+ {{0x78a4172a,0x7c26172b,0x7ac6841c,0xe3a78019}}, // _chiv, _nakr, _еске, _کشمی,
+ {{0x2bd29513,0x61e1972c,0x2019172d,0xaa7b01d0}}, // _ध्या, ball, resi_, _svým,
+ {{0x7bdd0393,0x4426972e,0x61e1972f,0x80d200ab}}, // _sesu, _hao_, call, _হিন্,
+ {{0x4426803a,0x20c1801c,0x7bdd08e5,0x20191730}}, // _kao_, _gói_, _pesu, pesi_,
+ {{0x7c260cc0,0x9584009a,0xbebd96b5,0x6e2500b9}}, // _cakr, _łącz, _krūt, _wahb,
+ {{0xd6d78a49,0x4426951e,0x69c51731,0xe3bf06a5}}, // [b70] _সম্প, _mao_, tche, _caña_,
+ {{0x44269732,0x27e08125,0x69c51733,0x386d9734}}, // _lao_, ðinn_, uche, _smer_,
+ {{0x69c506f8,0x7bdd1735,0x291906e2,0xfaa801a8}}, // rche, _tesu, تقاد_, أهلي_,
+ {{0x69c51736,0x44269737,0xd75980f7,0x61e19738}}, // sche, _nao_, يلات_, zall,
+ {{0x44291739,0x4439173a,0x69c5173b,0x61e1973c}}, // mda_, _ins_, pche, yall,
+ {{0x4439173d,0x7c260491,0x5fce8327,0x61e1973e}}, // _hns_, _zakr, हुबल, xall,
+ {{0x442902a3,0x44268028,0xc10515a9,0x7c3a0174}}, // oda_, _bao_, _توزي, _úcrá,
+ {{0x4429173f,0x44268104,0x61e19740,0xf53800be}}, // nda_, _cao_, wall, לטור_,
+ {{0x61e1855b,0x27ed9741,0xdb0982d0,0x20c18129}}, // tall, _iden_, _önüm, _sói_,
+ {{0x44291742,0x81bc81a9,0x25a0928a,0x7ea01743}}, // hda_, ldēj, řila_, köpa,
+ {{0x44391744,0x44291745,0x26cd82ee,0x61e19746}}, // _ons_, kda_, _hteo_, rall,
+ {{0x61e19747,0x44291748,0xd36e8077,0x2fdf867f}}, // sall, jda_, اهی_, _jeug_,
+ {{0x442912a8,0x61e19749,0xe28e974a,0xbebd80eb}}, // dda_, pall, _аа_, _grūt,
+ {{0x4439174b,0x4429174c,0x4426822e,0x6da609b8}}, // _ans_, eda_, _zao_, дига,
+ {{0x4426822e,0x7afb84a2,0x7c260110,0x27e200dd}}, // _yao_, luut, _pakr, pakn_,
+ {{0x4429174d,0x26cd825b,0x5ed380ab,0x442680ff}}, // gda_, _oteo_, _সিনে, _xao_,
+ {{0x1e860615,0x644b974e,0xeb97174f,0x7c299750}}, // [b80] _олим, _mogi, дит_, ider,
+ {{0x44391751,0x44261752,0x645d1753,0x7c26004f}}, // _ens_, žo_, _elsi, _wakr,
+ {{0x44291754,0x661c1755,0x27e00352,0x7c261756}}, // bda_, merk, _kein_, _takr,
+ {{0x7c299757,0x7afb9758,0x442902a3,0x3ebf9759}}, // jder, kuut, cda_, _buut_,
+ {{0x27e00352,0x44268028,0x2fdf81e4,0xdd8f00d7}}, // _mein_, _rao_, _deug_, _موی_,
+ {{0x4426975a,0x27ed975b,0x7c29975c,0x27e0031d}}, // _sao_, _eden_, eder, _lein_,
+ {{0xda0e80ff,0x2ee680b9,0xa3aa01d0,0xa3cb175d}}, // _bỏng_, ssof_, गेर_, रुव_,
+ {{0xe9a3175e,0x6724175f,0x644b9760,0x27e010f6}}, // _расп, _osij, _cogi, _nein_,
+ {{0x1dcf863a,0x60cd8289,0x7afb9761,0x44269762}}, // _स्वत, _čama, guut, _vao_,
+ {{0x4426822e,0x2489805c,0xdb22809a,0x64aa801b}}, // _wao_, čama_, _źród, _přis,
+ {{0x44269763,0x09f70051,0x44291764,0xd48f8012}}, // _tao_, ומים_, yda_, _ар_,
+ {{0x442902a3,0xdbd604a2,0x7afb9765,0x26c0047f}}, // xda_, _jääd, buut, _buio_,
+ {{0x27e002af,0xfe730154,0x26c00118,0x6e2880b9}}, // _dein_, ندس_, _cuio_, _kadb,
+ {{0x44390eef,0x661c1766,0x44291767,0x63a29768}}, // _sns_, gerk, wda_, _izon,
+ {{0x44290085,0x27e01769,0x44390b99,0x6fde8006}}, // tda_, _fein_, _pns_, मरां,
+ {{0xd5b8176a,0xaa7b026f,0x7af60168,0x4429176b}}, // ься_, _zvýh, fryt, uda_,
+ {{0x6e3a83b2,0xa3aa016f,0xf0940039,0xfc3f0216}}, // [b90] _ontb, गेल_, _בנק_, _chía_,
+ {{0x4429176c,0x6e28976d,0x27e0011e,0x7674176e}}, // sda_, _nadb, _zein_, _альф,
+ {{0x7c29976f,0x80e080ab,0x66051770,0xdb0d016a}}, // yder, পূর্, _ichk, rcañ,
+ {{0x44291771,0x44391772,0x245c0125,0x7af61773}}, // qda_, _uns_, _tíma_, bryt,
+ {{0x6e288079,0x22940106,0x6d550061,0xa3c29774}}, // _badb, täkt_, lyza, ंशन_,
+ {{0xe5a58e8e,0xceb8809a,0x603e8087,0x6da59775}}, // нили, _cię_, _pămâ, нила,
+ {{0x63a28065,0x61e51776,0xdc3a82d0,0x2ca68706}}, // _azon, mahl, _açıs, _bhod_,
+ {{0x03259777,0x27ed8022,0x69ca9778,0x61e51779}}, // един, _uden_, _affe, lahl,
+ {{0x7c29977a,0x27e0177b,0xe61082e3,0x661c177c}}, // rder, _rein_, _چشم_, yerk,
+ {{0x27e0177d,0x7c299045,0x63a2977e,0x3945977f}}, // _sein_, sder, _dzon, еног,
+ {{0x661c088b,0x7afb9780,0x9f4200d7,0x644b838a}}, // verk, suut, maké_, _togi,
+ {{0xed56893f,0x661c1781,0xe7308065,0x6e288035}}, // _אבער_, werk, _حصہ_, _zadb,
+ {{0x661c1782,0x443e001c,0x3a2786c0,0x61e50234}}, // terk, _đt_, _tanp_, kahl,
+ {{0xe3bf1783,0x64bc1784,0x2cad9785,0x7ae99786}}, // _baño_, _očit, lmed_, mset,
+ {{0x40960676,0x61e5010b,0xa97980be,0x7ae99787}}, // _прит, dahl, _מאַכ, lset,
+ {{0x46ea0698,0xe3bf06a5,0xfc3f001c,0x7bc99788}}, // _един_, _daño_, _phía_, nceu,
+ {{0x7ae99789,0x9f4203f8,0x27450168,0x3860178a}}, // [ba0] nset, kaké_, _vënë_, _ilir_,
+ {{0x7ae9978b,0x2cad978c,0x9f42026f,0x99858019}}, // iset, hmed_, jaké_, lelő_,
+ {{0x7ae99266,0xf77f041c,0x50ba0060,0x6e28978d}}, // hset, maça_, سداد_, _radb,
+ {{0xceb88610,0x27e0978e,0x2002178f,0xe3a7845b}}, // _się_, úin_, ngki_, _مر_,
+ {{0xe8050c28,0x2cad8074,0x61e51790,0x7ae99791}}, // ष्टा_, dmed_, bahl, jset,
+ {{0x7ae99792,0xf77f023e,0x9c7c80c3,0xae040072}}, // dset, naça_, _ivči, श्चन_,
+ {{0xf653010f,0x6e28807a,0x7ae99793,0xa9a301a1}}, // רצו_, _vadb, eset, _бирд,
+ {{0x7ae99794,0x25a081d0,0x2bd3001b,0x69a51795}}, // fset, řilo_, धुपा, _करती,
+ {{0x6e2883ac,0x087780be,0x3f8188c5,0x7ae99796}}, // _tadb, _נעמט_, şhur_, gset,
+ {{0x27e69797,0xdb2381a8,0x38601798,0x3eba1799}}, // laon_, órái, _alir_, _nipt_,
+ {{0x3860179a,0xd5a484e5,0x88bc801b,0x67d500e8}}, // _blir_, _ओरिज, jvět, кону,
+ {{0x248000ce,0x27e6979b,0x61e5179c,0xa3b103dd}}, // jnim_, naon_, zahl, टेड_,
+ {{0x24800025,0x64bc179d,0xe784179e,0x3ce30072}}, // dnim_, _očis, гуро, टीने_,
+ {{0xbb3b8451,0x09e380ab,0xfc3f00ff,0xe3bf0388}}, // _מעדי, _মাথা, _chín_, _paño_,
+ {{0xceb380be,0x321e9122,0x27e68bb1,0x89338481}}, // טיש_, mety_, kaon_, _معيا,
+ {{0xd49814d6,0x6d55009a,0x321e89a4,0x61e5179f}}, // еру_, ryza, lety_, wahl,
+ {{0x200c0201,0x29180037,0x09e38264,0xf77f02df}}, // [bb0] ədi_, _apra_, _মাতা, baça_,
+ {{0x7afd809f,0x248017a0,0x9e6701a8,0xa15917a1}}, // àsti, anim_, _ساخن, мазу_,
+ {{0xa3e08fd5,0xdfd180f7,0x61e517a2,0xf7730019}}, // थरा_, ليا_, rahl, _جاں_,
+ {{0x24800052,0x1b1780ab,0x2918008e,0xddc88bcf}}, // cnim_, তিতে_, _dpra_, _ćoša,
+ {{0xd9ad97a3,0x321e826f,0x7c2d17a4,0x58b881a8}}, // _घण्ट, kety_, mdar, رامج_,
+ {{0x7c2d17a5,0x7ae997a6,0x442b17a7,0x644f17a8}}, // ldar, vset, _hac_, _hoci,
+ {{0x644f17a9,0x64aa800d,0x64bc17aa,0xfc3180f7}}, // _koci, _přip, _učit, لحة_,
+ {{0x2489881d,0x7ae997ab,0x63a217ac,0xf3ff02df}}, // čamo_, tset, żone, _imãs_,
+ {{0x644f17ad,0x1c4297ae,0x9f42003d,0x2cad97af}}, // _moci, рным, paké_, rmed_,
+ {{0xa77480e9,0x7ae997b0,0x248000ce,0x7c2d17b1}}, // _случ, rset, znim_, hdar,
+ {{0x320b809a,0x442b0980,0x7c2d17b2,0x09e38264}}, // ęcy_, _oac_, kdar, _মাদা,
+ {{0x442b0114,0x7c2d17b3,0x7ae997b4,0x26df8135}}, // _nac_, jdar, pset, _kwuo_,
+ {{0x7c2d17b5,0x442d8b0b,0xf8b28158,0xe813816f}}, // ddar, mde_, ישט_, ण्या_,
+ {{0xf1b1053f,0x23690035,0xa3d40072,0x99858061}}, // जधान, dzaj_, सुन_, selő_,
+ {{0x1d070dae,0xfc3f0118,0x442b17b6,0x7c2b97b7}}, // кери_, _chío_, _bac_, _kagr,
+ {{0x644f040e,0x7c2d020d,0x442b17b8,0xc48310ca}}, // _coci, gdar, _cac_, алск,
+ {{0x7c2b97b9,0xa3e317ba,0xc0530039,0x628186cb}}, // [bc0] _magr, _नजर_, _כזה_, onlo,
+ {{0x248002a5,0x442d8364,0x7c2b97bb,0x60c397bc}}, // snim_, hde_, _lagr, _kunm,
+ {{0x442b0012,0x27e69049,0x442d97bd,0x7c2d17be}}, // _fac_, taon_, kde_, bdar,
+ {{0x442d97bf,0x38608013,0xd00f8eca,0x7e6197c0}}, // jde_, óir_, قلم_, _allp,
+ {{0x442d97c1,0xe1e784c0,0x442017c2,0x27e697c3}}, // dde_, _پس_, mei_, raon_,
+ {{0x442017c4,0x442d97c5,0x290c8085,0x765c17c6}}, // lei_, ede_, ytda_, nkry,
+ {{0x442d90f4,0x614317c7,0x3cfd801b,0xa8068256}}, // fde_, _кера, रूले_, _извл,
+ {{0x6abb0118,0x409597c8,0x78bb97c9,0x442b027d}}, // _éufr, _брут, _giuv, _xac_,
+ {{0x68e1013c,0x26df819d,0xd7078087,0x799c0114}}, // _ældr, _gwuo_, _инте_, _hyrw,
+ {{0x386680a9,0x7c2b97ca,0x442017cb,0x7c2d17cc}}, // lhor_, _eagr, hei_, zdar,
+ {{0x442017cd,0x26c497ce,0xdd1f81ac,0x5ec780ab}}, // kei_, _humo_, píšt, রীদে,
+ {{0xf9930051,0x386683a7,0x68e31735,0x7c2b80d7}}, // ירת_, nhor_, ândi, _gagr,
+ {{0x442017cf,0x6d588110,0x260980d4,0x321e89a4}}, // dei_, lyva, _सामी_, pety_,
+ {{0x7c2b97d0,0xdce50038,0x27e4802e,0x61e397d1}}, // _zagr, _vyhľ, _lemn_, _genl,
+ {{0x644f0a7a,0x241901bb,0x61e897d2,0x40350a08}}, // _poci, новы_, madl, _бейс,
+ {{0xfbd304c0,0x69ce0a0f,0x7c2d14e4,0x442017d3}}, // شتر_, _afbe, udar, gei_,
+ {{0x521517d4,0x386697d5,0x644f17d6,0x6d4a0706}}, // [bd0] лдат, dhor_, _voci, _àfan,
+ {{0x442d97d7,0xf09f009f,0xa2c381a2,0x78bb8084}}, // zde_, llà_, रदत्, _siuv,
+ {{0xf770096c,0x442017d8,0x21270104,0x6d5d8201}}, // _های_, bei_, ình_, _əsas,
+ {{0x61e897d9,0xcfde80c8,0x24868a20,0x442017da}}, // hadl, _ডাউন, _njom_, cei_,
+ {{0x442d97db,0xd9e386a7,0x61e897dc,0xdbd102f1}}, // vde_, _ग़लत_, kadl, _müüd,
+ {{0x442d97dd,0x6abc0609,0x26c482ed,0x6a6001fa}}, // wde_, _girf, _dumo_, _höfn,
+ {{0x7c2b97de,0x2609816f,0x61e897df,0x442d97e0}}, // _pagr, _साठी_, dadl, tde_,
+ {{0x6281862d,0x61e397e1,0x290117e2,0x26c497e3}}, // wnlo, _senl, muha_, _fumo_,
+ {{0x442d97e4,0x60c397e5,0x7aed11e6,0x78660693}}, // rde_, _sunm, nsat, _jóve,
+ {{0x442d9313,0xb7bd802e,0x78660073,0x672997e6}}, // sde_, _naţi, _móve, _isej,
+ {{0x61e397e7,0x7c2b97e8,0x26c497e9,0x290117ea}}, // _venl, _tagr, _zumo_, nuha_,
+ {{0x44e181e2,0xbf9b0073,0x442017eb,0x6f1b8133}}, // mų_, rgên, xei_, _kpuc,
+ {{0x442017ec,0x7866007b,0x44e18110,0x7bdb97ed}}, // vei_, _nóve, lų_, mbuu,
+ {{0x249f97ee,0x290117ef,0x442017f0,0x38ca8077}}, // llum_, kuha_, wei_, بایی_,
+ {{0x44e181e2,0x765c17f1,0x7cd180eb,0x7afb84dc}}, // nų_, rkry, _dārg, lrut,
+ {{0x765c079f,0x78660e15,0xa3e68105,0x290117f2}}, // skry, _bóve, _बड़ा_, duha_,
+ {{0xfc3f003e,0x7aed17f3,0xcb6717f4,0x64a3076a}}, // [be0] _dní_, gsat, гате_, сата,
+ {{0x442017f5,0x44e181e2,0xfeba0277,0x6b9d031d}}, // sei_, kų_, _ثابت_, _dysg,
+ {{0x44e181e2,0x629e17f6,0x442017f7,0x249f97f8}}, // jų_, rlpo, pei_, klum_,
+ {{0x8c4617f9,0x7afb97fa,0x44e18110,0x6abc17fb}}, // _семе, krut, dų_, _wirf,
+ {{0x09e800ab,0x7aed1380,0x7e788b67,0xe3b2015b}}, // _পাঠা, csat, tivp, _درد_,
+ {{0x2ca017fc,0x290117fd,0x6d588084,0xb5e20264}}, // llid_, buha_, tyva, _বাঁচ,
+ {{0x386697fe,0x2cc981ac,0x61e897ff,0x44e18110}}, // phor_, _ľudí_, vadl, gų_,
+ {{0x7afb9800,0x260981c4,0x7bdb9801,0x26c49802}}, // frut, _साथी_, gbuu, _tumo_,
+ {{0x68f5803e,0x3075831f,0x61e89803,0x41e68221}}, // ázdn, _турс, tadl, ліна,
+ {{0x471b80be,0x39148329,0xdbd10074,0xfd5d8870}}, // _אומג, амор, _süüd, _jizọ,
+ {{0x3de2150b,0x26098e18,0x69da9175,0x2ca00074}}, // _বাংল, _साती_, rbte, klid_,
+ {{0x7aed02a3,0xfc3f001c,0x7afb9804,0x3ebe9805}}, // ysat, _phím_, brut, _hitt_,
+ {{0x543b8158,0x7afb9806,0x61e89807,0xf77f03a7}}, // _רעדא, crut, padl, raço_,
+ {{0x26e5000f,0xcf9381c6,0x6d498035,0x6a60008b}}, // _कबीर_, מטר_, ślał, _söfn,
+ {{0x3ebe9808,0xf77f0073,0x6d4a07b6,0xf09f0980}}, // _mitt_, paço_, _àfal, plà_,
+ {{0x3ebe8cde,0x248d1809,0x7aed180a,0x2ca002f1}}, // _litt_, čemo_, tsat, glid_,
+ {{0x3a75180b,0x78ad180c,0xb27501a1,0x998c0b80}}, // [bf0] алар, _ihav, алаш, _ćoše_,
+ {{0x7aef840e,0x7ed40013,0x5ed380c8,0x7bcd180d}}, // ácti, _ازيا, _সিলে, scau,
+ {{0x7aed180e,0x66758077,0xf1a7180f,0xc9841810}}, // ssat, یدتر, уран, буци,
+ {{0x29011811,0x2c27035f,0x7aed1812,0x2ca01813}}, // ruha_, _сьог, psat, clid_,
+ {{0xb8170768,0xe81701b6,0xf7459814,0x26009815}}, // ध्यम_, ध्या_, рело, _राखी_,
+ {{0x62830259,0x44e181e2,0x09df00ab,0x7bc28176}}, // čnos, tų_, _ঢাকা, _agou,
+ {{0x3ebe9816,0xd6580051,0x58d4153d,0x7ae29817}}, // _ditt_, ריות_, _гост, _awot,
+ {{0xeb999687,0x99840013,0x7afb9818,0x44e18110}}, // кий_, _الفو, trut, rų_,
+ {{0x6f1b8024,0x09e800ab,0xc8649819,0x6729981a}}, // _upuc, _পাতা, _утри, _usej,
+ {{0x6285181b,0x7cd18029,0x7afb981c,0x7c2f117e}}, // nnho, _pārd, rrut, _lacr,
+ {{0xa06a181d,0x61e7181e,0x888608cc,0x186a181f}}, // вана_, _mejl, илож, вани_,
+ {{0xb4c19094,0xfa2580c8,0x7cd18029,0x78ad1820}}, // ंदी_, বাইল_, _vārd, _chav,
+ {{0xe9ce9821,0x2ca01822,0x7ae29823,0x7d039824}}, // _мк_, vlid_, _gwot, luns,
+ {{0x442f9825,0x61e7000d,0x3ebe09df,0x6e239826}}, // _hag_, _nejl, ött_, nenb,
+ {{0x442f9827,0xc62800ab,0xfd5d81bc,0x7d039828}}, // _kag_, মালা_, _rizọ, nuns,
+ {{0x442f84b5,0xbc630ab5,0xa3ac8935,0x26c41829}}, // _jag_, овск, _गरम_, ímos_,
+
+ {{0xa3b1016f,0x62970118,0x91fc80eb,0x6e23982a}}, // [c00] टेल_, foxo, bkād, kenb,
+ {{0xe9d700bf,0x442f982b,0x7ea0016d,0xd0e68fd3}}, // ику_, _lag_, köpi, _حکیم_,
+ {{0x3f9f982c,0x61e7013c,0x7d038669,0xa926182d}}, // _ayuu_, _dejl, juns, рдел,
+ {{0x442f982e,0x3ebe888b,0x7d03982f,0x60c7082c}}, // _nag_, _sitt_, duns, _dujm,
+ {{0x7c2407e1,0x61e71830,0x6e239831,0x2d9e9832}}, // meir, _fejl, fenb, _syte_,
+ {{0x7c241833,0xb4c19834,0x6e239835,0x7c2f012b}}, // leir, ंदू_, genb, _zacr,
+ {{0x394700f2,0x3ebe9836,0xddc8809a,0xafe68110}}, // änst_, _vitt_, _podł, _вобл,
+ {{0x7c24156b,0x442f9837,0x7ae28247,0x44321838}}, // neir, _cag_, _pwot, ndy_,
+ {{0x15e6000f,0x27e0807b,0x6e239839,0xd5b180ff}}, // करार_, ðinu_, benb, _máy_,
+ {{0x80ca8935,0x7d1e035f,0xf1aa0035,0x2d96983a}}, // _संदे, _opps, _करान, _крес,
+ {{0x3a370051,0x4432003e,0x442f983b,0xd57580e8}}, // ברים_, kdy_, _fag_, _луць,
+ {{0x442f983c,0x7c240ba3,0x69c3983d,0x1b1d0264}}, // _gag_, jeir, _egne, নিতে_,
+ {{0x7c24183e,0x4424983f,0x7ad486e2,0x629c1840}}, // deir, mem_, _اقتص, _okro,
+ {{0xa2c386a7,0x442f9841,0x78a2816b,0x443200e1}}, // रदर्, _zag_, hlov, edy_,
+ {{0x7c24031d,0x58871842,0x78a2816b,0xa4d5004a}}, // feir, _выпа, klov, собі,
+ {{0x44249843,0x61300117,0x78a28d11,0x7c241844}}, // nem_, zólá, jlov, geir,
+ {{0x78a29845,0x44249846,0x644d1847,0xa3b10035}}, // [c10] dlov, iem_, rjai, टें_,
+ {{0x44249848,0x62851849,0xa3cb184a,0x6e23984b}}, // hem_, unho, रुज_, xenb,
+ {{0x4424984c,0x61e7013c,0x7c24184d,0x6285184e}}, // kem_, _vejl, beir, rnho,
+ {{0x7c24184f,0x60c084b9,0x27e91850,0x9ef581a8}}, // ceir, _cimm, _jean_, مستش,
+ {{0x44249851,0x6e239852,0x442f9853,0x2b470071}}, // dem_, tenb, _rag_, _тэнг,
+ {{0x64428307,0x442f9854,0x7d039855,0x672d004f}}, // _anoi, _sag_, tuns, _msaj,
+ {{0x442f9856,0x78a28b67,0x60c0807b,0x40358098}}, // _pag_, blov, _fimm, _левс,
+ {{0x44249857,0x7d039858,0x7cd180eb,0x213e00f7}}, // gem_, runs, _pārb, _átha_,
+ {{0x442f9859,0x6e23985a,0x7d03985b,0x753a8102}}, // _vag_, penb, suns, _ertz,
+ {{0x4432185c,0x442f985d,0x60c082af,0x7d038077}}, // zdy_, _wag_, _zimm, puns,
+ {{0x236d8063,0x442f8252,0x4424985e,0x2913011b}}, // szej_, _tag_, bem_, ntxa_,
+ {{0x4424985f,0x7c24184f,0x442f81c0,0x245c008b}}, // cem_, xeir, _uag_, _tími_,
+ {{0x7c240548,0x43749860,0x6d410502,0x6d5c0428}}, // veir, _мушт, ålan, wyra,
+ {{0xda0305b3,0x6d5c1861,0xa3bd1862,0x69c39863}}, // _लागत_, tyra, ेखन_, _ugne,
+ {{0x7c241864,0x78a29865,0x98a7928a,0x27e91866}}, // teir, ylov, íně_, _fean_,
+ {{0x27e91867,0x29059868,0x3fe609a5,0xe8171869}}, // _gean_, mula_, _ужив, ध्दा_,
+ {{0x2905986a,0x7c24186b,0x387d8114,0x64aa81d0}}, // [c20] lula_, reir, diwr_, _přiz,
+ {{0x7c24186c,0x09e800ab,0x0f3701c6,0x200d8493}}, // seir, _পাহা, _פריט_, _acei_,
+ {{0x7d1e186d,0xed5a07ff,0xe8fa186e,0x27e900b9}}, // _upps, _сон_, уле_, _yean_,
+ {{0x4424986f,0x673b8ca3,0x3a259870,0x16058006}}, // xem_, _bruj, help_, _राउर_,
+ {{0x44249871,0x78a29872,0x29059873,0x7bc60122}}, // vem_, rlov, hula_, _igku,
+ {{0x78a29874,0x412a0cde,0x29059875,0xa3d40075}}, // slov, _його_, kula_, सुर_,
+ {{0x60c09876,0x26098701,0x7bc60122,0x9ac301b9}}, // _timm, _सारी_, _kgku, ċċes,
+ {{0x29059877,0xbf4d81d0,0x6d5900fc,0xafdb1277}}, // dula_, _šířk, _ƙwaz, hjør,
+ {{0xe7378364,0x53349878,0x673b9879,0x66e3987a}}, // _лет_, цепт, _gruj, _нота,
+ {{0x4424987b,0x3d190d38,0x6f060ebf,0x2905987c}}, // sem_, _पहले_, dukc, fula_,
+ {{0x2905987d,0x6e93987e,0x44248873,0x3a370039}}, // gula_, _علما, pem_, זרים_,
+ {{0x6ca6826a,0xc867187f,0x5edd0264,0xc95301c6}}, // _اصطل, стои, _বিবে, למת_,
+ {{0xa3bc9880,0x27e91881,0x26c1033e,0xb8ef86ae}}, // _आणि_, _vean_, _piho_, _वू_,
+ {{0x29058a40,0x63a29882,0xafdb00e8,0x161b8c28}}, // bula_, _iyon, gjør, प्पर_,
+ {{0x29059883,0x865b898a,0x63a2808e,0x280401d0}}, // cula_, נדלי, _hyon, ásné_,
+ {{0xd49b0912,0xd5d901ce,0x81c000ab,0x213f026c}}, // ург_, _ब्रज, ীরা_, _čuh_,
+ {{0x32550a13,0x672d004f,0x62889884,0x26099885}}, // [c30] овар, _usaj, ondo, _साली_,
+ {{0x62889886,0x80db80ab,0x98bf0084,0xf8c9827d}}, // nndo, _ভিত্, tytė_, _mẩn_,
+ {{0x38691887,0x63a29888,0x62889447,0x09e80264}}, // _klar_, _lyon, indo, _পাশা,
+ {{0x629a9889,0xf77080d5,0x6e27188a,0x27e9988b}}, // hoto, کام_, lejb, úan_,
+ {{0xddc5835f,0x290594e6,0x2489188c,0x61f8988d}}, // обли, zula_, mnam_, _odvl,
+ {{0xe61890ac,0xdce8817b,0x38690722,0x673b81a1}}, // йді_, _aydı, _llar_, _vruj,
+ {{0xe80a00cf,0x38690201,0x69c7035f,0x2489188e}}, // _वाला_, _olar_, _igje, onam_,
+ {{0x06d880ab,0x63a28c56,0xe459988f,0x29058234}}, // _সিরি, _byon, ржи_, vula_,
+ {{0x24891890,0x29059891,0xf8c980ff,0x60ca81ec}}, // inam_, wula_, _cẩn_, _aufm,
+ {{0x29059892,0x629a9893,0x63a286c4,0x38691894}}, // tula_, goto, _dyon, _alar_,
+ {{0x6026076a,0xd8261895,0x63a29896,0x38691897}}, // одна, одни, _eyon, _blar_,
+ {{0x29059898,0x38691899,0xe73a07c4,0x27ef80dd}}, // rula_, _clar_, аем_, nagn_,
+ {{0x2905989a,0x63a28365,0x6235957b,0xde888133}}, // sula_, _gyon, _меду, lịa_,
+ {{0x2905989b,0x61ea989c,0x629a989d,0x2489189e}}, // pula_, _gefl, coto, enam_,
+ {{0xdd920a47,0x8c7a0081,0x2c6706ae,0x66010789}}, // ذور_, ащат_, _sõda_, ólka,
+ {{0x60c91482,0x69c700f1,0x69d501ed,0x2489189f}}, // _čemp, _agje, _afze, gnam_,
+ {{0xd46718a0,0x161b00d4,0xc88916a5,0x03a30729}}, // [c40] ците_, _पॉवर_, _دخول_, дито,
+ {{0xae0e8aed,0xafdb054f,0x78a618a1,0x25ad06d4}}, // _साधन_, ljøp, llkv, řela_,
+ {{0x4fc698a2,0x38cb00d5,0x7cf081a8,0xc7c698a3}}, // осла, لانی_, _mórá, осли,
+ {{0xdca60e97,0x69da18a4,0x629a804f,0x09aa0072}}, // _фами, _प्री, zoto, _करीय,
+ {{0x41c9800c,0x644618a5,0x3ced0024,0x629a8010}}, // रशास, _inki, ćeve_, yoto,
+ {{0x72c30364,0x4733835f,0x6b849024,0xa3dc8072}}, // _обяз, ьніс, _žigo, तुन_,
+ {{0x629a98a6,0x26120074,0x78660118,0x68ed8035}}, // voto, _तानी_, _nóvo, _ładn,
+ {{0x69c7020f,0x7c3618a7,0x629a98a8,0x6ab298a9}}, // _zgje, ndyr, woto, ँग्र,
+ {{0x3ce9803b,0x629a82b8,0x38690362,0x3e6b8163}}, // ćava_, toto, _rlar_, _søte_,
+ {{0x2489105e,0xd25a80c4,0x24800079,0x7afd98aa}}, // znam_, рци_, hiim_, ásta,
+ {{0xaca401bc,0x2bac8424,0x628884fe,0x64460198}}, // _alọt, _चरवा, rndo, _onki,
+ {{0x0b4698ab,0x7ea00106,0x63a298ac,0x6f0298ad}}, // _мнен, köps, _tyon, oroc,
+ {{0x629a8c93,0x60c418ae,0x628f8866,0x06cc8264}}, // poto, _diim, écoc, রীরি,
+ {{0x5edd00ab,0x628e026c,0x22580102,0x644618af}}, // _বিদে, _njbo, _nork_, _anki,
+ {{0xd7fb18b0,0x64c518b1,0x60c418b2,0x6b60826b}}, // _куп_, वदेश, _fiim, _fágú,
+ {{0x38690458,0xd14b004e,0x60c418b3,0x6f0298b4}}, // _ular_, یشان_, _giim, kroc,
+ {{0x248918b5,0x3f8c0085,0x27ef98b6,0x22580d05}}, // [c50] rnam_, şdur_, vagn_, _bork_,
+ {{0xe80d85b3,0x644618b7,0x9388102a,0xe817064a}}, // _हाहा_, _enki, іста_, ध्रा_,
+ {{0xf7d88133,0x47d881bc,0xb11301bc,0x7ea4823e}}, // _ịrụp, _ịzụk, pụmk, còpi,
+ {{0xb8f318b8,0xc95380be,0x6f028037,0x2ee900fc}}, // _हं_, ומע_, froc, _kwaf_,
+ {{0x442918b9,0xe73998ba,0x6f0298bb,0x872781a8}}, // mea_, сел_, groc, تعام,
+ {{0x442918bc,0x27ef9277,0xb9010264,0x9605914f}}, // lea_, sagn_, _দি_, रलेट_,
+ {{0x308588ca,0x224998bd,0x3b0900b9,0x2fc902c4}}, // _فلسف, _đak_, luaq_, _ogag_,
+ {{0x442918be,0x6f0298bf,0x2fc90420,0x68ed8035}}, // nea_, broc, _ngag_, _łado,
+ {{0x4ea7838c,0xa3bd001b,0xeda8001b,0x225818c0}}, // _држа, ेखि_, _गर्छ, _york_,
+ {{0x28cf81b6,0x442918c1,0x27ed83b2,0xcb120496}}, // _संदि, hea_, _heen_, עלי_,
+ {{0x44290bec,0xd945919d,0x60c418c2,0x98a60087}}, // kea_, чени, _piim, зине,
+ {{0x26c5822e,0xc4d28039,0x442918c3,0x60c40079}}, // _hilo_, _נגד_, jea_, _qiim,
+ {{0x442918c4,0x60c418c5,0xeb8e98c6,0x27ed98c7}}, // dea_, _viim, _чи_, _meen_,
+ {{0xc4c60013,0xd35684de,0x27ed98c8,0xbb3a82f6}}, // كترو, _מיני_, _leen_, _לעני,
+ {{0xceb28051,0xc1aa009a,0x442918c9,0x44f701b9}}, // גים_, _करेग, fea_, għ_,
+ {{0x7c2998ca,0x26c584be,0x442918cb,0x6f0298cc}}, // neer, _lilo_, gea_, yroc,
+ {{0x2ee900fc,0x20120a03,0x2d850866,0x26c58f3e}}, // [c60] _gwaf_, _icyi_, ûler_, _oilo_,
+ {{0x628198cd,0x26c590ab,0x7c2998ce,0x69de8074}}, // nilo, _nilo_, heer, _õpet,
+ {{0x2366003b,0x27ed98cf,0x7c2998d0,0x35a618d1}}, // šoj_, _been_, keer, _ханг,
+ {{0x69c388fd,0x225818d2,0x442918d3,0xc18c80be}}, // _रणनी, _work_, cea_, סטאָ,
+ {{0x628198d4,0xbea68160,0x225818d5,0x7c2998d6}}, // kilo, падк, _tork_, deer,
+ {{0x6b8907ca,0x98ac880a,0x26c580c3,0xa3b10054}}, // _þega, ılır_, _cilo_, _ओरत_,
+ {{0x7c2998d7,0x628198d8,0x26c598d9,0x645998da}}, // feer, dilo, _dilo_, _bowi,
+ {{0x27ed98db,0x7c2998dc,0x629e18dd,0x67240267}}, // _geen_, geer, lopo, _opij,
+ {{0x6459809a,0x26c598de,0x61ee18df,0xd00f98e0}}, // _dowi, _filo_, _kebl, _حلق_,
+ {{0x442918e1,0x628198e2,0x25a58b64,0x26c589c4}}, // zea_, gilo, _fyll_, _gilo_,
+ {{0x7c298c56,0x764898e3,0x442918e4,0x61ee009a}}, // beer, _indy, yea_, _mebl,
+ {{0xa3e6800f,0x629e18e5,0x7c298a0f,0x27ed01df}}, // _बजट_, hopo, ceer, úen_,
+ {{0xb8d398e6,0x6a600125,0x629e18e7,0x765a98e8}}, // _जी_, _höfu, kopo, _koty,
+ {{0xcad78051,0x628198e9,0x61ee0084,0x20f8026c}}, // _צוות_, cilo, _nebl, kči_,
+ {{0x442918ea,0x765a98eb,0x20f8050b,0x629e0cd9}}, // tea_, _moty, jči_, dopo,
+ {{0xa3d4000d,0x81e900ab,0x4fc70fe6,0xea0000ff}}, // सँग_, _যাই_, _эска, _đạn_,
+ {{0x442918ec,0xe7e30d38,0x261a035a,0x245818ed}}, // [c70] rea_, _क्या_, म्ही_, чать_,
+ {{0x32d2801c,0x765a98ee,0x61fc0122,0x7d0a88ae}}, // _mây_, _noty, _cdrl, kufs,
+ {{0x442918ef,0x80a404c0,0x30158009,0x26c58122}}, // pea_, _زمین, ддер, _rilo_,
+ {{0x2c090077,0x7c2998f0,0x5ee18264,0x9f4b18f1}}, // _بعدی_, xeer, _নিবে, hací_,
+ {{0x7c2998f2,0x61ee18f3,0x2ca918f4,0x67f80019}}, // veer, _febl, mlad_, lújí,
+ {{0x2ca918f5,0xe5a598f6,0x645998f7,0x7c2998f8}}, // llad_, мили, _sowi, weer,
+ {{0x64598d38,0x765a98f9,0x7c2998fa,0x27ed98fb}}, // _powi, _doty, teer, _teen_,
+ {{0x386680f2,0x61e48eef,0x32d2801c,0xccf8801b}}, // ckor_, _đila, _bây_, _dvě_,
+ {{0x7c2998fc,0x06e200c8,0x628198fd,0x32d28028}}, // reer, _বিভি, tilo, _cây_,
+ {{0x7c2998fe,0xe8f998ff,0x32d2801c,0x2ca91900}}, // seer, оло_, _dây_, hlad_,
+ {{0x62818c6e,0x2ca91901,0xa6dd00c8,0x7c299902}}, // rilo, klad_, _বিষয়, peer,
+ {{0x6d419903,0x200f9904,0x06e200ab,0x1de61905}}, // _irla, nggi_, _বিবি, कर्ष_,
+ {{0x28cf80bc,0x32d28028,0x2ca91906,0x6279816b}}, // _संवि, _gây_, dlad_, dňov,
+ {{0x5edd00c8,0x248d9907,0x7cd50110,0x753c01d0}}, // _বিশে, lnem_, _sąra, tvrz,
+ {{0x64a61908,0xddd8811f,0x248d9909,0x6da30fe7}}, // дава, nivš, onem_, вира,
+ {{0x248d990a,0x161f0bb8,0x629e190b,0x4e1f03a4}}, // nnem_, म्बर_, wopo, म्बई_,
+ {{0xa5a485e8,0x64a31444,0x3ced0067,0xf1fa190c}}, // [c80] _चुनौ, тата, ćeva_, قعات_,
+ {{0x9f44803e,0x7cd180eb,0x1e83012f,0x386d8580}}, // _nemá_, _pārl, _альм, _aler_,
+ {{0x629e190d,0x85bb0065,0x2ca9190e,0x20f8190f}}, // ropo, _واپس_, blad_, uči_,
+ {{0x20d38028,0x61ee1910,0x7bcb803d,0x248d9911}}, // _mãi_, _webl, _nggu, jnem_,
+ {{0x248d9640,0x6d419912,0x629e1913,0x38669914}}, // dnem_, _arla, popo, rkor_,
+ {{0x38669915,0x2ca01916,0xa3bd0540,0x7bcb9917}}, // skor_, loid_, ेखर_, _aggu,
+ {{0x386d9918,0x9f4b03cb,0xceb30158,0xa06a9071}}, // _fler_, vací_, דיג_, _дава_,
+ {{0x38b58e23,0x7d0a81ec,0x60cd826c,0xe0438dca}}, // gård_, rufs, _čamp, _инси,
+ {{0x6d419919,0x27e6991a,0xc5f400ab,0xd62680f7}}, // _erla, mbon_, _জানা_, _يعطي,
+ {{0x20d380ff,0x78a4191b,0x27e6811b,0xd90e815b}}, // _bãi_, _akiv, lbon_, لیت_,
+ {{0x248d991c,0xddd88699,0x9f4b03c1,0x20d380ff}}, // bnem_, civš, rací_, _cãi_,
+ {{0x32d28028,0x27e68bfe,0xfe0e8424,0x20d3827d}}, // _tây_, nbon_, _साँस_, _dãi_,
+ {{0x7c22991d,0x80ca87e6,0x2ca9191e,0x2905991f}}, // _ibor, _संके, vlad_, arla_,
+ {{0x2ca91920,0x2005061c,0x6456022c,0x7ea4823e}}, // wlad_, şli_, ejyi, còpt,
+ {{0xda659921,0x6279816b,0x44220cdb,0x200f808b}}, // _ثاني, tňov, _bbk_, yggi_,
+ {{0x8af00086,0x6e3702a3,0x290c9922,0x2ca002c4}}, // yyət, _waxb, muda_, goid_,
+ {{0xfe0e8fd5,0x290c9923,0x7b180198,0x291801b9}}, // [c90] _सांस_, luda_, мотр_, _aqra_,
+ {{0xd5a48117,0x248d9924,0xe3b001a8,0x627984e8}}, // _نہ_, znem_, شرق_, sňov,
+ {{0x7c229925,0x6279826f,0x38b21926,0x7cd181a9}}, // _obor, pňov, lára_, _pārm,
+ {{0xddd8812b,0x5f959927,0x5f008074,0x4422008e}}, // vivš, ниет, _रिश्_, _gbk_,
+ {{0xf6259928,0x248d8353,0x7c2d1929,0x38b5816d}}, // едло, vnem_, mear, vård_,
+ {{0x4439192a,0xddd8812b,0x7c2d192b,0x645d038a}}, // _has_, tivš, lear, _hosi,
+ {{0x44390f29,0x248d8353,0xe5a280e8,0x79a4018b}}, // _kas_, tnem_, лиши, _арте,
+ {{0x645d192c,0x7c2d192d,0x6285192e,0x387f8397}}, // _josi, near, liho, _umur_,
+ {{0x4439022c,0x248d992f,0x9985801b,0x6d4301a1}}, // _mas_, rnem_, telů_, _šnal,
+ {{0x44391930,0x7c2d008c,0x78a40e23,0x38b21931}}, // _las_, hear, _skiv, dára_,
+ {{0x290c9932,0x6d419933,0x60c99934,0x443908dc}}, // guda_, _urla, _kiem, _oas_,
+ {{0x60c98110,0x7aeb81c0,0x6a6d80e1,0x20d38129}}, // _jiem, _twgt, _dúfa, _vãi_,
+ {{0xe28e9935,0x60c985b4,0x442d9936,0x7c2d1937}}, // _ба_, _miem, mee_, dear,
+ {{0x442d9938,0x786605b4,0x60c99939,0x4422193a}}, // lee_, _móvi, _liem, _sbk_,
+ {{0x4439193b,0x6fa0146d,0x645d0114,0x27ff90ab}}, // _bas_, _गुरू, _bosi, _odun_,
+ {{0x4439193c,0x442d993d,0x60c9993e,0x7c2d193f}}, // _cas_, nee_, _niem, gear,
+ {{0x44391940,0x645d1941,0x7c39831d,0x644b80dd}}, // [ca0] _das_, _dosi, _mawr, _mngi,
+ {{0x442d9942,0x27ff85a3,0x6aa1831d,0xae0e809a}}, // hee_, _adun_, nolf, _साइन_,
+ {{0x442d9943,0x26d2022e,0x44391944,0x7c2d1945}}, // kee_, _huyo_, _fas_, bear,
+ {{0x645d0870,0x60c99946,0x7c2d1947,0x78660118}}, // _gosi, _ciem, cear, _bóvi,
+ {{0x765e1948,0x442d9949,0xe818000f,0x3a380110}}, // _kopy, dee_, _थाना_, _tarp_,
+ {{0x4420194a,0x644b994b,0x27ff994c,0x27e6994d}}, // lfi_, _angi, _edun_, sbon_,
+ {{0x442d8135,0x4439194e,0x7ae4194f,0x69ce10ab}}, // fee_, _yas_, lpit, _igbe,
+ {{0x8fa69950,0x442d9951,0x44390069,0x7c3b820d}}, // _забе, gee_, _xas_, gdur,
+ {{0x7ae41952,0x7c399953,0xddcf81d0,0x44200286}}, // npit, _dawr, řešn, ifi_,
+ {{0x60c99954,0x290c9955,0x442009ca,0x644b9956}}, // _ziem, tuda_, hfi_, _engi,
+ {{0xd48f94b7,0x69ce0133,0x06d88264,0xa1949957}}, // _бр_, _mgbe, _সিটি, танч,
+ {{0x290c9958,0x7ae41959,0x7c22995a,0x442d995b}}, // ruda_, kpit, _ubor, cee_,
+ {{0x69dc040e,0x645d195c,0x26d205a4,0xc6f80221}}, // _ofre, _rosi, _cuyo_, ннях_,
+ {{0x62850144,0x2b400824,0x6e2e0061,0xb9068264}}, // xiho, dvic_, gebb, _পি_,
+ {{0x4439195d,0xa8a71264,0x4420195e,0x7c2d195f}}, // _pas_, _прик, ffi_, tear,
+ {{0x44391960,0x69dc1961,0x69ce10ab,0xa01b0106}}, // _qas_, _afre, _agbe, rnös,
+ {{0xe4ed023c,0x7c2d1962,0x60c99963,0x7cd180eb}}, // [cb0] _जबकि_, rear, _riem, _pārk,
+ {{0x44391964,0x60c99965,0x7c2d1966,0x442d9967}}, // _was_, _siem, sear, zee_,
+ {{0x44391968,0x60c99969,0x442d996a,0x645d0364}}, // _tas_, _piem, yee_, _tosi,
+ {{0x443910af,0x6285196b,0x69ce0091,0xdddc0110}}, // _uas_, siho, _egbe, mirš,
+ {{0xd366996c,0x6f1d196d,0xb8d80105,0xab87196e}}, // _ئه_, ntsc, _घी_, _чунк,
+ {{0x442d996f,0x6d4a00f7,0x6f1d0192,0x60c99970}}, // wee_, _áfac, itsc, _wiem,
+ {{0x60c99971,0x442d9972,0x6e3a831d,0x78a2826c}}, // _tiem, tee_, _datb, boov,
+ {{0xdd8e8013,0x20d7007e,0x6e3a82c4,0xa8a49973}}, // توى_, _içi_, _eatb, _брук,
+ {{0x2ba705e8,0x36d5835f,0x442d8051,0x2612154b}}, // _कुमा, _розр, ree_, _ताली_,
+ {{0x6f0f1974,0x39459594,0x442d9975,0xcb1400be}}, // ducc, вног, see_, אלץ_,
+ {{0x442d9976,0x26d21977,0x44201978,0x35558277}}, // pee_, _suyo_, yfi_, _جناز,
+ {{0xcc148077,0x644b9979,0x6e2e03f7,0x60dc802a}}, // _مذهب, _ungi, webb, írma,
+ {{0xa3a8816f,0x6e2e197a,0x69c390a1,0x547a81c6}}, // _खुप_, tebb, _रणवी, _קטנו,
+ {{0x27f202a3,0x2cad8051,0x6d4500dd,0xdee3197b}}, // _weyn_, lled_, _brha, рори,
+ {{0xe5a61401,0x7afd197c,0x6e2e0698,0x6da6018b}}, // тиви, _avst, rebb, тива,
+ {{0x64be0778,0xb4ac016f,0x26d2197d,0x7f44197e}}, // ्देश, गते_, _tuyo_, _triq,
+ {{0x4420197f,0xe3631980,0x7d0e1981,0x6d4502af}}, // [cc0] rfi_, акти, subs, _erha,
+ {{0x2cbf9982,0x2cad801b,0x63ab82c4,0x7ae41983}}, // hmud_, hled_, _mygn, rpit,
+ {{0x2ba70076,0x6e3c9984,0xa3ea02c7,0x7ae41985}}, // _कुठा, _órbi, _одна_, spit,
+ {{0xdee68ba1,0x66e69986,0x248202f7,0x7afb9987}}, // води, вода, _umkm_, ksut,
+ {{0x44fe01e2,0x2cad8428,0x628381d6,0x6e3a89c4}}, // lį_, dled_, _omno, _patb,
+ {{0x24921988,0x2cad822b,0xb7d280ab,0x20ee81d0}}, // lnym_, eled_, ারেট, yři_,
+ {{0x44fe01e2,0xda188076,0x660101fa,0x6e3a9989}}, // nį_, _दाबत_, ólki, _vatb,
+ {{0x2492198a,0x386002be,0x63ab8bc5,0x39470366}}, // nnym_, _noir_, _bygn, ænse_,
+ {{0x63ab8358,0x7afb998b,0xaec68196,0xa7a7998c}}, // _cygn, gsut, _абал, _акта_,
+ {{0x63ab816d,0x44fe0084,0x3860198d,0xf8ca027d}}, // _dygn, kį_, _aoir_, _mẩu_,
+ {{0xd6e200c8,0x2617998e,0x2cad8039,0x38600706}}, // _বিষয, _नाती_, bled_, _boir_,
+ {{0x2cad998f,0x38600635,0xb4ac016f,0x6f0f01e8}}, // cled_, _coir_, गतो_, tucc,
+ {{0x44269990,0xf8bf02be,0x24920dee,0x38600706}}, // _ibo_, rmé_, dnym_, _doir_,
+ {{0x6f1d1991,0x6f0f05dc,0x2c75013c,0xb7e3085d}}, // rtsc, rucc, _måde_, _क्रम_,
+ {{0x26cc9992,0x7cd180eb,0x2bc51993,0x6f1d1994}}, // _mido_, _pāri, लेना, stsc,
+ {{0x48150139,0x91f282f1,0x588701e5,0x38601995}}, // _смис, _आजुओ_, _шыла, _goir_,
+ {{0x201e0086,0x44269996,0x09bd1107,0x9cc801e5}}, // [cd0] əti_, _mbo_, ्ख्य, тыра_,
+ {{0x62889997,0x7afd8510,0x7ae28010,0x99671777}}, // nido, ásti, _mtot, ктел,
+ {{0xdfd18013,0x44269998,0x60cd1999,0x6729999a}}, // ميا_, _obo_, _kiam, _spej,
+ {{0x2c750f06,0x245881ac,0x4426999b,0x60cd199c}}, // _både_, _témy_, _nbo_, _jiam,
+ {{0x60cd199d,0xb8fa199e,0x26cc8135,0x160e816f}}, // _miam, _डू_, _bido_, _साखर_,
+ {{0x4426999f,0x7c3d19a0,0xa3e58beb,0x98b3817b}}, // _abo_, _hasr, बुन_, ıdır_,
+ {{0x62888510,0x261b0665,0xc2120039,0x6ed506b7}}, // dido, _बानी_, _להם_, _यूसु,
+ {{0x394999a1,0x60cd19a2,0x248919a3,0x26cc81df}}, // _čas_, _niam, niam_, _eido_,
+ {{0x408499a4,0x291e99a5,0x89f585a8,0x7c3181a1}}, // _турб, etta_, лянц, _čerč,
+ {{0x628899a6,0x386019a7,0x442699a8,0x7afb99a9}}, // gido, _soir_, _ebo_, rsut,
+ {{0x7afb99aa,0xe73c07d9,0x78bb81c0,0x60cd0176}}, // ssut, _küçü, _khuv, _biam,
+ {{0x60cd01e4,0x05a70105,0x442690ab,0x7c8499ab}}, // _ciam, _गड़ब, _gbo_, _куче,
+ {{0x386006e3,0x7e6199ac,0x60cd19ad,0x248919ae}}, // _voir_, _kolp, _diam, diam_,
+ {{0x62889313,0x443f99af,0x443d99b0,0x26cc8118}}, // cido, ldu_, _haw_, _xido_,
+ {{0x443d99b1,0x443f99b2,0xff1400ab,0x60cd19b3}}, // _kaw_, odu_, িবেশ_, _fiam,
+ {{0x60cd19b4,0x38b90019,0x44fe0110,0x7c3d01b4}}, // _giam, tére_, rį_, _casr,
+ {{0x4bd9176a,0x443d8114,0x422619b5,0x3b071182}}, // [ce0] ться_, _maw_, лдав, лето_,
+ {{0xe74719b6,0x78a619b7,0x443d8039,0x44fe0084}}, // ções_, lokv, _law_, pį_,
+ {{0x090619b8,0x9f520205,0x38b90019,0x26cc99b9}}, // упен, _seyè_, sére_, _rido_,
+ {{0x26cc99ba,0x38608125,0x78bb8548,0x443f816b}}, // _sido_, ðir_, _chuv, jdu_,
+ {{0x443f99bb,0x26cc99bc,0x443219bd,0x7e6199be}}, // ddu_, _pido_, mey_, _bolp,
+ {{0x0f370051,0x628883a8,0x7e6199bf,0x443f99c0}}, // טרנט_, xido, _colp, edu_,
+ {{0xe29707b6,0x60d50661,0xad268c2a,0x443d99c1}}, // _сар_, _guzm, ارتو, _baw_,
+ {{0xdd0401e2,0x443d8069,0x443219c2,0x7c2419c3}}, // ūrėt, _caw_, ney_, nfir,
+ {{0x62889313,0x443d99c4,0x26cc99c5,0xf743932a}}, // tido, _daw_, _tido_, _лето,
+ {{0x60cd19c6,0x59b804e5,0x7e6190dd,0xc3330051}}, // _siam, _अरार, _golp, צות_,
+ {{0x7c2419c7,0xc27b00be,0x291e8009,0x443f82a6}}, // kfir, גריי, utta_, bdu_,
+ {{0xf5378051,0x7cd18029,0x60cd0748,0x628899c8}}, // _תנאי_, _pārv, _qiam, sido,
+ {{0x443202a3,0x7c3d19c9,0x248919ca,0x442480b9}}, // dey_, _rasr, viam_, mfm_,
+ {{0xbb3b8158,0x2489009a,0x7ead80e1,0x35a986a7}}, // _רעגי, wiam_, túpi, _चुड़,
+ {{0x248919cb,0x60cd0069,0x61fa8201,0x7c2419cc}}, // tiam_, _tiam, matl, ffir,
+ {{0x61fa99cd,0x6d4899ce,0x44320079,0x644099cf}}, // latl, _mrda, gey_, ndmi,
+ {{0x248919d0,0x99900289,0x50b8006b,0xf77302e3}}, // [cf0] riam_, _čaša_, _حدود_, _داغ_,
+ {{0x7cd18029,0x61fa99d1,0xbbd186b7,0x443f99d2}}, // _kārt, natl, _हल्क, zdu_,
+ {{0x394719d3,0x443f99d4,0x7c3d19d5,0x7e61867f}}, // íns_, ydu_, _tasr, _rolp,
+ {{0x26179880,0x61fa85b2,0x443219d6,0x644f19d7}}, // _नाही_, hatl, cey_, _unci,
+ {{0x6d4899d8,0xe73999d9,0x443f99da,0x61fa99db}}, // _arda, тел_, vdu_, katl,
+ {{0xa5098fe6,0x8d6580af,0x26dc00be,0x61fa99dc}}, // века_, ивле, עקומ, jatl,
+ {{0xed5999dd,0x443d8e35,0x61fa99de,0x7e6199df}}, // той_, _paw_, datl, _volp,
+ {{0x443f99e0,0x3e7419e1,0xc60680ab,0xf0bb8fd3}}, // udu_, _mäta_, ল্লা_, _سازش_,
+ {{0xa3a8823c,0x443f99e2,0xfc3f07ca,0x290c85a4}}, // _खुद_, rdu_, _maí_, erda_,
+ {{0xc6a799e3,0x61fa99e4,0xea2490ab,0x2bd185fc}}, // _срби, gatl, ẹ́rẹ, _हलका,
+ {{0x443202c1,0x3ea799e5,0x443d99e6,0xf99180f7}}, // yey_, mont_, _taw_, تبة_,
+ {{0x18a60364,0x44320079,0x443f99e7,0x92a60035}}, // рамм, xey_, qdu_, _załą,
+ {{0x61fa899b,0xa3d919e8,0x290c99e9,0xbca499ea}}, // batl, ाशन_, arda_, _امني,
+ {{0x4b7a8158,0x443219eb,0x7ae999ec,0x200c0085}}, // _באנו, wey_, mpet, şdi_,
+ {{0x44320079,0x44f38087,0x8d630615,0xa3dc98a9}}, // tey_, nţ_, овре, तुक_,
+ {{0xb05b19ed,0x673b8bcf,0xd91a8e82,0x261219ee}}, // lmän, _osuj, _סופל, _ताकी_,
+ {{0x443219ef,0x7c2419f0,0x7ae999f1,0x3ea799f2}}, // [d00] rey_, rfir, npet, kont_,
+ {{0x14b8016f,0xab2719f3,0x7bdf01e8,0xf2df00ff}}, // _आठवण, рофа_, acqu, _trân_,
+ {{0xd00f19f4,0x7f498388,0x33200372,0x261b0074}}, // _علی_, _areq, stix_, _बाती_,
+ {{0x03a600e8,0x8c4610ca,0x044619f5,0x09d2801b}}, // _вимо, _теме, _темн, _सल्य,
+ {{0x27e985b4,0x61fa83bf,0x6d48812b,0xbb4300e8}}, // ñana_, yatl, _srda, _деяк,
+ {{0x2913002a,0xa01b0198,0xa3e206ae,0x290c9388}}, // buxa_, riöi, नुर_, yrda_,
+ {{0x61fa99f6,0x442499f7,0xd49a99f8,0x261795bc}}, // vatl, tfm_, кри_, _नारी_,
+ {{0x61fa80dd,0x290c8140,0xa0678dc0,0x7f4999f9}}, // watl, vrda_, _каса_, _freq,
+ {{0x7f4980f1,0xc1788110,0xa06799fa,0x3ced192c}}, // _greq, ybės_, _тата_, ćevi_,
+ {{0xaa668d91,0x7cd180eb,0x09ca00ab,0x672299fb}}, // ртик, _pārt, _ল্যা, mtoj,
+ {{0x61fa99fc,0xfce38c3e,0xd2578e11,0x6d4899fd}}, // ratl, _дохо, рцы_, _urda,
+ {{0x67228110,0x7cd181a9,0x2c78826b,0x61fa99fc}}, // otoj, _vārt, _béde_, satl,
+ {{0x67229608,0x628001e2,0xb7141194,0xab838eef}}, // ntoj, _įmon, одящ, _мушк,
+ {{0xea000104,0x61fa811c,0xfc3f041c,0x67228a14}}, // _đến_, qatl, _saí_, itoj,
+ {{0xb2bb8051,0xa5f819fe,0x28d01862,0xbd680012}}, // _שמור, ресу_, _संचि, арте_,
+ {{0x2c7500f2,0x672299ff,0xdb0f016a,0xb8670019}}, // _båda_, ktoj, _azcá, _خاتو,
+ {{0x4aba8074,0x3a750088,0xaaba8105,0x67228168}}, // [d10] _उठाव, блар, _उठाक, jtoj,
+ {{0x29130118,0x6d4300fe,0x3e740338,0xe7e300ba}}, // tuxa_, _šnau, _täta_, कुरा_,
+ {{0x2ba7000d,0xc5f20039,0xdd911a00,0x61f89a01}}, // _कुरा, _מדי_, _روح_, _mevl,
+ {{0x20190455,0x31578158,0x272086c0,0x67228168}}, // ngsi_, ייבן_, fòn_, ftoj,
+ {{0xf7459a02,0xb4b300d4,0xc8d0016f,0x27209a03}}, // село, टती_, _संघट, gòn_,
+ {{0x2ca9062d,0x78a2803e,0xbb3a00be,0x14b5016f}}, // load_, čova, _געשי, ंतवण,
+ {{0xfbdb0076,0x3ea782be,0x442b1a04,0x28f802c7}}, // _भलाम, ront_, _kbc_, _весь_,
+ {{0x1ee7826a,0x7ae980ce,0x2901004f,0x4adb1a05}}, // _نوری_, tpet, msha_, _बढाव,
+ {{0x7cd18029,0x3ea78019,0x4aa9891d,0x7ae99a06}}, // _pārs, pont_, лкин_, upet,
+ {{0xfc3f800d,0x38bc9a07,0x78ad1a08,0x7aed8085}}, // ží_, ríre_, _akav, _çatm,
+ {{0x61f89a09,0x7ae99a0a,0x186a1324,0xa06a1a0b}}, // _devl, spet, гани_, гана_,
+ {{0xf9920039,0xb1138133,0x764181b4,0x29011a0c}}, // לרי_, _kụzu, _haly, isha_,
+ {{0x248d8341,0x3b098d69,0x76419a0d,0x7205803d}}, // miem_, _село_, _kaly, _هوشم,
+ {{0x78ad00d2,0x248d80eb,0x61f8874c,0xb05b0198}}, // _ekav, liem_, _gevl, lmäl,
+ {{0x442b1a0e,0xceb29a0f,0xd5b21a10,0xd9c580ab}}, // _bbc_, פיל_, سفر_, _শ্রম,
+ {{0x248d9a11,0x672281e2,0xb05b016d,0x6d4a0118}}, // niem_, ytoj, nmäl, _áfam,
+ {{0xe29a041c,0xe3ba1a12,0x045700f7,0xf1bf81d0}}, // [d20] гаа_, уба_, حلقة_, řád_,
+ {{0x6f040e78,0x6f0483f2,0x248d9a13,0x76419a14}}, // _ivic, šick, hiem_, _naly,
+ {{0xfe370051,0x248d9a15,0x2b4b0326,0x64a60084}}, // _פרטי_, kiem_, _ercc_, _гана,
+ {{0x248d8341,0x91fc80eb,0x17fa80f7,0x6f041a16}}, // jiem_, nkār, اراة_, _kvic,
+ {{0x248d8029,0x501b0bea,0x76419a17,0xb4c4009a}}, // diem_, רונו, _baly, एगी_,
+ {{0x67229a18,0x1ae31a19,0xbea39a1a,0xa2e31a1b}}, // rtoj, _хорм, пачк, _хорд,
+ {{0x67228828,0x764181e2,0x261b1a1c,0x394a0580}}, // stoj, _daly, _बासी_, _urbs_,
+ {{0x248d9a1d,0x64440c56,0x7c361a1e,0x67228168}}, // giem_, ndii, neyr, ptoj,
+ {{0xa3a897ba,0x7cd181a9,0x3e799a1f,0x9e658061}}, // _खुश_, _pārr, _sète_, _ڈالن,
+ {{0x9e658013,0xc3330039,0x0665803d,0x78ad1a20}}, // _والن, לוח_, _والپ, _skav,
+ {{0xd0110199,0x6f029a21,0xa3a8852a,0x248d8ec3}}, // ولد_, lsoc, _खुर_, biem_,
+ {{0xc19b0158,0x248d9a22,0x764185ee,0x13b00264}}, // רשטי, ciem_, _zaly, চেয়,
+ {{0x61fe1a23,0x6f029a24,0x64441a25,0x5f940081}}, // kapl, nsoc, ddii, зият,
+ {{0x6f040267,0x64429a26,0x442b0df6,0x29010df6}}, // _dvic, _haoi, _sbc_, ysha_,
+ {{0x90e69a27,0x7c36008b,0xe5c40081,0x61fe1a28}}, // _استن, feyr, зсто, dapl,
+ {{0x38b902be,0xb4b3016f,0x644402d5,0x629c03ed}}, // méro_, टते_, gdii, _ajro,
+ {{0x64429a29,0x5eb800ab,0x290101b4,0xd9f901ae}}, // [d30] _maoi, _ইংরে, wsha_, инац_,
+ {{0x61fe1a2a,0x7f4d1a2b,0x60c09a2c,0x2901005d}}, // gapl, _iraq, _ahmm, tsha_,
+ {{0x6f040168,0x200900b9,0x442b1a2d,0xb05b0198}}, // _zvic, _kdai_, _tbc_, ymäl,
+ {{0x6d400029,0xdd00800d,0xdb0f00e7,0x442b1a2e}}, // āmat, štěn, _lycé, _ubc_,
+ {{0x3ced02a5,0x248d9a2f,0xa3a88105,0x76418110}}, // ćevu_, viem_, _खुल_, _paly,
+ {{0xf1c88bb8,0x999e8065,0x60c0808e,0x248d8035}}, // रधान, hető_, _ehmm, wiem_,
+ {{0x248d8029,0x9f40007b,0x76419a30,0x76439a31}}, // tiem_, _leið_, _valy, rdny,
+ {{0x130982da,0x44291a32,0x6442832f,0x26d30ba3}}, // аний_, nfa_, _caoi, _lixo_,
+ {{0x6442803c,0x290a8065,0x248d9a2f,0x7aed1a33}}, // _daoi, ában_, riem_, npat,
+ {{0x248d8029,0x090580e8,0x2009004f,0x38bc84e8}}, // siem_, опон, _adai_, míra_,
+ {{0xdd9b8153,0x64428068,0x644402a3,0x6f04190f}}, // _сша_, _faoi, ydii, _svic,
+ {{0xc5e90bea,0x43759a34,0x290b9a35,0x64428c49}}, // _יד_, _мулт, čca_, _gaoi,
+ {{0x99980289,0x4429031d,0xdd1e81ac,0xe28e9a36}}, // _marš_, dfa_, _víťa, _жа_,
+ {{0x7c298e5d,0x3e708706,0xa3e586ae,0x7aed066b}}, // lfer, _càth_, बुर_, dpat,
+ {{0x26d304c3,0x7aed0858,0x61fe0214,0xa3c18540}}, // _dixo_, epat, vapl, ंधक_,
+ {{0x13ea0196,0x74161a37,0x26d30ba3,0x6f0480e1}}, // рмай_, تورا, _eixo_, šici,
+ {{0xe1fa0d69,0x26d31a38,0x7aed1066,0x7c36007b}}, // [d40] ига_, _fixo_, gpat, reyr,
+ {{0x44291a39,0xf22186a7,0x63b6804a,0x41b61821}}, // afa_, _माफ़_, _øyne, осет,
+ {{0xda218e18,0x27241a3a,0x649780eb,0x05a99a3b}}, // _मानत_, lön_, rģij, _चुलब,
+ {{0x5ee180c8,0x7c2980f3,0x61fe02d0,0x4429018e}}, // _নিজে, jfer, sapl, cfa_,
+ {{0xee3a8012,0xaf369a3c,0xd00f8199,0x7c299670}}, // инд_, ترات, ئله_, dfer,
+ {{0x6442808c,0x999e8019,0x6f029a3d,0x7c298192}}, // _saoi, zető_, rsoc, efer,
+ {{0x7c299a3e,0x27241a3f,0x7cd180eb,0x61fc1384}}, // ffer, hön_, _pārp, _ierl,
+ {{0x61fc1a40,0x2c78803d,0x61e90bda,0x60c082d5}}, // _herl, _béda_, _đeli, _uhmm,
+ {{0x60dc1a41,0x7a3f8110,0x27241a42,0x61fc1a43}}, // _hurm, kštė, jön_, _kerl,
+ {{0x60dc1a44,0x61fc09a6,0x026a80e8,0x5ee18264}}, // _kurm, _jerl, рший_, _নিচে,
+ {{0x44291a45,0x3a370496,0x64428834,0x8cd61a46}}, // yfa_, חרים_, _taoi, _मंगो,
+ {{0xab2a9a47,0x26dc00ce,0x7c298118,0x6724022b}}, // _вода_, _čvor_, cfer, _bqij,
+ {{0xd90e89d7,0xa09b80be,0xe29a84ae,0x7d1c92f1}}, // ریک_, שיכט, _кад_, _årsb,
+ {{0x29070019,0x628a8234,0x25e601d0,0x63a282f9}}, // ának_, _umfo, जुली_, _ixon,
+ {{0x7e689a48,0x44291a49,0x45d41a4a,0x60dc1a4b}}, // _modp, tfa_, дорс, _nurm,
+ {{0xa01b1a4c,0xd5b80e02,0xe57180be,0x44291a4d}}, // riös, юся_, ײַל_, ufa_,
+ {{0x61fc1a4e,0x44291a4f,0x6d5a1a50,0x3a750088}}, // [d50] _berl, rfa_, ätar, плар,
+ {{0x44291a51,0x4eac80ab,0x656e0061,0x68e301df}}, // sfa_, গগুল, ínhá, índo,
+ {{0x05a98540,0x60dc1a52,0x38690573,0x442900b4}}, // _चुंब, _curm, _koar_, pfa_,
+ {{0x7aed1a53,0x61fc037a,0x60dc1a54,0x63a2808e}}, // ppat, _eerl, _durm, _oxon,
+ {{0x66c01836,0x61fc1a55,0x76451a56,0xdb07861c}}, // söke, _ferl, _kahy, ümüz,
+ {{0x61fc1a57,0x60dc01b4,0x0e648558,0x3e798176}}, // _gerl, _furm, мкін, _bèta_,
+ {{0xc6940158,0x7c2982af,0x60dc1a58,0xf3ff03a7}}, // ואס_, tfer, _gurm, _joão_,
+ {{0x629a9a59,0x320102ed,0x61e50428,0xab6582ac}}, // ento, lahy_, lchl, звил,
+ {{0x7c299a5a,0x61fc084a,0x5f94835f,0xa3ce0107}}, // rfer, _yerl, _житт, षेप_,
+ {{0x61e50635,0x60dc008e,0x320110ba,0x62800196}}, // nchl, _yurm, nahy_, _įmok,
+ {{0x27e084c4,0x850580d5,0x91fc80eb,0xa92281e5}}, // žine_, _روشن, rkāp, ндэл,
+ {{0x7e688efc,0x201d81b9,0x27240009,0x629a99b0}}, // _zodp, ngwi_, tön_, anto,
+ {{0x38690012,0xacaa8117,0xc31980c8,0x31b80526}}, // _doar_, _اپنے_, _তৈরি_, _अर्ध,
+ {{0x27241a5b,0x2b4f826c,0x7645111f,0x9f5d8077}}, // rön_, _irgc_, _cahy, gawé_,
+ {{0x491283bb,0xbea3014c,0xdd921a5c,0x61e51a5d}}, // _थियो_, харк, رور_, dchl,
+ {{0x442f807d,0x61fc1a5e,0x6d4180dd,0x3869010c}}, // _jbg_, _serl, _msla, _goar_,
+ {{0x61fc0455,0x20021a5f,0xd46711b3,0x60dc1a60}}, // [d60] _perl, maki_, чите_, _surm,
+ {{0x20021a61,0x6d4194f0,0x60dc1a62,0x2c71826b}}, // laki_, _osla, _purm, _dádi_,
+ {{0xceb31a63,0x61fc04eb,0x29059a64,0xd5b00065}}, // _איז_, _verl, nsla_, _شہر_,
+ {{0x20021a65,0x38cb8416,0xc6048540,0x64a61a66}}, // naki_, مانی_, रण्य_, _хама,
+ {{0x61fc1a67,0xe81f0006,0x6d419a68,0x60dc1a69}}, // _terl, _भासा_, _asla, _wurm,
+ {{0x442f810b,0x20021a6a,0x2907817f,0x7bd9819d}}, // _abg_, haki_, _ovna_, _agwu,
+ {{0xdb078a56,0x628e1a6b,0x20020041,0xe9a31663}}, // _člán, _imbo, kaki_, _зарп,
+ {{0x64461a6c,0x20020f28,0x2cad83b2,0x399b0039}}, // _jaki, jaki_, goed_, _הילד,
+ {{0x200207d9,0x6d41957a,0x6b841a6d,0x64461a6e}}, // daki_, _esla, nzig, _maki,
+ {{0x64461a6f,0x2a6a1a70,0xb05b0364,0x27e69a71}}, // _laki, _jobb_, mmäi, lcon_,
+ {{0x2bb9053f,0xa6e98028,0x24978077,0xbbb9146d}}, // _आर्थ, _chươ, _کنند_, _आर्क,
+ {{0x64461a72,0x76451a73,0x442f8338,0x63a20035}}, // _naki, _sahy, _gbg_, żony,
+ {{0x60c41a74,0x6b840a0f,0x628e0b81,0x24800168}}, // _dhim, jzig, _ombo, dhim_,
+ {{0x291a1a75,0x2c7181d0,0x64461a76,0x442208dc}}, // kupa_, _rádi_, _aaki, _ack_,
+ {{0x64461a77,0x20021a78,0x7647006a,0xa2c00b04}}, // _baki, baki_, rdjy, वतन्,
+ {{0x628e1313,0x76450397,0x64461a79,0x30a401e2}}, // _ambo, _wahy, _caki, _прыв,
+ {{0x64461a7a,0x3f9e0214,0x4e1c801b,0x76451a7b}}, // [d70] _daki, ştur_, नलाई_, _tahy,
+ {{0x291a0c53,0x6d41008b,0x78af1a7c,0x60c40326}}, // fupa_, ælas, locv, _zhim,
+ {{0x7c229a7d,0x2907001b,0x2509003d,0x4422006a}}, // _ocor, éna_, _برخی_, _fck_,
+ {{0x628e1a7e,0x61e50352,0x386987ca,0xdfd180f7}}, // _embo, schl, ðar_, نيا_,
+ {{0x7c2d1a7f,0x64978162,0x2cad9384,0x36699a80}}, // mfar, nţie, voed_, жало_,
+ {{0x442f9a81,0x443b00b9,0x20021a82,0xed599a83}}, // _sbg_, leq_, zaki_, жок_,
+ {{0xa9341a84,0x20021a85,0x291a1a86,0xdd918019}}, // _перш, yaki_, cupa_, کوں_,
+ {{0xe81f1a87,0x7c2d1a88,0x14bf0c78,0x443b1a89}}, // _भाषा_, nfar, ्षिण, neq_,
+ {{0x200d9a8a,0x20021a8b,0xaca38133,0xddd48088}}, // _idei_, vaki_, _nkụj, čaši,
+ {{0x60c41a8c,0xa6e98028,0x2002054e,0xddc18493}}, // _shim, _phươ, waki_, _colţ,
+ {{0x20021a8d,0x66039a8e,0x19c59a8f,0xa0a601a1}}, // taki_, mank, ьбом, данд,
+ {{0x1620023c,0x60d60201,0x27ff9a90,0x7d1c8257}}, // _बाहर_, _qiym, _jeun_, _årsa,
+ {{0xeaae8012,0x20021a91,0xb9048a74,0x6449831d}}, // _ай_, raki_, _भू_, mdei,
+ {{0x64461a92,0x442d8352,0xa6e98104,0x7c3b9a93}}, // _saki, lfe_, _thươ, leur,
+ {{0xceb281db,0x20021a94,0x6d58017f,0x2a6a1a95}}, // דים_, paki_, _švab, _robb_,
+ {{0x64499a96,0x6b840b90,0x442d9a97,0x7c2d1a98}}, // ndei, tzig, nfe_, gfar,
+ {{0x66039a99,0x64461a9a,0xfc3f026b,0x64978087}}, // [d80] kank, _vaki, _akís_, cţie,
+ {{0x64461a9b,0x7c3b9a9c,0x248000f1,0x6281805d}}, // _waki, heur, shim_, nhlo,
+ {{0x66039a9d,0x64461a9e,0x7c3b83b2,0x6f098216}}, // dank, _taki, keur, _avec,
+ {{0x291a1a9f,0x7d1c1aa0,0x22479aa1,0x7c3b8036}}, // rupa_, murs, _lank_, jeur,
+ {{0x9c7c8499,0x073a9381,0x6f099aa2,0x98a7012b}}, // _juče, تساب_, _cvec, žića_,
+ {{0x44201aa3,0x628e1aa4,0x26c59aa5,0x66039aa6}}, // lgi_, _umbo, _chlo_, gank,
+ {{0x442d9aa7,0x69dc0548,0x7c3b9aa8,0xe81f06ae}}, // ffe_, _igre, feur, _भाला_,
+ {{0x3ae400f2,0x6e3c1aa9,0x27ff9aaa,0x7c3b9aab}}, // _köp_, herb, _geun_, geur,
+ {{0x44201aac,0x22479aad,0x7ae20174,0xcf469aae}}, // igi_, _bank_, íoth, _юнай,
+ {{0x66039aaf,0x64499396,0x22478db1,0x38668338}}, // cank, adei, _cank_, ljor_,
+ {{0x7c3b85f8,0x22479ab0,0x765a9ab1,0x021704de}}, // beur, _dank_, _inty, וחים_,
+ {{0x7d1c1ab2,0xb4d80697,0x1c1d0ebf,0x38669ab3}}, // durs, ादो_, _फाइल_, njor_,
+ {{0x69dc1ab4,0x3f869ab5,0x6e3c1ab6,0x76489ab7}}, // _ogre, nzou_, ferb, _kady,
+ {{0xf98f99f4,0x62819ab8,0x64830006,0xddda800d}}, // ابی_, chlo, _kõig, _potř,
+ {{0xf1aa803d,0x76489ab9,0x7d1c1aba,0xc34880ff}}, // _باشه_, _mady, gurs, _hổ_,
+ {{0x44201abb,0x261b1551,0x66039abc,0x69dc1abd}}, // ggi_, _बाकी_, zank, _agre,
+ {{0x3ec71abe,0x66c000f2,0x443b1abf,0x439404bd}}, // [d90] _особ, söka, req_, ватс,
+ {{0x27ff81b0,0x7648923c,0x68fc0114,0xc34880ff}}, // _seun_, _nady, _bwrd, _mổ_,
+ {{0x6f1d002a,0x332901b9,0x30159ac0,0x48159ac1}}, // lusc, stax_, едер, емес,
+ {{0x6d450108,0x66039ac2,0x765a9ac3,0x8c3d8087}}, // _isha, wank, _anty, noşt,
+ {{0xa3b608fd,0xb4bd009a,0x76489ac4,0xc34880ff}}, // _चुप_, ेगी_, _bady, _nổ_,
+ {{0x68fc0428,0x171c03de,0x64598362,0x764890ba}}, // _fwrd, קווע, _snwi, _cady,
+ {{0x7ce787d9,0x7bc29ac5,0x7c3b90b6,0x3f868118}}, // _bıra, _azou, teur, azou_,
+ {{0x934591c7,0x0b459537,0xc348801c,0x61ea9ac6}}, // ение, енин, _bổ_, _affl,
+ {{0x442d9ac7,0xc3488028,0x6f09825b,0x7c3b9ac8}}, // rfe_, _cổ_, _uvec, reur,
+ {{0x272982ba,0x764883ec,0xaa459ac9,0x66038079}}, // mún_, _gady, хеол, qank,
+ {{0xa3b601ce,0x02db09c8,0x9c7c9123,0x6d451aca}}, // _चुन_, बदुन, _suče, _nsha,
+ {{0x386d8216,0x6e3c02af,0xf09f0580,0x6f1d0037}}, // _joer_, werb, gnà_, fusc,
+ {{0x66019acb,0x6d451acc,0x44200059,0xda0f016f}}, // _kelk, _asha, vgi_, ाणात_,
+ {{0x7d1c1434,0x9c7c992c,0x386d867f,0xb4d81acd}}, // turs, _vuče, _loer_, ाद्_,
+ {{0x387f82bb,0x66019ace,0x7afd1acf,0xed571ad0}}, // _olur_, _melk, _awst, нор_,
+ {{0x69c3803b,0x249f8125,0x6e3c1ad1,0x04431ad2}}, // _izne, nnum_, serb, лесн,
+ {{0x6d451ad3,0xc34880ff,0x249f808b,0xb09b1101}}, // [da0] _esha, _xổ_, inum_, _מיקר,
+ {{0x6d4500f1,0x387f9ad4,0x44201ad5,0xc7d701c6}}, // _fsha, _alur_, sgi_, _אופי_,
+ {{0x387f8358,0x76489ad6,0x386d9ad7,0x7cd180eb}}, // _blur_, _rady, _boer_, _dārz,
+ {{0xd7068767,0x7648820d,0x5f069ad8,0xfbc78277}}, // езди, _sady, езда, _مت_,
+ {{0x27299ad9,0x08e68a49,0x61368065,0x66019ada}}, // gún_, _কিছু_, _külö, _belk,
+ {{0x66018a56,0x7ce782bb,0x3f86802a,0xdd949adb}}, // _celk, _sıra, rzou_, калы,
+ {{0xc348801c,0x44fe001b,0x2ca01adc,0x249f807b}}, // _sổ_, nů_, onid_, fnum_,
+ {{0x249f807b,0x54548991,0x26da1add,0xf8ca00ff}}, // gnum_, твит, _lipo_, _tẩy_,
+ {{0xb05b016d,0x66019ade,0xc7a38139,0x764880e4}}, // lläg, _felk, _битк, _tady,
+ {{0xdee39adf,0xd90e8bca,0x2c7c0511,0x44fe01d0}}, // _софи, میت_, _oído_, ků_,
+ {{0x2d878d38,0xb05b00f2,0x44fe001b,0x2b9100e1}}, // czne_, nläg, jů_, môcť_,
+ {{0xc3488028,0x44fe001b,0x6fd6880a,0xbebb0168}}, // _tổ_, dů_, _gücü, ntëv,
+ {{0x44269ae0,0xb05b01ec,0xfebb01a8,0x98741ae1}}, // _ico_, hläg, كاست_, уляц,
+ {{0x66c0016d,0x6f1d1ae2,0xa01b0192,0x7aef802a}}, // sökn, rusc, chön, ícte,
+ {{0xb606026c,0x5558002e,0x26da0091,0x7ae29ae3}}, // dušč, наря_, _dipo_, _huot,
+ {{0xdcb80698,0x6f1d159c,0xb05b0338,0x6d450299}}, // ещу_, pusc, dläg, _vsha,
+ {{0x291e80df,0x64978087,0x61fb026c,0x386d800b}}, // [db0] luta_, nţia, _đulb, _roer_,
+ {{0x979c004c,0x6d4510af,0x64830006,0x7ae28009}}, // _משחק, _tsha, _sõid, _muot,
+ {{0x291e81dd,0x3b0902a6,0x6d451ae4,0xf1a7153d}}, // nuta_, ssaq_, _usha, хран,
+ {{0x442681e9,0x6f0d017f,0x66018009,0x26da004f}}, // _nco_, _kvac, _selk, _zipo_,
+ {{0x54541ae5,0x7ae281e2,0xf7459ae6,0x66018009}}, // _свят, _nuot, тело, _pelk,
+ {{0x291e9ae7,0x66071ae8,0x27299ae9,0x644d1aea}}, // kuta_, najk, rún_, ldai,
+ {{0x66019aeb,0xd65802f6,0x32059aec,0x291e9aed}}, // _velk, תיות_, valy_, juta_,
+ {{0x66019aee,0x6f0d0503,0x291e9aef,0x3e740799}}, // _welk, _ovac, duta_, _hätt_,
+ {{0x7ae280ab,0x249f807b,0x32059af0,0x5ba99af1}}, // _cuot, rnum_, taly_, ьким_,
+ {{0x291e9af2,0x644d0014,0xc3320039,0x7ae28084}}, // futa_, hdai, סוי_, _duot,
+ {{0x60c98748,0xb05b1af3,0x645d1af4,0x60db804f}}, // _khem, kläd, _onsi, _kium,
+ {{0x3e7400f2,0x32059af5,0x66e61af6,0x26da1af7}}, // _lätt_, saly_, кома, _sipo_,
+ {{0x26da1af8,0x443f9af9,0x6ec58b04,0x80cb80ab}}, // _pipo_, meu_, ितपु, িদপ্,
+ {{0x6da61afa,0x443f9afb,0x291e80a4,0x644b9afc}}, // вига, leu_, buta_, _hagi,
+ {{0xceb28f60,0x291e802e,0x644b9afd,0x6f0d1afe}}, // ייך_, cuta_, _kagi, _evac,
+ {{0x443f809f,0x44fe001b,0x7f8600f7,0x224a00ee}}, // neu_, rů_, _للبن, _sabk_,
+ {{0x644b9aff,0x44fe001b,0x6b899b00,0x443f9b01}}, // [dc0] _magi, sů_, nzeg, ieu_,
+ {{0x645d1b02,0x644b9b03,0x644d1a29,0xaa7b008b}}, // _ensi, _lagi, adai, _stýr,
+ {{0xaca40135,0x44e700ff,0x9c7c8b80,0x68e382df}}, // _amụt, _gõ_, _luča, _jund,
+ {{0x644b9b04,0x60db9b05,0x63a4807b,0xb05b1b06}}, // _nagi, _cium, _þing, rläg,
+ {{0x443f9b07,0x68e39b08,0x16160b86,0x60db9b09}}, // deu_, _lund, _तयार_, _dium,
+ {{0x7bc60db7,0x7ae284fa,0x7c240110,0x291e9b0a}}, // _izku, _ruot, lgir, yuta_,
+ {{0x644b9b0b,0x92de80c8,0xc5d580c8,0x60db8081}}, // _bagi, _তবে_, _স্বপ, _fium,
+ {{0x7c24199f,0x38c884c0,0x291e9b0c,0x443f809f}}, // ngir, _سازی_, vuta_, geu_,
+ {{0x7ae29b0d,0x44269b0e,0xb05b1b0f,0x394781a1}}, // _quot, _vco_, kmär, _asns_,
+ {{0x7ae29b10,0x291e80ad,0x3a279b11,0x68e39b12}}, // _vuot, tuta_, _ccnp_, _bund,
+ {{0x2004820f,0x443f9b13,0x68e39b14,0x38b90019}}, // _kemi_, beu_, _cund, kért_,
+ {{0x7ae2825d,0x291e9b15,0x443f9b16,0x200480f1}}, // _tuot, ruta_, ceu_, _jemi_,
+ {{0x6b898063,0xda3491e9,0x291e9b17,0x644082d0}}, // czeg, _белы, suta_, memi,
+ {{0x64409b18,0x27e000ad,0x291e8870,0xd9548277}}, // lemi, _egin_, puta_, _منتخ,
+ {{0x3e7404b8,0x660700d2,0x644b9b19,0x7bc60cdb}}, // _rätt_, rajk, _yagi, _azku,
+ {{0x64409b1a,0x3e7404b8,0x200480a4,0x7c241b1b}}, // nemi, _sätt_, _nemi_, ggir,
+ {{0x4ea71b1c,0xe3740196,0x644d1b1d,0x5a351354}}, // [dd0] _арна, алты, rdai, лнат,
+ {{0x60c99b1e,0x443f9b1f,0xf8bf1b20,0x64409b21}}, // _shem, zeu_, llé_, hemi,
+ {{0x60db9b22,0x60c981c0,0x39151b23,0x7bc6011b}}, // _pium, _phem, умер, _ezku,
+ {{0x2bae12ee,0x443f823e,0x33200722,0x645d1b24}}, // _झुका, xeu_, buix_, _unsi,
+ {{0x644b9b25,0x443f9b26,0x20049b27,0x60db804f}}, // _ragi, veu_, _demi_, _vium,
+ {{0x644b9b28,0x9c7c805c,0xe5a595b1,0x2004826b}}, // _sagi, _ruča, лили, _eemi_,
+ {{0x644b9b29,0x443f9b2a,0x60c99b2b,0x78a29b2c}}, // _pagi, teu_, _them, bnov,
+ {{0x64409b2d,0x68e38558,0x69c7135a,0x332d84b7}}, // gemi, _rund, _izje, ttex_,
+ {{0x443f9b2e,0x2cb9008e,0x91e59b2f,0xbebb0168}}, // reu_, _mksd_, лопе, ntës,
+ {{0x443f9b30,0x68e39b31,0x644b9b32,0x20049b33}}, // seu_, _pund, _wagi, _zemi_,
+ {{0x644b9b34,0xab5b0065,0xe4e7835f,0x6b899b35}}, // _tagi, _szül, _різн, szeg,
+ {{0x9b458277,0xa35592dc,0x68e38982,0x672d0372}}, // _منصو, _مختص, _vund, _bqaj,
+ {{0x9cd68039,0xbebb00f1,0x68e39b36,0x6d571b37}}, // _עונה_, jtës, _wund, _arxa,
+ {{0x68e39b38,0x78a29b39,0x539a83c8,0x6d5c81a8}}, // _tund, znov, _אינו, _árac,
+ {{0x9c7c811f,0x78a297ff,0x64978087,0xf8bf1b3a}}, // _mučn, ynov, nţio, blé_,
+ {{0xb05b0074,0x2121008e,0xdd8f01a8,0x672d0197}}, // smär, duhh_, سوق_, _fqaj,
+ {{0x74130077,0x20048301,0xb05b016d,0x78a29b3b}}, // [de0] صولا, _remi_, pmär, vnov,
+ {{0xddb615a9,0x64409b3c,0x7c241b3d,0xe3b7146c}}, // _محجب, zemi, sgir, убу_,
+ {{0x64409b3e,0x7a7a80be,0x7c240980,0xdce801a9}}, // yemi, ערעס, pgir, ēlēt,
+ {{0xe3a78077,0xdee6997b,0x644082f9,0xd70a9b3f}}, // _هر_, годи, xemi, енде_,
+ {{0xb8eb058c,0x78a29b40,0x2bba00f7,0x9c7c811a}}, // _री_, rnov, _ساحة_, _bučn,
+ {{0x91e3091c,0x09e30652,0x7d1c9b41,0x69dc8ec3}}, // _горе, _горн, _årsk, žrei,
+ {{0x644087d9,0xafdb0aa2,0x20f3026c,0x3a2a0267}}, // temi, ndør, _aćif_, _icbp_,
+ {{0x64978087,0x78a2816b,0x3ae98129,0x115b03de}}, // sţin, čovs, _búp_, _אדלע,
+ {{0x6d580da8,0xb5fc8609,0x3ae98129,0x543b03de}}, // _šval, _anġe, _cúp_, געפא,
+ {{0x7ae61b42,0x86c681a8,0x67228084,0x64409b43}}, // _mukt, _صيان, muoj, semi,
+ {{0x27e98353,0x64409b44,0x3cf800e1,0x64978087}}, // žane_, pemi, jprv_, cţio,
+ {{0xf99304de,0x64978087,0x81d600ab,0x1fdf064a}}, // מרת_, nţil, _সভা_, नखेड,
+ {{0x44ea9b45,0x67228110,0xf4878065,0x26de9b46}}, // _hù_, nuoj, _حالی, _kito_,
+ {{0xc7b382f6,0x81d49b47,0xe45f0106,0xb05b1b48}}, // מבר_, _колх, _snö_, fläc,
+ {{0x629a9b49,0x7ae61b4a,0x44ea877f,0x6ca71b4b}}, // mito, _aukt, _jù_, граж,
+ {{0x44ea9b4c,0x7ae6085c,0x26de9b4d,0x1f750a0e}}, // _mù_, _bukt, _lito_, _влия,
+ {{0x44ea8091,0x3a3807cf,0x26de8548,0x3a2a0088}}, // [df0] _lù_, _bbrp_, _oito_, _bcbp_,
+ {{0x44ea83d3,0x26de9882,0x195911e9,0x7ae61b4e}}, // _où_, _nito_, казы_, _dukt,
+ {{0x60cd1b4f,0x9c7c9b50,0x81b680ab,0x2121008e}}, // _kham, _ručn, চের_, uuhh_,
+ {{0xb4fa0158,0x26de9b51,0x89340013,0xa565803d}}, // _שפרי, _aito_, إعلا, یگان,
+ {{0x629a9b52,0xde059b53,0x2489198d,0xe758804a}}, // kito, апли, mham_, лиці_,
+ {{0x44ea9b4c,0x644f1b54,0x629a9b55,0x24891b56}}, // _bù_, _haci, jito, lham_,
+ {{0x26de82b8,0x200b1b57,0x44ea9b58,0x6f1d01ec}}, // _dito_, kaci_, _cù_, hrsc,
+ {{0x644f1b59,0xaf349125,0x24891b5a,0x44ea9b5b}}, // _jaci, _گرفت, nham_, _dù_,
+ {{0x200b003b,0x26de80a4,0x9c7c9b5c,0x6b8d1b5d}}, // daci_, _fito_, _tučn, nzag,
+ {{0x644f1b5e,0x68e700f1,0x60cd0518,0x659592b8}}, // _laci, _kujd, _aham, _казу,
+ {{0x76439b5f,0xf8661594,0x44391b60,0x644f0118}}, // meny, авно, _obs_, _oaci,
+ {{0x44391b61,0x60cd1b62,0x442b1b63,0x23608267}}, // _nbs_, _cham, _ncc_, _šije_,
+ {{0x60cd1b64,0x24891916,0x6b8d017f,0xdee61631}}, // _dham, dham_, jzag, роги,
+ {{0x44391b65,0x78a98582,0x76438365,0x96200035}}, // _abs_, _ljev, neny, बलेट_,
+ {{0x442b1b66,0x7c961b67,0x75239b68,0xf4961b69}}, // _bcc_, арац, nunz, араю,
+ {{0x3d0880cf,0x44391b6a,0x644f1b6b,0x24891b6c}}, // _सबसे_, _cbs_, _caci, gham_,
+ {{0x4439011b,0x644f1b6d,0x76439b6e,0x0206028b}}, // [e00] _dbs_, _daci, keny, изан,
+ {{0x442b0098,0xe1f700bf,0x75238ab4,0x61e380ee}}, // _ecc_, игу_, kunz, _jgnl,
+ {{0x2bb80105,0x26de809c,0x644f1162,0xfe3700be}}, // _आडवा, _rito_, _faci, _דריי_,
+ {{0x26de8698,0x60cd1b6f,0x6288820f,0x629a9b70}}, // _sito_, _xham, zhdo, zito,
+ {{0x78a982fd,0x26de9b71,0x44ea9b72,0x629a9b73}}, // _djev, _pito_, _sù_, yito,
+ {{0x75239b74,0x629a8118,0x76439b75,0x67228110}}, // funz, xito, geny, ruoj,
+ {{0x6d581b76,0x629a9b77,0x3218809a,0xe9671b2f}}, // _švaj, vito, órym_, _карл_,
+ {{0xc0e31421,0x7c360789,0x26de804f,0x644f03a8}}, // _фотк, nfyr, _wito_, _xaci,
+ {{0x8aa69b78,0x629a9b79,0x76439b7a,0x26de9b7b}}, // ирод, tito, beny, _tito_,
+ {{0x32078f4c,0x60cd1b7c,0xab5b0065,0xc3330bea}}, // _ceny_, _sham, _szük, קות_,
+ {{0xa3d80076,0x2d968d91,0x60cd005d,0x68e08059}}, // ाधड_, _трас, _pham, _kimd,
+ {{0x629a9b7d,0x66088041,0x859b81c6,0x5faa8072}}, // sito, _hedk, _בשבו, _कशाल,
+ {{0xc7c7117f,0x2bd29b7e,0x3eb81706,0x4fc70071}}, // рсни, देशा, fort_, рсна,
+ {{0x644f1b7f,0x6f1d1b80,0x6d5a8f28,0x60c281f6}}, // _saci, rrsc, _krta, hlom,
+ {{0xa3ea853e,0x241901bb,0x60cd1b81,0x200b1b82}}, // _मला_, ловы_, _tham, paci_,
+ {{0x6d5a805c,0x60cd1b83,0x320c8831,0x3d180fd5}}, // _mrta, _uham, lady_, _फिरे_,
+ {{0x644f1b84,0x2284016d,0x24868282,0xa3ca816f}}, // [e10] _vaci, _söka_, _mlom_, ळेच_,
+ {{0x6d5a82bb,0x78a9920e,0x78bb939c,0x62868168}}, // _orta, _sjev, _skuv, ëkoh,
+ {{0x78a98499,0xbebb00f1,0x27e482f7,0x644f1b85}}, // _pjev, rrëd, _bgmn_, _taci,
+ {{0x76439b86,0x320c84e8,0x60c29b87,0x442b1b88}}, // weny, hady_, glom, _ucc_,
+ {{0x44291b89,0x6d5a8b0b,0xfd4c80ff,0x78a985f3}}, // mga_, _arta, _thiể, _vjev,
+ {{0x75238b15,0x528500f7,0x6d5a80dd,0x7bda0039}}, // tunz, _السك, _brta, _תקשו,
+ {{0x6d5a805c,0x76439b8a,0x44291b8b,0x24869b8c}}, // _crta, reny, oga_, _blom_,
+ {{0x44291b8d,0x76439b8e,0x68e08110,0x78bb9b8f}}, // nga_, seny, _gimd, _ukuv,
+ {{0x291e85a4,0x6d5a9b90,0x75239b91,0x6d5c9b92}}, // erta_, _erta, sunz, _áran,
+ {{0xf67480f7,0x6d5a826c,0x752380b4,0x25478061}}, // _والخ, _frta, punz, _től_,
+ {{0xd9459b93,0xb4e60321,0x3eb81b94,0xd00a1b95}}, // щени, _पढे_, vort_, лене_,
+ {{0x3eb802af,0xb7bd802e,0x20090162,0xb5fc8372}}, // wort_, _acţi, _ceai_, _inġa,
+ {{0xd12e84c0,0x44291a21,0x42d580e8,0x291e9b96}}, // ومی_, dga_, _ліку, arta_,
+ {{0x201e1b97,0x44291b98,0x20090362,0x24869b99}}, // şti_, ega_, _eeai_, _zlom_,
+ {{0xe8f59b9a,0x291e8289,0x22459b9b,0x2d7e042b}}, // _استخ, crta_, melk_, _oćeš_,
+ {{0x44291b9c,0x27e08796,0xb05b1b9d,0xdb23807b}}, // gga_, žini_, llän, _þróu,
+ {{0xf8b31b9e,0x3eb81b9f,0x7afb9ba0,0x24801ba1}}, // [e20] _משה_, port_, nput, rkim_,
+ {{0x34d3809a,0xb05b1614,0xa96a1ba2,0x3d0882f1}}, // दगुद, nlän, лиза_, _सबले_,
+ {{0x7c299ba3,0x6608807a,0xc6930039,0x7afb89da}}, // kger, _redk, _מאז_, hput,
+ {{0x660e01b4,0x53a6161a,0x7c2981ed,0x0aea01a8}}, // labk, _гамб, jger, _مرسي_,
+ {{0x60c2815d,0x68fb1ba4,0xb05b0338,0x8c468085}}, // rlom, _čude, klän, qişə,
+ {{0x44ee0104,0xfd650028,0x6d5a808e,0xacf89ba5}}, // _ký_, _nguồ, _prta, анку_,
+ {{0x99860307,0x27e9803a,0xb05b016d,0x5d7a00be}}, // _الأو, žana_, dlän, טאַק,
+ {{0x7c299ba6,0x629e1ba7,0x6d5a9ba8,0x66089ba9}}, // gger, lipo, _vrta, _wedk,
+ {{0x44ee0104,0xeaf78077,0xc445803d,0x320a01ac}}, // _lý_, یریت_, زیون_, _keby_,
+ {{0xaa669baa,0x27328087,0x33770039,0x39149bab}}, // стик, mân_, צעים_, омор,
+ {{0x44291bac,0x27329bad,0x6d5a8102,0x44ee007b}}, // yga_, lân_, _urta, _ný_,
+ {{0x320c9bae,0x4429002a,0x9c7c8115,0x629d8036}}, // sady_, xga_, _mučk, ésor,
+ {{0x629e1baf,0x320c8690,0x9c7c8805,0xeb33804e}}, // kipo, pady_, _lučk, _فروخ,
+ {{0x442902a3,0x62819bb0,0x249900b9,0xacf91bb1}}, // wga_, cklo, _smsm_, ингу_,
+ {{0x40351056,0x787f00f1,0x629e1bb2,0x7a538591}}, // _дейс, _lëvi, dipo, _رضوا,
+ {{0x44291bb3,0xce330077,0xcd02809a,0xa0a400a9}}, // uga_, _کودک, mość_, _најд,
+ {{0x4429078a,0x660e1bb4,0x320a1bb5,0x3a7515b7}}, // [e30] rga_, babk, _beby_, олар,
+ {{0x9e071bb6,0x660e02a3,0x2eb40074,0x5b7b80be}}, // _учил, cabk, ंकृत, ארמא,
+ {{0xcd02809a,0x320a1bb7,0x9c7c811f,0x7c298be7}}, // ność_, _deby_, _vučj, yger,
+ {{0x50c90107,0xf8c90bb8,0x657a148c,0xdbd982df}}, // रतिष, रतिय, äthe, nção,
+ {{0x7aed0609,0x2ca91bb8,0x320a008e,0x62819bb9}}, // qqat, mnad_, _feby_, yklo,
+ {{0x45199bba,0xcd02809a,0xee3880e8,0x9c7c80c3}}, // ация_, kość_, йні_, _fučk,
+ {{0x20c9016f,0x7bc29bbb,0x648306ae,0x22458ec3}}, // रताध, _ayou, _lõim, velk_,
+ {{0xa2b90a0d,0x2ca91bbc,0x23608267,0x7c29823e}}, // ्तव्, nnad_, _šija_, uger,
+ {{0x2d8c157a,0xb05b148c,0xf09f023e,0x200f9bbd}}, // údez_, tlän, dià_, magi_,
+ {{0x200f9bbe,0x7afb9bbf,0x7c29996d,0x78ad07b8}}, // lagi_, rput, sger, _ajav,
+ {{0x684616de,0xb05b1bc0,0xe0461bc1,0x62819bc2}}, // онна, rlän, онни, rklo,
+ {{0x660e010b,0x6d419bc3,0x200f8074,0x61fc880a}}, // wabk, _ipla, nagi_, ırla,
+ {{0x61c6024c,0x3f841bc4,0xc1c6024c,0x3b09944c}}, // _वर्ष, ámu_, _वर्ग, _тело_,
+ {{0x200f9882,0x7c2f1bc5,0x96ba069b,0x3f8c0035}}, // hagi_, _accr, руму_, ądu_,
+ {{0xa50a0ae7,0x649a1bc6,0x7ae39bc7,0x200f9bc8}}, // реба_, атар_, _iint, kagi_,
+ {{0x90c31056,0x9c7c811f,0x660e1bc9,0x248d9bca}}, // _обще, _ručk, sabk, nhem_,
+ {{0x200f8006,0x7ae39bcb,0x8c3d8201,0xab2a1bcc}}, // [e40] dagi_, _kint, mişd, роза_,
+ {{0x7ae39bcd,0x6d419bce,0x9c7c9bcf,0x66c01bd0}}, // _jint, _opla, _pučk, töku,
+ {{0x629e1bd1,0x6d418069,0xdb060106,0x443d82ec}}, // ripo, _npla, _nykö, _obw_,
+ {{0x7ae391d6,0x629e1bd2,0x69c3831d,0x442f83a8}}, // _lint, sipo, _myne, _ncg_,
+ {{0xa2cb06bf,0x7ae39bd3,0x052680ab,0x249f9bd4}}, // _तीव्, _oint, _মনের_, dium_,
+ {{0x7ae39bd5,0x0576845b,0x63a481e2,0xddc18353}}, // _nint, قاعد, _žino, _dolž,
+ {{0xa2b900bc,0x200f9bd6,0x443d9bd7,0x442f838a}}, // ्तर्, bagi_, _bbw_, _bcg_,
+ {{0x69ce1bd8,0x6d5c9bd9,0x68e41bda,0x7aeb9bdb}}, // _izbe, _áram, _niid, _bugt,
+ {{0x0a6a80c4,0xcd02809a,0x926a9bdc,0xc4d30039}}, // арни_, wość_, арна_, וגה_,
+ {{0x7ae38859,0x539b0bea,0x34a708fd,0xd90e87d2}}, // _cint, ייפו, _गद्द, نیت_,
+ {{0x7ae39bdd,0x9c7c842b,0x68e41bde,0x2b4200b9}}, // _dint, _luči, _biid, _lpkc_,
+ {{0x68e402c1,0x8c468628,0x7ae39bdf,0x248d9be0}}, // _ciid, _феде, _eint, chem_,
+ {{0x98b3811f,0x68e41be1,0x629c1be2,0xf09f0722}}, // žeća_, _diid, _omro, rià_,
+ {{0x64830665,0x7d1c9be3,0x7ae39be4,0x6d5e008e}}, // _võim, _årss, _gint, _brpa,
+ {{0xd49b1be5,0x2d85027f,0x66cd81ac,0x29119be6}}, // ара_, ále_, núka, ázat_,
+ {{0xd7fb0698,0x7ae39be7,0xbca501a8,0x7aeb8c27}}, // _тук_, _zint, أمري, _yugt,
+ {{0x2d850289,0x7de705a8,0x5334047f,0x6d5e02d5}}, // [e50] šle_, _мінд, метт, _erpa,
+ {{0xe7f90076,0x64830006,0x249f9be8,0xa3e70d86}}, // ंडवा_, _kõik, zium_, _पलक_,
+ {{0x7583004e,0xdcef01a9,0x37f881bc,0x2ca91be9}}, // _غیرم, ēdēt, _ịkụz, pnad_,
+ {{0x92ab00c8,0x27e900dd,0xa2cb0aed,0x66c00106}}, // খতে_, _jgan_, _तीर्, sökt,
+ {{0x200f9bea,0x69ce1beb,0x64830074,0xae1f123a}}, // ragi_, _ezbe, _lõik, _बयान_,
+ {{0xe7e3146d,0x442f9bec,0x7bda0039,0x200f9bed}}, // _गणना_, _scg_, _לקרו, sagi_,
+ {{0x7ae39bee,0x442f8118,0x200f8c2e,0x8c3d9bef}}, // _rint, _pcg_, pagi_, yişd,
+ {{0x67228025,0x27e91bf0,0x69c38035,0x8c3d82d0}}, // broj, _ngan_, _ryne, kişe,
+ {{0x69c386be,0x68e418c2,0x248d91bf,0xdd918019}}, // _syne, _riid, rhem_, بوں_,
+ {{0x27e9035a,0x248d80f1,0x7c2d0114,0x68e41bf1}}, // _agan_, shem_, hgar, _siid,
+ {{0x63ad9bf2,0x648e8362,0x7c2d0299,0x8c3d829a}}, // _žand, _bùid, kgar, tişd,
+ {{0x2eb60d72,0x628a8019,0xdb0f01b3,0x7aeb9bf3}}, // ृत्त, _elfo, _excé, _tugt,
+ {{0xe28e8abe,0x44f180a9,0x6d5e173d,0x68e40406}}, // _за_, _há_, _srpa, _viid,
+ {{0x442d854f,0x27e91bf4,0x44f190ab,0x7c3b9bf5}}, // lge_, _egan_, _ká_, lfur,
+ {{0x44f19bf6,0x2ca002f9,0xf4840180,0x68e41bf7}}, // _já_, tiid_, واری, _tiid,
+ {{0x7c2d1bf8,0x7c3b9bf9,0x768f804a,0xb5fc822b}}, // ggar, nfur, _høyd, _anġl,
+ {{0x44f19bfa,0x442d9bfb,0x20d200f7,0x2ca01bfc}}, // [e60] _lá_, ige_, ráid_, riid_,
+ {{0x9c7c8eef,0x7c2d01e4,0x394302f7,0x20d201a8}}, // _vuči, agar, _bpjs_, sáid_,
+ {{0x44f19bfd,0xd7fa8530,0x66c0016d,0x867b0039}}, // _ná_, бул_, söks, _הריו,
+ {{0x67229bfe,0x9c7c826c,0xc8799bff,0x6aa181ec}}, // troj, _tuči, _coş_, hilf,
+ {{0xd00f9b9a,0x27e08009,0x442d8039,0x44f19c00}}, // اله_, äin_, dge_, _aá_,
+ {{0x44f18324,0x628504b8,0x629c1c01,0x67229c02}}, // _bá_, ckho, _umro, rroj,
+ {{0xd94312bc,0x44f19c03,0x7c3b9c04,0x61430a8e}}, // _пери, _cá_, ffur, _пера,
+ {{0x44f19c05,0x442d9c06,0xd00f866e,0x61fb0b80}}, // _dá_, gge_, _ملف_, _đuli,
+ {{0x3d088b9f,0x76900198,0x6aa18789,0x7ce79238}}, // _सबके_, _käyd, filf, _sırt,
+ {{0xaabe1c07,0x44f19c08,0x442d9c09,0x7c2d1c0a}}, // ्तिक, _fá_, age_, zgar,
+ {{0x44f19c0b,0xe8e0001c,0x7c2d1c0c,0x6f0981ec}}, // _gá_, _ngồi_, ygar, _zwec,
+ {{0xab2a96fe,0xd90e803d,0x64830074,0x7c3b8122}}, // _года_, پیک_, _võik, cfur,
+ {{0x2bdb800f,0xaabe14a7,0x7ce78214,0x4df083eb}}, // मेदा, ्ताक, _fırs, _चलाई_,
+ {{0x68fc00eb,0x1df80a14,0x2ee50192,0xddc5128a}}, // _otrd, зеры_, ölf_, _zahř,
+ {{0x291c0065,0xd23b8039,0xdb1d01d0,0x7e6880ee}}, // ával_, _הגול, _vysí, _mndp,
+ {{0x7c2d1c0d,0x5d541c0e,0xd1388196,0xb4fb8e82}}, // ugar, экст, mtą_, מפלי,
+ {{0x7c2d1c0f,0x64978162,0x768f80e8,0x386901a8}}, // [e70] rgar, cţiu, _høye, _inar_,
+ {{0x09e080c8,0x7c2d1c10,0xedf88c1c,0x6ce700e8}}, // _ম্যা, sgar, ुरोध_, _німе,
+ {{0x349502cb,0xc5fb1094,0xd1388110,0x62851c11}}, // _жанр, ्रीय_, ntą_, rkho,
+ {{0x44f19c12,0x9c7c88ae,0x78a280e5,0xdbf181d0}}, // _rá_, _kuču, giov, třík,
+ {{0x291804ae,0x2d8301e2,0xb8f406a7,0x44f19c13}}, // _evra_, lyje_, _सी_, _sá_,
+ {{0x44f19c14,0xd1388110,0x61ea810c,0xb05b1c15}}, // _pá_, ktą_, _ngfl, fläk,
+ {{0x442d8813,0x2d830110,0x38690f3e,0xe61099ea}}, // tge_, nyje_, _onar_, اشه_,
+ {{0x44f19c16,0xdbf1801b,0x442d9c17,0x78a29c18}}, // _vá_, přík, uge_, ciov,
+ {{0x44f1877f,0x6aa19c19,0x7c3b9c1a,0x26c7816b}}, // _wá_, tilf, rfur, plno_,
+ {{0x44f19301,0x6d451c1b,0x66020d11,0x38691c1c}}, // _tá_, _opha, _đoko, _anar_,
+ {{0x6fd9816f,0x386900dd,0x6d450282,0xbebb03ed}}, // बेरं, _bnar_, _npha, trën,
+ {{0xc689093f,0xbe2500ab,0x6724817f,0x768b0214}}, // _דא_, ম্মদ_, šije, _rüya,
+ {{0x6d4501f6,0x2cad9c1d,0x38b904e8,0x3ea31799}}, // _apha, mned_, néry_, fijt_,
+ {{0x2cbf9c1e,0xdce38084,0xcb1281c6,0xdd920180}}, // loud_, tynė, _ילד_, پور_,
+ {{0xdee30aac,0x7afd1c1f,0x66e31c20,0xe810800d}}, // тори, _atst, тора, ारमा_,
+ {{0x69da0073,0x68ee0326,0x2cad9c21,0x78a2802a}}, // _útei, _rubd, nned_, xiov,
+ {{0x60c41c22,0x7ae71c23,0xaca3819d,0x6d450234}}, // [e80] _ikim, _bijt, _nfọm, _epha,
+ {{0x2cbf85f8,0x7bc61c24,0xab5b0019,0x68ee1c25}}, // houd_, _vyku, _gyüm, _pubd,
+ {{0xceb30158,0xbebb0168,0x63a9007b,0x994a04a3}}, // _ביז_, ytëz, _þenn, تلال_,
+ {{0x2a698326,0x7aef0493,0x672b9c26,0xb4b781d0}}, // _ɗaba_, _fuct, fugj, चको_,
+ {{0x7e7a803a,0x78a29c27,0x76900009,0x3b04026b}}, // _potp, riov, _täyd, _dépò_,
+ {{0xd1389c28,0xa9261c29,0x6b840db1,0x6eeb0197}}, // ytą_, _одгл, myig, _użbe,
+ {{0x98a68e17,0x25078061,0x68fc0824,0x60c403f7}}, // _живе, ورٹی_, _utrd, _okim,
+ {{0x2cad9c2a,0x22581c2b,0x628e00e1,0x3d1103b7}}, // gned_, _hark_, _hlbo, _तबसे_,
+ {{0x6b840f18,0xb05b0338,0x628e1c2c,0x752a8326}}, // nyig, släk, _klbo, sufz,
+ {{0x6f0285a4,0x60c41c2d,0x38691c2e,0x32549138}}, // mpoc, _akim, _snar_, хвор,
+ {{0x29189c2f,0xaca4019d,0x22580039,0x6f028118}}, // éra_, _anọt, _mark_, lpoc,
+ {{0xd1389c30,0x60d6031d,0x2d830196,0x3af29c31}}, // rtą_, _chym, vyje_, _tâp_,
+ {{0xd1388d42,0x27ed1874,0x649798ad,0x00000000}}, // stą_, ženo_, rţit, --,
+ {{0x60c41c32,0x2bd5015c,0x2d830110,0x9c7c8da8}}, // _ekim, _दरबा, tyje_, _vuču,
+ {{0x7aef81ac,0x7aef1c33,0x68fb135a,0x6d581c34}}, // íctv, _suct, _čudo, _švar,
+ {{0x628e1c35,0x76570079,0x961480ab,0x2d830110}}, // _albo, _waxy, সলিম_, ryje_,
+ {{0x3f4f04b7,0x628e016b,0x7ebf01d0,0x3ea3090d}}, // [e90] _użu_, _blbo, tápě, pijt_,
+ {{0xdddc00d2,0x2d830084,0x6f0d00b4,0x2fc902c4}}, // skrš, pyje_, _iwac, _iyag_,
+ {{0x6d450234,0x22581c36,0x66151c37,0x6b841a14}}, // _upha, _dark_, mazk, ayig,
+ {{0x3869840e,0xc87982bb,0x8c3d8201,0x6f0d1c38}}, // ñar_, _kişi_, kişa, _kwac,
+ {{0x59d40e18,0x7afd068f,0x24891c39,0xfc3f0061}}, // _थरथर, _utst, mkam_, _amíg_,
+ {{0x8d558425,0x644283c9,0xed599c3a,0xaa450009}}, // етич, _aboi, зок_, ävää_,
+ {{0xa2c282f1,0xaabe1c3b,0x20e8807e,0x3f898084}}, // रकन्, ्तरक, _işin_, šau_,
+ {{0x44f51c3c,0xa3ba8b9f,0x24891c3d,0xa2b90ec5}}, // _må_, _आँख_, nkam_, ्तक्,
+ {{0x27ed9c3e,0x44f50aa2,0x2cbf9c3f,0x2cad8039}}, // _igen_, _lå_, roud_, rned_,
+ {{0x2cbf9c40,0x644d1c41,0x17fa1c42,0x00000000}}, // soud_, heai, ूर्व_, --,
+ {{0x44f50cde,0x68e98063,0xa969813a,0xaca38870}}, // _nå_, _kied, чина_, _ajụj,
+ {{0xf1b203c8,0x228082d6,0x6f0d0314,0x2bb88a74}}, // רסט_, _bòks_, _bwac, _उँचा,
+ {{0xeb8e891e,0x68e99c43,0x66150748,0x6f0d05ee}}, // _си_, _mied, fazk, _cwac,
+ {{0x52398158,0x443f9c44,0x68e99c45,0x6615011e}}, // _זײַנ, lfu_, _lied, gazk,
+ {{0x49bb83f8,0xfaff020f,0x04460468,0x64599c46}}, // _باشد_, _atë_, невн, _kawi,
+ {{0x44f51c47,0x68e99c48,0x63a48110,0x60c40010}}, // _då_, _nied, _žini, _ukim,
+ {{0x6d4107d9,0x64598010,0x22581c49,0xfc3f0032}}, // [ea0] çlar, _mawi, _park_, _ajís_,
+ {{0x44f50082,0x78a61c4a,0x27ed8590,0x77641c4b}}, // _få_, likv, _agen_, _irix,
+ {{0x44f50082,0x68e99c4c,0xe5a6028b,0x35c486a7}}, // _gå_, _bied, _чини, _लुढ़,
+ {{0x7e7e0502,0x6459809a,0x78a61c4d,0xb5fc822b}}, // _hopp, _nawi, nikv, _inġi,
+ {{0x7e7e0422,0xde5885e9,0x17548198,0x225806ae}}, // _kopp, далі_, твля, _tark_,
+ {{0x20120032,0x27ed8f06,0x443f874b,0x7e7e1c4e}}, // _leyi_, _egen_, efu_, _jopp,
+ {{0x80d100c8,0x52be1c4f,0x64830074,0x7e7e052e}}, // _সংস্, ्तीस, _võiv, _mopp,
+ {{0x7764062f,0x7e7e1c50,0xd4978ca4,0x44321c51}}, // _orix, _lopp, ерь_, ngy_,
+ {{0x64599c52,0x82a68d55,0x61f303ed,0x764e1c53}}, // _dawi, ешне, _çelë, keby,
+ {{0x2d96867c,0x68e980eb,0x60c28074,0x2012026b}}, // _прес, _zied, loom, _aeyi_,
+ {{0xddc88754,0x6f1b8024,0x39478b99,0x78a61c54}}, // _hodž, _zvuc, _cpns_, fikv,
+ {{0x645982b8,0x765a80b9,0xddc8817f,0x27ed04d6}}, // _gawi, _haty, _kodž, øen_,
+ {{0x44f50bfa,0x765a9c55,0x66150102,0x77641c56}}, // _så_, _katy, tazk, _crix,
+ {{0x44f50082,0x36d515d1,0x64598063,0xa6e780ff}}, // _på_, тогр, _zawi, _nhữ,
+ {{0xd5b81c57,0xb81081fe,0x66150cc7,0x7e7e1c58}}, // ест_, ाराम_, razk, _dopp,
+ {{0x77641041,0x39138098,0x3f868176,0x765a9c59}}, // _frix, _смър, kyou_, _laty,
+ {{0x68e99c5a,0x60c282a3,0x439417c8,0x77640118}}, // [eb0] _ried, doom, гатс, _grix,
+ {{0x6d488022,0x765a9c5b,0x68e99c5c,0x66d2007b}}, // _opda, _naty, _sied, lækn,
+ {{0x68e99c5d,0x673d1388,0x7aea81b9,0x39470722}}, // _pied, ltsj, _mift, ïns_,
+ {{0x20d20307,0x26c5826f,0xe0df1c5e,0x7e7e00e5}}, // háin_, _sklo_, nmò_, _zopp,
+ {{0x64599c5f,0x23d50f12,0x68e99c60,0x765a9c61}}, // _rawi, _दरिद, _vied, _baty,
+ {{0x68e99c62,0x64599c63,0x945d809a,0x7aea9c64}}, // _wied, _sawi, końc, _nift,
+ {{0x68e98364,0x64599c65,0xd6580039,0x443f9c66}}, // _tied, _pawi, גיות_, tfu_,
+ {{0x2d51800d,0x67249c67,0x27ed806a,0x0b459c68}}, // _vše_, šija, _ugen_, внин,
+ {{0x645b9c69,0xe2998e9f,0x443f9c6a,0x6f1b8699}}, // rdui, дап_, rfu_, _uvuc,
+ {{0x64598010,0x09e500ab,0x765a9c6b,0x443f9c6c}}, // _wawi, _প্যা, _gaty, sfu_,
+ {{0x81c98a49,0x20120f23,0xe9d98987,0x3ea78019}}, // লেন_, _peyi_, ько_, mint_,
+ {{0x387f83d3,0x7e0a0076,0x3ea79c6d,0x64830006}}, // _jour_, वर्ग_, lint_, _mõis,
+ {{0xa4d4835f,0x80d100ab,0x087680be,0x2d879c6e}}, // _соці, _সংশ্, דערט_, lyne_,
+ {{0x9f9e0073,0x3ea79c6f,0x60c29c70,0x78a61c71}}, // _ação_, nint_, zoom, sikv,
+ {{0x69ca8355,0x2d8c1c72,0x764e1c73,0x765c1c74}}, // _gyfe, áde_, reby, rdry,
+ {{0x29059c75,0x09b000ab,0x764e1c76,0x765c1c77}}, // mpla_, _করলা, seby, sdry,
+ {{0x7e7e12f1,0xf4128158,0x753c0063,0xfce3137b}}, // [ec0] _topp, יפן_, strz, рото,
+ {{0x64830074,0xddc89c78,0x386d83ba,0xdee61c79}}, // _sõit, _rodž, _aner_, _шопи,
+ {{0xfbc78077,0x386d9142,0x3ea79c7a,0xa2b31c7b}}, // _چت_, _bner_, dint_, _आदर्,
+ {{0xfbc7880b,0x2eca8bb3,0x387f80e7,0x765a9c7c}}, // _نت_, ित्त, _cour_, _saty,
+ {{0x765a8110,0x64830006,0x69d50019,0x387f9c7d}}, // _paty, _võit, _ezze, _dour_,
+ {{0x3ea79c7e,0x6f0207d9,0x649580f7,0x60c29c7f}}, // gint_, _çocu, _láid, soom,
+ {{0x82a30071,0x64b38087,0xfaa31b2f,0xc60b80ab}}, // _тарж, răin, _таро, _রাখা_,
+ {{0x44221c80,0xb4cc816f,0x69ca8428,0x70569c81}}, // _jdk_, ळते_, _ryfe, _جنرا,
+ {{0xee3a9c82,0x44221c83,0x8c3d880a,0x20d201a8}}, // _она_, _mdk_, mişl, táin_,
+ {{0x63a90110,0x3ea79c84,0x7c2900eb,0x44221c85}}, // _ženk, cint_, _ķerm, _ldk_,
+ {{0x387f873a,0x6d488add,0x63ad8125,0xe1348196}}, // _your_, _upda, _þann, ынны,
+ {{0x387f1c86,0x20d20013,0x63a481e2,0x386d04c3}}, // ður_, sáin_, _žinu, ñer_,
+ {{0x26c31c87,0x7aea84b7,0x7c229c88,0x66d2006a}}, // tojo_, _tift, _idor, rækn,
+ {{0x44f890ab,0x39580267,0x6d5c81a8,0x6fe901a9}}, // _ké_, _osrs_, _áras, _rīcī,
+ {{0x44f8877f,0x44220359,0x3958026c,0x673d006f}}, // _jé_, _bdk_, _nsrs_, stsj,
+ {{0x44f89c89,0x273b8104,0x65650267,0x80d10264}}, // _mé_, yên_, _vrhh, _সূর্,
+ {{0x44f88324,0x20191c8a,0x1af41c8b,0x26c31c8c}}, // [ed0] _lé_, masi_, иптя, pojo_,
+ {{0x20d20013,0x7e5500e8,0x68ed0362,0x44f881a8}}, // háil_, _своє, _hiad, _oé_,
+ {{0x387f9c8d,0x6618826f,0x68ed0019,0x3ea79c8e}}, // _pour_, davk, _kiad, vint_,
+ {{0x64830074,0x66020da8,0xdb1d0198,0x22840106}}, // _võis, _đoki, _pysä, _sökt_,
+ {{0xfeb80288,0x9cd60f60,0x20d200f7,0x3ea79c8f}}, // _سایت_, _תורה_, dáil_, tint_,
+ {{0x44f89c90,0x645d1c91,0x0ccb8eed,0x2ca9031d}}, // _bé_, _hasi, ात्म, liad_,
+ {{0x20191c92,0x44f88013,0x88838077,0x3ea79c93}}, // kasi_, _cé_, _پیشن, rint_,
+ {{0x2366803b,0x44f89c94,0x20d20013,0x2ca9031d}}, // _broj_, _dé_, gáil_, niad_,
+ {{0x645d1c95,0x661890d3,0x44391c96,0x69da01d0}}, // _masi, bavk, _mcs_, _úter,
+ {{0x44f8877f,0x2019002e,0x4439038a,0x7c228f3e}}, // _fé_, easi_, _lcs_, _edor,
+ {{0x2019022e,0x44f8877f,0xa29f8006,0x68ed01e4}}, // fasi_, _gé_, गोष्, _biad,
+ {{0x645d1c97,0x20191c98,0x68f51c99,0x20d200f7}}, // _nasi, gasi_, _muzd, cáil_,
+ {{0x7bcb87d9,0x32458767,0x68ed1c9a,0x2ca90355}}, // _uygu, _белг, _diad, diad_,
+ {{0xed5a0b30,0xb4be1c9b,0x248d801b,0x29059c9c}}, // ног_, ेत्_, lkem_, ppla_,
+ {{0x68ed1c9d,0x2ca9031d,0x68f500d2,0x20191c9e}}, // _fiad, fiad_, _nuzd, basi_,
+ {{0x6abe0f1b,0x68ed1c9f,0x2ca9031d,0x64a31ca0}}, // ्त्र, _giad, giad_, баса,
+ {{0x6b899ca1,0xab6607ac,0x645d1ca2,0x2fcd822c}}, // [ee0] nyeg, ывал, _dasi, _nyeg_,
+ {{0x248d8006,0x68ed1ca3,0x60c98deb,0x60db9ca4}}, // hkem_, _ziad, _akem, _ahum,
+ {{0x645d1ca5,0x44221ca6,0x60db9ca7,0xb05b01ec}}, // _fasi, _tdk_, _bhum, plät,
+ {{0x60db9ca8,0x44f89ca9,0x645d1caa,0xb05b1cab}}, // _chum, _ré_, _gasi, llär,
+ {{0x44f89cac,0x43751cad,0x60db8077,0x8c3d861c}}, // _sé_, _курт, _dhum, rişl,
+ {{0x44f88324,0x4ab8000c,0x645d1cae,0x248d81ed}}, // _pé_, _आदिव, _zasi, ekem_,
+ {{0x20191014,0x645d0309,0x20d20013,0x765e004f}}, // yasi_, _yasi, táil_, _mapy,
+ {{0x6f04007d,0x44f8801c,0x60db80b9,0x765e02d5}}, // _otic, _vé_, _ghum, _lapy,
+ {{0x36368277,0x2019059e,0xe80a835a,0x20d20013}}, // _مراس, vasi_, _ह्या_, ráil_,
+ {{0x44f89caf,0x20191cb0,0xfaff0168,0x20d200f7}}, // _té_, wasi_, _orën_, sáil_,
+ {{0x6f041cb1,0x68ed1cb2,0xe3af89a7,0x32638084}}, // _atic, _piad, قری_, стыв,
+ {{0x6d5a9cb3,0x68e29cb4,0x60db9cb5,0x6f1d1cb6}}, // _ista, nmod, _xhum, tssc,
+ {{0xb4c0823c,0xb4c2835a,0x2ca6813c,0x68ed1cb7}}, // ंकी_, ृती_, _imod_, _viad,
+ {{0x20191cb8,0x68ed1cb9,0x256387d9,0x645d1cba}}, // sasi_, _wiad, _yıl_, _sasi,
+ {{0x645d1cbb,0x68ed1cbc,0x6f040b87,0x201910e1}}, // _pasi, _tiad, _etic, pasi_,
+ {{0x69d89cbd,0x245802c7,0x752801ac,0x645d1cbe}}, // _izve, лась_, ádza, _qasi,
+ {{0x645d1cbf,0x2ca91cc0,0x68f51cc1,0x291e9cc2}}, // [ef0] _vasi, riad_, _suzd, lsta_,
+ {{0x60db8b18,0x645d1cc3,0x60c99cc4,0x291e9276}}, // _shum, _wasi, _skem, osta_,
+ {{0x645d1cc5,0x6d5a9cc6,0x41551cc7,0x81e68264}}, // _tasi, _nsta, рвес, যুর_,
+ {{0x291e913f,0x765e009a,0x3cfb8035,0x69ce0366}}, // ista_, _zapy, _लंबे_, _dybe,
+ {{0x8335093f,0x65689cc8,0x6d5a9cc9,0x2b401cca}}, // _דאָס_, _ardh, _asta, ctic_,
+ {{0x291e9ccb,0x7aee01ec,0x83fd8019,0x68f51ccc}}, // ksta_, _gibt, zdőd, _tuzd,
+ {{0x248d8419,0xd61881a8,0x291c01d0,0x2ca68197}}, // tkem_, اتها_, ávat_, _bmod_,
+ {{0x03258abe,0x291e8106,0x9f05803d,0xac858037}}, // адин, dsta_, _نورو, ргил,
+ {{0x291e9ccd,0x6d5a8c8c,0x656880f1,0xeb998530}}, // esta_, _esta, _erdh, тип_,
+ {{0xea899cce,0x78ab8aa2,0x7aa599f8,0x64878388}}, // тбол_, ligv, риоз, _añit,
+ {{0xe807053e,0xd9459ccf,0xe0da1cd0,0x26c79cd1}}, // _व्हा_, шени, кво_, mono_,
+ {{0xaca301bc,0x6e250014,0x26c79cd2,0x290381a9}}, // _brọd, _adhb, lono_, īja_,
+ {{0xe28e8abe,0x291e9cd3,0xb8fc9cd4,0x765e06c0}}, // _да_, asta_, _ती_, _papy,
+ {{0x91e59cd5,0x6d5a8114,0x26c78365,0x539a8039}}, // _коле, _ysta, nono_, _בינו,
+ {{0xb05b08dc,0x26c7847f,0x5fd80072,0x68e29bad}}, // rlär, iono_, _ठरवल, ymod,
+ {{0xdd8f0eca,0x77860098,0x62839cd6,0x765e004f}}, // ذوق_, _влиз, _hono, _wapy,
+ {{0x6f041601,0xd3870d70,0x26c79cd7,0x23e00424}}, // [f00] _utic, айте_, kono_, पेंद,
+ {{0x26c78110,0x2b401cd8,0xa01b1cd9,0x64440162}}, // jono_, rtic_, sköt, sfii,
+ {{0x2b401cda,0x628380e5,0xd83a81e5,0xf67b0158}}, // stic_, _mono, вэл_, _סאטמ,
+ {{0x2b401cdb,0x69ce003e,0x504685fa,0x650201bc}}, // ptic_, _vybe, редб, _ọkwụ,
+ {{0x26c79cdc,0xdfcf8013,0xbebb00f1,0x44fc1cdd}}, // fono_, ويم_, rrëv, _hí_,
+ {{0x44fc0324,0x62839cde,0x661c1cdf,0x68e284fe}}, // _kí_, _nono, nark, smod,
+ {{0x44fc1ce0,0xb87b000d,0xa01b0106,0xa3e3864a}}, // _jí_, dmín, ljöe, फेस_,
+ {{0x44fc1ce1,0x661c011e,0x3218008e,0x38601ce2}}, // _mí_, hark, _hery_, _nair_,
+ {{0xf749803d,0xa2c2816f,0xb7fd0424,0x321800b9}}, // _مجله_, रकल्, _एलबम_, _kery_,
+ {{0x62839ce3,0x26c79ce4,0x661c1ce5,0x7f3b825f}}, // _cono, cono_, jark, _בעמו,
+ {{0x44fc1ce6,0x6d5a9ce7,0x62839ce8,0x7c261ce9}}, // _ní_, _usta, _dono, _odkr,
+ {{0x291e9cea,0x38601ceb,0xdcee0110,0x66171cec}}, // rsta_, _cair_, mybė, _sexk,
+ {{0x44269ced,0x661c1cee,0x44fc1cef,0x38601cf0}}, // _ido_, fark, _aí_, _dair_,
+ {{0x44fc1cf1,0x661c1cf2,0x3f4280ff,0x27fe01d0}}, // _bí_, gark, _mưu_, ětna_,
+ {{0x3f428028,0x44269cf3,0x88d600ab,0xdcee0084}}, // _lưu_, _kdo_, _সংরক, nybė,
+ {{0x44fc1cf4,0x316d1cf5,0x30150187,0xd4981cf6}}, // _dí_, _čez_, _удир, ару_,
+ {{0x26c7861b,0x60cd1cf7,0x69ca8a74,0x24581cf8}}, // [f10] yono_, _ikam, _सुती, шать_,
+ {{0x996713f1,0x661c1cf9,0xdcee0084,0x3b09011c}}, // ител, cark, kybė, rpaq_,
+ {{0xd3668288,0x44269cfa,0x161a8063,0x26c78098}}, // _که_, _odo_, _नज़र_, vono_,
+ {{0x776984c3,0xa3cc81a2,0x44269cfb,0x40359cfc}}, // _prex, _शुभ_, _ndo_, седс,
+ {{0x26c79cfd,0x753501b9,0x32181cfe,0x60cd0c53}}, // tono_, luzz, _fery_, _mkam,
+ {{0xc9871a02,0x51871cff,0xee3880e8,0x76551d00}}, // _куби, _куба, ині_, nezy,
+ {{0x09e50a49,0x31e500c8,0x99850013,0x01e500c8}}, // _প্রা, _প্রশ, _الزو, _প্রদ,
+ {{0x69ca9d01,0xa8a48160,0x442680f1,0xe5709a37}}, // _सुधी, _друк, _cdo_, بطه_,
+ {{0x6b8d1d02,0x26c79d03,0x661c1d04,0x201d95d7}}, // nyag, pono_, yark, mawi_,
+ {{0x60cd0b15,0x44269d05,0x81c280c8,0x38601d06}}, // _akam, _edo_, ্ধন_, _sair_,
+ {{0xb87b026f,0x69ca8441,0xec15803d,0x661c1d07}}, // rmín, _सुदी, _هواد, vark,
+ {{0x656e8307,0x44fc077f,0x201d83f8,0x62839d08}}, // _ábha, _rí_, nawi_, _wono,
+ {{0x44fc1d09,0x2d8a834a,0x6d439d0a,0xafdb013c}}, // _sí_, _über_, ltna, lføj,
+ {{0x629a9d0b,0xb05b0106,0x201d9d0c,0x7e63808e}}, // chto, släp, hawi_, ndnp,
+ {{0x7e619d0d,0x661c02a3,0x201d9d0e,0x7c9602a9}}, // _malp, rark, kawi_, брац,
+ {{0x661c1d0f,0x3860003c,0x201d9d10,0x44fc1d11}}, // sark, _uair_, jawi_, _ví_,
+ {{0xd4671194,0xc9f600f7,0x201d80ee,0xb2260e02}}, // [f20] щите_, مساع, dawi_, ймал,
+ {{0x44fc1d12,0xe8108526,0x753501b9,0x2bac0072}}, // _tí_, ार्थ_, buzz, _घेणा,
+ {{0x81c98a49,0x32180051,0x3f42801c,0x75351d13}}, // লের_, _very_, _sưu_, cuzz,
+ {{0xa01b016d,0x443d9d14,0x26ca0234,0x7f4280e5}}, // nköp, _ncw_, lobo_, ttoq,
+ {{0x20020bda,0x645601cc,0x6d4380ff,0x649c8174}}, // icki_, meyi, etna, _héig,
+ {{0xa01b0364,0x2480005c,0x7e619d15,0xdcee0110}}, // hköp, ljim_, _calp, tybė,
+ {{0x201d82e7,0x7f4281b9,0x2cad9d16,0x25e39d17}}, // bawi_, stoq, fied_, टेली_,
+ {{0x69dc1d18,0x64561d19,0x44269d1a,0xdcee0084}}, // _izre, neyi, _qdo_, rybė,
+ {{0x6d698029,0x4426856c,0x57fb810f,0x26ca1d1b}}, // _īpaš, _vdo_, _תלמו, kobo_,
+ {{0x60cd1d1c,0x6d5e1d1d,0x321881ac,0xe810801b}}, // _skam, _ospa, úry_, ारका_,
+ {{0x2cad8b3c,0x44269d1e,0x67240da8,0x20f3026c}}, // bied_, _tdo_, _avij, _ućit_,
+ {{0x55778158,0x44269d1f,0x661a837b,0x629a80f1}}, // _לעבן_, _udo_, _hetk, shto,
+ {{0x6724003a,0x6d5181dd,0x29079d20,0x24800503}}, // _cvij, čkaš, _etna_, djim_,
+ {{0x672402fd,0x69dc017f,0x24868282,0x753504b7}}, // _dvij, _ozre, _hoom_, tuzz,
+ {{0x248681e9,0x59cf8aed,0x321e9d21,0xdd940048}}, // _koom_, _सुपर, maty_, пары,
+ {{0x753512bb,0x8c3d87d9,0x60cd1d22,0x6b8d1d23}}, // ruzz, tişi, _ukam, tyag,
+ {{0x6d5e1d24,0x26ca1d25,0xfbd30154,0x69dc00ee}}, // [f30] _espa, bobo_, ستر_, _azre,
+ {{0x63a9095e,0x661a9d26,0x201d9d27,0xbebb0168}}, // _žens, _netk, wawi_, rrës,
+ {{0x6724120e,0x2bd50d86,0x201d9d28,0x6f160035}}, // _zvij, _दरका, tawi_, _zwyc,
+ {{0x2ec186bf,0x24868282,0x6e289d29,0x4fd58171}}, // शक्त, _noom_, _addb, ожет,
+ {{0xf1cf8105,0x643a00be,0x201b008e,0xddda81a1}}, // _सुनन, _דערנ, _meqi_, _rotš,
+ {{0x3eae8e23,0x661a80d2,0x6d439d2a,0x443d8079}}, // gift_, _cetk, ttna, _scw_,
+ {{0x7e619d2b,0x78bb8711,0x7d0201d6,0x201d876d}}, // _valp, _tjuv, _čosk, pawi_,
+ {{0x62988aa2,0xcc898bda,0x6d439d2c,0x672281c0}}, // _alvo, рбие_, rtna, bsoj,
+ {{0x6287003a,0x6d439d2d,0x7e619d2e,0x254e811c}}, // _kojo, stna, _talp, _cəlb_,
+ {{0xa9259d2f,0x24800bda,0x6f099086,0x4ea78a2e}}, // одол, zjim_, _itec, _урба,
+ {{0x6724030b,0x3f8f8355,0x2d678024,0x24868069}}, // _svij, lygu_, _uđe_, _foom_,
+ {{0x649c8174,0xa91100d4,0x6287046d,0x7de00032}}, // _réig, डीएफ_, _lojo, _lásí,
+ {{0x661a8457,0x321e9d30,0x776d00e5,0x2d851d31}}, // _yetk, baty_, _crax, älen_,
+ {{0x21699d32,0x64499d33,0xa9699d34,0xfc3f023e}}, // _вики_, lfei, _вика_, _ací_,
+ {{0x649c8307,0x26ca1d35,0xa2ba03dd,0x249902f7}}, // _féid, robo_, ्वस्, _blsm_,
+ {{0x7c3b9d36,0x24869d37,0x26ca1d38,0x2fc0022c}}, // ngur, _xoom_, sobo_, _txig_,
+ {{0x6724030b,0x62871d39,0x776d1d3a,0x52aa0471}}, // [f40] _uvij, _bojo, _grax, авам_,
+ {{0x62871d3b,0x649c80f7,0x64561d3c,0x1ae31d3d}}, // _cojo, _téig, seyi, доум,
+ {{0x661a9d3e,0x6f0980e5,0xa01b0106,0x62871d3f}}, // _retk, _atec, ljöa, _dojo,
+ {{0x50ca009a,0x661a9d40,0x20ca001b,0x67228198}}, // रविष, _setk, रविध, tsoj,
+ {{0x44201d41,0x661a84c4,0x24868039,0xe8e08129}}, // mai_, _petk, _room_, hiệt_,
+ {{0x2a630267,0x24869d42,0x6b8b0106,0x6d5c02ed}}, // _gajb_, _soom_, ägge, owra,
+ {{0xd49a80c4,0x24869d43,0x661a9d44,0x69dc01a9}}, // ири_, _poom_, _vetk, _uzre,
+ {{0x44201d45,0xb19880ff,0x734a8087,0x672d9d46}}, // nai_, _ngưỡ, ачов_, šaja,
+ {{0x68fc1d47,0x61ef8085,0x649c81a8,0x1fd180ab}}, // _hurd, əklə, _réid, ়েবস,
+ {{0x68fc1d48,0x38669d49,0x7c3b9d4a,0x20e89238}}, // _kurd, ldor_, agur, _eşit_,
+ {{0x44201d4b,0x69ca946d,0xe45a835f,0x24868282}}, // kai_, _सुशी, _вже_, _toom_,
+ {{0x44201d4c,0x68fc1d4d,0x776d1d4e,0x290a0267}}, // jai_, _murd, _prax, _mtba_,
+ {{0x44201d4f,0x69ca864d,0x610180eb,0xe6671229}}, // dai_, _सुरी, _vēla, отво,
+ {{0x81c980ab,0x2455001c,0x7658804f,0x809f0035}}, // লেই_, _ấm_, nevy, खोजे,
+ {{0x44201d50,0xbd681d51,0x62870388,0x00950162}}, // fai_, орте_, _rojo, _никэ,
+ {{0x44201d52,0x776d062f,0x628700a4,0x7c209d53}}, // gai_, _trax, _sojo, namr,
+ {{0x62871d08,0x8c3d8214,0x1eab00f7,0x7bc10035}}, // [f50] _pojo, mişt, _نادي_, _ślub,
+ {{0x8c3d9d54,0x7afd9d55,0x1b040264,0x68fc1d56}}, // lişt, ísti, _লিখে_, _burd,
+ {{0x44201d57,0x6f099d58,0x7c209d59,0xad27003d}}, // bai_, _stec, kamr, _برخو,
+ {{0xe8f806b5,0x649580f7,0x8c3d8087,0x68fc1d5a}}, // ілі_, _cáil, nişt, _durd,
+ {{0xb9020076,0xb4d7016f,0xcb4400a9,0x62871d5b}}, // _नी_, िती_, _охри, _tojo,
+ {{0x81cd00c8,0x7afd1d5c,0x8afa01c6,0x24678129}}, // শেষ_, _iust, _להשי, _ốm_,
+ {{0xceb40158,0x6d471d5d,0x855780d5,0x649580f7}}, // ויס_, ntja, تیار_, _fáil,
+ {{0x7af50102,0x81c28264,0x442b1d5e,0x00000000}}, // _hizt, ্ধা_, _jdc_, --,
+ {{0xbf1592dc,0x64499d5f,0x7c3b9d60,0xe3b180f7}}, // _رواب, rfei, rgur, عرب_,
+ {{0x78ad1d61,0x64499d62,0x7c3b9d63,0x44201d64}}, // _amav, sfei, sgur, zai_,
+ {{0x81cd00c8,0x44201d65,0x68fc04c3,0x644f1d66}}, // শের_, yai_, _xurd, _obci,
+ {{0xa3ac0665,0x7c2081a1,0x44201d67,0x29e1841c}}, // _कइल_, camr, xai_, _açaí_,
+ {{0x442006f7,0x7afd01e2,0xb4c9816f,0xf77201a8}}, // vai_, _nust, ोती_, راح_,
+ {{0x44201d68,0x59cf86a7,0x91fc81a9,0x200d811c}}, // wai_, _सुथर, ndāl, əmiş_,
+ {{0x44201d69,0x7c960160,0x649580f7,0x442b00e5}}, // tai_, прац, _láim, _bdc_,
+ {{0x2be292ee,0x249f80a9,0x7afd1d6a,0xa3e28074}}, // _परमा, nhum_, _bust, _धरम_,
+ {{0x7af51d6b,0x44201d6c,0x59cf8076,0x7afd1d6d}}, // [f60] _bizt, rai_, _सुतर, _cust,
+ {{0x44201d6e,0x7c2080ee,0x2bce954b,0x7afd1d6f}}, // sai_, zamr, _हँसा, _dust,
+ {{0x68e40ad4,0x442b03f7,0xa3ac11bc,0x2be29d70}}, // _khid, _fdc_, _केर_, _परभा,
+ {{0x7afd1d71,0x20d21d72,0x68fc054f,0x69d51d73}}, // _fust, máis_, _vurd, _dyze,
+ {{0x7afd1d74,0x68fc034a,0x6726035f,0x672d812b}}, // _gust, _wurd, nskj, šajn,
+ {{0x425503bd,0x68fc1d75,0x63a41d76,0x543600d7}}, // _отст, _turd, mzin, _برگر,
+ {{0x7afd1d77,0x63a41d78,0x649581a8,0x321c82fe}}, // _zust, lzin, _táil, _revy_,
+ {{0x2ca0076d,0x7de70019,0xf7469b3f,0xc5f301c6}}, // nhid_, lésé, _небо, עדה_,
+ {{0x7afd062f,0x8fa39d79,0xe45703de,0x7de2826b}}, // _xust, _зате, ליקט_, _bísí,
+ {{0x63a41d7a,0x3a749d7b,0xcb1301c6,0x8f9b825f}}, // izin, флор, עלת_, _הימי,
+ {{0xa3cc8076,0x4035072a,0xade89d7c,0xfbc400ab}}, // _शुर_, менс, टेशन_, ্ধিত,
+ {{0x290a8341,0x68e41d7d,0x7bc30162,0x20d21d7e}}, // ība_, _chid, şnui, dáis_,
+ {{0x68e41d7f,0x442b1d80,0x63a400f3,0x8c3d9d81}}, // _dhid, _rdc_, jzin, rişt,
+ {{0x63a41d82,0x7afd1d83,0x442b0267,0x6d471d84}}, // dzin, _rust, _sdc_, ttja,
+ {{0xe5c4079e,0xd49b1d85,0x71f78154,0x78ad026f}}, // есто, бра_, _عروس_, _tmav,
+ {{0xb4d70e18,0xe45f0125,0x6d471d86,0x68e41d87}}, // िते_, _mjög_, rtja, _ghid,
+ {{0x6d4704a4,0x628a9d88,0xc62280ab,0xa3e71d89}}, // [f70] stja, _kofo, _নানা_, यें_,
+ {{0x6f0d1d8a,0xbd020144,0x8f9c01c6,0x628a8e06}}, // _itac, ñéca, _ליחי, _jofo,
+ {{0xb5fc84b7,0x78bd1d8b,0x442b006a,0x20d21d8c}}, // _paġn, onsv, _tdc_, cáis_,
+ {{0x442b1d8d,0xddde1601,0x69d501ac,0x2ca01d8e}}, // _udc_, _uopš, _vyze, chid_,
+ {{0x644d00b9,0xf7459d8f,0x5f94804a,0x7afd03ca}}, // mfai, фело, ницт, _uust,
+ {{0x628a81a9,0x20d200f7,0x6442802a,0x644d1d90}}, // _nofo, náir_, _acoi, lfai,
+ {{0x236d0858,0x249f9670,0x7d1a9670,0x24e99d91}}, // _šejh_, thum_, _cwts, омки_,
+ {{0x644d1d92,0x649580f7,0x9f6500e1,0xfd5e801c}}, // nfai, _táim, _štýl_, _huyề,
+ {{0x7690025d,0x6f0d1d93,0x80c900ab,0x32059d94}}, // _käyt, _ntac, রগঞ্, tcly_,
+ {{0x09059baa,0x68e41d95,0xd0480085,0x20d20216}}, // мпон, _shid, əməy, yáis_,
+ {{0x63a40098,0x661e1d96,0x6e239d97,0xdd9b8087}}, // zzin, _repk, manb, _уша_,
+ {{0x644280f7,0x7de70019,0x6f1b81bc,0xf485853d}}, // _gcoi, zésé, _kwuc, _تائی,
+ {{0x644d00f7,0xe3ae89a8,0x628a9d98,0x10a31d99}}, // dfai, _рб_, _fofo, тиян,
+ {{0x76900364,0x7afd9d9a,0x12fa8039,0xddc38019}}, // _näyt, ístu, _והסב, lenő,
+ {{0x2ca00014,0x644d1d9b,0x768f804a,0x2c0b0135}}, // thid_, ffai, _høys, _ụjọ_,
+ {{0x443f9d9c,0x63a41d9d,0x672981ca,0x20d200f7}}, // ngu_, tzin, _ovej, ráis_,
+ {{0xc61d00c8,0x6e239d9e,0x7de70019,0x236d806a}}, // [f80] _তারা_, kanb, tésé, nvej_,
+ {{0x248b00b9,0x2ca01d9f,0xe45f0106,0x6b8b0106}}, // _eocm_, shid_, _sköt_, ägga,
+ {{0x64a6014c,0x443f8ad4,0x81d600ab,0xc5fa02f1}}, // _жана, kgu_, াইভ_, ंशीय_,
+ {{0x765c1da0,0x753c1da1,0x38ab0035,0x644d01a8}}, // lery, murz, _górę_, cfai,
+ {{0x7c240f2b,0x201f808e,0x2b490aae,0x672981a1}}, // mair, _deui_, ctac_, _cvej,
+ {{0x7c241da2,0x64428013,0x443f9da3,0x2458801c}}, // lair, _scoi, egu_, _ẩm_,
+ {{0x26ce9da4,0xa01b007b,0x3d12016f,0xaca401bc}}, // rofo_, fjöl, धीचे_, _naịt,
+ {{0x7c241da5,0x628a82af,0x7764002a,0x2be28fb2}}, // nair, _sofo, _osix, _परदा,
+ {{0x9ce88117,0x6e239da6,0xd00f8bbe,0x648e8229}}, // _ہوئے_, banb, _خلق_, _bùir,
+ {{0x7c241da7,0x765c1da8,0x648e851e,0xee379da9}}, // hair, jery, _cùir, _ону_,
+ {{0x3a37078d,0x7c241daa,0x60cf0087,0xe873003d}}, // ורים_, kair, tocm, نندگ,
+ {{0x78a29dab,0x6f00804f,0x645b9dac,0x2baf8b99}}, // nhov, _kumc, ceui, _जेवा,
+ {{0x7c241dad,0xb90582ef,0x23e295a7,0x628a8915}}, // dair, _बी_, _परिद, _tofo,
+ {{0x44249dae,0xb4d70054,0x768f835f,0x59cf8f21}}, // lam_, ित्_, _høyr, _सुवर,
+ {{0x77641daf,0xa2cb86b7,0x20c70be2,0x3ce68069}}, // _esix, सकर्, _осиг, _khov_,
+ {{0x44249db0,0x64409db1,0x7c240c5e,0xa3e29344}}, // nam_, ngmi, gair, _धरा_,
+ {{0xf8bf0efc,0x644d1db2,0x69d880f3,0x765c02ec}}, // [f90] mné_, rfai, _hyve, bery,
+ {{0x44249db3,0x386901d8,0xf8bf03cb,0xfd5e8028}}, // ham_, _haar_, lné_, _quyề,
+ {{0x44249db4,0x6729811f,0x443f9db5,0x59d482f1}}, // kam_, _svej, ygu_, _ठुमर,
+ {{0xf8bf1db6,0x3869133a,0x7c241db7,0x656500b9}}, // nné_, _jaar_, cair, _ishh,
+ {{0x38691db8,0x6e239db9,0x76900364,0xe0d880e8}}, // _maar_, tanb, _täyt, єві_,
+ {{0x81c980ab,0x451989b5,0xa01b0061,0x6d4a9dba}}, // লেট_, пция_, gköz, ftfa,
+ {{0xf8bf1dbb,0x44249dbc,0xddde0035,0x645b9dbd}}, // kné_, fam_, _zapł, teui,
+ {{0x38691dbe,0x78a28775,0x6ac79516,0xf8bf027f}}, // _naar_, chov, रक्र, jné_,
+ {{0xf8bf0a56,0x610180eb,0x254e8085,0xaca40870}}, // dné_, _vēlm, _həll_, _saịt,
+ {{0x443f831d,0x961e001b,0x236d806a,0xf1bf81d6}}, // sgu_, _बजेट_, rvej_, ášok_,
+ {{0xe9d98098,0xae748065,0x236d806a,0xf99181a8}}, // яко_, _جھنڈ, svej_, ابخ_,
+ {{0x6e21811e,0x3ce681c0,0xf8bf00e7,0x7cc180eb}}, // _helb, _ghov_, gné_, mēro,
+ {{0x386901d8,0x442f9dbf,0x9f4c080a,0x7c2400eb}}, // _daar_, _idg_, _ölüm_, vair,
+ {{0x78a2826f,0x2cbf8074,0xade406ae,0x7ce58144}}, // zhov, lnud_, _गरदन_, sórd,
+ {{0x7c241dc0,0x69d88a8e,0xf8bf1dc1,0xc692810f}}, // tair, _gyve, bné_, נאל_,
+ {{0x386902a3,0xf8bf026f,0x23ce800d,0x765c1dc2}}, // _gaar_, cné_, _हुँद, sery,
+ {{0x7c241dc3,0x765c1dc4,0xfa3607c3,0x26d11dc5}}, // [fa0] rair, pery, فراد, bozo_,
+ {{0x7c241dc6,0x2be29344,0x0566046a,0x4a431dc7}}, // sair, _परसा, _звон, лнув,
+ {{0x44249dc8,0x442f979d,0x6f009dc9,0xf29683de}}, // yam_, _odg_, _sumc, עכער_,
+ {{0xafdb0bc5,0x76900009,0x38a201ac,0x44249dca}}, // mfør, _täys, _fóre_, xam_,
+ {{0xa01b0019,0x2cbf8074,0x78a29dcb,0x76450114}}, // tköz, dnud_, rhov, _ychy,
+ {{0x7d0182a3,0xf8bf03fb,0x44221dcc,0x60c401a1}}, // _buls, zné_, _hek_, _ljim,
+ {{0xb7bd802e,0xa0a680e8,0x78a2816d,0x6e218bbd}}, // _reţe, _завд, phov, _delb,
+ {{0x6d4a8e23,0x3ce6946a,0x442f85db,0xb4ca858c}}, // rtfa, _qhov_, _cdg_, लके_,
+ {{0xf8bf0ed7,0x64408006,0xe7378785,0x628e1dcd}}, // vné_, rgmi, _пет_, _kobo,
+ {{0xc3331dce,0x44248304,0x628e1dcf,0x6e219dd0}}, // נות_, sam_, _jobo, _gelb,
+ {{0xf8bf0a56,0x386914f9,0x7afd826f,0x3ce681e9}}, // tné_, _paar_, ístr, _thov_,
+ {{0x26f909a3,0x44221dd1,0xdd8f817e,0x386902a3}}, // ंद्र_, _nek_, _دوم_, _qaar_,
+ {{0xf8bf1dd2,0x6108801b,0x7af89dd3,0x60c41dd4}}, // rné_, _děla, _tivt, _djim,
+ {{0x386901d8,0xf8bf003e,0x7c2280f7,0x65650267}}, // _waar_, sné_, _heor, _pshh,
+ {{0xf8bf003e,0xf1d00128,0x7c22808e,0xafdb1dd5}}, // pné_, _तुलन, _keor, gfør,
+ {{0x11db8051,0x44221dd6,0x629a9dd7,0x7c22808e}}, // _מחוב, _cek_, lkto, _jeor,
+ {{0x628e1dd8,0x213e9a2e,0x2a7c890d,0x7c2f92ca}}, // [fb0] _bobo, luth_, _knvb_, ócri,
+ {{0xd0198935,0x44221dd9,0x629a9dda,0x628e1ddb}}, // दर्भ_, _eek_, nkto, _cobo,
+ {{0x628e1ddc,0x649c8036,0xb8128264,0x2bd1052a}}, // _dobo, _réin, _হয়ত_, _थुरा,
+ {{0x6e218352,0xb4ca83bb,0x7cc180eb,0x89341ddd}}, // _selb, लको_, vēro, اعلا,
+ {{0x6e219cbc,0x7d019dde,0x4f0890ac,0x36699ddf}}, // _pelb, _suls, енін_, дало_,
+ {{0x628e02c1,0xed599016,0x44221de0,0x442f9de1}}, // _gobo, док_, _zek_, _sdg_,
+ {{0x6abf0770,0x2cbf8074,0xa158054c,0x6e219de2}}, // ्वीर, tnud_, _пару_, _velb,
+ {{0x24891de3,0x628e1de4,0x629a9de5,0x6e219de6}}, // njam_, _zobo, ekto, _welb,
+ {{0x6e219de7,0x7c229de8,0x2ef59016,0xd0498326}}, // _telb, _deor, _азар, _sheɗ,
+ {{0x3da79285,0x29030010,0xb87b01d0,0x60d60338}}, // _преб, _kuja_, hlíd, _skym,
+ {{0x10a600e8,0x98a61593,0x7c228706,0x290313de}}, // винн, вине, _feor, _juja_,
+ {{0x92c380c8,0x61fa1de9,0x2efa026c,0x7c229dea}}, // ্ষে_, _útle, _sipf_, _geor,
+ {{0xeb8e964f,0x672d026c,0xc62600ab,0xda658088}}, // _ти_, _cvaj, _বাবা_, _авли,
+ {{0x76438393,0x44221deb,0xa7fb04c3,0x66e61dec}}, // ngny, _sek_, xeñe, тога,
+ {{0xceb29a0f,0x2a6a07c7,0x44220214,0x27ff90ab}}, // בים_, _rabb_, _pek_, _ogun_,
+ {{0xafdb0257,0x7c2283a8,0x6fb601a8,0x3958018e}}, // rfør, _xeor, _لمعا, _rprs_,
+ {{0x63a99ded,0xb87b1dee,0xe8d71def,0x44221df0}}, // [fc0] nzen, rmít, _בוקר_, _vek_,
+ {{0x68e99df1,0x63a981ed,0xafdb004a,0x27ff810c}}, // _ahed, izen, pfør, _agun_,
+ {{0x44221df2,0x68e98362,0x29030187,0x68fb8084}}, // _tek_, _bhed, _cuja_, _biud,
+ {{0x68fb9df3,0x7794803d,0xb87b03a8,0x649c81a8}}, // _ciud, هیزا, lmír, _céil,
+ {{0xdfd280f7,0x63a981ed,0x649c81a8,0x2d980061}}, // جيش_, jzen, _déil, lyre_,
+ {{0x63a99df4,0x7c2294ff,0x27ff80ad,0x6e9481a8}}, // dzen, _seor, _egun_, طلبا,
+ {{0xd9e605b3,0x68469d32,0x649c80f7,0x7c229df5}}, // _करित_, _инва, _féil, _peor,
+ {{0x61e307d9,0x68fb8098,0x68e98c9f,0x7afc1df6}}, // ünle, _giud, _ghed, _hirt,
+ {{0x69dc016d,0x69ca8fd5,0x7afc1df7,0x3dd28035}}, // _hyre, _सुजी, _kirt, ływ_,
+ {{0xbbe283eb,0x7afc02a3,0x60c29df8,0x5be29008}}, // _परीक, _jirt, lnom, _परीव,
+ {{0xe6be1513,0x672d0110,0x5d869df9,0x2d9803ed}}, // ोवैज, _svaj, _جلال, jyre_,
+ {{0x6f0400f7,0x60c29dfa,0x213e9dfb,0x6d5a8214}}, // _buic, nnom, ruth_, _ipta,
+ {{0x63a98d38,0x7afc0c41,0xb4be9a1c,0x59b2801b}}, // czen, _oirt, ँची_, ुपहर,
+ {{0x6f041dfc,0x7ce59dfd,0x71749ab3,0x3f8682df}}, // _duic, móra, _مهتا, ixou_,
+ {{0xddda801b,0x5c071dfe,0x69dc006f,0xb5fc81b9}}, // _patř, вяза, _nyre, _baġi,
+ {{0x7afc0013,0x3eb80083,0x672d80eb,0x2fc901c0}}, // _airt, airt_, šaji, _txag_,
+ {{0x649c80f7,0x24891dff,0xb87b1e00,0x56951246}}, // [fd0] _réil, rjam_, smís, _бапт,
+ {{0x22470065,0x6d5a9e01,0x68e99e02,0x29031e03}}, // ünk_, _opta, _shed, _puja_,
+ {{0xa3e70076,0xa2ba0f21,0x2d8c03ba,0xddc89e04}}, // येज_, ्वज्, ødet_, _andž,
+ {{0x69dc1e05,0xf8d180c8,0x60c29e06,0x7afc1e07}}, // _dyre, িষ্ঠ, gnom, _eirt,
+ {{0x44291e08,0xc43a0051,0x6d5a8df1,0x68fb9e09}}, // maa_, _אתרי, _apta, _viud,
+ {{0xf7708c2a,0x44291e0a,0x64958013,0x4aa71e0b}}, // غان_, laa_, _láit, _गतिव,
+ {{0x68e99e0c,0x20d91e0d,0x200981df,0xb87b0187}}, // _thed, néis_, ñais_, ilíb,
+ {{0x44291e0e,0x63a99e0f,0x2005009a,0x7d051e10}}, // naa_, tzen, ślij_, _muhs,
+ {{0xddc183bb,0x7b758199,0xaca38133,0xe8949e11}}, // _dalš, اطبا, _ntụl, _тать,
+ {{0x63a99e12,0x44291e0e,0x45459e13,0xd90c8065}}, // rzen, haa_, _منطق, ڈیو_,
+ {{0x44291e14,0x63a99e15,0x26c30353,0xaca38135}}, // kaa_, szen, dnjo_, _atụl,
+ {{0xe73a09a8,0x44291e16,0x20d91e17,0x3da49e18}}, // нем_, jaa_, déis_, _труб,
+ {{0x442902a3,0xb8cc1e19,0x7c299e1a,0x6f040039}}, // daa_, _गत_, maer, _quic,
+ {{0x2d9800f1,0x25ef035a,0x66e3816f,0x386d867f}}, // tyre_, _आणखी_, गतिक_, _laer_,
+ {{0x09be00c8,0xada307b6,0x44291e1b,0x6da315e0}}, // _অর্থ, барл, faa_, бира,
+ {{0x38a21e1c,0x442902a3,0x7afc1e1d,0xb87b1e1e}}, // _fóra_, gaa_, _sirt, smír,
+ {{0x2d9e15d8,0x09be1664,0x38a2009a,0x7afc1e1f}}, // [fe0] šte_, ्थ्य, _góra_, _pirt,
+ {{0x7c298114,0x649c80f7,0x0e661e20,0x20d91e21}}, // haer, _réim, _скон, béis_,
+ {{0x386d8358,0x44291e22,0x20d90e1b,0x7c298102}}, // _baer_, baa_, céis_, kaer,
+ {{0x7afc1e23,0x386d9e24,0x00da8eca,0x4fa69e25}}, // _wirt, _caer_, ربات_, лиев,
+ {{0xbbe2801b,0x386d9e26,0x7c299e27,0x7afc1e28}}, // _परेक, _daer_, daer, _tirt,
+ {{0x7afc01a8,0x20d901a8,0x3fcb9e29,0x78a40580}}, // _uirt, néir_, ردنی_, _lliv,
+ {{0xf9879e2a,0x2aa40065,0x64958013,0x7c2606cb}}, // _اب_, _több_, _náis, _kekr,
+ {{0xa01b1e2b,0x386d8286,0x7c299e2c,0xb4de8072}}, // ljöv, _gaer_, gaer, णते_,
+ {{0x40938064,0x649580f7,0x7c261e2d,0x26da01bc}}, // _القر, _ráit, _mekr, _okpo_,
+ {{0x415498a2,0x78a41e2e,0x59b8102e,0x629e1e2f}}, // авос, _aliv, _आधार, nkpo,
+ {{0x78a41e30,0x442902c1,0x7c2982c4,0x6e250214}}, // _bliv, yaa_, baer, _rehb,
+ {{0x44290962,0x7c26003a,0x26da0133,0xe3bf0511}}, // xaa_, _nekr, _akpo_, _leña_,
+ {{0x44291e31,0xe29a81e2,0xe0df00e5,0x91fc81a9}}, // vaa_, _пад_, rlò_, edāt,
+ {{0x44291e32,0x20d91e33,0x386d1e34,0x442680ff}}, // waa_, téis_, žer_, _heo_,
+ {{0x44291e35,0x44269e36,0x78a40609,0x254e811c}}, // taa_, _keo_, _fliv, _bəli_,
+ {{0x442682a5,0x649580f7,0x26da01bc,0x20d91e37}}, // _jeo_, _háir, _ekpo_, réis_,
+ {{0x44291e38,0x7c261e39,0x20d91e3a,0xbbe281a2}}, // [ff0] raa_, _dekr, séis_, _परोक,
+ {{0x44291e3b,0x44269e3c,0x27e900ad,0x7ce58125}}, // saa_, _leo_, _izan_, jórn,
+ {{0x44291e3d,0x68ed1e3e,0x649581a8,0x64ac0162}}, // paa_, _khad, _máir, rşir,
+ {{0x44268046,0x7c2600c9,0x0467133c,0xb4c0800d}}, // _neo_, _gekr, _стом, ूको_,
+ {{0xd366896c,0xddd70201,0x68ed1e3f,0x2ca900f7}}, // _به_, _yaxş, _mhad, mhad_,
+ {{0x1eea80d5,0xca49003d,0x63ad036a,0x9a870a4c}}, // نونی_, _جلسه_, mzan, _сумл,
+ {{0x44269e40,0x63ad1e41,0x1ddb00d4,0x41e4102a}}, // _beo_, lzan, _मुमत, _гіта,
+ {{0x61fa01fa,0x68ed1e42,0x27e91e43,0x44269e44}}, // _útla, _nhad, _ozan_, _ceo_,
+ {{0x63ad1e45,0x44269e46,0xbf1587bd,0x7c2982ec}}, // nzan, _deo_, _ضواب, raer,
+ {{0xdfd88698,0x60c98a38,0xa7fb01df,0x68ed004f}}, // _със_, _hjem, xeña, _ahad,
+ {{0x008609a0,0x78a40a20,0x27e91e47,0x442681ca}}, // ално, _pliv, _azan_, _feo_,
+ {{0x7e639e48,0x672d8042,0xac198638,0x44269e49}}, // lenp, šaju, _роду_, _geo_,
+ {{0x6d439e4a,0x81bd80c8,0x63ad0669,0x249f81e0}}, // luna, _আরও_, jzan, mkum_,
+ {{0xe296806d,0x63ad1988,0xbb3a8039,0x8f9a83de}}, // _ваш_, dzan, _יעני, דישי,
+ {{0x6d439e4b,0x60db9e4c,0x68ed008c,0x20d900f7}}, // nuna, _okum, _fhad, réir_,
+ {{0x60c99351,0xa7fb040e,0x68ed1e4d,0x44269e4e}}, // _njem, seña, _ghad, _xeo_,
+
+ {{0xd4670572,0x3b071927,0x6d439341,0xe3bf1e4f}}, // [1000] шите_, ието_, huna, _peña_,
+ {{0x29079e50,0x3ce01e51,0x60c9846d,0x248d83ba}}, // _kuna_, sliv_, _ajem, hjem_,
+ {{0x6d439e52,0x29079e53,0x249f808b,0x64599e54}}, // juna, _juna_, kkum_, _obwi,
+ {{0x3ea59e55,0x29079e56,0x26d81e57,0x315680be}}, // _allt_, _muna_, loro_, ייען_,
+ {{0x29079e58,0xe3bf04c3,0x63ad009a,0x60c98168}}, // _luna_, _teña_, czan, _djem,
+ {{0x44269e59,0x8d768416,0x60c985b4,0x64599e5a}}, // _seo_, لاعا, _ejem, _abwi,
+ {{0x6d43859c,0x290783c3,0x7d78803d,0x6e2a9e5b}}, // guna, _nuna_, امبر_, rafb,
+ {{0x26d81e5c,0xd7668065,0x60c99e5d,0x2ca01e5e}}, // horo_, _پارٹ, _gjem, nkid_,
+ {{0x44269e5f,0x64958013,0x29079e60,0xb5fc81b9}}, // _veo_, _páir, _auna_, _raġu,
+ {{0x29079e61,0x781f0e00,0x6d5e1e62,0x3a278118}}, // _buna_, पर्क_, _oppa, _cenp_,
+ {{0x26d81e63,0x44269e64,0x63ad1e65,0x27e004b9}}, // doro_, _teo_, zzan, _ayin_,
+ {{0xd83f83c1,0x7ae19e66,0x644b8122,0x6b9b9e67}}, // _účtu_, allt, _gcgi, byug,
+ {{0x6d5e1e68,0x64958013,0x61050110,0x27e0008e}}, // _appa, _táir, _vėli, _cyin_,
+ {{0x26d81e69,0x7d089e6a,0xd0650085,0x291a008e}}, // goro_, _kuds, _əsər, kppa_,
+ {{0x29078057,0x6e289e6b,0x68ed1e6c,0x3cf3016f}}, // _guna_, _medb, _thad, ंगले_,
+ {{0x1b1980c8,0x63ad1e6d,0x7d089b6c,0x7cc180eb}}, // _দিতে_, tzan, _muds, tēri,
+ {{0x26d81e6e,0x6d439e6f,0x2ca91e70,0x78bd1e71}}, // [1010] boro_, zuna, rhad_, misv,
+ {{0x26d81e72,0x60c995db,0x29079e73,0x6d4397e2}}, // coro_, _sjem, _yuna_, yuna,
+ {{0xceb20159,0x2f0b8022,0x83fd8019,0x249f9e74}}, // _זיי_, _søg_, ndőr, ykum_,
+ {{0x6d438365,0x78bd1e75,0x68e29e76,0xa49b07f1}}, // vuna, nisv, glod, _diòc,
+ {{0x95e90065,0x7c2d1e77,0x7e639e78,0x7ae19e79}}, // _جبکہ_, maar, tenp, yllt,
+ {{0x7c2d1e7a,0x6d439e7b,0x7d089e7c,0x22950174}}, // laar, tuna, _buds, _الآس,
+ {{0x365c0039,0x2ca681a3,0x60c9942f,0x248d807a}}, // דכונ, _blod_, _tjem, tjem_,
+ {{0x58d49e7d,0x7c2d1e7e,0x60db9e7f,0x26d801bc}}, // _дост, naar, _ukum, zoro_,
+ {{0x290783c3,0x6d439e80,0xf77384c0,0x248d9e81}}, // _suna_, suna, _باز_, rjem_,
+ {{0x6d439e82,0x249f9e83,0x7c2d1e84,0x29000074}}, // puna, skum_, haar, _siia_,
+ {{0xf769004c,0x26d80698,0x6f0986a5,0x6f161e85}}, // _רק_, voro_, _huec, _styc,
+ {{0x26d81e86,0x7c2d1e87,0x645982a0,0x290780fe}}, // woro_, jaar, _ubwi, _vuna_,
+ {{0x442d9e88,0x6f0981ca,0x7c2d1e89,0x290002f1}}, // mae_, _juec, daar, _viia_,
+ {{0x442d9e8a,0x64499e8b,0x6f098144,0xc7d6807c}}, // lae_, lgei, _muec, מורי_,
+ {{0x26d81e8c,0x776d03a8,0xf1cf8651,0x62839e8d}}, // roro_, _esax, _सुचन, _inno,
+ {{0x7c2d1e8e,0x26d81e8f,0x64499e90,0xdd8f1e91}}, // gaar, soro_, ngei, روق_,
+ {{0x09b500c8,0x2ca000ee,0x442d8118,0xbebb00f1}}, // [1020] _জুলা, rkid_, iae_, rsër,
+ {{0x442d9e92,0xe3a78065,0x2ca01e93,0xe7db8327}}, // hae_, _ہر_, skid_, _बड़प,
+ {{0x7c2d1e94,0x9e7b00be,0xed4594a0,0x04ca00f7}}, // baar, _ענטפ, _تھ_, _مودي_,
+ {{0x6c861e95,0x6e2e1e96,0x777b9e97,0x7c2d0079}}, // _الحم, labb, _brux, caar,
+ {{0x44201e98,0x3ebc8bfa,0x7d08803a,0x442d9e99}}, // mbi_, tivt_, _suds, dae_,
+ {{0xcd2909d7,0x6e2e1e9a,0x6f099e9b,0x62951e9c}}, // _حسین_, nabb, _duec, _gozo,
+ {{0x7d088359,0x61431e9d,0x649581a8,0x2b401e9e}}, // _quds, _нера, _páip, lric_,
+ {{0x62839e9f,0x5b148d91,0x6e2e1ea0,0x44201ea1}}, // _anno, омит, habb, nbi_,
+ {{0xdc9b0158,0x213e831d,0x78a2816b,0x657a8114}}, // טיקל, wrth_, mkov, _wrth,
+ {{0x290a1ea2,0x78a29ea3,0xd91a9ea4,0x6e2e1ea5}}, // _kuba_, lkov, льм_, jabb,
+ {{0xd48f9ea6,0x7c2d02a3,0x290a1ea7,0x442d82b8}}, // _ер_, yaar, _juba_, bae_,
+ {{0x442000fe,0x7d1c016b,0x62838c2e,0x2ca68122}}, // jbi_, dprs, _enno, _ulod_,
+ {{0x7c2d1ea8,0x44200805,0x2bd811bc,0xa7fb165f}}, // vaar, dbi_, _भुला, reño,
+ {{0x442d1234,0x29181ea9,0x7c2d02b5,0xae1405fc}}, // _že_, _otra_, waar, तुलन_,
+ {{0x3abb804c,0x7c2d1eaa,0x8af00201,0x6ca702eb}}, // _תמונ, taar, ktəb, браж,
+ {{0x44201eab,0x941913f1,0xfbe98326,0xd4981eac}}, // gbi_, ржат_, _øªù_, бру_,
+ {{0x7c2d1ead,0x61150201,0x78a2826f,0x6e2e1eae}}, // [1030] raar, _işlə, dkov, babb,
+ {{0x7c2d1eaf,0x7d028068,0xf8bf0036,0xff5001a8}}, // saar, _lios, lié_, _أخي_,
+ {{0x290a1083,0xe3bf04c3,0x6f09957a,0x44201eb0}}, // _cuba_, _teño_, _suec, bbi_,
+ {{0x290a00a4,0x6d471eb1,0xec6b0a0e,0xfc3f128a}}, // _duba_, muja, _срок_, _zlín_,
+ {{0x7c208713,0x6d4704be,0x6d5500ad,0x44391eb2}}, // dbmr, luja, ltza, _ids_,
+ {{0x442d9eb3,0x442b1eb4,0x29071571,0x44391eb5}}, // wae_, _hec_, ínas_, _hds_,
+ {{0x6d5500ad,0x442b0101,0x290a1eb6,0x6d47007a}}, // ntza, _kec_, _guba_, nuja,
+ {{0x6e2e1eb7,0xdfd58790,0x78a287df,0x6d550102}}, // zabb, _добы, ckov, itza,
+ {{0x442d9eb8,0x290a1eb9,0x7d029eba,0x442b1ebb}}, // rae_, _zuba_, _dios, _mec_,
+ {{0xadf9000f,0x442b01a9,0x6d471ebc,0x27ed8102}}, // ंधान_, _lec_, kuja, _izen_,
+ {{0xf8bf02be,0xa88a1ebd,0x44391ebe,0x7d028046}}, // fié_, айна_, _ods_, _fios,
+ {{0x25d6893f,0x44391ebf,0x442b1ec0,0x6d4701ac}}, // _ווען_, _nds_, _nec_, duja,
+ {{0x2cad813c,0x78a99ec1,0x6e2e1ec2,0x60cd1ec3}}, // mhed_, _mlev, tabb, _djam,
+ {{0x78a99ec4,0x05db0105,0x78a2826f,0x9c130135}}, // _llev, _मुहब, zkov, _kọwa,
+ {{0x6e2e1ec5,0x78a983ff,0xde03035f,0xfc3f0118}}, // rabb, _olev, дпри, _clío_,
+ {{0x03260698,0x44201ec6,0x2cad806a,0x290a1ec7}}, // ждан, ubi_, nhed_, _ruba_,
+ {{0x78a29ec8,0x290a1ec9,0x7c2b9eca,0x3eba09ca}}, // [1040] vkov, _suba_, _megr, _tmpt_,
+ {{0x44201ecb,0x645d0609,0x644f047f,0xa01b0106}}, // sbi_, _ebsi, _ecci, ljöp,
+ {{0x78a294f0,0x78a98022,0x38669ecc,0x2d780289}}, // tkov, _blev, teor_, _uče_,
+ {{0xddc89807,0x290a00b4,0x7a400091,0x803703de}}, // _nadš, _vuba_, _bátà, ַנצע_,
+ {{0x78a294f0,0xf8b30051,0x7d0281a8,0x61e38366}}, // rkov, ושא_, _rios, _lynl,
+ {{0x7d029ecd,0x290a1ece,0x877b010f,0x7b09129b}}, // _sios, _tuba_, יאני, _džud,
+ {{0x7c2b9ecf,0x78a285b7,0x27ed9ed0,0x98a69ed1}}, // _begr, pkov, _ezen_, _диве,
+ {{0x61ee012b,0x442b1ed2,0x2cad9ed3,0x78a987f1}}, // _izbl, _xec_, ghed_, _glev,
+ {{0x7cc18029,0xccf302f6,0x320d080a,0xf8bf1ed4}}, // vērt, וכה_, _şeyi_, vié_,
+ {{0x23620748,0x7d029ed5,0x290483e4,0x60cd1ed6}}, // _mpkj_, _wios, _hima_, _sjam,
+ {{0x61e38355,0xc27b010f,0x29049ed7,0x291880f1}}, // _cynl, בריי, _kima_, ëra_,
+ {{0x7c2b96a1,0xb87b00f7,0x29049ed8,0xbb8695a9}}, // _gegr, rlío, _jima_, _ولاي,
+ {{0x24869ed9,0x29189482,0x29049d96,0x81d600ab}}, // _inom_, ūra_, _mima_, াইট_,
+ {{0x26dc8289,0x6d471eda,0x442b1edb,0x6f1d0bfd}}, // novo_, tuja, _sec_, rpsc,
+ {{0xdcbb1edc,0x442b1edd,0xf8bf1ede,0x61e38114}}, // аща_, _pec_, pié_, _gynl,
+ {{0x29048353,0x62988efc,0x5b151edf,0x6d55011e}}, // _nima_, _hovo, змат, rtza,
+ {{0x442b0c79,0x62989c24,0x2486805c,0x63a09ee0}}, // [1050] _vec_, _kovo, _mnom_, nymn,
+ {{0x78a9800d,0x2904826b,0x291e808e,0x78bb8088}}, // _slev, _aima_, opta_, _smuv,
+ {{0x24868052,0x29049ee1,0x89d900f7,0x26dc9ee2}}, // _onom_, _bima_, حوار_, dovo_,
+ {{0xf7718307,0x29049092,0x44391ee3,0x644f0081}}, // فات_, _cima_, _uds_, _ucci,
+ {{0x8e859ee4,0x7c2205e4,0xddc883f2,0x7c2b9ee5}}, // згле, ñore, _radš, _regr,
+ {{0xa1370077,0x7ce59ee6,0xd6db9ee7,0x31351715}}, // _ورزش, móri, ште_, _негр,
+ {{0x62871ee8,0x6f0d008c,0x78a9826c,0x2cad9ee9}}, // _injo, _luac, _tlev, thed_,
+ {{0x644d1eea,0x7cc180eb,0x78bb80b4,0x29049eeb}}, // ngai, vērs, _umuv, _gima_,
+ {{0x6f0d0013,0x2cad813c,0x649c80f7,0x61e38370}}, // _nuac, rhed_, _réit, _synl,
+ {{0xd2a98009,0x2cad8051,0x649c9eec,0x62989eed}}, // ское_, shed_, _béis, _covo,
+ {{0x62989eee,0xa9699eef,0x21699ef0,0x2b490144}}, // _dovo, щина_, щини_, huac_,
+ {{0x2009002a,0x6f0d0834,0x92cb80ab,0x67299ef1}}, // _cgai_, _buac, লতে_, _kwej,
+ {{0x61fe803e,0x291e8087,0x7ea200e7,0x2d8512f1}}, // _úpln, apta_, _hôpi, ålen_,
+ {{0x6d439ef2,0x6f0d03ac,0xe3b0826a,0x59bd047d}}, // orna, _duac, _جرم_, ्पार,
+ {{0xb7bd8087,0xf3f98087,0xa5d606a7,0x10a61ef3}}, // _deţi, deţi_, _मुखौ, _никн,
+ {{0x649c80f7,0x62871ef4,0x6ab406a7,0x5f761ef5}}, // _héir, _anjo, ंफ्र, _فاخر,
+ {{0x7ce59ef6,0x6b6309c7,0x7cd38162,0x69c3002a}}, // [1060] góri, нкта, căre, únen,
+ {{0x6d410013,0x644d008c,0xe2970162,0x6d438bcf}}, // álac, agai, пау_, krna,
+ {{0xfe9b00be,0x386001b9,0xd24f1ef7,0x80669ef8}}, // _היימ, _kbir_, انم_, звеж,
+ {{0x649c8013,0x62871ef9,0xe7f3001b,0x2f10823e}}, // _léir, _enjo, _घरमा_, _pàg_,
+ {{0x44321efa,0x7c241efb,0x66e69efc,0x6d439efd}}, // may_, mbir, пода, erna,
+ {{0x7f429efe,0x44321eff,0x332001e0,0xb603816b}}, // rroq, lay_, mpix_, _otáč,
+ {{0x29048503,0x6298817f,0xacf881e2,0x490b801b}}, // _tima_, _rovo, онку_, _ठूलो_,
+ {{0x44321efa,0x26dc825b,0x7c241f00,0x649c9995}}, // nay_, sovo_, nbir, _réis,
+ {{0x62989f01,0x7d061f02,0x33200e23,0x6d438427}}, // _povo, _miks, npix_, arna,
+ {{0x44321f03,0x2cfa05fc,0x7d061f04,0x60c29f05}}, // hay_, ्दुल_, _liks, liom,
+ {{0x05db0576,0xcc128133,0x80b083eb,0xb7bd8087}}, // _मुंब, _bọtị, ंसपे, _reţi,
+ {{0x48ab19fe,0x7d061f06,0x24868bb1,0x75d381a8}}, // стом_, _niks, _unom_, _ثيما,
+ {{0x7c241f07,0x44321f08,0x6298807a,0x765c0428}}, // dbir, day_, _tovo, ffry,
+ {{0xd24f80a0,0x7c241f09,0x9c13019d,0x291e9f0a}}, // _منه_, ebir, _lọta, ppta_,
+ {{0x443201b4,0xda5b8496,0x6d4a9f0b,0xc9869f0c}}, // fay_, _הכול, nufa, _хули,
+ {{0x44321f0d,0x7ce580a9,0xb4d7023c,0x7c241f0e}}, // gay_, tóri, ाके_, gbir,
+ {{0x2d8c1f0f,0x7d061f10,0xf3f9802e,0xddde011c}}, // [1070] äder_, _diks, teţi_, _tapş,
+ {{0x78ad0a56,0x290e9f11,0x6d4a9f12,0x644d1f13}}, // _hlav, _kufa_, kufa, sgai,
+ {{0x44321f14,0x7c241f15,0xa9070591,0x7ce58187}}, // bay_, bbir, سبان, sóri,
+ {{0x443202c1,0x26c31f16,0xd138102a,0x6d4a9f17}}, // cay_, lijo_, яху_, dufa,
+ {{0x3b640021,0x7eaf80e8,0x9c1301bc,0xb4e90ec5}}, // _първ, _løpe, _dọta, यती_,
+ {{0x78ad1f18,0x6d4382a5,0x26c30353,0x628704b7}}, // _llav, trna, nijo_, _unjo,
+ {{0x60c29f19,0x672480eb,0x2d571f1a,0x290e89ab}}, // biom, ģija, nçe_, _nufa_,
+ {{0xc9520039,0x60c28706,0xdb009a1f,0x6729838a}}, // _שמן_, ciom, lymè, _twej,
+ {{0x1ee7819f,0x2d570214,0x81df00ab,0x8f55803d}}, // _فوری_, hçe_, দেশ_, _زناش,
+ {{0x44321f1b,0xb4e91f1c,0x2d5702d0,0x26c31f1d}}, // zay_, यतु_, kçe_, jijo_,
+ {{0x81df150b,0x443202c1,0x26c31f1e,0x78ad1f1f}}, // দের_, yay_, dijo_, _blav,
+ {{0xcea91a63,0x443202c1,0xb4d703bb,0x78ad0018}}, // _די_, xay_, ाको_, _clav,
+ {{0x610c07d9,0x44320205,0x7d061f20,0x26c3007a}}, // _işle, vay_, _riks, fijo_,
+ {{0x44321f21,0x60c2809a,0x78ad1f22,0x7d061f23}}, // way_, ziom, _elav, _siks,
+ {{0x44321f24,0x998583f8,0xfce61f25,0x7d060077}}, // tay_, _آلبو, дово, _piks,
+ {{0x442f9f26,0x1ad580ab,0x3ce91f27,0x61fa008b}}, // _jeg_, _হওয়া, glav_, _útli,
+ {{0x442f9f28,0xa8570051,0x7c241f29,0x44321f2a}}, // [1080] _meg_, _חיפה_, rbir, ray_,
+ {{0x44321f2b,0x26c30db7,0x7c241503,0x7d060242}}, // say_, cijo_, sbir, _wiks,
+ {{0x44321f2c,0xa96a045b,0x7d061f2d,0xd91000d7}}, // pay_, تمام_, _tiks, ایر_,
+ {{0x442f86c0,0x44320079,0x53ad83eb,0x7c2f1f2e}}, // _neg_, qay_, टनाश, _gecr,
+ {{0x3eac82c4,0x69d51f2f,0x7ae8805f,0x7ae385ee}}, // _pldt_, _exze, yldt, _oknt,
+ {{0xd25212dc,0x442f8006,0x6d589f30,0x60c29f31}}, // _جنس_, _aeg_, ttva, siom,
+ {{0x442f9f32,0xafdb0edd,0x20569a1b,0x443d8609}}, // _beg_, ngør, _отар, _bdw_,
+ {{0x63a402a3,0x442f8069,0x290e9f33,0x26c3007a}}, // oyin, _ceg_, _rufa_, zijo_,
+ {{0x63a41f34,0x442f9f35,0x629c1f36,0x9c13019d}}, // nyin, _deg_, _koro, _kọra,
+ {{0x68e41f37,0x629c1f38,0x395801c0,0x3b0780e7}}, // _akid, _joro, _kqrs_, _cinq_,
+ {{0x67228029,0x6da6845a,0x629c1f39,0x26c30353}}, // lpoj, _تمام, _moro, vijo_,
+ {{0x629c0397,0xa9c380e8,0x3f861f3a,0x3cfa0035}}, // _loro, тськ, _šoua_, ्दों_,
+ {{0xceeb1c12,0x26c3007a,0xe81e0035,0x7c2f1f3b}}, // قرآن_, tijo_, पड़ा_, _recr,
+ {{0x629c1f3c,0x442f80f3,0xb4d709c1,0x290e9f3d}}, // _noro, _zeg_, ाक्_, _tufa_,
+ {{0xd49b1f3e,0x26c30353,0x6f08951e,0x26ce9f3f}}, // ора_, rijo_, _midc, info_,
+ {{0xb4e91885,0x6b829f40,0x9f5500e8,0x26c3007a}}, // यते_, _krog, _звич, sijo_,
+ {{0x629c1f41,0x7ce580f7,0x7c220511,0x26c3007a}}, // [1090] _boro, mórt, ñora, pijo_,
+ {{0x27e91f42,0xddc38162,0x672d04be,0x68fd1f43}}, // _iyan_, nenţ, _iwaj, lmsd,
+ {{0x610c080a,0x629c1f44,0x7c2f0214,0x2a781f45}}, // _oğlu, _doro, _tecr, _darb_,
+ {{0x6b829f46,0x2cb2031d,0x27e900a4,0x38a21f47}}, // _orog, chyd_, _kyan_, _fóru_,
+ {{0x442f9f48,0x63b60063,0x629c1f49,0xb8d78105}}, // _reg_, czyn, _foro, _छत_,
+ {{0x442f8cde,0xd3560051,0x629c1f4a,0x9c13019d}}, // _seg_, _אישי_, _goro, _tọsa,
+ {{0x26c981dd,0xddc3802e,0x201281a8,0x672d1f4b}}, // đao_, denţ, íniú_, _lwaj,
+ {{0x628a9f4c,0xf1b200be,0xeb999508,0x394c82c4}}, // _anfo, _עסן_, зии_, duds_,
+ {{0x27e9076d,0x68f60114,0x6b829f4d,0x442f9f4e}}, // _nyan_, _rhyd, _crog, _veg_,
+ {{0x442f8574,0xddc38087,0x291101d4,0x2ef49ac0}}, // _weg_, genţ, _kuza_, _азур,
+ {{0x442f9f4f,0x6b829f50,0xa6db007b,0x2a6300b9}}, // _teg_, _erog, nuðu, _mbjb_,
+ {{0x6b829f51,0x2d98809f,0x8cc10327,0xb4e9103e}}, // _frog, _àrea_, रोमो, यतो_,
+ {{0xc00000c8,0x1b1980c8,0xd85001bc,0x7ba780f7}}, // ূর্ণ_, _দিকে_, _fọnt_, _تصام,
+ {{0x539a8451,0x2f1400f2,0x27e9020d,0xafdb0366}}, // _מינו, _väg_, _dyan_, tgør,
+ {{0x629c049c,0x98a781d0,0xcc128133,0x672d0032}}, // _roro, éně_, _kọpị, _ewaj,
+ {{0x68e40067,0x629c1f52,0x63a41f53,0xfce301a1}}, // _ukid, _soro, tyin, госо,
+ {{0xca860071,0x27e91f54,0x672d0041,0xd8d700be}}, // [10a0] нгай, _gyan_, _gwaj, _רופט_,
+ {{0x63b6009a,0x29111f55,0xddc18609,0x996e811c}}, // rzyn, _buza_, _balż, nəş_,
+ {{0xc8798086,0xdca61612,0x065700be,0xc8669289}}, // _baş_, _зани, _אייך_, етли,
+ {{0x60c60867,0x2ca9016d,0x7ae19f56,0x6d4e1f57}}, // nikm, ckad_, holt, muba,
+ {{0x7ae19f58,0x629c1f59,0x6d4e1f5a,0xb05b1f5b}}, // kolt, _toro, luba, onär,
+ {{0x26ce9f5c,0x7cd38087,0x7e7c0267,0x1fdf80ab}}, // unfo_, văra, ndrp, _বৃহস,
+ {{0x23668669,0x2911011f,0xddc38087,0x7ae19f5d}}, // _spoj_, _guza_, venţ, dolt,
+ {{0x91e31f5e,0x6d5c1f5f,0x67228198,0x39470118}}, // _сосе, itra, ppoj, ánse_,
+ {{0x290901e0,0x6d4e1f60,0xddc38087,0x68e29f61}}, // _riaa_, huba, tenţ, mood,
+ {{0x6d4e1f62,0x8af00085,0x394c8722,0x3f99841c}}, // kuba, xtəl, tuds_, _àsua_,
+ {{0xe3af84c0,0x6d5c0503,0x0c2685e9,0xc87983bf}}, // لری_, jtra, _змен, _yaş_,
+ {{0xeb9a81f3,0x01370039,0xe61f8028,0x6d4e1f63}}, // _ние_, ערכת_, _đôi_, duba,
+ {{0x7ae18ede,0xb4be9f64,0x28c006b7,0xdefb0009}}, // bolt, ुची_, शोधि, зыв_,
+ {{0x7ae19f65,0x68e28079,0x6d5c1f66,0x38a200f7}}, // colt, hood, ftra, _sórt_,
+ {{0x6d5c1f67,0x68e28ebc,0x45d41a0b,0x6e350968}}, // gtra, kood, горс, razb,
+ {{0xdef804b7,0x8af00085,0x7e7a808e,0x29111f68}}, // ċċa_, stəl, _latp, _ruza_,
+ {{0x68e282a3,0x2911005c,0x6d5c1f69,0x2ca91f6a}}, // [10b0] dood, _suza_, atra, rkad_,
+ {{0x7e7a805c,0x644800eb,0x6d4e1f6b,0x2ca91f6c}}, // _natp, _ēdie, buba, skad_,
+ {{0x7c361f6d,0xc9841f6e,0x3f849f6f,0x6720810c}}, // bayr, луци, _armu_, _atmj,
+ {{0xc5f2078d,0x68e28079,0x90e780d7,0x7ae18061}}, // _כדי_, good, رسان, zolt,
+ {{0x44291f70,0x4c9a04de,0xef18804a,0x643603de}}, // mba_, _חברו, емі_, _בארא_,
+ {{0x44291f71,0x07a282a9,0x7bdc8457,0x8fa29f72}}, // lba_, рашн, ğrul, раше,
+ {{0x691780eb,0x44291f73,0xc87982d0,0x68e29f74}}, // _aģen, oba_, _taş_, bood,
+ {{0xfebb8077,0x44291f75,0x4c859631,0xc48581a1}}, // _کارت_, nba_, тлив, тлик,
+ {{0xe803000d,0x44291f76,0xa3de8105,0x6d4e1f77}}, // रेमा_, iba_, _दुआ_, zuba,
+ {{0x6d4e1f78,0x44291f79,0x6d5c1f7a,0x2b4d87f1}}, // yuba, hba_, ytra, ruec_,
+ {{0x26c79f7b,0x61ea8355,0x60c61f7c,0x44290019}}, // mino_, _cyfl, tikm, kba_,
+ {{0x44291f7d,0x7ae19f7e,0xf4858065,0x6d5c1f7f}}, // jba_, solt, _جائی, vtra,
+ {{0x7fd610ac,0x2d858699,0x98bf1f80,0x3f84001b}}, // тіні, _hrle_, nutę_, ímu_,
+ {{0xaa458002,0x6d5c1f81,0x44291f82,0x26c79f83}}, // _рекл, ttra, eba_, nino_,
+ {{0xed571f84,0x61ea831d,0x68e28079,0x7d0b8122}}, // кор_, _gyfl, yood, _kigs,
+ {{0x44291f85,0x6d4e1f86,0x7d0b9f87,0x26c79f88}}, // gba_, ruba, _jigs, hino_,
+ {{0x26c79f89,0x6d4e1f8a,0x7c299f8b,0x387d8428}}, // [10c0] kino_, suba, iber, ddwr_,
+ {{0xb2231baa,0x44291f8c,0x68e29f8d,0x7c299f8e}}, // имул, aba_, wood, hber,
+ {{0x44291f8f,0xf8c617a3,0xa3de800d,0x644b1f90}}, // bba_, _लगाय, _दुई_, ógic,
+ {{0x64a61f91,0x7e7a9d08,0x44291f92,0x251e0135}}, // _рама, _satp, cba_, _ọnwụ_,
+ {{0xdfcf8307,0x68e29f93,0x7c299f94,0xddc39f95}}, // ييم_, rood, dber, venš,
+ {{0x26c79f96,0x656880b9,0xb87b0061,0x3ce6806a}}, // gino_, _ppdh, llít, _skov_,
+ {{0xceb30051,0x3cde98b8,0x628e1f97,0x127b00be}}, // שיו_, खवले_, _inbo, _קאלע,
+ {{0x7c299f98,0x98a38ae7,0x7d0b8267,0xb4be93e5}}, // gber, _кисе, _cigs, ुचे_,
+ {{0x26c79f99,0x2d859f9a,0x6ef20366,0xaf068196}}, // bino_, _erle_, væbn, _апал,
+ {{0x26c79f9b,0xc3330039,0x44291f9c,0x7c299f9d}}, // cino_, סות_, zba_, aber,
+ {{0x7c299f9e,0x44291f9f,0x2d85817f,0x40351fa0}}, // bber, yba_, _grle_, ленс,
+ {{0x30a71fa1,0x44290079,0x93469fa2,0x6abf03eb}}, // тров, xba_, _анде, _एग्र,
+ {{0x2d8c0e23,0x58d41194,0x44291fa3,0x64429fa4}}, // åden_, роят, vba_, _idoi,
+ {{0x41dd9094,0xddda8110,0x60c40c2e,0x442910ba}}, // _नुकस, _natū, _emim, wba_,
+ {{0x55581fa5,0x7b091fa6,0x26cd00ce,0xd3088129}}, // каря_, _džul, đeo_, _dệt_,
+ {{0x629a825b,0x628e1fa7,0x44291fa8,0xa7750012}}, // ljto, _anbo, uba_, _блич,
+ {{0x09bd06bf,0x1be30327,0x93f51fa9,0x6d4a81ec}}, // [10d0] ्प्य, _कुशल_, упац, hrfa,
+ {{0x44291faa,0xf1a71fab,0xdd03007e,0x6d481fac}}, // sba_, крен, ırıc, ádac,
+ {{0x26c79fad,0xd011936d,0x81e480ab,0x7c299fae}}, // vino_, ملا_, পের_, yber,
+ {{0x673b0fda,0xf1c30110,0x26c79faf,0x7fd58196}}, // šuju, _rašė_, wino_, лілі,
+ {{0x78a089d1,0x3ea10074,0xb4c10701,0x7d0b8366}}, // _domv, _koht_, ्ची_, _rigs,
+ {{0x644284c3,0x7d0b8123,0x4df59980,0x7c299fb0}}, // _adoi, _sigs, ляйт, wber,
+ {{0x26c79fb1,0x7d0b9400,0x3ced9fb2,0xfc3f1b88}}, // rino_, _pigs, tlev_, _flít_,
+ {{0x7eb487f1,0xf7738e06,0xb87b0174,0x0cd18264}}, // _ràpe, _پار_, ilís, াত্ত,
+ {{0x26c79fb3,0x68e9846d,0x81e800ab,0x13099fb4}}, // pino_, _iked, মেদ_, нний_,
+ {{0xd7f89b67,0x3cff81c0,0x3ced9fb5,0x6b6701a9}}, // _рус_, smuv_, slev_, mīgā,
+ {{0x2455806b,0xb87b0c52,0x27fa809a,0xa0a61adb}}, // _مناس, roín, ępne_, ганд,
+ {{0xc32400ab,0x5fc502f1,0xd308827d,0x00000000}}, // _পিসি_, _लेहल, _sệt_, --,
+ {{0x7ae5043d,0x4c9a8e82,0x2a7c87f1,0x27ed8176}}, // koht, _רבנו, _favb_, _jyen_,
+ {{0x1bb888ca,0x99858061,0x7ae50198,0x63bb9fb6}}, // _جامع_, _ملتو, joht, mzun,
+ {{0x27ed8205,0x270f80bc,0xc8798214,0x8af00085}}, // _lyen_, ादुर_, _ekşi_, stək,
+ {{0x7b090858,0x60c41fb7,0x27ed8144,0x2cad9fb8}}, // _džum, _umim, _oyen_, nked_,
+ {{0x63a99fb9,0x63bb9fba,0x2f1986c0,0x27ed8db1}}, // [10e0] nyen, nzun, _nèg_, _nyen_,
+ {{0xfeba0077,0xb87b016b,0x290d8300,0x68e99fbb}}, // _ساخت_, slít, _aiea_, _aked,
+ {{0xe5a61a02,0x35a60071,0x673b007a,0x6da61ac9}}, // _сини, _санг, šujt, _сина,
+ {{0x270f8076,0xd7fa9fbc,0x25a51b9d,0x68fb9fbd}}, // ादूर_, нул_, älle_, _chud,
+ {{0xe3b21fbe,0x7e7e1fbf,0xbea6854c,0x6e3884e8}}, // _طرح_, _kapp, ладк, tavb,
+ {{0x7e7e04b7,0xddc71807,0xf12400d5,0x68e981bc}}, // _japp, lejš, _نظری, _eked,
+ {{0x7e7e1fc0,0x7b09173d,0xccf8801b,0x21751fc1}}, // _mapp, _ožuj, _opět_, _кутр,
+ {{0xddc71fc2,0x24801fc3,0x26ca1fc4,0x645600fc}}, // nejš, ndim_, hibo_, ngyi,
+ {{0xa3de85e8,0x63a99fc5,0x26ca1fc6,0x1bd4802e}}, // _दुख_, gyen, kibo_, ропя,
+ {{0x7e7e1fc7,0x5a3482dc,0x9e65853d,0xf1269fc8}}, // _napp, шнот, _نامن, льмо,
+ {{0xddc88fda,0x67240091,0x23ba846d,0xd9178198}}, // _hadž, _atij, _bàjé_, лья_,
+ {{0x657a810c,0x7e7e1fc9,0x2cad8039,0x63a99fca}}, // _isth, _aapp, cked_, byen,
+ {{0xa3e401a2,0x0a948c5c,0x7e7e02f7,0x248002a6}}, // _पुल_, _валю, _bapp, ddim_,
+ {{0x4b7b80be,0x7e7e1fcb,0xddc881c8,0x29021fcc}}, // _נאוו, _capp, _madž, amka_,
+ {{0x7e7e1fcd,0xccfb04ae,0x61ee0338,0x291c04e8}}, // _dapp, ећа_, _nybl, íval_,
+ {{0xb4c10361,0x64561fce,0x38c90065,0x61f59fcf}}, // ्चे_, ggyi, _جاتی_, üzle,
+ {{0x36d50364,0x5a351fd0,0xddc880d2,0x26ca02ec}}, // [10f0] _вопр, инат, _nadž, bibo_,
+ {{0x26ca1fd1,0x2f198247,0xccf8801b,0x68fb89ab}}, // cibo_, _règ_, _zpět_, _shud,
+ {{0x657a822c,0x386901b9,0x65951246,0x7ae51fd2}}, // _nsth, _kbar_, _тану, roht,
+ {{0xf77185ff,0x7e7e1fd3,0xddc71fd4,0x6b670ec3}}, // قات_, _zapp, cejš, tīgā,
+ {{0x7c2d1fd5,0x443b0086,0x69dc1fd6,0x2ab60646}}, // mbar, maq_, _exre, _sæbe_,
+ {{0x443b1fd7,0x3a29826c,0x6b6700eb,0x171c00be}}, // laq_, _đapo_, rīgā, עווע,
+ {{0x7d0f0065,0x5e5800be,0x40358530,0x68fb9fd8}}, // _kics, טיגע_, _вебс, _thud,
+ {{0x0b4585c2,0x63bb9fd9,0x443b0085,0x68fb826c}}, // анин, tzun, naq_, _uhud,
+ {{0x3f8901c0,0x2cad9fda,0x60cb9fdb,0x7c2d1fdc}}, // _nrau_, rked_, migm, ibar,
+ {{0x4e7887bd,0x2cad9fdd,0x52a99fde,0x63a99fdf}}, // _محمد_, sked_, твом_, ryen,
+ {{0x63a99fe0,0x67241fe1,0x7c2d1fe2,0x7e7e1fe3}}, // syen, _stij, kbar, _rapp,
+ {{0x254101e2,0xa3b510c5,0x7e7e1fe4,0x7645008e}}, // dėl_, जना_, _sapp, _adhy,
+ {{0x442d9fe5,0x7e7e1fe6,0xddc71fe7,0x26ca1fe8}}, // mbe_, _papp, vejš, tibo_,
+ {{0x7c3b97ec,0x442d9fe9,0x7b0901a1,0x6b8401e8}}, // laur, lbe_, _džuk, vvig,
+ {{0xceb28bea,0x3f89002e,0x3cfd81c5,0x3c420019}}, // חים_, _erau_, _khwv_, _lévő_,
+ {{0x0c261fea,0x7c2d1feb,0x442d9fec,0x3f890384}}, // рман, gbar, nbe_, _frau_,
+ {{0x442d9fed,0x3f891fee,0x24801fef,0x032308cc}}, // [1100] ibe_, _grau_, rdim_, ждун,
+ {{0x236000f3,0x7c3b8010,0x6b841ff0,0x62819ff1}}, // rtij_, haur, rvig, ndlo,
+ {{0x6e3c0370,0x6aa39e72,0x7c2d1ff2,0x7c3b9ff3}}, // marb, _monf, bbar, kaur,
+ {{0x443b0086,0x442d9ff4,0x24800085,0x6e3c1ff5}}, // caq_, jbe_, qdim_, larb,
+ {{0x7d041ff6,0x442d8ee1,0x6fd680f7,0x7ccd80eb}}, // mmis, dbe_, جزائ, _mērķ,
+ {{0x44201ff7,0x7d041ff8,0x6b8b013c,0xddc89ff9}}, // lci_, lmis, ægge, _vadž,
+ {{0x40969285,0x61430972,0x38568081,0xd943151b}}, // _крат, _мера, _възс, _мери,
+ {{0x44201ffa,0x442d877f,0x387f811e,0x8fa68056}}, // nci_, gbe_, _gaur_, _кабе,
+ {{0x7d041ffb,0x44250085,0x3d16809a,0x29181ffc}}, // imis, _əl_, _पूरे_, _hura_,
+ {{0x29181ffd,0xf1eb0424,0x2ee78362,0x6d411d46}}, // _kura_, _जुड़_, conf_, šlai,
+ {{0x6e3c1ffe,0x442d9fff,0x81e800c8,0x44202000}}, // darb, bbe_, মের_, kci_,
+ {{0x3866a001,0x29182002,0x7c2d0079,0xd5778039}}, // nfor_, _mura_, xbar, _כתבה_,
+ {{0x29182003,0x7c22a004,0x7d040074,0x4ea72005}}, // _lura_, _ifor, dmis, арва,
+ {{0x44200063,0xa15886d2,0x6e3c2006,0x7d042007}}, // eci_, _баку_, garb, emis,
+ {{0x29182008,0x7c22a009,0x7d0f200a,0x78b6005f}}, // _nura_, _kfor, _pics, _flyv,
+ {{0x7d04200b,0x44220144,0x7c2d200c,0xb0350251}}, // gmis, _cfk_, ubar, _униш,
+ {{0x7c2d200d,0x443b200e,0x68ed200f,0x8afd800d}}, // [1110] rbar, raq_, _ikad, stře,
+ {{0x7c2d2010,0x442da011,0x7d040009,0x29182012}}, // sbar, zbe_, amis, _bura_,
+ {{0x29182013,0xa2d81344,0x7c228503,0x44202014}}, // _cura_, _मदर्, _ofor, bci_,
+ {{0x44202015,0x38698789,0x7c9500f7,0x6d552016}}, // cci_, ýar_, _الحص, muza,
+ {{0x3cf405b3,0x29181351,0x44392017,0x6d418668}}, // ंतरे_, _eura_, _ies_, _svla,
+ {{0x7c22a018,0x44392019,0x2918201a,0x7cd3802e}}, // _afor, _hes_, _fura_, mări,
+ {{0x4439201b,0x20d20038,0x63ad201c,0x2918201d}}, // _kes_, lšie_, lyan, _gura_,
+ {{0xec798dea,0x442d81ec,0x3cfd8282,0x9c5986d2}}, // упи_, ube_, _qhwv_, ушку_,
+ {{0x7c220511,0x6d55201e,0x60db804f,0x69ce80e1}}, // ñori, huza, _ijum, úben,
+ {{0x4439201f,0x7c3ba020,0x442da021,0x2b4d809f}}, // _les_, saur, sbe_, rrec_,
+ {{0x7c22831d,0x4439031d,0x63ad2022,0x6281a023}}, // _ffor, _oes_, hyan, rdlo,
+ {{0x44392024,0x63ad2025,0x3ea5a026,0x9c14019d}}, // _nes_, kyan, _holt_, _pọmp,
+ {{0xef198063,0x20d20038,0x442006d4,0x6e3c2027}}, // _już_, jšie_, vci_, tarb,
+ {{0xed5a2028,0x63ad10c1,0x249fa029,0x6d55202a}}, // лог_, dyan, ljum_, fuza,
+ {{0x3ea58813,0x7d040006,0x78a40364,0x6e3c0456}}, // _molt_, tmis, _toiv, rarb,
+ {{0x4439202b,0x25a500f2,0x4420202c,0xfd56819d}}, // _ces_, älla_, uci_, _albọ,
+ {{0x4439202d,0x4420202e,0x7e750f4c,0x64a302a9}}, // [1120] _des_, rci_, bezp, чата,
+ {{0x4420202f,0x3f8f0073,0x44392030,0x29122031}}, // sci_, _água_, _ees_, _hiya_,
+ {{0xc6930051,0x44392032,0x290001c0,0x8fc5003d}}, // _זאת_, _fes_, _khia_, _هزین,
+ {{0x92d800c8,0x44392033,0x7c3987d8,0xa14b0065}}, // াতে_, _ges_, _newr, اسلے_,
+ {{0x38668a38,0x63ad2034,0x80ca80ab,0x7cd38087}}, // rfor_, cyan, িকল্, cări,
+ {{0x7c22a035,0x60c9a036,0x291800a4,0x29120077}}, // _sfor, _emem, _tura_, _liya_,
+ {{0x05868087,0x11da00f7,0x29121142,0x237d80ee}}, // сулм, _صورة_, _oiya_, _aswj_,
+ {{0x0cb880c8,0x291204d2,0x3ce0812b,0x44390069}}, // ুক্ত, _niya_, čivo_, _xes_,
+ {{0xdfcf8013,0x99858084,0xe4fb114f,0x6d552037}}, // _كيف_, kalų_, ्षति_, zuza,
+ {{0x25618065,0x68ed08f8,0x9e66803d,0x29000870}}, // ból_, _skad, _دارن, _ahia_,
+ {{0xf09f009f,0x29122038,0x7648831d,0x7cd38087}}, // tjà_, _biya_, _iddy, zări,
+ {{0x29000028,0x63ad03c3,0x7c228aa2,0x9c14019d}}, // _chia_, yyan, _ufor, _dọkp,
+ {{0x29002039,0x7d1a8102,0x33f6936d,0x29121dd4}}, // _dhia_, _huts, _اساس, _diya_,
+ {{0x4439203a,0x7d1aa03b,0x20d201ac,0x6d55203c}}, // _ses_, _kuts, všie_, tuza,
+ {{0x443901c0,0x3b110609,0x2912203d,0x7cfe8168}}, // _pes_, _rizq_, _fiya_, përd,
+ {{0x4439203e,0x63ad203f,0x7d1aa040,0x6d551c88}}, // _qes_, tyan, _muts, ruza,
+ {{0x44392041,0xab870098,0x76489d30,0x7b141612}}, // [1130] _ves_, щувк, _oddy, здух,
+ {{0x2d8ca042,0x63ad2043,0x44392044,0x20d200e1}}, // _orde_, ryan, _wes_, ršie_,
+ {{0x44392045,0x63ad2046,0x7d1aa047,0x97d8015b}}, // _tes_, syan, _nuts, _نظیر_,
+ {{0x7648831d,0x20d201ac,0x515b83c8,0x43950098}}, // _addy, pšie_, רכאו, _дамс,
+ {{0x291e0118,0xf7458087,0x63ad00b9,0x2d8c9339}}, // _étan_, цело, qyan, _arde_,
+ {{0x25618065,0x6b8d911b,0x7d1a8db1,0xa3d60a75}}, // tól_, _šago, _buts, िपर_,
+ {{0x3ea58117,0x62952048,0x2ca6a049,0x045981a8}}, // _volt_, _inzo, _bood_, للغة_,
+ {{0xc325a04a,0x25618065,0x60dba04b,0x0325a04c}}, // омик, ról_, _ujum, один,
+ {{0x2ca6a04d,0x7b09026c,0x249f807b,0x290001c0}}, // _dood_, _džuv, rjum_, _rhia_,
+ {{0x6285204e,0x291204d2,0x02c5835f,0x2900204f}}, // ndho, _siya_, ійно, _shia_,
+ {{0xe0da0b9c,0x7d1aa050,0xd4e7835f,0x2bd00ebf}}, // иво_, _guts, _люди, _सेना,
+ {{0x2ca68051,0x290010af,0x3f8d8168,0x237d81c5}}, // _good_, _qhia_, _kreu_, _tswj_,
+ {{0x443fa051,0x03a58a13,0x2bd08074,0x6d439345}}, // mau_, _фило, _तइया, lsna,
+ {{0x443fa052,0x645ba053,0x6729a054,0x6b89a055}}, // lau_, lgui, _mtej, mveg,
+ {{0x29002056,0x2d8c2057,0x62850197,0x386da058}}, // _thia_, íde_, ddho, _ober_,
+ {{0x443fa059,0x62851175,0x3f8601a1,0x645ba05a}}, // nau_, edho, _šouu_, ngui,
+ {{0x42ca0d45,0x5aca01bb,0x6b89a05b,0x6283a05c}}, // [1140] аган_, алам_, nveg, _kano,
+ {{0x386d834a,0x443fa05d,0x2ca00859,0x7cd38087}}, // _aber_, hau_, sjid_, măru,
+ {{0x443fa05e,0xdca6205f,0x64a604ae,0x6729a060}}, // kau_, _дани, _дана, _atej,
+ {{0x186aa061,0x443fa062,0xa06aa063,0x7d1aa064}}, // ради_, jau_, рада_, _ruts,
+ {{0x443fa065,0x27e0a066,0x61402067,0x3f8da068}}, // dau_, çin_, _bálá, _creu_,
+ {{0x44322069,0x29068102,0x9f610019,0x27e081b9}}, // lby_, smoa_, ését_, ħin_,
+ {{0x68eb9148,0x4432206a,0x443f8114,0x672981bc}}, // bogd, oby_, fau_, _etej,
+ {{0xa3ae07e6,0x7c24206b,0x443f831d,0x5c988098}}, // _कथा_, ncir, gau_, ския_,
+ {{0x3f8da06c,0xae7a8364,0x6283a06d,0x7d1a80fc}}, // _greu_, _всех_, _bano, _wuts,
+ {{0x62839670,0x78a285a2,0x7d1aa06e,0x645b906c}}, // _cano, ljov, _tuts, agui,
+ {{0x443fa06f,0xe29a8104,0x69c12070,0x60cf0079}}, // bau_, _chưa_, _žlez, ticm,
+ {{0x78a281a1,0x6d58a071,0x44322072,0x00000000}}, // njov, muva, jby_, --,
+ {{0x44268870,0x6440a073,0x44322074,0x6d588406}}, // _ifo_, mami, dby_, luva,
+ {{0x6283a075,0x3ce98088,0x2bd08054,0xdea88041}}, // _gano, čave_, _तेया, _ƙoƙa,
+ {{0xa3d02076,0xeca709a5,0x9c8301d0,0x64408c53}}, // _वेब_, ојан, íčov, oami,
+ {{0x44322077,0xd4982078,0x6aaa01ec,0x19bb8039}}, // gby_, ору_, öffn, _כמוב,
+ {{0x628394be,0x7cd38162,0xdfd101bd,0x6d58a079}}, // [1150] _yano, căru, _سيد_, huva,
+ {{0x291ca07a,0x6440a07b,0x6d58a07c,0x4424801c}}, // _kuva_, hami, kuva, hcm_,
+ {{0xceb2093f,0x6440805b,0x6729800d,0xe2a68125}}, // _ניט_, kami, _stej, _áður_,
+ {{0x5d7a00be,0x3f8d809f,0x26d1207d,0x6d589adc}}, // _פארק, _preu_, lizo_, duva,
+ {{0x6440a07e,0x443f8110,0x291ca07f,0x7b6419a5}}, // dami, vau_, _luva_, _отре,
+ {{0x8af00201,0x4426a080,0x6d43a081,0x443fa082}}, // stər, _afo_, tsna, wau_,
+ {{0x443fa083,0x60cd2084,0x645ba085,0x7d02a086}}, // tau_, _omam, tgui, _bhos,
+ {{0x6440a087,0x62838364,0x6d43a088,0x3f8da089}}, // gami, _sano, rsna, _treu_,
+ {{0x443f8355,0x6d43a08a,0x7c3d208b,0x38a2009a}}, // rau_, ssna, _mesr, _góry_,
+ {{0x60cd208c,0x443fa08d,0x78a985f8,0x6283a08e}}, // _amam, sau_, _hoev, _qano,
+ {{0x4432208f,0x6283a090,0x443fa091,0x7d028083}}, // yby_, _vano, pau_, _fhos,
+ {{0x6283a092,0x291c8b80,0x44248c53,0x7d02a093}}, // _wano, _duva_, ccm_, _ghos,
+ {{0x7d09a094,0x69c18065,0x628382b8,0x4432003e}}, // mmes, szle, _tano, vby_,
+ {{0x7d09a095,0x78bb85a4,0x26d12096,0xe29a80ff}}, // lmes, _lluv, gizo_, _thưa_,
+ {{0x4432026f,0xde032097,0x6da62098,0x443d83ec}}, // tby_, епри, чива, _kew_,
+ {{0xdca31e25,0x7d098214,0x03260009,0x61fc8214}}, // наси, nmes, здан, ürle,
+ {{0x44322099,0x26d11ea2,0xeb97209a,0x6d4101fa}}, // [1160] rby_, bizo_, пит_, álar,
+ {{0x4432209b,0x09e60ae7,0xb87b209c,0x7c24209d}}, // sby_, позн, gníf, scir,
+ {{0x7d09a09e,0x6440999f,0x78a28144,0x7cfe83ed}}, // kmes, yami, tjov, hëra,
+ {{0x443d804c,0xd12f07bd,0x6440a09f,0x60cd00b9}}, // _new_, امن_, xami, _xmam,
+ {{0x6440a0a0,0x8af00085,0x2cbf80e4,0x7d09a0a1}}, // vami, stəq, dhud_, dmes,
+ {{0x6d58a0a2,0xab270698,0xd94620a3,0x33fb01c6}}, // tuva, _хора_, _неги, _להכנ,
+ {{0x7d02a0a4,0xda788038,0x6440a0a5,0x205699fa}}, // _phos, veď_, tami, _нтар,
+ {{0x6d58a0a6,0x443d808e,0x78bba0a7,0x89db01c6}}, // ruva, _cew_, _gluv, _החלי,
+ {{0x6440a0a8,0xe7378364,0x6d58a0a9,0x291c9adc}}, // rami, _нет_, suva, _suva_,
+ {{0xc333004c,0x6440a0aa,0xbebb03ed,0x6d58a079}}, // עות_, sami, hpër, puva,
+ {{0x7d02a0ab,0x2eeea0ac,0x443d8039,0x705519ea}}, // _thos, moff_, _few_, وندا,
+ {{0x443da0ad,0xf8bf01d0,0x38bb03ed,0x644085e7}}, // _gew_, uhé_, _bëra_, qami,
+ {{0x26d1022e,0xb8e503ca,0x2d8c0366,0xdcb10129}}, // tizo_, _एत_, æden_, ểm_,
+ {{0xba178013,0x2eeea0ae,0x7bc41c28,0x6b82a0af}}, // _فيها_, noff_, dziu, _isog,
+ {{0xdd940084,0x26d120b0,0x443da0b1,0x35e400e8}}, // нары, rizo_, _yew_, ецтв,
+ {{0x6aaaa0b2,0x26d120b3,0x60cd20b4,0x20d201d6}}, // _hoff, sizo_, _umam, pšia_,
+ {{0x6aaaa0b5,0x2bc70c78,0xe803064a,0xa3d009a9}}, // [1170] _koff, _रेखा, रेखा_, _वेद_,
+ {{0x7b09025b,0x672d20b6,0x6aaaa0b7,0x6d488253}}, // _ažur, _itaj, _joff, _ovda,
+ {{0x2d8c0082,0x6aaaa0b8,0x7d09a0b9,0x2d8120ba}}, // ådet_, _moff, ymes, _ushe_,
+ {{0x92bf00ab,0x6d55192c,0xcfd20091,0x98a60162}}, // ুকে_, mrza, _apẹẹ, mplă_,
+ {{0x2489009c,0x7b09026c,0x629883b2,0x7feba0bb}}, // mdam_, _džur, _onvo, حراف_,
+ {{0x443d81b9,0x6b8d0314,0x248920bc,0x672d0c53}}, // _sew_, mvag, ldam_, _mtaj,
+ {{0xa3b50063,0x78a9838e,0x58d5835f,0x236920bd}}, // जनक_, _toev, _жовт, ltaj_,
+ {{0x645f01f6,0x672d20be,0xb87b0118,0xe3b9a0bf}}, // ngqi, _otaj, alíz, жби_,
+ {{0xa3e7890a,0x236920c0,0xf770803f,0x7d09a0c1}}, // _मुख_, ntaj_, _مال_, rmes,
+ {{0x88bd000d,0xeab180f7,0x25e88321,0x7d09a0c2}}, // zpět, ئعة_, _चुकी_, smes,
+ {{0x7643a0c3,0x6b82a0c4,0x41b4896b,0x672d20c5}}, // many, _esog, есят, _ataj,
+ {{0x6298a0c6,0x6d550042,0x628720c7,0x7643a0c8}}, // _envo, drza, _lajo, lany,
+ {{0xa49b0104,0x7d1e0024,0x6d55066f,0x9b94862c}}, // _phòn, _sups, erza, _пицц,
+ {{0x7643a0c9,0x62870b48,0x6aaaa0ca,0x24890dba}}, // nany, _najo, _goff, edam_,
+ {{0x672d20cb,0x9fa00cab,0x24890901,0x236918ad}}, // _etaj, _méér_, fdam_, etaj_,
+ {{0x7643a0cc,0xc10600f7,0x38bb03ed,0x7bc402f9}}, // hany, _توبي, _tëra_, tziu,
+ {{0x7643a0cd,0x52aa176e,0xcb670dc0,0x248601ac}}, // [1180] kany, овам_, мате_, ľom_,
+ {{0x628720ce,0x7643a0cf,0x0f5701c6,0x63ab1d31}}, // _cajo, jany, _קיים_, ägna,
+ {{0x7643a0d0,0x7e7c20d1,0x673ba0d2,0x5e9b01c6}}, // dany, merp, _awuj, _וביק,
+ {{0x6d5c20d3,0x6aaa9c7b,0x628705ee,0x673b818e}}, // mura, _चक्र, _eajo, _bwuj,
+ {{0x7643a0d4,0x6d5c0886,0x62888355,0xb906a0d5}}, // fany, lura, yddo, _पद_,
+ {{0x395a20d6,0x7643a0d7,0x3ced0503,0x670020d8}}, // pups_, gany, čeve_, ोगिक_,
+ {{0x6d5c20d9,0xf98320da,0x6aaaa0db,0x99878196}}, // nura, _игро, _roff, _menų_,
+ {{0x2ebb0894,0x6aaaa0dc,0x2eee8748,0x7e7c20dd}}, // _उत्त, _soff, soff_, herp,
+ {{0x7643a08c,0x7eb48580,0x60c29a1f,0xda7a847f}}, // bany, _làpi, mhom, оян_,
+ {{0x3ce9803b,0xd2508077,0x764382a0,0x60c2a0de}}, // čava_, دند_, cany, lhom,
+ {{0x28f905e9,0x6288831d,0x644420df,0x7e7c20e0}}, // зень_, rddo, kaii, derp,
+ {{0x6d5c20e1,0x2369009a,0x6720a0e2,0x049381a8}}, // dura, ytaj_, _kumj, _للمح,
+ {{0x6aaaa0e3,0x6f17008e,0xfc3f0144,0x60dd20e4}}, // _toff, _pixc, _reí_, rnsm,
+ {{0x6d5c0010,0x7e7c20e5,0x62988192,0x68ef0df6}}, // fura, gerp, _unvo, socd,
+ {{0x628720e6,0x60c2805d,0x7eb487f1,0xa49b0706}}, // _rajo, khom, _càpi, _fhòl,
+ {{0x7643a0e7,0x6d4720e8,0xb4fb20e9,0x3f8481a1}}, // zany, rsja, ्षेप_, _osmu_,
+ {{0x248920ea,0x7643a0eb,0x2d9e02af,0x2d8c0aa2}}, // [1190] rdam_, yany, äter_, åder_,
+ {{0x6d5c20eb,0x236920ec,0x9af500f7,0xb87b0144}}, // bura, rtaj_, ركات, coír,
+ {{0x236920ed,0x7643a0ee,0x6d5c20ef,0x6b8d20f0}}, // staj_, vany, cura, svag,
+ {{0x7643a0f1,0x2bd020f2,0xe80c20f3,0xe0df01e8}}, // wany, _सेवा, सेवा_, nnò_,
+ {{0x7643a0f4,0x7d0d20f5,0xf388801c,0x628720f6}}, // tany, mmas, _nợ_, _tajo,
+ {{0x7d0d20f7,0x442920f8,0xddde20f9,0x69c586ae}}, // lmas, lca_, _hapš, _üheg,
+ {{0x7643a0fa,0x3f8480eb,0x442907b6,0x00000000}}, // rany, _esmu_, oca_, --,
+ {{0x7d0d20fb,0x7643a0fc,0x60c2a0fd,0xb385824f}}, // nmas, sany, chom, елил,
+ {{0x7643a0fe,0x7cfe80f1,0x7d0d20ff,0x375580d7}}, // pany, përn, imas, _سپاس,
+ {{0x3eac805f,0x13068364,0x6d5c2100,0xde97806b}}, // _godt_, нный_, yura, _تجرب,
+ {{0xd12e817e,0xe4a7802e,0x98bf0110,0x644f1660}}, // یمی_, _ордо, nutė_, _odci,
+ {{0x76418117,0xf9920158,0xb4e9000d,0x7eb4809f}}, // _hely, ארט_, मको_, _ràpi,
+ {{0x44292101,0xe8df8870,0x42cd80ab,0x6d5c2102}}, // dca_, _abịa_, রকৌশ, wura,
+ {{0x91e591c7,0x09e5a103,0x6d5c2104,0xa3d0081f}}, // _поле, _полн, tura, _वेष_,
+ {{0x7641a105,0x44292106,0xa3d3864a,0x998581a8}}, // _mely, fca_, _हेत_, _ألبو,
+ {{0x44290af8,0x442b2107,0x7d0d2108,0x78ad0176}}, // gca_, _cfc_, gmas, _goav,
+ {{0x68e40133,0x245a82d0,0x42562109,0x6b8900fe}}, // [11a0] _ijid, lümü_, етет, _šegr,
+ {{0x6d5c210a,0x4429210b,0x160f897d,0xb05b1b48}}, // pura, aca_, ाधार_, nhän,
+ {{0x60c2805d,0x2726001c,0x442b0140,0x8c460703}}, // thom, _hôn_, _ffc_, _пене,
+ {{0x4429210c,0x70f680f7,0x7eb00074,0x03a6210d}}, // cca_, رسائ, _täps, _зимо,
+ {{0xb87b001b,0x20d2007a,0xa2b4801b,0x74160061}}, // lníc, jšim_, ेसम्, _سوسا,
+ {{0x7cfe80f1,0x60c2966a,0x2726210e,0x7c2ba10f}}, // tëro, shom, _môn_, _afgr,
+ {{0x91e60abe,0xf388801c,0x629c2110,0x27262111}}, // _чове, _sợ_, _inro, _lôn_,
+ {{0x7ae381a1,0x75218661,0x68e401bc,0x7c29823e}}, // _ajnt, _dulz, _njid, gcer,
+ {{0xdcfa8087,0x63a42112,0x2245a113,0x69080118}}, // _astă, nxin, falk_, iñed,
+ {{0x44292114,0xf3888028,0x7641a115,0x68e4026b}}, // zca_, _vợ_, _gely, _ajid,
+ {{0x7cfe80f1,0x6d1f0424,0x1dcf0105,0x44292116}}, // qëro, _मूंग_, _हेंत, yca_,
+ {{0xb87b003e,0x8d5595e3,0x7c29a117,0x254c81d0}}, // dníc, _штуч, ccer, měl_,
+ {{0xe29aa118,0xe80c05b3,0xb05b01ec,0x68e40b80}}, // _над_, सेला_, bhän, _djid,
+ {{0x68e40133,0x4429009a,0x6f1d8037,0x6da189ab}}, // _ejid, wca_, _èsce, mɓar,
+ {{0x6f1aa119,0x7d0d211a,0x4429211b,0x212d01d0}}, // _mitc, tmas, tca_, _čeho_,
+ {{0x4429211c,0xdc88002e,0xccf80eef,0x629c211d}}, // uca_, нсул_, ећу_, _anro,
+ {{0x4429211e,0x7d0d0859,0x98bf002e,0x628aa11f}}, // [11b0] rca_, rmas, astă_, _kafo,
+ {{0xa3d0035a,0xb87b03fb,0x68fd2120,0x6d4a8b81}}, // _वेळ_, bníc, llsd, ksfa,
+ {{0x395e809f,0x7641a121,0x442b2122,0xd99900f7}}, // nuts_, _rely, _tfc_, بنات_,
+ {{0x7641a123,0x5fd28074,0x64429995,0x628aa124}}, // _sely, _देहल, _neoi, _lafo,
+ {{0x81bc81a9,0x27260129,0x2bd08072,0x7eb02125}}, // ucēj, _xôn_, _तेला, _käpp,
+ {{0x7521a126,0x20d20503,0x69d5016f,0xf6538065}}, // _pulz, všim_, _येथी, ائش_,
+ {{0xada58098,0x644280f7,0x6d4aa127,0x395e8106}}, // тайл, _beoi, gsfa, juts_,
+ {{0xe7d2a128,0xeb9993f1,0x644281a8,0x3ce98796}}, // _देवप, дии_, _ceoi, čavo_,
+ {{0xb87b026f,0x26c103a7,0xe3b180f7,0x248b00b9}}, // zníc, _olho_, شرة_, _macm_,
+ {{0x290902c1,0x68f62129,0x628a8114,0x7eb0156e}}, // _ahaa_, _skyd, _cafo, _näpp,
+ {{0x395e8722,0x628aa12a,0x27260428,0xbbeb8019}}, // guts_, _dafo, _sôn_, _کرام_,
+ {{0x2d988510,0xb87b027f,0x6442a12b,0x290901bc}}, // _área_, vníc, _geoi, _chaa_,
+ {{0xb05b00f2,0x6a859630,0xb90a0424,0x773a0b79}}, // mhäl, _алла, _मद_, ьянс_,
+ {{0xb87b003e,0x395e87fa,0x7982009a,0x628a8114}}, // tníc, buts_, łowi, _gafo,
+ {{0xe643212c,0x26c100e1,0x200400e1,0xbea309a5}}, // респ, _dlho_, ťmi_, шарк,
+ {{0x248da12d,0xb87b026f,0x2726001c,0x29090133}}, // ndem_, rníc, _tôn_, _ghaa_,
+ {{0x46ea0b88,0xb87b016b,0x2c070a27,0x236d8069}}, // [11c0] _один_, sníc, _शरणं_, ntej_,
+ {{0x63a40509,0x3ce001c0,0x63a68106,0x6d4803a2}}, // rxin, sniv_, äkni, ádas,
+ {{0x26d80365,0x67240198,0xae5701c6,0xc96606d2}}, // miro_, _huij, _בסיס_, твей,
+ {{0x7995009a,0x26d802ec,0x38cb9190,0x6d4a84fe}}, // _drzw, liro_, وانی_, vsfa,
+ {{0x6d5a0110,0xf093007c,0xe7d28072,0x23d08035}}, // štad, ינא_, _देशप, _तेंद,
+ {{0x26d802a0,0x248d81b9,0x6d4a848d,0x6d58816b}}, // niro_, edem_, tsfa, trva,
+ {{0xe9a3212e,0x628a808b,0x71a301a1,0x7cfe83ed}}, // _карп, _rafo, _карз, sërm,
+ {{0x7cfe80f1,0x26d800b4,0xb866826a,0x628a831d}}, // përm, hiro_, _جاسو, _safo,
+ {{0x2c6d0019,0x2004805c,0xc4d30039,0x26d8020c}}, // lódó_, _uzmi_, יגה_, kiro_,
+ {{0xbe858bbe,0x395ea12f,0x248005e1,0x69c882af}}, // _مجمو, tuts_, heim_, tzde,
+ {{0x9f4f007b,0x6442a130,0x24802131,0xc8b682f1}}, // _ágú_, _teoi, keim_, _अकाट,
+ {{0x26d82132,0x9f352133,0xe3baa134,0x68e28428}}, // eiro_, лемі, _обе_, nnod,
+ {{0x35a8800f,0x26d8106c,0x26c1022c,0x68e28118}}, // _कपड़, firo_, _plho_, inod,
+ {{0x26d82135,0xbc1b8039,0x6b842136,0x7a4801a9}}, // giro_, _חודש, dwig, nūtē,
+ {{0x1df817ae,0x2ca902f1,0x20d20bcf,0x51f380d7}}, // веры_, tjad_, kših_, _پسور,
+ {{0x20d20353,0x851b082e,0x2d1b01bc,0xa3d683ca}}, // jših_, _ụgbọ_, _ụgbụ_, _सधा_,
+ {{0x68e2803e,0xc2990e02,0x26d82137,0x3a752138}}, // [11d0] dnod, нках_, biro_, ллар,
+ {{0x26d802a0,0x3f89026c,0x5a342139,0x78bd213a}}, // ciro_, _isau_, инут, lksv,
+ {{0x68e382ba,0x68e2831d,0xceb204de,0x7bc98338}}, // éndo, fnod, _היי_, zzeu,
+ {{0x61fc213b,0x78bd0bfd,0x68e28286,0x6b84018f}}, // _dyrl, nksv, gnod, bwig,
+ {{0xb9ff81b6,0x78bd0a0f,0xe9ff00ff,0x8afd81d0}}, // _उर्फ_, iksv, _trắc_, stři,
+ {{0x7d1d1010,0x7c2d17d6,0x62340992,0x7e6381c0}}, // _hiss, lcar, _веру, ugnp,
+ {{0x68e2a13c,0xc984213d,0x4519a13e,0xb05b01ec}}, // bnod, _кути, нция_, thäl,
+ {{0x7c2d213f,0x7d1d01b9,0xdb1e016b,0xa49b051e}}, // ncar, _jiss, _švéd, _bhòi,
+ {{0x7d1d2140,0x248da141,0xb05b1b48,0x26d82142}}, // _miss, rdem_, rhäl, yiro_,
+ {{0x248d8573,0x7bc981ec,0x236da143,0x27e081a8}}, // sdem_, rzeu, rtej_, úine_,
+ {{0x291e02be,0xdd918e06,0x50460071,0x186a151a}}, // _état_, _دوا_, ленб, нани_,
+ {{0x7c2d2144,0xd5ba80be,0x26d82145,0x25f700c2}}, // jcar, _אזעל, wiro_, _एड़ी_,
+ {{0xe28e8934,0x1cb88077,0x26d82146,0x7c2d2147}}, // _ла_, _جالب_, tiro_, dcar,
+ {{0x442da148,0x7c3ba149,0x7d1d214a,0x35d886a7}}, // lce_, lbur, _aiss, _भेड़,
+ {{0x649a00ae,0x6281a14b,0x26d81b19,0x7d1d1531}}, // нтар_, melo, riro_, _biss,
+ {{0x442da14c,0x7786214d,0x6281a14e,0x7d1d214f}}, // nce_, _близ, lelo, _ciss,
+ {{0x68e2811f,0x7c3ba150,0x60c42151,0xe29a0073}}, // [11e0] vnod, ibur, _ilim, наа_,
+ {{0xc3328051,0x6281a152,0x7bc00201,0x21a61ddf}}, // צוב_, nelo, _ümum, лизм,
+ {{0x442d800d,0x2d9619e3,0x7d1d2153,0x25a50004}}, // kce_, _српс, _fiss, ällt_,
+ {{0x64a62154,0xdca62155,0x7c2d2156,0x7d1d2157}}, // _сама, _сами, ccar, _giss,
+ {{0x442da158,0x6281a159,0x7c3ba15a,0x6d4e215b}}, // dce_, kelo, dbur, lsba,
+ {{0x5694a15c,0x6281a15d,0x7cfe83ed,0x6d5c215e}}, // ракт, jelo, mëri, orra,
+ {{0x6281a15f,0x64462160,0x442202f7,0x628e02c4}}, // delo, _keki, _kgk_, _iabo,
+ {{0x7c3b8135,0x442fa161,0x64462162,0x628e2163}}, // gbur, _cfg_, _jeki, _habo,
+ {{0x28a80b75,0x628e2164,0xb4e4873c,0x6281a165}}, // गानि, _kabo, _नदी_, felo,
+ {{0x49749663,0x6281a166,0x442da167,0x26c7801b}}, // алос, gelo, ace_, chno_,
+ {{0x60c42168,0x442d801b,0x628e2169,0x6d41831d}}, // _blim, bce_, _mabo, _gwla,
+ {{0x442da16a,0x3866a16b,0x7cfe80f1,0x4422216c}}, // cce_, ngor_, këri, _ngk_,
+ {{0xb4c08592,0xb9098870,0x7c22a16d,0x78bd216e}}, // ँसी_, _achọ_, _igor, rksv,
+ {{0x442d0d38,0x7d1d0006,0x628e1c73,0x4422216f}}, // _że_, _siss, _nabo, _agk_,
+ {{0x64460586,0x60c404b7,0x7c2d2170,0x40350470}}, // _beki, _flim, tcar, _кейс,
+ {{0x60c42171,0x64462172,0x4422008e,0x3f890282}}, // _glim, _ceki, _cgk_, _tsau_,
+ {{0x7d1d2173,0x6d5c2174,0x628e2175,0x6d4e2176}}, // [11f0] _viss, arra, _babo, asba,
+ {{0x7d1d1ab0,0x442da177,0x628e106c,0xf8bf001b}}, // _wiss, zce_, _cabo, lké_,
+ {{0x7d1d2178,0xb87b00f7,0x628e2179,0x442da17a}}, // _tiss, nnío, _dabo, yce_,
+ {{0xf8bf217b,0x7c22a17c,0x6281a17d,0x38bb0168}}, // nké_, _ngor, zelo, _bëri_,
+ {{0x442d9249,0x07a5981f,0x8fa5a17e,0x36698db3}}, // vce_, ралн, рале, вало_,
+ {{0x7c22983e,0x8d5584db,0x628e217f,0x3869a180}}, // _agor, атич, _gabo, żar_,
+ {{0x62818218,0x442d909b,0x56b800be,0x49932181}}, // velo, tce_, רפון_, _دیار,
+ {{0x628e012b,0xed64026f,0x47359ef0,0x442da182}}, // _zabo, leží_, _внас, uce_,
+ {{0x62818162,0x442da183,0xe7308f24,0x6449a184}}, // telo, rce_, _فصل_, raei,
+ {{0x442da185,0x7c3ba186,0x60c42187,0x7c22a188}}, // sce_, sbur, _slim, _egor,
+ {{0x6281a189,0xceb8809a,0x442d90d1,0xaca38133}}, // relo, _więc_, pce_, _agụm,
+ {{0x62818049,0x65638187,0x272b806a,0x8af0011c}}, // selo, munh, _køn_, stəx,
+ {{0x7bcd218a,0x6446218b,0xfaa5a18c,0x68e98168}}, // jzau, _reki, _вало, _mjed,
+ {{0x6446218d,0x41b511bc,0x98ab0029,0x7c228353}}, // _seki, ंहास, ībā_, _zgor,
+ {{0x68fba18e,0x628e218f,0xb8cc00ab,0x63bba190}}, // _okud, _rabo, _গত_, lyun,
+ {{0xf8bf2191,0x628e03c3,0xdcfc0025,0x27ffa192}}, // cké_, _sabo, tvrđ, _oyun_,
+ {{0x63bba193,0x63a9a194,0x26c581df,0x628e2195}}, // [1200] nyun, nxen, _ollo_, _pabo,
+ {{0xc8798201,0x644607fb,0x68e98388,0xed4602c7}}, // _abş_, _weki, _ajed, _кноп,
+ {{0x64462196,0x44220ad4,0x63bb8573,0x27ffa197}}, // _teki, _tgk_, hyun, _ayun_,
+ {{0x7cfe820f,0x290d81df,0x26c58098,0x628e2198}}, // përi, _chea_, _allo_, _wabo,
+ {{0x628e2199,0x7cfe80f1,0x68e982ce,0x16348098}}, // _tabo, qëri, _djed, беля,
+ {{0x68e9a19a,0xf8bf01d0,0x7c22a19b,0x629801d6}}, // _ejed, zké_, _sgor, ôvod,
+ {{0x6563862f,0x91e3179e,0x09e307b6,0x20d201d6}}, // gunh, _доре, _дорн, lšiu_,
+ {{0x26c585b4,0x5fa6016f,0xb4c09513,0x320a219c}}, // _ello_, खमाल, ँसे_, _izby_,
+ {{0xef1a98a0,0x041800ab,0x63bb8019,0x63a98234}}, // _ама_, ধুরী_, gyun, gxen,
+ {{0x291f802e,0xb27381bb,0x657e00e8,0x2902219d}}, // _ziua_, ольш, _opph, elka_,
+ {{0xd90f03f8,0x2247a19e,0x26dca19f,0xadf20768}}, // ویت_, _denk_, mivo_, _आँगन_,
+ {{0x64a3a1a0,0x26dc8f20,0xddc501d0,0x5fdb8a27}}, // _маха, livo_, _schů, _मेडल,
+ {{0xdb028352,0x69c58074,0x27ed0214,0x752880dd}}, // _groß, _ühen, çen_, _hudz,
+ {{0xf8bf03cb,0x7648809a,0x2247a1a1,0x29020079}}, // ské_, _jedy, _genk_, alka_,
+ {{0xe3e88a49,0xd5b814b7,0x7528a1a2,0x2000026b}}, // _পরিব, аст_, _judz, _eyii_,
+ {{0x75288267,0xc05b035f,0x7bcd21a3,0x25de8074}}, // _mudz, _рік_, tzau, _कइनी_,
+ {{0x75288063,0x2bba0035,0x5399062c,0x2d9a0299}}, // [1210] _ludz, jącą_, ывая_, _erpe_,
+ {{0x68e9a1a4,0x68fba1a5,0x69c102f1,0xd7fc01c6}}, // _sjed, _skud, _ülek, _בהחל,
+ {{0x272b8aa2,0x248481b9,0xc8ab8098,0x63a4802a}}, // _søn_, kemm_, къде_, _áinf,
+ {{0xf7719301,0x9c13019d,0xa49b0a2a,0xe0d081a8}}, // كات_, _jọka, _chòt, وزن_,
+ {{0x2bde13d9,0x644d04a2,0x2d8c83a8,0x752880eb}}, // _नेपा, maai, _asde_, _audz,
+ {{0x6563a1a6,0x644d21a7,0xb87b01d0,0x20d201a1}}, // tunh, laai, dním, lšit_,
+ {{0x68e982fd,0xa8a7a1a8,0x785782e3,0x7528a1a9}}, // _tjed, _крак, _نیاز_, _cudz,
+ {{0x68e98025,0x6563a1aa,0x224786c0,0x60dd21ab}}, // _ujed, runh, _senk_, jism,
+ {{0x1c4281bb,0xfd488133,0x7b06808b,0x6f1d8037}}, // чным, _kemị, rðun, _èsco,
+ {{0x63a984c3,0x2cad82ce,0x6563a1ac,0x628521ad}}, // rxen, sjed_, punh, neho,
+ {{0x63bb96fb,0x386d8140,0x66c201d6,0x95c8a1ae}}, // syun, _kcer_, _dôkl, _куца_,
+ {{0xe73a0ab5,0x297a012a,0x6721a1af,0x397a0039}}, // лем_, נטרא, _hilj, נטרנ,
+ {{0xeaae9156,0x443fa1b0,0x8ca4809a,0x6d450122}}, // _ей_, mbu_, कारो, _awha,
+ {{0x443fa1b1,0x6285029b,0x6b89a1b2,0x6d4502c4}}, // lbu_, jeho, mweg, _bwha,
+ {{0x80b880c8,0xdca60dd3,0x2d950ff7,0x6285013c}}, // _আগস্, бави, ортс, deho,
+ {{0x70b6800f,0x443fa1b3,0x6721a1b4,0x0c26030d}}, // _अकेल, nbu_, _lilj, сман,
+ {{0x03a30e97,0x6b89a1b5,0x78a406c0,0x443fa1b6}}, // [1220] пито, nweg, _iniv, ibu_,
+ {{0x443f8609,0x62851854,0x2d9a0042,0xa49b06c4}}, // hbu_, geho, _trpe_, _akòd,
+ {{0x657e21b7,0x78a40e23,0x499a17ae,0x764895ab}}, // _upph, _kniv, ытая_, _redy,
+ {{0x764e0365,0x443f822b,0x7648a1b8,0x7528a1b9}}, // laby, jbu_, _sedy, _rudz,
+ {{0x7d0421ba,0x7528a1bb,0x5fd28074,0x4b550081}}, // mlis, _sudz, _देखल, _мърт,
+ {{0x7d0421bc,0x66fa0a74,0xf1ca800d,0xdfa580f7}}, // llis, ृतिक_, ानमन, تحمي,
+ {{0x171b0158,0x26dca1bd,0x6721a1be,0x2d9821bf}}, // נוצע, rivo_, _dilj, nvre_,
+ {{0x6cd487bd,0x7d0421c0,0x44320051,0x60dd21c1}}, // تقبا, nlis, ncy_, xism,
+ {{0xe81a1c4f,0x60c2a1b4,0x26dca1c2,0x47c680a9}}, // नेमा_, mkom, pivo_, _убав,
+ {{0x60c292f1,0x7d0421c3,0x6601835f,0x39ae8085}}, // lkom, hlis, _fylk, ləsi_,
+ {{0x443fa1c4,0x8afb00be,0x69c582f1,0x63a4a1c5}}, // bbu_, רהיי, _ühel, _šind,
+ {{0x236021c6,0x7d0421c7,0x66cb0214,0x26da21c8}}, // krij_, jlis, _müke, _ampo_,
+ {{0x4426a1c9,0xe6c48125,0xb87b026f,0x7d0401ac}}, // _igo_, _þjóð, lník, dlis,
+ {{0x7d1600e1,0xf1a69ad0,0x60dd0558,0x39ae8085}}, // emys, _грин, sism, həsi_,
+ {{0x6d5a003b,0x60c2a1ca,0xb87b00e1,0x7afaa1cb}}, // štan, kkom, nník, mott,
+ {{0xd9049fbe,0x60c2a1cc,0xf98f80f7,0x7d0421cd}}, // _سی_, jkom, _أبو_, glis,
+ {{0x2d8c013c,0x60c28370,0x764e21ce,0x644d21cf}}, // [1230] æder_, dkom, baby, raai,
+ {{0x7d0421d0,0x443fa1d1,0x7afa84fa,0x325521d2}}, // alis, zbu_, nott, івер,
+ {{0x7d0421d3,0x4426a1d4,0x443fa1d5,0x6ab500ff}}, // blis, _ogo_, ybu_, _vozf,
+ {{0x4426a1d6,0x60c282b8,0x212a0083,0xb87b026f}}, // _ngo_, gkom, _dubh_, dník,
+ {{0xd366a1d7,0x672985a4,0x443f826f,0x7afaa1d8}}, // _ته_, _quej, vbu_, kott,
+ {{0x24ab00ab,0x60c2a1d9,0xa3be8dd2,0x3ce90162}}, // _ঐতিহ, akom, ीनन_, lnav_,
+ {{0x7afaa1da,0x6721a1db,0x68ed21dc,0x442682c4}}, // dott, _vilj, _ojad, _bgo_,
+ {{0x6b89a1dd,0x39ae811c,0x60c2a1de,0xa0c5a1df}}, // tweg, cəsi_, ckom, _بيرو,
+ {{0x443fa1e0,0x7b1001ec,0x44268144,0x6b89890d}}, // rbu_, bäud, _dgo_, uweg,
+ {{0x44268a2c,0x66018d38,0x443fa1e1,0xb87b05b9}}, // _ego_, _tylk, sbu_, bník,
+ {{0x00860cec,0x7d0421e2,0x6b8981ec,0xb87b04e8}}, // олно, ylis, sweg, cník,
+ {{0x2eb421e3,0xc952007c,0x9c1301bc,0x7d0402ed}}, // ुस्त, ומט_, _tọha, xlis,
+ {{0xfc4601ac,0x764e21e4,0x68ed21e5,0xbebb0168}}, // šíka_, taby, _djad, jqës,
+ {{0x4432009a,0x7afaa1e6,0x6145814f,0x7d0421e7}}, // wcy_, cott, _дека, wlis,
+ {{0x60c9a1e8,0x644ba1e9,0x7d040b81,0x764e21ea}}, // _olem, _kegi, tlis, raby,
+ {{0x78a421eb,0x764e1b19,0x0ce800ab,0xb05b1a50}}, // _univ, saby, পত্ত, lkän,
+ {{0x272f084a,0x7d0421ec,0xb87b003e,0xeb970009}}, // [1240] _gün_, rlis, zník, оит_,
+ {{0xda6600f7,0x4432009a,0xdc3c9e1e,0x60dba1ed}}, // تاري, scy_, váčk, _amum,
+ {{0x7d0421ee,0x6459809a,0x60c29151,0x1d0a0703}}, // plis, _odwi, tkom, _вени_,
+ {{0xb87b003e,0x7afa8019,0x6fdb80d4,0x53a6161a}}, // vník, zott, _मेहं, _дамб,
+ {{0x39ae8085,0x2bd9a1ef,0x888d00be,0x60c9810c}}, // rəsi_, _बेला, טראַ, _dlem,
+ {{0x8f9b1b9e,0xb87b026f,0x60c2a1f0,0x66cb21f1}}, // ייני, tník, skom, _tüke,
+ {{0x2bde00ba,0xb05b016d,0x7afaa1f2,0x60c9a1f3}}, // _नेता, dkän, vott, _flem,
+ {{0x412721f4,0x60c98bc5,0xb87b1c18,0x644ba1f5}}, // _фото_, _glem, rník, _cegi,
+ {{0x7afa8065,0x2aaa9285,0x8e76a1f6,0x644ba1f7}}, // tott, атно_, _мужч, _degi,
+ {{0x7cfe80f1,0x6459a1f8,0x3ce00074,0x8af00085}}, // tërs, _edwi, hiiv_, qsəd,
+ {{0x2730a1f9,0x7afaa1fa,0xe8950087,0x644ba1fb}}, // _hàn_, rott, пань, _fegi,
+ {{0x6ab70a74,0x4426a1fc,0x2d9ea1fd,0xbebb0168}}, // _आक्र, _ugo_, _irte_, yqës,
+ {{0x6d5a0b48,0x69c10074,0x3ce902d4,0xdb2482f1}}, // štal, _ülev, vnav_, öpäe,
+ {{0x20050029,0x27308028,0xdefb0d15,0x2bd5001b}}, // āli_, _màn_, рым_, _ठेगा,
+ {{0x2730a1fe,0x6288a1ff,0xc8bc9a3b,0x2ebc80ab}}, // _làn_, medo, ्घाट, _অগাষ,
+ {{0x6288a200,0x70b98074,0x63ad2201,0x68ed2202}}, // ledo, _आवेल, txan, _ujad,
+ {{0x60c02203,0xb87b001b,0x68fd0168,0x27308129}}, // [1250] ömme, vníh, mosd, _nàn_,
+ {{0x6288a204,0x63ad2205,0x21248706,0x2d9e8192}}, // nedo, rxan, _aimh_, _orte_,
+ {{0xdc3f026f,0x672d0168,0xb87b01d0,0x59ca98b1}}, // líčk, _huaj, tníh, ानिर,
+ {{0xd04f0201,0x2730a206,0x628881bc,0x26f5a207}}, // ticə, _bàn_, hedo, ेत्र_,
+ {{0x2d9ea208,0x6d488079,0xf745a209,0x23d98127}}, // _arte_, _awda, чело, _बेंद,
+ {{0x2730a20a,0x644ba20b,0x62889351,0x672d00f1}}, // _dàn_, _segi, jedo, _muaj,
+ {{0x6288a20c,0x6b8d0a0f,0x672d0168,0x2d9e812b}}, // dedo, lwag, _luaj, _crte_,
+ {{0x60dba20d,0x60c9a20e,0x6295018e,0x29190088}}, // _umum, _ulem, _hazo, jmsa_,
+ {{0x6295220f,0x6b8d2210,0x644b808b,0x7cfe8168}}, // _kazo, nwag, _vegi, tërr,
+ {{0xdd959006,0x3ed580f7,0x62952211,0x6288a212}}, // _назы, _عقار, _jazo, gedo,
+ {{0x644ba213,0x62952214,0xb05b1a50,0x236901c5}}, // _tegi, _mazo, rkän, huaj_,
+ {{0x6b8d1b86,0x62952215,0xb05b0338,0x68fd2216}}, // kwag, _lazo, skän, gosd,
+ {{0x24892217,0xe299a218,0xa3d3816f,0x6288a219}}, // deam_, _гал_, _हेच_, bedo,
+ {{0x6d5a0904,0x2cb90706,0x62889ee5,0x672d03ed}}, // štam, _aosd_, cedo, _duaj,
+ {{0x2d9e003e,0x6da31a19,0xada30071,0x764380dd}}, // íte_, нира, нарл, ibny,
+ {{0x01c300c8,0x5fbd8651,0xd7bd8072,0x2cb90706}}, // ্পাদ, ्नाल, ्नाच, _cosd_,
+ {{0x6295221a,0x672d0fe2,0x673b8282,0xe45f0106}}, // [1260] _bazo, _guaj, _ntuj, _glöm_,
+ {{0x479b0051,0x0e660009,0x3f92221b,0x6295016a}}, // _פייס, _экон, _isyu_, _cazo,
+ {{0x2730a21c,0x69c1a21d,0x98b08289,0x3ebb0039}}, // _sàn_, nyle, šači_, _הציב,
+ {{0x7762a21e,0xa06aa21f,0x7ae1a220,0x6b8d2221}}, // trox, сада_, hilt, bwag,
+ {{0x7ae1a222,0xfc3f1727,0x64442223,0x65638192}}, // kilt, _unía_, mbii, ernh,
+ {{0x628897ad,0x996401d0,0x62952224,0x2b4014a9}}, // xedo, může_, _gazo, mpic_,
+ {{0x28af06b7,0x6b82a225,0x515519d9,0xb0cc1c7b}}, // टामि, _spog, _отту, ासाग,
+ {{0x2d9e8042,0x2124a226,0x6288831d,0x2730a227}}, // _vrte_, _uimh_, wedo, _tàn_,
+ {{0xfc3f82ba,0x62952228,0x66cb06ae,0xbbb88c28}}, // ñía_, _yazo, _lüka, ेनेक,
+ {{0x7ae1a229,0xb6c88065,0x7afe222a,0x7f3b8039}}, // gilt, _بارے_, nopt, _לעמו,
+ {{0x6d5a003b,0x2d9e811e,0x68fd222b,0x6b8d222c}}, // štaj, _urte_, tosd, zwag,
+ {{0xeafa0670,0x68e2a22d,0x2ca6a22e,0xab5d8035}}, // ्तुत_, niod, _unod_, wyże,
+ {{0x7ae1a22f,0xfaa68391,0x672d00f1,0x6288a230}}, // bilt, _надо, _quaj, pedo,
+ {{0x628880f1,0x0ebe8b9f,0x672d0168,0x6d552231}}, // qedo, ्सिड, _vuaj, tsza,
+ {{0x24892232,0x672504a2,0xf2978496,0x3ebe18b6}}, // team_, _vihj, _זכור_, ötta_,
+ {{0x6d552233,0x23692234,0x6b8d2235,0xfbde016f}}, // rsza, tuaj_, twag, _नेहम,
+ {{0x6d550065,0x62952236,0x68e2a237,0xa3be852a}}, // [1270] ssza, _pazo, diod, ीनि_,
+ {{0x6b8d2238,0x60cd2239,0x88bd8035,0x92940162}}, // rwag, _hlam, _ciśn, науц,
+ {{0xc1788110,0x673b80dd,0x64440365,0x7d028061}}, // ntė_, _stuj, bbii, _okos,
+ {{0xa3be823c,0xc5f2004c,0x2ec5a23a,0xc1788110}}, // ीना_, _ידי_, वस्त, itė_,
+ {{0x629501e0,0x635e0162,0x644f01a9,0x041b0264}}, // _tazo, tănţ, _ieci, _ফ্রী_,
+ {{0x60cd223b,0x7d02a23c,0x25de800d,0x7afe223d}}, // _llam, _akos, _केही_, copt,
+ {{0x644f1a67,0xf6508154,0x68e2826b,0x60cd02d0}}, // _keci, ائه_, biod, _olam,
+ {{0x68e2802e,0x2ee301ed,0x387f81bf,0xe8948110}}, // ciod, hijf_, _ubur_, _пась,
+ {{0x644f223e,0x7ae1a23f,0xeb99a240,0x645d0144}}, // _meci, tilt, цип_, _mdsi,
+ {{0x78bba241,0x69c10074,0x66cf80e8,0xb05b1743}}, // _houv, _üles, _bøke, skäl,
+ {{0x78bba242,0x7ae1a243,0x44390174,0xa88a2244}}, // _kouv, rilt, _ofs_, ойна_,
+ {{0xafe6035f,0xa3ad001b,0x7ae1a245,0x15e3850a}}, // домл, कमा_, silt, _केयर_,
+ {{0x78bba246,0xb8e61c7b,0x66d0016d,0x7d09a247}}, // _mouv, _एव_, _läke, mles,
+ {{0x78bba248,0x60cd2249,0x7d09938e,0x644f224a}}, // _louv, _elam, lles, _aeci,
+ {{0xcb128051,0x644f003d,0x66d00009,0x999801a1}}, // _שלו_, _beci, _näke, _serž_,
+ {{0x78bba24b,0x7afe02d6,0x68e2802a,0xb05b224c}}, // _nouv, wopt, xiod, shär,
+ {{0x7d09a24d,0x073a00f7,0xb05b01ec,0x443902ed}}, // [1280] iles, _بسبب_, phär, _dfs_,
+ {{0x244c001c,0x7d09a24e,0x273b8162,0x05000264}}, // ếm_, hles, _mână_, ্দের_,
+ {{0x27340364,0x2d9c0019,0x63a904b7,0x644f224f}}, // _hän_, _éve_, _ġene, _feci,
+ {{0x588381bb,0x78bb82be,0x7d098019,0x60cd00b9}}, // выша, _couv, jles, _xlam,
+ {{0x78bb83ec,0x2d8582d6,0xd5af80d7,0x711b03de}}, // _douv, _aple_, یفه_, קונפ,
+ {{0x273400f2,0x68e2a250,0xbea38652,0x44390299}}, // _män_, siod, качк, _zfs_,
+ {{0x2734016d,0x7d09a251,0x3d0a8072,0x26198075}}, // _län_, fles, ातले_, येली_,
+ {{0x78bba252,0x1ee88117,0x6abc007b,0x9972801b}}, // _gouv, _کوئی_, _horf, těže_,
+ {{0x66cf80e8,0x69c106ae,0x2d85a253,0x60cd0229}}, // _søke, _üler, _eple_, _rlam,
+ {{0x60cd2254,0xf743a255,0x7d1ba256,0xf7d7807c}}, // _slam, _рефо, amus, _חומש_,
+ {{0x7c398428,0xa50a8087,0xb05b1d31,0xa2d4a257}}, // _ffwr, _дежа_, skäm, योग्,
+ {{0x7d09a258,0x2bd981b6,0x22498904,0xc1788110}}, // cles, _बेका, _đake_, rtė_,
+ {{0xad1b8158,0xe81a101e,0x644f2259,0x6d588e51}}, // _וואר, नेला_, _reci, lsva,
+ {{0x644f225a,0x32078114,0x4439225b,0x63a2a25c}}, // _seci, _fyny_, _sfs_, _iron,
+ {{0x63a2a25d,0x6728a25e,0x6d58a25f,0x27fa809a}}, // _hron, _midj, nsva, ępny_,
+ {{0x60cd0812,0x629aa260,0x6ee900ce,0xc983802e}}, // _ulam, ldto, džbe, _руши,
+ {{0x66d004b8,0xbf0ea261,0x5b152262,0x63a28077}}, // [1290] _säke, िताभ_, емат, _jron,
+ {{0xd251045b,0x67160996,0x78bba263,0x6abc0114}}, // انا_, _दीपक_, _souv, _corf,
+ {{0x78bba264,0x25de835a,0x7d00a265,0x44390358}}, // _pouv, _केली_, koms, _tfs_,
+ {{0x62988021,0x63a2a266,0x90158221,0x7d0904b7}}, // _lavo, _oron, нфлі, ċess,
+ {{0x6abc2267,0x8e8590ca,0xe8d803c8,0x6d5aa1ad}}, // _forf, егле, וואר_, _avta,
+ {{0x6abc031d,0x2bd986a7,0x7a318061,0x7f4d02a6}}, // _gorf, _बेगा, _műté, _mwaq,
+ {{0xdce88182,0x63a2a268,0xd5b192c8,0x7d09a269}}, // _ardı, _aron, افع_, tles,
+ {{0x273b802e,0x9f518019,0x212900b9,0x6aaa8428}}, // _până_, ából_, _niah_, _anff,
+ {{0x7d09a26a,0x65938085,0x200921e5,0x03259af6}}, // rles, _məhə, _nyai_, ндин,
+ {{0xeab18013,0x39458987,0xaad9809a,0x7dee0019}}, // اعة_, нног, योंक, nősé,
+ {{0x6298a26b,0x212900b9,0x69c5226c,0x7d1ba26d}}, // _davo, _biah_, nyhe, pmus,
+ {{0x0ef80540,0xf9da00be,0x629aa26e,0x6d438372}}, // ंक्स_, פֿיל, adto, mpna,
+ {{0x63a2a26f,0x21290359,0xbbeb9c81,0x6d43a270}}, // _gron, _diah_, _برام_, lpna,
+ {{0x248d856c,0x2734016d,0xe3b080d5,0x290b017f}}, // leem_, _vän_, _خرم_, alca_,
+ {{0x57bd83bb,0xdd92187e,0x2cbda271,0x24990493}}, // ्नुह, دوز_, _kowd_, _basm_,
+ {{0x248d879f,0x050000ab,0x91fc81a9,0x94260087}}, // neem_, ্দ্র_, rgāt, ембе,
+ {{0x38600068,0x29022272,0x6abc2273,0xc4d280be}}, // [12a0] _idir_, moka_, _porf, אגן_,
+ {{0x248d8069,0x29022274,0x68e62275,0xe2972276}}, // heem_, loka_, likd, нау_,
+ {{0xa3bc04e5,0x67288699,0xa84a81a8,0x6abc2277}}, // _अथक_, _ridj, كلام_, _vorf,
+ {{0x25a5006a,0x26150816,0x26ca00b9,0x6d58a09b}}, // ælle_, _धरती_, lkbo_, vsva,
+ {{0x66e69383,0x656e2278,0x1c1c2279,0x6abc227a}}, // нода, lubh, पेशल_, _torf,
+ {{0x6456227b,0x6d58a27c,0x290200b4,0x6d41227d}}, // layi, tsva, hoka_, ílag,
+ {{0x6728a27e,0x29020365,0xe9f980ff,0x248d81c0}}, // _vidj, koka_, _đế_, feem_,
+ {{0x6298a27f,0x6d58a280,0x64562281,0x6728a282}}, // _savo, rsva, nayi, _widj,
+ {{0x29022283,0x6298a284,0x1e86a285,0x7d00a286}}, // doka_, _pavo, _плам, roms,
+ {{0x6456005d,0x656e0234,0xdce380eb,0x2fc681c0}}, // hayi, kubh, runā, myog_,
+ {{0xfbcf82e3,0x200900dd,0x29022287,0x212900b9}}, // لتی_, _syai_, foka_, _piah_,
+ {{0x63a28142,0x38600362,0x29022288,0x3cfa0035}}, // _tron, _cdir_, goka_, ंवों_,
+ {{0x09dd0b6f,0xbfa81fbc,0x6298a289,0x249900b9}}, // _मध्य, нтре_, _tavo, _rasm_,
+ {{0x38600086,0x63ad8390,0x61eb8019,0x3ebea28a}}, // _edir_, _šang, állá, _jott_,
+ {{0x2902228b,0x3a3780be,0xe29b80be,0x3eac808e}}, // boka_, _ארום_, _זשור, _mndt_,
+ {{0x3ebea28c,0x6456228d,0xade38074,0xb05b0198}}, // _lott_, gayi, _कइसन_, kkäi,
+ {{0x3a75228e,0x7dee0065,0xe7e081ce,0x66cb0059}}, // [12b0] клар, tősé, _खेला_, _yükl,
+ {{0x6911a28f,0x63bd8aa2,0x3ebe81a1,0x257a83ed}}, // gåen, øsni, _nott_, kël_,
+ {{0x64562290,0xa5f92134,0x1df90009,0x7ae52291}}, // bayi, _цену_, _цены_, riht,
+ {{0xf7718124,0xa90784c1,0x248d822c,0x69c50a53}}, // لات_, ربان, xeem_, ryhe,
+ {{0x7d0d2292,0x3ebea293,0x57bd81d0,0x23c72294}}, // mlas, _bott_, ्नेह, _díjú_,
+ {{0x29022295,0x66d00338,0xdb029407,0xaca78061}}, // zoka_, _käka, _droë, کھئے_,
+ {{0x6d5a0025,0x257a80f1,0x1af42296,0x3ebe847f}}, // štav, gël_, _спря, _dott_,
+ {{0x7d0d2297,0x2fc68420,0x6b848162,0x3eac9277}}, // nlas, byog_, _ţiga, _endt_,
+ {{0x66d000f2,0x3f8901c5,0x98a80087,0x07359bc1}}, // _läka, _npau_, _lună_, _резю,
+ {{0x7d0d073b,0x3ebea298,0x78ad2299,0x248da29a}}, // hlas, _gott_, _anav, seem_,
+ {{0x2902229b,0xa06a229c,0x3de100c8,0x7d0d043c}}, // toka_, мана_, _মুসল, klas,
+ {{0x48e60381,0x6f03a29d,0xa2a1a29e,0x23690304}}, // томв, lonc, _कोर्, kraj_,
+ {{0x29020ee1,0xe28ea29f,0x2bd98105,0x6449a2a0}}, // roka_, _ка_, _बेचा, mbei,
+ {{0x6f03a2a1,0x8a059a19,0x98a80087,0x6449a2a2}}, // nonc, _изле, _bună_, lbei,
+ {{0x645622a3,0x290222a4,0xfc3f01d6,0xceb28e82}}, // tayi, poka_, _kníh_, סיל_,
+ {{0x320a0690,0x6449a2a5,0x443d808e,0x7c3b8888}}, // _ryby_, nbei, _jfw_, ncur,
+ {{0x6f039122,0x645622a6,0xa50722a7,0x6911a2a8}}, // [12c0] konc, rayi, тета_, tåen,
+ {{0x6281822b,0x645622a9,0x4a431819,0x69c582f1}}, // nflo, sayi, инув, _ühes,
+ {{0x6d4182a6,0x645622aa,0x3ebea2ab,0x00000000}}, // _ntla, payi, _rott_, --,
+ {{0xf7468256,0x3ebea2ac,0x00e68081,0x2d3e0870}}, // ведо, _sott_, ъжен, _ọgwụ_,
+ {{0xd7ef8013,0x6f0380e7,0x7d0422ad,0x3ebea2ae}}, // يكم_, fonc, mois, _pott_,
+ {{0x7d0422af,0xa2a1a2b0,0x6d5a01ac,0x442f8752}}, // lois, _कोल्, štau, _agg_,
+ {{0x180f035a,0x6d5c048d,0x68e422b1,0x1ae319a5}}, // _सर्व_, nsra, _omid, _торм,
+ {{0x629c1a7b,0x7d0422b2,0x22498904,0x6d5c22b3}}, // _haro, nois, _đaka_, isra,
+ {{0x629c22b4,0xcb678fbb,0x4f268196,0x613e80eb}}, // _karo, вање_, _адаб, _mīle,
+ {{0x60c0a2b5,0x629c185c,0x629e22b6,0x02a6a2b7}}, // _homm, _jaro, ndpo, трим,
+ {{0x629c202e,0xf4848077,0x7d040364,0xd48f8ba8}}, // _maro, _ماشی, kois, _кр_,
+ {{0x629c009c,0xcfa98019,0x7793003d,0x6d5c22b8}}, // _laro, _تاہم_, لیبا, dsra,
+ {{0x248680fc,0x7d0422b9,0xb4c09130,0x98a80493}}, // _ibom_, dois, ुसी_, _sună_,
+ {{0x752b802a,0x76588010,0x60c0a2ba,0x68e422bb}}, // _zigz, navy, _lomm, _emid,
+ {{0x2fcd003b,0xd6d8035f,0xe5c411a8,0x7d0d22bc}}, // šeg_, ття_, асто, tlas,
+ {{0x60c0a2bd,0x78ad0c93,0x236922be,0x4aa6864a}}, // _nomm, _unav, traj_, _कोतव,
+ {{0x629c22bf,0x7b32809a,0x7d0d22c0,0x69c8a2c1}}, // [12d0] _baro, _dług, rlas, lyde,
+ {{0x7d0d22c2,0xade38074,0xfde38074,0x629c22c3}}, // slas, _कइलन_, _कइलस_, _caro,
+ {{0x7d0d22c4,0x7d041009,0x6f0380c3,0x6f0200f7}}, // plas, bois, vonc, _íoca,
+ {{0x60c0a2c5,0xf09f22c6,0x7d0d208e,0x48e180ab}}, // _comm, ndà_, qlas, যক্র,
+ {{0x6f0385b4,0x973580d5,0x60c0a2c7,0x26c1016b}}, // tonc, اکرا, _domm, _koho_,
+ {{0x629c22c8,0x60c08b99,0x26c103e4,0x69c88646}}, // _garo, _eomm, _joho_, kyde,
+ {{0xceb40451,0x26c11ab3,0xf0758019,0x60c08372}}, // ייס_, _moho_, _میاں_, _fomm,
+ {{0x60c0a2c9,0x290901bc,0x394601a1,0x7c3b82df}}, // _gomm, _nkaa_, ćost_, ucur,
+ {{0x6449a2ca,0x7c3b8012,0x57f5a2cb,0x27e080f7}}, // rbei, rcur, _спат, úint_,
+ {{0x3ce901e9,0x68e422cc,0xa3bb0006,0x290922cd}}, // hiav_, _smid, _अपन_, _akaa_,
+ {{0x6281a2ce,0xaad98770,0x59ca90be,0x6d41a2cf}}, // rflo, योगक, ानगर, _utla,
+ {{0x6281808b,0xf7460293,0x20c201a8,0x6fa78697}}, // sflo, лемо, _cóip_, चिपू,
+ {{0x6b65a2d0,0x2d0f835a,0x7b06807b,0x4b7980be}}, // _скла, ातील_, rður, _נאַו,
+ {{0x249fa2d1,0xe2968226,0x249d81c5,0x7ae381f6}}, // ldum_, _баш_, _hawm_, _umnt,
+ {{0xa0a622d2,0x7d040009,0x249d81e9,0x69118370}}, // _байд, tois, _kawm_, tåel,
+ {{0x249f8125,0x68e422d3,0x97c61e91,0x212d8359}}, // ndum_, _umid, _مقبو, _nieh_,
+ {{0xcb67023a,0x60c0a2d4,0x237fa2d5,0xdd038084}}, // [12e0] лате_, _romm, ntuj_, ūrėj,
+ {{0xda650013,0x249d90af,0x6d5a04c3,0x8f9b0039}}, // لامي, _lawm_, átas, _טיפי,
+ {{0x629c22d6,0x60c0a2d7,0x2cafa2d8,0x7d0422d9}}, // _varo, _pomm, _ongd_, pois,
+ {{0x249d81c0,0xa09a81a8,0x629c22da,0x237f9d21}}, // _nawm_, عضاء_, _waro, ktuj_,
+ {{0x629c00a4,0x44240014,0x69c88035,0x69da8216}}, // _taro, _àm_, zyde, zzte,
+ {{0xb8cba2db,0x2ca00074,0x3b0a0009,0x4e7a00be}}, // _को_, ldid_, _чего_, גאַצ,
+ {{0x60c0a2dc,0x57d001d0,0x2ca006ae,0xf8bf1a1f}}, // _tomm, हनुह, odid_, lmée_,
+ {{0x7ae8a2dd,0xbd878624,0x2ca022de,0x249d8069}}, // widt, انين_, ndid_, _cawm_,
+ {{0xc6768060,0xe646a2df,0x63a422e0,0x66d000f2}}, // _مطاب, _безп, nvin, _räkn,
+ {{0x69c88bfa,0x69da8352,0x63ad826f,0xe45f0009}}, // tyde, tzte, _šanc, _ylös_,
+ {{0x249d81c0,0x26c122e1,0xd2b70039,0x7c8689a0}}, // _fawm_, _roho_, אלית_, _буле,
+ {{0xa3e50665,0x8dfb8039,0x249d81c0,0x69c8a2e2}}, // _भइल_, _אהבת, _gawm_, ryde,
+ {{0x69da8065,0x63a40a0f,0x81bc00c8,0xf09f009f}}, // szte, jvin, েছি_, rdà_,
+ {{0x11d6803f,0x3ce68282,0x99d68277,0x539b84de}}, // _متعد, _hmov_, _متعا, _ניוו,
+ {{0x7f441883,0x58d50009,0x249d8069,0x1df8076a}}, // _etiq, _войт, _yawm_, геры_,
+ {{0x290f01a8,0x20f88084,0x2bd18b99,0x290903e4}}, // _óga_, yčia_, दैवा, _ukaa_,
+ {{0x26c10a56,0x395e8341,0x63a422e3,0x4a5520bf}}, // [12f0] _toho_, lsts_, gvin, икас,
+ {{0xb05b01ec,0x66d00106,0x3ce901c0,0x2739a2e4}}, // rkäu, _jäkl, siav_, _rèn_,
+ {{0x46150077,0x66d0016d,0x6d5a22e5,0xb035a209}}, // _خودر, _mäkl, štar, анеш,
+ {{0x6d4522e6,0x237f8035,0x395ea2e7,0x5c07047f}}, // _itha, ytuj_, ists_, _бяга,
+ {{0x644d22e8,0x249d822c,0xb4c30bbc,0xb05b0198}}, // mbai, _rawm_, ्सी_, lkäs,
+ {{0x395e8029,0x644d22e9,0xddc5a2ea,0x249d8282}}, // ksts_, lbai, ибли, _sawm_,
+ {{0xa56480f7,0xfc3180f7,0xb05b0198,0x249f811c}}, // مدين, يحة_, nkäs, tdum_,
+ {{0xff1580c8,0x644d0102,0x386080f7,0xdb06010c}}, // াদেশ_, nbai, óirt_, _arkè,
+ {{0x395e87e2,0x249f8457,0x6d450122,0x7d1d010c}}, // ests_, rdum_, _ltha, _mhss,
+ {{0x752f0063,0x644d03ac,0x237f809a,0x6d4522eb}}, // _licz, hbai, rtuj_, _otha,
+ {{0x249d81e9,0x6d45022c,0x2a760039,0x25b98199}}, // _tawm_, _ntha, בעתך_, _جهاد_,
+ {{0x68eb8cfa,0x63a403c7,0x752f0035,0x25de81d0}}, // nigd, yvin, _nicz, _केटी_,
+ {{0x6d4522ec,0xb8ee000f,0xa3e50074,0x26d822ed}}, // _atha, _शक_, _भेल_, thro_,
+ {{0x443fa2ee,0x63a40197,0x7d1d0706,0x68eb8420}}, // lcu_, vvin, _ahss, higd,
+ {{0x2d53802e,0xa01b0019,0x50460037,0xddc322ef}}, // nţe_, lföl, _кейб, обри,
+ {{0x1c4622f0,0x8a14803d,0x6d450706,0x47c6004a}}, // рнам, _اظها, _dtha, _вбив,
+ {{0x6d4522f1,0x6f07017f,0x68eba2f2,0x2ca00074}}, // [1300] _etha, bojc, digd, rdid_,
+ {{0x55e622f3,0xf8bf0036,0x0ea49370,0x3ce981d6}}, // _вооб, rmée_, _गोंड, čavy_,
+ {{0x765c22f4,0x2d9a095e,0x644d22f5,0xf0aa046d}}, // mary, _uspe_, bbai, _gbàá_,
+ {{0x270e8c78,0x64a622f6,0x39a58029,0x4fc68bba}}, // ित्र_, _тама, _mēs_, исла,
+ {{0x443fa2f7,0x02b580c2,0x98a622f8,0x3f82136f}}, // dcu_, _अचंभ, nslı_, ntku_,
+ {{0x765c22f9,0x5d66a2fa,0x658a8201,0x236da2fb}}, // nary, атиз, _rəhb, drej_,
+ {{0xdce18214,0x69c7806a,0x6d450706,0x290683e4}}, // _aslı, øjel, _xtha, pooa_,
+ {{0x399b012a,0x0ca78009,0x656e0c5e,0x765c22fc}}, // _בילד, атьи_, irbh, hary,
+ {{0xf743a2fd,0x765c22fe,0x225800c9,0xdb0609c4}}, // _мето, kary, _kerk_, _arké,
+ {{0x78a2a2ff,0x79a6a300,0x0f7b83c8,0xb4d203ca}}, // ldov, арме, _באמב, वसु_,
+ {{0x3a370051,0x765c01e2,0x22582301,0x6f070024}}, // ירים_, dary, _merk_, vojc,
+ {{0x60c40c64,0x1bea9508,0x395ea302,0x69de011b}}, // _coim, _идеи_, rsts_, izpe,
+ {{0x77f4a303,0x3949a304,0x60c42305,0xd7fb0530}}, // _अशोक_, _čase_, _doim, тун_,
+ {{0x0eeb2306,0x28dd82f1,0xb05b062c,0x7e78a307}}, // тьми_, _मतभि, tkäs, ngvp,
+ {{0x3f822308,0x7afda309,0xeda400e8,0xd2f88019}}, // atku_, éste, ошто, _لکھا_,
+ {{0x04c900f7,0x78a2a30a,0xdce7009a,0x60d6230b}}, // دوري_, jdov, kują, _glym,
+ {{0x644d230c,0x656e0c41,0x765c230d,0x78a281e8}}, // [1310] rbai, arbh, bary, ddov,
+ {{0x6e950013,0xf1a71980,0x644d0014,0xf699230e}}, // _الدا, ирен, sbai, _свој_,
+ {{0x6d45005d,0x20f8811f,0x28cc109b,0x3ce781ab}}, // _utha, rčin_, ासचि, _जगले_,
+ {{0xa3c7a30f,0x628501a8,0x271f847d,0x273d04be}}, // _उथल_, sfho, यग्र_, _kìn_,
+ {{0x443f8289,0xa3bb152c,0x2d8300f3,0x60c02310}}, // vcu_, _अपि_, ltje_, ömmi,
+ {{0x4aa68076,0xaaa6a311,0x6b9d002a,0x236d8b80}}, // _कोलव, _कोलक, _issg, vrej_,
+ {{0x2d832312,0xad5897ae,0x443fa313,0x645b888b}}, // ntje_, арых_, tcu_, taui,
+ {{0xfdf80039,0x66cf813c,0x84f900d4,0x20f8826f}}, // ימוש_, _køkk, ्किट_, nčil_,
+ {{0x60c40068,0x68e9a314,0x443fa315,0xe3e9803d}}, // _roim, _imed, rcu_, دکان_,
+ {{0x443f8012,0x236da316,0xa01b2317,0x9f958074}}, // scu_, rrej_, rföl, _müüa_,
+ {{0x66cf813c,0x98a62318,0x60d62319,0x39e9a31a}}, // _løkk, бине, _plym, удно_,
+ {{0x236d8353,0xa01b0106,0xfd1f0129,0x693a8197}}, // prej_, pföl, _nhì_, _kċej,
+ {{0x60c40009,0xfaa581e2,0x6f1e0118,0x66cf80e8}}, // _voim, _гало, _phpc, _nøkk,
+ {{0x3f82231b,0x78a28f20,0x291f81c0,0xe7371ac9}}, // rtku_, zdov, _lhua_, сер_,
+ {{0x60c40181,0x645990e1,0x68e98106,0x3f820696}}, // _toim, _kewi, _omed, stku_,
+ {{0xeb9a10ac,0x26c5a31c,0xb05b0b8b,0xb4d20075}}, // либ_, _lolo_, skär, वसे_,
+ {{0x63bb862f,0x765c010c,0x2d8300f3,0x2258231d}}, // [1320] nxun, pary, atje_, _perk_,
+ {{0x68fba31e,0x6459a31f,0xf4128451,0x78a0a320}}, // _ajud, _lewi, לפן_, _samv,
+ {{0x68fb816d,0x2722801c,0xdb042321,0x63a48118}}, // _bjud, _hưng_, nvié, _áinv,
+ {{0x225801d8,0x291fa322,0x6459831d,0x2cbfa323}}, // _werk_, _chua_, _newi, jjud_,
+ {{0x0eea835f,0x26c58038,0x7d09a324,0x96eaa325}}, // льки_, _bolo_, does, лька_,
+ {{0x527519b5,0xdb028187,0xdce70035,0x20f880c3}}, // _мусу, _proí, rują, mčim_,
+ {{0x2b46a326,0x6443009a,0x6b842327,0x64599781}}, // _stoc_, śnie, ltig, _bewi,
+ {{0x63ab82f7,0xdce70035,0xa7fb0118,0xd497a328}}, // _brgn, pują, lañe, брь_,
+ {{0x68e380f1,0xd2508013,0x6459831d,0x940d0326}}, // ënde, ونة_, _dewi, ɓoɓi_,
+ {{0x63ab006a,0x63a92329,0x6b84232a,0x6d4a8711}}, // ågni, _šenk, itig, ppfa,
+ {{0x2d98a32b,0x20120326,0x6b84232c,0x7d09a32d}}, // _ère_, _ayyi_, htig, boes,
+ {{0x2d9e8042,0x6459a32e,0x6b84232f,0x2ed10beb}}, // _iste_, _gewi, ktig, हस्त,
+ {{0xdfd80098,0x61e00085,0x765a8168,0x272280ff}}, // бър_, _əmla, _kety, _cưng_,
+ {{0x81de00c8,0xd9fa01cb,0xf40000ab,0xeaf58c33}}, // _তখন_, ्थात_, ্ধার_, ीकृत_,
+ {{0x2d9e800d,0x765aa330,0x6b8406a8,0x691881ca}}, // _jste_, _mety, etig, víen,
+ {{0x2d830a0f,0x2eeea331,0x62889de6,0x753a8db1}}, // rtje_, hiff_, lfdo, _mutz,
+ {{0x6b842332,0x2d832333,0x38690041,0x3ea12334}}, // [1330] gtig, stje_, _idar_, _taht_,
+ {{0x68e9a335,0x8c1b8bea,0x290b2336,0x765aa337}}, // _smed, רומי, loca_, _nety,
+ {{0xfd1f0104,0x399aa338,0x753a8352,0x6b842339}}, // _thì_, _jūs_, _nutz, atig,
+ {{0x26c5a33a,0x7e688216,0x291f8069,0x399a81a9}}, // _solo_, _addp, _qhua_, _mūs_,
+ {{0x66048182,0x765a904a,0x4918010f,0x2d9e8102}}, // şikl, _bety, זקאל_, _aste_,
+ {{0x26c581b4,0x35c98284,0x321c9075,0x6459a33b}}, // _qolo_, игло_, _ozvy_, _sewi,
+ {{0x765a820f,0x26c5a33c,0x291fa33d,0xd05d0085}}, // _dety, _volo_, _thua_, sisə,
+ {{0x6297233e,0x9345a33f,0x645f0085,0xf9921101}}, // lexo, оние, naqi, _קרן_,
+ {{0x2d9ea340,0x8c6480f7,0xdcfc00eb,0x03a5a341}}, // _este_, سطين, ntrā, ципо,
+ {{0x05ba2342,0x399a8029,0xdddc0019,0x38692343}}, // _उपलब, _būs_, zerű, _adar_,
+ {{0xe8fa2344,0x290319b7,0x7d09a345,0x63bb8118}}, // ило_, čkan_, poes, sxun,
+ {{0x160400d4,0x297a00be,0x79850114,0x63ab8669}}, // _शुगर_, סטרא, ithw, _trgn,
+ {{0xa3bb815b,0x68fd0216,0x940580d7,0x63ab8037}}, // _حاضر_, érda, _بوشه, _urgn,
+ {{0x673b8079,0x38692346,0x63a9a347,0xa8030118}}, // _muuj, _edar_, mven, úñas,
+ {{0x63a9a348,0x62971075,0x845a2349,0x6da31ba2}}, // lven, dexo, ират_, мира,
+ {{0x63a9a34a,0x9e3c9c18,0x6aa38114,0x161e80c2}}, // oven, _maďa, _hanf, येटर_,
+ {{0xb6a60ff7,0x0aea1b69,0xab6600e8,0x6aa38300}}, // [1340] _дипл, адай_, ювал, _kanf,
+ {{0x6b84234b,0x78a40282,0x63a9a34c,0x386904e8}}, // rtig, _haiv, iven, _zdar_,
+ {{0x6aa38590,0x28af000d,0x78a40198,0x9e3c80e1}}, // _manf, _जोडि, _kaiv, _naďa,
+ {{0x765aa34d,0x6aa3a34e,0xac760019,0x78a40144}}, // _sety, _lanf, _بادش, _jaiv,
+ {{0x78a40282,0x765aa34f,0x63a980f3,0x6b840079}}, // _maiv, _pety, jven, qtig,
+ {{0x63a9a350,0x78a42351,0x645d807b,0x356a9593}}, // dven, _laiv, ðsin, арин_,
+ {{0x20d7a352,0xd54780a9,0xa9672353,0xe6da8105}}, // _için_, опје_, _мира_, _भतीज,
+ {{0x66cb0459,0x6aa3826b,0xdb0b841c,0x2fcf804a}}, // _yüks, _aanf, _urgê, bygg_,
+ {{0x3949a354,0xaca401bc,0xfb868110,0xdb040580}}, // _časa_, _ndọt, цыйн, nviï,
+ {{0xcb1a80a9,0x63a4a355,0x386900b9,0x6aa38114}}, // јќи_, _šins, _sdar_, _canf,
+ {{0x09f88013,0x7afd85e4,0xaca40133,0x3f868548}}, // صفحة_, ésta, _adọt, ltou_,
+ {{0x63a9959b,0x7aef82ba,0x29030289,0x78a40037}}, // bven, éctr, čkao_, _caiv,
+ {{0x3f86a356,0x4ea72357,0x63a9802e,0x60358085}}, // ntou_, орва, cven, _həmç,
+ {{0xa3ae0df4,0x387f2358,0x3f86a359,0x66d9046d}}, // किन_, żur_, itou_, _bìka,
+ {{0x78178a74,0x290b1fea,0x7b1001ec,0x58d5091e}}, // _तर्क_, poca_, häus, _нокт,
+ {{0x628e235a,0xd5a48986,0x38690024,0x6aa3a35b}}, // _abbo, _یہ_, _udar_, _zanf,
+ {{0x28ab9a3b,0x3d0b8076,0x645f00f1,0xb87b0118}}, // [1350] _घोषि, ावते_, raqi, rhíd,
+ {{0x7e990019,0x238e00eb,0x2489026c,0xb87b0118}}, // _اندر_, dējā_, sfam_, eníx,
+ {{0xdcfc0029,0x6297002a,0x39580114,0x0e9a01c6}}, // strā, rexo, _cwrs_, _השתל,
+ {{0x23668025,0x7985235c,0x6ee9235d,0x6297235e}}, // _ovoj_, rthw, džbi, sexo,
+ {{0x7d0d002e,0x68ed0a2c,0x645d235f,0xba7700f7}}, // moas, _mmad, _iesi, _واست,
+ {{0x645d2360,0x6da59ba2,0x7d0d08b0,0x60dd0168}}, // _hesi, зика, loas, ërmj,
+ {{0xb14603f8,0x645d2361,0x6aa383ec,0x68ed02f1}}, // _فیلم_, _kesi, _ranf, _omad,
+ {{0x7d0d0ca4,0xdd8e80f7,0x2ec819e8,0x66d00198}}, // noas, اوي_, _रक्त, _väki,
+ {{0x645d2362,0x3f86a363,0x6aa3a364,0x63a980e7}}, // _mesi, ctou_, _panf, uven,
+ {{0x68ed2365,0x645d2366,0x44392367,0x6721a368}}, // _amad, _lesi, _lgs_, _uhlj,
+ {{0x60db84dc,0x4efa80be,0xaf0881a8,0x3ea58366}}, // _klum, _פלעג, _بقلم_, _ialt_,
+ {{0x21060c78,0x645d2369,0x44390420,0x7522a36a}}, // रवेश_, _nesi, _ngs_, _ghoz,
+ {{0x087680be,0x78a4236b,0xe45680be,0x3ea5a36c}}, // הערט_, _vaiv, _זינט_, _kalt_,
+ {{0x4439236d,0x7529a36e,0x60dba36f,0x68ed2370}}, // _ags_, lmez, _llum, _emad,
+ {{0x60dba371,0x2cf91344,0x645d2372,0x78a42373}}, // _olum, ्कूल_, _besi, _taiv,
+ {{0x645d0baf,0x0b4604db,0x60c9a374,0x201e0201}}, // _cesi, знан, _noem, ətin_,
+ {{0x7d1604fe,0x2d8782f1,0x64442375,0xdb0b87b6}}, // [1360] rlys, htne_, rcii, _argé,
+ {{0x60db88e9,0x2d87a376,0x645d1312,0x691d0866}}, // _alum, ktne_, _eesi, néen,
+ {{0x7d16006f,0x60dba377,0x2d930118,0x60c9a378}}, // plys, _blum, _gpxe_, _boem,
+ {{0xe04396d9,0x645d2379,0x7d0d237a,0xa7fb0118}}, // енци, _gesi, coas, laña,
+ {{0xdc370158,0x765e237b,0x3f86802a,0x6d45a37c}}, // _מאכט_, _kepy, utou_, íhan,
+ {{0x645d237d,0xa3e50d14,0xdee3a37e,0x2d878390}}, // _zesi, _भेट_, ночи, ftne_,
+ {{0x23668ee0,0x3f86a37f,0x645d005d,0x3ea5a380}}, // _svoj_, stou_, _yesi, _dalt_,
+ {{0x60db803a,0x2900022e,0x3f86a381,0x18a38254}}, // _glum, _njia_, ptou_, _пасм,
+ {{0x613a001b,0x3ea5804a,0x6f0e0326,0x66dd89c4}}, // _důle, _falt_, kobc, _sèke,
+ {{0x3ea5a382,0xfc3f0118,0x68ed2383,0x6da68ab2}}, // _galt_, _maía_, _smad, _низа,
+ {{0xa7fb0216,0xdd8f866e,0x1ea9a384,0x7416a385}}, // daña, _مول_, _باقي_, _کوشا,
+ {{0x69370499,0xfce3a386,0x236697aa,0x09f701c6}}, // _kćer, _похо, _tvoj_, למים_,
+ {{0x645d2387,0xa3cd8701,0x44390196,0xbff80470}}, // _resi, रहण_, _rgs_, зеях_,
+ {{0x645d2388,0x63a2a389,0xa7fb238a,0x29120365}}, // _sesi, _ison, gaña, _ekya_,
+ {{0xa3e5000f,0x56780009,0x7d0d238b,0x765e0176}}, // _भेज_, ября_, toas, _depy,
+ {{0x5fbea38c,0x68ed0079,0x7aee01ed,0xc8ab86a7}}, // ्हाल, _umad, _ambt, _घोंट,
+ {{0xd90f09d7,0x645d238d,0x7d0d002e,0xa7fb15c7}}, // [1370] _لیگ_, _vesi, roas, baña,
+ {{0x60dba38e,0x7f4d238f,0x63a28c53,0x645d005d}}, // _slum, _itaq, _mson, _wesi,
+ {{0x645d2390,0x63a282f7,0x68fd2391,0x60db8e79}}, // _tesi, _lson, onsd, _plum,
+ {{0x3ce02392,0x63a2a393,0x629aa394,0x68fd2395}}, // chiv_, _oson, heto, nnsd,
+ {{0x6d4a00f1,0xdcfc0087,0x63a2a396,0x6d5a85ee}}, // _çfar, stră, _nson, _awta,
+ {{0x629aa397,0x658a8201,0x2ca92398,0x63ad2399}}, // jeto, _təhl, ldad_, mvan,
+ {{0x3ea5a39a,0xa1941182,0x63a2a39b,0x60db9d40}}, // _valt_, _парч, _ason, _tlum,
+ {{0x2d87a39c,0x60db80ee,0x63ad239d,0x66d00106}}, // rtne_, _ulum, ovan, _häkt,
+ {{0x290f8867,0x752984b7,0x63ad239e,0x2d87a39f}}, // moga_, rmez, nvan, stne_,
+ {{0xa3ae146d,0x39459fc8,0x2bdf001b,0xe3ca8144}}, // कित_, мног, पैया, _soñó_,
+ {{0x7f4d0510,0x66d00106,0x6dd5804e,0x63a2a3a0}}, // _ataq, _mäkt, _تقاض, _eson,
+ {{0x290fa3a1,0x63ad1434,0x3940811a,0x2ca682c4}}, // noga_, kvan, _čist_, _gaod_,
+ {{0x2ca923a2,0x691d00e7,0x272401ec,0xe79c02d0}}, // ddad_, péen, höne_, _düğü,
+ {{0x63ad23a3,0x2ca90144,0x6b89a3a4,0x629aa3a5}}, // dvan, edad_, mteg, ceto,
+ {{0x386d834a,0x290f812b,0x6f0e003e,0x6b89811e}}, // _oder_, koga_, robc, lteg,
+ {{0xf8e2141b,0x386d8168,0x2eda84c5,0xa7fb0144}}, // _पतिय, _nder_, _भत्त, saña,
+ {{0xfa8e801c,0x6aa701a1,0x59cb8127,0xccf28039}}, // [1380] _mừng_, _bajf, ाहवर, דכן_,
+ {{0x386d80ee,0xe297002e,0xb05b1cab,0x63a6a3a6}}, // _ader_, мау_, hjäl, ækni,
+ {{0xe8208076,0xd62a01bb,0x88868a13,0xa87a00be}}, // _बरहा_, _фоне_, длеж, ַאַר,
+ {{0xa3bf86a7,0xa3cda3a7,0x61ee8118,0x657c062c}}, // ीहा_, रहा_, úbli, murh,
+ {{0x3860021e,0x6aa8831d,0x8cdb00d4,0x629aa3a8}}, // _meir_, yddf, नसरो, yeto,
+ {{0x386da3a9,0x473380e8,0x386023aa,0x629a8118}}, // _eder_, вніс, _leir_, xeto,
+ {{0xc6930051,0xe8208076,0x290fa3ab,0x657c00b9}}, // דאו_, _बरवा_, boga_, nurh,
+ {{0x248023ac,0x3eba00b9,0xdc13807e,0x629a9588}}, // ngim_, _lnpt_, _ağır, weto,
+ {{0xd90d8288,0x629aa3ad,0xa6868293,0xfa8e80ff}}, // _این_, teto, _олад, _dừng_,
+ {{0xb6cb8117,0x7b32809a,0x38600174,0x68e2936f}}, // _والے_, _tłum, _aeir_, lhod,
+ {{0x629aa3ae,0x658a8201,0x6b89a3af,0x66d018c2}}, // reto, _məhk, ateg, _läks,
+ {{0xa3de0fcc,0x63ad03bf,0x75498019,0x38600114}}, // तना_, yvan, őszö, _ceir_,
+ {{0x27e684b9,0x63a2a3b0,0x386000f7,0x9f59077f}}, // nzon_, _uson, _deir_, ìyàn_,
+ {{0xe1ef95e4,0x7ce30006,0x3eba0118,0x63ad23b1}}, // اسی_, _kõrg, _cnpt_, vvan,
+ {{0x9f34035f,0xdc3a07d9,0xc32780f7,0x290fa3b2}}, // тері, _açık, _تكون_, yoga_,
+ {{0x63ad23b3,0xadf18665,0x26cc846d,0x442223b4}}, // tvan, _अइसन_, _lodo_, _bzk_,
+ {{0x2ca91726,0x68e284c4,0x290f86ec,0xa7fb0511}}, // [1390] rdad_, dhod, voga_, maño,
+ {{0x26cca3b5,0xcf2715a9,0x63ad1457,0xa7fb0216}}, // _nodo_, _تربي, rvan, laño,
+ {{0x63ad23b6,0x6b898102,0x290fa3b7,0x7e63a3b8}}, // svan, zteg, toga_, vanp,
+ {{0x63ad01ed,0xf86981a8,0x2fe402d0,0x7e6381e0}}, // pvan, سمتي_, _örgü_, wanp,
+ {{0x26cc8db7,0x6aa7069f,0xfa8e801c,0x752d23b9}}, // _bodo_, _tajf, _rừng_, mmaz,
+ {{0x752d0065,0x26cc8333,0x290fa3ba,0xaaad86a7}}, // lmaz, _codo_, soga_, _झोंक,
+ {{0x26cca3bb,0x0445a3bc,0x68e281f6,0x3d0ba3bd}}, // _dodo_, нейн, bhod, ावले_,
+ {{0x68e28f4c,0x27e682d0,0x752d22f8,0x2f16008b}}, // chod, bzon_, nmaz, lægt_,
+ {{0x7e63a3be,0x248d8669,0xe3b18c3b,0x386023bf}}, // panp, rfem_, ارب_, _reir_,
+ {{0x7c22a3c0,0x5fbe950e,0x386023c1,0x1eea8061}}, // _dzor, ्हरल, _seir_, _فوقی_,
+ {{0x6b89a3c2,0xfa8e8028,0xaca38870,0x7c22a3c3}}, // steg, _từng_, _azụm, _ezor,
+ {{0xc689073a,0x26c7803a,0x98b880eb,0x7e61a3c4}}, // _לא_, ljno_, _kurā_, _help,
+ {{0xbd2c893f,0x95228117,0x6d41a3c5,0x752d0904}}, // _וואָ, _حکوم, _hula, dmaz,
+ {{0x68e28a56,0x7d1ba3c6,0x78a9a3c7,0xd36ea3c8}}, // zhod, llus, _laev, _اهو_,
+ {{0x6d41a3c9,0x386023ca,0x7d0902a5,0x442223cb}}, // _jula, _teir_, česn, _szk_,
+ {{0x6d41a3cc,0x64a323cd,0xdca30af0,0x7693861c}}, // _mula, ласа, ласи, lıyı,
+ {{0xac070056,0x68e29dc1,0x24070c24,0xeb97023a}}, // [13a0] ента_, vhod, енти_, нит_,
+ {{0xe9ff8104,0x6281a3ce,0x7d1ba3cf,0x7e6181bf}}, // _giải_, nglo, hlus, _nelp,
+ {{0x7d1ba3d0,0xae5b0051,0xd9108065,0x6d41a1b8}}, // klus, _מכיר, لیس_, _nula,
+ {{0xa01b00f2,0x38608013,0x26cca3d1,0x443da3d2}}, // mför, úir_, _sodo_, _ngw_,
+ {{0x6d41a3d3,0xa01b016d,0x26cca3d4,0x7d1b82f1}}, // _aula, lför, _podo_, dlus,
+ {{0x6d41a3d5,0x27e6a3d6,0x6e94a3d7,0x644b806a}}, // _bula, rzon_, тику, _afgi,
+ {{0x68e28efd,0x40969d85,0xa01b23d8,0x26cc8353}}, // phod, _прат, nför, _vodo_,
+ {{0x629e23d9,0x6d41a3da,0xf8bf0019,0x7ce302f1}}, // lepo, _dula, rmék_, _võrg,
+ {{0x26cc90dd,0xa2c40e70,0x6d41a3db,0x2904a3dc}}, // _todo_, राप्, _eula, _ijma_,
+ {{0x7c229234,0x6d41a3dd,0x60cd1613,0xa01b016d}}, // _vzor, _fula, _soam, kför,
+ {{0x6d41a3de,0x7c228035,0x5cd6804a,0x7d1ba3df}}, // _gula, _wzor, _підх, blus,
+ {{0x73e6a3e0,0x660907d9,0xa01b016d,0x7d1ba3e1}}, // _поез, şekk, dför, clus,
+ {{0x7c228a20,0x6d5e026b,0x6d41a3e2,0xddcc01d0}}, // _uzor, _awpa, _zula, _čišt,
+ {{0x629e23e3,0x66d0016d,0x661aa3e4,0x673a8372}}, // jepo, _säkr, _kytk, _jitj,
+ {{0xdd940048,0xa7fb128e,0x659423e5,0xa2c423e6}}, // лары, paño, лару, रान्,
+ {{0x752d0118,0x57b523e7,0x6aaa86b1,0x5183a3e8}}, // umaz, _абит, _haff, _суша,
+ {{0x752d23e9,0x6aaaa3ea,0x2f160366,0xd3711ef7}}, // [13b0] rmaz, _kaff, tægt_, شها_,
+ {{0x6aaaa3eb,0x629e23ec,0xff5001a8,0x78a9852a}}, // _jaff, gepo, _اخي_, _saev,
+ {{0x073601c6,0x7d1b8428,0x317ea3ed,0x661a862c}}, // _שאתם_, ylus, nutz_, _nytk,
+ {{0x6ee200e1,0x6d41a3ee,0x98bd02d6,0x00000000}}, // _vôbe, _rula, _ouvč_, --,
+ {{0x6d41a3ef,0x07a58fe7,0x317e81ec,0x7ce30074}}, // _sula, талн, hutz_, _võrd,
+ {{0x6d419397,0xa3ae0665,0xcf9400be,0x7e6705f3}}, // _pula, किर_, רטס_, najp,
+ {{0xf2d20159,0xdcfa884a,0x6b8d0601,0x7d1b8006}}, // _דעם_, _artı, ltag, tlus,
+ {{0xdb060884,0x6281a3f0,0x24868706,0x673a890d}}, // _erkä, wglo, _ccom_, _eitj,
+ {{0x7d1ba3f1,0x7e61a3f2,0x6aaa8365,0x6b8d1162}}, // rlus, _telp, _baff, ntag,
+ {{0x6d41a3f3,0x6aaaa3f4,0x7d1ba3f5,0x9e678065}}, // _tula, _caff, slus, _سائن,
+ {{0x7d1ba3f6,0x0574a3f7,0x6d41876d,0xc0e60a41}}, // plus, _ماند, _uula, вонк,
+ {{0x63ad88ae,0xa01b0106,0x6b8d23f8,0x62819670}}, // _šanu, vför, ktag, sglo,
+ {{0xa8568039,0x66048457,0x629523f9,0x9cd681c6}}, // כירה_, şikt, _obzo, _שונה_,
+ {{0xe1f183f8,0x6b8d23fa,0x658a8201,0x6aaaa3fb}}, // _پست_, dtag, _səhi, _gaff,
+ {{0xe73723fc,0x213b23fd,0x8d6304ae,0x925881a8}}, // тер_, _fiqh_, ивре, _أشهر_,
+ {{0xa3ae023c,0xa01b23fe,0x6aaaa3ff,0x2cada400}}, // किल_, rför, _zaff, nded_,
+ {{0x93432401,0xa01b00f2,0x6b8d10c1,0xcb672402}}, // [13c0] инте, sför, gtag, кате_,
+ {{0xf1fa2403,0x249fa404,0xa01b0106,0xd0f20032}}, // وعات_, heum_, pför, _faṣẹ,
+ {{0x629e2405,0xfaa31cce,0xcb1283c8,0xa2c41513}}, // repo, рахо, סלב_, राब्,
+ {{0xb91a0032,0x673aa406,0x080a80d7,0xdcfa82a6}}, // _aifẹ_, _sitj, وزشي_, _istħ,
+ {{0x2cada407,0x673aa408,0x2ca00074,0xdb0b823e}}, // dded_, _pitj, meid_, _orgà,
+ {{0xc7d70051,0x2ca02409,0xf4130039,0x63a41e8f}}, // כוני_, leid_, _כפר_, mwin,
+ {{0x6aaaa40a,0x63a4005d,0x5fa9000f,0x888482e3}}, // _raff, lwin, _चैनल, _میان,
+ {{0x4420801c,0x2ca002f1,0xfd0f81a8,0xef1f0061}}, // _ơi_, neid_, يجي_, ttük_,
+ {{0xccf30051,0x2b4600f7,0xf746a40b,0x63a4240c}}, // יכה_, íoch_, _резо, nwin,
+ {{0x2ca0240d,0x2129240e,0x2d9a0aa2,0x63ab006a}}, // heid_, _shah_, _oppe_, ægni,
+ {{0x60f9240f,0xc486a410,0x63a42411,0xfbcf8bca}}, // ення_, _алек, hwin, متی_,
+ {{0x6aaaa412,0x63a42413,0x6b8d2414,0xcd368481}}, // _waff, kwin, ytag, _شراب,
+ {{0x6aaa82a6,0x442080ff,0x2ca002f1,0x64430035}}, // _taff, _ái_, deid_, śnik,
+ {{0x29022415,0x442682a0,0x2d472416,0x6b8d2417}}, // anka_, _izo_, _põe_, vtag,
+ {{0x63a42418,0x69dca419,0x6b8085ee,0x00000000}}, // ewin, øred, lumg, --,
+ {{0x6b8d241a,0x200901c0,0x2ca0052a,0xba3781c6}}, // ttag, _txai_, geid_, _בטוח_,
+ {{0xbab902cb,0x3949807a,0x63a4241b,0x613a928a}}, // [13d0] нгах_, _časi_, gwin, _půln,
+ {{0x6b8d241c,0x24730028,0x7a40026f,0xbb1900f7}}, // rtag, ệm_, vští, رياض_,
+ {{0x6b8d241d,0x2ca0241e,0x7afda41f,0x249f808e}}, // stag, beid_, ésti, yeum_,
+ {{0xf7719a00,0x44268870,0xf2df002e,0x2ca00362}}, // مات_, _ozo_, _atât_, ceid_,
+ {{0x747a0039,0x78ad02f1,0x6d452420,0x7f198110}}, // _אנרג, _maav, _huha, ніку_,
+ {{0x6d452421,0x753d2422,0x6f152423,0x3ebea424}}, // _kuha, _hisz, nozc, _cntt_,
+ {{0x4426a425,0x6d450406,0x753d0019,0x29880dc7}}, // _azo_, _juha, _kisz, _исто_,
+ {{0x644d2426,0xf3f98087,0x60dd090d,0xd9288162}}, // ncai, laţi_, jksm, _ацул_,
+ {{0xe796853d,0x2cada427,0xf4959e29,0x644d2428}}, // _مالک, rded_, نشکد, icai,
+ {{0x63ad803b,0xdb0400a9,0x62852429,0x2fd9031d}}, // _šans, rviç, ngho, dysg_,
+ {{0x63a4242a,0x5dd58875,0x4426a42b,0xa2c403dd}}, // zwin, _حقائ, _ezo_, रात्,
+ {{0xcea90bea,0x78ad002a,0x29020079,0x63a40123}}, // _מי_, _caav, unka_, ywin,
+ {{0x645ba42c,0x7f44157a,0x776081c0,0xe2a880d7}}, // mbui, _quiq, _xwmx, راین_,
+ {{0x6d45242d,0x201e00eb,0x99d581ad,0xf3f98162}}, // _buha, āti_, _متبا, jaţi_,
+ {{0xfce6242e,0x2ca00074,0x63a4242f,0xe2968098}}, // гово, teid_, wwin, _сащ_,
+ {{0x30ae80ab,0x6d452430,0x03a613f7,0x78ad1a14}}, // _কক্স, _duha, либо, _gaav,
+ {{0x68e42431,0x2ca02432,0x6d452433,0x0c262434}}, // [13e0] _ilid, reid_, _euha, уман,
+ {{0xa2c42435,0x2ca00006,0x63a4044e,0x6aa1808e}}, // राध्, seid_, rwin, nelf,
+ {{0x3f822436,0x394f802e,0x6d4502a0,0x2d9a016d}}, // luku_, _însă_, _guha, _uppe_,
+ {{0x7d7b012a,0x74550698,0xa3e3809a,0x600a8110}}, // _אנטו, _върх, नना_, ннем_,
+ {{0x7d042437,0xa2c40697,0x6d452438,0x3f822439}}, // mnis, राद्, _zuha, nuku_,
+ {{0xf3f9802e,0x765c243a,0xe16601a8,0x716601a8}}, // caţi_, nbry, _مدري, _مارك,
+ {{0x91e60134,0x7d0d9234,0x3f82243b,0x68e4243c}}, // _сове, časn, huku_, _olid,
+ {{0x33749630,0x7d04138e,0x44268140,0x7981a43d}}, // агир, nnis, _pzo_, dulw,
+ {{0x7d04243e,0xd70a81f3,0x3f82126a,0x1616243f}}, // inis, _знае_, juku_, _दुसर_,
+ {{0xbda68077,0x78ad01c2,0x7d042440,0x68e42441}}, // _محصو, _saav, hnis, _alid,
+ {{0x7d040e51,0x527b00be,0x765c2280,0x6f1504e8}}, // knis, ענטא, dbry, vozc,
+ {{0x27e62442,0x33869c79,0x6d452443,0x2ba510d0}}, // _żona_, _букв, _ruha, _ऑनला,
+ {{0x44268870,0x6d450239,0x3f822444,0x60dd2445}}, // _uzo_, _suha, guku_, rksm,
+ {{0x6d452446,0x68e401f6,0x7d042447,0x02a3847f}}, // _puha, _elid, enis, _гръм,
+ {{0x59a40063,0x753d2448,0x78ad14c7,0x7afaa449}}, // _गैलर, _pisz, _taav, mitt,
+ {{0x7afaa44a,0x3f820904,0x5455244b,0x6d45244c}}, // litt, buku_, рват, _vuha,
+ {{0x753d0065,0x644d244d,0xf3f98087,0xe0208105}}, // [13f0] _visz, rcai, taţi_, _बरगद_,
+ {{0x644d244e,0x7afaa44f,0x320b0d8f,0x657a0174}}, // scai, nitt, нхен_, áthc,
+ {{0x753d0065,0x7d0402af,0x658a8085,0xad27004e}}, // _tisz, bnis, _məhs, _مرحو,
+ {{0x7afaa450,0x6aa18a0f,0xe82082f1,0x3ea32126}}, // hitt, zelf, _बरखा_, lejt_,
+ {{0x62989807,0x2d910102,0xd5b88dc1,0x938b2451}}, // _obvo, ltze_, _arā_, _ясна_,
+ {{0xd9f68075,0x4b7a03de,0x60c082d6,0x261a0075}}, // ुपात_, _באשו, _enmm, _मुडी_,
+ {{0x2d8309a4,0x7afaa452,0x249900b9,0x2d91011b}}, // nuje_, ditt, _kbsm_, ntze_,
+ {{0x09e10770,0x2909004f,0x67d58a42,0x7d0d94f0}}, // पनीय, _njaa_, _кобу, časo,
+ {{0x2d830bfb,0xb4d58b9f,0xe787a453,0x645ba454}}, // huje_, _सकी_, _љубо, rbui,
+ {{0x3ae400f2,0x6442a455,0x7afaa456,0x3f820289}}, // _köpa_, _egoi, gitt, vuku_,
+ {{0x41062457,0xdeba83c8,0x7d042458,0x8a388198}}, // изов, _שמעל, ynis, ляют_,
+ {{0x3c478013,0x3f822459,0x6aa1879f,0xab9580e8}}, // _إضاف, tuku_, self, _виді,
+ {{0x3949a19f,0x7d04007d,0x7d04a45a,0xf746245b}}, // _času_, vnis, čist, рего,
+ {{0x3f82003a,0x69daa45c,0x26d30118,0x7afa847f}}, // ruku_, byte, _coxo_, citt,
+ {{0x7d04245d,0x3f82245e,0x6da602c7,0x765c245f}}, // tnis, suku_, шива, rbry,
+ {{0x3f822460,0x81bd00ab,0x26f28697,0x7ce302f1}}, // puku_, েনা_, _अग्र_, _põra,
+
+ {{0x26d303a8,0x7d041162,0x234800d7,0x39478580}}, // [1400] _foxo_, rnis, اسری_, _huns_,
+ {{0x2d832461,0x42560dc0,0x3947867f,0x658a811c}}, // buje_, ртат, _kuns_, _məhr,
+ {{0x50669ac9,0x867b00be,0x7e7b0039,0xc6930039}}, // атла, _בריו, _באיז, _ואת_,
+ {{0xd7faa462,0xe7b30bca,0xa2c40aed,0x7afaa463}}, // кул_, _امید, राह्, zitt,
+ {{0x27e08307,0x3947862f,0x44242464,0x212d81b4}}, // áin_, _luns_, _ám_, _dheh_,
+ {{0x78a2a465,0x64428046,0xa2e60009,0xdd9aa466}}, // seov, _sgoi, _тогд, вши_,
+ {{0x35678048,0x3947a467,0x7afa95c8,0xf8bf00e7}}, // арын_, _nuns_, vitt, llée_,
+ {{0x3d10a468,0xd9432469,0x63b99670,0x658a811c}}, // ावें_, _фети, _brwn, _bəhr,
+ {{0x66768077,0x7afaa46a,0xb4d6816f,0x2b4680ff}}, // _ندار, titt, _हवी_, _quoc_,
+ {{0xf2d300be,0xf2df00ff,0x69daa46b,0x4ac90f3a}}, // ַער_, _luân_, tyte, रायव,
+ {{0x7afaa46c,0x394783a8,0x26d3241f,0x6f18a46d}}, // ritt, _cuns_, _roxo_, movc,
+ {{0x7afaa46e,0x2d83003e,0x3949811f,0x394784c3}}, // sitt, vuje_, _čast_, _duns_,
+ {{0xcb548277,0x658a8201,0x893780be,0x61ff8085}}, // _انتظ, _təhs, ירטע_, _əylə,
+ {{0x6f18a46f,0x6d48a470,0xdefb01e2,0x3ce681c0}}, // novc, _kuda, тым_, _hlov_,
+ {{0x998480f7,0x69da8168,0x272981a8,0x3ea30168}}, // _العو, qyte, núna_, rejt_,
+ {{0x6d48a471,0x3ea32472,0x657a00f7,0xf8bf2473}}, // _muda, sejt_, átha, rmés_,
+ {{0xc2991b69,0x3ea300f1,0x26d3002a,0x213f8359}}, // [1410] лках_, pejt_, _toxo_, _riuh_,
+ {{0xa2c40778,0x29192474,0x68fd2475,0xddc38087}}, // राष्, losa_, lisd, nanţ,
+ {{0xf9f901a8,0x6f18936f,0x80cb81a2,0x80c38072}}, // دفاع_, dovc, _सचदे, ळाले,
+ {{0x68fd2476,0xb4d5a0d5,0x6d57011b,0x112880e8}}, // nisd, _सके_, _itxa, аючи_,
+ {{0x38691de8,0x2d9ea477,0x395a02c4,0x6d48016b}}, // _mear_, _apte_, apps_, ídav,
+ {{0x6d48a478,0xef188a8e,0x386901a8,0x9a840251}}, // _buda, амі_, _lear_, _мусл,
+ {{0x29192479,0x6d48a47a,0xa2c40c46,0x7ae700b9}}, // kosa_, _cuda, रार्, _iljt,
+ {{0x6d48a47b,0x2489247c,0xf2df001c,0xe91980e8}}, // _duda, ngam_, _xuân_, _собі_,
+ {{0xdd0205f3,0x6d48a47d,0x3940247e,0x658a811c}}, // šuću, _euda, _riis_, _qəhr,
+ {{0x3940247f,0x7645031d,0xf3ff0187,0xcac98d15}}, // _siis_, _nghy, _leão_, угое_,
+ {{0x6d48a480,0xa06a2481,0x6d5701c5,0x186a2482}}, // _guda, лана_, _ntxa, лани_,
+ {{0xcd968039,0x38692483,0x29192484,0x62889de6}}, // _לדעת_, _cear_, gosa_, agdo,
+ {{0xe8f72485,0x39400074,0x6d570cdb,0xe2999c68}}, // сля_, _viis_, _atxa, _бал_,
+ {{0x7c2b8025,0x63a9a486,0x6d48a168,0xddc38087}}, // _izgr, mwen, _yuda, canţ,
+ {{0x63a9805d,0x39402487,0x38692488,0x8db5004a}}, // lwen, _tiis_, _fear_, ості,
+ {{0x3991a489,0xf2df0028,0x6b84248a,0xb4d5954b}}, // más_, _quân_, tuig, _सको_,
+ {{0x39918117,0x60c4248b,0xe29a02df,0x63a9a48c}}, // [1420] lás_, _inim, лаа_, nwen,
+ {{0x16aa00af,0xfd690133,0x20ef0326,0x7b1d823e}}, // _свои_, _jupụ, nƙin_, cèut,
+ {{0xe29702cb,0x38690051,0x63a9a48d,0xbed700be}}, // сах_, _year_, hwen, _הויז_,
+ {{0xb4d68e1a,0x6d5c0cd4,0x63a9a48e,0x6d48a48f}}, // _हवे_, mpra, kwen, _ruda,
+ {{0x6d48a490,0x351b0051,0xdefa8a41,0x64460122}}, // _suda, _תוכנ, лык_, _igki,
+ {{0x3ce6826f,0x3991a491,0x75242492,0x6ffc00eb}}, // _slov_, kás_, lliz, mācī,
+ {{0x6d5c0247,0xfe6f8060,0x29192493,0x6f18a494}}, // npra, ردو_, yosa_, rovc,
+ {{0x39918065,0x4422079f,0x3b548652,0xdbdd00e1}}, // dás_, _kyk_, окир, zšír,
+ {{0xddc3802e,0x9696813a,0x64460359,0xfd69019d}}, // tanţ, _греш, _mgki, _bupụ,
+ {{0x6d48a495,0x4974a45b,0x38690229,0x2ca4a496}}, // _tuda, олос, _sear_, semd_,
+ {{0x629c020f,0x4422079f,0xddc38087,0xba9b03de}}, // _mbro, _lyk_, ranţ, נסטי,
+ {{0x23c802f1,0x7aef8722,0x3ce6a497,0x6d5c2498}}, // रमाद, èctr, _ulov_, dpra,
+ {{0x7afe0125,0x75242499,0x6c36a3f7,0x5f94249a}}, // kipt, dliz, _افشا, пият,
+ {{0xb5fc84b7,0x39918019,0x60c4249b,0xddc3826c}}, // _reġj, bás_, _enim, lanš,
+ {{0x2919249c,0x1dbd881f,0x6d5c200b,0xf3ff02df}}, // posa_, ्मयत, gpra, _peão_,
+ {{0x99860063,0x4422249d,0xddc380eb,0x657c854f}}, // łoś_, _byk_, nanš, _århu,
+ {{0x8b5801a8,0x629c0362,0x2489249e,0x2d7301a1}}, // [1430] مجلس_, _bbro, rgam_, sće_,
+ {{0x7524249f,0x2d7324a0,0x3f868118,0x442224a1}}, // aliz, pće_, duou_, _dyk_,
+ {{0x752424a2,0x66d00009,0x7f3c00be,0x1a9b80be}}, // bliz, _näky, _געזו, ריבע,
+ {{0xad270416,0x38698125,0x9bc7027e,0xfc3f01df}}, // _درخو, úar_, _лёгк, _saíu_,
+ {{0x39918065,0x7c229eb3,0x7f49a4a3,0x39580580}}, // zás_, _nyor, _queq, _etrs_,
+ {{0x7afe059c,0xab5b02af,0x8d558056,0xe739a4a4}}, // cipt, _prüf, отич, шел_,
+ {{0x6d42a4a5,0xc05b8558,0xed598150,0x63bd008e}}, // _bioa, _біз_, шой_, _hrsn,
+ {{0x6d42a4a6,0x7cfe9482,0x68e2a4a7,0x63bd025b}}, // _cioa, tūra, ckod, _krsn,
+ {{0x69dc8bc5,0x68e9a4a8,0x63a98a0f,0x63bb00fe}}, // øren, _iled, uwen, _šunj,
+ {{0x60c4003b,0x3991a4a9,0x81d480c8,0x68e9800d}}, // _snim, tás_, _সেই_, _hled,
+ {{0x3ea785ef,0x68e9a4aa,0x63a9805d,0x54558323}}, // ment_, _kled, swen, _двет,
+ {{0x2d87a4ab,0x3ea08067,0x6d5c007a,0x3ea7a4ac}}, // mune_, đite_, vpra, lent_,
+ {{0x7c228065,0x7d248009,0x7c2ba4ad,0xb05b016d}}, // _gyor, _эффе, _uzgr, tjär,
+ {{0x4422067f,0x6d5c24ae,0x68e2a4af,0x7d1ba4b0}}, // _ryk_, tpra, zkod, lous,
+ {{0x3ea782be,0x8237826a,0x7d09a4b1,0x290d802a}}, // ient_, _ارشا, ones, _ojea_,
+ {{0x395824b2,0xe45a0323,0x3ea7a4b3,0x2a6a00ee}}, // _rtrs_, ржа_, hent_, _sebb_,
+ {{0x3ea7a4b4,0xa11300f7,0x752424b5,0x2a78008e}}, // [1440] kent_, _تويت, rliz, _pdrb_,
+ {{0x6d5c24b6,0x3ea7a4b7,0x63bd1b3b,0x68e9816a}}, // ppra, jent_, _drsn, _aled,
+ {{0x7afe24b8,0x3ea7a1eb,0x8c46054c,0x2d87a4b9}}, // ript, dent_, _мене, june_,
+ {{0x4fc681bb,0x3f868ece,0xa99a80be,0x2d87a4ba}}, // жска, tuou_, יבער, dune_,
+ {{0x7d1b81df,0x7d090669,0xddc38088,0xbba8886a}}, // dous, čest, vanš, _कनेक,
+ {{0x7afe08cf,0x3ea78bf9,0x68e2a4bb,0x68e9a4bc}}, // qipt, gent_, skod, _eled,
+ {{0xbb4311d2,0xc448819f,0xf9878277,0xa2cda4bd}}, // _церк, میان_, _تب_, तान्,
+ {{0xd4978012,0x7d1ba4be,0x151780f7,0xd25080d7}}, // орь_, gous, يزية_, کند_,
+ {{0x3ea7a4bf,0x39448101,0xe7a90035,0xcb0a81a4}}, // bent_, _iims_, _चैंप, иход_,
+ {{0x21268b50,0x3ea7a4c0,0xb5fc81b9,0x7d0981e8}}, // lloh_, cent_, _leġi, anes,
+ {{0x2d87a4c1,0x3958a4c2,0x6e93803d,0x7d1ba4c3}}, // cune_, ërs_, _آلما, bous,
+ {{0x7d1ba4c4,0xeb9a8a94,0x6d5aa4c5,0x6ec283ca}}, // cous, _вид_, _itta, लाकु,
+ {{0x7762011c,0x6f1c24c6,0x5ead8264,0x7c228db1}}, // _çoxl, forc, চারে, _uyor,
+ {{0x3f508028,0x6f1c0024,0x61318370,0x63a2826b}}, // _màu_, gorc, måle, _ipon,
+ {{0xd5b80abe,0x3eb30085,0xfc3f801b,0x2b4b01e8}}, // ост_, _vaxt_, žít_, _succ_,
+ {{0x3ea7a4c7,0x7d00880a,0x1fa724c8,0x63bd1b99}}, // zent_, nims, _дрог, _prsn,
+ {{0x964701e5,0x200c0201,0x3ea7a4c9,0x43940251}}, // [1450] _мэнд, şdir_, yent_, матс,
+ {{0x7d09a4ca,0x6d5a919d,0x68fba4cb,0x63bd0042}}, // znes, _otta, _smud, _vrsn,
+ {{0xaca301bc,0x3ea7a4cc,0x7d00a4cd,0xdcf50035}}, // _ahụb, vent_, kims, erzą,
+ {{0x3ea7a4ce,0x63a287c6,0x76550035,0x39448980}}, // went_, _opon, lczy, _cims_,
+ {{0x6d5aa4cf,0x7d098aa2,0x98c60289,0x995401d0}}, // _atta, vnes, šući_, _výše_,
+ {{0x63ad0555,0x643a0158,0x2d87a4d0,0x2ca924d1}}, // mwan, _לערנ, tune_, lead_,
+ {{0x7d1ba4d2,0x3ea78218,0x63ad24d3,0x63a2a4d4}}, // tous, rent_, lwan, _apon,
+ {{0x3ea7a4d5,0x68fba4d6,0x2ca924d7,0x2d87a4d8}}, // sent_, _umud, nead_, rune_,
+ {{0xff538b53,0x7d1ba4d9,0x291da4da,0x6d5a82ec}}, // _آخر_, rous, mowa_, _etta,
+ {{0x2ca924db,0x63a000f2,0xdd008029,0x7d09a4dc}}, // head_, _ämne, ētāj, snes,
+ {{0x63ad24dd,0x386da4de,0x7d1ba4df,0x63a2a4e0}}, // hwan, _keer_, pous, _epon,
+ {{0x63ad24e1,0x0d848048,0x386d8079,0x23759459}}, // kwan, _ілін, _jeer_, _فاتح,
+ {{0x386d9302,0xb5fc84b7,0x63ad24e2,0x7cee8362}}, // _meer_, _reġi, jwan, _dùrd,
+ {{0x386da4e3,0x63ad24e4,0x27328087,0x248d9dff}}, // _leer_, dwan, când_, lgem_,
+ {{0x1dbd88af,0xa928826f,0x291da4e5,0x09e581e5}}, // ्मित, ližš, kowa_, _хойн,
+ {{0x2ca90706,0x7afc06d4,0x6f1c016a,0x386d837a}}, // gead_, _smrt, sorc, _neer_,
+ {{0xfc3f04c3,0x291da4e6,0x63ad24e7,0x2d8324e8}}, // [1460] _saír_, dowa_, gwan, arje_,
+ {{0x290224e9,0xe81f8740,0x78b60214,0x6b96016d}}, // lika_, _बड़ा_, _hayv, rtyg,
+ {{0xb87b0876,0x5ba880d4,0x399504e1,0x6edb01c6}}, // chís, _कन्व, gås_, _החיפ,
+ {{0x29020288,0x63ad24ea,0x2fc001a3,0x3eaa0192}}, // nika_, bwan, _krig_, lebt_,
+ {{0x4cbb0051,0xdcfc0029,0x2d9824eb,0x6b898264}}, // _הזכו, strē, ltre_, jueg,
+ {{0x290224ec,0x6d4624ed,0xb4b48105,0x6b8424ee}}, // hika_, _hika, _छोड़_, mrig,
+ {{0x290224ef,0x2d9824f0,0x6d4624f1,0x291da4f2}}, // kika_, ntre_, _kika, bowa_,
+ {{0xe05591fb,0x94aa8bac,0x6eeb0201,0x6d4616f8}}, // _قیمت_, стка_, _müba, _jika,
+ {{0x290224f3,0x6d4624f4,0x6b8424f5,0x7f4a826a}}, // dika_, _mika, nrig, _طلاق_,
+ {{0x6d4624f6,0x386d8a0f,0x61e1a4f7,0x248d83a7}}, // _lika, _zeer_, fyll, agem_,
+ {{0x290224f8,0x63ad24f9,0x6b8402af,0x6d5a8b64}}, // fika_, zwan, hrig, _utta,
+ {{0xc0580a4c,0x63ad0063,0x2fc0031d,0x6b840bbd}}, // зір_, ywan, _brig_, krig,
+ {{0x613f0029,0x3869808b,0xb4db0722,0x27328087}}, // _jūli, ðari_, _diàl, pând_,
+ {{0x4426a4fa,0x6d4624fb,0xa3c209a3,0xa3c00540}}, // _iyo_, _aika, ्मन_, ंटन_,
+ {{0x26da0698,0xcb138158,0x6d4624fc,0x2902028d}}, // _dopo_, _אלס_, _bika, bika_,
+ {{0x6d4624fd,0x44268365,0xe81e123a,0x2d8300f1}}, // _cika, _kyo_, _युवा_, rrje_,
+ {{0x2d9c1918,0x6d46059c,0xa87c0051,0x386d82a3}}, // [1470] _även_, _dika, _האחר, _reer_,
+ {{0x63ad0578,0x6eef8022,0x7cea003e,0x291da4fe}}, // rwan, _købe, _výra, wowa_,
+ {{0x68ed07df,0x63ad24ff,0x6d462500,0x291da501}}, // _klad, swan, _fika, towa_,
+ {{0xf1c00104,0xf77185ff,0x399500f2,0xddde001b}}, // ương_, نات_, rås_, _nepř,
+ {{0x68ed15d8,0x6eef813c,0x4426a502,0x611200eb}}, // _mlad, _løbe, _nyo_, nāla,
+ {{0x386d81d8,0x6da5a503,0x291da504,0x6d462198}}, // _weer_, дика, sowa_, _zika,
+ {{0xfe708d4a,0x68ed046d,0x4426a505,0xc9842410}}, // اده_, _olad, _ayo_, _пути,
+ {{0x7d0d2506,0x44268e4d,0x29022507,0x61120ec3}}, // nnas, _byo_, xika_, kāla,
+ {{0x248da508,0x442682a0,0xdb1d2509,0x61e18114}}, // rgem_, _cyo_, _arsé, wyll,
+ {{0x6721803b,0x68ed250a,0x7cf1250b,0x7d0d031d}}, // _uklj, _alad, _hård, hnas,
+ {{0x2902250c,0x68ed0cda,0x60db8247,0x6f038098}}, // tika_, _blad, _koum, minc,
+ {{0x68ed250d,0x61e1a50e,0x7d0d026c,0xe45f0106}}, // _clad, ryll, jnas, _snön_,
+ {{0x2902250f,0x61e18355,0x6d462510,0xdb0f2511}}, // rika_, syll, _rika, _escé,
+ {{0x2d982512,0x68ed2513,0x29022514,0x7529a515}}, // ttre_, _elad, sika_, llez,
+ {{0xceb2878d,0xf7432516,0x29022517,0x31160a0e}}, // כים_, теро, pika_, _офис,
+ {{0xd1320b76,0x2d982518,0x7d0d007b,0x60dba519}}, // _جمع_, rtre_, gnas, _noum,
+ {{0x26da251a,0xeb97251b,0xddc7251c,0x442681c0}}, // [1480] _topo_, мит_, rajš, _xyo_,
+ {{0x6d46009c,0xa01b251d,0x7c26016b,0x7d0d0706}}, // _wika, rgöt, _vykr, anas,
+ {{0x6d460341,0x6f03a51e,0x644b81e8,0x810206a7}}, // _tika, dinc, _oggi, _लगाओ_,
+ {{0x6b84251f,0x61e30edd,0x236000f3,0xa3bf801b}}, // prig, ønla, ppij_, ुमा_,
+ {{0x4f579e91,0x60db8bfe,0x6f03a520,0x75298da8}}, // وجود_, _doum, finc, dlez,
+ {{0x644b8698,0x7d042521,0x75228353,0x75298010}}, // _aggi, liis, _skoz, elez,
+ {{0x1ea980f7,0x7ae30009,0xa01b2522,0x4096a523}}, // ثاني_, önte, ngör, _орат,
+ {{0x7529a524,0x2902816d,0x44268683,0x629e2525}}, // glez, _öka_, _pyo_, lfpo,
+ {{0x3866a526,0x6f0385a3,0xdb04002a,0xd00781a1}}, // mbor_, binc, lviñ, _жете_,
+ {{0x68ed077b,0x6d5e003a,0x2b47809f,0x7d040079}}, // _slad, _otpa, _cinc_, hiis,
+ {{0x6d5ea527,0x68ed0022,0x7d042528,0x7cf1104a}}, // ípad, _plad, kiis, _håre,
+ {{0x80e080c8,0x4426a529,0x7cf1006f,0x63a61384}}, // পোর্, _tyo_, _kåre, _opkn,
+ {{0x68ed0289,0x7d0402a3,0x6d5e252a,0x59b0805e}}, // _vlad, diis, _atpa, _जनवर,
+ {{0xe8df801c,0x68ed252b,0x3f99252c,0x69c101a1}}, // _miền_, _wlad, rtsu_, _šlem,
+ {{0x442d252d,0xc3488028,0xe8df801c,0x7d0d252e}}, // _še_, _nổi_, _liền_, tnas,
+ {{0x3998a52f,0x6f03a530,0x7d042531,0x30a704bd}}, // més_, zinc, giis, _цркв,
+ {{0xe667017a,0x3998a532,0x6e2883ba,0x60dba533}}, // [1490] _отно, lés_, _lydb, _roum,
+ {{0x7d0d2534,0x4ac9000f,0x3f8687b1,0x35b52535}}, // snas, राइव, drou_, _збор,
+ {{0x6f03a536,0x7d042537,0x3998a538,0xa2c42539}}, // vinc, biis, nés_, राज्,
+ {{0x7d0401b4,0xa8158162,0x4f958087,0x61360144}}, // ciis, ндеш, ерку, rálg,
+ {{0x3f868ece,0x7cf100f2,0x3949253a,0x399880e7}}, // grou_, _vård, _mias_, hés_,
+ {{0x39490282,0x8ca1809a,0x6d580036,0x6b8d0c53}}, // _lias_, क्नो, _évac, muag,
+ {{0x6f03a53b,0x7bc2a53c,0x60dba53d,0xada5a53e}}, // rinc, _arou, _toum, хайл,
+ {{0x6234253f,0x60c9a540,0x39490069,0x3998a541}}, // _ресу, _unem, _nias_, dés_,
+ {{0xa2c4016f,0x7bc2a542,0x6136016b,0x7529890d}}, // राच्, _crou, nále, rlez,
+ {{0xa3c20c78,0x7529928a,0x394906ae,0xaad20b84}}, // ्मत_, slez, _aias_, सायक,
+ {{0x39492543,0x3998a544,0x2b47874a,0x290f8db1}}, // _bias_, gés_, _vinc_, onga_,
+ {{0x290f81e0,0x7bc284cc,0xf8bf003e,0x39492545}}, // nnga_, _frou, blém_, _cias_,
+ {{0x39492546,0x7bc2a547,0x2b47809f,0x7d0421e8}}, // _dias_, _grou, _tinc_, viis,
+ {{0xfbd08065,0x6136026f,0x2cad8079,0x3998a548}}, // _ختم_, dále, leed_, bés_,
+ {{0x18a615e3,0x64a62549,0x399887e2,0x7d04254a}}, // _займ, нава, cés_, tiis,
+ {{0x7cea0a56,0x2d878c41,0x2900254b,0x2cad81b4}}, // _výro, irne_, _umia_, need_,
+ {{0x7d04254c,0x6136254d,0x6b9ba54e,0xccf280be}}, // [14a0] riis, ráld, ntug, אכן_,
+ {{0x7d0418dc,0x2cb90118,0x2cada54f,0x39490282}}, // siis, _easd_, heed_, _zias_,
+ {{0x39490282,0x7cf1120b,0x8886a550,0x7d042551}}, // _yias_, _såre, ележ, piis,
+ {{0xa3c206a7,0x70538077,0x3f86a552,0xdd2f001b}}, // ्मद_, هنما, trou_, pěšn,
+ {{0x39988065,0x2d87a553,0x78a42554,0x3c7701c6}}, // zés_, erne_, _mbiv, _אתכם_,
+ {{0x290fa555,0x399880e7,0x7bc48110,0x7cf10b64}}, // anga_, yés_, _šiuo, _våre,
+ {{0xf9879ddd,0x2cada556,0xc69300be,0x212b0114}}, // _حب_, feed_, טאג_, ylch_,
+ {{0xe8df8104,0x2cad8079,0x61469c8b,0x7cee8362}}, // _tiền_, geed_, _чеба, _bùra,
+ {{0x02e401ce,0xdccf8028,0x39492557,0x69c3a558}}, // _गवाह_, _tỉnh_, _rias_, _arne,
+ {{0x3998a559,0x7bc2a4de,0x93fb03c8,0x78a4255a}}, // tés_, _vrou, פליי, _abiv,
+ {{0x0d868847,0x3949255b,0xb05b0338,0xfaa38048}}, // _член, _pias_, ndäg, ушын,
+ {{0x39989b01,0xa3c21299,0xfc648bca,0x7bc2a55c}}, // rés_, ्मा_, _شخصی, _trou,
+ {{0x3998a532,0x3949255d,0xa30b8117,0xa2e69ccf}}, // sés_, _vias_, _کرنے_, _подд,
+ {{0x3998a55e,0x8c4395d2,0x26de804f,0x9f35004a}}, // pés_, _сече, _joto_, тегі,
+ {{0x394910af,0x3eba255f,0x62830511,0x7e752560}}, // _tias_, _dapt_, ónom, razp,
+ {{0x6b8d2561,0x0ccf00ab,0x81d60264,0x7cf1006f}}, // tuag, রস্ত, িনা_, _sårb,
+ {{0x60cd02a0,0x3eba002e,0x26cca562,0x7d02826c}}, // [14b0] _inam, _fapt_, _ondo_, _mmos,
+ {{0x78bd04fe,0x78a415db,0x26dea563,0x6b8d2564}}, // ldsv, _zbiv, _noto_, ruag,
+ {{0x2cad82c1,0xab5b02af,0x290f8bb1,0x7d02a565}}, // yeed_, _grün, tnga_, _omos,
+ {{0x82341301,0x78bd0bcb,0xa95400e8,0xd47900be}}, // اركا, ndsv, укці, _קאָל,
+ {{0x27e68065,0x78bd0fb0,0x60cd004f,0x26dea566}}, // gyon_, idsv, _mnam, _boto_,
+ {{0x7d02a567,0xe8df8028,0x752d2568,0x26de8333}}, // _amos, _biển_, llaz, _coto_,
+ {{0x30a7a569,0xa8a799b8,0x62340ca0,0x387f808e}}, // _прав, _прак, _беру, _tdur_,
+ {{0x6b9ba56a,0x7cf58661,0x09e480ab,0x81c88264}}, // ttug, _márg, _ফেভা, োনা_,
+ {{0x249fa56b,0x2cada56c,0xf69984ae,0x69dca56d}}, // rfum_, reed_, _овај_, ären,
+ {{0x6b9ba56e,0x60cd256f,0x78bba570,0x752d237d}}, // rtug, _anam, _hauv, hlaz,
+ {{0x3f8fa571,0x6723822b,0x6b9ba572,0x60cd0144}}, // lugu_, monj, stug, _bnam,
+ {{0x6723a573,0x60cd0362,0xa3d1001b,0x69c382d4}}, // lonj, _cnam, वमा_, _vrne,
+ {{0x3ae4016d,0x78bb80e7,0xe29983a7,0x442b2574}}, // _köpt_, _mauv, _жал_, _nyc_,
+ {{0x290680ad,0x6d4ba575,0x60cd2576,0x6723a577}}, // zioa_, _higa, _enam, nonj,
+ {{0x3f8f8578,0x6d4ba578,0x27e6942a,0x752d2579}}, // hugu_, _kiga, zyon_, flaz,
+ {{0x6d4b8300,0x3f8fa57a,0xfbe700ff,0x7cfea57b}}, // _jiga, kugu_, _thể_, tūri,
+ {{0x6d4ba57c,0x69c1a57d,0x0326257e,0x442b00ee}}, // [14c0] _miga, lvle, вдан, _cyc_,
+ {{0x6d4ba57f,0x394a026f,0x3f8fa580,0x6b89802a}}, // _liga, _časy_, dugu_, ireg,
+ {{0xe2971a8f,0x26dea581,0x6723a582,0x752d2583}}, // тах_, _roto_, donj, blaz,
+ {{0x26dea584,0x6b8980f3,0xe78681e2,0xa9670adb}}, // _soto_, kreg, вуко, тица_,
+ {{0xa2b2835a,0x26dea585,0xa8570039,0x75242586}}, // _असल्, _poto_, דינה_, moiz,
+ {{0x6b898620,0x6723a587,0x57fb0039,0x7bc6022b}}, // dreg, gonj, ולנו, _irku,
+ {{0x27e6a588,0x6d4ba589,0xe297258a,0x7bc081c0}}, // syon_, _biga, _шар_, wvmu,
+ {{0x399c0722,0x6abc258b,0x6b89a58c,0x753982d6}}, // mís_, _harf, freg, _chwz,
+ {{0x26de803e,0x6d4b859e,0x6b89a58d,0x6abc00a4}}, // _toto_, _diga, greg, _karf,
+ {{0x613184b8,0x78bb805c,0x60cd258e,0x78a2a58f}}, // håll, _zauv, _snam, lfov,
+ {{0xa2c30f21,0x399c2590,0x6abc2591,0x68e0a592}}, // रयत्, nís_, _marf, _komd,
+ {{0x6d4b84a7,0x7bc62593,0x7cf581ca,0x92ae00ab}}, // _giga, _orku, _cárd, কায়_,
+ {{0xe9ff8104,0x399c00f7,0x7bd601c0,0x53b6a594}}, // _phải_, hís_, jxyu, _अनिश,
+ {{0x6d4ba595,0x3d952596,0x28a4864a,0x65952597}}, // _ziga, лигр, _गॉसि, лагу,
+ {{0x5e58035f,0x7bc62598,0x2904a599,0x09e384bd}}, // тися_, _arku, _omma_, _војн,
+ {{0x5183a59a,0x60cd259b,0xc9838468,0x399c259c}}, // _туша, _unam, _туши, dís_,
+ {{0x6d408009,0x6abc259d,0xe9ff80ff,0x6d580866}}, // [14d0] imma, _barf, _thải_, _évan,
+ {{0x2904a59e,0x78bb82be,0xe8df8028,0x61360125}}, // _amma_, _sauv, _miễn_, mála,
+ {{0x6abc259f,0xce330bca,0x22470884,0xd36680d7}}, // _darf, _خودک, änk_, _گه_,
+ {{0xf8bf25a0,0xfc640698,0x3f8fa5a1,0xfc3f06a5}}, // ndé_, _върн, tugu_, _maíz_,
+ {{0xd3668154,0x70848048,0x6abc25a2,0x6d4ba5a3}}, // _ده_, ргіз, _farf, _riga,
+ {{0x6d4b883a,0x6abc25a4,0x7e7880eb,0x539a04de}}, // _siga, _garf, gavp, _אישו,
+ {{0x613600f7,0x399c25a5,0xdcef81a1,0x6d4ba5a6}}, // hála, cís_, _žeđa, _piga,
+ {{0x6b89a5a7,0x2602816f,0xe3b9a4c8,0x69c7008e}}, // treg, _वेळी_, еби_, _irje,
+ {{0x6d4ba5a8,0x6723a5a9,0x032581cf,0xecc50264}}, // _viga, ponj, лдин, _একাড,
+ {{0x6b89a5aa,0xe8df8104,0x61e8831d,0x645ba5ab}}, // rreg, _diễn_, fydl, scui,
+ {{0x049587bd,0x394d8b3c,0x6d4ba5ac,0x7c2b809a}}, // _ملاح, _kies_, _tiga, _wygr,
+ {{0x6b898e67,0x69c1a5ad,0xa6e28125,0x2d910234}}, // preg, rvle, _öðru, duze_,
+ {{0x7d09a5ae,0x394d9405,0xfaa5a5af,0x7524007d}}, // mies, _mies_, _бало, voiz,
+ {{0x272981df,0x69c725b0,0xe2998320,0x24990122}}, // múns_, _orje, _чак_, _acsm_,
+ {{0x52398158,0xd33681c6,0x26ca8118,0x2d910542}}, // _אײַנ, _גרסה_, _óboe_, guze_,
+ {{0x7d09a5b1,0x399c25b2,0x394d84b7,0xa3c00540}}, // nies, vís_, _nies_, ंटल_,
+ {{0x42ca02df,0x4ab7835a,0x7cf5a5b3,0x2d8a026c}}, // [14e0] нган_, _असाव, _kárb, srbe_,
+ {{0x80a005e8,0xe2970087,0xa3d48063,0x2cbd8282}}, // ग्रे, лау_, हमत_, _lawd_,
+ {{0x6abc25b4,0x869b012a,0x8cbf0327,0x7ce30074}}, // _varf, _אייז, _लोको, _võrr,
+ {{0x399c22c6,0x2cbd822c,0x6ae0801b,0x50ba8199}}, // rís_, _nawd_, _पक्र, عداد_,
+ {{0xb8dc9d01,0x394da5b5,0xdee6a5b6,0x7d09a5b7}}, // _अस_, _dies_, лоди, dies,
+ {{0x2d808029,0xd5ba9324,0x3f598580,0x394d821e}}, // ņiem_, еси_, _dèu_, _eies_,
+ {{0x7d09a5b8,0xc693025f,0x69c705f3,0xd13281a8}}, // fies, צאה_, _grje, _همس_,
+ {{0x2904a5b9,0xfc3f05e4,0x7d09a5ba,0x62838114}}, // _umma_, _raíz_, gies, _adno,
+ {{0x4e968307,0x2729862f,0xd2508013,0x12e68071}}, // _مشار, gúns_, ينة_, гінг,
+ {{0x7cf804c3,0x61e8816d,0xe8df801c,0x61360019}}, // _fírg, tydl, _viễn_, vála,
+ {{0x7d09a5bb,0x39152457,0x6eef83ba,0x28f925bc}}, // bies, имор, _møbl, вень_,
+ {{0x2d9102a0,0x8c3d802e,0x3ebe80e8,0x7d09a5bd}}, // vuze_, teşt, _hatt_, cies,
+ {{0x8cbf0063,0xead48678,0x3ebea5be,0xf8bf25bf}}, // _लोगो, _воль, _katt_, rdé_,
+ {{0x8c3d802e,0x43839ddd,0x613600f7,0xef1f07c0}}, // reşt, _الوق, rála, mrük_,
+ {{0x613625c0,0x8c3d802e,0x6d5802be,0x4a5b83c8}}, // máln, seşt, _éval, _אדוו,
+ {{0x69c700f1,0x628a0511,0xdb1981ec,0xa99b81c6}}, // _rrje, ófon, _erwä, _נבחר,
+ {{0xe8df8104,0x3a750087,0x1bd40081,0xa3e48075}}, // [14f0] _hiện_, илар, ботя, नहि_,
+ {{0x6136037d,0xe8df8028,0x7d0986d7,0x3ebe8b64}}, // náln, _kiện_, zies, _natt_,
+ {{0x38600125,0xc3338051,0xa2c9016f,0x28c6904f}}, // _yfir_, צוע_, _होण्, _रोहि,
+ {{0x290b04ef,0x7cf58019,0x80d80beb,0x2fc901b0}}, // nica_, _márc, याये, _krag_,
+ {{0x394d837f,0xdb0f05e4,0x7d09a5c1,0x613603b0}}, // _vies_, _escá, vies, káln,
+ {{0x236902ee,0x7d09a5c2,0x2cbd81de,0xb4db0229}}, // mpaj_, wies, _sawd_, _bhàg,
+ {{0x7d09a5c3,0x1dc58697,0x45199bba,0x6d4f10e1}}, // ties, विधत, кция_, _kica,
+ {{0x2bc912ee,0xa2c9016f,0x6d570079,0x7cf81520}}, // रिपा, _होत्, _muxa, _vírg,
+ {{0x6e959ad0,0x3ebe81b9,0x3ced9b3b,0x2fc901c0}}, // _визу, _fatt_, rkev_, _nrag_,
+ {{0x6d4f01c5,0x7d09a5c4,0x7cf5988b,0x4255824b}}, // _lica, sies, _bárc, штит,
+ {{0x290b1f65,0x7cf80065,0x6b8d08b3,0x7cf585e4}}, // fica_, _híre, hrag, _cárc,
+ {{0xe8df8028,0x61fa0006,0x6fdc80ab,0xe0461bc1}}, // _diện_, _ütle, _মেয়ে, инни,
+ {{0xf36792b2,0x08360678,0x61360c83,0x69dc8257}}, // _стен, рхня, báln, ører,
+ {{0xfc3f046d,0x7d0600ee,0x7cf801a8,0x2fc925c5}}, // _abío_, _smks, _míre, _drag_,
+ {{0x7ae38307,0x6d4f1258,0x649a25c6,0x2d9e00e7}}, // _iont, _bica, ктар_, îte_,
+ {{0x6b8d25c7,0xb4e3816f,0xa2cd816f,0x05550012}}, // frag, _नको_, ताक्, стря,
+ {{0x6d4f25c8,0xdbd98187,0x61360019,0x42561dc7}}, // [1500] _dica, _açúc, lálo, атет,
+ {{0x8ccc8063,0x68e40406,0x6d57002a,0xff7b00be}}, // _दोनो, _hoid, _fuxa, _שטימ,
+ {{0x6d4f00a9,0x613614b5,0x442f8084,0x3ebea0db}}, // _fica, nálo, _lyg_, _ratt_,
+ {{0x7ae3a5c9,0xdbdc003e,0x3ead0024,0x3ebe83e5}}, // _lont, lšíc, đete_, _satt_,
+ {{0x3ebe822b,0xc7a3964f,0x3869a5ca,0x684a0196}}, // _patt_, жичк, ñara_, būdž,
+ {{0x7ae3a5cb,0x7cf80013,0x290b002e,0xdfcf80f7}}, // _nont, _díre, zica_, بيه_,
+ {{0x442f808e,0x6ef48a2a,0x7cf80174,0x6d4f09ae}}, // _ayg_, _càba, _tírd, _yica,
+ {{0x290b062f,0x7ae3861f,0x3ea0801b,0x628303f2}}, // xica_, _aont, řit_, ónov,
+ {{0x2bc91a46,0x6136003e,0x60c2a5cc,0x3ebe80e8}}, // रिया, táln, mdom, _tatt_,
+ {{0xdbdc026f,0x60c28110,0x395825cd,0x68e40219}}, // jšíc, ldom, _kurs_, _aoid,
+ {{0x60c089ab,0x613625ce,0x7ae3a5cf,0x7e7700b9}}, // _kamm, ráln, _dont, _rexp,
+ {{0xb35689d7,0x03a68767,0x68e404c3,0x60c0a5d0}}, // _پیدا_, _видо, _coid, _jamm,
+ {{0x6d4f1155,0xe8010a43,0x7cf10b40,0x68e40187}}, // _rica, _लेखा_, _dårl, _doid,
+ {{0xe8df8028,0x6d4f25d1,0x6b8d25d2,0x6d57241f}}, // _tiện_, _sica, vrag, _puxa,
+ {{0xe5c40af2,0x5bb68ebf,0x13b699e8,0xab5b25d3}}, // осто, _अनुव, _अनुभ, _trük,
+ {{0x2fc68025,0x60c0a5d4,0x987515d1,0xb4c10074}}, // kvog_, _namm, _улиц, ंये_,
+ {{0x56950323,0xa2cb8072,0x7cf58061,0xdd2f128a}}, // [1510] _лапт, _थोड्, _jára, _těžb,
+ {{0x6b8d25d5,0x60c08df6,0xf9c71fb4,0x6d4f25d6}}, // rrag, _aamm, ищен, _wica,
+ {{0x395825d7,0x6d4f25d8,0x60c08c2e,0xb4db25d9}}, // _curs_, _tica, _bamm, _diàr,
+ {{0x6b82803a,0x60c2904a,0x60c0a5da,0x395825db}}, // _ovog, gdom, _camm, _durs_,
+ {{0x7d0d009a,0x7cf58a21,0x23668282,0x60c0888b}}, // mias, _nára, _ntoj_, _damm,
+ {{0xaf0480e8,0x395825dc,0x442fa5dd,0x60c28035}}, // опіл, _furs_, _ryg_, adom,
+ {{0x7ae3a5de,0x6b8281df,0x29090009,0x645a0087}}, // _ront, _avog, _omaa_, _ştii,
+ {{0x7d0d0048,0x61360061,0x201e0087,0xeb998991}}, // nias, gáll, ştii_, гии_,
+ {{0x7ae3a5df,0x69ca831d,0xb4db0706,0x57f583a7}}, // _pont, _arfe, _ghàe, _упат,
+ {{0x81e280c8,0x7d0d25e0,0x61360510,0x6b828289}}, // _নেই_, hias, tálo, _dvog,
+ {{0x68e425e1,0x7e7c007b,0x7ae3a5e2,0x60c0a5e3}}, // _poid, varp, _vont, _yamm,
+ {{0x5f0503bb,0x7ae38288,0x26d301df,0x673e00f1}}, // होस्_, _wont, _anxo_, _shpj,
+ {{0x68e40009,0x7e7c25e4,0x6b65946c,0x69ca874c}}, // _void, tarp, _укла, _erfe,
+ {{0x7afaa5e5,0x60c2a5e6,0x61fb09ab,0x613d25e7}}, // chtt, zdom, _ƙull, véle,
+ {{0xf8bf040e,0x99858624,0xa2db25e8,0x61462118}}, // glés_, _البو, नान्, _лека,
+ {{0x6ab78a74,0x3d08016f,0x2b59002a,0x7e7c039c}}, // _असुर, _सगळे_, _busc_, sarp,
+ {{0x6d4425e9,0x60c08e51,0x7cfc8065,0xdbdc026f}}, // [1520] rmia, _ramm, _férf, pšíc,
+ {{0x60c08f06,0x3f820052,0x6d4425ea,0x212d8077}}, // _samm, msku_, smia, _akeh_,
+ {{0x7cf807a3,0x7d0d25eb,0xc879880a,0x394001c0}}, // _círc, bias, _beş_, _khis_,
+ {{0x60c29c33,0x03a61630,0x27328087,0x2bbc82f1}}, // udom, _лимо, mâni_, ्टमा,
+ {{0x3f8225ec,0x44240028,0x81e600ab,0xbbb6824c}}, // nsku_, _âm_, _বেশ_, _अनेक,
+ {{0xb8ef901b,0x63a425ed,0x3ce0011a,0xe3e88019}}, // _वो_, mtin, ljiv_, یکشن_,
+ {{0x7bcba5ee,0xa3c2000c,0x6b82a5ef,0x60c088e5}}, // _argu, ्मक_, _svog, _tamm,
+ {{0xdb1d016d,0x18a38468,0x3ce0011f,0x26c30267}}, // _ersä, _насм, njiv_, zdjo_,
+ {{0x3f820025,0x241881bb,0xda7a9ebd,0xe73a8073}}, // jsku_, ропы_, лян_, _беа_,
+ {{0x3f82005c,0x4ace0327,0x3f5d00ff,0x27f98074}}, // dsku_, _होमव, _rìu_, _üsna_,
+ {{0x63a425f0,0xd2508c48,0x7bcba5f1,0x68fd03ed}}, // htin, بند_, _ergu, ërdo,
+ {{0x3a370051,0x63a425f2,0x6b828042,0x463b80be}}, // מרים_, ktin, _tvog, _געבע,
+ {{0x6d5aa5f3,0x7bcb811f,0x3946a216,0xc1a50778}}, // _huta, _grgu, imos_, _गङ्ग,
+ {{0x6d5aa5f4,0x61360019,0xdb1d0187,0x28cfa539}}, // _kuta, nálj, _essê, _सोनि,
+ {{0xc27b893f,0x6d5a8304,0x3ce6822c,0x2b59026c}}, // _גרוי, _juta, _koov_, _susc_,
+ {{0x6d5aa5f5,0x26c1018e,0xdb060019,0xa4f102d0}}, // _muta, _waho_, _eskü, _dışı_,
+ {{0x28d61a3b,0x63a425f6,0x3ce6a5f7,0x26c125f8}}, // [1530] धारि, gtin, _moov_, _taho_,
+ {{0x629aa5f9,0x7d0d25fa,0xda670013,0x39468876}}, // ngto, sias, صائي, emos_,
+ {{0x7cfc8117,0xb9b50872,0x6d5aa5fb,0x9962026f}}, // _kérd, تماع, _nuta, _píše_,
+ {{0x3835a5fc,0x212b25fd,0xd011803f,0xb4db0362}}, // онер, noch_, ولا_, _dhàc,
+ {{0x6d5aa5fe,0x656880f1,0x63a425ff,0x7d1d01ed}}, // _auta, _atdh, ctin, _ijss,
+ {{0x6d5aa600,0x39838214,0x7cf58333,0x212b2601}}, // _buta, yıs_, _cárn, hoch_,
+ {{0x78ad0025,0x6d5a80a4,0x2d832602,0x212b01ac}}, // _obav, _cuta, nsje_, koch_,
+ {{0x3f8201b4,0x6d5a8314,0x2d8301ed,0x212b01d6}}, // ysku_, _duta, isje_, joch_,
+ {{0x212b2603,0xeab99d7b,0x290fa604,0x7529a605}}, // doch_, айп_, miga_, roez,
+ {{0x290f9f6a,0xf6f5817e,0xdb1d016d,0x78ad1ea2}}, // liga_, _بزرگ, _ursä, _abav,
+ {{0xd9462606,0x6d5aa607,0xf7488013,0x7ae70669}}, // жени, _guta, _اللي_, _mojt,
+ {{0x63a42608,0x3f82003a,0x290fa609,0xa4f7803d}}, // ytin, tsku_, niga_, _دکتر_,
+ {{0x3f8403cb,0xe73a0991,0x7cf58019,0xbbeb803f}}, // ému_, ием_, _háro, _حرام_,
+ {{0x7f5b82a3,0x3f82260a,0x290fa60b,0x7cf58019}}, // _muuq, rsku_, higa_, _káro,
+ {{0x3940073a,0xdd920277,0x7bc9a60c,0x290fa60d}}, // _this_, ظور_, lveu, kiga_,
+ {{0x63a4260e,0x3f820067,0x4c6a0b87,0x212b260f}}, // ttin, psku_, риан_, coch_,
+ {{0x290fa610,0x63a42611,0x6ef00074,0x130a0110}}, // [1540] diga_, utin, _läbi, анай_,
+ {{0x63a42612,0x2d91009a,0xdcfc00eb,0x78ad2613}}, // rtin, brze_, turē, _zbav,
+ {{0x7cf5803e,0x5bb6800d,0x60c42614,0xfce614d6}}, // _náro, _अन्व, _kaim, _доно,
+ {{0x6d5aa615,0x63bb8870,0x63a42616,0xeb9a0c9d}}, // _ruta, kwun, ptin, _ким_,
+ {{0x3946a617,0x60c42618,0x6d5aa619,0xdb0406a8}}, // rmos_, _maim, _suta, ntië,
+ {{0x39469313,0x6d5aa61a,0x2480261b,0x60c4261c}}, // smos_, _puta, laim_, _laim,
+ {{0x2d98261d,0x290fa61e,0x6b84261f,0x7cfc83a8}}, // nure_, biga_, lsig, _lére,
+ {{0x443d82af,0x3eb801ec,0x6d5ca620,0x290fa621}}, // _bzw_, hert_, _éram, ciga_,
+ {{0x3eb82622,0x6b842623,0xa686838d,0x14a68d72}}, // kert_, nsig, _млад, क्षण,
+ {{0x6d5aa624,0x61360019,0x2d982625,0x212b01ec}}, // _tuta, lálh, kure_, woch_,
+ {{0xa2db1a3b,0xd62a8009,0x7cfca626,0x3eb82627}}, // नात्, _тоже_, _pérd, dert_,
+ {{0x60c4051e,0x613d007b,0x61360019,0x69c88106}}, // _caim, féla, nálh, rvde,
+ {{0xd83b01e5,0x60c42628,0x6b840042,0x7cf585b9}}, // рэн_, _daim, jsig, _záro,
+ {{0x6b842629,0x290f8f8e,0x2d83090d,0x3eb801e3}}, // dsig, ziga_, tsje_, gert_,
+ {{0x290f8ec8,0x6d42801c,0x7ae7262a,0x2d98262b}}, // yiga_, _khoa, _rojt, gure_,
+ {{0x24f80fe6,0x245802c7,0x7aea80eb,0x290fa62c}}, // онсы_, жать_, žotā, xiga_,
+ {{0x20020063,0x7cfc80e7,0x69de262d,0x290fa62e}}, // [1550] czki_, _gére, expe, viga_,
+ {{0x3eb807f1,0xdb040036,0x2d980a03,0x28ac06a7}}, // cert_, ntiè, bure_, ट्ठि,
+ {{0x7ae7262f,0x80b000c8,0x6b8418dc,0x6d352630}}, // _vojt, য়ার্, asig, _неоф,
+ {{0x52b7a631,0x02b783ca,0x6b840079,0x7cf8002a}}, // _अस्स, _अस्न, bsig, _tíra,
+ {{0x290fa632,0x3f992633,0x69ce2634,0x6b840079}}, // riga_, nusu_, _erbe, csig,
+ {{0x290fa635,0x6d429fa4,0x7cf5a636,0xed5986c8}}, // siga_, _ahoa, _páro, бок_,
+ {{0x3f9900f6,0x290f822e,0x2bc90f97,0x6d428362}}, // husu_, piga_, रिवा, _bhoa,
+ {{0x7cf58065,0xd5ae80f7,0x6136016b,0x7bcf01a1}}, // _váro, افي_, rálk, _krcu,
+ {{0x2d982637,0x2d98816d,0x3ec48009,0x48fa80be}}, // zure_, _åren_, östä_, _פּלא,
+ {{0x61360ede,0x60c42638,0x7bc980e7,0x613d2639}}, // máli, _saim, rveu, véla,
+ {{0x3eb8263a,0x7c2280ee,0x60c4263b,0x7bc9826c}}, // vert_, _exor, _paim, sveu,
+ {{0x3ea1263c,0x3eb802af,0x7cfc91b9,0x6b84263d}}, // _acht_, wert_, _pére, ysig,
+ {{0x26c5835a,0xe28ea63e,0x68e99989,0x3eb8263f}}, // _kalo_, _па_, _moed, tert_,
+ {{0xb8f305e8,0x2d982640,0x26c58a6e,0x60c42641}}, // _हो_, ture_, _jalo_, _waim,
+ {{0x26c58b5d,0x6281a642,0x60c42643,0x57b8809a}}, // _malo_, malo, _taim, _इन्ह,
+ {{0x3ea12644,0xab66076a,0x7d1ba645,0x3eb8035f}}, // _echt_, звал, nnus, sert_,
+ {{0x7cfc8c52,0x24800c5e,0x2d9822c5,0x6d498c41}}, // [1560] _hérc, raim_, sure_, imea,
+ {{0x2d980098,0x6b842646,0x26c5a647,0x6d4981a8}}, // pure_, rsig, _nalo_, hmea,
+ {{0x68fba648,0x68e9831e,0x14bd824c,0xdb040144}}, // _blud, _boed, ्याण, ntié,
+ {{0x7cfc862f,0x7cf58019,0x7e7e2649,0x00e68009}}, // _mérc, _bárm, _hepp, яжен,
+ {{0x7e7e007b,0x26c5a64a,0x68e281b9,0x68e98114}}, // _kepp, _balo_, rjod, _doed,
+ {{0x6d5e264b,0x62818503,0x6daa8201,0xf1db89c2}}, // _kupa, jalo, _fəal, यमान,
+ {{0x4343264c,0x6281a64d,0xbb43264e,0x69da00e1}}, // _черв, dalo, _черк, _štef,
+ {{0x68e9a64f,0x7e7e2650,0x7cf59984,0x7d1ba651}}, // _goed, _lepp, _fárm, gnus,
+ {{0x6d5e2652,0x26c5a653,0x613612ca,0x38668118}}, // _lupa, _falo_, cáli, mcor_,
+ {{0x6281a654,0xe3af8624,0x26c5a655,0x6d42a656}}, // galo, اري_, _galo_, _whoa,
+ {{0xd48fa657,0x386d0876,0x672e0353,0x3f86a363}}, // _пр_, ñero_, dobj, lsou_,
+ {{0x64a68048,0x26c593e0,0x3f992658,0xdca6a659}}, // _хада, _zalo_, tusu_, _хади,
+ {{0x6d5e265a,0xddc8826c,0x7e7e01e8,0x3f86a41f}}, // _aupa, _jedž, _bepp, nsou_,
+ {{0xb4db265b,0xddc881e2,0x628a813c,0x6d5e0e60}}, // _chàn, _medž, _udfo, _bupa,
+ {{0x28dd0663,0xa2a1816f,0x3f99265c,0x7e7e1ff5}}, // यादि, _खात्, susu_, _depp,
+ {{0x2eb9823c,0x645a0087,0x291ca65d,0x5d542255}}, // _इस्त, _ştir, éval_, екст,
+ {{0x68e98355,0x201e07d9,0xddc888ae,0x3f86801b}}, // [1570] _roed, ştir_, _nedž, jsou_,
+ {{0x68fb80eb,0x7cf80019,0x79850234,0x7bcd8084}}, // _slud, _hírl, tshw, _šauk,
+ {{0x68fba65e,0xaaae8bc2,0x68e9a65f,0xf1c201a9}}, // _plud, ज्यक, _poed, jušā_,
+ {{0x26c580a4,0xf9900bbe,0x61362660,0x7e7e2661}}, // _salo_, ابل_, táli, _zepp,
+ {{0x2b9319f4,0x68e985f8,0x7e7e04e6,0x37ab938c}}, // _سیاس, _voed, _yepp, стен_,
+ {{0xe8da00a5,0x2bb68105,0xddc88267,0x68e98cfa}}, // _बच्च, _अनजा, _dedž, _woed,
+ {{0x68e9a26f,0x7cf80144,0x7bc28176,0x27328162}}, // _toed, _oírl, _asou, vânt_,
+ {{0xddc8826c,0xed568098,0x69da0088,0x68fb82d0}}, // _fedž, мощ_, _šteg, _ulud,
+ {{0x6d4984bc,0x26c5a662,0xa2a18aed,0xddc8a663}}, // rmea, _talo_, _खाद्, _gedž,
+ {{0xff5380f7,0x38692664,0x3cff8901,0x6d49a12b}}, // _أخر_, _afar_, shuv_, smea,
+ {{0x387f800b,0x7bc2a665,0x7aeaa666,0x7cee851e}}, // _keur_, _esou, _doft, _cùrs,
+ {{0x6281a667,0x6d5e2668,0xc689007c,0xdb040020}}, // salo, _rupa, _כא_, rtié,
+ {{0x6d5e2669,0x387fa66a,0xfaa59612,0x69dc8338}}, // _supa, _meur_, _жало, ärer,
+ {{0x387f83d3,0x3cfd81c0,0x2bbc801b,0x6ef48362}}, // _leur_, _hlwv_, ्टरा, _càbh,
+ {{0x63a9a66b,0x64a6266c,0x7cfca639,0xe7371505}}, // lten, мава, _héra, фер_,
+ {{0x249f8125,0x2056266d,0x7cf8266e,0x6eef8366}}, // ngum_, _отпр, _víro, _købs,
+ {{0xb4db0142,0xe8df8104,0xb4be009a,0x7e7e266f}}, // [1580] _thàn, _chọn_, _इसी_, _tepp,
+ {{0x6138a670,0xb0c68aad,0x2d878aa2,0x63a9a671}}, // míli, _रोजग, ksne_, iten,
+ {{0xddd882a5,0x63a9a672,0x80a5103e,0x2d878c18}}, // kavš, hten, _कामे, jsne_,
+ {{0x7cf5801b,0x63a9a673,0x69c381a1,0x0cd88264}}, // _dárk, kten, _msne, _তত্ত,
+ {{0x387fa674,0xddd882a5,0x6ef48722,0x3866a675}}, // _deur_, davš, _hàbi, rcor_,
+ {{0x386687d0,0x6136007b,0xbd6a94ed,0x6b9ba676}}, // scor_, málu, орие_, duug,
+ {{0x63a9802e,0x94188201,0xf9878117,0xceb30039}}, // eten, ərə_, _جب_, דיו_,
+ {{0x7aea96f7,0x387fa677,0x67211809,0x2ca02678}}, // _soft, _geur_, člji, ngid_,
+ {{0x63a9a679,0x7aea8087,0x7cfc8036,0x290201b4}}, // gten, _poft, _céra, dhka_,
+ {{0x7aea80f1,0x78a403a8,0x69c80118,0x7cfc8036}}, // _qoft, _aciv, _ádes, _déra,
+ {{0xb4db0362,0x611c82d4,0x612802d0,0xdee38081}}, // _bhàl, včlj, kıld, _шофи,
+ {{0x61e3016d,0x63a9a67a,0xb4db0362,0x6cd3a0bb}}, // änli, bten, _chàl, _لقما,
+ {{0x628e267b,0x69c3a67c,0x63a9a67d,0x7cfc902d}}, // _odbo, _esne, cten, _géra,
+ {{0xaff980be,0x4426a67e,0xa3d2175d,0x2fd2267f}}, // פּרי, _ixo_, विन_, _dryg_,
+ {{0x6d46008e,0x9e341bcc,0xe93b07c3,0xb4db0362}}, // _bhka, верч, _وسعت_, _fhàl,
+ {{0x3ea98668,0x2b582680,0x628e0114,0x26da06c4}}, // đati_, _airc_, _adbo, _enpo_,
+ {{0x613896dc,0x7cfc8118,0x2b5800c3,0xa87c01c6}}, // [1590] cíli, _xéra, _birc_, _ואחר,
+ {{0x2d8506a5,0x7bcd156e,0x28dd1869,0x31670118}}, // áles_, rvau, यावि, ínzo_,
+ {{0x63a99124,0x387f80e7,0x2d9ca681,0x68ed011b}}, // zten, _peur_, juve_, _koad,
+ {{0x78bd2682,0x63a9a445,0xf77180a0,0x09b100ab}}, // nesv, yten, هات_, ছিলা,
+ {{0x6d4d2683,0x2b580118,0x645d0420,0x68ed033e}}, // mmaa, _firc_, _igsi, _moad,
+ {{0xd246a684,0x6d4d2685,0x68ed2686,0x61360019}}, // _من_, lmaa, _load, lált,
+ {{0x62852687,0x6138a688,0xddd8811f,0x442682df}}, // maho, zíli, tavš, _axo_,
+ {{0xd658004c,0x62852689,0x6d4d06d9,0x61360019}}, // ויות_, laho, nmaa, nált,
+ {{0x3834a68a,0x60dba68b,0x7522826c,0x249f9ab2}}, // _антр, _inum, _djoz, rgum_,
+ {{0x6285049f,0xe3c88028,0x7cf5a68c,0x6442826c}}, // naho, _dự_, _mári, _dzoi,
+ {{0x4439268d,0xc332010f,0x442690e4,0x7cf581a8}}, // _lys_, רוי_, _exo_, _lári,
+ {{0xcea9004c,0xb4be101c,0x7cfca68e,0x68ed040d}}, // _לי_, _इसे_, _hérn, _coad,
+ {{0xb8e80063,0x68ed1d3a,0x6285268f,0x6d59820d}}, // _उस_, _doad, kaho, _iiwa,
+ {{0x6d46020f,0x6561a690,0x29022691,0x6d59a692}}, // _shka, _kulh, shka_, _hiwa,
+ {{0x65618073,0x6d59a693,0x6138a694,0x386b00f7}}, // _julh, _kiwa, síli, éirí_,
+ {{0x656180a9,0x6d4d2695,0x6b962696,0x6d598458}}, // _mulh, gmaa, tryg, _jiwa,
+ {{0x44260003,0x7cf5a697,0x6128080a,0xf1c9801c}}, // [15a0] ço_, _cári, rıld, _hạ_,
+ {{0x60dba698,0x28ac0023,0x6d599110,0xa8570039}}, // _anum, ट्रि, _liwa, _איפה_,
+ {{0x753b826f,0x29002699,0x5ec880ab,0x61280457}}, // kluz, _klia_, লামে, pıld,
+ {{0x6d59a69a,0x6d4d0079,0x60c9816a,0x6d460198}}, // _niwa, cmaa, _caem, _uhka,
+ {{0xf1c9801c,0xe73a9505,0x6285269b,0x087700be}}, // _lạ_, пед_, baho, גענט_,
+ {{0x8f9b00be,0xe5c6a13d,0x6d5984b9,0xe8020d86}}, // פיצי, есио, _aiwa, रैया_,
+ {{0xe3c88104,0xaa430adb,0x2bd28f12,0x6d5998a8}}, // _sự_, _берл, तिभा, _biwa,
+ {{0x2d9815a0,0x6d59a69c,0x60c9808e,0xed5784ae}}, // irre_, _ciwa, _gaem, ној_,
+ {{0x53c904e5,0xd90d8416,0x6b898073,0xe8b5817b}}, // रिएश, _بین_, gseg, lışı,
+ {{0x68ed002a,0xf1c980ff,0x291203f7,0x23d283ca}}, // _soad, _bạ_, _amya_, तिबद,
+ {{0xf8dd10be,0xe8b588c5,0x7d040234,0x13170039}}, // यालय, nışı, khis, תחיל_,
+ {{0xe3c88104,0x78a2827f,0x7d04269d,0x6b89a69e}}, // _tự_, ngov, jhis, bseg,
+ {{0x66f48076,0x04578013,0x7d04269f,0x628526a0}}, // _अवाक_, _كلمة_, dhis, yaho,
+ {{0x3ea50019,0x44391c59,0x6d599f62,0x1ee7003d}}, // ült_, _rys_, _ziwa, روزی_,
+ {{0x7bc600f1,0x6d4d26a1,0x645d0122,0x29000144}}, // _asku, tmaa, _pgsi, _flia_,
+ {{0x7d0426a2,0xf98f9c12,0x23b79094,0x9a3b8039}}, // ghis, _ابو_, _अहमद, _מתוק,
+ {{0x6d4d26a3,0x61361ddd,0x60c9808e,0x54550323}}, // [15b0] rmaa, rált, _raem, тват,
+ {{0x2d8ca6a4,0x6d4d26a5,0x7cf58073,0x60c9a6a6}}, // _ovde_, smaa, _vári, _saem,
+ {{0x7bc600ad,0x3949059c,0x4439079f,0x7d0426a7}}, // _esku, _khas_, _wys_, bhis,
+ {{0x7d0426a8,0x628526a9,0x4439009a,0x38c98019}}, // chis, saho, _tys_, بائی_,
+ {{0x6d5987ba,0x6561a6aa,0x2d8c826c,0x7cfc80e7}}, // _riwa, _sulh, _avde_, _aéro,
+ {{0x63ad04a7,0x6d59a6ab,0xa3c006b7,0x7cfc810c}}, // mtan, _siwa, ूटर_, _béro,
+ {{0x50b111bc,0x60c9a6ac,0x6916016b,0x656180ee}}, // जभाष, _taem, dšen, _qulh,
+ {{0x925826ad,0x7cfca6ae,0x69c726af,0x6b89a6b0}}, // _част_, _déro, _isje, tseg,
+ {{0x6d598010,0x3f8b009a,0x2d8c82d0,0xd46881a8}}, // _viwa, jscu_, _evde_, رحيم_,
+ {{0x466ba6b1,0xdb0081fa,0x6d59a6b2,0xa3d2090f}}, // _храм_, ttmá, _wiwa, वित_,
+ {{0x6b89a6b3,0x290026b4,0x63ad26b5,0x394926b6}}, // sseg, _plia_, htan, _bhas_,
+ {{0x69d51024,0xdb00826f,0x394926b7,0xdddc0088}}, // _mrze, rtmá, _chas_, jarš,
+ {{0x63ad26b8,0xaca301bc,0xc95280be,0x7529017f}}, // jtan, _chọb, ימט_, čezl,
+ {{0x69c7030b,0x78a2803a,0x2cbfa6b9,0x69d50035}}, // _osje, zgov, leud_, _orze,
+ {{0x7d0426a7,0x2d8c26ba,0x386da6bb,0x28aa06ab}}, // this, éde_, _ofer_, _कापि,
+ {{0x2cbf831d,0x6283976f,0x7aee023e,0xbddc0032}}, // neud_, _heno, _sobt, _aṣòf,
+ {{0x63ad26bc,0xa3d2024c,0x628389c4,0x7aee022c}}, // [15c0] gtan, विध_, _keno, _pobt,
+ {{0x7d0426bd,0x13032063,0x6283a6be,0x386da6bf}}, // shis, азум, _jeno, _afer_,
+ {{0x62839a59,0x7d0426c0,0x63ad26c1,0x7cf808f1}}, // _meno, phis, atan, _líri,
+ {{0x63ad26c2,0xe8b5817b,0x69d50035,0x6f0190ba}}, // btan, rışı, _drze, _allc,
+ {{0x10a39593,0x7bc626c3,0x98a38a0e,0x69d526c4}}, // ричн, _usku, риче, _erze,
+ {{0x6283a6c5,0xa3d58c87,0x47338221,0x7cfca6c6}}, // _neno, हिन_, аніс, _péro,
+ {{0x91e3249a,0x78b6026f,0xe29700a9,0x27e0802e}}, // _коре, _obyv, _јас_, ţin_,
+ {{0x7cfca538,0x80ca8bb8,0x3f4f0197,0xaae001d0}}, // _véro, _सोचे, _jżur_, नावक,
+ {{0x62838c77,0xfd0f8077,0x9eaa80a9,0xf8bf00e7}}, // _beno, تجو_, _оваа_, nnée_,
+ {{0x80a50006,0x386026c7,0x7cf801a8,0xe6c581a8}}, // _काहे, _agir_, _díri, _متمي,
+ {{0x69240052,0x26dea6c8,0xe8169094,0x63ad0102}}, // rđen, _into_, _देता_, ztan,
+ {{0x63ad127a,0xd90f026a,0x2906a6c9,0x26cca6ca}}, // ytan, تیب_, nhoa_, _hado_,
+ {{0x26cca6cb,0x691626cc,0x7d02a6cd,0xd83801e5}}, // _kado_, pšen, _ilos, вэр_,
+ {{0x6283a6ce,0xd7f8176e,0xb8fa26cf,0x8c438991}}, // _geno, тую_, _डो_, _тече,
+ {{0x6288a6d0,0x26cca6d1,0xdddc026f,0x8233815b}}, // mado, _mado_, tarš, _پروا,
+ {{0x26cc8510,0x692426d2,0xd498237e,0xf8bf0118}}, // _lado_, nđel, кру_, roés_,
+ {{0x291926d3,0x63ad26d4,0x63a089ca,0xf8bf00e7}}, // [15d0] misa_, utan, numn, gnée_,
+ {{0x69d50610,0x6288a6d5,0x291926d6,0x63ad26d7}}, // _prze, nado, lisa_, rtan,
+ {{0x7d02a6d8,0xc8aa0076,0x7cf5a6d9,0xf8aa0105}}, // _olos, _कामट, _záru, _कामय,
+ {{0x29190418,0x63ad26da,0x63a0a6db,0x60cd26dc}}, // nisa_, ptan, kumn, _jaam,
+ {{0x60cd26dd,0xb8cb91bc,0x26cc822e,0x7cf596a5}}, // _maam, _खा_, _bado_, _márt,
+ {{0x6288a6de,0x69d50063,0xa3d69a3b,0x248926df}}, // jado, _trze, ाटन_, laam_,
+ {{0x26cc90dd,0x291926e0,0x7cf826e1,0x69c705f3}}, // _dado_, kisa_, _síri, _usje,
+ {{0x23ab813c,0x7d028051,0x2906a6e2,0x248903b2}}, // tøj_, _clos, choa_, naam_,
+ {{0x6b8d0003,0x6283a08d,0x291926e3,0x7cfca6e4}}, // nsag, _peno, disa_, _kérj,
+ {{0x6d5d0665,0x61280059,0x26cca6e5,0xa3ab00c2}}, // _lisa, dıla, _gado_, _गमन_,
+ {{0x60cd26e6,0x628380e1,0x29192486,0x0596804e}}, // _baam, _veno, fisa_, _مانگ,
+ {{0x6d5d0668,0x7cf580f7,0x29190365,0xc19a03de}}, // _nisa, _cárt, gisa_, עשרי,
+ {{0x3ce08076,0x776404c3,0xb4db0046,0xe8f72462}}, // कारे_, _quix, _ghài, уля_,
+ {{0x6288a6e7,0xe5a60d9e,0x6d5d0091,0x7d02805c}}, // cado, лиги, _aisa, _zlos,
+ {{0x6d5d1341,0x291911b7,0x60cd26e8,0x78a980d2}}, // _bisa, bisa_, _faam, _ocev,
+ {{0x29191092,0x6d5d26e9,0x6565010c,0x6f188bcf}}, // cisa_, _cisa, _duhh, zivc,
+ {{0x612805c5,0x03261957,0x6d5d19ad,0x93aa045b}}, // [15e0] cıla, гдан, _disa, عارف_,
+ {{0xf8bf02be,0x78a98333,0x656526ea,0x3cda86a7}}, // rnée_, _acev, _fuhh, _खोने_,
+ {{0x6d5d26eb,0x2d950249,0x60cd019e,0xaaa6864a}}, // _fisa, _кряс, _yaam, _खासक,
+ {{0x613d0acf,0x6d5d26ec,0xbfaa8284,0x7cfc8118}}, // téli, _gisa, етке_, _pérm,
+ {{0x6f188052,0x6288a6ed,0xb4c026ee,0x752426ef}}, // tivc, yado, ंजी_, mniz,
+ {{0xa3c101fe,0x6288a6f0,0x29190c6a,0x918607c3}}, // ंबर_, xado, zisa_, _مجرم,
+ {{0xe1e7a6f1,0x612802bb,0x7d03002e,0x6d4b826c}}, // _اس_, zıla, _însc, _bhga,
+ {{0x7cfc9fd1,0x6288a6f2,0x80d408fd,0xb88704c3}}, // _térm, wado, _बोले, _muíñ,
+ {{0x62888510,0x2904883f,0x60cd09f8,0x2919155f}}, // tado, _ilma_, _raam, visa_,
+ {{0x248900f3,0xc87987c0,0x443d8428,0x291926f3}}, // zaam_, _inşa_, _dyw_, wisa_,
+ {{0x63a0811f,0x291926f4,0x69160110,0x2258a6f5}}, // sumn, tisa_, pšel, ärk_,
+ {{0x612803bf,0x6b8d26f6,0x6b9ba6f7,0x60cd057b}}, // tıla, ysag, brug, _qaam,
+ {{0x628883f8,0x291926f8,0x60cd26f9,0x68fd26fa}}, // pado, risa_, _vaam, rksd,
+ {{0x61280182,0x6d5d0744,0x29190234,0x60cd26fb}}, // rıla, _sisa, sisa_, _waam,
+ {{0x60cd26fc,0xbcfb00e7,0xb887002a,0x69da26fd}}, // _taam, _liée, _cuíñ, _šten,
+ {{0x612807d9,0x69ca81b9,0x2fc901c0,0x6b8d26fe}}, // pıla, _isfe, _tsag_, tsag,
+ {{0x6d5d26ff,0x5b152700,0x6d4083ff,0x28aa2701}}, // [15f0] _visa, амат, ilma, _काति,
+ {{0x6d5d0854,0xa3d58519,0x2bb78105,0x6b8d2702}}, // _wisa, हित_, _अहसा, rsag,
+ {{0x6b8d114e,0x6d5d2703,0x26c32704,0x06cd80ab}}, // ssag, _tisa, mejo_, লামি,
+ {{0x26c32705,0x60c28358,0xc91380ab,0xe8168c28}}, // lejo_, geom, িক্ত_, _देहा_,
+ {{0xdb042706,0xa2d0016f,0xafe5a707,0x21290299}}, // stiá, _डोक्, ролл, _mjah_,
+ {{0x26c32708,0xa3d206b7,0x26d1026c,0x6b4b02af}}, // nejo_, विर_, ndzo_, füge,
+ {{0x6ead000d,0xba740065,0x5a440a8e,0x6b828242}}, // _जानु, _چاہت, _гэта, _awog,
+ {{0x2d67811f,0x6b9ba709,0x2d9e1f3a,0x62950140}}, // _uđem_, trug, šteg_, _hdzo,
+ {{0x69d8a70a,0xf7708077,0x6287270b,0x28d91c7b}}, // _arve, _فال_, _kejo, _बोधि,
+ {{0x26c30db7,0x6b9ba70c,0x21290b99,0x98148a47}}, // jejo_, rrug, _ajah_, _طبقا,
+ {{0x6287270d,0x69d8803b,0x394d8282,0x26c3270e}}, // _mejo, _crve, _khes_, dejo_,
+ {{0x69d8803b,0x6b9b803a,0x69de81ac,0x6287270f}}, // _drve, prug, _špec, _lejo,
+ {{0x95548bca,0x21290267,0x290c0110,0x3cff81c0}}, // _اخلا, _djah_, ėda_, mkuv_,
+ {{0x7cf58065,0xe2999577,0x3ced9de3,0x6287001b}}, // _társ, _рак_, ljev_, _nejo,
+ {{0x2d988cd7,0x2cb90706,0x2d5182d4,0x21a58037}}, // _året_, _bbsd_, _ušes_, _тийм,
+ {{0x25ed8df4,0x88830081,0x94262710,0x7d09a711}}, // _अपनी_, _длъж, амбе, nhes,
+ {{0xf77207bd,0xed572712,0x26c32713,0x03a62714}}, // [1600] _باب_, рот_, bejo_, ризо,
+ {{0x7d1ba715,0x7cf58511,0x752400b9,0x31679500}}, // hius, _márq, sniz, _kunz_,
+ {{0x7d1ba716,0x395f8359,0xa2a700c2,0x41aa0ae7}}, // kius, _bius_, _चार्, твен_,
+ {{0x613d2717,0x53a6217e,0x3cff81c0,0x6f1c0037}}, // bélu, _камб, jkuv_, lirc,
+ {{0x7d09a718,0x3ced85f3,0x395f823e,0x67d49f6e}}, // dhes, djev_, _dius_, солу,
+ {{0x98ab009a,0x62872719,0x63a4271a,0x7cfc8019}}, // mocą_, _gejo, muin, _térk,
+ {{0x63a4271b,0x7d1b8118,0xb23a00f7,0x4420802e}}, // luin, fius, _شكرا_, _ţi_,
+ {{0x6b828247,0x92a6009a,0x7d1b847f,0x395f8037}}, // _pwog, _wyłą, gius, _gius_,
+ {{0x6f1c271c,0x7cf5988b,0xdcef011f,0x6d40808b}}, // kirc, _párr, žeće, rlma,
+ {{0x7cfca71d,0x2d9c81d0,0x6f1c09ab,0x2fc99a1f}}, // _méri, prve_, jirc, çage_,
+ {{0x63a41581,0x7d1ba71e,0x3ced826c,0xf2d900be}}, // huin, bius, bjev_, אַרל,
+ {{0x7d09a71f,0x7d1ba720,0x63a42721,0xb4c503db}}, // ches, cius, kuin, _एसे_,
+ {{0x315780be,0x8c43a30e,0x7cf58144,0x6568a722}}, // ייטן_, _деце, _tárr, _hudh,
+ {{0x63a42723,0x65688303,0x31678037,0x46de2724}}, // duin, _kudh, _funz_, मागह,
+ {{0x26d10063,0x62872725,0x26c32726,0x518693bf}}, // rdzo_, _rejo, rejo_, _тула,
+ {{0x26c32727,0x6568a728,0x3f8f8114,0x7cfc9a90}}, // sejo_, _mudh, ysgu_, _béri,
+ {{0x63a4107f,0x2d5802c7,0x26c32729,0x62870088}}, // [1610] guin, рить_, pejo_, _pejo,
+ {{0x883c0039,0x6e2d8214,0x67389351,0x7cfc80e7}}, // _בתחו, şabi, jovj, _déri,
+ {{0x75d50199,0xb4db051e,0x6738a72a,0x656888f9}}, // _ايجا, _bhàt, dovj, _nudh,
+ {{0x7cfca72b,0xa3d6873c,0xb12580ab,0xb4db0362}}, // _féri, ाटा_, বত্ব_, _chàt,
+ {{0x63a40087,0x395f823e,0xb0aa0035,0x3cff822c}}, // cuin, _vius_, _कासग, vkuv_,
+ {{0x6568a72c,0x67388639,0x16a9a349,0xa3d581ab}}, // _budh, govj, увки_, हिस_,
+ {{0x394da72d,0xa194272e,0xdcfb1a3b,0x21390168}}, // _thes_, _марч, _एकाध_, kosh_,
+ {{0x2ca9272f,0x28cf9513,0xd5d82730,0x51840cc1}}, // ngad_, _सोचि, डितज, _фура,
+ {{0x291d80f6,0x3ced8db7,0x7d1ba731,0x7d09a732}}, // miwa_, rjev_, rius, rhes,
+ {{0x291d80f6,0xddde0f20,0xfe708b76,0x7305943d}}, // liwa_, _lepš, _قدم_, споз,
+ {{0xdb0403a7,0x7d09a733,0x657701e9,0x6568a734}}, // stiç, phes, _ntxh, _gudh,
+ {{0x291d804f,0x213912ec,0x05090264,0x14ca9ab3}}, // niwa_, gosh_, রচুর_, _مهمی_,
+ {{0xeb99a306,0xdb0b806a,0xc50c80be,0x9fd60264}}, // _сил_, _opgø, _בלאָ, সঙ্গ,
+ {{0xa3c10935,0x65689d08,0x291da735,0x644ba736}}, // ंबई_, _yudh, hiwa_, _izgi,
+ {{0x7ae3a737,0x291d80f6,0x7cfca738,0x6b5004b8}}, // _innt, kiwa_, _séri, lägg,
+ {{0xb466044f,0x7cfca739,0x2ca9016d,0x291d8010}}, // скал, _péri, ggad_, jiwa_,
+ {{0x09b201fe,0x612805c5,0x68e41dde,0x291d8010}}, // [1620] ीब्य, nılm, _inid, diwa_,
+ {{0x63a4125b,0x7cfc82be,0xb4db0229,0x2ca902c4}}, // ruin, _véri, _bhàs, agad_,
+ {{0x63a4273a,0x28dd016f,0x8db600e8,0x291d804f}}, // suin, याचि, _успі, fiwa_,
+ {{0x98a68cdf,0x612802d0,0x291da73b,0xb4db0a2a}}, // сиде, kılm, giwa_, _dhàs,
+ {{0x6568a73c,0x29020009,0x31b18019,0xc7a38992}}, // _sudh, ikka_, ház_, зичк,
+ {{0x61280085,0x6d44273d,0x4dbe80ab,0xa3bd00c2}}, // dılm, llia, ্মসূ, _आहत_,
+ {{0x38698052,0x6d44031d,0x6738a73e,0x291da73f}}, // žara_, olia, rovj, biwa_,
+ {{0x539894ed,0xbb748992,0x69ce01b4,0x7ae3851e}}, // авия_, огиј, _isbe, _annt,
+ {{0x2d8502ba,0x673882ce,0x656890e1,0x442f83a8}}, // ález_, povj, _wudh, _cxg_,
+ {{0x6d442362,0x78ad2740,0x68e42741,0x37be0264}}, // hlia, _scav, _anid, ইটার,
+ {{0xe3af9fbe,0xdd928077,0x6d440110,0x81b600ab}}, // وری_, _روش_, klia, জিক_,
+ {{0xd84304e8,0x00000000,0x00000000,0x00000000}}, // _ničí_, --, --, --,
+ {{0x2d910063,0xb0aa000f,0x28aa097d,0x6d440114}}, // wsze_, _कारग, _कारि, dlia,
+ {{0x291da742,0xa3d5a743,0x69dc2744,0x98be00eb}}, // ziwa_, हिर_, _orre, ētā_,
+ {{0x7af50e04,0x8cda016f,0x291da745,0x2d852746}}, // _rozt, _पोहो, yiwa_, èle_,
+ {{0x6d440698,0x2d912422,0x65628326,0x183a01bc}}, // glia, rsze_, _jioh, ọmụm,
+ {{0x290902a3,0x69dc2747,0x36d52748,0x7af50cc7}}, // [1630] _ilaa_, _arre, _допр, _pozt,
+ {{0x32672134,0x2d91009a,0xaa672749,0x6d44274a}}, // став, psze_, стак, alia,
+ {{0x291d8010,0x612803bf,0x7d030162,0xdb098174}}, // tiwa_, zılm, _înso, nteá,
+ {{0xdb0980f7,0x6128080a,0x628a8061,0x6ca7274b}}, // iteá, yılm, _lefo, _урож,
+ {{0x69dc274c,0x629898a6,0x291da74d,0xcf9b84ae}}, // _erre, _odvo, riwa_, ује_,
+ {{0x7d0d0065,0x291da74e,0xab5b0019,0xe81c0074}}, // lhas, siwa_, _csüt, _पइसा_,
+ {{0x24e98698,0xace9a74f,0xceb404de,0x28aa0ebf}}, // имки_, имка_, פיק_, _कालि,
+ {{0x28d90b75,0x61280457,0x2fcd81e9,0x6abc01a1}}, // _बोलि, tılm, _tseg_, _zbrf,
+ {{0x628a9c93,0x68e42750,0xa49b0176,0x130985a5}}, // _befo, _rnid, _alòk, иний_,
+ {{0x61280182,0x69060118,0x4225a42e,0x68f60850}}, // rılm, _cóen, ждов, _soyd,
+ {{0x90c60364,0x7d0d2751,0x60c600eb,0x6d440114}}, // обне, khas, tekm, ylia,
+ {{0xf9920051,0x7bcf2752,0x612813da,0x69da2753}}, // ורט_, _oscu, pılm, _štek,
+ {{0x3c47803f,0x7529802e,0x6b500884,0x7d0d2754}}, // _اضاف, mnez, läge, dhas,
+ {{0x2bcf901c,0x26c78010,0xa3d689f2,0x628a8dfb}}, // _तैया, neno_, ाटर_, _gefo,
+ {{0x6281a755,0x7bcf002e,0x9663120c,0x2ee580b9}}, // mblo, _ascu, дкре, _mnlf_,
+ {{0x68e40510,0xd46715d1,0x7d0d2756,0x6281a757}}, // _unid, жите_, ghas, lblo,
+ {{0x2ee58079,0x8c430e8e,0x24802758,0xdb00804a}}, // [1640] _onlf_, дете, rbim_, ttmø,
+ {{0x681c0029,0xc8aa0327,0xb0aa18a9,0xe29a23d7}}, // rādī, _काँट, _काँग, раж_,
+ {{0x673c2759,0x506682df,0x7d0d275a,0x26c78ef1}}, // morj, отла, bhas, deno_,
+ {{0x7d0d0870,0xdb0400f7,0x31ba80be,0x7cfc9ab3}}, // chas, ntiú, יזענ, _yéru,
+ {{0xa3ce09a3,0x765f8201,0x1eca81a1,0x7529a75b}}, // _शनि_, əyyə, илди_, dnez,
+ {{0x7d040009,0x26c780e5,0xdd9a97c8,0xa49b026b}}, // lkis, geno_, аши_, _alòh,
+ {{0xb0aa275c,0x69dc275d,0xd9a8800f,0x4c94a3e7}}, // _कांग, _urre, _कमेट, пийс,
+ {{0x61e10065,0x3ea0a75e,0x7d04275f,0x394001c0}}, // _álla, şit_, nkis, _nkis_,
+ {{0xd0078ae7,0x63b62760,0x2f0b806a,0x3946a761}}, // _дете_, ntyn, _søge_, mlos_,
+ {{0x3946840e,0x26c78828,0x2909004f,0x39400084}}, // llos_, ceno_, _slaa_, _akis_,
+ {{0xd48fa762,0x26d32763,0x7d042764,0xdb040216}}, // _ор_, _raxo_, kkis, guié,
+ {{0x394682af,0x26d32765,0x88b10180,0x657a8061}}, // nlos_, _saxo_, ریخچ, _itth,
+ {{0xc86400e8,0xdb0981a8,0xab2a86f9,0x6281a766}}, // дтри, steá, _кода_, bblo,
+ {{0x3ce681c5,0x6298812b,0x39401400,0x7c2da767}}, // _hnov_, _udvo, _ekis_, _žarg,
+ {{0x7d0d2768,0xf27b8051,0x394681e2,0x7cfc8c52}}, // thas, _דרוש, klos_, _fért,
+ {{0xdb0400f7,0x26c79620,0x394681c0,0x80a52769}}, // ctiú, zeno_, jlos_, _काटे,
+ {{0x7d0d276a,0x6b4f806a,0x394689ed,0xdb0d00f7}}, // [1650] rhas, søge, dlos_, staí,
+ {{0x7d0d276b,0x657a8019,0x26c783a8,0x39468020}}, // shas, _otth, xeno_, elos_,
+ {{0xceb20451,0x69240d11,0x69160353,0x7d0401de}}, // _איי_, rđev, ršev, bkis,
+ {{0x6b50016d,0x3946a76c,0x7d04276d,0x55da00be}}, // väge, glos_, ckis, _פֿינ,
+ {{0x657a8748,0x26c7a76e,0x60dc00be,0x66e580ae}}, // _atth, teno_, יקאנ, зола,
+ {{0x6281813c,0x644f01a9,0x39468118,0xf8bf01d0}}, // xblo, _izci, alos_, dném_,
+ {{0x26c7812b,0x80ac035a,0x39468ae5,0x7bc2a76f}}, // reno_, _झाले, blos_, _apou,
+ {{0x39468c52,0x61360065,0x673c007a,0x6b5001ec}}, // clos_, bály, zorj, räge,
+ {{0x76450009,0x6281a770,0x39520428,0x26c7a771}}, // _lyhy, tblo, _rhys_, peno_,
+ {{0x386913b8,0x13098d15,0x753b928a,0x7cfca772}}, // _agar_, сной_, souz, _sért,
+ {{0x753b811f,0x7d040082,0x7bc2a1bf,0x673c007a}}, // pouz, ykis, _epou, vorj,
+ {{0x2fdf805c,0x69d083eb,0x63b60198,0xbcfb0216}}, // _krug_, धिजी, ytyn, _sién,
+ {{0x673c2773,0xb4d7864a,0x4aac0072,0x69da81c0}}, // torj, ायी_, _चालव, avte,
+ {{0x4e1f85e8,0x3f9e026f,0x7bda8039,0x657a8388}}, // _बधाई_, čtu_, _לקנו, _ytth,
+ {{0x386d0301,0x7d042774,0xdb040036,0x649a0071}}, // žera_, tkis, quié, йтар_,
+ {{0x2571807b,0x63b60009,0x673c2775,0x61fc8338}}, // _mál_, ttyn, sorj, ärle,
+ {{0xf42a01ac,0x2fdf8282,0x7d042776,0xfbdf02df}}, // [1660] _opäť_, _nrug_, rkis, _crê_,
+ {{0x7d042777,0x27e02778,0xa3dc853f,0x60d62779}}, // skis, _irin_, डित_, _haym,
+ {{0x63b6277a,0x9d4624c8,0x60d6277b,0x2c75019d}}, // styn, _неод, _kaym, _ụde_,
+ {{0x2fdf8022,0x63a9a77c,0x63b6277d,0x7ae70168}}, // _brug_, kuen, ptyn, _enjt,
+ {{0x3946a77e,0xe81683a4,0x09a8800c,0x2efa01ec}}, // rlos_, _देखा_, _कम्य, _kopf_,
+ {{0x69c38fb0,0x26ca277f,0xf67a00be,0x290b00eb}}, // _opne, nebo_, זאַמ, ībai_,
+ {{0x629c2780,0x7bc2a781,0x25718118,0x3946a782}}, // _idro, _spou, _cál_, plos_,
+ {{0x27e0077f,0x399b0039,0x2571801b,0x63a995a0}}, // _orin_, _לילד, _dál_, fuen,
+ {{0x63a42783,0x63a9a784,0x628e0587,0x7cfc8125}}, // nrin, guen, _kebo, _sérs,
+ {{0xa3ab8063,0x63a42785,0x628e2786,0x877b83c8}}, // गढ़_, irin, _jebo, _לאמי,
+ {{0x63a42787,0x6446007b,0x26ca026b,0x27e02788}}, // hrin, _lyki, debo_, _arin_,
+ {{0x63a9a789,0x29068bf0,0x27e0278a,0x60d6278b}}, // buen, nkoa_, _brin_, _caym,
+ {{0xd83b01e5,0x63a9960a,0x5f940098,0x7cfc8207}}, // сэн_, cuen, ният, _férr,
+ {{0x628e14f0,0x27e0278c,0xa3e00327,0xf8bf0036}}, // _nebo, _drin_, थिन_, diée_,
+ {{0x27e0278d,0x2d9e003a,0x29068009,0x16042076}}, // _erin_, šten_, kkoa_, रनगर_,
+ {{0xca478013,0x63a4278e,0x629c031d,0xf8bf00e7}}, // _عليه_, frin, _adro, fiée_,
+ {{0x63a4278f,0xa3d58063,0x24580009,0x628e2790}}, // [1670] grin, हिए_, зать_, _bebo,
+ {{0x26ca2791,0x628e1e00,0x629c01e0,0x6d428326}}, // cebo_, _cebo, _cdro, _lkoa,
+ {{0x32072792,0x65662793,0x644600b9,0x163502a9}}, // ány_, _fikh, _eyki, _ценя,
+ {{0x25e0858c,0x6ebf800d,0xf42a01ac,0x5fb103db}}, // किनी_, ल्नु, _späť_, _जमाल,
+ {{0xf8bf0036,0x2571a794,0x61360118,0x32071517}}, // ciée_, _sál_, rálx, šny_,
+ {{0x628e105d,0x290fa795,0x6d42826b,0x670d03eb}}, // _gebo, shga_, _akoa, हसिक_,
+ {{0x69da0db7,0x30a7a796,0x20020035,0x60dd8144}}, // _štev, _нрав, zyki_, _ósmo,
+ {{0x58d49697,0x629c09a4,0x63a98102,0x26d80690}}, // _пост, _zdro, tuen, zdro_,
+ {{0x2be00f97,0x2fdf808e,0x68e9a797,0x19588196}}, // निया, _urug_, _ined, _хаты_,
+ {{0x68fb82d8,0x63a9a798,0x7c26807b,0x68e980e1}}, // _houd, ruen, úkra, _hned,
+ {{0x68fba799,0x80d700ab,0x2bdb820e,0x60d6279a}}, // _koud, ধান্, बिहा, _paym,
+ {{0x27e00c6e,0x26c10267,0x2002009a,0x63a4279b}}, // _prin_, _abho_, tyki_, yrin,
+ {{0x21221083,0x63a9a79c,0x6d5b836e,0xeb8e8934}}, // rikh_, quen, mmua, _ши_,
+ {{0x6d49a79d,0x672394e4,0x2906811e,0x6566005d}}, // llea, ninj, zkoa_, _sikh,
+ {{0xdd1c026f,0x63a4279e,0x644f817f,0xed168019}}, // _vážn, wrin, škić, _رہیں_,
+ {{0x628e279f,0x27e027a0,0x6d4990a2,0x752d27a1}}, // _sebo, _trin_, nlea, gnaz,
+ {{0x6d49a7a2,0xab66096b,0x59d584c5,0xc9530039}}, // [1680] ilea, двал, धिकर, _במה_,
+ {{0x290da7a3,0x63a427a4,0x03a301a1,0xf8bf0036}}, // _alea_, rrin, тифо, riée_,
+ {{0x68fba1bf,0x63a427a5,0x68e9822b,0xd46a06a1}}, // _boud, srin, _bned, _мине_,
+ {{0x6d5b822c,0x628e03fb,0xc7c68af2,0x8fa69182}}, // jmua, _webo, дски, даде,
+ {{0x628e27a6,0x68fba7a7,0x29068bf0,0x4c3601e2}}, // _tebo, _doud, rkoa_, _цэнт,
+ {{0xf8bf0019,0x291f80b9,0x2906811b,0x68e9a7a8}}, // tnék_, _emua_, skoa_, _ened,
+ {{0x626327a9,0x68fb8866,0x7afc02f9,0xdd1d136f}}, // _авра, _foud, _iort, lášs,
+ {{0x25a0a7aa,0x7afc27ab,0x68fba7ac,0xe81f84e5}}, // čil_, _hort, _goud, _मेवा_,
+ {{0x6723a7ad,0x6843a7ae,0x644e8035,0xaf2aa7af}}, // binj, _анта, ębio, _джаз_,
+ {{0x67238052,0x68fb8a0f,0xadba80f7,0xe45703de}}, // cinj, _zoud, _وهذا_, ויפט_,
+ {{0x6d49a7b0,0xf2d30039,0x36068199,0xbcfb010c}}, // blea, דעת_, _تواف, _diéj,
+ {{0x7afc011e,0xcb6a8323,0xb4db0032,0xf78702df}}, // _lort, _даде_, _akàn, íços_,
+ {{0xac978077,0x6d4082d6,0x7afc01b0,0x2d9e27b1}}, // _آنها_, moma, _oort, štel_,
+ {{0x6d409619,0x7afc160c,0xdce280c3,0x75e401bc}}, // loma, _nort, _mioč, _ọzay,
+ {{0xa3d689a3,0xdb0081ca,0x7bc601a9,0x6455866f}}, // ाटक_, cumá, _apku, ęzie,
+ {{0x6d40a7b2,0x50670abe,0x6b500106,0x66f802d0}}, // noma, _отпа, väga, _aşkı,
+ {{0x752d27b3,0xdb0d0073,0x7afc0687,0x3b550012}}, // [1690] rnaz, ntaç, _bort, екар,
+ {{0x6d40a7b4,0x68fba7b5,0x291f8890,0xdb0d0187}}, // homa, _soud, _smua_, itaç,
+ {{0x6d40a7b6,0x68fba7b7,0x7afc27b8,0xda0e80a5}}, // koma, _poud, _dort, ानित_,
+ {{0x2d8301ed,0xa49b0362,0xe7f3866f,0x3835a7b9}}, // mpje_, _clòt, _आपदा_, ннер,
+ {{0x67238025,0x69da003b,0x68fb80e7,0x2575016d}}, // tinj, _štet, _voud, _hål_,
+ {{0x63ad27ba,0x7afc27bb,0xe7338bbe,0x68fb9412}}, // muan, _gort, خصص_, _woud,
+ {{0x67238db7,0x63ad14ff,0xe81fa7bc,0x6d40a7bd}}, // rinj, luan, _मेरा_, foma,
+ {{0x25750082,0x6723a7be,0xdfd597ae,0x67d5a57e}}, // _mål_, sinj, _побы, _побу,
+ {{0x6d49a7bf,0x6d5ba7c0,0x77688118,0x386da7c1}}, // rlea, rmua, _fidx, _iger_,
+ {{0xa3e0090f,0x6d49a7c2,0xcb1200be,0x7afc16bb}}, // थित_, slea, ָלט_, _xort,
+ {{0x6d498e6f,0x7648a7c3,0x7d0f0019,0x6d40a7c4}}, // plea, _gydy, _olcs, boma,
+ {{0x6d40a7c5,0xdb0980a9,0xe8f7176e,0x75d38201}}, // coma, nteú, елю_, _nəzə,
+ {{0xa3d58076,0x316927c6,0xfaa5a3d7,0x7d098c9a}}, // हिओ_, _diaz_, _зало, mkes,
+ {{0x63ad0057,0x2d9e005c,0x7d099323,0x69c705f3}}, // duan, štem_, lkes, _opje,
+ {{0x63bba7c7,0x2f1400f2,0x2d8c27c8,0xed5a0d0e}}, // ltun, _lägg_, ède_, зов_,
+ {{0x7afc27c9,0xc5f280be,0x69a48072,0x1da78035}}, // _sort, נדן_, _किती, _गिनत,
+ {{0x63bb885c,0x63ad27ca,0xfd648a2c,0x03a327cb}}, // [16a0] ntun, guan, _akwụ, лито,
+ {{0x28c10054,0x7afc27cc,0x63bb8759,0x06860110}}, // ष्ठि, _qort, itun, _ягон,
+ {{0x63bba7cd,0x7afc11e6,0x6d40a7ce,0x7d09a7cf}}, // htun, _vort, yoma, kkes,
+ {{0x26da022e,0x7648831d,0x7afc27d0,0x61fca7d1}}, // _hapo_, _rydy, _wort, ørli,
+ {{0xe7e327d2,0x6d4613ed,0x63ad0144,0x7afc27d3}}, // खिया_, _ikka, cuan, _tort,
+ {{0x6b8427d4,0x6d40a7d5,0x386da7d6,0x2d8c80e4}}, // mpig, woma, _eger_, _pwde_,
+ {{0x60c427d7,0x2d9a9be6,0xceb300be,0x63bba7d8}}, // _obim, épe_, טיג_, etun,
+ {{0x63bb90f6,0x75d38085,0xc5f30039,0xd9e386a7}}, // ftun, _xəzə, רדה_, गिनत_,
+ {{0x6d40a7d9,0x63bba7da,0xd4e38009,0xdb040036}}, // roma, gtun, ующи, triè,
+ {{0x2d6e800d,0x6b4f813c,0xdb0d27db,0x26da27dc}}, // _před_, søgn, rtaç, _napo_,
+ {{0x6d4627dd,0xdb0402be,0xdb0d03a7,0x95caa7de}}, // _okka, rriè, staç, _хлеб_,
+ {{0x6d40a7df,0x69b1809a,0xdb0d0187,0xdcdf03db}}, // qoma, _अमरी, ptaç, _पोंछ,
+ {{0xb8d90576,0xa3e4800d,0xa2c106af,0xdb040118}}, // _चा_, _पछि_, र्ड्, ntió,
+ {{0x5453835f,0x6d4627e0,0x26da27e1,0xd04089ab}}, // _світ, _akka, _capo_, _hamɓ,
+ {{0x0dc81506,0x95c8028b,0x394227e2,0x26da026b}}, // нути_, нута_, boks_, _dapo_,
+ {{0x290b0341,0x63ad27e3,0x2b5827e4,0x394200b9}}, // ības_, tuan, _ahrc_, coks_,
+ {{0x8aa70153,0x69c727e5,0xfaa704fa,0x6b84076d}}, // [16b0] еред, _spje, ешен, gpig,
+ {{0x60dd0a64,0x2fc902e8,0x6d460365,0x25750106}}, // ldsm, _ipag_, _ekka, _tål_,
+ {{0xf99307bd,0x799aa7e6,0x63bb9124,0x67270267}}, // ابط_, nstw, ztun, mijj,
+ {{0x60dd23ea,0x21268358,0x2fc902d5,0x2d8127e7}}, // ndsm, gioh_, _kpag_, _ithe_,
+ {{0xb8c98b3b,0x63ad27e8,0x69d5009a,0x389c00be}}, // _गए_, quan, _wsze, ליאנ,
+ {{0x6d4d1e38,0x6da5867c,0x443901e0,0x6727026c}}, // llaa, вика, _ixs_, nijj,
+ {{0x53340425,0xa3b3816f,0x68ed27e9,0x7d09956e}}, // _серт, जून_, _onad, tkes,
+ {{0x63bba7ea,0x68ff02a3,0x386d813c,0x60dd006a}}, // ttun, _noqd, _uger_, jdsm,
+ {{0x63bb8009,0x7d09a7eb,0xd9d9801b,0xa2c11869}}, // utun, rkes, बट्ट, र्थ्,
+ {{0x613d0065,0x7d098100,0x63bb8367,0x68ed27ec}}, // mély, skes, rtun, _anad,
+ {{0x63bba7ed,0x6d4d27ee,0x3f80002e,0x2d81022c}}, // stun, klaa, _stiu_, _nthe_,
+ {{0x60db809f,0xdb2689a7,0x26da27ef,0x2d9e27f0}}, // _jaum, _یونی, _sapo_, štek_,
+ {{0x889a8039,0x26da24d4,0x2f14016d,0x60db804f}}, // וברי, _papo_, _läge_, _maum,
+ {{0x4ad78117,0x4ed51b69,0xdb0402be,0xfaa5a7f1}}, // _ساتھ_, люст, prié, _рако,
+ {{0xceb29a0f,0x63a9a7f2,0x656ba7f3,0xcb1281c6}}, // יים_, lren, _kigh, _דלג_,
+ {{0x6d4d27f4,0x26da004f,0x63a9a7f5,0x656b819d}}, // glaa, _wapo_, oren, _jigh,
+ {{0x26da0110,0x656b8039,0xb46601e5,0x2d8127f6}}, // [16c0] _tapo_, _migh, ткал, _ethe_,
+ {{0x656ba7f7,0x613d0019,0x68ed05f3,0x6d4d27f8}}, // _ligh, dély, _znad, alaa,
+ {{0x2be00076,0x63a9a7f9,0x644b8110,0x60db9655}}, // निहा, hren, _lygi, _baum,
+ {{0x63a982a5,0xfbab1344,0x656ba7fa,0x290027fb}}, // kren, _चमचम, _nigh, _joia_,
+ {{0x7c2da7fc,0x68438a14,0xb4c00beb,0x087700be}}, // _žarn, анца, ूजी_, דענט_,
+ {{0xa3c1009a,0x3cda8592,0x656b832f,0x7f4381e0}}, // ूबर_, _खोजे_, _aigh, gonq,
+ {{0xdb04040e,0x63a9a7fd,0x28dd92c6,0x2be0170c}}, // stió, eren, _नोकि, निवा,
+ {{0x6d59a7fe,0x290027ff,0x60dba800,0x6d442801}}, // _chwa, _noia_, _gaum, noia,
+ {{0xa7668be1,0x63a9906f,0x656ba802,0x6b9b84a7}}, // _акад, gren, _digh, gsug,
+ {{0x6d5983ac,0x68ed001b,0xbcfb0118,0x29120c2e}}, // _ehwa, _snad, _viéi, _alya_,
+ {{0xdb0404c3,0x7c2d807b,0x63a9a803,0x6727026c}}, // ntiñ, _þarn, aren, vijj,
+ {{0x0c26a804,0xe8e00028,0x799a809a,0x94268009}}, // _имен, _trời_, rstw, _имее,
+ {{0x692b000d,0x6727026c,0x60dd0cd7,0x6d441b1f}}, // třeb, tijj, rdsm, doia,
+ {{0xf8bf06a5,0x656b81bc,0x68ed0326,0x2912038a}}, // cién_, _zigh, _wnad, _elya_,
+ {{0x7e280d13,0xdb00816d,0xf62800e8,0x6d4d2805}}, // віта_, rumä, віти_, tlaa,
+ {{0xe7f3816f,0x6727026c,0x69d8a806,0x68ed01f8}}, // _आपला_, sijj, _isve, _unad,
+ {{0x54552807,0xdc550003,0x6d4d2808,0x60dba809}}, // [16d0] уват, увањ, rlaa, _raum,
+ {{0x6d4d0b3c,0xf1b6a769,0xe3b001a8,0x22950481}}, // slaa, _अमान, ترف_, الاس,
+ {{0x6d4d280a,0x26d1280b,0x60dba80c,0x63a99002}}, // plaa, mezo_, _paum, zren,
+ {{0x26d1280d,0xe0df280e,0x3015a80f,0x2600001b}}, // lezo_, ndò_, лдер, रहरी_,
+ {{0xe8d90104,0x656b8051,0xd91c0158,0x251a0039}}, // _chỉ_, _righ, וואל, _נושא,
+ {{0x69d893f6,0xb87b12ca,0x60db804f,0x26d12810}}, // _osve, ndíb, _waum, nezo_,
+ {{0x99678e9f,0x60dba811,0xdb040c52,0xf8bf00e7}}, // _атал, _taum, quiá, nnés_,
+ {{0x7d0d2812,0x26d12813,0x6b9ba814,0x39490282}}, // nkas, hezo_, tsug, _nkas_,
+ {{0x26d12813,0x0b45841c,0x63a9a815,0x69ca81ec}}, // kezo_, лнин, uren, _apfe,
+ {{0xe1f98364,0x63a9a816,0x656ba817,0x25788019}}, // его_, rren, _wigh, _fél_,
+ {{0x7d0d2818,0xa2ca06af,0xaca30032,0x26d10118}}, // kkas, _सॉफ्, _akọb, dezo_,
+ {{0x38698699,0x69a48beb,0x64558085,0x61f80589}}, // žaru_, _किरी, əzin, ävli,
+ {{0x03a58258,0x53a587b6,0x7d0d2819,0x69d88580}}, // _сило, _салб, dkas, _esve,
+ {{0x6295281a,0x26d12169,0x7529a81b,0xfd65027d}}, // _nezo, gezo_, liez, _truồ,
+ {{0xdca60cec,0x64a608d5,0xc0e6281c,0xdd1d01d0}}, // лави, лава, _бойк, lářs,
+ {{0x7d0d043b,0xa3e0281d,0x7e7c0216,0x7529a81e}}, // gkas, थिल_, scrp, niez,
+ {{0x0aea1bcc,0x6295281f,0x6eac009a,0x6d442820}}, // [16e0] ндай_, _bezo, _जयपु, roia,
+ {{0x7d0d0009,0xb4631a1a,0xdb00a821,0x6d442822}}, // akas, скул, stmö, soia,
+ {{0x62952823,0xd24f00f7,0x88868098,0x9abc84b7}}, // _dezo, كنك_, глеж, fiċj,
+ {{0xdb0401df,0xc0968290,0x25aa804a,0x31bc0118}}, // rtiñ, اجات, _åbli_, ríz_,
+ {{0xa3d28610,0x316da824,0xdb042825,0x6ac900ab}}, // _हैं_, _diez_, stiñ, রয়ো,
+ {{0x62952826,0x6f01a827,0xaa4602a9,0xa85701c6}}, // _gezo, _colc, _бегл, ניסה_,
+ {{0x645d8125,0x6f018098,0xceb30039,0xe5c69821}}, // ýsin, _dolc, ציה_, усло,
+ {{0xd1328b76,0x386d2828,0x78a4004f,0x75298cdb}}, // _امر_, žeri_, _ndiv, giez,
+ {{0x28dd800f,0x5b14a503,0x6f01a829,0xdb040187}}, // _नोटि, имит, _folc, guiç,
+ {{0x3946a82a,0x78a4282b,0x2578a82c,0x9bbb00be}}, // loos_, _adiv, _tél_, עציפ,
+ {{0xaac623a7,0x80de00c8,0xd2508077,0x7d0d282d}}, // र्यक, যান্, تند_, ykas,
+ {{0xdb0403a7,0x3f84808e,0xaca301bc,0x26de8a03}}, // buiç, _ktmu_, _kwụd, _hato_,
+ {{0xe8d90104,0xa3b3823c,0x26dea82e,0xed579baa}}, // _giờ_, जूद_, _kato_, _соц_,
+ {{0x7d0282a3,0x8c1b8039,0x26de82df,0xaca3019d}}, // _hoos, _אודי, _jato_, _mwụd,
+ {{0x6295282f,0xc27b893f,0x7d0289f8,0x26d12830}}, // _rezo, _ארוי, _koos, rezo_,
+ {{0x99838307,0x629aa831,0xf1a9809a,0x26dea832}}, // _اليو, lato, _कितन, _lato_,
+ {{0x62950216,0x26d10333,0x24180fe6,0x3946a833}}, // [16f0] _pezo, pezo_, готы_, doos_,
+ {{0x212b034a,0x27e92834,0x7d0d2835,0x830500ab}}, // lich_, _iran_, skas, োচ্চ_,
+ {{0xe8d900ff,0xa2c12836,0x8027881b,0xc1e8084b}}, // _nhị_, र्ष्, گرام, льце_,
+ {{0x629aa837,0x212b01e4,0x32641132,0x6f01a838}}, // hato, nich_, стув, _solc,
+ {{0x26dea839,0x4ac48651,0xe8d901bc,0x35ab06a7}}, // _bato_, वभाव, _ahị_, _छिड़,
+ {{0x7ceb04b8,0x656f283a,0x66e5a83b,0xaec480e8}}, // _förä, _hich, рока, йбіл,
+ {{0x212b0063,0x26dea83c,0x6f01a09c,0x656f283d}}, // kich_, _dato_, _volc, _kich,
+ {{0x212b0775,0xdb040073,0x63ad283e,0x27e9283f}}, // jich_, tuiç, oran, _oran_,
+ {{0x656f2840,0x212b0229,0x26de83a7,0x9ed98098}}, // _mich, dich_, _fato_, _имат_,
+ {{0x656f2841,0x26de954e,0x13098009,0x03259138}}, // _lich, _gato_, тной_, рдон,
+ {{0x63ad2842,0x7529a843,0x69de826f,0xcb140039}}, // hran, piez, _šper, מלץ_,
+ {{0x656f0943,0x212b2844,0x95678698,0x75ca8201}}, // _nich, gich_, _създ, _qəze,
+ {{0x26de8091,0x63ad0289,0x248d83a7,0x5edf00ab}}, // _yato_, jran, mbem_, মানে,
+ {{0x7d02822c,0x656f2845,0x6b89a846,0x27e901ec}}, // _zoos, _aich, mpeg, _dran_,
+ {{0x63ad2847,0xde032657,0x27e92848,0x656f106c}}, // eran, опри, _eran_, _bich,
+ {{0x63ad2849,0x212b01ac,0x69c1a84a,0x7de08019}}, // fran, cich_, mtle, lásá,
+ {{0x656f284b,0x27e9284c,0x998d23e3,0x0326284d}}, // [1700] _dich, _gran_, _ćeš_, адан,
+ {{0x6d5d284e,0x656f1f8b,0xfaa6284f,0x60c9a850}}, // _ehsa, _eich, ражо, _abem,
+ {{0x63ad1897,0xa2bd80bc,0x60c9838a,0xb87b016a}}, // aran, _शान्, _bbem, ldía,
+ {{0x629aa851,0x2ba6901c,0x26dea852,0xfce62853}}, // zato, _खिला, _sato_, _томо,
+ {{0x26dea854,0x7d0298d6,0xb87b05e4,0xdb09816a}}, // _pato_, _roos, ndía, nteó,
+ {{0x656f2855,0x69c1a280,0xadc30032,0x3d118091}}, // _zich, ktle, _atẹg, _báwo_,
+ {{0x656f20ba,0xfaa32856,0xdd978048,0x7de08019}}, // _yich, _фасо, ашы_, dásá,
+ {{0x629a8077,0x26de80a4,0x3d11846d,0x44f4a857}}, // wato, _wato_, _dáwo_, спис,
+ {{0x26de801b,0x2fc0022c,0x629aa858,0x69c18035}}, // _tato_, _nqig_, tato, etle,
+ {{0x68e0a859,0xe8d90104,0x5b148364,0x3178285a}}, // _hamd, _thị_, смот, _kurz_,
+ {{0x2904a85b,0x257c0013,0x13cf00c8,0xbcfb285c}}, // _koma_, _níl_, রিয়, _miér,
+ {{0x68e2a85d,0x27e90205,0x2904a85e,0x644686ae}}, // ndod, _pran_, _joma_, ükis,
+ {{0x212b285f,0x2f1400f2,0x656f2860,0xd83b0048}}, // rich_, _säga_, _rich, тэн_,
+ {{0x656f2861,0x212b01e4,0x69dc007a,0x69ce2862}}, // _sich, sich_, _osre, _opbe,
+ {{0x656f022e,0x2d8505f8,0x35e405a8,0x69c1890d}}, // _pich, ële_, оцтв, ctle,
+ {{0x2f140006,0x2904829b,0x5c750a95,0xfc3f002a}}, // _väga_, _noma_, слат, _adía_,
+ {{0x63ad2863,0x2129036e,0x656f2864,0x68e2831d}}, // [1710] uran, _imah_, _vich, ddod,
+ {{0xf1da8063,0x656f02af,0x59da92ee,0x63ad2865}}, // _बनान, _wich, _बनार, rran,
+ {{0x656f0a00,0x63ad16b2,0x644f009a,0xc69202f6}}, // _tich, sran, _wyci, _מאי_,
+ {{0x2904a866,0x63ad2867,0xc8798059,0x68e08114}}, // _coma_, pran, _başa_, _camd,
+ {{0x27e9a868,0x68e0809c,0x752d2869,0x6b5d8866}}, // ían_, _damd, miaz, lège,
+ {{0x6298a86a,0x81cd80c8,0x7bdd0051,0x752d030a}}, // _nevo, রিত_, _issu, liaz,
+ {{0x63a2a86b,0xe7f38063,0x816b8071,0x692b001b}}, // _avon, _आपका_, _араб_, vřen,
+ {{0xce380bea,0x2904a86c,0x752d286d,0x25a9a304}}, // יאות_, _goma_, niaz, čal_,
+ {{0x629894f9,0xe784a86e,0x7de08065,0x3015a481}}, // _bevo, _духо, tásá, _удар,
+ {{0x69c1a86f,0xf7708013,0x6b89a870,0xc2e98065}}, // ttle, _قال_, rpeg, اعظم_,
+ {{0x7c3b0125,0x395f81c0,0x6b89a871,0x6298a872}}, // _þurf, _khus_, speg, _devo,
+ {{0xa3d281ce,0x443d81c0,0x628e807b,0xc8798850}}, // _हैक_, _txw_, ðbor, _yaşa_,
+ {{0xe28e8ae7,0x43858307,0x752d2873,0x7d1ba874}}, // _на_, _التق, diaz, mhus,
+ {{0x6298a4de,0xe7fa816f,0x7d1ba875,0x68e2a876}}, // _gevo, ्हता_, lhus, zdod,
+ {{0xba9a8051,0x63a2883d,0x7bdd19c3,0x1a65853d}}, // _עסקי, _zvon, _assu, _دیدی_,
+ {{0x31a382bb,0x6143120c,0x2bac0a27,0x6281a877}}, // mız_, _неща, _चिदा, lclo,
+ {{0x2904a878,0x68e0a879,0xed5700a9,0xdb098216}}, // [1720] _roma_, _ramd, сот_, lteñ,
+ {{0xb87b287a,0x2904a87b,0x6281a87c,0x394d810c}}, // ndín, _soma_, nclo, _akes_,
+ {{0x31a38afe,0x58868084,0x7bdd0c56,0xdb0984c3}}, // nız_, была, _essu, nteñ,
+ {{0x752d287d,0x395fa87e,0x75ca8085,0x61fc85ec}}, // ciaz, _chus_, _nəza, ärli,
+ {{0xe1e789d7,0x68e2a87f,0x7d1ba880,0xc7968190}}, // _کس_, rdod, dhus, ёрды,
+ {{0x2904913b,0x44202881,0xdb04016a,0x99760049}}, // _woma_, mzi_, briá, _гуаш,
+ {{0x63a28125,0x69dc0a20,0x6d410125,0xacf891d2}}, // _svon, _usre, élag, инку_,
+ {{0x629e2882,0x7d1ba883,0x75ca8085,0x186aa884}}, // lapo, ghus, _cəza, _баби_,
+ {{0x44201167,0x7d0601e2,0xa3b383b7,0x77e20a27}}, // nzi_, _moks, जूल_, गटोक_,
+ {{0xbea3a885,0x629e2886,0x752d2887,0x6281a888}}, // _наук, napo, ziaz, gclo,
+ {{0x60f901d7,0x9cca8b71,0x14ca8009,0x8afb007c}}, // ання_, _была_, _были_, אליז,
+ {{0xeb9a87b6,0x629e02e8,0xe8d900ff,0x63a282f7}}, // _бид_, hapo, _thọ_, _tvon,
+ {{0x2571805c,0x629e2889,0x6ebf800f,0x6298a88a}}, // _ušla_, kapo, ल्कु, _tevo,
+ {{0x4420288b,0x657a94aa,0x543b80be,0x1ddc9834}}, // dzi_, _kuth, _געדא, _मनात,
+ {{0x918688ca,0x4420288c,0x50c8864a,0x752d288d}}, // _اجتم, ezi_, रभाष, tiaz,
+ {{0x2129288e,0x7a0a8085,0xa3e50327,0x2f0b821e}}, // _umah_, rətd, पटल_, _høgt_,
+ {{0x2d580153,0x60cd288f,0x752d0f92,0x657aa890}}, // [1730] сить_, _ibam, riaz, _luth,
+ {{0x629e2891,0x3f890187,0x394b1e9e,0x249902c4}}, // gapo, _itau_, locs_, _wesm_,
+ {{0xe5c72300,0x395f81c0,0x44202892,0x657a89c4}}, // _успо, _phus_, azi_, _nuth,
+ {{0x63b606d5,0x764482d0,0xe1f001a8,0xfbd00061}}, // buyn, ğiyl, عسل_, بتہ_,
+ {{0x7bc413df,0x2b930bca,0x657a8051,0xb4db026b}}, // ctiu, _ریاس, _auth, _ajàg,
+ {{0x6b8d2893,0x079c0039,0xdb231a50,0x31a3807e}}, // mpag, נסול, örän, yız_,
+ {{0x4519a33f,0x6b8d2894,0xe8d90028,0x7d060214}}, // иция_, lpag, _nhỏ_, _yoks,
+ {{0x69c52895,0x75ca8085,0x394b023e,0xdb1b8118}}, // mthe, _qəza, jocs_, rtuí,
+ {{0xfc318307,0x31d184e5,0x3f8910af,0x7d1b8370}}, // بحث_, तब्ध, _ntau_, rhus,
+ {{0x657a91e8,0x693c9807,0x60cd2896,0x7d1ba897}}, // _futh, nčen, _abam, shus,
+ {{0x44202898,0x3f89048f,0xb87b2899,0x657aa89a}}, // zzi_, _atau_, rdín, _guth,
+ {{0xc6890f60,0xe56e835f,0x69c50cac,0x291d8683}}, // _יא_, _із_, ithe, nhwa_,
+ {{0x31a383bf,0x81c100ab,0xfaa58196,0x2f10851e}}, // sız_, ্টি_, _дало, _bàgh_,
+ {{0x6b8d0f33,0x212f81e4,0xe3b0a89b,0x69c5289c}}, // dpag, high_, _سره_, kthe,
+ {{0x693c816b,0x366a2748,0x78a9a67b,0xdb098661}}, // dčen, раво_, _odev, nreí,
+ {{0x7ae3a89d,0x4420011e,0x2d78017f,0x81c100ab}}, // _hant, tzi_, _očev_, ্টা_,
+ {{0x7ae3861b,0x629e049f,0x66e60003,0x7d060b40}}, // [1740] _kant, tapo, _допа, _voks,
+ {{0x78a98012,0x249f81c5,0x7bc4289e,0x44200235}}, // _adev, haum_, rtiu, rzi_,
+ {{0x44200065,0x7bc4289f,0x68e428a0,0xe9da28a1}}, // szi_, stiu, _kaid, рке_,
+ {{0x6b8d28a2,0x26d828a3,0x777b8079,0x29020796}}, // bpag, lero_, _buux, njka_,
+ {{0x2ca028a4,0x657aa8a5,0x795d03f8,0x27ed9c4b}}, // maid_, _suth, néwa, _cren_,
+ {{0x26d828a6,0xfdfe0e18,0xf413004c,0x7ae3a8a7}}, // nero_, _उपास_, _חפש_, _nant,
+ {{0x63a428a8,0x27eda8a9,0x629c0029,0xd5b080f7}}, // lsin, _eren_, _iero, رفة_,
+ {{0x7ae382b5,0x68e40046,0x291da8aa,0xf1c303fb}}, // _aant, _naid, chwa_, žší_,
+ {{0x7ae3a0fc,0xa2ca0023,0x629c28ab,0xdddc01dd}}, // _bant, स्थ्, _kero, jbrž,
+ {{0x26d828ac,0xbcfb00f7,0x3d1880d7,0xdb0d0118}}, // jero_, _mhéa, _déwa_, traé,
+ {{0x7ae3a8ad,0x26d828ae,0x629c28af,0x27e028b0}}, // _dant, dero_, _mero, _asin_,
+ {{0x645c8086,0x2ca00074,0x7c65803d,0x68e428b1}}, // ərin, jaid_, _کامل, _caid,
+ {{0xb4d69344,0x26d828b2,0xc05b0558,0xb4b6a8b3}}, // ाजी_, fero_, рін_, जली_,
+ {{0xa3d785e8,0x629c1bfe,0x26d80e4d,0xf6518065}}, // ाबर_, _nero, gero_, _آئے_,
+ {{0xd49b0ae7,0x6b8d28b4,0x27e004be,0x68e428b5}}, // ира_, wpag, _esin_, _faid,
+ {{0x68e428b6,0x7ae3a8b7,0x25ad1f95,0x63a428b8}}, // _gaid, _zant, čel_, fsin,
+ {{0xbcfb0013,0x63a428b9,0x26d828ba,0x7ae3a8bb}}, // [1750] _chéa, gsin, bero_, _yant,
+ {{0x68e400f6,0xbcfb0307,0xa3ac000f,0x6b8d28bc}}, // _zaid, _dhéa, _गिर_, rpag,
+ {{0xa2bd81ab,0x212f80f7,0x26c10133,0x291d805d}}, // _शास्, tigh_, _icho_, thwa_,
+ {{0xa2ca824c,0xbcfb0013,0x2ca028bd,0x69c528be}}, // त्त्, _fhéa, caid_, rthe,
+ {{0x629c28bf,0xf484003d,0x212f8c5e,0x2fc6a8c0}}, // _fero, _تاپی, righ_, gtog_,
+ {{0x291d9ae4,0x629c28c1,0x69c528c2,0x212f81a8}}, // shwa_, _gero, pthe, sigh_,
+ {{0x7ae3a8c3,0x6ecd000c,0x6d44a8c4,0x61ee01a1}}, // _rant, द्यु, čian, _grbl,
+ {{0x7ae3933b,0x27eda8c5,0x628528c6,0xd4d9835f}}, // _sant, _tren_, lcho, ські_,
+ {{0x26c105a4,0x629c0c2e,0x26d828c7,0x27eda8c8}}, // _ocho_, _yero, yero_, _uren_,
+ {{0x628528c9,0x68e428ca,0x26c1022c,0x629c28cb}}, // ncho, _said, _ncho_, _xero,
+ {{0xdb0d02ba,0x26d815a4,0x68e428cc,0x63a428cd}}, // ntañ, vero_, _paid, zsin,
+ {{0x26c128ce,0x7c2d9bcf,0x9f4481df,0x63a428cf}}, // _acho_, _žaru, _irmá_, ysin,
+ {{0x26d828d0,0x2ca00006,0x68e428d1,0x628528d2}}, // tero_, vaid_, _vaid, kcho,
+ {{0x7ae38282,0x628528d3,0x25ac026f,0xbbe9804e}}, // _uant, jcho, ádlo_, _پرچم_,
+ {{0x26d828d4,0x628528d5,0xdb0d00e7,0x68e422af}}, // rero_, dcho, traî, _taid,
+ {{0x63a428d6,0x629c28d7,0x26d828d8,0xb4e606af}}, // tsin, _sero, sero_, पये_,
+ {{0xa5070098,0xd46710ca,0x61ee1f3a,0x1d072300}}, // [1760] чета_, зите_, _srbl, чети_,
+ {{0x63a428d9,0x2ca028da,0x6b50016d,0x6aa18b81}}, // rsin, said_, vägs, nalf,
+ {{0x629c28db,0x2ca017f6,0x63a41527,0xc8b50019}}, // _vero, paid_, ssin, _علیک,
+ {{0x63a428dc,0xbcfb00f7,0x629c28dd,0x00000000}}, // psin, _théa, _wero, --,
+ {{0x28c110be,0x629c28de,0x4abda23a,0x6285016b}}, // ष्टि, _tero, ्भाव, bcho,
+ {{0x656280f1,0x657e005d,0x45d4a8df,0x6285051e}}, // _shoh, _kuph, докс, ccho,
+ {{0x3981a8e0,0xc1da800f,0xdb04016d,0x7c240087}}, // _nós_, _बनेग, rriä, lzir,
+ {{0xdce428e1,0x48e38af2,0xb4d693e5,0x7d0428e2}}, // kmič, _посв, ाजे_, njis,
+ {{0xd943838d,0x7c2428e3,0xa2bd9834,0x614389b5}}, // _чети, nzir, _शाश्, _чета,
+ {{0x645c8201,0xdce42648,0xfbcf80a0,0xef178009}}, // əril, dmič, اتي_, _ему_,
+ {{0xa2c10540,0x58d90b79,0x6562a8e4,0x867581e2}}, // र्ट्, одня_, _thoh, _жыцц,
+ {{0xdb1ba8e5,0x2d570214,0x69cd86a7,0x62850690}}, // rtuá, rçek_, _तहरी, zcho,
+ {{0x506404ae,0xdb1b8187,0x75ca8085,0x6aa18c0b}}, // етра, stuá, _məzm, balf,
+ {{0x39818013,0x7c2428e6,0x6aa1823e,0xf53f021e}}, // _fós_, dzir, calf, _sjå_,
+ {{0x78a28a9e,0x6614128a,0x97579101,0x9f44128a}}, // kaov, _čeká, פילו_, ímá_,
+ {{0xd23b804c,0x7d04020f,0xe945026a,0x2a3b81c6}}, // _לגול, gjis, _ترتی, _לעומ,
+ {{0x628528e7,0x2b510267,0x3ce681a1,0x6f010061}}, // [1770] tcho, _pkzc_, _maov_, ölcs,
+ {{0x212da8e8,0x501c01c6,0x628513ff,0x4424a8e9}}, // _smeh_, _לוחו, ucho, izm_,
+ {{0xceb2093f,0x628528ea,0xcb138158,0x69c887f1}}, // _ביי_, rcho, עלע_, ntde,
+ {{0x628528eb,0xf8bf28ec,0x7c240bcf,0xb4d68327}}, // scho, ngé_, bzir, ाजो_,
+ {{0xdb0d0ef3,0x31648115,0xf8bf0036,0xc615a8ed}}, // stañ, _dhmz_, liés_, तन्य_,
+ {{0x8ae4035f,0x3ea328ee,0xdb0d0187,0x6440050b}}, // _післ, najt_, duaç, _žmig,
+ {{0x3164826c,0xa3e5000d,0x77770609,0xdb0d01a8}}, // _fhmz_, पटक_, _mixx, nraí,
+ {{0xfdf80039,0x7ae728ef,0x78ad0a03,0x3d18810c}}, // לצות_, _hajt, _ndav, _déwo_,
+ {{0x6aa1a8f0,0x04778065,0x7ae7007a,0x6569a8f1}}, // talf, _ہلاک_, _kajt, rmeh,
+ {{0x394f80eb,0x398183a7,0x6569a8f2,0xc9a98198}}, // logs_, _pós_, smeh, овое_,
+ {{0xd9462606,0x7ae728f3,0x7d1d28f4,0x645c8085}}, // дени, _majt, _olss, ərim,
+ {{0xcea9004c,0x7ae728f5,0xf8bf28f6,0x3981a6f0}}, // _כי_, _lajt, ggé_, _vós_,
+ {{0xd9aa01a2,0xbcfb00e7,0xe7f0881f,0xbbeb80d7}}, // _चट्ट, _phén, चिया_, _درام_,
+ {{0xdb0980f7,0x69d500f3,0xf8bf0036,0xdce428f7}}, // breá, _opze, fiés_, rmič,
+ {{0xbb5284e3,0xdb0d0013,0xc10600f7,0x63bba8f8}}, // _جنوب, graí, _يوتي, luun,
+ {{0x2cbf8074,0x29190168,0x7d0b8061,0x2d5389b2}}, // ngud_, ërat_, _jogs, mães_,
+ {{0x3ce60038,0x7bc9a8f9,0x7d0428fa,0x63bba8fb}}, // [1780] ľov_, nteu, rjis, nuun,
+ {{0xe8d90142,0xa3cf1a87,0x44630073,0x3ce600e1}}, // _thể_, _शहर_, твув, žov_,
+ {{0xaacfa3a7,0xa50a18a0,0x3ce9151e,0x7ae728fc}}, // त्यक, _дена_, bdav_, _dajt,
+ {{0xdb0d007b,0x7bc9a8fd,0x3cf90118,0x693c81d6}}, // stað, kteu, _gnsv_, mček,
+ {{0xd12f83f8,0x10a3a8fe,0x98a38991,0x87da9c12}}, // جمن_, тичн, тиче, تباس_,
+ {{0x6d448110,0x63bb8079,0xaacf8107,0x644602f9}}, // čiam, duun, त्मक, _ixki,
+ {{0x7d0ba8ff,0x60c402a5,0x98a9026c,0x69c8a900}}, // _bogs, _ocim, _smač_, xtde,
+ {{0xdb0d0073,0x7ae7251c,0x63bba901,0xfbaa862c}}, // tuaç, _zajt, fuun, отой_,
+ {{0x7d0b81e0,0x7ae701c0,0xaec6a902,0x693c928a}}, // _dogs, _yajt, _обал, yčej,
+ {{0x60c40073,0x764d8182,0x1f63a28e,0x59d205fb}}, // _acim, şaya, _акум, _सहार,
+ {{0xe1352903,0x195901bb,0xdb042904,0x81d68264}}, // енны, заны_, friú, সিভ_,
+ {{0x26dca905,0x7d0b80b9,0x27f22906,0xdb040036}}, // levo_, _gogs, _bryn_, ssiè,
+ {{0x7bc982c2,0x78a09142,0xf8bf00e7,0x69c8a907}}, // cteu, _memv, rgé_, stde,
+ {{0xdef8053b,0xdb0d2908,0x26dc80e5,0xf8bf0866}}, // мыс_, traí, nevo_, tiés_,
+ {{0xcad78051,0x7ae70019,0x3ea32909,0x7d1d02c4}}, // _חוות_, _rajt, rajt_, _plss,
+ {{0x7ae7290a,0xa2ca8d14,0xf8bf00e7,0x3ea3290b}}, // _sajt, त्र्, riés_, sajt_,
+ {{0x29190029,0x24580009,0x7ae7290c,0x60dd0123}}, // [1790] ūras_, дать_, _pajt, mesm,
+ {{0x60dd290d,0x26dc9620,0xbcfb00e7,0xc8798087}}, // lesm, jevo_, _théo, _iaşi_,
+ {{0x7ae7290e,0x26dca90f,0x51840110,0x85750009}}, // _vajt, devo_, вуча, _плох,
+ {{0x25f4825e,0x60dd2910,0x2561808b,0x63b62911}}, // ्मनी_, nesm, kóla_, bryn,
+ {{0x24b600be,0x63bb8079,0xf8bf00e7,0x7ae72912}}, // _אהרן_, xuun, chée_, _tajt,
+ {{0x656d1572,0x2b0003eb,0x2f188198,0x62830088}}, // lmah, राणु_, мочь_, žnob,
+ {{0x60dd2913,0x6aa52914,0x62888216,0x9f400362}}, // kesm, mahf, dcdo, _criù_,
+ {{0x7bc9a915,0x656d2916,0x63bba917,0x3ea12918}}, // tteu, nmah, tuun, _leht_,
+ {{0x31348703,0x2fc901c0,0x656d011b,0x6d4280c3}}, // _ретр, _nqag_, imah, _djoa,
+ {{0x291f8282,0x656d2919,0x26dc847f,0x7bc9a91a}}, // _hlua_, hmah, cevo_, rteu,
+ {{0x68e99083,0x656d09ca,0xf2d200be,0x68fba91b}}, // _kaed, kmah, נעט_, _knud,
+ {{0xaacf024c,0x4acf06bf,0x7bc980e7,0x656d026c}}, // स्तक, स्तव, pteu, jmah,
+ {{0x7c3b291c,0x395e00eb,0x653a80be,0x6aa500b9}}, // _žurn, ēts_, _צענד, kahf,
+ {{0x6d5b820f,0x6446008e,0x63a9a91d,0xbcfb09c4}}, // llua, _rxki, msen, _dhém,
+ {{0xceb29a0f,0x63a9a280,0xf8bf026f,0x24f61444}}, // טים_, lsen, riér_, нчар,
+ {{0x2edc800d,0x63b60d12,0xfce30171,0x693c81ac}}, // _यस्त, tryn, лосо, rček,
+ {{0x63a9a91e,0x6d5ba91f,0x6b4b0061,0xab662920}}, // [17a0] nsen, ilua, lügy, евал,
+ {{0x3ea10352,0x63a9a921,0x68fba922,0x291f8f3e}}, // _geht_, isen, _anud, _alua_,
+ {{0x64a625af,0xda0f83eb,0x81cd80ab,0x26dca923}}, // _пана, ाहित_, রিক_, vevo_,
+ {{0x92c20a49,0xc7c69b10,0x63a9a924,0x4fc68103}}, // ্যে_, ески, ksen, еска,
+ {{0xb05b0106,0x26dca925,0x98ab0087,0x6d5ba926}}, // lfär, tevo_, lică_, dlua,
+ {{0x63a997db,0x290d8176,0xde588084,0x36d4a927}}, // dsen, _eoea_, малі_, токр,
+ {{0xd91099f4,0xe3570451,0x78a6003a,0x4343014f}}, // _پیش_, _נשלח_, kakv, _серв,
+ {{0xafe69631,0x628a826c,0x26dc8267,0xd90d881b}}, // _побл, _sffo, sevo_, _ویل_,
+ {{0xcf469ae5,0x6459864c,0x26dc847f,0x60c281f6}}, // _знай, _cywi, pevo_, mgom,
+ {{0x21200455,0x9ad281bc,0x7a0a8085,0xef1981b9}}, // _alih_, _bịag, bətl, _tuża_,
+ {{0x2258813c,0x6d5ba928,0x673500f1,0x75d395a9}}, // ærk_, blua, vizj, _ميلا,
+ {{0x60c2a929,0x28c60ebf,0x98ab0087,0x67350035}}, // ngom, _वाणि, dică_, wizj,
+ {{0x60dd292a,0xac9781a8,0x2d81292b,0x64598114}}, // resm, _أنها_, _suhe_, _gywi,
+ {{0x98ab0087,0xb05b05ec,0xb6e4096b,0x60dd292c}}, // fică_, ffär, люск, sesm,
+ {{0x248901e0,0xa01b0019,0x6d408a53,0xaa57a92d}}, // tcam_, nböz, onma, _пишу_,
+ {{0x7aeaa92e,0x6d40a92f,0x81cd80c8,0x92bd00ab}}, // _haft, nnma, রিখ_, _আসে_,
+ {{0x24892930,0x7aeaa931,0x7ff701a8,0x6d40a932}}, // [17b0] rcam_, _kaft, _أسعا, inma,
+ {{0xdbc78074,0x656d2933,0x24892934,0xdb098187}}, // _tööp, smah, scam_, rreç,
+ {{0x6b828098,0xc3338039,0x24891286,0xaca30133}}, // _luog, נוע_, pcam_, _amụb,
+ {{0x75242935,0x7aeaa936,0x9f5206c4,0x41d201a2}}, // chiz, _laft, _aryè_, _सहरस,
+ {{0x2571812b,0x5eaa80ab,0x3d0489c8,0x672380f1}}, // _ušli_, চ্ছে, वाने_, thnj,
+ {{0x44290010,0x6d40a937,0x141a01c6,0x63bf0037}}, // mza_, enma, _חושב, muqn,
+ {{0x7d0f0061,0xb89581a8,0x661881d6,0x777a9fa4}}, // _kocs, _للأع, _čaká, _ditx,
+ {{0x63a9a938,0x44292939,0x67238168,0x7a0a8085}}, // tsen, oza_, shnj, rətl,
+ {{0x6d5b8867,0x777a89cb,0x25a0812b,0x98ab0087}}, // rlua, _fitx, ćila_, zică_,
+ {{0x4429293a,0x765a80f1,0x7aea8bfe,0x777a8cdb}}, // iza_, _fyty, _caft, _gitx,
+ {{0x7aea8455,0x3ea7a93b,0x63a9a93c,0x7bcd0198}}, // _daft, mant_, ssen, htau,
+ {{0x3ea79702,0x63a98721,0x61f50ee1,0x27260129}}, // lant_, psen, _mrzl, _kông_,
+ {{0xa3e593d9,0x7d09920e,0x4429293d,0x88e300ab}}, // _पनि_, mjes, jza_, যাংক,
+ {{0x3ea7a93e,0x7d09a93f,0x44292940,0x98ab002e}}, // nant_, ljes, dza_, tică_,
+ {{0x7d0f2941,0x44292942,0x80d28b75,0xed5a24a4}}, // _bocs, eza_, ण्डे, дов_,
+ {{0xed431980,0x98ab0087,0x3d0483db,0x7d09a943}}, // ансп, rică_, वाये_, njes,
+ {{0x75ca8201,0xed570003,0x2726001c,0x7d0f0706}}, // [17c0] _vəzi, тот_, _nông_, _docs,
+ {{0x693ca944,0x3ea7a945,0x752400b4,0x81bc80eb}}, // nčev, jant_, shiz, dzēj,
+ {{0x44290012,0x3ea7a946,0x7d1ba947,0x7d09a948}}, // aza_, dant_, kkus, kjes,
+ {{0x6ec4a6ee,0x272600ff,0x44292949,0xb87b294a}}, // _राहु, _bông_, bza_, rdív,
+ {{0x27260142,0x44290063,0xb91d01bc,0x2ba90ad5}}, // _công_, cza_, _anyụ_, _चौपा,
+ {{0x3ea7a94b,0xcf9300be,0xef19809a,0x272600ff}}, // gant_, סטו_, _dużo_, _dông_,
+ {{0x20008c6e,0x644e8085,0x7afe823e,0xdb0401df}}, // ţii_, əbiy, _òpti, quiñ,
+ {{0x7aea84dc,0x7d09a94c,0x7d1b8ec9,0xaacf909b}}, // _saft, gjes, gkus, त्सक,
+ {{0x3ea7a94d,0x6aa3a94e,0x6d408d1a,0xafe397c7}}, // bant_, _benf, rnma, _котл,
+ {{0x3ea7a94f,0x5186a950,0x2d6e801b,0xd764803d}}, // cant_, кума, _přes_, _صنای,
+ {{0xc333004c,0x691387d9,0x44292951,0xaad40c1c}}, // שות_, _içer, zza_, ठ्यक,
+ {{0x78a412f5,0x3f84a952,0x44292953,0x7bcd0084}}, // _ceiv, _kumu_, yza_, ytau,
+ {{0xead49641,0x99640071,0x3f849325,0x7c298035}}, // _боль, ртэл, _jumu_, czer,
+ {{0x7bc0a954,0x07a40048,0x3f8482ec,0x7f942218}}, // mumu, раён, _mumu_, рарх,
+ {{0x5faf0076,0x4b7b80be,0x3f848c2e,0xd37b8e82}}, // _झटकल, _קאוו, _lumu_, _קרוט,
+ {{0x44292955,0x7bcd24fb,0x3ea7a956,0xaac618b1}}, // tza_, ttau, zant_, र्घक,
+ {{0x2d580364,0x44292957,0x3ea7a958,0x8aa70b5b}}, // [17d0] тить_, uza_, yant_, вред,
+ {{0xd5a48986,0x27e9026b,0x8c3d82bb,0x7bcd2959}}, // _وہ_, _isan_, daşl, rtau,
+ {{0x3ea7a95a,0x4429295b,0x68ed295c,0x8af78201}}, // vant_, sza_, _kaad, _şəki,
+ {{0x7bc0a95d,0x4429008e,0xb7d5846d,0x213901c6}}, // kumu, pza_, _aṣaw, nish_,
+ {{0x7bc0a95e,0x68ed295f,0x7d09920e,0x2ca92960}}, // jumu, _maad, vjes, maad_,
+ {{0x68ed2961,0x2ca90079,0x39ae8084,0xdce98267}}, // _laad, laad_, kęs_, rmeč,
+ {{0x3ea7a962,0x657d2963,0xed59a964,0x41e40084}}, // rant_, _kish, нок_, _кіта,
+ {{0x7983a965,0x3ea7a966,0x61fa0019,0x272600ff}}, // _runw, sant_, _átla, _tông_,
+ {{0x657d2967,0x7d09a968,0x60c98267,0xbcfb0013}}, // _mish, rjes, _icem, _chéi,
+ {{0x657d2647,0x63ad2969,0x78a4296a,0x645d296b}}, // _lish, isan, _seiv, _mysi,
+ {{0x68ed296c,0x7c298117,0xc6928158,0xa0a61ddf}}, // _baad, szer, _האב_, ланд,
+ {{0x3ea58f06,0x63ad296d,0x8c3d8182,0x657d296e}}, // _helt_, ksan, laşm, _nish,
+ {{0xb8ec131d,0x7bc0a96f,0x6d59a970,0x68ed2971}}, // _ला_, cumu, _ikwa, _daad,
+ {{0x2d85a972,0x63ad2973,0x248da974,0xf53981ac}}, // _kule_, dsan, lcem_, liť_,
+ {{0x27e92975,0x1dc6820e,0x63bb80eb,0x7ae1a976}}, // _esan_, रूपत, lrun, melt,
+ {{0x28d30076,0xaacf86de,0x68ed0ef2,0xf53981ac}}, // ध्दि, त्रक, _gaad, niť_,
+ {{0x69c1a977,0xb7be80c8,0x63ad2978,0x2fcd81c0}}, // [17e0] lule, _ইন্ট, gsan, _nqeg_,
+ {{0x7ae1a979,0x29000282,0xb87b0019,0x81d680ab}}, // nelt, _hnia_, rdít, সির_,
+ {{0x2b898104,0x63bb8352,0x69c1a97a,0xe8d90028}}, // _lúc_, hrun, nule, _nhớ_,
+ {{0x645d2280,0x657d297b,0x6d598870,0x29120388}}, // _fysi, _gish, _nkwa, _joya_,
+ {{0x69c18fc4,0xf53981ac,0x7ae1a97c,0x63ad297d}}, // hule, diť_, kelt, csan,
+ {{0x6d441c30,0x6d59a97e,0x69c1a97f,0x6d5602ed}}, // lnia, _akwa, kule, loya,
+ {{0x63bb82af,0x25a082a5,0x657d0ab4,0xb87b2980}}, // erun, ćilo_, _yish, ndír,
+ {{0x69c1a981,0x7bc0a982,0x6d442983,0x39ae8110}}, // dule, tumu, nnia, vęs_,
+ {{0x68ed2984,0x63bb8a64,0xd90d8065,0x6d442985}}, // _raad, grun, _تین_, inia,
+ {{0x68ed2986,0x7bc0a987,0x29002988,0x6d562989}}, // _saad, rumu, _ania_, hoya,
+ {{0x69c19786,0x2912298a,0xdd8f8077,0x7bc0a98b}}, // gule, _boya_, _فول_, sumu,
+ {{0x68ed02c1,0x6d440069,0x2ca902a3,0x63ad298c}}, // _qaad, jnia, xaad_, ysan,
+ {{0x9cd78051,0x657d298d,0x68ed09f8,0x2900009a}}, // _תודה_, _rish, _vaad, _dnia_,
+ {{0x69dc298e,0x69c1a98f,0x2d858010,0x6d448110}}, // _opre, bule, _yule_, čiau,
+ {{0x69c1a990,0x693c8a2f,0x291200ee,0xfa558019}}, // cule, nčes, _foya_, _پروگ,
+ {{0x657d2991,0x6d562992,0x349501e5,0x636b2993}}, // _qish, goya, раар, zünd,
+ {{0x2ca91e94,0x27e91df5,0x2b89801c,0x9f400144}}, // [17f0] raad_, _usan_, _xúc_, _crió_,
+ {{0x61f8811f,0xf5398038,0x6b5d8722,0x63a2a994}}, // _krvl, ziť_, tègi, _mwon,
+ {{0x6d442995,0x645d2996,0x7aee01c5,0x69dc2997}}, // bnia, _wysi, _dabt, _cpre,
+ {{0x417480f7,0x57f58568,0x2ba91513,0x645d0035}}, // مانس, ипет, _चौथा, _tysi,
+ {{0x69dc2998,0xf5398038,0x69c1a999,0x2d85a99a}}, // _epre, viť_, zule, _sule_,
+ {{0x7d0d299b,0xc5f980c8,0x2d85a99c,0xbcfb1727}}, // ljas, _অথবা_, _pule_, _chév,
+ {{0xf53981ac,0x28c603eb,0x2ba9000d,0x7c2d299d}}, // tiť_, _वालि, _चौता, lzar,
+ {{0xdb04299e,0x3ea582af,0xa3b90105,0xa3e92279}}, // brió, _welt_, _घटा_, _मना_,
+ {{0x7c2d299f,0xdcfa8029,0x7ae1a9a0,0xf53981ac}}, // nzar, _attē, telt, riť_,
+ {{0x69c1a9a1,0xadf7015c,0x63bba9a2,0x2d85837b}}, // tule, ुमान_, rrun, _tule_,
+ {{0x6d44031d,0x8e848013,0xf53981ac,0xa2ca06ce}}, // ynia, _المه, piť_, स्ट्,
+ {{0x69c1a055,0x63bba9a3,0x61f8817f,0x7ae1a9a4}}, // rule, prun, _drvl, selt,
+ {{0xe28e828b,0x53a582fb,0x6d4429a5,0x6d4481ac}}, // _ма_, _талб, vnia, čiat,
+ {{0x69c1a9a6,0xf7721ddd,0x38690239,0x6d4429a7}}, // pule, تاج_, _dzar_, wnia,
+ {{0xada61a8f,0xe8d90028,0x7aee16bb,0x442da9a8}}, // рабл, _giữ_, _rabt, lze_,
+ {{0x7aee29a9,0x06af80ab,0xef8615e0,0x7d0d1443}}, // _sabt, চ্চি, _клип, gjas,
+
+ {{0x69dc29aa,0x2d9829ab,0x6d4429ac,0xafe61878}}, // [1800] _spre, spre_, rnia, _топл,
+ {{0x7aee02a3,0x6aa7026c,0x2561808b,0x442da9ad}}, // _qabt, _cejf, fólk_, ize_,
+ {{0x09e604fa,0xfc3f01ca,0x6d561149,0x7d0d026c}}, // роен, _veía_, poya, bjas,
+ {{0x316d83d3,0x442d8831,0xdb04188b,0x2900a38a}}, // _chez_, kze_, trió, ñia_,
+ {{0x7aee29ae,0x442da9af,0xe8d9001c,0x3d048075}}, // _tabt, jze_, _nhờ_, वासे_,
+ {{0x6b8480f7,0x442da9b0,0xd83f016b,0xe2970a42}}, // _éigi, dze_, _účel_, _вар_,
+ {{0xe1e78064,0x32070009,0x69dc00e1,0x6136017f}}, // _بس_, änyt_, _upre, ršlj,
+ {{0x7c3b02a5,0x6283a9b1,0x387f017f,0x7529a9b2}}, // _žuri, _agno, žuri_, ghez,
+ {{0x673c01c2,0x394682ba,0xe8d90028,0x6eb681a8}}, // kirj, mnos_, _chờ_, _نصائ,
+ {{0x3d0483b7,0xdb040118,0x7dfc8162,0x661c1235}}, // वाहे_, oriñ, _păsă, kyrk,
+ {{0x7980a9b3,0xdb098020,0x442d838a,0x7c2d0ca9}}, // _kimw, gueñ, aze_, zzar,
+ {{0x7529822e,0x3946a9b4,0x63a29528,0x6d4488fc}}, // chez, nnos_, _twon, čias,
+ {{0x442d8d38,0x7d02a9b5,0x63a2a9b6,0xdce88110}}, // cze_, _inos, _uwon, _sudė,
+ {{0xdced026f,0xa2c2000d,0x7bdb83c8,0xadc3826b}}, // rmač, _लाग्, _סקוו, _alẹm,
+ {{0xdb00a3fa,0xa6978039,0xeca70226,0x28d7a9b7}}, // gsmå, שכלה_, ијан, ण्डि,
+ {{0x7c2d29b8,0xdb0401df,0x80c1016f,0x39468019}}, // tzar, driñ, _वाटे, jnos_,
+ {{0x29e8002e,0x395929b9,0x7bc426f9,0x67da81a9}}, // [1810] _aşa_, moss_, guiu, nājā,
+ {{0x7d0d29ba,0x3946a9bb,0xd543026b,0xa91d80c3}}, // sjas, enos_, _bẹ́n, _miže,
+ {{0x442da9bc,0x6ecd8beb,0x7c2d29bd,0x798080b4}}, // zze_, _साबु, szar, _bimw,
+ {{0x3946a9be,0xe45f0198,0xed4680d7,0x645d82f1}}, // gnos_, _työ_, _رپ_, üsik,
+ {{0x6126007b,0xa91d80ce,0x6d5d1400,0x693c81a1}}, // _jóla, _niže, _iksa, tčer,
+ {{0x66e582a9,0x39469f90,0xc69500be,0xdee59033}}, // сока, anos_, זאַ_, соки,
+ {{0x7afa008b,0xa3a9864a,0x2d680799,0x3f8917c9}}, // _ótta, गीन_, ißen_, _luau_,
+ {{0x442da9bf,0x4c698037,0x7ae508e5,0x69c51a08}}, // tze_, лийн_, leht, muhe,
+ {{0x3d080076,0xc9528bea,0x442da9c0,0x60dba9c1}}, // हाते_, _זמן_, uze_, _ibum,
+ {{0x442d8b04,0x7d028353,0x78a9a9c2,0xb87b29c3}}, // rze_, _enos, _heev, teín,
+ {{0x442da95b,0x5d8580f7,0x7529805d,0x673c007a}}, // sze_, _الصل, phez, virj,
+ {{0x612629c4,0x2d6802af,0xf09f077f,0xe8d900ff}}, // _bóla, eßen_, gbà_, _thờ_,
+ {{0xdb0981ca,0xed59807a,0x249f82df,0x3d04873c}}, // sueñ, mož_, mbum_, वारे_,
+ {{0x6d5d29c5,0x539a8051,0x612619ba,0x249fa9c6}}, // _aksa, _עיצו, _dóla, lbum_,
+ {{0xdb098c15,0x637012d2,0xd6270af0,0xe164003d}}, // queñ, läng, боре_, _وضعی,
+ {{0x6ab79094,0x2ba9000f,0x4c861d85,0x8a168039}}, // _आयुर, _चौहा, слав, _לחצו_,
+ {{0xd6c30019,0x798080b4,0x673c007a,0x63620187}}, // [1820] _کمپی, _rimw, pirj, lôni,
+ {{0xadf985e8,0x27e00091,0x6ac600f7,0x6d5d17ab}}, // ्मान_, _ipin_, إقام, _eksa,
+ {{0x63700b2f,0x1efb00be,0x27ed86c4,0xed59a9c7}}, // häng, _עלטע, _asen_, kož_,
+ {{0xa2d80540,0xdb0429c8,0x78a98282,0x1d0a29c9}}, // _नॉर्, rriñ, _ceev, _семи_,
+ {{0x3946a82b,0xaab8026a,0x33b50a14,0x7bc429ca}}, // rnos_, _نگار_, _мёрт, quiu,
+ {{0x26ca29cb,0x7d028d11,0x656429cc,0x3946a720}}, // ngbo_, _snos, llih, snos_,
+ {{0x3d04800c,0xe7af009a,0x2d8229cd,0x877b03de}}, // वाले_, _जौनप, _kike_, נאצי,
+ {{0x27e029ce,0x71268277,0xf8b0803d,0x77868009}}, // _opin_, _ارسل, _آکا_, _глаз,
+ {{0x63701f0f,0x7d02807a,0xa3cb073c,0xa2d8001b}}, // gäng, _vnos, रूप_, न्थ्,
+ {{0x2d8229cf,0xaaa8036d,0x636203a7,0x7b39816b}}, // _like_, गरिक, fôni, lňuj,
+ {{0xd5b9835f,0xfbcf826a,0x712700f7,0x23a986a7}}, // _всі_, وتی_, مرحل, चीबद,
+ {{0x59c90e1a,0xa2d80023,0x7d028024,0x63701bc0}}, // ांतर, न्त्, _unos, länd,
+ {{0xa2d381fe,0xdc8e81bc,0x395929d0,0x26d829d1}}, // _बॉक्, _dịtụ_, ross_, ffro_,
+ {{0x43748077,0x4ac700d4,0x2d820300,0xe5c70a08}}, // _بهتر, _लाइव, _aike_, йсбо,
+ {{0xa3cb09c8,0xd0470085,0xe18980ff,0x2d821aad}}, // रंभ_, _əməl, _hẳn_, _bike_,
+ {{0x3eae8352,0x637012d2,0x2d820041,0x6aaaa9d2}}, // haft_, händ, _cike_, _heff,
+ {{0x2d8229d3,0x78a9a9d4,0x06e400ab,0xe50986a7}}, // [1830] _dike_, _reev, মাজি, वाति_,
+ {{0x9a8706d2,0xbcfb29d5,0x2d8211ce,0x8c1b81c6}}, // суал, _chér, _eike_, אומי,
+ {{0x3cfe8076,0x78a9822c,0xa3a909f2,0xd91b99b8}}, // _शोभे_, _peev, _गौर_, льд_,
+ {{0x69c529d6,0x69c080d4,0x6aaa8198,0x5c070bac}}, // ruhe, _विनी, _leff, _ляга,
+ {{0xf7719168,0xd009a9d7,0xe8d911d3,0x6362041c}}, // وات_, реле_, _nkọ_, zôni,
+ {{0xa2d80bb8,0xb6a593b4,0x2d959016,0x6aaaa9d8}}, // न्द्, цикл, орис, _neff,
+ {{0x2129059c,0x171c0158,0xe8d919a8,0x78a9a9d9}}, // _olah_, רווע, _akọ_, _teev,
+ {{0x58d48e8e,0x60dba9da,0x6370016d,0xa63c0039}}, // _ност, _ubum, täng, טגור,
+ {{0x98178077,0x798a81ec,0x395f8102,0x6aaaa9db}}, // _ابزا, _aufw, _ikus_, _beff,
+ {{0x63700884,0xdb160009,0x63620187,0xe9d98eef}}, // räng, styö, tôni, ако_,
+ {{0x9f4901ec,0x7a699324,0x6aaaa9dc,0x637029dd}}, // _spaß_, ринг_, _deff, säng,
+ {{0x628300e1,0x637005ec,0x6362041c,0x6f05027d}}, // ľnoh, mäne, rôni, _anhc,
+ {{0x6d49a9de,0x2d8229df,0xbf87801c,0xeaaea9e0}}, // mnea, _rike_, _điển_, _ой_,
+ {{0xe299917c,0x1ddd80d4,0x321ea9e1,0xd5b800eb}}, // _так_, _महात, byty_, šām_,
+ {{0x6b83a9e2,0x395fa9e3,0x6d499b1f,0xad3a004a}}, // _iing, _okus_, onea, ачає_,
+ {{0x6d49954c,0x6b838f68,0x39b580eb,0x6b8ba9e4}}, // nnea, _hing, mās_, _kugg,
+ {{0x65640877,0x6f1729e5,0x6d498068,0x39b58029}}, // [1840] rlih, _foxc, inea, lās_,
+ {{0x6b83a9e6,0x6b8ba9e7,0x6d498083,0xf77212c8}}, // _jing, _mugg, hnea, _تاب_,
+ {{0x63701918,0x39b58341,0x6b83a9e8,0xa3a98128}}, // vänd, nās_, _ming, गीत_,
+ {{0x6b83a9e9,0x752d29ea,0x69c1832f,0x80668081}}, // _ling, chaz, irle, овеж,
+ {{0x63700b2f,0x394d826c,0xbcfb0036,0x69c181ec}}, // tänd, _djes_, _thér, hrle,
+ {{0x6b83a9eb,0xd5ba84ad,0x39b58029,0x394da9ec}}, // _ning, аси_, kās_, _ejes_,
+ {{0x39b58341,0x637029ed,0x44200019,0x386d9403}}, // jās_, ränd, lyi_, _ezer_,
+ {{0x6d499581,0x6b83a9ee,0x39b580eb,0x6370016d}}, // gnea, _aing, dās_, sänd,
+ {{0x442029ef,0x6b83a9f0,0x25a9805c,0x73fa80ab}}, // nyi_, _bing, ćala_, েন্ট_,
+ {{0x37d000ab,0x2129111e,0xda7aa3e7,0xa3cb0ad5}}, // িবার, _slah_, иян_, रंत_,
+ {{0x39b580eb,0xaacf047d,0x9258a9f1,0x28f91b53}}, // gās_, स्टक, сант_, бень_,
+ {{0x394d0003,0x6b839546,0xe8d90028,0x6b8ba9f2}}, // ões_, _eing, _phố_, _fugg,
+ {{0xb97b812a,0x442002a0,0x2129026c,0x6b8ba9f3}}, // ינטי, jyi_, _vlah_, _gugg,
+ {{0x7d060110,0x4432009a,0x39b581a9,0xa49b07f1}}, // _anks, dzy_, bās_, _enòl,
+ {{0x2129015d,0x752d005d,0x39b580eb,0x612629f4}}, // _tlah_, thaz, cās_, _gólo,
+ {{0x6d40a9f5,0x6b83a9f6,0x212901bf,0x4add0a27}}, // nima, _zing, _ulah_, न्नव,
+ {{0x2d8c812b,0x6b83a9f7,0x44200019,0x61260118}}, // [1850] _lude_, _ying, gyi_, _móll,
+ {{0x6d40a883,0x2d8c8a0f,0x74150019,0x6b83a9f8}}, // hima, _oude_, _ہوجا, _xing,
+ {{0x6d40a9f9,0x69c8a9fa,0x7c20831d,0x752d0234}}, // kima, nude, hymr, phaz,
+ {{0x6d4081e2,0xdb098013,0x7c208aa2,0x442000b4}}, // jima, iseá, kymr, byi_,
+ {{0x44320063,0x6d40a9fb,0x3d08000f,0x2d8ca9fc}}, // czy_, dima, हारे_, _aude_,
+ {{0x2d8ca9fd,0x78ad29fe,0x68fa00eb,0x3eba00ee}}, // _bude_, _leav, ētdi, _xdpt_,
+ {{0x6d408079,0x69c8a9ff,0x6b8b914e,0x6d498cb5}}, // fima, jude, _sugg, tnea,
+ {{0x6b83aa00,0x395f8024,0x39b580eb,0x6126002a}}, // _sing, _ukus_, vās_, _cóll,
+ {{0x6b83aa01,0x3eac806a,0x7f5c0216,0xb4e91d40}}, // _ping, _fedt_, yorq, _यसै_,
+ {{0x39b58029,0x49c994bc,0x27ffaa02,0x6d498c49}}, // tās_, блон_, _irun_, snea,
+ {{0x644d81e2,0x6b83aa03,0xc4cb009a,0x44322a04}}, // _žaid, _ving, िलाओ, zzy_,
+ {{0x69c18915,0x70c6016f,0xdce99487,0x39b580eb}}, // rrle, वलेल, dmeć, rās_,
+ {{0x3f85aa05,0x39b581a9,0x3f8d8980,0x7f5c1d5e}}, // _kilu_, sās_, _jueu_, torq,
+ {{0x2d9611c7,0x7d19aa06,0x69c8aa07,0x6b838010}}, // зрас, _hows, bude, _uing,
+ {{0x80ab0a49,0xe737138f,0x78bb805c,0x69d7022c}}, // _কার্, чер_, _oduv, jtxe,
+ {{0x80da00ab,0x3f85aa08,0x44322a09,0x78ad01ed}}, // _বোর্, _lilu_, tzy_, _geav,
+ {{0x75242a0a,0xc1db80d4,0x3b182a0b,0x7af50102}}, // [1860] rkiz, _बहुग, _porq_, _bazt,
+ {{0x44321d21,0x6d40aa0c,0x39422a0d,0x3f85930c}}, // rzy_, zima, liks_, _nilu_,
+ {{0x27308104,0xe9da2a0e,0x44320063,0x68f62a0f}}, // _hàng_, ске_, szy_, _kayd,
+ {{0x3f8d81df,0xd910804e,0x39422a10,0x68f62a11}}, // _bueu_, خیر_, niks_, _jayd,
+ {{0x6d40aa12,0x68f619d4,0xa3cb0424,0x78a2aa13}}, // vima, _mayd, रूद_, rbov,
+ {{0x7af500ad,0x2d8caa14,0x273080ff,0x28d186a7}}, // _gazt, _pude_, _màng_, _हाथि,
+ {{0x91e32a15,0x6d40aa16,0x09e30811,0x7d1981e0}}, // _поре, tima, _порн, _bows,
+ {{0x539889b5,0xdb98a64e,0x69c88687,0x2cb201b4}}, // овия_, ович_, vude, nayd_,
+ {{0x27308028,0x7bc9aa17,0x7ae89b4a,0x63b6006f}}, // _nàng_, gueu, tedt, nsyn,
+ {{0x59d00063,0xaacc2a18,0x6d40aa19,0x798e0a8c}}, // _हमार, ालिक, sima, _kubw,
+ {{0xc4869895,0x6d40aa1a,0x68f60079,0x78ad0074}}, // _елек, pima, _bayd, _peav,
+ {{0x6f1aa817,0x68f601b4,0xa91d80c3,0x27308129}}, // _hotc, _cayd, _ližn, _bàng_,
+ {{0xc05b0196,0xb4e88074,0x27308028,0x69c8aa1b}}, // сін_, मजी_, _càng_, sude,
+ {{0x2730801c,0x7bc40084,0xdb098144,0x69c8aa1c}}, // _dàng_, driu, breñ, pude,
+ {{0x68f603bf,0x78ad1b17,0x7bc40074,0xa4d50b73}}, // _fayd, _teav, eriu, мові,
+ {{0x7bc40037,0x5ec58264,0x68f610ba,0x8d7405de}}, // friu, ল্পে, _gayd, فاقا,
+ {{0x6c5423e0,0xdcfa8029,0xc1580051,0x31ae80ab}}, // [1870] екту, _attī, _משהו_, _কমিশ,
+ {{0x798e03f7,0x60dd2a1d,0x6f1a9e44,0x00000000}}, // _bubw, lfsm, _notc, --,
+ {{0xa3b906af,0x79a419a5,0xa2cf8540,0x69c80163}}, // _घटक_, ерче, _दास्, _ådel,
+ {{0x9c13819d,0x3f8584ef,0x81df0264,0x68f62a1e}}, // _kọti, _silu_, তিল_, _xayd,
+ {{0xe0d08f24,0x7bc4009f,0x7d199770,0x6d4d02f1}}, // ازم_, criu, _rows, mnaa,
+ {{0xdb1b8548,0x05b1016f,0xdce181b9,0x82371e29}}, // truí, _जबाब, _kulħ, _فرزا,
+ {{0x25a982a5,0x7d19809a,0x6d4d2a1f,0xc5d58a4c}}, // ćalo_, _pows, onaa, філь,
+ {{0x6d4d2a20,0x27ff826c,0xe8d9001c,0xa3e10072}}, // nnaa, _trun_, _khổ_, _दहा_,
+ {{0x47d58013,0x3f85aa21,0x68eb8bda,0x6ece016f}}, // _زيار, _tilu_, megd, _तालु,
+ {{0x81c200c8,0x29092a22,0x80d9800c,0x273080ff}}, // ্বর_, _anaa_, फ्रे, _ràng_,
+ {{0x68f62a23,0x13e600c8,0x39422a24,0x1dc68424}}, // _payd, নিয়, tiks_, रूआत,
+ {{0xd7d1aa25,0x254580eb,0x68f60085,0xe7d181a2}}, // _समाच, tēlu_, _qayd, _समाप,
+ {{0x636b080a,0x39422a26,0x69c501ec,0x7bc98036}}, // tünl, riks_, hrhe, queu,
+ {{0x27308028,0x68f60079,0x6d4d2a27,0x6569805f}}, // _vàng_, _wayd, enaa, lleh,
+ {{0x212d848f,0x4ac700d4,0xa91d8bcf,0x63700106}}, // _oleh_, _लाजव, _nižo, jäna,
+ {{0x6d4d2a28,0x273080ff,0xadf08074,0x798e018f}}, // gnaa, _tàng_, _अईसन_, _rubw,
+ {{0xd467067c,0x04432118,0x3b8601e8,0x6b8700f3}}, // [1880] дите_, ветн, длаг, _bijg,
+ {{0x63b60aa2,0x212d8118,0xdcfd00eb,0xd7760b53}}, // rsyn, _aleh_, _visā, _تابع,
+ {{0x212d83ac,0x2eaa016f,0xfe7201a8,0x63b62a29}}, // _bleh_, करीत, _جدة_, ssyn,
+ {{0xeb9f0aa2,0x7bc413bf,0x2d5701b9,0x53a90072}}, // _brød_, priu, għed_, _चौकश,
+ {{0x69c503b2,0x6f1aaa2a,0x9f4907f1,0xd84f019d}}, // arhe, _potc, _graó_, lọra_,
+ {{0x212d80b9,0xdd9a9577,0x2d868114,0xa91d82d4}}, // _eleh_, оши_, _sioe_, _fižo,
+ {{0x66e00074,0xdce4016b,0x80cf001b,0xa3b0aa2b}}, // ख्यक_, hlič, _थाले, टीन_,
+ {{0x6ba5016d,0xdce4007a,0x9c1381bc,0xe73f041c}}, // _åtgä, klič, _nọsi, mpõe_,
+ {{0xf8639485,0x7c242a2c,0xd8d78158,0xa3a98072}}, // _авто, nyir, _קומט_, गील_,
+ {{0x61260125,0x394002be,0x7f4381e8,0xdce4016b}}, // _fólk, _amis_, cinq, dlič,
+ {{0x8e4a80f7,0x656981e0,0x28f92a2d,0xa2cf8cf0}}, // _بلاك_, bleh, пень_, _दार्,
+ {{0xf3888028,0x212d80ee,0xfd4f81bc,0x0c26802e}}, // _lợi_, _xleh_, _mejọ, _омен,
+ {{0x1bf289a3,0xdce4026f,0x60dd007b,0x28d1a0d5}}, // _अनिल_, glič, rfsm, _हासि,
+ {{0xf53f13c2,0x399383a7,0x4424809a,0x636f8aa2}}, // _små_, _fãs_, mym_, lønn,
+ {{0x1df811e9,0x4424aa2e,0x569402d3,0x6d4d2a2f}}, // меры_, lym_, таст, tnaa,
+ {{0xdce41024,0x7af8aa30,0xa91d8988,0xe8d9001c}}, // blič, _havt, _rižo, _phổ_,
+ {{0x4424aa31,0x4ea7143d,0x6d4d2a32,0x7af881c0}}, // [1890] nym_, _орна, rnaa, _kavt,
+ {{0xdb1606c0,0x656081a8,0x2fc6aa33,0x2d9e806a}}, // nsyè, homh, drog_, _otte_,
+ {{0x69da8009,0x212d81f4,0x69cf8035,0x29d88866}}, // ntte, _pleh_, ąceg, néa_,
+ {{0x69da8364,0x3cfe80d4,0x7c2400b4,0xe8d900ff}}, // itte, _शोले_, byir, _thổ_,
+ {{0x7feb87bd,0x93270b76,0x68ebaa34,0x6370089c}}, // تراف_, _قرآن, regd, männ,
+ {{0x4424831d,0x7d1d0019,0x7af8aa35,0x7df38085}}, // dym_, _hoss, _navt, _həsə,
+ {{0x628301ac,0xf388801c,0x212d82d4,0x7f43811c}}, // ľnos, _gợi_, _tleh_, rinq,
+ {{0x6283105e,0x2fc6805c,0x7d1d0364,0x98a01487}}, // žnos, brog_, _joss, _ilić_,
+ {{0x7d1d2a36,0x7df38201,0x2d9e8006,0x3d048540}}, // _moss, _məsə, _ette_, वाजे_,
+ {{0x8c9f80d4,0x3f9f808e,0x7af88115,0x6569aa37}}, // _ग्रो, _ituu_, _cavt, sleh,
+ {{0xa96a18ba,0xcb1204de,0x63701af3,0x38690b8e}}, // зина_, אלי_, känn, _ayar_,
+ {{0x7d1d0003,0xfe6e803d,0xdce4007a,0x4424939f}}, // _noss, _اگه_, tlič, bym_,
+ {{0xb4278c2a,0x4424809a,0xdce40289,0x7d0b80d7}}, // _تعاو, cym_, ulič, _ings,
+ {{0xdb1b8e14,0x75298019,0x798981b0,0x8c1a825f}}, // bruá, lkez, _liew, _מוני,
+ {{0x7bdbaa38,0xdd920277,0x3d04816f,0x6146031a}}, // ltuu, اور_, वाचे_, _пека,
+ {{0x7989809a,0x7d1d2a39,0xdce41320,0x5ec10264}}, // _niew, _coss, plič, শ্লে,
+ {{0x7d1d2a3a,0x98a0190f,0x7bdba3df,0x68e42a3b}}, // [18a0] _doss, _alić_, ntuu, _ibid,
+ {{0x7d0b82f7,0x3ced822c,0x425623d7,0x272b8163}}, // _lngs, heev_, нтет, _sønn_,
+ {{0x7d1d1260,0x3cfb0c28,0x3f920010,0xe8d9001c}}, // _foss, _लोके_, _huyu_, _chỗ_,
+ {{0x69c305e4,0x7bdbaa3c,0x2fc68d2f,0x59d206af}}, // ánea, ktuu, trog_, दंबर,
+ {{0x68e40135,0x7f469663,0xd43600f7,0xe73f0187}}, // _mbid, недж, _أعجب, spõe_,
+ {{0x69daaa3d,0xa8478b53,0x442484e8,0x57f38eef}}, // ytte, علوم_, vym_, упшт,
+ {{0x44248063,0x61262a3e,0x68e42a3f,0x63a42a40}}, // wym_, _eóli, _obid, lpin,
+ {{0x612603b0,0x7d1d01df,0x68e42a41,0x98a92a42}}, // _fóli, _xoss, _nbid, _plač_,
+ {{0x3946aa43,0x27340106,0x63a42a44,0x849780f7}}, // mios_, _känd_, npin, رئيس_,
+ {{0x656086be,0x39468196,0x68e42a45,0x3075aa46}}, // somh, lios_, _abid, _русс,
+ {{0xc3331dce,0x19590a14,0x2bc29244,0xfbc2aa47}}, // רות_, даны_, _शिवा, _शिवम,
+ {{0x3946aa48,0x69daaa49,0x98a900e1,0x6374823e}}, // nios_, rtte, _tlač_, fànc,
+ {{0x7d1d2a4a,0x629c00f1,0x7bdb80f3,0x628e04be}}, // _ross, _ofro, ctuu, _ogbo,
+ {{0x68e40146,0x6566008e,0x999e8019,0x254c81d0}}, // _ebid, _akkh, sztő_, děli_,
+ {{0x7d1d2a4b,0x39468110,0x7d0baa4c,0x80a21b7e}}, // _poss, kios_, _yngs, _क्वे,
+ {{0x9d180fe7,0x59c5800c,0xc4cb06b7,0x628e18b2}}, // ност_, _वितर, िलेख, _agbo,
+ {{0x39469984,0xeb93030f,0xf2c704ad,0xdb0d03a7}}, // [18b0] dios_, اظر_, есен, nsaç,
+ {{0x69c882d8,0xdb0d2868,0x7989a180,0xd011804e}}, // orde, trañ, _siew, یلا_,
+ {{0xa2d88b6f,0x2004aa4d,0xa7fd8019,0x3946aa4e}}, // _मान्, _armi_, _gyűj, fios_,
+ {{0x68fd2a4f,0x3946aa50,0x69c88635,0x628e0135}}, // ndsd, gios_, irde, _egbo,
+ {{0x79898051,0x2ca91e7a,0x69c8aa51,0x80098019}}, // _view, mbad_, hrde, براہ_,
+ {{0x2d8b0264,0x2ca90bb1,0x3669a09a,0xb87b0144}}, // _hice_, lbad_, мало_, leís,
+ {{0x75298065,0x8d558729,0x7d0b826c,0xed59a45b}}, // tkez, ктич, _pngs, мок_,
+ {{0x394690dd,0x7bdb8198,0x68fd2a52,0x2ca92a53}}, // cios_, ttuu, jdsd, nbad_,
+ {{0x80a22a54,0x7529aa55,0x69c880c9,0x2d932a56}}, // _क्षे, rkez, erde, _luxe_,
+ {{0x2d8b003b,0x7bdbaa57,0x3eb32a58,0x69ca03a7}}, // _lice_, rtuu, _next_, áfeg,
+ {{0x7bdb8f1a,0x81df00c8,0x68fbaa59,0x27e904a7}}, // stuu, তিক_, _kaud, _apan_,
+ {{0x61460b9c,0x2eaa04c5,0xfeac877f,0x63748722}}, // тема, कर्त, _iṣẹ_, tànc,
+ {{0x69c89699,0x656d026f,0x20042a5a,0x68fbaa5b}}, // arde, dlah, ími_, _maud,
+ {{0x2d8b0083,0x27e900dd,0x80a20701,0x3946aa5c}}, // _aice_, _dpan_, _क्रे, zios_,
+ {{0xa3d4035a,0x63bd0186,0x2d8b2a5d,0x1659245b}}, // सून_, _avsn, _bice_, ерть_,
+ {{0x2be5959a,0x291f802e,0x68fbaa5e,0x394684c3}}, // _कहां_, _noua_, _naud, xios_,
+ {{0x3cf4881f,0xb87b07e0,0x63bbaa5f,0x3946a581}}, // [18c0] ्यते_, rfíc, nsun, vios_,
+ {{0x63a42a60,0x6f1e02ce,0x5fb70039,0x81e480ab}}, // rpin, _uopc, _שהיא_, নির_,
+ {{0x39468511,0x63a42a61,0x68fb9482,0x2d8b0041}}, // tios_, spin, _baud, _fice_,
+ {{0x63a42a62,0x3eb81be3,0xd5b80029,0xc7c69d79}}, // ppin, lart_, šās_, вски,
+ {{0x3946aa63,0x68fbaa64,0x291f8012,0x2d802a65}}, // rios_, _daud, _doua_, mmie_,
+ {{0x394681e2,0x63bbaa66,0xfaa31957,0x2d8b0087}}, // sios_, dsun, _варо, _zice_,
+ {{0x39469f5c,0x41aa0221,0x68fb80e7,0x7afc0af6}}, // pios_, _іван_, _faud, _iart,
+ {{0x7afc2a67,0x29dc040e,0x68fbaa68,0x3eb80046}}, // _hart, mía_, _gaud, hart_,
+ {{0x29dc2868,0x63bbaa69,0x06b800ab,0x3eb82a6a}}, // lía_, gsun, _জানি, kart_,
+ {{0x68fbaa6b,0x7afc0102,0x77638118,0xc1808135}}, // _zaud, _jart, conx, _ịghọ,
+ {{0x27e901b0,0x29dc040e,0x69c880c9,0x7c3a0201}}, // _span_, nía_, urde, _ətra,
+ {{0xdca6902f,0x7afc020f,0xf8bf0019,0x64a69957}}, // _ради, _lart, mkék_, _рада,
+ {{0xb8eb0076,0xdb04040e,0xb4ae9d7c,0x29dc2a6c}}, // _लय_, nsió, करी_, hía_,
+ {{0x0ccb0b85,0xcf9380be,0x63a282b8,0x3eb82a6d}}, // िल्म, יטש_, _iton, gart_,
+ {{0xd5b80be1,0x80d3800f,0x29dc01ca,0xb8d90264}}, // кст_, _डाले, jía_, _চা_,
+ {{0x29dc02ba,0xa3e6816f,0xdb1b8073,0x7afc2a6e}}, // día_, _पहा_, truç, _aart,
+ {{0x3eb32a6f,0x3eb80502,0x5c750b01,0x656d0057}}, // [18d0] _text_, bart_, улат, rlah,
+ {{0x29dc040e,0x7afc2a70,0xe4e281c4,0x69de1c33}}, // fía_, _cart, क्ति_, ftpe,
+ {{0x9a872a71,0x29dc0c15,0x2d8b0025,0xaca301bc}}, // туал, gía_, _tice_, _alụb,
+ {{0x63a2aa72,0x59c59834,0x7afc167c,0x23c2825a}}, // _oton, _विसर, _eart, _शिंद,
+ {{0xb8db8424,0x3f8c81b9,0x69dc2a73,0x63a28542}}, // _आज_, _bidu_, _eqre, _nton,
+ {{0x7afc2a74,0x29dc040e,0x44292a75,0x543a00be}}, // _gart, bía_, mya_, _טערא,
+ {{0x29dc040e,0x63a2aa76,0x74d78035,0x02af2076}}, // cía_, _aton, _यादृ, टरीन,
+ {{0x7afc2a77,0x7bc9aa78,0x16b00105,0x752d01f6}}, // _zart, treu, _जज्ब, nkaz,
+ {{0xa855a796,0x7bc980e7,0x0ed300d4,0xbab581bb}}, // _скач, ureu, _तांड, лёны,
+ {{0x63bbaa79,0x89a9891c,0x386daa7a,0x7bc9aa7b}}, // rsun, нков_, _iyer_, rreu,
+ {{0x63bbaa7c,0x44292a7d,0x3eb82a7e,0x63a2aa7f}}, // ssun, hya_, vart_, _eton,
+ {{0x7fd605e9,0x44292a80,0x7bc9802e,0x798d02f7}}, // лігі, kya_, preu, _ciaw,
+ {{0xa2e587b6,0x59c594a7,0x6721aa81,0x44290578}}, // _болд, _विवर, _kolj, jya_,
+ {{0x3cfd81c0,0x7d1baa82,0x6d49aa83,0xa03a83de}}, // _hawv_, ljus, liea, געשפ,
+ {{0xceb29a0f,0xa2d88913,0x29dc07f4,0x3cfd81c0}}, // מים_, _मात्, xía_, _kawv_,
+ {{0x7afc2a84,0xb4ce2a85,0x29dc040e,0xb4c00c33}}, // _sart, रले_, vía_, ीले_,
+ {{0x7c29aa86,0x7c3baa87,0x44292a88,0x3eb81a1f}}, // [18e0] nyer, nzur, gya_, part_,
+ {{0x57fb0051,0x3cfd90af,0x7d1b8006,0x7afc00f1}}, // _טלפו, _lawv_, hjus, _qart,
+ {{0x7afc0b71,0x386d85b4,0xf77200d5,0xdce28743}}, // _vart, _ayer_, _پاک_, _skoč,
+ {{0x29dc161b,0x7afc2a89,0x44292a8a,0x3cfd8069}}, // ría_, _wart, bya_, _nawv_,
+ {{0x7afc2a8b,0x6d460182,0x29dc02ba,0x23658042}}, // _tart, _imka, sía_, kolj_,
+ {{0xa2d88fe4,0xdb04160a,0x29dc2a8c,0x78b60216}}, // _माध्, rsió, pía_, _leyv,
+ {{0xf41f825d,0x7afeaa63,0x672180ce,0x3d8a8198}}, // ään_, _ópti, _dolj, нщин_,
+ {{0xb4ae89a3,0x60c42a8d,0x3cfd8069,0x80d6016f}}, // करे_, _ndim, _cawv_, _भाषे,
+ {{0x7c299fce,0x3cfd81c0,0x6721a909,0x14d5866b}}, // gyer, _dawv_, _folj, _धारण,
+ {{0xa3b585b3,0xef83867c,0x60c42a8e,0x2365911b}}, // _जबर_, _глуп, _adim, golj_,
+ {{0xe6938307,0x6e938013,0x5e938013,0x41551016}}, // _المد, _الما, _المط, ивос,
+ {{0x44292778,0x3cfd81c0,0x7c29a42d,0x67218267}}, // yya_, _gawv_, byer, _zolj,
+ {{0x63a2aa8f,0x752d2a90,0x69c090be,0x386d0366}}, // _uton, vkaz, _विकी, æer_,
+ {{0x88bd809a,0x7bfb8039,0x7dea8085,0x4429004f}}, // _kośc, _נפוצ, _təsd, vya_,
+ {{0x2bc71834,0x4429010c,0xd2b783c8,0x6aba9a20}}, // _लिहा, wya_, _גלות_, matf,
+ {{0x44292a91,0x7982809a,0x06c600ab,0x752d01d6}}, // tya_, jmow, শ্লি, ukaz,
+ {{0x752281c0,0x44290176,0xdb04002a,0x394b2a92}}, // [18f0] _mooz, uya_, esiñ, mics_,
+ {{0xd25b0ab2,0x44292a93,0x394b2a94,0x752d0010}}, // нце_, rya_, lics_, skaz,
+ {{0x3207026f,0xeb9f0edd,0xdb580009,0x7c3b81e8}}, // íny_, _grøn_, уют_, zzur,
+ {{0x20092914,0xb4ae83ca,0x394b2a95,0x7c3b8035}}, // _krai_, करो_, nics_, yzur,
+ {{0xe8d90028,0x06b800ab,0xb9658032,0x3cfd8282}}, // _chứ_, _জাতি, _adìé, _rawv_,
+ {{0x3cfd81e9,0x92940048,0x8cad01d0,0x394b1e44}}, // _sawv_, _гарц, जरको, hics_,
+ {{0x23658353,0x3cfd81c0,0x7bcd008b,0x61ed8b80}}, // volj_, _pawv_, lrau, _ćalo,
+ {{0x20090110,0x7c29aa96,0x7c3baa97,0xf8b986ae}}, // _orai_, tyer, tzur, _उजिय,
+ {{0x75ec0065,0xa3cb0540,0x36d5aa98,0x394b2a99}}, // _közö, रंट_, _возр, dics_,
+ {{0xff538013,0xef678098,0x81e800ab,0x7c298d18}}, // _اخر_, _събо, বির_, ryer,
+ {{0x394b2a9a,0x88bd809a,0xb0b983eb,0x7bcd232a}}, // fics_, _gośc, _उजाग, hrau,
+ {{0x637000f2,0x291d9ae4,0x394b0722,0x7bcd2a9b}}, // tänk, njwa_, gics_, krau,
+ {{0xa0760071,0x273989c4,0x23658b80,0xe7b389ab}}, // рынш, _cèng_, polj_, _ƙaƙƙ,
+ {{0xfaa59abe,0x82a58364,0x7bcd2a9c,0x20092a9d}}, // _тако, _такж, drau, _drai_,
+ {{0x29560698,0x63a9aa9e,0x99d700ab,0x28a721e3}}, // _въпр, lpen, সবুক, _क्वि,
+ {{0xf3888104,0x27edaa9f,0x63a9aaa0,0x2d8f82d6}}, // _hợp_, _open_, open, _jige_,
+ {{0x63a9aaa1,0x2912019d,0x7bcd2aa2,0x290012d0}}, // [1900] npen, _inya_, grau, _iaia_,
+ {{0x2d8faaa3,0x60dbaaa4,0x29122aa5,0x68e9aaa6}}, // _lige_, _acum, _hnya_, _abed,
+ {{0xa2d880d4,0xe9da0604,0x80a68c2a,0xaadd858c}}, // _मास्, тке_, لمان, _मानक,
+ {{0x7bcd2aa7,0xa3e68bb8,0x3be586a7,0xdb04002a}}, // brau, _पहल_, _कहूँ_, rsiñ,
+ {{0xdefa8364,0x7982809a,0x7600aaa8,0xbcfb0091}}, // вый_, rmow, kázá, _akéw,
+ {{0x2d8f8ad0,0xd00f9a37,0x29002aa9,0xe80703eb}}, // _aige_, بله_, _laia_, शिता_,
+ {{0x636b05c5,0xd5b794ef,0x6d560079,0x2d8faaaa}}, // rünt, асы_, onya, _bige_,
+ {{0xe8d90028,0x6d562aab,0xa3ea0327,0x29002aac}}, // _thứ_, nnya, _महा_, _naia_,
+ {{0x8fa39383,0x61e90024,0x63a9aaad,0x2d8f8326}}, // _мате, _ćeli, gpen, _dige_,
+ {{0x29120a2c,0x8ca680d4,0x29020079,0x320a016b}}, // _anya_, _ट्रो, edka_, _krby_,
+ {{0x2bc29199,0x6d562aae,0x29002aaf,0x7522826c}}, // _शिका, knya, _baia_, _tooz,
+ {{0x38c9026a,0xdd8f806b,0x29002ab0,0x5fba86a7}}, // دادی_, _قول_, _caia_, _उबाल,
+ {{0xc05b0163,0xe53b8051,0x2006aab1,0x2912008e}}, // тін_, _בתאר, nvoi_, _dnya_,
+ {{0x28a70aed,0x29122ab2,0x394b2ab3,0x200902be}}, // _क्रि, _enya_, sics_, _vrai_,
+ {{0x68ff2ab4,0x68930077,0x3cdd864a,0x29002ab5}}, // _taqd, _ویدئ, क्के_, _faia_,
+ {{0x7bcd2ab6,0x20090028,0x29000102,0xc0cb0178}}, // trau, _trai_, _gaia_, _руке_,
+ {{0x39490025,0xe4f9aab7,0x3cde8035,0x44980198}}, // [1910] _imas_, ्यति_, _गाने_, рвую_,
+ {{0x27340006,0x6d562ab8,0x60db8087,0xfaa70196}}, // _täna_, anya, _scum, ршан,
+ {{0x81e480ab,0xa3cb11bc,0xa2dd125f,0x63a98102}}, // নিক_, रूज_, _पाठ्, zpen,
+ {{0x2912008e,0x27398036,0x637daab9,0x21248427}}, // _xnya_, _mène_, mène, _comh_,
+ {{0x838711e9,0x2d8faaba,0x98e481a8,0x21248427}}, // _выве, _rige_, تكنو, _domh_,
+ {{0x2d8faabb,0x394901c0,0x7bc0874d,0x2d84862c}}, // _sige_, _lmas_, dsmu, emme_,
+ {{0x80c200c8,0x2cadaabc,0x78bd2abd,0x2d8f806a}}, // ষ্ট্, tbed_, kasv, _pige_,
+ {{0x2608035a,0x28a70a3a,0x68e982ee,0x58d48f27}}, // हिती_, _क्लि, _ubed, _мост,
+ {{0x7c2d2abe,0x40349ccf,0xe29982df,0x2cadaabf}}, // nyar, _детс, лап_, rbed_,
+ {{0x7de7835f,0x39492ac0,0xf5e780e8,0x29002ac1}}, // _відд, _amas_, _відм, _saia_,
+ {{0xa2d8aac2,0xe8d90028,0x291209da,0x7c2d2ac3}}, // _मार्, _chữ_, _pnya_, hyar,
+ {{0x637005ec,0x3cde86a7,0x6d9581d6,0x049581a8}}, // räni, _गाये_, _všad, _الإح,
+ {{0x290001df,0x6c8580f7,0xdced082c,0x7c2d0314}}, // _vaia_, _الشم, jmađ, jyar,
+ {{0x3949059c,0x442daac4,0xdced011c,0x6d50866f}}, // _emas_, mye_, tmağ, ądał,
+ {{0x442daac5,0xe5a60628,0x6f01aac6,0x29120359}}, // lye_, сиби, _malc, _tnya_,
+ {{0x6d4d16d5,0xdced2ac7,0x29122ac8,0xe0da04ae}}, // giaa, rmağ, _unya_, _сви_,
+ {{0x442daac9,0x7c2d0065,0x443faaca,0xec7a16d9}}, // [1920] nye_, gyar, nzu_, упа_,
+ {{0xa2dd1f64,0x8a3a0009,0x7c248014,0x5fd18072}}, // _पाण्, ляет_, _àire, _समजल,
+ {{0xb4f981ab,0xd9108077,0xdce400ce,0x442d8168}}, // ्याय_, ویس_, jlić, hye_,
+ {{0x7c2d29b3,0x6d4d0a0f,0x442daacb,0x69c1aacc}}, // byar, ciaa, kye_, isle,
+ {{0xa2d8908a,0x572509a7,0x291283bf,0x442d8c97}}, // _माल्, _طریق, ıya_, jye_,
+ {{0x7d04103d,0x443f80eb,0x442daacd,0x290088fc}}, // ldis, dzu_, dye_, žia_,
+ {{0xe7f48b85,0xa2dd09c2,0xa3d40540,0x9c13819d}}, // _इनका_, _पात्, सूर_, _gọzi,
+ {{0x7d042ace,0xc0e39d51,0x442d8247,0x7bc68110}}, // ndis, _носк, fye_, škum,
+ {{0x442daacf,0x7d042ad0,0x78a2816b,0x6f01aad1}}, // gye_, idis, mcov, _falc,
+ {{0xdced012b,0x7d040009,0x78a2816b,0xd6d28bbe}}, // vlač, hdis, lcov, _رقص_,
+ {{0x69c1a10f,0x9952001b,0x442daad2,0x78bd2ad3}}, // gsle, lář_, aye_, tasv,
+ {{0x78a2803e,0x442da0d7,0xd25786b5,0x753b8010}}, // ncov, bye_, сць_, chuz,
+ {{0x7d042ad4,0x67250364,0xc8671194,0x506711b1}}, // ddis, _pohj, стни, стна,
+ {{0x6d5a2ad5,0x7bd62ad6,0xd6d80a42,0xa85781c6}}, // étan, duyu, йтс_, מיכה_,
+ {{0xdced0289,0x6d4d2ad7,0x80d88f3d,0x9577003d}}, // slač, tiaa, डलाइ, مدرض,
+ {{0x2d9a2ad8,0xdced0353,0x394903a7,0x7d042ad9}}, // _dupe_, plač, _umas_, gdis,
+ {{0xac18210d,0x241813cd,0x6d4d2ada,0xa3d40c28}}, // [1930] боту_, боты_, riaa, सूल_,
+ {{0x6d4d2adb,0x70d18105,0x7c2d2adc,0x7d02aadd}}, // siaa, हल्ल, ryar, _laos,
+ {{0x7c2d059c,0x637d809f,0x442d83ec,0x4c9520da}}, // syar, lènc, zye_, _минс,
+ {{0x27308104,0x26de82ba,0x442daade,0x7bd6008e}}, // _hành_, _acto_, yye_, buyu,
+ {{0x68ed2957,0xe8d901bc,0x675402e3,0x81e480ab}}, // _mbad, _ajị_, _ذخیر, নিট_,
+ {{0x7a3f8065,0xe7368e17,0x442daadf,0xf6500065}}, // _játé, жеш_, vye_, _آئی_,
+ {{0xf7708077,0x68ed2ae0,0x6f01aae1,0x78a283f2}}, // راه_, _obad, _valc, bcov,
+ {{0x442d8247,0x443f8102,0xa159a17e,0x273080ff}}, // tye_, tzu_, _табу_, _lành_,
+ {{0x63ad2ae2,0x60c9811f,0x4ad4835a,0xe8d92ae3}}, // npan, _idem, _दाखव, _ejị_,
+ {{0x443faae4,0xa1c58676,0x68ed2ae5,0x442daae6}}, // rzu_, обод, _abad, rye_,
+ {{0x442daae7,0x2d5701cd,0x63ad008e,0x5f1d1a46}}, // sye_, għek_, hpan, मान्_,
+ {{0xa3ea09f2,0x63ad2ae8,0x69c18e1c,0x3eba2ae9}}, // _महल_, kpan, rsle, _sept_,
+ {{0x41c583b7,0x637d8722,0x3cde80d4,0x6d4b8197}}, // _विकस, gènc, _गाते_, _imga,
+ {{0x69c380eb,0x2cbfaaea,0x2a8e8085,0x6d5d01a1}}, // _tvne, laud_, bəb_, _ajsa,
+ {{0x27308028,0x7d041849,0x78bb80e7,0x6da3197b}}, // _dành_, tdis, _oeuv, диса,
+ {{0x78bbaaeb,0x2fcd0353,0x0d9917ae,0x95990a95}}, // _neuv, šega_, стры_, стру_,
+ {{0xccf2836b,0x637d809f,0x63ad1341,0x78a2816b}}, // [1940] וכן_, cènc, gpan, vcov,
+ {{0x60c9960a,0x91eda0d8,0x7bd6080a,0x20000870}}, // _adem, _जहाज_, ruyu, _isii_,
+ {{0x6d4baaec,0xe1fa2aed,0x63ad01a8,0x48e10054}}, // _omga, рге_, apan, _कामो_,
+ {{0x7d0402c1,0xa50a1878,0x1d0a2aee,0x200daaef}}, // qdis, _тема_, _теми_, _brei_,
+ {{0x78a2aaf0,0x200d991f,0x60c9aaf1,0x3860811c}}, // rcov, _crei_, _ddem, çir_,
+ {{0x60c9aaf2,0x200d82af,0x7d02aaf3,0x78a2aaf4}}, // _edem, _drei_, _saos, scov,
+ {{0xa3dc825a,0x64490125,0x3f4d026c,0x27ff846d}}, // तून_, _þeir, džul_, _esun_,
+ {{0x200d81ec,0x6374823e,0x7bc40084,0x64408799}}, // _frei_, màni, osiu, tzmi,
+ {{0x81bc0a49,0x7bc42af5,0x741480f7,0x6440812b}}, // _আমি_, nsiu, سودا, uzmi,
+ {{0x290490fe,0x799c02a0,0x637d8722,0x6d4baaf6}}, // _hama_, _kurw, vènc, _emga,
+ {{0x2904aaf7,0x81e800ab,0x13b300ab,0x6abc0197}}, // _kama_, বিক_, টওয়, _merf,
+ {{0x290483c3,0x09cf00c8,0xe8d90028,0x2906aaf8}}, // _jama_, _রহমা, _thử_, ndoa_,
+ {{0x644080b9,0x6abc2af9,0x81b700fc,0x6370062c}}, // qzmi, _oerf, ɓɓuk, näns,
+ {{0x2904a9a9,0x637d87e2,0x6abc2afa,0x26dc8048}}, // _lama_, rènc, _nerf, ngvo_,
+ {{0x56940a7c,0xe8d9001c,0x637d809f,0xdcbb2296}}, // дарт, _chợ_, sènc, ища_,
+ {{0x5c992afb,0xa3ae8b75,0x27e6aafc,0x68ed13e3}}, // ская_, _ओझा_, kton_, _ubad,
+ {{0x6abc103c,0x6370016d,0x63ad2afd,0xdfdb0098}}, // [1950] _berf, jäns, upan, _тъй_,
+ {{0xc7b22afe,0xd3711b9a,0x63ad2aff,0xb2ab096b}}, // _רבי_, رها_, rpan, ртаж_,
+ {{0x6abc0a38,0x78bb82be,0x2904ab00,0x63701a50}}, // _derf, _peuv, _bama_, vänt,
+ {{0x7af72b01,0x2904a827,0x61e52b02,0x644004b7}}, // sext, _cama_, rthl, _żmie,
+ {{0x290480a4,0x01e080c8,0xd009802e,0x251a04de}}, // _dama_, ববিদ, селе_, _הורא,
+ {{0x200d802e,0xe3b200be,0x69d703a8,0x27398176}}, // _vrei_, _אױך_, puxe, _bèna_,
+ {{0x27e6825d,0x67288025,0x2904ab03,0x09af80ab}}, // aton_, _dodj, _fama_, _কিভা,
+ {{0x2904ab04,0x200d802e,0x2ee12417,0x6b9d2b05}}, // _gama_, _trei_, _lchf_, _kusg,
+ {{0xdb1b841c,0x27ff83db,0x1c0e01a2,0x4ea78162}}, // ssuí, _usun_, सियल_, _дреа,
+ {{0x290483c3,0x4a45a7cb,0x6b9d2b06,0x466b8878}}, // _zama_, знов, _musg, _крем_,
+ {{0xe8038c78,0xc86900be,0x69c52b07,0x2904ab08}}, // लिका_, _ען_, nshe, _yama_,
+ {{0x6723ab09,0x7dea8085,0x21290144,0x6d4bab0a}}, // ljnj, _məsl, _coah_, _umga,
+ {{0x88bd809a,0x200f1520,0x246483ed,0x69c52b0b}}, // _rośl, _ágil_, rëmë_, hshe,
+ {{0x69c702ce,0x395f816d,0x26080072,0x2904026c}}, // _ovje, _ljus_, हिली_, žma_,
+ {{0x6b9d0352,0x69c52b0c,0x6b402b0d,0x7dea829a}}, // _ausg, jshe, _högg, _nəsl,
+ {{0xe9a89b9a,0x6d5bab0e,0x2bc713ba,0xc4d60072}}, // _بدون_, nnua, _लिखा, धलेख,
+ {{0x2904ab0f,0x42ca01e5,0x2ef82b10,0xf99301c6}}, // [1960] _rama_, йган_, perf_, _ארה_,
+ {{0x799c2b11,0x7bc42462,0x63748722,0x1ddb8424}}, // _purw, rsiu, tàni, _यमंत,
+ {{0xa2dd2b12,0x6abc2b13,0x4ae2035a,0x67288067}}, // _पार्, _verf, _पाठव, _rodj,
+ {{0x2367173d,0x69c7111b,0x0edc001b,0x27e6ab14}}, // čnja_, _dvje, _बाँड, tton_,
+ {{0x67288f28,0x2904803a,0x2906ab15,0x6abc208d}}, // _podj, _vama_, rdoa_, _terf,
+ {{0x395f86c4,0x63701743,0x2904ab16,0x7d062b17}}, // _ejus_, räns, _wama_, _haks,
+ {{0x2904ab18,0x7d062b19,0x27e6ab1a,0x6728a76e}}, // _tama_, _kaks, ston_, _vodj,
+ {{0x7d062b1b,0x06c600ab,0x27e6ab1c,0x75242b1d}}, // _jaks, শ্চি, pton_, njiz,
+ {{0x7d062b1e,0x69c72b1f,0x0edc047d,0x64440365}}, // _maks, _zvje, _बांड, nzii,
+ {{0x3cde8e18,0x883b8039,0xbec280eb,0x44200722}}, // _गावे_, _התמו, šība, ixi_,
+ {{0x76438063,0x75240590,0x58d512b2,0x26cc831d}}, // czny, kjiz, монт, _iddo_,
+ {{0xf487819f,0x60c2804f,0x3ebea67f,0x44320198}}, // _کامی, naom, _hett_, kyy_,
+ {{0xfc3f2b20,0xa2dd1664,0xfc2fa0bb,0x798409c4}}, // _afín_, _पाल्, _محو_, _dhiw,
+ {{0x3da704fa,0x7d0600e4,0x2d9e8129,0x44200118}}, // зраб, _aaks, _kute_, dxi_,
+ {{0x7afaab21,0x249900b9,0x2d9eab22,0x3ebeab23}}, // mett, _pgsm_, _jute_, _mett_,
+ {{0x3ebeab24,0x69da9c61,0xb4c9000d,0xb4d7024c}}, // _lett_, mute, ैले_, सले_,
+ {{0x69c7030b,0x60cd2b25,0x69daab26,0x2d8909c4}}, // [1970] _svje, _idam, lute, rmae_,
+ {{0x7afaab27,0x69c5029b,0x2bd49094,0x394d822b}}, // nett, tshe, _दिना, _smes_,
+ {{0x26c3007a,0x6d40ab28,0x6aa52b29,0x7afa847f}}, // majo_, khma, rchf, iett,
+ {{0x69c52b2a,0x7afaab2b,0x26c3007a,0xac192b2c}}, // rshe, hett, lajo_, _дому_,
+ {{0x4addab2d,0x7afaa9a4,0x69c52b2e,0xb4db0091}}, // _मालव, kett, sshe, _alàg,
+ {{0x69daab2f,0x2d9eab30,0x26c32b31,0x7afaa452}}, // kute, _bute_, najo_, jett,
+ {{0x69c7030b,0x69daab32,0x41778416,0x7afaab33}}, // _uvje, jute, _فارس, dett,
+ {{0x2d9e80ad,0x69daab34,0x60cd2b35,0xbe3c01c6}}, // _dute_, dute, _ndam, מעות,
+ {{0x7afa81dc,0xb4ad901c,0x387fab36,0x6d5b8102}}, // fett, _कभी_, _uzur_, rnua,
+ {{0x60cd2b37,0x7afaab38,0x6fd2800f,0x26c30db7}}, // _adam, gett, _सितं, jajo_,
+ {{0x2d9e9b36,0x26c30353,0x69daab39,0x7764823e}}, // _gute_, dajo_, gute, éixe,
+ {{0x6d4082af,0x2734016d,0x604301bc,0x7e7eab3a}}, // chma, _tänk_, _ọmaj, üppe,
+ {{0x7d060341,0x442004b7,0x6b40016d,0x7afaab3b}}, // _raks, xxi_, _höge, bett,
+ {{0x7afaab3c,0xdb0b816d,0x6d4f2b3d,0x26c3007a}}, // cett, _avgö, _amca, gajo_,
+ {{0x7d062b3e,0x2d962b3f,0x2d9e02be,0x7d09ab40}}, // _paks, драс, ête_, odes,
+ {{0x44200102,0xe3ba20bf,0x44320198,0x2bd495bc}}, // txi_, жба_, tyy_, _दिमा,
+ {{0x26c31220,0x7dea8201,0x7d06021e,0x73060084}}, // [1980] bajo_, _rəsm, _vaks, мпаз,
+ {{0x7d098009,0x5fd306a7,0x753d0061,0x60c2804f}}, // hdes, _तितल, _elsz, waom,
+ {{0x7d062b41,0x00e6835f,0xf1a50558,0x3ebe821e}}, // _taks, джен, ерін, _rett_,
+ {{0x2d9ea36a,0x3ebe8558,0x7d09ab42,0x3cf98035}}, // _rute_, _sett_, jdes, ्यों_,
+ {{0x2d9e8c7b,0x69daab43,0x7afaab44,0x8fa38cf3}}, // _sute_, zute, yett, ваче,
+ {{0x69daab45,0xeb9f0bc5,0x437500e8,0xb86587c3}}, // yute, _prøv_, _зуст, رانو,
+ {{0x7afaab46,0x629c01dd,0x7d09874c,0x8a031285}}, // vett, _igro, fdes, _изре,
+ {{0x7afaab47,0xdc8e81bc,0x7d09ab48,0x490686a7}}, // wett, _dịnụ_, gdes, _सोचो_,
+ {{0x7afaab49,0x671e89a3,0x3ebeab4a,0x63a42b4b}}, // tett, पादक_, _tett_, nqin,
+ {{0x60c0ab4c,0x48e10076,0xf7729ddd,0x6d40ab4d}}, // _hemm, _काहो_, _شاء_, shma,
+ {{0x7afaab4e,0x60c082a6,0x7d09ab4f,0x26c30353}}, // rett, _kemm, bdes, vajo_,
+ {{0x92be80c8,0x69daab50,0x60c0822b,0x914b0d8e}}, // ইলে_, rute, _jemm, ічна_,
+ {{0xb9072b51,0x7afaab52,0x629c2b53,0x60c0ab54}}, // _पा_, pett, _ogro, _memm,
+ {{0x69daab55,0x60c0ab56,0xb8d60beb,0xdce98503}}, // pute, _lemm, _च्_, tleć,
+ {{0xe5c41285,0x26c32b57,0x2d86801c,0xf2068098}}, // ксто, rajo_, _khoe_, _цяло,
+ {{0x60c0ab58,0x9d180081,0xab5d8035,0xd7fb2b59}}, // _nemm, мост_, duży, _дуо_,
+ {{0xdce982a5,0x63adab5a,0x2139008e,0x21268da8}}, // [1990] sleć, _çant, mksh_, djoh_,
+ {{0x32110063,0x68fd2b5b,0xdb098187,0x629c0362}}, // _przy_, lesd, mpeã, _cgro,
+ {{0xe8d90104,0xe8070c78,0xd25b0746,0x7d098009}}, // _chủ_, शिका_, цца_, ydes,
+ {{0xa2d8853e,0x6562820f,0x60c0ab5c,0x290b0162}}, // _माझ्, _njoh, _cemm, ndca_,
+ {{0x7ac7013a,0x60c0ab5d,0xdce40214,0x6f08810c}}, // _осве, _demm, lliğ, _badc,
+ {{0x26c10775,0xaadd8996,0xdc1480c8,0x6d9101ac}}, // _jeho_, _माइक, িহাস_, _sťah,
+ {{0x60c0ab5e,0x58d58293,0x3d108105,0x2d992b5f}}, // _femm, _повт, _दोहे_, _kise_,
+ {{0x60c0ab60,0x69c8ab61,0x28db2b62,0x7d098198}}, // _gemm, dsde, _भागि, udes,
+ {{0x2d992b63,0x47d58013,0x7d09ab64,0x6d5a02be}}, // _mise_, _سيار, rdes, étai,
+ {{0x4225838b,0x26c100e1,0x2d992b65,0xdce402d0}}, // едов, _neho_, _lise_, kliğ,
+ {{0x78a4a354,0x4ae21391,0x9f8a8009,0x443980eb}}, // žive, _पासव, töön_, šs_,
+ {{0x2d992b66,0x68fd2b67,0x186a0956,0xa06a02ee}}, // _nise_, gesd, зами_, зама_,
+ {{0x2d8dab68,0x26c102c4,0x98a481a9,0x66052b69}}, // mmee_, _beho_, _jomā_, _ashk,
+ {{0xa3578077,0x613a823e,0x2d992b6a,0xa3d401d0}}, // _تخصص, _aïll, _aise_, संघ_,
+ {{0xe6170554,0xf53f0370,0xa3e2123a,0x2d992b6b}}, // ндр_, _blå_, नून_, _bise_,
+ {{0x6283803a,0x68e42b6c,0x6d95811f,0x2d99117d}}, // _izno, _ucid, _ošam, _cise_,
+ {{0x4ad384e5,0x2d992b6d,0xe7da80c8,0x7bc9ab6e}}, // [19a0] _दयाव, _dise_, _ধন্য, nseu,
+ {{0x60c0ab6f,0x394001c5,0xe8d9001c,0xd83f05b9}}, // _semm, _hlis_, _phủ_, íček_,
+ {{0xdebb010f,0x53368158,0xda1f8ebf,0x60c0808e}}, // _ממיל, ענען_, यमित_, _pemm,
+ {{0xe1ff809a,0xeb9a2b70,0x5976803d,0x7bd58035}}, // łów_, _ним_, رداز, ązuj,
+ {{0x6d562b71,0x7bcfab72,0x63a4011c,0xed50803d}}, // miya, ácul, qqin, یپت_,
+ {{0x6d562b73,0x629c003b,0xe8d92b74,0x9e068087}}, // liya, _ugro, _thủ_, ечил,
+ {{0x7bcbab75,0xe3630f2e,0xceb30051,0x60c0ab76}}, // _avgu, _скри, ניה_, _temm,
+ {{0xe2970bac,0x6d562b77,0x60c62b78,0x6d442b79}}, // _час_, niya, jakm, nhia,
+ {{0xe3af84c0,0x60c609da,0x69de2b7a,0xdce402d0}}, // یری_, dakm, lupe, zliğ,
+ {{0x6d562b7b,0xfd1f0028,0x39402b7c,0x3f9a0573}}, // hiya, _nhìn_, _alis_, _nipu_,
+ {{0x6d562b7d,0x6d440590,0x4dd6ab7e,0x68fd2b7f}}, // kiya, khia, _ستاس, tesd,
+ {{0xfc668098,0x6d562b80,0x88bd809a,0x68e2ab81}}, // _пълн, jiya, _dośw, ngod,
+ {{0x6d562b82,0x54338154,0xb4bc801b,0x557780be}}, // diya, _مرور, अरी_, _װעגן_,
+ {{0x2d992b83,0xb9c68013,0xae0d0076,0xa3ea01ce}}, // _sise_, _كتبه, हिलन_, _महज_,
+ {{0x2d990052,0x63a2ab84,0xe8d90a2c,0x6d562b85}}, // _pise_, _huon, _ahụ_, fiya,
+ {{0x63a280f6,0x6d562b86,0x6d4414ed,0xdce40214}}, // _kuon, giya, ghia, rliğ,
+ {{0x2ca901b4,0x63a28198,0x26c12b87,0x656094f2}}, // [19b0] rcad_, _juon, _teho_, inmh,
+ {{0xaa671519,0x63a2ab88,0x5f950eef,0x3a2b027d}}, // нтек, _muon, _шипт, _đểđă,
+ {{0x6d562b89,0x63a2ab8a,0xda6700f7,0xcad781c6}}, // biya, _luon, رائي, תובת_,
+ {{0x6d442b8b,0xa2dd0f8d,0x6d561cee,0x637d8247}}, // chia, _पाक्, ciya, vènm,
+ {{0x7c8418f6,0x63a2965a,0xdee5951a,0x7d0d008b}}, // _буре, _nuon, воли, mdas,
+ {{0x98a90267,0xbec280eb,0x7d0d2b8c,0x68e28118}}, // _mlađ_, šīna, ldas, agod,
+ {{0xae14023c,0x7bdf0388,0xc1790110,0x7764802a}}, // डियन_, luqu, ntės_, éixa,
+ {{0x63a2ab8d,0x7d0d2b8e,0x2734016d,0xacbb00e7}}, // _buon, ndas, _hänt_, _goût,
+ {{0x2d8d8b3c,0x27340106,0x6b400106,0x63a280ff}}, // rmee_, _känt_, _höga, _cuon,
+ {{0x6d562b8f,0x7bc9ab90,0x63a2ab91,0x78ad0722}}, // ziya, rseu, _duon, _afav,
+ {{0x216a2061,0xa96a18b0,0xd9460b01,0x7bc9ab92}}, // дини_, дина_, вени, sseu,
+ {{0x26c7ab93,0x7afe1919,0x8fa62b94,0x6d562b95}}, // lano_, zept, каме, xiya,
+ {{0xeb8ea64c,0xf9928158,0x7d0d2b96,0xa3a8835a}}, // _ви_, ירט_, ddas, _खूप_,
+ {{0x26c7ab97,0x7529a3e3,0x41d120f2,0x9a15ab98}}, // nano_, ljez, _हिंस, _афиш,
+ {{0x6d562b99,0x6d44031d,0xb87b0020,0x65c32b9a}}, // tiya, thia, rgía, ибра,
+ {{0xe8d90028,0x26c7ab9b,0x7d0d2b9c,0xb4dc2a85}}, // _phụ_, hano_, gdas, ठले_,
+ {{0x26c78f45,0x7d0b849f,0x7afe002e,0x644982af}}, // [19c0] kano_, _mags, tept, nzei,
+ {{0x6d4401c5,0x6d562b9d,0x60c40357,0x7c2680f2}}, // shia, siya, _heim, äkri,
+ {{0xb6d90158,0x60c41203,0x7dea8085,0x3ed900be}}, // _אַרט, _keim, _təsi, _אַרא,
+ {{0xa3e201b6,0x7d0b84d2,0x7afe2b9e,0x7989ab9f}}, // नंद_, _nags, sept, _chew,
+ {{0xb90aaba0,0x26c7aba1,0x79898077,0x87e38198}}, // _मा_, fano_, _dhew, ающе,
+ {{0x6a861b2f,0x4add816f,0x3d16020e,0xdbdb808b}}, // _алга, _मागव, _पोते_, _ráðg,
+ {{0x2d9c0065,0x63a2aba2,0x248080eb,0x7d0b95b6}}, // _éves_, _suon, _šim_, _bags,
+ {{0x3d19852a,0x644981ec,0x6b9c01b4,0x6b448176}}, // भागे_, fzei, _hirg, _mòge,
+ {{0x7d0b92fa,0x07a684a9,0x46ea81a1,0x6b9c2ba3}}, // _dags, _разн, _одан_, _kirg,
+ {{0xdced003b,0x39468073,0x63a2aba4,0x7d0d0019}}, // plać, lhos_, _vuon, zdas,
+ {{0x2bd48f85,0x53d4816f,0x7d0d07c0,0x7d0baba5}}, // _दिवा, _दिवश, ydas, _fags,
+ {{0x394680a9,0xdd968277,0x63a2aba6,0xa9210085}}, // nhos_, _شجاع, _tuon, _şöbə,
+ {{0x93468e97,0x14d7812a,0xf7730c2a,0xdb098013}}, // _инде, _יואל_, لاس_, speá,
+ {{0x69d300d4,0x5333aba7,0x6b9c2ba8,0xf09381c6}}, // _डिली, _вешт, _nirg, ינר_,
+ {{0x752f0063,0xa4d5035f,0x7e86801b,0x2fc02ba9}}, // _pocz, лові, _úspě, _ewig_,
+ {{0xb4cd016f,0x60c42baa,0xe7d280d4,0xd7d2970c}}, // रणे_, _geim, _सिंप, _सिंच,
+ {{0x65940a4c,0x24580364,0x7d0d2bab,0xdd940084}}, // [19d0] рату, вать_, rdas, раты,
+ {{0x26c79041,0x7d0d01e0,0x7cde0087,0x79899c80}}, // xano_, sdas, tărâ, _shew,
+ {{0x26c7abac,0x6b9c2bad,0xfaa40f27,0xe659002e}}, // vano_, _dirg, ршун, _чинч_,
+ {{0x69da808c,0x26c7abae,0x06d800ab,0x7d009b77}}, // irte, wano_, দ্দি, jems,
+ {{0x26c7abaf,0x7d0080d2,0x3869abb0,0x69da81ec}}, // tano_, dems, çar_, hrte,
+ {{0x386981cd,0x6b9c2bb1,0xa03c0039,0x7d0babb2}}, // ħar_, _girg, ועדפ, _sags,
+ {{0x78a48025,0x7d0b97ef,0xa3a88b85,0xa2b8801b}}, // živa, _pags, _खूब_, ोरन्,
+ {{0xf3ffabb3,0x3946abb4,0xb4ad835a,0x97c58013}}, // ção_, chos_, कडे_, _مقرو,
+ {{0xeb998db4,0x26c7abb5,0x69daabb6,0x290dabb7}}, // нии_, pano_, erte, _iaea_,
+ {{0x64498352,0x4add8023,0x60c42bb8,0x753b801b}}, // rzei, _माओव, _seim, skuz,
+ {{0x7d0babb9,0x52d000c8,0x2bc486b7,0x6449abba}}, // _tags, স্কৃ, लीबा, szei,
+ {{0xe4cb803d,0x26c59920,0xb87b2bbb,0x2e3a80be}}, // _آبان_, _helo_, rgín, _אגענ,
+ {{0x69daabbc,0xb4df816f,0xdb0f0511,0xfd1f00ff}}, // arte, तली_, _etcé, _chìm_,
+ {{0x25a581cd,0xe8e081d0,0x26c5805c,0x200900ee}}, // _kull_, _पञ्च, _jelo_, _dsai_,
+ {{0x26c5abbd,0x60c42bbe,0x16098aed,0x68e98904}}, // _melo_, _teim, विटर_, _oced,
+ {{0x2d8b09a4,0x2cad8039,0xf8a80105,0x61e1abbf}}, // _chce_, nced_, _गलिय, mull,
+ {{0x61e1898c,0xc9530051,0x7bcd1849,0xac860d45}}, // [19e0] lull, _למה_, gsau, угал,
+ {{0x7d00a1d1,0x29022bc0,0x491c000d,0x68e989b2}}, // zems, leka_, याको_, _aced,
+ {{0x61e1abc1,0xe0da94d6,0xaca401bc,0x248600e1}}, // null, еве_, _alụs, ťom_,
+ {{0x29022bc2,0x69ce9d3a,0x9fca8009,0x26ca2bc3}}, // neka_, ábei, егда_, labo_,
+ {{0x6b9c0029,0x26c5abc4,0x7e7e2bc5,0x2d802bc6}}, // _tirg, _belo_, _hypp, mlie_,
+ {{0x61e1820f,0x26c5a354,0x26ca2bc7,0x643b00be}}, // kull, _celo_, nabo_, _טעכנ,
+ {{0x21200025,0x26c5abc8,0x394681e0,0x63a60267}}, // _onih_, _delo_, phos_, _hukn,
+ {{0x9d220a49,0x29020499,0x61e1abc9,0xa534802e}}, // _নতুন_, jeka_, dull, рнич,
+ {{0x7866abca,0x26c5abcb,0x26ca1486,0x7d00abcc}}, // _сказ, _felo_, kabo_, rems,
+ {{0x25a5abcd,0x69da807a,0x3d19816f,0x26c5abce}}, // _full_, trte, _मोठे_, _gelo_,
+ {{0x61e1abcf,0x63a62bd0,0x037701c6,0x26ca2bd1}}, // gull, _lukn, רתית_, dabo_,
+ {{0x26c58db7,0x613f13de,0x7dea8201,0x29020555}}, // _zelo_, _gëll, _dəst, geka_,
+ {{0x3f9eabd2,0x54368bbe,0x63a602c4,0x26c5abd3}}, // _kitu_, _حرار, _nukn, _yelo_,
+ {{0x26ca2bd4,0x3f9e800e,0xa29485e9,0x26c58118}}, // gabo_, _jitu_, _калі, _xelo_,
+ {{0x1df801bb,0x2902005d,0x61e1abd5,0x3f9e8074}}, // леры_, beka_, cull, _mitu_,
+ {{0x2d800b91,0x20090041,0x7bcd2bd6,0x29022bd7}}, // glie_, _tsai_, tsau, ceka_,
+ {{0x20092bd8,0x5c990a14,0x36d5242e,0x3b551b2f}}, // [19f0] _usai_, ткая_, _копр, акар,
+ {{0xd4982bd9,0x7bcd2bda,0x3ea10122,0x6d5a0585}}, // урс_, rsau, _ught_, étar,
+ {{0x7bcd2bdb,0x798d2bdc,0x2d800de0,0x7dea8085}}, // ssau, _khaw, blie_, _xəst,
+ {{0x26c58503,0x30158012,0x98c780f7,0x25a5abdd}}, // _selo_, идер, اغان, _rull_,
+ {{0xf7719301,0x26c58003,0x25a58098,0xd0098012}}, // يات_, _pelo_, _sull_, теле_,
+ {{0x290220b3,0x3f9e80eb,0x61e1abde,0xddc88084}}, // zeka_, _citu_, yull, _dydž,
+ {{0x6b400352,0x3f9e80ad,0xed5983fb,0x26c5abdf}}, // _mögl, _ditu_, tiž_, _velo_,
+ {{0xd6580051,0xb4df8b86,0x613f00f1,0x61e1a450}}, // טיות_, तले_, _qëll, vull,
+ {{0xa3e591bc,0xa3dc8076,0x934590ff,0x26c59117}}, // बंध_, तंग_, иние, _telo_,
+ {{0x61e1abe0,0x2902022e,0x3f9e8877,0x44292be1}}, // tull, weka_, _gitu_, ixa_,
+ {{0x29022be2,0x69261b67,0x7c42811f,0x656401b4}}, // teka_, амна, _čvrš, ynih,
+ {{0x273d0104,0x798d01c5,0x5c7591c7,0x60cb97ea}}, // _hình_, _chaw, _клет, nagm,
+ {{0x29022be3,0x798d2be4,0x6d498ad0,0x26ca2be5}}, // reka_, _dhaw, mhea, tabo_,
+ {{0x61e1abe6,0x29021e98,0x63a62be7,0x644d00dd}}, // pull, seka_, _rukn, dzai,
+ {{0x273d0104,0x26ca2be8,0x63a62be9,0x3f8400eb}}, // _mình_, rabo_, _sukn, ēmu_,
+ {{0x20560d31,0x63a62bea,0x26ca0f8e,0x59da86ae}}, // _втор, _pukn, sabo_, _भितर,
+ {{0x6f03ab42,0x7c3b9437,0x78a92944,0x7c298980}}, // [1a00] kenc, nyur, ževc, nxer,
+ {{0x6f03abeb,0xdcfb811f,0x973c80fe,0x6d958084}}, // jenc, dluč, _kiće, _išau,
+ {{0x6f03abec,0x6edb0039,0xa3e5a6ee,0x2d801a1f}}, // denc, _בחיפ, बूत_, plie_,
+ {{0xebe6119d,0xaae2035a,0x3f9e835e,0xdb060168}}, // _комп, _पाकक, _situ_, _kukë,
+ {{0x6d498782,0x273d0104,0x7d042bed,0xe81911bc}}, // dhea, _bình_, meis, दिया_,
+ {{0x7d042bee,0x2612abef,0x69c38039,0xed5900e1}}, // leis, थिली_, _owne, ďže_,
+ {{0x3f9e8010,0x6fd280d4,0x645d808b,0xbd6a898d}}, // _vitu_, _सिकं, úsin, крие_,
+ {{0x6d4984bc,0xc0e3a386,0x7d040009,0x2b582bf0}}, // ghea, _моск, neis, _hmrc_,
+ {{0x3f9eabf1,0x6f03abf2,0xdee6abf3,0x798d2bf4}}, // _titu_, benc, _вози, _rhaw,
+ {{0x7d041276,0x798d00a4,0x79828051,0x973c9487}}, // heis, _shaw, llow, _biće,
+ {{0x6d498083,0x7d0407ac,0x6b400106,0x798d1236}}, // bhea, keis, _rögl, _phaw,
+ {{0x6d498782,0x798d022c,0x7bc286c0,0x85568061}}, // chea, _qhaw, _twou, _خیبر_,
+ {{0x58870196,0x7522abf5,0x442904b7,0x3202801b}}, // рына, _inoz, xxa_, ňky_,
+ {{0x6d462bf6,0x6d40abf7,0x973c80c3,0xc05800e8}}, // _alka, lkma, _fiće, рію_,
+ {{0xd378803a,0x798d01e9,0x69c38355,0xdcfbabf8}}, // moć_, _thaw, _gwne, zluč,
+ {{0x929b8051,0x7d042bf9,0x644d00ad,0x44292bfa}}, // _ביות, geis, tzai, txa_,
+ {{0xf8bf2bfb,0xdfd100f7,0xfaa72bfc,0x69d8016b}}, // [1a10] mbé_, _عيد_, ашен, áven,
+ {{0x6d462bfd,0xd378825b,0x44292bfe,0x6f0384c3}}, // _elka, noć_, rxa_, xenc,
+ {{0x3f83003b,0x387f9142,0x7d0f2bff,0x98bf8699}}, // mlju_, _syur_, _tacs, _smuđ_,
+ {{0x6d498362,0xae1a0039,0x6d40ac00,0x3e1603de}}, // xhea, _עורכ, jkma, _פֿיל_,
+ {{0xd3788d26,0x6b818114,0x1b0f00ab,0xf807a240}}, // koć_, yllg, সাবে_, рчен,
+ {{0xd246803f,0x38ad8032,0x7522ac01,0x6d409151}}, // _فن_, _bóró_, _anoz, ekma,
+ {{0x6d49954c,0x6f03ac02,0xed59a53f,0x60cb820d}}, // thea, renc, лок_, pagm,
+ {{0x23672bea,0x7c29ac03,0x61e5005d,0x6f03ac04}}, // čnju_, txer, muhl, senc,
+ {{0x273d0104,0x68fb99a8,0x973c8088,0x63ad2c05}}, // _tình_, _ibud, _siće, nqan,
+ {{0x6aa4817b,0x6d49ac06,0x60c998f0,0x973c811f}}, // _şifr, shea, _heem, _piće,
+ {{0x7d042c07,0xdce9ac08,0x60c9ac09,0x6d4980f5}}, // yeis, sleđ, _keem, phea,
+ {{0x7522826c,0x973c81a1,0xa3e201a2,0xfc339c81}}, // _gnoz, _viće, नूँ_, _وحش_,
+ {{0x442983d3,0x8d748872,0x7d042c0a,0x057480f7}}, // _ça_, _بالا, veis, _بالد,
+ {{0x63bb8dab,0x2cbf86be,0x02398b76,0x7d0402af}}, // mpun, lbud_, _مثبت_, weis,
+ {{0x68fbac0b,0x7d042c0c,0x78a90db7,0x19b59e91}}, // _obud, teis, ževa, _احتج,
+ {{0x60c9ac0d,0x90c61dc7,0x68fb8135,0x2b5800c3}}, // _neem, _убие, _nbud, _smrc_,
+ {{0x3f830a20,0x63bbac0e,0x44260073,0xa3e20beb}}, // [1a20] blju_, npun, ão_, नूं_,
+ {{0x29122c0f,0x7d040406,0xd24f003d,0xe6462c10}}, // _haya_, seis, کنم_, _геоп,
+ {{0x29122c11,0x60c9ac12,0x993a8221,0x2cbf81bf}}, // _kaya_, _beem, ляду_, kbud_,
+ {{0xc6930051,0x2912059c,0x63bbac13,0x68fb8362}}, // _מאת_, _jaya_, kpun, _cbud,
+ {{0x8ff78013,0xe73a8785,0xdfcf80f7,0xbcfb026b}}, // مرور_, лед_, فين_, _ajéw,
+ {{0x6d59ac14,0x68fba0c4,0x63bbac15,0x29122c16}}, // _amwa, _ebud, dpun, _laya_,
+ {{0x60c981c0,0x200dac17,0xceb303c8,0x8f9b0e82}}, // _feem, _esei_, ליג_, ניצי,
+ {{0x29122c18,0x63bba525,0xfc4684e8,0x60c9ac19}}, // _naya_, fpun, šími_, _geem,
+ {{0x63bb900c,0x6d40ac1a,0x61fb01a1,0x2906aaa0}}, // gpun, rkma, _ćuli, meoa_,
+ {{0xdb042c1b,0x68ed14ed,0x29122c1c,0x6d40ac1d}}, // mpió, _scad, _aaya_, skma,
+ {{0x291200a4,0x3f83003a,0x7c248110,0x8e8680f7}}, // _baya_, vlju_, _šird, _الاه,
+ {{0x291200ee,0x60c981c0,0xddcb01b9,0x73e681e2}}, // _caya_, _xeem, ġiżl, _годз,
+ {{0x291203c3,0x637d823e,0x27e6833e,0x3f830bcf}}, // _daya_, tènt, nuon_, tlju_,
+ {{0xc90580c2,0x83030264,0x2e1806c4,0xf8bf0061}}, // _रस्म_, _উচ্চ_, _bčf_, sbé_,
+ {{0x2c748019,0x29122c1e,0x2bc483eb,0x44b601bc}}, // _دیکھ_, _faya_, लीरा, _ọsụ_,
+ {{0x29122c1f,0x52838013,0x78a4920e,0x27e68420}}, // _gaya_, _عليك, živj, kuon_,
+ {{0xfd4f0028,0x994800a0,0x26ce85ee,0x3f830390}}, // [1a30] _khiế, _دليل_, kafo_, plju_,
+ {{0x63ad2c20,0x3949016b,0x60c9ac21,0x27e6ac22}}, // rqan, _hlas_, _seem, duon_,
+ {{0x39492c23,0x56952c24,0x27ff802e,0x29122c25}}, // _klas_, _фант, _spun_, _yaya_,
+ {{0x61e52c26,0x2cbf8338,0x63bbac27,0x399b2c28}}, // ruhl, xbud_, ypun, _būsi_,
+ {{0xb4e501ab,0x63ad2c29,0x6aa90162,0x9f999abe}}, // नली_, qqan, _şefu, авду_,
+ {{0x70b52c2a,0x60c98c2e,0x3949016a,0xfd4f00ff}}, // ंडुल, _weem, _llas_, _nhiế,
+ {{0x60c9ac2b,0x27ff80b9,0x39492c2c,0x2cbfac2d}}, // _teem, _wpun_, _olas_, tbud_,
+ {{0x63bb803a,0x2bd4901c,0x68fbac2e,0x6d4d2c2f}}, // tpun, _दिखा, _ubud, nhaa,
+ {{0x29122c30,0x6fe081fe,0x69da04b8,0x2cbf81a3}}, // _raya_, _नियं, _åter, rbud_,
+ {{0x2912048f,0xfd4f0104,0x6d4d2c31,0x63bbac32}}, // _saya_, _chiế, hhaa, rpun,
+ {{0x63bbac33,0x29120695,0x7cd98019,0x39492c34}}, // spun, _paya_, _نواز_, _blas_,
+ {{0x78a902a5,0x6d598578,0x39492c35,0x7c248a20}}, // ževn, _umwa, _clas_, _šire,
+ {{0x6d4d2c36,0x291215c7,0x224d01d0,0x232984dd}}, // dhaa, _vaya_, _řekl_, _воли_,
+ {{0x39492c37,0x6da62c38,0x6569ac39,0x973c81a1}}, // _elas_, жива, lneh, _kića,
+ {{0x63abac3a,0xa3e58105,0xad5a2c3b,0x29122c3c}}, // _hugn, बूर_, арах_, _taya_,
+ {{0x63a3aabd,0x6569ac3d,0x6d4d2c3e,0x6d430087}}, // _hinn, nneh, ghaa, _înal,
+ {{0x63a3ac3f,0x443fac40,0x442d9daf,0xe5770a18}}, // [1a40] _kinn, nyu_, nxe_, ізу_,
+ {{0x6fe081fe,0x63a3ac41,0x261a81ce,0x7c2480f7}}, // _निबं, _jinn, _मछली_, _áire,
+ {{0x64a60eab,0x443f8397,0x63ab816d,0x2d8c2c42}}, // _мана, hyu_, _lugn, ïdes_,
+ {{0x63a3ac43,0x6d4d2c44,0x443fac45,0xdce4017f}}, // _linn, chaa, kyu_, onič,
+ {{0xee02ac46,0xd70a8162,0x63ab82d5,0xc8cc801b}}, // लब्ध_, инде_, _nugn, ारबट,
+ {{0x1754a45b,0xfaa60703,0xd04e0085,0x501b04de}}, // явля, _фаво, ükəs, יונו,
+ {{0x79460063,0xd946879e,0x6146a7cb,0x6283835f}}, // _równ, _деви, _дева, _nyno,
+ {{0x6d4401e2,0x63abac47,0x7d162c48,0xdb040511}}, // nkia, _bugn, ndys, mpiñ,
+ {{0x63a3a4de,0xdce40052,0x443fac49,0xeb0682c7}}, // _binn, jnič, gyu_, очно,
+ {{0x63a3861f,0x6d5f036a,0x63abac4a,0xba548251}}, // _cinn, ziqa, _dugn, звој,
+ {{0x442d84c3,0x39492c4b,0x62838114,0x6d440198}}, // axe_, _plas_, _cyno, kkia,
+ {{0x63a38125,0x62838114,0x80b5064a,0x2ca682c4}}, // _einn, _dyno, ंडें, _ugod_,
+ {{0x63a39808,0xfd4f0104,0xcb6a80a9,0x61e18118}}, // _finn, _thiế, _каде_, arll,
+ {{0x63a3ac4c,0x628e0b80,0xdced0bcf,0xf48781f9}}, // _ginn, _ozbo, vlađ, _بامی,
+ {{0x26ccac4d,0x6d5f2c4e,0x6d4d044e,0x661e0369}}, // _medo_, tiqa, thaa, _crpk,
+ {{0xb4e52c4f,0x39492c50,0x91b780d5,0x63a3ac51}}, // नले_, _ulas_, _بطور_, _zinn,
+ {{0xe8731125,0x6d4d2c52,0x6d5f0748,0x628e2c53}}, // [1a50] _زندگ, rhaa, riqa, _azbo,
+ {{0x6d4d2c54,0x26cc8267,0xce950098,0x2bdd909b}}, // shaa, _nedo_, _напъ, _मिसा,
+ {{0x60cd059c,0x6cea9344,0xdced0289,0x2fc900e4}}, // _keam, _टांग_, slađ, _hwag_,
+ {{0xdd868065,0x6d5f00b9,0x26d12c55,0x60c2ac56}}, // _سو_, qiqa, lazo_, gbom,
+ {{0xf48419b8,0x880780d5,0x6d5d2c57,0x2be20075}}, // _журн, تظام, _imsa, _पिया,
+ {{0x26d12c58,0x60cd0635,0x26ccac59,0x61e88234}}, // nazo_, _leam, _cedo_, kudl,
+ {{0x63a3ac5a,0xc33203c8,0x6569ac39,0x63ab8079}}, // _rinn, _אום_, tneh, _sugn,
+ {{0x60cd0af6,0x26d1016a,0x442d89cb,0x63abac5b}}, // _neam, hazo_, txe_, _pugn,
+ {{0x6569ac5c,0xb4bb901c,0x399b0029,0xb4ab9a3b}}, // rneh, _अभी_, _jūsu_, _खली_,
+ {{0x399b0029,0xdce42c5d,0x442d84c3,0x6d932c5e}}, // _mūsu_, vnič, rxe_, lçad,
+ {{0x63a3ac5f,0x6d442c60,0xd9100065,0x98b00668}}, // _vinn, ykia, ہیں_, šača_,
+ {{0xdce4003b,0x6d93055a,0x63abac61,0x60cd01a8}}, // tnič, nçad, _tugn, _ceam,
+ {{0x6d4bac62,0x60cd1581,0x661e0253,0xe81e92ee}}, // _ilga, _deam, _srpk, पिया_,
+ {{0x26cc1cef,0x7d099196,0x6d5d2c63,0x61e8ac64}}, // údo_, lees, _amsa, budl,
+ {{0x614623cd,0x65c60098,0xd9461634,0x60dbac65}}, // _нека, ябва, _неки, _odum,
+ {{0x60cd2c66,0x7d09ac67,0x2a3a8039,0xdce41607}}, // _geam, nees, _לעצמ, pnič,
+ {{0x26d12c68,0x6d442c69,0x2900019d,0x2fc90428}}, // [1a60] bazo_, rkia, _ibia_, _gwag_,
+ {{0xe8190540,0x7d098009,0x78a9847f,0x8afc809a}}, // दिरा_, hees, _agev, mięc,
+ {{0x6d4bac6a,0x2366011f,0x7d09ac6b,0x7ae32c6c}}, // _olga, đoj_, kees, ónta,
+ {{0xdb042509,0xfaf88ec3,0x39b797c9,0x26ccac6d}}, // rpiñ, lnīt_, _găse_, _sedo_,
+ {{0x2d821f26,0x98a70a20,0x8afc809a,0x26cc97e9}}, // _ikke_, šića_, nięc, _pedo_,
+ {{0x6d93041c,0x60dbac6e,0x6d4ba0a5,0xa2bb0c9a}}, // açad, _edum, _alga, शुद्,
+ {{0x78ad803a,0x26ccac6f,0x6440a1f5,0x7d09ac70}}, // žava, _vedo_, wymi, fees,
+ {{0x7d09ac71,0x3f878289,0x6440ac72,0x5bb882ff}}, // gees, alnu_, tymi, олия_,
+ {{0x6fb685ff,0x7bd62c73,0x60cd2c74,0x26d12bd7}}, // _رمضا, nsyu, _ream, yazo_,
+ {{0x290010ec,0x60cd2c75,0xff24803d,0x656082a6}}, // _abia_, _seam, _تبری, simh,
+ {{0x628781cd,0xc3330039,0x60cd0074,0x7bd600dd}}, // żjon, תות_, _peam, hsyu,
+ {{0x39468110,0xf8cc850d,0x26d10010,0x7bd6076d}}, // nkos_, ारिय, wazo_, ksyu,
+ {{0x61e8ac76,0x315783c8,0x60cd2c77,0x26d12c78}}, // sudl, ליטן_, _veam, tazo_,
+ {{0xd7f81b69,0x973c8b80,0x2d8205ee,0xe8d904be}}, // чую_, _mićo, _akke_, _imọ_,
+ {{0x50ccac79,0x8afb8051,0x26d12c7a,0xf8cc8a74}}, // ाराष, _להגי, razo_, ाराय,
+ {{0x2d5801a0,0x6b40007b,0xe8d900ff,0x2fc902c4}}, // пись_, _mögu, _thự_, _twag_,
+ {{0x26d1062f,0x6728911b,0xb0c3097d,0x6b40007b}}, // [1a70] pazo_, _ondj, _व्यग, _lögu,
+ {{0x29190267,0xe1ab83db,0x399203a8,0xe9ff8129}}, // ldsa_, _घूँघ, ráse_, _hoại_,
+ {{0x7f3c0039,0xdb0b8687,0x62868035,0x22470061}}, // _לעזו, _utgå, ękow, ánk_,
+ {{0x67288088,0xbcfb0693,0x3946861b,0x6d5d2c7b}}, // _andj, _ejér, gkos_, _umsa,
+ {{0xdb1b85b4,0x24869352,0x29d88028,0x6d932c7c}}, // spué, _nyom_, hĩa_, rçad,
+ {{0xe9ff8104,0x7d09ac7d,0xdb0286a5,0x69d88081}}, // _loại_, wees, _otoñ, _ovve,
+ {{0x7d09ac7e,0xed5981bb,0x290b2c7f,0x673a85f3}}, // tees, жой_, keca_, _dotj,
+ {{0x656d2c80,0x63af007d,0x25de8697,0xe3b9ac81}}, // nnah, _kucn, _गिरी_, оби_,
+ {{0x69d8a35a,0x2bdd835a,0x394dac82,0x290b2c83}}, // _avve, _मिळा, _iles_, deca_,
+ {{0x990608fd,0xe9d98256,0x7d09ac84,0x4a459383}}, // षज्ञ_, око_, sees, днов,
+ {{0x7d09ac85,0x63a700f3,0x779082e3,0xdb0381d0}}, // pees, _mijn, _حیوا, _jiné,
+ {{0x69d803a7,0xe6c601a2,0x6b40016d,0x2ec60c2d}}, // ávei, वर्ज, _högt, वर्त,
+ {{0x7d5680be,0xdb038866,0x69d8ac86,0x656d0df6}}, // _קינד_, _liné, _evve, dnah,
+ {{0x394dac87,0x6265997b,0x7763ac88,0x63a72c89}}, // _lles_, _овла, ninx, _nijn,
+ {{0x8fa31814,0x8afc809a,0x394d808e,0x656d232a}}, // таре, sięc, _oles_, fnah,
+ {{0x19870098,0x63af0bcf,0xa1870081,0x8afc8035}}, // _общи_, _bucn, _обща_, pięc,
+ {{0x7984022e,0x63a70a0f,0x2486822c,0x63af00c3}}, // [1a80] _ikiw, _bijn, _xyom_, _cucn,
+ {{0x394d802e,0x7bd60057,0xdb098876,0x395fac8a}}, // _ales_, rsyu, mpeó, _amus_,
+ {{0xdb03ac8b,0x69c1ac8c,0x6b52013c,0x656d0079}}, // _ciné, nple, _lægg, bnah,
+ {{0x39468110,0x395f802a,0x2004ac8d,0x656d01b4}}, // ukos_, _cmus_, _ppmi_, cnah,
+ {{0x3946ac8e,0x63a700f3,0x6b40007b,0x77638118}}, // rkos_, _fijn, _sögu, finx,
+ {{0x394d8e67,0xd5ba83bc,0x39468612,0xdb03ac8f}}, // _eles_, оси_, skos_, _finé,
+ {{0x44222c90,0x53a700d4,0x394d9699,0x09d800ab}}, // _hrk_, _कूटश, _fles_, _সহকা,
+ {{0xb4e883eb,0x69c19c11,0x63a701ed,0x2d800037}}, // बले_, dple, _zijn, noie_,
+ {{0xd90d8bca,0xa2a203dd,0x64440365,0x25a69139}}, // _دین_, _कृत्, nyii, _viol_,
+ {{0x79842c91,0x5a349071,0x25f68035,0x628f8035}}, // _akiw, енот, _एमपी_, ęcon,
+ {{0x6b4000f2,0x290b2c92,0x656d02a3,0x212900dd}}, // _högs, teca_, ynah, _pnah_,
+ {{0x1c468544,0x78a4936f,0x79960286,0x2d4707b6}}, // _онем, živu, _chyw, _põem_,
+ {{0xb8d705fb,0x6d43002e,0x290b2c93,0x59ce016f}}, // _जल_, _înai, reca_, हीतर,
+ {{0x290b02a5,0x2bb501fe,0x63af011f,0x5ee080ab}}, // seca_, _अंबा, _rucn, প্রে,
+ {{0xd9f281b6,0x27e6ac94,0x290b2c95,0x3da701e2}}, // _अमित_, hron_, peca_, драб,
+ {{0xa3e58076,0x994402bb,0x63af10d3,0x656d2c96}}, // बूक_, mış_, _pucn, unah,
+ {{0x63a71696,0xdb039c81,0x994407c0,0x6f170144}}, // [1a90] _pijn, _siné, lış_, _taxc,
+ {{0x656d2c97,0x442214fb,0xdb038144,0xa3e68128}}, // snah, _drk_, _piné, _बटन_,
+ {{0x27e685a4,0x39920019,0x21f888f9,0x994408c5}}, // eron_, lása_, néh_, nış_,
+ {{0x63a700f3,0x26de833e,0x7c22ac98,0x6b448176}}, // _wijn, _adto_, _oror, _dògi,
+ {{0x7d0d2c99,0x80278013,0x78ad82a5,0x5ed380ab}}, // meas, برام, žavo, _সাবে,
+ {{0xf77381bd,0xae03016f,0x7d02ac9a,0x7d0d2c9b}}, // خاص_, _लहान_, _abos, leas,
+ {{0x753d2233,0x27e6ac9c,0x776388e9,0xc17788ca}}, // _kosz, aron_, rinx, _قدرت,
+ {{0x7643ac9d,0x7d0d2c9e,0x92942c9f,0x7c22aca0}}, // ryny, neas, _расц, _bror,
+ {{0x7996031d,0x76438198,0x99858061,0xf8b28e82}}, // _rhyw, syny, _élő_, _תשמ_,
+ {{0x7d0d2ca1,0x6b4000f2,0x39920019,0x69c1aca2}}, // heas, _högr, dása_, tple,
+ {{0x26c78353,0x7d0d0009,0x7c22a7a3,0xe1f6802e}}, // mbno_, keas, _eror, нгэ_,
+ {{0x361a8039,0x69c1aca3,0xaca38135,0x7c22802a}}, // _מועד, rple, _arụm, _fror,
+ {{0x2d802ca4,0x69c1aca5,0x291e001b,0xdb060c83}}, // voie_, sple, ěta_, _luká,
+ {{0x6d4f11b9,0xcc3a893f,0x7d1b8006,0x6b40007b}}, // _alca, _מענט, ldus, _lögr,
+ {{0x236580f2,0xd0408201,0x7d0d1995,0xd5b21a00}}, // milj_, _demə, feas, افر_,
+ {{0x7d1b8665,0xdb0985e4,0x7984004f,0x7d198326}}, // ndus, mpeñ, _ukiw, _jaws,
+ {{0x03260d9e,0x6b52013c,0xec770012,0x490000c2}}, // [1aa0] ндан, _læge, епт_, ष्णो_,
+ {{0xaca4019d,0xb4db0362,0x44220d55,0x27e69532}}, // _nzụt, _blàs, _vrk_, vron_,
+ {{0x00e694b8,0xb4db023e,0x2d73025b,0x7d0d2ca6}}, // ежен, _clàs, mćen_, beas,
+ {{0x27e6aca7,0x61e30065,0xaca40133,0xe0d29459}}, // tron_, ánla, _azụt, ازا_,
+ {{0x14d70051,0x673e2ca8,0x7d1baca9,0x27e6acaa}}, // _אוכל_, _kopj, ddus, uron_,
+ {{0x7bc42cab,0xf0930158,0x7c24a771,0x27e6acac}}, // mpiu, אנד_, _širo, rron_,
+ {{0x99440201,0x39920019,0x38728087,0x6b6680e7}}, // xış_, zása_, _ţară_, _légè,
+ {{0x78a90024,0xe9a38e63,0xdd9781e5,0x69dc27f5}}, // ževi, _басп, ншы_, _ivre,
+ {{0x3076938f,0x7d19acad,0x6b818352,0x4420009f}}, // _чувс, _daws, folg, nvi_,
+ {{0x994403bf,0x44202cae,0xf063acaf,0x973c81a1}}, // tış_, ivi_, _скуп, _mićk,
+ {{0x44202cb0,0xdb072cb1,0x2fc01670,0x7d1982ed}}, // hvi_, _dijè, _atig_, _faws,
+ {{0x4420003a,0x39920065,0x99441014,0x613f00f1}}, // kvi_, tása_, rış_, _pëlq,
+ {{0x53b5016f,0xa49b06c0,0x656282c4,0x23658b80}}, // _अंधश, _abòn, _imoh, bilj_,
+ {{0x7d19809a,0x44200088,0x7c2d8904,0xadf489c1}}, // _zaws, dvi_, _šarg, _इमान_,
+ {{0x6f1aacb2,0x753d1b35,0x3ea52cb3,0x7d0d2cb4}}, // _matc, _posz, ält_, teas,
+ {{0x69daacb5,0xdb06026f,0x2bb51d7c,0x3ceb00c2}}, // mste, _ruká, _अंदा, टलें_,
+ {{0x97a7028b,0x69dc1fcb,0xb4db0a2a,0x442001fa}}, // [1ab0] _прол, _avre, _blàr, gvi_,
+ {{0x6f1aacb6,0x7d1b8b80,0x69daa6a1,0x2009008e}}, // _natc, zdus, oste, _ipai_,
+ {{0x64410201,0x69daa7d0,0x44202cb7,0x7d0d2cb8}}, // _əliy, nste, avi_, peas,
+ {{0x69daacb9,0x3f832cba,0xf99f06c0,0x6b818061}}, // iste, loju_, nmè_, zolg,
+ {{0xb90992c7,0x7d19acbb,0x69dc2cbc,0x41748290}}, // _मय_, _raws, _evre, دالس,
+ {{0x65628122,0x2d8686cb,0xdcfb86d3,0xdce9826c}}, // _amoh, _akoe_, rouč, rneč,
+ {{0x6b81acbd,0xc8f58698,0xda0512ee,0x69daacbe}}, // volg, _извъ, _रहित_, jste,
+ {{0x26d300f1,0xab640380,0x387e0035,0x7c2000c3}}, // _lexo_, rmüş, ętrz_, _šmri,
+ {{0x69daacbf,0x81c300ab,0x6d498bc6,0x290facc0}}, // este, _এটি_, rkea, mega_,
+ {{0x26d30207,0x69daacc1,0x290facc2,0x656295d7}}, // _nexo_, fste, lega_, _emoh,
+ {{0x2bd18740,0x2fc023ea,0x28c3016f,0x291b19e7}}, // _दौरा, _stig_, _व्हि, _baqa_,
+ {{0x290f8db7,0x291dacc3,0x81c300c8,0x186a245b}}, // nega_, ndwa_, _এটা_, дами_,
+ {{0x3959020f,0x3de380ab,0x2d8d8748,0x61e8acc4}}, // ërsa_, _মহিল, mlee_, ardl,
+ {{0x290facc5,0xdf15035f,0x2d8dacc6,0x3f8306ec}}, // hega_, льсь, llee_, goju_,
+ {{0x290f8db7,0x5ed400c8,0x26d30118,0x628a8428}}, // kega_, _তাদে, _dexo_, _gyfo,
+ {{0x290facc7,0x44202cc8,0xa2a21299,0x25ab0037}}, // jega_, tvi_, _कृष्, _cicl_,
+ {{0xd4671a19,0xc4d28158,0x290f8006,0x6da30012}}, // [1ac0] вите_, יגן_, dega_, рита,
+ {{0x212d81f1,0x7bdb8009,0x39400362,0x7bc40048}}, // _aneh_, isuu, _hois_, rpiu,
+ {{0x26d800b4,0xab5b02af,0x2d8dacc9,0xe8fa81e2}}, // maro_, _stüc, klee_, дле_,
+ {{0x290facca,0x26d8009c,0x6f1a8aff,0xd7e6902a}}, // gega_, laro_, _satc, віко,
+ {{0xa41c80c8,0x394003d3,0x3166a75d,0x212d807a}}, // _তথ্য_, _mois_, rioz_, _dneh_,
+ {{0x6d932ccb,0x69da92cf,0xbcfb000d,0x26d81aae}}, // nçan, yste, _jmén, naro_,
+ {{0x670423e6,0x6f0307d9,0x69dc0067,0x52750087}}, // श्यक_, _önce, _uvre, _буту,
+ {{0x290f82a5,0x6f1a8039,0xe297186e,0x38c889a7}}, // cega_, _watc, _рас_, _ساری_,
+ {{0xa8878071,0xd175876a,0x98a39d32,0x69daaccc}}, // _айта_, _сыры, _вите, wste,
+ {{0x69da8574,0x39400068,0x5fdc0321,0x26d82ccd}}, // tste, _aois_, _बिजल, jaro_,
+ {{0x394002be,0xe7378f2e,0x69daacce,0x26d82ccf}}, // _bois_, _шеф_, uste, daro_,
+ {{0x69da8faf,0x26d303aa,0x7c2d8668,0xbcfb00e7}}, // rste, _sexo_, _šare, _amén,
+ {{0x39402cd0,0x69da8456,0xc0ab0875,0x50642cd1}}, // _dois_, sste, بادل_, атра,
+ {{0x26d82cd2,0x4426acd3,0x973c8669,0x628aacd4}}, // garo_, _iro_, _bići, _tyfo,
+ {{0x394003d3,0x3f832cd5,0xe8d90870,0x26d301df}}, // _fois_, roju_, _akụ_, _vexo_,
+ {{0x2d84acd6,0x4426acd7,0x63ad81e8,0x6e2505f3}}, // nome_, _kro_, _èanc, _vrhb,
+ {{0x290facd8,0x26d82cd9,0x27e096f2,0x26d3002a}}, // [1ad0] vega_, baro_, šinj_, _texo_,
+ {{0x9952003e,0x26d80314,0x61e30019,0x2d8d80b9}}, // máš_, caro_, ánlo, zlee_,
+ {{0x6d5c81e2,0x290facda,0x02a719b8,0x2d84acdb}}, // _įran, tega_, трам, kome_,
+ {{0x4426acdc,0xe5350009,0x2d848084,0xa6cf8264}}, // _oro_, _семь, jome_, _রাইট,
+ {{0xb4c12cdd,0x4426acde,0x290facdf,0xdee58790}}, // ुरी_, _nro_, rega_, голи,
+ {{0x290f8665,0x6d4d02c1,0x20d18104,0x77848112}}, // sega_, lkaa, ại_, аліз,
+ {{0x7bc2ace0,0x2d84ace1,0x80d280ab,0xfd1181a8}}, // _atou, fome_, _হার্, مجة_,
+ {{0xdb0d160a,0x6d4d1e0a,0x7d1d2ce2,0x2d84ace3}}, // mpañ, nkaa, _jass, gome_,
+ {{0x7d1d291e,0x4426ace4,0x39402ce5,0x98a008ae}}, // _mass, _cro_, _rois_, _inić_,
+ {{0x7d1d2ce6,0x7bdbace7,0x96ca8540,0xdced0024}}, // _lass, rsuu, _स्पॉ, dnač,
+ {{0x4426ace8,0x39402ce9,0x6d4d2cea,0xd9460468}}, // _ero_, _pois_, kkaa, гени,
+ {{0x7d1d2ceb,0x44268428,0x2d848039,0x6d4d2cec}}, // _nass, _fro_, come_, jkaa,
+ {{0xeb8eaced,0x4426acee,0x26d82cef,0x39402cf0}}, // _ги_, _gro_, taro_, _vois_,
+ {{0x973c80fe,0x06e580ab,0x6569acf1,0x59d0801b}}, // _pići, প্রি, lieh, थीहर,
+ {{0x7d1d2cf2,0xfce6210d,0x26d82cf3,0xa50a1ed1}}, // _bass, лово, raro_, нева_,
+ {{0x7d1d2cf4,0x291e0029,0x39990065,0x6d4d2cf5}}, // _cass, ētas_, tése_, gkaa,
+ {{0x2e178201,0x5f76003d,0x25a02cf6,0x69c380c3}}, // [1ae0] _dəfə_, _مادر, rmil_, _htne,
+ {{0x6d5602a0,0x46f604fa,0xdcf49487,0x7c24acf7}}, // shya, _счит, _čačk, _širk,
+ {{0x7d1d2cf8,0x29d18796,0x6d4d01b4,0xdb0a849c}}, // _fass, nša_, bkaa, _difè,
+ {{0x3940808b,0x631400ab,0x79862cf9,0x6d932cfa}}, // ðis_, সাইট_, nokw, nçal,
+ {{0x6f1e0117,0xe3b207d2,0x61fe2cfb,0x799b8077}}, // _kapc, _درج_, stpl, _dhuw,
+ {{0x779180d5,0x7c240842,0x9c12819d,0x798981bc}}, // قیقا, mvir, _dọlf, _ekew,
+ {{0xe297028b,0x5fe0823c,0x29d1a354,0xdced07df}}, // _бар_, _निकल, jša_, znač,
+ {{0x442693d9,0x69de2cfc,0x74149a37,0x68e40135}}, // _pro_, mspe, لوبا, _ndid,
+ {{0x7c242cfd,0x69de2cfe,0x136a9182,0x7f41acff}}, // nvir, lspe, ешни_, _folq,
+ {{0xa3e5901c,0x93fb012a,0xbcfb02be,0xe3bf81df}}, // _फिर_, עליי, _amél, íña_,
+ {{0x19590a7f,0xe1350364,0xaa94a6b1,0x69de2d00}}, // ваны_, анны, ринч, nspe,
+ {{0x25f38076,0x4426ad01,0x69de00f7,0x63ae02d6}}, // ्झरी_, _tro_, ispe, _libn,
+ {{0x44268353,0x68e40114,0x644682f1,0x27e00176}}, // _uro_, _ddid, äkid, _cvin_,
+ {{0x7d1d2d02,0x2b8f02af,0x69de2d03,0x7c290bda}}, // _sass, rück_, kspe, _šero,
+ {{0x7d042d04,0xd9048065,0x6d4d0a81,0xe5c40081}}, // ffis, _ٹی_, tkaa, йсто,
+ {{0x6298803b,0xb4c12d05,0x69de0e23,0x7d1d022b}}, // _izvo, ुरे_, dspe, _qass,
+ {{0x6d4d02c1,0x81b680c8,0x7afd9f3a,0x69c89fcd}}, // [1af0] rkaa, _ছবি_, đstv, lpde,
+ {{0x7d1d2d06,0x6d4d2d07,0x656982af,0x6e288370}}, // _wass, skaa, zieh, _ordb,
+ {{0x7d1d2d08,0x5ed380ab,0x69de2d09,0x63ae01b4}}, // _tass, _সালে, gspe, _dibn,
+ {{0xf8bf02be,0xa3e585fc,0x973c8390,0x7ff59459}}, // ncé_, _फिल_, _miću, لستا,
+ {{0x6d93055a,0x6e289995,0x69de0162,0xdb0d0333}}, // nçam, _ardb, aspe, spañ,
+ {{0x6d5801e2,0xb8ec80d4,0x99d712c8,0x79860234}}, // _įvai, _श्_, _مترا, zokw,
+ {{0xb8db80c8,0x8d558a13,0x6569ad0a,0xf8c980e7}}, // _আজ_, итич, tieh, _créé_,
+ {{0xf1a48554,0x754595ac,0x29d1811f,0xdce40289}}, // _грун, рниз, vša_, rnić,
+ {{0x1c4297ae,0xdb0f11b9,0x656984dc,0x6e288192}}, // жным, _lucí, rieh, _erdb,
+ {{0x7bdf0187,0xe787ad0b,0x60d62d0c,0x6569ad0d}}, // isqu, _субо, _seym, sieh,
+ {{0x4a758048,0x29d1ad0e,0x5efe809a,0x291f82f1}}, // рыпт, uša_, _शॉट्_, _kaua_,
+ {{0xb4e401fe,0xdb0385e4,0xfe708591,0x60260a41}}, // _नये_, _diná, _عدل_, адма,
+ {{0xb4c3885d,0x291f804f,0x6d93061c,0x2f248061}}, // ्री_, _maua_, rçal, ségű_,
+ {{0xfaa59597,0x2d8b001b,0xdb038e93,0x2909019d}}, // _кало, _akce_, _finá, _ebaa_,
+ {{0x7d042d0f,0x597604d9,0xbea61229,0x6d930722}}, // tfis, _выпу, равк, pçal,
+ {{0x7c2401e2,0x2c09809a,0x27e08805,0x29092d10}}, // tvir, _वहां_, šini_, _gbaa_,
+ {{0xdee62657,0xab662bd9,0x7d04007b,0x5ee080c8}}, // [1b00] _копи, авал, rfis, প্টে,
+ {{0x68fbad11,0x69de2a33,0x212000b9,0xb4c384c5}}, // _acud, tspe, _haih_, ्रु_,
+ {{0xbd8a003f,0x399c80f7,0xa3b60999,0xf8ba00d4}}, // _لندن_, híse_, _चूर_, _इलाय,
+ {{0xb4db0028,0x4fc6ab3f,0x6d430087,0x628e016b}}, // _hoàn, аска, _înap, _vybo,
+ {{0x628e2d12,0x63ae2d13,0x779482e3,0x27e08834}}, // _wybo, _tibn, میرا, éin_,
+ {{0x7c2480f7,0x69de2d14,0x2d922d15,0x7bc6062c}}, // _áiri, pspe, llye_, _itku,
+ {{0x91e302a4,0x25af81a1,0xdb0e0168,0xcb67002e}}, // _море, _bigl_, _libë, _таре_,
+ {{0x27e98341,0x7c2d8052,0x399c81a8,0xa534aba7}}, // šana_, _šara, físe_, снич,
+ {{0xef1a8abe,0x60cdad16,0x973c80d2,0xf8bf00e7}}, // _има_, ñame, _siću, ndée_,
+ {{0x3b5491b3,0x3944ad17,0xd00f80f7,0x316b0035}}, // скор, _homs_, _تلك_, wicz_,
+ {{0xe81e825e,0x44e880ab,0x7c2901b9,0x291f8c53}}, // पिका_, ক্ষক_, _ġerm, _yaua_,
+ {{0x7bc6003a,0x26dcad18,0x2b0606f0,0xbc6789a7}}, // _otku, lavo_, स्तु_, امین_,
+ {{0xb4d2ad19,0x7afc00d2,0xf8bf2d1a,0x1b1d0264}}, // वरी_, _ocrt, rcé_, নাতে_,
+ {{0xe78727f1,0x200501b9,0x26dc8611,0xdb0e13de}}, // румо, ċli_, navo_, _dibë,
+ {{0xd5b81e25,0x139b84de,0x539b8039,0x629880d2}}, // ист_, _שבוע, _שיוו, _uzvo,
+ {{0x394487fa,0x3a2d2d1b,0x48670081,0x00000000}}, // _noms_, _čep_, _въоб, --,
+ {{0x9fe180ab,0x798d2d1c,0xe7ee0105,0x3f87880a}}, // [1b10] _মঙ্গ, _ikaw, _जिया_, yonu_,
+ {{0xf2c7096b,0x7bdf02be,0x26dcad1d,0x68fb847f}}, // рсан, rsqu, javo_, _scud,
+ {{0xd1b80065,0x26dc8110,0x6f150216,0x79a786fd}}, // لانا_, davo_, mezc, арде,
+ {{0x3944ad1e,0x8c48017b,0xc10580f7,0xdb0e06c0}}, // _coms_, _bağı, صوتي, _libè,
+ {{0xdc350159,0x656d010c,0x2cbf811c,0x80c68327}}, // _האָט_, miah, vcud_, ाड़े,
+ {{0x8c480182,0x656d2d1f,0x6f152d20,0x6dbc007a}}, // _dağı, liah, nezc, _očal,
+ {{0xb87b17ad,0x6b52006a,0x291fad21,0xb345841c}}, // laíd, _mægl, _taua_, _loçã,
+ {{0x798d01c5,0xb4c38f21,0xe3bf81df,0x656d2d22}}, // _nkaw, ्रे_, íño_, niah,
+ {{0x69c7120e,0x21200590,0x26dc807a,0x442b0197}}, // _htje, _raih_, bavo_, _jrc_,
+ {{0xda0e0540,0x798d2d23,0x26dc81e8,0x442b2d24}}, // _सहमत_, _akaw, cavo_, _mrc_,
+ {{0xe69580f7,0x39920019,0x442b026c,0x6f150e1b}}, // _الاد, lási_, _lrc_, dezc,
+ {{0x6721ad25,0xf65282f6,0x8c48080a,0x7bc9ad26}}, // _halj, _מצב_, _yağı, ppeu,
+ {{0x656d2d27,0x4ca580ab,0x6721ad28,0xe81b03a4}}, // diah, _গ্রু, _kalj, _पैदा_,
+ {{0x20022d29,0x69c702ce,0x6721808e,0x6d5bad2a}}, // rtki_, _otje, _jalj, lhua,
+ {{0xceb281db,0xed5a1628,0xa6e3007b,0x20022d2b}}, // לים_, гов_, íðar, stki_,
+ {{0x6d5b80fc,0x26dc8517,0x7afc0267,0xc21281c6}}, // nhua, zavo_, _scrt, _זהה_,
+ {{0x64a30084,0x657603a6,0x7c3bad2c,0xdcfb8380}}, // [1b20] зата, rnyh, nxur, zluğ,
+ {{0x39448b3c,0x7c2bad2d,0xb05b016d,0x6721ad2e}}, // _soms_, _mrgr, rbät, _nalj,
+ {{0x260184c3,0x442b009f,0x6d5b8748,0x26dc8084}}, // jóo_, _erc_, khua, vavo_,
+ {{0x00da803f,0x3f8a0136,0xd91080d5,0x6d5b8282}}, // ابات_, lobu_, بیر_, jhua,
+ {{0xdcfb82a5,0x7d160079,0xdb0e00e7,0x5ec980ab}}, // jluđ, meys, _libé, _রয়ে,
+ {{0xa09b0158,0x06d880c8,0x6d4606cb,0x7d162d2f}}, // ניסט, _সাহি, _hoka, leys,
+ {{0x6d462d30,0x7c2b8114,0x60c415a0,0x3944ad31}}, // _koka, _argr, _ofim, _toms_,
+ {{0x2fda83f8,0x75242d32,0x6d462d33,0x6fb68077}}, // _مورد_, ndiz, _joka, _همرا,
+ {{0x7524004f,0x6d462d34,0x3eaa8388,0x67218168}}, // idiz, _moka, _ºbto_, _falj,
+ {{0x6d462d35,0x69c103d3,0xd00f8eca,0x8c1b0039}}, // _loka, _élec, _سلم_, נויי,
+ {{0xd9151006,0x60c904c3,0x61150fe6,0x60dd0102}}, // одны, ñemo, одну, tasm,
+ {{0x27e0805c,0xf53f016d,0x6d5bad36,0xd7fb0198}}, // šinu_, _ihåg_, chua, луг_,
+ {{0xbfb68104,0x588701e5,0x60dd2d37,0xc05803a9}}, // _điểm_, сына, rasm, бір_,
+ {{0x3f8a2d38,0x26da0144,0xac9784a3,0x3c778e82}}, // gobu_, _cepo_, _انها_, _פתגם_,
+ {{0x26da2d39,0x6f1505a4,0x656d00e1,0x39920019}}, // _depo_, rezc, tiah, zási_,
+ {{0x22ae8086,0x6d408247,0x7d160079,0x6a4a8085}}, // mək_, njma, geys, rəfd,
+ {{0x6d9303d3,0x69c71277,0xb4b9000f,0x656d2d3a}}, // [1b30] nçai, _stje, _चली_, riah,
+ {{0x656d01ac,0xb87b25a7,0x6b4d8144,0x6d5bad3b}}, // siah, raíd, _púgi, zhua,
+ {{0xfbda88fd,0x644d0239,0x821500f7,0x7d1601b4}}, // _मौसम, syai, تواص, beys,
+ {{0x39920065,0x6d5ba928,0x6dbc005c,0x752281ac}}, // tási_, xhua, _očaj, _naoz,
+ {{0x672198c5,0x261b8076,0x6d4080d2,0xf8078087}}, // _palj, _मनही_, djma, счен,
+ {{0x7c2d811f,0x44292d3c,0xf41f0198,0x2fc90122}}, // _šaro, mva_, _isä_, _mtag_,
+ {{0x6721803a,0x69c7120e,0xe81b175d,0x67238052}}, // _valj, _utje, _पैसा_, rdnj,
+ {{0x60c2ad3d,0xdd9180f7,0x44292d3e,0xd65804de}}, // ccom, بوع_, ova_, מיות_,
+ {{0xe3b693cd,0x7bcd2d3f,0x6721ad40,0x27e080f7}}, // обы_, npau, _talj, áint_,
+ {{0x44292d41,0x6d5bad42,0x98ad80e1,0x60dbad43}}, // iva_, shua, _hneď_, _heum,
+ {{0xe2999289,0x44278416,0x44290406,0x60db80dd}}, // ран_, _گراف, hva_, _keum,
+ {{0x4429003a,0xfd5681bc,0x6d5b81c0,0x7d1601b4}}, // kva_, _mgbọ, qhua, xeys,
+ {{0x6d462d44,0x2bb50d14,0xdce9ad45,0x26da0c53}}, // _roka, _अंगा, nieč, _pepo_,
+ {{0x6d462d46,0x7c29ad47,0xbf9b0073,0x83fd8019}}, // _soka, mver, stên, rződ,
+ {{0x22ae8086,0x3f8a026f,0x614617f4,0x7d162d48}}, // cək_, robu_, _мека, teys,
+ {{0xdce98110,0x7c29a7d1,0x9f468118,0xb05b0338}}, // kieč, over, iroá_, rbär,
+ {{0x06d900c8,0x7d162d49,0xfd5699a8,0x7bcd13d2}}, // [1b40] _তারি, reys, _agbọ, gpau,
+ {{0x68e9ad4a,0x75241b68,0x1b1780ab,0x7c29ad4b}}, // _aded, sdiz, তাকে_, iver,
+ {{0x7c299103,0x44292d4c,0xe8220ebf,0x8afc809a}}, // hver, ava_, मिका_, mięt,
+ {{0x60c29c33,0x7c29ad4d,0x629c1f95,0xf1a9803d}}, // ucom, kver, _vzro, مانه_,
+ {{0xb4d62d4e,0x7c29ad4f,0x88169a37,0x68e9ad50}}, // हरी_, jver, تباط, _dded,
+ {{0x05ea9d2f,0x60c2ad51,0xe3a78591,0x8afc809a}}, // афии_, scom, _زر_, nięt,
+ {{0xd6d98cde,0xda0e090a,0x629c003a,0xd5b78364}}, // сті_, _सहित_, _uzro, осы_,
+ {{0x7c290042,0x29002d52,0x7c29ad53,0x60db8706}}, // _šeri, _ncia_, fver, _geum,
+ {{0x7c29ad54,0x8d5b03de,0xb054819d,0x8b6681a8}}, // gver, יכקי, ọọrụ_, قادم,
+ {{0xb4d613d9,0x3f76809a,0xa3dc2207,0xeb4a9cad}}, // हरु_, dług_, णीय_, рчик_,
+ {{0x2b4783d3,0x06d900ab,0x79952d55,0x2c0600ab}}, // _donc_, _তালি, зинф, রিয়ে_,
+ {{0x81bc8341,0xb4b9023c,0x22ae8201,0x212682f7}}, // lvēk, _चले_, rək_, ndoh_,
+ {{0x2006ad56,0x22ae829a,0x32058833,0x69c807b6}}, // ntoi_, sək_, atly_, _édef,
+ {{0x6d9326e1,0xdefb0110,0x21248229,0x7c3b0580}}, // nçav, шым_, _lamh_, _àuri,
+ {{0xb4d60b6f,0xd25080f7,0xc4db04ae,0x3205ad57}}, // हरू_, _سنة_, иђа_, ctly_,
+ {{0xb87b0187,0x395d81c0,0x6ecb170c,0x200683ed}}, // raíb, xhws_, _त्रु, ktoi_,
+ {{0x29192d58,0x4c940198,0x0cab2d59,0x94ab14c4}}, // [1b50] mesa_, дитс, ртви_, ртва_,
+ {{0x29192d5a,0x60dbad5b,0x6f188904,0x7bcd00c6}}, // lesa_, _seum, jevc, rpau,
+ {{0x3915885f,0x44292d5c,0x7bcd0110,0x9268108d}}, // змер, sva_, spau, орца_,
+ {{0x68fd1887,0xdb0b8106,0xdb1bad5d,0x28f88749}}, // ngsd, _utgö, rqué, жець_,
+ {{0x25a90ecb,0x67250074,0x39490118,0xdb1b85c9}}, // mmal_, _kahj, _moas_, squé,
+ {{0x7c29ad5e,0x9a87143d,0x3949056c,0x25a92d5f}}, // vver, _дубл, _loas_, lmal_,
+ {{0xd90f8065,0x2006ad60,0x7c29890d,0x316f8118}}, // _لیا_, atoi_, wver, pigz_,
+ {{0x25a902af,0x29192d61,0x67252d62,0xea0000ff}}, // nmal_, jesa_, _lahj, _hiếp_,
+ {{0x29192d63,0xc10480f7,0x7c2980e7,0x657e80e7}}, // desa_, _توقي, uver, épha,
+ {{0x63bd2d64,0x25a901ec,0x67252d65,0x7d09888b}}, // _musn, hmal_, _nahj, sfes,
+ {{0x29192d66,0x39490548,0x25a92d67,0x29000144}}, // fesa_, _boas_, kmal_, _pcia_,
+ {{0x3949062f,0x29192d68,0x7c29ad69,0x9f42001b}}, // _coas_, gesa_, pver, mské_,
+ {{0xdb03ad6a,0x9f4203fb,0x63bb01df,0x44298548}}, // _minú, lské_, _éunh, _éa_,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x9f4203cb,0x6da62d6b,0x2bb5000f,0x442f82f7}}, // nské_, зива, _अंजा, _hrg_,
+ {{0xf9920077,0x7ae1ad6c,0x63bd031d,0xb4d617ba}}, // _ثبت_, lalt, _busn, हरे_,
+ {{0x29c18028,0x27e9ad6d,0xaaba0a47,0xf48400d7}}, // [1b60] _hóa_, šanj_, مدار_, ذاری,
+ {{0xdb0b8118,0x8b230d55,0x442f8362,0xed160035}}, // _augú, ндуе, _mrg_, _łódź_,
+ {{0x63b50da8,0x9f42016b,0xb6bb01c6,0xdb1989c4}}, // _dizn, jské_, _לציי, _cuwé,
+ {{0x7ae1ad6e,0x2d8081ac,0xa6d880ab,0x63bd2d6f}}, // halt, čiek_, _সাইট, _fusn,
+ {{0x18188077,0x7ae1a2d2,0x98a68162,0x29c1ad70}}, // تراک_, kalt, чиде, _lóa_,
+ {{0xbb4a0013,0x7bcb2d11,0x291902ec,0x2006ad71}}, // _الآن_, ígue, zesa_, rtoi_,
+ {{0x7ae18fe0,0x442f848d,0x6f188da8,0x29192d72}}, // dalt, _arg_, revc, yesa_,
+ {{0x6d4408de,0xd90d826a,0x7cf6880a,0x442f89ca}}, // njia, _نیم_, dürü, _brg_,
+ {{0x9f5900f2,0x29192d73,0x06e200ab,0x63bd0df6}}, // ltså_, vesa_, _বানি, _xusn,
+ {{0x27e6ad74,0x442f8573,0x44222d75,0x7ae1ad76}}, // mson_, _drg_, _msk_, galt,
+ {{0x442f82d8,0xc333036b,0x29192d77,0x6d5604b9}}, // _erg_, גות_, tesa_, kkya,
+ {{0x463b80be,0x26de876d,0x7ae194c7,0x68e28c53}}, // _לעבע, _heto_, aalt, naod,
+ {{0x7ae1ad78,0x26de80f1,0x24aa80ab,0x63602d79}}, // balt, _keto_, _গ্রহ, _göng,
+ {{0x27e683d3,0x7c22ad7a,0xd3708077,0x7ae1ad7b}}, // ison_, _isor, _جهت_, calt,
+ {{0x29192d7c,0x39920019,0x63b50289,0x2cb9008e}}, // pesa_, tást_, _rizn, _pgsd_,
+ {{0x26dead7d,0x9cfa8264,0x5d9901e2,0x3f8e9fb6}}, // _leto_, _আসুন_, ікаў_, hofu_,
+ {{0x68ed2d7e,0x68e2ad7f,0xa49b0032,0x27e68b80}}, // [1b70] _idad, daod, _abòr, json_,
+ {{0x8aa705c2,0x02a70adb,0x25a901ec,0x6d562d80}}, // пред, прем, smal_, akya,
+ {{0x2b0e8023,0x60cda511,0x98bf8087,0xfe1b8424}}, // त्यु_, ñamo, _nouă_, _भैंस_,
+ {{0x1a6804c0,0x63bd2d81,0x9f42026f,0x7c22ad82}}, // سیقی_, _tusn, vské_, _osor,
+ {{0xea000104,0x26dead83,0xbcfb002a,0x7d0d2d84}}, // _tiếp_, _beto_, _alég, mfas,
+ {{0x9f42003e,0x7c2d2d85,0xaae10cfd,0x26de8041}}, // tské_, mvar, _फ़िक, _ceto_,
+ {{0x7ae1ad86,0x26de80b9,0xa3df873c,0x68ed0d6a}}, // valt, _deto_, धीन_, _odad,
+ {{0x7d0d1fba,0x98bf802e,0x27e90292,0x9f42027f}}, // nfas, _două_, _ovan_, rské_,
+ {{0xb4bd835a,0x7ae1ad87,0xbcfb2491,0xdc0b00ab}}, // _आली_, talt, _elég, রিকস_,
+ {{0x63600459,0x68ed2d88,0x04ff80ab,0x7cf68059}}, // _gönd, _adad, ্যের_, türü,
+ {{0x27e906c0,0x7c22ad89,0x6d5d061c,0x987000e1}}, // _avan_, _esor, _olsa, rúča_,
+ {{0x7ae1a6e1,0x442f8796,0x29e78c18,0xdb06002a}}, // salt, _trg_, rđa_, _yukó,
+ {{0x442f80b9,0x7ae1ad8a,0xeb8e8087,0x7d1bad8b}}, // _urg_, palt, _аи_, meus,
+ {{0xdfcf0013,0x6d4bad8c,0x442d920f,0x61faad8d}}, // شيف_, _hoga, mve_, butl,
+ {{0xab28845b,0xdb1b97ad,0x7d0d2d8e,0x7c2d0084}}, // _رسول_, nquí, ffas, evar,
+ {{0x7529ad8f,0x6d4bad90,0x78bb822e,0xe5a304fa}}, // ndez, _joga, _nguv, виси,
+ {{0x9f5fad11,0x6360016d,0x7c2d2d91,0x2fcd822c}}, // [1b80] _aquí_, _löne, gvar, _nteg_,
+ {{0x6d4bad92,0xdcdaa769,0xa3df83eb,0xeb9f00e8}}, // _loga, _यज्ञ, धीय_, _spør_,
+ {{0x26deaa63,0x75262d93,0xc87f02af,0x7d1bad94}}, // _reto_, _takz, raße_, keus,
+ {{0x442d803a,0x63602d95,0x26dead96,0xaca38032}}, // kve_, _sönd, _seto_, _afọj,
+ {{0xd0108277,0x44222d97,0xa3f880ab,0x442d80f1}}, // الت_, _usk_, _অন্য_, jve_,
+ {{0x27e68782,0x60c9ad98,0x44200010,0x7d029429}}, // rson_, _efem, mwi_, _scos,
+ {{0xe1e78117,0x442dad99,0x656f031d,0x27e6ad9a}}, // _جس_, eve_, _ymch, sson_,
+ {{0x6f1c2d9b,0x636007d9,0x6d4b9a29,0x7d1bad9c}}, // herc, _döne, _coga, geus,
+ {{0xe3bf01df,0x26de928c,0xea0000ff,0x44200114}}, // _xuñ_, _teto_, _khắp_, nwi_,
+ {{0x6d9307e0,0x291202d6,0x68e0ad9d,0x61faad9e}}, // nçar, _abya_, _hemd, tutl,
+ {{0x6d4bad9f,0x7d2780f2,0x442dada0,0x7d1b8118}}, // _foga, örsä, ave_, beus,
+ {{0x7d1b8373,0x7c2284b9,0x442dada1,0x68e0826c}}, // ceus, _tsor, bve_, _jemd,
+ {{0xab2a91b3,0xf7730829,0x7c22882e,0x442000b4}}, // _мода_, ناس_, _usor, jwi_,
+ {{0x636007d9,0x6f1c2da2,0x44202da3,0x61faada4}}, // _yöne, gerc, dwi_, putl,
+ {{0x672880f1,0xb87b282b,0x7d0d242f,0x7dc782d0}}, // _madj, caín, tfas, _kısa,
+ {{0xbcfb2da5,0x6d4b862f,0x06e200ab,0x68ed2da6}}, // _amér, _xoga, _বাণি, _udad,
+ {{0xdd940196,0x7d0d10f6,0x6f1c2da7,0xb87b00f7}}, // [1b90] таты, rfas, berc, maío,
+ {{0x6f1c2da8,0x672889cf,0xb87b0013,0x38668805}}, // cerc, _nadj, laío, dzor_,
+ {{0x442dada9,0x2fcdadaa,0x3f912dab,0x2bdf06f0}}, // zve_, _steg_, mozu_, _पौरा,
+ {{0xb4bd835a,0x63b8942f,0xb87b00f7,0x8aa40dd3}}, // _आले_, _livn, naío, труд,
+ {{0xe739a66c,0xdd910065,0x442d0091,0x6d4badac}}, // зел_, _لوگ_, _ée_, _roga,
+ {{0x25a68019,0x6d4badad,0x442dadae,0xb87b01a8}}, // _ahol_, _soga, vve_, haío,
+ {{0xf7708bbe,0x995c803e,0x7ae52daf,0xe8d911d3}}, // جان_, síš_, maht, _alọ_,
+ {{0x442dadb0,0x6f1c003e,0x94259fd0,0xdb1b85e4}}, // tve_, zerc, емие, rquí,
+ {{0x6d4badb1,0x442dadb2,0x291da5f3,0x7d1badb3}}, // _voga, uve_, mewa_, reus,
+ {{0x291d822e,0x4a45867c,0x1b1d00c8,0x629881b0}}, // lewa_, енов, নাকে_, _byvo,
+ {{0x44202db4,0x63b88503,0x442dadb5,0x6d4b8014}}, // zwi_, _divn, sve_, _toga,
+ {{0x2fc00722,0x9952026f,0x291dadb6,0x25b90f3e}}, // _puig_, sáž_, newa_, _nisl_,
+ {{0x25adadb7,0x394d879f,0xdbd70406,0xf1b9a944}}, // mmel_, _moes_, _jäät, _miš_,
+ {{0x7ae5178f,0x442006c4,0x291dadb8,0x63b8a085}}, // jaht, vwi_, hewa_, _givn,
+ {{0x69962344,0x59b98076,0x291dadb9,0xba5280f7}}, // _прих, _ईंटर, kewa_, _منوع,
+ {{0xa0a60048,0xb87b0013,0xa2cd08fd,0xf1b98024}}, // _пайд, caío, दुस्, _niš_,
+ {{0x248604c3,0xac861663,0x291dadba,0x2bdb001b}}, // [1ba0] çom_, хгал, dewa_, मीला,
+ {{0x395fadbb,0xe6258028,0x44201044,0x6b520366}}, // _alus_, _đông_, rwi_, _nægt,
+ {{0x26d82dbc,0xa2a5053e,0x44202dbd,0x6728adbe}}, // mbro_, _करण्, swi_, _radj,
+ {{0x29022dbf,0x8c461593,0x39990019,0x67288353}}, // ngka_, _земе, tési_, _sadj,
+ {{0x394dadc0,0xea000129,0x2d802dc1,0x6728832a}}, // _does_, _thắp_, mnie_, _padj,
+ {{0x2d800063,0x69dc041c,0x6720052a,0x395fadc2}}, // lnie_, _twre, मजिक_, _elus_,
+ {{0xfaa31b2f,0x5693adc3,0x63830098,0x395f81b9}}, // _баро, лашт, _сгра, _flus_,
+ {{0x291dadc4,0x248681df,0xa53480c4,0xd00a9052}}, // cewa_, _pxom_, тнич, _небе_,
+ {{0x9f868221,0x63b89a7c,0xfbcf815b,0x2d802dc5}}, // _згад, _pivn, یتی_, inie_,
+ {{0x5a348e17,0x7c3b12ca,0x2d802dc6,0xdbdb846d}}, // внот, _áure, hnie_, _dààt,
+ {{0x7d0604b8,0x28f92dc7,0xb87b00f7,0x3f9110d1}}, // _ocks, мень_, taío, vozu_,
+ {{0x25d983f8,0xea000104,0x7c262dc8,0xa3c0000d}}, // _آهنگ_, _nhập_, _oskr, ँदा_,
+ {{0x2d800063,0xdd8f8154,0xb87b00f7,0xd83822f0}}, // dnie_, _اون_, raío, нэр_,
+ {{0x4426adc9,0x61fe003a,0x2d80009a,0xb87b00f7}}, // _iso_, kupl, enie_, saío,
+ {{0xbe880ca4,0x249f0088,0xdb03801b,0x59b09516}}, // есте_, _šum_, _jiný, जगार,
+ {{0x2d802dca,0x6564009c,0xba9b8039,0x4426adcb}}, // gnie_, ghih, _מסוי, _kso_,
+ {{0x44268088,0x26d800e5,0x7ae518c2,0x3ebe80c3}}, // [1bb0] _jso_, bbro_, taht, _ogtt_,
+ {{0x6d5a08cf,0xd6db0009,0x2d800035,0x63ad8037}}, // ëtar, _эти_, anie_, _èant,
+ {{0xf7718bca,0x395fa4cc,0xa4d9245b,0x2d9202a0}}, // یات_, _plus_, _одну_, boye_,
+ {{0xdbd70364,0x4426811e,0x60b59e91,0x2d80009a}}, // _päät, _oso_, نمائ, cnie_,
+ {{0x4426815e,0xb8e98076,0xbcfb02ba,0x7ae50364}}, // _nso_, _ईल_, _eléc, paht,
+ {{0x394d81b0,0x6d4f0214,0x21298824,0xbcfb0866}}, // _woes_, _hoca, žah_, _fléc,
+ {{0x4426adcc,0x291d8010,0x6d4f2dcd,0x6b52006a}}, // _aso_, pewa_, _koca, _vægt,
+ {{0x752d2dce,0x69c78efc,0x7415845b,0x8477826b}}, // ndaz, íjem, نوعا, _aáwẹ,
+ {{0x51849fa0,0xdb1882d6,0x5f09866f,0xe4c895a9}}, // _сура, _kivè, _साप्_, ربين_,
+ {{0x2d802448,0x799d01ec,0x6b52008b,0xdd0201a1}}, // znie_, elsw, _hægr, _čučk,
+ {{0x4426adcf,0x96ca8540,0x36d48009,0x68e602e8}}, // _eso_, _स्टॉ, _сохр, takd,
+ {{0x27e98341,0x2902816d,0x4ad18701,0x6dbc131b}}, // šanu_, _ökat_, _द्रव, _očar,
+ {{0xf1b28f60,0xd24e80d5,0x8b25add0,0x3ebe8326}}, // יסט_, منی_, _адле, _ygtt_,
+ {{0x06e200ab,0x7bc00187,0x3ebe004a,0x6d5b8bf0}}, // _বাহি, _émui, øtt_, lkua,
+ {{0x6d4f2dd1,0x2d800063,0xfce60171,0x4426add2}}, // _boca, tnie_, ково, _zso_,
+ {{0x7ae38de1,0x2bc30fb8,0x6d5badd3,0x26d82dd4}}, // _hent, _वंशा, nkua, sbro_,
+ {{0xc9530051,0x78a40503,0x69c3add5,0x2d802dd6}}, // [1bc0] _כמה_, _uziv, _hune, rnie_,
+ {{0x7ae3add7,0x69c39e98,0x27e02227,0x65642dd8}}, // _jent, _kune, _iwin_, shih,
+ {{0x6d4f0013,0x6dbc0353,0xdb1886c0,0xea0000ff}}, // _foca, _včas, _divè, _thập_,
+ {{0x7ae3add9,0x69c3adda,0x61fe2ddb,0x3f85826c}}, // _lent, _mune, rupl, _ajlu_,
+ {{0x69c3addc,0xf7468003,0xdb0baddd,0x7c26005c}}, // _lune, кедо, _bugü, _uskr,
+ {{0x7bd62dde,0x68e405d1,0x7d042ddf,0x7ae3ade0}}, // mpyu, _leid, lgis, _nent,
+ {{0x69c3ade1,0x91e30e02,0x27ed8051,0x752b81a1}}, // _nune, _восе, _even_, _bagz,
+ {{0x7d042de2,0x68e40114,0x39990f87,0x6d4f2de3}}, // ngis, _neid, nést_, _xoca,
+ {{0x7ae38763,0x63bc26a6,0x7c240192,0x27ed81f4}}, // _bent, _kirn, nwir, _gven_,
+ {{0x69c3ade4,0x3a27808e,0x4426807a,0xdb188061}}, // _bune, _bsnp_, _vso_, _kivé,
+ {{0x68e42de5,0x69c3ade6,0x7ae39ee5,0x6ed88f1b}}, // _beid, _cune, _dent, _न्यु,
+ {{0x442681e9,0x7c2400b4,0x7ae3ade7,0x629c2127}}, // _tso_, kwir, _eent, _myro,
+ {{0x7ae3ade8,0x5887014c,0x68e401e4,0x60cd8020}}, // _fent, тына, _deid, ñami,
+ {{0x7ae3ade9,0x63bc01a1,0x629c02c4,0x52e10035}}, // _gent, _nirn, _oyro, _फ़ेस,
+ {{0xe5c42dea,0xd378811f,0x68e40013,0x31668019}}, // исто, mić_, _feid, khoz_,
+ {{0x7ae3adeb,0x7982809a,0x7d042dec,0x9f59026f}}, // _zent, jnow, ggis, musí_,
+ {{0x63bc03c3,0x6c542ded,0x752d2dee,0x629c0420}}, // [1bd0] _birn, акту, rdaz, _ayro,
+ {{0x7ae3862f,0x629c2def,0x7d042df0,0x68e40bfd}}, // _xent, _byro, agis, _zeid,
+ {{0x3f83011f,0xdb050013,0x6562adf1,0x27edadf2}}, // mnju_, gmhá, _oloh, _sven_,
+ {{0xd37885f5,0x6d4f026c,0x7c2402a0,0x9f4203c1}}, // hić_, _uoca, bwir, mská_,
+ {{0xd3788025,0x7ae8adf3,0x629c0bb1,0x9f4205b9}}, // kić_, kadt, _eyro, lská_,
+ {{0x65628091,0xd378803b,0x6360007b,0x490886a7}}, // _aloh, jić_, _hönn, _हाथो_,
+ {{0x63602df4,0x9f42003e,0xd378803b,0x2d55013c}}, // _könn, nská_, dić_, _fået_,
+ {{0xdb1d0a56,0x2d55013c,0x661a8748,0x4424822c}}, // _musí, _gået_, _dptk, fwm_,
+ {{0x7ae38193,0x6d5badf5,0x69c3adf6,0x3f831351}}, // _pent, rkua, _sune, knju_,
+ {{0xd37885f5,0xe9d99016,0x69c3adf7,0x2902816d}}, // gić_, нко_, _pune, _ökar_,
+ {{0x3f83003b,0xc3320158,0xdb0385e4,0x68e42df8}}, // dnju_, זוי_, _sinó, _peid,
+ {{0x69c380fe,0x290911b9,0x7412803d,0x9f42016b}}, // _vune, _ccaa_, جویا, dská_,
+ {{0x68e42a24,0xd3788d78,0xe9e980e1,0xa3dfab12}}, // _veid, bić_, dnúť_, धीश_,
+ {{0x44322df9,0x68e40faf,0xd378809a,0x2d992dfa}}, // vvy_, _weid, cić_, _akse_,
+ {{0x68e42dfb,0x39990019,0x2bdf81fe,0x31790035}}, // _teid, tést_, पीरा, bisz_,
+ {{0x96d411bc,0x63bc2dfc,0x7c242dfd,0x2d960a14}}, // _ब्रॉ, _sirn, twir, ургс,
+ {{0x6da30087,0x09cc016f,0x3f832bea,0x629c0690}}, // [1be0] сита, ाऱ्य, bnju_, _syro,
+ {{0x7c242dfe,0x44322dff,0xfbcd00ab,0x87260009}}, // rwir, rvy_, _লিখত, _смож,
+ {{0xe8faa9d7,0x20022e00,0x7c242e01,0x644001a1}}, // еле_, luki_, swir, _šmid,
+ {{0x629c027f,0xd3788d78,0x786a8019,0x88e68198}}, // _vyro, zić_, _növé, ужде,
+ {{0x63bc2e02,0x20022e03,0xe3c880ff,0x629c0035}}, // _tirn, nuki_, _mực_, _wyro,
+ {{0xdb1c00f1,0xe3c88028,0x31668019,0x96a8001b}}, // _mirë, _lực_, shoz_, _गराउ,
+ {{0xd378803b,0x6b631594,0x7bc60300,0xd6d9804a}}, // vić_, _укра, _huku, тті_,
+ {{0x7bc62e04,0x3f83007d,0x1db4035a,0xd378809a}}, // _kuku, znju_, ंगित, wić_,
+ {{0xd378803a,0x12e68a4c,0x7bc62e05,0xe64483bf}}, // tić_, лінг, _juku, ılığ,
+ {{0x96d42e06,0x20022e07,0x7bc620d7,0x39402e08}}, // _ब्लॉ, duki_, _muku, _anis_,
+ {{0xd378803a,0x7bc62e09,0xdb0380f7,0x3f832e0a}}, // rić_, _luku, _ghní, vnju_,
+ {{0x2ee58613,0xe3c88028,0x9f42026f,0xf1ab0077}}, // _zelf_, _cực_, vská_, عاده_,
+ {{0x8d938013,0x3f830024,0x7bc62e0b,0x69c100e7}}, // _النش, tnju_, _nuku, _élev,
+ {{0x9f42026f,0x3179009a,0x28e30128,0x636001fa}}, // tská_, sisz_, परहि, _sönn,
+ {{0x58d50698,0x31790063,0xe8d90135,0x3f83012b}}, // _койт, pisz_, _ajụ_, rnju_,
+ {{0x7bc62e0c,0x3f8302a5,0x9f42026f,0x20022e0d}}, // _buku, snju_, rská_, buki_,
+ {{0x7bc60763,0x3f83173d,0x337513bf,0x80cd8264}}, // [1bf0] _cuku, pnju_, агар, ারম্,
+ {{0x7bc62e0e,0x9f42128a,0x332e8372,0xa3e6885d}}, // _duku, pská_, _jafx_, यीय_,
+ {{0xfaa7174a,0x02a726b1,0xb4db0362,0xe9e981d6}}, // ушан, урам, _cnàc, pnúť_,
+ {{0xdc34026f,0x27ed012b,0x2ee58039,0x3835802e}}, // _zúča, šenu_, _self_, инер,
+ {{0x7bc61b19,0x3b0a0580,0x5045847f,0x6360029a}}, // _guku, _fcbq_, релб, _könl,
+ {{0x6d4d2e0f,0xb0d580d4,0x29068102,0x2b140ebf}}, // ljaa, _ड्रग, agoa_, न्दु_,
+ {{0xf1a785c2,0x7bc601ec,0xd0560085,0xf1c980ff}}, // _бран, _zuku, _deyə, _mạc_,
+ {{0x442b2e10,0x6d4d2e11,0xf1c98028,0x528400f7}}, // _ksc_, njaa, _lạc_, _الفك,
+ {{0x7bc602a3,0xab6595ac,0x2ee5ae12,0x69ceae13}}, // _xuku, ивил, _telf_, íben,
+ {{0xce6b9a19,0x69c72e14,0xd7a4000f,0x68eb84dc}}, // _пред_, _kuje, _खींच, lagd,
+ {{0x216a22df,0x6723895e,0xa96a013a,0xfaa5ae15}}, // вини_, menj, вина_, рано,
+ {{0x69c704a1,0x67238353,0xdc3401ac,0x442b00ee}}, // _muje, lenj, _súča, _osc_,
+ {{0x6603ae16,0xe3c88028,0x25e30540,0xf1c9801c}}, // lunk, _vực_, टीसी_, _bạc_,
+ {{0x7bc62e17,0x66e301e5,0x20022e18,0x6723ae19}}, // _ruku, жора, ruki_, nenj,
+ {{0x13da80c8,0x7bc62e1a,0x20020590,0x799b8c56}}, // _দিয়, _suku, suki_, _okuw,
+ {{0x7bc613b8,0x6723ae1b,0x6446816d,0x39402e1c}}, // _puku, henj, åkig, _unis_,
+
+ {{0x6723ae1d,0x644984c3,0x69d5011e,0x68eb96fb}}, // [1c00] kenj, nxei, _atze, dagd,
+ {{0x44392e1e,0xaaba026a,0x6603ae1f,0x25a0141f}}, // _drs_, ندار_, kunk, slil_,
+ {{0x44392e20,0x7ae72e21,0x69c70052,0x67238805}}, // _ers_, _dejt, _cuje, denj,
+ {{0x7bc62e22,0xe3c8801c,0xdb0f0825,0x44392e23}}, // _tuku, _lựa_, _bucó, _frs_,
+ {{0x6a4a8201,0x2906ae24,0x33d52e25,0x3860978e}}, // rəfi, rgoa_, _гіст, áir_,
+ {{0x752f009a,0x6723ae26,0x25e30105,0x78a9826c}}, // _zacz, genj, टीवी_, _dzev,
+ {{0x69c18526,0x25fd8f85,0x7c2d8e9f,0x9f590036}}, // _रंगी, रंगी_, _šarp, ursé_,
+ {{0x3f98001b,0xdb1c21bf,0xa6e9801c,0x7ae72e27}}, // horu_, _sirè, _ngươ, _zejt,
+ {{0x69c0ae28,0x442b0118,0x6723ae29,0xdb072e2a}}, // _iime, _xsc_, benj, _gijó,
+ {{0x4aaa2e2b,0x6723803b,0x69c0ae2c,0x7c398122}}, // _कराव, cenj, _hime, _drwr,
+ {{0x69c10065,0x195917ae,0x69c0ae2d,0xe3c880ff}}, // _élet, ганы_, _kime, _dựa_,
+ {{0x50672e2e,0xf1c980ff,0x2c5e00eb,0x69c0ae2f}}, // атга, _sạc_, _kādā_, _jime,
+ {{0xb8cf0403,0x938a8098,0x8ca20d86,0x68f6031d}}, // _कर_, _иска_, _खरगो, _ddyd,
+ {{0x69c0ae30,0x3f9802a5,0x09d700ab,0x65662e31}}, // _lime, goru_, _হিসা, _alkh,
+ {{0x7ae72e32,0x752f0035,0xdef801e2,0xa1870d15}}, // _rejt, _pacz, шыя_, _выпл,
+ {{0x81c40a49,0x69c0ae33,0x672380d2,0x442b009f}}, // _এবং_, _nime, zenj, _psc_,
+ {{0x69c72e34,0x6d4d2e35,0x61fe110f,0xf2c701cf}}, // [1c10] _suje, rjaa, erpl, асен,
+ {{0x34a7023c,0x69c0ae36,0x44392e37,0xdb0a80f7}}, // _खरीद, _aime, _vrs_, _bhfé,
+ {{0x6723ae38,0x5f00800f,0x69c0ae39,0x6d428162}}, // venj, _राज्_, _bime, _onoa,
+ {{0xdd8683f8,0x98a9811f,0x44392e3a,0xdb0f0144}}, // _رو_, žač_, _trs_, _pucó,
+ {{0xe8f8902a,0x7ae70038,0x44392e3b,0x6723a76e}}, // рлі_, _tejt, _urs_, tenj,
+ {{0x44292578,0x2fc901e9,0x6d4282c4,0x6603a9bd}}, // mwa_, _muag_, _anoa, tunk,
+ {{0x672382a5,0x442926e0,0x2fc901e9,0xdb1881df}}, // renj, lwa_, _luag_, _civí,
+ {{0x6723825b,0x6603ae3c,0x99858013,0xf8bf2e3d}}, // senj, runk, _الرو, ndés_,
+ {{0x44292e3e,0x1c4297ae,0x681a80eb,0x6723acc7}}, // nwa_, зным, gādā, penj,
+ {{0x69c08010,0x6569ae3f,0x44292e40,0xb4e68035}}, // _zime, sheh, iwa_, _बड़ी_,
+ {{0x44292e41,0xb6358277,0xed358162,0x68e9ae42}}, // hwa_, _دفاع, _гэлэ, _keed,
+ {{0x44292e43,0x68e982a3,0xce689878,0x3f982e44}}, // kwa_, _jeed, _труд_, woru_,
+ {{0x3438819f,0xa2e800c8,0x68e9ae45,0x2fc901c5}}, // _پسند_, _পাসও, _meed, _cuag_,
+ {{0x59cf8a16,0x4429029b,0x50d80eed,0x68e9ae46}}, // _संपर, dwa_, _भ्रष, _leed,
+ {{0x44292e47,0x7c29844e,0xdb0380f7,0xc8798087}}, // ewa_, lwer, _ghná, _deşi_,
+ {{0xbf9b03a7,0x68e98039,0x44292e48,0x3f982e49}}, // quên, _need, fwa_, soru_,
+ {{0x44292e4a,0x7c29ae4b,0x69c0ae4c,0x75242a6b}}, // [1c20] gwa_, nwer, _rime, reiz,
+ {{0x69c0ae4d,0x06e200ab,0x290d8087,0x7d098192}}, // _sime, _বাকি, _acea_, hges,
+ {{0xcf57004c,0xe1fa168a,0xe80a8076,0x44291f11}}, // _הבית_, уге_, हूना_, awa_,
+ {{0x44291d1b,0xb4e6923a,0x7c29ae4e,0x2638026f}}, // bwa_, _बजे_, kwer, _ičo_,
+ {{0x7bcfa6f0,0x69c0ae4f,0x7d09949d,0x6360016d}}, // ícul, _vime, dges, _jönk,
+ {{0x69da9766,0xaa462e50,0x7c29ae51,0x2d9d8326}}, // ypte, _легл, dwer, _akwe_,
+ {{0x69c0ae52,0xf38a877f,0x68e9ae53,0x6b630d15}}, // _time, nṣe_, _feed, _экра,
+ {{0x68e98079,0x7c29ae54,0xea00001c,0xdb1885e4}}, // _geed, fwer, _chấp_, _viví,
+ {{0x7866a804,0x80668f75,0x7c29ae55,0xa6c000ab}}, // _указ, _уваж, gwer, ্রিয়,
+ {{0x681a8029,0x6e3ca868,0x2619009a,0x7d09ae56}}, // rādā, _árbo, _पहली_, ages,
+ {{0x44292e57,0x6b9b00be,0x68e981b4,0xcb9b1101}}, // zwa_, רשיד, _yeed, רסיט,
+ {{0xd90f15e4,0x44292e58,0x7c2983f4,0x2fc901c5}}, // ریخ_, ywa_, bwer, _puag_,
+ {{0x34a7016f,0x2fc901c0,0xf8ba970c,0x91ca80bc}}, // _खरेद, _quag_, ेशिय, _संवै,
+ {{0x44290205,0x2fc90069,0xd7f80198,0x69daae59}}, // vwa_, _vuag_, щую_, ppte,
+ {{0xf1c98028,0x7aea802e,0x442904b7,0x4b7b8e82}}, // _hạn_, _ieft, wwa_, _תאוו,
+ {{0x44292e5a,0x2fc92e5b,0x46d9064a,0x6b9a87f1}}, // twa_, _tuag_, _ब्रह, lotg,
+ {{0x68e9ae5c,0xd05d0201,0xf8bf2e5d,0x44292e5e}}, // [1c30] _reed, yasə, rdés_, uwa_,
+ {{0x44292e5f,0x7aea803a,0x69caae60,0xf1c980ff}}, // rwa_, _jeft, _kufe, _mạn_,
+ {{0x44292e61,0x201f8087,0x7d09867f,0x9d190087}}, // swa_, _spui_, yges, _конт_,
+ {{0xe705026a,0x44292e62,0x69ca8e72,0x3f9eae63}}, // _اسکی, pwa_, _mufe, _aktu_,
+ {{0x44292e64,0x260681ce,0xf1c9801c,0xbae800ab}}, // qwa_, सूसी_, _nạn_, _পারছ,
+ {{0x7c2f0698,0xe739ae65,0x7aea8085,0x45d58293}}, // _iscr, рек_, _neft, оцит,
+ {{0x7d098e61,0x636d8c52,0x7c299699,0x9c1381bc}}, // tges, _húng, wwer, _tọkw,
+ {{0xf1c98142,0xf77391fb,0x7c29ae66,0xed5993f1}}, // _bạn_, _کار_, twer, рои_,
+ {{0x7d09ae67,0x69d8ac62,0x7c2981ed,0x25fd8fd5}}, // rges, _atve, uwer, रूजी_,
+ {{0x7c29910f,0x7d09ae68,0x6d932e69,0x3205816b}}, // rwer, sges, lħad, tuly_,
+ {{0xdce2803a,0xea00001c,0x2d8910e4,0xdd9983f2}}, // _zloč, _thấp_, knae_, poň_,
+ {{0x26138073,0x24198009,0x7c29ae6a,0xdb0180e7}}, // mão_, _воды_, pwer, illé,
+ {{0x26138073,0x656d2e6b,0xb87b0207,0x6b9aae6c}}, // lão_, dhah, daís, botg,
+ {{0x88ba8039,0x78ad0b80,0xdb0a87f1,0x7bd98db1}}, // יזרי, _dzav, _difó, _itwu,
+ {{0x3ebe2e6d,0x443dae6e,0x7bcbae6f,0x26138187}}, // ätt_, _hrw_, _hugu, não_,
+ {{0x7bc39341,0x7bcb8f45,0x656d02b8,0x2d8902f7}}, // _hinu, _kugu, ghah, gnae_,
+ {{0x7bcbae70,0x7bc3ae71,0xfd648135,0x64a32e72}}, // [1c40] _jugu, _kinu, _anwụ, дата,
+ {{0xaac986bf,0xdb188019,0x443d8333,0x636001ec}}, // रशिक, _kivá, _mrw_, _köni,
+ {{0x442f82f7,0x26138187,0x7bcbae73,0x2d9e0036}}, // _lsg_, jão_, _lugu, îtes_,
+ {{0xdce280ce,0x26138073,0x8c4609e0,0x02aa12c6}}, // _ploč, dão_, _деме, _करीन,
+ {{0x274a80ae,0x2eeeae74,0x6d462e75,0x443dae76}}, // ичко_, taff_, _inka, _nrw_,
+ {{0x442209da,0x7bc3ae77,0x29d70372,0x2613841c}}, // _ipk_, _ninu, _bħad_, fão_,
+ {{0x7bcb9e44,0x2eeeae78,0x261383a7,0xf1c9801c}}, // _augu, raff_, gão_, _sạn_,
+ {{0x54549bc1,0x7bc38006,0x7bcbae79,0x44220b99}}, // звит, _ainu, _bugu, _kpk_,
+ {{0x68e2ae7a,0xa067ae7b,0xa2be063a,0x7bc3a68b}}, // mbod, _дата_, _वृत्, _binu,
+ {{0x7bcb8102,0xb4db009f,0x2d680214,0x7bc3808e}}, // _dugu, _anàl, rşey_, _cinu,
+ {{0x6d462e7c,0x78ad0019,0x7bc3ae7d,0x261383a7}}, // _onka, _szav, _dinu, cão_,
+ {{0x7bc3807b,0xdb1d02af,0xdb18826f,0x3f9c80fa}}, // _einu, _zusä, _divá, lovu_,
+ {{0x6440011f,0x6727022b,0x69caae7e,0x60c901a1}}, // _šmin, tejj, _wufe, žemo,
+ {{0x3f9c8025,0x6d460835,0x7c228136,0x7bc3ae7f}}, // novu_, _anka, _ipor, _ginu,
+ {{0x69d8ae80,0x44222e81,0xdb1c0118,0x656d00d7}}, // _utve, _apk_, _cirí, thah,
+ {{0x3f9c8042,0x31690052,0x44222e82,0x656d2e83}}, // hovu_, _ulaz_, _bpk_, uhah,
+ {{0xb87b2a63,0x261383a7,0x78ad03fb,0x636d802a}}, // [1c50] raís, zão_, _uzav, _fúnd,
+ {{0x656d2e84,0x27e92e85,0xfaa71383,0x6d462e86}}, // shah, _iwan_, ошен, _enka,
+ {{0x68ed0867,0xa2be0af3,0x26138073,0x27e90683}}, // _kead, _वृद्, xão_, _hwan_,
+ {{0x68e28091,0x27e92bb7,0xa2daa769,0x61e701c0}}, // gbod, _kwan_, पुत्, _vwjl,
+ {{0xd6bd00c8,0xf7458f13,0x442200b9,0xb8d3864a}}, // _অভিয, зело, _gpk_, _ऑर_,
+ {{0x26138003,0xdfce8013,0x7d0d2e87,0x7c2d2668}}, // tão_, ديو_, lgas, mwar,
+ {{0x7c22ae88,0x59c69344,0x7bc3ae89,0xf9938277}}, // _apor, _रंगर, _rinu, _قبر_,
+ {{0x261380a9,0x7d0d2e8a,0x7bcb809f,0x442fae8b}}, // rão_, ngas, _pugu, _psg_,
+ {{0x261380a9,0xa3e68076,0x7bc3ae8c,0x7c2d2e8d}}, // são_, यील_, _pinu, nwar,
+ {{0x59cf8935,0x261383a7,0x31b6066b,0x6d9304b7}}, // _संदर, pão_, ृद्ध, nħab,
+ {{0xe299839d,0x7c2d2e8e,0x27e92e8f,0x7bc3ae90}}, // сан_, hwar, _awan_, _vinu,
+ {{0x7c2d082e,0xbea8803d,0xfc3f077f,0x68ed0cb5}}, // kwar, _بهمن_, _orí_, _cead,
+ {{0x7bc3ae91,0xf1c98104,0x8d748077,0xdb1c0118}}, // _tinu, _tạo_, _تالا, _sirí,
+ {{0x81e10a49,0x442d82f4,0x7c2d2e92,0x657d2e93}}, // _দিন_, mwe_, dwar, _amsh,
+ {{0x6d5980a4,0x442dae94,0x68ed0ad0,0xdcfb807d}}, // _kowa, lwe_, _fead, gnuć,
+ {{0x60c9ae95,0x7d0d0fc3,0xdb1c03a2,0x3f9cae96}}, // _ngem, ggas, _virí, zovu_,
+ {{0x442dae97,0x7c2d2e98,0x2b581458,0x6d598c2e}}, // [1c60] nwe_, gwar, _porc_, _mowa,
+ {{0x083b0051,0x291f81bc,0x7d0d2e99,0x44222e9a}}, // _פעיל, _abua_, agas, _vpk_,
+ {{0xa9268991,0xb6a300e7,0x442dae9b,0xa75b01c6}}, // здел, éâtr, hwe_, _הדיר,
+ {{0x442da08c,0x6d59ae9c,0xfc3f01fa,0x68ed03a8}}, // kwe_, _nowa, _frí_, _xead,
+ {{0x07a69285,0x442d8578,0x684681e5,0x68e2ae9d}}, // падн, jwe_, янда, rbod,
+ {{0xe3a78b76,0x501b0039,0x21200359,0x1b0300ab}}, // _سر_, מונו, _lbih_, র্বে_,
+ {{0x7d1bae9e,0x2cc18201,0x442d81b0,0x6d59838a}}, // ffus, ərdə_, ewe_, _bowa,
+ {{0x3f9c825b,0x6b818013,0x69ce0087,0x7d7880f7}}, // sovu_, eilg, _iube, تمبر_,
+ {{0x442dae9f,0x69ce2ea0,0x68ed0051,0x6d59aea1}}, // gwe_, _hube, _read, _dowa,
+ {{0x69ce0f45,0x68ed2ea2,0xb4ac800d,0x27f7800d}}, // _kube, _sead, _गरी_, ření_,
+ {{0x52138071,0xcfc400ab,0x442daea3,0x7c2d2ea4}}, // ндэт, ্ঠান, awe_, zwar,
+ {{0x442daea5,0x7c22973d,0x7982809a,0x69ce1bcb}}, // bwe_, _tpor, niow, _mube,
+ {{0x7c22aea6,0x69ce2ea7,0x43468ba5,0x6b819c0a}}, // _upor, _lube, _недв, bilg,
+ {{0xc7c40009,0x6d59aea8,0xb9042261,0x69dc2ea9}}, // есси, _zowa, _प्_, _otre,
+ {{0xd7888104,0x68ed0006,0x21200fda,0x69ce05e4}}, // yển_, _tead, _fbih_, _nube,
+ {{0xbcfb2eaa,0x7c2d262b,0x7aee0f3e,0x7d0d0789}}, // _llév, twar, _aebt, ugas,
+ {{0x94ab2eab,0x7d0d2eac,0x69dc209c,0x2d5802c7}}, // [1c70] ства_, rgas, _atre, чить_,
+ {{0x212b2ead,0x69ce2eae,0x3dc680b9,0x7c2d2eaf}}, // lech_, _bube, _liow_, rwar,
+ {{0x69ce2eb0,0x442daeb1,0x200b00d2,0x35b52eb2}}, // _cube, zwe_, luci_, _обор,
+ {{0xfc3f2eb3,0x6b81aeb4,0x212b2eb5,0x69ce2eb6}}, // _trí_, zilg, nech_, _dube,
+ {{0xf77084c0,0x6d59aeb7,0x20192eb8,0x200b0503}}, // گان_, _rowa, ntsi_, nuci_,
+ {{0x6d59aeb9,0x7bdd2eba,0xf09400be,0x25a92ebb}}, // _sowa, _itsu, ענס_, llal_,
+ {{0xf770806b,0xa3c29a1c,0x69ce2ebc,0x442d867f}}, // دان_, ्दन_, _gube, wwe_,
+ {{0x442da0d7,0x61fa007b,0x7982809a,0x7bc70144}}, // twe_, _ætla, ciow, _hiju,
+ {{0x442d8586,0x6b81aebd,0x200b0042,0x212b001b}}, // uwe_, tilg, juci_, dech_,
+ {{0xe1f99bc1,0x442daebe,0xdb1c0019,0x7bcf261d}}, // ого_, rwe_, _kirá, _mucu,
+ {{0x7bcf2ebf,0x3f832ec0,0x442da6bd,0xf1c9801c}}, // _lucu, diju_, swe_, _tạm_,
+ {{0x290f859c,0x7bdd0006,0xdb1c0333,0x37e20264}}, // ngga_, _otsu, _mirá, _বিবর,
+ {{0x02aa1344,0x59cf858c,0x7bdd01e9,0x200b00d2}}, // _कर्न, _संवर, _ntsu, guci_,
+ {{0x3f830025,0x6b8182a6,0xdb1c0144,0x1b030264}}, // giju_, qilg, _oirá, র্তে_,
+ {{0x7bcf03d3,0x6372006a,0xe5a62ec1,0xddd401d6}}, // _aucu, _hæng, диви, ňažn,
+ {{0x7bcf0012,0xdb188364,0x200b2ec2,0x2d8daec3}}, // _bucu, _eivä, buci_, nnee_,
+ {{0xe73a15fe,0x3f831487,0x7bc72ec4,0x6721a7b1}}, // [1c80] _деп_, biju_, _biju, _oblj,
+ {{0x39490025,0x09e61bc1,0x6372006a,0x316d86c4}}, // _znas_, дожн, _mæng, _alez_,
+ {{0x7bc701f1,0x63720022,0x39990e06,0x6b9e2ec5}}, // _diju, _læng, vész_, ropg,
+ {{0x6e3ca6d5,0x7aee2ec6,0xdb1c0333,0xfc4a9434}}, // _árbi, _webt, _dirá, ntíð_,
+ {{0xb4ac800d,0xdfcf8c3b,0x7bc7017f,0x629500b9}}, // _गरे_, لين_, _fiju, _exzo,
+ {{0x212b085d,0x26d82ec7,0x3dc680b9,0x6d5d8106}}, // zech_, ncro_, _siow_, ösan,
+ {{0x69dc239a,0x63600019,0x200b2ec8,0xdb1c002a}}, // _utre, _dönt, zuci_, _girá,
+ {{0x2d8daec9,0xd8268a2e,0x63648580,0x6d56018f}}, // gnee_, _одби, _iòni, njya,
+ {{0xf8b28105,0xae0282f1,0x212b2eca,0x657a1a1f}}, // _जरिय, रूजन_, vech_, éthy,
+ {{0xd370845a,0x200b02a5,0x6eb6866e,0xdb0a8174}}, // اهد_, vuci_, _قصائ, _bhfá,
+ {{0xc3331a0f,0x3f83005c,0x212b001b,0x25a02ecb}}, // דות_, viju_, tech_, koil_,
+ {{0x80fb23e6,0x6372006a,0x63600106,0xe7f58035}}, // ्लेख_, _fæng, _köns, ुंचा_,
+ {{0x3f83025b,0x212b2ecc,0x63602ecd,0xd8380087}}, // tiju_, rech_, _yönt, мэр_,
+ {{0x4426aece,0x2d84aecf,0x6360016d,0xa2948a8e}}, // _ipo_, lime_, _möns, _палі,
+ {{0x290f8186,0x212b1e1e,0x200b09c4,0x63600106}}, // ygga_, pech_, suci_, _löns,
+ {{0x394905b4,0x26170073,0x2d84aed0,0x7bcf2ed1}}, // _unas_, rço_, nime_, _pucu,
+ {{0x36d516df,0x60cd26bd,0x9f4205b9,0xc3130032}}, // [1c90] _попр, _igam, lský_, _ajíṣ,
+ {{0x25a91807,0x4426aed2,0x316d81a1,0x2006aed3}}, // slal_, _mpo_, _slez_, droi_,
+ {{0x9f42027f,0x2d84aed4,0x7bc72ed5,0x636487f1}}, // nský_, kime_, _viju, _còni,
+ {{0x4adf0913,0x3949840e,0x4426aed6,0xdb1c0019}}, // _प्रव, ñas_, _opo_, _virá,
+ {{0x44268282,0x752d002e,0x636d80f7,0x60cd2ed7}}, // _npo_, meaz, _lúna, _mgam,
+ {{0x752d002e,0xbcfb2ed8,0x2d848118,0x798083f7}}, // leaz, _elét, eime_, _emmw,
+ {{0x442688cf,0x69ca8128,0x6da5aed9,0x38698916}}, // _apo_, _संगी, ника, šar_,
+ {{0x636000f2,0x60cd2eda,0x752d002e,0x2d84aedb}}, // _föns, _ngam, neaz, gime_,
+ {{0x6d5d2edc,0x6442aedd,0xa2d606bf,0x4426808e}}, // _mosa, _croi, मुक्, _cpo_,
+ {{0x644294e1,0x60cd20fc,0x4426aede,0xf1c9801c}}, // _droi, _agam, _dpo_, _hại_,
+ {{0x442684be,0x80c200d4,0x636d81a8,0x2d84aedf}}, // _epo_, रेने, _cúna, bime_,
+ {{0x6442aee0,0x6d5d2ee1,0x5c758256,0x752d0087}}, // _froi, _nosa, _плет, jeaz,
+ {{0x6d4baee2,0xeb8e96cf,0xf1c98028,0xec6e8dc0}}, // _inga, _би_, _mại_, _сп_,
+ {{0xf1c98104,0x27ed8f23,0xa3c08540,0x443fad3c}}, // _lại_, _mwen_, ंगण_, mvu_,
+ {{0x6d5d056f,0x69ce8e15,0x60cd002a,0x443faee3}}, // _bosa, íbet, _fgam, lvu_,
+ {{0x6d5d2ee4,0x753baee5,0xed5715d1,0xf65280be}}, // _cosa, nduz, нос_, נצן_,
+ {{0x6d5d2ee6,0x443f82ec,0x2fdf8069,0xf1ac1251}}, // [1ca0] _dosa, nvu_, _ntug_, _टीएन,
+ {{0x60dbaee7,0x25a02ee8,0x27e00110,0x79862ee9}}, // _afum, soil_, _itin_, likw,
+ {{0x6d4baeea,0x6d5d1d8c,0x090695bf,0x443faeeb}}, // _onga, _fosa, епен, hvu_,
+ {{0x2d84aeec,0x443f803a,0x7e6100f2,0x79862eed}}, // xime_, kvu_, älpe, nikw,
+ {{0x40349071,0x69c9aeee,0x63a1aeef,0x2d84aef0}}, // телс, _diee, koln, vime_,
+ {{0x6d4baef1,0xf1ca0129,0x4426aef2,0x60dbaef3}}, // _anga, _dại_, _rpo_, _efum,
+ {{0xe297111c,0xe5c68ada,0x443f9693,0x27ed8706}}, // _зар_, нсио, evu_, _ewen_,
+ {{0x9f42003e,0xd5b781bb,0xdb1c0214,0x29000135}}, // vský_, нсы_, _birç, _ndia_,
+ {{0x6d4b8359,0x6372006a,0x70af123a,0x79862ef4}}, // _dnga, _tænd, _घरेल, dikw,
+ {{0x8883a86e,0x2d84aef5,0x5c739663,0x752d002e}}, // _служ, sime_, ульт, zeaz,
+ {{0x27e0009c,0x7e9a8a47,0xa294902a,0x2a6a8061}}, // _atin_, _عنصر_, тані, ább_,
+ {{0x32029385,0x6442aef6,0x9f421c18,0x67d526b1}}, // čky_, _troi, rský_, кому,
+ {{0x38668722,0x752d0087,0x4426aef7,0x63a1aef8}}, // nyor_, veaz, _upo_, boln,
+ {{0x6d5d00a4,0x4432031d,0x66010019,0x69ce802a}}, // _sosa, dwy_, álko, íbes,
+ {{0xf1a72657,0x79a718ff,0x752d2ef9,0x644780eb}}, // _прин, _прие, teaz, ājie,
+ {{0x60cd2efa,0xbcfb25a7,0x29d70372,0xe534076a}}, // _ugam, _alér, _bħan_, лесь,
+ {{0x752d0012,0x7bcaaefb,0xfbd3006b,0x6d5d2efc}}, // [1cb0] reaz, _kifu, اتر_, _vosa,
+ {{0x673aa4a0,0x752d002e,0x60dbaefd,0x7bca804f}}, // _natj, seaz, _sfum, _jifu,
+ {{0x6d5d0013,0x752d0870,0x69c98029,0x61faaefe}}, // _tosa, peaz, _piee, nstl,
+ {{0x7ff5819f,0xbcfb0065,0x27ed86c0,0xb4db0980}}, // نستا, _elér, _pwen_, _anàv,
+ {{0xa3c283bb,0xe7399878,0xa3c0aeff,0x29d704b7}}, // ्दा_, дел_, ंगा_, _għan_,
+ {{0x25a6af00,0x6d4b80dd,0xdb0501a8,0x79862f01}}, // _akol_, _snga, rmhó, zikw,
+ {{0x6d4b80dd,0xed59af02,0x63a183c1,0x61ee0428}}, // _pnga, дой_, voln, _gwbl,
+ {{0xf1ca0104,0x92e900c8,0x63a1809a,0xf1a4af03}}, // _tại_, _যায়_, woln, _брун,
+ {{0x02c98aed,0x210401e2,0xe3b98703,0x656982af}}, // रश्न, nčių_, мби_, rkeh,
+ {{0x4a458e12,0x443faf04,0x200faf05,0x6569af06}}, // внов, rvu_, mugi_, skeh,
+ {{0x3f87af07,0x200faf08,0x6aaa831d,0x779087d2}}, // minu_, lugi_, _cyff, _دیوا,
+ {{0x3f87af09,0x6d4ba890,0x212f826c,0x69dc8174}}, // linu_, _unga, negh_, írea,
+ {{0x63a18db7,0x8d74af0a,0x79862f0b,0xb0a51774}}, // poln, _حالا, rikw, _गुदग,
+ {{0x25adaf0c,0x79862970,0xa283803d,0x25bf8087}}, // llel_, sikw, _میخو, lmul_,
+ {{0xbea60056,0x7af70079,0x6aaa8114,0xe3c880ff}}, // тавк, daxt, _gyff, _cựu_,
+ {{0x395faf0d,0x1614000f,0x7bcaaf0e,0x44320035}}, // _nous_, तंबर_, _zifu, twy_,
+ {{0xccf2812a,0xf773003d,0x3f87af0f,0x61fc0390}}, // [1cc0] יכן_, _چاپ_, kinu_, _svrl,
+ {{0x4432064c,0x200f8074,0xc88701ec,0x31caaac2}}, // rwy_, dugi_, ößer_, िद्ध,
+ {{0x3f878289,0xc33284de,0xf506028b,0x7ae58074}}, // dinu_, שוב_, _изно, _õhtu,
+ {{0xcfe680c8,0xb8162701,0x4a5a8039,0x200faf10}}, // _নিবন, _तमाम_, ודעו, fugi_,
+ {{0x395faf11,0x394d8775,0x261486b7,0x2d922f12}}, // _dous_, _dnes_, नूनी_, mnye_,
+ {{0x394daf13,0x3f87807b,0xbf9b00e7,0x2d9200dd}}, // _enes_, ginu_, quêt, lnye_,
+ {{0x6e288713,0x7bca9360,0x6aa301ec,0x2bd8143e}}, // _qpdb, _rifu, ünft, _भंडा,
+ {{0x2d922f14,0x7bcaaf15,0x3160011b,0xfbe78129}}, // nnye_, _sifu, _noiz_, _thế_,
+ {{0x3f878024,0x2d9200b9,0xd0e30c2d,0x47c68110}}, // binu_, inye_, _क्षण_, _абав,
+ {{0x3f8780d2,0x64460088,0x2d9200ee,0x3d148075}}, // cinu_, _mrki, hnye_, _धावे_,
+ {{0x27e68cc0,0xcb130039,0xc224826a,0x2d92036e}}, // lpon_, בלת_, اکتو, knye_,
+ {{0xfce6a1ae,0x64462f16,0x25a6820d,0x7984008e}}, // _родо, _orki, _ukol_, _cmiw,
+ {{0x7d02af17,0x2d9200b9,0x27e6af18,0xf1bf2ab5}}, // _idos, dnye_, npon_, rmá_,
+ {{0x29d704b7,0x257503ba,0x27e680e4,0x798403f7}}, // _bħal_, _måle_, ipon_, _emiw,
+ {{0x64462f19,0x636d81a8,0xfbb781c6,0xc333825f}}, // _arki, _múnl, _שפות_, _כוס_,
+ {{0x6446017f,0x39150391,0x81e100ab,0x69cb0107}}, // _brki, _смир, _দিল_, _सूची,
+ {{0x95c80bac,0x0dc82f1a,0x644488ae,0x5bb90fe6}}, // [1cd0] тура_, тури_, _šiij, елая_,
+ {{0x395faf1b,0xdb01af1c,0x2571af1d,0x64460140}}, // _sous_, golè, _mála_, _drki,
+ {{0x3f87803b,0x29d72f1e,0x64462f1f,0x7d0281ac}}, // vinu_, _għal_, _erki, _odos,
+ {{0x7d0280f1,0x6446026c,0x69c88037,0x200faf20}}, // _ndos, _frki, _èdel, tugi_,
+ {{0x395f9c8d,0x3f87af21,0x2575016d,0x6d4f0888}}, // _vous_, tinu_, _våld_, _inca,
+ {{0x753d0117,0xa2be00d4,0xa3b28105,0xf1ca001c}}, // _hasz, _वृक्, _झील_, _hạt_,
+ {{0x395f86e3,0x44392f22,0x753d29bd,0x200faf23}}, // _tous_, _hss_, _kasz, sugi_,
+ {{0x2249803a,0x394d809f,0x3f87af09,0xdb01af24}}, // _čak_, _unes_, sinu_, molé,
+ {{0xc9849506,0x5184830d,0x69d52f25,0xcfab8065}}, // _тури, _тура, _huze, _قائم_,
+ {{0x25bf802e,0x25ad816b,0x44392f26,0x69d52f27}}, // smul_, slel_, _mss_, _kuze,
+ {{0x75158307,0x443912b7,0x198a0ae7,0xa18a00c4}}, // _مواض, _lss_, ебни_, ебна_,
+ {{0x753d0d38,0x44390687,0x69d52f28,0x78ad228b}}, // _nasz, _oss_, _muze, _byav,
+ {{0x69d52f29,0xaac3000d,0x2d8909c4,0x63a509da}}, // _luze, वेतक, diae_, kohn,
+ {{0x6d4f2f2a,0x77ad8118,0xdce28140,0xe2998791}}, // _anca, fúxi, _ploć, _пал_,
+ {{0xa3c2800f,0x63a52f2b,0x656d0359,0x44392f2c}}, // ्दर_, dohn, fkah, _ass_,
+ {{0x6d5b8867,0x656d2575,0x2614816f,0x442b2f2d}}, // njua, gkah, नंती_, _bpc_,
+ {{0x44392f2e,0x03260162,0xe3ba2f2f,0xa4460071}}, // [1ce0] _css_, лдан, еба_, ынад,
+ {{0x6d4f2f30,0x68f60085,0x442b2f31,0x63768087}}, // _enca, _heyd, _dpc_, _gând,
+ {{0x6146af32,0xd9469fcb,0x471b00be,0x2d9200b9}}, // лежа, лежи, _אויג, pnye_,
+ {{0x44392f33,0x2d89002a,0x673e2f34,0xdb01a1bf}}, // _fss_, ciae_, _hapj, solè,
+ {{0x68f61d5a,0x776188f1,0x6d5b9abf,0x64460bf0}}, // _meyd, _bolx, djua, _urki,
+ {{0x27f2031d,0x8fa3adc3,0xfaa62154,0x753d066f}}, // _mwyn_, баче, _баго, _zasz,
+ {{0x27e6af35,0x09cf80a5,0xe1e78019,0x7c2b80eb}}, // spon_, _संगठ, _دس_, _apgr,
+ {{0x44202f36,0x7bce0587,0x44390683,0x6d5b8168}}, // oti_, _hibu, _yss_, gjua,
+ {{0x44202f37,0xa3c2835a,0x7bce0555,0x69d500ad}}, // nti_, ्दल_, _kibu, _zuze,
+ {{0x44202f38,0x3f8a022e,0x673e0019,0x69d519d5}}, // iti_, jibu_, _napj, _yuze,
+ {{0x44200a92,0xdd8f8bca,0x6b7b098a,0x63768087}}, // hti_, _کون_, גרינ, _rând,
+ {{0x84468b76,0x7bce2f39,0x7d02809a,0xddb68019}}, // _مختل, _libu, _udos, _صحاب,
+ {{0x44202f3a,0x68f62f3b,0x69c2808e,0x20000362}}, // jti_, _deyd, imoe, _cvii_,
+ {{0x44392f3c,0x447b893f,0x7bce0ab4,0x78ad018e}}, // _rss_, _אנדע, _nibu, _vyav,
+ {{0x44202f3d,0x7afa8364,0xd5bb15a4,0x2904805c}}, // eti_, matt, еса_, _odma_,
+ {{0x44202f3e,0x7bce2f3f,0x61fe2280,0x63a501ec}}, // fti_, _aibu, dspl, wohn,
+ {{0x656d2f40,0x442b01df,0x44200dc3,0x7bce1c61}}, // [1cf0] rkah, _qpc_, gti_, _bibu,
+ {{0x443902a5,0x59d901b6,0x7bce2f41,0x34d195a7}}, // _vss_, _बंदर, _cibu, _हल्द,
+ {{0x5a352f42,0x44202f43,0x7afa80e5,0x44390299}}, // рнет, ati_, iatt, _wss_,
+ {{0x7afaaf44,0x673e0654,0x44202f45,0xe9ab9e29}}, // hatt, _zapj, bti_, ردان_,
+ {{0x68f98063,0x44202f46,0xda010076,0xb97c0158}}, // rawd, cti_, _लटकत_, ענדי,
+ {{0x6d478029,0xa0669bf2,0xf74584db,0x7bce04a7}}, // ējam, раша_, реко, _gibu,
+ {{0x7afaaf47,0x7f3c00be,0xff048ea6,0xb87b0118}}, // datt, געוו, сячн, ncíb,
+ {{0xdb150118,0x656280c3,0x3f8a02ec,0x7bce06a0}}, // _tizó, _cooh, zibu_, _zibu,
+ {{0x6d5b94ec,0x236301c0,0x7bce0cb7,0x28c701a2}}, // rjua, _kojj_, _yibu, रेमि,
+ {{0x7afaaf48,0x25a08ee1,0x21290239,0x42259fab}}, // gatt, čila_, _abah_, адов,
+ {{0xe299af49,0x44202f4a,0x491a02f1,0x45d5af4b}}, // тан_, zti_, _भादो_, _тоес,
+ {{0x44202f4c,0x213f8867,0x68f60086,0x69d80073}}, // yti_, _jauh_, _qeyd, ívei,
+ {{0x7afaa280,0x3f8a022e,0x671c80a5,0x290c07c0}}, // batt, tibu_, _नामक_, ğda_,
+ {{0xcb6684dd,0xb4c19094,0x68f602a3,0x7afa8081}}, // _ваше_, ंधी_, _weyd, catt,
+ {{0x3f8a022e,0x7bce178f,0x0fc300e8,0x24f616cf}}, // ribu_, _ribu, ійсн, ичар,
+ {{0x442021da,0x7bce2f4d,0x3f8a2f4e,0x257185b9}}, // tti_, _sibu, sibu_, _málo_,
+ {{0x3b860110,0x44201aea,0x6386041c,0x31bc801b}}, // [1d00] алаг, uti_, агаа, níze_,
+ {{0x44200743,0xfbdf0142,0x394010af,0x61fe1d31}}, // rti_, _trên_, _hais_, tspl,
+ {{0x201faf4f,0x4aab8076,0x39402f50,0x69c280c9}}, // _aqui_, _घुमव, _kais_, tmoe,
+ {{0xc8ca8154,0x61fe2f51,0x636d816a,0x39402f52}}, // نوان_, rspl, _búnk, _jais_,
+ {{0x394019c3,0x7bce04a7,0x69c2a023,0x61fe2f53}}, // _mais_, _tibu, rmoe, sspl,
+ {{0x39402f54,0x63768087,0x69c2af55,0x9e069bb1}}, // _lais_, _mânc, smoe, ачил,
+ {{0x9c87026f,0xbcfb010c,0x798baf56,0xd6d98163}}, // počí, _moén, bigw, уті_,
+ {{0x5d5498a0,0x39402f57,0x6443009a,0xe29701a1}}, // скит, _nais_, łnie, _тас_,
+ {{0x7afaaf58,0x6d40ad03,0x68ed83a8,0xf1d00a27}}, // tatt, rdma, ñade, _संजन,
+ {{0x39520813,0xf36695b5,0x6562808e,0x7b668f13}}, // _anys_, ртин, _wooh, ртие,
+ {{0x7afaaf59,0x1b0300c8,0x0bb70039,0x39402f5a}}, // ratt, র্কে_, שלים_, _bais_,
+ {{0x644901e2,0x7afa920b,0x39402f5b,0x672d80ce}}, // _šeim, satt, _cais_, đaje,
+ {{0x39402f5c,0x7afaaf5d,0xc8642f5e,0x63768187}}, // _dais_, patt, отри, _cânc,
+ {{0x93fb8051,0x7afa8372,0x3ea98162,0xd7f80d15}}, // _ילדי, qatt, şate_, шую_,
+ {{0x394002be,0xe8d90135,0x63a8af5f,0xf1bf81d6}}, // _fais_, _amụ_, modn, žšia_,
+ {{0xda5b8051,0x69d88fee,0x2129010b,0x2baf0d86}}, // _יכול, _iuve, _ubah_, _जीजा,
+ {{0x54b901bb,0x68fd2f60,0x7af881c0,0x69d888dc}}, // [1d10] угая_, masd, _kevt, _huve,
+ {{0x8ae7035f,0x3b541242,0x69d8af61,0x28c7009a}}, // ріал, окур, _kuve, रेणि,
+ {{0xcfe200c8,0x213f8748,0x69da0019,0x39520114}}, // _বিএন, _pauh_, _étel, _ynys_,
+ {{0x29190397,0x63a8af62,0x69d8997a,0x68fd2f63}}, // ngsa_, hodn, _muve, nasd,
+ {{0x8d5a0051,0xdee581cf,0x69d8af64,0x504583a7}}, // _עכשי, боли, _luve, селб,
+ {{0x69d882be,0xcc3a0158,0xdc3a0158,0x543a00be}}, // _ouve, _געשט, _געשר, _געשא,
+ {{0x798b8010,0x69d88c16,0x81e10264,0x92580436}}, // pigw, _nuve, _দিক_, _карт_,
+ {{0x7bc52f65,0xf1ca00ff,0x069801c6,0xddd50e06}}, // lmhu, _nạp_, ידום_, nyző,
+ {{0x394000f6,0x7989804f,0x69d88036,0x201f8118}}, // _rais_, _imew, _auve, _uqui_,
+ {{0xf77395e4,0x63a8af66,0x4ea792bc,0x39402f67}}, // _بار_, godn, _креа, _sais_,
+ {{0x39402f68,0x636d8019,0xa96a1c79,0xd946116b}}, // _pais_, _júni, гина_, бени,
+ {{0x394000dd,0xdb050106,0x249d81c5,0x636d957a}}, // _qais_, llhä, _txwm_, _múni,
+ {{0x63a8883d,0x39402f69,0xab298bda,0xb900109b}}, // bodn, _vais_, _бола_, _तल_,
+ {{0x2bdb023c,0x53348ada,0x442f80ee,0x7777002a}}, // _बढ़ा, _мечт, _ipg_, _clxx,
+ {{0x394001e9,0x69d8af6a,0xa50a07a1,0x261801fe}}, // _tais_, _guve, лева_, बंधी_,
+ {{0xf1d01299,0x1c030006,0x442f80ee,0xa19309a5}}, // _सूचन, _लिखल_, _kpg_, _најч,
+ {{0xb87b2f6b,0x69d881ec,0x7bd98326,0xdb018144}}, // [1d20] rcíc, _zuve, _juwu, holí,
+ {{0xf3f98a49,0x26180540,0x46f6159f,0x7989804f}}, // _আমার_, बंदी_, _учит, _amew,
+ {{0x6e4603f8,0x2002127a,0x69d8862f,0x2571808b}}, // _انجم, lski_, _xuve, _páll_,
+ {{0x9f4907ca,0x442f8115,0xdb01a0ce,0xf8fa81a8}}, // _hvað_, _opg_, dolí, اءات_,
+ {{0x7f41af6c,0x7bd9803d,0x68e40c53,0xbf9b0866}}, // _balq, _nuwu, _mfid, prêt,
+ {{0x7f41862f,0x6d44011e,0xb87b0511,0x798991d3}}, // _calq, ldia, ncía, _emew,
+ {{0x644baf6d,0x41b6803d,0x65be8168,0x8afb04de}}, // _argi, _همسر, hëhe, _תהלי,
+ {{0x25a0a52d,0x63a8809a,0xb99680f7,0x657b96fb}}, // čilo_, wodn, _الرب, ghuh,
+ {{0x7c242f6e,0x69d8af6f,0x69c08009,0x656600ee}}, // ntir, _suve, _ihme, _mokh,
+ {{0x20020289,0x4f658013,0x6566029b,0x68e42f70}}, // dski_, _والف, _lokh, _afid,
+ {{0x9f5d0324,0x442f8695,0xe13521f6,0x68e92509}}, // _ìwé_, _epg_, онны, ñedo,
+ {{0x3f8e8010,0x5a9b00be,0x442f8118,0x7afe1ab3}}, // lifu_, רשטא, _fpg_, hapt,
+ {{0x6d440355,0x63a8803b,0x7afe2f71,0x68fd2f72}}, // ddia, podn, kapt, rasd,
+ {{0x7f418201,0x60d60019,0x3f8e8010,0x6d442f73}}, // _xalq, _egym, nifu_, edia,
+ {{0x41e68bbe,0xf1ca001c,0x65662f74,0xdb1c01a8}}, // _استف, _tạp_, _bokh, _ghré,
+ {{0x1619835a,0x6615011e,0xd0110b8c,0x56942f75}}, // नंतर_, ruzk, _قلب_, част,
+ {{0xdd1c816b,0x7c2400e4,0x9817136d,0xe5208105}}, // [1d30] kážk, gtir, _ابعا, _यानि_,
+ {{0x63600065,0x6615011e,0x27e910ab,0x25a90748}}, // _köny, puzk, _itan_, soal_,
+ {{0x6d440b50,0x7c242f76,0xd25101a8,0x7a3501a8}}, // bdia, atir, بنا_, تفاص,
+ {{0xac191480,0x68e2af77,0x7c242b6c,0x798f0db1}}, // _кому_, gcod, btir, nicw,
+ {{0x7afe2f78,0x27e90101,0x442480b9,0xe8f89138}}, // bapt, _jtan_, jtm_, слі_,
+ {{0x7afe2f79,0x2d9a8019,0xdb01af7a,0xd7c8af7b}}, // capt, épen_, tolí, مونه_,
+ {{0xdb18826f,0x442f82f7,0x7bd980d7,0x6d42af7c}}, // _chví, _spg_, _suwu, _baoa,
+ {{0x2409af7d,0xdb01af7e,0x68e2af7f,0x442f9670}}, // ании_, rolí, ccod, _ppg_,
+ {{0x75ca809a,0x1c4297ae,0x7f4180ee,0x0b59876a}}, // _języ, дным, _talq, арны_,
+ {{0x68e42f80,0x657b8010,0x645b8118,0x644b80c3}}, // _sfid, shuh, rxui, _vrgi,
+ {{0xcb120bea,0x2002009a,0xeab180f7,0x68fb80dd}}, // ולי_, wski_, بعة_, _keud,
+ {{0x2002003a,0x68fb82be,0x443d8069,0xa4f780d5}}, // tski_, _jeud, _tsw_, _اکثر_,
+ {{0x442f802a,0x443d81ec,0xbee5016f,0x68fb851e}}, // _upg_, _usw_, कडून_, _meud,
+ {{0x7d1b8074,0x94748875,0x68fb81e4,0x257c01a8}}, // lgus, _عدنا, _leud, _míle_,
+ {{0xfc3f160a,0xdce40279,0x27e92f81,0x212daf82}}, // _así_, nkić, _etan_, _obeh_,
+ {{0x20021487,0x7c242f83,0x75298081,0x291f8069}}, // pski_, ttir, nfez, _ncua_,
+ {{0xf7462f84,0xab662f85,0x61e902d4,0x3366102a}}, // [1d40] _депо, овал, _čela, оваг,
+ {{0x6d44002a,0x2575016d,0xb7d7004e,0x3cff8282}}, // sdia, _håll_, _گویا_, hauv_,
+ {{0xe0daaf86,0xdce40754,0x68e28bfd,0xfc3f0091}}, // аве_, jkić, tcod, _esí_,
+ {{0x64430035,0x671c82f1,0x27e90106,0x68fb851e}}, // łnia, _नाहक_, _ytan_, _ceud,
+ {{0x68fbaf87,0x68ed85e4,0x3f8eaf88,0x183680a0}}, // _deud, ñada, tifu_, تراح,
+ {{0x68e28f67,0x7d1baf89,0x69d82f8a,0x7c3b8123}}, // scod, egus, íves, dwur,
+ {{0xe2972f8b,0xdd1c816b,0x3f8eaf8c,0x7afc0162}}, // _дар_, rážk, rifu_, _iert,
+ {{0x3f8eaf8d,0x7d1baf8e,0x7ae901a1,0x717680d7}}, // sifu_, ggus, _đeti, _بهرا,
+ {{0x7afc2f8f,0x660381c0,0xe81603b7,0x7c3b8133}}, // _kert, asnk, _तमगा_, gwur,
+ {{0x69dc2f90,0xe4570158,0x442480ee,0x3946af91}}, // _kure, קייט_, rtm_, ldos_,
+ {{0xc4e6af92,0x798f2f93,0xbcfb04be,0x3946af94}}, // _джей, ticw, _eléy, odos_,
+ {{0x69dc2f95,0x3946af96,0x7afc2c98,0x21200176}}, // _mure, ndos_, _lert, _ccih_,
+ {{0x81ba00ab,0x3946af97,0x7dd58110,0x84968019}}, // _অংশ_, idos_, _išsa, _بجائ,
+ {{0x69dc062f,0x25a996f2,0x6738911b,0x7d00af98}}, // _oure, čale_, nevj, lams,
+ {{0xb7bb041c,0xccfb0eef,0x5559047f,0x645d862c}}, // stão, ића_, бавя_, äsiv,
+ {{0xdb1c0118,0x394481a9,0x7d00af99,0x7afc2f9a}}, // _oiró, _nams_, nams, _aert,
+ {{0x7afc100d,0x69dc2f9b,0x27e92f9c,0x0bd500f7}}, // [1d50] _bert, _aure, _utan_, سياح,
+ {{0x69dc2f9d,0x7afc10b6,0x20192f9e,0x6738af9f}}, // _bure, _cert, musi_, jevj,
+ {{0x20191ad4,0x7afc00d8,0xea00001c,0x28c71516}}, // lusi_, _dert, _đảo_, रेरि,
+ {{0x69dc2fa0,0x3f910052,0xf55900f7,0x7d008bcf}}, // _dure, lizu_, _الحب_, jams,
+ {{0x69dc2fa1,0x66e591b1,0x20192fa2,0xa3ca816f}}, // _eure, пола, nusi_, लगा_,
+ {{0x7afc2fa3,0x39468510,0x213900f1,0x69dc2fa4}}, // _gert, ados_, hesh_, _fure,
+ {{0x7c390214,0x7bdd2fa5,0xf77092c8,0x69dc2fa6}}, // şarı, _husu, خان_, _gure,
+ {{0x7bdd2fa7,0x20192fa8,0x7afc2fa9,0x7c3bafaa}}, // _kusu, kusi_, _zert, twur,
+ {{0x20192fab,0x7bdd2fac,0x7d1bafad,0x69dc2fa9}}, // jusi_, _jusu, rgus, _zure,
+ {{0x7bdd2fae,0x6449afaf,0x20192fb0,0x2571807b}}, // _musu, rvei, dusi_, _máli_,
+ {{0xcb12093f,0x2b1c85b3,0x6449afb1,0x7ae50014}}, // עלט_, _मारु_, svei, icht,
+ {{0x291dafb2,0xac198b71,0xaca38133,0xdb0a80f7}}, // ngwa_, _году_, _ahụk, _bhfó,
+ {{0x20192fb3,0x7bdd2fb4,0xdce28088,0x5ed080ab}}, // gusi_, _nusu, _glođ, _স্পে,
+ {{0xdb1c0706,0xc3110264,0x636d8174,0x7bd503df}}, // _chrì, স্তি_, _cúnt, _nizu,
+ {{0xe29690fc,0xed5a0847,0xdb01afb5,0x272f83bf}}, // _наш_, бов_, colá, mını_,
+ {{0x7afc14ff,0x20192fb6,0x6372013c,0x7bdd2fb7}}, // _sert, busi_, _tænk, _busu,
+ {{0x7afc2fb8,0x29022fb9,0x69dc2fba,0x7bdd02a3}}, // [1d60] _pert, maka_, _sure, _cusu,
+ {{0x29022fbb,0xef860c5c,0x272f861c,0x04fc00ab}}, // laka_, _хлоп, nını_, ইলের_,
+ {{0x7afc2fbc,0x7dc58511,0x7bdd002a,0x19ba82c7}}, // _vert, mósf, _eusu, будь_,
+ {{0x7ae50307,0x7afc206f,0xe5a60ada,0x69d60708}}, // acht, _wert, _хими, _jiye,
+ {{0x7afc0393,0x2d922fbd,0x672d80d2,0x25ad837a}}, // _tert, miye_, đajn, doel_,
+ {{0x69dc2fbe,0x6d462fbf,0x29022fc0,0x2d922fc1}}, // _ture, _haka, haka_, liye_,
+ {{0x6d462fc2,0x29022fc3,0x272f8059,0xfaa32fc4}}, // _kaka, kaka_, dını_, _заро,
+ {{0x6d462fc5,0x290209a6,0x2d922fc6,0x7ec70019}}, // _jaka, jaka_, niye_, lépé,
+ {{0x6d460846,0x7bdd2fc7,0x6618816b,0xb4c880d4}}, // _maka, _xusu, suvk, ोखे_,
+ {{0x6d462588,0x6843a87d,0xfc2f9a3c,0x20192fc8}}, // _laka, _януа, احي_, vusi_,
+ {{0x29022fc9,0x2d922fca,0x213900f1,0x69d62fcb}}, // faka_, kiye_, tesh_, _biye,
+ {{0x6d462fcc,0x20192fcd,0x644a00eb,0x69c41e68}}, // _naka, tusi_, āfij, _chie,
+ {{0x69d62fce,0x2d922fcf,0x69c4008e,0x21390168}}, // _diye, diye_, _dhie, resh_,
+ {{0x20192fd0,0x6d46076d,0x321a1d21,0x1be306a7}}, // rusi_, _aaka, kupy_, _कंबल_,
+ {{0x29022fd1,0xc6a41109,0x7bdd2fd2,0x644f017f}}, // baka_, ерти, _susu, _srci,
+ {{0x2d802fd3,0x6d461083,0x20192fd4,0x2d9202a0}}, // ghie_, _caka, pusi_, giye_,
+ {{0x071c9d01,0x6d462fd5,0xea000028,0x5c99027e}}, // [1d70] _नांव_, _daka, _đạo_, цкая_,
+ {{0xe3b8884a,0xa87c0039,0x7ae501ec,0xad272385}}, // nlı_, _לאחר, ucht, _کردو,
+ {{0x7bd52fd6,0x32070065,0x6d462fd7,0x68ed82ba}}, // _vizu, ény_, _faka, ñado,
+ {{0x2d802fd8,0x4426802a,0x637307d9,0x7ae501ec}}, // chie_, _oqo_, _sını, scht,
+ {{0x25ad80c9,0xb4d909c8,0xe3b88214,0x4426afd9}}, // voel_, ाशी_, klı_, _nqo_,
+ {{0x44292fda,0xaca3019d,0xe80e83b7,0x644d2fdb}}, // mta_, _laịb, _सिरा_, mvai,
+ {{0x6d462fdc,0x29022fdd,0xc33200be,0xee3980e8}}, // _yaka, yaka_, _שול_, іни_,
+ {{0x44292fde,0xd6580039,0x5ebf00ab,0xc1740326}}, // ota_, ליות_, _আলাই, _kiɗe_,
+ {{0xe3b888c5,0x2d838110,0x272f817b,0x644d22b2}}, // flı_, ėje_, tını_, nvai,
+ {{0x4429171c,0x29022a75,0x69d62fdf,0x29d781b9}}, // ita_, waka_, _siye, _għar_,
+ {{0x29022fe0,0x44292fe1,0x272f8afe,0x62852fe2}}, // taka_, hta_, rını_, nzho,
+ {{0x4429088b,0x272f8459,0x72078019,0x2cae819d}}, // kta_, sını_, _ہفتہ_, ịmị_,
+ {{0x29022fe3,0x6d462fe4,0xa01b0065,0x3b099232}}, // raka_, _raka, szön, _дело_,
+ {{0x6d462fe5,0x44292fe6,0x6f03afe7,0x6d49811e}}, // _saka, dta_, nanc, ldea,
+ {{0x6d462fe8,0x29022fe9,0x2d922fea,0xa2c9820e}}, // _paka, paka_, tiye_, हेश्,
+ {{0x6d49afeb,0x60db8d48,0x44290b0b,0xed572fec}}, // ndea, _ngum, fta_, мос_,
+ {{0x6d462fed,0x44292fee,0x2d922fef,0x6d49803c}}, // [1d80] _vaka, gta_, riye_, idea,
+ {{0x6d462ff0,0x2d922ff1,0x7c298b2b,0x6d498f31}}, // _waka, siye_, iter, hdea,
+ {{0x6d462ff2,0x44292ff3,0x7c29aff4,0x673c1e81}}, // _taka, ata_, hter, merj,
+ {{0x27e0025d,0x3d1805b3,0x44292ff5,0x443f882e}}, // _kuin_, _फाटे_, bta_, kwu_,
+ {{0x44291883,0x27e003d3,0x7d042ff6,0xe3b88182}}, // cta_, _juin_, mais, ylı_,
+ {{0x7d042ff7,0x6d59aff8,0x6f03aff9,0x673c2ffa}}, // lais, _anwa, ganc, nerj,
+ {{0x7c29affb,0x27edaffc,0xa3c38beb,0x656baffd}}, // eter, _eten_, ्षन_, _bogh,
+ {{0x7d042ffe,0x7c29afff,0x257880e7,0x753b804f}}, // nais, fter, _vélo_, geuz,
+ {{0x673c1a67,0x7c29b000,0x656bb001,0x2b47b002}}, // kerj, gter, _dogh, _banc_,
+ {{0x6f0381b1,0x7d041276,0x2d988019,0x2b47b003}}, // canc, hais, őre_, _canc_,
+ {{0x7d043004,0x656b8c64,0x81bd0029,0xa3dea6ee}}, // kais, _fogh, stēm, _दूर_,
+ {{0xaca48135,0x6d60816b,0x661c3005,0x7d043006}}, // _akụz, ávač, durk, jais,
+ {{0x44293007,0xdb088511,0x29002c03,0x27e00229}}, // xta_, lodí, _deia_, _cuin_,
+ {{0xe3b88201,0x27e03008,0x81bd00eb,0x673c3009}}, // qlı_, _duin_, otēk, gerj,
+ {{0xdfd41641,0xd90499f4,0x67d40ca4,0x79a71e25}}, // торы, _پی_, тору, _орие,
+ {{0x7d04300a,0x656bb00b,0x29ee8326,0x62850824}}, // gais, _xogh, mƙa_, vzho,
+ {{0x673c300c,0x4429300d,0x6f03b00e,0x7bd8b00f}}, // [1d90] berj, uta_, yanc, _kivu,
+ {{0x661c2d27,0x752d3010,0x31a403bf,0xa3c3b011}}, // burk, sfaz, mıza_, ्षय_,
+ {{0x7c29b012,0x7d043013,0x644d3014,0x27edb015}}, // zter, bais, svai, _sten_,
+ {{0x44293016,0x6f03b017,0xaca30133,0x764502a0}}, // pta_, wanc, _abụb, _ishy,
+ {{0x39493018,0x656b861f,0x06f30a74,0x44293019}}, // _maas_, _rogh, _अभाव_, qta_,
+ {{0xf1ca001c,0x656b80f7,0xa3e400d4,0x705292dc}}, // _dạy_, _sogh, _पंत_, انوا,
+ {{0x81bd0029,0xd90f80d5,0x6d49b01a,0x45d580b3}}, // rtēj, _نیا_, tdea, нцит,
+ {{0x6f03b01b,0x79828063,0x7c299140,0x1c45b01c}}, // sanc, chow, tter, еним,
+ {{0x7c29b01d,0x27ed835f,0xfc48801c,0x3dc68079}}, // uter, _uten_, _cử_, _dhow_,
+ {{0x6d49851e,0x61e90796,0xc48286d2,0x2d8c0866}}, // sdea, _čelj, ульк, édez_,
+ {{0x673c301e,0x7bd8b01f,0x68e48024,0x394910b0}}, // verj, _divu, žide, _baas_,
+ {{0x6d598114,0x92df80ab,0xf99f3020,0x27e009ff}}, // _unwa, তরে_, gnè_, _puin_,
+ {{0x673c3021,0xa3db000f,0x6007035f,0x27e0009f}}, // terj, _ढंग_, нням_, _quin_,
+ {{0xfaa584f6,0x201d82c4,0x29ee8326,0x7bd89a14}}, // _пало, iuwi_, bƙa_, _givu,
+ {{0xf772810f,0x7d043022,0x6f01b023,0xed5a16ba}}, // נקל_, tais, _melc, пов_,
+ {{0x27e0038e,0x61ee86a5,0x673c02d4,0x2d9f80e7}}, // _tuin_, íble, serj, nnue_,
+ {{0x7d043024,0x673c01b9,0xfc488028,0xdee61f3e}}, // [1da0] rais, perj, _xử_, _попи,
+ {{0x7d043025,0x60c4005d,0x8fa63026,0xdb1c01a8}}, // sais, _izim, _سمجه, _bhrá,
+ {{0xdb1c003e,0xe9da2a0e,0xc33282f6,0x4efb0039}}, // _chrá, чке_, רוב_, _מהיו,
+ {{0xfbdf00e7,0x4aaf0072,0x307a03de,0xdb0884e8}}, // _prêt_, _जुळव, ּאַנ, vodí,
+ {{0x6f018358,0xb8e88fd5,0x26180035,0x201d82d5}}, // _belc, _उर_, बंकी_, guwi_,
+ {{0x38cb8bca,0x3dc6b027,0xdfcf880b,0x60c400b8}}, // سانی_, _show_, مين_, _mzim,
+ {{0xfc488104,0x03a30b9c,0x53a32597,0x6f01b028}}, // _sử_, _сиро, _сарб, _delc,
+ {{0x7bd8b029,0x60c4302a,0x8c66838b,0x5d54b02b}}, // _sivu, _ozim, етод, ткит,
+ {{0x60c42f0e,0x10a38a2e,0x3949302c,0x81e78264}}, // _nzim, _битн, _raas_, মীম_,
+ {{0x6446008e,0xf8bf0036,0x25a00824,0x53b001a2}}, // _mski, ngée_, hnil_, जकिश,
+ {{0xc3331a0f,0x60c41f62,0x7bd8b02d,0x25a002d4}}, // אות_, _azim, _vivu, knil_,
+ {{0xfc488104,0x4c868098,0x4a9b00be,0x546a8081}}, // _tử_, _плев, לייג, _наем_,
+ {{0xb90905e8,0xdb0883a7,0xc95384de,0x25718061}}, // _बल_, godã, אמר_, _vált_,
+ {{0xa3c38e70,0x60c400eb,0x25a08353,0x3949302e}}, // ्षण_, _dzim, čilu_, _waas_,
+ {{0x3949302f,0x60c40316,0x64463030,0x6a869a1f}}, // _taas_, _ezim, _aski, _défé,
+ {{0xd5b83031,0x25a002d4,0x257c128a,0x3eba0163}}, // есу_, gnil_, _dílo_, _dypt_,
+ {{0x2ef50698,0x68ed017b,0xe802801b,0xafdb1277}}, // [1db0] _изпр, _ifad, रीमा_, rvøs,
+ {{0xc5f680ab,0x200b3032,0x8aa7031f,0x889c0039}}, // _ঘটনা_, msci_, нред, _מבחי,
+ {{0x64463033,0xd5df800f,0x321eb034,0xdd9100d7}}, // _eski, _पूंज, nuty_, _نود_,
+ {{0xc17400fc,0x7ec70032,0x1a6801f9,0x27e68299}}, // _kiɗa_, _eépí, ریقی_, fqon_,
+ {{0x2d84b035,0x6f01b036,0x6d5d0b03,0x64460326}}, // dhme_, _pelc, _insa, _gski,
+ {{0x7bdf83d3,0x7c2d020f,0x656f0352,0x39498df1}}, // _équi, mtar, _hoch, žas_,
+ {{0xfbdf0073,0xe7088154,0x6372008b,0x61dc00d4}}, // _três_, رتون_, _vænt, यदृष,
+ {{0x6d4d3037,0x7dd58110,0x6f0184e7,0x49049a37}}, // ndaa, _išsk, _welc, _موفق,
+ {{0x656f3038,0x200b00d2,0x7d02802e,0x6f01accd}}, // _moch, jsci_, _deos, _telc,
+ {{0x656f0083,0x7c2d3039,0x24890042,0x67200035}}, // _loch, itar, izam_, _बाइक_,
+ {{0xfaa59980,0x6a8682be,0x7c2d2b42,0x69c981e9}}, // тано, _réfé, htar, _khee,
+ {{0x656f303a,0x7d02b03b,0x853000fc,0x2d9f8037}}, // _noch, _geos, kiɗa, qnue_,
+ {{0x6d4d0ef2,0x7c2d0168,0x8ccd8035,0x1b0c0264}}, // ddaa, jtar, देशो, স্টে_,
+ {{0x6d4bb03c,0x24891a1d,0x6d5d303d,0x6569b03e}}, // _haga, dzam_, _ansa, ljeh,
+ {{0x6d4bb03f,0x656f3040,0x7c2d3041,0x20193042}}, // _kaga, _boch, etar, arsi_,
+ {{0x656f3043,0x6d4bb044,0xe8029834,0x44393045}}, // _coch, _jaga, रीडा_, _bps_,
+ {{0x60c402a5,0x6d4b8fa9,0x25a03046,0x7c2d3047}}, // [1dc0] _uzim, _maga, rnil_, gtar,
+ {{0x6d5d3048,0x61e3930c,0xa3c1823c,0x6f07003b}}, // _ensa, _kunl, ंति_, cajc,
+ {{0x442db049,0xbcfb087a,0x3f98005c,0x6d4d15e8}}, // hte_, _poét, miru_, bdaa,
+ {{0x6d4bb04a,0x3f98010b,0x656f304b,0x6d4d0079}}, // _naga, liru_, _goch, cdaa,
+ {{0x69c982a3,0x7c2d304c,0xa3c1a8b3,0x69db8722}}, // _dhee, ctar, ंता_, _diue,
+ {{0x4420304d,0x442d8ed8,0x7d02b04e,0x6d4b9bdb}}, // mui_, dte_, _seos, _aaga,
+ {{0x4420304f,0x6d4bb050,0x442d929d,0x26dc026f}}, // lui_, _baga, ete_, _úvod_,
+ {{0x75243051,0x7bdc3052,0x6d4bb053,0x657ba575}}, // ngiz, _hiru, _caga, gkuh,
+ {{0x6d4bb054,0x442db055,0x61e900d2,0x44203056}}, // _daga, gte_, _čeli, nui_,
+ {{0x2d8488cf,0x671c81ab,0x61e382bb,0x201902a5}}, // shme_, _नाटक_, _bunl, vrsi_,
+ {{0x44200867,0xdee387b6,0x442db057,0xf3638190}}, // hui_, _тохи, ate_, ртын,
+ {{0x442db058,0x6d4bb059,0x7c2d305a,0xfd5501bc}}, // bte_, _gaga, ytar, _draị,
+ {{0x442d8bf6,0x656f305b,0x4420305c,0x2904804f}}, // cte_, _roch, jui_, _mema_,
+ {{0x6d40b05d,0x6d4bb05e,0x4420305f,0x656f03b3}}, // lema, _zaga, dui_, _soch,
+ {{0x443902ee,0x35e4035f,0xda6f8698,0x6d4b81d3}}, // _sps_, ицтв, _тя_, _yaga,
+ {{0x7c2d3060,0x672d812b,0x6d40938e,0xdd11011c}}, // ttar, đaji, nema, _düşd,
+ {{0x44203061,0x54550b71,0x6d4d3062,0x7bdc3063}}, // [1dd0] gui_, ыват, rdaa, _biru,
+ {{0x09cb8eed,0x2d8210f4,0x6d408006,0x656f0352}}, // िष्य, _elke_, hema, _woch,
+ {{0x442db064,0x7bdc3065,0x69c981c5,0x6d40b066}}, // zte_, _diru, _phee, kema,
+ {{0x7c2d08cf,0x6d5d1341,0x6d40b067,0x44203068}}, // ptar, _unsa, jema, bui_,
+ {{0x6d4bb069,0x442db06a,0x7bdc306b,0x69db823e}}, // _raga, xte_, _firu, _viue,
+ {{0x7dd581e2,0x442d826f,0x7bdc306c,0xe8d9019d}}, // _išsi, vte_, _giru, _nnọ_,
+ {{0x6d4bb06d,0x69c9b06e,0xc794803d,0x6abc0114}}, // _paga, _thee, جشنب, _gyrf,
+ {{0xe8d90870,0x26dc007c,0x7bdc306f,0x6f0501d0}}, // _anọ_, וקומ, _ziru, _lehc,
+ {{0x7bca9301,0x442db070,0xf1bf0038,0xc6bf00c8}}, // _bhfu, ute_, dlá_, _আলোচ,
+ {{0x9ee980f7,0x6d4bb071,0x200980eb,0x212900b9}}, // _أفضل_, _waga, šais_, _acah_,
+ {{0x6d4bb072,0x35f59baa,0x65698499,0x6d40b073}}, // _taga, _спер, pjeh, bema,
+ {{0xdb089be6,0x29043074,0x8ed58264,0x06d58264}}, // rodá, úma_, _স্থগ, _স্থি,
+ {{0x394d831d,0xd9100019,0x442d8168,0x7c648019}}, // _maes_, میں_, qte_, _ناول,
+ {{0x749a8f60,0x649a8158,0x3ced8353,0x61e3826b}}, // _אינפ, _אינה, lcev_, _tunl,
+ {{0xe1f18288,0x20003075,0x3f983076,0x98a403bf}}, // _است_, _wwii_, riru_, ımı_,
+ {{0x7d098025,0x44200f52,0x0ce200ab,0x3ced8353}}, // naes, tui_, বর্ত, ncev_,
+ {{0x752404b9,0x3f98012b,0x26180074,0x644488ae}}, // [1de0] rgiz, piru_, बूजी_, _šiiz,
+ {{0xdd041010,0x39423077,0x2904b078,0x44203079}}, // ısın, leks_, _sema_, rui_,
+ {{0xeb96893f,0x6d408870,0x4420307a,0x75e78085}}, // נדער_, yema, sui_, _qızl,
+ {{0x673b005c,0x394218c2,0x4420307b,0x6d408f3e}}, // đuje, neks_, pui_, xema,
+ {{0x4420307c,0x6d40ad03,0xe3b10019,0x2904804f}}, // qui_, vema, صرے_, _vema_,
+ {{0x6fb4000f,0xa2e30256,0x7d06307d,0x39420074}}, // ंकिं, _горд, _heks, heks_,
+ {{0x6d40b07e,0x7d06307f,0xfbde0118,0x63768162}}, // tema, _keks, _iiª_, _mânt,
+ {{0x782680f7,0x746a8009,0xf65703c8,0xdd9080f7}}, // _معطل, еров_, רסקי_, توب_,
+ {{0x6d40b080,0x39423081,0xd1b38077,0x644d81a1}}, // rema, deks_, _اینک, _šair,
+ {{0x6d40b082,0x6d8784a1,0x79843083,0xff248065}}, // sema, _añad, _aliw, _خبری,
+ {{0x4ebf00c8,0xa49b0362,0xaace8074,0xfca980d7}}, // _আল্ল, _aeòl, हेंक, _یاهو_,
+ {{0x6fc005ec,0x25bf8162,0xfce6b084,0x394206ae}}, // böck, clul_, _содо, geks_,
+ {{0xa3e40540,0xe81c0006,0xa49b051e,0x8cb00c28}}, // _पूल_, भंगा_, _ceòl, _अँगो,
+ {{0xead48e02,0x63768087,0xf1bf03f2,0x1b1a0264}}, // _коль, _cânt, slá_, ন্তে_,
+ {{0xa3c3815c,0xa3c1b085,0x7d063086,0x5b7b80be}}, // ्षर_, ंतर_, _beks, _ארגא,
+ {{0x9e43016b,0xa49b0a2a,0x7984066f,0x6b9a8580}}, // žďov, _feòl, _gliw, litg,
+ {{0x7d062ca2,0x33f400f7,0xbcfb0511,0x24180fe6}}, // [1df0] _deks, مسلس, _anéc, лоты_,
+ {{0x290b3087,0x7d0602d5,0x69df0168,0x395fb088}}, // laca_, _eeks, _hiqe, _snus_,
+ {{0x3ebe9816,0xdb08807b,0x394db089,0xd6db004a}}, // _nytt_, lldó, _paes_, _яти_,
+ {{0x290b308a,0x4dfa0158,0x3547102a,0x3135804a}}, // naca_, _שפרא, _схов, редр,
+ {{0x3ced807a,0xd7058dae,0xada581d6,0x69df03ed}}, // vcev_, азли, nkúš, _miqe,
+ {{0x88c700c8,0x3ebe919b,0x69df308b,0x290b308c}}, // _এলাক, _bytt_, _liqe, haca_,
+ {{0x6d4f308d,0xd5ae0117,0x27780039,0x25bf8087}}, // _kaca, _رہے_, רגון_, tlul_,
+ {{0x290b0503,0x395f9ca7,0x2d890708,0x6d4f308e}}, // jaca_, _unus_, nhae_, _jaca,
+ {{0xb81ca3e6,0xe7e1023c,0x0d828112,0x87278c2a}}, // _नियम_, _गंगा_, ільн, _معام,
+ {{0x6d4f02a3,0x308481a8,0x60c9b08f,0x00000000}}, // _laca, _الكف, _izem, --,
+ {{0x39420006,0x69cd0362,0x49ca1ddf,0xb1130135}}, // teks_, _bhae, клон_, _tụle,
+ {{0x24968077,0x69cd0114,0x672d812b,0x290b3090}}, // انید_, _chae, đaju, gaca_,
+ {{0x7dc58207,0x764e807b,0x6b65a45b,0x7d06007b}}, // lóso, _ábyr, _вкла, _reks,
+ {{0x87b90676,0x39420665,0x64598609,0x248d822c}}, // густ_, seks_, _irwi, mzem_,
+ {{0x290b3091,0x2d960196,0xe81c8105,0x7d063092}}, // baca_, арас, _निभा_, _peks,
+ {{0x6d4f3093,0x290b3094,0x69cd0013,0xdb188176}}, // _caca, caca_, _ghae, _akvè,
+ {{0x7d06021e,0x6d4f01b4,0x81bd01a9,0x3f85b095}}, // [1e00] _veks, _daca, rtēt, _ollu_,
+ {{0x7bc18355,0x644b8077,0x6d4f0229,0x42560992}}, // nllu, _msgi, _eaca, итет,
+ {{0x7d060558,0x00e695f7,0x6d4f0014,0x7c3d3096}}, // _teks, ажен, _faca, _epsr,
+ {{0x6d4f3097,0x6e3e00b9,0x31608333,0x323680be}}, // _gaca, _ippb, ñiz_, יטען_,
+ {{0x60c98035,0x6d443098,0x69da8037,0x2f18b099}}, // _czem, meia, _ètem, голь_,
+ {{0x248daec8,0x2d80009a,0x60c9b09a,0x6d4f309b}}, // dzem_, lkie_, _dzem, _zaca,
+ {{0xfaa3108d,0xeb970b87,0x1c188074,0xe8e000ff}}, // _даро, _тир_, _दिहल_, _muối_,
+ {{0x2d80079f,0xe817016f,0x290b0e1b,0xad6680f7}}, // nkie_, _तिला_, xaca_, شابه,
+ {{0x290b003a,0x4432309c,0xb716803d,0x7b66b09d}}, // vaca_, nty_, _نباش, атне,
+ {{0x4432309e,0x61e0908c,0x443d808e,0x68e4309f}}, // ity_, _himl, _dpw_, _agid,
+ {{0x2d80079f,0x0cc3123a,0x3f9cb0a0,0x443230a1}}, // kkie_, _शर्म, mivu_, hty_,
+ {{0x443209a4,0x60c08009,0x3f9cb0a2,0x7bc1b0a3}}, // kty_, _kymm, livu_, allu,
+ {{0x7bce00b9,0x6d4430a4,0xdbdc81d0,0xdb050118}}, // _ohbu, deia, lšíh, rohú,
+ {{0x6d4f30a5,0xe8e00028,0x61e0b0a6,0x7c2430a7}}, // _saca, _cuối_, _liml, duir,
+ {{0x290b0024,0x6d4f30a8,0xada580e1,0x8de823e7}}, // paca_, _paca, skúš, ифта_,
+ {{0x69c88029,0xdb1c01a8,0x261803ca,0x443230a9}}, // _ūden, _ghrú, _फिरी_, fty_,
+ {{0x4424b0aa,0x6562803e,0x7bce0267,0x69c38036}}, // [1e10] num_, _mnoh, _bhbu, înem,
+ {{0x799d005d,0x25a9a2fb,0x6d4f30ab,0x8c1b8039}}, // lisw, čali_, _waca, כומי,
+ {{0x60c98117,0x6d4f04f9,0x442484a7,0x27e930ac}}, // _szem, _taca, hum_, _huan_,
+ {{0x4424b0ad,0x2d8030ae,0x799d005d,0x79a4838b}}, // kum_, ckie_, nisw, орче,
+ {{0x765a8063,0x4424b0af,0x27e902f9,0x7bc1b0b0}}, // _arty, jum_, _juan_, yllu,
+ {{0xbb850307,0x4424b0b1,0x2d86b0b2,0x7d0d30b3}}, // _السي, dum_, _aloe_, laas,
+ {{0x6fcd81ac,0x636da5b3,0x27e930b4,0x799d0234}}, // júce, _gúny, _luan_, kisw,
+ {{0x6fcdb0b5,0x7d0d30b6,0x44248125,0x248d8722}}, // dúce, naas, fum_, tzem_,
+ {{0x4424b0b7,0x60c0b0b8,0xe3b99a19,0x60c9b0b9}}, // gum_, _gymm, лби_, _uzem,
+ {{0xd7748013,0x5f748013,0x7d0d2933,0x61e0b0ba}}, // _والع, _والر, haas, _ziml,
+ {{0x7d0d30bb,0xe9da0021,0x248da422,0x26c101c0}}, // kaas, лко_, szem_, _nyho_,
+ {{0x27e91e66,0x186a30bc,0x443230bd,0x7d0d30be}}, // _buan_, гами_, yty_, jaas,
+ {{0x4424b0bf,0x7d0d02a3,0x443230c0,0x55528065}}, // cum_, daas, xty_, _رپور,
+ {{0xa0270125,0x27e930c1,0x291e02d0,0x7529b0c2}}, // _stöð, _duan_, şta_, lgez,
+ {{0xc9f5845b,0x51f59e13,0xfaa2b0c3,0x644981ec}}, // _استع, _استر, _нашо, lwei,
+ {{0x7d0d30c4,0x7529b0c5,0x7c2430c6,0x63bc30c7}}, // gaas, ngez, tuir, _skrn,
+ {{0x1ddf800f,0x69c280c9,0xd4671300,0x644982af}}, // [1e20] _पूछत, vloe, бите_, nwei,
+ {{0x443214e3,0x2d801a15,0x212d81f1,0x4f260991}}, // rty_, skie_, _aceh_, _удоб,
+ {{0x4432309c,0x6449b0c8,0x7c2402a5,0x7d0d30c9}}, // sty_, hwei, suir, baas,
+ {{0x4424b0ca,0x7529890d,0x443230cb,0x6f0e0326}}, // yum_, jgez, pty_, labc,
+ {{0x7c2430cc,0xdb01b0cd,0xe80b800f,0x3a258fb0}}, // quir, noló, _सौदा_, hulp_,
+ {{0xe82000a5,0x442030ce,0x64498114,0x799d0234}}, // _बिना_, mri_, dwei, zisw,
+ {{0x528600f7,0xa248019f,0x4424b0cf,0x442030d0}}, // _الأك, _خیال_, wum_, lri_,
+ {{0x442030d1,0x3f9cb0d2,0x75299d20,0xdb018298}}, // ori_, sivu_, ggez, koló,
+ {{0x442030d3,0x644997fb,0x3946b0d4,0x81ae0264}}, // nri_, gwei, meos_, কতা_,
+ {{0x4424b0d5,0xf36697cb,0x442030d6,0xdb018ce0}}, // rum_, стин, iri_, doló,
+ {{0x7d0d02c1,0x442030d7,0xed06803d,0x7dea8035}}, // yaas, hri_, _هواپ, _męsk,
+ {{0x27e930d8,0x7982809a,0x3946a5ca,0x4424808b}}, // _puan_, nkow, neos_, pum_,
+ {{0x27e930d9,0x543387d2,0x39520039,0x8c439cce}}, // _quan_, _فرور, _days_, _неце,
+ {{0x799d1c01,0x7d0d30da,0x3e580242,0x6f0e0326}}, // sisw, waas, _bčt_, gabc,
+ {{0x442030db,0x7d0d30dc,0x63ba9122,0x7a1c0087}}, // eri_, taas, motn, nătă,
+ {{0x27e930dd,0x63ba8353,0xe8d90133,0xdd940084}}, // _tuan_, lotn, _alụ_, пасы,
+ {{0x442030de,0xe8200006,0xdb01b0df,0x539901bb}}, // [1e30] gri_, _बिया_, coló, авая_,
+ {{0x7d0d30e0,0xdce982ce,0x3e5806c0,0x5b1530e1}}, // saas, vječ, _fčt_, імет,
+ {{0x26c701ac,0xd1758196,0x39468511,0x27f8807a}}, // _áno_, жылы, feos_, _črna_,
+ {{0x41c91869,0xdce9a4a0,0x59c908fd,0xf1c90ebf}}, // रतिस, tječ, रतिर, रतिन,
+ {{0x1869a457,0xa069828b,0x63bab0e2,0xa3d51354}}, // рали_, рала_, kotn, _момч,
+ {{0xaded9344,0x36198009,0x66fb06bf,0xdce982ce}}, // _चंदन_, ацию_, ्रिक_, rječ,
+ {{0x3cdb8076,0xdce9920e,0x25a930e3,0x752981ed}}, // खेने_, sječ, onal_, tgez,
+ {{0x25a930e4,0x4c85835f,0x6449b0e5,0x3946a706}}, // nnal_, жлив, twei, ceos_,
+ {{0x290fb0e6,0xe10100c8,0x7989826b,0x7529b0e7}}, // maga_, ্লাহ_, _ilew, rgez,
+ {{0x290fb0e8,0x6449b0e9,0x645d2bea,0x395230ea}}, // laga_, rwei, _mrsi, _says_,
+ {{0x395203d3,0x89aa0364,0x6449a7f9,0x60cd30eb}}, // _pays_, иков_, swei, _azam,
+ {{0x290fb0ec,0x442030ed,0xc1740326,0xdb01b0ee}}, // naga_, yri_, _kiɗi_, toló,
+ {{0x77770197,0xcb699073,0xe8e00129,0x25a904e8}}, // _boxx, _вале_, _ruồi_, dnal_,
+ {{0x442030ef,0x60cd0754,0x661a03f2,0x5ed080ab}}, // vri_, _dzam, átko, _স্টে,
+ {{0xb87b25a7,0x645d30f0,0xdb01b0f1,0x290fb0f2}}, // scíp, _arsi, soló, kaga_,
+ {{0x442030f3,0xa3c38a74,0x2d8db0f4,0xa0a6004a}}, // tri_, ्षक_, nhee_, _майд,
+ {{0x442030f5,0x7d0b8065,0x290fb0f6,0x9f840198}}, // [1e40] uri_, _megs, daga_, töä_,
+ {{0x442030f7,0x76418022,0x2bcf800c,0xa2d080d4}}, // rri_, _oply, _सीमा, डेक्,
+ {{0x79828063,0x644f30f8,0x442030f9,0x98b90084}}, // tkow, _esci, sri_, resą_,
+ {{0x442030fa,0x5f460416,0xed5a0f2f,0x08c69ccf}}, // pri_, _انگل, _том_, обен,
+ {{0x3946a82b,0x7982b0fb,0x61e430fc,0x798988f9}}, // reos_, rkow, _miil, _dlew,
+ {{0x7989b0fd,0xdfcf8013,0x394682ba,0x7982809a}}, // _elew, نين_, seos_, skow,
+ {{0x63baa67b,0x394685e4,0x290f820c,0xa3d70701}}, // votn, peos_, baga_, ागर_,
+ {{0x61e40a2c,0x6d5630fe,0x25a030ff,0x290fb100}}, // _niil, ndya, niil_, caga_,
+ {{0x98a38a29,0x61ea802e,0x60c40d4c,0x7c3604fe}}, // _жите, _sufl, _nyim, ntyr,
+ {{0x25a91b3b,0x61e40362,0x63a8808e,0xdb17002a}}, // znal_, _aiil, undn, toxé,
+ {{0xa2a001fe,0x63bab101,0x19590a41,0x326680e8}}, // _गेम्, rotn, баны_, ітов,
+ {{0x63bab102,0xdb17002a,0x60c40a03,0x6fc48580}}, // sotn, roxé, _byim, dòci,
+ {{0xb8f3035a,0x63ba807a,0x9f490187,0xdb01823e}}, // _वर_, potn, _itaú_, rolò,
+ {{0x290fb103,0x60cd007a,0xa294902a,0xdb170118}}, // zaga_, _vzam, _налі, poxé,
+ {{0x290fb104,0xdd9b240b,0x6933001b,0xdb23807b}}, // yaga_, аша_, _přeč, _þrát,
+ {{0xf1a40087,0x48151285,0x3f833105,0x1b0400ab}}, // _орын, _емис, skju_, _লাগে_,
+ {{0x6ebb05e8,0x25a90806,0x60cd3106,0xe8e0001c}}, // [1e50] _शुरु, rnal_, _uzam, _buổi_,
+ {{0xd25b2a0e,0x645d0042,0x69c0ac62,0xed5985f3}}, // ице_, _vrsi, _akme, može_,
+ {{0xdced0a20,0x290fb107,0xdb07008b,0x25a02960}}, // ljač, taga_, _skjó, biil_,
+ {{0x20090300,0x2b8086c0,0x25a00079,0x604081bc}}, // _kwai_, _wòch_, ciil_, _ịmag,
+ {{0x290fb108,0x644f3109,0x7d0b81b0,0x64468085}}, // raga_, _usci, _regs, çkil,
+ {{0x442927ba,0x290fb10a,0xd34300d5,0x637f83a7}}, // mua_, saga_, _تفسی, _têni,
+ {{0x4429310b,0x64428e67,0xf96bb10c,0x2d8d89ff}}, // lua_, _apoi, _край_, thee_,
+ {{0x752d310d,0xe79580d5,0x3cf7809a,0x764180e1}}, // ngaz, _کارک, ीरें_, _vply,
+ {{0xdced0067,0xd7f89ddf,0x1c428190,0x68e98609}}, // jjač, _дух_, еным, _iged,
+ {{0x61e42933,0x2d8d8079,0xdced0503,0x7d0b96a1}}, // _siil, shee_, djač, _wegs,
+ {{0x442910af,0x4b7c0158,0x7641816b,0x61e4156e}}, // hua_, יאזו, _uply, _piil,
+ {{0x4429310e,0xe8028c1c,0x248001a5,0x644f806a}}, // kua_, रीका_, yyim_, æcis,
+ {{0x4429310f,0x2d9d80b4,0x27e5a817,0xeb8e96c7}}, // jua_, _imwe_, _kiln_, _зи_,
+ {{0x44290ee2,0x7c298216,0x61e40079,0x752d047f}}, // dua_, muer, _wiil, egaz,
+ {{0x96632240,0x7c2981eb,0x63a1b110,0x68e9b111}}, // нкре, luer, miln, _oged,
+ {{0x752d1eb7,0x68e9b112,0xdce404a8,0x4429004f}}, // ggaz, _nged, njić, fua_,
+ {{0x44293113,0x27ed8102,0x7c29b060,0xd467013a}}, // [1e60] gua_, _nuen_, nuer, пите_,
+ {{0xe7ef8105,0x63a1b114,0x213f88f9,0x68e9a020}}, // _चढ़ा_, niln, _abuh_, _aged,
+ {{0x7c362280,0x24920063,0x44290087,0x248615d8}}, // styr, szym_, aua_, šom_,
+ {{0xb4ea8076,0x44293115,0x27ed80ab,0x290d802e}}, // _मले_, bua_, _buen_, _ceea_,
+ {{0xa3cb9521,0x7d04245e,0xe8e00028,0x63a1b116}}, // लता_, mbis, _tuổi_, kiln,
+ {{0x26c5800d,0x27ed811e,0xfbc78bbe,0x7c29b117}}, // _bylo_, _duen_, _ست_, duer,
+ {{0x63a1b118,0x68edaeb6,0x6b6305a8,0xa3c18074}}, // diln, žada, _якра, ूतन_,
+ {{0x7c298693,0xb608801b,0x7f8b0085,0x7d043119}}, // fuer, jišť, _müqa, nbis,
+ {{0x7c29ac92,0xdb050511,0x7d04311a,0xbcfb01d6}}, // guer, cohó, ibis, _poéz,
+ {{0x5ede80c8,0x39400057,0xfbbe1664,0x63a1a0d0}}, // নুষে, _abis_, ्तिम, giln,
+ {{0x27ed80ad,0x4429311b,0xa3e401ce,0x61ee04fe}}, // _zuen_, zua_, _पूछ_, _jubl,
+ {{0xed59803e,0x7c2984dc,0x63be027f,0xa3e79344}}, // tože_, buer, hopn, _मूक_,
+ {{0x63a1a842,0x7c2985b4,0xc8640adb,0x53348198}}, // biln, cuer, нтри, _желт,
+ {{0x61ee03d3,0x7c242944,0x4429311c,0x38660bcf}}, // _oubl, drir, vua_, _šorc_,
+ {{0xdced0042,0x7bda807b,0x433b8039,0x777ab11d}}, // sjač, mmtu, _העוב, _motx,
+ {{0x4429311e,0x7c24311f,0x7d04221b,0x55743120}}, // tua_, frir, gbis, нгст,
+ {{0x09c909a3,0x3f8c8459,0x7c243121,0x752d3122}}, // [1e70] रतीय, _oldu_, grir, rgaz,
+ {{0x3b540a4c,0x44293123,0x798d0282,0x44248c5e}}, // нкур, rua_, _hlaw, irm_,
+ {{0x44293124,0xb6068024,0x213f80ee,0x7c243125}}, // sua_, nošć, _pbuh_, arir,
+ {{0x15f205b3,0xdd86803f,0x44293126,0x7dc58510}}, // _आंतर_, _شو_, pua_, pósi,
+ {{0x27edb127,0xdb1707f4,0x44290098,0xdb1c3128}}, // _quen_, loxí, qua_, _chró,
+ {{0x798d031d,0x777a809f,0x3f9e80b9,0x3dc90080}}, // _llaw, _cotx, _cmtu_, llaw_,
+ {{0x63a1acc8,0x61ee02a5,0x7c299770,0x798d3129}}, // viln, _gubl, wuer, _olaw,
+ {{0x7c299914,0xb60680ce,0x63a18035,0x3dc91217}}, // tuer, došć, wiln, nlaw_,
+ {{0x63a1b12a,0x320102d5,0x3940002a,0x69c38036}}, // tiln, lphy_, _rbis_, îneu,
+ {{0x798d312b,0x7c29b12c,0x798611ee,0xaca3819d}}, // _alaw, ruer, wkkw, _nkụk,
+ {{0x7c299b6c,0x63a18353,0xb6068da4,0xa91d8bcf}}, // suer, riln, gošć, _alži,
+ {{0x7c298388,0x7e7e816d,0xaca3882e,0x2d9d82a0}}, // puer, äppe, _akụk, _umwe_,
+ {{0x798d312d,0x63a1b12e,0x3dc9033e,0xbcfb00e1}}, // _dlaw, piln, dlaw_, _inéh,
+ {{0x8c4295e0,0x61fb0b67,0x7c24312f,0xa49b0706}}, // _реше, _čuln, vrir, _leòs,
+ {{0xafe30037,0xdb1703a8,0x7d0f0061,0x4ad191be}}, // торл, goxí, _becs, _हरिव,
+ {{0x61ee3130,0x99520110,0x25adb131,0xe817016f}}, // _rubl, ršų_, nnel_, _तिचा_,
+ {{0x7d043132,0xdca322fd,0x63be007a,0x6fcd80e1}}, // [1e80] rbis, вати, topn, júco,
+ {{0x25bf82d6,0x7c24011e,0x387f0192,0x7dea8196}}, // houl_, rrir, äure_, _kęst,
+ {{0xa49b0706,0xdb1c0229,0x68ed81d6,0x3dc90428}}, // _beòs, _bhrò, ľadn, blaw_,
+ {{0x7c240081,0x3dc93133,0xdb1c1a26,0x2f0a8035}}, // prir, claw_, _phró, _mógł_,
+ {{0xb8f6901b,0x7bc38b99,0x7bc8807b,0xaca4019d}}, // _हर_, _oknu, yldu, _maịs,
+ {{0xdb1a826f,0x61ee02f1,0x9f458176,0xe29a3134}}, // poté, _tubl, _jilè_, _даг_,
+ {{0x2b580088,0x69c43135,0x61fc0503,0x9f8d008b}}, // _iarc_, _okie, _utrl, nþá_,
+ {{0xa5349612,0xb606811f,0x2d9202f7,0x29c40118}}, // хнич, vošć, nhye_, iñas_,
+ {{0x29069f33,0x20c381e5,0x7e878110,0xdb18b136}}, // mboa_, _айтм, _įspū, _akvá,
+ {{0x798d3137,0x69c42810,0xb606811f,0x2b583138}}, // _slaw, _akie, tošć, _jarc_,
+ {{0xa2c19513,0xd01380c8,0x798d01e9,0x3218826f}}, // _रुद्, _সময়_, _plaw, éry_,
+ {{0xe4a6954f,0xc486b139,0x68e4826c,0xb6068bcf}}, // _орло, _олек, židu, rošć,
+ {{0xa3e7823c,0xc058102a,0xbf0a016f,0x10740c4f}}, // _मंच_, мір_, वरून_, вляю,
+ {{0x0efb313a,0x2b58313b,0x47cc80ab,0x3dc902c4}}, // ्रेस_, _narc_, াদকী, wlaw_,
+ {{0x52bf0beb,0x9f45b13c,0x69c40609,0x3ceb010d}}, // _्रेस, _dilè_, _fkie, _pgcv_,
+ {{0x3dc90122,0x7f5701b4,0x58d5062c,0x637f8901}}, // ulaw_, _waxq, _пойт, _fêns,
+ {{0x7d020333,0x3dc9313d,0xb7d50032,0x9f4586c4}}, // [1e90] ñosa, rlaw_, _aṣen, _filè_,
+ {{0x6d978087,0x69dd313e,0xdb018366,0xdb1c0a2a}}, // nţar, lmse, anlæ, _shrò,
+ {{0xb50e86a7,0x7d0f313f,0xe1f00481,0xdb1c01e4}}, // सराय_, _tecs, لسن_, _phrò,
+ {{0x32013140,0x7dc5b141,0x645c80eb,0x68ed80e1}}, // rphy_, nóst, ārij, ľado,
+ {{0x68ed826f,0x2b583142,0x20190198,0x6008902a}}, // žado, _farc_, nssi_, днім_,
+ {{0x657d3143,0x7c2d3144,0xa49b0229,0x443907f1}}, // _hosh, muar, _feòr, _iqs_,
+ {{0x7c2d3145,0xee398cde,0x2bbe090f,0x657d3146}}, // luar, їни_, ्तरा, _kosh,
+ {{0x68ed3147,0x63a5178f,0x657d3148,0x6d87811b}}, // _ngad, lihn, _josh, _iñak,
+ {{0x657d3149,0x3834b14a,0x7c2d314b,0xed598904}}, // _mosh, _интр, nuar, rdž_,
+ {{0x69c4314c,0xa3bf0592,0x61e9b14d,0x63a500dd}}, // _skie, ँकि_, _hiel, nihn,
+ {{0x61e9b14e,0xa2d3016f,0x60c982f7,0x25ad8706}}, // _kiel, भेच्, _hyem, pnel_,
+ {{0x657d314f,0x7c2d3150,0xa3cf81a2,0x60c983f7}}, // _nosh, kuar, _वीर_, _kyem,
+ {{0x61e9b151,0xdb088511,0x656bb152,0xbcfb00f7}}, // _miel, godó, _ingh, _gnéi,
+ {{0x61e98341,0x7c2d3153,0x7c3bb154,0xdb1e0168}}, // _liel, duar, mtur, ropë,
+ {{0x6d59b155,0x657d3156,0x442db157,0x63a514e4}}, // _kawa, _bosh, lue_, dihn,
+ {{0x6d5bb158,0x61e9b159,0x7bc1b15a,0xaca381bc}}, // ndua, _niel, lolu, _nhịk,
+ {{0x2b58315b,0x6d59b083,0x7c2d315c,0x6aa806ce}}, // [1ea0] _parc_, _mawa, guar, कप्र,
+ {{0x6d59b15d,0x291fb15e,0xaca3819d,0x2d9202a0}}, // _lawa, _adua_, _ahịk, shye_,
+ {{0x61e9b15f,0x27ffa266,0x3ea63160,0xa9269d79}}, // _biel, _atun_, _чинг, едел,
+ {{0x61e9b161,0x6d59b162,0x657d3163,0xb17b016d}}, // _ciel, _nawa, _gosh, rmåg,
+ {{0xc7c69821,0x4fc6a950,0x61e988dd,0x7d1602a3}}, // нски, нска, _diel, mays,
+ {{0x7d163164,0x656bb165,0xfaa60d0e,0x442db166}}, // lays, _angh, _заго, due_,
+ {{0x6d59b167,0x657d3168,0x1dcb999e,0x61e98039}}, // _bawa, _yosh, ातंत, _fiel,
+ {{0x7d163169,0x6d59b16a,0x61e98609,0xdd9781a0}}, // nays, _cawa, _giel, ешь_,
+ {{0x442daa70,0x7c3b96fb,0x3cdb82f1,0x60c9b16b}}, // gue_, gtur, खेले_, _gyem,
+ {{0x09c90b6f,0xa0a3b16c,0x61e9b16d,0x1e969285}}, // रत्य, _сауд, _ziel, _прер,
+ {{0x200d8352,0x0c73819f,0x7d160b8a,0x7c2d316e}}, // _zwei_, ندید, kays, zuar,
+ {{0x6d59b16f,0xdce606c4,0x67eb0372,0x0efb01a2}}, // _gawa, _ankč, _ażja, ्र्स_,
+ {{0x7c3b9b9f,0x69dd3170,0x7bc1b171,0x7d160079}}, // ctur, rmse, bolu, days,
+ {{0x7bc1802e,0x657d3172,0x6d59b173,0x2019156e}}, // colu, _sosh, _zawa, rssi_,
+ {{0xdb1a87e2,0x6d59b174,0xa2a0016f,0x657d09be}}, // notí, _yawa, _गेल्, _posh,
+ {{0x7d160079,0x7c2201b9,0xdb01b175,0x6285016b}}, // gays, ġorn, vilé, vyho,
+ {{0x63a509da,0x61e9b176,0xb81ca076,0x6d4d3177}}, // [1eb0] tihn, _riel, _निगम_, reaa,
+ {{0x7c2d275d,0x61e9b178,0x6e2e3179,0xb17b0366}}, // ruar, _siel, bubb, småd,
+ {{0x61e9b17a,0x7d160079,0x442d8102,0xdb188106}}, // _piel, bays, zue_, _ikvä,
+ {{0x63a5010b,0x1c218665,0x61e98168,0x27ff808e}}, // sihn, _मिलल_, _qiel, _ptun_,
+ {{0x61e9b17b,0x6d59b17c,0x7c2d317d,0x29d900f7}}, // _viel, _rawa, quar, méad_,
+ {{0x61e9b17e,0x6d598886,0xdce9920e,0x442db17f}}, // _wiel, _sawa, ljeć, vue_,
+ {{0x44293180,0x61e9b181,0x6d59b182,0x6d40b183}}, // lra_, _tiel, _pawa, ffma,
+ {{0x7c3b81dc,0x44293184,0x8d658ada,0xf8bf07bc}}, // ttur, ora_, твие, ngés_,
+ {{0x44293185,0xe43280f7,0x7bc1b186,0x6d5bb187}}, // nra_, لفيد, tolu, rdua,
+ {{0x4429186b,0x6d59b188,0x442db189,0x394fb18a}}, // ira_, _wawa, rue_, legs_,
+ {{0x09d60a49,0x6d59b18b,0x7bc1b18c,0x316d8390}}, // _সংবা, _tawa, rolu, _knez_,
+ {{0x4429318d,0x7bc1b18e,0xdb1e02be,0x656bb18f}}, // kra_, solu, ropé, _ungh,
+ {{0x442db190,0x29120179,0x8edf00c8,0x44293191}}, // que_, _veya_, _ব্লগ, jra_,
+ {{0x44293192,0x701380ab,0x7d16057b,0x7c29a280}}, // dra_, _সমাজ_, ways, mrer,
+ {{0x44293193,0x65b301cd,0x6da61577,0x7d020333}}, // era_, għho, вива, ñoso,
+ {{0x7d09aa97,0x893612c8,0x6e2e3194,0x12e880be}}, // nbes, _شعبا, rubb, _אַפּ,
+ {{0x44293195,0xa91d803a,0x7d1602a3,0x28de0540}}, // [1ec0] gra_, _možd, rays, नेशि,
+ {{0x6e2e235a,0x7d163196,0x06863197,0x8cd6864a}}, // pubb, says, _агон, मेटो,
+ {{0x44293198,0x7c2982af,0xdce982ce,0x7d09874c}}, // ara_, hrer, bjeć, kbes,
+ {{0x29d900f7,0x7d1601b4,0x637e928a,0x6fcd8174}}, // céad_, qays, _záně, lúch,
+ {{0x7d09b199,0x317f83ec,0x6d46026b,0xe9478061}}, // dbes, _douz_, _ibka, ئرمی,
+ {{0x5ead80c8,0x7c29b19a,0xaca38135,0x7d09a0b5}}, // য়েছে, drer, _chọk, ebes,
+ {{0x6ae000c8,0x7c29b19b,0xf3f400ab,0x7d09b19c}}, // _প্রো, erer, _ছবির_, fbes,
+ {{0x7d09b19d,0x6ce68d13,0x7c2982df,0x6fcd8174}}, // gbes, кіпе, frer, húch,
+ {{0xc0458277,0x7c29b19e,0xdb1c01d0,0x00000000}}, // _مخلو, grer, _zkrá, --,
+ {{0xf4130051,0x2006b19f,0xf3669677,0xa91d9502}}, // ספת_, mpoi_, ттин, _požg,
+ {{0xc3331a0f,0x442931a0,0x79809088,0x7d09b1a1}}, // בות_, zra_, _komw, bbes,
+ {{0x7c29b1a2,0x316d31a3,0x6f188353,0x44290e20}}, // brer, ñez_, lavc, yra_,
+ {{0x75228133,0xb7d50032,0x6d958091,0xe9df002a}}, // _idoz, _aṣan, _dáad, rmú_,
+ {{0x442931a4,0x547b8039,0x9e3531a5,0x6d4631a6}}, // vra_, _קטגו, легч, _abka,
+ {{0xb8ca058c,0x442919e7,0x2bb1816f,0x2bec064a}}, // _गे_, wra_, ीकरा, _अंजू_,
+ {{0xa91d826f,0x7bc7104a,0x7e6531a7,0xdcfb811f}}, // _kože, _skju, _vrhp, rkuć,
+ {{0xdb1c31a8,0x291931a9,0x6f18807d,0xdcfb8289}}, // [1ed0] _skrá, masa_, kavc, skuć,
+ {{0xa91d803a,0x4ea72097,0x8f55035f,0xdce9920e}}, // _može, _арма, _свої, sjeć,
+ {{0x442931aa,0x6f188503,0x7522b1ab,0x61ed31ac}}, // sra_, davc, _odoz, _kial,
+ {{0x442931ad,0x291931ae,0x673b012b,0x60cd31af}}, // pra_, nasa_, đuju, _kyam,
+ {{0xa3c2853e,0xa91d8503,0x6d5d0cb5,0x6fcdb1b0}}, // ंवा_, _nože, _iasa, lúci,
+ {{0x291915bd,0x7522b1b1,0x24890282,0x2d82b1b2}}, // hasa_, _adoz, myam_, öken_,
+ {{0x6d5d31b3,0x6da59860,0x291931b4,0xe5a5a2fd}}, // _kasa, лика, kasa_, лики,
+ {{0x61ed31b5,0x7bc50091,0x7c29b1b6,0xe459813a}}, // _nial, lohu, trer, ежи_,
+ {{0x6d5d318b,0x60cd31b7,0x6f1880d2,0xa91d8279}}, // _masa, _nyam, bavc, _vožd,
+ {{0x7c29ac03,0x6d5d31b8,0x2bd90321,0x490b800d}}, // rrer, _lasa, _बीमा, ारको_,
+ {{0x61ed31b9,0x6fcd81ac,0x60cd31ba,0x25a91e1e}}, // _bial, júci, _ayam, kial_,
+ {{0x6d5d31bb,0x291931bc,0xb4db046d,0x60cd1c61}}, // _nasa, gasa_, _abàm, _byam,
+ {{0x61ed26fd,0x4429b1bd,0x9b458591,0x343980be}}, // _dial, _ía_, _منشو, _נײַע,
+ {{0x9474806b,0x656f1140,0x63a88699,0x443fb1be}}, // _خدما, _anch, cidn, mtu_,
+ {{0x6d5d31bf,0xa2ca89a3,0x291911c2,0x61ed31c0}}, // _basa, _सुप्, basa_, _fial,
+ {{0x5bbe0f21,0x61ed31c1,0x69c98069,0xed5715fc}}, // ्त्व, _gial, _nkee, лос_,
+ {{0x443f88a7,0x6d5d059c,0x2bf680be,0x69c631c2}}, // [1ee0] ntu_, _dasa, ָמען_, moke,
+ {{0x443fb1c3,0x69c631c4,0xa09b03de,0x69c9b1c5}}, // itu_, loke, _נייט, _akee,
+ {{0x6d5d31c6,0x443fb1c7,0x201f87e2,0x35a61986}}, // _fasa, htu_, _avui_, _банг,
+ {{0x25a930cc,0x6d5d31c8,0x05560d0e,0x644b8366}}, // cial_, _gasa, _стоя, _opgi,
+ {{0xdefa825d,0x443fb1c9,0xa2ca9516,0xbcfb0333}}, // ный_, jtu_, _सुन्, _enés,
+ {{0x6d5d0491,0xdb1c006a,0x69c631ca,0x7c2b1434}}, // _zasa, _skræ, hoke, ágre,
+ {{0x6d5d31cb,0x291931cc,0xb8eb800c,0xbb3b00be}}, // _yasa, yasa_, _रु_, _רעלי,
+ {{0xa91d80ce,0x6d5d02a3,0x69c623be,0x6f18b1cd}}, // _pože, _xasa, joke, savc,
+ {{0x61ed0307,0x20e303dd,0x63a8816d,0x6f18b1ce}}, // _rial, _गणेश_, tidn, pavc,
+ {{0x40938013,0x2d8231cf,0x25a931d0,0x61ed31d1}}, // _الكر, _loke_, zial_, _sial,
+ {{0x61ed0397,0x291931d2,0x60cd0590,0x443fb1d3}}, // _pial, tasa_, _syam, atu_,
+ {{0x6288b1d4,0xa91d807a,0x7bc5004f,0xe80b801b}}, // rydo, _tože, zohu, सीका_,
+ {{0x291931d5,0x629ab1d6,0x61ed31d7,0x44321126}}, // rasa_, szto, _vial, juy_,
+ {{0x6d5d2fd2,0x29192fc9,0x60cd0010,0x6fcd80e1}}, // _sasa, sasa_, _vyam, túci,
+ {{0x6d5d31d8,0xcddb0fbb,0x6d4431d9,0x69c62127}}, // _pasa, ења_, ffia, boke,
+ {{0x55580160,0x6d5d31da,0x2d580987,0x248905b0}}, // лася_, _qasa, лись_, tyam_,
+ {{0x5b1531db,0x6d5d31dc,0x7bc531dd,0x7bdc0083}}, // [1ef0] имат, _vasa, tohu, _bhru,
+ {{0x6d5d31de,0x25a931df,0x7bdc0c64,0x248931e0}}, // _wasa, sial_, _chru, ryam_,
+ {{0x6d5d31e1,0x443f811e,0x7ceb8019,0xd2510c3b}}, // _tasa, ztu_, _körü, تنا_,
+ {{0x6d5d0c41,0x3d0e809a,0xbcfb31e2,0xdb018216}}, // _uasa, सरों_, _enér, vilí,
+ {{0x65360158,0xe739a2f6,0x443fb1e3,0xf3ff0187}}, // _דארף_, вел_, xtu_, _irão_,
+ {{0x61f531e4,0x8ae70d13,0xdb01b1e5,0xdb0883a7}}, // _tuzl, _бібл, tilí, sidê,
+ {{0xed59af02,0x69dbb1e6,0x61fc8118,0x01d60264}}, // вой_, _thue, írll, _সংসদ,
+ {{0x443f9184,0x14258284,0x0d85824f,0x7c2d31c1}}, // ttu_, адим, рлин, orar,
+ {{0x443fb1e7,0x998480f7,0x7c2d04be,0x69c631e8}}, // utu_, _القو, nrar, voke,
+ {{0x395fb1e9,0x4a45acd1,0x443fb1ea,0x25a6a20c}}, // _haus_, анов, rtu_, _emol_,
+ {{0xa2cb2e06,0x69c6022e,0x395fb1eb,0x2fc7b1ec}}, // _तुम्, toke, _kaus_, mong_,
+ {{0x9f49007b,0x7c2d31ed,0x39490362,0x2fc7b1ee}}, // _stað_, krar, _cbas_, long_,
+ {{0x69c631ef,0xa2a71055,0x2d82007a,0xeb8eb1f0}}, // roke, _टेस्, _roke_, _ди_,
+ {{0x7d1bb1f1,0x395f81c5,0x2fc784d2,0x442d906f}}, // laus, _laus_, nong_, mre_,
+ {{0x66e61198,0xeab00a49,0xdee60b9c,0x69c60010}}, // рова, _কর্ম, рови, poke,
+ {{0x7ceb8b06,0x2fc7840a,0x7d0d31f2,0x442db1f3}}, // _görü, hong_, gbas, ore_,
+ {{0x2fc7b1f4,0x7c2d0baf,0x6db7012b,0x6d4431f5}}, // [1f00] kong_, grar, pćan, rfia,
+ {{0x442db1f6,0x6b83b1f7,0x2fc7b1f8,0xb6a61d8f}}, // ire_, _jong, jong_, _виол,
+ {{0x6f1c2c92,0x6b83b1f9,0x442db1fa,0x2fc782b8}}, // marc, _mong, hre_, dong_,
+ {{0xd94693b4,0x442db1fb,0x7d1b87ac,0x6f1c31fc}}, // реди, kre_, jaus, larc,
+ {{0x61e281b9,0x395f8282,0x7c2d002e,0x442db1fd}}, // rmol, _daus_, crar, jre_,
+ {{0x442d8e1c,0x2fc7b1fe,0x442031ff,0x7996031d}}, // dre_, gong_, msi_, _llyw,
+ {{0xd5ba9506,0x395f8282,0xf8d32769,0x6d4781a1}}, // кси_, _faus_, _सर्प, đjan,
+ {{0x442db200,0x44200190,0x6f1c3201,0x6b839e3f}}, // fre_, osi_, harc, _aong,
+ {{0x57eaa2f6,0x442d888b,0x6b83b202,0x2fc78bf2}}, // _адам_, gre_, _bong, bong_,
+ {{0x44203203,0x46a68098,0xbea68221,0x2fc7b204}}, // isi_, _казв, _казк, cong_,
+ {{0x6b83b205,0x442db206,0x6440b207,0x6f1c3208}}, // _dong, are_, stmi, darc,
+ {{0x442db209,0x4420320a,0x8c46875a,0x7d1b8051}}, // bre_, ksi_, _веде, caus,
+ {{0x6f1c2b02,0xfce68abe,0x4420320b,0x6b83907c}}, // farc, _тодо, jsi_, _fong,
+ {{0x6f1c320c,0x7c2d0012,0x8c1b83c8,0x6b83821e}}, // garc, vrar, _יודי, _gong,
+ {{0x7c22b20d,0x7bc8b20e,0x61418019,0x3160320f}}, // _hvor, modu, náló, _faiz_,
+ {{0x4420022e,0x44f53210,0x39151bdc,0x527b84de}}, // fsi_, спас, _умир, _ינוא,
+ {{0x2fc784d2,0x7d0d3211,0xe4e7035f,0x6b83b212}}, // [1f10] yong_, rbas, рівн, _yong,
+ {{0x7c2d3213,0x6f1c3214,0x395fb215,0xd9bf83eb}}, // rrar, carc, _saus_, एक्ट,
+ {{0x395fb216,0x4420127b,0x2bbe0424,0x442d8061}}, // _paus_, asi_, ्तजा, zre_,
+ {{0x442db217,0x2fc7833e,0x44203218,0xdb088722}}, // yre_, wong_, bsi_, sidè,
+ {{0x44203219,0x2fc7843c,0x557600be,0x395f81c0}}, // csi_, tong_, _הערן_, _vaus_,
+ {{0x442db21a,0x645d321b,0x7d02b21c,0x06b280ab}}, // vre_, _issi, _afos, য়েছি,
+ {{0x2fc782e8,0x22950013,0xa6c996fe,0x7c22b21d}}, // rong_, _الأس, ылка_, _avor,
+ {{0x6b83b21e,0x56b80039,0x2fc7b21f,0x6f1c3220}}, // _song, יפון_, song_, zarc,
+ {{0x442da962,0x6b83b221,0x7f8b0201,0x2fc7b222}}, // ure_, _pong, _hüqu, pong_,
+ {{0x442d8625,0x291db223,0x31600ad4,0x7c22a8fc}}, // rre_, lawa_, _saiz_, _dvor,
+ {{0xa2ca816f,0x442d8019,0x6f1c3224,0xb17b0502}}, // _सुद्, sre_, varc, rmån,
+ {{0x442da65d,0x291db225,0x44203226,0xd00a1d32}}, // pre_, nawa_, ysi_, леме_,
+ {{0x6f1c3227,0x8d5a8039,0x44200085,0x31601dd7}}, // tarc, _תכני, xsi_, _vaiz_,
+ {{0x291d9cbc,0x6b838010,0x6b9a8a11,0x7643808e}}, // hawa_, _uong, chtg, otny,
+ {{0x291db228,0x645d20ef,0x7c228253,0x2d96283b}}, // kawa_, _assi, _zvor, брас,
+ {{0x442024fc,0x6da3006d,0x6f1c3229,0x2b4a008e}}, // tsi_, писа, sarc, _sbbc_,
+ {{0x7b068065,0x4420322a,0x291db22b,0x249f82ce}}, // [1f20] _érté, usi_, dawa_, nzum_,
+ {{0x44202f37,0xa91db22c,0xa3dd80d4,0x61e60b81}}, // rsi_, _roža, _थीम_, lmkl,
+ {{0x645d322d,0x25adb22e,0xdb070019,0x60dbb22f}}, // _essi, kiel_, ámár, _azum,
+ {{0x291db230,0x38668067,0xa91d90d1,0xf1a5835f}}, // gawa_, tvor_, _poža, орін,
+ {{0x53c787e6,0x44203231,0x25ad81ac,0x6d563232}}, // रकाश, qsi_, diel_, meya,
+ {{0x60db8267,0x38cb803d,0xd8d700be,0x3f858140}}, // _dzum, دامی_, זונט_, _colu_,
+ {{0x25ad82d8,0x291d985b,0x3f8585aa,0x7c22b233}}, // fiel_, bawa_, _dolu_, _svor,
+ {{0x65643234,0x2d803235,0xd94397c8,0xdd978084}}, // ndih, njie_, _неси, йшы_,
+ {{0x91e38758,0x64443236,0x76438057,0x61f89123}}, // _хоте, ntii, atny, _suvl,
+ {{0x5c5b00be,0x69df00f1,0x3f858390,0x2902009a}}, // נדיק, _shqe, _golu_, ecka_,
+ {{0xa91d9234,0x64d506a7,0x25ad9695,0x6d56010c}}, // _možn, _दर्श, biel_, keya,
+ {{0x7c2284e8,0x25adb237,0x64440406,0x7bc8b238}}, // _tvor, ciel_, ktii, sodu,
+ {{0x399b020f,0xa3d805fb,0x3f858059,0x2d8001b0}}, // _nëse_, ाता_, _yolu_, djie_,
+ {{0x0a948d13,0x291db239,0xdbf1001b,0x2ca0026c}}, // _малю, zawa_, _příl, dzid_,
+ {{0x5694323a,0xa14380eb,0x291d801d,0xfdc406a7}}, // парт, šķir, yawa_, वकूफ,
+ {{0x6d56323b,0x644401b4,0x69cd323c,0x6d9c8174}}, // geya, ftii, _ukae, _déag,
+ {{0x5fba941b,0x2129036e,0x4dd401a8,0x765e06c4}}, // [1f30] ेवाल, _idah_, ستفس, _espy,
+ {{0xdea404c0,0x291db23d,0x6f1ab23e,0x25adb23f}}, // _کیفی, wawa_, _netc, ziel_,
+ {{0x291db1bb,0x6d563240,0xd6d9003d,0x72b901a8}}, // tawa_, beya, _خودش_, جهاز_,
+ {{0x6ab90beb,0x291d804f,0x8f5500d7,0x64443241}}, // _आशीर, uawa_, _انگش, btii,
+ {{0x291db242,0xf7708416,0x6d9c80f7,0x25adb243}}, // rawa_, کان_, _méad, viel_,
+ {{0x291db244,0x6d4b8359,0x25ad9699,0x8afe8326}}, // sawa_, _sbga, wiel_, _yaƙe,
+ {{0x2902016d,0xfebb815b,0x62341138,0x66e28048}}, // ycka_, داشت_, _неру, моша,
+ {{0x200903ac,0x7dc58019,0x23633245,0x7643b246}}, // _otai_, lósz, _hajj_, rtny,
+ {{0xa2b901b6,0x25adb247,0x60dbb248,0x92e680ab}}, // ्थव्, riel_, _uzum, যুৎ_,
+ {{0x42259895,0x212900dd,0xb17b0f91,0x25adb249}}, // одов, _adah_, smål, siel_,
+ {{0x80d100c8,0x25adb24a,0x09b580be,0x6d56324b}}, // সেম্, piel_, ַפֿט_, yeya,
+ {{0x6d9cb24c,0x64440198,0xbc758a19,0xdb038f35}}, // _réag, ytii, _وهاب, _imní,
+ {{0xe45f025d,0x6d56324d,0x10a30162,0x65642cc7}}, // _myös_, veya, диян, vdih,
+ {{0x69cbb24e,0x212900ee,0x1af38264,0x6d498174}}, // hoge, _edah_, _আজকে_, lfea,
+ {{0x59c9885d,0xba778077,0x2d80079f,0xb8d108af}}, // िकार, _داشت, tjie_, _टे_,
+ {{0x64440364,0x753ba479,0xf50a324f,0x29d901a8}}, // ttii, nguz, рнал_, néal_,
+ {{0x92d600c8,0x6d563250,0xd46718a0,0x3b862482}}, // [1f40] _হলে_, reya, оите_, олаг,
+ {{0x32d630bc,0xa91db251,0x64443241,0x213f80dd}}, // оцес, _božo, rtii, _acuh_,
+ {{0xe8fa9777,0x64443252,0x8b0780fc,0x69cb9a16}}, // але_, stii, _taƙƙ, foge,
+ {{0x6f1a82e6,0x69cbb253,0x64443254,0x7d1f011c}}, // _setc, goge, ptii, caqs,
+ {{0xe0568277,0xa91d807a,0x29d901a8,0x386001b9}}, // تخاب, _tožn, déal_, _jsir_,
+ {{0x44320114,0x7c240834,0x7d043255,0x9e06818b}}, // mry_, msir, lcis, очил,
+ {{0xdd9a9617,0x41ca1299,0x69cbb256,0xbf9b0036}}, // иши_, रवास, boge, mpêt,
+ {{0x69cb81ca,0xed59939c,0x7d043257,0x4432309c}}, // coge, kožu_, ncis, ory_,
+ {{0x7c243258,0xdb1e006a,0xc31e80ab,0x6d9c81a8}}, // nsir, ropæ, _দাবি_, _réad,
+ {{0x77928077,0x7c243259,0x44320748,0x926a88d5}}, // _زیبا, isir, iry_, арма_,
+ {{0x9d46817a,0x2009325a,0x4432016b,0xe3b8b25b}}, // _межд, _stai_, hry_, rnı_,
+ {{0x443201b0,0x7c240304,0x963385e9,0xa194102a}}, // kry_, ksir, _ініц, даюч,
+ {{0x92d600c8,0xa2ca800c,0x9f4c801b,0x7c240503}}, // _হলো_, _सुर्, _lidí_, jsir,
+ {{0x442680ce,0x69cb82af,0xc05800e8,0x09b79053}}, // _ivo_, zoge, цію_, _अद्य,
+ {{0x69cb82ec,0x4426b25c,0xddab0087,0x61fc0965}}, // yoge, _hvo_, атал_, _nurl,
+ {{0x2129035a,0x7c24325d,0x4432015c,0x2bbf2836}}, // _udah_, fsir, fry_, ्वभा,
+ {{0xb9c40013,0x6fca8540,0x78a288ae,0x3959325e}}, // [1f50] _تقيي, ाकां, jzov, mess_,
+ {{0x09b3853e,0x69cb8365,0x63ba81ec,0x3959325f}}, // ंच्य, woge, nntn, less_,
+ {{0xe7f9016f,0x44323260,0x46d20cfd,0x69cb8a85}}, // ंदवा_, ary_, _दुपह, toge,
+ {{0x44268025,0xe1f80110,0x39593261,0x7d04234a}}, // _ovo_, ігі_, ness_, ccis,
+ {{0x18698258,0xa2cb3262,0xf994025f,0x8caa81a2}}, // сали_, _तुर्, ורף_, _जेलो,
+ {{0x7d1d1831,0x23fa0039,0x69cbb263,0xbdf81190}}, // _hess, _להשא, soge, _دریا_,
+ {{0x61fc1486,0xc7b40039,0xa3d800d4,0xa2ca850a}}, // _gurl, ובס_, ातर_, _सुल्,
+ {{0xc4858785,0x4c8586e6,0xf1c004e8,0x673c0140}}, // злик, злив, čák_, zgrj,
+ {{0x7d1d114e,0xe3b1803f,0x937980f7,0x29d900e7}}, // _mess, فرد_, مصدر_, réal_,
+ {{0xf8ae80d5,0x29dc85a4,0x798980c9,0x7d1d0a85}}, // _حکم_, nían_, _hoew, _less,
+ {{0x4426803a,0x799b9916,0xdb0888f1,0x8a058110}}, // _evo_, _kluw, cidí, язне,
+ {{0x7d1d1927,0x76470366,0xe61080d7,0xc7a901c6}}, // _ness, dtjy, _ششم_, _גב_,
+ {{0xb4fa8039,0x764b8110,0x92b58fd3,0xdb01862c}}, // _הפני, žnyč, تحکا, tilä,
+ {{0xfaa5a2a7,0x44323264,0xed5981c8,0x6dac02d0}}, // _нало, vry_, lež_, rşam,
+ {{0x70b89513,0x7d1d02af,0x29dc85e4,0xa2cb01d0}}, // _अश्ल, _bess, dían_, _तुल्,
+ {{0xf99f3265,0x39593266,0x7c243267,0x7d1d3268}}, // chè_, cess_, tsir, _cess,
+ {{0x7d1d3269,0x3860326a,0x8afe89ab,0x7d04262b}}, // [1f60] _dess, _usir_, _haƙa, rcis,
+ {{0x4432326b,0xa01b0009,0x8afe8326,0xe7f9326c}}, // rry_, pyör, _kaƙa, ंदरा_,
+ {{0x4efb0051,0xe7170051,0x7c24326d,0x61e4326e}}, // _להיו, _מחיר_, ssir, _khil,
+ {{0xc693093f,0xd7fa8676,0x7d1d15fe,0xdb08a8e5}}, // _נאר_, бук_, _gess, vidí,
+ {{0x29dc82ba,0x61e40c41,0xed59915c,0x8afe89ab}}, // bían_, _mhil, dež_, _laƙa,
+ {{0xc7a38226,0xa2a7326f,0x29dc85e4,0x8fa3a57e}}, // ничк, _टेक्, cían_, наче,
+ {{0x442687ca,0x6566029b,0x7d1d3270,0xdb1e01ec}}, // _svo_, _kakh, _yess, ropä,
+ {{0x2abb0039,0x2d9fb271,0x4426929b,0x25a0008e}}, // _המלא, ghue_, _pvo_, nhil_,
+ {{0x3a3a04b7,0x212209c4,0xdb08a509,0xf0448fd3}}, // lupp_, dakh_, sidí, _تعزی,
+ {{0xe44f8013,0x6566005d,0x6f1e011f,0x8afe89ab}}, // اضي_, _lakh, _nepc, _baƙa,
+ {{0x395904b7,0x25a00d8b,0x61e40c41,0xfbab8035}}, // tess_, khil_, _bhil, टोधम,
+ {{0x61e43272,0x65663273,0x4426808b,0x867b81c6}}, // _chil, _nakh, _tvo_, _לרבו,
+ {{0x7d1d3274,0xa91da7b1,0x4426b275,0x39593276}}, // _ress, _božj, _uvo_, ress_,
+ {{0x6d9c8013,0x7d1d3277,0x39593278,0x656601b4}}, // _féac, _sess, sess_, _aakh,
+ {{0x7d1d00a9,0x6566005d,0xd3e582e3,0x29dc8511}}, // _pess, _bakh, _تقوی, vían_,
+ {{0x61e43279,0xbcfb03a8,0xdb08823e,0x98b901a9}}, // _ghil, _gaél, lidà, nesī_,
+ {{0x6ecd0f12,0x6c54096f,0x29dc8511,0x69cf016b}}, // [1f70] _दुरु, нкту, tían_, moce,
+ {{0x7d1d0123,0x69c08115,0x8afe89ab,0x889c01c6}}, // _wess, _ajme, _yaƙa, _לבחי,
+ {{0x29dc840e,0x7d1d327a,0xecf905e9,0xbb8400f7}}, // rían_, _tess, _менш_, علمي,
+ {{0x6f1e327b,0x25a000f3,0x69cf2c04,0xc2c40174}}, // _zepc, chil_, noce, ريني,
+ {{0xa3ccb27c,0x1b2280ab,0x6d4d008e,0x237800fe}}, // लवा_, _মানে_, mfaa, _fnrj_,
+ {{0x2d8b05f5,0x6566005d,0x6d4d327d,0xceba80fc}}, // _hoce_, _zakh, lfaa, waƙa_,
+ {{0x6566029b,0xb8f49664,0x6b83007b,0x443b327e}}, // _yakh, _सु_, öngu, luq_,
+ {{0x6d4d0763,0x6d9c80e7,0x799b80dd,0x2d8b327f}}, // nfaa, _réac, _uluw, _joce_,
+ {{0xcc8980d7,0x8afe8326,0x3f818338,0xafdb0646}}, // زنده_, _saƙa, öhus_, rtøj,
+ {{0x61e43280,0x2d8b002a,0x69d981ec,0x7d038061}}, // _shil, _loce_, llwe, _bíró,
+ {{0x52db3281,0x61e43282,0x7e7e816d,0xed598259}}, // _भरोस, _phil, äppt, pež_,
+ {{0x6723b283,0x2d8b3284,0x69cf3285,0xdb08841c}}, // lanj, _noce_, goce, tidã,
+ {{0x65663286,0xa3bf2743,0x81c200ab,0x8afe89ab}}, // _rakh, ुका_, ্গল_, _waƙa,
+ {{0x82358bca,0x6723b287,0xe299b288,0x61e40051}}, // _قربا, nanj, _мал_, _whil,
+ {{0x61e43289,0x2d8b012b,0x6d4d328a,0x25a00642}}, // _thil, _boce_, ffaa, thil_,
+ {{0x6723b28b,0x291f822c,0x6569b28c,0x68fbb28d}}, // hanj, _neua_, ndeh, _ngud,
+ {{0x6449b28e,0x6723b28f,0x2d8b3290,0xbfc62349}}, // [1f80] ntei, kanj, _doce_, _обик,
+ {{0x61b899e8,0x65663291,0xd9b886ce,0x212d80b9}}, // ेक्ष, _wakh, ेक्ट, _adeh_,
+ {{0x23678353,0x64499bda,0x35a60071,0x65663292}}, // _manj_, htei, _жанг, _takh,
+ {{0xe0dab293,0x2d8b3036,0x3a3a3294,0x6449b295}}, // ове_, _goce_, rupp_, ktei,
+ {{0x26039220,0xdcb90364,0x4fc69597,0xc7c697c8}}, // _año_, _ещё_, мска, мски,
+ {{0x09bb0519,0x31bb223a,0x6d42802e,0x65698fb0}}, // _उद्य, _उद्ध, _scoa, edeh,
+ {{0x9f4c8118,0x6e3c0144,0xc34e80ff,0x45d49860}}, // _cidá_, nurb, _bổng_, нокс,
+ {{0x717683f8,0xc34e801c,0x644980e3,0x68fb85ee}}, // _تهرا, _cổng_, ftei, _ggud,
+ {{0xe043b296,0x29e88085,0x69cf3297,0x3944841c}}, // _инти, _uşaq_, voce, _icms_,
+ {{0x6723803a,0x96968098,0x547b03de,0x75243298}}, // canj, _ореш, קטיו, haiz,
+ {{0x69cf3299,0xa3e606b7,0xd3370039,0xa2948d8e}}, // toce, _पठन_, וריה_, хані,
+ {{0x3872119b,0x7c3bb29a,0x7524026c,0x443b011c}}, // _bryr_, buur, jaiz, yuq_,
+ {{0x14e2a539,0x3f8cabf7,0x2d8b001b,0xdb1c07d3}}, // _परिण, _kodu_, _roce_, _skrý,
+ {{0xeaaf8416,0x61ee01ac,0xac978077,0xd90f015b}}, // اعی_, _ďale, _تنها_, دیک_,
+ {{0x2bbf14d5,0x3f8cb29b,0xdce28754,0x9f458118}}, // ्वसा, _modu_, _maoč, _pilú_,
+ {{0x23678838,0xa2ad800d,0x7bdab29c,0x693601d0}}, // _zanj_, ुपर्, lltu, _přež,
+ {{0x2d8b163e,0xf1bf026f,0x33750a42,0x6723b29d}}, // [1f90] _voce_, mná_, нгар, yanj,
+ {{0xdce285f5,0xf1bf027f,0xa3e48054,0x291f8722}}, // _naoč, lná_, _भीम_, _seua_,
+ {{0x6e3c2cb1,0xaad486a7,0xe3c300ab,0x2d8b0037}}, // curb, _ठुमक, ্গলব, _toce_,
+ {{0x59f9893f,0xf1bf2527,0xa91d96f2,0x6723a994}}, // פּעד, nná_, _koži, wanj,
+ {{0x67238025,0x443b0085,0x3f8c816b,0x64499500}}, // tanj, quq_, _bodu_, xtei,
+ {{0xf77180f7,0xa3dd809a,0x66e58071,0x28b201a2}}, // طات_, _थीं_, нола, ीपति,
+ {{0x6723b29e,0x92c280ab,0xe91980e8,0xa91d9249}}, // ranj, ্ধু_, _нові_, _loži,
+ {{0x6449b29f,0xf1bf05b9,0x6d40b2a0,0x212d82f7}}, // ttei, jná_, ggma, _udeh_,
+ {{0x44290788,0xf1bf0775,0x236799b7,0xa91d8450}}, // nsa_, dná_, _panj_, _noži,
+ {{0x442932a1,0x6449b2a2,0x2d8280f2,0x7c3b8079}}, // isa_, rtei, öker_, ruur,
+ {{0xd9460d0e,0xc34e8028,0xfbd20039,0x44291813}}, // нени, _tổng_, רתי_, hsa_,
+ {{0x3f878024,0xaca381bc,0x569486c8,0x644992af}}, // ljnu_, _ajụk, _шахт, ptei,
+ {{0xaacf885d,0x44291554,0xb9081404,0x25bf802e}}, // _सुरक, jsa_, _भर_, mnul_,
+ {{0x660195dd,0xe8df8135,0x7d09b2a3,0x442932a4}}, // _kulk, _arịa_, lces, dsa_,
+ {{0x44292d08,0x66018364,0x7c29b2a5,0xf1bf026f}}, // esa_, _julk, lser, bná_,
+ {{0x660180a4,0xf1bf016b,0xe7e002f1,0x6d9c9e37}}, // _mulk, cná_, _खीरा_, _léan,
+ {{0x7c29b2a6,0x442932a7,0x64a31597,0xdca31b78}}, // [1fa0] nser, gsa_, гата, гати,
+ {{0xa3c1800f,0x6721843d,0xf40180c8,0x68e432a8}}, // ्कि_, _nelj, _এবার_, _izid,
+ {{0x442932a9,0x1b1f00ab,0xdb1c0035,0x7d098035}}, // asa_, _পাশে_, _skró, kces,
+ {{0x7d0980d2,0x442932aa,0xf1a99ef7,0x3f8cb2ab}}, // jces, bsa_, وانه_, _sodu_,
+ {{0x1b1f00c8,0x442932ac,0xe814016f,0x6721b114}}, // _পারে_, csa_, तीचा_, _belj,
+ {{0x6d9c80f7,0x7c29b2ad,0x67218353,0x7dde80f1}}, // _céan, dser, _celj, tëso,
+ {{0x6d9c8307,0xa3b501d2,0xa3bf0a61,0x6721b2ae}}, // _déan, _जगत_, ुकर_, _delj,
+ {{0xa2ca8a75,0x7dde80f1,0x7bc3b2af,0x66018084}}, // _सुग्, rëso, _ajnu, _dulk,
+ {{0x6d460358,0x7c29b2b0,0xf1bf0a21,0xb7da8591}}, // _mcka, gser, vná_, _فورا_,
+ {{0xc45380f7,0x6d9c8b6a,0x1fb58195,0x69d60c8b}}, // اضيع, _géan, _исхр, _akye,
+ {{0xf1bf003e,0x7c2985ee,0x7dde8168,0x533700be}}, // tná_, aser, qëso, ענטן_,
+ {{0x442932b1,0x672180d2,0xa91db2b2,0x7d09a3e1}}, // ysa_, _zelj, _poži, cces,
+ {{0x55778158,0xf1bf003e,0xa3d0199e,0x6601b2b3}}, // _רעדן_, rná_, शवा_, _zulk,
+ {{0xe47b812a,0xf1bf026f,0x4429006a,0x5c978a8e}}, // _מרדכ, sná_, vsa_, _якія_,
+ {{0xf1bf026f,0x67e00106,0x68e401f6,0x6be381a8}}, // pná_, möjl, _ezid, _حكوم,
+ {{0x442932b4,0x13d580ab,0xa3e48beb,0xa91d82d4}}, // tsa_, _হওয়, _भीत_, _toži,
+ {{0x24580009,0x41c40c3b,0x442b32b5,0x98b90084}}, // [1fb0] нать_, _حقيق, _qvc_, mesį_,
+ {{0x69dd0014,0x23a4016d,0x236a0144,0xf11980c2}}, // llse, _nöjd_, _cabj_, दर्द_,
+ {{0x442932b6,0xa2d40ebf,0x3a3e82f7,0x290b0037}}, // ssa_, _बुद्, nutp_, occa_,
+ {{0x442932b7,0xc984838b,0x5184b2b8,0x7c29b2b9}}, // psa_, лучи, луча, yser,
+ {{0x34940196,0x657d32ba,0x6601b2bb,0x442932bc}}, // _баяр, _insh, _sulk, qsa_,
+ {{0x66019e1f,0x6d5f13de,0x7c29b2bd,0xb17b0338}}, // _pulk, leqa, vser, llåd,
+ {{0x644d32be,0x6721b2bf,0xbbbf1305,0x7c29b2c0}}, // ltai, _velj, ्वीक, wser,
+ {{0x6d5f208e,0x7c29b2c1,0x672732c2,0x644d32c3}}, // neqa, tser, hajj, otai,
+ {{0x6721b2c4,0x644d32c5,0x9f5307e2,0xa3b5064d}}, // _telj, ntai, _així_, _जगा_,
+ {{0x660190c0,0x25bf8359,0x7d0981e8,0x672728d8}}, // _tulk, snul_, sces, jajj,
+ {{0x644d32c6,0x7c29b2c7,0x2d890681,0x34de0ebf}}, // htai, sser, kjae_, _मर्द,
+ {{0xe2968554,0x644d32c8,0x60dbb2c9,0x61fbb2ca}}, // таю_, ktai, _kyum, _jiul,
+ {{0x6d5f0748,0x2ca9076c,0x656d01b4,0x8c45951b}}, // deqa, jzad_, ddah, _реле,
+ {{0x656b8013,0x644d32cb,0x2d8f8a0f,0x69dd0074}}, // _hagh, dtai, _hoge_, alse,
+ {{0x443fb2cc,0x644d0084,0x6d460122,0x9f4581a8}}, // luu_, etai, _pcka, _chlé_,
+ {{0xdde9003d,0x2d8fb2cd,0xfce615a6,0x656d32ce}}, // فرقه_, _joge_, вобо, gdah,
+ {{0x60dbb2cf,0x656bafbb,0x443fb2d0,0xdb1a816a}}, // [1fc0] _nyum, _magh, nuu_, cotó,
+ {{0x4e0608cc,0x656bb2d1,0x657d0c2e,0x22ba1ef5}}, // _изоб, _lagh, _ensh, وداع_,
+ {{0x23a400f2,0x644d032f,0x6a860098,0x3ea61630}}, // _nöje_, atai, ължа, _ринг,
+ {{0x443fb2d2,0x61e9b2d3,0x656bb2d4,0xc05a90ac}}, // kuu_, _chel, _nagh, дік_,
+ {{0xd0078698,0x92bd80c8,0xd94395ac,0x61fb8b20}}, // вече_, _আরো_, речи, _diul,
+ {{0xdefa81a0,0x6d4432d5,0x244b806a,0x443f8079}}, // мый_, lgia, døm_, duu_,
+ {{0x764e32d6,0x64598176,0x61fb8162,0xacbb0866}}, // ntby, _apwi, _fiul, _jeûn,
+ {{0x6d4432d7,0x61fbb2d8,0x2d8fb2d9,0x61e9b2da}}, // ngia, _giul, _coge_, _ghel,
+ {{0x80e101fe,0x656bb2db,0x443fb2dc,0x9f4c8198}}, // _नरें, _dagh, guu_, _pidä_,
+ {{0x6d442322,0xb4e28127,0x5fb68074,0xa91d8115}}, // hgia, _धरी_, _अगिल, _dožu,
+ {{0x75228267,0x46d232dd,0x656bb2de,0x19950a4c}}, // _teoz, _दुलह, _fagh, ганя,
+ {{0x2004a479,0x80d100ab,0x61e980f1,0x656b8870}}, // _kumi_, _সৃষ্, _xhel, _gagh,
+ {{0x8c4380e9,0x1d079634,0x656d32df,0xb17b0106}}, // _вече, _речи_, vdah, vlåd,
+ {{0xa2cb016f,0x656bb2e0,0x6440b2e1,0x2004b2e2}}, // _तुझ्, _zagh, mumi, _mumi_,
+ {{0x6440b2e3,0xa3c18128,0x6d9c80f7,0x2004b2e4}}, // lumi, ्कर_, _léam, _lumi_,
+ {{0x6d440698,0xa2b804c5,0x644d0364,0xa3b50740}}, // ggia, ्पन्, ttai, _जगह_,
+ {{0x6d9c94e1,0x6440b2e5,0x656d0359,0x644d051e}}, // [1fd0] _réal, numi, rdah, utai,
+ {{0x644d32e6,0x6d5f05e7,0x61fbb2e7,0xbbbf000c}}, // rtai, seqa, _siul, ्वेक,
+ {{0x6440822e,0x61e9b2e8,0x644d32e9,0x60db80ee}}, // humi, _phel, stai, _syum,
+ {{0x656d02ed,0x6440b2ea,0x2004859c,0x99d180ab}}, // qdah, kumi, _bumi_, িষ্ক,
+ {{0x443f82c1,0x64408029,0x7c841baa,0x656bb2eb}}, // xuu_, jumi, _куре, _ragh,
+ {{0x6440b2ec,0xb60583cb,0x7d0d32ed,0x2004b2ee}}, // dumi, hláš, lcas, _dumi_,
+ {{0x656b97ef,0x99678d69,0x61e9b2ef,0x28da10be}}, // _pagh, _итал, _thel, _युनि,
+ {{0x4efc0051,0x443fad48,0x3949022c,0xa91d812b}}, // ולוג, tuu_, _ncas_, _požu,
+ {{0x6440b2f0,0xa3d8016f,0xa3c18aed,0x60db8bdf}}, // gumi, ातच_, ्कल_, _uyum,
+ {{0x443f927b,0x244b8aa2,0xceeb92c8,0x7d0d0362}}, // ruu_, røm_, _قران_, hcas,
+ {{0xe299b2f1,0x3869318b,0x656bb2f2,0x2d8f933b}}, // хан_, _asar_, _tagh, _toge_,
+ {{0x98b20201,0xe5708c2a,0x6440b2f3,0x443fb2f4}}, // _sayı_, _وطن_, bumi, puu_,
+ {{0x7d1bb2f5,0xad9b1d3a,0x6440925c,0x7c2d0338}}, // mbus, _miúd, cumi, jsar,
+ {{0x442db2f6,0x7c2d0bbd,0x6a7781bc,0x39490580}}, // mse_, dsar, _ụfo, _ecas_,
+ {{0x442db2f7,0xed5a1928,0xd5ac8065,0x386902c4}}, // lse_, нов_, _رہی_, _esar_,
+ {{0xceb283c8,0x442db2f8,0x29000118,0x69c38061}}, // צין_, ose_, _ugia_, énel,
+ {{0x442db2f9,0x69db0158,0x707700f7,0x7c3b802a}}, // [1fe0] nse_, _אַזו, _مميز_, nrur,
+ {{0x442db2fa,0x6d9c80f7,0x1c1ba8b3,0x442fb2fb}}, // ise_, _réam, पीएल_, _mvg_,
+ {{0x6440b2fc,0x442db2fd,0x77f70039,0x2004b2fe}}, // zumi, hse_, _תמיד_, _sumi_,
+ {{0x442db2ff,0x9f5a06c0,0x7d0d00e5,0xaca384be}}, // kse_, _sipè_, ccas, _akọk,
+ {{0x64e2823c,0x442db300,0x25d70039,0x69c2b301}}, // _परेश, jse_, _תוכן_, rnoe,
+ {{0x38cb91fb,0x442db302,0xb7d280ab,0x6440b303}}, // رانی_, dse_, াষ্ট, vumi,
+ {{0xfaa617f4,0x7bcb087a,0x499482e3,0x316d80b9}}, // _саво, égue, _پیشر, _faez_,
+ {{0x6440b304,0x6d9c80f7,0x62810088,0x7d1bb305}}, // tumi, _téam, _šlog, gbus,
+ {{0x5b26aafb,0xd326976a,0x442db306,0xb99380f7}}, // льна, льни, gse_, _القب,
+ {{0x6440b307,0xf366a3e0,0x3946b308,0xaca401bc}}, // rumi, утин, lgos_, _nsọt,
+ {{0x442db309,0x7c2d0029,0xafdb0aa2,0x7dde8168}}, // ase_, zsar, ktør, hësi,
+ {{0x3946b30a,0xdb01b30b,0x7dde8168,0xaca4046d}}, // ngos_, filó, kësi, _asọt,
+ {{0x7c3b802e,0x7d028135,0xc5f38039,0x442db30c}}, // crur, _igos, _הדף_, cse_,
+ {{0x6ed61d17,0x66051e71,0xb8fd32dd,0xb8dd03b7}}, // _मुहु, _puhk, _तँ_, _आध_,
+ {{0x4422330d,0x7c2d01e0,0x394900b9,0x7dde8168}}, // _awk_, wsar, _tcas_, tësh,
+ {{0x63ba83cb,0x7c2d330e,0x4422036e,0x27ec80ee}}, // litn, tsar, _bwk_, _lhdn_,
+ {{0x386903aa,0x7d0d17d6,0x69c382be,0x26de808e}}, // [1ff0] _usar_, rcas, énem, _lyto_,
+ {{0x212b330f,0x3946b310,0x63ba8f20,0x798083ec}}, // lach_, egos_, nitn, _anmw,
+ {{0x7c2d0e20,0x61ed1b4f,0x8afe8300,0x316d8144}}, // ssar, _khal, _baƙi, _paez_,
+ {{0x212b3311,0x7d02b312,0x7c2d26b1,0x442db313}}, // nach_, _ngos, psar, yse_,
+ {{0x39498029,0x1869817c,0xa069b314,0x61ed125b}}, // ļas_, тали_, тала_, _mhal,
+ {{0x212b0083,0x656f3315,0x25a93316,0xe736b317}}, // hach_, _hach, lhal_, аеш_,
+ {{0x656f3318,0x212b1988,0x244f0457,0x7c2284be}}, // _kach, kach_, lüm_, _awor,
+ {{0x442db319,0x629a8d42,0x212b0035,0x2bbf0b84}}, // tse_, dyto, jach_, ्वका,
+ {{0x212b330f,0x291db31a,0x656f331b,0x442d90b6}}, // dach_, mbwa_, _mach, use_,
+ {{0x656f331c,0xe7a983bb,0x7d028870,0x61ed011e}}, // _lach, _कतिप, _egos, _ahal,
+ {{0x61ed331d,0x26e4000d,0x4acf999e,0xf98617c7}}, // _bhal, _गरेर_, _सुखव, агно,
+ {{0x656f331e,0x442db31f,0x212b3320,0x3f812f9f}}, // _nach, pse_, gach_, _anhu_,
+ {{0x7643b321,0x61ed02c1,0x15f484e3,0x25a905a1}}, // luny, _dhal, _اسلح, dhal_,
+ {{0x629ab322,0x656f3323,0xdca28fc8,0x63ba9b40}}, // byto, _aach, _фаши, citn,
+ {{0xb8fd0935,0x645d0110,0x212b3324,0x39468110}}, // _तु_, _apsi, bach_, ygos_,
+ {{0x7dde820f,0x61ed3325,0x3d20009a,0x25a901e4}}, // tësi, _ghal, बरें_, ghal_,
+
+ {{0x656f3326,0x7643b327,0x8c46143b,0x044600e8}}, // [2000] _dach, huny, резе, резн,
+ {{0x7dde820f,0x656f0ad0,0x7643b328,0xd76402e3}}, // rësi, _each, kuny, _اندی,
+ {{0x320787e2,0x656f3329,0x34e28b04,0x764389ca}}, // _juny_, _fach, _पर्द, juny,
+ {{0x656f332a,0x25a91054,0x39468110,0xd2e603a4}}, // _gach, chal_, ugos_, _करीब_,
+ {{0x39469e00,0x7dde80f1,0x8eb3003d,0x2d820326}}, // rgos_, qësi, _همیش, _inke_,
+ {{0x656f0f66,0x81bd0029,0x394685a4,0x6d59b32b}}, // _zach, lsēt, sgos_, _abwa,
+ {{0x76438dc4,0x8afe8326,0x56948705,0x798085ee}}, // guny, _taƙi, сайт, _tnmw,
+ {{0x6d5606c0,0x645d00e5,0x25a0332c,0x63bab32d}}, // nfya, _ypsi, nkil_, witn,
+ {{0x88bd8063,0x2fd8016d,0x7763ac88,0xdb1aa5ce}}, // _jeśl, korg_, benx, entá,
+ {{0x61ed332e,0x29120010,0x386980f1,0x26de801b}}, // _shal, _afya_, çare_, _tyto_,
+ {{0x212b332f,0xc333078d,0x76439481,0x7ac69509}}, // tach_, חות_, cuny, рспе,
+ {{0x7d028025,0x7c22809a,0x29dc8511,0x96958bda}}, // _ugos, _twor, mías_, _уруш,
+ {{0x212b3330,0x29dc8511,0x6728b331,0xa49b0580}}, // rach_, lías_, _kedj, _agòn,
+ {{0x212b3311,0x656f3332,0x2d823333,0x644f809f}}, // sach_, _sach, _anke_, àcie,
+ {{0x61ed1e66,0x6728b334,0x656f0e44,0x29dc8511}}, // _thal, _medj, _pach, nías_,
+ {{0x6728807d,0x61ed004f,0x656f2795,0x8afe8326}}, // _ledj, _uhal, _qach, _saƙw,
+ {{0x2fd83335,0x656f1030,0x25a90965,0x61e28122}}, // [2010] borg_, _vach, rhal_, dlol,
+ {{0x656f3336,0x672882fd,0x9f5e820f,0x25a91b66}}, // _wach, _nedj, _ditë_, shal_,
+ {{0x656f3337,0x68e98019,0x200102c4,0x20090326}}, // _tach, _szed, _iihi_, _huai_,
+ {{0x656f1a29,0x76438aef,0x29dc85e4,0x2cad808e}}, // _uach, vuny, días_, yzed_,
+ {{0xa3e680c2,0xa2d88540,0xe7398256,0x68e0831d}}, // पता_, _नुस्, гел_, _cymd,
+ {{0x785a8029,0x7643b338,0x29dc82ba,0x8a170077}}, // dāvā, tuny, fías_, _نظرا,
+ {{0xf7708bbe,0x29dc85a4,0xed598d55,0x7763802a}}, // بان_, gías_, гой_, renx,
+ {{0xa3b305e8,0x764389ca,0x752d3339,0x2fd800f3}}, // जोर_, runy, naaz, zorg_,
+ {{0xbee403bb,0x76438d6d,0x68e08114,0x8afe80fc}}, // _गर्न_, suny, _gymd, _maƙu,
+ {{0x76438886,0xed59812b,0x29dc8020,0x4a4592ea}}, // puny, diže_, bías_, бнов,
+ {{0xe1fa0396,0x29dc85e4,0x6b95333a,0xd5758110}}, // лго_, cías_, _mozg, _гуль,
+ {{0x6d5982a0,0x200900ee,0x6728826c,0x35f5abd9}}, // _ubwa, _buai_, _zedj, _упер,
+ {{0xd24e8416,0x200900ee,0x2fd8008b,0xf1b283de}}, // ونی_, _cuai_, torg_, לסט_,
+ {{0x443fb33b,0x200900b9,0x6d49b33c,0xf4128039}}, // mru_, _duai_, lgea, _צפו_,
+ {{0x78c10086,0x66e6333d,0xdb1ab33e,0x6b83b33f}}, // _əvvə, сова, zitë, _inng,
+ {{0x6d49947d,0x29008110,0x394d81c0,0xdce4012b}}, // ngea, žiai_, _nces_, ndić,
+ {{0x6d49832f,0x69d9809a,0x7984020d,0x20093340}}, // [2020] igea, dowe, _iniw, _guai_,
+ {{0x25a03341,0x443fb342,0x68e08106,0x65641f5d}}, // skil_, iru_, _rymd, seih,
+ {{0x29dc84c3,0x443fb343,0x490680f7,0x67288b80}}, // xías_, hru_, مواق, _redj,
+ {{0x5d860013,0x614691b3,0x705309d7,0xd764015b}}, // _الحل, седа, _بنیا, _انگی,
+ {{0x2056b344,0x6b95026c,0xe3b10065,0x67288088}}, // стер, _fozg, سرے_, _pedj,
+ {{0x443fb345,0x29dc91b9,0x23658267,0xfbc78481}}, // dru_, tías_, jelj_, _رت_,
+ {{0x44321486,0x443fb346,0x9f520144,0xe2970088}}, // lsy_, eru_, _huyó_, _лар_,
+ {{0x29dc960a,0x443f8b67,0x98a68012,0x15fa0bb8}}, // rías_, fru_, _либе, ्दिर_,
+ {{0xdb1ab347,0x44323348,0x443fb349,0x29dc8511}}, // lité, nsy_, gru_, sías_,
+ {{0x320a334a,0x6d498706,0x29dc8333,0x7984113b}}, // _kuby_, agea, pías_, _aniw,
+ {{0xa3d90ee6,0xdd8f8162,0x15f811bc,0x200903ac}}, // ावत_, _еш_, ंगार_, _suai_,
+ {{0x443fb34b,0x394d334c,0x7dde8168,0x25a682d5}}, // bru_, ües_, tësu, _ulol_,
+ {{0x443f8012,0x2009334d,0x8afe89ab,0x6ac4066b}}, // cru_, _quai_, _raƙu, वपुर,
+ {{0x7dde80f1,0x442684be,0x443201b0,0x33f68226}}, // rësu, _iwo_, dsy_, _учес,
+ {{0x9f5e80e7,0x3da709a8,0xdced0699,0x69d9804f}}, // _cité_, браб, rdač, yowe,
+ {{0x7bda8bd2,0xd131003d,0x200900ee,0xb88601d0}}, // lotu, _کمک_, _tuai_, _spíš,
+ {{0x6eda8076,0x7dde80f1,0xdcfd026c,0xb60689d1}}, // [2030] _पुसु, qësu, _posđ, lišć,
+ {{0x6ac401c4,0x68ed0b67,0x44269437,0xa3b3009a}}, // वपूर, _izad, _mwo_, जों_,
+ {{0x69d9b34e,0x3cde016f,0x7bc8b34f,0xdd950196}}, // towe, _कुठे_, indu, _даны,
+ {{0x19598084,0x4426877f,0xa159b350,0x127b80be}}, // рады_, _owo_, раду_, טאבע,
+ {{0x44268091,0xf09f3351,0x69d9b352,0x7bda9619}}, // _nwo_, nyà_, rowe, kotu,
+ {{0x8cdc800f,0x645c802e,0xbcfb3353,0x01cf00ab}}, // _पड़ो, ăril, _abég, রতিদ,
+ {{0x35f803f8,0x4426b354,0xdb1a80e7,0x7bda8d15}}, // _خرید_, _awo_, cité, dotu,
+ {{0xa2b81d40,0x44268e4d,0xd0d58098,0x394db355}}, // ्पर्, _bwo_, _добъ, _uces_,
+ {{0x6d498068,0x443fb356,0x386d8051,0x442682f7}}, // rgea, uru_, _user_, _cwo_,
+ {{0x443f8aee,0x6d49b357,0x212f8229,0xdb0e008b}}, // rru_, sgea, lagh_, _embæ,
+ {{0x6d5d0a56,0x68ed0201,0x6602b358,0x198a0098}}, // _obsa, _azad, _fiok, абни_,
+ {{0x443fb359,0x66028037,0x23659502,0xbcfb046d}}, // pru_, _giok, selj_, _gbég,
+ {{0xa3d90576,0x4426b35a,0x2365817f,0x629e009a}}, // ावा_, _gwo_, pelj_, zypo,
+ {{0x6d5d335b,0x6d4f0098,0x25bf8087,0x6b8380e8}}, // _absa, _acca, liul_, _unng,
+ {{0x9f52046d,0xa50a335c,0x68ed0c2e,0xd62a108d}}, // _buyò_, иева_, _ezad, робе_,
+ {{0x44321af4,0xdb1a82be,0x25bf802e,0x386000b4}}, // tsy_, vité, niul_, _tpir_,
+ {{0x249f9ca6,0x212f8706,0x290501d0,0x752bb35d}}, // [2040] nyum_, dagh_, ělat_, _megz,
+ {{0x4432335e,0x212280bc,0x25bf8162,0x61e60fb6}}, // rsy_, मर्श_, hiul_, llkl,
+ {{0xed5a00a9,0x3dc902d5,0x7a08928a,0x826601a8}}, // _кон_, bnaw_, _pěti, تهان,
+ {{0x00e6a606,0xe9da8604,0xdb1ab35f,0xd3788503}}, // ожен, шке_, rité, moće_,
+ {{0x25bf802e,0x7bda804f,0x7bc8808b,0x25ad9ab3}}, // diul_, yotu, yndu, dhel_,
+ {{0x442682a0,0x2d99b360,0xc7a39a19,0x8fa39ad8}}, // _rwo_, ösen_, мичк, маче,
+ {{0x59ca09c2,0x2454039c,0xf8bf0866,0xd37880fe}}, // ाचार, läm_, mbée_, noće_,
+ {{0x2cbf00fe,0x25adb361,0x2d9202a0,0xdc1e80ab}}, // _žudi_, ghel_, njye_, _দিবস_,
+ {{0xc5f3078d,0x938a9b2f,0x7bdab362,0x2717001b}}, // ודה_, асна_, totu, nění_,
+ {{0xa3e48105,0xa3ea00bc,0x442681ed,0x68ed0035}}, // _भीख_, मति_, _vwo_, _rzad,
+ {{0x7ae3831d,0x86c680f7,0xf1bf01df,0xddc8826c}}, // _cynt, _بيان, tiá_, _irdž,
+ {{0xb606b363,0x44268051,0x25bf802e,0x29008110}}, // rišć, _two_, ciul_, žiau_,
+ {{0x44268578,0x7bdaae5e,0x25d9803d,0xcf00011c}}, // _uwo_, potu, _اهنگ_, əşən_,
+ {{0x4374803d,0x271701d0,0x68ed016b,0xc6a71d7b}}, // _رهبر, dění_, _vzad, орби,
+ {{0x3ea50019,0x7ae38114,0xa3d906ae,0xed59811f}}, // ált_, _gynt, ावस_, liža_,
+ {{0x321e89c4,0xdb1703a8,0x51870b26,0x7647018e}}, // mpty_, rixí, _фука, rujy,
+ {{0x5fbc801b,0x030e8816,0x68ed3364,0x6d9c81a8}}, // [2050] ्चाल, _सलाह_, _uzad, _héas,
+ {{0x69dd3365,0x09d38bbc,0xd37101a8,0x7a35819d}}, // lose, तव्य, عها_, _ẹtit,
+ {{0xe8d90870,0xdb01956e,0x211881bc,0xc3240264}}, // _mbọ_, silö, _ịtụl, _পাখি_,
+ {{0xe0d383f8,0x6b988edd,0x249f9c99,0x67ed95a0}}, // وزش_, _lovg, yyum_, dúja,
+ {{0x29090122,0x95160039,0xdb1e0580,0x6d4d14ba}}, // _mgaa_, _מקרא_, kipè, mgaa,
+ {{0x81c30a49,0x2d9900b4,0x6cfa0039,0x1b2280ab}}, // ্তি_, _hose_, _לפרס, _মাঝে_,
+ {{0x2d992637,0x69dd3366,0x25adb367,0x67ed8019}}, // _kose_, kose, thel_, gúja,
+ {{0x6d4d12eb,0xdceb84b7,0xe8d9046d,0x29093368}}, // ngaa, _jogħ, _abọ_, _ngaa_,
+ {{0x65a30006,0x81c300c8,0x25bf802e,0xdceb9693}}, // _põhj, ্তা_, riul_, _bagā,
+ {{0x5ba98319,0x290901bc,0xdceb84b7,0x26d30216}}, // ском_, _agaa_, _logħ, _oxxo_,
+ {{0x69cbb369,0x249fb36a,0x25bf8087,0x95cba857}}, // onge, syum_, piul_, _куда_,
+ {{0x2005822e,0x2ca0336b,0x69dd0168,0x186a2306}}, // _hili_, yyid_, gose, бами_,
+ {{0x69cbb36c,0xda0b8035,0x6d5b8c53,0x6d4d336d}}, // inge, _सूरत_, mfua, dgaa,
+ {{0xd9b8800c,0xa4fa81c6,0xaabd09c1,0x2d99010c}}, // _अष्ट, _פלסט, ्पाक, _aose_,
+ {{0x2d9902a0,0x2005b36e,0x59db86bf,0x76438359}}, // _bose_, _mili_, यकार, nrny,
+ {{0xf992003f,0x2005b36f,0x6d4d3370,0x2d990098}}, // _سبب_, _lili_, ggaa, _cose_,
+ {{0xdb1e020f,0x02c4016f,0x6ac4036d,0x7643808e}}, // [2060] qipë, वप्न, वप्र, hrny,
+ {{0x6d4d1cbc,0x6569b371,0x395f89cd,0xa3cb8072}}, // agaa, heeh, _abus_, रचि_,
+ {{0x2d993372,0x479b0158,0x98b0805c,0x179b00be}}, // _fose_, _הייס, đači_, _הייב,
+ {{0x7bc1b373,0x6d9c80f7,0xddc88754,0x80da80ab}}, // hilu, _léar, _srdž, _বৃষ্,
+ {{0xd12f8d4a,0xa3bd835a,0x75243374,0x7bc1b375}}, // امه_, ीचा_, mbiz, kilu,
+ {{0x26118104,0x2005b376,0xdb1e09c4,0x6449809f}}, // _báo_, _cili_, kipé, duei,
+ {{0xa3d91cd4,0x2005b2db,0x26118028,0x44203377}}, // ावर_, _dili_, _cáo_, lpi_,
+ {{0x7bde23be,0xdcebb378,0x66061bcd,0x77ab0085}}, // lopu, _xogħ, _jikk, _müxa,
+ {{0x6449abe1,0x2005b379,0x6d9c81a8,0x4420337a}}, // guei, _fili_, _béar, npi_,
+ {{0x0b8a8364,0x3160008e,0x6d9c81a8,0x6606337b}}, // ссии_, _abiz_, _céar, _likk,
+ {{0xe4d699f4,0x46db0105,0x6d9c81a8,0x644401b4}}, // _کتاب, _मुंह, _déar, hrii,
+ {{0xdb1ab37c,0x7bde0133,0x6d9c8388,0xfbc400ab}}, // mití, hopu, _véas, ্তিত,
+ {{0x644983d3,0x7bc1b37d,0xdb1ab37e,0x7bde0198}}, // cuei, bilu, lití, kopu,
+ {{0x2259809a,0xe8d9019d,0x200581b4,0x6d9c8174}}, // ńska_, _nnụ_, _xili_, _géar,
+ {{0x6d4d26a1,0x6a1380f7,0xe3c400ab,0xdb1e00f7}}, // tgaa, _كبير, ্তাব, cipé,
+ {{0x66060065,0xe8d90870,0x91e40eef,0x1c11864a}}, // _cikk, _anụ_, _поје, _डूडल_,
+ {{0x6606337f,0xa3d90006,0x539921f6,0x442000e4}}, // [2070] _dikk, ावल_, овая_, gpi_,
+ {{0x25ef05b3,0x3374002e,0x2a6302f7,0x7bde008e}}, // _आठवी_, нгур, _ppjb_, gopu,
+ {{0x35f5018b,0xdb0181ec,0x44203380,0xa3b30a43}}, // _опор, chlä, api_, जोग_,
+ {{0x69cb8081,0x644410b5,0xdb1e0198,0x7bc1b381}}, // unge, brii, enpä, zilu,
+ {{0x2005822e,0x200d8081,0x3ce681c0,0x7777022b}}, // _pili_, _quei_, _nyov_, _kaxx,
+ {{0x69dab382,0xdb23003d,0x66e5b383,0xdee5951b}}, // _íten, _سوری, мола, моли,
+ {{0x7bc184b7,0xe7308fc5,0x44393384,0x6d5b80b9}}, // vilu, اصه_, _hvs_, tfua,
+ {{0x69c288f1,0x2005b385,0x395f833e,0x6449b386}}, // cioe, _wili_, _ubus_, tuei,
+ {{0x7bc1b1c4,0x387f81b4,0x752f0035,0x290080e1}}, // tilu, _urur_, _mecz, žiar_,
+ {{0x752f009a,0x261180ff,0x799b8bfd,0xad9b0174}}, // _lecz, _táo_, _houw, _siúl,
+ {{0xd94623e0,0x7bc18a6e,0x61460d45,0x44200102}}, // мени, rilu, мена, zpi_,
+ {{0x5ec100ab,0x799bb387,0x7bc1b388,0x44203389}}, // _শুভে, _jouw, silu, ypi_,
+ {{0x6606338a,0x7bc1802e,0x6d9c80f7,0x64499a7d}}, // _rikk, pilu, _téar, quei,
+ {{0x660606be,0x69c28102,0x777700e5,0x799bb38b}}, // _sikk, zioe, _caxx, _louw,
+ {{0x66e3338c,0x6606037b,0x7a08800d,0x1db183eb}}, // вора, _pikk, _děts, _जतात,
+ {{0x69bf89a3,0x3dcd822b,0x644414c7,0xdb1c338d}}, // लोमी, nnew_, trii, _imré,
+ {{0x7bde0b67,0x5f76045b,0xdb1a8524,0xa3d9016f}}, // [2080] topu, _قادر, zití, ावं_,
+ {{0x4439338e,0x64441eaf,0x660625d0,0xf77201f9}}, // _dvs_, rrii, _wikk, _شاخ_,
+ {{0x81c300c8,0x6606338f,0x7c360338,0x799b8a0f}}, // ্তর_, _tikk, ssyr, _bouw,
+ {{0x64a60071,0x64443390,0x2904026c,0x6d9c810c}}, // _жама, prii, žmau_, _néap,
+ {{0x69c2811b,0x799b9c11,0x2a6d01a1,0xdb1a82df}}, // rioe, _douw, _ćebe_, nitã,
+ {{0x43753391,0x7a0c0087,0xd378abea,0x7989b392}}, // _пуст, _aşte, koća_, _enew,
+ {{0x7c2b8114,0xa3db06af,0x799b8176,0x6b9c304c}}, // _awgr, डकर_, _fouw, _iorg,
+ {{0x5c749508,0x65a30006,0x6b9c3393,0x61e43394}}, // елит, _põhi, _horg, _nkil,
+ {{0x69c0b395,0x6b9c1388,0xbeeb064a,0x21268d8b}}, // _imme, _korg, _टर्न_, mboh_,
+ {{0x61e40010,0xf3668d9e,0x7d0b8558,0x7ac6818b}}, // _akil, фтин, _eggs, дсме,
+ {{0xa3b60e18,0x3ddf8a0f,0xbcfb01a8,0x6b9c3396}}, // _जतन_, bouw_, _mbéa, _morg,
+ {{0x6b9c0c41,0x9e658065,0x61f610ae,0x237802d6}}, // _lorg, _سامن, _chyl, _larj_,
+ {{0x6b9c01b0,0x0cc900ab,0x6562818e,0x00000000}}, // _oorg, _শর্ত, _iboh, --,
+ {{0xead49ea4,0x6b9c0cde,0x2cb0861c,0x61e43397}}, // _поль, _norg, ıldı_, _ekil,
+ {{0xbd0580e7,0x69c08a6f,0xf8ae004e,0xdd9b1617}}, // _théâ, _omme, لکی_, оша_,
+ {{0xfaa7803d,0xdb1a92d0,0xc5f881a9,0x96f80993}}, // _تجهی, lità, _spēj_, фест_,
+ {{0x3cfc011b,0x6b9c3398,0xdce980d2,0x94ab06b5}}, // [2090] рvv_, _borg, rdeć, цтва_,
+ {{0x799b82d6,0x69c0b399,0x44390370,0xdb1ab39a}}, // _souw, _amme, _vvs_, nità,
+ {{0x6562816b,0xafdb004a,0x69cf0037,0xfd100061}}, // _oboh, nrøm, once, اجہ_,
+ {{0x77b000f2,0x442b041c,0x673507d8,0x3495164f}}, // _växe, _twc_, lazj, _замр,
+ {{0x6b9c1c93,0x2d8b02d0,0x7e620b80,0x799b890d}}, // _forg, _ince_, _ćopi, _vouw,
+ {{0x69c0b39b,0xdce407d9,0x644d000e,0x67352358}}, // _emme, ldiğ, muai, nazj,
+ {{0x644d32be,0x4ae401e2,0x4ad903b7,0x16a9af92}}, // luai, _аўта, _बँटव, явки_,
+ {{0x6b9c339c,0xc8c0835a,0x7bc5339d,0xdce40214}}, // _zorg, _शेवट, lihu, ndiğ,
+ {{0x4439b39e,0x644d339f,0x3ddf81ed,0x290d80e5}}, // és_, nuai, rouw_, _igea_,
+ {{0x61e42b0d,0x3dcd89c4,0x69cf24cc,0x7bc533a0}}, // _skil, snew_, ence, nihu,
+ {{0x25a90065,0xfaa58470,0x2d8b1b7d,0x644d01a8}}, // kkal_, хано, _once_, huai,
+ {{0x9f47816b,0x22598035,0x6dc792c8,0x7d0b80c3}}, // dlné_, ńsko_, _غزال, _uggs,
+ {{0x320902d5,0xa5c6807b,0x6abd1107,0x7bc533a1}}, // _diay_, sjóð, ्पुर, kihu,
+ {{0x2d8b33a2,0x644d33a3,0xdb1a8980,0x2d9db3a4}}, // _ance_, duai, cità, _howe_,
+ {{0x645bb3a5,0x24598385,0x2d9da0d2,0x7a0901d0}}, // ltui, lèm_, _kowe_, _větr,
+ {{0x65aa003e,0x657b9849,0x3209001c,0x06072dd0}}, // _výho, nduh, _giay_, енск_,
+ {{0xd46715fd,0x04430e6b,0xb4db026b,0x68e980d7}}, // [20a0] ните_, летн, _adàb, _nyed,
+ {{0x69c633a6,0x2d8b03a8,0x213f885c,0x6abd0b04}}, // like, _ence_, _aduh_, ्पूर,
+ {{0x6b9c02af,0xdb1ab3a7,0x814680f7,0x68e9ac9a}}, // _vorg, nitá, فنان, _ayed,
+ {{0x69c633a8,0x2d9d809a,0x68e985ee,0x645b9407}}, // nike, _nowe_, _byed, ktui,
+ {{0x644d0748,0x764e0c8b,0x6b9c33a9,0x201209ab}}, // cuai, luby, _torg, _juyi_,
+ {{0x290d802a,0x69c633aa,0x201209ab,0x6283b3ab}}, // _egea_, hike, _muyi_, _orno,
+ {{0x201fb3ac,0xfa670d8f,0xda0b8327,0x56949383}}, // _etui_, _парк_, _संगत_, тайт,
+ {{0xd5b7abca,0xf3908028,0xc693036b,0x69c0b3ad}}, // есь_, _cảnh_, ראה_, _umme,
+ {{0x6283b3ae,0x7c2433af,0x2d9db3b0,0x61e2a6e8}}, // _arno, npir, _dowe_, mool,
+ {{0x61e2b3b1,0x765c009a,0xafdb03ba,0x39402179}}, // lool, ktry, trøm, _adis_,
+ {{0x6283803b,0x69c633b2,0xf8bf8125,0xdb1ab3b3}}, // _crno, fike, _þér_, gitá,
+ {{0x248000ce,0x765c01a3,0x69c61c88,0x201233b4}}, // kvim_, dtry, gike, _buyi_,
+ {{0x3f9e84b9,0xfbcf82e3,0x644d0ba3,0xdce402d0}}, // _kotu_, شتی_, xuai, vdiğ,
+ {{0x8c1b893f,0x394033b5,0x229480f7,0x7c24007a}}, // _צווי, _edis_, _للتس, dpir,
+ {{0x61e2b3b6,0x69c633b7,0x61eb951e,0xfe700065}}, // kool, bike, ylgl, یدہ_,
+ {{0x644d33b8,0x3f9eb3b9,0xf0a680ff,0xdced01a1}}, // tuai, _lotu_, _đành_, ndać,
+ {{0x3cfc2e2b,0xdce407d9,0x798d33ba,0x32111142}}, // [20b0] लेले_, rdiğ, _inaw, _suzy_,
+ {{0x25a933bb,0x657b9408,0x644d032f,0x386933bc}}, // skal_, zduh, ruai, _ipar_,
+ {{0x644d25e0,0x325511d2,0xc3338039,0xdcef0162}}, // suai, твер, רוע_, _bacă,
+ {{0x7aea83ed,0x27e333bd,0x247202d6,0x7bc533be}}, // _myft, lojn_, _fņm_, sihu,
+ {{0x7aea8408,0x777aabfa,0x6579b3bf,0xf3908129}}, // _lyft, _batx, _rawh, _rảnh_,
+ {{0xf7718250,0x69c633c0,0x10a282ee,0xdb1a85be}}, // شات_, zike, ришн, litæ,
+ {{0x69c633c1,0x0675b3c2,0x61e2b3c3,0xd91581e2}}, // yike, туля, bool, _адбы,
+ {{0xd6ce8013,0xfc3f826f,0x61e2b3c4,0x68fb0084}}, // تقى_, ším_, cool, žudy,
+ {{0x69c633c5,0x2a6780ee,0x777ab3c6,0x657bb3c7}}, // vike, _spnb_, _fatx, rduh,
+ {{0x09ca053e,0x645b8b3c,0x798d33c8,0x5ed700ab}}, // ाच्य, rtui, _anaw, _ধরনে,
+ {{0x69c6288e,0x6579b3c9,0x645bb3ca,0x2fc7b3cb}}, // tike, _tawh, stui, ming_,
+ {{0xaca3819d,0x62838267,0xf8bf002a,0x765c33cc}}, // _amụk, _vrno, rbén_, vtry,
+ {{0x7529b3cd,0x98a48084,0x7c24010c,0x69c633ce}}, // mbez, _temą_, xpir, rike,
+ {{0x69c633cf,0x7c3b80dd,0x6449b3d0,0x644680fe}}, // sike, msur, mrei, škic,
+ {{0x66e62344,0x2c1980c8,0xdee6210d,0xd007023a}}, // това, _দিয়ে_, тови, тере_,
+ {{0x2fc7b3d1,0x7d1b8289,0x661c8019,0xceb284de}}, // hing_, ncus, _érke, ביל_,
+ {{0x2fc7860c,0xff26087e,0xe8fa0698,0x973c8a20}}, // [20c0] king_, _импо, _юли_, _hoće,
+ {{0x2fc7b3d2,0x6449b3d3,0x7c3bb3d4,0xd00f004e}}, // jing_, irei, isur, _گلی_,
+ {{0x2fc7b3d5,0x24800025,0x6449b3d6,0x61e2b3d7}}, // ding_, svim_, hrei, tool,
+ {{0x6449b3d8,0x3ea7831d,0x442f80b9,0x6cc6053b}}, // krei, dynt_, _owg_, _айма,
+ {{0xb82280c8,0x61e290b5,0x442f8282,0x2056b3d9}}, // _নিহত_, rool, _nwg_, ттер,
+ {{0x6449b3da,0xb17b016d,0x2fc7b3db,0x61e2b3dc}}, // drei, llåt, ging_, sool,
+ {{0x973cb3dd,0x163780f7,0x61e2b3de,0x660bb3df}}, // _noće, نسية_, pool, _bigk,
+ {{0x6449b3e0,0xd01a00c8,0x557481f3,0x442f80b9}}, // frei, তীয়_, угит, _bwg_,
+ {{0x2fc797ef,0x6449a2a5,0xe817000f,0x7aea816d}}, // bing_, grei, _धंधा_, _syft,
+ {{0x442f8358,0x2fc7b3e1,0x69c42e3a,0x6e3e01a1}}, // _dwg_, cing_, _amie, _ovpb,
+ {{0x38691610,0x19590a14,0xf1f60035,0x23a40338}}, // _spar_, наны_, ीगढ़_, _höjs_,
+ {{0x6449b3e2,0xf1a71980,0x973c8267,0x200c804f}}, // brei, трон, _doće, _hidi_,
+ {{0xe1ff05a4,0xc05810ac,0x6e22192c,0xa3e203db}}, // rmó_, кір_, _čoba, दवा_,
+ {{0x9f5307e2,0x2618801c,0x38698168,0x7c22b3e3}}, // _això_, _kéo_, çari_, _itor,
+ {{0xf8bf0003,0x4ea433e4,0xc6a41317,0x200cb3e5}}, // mbém_, арта, арти, _midi_,
+ {{0x7c22b3e6,0x798d33e7,0x200c81d0,0x2fc78e9e}}, // _ktor, _unaw, _lidi_, zing_,
+ {{0x2fc78051,0x6abd01a2,0x69c380e7,0x9f430168}}, // [20d0] ying_, ्प्र, ènem, nojë_,
+ {{0x69c4219c,0x68ed02c1,0x8aa7023a,0x02a700e8}}, // _zmie, _iyad, кред, крем,
+ {{0x9f5e8364,0x2fc7b3e8,0x26188866,0x75d50c3b}}, // _mitä_, ving_, _néo_, _ميدا,
+ {{0xc6920159,0x7bc88867,0x7c22b3e9,0xdd9513cd}}, // _האט_, hidu, _otor, _самы,
+ {{0x2fc7b3ea,0x200c9a73,0x7bc8b3eb,0x248681c0}}, // ting_, _bidi_, kidu, _nrom_,
+ {{0x657d33ec,0xfc3f803e,0x261880ff,0x442f822c}}, // _hash, šík_, _béo_, _rwg_,
+ {{0x657d33ed,0x7c22b3ee,0xcf94098a,0x7bc8a060}}, // _kash, _ator, יטס_, didu,
+ {{0x657d020f,0x7c3bb3ef,0x442f822c,0x24869c11}}, // _jash, tsur, _pwg_, _brom_,
+ {{0x2fc7b3f0,0x657d33f1,0x61e9b3f2,0x75299083}}, // ping_, _mash, _ikel, rbez,
+ {{0x6449b3f3,0x657d33f4,0x7c3bb3f5,0x69c433f6}}, // rrei, _lash, rsur, _smie,
+ {{0x7c22811e,0x6449aa1d,0xdb1ab3f7,0x68ed01b4}}, // _etor, srei, entó, _ayad,
+ {{0x2486804c,0x442f81e9,0x64498b2b,0x64468110}}, // _from_, _twg_, prei, škia,
+ {{0x3fc80077,0x7bc8b3f8,0x8c458b5b,0x2d8fad00}}, // ندسی_, bidu, _селе, _inge_,
+ {{0xf53980e1,0x657d01b4,0x7bc38234,0x25adb3f9}}, // lať_, _aash, _umnu, lkel_,
+ {{0x657d33fa,0x44220ac6,0x69c401b9,0x61e9b3fb}}, // _bash, _stk_, _tmie, _okel,
+ {{0x25adb3fc,0x657d33fd,0x69c41988,0xf53980e1}}, // nkel_, _cash, _umie, nať_,
+ {{0x657d33fe,0xb17b0106,0x68ed09ab,0x81cc8264}}, // [20e0] _dash, rlåt, _gyad, রকম_,
+ {{0x5503176e,0xb3468073,0x9f5e0009,0x61e9b3ff}}, // спуб, moçõ, ötä_, _akel,
+ {{0x657d3400,0x25adb401,0xbea60912,0x3ea627a9}}, // _fash, kkel_, _санк, _синг,
+ {{0x61fbb402,0x657d3403,0x3ea508aa,0x2247016b}}, // _chul, _gash, älte_, ánka_,
+ {{0xe8948554,0xf539b404,0x44220867,0x261a000f}}, // раль, dať_, _utk_, _मंडी_,
+ {{0xafdb0022,0x2d8fb405,0x61e98135,0x9f430168}}, // rsøg, _ange_, _ekel, vojë_,
+ {{0x200c803a,0x657d3406,0x67388db7,0x61e6026c}}, // _vidi_, _yash, ravj, kokl,
+ {{0x9f5e8364,0x6d563407,0x7bc8809a,0xd49ab408}}, // _sitä_, ngya, widu, еро_,
+ {{0xa84a88ca,0x61e600eb,0x161602f1,0x2d803409}}, // _سلام_, dokl, _दूसर_, idie_,
+ {{0x9f4300f1,0x2912340a,0x99860084,0x2d8fb40b}}, // rojë_, _agya_, kslų_, _enge_,
+ {{0x7bc8b40c,0xdb1a8a48,0x68ed0359,0x9f430168}}, // ridu, vitä, _syad, sojë_,
+ {{0x2eb08bb8,0x2486b40d,0x2d800493,0x25adb40e}}, // जनीत, _trom_, jdie_, ckel_,
+ {{0x657d00a4,0x7bc8b40f,0x21393410,0x73c4a181}}, // _rash, pidu, rash_, _پيغم,
+ {{0x657d00a4,0x2a660035,0x14d781c6,0x29120c2e}}, // _sash, łoby_, _גודל_, _egya_,
+ {{0x657d3411,0x39448144,0xdb1ab412,0x8b6580d7}}, // _pash, _odms_, ritä, _خانم,
+ {{0x6d562acf,0x61e63413,0x657d3414,0xca4781a8}}, // ggya, cokl, _qash, _إليه_,
+ {{0x657d3415,0xdb1ab416,0xf53f3417,0xdce284e8}}, // [20f0] _vash, pitä, _slås_, _oboč,
+ {{0x91fd0029,0x61fb822e,0xf53981ac,0x657d3418}}, // stād, _shul, zať_, _wash,
+ {{0x657d2b2a,0x3a7501e5,0x20d40bca,0x92590009}}, // _tash, рлер, _نتیج, тает_,
+ {{0x657d3419,0x6e250362,0x57dd8072,0x3e410048}}, // _uash, _ithb, यव्ह, mėt_,
+ {{0xf539b404,0x752d341a,0xd5b1827d,0xa3d48b99}}, // vať_, mbaz, _lúc_, सचा_,
+ {{0x44290d54,0x2259809a,0x61fb82f7,0x3f69b41b}}, // mpa_, ński_, _whul, тико_,
+ {{0xf53981ac,0x25ad8074,0xfd1180f7,0xf77403de}}, // tať_, tkel_, يجة_, מקס_,
+ {{0x1c4593b4,0x644d0090,0x4429341c,0xada58198}}, // аним, orai, opa_, раил,
+ {{0x4429028a,0x69cbb41d,0x644d061f,0xf53981ac}}, // npa_, mige, nrai, rať_,
+ {{0x69cb84e1,0xf53981ac,0x5ba9968a,0x4429341e}}, // lige, sať_, тком_, ipa_,
+ {{0x644d0a1c,0x27e7b41f,0xd6d18a47,0x25b08061}}, // hrai, monn_, _بقا_, álló_,
+ {{0x2d8f8bc5,0x69cbb420,0x44290133,0x660f3421}}, // _unge_, nige, kpa_, _nick,
+ {{0x6e2500f7,0x80c001d0,0xac278162,0x00000000}}, // _athb, _लेखे, _сфек, --,
+ {{0x644d1dad,0x7c2999ad,0xdcfb9487,0x443fb422}}, // drai, mper, nduč, msu_,
+ {{0xa91d8a56,0xe7370a94,0x09b5816f,0x63a38046}}, // _každ, рес_, ंसाठ, _ionn,
+ {{0x63a3b423,0x644d3424,0xf53f0082,0x248d3425}}, // _honn, frai, _slår_, _šema_,
+ {{0x63a3b426,0x443f8a73,0x2d8004df,0x644d3427}}, // [2100] _konn, nsu_, rdie_, grai,
+ {{0x443f9572,0xdcfb8d2f,0xdb0183ba,0x63a3a1d8}}, // isu_, jduč, rklæ, _jonn,
+ {{0x69cbb428,0x673c16c4,0x973c8289,0x3dd7077f}}, // fige, marj, _noća, úewé_,
+ {{0x7c29b429,0x673c2cc7,0x63a381a8,0x88868db6}}, // kper, larj, _lonn, илеж,
+ {{0x6604043d,0x644d0cb5,0x261c002a,0x91fd00eb}}, // mmik, crai, _hío_, ltāc,
+ {{0x673c342a,0x7c29b42b,0x63a3b42c,0x6604342d}}, // narj, dper, _nonn, lmik,
+ {{0x69cbb42e,0x29568098,0x2eb080c2,0xafdb0aa2}}, // bige, _възр, जनेत, prøv,
+ {{0xd378803a,0x443fb42f,0x91e39a19,0x261c06a5}}, // moći_, fsu_, _досе, _mío_,
+ {{0x26168063,0x673c3430,0x63a3b431,0x261c0661}}, // _पूरी_, karj, _bonn, _lío_,
+ {{0x673c3432,0xeb06a10d,0x63a38213,0x798082a0}}, // jarj, ично, _conn, _hamw,
+ {{0x6d460a56,0x7980b433,0x673c3434,0x63a3b435}}, // _odka, _kamw, darj, _donn,
+ {{0x7c26003b,0x44293436,0x63a38b99,0x7982b437}}, // _otkr, ypa_, _eonn, ndow,
+ {{0x660f3438,0x63a3b439,0x84638081,0x98b20176}}, // _rick, _fonn, _дъще, _feyč_,
+ {{0x4426b43a,0x63a3b43b,0x644d03d3,0x6d460079}}, // _ito_, _gonn, vrai, _adka,
+ {{0x7c260612,0xc3fb8039,0x644083ba,0x7a138162}}, // _atkr, _שלוש, lsmi, _bătu,
+ {{0xa91d82a5,0x4426b43c,0x63a3b43d,0x644d343e}}, // _kaže, _kto_, _zonn, trai,
+ {{0x63a382ec,0x61ed17ea,0x69cbb43f,0x644d0fef}}, // [2110] _yonn, _ikal, vige, urai,
+ {{0x644d3440,0x44293441,0x4426b442,0x69d880e7}}, // rrai, rpa_, _mto_, éven,
+ {{0x44293443,0x69cbb444,0x79809b19,0x386d8087}}, // spa_, tige, _bamw, _sper_,
+ {{0x4426b445,0x443fb446,0xf5950f99,0x6440b447}}, // _oto_, ysu_, _الحج, ksmi,
+ {{0x4426b448,0x702280c8,0xfd960039,0x61ed004f}}, // _nto_, _নিউজ_, _הדרך_, _mkal,
+ {{0x9f4780f1,0x6440b449,0x628ab44a,0xdb1e02f1}}, // monë_, dsmi, _orfo, gipä,
+ {{0x4426b44b,0x3f811083,0x6442b44c,0x28a7864a}}, // _ato_, _mahu_, _avoi, _कॅरि,
+ {{0x81bd0341,0x63a3b44d,0x7c2983ab,0x973c83b1}}, // spēj, _sonn, tper, _voća,
+ {{0x69c9b44e,0xb4f5313a,0x27e78247,0x63a3b44f}}, // _imee, _आरोप_, ponn_, _ponn,
+ {{0x443fb450,0x7c29b451,0xd7fb8364,0x61ed3452}}, // rsu_, rper, _руб_, _akal,
+ {{0x4426b453,0x443fb454,0x673c3455,0x63a389ff}}, // _eto_, ssu_, varj, _vonn,
+ {{0x261c02ba,0x443fb456,0x63a38c2e,0x3e619de9}}, // _río_, psu_, _wonn, mót_,
+ {{0x673c3457,0x628a8352,0x973c805c,0x3f810458}}, // tarj, _erfo, _moćn, _bahu_,
+ {{0x261c157a,0x63bab458,0x23268dcd,0xab26964f}}, // _pío_, chtn, _лоши_, _лоша_,
+ {{0x660426a6,0xdfcf00f7,0x673c054b,0x65b5816b}}, // tmik, ريف_, rarj, _záha,
+ {{0x973c803b,0x673c3459,0xd01f80ab,0xdb019b48}}, // _noćn, sarj, নীয়_, rklä,
+ {{0x6da3345a,0x6604345b,0x3f810115,0x44268144}}, // [2120] чита, rmik, _fahu_, _xto_,
+ {{0xa3ca8aec,0x261c2848,0x69c9b45c,0x2a6e8144}}, // रोप_, _tío_, _amee, _ypfb_,
+ {{0xdb1a84c3,0x9f4c801c,0x8e8600f7,0x798082d6}}, // titú, _đoàn_, _الجه, _pamw,
+ {{0xe93a9ddd,0x8d560d0e,0xe9da8a26,0x6d460366}}, // اسات_, _уточ, ыке_, _udka,
+ {{0xa3e79130,0x7982b45d,0x6000345e,0x515b01c6}}, // पवा_, rdow, römf, רכנו,
+ {{0xc059835f,0xd9a5016f,0xdefa8009,0xbcfb0032}}, // рії_, _ऑक्ट, лый_, _abéw,
+ {{0x44268025,0x2d8200a4,0xa91db45f,0x65b59727}}, // _sto_, _kake_, _saže, _sáha,
+ {{0x6440a09b,0xa91d8110,0x25a03460,0xa3ca81a2}}, // tsmi, _paže, njil_, रोन_,
+ {{0x2d820051,0x77b0016d,0x5bb895a6,0x4426841c}}, // _make_, _växj, илия_, _qto_,
+ {{0xa91d805c,0x61ed0687,0xd37a8ae7,0xf8bf00e7}}, // _važe, _skal, учи_, ncée_,
+ {{0x77ab0085,0x9f8d01d0,0x25a0008e,0xc7b381c6}}, // _müxt, uží_, kjil_, _צבע_,
+ {{0xdb1c0125,0x2d820041,0xdb03b33e,0x64563461}}, // _umræ, _nake_, _zonë, kuyi,
+ {{0x4426b462,0xafdb0aa2,0x673a8009,0x5c068a4c}}, // _uto_, drør, _ketj, _ляка,
+ {{0x7bcf80e7,0x62348e8e,0x2d82026b,0x43948254}}, // écut, _деку, _aake_, _факс,
+ {{0x25a6b463,0x673a81a1,0x61ed3464,0x7bca9b8f}}, // _kool_, _metj, _tkal, _imfu,
+ {{0x673a82ce,0xa2b101bc,0x7bdc022b,0x61ed3465}}, // _letj, _dọ́l, _ajru, _ukal,
+ {{0x3f811a67,0x21f9003e,0x64a40003,0x2d8203c3}}, // [2130] _tahu_, lého_, паѓа, _dake_,
+ {{0x261a009a,0x673a8fb0,0x69c98cfa,0x661a8326}}, // _मूवी_, _netj, _smee, _outk,
+ {{0x21f90775,0xdb03809f,0x02d90eca,0x1df90190}}, // ného_, _conè, جواب_, _вены_,
+ {{0xd378803b,0x25a68079,0x9f4c801c,0x64560c6a}}, // moću_, _nool_, _đoán_, buyi,
+ {{0x3dcdb466,0x973ca828,0x673a8aa2,0x21f9016b}}, // view_, _voćn, _betj, hého_,
+ {{0x21f90a56,0x7c2d2a70,0x2d82022e,0xb4b5047d}}, // kého_, mpar, _zake_, जनी_,
+ {{0x2d823467,0x201302a6,0x69cf3468,0x9989009a}}, // _yake_, _mixi_, kice, _miał_,
+ {{0x0c258790,0x69c9b469,0x94258049,0x25a681c6}}, // омин, _umee, омие, _cool_,
+ {{0x6f001931,0x7c2d346a,0x9f47b46b,0x57d58073}}, // _kábí, npar, yonè_, _доаѓ,
+ {{0x4a4599a4,0x3949346c,0x61ebb46d,0xd378842b}}, // онов, _adas_, logl, koću_,
+ {{0x395f88ae,0xe9da2748,0x63a7346e,0x69cf1ed3}}, // _kcus_, ико_, _mojn, fice,
+ {{0x61ebb46f,0x69cf2e4d,0x39490144,0x7a138162}}, // nogl, gice, _cdas_, _bătr,
+ {{0x09a78076,0x7a13802e,0x201301b4,0x64a58152}}, // _खवैय, _cătr, _bixi_, _фала,
+ {{0x2d823470,0x44079ccf,0x63a701c0,0xe7371c8b}}, // _sake_, _учеб, _nojn, жер_,
+ {{0x2d823471,0x442db472,0x7643b473,0x320682c4}}, // _pake_, lpe_, nsny, ymoy_,
+ {{0x28c50df4,0x6d5bb474,0x6000016d,0x6569b475}}, // _लेकि, ngua, döme, nfeh,
+ {{0x6b83b476,0x90c30364,0xfaf80029,0x442d8247}}, // [2140] _kang, _объе, _šī_, npe_,
+ {{0x6b83828a,0xdb1c0082,0x442db477,0x7a0c8201}}, // _jang, _områ, ipe_, _işti,
+ {{0x6b83b478,0x6da6069b,0x79840041,0x2d820051}}, // _mang, _дина, _kaiw, _take_,
+ {{0xa96a81a1,0x442db479,0xdb03b47a,0x61ebb47b}}, // рида_, kpe_, _coné, gogl,
+ {{0x63a70042,0x7984020d,0x00da9a10,0xa3ca847d}}, // _fojn, _maiw, جبات_, रोड_,
+ {{0x6b83b47c,0x69cf347d,0x661a8198,0x25a681c7}}, // _nang, zice, _putk, _sool_,
+ {{0xd5ba95fe,0x179b0051,0xdb1a80f1,0x8f9b010f}}, // иси_, ייסב, shtë, ייסי,
+ {{0x6b8381d8,0x21f90ed7,0x7984347e,0x61eb8081}}, // _aang, vého_, _naiw, cogl,
+ {{0x64441e89,0x764380dd,0x442d82d5,0x69cf347f}}, // nsii, asny, gpe_, vice,
+ {{0x6b838886,0x70538077,0x661ab480,0x64443481}}, // _cang, _آنلا, _tutk, isii,
+ {{0x25a6b482,0x7414803d,0x442db483,0x798404b9}}, // _tool_, _کوتا, ape_, _baiw,
+ {{0x21f9003e,0x64441bda,0xd46a9c82,0x6b83b484}}, // rého_, ksii, _виде_, _eang,
+ {{0x6d40b485,0x3866b486,0xdd8f8c2a,0x6b83b487}}, // mama, ntor_, _جون_, _fang,
+ {{0x6d40b488,0x69cf3489,0xa91d8110,0xb4c892e0}}, // lama, sice, _maža, ोपी_,
+ {{0xa91d8390,0x69cf348a,0x2013348b,0xb5a70956}}, // _laža, pice, _vixi_, орай,
+ {{0x6d40b48c,0x6b83b48d,0x9f4780d7,0x63a701c0}}, // nama, _zang, roné_, _sojn,
+ {{0x6b83b48e,0xa91d803a,0xb4b50361,0x63a70069}}, // [2150] _yang, _naža, जने_, _pojn,
+ {{0x7c2d348f,0x6d40b490,0x6b83b491,0xc2e900ab}}, // rpar, hama, _xang, _কৃষি_,
+ {{0x6d40b492,0x7a400013,0x250909a7,0x65aa05b9}}, // kama, _rátá, _گردی_, _výhr,
+ {{0xa3ca9a46,0xf67a0158,0x19598e11,0x1a9b80be}}, // रोत_, _פארמ, сады_, ייבע,
+ {{0x6d40b493,0x859a0039,0x61ebb494,0xf1b40039}}, // dama, _השרו, rogl, וסף_,
+ {{0xa91d96b5,0x3e4581a9,0xec3601c6,0xf8bf002a}}, // _daža, tēt_, _מאשר_, lbés_,
+ {{0x44393495,0x31359c79,0x628e025b,0x69cd14c7}}, // _hws_, _невр, _grbo, _omae,
+ {{0x6d40b496,0x753d0063,0x31790063,0x6602b497}}, // gama, _jesz, jesz_, _chok,
+ {{0x6b839a92,0x442db498,0x3866b499,0x6d5bb150}}, // _pang, upe_, ctor_, rgua,
+ {{0x69dd03d3,0x753d100b,0x6d5bb49a,0x75d38087}}, // éren, _lesz, sgua, văzu,
+ {{0x656f1385,0x656982af,0x4439349b,0xddc880eb}}, // _obch, pfeh, _lws_, žoša,
+ {{0x6d408faa,0x442d8dfc,0x6b83b49c,0x7bda8009}}, // cama, ppe_, _wang, antu,
+ {{0x6b83859e,0x4439146a,0x3f85b49d,0x442b349e}}, // _tang, _nws_, _kalu_, _ntc_,
+ {{0x6609b49f,0x61a88670,0x3f8591ee,0x79840300}}, // lmek, _कक्ष, _jalu_, _waiw,
+ {{0x753d0065,0x798434a0,0x443934a1,0xcf9280be}}, // _besz, _taiw, _aws_, עטל_,
+ {{0x3f85848f,0x3179009a,0xf1bf026f,0x741600f7}}, // _lalu_, cesz_, chá_, كورا,
+ {{0x39420006,0x75241e41,0xa5070c5c,0x25bf80ee}}, // [2160] maks_, rciz, зета_, ihul_,
+ {{0x6d40b4a2,0x64440079,0xb90334a3,0x442034a4}}, // zama, rsii, _язык, rqi_,
+ {{0x442b34a5,0x753d0019,0xa91d80d2,0xdb1ab4a6}}, // _etc_, _fesz, _saža, uhté,
+ {{0x3866b4a7,0x628e042b,0x25bfaf9f,0x5886a1f6}}, // ttor_, _vrbo, jhul_, зыка,
+ {{0x6d40b4a8,0xd90d826a,0x3f85b4a9,0x442034aa}}, // vama, لیف_, _balu_, qqi_,
+ {{0x6d4bb4ab,0x3866b4ac,0x6602b4ad,0xa91d805c}}, // _adga, rtor_, _shok, _važa,
+ {{0x6d40b066,0x7bce34ae,0x7c2b80eb,0x59dc020e}}, // tama, _imbu, _atgr, मचार,
+ {{0x80cd81ab,0xbcfb34af,0x9adb0039,0x661e026c}}, // _देवे, _ibér, _החלט, _mupk,
+ {{0x6d40b4b0,0xa91d8024,0x661e00d2,0x6d4b01a8}}, // rama, _kažn, _lupk, ógai,
+ {{0x6d40b4b1,0xad9b001c,0x644fb4b2,0x3f859ffe}}, // sama, _nhún, ácia, _galu_,
+ {{0x21fc801b,0x7bce0359,0x21790190,0xdcfd01a9}}, // ního_, _mmbu, ойны_, _masā,
+ {{0xa91d8503,0x25bfb4b3,0x6d40b4b4,0x80ab00ab}}, // _lažn, chul_, qama, _চেষ্,
+ {{0x2ac282be,0x4420802e,0x26099094,0x3a20a329}}, // _bébé_, _îi_, ागरी_, _čipu_,
+ {{0xad9b0104,0xefc815da,0x03a3a49a,0xf09f07f1}}, // _chún, пуск_, _пичо, rxà_,
+ {{0x248200ee,0x394234b5,0xdb07010c,0x21fc928a}}, // _askm_, baks_, _gojè, jího_,
+ {{0x40ee082e,0x22f78039,0x44391670,0x9df90a4c}}, // ọrọ_, _מזון_, _pws_, інат_,
+ {{0x753d0065,0xf8bf0b2c,0xa91d8110,0xbcfb026b}}, // [2170] _vesz, rbés_, _bažn, _abér,
+ {{0x6ad080ab,0x442b00ff,0x051d0264,0x44390bfd}}, // _সুযো, _vtc_, তরের_, _vws_,
+ {{0x753d0065,0xa91d81e2,0x22470061,0x69dd29d8}}, // _tesz, _dažn, énk_, onse,
+ {{0x7bce34b6,0x6aaa00f2,0x3f85b4b7,0x69dd34b8}}, // _embu, äffa, _salu_, nnse,
+ {{0xc5fb0670,0x3f858573,0x2d9934b9,0x656d3323}}, // ्षीय_, _palu_, _inse_, mfah,
+ {{0x1408016f,0xab8434ba,0xbcfb00f7,0xdfd181a8}}, // वगृह_, _пуск, _gcéa, ضيع_,
+ {{0x21fc801b,0x6609b4bb,0x63a3007b,0x3f8592a5}}, // cího_, tmek, önnu, _valu_,
+ {{0x2d8934bc,0x75fe8168,0x629a2bea,0x69dd01ed}}, // ndae_, tëza, _štog, jnse,
+ {{0x91fd0029,0x394d80d2,0x6609b4bd,0xfc058652}}, // rtāl, _ides_, rmek, мпио,
+ {{0x63aa8114,0x7655009a,0xe3b181a8,0xdb1ab4be}}, // _cofn, krzy, ئرة_, ditó,
+ {{0x91e634bf,0xf1c4801b,0x3942103d,0x29000019}}, // доне, लोकन, taks_, _szia_,
+ {{0x6d4b813c,0x44b5abfc,0xbcb58ada,0x7f4381b4}}, // _udga, _обес, _обещ, lanq,
+ {{0xe87c0459,0xa91d80d2,0xa3ca83dd,0x21fc81d0}}, // _düşü, _lažo, रोह_, zího_,
+ {{0x2d9934c0,0x39420006,0x3fcc00ab,0x63aa8428}}, // _anse_, saks_, লক্ষ, _gofn,
+ {{0x394d82a5,0xa91d8da8,0xdb03826f,0x248d83cd}}, // _odes_, _ražn, _koní, lvem_,
+ {{0xbcfb00f7,0x394d89c4,0x68f6062c,0x70ef0133}}, // _scéa, _ndes_, _tyyd, ụrụi,
+ {{0xd4670f13,0xa91d8025,0xdcfd0029,0x64468289}}, // [2180] мите_, _pažn, _pasā, škiv,
+ {{0x973cb4c1,0x6b8701a1,0x2d9934c2,0x394d8580}}, // _voćk, _cajg, _ense_, _ades_,
+ {{0xa91d803a,0x38cb0077,0xa3af0665,0x213fa14a}}, // _važn, _هایی_, _ओकर_, _beuh_,
+ {{0x2005b4c3,0xbbaa946d,0xe8fab4c4,0x765c00b4}}, // _ahli_, _चक्क, оле_, mury,
+ {{0x39402763,0x7bc1859c,0x6b8734c5,0x394d9670}}, // _meis_, khlu, _fajg, _ddes_,
+ {{0x39401e59,0x6d4434c6,0x394d8009,0xc5f8b4c7}}, // _leis_, laia, _edes_, ुष्य_,
+ {{0xd90d8065,0x5a550098,0x9e06a1ae,0xdb038980}}, // لیہ_, _пъту, мчил, _boní,
+ {{0x77b4809f,0xdb038473,0x6d4433bc,0x973c8bcf}}, // _màxi, _coní, naia, _moći,
+ {{0x628382c4,0xa06ab4bf,0xf1bd8035,0xdb03846d}}, // _asno, _фаза_, ्फरन, _doní,
+ {{0x765c023b,0x0a6a9a02,0x7bc1b4c8,0x6d44011b}}, // kury, орми_, ghlu, haia,
+ {{0x6d440867,0x973c80d2,0x6fb380d7,0x452a8162}}, // kaia, _noći, _جملا, _джен_,
+ {{0x85068872,0x225185b9,0x24800748,0x69c2b4c9}}, // _قوان, ázka_, kwim_, nhoe,
+ {{0x69dd34ca,0x394000f7,0xc8642503,0x6d4434cb}}, // rnse, _deis_, ктри, daia,
+ {{0x7655009a,0x7bc1b4cc,0x6004823e,0xdb1ab4cd}}, // trzy, chlu, lòmb, ritó,
+ {{0xc1ee80d4,0x05b280d4,0x6d4403cd,0x3940212b}}, // जवाब_, _जवाब, faia, _feis_,
+ {{0xa91d8029,0x6d4434ce,0x973c81a1,0x69d60061}}, // _ražo, gaia, _doći, ézet,
+ {{0x656d34cf,0xe9df00e1,0x261683db,0xa4c0099b}}, // [2190] rfah, mnú_, _पूछी_, rüşü_,
+ {{0x2d89067f,0x765c00b4,0xe9df00e1,0x09d78072}}, // sdae_, bury, lnú_, ढच्य,
+ {{0x77b0016d,0x33f40106,0x6d4434d0,0x248d81d0}}, // _växt, växt_, baia, zvem_,
+ {{0xd5a68591,0xe9df34d1,0x9f47823e,0x397c03de}}, // _صف_, nnú_, roní_, ַטונ,
+ {{0xe7eb8076,0xe3b88214,0x394d8168,0x660d21d8}}, // टवला_, kkı_, _vdes_, mmak,
+ {{0x660d34d2,0xe9df00f7,0x01c98019,0x65bcb4d3}}, // lmak, hnú_, _کورٹ_, _réha,
+ {{0xfe7189d7,0x7f43818a,0x7bc18282,0xd34600d7}}, // ادت_, ranq, vhlu, _سینه_,
+ {{0x660d34d4,0x2247027f,0x394db4d5,0x91fd00eb}}, // nmak, ánku_, _udes_, stāj,
+ {{0xe9df01ac,0xa924826f,0x6000016d,0x799b8300}}, // dnú_, _úžas, dömn, _inuw,
+ {{0xf77095e4,0x39401313,0xb605816b,0xa3caa0e9}}, // _نام_, _seis_, dnáš, रोल_,
+ {{0xeb920158,0xa3b70665,0xe299b4d6,0xd5bc046d}}, // אָר_, _जवन_, чан_, _bọ́_,
+ {{0x3ce08025,0xdb070125,0x29d200d2,0x1df9064a}}, // _žive_, _hljó, ršao_, ंतोष_,
+ {{0xbcfb1db6,0x777c002a,0x6e2180b9,0x6d4402df}}, // _scén, terx, _hulb, vaia,
+ {{0x6d44010b,0x7529b4d7,0x29d2026c,0xb4cb83b7}}, // waia, lcez, pšao_, रछी_,
+ {{0x670f800c,0xa91d8024,0x6449b4d8,0x394034d9}}, // ाधिक_, _pažl, lsei, _teis_,
+ {{0x660d34da,0x973c80c3,0x6e21a7d1,0x386902a6}}, // gmak, _voći, _mulb, _fqar_,
+ {{0x6449b4db,0x6d4434dc,0x6b5600f7,0x765c34dd}}, // [21a0] nsei, raia, فضائ, sury,
+ {{0x7c2b2509,0x6d441c91,0x26cc0548,0x64498a2a}}, // ígra, saia, údos_, isei,
+ {{0xc0a90077,0x6d44208b,0x69c2b4de,0x443db41f}}, // _فایل_, paia, thoe, _lww_,
+ {{0x64a61652,0x5066a17e,0xdca6067c,0xe046867c}}, // _зама, етка, _зами, енди,
+ {{0x69c286a8,0x248000ee,0x2ac60032,0x201a2aa0}}, // rhoe, qwim_, _bíbé_, _kipi_,
+ {{0x61e410d3,0x6449b4df,0xc7a382d3,0x69c29de6}}, // _ljil, dsei, личк, shoe,
+ {{0x539b0051,0x442234e0,0xa3dd02f1,0x644b868f}}, // _צילו, _huk_, तचर_, _avgi,
+ {{0x38cb80d5,0x9f4a03fb,0x75fe80f1,0xa91d807a}}, // _کافی_, dobí_, rëzo, _lažj,
+ {{0x69c0b4e1,0x44220110,0x91fd00eb,0x63ae012b}}, // _ilme, _juk_, stāk, _kobn,
+ {{0xa3ca8d38,0x61e4022e,0x442f89ca,0x7ac6a155}}, // रों_, _ajil, _dtg_, есме,
+ {{0x19590a14,0xe3b883bf,0x2ec69c12,0xe9df00e1}}, // маны_, rkı_, _ثقاف, tnú_,
+ {{0x644982af,0x394695d7,0xcc88801c,0x3ce6822c}}, // bsei, naos_, _tớ_, _txov_,
+ {{0x44220b18,0x61e434e2,0xe9df00e1,0x201a00c3}}, // _nuk_, _djil, rnú_, _bipi_,
+ {{0xc5fb05e8,0x6d4f0683,0x6446807a,0x8c1b81c6}}, // ्ष्य_, _sdca, škis, _זוגי,
+ {{0x69c0b4e3,0x442234e4,0x660d34e5,0xd5bb2318}}, // _olme, _auk_, tmak, пса_,
+ {{0x7c228009,0x61e40168,0x3946b4e6,0x6b8ab4e7}}, // _kuor, _gjil, jaos_, _hafg,
+ {{0x660d34e8,0x442219d5,0x6ad900c2,0x3946b4e9}}, // [21b0] rmak, _cuk_, भप्र, daos_,
+ {{0x69c0b4ea,0x442203c3,0x7c228081,0xdb150118}}, // _alme, _duk_, _muor, _alzá,
+ {{0xdb03816b,0xdb188216,0x660d34eb,0x29048c53}}, // _koná, _olví, pmak, _azma_,
+ {{0xa2d5053e,0x3e4c800d,0x28d2908a,0x2486826c}}, // _येण्, dět_, _देहि, _osom_,
+ {{0x7c2293cd,0xf8b334ec,0xdb03802a,0x7f4180f1}}, // _nuor, ुनिय, _moná, _pelq,
+ {{0x2d8b0a49,0x9a8734ed,0x69c08a09,0xa91d8110}}, // _hace_, _публ, _elme, _kažk,
+ {{0x973c817f,0x44220102,0x2d8b34ee,0x60000338}}, // _noću, _zuk_, _kace_, döml,
+ {{0x25a934ef,0x2d8b02a5,0x442f9083,0x6449b4f0}}, // njal_, _jace_, _ptg_, tsei,
+ {{0x7c228098,0x1c428084,0x2d8b04b9,0x6d42802e}}, // _cuor, аным, _mace_, _deoa,
+ {{0x61f634f1,0x6449b4f2,0x8b958878,0x6295026c}}, // _skyl, rsei, трич, _krzo,
+ {{0x13098009,0xa2e581e5,0x320902c4,0x920d0035}}, // ьной_, тонд, _bhay_, सगंज_,
+ {{0x7c228098,0x442fb4f3,0x62950b80,0x201a076d}}, // _fuor, _ttg_, _mrzo, _sipi_,
+ {{0x9f4a026f,0x6446929b,0xef1880eb,0x2d9d8870}}, // sobí_, škir, _daļu_, _inwe_,
+ {{0x4422067f,0x2b4302d4,0x61fd0299,0x00000000}}, // _ruk_, _nejc_, alsl, --,
+ {{0xf74314d6,0x442234f4,0x201a34f5,0x2d8b0041}}, // ресо, _suk_, _vipi_, _bace_,
+ {{0xc692810f,0x2d8b026c,0x63ae34f6,0xdb070216}}, // סאן_, _cace_, _sobn, _cojí,
+ {{0x201a34f7,0x2d8b00a4,0xab661505,0x62950333}}, // [21c0] _tipi_, _dace_, квал, _arzo,
+ {{0x291f9fd1,0x44220024,0x2d8b008e,0x35c60035}}, // _agua_, _vuk_, _eace_, रोज़,
+ {{0x68fb9220,0xcf570051,0x0f570158,0x2d8b34f8}}, // _ayud, _בבית_, _ביים_, _face_,
+ {{0xa7fb01df,0x25a907d8,0x2d8b0314,0x2c6b8366}}, // ruñe, cjal_, _gace_, kød_,
+ {{0xf993010f,0x64430a20,0x2d80002e,0x3946b4f9}}, // ירא_, ćnic, meie_, raos_,
+ {{0x60048247,0x6d4283a8,0x2d8b34fa,0x2d80004a}}, // fòma, _seoa, _zace_, leie_,
+ {{0x7c228364,0xfaa334fb,0x2d8b00a4,0xc7b888ae}}, // _suor, _каро, _yace_, _niđe_,
+ {{0xa2c590a1,0x656434fc,0x5e5700be,0x2d8b0118}}, // ानन्, ngih, דיקע_, _xace_,
+ {{0x68fbb15d,0x2904b4fd,0x6d4286ae,0x7dde8ec3}}, // _gyud, _uzma_, _veoa, nīsi,
+ {{0x7c228009,0x212000b9,0x2d9d8870,0x32090df6}}, // _vuor, _agih_, _enwe_, _shay_,
+ {{0x2d8001b0,0xdea3804e,0xd7ef80f7,0xa5b300c2}}, // keie_, _نیوی, _بكل_, _इकलौ,
+ {{0x61e2b4fe,0x7c228009,0xb6d980be,0x24868069}}, // nnol, _tuor, _אַנט, _tsom_,
+ {{0x7c2d8457,0x2d8b34ff,0x3f8cb500,0x61fd29c6}}, // _çarp, _race_, _kadu_, rlsl,
+ {{0xb8cb243f,0x2d8b0041,0x7dde81a9,0x6b8a8366}}, // _गप_, _sace_, dīsi, _uafg,
+ {{0x8c1b810f,0x32090028,0x3f8c8b20,0x2d8b3501}}, // _קווי, _thay_, _madu_, _pace_,
+ {{0x69c60352,0xdb1ab502,0xf1b98668,0x320902d5}}, // chke, chtá, _kiše_, _uhay_,
+ {{0x33750021,0x25a93503,0xa5f82466,0x61e2b504}}, // [21d0] лгар, rjal_, тету_, dnol,
+ {{0x1ae700ab,0xe1ff3505,0x248d026c,0x2d8b0041}}, // _করবে_, lló_, _šems_, _wace_,
+ {{0x2d8b04b9,0x798d3506,0x68fb9400,0x2d8db507}}, // _tace_, _kaaw, _syud, ydee_,
+ {{0x61e2b508,0xe1ff0019,0x3f9e8041,0xfb1580be}}, // gnol, nló_, _antu_, אַנט_,
+ {{0x80cd8063,0x040d801c,0x518481e5,0x3f8c8102}}, // _देखे, _cườn, руха, _badu_,
+ {{0xa91d8110,0x69d885db,0xa5bb0825,0x040d80ff}}, // _maži, éver, _bióg, _dườn,
+ {{0xf770a6f1,0xf1b98267,0x442932aa,0x661d3509}}, // تان_, _aiše_, lqa_, _kisk,
+ {{0x661d350a,0x644d350b,0x752d350c,0x3ea98d26}}, // _jisk, osai, ncaz, ćat_,
+ {{0xa3b70076,0x7afc00f1,0xad9b00f7,0x644d350d}}, // _जवा_, _zyrt, _chúi, nsai,
+ {{0x3f8cb50e,0x09dc035a,0x69d9b50f,0x2c6b8edd}}, // _gadu_, मच्य, liwe, rød_,
+ {{0xd9460dd3,0xd57589c7,0x61461597,0xa96a1677}}, // лени, _буль, лена, нина_,
+ {{0x7c958591,0x798d0079,0x644d3510,0xaca38133}}, // _مشتا, _caaw, ksai, _alụk,
+ {{0xeb8eb511,0x753ba742,0x798d3512,0x3f8cb513}}, // _ли_, mbuz, _daaw, _yadu_,
+ {{0x69c600f1,0xe80403b7,0xe1ff0216,0x661d2f52}}, // shke, _शीशा_, aló_, _aisk,
+ {{0x6b8184cc,0xe1ff3514,0x661d3515,0x3ebe007b}}, // melg, bló_, _bisk, átt_,
+ {{0x61e28085,0x8886148d,0x69d9804f,0x3f8c258f}}, // xnol, _ближ, jiwe, ždu_,
+ {{0xb0d2809a,0x645bb516,0x69c43517,0x7c29b518}}, // [21e0] _देंग, nrui, _ilie, nqer,
+ {{0xb6a60cec,0x3ea6045e,0xdb0380e7,0x69c43519}}, // _биол, _биог, _conç, _hlie,
+ {{0x661d155f,0x644d10eb,0x50d6803d,0x7c36351a}}, // _fisk, asai, گزار, spyr,
+ {{0x3f8ca1dc,0x91fd0029,0x249f0024,0xa91d8f20}}, // _sadu_, stāv, _šume_, _zaži,
+ {{0x7bc38006,0x644d351b,0x69c401ac,0x6d4601a9}}, // _olnu, csai, _mlie, _ieka,
+ {{0x91fd0029,0x61e2a1a2,0x6d46351c,0x661d016b}}, // ltāt, snol, _heka, _zisk,
+ {{0x6d4634c3,0x26058076,0x69c4351d,0xf1c1026f}}, // _keka, _हीही_, _olie, ášky_,
+ {{0x5454b51e,0x645bb51f,0x2627826c,0x040d801c}}, // авит, frui, _gđom_, _tườn,
+ {{0xa2d9a38c,0x973c8088,0x7bc68612,0x6b81b520}}, // _फेब्, _anće, ūkum, felg,
+ {{0xf1b9803a,0x1ae70a49,0x6d46127a,0xef918077}}, // _više_, _করতে_, _leka, _ویند,
+ {{0x69c43521,0xb3458187,0x798d02d5,0xd3778048}}, // _blie, _seçã, _paaw, учы_,
+ {{0x6d463522,0x645b933a,0x8c46954f,0x0446af4b}}, // _neka, brui, _беде, _бедн,
+ {{0x645bb523,0x201eb524,0xa91d8110,0x107403c7}}, // crui, _kiti_, _paži, бляю,
+ {{0xa3e88b3b,0x69c43525,0x92948ff7,0x69d9b526}}, // _यदि_, _elie, _вакц, ziwe,
+ {{0x6d463527,0x4426822e,0xdd9b3528,0x7bda8364}}, // _beka, _huo_, нша_, mitu,
+ {{0x6d463529,0x44268110,0x7c26352a,0x69c4352b}}, // _ceka, _kuo_, _bukr, _glie,
+ {{0x661d28d1,0x2455003d,0x6298b52c,0x24580009}}, // [21f0] _visk, شناس, _hrvo, лать_,
+ {{0x644d352d,0x442901b9,0x661d352e,0xf1bf01d0}}, // rsai, rqa_, _wisk, lká_,
+ {{0x644d352f,0x661d3530,0x6d463531,0x44293532}}, // ssai, _tisk, _feka, sqa_,
+ {{0x19598196,0x7bda8744,0xf1bf3533,0x3ebe0bc5}}, // тады_, hitu, nká_, øtte_,
+ {{0x201e8052,0x44268a8e,0xa0a58adb,0x7bdab534}}, // _biti_, _nuo_, ралд, kitu,
+ {{0x6d463535,0x65b5803e,0x201eb536,0x62988019}}, // _zeka, _náhr, _citi_, _orvo,
+ {{0x74d904c5,0xda05016f,0x8c0a80ab,0x6d46071f}}, // _नेतृ, रतात_, রদান_, _yeka,
+ {{0x4426b537,0xf2d204de,0x201e8084,0x225c01d6}}, // _buo_, _ועל_, _eiti_, ávke_,
+ {{0x62988364,0x44268052,0xe1348012,0x05a68009}}, // _arvo, _cuo_, _ынты, рвый_,
+ {{0xf77388ca,0x69c43538,0x7bdab539,0x6721805c}}, // _دار_, _slie, gitu, _uglj,
+ {{0x69c4353a,0x62988699,0x61e9835f,0x2909019d}}, // _plie, _crvo, _kjel, _azaa_,
+ {{0x629882a5,0x645bb53b,0x6b81b53c,0x2258b53d}}, // _drvo, prui, selg, árka_,
+ {{0x6d460025,0x69c40b3c,0x628aabe1,0x629880f3}}, // _reka, _vlie, _esfo, _ervo,
+ {{0x6d460812,0x7bdab53e,0x91fd00eb,0x7bc8b53f}}, // _seka, citu, tuāc, chdu,
+ {{0x61fbb540,0x69c401b9,0x7c263541,0x2d8fb542}}, // _okul, _tlie, _sukr, _kage_,
+ {{0xf1bf0a56,0x65b581ac,0xceb28039,0x61e9b543}}, // cká_, _záhr, חיל_, _njel,
+ {{0x9f5e8009,0x6fc5816f,0xd7c58072,0xa91d80c3}}, // [2200] _yhtä_, वसां, वसाच, _kažu,
+ {{0x2d8fb544,0x61fbb545,0x75fe80f1,0x91fd01a9}}, // _lage_, _akul, rëzi, grāf,
+ {{0x6d460886,0x61e98654,0xbea614bc,0xa9268db4}}, // _teka, _bjel, _танк, адел,
+ {{0x201eb546,0x61e9920e,0x25adb547,0xc05a8d13}}, // _siti_, _cjel, jjel_, вік_,
+ {{0x61e9830b,0x201eb548,0x25a20456,0xd378807d}}, // _djel, _piti_, _inkl_, liće_,
+ {{0x91fd0341,0xd90d80d5,0x7c2480f7,0x44268870}}, // ntār, ریل_, _éiri, _ruo_,
+ {{0x44268698,0x4b7b0bea,0x7bdab549,0x64428063}}, // _suo_, _שאלו, vitu, _swoi,
+ {{0x2d920205,0x61e9945c,0xbcfb00f7,0x4426b54a}}, // ndye_, _gjel, _scéi, _puo_,
+ {{0x2d8fb54b,0x201eb54c,0xbc6780f7,0x7bdaa4cf}}, // _dage_, _titi_, لمين_, titu,
+ {{0x62988b5d,0x90e5803d,0xc5e3354d,0x201e8162}}, // _prvo, _اسفن, _गद्य_, _uiti_,
+ {{0xe7e38076,0xbeaa8077,0xf1b98353,0x2d8f8041}}, // कचरा_, _جهان_, _hiša_, _fage_,
+ {{0x4426b4ba,0x7bdab54e,0x28db946d,0x6da39541}}, // _tuo_, situ, _मेडि, _миха,
+ {{0x6e28827f,0xe3af819f,0xdb0e0214,0x75d381a8}}, // _hudb, سری_, _albü, _بينا,
+ {{0xf1bf003e,0x2d84b54f,0xf1b98390,0x248b008e}}, // ská_, leme_, _miša_, _rscm_,
+ {{0x394b3550,0x69dd0bfe,0x7bd89d61,0xadc38091}}, // pacs_, éres, _imvu, _atẹl,
+ {{0xdcfb83bf,0xf99f1b01,0x2d84b551,0xfa3395a9}}, // nduğ, rmès_, neme_, _برود,
+ {{0x69dd138e,0xf1b985a2,0x6e2889d1,0x600d8020}}, // [2210] mise, _niša_, _ludb, lúme,
+ {{0x61fbb552,0x3dc90069,0x60c00bc5,0x69dd138e}}, // _skul, shaw_, ømme, lise,
+ {{0x2003003b,0xe6950013,0x6e950013,0xf1b988ae}}, // mlji_, _الجد, _الجا, _aiša_,
+ {{0x69dd3553,0x6286809a,0xa5bb0333,0xdb0e0216}}, // nise, łkow, _dióc, _albó,
+ {{0x2d8f84b9,0x2d84803e,0x9f51001b,0x260a9993}}, // _rage_, deme_, hozí_, ातनी_,
+ {{0x69dd3554,0x39490c5e,0x7dde80eb,0x490a0fb2}}, // hise, _leas_, zīst, _सरसो_,
+ {{0x2d8fb555,0x320d804c,0x69dd3556,0x7c2d3557}}, // _page_, _they_, kise, lqar,
+ {{0x59f98009,0x61fb805d,0x3940807b,0xc7b8811f}}, // _себя_, _ukul, ðist_, _viđa_,
+ {{0x7bc704c4,0x69dd0006,0x26c181ac,0x63b50bcf}}, // _klju, dise, šho_, _kozn,
+ {{0x6e28835d,0x39490118,0x25bfb4b1,0x3a2002f7}}, // _fudb, _aeas_, skul_, _siip_,
+ {{0x2d8fb558,0x69dd3559,0x7dde8029,0x63b5355a}}, // _tage_, fise, tīst, _mozn,
+ {{0x63b50904,0xd3788253,0x69dd355b,0x2d849e1c}}, // _lozn, viće_, gise, ceme_,
+ {{0x27f8355c,0x39490083,0x7bc7355d,0xec3580be}}, // torn_, _deas_, _olju, _פאַר_,
+ {{0x7c3bb55e,0xd3788289,0x69dd355f,0xdc3680be}}, // mpur, tiće_, aise, _האסט_,
+ {{0xed5a0758,0xada61b10,0x6da61630,0x75fe8168}}, // лов_, бавл, бива, rëzv,
+ {{0x249f01dd,0x6569b560,0xd3788289,0xe3bf03a8}}, // _šuma_, ngeh, riće_, _buño_,
+ {{0x61f9809a,0xdca31ac9,0xd3788904,0x7bc700ce}}, // [2220] dowl, пати, siće_, _blju,
+ {{0x7bc1b561,0x65bc82be,0xd3788289,0x7f8600f7}}, // nklu, _véhi, piće_, _الدن,
+ {{0x63b53562,0xdceb80eb,0xdb0e06c4,0xce9581a9}}, // _dozn, _angļ, _albò, šējā_,
+ {{0x629a2a3e,0x27e981a8,0xdb1c0106,0x9f580168}}, // _átom, éann_, _omrö, korë_,
+ {{0x6e288025,0x28d2824c,0xdb0a8118,0x66043563}}, // _sudb, _देखि, _mofá, mlik,
+ {{0x824a2f0a,0x66043564,0xdfd080f7,0x79863312}}, // _مشرف_, llik, بيت_, hekw,
+ {{0x7bde3565,0x69dd29f7,0x8f9b0039,0xddcb826c}}, // mipu, yise, טיסי, _šiši,
+ {{0x7bde0886,0x63b50038,0x66043566,0x69cba7f9}}, // lipu, _zozn, nlik, chge,
+ {{0xe3bf07f4,0xdcfb83bf,0x6604067f,0x69dd3567}}, // _xuño_, rduğ, ilik, vise,
+ {{0x7bde03f8,0x325485c2,0xdee68992,0x7bc1a168}}, // nipu, овор, _лози, gklu,
+ {{0x69dd3568,0x66043569,0xd5af9b4b,0x93fb00be}}, // tise, klik, _ес_, רליי,
+ {{0x645d82ba,0x7bde003d,0xba9b03c8,0x69c2b56a}}, // ásic, hipu, אסטי, nkoe,
+ {{0x7bde356b,0x6604356c,0x39490511,0x600d8020}}, // kipu, dlik, _veas_, súme,
+ {{0x69dd356d,0x7bc182af,0xcc57810f,0x629c356e}}, // sise, cklu, _פסוק_, _orro,
+ {{0x98a51010,0x7bde356f,0x91fd00eb,0x7bdb8039}}, // ğlı_, dipu, trād, _תקוו,
+ {{0x98a50182,0x66043570,0x1ae700ab,0x7d02838a}}, // şlı_, glik, _করলে_, _kyos,
+ {{0x2003005c,0x545500e8,0xe9df01a8,0x93270061}}, // [2230] plji_, юват, miú_, مران,
+ {{0x7bde1b6b,0x61ed23ea,0xad9b00f7,0xe9df01a8}}, // gipu, _hjal, _chúr, liú_,
+ {{0x63b5135a,0x387fb571,0xc33381c6,0x7bc702d4}}, // _vozn, _spur_, פוף_, _vlju,
+ {{0xdb038038,0xe9df00f7,0x3949a511,0x6e22b572}}, // _ponú, niú_, úas_, _niob,
+ {{0x628e3573,0x7c2d0609,0x629c3574,0x27e985e4}}, // _esbo, qqar, _erro, éano_,
+ {{0x44393575,0x7bc70042,0x9848a1f6,0xad9b0f35}}, // _its_, _ulju, сяца_, _thús,
+ {{0x798620c4,0x61ed3576,0x6569b577,0x3ffc03de}}, // yekw, _ojal, tgeh, אפגע,
+ {{0x44230586,0xe4598af2,0xf8bf3043,0x7d0282a0}}, // _hij_, ажи_, ncés_, _byos,
+ {{0xdb1a80f7,0xa3b00540,0x6e22b578,0x6569b579}}, // chtú, टॉक_, _diob, rgeh,
+ {{0x7986357a,0xa3d3800f,0x61ed357b,0x4423357c}}, // wekw, सों_, _ajal, _jij_,
+ {{0xf77095e4,0x44230613,0x7986357d,0x656f0098}}, // _سال_, _mij_, tekw, _occh,
+ {{0x7c3bb57e,0x44230069,0x6e22b57f,0x644301dd}}, // ppur, _lij_, _giob, ćnij,
+ {{0x61ed3580,0x79862f0b,0x44393581,0x69c9826c}}, // _djal, rekw, _nts_, _mlee,
+ {{0x4423022c,0xee3a2606,0xd9b697a3,0xaa45a28e}}, // _nij_, инг_, _अक्ट, _декл,
+ {{0x61ed3582,0x69c9b583,0x6d4b8590,0x66043584}}, // _fjal, _olee, _kega, tlik,
+ {{0x61ed3585,0x806611c7,0x2251803e,0xe5a30fe7}}, // _gjal, _движ, ázku_, миси,
+ {{0x44231dbe,0x66043586,0x7bde1d29,0xd3788024}}, // [2240] _bij_, rlik, tipu, lića_,
+ {{0x66043587,0x3f879351,0x6d4bb588,0x69c9b589}}, // slik, jenu_, _lega, _alee,
+ {{0x6604358a,0x7bde358b,0x3f87b58c,0x44390722}}, // plik, ripu, denu_, _ets_,
+ {{0x7bde358d,0x64a608d5,0x0906802e,0xf6530039}}, // sipu, _дама, опен, _קצר_,
+ {{0x4423358e,0x6e22816a,0xd378b58f,0x69c2800b}}, // _fij_, _riob, hića_, rkoe,
+ {{0x69c995ba,0xd3788289,0xc05980e8,0xa09b03c8}}, // _elee, kića_, сії_, יינט,
+ {{0x600487e2,0xeb910158,0xd37881dd,0x628e020d}}, // nòmi, _אָן_, jića_, _usbo,
+ {{0x44230613,0xd378803b,0x6d4b90e1,0x5d5480b3}}, // _zij_, dića_, _cega, чкит,
+ {{0x6d4bb590,0x4423022c,0x7c238091,0x6d598365}}, // _dega, _yij_, _binr, _ddwa,
+ {{0x6d59b591,0x3f87825b,0xd378812b,0x61ed070b}}, // _edwa, cenu_, fića_, _sjal,
+ {{0x6d4b9e72,0xc33302f6,0xd3788042,0x69c98009}}, // _fega, וות_, gića_, _ylee,
+ {{0x7c23a32a,0x6e22826c,0x78a201a9,0xfec20264}}, // _einr, _uiob, _šova, োপাধ,
+ {{0xa5078ae7,0xe9df0013,0x6443005c,0xc95384de}}, // _деца_, riú_, ćnik, ומר_,
+ {{0xd7fb138c,0x29050029,0xd378812b,0xe9df00f7}}, // рум_, āla_, bića_, siú_,
+ {{0xcddb19e3,0x442300f3,0x7c2b8398,0xa3ae1199}}, // ања_, _rij_, _zugr, कान_,
+ {{0x442301c5,0x442b0722,0x7bdc2bb7,0x7dce8229}}, // _sij_, _puc_, _amru, _dùsg,
+ {{0x44230282,0x61fd0a96,0x92e700c8,0x7e6701c0}}, // [2250] _pij_, mosl, _পরে_, sujp,
+ {{0x44233592,0x69c9b593,0x7a330609,0x9f430216}}, // _qij_, _slee, _aħta, rojó_,
+ {{0x69c9b594,0x4423290e,0x8ae7035f,0x443902f7}}, // _plee, _vij_, ціал, _wts_,
+ {{0x44230613,0x61fd3595,0xbcfb00e1,0x442b00ff}}, // _wij_, nosl, _dcér, _tuc_,
+ {{0x44233596,0x8fa58d13,0x4439008e,0x69c983b2}}, // _tij_, зале, _uts_, _vlee,
+ {{0x6d4b885c,0x2904831d,0x94ba0039,0x9f4780e7}}, // _sega, _dyma_, _המשת, onné_,
+ {{0x3f878289,0x9258038d,0x6d4ba5c8,0xed59b597}}, // renu_, _март_, _pega, бой_,
+ {{0xd378803b,0x656d3598,0xd90e8065,0x21290748}}, // vića_, ngah, ئیے_, _ngah_,
+ {{0x6d4bb599,0x205481a0,0x478b8009,0x3f87929b}}, // _vega, _октя, _всем_, penu_,
+ {{0xd3788067,0x63b881c0,0x6d4bb59a,0xf1b9826c}}, // tića_, _covn, _wega, _hoš_,
+ {{0x88bd80ab,0x7bc500dd,0x9f4e03c1,0x6b950612}}, // _অধিক, ikhu, áním_, _mazg,
+ {{0xf1b9803a,0x22470ed7,0xd3788024,0x20010359}}, // _još_, ánky_, rića_, _akhi_,
+ {{0xa3dc05b3,0x64a599b5,0x394daa3c,0xd378812b}}, // तोड_, _хала, _mees_, sića_,
+ {{0x394db59b,0x443f8812,0x4375893f,0xf1b9805c}}, // _lees_, mpu_, _זײַן_, _loš_,
+ {{0x443fb59c,0x6012007b,0x61fd176d,0xc33283de}}, // lpu_, væmd, bosl, _אוו_,
+ {{0xc8bd89a3,0x25a6040e,0x394d8282,0x69cf026f}}, // ्नाट, ñol_, _nees_, chce,
+ {{0x6b950115,0x2ea883e8,0x443f8176,0xdd8f0b8c}}, // [2260] _bazg, _कप्त, npu_, دوق_,
+ {{0xdb0383d3,0x389b010f,0x60001cab,0x3a24b59d}}, // _anné, _פיינ, röms, _simp_,
+ {{0xf1b98353,0xd36f0154,0x394d9af4,0xc7b8811f}}, // _boš_, اهم_, _bees_, _rođ_,
+ {{0x2d89359e,0xa96a8f27,0x69c615c8,0x3f8a359f}}, // ceae_, сида_, nkke, lebu_,
+ {{0x6d441cdc,0x394db5a0,0x68438329,0xbcfb026b}}, // mbia, _dees_, енча, _adéf,
+ {{0x6d440110,0x3f9835a1,0x6e2602d5,0x64560034}}, // lbia, ndru_, _hikb, msyi,
+ {{0x3a248012,0x63b8803e,0x09cc035a,0xd946a09a}}, // _timp_, _rovn, ासाठ, _неви,
+ {{0x394db5a2,0x6fcb01cc,0x3a24816a,0x3f8a35a3}}, // _gees_, _müca, _uimp_, hebu_,
+ {{0x69dd02be,0x645635a4,0x443f82d5,0x48ce0264}}, // èrem, nsyi, gpu_, রপুর,
+ {{0x5a348572,0x6f668073,0x799605ee,0x61e2b5a5}}, // мнот, _ѕвез, _aayw, liol,
+ {{0xa5bb0207,0x7f3b8039,0x799602d5,0x60c29fce}}, // _biól, _העבו, _bayw, lyom,
+ {{0x394d822c,0x61e2b5a6,0x25a68114,0xdceb02d4}}, // _xees_, niol, _unol_, _žičn,
+ {{0x60c2b5a7,0xdca39c79,0x61fd1b50,0x2bd9809a}}, // nyom, _паци, rosl, योहा,
+ {{0xb7bd802e,0x6fbe80d4,0x3f8a35a8,0x61e2b5a9}}, // _puţi, ्सिं, gebu_, hiol,
+ {{0x644086a8,0xbcfb0118,0xddd035aa,0x61e2b5ab}}, // lpmi, _acép, _šeši, kiol,
+ {{0xb8e30a49,0x6d4435ac,0x76550035,0x69c60192}}, // _এই_, gbia, rszy, ckke,
+ {{0x57f5120c,0x394db5ad,0x69df0609,0xb0db8a27}}, // [2270] _опит, _rees_, _imqe, _मेंग,
+ {{0xfe6e19f4,0xa3d7000f,0x60cf080a,0x76550035}}, // دگی_, ़ों_, ınmı, pszy,
+ {{0x6d4435ae,0x6602b5af,0x394d9d14,0x6b9502d0}}, // bbia, _okok, _pees_, _vazg,
+ {{0xe8d935b0,0x61e2847f,0x394d8282,0x27e301ed}}, // _ndị_, giol, _qees_, lijn_,
+ {{0x394db5b1,0x315800be,0x60c28019,0xf1b98af8}}, // _vees_, ליאן_, gyom, _hišk_,
+ {{0x394d84df,0x660290ab,0xe8d90870,0xbcfb046d}}, // _wees_, _akok, _adị_, _adég,
+ {{0x6d4f0397,0xdd918117,0x69c60b40,0x629a35b2}}, // _keca, ہوں_, ykke, _štov,
+ {{0xf1948153,0x7c2735b3,0x8459b5b4,0x38608503}}, // _пись, _hijr, орот_, šira_,
+ {{0x8554803d,0x225800cd,0x799600fc,0x6f0901d0}}, // ریور_, _zvrk_, _rayw, řech,
+ {{0x6d4f35b5,0x69cd0d62,0x443fb5b6,0x3e578162}}, // _leca, _alae, rpu_, păt_,
+ {{0x7c2f0c6e,0xa18a2b3f,0xa3ae34ec,0x69cd031d}}, // _lucr, обна_, कात_, _blae,
+ {{0x443f8fc0,0x69cd35b7,0x6d4f35b8,0x88e60ca0}}, // ppu_, _clae, _neca, джме,
+ {{0x661bb5b9,0xc4fb803d,0x69c60198,0x6609b5ba}}, // mmuk, _اعضا_, rkke, mlek,
+ {{0x69cd02c4,0x661b8a41,0x8b0381d0,0x7d061c77}}, // _elae, lmuk, ářsk, _ryks,
+ {{0xf77209d7,0x6d4f3499,0x2bda8077,0x442fb5bb}}, // _یاد_, _beca, _بانک_, _hug_,
+ {{0x4427b5bc,0x6d4f35bd,0x7e61007b,0x6609b5be}}, // _hin_, _ceca, álpa, nlek,
+ {{0x6d442bb8,0xe3ba0cf3,0xfce30195,0x442fb5bf}}, // [2280] rbia, оба_, вото, _jug_,
+ {{0x44278b1b,0x8bf100c8,0x64561600,0x7d060110}}, // _jin_, _জীবন_, rsyi, _vyks,
+ {{0x442fb5c0,0x9f5c80f1,0x7c2f0118,0xbcfb26b2}}, // _lug_, sovë_, _eucr, _adéd,
+ {{0x4427b5c1,0x6609869f,0x9f5c83c1,0x661b80b9}}, // _lin_, jlek, mové_, jmuk,
+ {{0x9f5c803e,0xa3bf897d,0x645d816b,0x443d81c0}}, // lové_, ीसा_, ásil, _ntw_,
+ {{0x4427b5c2,0x6609b5c3,0x8fa38878,0xd8d735c4}}, // _nin_, elek, каче, וונט_,
+ {{0xdb03a6d5,0x9f5c827f,0x34b88bb8,0xd6d080f7}}, // _monó, nové_, ेन्द, طقة_,
+ {{0xd5b08307,0x4427b5c5,0x27e301ed,0x661b977c}}, // افة_, _ain_, zijn_, gmuk,
+ {{0x4427b5c6,0x442f8640,0x9f5c826f,0xf41f0338}}, // _bin_, _cug_, hové_, _isär_,
+ {{0x63bc0a3e,0x9f5c83cb,0x39468110,0xa3d8809a}}, // _jorn, kové_, lbos_, ठों_,
+ {{0xbcfb35c7,0x9f5c826f,0x2fd7803d,0x69cd35c8}}, // _idée, jové_, _تولد_, _plae,
+ {{0x4427b5c9,0x9f5c83fb,0x3ce08289,0x8c4687b6}}, // _ein_, dové_, _živu_, _жеде,
+ {{0x6d4f1520,0x29f8b5ca,0x443d808e,0xdb18802a}}, // _reca, mčad_, _gtw_, _mové,
+ {{0x6d4f1a67,0x4427b5cb,0x9f4300f1,0xdb038388}}, // _seca, _gin_, mijë_, _conó,
+ {{0xd5bb35cc,0x7c2f1ebb,0xdb038118,0x9f5c85b9}}, // оса_, _sucr, _donó, gové_,
+ {{0x4427b5cd,0xdb18827f,0xca2980be,0x29f8811f}}, // _zin_, _nové, _צופֿ, nčad_,
+ {{0x63bc35ce,0x4427a778,0x6d4f35cf,0x62978118}}, // [2290] _born, _yin_, _veca, _áxor,
+ {{0x2fc70104,0x44278028,0x60c000f2,0x9f5c826f}}, // _ông_, _xin_, ämme, bové_,
+ {{0x3d0583bb,0x386680ee,0x63bc35d0,0xddc38087}}, // _हुने_, eror_, _dorn, nunţ,
+ {{0x6d5d006a,0x6eae001b,0x2909008e,0xf1b980ce}}, // _udsa, ीहरु, _kyaa_, _kiši_,
+ {{0xdd3a0051,0x63bc080e,0x798bb5d1,0x6609941f}}, // _מערכ, _forn, regw, vlek,
+ {{0x2d990079,0xe7398b9b,0xe8d9019d,0xe61180d7}}, // _hase_, чек_, _ndọ_, گشت_,
+ {{0x4427b5d2,0x2d9935d3,0x27e981dd,0x442fb5d4}}, // _rin_, _kase_, đan_, _sug_,
+ {{0x4427b5d5,0xdcf380fe,0xdceb81cd,0xe8d901bc}}, // _sin_, šače, _jagħ, _adọ_,
+ {{0x6609b5d6,0x661b859c,0xdceb81cd,0x9f5c826f}}, // rlek, rmuk, _magħ, zové_,
+ {{0xe3b18307,0x63bc35d7,0x2d9935d8,0xe8bd8540}}, // ارة_, _xorn, _lase_, ्नीच,
+ {{0x29090962,0x4427b5d9,0x2bb1809a,0x6609b5da}}, // _ayaa_, _vin_, जाबा, plek,
+ {{0x442f81e9,0x9f5c826f,0xf99f0247,0x9f4781a8}}, // _tug_, vové_, blèm_, inní_,
+ {{0x4427b5db,0x30750d91,0x2ec81d40,0x1c4485a8}}, // _tin_, курс, रन्त, _анім,
+ {{0x9f5c803e,0x44278f29,0x6aae81a8,0x25bd81b4}}, // tové_, _uin_, óifí, _howl_,
+ {{0xa3ae2a54,0xb9072261,0xf8bd800d,0x39468110}}, // कास_, _ये_, ्नुप, ybos_,
+ {{0x9f5c803e,0x2d9935dc,0x212d90e1,0xdb1c00e7}}, // rové_, _case_, _ngeh_, _forê,
+ {{0xa3c3835a,0x3dd20355,0x9f5c803e,0x7de8801b}}, // [22a0] _एका_, rhyw_, sové_, měst,
+ {{0xdcea812b,0x61e635dd,0xfc3f0013,0x395f90f0}}, // šiće, likl, _dtí_, _adus_,
+ {{0xa3dc058c,0x63bc01ec,0x160fa0d5,0x6600a0e2}}, // तोष_, _vorn, ातार_, yomk,
+ {{0x3f9a35de,0x61e60255,0x629a00c3,0xfb1683de}}, // _hapu_, nikl, _štos, ײַבט_,
+ {{0x63bc35df,0xe8948ca0,0x6d5635e0,0xa927835f}}, // _torn, таль, maya, ніше_,
+ {{0x2d990f20,0x2d8035e1,0xdb03823e,0x644435e2}}, // _zase_, lfie_, _anní, mpii,
+ {{0xaade8105,0x3f9a1249,0xdb1c0168,0x61e635e3}}, // _फेंक, _mapu_, _morë, kikl,
+ {{0xceb30051,0x6d5635e4,0x442a0133,0xa3ae11bc}}, // ריה_, naya, _hib_, काव_,
+ {{0x442a35e5,0x61e635e6,0x18678073,0xd1758084}}, // _kib_, dikl, _пати_, _шыры,
+ {{0x6d5635e7,0x3a29059c,0x442a056c,0xb3469243}}, // haya, _siap_, _jib_, diçõ,
+ {{0x6d5602e8,0x78a435e8,0xd257804a,0x2ca002f1}}, // kaya, _ariv, нця_, hvid_,
+ {{0x442a35e9,0x6d5634c3,0x78a435ea,0x3e7a8168}}, // _lib_, jaya, _briv, mët_,
+ {{0x7dca003e,0x6d5635eb,0x3e7a820f,0x1ae700c8}}, // _výsl, daya, lët_, _করছে_,
+ {{0x442a35ec,0xf1b98024,0xb4be864a,0x78a435ed}}, // _nib_, _viši_, ीनी_, _driv,
+ {{0x2d990205,0x3a290057,0x2d802cbd,0x3e7a80f1}}, // _pase_, _tiap_, ffie_, nët_,
+ {{0x6d561efa,0x32538698,0xb3468073,0x78a40370}}, // gaya, _свър, biçõ, _friv,
+ {{0xf3670758,0x442a03ec,0x3e7a80f1,0xdb15026f}}, // [22b0] етен, _bib_, hët_, _vozí,
+ {{0x442a35ee,0x3e7a820f,0x667200d7,0x7c2a8372}}, // _cib_, kët_, دگیر, _jifr,
+ {{0x442a02a3,0xdcebaf1e,0x6d5635ef,0x3e7a80f1}}, // _dib_, _tagħ, baya, jët_,
+ {{0x200335f0,0x799d35f1,0x6d5635f2,0x7de7810c}}, // loji_, ndsw, caya, _mèsè,
+ {{0x660d35f3,0x249f03a7,0x799d1989,0x7dda128a}}, // mlak, _éum_, idsw, růst,
+ {{0x660d35f4,0x61e61c37,0xdee584ae,0x442a35f5}}, // llak, zikl, коли, _gib_,
+ {{0xa3ae0d86,0x34c98bb8,0x3e7a8168,0xddd48088}}, // कार_, िन्द, gët_, _šaši,
+ {{0x442a0282,0x4ce280ab,0x660d35f6,0x4c85b5f7}}, // _zib_, _পড়ু, nlak, влив,
+ {{0x61e606be,0x442a022c,0xc8e00540,0x7c2a808b}}, // vikl, _yib_, _पेंट, _bifr,
+ {{0xad9b01fa,0xd7598b76,0x660d005d,0x20030803}}, // _skúl, _آلات_, hlak, joji_,
+ {{0x6d5635f8,0x7c2aabbe,0x27e7807b,0xa96a0e0b}}, // yaya, _difr, minn_, мина_,
+ {{0xe3b18bca,0x660d0024,0x27e7b5f9,0x3f9a355e}}, // ارک_, jlak, linn_, _sapu_,
+ {{0xeb8e9ddf,0x660d35fa,0x1a65819f,0x61e635fb}}, // _ки_, dlak, ریکی_, rikl,
+ {{0x20030db7,0x6d562b36,0xb34683a7,0x777703a8}}, // goji_, waya, riçõ, _ccxx,
+ {{0x6d5635fc,0xb34683a7,0xa3d686af,0x645d0711}}, // taya, siçõ, _सतत_, _avsi,
+ {{0x442a10af,0x660d221b,0x27e78046,0x799bb5fd}}, // _sib_, glak, hinn_, _nauw,
+ {{0xa3ae09a3,0x442a01c5,0x6d5635fe,0x27e7807b}}, // [22c0] काल_, _pib_, raya, kinn_,
+ {{0x6d5635ff,0x442a0069,0xa3dc0063,0x80db3600}}, // saya, _qib_, तों_, _যুক্,
+ {{0x6d563601,0x442a0282,0x799bb602,0x81f7004e}}, // paya, _vib_, _bauw, _سفیر_,
+ {{0x442a0e60,0x5e570158,0x6e2b877f,0x6d5601b4}}, // _wib_, _ביטע_, _nigb, qaya,
+ {{0x442a01e9,0xf1b9a35d,0xf772803f,0x6d49b603}}, // _tib_, _nišu_, لاغ_, dbea,
+ {{0x27e7b604,0x3e7a80f1,0x6e2b8091,0xf09303c8}}, // ginn_, tët_, _aigb, ינד_,
+ {{0x61e400ce,0xf1b9826c,0xbcfb3605,0xd6cf80a0}}, // _omil, _aišu_, _idéa, يقه_,
+ {{0x3e7a88cf,0x6b9c3606,0x7c2ab607,0x2bca99e8}}, // rët_, _harg, _sifr, िस्थ,
+ {{0xb4be913d,0xb4cc81fe,0x6b9c3608,0xd3788289}}, // ीने_, रने_, _karg, lići_,
+ {{0x61e43609,0x69c0b60a,0xf1b98699,0x27e781e4}}, // _amil, _home, _dišu_, cinn_,
+ {{0x6d49b60b,0xd6c48bca,0xa3e3923a,0xa3c38006}}, // bbea, _حمای, _नगर_, _एकर_,
+ {{0x6b9c33fe,0xdb1c360c,0x3869b60d,0x6e2b8915}}, // _larg, _coré, šare_, _gigb,
+ {{0xdb151727,0xdb1c12b6,0x2d86810c,0xd3788140}}, // _mozá, _doré, _iboe_, hići_,
+ {{0x61e4360e,0x5ba38698,0xbcfb00f7,0xa3e1824c}}, // _emil, _връз, _ndéa, नोद_,
+ {{0x27ed003b,0xdd9b0791,0x660d360f,0x9e07035f}}, // đen_, мша_, tlak, вчал,
+ {{0xd378805c,0x2ba68beb,0x973c8115,0x59670081}}, // dići_, _कोता, _inću, _ръка,
+ {{0x660d14ff,0xa3ae00d4,0x629a3610,0x24580009}}, // [22d0] rlak, कां_, _štop, кать_,
+ {{0x6b9c10dd,0xd3789487,0x39593611,0xfc3f0511}}, // _carg, fići_, lass_, _cría_,
+ {{0x6b9c3612,0x799bb613,0x69c0a170,0x656281d6}}, // _darg, _pauw, _bome, _odoh,
+ {{0x249f00fe,0xf99f06c0,0xf1b989d1,0xa11581a8}}, // _šumu_, ndè_, _ništ_, هوات,
+ {{0x27e7b614,0x69cb83b2,0xfc3f2a8c,0xa0698254}}, // tinn_, rkge, _fría_, хала_,
+ {{0x6b9c3615,0xd37882a5,0x628a806a,0xa3ae3616}}, // _garg, bići_, _opfo, काः_,
+ {{0x27e7b617,0x69c0b618,0x2bb18076,0x7dd586a5}}, // rinn_, _fome, जासा, _láse,
+ {{0x27e7b619,0x2ca6811f,0xf1b98024,0x69c0b61a}}, // sinn_, _brod_, _pišu_, _gome,
+ {{0x6d49b61b,0x9f478168,0x3f910102,0x6b9c02d0}}, // rbea, ninë_, kezu_, _yarg,
+ {{0x69c08a0f,0x6d4981e4,0xf1b980ce,0x249900ee}}, // _zome, sbea, _višu_, _mssm_,
+ {{0x39ea0ae7,0xdb1c0548,0x44211a29,0x42260193}}, // едно_, _poré, imh_, лдов,
+ {{0x9f4e003e,0x186a11d2,0x7dca03f2,0xceb280be}}, // čník_, нами_, _výsk, _ליב_,
+ {{0xc60f86b7,0xa91d8110,0x6fcb01ec,0xd37880d2}}, // ातीय_, _pažy, _küch, zići_,
+ {{0xaae4803d,0x3a2d81ed,0x66e6361c,0x2d9d867f}}, // اسیو, _liep_, гога, _hawe_,
+ {{0x2d9db61d,0xf7430098,0x66040bf0,0xcb5681c6}}, // _kawe_, сесо, toik, _עסקה_,
+ {{0xd3788052,0x2d9fb61e,0xa01b0019,0xed598088}}, // vići_, ndue_, gtöb, naž_,
+ {{0xd4671289,0x5baa03c7,0xb38607b6,0x6b9c361f}}, // [22e0] лите_, нкам_, ллал, _parg,
+ {{0x69c0b620,0xd378812b,0x3ce68006,0x5eff00c2}}, // _some, tići_, _जइसे_, _शुक्_,
+ {{0x68fb87f4,0x69c0a39f,0x6b9c3621,0x63bc9252}}, // _axud, _pome, _varg, örni,
+ {{0xc693093f,0x6b9c3622,0x4421061f,0x2d9db623}}, // _פאר_, _warg, amh_, _nawe_,
+ {{0x2d922fca,0x17548c40,0x3a2d837a,0xd7e68d8e}}, // meye_, авля, _diep_, ліко,
+ {{0x6fcb02af,0x78a2816b,0x2d9d8032,0x69c0b624}}, // _rück, rvov, _aawe_, _wome,
+ {{0x6442b625,0x24803626,0xb4cc8a27,0xe8d901bc}}, // _stoi, ltim_, रन्_, _ibụ_,
+ {{0x2d922fca,0x225c026f,0xa3d686b7,0x25a03627}}, // neye_, ávku_, _सतह_, ndil_,
+ {{0x98a39e25,0x24803628,0xf74692b2,0x7c2e18d5}}, // _лите, ntim_, _безо, _kibr,
+ {{0x7c2e3629,0x320698b3,0x248002d5,0xdcef0669}}, // _jibr, looy_, itim_, šeće,
+ {{0x3f910052,0x8646b210,0x2d92362a,0x3f9e80b9}}, // vezu_, _снеж, keye_, _iatu_,
+ {{0x2480362b,0x7c2e3437,0xf6518117,0x2d9db62c}}, // ktim_, _libr, _گئے_, _gawe_,
+ {{0x7c2e0013,0x3f91007d,0x3f9e8a36,0x2480362d}}, // _oibr, tezu_, _katu_, jtim_,
+ {{0x3940026c,0x3f9eb62e,0x7d0686ae,0x3959362f}}, // _efis_, _jatu_, üksu, sass_,
+ {{0x6298803b,0xa3ae0697,0x2d9dafc0,0x39590687}}, // _usvo, काई_, _yawe_, pass_,
+ {{0xf99307bd,0x7c2e00f7,0x3f9e80eb,0xe8d90870}}, // _طبع_, _aibr, _latu_, _abụ_,
+ {{0x442e81cd,0x3b5528df,0x4421008e,0x7c2e0f3e}}, // [22f0] _kif_, икар, tmh_, _bibr,
+ {{0x9f47820f,0x3f9eb630,0x7c2e3631,0x3a2d8299}}, // rinë_, _natu_, _cibr, _siep_,
+ {{0x06bf00c8,0x9f4780f1,0xa5bb0825,0x93880084}}, // _উইকি, sinë_, _biót, ыста_,
+ {{0xaca3019d,0x7dd5a6e7,0x9f47b632,0xd37880ce}}, // _agụb, _másc, pinë_, miću_,
+ {{0x3f9e9067,0x6d4d2339,0xd37881dd,0x9f47b543}}, // _batu_, mbaa, liću_, liné_,
+ {{0xdcfd0029,0x7c2e3633,0xf77191cc,0x764500f1}}, // _lasī, _gibr, زات_, _kthy,
+ {{0x45d598a0,0x3f9eaa6b,0xd378812b,0x3a2d8129}}, // ицит, _datu_, niću_, _tiep_,
+ {{0x6d4d02b5,0xb88681d0,0x442eb634,0x4465a341}}, // nbaa, _sníž, _aif_, авив,
+ {{0x851fb635,0x61eb8098,0xed598916,0x3f9eb636}}, // मेंट_, migl, raž_, _fatu_,
+ {{0x2d9dafc0,0x442e80fc,0xa5bb00f7,0x2d923637}}, // _wawe_, _cif_, _fhóg, zeye_,
+ {{0x629e9fc2,0x6d4d05f8,0xd378811f,0x6b600061}}, // _špor, kbaa, jiću_, lágí,
+ {{0xd3788024,0x9f47b638,0x78a9b639,0x61ebb63a}}, // diću_, diné_, _krev, nigl,
+ {{0x9f5c803e,0x20078006,0x6d5b8b99,0x6d4d329a}}, // lová_, ooni_, maua, dbaa,
+ {{0x2007b63b,0xd378812b,0x6d5bb63c,0xbcfb046d}}, // noni_, fiću_, laua, _adéo,
+ {{0xed5a00af,0x9f5c803e,0x2d92362a,0x9f47b63d}}, // ков_, nová_, teye_, giné_,
+ {{0x7bc38125,0x2007a7ce,0x38608289,0x78a9ace1}}, // _honu, honi_, širi_, _orev,
+ {{0x7bc3b63e,0x2007b63f,0x69d60065,0x2d922fca}}, // [2300] _konu, koni_, _ilye, reye_,
+ {{0x2007b640,0x69aa05b3,0x9f5c827f,0x2d923641}}, // joni_, _जोती, ková_, seye_,
+ {{0x7f5c0e99,0xeb97228e,0x3f9eb529,0x24803642}}, // marq, рих_, _ratu_, stim_,
+ {{0x3f9e848f,0x22518efc,0xa96a8b26,0x6d5b8590}}, // _satu_, ázky_, тида_, jaua,
+ {{0x78a9b643,0x7c2e3644,0x386080f7,0xad9b026b}}, // _crev, _tibr, éir_, _akúw,
+ {{0x78a9a39f,0x7f5c1883,0x60000198,0x26170036}}, // _drev, narq, tömy, nçon_,
+ {{0x61ebb645,0x442eb646,0xd9109e29,0xd1768198}}, // bigl, _rif_, _کیش_, _вызы,
+ {{0x3f9e80f6,0x69d9b647,0xa3ae0076,0x69c413df}}, // _watu_, chwe, काक_, _noie,
+ {{0x3f9e8d4c,0x030b809a,0x20078dc4,0x2e3c007b}}, // _tatu_, _सुबह_, boni_, _líf_,
+ {{0x9f5c826f,0xa5bb00f7,0x2007b648,0x7dd58187}}, // bová_, _thóg, coni_, _pásc,
+ {{0x1fb584fa,0xbcfb3649,0x7bc3b64a,0x98a20085}}, // _устр, _adél, _donu, dakı_,
+ {{0xd378803b,0x644601dd,0x6d5bb19f,0x973c8b80}}, // viću_, _otki, caua, _daće,
+ {{0xa3e7000f,0x90980656,0x2b06b64b,0x7dd1364c}}, // _बगल_, авят_, _सुरु_, _såso,
+ {{0xd378a828,0x9f4780d7,0x9217141b,0x628e0fb0}}, // tiću_, tiné_, _धीरज_, _opbo,
+ {{0xc6a41980,0x69d62347,0x799f036e,0x6d4d364d}}, // орти, _flye, _taqw, tbaa,
+ {{0xd3788289,0x69c40102,0xcdd804ae,0x9f479ab3}}, // riću_, _goie, ању_, riné_,
+ {{0xdce6084a,0x6d4d0c11,0xf94a0158,0x2007b64e}}, // [2310] _bakı, rbaa, _געפֿ, yoni_,
+ {{0x6d4d2023,0xd79500f7,0x69c400f3,0x3ceb016f}}, // sbaa, _الدخ, _zoie, _घेणे_,
+ {{0x644607d9,0x69d98234,0x6d4d0cfa,0xfc3f0511}}, // _etki, thwe, pbaa, _trío_,
+ {{0xf993803d,0x7dd8001b,0x9f5c816b,0x69c4002a}}, // تبط_, _píse, vová_, _xoie,
+ {{0xf99f009f,0x2d8b01c0,0x61ebb64f,0x98a5b650}}, // llès_, _ibce_, rigl, силе,
+ {{0x61ebb651,0x9f5c9c18,0x78a98390,0x6aaa8428}}, // sigl, tová_, _vrev, _orff,
+ {{0x44390bfa,0x2007b652,0x6d5b8b17,0xe61f001c}}, // _hus_, roni_, taua, _khô_,
+ {{0x7bc3b653,0x78a9b654,0x44393655,0x2007b656}}, // _sonu, _trev, _kus_, soni_,
+ {{0x7bc3b657,0x61e9b658,0xdce607d9,0x44393659}}, // _ponu, _imel, _yakı, _jus_,
+ {{0x4439146a,0xa3e18df4,0x7bc3b65a,0x395d9393}}, // _mus_, नों_, _qonu, laws_,
+ {{0x443910af,0x6449b65b,0x4431365c,0x2d8b21d5}}, // _lus_, spei, _miz_, _obce_,
+ {{0x4439023e,0xbcfb0032,0x3cfd81c5,0x6449b65d}}, // _ous_, _adém, _txwv_, ppei,
+ {{0x44390069,0x61e98133,0xc884817b,0x53a5802e}}, // _nus_, _mmel, cağı_, _галб,
+ {{0x7bdab3d6,0x6d598072,0x61fb8289,0x248f80b9}}, // chtu, _hewa, _ljul, _ipgm_,
+ {{0x4439365e,0xdb188117,0x6d59b65f,0x7dd5803e}}, // _aus_, _tová, _kewa, _zása,
+ {{0x25bf9ab3,0x443100eb,0xdce602d0,0x44393660}}, // njul_, _aiz_, _sakı, _bus_,
+ {{0x4431173c,0x6d59b661,0x44393662,0x2b580019}}, // [2320] _biz_, _mewa, _cus_, _perc_,
+ {{0x44393663,0x61e9b664,0x6d598890,0xf1a9803d}}, // _dus_, _amel, _lewa, یانه_,
+ {{0x44393665,0x44310073,0x6fd0016d,0x479b03c8}}, // _eus_, _diz_, _läck, _גייס,
+ {{0x26173666,0x200a3667,0x29f88bcf,0xa3a80f12}}, // nçol_, lobi_, rčao_, _खोर_,
+ {{0x1cba030f,0x4fc68c07,0xc7c688cc,0x06e580c8}}, // _صاحب_, йска, йски, _পুলি,
+ {{0x161c023c,0x61e9b668,0x43751bdc,0x29f8826c}}, // _भीतर_, _emel, _муст, pčao_,
+ {{0x44393669,0x6d59b3fc,0xe0df01e8,0x2fc582d4}}, // _zus_, _bewa, zzò_, _dolg_,
+ {{0x443901e9,0x6d5980a4,0x6fd012d2,0x7dd58333}}, // _yus_, _cewa, _bäck, _pása,
+ {{0x6d598812,0x443901e9,0xbcfb026b,0xea010129}}, // _dewa, _xus_, _adéj, _quậy_,
+ {{0x6fd00106,0x9b588073,0x4431366a,0x249d8176}}, // _däck, шиот_, _xiz_, _dswm_,
+ {{0x66098fb0,0x51f900e8,0x200a328b,0x91fd01a9}}, // boek, иною_, dobi_, krāt,
+ {{0x6d59b66b,0x7bda89be,0xf2d300be,0x386081a1}}, // _gewa, shtu, פער_, širu_,
+ {{0x6e3ab66c,0xb4db046d,0xb87b01d6,0x63bca51d}}, // _hutb, _afàn, dzíc, örns,
+ {{0xa3a809a3,0x63ad80fc,0x3fe70264,0x03a38162}}, // _खोल_, _ɗank, পক্ষ, _ничо,
+ {{0x4439366d,0xd35b8bea,0x48e90074,0xe2468250}}, // _sus_, _גדול, _चेरो_, _اختي,
+ {{0x44310182,0x4439366e,0xdb0a823e,0x395d8282}}, // _siz_, _pus_, _infà, xaws_,
+ {{0xf1f800d5,0x71f80829,0x320b009a,0x44390069}}, // [2330] _دعوت_, _دروس_, mocy_, _qus_,
+ {{0x66098a0f,0x6fd002af,0x200a010c,0x4439366f}}, // zoek, _näch, cobi_, _vus_,
+ {{0x6fb5003d,0x443101d0,0xe5350150,0x4439003d}}, // _همکا, _viz_, _мень, _wus_,
+ {{0x4439146a,0x7dd10022,0x6fcb0214,0x3f8c985b}}, // _tus_, _måsk, _vücu, _abdu_,
+ {{0x7dca027f,0x44390006,0x6d5f3670,0x9ccb01bb}}, // _výst, _uus_, maqa, _сына_,
+ {{0x644d0d17,0x6d5989ca,0x6fd000f2,0x6e3a8122}}, // mpai, _sewa, _räck, _butb,
+ {{0x2fc58b3c,0x7ac40056,0x6d599295,0x644d3671}}, // _volg_, _есте, _pewa, lpai,
+ {{0xa2d786ab,0x61e9b672,0x1c459017,0x59f98009}}, // मनस्, _umel, оним, _тебя_,
+ {{0x644d3673,0x22478b99,0x6609b674,0x386081a8}}, // npai, _stnk_, roek, áirt_,
+ {{0x7ff78277,0xdb1c12da,0xd2658ada,0x660981b0}}, // _اسرا, _horá, окой, soek,
+ {{0x62853675,0x6d598562,0xdb1c2792,0x78ad3676}}, // ntho, _tewa, _korá, _arav,
+ {{0x6fd0016d,0x7bc73677,0x249d8282,0x62850428}}, // _täck, _loju, _tswm_, itho,
+ {{0x200a046d,0x394d0e15,0x62850362,0xb915826b}}, // tobi_, ñesa_, htho, _agbẹ_,
+ {{0x34b283f8,0x2329b678,0x78ad011a,0xdbf18085}}, // _آموز, _коли_, _drav, dəçi,
+ {{0x200a3679,0x6285367a,0x443fb67b,0x61ef1c33}}, // robi_, jtho, lqu_, bicl,
+ {{0x63a3b67c,0x69dd0352,0x7dd58065,0xdd920b76}}, // _hann, chse, _máso, بور_,
+ {{0x63a3b67d,0x6285031d,0x13ea0048,0x7bc7367e}}, // [2340] _kann, etho, амай_, _boju,
+ {{0x63a3b67f,0xad9b3680,0xa3e5103e,0x63bb0326}}, // _jann, _skút, बोल_, _ɗung,
+ {{0x63a3b681,0x3f9804b6,0xe29a2005,0x6d5f3670}}, // _mann, meru_, рае_, baqa,
+ {{0x63a3b682,0x63a50014,0xdb1c15a0,0x29f880c3}}, // _lann, adhn, _corá, pčam_,
+ {{0x92bc0a49,0xaca3819d,0x91a00129,0x7bc70032}}, // েছে_, _blọk, _chỉ_, _foju,
+ {{0x63a3b683,0x6d441f50,0x66163684,0x3f983685}}, // _nann, lcia, llyk, neru_,
+ {{0xdb1c01a8,0x62853686,0xd90d8019,0x9f9d026b}}, // _forá, ctho, نیہ_, _dìè_,
+ {{0x6d442d2a,0x26c08052,0x39428006,0x63a38c11}}, // ncia, ćio_, _üks_, _aann,
+ {{0x63a3b687,0x2249b688,0x6d443689,0x3f98368a}}, // _bann, _čaka_, icia, keru_,
+ {{0x69c28b3c,0x3f9800ce,0x63a39e68,0x7dd112c3}}, // ljoe, jeru_, _cann, _påsk,
+ {{0x61ef368b,0x1fb5b68c,0x69dd0168,0x2905080a}}, // ticl, _эстр, thse, ğlam_,
+ {{0x78ad368d,0x51f80190,0x63a3998d,0x6d44368e}}, // _prav, жнюю_, _eann, jcia,
+ {{0x63a3b68f,0x7dd59c18,0x3f983345,0xa3c3816f}}, // _fann, _záso, feru_, _एकच_,
+ {{0xa3ce81b6,0x3f983690,0x6560b691,0x7ddc80e7}}, // _रवि_, geru_, lamh, _dése,
+ {{0x9f340163,0xbcfb03a7,0x66163692,0x6d5f10ba}}, // дері, _idéi, flyk, taqa,
+ {{0x7dd5803e,0x78ad3693,0x63a390ed,0x24408247}}, // _násl, _trav, _zann, _kòm_,
+ {{0x63a3b694,0xad9b01ac,0x78ad3695,0x3f983696}}, // [2350] _yann, _skús, _urav, beru_,
+ {{0x6d5f3697,0x644d0f31,0xdb1c0019,0x57f40198}}, // saqa, rpai, _sorá, _опыт,
+ {{0x644d3698,0xdd910013,0x61ed007a,0x5a35046e}}, // spai, _كود_, _kmal, чнет,
+ {{0x62850347,0x6d440098,0xfe9a00be,0xf1bf3699}}, // rtho, ccia, _פירמ, zmán_,
+ {{0x6d5d00eb,0x61ed0135,0x6285369a,0x660d09c4}}, // _iesa, _mmal, stho, moak,
+ {{0x6d5d369b,0x1da39513,0x940500d7,0x7dd5816b}}, // _hesa, _खोजत, _روزه, _páso,
+ {{0x63a3b69c,0x6d5d369d,0x7c3d3644,0xdb1c0207}}, // _rann, _kesa, _husr, _torá,
+ {{0x63a3b69e,0x3f98369f,0x6d5d2a81,0xbca580f7}}, // _sann, zeru_, _jesa, كمبي,
+ {{0x6d5d36a0,0x63a3b6a1,0xb4d58816,0x6e22b6a2}}, // _mesa, _pann, सने_, _dhob,
+ {{0x7ddc83d3,0x61ed36a3,0x443fb6a4,0x6d5d2890}}, // _rése, _amal, rqu_, _lesa,
+ {{0x63a3b6a5,0x443f82be,0xe8e10028,0x3f9810e8}}, // _vann, squ_, _đều_, veru_,
+ {{0x63a3b6a6,0x6d5d36a7,0xdb1c013c,0x3f9836a8}}, // _wann, _nesa, _foræ, weru_,
+ {{0x7c3d36a9,0x660d0359,0x63a3b6aa,0x6e298aa2}}, // _nusr, doak, _tann, mmeb,
+ {{0xf8a583f8,0x2bbb8307,0x69db9fd6,0x63a390ac}}, // _یک_, _خاصة_, _llue, _uann,
+ {{0x6d5d1a67,0x7c3d02af,0xa3b52342,0xaca40133}}, // _besa, _ausr, झाई_, _mpụt,
+ {{0xf06636ab,0x1c46268a,0x7c3501a9,0x29f88140}}, // _екип, знам, _aizr, rčak_,
+ {{0x6d5d36ac,0xdb1c00f1,0x3f9836ad,0x7c3d008e}}, // [2360] _desa, _korç, peru_, _cusr,
+ {{0x6d4436ae,0x69db8364,0xe81b86a7,0x66160a64}}, // scia, _alue, _पीला_, slyk,
+ {{0x69c28901,0x8c1a8039,0xe29723d7,0x160b8beb}}, // tjoe, צועי, зах_, हकार_,
+ {{0x6d5d36af,0xf3f1aae3,0xaca40135,0x236a0122}}, // _gesa, hị_, _apụt, _wdbj_,
+ {{0xf5370051,0x2d82016d,0x6e2982f1,0xb4d586ae}}, // _מרכז_, _icke_, dmeb, सनो_,
+ {{0x56949269,0x6e949c8b,0xbcfb0091,0x2440b6b0}}, // дакт, дику, _adéw, _ròm_,
+ {{0xcf268013,0x6e22b6b1,0x69dba347,0x60168187}}, // أرشي, _phob, _flue, nâmi,
+ {{0xe9a39273,0x21a390ca,0x7c3d0359,0xade380e8}}, // _пасп, _писм, _yusr, ецьк,
+ {{0xdb1c007e,0x28168061,0xdcebb6b2,0x6560b6b3}}, // _borç, _فورس, _bagı, ramh,
+ {{0x46a68554,0x6560b6b4,0x61ed0039,0xa01b0009}}, // _назв, samh, _smal, ttöm,
+ {{0xe9df0013,0x6e2289ef,0x9f5c81d6,0xbeaa80d7}}, // thú_, _thob, kovú_, _دهان_,
+ {{0x39469313,0xdb03803d,0x26170214,0x7d028102}}, // ncos_, _manè, rçok_, _txos,
+ {{0xdb1c2670,0x39468c52,0x7dd801d0,0x9ac481b9}}, // _forç, icos_, _písn, _suċċ,
+ {{0x6d5d355e,0x92578077,0xdc378158,0xa5bb36b5}}, // _sesa, _کشور_, _זאגט_, _chón,
+ {{0x6d5d36b6,0x7c3d003a,0x9f4a0388,0x660d36b7}}, // _pesa, _susr, cibí_, toak,
+ {{0x61ed3496,0x442303ac,0x42fb8039,0xe1ab8beb}}, // _umal, _shj_, _להוס, _घोंघ,
+ {{0x394936b8,0x660d36b9,0x7bcab6ba,0x7bdc05ee}}, // [2370] _ifas_, roak, _kofu, _blru,
+ {{0x3f8104cd,0x6e991b2f,0x3946b6bb,0xdd9985b9}}, // _uchu_, _двор_, ecos_, zeň_,
+ {{0xaadc85b3,0x386936bc,0xdb580198,0xdd0c0035}}, // मनिक, _hvar_, яют_, góło,
+ {{0xd13191cc,0x61fd36bd,0x38690687,0x23c8801b}}, // نما_, nnsl, _kvar_, ाउँद,
+ {{0xdd99803e,0x8fa587ff,0x92bd00ab,0x07a5b6ab}}, // veň_, дале, ইনে_, далн,
+ {{0x25a92b24,0xb4d903db,0x7bcab14f,0x443d8bfd}}, // ldal_, ़नी_, _nofu, _ruw_,
+ {{0x24890065,0x9f478216,0x6446808b,0x5692aba7}}, // ltam_, giná_, íkin, вајт,
+ {{0x96270341,0x5334013a,0x25a936be,0x25a68c41}}, // _uzņē, _чест, ndal_, _caol_,
+ {{0x248936bf,0xf1bf1bd9,0x7dd5807b,0xea0000ff}}, // ntam_, rmál_, _hásk, _ngại_,
+ {{0x63a72ce4,0x320f8051,0xdd99826f,0x394936c0}}, // _jajn, logy_, seň_, _afas_,
+ {{0x25a936c1,0xdd99816b,0x3ed58f99,0x395f81ed}}, // kdal_, peň_, _تقار, _keus_,
+ {{0x77638a2b,0x25a68c41,0x67219351,0xaca3026b}}, // lanx, _gaol_, _izlj, _afọb,
+ {{0x7dd5803e,0x395fb6c2,0xb87b0019,0xa5bb001c}}, // _lásk, _meus_, szín, _phón,
+ {{0x63a736c3,0x0c24835f,0x7dd800e1,0x24890019}}, // _najn, _змін, _píso, dtam_,
+ {{0x16a70381,0xe73715fe,0x386906ab,0xe7e58074}}, // мври_, дер_, _evar_, _कतना_,
+ {{0x200eb6c4,0xcb128039,0x601683a7,0x26138187}}, // zofi_, _שלה_, râmi, mãos_,
+ {{0x224d003b,0x39a681e2,0x63a70019,0xdb038077}}, // [2380] _čeka_, _jūsų_, _bajn, _sanè,
+ {{0x39a681e2,0x6721b6c5,0xafdb0aa2,0x78a436c6}}, // _mūsų_, _ozlj, spør, _isiv,
+ {{0xe72a198c,0x395f8580,0xea010129,0x7ddc810c}}, // _фонд_, _beus_, _quầy_, _lésa,
+ {{0xfc3f0216,0x395f8362,0xbcfb0580,0xa01b062c}}, // _fuí_, _ceus_, _adéu, ytök,
+ {{0x395fb6c7,0x5fd20063,0x63a7009a,0x18678991}}, // _deus_, _हवाल, _fajn, дачи_,
+ {{0x25a680f7,0x63a736c8,0xb03380e8,0x2cad99dc}}, // _saol_, _gajn, вніш, dved_,
+ {{0xc7b88028,0xdb1c016d,0x69d880f1,0x61fd2496}}, // _vnđ_, _borå, ëves, ynsl,
+ {{0x746a8153,0x443836c9,0x395fb6ca,0x200eb6cb}}, // оров_, _hir_, _geus_, sofi_,
+ {{0x61e2b6cc,0x6f0d8380,0xd49a804a,0x6fd487f1}}, // mhol, ğaca, іри_, _làci,
+ {{0x5f76845b,0x1b2100ab,0x61e28a33,0x7ddc80e7}}, // _زائر, _বলতে_, lhol, _désa,
+ {{0x38690082,0x443836cd,0x33770039,0xc7b384de}}, // _svar_, _mir_, ועים_, _שבע_,
+ {{0xc333078d,0x61e2835f,0x443836ce,0xdd8f80f7}}, // כות_, nhol, _lir_, _نوم_,
+ {{0x7dd104b8,0xdd8f8bca,0x2ca00239,0x443801e4}}, // _måst, _خون_, jwid_, _oir_,
+ {{0x44380609,0x7dd103a6,0x61e2b6cf,0x7522b6d0}}, // _nir_, _låst, hhol, _izoz,
+ {{0x38c5020f,0x61e2805d,0xe61300f7,0x63a736d1}}, // _bërë_, khol, نشر_, _rajn,
+ {{0xe81f058c,0x2d8036d2,0x443836d3,0x24890019}}, // _मीरा_, ggie_, _air_, ttam_,
+ {{0x443836d4,0x61e2813c,0x395f809f,0xb4d9009a}}, // [2390] _bir_, dhol, _reus_, ़ने_,
+ {{0x395f907f,0x3f9c8fda,0x7dd5b6d5,0xd838886f}}, // _seus_, jevu_, _pásk, _nič_,
+ {{0x443836d6,0x248936d7,0xe1ff1726,0x395f823e}}, // _dir_, stam_, onó_, _peus_,
+ {{0x7c38b6d8,0x201136d9,0x443836da,0x660296f2}}, // _livr, lozi_, _eir_, _njok,
+ {{0x7b67025d,0x63a7003a,0x386980f7,0x395f8722}}, // _отве, _tajn, éar_, _veus_,
+ {{0x187c0039,0x4438035f,0x7c38826c,0x48e080ab}}, // קטוב, _gir_, _nivr, বপূর,
+ {{0x776384c3,0x395fb6db,0x260b86bf,0xa3a80075}}, // ranx, _teus_, ावली_, _खोट_,
+ {{0x61e2b6dc,0x442c8fb0,0xd5b18872,0xdcef08c5}}, // chol, emd_, افظ_, _hacı,
+ {{0x201120d7,0xd83881a1,0x6012006a,0x316036dd}}, // kozi_, _fič_, kæmp, _reiz_,
+ {{0x7dd58019,0x2cad8039,0x3f9c8699,0x9f9d026b}}, // _mási, rved_, cevu_, _bìí_,
+ {{0x656f2158,0xd24e8416,0x7dd8209c,0xb81b8035}}, // _odch, ینی_, _sísm, _पीएम_,
+ {{0x7dd8026f,0x66028168,0x644f05f3,0x69c6022c}}, // _písm, _gjok, _otci, ujke,
+ {{0x764197ea,0x7dd5816b,0x65640009,0x26170187}}, // _huly, _nási, vaih, nçou_,
+ {{0x201136de,0x661ba818,0x6d49b6df,0x60c4956e}}, // gozi_, lluk, lcea, äimi,
+ {{0x442782af,0x66e61bc1,0xdee60b9c,0x443836e0}}, // _ihn_, хова, хови, _rir_,
+ {{0x7dd59313,0xa3a80fea,0x7641b6e1,0xd24e80f7}}, // _bási, _खोज_, _muly, _اني_,
+ {{0x20110578,0x656436e2,0xfce3023f,0x91fd00eb}}, // [23a0] bozi_, raih, гото, ksāj,
+ {{0x9f9d0324,0xfc3f00f7,0x661b829b,0x085704de}}, // _yìí_, _arís_, hluk, _רבים_,
+ {{0x44382674,0x7dd10bfa,0xdb1c01ac,0x442782f7}}, // _vir_, _påst, _horú, _mhn_,
+ {{0x443836e3,0x3f85b6e4,0xceb302f6,0x2a6a8019}}, // _wir_, _aclu_, _עיר_, ábbi_,
+ {{0x443806f6,0x61e2805f,0xdb1886c4,0x3f9cb6e5}}, // _tir_, rhol, _envè, tevu_,
+ {{0x6602b6e6,0x7641b6e7,0x442c06c0,0x998d81d6}}, // _sjok, _buly, _òd_, _vieš_,
+ {{0xfc031f25,0x7dd5826f,0x6996b6e8,0x3f9cb6e9}}, // _спро, _zási, _прах, revu_,
+ {{0x661baf08,0x291e009a,0x3f9c8267,0x69d98252}}, // gluk, ętaj_, sevu_, ckwe,
+ {{0x09e38e02,0xb4da8105,0x7bce36ea,0xdb188187}}, // _сотн, ठने_, _kobu, _invé,
+ {{0x7bce0087,0x8afe89ab,0x236580c3,0x3ea9866f}}, // _jobu, _leƙe, galj_, łata_,
+ {{0x7c3880e7,0x76419fce,0x7bce36eb,0x442c80f3}}, // _vivr, _guly, _mobu, rmd_,
+ {{0x7982b6ec,0x225c003e,0x6136880a,0x7bce0234}}, // ngow, ávky_, _gülü, _lobu,
+ {{0x20110423,0x23658904,0xe3b2803d,0x799d1e26}}, // tozi_, balj_, _مرغ_, resw,
+ {{0xb8c90d38,0x644fa6e1,0x2ca68114,0x799d2e4d}}, // _गई_, ície, _isod_, sesw,
+ {{0x201100b4,0x7a3e8084,0xe28f81a8,0x6e942f7d}}, // rozi_, _būte, _هذي_, лиру,
+ {{0x63aaa8b8,0x201136ed,0x34952f02,0x2365026c}}, // _hafn, sozi_, кабр, _žlj_,
+ {{0x7dd836ee,0x7bce36ef,0xbcfb046d,0x3a29033e}}, // [23b0] _získ, _bobu, _adér, _ihap_,
+ {{0x63aa8125,0xdce0b6f0,0x6442b6f1,0x7bce0c4d}}, // _jafn, jamč, _muoi, _cobu,
+ {{0xa0360039,0xbb8401a8,0x29d782d0,0x9f8f02f1}}, // _שאתה_, المي, _uçak_, _müü_,
+ {{0x6562b6f2,0xcb1380be,0x9f58007b,0x7ae582f1}}, // _neoh, אלע_, lorð_, _ühte,
+ {{0xf9940039,0x7641b6f3,0x69d99699,0x661b817f}}, // טרף_, _suly, rkwe, vluk,
+ {{0x69d9b6f4,0x6298837a,0x63aa807b,0x3f83008b}}, // skwe, _opvo, _nafn, ngju_,
+ {{0x23658390,0x628b0493,0x5f05847f,0x9f47862c}}, // valj_, ăgos, ъзка, vinä_,
+ {{0xceb40158,0x7ddc82be,0x7bce36f5,0x9f5c816b}}, // ריק_, _déso, _zobu, mový_,
+ {{0x661b8867,0x2365b55d,0xa5bb0c52,0x9f5c826f}}, // rluk, talj_, _dióx, lový_,
+ {{0x6d4980f7,0x1a65815b,0xe50203eb,0x9aa40019}}, // scea, نیتی_, लपति_, _جمہو,
+ {{0x386db20d,0x9f5c826f,0xb17b0aa2,0x261726e1}}, // _hver_, nový_, rhån, nços_,
+ {{0x999880eb,0x3a2936f6,0x186a0a7c,0x2365817f}}, // _kurš_, _chap_, мами_, salj_,
+ {{0x4427810b,0x9f5c816b,0x2365817f,0x9f9d026b}}, // _thn_, hový_, palj_, _bìà_,
+ {{0xb4bf835a,0x9f5c83fb,0x29f896f2,0xb4cd816f}}, // ीही_, kový_, nčar_, रही_,
+ {{0xfaa5a64e,0x9f5c816b,0x248db6f7,0x91a000ff}}, // _райо, jový_, ltem_, _thì_,
+ {{0x386d8051,0x9f5c826f,0x98be0214,0x63bc36f8}}, // _over_, dový_, ştı_, _snrn,
+ {{0xd4670381,0x248db6f9,0x9f58008b,0xb38622d0}}, // [23c0] ките_, ntem_, borð_, клал,
+ {{0x7dd836fa,0xeafa0250,0x248db6fb,0x2d9f8a53}}, // _mísi, ورات_, item_, heue_,
+ {{0x386d8098,0xdb1c077f,0x66e636fc,0xb4cd8074}}, // _aver_, _yorù, _рона, रहु_,
+ {{0x7ddcb6fd,0xdb18803e,0xe9da80e8,0x248db6fe}}, // _réso, _nový, ьке_, ktem_,
+ {{0x80be8540,0xe8949663,0xe0e080ab,0x7bce36ff}}, // ्हें, уаль, বপ্ন, _tobu,
+ {{0xdfcf8624,0x248db700,0x6562a6ac,0x9f5c8a21}}, // يين_, dtem_, _seoh, bový_,
+ {{0x6fdd809f,0x386d8039,0x2480051e,0x973c8b80}}, // _tècn, _ever_, luim_, _jaći,
+ {{0x5f168076,0x752601c0,0x25a0051e,0x442a008e}}, // _पुण्_, _kzkz, neil_, _hhb_,
+ {{0xc5f30051,0x6442838f,0xdb01865d,0x3a2902c4}}, // ידה_, _quoi, ndlæ, _rhap_,
+ {{0x32020038,0x7c3c04b7,0x660db701,0x25a03702}}, // enky_, _jirr, čaka, heil_,
+ {{0xe9f921d2,0x0d8682a9,0x5a351508,0x37ba00ab}}, // енні_, _ален, лнот, ুসার,
+ {{0xcb1304de,0x973c80fe,0x39400db1,0x91a0027d}}, // ילת_, _naći, _cgis_, _phí_,
+ {{0x386d3703,0x6abf016f,0x248d87f1,0x9f5c816b}}, // éer_, _एप्र, ctem_, zový_,
+ {{0x24803704,0x7c3c3705,0xdb1c03ba,0xd7c900d7}}, // duim_, _nirr, _forø, روزه_,
+ {{0x88cb00ab,0xbcfb046d,0x973c8140,0x1af080ab}}, // _লেখক, _adép, _baći, _ঘুরে_,
+ {{0x32538098,0x9f5c826f,0x25a001e4,0x7c3c0362}}, // _твър, vový_, geil_, _airr,
+ {{0x80be8063,0x443cb706,0x7c3c3707,0x38600428}}, // [23d0] ्हों, _kiv_, _birr, _gwir_,
+ {{0x9f5c803e,0x7c3c3708,0x6fdd8722,0x442a01a1}}, // tový_, _cirr, _rèco, _chb_,
+ {{0x443cb709,0x20031487,0x7dd5816b,0x248d8019}}, // _miv_, mnji_, _zásu, ztem_,
+ {{0xe7e581fe,0x443ca253,0x261707e0,0x973c81dd}}, // कसभा_, _liv_, rços_, _gaći,
+ {{0x7dd5b70a,0x9f5c826f,0xdfd58d55,0x851e8054}}, // _mást, sový_, лоды, _परगट_,
+ {{0x443cb70b,0x7dd581ca,0x6e3d00fc,0x9f5c84e8}}, // _niv_, _lást, _hisb, pový_,
+ {{0x6e3d370c,0x4e9580f7,0xf1a7828b,0x7645008e}}, // _kisb, اشتر, _иран, _juhy,
+ {{0x7dd5803e,0x248d8065,0x26c9805c,0x069680f7}}, // _nást, ttem_, ćao_, انية_,
+ {{0x6e3d370d,0x31348596,0xb17b00f2,0xa5bb0013}}, // _misb, _ветр, rhål, _chói,
+ {{0x7dd80125,0x200302a5,0x248db70e,0xb17b016d}}, // _vísi, jnji_, rtem_, shål,
+ {{0x7d1d00f2,0x1603015c,0x17f880f7,0xdb03b70f}}, // _lyss, रचार_, _شركة_, _taní,
+ {{0x64b592dc,0x6e3d3710,0x2b4e8326,0x61f982c4}}, // _محتر, _nisb, _effc_, niwl,
+ {{0x6d5b9a67,0x443cb711,0x25a01aab,0x6fd4823e}}, // mbua, _fiv_, veil_, _tàct,
+ {{0x443c813c,0x291e00eb,0x2a6c8101,0x75fe80eb}}, // _giv_, āta_, bsdb_, vīzi,
+ {{0x66e31ad2,0x25a03712,0x6e3d3713,0xdee33714}}, // бора, teil_, _bisb, бори,
+ {{0x64a60972,0x443c82a5,0x7dd58207,0x7c3c1037}}, // лаба, _ziv_, _gást, _sirr,
+ {{0x29050459,0xa3d78768,0x25a03715,0xb4de3716}}, // [23e0] şlar_, _सवा_, reil_, तने_,
+ {{0x7dd585b9,0xfc3f00f7,0x25a001e4,0x24803717}}, // _zást, _tsín_, seil_, ruim_,
+ {{0xdb0a8980,0x764509c4,0x7c3c3718,0x1bf683de}}, // _cafè, _guhy, _virr, נצער_,
+ {{0x00e69508,0x7cda01a9,0xaa570290,0x067b03de}}, // лжен, _pārā, النا_, _אנטל,
+ {{0xb4cd8076,0x66043719,0xd9178160,0x7c3c01b9}}, // रहो_, mnik, льш_, _tirr,
+ {{0x6604371a,0x6281b71b,0x63a182a5,0xeeaa8e8e}}, // lnik, kulo, jeln, етик_,
+ {{0x63a1b71c,0x69dd0009,0xdb01816d,0x656615bd}}, // deln, ykse, ndlä, _kekh,
+ {{0x443c90af,0x5c749537,0x6604371d,0x6e3d02a3}}, // _siv_, алит, nnik, _xisb,
+ {{0x69c08511,0x443c81c5,0xa3e8023c,0x600a8ae7}}, // _inme, _piv_, _बता_, _знам_,
+ {{0xda7ab71e,0x644601d3,0x644fa363,0x61e4371f}}, // ням_, _muki, ícia, _alil,
+ {{0xd00f803f,0x66043065,0x443cb720,0x64463721}}, // _علم_, knik, _viv_, _luki,
+ {{0x69dd0364,0x15469445,0xa01b0009,0xe1350198}}, // ukse, _седм, ytös, инны,
+ {{0x20030024,0x443c81c5,0x644604b9,0x5c568139}}, // tnji_, _tiv_, _nuki, _стеф,
+ {{0x6281b722,0x53348adb,0x7d1d3723,0x3cff0110}}, // bulo, _келт, _ryss, _žuvo_,
+ {{0x6281b724,0xd5bb3725,0x64463726,0x9f3500e8}}, // culo, нса_, _auki, реві,
+ {{0x20030052,0x5d551182,0x7d1d1a50,0x660407f6}}, // snji_, ркат, _pyss, gnik,
+ {{0xd37b1860,0x6e3d03ab,0xf99000f7,0x8f55003d}}, // [23f0] ече_, _visb, _ربي_, _پنجش,
+ {{0x69c0b727,0x644604b9,0x6d4d3728,0x926b2133}}, // _anme, _duki, scaa, ерда_,
+ {{0x66040db7,0xdb038e93,0x63ae0282,0x973c990f}}, // bnik, _kaná, _dabn, _kaću,
+ {{0xdb0ab729,0x6d42801c,0x5fc32bef,0x629c372a}}, // _café, _ngoa, शावल, _dpro,
+ {{0x79c989a7,0x60168087,0x601f8187,0x644600b4}}, // _یوسف_, tâmp, lêmi, _guki,
+ {{0x629c002a,0x3d18800f,0x69c0b72b,0x244981fa}}, // _fpro, _पड़े_, _enme, _rúm_,
+ {{0x6d5b8201,0x64460267,0xd3440019,0x0f5801c6}}, // tbua, _zuki, _اہمی, ניהם_,
+ {{0x660d8353,0x9f580168,0x78a9822c,0x9f4100e1}}, // čako, tirë_, _tsev, _dlhé_,
+ {{0x63a1b72c,0x6d5bb72d,0xa787815b,0x78a9b72e}}, // teln, rbua, _مشاو, _usev,
+ {{0x6281b72f,0xdb18840e,0x66043730,0x6619861b}}, // tulo, _enví, znik, _wkwk,
+ {{0x4fc4a344,0xdb038c83,0x212900b9,0x3b098ae7}}, // _уста, _baná, _azah_, тено_,
+ {{0x63a1b731,0x66040085,0xd9f780d4,0x601f8187}}, // seln, xnik, ूचित_, dêmi,
+ {{0x7dd8000d,0x63a1b732,0x628182e8,0x3d04009a}}, // _míst, peln, sulo, _रखें_,
+ {{0x66040063,0x64462fc0,0x7dd8026f,0xdb0380f7}}, // wnik, _ruki, _líst, _eaná,
+ {{0x316b0063,0x09e62e7b,0x656600b9,0xdb070144}}, // bacz_, _войн, _pekh, _bají,
+ {{0xe9df01ac,0x260b897d,0x290d8118,0xdb038216}}, // ckú_, ावटी_, _oxea_, _ganá,
+
+ {{0xab66045d,0xf1bf0c83,0xa01b1e2b,0xddd880d2}}, // [2400] ивал, rmát_, rtör, nuvš,
+ {{0x66040668,0x6446042b,0x629c01b9,0x68200019}}, // snik, _vuki, _ppro, ködi,
+ {{0x66043733,0x65660101,0xb1468dc0,0x877b025f}}, // pnik, _tekh, анел, _באיי,
+ {{0x20183734,0x64463735,0xfc48801c,0x2d9606f1}}, // lori_, _tuki, _lửa_, _трос,
+ {{0x4fc691b3,0x629c009a,0x67450177,0x9f513736}}, // иска, _wpro, _átjá, lizá_,
+ {{0xdb07000d,0x20183737,0xfc48801c,0x63bb00fc}}, // _zají, nori_, _nửa_, _ɗunk,
+ {{0x46678fe6,0x7ddc80e7,0xb76580e8,0xa5f888ed}}, // арым_, _hési, стій, леку_,
+ {{0xd5b79fc8,0x20180114,0x9fa3808b,0x7dd58333}}, // ась_, hori_, síðu_, _básq,
+ {{0xad9b007b,0x644000e7,0x22478144,0x5f16b738}}, // _sjúk, _émis, _aunk_, _पुर्_,
+ {{0xfc488028,0x20180ff5,0x6609005c,0x60e8880a}}, // _cửa_, jori_, čeki, ırmı,
+ {{0xb4db0028,0x20183739,0x91fd00eb,0xa6ca80f7}}, // _ngàn, dori_, ksāt, _جوال_,
+ {{0x7dd807d0,0xd49ab73a,0xdb1c0fba,0x05838d9e}}, // _míss, _арк_, _horó, _душм,
+ {{0xfbcf87d2,0x7648b73b,0x245481ad,0x291f81c0}}, // ستی_, _hudy, _انتس, _xyua_,
+ {{0xb7f88aed,0xd7fb0a3d,0x7a2382f1,0xe9df01d6}}, // ्चिम_, тум_, võtm, skú_,
+ {{0xdb038019,0x62351182,0xdb1c349a,0x5f1a0fb2}}, // _taná, _леку, _moró, _मुद्_,
+ {{0x3c2b83ba,0xdb188580,0x5d78062c,0x2367b73c}}, // røve_, _invà, ийся_, _zenj_,
+ {{0x20180503,0x22a80214,0x44210f3e,0x409500f7}}, // [2410] bori_, _aşk_, tlh_, _الخر,
+ {{0x7ddc82be,0x3a3f808e,0x2018373d,0xdb0e003d}}, // _dési, _siup_, cori_, _kabè,
+ {{0x24848122,0xa5bb01a8,0x442989ab,0x3a2d8129}}, // humm_, _bhót, _ɗa_, _phep_,
+ {{0x29d78380,0x32191122,0xf54f019d,0xe1f0ab7e}}, // _açar_, nosy_, _rụwa_, وسن_,
+ {{0x7dd812ca,0xdce28088,0xe80e03b7,0x245d026b}}, // _víst, _beoč, _सदमा_, _dámò_,
+ {{0xf77185ff,0x656d373e,0xab8411b3,0x7648809a}}, // سات_, laah, _муск, _budy,
+ {{0x3a3f8359,0x212981ac,0xfc4880ff,0x61fd373f}}, // _tiup_, ťah_, _rửa_, kisl,
+ {{0xfc488028,0x61fd3740,0x798d0069,0x644fb741}}, // _sửa_, jisl, _ncaw, ício,
+ {{0x442e8267,0x628515d0,0x20183742,0x22400074}}, // _bhf_, luho, yori_, _riik_,
+ {{0x53c982de,0x63a502d4,0x656d3743,0x7dd58216}}, // угом_, nehn, haah, _vásq,
+ {{0xa96a013a,0x216a17c8,0xae7880e8,0x656d3744}}, // лина_, лини_, _всіх_, kaah,
+ {{0xdb038073,0x656d0079,0x442e807b,0x6fdd8036}}, // _lanç, jaah, _ehf_, _sèch,
+ {{0xeb9998a0,0x63c281d0,0x799bb745,0x91ba81c6}}, // _бил_, ávní, _mbuw, אמרי,
+ {{0x7ddc82be,0xd91a8039,0xb4e39344,0x7648808e}}, // _rési, רושל, ननी_, _yudy,
+ {{0x799bb746,0x20183747,0x6729000d,0xe2998162}}, // _obuw, rori_, řejn, _сай_,
+ {{0xfbdf001c,0xbb3a80be,0x7ddca848,0xa2c4852a}}, // _quên_, _געצי, _pési, िमर्,
+ {{0xbddb00e7,0xfd6481bc,0x7bd50214,0xdb18b748}}, // [2420] _sièc, _agwụ, _bozu, _invá,
+ {{0xbddb03d3,0xdb0391dd,0xdb0e3749,0x9f860cf9}}, // _pièc, _canç, _habé, _угод,
+ {{0xfe721168,0xb87b0065,0xe5a6134e,0x69c41254}}, // _عدد_, szít, _лини, _knie,
+ {{0x69d6374a,0xed360162,0xc8b50065,0x3eb809d1}}, // _joye, _гэгэ, _الیک, mvrt_,
+ {{0x69d602be,0x7bc3838a,0xdb1c374b,0x69c4009a}}, // _moye, _onnu, _poró, _mnie,
+ {{0x6e241a5a,0xa09b010f,0x6285374c,0x2bc9374d}}, // llib, ריסט, buho, राणा,
+ {{0x2291b74e,0x3eb8007a,0x78bb8144,0x7ddc87f1}}, // ják_, nvrt_, _eruv, _mésv,
+ {{0x7bc3b74f,0x5454b750,0x61fd04c3,0xfbc90ebf}}, // _annu, овит, xisl, राथम,
+ {{0x61fd003e,0xdce28267,0xf1c3801b,0x61e28e3a}}, // visl, _teoč, šší_, mkol,
+ {{0xb274b751,0x248001a8,0xf54f01bc,0xd4978a14}}, // олош, irim_, _hụta_, ёры_,
+ {{0x61fd0038,0x24803752,0x441b00be,0x69d63753}}, // tisl, hrim_, רויס, _boye,
+ {{0x14c83754,0x7bc3b755,0x61e2b756,0xd46a80a9}}, // रमाण, _ennu, nkol, _биде_,
+ {{0xdca3b714,0xf54f0135,0xdce60609,0x682d81d6}}, // _наци, _mụta_, _dekċ, súde,
+ {{0xdb018019,0x656d0870,0xd7f810bc,0xb8d11664}}, // zelé, waah, бус_, _ऐन_,
+ {{0x61e2b757,0x7bd5185c,0xdb0a8247,0xf40000ab}}, // kkol, _rozu, _enfò, ্তার_,
+ {{0x6e243758,0xbcfb0019,0x9f47816b,0x78ad3759}}, // glib, _idéz, diný_, _tsav,
+ {{0x63a5375a,0x656d02a3,0x7bd50085,0xdb018019}}, // [2430] tehn, raah, _pozu, velé,
+ {{0xe1ff1362,0xa5bb01a8,0x6285375b,0xdb1c2509}}, // lió_, _chór, tuho, _enrí,
+ {{0xa6ab0b76,0xe7c7809a,0xdb018019,0x7bd50267}}, // _صادق_, लासप, telé, _vozu,
+ {{0xe1ff07e2,0x201eb75c,0x78bbb75d,0x61e2a476}}, // nió_, _akti_, _pruv, gkol,
+ {{0x7bd51ccc,0xdb018019,0x3ea1008e,0xb97c03c8}}, // _tozu, relé, _hpht_, רנדי,
+ {{0xf2e7119d,0x66090289,0x7e55988f,0x22918061}}, // _любо, čeku, отиц, yák_,
+ {{0xb7dc00be,0xdb0187bc,0xdddc0b80,0x7ddc810c}}, // רקוי, pelé, lurš, _lésu,
+ {{0x649982a4,0x2291a8e5,0xec79b408,0x91a90129}}, // атор_, vák_, апи_, _nhà_,
+ {{0xe1ff375e,0xb4e3809a,0x61fbb75f,0x13e9b760}}, // dió_, नने_, _imul, имий_,
+ {{0x69c43761,0x69d600e7,0x2291a792,0xdb0e3762}}, // _snie, _soye, ták_, _sabé,
+ {{0xd5ae8077,0xab298470,0xe299911c,0x645bb27d}}, // رفی_, рона_, шан_, spui,
+ {{0x5ed280c8,0xe1ff3763,0xbba60540,0x27fa0101}}, // _থেকে, gió_, _ऑस्क, _smpn_,
+ {{0x69d63764,0x9f45b765,0x776e011c,0x2291a792}}, // _voye, _allé_, tabx, sák_,
+ {{0xa2e58193,0x3eb8025b,0x61e9802a,0x61e2b766}}, // _молд, tvrt_, _llel, zkol,
+ {{0x61fb8c56,0xe1ff00ab,0xdb1c0125,0x7f430e86}}, // _omul, bió_, _norð, держ,
+ {{0xe1ff3767,0x6609b768,0x644b8578,0x69c40905}}, // ció_, nnek, _kugi, _unie,
+ {{0x656bb769,0xb87b0065,0x6e240057,0x644b81a1}}, // [2440] _megh, szír, rlib, _jugi,
+ {{0x9f580013,0xdb070065,0x644bb76a,0x61fbb76b}}, // oirí_, _sajá, _mugi, _amul,
+ {{0x44258224,0x9d460256,0x661ba3e4,0xf54f0135}}, // ill_, _менд, kouk, _pụta_,
+ {{0xa9268381,0x661b8198,0x2480376c,0x660981a1}}, // одел, jouk, prim_, jnek,
+ {{0x61e2b76d,0x7642810c,0x9f5c936f,0xdce60af8}}, // rkol, _rioy, livé_, _kekč,
+ {{0x61e98135,0x6609811b,0x2d9d838a,0x6443b76e}}, // _elel, enek, _abwe_, _nini,
+ {{0x644b8110,0x98a38084,0xfe6fa1df,0x656b80e5}}, // _augi, _ąją_, ندي_, _begh,
+ {{0x9343035f,0x644bb76f,0x6443b770,0x4425b771}}, // _інте, _bugi, _aini, ell_,
+ {{0x6443b772,0x656bb773,0xe1ff06a5,0x644bb2a3}}, // _bini, _degh, vió_, _cugi,
+ {{0x7ddc83d3,0x644b811f,0x6443b774,0xa2f480e8}}, // _résu, _dugi, _cini, зпоч,
+ {{0xe1ff05a4,0x661bb775,0xe3b88214,0x3eb88061}}, // tió_, bouk, llık_, _ért_,
+ {{0x201c89cf,0x644bb776,0x644391e6,0x81e30264}}, // lovi_, _fugi, _eini, ফোন_,
+ {{0xe1ff05a4,0xa92b10ac,0xe3b88214,0x64439e68}}, // rió_, ріне_, nlık_, _fini,
+ {{0xbebc8341,0xe1ff07e2,0x6443b777,0x3a203778}}, // _dzīv, sió_, _gini, _ekip_,
+ {{0x3dc68051,0x23c9064a,0x3a26b779,0x356b0abe}}, // _know_, रासद, klop_, иран_,
+ {{0x63a8b77a,0x7a120029,0x644b8358,0x6443b77b}}, // ledn, tāte, _yugi, _zini,
+ {{0x628880ab,0x34940009,0x7dd80661,0x25d78e82}}, // [2450] ludo, матр, _vísp, קומן_,
+ {{0xa2be86bf,0x61fbb77c,0x60c00366,0x9f3501b5}}, // षिप्, _smul, æmme, _непі,
+ {{0x201c803a,0x62888301,0x7afc80f7,0x7ddc82df}}, // dovi_, nudo, úrth, _jéss,
+ {{0x7bd8b77d,0xdb188106,0x386900fc,0x6600928a}}, // _lovu, _invä, _kwar_, jimk,
+ {{0x4425b77e,0x998d81ac,0x201c8390,0xf7f4003d}}, // yll_, _tiež_, fovi_, _رسید,
+ {{0x63a8807d,0x201c812b,0xa2a6000f,0x644bb77f}}, // jedn, govi_, _टैक्, _rugi,
+ {{0xfbd20051,0x644bb780,0x661bb781,0x656b80b9}}, // _אתם_, _sugi, touk, _pegh,
+ {{0x2bc91344,0x25a90014,0x63b51286,0x644bb648}}, // रावा, neal_, _hazn, _pugi,
+ {{0x6443b782,0x201c8024,0x24893783,0x321d8035}}, // _pini, bovi_, nuam_, mowy_,
+ {{0x656bb784,0x644381f6,0x25a910eb,0x321d8035}}, // _wegh, _qini, heal_, lowy_,
+ {{0x316db785,0x7bd8b786,0x63b5139c,0x386904b9}}, // _keez_, _dovu, _mazn, _awar_,
+ {{0x63b500d2,0x644bb787,0x3d959445,0x321d8035}}, // _lazn, _tugi, _хидр, nowy_,
+ {{0xdb189918,0x64439e82,0x63a882ee,0x25a93788}}, // _anvä, _tini, bedn, deal_,
+ {{0x386901cd,0x628889a4,0x9f5cb789,0x2a67841c}}, // _dwar_, budo, tivé_, _twnb_,
+ {{0xf1bf0013,0xfaa28698,0x62888c52,0xe7f38006}}, // mlán_, _защо, cudo, _अतना_,
+ {{0x201c8024,0x7bc72323,0x25a9378a,0xceb282f6}}, // zovi_, _anju, geal_, עין_,
+ {{0xdca30993,0x321d809a,0x248903ac,0x64a3017d}}, // [2460] нати, dowy_, guam_, ната,
+ {{0xa097010f,0x66008850,0xdb1c0338,0x32021e1e}}, // _צדיק_, zimk, _morö, liky_,
+ {{0x201c8052,0x386902af,0xdce08289,0x3ce60a21}}, // vovi_, _zwar_, pamć, šov_,
+ {{0x3202026f,0x63a8809a,0x8c460198,0xaca40133}}, // niky_, zedn, _неме, _ahụr,
+ {{0xd7c78e1a,0x25d7093f,0xe89497d4,0x63b5378b}}, // लांच, _זוכן_, фаль, _fazn,
+ {{0xdb0e040e,0xdb0a80f2,0x63b521a2,0x7416015b}}, // _habí, _infö, _gazn, _روزا,
+ {{0xcfb600ab,0x3a2681bf,0xfbc90c1c,0x76460c2e}}, // ঞাপন, plop_, रारम, _kiky,
+ {{0xf64f803d,0xdb0380e1,0x7646008e,0xceb301c6}}, // دئو_, _fanú, _jiky, תיה_,
+ {{0x7bd8803b,0x201c803b,0x66008877,0xdb088187}}, // _povu, povi_, rimk, cedê,
+ {{0x6288b78c,0x9f4781e8,0x7ddc8187,0xb05b0106}}, // tudo, sinò_, _péss, lväg,
+ {{0x63a8b78d,0xa75b8039,0x61358019,0x3202016b}}, // redn, _הדבר, pülé, fiky_,
+ {{0xdb0e03bb,0x6288806a,0xddcb026c,0x80b38035}}, // _nabí, rudo, _žiža, _इनमे,
+ {{0x68e2b4c2,0x7bd8b78e,0x2fda0473,0x7a2382f1}}, // nyod, _tovu, _bopg_, mõtt,
+ {{0x3f8eb78f,0xf1bf3790,0xa01b0061,0xd7f80087}}, // ngfu_, clán_, ltöz, пус_,
+ {{0x752f0d38,0xa3b58592,0x25a90c41,0x245280ff}}, // _szcz, _चोट_, teal_, _hâm_,
+ {{0x63b5003b,0xe3b882bb,0x6564177c,0x6a1501b5}}, // _sazn, ldı_, gbih, _імпу,
+ {{0x61ed3791,0xb4e73792,0x67ef813c,0x9f582f10}}, // [2470] _ilal, बने_, _høje, birà_,
+ {{0xe3b882bb,0x25a93793,0x67250009,0x68e2b794}}, // ndı_, seal_, _tyhj, dyod,
+ {{0x98b90459,0x63b500d2,0x25a91c1d,0x24890069}}, // ması_, _vazn, peal_, suam_,
+ {{0x7a3f1482,0x9f580207,0x62970102,0x22950687}}, // _būti, mirá_, rtxo, råk_,
+ {{0x656f031d,0x248907b6,0x321db795,0x7a1200eb}}, // _iech, quam_, rowy_, nāta,
+ {{0xb8d585e8,0x656f3796,0x7bc70573,0x98b9080a}}, // _जन_, _hech, _unju, nası_,
+ {{0x656f3797,0xdce600eb,0x9f581e00,0x60c0b798}}, // _kech, _iekā, nirá_, _ermm,
+ {{0x644f0587,0x660d3799,0x20031ec1,0x6447379a}}, // _kuci, nnak, hiji_, _hiji,
+ {{0x98b90182,0x6447379b,0xf1bf0e1b,0xdcfd0457}}, // kası_, _kiji, tlán_, _kası,
+ {{0x656f379c,0x61ed2b3e,0x2003379d,0x6447379e}}, // _lech, _alal, jiji_, _jiji,
+ {{0x3202026f,0x6447071d,0xdb088722,0x26c101c5}}, // tiky_, _miji, nedè, _nrho_,
+ {{0x656f0775,0x471a893f,0x645d01c2,0x69db83d3}}, // _nech, _וועג, _otsi, _joue,
+ {{0x25098288,0x660d00ce,0x645d10af,0x69db9e9e}}, // _برای_, dnak, _ntsi, _moue,
+ {{0xdcfd07d9,0xdb0e0d76,0x69dbacf0,0xdce600eb}}, // _nası, _sabí, _loue, _nekā,
+ {{0x645d0a8e,0x656f379f,0x9f5837a0,0x69c9b7a1}}, // _atsi, _bech, rirà_, _onee,
+ {{0x660d37a2,0x644737a3,0x656f1f1c,0xdb0a81a8}}, // gnak, _aiji, _cech, _uafá,
+ {{0x20030d11,0x644f0698,0x656f37a4,0x644737a5}}, // [2480] biji_, _cuci, _dech, _biji,
+ {{0x644f37a6,0x9f459ec4,0x9f5805e4,0x656404e6}}, // _duci, _allí_, birá_, sbih,
+ {{0x6d598c0b,0xe2971a8f,0x656f03aa,0x644737a7}}, // _ofwa, дах_, _fech, _diji,
+ {{0x6d4b8854,0xa96789a0,0x644f047f,0x79a38992}}, // _ngga, дица_, _fuci, врше,
+ {{0x660437a8,0x7a238074,0xd6cf803d,0x644737a9}}, // miik, võtt, یقه_, _fiji,
+ {{0x66040ef6,0x6d4bb7aa,0xb05b016d,0x68e2b7ab}}, // liik, _agga, sväg, ryod,
+ {{0x644f04b9,0x656f37ac,0x2452b7ad,0x7a2382f1}}, // _zuci, _yech, _sâm_, tõtt,
+ {{0x98b90059,0x7bdc0079,0xc8838214,0x1dc58b84}}, // zası_, _horu, üğü_, वागत,
+ {{0x7bdc37ae,0x98b90201,0xe3b8811c,0x9f5802df}}, // _koru, yası_, tdı_, zirá_,
+ {{0x61ed0bda,0x88bd809a,0x6d4bb7af,0xc0a98481}}, // _slal, _myśl, _egga, _عاقل_,
+ {{0x200302a5,0xddcb803a,0xe3b882bb,0xdd8f81a1}}, // viji_, žišt, rdı_, _иш_,
+ {{0x24528104,0x7bdc046d,0x7c669459,0x9f58018a}}, // _tâm_, _loru, _قاتل, virá_,
+ {{0x656f37b0,0x60f801bb,0x64c8000f,0x660d01ac}}, // _rech, дняя_, रमोश, vnak,
+ {{0x9f582706,0x656f37b1,0x67ef8366,0xdce0928a}}, // tirá_, _sech, _tøje, zamě,
+ {{0x656f37b2,0xa3d205fb,0x75ea003e,0x98b9017b}}, // _pech, वान_, _význ, rası_,
+ {{0x200302a5,0x6e20b7b3,0x644737b4,0x9f5837b5}}, // siji_, nomb, _siji, rirá_,
+ {{0x656f37b6,0x660d270b,0x25a20748,0x69db80e7}}, // [2490] _vech, rnak, _dbkl_, _roue,
+ {{0x7bdc37b7,0xfe710065,0x656f02af,0x644f0bcf}}, // _coru, _مگر_, _wech, _vuci,
+ {{0x7bdc1249,0x6e20b7b8,0x644737b9,0x69dbb7ba}}, // _doru, komb, _viji, _poue,
+ {{0x7bdc0176,0x3e7a8029,0xdb088019,0xdb0e0187}}, // _eoru, dīt_, gedé, _sabã,
+ {{0xa0699a8f,0x18699980,0x645d37bb,0x6e29b7bc}}, // чала_, чали_, _utsi, vleb,
+ {{0x63b88b40,0x7bdc1035,0xdb070722,0x76b3811c}}, // _navn, _goru, _majú, _həyə,
+ {{0xfc488104,0x26c183bb,0xd6d19e95,0x442137bd}}, // _gửi_, ího_, اقع_, moh_,
+ {{0x6e20a578,0x442102d4,0x22988019,0x7bdc0214}}, // gomb, loh_, dék_, _zoru,
+ {{0x7bdc37be,0x6e29b527,0xdb018a21,0x212900b9}}, // _yoru, rleb, delá, _nyah_,
+ {{0xf6e7b7bf,0xa3d20576,0x35d106a7,0x2bd20072}}, // _оцен, वाय_, हाड़, साया,
+ {{0x63b88025,0x21290057,0xdb15026f,0x6e20a0b0}}, // _davn, _ayah_, _bazé, bomb,
+ {{0x61f98afe,0x7bca8365,0x6d4ba475,0x70cb0a43}}, // _çalı, _enfu, _ugga, ामूल,
+ {{0x44211ad4,0x629a8009,0x1c398a14,0x66040198}}, // koh_, atto, _езды_, viik,
+ {{0xcc76810f,0xf1b981a1,0x212937c0,0xdb0e0338}}, // _טענה_, _laš_, _dyah_, _inbö,
+ {{0x44210057,0x5f1a0105,0x660437c1,0x6da61634}}, // doh_, _मुख्_, tiik, нива,
+ {{0x7bdc0afe,0x25ad85f8,0xc1ba00a0,0x22490122}}, // _soru, neel_, _رابط_, _giak_,
+ {{0x7bdc37c2,0x660430c4,0x1de10074,0x76b38085}}, // [24a0] _poru, riik, _नवरत, _dəyə,
+ {{0xa09b0158,0x25ad8fb0,0x7bdc0201,0x660437c3}}, // _צייט, heel_, _qoru, siik,
+ {{0xf1b9803a,0x386d810c,0x2bb78076,0x88bd809a}}, // _baš_, _awer_, _असना, _wyśl,
+ {{0x60c406a4,0xd910804e,0x9f5c8118,0x62530372}}, // _krim, لیز_, riví_, _għom,
+ {{0xf1238b88,0x44210458,0x25ad837a,0x9f458722}}, // льшо, boh_, deel_, _allà_,
+ {{0x3e7a8029,0x442137c4,0x65760df6,0xe7c78327}}, // tīt_, coh_, layh, लागप,
+ {{0x44380352,0x63b88805,0x6e20b7c5,0x629a8706}}, // _ihr_, _ravn, tomb, xtto,
+ {{0x3e7a80eb,0x63b88e66,0x32078061,0x2d9237c6}}, // rīt_, _savn, ényt_, ngye_,
+ {{0x3e7a8029,0x248d855a,0x224911d6,0x2cb2031d}}, // sīt_, guem_, _riak_, nwyd_,
+ {{0x22988065,0xa87b8051,0xa01b089c,0xa5bb0032}}, // ték_, _מאמר, tröm, _akól,
+ {{0x60c437c7,0x056682ff,0x44380706,0xda77b7c8}}, // _arim, _звен, _mhr_, няя_,
+ {{0xddea803d,0x60c437c9,0xf8bf016b,0x20010106}}, // _عرضه_, _brim, tvé_, _smhi_,
+ {{0xebe6b2b8,0xf8bf02be,0x442109c4,0x60c4079a}}, // _подп, uvé_, yoh_, _crim,
+ {{0xf8bf00e7,0xdb188168,0x2cb20114,0x44380133}}, // rvé_, _javë, dwyd_, _nhr_,
+ {{0x443e8352,0x60c437ca,0x6e26008e,0x25a00428}}, // mmt_, _erim, _bkkb, ffil_,
+ {{0x7c3e12ca,0x442ca51d,0x60c404dc,0xbcfb0061}}, // empr, lld_, _frim, _beép,
+ {{0x644ab7cb,0x60c40d1a,0x69cd004f,0xe4531ef5}}, // [24b0] _kifi, _grim, _inae, اضر_,
+ {{0x7a3637cc,0x1602016f,0x68470032,0x62888037}}, // ráte, रोबर_, _dédì, ordo,
+ {{0xa5f937cd,0x442c808b,0x443837ce,0x8c1b81c6}}, // _жену_, ild_, _dhr_, לומי,
+ {{0x442137cf,0x443e8192,0x644ab7d0,0x225c1517}}, // soh_, hmt_, _lifi, ívka_,
+ {{0x25ad90f4,0x644a803c,0x442110e1,0xed59812b}}, // veel_, _oifi, poh_, ndže_,
+ {{0x9f4585a4,0x442137d1,0x644ab7d2,0x66028314}}, // _allá_, qoh_, _nifi, _amok,
+ {{0xf1a7a451,0x6e2d1793,0x25adb7d3,0xf1b981f4}}, // _пран, olab, teel_, _taš_,
+ {{0x6e2d37d4,0xa91d808d,0x2bc9001b,0x02a28133}}, // nlab, _adže, राका, _chọọ,
+ {{0x8cc4009a,0x6e2d0051,0x628881ca,0x644a80b4}}, // रियो, ilab, erdo, _bifi,
+ {{0xa5bb0125,0x69cd37d5,0x6e2d005d,0x24890187}}, // _skól, _anae, hlab, iram_,
+ {{0xa3d2035a,0x24893752,0x05e10035,0x60c437d6}}, // वात_, hram_, _नवंब, _srim,
+ {{0xd70d83eb,0x442c81ed,0x24890f7c,0xdd1d8162}}, // िपीठ_, ald_, kram_, _câţi,
+ {{0x248937d7,0x644ab15e,0x248db7d8,0x7c218b99}}, // jram_, _fifi, quem_, polr,
+ {{0x2007b64e,0x65698aa2,0x7a38b7d9,0x644a8314}}, // nini_, lbeh, míte, _gifi,
+ {{0xd62a37da,0xa5961bc1,0x91fd00eb,0x6e2d349a}}, // дове_, кращ, mpān, flab,
+ {{0x200780f6,0x6e2d37db,0xa6e9801c,0x6569b7dc}}, // hini_, glab, _trươ, nbeh,
+ {{0x200780f6,0xb9070a49,0x25a037dd,0xa5071628}}, // [24c0] kini_, _যে_, rfil_, вета_,
+ {{0xdca32410,0x2007b7de,0x64a30fbf,0x2bd210be}}, // рафи, jini_, рафа, साधा,
+ {{0xee3a0604,0x44278239,0xd259102a,0xeb9737df}}, // _оно_, _mkn_, еці_, тих_,
+ {{0x248902df,0x6d5b81c0,0x656981ed,0x58868a14}}, // bram_, jcua, jbeh, выка,
+ {{0xa3d21370,0x2007b7e0,0x443ea51d,0xdce4012b}}, // वाद_, fini_, ymt_, obič,
+ {{0x2007b7e1,0x44380352,0x656980e1,0x02a281bc}}, // gini_, _uhr_, ebeh, _shọọ,
+ {{0x60090503,0x644ab7e2,0xa3d58aad,0x63bc29de}}, // nžma, _rifi, हान_, _iarn,
+ {{0x44279600,0x644ab7e3,0x6adb00ab,0x531b01c6}}, // _akn_, _sifi, _যেকো, _חולצ,
+ {{0x61e08068,0x41d6000c,0x442789ca,0x229c026f}}, // _ioml, धानस, _bkn_, lík_,
+ {{0x3866b7e4,0x442c8051,0x2bb7816f,0x63bc37e5}}, // mpor_, uld_, _असणा, _jarn,
+ {{0x63bc37e6,0x443ea09b,0x229c03cb,0x764b8065}}, // _marn, rmt_, ník_, _figy,
+ {{0x2489061c,0x61e080b9,0x8b0781d0,0xad9b0032}}, // yram_, _joml, _skří, _amún,
+ {{0x63bc00b9,0x6ba78106,0x442c80b9,0xcdf781c6}}, // _oarn, ärgå, pld_, _במאי_,
+ {{0xdb03b7e7,0x44278118,0x69cd0114,0x61e0b7e8}}, // _canó, _gkn_, _wnae, _loml,
+ {{0x2bb7835a,0xdceb80eb,0x5c37807c,0xdb019647}}, // _असता, _iegā, _שטוב_, gelä,
+ {{0x24892ec0,0x518715da,0x61e0836a,0x229c016b}}, // tram_, _рука, _noml, dík_,
+ {{0x6e2d37e9,0x2007b7ea,0x63bc37eb,0x7bda822c}}, // [24d0] rlab, yini_, _barn, ojtu,
+ {{0x31790117,0x248937ec,0xd378809a,0x6e2d37ed}}, // lasz_, rram_, nać_, slab,
+ {{0xf09f01c1,0x16229c4f,0x6e2437ee,0x78a980e1}}, // ltà_, मवार_, boib, _spev,
+ {{0xd378b7ef,0xf6500117,0xf1bf0032,0xa5f902de}}, // hać_, _گئی_, ndá_, _чему_,
+ {{0xd378809a,0xf09f3340,0x3c398036,0x61e081d0}}, // kać_, ntà_, lève_, _doml,
+ {{0xe73989e0,0xb4df016f,0xf09f3340,0x1e95b7f0}}, // нел_, तही_, ità_, грир,
+ {{0x2007b7f1,0xed599641,0xd3788063,0x9f5105a4}}, // rini_, ной_, dać_, lizó_,
+ {{0x2007b7f2,0x26c5803a,0xe8d9082e,0xe3b88085}}, // sini_, _vrlo_, _afọ_, dlıq_,
+ {{0xdd118182,0x2007b28f,0xdd918065,0x9f5101ca}}, // _düşm, pini_, لوں_, nizó_,
+ {{0x7a3896a5,0x3a292c8d,0x7c25016b,0x2007b7f3}}, // ríte, _akap_, nohr, qini_,
+ {{0x81b68a49,0x420a1056,0x224d81e2,0xf09f0098}}, // জার_, енно_, _kiek_, età_,
+ {{0x3a2900b9,0xdb088333,0x6ec08651,0x75ea01d0}}, // _ckap_, cedí, विरु, _výzk,
+ {{0x22478400,0x4427b7f4,0x229c016b,0x66099c11}}, // ínky_, _tkn_, zík_, miek,
+ {{0x660985d7,0x25bdb7f5,0x224db7f6,0xdb01b7f7}}, // liek, _hawl_, _liek_, telä,
+ {{0x4425b7f8,0x98a50029,0x249fb7f9,0xd757004e}}, // mol_, ālā_, ltum_, الفت_,
+ {{0x66098341,0x229c0125,0xe1f20077,0x25bfb7fa}}, // niek, vík_, _دست_, ndul_,
+ {{0xfbb788af,0x61e0b286,0x63bc1b40,0xd57505a8}}, // [24e0] _असाम, _roml, _parn, русь,
+ {{0x44258355,0x229c37fb,0x61e0b7fc,0x69a021ef}}, // nol_, tík_, _soml, ग्री,
+ {{0xdee60187,0x63bc37fd,0x7f9b0039,0x51869860}}, // _сони, _varn, _אביז, гула,
+ {{0x4425b7fe,0xa3d20ebf,0x200a37ff,0xd3788035}}, // hol_, वाह_, libi_, zać_,
+ {{0x4425b800,0x63bc3801,0x7a1c81d0,0x25bf8326}}, // kol_, _tarn, jčte, ddul_,
+ {{0xb4c20aed,0x2ca00e23,0x4425b802,0x3866b803}}, // ्मी_, mtid_, jol_, rpor_,
+ {{0x2ca02e80,0x4425b7fe,0x3866b804,0x09a980c8}}, // ltid_, dol_, spor_, ওয়া,
+ {{0xd3788d38,0x200a03bf,0xb05b016d,0xa3d2056b}}, // wać_, hibi_, dvän, वाव_,
+ {{0x7ddc8117,0x644e3805,0x200a3806,0x4425831d}}, // _kész, _kibi, kibi_, fol_,
+ {{0x3a2680c9,0x4425b807,0x200a04b9,0x644e21c7}}, // loop_, gol_, jibi_, _jibi,
+ {{0x9e669d85,0x53348764,0xa91d8267,0x6609adc5}}, // _свед, рент, _idža, biek,
+ {{0xf09f0698,0xf2d30158,0xe3b88085,0xd378809a}}, // ttà_, צער_, rlıq_, sać_,
+ {{0x4425b808,0x7984031d,0x3dc00114,0x7d7b83de}}, // bol_, _ddiw, ddiw_, _טראג,
+ {{0x4425b809,0xf09f01c1,0x644e380a,0xc3568698}}, // col_, rtà_, _nibi, _съдъ,
+ {{0x3a268cfa,0xa36f80e1,0xd90f003d,0xf09f380b}}, // koop_, äčši, تیک_, stà_,
+ {{0x2bd290be,0x270e8201,0xb4c20361,0x442a0573}}, // तावा, _mən_, ्मू_, _akb_,
+ {{0x9f4100f1,0xa91d8390,0x200a380c,0x1ae200ab}}, // [24f0] _kohë_, _odža, bibi_, _গেলে_,
+ {{0x644e380d,0x320b380e,0x6609b80f,0xbcfb0118}}, // _cibi, licy_, ziek, _aféc,
+ {{0x644e3810,0xddd00214,0xe45f0106,0x9e350391}}, // _dibi, _çeşi, _grön_, _венч,
+ {{0x320b0035,0x4425b811,0xac951f96,0x69d80f12}}, // nicy_, zol_, _камш, यायी,
+ {{0x656d1ad4,0x4425b812,0xa3e5950e,0x2ca00428}}, // mbah, yol_, _नवल_, ctid_,
+ {{0xdceb8087,0xba7712c8,0x656d3813,0x9f518085}}, // _legă, _مارت, lbah, _özü_,
+ {{0x224da338,0x6609b814,0xb3d2035a,0xec360039}}, // _tiek_, tiek, सारख, _כאשר_,
+ {{0x656d3815,0xfbb78063,0x249fb816,0x4425b817}}, // nbah, _असहम, ttum_, wol_,
+ {{0x4425b818,0x25bfb819,0xec768a14,0x20ce808b}}, // tol_, rdul_, апы_, rðið_,
+ {{0xa3ab01b6,0x6609b81a,0x249fb81b,0x60c9b81c}}, // _कॉम_, siek, rtum_, _irem,
+ {{0x249fb81d,0xddc701e2,0xc5d5a1d2,0x216a1d34}}, // stum_, gpjū, _віль, кини_,
+ {{0x4425831d,0xb05b00f2,0x249f808b,0xcfaa80ab}}, // sol_, rvän, ptum_, _গোপন,
+ {{0x69d801ab,0x777702f7,0x7a1200eb,0xb05b0106}}, // याबी, _bexx, nāti, svän,
+ {{0x88bd809a,0x248db81e,0x60c9804f,0x4425b81f}}, // _wyśw, mrem_, _mrem, qol_,
+ {{0x7ddc8117,0x645b9220,0xf74602c7,0x200a3820}}, // _rész, lqui, рево, ribi_,
+ {{0xdd921b9a,0x644e3821,0x06af80ab,0x8e55035f}}, // تور_, _sibi, কিপি, стрі,
+ {{0x7a3614b5,0x9f4780f7,0xa3d2064a,0x248d808e}}, // [2500] láto, ghnú_, वाल_, nrem_,
+ {{0x907b0158,0x7bc1b822,0x2ca0005f,0x187b00be}}, // _שטיי, ndlu, rtid_, _שטיב,
+ {{0x2ca03823,0xe29a027e,0x7a360c83,0xdb1503a7}}, // stid_, тае_, náto, _razã,
+ {{0x60c98456,0x7ae31af3,0x50669986,0xb05b016d}}, // _brem, änta, атка, kväl,
+ {{0x270e8085,0xe046af75,0x76440041,0xf8b80074}}, // _sən_, анди, mmiy, ेटिय,
+ {{0x76443824,0x24803825,0x7a360c83,0xdb073826}}, // lmiy, msim_, káto, _cajó,
+ {{0x44443827,0x764402a3,0x60c9b828,0x7a3d282c}}, // mm_, omiy, _erem, léte,
+ {{0x60c98ac6,0xe5c68544,0x806680e8,0x61e43829}}, // _frem, рско, _вваж, _noil,
+ {{0x2bd2800d,0x7a388aae,0x6aa9026c,0x4444382a}}, // ताला, cíta, _ćefi, om_,
+ {{0x4444382b,0x69c2838e,0xdb1c06c0,0x628781b9}}, // nm_, ldoe, _parè, ġjon,
+ {{0x44442404,0x78ad003a,0x78a28a9e,0x4b7b00be}}, // im_, _spav, ltov, כטיג,
+ {{0x61e40083,0x261704b7,0xdca68d91,0x248db82c}}, // _coil, għol_, _вади, brem_,
+ {{0x78a284e8,0xfeb804c0,0x69c0b82d,0xb4c206a7}}, // ntov, یافت_, _mame, ्मो_,
+ {{0x4444382e,0xb4c224bd,0xdb18b82f,0x8c43802e}}, // jm_, ्मै_, _haví, _дече,
+ {{0x69c08012,0x61e41a29,0xc9872707,0x0cb6ab51}}, // _oame, _foil, _купи, _अनुम,
+ {{0x44443830,0x81ab8a49,0x5d550381,0xdb0504b8}}, // em_, _কোন_, скат, nehå,
+ {{0xa3d20006,0x78a2b831,0x78ad026c,0xbddb0036}}, // [2510] वां_, jtov, _upav, _bièr,
+ {{0x6380001b,0x656d31a6,0x6fb509a7,0x7a388c83}}, // vání, sbah, _امدا, víta,
+ {{0x60c9b4c1,0xdb1501ac,0x645b8661,0x69c09ea2}}, // _srem, _bazá, zqui, _bame,
+ {{0xb5a73832,0x44443833,0xc9878966,0xdb18801b}}, // _трой, am_, ружи, _naví,
+ {{0x69d8016f,0x7c2880f1,0x7a3603b0,0xb05b0106}}, // याती, kodr, záto, kväm,
+ {{0x645d82ba,0xa3d224bd,0xf1bf002a,0xafe28198}}, // ísim, वाः_, xoán_, зошл,
+ {{0x75ea026f,0x657b820d,0x69c0b834,0x7a38b835}}, // _výzv, tauh, _fame, síta,
+ {{0x660d3836,0x248db837,0x69c0b838,0xff1804de}}, // niak, trem_, _game, יקות_,
+ {{0x32f78085,0xf1bf00e1,0x7c28b839,0x60c9a5f4}}, // məyə_, hlás_, fodr, _urem,
+ {{0x61e42174,0x69c0b83a,0x248db83b,0x660d0102}}, // _soil, _zame, rrem_, hiak,
+ {{0x645bb83c,0x61e4383d,0xdb1c366a,0x69c0b83e}}, // squi, _poil, _paré, _yame,
+ {{0x7a36037d,0x245b8073,0xa06a017c,0xb0c30361}}, // ráto, _têm_, лама_, शिंग,
+ {{0x660d383f,0x61e402be,0x6e298118,0xfd568039}}, // diak, _voil, moeb, _השני_,
+ {{0x7afe3840,0x76aa8201,0x7a3d0019,0x7c28b49a}}, // yzpt, _həya, véte, codr,
+ {{0x670d83bb,0x229580f7,0xe45f0106,0x528581a8}}, // िपटक_, _العس, _dröm_, _البك,
+ {{0x78a2803e,0x4444146a,0x6459b841,0x660d3842}}, // ytov, wm_, _kuwi, giak,
+ {{0x228387d9,0x78a2826f,0x7ae30106,0x146291d3}}, // [2520] lık_, xtov, äntn, _kọọp,
+ {{0x444410af,0x69c092cf,0x76443843,0x24803844}}, // um_, _same, smiy, rsim_,
+ {{0x62818422,0x7d090503,0xdcef0029,0x24803845}}, // nslo, _žest, _vecā, ssim_,
+ {{0x44443846,0xe8fa8b79,0x69c09abf,0x6579b847}}, // sm_, лле_, _qame, _newh,
+ {{0x69c2800b,0x17549af1,0x6459b848,0xb3b7b849}}, // rdoe, овля, _nuwi, _असंख,
+ {{0x69c080f6,0x78a2b84a,0xab2a1d85,0x62838503}}, // _wame, rtov, _бога_, _ovno,
+ {{0x78a2b84b,0x69c086c7,0x76aa8085,0xdb18b6fa}}, // stov, _tame, _bəya, _paví,
+ {{0x8a06875a,0x78a2b84c,0x22838214,0x6459820d}}, // _узбе, ptov, dık_, _buwi,
+ {{0x628380d2,0xb87b0019,0x7a388061,0x645989c4}}, // _avno, nyít, líto, _cuwi,
+ {{0x660d384d,0x2cbd82d5,0x27e58326,0x6459b84e}}, // ziak, _dswd_, _goln_, _duwi,
+ {{0x02a39fb4,0xdb050106,0x91bc914f,0x6281a26d}}, // прям, pehå, _ईसाई, gslo,
+ {{0x7f3b8158,0x2240384f,0x224680b9,0x200e811c}}, // _געבו, _chik_, nmok_, lifi_,
+ {{0x25b7803d,0x22401ab3,0x9f4101d6,0xe9df0061}}, // _دهند_, _dhik_, _dlhý_, rjú_,
+ {{0x765a9fc5,0x7bcd84b9,0x26cc81a1,0xe8d9082e}}, // _kuty, _ɗauk, _krdo_, _ndụ_,
+ {{0xa3d583a4,0xa3cc2e2b,0x6b7b83c8,0x4b7b810f}}, // हार_, _शोध_, _גרונ, _גאוו,
+ {{0xe8d901bc,0x765a8c2e,0xf1bf0019,0x75f581d0}}, // _adụ_, _muty, rlás_, _lázn,
+ {{0xc4b681fe,0x660d0695,0xae023850,0x045b00d7}}, // [2530] _अनोख, riak, रोइन_, اجرت_,
+ {{0x3a2d81b0,0x60cd018e,0x41a5000c,0x660d3851}}, // _skep_, _iram, ग्रस, siak,
+ {{0x1dc43852,0xbddb06c0,0x660d3853,0x31698333}}, // _लोकत, _chèc, piak, ñazo_,
+ {{0x60cd3854,0x3f9e80e7,0xdb0183ba,0x22469fdb}}, // _kram, _actu_, relø, gmok_,
+ {{0x200e84bb,0x26ccb855,0x777a8580,0xf8c903db}}, // fifi_, _ardo_, _betx, िटिय,
+ {{0x26cc8052,0x765ab856,0x6281b857,0x26c7817f}}, // _brdo_, _buty, yslo, tvno_,
+ {{0x6e29b858,0x7bd53859,0xa91d8267,0xe7398009}}, // toeb, _inzu, _idžm, шек_,
+ {{0x7d0907ca,0xdb0a82af,0x25a91823,0x63b7026f}}, // _þess, _dafü, nfal_, lexn,
+ {{0x38690072,0xa3bd116e,0xa3d58ebf,0x79898176}}, // _ntar_, _आसा_, हाल_, _idew,
+ {{0x7bc50077,0x228387d9,0x98178b8c,0x52a98319}}, // ndhu, tık_, _ابرا, авом_,
+ {{0x60cd385a,0xbf1580f7,0xa3ab03dd,0x28c78424}}, // _aram, _بواب, क्य_, लिवि,
+ {{0x6459b85b,0x75f59c18,0x60cd385c,0x7a3601d0}}, // _tuwi, _zázn, _bram, mátk,
+ {{0x7a36026f,0x316d2509,0x89360bbe,0x62819ff5}}, // látk, ñeza_, تعدا, sslo,
+ {{0x6281b85d,0xcb6980e8,0x6283b85e,0x7a1201a9}}, // pslo, _таке_, _tvno, vātu,
+ {{0x26cc1313,0x7bc3802e,0x60cd385f,0x65c60081}}, // ído_, _ianu, _eram, обва,
+ {{0x60cd3860,0x7bd502af,0x660b80b9,0x7989b861}}, // _fram, _anzu, _jmgk, _ndew,
+ {{0xdca31980,0x69d63862,0x7bc3b863,0x64a30d46}}, // [2540] мати, _inye, _kanu, мата,
+ {{0x69c40102,0x38a43864,0x7bc53865,0x7989b866}}, // _haie, mör_, gdhu, _adew,
+ {{0x75f5a5ce,0x7bc39e44,0x2bce035a,0x0576080b}}, // _názo, _manu, _होणा, _واحد,
+ {{0xa3d58e88,0x7bc3b867,0x69c4011b,0x69c63868}}, // हाँ_, _lanu, _jaie, ndke,
+ {{0x79898114,0x2db70039,0x7a388019,0x3ebeb869}}, // _ddew, _ולכן_, síto, _sstt_,
+ {{0x7a38b86a,0x629701df,0x7bc3b86b,0xdb1c06a5}}, // píto, buxo, _nanu, _harí,
+ {{0x81e68a49,0x38a429ed,0x69d6386c,0xf1ca000c}}, // যোগ_, hör_, _onye, ियान,
+ {{0x85f70158,0x2bce2539,0x442e88dc,0x69d601bc}}, // ַציע_, _होता, _skf_, _nnye,
+ {{0xdb1c386d,0xa3d5881f,0x6283026f,0x1da6a0f2}}, // _marí, हां_, ánov, क्षत,
+ {{0x7a360013,0xe9ff8028,0x69d60247,0x60c080fc}}, // láth, _kiến_, _anye, _ismm,
+ {{0x6fde01aa,0x69c40c50,0x386910b6,0xada68196}}, // मानं, _baie, _star_, _мажл,
+ {{0x38a4386e,0x61e2b86f,0x60cd3870,0x7a3601a8}}, // för_, njol, _pram, náth,
+ {{0x38a4016d,0xa3ab00d4,0x7a3d00e7,0x10741273}}, // gör_, क्ड_, géta, мляю,
+ {{0x69d6082e,0x7bc3b871,0x9f45826b,0x248681f4}}, // _enye, _ganu, _bolè_, _ivom_,
+ {{0x226380e1,0x98ac00eb,0x61e283ba,0xdb1c046d}}, // ľské_, ādā_, kjol, _barí,
+ {{0x442cb872,0x2d580153,0xc333812a,0xa5bb026b}}, // lod_, жить_, _קוק_, _akór,
+ {{0x80ba8c28,0x2902009a,0xa8028214,0xdb1c05e4}}, // [2550] _शैले, czka_, şıyo, _darí,
+ {{0x69a71094,0x442cb873,0xe9ff8028,0x7bc38079}}, // ट्री, nod_, _biến_, _xanu,
+ {{0x20113874,0xdb1c22d8,0x2c608247,0x7bc53875}}, // mizi_, _farí, _lòd_, rdhu,
+ {{0x24868025,0x5bb783dd,0x20112693,0x442cb876}}, // _ovom_, _अस्व, lizi_, hod_,
+ {{0x442cb877,0x7e6202d4,0x3f69996e,0xead0826b}}, // kod_, _čopi, било_, _jẹbi_,
+ {{0x657d1b64,0xcc76012a,0x20113878,0x442c8682}}, // _hesh, _מעשה_, nizi_, jod_,
+ {{0x657d135e,0xfd4c8028,0x533433e4,0x645d0010}}, // _kesh, _triể, _жерт, _husi,
+ {{0x645d273f,0x657d3879,0x7bc39bb7,0xf0768065}}, // _kusi, _jesh, _sanu, _کیوں_,
+ {{0x7bc3b87a,0x64551d1b,0x442c831d,0x657d387b}}, // _panu, _kizi, fod_, _mesh,
+ {{0x7a361313,0x442cb87c,0x7bc38201,0x1c428d15}}, // máti, god_, _qanu, нным,
+ {{0x69c4387d,0x6455387e,0x7bc3b87f,0x645d3880}}, // _paie, _mizi, _vanu, _lusi,
+ {{0x38a43881,0x61e9a32d,0x3d0f8072,0x273481a8}}, // tör_, _joel, _तेथे_, súnú_,
+ {{0x645d01e2,0x442c831d,0x7bc3b882,0xa01b02af}}, // _nusi, bod_, _tanu, rsön,
+ {{0x76499d5a,0x64553883,0x7a3d3884,0x7fd50d8e}}, // lmey, _nizi, réta, місі,
+ {{0x61fbb885,0x657d3886,0x645d1aea,0x29022448}}, // _olul, _besh, _ausi, szka_,
+ {{0xd90d03f8,0x645d3887,0xe8860b5b,0x661ba645}}, // ایی_, _busi, _егип, nnuk,
+ {{0x64553888,0x657d3889,0xdb0a8125,0xdb1c388a}}, // [2560] _bizi, _desh, _hafð, _varí,
+ {{0xf7461fab,0x6b81b88b,0x2d800036,0x7a36041c}}, // _непо, nalg, saie_, dáti,
+ {{0x1dbd863a,0x6455388c,0x6285388d,0x7a388019}}, // ्यात, _dizi, gsho, sítm,
+ {{0x6d59882e,0x442cb88e,0xa3cc0105,0x61e9b88f}}, // _ngwa, zod_, _शोर_, _coel,
+ {{0x61e98b3c,0x442c97ea,0xed52003d,0x6b81b890}}, // _doel, yod_, _سپس_, kalg,
+ {{0x62853891,0xe9ff8028,0x2609800f,0x6d59b892}}, // bsho, _tiến_, ़ोसी_, _agwa,
+ {{0x8c430956,0x24868025,0x04430607,0xe2970ba7}}, // _чере, _svom_, _черн, _нар_,
+ {{0xd49a85f1,0xceb30051,0x442c8428,0x645506a0}}, // бро_, גיה_, wod_, _zizi,
+ {{0x38c883f8,0x442cb893,0x7d043894,0x290280f2}}, // _بازی_, tod_, nzis, _åka_,
+ {{0xe7d700ab,0x7e6d8279,0xcfaa803d,0x6e2d0ece}}, // _হত্য, ćapr, _دارم_, zoab,
+ {{0x764280dd,0x644880eb,0x39402168,0x20113895}}, // _thoy, rmdi, _azis_, vizi_,
+ {{0x24868052,0x442cb896,0x7a38816b,0x61e98118}}, // _tvom_, sod_, dítk, _xoel,
+ {{0x657d3897,0x6b81b898,0x50672b3f,0x20113899}}, // _resh, balg, ятна, tizi_,
+ {{0x645d389a,0x1ae200c8,0x657d389b,0xdb0e0020}}, // _rusi, _গেছে_, _sesh, _jabó,
+ {{0x645d29fb,0xa3e02d19,0x657d389c,0xa3c20e88}}, // _susi, थान_, _pesh, ्यन_,
+ {{0x63bab89d,0x6455389e,0x645d389f,0xa4d500e8}}, // letn, _sizi, _pusi, мобі,
+ {{0x629a98ac,0x657d00f1,0x2718809a,0x61e9910f}}, // [2570] luto, _vesh, धपुर_, _roel,
+ {{0x62851ded,0x6455029a,0xf045026a,0x78fc0039}}, // tsho, _qizi, _تعبی, _לפחו,
+ {{0x645538a0,0x3f830025,0x629a90dd,0x2bae816f}}, // _vizi, maju_, nuto, ज्या,
+ {{0x2fc58bc5,0x628538a1,0x645d38a2,0x64551e8f}}, // _salg_, rsho, _tusi, _wizi,
+ {{0x48aba4c8,0x645d0364,0x63bab642,0x61e9838e}}, // стем_, _uusi, ketn, _voel,
+ {{0x63bab8a3,0x629ab8a4,0x7a36016b,0x2ca90106}}, // jetn, kuto, ráti, mtad_,
+ {{0x2ca938a5,0x61e9837a,0x6b81b8a6,0x2fc58257}}, // ltad_, _toel, valg, _valg_,
+ {{0x629ab8a7,0x3f831d96,0x61fbb8a8,0x661b985b}}, // duto, haju_, _ulul, unuk,
+ {{0x3f83003b,0x6b81b8a9,0x2ca938aa,0x76498214}}, // kaju_, talg, ntad_, rmey,
+ {{0x3f830025,0x9f638009,0x798d00a4,0x3e7a80eb}}, // jaju_, ävät_, _adaw, būt_,
+ {{0x3f830289,0x7bc70c9e,0x629ab8ab,0x7a3d026f}}, // daju_, _maju, guto, rétn,
+ {{0x7bc7036e,0x2ca938ac,0x7bd8b8ad,0xa91d81a1}}, // _laju, ktad_, _envu, _adži,
+ {{0xa3c88e18,0xb4bc009a,0xa3c21b7e,0x3ce00118}}, // _लोक_, _आने_, ्यम_, xxiv_,
+ {{0x63ba80d2,0xfd108bbe,0xdb0e002a,0x2d9f841c}}, // cetn, _حجم_, _xabó, lgue_,
+ {{0xe9ff8028,0x67ef806a,0xa3d1109b,0x798d0197}}, // _nhắn_, _højs, वयन_, _fdaw,
+ {{0xa3ab0305,0xee3a0d13,0x51560d5f,0x6009026c}}, // क्स_, іна_, _отпу, džmu,
+ {{0x3f830025,0xd0408201,0xbc6a08ca,0x2ca938ae}}, // [2580] baju_, _ermə, زمان_, gtad_,
+ {{0x3f830025,0x60c438af,0x7d0438b0,0xdb1c0722}}, // caju_, _isim, rzis, _parà,
+ {{0xdb1c38b1,0x09e699b8,0x7bc72ad8,0xe9ff801c}}, // _bará, можн, _daju, _chắn_,
+ {{0xdb1c2b72,0xaca40135,0xaca38133,0xdb0e2c6c}}, // _cará, _akụr, _anọk, _sabó,
+ {{0x7a38b8b2,0x307a00be,0xdb0e24a3,0x7bc89113}}, // níti, קאַנ, _pabó, yddu,
+ {{0x60c40010,0x7bc738b3,0x386010eb,0x7a3638b4}}, // _msim, _gaju, _muir_, mátu,
+ {{0xa3c880cf,0xdb1c38b5,0x25adb8b6,0x63bab8b7}}, // _लोग_, _fará, ffel_, vetn,
+ {{0xdb1c037d,0x60c438b8,0x3f8300d2,0x25a038b9}}, // _gará, _osim, zaju_, ngil_,
+ {{0x80a68bca,0x7bc72e22,0x60c438ba,0x063703de}}, // _زمان, _yaju, _nsim, ונקט_,
+ {{0xa3d58ee6,0x427487b6,0xdb1c38bb,0xb05b0192}}, // हाग_, нгос, _zará, hwäc,
+ {{0x3f830025,0x59d00074,0x7bc88c72,0x629a82d5}}, // vaju_, _तोहर, rddu, uuto,
+ {{0x629ab8bc,0x3f8310ab,0x51f80a14,0x27ec80e1}}, // ruto, waju_, днюю_, _hodn_,
+ {{0x38601536,0x3f830025,0x6e2290ab,0xd90f04c0}}, // _cuir_, taju_, _ijob, شید_,
+ {{0x629a8637,0x67ef813c,0x2d84b8bd,0x59dd001b}}, // puto, _højr, lame_, याहर,
+ {{0x3f830025,0x60c438be,0x7bc738bf,0xa01b0338}}, // raju_, _esim, _raju, lsök,
+ {{0x3f830052,0x7c3ab8c0,0x4d7b80be,0x2ca938c1}}, // saju_, lltr, ַרבע, ttad_,
+ {{0x6458b8c2,0x3f830052,0xe81fb8c3,0x7bc7031b}}, // [2590] _kivi, paju_, _बदला_, _paju,
+ {{0x2ca938c4,0x2c64016d,0x2d84b8c5,0x59dd016f}}, // rtad_, _död_, hame_, यावर,
+ {{0xa3ab053f,0x6fdd835a,0x3a2fb8c6,0x2ca9180a}}, // क्ष_, पासू, togp_, stad_,
+ {{0x6458b8c7,0xdb260077,0x2d848110,0x7bc70010}}, // _livi, رونی, jame_, _waju,
+ {{0x7bc738c8,0x9f5800f7,0x6d5d0420,0x25ad8106}}, // _taju, mhrá_, _igsa, vfel_,
+ {{0x764d1d5a,0x248938c9,0x07a29182,0xbcfb0032}}, // lmay, msam_, гашн, _agég,
+ {{0x25ad8012,0x49bb8077,0x9f458722,0x82a401a1}}, // tfel_, _وارد_, _allò_, _иште,
+ {{0xcf58004c,0xa3ab09a3,0x2d84b8ca,0x764d03bf}}, // ובות_, क्र_, game_, nmay,
+ {{0x2d9f8333,0xed5982ce,0x248938cb,0x25adb8cc}}, // rgue_, ježe_, nsam_, rfel_,
+ {{0x3ec48364,0x69cb8c4d,0x6e229d36,0x5ba980bf}}, // ästä_, ldge, _ejob, чком_,
+ {{0xa91d807a,0x61ed0087,0x3d0f8035,0x764d02d0}}, // _deže, _boal, _तेरे_, kmay,
+ {{0x61ed38cd,0x69cb84eb,0xc7a90bea,0x25e0816f}}, // _coal, ndge, _לב_, काणी_,
+ {{0xe9ff8104,0x7e61837a,0x69c9808e,0x9f45823e}}, // _nhận_, _hulp, _maee, _molí_,
+ {{0x7a38b8ce,0xb8e8001b,0x2c64016d,0x6e3b9a7f}}, // ríti, _ईन_, _röd_, llub,
+ {{0x6e29016b,0x1da686a7,0xb87b01df,0xf6262ba7}}, // čebn, क्कत, rxía, едво,
+ {{0x60c438cf,0x4423008e,0xa2c300bc,0x91fd01a9}}, // _tsim, _ajj_, रबन्, spār,
+ {{0xdd8f0c48,0x798638d0,0x24891849,0x60c42cc3}}, // [25a0] صول_, makw, gsam_, _usim,
+ {{0x61ed0613,0x26c5835f,0x2d84b8d1,0x7aea0106}}, // _zoal, _oslo_, zame_, äfto,
+ {{0x6e3bb8d2,0xdb1c0073,0xeac9877f,0x2bf68158}}, // klub, _març, _jẹ_, עמען_,
+ {{0xeac98028,0x798638d3,0x5fde001b,0x4911852a}}, // _mẹ_, nakw, मावल, _देहो_,
+ {{0x2d8491ba,0xdb1a80f1,0x6b850748,0x27fe82f7}}, // vame_, tetë, bahg, _pltn_,
+ {{0xfaa638d4,0xe7e30035,0xdcc88129,0x290688f1}}, // _паго, खाया_, _rỉ_, rzoa_,
+ {{0x75f8026f,0x4df43281,0x2d84b8d5,0x2d82203d}}, // _nízk, _इकाई_, tame_, _keke_,
+ {{0x6458b8d6,0x63be04c4,0x433b0158,0xc1ca0bb8}}, // _sivi, lepn, _זעלב, ियोग,
+ {{0x2d84b8d7,0x61ed38d8,0xdb1c38d9,0x798638da}}, // rame_, _roal, _barç, dakw,
+ {{0xdee68b9c,0xa01b04b8,0x61ed0812,0xdb1ab8db}}, // _пози, rsök, _soal, leté,
+ {{0x645891b9,0x645d8091,0x629e38dc,0x59dd001b}}, // _vivi, ísir, nupo, यालर,
+ {{0x2d820025,0xdb1a8019,0xf54f0a2c,0xaca48133}}, // _neke_, neté, _bụla_, _abụz,
+ {{0xa3c238dd,0x66040ce1,0xed59811f,0x61ed0493}}, // ्या_, dhik, teže_, _voal,
+ {{0x2bd7823c,0x629e279f,0xa91db8de,0xdb1c360c}}, // _भोपा, kupo, _idžt, _garç,
+ {{0x79862f0b,0x764d03bf,0xed599502,0xf647016c}}, // bakw, tmay, reže_, ехан,
+ {{0xbddb02be,0xe9ff801c,0x7bdc01ec,0x7bca925c}}, // _thèm, _phận_, _anru, _hafu,
+ {{0xdbf98063,0x76aa8201,0x764d38df,0x5d550ae7}}, // [25b0] _głów, _dəyi, rmay, ткат,
+ {{0xb60381ac,0x7bca87de,0xdd868061,0xb60680ce}}, // čšin, _jafu, _ھو_, lešć,
+ {{0x7bca822e,0x2489153a,0x764d01cc,0xb05b0338}}, // _mafu, ssam_, pmay, sväs,
+ {{0x660438e0,0x38a9b8e1,0xe9ff80ff,0x7bcab8e2}}, // chik, túr_, _thận_, _lafu,
+ {{0x8fa599b8,0x69cb89ff,0x491180c2,0x28f8804a}}, // вале, rdge, _देशो_, нець_,
+ {{0x7bca819e,0xe7398ce6,0x673a8123,0xe8d9027d}}, // _nafu, мел_, _bytj, _ngọ_,
+ {{0x38a981a8,0xd00eab7e,0x798629f7,0x3d150072}}, // súr_, تلو_, yakw, _नेते_,
+ {{0xa91da35d,0x53340615,0xeac9877f,0xed59a1f6}}, // _vežb, _сест, _rẹ_, мой_,
+ {{0xdb1c03bf,0x6e3b8359,0x7bcaa016,0xf1bf00e1}}, // _parç, rlub, _bafu, deá_,
+ {{0x75f5803e,0x798638e3,0x6e3bb8e4,0x7bca87b6}}, // _názv, wakw, slub, _cafu,
+ {{0xf1dd053e,0x91f581ab,0x798638e5,0xfbd20039}}, // यांन, _आवाज_, takw, בתי_,
+ {{0x22490205,0xa5bb007b,0x3f87b8e6,0xe8d9019d}}, // _chak_, _sjón, lanu_, _egọ_,
+ {{0x2d8238e7,0x7986062b,0xd9e380ab,0x63b50654}}, // _reke_, rakw, _মতাম, _obzn,
+ {{0xdb1a8065,0x35a7800f,0x3dcdb8e8,0x2d8238e9}}, // zeté, _गाड़, ldew_, _seke_,
+ {{0x443338ea,0x2d8238eb,0x660438ec,0xb05b0106}}, // dox_, _peke_, thik, tvär,
+ {{0x6b839755,0x25bf82b1,0xf1bf00f7,0xdb0181ec}}, // _heng, neul_, ceá_, rflä,
+ {{0x2cada543,0x443338ed,0x7bca9d1b,0x6b83b8ee}}, // [25c0] nted_, fox_, _yafu, _keng,
+ {{0x395f9e59,0x660438ef,0x389b0158,0x0e6600ae}}, // _agus_, shik, _קיינ, _икон,
+ {{0x6b839dae,0x201838f0,0x3f87b8f1,0x395f8359}}, // _meng, miri_, danu_, _bgus_,
+ {{0x629e38f2,0x201838f3,0xb33c84b7,0x63be02f7}}, // rupo, liri_, ngħa, sepn,
+ {{0x629e38f4,0x25bf82b1,0x2cadb358,0xf1aa0c28}}, // supo, deul_, jted_, _कानन,
+ {{0x9346217e,0x3f87b30d,0xe9ff8028,0xf1e40105}}, // _анге, ganu_, _phản_, गाड़_,
+ {{0xcac702de,0x2ca00fb0,0xa2e338f5,0x1ae30436}}, // _игре_, luid_, _корд, _корм,
+ {{0x645c011e,0x201838f6,0x7c3e1ff5,0x25bfb8f7}}, // _hiri, hiri_, llpr, geul_,
+ {{0x201838f8,0x6b83b8f9,0x443838fa,0x69d3826f}}, // kiri_, _beng, _kkr_, ádež,
+ {{0x69a5035a,0x645c38fb,0x201838fc,0x44382daf}}, // _काही, _jiri, jiri_, _jkr_,
+ {{0x6b839dae,0x201820eb,0xcfa99a3c,0x25bf8a2a}}, // _deng, diri_, _سالم_, beul_,
+ {{0xc333073a,0x7a388510,0x645c38fd,0x7bcaa73f}}, // יות_, pítu, _liri, _wafu,
+ {{0x6b83b6bd,0x2cad8051,0x201838fe,0x7bca8b4a}}, // _feng, cted_, firi_, _tafu,
+ {{0x6b83b8ff,0xdb1703a7,0xaca4082e,0x3eba02f7}}, // _geng, nexã, _chọr, _bppt_,
+ {{0x6602a2ba,0xbe881d51,0x6d460102,0x7a3d0036}}, // _klok, ксте_, _azka, héti,
+ {{0x443eb900,0x645c3901,0x3f878024,0xa91db8b7}}, // llt_, _airi, zanu_, _leža,
+ {{0x645c3902,0x20180542,0x73d9221f,0x6b838d4e}}, // [25d0] _biri, biri_, ндар_, _yeng,
+ {{0x33740b01,0xaca40133,0x46ac06a7,0x25bf8087}}, // игур, _ghọr, _चहचह, zeul_,
+ {{0x443eb903,0x6602b904,0x3f87b905,0x7d09847f}}, // ilt_, _olok, vanu_, zzes,
+ {{0x443e82af,0x645c3906,0xa2940d8e,0x3f87b907}}, // hlt_, _eiri, раці, wanu_,
+ {{0x69349d85,0x3f878a3b,0xa91db908,0xe1348110}}, // анцу, tanu_, _beža, анцы,
+ {{0x645c3909,0x660292bf,0x13a7015b,0x2fde8118}}, // _giri, _alok, _جنسی_, _cntg_,
+ {{0x6b83b90a,0x7a388065,0x6b888144,0x2be00f97}}, // _reng, gíts, dadg, नारा,
+ {{0x6b83a4ff,0x2018390b,0x656d355e,0x9d458b79}}, // _seng, ziri_, ncah, лейд,
+ {{0x6b83859c,0x629ab90c,0x765d2ac5,0x89378875}}, // _peng, erto, _misy, _شعرا,
+ {{0x6602b029,0x60c9b90d,0x645c02a3,0x2cadb90e}}, // _elok, _isem, _xiri, rted_,
+ {{0x6602b90f,0x2cadb910,0x6b83b911,0x7d09900b}}, // _flok, sted_, _veng, szes,
+ {{0x6b83b912,0x201837a3,0x3f85b913,0x443eb914}}, // _weng, wiri_, _helu_, alt_,
+ {{0x25e0853f,0x20183915,0x657b903b,0x6b888079}}, // कारी_, tiri_, mbuh, badg,
+ {{0x9d1a8158,0x5ebb00c8,0x60c9b916,0x765d178f}}, // _אונט, _উপজে, _msem, _aisy,
+ {{0x20183917,0x657b82a5,0x7bc18e9a,0x3f85b918}}, // riri_, obuh, melu, _melu_,
+ {{0x645c3919,0x2018391a,0x60c9b91b,0xa3cc0beb}}, // _siri, siri_, _osem, _शोज_,
+ {{0x271f890a,0x20182605,0x765d391c,0x248db91d}}, // [25e0] यपुर_, piri_, _disy, nsem_,
+ {{0xdb150118,0x20180168,0xdb08b91e,0x6d4603c1}}, // _nazó, qiri_, yedü, _vzka,
+ {{0x645c391f,0x60c9b920,0xa91d99b7,0x200783ed}}, // _viri, _asem, _reža, dhni_,
+ {{0x7a3d10dd,0x645c3921,0x3f8a3922,0x248db923}}, // téti, _wiri, labu_, ksem_,
+ {{0x645c3924,0x7bc1a5e0,0x3f85b925,0x44383926}}, // _tiri, kelu, _belu_, _tkr_,
+ {{0x3f85b927,0x67d4af84,0x7a3d0866,0x645c051e}}, // _celu_, року, réti, _uiri,
+ {{0x60c9b928,0x7bc1b929,0x3f858b89,0x6602b92a}}, // _esem, delu, _delu_, _plok,
+ {{0x4427826c,0x7bce392b,0x3f8a2813,0x629a80e1}}, // _ajn_, _habu, habu_, vrto,
+ {{0xa91d86c2,0x2fde80dd,0xed59812b,0x7a3881ad}}, // _teža, _tntg_, teža_, síts,
+ {{0x7bc1844e,0x20078b04,0x443eb92c,0x7bce392d}}, // gelu, chni_, ult_, _jabu,
+ {{0x7bce31fe,0xd43700be,0xed59826c,0x81ce0264}}, // _mabu, רטיי_, reža_, রান_,
+ {{0x7bce392e,0x5fe2863a,0x6602922e,0xd7e28072}}, // _labu, पावल, _ulok, पावच,
+ {{0x7bc1a13f,0x78a281e8,0x765d00b9,0x69cf0106}}, // belu, nuov, _risy, rdce,
+ {{0x7bce392f,0xdb1ab930,0xb8ed03e8,0xbcfb0118}}, // _nabu, letí, _रन_, _ofér,
+ {{0x3219096a,0xa91db931,0xf3f90087,0x78a29639}}, // pisy_, _nežn, _îţi_, huov,
+ {{0xe66410f8,0x7bce3932,0x2bc706a7,0x78a2b933}}, // стро, _aabu, रजभा, kuov,
+ {{0x3f8a022e,0x2616108a,0x7bce2ae5,0x69c28bc5}}, // [25f0] babu_, _नगरी_, _babu, deoe,
+ {{0x59dd016f,0x26e49d89,0xa91d81ac,0x5d541860}}, // याकर, _कपूर_, _bežn, скут,
+ {{0x753d0063,0xdb152d11,0x7afa8f06,0x63a501a8}}, // _wysz, _razó, nytt, rghn,
+ {{0x3f858067,0x6562836a,0x2614016f,0x7bc1b934}}, // _selu_, _ngoh, नोदी_, zelu,
+ {{0x3f8590ab,0xa3d486ab,0x7afa8d1a,0x2259128a}}, // _pelu_, हया_, hytt, írky_,
+ {{0xb8cd923a,0xb4c101fe,0xa3ac01b6,0xa3c21905}}, // _कम_, ंबी_, _गाय_, ्यः_,
+ {{0x7bc1802e,0x2c6980e1,0x69c29c11,0x3f858609}}, // velu, _súd_, beoe, _velu_,
+ {{0x7bce3935,0x2007b936,0x6b870b80,0x7bc1b937}}, // _zabu, shni_, _hejg, welu,
+ {{0x81ce0a49,0xccf20158,0x7bc1b938,0xb882000d}}, // রাম_, ַכט_, telu, říze,
+ {{0x3ea300f1,0x798bb939,0x1da781ab,0x5e8781e2}}, // kujt_, lagw, _गावत, _будз,
+ {{0x200581b9,0x248db666,0xa72000ab,0x7bc18087}}, // _illi_, ssem_, _ধর্ম_, relu,
+ {{0x186a0c8e,0xdb0529ed,0x7bc1abf2,0xc04903c8}}, // ками_, behö, selu, _חז_,
+ {{0x39490063,0x3f8a393a,0x7d0d0bcf,0x2b158074}}, // _czas_, tabu_, jzas, _फेरु_,
+ {{0x2bd505b3,0x661b8110,0x68e204b7,0x8fa600a9}}, // _डोला, liuk, _ġodd, јаве,
+ {{0x7bce393b,0x539a8039,0x3f8a393c,0xe73a0323}}, // _rabu, _סיקו, rabu_, тев_,
+ {{0x7bce393d,0x3f8a004f,0x75f5816b,0xa91d81f4}}, // _sabu, sabu_, _zázr, _režn,
+ {{0xdca60ba8,0xd46718a0,0x7bce1b4d,0xed5a00a9}}, // [2600] рази, иите_, _pabu, тоа_,
+ {{0x9f4c25b3,0x7bce393e,0x4f96393f,0x25ab0609}}, // ódó_, _qabu, _триу, _eccl_,
+ {{0xbf9b012a,0x6441b940,0x213f8748,0x661bb941}}, // _בייש, illi, _ayuh_, kiuk,
+ {{0x7bce0533,0x64a63197,0xbcfb0019,0x2005b942}}, // _wabu, _кама, _igén, _alli_,
+ {{0x7fe9019f,0xe7e29a1c,0xb5fb0118,0x5fe2801b}}, // _شریف_, पालप, _fiáb, पालल,
+ {{0xdb17002a,0x7afa8198,0x224d9fa4,0x26da1502}}, // sexá, yytt, _ehek_, _krpo_,
+ {{0x9e06a3e0,0xddc400eb,0x2900a8c4,0x42d505a8}}, // ичил, rmiņ, šia_, _літу,
+ {{0xa01b2522,0x64418009,0xdef78190,0x88818065}}, // nsör, elli, рыш_, _کیون,
+ {{0xafe6b943,0xa06a8198,0x75fc8176,0x07a69fab}}, // _коал, _раза_, _jézi, _вазн,
+ {{0x34b70051,0xe9ff8104,0xf366a550,0x690883bf}}, // ספים_, _phần_, штин, ırdı,
+ {{0xa3ab1391,0x8aa39fb4,0x6609805d,0x03d7007c}}, // क्ट_, оряд, bhek, בוים_,
+ {{0xf2d30158,0x9f5800f7,0x6609804f,0x6441b853}}, // קער_, bhrú_, chek, alli,
+ {{0x1bd48698,0xc31a00c8,0xbcfb3353,0x7afa8009}}, // _голя, _তুমি_, _agén, sytt,
+ {{0xe9ff8028,0x2005007b,0x9f4c816b,0x331780d7}}, // _thần_, óli_, _lodí_, ازید_,
+ {{0x543980be,0x798b8545,0xf8ae053d,0xc66809a5}}, // _געװא, yagw, وکی_, иште_,
+ {{0x442a3944,0x7a2801cc,0xe9df002a,0x443c80c3}}, // _ajb_, nıtl, flúe_, _hkv_,
+ {{0xf36701f3,0x39490289,0x442a3945,0x201cb307}}, // [2610] атен, _uzas_, _bjb_, kivi_,
+ {{0x60cd3946,0x442a0b80,0x81ce00ab,0x224d8168}}, // _isam, _cjb_, রাণ_, _shek_,
+ {{0xb5fb0118,0xa91d81a1,0x2c7d0032,0x931901a8}}, // _viáb, _pežo, _bádò_, دقاء_,
+ {{0x38690748,0xa2d7b947,0x644e0f3e,0xdb019277}}, // _kuar_, यित्, _ehbi, rflø,
+ {{0x98c7917e,0xe9df04c3,0x442a1142,0xe9ff801c}}, // рсел, clúe_, _fjb_, _nhấn_,
+ {{0x38693948,0x25a93949,0x60cd004f,0x7e68810c}}, // _muar_, lgal_, _msam, _budp,
+ {{0x386914ff,0x6609b94a,0xa91d817f,0x2294160e}}, // _luar_, thek, _težo, _миря,
+ {{0xc485b6ab,0x25a9394b,0xf1cf8105,0x5bc711bc}}, // блик, ngal_, _सोचन, _रघुव,
+ {{0x201c811f,0x999f826f,0x386900b9,0xe9ff80ff}}, // bivi_, _chuť_, _nuar_, _chấn_,
+ {{0x75f58feb,0xf09f394c,0x6609b94d,0x2005b94e}}, // _vázq, drà_, shek, _ulli_,
+ {{0xd7f88554,0x60cd394f,0x7c2200e7,0xf09f047f}}, // _тут_, _asam, éori, erà_,
+ {{0xa3de035a,0x60cd00b9,0x7bc50168,0x6441b950}}, // _दोन_, _bsam, hehu, slli,
+ {{0x7bc521ad,0xed4e9da9,0xa3e803db,0x7989b951}}, // kehu, _хо_, _मचल_, _meew,
+ {{0x6d4bb952,0x438580f7,0x183580be,0x7989b953}}, // _izga, _الثق, מאָל_, _leew,
+ {{0x60cd3954,0xa3ab0cf0,0xf09f023e,0x249f9a16}}, // _esam, क्छ_, arà_, lrum_,
+ {{0x25a9015b,0x60dbb955,0x38693956,0x4d66044f}}, // ggal_, _orum, _fuar_, скав,
+ {{0x61e428af,0xe3ba0fbe,0x7a3d007b,0x1b1500ab}}, // [2620] _inil, лба_, rétt, _তুলে_,
+ {{0x69c63957,0x25a900f7,0xf1bf00f7,0x7bc53958}}, // leke, agal_, nnán_, gehu,
+ {{0xa3b92cdd,0x60dba60d,0x7a280214,0x59dd06ae}}, // _अउर_, _arum, yıtl, याचर,
+ {{0x61f60901,0xdb1a87f0,0x7a3d3959,0x994a81a8}}, // _joyl, netá, létr, هلال_,
+ {{0x201cb95a,0x2d80063c,0x10a3b95b,0x98a392b2}}, // tivi_, mbie_, зичн, зиче,
+ {{0x60dba37a,0x7644395c,0x7a3d2ad5,0x69c613fa}}, // _drum, lliy, nétr, heke,
+ {{0xfaa32cd1,0x69c6395d,0x201c939a,0x443a056a}}, // _наро, keke, rivi_, mop_,
+ {{0x4444395e,0x60dbb95f,0xa3c20076,0x5c74917f}}, // ll_, _frum, ्यक_, олит,
+ {{0x69c60065,0x8cdb01a2,0x60dbb960,0x2a6a00b9}}, // deke, नियो, _grum, _hubb_,
+ {{0x61e43961,0xda7a8d70,0x44443962,0x443a000b}}, // _anil, лям_, nl_, nop_,
+ {{0x44441dad,0x76443963,0x61f63964,0xdb1a8144}}, // il_, kliy, _boyl, fetá,
+ {{0x69c63965,0x64a6add0,0x444413ac,0xf09f1e06}}, // geke, _гада, hl_, trà_,
+ {{0x443a3966,0x41aa101e,0x63bc0824,0x4444083d}}, // kop_, _कारस, _obrn, kl_,
+ {{0x3e6e000d,0x44443967,0xf09f19bf,0x6e3d00b9}}, // _být_, jl_, rrà_, _sksb,
+ {{0xd5bb17d6,0x69c63968,0x76443969,0xa3d5801b}}, // лса_, beke, fliy, ाएर_,
+ {{0x4444396a,0x60cd396b,0x7c3ab732,0x69c6396c}}, // el_, _tsam, lotr, ceke,
+ {{0x320910ba,0x6fa9816f,0x7bc5396d,0x3f8eb0a0}}, // [2630] _ilay_, _घालू, tehu, kafu_,
+ {{0xf367396e,0x4444396f,0x7b671287,0x7c28b970}}, // стан, gl_, стае, nndr,
+ {{0x76443971,0x2ca00074,0xb99592c8,0x2367007a}}, // bliy, arid_, قلاب, žnjo_,
+ {{0xddc1802e,0x80cc00ab,0x7a28011c,0xdb088338}}, // _mulţ, ামর্, yıtm, bedö,
+ {{0x4444031d,0xb05b00e1,0x7bc53972,0x3f8e89ab}}, // bl_, dväz, pehu, fafu_,
+ {{0x69c63973,0x7c3a9c67,0x63bc008e,0x249fb974}}, // zeke, jotr, _gbrn, vrum_,
+ {{0x41aa0f12,0x2d8b0679,0x1dda8105,0x3209007e}}, // _कालस, _kece_, _मोहत, _olay_,
+ {{0xa3e70076,0xf1d9000f,0xd346003d,0x64453975}}, // भाव_, _बोलन, _بیمه_, llhi,
+ {{0x7c3ab976,0xdb1e0087,0x60dbb977,0x69c61104}}, // fotr, cepâ, _urum, veke,
+ {{0x32093978,0x69c63979,0x61e4397a,0x249fb97b}}, // _alay_, weke, _snil, rrum_,
+ {{0x69c6397c,0x2fc7b97d,0x5fae816f,0x26c101c0}}, // teke, meng_, _घातल, _npho_,
+ {{0x2d8b02a5,0x2fc7b97e,0x3ea78722,0xf1bb80d4}}, // _nece_, leng_, munt_, ोजिन,
+ {{0x6e3b8024,0x443a10c1,0x29041ef5,0x660d003d}}, // moub, yop_, áma_, dhak,
+ {{0x69c6397f,0x2fc783f8,0x78a60289,0x290c009a}}, // seke, neng_, rukv, ąda_,
+ {{0x4444006a,0xdb1ab980,0x69c63981,0x764407c0}}, // vl_, petá, peke, tliy,
+ {{0x3d150665,0x2fc7b982,0x2d8b3983,0x660d0ec9}}, // _नइखे_, heng_, _cece_, ghak,
+ {{0x2d8b0eef,0x76440201,0x2fc7b984,0x44442ead}}, // [2640] _dece_, rliy, keng_, tl_,
+ {{0x44443985,0x2fc78077,0xa91d82d4,0x41aa03eb}}, // ul_, jeng_, _težj, _काँस,
+ {{0x42ca0071,0x443a3986,0x2fc7b987,0x2d8b047f}}, // рген_, rop_, deng_, _fece_,
+ {{0xa3d58063,0xe0da9935,0x660d3988,0xfce616df}}, // ाएँ_, иве_, chak, _домо,
+ {{0x7e628118,0x7c288558,0x9989136f,0x660403e4}}, // _riop, yndr, _dlaň_, mkik,
+ {{0x2bae9305,0x7e62b989,0x2fc7b98a,0xdc3987c0}}, // ज्ञा, _siop, geng_, kçıl,
+ {{0x491a800d,0x23270098,0xe2970d5f,0x6fad000f}}, // _मेरो_, _дори_, _мар_, _जासू,
+ {{0x16378013,0x7ae10065,0xd5b78987,0xb5fb36fa}}, // يسية_, _álta, ось_, _lián,
+ {{0xa3d58063,0x3f8e84cd,0xf6530039,0x7982a4f2}}, // ाएं_, pafu_, וצה_, mbow,
+ {{0x22400019,0x3ea78176,0x224681b9,0x60cd9b26}}, // _akik_, bunt_, llok_, çame,
+ {{0x32092a92,0xdd8f8279,0xe73a804a,0x66042c6b}}, // _play_, _ош_, _теж_, kkik,
+ {{0x7c3ab98b,0x601000f2,0x80ac0816,0x2246808e}}, // sotr, _jämf, _जमशे, nlok_,
+ {{0x7c3ab98c,0xfbcf87d2,0x2d8b398d,0xd2580a8e}}, // potr, رتی_, _rece_, юць_,
+ {{0x6e20b98e,0x39450029,0xd83b22f0,0xb4d690be}}, // limb, āls_, рэм_, ाटी_,
+ {{0x28d1800c,0x2246b98f,0x660d0c14,0x78a2826c}}, // _दैनि, klok_, thak, hrov,
+ {{0x6e208397,0x2246b990,0xdb1c02af,0xa5bb008b}}, // nimb, jlok_, _darü, _njót,
+ {{0x2d8b0052,0x660d3991,0x31690019,0x78a288ae}}, // [2650] _vece_, rhak, _igaz_, jrov,
+ {{0x660d0578,0x6e20b992,0xdb1c3993,0x69bc0f8d}}, // shak, himb, _naró, ष्ठी,
+ {{0x3f913994,0x6e209e45,0xa91d8353,0x660d005d}}, // mazu_, kimb, _težk, phak,
+ {{0x3f91005c,0xdce982ee,0x2fc7b995,0x2a649600}}, // lazu_, zbeđ, weng_, _cimb_,
+ {{0x2fc783f8,0x78a2826f,0xdb1c016a,0xdb150187}}, // teng_, grov, _baró, _razõ,
+ {{0xf77185ff,0xdb1c01df,0xa3da9344,0x7bc88369}}, // رات_, _caró, _ढोल_, jedu,
+ {{0x2fc7b996,0x44210824,0x224693eb,0x63b405b9}}, // reng_, mih_, blok_, žený,
+ {{0x3ea7b997,0x2fc7b998,0xc4859e25,0xed599024}}, // runt_, seng_, плик, leži_,
+ {{0x629c84c3,0x2fc78d6d,0x6e299dcb,0x442e81a1}}, // áron, peng_, rneb, _bjf_,
+ {{0xa91db999,0x3ea7b99a,0xdc39880a,0x7bc8b99b}}, // _neži, punt_, tçıl, gedu,
+ {{0x7bd5399c,0xf8a9010f,0x6e20b99d,0x661d307d}}, // _mazu, _תש_, bimb, _omsk,
+ {{0x44211600,0xdeba8039,0xdb1c0035,0x06c380ab}}, // hih_, _למעל, _zaró, ্মদি,
+ {{0x4421399e,0xfce5b99f,0x3e71b9a0,0xa5bb0125}}, // kih_, _холо, _hát_, _ljós,
+ {{0x442102f7,0xed59b5b2,0x913a80be,0x7bc8b9a1}}, // jih_, ježi_, מערק, cedu,
+ {{0x26de01df,0xa3c10ebf,0x8c3d8214,0xdee334bf}}, // íto_, ्जन_, nuşm, нори,
+ {{0x799b8077,0x3e71801c,0x386d81b0,0x8afa81c6}}, // _nduw, _mát_, _ouer_, _להצי,
+ {{0x3e71b9a2,0x660439a3,0x7bd539a4,0x386d8326}}, // [2660] _lát_, rkik, _bazu, _nuer_,
+ {{0x660439a5,0x7bd50012,0x6e20b3cd,0x69d601b4}}, // skik, _cazu, zimb, _haye,
+ {{0x3f8c8805,0xe6460a13,0x38b60e34,0x6e20b9a6}}, // _redu_, _неоп, lær_, yimb,
+ {{0x23720029,0x3f8cb9a7,0x69d6010c,0x7bc8811b}}, // _šajā_, _sedu_, _jaye, zedu,
+ {{0x4421048f,0x69d6298f,0x38b600b2,0x6e208234}}, // bih_, _maye, nær_, vimb,
+ {{0x2d9239a8,0x4421135a,0x25adb9a9,0x3e71b9aa}}, // laye_, cih_, egel_, _bát_,
+ {{0xc0598cde,0x7bc8b9ab,0x3e7180ff,0xdb1c0511}}, // ції_, vedu, _cát_, _varó,
+ {{0x3e71826f,0x78a2b9ac,0x25adb9ad,0x69d61f4b}}, // _dát_, prov, ggel_, _naye,
+ {{0x6e20b9ae,0x7bd50309,0xb4d68744,0xb05b0106}}, // rimb, _yazu, ाटे_, pväx,
+ {{0x6e2081d3,0x798d30b2,0x2d9239af,0x6d4600ee}}, // simb, _seaw, haye_, _myka,
+ {{0x69d639b0,0x600b0201,0x60c439b1,0x26de80e5}}, // _baye, _nüma, _apim, _urto_,
+ {{0x69d639b2,0xa3de10f7,0x38b60e51,0xb05b0865}}, // _caye, _दोस_, fær_, hwät,
+ {{0xb5fb2a63,0x7bc8b9b3,0x2d9239b4,0x5c068098}}, // _diál, pedu, daye_, _няка,
+ {{0xdb1a8009,0xba73803d,0x7c3e026c,0x545380e8}}, // tetä, بایت, jopr, _звіт,
+ {{0x645a8125,0x69c081ac,0x98be00eb,0xdd9b0a42}}, // mmti, _obme, ātā_, йша_,
+ {{0x443eb72f,0x442103f8,0x2d9239b5,0x7e6600b9}}, // lot_, wih_, gaye_, _cikp,
+ {{0x42380bea,0xa91d803a,0xed5980fe,0x200c852a}}, // [2670] _מנהל_, _teži, teži_, _oldi_,
+ {{0x443eb9b6,0xf0920bea,0x442cb9b7,0x69c08192}}, // not_, _שני_, nnd_, _abme,
+ {{0x2d9239b8,0x442139b9,0x7bd5002e,0x8c4181ec}}, // baye_, rih_, _vazu, äßig,
+ {{0x442114e5,0xa2a483eb,0x200c89e8,0x661d39ba}}, // sih_, _किन्, _aldi_, _umsk,
+ {{0x443eb9bb,0xddc41010,0x386d8073,0x89378013}}, // kot_, lmiş, _quer_, أعضا,
+ {{0xd3668013,0x443e8029,0x442101a5,0x7642930c}}, // _له_, jot_, qih_, _akoy,
+ {{0x443eb9bc,0x6e2d39bd,0x29021d21,0xddc40457}}, // dot_, onab, zyka_, nmiş,
+ {{0x6e2d02ec,0x6448a023,0x442c9527,0xcfcd80ab}}, // nnab, eldi, end_, লাইন,
+ {{0x62988081,0x442c807b,0x443eb9be,0x4c358084}}, // _avvo, fnd_, fot_, дэнт,
+ {{0x443eb9bf,0x69d9a9d8,0x69d639c0,0x69cbb2c7}}, // got_, ldwe, _saye, lege,
+ {{0x61fbb9c1,0x81d700ab,0xdb1c007b,0x69d612b6}}, // _koul, সান_, _jarð, _paye,
+ {{0x69d9b9c2,0xa3c10076,0x29020063,0x61fb8009}}, // ndwe, ्जड_, tyka_, _joul,
+ {{0x200403a7,0x515539c3,0x6e2d0114,0xaca381bc}}, // êmio_, етру, dnab, _maịk,
+ {{0xb8f61008,0x443eb9c4,0x61fba1bf,0x69d6029b}}, // _सन_, cot_, _loul, _waye,
+ {{0x7c3e007d,0xf7430767,0x7f430009,0x2d9239c5}}, // vopr, веро, верж, taye_,
+ {{0x237801dd,0x6e2d39c6,0x69cbb9c7,0x249900b9}}, // _sfrj_, gnab, jege, _bvsm_,
+ {{0x7c3e39c8,0x2d9239c9,0x69cbb9ca,0x60c425f4}}, // [2680] topr, raye_, dege, _upim,
+ {{0x2d8f8c39,0x61e9b9cb,0x2d9239cc,0xa91d81a1}}, // _lege_, _anel, saye_, _ježu,
+ {{0x61fbb9cd,0x76499632,0x320d828d,0x628182d6}}, // _boul, kley, _bley_, nplo,
+ {{0x61fb8809,0x69cb8c1b,0x6b8e0102,0x443eb9ce}}, // _coul, gege, _webg, zot_,
+ {{0x443eb9cf,0x442c807b,0x64488bbd,0x61fbb9d0}}, // yot_, ynd_, yldi, _doul,
+ {{0xd12f9a37,0x6e2439d1,0x656b80e5,0x237802df}}, // جمه_, liib, _aggh, _ufrj_,
+ {{0x443e86d4,0xdb1c007b,0x61fbb9d2,0x2d8fb9d3}}, // vot_, _garð, _foul, _bege_,
+ {{0xceb30051,0xd49a813a,0x3ea0801b,0x442cb9d4}}, // דיה_, оро_, čit_, wnd_,
+ {{0x443eb9d5,0x7d040a40,0x64a383a7,0x2d8fb9d6}}, // tot_, nyis, вања, _dege_,
+ {{0x442c8051,0x61fb8bfe,0xed598654,0x9f5a03ed}}, // und_, _zoul, ježu_, _copë_,
+ {{0x443ea638,0xa3c10054,0x442cb9d7,0x271c8264}}, // rot_, ्जत_, rnd_, _পুরো_,
+ {{0x443eb9d8,0x2d8f90ab,0x20ca0540,0x7d040009}}, // sot_, _gege_, िबंध, kyis,
+ {{0xb4bd1880,0x9cb381a8,0xe8e081bc,0x6e2401b4}}, // _आहे_, _لمنت, daịs_, diib,
+ {{0x36d5146c,0xdb1e0187,0xa29483a9,0x2d8fb9d9}}, // ногр, cepç, _закі, _zege_,
+ {{0x80d5800f,0x69cbb9da,0x683f01d0,0xc5fb0073}}, // _मैने, yege, vádě, оѓа_,
+ {{0xc3338039,0xddc40059,0x7640b9db,0x7bd88bdf}}, // _שוק_, rmiş, nomy, _havu,
+ {{0x61fb9dac,0x69cbb9dc,0x7bd8b9dd,0x54550103}}, // [2690] _roul, vege, _kavu, еват,
+ {{0x69cbb9de,0x61e9b9df,0x61fbb9e0,0x2a69022c}}, // wege, _snel, _soul, _hiab_,
+ {{0x69cbb9e1,0x61fbb9e2,0x2a690069,0x7640b9e3}}, // tege, _poul, _kiab_, komy,
+ {{0x7bd8b9e4,0xdb1c007b,0x7640809a,0xe4a70087}}, // _lavu, _varð, jomy, _орго,
+ {{0xf1c1052a,0x61fb82be,0x69d9800b,0x7d16009a}}, // ष्यन, _voul, rdwe, czys,
+ {{0x61fbb624,0x2a6901c5,0x78ab8cfa,0x7bd8ace3}}, // _woul, _liab_, rugv, _navu,
+ {{0x61fba7a7,0x7649b9e5,0x69cbaf5f,0x2007b9e6}}, // _toul, tley, pege, rkni_,
+ {{0xb8d580c8,0x61e9b9e7,0x2a69022c,0x6ec206a7}}, // _জন_, _unel, _niab_, _लहसु,
+ {{0x3e7539e8,0xdd918117,0x7bd8b9e9,0x3949022c}}, // _låt_, نوں_, _bavu, _nyas_,
+ {{0x7649b9ea,0x6281b52e,0x3707a240,0xaf078992}}, // sley, tplo, _очев, _очек,
+ {{0x3e75016d,0x76498ce9,0x7bd8b9eb,0xdbcc9434}}, // _nåt_, pley, _davu, róðu,
+ {{0x2a69022c,0x7afc816d,0xaca38135,0x6e240079}}, // _ciab_, ärta, _abụk, xiib,
+ {{0xa3c1053f,0x7bd8822b,0xed59812b,0x2a690176}}, // ्जा_, _favu, težu_, _diab_,
+ {{0x7bd8b9ec,0x66099dcb,0x39490122,0x3d188035}}, // _gavu, lkek, _dyas_, _बेटे_,
+ {{0x7bc3836e,0x6e2402ec,0x2a690037,0x84648081}}, // _ibnu, tiib, _fiab_, _ръце,
+ {{0x7d040364,0x6441b9ed,0xee3a00e8,0x7bd89acc}}, // tyis, loli, їна_, _zavu,
+ {{0x44259a29,0xdca30e6b,0x7bd882a0,0x64a30f04}}, // [26a0] oil_, лати, _yavu, лата,
+ {{0x7d160d38,0xa3e7073c,0x4425b9ee,0x68e439ef}}, // rzys, भाग_, nil_, _irid,
+ {{0xa3b7835a,0x387f9e03,0xad9b39f0,0x68e40140}}, // _छान_, _atur_, _clús, _hrid,
+ {{0x44259882,0xa2d51516,0xaca401bc,0x68e439f1}}, // hil_, णिज्, _ajụr, _krid,
+ {{0x6441b7c2,0x394901c0,0x69c439f2,0xc0e38a41}}, // koli, _xyas_, _mbie, лоцк,
+ {{0x4425ac17,0x2ab88065,0xe8d9001c,0x64418805}}, // jil_, yéb_, _ngờ_, joli,
+ {{0x6441b9f3,0x69c439f4,0x0cd0146d,0xa3e70254}}, // doli, _obie, _हनुम, _ядра_,
+ {{0x6ed38086,0x7bd8b9f5,0x68e4026c,0xe7e080d4}}, // _xəbə, _savu, _orid, _गोरा_,
+ {{0x6441b9f6,0xddde00eb,0xf8d49513,0xdb1e2551}}, // foli, _atpū, _धनिय, sepä,
+ {{0x69c439f7,0x6441b9f8,0x2a6910af,0x600b0059}}, // _abie, goli, _siab_, _cüml,
+ {{0x68e439f9,0xa3b6801b,0x2a6901c0,0xdb1c08f1}}, // _arid, ङ्ग_, _piab_, _obrí,
+ {{0xdc12817b,0xc3330039,0x44258c5e,0x7bd8b9fa}}, // rşıl, טות_, ail_, _wavu,
+ {{0x6e22b9fb,0x68e439fc,0x3d189513,0xc05b00e8}}, // _imob, _crid, _बेचे_, зів_,
+ {{0x4425b9fd,0x6441b9fe,0x20d60efd,0x6ed38201}}, // cil_, coli, džić_, _səbə,
+ {{0x9f34021e,0x68e434c2,0x2a690282,0x80c400ab}}, // гері, _erid, _tiab_, শিষ্,
+ {{0x68e439ff,0xe7e786a7,0x45d4004a,0x44d202d6}}, // _frid, _टोना_, лосс, _bņ_,
+ {{0x629aa187,0x629c0201,0x61ed3a00,0x68e40653}}, // [26b0] lsto, _avro, _inal, _grid,
+ {{0x69cf0179,0x69c4219c,0x600b03e3,0xdb01ba01}}, // lece, _zbie, _kümm, rflö,
+ {{0xd0110117,0x61ed3a02,0xdb1a806a,0xc1740041}}, // _ملک_, _knal, getø, _haɗa_,
+ {{0x69cf3a03,0x6441ba04,0xdbd708e5,0x629aba05}}, // nece, zoli, _sääs, isto,
+ {{0x629c3a06,0xdbd703ff,0x44259072,0x6441ba07}}, // _evro, _pääs, yil_, yoli,
+ {{0x63552748,0x44258201,0x44251bc0,0x2ca93a08}}, // _авгу, xil_, _öl_, mrad_,
+ {{0x629a803b,0x25bfba09,0x61ed3a0a,0x4425ba0b}}, // jsto, tful_, _onal, vil_,
+ {{0x62850c63,0x4425ba0c,0x78a48024,0x764d3a0d}}, // mpho, wil_, šiva, nlay,
+ {{0x629a81ac,0x6fb2009a,0x442300b9,0xc1740300}}, // esto, _जालं, _kmj_, _naɗa_,
+ {{0x2367003b,0x62853a0e,0x69db811e,0x629aba0f}}, // žnju_, opho, _haue, fsto,
+ {{0x15b981e5,0x764d07d9,0x68e40140,0x629aba10}}, // _жылы_, klay, _srid, gsto,
+ {{0x68e43a11,0x4425ba12,0xa2c40a74,0xf2d200be}}, // _prid, sil_, _रहस्, בעט_,
+ {{0x4425ba13,0x764d3a14,0x66068019,0x22423a15}}, // pil_, dlay, ökke, rokk_,
+ {{0x60c98133,0x68e40b81,0x7fd50558,0x61ed3a16}}, // _mpem, _vrid, лісі, _enal,
+ {{0x69cf0207,0xa0673a17,0x629aba18,0x80b3864a}}, // bece, тара_, csto, उंडे,
+ {{0x6458820f,0x764d3a19,0x940c0201,0xf1bf0333}}, // _zhvi, glay, ələr_, lián_,
+ {{0x2ca91487,0xdd8f0875,0x05661289,0x8e5500e8}}, // [26c0] grad_, زول_, тван, утрі,
+ {{0x61ed0ebf,0x7bc1ba1a,0x6d4b851e,0x62868904}}, // _znal, nflu, _myga, ćkov,
+ {{0xe2970bac,0x3f9804b9,0x69db81ec,0x442300c3}}, // вах_, maru_, _baue, _dmj_,
+ {{0x3ced017f,0x69db9e9e,0xaca38091,0x2ca93a1b}}, // _ševe_, _caue, _apọj, brad_,
+ {{0x69db9919,0xa967951b,0xd7fa8162,0x8c3dba1c}}, // _daue, вица_, дуй_, luşu,
+ {{0xbcfb0065,0x442300ee,0x25daa743,0x9f6580e1}}, // _egés, _gmj_, _खोजी_, _štát_,
+ {{0x48ee8063,0x69cf07d9,0x59dd93ba,0x8c3d82d0}}, // _आपको_, yece, _नोकर, nuşu,
+ {{0x44443a1d,0x7bdc3a1e,0xe5c68895,0x3f983a1f}}, // lo_, _haru, тско, haru_,
+ {{0x2603a70d,0x3f9803c3,0x7bdc3a20,0x629aba21}}, // _años_, karu_, _karu, wsto,
+ {{0x44443a22,0x629a8a10,0x7bdc09ab,0x7bde000b}}, // no_, tsto, _jaru, ndpu,
+ {{0x44443a23,0x6e2281e0,0x60148722,0x81dc00ab}}, // io_, _tmob, _càme, ঠান_,
+ {{0x44443a24,0x7bdc3a25,0x3f933a26,0x7e6b8122}}, // ho_, _laru, _sexu_, _gigp,
+ {{0x44443a27,0x3e78ba28,0x69dd3a29,0xf1bf188b}}, // ko_, _hét_, rdse, bián_,
+ {{0x44443a2a,0x3e788065,0x629a84fe,0x69cf3a2b}}, // jo_, _két_, psto, sece,
+ {{0x44443a2c,0x5f943a2d,0x7c28a6ca,0xa3ac064a}}, // do_, рист, midr, _गाज_,
+ {{0x4444229d,0x3ce6805c,0x7bdc0091,0x61ed3a2e}}, // eo_, _krov_, _aaru, _unal,
+ {{0x444405ee,0x68e29487,0x7bdc3a2f,0x764d0059}}, // [26d0] fo_, jvod, _baru, rlay,
+ {{0x7bdc10da,0x600b0182,0x69dbba30,0x2ca93a31}}, // _caru, _mümk, _saue, rrad_,
+ {{0x764d3a32,0xf8bf00e7,0x3e78801c,0xba7401a8}}, // play, lté_, _nét_, لانت,
+ {{0xc3338051,0x68e20029,0xb05b0884,0x60100106}}, // בוע_, _šodi, städ, _jämn,
+ {{0x44443a33,0xf8bf3a34,0x7bdc3a35,0x3ce68069}}, // bo_, nté_, _faru, _nrov_,
+ {{0x601004b8,0x7bdc37a5,0xf8bf00e7,0x8c3d82d0}}, // _lämn, _garu, ité_, nuşt,
+ {{0xaa67b1db,0x8cdb0c1c,0xf1bf0118,0x7afc8338}}, // _атак, निको, vián_, ärtl,
+ {{0x53340ab5,0x64451066,0x7bdc136f,0x6010016d}}, // _тест, lohi, _zaru, _nämn,
+ {{0xc104a181,0x29198711,0x7bdc3a36,0x660d3a37}}, // _پولي, _åsa_, _yaru, ikak,
+ {{0x6b950024,0x22490ad4,0x7bdc02a3,0xa2a480d4}}, // _jezg, _akak_, _xaru, _किश्,
+ {{0x3f983a38,0x7bc1ba39,0x660d3a3a,0xdd91803d}}, // varu_, rflu, kkak, _هوا_,
+ {{0x644504b9,0x3f980f3d,0x7999a914,0x9d199290}}, // hohi, waru_, naww, _поет_,
+ {{0x3ce68bbd,0x625880f7,0x64452020,0x7c2884e1}}, // _grov_, líoc, kohi, bidr,
+ {{0x44443a3b,0x3e718038,0x31568158,0x6e29ba3c}}, // xo_, _ešte_, טירן_, lieb,
+ {{0xdb1c0a56,0x6da61980,0x3f983a3d,0x3e78801c}}, // _obrá, лива, raru_, _xét_,
+ {{0x44443a3e,0x7bdc3a3f,0x660d04d2,0x2bc606b7}}, // wo_, _saru, gkak, र्ना,
+ {{0x44440323,0x7d098a5a,0xf8bf02be,0xe45a0abe}}, // [26e0] to_, nyes, cté_, ежа_,
+ {{0x78a90904,0x2013008e,0x44441823,0x6e298192}}, // ševc, _glxi_, uo_, hieb,
+ {{0x7bdc08a3,0x91e6aeb2,0x81d700ab,0x2cbf8074}}, // _varu, ложе, সার_, htud_,
+ {{0x44443a40,0x7bdc3a41,0xe947804e,0x2cbf8122}}, // so_, _waru, گرمی, ktud_,
+ {{0x859b0051,0x7bdc3a42,0xa3b3816f,0x69c2ba43}}, // _אשכו, _taru, टला_, rfoe,
+ {{0xd6da860a,0xa2a4816f,0x44443a14,0xa0679ad8}}, // ети_, _किल्, qo_, гача_,
+ {{0x5fb20076,0x62588013,0x7c28ba44,0x645c00e1}}, // _जाईल, ríob, vidr, _ihri,
+ {{0x3ce6888b,0xa5bd8084,0xf8bf04e8,0x7c288799}}, // _prov_, liųj, yté_, widr,
+ {{0x27ef800d,0x7c28ba45,0x20183a46,0x247202d6}}, // ální_, tidr, khri_, _fņme_,
+ {{0xd12f87bd,0x610a80eb,0x660d33bc,0x63ad8041}}, // _عمل_, _vēlā, zkak, _ɓang,
+ {{0xa68697f9,0x2bc623bd,0xfbc60f1b,0x629e3a47}}, // _след, र्या, र्यम, nspo,
+ {{0x54e688ca,0xaca43a48,0xf8bf2538,0x9f5e9b3a}}, // _مستق, _akọr, tté_, _noté_,
+ {{0xf8bf02be,0x02d110a1,0x645c3a49,0xe9df3a4a}}, // uté_, _सन्न, _ohri, rdú_,
+ {{0x2bc6053f,0xddd00503,0xf8bf02be,0xc1740326}}, // र्मा, _češk, rté_, _faɗo_,
+ {{0xf8bf3a4b,0x6b7b812a,0x6e95079e,0xdce083bf}}, // sté_, _ארונ, рибу, mamı,
+ {{0x66028006,0xdce083bf,0x629e3a4c,0xf8bf00e7}}, // _jook, lamı, dspo, pté_,
+ {{0x64450284,0x660d3a4d,0x645c0083,0x8234026a}}, // [26f0] tohi, rkak, _bhri, _دروا,
+ {{0x6b9a809f,0x6602ba4e,0x645c3a4f,0xbd6817ae}}, // natg, _look, _chri, урсе_,
+ {{0x64453a50,0x629e3a51,0xdddc011f,0xdb1a8118}}, // rohi, gspo, sprš, metó,
+ {{0xfde912c6,0x660285ee,0x60cd0133,0xb5fb2620}}, // _ऑफिस_, _nook, _kpam, _ciát,
+ {{0x69df00f1,0x26cc82c4,0x645c0635,0x02d1ba52}}, // _maqe, _apdo_, _fhri, _तन्न,
+ {{0x60cd2fc0,0x645c032f,0x60103a53,0x7999ba54}}, // _mpam, _ghri, _näml, saww,
+ {{0x6602ba55,0x629e0088,0xe57180f7,0x6e29ba56}}, // _book, cspo, يطة_, tieb,
+ {{0x82378013,0x62588013,0x7aea013c,0x6602ba57}}, // _إرسا, ríoc, æfte, _cook,
+ {{0xf1bf003e,0x6e29ba58,0x1fa79a19,0xdd99816b}}, // riál_, rieb, _брег, raň_,
+ {{0x60dbba59,0x68e9a9cb,0x2cbf8151,0x6e29ba5a}}, // _isum, _ired, rtud_, sieb,
+ {{0xfbd20039,0xa5bb026b,0x60cd08f9,0x2cbfba5b}}, // חתי_, _amóm, _apam, stud_,
+ {{0x01660656,0xaab682f1,0x998f8084,0xb34587f1}}, // икно, _अमरक, mogų_, _alçà,
+ {{0xe0d70098,0x291da198,0xaca4019d,0x2d8dba5c}}, // авя_, nzwa_, _kwụt, mbee_,
+ {{0xb8ee023c,0x6b9a87f1,0x6e2600b9,0x6602838a}}, // _रह_, catg, _smkb, _zook,
+ {{0x44d68d38,0x69df00f1,0x6602846d,0xd62a07a1}}, // _zł_, _faqe, _yook, вове_,
+ {{0x78a90db7,0x0d860470,0x3ced017f,0xee3723d7}}, // ševa, рлан, _ševa_, анс_,
+ {{0x249fba5d,0x8fa60294,0x629e3a5e,0x645c082a}}, // [2700] nsum_, _капе, wspo, _phri,
+ {{0x389b00be,0x2246ba5f,0x69c982ec,0x515b01c6}}, // _שיינ, wook_, _abee, _בכפו,
+ {{0x60dba3be,0xf1eb01ce,0x68e9ba60,0xf77881b9}}, // _asum, _जोड़_, _ared, _naħa_,
+ {{0x68e992f1,0x00e6919d,0xb05b016d,0x249f8359}}, // _bred, ижен, ptäc, ksum_,
+ {{0x68e9b206,0x66029c11,0x2246ba61,0x60100009}}, // _cred, _rook, rook_, _lämm,
+ {{0xb8ce8403,0xa2e60364,0x2cadb6b8,0x6602b0c9}}, // _कि_, _когд, dred_, _sook,
+ {{0xe297333d,0x68e99d6b,0xfaa302cb,0x2bc606bf}}, // _вас_, _ered, _маро, र्णा,
+ {{0x68e98082,0x2d801122,0x28da8006,0x3e7c001b}}, // _fred, ncie_, _मैथि, _mít_,
+ {{0x68e9ba62,0x6b9a809f,0x999901ac,0xfbc624bd}}, // _gred, tatg, nosť_, र्थम,
+ {{0x5fb20076,0xe3b89014,0x60c2ba63,0x66028b99}}, // _जागल, lnız_, mtom, _wook,
+ {{0x2d8021a9,0x6b9a8722,0x33770039,0x61e089c4}}, // kcie_, ratg, יעים_, _kaml,
+ {{0x61e2ba64,0x2bc604c5,0x6ab681ce,0x2cadba65}}, // ndol, र्ता, _अमीर, bred_,
+ {{0x61e08010,0x3e7195db,0x63bc00d2,0x6b9aba66}}, // _maml, _išta_, _ocrn, patg,
+ {{0x61e28364,0x44278b99,0x6b9a8197,0x2d80009a}}, // hdol, _gmn_, qatg, ecie_,
+ {{0x6448ba67,0xf09f3a68,0xd5bb0d5f,0xdb1aba69}}, // modi, ssà_, кса_, retó,
+ {{0x442cba6a,0x248901bf,0xef190035,0xdb1a8118}}, // lid_, tpam_, ąż_, setó,
+ {{0x60cd3a6b,0x442cba6c,0x3f9c9e8f,0x61e28114}}, // [2710] _upam, oid_, kavu_, ddol,
+ {{0x4ada8006,0x6448ba6d,0x442cba6e,0x3f9ca6fd}}, // _बनाव, nodi, nid_, javu_,
+ {{0x2bc60778,0xb5fb3a6f,0x25a6ba70,0x61e085e7}}, // र्धा, _diár, _odol_, _baml,
+ {{0x68e9ba71,0xa3e705fc,0x64488f20,0x2d8000e5}}, // _pred, _मोर_, hodi, ccie_,
+ {{0x69bc016f,0x442cba72,0x95080065,0x84670098}}, // ष्टी, kid_, _پہلے_, _въве,
+ {{0x68e9ba73,0x6e2d3a74,0xe7398e97,0x442cb93e}}, // _vred, liab, лел_, jid_,
+ {{0x2486ba75,0x7d0d3969,0x61e0856c,0x2d993a76}}, // _atom_, lyas, _faml, _kese_,
+ {{0x61e08558,0x249f856c,0x68e9904a,0x625880f7}}, // _gaml, tsum_, _tred, líon,
+ {{0xa3ab0d38,0x2d993a77,0x7d0d3a78,0x893993f7}}, // _गया_, _mese_, nyas, _спас_,
+ {{0x6448803a,0x6e2d3630,0x249fba79,0x2d993a7a}}, // godi, hiab, rsum_, _lese_,
+ {{0x9cf98a49,0x249fba7b,0x81da80c8,0x8c1a0039}}, // েছেন_, ssum_, ়ার_, רותי,
+ {{0x2d993a7c,0x442cba7d,0xf53f0106,0x20058428}}, // _nese_, aid_, _oxå_, _holi_,
+ {{0xeb8e80ae,0xddc602a5,0x2005ba7e,0x44278359}}, // _ни_, _nikš, _koli_, _tmn_,
+ {{0x999901ac,0x7d0d3a7f,0x6b98826c,0x4427926a}}, // vosť_, dyas, _gevg, _umn_,
+ {{0x81bf00c8,0x20058668,0x2d993a80,0xddc60110}}, // ীয়_, _moli_, _bese_, _aikš,
+ {{0x3f9c8da8,0x2d990511,0x2ca00074,0xdb170187}}, // zavu_, _cese_, tsid_, nexõ,
+ {{0x2d993a81,0x2aa38201,0x2d803a82,0x61e0a699}}, // [2720] _dese_, lıb_, rcie_, _raml,
+ {{0x61e08370,0xe7e782f1,0x25e80816,0x80ad04c5}}, // _saml, _टोला_, _चोरी_, _जिते,
+ {{0xe80d0063,0x2ca002f1,0x2aa38201,0x2a600282}}, // _सकता_, ssid_, nıb_, _khib_,
+ {{0x60c2ba83,0x6e2d3a84,0x753d8035,0x6448b327}}, // ttom, ciab, ższy, zodi,
+ {{0x2005ba85,0x442cba86,0xdb1e0019,0x81da80ab}}, // _boli_, yid_, lepü, ়াল_,
+ {{0x60c29f5d,0x66061e62,0x442cba87,0xa5bb0032}}, // rtom, _hokk, xid_, _amóh,
+ {{0x660618c2,0x442a3a88,0x60c2a79a,0x2005ba89}}, // _kokk, _imb_, stom, _doli_,
+ {{0x6b989010,0x442cba8a,0x3f9a0904,0xd5ba8d91}}, // _sevg, wid_, _lepu_, уск_,
+ {{0x8fa6ba8b,0x38cb815b,0xa3b3816f,0x2005ba8c}}, // _газе, _مالی_, टलं_, _foli_,
+ {{0xb5fb0104,0x442a0267,0xdbf0801b,0x66060a64}}, // _khác, _jmb_, _tříd, _lokk,
+ {{0x442cba8d,0x6448ba8e,0xe9f90160,0x95ca80e8}}, // rid_, rodi, анні_, _була_,
+ {{0x442c8665,0x661b822e,0x66060125,0x2a6001c0}}, // sid_, chuk, _nokk, _chib_,
+ {{0xa3b8023c,0x442a01e0,0x799d3a8f,0x2486ba90}}, // छला_, _omb_, rasw, _utom_,
+ {{0x2d993a91,0x7d0d3a92,0x3f9a3a93,0xa3e706a7}}, // _sese_, vyas, _cepu_, _मों_,
+ {{0x2d993a94,0x877b82f6,0x59c88540,0x66063a95}}, // _pese_, ראלי, रभार, _bokk,
+ {{0x442a138d,0x7d0d3a96,0xb5fb01a8,0xdceb81b9}}, // _amb_, tyas, _sháb, _qegħ,
+ {{0x62871434,0x2d993a97,0x442a3a98,0x26c30353}}, // [2730] _stjo, _vese_, _bmb_, stjo_,
+ {{0x224b3a99,0x6e2d056c,0x2d993a9a,0x7d0d3a9b}}, // lock_, siab, _wese_, ryas,
+ {{0x7d0d3a9c,0x68ed03fb,0x625880f7,0x2005ba9d}}, // syas, _hrad, ríon, _roli_,
+ {{0x09ab00c8,0x41558a08,0x66063a9e,0x3abb80be}}, // গ্রা, овес, _gokk, רמאנ,
+ {{0xed468986,0x09e300c8,0x26de9fa4,0x2aa38085}}, // _آپ_, যাপা, _asto_, zıb_,
+ {{0x2aa38201,0xa29506b5,0x68ed0010,0x62588118}}, // yıb_, _магі, _mrad, líol,
+ {{0xb05b0009,0x66063a9f,0x661bbaa0,0x2a668282}}, // ytän, _yokk, thuk, bmob_,
+ {{0xf2d2093f,0x68ed3aa1,0xc48584dd,0x7bd73aa2}}, // _קען_, _orad, олик, lexu,
+ {{0x2bc614a7,0x26de9220,0xe3b19e13,0x2a6001c0}}, // र्वा, _esto_, ورد_, _rhib_,
+ {{0x5ac9baa3,0x661bbaa4,0x2aa38085,0x2a6010ba}}, // алом_, shuk, tıb_, _shib_,
+ {{0x68ed3a1c,0x78a9005c,0xb05b0009,0xd7f8801c}}, // _arad, ševl, ttän, _khăn_,
+ {{0x2a6001c5,0x2aa38085,0xb6868019,0xdd1e928a}}, // _qhib_, rıb_, _بھول_, _síťo,
+ {{0x786004b8,0x6e2b826b,0x7e7a890d,0xb05b1c50}}, // höve, _imgb, _zutp, rtän,
+ {{0xb05b3aa5,0x799b82f7,0x66063aa6,0x68ed3aa7}}, // stän, _leuw, _sokk, _drad,
+ {{0xdee3117e,0x442a3aa8,0x66e33aa9,0x2a600069}}, // мори, _rmb_, мора, _thib_,
+ {{0x68ed3aaa,0x78a414a2,0xe1ff0511,0x39401d67}}, // _frad, _tviv, lmón_, _txis_,
+ {{0xad660077,0x64430353,0x3f9a3aab,0x248d80b9}}, // [2740] زاره, čnin, _tepu_, npem_,
+ {{0x6b5622ea,0x66063aac,0x61e62ec5,0xc17400fc}}, // отех, _wokk, ldkl, _naɗi_,
+ {{0x61e402b8,0x59c10107,0x86460f75,0x68ed04e8}}, // _kail, ष्कर, _множ, _zrad,
+ {{0xd7f880ff,0x62588118,0x3f858162,0xdcfd0084}}, // _chăn_, cíol, _aflu_, _nesė,
+ {{0x25a03aad,0xe8949a8f,0x2d9201d3,0x10a3b7df}}, // mail_, чаль, mbye_, дичн,
+ {{0x4aa9853e,0xeeaa8b87,0xacd90158,0x76aa910b}}, // _किंव, атик_, _פֿרי, атив_,
+ {{0xa01b0198,0xc1740300,0x09b20264,0x26de8037}}, // mpöt, _daɗi_, ট্যা, _qsto_,
+ {{0x78ad8025,0x25a03aae,0x61469577,0x6b9c3aaf}}, // šava, nail_, _деба, _herg,
+ {{0x07a69cb2,0x625880f7,0x6b9c0074,0x7aee007a}}, // _маан, níom, _kerg, _hrbt,
+ {{0x25a004bc,0x3eb800f1,0x6b9c3ab0,0x9b6a8323}}, // hail_, kurt_, _jerg, ишна_,
+ {{0x61e4154c,0xa5bb0073,0x25a03ab1,0x68ed3ab2}}, // _bail, _imóv, kail_, _srad,
+ {{0x68ed01e2,0x61e43ab3,0x201a00df,0xba2b00e8}}, // _prad, _cail, _alpi_, ріод_,
+ {{0x25a0125b,0xcd368c2b,0x660f9614,0x61e42612}}, // dail_, _براب, öcke, _dail,
+ {{0xcbd500c8,0x3eb83ab4,0x6b9c3ab5,0x7c2aa32a}}, // _হচ্ছ, furt_, _nerg, _umfr,
+ {{0x09d000c8,0xdd9b0615,0x25a03ab6,0xdb4f80e8}}, // িয়া, иша_, fail_, _цю_,
+ {{0x3ead0775,0x61e43ab7,0x25a00014,0x7bce01b9}}, // čet_, _gail, gail_, _abbu,
+ {{0x6b9c3ab8,0x2fd80338,0x68ed1408,0xc05b035f}}, // [2750] _berg, berg_, _urad, _він_,
+ {{0xa2a490f7,0x6b9c3ab9,0x7c3ababa,0x61e40102}}, // _किञ्, _cerg, nntr, _zail,
+ {{0x23c2800f,0x25a00013,0x926ba3d7,0x6b9c3abb}}, // _शानद, bail_, арда_, _derg,
+ {{0x25a0008c,0x7c3ababc,0x6b9c1384,0x69c0babd}}, // cail_, hntr, _eerg, _ccme,
+ {{0x6b9c2ebd,0xc5fa0039,0x6d5d1247,0x00000000}}, // _ferg, _הפרט, _uzsa, --,
+ {{0x3abb03f8,0x6b9c3abe,0xe8d90133,0x66e598d1}}, // _پاسخ_, _gerg, _nzọ_, зока,
+ {{0xa195835f,0x199589a8,0x7e628a15,0x160f08d4}}, // _навч, _навя, _bhop, _सवार_,
+ {{0x7c3ab49a,0x6b9c0102,0xe8d901bc,0xd6ce80f7}}, // entr, _zerg, _azọ_, اقي_,
+ {{0x69d9babf,0x6d4301ec,0xc1740326,0x81198652}}, // mewe, ßnah, _taɗi_, _ужас_,
+ {{0x69d9bac0,0x4b258221,0x3e71826c,0x6b9c0118}}, // lewe, змов, _tšto_, _xerg,
+ {{0x81e500c8,0xef20009a,0xb05b0106,0x91e61617}}, // নান_, ążki_, rtäl, поне,
+ {{0xb05b04b8,0x7a362c62,0x6eca811c,0x61463ac1}}, // stäl, kšte, _məbl, зема,
+ {{0x25a002be,0x61e60123,0x61e43ac2,0x7e698f81}}, // vail_, rdkl, _vail, mmep,
+ {{0x7c94803d,0x601d86c0,0x62890bcf,0xb4d7bac3}}, // _آشنا, _fème, _čeon, ाबी_,
+ {{0x61e43ac4,0x81b380ab,0x4e1f8105,0x25a010b6}}, // _tail, জ্য_, _बताई_, tail_,
+ {{0x2cb201b0,0xbddb313c,0x69d98bfd,0x6e3bbac5}}, // tryd_, _ajèb, jewe, nnub,
+ {{0x25a03ac6,0x69d9bac7,0x2d9d819d,0x3eb83ac8}}, // [2760] rail_, dewe, _mewe_, surt_,
+ {{0x2d9d879f,0xdce600eb,0x25a010a2,0x625881a8}}, // _lewe_, _iekļ, sail_, ríom,
+ {{0x6b9c1d6f,0x877b0039,0x51869777,0x9f970039}}, // _verg, _לאיי, пула, _מדיה_,
+ {{0x69d9844e,0x63a1bac9,0xe8faa462,0x320900ff}}, // gewe, haln, йле_, _xoay_,
+ {{0x6b9c0867,0x3dc380c8,0xb5fb001c,0xc5c380ab}}, // _terg, ্যাল, _khán, ্যাপ,
+ {{0x7e629838,0xfaa63aca,0x9326015b,0x660401c8}}, // _shop, _наго, _فرزن, ljik,
+ {{0xeab680ab,0x8c1b0039,0x7c2e12b8,0x69d99ab0}}, // _জন্ম, _פולי, _imbr, bewe,
+ {{0x91e3bacb,0x44f49317,0x2d9d8b99,0x66043acc}}, // _посе, дпис, _cewe_, njik,
+ {{0x2ba683bb,0x63a1811f,0x81e500ab,0x7e7e2853}}, // jící_, faln, নাম_, _lupp,
+ {{0xb05b0364,0x2281808b,0xb5fb00ff,0x2ba681d0}}, // ttäm, _bók_, _nhán, dící_,
+ {{0x81df80c8,0x316034e8,0xe1d9035f,0xeee4803d}}, // তার_, _aziz_, одні_, _تغذی,
+ {{0xddc88267,0x250b804e,0xa194102a,0x752400ee}}, // _hudž, _گرمی_, наюч, kziz,
+ {{0xd6258013,0xb05b3acd,0xf1a40084,0x3f9e80f1}}, // _تعلي, stäm, эрэн, _ketu_,
+ {{0x7a360a21,0x36d51541,0x7afc9614,0x63a1bace}}, // všte, могр, ärts, caln,
+ {{0x3f9ebacf,0x7bdabad0,0xddc88267,0x628aa1ad}}, // _metu_, metu, _mudž, _utfo,
+ {{0x7bdabad1,0x3f9e9a75,0x660400f1,0xe8d901bc}}, // letu, _letu_, gjik, _afụ_,
+ {{0x69d9bad2,0xb05b1c15,0xb5fb0f35,0x65600a2a}}, // [2770] vewe, ntäk, _fhán, _ùmhl,
+ {{0x7bdabad3,0x69d983f7,0x705500d7,0xf7788372}}, // netu, wewe, _زنجا, _daħk_,
+ {{0x442e8390,0x34d9800d,0xd7f8801c,0x6b8501ec}}, // _mmf_, _भन्द, _chăm_, rchg,
+ {{0x7a361601,0x7bda9b17,0x63a186e4,0xe8d92ae3}}, // pšte, hetu, zaln, _efụ_,
+ {{0xddc8bad4,0x37e588ed,0x81df80ab,0x7e7e01e8}}, // _budž, долг, তাল_, _zupp,
+ {{0xacfa0039,0x78ad8390,0x69d9bad5,0x2d9d867f}}, // _והשכ, šavo, sewe, _sewe_,
+ {{0x63a18db7,0x7bdabad6,0xb05b0009,0x69d985ee}}, // valn, detu, ttäj, pewe,
+ {{0x601000f2,0x44333ad7,0x26c78796,0x63a18035}}, // _hämt, lix_, rtno_, waln,
+ {{0x26c78353,0x7e6993c2,0x9f5e8216,0x78600338}}, // stno_, rmep, _votá_, höva,
+ {{0x2d9d822e,0x7bdabad8,0x60100106,0x7e6481b9}}, // _wewe_, getu, _jämt, ċipa,
+ {{0x387f838e,0x569484ae,0xfbcf1a46,0x04ef8264}}, // _huur_, _захт, स्यम, য়নের_,
+ {{0x63a1bad9,0x3f9e8010,0xdcf98fd3,0xb5fb128a}}, // saln, _zetu_, _وفات_, _shán,
+ {{0x7e7e0213,0x63a18f28,0x3f9e822e,0x6443003a}}, // _supp, paln, _yetu_, čnij,
+ {{0x2281807b,0x387f837a,0x7e7e3ada,0x2bcf0b84}}, // _tók_, _muur_, _pupp, स्मा,
+ {{0x59c6035a,0x8c428279,0x44333adb,0x41c60035}}, // _वापर, _шеше, dix_, _वापस,
+ {{0x25bfbadc,0x4433023e,0x2b0a801b,0x7524011b}}, // ngul_, eix_, ाहरु_, tziz,
+ {{0xb5fb0104,0xdca32f84,0x64a3176e,0x387fb512}}, // [2780] _thán, кати, ката, _nuur_,
+ {{0x661d15f8,0x68e43add,0x442100dd,0x7e7e365d}}, // _elsk, _isid, ghh_, _tupp,
+ {{0x2bcf8f21,0x78a98698,0x78ad8301,0x9f458168}}, // त्या, _avev, šavl, _dalë_,
+ {{0x442134b1,0xd378812b,0x81e50264,0x387f91b8}}, // ahh_, meće_, নাথ_, _buur_,
+ {{0xd37890d3,0x7bda804f,0x9f458168,0x02da890f}}, // leće_, yetu, _falë_, _बन्न,
+ {{0x387fbade,0xd7f8801c,0xd0108c3b,0x2bcfba52}}, // _duur_, _thăm_, طلب_, त्मा,
+ {{0x7bdabadf,0xa3e7150e,0xd3788904,0x6a8307b6}}, // vetu, _मोट_, neće_, _алса,
+ {{0x78a9003b,0x973c803a,0x3f9e822e,0x25bf9295}}, // ševi, _neće, _wetu_, ggul_,
+ {{0x7bdabae0,0xe3ce8028,0x47c684ae,0x60101a50}}, // tetu, _dựng_, _обав, _jäms,
+ {{0x69c43ae1,0xf0638084,0xa3de0054,0x25bf8493}}, // _acie, _акуп, ढ़ि_, agul_,
+ {{0x0bb70051,0xd378a3e3,0x22590198,0x387f890d}}, // ולים_, jeće_, зины_, _zuur_,
+ {{0xd378803a,0x7bdabae2,0x442e9849,0xdd90880b}}, // deće_, setu, _wmf_, _صوت_,
+ {{0x200cbae3,0xb4bd02f1,0xa3de0105,0xf1bf0144}}, // _kodi_, _इमे_, ढ़ा_, rgá_,
+ {{0x200c86c0,0x6562b106,0x44332e12,0xb8f7170c}}, // _jodi_, _izoh, xix_, _सह_,
+ {{0x200cbae4,0x45d50468,0xe6dca342,0x76878214}}, // _modi_, новс, _मनोज, _kıya,
+ {{0x200cbae5,0x59c605e8,0x645a831d,0xf1d0001c}}, // _lodi_, _वायर, llti, _hạng_,
+ {{0x69dd3ae6,0x44333ae7,0x443e81e4,0x2d580b88}}, // [2790] mese, tix_, ont_, дить_,
+ {{0x443ea1ec,0x69dd3ae8,0x200c8114,0xc86700f7}}, // nnt_, lese, _nodi_, _تطبي,
+ {{0x443ea0a4,0x44333ae9,0xf1d00028,0x387f8074}}, // int_, rix_, _mạng_, _suur_,
+ {{0x443e82af,0x201e94ed,0x44210b17,0x32193aea}}, // hnt_, _alti_, shh_, nksy_,
+ {{0x629c08d9,0x200cbaeb,0x78bd15a0,0x7a36131b}}, // _ewro, _bodi_, nusv, nšta,
+ {{0xb907901b,0x85740d13,0x649a0051,0x76428136}}, // _बन_, _шлях, _מישה, _ajoy,
+ {{0x63a53aec,0x69dd3aed,0x7ac411d5,0x2aa81539}}, // mahn, kese, _исте, _отто_,
+ {{0x443ebaee,0xceb40f60,0x63a51067,0x261c00f7}}, // ent_, דיק_, lahn, _líon_,
+ {{0x2bcf29b7,0x9f45826f,0x61fb9698,0x2ca90ce9}}, // स्था, _malé_, _inul, nsad_,
+ {{0xd3788024,0x63a53aef,0x69c43af0,0x443ebaf1}}, // zeće_, nahn, _scie, gnt_,
+ {{0xf1d0001c,0x61fbbaf2,0x69dd1267,0x61e9baf3}}, // _dạng_, _knul, fese, _kael,
+ {{0x2bcf0d86,0x443ebaf4,0x69dd3af5,0x200cb3b5}}, // स्ता, ant_, gese, _zodi_,
+ {{0x973c8052,0x61e9822e,0xa1550698,0x63a509ca}}, // _veće, _mael, върш, kahn,
+ {{0xfaa59fa1,0xdb1c0013,0x63a50be6,0x61e9baf6}}, // _зако, _scrí, jahn, _lael,
+ {{0x63a509ca,0x8c1a8039,0x69dd3af7,0xd3789487}}, // dahn, _מוסי, bese, teće_,
+ {{0x69dd3af8,0x20183af9,0x61e9bafa,0x9f45bafb}}, // cese, skri_, _nael, _valè_,
+ {{0x2edc800d,0xd378812b,0x15f28105,0x63a53afc}}, // [27a0] _मन्त, reće_, _अफसर_, fahn,
+ {{0x61fbbafd,0x6fd387d9,0x63a53afe,0x7bc1baff}}, // _anul, nıcı, gahn, nglu,
+ {{0x3f983b00,0x23c6024c,0x2bc60744,0xa79b007c}}, // mbru_, र्गद, र्गा, _משיח,
+ {{0x6455106d,0x2bcf824c,0xdb0181ac,0xfbcf8a27}}, // _ekzi, त्ता, galé, त्तम,
+ {{0x443ebb01,0x63a53b02,0x6d5982e9,0x6b8081bc}}, // ynt_, bahn, _nywa, ịgid,
+ {{0x69dd3b03,0xb05b0364,0x9f34b73a,0x7bc180ce}}, // zese, ttäi, келі, jglu,
+ {{0x8c430765,0x69dd2499,0xd6d78150,0x765b86a5}}, // _сере, yese, еты_, fluy,
+ {{0x61e9803c,0x2bc28006,0xa3d2bb04,0x443ebb05}}, // _gael, _शाहा, ह्न_, wnt_,
+ {{0x59a70063,0x69dd3b06,0x290280f1,0x2bc70cf0}}, // _ख़बर, vese, _çka_, _लामा,
+ {{0x320dbb07,0x69dd3b08,0xe8d78039,0x443ebb09}}, // _zoey_, wese, _אומר_, unt_,
+ {{0x69dd3b0a,0xa19501bb,0x600f83ba,0x443eb2c7}}, // tese, канч, _tømr, rnt_,
+ {{0xf2d30158,0x765b85b4,0x3a200028,0x2bc70076}}, // נער_, cluy, _clip_, _लाभा,
+ {{0x63a50d92,0x6e243b0b,0x09e300ab,0x7bde3b0c}}, // yahn, dhib, যালা, kepu,
+ {{0xb05b0009,0x6010016d,0x69dd3b0d,0xb803b716}}, // htäv, _sämr, sese, लायम_,
+ {{0x387a026c,0xc22183a4,0x63a5011a,0x69dd3b0e}}, // _dipr_, _मतलब_, vahn, pese,
+ {{0x6e24222e,0x63a5010b,0x7a360b67,0x09d580ab}}, // ghib, wahn, pšta, _সোনা,
+ {{0xd90495e4,0xa5bb0118,0xb87b01fa,0x7e6d27d1}}, // [27b0] _ای_, _alóc, rvís, rmap,
+ {{0x7e6d010c,0x2bd80035,0x2ca9308a,0xf8bf0144}}, // smap, ड़गा, rsad_, mué_,
+ {{0xa8580051,0x63a53b0f,0x2ca93b10,0x61e9bb11}}, // _איזה_, rahn, ssad_, _pael,
+ {{0x6e243b12,0x63a50d92,0x92b401a8,0x798d0197}}, // chib, sahn, احلا, _jfaw,
+ {{0x91bf81bc,0x2604815c,0x60103b13,0x9f5a02d6}}, // _ahụ_, वानी_, _kämp, _anpè_,
+ {{0x7bde0012,0xec350158,0xdb01a82b,0xe05780d5}}, // cepu, _יאָר_, salé, ریات_,
+ {{0xf4c280c8,0x765b9238,0x61fb85ee,0x6d598123}}, // ্টোব, tluy, _tnul, _sywa,
+ {{0xe8d9001c,0x60103b14,0x660f3b15,0x6288002a}}, // _ngữ_, _lämp, _jock, _édoa,
+ {{0x6fd381cc,0xe1ff0035,0x9f5a0176,0x5a4493bf}}, // tıcı, lmów_, _enpè_, _сэра,
+ {{0x2a6902d5,0x660f24c0,0x249680d7,0x6e2402d4}}, // _ahab_, _lock, کنید_, zhib,
+ {{0x23298c24,0xab2990f8,0x7bc1a0f0,0x6fd3899b}}, // фони_, фона_, rglu, rıcı,
+ {{0x65958cec,0x7aea006a,0x8d878081,0x2a690362}}, // _раду, æfti, _чужд, _chab_,
+ {{0xf8bf3b16,0x261c01a8,0x2a6901b4,0xbcfb0061}}, // gué_, _díol_, _dhab_, _szén,
+ {{0x2cbf8079,0x6eca829a,0x00000000,0x00000000}}, // muud_, _səbi, --, --,
+ {{0x07a33b17,0x8fa31597,0x6e243b18,0x317fbb19}}, // расн, расе, thib, _oguz_,
+ {{0x63a3bb1a,0x68e2831d,0x7bd501ec,0x661bbb1b}}, // _henn, ywod, _abzu, nkuk,
+ {{0x6441bb1c,0x6283bb1d,0x7bde3b1e,0x69d63b1f}}, // [27c0] onli, _huno, tepu, _ibye,
+ {{0x6283bb20,0xd3788289,0x6441bb21,0xe29a0cf9}}, // _kuno, leća_, nnli, даж_,
+ {{0x81e50a49,0x260480d4,0x63a38e20,0xb05b0364}}, // নার_, वामी_, _menn, ttäv,
+ {{0x63a3bb22,0x62838542,0x7bde0b17,0xaca40133}}, // _lenn, _muno, sepu, _amụr,
+ {{0xe3a7850c,0x0cbc80ab,0x69c2bb23,0x92cd80ab}}, // _پر_, _অন্ত, rgoe, রিয়_,
+ {{0x09d600c8,0x63a3bb24,0x64418a0f,0xb05b0009}}, // _তোমা, _nenn, jnli, stäv,
+ {{0x29008012,0x973c80d2,0x69d63b25,0xc5eb80ab}}, // ţia_, _beća, _obye, কানা_,
+ {{0xd37882fd,0x2bcf815c,0x64419412,0x661b809c}}, // jeća_, त्सा, enli, gkuk,
+ {{0x81e88a49,0xd3788025,0x70dc035a,0x24860307}}, // বাদ_, deća_, यबोल, íomh_,
+ {{0x63a3bb26,0xe7c9809a,0x2bc6001b,0x6283bb27}}, // _cenn, _रायप, र्चा, _buno,
+ {{0x63a381a3,0x62838012,0x6d448038,0x0c7380f7}}, // _denn, _cuno, _žiad, جديد,
+ {{0x68f63b28,0xf8bf00e7,0x62839fc9,0x2a69022c}}, // _bryd, tué_, _duno, _qhab_,
+ {{0xc05b035f,0x63a3bb29,0xac0a8eef,0xe7c986f0}}, // дів_, _fenn, _онда_, _रामप,
+ {{0x63a3bb2a,0x660f3b2b,0xd9180009,0x69d602ec}}, // _genn, _sock, тью_, _ebye,
+ {{0x6e228259,0x63a88f09,0x2a690282,0x62838087}}, // _klob, madn, _thab_, _guno,
+ {{0x78ad8024,0x33778039,0xd5b80c0e,0x62810036}}, // šavi, _בתוך_, ксу_, _éloi,
+ {{0x2bcf835a,0xf8bf3b2c,0xfbcf81d0,0x63a382ec}}, // [27d0] त्वा, qué_, त्वम, _yenn,
+ {{0x61ed3b2d,0x6e228722,0xddc43b2e,0x27fe808e}}, // _haal, _llob, rmiš, _nntn_,
+ {{0xe7ec00d4,0x61ed3b2f,0x660f011f,0xe29800e8}}, // _छोटा_, _kaal, _tock, ває_,
+ {{0x61ed0079,0x63a8bb30,0xc3338039,0x645e3b31}}, // _jaal, hadn, סוף_, alpi,
+ {{0x61ed3b32,0x25a93b33,0x394980a9,0x660982fd}}, // _maal, maal_, ças_, vjek,
+ {{0xd378803b,0xa3d2ac2a,0x973c9432,0xab9400e8}}, // zeća_, _हॉल_, _seća, _вирі,
+ {{0x63a38e20,0x973c8699,0xadc40032,0x7a26008b}}, // _renn, _peća, _ajẹs, _hóte,
+ {{0x61ed3b34,0x25a92409,0xdb0380e7,0xa3cf06bf}}, // _naal, naal_, _fenê, व्र_,
+ {{0x69cc826f,0x7e7d3b35,0x661bbb36,0x62838cf4}}, // ílež, _misp, rkuk, _suno,
+ {{0x7bc53aef,0x6283bb37,0x6609bb38,0x25a92808}}, // nghu, _puno, sjek, haal_,
+ {{0x63a3bb39,0xc5d5835f,0x61ed3b3a,0xbcfb0019}}, // _venn, _біль, _baal, _szél,
+ {{0xb5fb0013,0x61ed02a3,0x63a3949d,0x68f6031d}}, // _fhái, _caal, _wenn, _pryd,
+ {{0x61ed3b3b,0x25a93b3c,0x04c881a8,0x7a26016a}}, // _daal, daal_, _لوني_, _nóte,
+ {{0x6283bb3d,0x68f601b0,0xf77884b7,0x013680be}}, // _tuno, _vryd, _taħt_, אָלט_,
+ {{0x18671927,0x61ed3824,0xa3aa000f,0xb05b0004}}, // уари_, _faal, _गजब_, kräf,
+ {{0x2bcf8894,0xc6928158,0x25a902a3,0xfbcfb738}}, // त्रा, מאל_, gaal_, त्रम,
+ {{0x05663b3e,0xfbb783eb,0xdd8f0bbe,0x30be8264}}, // [27e0] уван, _आयाम, سول_, _ইন্স,
+ {{0x69c9802e,0x309b0bea,0x389b010f,0x61ed0365}}, // _acee, _רשימ, _ריינ, _zaal,
+ {{0x3ce58063,0x81e880ab,0x61ed1a25,0x25a93b3f}}, // _जैसे_, বাস_, _yaal, baal_,
+ {{0x69c601da,0x61ed02a3,0xb05b02af,0x63a8807a}}, // ngke, _xaal, stät, zadn,
+ {{0x522d00be,0xc05a81e2,0x9f45bb40,0xa3cf064a}}, // וואַ, нік_, _dalí_, _वॉक_,
+ {{0x6e2295d8,0xcaa600f7,0x3a26b437,0xdd3b03de}}, // _slob, _مصري, shop_, _װעלכ,
+ {{0x444439e8,0x63a8a19f,0x68e9a499,0x94230b5b}}, // mn_, vadn, _esed, _умре,
+ {{0x443a3b41,0x44440b2f,0x201c8904,0xa281809a}}, // lip_, ln_, skvi_, łość_,
+ {{0x444406ba,0x61ed0079,0x2ba80651,0x3d0e8074}}, // on_, _raal, गरपा, िहें_,
+ {{0x44443b42,0x443a336a,0x61ed3b43,0x60c2bb44}}, // nn_, nip_, _saal, muom,
+ {{0x44443b45,0x25a902a3,0x26048aed,0x61ed3b46}}, // in_, yaal_, वादी_, _paal,
+ {{0xc333078d,0x6eca8201,0xb5fb0013,0x63a8bb47}}, // מות_, _qəbu, _thái, sadn,
+ {{0x7e7d09ba,0x443a3b48,0xdddb880a,0x61ed3b49}}, // _risp, kip_, lmuş, _vaal,
+ {{0x4394bb4a,0x44443b4b,0x443a06c0,0xf1bf0013}}, // _такс, jn_, jip_, chán_,
+ {{0x25a92808,0x61ed0c1f,0x7c3abb4c,0x5f941510}}, // taal_, _taal, mitr, сист,
+ {{0x5eb980ab,0x92b392c8,0x6eca8085,0x387ebb4d}}, // _আহমে, _احوا, _həbs, _litr_,
+ {{0x61e2bb4e,0x25a90c11,0x7e7d0029,0x7a26008b}}, // [27f0] deol, raal_, _visp, _fótb,
+ {{0x7c3aab02,0xddc98214,0x443a02e8,0x25a68114}}, // nitr, kleş, gip_, _leol_,
+ {{0x45d52a71,0xb05b1af3,0x25a91ad7,0x78bc80eb}}, // _конс, träf, paal_, _ārva,
+ {{0x7c3a811a,0x973c9123,0x3e410110,0x09e300ab}}, // hitr, _pećn, kėti_, যাকা,
+ {{0x44443b4f,0x9f4781d0,0x26d10cdb,0xe94580d7}}, // bn_, rdní_, ltzo_, جرای,
+ {{0x660d2d2e,0x3e4101e2,0xb5fb3b50,0x25a9807a}}, // ljak, dėti_, _cháv, _žal_,
+ {{0xdb03bb51,0x7c3abb52,0xdb01bb53,0x26d10cdb}}, // _genè, ditr, talí, ntzo_,
+ {{0xed59b2b8,0x645704c3,0x25a68c5e,0xc3320039}}, // кой_, loxi, _ceol_, _וול_,
+ {{0x0c258ab2,0x387ebb54,0x68e9bb55,0xac858073}}, // имин, _fitr_, _used, јгол,
+ {{0xdb01bb56,0x2cad9c1d,0x22490458,0x660d156e}}, // salí, rsed_, _ajak_, hjak,
+ {{0x7f849301,0x81e88a49,0x2a6d9d14,0x69cc800d}}, // _المن, বার_, _kheb_, _हामी,
+ {{0xd5758e02,0x26c306a5,0xc86904de,0x20010122}}, // _куль, dujo_, _חן_, _anhi_,
+ {{0xeb8ea42e,0x444402c1,0x62873b57,0x443a0214}}, // _ми_, yn_, _lujo, yip_,
+ {{0x7c288014,0x7c3abb58,0x63a7001b,0xa06a01e2}}, // chdr, citr, _nejn, тага_,
+ {{0x44440b40,0x81ab80ab,0x28b20074,0xcc3a80be}}, // vn_, _খান_, _जिकि, _בעסט,
+ {{0x44443b59,0xe8fa0b30,0x644f80e7,0x64453b5a}}, // wn_, _али_, écif, enhi,
+
+ {{0x628702be,0x443a3b5b,0x7d098118,0xddc98214}}, // [2800] _aujo, tip_, nxes, zleş,
+ {{0x44443b5c,0x6457002a,0x26c30e1b,0xdddb8380}}, // un_, goxi, bujo_, ymuş,
+ {{0x44443b5d,0x63a701c0,0x67bb0039,0x395f80e4}}, // rn_, _dejn, _במיק, _ayus_,
+ {{0xb05b034a,0xe7d201ab,0x7d1b8365,0x2efa2c92}}, // träg, द्रप, kyus, _irpf_,
+ {{0x260481c4,0x61e2bb5e,0x44442ebd,0xae0982f1}}, // वासी_, reol, pn_, वानन_,
+ {{0x443a00f1,0x2056bb5f,0x25a680f7,0x64a31986}}, // qip_, штер, _seol_, _фара,
+ {{0x2120935a,0x60c2827e,0x973c812b,0x6e298a10}}, // ših_, suom, _većo, fheb,
+ {{0xdb03aa63,0xfe6f80a0,0xddc98214,0xdddba2f8}}, // _gené, ودي_, rleş, rmuş,
+ {{0x394d0063,0x87da84c0,0x9c469016,0x7c28bb60}}, // żesz_, _لباس_, ихол, thdr,
+ {{0xbcfb0019,0x7ed680e8,0xdb038061,0x6280bb61}}, // _szék, _відч, _zené, _himo,
+ {{0x752d01dc,0x6280bb62,0x233881e2,0x6bd8803d}}, // zzaz, _kimo, упны_, نوکس_,
+ {{0xdb0384c3,0xdd8f8019,0x7c3a8084,0x6cd68019}}, // _xené, _قوم_, sitr, _نقصا,
+ {{0x04948624,0x7c948013,0x62808f20,0x6fcd83eb}}, // _التح, _التص, _mimo, _सामू,
+ {{0x251b8e82,0xd83b0087,0x798403f7,0x04dbb5c4}}, // _בודא, тэм_, _egiw, _סקול,
+ {{0x6f1c031d,0xbe880860,0x63a7007a,0x6602bb63}}, // gyrc, исте_, _rejn, _knok,
+ {{0x4b7b812a,0x30a72386,0x628084a7,0x6d5c8457}}, // _באוו, _кров, _nimo, ğraf,
+ {{0xb05b016d,0x9f45826f,0x786d841c,0x629abb64}}, // [2810] träd, _malá_, lúve, lpto,
+ {{0x6e29bb65,0x6280bb66,0x62873b67,0x224680e1}}, // zheb, _aimo, _pujo, enok_,
+ {{0x2d8280e1,0x19b99539,0x2bd89869,0xd8488133}}, // ľkej_, гуль_, ब्या, kọb_,
+ {{0x62870279,0x2a6d8282,0x645c03ec,0x6457002a}}, // _vujo, _qheb_, _ekri, soxi,
+ {{0x6280bb68,0x26dea6e7,0xb5fb01a8,0xe8038072}}, // _dimo, _apto_, _dhát, लाला_,
+ {{0x09e300ab,0x7a263b69,0x6602b6ef,0x1d1601c6}}, // যাটা, _móta, _anok, _הקשר_,
+ {{0xdb0386a5,0x2a7f8748,0xdb018c83,0x316981d6}}, // _tené, _tiub_, kalá, ťaz_,
+ {{0xb8cd80c8,0xf2d204de,0x62809f6d,0x26dea5ca}}, // _কম_, _מעל_, _gimo, _dpto_,
+ {{0x6e298648,0xbc198048,0x7a2601a8,0x2bc284c5}}, // rheb, гіні_, _nóta, _शाखा,
+ {{0x40849cce,0x98148077,0x9b440117,0x68fb811e}}, // _футб, _وبلا, _انہو, _irud,
+ {{0x44213b6a,0xdce78182,0x682b0201,0x68ed3b6b}}, // ikh_, ımız, _müdd, _asad,
+ {{0xdb018019,0x6eca8085,0xf8bf023e,0xa5bb008b}}, // galá, _təbr, fré_, _blóm,
+ {{0x2604bb6c,0x291dbb6d,0xc9560009,0x8505803d}}, // वारी_, nywa_, стны, _دوشن,
+ {{0x765d0e87,0x2cbfb55e,0x3cfe01d0,0xc2c880d7}}, // _aksy, mrud_, _čtv_, دبیل_,
+ {{0xdb0184be,0x66f98107,0xd62a0320,0xf99f01e8}}, // balá, ्मिक_, гове_, bbè_,
+ {{0x645c0bbd,0xf8bf003e,0x0d861bf2,0x442102a3}}, // _skri, bré_, слан, ekh_,
+ {{0x61e63b6e,0xcc888104,0x44383b6f,0x6280bb70}}, // [2820] mekl, _lớn_, _pmr_, _rimo,
+ {{0x69db85a4,0x44278890,0x61e63b71,0x4adb8fb8}}, // _abue, _jln_, lekl, _बहाव,
+ {{0x4427bb72,0xfc3f07e2,0x6d4f3b40,0x25ad8bcb}}, // _mln_, _avís_, _exca, kael_,
+ {{0x68fb84fe,0x80cb00c8,0x61e63b73,0xd2598d8e}}, // _brud, িবর্, nekl, аці_,
+ {{0x25a00393,0x6280a6d5,0x25ad8114,0xddcd1bef}}, // mbil_, _vimo, dael_, llaş,
+ {{0x645c3b74,0x25a03b75,0xe447bb76,0x44383b77}}, // _ukri, lbil_, _رض_, _umr_,
+ {{0x62808a5e,0xddcd0059,0x61e60214,0x09b200ab}}, // _timo, nlaş, kekl, য়্যা,
+ {{0x61e6111b,0x7e64020d,0x25a004e1,0xa5bb026b}}, // jekl, nlip, nbil_, _alój,
+ {{0xa9268009,0x4427810b,0x61e63b78,0x998d81a1}}, // _удал, _bln_, dekl, _umeš_,
+ {{0xddcd0214,0xf8bf00e7,0xb34682df,0xe8248061}}, // klaş, vré_, jeçõ, _بذری,
+ {{0xdb01bb79,0x5c5b00be,0x7a263b7a,0x2bf701c6}}, // talá, רדיק, _sóta, זמין_,
+ {{0xad9b0073,0x5d868013,0x2604816f,0x5b7b03c8}}, // _anún, _الإل, वाळी_, טריא,
+ {{0xfdf78039,0x44213b7b,0x661d9c50,0xf8bf3b7c}}, // _לצאת_, ykh_, öske, uré_,
+ {{0xa2c08ec5,0xf8bf3b7d,0xb9218032,0x96eb0b79}}, // _विप्, rré_, _aisọ_, льба_,
+ {{0x443e8e79,0x6fda800f,0x501b8039,0x543b80be}}, // mit_, प्यू, נולו, _געגא,
+ {{0x645abb7e,0xe8d90028,0x7bdc3b7f,0x61e63b80}}, // loti, _ngủ_, _abru, cekl,
+ {{0x68ed0510,0x2bcf89a9,0x973c8b67,0x3a2902c4}}, // [2830] _usad, त्का, _bećk, _ilap_,
+ {{0x44273b81,0x645abb82,0x3ce0800f,0x97a70087}}, // ón_, noti, _कहने_, _урил,
+ {{0x6448bb83,0x3a29000b,0xfa8980ff,0x68fb8115}}, // indi, _klap_, _lẫn_, _srud,
+ {{0x443ebb84,0xc1b70c28,0x1df90009,0x7e643b85}}, // hit_, _अयोग, _темы_, clip,
+ {{0x443eb1f4,0x645abb86,0x7a36012b,0x65628db1}}, // kit_, koti, ušti, _nyoh,
+ {{0x62988b5d,0x443ebb87,0x63aa8125,0x645abb88}}, // _otvo, jit_, _nefn, joti,
+ {{0xa3be92ee,0x443ebb89,0x645abb8a,0x6562bb8b}}, // ेलन_, dit_, doti, _ayoh,
+ {{0x7a36003b,0xe619abf3,0x644898f2,0x68fbacdb}}, // pšti, рди_, endi, _trud,
+ {{0x44278894,0x25adbb8c,0x6448bb8d,0x64890b80}}, // _pln_, rael_, fndi, _džig,
+ {{0x443ebb8e,0xfa898104,0xddcd05c5,0x63aa831d}}, // git_, _dẫn_, ylaş, _cefn,
+ {{0xcb69bb8f,0x63aa8355,0x27e7bb90,0xdb1c00f7}}, // ране_, _defn, menn_, _scrú,
+ {{0x69cb8397,0x27e7bb91,0x60c615bd,0x64488010}}, // ngge, lenn_, tukm, andi,
+ {{0xe3ae8c8e,0x645abb92,0x61e63b93,0xa3aa2743}}, // _об_, boti, rekl, _गजल_,
+ {{0x442cbb94,0x61e60e63,0x645abb95,0x27e7bb96}}, // chd_, sekl, coti, nenn_,
+ {{0xa3d28063,0x26c7bb97,0x224d81bf,0x61e63b98}}, // _हॉट_, nuno_, _ojek_, pekl,
+ {{0x22b580eb,0xddcd08c5,0x27e7bb99,0x7649bb9a}}, // māk_, rlaş, henn_, nney,
+ {{0x3ea0805c,0x22b580eb,0x7e6412b0,0x81d300ab}}, // [2840] ćiti_, lāk_, rlip, _হোক_,
+ {{0x25a023f8,0x27e78f7c,0xebe30af2,0xddcd099b}}, // sbil_, jenn_, моуп, plaş,
+ {{0x27e786c4,0x6e2d179f,0xd24f004e,0x22b580eb}}, // denn_, bhab, _سنی_, nāk_,
+ {{0x6e2d3b9b,0x443ebb9c,0xe8fa9777,0xddcd002e}}, // chab, zit_, иле_, rmaţ,
+ {{0x3ead003b,0x644892fa,0x645abb9d,0x28f8bb9e}}, // ćete_, yndi, yoti, сель_,
+ {{0x443ebb9f,0x6489017f,0x973c826c,0x224d80dd}}, // xit_, _džid, _heći, _ejek_,
+ {{0x645a8025,0xdcf60059,0x26c7bba0,0xdb03b49a}}, // voti, mayı, guno_, _bení,
+ {{0xdcf60182,0x443ebba1,0xdb0381d0,0x629e0a6f}}, // layı, wit_, _cení, mppo,
+ {{0x443ebba2,0x62988025,0x645abba3,0xdb03801b}}, // tit_, _stvo, toti, _dení,
+ {{0x186a8698,0xfa898028,0xd37882a5,0x47c39194}}, // _тази_, _vẫn_, leći_, _обув,
+ {{0x443e8e79,0x2bcf12e0,0x7e9180eb,0x1bfb00be}}, // rit_, स्टा, _tāpa, בליב,
+ {{0x2d9287d9,0x443ebba4,0xed5a9d34,0xd37882a5}}, // _üye_, sit_, _кое_, neći_,
+ {{0x443eb73b,0xda7b8051,0x9f5ea5b3,0xb4e918b8}}, // pit_, ינטר, _totó_, _मने_,
+ {{0xe8d92ae3,0x76408110,0x22b580eb,0x443e8168}}, // _ngụ_, limy, bāk_, qit_,
+ {{0x261c0307,0x5694044f,0x60c2bba5,0x69cb8bc5}}, // _níos_, даст, hrom, ygge,
+ {{0x2bdd0beb,0x60c2803e,0xd378805c,0x27e782d6}}, // न्ना, krom, jeći_, zenn_,
+ {{0xd378803a,0x98be002e,0x6e2d3ba6,0x60c28bcf}}, // [2850] deći_, ătă_, rhab, jrom,
+ {{0x60c2a7d9,0x6e2d2a19,0x26c7bba7,0x1c050105}}, // drom, shab, yuno_, राइल_,
+ {{0x2c000076,0xce5907b6,0x261c01a8,0x764981b4}}, // राजू_, _ганц_, _cíos_, yney,
+ {{0x26c78ca2,0x9f4c8980,0xcb138e82,0xa49b0580}}, // vuno_, _cadí_, ולע_, _pròd,
+ {{0x260809a3,0x8cc20996,0x22b580eb,0x22a78019}}, // हारी_, _लियो, zāk_, zők_,
+ {{0x442a01bc,0xf8078d0e,0xd3e300d5,0x261c03a8}}, // _glb_, ючен, _تقسی, _fíos_,
+ {{0xd00e80f7,0x224d813c,0xdb03801b,0xd378825b}}, // الى_, _tjek_, _pení, beći_,
+ {{0xdb08a54d,0x0609af2f,0x3e61808b,0x44ed3ba8}}, // gadí, сник_, jótt_, _kž_,
+ {{0x27e7bba9,0x7649bbaa,0xe4d680f7,0xdb0386a5}}, // penn_, rney, كويت_, _vení,
+ {{0x1fa78a08,0x22a78019,0x26c78ce9,0x870780f7}}, // _фраг, tők_, puno_, _وبال,
+ {{0xa3dd0023,0xdb039099,0x98b00067,0x2bdd0744}}, // थ्य_, _tení, đača_, न्या,
+ {{0x22b58341,0x2c6d0efc,0x26f98740,0x9f5e81e8}}, // rāk_, _vždy_, ्मीर_, _totò_,
+ {{0x48fe80ba,0x78720366,0x601d82d6,0x22b581a9}}, // लियो_, hæve, _fèmt, sāk_,
+ {{0xd378807d,0x3ce0809a,0x261c00f7,0xfe7981d0}}, // zeći_, _कहते_, _níor_, lmů_,
+ {{0x6441bbab,0x44ed0a56,0x973c8289,0x91cc801b}}, // mili, _až_, _peći, _हालै,
+ {{0x261c3b56,0xe1ff02ba,0xa2c0923a,0xddcd026c}}, // _ríos_, llón_, _वित्, tmaš,
+ {{0x973c803a,0x52db81ab,0x261c0013,0xd37882a5}}, // [2860] _veći, _बहुस, _síos_, veći_,
+ {{0x44ed3bac,0x29003bad,0xb4633bae,0x6e243baf}}, // _dž_, _iria_, екул, skib,
+ {{0x61f63bb0,0xd378805c,0x62978333,0xbcfb0061}}, // _kayl, teći_, _éxod, _szét,
+ {{0x7aba8039,0xcc88801c,0x64891f3a,0xfe7981d0}}, // מצעו, _sớm_, _džib, jmů_,
+ {{0x6441bbb1,0x201a1ab9,0xd3788052,0x26d83bb2}}, // kili, _kopi_, reći_, ltro_,
+ {{0xd37882a5,0x4425979d,0x6441804f,0xb05b01ec}}, // seći_, jkl_, jili, hrän,
+ {{0x6441bbb3,0xdb0885dc,0xd378811f,0x442580ee}}, // dili, tadí, peći_, dkl_,
+ {{0x60c2bbb4,0x682b1014,0x26c50024,0x29003bb5}}, // prom, _müda, šlog_, _oria_,
+ {{0x6441bbb6,0xdb0884e8,0x628e3bb7,0xbcfb2190}}, // fili, radí, _hubo, _nyél,
+ {{0x628e3623,0x26ca3bb8,0x3949802a,0x682f8821}}, // _kubo, kubo_, úase_, _rødg,
+ {{0x644f83d3,0x61f63bb9,0x46a3869b,0x28af000f}}, // écia, _bayl, _захв, जीनि,
+ {{0xb05b00f2,0x7ae101ec,0x645e3b30,0x628e199f}}, // grän, _älte, hopi, _mubo,
+ {{0x628e3bba,0x21670554,0x2baf009a,0x29003666}}, // _lubo, ятог, टरमा, _cria_,
+ {{0xb8f53bbb,0x629c3bbc,0x645e0369,0xe1ff0333}}, // _हम_, _otro, jopi, clón_,
+ {{0x629c827f,0xf1c6016f,0x628e1cb1,0xb05b2651}}, // írod, _वाचन, _nubo, brän,
+ {{0x29003bbd,0xade280d4,0x3e4581a9,0xee49827d}}, // _fria_, _कॉमन_, bētu_, hẽn_,
+ {{0x629c0df1,0x320902c1,0xbcfb0065,0x91bc0039}}, // [2870] _atro, _inay_, _azér, _המחי,
+ {{0x628e3107,0x44ed3ba8,0x26ca00e4,0x682f8366}}, // _bubo, _vž_, bubo_, _køde,
+ {{0x41d1809a,0x26ca01e8,0x628e0c1d,0x682f854f}}, // _हादस, cubo_, _cubo, _jøde,
+ {{0x628e003a,0x682f813c,0x3a26820d,0x69cf01f6}}, // _dubo, _møde, gkop_, ngce,
+ {{0xa3be8663,0x44ed3bbe,0xbcfb0019,0xd37880d2}}, // ेला_, _už_, _ezér, meću_,
+ {{0x44250355,0x23c303d3,0x9f478efc,0x7a3601ac}}, // _ôl_, _déjà_, lené_, nštr,
+ {{0xe81e9344,0x320902d0,0x6441938e,0x682f805f}}, // _बकरा_, _onay_, vili, _rødd,
+ {{0x64418355,0xc7448013,0xdb03826f,0x764d3bbf}}, // wili, عضوي, _nená, nnay,
+ {{0x3d000e5b,0xdb01b60c,0x280881d0,0xdb07349a}}, // रिये_, mblé, átní_, _mejí,
+ {{0x61f61b37,0x32093bc0,0xe7b582e3,0x09c6016f}}, // _sayl, _anay_, _آماد, वल्य,
+ {{0x61f602bb,0xb05b1bc0,0x1b1f00ab,0x3a2dbbc1}}, // _payl, trän, _যেতে_, _klep_,
+ {{0x6441b05c,0x2900016f,0x25740125,0xd37895db}}, // sili, _pria_, _júlí_, jeću_,
+ {{0x6441bbc2,0x9f478038,0xd37881dd,0x645e0010}}, // pili, dené_, deću_, yopi,
+ {{0x3e458029,0xaac58996,0x26d80098,0x7e69829e}}, // rētu_, _विपक, ttro_, llep,
+ {{0x683000f2,0x2ba381a2,0x26d8002a,0x25b202c4}}, // _rädd, _ख्या, utro_, tayl_,
+ {{0x29000722,0x764d3bc3,0x628e3bc4,0x7bc301fa}}, // _tria_, gnay, _rubo, ónun,
+ {{0x629c0942,0x628e003b,0x224038dc,0x2bd48105}}, // [2880] _stro, _subo, _imik_, _दामा,
+ {{0xd7ca003d,0xf1bf0b4c,0x6d4480e1,0x764d01b4}}, // _بوده_, nkán_, _žiak, anay,
+ {{0x645e3bc5,0x9f47803e,0x2bdd001b,0x63bc8ff4}}, // ropi, bené_, न्धा, órni,
+ {{0x7e69985c,0xfaf800eb,0x63ae3bc6,0x63a90390}}, // jlep, _šīs_, _webn, _đene,
+ {{0x5fc985b3,0x2bc703db,0x249d81c0,0x08979a3c}}, // _राखल, _लाचा, _ntwm_, وضوع_,
+ {{0x7e62bbc7,0x628e381c,0x0ee20bb8,0x2bdd275c}}, // _skop, _tubo, _पहाड, न्दा,
+ {{0x44442f3a,0x61ebbbc8,0x629c1277,0x63833bc9}}, // mi_, begl, _utro, _агра,
+ {{0x26c0bbca,0x2487803c,0x64891487,0x7e69bbcb}}, // čio_, _ainm_, _užic, glep,
+ {{0xdb08862f,0x752402ec,0x01d7830f,0x83fc8654}}, // dadá, nyiz, _توقع_, hođe,
+ {{0x44443bcc,0x83fc82fd,0x9f47827f,0xd3788289}}, // ni_, kođe, zené_, zeću_,
+ {{0xcc888142,0x44443bcd,0x764d02a3,0xd6cf806b}}, // _mới_, ii_, ynay, _نقل_,
+ {{0xc7b8803a,0x7e62bbce,0xb5fb0032,0xdb088061}}, // _dođe_, _ukop, _akán, gadá,
+ {{0x9f478a21,0x7e60bbcf,0x69dd007b,0xd8488133}}, // vené_, momp, rfse, họm_,
+ {{0x61eb8063,0x7e608812,0x656f81ec,0x601d89c4}}, // zegl, lomp, üche, _jèmp,
+ {{0x5f9417f4,0x83fc803b,0x290d002e,0x2bf3816f}}, // тист, gođe, ţea_, _असतं_,
+ {{0x44443bd0,0x41e70d13,0x5c990a14,0x32090122}}, // ei_, _ціка, зкая_, _unay_,
+ {{0x44443a20,0x78a2a828,0x5c750009,0xd37882a5}}, // [2890] fi_, jpov, елат, reću_,
+ {{0x83fc8052,0x78a2803e,0x9f4c026f,0x6f030118}}, // bođe, dpov, ždá_, _ánco,
+ {{0xf1b9803a,0xb5fd84a8,0x64890110,0x9f479c18}}, // _loše_, loše, _džia, pené_,
+ {{0x44443bd1,0x6830016d,0xdb1c2509,0x7d02bbd2}}, // ai_, _väde, _acró, _oros,
+ {{0x5d852f0a,0xb5fd80ce,0x0ef7809a,0x798d0c53}}, // _سلسل, noše, ंट्स_, _mgaw,
+ {{0x61eb8353,0x160d016f,0x661d0901,0xd848819d}}, // segl, हावर_, _hosk, bọm_,
+ {{0x661d0364,0xa2c08b84,0x64453385,0x7e69bbd3}}, // _kosk, _विष्, mihi, tlep,
+ {{0x7d02bbd4,0x64453bd5,0x798d0c14,0x661d0009}}, // _bros, lihi, _ngaw, _josk,
+ {{0x661d3bd6,0x7e69810b,0xb5fd8279,0xdfdb8098}}, // _mosk, rlep, joše, _мъж_,
+ {{0xa2c090c5,0xa2c4035a,0x7d02bbd7,0x64453bd8}}, // _विश्, ांच्, _dros, nihi,
+ {{0x7d028bf0,0xdb039d1a,0xdb0894a8,0x7e69bbd9}}, // _eros, _menç, tadá, plep,
+ {{0x44443bda,0x83fc8025,0xb87b11b9,0x64453bdb}}, // zi_, vođe, ntíf, hihi,
+ {{0xdb08bbdc,0x660b8288,0xfce5835f,0x64453bdd}}, // radá, _ingk, _чоло, kihi,
+ {{0x26c78098,0x7af50019,0x6e298aa2,0x95cc8085}}, // orno_, _oszt, lkeb, _çərç,
+ {{0x44440743,0xdee30328,0xbcfb0019,0x2edd8ebf}}, // vi_, лори, _szép, _महोत,
+ {{0x444437b4,0x26128bb8,0x83fc805c,0x9f583bde}}, // wi_, थायी_, rođe, ndré_,
+ {{0x61e42562,0x661d1988,0x660b8359,0x7af50019}}, // [28a0] _ibil, _dosk, _mngk, _aszt,
+ {{0x44442bb8,0x799b8300,0x76443bdf,0x394d03a8}}, // ui_, _afuw, siiy, úese_,
+ {{0x4fd7093f,0xe7c983db,0x7fd70158,0x660b8e60}}, // _אויב_, _राजप, _אויס_, _ongk,
+ {{0xcc888142,0x3ce08006,0x09c700ab,0x6ad00035}}, // _với_, _कहले_, ষ্ঠা, _हमीर,
+ {{0x44440573,0x47b300c8,0x61e4283a,0x64453be0}}, // pi_, _জাতী, _mbil, bihi,
+ {{0x660b9ad4,0xcc888104,0x25dda23a,0x44443be1}}, // _angk, _tới_, क्सी_, qi_,
+ {{0x61e40025,0x78a2807d,0x7e6080d7,0xdb0a806a}}, // _obil, spov, tomp, _udfø,
+ {{0x78a280c3,0x6fd281fe,0x7d090118,0x5fd28072}}, // ppov, _सावं, _áesc, _सावल,
+ {{0x5454bac1,0x02e0001b,0xfd1285ff,0x69c08609}}, // квит, _नहुन, اجع_, _idme,
+ {{0x660b8455,0xccf30039,0xf1b981a1,0x7d1d8106}}, // _engk, רכז_, _voše_, ässa,
+ {{0x200cbbe2,0x6fd70074,0xdb0e1f90,0xaa59168a}}, // _indi_, _बाबू, _bebé, дину_,
+ {{0x5ab7093f,0x7d02b807,0xfe7282e3,0x200c876d}}, // עלכע_, _tros, _صدا_, _hndi_,
+ {{0x442e8bc5,0x661d3be3,0x644502a3,0x64a38a4c}}, // _tlf_, _rosk, yihi, _бача,
+ {{0x61e40146,0xb5fd8796,0xdb070061,0xdb038106}}, // _ebil, roše, _lejá, _benä,
+ {{0x661d1234,0xadf4823c,0xa3d9b792,0x201e8ff2}}, // _posk, _आसान_, _डॉट_, _moti_,
+ {{0x27fa002a,0xa5bb0032,0x201ebbe4,0x78ad0b5e}}, // _eapn_, _alór, _loti_, _twav,
+ {{0x64453be5,0x8bc71285,0xf1bf0c83,0xb05b0338}}, // [28b0] tihi, есед, nkám_, nräk,
+ {{0x201eaad4,0x69c087fc,0x61e4111b,0xb4e107e6}}, // _noti_, _adme, _zbil, _दही_,
+ {{0x64453be6,0xdb039fee,0xb5fd8140,0xdb070061}}, // rihi, _venç, nošc, _bejá,
+ {{0x61fd2d85,0x61ef04cc,0x200c9437,0xaca3019d}}, // ndsl, necl, _andi_, _amục,
+ {{0xd3668077,0x25a904a7,0x7e6d3be7,0xa5bb3be8}}, // _چه_, mbal_, mlap, _flór,
+ {{0xd3668077,0x7e6d3be9,0xdb18802a,0x28980039}}, // _نه_, llap, _adví, עדון_,
+ {{0x6c540470,0xf456007c,0xe4543bea,0x8f9c00be}}, // _скру, _יישר_, _скры, ליגי,
+ {{0x2499a6d5,0x60c481e2,0x200cbbeb,0x59d8864a}}, // ísmo_, šima, _endi_, _डायर,
+ {{0x320d8079,0x9f47bbec,0x9f4a0722,0xdb0a83a8}}, // _iney_, mení_, rebé_, _defí,
+ {{0x9f47800d,0xb5fb3bed,0x6e29806f,0x61e400e5}}, // lení_, _skál, skeb, _sbil,
+ {{0x68300006,0x61fbbbee,0xbf15819f,0x6fdf00d4}}, // _näda, _kaul, _جواب, _पॉइं,
+ {{0x660b94ec,0xa696810f,0x201e80f1,0x61fbbbef}}, // _ungk, _סכנה_, _zoti_, _jaul,
+ {{0x25a93363,0x463a8158,0x29040376,0x91ba81c6}}, // dbal_, _קענע, íma_, ומרי,
+ {{0x61fbbbf0,0x6a7888f1,0x69a23bf1,0x25dd8519}}, // _laul, mífe, _क्री, क्री_,
+ {{0xf1b385fc,0x998f8110,0x61e43bf2,0x6443bbf3}}, // ुणान, nigų_, _tbil, _imni,
+ {{0x61e40503,0x9f4783fb,0xe8d18424,0x7e6d1ac4}}, // _ubil, jení_, _समूच, glap,
+ {{0x9f47803e,0x0d86174a,0x6a7888f1,0x44313bf4}}, // [28c0] dení_, тлан, nífe, _blz_,
+ {{0xe0b70f60,0x03a607b6,0x64890084,0x9f5a03ed}}, // _שליט_, тижо, _džio, _japë_,
+ {{0x2bd48076,0x61fbbbf5,0xe9da9cf8,0x321f826b}}, // _दासा, _baul, дке_, _bouy_,
+ {{0x201e8205,0x98a69630,0x6489025b,0x831a80be}}, // _soti_, виде, _užin, וועז,
+ {{0x201ebbf6,0x61fb84b9,0xdb0a81df,0x83fc826c}}, // _poti_, _daul, _refí, hođa,
+ {{0x03a3035f,0xf1bf04e8,0x6b9c02df,0x61fb82f9}}, // _виро, vkám_, _ufrg, _eaul,
+ {{0x8c431fab,0x201eaa4d,0x044322b7,0xddcd0087}}, // _тере, _voti_, _терн, noaş,
+ {{0x9f47800d,0x6443bbf7,0x61fbbbf8,0x35f4a42e}}, // cení_, _amni, _gaul, упир,
+ {{0x29048012,0x61ef0358,0x201e802e,0x9f49816b}}, // _urma_, vecl, _toti_, čkám_,
+ {{0xfa8a0028,0x200cbbf9,0x1e1f03db,0xa84a80b7}}, // _mẫu_, _undi_, _यक्ष_, _غلام_,
+ {{0x83fc812b,0x1db23bfa,0x0bb7025f,0x61fbaaa0}}, // gođa, जरात, כלים_, _yaul,
+ {{0x27e985f5,0xf2d30158,0x61e29529,0x60dd0711}}, // đane_, סער_, nfol, ttsm,
+ {{0x9f45826f,0xcc8880ff,0x6a788118,0x2e37825f}}, // _malý_, _bớt_, cífe, _בראש_,
+ {{0x9f47800d,0x36d50328,0xdb05008b,0x69b480d4}}, // zení_, логр, rahú, आरडी,
+ {{0x25a93bfb,0xf1b9805c,0x442cbbfc,0x69d382f1}}, // tbal_, _loša_, mkd_, _डाली,
+ {{0x2d8f006a,0x61e2bbfd,0x64438035,0xe654004a}}, // øge_, jfol, _zmni, авсь,
+ {{0x25a6040e,0x3ea9803a,0x23b6800f,0x9f47800d}}, // [28d0] ñola_, ćati_, _आजाद, vení_,
+ {{0x61fba338,0x7e6d189e,0x25a93bfe,0x57f5164c}}, // _saul, slap, sbal_, _спот,
+ {{0x61fbbbff,0x68e98098,0x61e29106,0x9f4783f2}}, // _paul, _sped, ffol, tení_,
+ {{0x64489e74,0x6e36026f,0x61fb80dd,0xe1ff3c00}}, // hidi, chyb, _qaul, ndó_,
+ {{0xdca680f7,0x6e2d3c01,0xe0df3c02,0x660f0b80}}, // _دى_, mkab, ntò_, _inck,
+ {{0xb5fd80eb,0x628d00fc,0x6e2d3c03,0xf1b98115}}, // doša, _liao, lkab, _doša_,
+ {{0x61fbbc04,0x6448bc05,0x81cb00ab,0x3669960f}}, // _taul, didi, র্য_, нако_,
+ {{0x83fc803a,0xc7b88024,0xc69400be,0x644380b9}}, // vođa, _vođa_, סאפ_, _smni,
+ {{0xf1b98e9f,0x7aea8706,0x7a2601fa,0x4bd981e2}}, // _goša_, _apft, _mótt, ньня_,
+ {{0x6448bc06,0xe5768048,0x62953c07,0x628d0362}}, // gidi, ызы_, _kuzo, _aiao,
+ {{0x2bd49d01,0x6e2d3c08,0xcc888129,0xeab9814c}}, // _दारा, kkab, _rớt_, эйн_,
+ {{0xe9df00f7,0x83fc81dd,0x628d3c09,0x82361190}}, // siún_, rođa, _ciao, اردا,
+ {{0x6448bc0a,0x7d0902a5,0x628d2322,0xfd108154}}, // bidi, _šesn, _diao, _وجه_,
+ {{0xddc4001b,0x9f430009,0xdb03a84c,0x7a263c0b}}, // dliš, kejä_, _menú, _rótu,
+ {{0x75298019,0xae179664,0xd5b786b7,0x3ebe007b}}, // lyez, थापन_, _इजाज, étt_,
+ {{0x661b8867,0x6e2d043b,0x81cb00ab,0x67210589}}, // njuk, gkab, র্ব_, älja,
+ {{0x7529bc0c,0x62950102,0x753bbc0d,0x7a26008b}}, // [28e0] nyez, _auzo, nzuz, _dótt,
+ {{0x63b50390,0x661b98c2,0x26d10234,0xbc6a0bbe}}, // _cezn, hjuk, buzo_, رمان_,
+ {{0x6e460878,0x49ca9508,0xd62a3c0e,0x61e2804a}}, // _бенз, елен_, _зоне_, tfol,
+ {{0x6448bc0f,0x78a42d38,0xb05b0106,0x7afcbc10}}, // zidi, _ktiv, tsäg, ærti,
+ {{0x28f88e86,0xad9b01ac,0x6448bc11,0xdb01816b}}, // тель_, _vnút, yidi, valý,
+ {{0x7d043c12,0xdb0387e0,0xb8dc8074,0xb5fd80eb}}, // mvis, _denú, _आब_, voša,
+ {{0x62953c13,0xdb18816b,0xa3d7897d,0xddc401a9}}, // _guzo, _odvá, िलन_, rniņ,
+ {{0xb5fd8029,0xceb30039,0x6448831d,0x69d609c1}}, // toša, זיה_, widi, _भाषी,
+ {{0x2fd68013,0x81cb00c8,0x7d040082,0x3ce03c14}}, // _متاح, র্ড_, nvis, ntiv_,
+ {{0x69c4011e,0xb5fd80eb,0x2a66809c,0x628281b4}}, // _adie, roša, loob_, lmoo,
+ {{0x78a40073,0xd7938013,0x2f56a83b,0xb5fd812b}}, // _ativ, _المخ, _стас, mošn,
+ {{0xa2cc0fea,0xb5fd93cf,0x6448bc15,0x2a66856c}}, // _हिन्, lošn, sidi, noob_,
+ {{0xa2c08744,0x9f47803e,0x6448bc16,0xe1ff3c17}}, // _विक्, mená_, pidi, rdó_,
+ {{0x9f47803e,0x62828282,0x69c43c18,0xb5fd826c}}, // lená_, hmoo, _edie, nošn,
+ {{0xd904850c,0xf8d1bc19,0x6e22bc1a,0xae098023}}, // _کی_, _सम्प, _koob, वाचन_,
+ {{0x63b51cf3,0xadf98bb8,0x69c00333,0xddc40088}}, // _sezn, ्ययन_, ómet, tliš,
+ {{0x6e228952,0xdb0e05e4,0xd3a702df,0xbcfb08f9}}, // [28f0] _moob, _debí, греп, _nyét,
+ {{0x68ed3c1b,0xad9b0e67,0x6e2d013c,0x224b3c1c}}, // _ipad, _saúd, skab, lick_,
+ {{0x81cb00ab,0x7d043c1d,0xe29800e8,0x09bc80ab}}, // র্ণ_, avis, гає_, _ইউজা,
+ {{0x98a99487,0x6e22bc1e,0xdb07016d,0xa4d51138}}, // šač_, _noob, _rejä, _комі,
+ {{0x212b3c1f,0x90e48154,0x9f478a21,0x3ce00087}}, // nych_, مسون, dená_, ctiv_,
+ {{0x63ba8a20,0x43943c20,0x2ca9003d,0x41268009}}, // jatn, _гарс, mpad_, рошо_,
+ {{0x6e22bc21,0x59c5016f,0xc7d8007c,0x2ca92a33}}, // _boob, वणार, אווי_, lpad_,
+ {{0x6e22bc22,0x060995e0,0x212b01ac,0x442304e4}}, // _coob, тник_, kych_, _hoj_,
+ {{0x4423146a,0x680b8029,0x63ba9ce5,0xe61694ef}}, // _koj_, _pēdē, fatn, рды_,
+ {{0x4423003a,0x3ce5901b,0x212b3c23,0xa0c480f7}}, // _joj_, _कहीं_, dych_, _ديكو,
+ {{0x44232494,0x68ed3c24,0x6e22bc25,0x9f45bc26}}, // _moj_, _apad, _foob, _való_,
+ {{0x442301e9,0x6e2282a3,0xa2d626ee,0xfe9a8039}}, // _loj_, _goob, मंत्, _סינמ,
+ {{0xd1760791,0x61e98d4c,0x63ba8057,0x6721016d}}, // рыны, _mbel, batn, äljn,
+ {{0x80cd89a3,0x7d0412bb,0x98a0803a,0x442301e9}}, // _सिने, vvis, šić_, _noj_,
+ {{0x61e9bc27,0xc4478117,0x6fd703eb,0xdfcf00f7}}, // _obel, _لیکن_, _बारू, ظيف_,
+ {{0x7c2387a3,0x7d043c28,0x3ce004b7,0x6a761e00}}, // _honr, tvis, ttiv_, máfo,
+ {{0x212b0063,0x1c460110,0xa4463c29,0x656f0114}}, // [2900] cych_, анам, анад, _cych,
+ {{0x629e9313,0x61e9bc2a,0x69db85b4,0x442301e9}}, // _époc, _abel, _acue, _coj_,
+ {{0xb87b3c2b,0x68fbbc2c,0x25adbc2d,0x2bdda539}}, // stíc, _asud, kbel_, _माना,
+ {{0x27ed003a,0x09e69cf6,0x2cbf9922,0xcc888028}}, // đene_, родн, ksud_, _lớp_,
+ {{0x63ba810b,0x6282bc2e,0x9f47826f,0x6f088a21}}, // yatn, rmoo, vená_, _srdc,
+ {{0x61e9bc2f,0x6e228952,0x3ec68009,0x44230147}}, // _ebel, _soob, асиб, _goj_,
+ {{0x63ba803b,0x212b009a,0x9f58009f,0xb87b05e4}}, // vatn, zych_, ndrà_, ntía,
+ {{0x27e98025,0x2c0a035a,0x63ba89ca,0x2fc5802a}}, // đana_, _होतं_, watn, _edlg_,
+ {{0x63ba89a4,0x80a6826a,0x15e2816f,0x27e08041}}, // tatn, _ضمان, क्षर_, ƙin_,
+ {{0x7c23bc30,0x61ff3c31,0xaca401bc,0x3cf90816}}, // _conr, _saql, _amịr, _उनसे_,
+ {{0x212b0d38,0x25ad923f,0x64890699,0xdfcf81a8}}, // wych_, bbel_, _džih, _زين_,
+ {{0x2ca93c32,0xfce68171,0xade2b13a,0x212b0dee}}, // ypad_, _водо, क्शन_, tych_,
+ {{0x224b3c33,0x63babc34,0xf99f0722,0x984b0081}}, // rick_, patn, rcè_, вява_,
+ {{0x212b3c23,0x656f000d,0x26c50289,0xf1a70697}}, // rych_, _rych, šlom_, _क्रन,
+ {{0x442301c5,0xf8bf1b20,0xc058035f,0xe29780a9}}, // _roj_, ssé_, рія_, _кај_,
+ {{0x44230282,0x7a360084,0x2ca909ff,0xdda80087}}, // _soj_, kšty, tpad_, итул_,
+ {{0x442301c5,0xdb050013,0xb5fdbc35,0x63b881c0}}, // [2910] _poj_, rbhí, došl, _kevn,
+ {{0x656f027f,0x61e989da,0x0ba72e2e,0x44232928}}, // _vych, _sbel, ршам, _qoj_,
+ {{0xd7dc023c,0xed59803a,0x44230640,0x656f009a}}, // _बातच, maže_, _voj_, _wych,
+ {{0xed598503,0x6298bc36,0x63b8801b,0x2ca90106}}, // laže_, _muvo, _levn, ppad_,
+ {{0x44233c37,0x3ce58105,0x20010a63,0xe1ff3c38}}, // _toj_, _कहें_, _kahi_, llós_,
+ {{0x9f5801ca,0x63b880e8,0x81dd00ab,0x20033c39}}, // ldrá_, _nevn, ড়ি_, ndji_,
+ {{0x26c19385,0x6298b340,0x752d1fce,0xad9b0187}}, // ého_, _nuvo, lyaz, _gaúc,
+ {{0x7c23bc3a,0xa2c0825e,0x9f580693,0x26c98067}}, // _sonr, _विज्, ndrá_, čao_,
+ {{0x81dd00c8,0xdd918117,0x68fb8669,0xeb999a19}}, // ড়া_, _ہوا_, _usud, лип_,
+ {{0xc6e78cde,0x200100ad,0x7c3abbb0,0x6a761984}}, // _відп, _nahi_, ghtr, táfo,
+ {{0x26c502a5,0x2a7f8282,0xd6db84ae,0xe365a306}}, // šloj_, _khub_, _шта_, скни,
+ {{0x69d9bc3b,0xf59580f7,0x81cb00ab,0x8d9580f7}}, // ngwe, _الإج, র্স_, _الإش,
+ {{0x20011149,0xbca5803d,0x66ea811c,0xdb15136f}}, // _bahi_, _امتي, _təkc, _dezé,
+ {{0x7c3abc3c,0x22490859,0xf1b981dd,0x8fa30cec}}, // chtr, _emak_, _leš_, заре,
+ {{0xe7371860,0x9e5a3c3d,0xdb1c016b,0x2ca68122}}, // бер_, граф_, _odrá, _ztod_,
+ {{0x2a7f81c0,0xf1b9bc3e,0xb05b189c,0xb87b157a}}, // _nhub_, _neš_, tsäc, rtía,
+ {{0xe57a0dc0,0xb87b0511,0x15b90196,0x629881ec}}, // [2920] уза_, stía, рысы_, _zuvo,
+ {{0x8ccf809a,0xd37e8042,0x9f47bc3f,0x09b00264}}, // _दिनो, šćem_, cenç_, _কাটা,
+ {{0xfdf9a3bd,0xdb0a82af,0xb05b3c40,0xadf9800c}}, // ्यास_, _gefä, rrät, ्यान_,
+ {{0xd5b90009,0xb05b016d,0xb7b5819d,0xd186ab3f}}, // _всё_, srät, _kọw, блей,
+ {{0xa3d78744,0x69db00be,0xb05b0106,0x9c82807a}}, // िला_, אַוו, prät, ščil,
+ {{0x64a3215c,0x33d5035f,0x66043c41,0xdfcf8013}}, // _хара, _міст, ldik, فيه_,
+ {{0xb5fd8025,0x2ca68082,0x68e280df,0xe8190778}}, // pošl, _stod_, stod, दाता_,
+ {{0xb8ce8a49,0x66043c42,0x60c40669,0x91e38ae7}}, // _কি_, ndik, _ovim, _носе,
+ {{0x63b88efc,0x6298b5aa,0xe12387b6,0x68300338}}, // _pevn, _suvo, _емти, _vädj,
+ {{0x96ba941e,0x22493c43,0x4438008e,0x60c2bc44}}, // _буду_, _smak_, _jlr_, msom,
+ {{0x6e969ddd,0xe69680f7,0x60c284dc,0x7d0b9384}}, // _الصا, _الصد, lsom, _ergs,
+ {{0xe7178051,0x80dd00c8,0x7c3a80f1,0xc33302f6}}, // _לחבר_, যবস্, shtr, פור_,
+ {{0x63be0877,0x60c2bc45,0x9e6580f7,0xa3ca2c4f}}, // kapn, nsom, _بالن, ोला_,
+ {{0xf1db0101,0x60c29149,0x25de99e8,0xdb1c006a}}, // _भावन, isom, _गाडी_, _idræ,
+ {{0x63be09ca,0x9f47bc46,0x02a281bc,0xddc9826c}}, // dapn, renç_, _klọọ, lleš,
+ {{0x66028074,0x22492bea,0x20012aa0,0xed59b114}}, // _jaok, _umak_, _wahi_, saže_,
+ {{0x5fc88aad,0x2001010a,0x61ed3c47,0x443800b9}}, // [2930] रणाल, _tahi_, _ibal, _blr_,
+ {{0x7bdabc48,0x60c28e23,0x44e2807b,0xb6069487}}, // ngtu, dsom, ið_, lašć,
+ {{0x62808267,0xd37b1401,0x319e8133,0x645c0580}}, // _bhmo, уча_, _ịza_, _djri,
+ {{0x62808858,0x2a7f822c,0x6602aa42,0x998dbc49}}, // _chmo, _qhub_, _naok, _aleš_,
+ {{0x61ed273b,0x4ac50076,0x7bc30125,0xf1b984c4}}, // _mbal, _लटकव, ónus, _veš_,
+ {{0xc60100c8,0x9ea992b2,0x16a99485,0xd00981ae}}, // _একটা_, авка_, авки_, реке_,
+ {{0xe7398c6e,0x62358009,0x6e3ba0b3,0x46399156}}, // рей_, _деву, thub, ичия_,
+ {{0x82378f24,0xe7f5809a,0x7bda837a,0x25de816f}}, // _ارسا, _इसका_, egtu, _गाणी_,
+ {{0x6e3b8867,0x7e69838e,0xddc9807a,0x998d9024}}, // rhub, roep, gleš, _fleš_,
+ {{0x61ed3c4a,0x6e3bbc4b,0x7e6982f7,0x72c58087}}, // _abal, shub, soep, обоз,
+ {{0x60c4003a,0xfbd204de,0x61ed0c2e,0x7c2701a1}}, // _svim, ותי_, _bbal, _mojr,
+ {{0x3cfd90af,0xa2cc05fb,0x6e3b805d,0x7c270168}}, // _tswv_, _हिस्, qhub, _lojr,
+ {{0xb87b12cc,0x644f82be,0xe44e8a08,0x9f478009}}, // rtín, écis, _жж_, senä_,
+ {{0x6e3981c0,0xb5fd8450,0x61ed3c4c,0xdfd201a8}}, // _hlwb, košk, _ebal, بيس_,
+ {{0x7d09bc4d,0xb5fd8052,0x81b900ab,0x969510f8}}, // lves, jošk, _চার_, зруш,
+ {{0x4427bc4e,0x27ed003a,0x78a9801b,0x85053c4f}}, // _hon_, đena_, _otev, रिंट_,
+ {{0x66043c50,0x4427bc51,0x63be3c52,0x0c263c53}}, // [2940] rdik, _kon_, tapn, оман,
+ {{0x69c982a3,0x44279d6a,0x2aed80a5,0xf20286a7}}, // _adee, _jon_, _जहाँ_, _रोज़_,
+ {{0x442786e3,0x290dbc54,0x6d40ae4d,0xf1b985f3}}, // _mon_, _area_, zzma, _gošk_,
+ {{0x290dbc55,0x4427801c,0xe4da003d,0x60c2bc56}}, // _brea_, _lon_, _پوست_, tsom,
+ {{0xa88a8071,0x290db340,0xdb1abc57,0x208a8558}}, // айда_, _crea_, raté, айди_,
+ {{0x4427bc58,0xb8f28f1b,0x764b831d,0x29000748}}, // _non_, _वि_, _amgy, _msia_,
+ {{0xab2a0021,0x71a68705,0x26d83c59,0xb5fd826c}}, // _това_, _мавз, nuro_, cošk,
+ {{0x44278af9,0x27f830e5,0x25bfbc5a,0x290d8118}}, // _aon_, hern_, gaul_, _frea_,
+ {{0x4427bc5b,0x629c3c5c,0xe4e6835f,0x27f81647}}, // _bon_, _huro, ційн, kern_,
+ {{0x4427bc5d,0x629c3c5e,0x44e28125,0x26d83c5f}}, // _con_, _kuro, rð_, kuro_,
+ {{0x629c00a9,0x2be20b86,0x44278051,0xa3e205e8}}, // _juro, _पाना, _don_, _धान_,
+ {{0x26d83c60,0x629c3c61,0x64893c62,0x63bc02af}}, // duro_, _muro, _uživ, _lern,
+ {{0x4427bc63,0x3204809a,0x27f83c64,0xc21800e8}}, // _fon_, _mamy_, fern_, ією_,
+ {{0x44279a03,0x9f4c027f,0x27f83a53,0xb87b01a8}}, // _gon_, ždý_, gern_, rtío,
+ {{0xd5bb35cc,0x764b831d,0x629c0110,0x26c53c65}}, // иса_, _ymgy, _nuro, élo_,
+ {{0x26cd003a,0x4427bc66,0x61ed3c67,0xc7b880ce}}, // čeo_, _zon_, _ubal, _dođi_,
+ {{0x4427bc68,0x2d8f034a,0x63bc383f,0xdb0385e4}}, // [2950] _yon_, äge_, _bern, _fenó,
+ {{0xf1d88023,0xfbab0048,0x645abc69,0x63bc3c6a}}, // _डाउन, ртай_, nnti, _cern,
+ {{0x63bc3c6b,0xb87b0272,0x26d83c6c,0x7d09bc6d}}, // _dern, ntím, curo_, zves,
+ {{0xb5fd8db7,0x290d802e,0xf9c7835f,0x629c3c6e}}, // rošk, _prea_, іщен, _duro,
+ {{0x8d87361c,0x361a04de,0xddc8809a,0xdb188118}}, // _фунд, _הורד, łośc, _devé,
+ {{0x63bc3c6f,0x629c1bea,0x290d802e,0x7bce8207}}, // _gern, _furo, _vrea_, óbul,
+ {{0xf1b9812b,0x629c3c70,0x443ebc71,0x9f4781d6}}, // _loši_, _guro, dht_, lenú_,
+ {{0xf1bf87ca,0x4427bc72,0x682f8bc5,0x443e81ec}}, // _þá_, _son_, _nødv, eht_,
+ {{0x63bc3c73,0x27f8131e,0x7bca802a,0x225fa168}}, // _yern, yern_, _bdfu, _ijuk_,
+ {{0x443e804c,0x2900008e,0x69dd3c74,0x442783e7}}, // ght_, _rsia_, egse, _qon_,
+ {{0x44278943,0xa822803d,0x63a502af,0xdb0e01ec}}, // _von_, _سکون, ichn, _gebä,
+ {{0x2005bc75,0xdb03862f,0x645abc76,0xfc3f178e}}, // _hali_, _senó, anti, _stíl_,
+ {{0x20058859,0x4427bc77,0x27f83c78,0xdb0881a8}}, // _kali_, _ton_, tern_, fadó,
+ {{0x443e8bd5,0x26d83c79,0x9f5a047f,0x27f801ec}}, // cht_, turo_, _papà_, uern_,
+ {{0xe7ed9880,0xe7373c7a,0x7bc18c0f,0x63bc0d11}}, // च्या_, пер_, malu, _rern,
+ {{0x7bc19a67,0x200580d7,0x629a00e7,0x26d800b4}}, // lalu, _lali_, _étoi, ruro_,
+ {{0x63bc0393,0x29000057,0xdb1891b9,0x27f83c7b}}, // [2960] _pern, _usia_, _revé, pern_,
+ {{0x7bc1bc7c,0x26d81f78,0x224dbc7d,0x2005bc7e}}, // nalu, puro_, _amek_, _nali_,
+ {{0xeb971bc1,0x179b010f,0x629620b0,0xe7b70039}}, // чих_, _לייב, _kiyo, _מהיר_,
+ {{0x63bc0077,0x629610fe,0xd36f12c5,0xdb1c0168}}, // _wern, _jiyo, دهم_, _herë,
+ {{0xe5720b76,0x63bc3c7f,0x2005bc80,0x645abc81}}, // _سطح_, _tern, _bali_, ynti,
+ {{0x66063c82,0x200582a3,0x7bc1bc83,0x186794f6}}, // _hakk, _cali_, jalu, пачи_,
+ {{0x2005bc84,0x81d400c8,0xed59a944,0x8f9b03c8}}, // _dali_, স্য_, važa_, זיצי,
+ {{0x225a8013,0x442a3c85,0x7e648088,0x62963c86}}, // _الرد_, _hob_, čipo, _niyo,
+ {{0x6d440698,0x442a3c87,0x7bc1bc88,0x682f9dd5}}, // nzia, _kob_, falu, _rødv,
+ {{0x20058a8e,0x645abc89,0x7bde04a7,0x64893c8a}}, // _gali_, unti, ngpu, _užit,
+ {{0x442a08df,0x62963c8b,0xddcd001b,0x62840046}}, // _mob_, _biyo, hlaš, _bhio,
+ {{0x443e88cf,0x1e8691e9,0x442a3c8c,0x62843c8d}}, // sht_, _элем, _lob_, _chio,
+ {{0x62963c8e,0x588713cd,0x7bc1a45e,0x9f5e809f}}, // _diyo, зыва, balu, _matí_,
+ {{0x6d440063,0x27ed003b,0xc9538051,0x442a3c8f}}, // dzia, đeno_, תמש_, _nob_,
+ {{0x6284003c,0x7e6d0955,0x3ce681c0,0x66063c90}}, // _fhio, toap, _hqov_, _bakk,
+ {{0x33778051,0xf4a800c8,0x629606a9,0xa2d30105}}, // _מתוך_, _ওয়েব, _giyo, _डिब्,
+ {{0x81c28a49,0x6a7d040e,0x7e6d002e,0xdb08bc91}}, // [2970] ংলা_, léfo, roap, padó,
+ {{0x442a0069,0x6296132e,0x225f8186,0x78ad3c92}}, // _cob_, _ziyo, _sjuk_, _itav,
+ {{0x3cf9101c,0x63a50114,0x6606270c,0x62963c93}}, // _उनके_, rchn, _fakk, _yiyo,
+ {{0x63a502af,0xe9df3c94,0x2005bc95,0x644e02ec}}, // schn, ngú_, _sali_, _embi,
+ {{0x8c3b0352,0x3ce6822c,0x9984801b,0x443c9d14}}, // _auße, _nqov_, _domů_, _olv_,
+ {{0x7d0d3c96,0x6a7612ca,0x66ea8085,0x66063c97}}, // mvas, ráfi, _təkl, _zakk,
+ {{0x7d02933b,0x7bc193bd,0x2005bc98,0x66060c2e}}, // _asos, valu, _vali_, _yakk,
+ {{0x2005a223,0x2d998aa2,0x442a06b9,0x78ad3c99}}, // _wali_, øse_, _zob_, _otav,
+ {{0x7bc1bc9a,0x799b8122,0x442a0282,0xd0d48081}}, // talu, _iguw, _yob_, _потъ,
+ {{0x62963c9b,0x7c2a831d,0x62840c41,0x442a0069}}, // _siyo, _cofr, _shio, _xob_,
+ {{0x37e300c8,0xb05b00f2,0x7bc1bc9c,0x6fd7016f}}, // য়ার, rsäl, ralu, _बाजू,
+ {{0x7bc1bc9d,0xc7b88052,0x1c0a016f,0x5eac00ab}}, // salu, _dođu_, _होईल_, _ছিলে,
+ {{0xed4e8ea2,0x7bc1bc9e,0xdb1c00f1,0xdb1e0511}}, // _ро_, palu, _perë, rapé,
+ {{0xb8f61834,0x40950847,0x66063c9f,0x6609bca0}}, // _हि_, ерст, _sakk, ldek,
+ {{0xdee33ca1,0x628409de,0xe73a0009,0x66e3008f}}, // кори, _thio, цев_, кора,
+ {{0x442a3ca2,0x6d440102,0x644e00dd,0xa3a98a74}}, // _sob_, tzia, _smbi, गुर_,
+ {{0x442a3ca3,0x66063ca4,0x15fd0035,0x6e24008e}}, // [2980] _pob_, _vakk, उज़र_, rjib,
+ {{0x6d443ca5,0x66063ca6,0x64418234,0x66098198}}, // rzia, _wakk, nhli, hdek,
+ {{0x7d090ef1,0x66063ca7,0xdb1c3ca8,0xf1b9812b}}, // _šest, _takk, _keré, _lošu_,
+ {{0x7df9800c,0x8c480085,0xa2d5bca9,0xceb304de}}, // ्योग_, _başç, _भिन्, _שיר_,
+ {{0x442a0282,0x81cb00ab,0x66098365,0x201a008e}}, // _tob_, র্ক_, ddek, _knpi_,
+ {{0x76aa9697,0x644e0f38,0x60c9807a,0x9f5806c0}}, // отив_, _umbi, _dvem, terè_,
+ {{0xe80f0023,0xd3721b9a,0x64418782,0xdb1a89b2}}, // ायता_, _شهر_, dhli, tatí,
+ {{0x60c9011f,0x7c2abcaa,0x81d400ab,0x249806ae}}, // šemi, _sofr, স্ত_, _hirm_,
+ {{0x6e2b826b,0x998d826f,0xdb1abcab,0x69c080eb}}, // _dogb, _pleť_, ratí, _ieme,
+ {{0x69c0bcac,0x682f8257,0x29049e1d,0x61e41a54}}, // _heme, _døds, _isma_, _acil,
+ {{0x69c0bcad,0xda1a0540,0x29122168,0xdb1a8511}}, // _keme, धांत_, _arya_, patí,
+ {{0x69c0bcae,0x78a082a0,0x682f8257,0xc7b880d2}}, // _jeme, _kumv, _føds, _vođu_,
+ {{0x69c08393,0x3946bcaf,0xf8ae015b,0x656f81ec}}, // _meme, nzos_, یکی_, ücht,
+ {{0x644182af,0xdb0e0118,0x69c0a385,0x59db0072}}, // chli, _debú, _leme, _भाकर,
+ {{0xfe730c2a,0x76410110,0x7793003d,0x32670081}}, // ندر_, ūlym, ضیحا, _отив,
+ {{0x27e98025,0x69c0bcb0,0xdb15026f,0xe508801c}}, // đani_, _neme, _nezá, _kỷ_,
+ {{0xa2da8b6f,0x3d118076,0x61fd3cb1,0xddc40cd9}}, // [2990] पूर्, तिये_, mesl, bliž,
+ {{0x69c088f1,0x32093cb2,0x4ea73cb3,0x660982d0}}, // _aeme, _haay_, _орма, zdek,
+ {{0x60c9803a,0x69c0bcb4,0x2904b996,0x3ce93cb5}}, // _svem, _beme, _asma_, stav_,
+ {{0xfe350158,0x61fd3cb6,0xdb18801b,0xdb050b6a}}, // _נאָך_, nesl, _neví, nché,
+ {{0x69c08b0b,0xadd604de,0x29048122,0x32092676}}, // _deme, _אורח_, _csma_, _maay_,
+ {{0x75160158,0x9f0580f7,0xa3b086ae,0xfd66819d}}, // _אַלע_, فوتو, टुम_, sspọ,
+ {{0x69c0bcb7,0x7bc511df,0x66098fb0,0x20d5807b}}, // _feme, mahu, tdek, _búið_,
+ {{0x69c0915b,0x7bc53cb8,0xb87b3cb9,0x32090122}}, // _geme, lahu, luíd, _naay_,
+ {{0x6441bcba,0xdb18816b,0x2904bcbb,0x27e98300}}, // thli, _deví, _gsma_, ƙan_,
+ {{0x69c0bcbc,0x7bc51341,0xc9849510,0x61e401a8}}, // _zeme, nahu, _руси, _scil,
+ {{0x69c0bcbd,0x6e2bbcbe,0x35b59138,0xdb1c2190}}, // _yeme, _togb, _збер, _peré,
+ {{0xcb120158,0x61fd00c9,0x6a788207,0x6834809f}}, // אלט_, gesl, tífi, _ràdi,
+ {{0x7bc53cbf,0x2d9d882e,0x78a081c0,0x6f053cc0}}, // kahu, _igwe_, _xumv, _ashc,
+ {{0xf8b28039,0xe3d500ab,0x68300106,0x7e7bb65d}}, // רשם_, স্তব, _räds, llup,
+ {{0xdb1c03b0,0x3a3f9d19,0x61fd3cc1,0x7bc53cc2}}, // _teré, _olup_, besl, dahu,
+ {{0xa49b009f,0x61e4017f,0xdb0381ec,0x7e7b8ff9}}, // _pròp, _ucil, _benö, nlup,
+ {{0xc6928f60,0x69c08fc6,0x7afb20a7,0x6bd60061}}, // [29a0] ראן_, _reme, _ćuta, فتار,
+ {{0x69c62a84,0x7bc53cc3,0xdb1a8019,0x7e7b8234}}, // lake, gahu, latá, hlup,
+ {{0x69c095bd,0xe1fa8f27,0x645e15b3,0x6735009a}}, // _peme, зге_, rnpi, cyzj,
+ {{0xe64611d2,0x69c63cc4,0x51868e02,0x4ad1016f}}, // _чемп, nake, нула, _हिरव,
+ {{0x76560be8,0xa3cf8778,0x7bc53cc5,0xb87b0ba3}}, // miyy, षणा_, bahu, buíd,
+ {{0x69c63cc6,0x76560086,0x26dcbcc7,0x3946bcc8}}, // hake, liyy, tuvo_, rzos_,
+ {{0x44441e59,0x81cb00c8,0x69c603f8,0x8c1b0158}}, // mh_, র্ট_, kake, _צולי,
+ {{0x76560be8,0x3a3f8668,0x7e7bbcc9,0x69c63cca}}, // niyy, _glup_, glup, jake,
+ {{0xd257810f,0x69c63ccb,0x40a88065,0x7c2e3ccc}}, // _נשמה_, dake, _آخری_, _kobr,
+ {{0x44440142,0xb05b00f2,0x9f47826f,0x224006df}}, // nh_, rsäk, lený_, _alik_,
+ {{0x3fc98077,0x1de10e5b,0x22403ccd,0xb21b00ec}}, // _آگهی_, _फालत, _blik_, nlæg,
+ {{0x61e2b2e3,0xb5fb0065,0x64a6aeab,0xa2d30eed}}, // ngol, _aján, _зада, _डिस्,
+ {{0x76563cce,0x76443ccf,0x44443cd0,0x61fd08f8}}, // diyy, dhiy, kh_, resl,
+ {{0x444428ee,0x2c16809a,0x7c2e3cd1,0x7984009a}}, // jh_, ताओं_, _nobr, _dziw,
+ {{0x4444279d,0xb5fd80d2,0xb87b0e14,0x78ad3cd2}}, // dh_, pošt, ktív, _živč,
+ {{0x44443cd3,0x442e84eb,0xa2d5800d,0xdb1abcd4}}, // eh_, _hof_, _भित्, catá,
+ {{0x7bc53cd5,0x5c753160,0x25a93cd6,0x9f478a21}}, // [29b0] tahu, влат, rcal_, dený_,
+ {{0xddeb0077,0x44443cd7,0x25a93cd8,0x61e2a460}}, // _کرده_, gh_, scal_, egol,
+ {{0xa3e20006,0x7bc53cd9,0xb3e215fb,0xb87b26f0}}, // _धार_, rahu, _पारख, ruíd,
+ {{0x7bc50bfb,0x44443cda,0x61e2aa96,0x442ebcdb}}, // sahu, ah_, ggol, _lof_,
+ {{0x44441523,0x661d3cdc,0x26cc8267,0x7bc53cdd}}, // bh_, _insk, _avdo_, pahu,
+ {{0x44443cde,0x81cb00c8,0xb87b3cdf,0x69c63ce0}}, // ch_, র্চ_, quíd, zake,
+ {{0xe8221a3b,0xd9d900c8,0x1c0a005e,0x9f47826f}}, // माता_, ধ্যম, _होटल_, bený_,
+ {{0x660d3ce1,0x7c2e0ed7,0x645704c3,0x60cd3bce}}, // ndak, _zobr, lixi, _ovam,
+ {{0x7e7b810b,0xe1ff0019,0xe81e8105,0x26cc82f7}}, // rlup, deó_, पाशा_, _evdo_,
+ {{0x2240021e,0x76560201,0x442e8428,0x69c63ce2}}, // _slik_, ziyy, _cof_, wake,
+ {{0x2fc7bce3,0xdb1a8065,0x69c63ce4,0xdb0a82af}}, // mang_, tatá, take, _gefü,
+ {{0x2fc7ac11,0x444400f1,0x3eaa803e,0x38a60187}}, // lang_, zh_, žité_, _pôr_,
+ {{0x69c63ce5,0x1d0987eb,0xf1bf0019,0x76560085}}, // rake, _цели_, pján_, viyy,
+ {{0x2fc7ad67,0x76563a20,0x69c63ce6,0x6d458085}}, // nang_, wiyy, sake, _əhal,
+ {{0x64573631,0xe45680be,0x76443ce7,0x660b8122}}, // dixi, _ביסט_, thiy, _kagk,
+ {{0xadf99391,0x2fc7afd2,0xb17b016d,0x49730a2c}}, // ्यटन_, hang_, mgån, ụtụ_,
+ {{0x44443ce8,0xed5720bf,0x7c2e3ce9,0x660b84d2}}, // [29c0] th_, вот_, _pobr, _magk,
+ {{0x661d3cea,0x2fc78455,0x7bc3bceb,0xb5fb007b}}, // _ensk, jang_, _jenu, _hjál,
+ {{0x7bc3845c,0x2fc78458,0x3ced8917,0x9f480009}}, // _menu, dang_, htev_, ähän_,
+ {{0x44443cec,0x660b849f,0xb21b0215,0x6289bced}}, // sh_, _nagk, rlæg, _cheo,
+ {{0x61f600f1,0x2fc78456,0x9f4780e1,0x69c43cee}}, // _mbyl, fang_, rený_, _meie,
+ {{0x2fc7bcef,0x69c42aef,0x7bc3a5c6,0xb87b03a7}}, // gang_, _leie, _nenu, stív,
+ {{0x44208104,0x2d808612,0x660bbcf0,0xd8388390}}, // _đi_, _šie_, _bagk, _niče_,
+ {{0x27ed0025,0x6289bcf1,0x69c400eb,0x442eb6b0}}, // đeni_, _gheo, _neie, _sof_,
+ {{0x2fc7977c,0x18a696be,0x7bc3ae68,0xa0a6885f}}, // bang_, _разм, _benu, _разд,
+ {{0x442e82a3,0x2fc7bcf2,0x7bc3bcf3,0xdb1a82d0}}, // _qof_, cang_, _cenu, natç,
+ {{0x201e81cd,0x60cd1a16,0x6aa481a1,0xdb188061}}, // _inti_, _svam, _čifs, _bevá,
+ {{0x200cbcf4,0x7d043cf5,0x05e103eb,0xddcd0035}}, // _hadi_, kwis, _फाइब, ział,
+ {{0xe80f035a,0x6a7d02be,0x7bc38915,0x200cbcf6}}, // ायला_, néfi, _fenu, _kadi_,
+ {{0x200c848f,0x6aa384be,0x645711ee,0x78a401ed}}, // _jadi_, _funf, xixi, _duiv,
+ {{0x45d50391,0x7bc89d9f,0x200c83ec,0x69c43cf7}}, // ловс, madu, _madi_, _feie,
+ {{0x6d0e01fe,0x200cbcf8,0x7bc89f61,0x7bc3bcf9}}, // सिंग_, _ladi_, ladu, _zenu,
+ {{0xd9049fbe,0x2fc7ac11,0x2d580364,0x62898355}}, // [29d0] _بی_, yang_, вить_, _rheo,
+ {{0xda2105b3,0x200c990f,0x7642ab02,0x6289bcfa}}, // यावत_, _nadi_, _lloy, _sheo,
+ {{0x64573cfb,0xddcd009a,0x2fc7800b,0x6289851e}}, // rixi, riał, vang_, _pheo,
+ {{0x2fc7ac11,0x22590370,0x661d0858,0x629e80e7}}, // wang_, nisk_, _unsk, _épou,
+ {{0x2fc79ac4,0x4dda0051,0x200c96ea,0x7bc8bcfc}}, // tang_, _אחרו, _badi_, kadu,
+ {{0xd366af7b,0xa2cc3cfd,0x7bc88074,0x764288b1}}, // _هه_, _हिच्, jadu, _aloy,
+ {{0x2fc78455,0x660b9856,0x7bc3bcfe,0x200c83f8}}, // rang_, _pagk, _renu, _dadi_,
+ {{0xc3321a63,0x2fc7bbc2,0x6d49811e,0x201e8081}}, // _פון_, sang_, tzea, _enti_,
+ {{0x7bc394ec,0x2fc79291,0x22592280,0x6e2989ca}}, // _penu, pang_, disk_, rjeb,
+ {{0xb5fb0125,0x7bc8bcff,0x69c41789,0x200cbd00}}, // _sjál, gadu, _seie, _gadi_,
+ {{0x78a403d3,0x7bc3bd01,0x22590370,0xc73580d5}}, // _suiv, _venu, fisk_, _حفاظ,
+ {{0x22590370,0x76429bad,0xb17b0106,0x68fb819d}}, // gisk_, _gloy, rgån, _kpud,
+ {{0x7bc3bd02,0xfe70845b,0x7bc8bd03,0xc9550048}}, // _tenu, _بدل_, badu, атры,
+ {{0xdee61628,0xdb1c040e,0x6cd28c2a,0x66e60d91}}, // логи, _serí, _اقوا, лога,
+ {{0xdb1c26f0,0xe81901ce,0x61e9bd04,0x22592280}}, // _perí, दाजा_, _ocel, bisk_,
+ {{0x28dc1d01,0x3ea58c6e,0x25adbd05,0x6d410201}}, // _बिपि, _mult_, ncel_, _əlav,
+ {{0xdb0a8125,0x3a20336a,0xdb1c3d06,0x4431173c}}, // [29e0] _hefð, _inip_, _verí, _boz_,
+ {{0x7d043d07,0x61e9b296,0x6600803e,0x657d00f1}}, // rwis, _acel, zemk, _dysh,
+ {{0x200c8025,0x59b80076,0x7ae1bd08,0xddc98025}}, // _radi_, _आभार, nult, dlež,
+ {{0x66ea8201,0x61e60b20,0x2fc583a8,0x64552733}}, // _məkt, ngkl, _aelg_, _emzi,
+ {{0x7ae1bd09,0x44310ba3,0x9f5801e8,0xc7b881f4}}, // hult, _foz_, lerà_, _anđa_,
+ {{0x648b1d3a,0xdcfd00eb,0x53a3102a,0x3ea5aa65}}, // güid, _uzsā, _гарб, _bult_,
+ {{0xf1ba0028,0xa3e9016f,0xe8d901bc,0x9f5801e8}}, // _đơn_, _यात_, _izụ_, nerà_,
+ {{0xe297bd0a,0x7e640074,0x96349ad2,0xb05b0106}}, // _бас_, nnip, рниц, lsät,
+ {{0x200c8867,0x7bc8bd0b,0x80a018b8,0x3d170d86}}, // _tadi_, tadu, _गंगे, निये_,
+ {{0xcb1f80d4,0xf1b9826c,0x201e809c,0x61e9801b}}, // यमंड_, _inša_, _unti_, _zcel,
+ {{0x22590370,0xa3e5835a,0xb89380f7,0x7e6414cf}}, // tisk_, _फार_, _اللع, knip,
+ {{0x9f58047f,0xd9150048,0xdb050187,0x80c1864a}}, // derà_, адмы, lchã, रीवे,
+ {{0x22590370,0x7bc8ac73,0xf1bf0019,0x3ea580f3}}, // risk_, padu, lják_, _zult_,
+ {{0x22590370,0xb5fb03fb,0xe7e300ab,0xbddb09c4}}, // sisk_, _ukáz, য়্য, _clèn,
+ {{0x645abd0c,0x81dc80c8,0x22590370,0x7ae1879a}}, // miti, ত্য_, pisk_, cult,
+ {{0xe8d90870,0x443ebd0d,0x442c8106,0x29090ec9}}, // _azụ_, lkt_, ljd_, _tsaa_,
+ {{0x54551878,0xe8220076,0xa3e90740,0xdc5504bd}}, // [29f0] ават, माशा_, _याद_, авањ,
+ {{0xe7e081fe,0x61e98098,0xb87b05a4,0x645ab282}}, // _नागप, _scel, dríg, niti,
+ {{0x443126d5,0x443ebd0e,0x20033d0f,0xed59890c}}, // _voz_, ikt_, meji_, raži_,
+ {{0x20033d10,0xdb1c0073,0x27ed005c,0x645abd11}}, // leji_, _serã, đenu_, hiti,
+ {{0x25ea146d,0x628d125b,0x91bf819d,0x3ea5bd12}}, // _छाती_, _mhao, _ajụ_, _sult_,
+ {{0x443e8613,0x645abd13,0x20110bb7,0x7c3e048d}}, // jkt_, jiti, ndzi_, ckpr,
+ {{0xfc3f02ba,0x645abd14,0xdb1c03a7,0x2fc5bd15}}, // _guía_, diti, _verã, _velg_,
+ {{0xceb40451,0x6e2d3d16,0x443e97dd,0x81dc80ab}}, // זיק_, njab, ekt_, ত্ব_,
+ {{0x645abd17,0x20033d18,0x7bc7059c,0x1c45960f}}, // fiti, keji_, _keju, иним,
+ {{0x1c1f8cce,0x69cbbd19,0x7bc73d1a,0xdb1c002a}}, // बाइल_, lage, _jeju, _herá,
+ {{0x3f678a95,0xcb69813a,0x38ab83ba,0x7bc70133}}, // _стаб, тане_, _hør_, _meju,
+ {{0x628d1a29,0x69cbbd1b,0x660f0004,0x7bc735cf}}, // _chao, nage, _nack, _leju,
+ {{0x645abd1c,0x628d0ad0,0x9f580e67,0xfe708872}}, // biti, _dhao, derá_, _عدم_,
+ {{0x443ebd1d,0x69cbbd1e,0x7ae1bd1f,0x9f583340}}, // ckt_, hage, pult, terà_,
+ {{0x660f3d20,0x69cbbd21,0x38ab8aa2,0x628d0014}}, // _back, kage, _lør_, _fhao,
+ {{0x628d0083,0x660f0b80,0x7aee826f,0x9f583d22}}, // _ghao, _cack, žití, gerá_,
+ {{0xb05b1a50,0x7afc14e4,0x6486808b,0x69d60420}}, // [2a00] tsät, _sprt, iðin, _idye,
+ {{0x7d0b8077,0x628d0326,0x9f583340,0x7bc70144}}, // _msgs, _zhao, perà_, _ceju,
+ {{0xb05b0884,0x9f583d23,0x660f3d24,0xf1da2207}}, // rsät, berá_, _fack, _भयान,
+ {{0x9f5810dd,0x660f02a5,0x38ab8a38,0xed598024}}, // cerá_, _gack, _bør_, lažu_,
+ {{0x03a6143b,0x645a82ec,0x3940bd25,0x443ebd26}}, // _виго, yiti, áis_, ykt_,
+ {{0x660421c0,0x645abd27,0x26dc9c67,0xf1bf0019}}, // leik, xiti, trvo_, tják_,
+ {{0xe7ee023c,0x1c1fa3bd,0x69cbb104,0x12e080ab}}, // _जाना_, बाईल_, bage, _বন্দ,
+ {{0x38ab8a38,0x261983e8,0x69cbbd28,0x7d0b8176}}, // _før_, _मोदी_, cage, _bsgs,
+ {{0x38ab8022,0x61439263,0xed598289,0x645abd29}}, // _gør_, _дета, kažu_, titi,
+ {{0x62829b98,0x628d008c,0x69d62dcb,0x9f5800e1}}, // lloo, _shao, _adye, zerá_,
+ {{0x443ebd2a,0x63a380ee,0x3dc680ee,0x6d4d0365}}, // rkt_, _dgnn, _teow_, zzaa,
+ {{0x443e819b,0x645abd2b,0x6282a695,0x2efe826c}}, // skt_, siti, nloo, _iptf_,
+ {{0x645a8c53,0x9f5803a7,0xc05b00e8,0x68e48046}}, // piti, verá_, вів_, àidh,
+ {{0x02a6aed9,0x69cbbd2c,0xc05b00e8,0x69d600e4}}, // _крим, zage, тім_, _edye,
+ {{0x628d0ad0,0x9f580207,0x69cbbd2d,0x44f0bd2e}}, // _thao, terá_, yage, là_,
+ {{0x7bc70867,0xf8a6897d,0xb05b0198,0xe61f00ff}}, // _seju, खदाय, tsäs, _khôi_,
+ {{0x660f00f2,0xdb050a21,0x7bc70458,0x44f0a2c6}}, // [2a10] _vack, dchá, _peju, nà_,
+ {{0xdb1c040e,0x6d4d0a0f,0x69cbbd2f,0x38ab83ba}}, // _será, rzaa, wage, _rør_,
+ {{0x69cbbd30,0xa2f53d31,0x6282a0d1,0x38ab821e}}, // tage, спеч, floo, _sør_,
+ {{0xceb20159,0x80db800f,0xb9100135,0x628289ff}}, // _מיט_, _निदे, _nanị_, gloo,
+ {{0xdb1c0da1,0xe9d88110,0xe7ee0424,0x81d400ab}}, // _verá, ркі_, _जामा_, স্ক_,
+ {{0x6e22bd32,0x44f0bd33,0x7bd53d34,0xdb0501a8}}, // _anob, dà_, _udzu, achá,
+ {{0xdb1c03a2,0x6282930d,0x64470197,0x41e785a8}}, // _terá, bloo, _ilji, _віза,
+ {{0x38ab8aa2,0x683d8722,0x6aa09bec,0x7d0b8122}}, // _tør_, _mèdi, _gimf, _psgs,
+ {{0x64868125,0xf80780e8,0x79808114,0x7649875e}}, // rðin, _вчен, _gymw, rhey,
+ {{0x308580f7,0x7649bd35,0xe0df3d36,0x61ed0c1d}}, // _الطف, shey, drò_, _acal,
+ {{0x78a98264,0xb87b01df,0x9f58009f,0x442300fc}}, // _huev, tuín, merç_, _mnj_,
+ {{0x78a1bd37,0x66ea811c,0x61fb810c,0x6458913b}}, // _hilv, _təkr, _jbul, _emvi,
+ {{0x78a986a5,0x25bf91d6,0x66043d38,0x61fbbd39}}, // _juev, mbul_, veik, _mbul,
+ {{0x78a986a5,0x25bf8122,0x7e7d3d3a,0xf1bf041c}}, // _muev, lbul_, _aksp, abá_,
+ {{0x66043d3b,0xa2d31a3b,0x7d098d33,0xddcb2828}}, // teik, _डिग्, lwes, _šiša,
+ {{0x25bf87d9,0x290d8e1b,0xed598301,0x64470300}}, // nbul_, _osea_, sažu_, _alji,
+ {{0x78a9a70d,0x66043d3c,0xddc98035,0x7c3506c4}}, // [2a20] _nuev, reik, wieś, _bozr,
+ {{0x61fbbd3d,0xdd8f0591,0xaca38133,0x628281e0}}, // _abul, رول_, _adịk, wloo,
+ {{0xdb1c3d3e,0x25bf883a,0xb09b0039,0x7d0980ed}}, // _merç, kbul_, _תייר, hwes,
+ {{0xd910819f,0x7d09805d,0xb87b3d3f,0x6b7a80be}}, // ویز_, kwes, críb, ארענ,
+ {{0xc05a8d8e,0x6441bd40,0x78a98333,0x78a192f1}}, // лік_, kkli, _cuev, _bilv,
+ {{0x78a18341,0x6447005c,0x61fbbd41,0xdb1abd42}}, // _cilv, _glji, _ebul, ratú,
+ {{0x6282bd43,0x645e3d44,0x6441816d,0x7ff380f7}}, // ploo, mipi, dkli, سسوا,
+ {{0x44f081dc,0xc8e0800f,0x6f1a807a,0x44b480e8}}, // tà_, _निपट, _vrtc, обис,
+ {{0xa49b0722,0xdb1c0187,0x7afb0da8,0x68e60711}}, // _pròx, _berç, _ćuti, jukd,
+ {{0x44f0bd45,0x61fb80f1,0x645e3d46,0x15ea83eb}}, // rà_, _zbul, nipi, _टावर_,
+ {{0x7f868307,0x6d410201,0x44f0bd33,0x547b0039}}, // _الان, _əlaq, sà_, רטיו,
+ {{0xc3331a0f,0x7a3f020f,0xe61f0028,0xa5bb37b5}}, // לות_, _këti, _thôi_, _anón,
+ {{0x6da38c48,0x645e3d47,0xdb1c0009,0xb87b0118}}, // _عموم, kipi, _herä, buíl,
+ {{0x6441bd48,0xdb1c3d49,0x69c01434,0xe0df01e8}}, // ckli, _gerç, ðmer, rrò_,
+ {{0x623507b6,0x7c351d21,0x466b072a,0xc0580d8e}}, // _меку, _rozr, грам_, сія_,
+ {{0x7bca87ca,0x4caa00ab,0x6447025b,0x68e602c4}}, // _hefu, _চৌধু, _slji, bukd,
+ {{0x69cf0333,0x7c350038,0x7cd9003d,0x7bca80b9}}, // [2a30] mace, _pozr, شواز_, _kefu,
+ {{0x69cf0018,0x4d9807ac,0x6aaabd4a,0x645e3d4b}}, // lace, скую_, _kuff, gipi,
+ {{0xe7ee00cf,0xe9f90028,0x78a983a8,0x25a6bd4c}}, // _जाता_, _giả_, _suev, _ogol_,
+ {{0xa49b0176,0xb87b03cd,0x38af0850,0x04060264}}, // _asòt, bríc, _kür_, রাগী_,
+ {{0x61fbbd4d,0x7bd880d2,0x78a18b0c,0x78a9a4a3}}, // _vbul, _odvu, _pilv, _quev,
+ {{0x8aa70785,0x02a70a8e,0xdb1c00f2,0x25b3914f}}, // _град, _грам, _berä, ीर्ण,
+ {{0x81d40a49,0xa6b480ab,0x80dbb13a,0x69cf3d4e}}, // স্ট_, _টিউট, _निवे, kace,
+ {{0xee398abe,0x764d3d4f,0x7d09bd50,0x68e623c1}}, // рни_, nhay, twes, yukd,
+ {{0x82348b76,0x78a18a35,0x7bcabd51,0x893784e3}}, // _عرفا, _tilv, _befu, _اعزا,
+ {{0xf9909381,0x22493d52,0x7d09bd53,0x26199344}}, // _سبق_, _alak_, rwes, _मोरी_,
+ {{0x2007bd54,0x6441bd55,0xdb1c3d56,0x69cf3d57}}, // meni_, rkli, _gerä, face,
+ {{0x2007829b,0x81e200c8,0x64419aed,0x7d09bd58}}, // leni_, ন্ন_, skli, pwes,
+ {{0x7e7b8052,0xdb1c0073,0xe7ee0074,0xa2d58f0f}}, // moup, _terç, _जादा_, _भिक्,
+ {{0x7bca9546,0x2007805d,0x22493d59,0x7e698216}}, // _gefu, neni_, _elak_, lnep,
+ {{0x07a33d5a,0x2bbfb011,0x032580e8,0x8fa3151a}}, // дарн, _श्या, _єдин, даре,
+ {{0x38af0943,0xb87b3d5b,0x2007bd5c,0x7e69bd5d}}, // _für_, tríc, heni_, nnep,
+ {{0x645e3d5e,0x2007bd5f,0x3ea2016d,0xe45a10ee}}, // [2a40] tipi, keni_, _vikt_, ажа_,
+ {{0x62863d60,0x2a7f8326,0x81b00326,0xc8e080c2}}, // llko, _akub_, _daɓe, _निबट,
+ {{0xe5a60676,0x6da61354,0x41aa81f3,0x7e7b801b}}, // _мини, _мина, авен_, koup,
+ {{0x764d3d61,0x645e0b51,0x387e82f7,0xb5fb008b}}, // chay, sipi, _sktr_, _sjáv,
+ {{0xa3e9853e,0xe80f016f,0xaca381bc,0x645e3987}}, // यला_, ायचा_, _adọk, pipi,
+ {{0xd6daa885,0x2007bd62,0xdb1c0009,0xdfd080f7}}, // ати_, geni_, _perä, ريب_,
+ {{0x44f40364,0x27e087d9,0x44443d63,0x81e200ab}}, // lä_, ğin_, mk_, ন্য_,
+ {{0x44383d64,0xa534bd65,0x91e60592,0xdb1c02af}}, // _hor_, знич, _कागज_, _verä,
+ {{0x44f40364,0xc6930bea,0x2007bd66,0xd40684fa}}, // nä_, ואה_, beni_, ояни,
+ {{0x44443d67,0x81e200ab,0xb35480e8,0x69cf0f89}}, // nk_, ন্ম_, зкош, wace,
+ {{0x444406a8,0x443801ba,0x6b838355,0x28c68eed}}, // ik_, _mor_, _cyng, रीरि,
+ {{0x81dc8a49,0xb5fb00ab,0x44381bdd,0xb87b05a4}}, // ত্র_, _imág, _lor_, dría,
+ {{0x443804df,0x7e608812,0x44443d68,0x44f40364}}, // _oor_, mimp, kk_, jä_,
+ {{0x44440586,0x6aaabd69,0x44f40009,0x7d02bd6a}}, // jk_, _tuff, dä_, _ipos,
+ {{0x69cf087a,0x764d3d6b,0x6b838114,0xd838bd6c}}, // pace, thay, _gyng, _moč_,
+ {{0x2cb90358,0x7e60b996,0x44443d6d,0x38af1266}}, // _ptsd_, nimp, ek_, _tür_,
+ {{0x44380bfa,0xa3ae86a7,0xd7fb259a,0x5d5501f3}}, // [2a50] _bor_, _कला_, _кун_, чкат,
+ {{0x44383d6e,0x64dd835a,0x764d2751,0x68e29ad5}}, // _cor_, _मिसळ, shay, drod,
+ {{0x443822a0,0xf8bf24df,0x6d40bd6f,0x7ae88122}}, // _dor_, lpé_, hyma, nudt,
+ {{0x2007ae95,0x444404e0,0xc3338158,0x7c38bd70}}, // weni_, ak_, ווע_, _lovr,
+ {{0x4438073a,0x2ca30613,0x2007bd71,0x68e2809a}}, // _for_, _tijd_, teni_, grod,
+ {{0x44383d72,0x6d408110,0xd7fb8049,0xf8bf0036}}, // _gor_, dyma, руд_, ipé_,
+ {{0xe739bd73,0x2007812b,0x7d0d3d74,0x44298028}}, // сей_, reni_, lwas, _đa_,
+ {{0xb0dc05b3,0x2007b082,0x28dc0054,0x44383d75}}, // _बिलग, seni_, _बिलि, _zor_,
+ {{0xdb050352,0x81e200c8,0x7d0d3d76,0x83fc876c}}, // schä, ন্ড_, nwas, ziđe,
+ {{0x20079cb5,0xbebc80eb,0x76443d77,0x28e0a207}}, // qeni_, _brīd, zkiy, _निति,
+ {{0x7c388098,0x59cf8aed,0x7d0d3d78,0x7d02bd79}}, // _dovr, _हजार, hwas, _epos,
+ {{0x7d0d3d7a,0x83fc8024,0x60c9a6bd,0x7e608359}}, // kwas, viđe, _kwem, cimp,
+ {{0x6f1e0b80,0x69cd00c3,0xa56780d7,0x271c8129}}, // _srpc, _deae, _بدان, _ảnh_,
+ {{0x6b8388f3,0x44f40009,0x60c9a994,0x6aa411fe}}, // _tyng, vä_, _mwem, _wiif,
+ {{0xcb6694d6,0x4427bd7b,0xaa461c57,0xbebc81a9}}, // _наше_, _inn_, _некл, _grīd,
+ {{0x44f4025d,0x7c2706cb,0x6f1e00ce,0xb87b0333}}, // tä_, _anjr, _vrpc, rría,
+ {{0x76443d7c,0x7d0d1400,0x28e081a2,0x6e39822b}}, // [2a60] rkiy, gwas, _निधि, _mowb,
+ {{0x44443d7d,0x764400a4,0x29120590,0x69db8216}}, // uk_, skiy, _isya_, _adue,
+ {{0x44383d7e,0xf62601f3,0x6d40809a,0x7d1b8074}}, // _vor_, _едно, zyma, hvus,
+ {{0x4444006f,0x64a61506,0xdca600ae,0x645c01ed}}, // sk_, _нама, _нами, _wmri,
+ {{0x44383d7f,0x4427bd80,0xf1a9803d,0x291f80f7}}, // _tor_, _onn_, زانه_, _crua_,
+ {{0x645c2c5d,0x68e2a00a,0x69db8118,0x69c286a8}}, // _umri, rrod, _edue, sboe,
+ {{0x7d02bd81,0xa91d826f,0x1c020540,0x7bdc00ee}}, // _spos, _vyža, _रॉयल_, _idru,
+ {{0x4427bd82,0x81e20a49,0x7c38bd83,0x6d408d42}}, // _ann_, ন্ত_, _sovr, tyma,
+ {{0x7c388b48,0x7bce0763,0xe297b73a,0x48e390ca}}, // _povr, _kebu, _хат_, _потв,
+ {{0x7e60bd84,0xb5fd85f5,0x6d409ad5,0x5fd0801b}}, // simp, više, ryma, _सजिल,
+ {{0x29123d85,0x7e6088de,0x60c98a0f,0x6d409ad5}}, // _asya_, pimp, _zwem, syma,
+ {{0x44278357,0x7bce3d86,0x78ad01c0,0x3ce901c0}}, // _enn_, _lebu, _puav, yuav_,
+ {{0xdcf783f8,0x7d028754,0x7c38807a,0x2e3781c6}}, // _فروش_, _upos, _tovr, _מראש_,
+ {{0x7bce3d87,0xb905058c,0xb5fd8916,0xa8a416ba}}, // _nebu, _पट_, riše, ерск,
+ {{0x5433826a,0xabfb81c6,0x81e200ab,0xff041cf8}}, // _سرور, _ההור, ন্ধ_, нятн,
+ {{0x7d0d3d88,0xb5fd811a,0xf8bf00e7,0xa49b026b}}, // twas, piše, ppé_, _asòp,
+ {{0x59c30327,0x7bce3d89,0x3a290359,0xdcef01a9}}, // [2a70] _व्यर, _bebu, _inap_, _izcī,
+ {{0xfbab0196,0x81e200c8,0x200a3d8a,0x6609bd8b}}, // стай_, ন্দ_, cebi_, zeek,
+ {{0x7529012b,0x64451cbc,0x3d0e9344,0x3a292862}}, // _šezd, rkhi, _तैसे_, _knap_,
+ {{0xdb15026f,0xfd1f001c,0xdb1ab4be,0xa3dc8ebf}}, // _sezó, _trì_, lató, तृत_,
+ {{0xe1f09e13,0xb5fb0125,0x9f5106a5,0x7e6d3d8c}}, // اسم_, _fjár, pezó_, mnap,
+ {{0x7bce2e68,0xdb1abd8d,0x4174803d,0x6609bd8e}}, // _gebu, nató, رانس, week,
+ {{0x66098c56,0xae1e8128,0x7bc53d8f,0x3a290db1}}, // teek, _मोहन_, mbhu, _onap_,
+ {{0xda1d1d01,0x7bdc1fe7,0x7e6d25d3,0x7bce0caa}}, // _बोलत_, _zdru, nnap, _zebu,
+ {{0x5fbf000d,0xe7ee0665,0x66099306,0xd7bf0d14}}, // ्राल, _जाला_, reek, ्राच,
+ {{0x68e48307,0x6609bd90,0x5ee58035,0xbb4595b5}}, // áide, seek, _टिप्_, деок,
+ {{0x7e6d09ff,0xe29a045e,0x3ea68118,0x212000b9}}, // knap, жан_, _fiot_, _srih_,
+ {{0xd9ff8128,0xb87b01a8,0x3a290229,0x9f580144}}, // ोजित_, frío, _cnap_, perú_,
+ {{0x7e6d3d91,0xdb0a80f2,0xdb038144,0x68eb833e}}, // dnap, _affä, _igní, nugd,
+ {{0xdb1abd92,0xe8008f1b,0xa91d816b,0x67218bcf}}, // gató, ल्पा_, _lyžo, _krlj,
+ {{0x81e58a49,0xa0a33d93,0x7bce3d94,0x224d8327}}, // ব্য_, тард, _rebu, _olek_,
+ {{0x7bce1a67,0x2ca78006,0x7ae500d2,0x6721812b}}, // _sebu, _hind_, drht, _mrlj,
+ {{0x1be18074,0x7bce02f7,0x62843d95,0x2bb203db}}, // [2a80] _खयाल_, _pebu, _ikio, _जलदा,
+ {{0x1daa0054,0x225f8642,0x67218140,0xdb1abd96}}, // _कलकत, _amuk_, _orlj, cató,
+ {{0x38b41918,0x28e083eb,0x65c62098,0x2ca7bd97}}, // _här_, _निहि, _обна, _mind_,
+ {{0x7bce3d98,0x1bf00105,0x2ca7bd99,0xdb18816b}}, // _webu, _चावल_, _lind_, _nevý,
+ {{0x7bce3d9a,0x83fc826c,0xb17b03a6,0x644f81a9}}, // _tebu, hiđa, lgår, īcij,
+ {{0x7bdc003a,0x224dbd9b,0x59cc800d,0x46d20105}}, // _udru, _elek_, ारहर, _तबाह,
+ {{0x38b400f2,0x224dbd9c,0xb17b04e1,0x644e3d9d}}, // _lär_, _flek_, ngår, _ilbi,
+ {{0x44208193,0x1514a49a,0x7e64046d,0x442a35ca}}, // _şi_, едия, niip, _hnb_,
+ {{0x38b41918,0x7c3c17ab,0x6d440812,0xd8388289}}, // _när_, _korr, nyia, _liči_,
+ {{0xdb1c0aa2,0x6284004f,0x2ca78087,0x67218503}}, // _berø, _akio, _cind_, _frlj,
+ {{0x9e3c826f,0x672180fe,0x60c28d55,0x9f5e823e}}, // _buďt, _grlj, lpom, _obté_,
+ {{0x38b40884,0x81e200ab,0x2ca780f3,0x60c28035}}, // _bär_, ন্স_, _eind_, opom,
+ {{0x2ca7bd9e,0xa3c0064a,0xdb1abd9f,0x644e047f}}, // _find_, ीरा_, tató, _olbi,
+ {{0x38b41918,0x7c3c3da0,0x62843da1,0x75228db1}}, // _där_, _norr, _ekio, _iroz,
+ {{0xe8009880,0x9f5f07d9,0xb5fd8024,0x7522826f}}, // ल्या_, _ürün_, liša, _hroz,
+ {{0x644e011e,0x443cbda2,0x7e6402c4,0x81b00300}}, // _albi, _hov_, giip, _taɓa,
+ {{0x443cbda3,0xdb1abda4,0x66028077,0x60c2875f}}, // [2a90] _kov_, catò, _mbok, jpom,
+ {{0x7c3c3da5,0xe3b88457,0x6f05001c,0x225f806a}}, // _corr, ncı_, _tphc, _smuk_,
+ {{0x443c88df,0x224d90f4,0x7c3c3da6,0x60cd008e}}, // _mov_, _plek_, _dorr, _hwam,
+ {{0x60cd3da7,0x443cbda8,0x644e0808,0x442a3da9}}, // _kwam, _lov_, _elbi, _enb_,
+ {{0x7c3c3b39,0xa8038214,0x6e3d0122,0x67218503}}, // _forr, _çıkt, _iosb, _srlj,
+ {{0x67218052,0x6602afd5,0x7c3c3daa,0x60cd2169}}, // _prlj, _abok, _gorr, _mwam,
+ {{0x3949a338,0xddc40353,0x98bf807e,0x60cd0c6a}}, // šas_, lniš, ırım_, _lwam,
+ {{0x2ca7834a,0x439413cd,0x83fc82a5,0x75228bcf}}, // _sind_, _расс, viđa, _broz,
+ {{0x7522a7f5,0x60cd0133,0x2ca7bdab,0xd7fbbdac}}, // _croz, _nwam, _pind_, _муж_,
+ {{0x660d3dad,0xfc318307,0x443c946a,0x20cd3dae}}, // heak, احة_, _cov_, dži_,
+ {{0x2ca79699,0x443cbc22,0x60cd2142,0xda661612}}, // _vind_, _dov_, _awam, евни,
+ {{0x23bf873c,0x2bbf8744,0xfbbf853f,0xee2e8a8e}}, // _श्रद, _श्रा, _श्रम, _ён_,
+ {{0xed4ebdaf,0x7522bdb0,0x1bf00076,0x6e2b8915}}, // _со_, _groz, _चालल_, _ingb,
+ {{0x6aa996a1,0xddc4135a,0x26de0024,0x653a80be}}, // _lief, dniš, štom_, _מענד,
+ {{0x44f9bdb1,0xdb18802a,0x6619a358,0xe73a1289}}, // lè_, _devó, _kawk, чев_,
+ {{0x661bbdb2,0xb17b0e23,0x62840c53,0x443c8069}}, // nduk, rgår, _ukio, _zov_,
+ {{0xfce32856,0x14262f84,0x60cd03c3,0x660301e5}}, // [2aa0] лото, ндам, _gwam, ыпта,
+ {{0x78fb0051,0x443c81e9,0x395704de,0x7e7b0289}}, // _צפיו, _xov_, _נשים_, čupa,
+ {{0x7c3c3db3,0x6d441283,0xb5fd8bcf,0x6aa9bdb4}}, // _vorr, syia, ziša, _bief,
+ {{0xddc40db7,0x7ae30742,0x661babea,0x2bb206b7}}, // bniš, ánta, jduk, _जलवा,
+ {{0xa3e9016f,0x442a3db5,0x44f986c0,0x6aa986a8}}, // _याच_, _tnb_, jè_, _dief,
+ {{0x75249dd7,0x98a38991,0x6e2b826b,0x10a38c9b}}, // _šizo, гиче, _angb, гичн,
+ {{0x91e6964f,0x443c81e9,0x09e6835f,0x7989bdb6}}, // _пове, _rov_, _повн, _eyew,
+ {{0x7522bdb7,0x443cbdb8,0xb87b3db9,0x661b8bb1}}, // _proz, _sov_, quív, gduk,
+ {{0x443c81c5,0x6ecc8105,0xc8ca9ef7,0x09cc8054}}, // _pov_, _सबकु, _روان_, ारीय,
+ {{0xa2b48076,0x60cd2fc0,0x61f61c33,0x201a0019}}, // _ईंद्, _rwam, _acyl, _napi_,
+ {{0x443cbdba,0xddc4007a,0xdee381a1,0xcf1200ab}}, // _vov_, zniš, _бохи, হমুদ_,
+ {{0xb5fdbdbb,0x7649bdbc,0xdc3c8042,0x7522bdbd}}, // lišn, ckey, _išču, _troz,
+ {{0x81a900c8,0x443c9d14,0x261981ce,0x39468019}}, // খুন_, _tov_, _मोटी_, nyos_,
+ {{0x201a3dbe,0xa3490032,0xb87b002a,0xb5fdbdbf}}, // _capi_, _bọ́t, prím, unšv,
+ {{0x45d51630,0x81b000fc,0x660d3dc0,0x141b81c6}}, // ковс, _gaɓo, teak, _מוגב,
+ {{0x6d1c00d4,0xddc40ee1,0x63ae019d,0x60cd1f34}}, // निंग_, tniš, _agbn, _twam,
+ {{0x30a7007f,0xa8a71541,0x31351777,0xb87b008b}}, // [2ab0] _пров, _прок, _репр, nrík,
+ {{0xb5fd8052,0xddc4007a,0x7989808e,0x63ba88ae}}, // jišn, rniš, _ryew, nctn,
+ {{0xb5fd803a,0x28b717ba,0xddc4012b,0xf6990dc7}}, // dišn, _इंडि, sniš, _овој_,
+ {{0x44f99c5e,0x290901bc,0xd83889d1,0xb926019d}}, // yè_, _kpaa_, _miču_, _nkwọ_,
+ {{0xca749459,0x201a3dc1,0xdb0187f1,0x00000000}}, // دالغ, _yapi_, nclò, --,
+ {{0x6bd40c2b,0xb92601bc,0x290905ee,0x7e628176}}, // _متفر, _akwọ_, _mpaa_, _amop,
+ {{0x439415fe,0x6aa981ec,0xd838817f,0x79c980f7}}, // _барс, _tief, _niču_, _يوسف_,
+ {{0x44f986c0,0x9f4c8580,0x68fd072c,0x4ada825a}}, // tè_, _abdó_, ktsd, _भिजव,
+ {{0xb5fd8699,0x60db80b8,0x3d170072,0x81b000fc}}, // bišn, _uvum, निटे_, _saɓo,
+ {{0x44f9bdc2,0xd83881a1,0x26de0061,0x7bd71029}}, // rè_, _biču_, átok_, naxu,
+ {{0x201a0458,0x9f0492dc,0x3a3f86c0,0x26c101c0}}, // _rapi_, _مولو, _koup_, _ntho_,
+ {{0x201a08b3,0x44f9bdc3,0xa96a2300,0x216a2d59}}, // _sapi_, pè_, дима_, дими_,
+ {{0x212400f7,0x6486807b,0x9f5801fa,0x44f98037}}, // ímh_, gðis, rfrí_, qè_,
+ {{0x66e600a9,0xed5684ae,0x3a3f92b6,0xb05b148c}}, // кога, _још_, _loup_, spän,
+ {{0xe1ff388a,0xfc3f0ba3,0xb5fb008b,0x17790198}}, // mión_, _luís_, _smáa, есть_,
+ {{0x80d280c8,0xe5f3002e,0x201a0010,0x224b1142}}, // _সমস্, văţă, _wapi_, ckck_,
+ {{0x201a0500,0xb87b04c3,0x28dc0d86,0xbea33dc4}}, // [2ac0] _tapi_, tuít, _बिटि, ратк,
+ {{0xe1ff128e,0x3eab0db1,0xf1a701d0,0x224002f9}}, // nión_, _eict_, _गणतन, _hoik_,
+ {{0xb5fd805c,0x3a3f82d6,0x3e7a8ec3,0x00000000}}, // višn, _boup_, līte_, --,
+ {{0x3a3f82be,0x20058bb1,0xb87b3dc5,0xbebc81a9}}, // _coup_, _abli_, buís, _brīn,
+ {{0x6f088118,0x2444007b,0x99dd8176,0xc7b880fe}}, // _spdc, _sömu_, _alňs, _lađe_,
+ {{0x75360713,0xdb1c0065,0xe9f910ab,0x3ea03dc6}}, // mxyz, _kerü, _diẹ_, lmit_,
+ {{0xc7b8805c,0xb5fd8390,0x7c2e3dc7,0xe8e0001c}}, // _nađe_, rišn, _inbr, _ước_,
+ {{0x2005826c,0xeb970956,0x9f580144,0x5eea01d0}}, // _ebli_, _рис_, leró_, _छिन्_,
+ {{0x5ea381a8,0xdb1abdc8,0x5275bd93,0x00000000}}, // جميل, natö, _суру, --,
+ {{0xe1ff3dc9,0xe7ee016f,0x9f580388,0x515b807c}}, // gión_, _जागा_, neró_, _חכמו,
+ {{0xad9b04be,0x3d178035,0x2252067f,0xdc3c80c3}}, // _abúl, _बैठे_, _blyk_, _ušču,
+ {{0x28b70540,0xb87b01ac,0xdb1c3dca,0xacbb0036}}, // _इंदि, prík, _heró, _brûl,
+ {{0xe1ff04c3,0x81cc00ab,0xb87b016a,0x7c2e1c11}}, // bión_, _লাখ_, nsíg, _onbr,
+ {{0xe1ff161b,0xd83882a5,0x545386cf,0x14d78158}}, // ción_, _tiču_, _квіт, _יודל_,
+ {{0x386d02fd,0x44d00063,0x3eab02f7,0x9f5801ca}}, // đer_, eń_, _pict_, deró_,
+ {{0x320f0065,0x02a4027e,0x5bcb1905,0x7c2e3dcb}}, // _úgy_, _крым, िर्व, _anbr,
+ {{0xf1b98042,0x35b415da,0x9a6800f7,0x5c7511b3}}, // [2ad0] _jaše_, рбур, _جميل_, глат,
+ {{0x7ae33dcc,0xa5bb3dcd,0x7bc582f1,0x5454102a}}, // ánto, _anót, _õhut, авут,
+ {{0x629f047f,0xe81d80d4,0x6aad004f,0x3ea00039}}, // _èpos, _योगा_, _kiaf, bmit_,
+ {{0x20113dce,0xe45f00f2,0x3ce90353,0x798d31af}}, // lezi_, _stöd_, prav_, _kyaw,
+ {{0x26debdcf,0xc7b80289,0x91058d15,0x6aad2aa0}}, // _avto_, žđe_, упле, _miaf,
+ {{0xe1ff02ba,0x661d04b9,0x24440106,0x764d3dd0}}, // xión_, _hask, _döms_, lkay,
+ {{0x27e9817b,0x9388002e,0x8aa78088,0xead41091}}, // ğan_, _аста_, _арад, _вось,
+ {{0x1dd30076,0x27e982bb,0x765801e2,0x442e81e0}}, // तरित, şan_, _įvyk, _anf_,
+ {{0xe1ff1727,0x798d0397,0x2a648098,0x442ebdd1}}, // tión_, _nyaw, _търг, _bnf_,
+ {{0x661d19e0,0x69d9bdd2,0x3ea000f1,0xec769619}}, // _lask, lawe, zmit_, лпы_,
+ {{0x7bd53dd3,0x6aad3dd4,0x25af80dd,0x9f5801e8}}, // _mezu, _biaf, _tggl_, herò_,
+ {{0xe1ff161b,0xceb2804c,0x69d9822e,0x1cb98013}}, // sión_, _היא_, nawe, _كاتب_,
+ {{0xfce58729,0x798d00b4,0xdb0e0032,0x764d3dd5}}, // _соло, _cyaw, _egbè, dkay,
+ {{0x44fd3dd6,0x7e698110,0x201124e7,0xdb1c002a}}, // dì_, liep, gezi_, _xeró,
+ {{0xcfd880c8,0xb87b0ece,0x2d8f01ec,0x69bc064a}}, // _সামন, tuír, ügen_, _एलपी,
+ {{0x661d3dd7,0x69d9bdd8,0x6e3b8365,0xe3cd00ab}}, // _cask, jawe, njub, _লাগব,
+ {{0x61e43dd9,0x3ea00648,0x62863dda,0x9f580388}}, // [2ae0] _idil, rmit_, moko, teró_,
+ {{0x62863ddb,0x09cc90be,0xdb1c0019,0x3ea03ddc}}, // loko, ार्य, _terü, smit_,
+ {{0x272382bb,0x6e3bbb0f,0x661d3ddd,0x91d086a7}}, // _nın_, kjub, _fask, दरलै,
+ {{0x2485bdde,0x661d00a4,0x62860b09,0x69d9bddf}}, // holm_, _gask, noko, gawe,
+ {{0x1ae03792,0x186a9bdc,0x9f581e09,0x91e3bde0}}, // _पटकथ, нади_, peró_, соче,
+ {{0x69d63de1,0x62863de2,0x7bd53de3,0x6d5b8748}}, // _leye, hoko, _gezu, dzua,
+ {{0xeb970bda,0x69d99e54,0xdb1c0333,0x62863de4}}, // _бир_, bawe, _veró, koko,
+ {{0x660402ee,0xf1b9b6f0,0x60c4072f,0x79892358}}, // nfik, _paše_, _otim, żewi,
+ {{0x54548e8e,0x20110c2e,0xb21b03ba,0x60c43de5}}, // ивит, yezi_, klær, _ntim,
+ {{0xf1b99eee,0x4cc380ab,0xdb0e046d,0xe64487c0}}, // _vaše_, _শিশু, _agbé, ılış,
+ {{0x60c43de6,0x798d0859,0x4394bde7,0xaa5919fe}}, // _atim, _syaw, ранс, вину_,
+ {{0x201ea7ba,0x20112994,0xe45287d2,0x62863de8}}, // _hati_, wezi_, _گفتگ, goko,
+ {{0x201e9af2,0x661d3de9,0x20113dea,0x61e40114}}, // _kati_, _rask, tezi_, _ddil,
+ {{0x61e40a0b,0x661d3deb,0x201e8397,0x76428079}}, // _edil, _sask, _jati_, _hooy,
+ {{0x7bdabdec,0x661d3ded,0x201e885c,0x20113dee}}, // matu, _pask, _mati_, rezi_,
+ {{0x201e8324,0x7642ba46,0x69d61210,0xdb188192}}, // _lati_, _jooy, _geye, _bevö,
+ {{0x20113def,0x764d3df0,0xdb0501ec,0xdddb81d0}}, // [2af0] pezi_, rkay, schü, nouš,
+ {{0x201ebdf1,0x69c0bdf2,0x44fd3df3,0x661d3df4}}, // _nati_, _afme, rì_, _wask,
+ {{0x7bd50b67,0x69d9a169,0x44fd3d36,0x8af902a4}}, // _vezu, tawe, sì_, тнес_,
+ {{0x7bdabdf5,0x7f3a00be,0x21290168,0x7d160428}}, // hatu, _סערו, _krah_, bwys,
+ {{0x7bdabdf6,0x201ebdf7,0xe0df3df8,0x6728bdf9}}, // katu, _bati_, nsò_, _ardj,
+ {{0xe800a9b7,0xd2d7190c,0xb3468214,0x67288b80}}, // ल्ला_, _مغرب, _kaçı, _brdj,
+ {{0xe73982da,0x201ebdfa,0x7bdabdfb,0x629b801c}}, // тей_, _dati_, datu, _thuo,
+ {{0x8aa48aca,0xddc2809a,0xb87b0317,0xa2678098}}, // _груд, _iloś, crív, _съгл,
+ {{0x7bdabdfc,0xee36827e,0x69d63dfd,0xb5fd8984}}, // fatu, рны_, _reye, lišk,
+ {{0x6e3bbdfe,0x201e80f1,0x62863dff,0x44213b15}}, // sjub, _gati_, woko, ndh_,
+ {{0x4421279d,0x21290397,0x62863e00,0xb5fd8984}}, // idh_, _arah_, toko, nišk,
+ {{0x201e811b,0x3cee8072,0x764281b4,0x2cae8362}}, // _zati_, _आमचे_, _gooy, _eifd_,
+ {{0x62863e01,0x7bda9611,0x44313e02,0x8e8480f7}}, // roko, batu, _onz_, _النه,
+ {{0x7bdabe03,0x9f58007b,0x25bfbe04,0x6c7980be}}, // catu, ferð_, lcul_, _שאַפ,
+ {{0x07a605d3,0x8fa61cd5,0x62863e05,0x45d61354}}, // равн, раве, poko, ицат,
+ {{0x25bf8590,0x13da80ab,0x64553e06,0x6443be07}}, // ncul_, _দায়, _alzi, _honi,
+ {{0x6443be08,0x6604208b,0xb4663e09,0x21292368}}, // [2b00] _koni, rfik, акал, _grah_,
+ {{0x75243e0a,0x3eaf8a0f,0xe457113f,0x61fb87db}}, // rviz, _ligt_, ажу_, _acul,
+ {{0x3a200a8e,0x6443a776,0x44310267,0x201ebe0b}}, // _kaip_, _moni, _dnz_, _rati_,
+ {{0x201e803b,0x4421154c,0x6443946f,0xa9268e97}}, // _sati_, adh_, _loni, идел,
+ {{0x201e9f7c,0xd3788025,0x7bda8545,0x67288904}}, // _pati_, maće_, yatu, _srdj,
+ {{0xdb1c0125,0x7ee68790,0x6443be0c,0x6aa2bb99}}, // _ferð, ацие, _noni, smof,
+ {{0x7bdabe0d,0xdb1c0125,0xd6d784d9,0x290d8102}}, // vatu, _gerð, аты_, _epea_,
+ {{0xf53f3e0e,0x7bdaa892,0x7e7601e0,0x96348389}}, // _igår_, watu, nnyp, сниц,
+ {{0x7bdabe0f,0xd90e8bca,0xdd978ca4,0xd49a8256}}, // tatu, _آیت_, ишь_, кро_,
+ {{0x656fb128,0x7642885a,0x320902c4,0x6443a91e}}, // úcha, _wooy, _sbay_, _coni,
+ {{0xc5e880c8,0x0bb70051,0x6f09009a,0xc7b8817f}}, // ক্ষা_, ילים_, łecz, _gađa_,
+ {{0x7bdabe10,0x3ced8082,0xd3789487,0xaa673e11}}, // satu, brev_, jaće_, рток,
+ {{0x7bdabe12,0xdfdb0098,0x6443be13,0xd37880ce}}, // patu, къв_, _foni, daće_,
+ {{0x27ed0352,0x64438988,0x588701e2,0xf1b9be14}}, // ßen_, _goni, рыма, _maša_,
+ {{0x80d881ce,0x09d780ab,0xe0df3a68,0x27ed02d0}}, // _नौसे, _হাসা, ssò_, ğen_,
+ {{0xb5fd8110,0x27ed0214,0xe65400e8,0x443e8106}}, // višk, şen_, овсь, ljt_,
+ {{0xf1b9be15,0x69dd3e16,0xc7b80289,0x656d81ac}}, // [2b10] _naša_, mase, žđa_, _ľahk,
+ {{0x69dd3e17,0x57f505f1,0xa802899b,0x2a690282}}, // lase, _упот, çıla, _hmab_,
+ {{0x94a803a7,0xd3788b67,0xbebc81a9,0x442103ed}}, // ртта_, baće_, _drīk, rdh_,
+ {{0xdb1c07ca,0x69dd3e18,0xb5fd8984,0xc7b882a5}}, // _verð, nase, rišk, _rađa_,
+ {{0x634a8201,0xb5fd8110,0x645a8079,0xe4a703a7}}, // lənd, sišk, khti, _врво,
+ {{0x69dd3e19,0x89348065,0xe2d90065,0xb87b157a}}, // hase, تعما, _مارچ_, crít,
+ {{0x81e200c8,0x69dd3e1a,0x6d4d0def,0xb92081bc}}, // ন্ট_, kase, lyaa, _karị_,
+ {{0x6443be1b,0x80b800c8,0x69dd3e1c,0xb920819d}}, // _soni, _অবস্, jase, _jarị_,
+ {{0x6d4d0d7a,0x6443be1d,0xb5fd8e63,0x25bf8087}}, // nyaa, _poni, niši, rcul_,
+ {{0x25bfbe1e,0xd37885a2,0x799b8300,0x628d3e1f}}, // scul_, zaće_, _izuw, _akao,
+ {{0xd7958013,0x3a2003ac,0x6443be20,0x7e6d3e21}}, // _الاخ, _paip_, _voni, kiap,
+ {{0xc0493e22,0x64438613,0x69dd3e23,0x42561878}}, // _אז_, _woni, gase, стот,
+ {{0xd3788024,0xa3d73e24,0x2f01be25,0x2ba7016f}}, // vaće_, सरा_, lóg_, केता,
+ {{0x645abe26,0x2902231b,0x7bd8910f,0x10a6002e}}, // chti, rtka_, _gevu, сигн,
+ {{0x3a200a8e,0xd378876c,0xaa4601e5,0xdddb81b9}}, // _taip_, taće_, _гейл, kluż,
+ {{0x7bc1be27,0x765bbe28,0x69c101d0,0xaac48a19}}, // lclu, nhuy, ělec, _بتون,
+ {{0xe5a324c8,0xd37881dd,0x6da33e29,0x3ae382d0}}, // [2b20] зити, raće_, зита, lıp_,
+ {{0xf1b981f4,0xd3788390,0xb87b2216,0xb3d58264}}, // _raša_, saće_, rsíc, _দাঁড়,
+ {{0x3ce01031,0xf1b9979d,0x68e4013c,0x6d4d0d92}}, // ssiv_, _saša_, _hvid, ayaa,
+ {{0xeb9702c7,0xe8040105,0xddcd0087,0xb5fd8115}}, // щих_, श्रा_, rnaţ, biši,
+ {{0xcfcf80c8,0xaca40135,0x7bc194a2,0xdd920c2a}}, // _রাজন, _anụr, kclu, _روس_,
+ {{0xf1b9be2a,0x3ae382d0,0x6e2408bf,0x99dd84e8}}, // _vaša_, kıp_, ldib, _voňa,
+ {{0x76460110,0x7bd8be2b,0x69dd3e2c,0x7bde3e2d}}, // _koky, _revu, yase, mapu,
+ {{0x6e243e2e,0xdb1c016d,0x645d80f1,0xceb30039}}, // ndib, _berö, ësin, היה_,
+ {{0x7d043e2f,0x764601e2,0x69dd03ca,0x7bd881a1}}, // ntis, _moky, vase, _pevu,
+ {{0x7bde103b,0xb4d6000d,0x409680f7,0xe7f28592}}, // napu, _सबै_, _الطر, _घाटा_,
+ {{0x69dd3e30,0x7d043e31,0x68e40e94,0x539880e8}}, // tase, htis, _avid, рвня_,
+ {{0x645a820f,0x7d04120b,0x7ae30511,0xf363876a}}, // shti, ktis, ánti, ятын,
+ {{0x7bde3e32,0xd90f0077,0x6443016d,0x6d4d0079}}, // kapu, رید_, önik, xyaa,
+ {{0x80b800ab,0x634a8085,0x69dd3e33,0x68e901a1}}, // _অবশ্, tənd, sase, šedo,
+ {{0xf773115f,0x6fda0ee6,0x69dd3e34,0x764602c4}}, // ساس_, _बजरं, pase, _boky,
+ {{0xdceb0a21,0x8afc809a,0x7d04075e,0x6e240ec9}}, // žičk, rzęd, ftis, gdib,
+ {{0x76463e35,0x68e40dc1,0x26de14b5,0x68e28824}}, // [2b30] _doky, _gvid, átor_, jsod,
+ {{0x7e6d1083,0xb5fd811f,0x7bde3e36,0x6d4d0079}}, // siap, riši, gapu, ryaa,
+ {{0x6d4d07d5,0xb87b002a,0x6e242ad9,0xb5fd8088}}, // syaa, brír, bdib, mišv,
+ {{0x09ad00c8,0x9989003a,0x200cbe37,0xa3c90424}}, // _ক্যা, _znaš_, _abdi_, ैरह_,
+ {{0xab620182,0x645885b4,0x7d043e38,0xe9d88a8e}}, // _çünk, _olvi, ctis, скі_,
+ {{0x3ae38214,0xd5e28032,0x7ff5803d,0x1ec98878}}, // yıp_, _amò, زستا, алки_,
+ {{0x8fa2867c,0xaad786a7,0xb87b0144,0x70b50035}}, // маше, _भौंक, nríq, ंदुल,
+ {{0x245b83d3,0x44233e39,0x6e229493,0x2f01be3a}}, // _même_, _haj_, _caob, róg_,
+ {{0x44233e3b,0x64470025,0x628b9f1e,0x1fa61d40}}, // _kaj_, _koji, mogo, _खण्ड,
+ {{0x628bbe3c,0xdb1c10f6,0x3d098321,0x60c9b593}}, // logo, _verö, _वहीं_, _item,
+ {{0x68e4011f,0xeb99be3d,0x64900364,0x3b099ac9}}, // _svid, рин_, päiv, шено_,
+ {{0x44233e3e,0x64473e3f,0x7bde01df,0xa3b58ebf}}, // _laj_, _loji, zapu, _झलक_,
+ {{0x7bde2d1f,0xe820800d,0x229c816b,0xa3a93738}}, // yapu, _यसमा_, níka_, खेत_,
+ {{0x44233e40,0x9f5800e7,0x6e228b5d,0x660998e8}}, // _naj_, lgré_, _zaob, lfek,
+ {{0xe29a1b93,0x61e99ba5,0xa7aa3e41,0xf1b98353}}, // _как_, _odel, рква_, _našo_,
+ {{0x69db8352,0x7bde04cd,0x61e9803d,0x6e2427f5}}, // _neue, wapu, _ndel, udib,
+ {{0x6447025b,0x7bde2575,0x0eb6000f,0x628bbe42}}, // [2b40] _boji, tapu, _आंकड, dogo,
+ {{0x7659831d,0x44233e43,0x61e9be44,0x64473e45}}, // _llwy, _caj_, _adel, _coji,
+ {{0xb7240077,0x7c238722,0x8fa6284f,0xe73a1bdc}}, // _عکسه, _manr, _фане, _тен_,
+ {{0x20180179,0x7afd8063,0x212000dd,0x6b7a80be}}, // leri_, ństw, _ksih_, ברענ,
+ {{0x44233e46,0x64a602cb,0x69c284c3,0x7bde3e47}}, // _faj_, _мама, rcoe, papu,
+ {{0x6e22a8e1,0x20183e48,0x61e9be49,0x2912022e}}, // _saob, neri_, _edel, _mpya_,
+ {{0xf8bf8a0f,0x6609a09b,0x68e2be4a,0x69db81ec}}, // _één_, ffek, ssod, _feue,
+ {{0x442301e9,0x7bdc3e4b,0x20183e4c,0xe3b8817b}}, // _zaj_, _heru, heri_, rdım_,
+ {{0x7bdc07d5,0x7c23be4d,0xd378911b,0x442301c5}}, // _keru, _banr, daća_, _yaj_,
+ {{0x2018003a,0xddc40f20,0x344a9810,0x44230069}}, // jeri_, jniž, рчин_, _xaj_,
+ {{0x7bdc1a67,0x6e228ad0,0x64900009,0x61e9806a}}, // _meru, _taob, läis, _ydel,
+ {{0xbebc8029,0xf1b18105,0xbddb0036,0x7529811a}}, // _brīv, ुँचन, _poèm, bvez,
+ {{0x2906be4e,0x64900198,0xe53486d2,0xddcd01d0}}, // ntoa_, näis, _хель, chař,
+ {{0x20183e4f,0x7bdc3e50,0x7c238114,0xe3af8019}}, // geri_, _neru, _ganr, عری_,
+ {{0x6447349a,0x6abc00dd,0x466b3e51,0xe9f9001c}}, // _roji, _nurf, арам_, _chả_,
+ {{0x44233e52,0x644704b9,0x64900009,0x7bdc3e53}}, // _saj_, _soji, käis, _aeru,
+ {{0x7bdc3e54,0x20183e55,0x44233e56,0x628b8503}}, // [2b50] _beru, beri_, _paj_, vogo,
+ {{0xb5fd803a,0x20183e57,0x7bdc3e58,0xf1b9b78b}}, // lišt, ceri_, _ceru, _pašo_,
+ {{0x442301e9,0x628b8503,0x60c987ea,0x7bdc3e59}}, // _vaj_, togo, _stem, _deru,
+ {{0x61fd2280,0xf1b9807a,0xb87b0187,0x78bb87b8}}, // ngsl, _vašo_, juíz, _puuv,
+ {{0x44230025,0x3ea90364,0x7bdc3e5a,0x61fd3e5b}}, // _taj_, mmat_, _feru, igsl,
+ {{0x7bdc044e,0x3ea91f5d,0xb5fd807a,0x44b40098}}, // _geru, lmat_, hišt, _обяс,
+ {{0x7bc51b7c,0xa2ab8697,0x69db81ec,0x6abc0041}}, // mchu, जगद्, _teue, _gurf,
+ {{0xee39867c,0xe9df87ca,0xb5fd90d1,0x61e9be5c}}, // сни_, _þú_, jišt, _udel,
+ {{0x7bdc1d1b,0x1c0b000d,0x1a68803d,0x195881bb}}, // _yeru, स्थल_, _بینی_, _даты_,
+ {{0x200100f6,0x75298065,0x3ea927df,0x7bc53e5d}}, // _nchi_, rvez, hmat_, nchu,
+ {{0xf9890159,0x7c23be5e,0x3ea9059c,0x260c11bc}}, // _ער_, _vanr, kmat_, ड्डी_,
+ {{0xc86904de,0x98a90279,0x6721817f,0x20183e5f}}, // _גן_, _drač_, _islj, weri_,
+ {{0x20183e60,0x3ea90ad4,0xd378812b,0x7c23861c}}, // teri_, dmat_, raća_, _tanr,
+ {{0xb5fd8b67,0x39408198,0x2a7f82d4,0x64902079}}, // rišu, äisi_, _ljub_, häir,
+ {{0x2bd1835a,0x7bdc11ee,0x20183e61,0x43758158}}, // _द्या, _reru, reri_, _מײַן_,
+ {{0x7bdc085c,0x20010133,0x3ea9028c,0x98a008ae}}, // _seru, _echi_, gmat_, _orić_,
+ {{0x7bdc3e62,0x959a3aa3,0x20183e63,0x056636ae}}, // [2b60] _peru, ству_, peri_, чван,
+ {{0xf1bf3e64,0x14231b67,0xdb0e04be,0x69c608f9}}, // ndán_, ндум, _agbá, lcke,
+ {{0x7bdc3e65,0x2ba704e5,0xdc9a80be,0x68f981ed}}, // _veru, केला, ציעל, euwd,
+ {{0x64900364,0xb87b01df,0x645e3df9,0xfc3f01df}}, // täis, xuíz, shpi, _xuíz_,
+ {{0x7bdc03ac,0xc5f3012a,0x3a26800b,0xf91880d7}}, // _teru, _אדר_, rdop_, تراژ_,
+ {{0x7bc5279e,0xb5fd8668,0x2a7fb2df,0x6e26020d}}, // cchu, zišt, _ejub_, _hakb,
+ {{0x64a30be4,0x2906be66,0x98a00b80,0x63830251}}, // _чара, stoa_, _erić_, _згра,
+ {{0x44443e67,0xb86680d5,0xa534be68,0x64903e69}}, // lj_, _کارو, днич, päis,
+ {{0x444401e9,0xb5fd9620,0xfc3f157a,0x6e263e6a}}, // oj_, višt, _ruíz_, _makb,
+ {{0x44443e6b,0x3ea93e6c,0xfd0f81a8,0x6aa405ee}}, // nj_, zmat_, اجي_, _ahif,
+ {{0x444401c5,0x443801b0,0xb5fd90d1,0x04fe80ab}}, // ij_, _mnr_, tišt, ্টের_,
+ {{0x6aa43e6d,0x7f3b8039,0x20013e6e,0x61e2be6f}}, // _chif, _לעבו, _schi_, naol,
+ {{0xb5fd803a,0x444400ee,0x3ebe9670,0x81e180ab}}, // rišt, kj_, _hutt_, _দাম_,
+ {{0x8c438652,0xb5fd8115,0x042080ab,0x7b058198}}, // _пече, sišt, বাহী_, öttö,
+ {{0xb5fd801b,0xe9ff801c,0x6e263e70,0x98a68088}}, // pišt, _loạn_, _bakb, jvoč_,
+ {{0x44443e71,0x7afabe72,0x6aa40d8b,0x5f9400e8}}, // ej_, mutt, _ghif, хист,
+ {{0x644abe73,0x7afabe74,0x61ed3240,0x628f01ac}}, // [2b70] _kofi, lutt, _idal, moco,
+ {{0x60cd3e75,0x5ba71777,0x7c2894ba,0x628f0c1d}}, // _itam, през, nddr, loco,
+ {{0x7afaaa3d,0x7bc53e76,0x644a8915,0x660281a1}}, // nutt, rchu, _mofi, _ocok,
+ {{0x444410af,0xe1ff3e77,0x7bc51d77,0x27e33e78}}, // aj_, ngó_, schu, lajn_,
+ {{0xa95480e8,0x92b48060,0xb5fb04be,0x7afaaada}}, // нкці, رحما, _alág, hutt,
+ {{0xf77083f8,0x7afabe79,0xe0d08591,0x644abe7a}}, // گاه_, kutt, ازن_, _nofi,
+ {{0x9ea98056,0x69c600f2,0x16a99240,0x628f06cb}}, // овка_, ycke, овки_, koco,
+ {{0x7c273e7b,0x61ed3e7c,0x7afaa1da,0x2a7faf12}}, // _hajr, _ndal, dutt, _ujub_,
+ {{0x644abe7d,0x68e480f7,0x3e6a803d,0x60cd3051}}, // _bofi, áidt, تجوی_, _ntam,
+ {{0x61ed045c,0x644abe7e,0x6aa433fe,0x8b07928a}}, // _adal, _cofi, _shif, _naří,
+ {{0x60cd3e7f,0x1c020b04,0x6721856f,0x28b706af}}, // _atam, र्टल_, _uslj, _इंजि,
+ {{0x628f002a,0x60db8a03,0x2d8e8135,0x68e9be80}}, // goco, _kwum, ịlị_, _kved,
+ {{0x752d02a5,0x660d3e81,0x98a48087,0x61ed3e82}}, // jvaz, dfak, _urmă_, _ddal,
+ {{0x764b8065,0x6e298ca6,0x7afa80df,0x7c273e83}}, // _hogy, ldeb, butt, _najr,
+ {{0x2dcb0a2c,0x28dd8b75,0x628f0359,0x60cd34c2}}, // _ụlọ_, नीति, boco, _etam,
+ {{0x44279e7c,0x444401e9,0x661bbe84,0x96950f27}}, // _han_, wj_, neuk, еруш,
+ {{0x4427be85,0x7c27269d,0x644a8b99,0xfce30009}}, // [2b80] _kan_, _bajr, _yofi, кото,
+ {{0x444410af,0x4427be86,0xb5fb3e87,0x660d036e}}, // uj_, _jan_, _alád, afak,
+ {{0x4427be88,0x6e263e89,0x7d09be8a,0xe1ff3d9f}}, // _man_, _takb, htes, ciós_,
+ {{0x4427be8b,0x8b07801b,0x7d09be8c,0xe1ff009a}}, // _lan_, _zaří, ktes, chód_,
+ {{0x09e390f8,0x44278129,0xe3e380ab,0x61e2be8d}}, // точн, _oan_, _মানব, raol,
+ {{0x4427be8e,0x645c048d,0xdfcf80a0,0x68e981a1}}, // _nan_, _ulri, ليه_, _dved,
+ {{0x63bc01a1,0x61e280e5,0xdde181f4,0x60db871f}}, // _igrn, paol, _šuša, _ewum,
+ {{0x4427bade,0x27e328fc,0x661b804f,0x7afa8198}}, // _aan_, zajn_, geuk, vutt,
+ {{0x4427be8f,0x628f1d3a,0xe7cfbbf1,0xc7b8826c}}, // _ban_, voco, _स्वप, _lađi_,
+ {{0x4427804c,0x7afabe90,0x61e0816d,0x3ebe847f}}, // _can_, tutt, _heml, _tutt_,
+ {{0x4427be91,0x764b8019,0x68e98259,0x61e0b204}}, // _dan_, _fogy, _zved, _keml,
+ {{0xc7b8be92,0x60c0be93,0x7afa81dc,0x4427be94}}, // _nađi_, _kumm, rutt, _ean_,
+ {{0x44278cf7,0x27e3012b,0xe8058e5b,0x60c0be95}}, // _fan_, tajn_, _रामा_, _jumm,
+ {{0x4427be96,0x60c0be97,0xa3dfbe98,0x628f3e99}}, // _gan_, _mumm, तरण_, soco,
+ {{0x764b88b3,0x27e304b7,0x2bdd0072,0x7bca82a6}}, // _yogy, rajn_, यरसा, _iffu,
+ {{0x61ed3e9a,0x4427be9b,0x61e0854f,0xfe1480d4}}, // _udal, _zan_, _neml, _थॉमस_,
+ {{0x4427be9c,0x60c0be9d,0x60cd04bf,0x6e940db6}}, // [2b90] _yan_, _numm, _utam, титу,
+ {{0x201c803a,0x752d2d08,0x69c980f3,0x4cc800ab}}, // jevi_, rvaz, _sfee, _লিখু,
+ {{0xa3a90d38,0x68e9be9e,0xc7d60051,0xd3711e13}}, // खें_, _sved, _אותי_, دها_,
+ {{0xa4d500e8,0x3a291699,0x7d09971c,0xd3a4066a}}, // _помі, _jaap_, ytes, труп,
+ {{0xb5fb003e,0x7c270197,0xe6ed81ac,0xcce700f7}}, // _vlád, _tajr, _väčš, _تسجي,
+ {{0x07a5838d,0x8fa58847,0xa15801c6,0x78fc01c6}}, // налн, нале, ובדה_, קלופ,
+ {{0x4427be9f,0x3eb90157,0x26c13ea0,0x2eb591d0}}, // _ran_, _kist_, _juho_, нсис,
+ {{0x6d258dcd,0x7bca924b,0x2d990198,0x26c13ea1}}, // едиз, _affu, _kyse_, _muho_,
+ {{0x68e9827f,0x6fca90c5,0x3eb93ea2,0xe5798009}}, // _uved, _स्कू, _mist_, язи_,
+ {{0xf1b9bc6d,0x3eb93ea3,0x201c828e,0x224d83b2}}, // _naši_, _list_, cevi_, _hoek_,
+ {{0x4427bea4,0xe29a0d69,0x09ad00ab,0xdbdc81fa}}, // _van_, зан_, _ক্লা, ráði,
+ {{0x4427bea5,0x7bca8c2e,0x60c0a0ae,0xc332007c}}, // _wan_, _effu, _yumm, אוט_,
+ {{0x3ead8f06,0xdb1c00f2,0xddc98029,0x4427b3ce}}, // mmet_, _ifrå, cieš, _tan_,
+ {{0x3eadbea6,0x4427a12b,0x26c13ea7,0x3eb90362}}, // lmet_, _uan_, _buho_, _aist_,
+ {{0x3eb93ea8,0x4ac6903e,0xa0a3373a,0x66c782d0}}, // _bist_, _रूपव, уард, _yıka,
+ {{0x09da00ab,0xb5fb3ea9,0xe61f00e1,0x3e7a81a9}}, // _থাকা, _aláb, _skôr_, tīti_,
+ {{0x61e63eaa,0x33f6183a,0xdbe2826b,0x3ead8386}}, // [2ba0] makl, ечес, _béèd, imet_,
+ {{0x61e63eab,0x60c0beac,0x3eadbead,0xa3df8c28}}, // lakl, _rumm, hmet_, तरि_,
+ {{0x224d8b3c,0x09d780c8,0xaca38135,0x60c0beae}}, // _boek_, _হাজা, _ahịh, _summ,
+ {{0x2cb8034a,0x3ead80f1,0xb34683a7,0x224d8267}}, // _wird_, jmet_, laçõ, _coek_,
+ {{0x645b0065,0x3eadbeaf,0x201cbeb0,0x601382d0}}, // áció, dmet_, tevi_, kımı,
+ {{0xeee70077,0x20c6001c,0xddc98bb6,0xd6da8c24}}, // _تغیی, _môi_, vieš, пти_,
+ {{0x442a0282,0x03a33eb1,0xf093012a,0xda200105}}, // _iab_, _биро, אנג_, _बसंत_,
+ {{0x9634867c,0x442a3eb2,0x60c0beb3,0xddc981a9}}, // тниц, _hab_, _tumm, tieš,
+ {{0x06e085fc,0x644e0063,0x61e61ca6,0x442a3eb4}}, // _गौरव_, _kobi, dakl, _kab_,
+ {{0x224d8613,0x545498a0,0xddc9b7f6,0x2cb8816d}}, // _zoek_, твот, rieš, örd_,
+ {{0x442a2f54,0x3ea68168,0xb346841c,0x7e640234}}, // _mab_, _thot_, daçõ, khip,
+ {{0xf1b981a1,0x2456007b,0x20c600ff,0xa75b825f}}, // _saši_, _dæmi_, _bôi_, _מדבר,
+ {{0x2bd18105,0x51843eb5,0x7e6409c4,0x26c13eb6}}, // _द्वा, гура, dhip, _suho_,
+ {{0x442a0069,0x3eb9035f,0xb346beb7,0x634a8085}}, // _nab_, _sist_, gaçõ, lənm,
+ {{0xf1b9beb8,0x61f40106,0x5f95041c,0x443f0129}}, // _vaši_, ådlö, тивт, _đu_,
+ {{0x78ba9087,0x61e60214,0x3a293bb0,0x439b8039}}, // _litv, cakl, _uaap_, _מבוג,
+ {{0x442a3996,0x3eb93eb9,0x8d770eca,0x442c8114}}, // [2bb0] _bab_, _vist_, رارا, odd_,
+ {{0x442a2bf4,0x645abeba,0xb3468073,0xe8058beb}}, // _cab_, nkti, caçõ, _राधा_,
+ {{0x442a01e9,0x442cb3d0,0x443c81a1,0x2d99067f}}, // _dab_, idd_, _mnv_, _wyse_,
+ {{0x7c2a83ec,0xdbe2846d,0x442a1fa4,0x6013a2f8}}, // _lafr, _dééd, _eab_, zımı,
+ {{0x442a3dd7,0x6e3d01ec,0x26debebb,0x99800088}}, // _fab_, _insb, _awto_, _kaiš_,
+ {{0x644e1e4f,0x61e61286,0xa3bc8fcc,0x442a3ebc}}, // _gobi, zakl, _अलग_, _gab_,
+ {{0x7d0d3ebd,0xddcd0087,0x81e70264,0xdb1c08f9}}, // ltas, ciaţ, _পান_, _ngrè,
+ {{0x6e2d3ebe,0xb34680a9,0x442c8355,0x3eb9bebf}}, // ndab, zaçõ, edd_, öst_,
+ {{0x3eadbec0,0x442a3ec1,0x12bd80c8,0x443cba98}}, // rmet_, _yab_, _আবেদ, _bnv_,
+ {{0x09e100ab,0x3eadbec2,0x442a0069,0x61e600b9}}, // _ভাষা, smet_, _xab_, wakl,
+ {{0x6fca800f,0x61e622d1,0x764f3ec3,0x601382bb}}, // _स्टू, takl, _oocy, rımı,
+ {{0x27e7bec4,0x161126ee,0x7d0d3e8c,0x799b808e}}, // lann_, _डॉलर_, ktas, _kyuw,
+ {{0xed4e86f1,0xb3468073,0x61e63ec5,0x6e2d0079}}, // _то_, taçõ, rakl, ddab,
+ {{0x27e78013,0x7d0d013c,0x38a4016d,0xfaf880eb}}, // nann_, dtas, föra_, brī_,
+ {{0x644e21a9,0x442a3ec6,0xb3468073,0x6b66a55e}}, // _robi, _rab_, raçõ, _végé,
+ {{0x442a01e9,0x20c60104,0x644e3ec7,0x27e7ad53}}, // _sab_, _tôi_, _sobi, hann_,
+ {{0xd0408086,0x442a10af,0x6e2b849f,0x644e1188}}, // [2bc0] _etmə, _pab_, _magb, _pobi,
+ {{0x442a01e9,0x61e40357,0x644e02f7,0x6e2b84be}}, // _qab_, _heil, _qobi, _lagb,
+ {{0x2c138c28,0x442a3ec8,0xbddb3b51,0x27e7bec9}}, // त्यं_, _vab_, _poèt, dann_,
+ {{0x6aa9beca,0x6e2b849f,0x7afe3ecb,0x60c40010}}, // _chef, _nagb, rupt, _kuim,
+ {{0x442a01e9,0x442ca065,0xe3b88059,0x27e7becc}}, // _tab_, ydd_, ndır_, fann_,
+ {{0xcee919f4,0x27e7becd,0x442a3ece,0x644e005c}}, // _ترین_, gann_, _uab_, _uobi,
+ {{0x8a06867c,0xb8c98fd5,0x7c2abecf,0xa7fc8457}}, // _изве, _गी_, _rafr, _alıc,
+ {{0xfaa6953d,0x66043ed0,0x61e43ed1,0x442c831d}}, // _рабо, ngik, _neil, wdd_,
+ {{0xd3788067,0x27e7bed2,0x47e180ab,0xa3d600a5}}, // maći_, bann_, _নারী, _सभा_,
+ {{0x78ba85f8,0xdb1c00e7,0x27e79a26,0x6b9c3ed3}}, // _uitv, _agré, cann_, _kyrg,
+ {{0x6fd8873c,0x442c8355,0x68ed0b48,0x7d0d3012}}, // _न्यू, rdd_, _svad, ztas,
+ {{0xd3788a20,0x6f798158,0x6282bed4,0x7d0d3ed5}}, // naći_, _אָנג, nnoo, ytas,
+ {{0x61e43ed6,0x60c40ad0,0x94030105,0x6e20bed7}}, // _deil, _cuim, _लालच_, memb,
+ {{0x6e209fce,0x58d42240,0xa2cabed8,0x645d8168}}, // lemb, рорт, _संप्, ësit,
+ {{0x7e62bed9,0x61e43eda,0x260f8701,0x8c1b8039}}, // _klop, _feil, थ्वी_, _חווי,
+ {{0x6e20bedb,0x60c40020,0x7d0d0e20,0x61e43edc}}, // nemb, _fuim, ttas, _geil,
+ {{0xa5070009,0x3944016d,0x7d00bedd,0x16df9513}}, // [2bd0] ъявл, ämst_, nums, _नब्ब,
+ {{0x7d0d3ede,0xbbdd0540,0x6e209b8f,0x61e40f67}}, // rtas, यरेक, hemb, _zeil,
+ {{0x27e7bedf,0xddc6009a,0x3946802a,0x7d008088}}, // vann_, _dokł, exos_, hums,
+ {{0x6e20bee0,0x27e7bee1,0x628287b8,0x625396f0}}, // jemb, wann_, gnoo, _açor,
+ {{0x7d00bee2,0x6e208365,0x27e7bee3,0x6f1a8118}}, // jums, demb, tann_, _aptc,
+ {{0x6e2bbee4,0xfa2380ab,0x628281b4,0xaa20050a}}, // _sagb, ফাইল_, anoo, _बस्छ_,
+ {{0x6e2b97ef,0x27e7bee5,0x44211959,0x12bd80ab}}, // _pagb, rann_, meh_, _আব্দ,
+ {{0x44210fc7,0x27e7bee6,0xddcd0029,0x05a70b75}}, // leh_, sann_, skaņ, _खराब,
+ {{0x27e7bb99,0x61e4350d,0xf1b980eb,0x3a3f8122}}, // pann_, _reil, _pašu_, _inup_,
+ {{0x44213ee7,0x60c405f8,0xe9f9001c,0x61e42d01}}, // neh_, _ruim, _thẻ_, _seil,
+ {{0x6e2bbee8,0x91e61d7b,0x60c40cdd,0x21292b36}}, // _tagb, лоне, _suim, _asah_,
+ {{0x44213592,0xcd358416,0x7d00bee9,0x940389ab}}, // heh_, _مرتب, bums, ƙƙoƙ,
+ {{0x61e43eea,0x645d820f,0x4421085c,0x290f839c}}, // _veil, ësis, keh_, ntga_,
+ {{0xe3b882bb,0x4421120e,0x26c5beeb,0x61e43eec}}, // rdır_, jeh_, _kulo_, _weil,
+ {{0x61e43eed,0x91e38074,0x212900b9,0xf1b980c3}}, // _teil, खराज_, _esah_, _zašt_,
+ {{0xa2060fdd,0x26c5beee,0x75243eef,0x7874011c}}, // _спид, _mulo_, twiz, _müvə,
+ {{0x0eb90b71,0x96b909c7,0xe9f9001c,0x22400242}}, // [2be0] туры_, туру_, _nhẹ_, _inik_,
+ {{0x6e20bef0,0x29023ef1,0x44213ef2,0x75240db1}}, // zemb, muka_, geh_, rwiz,
+ {{0x7d00b78b,0x6e20b92f,0xd37884a8,0xb17d80e1}}, // zums, yemb, taći_, _reťa,
+ {{0x1efb03c8,0x26d83ef3,0x1c1383eb,0x2c13808e}}, // _אלטע, mpro_, त्तल_, त्तं_,
+ {{0x29023ef4,0x6282ba61,0x44210077,0xb87b041c}}, // nuka_, rnoo, beh_, rsív,
+ {{0x3cf98063,0xb87b00a9,0x3ea03ef5,0x66c787d9}}, // ोंने_, ssív, llit_, _tıkl,
+ {{0x6e208980,0x26c5bef6,0xe8162a2b,0x290200b4}}, // temb, _culo_, द्या_, huka_,
+ {{0x7c2e3ef7,0xe3c1080a,0x26c5820d,0x29023ef8}}, // _habr, ğış_, _dulo_, kuka_,
+ {{0x6e209bcb,0x5986b2f1,0x634a8201,0x7c2e3ef9}}, // remb, _слаб, məni, _kabr,
+ {{0x634a8085,0x7ea58019,0x3e7a80eb,0xd91b807c}}, // ləni, rópa, tīts_, _אומל,
+ {{0x6e209eb3,0x7d00befa,0x7c2e1b42,0x3ea03efb}}, // pemb, sums, _mabr, klit_,
+ {{0x7c2e3efc,0xfce6beb1,0x29023efd,0xa2950221}}, // _labr, _бодо, fuka_, рамі,
+ {{0x6e938624,0xf1b9826c,0x44212eb7,0x667b80be}}, // _النا, _hašr_, yeh_, _אטאק,
+ {{0x2240026c,0xe1ff0333,0x36d50eef,0x6d468198}}, // _enik_, chón_, иогр, äkau,
+ {{0xe7f7946d,0x395f0bb6,0x442e816d,0x2bda801b}}, // ीलता_, šus_, _iaf_, _म्या,
+ {{0x3ea03efe,0x44212709,0x442ebeff,0xf8bf0036}}, // glit_, weh_, _haf_, uvés_,
+ {{0x3cee0063,0x2129078a,0xf8bf02be,0x44213a20}}, // [2bf0] ेंगे_, _usah_, rvés_, teh_,
+ {{0x442ebf00,0x225d861b,0xb5fb0511,0x35b43c20}}, // _jaf_, wkwk_, _glán, сбур,
+ {{0x2baa035a,0x9f4780f1,0x44213f01,0x69dd02c4}}, // _करणा, ranë_, reh_, obse,
+ {{0x44213f02,0x60cd9984,0x27e588ae,0x644806c0}}, // seh_, íamo, _seln_, _òdin,
+ {{0x7c2e3f03,0x44213f04,0xd378812b,0xa5bb1e00}}, // _fabr, peh_, maću_, _caót,
+ {{0x46f5a3fc,0x442ebf05,0x3f9e826f,0x26c5bf06}}, // ачит, _naf_, _bytu_, _pulo_,
+ {{0xf771936d,0x36698dc7,0xe7e6016f,0x2902287b}}, // غات_, како_, करता_, zuka_,
+ {{0xee398be2,0x7c2e1620,0xbb840013,0x7c21bf07}}, // тни_, _zabr, _الفي, welr,
+ {{0x81eb00ab,0x656d3f08,0x9f43001b,0x98b9beb1}}, // মলা_, nzah, dají_, _алат_,
+ {{0x9f6b8021,0x26c5bf09,0x5c558425,0xd3788858}}, // _през_, _tulo_, ртиф, haću_,
+ {{0x9f47bf0a,0x290201d3,0x6aad0706,0x68fd000b}}, // kané_, wuka_, _bhaf, ersd,
+ {{0x61eb8c27,0x6f03b3d2,0x6aad3f0b,0x29023f0c}}, // nagl, munc, _chaf, tuka_,
+ {{0x9f47bf0d,0x6f03bf0e,0xb5fb003e,0xaca3819d}}, // dané_, lunc, _plán, _agụk,
+ {{0x29023f0f,0x442ebf10,0x61eb948c,0x2007bf11}}, // ruka_, _gaf_, hagl, ngni_,
+ {{0x6f03a623,0xfaa62103,0x66e6272e,0x29023ebe}}, // nunc, _како, роба, suka_,
+ {{0x22403f12,0xdbdf0125,0x7afc8c58,0x2902004f}}, // _unik_, _síðu, árte, puka_,
+
+ {{0x6d5b81c5,0x7c2e3f13,0x3ea0007b,0x69c414ed}}, // [2c00] nyua, _pabr, rlit_, _igie,
+ {{0xa7fc82bb,0x6f03bf14,0x3ea03f15,0x2baa02f1}}, // _alın, kunc, slit_, _करदा,
+ {{0x6287003e,0x9f47bf16,0xf1a900d7,0x6d5b808e}}, // čkov, bané_, _لایه_, hyua,
+ {{0xeb9715f7,0x2c64016d,0x62863d40,0x6f038bcf}}, // ших_, _döda_, nnko, dunc,
+ {{0x7c2e3f17,0x7bc38081,0xb17d80e1,0x6d5b81c0}}, // _tabr, _ognu, _deťo, jyua,
+ {{0x7d043d95,0xa073835f,0xceb40085,0x2c640106}}, // muis, огіч, lmə_, _föda_,
+ {{0x7d043f18,0x53aa06b7,0xa2ca8935,0x61eb80e5}}, // luis, _करिश, _संध्, bagl,
+ {{0xa2cb09c8,0x62898168,0x2bd21130,0x61ebbf19}}, // _तंत्, _gjeo, दुरा, cagl,
+ {{0x9f43001b,0x7d0405ed,0x6d5b81c0,0xbebc81a9}}, // vají_, nuis, gyua, _krīz,
+ {{0x62863f1a,0x442ead8d,0x9f479c18,0xb806803d}}, // enko, _qaf_, zané_, _خبره,
+ {{0xe9f90028,0x6f03bf1b,0x7d04000b,0x9f478077}}, // _chế_, cunc, huis, yané_,
+ {{0xc3330051,0x7d043f1c,0x7e69bf1d,0x442ebf1e}}, // קור_, kuis, chep, _waf_,
+ {{0x9f478a56,0x442e9acf,0x7d040a6f,0x61f6031d}}, // vané_, _taf_, juis, _ddyl,
+ {{0x786201ac,0x0377804e,0x7d043f1f,0x251b807c}}, // _dôvo, احیت_, duis, _סווא,
+ {{0xe816035a,0x9f47bf20,0xb4c0000d,0xe9f9001c}}, // द्धा_, tané_, ँदै_, _ghế_,
+ {{0x798d10af,0x2c64016d,0xdce60214,0x5f943f21}}, // _txaw, _röda_, _aykı, цист,
+ {{0x7d04087a,0x764d1cbc,0x9f47bf22,0xd90482e3}}, // [2c10] guis, rjay, rané_, _تی_,
+ {{0xe8160778,0x9f47bf22,0xea00001c,0x98650065}}, // द्दा_, sané_, _trải_, _ایسے_,
+ {{0x61ebbf23,0x9f47bf0a,0x5b15148d,0x656d0192}}, // tagl, pané_, смет, szah,
+ {{0x09bb00c8,0x7d043f24,0xe8f580f7,0xdddb826c}}, // _অ্যা, buis, مستخ, knuš,
+ {{0xe1f88048,0x3cfe0063,0x200c8a03,0x7e7b80c3}}, // ргі_, _लिये_, _bcdi_, viup,
+ {{0x6f039e53,0xf1bf0061,0x6d5b81c0,0x26d984e8}}, // tunc, ldás_, vyua, _čsob_,
+ {{0xda3597ae,0x61eb809c,0xb5fb0388,0x7e7b80dd}}, // _левы, pagl, _llám, tiup,
+ {{0x44313f25,0x7ea58118,0x6d498102,0x6f03bf26}}, // _haz_, rópo, txea, runc,
+ {{0xee36827e,0x44311c66,0x2aff0054,0x7ea587f0}}, // сны_, _kaz_, शंकु_, sópo,
+ {{0x44310db7,0x3c658364,0xb4658364,0x629982ec}}, // _jaz_, ског, скол, lowo,
+ {{0x64553f27,0x61e98006,0x6d5bbf28,0x20128085}}, // _mozi, _keel, syua, əyi_,
+ {{0x6455003a,0x99808038,0x786201ac,0x60c1bf29}}, // _lozi, žiť_, _pôvo, _hilm,
+ {{0x61e9bf2a,0xf6528039,0x61e4a320,0xd54892c5}}, // _meel, _מצא_, ðili, _نجوم_,
+ {{0xe9f90104,0x1c030665,0x753680be,0x64553f2b}}, // _thế_, _लागल_, _לאנד_, _nozi,
+ {{0x4425b30d,0x6443bf2c,0x6299bf2d,0xceb40085}}, // mel_, _inni, kowo, tmə_,
+ {{0x6609bf2e,0x9f4583fb,0x7d0404fa,0xcf9283de}}, // ngek, _celé_, tuis, פטן_,
+ {{0x6299a4f2,0x44313f2f,0x75298a0f,0x6e241655}}, // [2c20] dowo, _baz_, nwez, reib,
+ {{0x64550073,0x44313f30,0x2292016b,0xa3ab8576}}, // _cozi, _caz_, váky_, _खरा_,
+ {{0x61e98c11,0x64553c13,0x291f8009,0xddcb8035}}, // _beel, _dozi, _apua_, _mogł,
+ {{0x4425bf31,0x61e98079,0x201820f0,0x2292016b}}, // hel_, _ceel, lfri_, táky_,
+ {{0x4425bf32,0x60c19010,0x61e98b3c,0x44313f33}}, // kel_, _bilm, _deel, _faz_,
+ {{0xd2598029,0xbb460b88,0xd90d80d5,0x38a9bf34}}, // _viņa_, _легк, ئیل_, túra_,
+ {{0xe80e8e18,0x26c23f35,0x60c1bf36,0xf1bf00e1}}, // _साठा_, _kiko_, _dilm, ndár_,
+ {{0x4425838e,0x61e9aa57,0x6299809a,0x6609836c}}, // eel_, _geel, cowo, ggek,
+ {{0x4425bf37,0x64550db1,0x443103bf,0x89669821}}, // fel_, _yozi, _yaz_, _укаж,
+ {{0xddc28efc,0x61e9bf38,0x26c23f39,0xe8d781c6}}, // _umož, _zeel, _liko_, _לומר_,
+ {{0x61e982a3,0xfd0f80d5,0xb4db0722,0xf743bcb3}}, // _yeel, وجی_, _gràf, _нефо,
+ {{0x32670dea,0x6443bf3a,0x2120008e,0x466980e8}}, // стов, _enni, _bpih_, _крім_,
+ {{0x9f45bf3b,0x4425bf3c,0xb5fb0118,0x201801b9}}, // _relé_, bel_, _imáx, ffri_,
+ {{0x4425bf3d,0x26c20041,0x27ee808b,0x2e3501bc}}, // cel_, _aiko_, nafn_, _ụf_,
+ {{0x443121a9,0x27ff0182,0x26c20870,0xeac9801c}}, // _raz_, ğun_, _biko_, _hẹn_,
+ {{0x64553f3e,0x26c23f3f,0xea00001c,0xf1ba97ba}}, // _sozi, _ciko_, _trại_, ेशान,
+ {{0x5455351e,0x443110dd,0x26c202d5,0xa7fc82d0}}, // [2c30] оват, _paz_, _diko_, _alım,
+ {{0x2a690282,0x61e9bf40,0x44310085,0x61ef117d}}, // _hlab_, _seel, _qaz_, lacl,
+ {{0x64551874,0x94a80698,0x50d40065,0x212b031d}}, // _vozi, стта_, _وزیر, lwch_,
+ {{0x4425bf41,0x60c1843d,0x61ef3f42,0xdb0e0091}}, // zel_, _silm, nacl, _agbó,
+ {{0x6299b746,0x60c985a4,0x44313975,0xe3e800c8}}, // rowo, _quem, _taz_, _পারব,
+ {{0x212d8353,0x60c1a795,0x81e180ab,0x6299bf43}}, // _vseh_, _qilm, _দাও_, sowo,
+ {{0x4425bf44,0x2bdf1a87,0x786b07d9,0xb8d0873c}}, // vel_, _प्या, _güve, _टी_,
+ {{0x442585ad,0x0f7c0039,0xd04d00fc,0x826589c4}}, // wel_, טרול, _kwaɓ, _بهرن,
+ {{0x60c1bf45,0x4425bf46,0x61ef0118,0x6609b5da}}, // _tilm, tel_, dacl, rgek,
+ {{0x66099591,0x2a6900e4,0x212b0114,0x752981ed}}, // sgek, _alab_, dwch_, rwez,
+ {{0x2dd5b71e,0x4425bf47,0x2a6900c3,0x7e6d3553}}, // _джер, rel_, _blab_, khap,
+ {{0x4425bf48,0x753d0065,0xd6ab8591,0x9f4783f2}}, // sel_, _orsz, _صدام_, naní_,
+ {{0xa4f7826a,0x26c23f49,0x7e6d1ab3,0x212b0428}}, // _اکبر_, _riko_, dhap, gwch_,
+ {{0x6443807b,0x8c4581a1,0x26c23f4a,0x2a6902f1}}, // _unni, _феке, _siko_, _elab_,
+ {{0x81ea80ab,0x18a301e2,0xa0a33c20,0xe81b8072}}, // _মাস_, фарм, фард, प्या_,
+ {{0x2a6902c4,0x8afc8035,0x00ca1cb2,0xf1bf0118}}, // _glab_, rzęt, _клик_, meán_,
+ {{0x63a3831d,0xf1bf00f7,0xec7a0615,0x9f4783f2}}, // [2c40] _hynn, leán_, апа_, daní_,
+ {{0x63a3bf4b,0x67218024,0x0c230198,0x3e6702f1}}, // _kynn, _oplj, ммун, _võta_,
+ {{0x49ca9354,0xeac9801c,0xa5bb026b,0xdb0e0032}}, // ален_, _mẹo_, _abód, _agbò,
+ {{0x7e6d3f4c,0xe81600c2,0x63a3bf4d,0xa3d602f1}}, // chap, द्रा_, _mynn, _सभक_,
+ {{0x2ba6864a,0xb4db0706,0xdfd48a41,0xb5fd81d6}}, // _ऑर्थ, _cràd, полы, chši,
+ {{0x6e360110,0x3a26808e,0x29020df6,0x00000000}}, // ldyb, seop_, irka_, --,
+ {{0xed508019,0xdb0e046d,0x42d60a18,0x03a333d9}}, // کھا_, _egbò, _фігу, _жиро,
+ {{0x6e361503,0x63ae026c,0x6722026c,0xf1bf00f7}}, // ndyb, _izbn, _ćojl, deán_,
+ {{0xceb304de,0xddc2809a,0xd25980eb,0xd00a84ae}}, // ויה_, _umoż, _ziņo_, _везе_,
+ {{0x31e3016f,0x09e30d14,0x0bf38009,0x63a38114}}, // पर्ध, पर्य, мпью, _bynn,
+ {{0x2a690069,0x63a38355,0x61ef3f4e,0x20d0801c}}, // _plab_, _cynn, tacl, _hài_,
+ {{0x61e2bf4f,0xb5fb03c1,0x212b0114,0x63b5816b}}, // nbol, _vlák, twch_, ýzna,
+ {{0xa2ca85b3,0x26ccbf50,0x17e200ab,0x61ef27d3}}, // _सूर्, _hudo_, _বাড়ি, racl,
+ {{0x80dd00c8,0x26cc8168,0x212b0114,0x61ef00e5}}, // _বিদ্, _kudo_, rwch_, sacl,
+ {{0x63a38355,0x61e2bf51,0x8f343c7a,0x29023f52}}, // _gynn, kbol, мерц, arka_,
+ {{0x26cc81b4,0xfb8780d7,0x6458b3d9,0x60dd20e4}}, // _mudo_, _بدهی, _hovi, ppsm,
+ {{0x9f479dc1,0x61e2813c,0x6458bf53,0x7e6d33fe}}, // [2c50] vaní_, dbol, _kovi, rhap,
+ {{0x64588390,0x97152ded,0x61ed1357,0x7e6d3f54}}, // _jovi, _емоц, _heal, shap,
+ {{0x6458bf55,0xd4983f56,0x8c2580c8,0x7e6d3f57}}, // _movi, іру_, বাচন_, phap,
+ {{0x20d08142,0x7c288358,0x6458bf58,0x61e28b4e}}, // _bài_, hedr, _lovi, gbol,
+ {{0xdd868307,0x20d08028,0xcfbd00ab,0x9f47826f}}, // _أو_, _cài_, _অভিন, raní_,
+ {{0x20d08028,0x61ed0c9f,0x55ba0039,0x26cca256}}, // _dài_, _leal, _למרו, _budo_,
+ {{0x61e28682,0x26cc8699,0xdb0385b9,0x752d3f59}}, // bbol, _cudo_, _ozná, lwaz,
+ {{0x660d3f5a,0x29023f5b,0x26cc8216,0x69db15c7}}, // ngak, yrka_, _dudo_, ñuel,
+ {{0x7c28bf5c,0xd840026f,0x63a3bf5d,0xb5fb00f7}}, // fedr, íčky_, _synn, _clái,
+ {{0x7c289254,0x26cc88ec,0x60db8d57,0x68e981bc}}, // gedr, _fudo_, _itum, _iwed,
+ {{0x61ed17ca,0x628b8010,0x7c353f5e,0x6458bf5f}}, // _beal, ongo, _mazr, _dovi,
+ {{0x752d3f60,0x213f8988,0xf1bf00f7,0x61ed0ae3}}, // kwaz, _kruh_, reán_, _ceal,
+ {{0x7c28bf61,0xf1bf00f7,0x752d1118,0x20d080ff}}, // bedr, seán_, jwaz, _xài_,
+ {{0x60db822e,0x7d09bf62,0x653a80be,0x7c353f63}}, // _mtum, mues, _לענד, _nazr,
+ {{0x629d2817,0x69c99e3b,0xa7aa1269,0x61ed3f64}}, // boso, _ogee, сква_, _feal,
+ {{0x64470057,0x61ed0083,0x6458817f,0xc6c33832}}, // _anji, _geal, _zovi, ейск,
+ {{0x2bd2a076,0xf1bf00f7,0x60cd00ee,0x7d098168}}, // [2c60] _सलमा, neál_, _guam, nues,
+ {{0x7e643f65,0x39403f66,0x69c99a14,0x673e05f3}}, // skip, _iris_, _agee, _trpj,
+ {{0xe8059039,0x6d4b016d,0xf8b30039,0x60dbbf67}}, // _राजा_, ägar, _רשת_, _atum,
+ {{0x39403f68,0x64473f69,0xf53f021e,0x7659809a}}, // _kris_, _enji, _frå_, _nowy,
+ {{0x7c3500b9,0xf53f01a3,0x463b00be,0xb5fb0091}}, // _fazr, _grå_, _העכע, _aláw,
+ {{0xb8f4a3a7,0x26cc85a4,0x61e2ae6c,0x20d0801c}}, // _सं_, _pudo_, sbol, _vài_,
+ {{0x7c28bf6a,0x75229fd4,0xb5fb00f7,0x91e68319}}, // vedr, _spoz, _slái, _нове,
+ {{0x20d08104,0x80dd00c8,0x39400091,0xfaa380e8}}, // _tài_, _বিস্, _oris_, _засо,
+ {{0x6458bf6b,0x2ab20019,0x394001c0,0x68e98114}}, // _povi, kább_, _nris_, _gwed,
+ {{0x26cc80a9,0x7d04244d,0xf044826a,0x6282bf6c}}, // _tudo_, iris, _تعری, lioo,
+ {{0x61ed0665,0x60cd0867,0xe61a8f2f,0x7c288082}}, // _peal, _suam, _где_, redr,
+ {{0x7d043f6d,0xe81f052a,0xaf4980f7,0xcf2580f7}}, // kris, म्ना_, _بشكل_, _تركي,
+ {{0x8c469232,0x7d098693,0xb5fb027f,0xf487881b}}, // _неде, cues, _kláv, _کانی,
+ {{0x629d3f6e,0x7d043f6f,0x61e0007e,0x80e18264}}, // soso, dris, ımla, _নিম্,
+ {{0xda0d823c,0x25afbf70,0x60c502f1,0x628b8428}}, // _हालत_, _zzgl_, _vihm, yngo,
+ {{0x443f07d9,0x752d3f71,0x7d0410b9,0x7c350289}}, // _şu_, twaz, fris, _sazr,
+ {{0xe7cc8076,0x224901ac,0x660d3f72,0xba3b0cd1}}, // [2c70] ाशाप, _inak_, rgak, roïd,
+ {{0x628b8114,0x29193f73,0xb4db25dc,0xdfd101a8}}, // wngo, mtsa_, _aràc, _جيد_,
+ {{0xaac70c48,0x7d0412a5,0x290b3f74,0x752d3f75}}, // ستان, aris, luca_, swaz,
+ {{0x7d043f76,0xe3b00013,0x7d180013,0x68e9809c}}, // bris, ارك_, éasá, _pwed,
+ {{0x6e29bf77,0xaca481bc,0x9f47826f,0x29193f78}}, // veeb, _agọz, daná_, ntsa_,
+ {{0x3ea93f79,0xf9940158,0xad9b0032,0x8fa58323}}, // llat_, ערס_, _adúg, мале,
+ {{0xe80e83db,0xb8f48e5b,0x12c780ab,0x7659a9a7}}, // _सारा_, _सू_, _শব্দ, _powy,
+ {{0xd87783f8,0xb4db07e2,0x7d0980f1,0x645cbf7a}}, // _کارب, _gràc, tues, örin,
+ {{0x60db822e,0x63a70168,0x6e2991c9,0x290b3f7b}}, // _utum, _hyjn, reeb, juca_,
+ {{0x22491397,0x7d0980f1,0x2a6dbf7c,0x78700198}}, // _anak_, rues, _hleb_, _käve,
+ {{0x6ee7bf7d,0x2ab20065,0x3ea90587,0x7d0980f1}}, // _رسال, vább_, klat_, sues,
+ {{0x39402280,0x7d099220,0x38a402af,0x7d04007b}}, // _pris_, pues, hört_, yris,
+ {{0x7d0993a0,0x3ea90461,0x66c782d0,0xbbaa0bbc}}, // ques, dlat_, _fıkr, _कर्क,
+ {{0x80dd0a49,0x22493f7e,0x638585c2,0x3e83846d}}, // _বিশ্, _enak_, _огла, _fètè_,
+ {{0x07a30a13,0xb8d50006,0xc79517ae,0xe29f007b}}, // варн, _छी_, ерты, boð_,
+ {{0xe1ff1e1e,0x38a4008b,0xecea246c,0xe05800d7}}, // lkón_, vöru_, ждал_, _کیست_,
+ {{0x32d38028,0x14c80077,0xe80e81ab,0x38a40106}}, // [2c80] _hãy_, سهای_, _साला_, fört_,
+ {{0x26cc1313,0x81ea8264,0x9f479ee0,0xa2e6062c}}, // ídos_, _মাই_, zaná_, дожд,
+ {{0x3ea93798,0xb4db009f,0x98a90669,0x3e7a81a9}}, // blat_, _pràc, _građ_, sūti_,
+ {{0x41aa8f80,0xb5fb0a21,0x7d0410b6,0xaca4019d}}, // овен_, _sláv, pris, _inọr,
+ {{0x9f47803e,0x20d40406,0xb5fb3f7f,0x62828074}}, // vaná_, _jäi_, _cláu, rioo,
+ {{0xd6daa64c,0x62828006,0xbddb010c,0xa3c80576}}, // оти_, sioo, _anèh, _उलट_,
+ {{0x44383f80,0xdca31c8b,0x9f47b53d,0x645c3f81}}, // _iar_, _рари, taná_, _iori,
+ {{0x44383f82,0x645c3f83,0xa3c390c5,0x1d3488cc}}, // _har_, _hori, ्शन_, ения,
+ {{0x645c003a,0x4438252d,0x64c895fb,0x9f47bf84}}, // _kori, _kar_, रदेश, raná_,
+ {{0x645c2948,0x628081c0,0x83868071,0x6aa43f85}}, // _jori, _hmmo, дыме, _akif,
+ {{0x645c3f86,0xd37a80e8,0x443810b6,0x9f47bf87}}, // _mori, ючи_, _mar_, paná_,
+ {{0xaacf853f,0x6ab6031d,0x05668012,0x32d38129}}, // _संरक, _chyf, _овен, _dãy_,
+ {{0x3ea93f88,0xa2ca800c,0xaca401bc,0x7c3e8118}}, // vlat_, _संक्, _anọr, _ópro,
+ {{0x645c1c1f,0xb9072cdd,0x44383f89,0xdbf3080a}}, // _nori, _पऽ_, _nar_, _işçi,
+ {{0xc05805e9,0x442ca427,0x25ad1f95,0xd8388503}}, // фія_, med_, _šel_, _mač_,
+ {{0x442cbf8a,0x7c38bf8b,0x3ea93f8c,0x60c8bf8d}}, // led_, _havr, ulat_, _lidm,
+ {{0x67d537cd,0x442c8114,0x443801c6,0x7c3882d0}}, // [2c90] _попу, oed_, _bar_, _kavr,
+ {{0x6280bf8e,0xb5fb1931,0x645c3f8f,0x3ea9001b}}, // _ammo, _alát, _cori, slat_,
+ {{0x44383f90,0x645c3f91,0x2a6d81c0,0x7c38bf92}}, // _dar_, _dori, _pleb_, _mavr,
+ {{0x23df08d4,0x442cbf93,0x68ed3f94,0xdd140038}}, // _प्रद, hed_, _kwad, _súťa,
+ {{0x6025a31a,0x645c3f95,0x26de8081,0x6d448110}}, // едла, _fori, _atto_, _šian,
+ {{0x3e71803e,0x442cbf96,0x44383f97,0x7d0d010b}}, // _máte_, jed_, _gar_, muas,
+ {{0x442c804c,0x7d0d3d84,0x8fa2a410,0x6d428cdb}}, // ded_, luas, лаше, _aroa,
+ {{0x4438003a,0xbb480077,0x9f45826f,0x645c0102}}, // _zar_, _تلفن_, _celá_, _zori,
+ {{0x645c026b,0xddc2a7aa,0x44383f98,0x38b60aa2}}, // _yori, _ploš, _yar_, lære_,
+ {{0xe4518013,0x765d3f99,0x6aa40257,0x645c3f9a}}, // اضة_, _losy, _skif, _xori,
+ {{0x7c3883bf,0x68ed38b3,0x6d42bf9b,0x09c80d72}}, // _davr, _awad, _eroa, रख्य,
+ {{0x7d0d0590,0x644ab21a,0xdb1e81d0,0x68ed3f9c}}, // kuas, _enfi, áván, _bwad,
+ {{0x764b8065,0x4423136f,0x6d428087,0x3e6e008b}}, // _ingy, _obj_, _groa, _nýta_,
+ {{0x60c88201,0x2bdf0305,0x442cadc0,0xddc28d2f}}, // _xidm, _प्ला, ced_, _uloš,
+ {{0x7c2380ee,0x7d1bbf9d,0x765d010c,0x68ed3f9e}}, // _ibnr, ltus, _bosy, _ewad,
+ {{0x7c3882a5,0x998d012b,0x7ea58228,0x44383f9f}}, // _zavr, žeš_, cópt, _sar_,
+ {{0x44383fa0,0xeb1f80cf,0x62863fa1,0x645c3fa2}}, // [2ca0] _par_, _बहुत_, miko, _pori,
+ {{0x62860ef1,0x69dbbfa3,0x7d1b8009,0x645c01b4}}, // liko, _afue, itus, _qori,
+ {{0x44383fa4,0x020692bc,0xb5fb3fa5,0x645c3fa6}}, // _var_, езен, _clás, _vori,
+ {{0xe29799b8,0xfce604a9,0x62863fa7,0x442cbfa8}}, // нах_, _помо, niko, zed_,
+ {{0x44381100,0xa96796d9,0x216790ca,0x645c15a4}}, // _tar_, ница_, ници_, _tori,
+ {{0x9f4301ac,0x62863fa9,0xdfcf80f7,0x29068102}}, // vajú_, hiko, ميه_, rroa_,
+ {{0x62863faa,0x442c8051,0x6e398420,0x7bce3fab}}, // kiko, ved_, _bawb, _igbu,
+ {{0x7c38803a,0xb65b0158,0x6d42bfac,0x442cbfad}}, // _savr, ִדיש, _proa, wed_,
+ {{0x442c804c,0x62863fae,0xb5fb3faf,0x2dd780f7}}, // ted_, diko, _klár, ربية_,
+ {{0x6aa280c9,0x99808110,0x9f4300e1,0x764bbfb0}}, // loof, žių_, rajú_, _engy,
+ {{0x442cbfb1,0x62863fb2,0x141b00be,0x68ed337a}}, // red_, fiko, הויב, _swad,
+ {{0x442c8434,0x79a70c9d,0x62863fb3,0x2bd28fb2}}, // sed_, ерге, giko, _सलवा,
+ {{0x442c8039,0x3ea6876d,0x7bce2d10,0x7bdc03ed}}, // ped_, _ikot_, _ogbu, _ofru,
+ {{0x765d3a7f,0xd25980eb,0x7413803d,0x6aa29de6}}, // _sosy, _viņi_, _موها, hoof,
+ {{0x02a71afa,0x533495b5,0x62861b19,0xc90d801b}}, // _прим, _рейт, biko, _सम्म_,
+ {{0x68ed3fb4,0xb5fb04be,0x7bce3fb5,0x7d0d12af}}, // _twad, _alár, _agbu, tuas,
+ {{0x6e2d2305,0xe59810ac,0xbbeb01a8,0x9f4a02df}}, // [2cb0] reab, екті_, كرام_, rabá_,
+ {{0xb5fb0307,0xb4ab000f,0x7bdabfb6,0x6e2d3fb7}}, // _clár, खते_, nctu, seab,
+ {{0x68fb8024,0x229c816b,0xf99009a7,0x200101b4}}, // _svud, líky_, _نبی_, _idhi_,
+ {{0x7d0d010b,0x7bce0135,0x636b8061,0x212900ff}}, // puas, _egbu, lönö, _kpah_,
+ {{0x229c83c1,0x865a025f,0x7d0d0037,0x81c98264}}, // níky_, _חדרי, quas, লুন_,
+ {{0x62863fb8,0x38b62419,0x1ae68264,0x7afaa7d1}}, // ziko, pære_, _কিনে_, kstt,
+ {{0x201a8722,0x62860cb7,0xd00e81a8,0xdb1506c4}}, // òpia_, yiko, زلي_, _egzò,
+ {{0x62863fb9,0x7c238359,0x7d1ba2c2,0x21290d8b}}, // xiko, _sbnr, ttus, _opah_,
+ {{0xee36827e,0x44ef2358,0x26d30ba3,0x7e69bfba}}, // тны_, nż_, _luxo_, rkep,
+ {{0x44658895,0x2bb80424,0x58d48abe,0x290fbfbb}}, // твов, _अरमा, _софт, muga_,
+ {{0xe29a22a7,0x62863fbc,0x7d1ba47f,0xfbd20051}}, // дан_, tiko, stus, יתי_,
+ {{0x7ea5bfbd,0x225f86c0,0x20013fbe,0xa3b91a3b}}, // róps, _jouk_, _adhi_, _चरन_,
+ {{0x62863fbf,0x3eadbfc0,0xa56480f7,0x290fb044}}, // riko, mlet_, _مدون, nuga_,
+ {{0x3ea0931b,0x26d30118,0x291d804f,0x3ebfbfc1}}, // čite_, _buxo_, itwa_, lmut_,
+ {{0x44ef0063,0x26d301df,0x8fa600b3,0x26cb3fc2}}, // eż_, _cuxo_, таве, _bico_,
+ {{0x7bdc3fc3,0x7aed809a,0x26cb0140,0x291d86c0}}, // _sfru, _łatw, _cico_, ktwa_,
+ {{0xc5f28451,0x7d09bfc4,0x26cb1137,0x3eadbfc5}}, // [2cc0] ידן_, nres, _dico_, ilet_,
+ {{0xb4db3fc6,0x3eadbfc7,0x69c2001b,0x224d8b09}}, // _tràn, hlet_, रेणी, _anek_,
+ {{0x3eadbfc8,0x26cb3074,0x7d0981ec,0x78a3ba92}}, // klet_, _fico_, hres, donv,
+ {{0x290f8365,0x6d448110,0x5186a749,0xe8df81bc}}, // fuga_, _šiam, кула, _erọn_,
+ {{0xc05a80e8,0x6aa2bfc9,0x290fbfca,0x78a3bfcb}}, // дій_, roof, guga_, fonv,
+ {{0x7afc24a0,0xa3c392ee,0x7d09bfcc,0x80e180ab}}, // _tvrt, ्शा_, dres, _নিশ্,
+ {{0x7c3c0af9,0x644e3fcd,0xb4c0009a,0x3eadbfce}}, // _iarr, _inbi, ुदी_, flet_,
+ {{0x80e18a49,0x7d09bfcf,0x290f81d3,0x6d460267}}, // _নির্, fres, buga_, _krka,
+ {{0x7c3c3fd0,0x7d099b7d,0x291d8035,0x08c6a2f0}}, // _karr, gres, ctwa_, _абан,
+ {{0x7c3c00ad,0x62843fd1,0x225fbfd2,0x6d4601a1}}, // _jarr, _amio, _zouk_, _mrka,
+ {{0x7c3c33fe,0xd7a99a3b,0xd25102e3,0x3ead8e1c}}, // _marr, _चुपच, مند_, blet_,
+ {{0x7c3c011e,0x7d09a09c,0x6d463fd3,0x9e669d85}}, // _larr, bres, _orka, _швед,
+ {{0x9553803d,0xa2d4064a,0x316d0333,0x518401ae}}, // _نخوا, _बूस्, áez_, аура,
+ {{0x58870048,0x26d301df,0x8cd60105,0x8c0800ab}}, // тыма, _puxo_, _बढ़ो, র্তন_,
+ {{0x54338c2a,0x26cb0207,0x443c8282,0xd25980eb}}, // _ضرور, _pico_, _iav_, _ziņu_,
+ {{0x443ebfd4,0x443cbdd7,0x3cfe000f,0x644e3fd5}}, // ldt_, _hav_, _लिखे_, _anbi,
+ {{0x80dd00c8,0x443c81c5,0x27f8209b,0x2129010b}}, // [2cd0] _বিক্, _kav_, barn_, _upah_,
+ {{0x443ebfd6,0x7c3c360a,0x290f8578,0x443cbfd7}}, // ndt_, _carr, vuga_, _jav_,
+ {{0x7c3c3fd8,0x443c822c,0x3e71bfd9,0x225f807a}}, // _darr, _mav_, _láta_, _pouk_,
+ {{0x443cbfda,0x7c3c0ad0,0x6602bfdb,0x99848110}}, // _lav_, _earr, _ndok, _namų_,
+ {{0x7c3c3fdc,0x3eadbfdd,0xe8df80ff,0x443cbfde}}, // _farr, vlet_, _trọn_, _oav_,
+ {{0x443c8341,0x7c3c159d,0x290fbfdf,0x443e80f3}}, // _nav_, _garr, ruga_, jdt_,
+ {{0x28da0996,0x6d4600e1,0x290fbfe0,0x6e3d3fe1}}, // _यूनि, _zrka, suga_, _kasb,
+ {{0x443e80f3,0x3ebf8980,0x7e6d3fe2,0x224d8573}}, // edt_, umut_, nkap, _unek_,
+ {{0x3ebf9e03,0x6e3d3fe3,0x9a848b5b,0x3ead8eb9}}, // rmut_, _masb, _турл, rlet_,
+ {{0x443c8069,0x3eadbfe4,0x3e71bfe5,0x649d809f}}, // _cav_, slet_, _dáta_, nèix,
+ {{0x443c81c5,0x46158fc5,0x7e6d2450,0x27f83f88}}, // _dav_, _موار, kkap, varn_,
+ {{0x7d09bf55,0x3f7a8158,0x6e3d3fe6,0xe0c900be}}, // pres, _אָדע, _nasb, _זײ_,
+ {{0x32368158,0x81ac00ab,0x321e0035,0x443e8118}}, // נטשן_, কেন_, ęty_, bdt_,
+ {{0x40953fe7,0x443c93c2,0x81ea80ab,0xe81f001b}}, // арст, _gav_, _মাছ_, म्ला_,
+ {{0xd62a2481,0xdc9a807c,0x7c3c0041,0x61f9893d}}, // нове_, _איסל, _rarr, kawl,
+ {{0x7e6d26bc,0x7c3c3fe8,0x442780b9,0x6aa98088}}, // gkap, _sarr, _hbn_, _nkef,
+ {{0x26c03fe9,0x0c260dc0,0x443c81c5,0xa5072f4b}}, // [2ce0] rmio_, лман, _yav_, лета_,
+ {{0x443c90af,0xf54f81bc,0x7c3c27cc,0x786f83ba}}, // _xav_, _bụzi_, _qarr, _søvn,
+ {{0xa3b903eb,0x7c3c3fea,0x9f4c8388,0x6e3d01a1}}, // _चरण_, _varr, _pedí_, _fasb,
+ {{0x2ca5bfeb,0x6d460904,0x998d809a,0xe3c580ab}}, // hold_, _trka, _oceń_, _এভাব,
+ {{0x7c3c3fec,0x64430073,0x442780cd,0x6abb81b4}}, // _tarr, ônic, _obn_, _dhuf,
+ {{0x61f6395c,0x4427bb15,0x3e7181a8,0x60d602c4}}, // _leyl, _nbn_, _ráta_, _muym,
+ {{0x6d443fed,0x9f580168,0x3ea4bfee,0x4acf8c28}}, // lvia, darë_, tomt_, _संगव,
+ {{0xa3b90076,0x7ae302b7,0x443cbfef,0x9b9300f7}}, // _चरत_, ínte, _sav_, _الفت,
+ {{0x5c74a657,0x443cbff0,0x6d44009f,0x6143bff1}}, // илит, _pav_, nvia, _лета,
+ {{0xd5c085e8,0x290b00d2,0x3ea48aa2,0x443c8282}}, // शेषज, vrca_, somt_, _qav_,
+ {{0x443ebff2,0x5fa40076,0x68e40110,0x7e6d076d}}, // rdt_, _खुलल, _atid, ykap,
+ {{0x201a0197,0x673a911b,0x66028f90,0xd7f88129}}, // _acpi_, _istj, _udok, _xoăn_,
+ {{0x443cbff3,0x6d4401ac,0x2ca5bff4,0x6c548073}}, // _tav_, jvia, bold_, _вклу,
+ {{0x443cbff5,0x6d3b8039,0x9f4780e1,0xb8fd3ff6}}, // _uav_, _נתונ, vanú_, _तं_,
+ {{0xb4c08e00,0xb4c286a7,0x68e406c0,0x99990019}}, // ंगी_, ्दी_, _etid, reső_,
+ {{0x58d50021,0xe80e8996,0x4ea70048,0x228b026b}}, // _коит, _साझा_, урга, _dòkè_,
+ {{0xb4db0722,0xb8049199,0x260a03db,0x6e3d342b}}, // [2cf0] _tràm, रणाम_, िलजी_, _vasb,
+ {{0x7870016d,0x7e6d3ff7,0x673a82f1,0x6e3d1412}}, // _jävl, skap, _ostj, _wasb,
+ {{0x6e3d00ee,0xe7eb016f,0x649d809f,0xd4983ff8}}, // _tasb, _ज्या_, rèix, урт_,
+ {{0x39493ff9,0x6e3d008e,0xb4c2bffa,0x61f6011c}}, // _kras_, _uasb, ्दु_, _xeyl,
+ {{0x61f9ba65,0x673a8609,0xc5fa01c6,0xa3ab801b}}, // rawl, _astj, _בפרט, _कुन_,
+ {{0x216684ae,0x1bd40cb1,0x7d0d33a0,0xa3d401cf}}, // риши_, _горя, mras, _горч,
+ {{0x6fcc80d4,0x21de819d,0x2ca593c2,0x9f5803ed}}, // ाशगं, _ịhe_, vold_, varë_,
+ {{0x39492e91,0x0e658c9b,0x7e6285d8,0x2ca5bffb}}, // _oras_, ркин, _coop, wold_,
+ {{0x7ae48025,0xb4c2a701,0x7d0d2993,0x9f5800f1}}, // _čita, ्दू_, nras, tarë_,
+ {{0x3ea080d2,0x4b259957,0xac859bcc,0x4c85a481}}, // čita_, амов, ргол, рлов,
+ {{0x68e40052,0xeb999017,0x39493ffc,0x7d0d3ffd}}, // _stid, тин_, _aras_, hras,
+ {{0x80dd0a49,0xfaa62bd9,0x628b8428,0x39493ffe}}, // _বিজ্, рамо, nigo, _bras_,
+ {{0xe29719dd,0x9f583fff,0x201a03a8,0x61e90118}}, // рая_, maré_, _pcpi_, ñela,
+ {{0x7870016d,0xd4668b30,0x394930b8,0x628bc000}}, // _gävl, _више_, _dras_, higo,
+ {{0xb8fd1551,0xe7e50b9f,0x39492620,0x628b8545}}, // _तू_, कड़ा_, _eras_, kigo,
+ {{0x7e628609,0xddc9179d,0x63ae06e1,0x26cf84b9}}, // _xoop, _šošt, _rybn, _jigo_,
+ {{0x7d0d4001,0x291f81c0,0x39490f7c,0xac860cf9}}, // [2d00] gras, _nqua_, _gras_, агал,
+ {{0xd12f00d5,0x26cfc002,0x0eb8016f,0x9f580144}}, // _کمی_, _ligo_, _आठवड, haré_,
+ {{0x2bab8592,0x2abc8e1b,0x7d0d1581,0xdddd0019}}, // _घुमा, híbe_, aras, _első,
+ {{0x9f4c826f,0x628baa96,0x6441808e,0x6da6004a}}, // _nedá_, gigo, hdli, _вима,
+ {{0x44e98510,0x7d0d1a2e,0x22402eac,0x290083a7}}, // _nº_, cras, _maik_, éia_,
+ {{0x20184003,0x26cf82f7,0x37069cf8,0x403495b7}}, // ngri_, _aigo_, ачив, секс,
+ {{0x628bc004,0x26cfc005,0x291f80e5,0x9f580580}}, // bigo, _bigo_, _equa_, faré_,
+ {{0xf2a38e8e,0x22400483,0x644182af,0x26cf882c}}, // _дисп, _naik_, edli, _cigo_,
+ {{0x26cfa6d5,0xd5b78987,0x61ed86a5,0xf8bf0866}}, // _digo_, ись_, ñale, ltée_,
+ {{0xb4c08063,0x8c668012,0xceea80e8,0xb4c28128}}, // ंगे_, ртид, _адже_, ्दे_,
+ {{0x22401a67,0x3e75016d,0x39468110,0x225209ca}}, // _baik_, _låta_, lvos_, _bnyk_,
+ {{0xc5f080c8,0xbd878416,0x394900d7,0x1bf283a4}}, // _টাকা_, انین_, _pras_, _आजकल_,
+ {{0x787000f2,0xcd36826a,0x224000b9,0x3946802a}}, // _tävl, _خراب, _daik_, nvos_,
+ {{0x83f89c79,0x533483c7,0x27fc83ba,0xe9df4006}}, // _секс_, _гект, navn_, scú_,
+ {{0x1cbb8158,0x44333075,0x06fd81d0,0x22404007}}, // _שמוע, vex_, žívá_, _faik_,
+ {{0x3949040e,0x27fc8edd,0xaa2d0198,0x2c190072}}, // _tras_, havn_, ämää_, _पाहू_,
+ {{0xddc40289,0x61ef00b9,0xdb1c0035,0x6d4002d4}}, // [2d10] tkiš, mbcl, _ogró, _šmar,
+ {{0xe1ff0035,0xc3cb01a8,0x61fd208e,0xeaca0129}}, // tków_, نظام_, lasl, _hẹp_,
+ {{0x44334008,0x628b8e67,0x389c098a,0x1da72730}}, // rex_, tigo, _ביזנ, _कुरत,
+ {{0x7d0d4009,0xde590221,0xa3e68eed,0x7d028654}}, // pras, _самі_, युत_, _ovos,
+ {{0x394982ba,0x3ce6822c,0x26cf87e4,0x0dcb00a9}}, // ías_, _ntov_, _rigo_, _јуни_,
+ {{0x26cfc00a,0x628bc00b,0xb4c2801b,0xb4c091be}}, // _sigo_, sigo, ्दो_, ंगो_,
+ {{0xb4c283bb,0x61fd011e,0x261b816f,0x9f58400c}}, // ्दै_, kasl, _यादी_, taré_,
+ {{0xee39998c,0x201809d1,0x2f9801c6,0xb4db0706}}, // уни_, zgri_, יכון_, _arài,
+ {{0x26cf87f4,0x656d400d,0x88078c2b,0x6289c00e}}, // _vigo_, nyah, _نظام, _imeo,
+ {{0x7d02811f,0x6aad400f,0x3e7181ac,0x101781f9}}, // _dvos, _akaf, _táto_, _خبرد,
+ {{0x6441c010,0x69cb9c7b,0xa25b0187,0x9f584011}}, // rdli, _तृती, rmôn, paré_,
+ {{0xc869093f,0x2721ad11,0x6f154012,0x625a33e4}}, // _אן_, món_, duzc, урор_,
+ {{0x272188a4,0xa5098b9c,0x2c6980e1,0xeaca0129}}, // lón_, _села_, _súdu_, _dẹp_,
+ {{0x645cc013,0xb4db051e,0x656d0df6,0x37e301a1}}, // örit, _grài, dyah, зорг,
+ {{0x2721862f,0x628981e8,0x224000ee,0xb21b006a}}, // nón_, _omeo, _taik_, dhæf,
+ {{0x442a4014,0x61fd0279,0xaad006b7,0x27218118}}, // _sbb_, casl, _सूचक, ión_,
+ {{0x442a0886,0x61e42c9a,0xed574015,0x2721c016}}, // [2d20] _pbb_, _ifil, бот_, hón_,
+ {{0x98a90110,0x6289aef4,0xa3ab8853,0xe09e8135}}, // _ypač_, _ameo, कअप_, kọọ_,
+ {{0x29024017,0x2721c018,0xf8bf0036,0x656d2676}}, // lska_, jón_, utée_, ayah,
+ {{0x27218c58,0x786b0085,0xd9fa03eb,0xf1fa8124}}, // dón_, _qüvv, ्णित_, اعات_,
+ {{0x19588fe6,0x186a8bc7,0xceb40085,0x656d00b4}}, // ралы_, лади_, mlə_, cyah,
+ {{0xfce34019,0x2721bfe5,0x76440085,0x29020079}}, // _хоро, fón_, ldiy, iska_,
+ {{0x6d4bc01a,0x3946c01b,0x272182ba,0x4444401c}}, // _arga, svos_, gón_, md_,
+ {{0x22968013,0x3ea0803b,0x7644401d,0x329681a8}}, // _الرس, čito_, ndiy, _الرأ,
+ {{0x75243724,0x2d80401e,0x444419c2,0x26da401f}}, // ntiz, nzie_, od_, _lupo_,
+ {{0xd6d28872,0xb865af0a,0x290202a5,0x2721c020}}, // _فقط_, _والو, dska_, bón_,
+ {{0x6d4bc021,0x27218d76,0xceb40085,0xae209a3b}}, // _erga, cón_, klə_, _यापन_,
+ {{0x6d4481e2,0x44444022,0x9c8701d0,0x6d898019}}, // _šiau, hd_, _kočá, امیہ_,
+ {{0x0c738077,0x61e4002a,0xd0418085,0x76444023}}, // زدید, _dfil, _ailə, ddiy,
+ {{0x2d800d38,0x69d60117,0xd0418086,0x44440613}}, // dzie_, _egye, _bilə, jd_,
+ {{0x44442065,0x1bfb8051,0x93fb810f,0xaae68bbe}}, // dd_, _גלוב, _גלוי, _استو,
+ {{0x44444024,0x5f94138c,0x656d056a,0xa19514ed}}, // ed_, чист, tyah, _майч,
+ {{0x2721840e,0x9f47826f,0x44440a0f,0x2d58245b}}, // [2d30] zón_, daný_, fd_, бить_,
+ {{0x44444025,0x80dc809a,0x656d0079,0xd37b0f13}}, // gd_, _पढ़े, ryah, лче_,
+ {{0x272181df,0x45d51775,0xb5fd807a,0x57fa01c6}}, // xón_, _монс, ljše, _דלתו,
+ {{0x44444026,0x7ae480ce,0x656d16fb,0x61e281f6}}, // ad_, _čitl, pyah, gcol,
+ {{0xae1a0158,0xd5a689a7,0x444402f7,0xb5fd82d4}}, // _דורכ, _آف_, bd_, njše,
+ {{0x2721988b,0xd7fb8037,0x26da004f,0x628f0493}}, // tón_, ууд_, _yupo_, hico,
+ {{0x64554027,0x320901b4,0x628f34d3,0x7e7b8234}}, // _inzi, _oday_, kico, thup,
+ {{0x272188a9,0x29020079,0x644502a3,0x628f0503}}, // rón_, yska_, ldhi, jico,
+ {{0x6499840d,0x2721988b,0xb5fb4028,0xec79964f}}, // итор_, són_, _bláz, ипи_,
+ {{0x64454029,0x27218511,0x61fbc02a,0x61e4063f}}, // ndhi, pón_, _heul, _sfil,
+ {{0xe82009a3,0x628f1313,0x76440201,0x68f60355}}, // _बाबा_, fico, ydiy, _swyd,
+ {{0x2902012b,0xd5759a8f,0x60db9e08,0x628f402b}}, // tska_, _муль, _kuum, gico,
+ {{0x444402a3,0x787004a2,0xddc2c02c,0x26da0216}}, // yd_, _hävi, _zlož, _supo_,
+ {{0x60dbc02d,0x26da04be,0x38b6006a,0x78700198}}, // _muum, _pupo_, mærk_, _kävi,
+ {{0x61e29c33,0xceb40085,0x6d49c02e,0x4444004a}}, // ycol, tlə_, lvea, vd_,
+ {{0x6455402f,0x75244030,0xee3a09a5,0x61fbc031}}, // _anzi, ttiz, јна_, _neul,
+ {{0x6443c032,0x9f478a56,0xa3c383b7,0x76440201}}, // [2d40] _kani, vaný_, ्शक_, rdiy,
+ {{0x3e78c033,0x6299867f,0xa3bd8424,0x44443ce8}}, // _méta_, enwo, _आरा_, ud_,
+ {{0x673e030b,0x75244034,0x61fb851e,0x213f8c68}}, // _uspj, stiz, _beul, _asuh_,
+ {{0x44444035,0x75241c8c,0x64554036,0x6443c037}}, // sd_, ptiz, _enzi, _lani,
+ {{0x81ac00c8,0x61e2c038,0x394dc039,0x61ed85a4}}, // কের_, rcol, _cres_, ñala,
+ {{0x644391df,0x61e2c03a,0xb902aa85,0xd90d803d}}, // _nani, scol, _नं_, ایل_,
+ {{0x394d9726,0x25e10076,0xe3a7819f,0xdfcf80f7}}, // _eres_, _कलमी_, _آر_, نيه_,
+ {{0xddc29807,0x628f403b,0x61fbc03c,0x39400f29}}, // _vlož, xico, _geul, _osis_,
+ {{0x7d04403d,0x64438af5,0x261b8054,0xd90d8fd3}}, // nsis, _bani, _यारी_, _مین_,
+ {{0x6443c03e,0x7d04403f,0x2246c040,0xe46a891c}}, // _cani, isis, ldok_, ршил_,
+ {{0xddc287df,0x628f0888,0x39400084,0x9f5c84e8}}, // _ulož, tico, _asis_, kavé_,
+ {{0x7d044041,0xd3778110,0x0dca8e17,0xdb214042}}, // ksis, ючы_, _кули_, étét,
+ {{0x628f4043,0x21268867,0x66008d92,0x394d1e09}}, // rico, ntoh_, mamk, íes_,
+ {{0x628f25a7,0x6443c044,0x66008079,0xfbcf82e3}}, // sico, _gani, lamk, عتی_,
+ {{0x368b2748,0x628f1820,0x81ac00ab,0x44e0c045}}, // асан_, pico, কেল_, _hò_,
+ {{0x44e0c046,0xcb138bea,0x7c2e4047,0x66008010}}, // _kò_, _חלק_, _abbr, namk,
+ {{0xd90489d7,0x7d0402b8,0x95cb0323,0x628d0b80}}, // [2d50] _جی_, gsis, рува_, _imao,
+ {{0x44e0c048,0x61fb82be,0x60dba1e8,0x660095e8}}, // _mò_, _seul, _ruum, hamk,
+ {{0x44e0c048,0x68e993e1,0x3e7180f7,0x64454049}}, // _lò_, _sted, _fáth_, rdhi,
+ {{0x9f45803e,0x660089ca,0x7c2e0ab3,0xb4db051e}}, // _celý_, jamk, _ebbr, _bràt,
+ {{0x2a69404a,0x61fba5d2,0x44e083ec,0x6600c04b}}, // _moab_, _veul, _nò_, damk,
+ {{0xbca500f7,0x68e980e1,0x38b6006a,0x3e7c01d0}}, // _رمزي, _vted, værk_, _víte_,
+ {{0xf7718bbe,0x644382a5,0x2cac831d,0x61fbc04c}}, // عات_, _rani, dodd_, _teul,
+ {{0x394da84c,0x44e0c04d,0xe1ff8125,0x2919404e}}, // _tres_, _bò_, _þó_, kusa_,
+ {{0x6443c04f,0x44e0c050,0x7982809a,0x2cac831d}}, // _pani, _cò_, czow, fodd_,
+ {{0x44e090ab,0x629d087a,0x628d0133,0xc48283c7}}, // _dò_, éron, _amao, ельк,
+ {{0xf1a90159,0x3e718324,0x22920110,0xa3ab9513}}, // _עס_, _láti_, eška_, _कुश_,
+ {{0x644395bd,0xe29a108d,0x44e0c051,0x394002f7}}, // _wani, сам_, _fò_, _psis_,
+ {{0x44e09b4c,0x2eaa8117,0x6443c052,0xbbb8000d}}, // _gò_, _اپنی_, _tani, _अर्क,
+ {{0xf09f3c3f,0x26c20168,0xb87b00e1,0x15429092}}, // dràs_, _shko_, dpís, _чешм,
+ {{0x1fb523d7,0x38ca0019,0xeb96911c,0x9f5c936f}}, // дстр, لاڑی_, _ниш_, tavé_,
+ {{0x3940146a,0x29194053,0x3e6e007b,0x7d044054}}, // _tsis_, busa_, _nýtt_, tsis,
+ {{0x29193340,0x9f5c816b,0xed574055,0x44e080e5}}, // [2d60] cusa_, ravé_, пот_, _xò_,
+ {{0x7d044056,0x3d13a743,0xf5070e49,0x99dd80e1}}, // rsis, _दिने_, янул_, _raňa,
+ {{0x9f45c057,0x2baf06a7,0xb4db026b,0x7ae48140}}, // _meló_, _जुदा, _bràs, _čitk,
+ {{0x81ac00ab,0xfaa385a8,0xf8bf4058,0x3202016b}}, // কেঃ_, нахо, ntén_, naky_,
+ {{0x2fda00b9,0x6d4f00c3,0x9f58023e,0xb4db01e4}}, // _kgpg_, _grca, larà_, _dràs,
+ {{0x44e08324,0x38b6007b,0x79828035,0x55bb01c6}}, // _rò_, færi_, rzow, _למכו,
+ {{0x44e0c059,0xa3ab80a5,0x9f580722,0x76462f76}}, // _sò_, _कुल_, narà_, _kaky,
+ {{0x44e0c05a,0x443a405b,0x3202016b,0x7e6402f7}}, // _pò_, lep_, jaky_, njip,
+ {{0x2919036a,0xa3c9800c,0x7646405c,0x1dbe0743}}, // xusa_, लेस_, _maky, ्धित,
+ {{0x628d0ad0,0x443a2190,0xa3e6864a,0x2292007a}}, // _smao, nep_, युं_, vška_,
+ {{0x59a51055,0xd25102e3,0x44e0877f,0x200c8114}}, // _गुजर, نند_, _wò_, _iddi_,
+ {{0x44e090ab,0x443a405d,0x9f580722,0x76460c2e}}, // _tò_, hep_, darà_, _naky,
+ {{0x443a37c0,0x442ec05e,0xc05b00e8,0x41c70035}}, // kep_, _tbf_, бів_, रेंस,
+ {{0x2919405f,0x443a02ce,0xdd3b8039,0x764600e4}}, // rusa_, jep_, _לעדכ, _aaky,
+ {{0xb5fd84c4,0x64588ca9,0x3ae18028,0x6d4f00d2}}, // ljša, _invi, _góp_, _srca,
+ {{0xfbb78051,0x81ac00ab,0x29192fdb,0x26de8f8e}}, // _מפות_, কেই_, pusa_, _muto_,
+ {{0x2d580364,0x26dec060,0xb5fd807a,0x76463a20}}, // [2d70] пить_, _luto_, njša, _daky,
+ {{0x443a003d,0x6448c061,0x9f580722,0xf09f023e}}, // gep_, nddi, barà_, rràs_,
+ {{0x20034062,0x9f580722,0x64488114,0x61ed8333}}, // maji_, carà_, iddi, ñalo,
+ {{0x2ea8035a,0x6d4f02a5,0x63b5026f,0x7c3ac063}}, // ककृत, _trca, _vyzn, hetr,
+ {{0xa06994bc,0x9f581984,0x7c3a81a3,0x6d4f0162}}, // жала_, mará_, ketr, _urca,
+ {{0x26dec064,0x9f58018a,0x443a4065,0x20034066}}, // _buto_, lará_, cep_, naji_,
+ {{0x60df036e,0x6d42802a,0xa6c98572,0x442300ee}}, // _luqm, _asoa, олка_, _icj_,
+ {{0x9f581f5c,0x6447054e,0x20034067,0x442c81ed}}, // nará_, _haji, haji_, efd_,
+ {{0x64471600,0x6d4d18c7,0x20034068,0x61e9846d}}, // _kaji, nvaa, kaji_, _ifel,
+ {{0x7c3ab66b,0x20034069,0x6447406a,0xd90d8019}}, // getr, jaji_, _jaji, _میچ_,
+ {{0x6447406b,0x29090364,0xe8171c3b,0x68ed406c}}, // _maji, _avaa_, _ताजा_, _atad,
+ {{0x6458c06d,0x6447406e,0xdd8e826a,0x9f58406f}}, // _envi, _laji, اوی_, jará_,
+ {{0xda1f1499,0x76460455,0xe8f70554,0x9f582a63}}, // _भारत_, _raky, мля_, dará_,
+ {{0x64470c9e,0xf8bf02b7,0x9f580722,0x20034070}}, // _naji, stén_, tarà_, gaji_,
+ {{0x4aad8076,0x68ed4071,0xa3c9858c,0x7529b905}}, // टकाव, _etad, लेश_, ltez,
+ {{0x9f580a3e,0x44230176,0x7649c072,0xe8204073}}, // gará_, _acj_, ndey, _बावा_,
+ {{0x443a37c0,0x20034074,0x7529b349,0xdb0e001b}}, // [2d80] tep_, baji_, ntez, _vybí,
+ {{0x69dbc075,0x7e7d0362,0xfbd2030f,0x9f58023e}}, // _ague, _elsp, نتا_, parà_,
+ {{0x443a4076,0x76464077,0xaec60ff0,0x9f581984}}, // rep_, _taky, _обол, bará_,
+ {{0x9f580acf,0x443a292a,0xe297a434,0x99dd826f}}, // cará_, sep_, мах_, _daňo,
+ {{0x6448a065,0xd1308416,0x66043842,0x7649c078}}, // yddi, امت_, maik, ddey,
+ {{0x66044079,0x2906862f,0xd6ea8ae7,0x64471cc5}}, // laik, rsoa_, офил_, _gaji,
+ {{0x29068073,0x7bdc407a,0xdfd081a8,0x7c3ac07b}}, // ssoa_, _igru, ديث_, vetr,
+ {{0x2003022e,0x660416d5,0x667b00be,0xc9568364}}, // zaji_, naik, ַװיג, _отзы,
+ {{0x9f5e80f1,0x6447407c,0x6f1c3aaa,0x2d848289}}, // _ketë_, _yaji, hurc, uzme_,
+ {{0x9f5e820f,0x9f58407d,0x3e78800d,0x26de801b}}, // _jetë_, zará_, _této_, _tuto_,
+ {{0xd62a9289,0x66042bc6,0x6448c07e,0xa3c98072}}, // _може_, kaik, rddi, लेल_,
+ {{0x6f1c02af,0x7d1bc07f,0x6282aeee,0x2003004f}}, // durc, buus, nhoo, waji_,
+ {{0x941e023c,0x7c3ac080,0x2003022e,0x9f582a63}}, // _पांच_, petr, taji_, vará_,
+ {{0xddcd10d3,0xc05801b5,0x656f81ec,0x7bdc003d}}, // jkaš, міс_, äche, _ngru,
+ {{0x9f581313,0x64474081,0x26c6c082,0xe8202a85}}, // tará_, _raji, _khoo_, _बारा_,
+ {{0x4ea418a0,0x7bdc2e88,0x64471eb3,0x66044083}}, // вруа, _agru, _saji, gaik,
+ {{0x9f581fd1,0xdd9401bb,0xa5bb0118,0x64470168}}, // [2d90] rará_, ваты, _ecón, _paji,
+ {{0x31560051,0xf5488135,0x6d4d3768,0x9f582a63}}, // _ניתן_, _mụ_, rvaa, sará_,
+ {{0x66040867,0x9f5826d5,0xf8bf0073,0xa5bb00f7}}, // baik, pará_, ntém_, _gcón,
+ {{0xb4ca84e5,0xfd5e80ff,0x387e8037,0x6d4d090d}}, // लगु_, _quyể, _altr_, pvaa,
+ {{0x224901d8,0xc6160051,0x50b581bb,0x801807d2}}, // _maak_, _אחרי_, нску, _عزیز_,
+ {{0x26d94084,0x7d1b862c,0x2b430980,0x9f5e826b}}, // _hiso_, vuus, _tsjc_, _ketè_,
+ {{0xfd5e8028,0x7afc0114,0x38b603ba,0x9f5c84e8}}, // _tuyể, _gwrt, mært_, baví_,
+ {{0x26c6c085,0xf548b5b0,0x06098d5f,0x7d1b82ec}}, // _choo_, _bụ_, чник_, tuus,
+ {{0xf5488028,0xdc3a8201,0x2a7f8420,0x0c95add0}}, // _cụ_, _açıq, _ilub_, ешня,
+ {{0x7529c086,0x2a7f81e9,0x6ac800d4,0x291dc087}}, // rtez, _hlub_, रग्र, muwa_,
+ {{0x44211523,0x7d1b9572,0xa3c98740,0xe8d68039}}, // igh_, suus, लें_, _נוער_,
+ {{0xbbdc000f,0x26d90353,0x395201b0,0x25a0811a}}, // _बल्क, _niso_, _prys_, _žile_,
+ {{0xf548aae3,0xaca381bc,0x66040d15,0xb5fd81a1}}, // _gụ_, _azụk, vaik, ljšo,
+ {{0x660401bc,0xd11c1344,0x2a6dc088,0xf1b9816b}}, // waik, भूषण_, _loeb_, _myš_,
+ {{0xc7b2893f,0x6ab630bc,0x7d098bc5,0x2baf00a5}}, // ָבן_, _псих, lses, _जुला,
+ {{0x291da0d4,0x9f5c1c86,0x68339010,0x7bc38024}}, // kuwa_, _því_, lıdı, _iznu,
+ {{0x26d94089,0x6604408a,0xafe60391,0xf8af8beb}}, // [2da0] _diso_, raik, _попл, टवाय,
+ {{0x6604408b,0x6457a6d5,0x7d098009,0x224900f3}}, // saik, _óxid, ises, _zaak_,
+ {{0xddcd0067,0x9f5ec08c,0x6604408d,0x3ce506a7}}, // rkaš, _vetë_, paik, _झूठे_,
+ {{0x09e6b38c,0x7d09c08e,0x2366003a,0x442101e4}}, // ходн, kses, ćoj_, agh_,
+ {{0x6282ba61,0x941e0076,0xaca40135,0x9f5c83fb}}, // rhoo, _पाऊच_, _abụr, raví_,
+ {{0x6d46408f,0x3b0a14b7,0x6282c090,0xe3b10117}}, // _iska, _него_, shoo, ارے_,
+ {{0x64a32028,0x645c4091,0x3ea00168,0x26c681c0}}, // _сара, _inri, onit_, _phoo_,
+ {{0x3ea01af3,0x291d84b9,0xfd55019d,0x9405811c}}, // nnit_, buwa_, _nkaọ, milə_,
+ {{0x6280825d,0x2249079f,0x3ea0372f,0xdd90803d}}, // _ilmo, _raak_, init_, شود_,
+ {{0xf5488104,0x224901b0,0xfd550133,0x6386c092}}, // _vụ_, _saak_, _akaọ, _néné,
+ {{0x69c40698,0x7ae92828,0xa5bb0118,0xfd4981bc}}, // _azie, _četk, _acól, _kalị,
+ {{0x6d464093,0x09d480c8,0x7d0982f7,0xf54880ff}}, // _oska, _হ্যা, bses, _tụ_,
+ {{0x22490a0f,0xf8bf027f,0x638682be,0x26d93d36}}, // _vaak_, stém_, _béné, _riso_,
+ {{0x69c44094,0x3ea0825b,0x638689c4,0xf1c70035}}, // _dzie, čitu_, _céné, रेगन,
+ {{0x443e83ed,0x6d464095,0x26d926de,0x60da83e3}}, // met_, _aska, _piso_, _mitm,
+ {{0x644ac096,0x2001010c,0x291d80a4,0x62670b8c}}, // _hafi, _wehi_, yuwa_, _سابق,
+ {{0x644ac097,0x26d90611,0x7c3e01f4,0xdb0e026f}}, // [2db0] _kafi, _viso_, fepr, _rybá,
+ {{0x638683d3,0x44210051,0x7d1d8722,0x64848014}}, // _géné, ugh_, àssi, _dòig,
+ {{0x6d4600ad,0x7f4500f1,0xa3c2b852,0x41ee819d}}, // _eska, _ushq, ्धि_, _ịsị_,
+ {{0x443ec098,0xa3cd0b6f,0x644a80a4,0x291dc099}}, // het_, शेष_, _lafi, tuwa_,
+ {{0x443ec09a,0x765d07d5,0xfe79800d,0xd9458abe}}, // ket_, _insy, vků_, тели,
+ {{0x443ec09b,0x22498025,0xa3c28894,0x291dc09c}}, // jet_, žak_, ्धा_, ruwa_,
+ {{0x443e920b,0xb4bd0b9f,0x752d0211,0x291dc087}}, // det_, आती_, ltaz, suwa_,
+ {{0x764d409d,0x81ac00ab,0x644a846d,0x7d098074}}, // nday, কেট_, _aafi, tses,
+ {{0x752d2579,0x443ec09e,0x644ac09f,0x290b009a}}, // ntaz, fet_, _bafi, jsca_,
+ {{0x443e8a33,0x60da8214,0x6ab62a33,0xf9908199}}, // get_, _gitm, _skyf, _طبق_,
+ {{0x2007c0a0,0x5f798077,0x8c3c87d9,0x03a5818b}}, // mani_, _تماس_, toğr, тино,
+ {{0x20079fb6,0x752d0695,0x7c3e009a,0x7d09c0a1}}, // lani_, ktaz, zepr, pses,
+ {{0x443ec0a2,0x764d0ec8,0x644ac0a3,0xb4db026b}}, // bet_, dday, _fafi, _asàm,
+ {{0x2007c0a4,0x764b8065,0x7e6985f3,0x62888110}}, // nani_, _hagy, ljep, _įdom,
+ {{0x764bc0a5,0x66e604ae,0x290b1947,0xe7271290}}, // _kagy, тоба, asca_, корд_,
+ {{0x644ab6ba,0x764d179b,0x7e7b8168,0x7e69beb6}}, // _zafi, gday, nkup, njep,
+ {{0x764b8117,0x2007c0a6,0x7c3e40a7,0x3ea040a8}}, // [2dc0] _magy, kani_, tepr, rnit_,
+ {{0x7ae3c0a9,0x644a8079,0x3ea040aa,0x711b00be}}, // _kunt, _xafi, snit_, _קויפ,
+ {{0x290d8012,0x7ae3c0ab,0x7c3e3665,0x68e4038e}}, // _avea_, _junt, repr, _huid,
+ {{0x764b8117,0x7ae3c0ac,0x443ec0ad,0x68e40006}}, // _nagy, _munt, zet_, _kuid,
+ {{0x443ec0ae,0x2007c0af,0x94058085,0x6d463e74}}, // yet_, fani_, silə_, _uska,
+ {{0x443e40b0,0x68e4043d,0xf1a9803d,0x6602c0b1}}, // _út_, _muid, رانه_, _seok,
+ {{0x443e8370,0x644a8c9e,0x7ae3c0b2,0x764bad80}}, // vet_, _rafi, _nunt, _bagy,
+ {{0x6e2440b3,0x443ec0b4,0xf8bf0019,0x7e7babb9}}, // ngib, wet_, tték_, gkup,
+ {{0x7ae9003b,0x443e8e79,0x20079fe5,0x48e6835f}}, // _četi, tet_, bani_, _розв,
+ {{0x7ae3c0b5,0x60da8cfa,0xa3d7047d,0x443ec0b6}}, // _bunt, _uitm, ाखच_, uet_,
+ {{0x443ec0b7,0x8c1b0158,0x7ae39b96,0x8fa38bc7}}, // ret_, וויי, _cunt, _кафе,
+ {{0x443ebe74,0x68e40014,0x92580009,0x644aa647}}, // set_, _buid, вают_, _wafi,
+ {{0x68e410eb,0x443e85a0,0x644a80a4,0x61ed8511}}, // _cuid, pet_, _tafi, ñali,
+ {{0xa8a440b8,0x1c1e0592,0xcb548b76,0x443e820f}}, // арск, _पागल_, _منتظ, qet_,
+ {{0xd6db130f,0x764d40b9,0x290b01a1,0x6b8d8326}}, // дта_, tday, ssca_, _ƙage,
+ {{0x34db035a,0x752d40ba,0x0bb78039,0x6e2423ff}}, // बद्द, ttaz, _שלהם_, ggib,
+ {{0x2007c0bb,0x68e4114e,0x51870d8e,0xb6040249}}, // [2dd0] yani_, _guid, _шука, ряск,
+ {{0x752d40bc,0x1acb8eed,0x041480c8,0x63bc40bd}}, // rtaz, िष्ठ, ত্রী_, _byrn,
+ {{0x2007c0be,0x7ae387f4,0x752d40bf,0x68e400f3}}, // vani_, _xunt, staz, _zuid,
+ {{0x2007c0c0,0xf67900be,0x3d13816f,0x657200f1}}, // wani_, _נאָמ, _दिले_, ëdhë,
+ {{0x2007c0c1,0x7640c0c2,0x66ea8035,0x9564047f}}, // tani_, demy, _ręka, _кърд,
+ {{0xfaf30277,0xb4ac801b,0x7d0d40c3,0x66d58084}}, // _نثر_, कको_, msas, _iški,
+ {{0x2007c0c4,0x61e49601,0x3e750687,0x442797e9}}, // rani_, žila, _fått_, _rcn_,
+ {{0x7ae3c0c5,0x3e750448,0xdd918065,0xdb258019}}, // _runt, _gått_, یوں_, épít,
+ {{0x7ae3c0c6,0x764bc0c7,0x7e7b9abf,0x78b50503}}, // _sunt, _vagy, rkup, kozv,
+ {{0x68e421fd,0x3a2900dd,0x1abe020e,0x764b85ee}}, // _ruid, _acap_, ोष्ठ, _wagy,
+ {{0x394940c8,0x68e440c9,0xb0b680be,0xe29a1b23}}, // _asas_, _suid, _עפעס_, еан_,
+ {{0x68e42cc5,0x2005b241,0xad9b04f0,0x7d0d40ca}}, // _puid, _heli_, _reún, ksas,
+ {{0x2005c0cb,0x78b501a1,0x64848362,0xc871826b}}, // _keli_, fozv, _dòib, _ko̟_,
+ {{0x7ae3c0cc,0x3ebf8364,0x2005811f,0x628600f1}}, // _tunt, llut_, _jeli_, shko,
+ {{0x394940cd,0xe29a1927,0xa3c98778,0x2005af8c}}, // _esas_, _пак_, लेख_, _meli_,
+ {{0x6441c0ce,0x224d8b99,0x44e990ab,0x20058234}}, // leli, _naek_, _kú_, _leli_,
+ {{0x44e9c0cf,0xa3e7a701,0x7d0d02b8,0x6284400e}}, // [2de0] _jú_, _भला_, gsas, _ilio,
+ {{0x6441c0d0,0x44e9877f,0x629d026f,0x7640809a}}, // neli, _mú_, érov, zemy,
+ {{0x44e99ca9,0x224d90e4,0x25ab40d1,0x6609a43c}}, // _lú_, _baek_, _excl_, kaek,
+ {{0xa3ab9499,0x64418a92,0x200a3368,0x60de008e}}, // _कुछ_, heli, labi_, _hipm,
+ {{0x44e9c0d2,0x2005c0d3,0xd378809a,0x7d0d40d4}}, // _nú_, _beli_, zyć_, csas,
+ {{0x7ae9003b,0x6441c0d5,0x200a2fc2,0xf8b40b9f}}, // _četv, jeli, nabi_, ंकिय,
+ {{0x64418de8,0xd6da86a1,0x2005c0d6,0x09b580ab}}, // deli, нти_, _deli_, জেলা,
+ {{0x44e9c0d7,0x644e05d8,0x26dd8300,0x26c000e5}}, // _bú_, _habi, _ciwo_, nlio_,
+ {{0x66061f58,0x6441c0d8,0xd49ac0d9,0x7640a1ce}}, // _mekk, feli, ерн_, remy,
+ {{0x6441c0da,0x660640db,0x27f7800d,0x62840010}}, // geli, _lekk, čení_, _alio,
+ {{0x644e40dc,0xd7ef80f7,0x44380706,0x2a60008e}}, // _mabi, _ركن_, _mbr_, _bnib_,
+ {{0x644e1fc6,0x20058052,0x6284051e,0x66062ffb}}, // _labi, _zeli_, _clio, _nekk,
+ {{0x44e99e4e,0xa3d606b7,0x443840dd,0x9f5e8216}}, // _gú_, ाइड_, _obr_, _metí_,
+ {{0xac18835f,0x6441a2f8,0x200a0040,0x224d1cf3}}, // _року_, celi, gabi_, žek_,
+ {{0x447b8158,0x660640de,0x1ee7003d,0xe80c897d}}, // _אנגע, _bekk, اوری_, _सजना_,
+ {{0x26c00021,0x44382eaa,0x3a290890,0x62841581}}, // glio_, _abr_, _ucap_, _glio,
+ {{0x644e2f14,0xe21400a0,0x660640df,0x44380366}}, // [2df0] _babi, _طبيع, _dekk, _bbr_,
+ {{0x7d0d40e0,0x888300d7,0x229202d4,0x320b066f}}, // rsas, _ایسن, vški_, lacy_,
+ {{0x644e40e1,0xaca48135,0x62840009,0x20059c40}}, // _dabi, _azịz, _ylio, _reli_,
+ {{0x6441c0e2,0x66061c11,0x62359c3a,0x44380428}}, // zeli, _gekk, реду, _ebr_,
+ {{0x9f5e80e1,0x44258114,0x2005c0e3,0x6441c0e4}}, // _detí_, ygl_, _peli_, yeli,
+ {{0x44e990ab,0x8f15808f,0x44250aa2,0x7e6d0074}}, // _rú_, афич, _øl_, ljap,
+ {{0x44e9c0e5,0x200585f3,0xb5fd807a,0x7d029c00}}, // _sú_, _veli_, ljši, _awos,
+ {{0x6441b986,0x7f3c00be,0x20058079,0x225f8503}}, // weli, געהו, _weli_, _unuk_,
+ {{0xc4858251,0x644e1ea2,0x6f04811f,0x26c380eb}}, // илик, _yabi, ćica, ējo_,
+ {{0x99848013,0x7ae700f1,0x3ebf80f2,0x7d1ba03b}}, // _الكو, _kujt, slut_, rrus,
+ {{0xda661264,0x626600af,0x26c00114,0x200a40e6}}, // авни, авна, ylio_, vabi_,
+ {{0x44e9c0e7,0xe29a1052,0xc5d5835f,0x6723b6c8}}, // _tú_, там_, _кіль, munj,
+ {{0x27318104,0x64418436,0x67238359,0x200a3722}}, // _hơn_, peli, lunj, tabi_,
+ {{0xe3b0803d,0x6d59a668,0x660640e8,0x8d748f24}}, // _کره_, _irwa, _sekk, _داما,
+ {{0x67238867,0x200a40e9,0xb4c383dd,0xb4c1b4ec}}, // nunj, rabi_, ्षी_, ंती_,
+ {{0x644e40ea,0x6284022e,0x200a40eb,0x7ebe80f1}}, // _sabi, _ulio, sabi_, tëpi,
+ {{0x644e15d0,0xb2bb0039,0x61e440ec,0xf5e729c9}}, // [2e00] _pabi, _במקר, _igil, рмул_,
+ {{0x26c00025,0x644e381f,0x4463035f,0x6723912e}}, // slio_, _qabi, овув, kunj,
+ {{0x26c006a5,0x66060074,0x644e007a,0xf1bf2294}}, // plio_, _tekk, _vabi, ngán_,
+ {{0x53368158,0xb4c181fe,0x67238699,0x6d59a525}}, // ינען_, ंतु_, dunj, _orwa,
+ {{0x644e40ed,0x656f81ec,0xe046824f,0xa06a8eef}}, // _tabi, ächl, инди, када_,
+ {{0xfb26003d,0x442a01e0,0x4438026c,0x764f023b}}, // _پرسپ, _ucb_, _ubr_, _zacy,
+ {{0x6d4bc0ee,0x6d59c0ef,0x3cec0540,0x61e40106}}, // _asga, _arwa, _अंडे_, _ogil,
+ {{0x2baf08fd,0x66c58125,0x61e424ff,0x7d0286c0}}, // _जुटा, sókn, _ngil, _pwos,
+ {{0x320b40f0,0xe7fb0ebf,0x444403e4,0x657b82c4}}, // vacy_, ्रमा_, oe_, gyuh,
+ {{0x37b28264,0x7ae09867,0x22920196,0x00000000}}, // টেগর, _himt, išku_, --,
+ {{0x94738307,0x444440f1,0x6d5982af,0xe82040f2}}, // تديا, ie_, _erwa, _बाजा_,
+ {{0x44441941,0x1eda80f7,0x48fd800d,0x7d028a03}}, // he_, _شباب_, रीको_, _twos,
+ {{0x64a69a80,0xe29801e2,0x673e0580,0x81e88264}}, // _када, раў_, _appj, _মজা_,
+ {{0x653b83c8,0x61e440f3,0x69c09d94,0x27248133}}, // _סעוד, _egil, _myme, _ịnye_,
+ {{0x7e629254,0xa3b7826a,0x9e070f27,0xddc60035}}, // _knop, _پاور_, ичал, _nakł,
+ {{0x444440f4,0x9f5e8081,0x962100d4,0x33db81c6}}, // ee_, _metà_, यलेट_, _בחוד,
+ {{0x629d40f5,0x7524002a,0x44443f47,0x7e6d2079}}, // [2e10] miso, guiz, fe_, rjap,
+ {{0xfd4d0104,0x91bc0039,0x629d40f6,0x6abb8aa2}}, // _khoả, _במחי, liso, _skuf,
+ {{0x7ae0808e,0x69c0c0f7,0xf36740f8,0x7e62c0f9}}, // _bimt, _ayme, штан, _onop,
+ {{0x444440fa,0x4b378051,0x6723a813,0x69c0867f}}, // ae_, שראל_, vunj, _byme,
+ {{0x444440fb,0x660d02d8,0x2731801c,0x463a00be}}, // be_, maak, _sơn_, _גערע,
+ {{0x67238590,0x660d40fc,0x6c8500f7,0x6d9e0609}}, // tunj, laak, _السم, _dħaħ,
+ {{0x629d40fd,0xf8bf255e,0x6445004f,0x3f8184e8}}, // kiso, ltés_, mehi, áhu_,
+ {{0x9aa58077,0x7ebe80f1,0x64453d46,0x660d40fe}}, // _امرو, këpu, lehi, naak,
+ {{0xf8bf255e,0x3e788125,0xddc6009a,0x1fd410c8}}, // ntés_, _rétt_, _zakł, _दण्ड,
+ {{0x4c8598a2,0xd8748013,0xb4c38d14,0xcc89803d}}, // слов, _والب, ्षे_, انده_,
+ {{0x3a2d011b,0x5f79987e,0x09e61bc1,0xdb1c00e7}}, // мep_, _حماس_, йонн, _pyré,
+ {{0x44441e98,0x68e1c0ff,0x629d1fca,0x6d59809a}}, // ze_, _hild, giso, _trwa,
+ {{0x44444100,0x6d5980b4,0x68e184d6,0x6e2994f9}}, // ye_, _urwa, _kild, mgeb,
+ {{0x44443a3b,0x037989a7,0x68e18bce,0x6e29a0b5}}, // xe_, _محبت_, _jild, lgeb,
+ {{0x177911d2,0x629d1f34,0x44fb802a,0x03a28037}}, // асть_, biso, _hª_, _нишо,
+ {{0x07a31ae5,0x75244101,0x6e3b0144,0x6e29b2c7}}, // часн, tuiz, _ñubl, ngeb,
+ {{0x6ab98362,0x7ae08084,0x0dca0b69,0x64848229}}, // [2e20] dowf, _rimt, улай_, _mòin,
+ {{0x7ae94102,0x44444103,0x7ae0c104,0x44fbad16}}, // _četr, ue_, _simt, _mª_,
+ {{0x18770039,0x48ee11bc,0xe9f90032,0xb5fd807a}}, // _העיר_, _इंडो_, _ajẹ_, ljšu,
+ {{0x366a0fe7,0x69c086a5,0x2c21816f,0x7f3b00be}}, // _само_, _pyme, _माझं_, _געטו,
+ {{0x68e1b2a6,0x7524016a,0x6e299337,0xf8bf4105}}, // _bild, quiz, dgeb, ctés_,
+ {{0xb4c386b7,0x69c08a21,0x44444106,0x629d4107}}, // ्षो_, _vyme, qe_, ziso,
+ {{0x43860013,0x394d84c3,0x68e18965,0xd6d096a5}}, // _الأق, _eses_, _dild, وقت_,
+ {{0xdd080038,0x8cf4c108,0x39401893,0x44f4817c}}, // _môže, озиц, _opis_, опис,
+ {{0x68e9c109,0x39401dd3,0xf65301c6,0xbcfb046d}}, // _gued, _npis_, לצה_, _eréf,
+ {{0x68e1807b,0x6282c10a,0x04669510,0x44fb802a}}, // _gild, lkoo, стим, _dª_,
+ {{0x2d82895e,0x629d410b,0x44fb8118,0x7c288bfd}}, // ške_, tiso, _eª_, rgdr,
+ {{0x6282c10c,0x25a98289,0x6d49c10d,0x7e62c10e}}, // nkoo, _žale_, bwea, _unop,
+ {{0x787d8247,0x68e19238,0xe1ff410f,0x660d0365}}, // _sèvi, _yild, rbó_, vaak,
+ {{0x629d4110,0x200e8239,0x68e18079,0xd9180009}}, // siso, nafi_, _xild, щью_,
+ {{0x6282c111,0x5c3784de,0x443c8dcc,0x64450087}}, // kkoo, _הטוב_, _ibv_, vehi,
+ {{0xf96b0098,0x62828952,0x61e481a1,0xfaa42ba7}}, // _брой_, jkoo, žilj, _најо,
+ {{0xe29f0125,0x62829ead,0x660d410c,0xf8bf02be}}, // [2e30] mið_, dkoo, raak, utés_,
+ {{0xe29f0125,0x68e9c112,0x660d257a,0xa3cf8fd5}}, // lið_, _rued, saak, _शरम_,
+ {{0x660d4113,0x64454114,0xda7b85e9,0xe80c8105}}, // paak, rehi, ляд_, _सज़ा_,
+ {{0x68e9a70d,0xaac90f21,0x02c903b7,0x7aeac115}}, // _pued, रतिक, रतिभ, _muft,
+ {{0x68e9ad11,0x7aeac116,0x68e1c117,0x6aad00f1}}, // _qued, _luft, _pild, _mjaf,
+ {{0x46f595d1,0xbcfb02be,0x68e1a795,0xa3d60424}}, // очит, _préf, _qild, ाइश_,
+ {{0xe29f07ca,0x68e1c118,0x6e299781,0x3ea913f0}}, // kið_, _vild, tgeb, onat_,
+ {{0x3ea91c15,0x62349509,0xa3bc80c2,0x68e19457}}, // nnat_, _несу, _आड़_, _wild,
+ {{0x6e298df3,0x7aea82af,0xd131b026,0xe29f008b}}, // rgeb, _auft, _زما_, dið_,
+ {{0x6d5d02a5,0x6e298223,0x6aad0032,0x3ea94119}}, // _mrsa, sgeb, _ajaf, hnat_,
+ {{0xe29f0125,0x3ea9411a,0x3940411b,0x37aba12e}}, // fið_, knat_, _spis_, _стан_,
+ {{0x6d4f411c,0xe29f0125,0x6d5d411d,0x442e8358}}, // _osca, gið_, _orsa, _ecf_,
+ {{0xa509902f,0x3eb88019,0x3ea901d0,0x442e8580}}, // _тела_, _írta_, dnat_, _fcf_,
+ {{0x62898355,0x39408085,0x3940007a,0x70b40c28}}, // _lleo, çisi_, _vpis_, ंकेल,
+ {{0x62899cb2,0x39400035,0x6d5d411e,0xafe310ac}}, // _oleo, _wpis_, _arsa, дорл,
+ {{0x3ea9411f,0x6d5d036e,0x6aad008b,0xd1322b7e}}, // gnat_, _brsa, _gjaf, _قمع_,
+ {{0x3940005c,0x660b8065,0x3ce300f3,0xf8bf27d1}}, // [2e40] _upis_, _megk, _bijv_, rtér_,
+ {{0x3ea9036e,0xa3d60740,0x85218e33,0x660b8019}}, // anat_, ाइल_, _मिनट_, _legk,
+ {{0x7ae43006,0x6d5d0910,0x2a6480b9,0x6d4f0c8c}}, // _kiit, _ersa, _pnmb_, _esca,
+ {{0x6d4f0580,0xfaa39246,0x7ae40df6,0x09de0264}}, // _fsca, махо, _jiit, _ভ্যা,
+ {{0xbcfb12ab,0x628290bd,0x82d703de,0x3ea04120}}, // _créd, rkoo, קונג_, miit_,
+ {{0x7ae40a92,0x6282c121,0x3ea04122,0xb4b3800f}}, // _liit, skoo, liit_, झको_,
+ {{0x200ea3ac,0xbcfb05db,0x443c81a1,0x8cb9175d}}, // rafi_, _prég, _rbv_, ्तरो,
+ {{0x7ae40364,0xf41f00ab,0x9f58349a,0xbcfb0036}}, // _niit, ন্দর_, laró_, _fréd,
+ {{0xe29f01fa,0x78bc4123,0x2d801c40,0x442e9a1f}}, // við_, korv, nyie_, _pcf_,
+ {{0xb5fb1313,0x6fc0016f,0x7d1d8019,0x6aad02f7}}, // _anál, _शुभं, ássa, _sjaf,
+ {{0xe29f07ca,0x7bc3831d,0x7ae44124,0x69c44125}}, // tið_, _cynu, _biit, _ayie,
+ {{0xe8d90028,0xf8bf04c3,0x442e8609,0x81af0264}}, // _trị_, guén_, _wcf_, _করব_,
+ {{0xe29f07ca,0x3ea9001b,0x7ae44126,0x78bc03ba}}, // rið_, vnat_, _diit, forv,
+ {{0xe29f007b,0x50ca852a,0x6d428037,0x7aeac127}}, // sið_, ितिष, _ipoa, _tuft,
+ {{0x3f85003e,0xc058035f,0x200ca068,0x64488b23}}, // álu_, ція_, _medi_, medi,
+ {{0x6448c128,0x3ea000e4,0xe298038c,0x442cc129}}, // ledi, giit_, _мај_, lgd_,
+ {{0x3ea9412a,0x7bce0088,0x442c81ed,0x98ca8035}}, // [2e50] rnat_, _azbu, ogd_, िताए,
+ {{0x6448c12b,0x442c9808,0x6d5d19b7,0x28d20035}}, // nedi, ngd_, _vrsa, _दीपि,
+ {{0x2011412c,0x442cc12d,0xa801816a,0x3ea92c15}}, // mazi_, igd_, _íñig, pnat_,
+ {{0xbcfb12ab,0x68ed412e,0x6d5d09d1,0x6abd1e03}}, // _préd, _kuad, _trsa, nosf,
+ {{0x68ed0748,0x6d4f0087,0x6d5d412f,0x59d34130}}, // _juad, _usca, _ursa, _सरफर,
+ {{0x64488025,0xbcfb03d3,0x201106a0,0x200c81a1}}, // jedi, _crée, nazi_, _cedi_,
+ {{0x02a78f04,0x200c817b,0x6448c131,0x6d4d4132}}, // _драм, _dedi_, dedi, lwaa,
+ {{0x6e2d4133,0x20114134,0x64554135,0x200c846d}}, // ngab, hazi_, _hazi, _eedi_,
+ {{0x20113f6d,0x7ae40009,0xe8d90133,0x61e98f3e}}, // kazi_, _riit, _arọ_, _igel,
+ {{0x7ae40364,0x442c8106,0x20114136,0x68fb8122}}, // _siit, ggd_, jazi_, _itud,
+ {{0x64554137,0x2909019d,0x395f8573,0x69c400b9}}, // _mazi, _awaa_, _hrus_, _syie,
+ {{0x6d4d4138,0x64554139,0x442c8a0f,0x395f8461}}, // kwaa, _lazi, agd_, _krus_,
+ {{0x7ae4413a,0x68ed0c15,0x764982a3,0x6448c13b}}, // _viit, _cuad, meey, bedi,
+ {{0x6455413c,0x6448bbfe,0x6d4d4132,0x20113bb8}}, // _nazi, cedi, dwaa, gazi_,
+ {{0x6e2d01ec,0x2aa40061,0x366a0323,0x7ae418c2}}, // fgab, rűbb_, баво_, _tiit,
+ {{0x61e9840c,0x61e482ce,0xf8bf413d,0x68ed10eb}}, // _ngel, žili, quén_, _fuad,
+ {{0x64550dbb,0x68ed3dbe,0x6d4d18f8,0x2011413e}}, // [2e60] _bazi, _guad, gwaa, bazi_,
+ {{0x6455413f,0x31370051,0x6ad100c8,0xf8bf02be}}, // _cazi, _צריך_, _সংযো, trée_,
+ {{0xb5fb003e,0x645a00a9,0x395f8458,0x307b0039}}, // _znám, _ótim, _arus_, _האינ,
+ {{0x25ad003a,0x20c98013,0x31603a9c,0x3d1c8740}}, // _žele_, súil_, _kriz_, _मिले_,
+ {{0x64554140,0x66044141,0x200cc142,0x76498079}}, // _fazi, mbik, _pedi_, deey,
+ {{0x61ff8201,0x644881df,0x64554143,0x66044144}}, // ıqla, xedi, _gazi, lbik,
+ {{0x43430e86,0x200c8098,0x7d160b81,0x68fb902d}}, // _перв, _vedi_, lsys, _etud,
+ {{0x200ca065,0xd90d8bca,0x20112486,0x64550234}}, // _wedi_, _چین_, zazi_, _zazi,
+ {{0x644887d9,0xc10680f7,0x395f92f1,0x7d164145}}, // tedi, _رواي, _grus_, nsys,
+ {{0x672380fe,0x6aa2804f,0x6f150035,0xc917807c}}, // brnj, liof, yszc, _מחמת_,
+ {{0x68ed1286,0x442c80f3,0x6448aad1,0x20114146}}, // _suad, rgd_, redi, vazi_,
+ {{0x6448c147,0x20114148,0x75244149,0xa25b00e7}}, // sedi, wazi_, kriz, plôm,
+ {{0x20112f6a,0x6448c14a,0x7aee022c,0x68ed1ee5}}, // tazi_, pedi, _lubt, _quad,
+ {{0xe521800d,0xe8d900ff,0xb4db026b,0xe4d780d7}}, // _मिति_, _trọ_, _apàn, _فونت_,
+ {{0x645506b9,0x2011414b,0xddc9920e,0x6af380f7}}, // _razi, razi_, mješ, _كثير,
+ {{0xddc9ae0a,0x6455414c,0x6d4d18f8,0x68e5008e}}, // lješ, _sazi, twaa, _wihd,
+ {{0x6455414d,0x6e2d414e,0xc8670ab2,0x95cb0f9c}}, // [2e70] _pazi, rgab, _етни, сува_,
+ {{0x6d4d21cf,0x2a690069,0x6e2d414f,0x787d8036}}, // rwaa, _hnab_, sgab, _lèvr,
+ {{0x81af00c8,0x64550e64,0x628d190f,0x6d4d4150}}, // _করি_, _vazi, _klao, swaa,
+ {{0x6455280d,0x75244151,0x7aee009f,0x76498079}}, // _wazi, briz, _dubt, xeey,
+ {{0xdb188364,0x75240098,0x6d4d0359,0x645534c2}}, // _hyvä, criz, qwaa, _tazi,
+ {{0x81af0a49,0x26c90661,0x76499a25,0x224b4152}}, // _করা_, llao_, weey, heck_,
+ {{0xe73999b5,0xdb188198,0x7649a223,0x7aee0df6}}, // цей_, _jyvä, teey, _gubt,
+ {{0x61e900ce,0x395f8854,0xbcfb00e1,0x6564826c}}, // želj, _trus_, _gréc, _šiha,
+ {{0x395f9083,0xb3458073,0x764982a3,0x9f5c816b}}, // _urus_, _opçã, reey, mavý_,
+ {{0x660f4153,0x3b85ab2c,0x6d5b9c85,0x212b016b}}, // _leck, длог, rvua, duch_,
+ {{0xf8bf0073,0x78a3807a,0x26c90699,0x628d0f35}}, // guém_, minv, klao_, _blao,
+ {{0x76498079,0x31604154,0x628d0834,0xedd581a8}}, // qeey, _priz_, _clao, _قياد,
+ {{0x752987a3,0x62860364,0x3ead8687,0xcdcb853d}}, // quez, rkko, mnet_, _اذان_,
+ {{0x3ebf8e35,0x62860006,0x92ae00ab,0x661d128d}}, // lout_, skko, কতে_, _adsk,
+ {{0xfbd182f1,0x23d18035,0x81df00ab,0x3ead8168}}, // _हरिम, _हरिद, দুল_, onet_,
+ {{0x3eadc155,0x3ebf800d,0x75244156,0x212b4157}}, // nnet_, nout_, triz, buch_,
+ {{0x6604320e,0x6da3002e,0xec7a2457,0xe5a34158}}, // [2e80] rbik, вита, опа_, вити,
+ {{0x3eadc159,0x7524011e,0xfbd1903e,0x3ebf9151}}, // hnet_, rriz, _हराम, hout_,
+ {{0xbcfb03d3,0x3ebf83ec,0x752400dd,0x93c30087}}, // _préc, kout_, sriz, _ţări,
+ {{0x660f1486,0x8b651ddd,0x65690fee,0x3ebf9a1f}}, // _geck, عالم, _šehe, jout_,
+ {{0xd910803d,0x3cfd81c0,0xa3df02f1,0x2ee68362}}, // ایط_, _ntwv_, देब_, _riof_,
+ {{0x3eadc15a,0xb5fb0144,0x32e802d0,0x787d8866}}, // enet_, _anáh, _eşya_, _sèvr,
+ {{0xddc98499,0x3eadc15b,0xfaa6853b,0x44f28162}}, // vješ, fnet_, _жаво, _sâ_,
+ {{0x3ebf8e35,0x3ead920b,0xdb0e0035,0x7c3e0901}}, // gout_, gnet_, _wybó, lfpr,
+ {{0x3206c15c,0xa3d60035,0xbcfb09c4,0xddc98140}}, // mboy_, ाइक_, _kréa, tješ,
+ {{0x3374879e,0x2126808e,0xb866a0bb,0x6ef58609}}, // егор, mroh_, _تارو, _iġbn,
+ {{0x3ebf93ff,0xddc98639,0x3eadc15d,0xda350e11}}, // bout_, rješ, bnet_, нены,
+ {{0xcfa98064,0x3206c15e,0xdddb807a,0xbd4680f7}}, // _عالم_, nboy_, skuš, _يناي,
+ {{0xddc98499,0x68e8c15f,0x224b1230,0xdb0a8106}}, // pješ, _kidd, reck_, _nyfö,
+ {{0x55778158,0x660f4160,0x212b4161,0x3dcd01b9}}, // _קעגן_, _seck, ruch_, _ġew_,
+ {{0x6d460bb6,0x212b01ec,0x660f136f,0x320682c4}}, // _apka, such_, _peck, kboy_,
+ {{0x443ec162,0x68e8c163,0x64588a55,0xbcfb010c}}, // lft_, _lidd, _havi, _aréa,
+ {{0x660f04b8,0xf8ae0077,0x6458c164,0x2d84b06a}}, // [2e90] _veck, شکی_, _kavi, nyme_,
+ {{0x645880ce,0xbcfb03d3,0xdb1880f2,0x26c9005c}}, // _javi, _créa, _tyvä, slao_,
+ {{0x660f00f2,0x3ead8aa2,0x443ec165,0x6458c166}}, // _teck, ynet_, ift_, _mavi,
+ {{0x64588247,0x661d013c,0xe29880e8,0x69dd816b}}, // _lavi, _udsk, має_, _úsek,
+ {{0x29190364,0xa069a434,0xaca3019d,0xdb1c05b9}}, // nssa_, зала_, _abục, _vyrá,
+ {{0xbcfb0013,0x68e8c167,0x443e8a0f,0x29190364}}, // _gréa, _cidd, jft_, issa_,
+ {{0x68e88114,0x78a3a3be,0x65628db1,0x3eadc168}}, // _didd, rinv, _aroh, tnet_,
+ {{0x61ed4169,0x443e80f3,0x68e88114,0x645894c7}}, // _ngal, eft_, _eidd, _aavi,
+ {{0x443ea06f,0xdbd68364,0xc0cbc16a,0x3ebf9823}}, // fft_, _lääk, _дуже_, rout_,
+ {{0xe1fa00ba,0x6458c16b,0x61ed416c,0xb4b981ab}}, // ूर्ण_, _cavi, _agal, चवे_,
+ {{0x6458c16d,0x29190364,0x7ae98084,0x53c980e8}}, // _davi, essa_, _kiet, ягом_,
+ {{0x60c5008e,0x7e7d00eb,0x2d848216,0x656f8192}}, // _akhm, _nosp, byme_, ächs,
+ {{0x7ae9c16e,0xf2d280be,0x961500ab,0x61fb804f}}, // _miet, טעט_, িলাম_, _mful,
+ {{0x7ae9c16f,0x61ed4170,0xe00282f1,0x752d0ab3}}, // _liet, _egal, रुपद_, duaz,
+ {{0x2bc69664,0xb4cb8361,0xfbc6800d,0x26c0047f}}, // _रुपा, लती_, _रुपम, toio_,
+ {{0x7ae9c171,0x64588b48,0xe81c000d,0x139a8039}}, // _niet, _zavi, _भएका_, _מבצע,
+ {{0x7e7d1284,0x753bc172,0x69c9c173,0xd9de00ab}}, // [2ea0] _dosp, ntuz, _nyee, _ভ্রম,
+ {{0xe0d207bd,0x6ab88996,0x64589e9e,0x26c000e5}}, // _حزب_, इक्र, _xavi, soio_,
+ {{0x7ae9c174,0x389b0158,0xb2269c8b,0x7e6b82c4}}, // _biet, _זיינ, емел, _ongp,
+ {{0x68e8a629,0x7ae980eb,0x7e7d4175,0x2ca5977a}}, // _sidd, _ciet, _gosp, hild_,
+ {{0xc1e78009,0x290d8326,0xe9478019,0x752d00e5}}, // ньше_, _cwea_, ٹرنی, cuaz,
+ {{0x59d304e5,0x7e6b81e0,0x66160df6,0x61fb8c2e}}, // _सरसर, _angp, layk, _eful,
+ {{0xb5fb0013,0x7ae9a64f,0x6458c176,0x0cbe14d5}}, // _snái, _fiet, _ravi, ्तीम,
+ {{0x6458b1dc,0xd6d784d9,0xddcd01dd,0x661601e0}}, // _savi, нты_, ljaš, nayk,
+ {{0x6458c177,0xa90a80a0,0x7ae38fb0,0x290001c0}}, // _pavi, _ريال_, ïnte, _ntia_,
+ {{0xd91b8051,0x4f96a748,0x253700be,0xb4db026b}}, // _מומל, _преу, עניש_, _apàj,
+ {{0xa3d6005e,0x443ec178,0x64588b80,0x61ed00b9}}, // ाइट_, rft_, _vavi, _pgal,
+ {{0x273a80f1,0xdbd68074,0x2d848216,0x6458c179}}, // mën_, _rääk, pyme_, _wavi,
+ {{0x2ca5c17a,0x273a80f1,0xd2510077,0x1a9b80be}}, // bild_, lën_, هنگ_, ליטע,
+ {{0x7e7d417b,0xbcfb0176,0xdbd68198,0x3a2002d0}}, // _sosp, _brén, _pääk, _edip_,
+ {{0x921089a3,0x273a820f,0xd9048117,0x6e671383}}, // ाराज_, nën_, _گی_, нтаж,
+ {{0x41de0076,0x752d417c,0x61ed417d,0xadc3846d}}, // नेहस, tuaz, _ugal, _akẹk,
+ {{0xd90499f4,0x273a80f1,0xdd9401bb,0x7ae9c17e}}, // [2eb0] _دی_, hën_, гаты, _riet,
+ {{0x7ae9c17f,0x273a80f1,0xe802801b,0x752d0ab3}}, // _siet, kën_, रुमा_, ruaz,
+ {{0x7ae9c180,0x273a80f1,0x7e7d1c19,0x69c982c4}}, // _piet, jën_, _tosp, _syee,
+ {{0xed510117,0x386c8267,0x78a700f3,0x273a8168}}, // _پھر_, _andr_, lijv, dën_,
+ {{0x7ae9c181,0xe8f88221,0x273a80f3,0xddcd0503}}, // _viet, елі_, eën_, bjaš,
+ {{0x07a590ca,0x8fa59597,0x26c6a914,0xad9b026b}}, // калн, кале, _akoo_, _afúg,
+ {{0x7ae9c182,0x273a80f1,0x6b83016d,0x394900ee}}, // _tiet, gën_, ängd, _lpas_,
+ {{0xdddd00eb,0x2ca5874c,0x92588071,0x78a701ed}}, // _nosū, wild_, _паст_, hijv,
+ {{0x2d8b83cb,0x648d80f7,0x39490282,0x81b00326}}, // áce_, _dúin, _npas_, _keɓe,
+ {{0xb4cba8b3,0x44330683,0x4c8582eb,0x9d44b026}}, // लते_, ngx_, тлов, ائين,
+ {{0x39494183,0x7d042dc8,0x3a200101,0x6aa62709}}, // _apas_, zpis, _pdip_, tikf,
+ {{0xfeb88013,0x60c39727,0xddcd011f,0xfaa64184}}, // _كانت_, lonm, zjaš, тамо,
+ {{0xe29731a5,0x82768158,0x6609c185,0x7e9a83de}}, // тая_, דערע_, mbek, _קסנו,
+ {{0xbcfb4186,0x9103002e,0x9345813a,0x26cd816a}}, // _prén, рпре, _анке, lleo_,
+ {{0xe7370256,0x7d1bc187,0xb8ca326c,0x3f8c016b}}, // кер_, lsus, _खग_, ádu_,
+ {{0x7d040025,0x61ed811f,0xbcfb21bf,0xe3e880be}}, // tpis, žalj, _créo, _אַפֿ,
+ {{0xdee33344,0x7bd50081,0x66e303c7,0x66160079}}, // [2ec0] роти, _azzu, рота, rayk,
+ {{0x14c803f8,0x0cbe023c,0xbcfb03b0,0x7d040057}}, // رهای_, ्तेम, _trén, rpis,
+ {{0x20184188,0x91f6000f,0xddc2805c,0x7dd8026b}}, // mari_, _इलाज_, _mnoš, _búsí,
+ {{0x20184189,0x7d1b8100,0x273a80f1,0xd36f00f7}}, // lari_, ksus, vën_, تهم_,
+ {{0xdb0e0106,0x2a7f8242,0x7bd5038a,0xc02f0129}}, // _nybö, _doub_, _ezzu, _điếu_,
+ {{0x273a88cf,0xa3df0b75,0xd90d80d5,0xe0438012}}, // tën_, देह_, سیم_, инчи,
+ {{0x64a32133,0xdca30a7c,0xdb1c01a3,0x75298c2e}}, // _тара, _тари, _byrå, erez,
+ {{0x273a820f,0x2018418a,0x6b830b2f,0x66098091}}, // rën_, hari_, änge, gbek,
+ {{0x645c418b,0x64419849,0x273a820f,0x7d1b9066}}, // _kari, ffli, sën_, gsus,
+ {{0xa3df03b7,0x2018319f,0x290686cb,0x3eb20009}}, // देव_, jari_, mpoa_, hnyt_,
+ {{0xf4fc8133,0x7529c18c,0xbddb010c,0x5fb6873c}}, // _dịịr, arez, _abèl, _अखिल,
+ {{0x645c418d,0x26cda34a,0x2fd7803d,0xc33302f6}}, // _lari, cleo_, _شوند_, סור_,
+ {{0x81e480c8,0x6280c18e,0x6e228135,0x7529a49a}}, // পুর_, _momo, _idob, crez,
+ {{0x645c418f,0x4ec400ab,0x9f35102a,0x6280c190}}, // _nari, ্দোল, легі, _lomo,
+ {{0x9cd78039,0x78a74191,0x6cd980be,0xbcfb0036}}, // _שווה_, rijv, _אַקצ, _préo,
+ {{0x6280c192,0x645c4193,0x9f98826f,0x26c4c194}}, // _nomo, _aari, jším_, nomo_,
+ {{0x645c4195,0x20184196,0x35f44197,0x44384198}}, // [2ed0] _bari, bari_, рпур, _bcr_,
+ {{0x645c4199,0x7d1d83a7,0x26c481e0,0xa855419a}}, // _cari, ássi, homo_, _скоч,
+ {{0x645c16a7,0x26c494c6,0x6280bc51,0x7aed019e}}, // _dari, komo_, _bomo, _kiat,
+ {{0x3e040081,0x6e22c19b,0x7aed0609,0x60da852a}}, // _вярв, _ndob, _jiat, _ahtm,
+ {{0x645c419c,0x6280c19d,0x7aed0065,0xbcfb037d}}, // _fari, _domo, _miat, _krém,
+ {{0x645c1a85,0x3ea9419e,0x765d111e,0x2ecb89c2}}, // _gari, liat_, _hasy, ात्त,
+ {{0xb6a59a02,0xa3df419f,0xa283803d,0x7d02c1a0}}, // ликл, देश_, _پیشو, _atos,
+ {{0x7529c1a1,0x645c41a2,0x7d1b8006,0x201841a3}}, // trez, _zari, tsus, zari_,
+ {{0xd4159641,0x20183e75,0x765d1904,0x59b7800f}}, // льны, yari_, _masy, _आखिर,
+ {{0x645c1b37,0x13098364,0x7529bfae,0x6e228135}}, // _xari, нной_, rrez, _edob,
+ {{0x26cd85b4,0x3eb20364,0x6280836a,0x6441c1a4}}, // pleo_, ynyt_, _yomo, rfli,
+ {{0x765d0247,0x26c4b6f1,0x7aed41a5,0x64419bc2}}, // _nasy, como_, _ciat, sfli,
+ {{0x7aed0500,0x3ea941a6,0x8c458be2,0xe56e84ae}}, // _diat, diat_, _беле, _уз_,
+ {{0x6d59b93e,0x03198013,0x7e7b805c,0xddc2825b}}, // _iswa, _كتبت_, ljup, _unoš,
+ {{0x201841a7,0x645c04b9,0x7aed0019,0x765d41a8}}, // rari_, _rari, _fiat, _basy,
+ {{0x645c41a9,0x7aed41aa,0x998601ad,0xbcfb026b}}, // _sari, _giat, _ملبو, _erém,
+ {{0xcbb00a49,0x056611e9,0x8d6621f6,0x765d0359}}, // [2ee0] _করেছ, ыван, ывае, _dasy,
+ {{0x628090dd,0x645c41ab,0x399b01c6,0x6d59a73b}}, // _somo, _qari, _נייד, _mswa,
+ {{0x0326280f,0x3ea941ac,0x26c4804f,0x4b2602de}}, // уден, biat_, yomo_, умев,
+ {{0xe297c1ad,0x3ea913f0,0x6d4ba862,0xdce284b7}}, // лах_, ciat_, _opga, _proċ,
+ {{0x645c41ae,0xa9678d69,0x19588190,0x9f98816b}}, // _tari, лица_, талы_, rším_,
+ {{0x44380216,0xd00f81a8,0xe8d90129,0x6e3641af}}, // _ucr_, كله_, _trễ_, lgyb,
+ {{0x7af500ad,0x7d028063,0x6d4b8bb6,0xb4e020d5}}, // _guzt, _stos, _apga, दगी_,
+ {{0x21200025,0x7f3b00be,0x44443593,0x212b0114}}, // _ovih_, געקו, lf_, yrch_,
+ {{0x7aed41b0,0x26c4c1b1,0xa8368065,0xddc404b7}}, // _riat, romo_, _معاش, jjiż,
+ {{0xb99380f7,0x26c4a169,0x212b016b,0xb80a81a8}}, // _الكب, somo_, vrch_, _أيام_,
+ {{0x444441b2,0x7aed41b3,0x6d4b8118,0x69cd008e}}, // if_, _piat, _epga, _syae,
+ {{0x6e228805,0xf1b88c87,0x6484851e,0x92580198}}, // _udob, _अशान, _còir, гают_,
+ {{0x7aed27c5,0xa802017b,0x3ea941b4,0x32190690}}, // _viat, ğınd, viat_, rasy_,
+ {{0x44440613,0xa80202bb,0xe3a4803d,0x7c38817f}}, // jf_, şınd, _پشتی, _ucvr,
+ {{0xd6db00ae,0x765d41b5,0x7ae2816d,0x6d408965}}, // ета_, _pasy, kmot, ltma,
+ {{0x7bd8803a,0x6e36008e,0x61ed811f,0x5e5800e8}}, // _izvu, ggyb, žali, лися_,
+ {{0x44440af4,0x6d40c1b6,0x1fe1aa54,0xf1a48035}}, // [2ef0] ff_, ntma, _पण्ड, _खेलन,
+ {{0x3ea911d6,0x78a5007b,0xe1ff41b7,0x765d3907}}, // siat_, _umhv, lcó_, _wasy,
+ {{0x3ea941b8,0x1cbb80be,0x75eb8019,0x3d1b026b}}, // piat_, עמבע, _közü, _bàwá_,
+ {{0x44440c72,0xe1ff0216,0x291d8a03,0x7ae2c1b9}}, // af_, ncó_, tswa_, gmot,
+ {{0x660d3996,0x224908b3,0x444400b9,0x81b009ab}}, // mbak, _mbak_, bf_, _keɓa,
+ {{0x660d035f,0x3ea69ca7,0x5f05a7ae,0x60c70168}}, // lbak, _amot_, азка, nojm,
+ {{0x96958088,0x752d00b9,0x7ae29500,0x3d1b026b}}, // ириш, lraz, bmot, _fàwá_,
+ {{0x31690d38,0x660d0102,0xada600e1,0x752d0ab3}}, // _oraz_, nbak, slúž, oraz,
+ {{0x212b8028,0x60c741ba,0xef190035,0xdf61846d}}, // _ích_, kojm, ąży_, _bẹ́è,
+ {{0x3ea680f2,0x66f18029,0x1035803d,0x6aab928d}}, // _emot_, _sāku, _خبرگ, ligf,
+ {{0x21200025,0xe29a1612,0x752d001b,0x2bfb85fc}}, // _svih_, ван_, hraz, लुगू_,
+ {{0x09e61056,0xfc3f800d,0x91e60652,0x752d41bb}}, // ионн, ří_, ионе, kraz,
+ {{0x6d59c1bc,0x89d6021e,0x6f050090,0x6d408bfd}}, // _uswa, рінш, _athc, ctma,
+ {{0x661bc1bd,0x752d0503,0x7ae28289,0x225fc1be}}, // lauk, draz, zmot, _lauk_,
+ {{0x44fb879f,0x752d011e,0x7d09987a,0xdb038980}}, // _hê_, eraz, lpes, _exnò,
+ {{0x44fb801c,0xfb24803d,0x316903ec,0x75eb83bf}}, // _kê_, _اروپ, _fraz_, _gözü,
+ {{0x752d23e9,0xf8bf41bf,0xa3e8086a,0x394d8282}}, // [2f00] graz, gués_, मेन_, _npes_,
+ {{0x444441c0,0x2a600282,0x661bc1c1,0x44fb801c}}, // uf_, _haib_, hauk, _mê_,
+ {{0x444441c2,0x66f18029,0x44fbc1c3,0x660d41c4}}, // rf_, _māks, _lê_, bbak,
+ {{0x752d0ed7,0xd7bb0051,0xe24680f7,0x661bc1c5}}, // braz, _יציר, شخصي, jauk,
+ {{0x28db1094,0x661b86ac,0x752d047f,0x444401ec}}, // _मीडि, dauk, craz, pf_,
+ {{0xdce28025,0x2a600282,0x7e0e863a,0x6284159d}}, // _proč, _laib_, सर्ग_, _loio,
+ {{0xe7e18074,0xd3721a37,0x20c981a8,0xc1d341c6}}, // गेला_, _ظهر_, túir_, _सर्ग,
+ {{0xdce28353,0x2a6041c7,0x629981ec,0x44fb80ff}}, // _vroč, _naib_, chwo, _bê_,
+ {{0x8c3c8214,0xddc98214,0x394001c5,0x66fc016b}}, // liği, rdeş, _nqis_, _očko,
+ {{0x44fbc1c8,0x6721081d,0x75eb8059,0x07a68389}}, // _dê_, šlji, _sözü, _казн,
+ {{0x99920176,0x628407b6,0x3169128a,0x57fbb5c4}}, // _deyō_, _boio, _sraz_, _סלאו,
+ {{0x201cc1c9,0x38ad0267,0x661b82f7,0x62840362}}, // lavi_, džra_, cauk, _coio,
+ {{0x6602c1ca,0x2a6001c0,0xe1ff1d24,0x60c70168}}, // _ifok, _daib_, rcó_, tojm,
+ {{0xda0712e0,0x8d85804e,0x629641cb,0xe1ff41cc}}, // _व्रत_, _دشمن, _elyo, scó_,
+ {{0x2a600069,0x629602f7,0x629880eb,0x60c70fd4}}, // _faib_, _flyo, īvoj, rojm,
+ {{0x35f541cd,0x8c3c87d9,0x2a60061b,0x316903a7}}, // спар, diği, _gaib_, _traz_,
+ {{0x201c8796,0x95cb0f9c,0x752d2801,0x44fb827d}}, // [2f10] kavi_, тува_, uraz, _xê_,
+ {{0x752d2798,0x70551125,0x7af881c0,0xf36741ce}}, // rraz, _اندا, _kuvt, итен,
+ {{0x201c8214,0x6a1780f7,0xa8560039,0x6aabc1cf}}, // davi_, لبشر, _היתה_, tigf,
+ {{0x752280d2,0x2a6001c5,0x6235aba7,0x99d981a8}}, // _ovoz, _xaib_, седу, هواء_,
+ {{0xf8bf2544,0xaca48032,0xe8d90133,0x7afb0390}}, // qués_, _ajọy, _nsị_, _čutu,
+ {{0x386102af,0x88c880e8,0x6602826b,0x6d5d41d0}}, // _jahr_, алів_, _afok, _issa,
+ {{0xfbe5150b,0xe3e500c8,0x44fb84df,0xb3e500c8}}, // _প্রত, _প্রব, _sê_, _প্রজ,
+ {{0x02a481ae,0x9454011c,0xbcfb41d1,0xcfb801c6}}, // _друм, _çərş, _préh, ילוי_,
+ {{0x201c803a,0x661b9608,0x6aa99cfb,0x443c8144}}, // bavi_, rauk, _imef, _bcv_,
+ {{0x661b8029,0x2a6001e9,0x44fbc1d2,0x7522abea}}, // sauk, _saib_, _vê_, _dvoz,
+ {{0x7d09c1d3,0x1fa7ae15,0x661b8009,0x2a600282}}, // spes, _траг, pauk, _paib_,
+ {{0x7d09ad03,0xdb1e801b,0x2a600069,0xc7b281c6}}, // ppes, íván, _qaib_, _צבא_,
+ {{0xddc982fd,0xd6c68077,0x3eadc1d4,0x75229487}}, // ljež, _امنی, miet_, _gvoz,
+ {{0x3eadc1d5,0xbcfb01a8,0xe73a323a,0x40951229}}, // liet_, _gréi, лег_, орст,
+ {{0xd62a0539,0xdfd200f7,0x2a600748,0x6d5d41d6}}, // лове_, سير_, _taib_, _assa,
+ {{0x3eadb3da,0x629880eb,0x201c935a,0x6abb826c}}, // niet_, īvok, zavi_, _njuf,
+ {{0x6b830884,0x44278748,0xa2c50519,0x98a60198}}, // [2f20] ängn, _kdn_, रवर्, _гипе,
+ {{0xb97b0039,0x6aa9804f,0x8c3c8214,0xa3ab83eb}}, // _עניי, _amef, tiği, _गधा_,
+ {{0x6d5d41d7,0x8c1a8039,0x201c8669,0x0b461383}}, // _essa, רועי, vavi_, снен,
+ {{0x3eadaf1e,0xf8bf0019,0x320237ba,0x8c3c82d0}}, // jiet_, tség_, ncky_, riği,
+ {{0x7243803d,0x3eadc1d8,0x64430722,0x442780fe}}, // سپول, diet_, ònic, _odn_,
+ {{0x2eb29c42,0x6aa98133,0xddcd0085,0x0496066e}}, // ुक्त, _emef, ldaş, _السح,
+ {{0x6d4441d9,0xf8bf0019,0x9f580216,0xe809016f}}, // ltia, sség_, mbró_, वडता_,
+ {{0xddcd0059,0x201a179d,0xe297c1da,0x7e6441db}}, // ndaş, _lepi_, _лас_, ndip,
+ {{0x6d4441dc,0x4427c1dd,0x25a0011c,0x5eff03b7}}, // ntia, _bdn_, nzil_, _शून्_,
+ {{0x201a010c,0xb4c298b8,0x81ae0264,0x6d4441de}}, // _nepi_, ंवे_, কথা_, itia,
+ {{0x3eadc1df,0x6d4441e0,0xf8bf0036,0x2caca906}}, // biet_, htia, nsée_, ridd_,
+ {{0x59aa09a3,0x6d44085c,0x3eadc1e1,0x4427abf0}}, // _चेहर, ktia, ciet_, _edn_,
+ {{0xbcfb00f7,0x7e6441e2,0xad9b41e3,0x6d4401c0}}, // _tréi, ddip, _afún, jtia,
+ {{0x443c8144,0x321d8035,0xf99f0037,0x201a2106}}, // _ucv_, zawy_, rzè_, _cepi_,
+ {{0x201a0205,0x7e6282c4,0x25a00122,0x2bcfa836}}, // _depi_, _kaop, ezil_, _सुपा,
+ {{0xa3be0074,0x38610192,0x7c3a8a53,0x644889ff}}, // ुअन_, _wahr_, lgtr, lfdi,
+ {{0x7bdc00ee,0x6d44022c,0x9419098d,0xad9b0032}}, // [2f30] _azru, gtia, ижат_, _efún,
+ {{0x7c3ac1e4,0x3ead81a9,0x1dba0a27,0x00000000}}, // ngtr, ziet_, _इशरत, --,
+ {{0xd3710013,0x69c082a3,0xd9f995fe,0x321d8035}}, // بها_, _axme, анец_, tawy_,
+ {{0xddc9920e,0x7e629c18,0xa3e8064a,0x6aaf0122}}, // vjež, _naop, मेध_, nicf,
+ {{0x2bcf85e8,0xa92480e8,0x2bdb05fc,0xbcfb016a}}, // _सुना, зділ, _भरवा, _arév,
+ {{0x2003009a,0xa069942e,0x2909004f,0xd8388b80}}, // ncji_, рака_, _mtaa_, _omču_,
+ {{0x3eadc1e5,0x7d0d0137,0xe8d90870,0x645741e6}}, // tiet_, lpas, _nsọ_, mexi,
+ {{0x645741e7,0x44278ad4,0x6aa980b8,0x7e62c1e8}}, // lexi, _sdn_, _umef, _caop,
+ {{0x0c798e11,0x20030063,0x7d0d41e9,0xe8d901bc}}, // асны_, kcji_, npas, _asọ_,
+ {{0x64570baf,0x3eadc1ea,0x4b25a4c8,0x8d5c025f}}, // nexi, siet_, омов, רכזי,
+ {{0x6d44011e,0x3f678289,0xa49b2b5f,0x68fb8198}}, // ztia, _uđu_, _atòk, _huud,
+ {{0x201a41eb,0x7d0d06cb,0x316d8267,0x39ea2240}}, // _sepi_, kpas, _krez_, идно_,
+ {{0x6abd0661,0x7d0d090d,0x224d88f9,0x68fbc1ec}}, // érfa, jpas, _mbek_, _juud,
+ {{0x68fb8006,0x64b80a0d,0xfd62819d,0xf8bf1dac}}, // _muud, _आदेश, _latị, ysée_,
+ {{0x6b831647,0x7c648019,0xaac9a743,0x921983eb}}, // ängl, _راول, िकाक, दराज_,
+ {{0x6e3bbedb,0x6d4441ed,0x527633c2,0x316d8162}}, // ngub, ttia, _фуку, _orez_,
+ {{0x201a010b,0x7d0d10c1,0xddcd0085,0x64968087}}, // [2f40] _tepi_, gpas, rdaş, _mâin,
+ {{0x3a3f8580,0x7698026b,0x61e6928a,0x224d82f9}}, // _acup_, _díya, _úkli, _abek_,
+ {{0x6d4441ee,0x2bcf816f,0x7d0d41ef,0xe1fa82ee}}, // stia, _सुमा, apas, аге_,
+ {{0x316d8db7,0x2005c1f0,0x7bdc016b,0x6d4441f1}}, // _brez_, _afli_, _vzru, ptia,
+ {{0xf8bf00e7,0x18748d8e,0x6f088144,0xc7b880fe}}, // ssée_, згля, _stdc, _međe_,
+ {{0x7e62a8e1,0xf72a9fbc,0xbcfb3477,0x24878247}}, // _saop, аций_, _prév, _nonm_,
+ {{0xab2741f2,0xd2598176,0x644e0372,0x7bdc41f3}}, // _мора_, _djņb_, _ibbi, _uzru,
+ {{0x660409c3,0xf5269af6,0x656941f4,0xc7b8826c}}, // ncik, офон, _šehi, _neđe_,
+ {{0x752437f2,0x7e9b03c8,0x22b601a9,0x20030035}}, // nsiz, יסקו, cāki_, ycji_,
+ {{0x7afc41f5,0x224041f6,0x60c200f7,0x62828009}}, // _kurt, _acik_, _íomh, ljoo,
+ {{0x660403ac,0x442a011c,0x316002d6,0xc60681e2}}, // kcik, _mdb_, _asiz_, _дзей,
+ {{0x6282c1f7,0x929401e2,0x39468084,0x752436b2}}, // njoo, даюц, otos_, ksiz,
+ {{0x394687a3,0x6e20c1f8,0x644e01e8,0x6ef58084}}, // ntos_, mamb, _obbi, _išba,
+ {{0x6e20c04e,0x3946af32,0x6ab786ab,0xc0cb3dac}}, // lamb, itos_, _अद्र, руме_,
+ {{0x58d41c8b,0x7d0d0037,0x14c78d1c,0xeeab1be5}}, // форт, wpas, रकरण, атак_,
+ {{0x644e41f9,0x7d0d0f33,0x39468612,0xc3338039}}, // _abbi, tpas, ktos_, _חוק_,
+ {{0x6457062f,0x7afc0102,0x6e20804f,0xa3e81130}}, // [2f50] texi, _aurt, iamb, मेस_,
+ {{0x6e20bc07,0x644e026c,0xd5b80d8e,0xc954007c}}, // hamb, _cbbi, іст_, _חמץ_,
+ {{0x68fbc1fa,0x7afc1ee5,0xdce4016b,0x2a648326}}, // _suud, _curt, cvič, _bamb_,
+ {{0x7d0d41fb,0x68fb8006,0xbcfb087a,0x6e20a994}}, // ppas, _puud, _brét, jamb,
+ {{0x661d0bb6,0xa9698dfd,0x21390147,0x21698258}}, // _iesk, сила_, nush_, сили_,
+ {{0xa5bb01a8,0x7afc41fc,0xacfa01c6,0x26cf81a1}}, // _ndói, _furt, _להשכ, _skgo_,
+ {{0x44210812,0x661d0181,0x7afc41fd,0x2a6680e4}}, // mah_, _kesk, _gurt, bdob_,
+ {{0x4421059c,0x6e208f18,0xbcfb07ca,0x213900f1}}, // lah_, gamb, _frét, kush_,
+ {{0x661d41fe,0x3946c1ff,0x395f9295,0x2d8b82be}}, // _mesk, ctos_, _usus_, âce_,
+ {{0x44210859,0x23bf035a,0x6aad1ea2,0x7afc02d0}}, // nah_, _एखाद, _amaf, _yurt,
+ {{0x44214200,0x6e209e98,0x2d82804a,0xbb428190}}, // iah_, bamb, øker_, неэк,
+ {{0x44214201,0x661d4202,0x6e20a623,0x213902f7}}, // hah_, _nesk, camb, fush_,
+ {{0x44210812,0xed58804a,0xe80281d0,0x24878242}}, // kah_, _мої_, रुका_, _tonm_,
+ {{0x20cb9e0b,0xf8bf157a,0xdce421d1,0x450900ab}}, // िविध, drés_, tvič, _লিংক_,
+ {{0x442114ec,0x66e62804,0x661d4203,0x39468019}}, // dah_, зова, _besk, ztos_,
+ {{0xa5bb002a,0x661d1f01,0x752422f8,0x442a1a1f}}, // _xeóg, _cesk, tsiz, _sdb_,
+ {{0x4421059c,0x7afc4204,0xf8bf3b51,0x442a02f7}}, // [2f60] fah_, _surt, grés_, _pdb_,
+ {{0x4421059e,0x7afc14ed,0x6e208dbb,0x7c218b81}}, // gah_, _purt, zamb, nalr,
+ {{0x6e209bcb,0xbcfb0036,0x7afc0085,0x6289810c}}, // yamb, _brés, _qurt, _boeo,
+ {{0x661d079f,0xbcfb0036,0x37d18264,0x254e0ec3}}, // _gesk, _prét, াধার, tēlā_,
+ {{0x44214205,0x22b8807a,0x6e20c206,0x62829bf1}}, // bah_, nčki_, vamb, rjoo,
+ {{0x44212254,0x98a395e0,0x6e208f0d,0x10a39017}}, // cah_, ниче, wamb, ничн,
+ {{0xb4d809a3,0x6e208545,0xfbc30221,0x7ae4291f}}, // ाती_, tamb, _збро, _ohit,
+ {{0x39468511,0x2bcf89a9,0x3da680e8,0x7e663d9c}}, // ptos_, _सुधा, зроб, _jakp,
+ {{0x6e20c207,0xa3e8009a,0x7e66419b,0xa49b4208}}, // ramb, मेर_, _makp, _awòy,
+ {{0x7ae42fc0,0x61e08bcf,0x7af60176,0x32068144}}, // _ahit, _izml, _aiyt, lcoy_,
+ {{0x6e208010,0x7ae40046,0xb5fb04be,0x623516cf}}, // pamb, _bhit, _baál, мену,
+ {{0x7ae44209,0x44214205,0x201e95c8,0xb4d8114f}}, // _chit, zah_, _heti_, ातु_,
+ {{0x4421355e,0x5bb903ca,0xa3d3000d,0xcfaa00ab}}, // yah_, _आश्व, _हुन_, _খুলন,
+ {{0x7ae40006,0x201e80ee,0x212d001b,0xa3b480e8}}, // _ehit, _jeti_, šeho_, _облі,
+ {{0xc7c40758,0x4421420a,0x4fc40ff7,0x661d04c4}}, // ести, vah_, еста, _pesk,
+ {{0x44210812,0xddc29807,0x443e95ce,0x201ec20b}}, // wah_, _množ, lgt_, _leti_,
+ {{0x4421059e,0x7e6603e9,0xbcfb046d,0x661d17ab}}, // [2f70] tah_, _dakp, _arér, _vesk,
+ {{0x645abd75,0x6728830b,0x443e81e3,0x656f05b9}}, // neti, _ovdj, ngt_, _vrch,
+ {{0x4421059c,0xbcfb03d3,0x661d0025,0x6abd420c}}, // rah_, _prés, _tesk, onsf,
+ {{0xd3668077,0xb184826f,0xf8bf07f1,0xdd9501e5}}, // _یه_, _šťas, prés_, _жамы,
+ {{0x442137b4,0x656f0c41,0x201e8102,0x22b601a9}}, // pah_, _urch, _beti_, cāku_,
+ {{0x68ff02c1,0x443e8a0f,0x44210455,0xe7e41344}}, // _muqd, jgt_, qah_, _गरमा_,
+ {{0x645ac20d,0x201e8038,0xbcfb420e,0x65628a03}}, // deti, _deti_, _trés, _asoh,
+ {{0x443ec20f,0x22b600eb,0x2d9596ba,0x68f701b4}}, // egt_, nākt_, дрос, _lixd,
+ {{0xdddd009a,0x62988029,0x7c218c44,0x3a2d810c}}, // _hasł, īvot, talr, _idep_,
+ {{0x7ae44210,0x443ec211,0x7305816c,0x201e807b}}, // _shit, ggt_, мпоз, _geti_,
+ {{0xe1ff05e4,0x9c87001b,0x7fb980d7,0x60c481a8}}, // rdón_, _začá, _بهار_, éime,
+ {{0xd5ae8bca,0x443e8a0f,0xe2970c73,0x648d81a8}}, // افی_, agt_, даю_, _cúir,
+ {{0x645ac212,0x648d80f7,0x8c458dbd,0x7c2988ed}}, // beti, _dúir, _желе, _долг_,
+ {{0x7ae40039,0x645ac213,0x7e664214,0x22b882d4}}, // _whit, ceti, _sakp, rčki_,
+ {{0x6d499a29,0x61fbc215,0x7ae44216,0x7e664217}}, // ltea, _ogul, _thit, _pakp,
+ {{0xe8d90028,0x61fbc218,0x7e69a7d1,0x7ae42637}}, // _trở_, _ngul, ndep, _uhit,
+ {{0x3ea01384,0x6fe71a1f,0x17fa01a8,0x75d30174}}, // [2f80] rhit_, técé, _وردة_, نيفا,
+ {{0x62860024,0x61fbc219,0xb4d800dc,0xc7b8805c}}, // ljko, _agul, ाते_, _leđa_,
+ {{0xe814053e,0xa3e8101c,0x307b0039,0x201ec21a}}, // _त्या_, में_, _ואינ, _reti_,
+ {{0x645ac21b,0x673c16f2,0x201ec21c,0xe9da8b41}}, // zeti, murj, _seti_, оке_,
+ {{0x443e8bfa,0xbcfb00e7,0xba480110,0x645ac21d}}, // ygt_, _fréq, _grįž, yeti,
+ {{0xd00f8624,0xf2bb1a3b,0x3a2da4a8,0x6d4980f7}}, // لله_, _उद्घ, _edep_, dtea,
+ {{0x201ec21e,0x290d8118,0x36d48878,0x321f8176}}, // _veti_, _etea_, нокр, _feuy_,
+ {{0xebe317ae,0xcfc000ab,0x6e24421f,0x769c810c}}, // _росп, _ইরান, naib, _béya,
+ {{0x645ac220,0x1ee88065,0x8146803d,0x63ae0084}}, // teti, _ہوئی_, _زنان, žinė,
+ {{0xe57100be,0x2bcf8c2d,0x443ec221,0x61fb8140}}, // אַט_, _सुहा, ugt_, _zgul,
+ {{0x443ec222,0xd6cfbe29,0x6e24009c,0xc7b8826c}}, // rgt_, _ат_, kaib, _feđa_,
+ {{0x645a825b,0x3a2008e9,0x6e2410e1,0x656280a4}}, // seti, _ceip_, jaib, _tsoh,
+ {{0x7c2e28e1,0x659691cc,0x6d49a9de,0xbcfb002a}}, // _odbr, _تجار, ctea, _asén,
+ {{0xe3af95e4,0x29000980,0x69b1820e,0x2bcfa769}}, // اری_, _duia_, _आइसी, _सुवा,
+ {{0xf3638048,0xf1b98916,0xbddb026b,0xceb40085}}, // _атын, _meša_, _abèp, fiə_,
+ {{0x1de285b3,0x76408683,0xdddb9487,0x6e240fd2}}, // _परात, ngmy, ljuš, gaib,
+ {{0x29004223,0x3d1a001b,0xd7fb1a4a,0x7d0d83ed}}, // [2f90] _guia_, मीले_, _мун_, _çast,
+ {{0x2a692d81,0xdddb80fe,0x66de83ed,0x61e01238}}, // _haab_, njuš, këko, ümle,
+ {{0x5fae8076,0x656d1e71,0x5a3595fd,0x6d498102}}, // _झेलल, svah, енет, ztea,
+ {{0x6e244224,0x38ad0267,0x6aa28428,0x99920176}}, // caib, džri_, ghof, _deyņ_,
+ {{0x7e6980e1,0xc7b881a1,0x49b8803d,0xa3b40c33}}, // vdep, _ređa_, واهد_, _जेठ_,
+ {{0x2bbe170c,0xfc4e827d,0x2a690df6,0xf09f07f1}}, // ्थशा, _sửng_, _laab_, mpàs_,
+ {{0xb906800d,0x46f5a856,0x7c254225,0xe7399480}}, // _यी_, нчит, mahr, чей_,
+ {{0x6d49ac77,0x2a6901c0,0x6aa281f8,0x61fb011f}}, // ttea, _naab_, chof, žulj,
+ {{0x81b400c8,0x7e69c226,0xee368048,0x2d99c227}}, // _জুন_, rdep, хны_, áse_,
+ {{0x6d49a174,0xb21b006a,0x31788019,0x442e8a03}}, // rtea, sjæl, _تحفظ_, _cdf_,
+ {{0x6d49c228,0x7a6a1c79,0x2bcf9a46,0x03228162}}, // stea, пинг_, _सुशा, лдэн,
+ {{0x9cea8117,0x6d49c229,0x51f70065,0x7c2500b9}}, // _ہونے_, ptea, کسپر, hahr,
+ {{0xbddb00e7,0x349581e2,0x442ec22a,0x2bcf8c2d}}, // _scèn, _падр, _fdf_, _सुरा,
+ {{0x92c200c8,0x6f018019,0x442e8350,0x7c250192}}, // ্গে_, _kulc, _gdf_, jahr,
+ {{0x6e24422b,0x3a200356,0x61dc801b,0x7bd88084}}, // taib, _teip_, _बर्ष, _gyvu,
+ {{0xb4d80e70,0x6609c22c,0x3ebf8364,0xbcfb02be}}, // ात्_, ncek, nnut_, _prép,
+ {{0x7c250352,0x6e24212b,0xed5700a9,0x69c10214}}, // [2fa0] fahr, raib, нот_, şler,
+ {{0x6441c22d,0x03a60604,0x6e24422e,0xa3be00d4}}, // ngli, низо, saib, ुअल_,
+ {{0x2018422f,0xdce98353,0x6e240637,0x2a6901b4}}, // mbri_, jveč, paib, _yaab_,
+ {{0x4425c230,0x2ebd0a74,0xe814081f,0xfaa39986}}, // hal_, ्कृत, _त्ता_, лахо,
+ {{0x4425c231,0x7d043557,0xaca384be,0x26d208ce}}, // kal_, qqis, _ayọk, moyo_,
+ {{0x6cd600f7,0x6aa2c232,0x7c2e006a,0xfbd10019}}, // _أقسا, shof, _udbr, اتے_,
+ {{0x442e9c88,0x59dc800f,0xf1b9825b,0x82a680e8}}, // _rdf_, _बरकर, _veša_, _завж,
+ {{0x3ebf8052,0x6f01c233,0x645e4234,0x61e40bcf}}, // gnut_, _dulc, lepi, _ozil,
+ {{0x4425c235,0x8ffa826a,0xfd0f853d,0x26c04236}}, // fal_, _فرار_, یجی_, inio_,
+ {{0x2a690006,0x4425c237,0xc5f30158,0x645e4238}}, // _saab_, gal_, נדז_, nepi,
+ {{0x2a6901c0,0x34b7025f,0x61e44239,0xc61f8264}}, // _paab_, תפים_, _azil, _ধারা_,
+ {{0x6ee5c23a,0x4425a82a,0x645e423b,0xa3d30d86}}, // tóbe, aal_, hepi, _हँस_,
+ {{0x26c0423c,0x44259357,0xa3e481a2,0x672a804a}}, // dnio_, bal_, पेज_, lsfj,
+ {{0x44259966,0x645e120e,0x7c2d8300,0x26c0423d}}, // cal_, jepi, _ɓark, enio_,
+ {{0x645e423e,0x7ea30074,0xdfd521f6,0x61e4423f}}, // depi, _lõpe, товы, _ezil,
+ {{0xa3e705b3,0xd6d83fe7,0x7bdb84de,0x7c2501ec}}, // _परब_, ету_, _מקוו, wahr,
+ {{0x661d8125,0xa2ce1391,0x2d5802c7,0x7d02827e}}, // [2fb0] ðski, सवर्, нить_, _juos,
+ {{0x6ac98f21,0x2d870e78,0xc60d8f12,0xc7b8826c}}, // िक्र, ćne_, _सभ्य_, _neđo_,
+ {{0x212b1cb6,0x2be60072,0x7d1d8e06,0x26d24240}}, // lsch_, _करणं_, ássz, boyo_,
+ {{0x4425c241,0xc5fe8ec3,0xe8d91029,0x00000000}}, // zal_, šējo_, _trồ_, --,
+ {{0x7d028110,0x212b0b2f,0x62999c11,0x68faa08e}}, // _nuos, nsch_, rkwo, _bitd,
+ {{0x6f01c242,0x7e6d4243,0x4425865f,0xf1b981a1}}, // _pulc, ldap, xal_, _fešn_,
+ {{0x2bb80013,0x23cfae06,0x6d4d4244,0x46a58abe}}, // _كافة_, _सुंद, ltaa, какв,
+ {{0x7e6d0393,0x44234245,0x44312b1f,0x6d4d05ee}}, // ndap, _hej_, _hdz_, otaa,
+ {{0x6d4d4246,0x64418352,0x6e22bf21,0xc05b80e8}}, // ntaa, tgli, _deob, _ніж_,
+ {{0x44231bae,0xeb99997b,0x1fa790ca,0xd90d8077}}, // _jej_, дио_, _прег, _هیچ_,
+ {{0x4425c247,0x44234248,0x28b685b3,0xfaa64249}}, // ral_, _mej_, _अगति, вано,
+ {{0x4423424a,0x645e0063,0xddc4002e,0x64418114}}, // _lej_, zepi, ndiţ, sgli,
+ {{0x6455003a,0x81d380ab,0x7afb8061,0x672481a1}}, // _obzi, হের_, _miut, ćije,
+ {{0x4423424b,0x7e7d424c,0x4425b81f,0xe3b99bc1}}, // _nej_, _ansp, qal_, _обл_,
+ {{0x26c0009a,0x6e228101,0x26d205ee,0x03a2804a}}, // tnio_, _yeob, toyo_, _вищо,
+ {{0x7c238082,0x6455271c,0x443102b6,0xc6c309a8}}, // _henr, _abzi, _adz_, айск,
+ {{0x6d4d2a22,0x7e6b849f,0xa2c0800f,0x26d23bb0}}, // [2fc0] gtaa, _magp, लचस्, royo_,
+ {{0x7afb8698,0xeb9a07b6,0x26c0424d,0x7e7d03ec}}, // _aiut, миз_, snio_, _ensp,
+ {{0x442301e9,0x8fa61287,0x6d4d057b,0x645e16d8}}, // _dej_, _ране, ataa, repi,
+ {{0xe2978554,0x7ae9b86c,0x7afb87e2,0x7e6b97ef}}, // ках_, _chet, _ciut, _nagp,
+ {{0x6d4d2f26,0x7ae981e4,0x7ceb01ec,0x6e228980}}, // ctaa, _dhet, dürf, _reob,
+ {{0x442314da,0xf7728b8c,0x212d8353,0xaca4019d}}, // _gej_, لاع_, _dveh_, _kpọs,
+ {{0x7d028364,0xb17d81ac,0x6b8305ec,0x6d5986c4}}, // _suos, _vzťa, ängt, _apwa,
+ {{0x7644424e,0xa3e7035a,0x4444424f,0x7afbc250}}, // ngiy, _परत_, lg_, _giut,
+ {{0xceb3004c,0x44444251,0xa2cf83eb,0xade380e8}}, // ייה_, og_, धकर्, ицьк,
+ {{0x7d028364,0x68fa85f8,0xb4ccc252,0xb86582e3}}, // _vuos, _uitd, लवे_, _مامو,
+ {{0xdeef9641,0x444420a4,0xa2951ae5,0x387e8012}}, // _вы_, ig_, гані, _intr_,
+ {{0x29048a73,0x7bdc03ac,0x7d028009,0x98b1026c}}, // _kuma_, _myru, _tuos, _čađu_,
+ {{0xf2d38158,0xe53487ac,0x2904c253,0x386cc254}}, // שער_, _сель, _juma_, _kadr_,
+ {{0x44440a0f,0x212b4255,0x2904c256,0x25a904e8}}, // jg_, rsch_, _muma_, vzal_,
+ {{0x6d408086,0x87040698,0xb8cb198e,0x29048b09}}, // luma, ияте, _खत_, _luma_,
+ {{0x44444257,0x7c28c258,0x7e60a5e0,0x6d4d0009}}, // eg_, ladr, nemp, ttaa,
+ {{0x6d40c259,0xdef8259a,0x7e6d14ff,0x44230069}}, // [2fd0] numa, тыр_, rdap, _pej_,
+ {{0x4444021e,0x6d4d2a57,0x7e60c25a,0x7ae9adef}}, // gg_, rtaa, hemp, _shet,
+ {{0x6d4d06dc,0x6d40c25b,0x50b80624,0x44230022}}, // staa, huma, _جديد_, _vej_,
+ {{0x4444425c,0x6d40be95,0x6d4d059c,0x7e60c25d}}, // ag_, kuma, ptaa, jemp,
+ {{0xdd868b8c,0x4423425e,0x7c28b962,0x6d40c25f}}, // _او_, _tej_, kadr, juma,
+ {{0x98e580f7,0x7c2880e1,0x6d40c260,0x6b8305ec}}, // دكتو, jadr, duma, ängs,
+ {{0x7e6b9856,0xa3e72b62,0x005804de,0x22490214}}, // _pagp, _परि_, דשות_, _ocak_,
+ {{0xdb240307,0x660d01bf,0x6d40b9fe,0x7c23a6f9}}, // _úsái, ncak, fuma, _senr,
+ {{0x6d40c261,0xb5fd920e,0x752d2a4d,0x41b780d4}}, // guma, ješe, nsaz, _अधिस,
+ {{0x64451023,0x3485976e,0x2a7f90af,0x224901bf}}, // nghi, угог, _hnub_, _acak_,
+ {{0x7c23862f,0x94758077,0x7e6bc262,0x64450014}}, // _venr, ندگا, _tagp, ighi,
+ {{0xc86902f6,0x6d40c263,0x7e6089c4,0x200100e5}}, // _מן_, buma, cemp, _aghi_,
+ {{0x661bbbab,0xf2d2810f,0x7c23c264,0x7bcaa4a3}}, // mbuk, מעט_, _tenr, _exfu,
+ {{0x91030628,0xb4bf000d,0x7c28c265,0x91ba8039}}, // спре, ुको_, cadr, תמשי,
+ {{0x2d8f1bc0,0x3f9e026f,0xb4c199e8,0xb8dc26ee}}, // ägen_, átu_, ्की_, _आग_,
+ {{0x444401c5,0x6b9c8edd,0x661bc266,0x2bb28697}}, // wg_, ørge, nbuk, ुपरा,
+ {{0x83fc8025,0x44444267,0x394d8069,0x6aa4004f}}, // [2fe0] ređe, tg_, _nqes_, _ilif,
+ {{0x444401e9,0x7e60b937,0xf0664268,0x2904c269}}, // ug_, zemp, _скоп, _ruma_,
+ {{0xe29ab93f,0x7e60932c,0xd62a0098,0xeb9707b6}}, // наж_, yemp, _поне_, лиф_,
+ {{0x44440046,0x6d40ad80,0x094a9505,0x386c80fc}}, // sg_, yuma, ечки_, _sadr_,
+ {{0x78b885a2,0x5f76053d,0x38a2807b,0xa3e80697}}, // tivv, _شاگر, _aðra_, मेज_,
+ {{0x4b26136d,0x386cc26a,0xddc2817f,0x2ee00713}}, // _معرف, _qadr_, _naoš, llif_,
+ {{0x6aa43ca6,0xd6d0a0bb,0x394206ae,0x3a24bf3c}}, // _olif, يقت_, huks_, _temp_,
+ {{0x1d349194,0xa534a748,0x661b9fdb,0x6b83016d}}, // ания, анич, gbuk, ånge,
+ {{0x7e60c26b,0x6280b724,0x22938013,0x656d826c}}, // remp, _inmo, _القس, _šahr,
+ {{0x6d40c26c,0x7e608821,0xd5638698,0x6aa4426d}}, // ruma, semp, стъп, _alif,
+ {{0x2ee00059,0x6d40c26e,0x661baceb,0xf8bf0e06}}, // klif_, suma, bbuk, ssék_,
+ {{0x2246c26f,0x7c28879f,0x6d40c270,0x753b8037}}, // ngok_, sadr, puma, bruz,
+ {{0x629d06be,0xc05b035f,0x753b9e00,0x7c28c271}}, // rkso, нів_, cruz, padr,
+ {{0x6fd60540,0x2a6d001b,0x6aa44272,0xdced011f}}, // _मुहू, žeb_, _elif, tvač,
+ {{0x62808629,0xb5fd8916,0x7d064273,0xa3b4123a}}, // _onmo, reše, _auks, _जेल_,
+ {{0x7d064274,0x7d0d80f7,0xb4bf064a,0xddc28390}}, // _buks, _éasc, ुक्_, _zaoš,
+ {{0x660d4275,0x7aed4276,0xa3b8816f,0x6e261699}}, // [2ff0] rcak, _ihat, _घेत_, _dekb,
+ {{0x6280c277,0x660d4278,0x752d2d08,0x68fe0122}}, // _anmo, scak, rsaz, _gipd,
+ {{0x7aed393e,0x6445180b,0x752d4279,0xb4b51344}}, // _khat, rghi, ssaz, _जगी_,
+ {{0x660291e8,0x7ceb427a,0xa3be903e,0x19b98110}}, // _ngok, würd, _ेशन_, куль_,
+ {{0x2169869b,0xa9698258,0x7aed01e4,0xa5bb0118}}, // тили_, тила_, _mhat, _xeól,
+ {{0xe739ab3f,0x7c8701bb,0xf4870221,0x3ea90061}}, // век_, _суме, _сумн, lhat_,
+ {{0xe8d901bc,0x7aed427b,0x75229210,0x07a59f50}}, // _apị_, _ohat, _awoz, райн,
+ {{0x7aed2ae3,0x753baa4d,0x8d55a138,0x7d0636b2}}, // _nhat, truz, аточ, _yuks,
+ {{0x661b94ec,0x7e6f051e,0x315880f7,0x78478019}}, // rbuk, _macp, _مجلة_, _مضام,
+ {{0x753ba579,0xdd99952c,0x6602811e,0xb4c19299}}, // rruz, ryň_, _egok, ्के_,
+ {{0xe2860fe7,0x61e10201,0x23cf85fc,0x7aed01e4}}, // ални, əlli, _सुखद, _bhat,
+ {{0x7f438098,0x753b82a5,0x39420074,0x25b6803d}}, // munq, pruz, tuks_, اهید_,
+ {{0x7aed427c,0x6e260ae6,0x7f438081,0xcad68039}}, // _dhat, _rekb, lunq, שורת_,
+ {{0x8c428c4f,0x7c2701d0,0x7d06427d,0x7ebe9d46}}, // _деше, _nejr, _ruks, rūpe,
+ {{0x7d06427e,0x6d5d06f7,0xd62a205f,0x8c2400ab}}, // _suks, _apsa, кове_, _পালন_,
+ {{0x4427c27f,0xa3ea8076,0x3ea94280,0xa5bb0207}}, // _hen_, _मरत_, ghat_, _teól,
+
+ {{0xcbe20a49,0x4427c281,0x0c260103,0xe9df01df}}, // [3000] _বলেছ, _ken_, иман, lgún_,
+ {{0x4427800d,0xac83176e,0x7d060bcf,0x25adb075}}, // _jen_, огул, _vuks, izel_,
+ {{0x44278039,0xa3d30063,0xe9df040e,0x6e264282}}, // _men_, _हुआ_, ngún_, _tekb,
+ {{0x4427b404,0x3ea94283,0x7d064284,0x5b5683de}}, // _len_, chat_, _tuks, ישען_,
+ {{0x7c27006a,0x6d5d011b,0x19588a41,0x4427c285}}, // _fejr, _gpsa, уалы_, _oen_,
+ {{0xb4c18023,0x61e980eb,0x64a64286,0xddcd0493}}, // ्को_, _dzel, _тага, leaş,
+ {{0x61e9c287,0x25ada948,0x6d440493,0xb4c181d0}}, // _ezel, ezel_, luia, ्कै_,
+ {{0x83fc920e,0x3ea04288,0x442782f9,0xe297b73a}}, // jeđa, nkit_, _aen_, _кас_,
+ {{0xe8fa8c9b,0x3b078216,0xc7b881f4,0x74c68081}}, // _ала_, _aunq_, _međi_, ищож,
+ {{0x7aed2e93,0x44278e3a,0x7ae2c289,0xaa1f001b}}, // _shat, _cen_, llot, पर्छ_,
+ {{0x44278257,0xb4790158,0x7aed428a,0x9e668b88}}, // _den_, אָרי, _phat, _введ,
+ {{0x44279302,0x96fa8829,0xd90f1ab3,0x6b81428b}}, // _een_, _شعار_, ویب_, _álge,
+ {{0xa3d30063,0x4427b778,0xad9b0032,0x7ae28037}}, // _हुई_, _fen_, _agún, ilot,
+ {{0x44278f23,0xa8a416d9,0x7aed2c04,0xa3e70074}}, // _gen_, орск, _what, _परल_,
+ {{0x7c658288,0x7aed428c,0x442cc28d,0xb5fdc28e}}, // _دانل, _that, mad_, meša,
+ {{0x442780ad,0x93fb8158,0x7c27428f,0x442cc290}}, // _zen_, _בלוי, _sejr, lad_,
+ {{0x44278077,0x3ea94291,0x78bc0118,0xad9b0091}}, // [3010] _yen_, rhat_, birv, _egún,
+ {{0x442c888b,0x442700f7,0x3ea94292,0xe8d901bc}}, // nad_, ún_, shat_, _kpọ_,
+ {{0x61e98019,0x7c27006a,0x3ea68722,0x6448809c}}, // _szel, _vejr, _olot_, igdi,
+ {{0x442cc293,0xdd910065,0x3ea04294,0x42d8902a}}, // had_, _فوج_, ckit_, афія_,
+ {{0x8d8707a1,0x290101e0,0x777a8102,0x6d440162}}, // _тунд, _kiha_, _artx, cuia,
+ {{0x4174819f,0x442c8665,0xb5fd920e,0x6e2d02a3}}, // غانس, jad_, ješa, laab,
+ {{0x442cc295,0x4427b665,0xb5fd8024,0x290104c4}}, // dad_, _ren_, deša, _miha_,
+ {{0x4427c296,0xa3d30d38,0x6e2d02ec,0x3ea6abd5}}, // _sen_, _हुए_, naab, _clot_,
+ {{0xddc60db7,0x17e40697,0x442cc297,0xf2d280be}}, // _kakš, _गर्व_, fad_, _זען_,
+ {{0x442cc298,0x3eb900b9,0x44278168,0xdce28115}}, // gad_, _lmst_, _qen_, _brođ,
+ {{0xe29a2118,0x4dde03bb,0xc62680c8,0x3ea6813c}}, // ган_, _कुनै_, _যারা_, _flot_,
+ {{0x4427c299,0x3a290219,0x6e2d01b4,0x7c3b00fc}}, // _wen_, _ceap_, jaab, _ɓura,
+ {{0x442cc29a,0x83fc8904,0xc7b881a1,0x442781c6}}, // bad_, ređa, _ređi_, _ten_,
+ {{0x7d1bc29b,0x442cc29c,0xb5fd85a2,0x7cf002f1}}, // mpus, cad_, ceša, märg,
+ {{0x2bcf8d14,0x2901033e,0x31ae8061,0x78bc008b}}, // _सुचा, _diha_, _közé_, rirv,
+ {{0x539a84de,0x7649c29d,0x422619a5,0xb2261ad0}}, // _תיקו, ngey, _удив, _умил,
+ {{0x395f81c0,0x290101b9,0x3ea01619,0x7e64429e}}, // [3020] _npus_, _fiha_, rkit_, reip,
+ {{0x6296429f,0x3ea02d5c,0xbcfb026b,0x61e08338}}, // _hoyo, skit_, _apéb, _ryml,
+ {{0x395fc2a0,0x2d8f006a,0x6e2d1a25,0x62961a85}}, // _apus_, øger_, baab, _koyo,
+ {{0x442c825b,0x20058098,0x63ae1487,0x6e2d0079}}, // zad_, _agli_, _đinđ, caab,
+ {{0x442caa22,0x6d4426f0,0xc05a85a8,0x629605fe}}, // yad_, quia, гій_, _moyo,
+ {{0x7ae2838e,0x940736ae,0x987a00be,0x28f89b53}}, // slot, _герб_, ראַט, шель_,
+ {{0x442cacdd,0x7ae2870d,0x443842a1,0xb4d442a2}}, // vad_, plot, _idr_, हकी_,
+ {{0x442a42a3,0x442cc2a4,0x20058081,0xe6668ea6}}, // _heb_, wad_, _egli_, стко,
+ {{0x442cc2a5,0x442a42a6,0x7d1babb9,0x7c3c8511}}, // tad_, _keb_, gpus, órro,
+ {{0xdce2805c,0xfd4d0135,0x442a00eb,0x26dd81bc}}, // _prođ, _achị, _jeb_, _ekwo_,
+ {{0x442cc2a7,0xef1a95fd,0x443842a8,0xf1b9979d}}, // rad_, _сме_, _mdr_, _reši_,
+ {{0x442cc2a9,0x442a42aa,0x6e2d0079,0xe8d9001c}}, // sad_, _leb_, xaab, _trừ_,
+ {{0x645c26f0,0x588707ac,0xb5fdc2ab,0x81da80ab}}, // _obri, быва, peša, ডের_,
+ {{0x442a3ca3,0x224d42ac,0x6e2d0ef2,0x442c9669}}, // _neb_, żek_, waab, qad_,
+ {{0x49930077,0xdce280d2,0x6e2d257a,0xdbf1001b}}, // زیگر, _urođ, taab, _přím,
+ {{0x629604b9,0x237f05f3,0x59da3947,0x6284051e}}, // _goyo, _čuj_, _युवर, _gnio,
+ {{0x38ab8aa2,0x442a028d,0xca380039,0xd8388267}}, // [3030] _høre_, _beb_, _הנחה_, _elči_,
+ {{0x39468c52,0x38ab813c,0xa3cc809a,0x6e2d1a25}}, // duos_, _køre_, _रखा_, saab,
+ {{0x442a42ad,0x395f802e,0xb5fd920e,0x62960010}}, // _deb_, _spus_, ješn, _yoyo,
+ {{0x645c0114,0x442a42ae,0x7c2ac2af,0x38ab80e8}}, // _ebri, _eeb_, _lefr, _møre_,
+ {{0xc7b88025,0xddc60353,0x3946c2b0,0x2bd90105}}, // _među_, _takš, guos_, _बुरा,
+ {{0xb608003e,0x7cf0016d,0x6d40825b,0xb5fb0032}}, // _vyšš, gärd, drma, _abág,
+ {{0x6d40a525,0x7cef83ba,0x26c90c93,0x44210865}}, // erma, nøre, onao_, mbh_,
+ {{0x442a01c5,0x6f0294ed,0x44210046,0x6aa28c54}}, // _zeb_, _cioc, lbh_, ckof,
+ {{0xa2d68b84,0x44210ad0,0x1a6880d5,0x764982a3}}, // यकर्, obh_, _دینی_, rgey,
+ {{0x40349ddf,0x44210cb5,0x442a022c,0x6569c2b1}}, // _неус, nbh_, _xeb_, rweh,
+ {{0x61ed42b2,0xda6634d6,0x4421152f,0x6d40c2b3}}, // _azal, овни, ibh_, arma,
+ {{0x6f028698,0x2a720079,0xc3320496,0x38ab8366}}, // _gioc, _qayb_, ווט_, _døre_,
+ {{0xdce9803b,0x7cf012d2,0x3ead9f20,0x7de791cc}}, // jveć, läre, mhet_, _اسام,
+ {{0x2ee20ebf,0x03a598a2,0x7c2a9ab0,0x7cef8b40}}, // पत्त, _мило, _gefr, føre,
+ {{0x38ab8022,0x7d0bb6bd,0x656f42b4,0x7cf00884}}, // _gøre_, _hugs, _asch, näre,
+ {{0x442a42b5,0xe61a197b,0x3eada5be,0x7d03c2b6}}, // _seb_, рда_, nhet_, _hins,
+ {{0x442a146a,0x7d03c2b7,0x6e2b8019,0xe1ff0511}}, // [3040] _peb_, _kins, _megb, león_,
+ {{0x7d03c2b8,0x6e2bc2b9,0x6aa9846d,0x442a0069}}, // _jins, _legb, _alef, _qeb_,
+ {{0x7d03c2ba,0xbcfb03d3,0x7cf02e6d,0x8b6a0009}}, // _mins, _spéc, värd, риев_,
+ {{0x442a3c72,0x6aa9c2bb,0xf99f00e7,0x4421032f}}, // _web_, _clef, ccès_, abh_,
+ {{0x442a10af,0x644302ba,0x3eadc2bc,0x443802f7}}, // _teb_, ónic, dhet_, _tdr_,
+ {{0x98a39194,0x7d03c2bd,0x7cf0016d,0x2a6690e4}}, // миче, _nins, färe, seob_,
+ {{0xf093093f,0x7d0b8029,0xfaa68084,0xb86580f7}}, // ונג_, _augs, _даво, ثانو,
+ {{0x7d03adb3,0x3ead904d,0x60dc0057,0x7d0b9a76}}, // _ains, ghet_, horm, _bugs,
+ {{0x61e4019f,0x6d561072,0x60dc0061,0x7d03c2be}}, // _nyil, ntya, korm, _bins,
+ {{0x26c9003b,0xb5fd9734,0x59c086b7,0x7d038b16}}, // znao_, pešn, _शेयर, _cins,
+ {{0x7d03c2bf,0x61ed2422,0xdee69285,0x6f028635}}, // _dins, _szal, _дожи, _tioc,
+ {{0x7d03c2c0,0x2bbd0bb8,0x3eadbfb6,0xddcd0162}}, // _eins, ्पता, chet_, ndaţ,
+ {{0x7d03b43f,0x672d812b,0x26c9017f,0x61e40a03}}, // _fins, ćaje, vnao_, _cyil,
+ {{0x7d03c2c1,0x1c0e8fd5,0x7cef8aa2,0xf7730c48}}, // _gins, _सलिल_, tøre, واز_,
+ {{0x09b7885d,0xb4e6816f,0x788080e1,0xeabf00e5}}, // _अध्य, पती_, _závä, più_,
+ {{0x7d03c2c2,0x9d15176e,0x7cef83ba,0x69da0162}}, // _zins, здач, røre, ştea,
+ {{0x61ed035c,0x6d5602d5,0x7d03b6ba,0x5fdb2a2b}}, // [3050] _uzal, gtya, _yins, _मुरल,
+ {{0x3eadc2c3,0xd37b42c4,0xde8f80ff,0x92c780ab}}, // zhet_, иче_, _kịch_, _উঠে_,
+ {{0x44210ad0,0xdce9803b,0xf8bf00e7,0xd7df816f}}, // rbh_, sveć, ssés_, _पुढच,
+ {{0x1dca82f1,0x656f3a3c,0x69c000d4,0x442142c5}}, // ापात, _tsch, _लेडी, sbh_,
+ {{0xde8f8104,0x249817af,0x656f1075,0xa92480e8}}, // _lịch_, _form_, _usch, дділ,
+ {{0x3cf6800f,0xc5fa0039,0x7d0b8110,0x6ee581df}}, // ीदों_, _לפרט, _rugs, móbi,
+ {{0x6562c2c6,0x656d42c7,0x248980ff,0x3eadc2c8}}, // _apoh, lwah, _đam_, thet_,
+ {{0x32090028,0x764d42c9,0x7d03c2ca,0x6abb8cb7}}, // _ngay_, ngay, _sins, _umuf,
+ {{0x3eadc2cb,0x7d03c2cc,0x3ebf80f1,0x656d42cd}}, // rhet_, _pins, riut_, nwah,
+ {{0x2ca991b3,0x3eadc2ce,0x32090bb1,0x6e2b9591}}, // скоп_, shet_, _agay_, _wegb,
+ {{0x3c21811b,0x7d039020,0x61e403ac,0x60dc1e7a}}, // _vгv_, _vins, _syil, vorm,
+ {{0xde8f8104,0x7d039412,0x656d0518,0xa96a419a}}, // _dịch_, _wins, kwah, бима_,
+ {{0x290580f6,0xb7bc8029,0x7d0381b9,0x49990198}}, // _kila_, _reģi, _tins, отря_,
+ {{0x23bd1d40,0xa3ab0b85,0x764d0ef2,0x320902c4}}, // ्पाद, गना_, egay, _egay_,
+ {{0x17790364,0xfaa634c4,0x29058695,0xd00f1a00}}, // ость_, _нако, _mila_, الف_,
+ {{0x60dc2cf0,0x539a81c6,0x6b9c9d31,0xf77283de}}, // sorm, _ליצו, ärge, עקן_,
+ {{0x60dc10c1,0xb5fb0118,0xa11300f7,0xdb0d01d6}}, // [3060] porm, _abáb, _كويت, ľkým,
+ {{0x290584d2,0x6aac1244,0xb17b03c8,0xf8a5826a}}, // _nila_, _चतुर, _פטיר, _شک_,
+ {{0x389b0f60,0x249842cf,0x309b0039,0x6d5618ac}}, // _היינ, _vorm_, _השימ, stya,
+ {{0x2905ab40,0xf1e10424,0xe9f90091,0x3a2d8197}}, // _aila_, _गुड़_, _alẹ_, _ceep_,
+ {{0xbefa000f,0x6e240365,0x249842d0,0x055523e7}}, // ्दीन_, mbib, _torm_, _отря,
+ {{0x2905820f,0x6e24004a,0xa3cc82f1,0x7ceb22f8}}, // _cila_, lbib, _रखल_, nürl,
+ {{0x36d4af49,0x38cb815b,0xa8570039,0x2905bcb2}}, // мокр, حانی_, פיקה_, _dila_,
+ {{0x877b00be,0x6e2405ee,0x6d4412a5,0x7c2e4290}}, // מאצי, nbib, oria, _hebr,
+ {{0x6ce681e5,0x6d49818a,0x29058558,0x614396cf}}, // зіне, guea, _fila_, _пета,
+ {{0x29058057,0x6d4442d1,0xf1b981a1,0xd00f81a8}}, // _gila_, iria, _vešt_, _ملك_,
+ {{0xe45700be,0x6d4442d2,0xccf881d0,0x7ceb02d0}}, // הייט_, hria, rmě_, dürl,
+ {{0x2905be50,0xa3bd8035,0x6d442f52,0xdc02936f}}, // _zila_, _इधर_, kria, _učím,
+ {{0x867b8039,0xc50a80f7,0x6d441b6b,0x69da0087}}, // _לראו, _اتصل_, jria, şten,
+ {{0xddeb893f,0xb1138a2c,0x200c35ca,0x6d4442d3}}, // _פֿאָ, wụkw, ždin_, dria,
+ {{0x7127026a,0x7b0681d0,0xd7f808b0,0x443c81e8}}, // _کربل, ístě, зут_, _idv_,
+ {{0xdce4003b,0x56941d85,0x442e8125,0x6e2402b8}}, // dviđ, наст, _hef_, gbib,
+ {{0x7c2e1c2c,0x1df801bb,0x764d42d4,0x5c990fe6}}, // [3070] _bebr, зеты_, rgay, окая_,
+ {{0x7c2e42d5,0xd01104c1,0x656d42d6,0xe8d30065}}, // _cebr, _جلد_, rwah, _سمجھ,
+ {{0x656d42d7,0xdced0a20,0x7c2e0eb9,0x98ab0087}}, // swah, hvać, _debr, ască_,
+ {{0x2905c2d8,0x38af0192,0x442ec2d9,0x3f57011c}}, // _sila_, _würd_, _lef_, mçu_,
+ {{0x29059341,0x6d44041c,0x63ad8326,0x3f57011c}}, // _pila_, cria, _ƙank, lçu_,
+ {{0x7c2e0574,0x6aad2d6f,0x26f79053,0x442ec2da}}, // _gebr, _llaf, ुद्र_, _nef_,
+ {{0xb4d9035a,0xb8f589f2,0x3f5742db,0xa3d40071}}, // ावी_, _हद_, nçu_, _зорч,
+ {{0x443342dc,0x443c9fcd,0x7c2e42dd,0x317f82f7}}, // lax_, _adv_, _zebr, _truz_,
+ {{0x2905c2de,0xa91d80d2,0x442ec2df,0x7cf01cab}}, // _tila_, _brže, _bef_, lära,
+ {{0x6aad42e0,0x4c69b7cd,0x6e240bcf,0x443c8299}}, // _alaf, цион_, zbib, _cdv_,
+ {{0x62030086,0x443c8353,0x60c38a6e,0x32460048}}, // ətlə, _ddv_, minm, менг,
+ {{0x60c3c2e1,0x7d070a0f,0x7c250cb5,0x929d809a}}, // linm, _lijs, hbhr, _miło,
+ {{0x7ceb3ea8,0x6d49c02b,0x3ea2816d,0x27e08214}}, // türl, quea, ökte_, ğine_,
+ {{0x1dd8000f,0xfbc58a29,0xb69b03a7,0x60c38041}}, // _भुगत, _обло, _diâm, ninm,
+ {{0x66e6141e,0x7c2e1f1f,0xdee6280f,0x44330079}}, // дова, _rebr, дови, dax_,
+ {{0x26f80074,0xb5fd911b,0x26cd8118,0x7d0f2ef8}}, // ंगेर_, ješk, nneo_, _bucs,
+ {{0x38af0214,0x7c2e42e2,0xed5700a9,0x60c3a008}}, // [3080] _süre_, _pebr, мот_, kinm,
+ {{0xf8d20327,0x6f062a0b,0x443301b4,0x6d442816}}, // _सदिय, _wikc, gax_, rria,
+ {{0xddcd42e3,0xbddb09c4,0x7cf00106,0x7d071799}}, // rdaš, _adèd, gära, _dijs,
+ {{0x9c7cc2e4,0x7d0f0350,0x6d4442e5,0xeb9680be}}, // moče, _fucs, pria, ודער_,
+ {{0x9c7c9fe7,0x26cd8279,0x657b026c,0x443301b4}}, // loče, dneo_, _šuhv, bax_,
+ {{0x1de1823c,0xfbd90996,0xab2a0098,0x60c3808b}}, // _पड़त, _बुकम, _мога_, ginm,
+ {{0xb4ea0af3,0xfc030012,0x443c8036,0xd83882d4}}, // मती_, _апро, _rdv_, _noče_,
+ {{0x3da692c0,0x442ec2e6,0x6b83013c,0x629b8122}}, // дроб, _sef_, ænge, _gouo,
+ {{0x443c8a20,0x63bb04b9,0xa91d812b,0x672d8904}}, // _pdv_, _ƙung, _prže, ćaja,
+ {{0x7ae442e7,0x6aad42e8,0x9c7c84c4,0x60c39040}}, // _akit, _slaf, koče, cinm,
+ {{0x7eb100e8,0x442eb9e6,0x9c7c9911,0xf77281a8}}, // _håpe, _vef_, joče, _جاء_,
+ {{0x1d0a9a34,0x7af61803,0xa2a180d4,0xe5c68c6c}}, // _деди_, _chyt, कॉप्, _осло,
+ {{0xa3088117,0x26c4abe9,0x25de0074,0xa91d82d4}}, // _کرتے_, mimo_, _कुली_, _trže,
+ {{0x3946956b,0x7ae442e9,0x1bd49bc1,0xd838a4b2}}, // iros_, _ekit, _поля, _foče_,
+ {{0x9c7c8353,0xa2bf80d4,0x248d001c,0x7d0700f3}}, // goče, लोड्, _đem_, _rijs,
+ {{0x26c4c2ea,0x2d82026c,0x83fc9351,0x443321e7}}, // nimo_, _brke_, jeđi, wax_,
+ {{0x44331cbe,0x2fdf009a,0x60c3aade,0x6722026c}}, // [3090] tax_, ług_, yinm, _čoje,
+ {{0xb4d92e2b,0x3f81026f,0x7afd0bcb,0x26c4b2db}}, // ावे_, _trhu_, lmst, himo_,
+ {{0x38ab8022,0x443342eb,0x26c48110,0x39469d24}}, // _børn_, rax_, kimo_, eros_,
+ {{0x26c481e2,0xd6270013,0x2d8200d2,0xd9f3016f}}, // jimo_, _تعدي, _frke_, _घरात_,
+ {{0x39468333,0x2d82017f,0xe1ff0019,0xb5fd8115}}, // gros_, _grke_, deók_, rešk,
+ {{0x7cf042ec,0x99d4803d,0x7b670997,0xe1f081a8}}, // pära, ختما, _отбе, جسم_,
+ {{0x60c384b9,0x83fc8088,0xc8788380,0x5b1416dd}}, // rinm, beđi, _dağ_, _амст,
+ {{0x3946840e,0xee398abe,0xfe719e13,0x3ea942ed}}, // bros_, чни_, ادث_, nkat_,
+ {{0x26cdc2ee,0x443107d9,0x98ef000f,0x3a3f8359}}, // rneo_, _kez_, छताछ_, _idup_,
+ {{0xb5fd86f7,0x7ae41fe6,0x16010592,0xd01180f7}}, // neši, _skit, लेयर_, _إلا_,
+ {{0x26c4c2ef,0x3ea942f0,0xc2c600f7,0x3b0900b9}}, // bimo_, kkat_, فيدي, _biaq_,
+ {{0x7cf000f2,0x6d4d42f1,0xeab20250,0x614617c8}}, // järn, kuaa, اعد_, нема,
+ {{0x8c458c24,0xb5fdc2f2,0x7e6d0162,0xc878807e}}, // _зеле, keši, deap, _yağ_,
+ {{0xb5fd920e,0x26fa01c4,0x7e7b88dc,0xb4d9326c}}, // ješi, ्द्र_, ldup, ावो_,
+ {{0xb4bd835a,0x7ea30006,0x46f61ccf,0x645c8722}}, // ीची_, _lõpu, ечат, òrie,
+ {{0x3ea942f3,0x7eaa026f,0x291f81bc,0xcf9280be}}, // gkat_, _výpo, _otua_, נטן_,
+ {{0xac8607a1,0x61e980d5,0x291f8282,0x3486002d}}, // [30a0] нгал, _nyel, _ntua_, нгаг,
+ {{0x23d63408,0x44310038,0xb4ea058c,0x26c4912e}}, // нцер, _cez_, मते_, zimo_,
+ {{0x7b090182,0x443133f3,0x291fc2f4,0x61e9c2f5}}, // _üstü, _dez_, _atua_, _ayel,
+ {{0x39469313,0x6d5bc2f6,0xe8d90028,0xa50a024b}}, // tros_, ktua, _trợ_, _нема_,
+ {{0x26c4c2f7,0x83fc8052,0x44310073,0xf53981ac}}, // vimo_, ređi, _fez_, jsť_,
+ {{0x39468fba,0x672d9601,0x20070722,0xa5bb42f8}}, // rros_, ćajn, _únic_, _teór,
+ {{0xcb671afa,0x291229e2,0x2d8042f9,0x25fc109b}}, // _паре_, _luya_, lvie_, लेरी_,
+ {{0x38b4016d,0x6d5b8168,0x6e362676,0x2d800037}}, // _färg_, ftua, nayb, ovie_,
+ {{0x26c4c2fa,0x7ae294c7,0x2d8037ba,0x6d5bc2fb}}, // rimo_, moot, nvie_, gtua,
+ {{0x2240103b,0x387a01ac,0x7ae2b0c9,0xd5e2819d}}, // _adik_, _napr_, loot, _anọ,
+ {{0x7984380a,0x26c49c2d,0xe7e00e70,0xd00f80f7}}, // _ariw, pimo_, _खुला_, _الم_,
+ {{0x7ae28a52,0x291204bb,0x9e739ef7,0x79840286}}, // noot, _buya_, _مهمت, _briw,
+ {{0x64a6a549,0xf2d38158,0x24588554,0x29120876}}, // _пада, רער_, _жаль_, _cuya_,
+ {{0x79840c0b,0x26c208ae,0x6b8388ae,0x2ed9036d}}, // _driw, _amko_, _frng, भक्त,
+ {{0xe7db0a16,0x64432848,0x79840c2e,0x7e6d0087}}, // _मुखप, ónim, _eriw, teap,
+ {{0x443142fc,0x6d4d07b8,0x63a51e1e,0x3cfe8037}}, // _sez_, tuaa, vyhn, lmtv_,
+ {{0x25fc035a,0x628d0010,0x443101ca,0xdce4009a}}, // [30b0] लेली_, _inao, _pez_, zwią,
+ {{0x26c208ae,0x3ea942fd,0x2f150106,0x6d4d0299}}, // _emko_, skat_, _tåg_, ruaa,
+ {{0x443110dd,0x6e360461,0x92598009,0x61e980b9}}, // _vez_, bayb, дает_, _syel,
+ {{0x61e981e0,0x443142fe,0x3ea903ed,0x9f961101}}, // _pyel, _wez_, qkat_, _סדרה_,
+ {{0x443142ff,0x628d4300,0x644a12ca,0x38b40338}}, // _tez_, _mnao, ófic, _färd_,
+ {{0xf771845b,0xf745bacb,0xfaff0168,0x6d5b81c0}}, // لاب_, веко, hmë_, vtua,
+ {{0x6a159a80,0xd7fb8162,0x7e7985ee,0xe2d980d7}}, // _амбу, чуд_, _pawp, _قارچ_,
+ {{0xfaff020f,0x629f02a3,0x6d5bc301,0xa3e60072}}, // jmë_, _noqo, ttua, _पडत_,
+ {{0xc21284de,0x7cf008dc,0x7e7b839c,0x7003019d}}, // _כהן_, kärl, rdup, ọchu,
+ {{0xb4bdae2b,0x628d4302,0x27e08162,0xb4cb8072}}, // ीचे_, _anao, şina_, रचे_,
+ {{0x6d5bb150,0x629f02a3,0xf4120039,0x2120025b}}, // stua, _boqo, יפי_, _stih_,
+ {{0x68e38a40,0x6d5bc303,0x29124183,0x83fc9351}}, // lond, ptua, _puya_, jeđu,
+ {{0x6e298359,0x645c8722,0x6a3a81c6,0x7cf00338}}, // mbeb, òric, וגרפ, färl,
+ {{0xdca28572,0x2eedc304,0x64a2b317,0x661d4305}}, // _ваши, llef_, _ваша, _afsk,
+ {{0xafe611d2,0x291204b9,0x20cd0267,0x7ec789c4}}, // товл, _wuya_, džid_, _gèpè,
+ {{0x661b9599,0x68e3c306,0xfe431b47,0x29124307}}, // ncuk, hond, инсо, _tuya_,
+ {{0x9c7c9f95,0x6d49c308,0xec7a02a4,0x657b026c}}, // [30c0] loča, nrea, мпа_, _šuhr,
+ {{0x6d49954c,0x31370051,0xa3d59344,0x293704de}}, // irea, _שנים_, _सखा_, _שאין_,
+ {{0x6d49c309,0xaac60105,0x4ac60074,0x38b4016d}}, // hrea, _लगाक, _लगाव, _värd_,
+ {{0x23270073,0xdcfb8bcf,0x0a678195,0xc4c40264}}, // тоци_, dvuč, врши_, ্তেজ,
+ {{0x90cb011b,0x7ae29328,0x75431431,0x6d498390}}, // _vvрv, root, _княз, jrea,
+ {{0x68e3c30a,0x91bb0039,0x6f0b8706,0x48d080ab}}, // gond, ומני, _aigc, িত্র,
+ {{0xb8ea8bb8,0x7bc7bdcc,0x2a7d81c0,0x547b01c6}}, // _लग_, újul, bdwb_, _קטלו,
+ {{0x9c7c805c,0x6d49a086,0xb7db03c8,0x63a3430b}}, // doča, frea, וקצי, änne,
+ {{0xa91dc30c,0x6ee581ac,0x44208300,0x09068196}}, // _trža, tóbr, _ƙi_, _апан,
+ {{0x8ffa9125,0x21269083,0x68e3914e,0xd838a4b2}}, // _قرار_, mpoh_, cond, _foča_,
+ {{0xda3507ac,0x6d49c30d,0x9c7c8353,0x7cf0016d}}, // лены, area, goča, värl,
+ {{0xa91d803a,0x8aa70012,0x2eed8242,0x02a70425}}, // _mržn, трод, clef_, тром,
+ {{0x61f6009a,0x6d498051,0xfaff00f1,0x290c8300}}, // _czyl, crea, rmë_, _kida_,
+ {{0x929d809a,0x290c86c0,0x7522ac18,0xfaff0168}}, // _piłk, _jida_, _itoz, smë_,
+ {{0x290cc30e,0xb4de8076,0xcfa4002e,0x40340198}}, // _mida_, थकी_, ашти, ресс,
+ {{0x2f188117,0xa3e6000f,0x68e3c30f,0x628d022e}}, // _még_, _पड़_, zond, _unao,
+ {{0x83fc805c,0xdcfb8289,0x68e3c310,0x261903b7}}, // [30d0] ređu, zvuč, yond, युगी_,
+ {{0x6adba9b7,0xb5fd8eef,0x61ed4311,0x5f95154f}}, // यक्र, mešt, _iyal, _биот,
+ {{0x68e38a0f,0xddc40110,0x6aa0913b,0x6d49c312}}, // vond, reiš, _bomf, zrea,
+ {{0x68e380c9,0x7b643e29,0x6e95069c,0x61ed4313}}, // wond, ртфе, _симу, _kyal,
+ {{0x68e3c314,0x38b402af,0x290c95d0,0xb5fd943c}}, // tond, _wäre_, _bida_, nešt,
+ {{0x6d49b760,0x290c81b4,0x61ed3ebe,0xfc4781d6}}, // vrea, _cida_, _myal, číky_,
+ {{0x53343725,0xf094012a,0x290cc315,0xdce4009a}}, // _вест, ינס_, _dida_, jwię,
+ {{0x68e3c316,0x7cf008e5,0xf770803d,0xe579951b}}, // sond, järj, تاه_, ези_,
+ {{0x68e383d3,0xb5fd82fd,0x61ed4317,0x6e29c318}}, // pond, ješt, _nyal, rbeb,
+ {{0x290cafd5,0x68e3829b,0x9c7cc319,0x38ab83ba}}, // _gida_, qond, toča, _mørk_,
+ {{0x7afb8c9e,0x61ed431a,0x752281bc,0x2d8681e8}}, // _khut, _ayal, _etoz, _eroe_,
+ {{0x7e7d431b,0x9c7c8353,0x290c8668,0xe29a431c}}, // _nasp, roča, _zida_, хам_,
+ {{0xc95601bb,0x38c8003d,0x9c7cc31d,0x78a1a1b4}}, // ытны, ناسی_, močn, _kolv,
+ {{0x9c7cc31e,0x3eada28f,0xdddd009a,0x438580f7}}, // ločn, lket_, _obsł, _ملتق,
+ {{0xaa7b0125,0x7ae9c31f,0x2b180fd5,0x61ed4320}}, // glýs, _oket, _बंधु_, _eyal,
+ {{0x9c7c826f,0x7e7d4321,0x7ea30074,0x7ae9819d}}, // nočn, _casp, _sõpr, _nket,
+ {{0x61ed1eb7,0x0d860196,0x7e7d4322,0x95860fe6}}, // [30e0] _gyal, ылан, _dasp, ылае,
+ {{0x7cf04323,0xddc6027f,0x2718007a,0xa91d8bda}}, // märk, _takž, _učno_, _pržn,
+ {{0x39400a38,0x3ead93e1,0x7afba2f5,0x290cc324}}, // _hvis_, kket_, _bhut, _rida_,
+ {{0xe9da8895,0x290c92cf,0x7afbc325,0xbae5835f}}, // нке_, _sida_, _chut, ацій,
+ {{0xc1e78987,0x290c8661,0x10a682a4,0x5a33804a}}, // льше_, _pida_, лидн, сніт,
+ {{0xa91d8805,0x7ae9c326,0x3eadb42b,0x7d1b02df}}, // _tržn, _eket, eket_, _éusa,
+ {{0x69dc809a,0x657980b9,0x7d0e01b4,0x3b0d8609}}, // _śred, _aswh, _iibs, _fieq_,
+ {{0x443a4327,0xdd908591,0xd6d78fe6,0xe8d9019d}}, // lap_, قوب_, лты_, _irụ_,
+ {{0xbcfb00f7,0x45d484bd,0xb5fb0032,0x290c821e}}, // _spéi, ропс, _abáj, _tida_,
+ {{0x443a4328,0xb5fd8eef,0x5b26976e,0x78a1baf2}}, // nap_, vešt, льма, _golv,
+ {{0x39404329,0x61ed00dd,0xdfcf80f7,0xdeefa462}}, // _avis_, _syal, _فيك_, _гы_,
+ {{0x3ead9cd6,0x7d0e02a6,0x7eb581d6,0x63a897db}}, // cket_, _libs, _pápe, rydn,
+ {{0xf4868110,0x61ed05ee,0x7d0e0706,0x7522c32a}}, // _буйн, _qyal, _oibs, _utoz,
+ {{0x6b9c8022,0xed578051,0x443a07d5,0x26c68069}}, // ørgs, _עבור_, jap_, _hmoo_,
+ {{0x443a14ff,0x8c1b8039,0xb8dd016f,0x69c03382}}, // dap_, _אווי, _आत_, úmer,
+ {{0x4ea4432b,0x7d0e0362,0x7c3ac32c,0xaa7b008b}}, // бруа, _aibs, latr, rlýs,
+ {{0x7e7d1c67,0xe8d9082e,0x443a2e83,0x26c681bc}}, // [30f0] _vasp, _arụ_, fap_, _mmoo_,
+ {{0x443a432d,0xaa7b0125,0x7e7d0b17,0x25a9432e}}, // gap_, plýs, _wasp, ryal_,
+ {{0x25a91b71,0xd3710624,0x7afb81e4,0x7c3a847f}}, // syal_, تها_, _phut, iatr,
+ {{0xf53f04b8,0x78a1c32f,0x8f9a010f,0x7c3ac330}}, // _två_, _solv, _סירי, hatr,
+ {{0x6e2d4331,0x210f92ee,0x78a1b1d7,0x7c3ac332}}, // mbab, ादेश_, _polv, katr,
+ {{0x443a0886,0x7c3a805c,0x07a58698,0x6e2d4333}}, // cap_, jatr, иалн, lbab,
+ {{0x78a1c334,0x9c7c8038,0xc4d20039,0x7afbc335}}, // _volv, točn, _וגם_, _thut,
+ {{0xd838826c,0xa3b20035,0x7545a481,0x6f0f0197}}, // _fočo_, टनर_, аниз, _jicc,
+ {{0x9c7c905e,0x6d4d36c0,0x38b4016d,0x6f0f0b57}}, // ročn, nraa, _lära_, _micc,
+ {{0x3eada97c,0x7eaa026f,0x99d491cc,0x2a7f8282}}, // sket_, _výpi, _اتفا, _haub_,
+ {{0x38b400f2,0xf79984c1,0x2a7f822c,0x394001a1}}, // _nära_, _جناب_, _kaub_, _svis_,
+ {{0x6f0f4336,0x6d4d23b6,0x7cf03a53,0x8b0881d0}}, // _nicc, kraa, tärk, mořá,
+ {{0xfeb88416,0x2a7f822c,0x690b02a6,0xed598087}}, // _حالت_, _maub_, _eżem, _рол_,
+ {{0x6e3bc337,0x6d4d10b0,0x2a7f8282,0x75298548}}, // laub, draa, _laub_, mpez,
+ {{0xac7483f8,0x443a07d9,0x6f0f23b9,0x6d4d0079}}, // _دانش, vap_, _bicc, eraa,
+ {{0xddc60063,0x6e2d02b8,0x6f0f4338,0x7a0c00eb}}, // _takż, gbab, _cicc, _pētī,
+ {{0x443a1ca6,0x6f0f4339,0x6d4d387f,0x395f8282}}, // [3100] tap_, _dicc, graa, _nqus_,
+ {{0x7cef8aa2,0xe3b200f7,0x8c4604db,0x25fc0035}}, // føri, يرا_, реже, लेगी_,
+ {{0x443a28af,0x798980a4,0x6f0f433a,0x6e2d433b}}, // rap_, _arew, _ficc, bbab,
+ {{0x443a336a,0x63a300f2,0x2a7f822c,0x6d4d0f1a}}, // sap_, änna, _caub_, braa,
+ {{0x3940c33c,0x6f043c32,0x7989a319,0x2a7f8122}}, // éis_, mmic, _crew, _daub_,
+ {{0x6ab6031d,0x6f040229,0x7989809a,0xe8d9001c}}, // _llyf, lmic, _drew, _trụ_,
+ {{0xd6dac33d,0xbb43433e,0x7c3aaf5d,0xd90d87d2}}, // кти_, _мерк, vatr, ریم_,
+ {{0x4438433f,0xb4de81a2,0x6f040229,0x38698503}}, // _her_, थक्_, nmic, đara_,
+ {{0x44380db7,0xdd908591,0xe51c005e,0x6280c340}}, // _ker_, سود_, _भूमि_, _iamo,
+ {{0x4438003a,0x6280c341,0x628282b5,0x2a7f81c5}}, // _jer_, _hamo, ldoo, _zaub_,
+ {{0x6f04003a,0x4438179a,0x7c3ac342,0x6280c343}}, // kmic, _mer_, ratr, _kamo,
+ {{0xf2c70f16,0x62808fe2,0x44383caa,0x20cd0110}}, // рсон, _jamo, _ler_, džia_,
+ {{0x44384344,0xdd8f8013,0x6f0404a8,0x6f0f0098}}, // _oer_, _يوم_, dmic, _ricc,
+ {{0x443804b8,0x3d1a01ce,0x6d4d038e,0x6f0f047f}}, // _ner_, _बढ़े_, vraa, _sicc,
+ {{0x6f0f0698,0xd8388d11,0x040480ab,0x13a980d7}}, // _picc, _meč_, _শ্রী_, _منوی_,
+ {{0x6280c345,0x5eb404fa,0x6d4d26a1,0x44384346}}, // _namo, ойст, traa, _aer_,
+ {{0x44382d85,0x6d40c347,0x628282a3,0x6e2d0ad4}}, // [3110] _ber_, nsma, ddoo, rbab,
+ {{0x7c388efd,0x6d4d4348,0x44384349,0x645c00ee}}, // _jevr, rraa, _cer_, _ccri,
+ {{0x4438434a,0xdd86850c,0x2a7f90af,0x6d4d35da}}, // _der_, _کو_, _paub_, sraa,
+ {{0x6e951e95,0x6d4d00c9,0xe69516a5,0x645c00e7}}, // _الحا, praa, _الحد, _ecri,
+ {{0x4438434b,0xdced02a5,0x7eb5826f,0xd8388289}}, // _fer_, svađ, _nápa, _beč_,
+ {{0x4438434c,0x6e22c34d,0x2d8b01a1,0x7ceb0380}}, // _ger_, _afob, _irce_, rürs,
+ {{0x2a7f81c5,0x6d42c34e,0x6d46c34f,0x2fde83a8}}, // _taub_, _avoa, škal, _cxtg_,
+ {{0x44211f4c,0x4438011e,0x6280c350,0x4177803d}}, // lch_, _zer_, _gamo, _پارس,
+ {{0x443802bb,0x9c7c8353,0x6d40a310,0x7c38837a}}, // _yer_, močj, gsma, _bevr,
+ {{0x7aed4351,0x44214352,0x6aa40362,0x67d59bcc}}, // _akat, nch_, _soif, _лозу,
+ {{0x44214353,0x7c38c354,0xa3d72836,0x6280b394}}, // ich_, _devr, ापि_, _yamo,
+ {{0xfbd20039,0x62808118,0x75299e41,0xf8a91101}}, // לתי_, _xamo, ppez, _אש_,
+ {{0x7c38c355,0xd46989a5,0x6b8a9699,0xf8db8035}}, // _fevr, _биле_, _erfg, _बदाय,
+ {{0x7c3880c9,0x7cf01bda,0x7aed4356,0xe8f7002e}}, // _gevr, päri, _ekat, йля_,
+ {{0xf412804c,0x78a5012b,0x7eb5803e,0x2d8b4357}}, // _צפה_, _dohv, _zápa, _arce_,
+ {{0x645c4358,0x44384359,0x644721e5,0x628281b4}}, // _scri, _ser_, _adji, xdoo,
+ {{0x4438435a,0xbea315e3,0x6280c35b,0xa3cb8105}}, // [3120] _per_, чатк, _ramo, _रेत_,
+ {{0x9992000d,0xc5f283c8,0x7cf00338,0x4423018e}}, // _když_, לדן_, härv, _cfj_,
+ {{0x443810dd,0x6280abd2,0x7f860013,0xd838b251}}, // _ver_, _pamo, _الجن, _reč_,
+ {{0x44380352,0x93bc802e,0xd25980e8,0x4421435c}}, // _wer_, mbăt, иці_, ach_,
+ {{0x4438435d,0x6282b062,0x6280c35e,0x673b005c}}, // _ter_, rdoo, _vamo, ćuje,
+ {{0xdb1a8065,0x3ea7002e,0xd7fa8193,0x52a50540}}, // szté, _într_, луй_, _ऑक्स,
+ {{0xd838a52d,0x6564435f,0xf1bf0065,0x20c980f7}}, // _več_, ltih, zzá_, جبني_,
+ {{0x6d40c360,0x7c3893ff,0x62828df6,0xc6f882ee}}, // tsma, _sevr, qdoo, јних_,
+ {{0x94868087,0x6d40c361,0x656419b0,0x20cd01a1}}, // бынд, usma, ntih, džin_,
+ {{0x7aed4362,0x9c7c8353,0x38b9b66f,0xb866815b}}, // _skat, močk, _père_, _دارو,
+ {{0x95f5064a,0xaca40133,0xd2510f24,0x6d564363}}, // _आर्ट_, _adịr, وند_, huya,
+ {{0x38af02bb,0x6d564364,0x7c3e26a6,0xddcd0087}}, // _türk_, kuya, hapr, neaţ,
+ {{0x44212ecc,0x9c830db7,0x7c3883b2,0x65642992}}, // ych_, ščit, _tevr, jtih,
+ {{0x7cfd80e7,0xc0c84365,0x9a298129,0x6d5602d5}}, // tère, бусе_, _mươi_, duya,
+ {{0x443eabab,0xaab8901b,0xbcfb02be,0x38af209e}}, // mat_, _आतंक, _opér, _jüri_,
+ {{0x44212065,0xd2510117,0x38ab8aa2,0x7aed4366}}, // wch_, _کچھ_, _hørt_, _ukat,
+ {{0x44213693,0x69da1d54,0xf77804b7,0x38ab806a}}, // [3130] tch_, şter, ħħa_, _kørt_,
+ {{0x3f8c80ad,0x645aac60,0x44214367,0x798d4368}}, // _ordu_, ngti, uch_, _iraw,
+ {{0x44214369,0x443e8087,0xbcfb08f9,0xcfad8264}}, // rch_, iat_, _spés, _কেনন,
+ {{0x443ec36a,0x44211ab0,0xe5700065,0x2d8b017f}}, // hat_, sch_, بطہ_, _trce_,
+ {{0x443ec36b,0x7c3e01f6,0x9967c36c,0x5d330019}}, // kat_, bapr, стел, _رہائ,
+ {{0x443ec36d,0x3f8c80d2,0x02c6016f,0x493c03c8}}, // jat_, _brdu_, _लग्न, נגוו,
+ {{0x443ec36e,0xf5228133,0x291308dc,0xf3f32384}}, // dat_, _rịọr, _mixa_, _رأس_,
+ {{0x92b788ca,0x2249061b,0x9c7c826f,0x645ac36f}}, // _احسا, _ndak_, bočk, egti,
+ {{0x99d48013,0x212907d5,0x798d0069,0x2d8b801b}}, // _فتكا, _ntah_, _nraw, íce_,
+ {{0x443ec370,0xe4c5835f,0x20cd0110,0x7cef804a}}, // gat_, ійни, džio_, jørt,
+ {{0x798d4371,0x224900b9,0x7cf00198,0xf9891101}}, // _araw, _bdak_, märt, _שר_,
+ {{0xef0e8d13,0xae0908fd,0x798d4372,0x65643aab}}, // _ім_, वधान_, _braw, ytih,
+ {{0x443e8859,0xe5a597c8,0x672d82a5,0x8c4595b7}}, // bat_, _фили, ćaju, _деле,
+ {{0x443ea9ab,0x248300dd,0xbf0204c5,0x798d4373}}, // cat_, _najm_, लग्न_, _draw,
+ {{0xfaa6104b,0xa91d931b,0x3f8c016b,0x7c3e4374}}, // _мако, _drži, ídu_, vapr,
+ {{0x765bc375,0x2ca783b2,0x93bc8087,0x7c3e008e}}, // nguy, _hond_, rbăt, wapr,
+ {{0x27e087d9,0x798d4376,0x29134377,0x62964378}}, // [3140] ğini_, _graw, _fixa_, _inyo,
+ {{0x33f60e8e,0x27e0c379,0x629601c0,0x62863dab}}, // очес, şini_, _hnyo, ldko,
+ {{0x2ca7ac23,0x7eb5803e,0x656404e8,0x7c3e437a}}, // _mond_, _nápo, stih, rapr,
+ {{0x443e9f41,0x7ebc8065,0x62861cb6,0x7c3e437b}}, // zat_, _képe, ndko, sapr,
+ {{0x6284156b,0x443ec1ae,0xe894812f,0x88e699a5}}, // _maio, yat_, заль, ожде,
+ {{0x443e881a,0x5fcf0035,0x628412f5,0x11398084}}, // xat_, _हेडल, _laio, сяцы_,
+ {{0x443ec37c,0x6d4436ae,0x62960870,0x9949809a}}, // vat_, lsia, _onyo, góły_,
+ {{0x7c3c437d,0xa3d705b3,0x9a29801c,0x7cf48722}}, // _herr, ापर_, _tươi_, màri,
+ {{0x443ec37e,0x7c3c0364,0x9c7c8db7,0x41278256}}, // tat_, _kerr, loči, _мото_,
+ {{0x0caa938f,0x443ec37f,0x62964380,0x3b5486d2}}, // ртии_, uat_, _anyo, пкор,
+ {{0x7c3c4381,0x6d4403ac,0x7cf4809f,0x62844382}}, // _merr, hsia, nàri, _baio,
+ {{0x798d4383,0x672dabea,0x6b83006a,0x7c3c4384}}, // _praw, ćajt, ængi, _lerr,
+ {{0x443e8595,0xf1c5016f,0xaca4019d,0xd251003d}}, // pat_, _लेखन, _adọr, ونگ_,
+ {{0x443ec385,0x7ebc80e7,0x843880f7,0x9c7c8796}}, // qat_, _dépe, _اكثر_, koči,
+ {{0x224909da,0x9c7c807a,0x6d461f0a,0xa8140073}}, // _tdak_, joči, _avka, ддрш,
+ {{0x798d4386,0x56944286,0x7eb5826f,0x7cf48722}}, // _traw, маст, _nápl, dàri,
+ {{0x7c3c4387,0x443c9393,0xa91d803a,0x7ebc8019}}, // [3150] _berr, _kev_, _trži, _gépe,
+ {{0x7c3c4388,0x2d892a23,0x449811e9,0x7d1d87e0}}, // _cerr, rvae_, овую_, íssi,
+ {{0x9c7c8353,0x7eaa026f,0x395901ec,0x443cc389}}, // goči, _výpr, luss_, _mev_,
+ {{0xb77a093f,0x0f7a0158,0x7cf00004,0x877a03c8}}, // _פארש, _פארב, färs, _פארי,
+ {{0x7c3c438a,0xc692093f,0x6b8e022b,0x24830088}}, // _ferr, _נאך_, _erbg, _tajm_,
+ {{0x443c8282,0x7c3c01e1,0x63a300e8,0x3f8103ed}}, // _nev_, _gerr, ønns, _kshu_,
+ {{0x2ca7c38b,0xda3597ae,0x78a8817f,0xe7398009}}, // _rond_, _невы, _dodv, щей_,
+ {{0x7d1d1328,0x7c3c0102,0x7640b8ba,0x7c2528c6}}, // _kuss, _zerr, gamy, lchr,
+ {{0xee3681e2,0x6284438c,0x7cf00198,0x7d1d1f5d}}, // чны_, _raio, märr, _juss,
+ {{0x443c81e9,0x7d1d438d,0x6aa980f3,0x7c3c438e}}, // _cev_, _muss, _hoef, _xerr,
+ {{0x7d1d12bb,0x2ca78a0f,0x62840037,0x6f028834}}, // _luss, _vond_, _paio, _fhoc,
+ {{0x6d440114,0x443c8609,0x2ca79989,0x6f028362}}, // ysia, _eev_, _wond_, _ghoc,
+ {{0x361a812a,0x3495a133,0x443cc38f,0x5ec300ab}}, // _הונד, _надр, _fev_, ্কাই,
+ {{0x3e4c8110,0x7e6f00c3,0x3ebf808e,0xe51f06ae}}, // rėtų_, _abcp, lhut_, _बढ़ि_,
+ {{0x6441c390,0x7d1d4391,0xdfd200f7,0xc7a60098}}, // mali, _auss, رير_, чивк,
+ {{0x7c3c1e9e,0x7d1d4392,0x7d150029,0x6d440006}}, // _serr, _buss, _aizs, tsia,
+ {{0x7c3c4393,0x6d444394,0x6e3d4395,0x6e2405e4}}, // [3160] _perr, usia, _desb, rcib,
+ {{0x6abbc396,0x7cf4809f,0x6e243648,0x443c856c}}, // _aluf, tàri, scib, _xev_,
+ {{0xd838805c,0x6e3d010c,0x6d440ca9,0x3ebfc397}}, // _uoči_, _fesb, ssia, khut_,
+ {{0x9c7c824a,0x6441bdf5,0xe80f946d,0xdddba30a}}, // roči, hali, ाधना_, zduš,
+ {{0x6441843c,0x7c3c05d8,0x290a004f,0x26d201b4}}, // kali, _terr, omba_, miyo_,
+ {{0x6441c398,0x69c28613,0x26d24399,0x92cc80ab}}, // jali, rzoe, liyo_, রকে_,
+ {{0x6abb9045,0xf0930039,0xa3cba0d5,0xdddb807a}}, // _fluf, תנו_, _रेल_, vduš,
+ {{0x6d558063,0x443cc39a,0x69cd94d5,0x3ebf8359}}, // ązan, _sev_, _देवी, ghut_,
+ {{0x6441c39b,0x443c822c,0x76408a03,0x25a00118}}, // fali, _pev_, ramy, nxil_,
+ {{0x6441c39c,0x26d23a19,0x645e033e,0x31bb8039}}, // gali, hiyo_, ngpi, _הזמנ,
+ {{0x443c8069,0x61e412ca,0x81aa00ab,0x6f1e016a}}, // _vev_, _axil, _গেল_, _nupc,
+ {{0xed5aa7de,0x201a03a8,0x6441956e,0x26d20079}}, // _год_, _agpi_, aali, jiyo_,
+ {{0x6441c39d,0x26d2439e,0x443cc39f,0x6e3d43a0}}, // bali, diyo_, _tev_, _resb,
+ {{0x64a381e2,0xa3ac0af7,0x39590dba,0x6e3d43a1}}, // _пача, गहा_, russ_, _sesb,
+ {{0x7d1d43a2,0x6e3d008e,0x7f9443a3,0x2d823f4f}}, // _suss, _pesb, нарх, _aske_,
+ {{0xc6f9102a,0x26c00122,0x0ccd80ab,0x67278061}}, // інах_, ghio_, রকৃত, _éjje,
+ {{0x3d1706a7,0xd83880c3,0x1ddc820e,0x7c2543a4}}, // [3170] _भूखे_, _hoču_, मपित, tchr,
+ {{0xad9b0e1b,0x442743a5,0xc1bc0039,0x3f8101c0}}, // _azúc, ùn_, _למחש, _tshu_,
+ {{0x23790b76,0x09aa80c8,0x7d150019,0x2d8206c0}}, // تماد_, _খেলা, _vizs, _eske_,
+ {{0x7d1d240d,0x7c2543a6,0x6441c3a7,0x26c043a8}}, // _tuss, schr, zali, chio_,
+ {{0x6441c3a9,0x6f1e00d2,0x0ef9064a,0x69348956}}, // yali, _zupc, ंतीस_, енчу,
+ {{0x7e62c3aa,0x64418085,0xb9260032,0xf4840098}}, // _acop, xali, _aawọ_, _пусн,
+ {{0x238c801b,0x66e58a13,0xb5fb046d,0x6aa9989f}}, // _něj_, мока, _gbár, _toef,
+ {{0x2bd30074,0x3949016a,0x7e6d0118,0x44278299}}, // _धेया, _ovas_, nfap, _sfn_,
+ {{0x26c381e2,0x224dc3ab,0x6f09872a,0x442580b9}}, // ėjo_, _idek_, rmec, tcl_,
+ {{0x26d2005d,0x38b40f02,0x3a3f9989,0xa91d936f}}, // ziyo_, _värk_, _heup_, _držt,
+ {{0x3b0a1ac9,0x68e48174,0x394902f1,0x61f60198}}, // жено_, éide, _avas_, _syyl,
+ {{0xe29a31a5,0x80d00540,0x216a0009,0x291f804f}}, // цам_, डोने, оими_, _kuua_,
+ {{0x68ee05ee,0xf652825f,0x44279914,0x7e7baaa0}}, // tobd, _יצא_, _tfn_, meup,
+ {{0x31568158,0xd838826c,0x6441c3ac,0x6ada9a46}}, // וישן_, _foču_, qali, _भद्र,
+ {{0x26c0031d,0x7ebcc186,0xdce401b9,0x291f82f1}}, // thio_, _hépa, stiċ, _luua_,
+ {{0x61f60009,0x212d8326,0x61e40cdb,0x224d9ab3}}, // _tyyl, _oteh_, _txil, _ndek_,
+ {{0xf1bf1d8c,0x26d243ad,0x6d468e63,0x6569c3ae}}, // [3180] lcán_, riyo_, škav, nteh,
+ {{0x3157093f,0x24878247,0x26d243af,0x9c7ca648}}, // _קיין_, _janm_, siyo_, boču,
+ {{0x24878247,0x224001e0,0x212dc3b0,0x9a86a853}}, // _manm_, _keik_, _ateh_, дулл,
+ {{0xe1fa8604,0x212003ac,0xa3d48054,0x38af01ec}}, // оге_, _kuih_, _सेफ_, _fürs_,
+ {{0x397a9e13,0x2d8200ce,0x29058362,0x69c633bc}}, // تصاد_, _uske_, _bhla_, nzke,
+ {{0x7e62c3b1,0x3a26be1e,0x22402496,0x212000ee}}, // _scop, scop_, _leik_, _muih_,
+ {{0x638314b7,0x3946a31e,0xdb832482,0x212009c4}}, // _игра, ssos_, _игри, _luih_,
+ {{0x444443b2,0xf8bb0e70,0x81c400c8,0x2f178009}}, // la_, _उत्प, _এখন_, мощь_,
+ {{0x786682bc,0x4444011e,0x248783ec,0x6d4f8b80}}, // _оказ, oa_, _banm_, šcan,
+ {{0x444443b3,0x7ebc83d3,0x09038162,0x00000000}}, // na_, _dépa, _апун, --,
+ {{0x44440c39,0x79841e8c,0x403380e8,0xb86584c1}}, // ia_, _asiw, _реєс, _سالو,
+ {{0x444443b4,0x63a33c40,0x21202b36,0x6d5b820d}}, // ha_, änni, _buih_, buua,
+ {{0x4444400f,0x24878247,0xf1bf0e1b,0xf5b6815b}}, // ka_, _fanm_, acán_, _تصاد,
+ {{0xfbcf89d7,0x690b0372,0xfd4f0129,0x6602c3b5}}, // اتی_, _ażer, _triế, _izok,
+ {{0xc5888104,0xd7f801a0,0x2f01b128,0x86020176}}, // _hồ_, дут_, lóga_, _dakٍ_,
+ {{0x4444002e,0x3869811f,0x442a43b6,0x76440326}}, // ea_, đari_, _afb_, gaiy,
+ {{0x444443b7,0x09d90a49,0x394926d5,0xfd1f36f1}}, // [3190] fa_, _সরকা, _uvas_, _quì_,
+ {{0x80b383db,0xc58880ff,0x599601c6,0x9c7c81d6}}, // _अवधे, _mồ_, _נכתב_, poču,
+ {{0x70db86a7,0xc58880ff,0x32558d9e,0x442a43b8}}, // _बदौल, _lồ_, евер, _dfb_,
+ {{0x7d188bfa,0x444443b9,0x527c03de,0x6f060362}}, // _livs, aa_, ַנוא, _ghkc,
+ {{0x7c2aab5e,0x5e5600be,0x6f0d04b9,0x442a1ab3}}, // _offr, _אירע_, mmac, _ffb_,
+ {{0x6f0d43ba,0xf8bf03c9,0x7ebc80e7,0xd1b8806b}}, // lmac, mpés_, _répa, باما_,
+ {{0x64452fd2,0x6602c3bb,0x7ebc80e7,0x7ceb43bc}}, // mahi, _azok, _sépa, kürz,
+ {{0x7c2ac3bd,0xc58880ff,0xf4298198,0x291f82f1}}, // _affr, _bồ_, kkää_, _tuua_,
+ {{0x7d1881dd,0xa3d48424,0x98758d46,0x224043be}}, // _bivs, _सेब_, _плац, _reik_,
+ {{0x6445132c,0x6569afb3,0x6f0d427a,0x31348087}}, // nahi, rteh, hmac, _рефр,
+ {{0xeb998785,0x6569c3bf,0x6f0d43c0,0xccf20039}}, // чин_, steh, kmac, רכי_,
+ {{0x64452813,0x87b680be,0x6aad0087,0x1a66015b}}, // hahi, _אלעס_, _coaf, ریخی_,
+ {{0xed4ea40f,0x463a893f,0x64451066,0xa3d48074}}, // _що_, _זענע, kahi, _सेठ_,
+ {{0x444416cb,0xdb1a8065,0x73e58698,0xfc3189a7}}, // xa_, sztá, _полз, _صحت_,
+ {{0x6d49c3c1,0x644543c2,0x910311c7,0x224000b9}}, // lsea, dahi, упре, _teik_,
+ {{0x6443007b,0x98a00369,0x2d800035,0x6721ad2e}}, // ðnin, _otić_, twie_, _mulj,
+ {{0x6d49c3c3,0x672181b9,0x7eb5a509,0x76442008}}, // [31a0] nsea, _lulj, _cápi, raiy,
+ {{0x6d498ad0,0x9f8643c4,0x444443c5,0x26df80d2}}, // isea, _згод, ua_, hnuo_,
+ {{0x7c249010,0x26df805c,0x929d809a,0x6e460af2}}, // _şirk, knuo_, _chło, _пенз,
+ {{0x444443c6,0xe60780f7,0xaa7b026f,0x7c3b0115}}, // sa_, _مش_, mnýc, _đure,
+ {{0x644543c7,0xaa7b026f,0xd6d080d5,0xda6580f7}}, // bahi, lnýc, یقت_, ضاني,
+ {{0x10a38a94,0x444443c8,0x98a395e0,0x6d4981e4}}, // личн, qa_, личе, dsea,
+ {{0xaa7b026f,0x6602900b,0x7eb5826f,0x291a008e}}, // nnýc, _szok, _zápi, _mipa_,
+ {{0x6721c3c9,0x26df80ce,0x7d1b43ca,0x6f199670}}, // _dulj, gnuo_, _èusc, _ciwc,
+ {{0x6d49c3cb,0x91e38081,0x301a81a8,0x66db062c}}, // gsea, _боте, _فتاة_, läkä,
+ {{0x291a077f,0xa2a3064a,0x7ae41c40,0x224682d5}}, // _nipa_, गाभ्, _ajit, laok_,
+ {{0xf3898104,0xaa7b026f,0x31e280ab,0x6f0d10ae}}, // _cả_, jnýc, _বরিশ, ymac,
+ {{0xaa7b0efc,0x6d498362,0x34dd109b,0xad2681a8}}, // dnýc, bsea, _मद्द, _كرتو,
+ {{0x6721826c,0x64454331,0x291a0115,0x6d498427}}, // _zulj, yahi, _bipa_, csea,
+ {{0x7eb58510,0x26f90935,0xb4af1d70,0x212b802a}}, // _rápi, ंत्र_, _ओके_, _éche_,
+ {{0x6445365d,0x6f0d43cc,0x291a43cd,0xbddb010c}}, // vahi, tmac, _dipa_, _ndèr,
+ {{0x7ae40b18,0x6445323d,0x7cf487f1,0xabfb81c6}}, // _gjit, wahi, càrr, _מהור,
+ {{0x64453e75,0x7c3b0267,0x988381d6,0x75228084}}, // [31b0] tahi, _đurb, víľu_, _juoz,
+ {{0x7afd43ce,0xaa7b0a21,0x6f0d0d35,0x98650065}}, // llst, bnýc, smac, _جیسے_,
+ {{0xc4d380be,0xf09f80ff,0x764b0019,0xd6d10f24}}, // נגע_, _đà_, ógys, _نقد_,
+ {{0x644543cf,0x291a0279,0x59d2809a,0xbd05826b}}, // sahi, _zipa_, _देहर, _akẹ́,
+ {{0x67218364,0x64453bc2,0x41b32594,0xc2c481a8}}, // _sulj, pahi, ुनिस, ديني,
+ {{0xdb2304c0,0x3ea943d0,0x9dd60039,0x7afd3b13}}, // _موسی, ljat_, _יורק_, hlst,
+ {{0x656d43d1,0x26df8503,0x07a58c07,0x9be401e5}}, // ltah, tnuo_, тайн, _бітк,
+ {{0x7ebc82be,0xcf248154,0x3ea943d2,0xf1d29094}}, // _dépo, دروي, njat_, _देवन,
+ {{0xceb40f60,0x391593bd,0x6e29c3d3,0x26df8289}}, // טיק_, _амар, rceb, rnuo_,
+ {{0x6d49c3d4,0x26df812b,0x7af608f8,0x7afd09e5}}, // rsea, snuo_, _skyt, elst,
+ {{0xa3ba01ce,0x6d49c3d5,0x3ea90267,0x3fdc00ab}}, // आईए_, ssea, kjat_, _ধর্ষ,
+ {{0x291a0699,0xaa7b0a21,0x3ea902a6,0x6d5f005d}}, // _sipa_, vnýc, jjat_, kuqa,
+ {{0x6d46803a,0xcb128158,0x20d600e8,0x656d33d2}}, // škar, ילט_, вівс, jtah,
+ {{0xaa7b0efc,0xf389801c,0x7afd2525,0x20188580}}, // tnýc, _tả_, alst, _úric_,
+ {{0x46f600e8,0x7eb816dc,0x10a306e9,0xddca0035}}, // вчат, _hípi, ширн, łoży,
+ {{0xcf928158,0xaa7b026f,0x321f801c,0x20cd0084}}, // סטן_, rnýc, _nguy_, džiu_,
+ {{0x6443c3d6,0x61fbbd9b,0xaa7b026f,0x656d2a22}}, // [31c0] _keni, _nyul, snýc, gtah,
+ {{0x6443859c,0xaa7b016b,0xfaf901a9,0x66db062c}}, // _jeni, pnýc, stīt_, täkä,
+ {{0x394d87a3,0x61e99a16,0x7eb82509,0x213f810c}}, // _aves_, _axel, _lípi, _awuh_,
+ {{0x60c1c3d7,0x7ebc83d3,0x6443afbc,0x79828035}}, // _allm, _répo, _leni, twow,
+ {{0x80b38075,0x7ebc82be,0x200580b9,0x3eab016b}}, // _अवशे, _dépl, _azli_, žitá_,
+ {{0x7982809a,0xed59816b,0x6443c3d8,0x2126808e}}, // rwow, drž_, _neni, rqoh_,
+ {{0x61e9c3d9,0x7ee69d51,0xf6e68ff7,0x213f88f9}}, // _exel, кцие, кцин, _ewuh_,
+ {{0x2d86a09c,0x7e6443da,0xd6d784d9,0x7bc8816b}}, // _psoe_, ngip, кты_, vzdu,
+ {{0x6443c3db,0x7ae281f6,0x2d9206c0,0x61fb8061}}, // _beni, mnot, nvye_, _gyul,
+ {{0xd90e826a,0xe7ed81fe,0x64438942,0x7ae2c3dc}}, // _بیت_, _चुका_, _ceni, lnot,
+ {{0x7d1c43dd,0x34b70039,0x6443c3de,0x9f4f061c}}, // _mirs, דפים_, _deni, ünüz_,
+ {{0x7ae2c3df,0x4fd98158,0x41e705e9,0x394d8c1a}}, // nnot, _אַנד, віга, _yves_,
+ {{0x394d06e3,0xb3568416,0x7afd016d,0xdd9287d2}}, // ées_, ریکا_, rlst, _خوش_,
+ {{0x799643e0,0x61e200c3,0x26c243e1,0x00000000}}, // _dryw, _žolc, _alko_, --,
+ {{0x6448c3e2,0x251b898a,0x92950a42,0x442ec3e3}}, // madi, _קווא, _байц, _iff_,
+ {{0x6443c3e4,0x7d1c0782,0xaf34003d,0x7c2e43e5}}, // _zeni, _airs, شرفت, _afbr,
+ {{0x7ae28775,0x6443884a,0x3ea943e6,0xe8d90133}}, // [31d0] dnot, _yeni, rjat_, _asụ_,
+ {{0xfaff020f,0x656d0f68,0x6443c3e7,0x3e530028}}, // llë_, rtah, _xeni, ệt_,
+ {{0xa2b486bf,0x656d132c,0x38b4016d,0x5a35867c}}, // _आवश्, stah, _värt_, гнет,
+ {{0x941580be,0x628f43e8,0x7ae285ed,0x2bc200bc}}, // אַנד_, ndco, gnot, वैधा,
+ {{0x7d1c0051,0x442e8051,0x69cd93ba,0xaca301bc}}, // _firs, _off_, _देखी, _atụb,
+ {{0x6448c3e9,0x7c341663,0xdce981b9,0x7ae28198}}, // jadi, _верх, rteċ, anot,
+ {{0x3d06800f,0x6448c3ea,0xccf201c6,0x8af98085}}, // _सीधे_, dadi, _לכך_, _atəş,
+ {{0x6e2d43eb,0x2a6901c0,0xb5fd80eb,0x7cfd823e}}, // ncab, _ncab_, egša, mèri,
+ {{0x6448c3ec,0x6d4d02a1,0x4035ad59,0x6f1d079a}}, // fadi, nsaa, _безс, _misc,
+ {{0x6448c3ed,0x60c180f7,0x6f1d028e,0x64438168}}, // gadi, _ullm, _lisc, _qeni,
+ {{0x7a6a0758,0x6443c3ee,0x7c958077,0x68f5026c}}, // нинг_, _veni, نشگا, dozd,
+ {{0x79960063,0x64438352,0x6d4d25e0,0x68ed8013}}, // _pryw, _weni, ksaa, éadf,
+ {{0x6443af30,0x645c9313,0x6448c3ef,0x442c89c4}}, // _teni, óric, badi, bcd_,
+ {{0x6f1d0013,0x6448c3f0,0x6d4d43f1,0x644383ba}}, // _aisc, cadi, dsaa, _ueni,
+ {{0x1fb501ae,0x2cb88e43,0x7d1c0118,0xd36e81a8}}, // астр, örde_, _rirs, _فهو_,
+ {{0x63bc8aa2,0x292500f7,0x7c3b0da8,0x6d4d067f}}, // ørne, úpaí_, _đura, fsaa,
+ {{0xceb28bea,0xed5700a9,0x7d1c0077,0xe5a33650}}, // [31e0] זין_, лот_, _pirs, бити,
+ {{0x888610f8,0x6f1d43f2,0x03a622a7,0x7ff600f7}}, // _слож, _eisc, лизо, _مسجا,
+ {{0x7d1c0bb6,0xfaf900eb,0xad9b0e1b,0x7ae2c3f3}}, // _virs, brīd_, _ayúd, tnot,
+ {{0x6d5b820f,0x6e2d1ac6,0xbea60a08,0x80a201ab}}, // krua, ccab, _самк, खावे,
+ {{0x7ae29c4b,0x7d1c43f4,0x2d8402be,0x6448ba14}}, // rnot, _tirs, _ème_, yadi,
+ {{0x7ae2c3f5,0xb6058088,0x2cb102d4,0x6448857b}}, // snot, _mišč, _gozd_, xadi,
+ {{0x6448b1dc,0x9cd704de,0xe3a78829,0x442c8122}}, // vadi, _עולה_, _ار_, vcd_,
+ {{0x6448c3f6,0x442e8da8,0x6e3b97ea,0x798611ee}}, // wadi, _sff_, gbub, kwkw,
+ {{0xceb3025f,0x6d5bc3f7,0x442cc3f8,0xbddb0176}}, // מיה_, grua, tcd_, _neèl,
+ {{0xddcb8087,0x752600ee,0x3374c3f9,0x3a098fd3}}, // ţişt, _mukz, агор, _شکنی_,
+ {{0x6448c3fa,0x7eb5a6e7,0x4f5a92c8,0x5fa89664}}, // radi, _cáps, _سجاد_, _कपिल,
+ {{0x6448c3fb,0x291ec3fc,0x6d4d0079,0x7646306c}}, // sadi, _hita_, ysaa, _neky,
+ {{0x291ec3fd,0x6f1d01e8,0x6448c3fe,0xa3d482f1}}, // _kita_, _risc, padi, _सेर_,
+ {{0x6f1d43ff,0x1be40540,0x291ec400,0x644881b4}}, // _sisc, कपाल_, _jita_, qadi,
+ {{0x291e854e,0x764601eb,0x40340009,0x248e80ee}}, // _mita_, _beky, сесс, _iafm_,
+ {{0x96353ff8,0xf1a72927,0x291ec401,0x6d4d2adb}}, // рнац, _брон, _lita_, tsaa,
+ {{0x2d580c4f,0xf8ae004e,0x272ac402,0xe9058129}}, // [31f0] лить_, سکی_, _lùn_, _lượ,
+ {{0x6d4d4403,0x6e2d0333,0x7cfd809f,0x0f35003d}}, // rsaa, scab, tèri, _نکرد,
+ {{0x6d4d4404,0xd25192dc,0x6f1d34cc,0xbc070a0e}}, // ssaa, قنا_, _tisc, учай,
+ {{0x6e22ae95,0x6f1d00f7,0x291ec405,0x0ce1a539}}, // _ngob, _uisc, _aita_, _पद्म,
+ {{0xa0698638,0x1869845d,0x68458071,0xe0458767}}, // вала_, вали_, анла, анли,
+ {{0x2f55a2fd,0x272a9b4c,0x2d990511,0x7eb5816b}}, // атис, _bùn_, _irse_, _nápr,
+ {{0x256f2d46,0x291ec406,0x628281ed,0x6d4285ee}}, // lül_, _dita_, beoo, _awoa,
+ {{0xca488307,0x443108ae,0x6d5bc407,0x2d990b80}}, // _ملفه_, _hfz_, trua, _krse_,
+ {{0x7afbb4c6,0x291ec408,0x64474409,0xa2c28f12}}, // _ikut, _fita_, _keji, लसर्,
+ {{0x6d5ba75d,0x6447000d,0x291e8f33,0xeb998eef}}, // rrua, _jeji, _gita_, вио_,
+ {{0x6447440a,0x645c8187,0xb6058115,0x7ae98163}}, // _meji, ória, _sišč, _kjet,
+ {{0x2d8b0267,0x64470b95,0x60c500dd,0xb605807a}}, // _osce_, _leji, _alhm, _pišč,
+ {{0xddc2800d,0x7afb8010,0x2611ab51,0x7ae980f1}}, // _zbož, _mkut, देशी_, _mjet,
+ {{0x7ae982fd,0xe8f72856,0x6ac28778,0x3eadc40b}}, // _ljet, иля_, _शत्र, ljet_,
+ {{0x7afbc40c,0xb8de0f8d,0x290400eb,0x2d8b0037}}, // _okut, _आव_, ēma_, _asce_,
+ {{0x5de6098d,0xc6c302a9,0x3eadaed5,0x068603a7}}, // ажба, ойск, njet_, јган,
+ {{0x1c4619fe,0xa4460d0e,0x256f440d,0x4ad80019}}, // [3200] инам, инад, gül_, _لاکھ_,
+ {{0x7ae9c40e,0xf1c20824,0x3eadc40f,0x389a03de}}, // _ajet, rišč_, hjet_, _ייִנ,
+ {{0x0c790196,0xdddb8289,0x2d8b047f,0x63a30106}}, // ысты_, jduž, _esce_, ännp,
+ {{0x291ec410,0x2bda016f,0x3ebfc411,0x78a186c4}}, // _sita_, _येणा, jkut_, _anlv,
+ {{0x27f70bca,0x7ae982fd,0x3ead82ce,0x3f9a2e0a}}, // _مفید_, _djet, djet_, _hrpu_,
+ {{0x64550063,0x60dc4412,0xd8f780e8,0x7afb8c6a}}, // _gdzi, nirm, анії_, _ekut,
+ {{0x291ec413,0x3cf50072,0x65640198,0x7ae983ed}}, // _vita_, ्तरे_, luih, _fjet,
+ {{0x7ae984b1,0x12c100c8,0x3f988125,0x3ead80f1}}, // _gjet, _উদ্দ, _árum_, gjet_,
+ {{0x291ec414,0x2bda016f,0xd04a8085,0xe4a6c415}}, // _tita_, _येता, zifə, арно,
+ {{0xd49a8847,0x291e8087,0x27ed3349,0x26c900fc}}, // три_, _uita_, şeni_, zhao_,
+ {{0x60dc0850,0x256f0019,0x26c00037,0x92952ba7}}, // dirm, zül_, kkio_, банц,
+ {{0x63bac416,0x81c301a9,0xd5e2846d,0x5693804a}}, // rytn, ņēmi, _gbo̟, _маєт,
+ {{0x60dc4417,0x20b4000f,0xd534826a,0xfce69c79}}, // firm, ुसंध, _افتخ, _кодо,
+ {{0x26c681c0,0x60dc4418,0x256f0019,0x7c3b0904}}, // _hloo_, girm, vül_, _đuro,
+ {{0xb5e880c8,0x23278b30,0xab278fbb,0x64474419}}, // _পরিচ, _који_, _која_, _reji,
+ {{0x256f0019,0x3a2480b9,0xa2d6064a,0x61ed011b}}, // tül_, _ngmp_, मोद्, _txal,
+ {{0x64470699,0x60dc441a,0xdddb8754,0x44230144}}, // [3210] _peji, birm, zduž, _pgj_,
+ {{0x7afb8a7a,0x7ae9911b,0x256f0019,0x60dc441b}}, // _skut, _sjet, rül_, cirm,
+ {{0x443e82af,0x9f5e80f1,0x68f88b67,0x6447441c}}, // ibt_, _dytë_, dovd, _veji,
+ {{0x21290590,0xb69b00e7,0x29034292,0x66e881d0}}, // _kuah_, _chât, llja_, děko,
+ {{0x7ae9c41d,0x3ead911b,0x644705a4,0x764d441e}}, // _vjet, vjet_, _teji, maay,
+ {{0xe8f88160,0x6455009a,0xaa7b016b,0x764d441f}}, // алі_, _udzi, dným, laay,
+ {{0x7ae988cf,0x3ead80f1,0x02a79273,0x212900ee}}, // _tjet, tjet_, _крам, _luah_,
+ {{0x7afb91e8,0x764d1a25,0x7ae9bc49,0x7980819d}}, // _ukut, naay, _ujet, _ịweg,
+ {{0x6eba0076,0x3eadc420,0x3ebfc421,0x332001b4}}, // _एकसु, rjet_, rkut_, _riix_,
+ {{0x3eadc422,0x764d0079,0x212109da,0x26c0011b}}, // sjet_, haay, _nihh_, zkio_,
+ {{0x798d1629,0x80ab14d5,0xaca30133,0x764d132f}}, // _asaw, ञाने, _adịc, kaay,
+ {{0x212914ec,0xaa7b016b,0xe1ff016a,0x799b810c}}, // _buah_, bným, rgón_, _kruw,
+ {{0xed599182,0x764d4423,0x60dc4424,0x443e80f2}}, // _сол_, daay, tirm, bbt_,
+ {{0x6c3589d7,0x2d8902f7,0xdce401a1,0x7af9c425}}, // _افغا, dwae_, mtić, howt,
+ {{0x5b358b76,0xbea60d69,0xe29a2f4b,0x60dc4426}}, // _اعتر, бавк, _бай_, rirm,
+ {{0xc2460652,0x2a6d822c,0x9f5e8168,0x2bc60697}}, // _уник, _nceb_, _sytë_, लनसा,
+ {{0x6da3024f,0x29030ee1,0xe8df9d12,0x60dc0637}}, // [3220] пита, blja_, _awọn_, pirm,
+ {{0x308600f7,0x07fa003d,0x387a018e,0x0ffa01a8}}, // _الدف, _مرجع_, _ubpr_, _معجب_,
+ {{0xe29ac427,0x799bc428,0xa2e6a3e7,0x202901d6}}, // лаж_, _aruw, божд, átiť_,
+ {{0x673b0bcf,0xdce40115,0x764d01b4,0x799bc429}}, // ćujt, ktić, caay, _bruw,
+ {{0x6728ab1f,0xf1d28321,0x6f04442a,0xdce40bda}}, // _sudj, _देखन, mlic, jtić,
+ {{0x6f04442b,0x672886cb,0xaa7b016b,0x6286442c}}, // llic, _pudj, vným, heko,
+ {{0x68f884b7,0x35d086a7,0x38cb8180,0x6f043648}}, // rovd, _तेज़, دانی_, olic,
+ {{0x1dd9823c,0x6286345f,0x2bb50076,0x6f0402af}}, // _बेहत, jeko, ंहरा, nlic,
+ {{0x645c8073,0x62862cd7,0x6f04442d,0x7c29026c}}, // ório, deko, ilic, _đerz,
+ {{0x67288065,0x6f0402af,0x21290359,0x6aa40114}}, // _tudj, hlic, _ruah_, _anif,
+ {{0x6f04442e,0x23658bcf,0x764d442f,0x443ec430}}, // klic, gulj_, yaay, rbt_,
+ {{0x6286044e,0x21293ab1,0x212102f7,0xd5a48180}}, // geko, _puah_, _sihh_, _ولای,
+ {{0x6f044431,0x290300d2,0xc05b00e8,0x7ae38511}}, // dlic, tlja_, лів_, énti,
+ {{0x764d0ec8,0xf6178039,0x6f044432,0x66e50084}}, // waay, _החדש_, elic, mėki,
+ {{0x6f044433,0x628609e8,0x764d257a,0x2129008e}}, // flic, beko, taay, _wuah_,
+ {{0x6f044431,0x798d4434,0x36d40cec,0x29030699}}, // glic, _tsaw, поср, slja_,
+ {{0x29030503,0xdced1bfe,0x798d2647,0x764d3bdf}}, // [3230] plja_, stač, _usaw, raay,
+ {{0x764d02ec,0xddd00162,0x66029075,0x00000000}}, // saay, ţeşt, _myok, --,
+ {{0x7c2d8214,0xdce08289,0x660283f7,0x764d033e}}, // _şark, jumč, _lyok, paay,
+ {{0x799bc435,0x26d90192,0x99998056,0x764d0df6}}, // _pruw, _umso_, икет_, qaay,
+ {{0x6458935a,0x6602925c,0xd825c436,0x6025841c}}, // _odvi, _nyok, одли, одла,
+ {{0x47598021,0x10a281f3,0x98a28323,0x62864437}}, // ария_, дишн, дише, zeko,
+ {{0xe7398009,0x6602c438,0x7e699384,0xdddb81a1}}, // шей_, _ayok, tgep, deuš,
+ {{0x2ee9031d,0x64589527,0x6602c439,0x62860cdb}}, // nnaf_, _advi, _byok, xeko,
+ {{0x6286443a,0x644ab611,0x7e69b177,0xb88380e1}}, // veko, _befi, rgep, _príč,
+ {{0x7aed443b,0x7cfdc43c,0xcd2881a8,0x753bc43d}}, // _ajat, vèrs, حسين_, rpuz,
+ {{0x62861711,0xdce4003b,0x6f04443e,0x64988196}}, // teko, stić, ylic, стыр_,
+ {{0x67228144,0x2485c43f,0x644a8032,0x6d4f8118}}, // _gioj, selm_, _eefi, ácas,
+ {{0xe56ea59a,0x644a802a,0x412a042f,0x7aed0168}}, // _юз_, _fefi, рого_, _djat,
+ {{0x8afd0d38,0x644ac440,0x186a0dc7,0x52b68fea}}, // stęp, _gefi, јави_, _अक्स,
+ {{0x6f040352,0x6441c441,0x68e480f7,0x93bc8087}}, // tlic, mbli, éidi, scăr,
+ {{0x7aed020f,0xee3a1289,0xf4128039,0x752382af}}, // _gjat, рна_, _קפה_, _hinz,
+ {{0x6f0443a6,0x764b8065,0x5d6a2306,0x6441c442}}, // [3240] rlic, _megy, ризм_, obli,
+ {{0x764b8065,0x44278057,0x81c200ab,0x6441989c}}, // _legy, _jgn_, ্পদ_, nbli,
+ {{0x6f044443,0x68fc04fe,0x61fb04b7,0x93460558}}, // plic, mord, żult, інде,
+ {{0xf1db946d,0xa206b01c,0x7523c444,0xe5a62482}}, // _मेहन, опед, _linz, _дими,
+ {{0x6722957a,0x68fc01ed,0x9c7c87dd,0x4427c445}}, // _rioj, oord, liče, _ogn_,
+ {{0x257404b8,0x68fc4446,0x764b82f7,0x4427c447}}, // mäl_, nord, _aegy, _ngn_,
+ {{0x9c7c803a,0x764b8a38,0x67228388,0x660280b9}}, // niče, _begy, _pioj, _syok,
+ {{0x35b4938c,0x752b810c,0x1ef781a8,0x68fc39dc}}, // збир, _bugz, يعية_, hord,
+ {{0x65762c8d,0xd04e0201,0x0caa9a19,0x94aa9ac9}}, // ntyh, sibə, атни_, атна_,
+ {{0xf06390ef,0x7ebc880d,0x7523c448,0x2cb80370}}, // _окуп, _répu, _cinz, _jord_,
+ {{0x44278483,0x9c7c8067,0x7aed4449,0x2cb81b4a}}, // _dgn_, jiče, _pjat, _mord_,
+ {{0xf3ec0a49,0x75238352,0x04fc80c8,0x050a80ab}}, // _করার_, _einz, ীদের_, রদের_,
+ {{0x644181dc,0xead2877f,0x2574016d,0x2d968294}}, // bbli, _rẹ̀_, jäl_, _фрес,
+ {{0x64588022,0x2cb84359,0x2bca800f,0x644a80c3}}, // _udvi, _nord_, ानदा, _uefi,
+ {{0xd6db444a,0x7aed0aa8,0xcb6782ee,0x00000000}}, // ата_, _tjat, _даје_, --,
+ {{0xa5351091,0x4427890d,0x25740338,0x7e7d09c4}}, // знач, _zgn_, fäl_, _qbsp,
+ {{0x2cb8444b,0x38c900d7,0xdb17066f,0x06cd0264}}, // [3250] _bord_, _هادی_, ńców, রচলি,
+ {{0x7afd444c,0x68fc07b5,0x3f9eb3bb,0x44278118}}, // lost, cord, _ortu_, _xgn_,
+ {{0xd0f80441,0x2d82444d,0xfe700019,0xfbab01e5}}, // ंकरण_, _epke_, عدہ_, штай_,
+ {{0x6ffa01c6,0x25bf8326,0x6e960061,0x3eb415d8}}, // _להתפ, yyul_, _فلپا, ľatá_,
+ {{0x64418114,0x3f9eb855,0x2cb8444e,0x764b83f7}}, // ybli, _artu_, _ford_, _regy,
+ {{0xe0d8835f,0xfbca89c2,0x55ba0039,0x752b890d}}, // ові_, ानाम, _המשו, _rugz,
+ {{0x3eb9444f,0x602283c7,0x7ebc8019,0x3f9e811f}}, // _kost_, ндша, _néps, _crtu_,
+ {{0x7e6d4450,0x0ea98364,0x7523950f,0x64418176}}, // ngap, ский_, _sinz, wbli,
+ {{0x3eb90051,0x68fc07d9,0x95558013,0x7ebc8ece}}, // _most_, yord, أخبا, _répt,
+ {{0xe73380f7,0x7ebc85e4,0x2b478114,0x6441c451}}, // _قصص_, _sépt, _pwnc_, ubli,
+ {{0x6441b958,0x7afd4452,0xe29a1510,0x68fc00c9}}, // rbli, fost, бан_, vord,
+ {{0x7afd4453,0x2907c454,0x9c7cc455,0x212d80ee}}, // gost, llna_, viče, _kueh_,
+ {{0x442780dd,0x261183eb,0x224dc456,0x75239abf}}, // _tgn_, देखी_, _meek_, _tinz,
+ {{0x97428067,0x659a812a,0x2574016d,0x27318091}}, // šćen, _לינק, väl_, _kán_,
+ {{0x3f5701cd,0x07a3140b,0x3eb94457,0x273180e1}}, // għu_, нарн, _bost_, _ján_,
+ {{0xcf92893f,0x7afd4458,0x7e6d255f,0x63bc8338}}, // עטן_, cost, ggap, ärne,
+ {{0x056636ae,0x68fc4459,0x27319cac,0x38b402af}}, // [3260] яван, pord, _lán_, _märz_,
+ {{0x629610c1,0x7e6d02c4,0x9c7c82d4,0x2731827d}}, // _hayo, agap, piče, _oán_,
+ {{0x3eb9445a,0x6296445b,0xc61d00ab,0x7bc1810c}}, // _fost_, _kayo, ধুলা_, nylu,
+ {{0x2fc50bc5,0x2cb80039,0xc7b303c8,0x212d8144}}, // ølge_, _word_, _טבת_, _bueh_,
+ {{0xd24f83f8,0xe8948c5c,0x7644445c,0x673a80b9}}, // انه_, даль, mbiy, _sttj,
+ {{0x27318104,0x7afd0dee,0x20058cf0,0x629631ee}}, // _bán_, zost, _byli_, _layo,
+ {{0xd6da8987,0x764402a3,0x2731801c,0x6d5d81a8}}, // йти_, obiy, _cán_, ásan,
+ {{0x44440352,0x7c3b0d11,0x98788353,0x442a1705}}, // lb_, _đuri, _išči_, _hgb_,
+ {{0x644e445d,0x7afd445e,0x444410af,0x6d56445f}}, // _kebi, vost, ob_, nsya,
+ {{0x7ae2c460,0x44444461,0x3d068424,0xdb1b0019}}, // liot, nb_, _सीखे_, sztü,
+ {{0x44440cd8,0x2731c462,0x62961e98,0x6d56010b}}, // ib_, _gán_, _bayo, hsya,
+ {{0x644e048f,0xddc400eb,0x6d564463,0x629601e0}}, // _lebi, maiņ, ksya, _cayo,
+ {{0x64a6b031,0x6296033e,0xdca6824f,0x0e9b8039}}, // _нада, _dayo, _нади, _לשאל,
+ {{0x760b8158,0x644e4464,0x442a4465,0xf8be83eb}}, // _פּאַ, _nebi, _ngb_, ्सिय,
+ {{0x3eb90051,0x62808353,0x44444466,0x78ba9309}}, // _post_, _obmo, db_, _motv,
+ {{0x44444467,0x629602b8,0x442a02af,0x65640bbd}}, // eb_, _gayo, _agb_, frih,
+ {{0xdb1b2422,0x7e6d3da1,0xa2aa0e00,0x672601b9}}, // [3270] sztó, rgap, टार्, _dikj,
+ {{0x66060aa2,0xb03523e7,0x66ca04e8,0x290701a8}}, // _dykk, _юнош, _výka, _óna_,
+ {{0x764421c7,0xa3e28540,0x62961c61,0x316c0035}}, // bbiy, _नेम_, _yayo, ądze_,
+ {{0x4444146a,0x2731c468,0x66ca026f,0x645c09c4}}, // ab_, _rán_, _týka, _edri,
+ {{0x44444469,0x27318091,0x442a446a,0x9c7c8110}}, // bb_, _sán_, _fgb_, anči,
+ {{0x644e105d,0x224d8039,0x2731816b,0xb5fb026b}}, // _gebi, _week_, _pán_, _adág,
+ {{0x7ebcc186,0xb90586a7,0x690d802a,0x644505e7}}, // _répr, _पग_, búen, mbhi,
+ {{0x3eb9808b,0x7ae2c46b,0x442a446c,0x78a8bff4}}, // ðst_, ciot, _zgb_, _endv,
+ {{0x6296446d,0x78278154,0x802782e3,0x628bc46e}}, // _rayo, _فعال, _فرام, mego,
+ {{0x2731c46f,0x628b88ff,0x62960637,0x6299c470}}, // _tán_, lego, _sayo, ldwo,
+ {{0xb4cd8076,0x62964471,0x764401b4,0x7527090d}}, // _रती_, _payo, ybiy, _mijz,
+ {{0x628bc472,0xbe15845b,0x91e61cb2,0x63a31c50}}, // nego, _قواع, доме, änny,
+ {{0x6f098c0b,0xdc0a1993,0xed4e9c3a,0x6f1bc473}}, // mlec, _वर्ड_, _шо_, mmuc,
+ {{0x7ae2c474,0x628b83a8,0x6f09c475,0x6606054f}}, // ziot, hego, llec, _rykk,
+ {{0xa3dd4476,0x62964477,0x660600e8,0x628b810c}}, // _तेल_, _tayo, _sykk, kego,
+ {{0x68e3c478,0x44442f54,0x9c7c805c,0x628bad61}}, // hind, wb_, miča, jego,
+ {{0x76444479,0x75270a0f,0x764f009a,0x644e447a}}, // [3280] rbiy, _bijz, _decy, _pebi,
+ {{0x444401e9,0x6d56447b,0x6f0982af,0x6aa99670}}, // ub_, rsya, hlec, _anef,
+ {{0x9c7c803a,0x6f09b1cd,0x764f0118,0x6d5600dd}}, // niča, klec, _fecy, ssya,
+ {{0x6606447c,0xb4db0032,0x628b89ff,0x6d560122}}, // _tykk, _atàd, gego, psya,
+ {{0x68e3c47d,0x78ba9620,0x7ae2c47e,0x644e447f}}, // find, _potv, riot, _tebi,
+ {{0x68e3859f,0x7ae38511,0x6f09c480,0x6f02c481}}, // gind, éntr, elec, _skoc,
+ {{0x628ba586,0xad9b0019,0xdfd080f7,0x2a7d822c}}, // bego, _nyúj, طية_, bfwb_,
+ {{0x628b8267,0x26d2008e,0x6abb9a1f,0x656d81d6}}, // cego, nhyo_, _gouf, _ťaha,
+ {{0x68e3c482,0x2246ae8f,0x24984483,0x3f8581a1}}, // bind, mbok_, _karm_, _eplu_,
+ {{0xa2da88d4,0x68e3c484,0x78a8b64c,0x9c7c812b}}, // पोर्, cind, _undv, fiča,
+ {{0x6f09840e,0x7d0880f2,0x9c7c812b,0x24980118}}, // blec, rlds, giča, _marm_,
+ {{0x7c2d8214,0x24980502,0x2246c485,0x6d40a651}}, // _şart, _larm_, nbok_, ppma,
+ {{0x2bde8beb,0xace981f9,0x471bb5c4,0xbddb026b}}, // _फेरा, _کرنل_, _וואג, _leèr,
+ {{0x9c7c803a,0x660f81ec,0x7b76a3f7,0x212b81e8}}, // biča, ücke, _قطعا, _èche_,
+ {{0xa3dd0e18,0x0cca864a,0x764f0428,0x22469388}}, // _तें_, िसीम, _pecy, kbok_,
+ {{0x68e3c486,0x290a0144,0x929d8035,0xbbca9d40}}, // zind, alba_, _okła, ानीक,
+ {{0xa3e285b3,0x68e3c487,0x9d153e09,0x22469f0a}}, // [3290] _नेत_, yind, едач, dbok_,
+ {{0x6abba246,0x68e3c488,0x628b8c2e,0xee3f01d6}}, // _souf, xind, wego, _iným_,
+ {{0xdce9805c,0x64450c41,0x6f09c489,0x3a3c8135}}, // steć, rbhi, ylec, _ịgụ_,
+ {{0x75270a0f,0x68e3c48a,0x9c13019d,0x6fae000d}}, // _wijz, wind, _bọro, ीहरू,
+ {{0x9c7c8024,0x6299c48b,0xbbca9664,0xcad80039}}, // ziča, rdwo, ानुक, וואת_,
+ {{0x26c9448c,0x3cf886a7,0x7b0382f1,0x6d5d8174}}, // lkao_, ँवों_, tõus, ásam,
+ {{0x68e3c48d,0x656d03ac,0x6abbc48e,0xfe7181a8}}, // rind, luah, _touf, هدة_,
+ {{0x68e3c48f,0xf53f0f96,0x273500f2,0x9c7c8503}}, // sind, _tråd_, _mån_, viča,
+ {{0x68e3c490,0x6f099e26,0x27350370,0x9980009a}}, // pind, rlec, _lån_, _dziś_,
+ {{0x9c7c803a,0x9742803a,0xc4d20158,0x213f82f7}}, // tiča, šćan, ָגט_, _ituh_,
+ {{0xe29a221f,0x273500f2,0x320902a3,0x28ac8aed}}, // пан_, _nån_, _ayay_, चारि,
+ {{0xe29a1597,0xd0550085,0x63bc816d,0xdce40214}}, // чам_, mizə, ärna, ktiğ,
+ {{0x9c7cc491,0xa3e600c2,0xfd5081bc,0x213f8609}}, // mičn, _बेन_, _medị, _jtuh_,
+ {{0x9c7c80ce,0x657b8c93,0x213f83e4,0x00000000}}, // piča, mtuh, _mtuh_, --,
+ {{0x9cd68051,0x657bb4e0,0x8cac866f,0x394d8176}}, // _רוצה_, ltuh, जारो, _lwes_,
+ {{0x7bda8051,0x66ca0efc,0x225f923c,0x2201019d}}, // _מקצו, _výko, _nduk_, ụsịl,
+ {{0x657bc492,0x7c3b0085,0x25798247,0x290580b9}}, // [32a0] ntuh, _şura, nèl_, _okla_,
+ {{0x7ae64493,0x225f8763,0x33f6046e,0xfa1580ab}}, // likt, _aduk_, нчес, িশাল_,
+ {{0x929d809a,0x213f8573,0x24980370,0x69c64494}}, // _skła, _atuh_, _varm_, lyke,
+ {{0x213f808e,0x26c900d2,0x657b977c,0x9c7c8824}}, // _btuh_, ckao_, ktuh, jičn,
+ {{0x28ac85e8,0x9c7c8b24,0x27f200eb,0x76560041}}, // चालि, dičn, šanā_, mayy,
+ {{0x26cf90dd,0xd90d80d5,0x02cc13e5,0x257986c0}}, // _algo_, قیق_, ासुन, dèl_,
+ {{0x56948607,0x9c7c8824,0xb9250133,0x929d8035}}, // вайт, fičn, _akpụ_, _wkła,
+ {{0xd6d78009,0x39400e35,0xdd908250,0xc7b304de}}, // еть_, _otis_, لوب_, רבה_,
+ {{0x3940022c,0x929d809a,0x88869a19,0xe0dab93f}}, // _ntis_, _ukła, _плаж, _ева_,
+ {{0x6284217b,0x20128085,0x2a604495,0x20d402f1}}, // _abio, əyin_, _adib_, _jäid_,
+ {{0xdeef93cd,0x9c7c803a,0x394006c0,0x76560041}}, // _бы_, bičn, _atis_, kayy,
+ {{0xe8d901bc,0x7ec68036,0x27350106,0x28a482f1}}, // _mpụ_, _dépê, _rån_, _गोति,
+ {{0x2735016d,0x765605a3,0x66ca016b,0x248c808e}}, // _sån_, dayy, _výkl, pedm_,
+ {{0x2ec90e70,0x2a600201,0x7d018706,0x628402c4}}, // रस्त, _edib_, cols, _ebio,
+ {{0xb909035a,0xf53f13c2,0x394002f7,0x4394b61c}}, // _मग_, _stå_, _etis_, _райс,
+ {{0x5694210d,0xf1c68028,0x752a9087,0xdce40214}}, // ласт, _đánh_, _hifz, ttiğ,
+ {{0xb81680ab,0x6aad0010,0xe8d90133,0x6d44222e}}, // [32b0] াশিত_, _inaf, _apụ_, gpia,
+ {{0x629d4496,0x473502de,0x9c7c8805,0x26c90503}}, // ldso, _инос, zičn, skao_,
+ {{0x290300f6,0x37e580ab,0x76560326,0x26c900fe}}, // moja_, _পুনর, bayy, pkao_,
+ {{0xd6db0160,0x25798205,0x7b2c0214,0x29032dd9}}, // _хто_, yèl_, _ağus, loja_,
+ {{0x9c7c9408,0x7bc50859,0x6f0d2f9a,0xb0ac80d4}}, // vičn, syhu, mlac, चांग,
+ {{0x25798247,0x29034497,0x9c7c9b2c,0xd0098012}}, // vèl_, noja_, dičo, меле_,
+ {{0x9c7c8ee1,0x6aad00c9,0x628f00b9,0x6f0d0635}}, // tičn, _onaf, keco, olac,
+ {{0x2579c498,0x225f86cb,0x6f0d4499,0x442e8366}}, // tèl_, _uduk_, nlac, _agf_,
+ {{0x9c7c84c4,0x213fbcf2,0x2903449a,0x7ae61f6c}}, // ričn, _utuh_, koja_, vikt,
+ {{0x6f0d3428,0x657b80dd,0x6aad449b,0xa3e282f1}}, // hlac, rtuh, _anaf, _नेह_,
+ {{0x657ba108,0x765600a4,0x7ae6449c,0xe7b59459}}, // stuh, yayy, tikt, _جماد,
+ {{0x2d4f0063,0x6e3d0267,0x6448826c,0x6f0d00e1}}, // kże_, _nfsb, abdi, jlac,
+ {{0x7d01809a,0x69d9c49d,0x7649bdd0,0x443c8163}}, // pols, nzwe, mbey, _ffv_,
+ {{0x7ae60448,0x29034319,0x442e81ec,0x61430956}}, // sikt, goja_, _ggf_, рера,
+ {{0x66e62707,0x225202c4,0x6d440114,0xd0070284}}, // вова, _teyk_, wpia, вере_,
+ {{0x6e3b8ad4,0x6f0d449e,0x246400eb,0xe3b2026a}}, // ncub, glac, zīmē_, یرا_,
+ {{0x7ae4449f,0xed570003,0xf65280be,0x765600a4}}, // [32c0] _imit, кот_, יצן_, rayy,
+ {{0x672b820f,0x6f0d1a29,0xa3c88105,0x315700be}}, // _ligj, alac, लैक_, _שיין_,
+ {{0x6f0d44a0,0x7989c4a1,0x6d5bc4a2,0xd8388668}}, // blac, _apew, hsua, _jače_,
+ {{0x6146b4fb,0x765600dd,0xd0418085,0x6d440a0e}}, // веда, qayy, _illə, ppia,
+ {{0xd91089d7,0x6d5b81c0,0x7ae4038a,0x64488df6}}, // میر_, jsua, _mmit, ybdi,
+ {{0x044344a3,0x8c432341,0x65699831,0x9c7c9e1e}}, // _верн, _вере, dreh, vičo,
+ {{0x66d10106,0xc60700ab,0xdce0826c,0x20d402f1}}, // _håka, লেদা_, lumć, _käib_,
+ {{0xe81a0105,0x25a99f27,0x0cca9905,0x68e7090d}}, // _धरना_, _šala_, िस्म, zijd,
+ {{0x6569c4a4,0x80d300c8,0x628f44a5,0x672b8168}}, // greh, _সদস্, veco, _digj,
+ {{0x3946a5a7,0xe4a6916b,0x7ae444a6,0x6282c3f1}}, // mpos_, трио, _amit, lfoo,
+ {{0x290306ee,0xdced025b,0xd4978d15,0x3946989e}}, // voja_, stać, тры_, lpos_,
+ {{0x290e8114,0x7e989ef7,0x68e701ed,0xdce41f27}}, // llfa_, _سنتر_, wijd, drič,
+ {{0x6f0d0052,0x290344a7,0x68e700f3,0x4cba80ab}}, // vlac, toja_, tijd, ইসবু,
+ {{0x7ae444a8,0x394682ba,0x5ba687b6,0xd3a68087}}, // _emit, ipos_, _ариз, _арип,
+ {{0x4fc40190,0xc7c412b2,0x290344a9,0x68e700f3}}, // аста, асти, roja_, rijd,
+ {{0x6aad004f,0x8f9b83de,0x290344aa,0x212c8168}}, // _unaf, וילי, soja_, _lidh_,
+ {{0x6f0d198d,0x1be8064a,0xa6152bd9,0xa5bb2294}}, // [32d0] rlac, _टेबल_, умач, _agór,
+ {{0xdce41024,0x26058424,0x03c744ab,0xe7e38074}}, // brič, _हँसी_, ксем, _कइला_,
+ {{0x645744ac,0x6f0d2183,0x273880ff,0xed46804e}}, // raxi, plac, _nén_, _گپ_,
+ {{0x69d982af,0xe8e1001c,0x6d5d44ad,0x212c8229}}, // tzwe, _đột_, _tvsa, _aidh_,
+ {{0x212c8014,0x9985987e,0x3946b88d,0xda788c4f}}, // _bidh_, التو, gpos_, тях_,
+ {{0x92e380ab,0x212cc4ae,0x69d99cb6,0x2738c4af}}, // নকে_, _cidh_, rzwe, _bén_,
+ {{0xe81e8e18,0x27388013,0x66e5802e,0x660b80ee}}, // येला_, _cén_, лока, _sygk,
+ {{0xdd91880b,0x3869880a,0x64553de3,0x6d5bc4b0}}, // موع_, şar_, _hezi, tsua,
+ {{0x6e3bc4b1,0x273881ed,0x25a68115,0x28a48c2d}}, // rcub, _eén_, _crol_, _गोवि,
+ {{0x6e3bab72,0x7b2c88c5,0x6f04002a,0x9c7ca944}}, // scub, _uğur, zoic, ličj,
+ {{0xa3d60f12,0xd0118f99,0x6d5bc4b2,0x2738c4b3}}, // िनय_, _الا_, ssua, _gén_,
+ {{0x645544b4,0xd94601f3,0x6569c4b5,0x216a035f}}, // _lezi, леми, preh, ними_,
+ {{0x60c198c5,0x3eaf80b9,0xdbc982f1,0x7d0503ed}}, // _kolm, _ingt_, _rõõm, kohs,
+ {{0x645544b6,0x273888f9,0x66d10711,0x8d5a8039}}, // _nezi, _yén_, _råka, _טכני,
+ {{0xf456810f,0x657d008e,0x66d10aa2,0x2d9944b7}}, // _עיקר_, _aqsh, _såka, _asse_,
+ {{0x66e301a1,0xdb041a1f,0x94428061,0x7ae4113b}}, // боса, _orné, _پھین, _umit,
+ {{0x645544b8,0x6f0444b9,0x25a601a8,0x3dc90122}}, // [32e0] _bezi, roic, íol_, gyaw_,
+ {{0xdce40805,0x779402e3,0x6ea401ab,0x85038a74}}, // prič, _پیرا, _चोरु, रकिट_,
+ {{0x2d9900a9,0xe7e3835a,0x332daf1e,0x645510e8}}, // _esse_, _केला_, _biex_, _dezi,
+ {{0xa3c88105,0x394682c4,0x443144ba,0x2d860187}}, // लैट_, tpos_, _egz_, çoes_,
+ {{0x9f060065,0xaca401bc,0xaa5795a9,0x63a705ee}}, // _لوگو, _atụs, الها_, _erjn,
+ {{0x645544bb,0x307a00be,0xb42601ad,0x7e62c4bc}}, // _gezi, גאַנ, _معزو, _sdop,
+ {{0x26c244bd,0xbf0f835a,0x3946c4be,0x3eaf82f7}}, // _koko_, ातून_, spos_, _bngt_,
+ {{0x3ea008cf,0x2738c4bf,0x25a69d3a,0x64550234}}, // ndit_, _vén_, _prol_, _zezi,
+ {{0x2d8044c0,0xb8ea80ab,0x7ebc8019,0x26c244c1}}, // ntie_, _লগ_, _képz, _moko_,
+ {{0x60c18503,0x26c244c2,0x859700be,0x9c7c98a6}}, // _golm, _loko_, נדיג_, mičk,
+ {{0x9c7c9249,0xa92385b9,0x8cd90035,0xee868084}}, // ličk, ížen, फोटो, _было,
+ {{0xbda68154,0x57fb8039,0x26c2021e,0xdc3c8517}}, // _محتو, _אלבו, _noko_, mšči,
+ {{0x9c7c82a5,0x7bdab1d6,0xb5fb0091,0x6d42804f}}, // ničk, sztu, _adán, _utoa,
+ {{0x3aeb06c4,0x316002d6,0xa2a6864a,0x22403353}}, // _bòpè_, _dviz_, _घोस्, _efik_,
+ {{0x645544c3,0x26c200a4,0x9c7c805c,0xdc3c8353}}, // _rezi, _boko_, hičk, nšči,
+ {{0x645544c4,0x26c201f4,0xdc3c9502,0x00000000}}, // _sezi, _coko_, išči, --,
+ {{0xa3dd023c,0x26c2026b,0xdc55230e,0x645544c5}}, // [32f0] _तेज_, _doko_, ивањ, _pezi,
+ {{0xf53f1918,0x9c7c803e,0x2bb884e5,0x443ec4c6}}, // _från_, dičk, इमपा, nct_,
+ {{0x5a35938c,0x92598364,0x2d8044c7,0x31600267}}, // анет, вает_, atie_, _zviz_,
+ {{0x9c7c8024,0x64550234,0xf991853d,0x60c1812c}}, // fičk, _wezi, قبت_, _solm,
+ {{0x2d8044c8,0x629f44c9,0x50b800d5,0x645544ca}}, // ctie_, _maqo, _شدید_, _tezi,
+ {{0xb4c38a61,0x645ac4cb,0xe05780d5,0x3eaf836e}}, // _एके_, jati, ایات_, _sngt_,
+ {{0x44253715,0xa3dd016f,0x60c1858f,0x645aa62b}}, // _àl_, _तेच_, _volm, dati,
+ {{0x8c0a80c8,0x9c7c9807,0x660f016d,0x0565c4cc}}, // রধান_, bičk, _kyck, авин,
+ {{0x645a80b4,0x63418029,0x057680f7,0x798d0069}}, // fati, _mēne, لمية_, _npaw,
+ {{0x660f1918,0x3b858b79,0xc48293cd,0x7aebc4cd}}, // _myck, алог, ольк, ligt,
+ {{0x660f00f2,0x2907c4ce,0x764d44cf,0x629f3670}}, // _lyck, mona_, kbay, _baqo,
+ {{0x2907c4d0,0xceb40201,0x443e85ae,0xf770806b}}, // lona_, rmək_, act_, _رام_,
+ {{0x660f016d,0x656d03ac,0x26c244d1,0xa91d8084}}, // _nyck, jrah, _roko_, _apži,
+ {{0x645a87cc,0x03a58cde,0x29079fca,0x6d4980a0}}, // cati, _вико, nona_, mpea,
+ {{0x9c7c8289,0xd8388024,0xb21b013c,0x26c206c0}}, // zičk, _jača_, lfæl, _poko_,
+ {{0xe51980a5,0x2907c4d2,0x799bc4d3,0x764d132b}}, // _नीति_, hona_, _osuw, gbay,
+ {{0x3ea044d4,0x7aebc4d5,0x656d44d6,0x6d49c4d7}}, // [3300] rdit_, digt, grah, npea,
+ {{0x2d8044d8,0x9c7c8f20,0x2907c4d9,0xb4c3908a}}, // rtie_, vičk, jona_, _एको_,
+ {{0xa7fc8182,0x26c20854,0x2d80281b,0x3dbd80c8}}, // _adın, _toko_, stie_, েছিল,
+ {{0x9c7c8025,0x656d0726,0x645ab499,0x2d8044da}}, // tičk, brah, zati, ptie_,
+ {{0x69268087,0xaca40133,0xa3e60006,0x672401b9}}, // амда, _agụr, _बेर_, mmij,
+ {{0x273c221c,0x9c7c803b,0x290787e2,0x645ac4db}}, // _kín_, ričk, gona_, xati,
+ {{0x645ac4dc,0xe0b703de,0x0306064a,0x9c7cc4dd}}, // vati, ילסט_, रवाह_, sičk,
+ {{0x645a9dde,0x273c0125,0xf102001b,0xd8388088}}, // wati, _mín_, लकुद_, _dača_,
+ {{0x2907c4de,0x01cb00ab,0x38658314,0x25b0852a}}, // bona_, _লেনদ, _fdlr_, जमेण,
+ {{0x2907b227,0x273c0118,0x67240267,0xdc3c82d4}}, // cona_, _oín_, hmij, pšči,
+ {{0x645ac4df,0x764d0c27,0x50b5c4e0,0xb4d68beb}}, // rati, ybay, _усту, _सती_,
+ {{0x645a9dbd,0x6d46003a,0x672437d7,0x629f0609}}, // sati, _otka, jmij, _qaqo,
+ {{0xc05b0cde,0x79828063,0x2c06816f,0x645ac4e1}}, // ків_, ntow, _सुरू_, pati,
+ {{0x645ac4e2,0x386d02af,0xa3d6364b,0x660f0106}}, // qati, ßer_, िना_, _ryck,
+ {{0x386d07d9,0x251b812a,0x6d46252a,0x64588029}}, // ğer_, _שווא, _atka, _ievi,
+ {{0x656d44e3,0x7982809a,0xd6d80c0e,0x6458bb2d}}, // trah, ktow, ату_, _hevi,
+ {{0x764d0965,0x2907c4e4,0x798d005d,0x84470481}}, // [3310] rbay, yona_, _upaw, لخال,
+ {{0x40950013,0xb89500f7,0x6458c4e5,0x67240079}}, // _الدر, _الدع, _jevi, amij,
+ {{0x64588267,0x290795a4,0xd00800a9,0x656d44e6}}, // _mevi, vona_, _веќе_, srah,
+ {{0x660f04b8,0x23670353,0x290c809c,0x69cb8106}}, // _tyck, šnjo_, _akda_, tyge,
+ {{0x7aebc4e7,0x7d08c4e8,0x6aa0982f,0x1869971c}}, // rigt, kods, _damf, гали_,
+ {{0x6458c4e9,0x66d8026f,0x7aebc4ea,0x7642826b}}, // _nevi, _víke, sigt, _afoy,
+ {{0x2907c4eb,0x6aa080fc,0x79828035,0x7aebc4ec}}, // rona_, _famf, atow, pigt,
+ {{0x20d40364,0x2907c4ed,0x66d584be,0x77f8025f}}, // _näin_, sona_, _báka, ימוד_,
+ {{0x7ae9c4ee,0x6458c4ef,0x2738801c,0x799bc4f0}}, // _imet, _bevi, _lĩnh_, _usuw,
+ {{0x2ba58341,0x6aa0c4f1,0x66d10186,0x25ff0424}}, // _pēc_, _zamf, _såkl, _रखनी_,
+ {{0x7ae98353,0x1fe58264,0xfaa600e5,0xe217824c}}, // _kmet, _পুরস, бано, _तरुण_,
+ {{0x6d5d8118,0x7afb85ee,0x6aa08580,0x6d49c4f2}}, // ásas, _jjut, _xamf, ppea,
+ {{0x4545806b,0x64a5a950,0x7ae98135,0x68e8831d}}, // _انتق, _гала, _mmet, _ymdd,
+ {{0x7afb811f,0x05b8826a,0x6458c4f3,0x6ac48054}}, // _ljut, _خدمت_, _gevi, _वक्र,
+ {{0x66d58a56,0xb8f00996,0x7b33802e,0x96db801b}}, // _záka, _शव_, _căut, _बताउ,
+ {{0x3ebf8867,0x9c7c81e2,0x7afbc4f4,0x6458913b}}, // njut_, viči, _njut, _zevi,
+ {{0xe61a102f,0x67240a0f,0x998600f7,0xb5fb01d0}}, // [3320] уда_, rmij, _الخو, _udál,
+ {{0x7afbc4f5,0x7ae9c4f6,0x6724120e,0x6aa0c4f7}}, // _ajut, _amet, smij, _samf,
+ {{0x7ae98037,0x1d1683de,0xa09b0538,0x2fd00493}}, // _bmet, צקער_, _גייט, ângă_,
+ {{0x76598355,0xdbd0027f,0x9c7c943c,0x290a44f8}}, // _newy, _užív, riči, loba_,
+ {{0xe3a78986,0x78a180eb,0x6d460687,0x798280fc}}, // _کر_, _balv, _utka, utow,
+ {{0x7ae9c4f9,0xa3d50076,0x79828063,0x26c0012b}}, // _emet, ाईश_, rtow, ljio_,
+ {{0x6458c4fa,0x645e44fb,0xf0930039,0x7982c4fc}}, // _revi, mapi, פנה_, stow,
+ {{0x6458a2f8,0x645e44fd,0x26c0012b,0x26d2008e}}, // _sevi, lapi, njio_, nkyo_,
+ {{0x7cd88bba,0x7ae3838e,0x645882f7,0x7d08c4fe}}, // имир_, ënte, _pevi, tods,
+ {{0x78a1a338,0x645e1868,0x290a1c00,0xd49a9593}}, // _galv, napi, joba_, ури_,
+ {{0x387e80f6,0xa6868e17,0x241897ae,0xdee68e49}}, // _ictr_, _глед, роны_, _ложи,
+ {{0x3f9e803a,0x645e0077,0x6443807b,0x7659867f}}, // _istu_, hapi, _efni, _gewy,
+ {{0x645e272f,0x290a0267,0x612018b6,0xa6a987c3}}, // kapi, foba_, köld, _خالق_,
+ {{0x64a38d70,0x27388104,0x290a0365,0x7e7d026c}}, // _нача, _cũng_, goba_, _scsp,
+ {{0x368b0071,0x645e3afe,0x673ac4ff,0x273880ff}}, // лсан_, dapi, _jutj, _dũng_,
+ {{0xccf8800d,0x2bac04c5,0x2738801c,0xfbdf00ab}}, // lně_, घटना, _vĩnh_, _বুঝত,
+ {{0x634501e2,0x60c51d0e,0x628d4500,0x673a8168}}, // [3330] _mėne, _tohm, _ibao, _lutj,
+ {{0x645e0447,0x7aef2bd5,0x290a4501,0x69cf1916}}, // gapi, lict, coba_, myce,
+ {{0x43941b47,0x3ea24502,0x16350198,0x9e350196}}, // захс, _fakt_, _меня, _менч,
+ {{0x78a1a91e,0x7aef109b,0xd0488085,0x6f09802a}}, // _salv, nict, _ildə, xoec,
+ {{0x78a18364,0x645e25b3,0x1a68815b,0xccf881d0}}, // _palv, bapi, صیلی_, kně_,
+ {{0x661d0029,0xd90f80d5,0xccf8801b,0x6d4d08e5}}, // _izsk, ریا_, jně_, mpaa,
+ {{0xccf8800d,0x2eed85f8,0x78a18399,0x25a94503}}, // dně_, tief_, _valv, lval_,
+ {{0x7ae9c504,0x7afb803b,0xee399a19,0x290a4505}}, // _umet, _ujut, шни_, zoba_,
+ {{0x7aef0bd9,0x2eed85f8,0x78a1c506,0x25a90cfa}}, // dict, rief_, _talv, nval_,
+ {{0x33658c8e,0x2eed8a0f,0x2a69208b,0x6f0496f2}}, // овог, sief_, _adab_, čica,
+ {{0x39491a67,0x2ca30065,0x66d8007b,0xf53f0711}}, // _atas_, _majd_, _líka, _bråk_,
+ {{0xc5d58d13,0xeb968009,0xc04903c8,0x6441c507}}, // _міль, цию_, _יז_, scli,
+ {{0x290a4508,0x80b500ab,0x3ea2016d,0xccf8801b}}, // toba_, ুসন্, _rakt_, bně_,
+ {{0x26d904e7,0x6efe81a9,0xccf881d0,0xceb4011c}}, // _also_, mība, cně_, hmət_,
+ {{0x6efe80eb,0x61200dfa,0x3ea24509,0x7ce90085}}, // lība, völd, _pakt_, _görə,
+ {{0x7e7bc50a,0xa3e283b6,0x290a450b,0x645e004f}}, // ngup, _नेट_, soba_, wapi,
+ {{0x0566035f,0x6efe80eb,0x6d4d2575,0xceb40085}}, // [3340] юван, nība, gpaa, dmət_,
+ {{0x6aa403ae,0xa3d52743,0xdb210198,0x7b3418ad}}, // _haif, ाईं_, ätök, _găur,
+ {{0xeb9a450c,0x3ea2209b,0x3984450d,0x6aa41325}}, // рие_, _takt_, lös_, _kaif,
+ {{0x66dcc50e,0x645e0d58,0x094aa748,0x914a97c8}}, // _kéke, sapi, ачки_, ачка_,
+ {{0x673ac50f,0x645e0ed0,0xccf8801b,0x39842976}}, // _sutj, papi, yně_, nös_,
+ {{0x6aa400a4,0x83fc812b,0x6efe80eb,0xb50386b7}}, // _laif, lađe, dība, रकोप_,
+ {{0xd6daa05f,0xccf8800d,0x444400ee,0xcb670012}}, // ити_, vně_, mc_, _маре_,
+ {{0x83fc803a,0x76444510,0x44444511,0x758a891c}}, // nađe, nciy, lc_, асов_,
+ {{0x01638951,0x645c0590,0xccf8800d,0x44444402}}, // _окто, _keri, tně_, oc_,
+ {{0x44444512,0x645c4513,0x394981df,0x673a83ed}}, // nc_, _jeri, íase_, _tutj,
+ {{0x44444514,0x44384515,0xccf8801b,0x2018807b}}, // ic_, _mgr_, rně_, _ári_,
+ {{0xccf8801b,0x65644516,0x6aa42bbe,0xe8e100ff}}, // sně_, ksih, _caif, _đốt_,
+ {{0x66d58775,0x7e608590,0x64a69e25,0x6efe8029}}, // _záko, mamp, _мада, cība,
+ {{0x645c4517,0x9c7c82a5,0x7ce90085,0x69cf0362}}, // _neri, tiču, _törə, ryce,
+ {{0x7ce04426,0x6aa40300,0x40341290,0xceb40085}}, // _körf, _faif, дерс, ymət_,
+ {{0x7e60c518,0x66d5803e,0x83fc8067,0x645c4519}}, // namp, _nákl, gađe, _aeri,
+ {{0x645c451a,0x25a9451b,0x6564451c,0x4444451d}}, // [3350] _beri, uval_, gsih, fc_,
+ {{0x645c0859,0x25a935da,0x200101c0,0x2bae8697}}, // _ceri, rval_, _txhi_, टिपा,
+ {{0x6d4d387f,0x6efe80eb,0x4438451e,0x6b850122}}, // spaa, zība, _dgr_, uthg,
+ {{0x4444451f,0xb5fd8353,0x6d4d0009,0x76440041}}, // ac_, laše, ppaa, cciy,
+ {{0x27ff0459,0x661d0029,0x7aed4520,0x78a54521}}, // ğunu_, _uzsk, _mmat, _kahv,
+ {{0x645c4522,0x4438016d,0x8505015b,0xb5fb00f7}}, // _geri, _ggr_, _روزن, _ndái,
+ {{0x765d0247,0x64454523,0x6722a817,0xd343015b}}, // _kesy, mchi, _bhoj, _آفری,
+ {{0x64454524,0x645c4525,0x6efe80eb,0x7e60a34d}}, // lchi, _zeri, tība, gamp,
+ {{0x645c0459,0x765d4526,0x80cc052a,0x75351a14}}, // _yeri, _mesy, _सकले, _kizz,
+ {{0x66d58a56,0xdced003e,0x75350609,0x6aa44527}}, // _zákl, krač, _jizz, _saif,
+ {{0x7ce000f2,0x64454528,0x6efe8029,0xd378812b}}, // _förf, ichi, sība, guć_,
+ {{0xf8bd80ab,0x6efe81a9,0x7aed0118,0xdced007a}}, // _অতিথ, lībn, _cmat, drač,
+ {{0x171a8158,0x14a6816f,0x83fc8289,0x6729c529}}, // _פונע, _कोकण, vađe, mmej,
+ {{0x7aed011e,0x75350c9f,0x17fa00f7,0xdca5a659}}, // _emat, _nizz, مرأة_, _хаки,
+ {{0x6aa4452a,0x753d452b,0x61e2c52c,0x3f87812b}}, // _taif, _ausz, zzol, ntnu_,
+ {{0xee3a3c3d,0xaad00076,0x3b070698,0x443801e4}}, // сна_, _हकिक, _нещо_, _sgr_,
+ {{0x83fc803a,0x4438452d,0xa3de001b,0x765d0176}}, // [3360] rađe, _pgr_, दैन_, _desy,
+ {{0x65640057,0x4444452e,0x6445056c,0x3f87825b}}, // rsih, uc_, gchi, ktnu_,
+ {{0x444443c3,0x7535452f,0x6f0d062f,0x645c3396}}, // rc_, _dizz, boac, _veri,
+ {{0xd7bb004c,0x78a50289,0x4444444b,0xdce40024}}, // _הצטר, _zahv, sc_, krić,
+ {{0x60c88690,0x645c1342,0xdce4026c,0x7e60c530}}, // _podm, _teri, jrić, vamp,
+ {{0x64450698,0xdce402a5,0x9c13019d,0xb5fb046d}}, // cchi, drić, _bọdo, _adáw,
+ {{0x7e60c531,0xb8f4857a,0x61e28019,0x6d4b8110}}, // tamp, _हक_, szol, _atga,
+ {{0xb8d39370,0x6f04992c,0x35b4807f,0x7535038a}}, // _जो_, čico, дбир, _zizz,
+ {{0x7e608886,0xa06ac532,0x186a98a3,0x60229b88}}, // ramp, _газа_, _гази_, _bímí,
+ {{0x7aed0025,0x7e60b55e,0x656fc533,0xb4d6809a}}, // _smat, samp, ácha, ससे_,
+ {{0xa3be05b3,0x78a50006,0x7e609890,0x7b370b67}}, // ेहि_, _rahv, pamp, _ućut,
+ {{0x7ce00106,0x66028102,0xd7ef81a8,0xdced02d4}}, // _förg, _txok, _حكم_, vrač,
+ {{0xf2d38158,0xf1be8076,0x7ce0016d,0xaca40032}}, // גער_, ्हिन, _körd, _awọs,
+ {{0x2367012b,0x3d148740,0x765d445f,0x6299809a}}, // šnji_, _नीचे_, _sesy, zewo,
+ {{0xbebb020f,0x78a508e5,0x7ce00884,0xb5fd9809}}, // _dhën, _vahv, _mörd, raše,
+ {{0x59be8e18,0xabfb8051,0x7ce000f2,0x7aed4534}}, // ्हार, _להור, _lörd, _umat,
+ {{0xdd940084,0x65940254,0x644501c6,0x78a5209e}}, // [3370] наты, нату, tchi, _tahv,
+ {{0x81e600ab,0xdced026c,0x3ea680e4,0xa11501a8}}, // _বুক_, prač, _laot_, _زوجت,
+ {{0x62998e35,0x7afd05e6,0x75350197,0x64454535}}, // tewo, onst, _vizz, rchi,
+ {{0x753d4536,0x7afd0e5d,0x64454537,0x386ca817}}, // _tusz, nnst, schi, _addr_,
+ {{0x6299be5b,0x7afd4538,0x64454539,0x7535111d}}, // rewo, inst, pchi, _tizz,
+ {{0x66d8007b,0x7afd30e5,0x008a00be,0xdce40390}}, // _líkl, hnst, ַרפֿ, vrić,
+ {{0xb4fb03eb,0x7ce007c0,0xdb240061,0x917a027d}}, // ्वीप_, _dörd, őség, _mất_,
+ {{0x24bf00c8,0xdd918872,0x3f87825b,0x26cb40d4}}, // _ইতিহ, نوع_, rtnu_, _joco_,
+ {{0x7ce029ed,0x3f87c53a,0x29010267,0xfce591d0}}, // _förd, stnu_, _ojha_, допо,
+ {{0xd13183f8,0x7afd453b,0x26cb453c,0x6729c28e}}, // _شما_, enst, _loco_, smej,
+ {{0xf77092dc,0x3169453d,0xb7d58c2a,0x00000000}}, // _مان_, _avaz_, _تقاب, --,
+ {{0xbebb020f,0xc8690051,0x2901008e,0x25a9811f}}, // _shën, _כן_, _ajha_, _šalu_,
+ {{0xdce2807a,0x3ea90106,0x6d4b80e8,0x7ce001ec}}, // _zvoč, ddat_, _utga, _höre,
+ {{0x225f8613,0x7afd2c15,0xda2081ab,0xbddb0176}}, // _leuk_, anst, _बरसत_, _efèk,
+ {{0x25ad82f1,0xaa7b06d4,0x00000000,0x00000000}}, // lvel_, uhýc, --, --,
+ {{0x2ca7b96a,0x394d826c,0xef1a0221,0x4ae206ab}}, // _hand_, _otes_, іма_, _पतिव,
+ {{0xbebb00f1,0x394d8069,0x0906237e,0x2ca7c53e}}, // [3380] _thën, _ntes_, _опин, _kand_,
+ {{0x2ee0453f,0x3ea601a9,0x62840176,0x66dc88f9}}, // shif_, žot_, _hcio, _méka,
+ {{0x3940240d,0x2ca7813c,0x4f260d69,0x66dc801b}}, // _huis_, _mand_, _одоб, _léka,
+ {{0x2ca7c540,0x26cb0279,0x83fc8301,0x2d9f81bc}}, // _land_, _goco_, nađa, kwue_,
+ {{0x6143c541,0xd00f0416,0xa1588009,0x394001b0}}, // веча, _ملی_, чалу_, _juis_,
+ {{0x83fc812b,0x39404542,0xd83881a1,0x2a602b3a}}, // hađa, _muis_, _kači_, _leib_,
+ {{0xd838a828,0xb906856b,0xdb02016b,0x394dc543}}, // _jači_, _पत_, zvlá, _etes_,
+ {{0x2ca781b0,0x7e642307,0x225f8cdb,0x356a862c}}, // _aand_, naip, _geuk_, орон_,
+ {{0x25a01705,0x2ca79aad,0x20d08362,0x66dc810c}}, // nwil_, _band_, _bàit_, _céka,
+ {{0x7ce00004,0x2ca78012,0x29188019,0x38cb8065}}, // _före, _cand_, _óra_, _والی_,
+ {{0x7ce04544,0x7e643e21,0xb5fb04e8,0x917a0129}}, // _göre, kaip, _udáv, _rất_,
+ {{0x83fc803a,0xf9e700ab,0x7e6406df,0xeb9100be}}, // gađa, _গুগল_, jaip, אָך_,
+ {{0x51844365,0xaca40032,0x2ca7ab29,0xc984002e}}, // кура, _afọr, _fand_, кури,
+ {{0x6e22c545,0x39404546,0xa8030214,0x26cb4547}}, // _izob, _duis_, _çıkm, _soco_,
+ {{0x26cb4548,0x83fc8024,0xb5fd812b,0x3ea90cfa}}, // _poco_, bađa, maša, tdat_,
+ {{0xb5fd992c,0x2ca781ed,0x5694031a,0x628401a8}}, // laša, _zand_, каст, _gcio,
+ {{0x78baa4de,0x3ea94549,0xd83881a1,0x7ae289c4}}, // [3390] _ontv, rdat_, _dači_, dhot,
+ {{0x2edb946d,0x78a8b78d,0xdb0401ec,0x651501ad}}, // _बत्त, _nadv, _ernä, روائ,
+ {{0x68f502bb,0x3a759a1b,0x26cb428b,0x394d8580}}, // mizd, елер, _toco_, _stes_,
+ {{0x7ce0016d,0x2911454a,0x7ae2808e,0x628f01e8}}, // _röre, loza_, ghot, nfco,
+ {{0x644ac54b,0x672d80e1,0x7ce0048d,0x99158a4c}}, // _offi, ňajk, _söre, нькі,
+ {{0x68f503bf,0x8d5a02f6,0x2911154e,0x66dcbd9b}}, // nizd, _מכשי, noza_, _réka,
+ {{0x3861034a,0x7ce004b8,0x2ca78252,0x225f90e4}}, // _mehr_, _förb, _rand_, _teuk_,
+ {{0xfaff08cf,0xf838004c,0x644ac54c,0x7ae2c54d}}, // jnë_, ונות_, _affi, chot,
+ {{0x291129f7,0x91ee00d4,0x2ca781ed,0x6f0282a6}}, // koza_, _चेंज_, _pand_, _djoc,
+ {{0x23670025,0x6284454e,0x25adc54f,0x7ce04550}}, // šnju_, _scio, rvel_, _töre,
+ {{0x394003d3,0x2ca7c551,0x6abbc552,0xa3d50701}}, // _suis_, _vand_, _knuf, ाईट_,
+ {{0x394002be,0x629d2b60,0x644a9fe3,0x660f81ec}}, // _puis_, geso, _effi, ücks,
+ {{0xa4d5835f,0x394003a7,0x3989c553,0x672d026c}}, // _поді, _quis_, nús_, dmaj,
+ {{0x628b016d,0x29114554,0x83fcac08,0xdca2bd93}}, // _ögon, goza_, sađa, _баши,
+ {{0x629d0c30,0x39899de9,0x6d4f4555,0xfd64019d}}, // beso, hús_, _atca, barị,
+ {{0x629d39f4,0x394001b0,0x6f1bbf24,0x62840b80}}, // ceso, _tuis_, nluc, _ucio,
+ {{0x7e6416da,0xe6e0000f,0x29111ea2,0xb5fb01a8}}, // [33a0] raip, _नतीज, boza_, _reác,
+ {{0x25a04556,0x6d4400b9,0x6f1bc557,0xdc3c81f4}}, // rwil_, rqia, hluc, ršći,
+ {{0x7ae2c558,0xd83885a2,0x7e644559,0x27218c83}}, // thot, _tači_, paip, zóna_,
+ {{0xbee992c6,0xc7b3025f,0xca26ae65,0x69c28cdb}}, // टफोन_, _עבר_, нфли, txoe,
+ {{0xc69300be,0x6f1b8041,0x7ae2c55a,0xb21b455b}}, // _גאר_, dluc, rhot, lfær,
+ {{0x6aa900c8,0x97a69afa,0x7ae2a5f4,0x644300e1}}, // গাযো, ерил, shot, ľnic,
+ {{0x7a368a0b,0x6443000a,0x7ae28c92,0x629d00f1}}, // _bütü, žnic, phot, yeso,
+ {{0x29112c01,0x20d08706,0x7f4181b4,0x2389128a}}, // zoza_, _càir_, _dulq, žují_,
+ {{0x94868162,0x569b0051,0x8cc4064a,0x3989bdcd}}, // нынд, ליקצ, रापो, cús_,
+ {{0xdce40024,0x629d227b,0x672d01a1,0xa5bb136f}}, // ksič, weso, zmaj, _ozón,
+ {{0xb5fdc55c,0x291100ce,0x6f02816d,0x629d455d}}, // raša, voza_, _tjoc, teso,
+ {{0x38610352,0xb5fd8024,0x62828365,0x15eb8074}}, // _sehr_, mašn, ngoo, _जेकर_,
+ {{0xb5fd8699,0xaca4819d,0x29110435,0x25a0847f}}, // lašn, _arụz, toza_, _èil_,
+ {{0x644a81c1,0x52dd824c,0xddc984e8,0xba3b0901}}, // _uffi, _मत्स, dceň, deïe,
+ {{0xd6db0328,0x629d455e,0x672d455f,0x61204560}}, // пта_, peso, tmaj, möll,
+ {{0xf5e70d8e,0x629d03ed,0x61200198,0x6d428c93}}, // нізм, qeso, löll, _kuoa,
+ {{0x672d4561,0x68f516f2,0xb5fb0091,0x386100ee}}, // [33b0] rmaj, pizd, _adár, _tehr_,
+ {{0xb5fd811f,0x6abb9de6,0x6aa9808e,0x672d4562}}, // kašn, _snuf, _saef, smaj,
+ {{0xb5fd8025,0xdcfb8289,0xd2518250,0xf09201c6}}, // jašn, stuć, لنا_, _דני_,
+ {{0x7e62825b,0xb5fd803b,0xf41380be,0xd838811f}}, // _neop, dašn, אפע_, _jaču_,
+ {{0x61204563,0x6f1b8a58,0x6d4f0061,0x5eef8035}}, // köll, vluc, _utca, इफस्_,
+ {{0x2d994564,0x26c902a5,0x7f41c565,0x66d5808b}}, // _ipse_, ljao_, _pulq, _jákv,
+ {{0x3989957a,0x7e628279,0x66e58652,0xbea58081}}, // sús_, _beop, кока, вайк,
+ {{0x26c90052,0x6569a55a,0x5334c566,0x66dc89c4}}, // njao_, tseh, _бест, _déko,
+ {{0xe8d904be,0x06098956,0x7e628162,0xd3788035}}, // _awọ_, ьник_, _deop, erć_,
+ {{0x6f1b8307,0x8cc40beb,0x656981ec,0xdbde808b}}, // sluc, रायो, rseh, tíða,
+ {{0x3b0a3408,0x2d99008e,0x65699831,0xd83881a1}}, // дено_, _lpse_, sseh, _baču_,
+ {{0x63b50699,0xdce9805c,0x7e629412,0xf4120039}}, // _mrzn, kreć, _geop, שפט_,
+ {{0xe7308277,0x4e7880f7,0x26c90b67,0x7ce48706}}, // _اصل_, _أحمد_, djao_, _bòrd,
+ {{0x3eada382,0x8c4281a1,0x6d404567,0xa3e11795}}, // ldet_, _кеше, _émai, धना_,
+ {{0xb90a05b3,0x2d994568,0x26cfc569,0x6282bdd5}}, // _मत_, _apse_, _kogo_, ygoo,
+ {{0x3ead8d1a,0xf7728158,0x26cf80a9,0x7e62acff}}, // ndet_, יקל_, _jogo_, _xeop,
+ {{0x26cf817f,0xb21b03ba,0x3eada63f,0x66d103ba}}, // [33c0] _mogo_, lgæn, idet_, _påkr,
+ {{0xe8f7012f,0xb97b04de,0x3ead8198,0x317b007c}}, // _аль_, _קניי, hdet_, _קרימ,
+ {{0x316d82be,0x64a3431c,0x18770039,0x2d8d8198}}, // _avez_, раха, _בעיר_, htee_,
+ {{0x68e1816d,0x3ead813c,0x29058267,0x26cf82d4}}, // _alld, jdet_, _ajla_, _nogo_,
+ {{0x64430110,0x62829ba9,0x3eadc155,0x66dc89c4}}, // žnia, rgoo, ddet_, _dékl,
+ {{0x224d88b3,0xc9530039,0x7ce0016d,0x3ea0456a}}, // _efek_, סמו_, _föra, leit_,
+ {{0x7e629706,0x7ce0016d,0x26cfc56b,0x7416003d}}, // _peop, _göra, _bogo_, _شورا,
+ {{0x81e800c8,0xeb0d90be,0x753c026c,0x3ea00a2a}}, // _যখন_, िकृत_, _hirz, neit_,
+ {{0x66d5803e,0xd946a657,0x80c28fb8,0x26cfa07b}}, // _náku, _рези, लादे, _dogo_,
+ {{0x3ea0456c,0x9b588003,0x70d10665,0x224000b9}}, // heit_, ниот_, _सकेल, _agik_,
+ {{0x3ea002af,0x83fcb4c1,0x753c456d,0x26cf8317}}, // keit_, rađo, _mirz, _fogo_,
+ {{0xb4df1c7b,0x26cfc56e,0x50f28054,0xbe8581a8}}, // _तत्_, _gogo_, _अगुआ_, _مجهو,
+ {{0x3944a338,0xdb218065,0x7e7d8207,0x316d00e7}}, // _jums_, ítés, óspe, éez_,
+ {{0x3944a338,0xab650f4f,0x104b0198,0x629602ec}}, // _mums_, ngüí, ьями_, _ebyo,
+ {{0x09cb00c8,0xd8388029,0xb5fb002a,0x601823e7}}, // _লেখা, _taču_, _adáp, воря_,
+ {{0x26cf84c3,0xdce08110,0x7ce0456f,0xdce980fe}}, // _xogo_, tumė, _röra, treć,
+ {{0x1df821f6,0x9f34021e,0x753c4570,0xa5f84571}}, // [33d0] веты_, реті, _birz, вету_,
+ {{0x09be80c2,0xfd62819d,0x798d0197,0x6aad0299}}, // ्हैय, _setị, _iqaw, _haaf,
+ {{0xdce9803a,0x29030024,0x3ea002af,0x66d5816b}}, // sreć, mnja_, beit_, _záku,
+ {{0x2d800c6e,0x6aad00ee,0xeb0d8072,0x7b188118}}, // buie_, _jaaf, िकेत_, díus,
+ {{0xadf0035a,0x6aad4572,0x644e026c,0x667a01c6}}, // _घेऊन_, _maaf, _ffbi, _אטרק,
+ {{0x66d80125,0x89dc0039,0x9b45045b,0x7ce005ec}}, // _ríki, וחדי, _منسو, _hörn,
+ {{0x7ce00065,0x26cfb075,0x61200106,0xd34700d7}}, // _körn, _pogo_, följ, _شیشه_,
+ {{0xbb840013,0x29034573,0x63b80366,0x6aad4574}}, // _خلفي, hnja_, ævne, _naaf,
+ {{0x3ead88f8,0x656d10c1,0x798d01c0,0x673d4575}}, // rdet_, nsah, _nqaw, _misj,
+ {{0x3ea002af,0x645e8074,0x66d8008b,0x68e1c576}}, // zeit_, _õpil, _víki, _ulld,
+ {{0xa5bb0019,0x9f558af2,0x2d8daa57,0x6aad4577}}, // _szól, _свеч, stee_, _baaf,
+ {{0xa5bb0065,0x6aad0079,0x799b819d,0x645aa676}}, // _gyóg, _caaf, _kpuw, abti,
+ {{0x69d9b9a6,0x765b80b4,0x6dc492c8,0x661d006f}}, // nywe, mbuy, _نزول, _nysk,
+ {{0x3ea002af,0xd9430012,0x290300c3,0x657b8c53}}, // weit_, сери, gnja_, mruh,
+ {{0x3ea024de,0x66e60785,0x1cb8af0a,0xdee62bf3}}, // teit_, гова, _جانب_, гови,
+ {{0xeabe80ab,0x7e55116b,0x2d804578,0xceb40085}}, // _আত্ম, итуц, tuie_, lməz_,
+ {{0x2bcb85e8,0x29034579,0x7ae4457a,0x3ea0457b}}, // [33e0] ाहका, bnja_, _ilit, reit_,
+ {{0x661d009a,0xe617229c,0x2903017f,0x3f9a008e}}, // _dysk, уду_, cnja_, _tppu_,
+ {{0x7ce0016d,0x2b4f8087,0x656d009c,0x753c00eb}}, // _förn, _încă_, asah, _virz,
+ {{0x62862e8c,0x55e6197b,0x6aad0079,0x656d04e8}}, // ngko, _бомб, _xaaf, bsah,
+ {{0xf5488104,0xceb303c8,0x4427826c,0x7ae44546}}, // _mục_, _פיש_, _lzn_, _mlit,
+ {{0x6f040e3f,0xf548801c,0x7b188118,0x386a00c3}}, // lnic, _lục_, tíus, nabr_,
+ {{0xfbc78624,0x6f0415db,0x248a01e0,0x7ae4457c}}, // _ات_, onic, _icbm_, _olit,
+ {{0x6f04457d,0x6d46457e,0x290302a5,0xa3c98327}}, // nnic, _kuka, znja_, ोहन_,
+ {{0xa206abd9,0x1a069501,0x44278201,0x6f042c99}}, // _спад, _спам, _azn_, inic,
+ {{0x6d46457f,0x7ae43083,0x6f044580,0x764d4581}}, // _muka, _alit, hnic, zcay,
+ {{0xd4979006,0x2903173d,0x7ae44582,0x6d460e5a}}, // уры_, vnja_, _blit, _luka,
+ {{0x291a0870,0xf548801c,0xb5fb0019,0x51870652}}, // _akpa_, _cục_, _beál, луга,
+ {{0x6f041620,0xf5488028,0x90d580ff,0x7ce48362}}, // dnic, _dục_, _mìn, _còrc,
+ {{0x661d4583,0x6f043648,0x6aad4584,0x28c79a3b}}, // _rysk, enic, _waaf, लायि,
+ {{0x7ae44585,0xc4f7807c,0x661d0711,0x6f0401e0}}, // _flit, _תמוז_, _sysk, fnic,
+ {{0x6d464586,0x656d1400,0x29030da8,0xdce40b80}}, // _buka, tsah, snja_, driđ,
+ {{0x764d059c,0x29034587,0x6d460ad4,0x644f809a}}, // [33f0] rcay, pnja_, _cuka, ście,
+ {{0x6d464588,0x764d208b,0x656d0057,0x661d4589}}, // _duka, scay, rsah, _vysk,
+ {{0x6d46458a,0xfff900d5,0x569501e2,0x6e95179e}}, // _euka, _شکست_, _кант, _кину,
+ {{0xdced0024,0x661d0f06,0x7ce00106,0x98b30087}}, // krać, _tysk, _föro, _fixă_,
+ {{0xe1358084,0x213e803c,0xaca3019d,0x09d880ab}}, // анды, _bith_, _agục, _দেবা,
+ {{0x6ae210c5,0x2618959a,0x02e20128,0xd3669a5c}}, // _पत्र, _पड़ी_, _पत्न, _وه_,
+ {{0x3f830397,0x6d460041,0x3f91011b,0x6b818118}}, // nuju_, _zuka, ntzu_, xulg,
+ {{0x6b81841c,0x6d460214,0x2d9580a1,0xfe458073}}, // vulg, _yuka, арос, шнио,
+ {{0x386a1072,0x608780f7,0xdced0088,0x51878a3d}}, // yabr_, _مشاك, grać, _љуба,
+ {{0x3f8302a5,0x81cb00ab,0xb5fb03b0,0xb4658a13}}, // kuju_, লনা_, _reál, школ,
+ {{0x3f83003a,0x78a38a10,0x9b060b5f,0x02c60225}}, // juju_, menv, изод, айно,
+ {{0xdced28e1,0x3f830052,0x4e198009,0x63b880ce}}, // brać, duju_, _июля_, _drvn,
+ {{0xd946160f,0x6286458b,0x41a702a9,0x78c48061}}, // реги, rgko, авян_, _átvé,
+ {{0x6d462ce3,0x6f040063,0xcc898077,0x412a11d2}}, // _ruka, wnic, _شنبه_, того_,
+ {{0x46f62bca,0xf5488028,0x3f8301dd,0x52760c24}}, // ачат, _tục_, guju_, _купу,
+ {{0xb5fd9809,0xfd4c801c,0x29d800e1,0xdb0ec58c}}, // jašk, _nhiễ, eľať_, _orbá,
+
+ {{0xe7371b47,0x7ce000f2,0xb4662e7b,0xee3a013a}}, // [3400] рет_, _förl, икал, тна_,
+ {{0x6f0410d1,0x6d460503,0x3eafc58d,0x25bf19ce}}, // snic, _vuka, _lagt_, _šulc_,
+ {{0x3f830052,0x307b0039,0xf1b9826c,0x213e80f7}}, // cuju_, _באינ, _arš_, _rith_,
+ {{0x2918458e,0xeb968158,0x213e8a2a,0x68fc080a}}, // lora_, ידער_, _sith_, lird,
+ {{0x332d82a3,0x80c29d17,0x26cd80d2,0x7ce0458f}}, // _dhex_, लाले, djeo_, _hörm,
+ {{0xdced0024,0x78a3c590,0x6aa28174,0x307a03de}}, // vrać, genv, seof, דאַנ,
+ {{0xe3a783f8,0xa3ba0006,0x96f88056,0x7ee6b14a}}, // _بر_, _अछि_, лект_, ицие,
+ {{0x213e873a,0x29184591,0x68fc0285,0xd6d78198}}, // _with_, hora_, hird, иты_,
+ {{0x3f830025,0x29182bd4,0x68fc007e,0xaa7b04e8}}, // zuju_, kora_, kird, hkýc,
+ {{0x63b8800d,0x29183724,0x14c9036d,0x7bda9c28}}, // _prvn, jora_, रायण, tytu,
+ {{0x26c20364,0x68fc07c0,0x66e683bd,0xa113853d}}, // _onko_, dird, _кожа, _بولت,
+ {{0x83fc8024,0xdced02a5,0x3f830b67,0x32674592}}, // nađi, prać, vuju_, штов,
+ {{0xbddb1b09,0xf8da86a7,0xe4e680e8,0x7d1c04e8}}, // _agèn, _बकाय, _війн, _okrs,
+ {{0x3f831cbc,0x26c206c0,0x68fc4593,0x3a20076d}}, // tuju_, _anko_, gird, _dyip_,
+ {{0xbb849e95,0x438480f7,0x2ee9031d,0x533514f6}}, // _العي, _العق, thaf_, _лект,
+ {{0x2fcd003a,0x643b8833,0x2d920901,0x3f911fa4}}, // ćeg_, _בעונ, ftye_, rtzu_,
+ {{0x7afd4594,0x29184595,0x3f830052,0x2d84c596}}, // [3410] mist, bora_, suju_, nume_,
+ {{0x29184597,0x7ce000f2,0x3f830024,0xfd100290}}, // cora_, _förm, puju_, وجل_,
+ {{0xaa7b0a56,0x69dd2280,0x7ce002bb,0x3949022c}}, // ckýc, lyse, _görm, _huas_,
+ {{0x7b1d05e4,0x39494598,0x2d849611,0x83fc8042}}, // céut, _kuas_, kume_, gađi,
+ {{0x2d84c599,0x78a38d35,0xf1a7abca,0x26cd826c}}, // jume_, tenv, ррен, vjeo_,
+ {{0x3eaf8189,0x39490069,0x31580039,0xac191a0b}}, // _sagt_, _muas_, סיון_, _богу_,
+ {{0x7afd2a37,0x3949459a,0x3a8787ac,0x48788d13}}, // kist, _luas_, _вызв, рсія_,
+ {{0x7afd459b,0x78a3907f,0xb5fd8067,0xf1b9826c}}, // jist, senv, laši, _trš_,
+ {{0x7afd2cb8,0x39490014,0x25a90948,0x2d84c59c}}, // dist, _nuas_, nwal_, gume_,
+ {{0x50db8128,0x7e6d2f77,0xb5fd833b,0x7afd0a52}}, // _भविष, haap, naši, eist,
+ {{0x291802a5,0x7ce004b8,0x7e6d459d,0xa3d70076}}, // vora_, _börj, kaap, िहि_,
+ {{0x394909ca,0xc049007c,0x2ee68122,0x7b1d0118}}, // _buas_, _טז_, _glof_, déus,
+ {{0x394923f0,0x2d84802a,0x68fc03bf,0xb5fd8390}}, // _cuas_, cume_, tird, kaši,
+ {{0x6443003a,0x6d5b8812,0x3949459e,0x25a91dde}}, // žnij, mpua, _duas_, dwal_,
+ {{0x7afd459f,0x291845a0,0x2ca5a862,0x5eaa80c8}}, // bist, rora_, meld_, কারে,
+ {{0xe9ff801c,0x70160f97,0xf54880ff,0x6306003d}}, // _ngắn_, _दुःख_, _mụn_, _دوبل,
+ {{0xf50a11b3,0xaa7b03fb,0xb5fd812b,0x70b7801b}}, // [3420] гнал_, skýc, faši, _आफूल,
+ {{0x77ca14d6,0xad9b0118,0xb5fd817f,0xee374592}}, // _блог_, _axús, gaši, сну_,
+ {{0x493b0051,0x7b1d0511,0x3ea601a4,0xaf0a006b}}, // _תגיו, péut, _линг, _مقدم_,
+ {{0x7ce02203,0x6d5b8359,0x2907c5a1,0x2ca5c5a2}}, // _körk, kpua, enna_, held_,
+ {{0x2ca5c5a3,0x83fc805c,0x67242dbe,0xb5fd9351}}, // keld_, rađi, mlij, baši,
+ {{0x7afd45a4,0x7ce00ffd,0xceb40085,0x836a003d}}, // zist, _mörk, ldə_, _مصرف_,
+ {{0x2249807d,0x442a010c,0x8f34c5a5,0x79860caa}}, // žaka_, _uzb_, оекц, hukw,
+ {{0xceb402bf,0x7afd45a6,0x29078307,0x2d84c5a7}}, // ndə_, xist, anna_, tume_,
+ {{0x672445a8,0xceb30039,0x2ca581ec,0x6d5b809c}}, // ilij, ליה_, feld_, gpua,
+ {{0x394945a9,0x7afd45aa,0x2d849c2d,0x2ca58574}}, // _ruas_, wist, rume_, geld_,
+ {{0xa3e805b3,0x394945ab,0x2a690006,0xceb40201}}, // यनि_, _suas_, _peab_, kdə_,
+ {{0x394901e9,0x67240253,0x798606bb,0x2d801142}}, // _puas_, jlij, fukw, krie_,
+ {{0x3949022c,0x672400d2,0x656f80f7,0x7e6d43e1}}, // _quas_, dlij, ácht, vaap,
+ {{0x7afd45ac,0x394901c0,0x1c0802f1,0x69dd0901}}, // sist, _vuas_, वपाल_, ryse,
+ {{0x7afd410b,0x64430805,0x7d1ac5ad,0x6e22808e}}, // pist, žnik, mots, _hyob,
+ {{0x394945ae,0x7ce000f2,0x3f7302a5,0xaae708ca}}, // _tuas_, _förk, rću_, _دستو,
+ {{0xb5fd8289,0x2907c5af,0x7ce009ce,0xf8ae053d}}, // [3430] taši, ynna_, _görk, رکی_,
+ {{0xfaff037a,0x5c750071,0x9c470fbf,0xf99001a8}}, // lië_, ілет, схал, _أبي_,
+ {{0x672445b0,0x2d8045b1,0x28c781a2,0x7e6d2460}}, // blij, arie_, लासि, paap,
+ {{0x2d80002e,0x291eafbb,0x63ae00dd,0xfaff06a8}}, // brie_, _akta_, _dsbn, nië_,
+ {{0xdd86a6f1,0x6e22c5b2,0x9fa2807b,0x7d1ac5b3}}, // _تو_, _nyob, _síða_, kots,
+ {{0xb4d78076,0x7bc5807b,0xa3b6000f,0x6eea01ac}}, // _सवे_, _áhug, जिश_, _výbe,
+ {{0xdb0b0207,0x2ca5beea,0xe3b31ef5,0x60dc007c}}, // _esfé, veld_, _عرس_, רקונ,
+ {{0xba7784c0,0x2ca5baa7,0xe5798d69,0xa3b60768}}, // _داست, weld_, ази_, जिर_,
+ {{0x7ae987d9,0x7afbc5b4,0x2ca5c5b5,0xfaff074c}}, // _ilet, _imut, teld_, dië_,
+ {{0x6d5ba08b,0xceb40085,0x67240140,0xdced09d1}}, // rpua, zdə_, zlij, ksač,
+ {{0x2ca590f4,0x7ae9c5b6,0xeb9995fc,0x130a0009}}, // reld_, _klet, шин_, иной_,
+ {{0x628b9d29,0xdb1c82be,0x3f879db9,0x7afb8372}}, // nggo, _arrê, munu_, _jmut,
+ {{0x7ce000f2,0x7d1ac53e,0x3f87880a,0xec6e8ea2}}, // _förh, bots, lunu_, _оп_,
+ {{0x6d59c5b7,0x7ae9c5b8,0x64470168,0x7ae2c5b9}}, // _itwa, _llet, _ngji, zkot,
+ {{0xb9020076,0x48e1acdd,0x7ae9837b,0x672445ba}}, // _दव_, _कवनो_, _olet, tlij,
+ {{0x6d43bcef,0xbebb020f,0x6f0985ef,0x6d4ba578}}, // _hina, _shër, nnec, _kuga,
+ {{0x6d43c5bb,0x67240613,0x6d4bc5bc,0xceb40086}}, // [3440] _kina, rlij, _juga, rdə_,
+ {{0x67240582,0x6d4bb06f,0x2d801337,0x6d43c5bd}}, // slij, _muga, rrie_, _jina,
+ {{0x6d4bc5be,0x6d439d29,0x672400d2,0xa09b00be}}, // _luga, _mina, plij, _דייט,
+ {{0xe29aa569,0xdcfba3e3,0xd2509b9a,0x6d43c5bf}}, // рад_, druč, رنت_, _lina,
+ {{0x6d43811e,0xdb218019,0x6f1b801b,0x6d4bc5c0}}, // _oina, ítás, douc, _nuga,
+ {{0x7ae9c5c1,0x6d43c5c2,0xfbc99a1c,0x3a2d8da8}}, // _elet, _nina, िमाम, _dzep_,
+ {{0x6d598542,0x6d4bc5c3,0x7ae992ed,0xd90d87d2}}, // _atwa, _auga, _flet, دیل_,
+ {{0x6d4bad58,0x7c2e45c4,0x6d43c5c5,0x644700f1}}, // _buga, _izbr, _aina, _zgji,
+ {{0x6d4bc5c6,0x29008187,0x6d4380f8,0xe4a6948d}}, // _cuga, éias_, _bina, орно,
+ {{0x6d4b84a7,0x6d43c5c7,0x332681b9,0xb5fd81a1}}, // _duga, _cina, llox_, jašu,
+ {{0x6d598352,0x2e4e81bc,0x6d4bc5c8,0x7d1ac5c9}}, // _etwa, _dọrọ_, _euga, rots,
+ {{0x2eed9a16,0x6d438357,0x7c2e008e,0x6f1b8036}}, // chef_, _eina, _mzbr, couc,
+ {{0xc6499ddd,0xfaff0cfa,0x6d4b8978,0x2e4e81bc}}, // _اجمل_, rië_, _guga, _fọrọ_,
+ {{0x6d438bf2,0xfaff45ca,0x3a2482c4,0x7c2e04e8}}, // _gina, sië_, _mymp_, _ozbr,
+ {{0x291c8087,0x466b0323,0x6d4bc5cb,0x7c238428}}, // iova_, ирам_, _zuga, _gynr,
+ {{0x291cc5cc,0x6d43aef4,0xa3c99370,0x6d4ba6e8}}, // hova_, _zina, ोहर_, _yuga,
+ {{0x61200065,0x83fc803a,0xdd940110,0x344b0878}}, // [3450] tölt, rađu, маты, ачен_,
+ {{0xb5fd8052,0x7ae98bc5,0x61320366,0x6f008041}}, // mašt, _slet, gæld, himc,
+ {{0xf092004c,0xb5fd8025,0x141900f7,0x2249008e}}, // _אני_, lašt, حياة_, _kgak_,
+ {{0x9948015b,0x224900ee,0x26c6819d,0x79828035}}, // _دلیل_, _jgak_, _nnoo_, frow,
+ {{0x386c8267,0x6f00817f,0xdce98088,0x7982a17a}}, // _bedr_, dimc, zređ, grow,
+ {{0x6d4bc5cd,0x3ea945ce,0x463a03c8,0xe4a401a1}}, // _ruga, leat_, _לערע, _орсо,
+ {{0x6d43c5cf,0xf77180f7,0x98a280e8,0x2721816b}}, // _rina, ءات_, хище, zónu_,
+ {{0x6d43c5d0,0xdce98eef,0x7afb8578,0x2249015d}}, // _sina, vređ, _umut, _ngak_,
+ {{0x6f09c5d1,0x6d43b877,0x291c8067,0xddcb80eb}}, // rnec, _pina, bova_, _iegū,
+ {{0xa3df0b3b,0x22490867,0x6f1bc5d2,0x291cc5d3}}, // _तथा_, _agak_, souc, cova_,
+ {{0x6d43c5d4,0x6f00803a,0x644f809a,0x2a6d8069}}, // _vina, bimc, ścio, _keeb_,
+ {{0x26d9062f,0x6d4bc5d5,0x6d43c5d6,0x2121008e}}, // _noso_, _tuga, _wina, _akhh_,
+ {{0x6d4392e8,0x2a6d81c0,0x6d59c5d7,0xdc3c80eb}}, // _tina, _meeb_, _utwa, kšķi,
+ {{0xdce990d3,0xd47980be,0x2a6d8cd8,0xe8e100ff}}, // pređ, _האַל, _leeb_, _đợt_,
+ {{0xa3cc890a,0xb5fdb1a7,0x26d945d8,0xe0161513}}, // रमण_, rašu, _boso_, _दुखद_,
+ {{0x9c7c83fb,0x3f51801c,0x2a6d8069,0xc7b280be}}, // nkčn, _máu_, _neeb_, עבן_,
+ {{0x0d860071,0x6da3079e,0x25ada42f,0x272191a9}}, // [3460] ялан, нита, nwel_, lóns_,
+ {{0xe52311c7,0x6ab60915,0x5fbd8651,0x39458687}}, // едуп, _hayf, ्माल, _nils_,
+ {{0x6132013c,0xeb9a1ed1,0x68e1345e,0x6ecc001b}}, // ræld, сие_, ölde, हादु,
+ {{0x2a6d81e9,0x3ea90609,0x3f8a0bb1,0x79828035}}, // _ceeb_, ceat_, mubu_, trow,
+ {{0x6ab61c33,0x7d01c5d9,0x25ad80f3,0x3f9845da}}, // _mayf, kils, jwel_, ltru_,
+ {{0x394d87e2,0xd90d89a7,0xdb07807b,0xed5713d1}}, // _dues_, لیق_, _brjó, _хор_,
+ {{0x3f980193,0x2a6d8069,0x7c2e2e63,0xb5fd811f}}, // ntru_, _feeb_, _uzbr, zašt,
+ {{0x291cc5db,0xc7b30039,0x7d1e016d,0x7982809a}}, // sova_, תבה_, lops, prow,
+ {{0x291c8503,0x3945c5dc,0x6f009040,0x2167c5dd}}, // pova_, _fils_, rimc, _нити_,
+ {{0x3f8a45de,0xb5fd80fe,0x61e28176,0x27218118}}, // kubu_, vašt, myol, fóns_,
+ {{0x7ce004b8,0x61e283f7,0x2a6d81c5,0xf8b3807c}}, // _förv, lyol, _yeeb_, _רשע_,
+ {{0x2a6d81e9,0xdb0e8980,0xb5fd8669,0x6b888366}}, // _xeeb_, _arbú, tašt, rudg,
+ {{0x61e2c5df,0x7d01c5e0,0xa3b306af,0x26d91ab3}}, // nyol, bils, _जैन_, _roso_,
+ {{0x26d945e1,0x7d578039,0xa63b8039,0xb5fd943c}}, // _soso_, _ציוד_, _הגדר, rašt,
+ {{0xb8f5000f,0x7d1e0573,0x26d90573,0x2721802a}}, // _सच_, dops, _poso_, cóns_,
+ {{0xdb1583a8,0xb5fd9351,0xf7700061,0xd6d804dd}}, // _orzá, pašt, طاف_, пту_,
+ {{0xceb40201,0xf6500064,0x7aed45e2,0x26d901df}}, // [3470] vlət_, ائف_, _ilat, _voso_,
+ {{0x6eea026f,0x3ea945e3,0x2a6d822c,0x394d8036}}, // _výba, seat_, _seeb_, _rues_,
+ {{0x2a6d81c0,0x3ea945e4,0xa7740162,0x0edc80c2}}, // _peeb_, peat_, _плэч, _बवंड,
+ {{0x394d85b4,0x60c88006,0x628f005d,0x2a6d8cd8}}, // _pues_, _andm, ngco, _qeeb_,
+ {{0x272187f4,0x6f0d394e,0x3f51c5e5,0x394dc5e6}}, // zóns_, mnac, _sáu_, _ques_,
+ {{0xa3cc83bb,0xa3be800d,0x394d82be,0x47598b33}}, // रमा_, ीमा_, _vues_, ория_,
+ {{0x2a6dc5e7,0xceb40085,0x7d01c5e8,0x25ad8197}}, // _teeb_, mlər_, vils, wwel_,
+ {{0x6d471067,0x6458813c,0xceb40085,0x443100b9}}, // _hija, _afvi, llər_, _izz_,
+ {{0x6d4f002e,0x6d4745e9,0xa2d109c2,0x7d019277}}, // _juca, _kija, धान्, tils,
+ {{0x7aed45ea,0x6ab60179,0x7ce023fe,0x6d4f0079}}, // _alat, _sayf, _föru, _muca,
+ {{0x6d4f45eb,0x7ce00019,0x2d848dd7,0xa3b6009a}}, // _luca, _törv, arme_, जिए_,
+ {{0x7aed45ec,0x6d5d4405,0x272181df,0x7d01c5ed}}, // _clat, _otsa, róns_, sils,
+ {{0x2360805c,0xe8160076,0x80b4809a,0xceb40201}}, // _čije_, _दुजा_, _इसमे, klər_,
+ {{0x7aed12d9,0x6d4745ee,0xdcfb80eb,0x6729c5ef}}, // _elat, _nija, bruā, llej,
+ {{0xd9431172,0x6d5d1d4c,0x6143280f,0x7aed45f0}}, // тери, _atsa, тера, _flat,
+ {{0x6d4f45f1,0x6f0d45f2,0xdced00c3,0xbfc3047f}}, // _buca, gnac, drađ, ъбск,
+ {{0x290342e3,0xcb130051,0x44278355,0x3f9845f3}}, // [3480] bija_, _שלח_, _hyn_, stru_,
+ {{0x3e6c0104,0x7aed45f4,0x6d4f45f5,0x6f0d330f}}, // ết_, _zlat, _duca, anac,
+ {{0xdced003a,0x6d4745f6,0x7d1e2771,0x6d4f03cd}}, // građ, _dija, rops, _euca,
+ {{0x44279d33,0x673b809a,0x60dac5f7,0x7d1e45f8}}, // _myn_, jmuj, _sotm, sops,
+ {{0x6fc08076,0x6d4745f9,0xceb40201,0x4427b07d}}, // विभू, _fija, blər_, _lyn_,
+ {{0x6f0403e4,0xdced0503,0x764b8c2e,0xd25982d6}}, // liic, brađ, _aggy, _efņ_,
+ {{0x672985a4,0xb8d69278,0x62880110,0x4427c5fa}}, // flej, _जस_, ėdoj, _nyn_,
+ {{0xd6d08013,0x6d4f0388,0x6729807a,0x3f808110}}, // اقة_, _yuca, glej, čiu_,
+ {{0xa3b61a46,0x7ce000f2,0x9f4545fb,0x6459009a}}, // जिक_, _fört, álé_, świe,
+ {{0x6f0d009a,0x764b8365,0x290304b7,0x2cb801a1}}, // znac, _eggy, xija_, _jard_,
+ {{0x4427831d,0x6ef1013c,0x7ce012d2,0x2cb8022b}}, // _cyn_, _håbe, _hörs, _mard_,
+ {{0x44278114,0x2d4d017f,0x29033907,0x8c3c8214}}, // _dyn_, _užeg_, wija_, neği,
+ {{0xf2d3893f,0x7aed012b,0xdced011f,0xceb4011c}}, // דער_, _vlat, zrađ, ylər_,
+ {{0xa6db008b,0x442785b1,0x6d4f2637,0x6ef10646}}, // _viðm, _fyn_, _ruca, _måbe,
+ {{0x6d471f3a,0xd6db1928,0x7aed003d,0x4427c5fc}}, // _rija, ота_, _tlat, _gyn_,
+ {{0x6d4f003b,0x6d4745fd,0x2cb80cfa,0x2cacc5fe}}, // _puca, _sija, _aard_, ledd_,
+ {{0x2cac831d,0xceb40085,0x9df9098d,0x50670081}}, // [3490] oedd_, tlər_, чнат_, _отна,
+ {{0x6f0d45ff,0x973c8d26,0x2cb8062d,0x2cac8114}}, // snac, _općo, _card_, nedd_,
+ {{0xceb40086,0x6d474600,0x6720911b,0x673b8035}}, // rlər_, _vija, komj, ymuj,
+ {{0x7ce04323,0x521580e5,0x2cac831d,0x6d4737c0}}, // _börs, ндет, hedd_, _wija,
+ {{0x865a0051,0xa9698258,0x2169a2ea,0xb65a007c}}, // _מדרי, чила_, чили_, _מדרש,
+ {{0x798bc601,0xa3be82f1,0x2cb84602,0x3eb932c7}}, // rugw, ँटा_, _gard_, _hast_,
+ {{0xddc600eb,0x3eb94603,0x67298074,0xf1b9816b}}, // _iekš, _kast_, tlej, _myši_,
+ {{0x7ce04604,0x8c3c87d9,0xb4db0722,0xe9ff8129}}, // _förs, ceği, _guàr, _ngần_,
+ {{0x7ce04605,0x99678221,0x7cf2013c,0xe8d9082e}}, // _tört, _італ, _færd, _atọ_,
+ {{0x67bb8060,0x644e8289,0xdb0b4606,0x0d828e11}}, // _واضح_, žbin, _erfü, ыльн,
+ {{0xab2a1c79,0xc2090039,0x7ced80f7,0x6729c607}}, // зона_, _פה_, _cúra, plej,
+ {{0x7af98059,0xbebb0168,0xf41203de,0x2a7f8197}}, // _çatı, _skën, רפט_, _jdub_,
+ {{0x3eadc608,0xaca38135,0xc33284de,0x4427c609}}, // meet_, _arụk, טוט_, _wyn_,
+ {{0xdce98279,0x3eadaada,0x7cf2006a,0x80bf8264}}, // dseć, leet_, _kære, ্সক্,
+ {{0x07a300c4,0x6ee302f1,0x8fa317c8,0x3eb9460a}}, // ларн, _lõbu, ларе, _bast_,
+ {{0x3ead8364,0x6eea003e,0x224d82d5,0x26dda0d2}}, // neet_, _výbo, _ngek_, _jowo_,
+ {{0x7cf20bc5,0x395f8282,0x629600b4,0x6f04460b}}, // [34a0] _lære, _ntus_, _icyo, riic,
+ {{0x050000ab,0xdd8f12dc,0x6f040362,0x26ddc60c}}, // ্ত্র_, صوم_, siic, _lowo_,
+ {{0x3eb9460d,0x66e61f50,0x3ead956e,0xaca401bc}}, // _fast_, _зона, keet_, _nzụr,
+ {{0x69c4460e,0x3eb9460f,0x60de4610,0x3ead8198}}, // _krie, _gast_, _hopm, jeet_,
+ {{0x2cb84611,0x6726021e,0x7ce0016d,0xe8948c5c}}, // _tard_, _ikkj, _dörr, валь,
+ {{0x65644612,0x7cf20aa2,0x6ef1006a,0x290e8333}}, // mpih, _bære, _våbe, unfa_,
+ {{0x7ce000f2,0x25a68696,0x09d880ab,0x7cf2013c}}, // _förr, _spol_, _দেখা, _værd,
+ {{0x8f9b0451,0x61e601e2,0x539b0039,0x26dd8b99}}, // ליצי, kykl, _מילו, _cowo_,
+ {{0x55068a41,0x10a6a33f,0x3f8780d2,0xdce28bcf}}, // ечна, _пиан, arnu_, _svođ,
+ {{0x5597093f,0xfd4d019d,0xdb1581ec,0x7e7688ae}}, // עדיע_, _aghị, _erzä, _šapć,
+ {{0xccf30051,0x2cac8514,0x644e01bc,0xe1ff808b}}, // רכת_, redd_, _mgbi, _þór_,
+ {{0x2cac8514,0xa6db007b,0x39524613,0xf1bf03f2}}, // sedd_, _viðk, _buys_, tvá_,
+ {{0xfce697f9,0x2b493404,0x3eb92ed5,0x645c0aa8}}, // _подо, _viac_, _rast_, _ofri,
+ {{0x313515b7,0x7521823e,0x3eb90a53,0x2c86826b}}, // тегр, colz, _sast_, _dídà_,
+ {{0x6280803a,0xdce28024,0x78bab860,0x3ea02cf0}}, // _odmo, _uvođ, _matv, ffit_,
+ {{0x645c4614,0x69c40051,0x644e4615,0x6d4aa813}}, // _afri, _frie, _agbi, _hifa,
+ {{0x6d4ac616,0x65640bb1,0x7d250019,0x644e0609}}, // [34b0] _kifa, gpih, نفرن, _bgbi,
+ {{0x645abb09,0x29014617,0x3f8ec618,0x6d4ac619}}, // ncti, _umha_, kufu_, _jifa,
+ {{0xa6db007b,0x6d4a804f,0x3eb9319a,0x7cf203ba}}, // _liði, _mifa, _tast_, _bærb,
+ {{0x6abd2a4f,0xf6790158,0x7c2a8aff,0x877a007c}}, // ndsf, _קאָמ, _myfr, _קארי,
+ {{0x645c031d,0x442b80ff,0x6abd2023,0x7c2a8428}}, // _ffri, ́c_, idsf, _lyfr,
+ {{0x672d461a,0x3ead867f,0x6d4ac61b,0x91e28dac}}, // llaj, weet_, _nifa, роше,
+ {{0x3ead88e5,0x8aa781e2,0x6fb280f7,0x61e60402}}, // teet_, _прад, لموا, zykl,
+ {{0x78a4803a,0x7cf20257,0x645a802e,0x672d423b}}, // điva, _være, ecti, nlaj,
+ {{0x23608067,0x6d4ac61c,0x7ce0461d,0x78ba8088}}, // _čija_, _bifa, _körp, _fatv,
+ {{0x2d838805,0x82348013,0x78bac61e,0x7bc3826c}}, // čje_, _إرفا, _gatv, _prnu,
+ {{0x7c2a8355,0xc6e7835f,0x6abb8352,0x6d4ac61f}}, // _cyfr, _підп, _kauf, _difa,
+ {{0x69c44620,0x2907c621,0x29110098,0xe1ff4622}}, // _prie, lina_, enza_, rbón_,
+ {{0x6d4aab40,0x399b83a7,0x2d8d876b,0x95e98019}}, // _fifa, nês_, quee_, _تباہ_,
+ {{0x69c424de,0x6abb90f6,0x2d89067f,0x6d4a8a03}}, // _vrie, _lauf, drae_, _gifa,
+ {{0x7c2a831d,0x61e60b73,0xf0928039,0x81d780ab}}, // _gyfr, sykl, ינם_, ানি_,
+ {{0x2907c623,0x29114624,0xe1f21381,0x672d38de}}, // hina_, anza_, _نسخ_, glaj,
+ {{0x2907c625,0x60de4626,0x657bc627,0x7afd03ed}}, // [34c0] kina_, _topm, nsuh, ërte,
+ {{0x81d780c8,0x7ce4809f,0x25b2079f,0x656409ca}}, // ানা_, _fòru, rwyl_, rpih,
+ {{0x2907803a,0x672d4628,0x644f809a,0x1fbd8035}}, // dina_, blaj, ściw, ्मोड,
+ {{0x66e61511,0x672d06a5,0xe1ff0019,0x2d891b56}}, // _рома, claj, kból_, brae_,
+ {{0x78baac62,0x6459009a,0x2907c629,0x7ce0016d}}, // _patv, świa, fina_, _förp,
+ {{0x28d5462a,0x97a69541,0x645c462b,0xa3b3462c}}, // दायि, врил, _ufri, _जैव_,
+ {{0xdfd080f7,0x6abb9c7d,0x8b26a2f6,0x28c781a2}}, // صية_, _fauf, _адве, लाजि,
+ {{0xa3d584e5,0x1ae3b33d,0x3f8ec62d,0x399b8187}}, // समन_, _косм, rufu_, bês_,
+ {{0x2907be55,0x399b8073,0x81e700ab,0x3f8e8b3f}}, // bina_, cês_, _যেন_, sufu_,
+ {{0x799d01c0,0xf212819d,0x6abb866f,0xfd4e0135}}, // vtsw, bọrọ, _zauf, wekọ,
+ {{0x6d4a8010,0xd251003d,0x69c2874c,0x7ae081f6}}, // _vifa, ینگ_, lvoe, _komt,
+ {{0xb69b0087,0x7ce48580,0x6d4a818f,0x69dd87b6}}, // _amân, _aòrt, _wifa, _àsec,
+ {{0x69c2910f,0x6abd04e1,0x394cc62e,0x7ae3841c}}, // nvoe, rdsf, _kids_, ênti,
+ {{0x55778158,0x7ae0a499,0x2d89067f,0xf548827d}}, // _יעדן_, _lomt, vrae_, _cụt_,
+ {{0x799d01c0,0xa6db007b,0xa3cc86a7,0x7ce48a2a}}, // stsw, _miðv, _रपट_, _dòrt,
+ {{0x6609007b,0x394ca06a,0x2d89462f,0xd5b69a3b}}, // _þekk, _lids_, trae_, _अनाज,
+ {{0x672d4630,0x06c500ab,0x2907a28d,0xd49b1775}}, // [34d0] rlaj, _একদি, yina_, дре_,
+ {{0x290787f4,0x2d89002a,0x7d08826c,0x2c7c01d0}}, // xina_, rrae_, nids, vádí_,
+ {{0xa3d584e5,0xd251990c,0x6ef5a27d,0xa3b302f1}}, // समय_, منا_, _hábe, _जैश_,
+ {{0x7ae087fa,0x2907c631,0x7d088392,0xaad606a7}}, // _comt, wina_, hids, धायक,
+ {{0x4aa51d01,0xdcef0201,0xf99f0247,0xdb0e8118}}, // ग्यव, şdır, ntè_, _arbó,
+ {{0x3ea6c632,0x6e2b8428,0x6934b73a,0xdb1c8580}}, // _abot_, _rygb, анчу, _errà,
+ {{0x6abb8433,0x3f588187,0x65628915,0x3f91007e}}, // _tauf, _céu_, _atoh, nuzu_,
+ {{0x2907c633,0xf8380051,0xf99f0247,0x3f588722}}, // sina_, כנות_, ktè_, _déu_,
+ {{0x7bc181c0,0x61320366,0xfec50264,0x2b4daaa0}}, // wvlu, fæll, _একাধ, _iiec_,
+ {{0xee368110,0x394cc634,0x2907b14f,0x7cf2008b}}, // ыны_, _gids_, qina_, _kæra,
+ {{0xe29a2e65,0xd1208074,0xfc0580e2,0xe1ff0061}}, // нан_, मकरण_, упно, sból_,
+ {{0x68e184c7,0x2249856f,0xdb1583a8,0x65628168}}, // _hold, žaku_, _arzú, _ftoh,
+ {{0x68e1c635,0x6f09809a,0x26cfc636,0xdbe700ab}}, // _kold, miec, _ingo_, _গেমস_,
+ {{0x6f0980eb,0x68e1826c,0xf1b98267,0x293580be}}, // liec, _jold, _msš_, טאָן_,
+ {{0x68e1c637,0x4424819f,0xa6db01fa,0x3f910102}}, // _mold, _پروف, _liðu, guzu_,
+ {{0xb8eb000f,0x6f099a1d,0x6ee30006,0x2b9c001b}}, // _रो_, niec, _sõbr, víc_,
+ {{0xa6db0125,0x7bc7003d,0xa2d14638,0x80b38424}}, // [34e0] _niðu, _arju, धार्, ंजाइ,
+ {{0x69ba8035,0x7ae0c639,0x6909463a,0x59c7914f}}, // _एनबी, _somt, lžen, लिनर,
+ {{0x389b093f,0xb09b012a,0xa2d8024c,0xdb1c8825}}, // _איינ, _אייר, याप्, _arrá,
+ {{0x290a463b,0x69c285f8,0x7cf68087,0x3cf00072}}, // liba_, tvoe, _târg, _इतके_,
+ {{0x28130077,0xc05a835f,0x2b9c03fb,0x68e1c63c}}, // _نویس, ній_, síc_, _bold,
+ {{0x69c2838e,0x26cfc63d,0x29058362,0x3eb202c4}}, // rvoe, _ango_, _cmla_, leyt_,
+ {{0x7ae09517,0x6d5dc63e,0x7d08a9c6,0x6d418916}}, // _tomt, ísan, vids, dmla,
+ {{0x6d4e463f,0x2d829cf5,0x66090267,0x3dc6c640}}, // _hiba, škem_, _žeki, _prow_,
+ {{0x6d4e331a,0xa2d804c5,0x7d088082,0x5896803d}}, // _kiba, यान्, tids, _مجاز,
+ {{0xdce981ac,0x8f76835f,0x6d4e4641,0x68e1c642}}, // ateľ, _буді, _jiba, _gold,
+ {{0x6f09c643,0x7d088b81,0x290a4644,0x6d4e4645}}, // biec, rids, diba_, _miba,
+ {{0x6d4e4646,0x973c82fd,0xa2ca81ab,0x68e1c647}}, // _liba, _opći, _सोम्, _zold,
+ {{0x7d08c648,0x68e18059,0x26c0031d,0xf1c784e5}}, // pids, _yold, ddio_, लियन,
+ {{0x25a0013c,0x7faa8201,0x5d8380f7,0x68e18118}}, // dtil_, _təqd, _الول, _xold,
+ {{0xe44f9368,0xd7f80364,0xf99f4649,0x7c2e0502}}, // _عضو_, гут_, stè_, _nybr,
+ {{0x6d4e130c,0xa4ff000c,0x3f9101f4,0x636081bc}}, // _aiba, ोच्च_, ruzu_, _ịnwa,
+ {{0x6d4e4207,0xdd950071,0x2d9200b4,0x443809ed}}, // [34f0] _biba, разы, guye_, _azr_,
+ {{0x2d873a71,0x290a0661,0x6d4e464a,0x753c0035}}, // čne_, ciba_, _ciba, _chrz,
+ {{0x6d4e0be6,0x68e183a8,0x32558110,0xaa55a748}}, // _diba, _rold, авер, авеш,
+ {{0xd6db464b,0x2d923623,0x6d4e0102,0x65950110}}, // _что_, buye_, _eiba, _каму,
+ {{0x68e1c03c,0x25a0464c,0x2b4d80ff,0x442ec64d}}, // _pold, ctil_, _viec_, _lyf_,
+ {{0x6d4e464e,0x6f09809a,0x68e1a08e,0xa6db007b}}, // _giba, wiec, _qold, _viðu,
+ {{0x68e1bb63,0x6f098029,0xe3e480c8,0x46f5c64f}}, // _vold, tiec, _ফেসব, ичит,
+ {{0x290a0309,0x9585a209,0x6d4e44de,0x9a1281bc}}, // ziba_, алие, _ziba, pọpụ,
+ {{0x68e19a76,0xe57697ae,0x19598a14,0x3ebfc650}}, // _told, азы_, _разы_, rdut_,
+ {{0x6f098114,0xb3a98214,0x6d4e0118,0xdce981d6}}, // siec, _çıkı, _xiba, steľ,
+ {{0xd01187bd,0x6f098029,0x6d41b974,0xfd5e01bc}}, // _بلا_, piec, rmla, _bayọ,
+ {{0xb6c98117,0x69c9c651,0x690901d0,0x1f5a81c6}}, // _جائے_, _kree, užen, _סדנא,
+ {{0x290a4652,0x69c9808e,0x956795a6,0x7f570036}}, // tiba_, _jree, _съед, _auxq,
+ {{0x7e7b8867,0x8c45c653,0x672481dd,0x2ca780ff}}, // laup, _келе, čije, _ubnd_,
+ {{0x61eb807b,0x6aa9c654,0x37e34655,0x2f1a8ec3}}, // hygl, _obef, оорг, līga_,
+ {{0xe8df8028,0x09e60e97,0x539a84de,0x26c044b2}}, // _muốn_, _воин, _סיפו, udio_,
+ {{0xed570003,0x6d5bc656,0x9f453649,0x69c981c0}}, // [3500] иот_, nqua, álá_, _nree,
+ {{0x6909003e,0x7ae41bda,0x25a03a4c,0xb97b02f6}}, // nžel, _hoit, rtil_, _שניי,
+ {{0x25a04657,0x7e7b862c,0x1d19035f,0xeb9a04fa}}, // stil_, kaup, ують_, тие_,
+ {{0x7c2e03cb,0xfaa690fc,0x7ae40009,0x82a68110}}, // _vybr, радо, _joit, радж,
+ {{0x7ae407f4,0x44260063,0x41b695a4,0x7c2e009a}}, // _moit, ło_, рсет, _wybr,
+ {{0x7ae4062f,0x6f160dee,0xa3d5801b,0xb11480ab}}, // _loit, lnyc, समा_, িত্ব_,
+ {{0xe8df801c,0xc60e8a27,0xd00f80a0,0xa2c181d0}}, // _cuốn_, ाप्य_, وله_, _रोक्,
+ {{0x6f164658,0x7ae44659,0x7e7bc65a,0x69c9b437}}, // nnyc, _noit, gaup, _free,
+ {{0x2d80465b,0x69c9c65c,0x1dc9066b,0x50b6804a}}, // nsie_, _gree, रियत, _всеу,
+ {{0x44f491d5,0x7af60110,0x3f5c0118,0x2d804543}}, // спос, _alyt, _oíu_, isie_,
+ {{0x7ae400e7,0xdeef81e2,0x3f5c465d,0x443893c2}}, // _boit, _ды_, _níu_, år_,
+ {{0xe5a6867c,0x7ae40068,0x6da6c65e,0x2d80079f}}, // _види, _coit, _вида, ksie_,
+ {{0x6f160dee,0x2d8000e1,0x2369816b,0x4efb83c8}}, // dnyc, jsie_, _čaje_, _בלאג,
+ {{0xddcf0162,0xa6db01fa,0x6abf022b,0x00000000}}, // _lecţ, _niðr, _waqf, --,
+ {{0x7af612f1,0xc7c41285,0x7c388024,0x9a872bca}}, // _flyt, ости, _uzvr, _купл,
+ {{0x27219c18,0x7ae40cdb,0xa2d8001b,0xddc998ad}}, // fóny_, _goit, याण्, nceş,
+ {{0x7cf21277,0x635398ad,0x2d8005ee,0xa2ca81d0}}, // [3510] _værn, _dănu, gsie_, _सोध्,
+ {{0x629b8098,0x04670ea6,0xeb1f99e8,0x6aa98037}}, // _scuo, ртам, यक्त_, _sbef,
+ {{0x6f16009a,0x61368087,0xd6d100f7,0x2d8d80b9}}, // bnyc, tâln, _فقد_, zree_,
+ {{0x69c9c65f,0xa6db0125,0x7528809a,0xd64e819d}}, // _pree, _viðs, hodz, _kọdụ_,
+ {{0xa2d80aed,0xe1ff38a5,0x68e50364,0x7cf24660}}, // यात्, nzó_, _kohd, _kærl,
+ {{0x69c980c9,0x6f0d4661,0xb4e5816f,0x68e50198}}, // _vree, liac, _नवे_, _johd,
+ {{0x6fd70540,0xa3bb000d,0x68e500ee,0x7e7b96b5}}, // डमिं, _अनि_, _mohd, taup,
+ {{0x6f0d4662,0x2d8d81b0,0x6d450c5e,0x38180039}}, // niac, tree_, lmha, יקום_,
+ {{0x6b5e8029,0x6ef5816a,0x9f4e004a,0xd64e819d}}, // _rīga, _cába, _åfå_, _nọdụ_,
+ {{0x6f16219c,0x6d45008c,0x7ae44663,0x6d5bb06a}}, // znyc, nmha, _soit, rqua,
+ {{0x69090110,0x6d5bc664,0x7ae43940,0x65699024}}, // ržel, squa, _poit, speh,
+ {{0x60c1c665,0xd64e8133,0xdce080eb,0x65958048}}, // _halm, _bọdụ_, rpmā, _гаду,
+ {{0x7ae44666,0x60c1c667,0x333f81cd,0xe2970009}}, // _voit, _kalm, _mhux_, щая_,
+ {{0x60c19ab3,0xd466835f,0x6f16009a,0xeb999cb2}}, // _jalm, _лише_, wnyc, _бик_,
+ {{0xbb858307,0x60c1c668,0x6ef5803e,0x6d59c669}}, // _البي, _malm, _zába, _huwa,
+ {{0x09bd8a49,0x6d598303,0xd64e819d,0x2d80000b}}, // _আপনা, _kuwa, _fọdụ_, tsie_,
+ {{0xac8601e5,0xe73715d1,0x0dca01e5,0x7faa8201}}, // [3520] йгал, сет_, ылай_, _məqa,
+ {{0x2d801de5,0x6d59c66a,0xddcf0087,0x5c56466b}}, // rsie_, _muwa, _secţ, стаф,
+ {{0x2d80466c,0xb5fd81e2,0x8ca9886a,0x6d59b15d}}, // ssie_, rašy, ज्यो, _luwa,
+ {{0x6f0d11b9,0x6455466d,0x2d803f1a,0xe5a601ae}}, // ciac, _egzi, psie_, _лими,
+ {{0x26c2003a,0x2b400083,0x6d59c66e,0x4a9b03c8}}, // _iako_, _mhic_, _nuwa, רינג,
+ {{0xa50a0381,0x86370039,0x6eea016b,0x26c2466f}}, // _сега_, _לרכב_, _výbu, _hako_,
+ {{0x60c1abea,0x6ef58510,0x68e10ede,0x6d59854e}}, // _dalm, _sába, öldi, _auwa,
+ {{0x26c24670,0x6d59c671,0xe8df801c,0xe807816f}}, // _jako_, _buwa, _buồn_, _वेळा_,
+ {{0x26c2001d,0x2dda81a8,0x6d59aeb7,0xa2c1a128}}, // _mako_, ابعة_, _cuwa, _रोट्,
+ {{0x6d59c672,0x2d8a875f,0x60c181b4,0x6f0d4673}}, // _duwa, čbe_, _galm, ziac,
+ {{0x63a582af,0x7cf20b40,0x75289988,0x7afd0168}}, // _ähnl, _nærm, rodz, ërto,
+ {{0x7cf20bc5,0x26c24674,0x083b80be,0x6d5980fc}}, // _særl, _nako_, _געבל, _fuwa,
+ {{0x7982809a,0x2d4d0279,0x1562819d,0x26c380eb}}, // nsow, _džej_, _fọọa, ējot_,
+ {{0x19580791,0xa2d5816f,0x2ee681b0,0x26c205ee}}, // сары_, णाऱ्, _hoof_, _aako_,
+ {{0x6e3c82bf,0x6d59c675,0x26c204bb,0x78b5330c}}, // ərba, _zuwa, _bako_, rezv,
+ {{0x877b83c8,0x290e83e4,0xdce080eb,0xceb981d0}}, // טאלי, hifa_, ksmī, toři_,
+ {{0x28b98321,0x26c21f2c,0x690909d1,0x26c701d0}}, // [3530] _इसलि, _dako_, nžek, ěno_,
+ {{0x6f0d0038,0x6d451769,0x89dc01c6,0x2ee68a53}}, // siac, umha, _תחזי, _loof_,
+ {{0x6d454676,0x2d4d00d2,0x6f0d34cd,0x26c24677}}, // rmha, _užem_, piac, _fako_,
+ {{0x539b8039,0x6d450c41,0xf2d400be,0x81bd8264}}, // ריאו, smha, ּעס_, ইমস_,
+ {{0xb2bc00be,0x994801f9,0x6aad2eae,0x6f1d0bfd}}, // טמאר, _خلیل_, _mbaf, _ijsc,
+ {{0x6d59c678,0x656d4679,0x60c18085,0x26c2467a}}, // _ruwa, mpah, _qalm, _zako_,
+ {{0x60c1be00,0x26c2022e,0x6d59c67b,0x43943cb3}}, // _valm, _yako_, _suwa, _нарс,
+ {{0x0565916b,0xa3ae0006,0x46e98162,0x7d0a8362}}, // овин, _कहत_, рдин_, _amfs,
+ {{0x60c1c67c,0x4add00c2,0xa2d8024c,0x656d2916}}, // _talm, यानव, यास्, npah,
+ {{0x3b859383,0x6aad467d,0x5ba99541,0x672481a1}}, // олог, _abaf, аком_, čija,
+ {{0x28158013,0x656d0573,0x443c81a1,0x69cd0915}}, // _بواس, hpah, _czv_, _arae,
+ {{0x6d59c67e,0x4acf9199,0x63a50c5e,0x69cd1aa5}}, // _tuwa, _सोमव, ithn, _brae,
+ {{0xabd78065,0x26c2011b,0x9f4c01d0,0x6ef58061}}, // _گزشت, _rako_, ádí_, _hábo,
+ {{0x26c2467f,0x672f009a,0x2d4d0088,0x69cd1eb8}}, // _sako_, _akcj, _džek_, _drae,
+ {{0xc447803d,0x3ea937f7,0x7ce00061,0x69cd4680}}, // _دیدن_, ffat_, _törz, _erae,
+ {{0x7afd0352,0x6aad11d3,0x9f590061,0x1b180264}}, // chst, _gbaf, ázás_, _ধীরে_,
+ {{0x23608025,0x69cd4681,0x26c20042,0x6da30012}}, // [3540] _čiji_, _grae, _vako_, мита,
+ {{0x26c2022e,0x62898091,0xe5773a8b,0xa2d80b99}}, // _wako_, _adeo, юзу_, याव्,
+ {{0x6ef5826f,0x6efc80e7,0xdee62f2f,0x66e6116b}}, // _nábo, _hébe, _дони, _дона,
+ {{0x3f984682,0x49ca91b3,0xa25b01d6,0x7982b235}}, // muru_, илен_, jdôl, tsow,
+ {{0xdce28503,0x3f980458,0x63a500f7,0x290e93a9}}, // _stoč, luru_, athn, tifa_,
+ {{0xc5f30051,0xf7729b9a,0x7cf2013c,0x6ef58f09}}, // _חדש_, واع_, _mærk, _bábo,
+ {{0x290ec683,0x4aca32dd,0xa3ae02f1,0xaadd03eb}}, // rifa_, ियाव, _कहि_, यामक,
+ {{0x78ad812b,0x27e08024,0xe297a72e,0x290ec684}}, // đava, ćin_, _мас_, sifa_,
+ {{0x26c281ac,0x3f983a2f,0x7cf227d1,0xe0daa481}}, // ľko_, huru_, _nærk, _ква_,
+ {{0xa3ae0d38,0x3f984685,0x69c08065,0x26c2877b}}, // _कहा_, kuru_, _isme, žko_,
+ {{0x7e644686,0x3ce7826c,0xdce2812b,0x3f982a3b}}, // kcip, _donv_, _utoč, juru_,
+ {{0x69cd4687,0xeb07035f,0x6efc83c9,0x7ae2808e}}, // _prae, ічно, _bébe, njot,
+ {{0x9e65803d,0xad5a8198,0x6ef5936f,0xddc3802e}}, // _رانن, _трех_, _zábo, _обци,
+ {{0x3f984134,0x6efc84c3,0x443c8b24,0x69cd0a53}}, // furu_, _débe, _tzv_, _vrae,
+ {{0x3f98376a,0x443cb5ca,0x290c82d5,0xb90312c6}}, // guru_, _uzv_, _mmda_, _नच_,
+ {{0x69c08904,0x69cd28c6,0xd6d84688,0x37bc80ab}}, // _osme, _trae, оту_, _অপার,
+ {{0xa2d80b84,0x2d848101,0x61e48198,0x31690326}}, // [3550] यार्, nsme_, äill, _utaz_,
+ {{0x3f984689,0x2d84c68a,0x656d00dd,0x8883003d}}, // buru_, isme_, rpah, _زیرن,
+ {{0xa3cd9d40,0x69c0c68b,0x291123a8,0x656d468c}}, // रिय_, _asme, miza_, spah,
+ {{0x0cb785b3,0x629d02e8,0x63a5031d,0x2d8480eb}}, // _अस्म, ngso, rthn, ksme_,
+ {{0x7e7d0341,0x68e8c68d,0x2d8486d4,0x1a9c03de}}, // _iesp, _bodd, jsme_, שידע,
+ {{0x2911468e,0x91bc0039,0x68e891fe,0x7cf204d6}}, // niza_, ימדי, _codd, _nærh,
+ {{0x20032180,0x69c0c68f,0x6d5d1d4f,0x68e8c690}}, // nzji_, _esme, _husa, _dodd,
+ {{0x2d9592c0,0x6d5d43b7,0x7e7d4691,0x6b830511}}, // прос, _kusa, _jesp, ángu,
+ {{0x3f984692,0x291139e1,0x6d552fc0,0xfaff00f1}}, // zuru_, kiza_, _kiza, dhë_,
+ {{0x6d5d4693,0x7afb8125,0x7e7d0247,0x29110c53}}, // _musa, _hlut, _lesp, jiza_,
+ {{0xca291dce,0x6d5d4694,0x2d84c695,0x7ae9aada}}, // _עם_, _lusa, asme_, _koet,
+ {{0xf770819f,0x6d5d241f,0xdb04006a,0x25608176}}, // _شام_, _ousa, _opnå, _fòl_,
+ {{0x7ae98613,0x29114696,0x7cf30214,0x7d1a81c0}}, // _moet, fiza_, _kırı, bnts,
+ {{0x7cf2013c,0x3f984697,0x29114698,0x6d554699}}, // _værk, turu_, giza_, _niza,
+ {{0x7afbba3e,0x79998748,0x6d5d469a,0xd9b88bb8}}, // _olut, huww, _ausa, _इन्ट,
+ {{0x05661269,0xdee3333d,0x3f98469b,0x66e30615}}, // зван, носи, ruru_, носа,
+ {{0x6d55469c,0x7e7d0927,0x2911409f,0x60c5469d}}, // [3560] _biza, _desp, biza_, _fahm,
+ {{0x3f98469e,0xdd8f0f99,0x6d5d469f,0x7afbc6a0}}, // puru_, اول_, _dusa, _alut,
+ {{0x46bd816f,0x7ae9a26f,0x68e88114,0x7afb81ec}}, // ्याह, _boet, _rodd, _blut,
+ {{0x7e7d16a1,0xe29ac6a1,0x68e8c6a2,0x6d5d46a3}}, // _gesp, сад_, _sodd, _fusa,
+ {{0x6d5d46a4,0x6d552d68,0x63828019,0x7ae28009}}, // _gusa, _fiza, _kíná, rjot,
+ {{0x7f42820f,0x7bce0a20,0x6d55011e,0x7e7d009a}}, // _shoq, _trbu, _giza, _zesp,
+ {{0x7afbc6a5,0x6d5d0352,0xd0108013,0x7cf22ba5}}, // _flut, _zusa, ضلة_, _næri,
+ {{0x67248042,0x799980ee,0x6d550ab4,0xe2f88019}}, // čijo, buww, _ziza, _بورڈ_,
+ {{0x69c084c4,0x660446a6,0x3cfe951e,0x6d5d0079}}, // _usme, nzik, thtv_, _xusa,
+ {{0x7ae9c6a7,0x6d439995,0x636181bc,0x7d1a82f9}}, // _zoet, _chna, _ịnap, unts,
+ {{0xfaff020f,0xa2950221,0x256086c0,0x929511cf}}, // thë_, мані, _wòl_, манц,
+ {{0x23be001b,0xf7730019,0x7d1a81c0,0x29110314}}, // _můj_, ہار_, snts, wiza_,
+ {{0xdd8f845b,0x236082a5,0x7ced80f7,0x29112fe0}}, // _مون_, _čiju_, _cúrs, tiza_,
+ {{0x6d5d0f43,0xfaff00f1,0x0cc700ab,0x7bdc8074}}, // _rusa, shë_, _শক্ত, ärus,
+ {{0x29112ea5,0x6d5d0590,0x6d5546a8,0x443a8028}}, // riza_, _susa, _riza, _áp_,
+ {{0x6d5d0859,0xb8f41516,0x70b9a0e9,0x29110316}}, // _pusa, _सो_, _इस्ल, siza_,
+ {{0xdce98a56,0x7e7d12d0,0x6d5525ca,0x5d552700}}, // [3570] zpeč, _vesp, _piza, дкат,
+ {{0x7afbc6a9,0xe94500d5,0x7ae9c6aa,0x672d0c93}}, // _slut, _تربی, _soet, soaj,
+ {{0x7afbc6ab,0xccf380be,0xe8d901bc,0x7e7d46ac}}, // _plut, עכע_, _juọ_, _tesp,
+ {{0x6d550c5a,0xa3cd90c5,0xa3ae000f,0x6d5d11b5}}, // _wiza, रित_, _कहर_, _tusa,
+ {{0xdd868117,0x7ae985f8,0xa2a50bb3,0x6d5d46ad}}, // _جو_, _voet, _कान्, _uusa,
+ {{0x25a946ae,0xf770003d,0xc2448d8e,0xbcfb1727}}, // mtal_, _جای_, хнік, _quéd,
+ {{0x7ae981b7,0x25a920f0,0x7afb89c4,0x7cf698ad}}, // _toet, ltal_, _tlut, _cârm,
+ {{0x26c90010,0xdb1900e1,0x25a924d7,0x628d01a8}}, // ndao_, _prvý, otal_, _ndao,
+ {{0x25a946af,0x7cf305e0,0x917a027d,0x387e8c0b}}, // ntal_, _tırı, _mật_, _fetr_,
+ {{0xab659980,0x2a690176,0x7ce482d6,0x628d0493}}, // мвол, _afab_, _kòry, _adao,
+ {{0xccf2012a,0x6ef83666,0x1c459fb4,0xd7c186ae}}, // דכי_, _bíbl, ьном, _शनिच,
+ {{0x395f82f1,0x52b801a2,0xc87882d0,0x769681cc}}, // _kuus_, इजेस, _doğu_, rüyü,
+ {{0x64a5817d,0xb4bd081f,0xa06a0196,0x5fc5c6b0}}, // _жала, _आसू_, жага_, विकल,
+ {{0xa3ae0006,0xdb190580,0xdcfb80c3,0x7cf22419}}, // _कहल_, _esvà, pruđ, _hærv,
+ {{0x07a306c8,0xe8d9019d,0x2d8f0216,0x225f026c}}, // карн, _guọ_, ágen_, žuka_,
+ {{0x6604011b,0x6ef80144,0x25a9090d,0x98bf822b}}, // tzik, _víbo, ftal_, _ftuħ_,
+ {{0x395f84df,0x28de06ae,0xe1ff0511,0xccc6361c}}, // [3580] _nuus_, मादि, lcón_, мбай,
+ {{0xf53f108c,0x660446b1,0x6ef595d8,0x69c446b2}}, // _spår_, rzik, _bábk, _isie,
+ {{0xe1ff06a5,0xf365021e,0x78bc0cab,0x69c402a6}}, // ncón_, етін, merv, _hsie,
+ {{0x31602d68,0x78bc46b3,0xa2d4081f,0x7cf20e51}}, // _kuiz_, lerv, _बोम्, _nærv,
+ {{0x7bda086f,0x7afd00f1,0x387e801b,0x31600187}}, // _štud, ërti, _petr_, _juiz_,
+ {{0xa2a5001b,0x316000b9,0x2f1a80eb,0x69c446b4}}, // _काभ्, _muiz_, mīgi_, _msie,
+ {{0xb9068105,0x6b632b3f,0x64a346b5,0x3ead926c}}, // _बच_, _акса, _шара, ffet_,
+ {{0x78a9003a,0x3ea038dc,0x8c431cb2,0x758a8009}}, // đevi, ngit_, _ресе, осов_,
+ {{0x443846b6,0xb8f480ab,0x2f1a80eb,0x78bc46b7}}, // _hyr_, _হক_, nīgi_, kerv,
+ {{0x62808020,0x6efc82be,0xb143802e,0x690904e8}}, // _hemo, _déba, _анул, nžet,
+ {{0x78bc01eb,0x38cb80d5,0xaad002f1,0x7cedc6b8}}, // derv, _باقی_, _तोहक, _púrr,
+ {{0x60c8c6b9,0x443846ba,0xed5a95be,0x25a904e8}}, // _hadm, _myr_, _дод_, ytal_,
+ {{0xe707826a,0x5ea38065,0x645c46bb,0x5573902a}}, // _تسلی, _شمول, _ogri, _агіт,
+ {{0x672d812b,0x645c2b0f,0xa3de000d,0x25a9016d}}, // čaje, _ngri, तमा_, vtal_,
+ {{0x403419b5,0x690946bc,0xdb190118,0xd5c880ff}}, // верс, džet, _esvá, _hề_,
+ {{0x6d588019,0x25a946bd,0x3ea015d4,0xd5c88129}}, // _hiva, ttal_, ggit_, _kề_,
+ {{0x7cff879f,0x60da80c9,0x7cf246be,0x9f340163}}, // [3590] _wêre, _ontm, _læru, теті,
+ {{0x44384285,0x3f9c82ec,0x7aed46bf,0x3d940758}}, // _byr_, kuvu_, _hoat, тифр,
+ {{0x62809de6,0x6722c6c0,0xd5c88129,0x44381a1f}}, // _bemo, _ojoj, _lề_, _cyr_,
+ {{0x6d58c6c1,0x44382280,0xbcfb00e7,0xdced0279}}, // _liva, _dyr_, _québ, mpač,
+ {{0x7aed01e0,0xa069c6c2,0x31600118,0x7d088084}}, // _moat, пала_, _xuiz_, _kūrė,
+ {{0x628082c4,0x7aed46c3,0x4438245f,0x60c8bcc0}}, // _eemo, _loat, _fyr_, _cadm,
+ {{0x6ef58acf,0x6d47026c,0x2d4d0088,0x6efcc6c4}}, // _hábi, _ihja, _džet_, _séba,
+ {{0x6280837a,0x6d58c6c5,0xa3cd84c5,0x60c88118}}, // _gemo, _aiva, रिस_, _eadm,
+ {{0x79a4b16c,0x6d58c6c6,0x2d8946c7,0x6abd0261}}, // _архе, _biva, nsae_, desf,
+ {{0x620183bf,0x3eadc6c8,0x6d58c6c9,0x6b650084}}, // ızlı, sfet_, _civa, _jėgo,
+ {{0x7c958077,0x290300f1,0x78bc0be7,0x6ef58187}}, // وشگا, dhja_, verv, _lábi,
+ {{0xe1ff157a,0x6d58c34e,0x2e4e819d,0x78bc46ca}}, // rcón_, _eiva, _kọlọ_, werv,
+ {{0x6d470009,0x6d588901,0x99d60019,0x2bb18074}}, // _ohja, _fiva, رتحا, _जहवा,
+ {{0xa3c123bd,0x64a58ba7,0x6ef580f7,0x6d58c6cb}}, // ्टा_, _чака, _sábh, _giva,
+ {{0x657d0032,0x0ea686a7,0x656f0c0b,0xfc648081}}, // _awsh, क्कड, _atch, _ръчн,
+ {{0xdbd718c5,0x6443c6cc,0xc4c60077,0x7aed46cd}}, // _määr, _izni, کترو, _goat,
+ {{0x628600a4,0x443e1fe7,0x6280c6ce,0x443846cf}}, // [35a0] mako, _št_, _remo, _syr_,
+ {{0x6280859c,0x628646d0,0x2ebd816f,0x2f1a80eb}}, // _semo, lako, ्युत, rīgi_,
+ {{0x62808057,0x6ef580f7,0x60c8812b,0xbcfb041c}}, // _pemo, _tábh, _radm, _suéc,
+ {{0x869a0364,0x22288077,0xed5a2355,0xe3631b3f}}, // _этот_, وزشی_, _ном_, укци,
+ {{0x62809e00,0x60c889c4,0x395a46d1,0x260faa47}}, // _vemo, _padm, _kips_, _तेली_,
+ {{0x7faa8085,0xc0e38c4f,0x628646d2,0xb86680d7}}, // _həqi, лочк, hako, راتو,
+ {{0x8c430c8e,0x6280c6d3,0x443e0019,0xddcd0289}}, // _бере, _temo, _át_, mbaš,
+ {{0xd5e28032,0x6d58c6d4,0xa6aa81a8,0x03d701c6}}, // _afó, _siva, سابق_, _אולם_,
+ {{0x6d588669,0x644380ee,0x7ced9e00,0x7aed0162}}, // _piva, _azni, _púrp, _roat,
+ {{0xd5c88142,0x81e600ab,0x81b380ab,0xc42a9bc1}}, // _về_, পনা_, টিশ_, зюме_,
+ {{0x7aed0c6e,0x6d58c6d5,0xa2d58072,0x6abd039c}}, // _poat, _viva, णाच्, tesf,
+ {{0x81b380c8,0x78a5026f,0x6aa293fa,0x628646d6}}, // টির_, _schv, ngof, gako,
+ {{0x6f0401e4,0x6d58c6d7,0xdc12a2f8,0xee3ac3e1}}, // dhic, _tiva, rşım, _энд_,
+ {{0x395a3c2d,0xa3cd9a87,0x248587f1,0x7cf2006a}}, // _cips_, रिश_, calm_, _mærs,
+ {{0x7aed0c6e,0x628637c6,0xa2d80c78,0xddcd026c}}, // _toat, bako, याख्, dbaš,
+ {{0x290300f1,0x6f043279,0x643b8039,0xdc3b84de}}, // shja_, ghic, _מעונ, _מעור,
+ {{0xdd940196,0x6594361c,0x3d941a02,0x7faa8085}}, // [35b0] латы, лату, литр, _dəqi,
+ {{0xac272d55,0xbcfb026b,0xa3e20035,0x2f1a81a9}}, // нфек, _atér, _गपशप_, mīgu_,
+ {{0x2d890609,0x2901153a,0x80a49107,0x6efc83a8}}, // ssae_, _ilha_, च्छे, _nébo,
+ {{0x91bf81ce,0x9e150112,0x6f0446d8,0x59bf909b}}, // _एनआई, _адмі, chic, _एनआर,
+ {{0x3f8c826c,0x2b490c41,0xdb1c8019,0x2d8b01a1}}, // _avdu_, _mhac_, _arró, _uvce_,
+ {{0x628646d9,0x6e398428,0xdbd72551,0x395a07f1}}, // zako, _rywb, _päär, _xips_,
+ {{0x628646da,0x7cf23c10,0x6efc89c4,0xafe58084}}, // yako, _vært, _cébo, тойл,
+ {{0xdbd7043d,0x7cf20125,0x25b900b9,0x2b4900ff}}, // _väär, _færs, _kpsl_, _nhac_,
+ {{0xe7f38f21,0xdb1c80ab,0x290103a7,0x26cb46db}}, // _अथवा_, _erró, _olha_, _maco_,
+ {{0x6b5e80eb,0x673b8968,0x6286019e,0xd9559190}}, // _līgu, sluj, wako, _شناخ,
+ {{0xe29a12a0,0x628646dc,0xaadd0aed,0xdce42bea}}, // ман_, tako, यांक, spić,
+ {{0x6443803e,0x46128180,0x2b4946dd,0x26cb46de}}, // _vzni, اویر, _chac_, _naco_,
+ {{0xed4eabb3,0x62863539,0x28de0327,0x2485c6df}}, // _во_, rako, मालि, palm_,
+ {{0x628646e0,0x7c878878,0xf487a0bf,0xdd92004e}}, // sako, _чуде, _чудн, یوز_,
+ {{0x6f0414f2,0xdb02002a,0xe73a0572,0x25ada2ab}}, // thic, atlá, дев_, ltel_,
+ {{0x3ebf81ec,0x7cf201fa,0x07a30073,0x7b119e1e}}, // neut_, _nærr, јатн, sťuj,
+ {{0x6609829b,0xe8120768,0x7bd546e1,0x68470118}}, // [35c0] nzek, _डेरा_, _arzu, _pédí,
+ {{0x67248fda,0x6f0400fa,0xbcfb0036,0xdbe48091}}, // čiji, shic, _stér, _bèèr,
+ {{0x291846e2,0xe0fb03c8,0x261883eb,0x6ef846e3}}, // mira_, _חליל, _बेनी_, _líbi,
+ {{0x69d608cf,0x29180c56,0xe6bd816f,0xdb1c8722}}, // _krye, lira_, ्योज, _arrò,
+ {{0x4aaa0076,0xe894a325,0x628446e4,0xa2a504c5}}, // _कानव, галь, _meio, _कास्,
+ {{0x291846e5,0x66098029,0x81b380c8,0x69c2874c}}, // nira_, dzek, টিং_, rwoe,
+ {{0x0fe08a49,0x7cf215f8,0x38cb87d2,0xebe3006d}}, // বন্ধ, _færr, بانی_, _корп,
+ {{0x6d5c46e6,0x291846e7,0xa3c10128,0x69d62879}}, // _hira, hira_, ्टर_, _orye,
+ {{0x291820d7,0x6f030012,0xf1aa8077,0x28e2809a}}, // kira_, _înce, _تازه_, पादि,
+ {{0x6d5c46e8,0x672d8052,0x29181b52,0x644346e9}}, // _jira, čaja, jira_, żnie,
+ {{0x291846ea,0x3ebfc6eb,0xdc9b007c,0x69d62ce1}}, // dira_, beut_, וייל, _arye,
+ {{0x29180548,0x6d5c46ec,0x65628198,0x200a0140}}, // eira_, _lira, _tuoh, dzbi_,
+ {{0xc3338051,0x65948087,0x25adb49a,0x69d607f1}}, // שור_, _салу, ctel_, _crye,
+ {{0x291846ed,0xa2a51d7c,0x26cb04f0,0x853000fc}}, // gira_, _काव्, _saco_, buɗi,
+ {{0x19580110,0x6f02c6ee,0x26cb157a,0x5433815b}}, // тары_, _kloc, _paco_, _پرور,
+ {{0x25a01301,0x6d5c010a,0x6ef58207,0x69d6028c}}, // fuil_, _aira, _fábu, _frye,
+ {{0x291846ef,0x2f1a81a9,0x2369877b,0x69d646f0}}, // [35d0] bira_, līgs_, _čaji_, _grye,
+ {{0x29182e96,0x6f02c5b8,0x7afd417e,0x6d5c01b4}}, // cira_, _lloc, lkst, _cira,
+ {{0xa2d8053e,0x6d5c316a,0x2bd5809a,0x2d4d0668}}, // याच्, _dira, धिया, _džep_,
+ {{0x7afd46f1,0x26c0041c,0x6d5c46f2,0xbddb010c}}, // nkst, ceio_, _eira, _nyèg,
+ {{0x6d5c46f3,0x442a0118,0x99158d8e,0x7bd5009a}}, // _fira, _exb_, лькі, _wrzu,
+ {{0x6d5c146f,0x7c3c0125,0xa5da00be,0x6f028087}}, // _gira, _fyrr, _אַלי, _aloc,
+ {{0x6f02aaf4,0x6e3d031d,0x7c3c0114,0x46aa001b}}, // _bloc, _hysb, _gyrr, ङ्कह,
+ {{0x660980ad,0x25adc6f4,0x291846f5,0x7e6d0867}}, // tzek, ttel_, zira_, ncap,
+ {{0x6d4a8013,0x29180e4d,0x25ad8e61,0x6d5c0079}}, // _bhfa, yira_, utel_, _yira,
+ {{0x6609c6f6,0x2ed0000f,0x25ad92af,0x291801df}}, // rzek, _हफ्त, rtel_, xira_,
+ {{0x25adc6f7,0x66098019,0x7e6d008e,0x29079a26}}, // stel_, szek, kcap, mhna_,
+ {{0x7f5d0609,0x6f02c6f8,0x29182bc2,0xaca3819d}}, // _nisq, _gloc, wira_, _krịk,
+ {{0xa2a5085d,0xe4c8815b,0x7e6d0299,0x628440d9}}, // _कार्, ربین_, dcap, _veio,
+ {{0x361a8158,0x6f028052,0xa2d4090f,0xef19a358}}, // _אונד, _zloc, _बोर्, liż_,
+ {{0x291846f9,0x7f5d46fa,0x60260098,0x6d5c46fb}}, // rira_, _bisq, удва, _rira,
+ {{0xbcfb46fc,0xef1981b9,0x6ef5841c,0x7faa811c}}, // _quén, niż_, _tábu, _təqv,
+ {{0x291846fd,0xb90a00c8,0x2569c6fe,0x7f5d46ff}}, // [35e0] pira_, _মত_, _júl_, _disq,
+ {{0xb5fb0118,0x6d5c2691,0x26c00187,0xee3746a1}}, // _afás, _qira, seio_, уну_,
+ {{0x6d5c4700,0x9f5800f1,0x290781e4,0x656983ed}}, // _vira, nyrë_, dhna_, hqeh,
+ {{0x6d5c2b11,0x7e6d01e8,0x7cf68162,0x5c3683de}}, // _wira, ccap, _hârt, ארען_,
+ {{0x6d5c4701,0x25a00661,0x427b00be,0x98a7c702}}, // _tira, quil_, גאנג, zoną_,
+ {{0x6724003a,0xa3e3800d,0xbebb00f1,0xa3d692ee}}, // lnij, नमा_, _njëj, सिम_,
+ {{0xc5f18a49,0x6f02aa07,0xa2a52128,0x6d4781cd}}, // _জেলা_, _ploc, _काल्, ċjal,
+ {{0x6d4a80f1,0x672401cd,0x6566005d,0xa6f48162}}, // _shfa, nnij, _kukh, рзиш,
+ {{0x94868162,0xe9da835f,0xb226bb4a,0x443c854f}}, // лынд, _яка_, _смал, _syv_,
+ {{0x38c8819f,0x656641a8,0x2907801b,0x6724026c}}, // _جاری_, _mukh, chna_, hnij,
+ {{0xdb020bd7,0x5ea68019,0x6f028369,0x4c9b00be}}, // ktlä, _ممال, _tloc, עשטע,
+ {{0x67240025,0xe45700be,0x8aff0041,0x2f1a80eb}}, // jnij, טייט_, _buƙa, rīgs_,
+ {{0x7f5d02be,0x92580198,0x0eab00c2,0xb69b0493}}, // _risq, лают_, _छाबड, _blân,
+ {{0xbcfb0144,0x98a78084,0xbebb03ed,0xbd029517}}, // _cuél, poną_, _gjëj, _šéfr,
+ {{0x395e910f,0xffe4104b,0x7e6d0580,0x7d1ac703}}, // _mits_, бютн, tcap, mits,
+ {{0x7d1ac704,0xc8780059,0xeafb003d,0x2bc8016f}}, // lits, ığa_, _سرعت_, रबना,
+ {{0xd49b023a,0x7e6d10e1,0x2d8f0019,0xb5fb0091}}, // [35f0] ере_, rcap, ége_, _afár,
+ {{0x7d1ac705,0x65664706,0x395e823e,0x3f9c8699}}, // nits, _dukh, _nits_, krvu_,
+ {{0x67244707,0x7faa8201,0x9c4701b5,0x1cb815a9}}, // bnij, _məqs, ухал, طالب_,
+ {{0x67240052,0x7e62c708,0x7d1ac709,0x2d8d857b}}, // cnij, _ngop, hits, ysee_,
+ {{0xdd0107d9,0xef1984b7,0x7cdb00eb,0xd49b0073}}, // ştır, viż_, _pārē, _прв_,
+ {{0x69c9807a,0x7e6280e5,0x395e81a9,0x6ce73f56}}, // _vsee, _agop, _cits_, _сіме,
+ {{0x80ad1370,0x7d1ac70a,0x395e9b01,0xbebb0168}}, // _जाने, dits, _dits_, _njëk,
+ {{0xb8fcc70b,0x4975a57e,0x69c990af,0x2d8d8c04}}, // _तो_, _влас, _tsee, tsee_,
+ {{0x7bd88006,0x660d2486,0x09b88277,0x628bc70c}}, // _arvu, nzak, _مطلع_, mago,
+ {{0x7d1ac70d,0x5f958098,0x2d8d8c4d,0x7b16470e}}, // gits, _вижт, rsee_, ršum,
+ {{0xd8748060,0x04458c8e,0x2905c70f,0x3b0a0ae7}}, // _طالب, ренн, _illa_, вено_,
+ {{0x60c38179,0x628bc710,0x216a0b88,0x25698019}}, // lenm, nago, кими_, _túl_,
+ {{0xfce5b0bc,0x6ef59313,0x67240025,0x7bcac711}}, // _воло, _fábr, vnij, _esfu,
+ {{0x81b800c8,0x26cf9143,0x393601e5,0x660d4712}}, // ছিল_, _hago_, рэмс, dzak,
+ {{0x67240025,0xb8ee0c2d,0x628ba272,0x26cfc713}}, // tnij, _रस_, kago, _kago_,
+ {{0x6f0980a9,0x26cfc714,0x2caa008e,0x6ef5816b}}, // nhec, _jago_, _scbd_, _zábr,
+ {{0x29058181,0x6724012b,0xa3cd8ebf,0x6da611f3}}, // [3600] _olla_, rnij, रिक_, риза,
+ {{0x67240025,0xb4c2053e,0x80ad4252,0x6d41831d}}, // snij, ्ये_, _जाये, nlla,
+ {{0x24878f23,0x6d41c715,0x6cd31a37,0x389b03c8}}, // _menm_, illa, تقلا, _ביינ,
+ {{0x2905c716,0x628baab2,0x7d1ac717,0xe29ac718}}, // _alla_, gago, zits, тад_,
+ {{0x395e82e6,0x6f030087,0x7d1a8ab4,0x28e28bc2}}, // _pits_, _înca, yits, पालि,
+ {{0x3f9cb4d5,0xaca3819d,0x89da826a,0x26cf8091}}, // trvu_, _brọk, _یوزر_, _aago_,
+ {{0xb8cec252,0x7b240052,0x26cf82b8,0x628bc719}}, // _का_, rđuj, _bago_, bago,
+ {{0xd6d7c64b,0x2905c71a,0x6d41c71b,0xa8570039}}, // ать_, _ella_, ella, סיקה_,
+ {{0x26cf811e,0x10a6835f,0x7d1ac71c,0xc0e69b47}}, // _dago_, _визн, tits, _коак,
+ {{0x6208807e,0x60c3c71d,0x6296038a,0xdb0209c4}}, // ırlı, cenm, _adyo, lulè,
+ {{0x7d1ac71e,0x80c50f12,0x26cf83a8,0x660d471f}}, // rits, ाजसे, _fago_, zzak,
+ {{0x6d41c720,0x291cc721,0x7d1ac722,0x26cfc723}}, // alla, liva_, sits, _gago_,
+ {{0x7bda0bcf,0x26c4c724,0xb5fb0032,0x8b57807c}}, // _štul, memo_, _agán, ייטס_,
+ {{0xb4c2000d,0x29050125,0x291cc725,0x7cf68087}}, // ्यो_, óla_, niva_, _vârs,
+ {{0xb4c201d0,0x9b9488ca,0x26cf8091,0xdb0b006a}}, // ्यै_, _البت, _yago_, _opfø,
+ {{0x27ed003b,0x660d011e,0x2fcd009a,0x6d4e25b0}}, // ćen_, tzak, łego_, _ahba,
+ {{0x60c3b78f,0x798404b9,0x224902d0,0x291cc726}}, // [3610] yenm, _gwiw, _uzak_, kiva_,
+ {{0x61fd2158,0x660d4727,0x6efc8eba,0xe1ff009a}}, // mysl, rzak, _débi, wców_,
+ {{0x660d0019,0x60c3c728,0x26188074,0x9f3505a8}}, // szak, venm, _बेसी_, _гені,
+ {{0xf21c000f,0xaefb0706,0x6e32128a,0xf21281bc}}, // _पेड़_, _diùc, _žebř, rọnọ,
+ {{0x6d41c729,0x60c3c72a,0x26cfc72b,0xa9c78087}}, // ylla, tenm, _rago_, рсек,
+ {{0x628babd3,0x3169083a,0x291c8037,0x7c280088}}, // sago, _muaz_, giva_, _ždre,
+ {{0x26cf9fd1,0x260f823c,0x628bc72c,0xa2d8816f}}, // _pago_, _तेजी_, pago, _नोव्,
+ {{0x3ea91904,0x53348328,0x3eb9808b,0x0d858ba8}}, // ngat_, _дест, ýst_, блин,
+ {{0x20c78364,0x291cc72d,0xc33280be,0x98c79630}}, // _всег, biva_, _זון_, _всел,
+ {{0xdb1c83d3,0xa5da00be,0x6aad01a1,0x6f098144}}, // _aprè, אַטי, _acaf, shec,
+ {{0x63a50057,0x2bda009a,0x26cfc72e,0x673d02f1}}, // nuhn, णिया, _tago_, _oksj,
+ {{0x26c490d3,0xc86900be,0x442e81e0,0x6d41c5f7}}, // cemo_, _טן_, _dxf_, slla,
+ {{0xdb02472f,0x6abb81bc,0x61fd0196,0x248782d6}}, // nulé, _mbuf, gysl, _tenm_,
+ {{0xcd768039,0x673d00e8,0xa9670f9c,0xed4480e8}}, // _העסק_, _aksj, сира_, _дніп,
+ {{0xa3cd85b3,0xd0071fc1,0x69cd0cdb,0x6abbbbb8}}, // रिओ_, бере_, _esae, _obuf,
+ {{0x291c82a5,0x98a7809a,0x3ea94730,0x753c14df}}, // ziva_, ronę_, ggat_, _skrz,
+ {{0xc27b0039,0x10a600e8,0x98a61bc1,0xaefb0a2a}}, // [3620] _גרפי, _липн, _липе, _siùc,
+ {{0x26c4c731,0x6abbc732,0x7af601e0,0x88ca0c4f}}, // zemo_, _abuf, _hoyt, _слов_,
+ {{0xcfea03f8,0x63a50867,0x26c480b4,0x25b20123}}, // _صفحه_, guhn, yemo_, styl_,
+ {{0xfaa6a09a,0x69db8625,0xddc981d6,0x7ae40609}}, // садо, _brue, dbež, _jnit,
+ {{0xdce28052,0xd946902f,0x61469dc7,0xd91a89c7}}, // _suoč, беди, беда, льк_,
+ {{0x63a50057,0x26c4a813,0x6abba970,0x291a00b9}}, // buhn, wemo_, _ebuf, _jmpa_,
+ {{0x291c888b,0x7ae40091,0x26c48314,0xa3bc03ca}}, // riva_, _onit, temo_, _अहि_,
+ {{0xa3d6946d,0xc6f8a466,0xe8d900ff,0x69db888a}}, // सिस_, рних_, _tuệ_, _frue,
+ {{0x26c4c733,0x249800b9,0x69dbc734,0x291cc735}}, // remo_, _kdrm_, _grue, piva_,
+ {{0x6aad3648,0x26c4c736,0x7d1e02d5,0xbf0500a5}}, // _scaf, semo_, nips, _रत्न_,
+ {{0xb5fb0118,0x25a00d8b,0x261885fc,0xdb1cc737}}, // _agál, hril_, _बेरी_, _apré,
+ {{0xa3cd86a7,0x291a4738,0x69cd0118,0x7ae40362}}, // रिज_, _ampa_, _psae, _cnit,
+ {{0x4aaa0c78,0x7d1e4739,0x2d9fc73a,0xaaaa0744}}, // _कारव, kips, crue_, _कारक,
+ {{0x7ae42df1,0x25a0473b,0x2d92067f,0xf1270c5c}}, // _enit, dril_, dsye_, сьмо,
+ {{0x28de1a46,0x24b78039,0x76428681,0x236a010c}}, // माजि, _ההון_, _hyoy, _nubj_,
+ {{0x69c0838e,0x3ea90087,0xf99f03ec,0x764283f7}}, // _opme, ugat_, prè_, _kyoy,
+ {{0x7bdc4395,0xd0d50098,0xddc99024,0x44f50bba}}, // [3630] _arru, _допъ, zbež, опас,
+ {{0x04671baa,0x8c671ddf,0x63a50b20,0x753a8f3e}}, // стам, стад, tuhn, notz,
+ {{0x69c08029,0x7f4405c9,0xbc4b0150,0x645a473c}}, // _apme, bliq, учае_, _útil,
+ {{0x69db85b4,0xa294835f,0x63a5010b,0x672d9bcf}}, // _prue, заці, ruhn, čajk,
+ {{0x6f0d061f,0x63a509ca,0xe1ff4057,0x7bdc0102}}, // mhac, suhn, nyó_, _erru,
+ {{0x28ad0743,0xf7718250,0x63a500c3,0x6f0d07b6}}, // ञ्चि, واب_, puhn, lhac,
+ {{0x7762c73d,0x764284b9,0x628f2190,0x37868081}}, // _biox, _ayoy, kaco, общо_,
+ {{0x6d45473e,0x2d9584fa,0x69db8257,0x6abb9d61}}, // llha, орос, _true, _ubuf,
+ {{0x2903473f,0xd629c740,0xdd8e80a0,0x76428db1}}, // kkja_, ропе_, نوي_, _cyoy,
+ {{0x26d980e1,0x7af64741,0x753a811b,0xdb059d31}}, // ôsob_, _soyt, gotz, sthä,
+ {{0x02c61194,0x25a000b9,0xddc28035,0x7642c742}}, // ойно, zril_, _czoł, _eyoy,
+ {{0x7bda00cd,0x6b9c8661,0x628f4743,0x25a0113b}}, // _štuk, árga, gaco, yril_,
+ {{0x6f0d24ee,0xa96a047f,0x2d4d1f3a,0x3204016b}}, // dhac, рига_, _džez_, ámy_,
+ {{0x6efc82be,0x6729c744,0xb4e80327,0x320c066f}}, // _débu, lnej, _मचे_, ądy_,
+ {{0x656bc745,0xd9430012,0xbf0511bc,0x6b650110}}, // _hugh, фери, रसून_, _mėgs,
+ {{0x7ae439ac,0x6f0d0870,0x656b8c53,0x6729c746}}, // _unit, ghac, _kugh, nnej,
+ {{0x6563c747,0x2498288e,0x80a50019,0x20110037}}, // [3640] _kinh, _pdrm_, _کمپن, azzi_,
+ {{0x25a0433a,0x6563c748,0x6443b4f1,0xdca30adb}}, // rril_, _jinh, _kyni, дафи,
+ {{0x6563c749,0x7d1e2585,0x7f4405c9,0x656bc74a}}, // _minh, rips, pliq, _lugh,
+ {{0xe9daaa0e,0x6563c1c8,0x6f0d474b,0x6729c74c}}, // ике_, _linh, chac, jnej,
+ {{0x68f70201,0x7762802a,0x6d450114,0x752895d8}}, // _yoxd, _riox, blha, yndz,
+ {{0x628f21a2,0x1d0a245b,0xf8068009,0x224d8b67}}, // zaco, _теги_, жчин, _dzek_,
+ {{0xe3a78416,0x224d8019,0xeb971b3f,0x28b20f3d}}, // _تر_, _ezek_, _мир_, _जानि,
+ {{0x644e0ee1,0x656bc74d,0x63b303c1,0xe1faa59a}}, // _izbi, _bugh, žení, _кгб_,
+ {{0x6563c74e,0x62829151,0x64439428,0x3cf80589}}, // _binh, mboo, _ayni, _korv_,
+ {{0xa3d685e8,0x2b46823e,0x32d700ff,0x656bc74f}}, // सिल_, lloc_, _ủy_, _dugh,
+ {{0x6563c345,0x64438114,0x6729875f,0x628f4750}}, // _dinh, _cyni, bnej, taco,
+ {{0x6563c751,0x672981ac,0x644381e6,0x753a8102}}, // _einh, cnej, _dyni, sotz,
+ {{0x628f0a7a,0x7d1c026c,0x753ac752,0x6563c753}}, // raco, _omrs, potz, _finh,
+ {{0x644e0025,0x65639ac4,0x628f2484,0x6282c754}}, // _ozbi, _ginh, saco, hboo,
+ {{0x6f0d125b,0x6282c755,0x20051d9a,0x60c74756}}, // thac, kboo, áli_, rejm,
+ {{0x442d003a,0xe7f280cf,0x443100ee,0x290346be}}, // _će_, _अपना_, _rxz_, skja_,
+ {{0x200518a6,0x6d45112e,0x63a884ef,0x644e00fc}}, // [3650] šli_, tlha, ludn, _azbi,
+ {{0x6729a7aa,0x7c280bcf,0x62829447,0x6563c757}}, // znej, _ždra, eboo, _xinh,
+ {{0x63a8807a,0x62829de6,0xa2a5152c,0x290eaaa0}}, // nudn, fboo, _काञ्, dhfa_,
+ {{0x6282c758,0x672901a1,0xddcd4759,0x7d1c05ee}}, // gboo, čejs, mbaž, _emrs,
+ {{0x6729a19f,0x63a8c75a,0x28b2064a,0xed5b0180}}, // vnej, hudn, _जामि, _نشست_,
+ {{0x656bc75b,0xb17c04de,0x98aa01d0,0x69dd8118}}, // _rugh, סטור, době_, _ásem,
+ {{0xa2ac035a,0x6729c75c,0xe8d9019d,0x3d00016f}}, // _झाल्, tnej, _gwụ_, _शकते_,
+ {{0x0d859d8f,0x656ba7e4,0x63a8a676,0x2b46a112}}, // плин, _pugh, dudn, cloc_,
+ {{0x6443c75d,0x67298165,0x65638187,0xb3648081}}, // _syni, rnej, _pinh, _църк,
+ {{0x67299fe7,0xe6168e11,0x656bc75e,0x2b400af8}}, // snej, яды_, _vugh, _skic_,
+ {{0x6563c75f,0x6f1d00f3,0x628d0362,0xbb460254}}, // _vinh, _omsc, _beao, зенк,
+ {{0x6443850f,0x7d0ac760,0x798994ee,0x691006ae}}, // _vyni, _elfs, _kwew, _käeg,
+ {{0x55e581a1,0x25a913b8,0x6563c1c8,0x64438063}}, // _холб, jual_, _tinh, _wyni,
+ {{0x3eadc761,0x63a8c02c,0x25a94762,0x4dda81c6}}, // lget_, budn, dual_, _לחנו,
+ {{0x6f040668,0x3ce8999e,0x6282c763,0xbebb0168}}, // tkic, जारे_, yboo, _njës,
+ {{0x3eadb7c0,0x7d1c011f,0x628281e0,0xdce60da8}}, // nget_, _smrs, xboo, _vukč,
+ {{0x6f0400d2,0x316d8388,0xf6510019,0x7989838a}}, // [3660] rkic, _nuez_, ہئے_, _nwew,
+ {{0x6f044764,0x6282c3b6,0x600a0791,0xd88400d7}}, // skic, wboo, _гном_, _تهدی,
+ {{0xc9530039,0x798982e9,0x5fe04765,0xe0fb025f}}, // _רמת_, _awew, _पछिल, _הליל,
+ {{0xcfc380c8,0x25a90748,0x798983f7,0xe2979541}}, // ্মান, bual_, _bwew, зац_,
+ {{0xd9468aac,0x3eadc766,0x6282c767,0x237f005c}}, // педи, dget_, rboo, _čuje_,
+ {{0x673b4768,0x2d804769,0x69c403a8,0x7bc3c76a}}, // čuje, mpie_, _mpie, _opnu,
+ {{0x6282a2dd,0x2b4681e0,0xab648192,0x2ca7016a}}, // pboo, ploc_, grüß, ónde_,
+ {{0x3ead8f06,0x3da69033,0x69c40dee,0xb2268012}}, // gget_, проб, _opie, _ембл,
+ {{0xa0a68084,0x96349fb4,0x5d549182,0x316d83c9}}, // _наад, ьниц, мкит, _guez_,
+ {{0xcb98835f,0x63a8822b,0x48d70264,0xa61701c6}}, // ової_, tudn, _সক্র, פקיד_,
+ {{0xa9f7000d,0x5335151c,0x6efc8036,0x68e88366}}, // ुन्छ_, дент, _débr, _indd,
+ {{0x63a8850a,0x68fac76b,0x61e09502,0xcb0b1e11}}, // rudn, _hotd, _krml, сход_,
+ {{0x76460009,0xaefb0229,0xc6938e82,0xa6b40264}}, // _nyky, _diùl, _נאס_, ুযায়,
+ {{0xa3df0935,0xbebb00f1,0xd90f0077,0x61e080c3}}, // तिम_, _njër, اید_, _mrml,
+ {{0xc7c40db6,0x4fc40a9f,0xdb1cbd42,0x1cbb84de}}, // нсти, нста, _aprí, _המוע,
+ {{0x6d48c76c,0x25a9476d,0xdb1c81a8,0x64430035}}, // llda, tual_, _bprí, żnik,
+ {{0xa63400e8,0x69dd8187,0xf1b118a9,0xe3b8807e}}, // [3670] енті, _àsex, _जमिन, ltı_,
+ {{0x4d980a41,0x64c7052a,0x291e8db1,0x68fa8799}}, // якую_, रजेश, _nmta_, _notd,
+ {{0x28b21e19,0xe3b883bf,0x3ead8106,0x25a930e3}}, // _जाति, ntı_, yget_, sual_,
+ {{0xf1b1000f,0x290cc76e,0x6f1d0192,0xa3d481a2}}, // _जमान, _alda_, _umsc, _हनन_,
+ {{0xeab081a8,0x672d0822,0x9647259a,0xa9f981d0}}, // اعم_, mnaj, _хэнд, ्नेछ_,
+ {{0xd0450201,0xe3b88457,0xbebb00f1,0x3e7c01d0}}, // _sahə, ktı_, _gjër, rátů_,
+ {{0xb8d5abef,0xdfce80f7,0x656f476f,0x60d70df6}}, // _झा_, ليو_, _huch, _maxm,
+ {{0x656f4770,0x6d5a8640,0xe5799ddf,0xfbd20039}}, // _kuch, emta, ози_, _בתל_,
+ {{0x51878098,0x3eadbfcc,0x61e0812b,0x7ae9c771}}, // _хуба, rget_, _grml, _inet,
+ {{0x656f1220,0x69dd0353,0x7afb838e,0x2f23826b}}, // _much, dvse, _hout, _bègè_,
+ {{0x656f4772,0x69c44773,0x7afb8247,0xfaa60993}}, // _luch, _spie, _kout, мано,
+ {{0x6d48c774,0x6f03002e,0x7afb8009,0x656f4775}}, // alda, _înch, _jout, _ouch,
+ {{0x657d01e9,0x60d70085,0x68e88114,0xd706804a}}, // _ntsh, _baxm, _yndd, _інше_,
+ {{0x6606804a,0xdce280e1,0xbcfb0144,0xa3c2170c}}, // økke, _zvoľ, _mués, ्बम_,
+ {{0x7ae9811b,0x1867164f,0xdb1c80e1,0xa067030d}}, // _onet, дари_, _sprí, дара_,
+ {{0x656f4776,0x7afbc777,0x69c01238,0x7ae985ee}}, // _buch, _nout, çmes, _nnet,
+ {{0x656f2423,0x2bd580c2,0xf7430993,0x60d700f5}}, // [3680] _cuch, धिका, тето, _faxm,
+ {{0x656f0a7a,0x7ae98217,0x2d80002f,0x80bf8035}}, // _duch, _anet, rpie_, ल्मे,
+ {{0x7afbadb2,0x6d4380b9,0x628601c0,0x8e862606}}, // _bout, _mkna, obko, _огне,
+ {{0x2bf68158,0xe51dab62,0x7afbc778,0x4e0d8441}}, // ומען_, योति_, _cout, िनाई_,
+ {{0x7afbc779,0xfe420065,0xc69304de,0xb17b013c}}, // _dout, _شکری, _באר_, rvåg,
+ {{0x60c180b9,0x7ae9c77a,0xe3b8811c,0xbebb03ed}}, // _cblm, _enet, ytı_, _ujër,
+ {{0x7afbc77b,0xd01080f7,0x3320002a,0xe3b88085}}, // _fout, طلة_, _omix_, xtı_,
+ {{0x6d43c77c,0x7afba9d5,0x7ae9c77d,0xd75781a8}}, // _akna, _gout, _gnet, وجيا_,
+ {{0x6604373c,0x68fac65b,0xa3b089c8,0x3494b383}}, // nyik, _totd, टून_, напр,
+ {{0x672d0289,0x7afb8247,0xe3b88214,0xbcfb0661}}, // znaj, _zout, ttı_, _huér,
+ {{0x7afbc77e,0x2be10697,0x60d701b4,0x48eb80c2}}, // _yout, फिया, _raxm, चारो_,
+ {{0xe3af817e,0x38638019,0xdd8f803d,0xe3b8807e}}, // گری_, _újra_, _چون_, rtı_,
+ {{0x6ef5803e,0x672d807a,0xdd8f8a47,0x6abd007b}}, // _náby, čajt, _نون_, rfsf,
+ {{0xe3af80d5,0x656f0491,0xe3b882d0,0x61e48198}}, // دری_, _ruch, ptı_, äily,
+ {{0x656f477f,0xb88201ac,0x628608dc,0xc7b80a41}}, // _such, _číta, bbko, мёт_,
+ {{0xdcc70f21,0xa3db82f1,0x63a5016b,0x656701e9}}, // रज्ञ, ठिर_, vrhn, _sijh,
+ {{0xa3d69299,0x672d3732,0x7afbc780,0x5d554781}}, // [3690] सिक_, rnaj, _rout, екат,
+ {{0x7afbc782,0x88c4009a,0x2fd303a8,0xdb020658}}, // _sout, ęści, _tsxg_, mulá,
+ {{0x7afbc783,0xfe700117,0x5695151b,0x656f4784}}, // _pout, ادہ_, _пант, _wuch,
+ {{0x224910fe,0x656f4785,0xd6d98035,0x3f8301b9}}, // _kyak_, _tuch, koła_, mpju_,
+ {{0xe664259a,0x7bda0088,0x7ae9807a,0x2fc580b9}}, // _атро, _štut, _vnet, _splg_,
+ {{0x23690282,0xc9849d7b,0xe8f88d13,0xe9d886cf}}, // _liaj_, туци, млі_, ькі_,
+ {{0x7afba24b,0x26d94786,0xf7458293,0xbcfb0144}}, // _tout, _kaso_, неко, _fuér,
+ {{0x236901c5,0x26d90102,0x7ae9c787,0xdb024788}}, // _niaj_, _jaso_, _unet, kulá,
+ {{0x2d998065,0x26d94789,0x2d8b82be,0x2bd18a27}}, // ése_, _maso_, èce_, _तनहा,
+ {{0xf8078364,0xdb022792,0x26d9478a,0x00000000}}, // _очен, dulá, _laso_, --,
+ {{0x8b2600af,0x8cc11199,0x798d38b3,0x224902d0}}, // едне, र्यो, _awaw, _ayak_,
+ {{0x28b20576,0x236901c5,0x26d9350c,0x798d2578}}, // _जाहि, _ciaj_, _naso_, _bwaw,
+ {{0xdca59383,0x64a59957,0xdb020f09,0xf1e283b6}}, // _зали, _зала, gulá, पियन,
+ {{0x2618823c,0x80bb000c,0x656fc533,0x2249478b}}, // _बेटी_, श्ले, íchu, _dyak_,
+ {{0x26d9478c,0x6724478d,0x79828035,0x3e750133}}, // _baso_, tiij, zpow, _ụta_,
+ {{0x6d419a9f,0xdb1c826f,0xdcba0768,0xfe7981d0}}, // lola, _oprá, ैज्ञ, nců_,
+ {{0xe5a30aac,0x6da31a34,0xdce381a1,0xada302df}}, // [36a0] лити, лита, _linč, латл,
+ {{0x6d41c78e,0xaac1000d,0x02c103b6,0x8aff09ab}}, // nola, ष्ठक, ष्ठभ, _duƙu,
+ {{0x26d91c7d,0x660436e2,0x6616009a,0x25ad8216}}, // _faso_, syik, szyk, huel_,
+ {{0x6d41c78f,0xbcfb087a,0x67240079,0xe29280f7}}, // hola, _quér, qiij, اذا_,
+ {{0x6d41be4f,0x61e20253,0x68fe4790,0xdced04b7}}, // kola, _šolj, _kopd, ssaġ,
+ {{0x6b51816d,0x6d418573,0x63ba90cc,0x2a6d826b}}, // råga, jola, yttn, _egeb_,
+ {{0x6d41c791,0xfaa681e2,0xdce38140,0x7cf68162}}, // dola, _паво, _cinč, _târz,
+ {{0x61e410ab,0x6e2d8b80,0xb1839c18,0xdce38b80}}, // _oril, _žaba, šťan, _dinč,
+ {{0x25adb0a4,0x6d41acc4,0xe2a6807b,0x6d5e1867}}, // guel_, fola, æðum_, ompa,
+ {{0x6d41c792,0xe9d780e8,0x6b9c06ec,0x798d0a03}}, // gola, _яку_, _kvrg, _rwaw,
+ {{0x66e6a4a4,0xdb1c800d,0x22490239,0xf8b39101}}, // _пожа, _zprá, _syak_, _תשע_,
+ {{0x201800b9,0x61e44793,0x60dab5c3,0xdb024794}}, // dzri_, _bril, _hatm, tulá,
+ {{0x6d41c795,0x23694796,0x69068125,0xc3338039}}, // bola, _viaj_, _aðei, רור_,
+ {{0x6d41b5dc,0xa3c2123a,0xd0488085,0x2eeb0036}}, // cola, ्बा_, _sadə, _sncf_,
+ {{0x26d9040e,0x23690282,0x61e4037b,0xb4ac9834}}, // _paso_, _tiaj_, _eril, गली_,
+ {{0xdb0203b0,0xd6d80cf9,0xf7700019,0x61e42ca0}}, // pulá, нту_, صاف_, _fril,
+ {{0x6722822e,0x7aed4797,0xe573080b,0xe4a715b1}}, // [36b0] _mmoj, _inat, اطر_, ерео,
+ {{0x6fe085b3,0xaab12bef,0x63a8c798,0x81d68264}}, // पटां, _झाँक, ordn, িমা_,
+ {{0xdb1c8a56,0x26d93e97,0x7aed007b,0x6b9a888b}}, // _sprá, _taso_, _knat, nstg,
+ {{0x6d41c799,0x66fb01ab,0x8aff00fc,0xa5a18035}}, // zola, _एकटक_, _tuƙu, _खिलौ,
+ {{0x7aed479a,0xe4a7230e,0xdce3850b,0x25ad8036}}, // _mnat, _прво, _sinč, xuel_,
+ {{0x7f4d03e7,0xeafe0cf0,0x4438008e,0x60dac79b}}, // llaq, _उक्त_, _fxr_, _catm,
+ {{0x6d418a9e,0xe7399577,0x644a8186,0xaab106a7}}, // vola, мек_, _nyfi, _झांक,
+ {{0x25adc79c,0xdce39fa6,0x6455479d,0x6d41c79e}}, // tuel_, _vinč, _izzi, wola,
+ {{0x6d41c79f,0x6f09810b,0x60dac7a0,0x200980eb}}, // tola, rkec, _fatm, ņai_,
+ {{0x7aed47a1,0x270e159a,0x25ad957a,0x25a9020d}}, // _anat, _सत्र_, ruel_, iral_,
+ {{0x63a880d2,0x644a8114,0x25a900e1,0x6d588609}}, // grdn, _cyfi, hral_, _dhva,
+ {{0x6d41c7a2,0x74c10075,0x25a90b17,0x68fe02c4}}, // sola, र्तृ, kral_, _sopd,
+ {{0x6d41c7a3,0x25adc7a4,0x1c1e035a,0x63a8808e}}, // pola, quel_, _येईल_, ardn,
+ {{0x7e7d47a5,0x6d4181b4,0xe5a2954f,0xddc28115}}, // _afsp, qola, _тиши, _ugoš,
+ {{0x21670b9c,0xa9671878,0x764b8114,0x673b0390}}, // тири_, тира_, _hygy, čujn,
+ {{0x61e447a6,0x69c9815f,0x7c2e8333,0x84648081}}, // _tril, _opee, úbre, _съче,
+ {{0x61e4002e,0x69c981c0,0x6b9c0289,0x0e6647a7}}, // [36c0] _uril, _npee, _svrg, ткан,
+ {{0x7aed003a,0x25762320,0xd9100117,0xc00617c8}}, // _znat, _væl_, _ایس_, _апок,
+ {{0xeb9747a8,0xa3df28b3,0x7d03016d,0x6b51816d}}, // вих_, तिष_, önsa, rågn,
+ {{0x130a8190,0x60daa2f8,0xfaa68779,0x2aeb114f}}, // ьней_, _satm, тадо, टाएँ_,
+ {{0xceb30158,0xdb020bc5,0x827700be,0x161a064a}}, // _דיר_, pulæ, דענע_, _नेचर_,
+ {{0x92ce00ab,0x6b588531,0x8cc104c5,0x6722882c}}, // রায়_, díge, र्दो, _smoj,
+ {{0xdbcc007b,0xd00f80f7,0xc01000ff,0x2d9d82d6}}, // _góða, يله_, _đuổi_, _avwe_,
+ {{0x6d58803a,0xa3df2cdd,0xceb40085,0x63a880fe}}, // _shva, तिर_, ciət_, vrdn,
+ {{0x32469ac9,0x94868162,0x60da9238,0x2127827d}}, // _реаг, кынд, _tatm, binh_,
+ {{0x6ad292ee,0xb4ac8d14,0x44f4813a,0x61e2829a}}, // _ससुर, गले_, упос, mvol,
+ {{0xe9ff8028,0x61e2c7a9,0x929547aa,0xdb023b13}}, // _thảo_, lvol, ланц, kulä,
+ {{0xdb9b010f,0x6722822e,0xf6268ab2,0x7e7bc7ab}}, // וסטר, _umoj, _идео, ccup,
+ {{0xf2d3893f,0x61e2907f,0x69dd81fa,0xceb381c6}}, // בער_, nvol, _áset, _כיף_,
+ {{0x6d58803b,0x25a90085,0xa8a4017c,0x3494c7ac}}, // _uhva, vral_, ирск, _сакр,
+ {{0xe7e58128,0x7f4d02a6,0x40350073,0xe8940110}}, // किया_, tlaq, уевс, рась,
+ {{0x7aed47ad,0xb7db83c8,0x6d4747ae,0x7528ae63}}, // _unat, וקלי, _skja, lidz,
+ {{0x273701cd,0x2d8f02be,0xe6640878,0x7e55004a}}, // [36d0] għna_, ège_, атфо, уває,
+ {{0x58d50009,0x61e28696,0x2ba99869,0x7528a795}}, // _соот, dvol, _किमा, nidz,
+ {{0x69c9c7af,0xdd86804e,0x7a0a81a9,0x2127827d}}, // _spee, _گو_, _lētā, vinh_,
+ {{0x25a947b0,0x7b3c812b,0x6b518106,0x6f160ebf}}, // pral_, jčud, rågo, chyc,
+ {{0xdd869fbe,0xf1bf47b1,0x28c0000f,0x8d874184}}, // _دو_, ntá_, श्वि, _шунд,
+ {{0x6f0d47b2,0x26c685ee,0xb4ac8072,0xadc38032}}, // lkac, _aboo_, गलो_, _gbẹk,
+ {{0xf7718872,0x6d4515e6,0x645547b3,0x290147b4}}, // حات_, moha, _uzzi, _moha_,
+ {{0x6d452916,0xa3c22b51,0xdb0580f7,0x6f0d47b5}}, // loha, ्बर_, rthó, nkac,
+ {{0x752880dd,0x62998077,0x645c808b,0x7bca8a03}}, // fidz, mawo, ýrin, _apfu,
+ {{0x6299c7b6,0x7c2805f3,0x6b58c7b7,0x63730372}}, // lawo, _ždri, ríge, _aħni,
+ {{0xc2091101,0x6f0d0326,0xf1bf02df,0x5edb8264}}, // _צה_, kkac, etá_, ভাবে,
+ {{0xa77a80be,0x26c68133,0x6299c7b8,0x2f11826b}}, // _טרעפ, _gboo_, nawo, _fága_,
+ {{0xed4ec44a,0xa3d381fe,0x6d4547b9,0x6729c702}}, // _го_, _सैर_, koha, miej,
+ {{0x6d4547ba,0x3ea200dd,0x67298110,0x75288267}}, // joha, _sdkt_, liej, cidz,
+ {{0x37c380c8,0xab2a0009,0x26dd80a4,0x6d45059e}}, // ্মকর, _пока_, _kawo_, doha,
+ {{0x67298d12,0x236d809a,0x26dd84b9,0x26df8084}}, // niej, _niej_, _jawo_, nduo_,
+ {{0x25bf8012,0x6609ae43,0xdce38029,0xe7370364}}, // [36e0] ntul_, nyek, _zinā, ует_,
+ {{0x62960637,0xa3c7a342,0x798402d5,0x25bf8162}}, // _heyo, _उहा_, _itiw, itul_,
+ {{0xdb023c40,0x6e2d8bcf,0x6729866f,0xb17b0338}}, // pulä, _žabl, kiej, nvån,
+ {{0xf8c00a27,0x26ddc7bb,0xa3e48128,0x6f0d47bc}}, // श्रय, _nawo_, नित_, ckac,
+ {{0x6d4547bd,0xf1268ca0,0x25bfaf9f,0xe81c8035}}, // boha, лько, jtul_, _भेजा_,
+ {{0x61e2b3fc,0x26d2005d,0xbebd8110,0x6d450034}}, // rvol, leyo_, siūl, coha,
+ {{0x3d0783bb,0x61e294f9,0xa3df0743,0x6d5c47be}}, // ाउने_, svol, तिः_, _ihra,
+ {{0x26d247bf,0x61e29384,0x758aa3e7,0x75288035}}, // neyo_, pvol, нсов_, widz,
+ {{0x9d149bc1,0x26dd9cee,0xa0b68a47,0x1634c7c0}}, // идич, _dawo_, _محاص, репя,
+ {{0x88bc801b,0x6aa4046d,0x26d202c4,0x02039a47}}, // _změn, _adif, heyo_, _узун,
+ {{0x9b5900a9,0xef868615,0x61d4b738,0x25bf8162}}, // киот_, _слеп, _धनुष, atul_,
+ {{0xa3a9800f,0x667680f7,0x6609c7c1,0x290147c2}}, // गंज_, _إدار, byek, _soha_,
+ {{0x25bf802e,0x6d5c2cc7,0x6d4504a7,0xf2d38158}}, // ctul_, _ohra, yoha, זער_,
+ {{0x6aa41883,0x60de47c3,0x863781c6,0x26ddb08f}}, // _edif, _capm, _גאדג_, _zawo_,
+ {{0x6d4503a7,0xf1bf1c18,0x7984019d,0x19580009}}, // voha, stá_, _etiw, уары_,
+ {{0x6458c7c4,0x2ba980a5,0x656e0c41,0x6b5647c5}}, // _izvi, _किता, _aibh, pága,
+ {{0x6f0d47c6,0x6d4547c7,0x291800f7,0x6d5c0c5e}}, // [36f0] rkac, toha, bhra_, _bhra,
+ {{0xb0b20063,0x6d5c0219,0x67298084,0x291847c8}}, // _जाएग, _chra, ziej, chra_,
+ {{0x62960010,0x6299c7c9,0x644e0122,0x3a75a4c8}}, // _yeyo, tawo, _cybi, алер,
+ {{0x98a9805c,0x6f028079,0x6d4529e2,0x656e0219}}, // đač_, _nooc, soha, _eibh,
+ {{0x60de07d9,0x62999410,0x6d5c032f,0xf99f06c0}}, // _yapm, rawo, _fhra, nsè_,
+ {{0x6d5c0c41,0x26ddc7ca,0xbebb03ed,0x656e47cb}}, // _ghra, _sawo_, _njëz, _gibh,
+ {{0xfe718307,0xdb1ca32f,0xb8cd86ae,0x629986a9}}, // يدة_, _språ, _कय_, pawo,
+ {{0x88bc81d0,0x6609810c,0x661b811b,0xaefb0706}}, // _uměn, tyek, tzuk, _dhùg,
+ {{0x629647cc,0x7afd01a1,0x67298196,0xa8a798a3}}, // _reyo, djst, riej, _брек,
+ {{0x25bf802e,0x629647cd,0x661b9bb7,0x656e022c}}, // rtul_, _seyo, rzuk, _xibh,
+ {{0x25bf8012,0x661b8063,0x5ea780ab,0x26dd9341}}, // stul_, szuk, ক্ষে, _tawo_,
+ {{0x25bf8012,0x3947c7ce,0xda66280f,0xb17b016d}}, // ptul_, lons_, ивни, rvån,
+ {{0x82d6812a,0x80df00ab,0xbbd10651,0xaefb0706}}, // בורג_, মান্, सबुक, _liùs,
+ {{0x3947c7cf,0x26d201f6,0x66068009,0x25ad80e1}}, // nons_, weyo_, äkke, mrel_,
+ {{0x6f02c7d0,0x7d0380f7,0x5f0647d1,0xdfd200f7}}, // _yooc, _ions, азва, غير_,
+ {{0x1e86024f,0x06860470,0x6d5c47d2,0x61e98493}}, // рлам, рган, _shra, _orel,
+ {{0x7d03c7d3,0x78bb005c,0xa5260a8e,0xa1560098}}, // [3700] _kons, đuvr, амад, _външ,
+ {{0x64a647d4,0x26d247d5,0xdca60374,0x3947c7d6}}, // ража, seyo_, ражи, jons_,
+ {{0x61e98333,0xe29a81e2,0x7d0390b6,0x93469374}}, // _arel, вае_, _mons, анже,
+ {{0xf99f06c0,0xdee602ee,0x66e61509,0x7d03c7d7}}, // zyèm_, _томи, _тома, _lons,
+ {{0x644e0063,0x6d5c0224,0x3947c7d8,0xd90d826a}}, // _wybi, _thra, fons_, کیل_,
+ {{0x97a6a804,0x6d5c3933,0x656e01e4,0x25a007c0}}, // арил, _uhra, _uibh, msil_,
+ {{0xf0930051,0x64a302cb,0xdfd08013,0x03a30dbd}}, // חנו_, _фаса, سية_, _миро,
+ {{0x61e98a35,0xbb438012,0xa3e48bbc,0x3ec38d15}}, // _frel, _деск, निस_, осьб,
+ {{0xc6f79006,0x61e9c7d9,0xe0da97d6,0x394787f1}}, // рных_, _grel, _ива_, bons_,
+ {{0xdceb8087,0x7ae2b3bc,0x8fa69bdc,0x81cc0264}}, // _rugă, ldot, _важе, রটা_,
+ {{0x25a00201,0x2f150106,0xa3df0035,0x69c2b5da}}, // hsil_, _låga_, तिए_, ltoe,
+ {{0x7ae2b56e,0x25a044cf,0x98a301a9,0xaefb0706}}, // ndot, ksil_, dijā_, _liùr,
+ {{0x7d03c2af,0x69c2c7da,0xdd8e8019,0x273a8ec3}}, // _fons, ntoe, _کوڈ_, cīna_,
+ {{0x29c28028,0x7ae28198,0x2d050072,0x7d03a937}}, // _mưa_, hdot, _शकेल_, _gons,
+ {{0x753d8065,0x6458bbac,0x69c28009,0x61e247db}}, // észe, _uzvi, htoe, _šolt,
+ {{0x6d48c7dc,0x25a00609,0xa3c21513,0x7d03c7dd}}, // loda, fsil_, ्बई_, _zons,
+ {{0x629d47de,0x394780e7,0x7ae2ad08,0x7bdc47df}}, // [3710] maso, yons_, ddot, _asru,
+ {{0x44f40705,0xa8a71290,0x3947c5e6,0x30a718c6}}, // опус, _трик, xons_, _трив,
+ {{0x61e982a5,0x98a300eb,0x394780e7,0x9cd78039}}, // _srel, cijā_, vons_, שובה_,
+ {{0xfaff020f,0x629d02c4,0x06ac80c8,0x2ba9923a}}, // një_, naso, ক্তি, _किसा,
+ {{0xa2bc29b7,0x249847e0,0x672d0010,0x3947c7e1}}, // ष्ट्, _ferm_, miaj, tons_,
+ {{0x629d47e2,0x61e98da8,0x660d0542,0x88bc801b}}, // haso, _vrel, myak, _uměl,
+ {{0x3947c7e3,0x6d48c7e4,0xb8ff931d,0x660d2a96}}, // rons_, doda, _तस_, lyak,
+ {{0x4975860a,0x7d03c7e5,0xf83804de,0x28a70b04}}, // _глас, _sons, ינות_, कृति,
+ {{0x660d4450,0xa3df0aad,0x7d03c7e6,0x629d47e7}}, // nyak, तिक_, _pons, daso,
+ {{0x6d48803a,0x7bc181ec,0x660d02d5,0x7ae0b6ba}}, // goda, ttlu, iyak, _zamt,
+ {{0xe29a031f,0x04458e02,0x94078085,0x660d00b4}}, // лан_, сенн, _yenə_, hyak,
+ {{0x2905c7e8,0xa3e8000f,0x2d85219f,0xf1bb800f}}, // _hola_, बित_, _člen_, _ईमान,
+ {{0xfce5895a,0x6d48c7e9,0x68e1c7ea,0x7bc1c7eb}}, // _голо, boda, _kald, stlu,
+ {{0x7d1ac3a6,0x61e20353,0x68e380f1,0x0fda0e11}}, // chts, _šols, ndnd, льбы_,
+ {{0x8cca03db,0x09d4873c,0x290590e5,0xb4b3816f}}, // स्फो, _धन्य, _mola_, टली_,
+ {{0x290580e4,0x629d0511,0x98a300eb,0x6b5d00e7}}, // _lola_, caso, rijā_, tége,
+ {{0x660d01bf,0x60d5026c,0x7c248326,0x7ae08366}}, // [3720] gyak, bezm, _ƙirg, _ramt,
+ {{0x2ed485e8,0x25a047ec,0x7ae0c7ed,0x2905811e}}, // _दस्त, rsil_, _samt, _nola_,
+ {{0x6b5d0117,0x7ae08052,0x940c8201,0x4adc0074}}, // sége, _pamt, fadə_, _बसाव,
+ {{0x1db78540,0xb21b008b,0x734980e8,0x2b5f80ff}}, // _आमंत, mbær, ачів_, _chuc_,
+ {{0x68e1c7ee,0x2905a362,0xd9108416,0x59f884fa}}, // _bald, _bola_, ویر_, реля_,
+ {{0x69c29de6,0xd9438198,0xa80282d0,0x7ae0c7ef}}, // rtoe, печи, şıml, _wamt,
+ {{0x6d488f20,0x69c29e7e,0x2905c7f0,0x629d2290}}, // voda, stoe, _dola_, yaso,
+ {{0xdc3700be,0x69c289ff,0x6d48c7f1,0x6f060af8}}, // _האלט_, ptoe, woda, _jokc,
+ {{0x6b58c7f2,0x68e1c7f3,0x6d48c7f4,0x6282ad81}}, // lígo, _fald, toda, mcoo,
+ {{0x68e1c7f5,0x28c480d4,0x629d0b3f,0xa3bc001b}}, // _gald, ल्शि, waso, _आमा_,
+ {{0x6d48ac28,0x629d3721,0x8cca0035,0x2458b23a}}, // roda, taso, स्यो, рань_,
+ {{0x2ba98996,0x23a9800f,0x23600282,0x660d00a4}}, // _किरा, _किरद, _chij_, yyak,
+ {{0x629d071e,0x2905c6e1,0x6d488b24,0x63ab009a}}, // raso, _yola_, poda, ągni,
+ {{0x672d009a,0x463b80be,0x629d0102,0x47d200ab}}, // wiaj, _געדע, saso, হিনী,
+ {{0xaac89d01,0x629d47f6,0x4ac88ebf,0x5f940012}}, // रभाक, paso, रभाव, зист,
+ {{0x6d4e1953,0x660d3d9b,0x6b5d0061,0xec3781c6}}, // _akba, tyak, ségb, _לאור_,
+ {{0x645c4022,0xe8d90870,0x23668699,0x246e811c}}, // [3730] _azri, _atụ_, dmoj_, ləm_,
+ {{0xe29f007b,0xe6c00668,0x61ed47f7,0x660d47f8}}, // lað_, čišć, _iral, ryak,
+ {{0x2905ba69,0x8cca80d4,0x660d04bf,0x3e6f8214}}, // _rola_, त्यो, syak, _kötü_,
+ {{0x61ed0289,0xe29f0125,0x325590f8,0xd6db47f9}}, // _kral, nað_, овер, _сто_,
+ {{0x290581df,0x28c0023c,0x7c87373a,0x99d780f7}}, // _pola_, श्कि, _губе, إتصا,
+ {{0x6440009a,0x68e19b37,0xa969818b,0x21699509}}, // _śmie, _qald, шила_, шили_,
+ {{0x68e1c7fa,0xe29f007b,0x2905c7fb,0xdbf1801b}}, // _vald, kað_, _vola_, _přít,
+ {{0x68e1c7fc,0x0d8585c2,0x7bc547fd,0x777ab11d}}, // _wald, олин, mthu, _dutx,
+ {{0x68e180ad,0x7bc53a20,0xe5768e11,0x645c00b9}}, // _tald, lthu, озы_, _zzri,
+ {{0xb4b3b64b,0x7989c7fe,0xdb1c83ba,0x628447ff}}, // टले_, _itew, _sprø, _sfio,
+ {{0x61ed4800,0x7bc54801,0x777a811e,0x394080f7}}, // _aral, nthu, _gutx, éise_,
+ {{0x61ed141f,0xbfb492c8,0xd2508065,0x44211a1f}}, // _bral, _رحمت, _آنے_, izh_,
+ {{0x4e78845b,0x64a5bdc4,0xdca597c8,0x61ed0362}}, // _احمد_, _дала, _дали, _cral,
+ {{0xd56787f2,0xdc698087,0xe3b18742,0x7bc50687}}, // _утеп, _калд_, _قرب_, kthu,
+ {{0xe7ec8e18,0xa06a0f04,0x186a14b8,0x61ed4802}}, // झिया_, рава_, рави_, _eral,
+ {{0x61ed4803,0x6aa98c53,0x7c2d8300,0x81d08264}}, // _fral, _ndef, _ƙirƙ, ষিত_,
+ {{0x61ed2590,0x7ae44804,0xd7f8002e,0x44210dc1}}, // [3740] _gral, _iait, ţă_, ezh_,
+ {{0x7ae44805,0x2716864a,0x6aa9826b,0x7d070b80}}, // _hait, _तत्र_, _adef, _cojs,
+ {{0x7ae44806,0x7af60f91,0x8b2680e8,0x6f0602f7}}, // _kait, _knyt, одже, _tokc,
+ {{0x7ae40102,0x741301a8,0xfaff0168,0xa80282d0}}, // _jait, بوما, hmën_, şıkl,
+ {{0x7ae44807,0x69c60065,0x6aa9831d,0x6282c808}}, // _mait, ntke, _ddef, rcoo,
+ {{0x7ae44663,0x3ea04809,0xed570d0e,0x6282c80a}}, // _lait, lait_, _моя_, scoo,
+ {{0xea0100ff,0x6b5d4089,0xfbc33a2d,0xd7fa8c6e}}, // _đậu_, héga, _обро, рул_,
+ {{0x7ae4480b,0x3ea0480c,0xd838882c,0xc6f8a466}}, // _nait, nait_, _neče_, сних_,
+ {{0xe29f007b,0xaf079a3c,0xb17b004a,0xb0ca1c4f}}, // vað_, تقيم_, rvåk, िभाग,
+ {{0x61ed00f1,0x38c887d2,0xdb218019,0x7af60039}}, // _rral, _داری_, ütör, _anyt,
+ {{0x7ae4480d,0xe29f0125,0x06ac80ab,0x98a30084}}, // _bait, tað_, ক্ষি, niją_,
+ {{0x06658077,0x246e8201,0x61ed480e,0x7ae40cac}}, // _کامپ, rəm_, _pral, _cait,
+ {{0x7ae4480f,0x3ea026b9,0xe29f007b,0x41e4102a}}, // _dait, dait_, rað_, міра,
+ {{0x7bc50428,0xe29f008b,0x25a68299,0x752a913b}}, // ythu, sað_, _ivol_, _umfz,
+ {{0xbbc903bb,0x7d070353,0xe29f008b,0xa2db816f}}, // _रहेक, _rojs, pað_, _नसल्,
+ {{0x61ed1e72,0x7ae44810,0x06ac80ab,0xd6ac80ab}}, // _tral, _gait, ক্রি, ক্রয,
+ {{0x690b0722,0xaded81af,0xdb02016b,0x61ed4811}}, // [3750] _qües, जियन_, nulý, _ural,
+ {{0x929d809a,0x7ae40102,0x7bc54812,0xa2c10894}}, // _poło, _zait, tthu, _वाड्,
+ {{0x7ae4035a,0x3ea04813,0xb4ab009a,0x7d0704ef}}, // _yait, bait_, _गयी_, _vojs,
+ {{0x6abd2280,0x442103e7,0x61290858,0x7d070035}}, // ngsf, rzh_, džli, _wojs,
+ {{0x237f02a5,0x68e50009,0x7bc509ff,0xa3e1941b}}, // _čuju_, _kahd, sthu, _नैन_,
+ {{0x673b003a,0x290302f7,0x68e54269,0x92d70264}}, // čuju, njja_, _jahd, সায়_,
+ {{0xa2959ae5,0x68e54814,0x98a301e2,0x657d08dc}}, // _наві, _mahd, ciją_, _hush,
+ {{0x657d4815,0xdfce803d,0x753a8102,0x7b3c81a1}}, // _kush, ايي_, entz, nčul,
+ {{0x64469010,0xd838b1a7,0x80dc00d4,0x7ae44816}}, // şkil, _reče_, _फसले, _rait,
+ {{0x7ae44817,0x58bb8bbe,0x6e200074,0x68e54818}}, // _sait, _خارج_, _ümbe, _nahd,
+ {{0x6d890038,0xd838850b,0x7ae42ff6,0xfaff0168}}, // _hľad, _peče_, _pait, rmën_,
+ {{0x65750326,0x96b8c6a1,0x62810035,0x2fc78122}}, // _lizh, _душу_, ślon, mtng_,
+ {{0xd83890d3,0x3ea04819,0x213100dd,0x2bd218a9}}, // _veče_, vait_, fizh_, _सहभा,
+ {{0x645d8086,0xeb18864a,0xdd920065,0xeb08943e}}, // əsin, _दत्त_, ہور_, _वक्त_,
+ {{0x7ae4481a,0x2fd3002a,0x442184e1,0x3ea04042}}, // _tait, _cpxg_, _åh_, tait_,
+ {{0x657d297b,0x673b98c2,0x77698144,0x0d860bda}}, // _bush, nnuj, nmex, злан,
+ {{0x3ea0174b,0xf1da809a,0xfe43481b,0x68e5481c}}, // [3760] rait_, _मैंन, енто, _fahd,
+ {{0x657d481d,0x3ea0481e,0xd5af0591,0x6b56041c}}, // _dush, sait_, افل_, dági,
+ {{0x3c21811b,0x3ea0481f,0xa3e48fea,0xa87b0039}}, // _eгvv_, pait_, निए_, _לאיר,
+ {{0x657d4820,0x98a30110,0x2b81c821,0x63be80e8}}, // _fush, riją_, _móc_, _åpne,
+ {{0xceb3093f,0x657d4822,0x673b817f,0xd6c380ab}}, // _איר_, _gush, dnuj, ্যায,
+ {{0x28c00778,0x6455026f,0x2d8281d0,0xf53f0338}}, // श्चि, _fyzi, íkem_, _tvåa_,
+ {{0x04430ca0,0xbbda801b,0x2b818129,0x48fe8327}}, // _зерн, _बनेक, _nóc_, लाबो_,
+ {{0x1ae39198,0x543b0039,0x671c0eed,0x657d00fc}}, // _посм, _העלא, नसिक_, _yush,
+ {{0x4343997b,0x6e242c91,0x657d0df6,0xbb438103}}, // _четв, nzib, _xush, _четк,
+ {{0x47d480ab,0x2b818129,0x6b518338,0x6e2405ee}}, // থিবী, _bóc_, bågs, izib,
+ {{0xdbf1801b,0x2b8180ff,0x69c0023e,0x4ae08072}}, // _přát, _cóc_, àmen, _नसाव,
+ {{0x1dac4823,0x60c4973d,0xf6e580e8,0x6aa2804f}}, // _चिंत, đimu, _оцін, naof,
+ {{0x6abd03a6,0x6f044824,0x872581a8,0x9f47010c}}, // rgsf, djic, _تعلم, _éné_,
+ {{0xb4ab009a,0x61ed8201,0xb4e6000d,0x67d51775}}, // _गये_, çilə, भयो_, могу,
+ {{0x3d000a16,0x2b81801c,0xd7f842c4,0xb9054825}}, // राने_, _góc_, пут_, _नस_,
+ {{0x443f003a,0x63ba858b,0x7d0ac826,0x68e5022b}}, // _ću_, mutn, _hofs, _wahd,
+ {{0xa3e49a46,0x1bea8ee6,0xa2a20105,0x6aa28187}}, // [3770] निक_, टिवल_, _ख़त्, daof,
+ {{0x04670676,0xf1b20035,0xe61f0114,0x798d4827}}, // птем, _जितन, _ffôn_, _itaw,
+ {{0x69100006,0xedd9801b,0x1e188697,0x23810196}}, // _päev, _भन्छ, धनुष_, _vėjo_,
+ {{0x657d4828,0x6b560187,0xdb058168,0x6f16237c}}, // _tush, tági, mshë, ckyc,
+ {{0xdb1c8019,0x63ba816b,0x672083ed,0xbebd8196}}, // _apró, hutn, dhmj, kiūr,
+ {{0x63ba8057,0xdb1b0009,0xe4e400e8,0xe9d88a18}}, // kutn, lttä, _пісн, які_,
+ {{0x1bd44829,0x20548098,0xdb058168,0x62860106}}, // _поря, нтър, nshë, ycko,
+ {{0x63ba80dd,0xe61995a6,0x629f1cb5,0xdb1b0198}}, // dutn, жди_, _neqo, nttä,
+ {{0x798d146a,0x2b81c82a,0x98a78087,0x7769c82b}}, // _ntaw, _sóc_, gină_, rmex,
+ {{0xee369842,0x3d000105,0xe6c4876c,0xdb058168}}, // ьны_, राये_, češć, kshë,
+ {{0x3c65835f,0x2d540006,0x798d373c,0xdb058168}}, // ьког, päev_, _ataw, jshë,
+ {{0x57278250,0x799b81bc,0x2b818129,0x3dc9482c}}, // _عراق, _kwuw, _vóc_, ktaw_,
+ {{0x3d002724,0x80c4801b,0xdb058168,0x98a78162}}, // रामे_, _रामे, eshë, cină_,
+ {{0x63ba8f68,0x2b818028,0x3ebf8722,0xdb058168}}, // butn, _tóc_, lgut_, fshë,
+ {{0x63ba80b9,0x06e080ab,0x798d2e00,0xa3dc2724}}, // cutn, ভারি, _etaw, _तैं_,
+ {{0x3ebfc82d,0xdbd90073,0x2c0a801b,0x7b060118}}, // ngut_, nçõe, ाहरू_, _lóuz,
+ {{0xf50a228e,0xd8389c67,0xdbd9241f,0xaefb00ff}}, // [3780] онал_, _meča_, içõe, _thùn,
+ {{0xb4b8009a,0x61e4482e,0x3ce7c82f,0x6f1601d6}}, // छले_, _isil, _janv_, skyc,
+ {{0x6f660073,0x0aea8049,0xb6a38129,0x799b85e7}}, // _овоз, ждей_, _phầ, _awuw,
+ {{0x60dc4830,0x7c650277,0xdb1c8176,0xddcb4831}}, // lerm, رالل, _aprò, žišk,
+ {{0xa2c100d4,0xb5fb016b,0xdb1c8035,0x2baa06a7}}, // _वास्, _vzác, _spró, _चटका,
+ {{0xa91d8289,0x26c01aae,0x7d030106,0x98a78162}}, // _nužd, lgio_, önst, vină_,
+ {{0x38cb89d7,0xd83880d2,0x799b8c2e,0x4425ad1b}}, // تانی_, _beča_, _ewuw, jzl_,
+ {{0x60dc4832,0x61e422cd,0xef91803d,0x69100074}}, // herm, _osil, ایند, _käes,
+ {{0x60dc1e62,0x75d480f7,0x8c3c8085,0x29184833}}, // kerm, ريدا, dağa, kkra_,
+ {{0x6b5d03d3,0x2fdf0063,0x63ba8ad4,0xdbd90187}}, // tégo, ługi_, tutn, açõe,
+ {{0xdb1b025d,0x61e44834,0xccf8801b,0x798d009a}}, // yttä, _asil, dmět_, _staw,
+ {{0x68e8c835,0x290c805c,0x79808b15,0x63ba8359}}, // _hadd, _hoda_, _kumw, rutn,
+ {{0x60dc1cdc,0xff278077,0x63ba8025,0x68e8c836}}, // ferm, _تبلی, sutn, _kadd,
+ {{0xfc2f83f8,0x79808542,0x660f8884,0xdb0580f1}}, // احی_, _mumw, äcke, tshë,
+ {{0x68e882bb,0x61e44837,0xba3b4838,0x63ba80b9}}, // _madd, _esil, caïn, qutn,
+ {{0x26c00021,0x6d5ac839,0x8ccaa29e,0xc3280013}}, // ggio_, llta, त्रो, _يكون_,
+ {{0x798d483a,0x64588364,0xc61200c8,0x68fa838e}}, // [3790] _utaw, _hyvi, _সেবা_, _ontd,
+ {{0x290c80dd,0x2918016d,0x68e88365,0x6d5a85ee}}, // _noda_, ckra_, _nadd, nlta,
+ {{0x3dc946e9,0xa7750abe,0xa3e4bed8,0x60c50176}}, // staw_, _плоч, निट_, _ichm,
+ {{0x160e83db,0xae0e8d86,0xbebb0168,0x290c8362}}, // िहार_, िहान_, _emër, _aoda_,
+ {{0x68e88ec8,0x290cc83b,0x3f8137ee,0xf1a7980b}}, // _badd, _boda_, _huhu_, френ,
+ {{0x41b2009a,0x290c88d8,0x3f810074,0xd838979d}}, // _जिसस, _coda_, _kuhu_, _seča_,
+ {{0xa91d817f,0x78a38338,0xa92781a9,0x25a9483c}}, // _ružd, ranv, _režī, lsal_,
+ {{0x6d5ac83d,0x78a38365,0x68fa81ec,0x59b18beb}}, // elta, sanv, _entd, ुंदर,
+ {{0x7afbc83e,0x290cc83f,0xb9b58875,0x98a30084}}, // _inut, _foda_, _سماع, siję_,
+ {{0x290cc840,0x68e8985b,0x7afb816b,0x7ae98198}}, // _goda_, _gadd, _hnut, _haet,
+ {{0x60dc0f82,0xc4d200be,0x3f81054e,0x29c981ca}}, // verm, נגט_, _nuhu_, lúa_,
+ {{0x78a1c841,0x6d5a8046,0x61e401e0,0x60c54842}}, // _helv, alta, _psil, _achm,
+ {{0xa2c1000c,0x68e8c843,0x29c982ba,0x60dc4844}}, // _वार्, _yadd, núa_, term,
+ {{0x7ae980f7,0x291e1eca,0x3f8100fc,0x8c4581cf}}, // _laet, óta_, _buhu_, _пеле,
+ {{0x9d1a80be,0x46f60678,0x69cb8f67,0x7c8488ed}}, // _מוסט, нчат, htge, _руче,
+ {{0xdee34845,0xe45680be,0x60dc4846,0x7c2d8041}}, // лоси, _איצט_, serm, _ƙarf,
+ {{0xea010104,0x26c02118,0xc69280be,0x69c981c0}}, // [37a0] _đầu_, rgio_, טאן_, _nqee,
+ {{0x7afb99f7,0x79808314,0x7d7a83de,0xafe61a80}}, // _anut, _rumw, פרעג, _зоол,
+ {{0x389b0158,0x68e8c847,0x69c980b9,0x6aa09b6b}}, // _מיינ, _radd, _aqee, _pemf,
+ {{0x6d554848,0x7ae9c849,0x612201ac,0xfe6788ca}}, // _ekza, _caet, _dôle, _مد_,
+ {{0xa3e50105,0xb17b04e1,0x7ae98114,0x6d5a8106}}, // _बैठ_, lvår, _daet, ylta,
+ {{0x68e896bb,0x672404bf,0x1c0f83b7,0x2ca581fa}}, // _qadd, lhij, ाहाल_, jald_,
+ {{0xa2c101b6,0xb90895bc,0x4ac6016f,0x78a184fe}}, // _वाल्, _बस_, _वाढव, _delv,
+ {{0x68e8c836,0x8b668077,0x7ae9c84a,0x7659831d}}, // _wadd, _سازم, _gaet, _bywy,
+ {{0x290c9313,0xa84a80d5,0x78a18019,0x2ca58370}}, // _toda_, _کلام_, _felv, fald_,
+ {{0x3c2500ad,0x2d82484b,0x3ea49722,0xb17b016d}}, // _vзvv_, _muke_, samt_, kvår,
+ {{0x7d0e484c,0x929530c6,0xe57103de,0xa2950a4c}}, // _mobs, канц, יַט_, кані,
+ {{0xa4fd000c,0x60c502af,0x6458816b,0x3320484d}}, // _उच्च_, _schm, _vyvi, _clix_,
+ {{0x3f81059c,0x2ca581ec,0x6e3b00e1,0xd2440087}}, // _suhu_, bald_, _ľubo, гэри,
+ {{0x63be04e8,0xd5b990ac,0x7d0e0609,0x39860c0b}}, // kupn, _ісі_, _nobs, _dôs_,
+ {{0x67d41541,0xdfd80098,0x332007f1,0x6b588118}}, // лору, нът_, _flix_, lígu,
+ {{0x63be0057,0x2d82484e,0x61e292eb,0x25a9484f}}, // dupn, _buke_, kwol, tsal_,
+ {{0x2d820267,0x25a91b6c,0xe7e28035,0x5d550323}}, // [37b0] _cuke_, usal_, _कैसा_, вкат,
+ {{0x2edd80d4,0x2d820e79,0x7d1a82f1,0x7ae9c850}}, // _मस्त, _duke_, nkts, _saet,
+ {{0x25a94851,0x236901c0,0x7ae9c852,0x78a182f1}}, // ssal_, _khaj_, _paet, _relv,
+ {{0x78a1aa3d,0x67240a0f,0x29c985e4,0x25a9001b}}, // _selv, chij, túa_, psal_,
+ {{0xdd86881b,0x66640098,0x5f0490a1,0xe5c72f42}}, // _خو_, _бърз, वान्_, _ясно,
+ {{0x69cb84eb,0x78a70669,0x88bc81d0,0xccf881d0}}, // rtge, najv, _směr, změr_,
+ {{0x69cbc853,0x7c288390,0x7ae9c854,0x63be008e}}, // stge, jzdr, _taet, cupn,
+ {{0xf77185ff,0x256600e7,0x26c6bb40,0xadc40032}}, // جات_, rôle_, _ccoo_, _akẹr,
+ {{0x49758e9f,0x62849c67,0x9fb6001b,0x99d4bb76}}, // _алас, žioc, vřít_, _متلا,
+ {{0xe9759a3c,0x2f11c855,0x61e482d0,0x26c68037}}, // _شهاد, _págs_, çile, _ecoo_,
+ {{0xa91d8067,0x8c459e25,0xecc6086a,0xa8028380}}, // _tužb, тене, र्टफ, şırl,
+ {{0x76598114,0x6f0f01e8,0x23690242,0xf1bf016a}}, // _tywy, _nocc, _chaj_, rzán_,
+ {{0xb5fb0061,0x6aa600b9,0xf1bf041c,0x0b4301b5}}, // _szán, rakf, guá_, рнян,
+ {{0x2d82003a,0x6fd88424,0x6f09a6af,0x9e6483a9}}, // _ruke_, _महमू, ljec, _свід,
+ {{0x2d8203c3,0xea010028,0x10a33751,0x6f0f0081}}, // _suke_, _đấu_, рисн, _bocc,
+ {{0x2d82020d,0x6f0f4856,0x22160a41,0x66040102}}, // _puke_, _cocc, _афор, txik,
+ {{0x67244857,0x6da60258,0x6f0f0081,0xb17b0106}}, // [37c0] rhij, тиза, _docc, rvår,
+ {{0x6b83c858,0x672402a0,0x63be05b9,0xe6c91351}}, // _kung, shij, tupn, čašć,
+ {{0x6d41c859,0x6b83c85a,0x68fe00e5,0x7c290326}}, // inla, _jung, _inpd, _ƙera,
+ {{0x6b83b1d5,0x6441485b,0x63be017f,0x6f0f047f}}, // _mung, _žlic, rupn, _gocc,
+ {{0x9c7c827f,0x6f09a1a2,0xaca3819d,0x6b83c290}}, // leče, djec, _apọk, _lung,
+ {{0x61e28114,0x6f0f01e8,0x6aa421fb,0x7b3c8088}}, // rwol, _zocc, _leif, jčuv,
+ {{0x03a3214d,0x6a86a550,0x6b83c85c,0xb4de485d}}, // _висо, _алба, _nung, ढ़ी_,
+ {{0x6d5e485e,0x2ca32f9f,0x04431505,0x2fda00b9}}, // llpa, _zejd_, _тесн, _mppg_,
+ {{0x91e399a4,0x5d548323,0x3eb981a8,0x6b83aaa0}}, // _коте, лкит, ósta_, _aung,
+ {{0xe29a0028,0x660f816d,0xa8028085,0xfe7f023e}}, // _đưa_, äcka, şıql, _raïm_,
+ {{0x9c7c920e,0x6b83c85f,0x6b5d12ab,0x6f09c860}}, // ječe, _cung, légi, bjec,
+ {{0x6b839b11,0x61f60aa2,0xddcfc861,0x7984010c}}, // _dung, _bryl, žešk, _buiw,
+ {{0x6f0f0081,0x645c4862,0x6aa44863,0x7def8061}}, // _rocc, _lyri, _deif, rősö,
+ {{0x6f0f4864,0x7984067f,0x2f150687,0x61f60286}}, // _socc, _duiw, _lågt_, _dryl,
+ {{0xf7730bbe,0x28d20778,0x6b83c865,0x236901c5}}, // لار_, द्धि, _gung, _thaj_,
+ {{0x5eb40ada,0x6280808e,0x27fe0106,0x2007023e}}, // ийст, _ngmo, åtna_, _ànim_,
+ {{0x657c076d,0x79840041,0x7aed02c4,0x645c22f8}}, // [37d0] _birh, _guiw, _iaat, _ayri,
+ {{0x7aed4866,0x672280f1,0x6b83b107,0x98a79c28}}, // _haat, _lloj, _yung, binę_,
+ {{0x6f0f0081,0xb227007b,0x7aed4867,0x6b8388f1}}, // _tocc, _fræð, _kaat, _xung,
+ {{0x7c248326,0x3f570866,0x27f9928a,0xb22701fa}}, // _ƙirj, rçus_, _čsn_, _græð,
+ {{0x7aed1694,0x3ea94868,0x6f09af9f,0x69cf45e6}}, // _maat, maat_, vjec, ntce,
+ {{0x645c1c86,0xeb9308ca,0x7aed4869,0x1bf4823c}}, // _fyri, _نظر_, _laat, ेमाल_,
+ {{0x6f09a4a0,0x98a28009,0xb8dbb26c,0x3d00114f}}, // tjec, щище, _अय_, रारे_,
+ {{0x765d0364,0x6b83c86a,0x7aed486b,0xa91d95db}}, // _kysy, _rung, _naat, _duža,
+ {{0x6aa4486c,0xdce38bda,0x8cca04c5,0xaefb01e4}}, // _reif, _sinđ, स्को, _chùi,
+ {{0x6b8383f8,0x61e9c86d,0x6f0982ce,0x6e29851c}}, // _pung, _isel, sjec, rzeb,
+ {{0xb5fb0065,0x7aed0365,0x6722c86e,0xfaa6198c}}, // _szál, _baat, _floj, лано,
+ {{0x7aed486f,0x15f587bd,0x61f6016d,0x6b58957a}}, // _caat, _مستح, _pryl, lígr,
+ {{0x9c7cc870,0x3f858b20,0x6b83ab35,0x3ea91e7a}}, // veče, _hulu_, _wung, daat_,
+ {{0x6b83c871,0x25adb804,0x64a5c872,0x92df80ab}}, // _tung, msel_, _сака, তায়_,
+ {{0xc815035f,0x25ad831e,0x3ea94873,0x3f859c67}}, // аєть, lsel_, faat_, _julu_,
+ {{0x7bc1c874,0xa3b89344,0x61e9c875,0x3f858b99}}, // mulu, _चित_, _osel, _mulu_,
+ {{0x7bc1c876,0x201e4877,0x765d0355,0x645c4878}}, // [37e0] lulu, šti_, _cysy, _syri,
+ {{0xb4de0b9f,0x319b012a,0x7aed24f2,0xb19b03c8}}, // ढ़े_, _רבינ, _zaat, _רייכ,
+ {{0xf5368158,0x9c7cc879,0x7bc1c87a,0x69db85a4}}, // נטער_, peče, nulu, _apue,
+ {{0x25adc87b,0x3ea9487c,0x645c3999,0x6b5d4011}}, // ksel_, caat_, _vyri, tégi,
+ {{0x7bc1c87d,0x99d380f7,0x657c02c4,0x765d0114}}, // hulu, متوا, _tirh, _gysy,
+ {{0x7bc1bc4c,0x645c01e2,0x3f85b661,0xdce380eb}}, // kulu, _tyri, _bulu_, _minē,
+ {{0x7bc1c87e,0xd54709b5,0xd90d803d,0x61e9c87f}}, // julu, _спре_, بیل_, _esel,
+ {{0x3f858393,0x7bc1c880,0xc6f89fde,0x60c8849c}}, // _dulu_, dulu, тних_, _wcdm,
+ {{0x25ad84fe,0xd33581e2,0xe1faa59a,0x7ae2c881}}, // gsel_, _тэры, _яга_, meot,
+ {{0x7aed3126,0xa91d811f,0x80d601c4,0x7bc1c882}}, // _saat, _tuža, म्मे, fulu,
+ {{0x7bc1c883,0x7aed4884,0xa3bb80d4,0x3f85c885}}, // gulu, _paat, इंग_, _gulu_,
+ {{0xb5fb0117,0x23a58076,0x7aed02a3,0x317e811e}}, // _szám, _खबरद, _qaat, _hitz_,
+ {{0x7aed247f,0xa5968698,0x3f85c206,0x3ea90a53}}, // _vaat, _срещ, _zulu_, vaat_,
+ {{0x7bc1b598,0xa2c103dd,0x6d58807a,0x7aed4886}}, // bulu, _वाक्, _ukva, _waat,
+ {{0x7aed4887,0x7bc1c888,0x3ea9282a,0x753aabfa}}, // _taat, culu, taat_, mitz,
+ {{0x753a8813,0xa91d8499,0xcb69003d,0x765d0009}}, // litz, _nužn, _جمعه_, _pysy,
+ {{0x3ea94889,0x7bdc488a,0xdb1e80e7,0x7ae2a9a4}}, // [37f0] raat_, _apru, cupé, deot,
+ {{0x753a87e2,0x3ea9015d,0xd7f88087,0x628f0088}}, // nitz, saat_, _oră_, lcco,
+ {{0x765d009a,0x61e98fd6,0xb97a01c6,0x04671246}}, // _wysy, _ssel, _שנתי, утам,
+ {{0x92eb80f7,0x753a8102,0x3ea905ee,0x7f440722}}, // عراق_, hitz, qaat_, cniq,
+ {{0xa91d803a,0x7bc1c88b,0x6f0d488c,0x753a8102}}, // _dužn, zulu, mjac, kitz,
+ {{0xf77f07d9,0x6f0d003a,0xe8f885a8,0x7a2a8061}}, // _hiç_, ljac, ллі_, _söté,
+ {{0x753a8102,0xd70595a6,0x6b5d0036,0x7bc18162}}, // ditz, изки, légu, xulu,
+ {{0x6d573d27,0x6f0d2e96,0xd34389a7,0x25adc88d}}, // loxa, njac, _افری, tsel_,
+ {{0xd7068009,0x6e2d488e,0x2bdd8035,0xb5fb0061}}, // рные_, nzab, _फैजा, _száj,
+ {{0x7bc1ad7b,0x25adc88f,0x753a811b,0x6d4508b6}}, // tulu, rsel_, gitz, nnha,
+ {{0x30848013,0x237f8176,0xe29a079e,0xa2cc800d}}, // _المف, _kiuj_, кан_, _हाम्,
+ {{0x68e3c890,0x7bc1c891,0x6f0d02a5,0x290144e8}}, // lend, rulu, jjac, _anha_,
+ {{0x7bc1c892,0xfe9a8051,0x6f0d10d3,0x753ac893}}, // sulu, _סיסמ, djac, bitz,
+ {{0x82768158,0x68e3c894,0x6d454895,0xa91d80e1}}, // יערע_, nend, jnha, _mužo,
+ {{0x2bb10063,0xa3e5198e,0x68fc86a5,0x8fa6096f}}, // jąc_, _बैल_, _órde, рабе,
+ {{0x68e3c896,0x273f0009,0xd91009a7,0x6fd8bac3}}, // hend, vänä_, _کیس_, _महसू,
+
+ {{0x661bafb7,0xa91d805c,0x29130118,0x63a38114}}, // [3800] nyuk, _ružn, _foxa_, _hwnn,
+ {{0x33f61b93,0x26cb0098,0x68e3ad03,0xf1bf0061}}, // ичес, _ecco_, jend, zzák_,
+ {{0x68e3c897,0xa3bb8540,0x225f808e,0x2ca7c898}}, // dend, इंट_, _ayuk_, _mend_,
+ {{0x753a811e,0x28d2000c,0x6f0d009a,0x48ee001b}}, // zitz, द्रि, cjac, इएको_,
+ {{0x2bd20c78,0x272a801c,0xdb020338,0x273f062c}}, // _सहका, _hùng_, mslä, sänä_,
+ {{0x39582bd5,0x68e3c899,0xc05a80e8,0x8b96351e}}, // nors_, gend, кій_, _врач,
+ {{0xa91d8024,0x8c430b88,0xf8b30039,0x443e16d2}}, // _tužn, _дере, קשה_, _ät_,
+ {{0x9c7c93ee,0x6d5c007a,0xd838826c,0x395800e7}}, // ječa, _hkra, _ječi_, hors_,
+ {{0x60ea8ae7,0x753aabfa,0x3958489a,0x2a6001c0}}, // _имам_, titz, kors_, _nyib_,
+ {{0x395802e6,0xd8388279,0x6443009a,0x63a385ee}}, // jors_, _leči_, źnie, _bwnn,
+ {{0x39580813,0x753ac89b,0x6e2d0d3d,0xb0aa0035}}, // dors_, ritz, zzab, करनग,
+ {{0x753a9919,0xd6d9809a,0xee3a8bda,0x48fe801b}}, // sitz, koły_, _ине_, लाको_,
+ {{0x78a88065,0xf5948013,0x6d5c105e,0x3958489c}}, // _kedv, _التج, _okra, fors_,
+ {{0x78a8803b,0xf77f00f1,0x272a8129,0x6fca852a}}, // _jedv, _siç_, _bùng_, संपू,
+ {{0x272a8104,0x4ea70073,0xaad781d0,0x4ad78107}}, // _cùng_, ираа, ण्डक, ण्डव,
+ {{0x68e3c89d,0x272a8104,0x6d5c489e,0x6c7b812a}}, // zend, _dùng_, _akra, _באופ,
+ {{0x68e3c89f,0xdceb0696,0x2913002a,0x8cc200dc}}, // [3810] yend, šičk, _toxa_, _लाखो,
+ {{0x290148a0,0x61ed48a1,0xa91d81ac,0x6e2d48a2}}, // _unha_, _isal, _ružo, rzab,
+ {{0x68e3c8a3,0x6d4548a4,0x6d570358,0x236d81c0}}, // vend, rnha, roxa, _phej_,
+ {{0x68e3c8a5,0x28c9816f,0x1fcc80ab,0x2b591717}}, // wend, _राहि, লবাস, nosc_,
+ {{0xe91980e8,0xaefb051e,0x61ed0372,0x3be8029a}}, // лоді_, _dhùt, _jsal, _eşq_,
+ {{0xf1bf1984,0x6f02c8a6,0x61ed48a7,0x98a2a482}}, // irá_, _anoc, _msal, пише,
+ {{0xe7398a41,0x2ca7c8a8,0x63be011f,0xf1bf3fe5}}, // лек_, _rend_, crpn, hrá_,
+ {{0x61ed01c2,0x7bc548a9,0x2ca78039,0xf1bf007b}}, // _osal, muhu, _send_, krá_,
+ {{0x612b0065,0x68e3a840,0x7bc50886,0x9c7c8353}}, // _küld, pend, luhu, veča,
+ {{0x6aa98586,0xb8fc80c8,0xf1bf48aa,0x0d829439}}, // _heef, _তো_, drá_, яльн,
+ {{0x61ed48ab,0x2ca7c8ac,0x395848ad,0x661b8859}}, // _asal, _vend_, vors_, syuk,
+ {{0x6f02c8ae,0x61fbc8af,0x7981a676,0x2c4100fc}}, // _gnoc, _krul, _hilw, _mɗd_,
+ {{0x61ed0065,0xd838979d,0x7bc507fe,0x9c7c8024}}, // _csal, _reči_, huhu, reča,
+ {{0x6aa9838e,0x9c7cc8b0,0x7bc513fa,0xfd48819d}}, // _leef, seča, kuhu, _memọ,
+ {{0x9c7c800d,0x7981ab02,0xf1bf1f90,0x7d03c8b1}}, // lečn, _milw, ará_, _inns,
+ {{0xf1bf48b2,0x1e86161a,0xdc3984b7,0x272a801c}}, // brá_, слам, _aħħa, _vùng_,
+ {{0x9c7c803e,0x7ae60214,0xa3ca016f,0x2fdf0162}}, // [3820] nečn, mekt, ळून_, ăuga_,
+ {{0x25bf9eb1,0x272a801c,0x23db8ebf,0x69c6005d}}, // irul_, _tùng_, _बहाद, muke,
+ {{0x69c622b3,0x6aa9c8b3,0x25bf836e,0x61fbc8b4}}, // luke, _beef, hrul_, _arul,
+ {{0x61fbc8b5,0xaefb0a2a,0x78a89be9,0x6b5d1a1f}}, // _brul, _dhùs, _sedv, légr,
+ {{0x9c7c94ce,0x28c9ae06,0x3ea048b6,0x7ae6011e}}, // ječn, _राशि, mbit_, iekt,
+ {{0x25bf8abf,0x28db000c,0x9c7c816b,0x6d5c1d56}}, // drul_, म्पि, dečn, _ukra,
+ {{0xdfd080f7,0x93bc8087,0x64a302ac,0x6ab780ab}}, // ذية_, nzăt, _хаса, _ইউরো,
+ {{0x6aa98a0f,0x6d8908ae,0xa3ab8fea,0x7d03998d}}, // _geef, _džaf, गठन_, _anns,
+ {{0xc6f791e9,0xa63b00be,0xa91d812b,0x79819acf}}, // сных_, ָגיר, _mužj, _filw,
+ {{0xb606812b,0x73d8b09d,0xdce38084,0x69c648b7}}, // vršć, адор_, _minė, duke,
+ {{0x7ae63f82,0x628581d0,0x3ea00ec9,0x3ead82c4}}, // fekt, ěhov, kbit_, baet_,
+ {{0x09bf00ab,0x3f8200d7,0x8e8681a8,0x25bfc8b8}}, // েবসা, _niku_, _الإه, brul_,
+ {{0xc3338051,0x25bf8162,0x9418011c,0xd75781a8}}, // פוש_, crul_, _verə_, _النت_,
+ {{0xa2d901fe,0xecc7009a,0x3f820032,0xdcfc0b67}}, // _फॉर्, _लाइफ, _aiku_, _birč,
+ {{0x0d3b804c,0x7d1509a4,0x77640118,0xd6db19ab}}, // _תגוב, _rozs, flix, лта_,
+ {{0xf7700250,0x61ed00a4,0x7ae60214,0x3f8200b9}}, // فاق_, _tsal, cekt, _ciku_,
+ {{0xe66711c7,0xb505000f,0x61ed48b9,0x9c8700f7}}, // [3830] ство, रालय_, _usal, مشاه,
+ {{0x7bc5022e,0x6d48a3aa,0x25e08006,0xdcfc01a1}}, // tuhu, nnda, _कहनी_, _firč,
+ {{0x25bf80ee,0xaefb0229,0x6d48c8ba,0xadea86ae}}, // zrul_, _dhùr, inda, _जैसन_,
+ {{0x61fb8bfd,0x6d5ac8bb,0x7bc548bc,0x7981c8bd}}, // _prul, hota, ruhu, _silw,
+ {{0x6d5ac8be,0xd838b4c1,0x7bc51e8f,0x2179c3e1}}, // kota, _meču_, suhu, айды_,
+ {{0x2d990364,0x68e70067,0x92e500c8,0x65940a42}}, // _itse_, nejd, নায়_, _хару,
+ {{0x7ae648bf,0xb9260135,0x6d5ac8c0,0xdfce80f7}}, // yekt, _agwọ_, dota, نيو_,
+ {{0xf3898104,0x6d48c8c1,0xef19a14d,0x9c7c800d}}, // _bản_, enda, рми_, tečn,
+ {{0x6b8a8352,0x2d8b04a8,0x6d48807b,0xf38980ff}}, // _aufg, _juce_, fnda, _cản_,
+ {{0x6d5ac8c2,0x9c7ca19f,0x45198997,0xc58880ff}}, // gota, rečn, рция_, _tồn_,
+ {{0x5fdd91be,0x9c7c84c4,0x637180f2,0x2d8b48c3}}, // _महिल, sečn, gång, _luce_,
+ {{0x6d48c8c4,0x2fc78077,0x69c612a5,0x2d9902f1}}, // anda, mung_, tuke, _otse_,
+ {{0x6d5ac8c5,0x2fc7c8c6,0x2d990069,0x6b5d02be}}, // bota, lung_, _ntse_, tégr,
+ {{0x2d83003a,0xe7dd81ab,0x3f8200f6,0xf99284de}}, // _nije_, _महाप, _siku_, ורך_,
+ {{0x2fc7c8c7,0x3cee835a,0x332948c8,0x3f82026c}}, // nung_, _असते_, _flax_, _piku_,
+ {{0x7f49c73a,0xf389801c,0x6d4b2d5d,0x28db04c5}}, // nneq, _hảo_, égan, म्बि,
+ {{0x68e70022,0xf09f8104,0x2fc7c8c9,0xe7e9800f}}, // [3840] bejd, _đàn_, hung_, झौता_,
+ {{0x2fc78854,0x61e48059,0x2d8b202a,0xf53f0106}}, // kung_, çilm, _duce_, _svår_,
+ {{0x98788038,0x2fc7a08b,0x13ea0087,0x2d8348ca}}, // _páči_, jung_, рмей_, _dije_,
+ {{0x672d48cb,0xe1fa9634,0xc7b3007c,0x6d5ac8cc}}, // chaj, иге_, _קבר_, zota,
+ {{0xe8949a8f,0x6d488125,0x2b4d826c,0x6f0614a2}}, // паль, ynda, _djec_, _inkc,
+ {{0x6b8432f4,0xf8aa0105,0x2fc781ec,0x2d830176}}, // _liig, करिय, fung_, _gije_,
+ {{0xb8f2aa18,0xf3898104,0x6d5ac8cd,0xf72a80d6}}, // _वा_, _sản_, vota, иций_,
+ {{0x68e7009a,0x672448ce,0x4b34803d,0x6d5ac8cf}}, // zejd, nkij, _عکاس, wota,
+ {{0xf3898104,0xf7730039,0x6d5ac8d0,0x1ee8803d}}, // _bảo_, וקה_, tota, لوژی_,
+ {{0x61300004,0x6d48c8d1,0xd83881a1,0x23668168}}, // _hälf, unda, _seču_, lloj_,
+ {{0x6d5a92bf,0x672448d2,0x6d48c8d3,0xd7ef80f7}}, // rota, kkij, rnda, _لكل_,
+ {{0x51871454,0x78ba9c33,0x6d5ac8d4,0x67242948}}, // жува, _hdtv, sota, jkij,
+ {{0x6d5ac8d5,0x3f8c84b9,0x9f4c80f7,0xb5fb046d}}, // pota, _hudu_, _ordú_, _ayán,
+ {{0x3f8cc8d6,0x6d5a8079,0x0e8e819d,0x2d8b01d0}}, // _kudu_, qota, _gịrị_, _ruce_,
+ {{0x28d28076,0x69c2c8d7,0x637192f1,0x29050850}}, // _सानि, hroe, låne, ıla_,
+ {{0x6d4e026c,0x7d18c8d8,0x2bc9016f,0x2d8348d9}}, // _ajba, _hovs, ांसा, _sije_,
+ {{0x672d48da,0x7bc8973c,0x2fc781ec,0x80cd8035}}, // [3850] rhaj, ludu, zung_, सलमे,
+ {{0x672d022e,0x2d8b02a5,0xf09f8028,0x850512c6}}, // shaj, _vuce_, _đào_, राइट_,
+ {{0x80c48778,0x3eb98110,0x2d8b00a4,0xd25100d7}}, // _राजे, ųsti_, _wuce_, _زنگ_,
+ {{0x7d18c8db,0xdceb9dd7,0xa4a5826b,0x6724026c}}, // _lovs, _čiči, _afọ̀, ckij,
+ {{0x69c280c9,0x2fc7c8dc,0x7bc880b8,0x2b4d8168}}, // groe, wung_, hudu, _vjec_,
+ {{0x2fc7c8dd,0x3f8ca771,0xa91db5ca,0x7d18976d}}, // tung_, _budu_, _kuži, _novs,
+ {{0x6371816d,0xe8d900ff,0x2fc781ec,0xa91d80c3}}, // tånd, _quỹ_, uung_, _juži,
+ {{0x2fc7c8de,0x7bc8c8df,0x3f8ca0d2,0xc61200ab}}, // rung_, dudu, _dudu_, _সেটা_,
+ {{0x2fc7c8e0,0x6b840006,0x2d9980f1,0x3d000072}}, // sung_, _riig, ëse_, राचे_,
+ {{0x2fc78886,0xee368084,0xc588801c,0x799bc8e1}}, // pung_, яны_, _gồm_, _ituw,
+ {{0x3f8c8041,0x798d0c53,0x7bc8c8e2,0x46cb001b}}, // _gudu_, _auaw, gudu, िलाह,
+ {{0x09e6091d,0x26dd8578,0x1becbed8,0x612f805f}}, // помн, _ubwo_, _जनरल_, _bølg,
+ {{0xb0d2b852,0x6d890038,0xa2d394d5,0x6ed091be}}, // _सामग, _zľav, _डान्, _धातु,
+ {{0x867a8051,0xfe708077,0xa91d856f,0x14c7016f}}, // _פרסו, _شدن_, _buži, _लागण,
+ {{0xab99987e,0x672448e3,0x7bc881cc,0xed5981a1}}, // _اختر_, tkij, cudu, juži_,
+ {{0x6d41c8e4,0x612f86be,0xa91d8025,0x753c0668}}, // lila, _følg, _duži, _smrz,
+ {{0x672448e5,0x9c7c911b,0x5bdb9a46,0x6443008b}}, // [3860] rkij, ječj, _बहुव, únin,
+ {{0x6d41bb84,0x61e404be,0x6abbc7fe,0x4d630073}}, // nila, _ipil, _aduf, екув,
+ {{0x711b0158,0xa7fc8059,0x3d0c86a7,0x28d283a4}}, // _הויפ, _ayın, ़ाते_, _साबि,
+ {{0x6d41c518,0x6d890253,0xeb9680be,0x61e400b9}}, // hila, _džab, לדער_, _kpil,
+ {{0x291a48e6,0x6d419dde,0x61300106,0xc33301c6}}, // _kopa_, kila, _välf, עוד_,
+ {{0x25a0445d,0x612b0006,0x291a0009,0x27fe80b9}}, // mpil_, _küla, _jopa_, _prtn_,
+ {{0xc44809d7,0xfaa692a0,0x3d090075,0xd7fa802e}}, // _بیان_, _наво, सारे_, сул_,
+ {{0xf3898104,0x96eac7a8,0x69c286a8,0x61300106}}, // _cảm_, ська_, proe, _fälg,
+ {{0x7d18a7aa,0x4ac60072,0x3f8cc8e7,0x6f19c8e8}}, // _povs, _वाजव, _wudu_, _dowc,
+ {{0x6aad3e5b,0x7bc88886,0x3f8c89ab,0x63bc84e8}}, // _seaf, tudu, _tudu_, árno,
+ {{0x1e869519,0x6db5817b,0x61e448e9,0xd12f8872}}, // _елем, _aşağ, _apil, _حمل_,
+ {{0xa91dc8ea,0x9c7c81d0,0x212c8362,0x61e40f35}}, // _ruži, lečk, _hldh_, _bpil,
+ {{0x637f00eb,0xc3338039,0xa91d8110,0xd7098054}}, // _jūni, תור_, _suži, वारथ_,
+ {{0x6b9c00d2,0x6d5e012b,0xa91d80c3,0x256f02f1}}, // _otrg, jopa, _puži, küla_,
+ {{0x6d5e2a0b,0xd1488028,0xf99f06c0,0xaac6016f}}, // dopa, yễn_, spè_, _वाचक,
+ {{0x8f9b810f,0xf09f80ff,0x798d05ee,0x6491007a}}, // מילי, _đàm_, _tuaw, zžič,
+ {{0xbf1d053e,0x78838019,0x9f5e8168,0x320002d6}}, // [3870] _नवीन_, _kívü, _artë_, _griy_,
+ {{0xf9940451,0xa91d803b,0x9c7cc8eb,0x612f806a}}, // _ארץ_, _tuži, ječk, _køle,
+ {{0x442748ec,0x25a00420,0x799b8cab,0xa02206ae}}, // án_, apil_, _stuw, ööta,
+ {{0x394300eb,0xaabf0f0a,0x99990035,0xceeb01f9}}, // lijs_, ्लिक, mysł_, _اردن_,
+ {{0x6d41c8ed,0x6d5e48ee,0xa2c006f0,0x442100b9}}, // yila, bopa, _वयस्, syh_,
+ {{0x6d41c8ef,0x5d8500d5,0x394300eb,0x61300f02}}, // xila, _زلزل, nijs_, _välg,
+ {{0x81e300ab,0x213e8168,0x6e2002f1,0xe7398b7d}}, // নটি_, _dmth_, _ümbr, жей_,
+ {{0x6604c8f0,0x28d281a2,0x6fcf816f,0x6d41c8f1}}, // _šika, _साथि, संबं, wila,
+ {{0x2731c8f2,0x3f868176,0x80ce064a,0x0609804a}}, // _láng_, _diou_, _ताले, інок_,
+ {{0x7af60201,0x7aeb81ec,0x69cb8f8e,0x81e30264}}, // _sayt, legt, muge, নটা_,
+ {{0x61e4320d,0x291a00ab,0x7af648f3,0xa3a8816f}}, // _spil, _ropa_, _payt, खील_,
+ {{0x7af6011c,0x291a47b7,0xf1d18aad,0x2d8b85e4}}, // _qayt, _sopa_, _समान, íces_,
+ {{0x6d41c8f4,0xdb1b0364,0x69d9a41b,0x69cb9eb1}}, // pila, yttö, ntwe, nuge,
+ {{0x61e488c5,0x612f8b40,0xafe61bb1,0x6d418438}}, // çili, _føle, могл, qila,
+ {{0xb8f6053e,0xe0d200d7,0x67030133,0x2d8f81c6}}, // _हा_, _یزد_, _ọkai, _huge_,
+ {{0xa91d8503,0x2731801c,0xf4128039,0xdbc60074}}, // _gužv, _dáng_, _יפו_, _köög,
+ {{0xb5fb0019,0x2d8f8036,0x6db5811c,0x0d860071}}, // [3880] _szár, _juge_, _uşağ, длан,
+ {{0x3eafc8f5,0xc8ca003d,0x28d286ab,0x4176003d}}, // _legt_, _بودن_, _साधि, _تابس,
+ {{0x6d5e48f6,0x613004b8,0x5fe2364b,0x9c7cac90}}, // ropa, _väld, _पहिल, večk,
+ {{0x69d99fba,0xfbc58beb,0x2bc5861e,0x4a46c8f7}}, // ftwe, _विनम, _विना, мнев,
+ {{0xceb31def,0x63760c58,0x3cee816f,0x6d5e48f8}}, // _שיש_, mánd, _असले_, popa,
+ {{0x332d86cb,0xfbc61fb4,0xcf9a84ae,0x7bc30144}}, // _elex_, _обго, оји_, ánuc,
+ {{0x399404b8,0x9c7c826f,0x3d119862,0x2d8f91e6}}, // _läs_, rečk, ताने_, _auge_,
+ {{0xc5888028,0x63760511,0x2d8f89ab,0x2d8792a5}}, // _hồi_, nánd, _buge_, _aine_,
+ {{0x2d878012,0x6e24018c,0xc62000ab,0x9c7cc877}}, // _bine_, nyib, _নেতা_, meči,
+ {{0x2d87c8f9,0x9c7c8353,0x2d8f805c,0x2b40009f}}, // _cine_, leči, _duge_, _amic_,
+ {{0x2d8786be,0x69c048fa,0xa3a88006,0xd6cfb6fc}}, // _dine_, ámen, खीं_, _ит_,
+ {{0x2d878943,0x3d050063,0x7d1c02be,0x80da8aed}}, // _eine_, _होने_, _lors, प्रे,
+ {{0x2d878653,0x27318028,0x7d1c00c9,0x660f816d}}, // _fine_, _sáng_, _oors, äckt,
+ {{0x7d1c0cde,0x3eaf8a0f,0x6602c8fb,0x2d8784b9}}, // _nors, _zegt_, _irok, _gine_,
+ {{0x394348fc,0xc58880ff,0x69cb81ec,0xdfd80081}}, // rijs_, _nồi_, zuge, мът_,
+ {{0x9c7c82fd,0x63760511,0x15f282f1,0x2d87c8fd}}, // ječi, gánd, _अनार_, _zine_,
+ {{0x171b8158,0x2d878214,0xe784323a,0xbf1f0105}}, // [3890] קומע, _yine_, туро, _यकीन_,
+ {{0x7d1c1137,0x2bc58c46,0xad9b0118,0xc58880ff}}, // _cors, _विमा, _crúa, _bồi_,
+ {{0xe3b89014,0xe1ff0216,0x6376016a,0x7c2d8041}}, // nsı_, lvó_, bánd, _ƙari,
+ {{0x32559baa,0x637605e4,0x0f2700e8,0x66028102}}, // нвер, cánd, _цьом, _orok,
+ {{0x7d1c15f8,0x2bc58dbc,0x7aebc8fe,0xdddd009a}}, // _fors, _विभा, regt, _wysł,
+ {{0x69cbc8ff,0x7d1c4900,0x70c9835a,0x69d98a10}}, // ruge, _gors, रलेल, rtwe,
+ {{0x6d45009c,0x6602c901,0x3d0506a7,0x69cbc902}}, // miha, _arok, _होये_, suge,
+ {{0x6d45028a,0xf7718829,0x2d87c903,0x69c60f06}}, // liha, دات_, _sine_, yrke,
+ {{0x2a6901c5,0x78b508ae,0xf1a78a4c,0x79888372}}, // _nyab_, kazv, _іран, _fidw,
+ {{0x6f1d4904,0x6e9f864a,0x28df0540,0x6d454905}}, // _losc, _ग्रु, _पॉलि, niha,
+ {{0x2d87c906,0x934601bb,0x6b898019,0x69b20074}}, // _vine_, енне, _kieg, _अबही,
+ {{0x65959a12,0x6602805f,0xdd958190,0x637603a8}}, // _заду, _frok, _зады, xánd,
+ {{0xed4ec907,0x6b89c908,0x2d87c909,0x6d45490a}}, // _бо_, _mieg, _tine_, kiha,
+ {{0x6b89c90b,0x412a00e8,0x2bd003eb,0x2eed874c}}, // _lieg, чого_, _हमला, leef_,
+ {{0xdee62344,0x637602ba,0xd466c90c,0xd007490d}}, // нови, tánd, _пише_, нере_,
+ {{0xc5888104,0x6729c90e,0x6f1d490f,0x6b89c910}}, // _rồi_, nkej, _cosc, _nieg,
+ {{0xee3a1506,0xdcf40024,0x7ae40a6d,0x6d4504be}}, // [38a0] чна_, šačk, _ibit, fiha,
+ {{0x7d1c0079,0xa91d826f,0x63760511,0x98a78110}}, // _qors, _mužs, sánd, minė_,
+ {{0x7d1c0352,0x20c9853f,0x6f1d4911,0x798880dd}}, // _vors, _राजध, _fosc, _ridw,
+ {{0x28d28f85,0xeb92893f,0x6b89c912,0x7f440079}}, // _साहि, אָס_, _cieg, qiiq,
+ {{0x7d1c2280,0x6b89a4b5,0x63762511,0xf38a001c}}, // _tors, _dieg, cáne, _cải_,
+ {{0x6604816b,0x41d1864a,0x4425c913,0xf38a0129}}, // _šiko, _समरस, kyl_, _dải_,
+ {{0x50b78013,0xfc1a20bb,0x2d998118,0x7ae444dc}}, // ردود_, _عقرب_, ísen_, _obit,
+ {{0x26d2033e,0x20d29199,0x3d0e01ab,0x28d2861e}}, // ngyo_, _सावध, ठावे_, _सावि,
+ {{0xdb022739,0x020680e8,0x2d8a166a,0x657e0866}}, // mplè, _язан, _kibe_, omph,
+ {{0x44258c0b,0x7ae44914,0xe7b10035,0x645d8085}}, // fyl_, _abit, _जबलप, əsiz,
+ {{0x290c83c3,0xd5c28f12,0x61f604fe,0x63ae008e}}, // _inda_, _शिवज, _asyl, _jwbn,
+ {{0x291ec915,0xb605826c,0xf09f801c,0xbacf00ab}}, // _hota_, _bošč, _đài_, িজ্ঞ,
+ {{0x291e8500,0xe5a387b6,0x6d4501d4,0x8c2400ab}}, // _kota_, _бичи, yiha, _যেমন_,
+ {{0x656180a9,0x2d8a3129,0x7ae402ec,0x291ec916}}, // colh, _nibe_, _ebit, _jota_,
+ {{0xb4be8576,0xf2b000ab,0x291ec917,0x6f1d00e1}}, // ेली_, _কারণ, _mota_, _posc,
+ {{0x63763930,0x628d0b67,0x6d451f78,0x63788118}}, // táne, _ugao, wiha, nínd,
+ {{0x290c803a,0x6d453f12,0x273500f2,0x6b89816a}}, // [38b0] _onda_, tiha, _lång_, _rieg,
+ {{0x7afd200b,0x637604e8,0x7aef4918,0x6b89c919}}, // ldst, ráne, lect, _sieg,
+ {{0x98c4025d,0x6d4542f3,0x6b89c91a,0x63baad03}}, // _ссыл, riha, _pieg, nstn,
+ {{0x290c8852,0x6d450859,0x6b8981b9,0xe8d90129}}, // _anda_, siha, _qieg, _tuỳ_,
+ {{0x69dd491b,0x7aef0012,0x291ec91c,0x6b8980eb}}, // ntse, iect, _bota_, _vieg,
+ {{0xa069bc7a,0xe61f001c,0x290c808e,0xf38a00ff}}, // мала_, _ngôi_, _cnda_, _vải_,
+ {{0x6b89af1e,0x2bc586a7,0x629687d9,0x63ba875f}}, // _tieg, _विधा, ıyor, jstn,
+ {{0x3d05491d,0x290cc91e,0xf38a0028,0x06098a13}}, // _होते_, _enda_, _tải_, дник_,
+ {{0xb4be800f,0x67298057,0x69dd491f,0xceb403c8}}, // ेलू_, rkej, jtse, ניץ_,
+ {{0x7afbc920,0x69db913b,0x3f8b005c,0xdb024921}}, // _haut, _ique, _licu_, mplé,
+ {{0x273500f2,0x7aef0806,0x7afbc922,0x7afd00f3}}, // _gång_, fect, _kaut, fdst,
+ {{0x7afbb761,0x3947c923,0x98a781e2,0x3dc900ee}}, // _jaut, lins_, tinė_, kraw_,
+ {{0x7afbc924,0x69cf017f,0x2f188061,0x656182df}}, // _maut, guce, _négy_, polh,
+ {{0x6606813c,0x3dc94925,0x7afbc926,0x39479e9e}}, // ække, draw_, _laut, nins_,
+ {{0xa0672410,0x69dd0613,0x186705da,0x6378c927}}, // вара_, atse, вари_, líne,
+ {{0xed59803b,0xc6c31056,0xcf9280be,0x69cf4928}}, // drži_, ийск, שטן_, buce,
+ {{0x3dc93a65,0x2d8a4929,0x1e86373a,0x3f8b2433}}, // [38c0] graw_, _pibe_, тлам, _dicu_,
+ {{0x61ed81b9,0x3947c92a,0xad9b492b,0x657e4754}}, // ħall, jins_, _trún, umph,
+ {{0x69db938d,0x7afbc92c,0xf1bf25b3,0x3ea9492d}}, // _aque, _baut, nyán_, bbat_,
+ {{0x291e809f,0x776d01df,0x7afbb499,0xf1bf0144}}, // _sota_, clax, _caut, iyán_,
+ {{0x7fe203a4,0x291ec92e,0x2b4681a8,0x8c9605a8}}, // _पहुँ, _pota_, rioc_, _арбі,
+ {{0xdca3345a,0x64a30f04,0xb5fb0032,0x7afb89c4}}, // _вари, _вара, _ayáw, _eaut,
+ {{0x15432f84,0x7aef1220,0x291ead77,0x7afb80e7}}, // _терм, yect, _vota_, _faut,
+ {{0x7aef492f,0x7afbc930,0x8a06813a,0x394783ac}}, // xect, _gaut, _избе, ains_,
+ {{0x291ebb67,0xae0503b7,0xe0da8fbb,0x7aef01e0}}, // _tota_, रियन_, _ова_, vect,
+ {{0x2176975e,0xb4be8f85,0x69cf02a5,0x39478cf8}}, // _рубр, ेले_, vuce, cins_,
+ {{0x7aef4918,0x3cfa009a,0x2005abea,0x33200980}}, // tect, _उसने_, _grli_, _boix_,
+ {{0x69cf00e7,0x63bac931,0x65c681e5,0x46a68081}}, // tuce, rstn, _абда, _радв,
+ {{0x7aef0918,0x61e980b9,0x6130039c,0x9c7c82d4}}, // rect, _xpel, _säla, veču,
+ {{0x69dd05e1,0x273513c2,0xdc558061,0x7bde1db4}}, // rtse, _måne_, ئریک, ktpu,
+ {{0x6d488c52,0x5ea400ab,0x7aef0cf7,0x69dd4932}}, // mida, _ওয়ে, pect, stse,
+ {{0x6d4887a3,0x3ea907d5,0x6376001b,0x3947c933}}, // lida, ubat_, ránc, zins_,
+ {{0xb0d29cd4,0x3ea9223e,0x7afbc934,0x3f930079}}, // [38d0] _सांग, rbat_, _raut, _wuxu_,
+ {{0x6d489313,0x3947a2dd,0x7afbc935,0x637638e1}}, // nida, xins_, _saut, mána,
+ {{0x61e9ab61,0x7afbaf40,0x63764936,0xc3160264}}, // _spel, _paut, lána, াসরি_,
+ {{0xfaff00f1,0x6d48c937,0xe3b000f7,0xc2c480f7}}, // ndë_, hida, ترك_, ليمي,
+ {{0x3947c938,0xa2d601fe,0x6b8d0bb1,0xe9df4939}}, // tins_, _भास्, _miag, ntú_,
+ {{0xf1d58a61,0x30758009,0x7afbc93a,0xa805002a}}, // डंबन, луйс, _waut, coñé,
+ {{0x7afbc93b,0x3947c93c,0xf7458fc8,0xdcfc1f3a}}, // _taut, rins_, леко, _sirć,
+ {{0x3947c93d,0x6b8d1083,0xdb0200e7,0xe1f3803d}}, // sins_, _niag, pplé, _پسر_,
+ {{0x6d48b546,0x90e78eca,0x2121008e,0xa1948323}}, // fida, _لسان, _oohh_, _вауч,
+ {{0xee36979e,0x5ba980e2,0x6376493e,0xdb1c8176}}, // унь_, нком_, dána, _avrè,
+ {{0x6b951e4f,0x6b8d493f,0x672d4940,0x20058037}}, // _juzg, _biag, kkaj, _urli_,
+ {{0x257982be,0x6b954941,0x11d6102a,0x672d0115}}, // dèle_, _muzg, лігр, jkaj,
+ {{0xfce5818b,0x2bc58a3a,0xaad1800f,0x9f5e3649}}, // _боло, _विवा, _हाईक, ítí_,
+ {{0x6d488510,0x6f1b8052,0xb602801b,0x399993f8}}, // cida, ljuc, čátk, _mès_,
+ {{0x74d89008,0x3999c942,0x3d1706af,0x69cb8192}}, // _मातृ, _lès_, नाने_, hrge,
+ {{0x3d118e18,0x6d890fda,0x69c4800f,0x6b8d0bb1}}, // तावे_, _džam, _रिली, _giag,
+ {{0xa3b603b7,0x6e298393,0xc69280be,0x80b300ab}}, // [38e0] चीन_, nyeb, מאן_, _জার্,
+ {{0xdff38935,0xc8d28670,0xfd9e8135,0xe6d0928a}}, // _आनंद_, _साइट, _jụọ_, _सञ्ज,
+ {{0xa3bb80dc,0x6ab60059,0x6f1b817f,0xfd9e8135}}, // ेंट_, _keyf, kjuc, _mụọ_,
+ {{0x2ca70a0b,0x6d48c943,0xe73a0364,0xe29aa63e}}, // ında_, zida, _чем_, хад_,
+ {{0x82d70158,0xb17b0bfa,0x59f8c944,0x6d48c945}}, // רונג_, ståe, теля_, yida,
+ {{0x399982be,0x6ab6007b,0x660400b4,0x7ae2a0d1}}, // _dès_, _leyf, mvik, sfot,
+ {{0xe3a7896c,0x6d489313,0xd90d8bca,0x6604075e}}, // _در_, vida, ظیم_, lvik,
+ {{0xb8cb809a,0x3999c946,0xcfe780f7,0x2cba0122}}, // _क्_, _fès_, افيه_, lapd_,
+ {{0x10a68d31,0x66043f1e,0x6b8d0014,0xa534af75}}, // _бизн, nvik, _riag, инич,
+ {{0x46a483dd,0x6b8d3d34,0x23692009,0x2bc58c2d}}, // _ग्रह, _siag, _skaj_, _विशा,
+ {{0x2056a569,0x6d488511,0x40938307,0xd6c491fb}}, // _стар, rida, _المر, _نمای,
+ {{0x6d48c947,0x80d70b86,0x2bc581a2,0x27ec82f7}}, // sida, _बाहे, _विरा, _ipdn_,
+ {{0x7c3a8065,0x6d48c948,0xfe728065,0x96b985e9}}, // sztr, pida, _ادا_, _руху_,
+ {{0x98a78024,0x660414a2,0x6376066e,0x68fe0722}}, // čići_, dvik, rána, _capd,
+ {{0x629d0117,0xb8fd0bb8,0x09d18b84,0x67228282}}, // pcso, _डा_, _सम्य, _kooj,
+ {{0x67228079,0x613016d2,0x637d09c4,0x96f820bf}}, // _jooj, _häll, déng, лест_,
+ {{0x6b82811f,0x613000f2,0x799600fc,0x672d4949}}, // [38f0] dmog, _käll, _guyw, rkaj,
+ {{0x672d494a,0x627c0051,0x61300406,0x45d40470}}, // skaj, _אנחנ, _jäll, софс,
+ {{0xa9260029,0x5b27814c,0x53a400f7,0xbc070110}}, // _dažā, льда, مملك, ычай,
+ {{0x2d8ec94b,0x637887e0,0x61ed01bc,0xade58074}}, // _life_, vínc, _kpal, _कहलन_,
+ {{0x99d78013,0xd5af0117,0x6d4b01df,0x7aff02a6}}, // اتصا, _رہا_, égas, _maqt,
+ {{0x2cf3835a,0x34941b53,0xa4f3816f,0x637d0333}}, // _असेल_, _гарр, _असेच_, lénd,
+ {{0xad9b10ab,0x60de86c0,0xfd9e8870,0x25a902c4}}, // _orúk, òpma, _rụọ_, lpal_,
+ {{0x6d588699,0x637d157a,0xcf2581a8,0x260906af}}, // _ajva, nénd, شرعي, सिपी_,
+ {{0xed5998a0,0xef1684d9,0x3d11801b,0xfd9e8133}}, // кои_, рмы_, ताले_, _pụọ_,
+ {{0x61fb9efa,0xd1678098,0xed4580e8,0x63789e1c}}, // _isul, _съби, рноп, mína,
+ {{0x768b8201,0x61ed494c,0xf77081f9,0xad9b1434}}, // _böyü, _apal, _کال_, _brúk,
+ {{0xe2860139,0x60c501a8,0x6b6607b6,0x273b846d}}, // илни, _adhm, ркма, _dànù_,
+ {{0x7ae98f5d,0x61300b2f,0x998081ac,0x7bc3367e}}, // _mbet, _fäll, šiť_, ánuj,
+ {{0x613004b8,0xdca59317,0x25bf89ca,0x64a59269}}, // _gäll, _таки, msul_, _така,
+ {{0xa06a05d3,0x80d7090f,0x186a0767,0x7ae9b78d}}, // тава_, _बारे, тави_, _obet,
+ {{0xb6a602bc,0x660400e8,0x7d1cc56f,0x637629f4}}, // риал, tvik, örsv, máno,
+ {{0x7dc60077,0x25bf802e,0x63760019,0x66030012}}, // [3900] شقان, nsul_, láno, опта,
+ {{0x3f8f8503,0x66043568,0x3ead8214,0x127b00be}}, // _ligu_, rvik, hbet_, _באפע,
+ {{0xc5f2c94d,0x3cdd8996,0xf5368158,0x6604494e}}, // _अन्य_, _खाने_, סטער_, svik,
+ {{0x25bf8074,0x637d4089,0x78bc494f,0x79a68425}}, // ksul_, cénd, larv, арке,
+ {{0x6b82ac00,0x6722c950,0x63a94951,0x23bc0032}}, // rmog, _rooj, _čene, _bájá_,
+ {{0x637d3b40,0x67228074,0x78bc1bd0,0x31678267}}, // méne, _sooj, narv, gonz_,
+ {{0xd7fa8c6e,0x61fbc952,0x3f8f8326,0x02b60072}}, // тул_, _esul, _bigu_, _अजून,
+ {{0x3ea04953,0x6130016d,0xab278323,0x61348362}}, // ncit_, _säll, _коса_, _dàla,
+ {{0xb5fb0019,0x66e286ce,0x3f8f811b,0x3dcd8176}}, // _száz, क्षक_, _digu_, grew_,
+ {{0x96a68540,0xdce182d4,0x61e2c954,0x612b4955}}, // _ट्रॉ, bolč, mtol, _tüli,
+ {{0x61ed4956,0xa41480c8,0x3ead8ecb,0x6d890267}}, // _spal, ান্য_, bbet_, _džah,
+ {{0x61ed162a,0x27f80114,0x61e28c53,0x3dc002d5}}, // _ppal, dwrn_, otol, ksiw_,
+ {{0x61e2c957,0x61300364,0x9da78098,0x2d968e0b}}, // ntol, _täll, _къща_, _трес,
+ {{0x7523c958,0x7aff0079,0x2703001c,0x3cdd83db}}, // _gonz, _waqt, _ổn_, _खाये_,
+ {{0x637d01df,0xd6db10f8,0xb5fb0019,0xd48f9cff}}, // ténd, кта_, _nyár, _фр_,
+ {{0x75238144,0x6130156e,0x6568c959,0x3f8f008b}}, // _zonz, _jälj, lodh, ögu_,
+ {{0xdb02016d,0x442c955c,0x637d495a,0x78bc3c49}}, // [3910] tslö, lyd_, rénd, barv,
+ {{0x25a9036e,0x78bc495b,0x929d8035,0xb912819d}}, // rpal_, carv, _małe, renụ_,
+ {{0x443ec95c,0x49140054,0xdb020338,0x656f8866}}, // nzt_, दारो_, rslö, îchi,
+ {{0x2731826f,0x63760019,0x69ce001b,0x25a90061}}, // _ráno_, jánl, _तिमी, ppal_,
+ {{0x877a004c,0x290102e7,0x10a58abe,0xfaff0168}}, // _תארי, _kaha_, билн, llës_,
+ {{0x63a4803e,0x2903495d,0xb34784b7,0x3eb90aa2}}, // _činn, ndja_, _saħħ, _hest_,
+ {{0x2901363c,0xb5fb0019,0x01380039,0x2d990c86}}, // _maha_, _gyár, שרות_, _huse_,
+ {{0x3eb90610,0x81e680c8,0x6378826f,0x25a680ee}}, // _jest_, _বছর_, pína, _btol_,
+ {{0xf2d28159,0x6e2d495e,0x3eb9120b,0x61fb8573}}, // _מען_, nyab, _mest_, _usul,
+ {{0xf9908872,0x442c8355,0x68f5495f,0x25bf802e}}, // _قبل_, fyd_, jezd, rsul_,
+ {{0x442cc960,0x237f81c0,0x3cfa009a,0x2d914961}}, // gyd_, _khuj_, _उससे_, _mize_,
+ {{0xe6958013,0x25bf94e4,0x637601ac,0x05c70072}}, // _الإد, psul_, ráno, _लिंब,
+ {{0x8e8580f7,0x78bc3ae2,0x29014962,0x6568c963}}, // _الشه, tarv, _baha_, bodh,
+ {{0x7d1c80f2,0x753ac964,0xa0a30081,0x637629f4}}, // örst, chtz, зард, páno,
+ {{0x29010b06,0x3eb94965,0xd9430e8e,0x236dc966}}, // _daha_, _best_, чери, _okej_,
+ {{0xdd9200f7,0x637d0061,0xdceb8372,0x78bc2818}}, // كور_, téne, _bugħ, sarv,
+ {{0x61300364,0x3bc38201,0x2d910c12,0x29010362}}, // [3920] _jälk, lıq_, _bize_, _faha_,
+ {{0x3eb90006,0x3ea04967,0x2d9101d6,0x29010db1}}, // _eest_, scit_, _cize_, _gaha_,
+ {{0x41760986,0xdd8f0b76,0x3eb94968,0x69c401ac}}, // _پاکس, روم_, _fest_, _hvie,
+ {{0x2cb84969,0x61e2a1da,0x69c40110,0x2ca10046}}, // _werd_, ttol, _kvie, achd_,
+ {{0x237f81c0,0x2d9102d6,0x6130062c,0x7c3e0061}}, // _dhuj_, _fize_, _nälk, szpr,
+ {{0x613004b8,0x6f041df5,0x7bc1c96a,0xada3259a}}, // _sälj, ldic, kslu, _харл,
+ {{0xb9008d38,0x61e2c96b,0x443e2e80,0xa7fc880a}}, // _था_, stol, _åt_, _ayır,
+ {{0x6f040c6b,0x29033610,0x6378816b,0x3f84808e}}, // ndic, zdja_, míno, ummu_,
+ {{0x6130496c,0x442c8355,0x612b13f4,0xa80583a8}}, // _välj, wyd_, _kült, _poñí,
+ {{0x443e834a,0x442c879f,0x69cf0699,0xccfa8eef}}, // tzt_, tyd_, vrce, ући_,
+ {{0x2901496d,0x68f51024,0x351b00be,0x78ba80eb}}, // _raha_, vezd, אוינ, _ietv,
+ {{0x2901003d,0x442cc96e,0x443e81ec,0x6aa2b648}}, // _saha_, ryd_, rzt_, ncof,
+ {{0x667692dc,0x443eb1d6,0x4f6580f7,0x69cf496f}}, // _ادار, szt_, _بالف, urce,
+ {{0x3eb90006,0x69c42d45,0x70d3016f,0x2d9912ec}}, // _sest_, _dvie, तलेल, _ruse_,
+ {{0x40342357,0x3cddc970,0xdbf1001b,0x29034971}}, // перс, _खाते_, _příj, rdja_,
+ {{0x2d914972,0x6e2d4973,0x2d994974,0x6d5c4975}}, // _size_, tyab, _puse_, _ajra,
+ {{0x29010074,0x3eb94976,0x69c005e4,0x7aed4977}}, // [3930] _taha_, _vest_, ámet, _ibat,
+ {{0x78bac978,0xdb1b00f7,0x6f028834,0x3eb90039}}, // _netv, istí, _laoc, _west_,
+ {{0x6e95153d,0x69c41a2f,0x6e2d0359,0x3eb94979}}, // _мину, _zvie, syab, _test_,
+ {{0x6f02817f,0x2d91018f,0x98a78110,0xd2518829}}, // _naoc, _wize_, tinį_, ونا_,
+ {{0x6d4a8082,0x7aed497a,0x4cd080ab,0x200cbfae}}, // _omfa, _mbat, ত্যু, _ardi_,
+ {{0x527c00be,0x612b497b,0x7bc1875e,0x98a78084}}, // אנדא, _gült, yslu, rinį_,
+ {{0x7aed497c,0xe809873c,0x69a01094,0xb8f18072}}, // _obat, विधा_, _गंभी, _वय_,
+ {{0x3eb987ca,0x6d4a83c3,0xf83804de,0x3d17131d}}, // úst_, _amfa, מנות_, नावे_,
+ {{0x78bab66c,0x673bc97d,0x200c9c0a,0xa3cb8075}}, // _fetv, rhuj, _erdi_, _लिह_,
+ {{0x28e0890a,0x7aed1ea2,0x613000f2,0xe8098540}}, // _नायि, _abat, _välk, विदा_,
+ {{0x69c4416f,0xe8559459,0xc4d201c6,0x09be8264}}, // _svie, _بناد, וגי_, _উৎপা,
+ {{0xf8a9004c,0x7bc1c97e,0x3f9a012b,0x28dc000d}}, // _יש_, rslu, _rupu_, _बाहि,
+ {{0x2bc5a9b7,0x7bc1a42f,0x3ebfc97f,0x4ade86af}}, // _विका, sslu, maut_, फ्टव,
+ {{0xb17b00f2,0x3f9a4980,0x7aed0dde,0x7bc1888b}}, // stån, _pupu_, _ebat, pslu,
+ {{0x273c0013,0x2d9f82be,0x6609c981,0xa2e60081}}, // _líne_, lque_, lvek, _дойд,
+ {{0x7d03c982,0x637d366a,0x6f040362,0x7aed01bc}}, // _hans, rénc, udic, _gbat,
+ {{0x7d03c983,0x2d9fc984,0xa526035f,0x69c400e1}}, // [3940] _kans, nque_, омад, _uvie,
+ {{0x2bed823c,0x3f9a0870,0x29040029,0x2d9f9ede}}, // _जहां_, _tupu_, ēmas_, ique_,
+ {{0x7d03c985,0x320d80b9,0x273889c4,0x3ebf8035}}, // _mans, _arey_, _léna_, kaut_,
+ {{0x7d03c986,0x78ba979d,0xfa2580ab,0x320d802a}}, // _lans, _setv, _মেইল_, _brey_,
+ {{0x394e82be,0xe8fac987,0x3d050074,0x7d0388dc}}, // tifs_, илд_, _होके_, _oans,
+ {{0x69c2879f,0x97a68a95,0x7d03871f,0x26c00081}}, // rsoe, орил, _nans, laio_,
+ {{0xdfd08307,0x256f0065,0x3d17000f,0xe80d0327}}, // رية_, rült_, नारे_, हिया_,
+ {{0xa2d601ab,0x7d03bade,0x03679a02,0x644180b9}}, // _भाग्, _aans, _диск_, dzli,
+ {{0x7d03c988,0x9989811a,0x260006f0,0xdb1e8118}}, // _bans, šaš_, रिटी_, supó,
+ {{0x2bd12b51,0x7aed23b9,0x200c807a,0x6b9c4989}}, // _हिमा, _sbat, _trdi_, _kurg,
+ {{0x7d03c98a,0x3ebf81ec,0x66e6c872,0x6f028289}}, // _dans, baut_, _можа, _taoc,
+ {{0x6b9c08bf,0xe3af826a,0x3b068609,0x2d9f8144}}, // _murg, کری_, ndoq_, bque_,
+ {{0xed5a9efc,0xda7b2630,0x637d2ad5,0x7ae080b9}}, // _код_, рян_, géna, _mcmt,
+ {{0x6d4ac98b,0x7d03a28f,0xa3b5864a,0x41d280d4}}, // _umfa, _gans, _चौक_, _सिनस,
+ {{0x2731801c,0x28dc143e,0x69c00118,0x2f190198}}, // _bánh_, _बारि, ámer, бовь_,
+ {{0x4c3b8051,0x7aed06df,0x2fcd0668,0x2731801c}}, // _כתוב, _ubat, čeg_, _cánh_,
+ {{0x7d03c98c,0xd49b498d,0x6d5ac98e,0x3d0f816f}}, // [3950] _yans, аре_, onta, _थोडे_,
+ {{0x6d5ac98f,0xdfdb0021,0x63a48025,0xe6b788fd}}, // nnta, _към_, _činj, _इज्ज,
+ {{0xa2f5835f,0x6d5ac990,0x6b9c4991,0x92dd0264}}, // зпеч, inta, _curg, ড়ী_,
+ {{0xdd95259a,0x6b9c4992,0x659518d1,0x20014993}}, // _намы, _durg, _наму, _ishi_,
+ {{0x6d581351,0xe9d885a8,0x863503de,0x7e7d0ec3}}, // čvar, жкі_, _זאָג_, _uzsp,
+ {{0x6d5a81c0,0x3f894994,0x5ef301bc,0x637604e8}}, // jnta, lmau_, _ịhọọ, tánk,
+ {{0x6b9c16ea,0xc95403c8,0x7d03c995,0x6abb8192}}, // _gurg, ומס_, _rans, _teuf,
+ {{0x63761389,0x61300a92,0xef19a1ae,0x7bc54996}}, // ránk, _väli, сми_, mshu,
+ {{0x7d038983,0x3ebf80e3,0x28dc0107,0x30a79f72}}, // _pans, raut_, _बालि, _древ,
+ {{0x80d701fe,0x644182af,0x69d9c997,0x63764998}}, // _बागे, tzli, muwe, pánk,
+ {{0x7d03821e,0xc4d200be,0x69d9c999,0x2d9f8806}}, // _vans, עגט_, luwe, sque_,
+ {{0x2d999313,0x6d5ac99a,0x2905c99b,0x6441a85a}}, // íses_, anta, _hala_, rzli,
+ {{0x2905c99c,0x7d03c99d,0x20010326,0x7bc50cd5}}, // _kala_, _tans, _ashi_, hshu,
+ {{0x2907c99e,0x7d038b40,0x2731801c,0x29fd81ac}}, // ndna_, _uans, _sánh_, _mňa_,
+ {{0x29058b5d,0xb9250135,0x69d983e9,0x26c0499f}}, // _mala_, _hapụ_, huwe, taio_,
+ {{0x29058079,0x660f0267,0x69d9c9a0,0x7bc50bbd}}, // _lala_, _brck, kuwe, dshu,
+ {{0x26c00098,0x9ad301bc,0x0e6306f9,0x96630dff}}, // [3960] raio_, _dịde, ектн, екте,
+ {{0x649a49a1,0x69d9c9a2,0x26c00ba3,0x69c649a3}}, // стар_, duwe, saio_, mske,
+ {{0x69c649a4,0x389b0158,0xc2f100ab,0xa3b60fd5}}, // lske, _ליינ, য়ারি_, चीं_,
+ {{0x23a1809a,0x290799e7,0x637881d0,0xe3c90216}}, // _mój_, edna_, mínk, _soñé_,
+ {{0x2905c9a5,0x2d9d84df,0x637602ba,0x660f04a8}}, // _bala_, _nuwe_, gáni, _grck,
+ {{0x6b9c49a6,0x6f060ad4,0x29fd81ac,0x6d891c78}}, // _turg, _hakc, _dňa_, _džas,
+ {{0x6d4e49a7,0xb4ca0bf5,0xad9b29c4,0x7c650060}}, // _imba, ोली_, _frús, _قافل,
+ {{0x91e68f04,0x672449a8,0x29079498,0xceb40085}}, // _нобе, njij, adna_, nbə_,
+ {{0xb0dc11bc,0x20dc09c8,0x637602ba,0x2905c9a9}}, // _बांग, _बांध, cáni, _fala_,
+ {{0x6d5ac9aa,0x613dace0,0x69c649ab,0x612f83ba}}, // unta, _pèle, dske, _sølv,
+ {{0x444449ac,0x3cdd8127,0xd6cf854c,0xa438902a}}, // nz_, _खारे_, _пт_, ізму_,
+ {{0x444449ad,0x6b84357f,0x66e38a74,0xdee68d0e}}, // iz_, _chig, _गायक_, _ходи,
+ {{0x6b8402c1,0x6d4e1b6b,0x6e9380f7,0x95868037}}, // _dhig, _omba, _كلما, _елде,
+ {{0x2bc58894,0x92dd00ab,0x6602c9ae,0x25f20074}}, // _विचा, ড়ে_, _isok, ुबनी_,
+ {{0xc86402df,0x63a901a1,0xa4270264,0x776d0118}}, // етти, _čeno, _মধ্য_, toax,
+ {{0x6d4e20d4,0x44441a15,0x7bdac9af,0x3f9e80a4}}, // _amba, dz_, mutu, _mutu_,
+ {{0x7bda81c2,0x444449b0,0x3f890428,0x69c60061}}, // [3970] lutu, ez_, rmau_, cske,
+ {{0x7bc5164d,0x2bd106a7,0x660f01f4,0x6b8400fc}}, // tshu, _हिदा, _vrck, _zhig,
+ {{0x25f90063,0x9f5e85a4,0x629a001b,0xe57012c5}}, // _उनकी_, _esté_, ětov, اطق_,
+ {{0x63760333,0x6d4e4899,0x6602811b,0x69d9a994}}, // táni, _emba, _osok, tuwe,
+ {{0x2905c9b1,0x3f9e8052,0x44440102,0x7bdac9b2}}, // _pala_, _autu_, az_, hutu,
+ {{0x637649b3,0x6d4e00dd,0x3f9e989e,0x2905c9b4}}, // ráni, _gmba, _butu_, _qala_,
+ {{0x4444009a,0x660d2125,0x7bda87b8,0x64e20072}}, // cz_, lvak, jutu, _पातळ,
+ {{0x2905c5bb,0x05a9835f,0xee398fe7,0x44330748}}, // _wala_, овий_, жни_, myx_,
+ {{0x6d890a20,0x29058e20,0x05b9803d,0xa3df816f}}, // _užas, _tala_, زگشت_, धून_,
+ {{0x6b8403c3,0xb4d7b64b,0x7bda854e,0x69c6251c}}, // _shig, िले_, futu, vske,
+ {{0xa3d49094,0x7d070b48,0x637d016b,0x25740106}}, // िंग_, _majs, rénn, mält_,
+ {{0x60c3c9b5,0x7d070390,0x09e200ab,0x929d809a}}, // lanm, _lajs, যবসা, _mało,
+ {{0xe56e8698,0x612f83ba,0x3f9e8bcf,0x25fd0ad5}}, // _аз_, _følt, _zutu_, _रैली_,
+ {{0x69c60d5b,0x7bdabc80,0x6f098c57,0x673d49b6}}, // rske, butu, ldec, _alsj,
+ {{0x6d41c9b7,0x6b8401e4,0x6f060ad4,0xafe32597}}, // mhla, _thig, _pakc, корл,
+ {{0x672b88b3,0x6f09a3ed,0x629b801c,0x69c649b8}}, // _jogj, ndec, _nguo, pske,
+ {{0x09e200c8,0x63a4803b,0x6f0649b9,0xfb87004e}}, // [3980] যবহা, _čini, _vakc, _تدوی,
+ {{0x44440ad9,0x6378c9ba,0x6d41805d,0x25740106}}, // tz_, míni, nhla, jält_,
+ {{0xd91b0158,0x68fc09ca,0xf74649bb,0x711b00be}}, // _וויל, merd, _жено, _וויפ,
+ {{0x44441013,0x68fc292e,0x3cfa0063,0x6ea506a7}}, // rz_, lerd, _उसके_, _कलयु,
+ {{0x444449bc,0xd9469d32,0x6d41810b,0x636803bf}}, // sz_, меди, khla, mınd,
+ {{0x3f9e82a5,0x6d4e49bd,0x636802bb,0xb4d7800d}}, // _putu_, _umba, lınd, िलो_,
+ {{0xd0108d4a,0x613000f2,0x2b0e101c,0x6d41a12b}}, // الب_, _häls, ियाँ_, dhla,
+ {{0x636802bb,0x7bda84a2,0x6d8900d2,0x68fc49be}}, // nınd, vutu, _užar, herd,
+ {{0x10a68364,0x2d9849bf,0x68fc49c0,0x60c389ab}}, // _жизн, _kire_, kerd, banm,
+ {{0x6d41954c,0x7bdac9c1,0x3f9ec9c2,0x25740106}}, // ghla, tutu, _tutu_, mäls_,
+ {{0x68fc49c3,0x2d9849c4,0x44388298,0x660d3bac}}, // derd, _mire_, ár_, zvak,
+ {{0x2d9849c5,0x7c4481dd,0x291a01df,0x2bd1123a}}, // _lire_, _đurđ, _anpa_, _हिसा,
+ {{0xd3790025,0x7bda8006,0x636803bf,0x6d418c49}}, // juće_, sutu, dınd, bhla,
+ {{0x6d4184ac,0x7bdac9c6,0xd3790024,0x26c4c9c7}}, // chla, putu, duće_, lamo_,
+ {{0xa3cb8610,0x636805c5,0x4fc40678,0x273c0125}}, // _लिए_, fınd, кста, _sína_,
+ {{0x26c4c9c8,0x2d982305,0x60c3b969,0x3995026b}}, // namo_, _aire_, zanm, _bùsì_,
+ {{0xd379003a,0x68fc1c37,0xa3dc016f,0x8af78085}}, // [3990] guće_, berd, णूस_, şəkk,
+ {{0x69dd49c9,0x660d0336,0x2d9849ca,0x273c026f}}, // muse, rvak, _cire_, _vína_,
+ {{0x2d9849cb,0x8af9a296,0x69dd49cc,0x61303568}}, // _dire_, знес_, luse, _vält,
+ {{0x26c4c9cd,0x490703bb,0xf38a001c,0x6d41816b}}, // jamo_, िएको_, _bảy_, zhla,
+ {{0xfe088104,0x26c4c9ce,0x69dd49cf,0xad9b00f7}}, // _nữ_, damo_, nuse, _grúp,
+ {{0x637649d0,0xd122146d,0xde1600be,0x3ea949d1}}, // lánt, मायण_, _אַלץ_, lcat_,
+ {{0xb9078006,0x69dd2280,0x60c3c9d2,0x6f0981ec}}, // _भा_, huse, ranm, tdec,
+ {{0x3ea949d3,0x69dd3553,0xdb1b1bc0,0x7afd0c6b}}, // ncat_, kuse, nstä, jest,
+ {{0x7afd49d4,0x29d58c3b,0x3f990115,0x68e50229}}, // dest, _سياس, _misu_, _ochd,
+ {{0x69dd0744,0x9a8483c7,0xfe08801c,0x06828071}}, // duse, _русл, _dữ_, ыгын,
+ {{0x116a84c0,0x26c4c9d5,0x973c826c,0xdb1b0106}}, // _علمی_, bamo_, _suće, kstä,
+ {{0x3f990025,0x60c1810a,0xf4120158,0x26c4ae96}}, // _nisu_, _helm, אפט_, camo_,
+ {{0x68fc438b,0x60c1c9d6,0x69dd0006,0xf38a001c}}, // terd, _kelm, guse, _xảy_,
+ {{0xf1b28051,0x6e20009f,0xfc31987e,0xb606807b}}, // רסם_, _àmbi, _محب_, sráð,
+ {{0x63681010,0x3f9900dd,0x60c1c9d7,0x2d980036}}, // tınd, _bisu_, _melm, _rire_,
+ {{0x28dc0665,0x6d5e01e0,0xb5fb06a5,0x68fc38b9}}, // _बाकि, wnpa, _exám, serd,
+ {{0x63680afe,0x6996007f,0xcf92812a,0x3dc91c33}}, // [39a0] rınd, _прох, רטן_, gsaw_,
+ {{0x63680a0b,0xec098104,0x59c98105,0x26c4baa4}}, // sınd, _kế_, _रिजर, zamo_,
+ {{0x491d0076,0xf5368158,0x2d9806c0,0xd3791bcf}}, // मारो_, עטער_, _vire_, suće_,
+ {{0x3ea949d8,0x20570158,0x661b49d9,0x442602a5}}, // ccat_, _טייל_, _šuke, ćo_,
+ {{0x2d878352,0x20058587,0x635c8353,0x798e0c53}}, // _ohne_, _asli_, nčni, ombw,
+ {{0x3f868358,0x7afd49da,0xdb1b01a3,0x26c48dc5}}, // _shou_, zest, mstå, wamo_,
+ {{0x26c4c331,0x22468019,0x333fae1c,0x3f86822c}}, // tamo_, szok_, _flux_, _phou_,
+ {{0x7afd062f,0x60c18074,0x62840037,0x752e0609}}, // xest, _eelm, _ozio, _hobz,
+ {{0x26c4c9db,0xbb4381bb,0xdb1b0f96,0x6e242c88}}, // ramo_, _сетк, nstå, nxib,
+ {{0xa3cb8b85,0x60c18214,0x7ae2c9dc,0x7afd49dd}}, // _लिख_, _gelm, lgot, west,
+ {{0x2b400eef,0x62840098,0x7bde17a0,0x7afd0888}}, // _blic_, _azio, nupu, test,
+ {{0x69dd0665,0x7ae2c9de,0xec0980ff,0xdbc702f1}}, // tuse, ngot, _dế_, _tööv,
+ {{0x7afd49df,0x7bde0ad4,0xdb1b0106,0x799a8737}}, // rest, hupu, vstä, _kitw,
+ {{0x7afd49e0,0x69dd49e1,0xa4d4835f,0x7bde49e2}}, // sest, ruse, _рокі, kupu,
+ {{0x62840870,0xd7f849e3,0x69dd49e4,0xdb1b12d2}}, // _ezio, нут_, suse, tstä,
+ {{0x799ac9e5,0x26c24001,0x7d0ac9e6,0x69dd49e7}}, // _litw, _ceko_, _hafs, puse,
+ {{0x3ea949e8,0x3f993761,0xdb1b0b2f,0x7d0a80f1}}, // [39b0] rcat_, _visu_, rstä, _kafs,
+ {{0xceeb0077,0xdb1b1f0f,0x3ea9437e,0xfd61001c}}, // _کردن_, sstä, scat_, _huyế,
+ {{0x9f478a21,0x3f990034,0x60c749e9,0xec0980ff}}, // stné_, _tisu_, majm, _xế_,
+ {{0xd6db025d,0x7ae28587,0x60c1c9ea,0x5b2499b8}}, // _это_, ggot, _selm, льча,
+ {{0x799ac9eb,0x6f1d3c72,0x3984016d,0xd7f88087}}, // _bitw, _insc, lösa_, _apă_,
+ {{0x7d0ac9ec,0x98b40289,0x26c20669,0x3cfe8267}}, // _nafs, čeći_, _zeko_, jetv_,
+ {{0xe81400d4,0x60c183cb,0x48e90035,0xb8140f0a}}, // डिया_, _velm, _जानो_, डियम_,
+ {{0x27e700f2,0x43948198,0xa2cd9905,0x78b885ee}}, // _ännu_, _басс, दृश्, abvv,
+ {{0x7b648dc8,0xd7068009,0x60c1c9ed,0x644881ac}}, // _стре, тные_, _telm, ezdi,
+ {{0x29cd0038,0x2bd80b9f,0x6f0d442d,0xdb058192}}, // dľa_, _डिमा, hdac, sphä,
+ {{0xcb6a0463,0x6f1d49ee,0x69cb8c4d,0xd11e8dd2}}, // дане_, _onsc, lsge, पारण_,
+ {{0x69d986a8,0xdd0200c3,0x3cddb26c,0x63680850}}, // orwe, _čuči, _खाके_, dınc,
+ {{0xad9b49ef,0x799a80b4,0xee2e91b3,0xe809a594}}, // _asúm, _yitw, _тн_, विका_,
+ {{0x4999025d,0x23d11094,0xec098104,0x6f1d02af}}, // ется_, _हिंद, _tế_, _ansc,
+ {{0xdb0810ab,0x929d809a,0x8c428a0e,0x69d981ec}}, // ólóh, _całk, _веще, hrwe,
+ {{0x973c8499,0xd36f00f7,0x6729c9f0,0xdb1b108c}}, // _kuća, _وهو_, njej, tstå,
+ {{0xb6a30087,0xdca61810,0x63a3888b,0x7f440168}}, // [39c0] риул, вази, _hunn, rhiq,
+ {{0x63a39682,0xdb1b0bfa,0x1dd4835a,0x7bde49f1}}, // _kunn, rstå, _दिसत, tupu,
+ {{0x7c960ef5,0x40d90158,0x7f4402be,0xd7ca0077}}, // трац, _אַרײ, phiq, _توجه_,
+ {{0x6b89c9f2,0x799ac9f3,0xdb1b0186,0x673b8b67}}, // _cheg, _sitw, pstå, jkuj,
+ {{0x7bde49f4,0xcb13010f,0x6fbe80c8,0x8b6592dc}}, // supu, ילא_, _ইমেই, دالم,
+ {{0x49738160,0x7bde3f8c,0x6d4501ec,0x27fa008e}}, // аліс, pupu, chha, _kppn_,
+ {{0x7af6016b,0x6b9b9d15,0xa87a00be,0x237d822c}}, // _obyt, _fiug, ואַר, blwj_,
+ {{0x6b9b8698,0x657e151e,0x60c701dd,0x799a80b4}}, // _giug, llph, zajm, _witw,
+ {{0xe9a68698,0xd379005c,0x660400e4,0x25f4801b}}, // _разп, juća_, nwik, ्बधी_,
+ {{0x973cc3c9,0xd3791487,0x799a837a,0x205689b1}}, // _duća, duća_, _uitw, _итер,
+ {{0x291e82a3,0x63a38014,0x637d03c9,0xbd06826b}}, // _inta_, _cunn, léni, _abéò,
+ {{0x290ca480,0xfd610028,0x63a3c9f5,0x99ae00ab}}, // _hada_, _quyế, _dunn, _কয়েক,
+ {{0x7d0ac9f6,0xd379005c,0x63a3808e,0x6d86808b}}, // _tafs, guća_, _eunn, rðab,
+ {{0x672f42ac,0x63a3c9f7,0xf8b48665,0x290cc9f8}}, // _socj, _funn, ुरिय, _jada_,
+ {{0x63a3c9f9,0x290cc9fa,0xfd610028,0x6378957a}}, // _gunn, _mada_, _tuyế, níns,
+ {{0xd6d822d2,0xf1a71810,0x6f0d457d,0x2a6901c0}}, // кту_, _ирон, udac, _txab_,
+ {{0x6f0d49fb,0xc299035f,0x69c003a7,0x291ea168}}, // [39d0] rdac, _яких_, âmet, _onta_,
+ {{0x290ca5a7,0xf8b70af3,0x7af613e2,0x63a39bb7}}, // _nada_, _अभिय, _zbyt, _yunn,
+ {{0x4035324f,0x290e8114,0x69c08609,0x63680380}}, // _сенс, ddfa_, _awme, lına,
+ {{0x41c400d5,0x291ec9fc,0x6f0084e8,0x2d9c9357}}, // _حقیق, _anta_, jemc, _live_,
+ {{0x290cc917,0x1959876a,0x637d49fd,0x66f603c8}}, // _bada_, нады_, géni, _גמרא_,
+ {{0x69cbc9fe,0xc4bd80ab,0x60c50197,0x61ed0197}}, // rsge, _ইঞ্জ, _jehm, _mqal,
+ {{0x6d550059,0x60c549ff,0xf7f581ad,0xd379026c}}, // _imza, _mehm, استد, zuća_,
+ {{0x53348f5a,0x61ed05e7,0x80dc00c8,0xaf0580bf}}, // _кест, _oqal, ম্প্, упил,
+ {{0x290cca00,0x63a38e20,0x637d2511,0x6729ca01}}, // _fada_, _sunn, céni, rjej,
+ {{0x290c8341,0x9f5e87e2,0x60c502af,0xc9878e97}}, // _gada_, _està_, _nehm, _руби,
+ {{0x6f0ba888,0x7f42ca02,0x61ed4a03,0x637884e8}}, // _tagc, _floq, _aqal, cíns,
+ {{0xc654803d,0x63a3ca04,0x27350338,0x3f4706ae}}, // افیک, _vunn, _måns_, _jõud_,
+ {{0x973c803b,0xf77092dc,0x290cca05,0x7bda89ff}}, // _kućn, _بال_, _yada_, artu,
+ {{0x06e3959a,0xa11609a7,0x2d9c8039,0x63a3ca06}}, // _गाँव_, دوست, _give_, _tunn,
+ {{0xd3794a07,0x7d01aa33,0x18670ae7,0x6d438037}}, // suća_, mels, гари_, _ilna,
+ {{0x290c003a,0x7af6003e,0x66044a08,0x7c3c8bc5}}, // žda_, _ubyt, twik, ørre,
+ {{0x60c52991,0x637d01df,0x558a096f,0x33200197}}, // [39e0] _fehm, xéni, ебам_, _inix_,
+ {{0x27350186,0x66040077,0x090606e6,0x64648110}}, // _sånt_, rwik, _спон, štiš,
+ {{0x06e3959a,0xdd8f00f7,0x48e9016f,0x9f5e4a09}}, // _गांव_, أول_, _जातो_, ító_,
+ {{0x290c803b,0xb7db010f,0xbda600d5,0x7d01804a}}, // _sada_, _אקטי, _محسو, hels,
+ {{0x290c9dae,0x7d018082,0x799e4a0a,0x6b460032}}, // _pada_, kels, _kipw, _kógb,
+ {{0x64a33056,0x290c9a7b,0x7d018686,0x637d0866}}, // _гара, _qada_, jels, réni,
+ {{0x2d9c8205,0x8c461510,0x7d018f06,0x290cca0b}}, // _rive_, _севе, dels, _vada_,
+ {{0x290c82a3,0x7d0e393e,0x4734a209,0xa81780d7}}, // _wada_, _habs, рнис, اتور_,
+ {{0xa3e68996,0xd2669e25,0x2611864a,0x660d826c}}, // यून_, _скай, तिषी_, _šakr,
+ {{0x7d01920b,0x291eabc6,0x6378ca0c,0x5d54a240}}, // gels, _unta_, píns, ркот,
+ {{0x9f5e9313,0x2498816d,0x443a0901,0xaca48135}}, // _está_, ärm_, nyp_, _ntụz,
+ {{0x6d43ca0d,0x9295249a,0xdb1b1277,0xe9f90129}}, // _elna, ианц, nstø, _ngả_,
+ {{0x60c504a2,0x2d9c83a7,0x7d019c5c,0x61e28110}}, // _pehm, _tive_, bels, nuol,
+ {{0xdd8f92dc,0x7d01ca0e,0x973c811f,0x61ef14cf}}, // _فون_, cels, _kućo, rtcl,
+ {{0x273c001c,0x3f9d8041,0x6d488c41,0x6d5abf3c}}, // _kính_, _yiwu_, mhda, mita,
+ {{0x61e2ca0f,0x7d0e02c4,0x8c1b9101,0xaefb026b}}, // kuol, _aabs, _טווי, _ijùk,
+ {{0x5d553dc4,0x613d8247,0x80ca80ab,0x6e9901a1}}, // [39f0] акат, _sèlm, _রান্, твар_,
+ {{0x6d5a85d8,0x7d0e0079,0x7ae9ca10,0x273c00ff}}, // nita, _cabs, _scet, _lính_,
+ {{0x61fb9a62,0x6d5a8267,0x02d08072,0x63ad81a1}}, // _spul, iita, हणून, _čank,
+ {{0x6d5aca11,0xb17b016d,0x52158991,0x7d01ca12}}, // hita, fråg, идет, zels,
+ {{0x6d5aca13,0x7d01ca14,0x36de86a7,0x90c58110}}, // kita, yels, _गाड़ी_, абле,
+ {{0xdb04020f,0x26c98a20,0x6d5a9c9e,0x29110b80}}, // _punë, _žao_, jita, ndza_,
+ {{0x7d018370,0xb17b0082,0x9f47800d,0xe73181a8}}, // vels, mråd, stní_, قصة_,
+ {{0x25a6808e,0xadc404be,0x7d01ca15,0xb17b006f}}, // _buol_, _amẹr, wels, lråd,
+ {{0x6d5aca16,0x7d01ca17,0x6f0f4a18,0x3f838029}}, // fita, tels, _macc, ēju_,
+ {{0x6d5aca19,0x6d48c4b9,0x6f0f4a1a,0x7d0e0079}}, // gita, ghda, _lacc, _xabs,
+ {{0x7d018370,0x6b8d4a1b,0x69dd044e,0xd469b1f0}}, // rels, _bhag, erse, тине_,
+ {{0x7d0181eb,0x6b8d1c40,0xa3ab85fc,0x672d0365}}, // sels, _chag, _कंठ_, jjaj,
+ {{0xf770ca1c,0x672d20a7,0x6b8d02a3,0x9f048065}}, // _عام_, djaj, _dhag, _دونو,
+ {{0x6d5aca1d,0x6d488782,0xd379005c,0x29032446}}, // cita, chda, vrće_, geja_,
+ {{0x63a70353,0x1fb50221,0xd47980be,0x7d0e4a1e}}, // _nujn, йстр, _באַל, _rabs,
+ {{0x6f0f4a1f,0x6b8d01bc,0x273c046d,0x7d0e4a20}}, // _cacc, _ghag, _kíni_, _sabs,
+ {{0xee372318,0x4b7b00be,0x2a7f81c0,0x7b0800eb}}, // [3a00] ант_, _באקו, _nyub_, āstī,
+ {{0xf53f13c2,0x7d0e02a3,0x14c8003d,0x443a4a21}}, // _står_, _qabs, اهای_, typ_,
+ {{0x6f0f0098,0x6c4a04c1,0x2a7f8642,0x6289810c}}, // _facc, خلاف_, _ayub_, _azeo,
+ {{0x55e60328,0x6d5aca22,0x3945ca23,0xa3e6835a}}, // _комб, zita, _alls_, यंत_,
+ {{0x7d0e01c5,0x637d4a24,0xdb1b0aa2,0x6d5ab6ed}}, // _tabs, mént, rstø, yita,
+ {{0x6d5aca25,0x6f040352,0x6f0f00df,0xb4cf852a}}, // xita, leic, _zacc, षणो_,
+ {{0x6d5aa05a,0xed572481,0x2a7f89c4,0xf1d286a7}}, // vita, _тор_, _eyub_, _सिकन,
+ {{0x6d5aca26,0xdefa8084,0x61e293cd,0x3945809f}}, // wita, тык_, puol, _ells_,
+ {{0x6d5aca27,0x6b8d0355,0x2366807d,0x48e6a2f3}}, // tita, _rhag, mnoj_, _возв,
+ {{0x23668025,0x69dd0052,0x6b8d4a28,0x236901dd}}, // lnoj_, vrse, _shag, _sjaj_,
+ {{0x273c0104,0x6d5a8908,0xc8b581e5,0x29034a29}}, // _tính_, rita, _ысты, veja_,
+ {{0x6b82ca2a,0x63a0933b,0x6d5aca2b,0x69c40372}}, // nlog, _jimn, sita, _bwie,
+ {{0x6f0f2b3c,0x290346b2,0x69dd4a2c,0x05b4806b}}, // _racc, teja_, urse, _امتح,
+ {{0xd5fb8051,0x5ba6828b,0x6f0f4a2d,0x69c42180}}, // _ספור, _криз, _sacc, _dwie,
+ {{0x6f0f0081,0x6b8d008c,0x6d5880b4,0x60c8a25b}}, // _pacc, _thag, _imva, _medm,
+ {{0x236680d2,0x63a7173d,0x29034a2e,0xddc4009a}}, // jnoj_, _rujn, seja_, dził,
+ {{0x23668025,0x672d4a2f,0x212100ff,0xb17b04e1}}, // [3a10] dnoj_, rjaj, _tnhh_, tråd,
+ {{0x64410074,0x6f0f04b9,0x764d1037,0x63a0ca30}}, // _ülik, _wacc, rzay, _aimn,
+ {{0x6f0f4a31,0x69c44a32,0x6b828114,0xb17b006f}}, // _tacc, _zwie, flog, rråd,
+ {{0x2366826c,0x637d0511,0xa3e603b7,0xb17b0f91}}, // gnoj_, cént, पढ़_, sråd,
+ {{0x636802bb,0x6280831d,0x6d589406,0x63a0811a}}, // dınl, _cymo, _omva, _dimn,
+ {{0xdb0780f2,0x2bac801b,0x6280ca33,0x2aadca34}}, // _stjä, _něco_, _dymo, _bóbó_,
+ {{0xe9df00f7,0xb90c0133,0x2366812b,0x60c882d5}}, // hrú_, _achụ_, bnoj_, _dedm,
+ {{0x75350117,0x236682a5,0x6b828980,0x7d0500b9}}, // _hozz, cnoj_, clog, lehs,
+ {{0x7afbca35,0x6d86807b,0x7f4980f1,0x6d470301}}, // _ibut, rðan, rheq, _klja,
+ {{0x63a08e04,0x6f0402af,0x6722ca36,0x39458722}}, // _zimn, zeic, _enoj, _ulls_,
+ {{0x69c44a37,0x753500df,0xeb9a2300,0x6d4701f4}}, // _swie, _mozz, лио_, _mlja,
+ {{0x67228805,0x63a081df,0xa3c914d5,0x7aed01a1}}, // _gnoj, _ximn, लीय_, _ccat,
+ {{0x3da7ca38,0x3ebf859c,0xb5a7a657,0xd77480f7}}, // _треб, mbut_, _трей, _بالع,
+ {{0x4e7a8158,0x2366812b,0x75354a39,0x6722883d}}, // _גאנצ, znoj_, _nozz, _znoj,
+ {{0x637d0c15,0x7afbca3a,0x6f0400eb,0xa78604a3}}, // tént, _obut, teic, مشرو,
+ {{0x69c44a3b,0x6d4716ea,0x6b6b009a,0x27ea806a}}, // _twie, _alja, lęgn, _åbne_,
+ {{0x6f04034a,0x23668025,0x6609ca3c,0xe61a0190}}, // [3a20] reic, vnoj_, nwek, ыда_,
+ {{0x6d41ca3d,0x4cd980c8,0x637d01df,0x753501e8}}, // nkla, ব্রু, sént, _cozz,
+ {{0x2366803a,0x636807d9,0x63a0808e,0xa5dd0035}}, // tnoj_, yınl, _pimn, _पिथौ,
+ {{0xf0930039,0x6609ca3e,0x60c89620,0x63680085}}, // _מנת_, kwek, _sedm, xınl,
+ {{0xceb3093f,0x26c042ee,0x23668025,0x6b82ca3f}}, // _מיר_, mbio_, rnoj_, rlog,
+ {{0x2366803b,0x7afbca40,0x75354a41,0xa7b8ca42}}, // snoj_, _ebut, _gozz, алку_,
+ {{0x6c360288,0x6d5e4a43,0x63a0ca44,0x6d4185b9}}, // _افزا, mipa, _timn, dkla,
+ {{0x6d5e04d2,0x62808114,0x61e6377d,0x543b00be}}, // lipa, _tymo, kukl, _געלא,
+ {{0x61e610f5,0x160e03db,0xfa6a8162,0x3f4706ae}}, // jukl, सिटर_, _саак_, _jõua_,
+ {{0x3ebf8bb1,0x61e64a45,0xf8ac83db,0x9ad301bc}}, // abut_, dukl, _चलिय, _tịke,
+ {{0xdefa8364,0x308680f7,0x9f47826f,0xa3ab8105}}, // _был_, _الاف, stná_, _कूद_,
+ {{0xdc9b0158,0x6d5e4a46,0x2be00dd2,0x973c992c}}, // טייל, hipa, _पटना, _mućk,
+ {{0x6d5e4a47,0x6e968013,0x44274a48,0x98ac90a1}}, // kipa, _الشا, ̣n_, _चलाए,
+ {{0x6d41ca49,0x067b8158,0x6d5e4a4a,0xe3af82e3}}, // ckla, _ענגל, jipa, بری_,
+ {{0x6d5e4a4b,0xa89905a8,0x75350b91,0x657aca4c}}, // dipa, ркву_, _rozz, moth,
+ {{0x3ceb4a4d,0x6d4700d2,0x7eb90098,0x657aca4e}}, // _चाहे_, _slja, ргас_, loth,
+ {{0x171b8158,0x6d47003b,0xf1b9807d,0xa535067c}}, // [3a30] נומע, _plja, _krše_, онач,
+ {{0x657a8bef,0x3ebf8359,0x628d1dd7,0x98c70a0e}}, // noth, zbut_, _izao, _усил,
+ {{0x443eca4f,0x26c00081,0xfbd0003d,0x293800be}}, // nyt_, bbio_, فتن_, _לאזן_,
+ {{0x291301df,0xd9ed0f12,0xe2f98558,0x6d41a1c5}}, // _haxa_, _जमात_, реді_, zkla,
+ {{0x6d41a310,0x75354a50,0x2b4900c3,0x657a81f6}}, // ykla, _tozz, _mlac_, koth,
+ {{0x7ff40077,0xa5bb007b,0x6d47042b,0x59dc0441}}, // _بسیا, _próf, _ulja, _बिसर,
+ {{0x6d418186,0xcfb480ab,0x661d192c,0x6d86808b}}, // vkla, _টিউন, _hrsk, rðal,
+ {{0x7afb8578,0x59c6800d,0x29130118,0x26cb01a1}}, // _ubut, रीहर, _laxa_, _jeco_,
+ {{0x161c000f,0xee3687ac,0x3ebfca51,0x26cb0118}}, // नियर_, жны_, rbut_, _meco_,
+ {{0x61e6011f,0xd3790024,0x661d00cd,0x26c00289}}, // vukl, vrća_, _mrsk, zbio_,
+ {{0xdb1b0789,0xf6d580e8,0xd847ca52,0x2b490c0b}}, // sstý, _діля, _ухап, _blac_,
+ {{0xa3e105e8,0xf7708013,0x6e24c7db,0x61e64a53}}, // _दिन_, _كان_, _šibe, tukl,
+ {{0xa3c9016f,0x6d41ca54,0x29130be9,0xdb1e83ba}}, // लीत_, pkla, _baxa_, rspø,
+ {{0x443e9122,0x2907ca55,0x31af080a,0x07a60226}}, // byt_, nena_, _düz_, жавн,
+ {{0xa0a34a56,0x61e64a57,0x475a23bc,0xe73a4a58}}, // дард, sukl, арая_, аев_,
+ {{0x661d0db7,0x77698557,0x2907ca59,0xb3470187}}, // _brsk, nnex, hena_, ruçõ,
+ {{0x2907ca5a,0x80ca80ab,0xdb040216,0x2913011b}}, // [3a40] kena_, _রাস্, _juní, _faxa_,
+ {{0x2d85ca5b,0x661d0289,0x63a40074,0xf1b901d0}}, // nlle_, _drsk, _hiin, íše_,
+ {{0x6d5e16a7,0x63a432d2,0x273c007b,0x2d85bc72}}, // ripa, _kiin, _sínu_, ille_,
+ {{0x6d5e4022,0x64990c51,0x7f4d0144,0x31af061c}}, // sipa, стур_, chaq, _yüz_,
+ {{0x63a44a5c,0x67264a5d,0xdb00ca5e,0x657a81f6}}, // _miin, _inkj, _jimé, yoth,
+ {{0x61438009,0x63a418c2,0x2bda8035,0xceb4011c}}, // меча, _liin, _भिला, lcə_,
+ {{0xaada01fe,0xb4e601ce,0x98be8087,0xa7fc811c}}, // _भयंक, फली_, _altă_, _axır,
+ {{0x44208a56,0x63a44a5f,0x963494d6,0x973c805c}}, // _či_, _niin, зниц, _kući,
+ {{0x550691e9,0xdb1b02af,0x628400b9,0xcd069033}}, // очна, rstü, _nyio, очни,
+ {{0x63764a60,0xdd908872,0x443eca61,0x2b491a2e}}, // mány, عود_, tyt_, _slac_,
+ {{0xd6cfca62,0xf1b982a5,0x2b494a63,0xad1b00be}}, // _от_, _vrše_, _plac_, בויר,
+ {{0x74ba85e8,0x8cba8ebf,0x443ea133,0x657aca64}}, // _श्रृ, _श्रो, ryt_, soth,
+ {{0xf2d39b9e,0xd37900fe,0x63a4404b,0xfd0f8077}}, // וער_, nući_, _diin, رجی_,
+ {{0x26cb06ea,0x443eca65,0x6602b13c,0x63b880ff}}, // _seco_, pyt_, _ipok, _ttvn,
+ {{0x63a40012,0xb4d6800c,0xa1578039,0x7d08ca66}}, // _fiin, िणी_, קבלה_, meds,
+ {{0x29130962,0x3cfd000d,0x2907ca67,0x661d01dd}}, // _waxa_, र्ने_, zena_, _prsk,
+ {{0xd3790025,0x29132c59,0x2907ca68,0xdb00ca69}}, // [3a50] jući_, _taxa_, yena_, _gimé,
+ {{0xd379003a,0x290792f5,0x7d0881a3,0x201eca6a}}, // dući_, xena_, neds, _orti_,
+ {{0x61e287f1,0x76408428,0x4ab99273,0xd7a98035}}, // erol, hymy, буля_, _चूंच,
+ {{0x7d088022,0x6d5c2d68,0xa3ccaa47,0x80ca80ab}}, // heds, _emra, शीय_, _রাষ্,
+ {{0xd3790052,0x201e96b2,0x776980ee,0xdb00816a}}, // gući_, _arti_, vnex, _ximé,
+ {{0xe817b2dd,0x2b9200e1,0x201e808e,0x7d088140}}, // थिला_, máce_, _brti_, jeds,
+ {{0x2907853e,0x25e30b9f,0x236d816d,0x6d450234}}, // rena_, टंकी_, _tjej_, mkha,
+ {{0x81aa80c8,0x27e7ca6b,0x3f5701b9,0x15ef06ae}}, // _খবর_, runn_, għux_, _चमार_,
+ {{0x2907ca6c,0x201e8ad4,0x7769822b,0x2d85823e}}, // pena_, _erti_, rnex, tlle_,
+ {{0x63a40df8,0x2d85a1aa,0x6d454a6d,0x613d8580}}, // _siin, ulle_, nkha, _cèlt,
+ {{0x2d85862f,0xa3e68b9f,0xdb00ca6e,0x6602bc95}}, // rlle_, यूल_, _simé, _epok,
+ {{0x2d8581df,0xd8748591,0x69c9ca6f,0x660d0234}}, // slle_, _غالب, _kwee, kwak,
+ {{0x7d08ca70,0x63a44a71,0x4a430081,0x69c985ee}}, // beds, _viin, еняв, _jwee,
+ {{0x25bfad7b,0xfbd180f7,0xd1320019,0xc7b88140}}, // mpul_, _كتب_, رمز_, _hrđa_,
+ {{0x6561ca72,0x6023002d,0xd8233943,0x3d0f0035}}, // milh, ндра, ндри, ड़ने_,
+ {{0xcc3a8158,0x0eaa0a8e,0x6aa9ca73,0x38650174}}, // _לעצט, скай_, _ngef, úlra_,
+ {{0xe7371172,0x6d5c00b9,0xa5bb0032,0xc7b88088}}, // [3a60] чет_, _pmra, _arób, _mrđa_,
+ {{0xd379005c,0x65618187,0x98ac8084,0x63760061}}, // vući_, nilh, _kodą_, vány,
+ {{0x31570159,0x4915170c,0xe7eb000f,0x6441831d}}, // _זיין_, _फोटो_, जूदा_, nyli,
+ {{0x7d088063,0x290a4a74,0x6da6045e,0x6e2003a8}}, // zeds, leba_, _нима, _ámbo,
+ {{0x26d24a75,0x6d450326,0x6f098df6,0xe5a61289}}, // mayo_, bkha, deec, _хиги,
+ {{0x61e29220,0xe4da1125,0x26d24a76,0x6d5c0d1b}}, // rrol, _دوست_, layo_, _umra,
+ {{0x6561ca77,0x28bf0d14,0xd9d9001b,0x7d0884d6}}, // dilh, ्रति, _भट्ट, veds,
+ {{0x26d24a78,0x61e28754,0x657e4a79,0x64a30110}}, // nayo_, prol, loph, _часа,
+ {{0x91e38468,0x7538a7aa,0xeb979577,0xbebb0cab}}, // _поте, _povz, _хит_, _poëz,
+ {{0x290a453d,0xb3e006a7,0x2c000074,0x73d89ad8}}, // jeba_, _पटाख, _ईहां_, одор_,
+ {{0x7d08ca7a,0x76408364,0x26d20418,0x69c981ed}}, // reds, symy, kayo_, _zwee,
+ {{0x657e22f1,0xc7b384de,0x6d4b01a9,0x394c8174}}, // hoph, _טבע_, īgai, _hlds_,
+ {{0x7afda9ed,0x63ae11fe,0x6602b731,0xf1b989d1}}, // _öste, _lubn, _upok, _krša_,
+ {{0x6d4a808e,0x3f823360,0xad9b0333,0xada38073}}, // _ulfa, _akku_, _apún, _зачл,
+ {{0xd6db163f,0x7d150353,0x34fb81c6,0x657e4a7b}}, // йта_, _razs, _ההוד, doph,
+ {{0x29184a7c,0x35d18105,0x26d2260b,0x8f340791}}, // adra_, _दौड़, gayo_, несц,
+ {{0xaac32207,0x4ac323bd,0xae1f8054,0x6d452322}}, // [3a70] _व्यक, _व्यव, बियन_, tkha,
+ {{0xa5bb2422,0xd49b1bab,0x660d450b,0x290a0644}}, // _prób, бре_, rwak, ceba_,
+ {{0x26d200f6,0x660d0726,0x6d45332e,0xa5bb3cd4}}, // bayo_, swak, rkha, _bróc,
+ {{0x1ddd809a,0x6d454a7d,0xf1b98267,0x7fd604de}}, // _मिलत, skha, _arša_, _קורס_,
+ {{0xbef88b9f,0xdb020106,0x657e0234,0x63760118}}, // ्जैन_, pplö, boph, ránx,
+ {{0xf8bf024c,0x3f890114,0x657e01f6,0x2bac81d0}}, // ्रिय, llau_, coph, _těch_,
+ {{0x656183a7,0xe0d09a37,0x3f890114,0x7bdc8061}}, // vilh, ازه_, olau_, árul,
+ {{0x39841bc0,0x290a009a,0x69c99457,0x321f801c}}, // löst_, zeba_, _twee, _truy_,
+ {{0x50bf000f,0xa8a78dae,0x1ddd816f,0xd3791487}}, // ्राष, _ерек, _मिळत, nuću_,
+ {{0x61e4ca7e,0x25bfca7f,0x26d2005d,0x7afd1384}}, // áile, rpul_, zayo_, efst,
+ {{0x63ae4a80,0xeb9a002e,0x6561ca77,0x26d21fca}}, // _xubn, жин_, rilh, yayo_,
+ {{0x657e1c96,0x8c1a0039,0x3cfd016f,0x2901198d}}, // zoph, קורי, र्ते_, _abha_,
+ {{0xd379012b,0xa3b2800f,0x7e6902d4,0xe5e680d7}}, // juću_, _झूठ_, _žepn, _قزوی,
+ {{0x59e0816f,0x26d24a81,0x200580b9,0x2242004a}}, // _निसर, wayo_, _kpli_, rykk_,
+ {{0x26d231bc,0x290a4a82,0x29184a83,0xb4e98576}}, // tayo_, reba_, rdra_, यली_,
+ {{0x60c11010,0x26cf8063,0x7bc18197,0x63ae0140}}, // ılma, _jego_, mplu, _rubn,
+ {{0xd1ba0b76,0x6b8402a0,0x26d20924,0x3a200077}}, // [3a80] _بابا_, _ikig, rayo_, _urip_,
+ {{0x26d2005d,0x3eb9007b,0x61eb8558,0xf507002e}}, // sayo_, _efst_, dugl, мнул_,
+ {{0xe73a0381,0x657e4a84,0xa3c9016f,0x7bc1ca85}}, // _ден_, roph, लील_, nplu,
+ {{0x26cf803a,0x27e50038,0x728a95a6,0x394c8216}}, // _nego_, álne_, обег_, _slds_,
+ {{0x75490117,0x61eb80fe,0x2bac81d0,0x6b8403e4}}, // ászó, gugl, _věci_, _mkig,
+ {{0xe8948c5c,0xa5bb0035,0x7bc18299,0x5183af75}}, // наль, _wróc, kplu, куща,
+ {{0x9f4301d0,0x26cfca86,0xb90680c2,0x59e08107}}, // zují_, _bego_, _भय_, _निवर,
+ {{0x4444309c,0x8e1485c2,0x48de052a,0x1e1495a4}}, // my_, едиц, कृतो_, емия,
+ {{0x753c0063,0x6a8397ae,0xe9d78d5f,0x3ea28106}}, // _korz, _алта, _окт_, äkt_,
+ {{0x4a578039,0x69c289ff,0x9f4301d0,0x6b840f45}}, // _חשמל_, mpoe, vují_, _akig,
+ {{0x44440f66,0xdb00ca87,0x973c8369,0xdb1b0198}}, // ny_, _limí, _suću, istö,
+ {{0xf6d68bca,0xa3e10e5b,0x3a26026c,0x26cf80c3}}, // _آزاد, _दिस_, _šopa_, _gego_,
+ {{0x44440775,0x51871507,0xc33382f6,0x527b80be}}, // hy_, дува, דור_, ַנלא,
+ {{0x44440a56,0x6e22948e,0x753c00ee,0x6b8402ec}}, // ky_, _irob, _norz, _ekig,
+ {{0x80d380c8,0x6e22816b,0x1fa70e63,0x140d0327}}, // _সাম্, _hrob, драг, _सनेह_,
+ {{0x2bd80996,0x5f940468,0x9f580247,0xf7700124}}, // _डिजा, вист, ntrè_, لاق_,
+ {{0x44440079,0x2005001b,0xd2520019,0x88bc81d0}}, // [3a90] ey_, ůli_, _منٹ_, _vněj,
+ {{0x30150c8e,0x7d1abff4,0x6d48ca88,0x4444309c}}, // _здор, ndts, nkda, fy_,
+ {{0x44444a89,0xa3c90b9f,0xd3791bcf,0x36d50009}}, // gy_, लीं_, suću_, _понр,
+ {{0x309a0051,0x6d4e4a8a,0x9f4780e1,0x16350081}}, // _משתמ, _elba, stnú_, _земя,
+ {{0x753c4a8b,0x26cf92f5,0x7d18816d,0x44444a8c}}, // _forz, _rego_, _oavs, ay_,
+ {{0x44440f4c,0x6f0d0c64,0x61eb811f,0x41e080d4}}, // by_, leac, rugl, _निरस,
+ {{0x62868bc5,0x4444137c,0x61eb8042,0x673d01a1}}, // _økon, cy_, sugl, _kosj,
+ {{0x6f0d154c,0xee398dd3,0x6e228114,0xac940d15}}, // neac, зни_, _brob, _расш,
+ {{0xe1f0826a,0x68138201,0x6abb81bc,0x6e228115}}, // _نسل_, _mədə, _ifuf, _crob,
+ {{0xe00f0c69,0x3159990c,0x29cd4a8d,0x6f0d0ad0}}, // ाबाद_, _علاج_, dža_, heac,
+ {{0x26cf8d38,0x63a981cd,0x62898683,0x636802d0}}, // _tego_, _kien, _hyeo, kınt,
+ {{0x91e60256,0xf092807c,0x79854a8e,0x81d700ab}}, // номе, _גנב_, _akhw, _সহজ_,
+ {{0x6f0d154c,0x63a9892d,0x69d9ca8f,0xdc3b881d}}, // deac, _mien, nswe, _išče,
+ {{0x63a9ca90,0x6289ca91,0x6f1bc3ca,0x7bc193ac}}, // _lien, _myeo, lduc, pplu,
+ {{0x6e228063,0xf1dc0996,0x6f0d00f7,0x6b464a92}}, // _zrob, _बिकन, feac, _lógi,
+ {{0x44440a56,0x63a9ca93,0x6f0d4772,0xd252045b}}, // vy_, _nien, geac, _منع_,
+ {{0x44440d38,0x2b0020f2,0x9f584a94,0x69cd0114}}, // [3aa0] wy_, ष्णु_, ntré_, _gwae,
+ {{0xa3e386a7,0x60d53cd4,0x98a582d6,0x69d989ff}}, // _फटा_, cazm, _anlč_, dswe,
+ {{0xdb1b2298,0x63a9ca95,0xdd8f187e,0x44444a96}}, // rstö, _bien, حول_, uy_,
+ {{0x444414e3,0x63a991b9,0x291a022e,0xa5bb016a}}, // ry_, _cien, _hapa_, _irón,
+ {{0x63a98f29,0x4444096a,0x05d48072,0xdcfc01a9}}, // _dien, sy_, _धबधब, _ekrā,
+ {{0x4444185c,0xa5bb007b,0x63a9ca97,0xdb040073}}, // py_, _krón, _eien, _funç,
+ {{0x2d980352,0x69c2a023,0x63a98687,0x69d98a53}}, // _ihre_, spoe, _fien, aswe,
+ {{0x291a4a98,0xa84780f7,0xdb0b00e8,0x7d18ca99}}, // _lapa_, عليم_, _utfø, _savs,
+ {{0x3cfd0540,0x6289810c,0x6b82ca9a,0x52cc81d0}}, // र्वे_, _gyeo, moog, ारीस,
+ {{0x291a4a9b,0x6b828006,0x63a98102,0x672bca9c}}, // _napa_, loog, _zien, _engj,
+ {{0xd498835f,0x6d48ca9d,0x22468019,0x6f19ca9e}}, // ької_, rkda, lyok_, _fawc,
+ {{0x6e2287e2,0x6f02ca9f,0x63a981c0,0x290ecaa0}}, // _trob, _uboc, _xien, lefa_,
+ {{0x291a1083,0x7d188214,0x6e2281ac,0x22468019}}, // _bapa_, _tavs, _urob, nyok_,
+ {{0xa3e10740,0x291a4aa1,0x290e8c0b,0x6b82af26}}, // _दिल_, _capa_, nefa_, hoog,
+ {{0x673d030b,0x6f0d0af9,0x1cb7812a,0xa5bb02ba}}, // _posj, teac, _אלול_, _crón,
+ {{0x68138201,0x2d8c816d,0x6b82b0be,0x7bdabfa4}}, // _qədə, llde_, joog, mstu,
+ {{0x6f0d1523,0x62350dca,0xf1a71fab,0x63a9caa2}}, // [3ab0] reac, _репу, _прон, _rien,
+ {{0x6f0d154c,0x62898708,0x4ea403c7,0x442302f7}}, // seac, _ryeo, урса, _prj_,
+ {{0x63a9caa3,0x9cd78051,0x41b7000f,0x2d9e8fb0}}, // _pien, גובה_, _इंडस, imte_,
+ {{0x7bdacaa4,0x69d9caa5,0xeab08154,0xdb07caa6}}, // istu, tswe, معه_, _bujá,
+ {{0x63a9caa7,0xdb078125,0x22468065,0x213e8051}}, // _vien, _stjó, gyok_, _both_,
+ {{0xceb40086,0x7bdacaa8,0xfbd30154,0x26c94aa9}}, // ndən_, kstu, _متر_, lbao_,
+ {{0x63a9caaa,0xe7399687,0x69c087d9,0xac940196}}, // _tien, дей_, _etme, _барш,
+ {{0x2d8c8cfa,0x61e4bb2e,0x7bc501f6,0x2246808e}}, // elde_, šila, mphu, byok_,
+ {{0xb11580b3,0x81bb80ab,0xceb40085,0x7bdacaab}}, // _имаш, _আটক_, kdən_, estu,
+ {{0x637d0065,0xdb0e8511,0x7bda867f,0x6b460789}}, // mény, _rubé, fstu, _jógv,
+ {{0x9f5825bf,0x7bdacaac,0x316780b9,0x15f3064a}}, // stré_, gstu, minz_, इंडर_,
+ {{0x291a3204,0x069980f7,0x2d8ccaad,0x61fd4aae}}, // _sapa_, _قناة_, alde_, ftsl,
+ {{0x291a3f71,0x20d600e8,0xe29703c7,0xceb4011c}}, // _papa_, ківс, каю_, fdən_,
+ {{0x6e200da1,0x3f8d894d,0xf8d19a3b,0x6d478338}}, // _ámbi, lleu_, हरिय, öjad,
+ {{0x4879212c,0x5d7a83de,0x61fd1c11,0xb8ea03ca}}, // естя_, _קאצק, atsl, _ऊभ_,
+ {{0x27e501ac,0xa3e68beb,0xe8030697,0x1fe380ab}}, // álna_, _पित_, _लहरा_, _মহাস,
+ {{0x8fa31285,0x291a4aaf,0x07a310f8,0xdcfa80eb}}, // [3ac0] рате, _tapa_, ратн, lotā,
+ {{0x64458006,0x63ae81a8,0xf29701c6,0x9f4302df}}, // _ühis, _úiné, _בכפר_, rujá_,
+ {{0x6b82cab0,0x66e60dca,0x213e8252,0xdcfa81a9}}, // toog, _иона, _roth_, notā,
+ {{0xf7460d0e,0xa5bb0035,0xb4ae86a7,0x41e68a4c}}, // _ремо, _król, _कल्_, віка,
+ {{0x6d8681fa,0x637d0061,0x333f8036,0x6b82cab1}}, // iðar, gény, _doux_, roog,
+ {{0x7bc50640,0x7bdacab2,0xf80680e8,0xdcfa81a9}}, // bphu, ystu, вчин, kotā,
+ {{0xb50206bf,0x9d1b00be,0x351b0039,0xdfd080f7}}, // र्णय_, _קולט, _קולנ, حيب_,
+ {{0x2d8786c0,0xe7e38105,0x290ecab3,0xceb40085}}, // _akne_, _गिरा_, sefa_, zdən_,
+ {{0x7d1c4ab4,0x7bdabdd7,0x68ed8019,0x69c08163}}, // _kars, wstu, ándé, _utme,
+ {{0xfe6f9301,0xf1dc0fea,0x7bdaad4d,0xdcfe00fe}}, // تدى_, _बिजन, tstu, kopč,
+ {{0xc0a980f7,0x2d9eb3fc,0xa5bb0032,0xa0a68081}}, // _كامل_, rmte_, _aról, _ражд,
+ {{0xe50203eb,0x7bdacab5,0xd5e2058c,0x7d1c0f06}}, // र्ति_, rstu, _पिंज, _lars,
+ {{0x29042828,0x7d1c011b,0x9b581cc7,0xa3e6aeff}}, // đman_, _oars, тият_, यूज_,
+ {{0x70bf0aed,0x6d86807b,0x61ef0503,0x7d1c4ab6}}, // ्रेल, rðas, rucl, _nars,
+ {{0xceb40201,0xd37b0fe7,0x224d1eee,0xdfd80098}}, // rdən_, _очи_, šek_, кът_,
+ {{0xd4378039,0x34cb2769,0x6568cab7,0x3eaf128d}}, // _ירוק_, िर्द, lidh, øgt_,
+ {{0x333fcab8,0x26c90bcf,0xe3b8811c,0x680a811c}}, // [3ad0] _roux_, rbao_, lqı_, _hədd,
+ {{0x7d1c4ab9,0xbebb020f,0x2b8e803e,0x637d0019}}, // _cars, _anët, lých_, vény,
+ {{0x7d1c4aba,0x29114abb,0x6ca704bd,0x2ca70bd7}}, // _dars, meza_, траж, änd_,
+ {{0x2b8e8775,0x29112420,0x7d1c0706,0xd4980a95}}, // ných_, leza_, _ears, тру_,
+ {{0x7d1c4abc,0x63ad4abd,0xe1ff1355,0x64488114}}, // _fars, _mian, ntó_, hydi,
+ {{0x29114abe,0x7d1c4abf,0xd5fa0051,0x98c602a5}}, // neza_, _gars, _אפשר, čući_,
+ {{0x2b8e8ed7,0xccf20051,0x6142a3e7,0x3f84bebe}}, // kých_, _לכם_, сеща, fomu_,
+ {{0xe61994b7,0x63ad4ac0,0x4429976d,0x2911300f}}, // еди_, _nian, _ča_, heza_,
+ {{0x29111b83,0x2b8e816b,0x69dd00f3,0x6b9bcac1}}, // keza_, dých_, jsse, _ihug,
+ {{0x6f1d4ac2,0xdcfa8029,0x80ca873c,0x28ac800d}}, // _lasc, totā, _स्पे, _चलचि,
+ {{0x63ad0098,0x63b502a5,0x69dd4ac3,0x3f8480b8}}, // _bian, _juzn, esse, bomu_,
+ {{0x6f1d4ac4,0xc7b28039,0x63ad4ac5,0x3f84cac6}}, // _nasc, _הבא_, _cian, comu_,
+ {{0x63ad1ad4,0x6d868125,0xee2e8193,0x65688010}}, // _dian, rðar, _ун_, bidh,
+ {{0x2911300f,0xaca38135,0x660b80b9,0xe00f064a}}, // geza_, _atụk, _ipgk, ाबंद_,
+ {{0xdee6134e,0x63ad4ac7,0x2d85cac8,0x66e64ac9}}, // лови, _fian, mole_, лова,
+ {{0x63ad35db,0xd6d98035,0x80ca8c2d,0x2f552714}}, // _gian, nał_, _स्ने, атус,
+ {{0x7d1c014c,0x29114aca,0x7ae40289,0xceb284de}}, // [3ae0] _pars, beza_, _idit, מין_,
+ {{0xdca62eb2,0x4427cacb,0x7d1c0be9,0x6b9bac18}}, // гази, _krn_, _qars, _ahug,
+ {{0x6f1d291e,0x69c40372,0x2d858037,0xd6d9866f}}, // _fasc, _htie, iole_, kał_,
+ {{0x7d1c4acc,0x2d858234,0x63b5007d,0x6b9b80f7}}, // _wars, hole_, _duzn, _chug,
+ {{0x673b8efd,0x298a8ae7,0x614694b7,0x7d1c4acd}}, // djuj, нско_, леда, _tars,
+ {{0xc27b00be,0xb605807a,0x6b89819d,0x2d85c902}}, // ערסי, _mašč, _ekeg, jole_,
+ {{0xa3e1090a,0x9f479b2c,0x2d85cace,0x7ae44acf}}, // _दिए_, stný_, dole_, _odit,
+ {{0x20d88110,0xdb1900e1,0x7ae42eae,0x29110b5e}}, // džių_, _otvá, _ndit, zeza_,
+ {{0x2b8e8ed7,0x70f684e3,0x80d380c8,0x63ad4ad0}}, // vých_, _مسائ, _সার্, _rian,
+ {{0x7ae42822,0x2d85cad1,0x551406a7,0x7bde00b9}}, // _adit, gole_, ड़िए_, ospu,
+ {{0x261405e8,0x6568cad2,0x291100fa,0x2b8e803e}}, // नौती_, ridh, veza_, tých_,
+ {{0x2911229b,0x291ecad3,0xb6058754,0xd4978196}}, // weza_, _hata_, _bašč, ыры_,
+ {{0x291ea7ba,0x29114ad4,0x2b8e803e,0x645a8065}}, // _kata_, teza_, rých_, szti,
+ {{0x2d85cad5,0xd379011f,0x6e93880b,0xe1ff285c}}, // cole_, vrću_, _الها, rtó_,
+ {{0x29110fb7,0x291ecad6,0x69c44ad7,0xe1ff4ad8}}, // reza_, _mata_, _etie, stó_,
+ {{0x2f1802c7,0x7bde0aa2,0x291ecad9,0xe1ff01ca}}, // лось_, dspu, _lata_, ptó_,
+ {{0x29112467,0xdb0e8144,0x59e090a1,0x63b500fe}}, // [3af0] peza_, _cubí, _निगर, _suzn,
+ {{0x6b9bb3ed,0x291ecada,0x63a281e2,0x6f1d01ec}}, // _shug, _nata_, emon, _wasc,
+ {{0xb6058db7,0xc1740039,0x6f1d4adb,0x3a2902c4}}, // _zašč, _לחץ_, _tasc, _hrap_,
+ {{0xfbd00077,0xd2518013,0xf2960091,0x3a29000b}}, // ستم_, ينا_, _abẹ́_, _krap_,
+ {{0x291eb4b1,0xf1b98904,0xe704026a,0xa3c91094}}, // _bata_, _krši_, _اسپی, लीज_,
+ {{0xd6d98063,0x98ba0029,0x63b50503,0xa069bd93}}, // wał_, _kopā_, _tuzn, кала_,
+ {{0x6b9b8c64,0x2d858098,0x291ecadc,0x2f55cadd}}, // _thug, vole_, _data_, итис,
+ {{0x5ba48436,0x6b460118,0x2d85cade,0xddc98035}}, // _груз, _lógr, wole_, cześ,
+ {{0x291ecadf,0x00e5803d,0x4427c51b,0xdb0e8118}}, // _fata_, _نتای, _srn_, _xubí,
+ {{0x0565be41,0x291ecae0,0xd6d9809a,0xb6058289}}, // рвон, _gata_, sał_, _rašč,
+ {{0x2815803d,0x66160035,0xeb9a1073,0x3a294ae1}}, // _خواس, zwyk, кио_, _arap_,
+ {{0xe81b800f,0x291e80a4,0x34b20816,0x25ee8072}}, // _भैया_, _zata_, _जल्द, _आमची_,
+ {{0xed4ea64c,0x291ecae2,0x26cd816a,0x4427808e}}, // _до_, _yata_, mbeo_, _wrn_,
+ {{0x64a58f04,0x44278390,0xa5bb0035,0xdb158168}}, // _кала, _trn_, _trój, _buzë,
+ {{0x6d4391cd,0xa0a6280f,0x4427cae3,0x7bc380dd}}, // _iona, равд, _urn_, _utnu,
+ {{0x6e248754,0x3ebfbb5b,0x6d438102,0x70d1800d}}, // _šibi, ncut_, _hona, हरुल,
+ {{0x05664ae4,0x6b460013,0x6d43cae5,0x6f041ac6}}, // [3b00] аван, _fógr, _kona, rfic,
+ {{0x6e24016a,0x6f044ae6,0x7bde4ae7,0xa3e1066f}}, // rvib, sfic, tspu, _दिख_,
+ {{0xe5020054,0x60dc4ae8,0xaaba03a4,0x161c8105}}, // र्षि_, marm, _इलाक, _बैनर_,
+ {{0x291e803a,0x60dc4ae9,0x7bde4aea,0xdb0e8118}}, // _sata_, larm, rspu, _tubí,
+ {{0x70d1800d,0xf8da00c2,0x320204e8,0xda63a42e}}, // हरूल, _बजाय, ntky_, овци,
+ {{0x63a2caeb,0x26c04aec,0x60dc007b,0x3f920683}}, // smon, lcio_, narm, llyu_,
+ {{0xd01081a8,0xb8deb26c,0xa8a38081,0x201a8037}}, // سلة_, _इल_, орък, ìpi_,
+ {{0x26c0100a,0x291e83c3,0x6d438ad0,0x9c828efc}}, // ncio_, _wata_, _aona, _účin,
+ {{0x291e81bf,0x6d43caed,0x60dc4aee,0x26c04aef}}, // _tata_, _bona, karm, icio_,
+ {{0x6d438cac,0x5d5481f3,0x60dc4af0,0x2b92016b}}, // _cona, скот, jarm, lách_,
+ {{0x60dc4af1,0x34b70039,0x3320009f,0x69c04af2}}, // darm, יפים_, _baix_, ímen,
+ {{0x2b920626,0x680a8201,0x6d438118,0x3d060f0a}}, // nách_, _tədb, _eona, स्ते_,
+ {{0x60dc4af3,0x6d43caf4,0x4cd580ab,0xee948019}}, // farm, _fona, _দারু, _اداک,
+ {{0x6d43caf5,0xc98700b3,0x224b016d,0x2b92016b}}, // _gona, руми, ryck_, hách_,
+ {{0xf1b98024,0x2b92026f,0x32021e1e,0x656f8866}}, // _vrši_, kách_, atky_, èche,
+ {{0x442d252d,0x6d43caf6,0xf77301ad,0x9f4301d6}}, // _če_, _zona, زاز_, lujú_,
+ {{0x61e48796,0x236906c0,0x442a4af7,0x60dc13c4}}, // [3b10] šilj, _imaj_, _arb_, barm,
+ {{0xa2c9800d,0x60dc4af8,0x7afb8087,0x443a0216}}, // हुन्, carm, _scut, fxp_,
+ {{0xdcf8801b,0x92d900ab,0x320db581,0xf1cf90be}}, // _skvě, ালী_, _spey_, _सौजन,
+ {{0x26c00098,0x442a00ee,0x2121008e,0x9f4300e1}}, // ccio_, _drb_, _hahh_, hujú_,
+ {{0x442a4af9,0x6d5ac00d,0x3ebf82d0,0x6b8d0c53}}, // _erb_, khta, vcut_, _mkag,
+ {{0xe8f8804a,0xdcfc066f,0x2bd90180,0xf4350012}}, // илі_, _okrę, _مارک_, _легэ,
+ {{0x44254afa,0x442a00d2,0x3f6984ae,0x26d94afb}}, // _él_, _grb_, лико_, _keso_,
+ {{0xef198993,0x21210a84,0x7c25807b,0x7afb8b80}}, // уми_, _lahh_, _áhri, _ucut,
+ {{0xd4d98112,0x60dc4afc,0x80dd00ab,0x9f584afd}}, // льні_, yarm, _বান্, ntrá_,
+ {{0x3ebf93df,0x6b8d46d6,0x6d438085,0x987800d2}}, // scut_, _akag, _qona, _ušća_,
+ {{0xb8958013,0x40958013,0x6d43cafe,0x60dc4aff}}, // _الاع, _الار, _vona, varm,
+ {{0x60dc4b00,0x212134b1,0xa3ea0107,0x63b880fe}}, // warm, _aahh_, _मिस_, _duvn,
+ {{0x6d43cb01,0x5ea580ab,0x7c2a8192,0x998001f4}}, // _tona, _গ্রে, _erfr, _ariš_,
+ {{0x6d5a9301,0x3202016b,0x2c57811c,0x9f4301d6}}, // chta, rtky_, hədə_, cujú_,
+ {{0x26d94b02,0x3202026f,0x212100dd,0x2c578085}}, // _beso_, stky_, _dahh_, kədə_,
+ {{0x394580e4,0xdb1c84e8,0xdb028a53,0x2efd8901}}, // _lols_, _otrá, rmoë, _jcwf_,
+ {{0x644300f2,0x442a1024,0x2b921c18,0xf7430073}}, // [3b20] änin, _srb_, vách_, чето,
+ {{0x23690503,0x26c04b03,0x61e4113b,0xd9100061}}, // _zmaj_, scio_, _ivil, _تیس_,
+ {{0xdb1ca336,0x61e40a38,0x2b92026f,0xe2caa0bf}}, // _atrá, _hvil, tách_, глед_,
+ {{0xd0488085,0xe73a4b04,0xf1b9842b,0x9f4300e1}}, // _gedə, _сем_, _buš_, zujú_,
+ {{0x672437d7,0x21210101,0x2b92016b,0xaeff8264}}, // mdij, _yahh_, rách_, ্যাণ_,
+ {{0x39459e9e,0x6f1600b9,0x64a61c8b,0x3ce781a1}}, // _cols_, leyc, _гага, _ndnv_,
+ {{0xa3f28a49,0x629b819d,0x3dc6b603,0x3d1402f1}}, // _জন্য_, _ezuo, _stow_, ड़ीं_,
+ {{0xb8ef97ba,0x26d9002a,0xf1b98140,0xc7b301c6}}, // _व्_, _xeso_, _fuš_, הבה_,
+ {{0x66040d66,0xa5bb00f7,0x03a381cf,0x50d680d7}}, // ntik, _prói, _хито, _هزار,
+ {{0xb9968013,0xa3b98f12,0x67240072,0x39459b26}}, // _الطب, _अंश_, hdij, _gols_,
+ {{0x69b2000f,0x61e42f2d,0x66040009,0x974300ce}}, // _इंजी, _avil, htik, šćiv,
+ {{0x6d5acb05,0x6724026c,0x75e1819d,0x04638190}}, // shta, jdij, _ịzer, ятым,
+ {{0x26d9047f,0x4e968c2b,0xc6968019,0x539b83de}}, // _reso_, _اشار, _اشاع, טיטו,
+ {{0x92d900ab,0x7c3eb60c,0x6023128a,0x69b0064a}}, // ালে_, _àpro, jímá, ंदगी,
+ {{0x26d92de9,0xf4848077,0xc8cf864a,0x67228326}}, // _peso_, _باشی, _स्फट, _kaoj,
+ {{0xd6d80ca0,0xe7f08744,0x6b8d1d22,0x764d4b06}}, // йту_, _घटना_, _ukag, tyay,
+ {{0xf77005ff,0x66040bb1,0x93bc8162,0x518701e5}}, // [3b30] راف_, gtik, zvăl, _гуна,
+ {{0x2b921c18,0x61e29388,0x764d4b07,0x201ecb08}}, // váci_, dsol, ryay, _osti_,
+ {{0xdb1c9385,0xd2510416,0xf99404de,0x61e287cd}}, // _strá, _رنگ_, _מרץ_, esol,
+ {{0x394587fa,0xdb19016d,0x66040122,0x59d00b84}}, // _sols_, _utvä, btik, तीकर,
+ {{0x4cde8a49,0x6d588065,0x2c578201,0x61458ada}}, // _মানু, _olva, rədə_, жела,
+ {{0x60da8118,0x6e2d990f,0x2c57811c,0x2b92136f}}, // _cetm, _šabo, sədə_, ráci_,
+ {{0x394587fc,0xf9908154,0xddd6009a,0x5edd8264}}, // _vols_, ربه_, czył, _যাবে,
+ {{0x6d58cb09,0x6d474b0a,0xf1b9bc6d,0x290c84b7}}, // _alva, _hoja, _tuš_, _ebda_,
+ {{0x201eb819,0x9f5c801b,0x80caa769,0x60dacb0b}}, // _esti_, ctví_, _स्वे, _fetm,
+ {{0x6b8bbe9d,0xe5699630,0x80d380ab,0x60da8085}}, // logg, риод_, _সাক্, _getm,
+ {{0xca291a0f,0x61e40098,0x6d474b0c,0x66044b0d}}, // _שם_, _svil, _moja, ztik,
+ {{0x6d474b0e,0x672281c0,0x6d588f7c,0x6b8b867f}}, // _loja, _gaoj, _elva, nogg,
+ {{0x84e5a103,0xfce5964c,0x8d74a3f7,0x67244b0f}}, // _долж, _доло, _کانا, vdij,
+ {{0xa3ea101b,0x412a11d2,0x0d84b73a,0x28cf863a}}, // _मिल_, шого_, _елін, _स्मि,
+ {{0x1b0f00ab,0x96c7082e,0x75238c53,0x06ae8264}}, // িয়ে_, _ụfọd, _ianz, ছুদি,
+ {{0x66044b10,0x7ae98578,0x752380b4,0x61e40370}}, // ttik, _ndet, _hanz, _tvil,
+ {{0x6d47025b,0xee3a067c,0x7523cb11,0x92e900a0}}, // [3b40] _boja, шна_, _kanz, _طريق_,
+ {{0x3a2017ea,0x90c308b0,0x3d140035,0xb17b0106}}, // _isip_, _обье, ड़ें_, lsån,
+ {{0x7523cb12,0x032695b5,0x29182104,0xf7464b13}}, // _manz, жден, mera_, _дено,
+ {{0x29184b14,0x7523840e,0x60da87e2,0xeb978009}}, // lera_, _lanz, _setm, оих_,
+ {{0xada601a1,0x3a2d81a1,0x6d471e67,0x6a1692c5}}, // _мамл, _crep_, _foja, تبار,
+ {{0x7ae9cb15,0x61e2b550,0xdb0b016d,0x6d470390}}, // _edet, rsol, _utfö, _goja,
+ {{0x29184b16,0x6f09cb17,0xd83a8048,0x60da80f1}}, // iera_, ffec, рэл_, _vetm,
+ {{0x29184b18,0x9c7c90d1,0x7523cb19,0xdb1ca3a6}}, // hera_, nače, _aanz, _stræ,
+ {{0x29184b1a,0x7523cb1b,0x63bc15d0,0x3a2dcb1c}}, // kera_, _banz, _hurn, _grep_,
+ {{0x7523cb1d,0x70b9035a,0xa06a8098,0x98a68878}}, // _canz, _आलेल, _каза_, _дизе,
+ {{0x63bc4b1e,0x7523c62f,0x3a20008e,0x201ecb1f}}, // _jurn, _danz, _asip_, _usti_,
+ {{0x63bc1cc5,0x25eb00d4,0xa5bb009a,0x9c7c811a}}, // _murn, _चिली_, _krót, jače,
+ {{0xa2bc050a,0x320682c4,0x2ee00140,0xe57a8087}}, // _एलर्, ntoy_, daif_, _кзд_,
+ {{0x29180542,0x752382af,0xd5af8558,0x3d140035}}, // gera_, _ganz, _іс_, ड़ों_,
+ {{0xd6db067c,0xa89914bc,0x20990d15,0x36d50103}}, // ита_, скву_, сквы_, зобр,
+ {{0x27ed04c4,0x7523822e,0x2d8ccb20,0x6d474b21}}, // čen_, _zanz, lode_, _soja,
+ {{0x29184b22,0x7c2e4b23,0x752383c3,0xe4cb019f}}, // [3b50] bera_, _arbr, _yanz, لبان_,
+ {{0x80f501bb,0xa5bb03a8,0x2d8c929d,0xd7fb0012}}, // _эпох, _asóc, node_, _кум_,
+ {{0x5b159289,0x9c7c8024,0x290106cb,0xf8b1003d}}, // змет, bače, _icha_, _سکس_,
+ {{0x501a004c,0x3a2d82e6,0x2d8c8b48,0xe3b8373a}}, // _פורו, _prep_, hode_, ібі_,
+ {{0x6d4701a1,0x2d8c9a40,0x7f3a025f,0x6b8b8a53}}, // _toja, kode_, _הערו, rogg,
+ {{0x6b8bcb24,0x6d5e0192,0xe8b9275c,0x63bc15df}}, // sogg, chpa, _आलोच, _furn,
+ {{0x63bc4b25,0xb4d734ec,0x6b8b847f,0xa5bb09aa}}, // _gurn, सरी_, pogg, _erót,
+ {{0xd00e8013,0x636883bd,0x29184b26,0x7523cb27}}, // الي_, _друг_, zera_, _sanz,
+ {{0x28cf9a87,0x236d8ad4,0x29014b28,0x63bc4b29}}, // _स्थि, _imej_, _ocha_, _zurn,
+ {{0x9c7c80fe,0x29014085,0x6f09bb50,0x29184b2a}}, // zače, _ncha_, sfec, xera_,
+ {{0x7523cb2b,0xb97a80be,0xccf200be,0x317a80be}}, // _vanz, _פרעז, עכט_, _פרעמ,
+ {{0x29014b2c,0xe50b00c2,0x29184b2d,0x28cf8327}}, // _acha_, स्ति_, wera_, _स्ति,
+ {{0x7523cb2e,0xd2520019,0x442ea5d0,0xb6ba8e82}}, // _tanz, رنس_, _erf_, _הצני,
+ {{0x27f80039,0x7afd06a8,0x75238c53,0x442e8493}}, // turn_, agst, _uanz, _frf_,
+ {{0x29184b2f,0x2007b8e9,0x2f5601a1,0x8fa61eef}}, // rera_, ntni_, птас, заве,
+ {{0xb8f30076,0x63ba26d5,0x29014b30,0x9f5803ed}}, // _ह्_, _étni, _echa_, murë_,
+ {{0x63bc4b31,0x2ee000f7,0x9c7c812b,0x26dd81bc}}, // [3b60] _surn, raif_, rače, _mewo_,
+ {{0x63bc21e5,0x20078968,0x6284008e,0x33248282}}, // _purn, ktni_, _ixio, _samx_,
+ {{0xa3dec19f,0x3f8d8118,0xd25280d7,0x61e60687}}, // दीप_, koeu_, کنش_, lskl,
+ {{0x2d8c8052,0xeb9a8009,0xa5bb4b32,0xaefb026b}}, // zode_, щие_, _prót, _alùf,
+ {{0x61e60338,0x63a41e3f,0x60de008e,0x61ed807a}}, // nskl, _mhin, _kepm, šaln,
+ {{0xe8949663,0xdb1c8004,0xf8be4b33,0x3206cb34}}, // маль, _strä, ्डिय, rtoy_,
+ {{0x2d8c803b,0x55f880c8,0x64a6221f,0xada31290}}, // vode_, _অনেক_, _хава, _чарл,
+ {{0x9634a748,0x442e826c,0x6449061c,0x63a4027d}}, // дниц, _rrf_, çmiş, _nhin,
+ {{0xa3de8b9f,0xbb3b8039,0x80dd00ab,0x798e02a0}}, // दीन_, _פעמי, _বাস্, kobw,
+ {{0x7c86908d,0x61e60b81,0x63a44b35,0x442e82df}}, // зуме, dskl, _ahin, _prf_,
+ {{0x9c7ccb36,0x63a41581,0xb4bcb011,0xbae100ab}}, // dačc, _bhin, _अली_, _ভাবছ,
+ {{0x63a44b37,0x29014b38,0x7ae2804f,0xaca3819d}}, // _chin, _scha_, naot, _gwọk,
+ {{0x63a402a3,0xb7e0800c,0xdb0400ff,0x60cb8061}}, // _dhin, _गौतम_, _khiê, _élmé,
+ {{0x6e22abd7,0x63b60748,0x442e82df,0x6d4a2b0d}}, // _isob, _eiyn, _trf_, öfal,
+ {{0xd90f0077,0xd5a5803d,0x7d1acb39,0x442e80b9}}, // نیک_, _تلوی, mets, _urf_,
+ {{0x63a40ad7,0x95d90081,0x6d5c4b3a,0xa2de0072}}, // _ghin, ждат_, _alra, परण्,
+ {{0x59b72279,0xf1d780c8,0x7aed011e,0x6d4acb3b}}, // [3b70] _इंटर, _সম্ভ, _idat, _kofa,
+ {{0xdb040028,0xb4d734ec,0xb4c9016f,0x29014b3c}}, // _nhiê, सरे_, ैरे_, _ucha_,
+ {{0xa5bb008b,0x8c190c3b,0x9cd781c6,0xdb1ccb3d}}, // _prós, تيار_, פואה_, _kurè,
+ {{0x7d1aa5be,0x27e500e1,0xfaff0168,0x63a403ed}}, // hets, álnu_, ngë_, _xhin,
+ {{0x7d1acb3e,0x539c0039,0x6f0d4b3f,0x6e22cb40}}, // kets, זיאו, mfac, _nsob,
+ {{0x7d1a82fd,0xdb04001c,0x6f028118,0x67260fbc}}, // jets, _chiê, _acoc, _zakj,
+ {{0x29c287ca,0x7aed2503,0xdb1c8082,0x28de8105}}, // _eða_, _odat, _strå, _नजरि,
+ {{0x6f0d0352,0x80380039,0x3eb98aa2,0x20079b39}}, // nfac, לנוע_, øst_, rtni_,
+ {{0x2007b118,0x63a44b41,0x61e48013,0x6adc00ab}}, // stni_, _rhin, áilt, _ভালো,
+ {{0x7aed4b42,0x7d1a82a0,0xa2c99d17,0x6d4acb43}}, // _adat, gets, हुर्, _cofa,
+ {{0x68e3cb44,0x63a44b45,0xceb40085,0x69cd4b46}}, // mand, _phin, ndər_, _atae,
+ {{0x68e3b396,0x61e980e8,0xe29a4b47,0xdb0a81d6}}, // land, _kvel, жам_, _šnúr,
+ {{0x7d1a84b8,0x27e0a3e3,0xa3b9a539,0x7aed031d}}, // bets, ćine_, _अंक_, _ddat,
+ {{0x25ee000f,0x63a43c32,0x6d4a831d,0xb4d70441}}, // ेंसी_, _whin, _gofa, सरो_,
+ {{0x63a40051,0x6609cb48,0x672601ed,0x7aed0372}}, // _thin, ltek, _pakj, _fdat,
+ {{0x7e7b4573,0x61e9cb49,0x6729cb4a,0xe82080c2}}, // _župa, _ovel, ndej, _मनसा_,
+ {{0xdd9207bd,0x80dd00c8,0x6609cb4b,0x68e385d1}}, // [3b80] نور_, _বার্, ntek, kand,
+ {{0x68e3cb4c,0x443101dd,0x442300b9,0xa5bb0511}}, // jand, _brz_, _bsj_, _prór,
+ {{0x68e3cb4d,0x6726403c,0x6f0d0122,0x61e98e5a}}, // dand, _takj, bfac, _avel,
+ {{0x9c7c803a,0x6729cb4e,0xaefb0362,0x65c61198}}, // nača, jdej, _clùd, _обма,
+ {{0xa3de023c,0x68e3cb4f,0xdb040028,0x61e98088}}, // _दौर_, fand, _phiê, _cvel,
+ {{0x68e3cb50,0x6da62262,0x6f0281e8,0x7ae29b88}}, // gand, _цига, _scoc, saot,
+ {{0x9c7c8267,0xa3b9970c,0xd5ad0019,0x443100e5}}, // kača, _अंग_, _اہل_, _grz_,
+ {{0x9c7c803b,0xdca30ab2,0x6d4a9277,0x61e94b51}}, // jača, _раси, _sofa, šeli,
+ {{0xf8cf8aed,0xdb040028,0x6609b9ad,0x9c7c856f}}, // _स्वय, _thiê, gtek, dača,
+ {{0x7afd91bf,0x657e01a8,0x7ae08106,0x7bdc010c}}, // _östr, nnph, _hemt, _kwru,
+ {{0x291ccb52,0x7d1acb53,0x69c0cb54,0xfa1300ab}}, // meva_, rets, _hume, িয়াল_,
+ {{0xee3a93cd,0x69c09167,0x291c94c7,0x7d1a932e}}, // _мне_, _kume, leva_, sets,
+ {{0xf2d38158,0x69c0cb55,0x6d4a822e,0x5eac80ab}}, // כער_, _jume, _tofa, _ট্রে,
+ {{0x69c0cb56,0x291ccb57,0xdb1c82be,0xceb40085}}, // _mume, neva_, _duré, ydər_,
+ {{0x69c0cb58,0x9c7c9123,0xa5bb0032,0xa3ea064a}}, // _lume, bača, _aróp, _मिग_,
+ {{0x7ae0cb59,0xb4d7016f,0x4423008e,0xaefb026b}}, // _nemt, सर्_, _rsj_, _amùr,
+ {{0x68e3cb5a,0x69c0a91e,0xdb008511,0xf1a70088}}, // [3b90] yand, _nume, _limó, _орон,
+ {{0x291ca3e3,0x15f3035a,0xd49b0284,0x6e2d3a65}}, // jeva_, ेंबर_, оре_, rvab,
+ {{0x7527020f,0x291ccb5b,0xf48500d5,0x66098102}}, // _vajz, deva_, رائی, ztek,
+ {{0x68e3b8ba,0x69c0930e,0x395ecb5c,0x3d0e9f1c}}, // wand, _bume, _alts_, त्ते_,
+ {{0x68e3cb5d,0xdb1c802a,0xe5c40198,0xeb9200be}}, // tand, _xuré, _ассо, אָג_,
+ {{0xa0a5a155,0x25b80add,0x7c3a042b,0xe8e000ff}}, // налд, _girl_, _štre, _kiều_,
+ {{0x7ae08e23,0x1fb58d69,0xdb008118,0x442300b9}}, // _femt, нсир, _cimó, _usj_,
+ {{0x69c0bd02,0x491b800d,0x9f582ad5,0xa295902a}}, // _fume, _यसको_, turé_, _забі,
+ {{0x68e39c40,0x61e9b28b,0x69c0cb5e,0x6729bfd7}}, // pand, _uvel, _gume, rdej,
+ {{0x6609cb5f,0x291ccb60,0x7bc800e7,0x61fd4b61}}, // rtek, ceva_, _éduc, dusl,
+ {{0x6609cb62,0xeb9a0056,0x9f580036,0x9c7c81a1}}, // stek, зин_, suré_, tača,
+ {{0x68e18e20,0x6609cb63,0xdb1c98cc,0xd6d9866f}}, // _held, ptek, _puré, ykły_,
+ {{0x7e7b4573,0x68e1bfc9,0x632200ab,0x61fd02f1}}, // _župn, _keld, যালয়_, gusl,
+ {{0x9c7c84e8,0xdb190087,0x68e1a6af,0xade302f1}}, // mačn, _cuvâ, _jeld, गठनन_,
+ {{0x9c7c9f95,0x0396017a,0x68e1977a,0xfbc6800d}}, // lačn, _прия, _meld, _रूपम,
+ {{0x28d00ebf,0xa5bb0073,0x20050722,0x291c80d2}}, // _त्रि, _próp, àlia_, zeva_,
+ {{0x61ed8d11,0x9c7c803a,0xc7b2812a,0x7ae64b64}}, // [3ba0] šalj, načn, רבן_, makt,
+ {{0x2cbc004a,0x6aa40234,0x26cb0037,0x7ae0cb65}}, // øvd_, _izif, _efco_, _semt,
+ {{0x69c0cb66,0x66e60b9c,0xdee61b3f,0x291ccb67}}, // _sume, _пона, _пони, veva_,
+ {{0x9c7c826f,0xb4bc864a,0xaca401bc,0xe1ff002a}}, // kačn, _अल्_, _nrụr, izón_,
+ {{0x291ca49a,0x68e1cb68,0xdb00957a,0x395e808e}}, // teva_, _beld, _simó, _plts_,
+ {{0x6b841487,0x6fc78214,0xaca40135,0x68e18661}}, // _ljig, _sıca, _arụr, _celd,
+ {{0x64a34b69,0xdca31ddf,0x7ae64b6a,0x291ca420}}, // _бара, _бари, kakt, reva_,
+ {{0x69c0b202,0x4444022b,0x6d4e4b6b,0x59dd801b}}, // _tume, mx_, _hoba, नीहर,
+ {{0x291ccb6c,0x9c7ca19f,0x44444b6d,0x68e1cb6e}}, // peva_, gačn, lx_, _feld,
+ {{0x68e1cb6f,0xdb00a620,0xc0aa9459,0x6d4e4b70}}, // _geld, _timó, _فاضل_, _joba,
+ {{0x7ae628d2,0x6d4e3fab,0x236000f3,0x44444b71}}, // fakt, _moba, _blij_, nx_,
+ {{0x4444252f,0x61fd2cb0,0x5fb40424,0x68e1cb72}}, // ix_, tusl, ंगफल, _zeld,
+ {{0xf99f06c0,0xc33384de,0xe1ff0118,0x3f800035}}, // stèm_, אור_, azón_, dniu_,
+ {{0x7d1e02a5,0x2b840457,0x6d4e4b73,0x6b840135}}, // jeps, rıca_, _noba, _ejig,
+ {{0x7d1e4b74,0x6aa40c6a,0x97a71510,0xa0c401a8}}, // deps, _ezif, ерал, ايفو,
+ {{0x5f94038b,0x7ae64b75,0x7c9b810f,0x4444022b}}, // гист, cakt, _משוג, dx_,
+ {{0x6d4e2c65,0x444419e7,0x80e18264,0x2b9c928a}}, // [3bb0] _boba, ex_, _নাস্, mích_,
+ {{0x6d5acb76,0x44444b77,0x6b9d2900,0x61ed4b78}}, // nkta, fx_, llsg, _ival,
+ {{0x9c7c826f,0xa5bb002a,0x5f0c00d4,0xdbdb9727}}, // začn, _isóm, _डॉक्_, _yáñe,
+ {{0x2b9c800d,0x68e70019,0x68e1c0de,0x29030357}}, // ních_, lajd, _seld, lgja_,
+ {{0x22588009,0xaa58960f,0xe1ff01ca,0x68e181a9}}, // ницы_, ницу_, nuó_, _peld,
+ {{0x865a0bea,0x29031805,0x6d4e4b79,0x7ae64848}}, // _חדשי, ngja_, _goba, zakt,
+ {{0xe8e00028,0x68e18357,0x660d0956,0x7ae6004a}}, // _hiểu_, _veld, ltak, yakt,
+ {{0x6d4e0063,0xee398139,0x672d4b7a,0xe8e00028}}, // _zoba, дни_, ndaj, _kiểu_,
+ {{0x27e08025,0x0d858e8e,0x338488d5,0x68e1cb7b}}, // ćina_, клин, _сурв, _teld,
+ {{0xb2748468,0x9c7c803e,0x41b584c0,0x7ae60057}}, // _слуш, račn, _شمار, wakt,
+ {{0x80ca904f,0x61ed20a5,0x63bb804f,0x9c7c80e1}}, // _स्टे, _aval, _kiun, sačn,
+ {{0x9c7c8968,0x2cb00085,0xe1ff2c92,0x628c8035}}, // pačn, ündə_, rzón_, żnoś,
+ {{0x2d8701ac,0x63bba810,0x63660118,0x63a98428}}, // čnej_, _miun, _lóng, _mhen,
+ {{0x7ae6272f,0x29030357,0xdb0b4b7c,0x673d0197}}, // sakt, ggja_, _bufó, _ansj,
+ {{0x66e619b8,0xdee6257e,0x6d4e4b7d,0xc0e311b3}}, // кова, кови, _roba, роск,
+ {{0x2b9c83cb,0x660d1c66,0xe8e00028,0xd5c302f1}}, // cích_, ftak, _biểu_, _वंशज,
+ {{0x6d4e4b7e,0x660d1d23,0x672b80f1,0x7bc3a7f3}}, // [3bc0] _poba, gtak, _magj, _hunu,
+ {{0x63a9a087,0x44444b7f,0x7bc38010,0x7ae40e20}}, // _ahen, tx_, _kunu, _heit,
+ {{0x444406e3,0x61ed0052,0x660d4b80,0x272192c7}}, // ux_, _zval, atak, _मसूर_,
+ {{0x7bc39a5a,0x44444b81,0x63a99c40,0x7d1e4b82}}, // _munu, rx_, _chen, seps,
+ {{0x7ae44b83,0xa96a97f4,0x63bb81fb,0x63a9cb84}}, // _meit, мида_, _diun, _dhen,
+ {{0x7ae44b85,0x7e69cb86,0x2b9c81d0,0x442784e8}}, // _leit, dzep, zích_, _osn_,
+ {{0x8c43028e,0xebe32a98,0x7bc3cb87,0x2d98026c}}, // _кере, _восп, _nunu, _ikre_,
+ {{0x63bbcb88,0x6f044b89,0x59dd8740,0x03a686a1}}, // _giun, ngic, _नौकर, _рибо,
+ {{0xf7730051,0xdb1ccb8a,0x9c7c826f,0x692680a9}}, // יקה_, _jurí, tačo, _имаа,
+ {{0x7bc3cb8b,0x63a9cb8c,0x6b82882c,0x73d882fb}}, // _bunu, _zhen, lnog, ндор_,
+ {{0x7ae4034a,0x6d5a80e3,0x68e72c08,0xaf06cb8d}}, // _beit, rkta, vajd, _апел,
+ {{0x3f8081e2,0x7ae40083,0x16dc016f,0x7bc3cb8e}}, // čius_, _ceit, बरोब, _dunu,
+ {{0x3ea08013,0x7ae41c10,0x7c3e826c,0x2b9c81a8}}, // _áit_, _deit, _šprd, rích_,
+ {{0x3cfe0c87,0x66008057,0x28c6940d,0x6b828428}}, // _लागे_, mumk, रुचि, hnog,
+ {{0x7ae44b8f,0x7bc38397,0x4fc40391,0x212c820f}}, // _feit, _gunu, иста, _madh_,
+ {{0x2d9eae76,0x3d0e816f,0x61ed943c,0x2d9802c4}}, // llte_, त्रे_, šali, _akre_,
+ {{0x636605a4,0x6b82805c,0x61ed4b90,0x63bb8081}}, // [3bd0] _dónd, dnog, _uval, _riun,
+ {{0x7c3a025b,0x63a9cb91,0x7ae40352,0x63bb827e}}, // _štra, _shen, _zeit, _siun,
+ {{0x2d9e8ad0,0x6b82831d,0xd49b4184,0x660d4a2b}}, // ilte_, fnog, фра_, stak,
+ {{0x7ae4062f,0x4ea701e2,0xe8e00028,0x2d9e81ec}}, // _xeit, _арга, _tiểu_, hlte_,
+ {{0x63bb8010,0x2ee90359,0xad9b0118,0x2175b160}}, // _viun, maaf_, _opúx, лукр,
+ {{0x776284c3,0x02a3019d,0x2ee94090,0x3f868242}}, // _alox, _arịọ, laaf_, _ajou_,
+ {{0xd4980190,0x68e50965,0x101581a8,0x212c8a2a}}, // _арт_, _mehd, ابتد, _dadh_,
+ {{0x68e50009,0x7bc3cb92,0xd10580dc,0x212c8219}}, // _lehd, _runu, _रावण_, _eadh_,
+ {{0xa3e7101b,0xdb1c84c3,0x7bc3cb93,0x799500b4}}, // _मौत_, _xurí, _sunu, kozw,
+ {{0x7ae44b94,0x7762cb95,0x7bc3cb96,0x2ee94b97}}, // _seit, _elox, _punu, haaf_,
+ {{0x7ae44852,0x7bc3828d,0x3b0a177f,0x672b8061}}, // _peit, _qunu, нено_, _tagj,
+ {{0x67209351,0xa2aa904f,0x63760115,0xe61180d7}}, // cemj, जेक्, ršno, _هشت_,
+ {{0x7ae40125,0x68e5026c,0x06b180ab,0x69c43f55}}, // _veit, _behd, _ট্রি, _quie,
+ {{0x7ae436e3,0x7bc3cb98,0xfce58dc9,0xdb19006a}}, // _weit, _tunu, _соко, _nuvæ,
+ {{0xb4df8576,0x7ae4031d,0x4427a20e,0x63660118}}, // तरी_, _teit, _usn_, _pónd,
+ {{0xfbd28051,0x80d41053,0x6e3b0bcf,0x26cd9ed4}}, // _פתח_, _ब्रे, _šuba, nceo_,
+ {{0x22460179,0x36674b99,0x6b82812b,0x0d861a4a}}, // [3be0] _çok_, гато_, vnog, глан,
+ {{0x04460ba5,0x6e2409ca,0x8c461508,0x636607f1}}, // лезн, rwib, лезе, _dóne,
+ {{0x98a8000d,0xf8a8000d,0x6b828140,0x212c851e}}, // _गरिए, _गरिय, tnog, _radh_,
+ {{0xe51381c4,0x25e80540,0xdb1ccb9a,0x7c3ecb9b}}, // त्ति_, _चौथी_, _turí, _ápre,
+ {{0x320201ac,0x5b7b00be,0xf1bf2792,0x2d9e821e}}, // nuky_, ערנא, lvár_, ylte_,
+ {{0x6b8282ce,0xa5bb002a,0x75218019,0x31348c4f}}, // snog, _apóf, jelz, рекр,
+ {{0xfaff00f1,0x8d630320,0x27e081a1,0x6720a157}}, // dhën_, _увре, ćino_, temj,
+ {{0x442a056c,0x9f549156,0x998d8087,0x17548081}}, // _isb_, рвич, _preţ_, рвия,
+ {{0x28cf8540,0x6ce6a133,0x998900e1,0xe4e6bf56}}, // _स्कि, ліне, _hrať_, лінн,
+ {{0x98ba0176,0x752194cf,0xf1bf4b9c,0xf28bb5c4}}, // _anpč_, gelz, kvár_, _אָבֿ,
+ {{0x9c7c803b,0x9f5806a5,0x2cb8816d,0xe7b4015c}}, // mačk, ntró_, ärd_, ंगाप,
+ {{0x78a8a3e3,0x3d140f97,0x442a4b9d,0xda350009}}, // _izdv, न्ते_, _msb_, аемы,
+ {{0xed5a8544,0xa77b80be,0x442a01e0,0x629e8035}}, // _под_, _אראפ, _lsb_, ępow,
+ {{0x9c7c803b,0x44384b9e,0xceb383c8,0x442a0558}}, // načk, _orr_, _פיס_, _osb_,
+ {{0xe3b00416,0x657acb9f,0x65688c5e,0x442a4ba0}}, // تری_, mith, mhdh, _nsb_,
+ {{0x657acba1,0xf40a00ab,0x7c2acba2,0x9c7c9249}}, // lith, রবার_, _isfr, hačk,
+ {{0x68e519e0,0x63ad4ba3,0x442a079f,0x557506e6}}, // [3bf0] _tehd, _ihan, _asb_, агат,
+ {{0x9c7c82a5,0x63760353,0x046701a1,0x657acba4}}, // jačk, jšnj, лтем, nith,
+ {{0x2ca70e23,0x9c7c826f,0x63ad4ba5,0x752e0326}}, // ånd_, dačk, _khan, _dabz,
+ {{0xd49846a1,0x20030b20,0x442a4ba6,0xbe050065}}, // уру_, muji_, _dsb_, _پوسٹ,
+ {{0x63ad4ba7,0x81c780eb,0x442a008b,0x657a879d}}, // _mhan, klēš, _esb_, kith,
+ {{0x657a80f1,0x9c7ccba8,0x63ad4ba9,0x645a84f6}}, // jith, gačk, _lhan, kyti,
+ {{0x5ba78767,0x63ad4baa,0x657a90fa,0x315804de}}, // _браз, _ohan, dith, ריון_,
+ {{0x63ad0028,0x7bd5011e,0x61fd2329,0x657a8428}}, // _nhan, _itzu, krsl, eith,
+ {{0x9c7ccbab,0x9f4c4bac,0x7bc74bad,0xa3e78540}}, // bačk, édé_, _huju, मीन_,
+ {{0x7bc73e18,0xb4dfb792,0x8d5589e0,0x63ad02a0}}, // _kuju, तरे_, ртич, _ahan,
+ {{0xcb6a0021,0x64a59908,0xe8e00028,0x7bc7059c}}, // ване_, рана, _hiệu_, _juju,
+ {{0x63ad3693,0x661d45f8,0xfaff00f1,0x7bc74bae}}, // _chan, _opsk, shën_, _muju,
+ {{0x63ad4baf,0x6db2809a,0x7bc74bb0,0x657a81f6}}, // _dhan, _płat, _luju, bith,
+ {{0x2907cbb1,0x657acbb2,0x63ad0114,0x3202016b}}, // ngna_, cith, _ehan, ruky_,
+ {{0xb8ce22db,0x661d0df1,0xe8e00028,0x6b9b8c56}}, // _और_, _apsk, _liệu_, _okug,
+ {{0x63ad3343,0x2b404bb3,0x7aeba5dd,0x6b9bcbb4}}, // _ghan, _unic_, jagt, _nkug,
+
+ {{0x539b0039,0x7aeb81b0,0xc4e60198,0x7bd507f1}}, // [3c00] _ביקו, dagt, ижай, _atzu,
+ {{0x63ad23ac,0x845a3314,0x7bc74bb5,0x752e4bb6}}, // _zhan, крет_, _buju, _qabz,
+ {{0x9c7c812b,0xb4d60063,0x0c261e25,0xf0aa17ae}}, // vačk, _सभी_, рмен, _школ_,
+ {{0x657a805d,0x9f58023e,0x6d5e06ae,0x61e44a4e}}, // zith, rtró_, skpa, _kwil,
+ {{0x2d828019,0x9f581e21,0x657a8234,0x9c7c936f}}, // ékek_, stró_, yith, tačk,
+ {{0x442a4bb7,0x61e40010,0xe8e000ff,0xdb1c9820}}, // _tsb_, _mwil, _diệu_, _durá,
+ {{0x9c7c803b,0x6b5f00f1,0x68eacbb8,0x44381929}}, // račk, _dëgj, rafd, _urr_,
+ {{0x7aeb8079,0x2ca71f8e,0x03d70039,0xd1388035}}, // cagt, ünde_, _כולם_, _skąd_,
+ {{0x9c7ccbb9,0x63ad031d,0x657a8174,0x70f69ddd}}, // mači, _rhan, tith, _نسائ,
+ {{0x9c7ccbba,0xa9068077,0x7bc084a7,0x21a68162}}, // lači, _خبرن, _himu, _тизм,
+ {{0x657a8013,0x7bc0b7c6,0x44388feb,0x63a280e5}}, // rith, _kimu, ír_, olon,
+ {{0x68e8cbbb,0x657acbbc,0x9c7c93b3,0x64410118}}, // _hedd, sith, nači, _álie,
+ {{0x645acbbd,0x7bc0cbbe,0xda780071,0x27e9812b}}, // syti, _mimu, аяр_, ćane_,
+ {{0x7af6009a,0x63a28234,0x63ad4bbf,0x7e6d007a}}, // _edyt, hlon, _whan, vzap,
+ {{0x68e8bc4e,0x63a28289,0x63ad4bc0,0x6db28063}}, // _medd, klon, _than, _włas,
+ {{0x7bc7288e,0x501b8051,0x9c7c811f,0x68e888f3}}, // _ruju, כולו, jači, _ledd,
+ {{0x7a1a80eb,0xba0180ab,0x63a2cbc1,0x68e88114}}, // [3c10] nātā, _এন্ড_, dlon, _oedd,
+ {{0x7bc70748,0x6b9b9020,0x68e88365,0x3cec826c}}, // _puju, _skug, _nedd, nadv_,
+ {{0x63660065,0x6b89882c,0x7bc08f8e,0x5c070081}}, // _hóna, _pjeg, _bimu, ряза,
+ {{0x9c7c8025,0x41c4003d,0x63a2c4cf,0xbd05846d}}, // gači, _دقیق, glon, _adẹ́,
+ {{0x19598110,0x7bc74bc2,0x7bc08be6,0xab5da358}}, // лады_, _wuju, _dimu, _niże,
+ {{0x7bc7028a,0x63a2c1cb,0x98ac83bf,0x6366016a}}, // _tuju, alon, _aldı_, _móna,
+ {{0x63a2866b,0x7aeb97ea,0x680a8085,0x7bce957a}}, // blon, pagt, _sədr, íbul,
+ {{0xbb858013,0x438580f7,0x9b94a255,0x6b9ba6bd}}, // _الري, _الرق, _дисц, _ukug,
+ {{0x68e88114,0x290c8326,0x163981a8,0xa7628081}}, // _fedd, _fcda_, اسبة_, якъд,
+ {{0x4d65ae7b,0x3a2d808e,0x6d5503ec,0x7c3e8988}}, // сков, _isep_, _joza, _špra,
+ {{0x69c9808e,0xd05c0085,0xaefb026b,0x3f58841c}}, // _huee, barə, _alùm, _céus_,
+ {{0x69c19c37,0x6d41007b,0x291a00ee,0x60c54bc3}}, // _hile, _ólaf, _sbpa_, _aghm,
+ {{0xe1ff1355,0x69c1cbc4,0x636600f7,0x3f9d8135}}, // gró_, _kile, _cóna, _ikwu_,
+ {{0x2bcf8894,0xe50303bb,0x7ae9a444,0x6d5500eb}}, // _संपा, _लागि_, _leet, _noza,
+ {{0x6d43af9c,0x05748277,0x201e24df,0x98a612ea}}, // _inna, _باند, ïti_, сиве,
+ {{0x6f09a6db,0x69c19c66,0xa3e781a2,0xe1ff4bc5}}, // ngec, _lile, मीण_, bró_,
+ {{0xee3a25c6,0x69c18083,0x6e29cbc6,0x6b998079}}, // [3c20] ына_, _oile, nweb, dowg,
+ {{0x7bc09219,0x66040cfa,0xd05c0085,0xa3de9521}}, // _simu, ruik, zarə, दीक_,
+ {{0x68e8805f,0x63a2809c,0xd5c8801c,0x3a2d81bf}}, // _redd, tlon, _nền_, _asep_,
+ {{0x387a8201,0x69c1cbc7,0xaca38133,0x68e8cbc8}}, // _üzrə_, _aile, _atịk, _sedd,
+ {{0x7ae999a8,0x9c7cb5aa,0x6d550118,0x63a2cbc9}}, // _deet, rači, _foza, rlon,
+ {{0xdd9215e4,0x6d554bca,0xe6e08054,0x69c1cbcb}}, // _روز_, _goza, नर्ज, _cile,
+ {{0x7bc0878a,0x69c18b23,0x2c1c00c2,0x657e0234}}, // _timu, _dile, _नहिं_, miph,
+ {{0x6d43aa4c,0x69c18046,0x2d9a4bcc,0x7ae9cbcd}}, // _anna, _eile, lope_, _geet,
+ {{0xa5bb0510,0x2cea8987,0x6d551a14,0x68ee00b9}}, // _próx, льно_, _yoza, kabd,
+ {{0x8e5700be,0x3f9d8870,0x657e005d,0x68ee00b9}}, // צייג_, _ekwu_, niph, jabd,
+ {{0xfaff020f,0xaf0a89d7,0x80a692c8,0xa5bb12ca}}, // shëm_, _مقام_, _امان, _isót,
+ {{0x6d43b755,0x69c1aef9,0xe1ff4bce,0x657e164d}}, // _enna, _zile, tró_, hiph,
+ {{0xc7978065,0x5e5a9459,0xbc67804e,0x363680f7}}, // _اپنا_, _حجاج_, لمین_, _دراس,
+ {{0x69c1cbcf,0xfbcf9e29,0xe1ff4bd0,0x7bdacbd1}}, // _xile, بتی_, rró_, pptu,
+ {{0x6d554bd2,0xdbe283a8,0x115b80be,0x2bcf852a}}, // _roza, _téñe, נדלע, _संया,
+ {{0x28d92539,0x443f0669,0x442ecbd3,0x9f051c81}}, // _ब्रि, _ču_, _isf_, تورو,
+ {{0x67d18029,0x7ae9cbd4,0xa2a04476,0x443ca4b9}}, // [3c30] _māja, _reet, _गुप्, _hrv_,
+ {{0x7bca874b,0x7ae9cbd5,0x443ca8fc,0x6eff026b}}, // _kufu, _seet, _krv_, _bàbà,
+ {{0x6d55003b,0x69c1cbd6,0x7ae98650,0xa5bb0333}}, // _voza, _rile, _peet, _apóc,
+ {{0x2564016d,0x1dc883eb,0x7aef4bd7,0x7bcacb40}}, // _höll_, रदात, nact, _mufu,
+ {{0x2bcf83b7,0x6d5509ab,0x7c2e4bd8,0x7ae982f1}}, // _संभा, _toza, _esbr, _veet,
+ {{0x7ae9cbd9,0x6b8d017f,0x657e4bda,0xdb0d003d}}, // _weet, _ljag, ciph, _dhaé,
+ {{0xb4e50f12,0x02fb3852,0x69c1cbdb,0xa2a018a9}}, // नरी_, ्लाह_, _vile, _गुन्,
+ {{0xf7718250,0x3f890355,0x27e501ac,0x23cf86b7}}, // بات_, nnau_, álny_, _संबद,
+ {{0x14da9880,0x7aef4bdc,0x442e8358,0x645c8bc5}}, // _म्हण, dact, _asf_, ørin,
+ {{0x6b8d4bdd,0x2bb99664,0xe579981f,0x442e8388}}, // _ajag, ेदवा, рзо_, _bsf_,
+ {{0x3a2021bf,0x443c82f7,0xa34a1a19,0x1b4a0ae7}}, // _spip_, _crv_, азна_, азни_,
+ {{0xeaba01e8,0x2007cbde,0xe51900bc,0x920d8744}}, // ийн_, muni_, न्ति_, _समाज_,
+ {{0x3dd901c0,0x2007cbdf,0xe7968019,0x657e0234}}, // _ntsw_, luni_, _بالک, yiph,
+ {{0x6f1d02af,0x6aad4be0,0xfd52019d,0x200c023e}}, // _absc, _dzaf, _dajụ, àdio_,
+ {{0x2007cbe1,0x2918054f,0x7bcacbe2,0x68ee4be3}}, // nuni_, rfra_, _gufu, rabd,
+ {{0x3f891670,0xa3de82f1,0xa3cd0327,0x2087004a}}, // gnau_, दीओ_, रदन_, ійти_,
+ {{0x27e08025,0xbea312ef,0xee373c20,0x2007a577}}, // [3c40] ćini_, датк, онт_, huni_,
+ {{0x6f1d4be4,0x20079bb7,0x645e4be5,0x443c80b9}}, // _ebsc, kuni_, typi, _yrv_,
+ {{0x2007cbe6,0x657e4be7,0x2127826c,0x7ac69e9d}}, // juni_, riph, denh_, осле,
+ {{0xeb9a8ca4,0x8cda00d4,0xe73a0009,0x3e7002be}}, // шие_, _प्रो, _тем_, _côté_,
+ {{0x094a9445,0xee371663,0x27ed0024,0x914aabf3}}, // ички_, _дня_, ćene_, ичка_,
+ {{0xaca391d3,0x661603ba,0x2007a198,0x7aef0087}}, // _atọk, mtyk, funi_, zact,
+ {{0x64a30a42,0xdca30cec,0x2007b8dc,0xdb0f026f}}, // _жара, _жари, guni_, ždéh,
+ {{0x3b078ae7,0x7bca818e,0x7bde0198,0x29008162}}, // _месо_, _rufu, mppu, şia_,
+ {{0xc7b31a0f,0x799c0578,0x27e9805c,0xed8ac81b}}, // ובה_, korw, ćana_, исок_,
+ {{0x661601e2,0x2007822e,0xdb0d00e7,0x067b007c}}, // ityk, buni_, _chaî, ינפל,
+ {{0x20078098,0x3f89031d,0x23694be8,0x6aad0035}}, // cuni_, ynau_, _plaj_, _szaf,
+ {{0x926b3a8b,0x66164be9,0x6aa40a03,0x443c9502}}, // арга_, ktyk, _byif, _vrv_,
+ {{0x81d300ab,0x64480118,0x799c4bea,0x236900c3}}, // হীন_, _ádif, forw, _vlaj_,
+ {{0x7aef4beb,0x7bcacbec,0x7c3e802a,0x69dd0e20}}, // sact, _tufu, _ápro, rpse,
+ {{0xd5c88028,0x752881ac,0xd24e004e,0x443c8980}}, // _mềm_, medz, نچی_, _urv_,
+ {{0x2bcf89a3,0x27fe1e1e,0x69dd2f9c,0xa857825f}}, // _संता, átna_, ppse, חילה_,
+ {{0x20078245,0x66f40e02,0xef19a180,0x3f890428}}, // [3c50] zuni_, ьпту, niża_, rnau_,
+ {{0x6d589487,0x2007cbed,0x8c6701a8,0x7d1a8192}}, // _jova, yuni_, يطان, nfts,
+ {{0x6d58cbee,0x7aed010b,0x256980e1,0x49b800d7}}, // _mova, _keat, _júla_, _بايد_,
+ {{0xa2a00697,0x6d58cbef,0x7ae28637,0x20079fb6}}, // _गुड्, _lova, gbot, vuni_,
+ {{0x69c5169f,0x6145980f,0xd9458256,0x7aed4bf0}}, // _kihe, зела, зели, _meat,
+ {{0x6d5890ac,0x2007cbf1,0x4129813a,0xaca381bc}}, // _nova, tuni_, соко_, _stọk,
+ {{0x77a5862f,0xfbc68beb,0x6d474bf2,0x2bc6816f}}, // lóxi, _रंगम, _inja, _रंगा,
+ {{0xdb1c98b6,0x6f0d4bf3,0xd343815b,0x6e2281a8}}, // _strö, ngac, _تفری, _bpob,
+ {{0xc3329a63,0x6b9d002a,0x6d470088,0x70a8125f}}, // _און_, dosg, _knja, _गर्ल,
+ {{0x7ac4821e,0x4423008e,0x61e98caa,0xceb981d0}}, // _істе, _kpj_, _iwel, neři_,
+ {{0x04458c8e,0x75351917,0x6d58a5c6,0x442303ac}}, // ченн, _mazz, _dova, _jpj_,
+ {{0x61e980b8,0x69c50364,0x7aed04bc,0x66164bf4}}, // _kwel, _aihe, _ceat, ytyk,
+ {{0x799c4bf5,0x442300dd,0x99890503,0x69c53977}}, // torw, _lpj_, _draž_, _bihe,
+ {{0x6d5880cd,0x753504b7,0xb227007b,0x61e9874b}}, // _gova, _nazz, _svæð, _mwel,
+ {{0x2564016d,0x7aed0051,0x69c52577,0x44310b67}}, // _följ_, _feat, _dihe, _nsz_,
+ {{0x6d470034,0x6f1ba43d,0xdfcf00f7,0x7aed0427}}, // _anja, nfuc, طيك_, _geat,
+ {{0x75354bf6,0x69c50609,0x77a581df,0x26c2a461}}, // [3c60] _bazz, _fihe, góxi, žkov_,
+ {{0xaa3b0039,0x75350081,0x66161c59,0x2d8100b4}}, // _פתיח, _cazz, rtyk, gihe_,
+ {{0xa0a6002e,0x661630bd,0x69dbcbf7,0xdd8f00f7}}, // _ханд, styk, _atue, دول_,
+ {{0x7f4a1381,0x6d470a40,0xe3c9801c,0x69c53ea1}}, // _الحق_, _enja, _nằm_, _zihe,
+ {{0x7ae283ab,0x20184bf8,0xf8dd800d,0x200a4bf9}}, // rbot, ltri_, नुभय, lubi_,
+ {{0xdb15cbfa,0x75352565,0x6d470669,0x660412b7}}, // _buzó, _gazz, _gnja, mrik,
+ {{0x61e9cbfb,0x201838c6,0x6d58a732,0xd83a81e5}}, // _ewel, ntri_, _rova, сэл_,
+ {{0x95d88009,0x753504b9,0x6d58a8d2,0x6a86cbfc}}, // одит_, _zazz, _sova, _елба,
+ {{0x3a3fcbfd,0x7bce0812,0x61e98355,0x7aed4bfe}}, // _grup_, _hubu, _gwel, _reat,
+ {{0x7bce20e7,0x6b82cbff,0x7bcd8088,0x64482738}}, // _kubu, liog, _čaur, _àdis,
+ {{0x75288029,0x6366007b,0xd5b88029,0x43950d5f}}, // redz, _tónl, rmā_, данс,
+ {{0x98ac8029,0x66044c00,0xd5b880eb,0x22404c01}}, // _gadā_, krik, smā_, _brik_,
+ {{0x27e085f5,0x6d589fc2,0x7bce4c02,0x212a026c}}, // ćinu_, _tova, _lubu, febh_,
+ {{0x6604180a,0xdb0981a8,0x35b38d8e,0xdb0d1627}}, // drik, _mheá, _збір, _ciaò,
+ {{0x7aed4c03,0x753512bb,0x36d51bb6,0x7bce3cc2}}, // _teat, _razz, добр, _nubu,
+ {{0x27ff0805,0x2d9ea7d4,0x97a40162,0x26c2816b}}, // čun_, lote_, ерул, žkou_,
+ {{0x6f0d3330,0x75350081,0x6d4700dd,0x224000b9}}, // [3c70] rgac, _pazz, _pnja, _grik_,
+ {{0x2d9ecc04,0x442300b9,0x2fc6c74f,0xdb1c816a}}, // note_, _ppj_, _liog_, _gurú,
+ {{0x69db8aa2,0x2d8ccc05,0x200a00e5,0x7bce2596}}, // _stue, inde_, cubi_, _cubu,
+ {{0x2bcfbca9,0x23cf800c,0x7bce4c06,0x75354bcd}}, // _संसा, _संसद, _dubu, _wazz,
+ {{0xb90a0104,0xe1f881e5,0x75351347,0xcf560039}}, // _nghệ_, згі_, _tazz, _חברת_,
+ {{0x09e200ab,0x2d8c81ed,0x2d9eb689,0x98a59ef3}}, // _বিমা, jnde_, jote_, миле,
+ {{0xb4ab816f,0x27e98503,0x7bce2c45,0x2d9e8a41}}, // _खरे_, ćano_, _gubu, dote_,
+ {{0x2d8ccc07,0x23668052,0x6d5c8bd7,0x53348012}}, // ende_, ckoj_, öran, _пест,
+ {{0x09e20a49,0xee368110,0x88c500ab,0x61fb005c}}, // _বিভা, дны_, ্রিক, šulj,
+ {{0x2d9ecc08,0xe81e009a,0x9e6481a8,0xdce70824}}, // gote_, _पहला_, عاين, _oljč,
+ {{0x2fc68362,0x7983912e,0x61fc81a9,0xe3c9827d}}, // _fiog_, minw, _ārli, _tằm_,
+ {{0x2d8c87b3,0x09e20264,0xc8e64c09,0x7983bcb2}}, // ande_, _বিবা, कर्म_, linw,
+ {{0x0caa000c,0xf77088ca,0x2bcfa539,0x2d9ea26b}}, // _कर्म, _حال_, _संवा, bote_,
+ {{0xd5bb86b5,0x2d9ecc0a,0x20183105,0xdb0d002a}}, // _усе_, cote_, ttri_, _diañ,
+ {{0x07a6069b,0x236d8f4c,0x2b494c0b,0x8fa64c0c}}, // давн, _olej_, _fnac_, даве,
+ {{0x66044c0d,0x290402bb,0x22401299,0x7bce4c0e}}, // trik, şma_, _trik_, _rubu,
+ {{0x63a44c0f,0x7bce4259,0x27ed003a,0xdcfa8029}}, // [3c80] _ikin, _subu, ćena_, litā,
+ {{0x5983176e,0x66044c10,0xdd8f0290,0xab5d8609}}, // _альб, rrik, لوق_, _biżo,
+ {{0x660412a5,0x3a2480b9,0x2366811f,0xdcfa81a9}}, // srik, _spmp_, tkoj_, nitā,
+ {{0x491a03bb,0x2ca7cc11,0x61e9002e,0x2d9e8c53}}, // म्रो_, _mynd_, ţele, zote_,
+ {{0x236d81c0,0x23668168,0x63a40c93,0x2d9e80f8}}, // _dlej_, rkoj_, _mkin, yote_,
+ {{0x7bce010b,0x2366803b,0x6b828110,0x7c3a15f2}}, // _tubu, skoj_, siog, _štru,
+ {{0xa3ae03b7,0x7bdc4c12,0x2d9ebfd7,0x65631a42}}, // कता_, _utru, vote_, önhe,
+ {{0x96348785,0xc6f8c7a8,0x6d5c2f96,0x2d9e804f}}, // ениц, чних_, _hora, wote_,
+ {{0x7e643286,0x2d9ecc13,0xdb040f35,0x00000000}}, // nyip, tote_, _fhiú, --,
+ {{0x63a44c14,0xa3cf959a,0xb4e881fe,0x6d5c4022}}, // _akin, _वंश_, बरे_, _jora,
+ {{0x7bda02be,0xb4de809a,0xcfa98019,0x69c88df6}}, // _étud, _तभी_, _کالم_, _iide,
+ {{0x63b6031d,0x6d5c4c15,0x69c68c1c,0xa5bb4c16}}, // _chyn, _lora, ादकी, _apól,
+ {{0x69c8cc17,0x63a40122,0x61e2cc18,0xfaff03ed}}, // _kide, _dkin, npol, dhët_,
+ {{0x3f84cc19,0x63a44c1a,0x6d5c4c1b,0x69c8cc1c}}, // limu_, _ekin, _nora, _jide,
+ {{0x58d40729,0xb8d7035a,0x69c8cc1d,0x6d4aa91e}}, // тост, _जर_, _mide, _infa,
+ {{0x69c8cc1e,0x7538cc1f,0x65ab164a,0xab5d81b9}}, // _lide, _havz, rühm, _riżo,
+ {{0x6d5c1f32,0x69c88cb5,0xa3e711bc,0x443e81a3}}, // [3c90] _bora, _oide, _मौज_, lvt_,
+ {{0x3f84822e,0x69c8cc20,0xbddb0362,0xb91281bc}}, // himu_, _nide, _crèa, ranụ_,
+ {{0x236d8bda,0x3f84cc21,0x7538cc22,0x68f50380}}, // _plej_, kimu_, _mavz, mazd,
+ {{0x14df03b7,0xdb041a26,0x6d5c01a8,0x61e2bd53}}, // _प्रण, _shiú, _eora, fpol,
+ {{0x6d5c4c23,0x69c880ad,0xdb1602be,0xb4bb852a}}, // _fora, _bide, écéd, _अण्_,
+ {{0x6d5c4c24,0x61ed2994,0x7538a19f,0x6722846d}}, // _gora, _mwal, _navz, _aboj,
+ {{0x69c8b2e7,0x61e2bcc0,0x10a2940b,0x7642948e}}, // _dide, apol, лишн, _aroy,
+ {{0x6d4acc25,0x69c8cc26,0xdcfa80eb,0x7642cc27}}, // _anfa, _eide, vitā, _broy,
+ {{0x69c89c6d,0x2ca7a09b,0x63b6031d,0x7642bffe}}, // _fide, _synd_, _rhyn, _croy,
+ {{0x69c8cc28,0x68f51988,0x128a80d5,0x6d5c3dd5}}, // _gide, jazd, _عملی_, _xora,
+ {{0x809f0996,0x61ed4c29,0x98148124,0xa2c204c5}}, // _खुले, _awal, _لبنا, रेन्,
+ {{0x7bc98d00,0x6d4acc2a,0xe29a23cd,0xdcfa80eb}}, // _jieu, _enfa, зам_, ritā,
+ {{0x7bc982be,0xfbcf864a,0x7f4b81f6,0x7642cc2b}}, // _mieu, _सूरम, _ingq, _groy,
+ {{0x7bc9cc2c,0xdcfa80eb,0x200781e8,0x68f50061}}, // _lieu, pitā, orni_, gazd,
+ {{0x2d85cc2d,0x7f5d02ba,0x61ed0c2e,0x61e2807a}}, // mile_, _bosq, _ewal, zpol,
+ {{0x7bc98586,0xe29a0021,0x2d85cc2e,0x6d5c4c2f}}, // _nieu, _най_, lile_, _sora,
+ {{0x6d5c4c30,0x661b885c,0xdd920154,0x61ed4c31}}, // [3ca0] _pora, ntuk, هور_, _gwal,
+ {{0xa3b4023c,0x2d85cc32,0x6d5c02a3,0x661b8bc6}}, // _जीत_, nile_, _qora, ituk,
+ {{0x6aba8076,0x6d5c4c33,0xdcfa802e,0xaacb8ee6}}, // ेश्र, _vora, lită, िशंक,
+ {{0x69c8cc34,0xeb97cc35,0x2d85805d,0x673b809a}}, // _side, них_, hile_, jduj,
+ {{0x6d5c4c36,0x2d85829b,0x69c8cc37,0x57be03eb}}, // _tora, kile_, _pide, ्दीह,
+ {{0x1ae700c8,0x61e4802e,0x61e2cc38,0x61fb04f0}}, // _কাছে_, ţilo, rpol, áuli,
+ {{0x2d85cc39,0x7dc90110,0x443e4c3a,0xc7c69ed1}}, // dile_, _užsa, _ét_, нсии,
+ {{0xfaa6cc3b,0x7642cc3c,0x7c3e8019,0x35e687a1}}, // _забо, _proy, _ápri, нцов,
+ {{0x3f848542,0xf652990c,0x69c8904a,0x2d85cc3d}}, // rimu_, ائع_, _tide, file_,
+ {{0x3f84cc3e,0x63a291c9,0x2d85be10,0x4427808e}}, // simu_, loon, gile_, _bpn_,
+ {{0x64438052,0x4c948db3,0xddcd2bea,0x68f5026c}}, // _crni, тинс, izaš, vazd,
+ {{0x67229bcf,0x442780dd,0x63a282ec,0x6443c861}}, // _uboj, _dpn_, noon, _drni,
+ {{0x2d85b057,0x4427c929,0x6366002a,0x2a668282}}, // bile_, _epn_, _xóni, nyob_,
+ {{0x2d85cc3f,0x63a2cc40,0x6d4acc41,0x4aa5104f}}, // cile_, hoon, _unfa, _गुणव,
+ {{0xd3a74249,0x27ed003a,0x395e82be,0xb8e91c3b}}, // _прип, ćeno_, _mots_, _ऋण_,
+ {{0x25a102af,0x63a281ab,0x61ed4c42,0xbddb010c}}, // wohl_, joon, _twal, _drèn,
+ {{0x7bc9bd3e,0xfaff00f1,0x63a2a960,0xdcfa8162}}, // [3cb0] _rieu, shës_, doon, cită,
+ {{0x200eaf9f,0x7bc9cc43,0x673b8b80,0xd2519190}}, // kufi_, _sieu, zduj, ینا_,
+ {{0x63a298f0,0x61fb84e1,0x661b8102,0x44271e9e}}, // foon, _svul, ztuk, ïn_,
+ {{0x2d85cc44,0x63a2cc45,0x200782d4,0xdb160176}}, // zile_, goon, vrni_, _chyè,
+ {{0x239600eb,0x7bc980e7,0x61e080b9,0xe9d88084}}, // nāja_, _vieu, _ctml, екі_,
+ {{0x395ecc46,0x79870314,0x2f3f019d,0x2d85cc47}}, // _cots_, nijw, _ọghi_, xile_,
+ {{0xdd94053b,0x200e80b4,0x2795cbfc,0x0138025f}}, // _сары, gufi_, кшир, גרות_,
+ {{0x661b985b,0x6366016a,0xc01c80ff,0x20078168}}, // ttuk, _tóni, _hước_, rrni_,
+ {{0xe3b0845b,0x2d85cc48,0x290147cd,0x2b4dcc49}}, // _طرف_, tile_, _odha_, _inec_,
+ {{0x4d659ae5,0x661bcc4a,0x89348077,0x1c459383}}, // тков, rtuk, _معما, вном,
+ {{0x2d85cc4b,0x661b8bc4,0xeb9a1584,0x3d1906af}}, // rile_, stuk, дин_, _पाने_,
+ {{0x2d85cc4c,0x0056812a,0x6443807a,0xdcfa8493}}, // sile_, _בשעת_, _vrni, tită,
+ {{0xeaf20105,0x3f7a80be,0x3b000168,0xdcfc4b1f}}, // _अजीत_, _קאנס, _vdiq_, _smrč,
+ {{0xc01c8104,0xdcfa8087,0x6443cc4d,0x4427cc4e}}, // _nước_, rită, _trni, _tpn_,
+ {{0x10a63ca1,0x98a62402,0x63a2b50f,0x6443cc4f}}, // тивн, тиве, yoon, _urni,
+ {{0xd00f0b76,0x20050c4e,0x57be001b,0x2004239f}}, // تلف_, ália_, ्देह, čmi_,
+ {{0x7c24803b,0x6b844c50,0xc01c8028,0x96632630}}, // [3cc0] _ćiri, _imig, _bước_, акте,
+ {{0x63a2bca6,0x6d8b00eb,0x5bcaa076,0xc01c80ff}}, // woon, kļau, िद्व, _cước_,
+ {{0x63a2cc51,0x6ea2146d,0x5f060a13,0x80c780ab}}, // toon, _कुरु, _изна, রুত্,
+ {{0x91e69a19,0x395ebb11,0x67d181a9,0x26cf80d7}}, // воде, _sots_, _gāji, _nggo_,
+ {{0x395e809f,0x63a2cc52,0x2fc04c53,0x2564007b}}, // _pots_, roon, mmig_, _sölu_,
+ {{0xfce34c54,0x63a2cc55,0x6609cc56,0xc27b01c6}}, // _воро, soon, drek, ורני,
+ {{0x395e8722,0x63a2ae05,0x6d4e4c57,0x6609cc58}}, // _vots_, poon, _inba, erek,
+ {{0xf093078d,0x442a4c59,0x753c31a6,0x65b001ec}}, // ינו_, _ipb_, _harz, zähl,
+ {{0x395e8813,0x753c0f89,0x6609cc5a,0x443800ee}}, // _tots_, _karz, grek, _hsr_,
+ {{0x6b84402b,0xc01c8129,0xb5fd928a,0x26cf9407}}, // _amig, _xước_, myšl, _eggo_,
+ {{0x753c1cdc,0x6ab2abef,0x6609811e,0x21759ef8}}, // _marz, _जरुर, arek, _сутр,
+ {{0x44383075,0x23960029,0x66099de6,0x65b001ec}}, // _msr_, tāja_, brek, wähl,
+ {{0x51871507,0x80c780c8,0x2d8f0019,0xed5a8081}}, // вува, রুদ্, égek_, _оод_,
+ {{0x6b844c5b,0x753c009a,0x443808ae,0xaefb026b}}, // _emig, _narz, _osr_, _alùp,
+ {{0x97a70009,0x843880f7,0xa3b44c5c,0xd7f8002e}}, // врал, _أكبر_, _जीव_, кут_,
+ {{0x6ab2901b,0x6d4e4c5d,0x24b800ab,0x63660dfe}}, // _जरूर, _anba, জশাহ, _bónu,
+ {{0x27e9812b,0x68f88858,0x7c3a0b80,0xc7c80198}}, // [3cd0] ćani_, kavd, _štrp, тыре_,
+ {{0x1c099a3b,0x6935028e,0xdcb10129,0xf4d10264}}, // _विमल_, _сноу, _ở_, ির্ব,
+ {{0x9f5806a5,0xdce88110,0x7bcd0110,0x6acb83b6}}, // guró_, sidė, _kiau, िश्र,
+ {{0x6d4e4c5e,0x201111b7,0x09cb816f,0x212580dd}}, // _enba, muzi_, ाद्य, _sblh_,
+ {{0x2011454a,0x3f8942d1,0x753c4c5f,0xd83882d6}}, // luzi_, miau_, _farz, _asč_,
+ {{0x3f894c60,0x7c388052,0xdb1ccc61,0x6f02cc62}}, // liau_, _osvr, _zurü, _adoc,
+ {{0xb8dbb850,0xc954010f,0x20112169,0x2576006a}}, // _घर_, ימס_, nuzi_, _sælg_,
+ {{0xee399a19,0x3f89031d,0x753c009a,0xb4c10105}}, // ени_, niau_, _zarz, ुशी_,
+ {{0xdb1c8e14,0x673d07ba,0x09dd80ab,0x63a9cc63}}, // _euró, _masj, _ঠিকা, _iken,
+ {{0x6609cc64,0x6f02819d,0x7bd52479,0x2576006a}}, // rrek, _edoc, _kuzu, _vælg_,
+ {{0x9f0591cc,0x78548416,0x7bd50c9e,0x63bb801c}}, // _موضو, _نیوز_, _juzu, _khun,
+ {{0x91e60d9e,0x7bd50ce7,0x36d580a9,0x673d035f}}, // ломе, _muzu, _содр, _nasj,
+ {{0x7bcd00dd,0x660586c8,0x2ed900ab,0x3f890114}}, // _diau, _спла, তরাষ, diau_,
+ {{0x201102ec,0xbddb00e7,0x644702d0,0xe4e7804a}}, // fuzi_, _crèm, _orji, _рідн,
+ {{0x5f060698,0x2011114c,0x63a9cc65,0x6aad14ee}}, // лзва, guzi_, _oken, _eyaf,
+ {{0x3f890a8e,0x63a9862b,0xe7374c66,0x63bb801c}}, // giau_, _nken, лес_, _nhun,
+ {{0x6443013c,0x753c3fc3,0x7e69a0cf,0x200a0267}}, // [3ce0] ænin, _parz, nyep, srbi_,
+ {{0x2018832b,0x61e40247,0x63a9cc67,0x491b00d4}}, // _årig_, _itil, _aken, _बायो_,
+ {{0x711b093f,0x63bb8c5e,0x3f894c68,0x753c14af}}, // _אויפ, _bhun, biau_, _varz,
+ {{0x63bbcc69,0x7bd50102,0x3f894c68,0xe29a81e2}}, // _chun, _duzu, ciau_, нае_,
+ {{0x25a5a28c,0xa68304cf,0x63bbcc6a,0x753c4a05}}, // koll_, _гляд, _dhun, _tarz,
+ {{0x64a64c6b,0x63a98caa,0x673d02ce,0x7af64c6c}}, // _бага, _eken, _zasj, _leyt,
+ {{0xebe32078,0x629c8063,0x63a61142,0x68f887fd}}, // _госп, _środ, kokn, ravd,
+ {{0xb4c0000f,0x7c9300f7,0x61e44c6d,0x7af64c6c}}, // ँधी_, _الفص, _otil, _neyt,
+ {{0xa2cb88af,0x44440ecb,0xa5bb00f7,0xe297c653}}, // _तृप्, lv_, _spói, _бат_,
+ {{0x444401e9,0xa3d30063,0x71d78039,0x7bcd0110}}, // ov_, _हूँ_, _עובד_, _siau,
+ {{0x61e44c6e,0x4ac70076,0x7c2902ee,0x6aad208b}}, // _atil, रेनव, _ćerk, _syaf,
+ {{0x44444c6f,0x7bc286c0,0xaca40133,0x63a980b9}}, // iv_, nmou, _arịr, _xken,
+ {{0xc3338051,0x388e8086,0x25a58106,0x69d60144}}, // זור_, _hər_, boll_, _cuye,
+ {{0x7bc2abb7,0x3f890114,0x518419b8,0x61e90162}}, // hmou, wiau_, буса, ţelo,
+ {{0x21310176,0x776282a3,0xa3d3009a,0x44444c70}}, // rezh_, _koox, _हूं_, jv_,
+ {{0x63a63286,0x201100b4,0x7bd525f4,0x61e44c71}}, // cokn, ruzi_, _ruzu, _ftil,
+ {{0x444401c5,0x8cf51628,0x3f894c72,0xc9872276}}, // [3cf0] ev_, изац, riau_, _буни,
+ {{0x63bbcc73,0x63a9cc74,0x3f894c75,0x213ecc76}}, // _shun, _sken, siau_, _nath_,
+ {{0x3d1c8441,0xe1ff0019,0xd6d100f7,0x3f891ad5}}, // _माने_, lsó_, _وقد_, piau_,
+ {{0xb5190e18,0x2249013c,0x25b280d4,0x6d581743}}, // न्जय_, _krak_, ुगुण, övar,
+ {{0x444401e9,0xe9ff801c,0xe1ff4c77,0xfaa5aba7}}, // av_, _trận_, nsó_, јало,
+ {{0x2249011f,0x3f86cc78,0x213ea0a4,0xa593004e}}, // _mrak_, _amou_, _cath_, _اگرچ,
+ {{0x25a5ba30,0x213e8090,0x705281a8,0xe73991b1}}, // voll_, _dath_, قنوا, вей_,
+ {{0x63a9a001,0x163981a8,0x63bb8108,0x6f658081}}, // _uken, نسخة_, _uhun, рвиз,
+ {{0x660d22f8,0x44214c79,0x7af61bef,0x213e8114}}, // nrak, lth_, _reyt, _fath_,
+ {{0x45859cce,0x6b8b9277,0x27058028,0x79c88875}}, // ргов, ligg, ồn_, _موقف_,
+ {{0x61e42e52,0x47358dea,0x22490859,0xf1c8001b}}, // _stil, _внес, _arak_, ntář_,
+ {{0x44214c7a,0x22492f66,0x1ee7803d,0x660d01a9}}, // ith_, _brak_, _صوتی_, krak,
+ {{0xeb970b33,0x25a5cc7b,0x27e9992c,0x7ae91f49}}, // рия_, poll_, ćanu_, ñeta,
+ {{0xa2c21a46,0xa5098767,0xfce58eef,0x69c381a8}}, // रेष्, _река_, _токо, nmne,
+ {{0x69c3cc7c,0x9d1a807c,0x660d4c7d,0x2c7b846d}}, // imne, _יוסט, erak, _dádé_,
+ {{0x44213437,0x6840801b,0x69d600ff,0x22490197}}, // dth_, ládá, _tuye, _frak_,
+ {{0x44212065,0x444410af,0xa2c22d05,0x7de50250}}, // [3d00] eth_, wv_, रेश्, مسلم,
+ {{0xe5772386,0xab5d81b9,0x79fb04de,0x44444c7e}}, // азу_, _miżu, _שליח, tv_,
+ {{0x68fc4c7f,0x44440069,0x2abb077f,0x22493733}}, // mard, uv_, _tàbí_, _zrak_,
+ {{0x779411fb,0x68fc0459,0x44444c80,0x69c387d8}}, // _ایرا, lard, rv_, emne,
+ {{0x80ad80c8,0x2d87810c,0x69c382f7,0x1603001b}}, // টেম্, _omne_, fmne, _लिएर_,
+ {{0x99f580e8,0x29184c81,0x333f8036,0x213e808e}}, // сяці, ngra_, _eaux_, _qath_,
+ {{0xa967824b,0xd83a8087,0x333fc73a,0x21678eef}}, // рија_, тэл_, _faux_, рији_,
+ {{0x68fc4c82,0xb9178135,0x93168180,0xab5d8035}}, // hard, _kedụ_, _خورش, _biżu,
+ {{0x68fc4c83,0xba29830f,0x27ed003b,0xae0c0540}}, // kard, _مسلم_, ćeni_, िंटन_,
+ {{0x68fc025b,0x656380a9,0x5d668c9d,0x2d8780ee}}, // jard, _conh, ртиз, _cmne_,
+ {{0x2cb88e23,0xe2f900e8,0x3cf80122,0x10a680e8}}, // ård_, лені_, _merv_, _тижн,
+ {{0x2d878bc5,0x80ad80ab,0x68f74c84,0xbb940198}}, // _emne_, _চলচ্, _sexd, жающ,
+ {{0x52141fb4,0x3636803d,0x68fc4c85,0x491b0074}}, // одят, _خراس, fard, _बादो_,
+ {{0xac1885e9,0x44214c86,0x7c2e3ccd,0x68fc10b6}}, // _боку_, yth_, _opbr, gard,
+ {{0x2bd04638,0xb4d2816f,0x657a80f7,0xb868024b}}, // _संचा, वशी_, mhth, ајте_,
+ {{0x2d8ccc87,0x8b6700d5,0x291800f7,0x36d5287d}}, // lide_, _خاتم, agra_, соар,
+ {{0x7afd2551,0x7bd880f2,0xdef80196,0x63ad4c88}}, // [3d10] mast, _huvu, рыс_, _ikan,
+ {{0x7afd4c89,0x7bd88abd,0xdcf907bd,0x2d8ccc8a}}, // last, _kuvu, افات_, nide_,
+ {{0x65ab02af,0x9f35102a,0x63ad0365,0x95c823d7}}, // führ, _лені, _kkan, рута_,
+ {{0x7afd0711,0x44212dc0,0xe4578158,0xa3b4835f}}, // nast, rth_, ייבט_, обхі,
+ {{0x07560039,0x6b8bcc8b,0x7bd88009,0x2d8c8dde}}, // _הסרט_, rigg, _luvu, kide_,
+ {{0x7afd203b,0x443c8082,0x463a0158,0xab5d84b7}}, // hast, _osv_, _געשע, _riżu,
+ {{0x6602ace1,0x63ad4c8c,0x333f80e7,0x7e6d44cf}}, // _avok, _okan, _taux_, lyap,
+ {{0x65638073,0x09e680ab,0x7afd23c6,0x69dc875f}}, // _sonh, _নিরা, jast, _črev,
+ {{0x7afd4c8d,0x9f5e4c8e,0x6b89cc8f,0x2d8ccc90}}, // dast, été_, _imeg, fide_,
+ {{0x8d5585c2,0x2d8c8870,0x63ad4c91,0x657acc92}}, // стич, gide_, _akan, ghth,
+ {{0xbb460009,0x64a5980f,0x6d41cc93,0xcb6a36ab}}, // бенк, сана, rdla, гане_,
+ {{0x7afd0558,0x3d1c8424,0xe45600be,0x316581ec}}, // gast, _माथे_, צירט_, _holz_,
+ {{0x644a91f8,0x2d8c811e,0x139a80be,0x6b898135}}, // _erfi, bide_, רברע, _mmeg,
+ {{0x2d8ca61d,0x63ad14ee,0x3f8dcc94,0x657a8d35}}, // cide_, _ekan, lieu_, chth,
+ {{0x2b400046,0xe1f2003d,0x3d1c950e,0x660282d4}}, // _taic_, _کسب_, _माते_, _zvok,
+ {{0xea5a0fbb,0x316581de,0x68fc4c95,0x43930992}}, // _број_, _lolz_, sard, _најс,
+ {{0x09e200ab,0x7ae44c96,0xa3b4326c,0x6ea2001b}}, // [3d20] _বিকা, _ifit, _जीए_, _कुखु,
+ {{0x6b9bcc97,0x442ec752,0x32023d23,0x6b89b73c}}, // _ajug, _ypf_, msky_, _ameg,
+ {{0xa5bb0207,0xd8d70158,0x0c260785,0xa06a01a1}}, // _após, _לויט_, смен, _сана_,
+ {{0xeafa8b53,0x8afd801b,0x798e02ec,0xdce381d0}}, // ارات_, _pořa, libw, _plně,
+ {{0x3202237c,0xa96a902f,0x6b9b8118,0x65ab01ec}}, // nsky_, лида_, _djug, rühr,
+ {{0x6b898135,0x2d8f0019,0x7c330e15,0x6b9b819d}}, // _emeg, éget_, ñerí, _ejug,
+ {{0x764b8114,0x7afd1a73,0x2d8cca4c,0x6602b37e}}, // _argy, yast, vide_, _svok,
+ {{0x442e8a5a,0x2d8ccc98,0x7bd88435,0xbddb09c4}}, // _rpf_, wide_, _ruvu, _crèw,
+ {{0x7afd10a6,0x2d8c8074,0x657aca30,0xda638323}}, // vast, tide_, thth, _евти,
+ {{0x867b004c,0x7afd1a94,0x716580f7,0x442ebaab}}, // טרפו, wast, _والك, _ppf_,
+ {{0xdeef814c,0x2d8ccc99,0x672082f1,0x6e2400b9}}, // _ны_, ride_, _यापक_, htib,
+ {{0x6e244363,0x2d8ca62b,0x4c9b00be,0x3f8d823e}}, // ktib, side_, רשטע, cieu_,
+ {{0x2d8ccc9a,0xe5a68e97,0x3d190327,0x256f02d0}}, // pide_, _лиди, _पाले_, _yılı_,
+ {{0x7bd8cc9b,0x7ae40789,0x7afd33e1,0x443ccc9c}}, // _tuvu, _efit, sast, _tsv_,
+ {{0x7dc901e2,0xe8140076,0x442e8980,0x38c30036}}, // _užsi, तूला_, _upf_, déré_,
+ {{0x2d8a028d,0x63ad0dc5,0x44ce8085,0x7e6d02d0}}, // _ambe_, _ukan, mə_, tyap,
+ {{0x320f0065,0x61f6031d,0x44ce8085,0x6e24076d}}, // [3d30] _így_, _gwyl, lə_, gtib,
+ {{0x67d50110,0x7d1acc9d,0x69dc8074,0x6b898084}}, // _sąju, ngts, _पढ़ी, _smeg,
+ {{0x44ce8086,0xe43503f8,0xa2cbb947,0x7f440df6}}, // nə_, تفاد, तेन्, bdiq,
+ {{0x291ecc9e,0xdfc681a8,0xe3b902d0,0x8c1b9101}}, // _acta_, _زي_, ktım_, רואי,
+ {{0x27ed012b,0x38c34033,0x2d8f0019,0x2bb880f7}}, // ćenu_, béré_, éges_, حالة_,
+ {{0x6ea20f85,0x3cfe88ae,0x98be861c,0xbddb0036}}, // _कुटु, jatv_, _altı_, _brèv,
+ {{0x6d554c9f,0x29f6809a,0x212c82c4,0xdb060c83}}, // _inza, ała_, _dbdh_, soké,
+ {{0x44ce8201,0x798e0c56,0x06098951,0x6b89cca0}}, // də_, zibw, аник_, _umeg,
+ {{0x63ab803b,0x332db49a,0x96b985e9,0x200500ce}}, // mogn, _ibex_, шуку_, šlih_,
+ {{0x6d4503f8,0x7afb8352,0x63ab9137,0x3202136f}}, // ndha, _heut, logn, vsky_,
+ {{0x61e98d11,0x7afba08b,0x69c1a499,0xbddb0036}}, // _htel, _keut, _ihle, _grèv,
+ {{0x7c254ca1,0x61f60114,0x6d5c8106,0x224d8176}}, // ithr, _pwyl, örar, _krek_,
+ {{0x3204003e,0x798e02ec,0xbb3a83de,0x7afbcca2}}, // émy_, tibw, בערי, _meut,
+ {{0x7afb82af,0x32021ee0,0x6729822b,0x2effcca3}}, // _leut, rsky_, lfej, lauf_,
+ {{0x6d450428,0x7ae404b7,0x798e4ca4,0xafe302c0}}, // ddha, _tfit, ribw, досл,
+ {{0x6d43cca5,0x6f1b859c,0x6e244ca6,0x46ea0c9b}}, // _hana, nguc, ttib, рдан_,
+ {{0x05662386,0x614629c9,0x20d601e2,0x69c1cca7}}, // [3d40] ован, _депа, _мінс, _ohle,
+ {{0x6d43cca8,0x6e244ca9,0xa5bb066e,0x3f8b026c}}, // _jana, rtib, _spór, _emcu_,
+ {{0x224dccaa,0x3a3f810c,0x0b468a0e,0x7afbccab}}, // _arek_, _asup_, жнен, _beut,
+ {{0xab2a941e,0x6d43ccac,0xfe678c3b,0x20184cad}}, // рода_, _lana, _قد_, muri_,
+ {{0x20184cae,0x7afbb2c7,0x7c250083,0x6d4501b4}}, // luri_, _deut, athr, bdha,
+ {{0x6d43ccaf,0x69c1b30f,0x44ce8201,0xbddb009f}}, // _nana, _chle, yə_, _prèv,
+ {{0x657e0013,0x20184cb0,0x29f6809a,0x61e98009}}, // mhph, nuri_, sła_, _etel,
+ {{0x6d4382a3,0x69c7120e,0x44ce8201,0x3860826c}}, // _aana, zmje, və_, ćir_,
+ {{0x20182967,0x7bdc25f3,0x66044cb1,0x224d8168}}, // huri_, _huru, nsik, _grek_,
+ {{0x7bdc4cb2,0x20184cb3,0x637f020f,0xa2a38bb8}}, // _kuru, kuri_, _këng, _छुट्,
+ {{0x6d438025,0x9cdb0158,0x7bdc4cb4,0x224037d1}}, // _dana, יקיפ, _juru, _asik_,
+ {{0xc5f980c8,0x20184cb5,0x7bdc4cb6,0x883b8039}}, // _আমরা_, duri_, _muru, _כתבו,
+ {{0x6d43ccb7,0x7bdc0458,0x6d450079,0x4e969459}}, // _fana, _luru, ydha, _بشار,
+ {{0x6d43ccb8,0x7c254cb9,0x2735001c,0x20184cba}}, // _gana, ythr, _ấn_, furi_,
+ {{0xf77312dc,0x20184cbb,0xe9ff801c,0x69c700f1}}, // وار_, guri_, _trần_, rmje,
+ {{0x69c71351,0x20030035,0xa5bb0032,0x3a2682df}}, // smje, rsji_, _apóp, ktop_,
+ {{0x6b8d4cbc,0x6d43bbb1,0x0ec501fe,0x66043d52}}, // [3d50] _imag, _yana, लेंड, gsik,
+ {{0x7bdc1c88,0x6d43ccbd,0x20184cbe,0x09e200ab}}, // _buru, _xana, buri_, _বিচা,
+ {{0x20180012,0x6d454cbf,0x7afb9699,0x63abccc0}}, // curi_, rdha, _peut, togn,
+ {{0x7bdc4cc1,0x23960029,0x7c2528c6,0x224dccc2}}, // _duru, tāji_, rthr, _prek_,
+ {{0x644e4cc3,0x63abae6c,0x69c1be76,0x65b014c7}}, // _erbi, rogn, _phle, täht,
+ {{0x63ab8098,0x7bdc49fc,0xd24e8bbe,0x7c25388d}}, // sogn, _furu, انو_, pthr,
+ {{0x6d43ccc4,0xdcfc02a5,0xd0f8010f,0x637f00f1}}, // _rana, _smrć, חמות_, _kënd,
+ {{0x61e9a28c,0x3a3f82c4,0x32191122,0x1606970c}}, // _ttel, _tsup_, kusy_, _शिखर_,
+ {{0x6d43ccc5,0x20184cc6,0x7bdc4cc7,0x2eff82af}}, // _pana, zuri_, _zuru, rauf_,
+ {{0x0d828e86,0x6b8d4bb4,0x76498364,0x2eff83ac}}, // ельн, _amag, rvey, sauf_,
+ {{0x6d439e7a,0x3d1c800f,0xc17281c6,0x20184cc8}}, // _vana, _मारे_, _רחב_, xuri_,
+ {{0x6d43a96c,0xeb9a1401,0x20184cc9,0xddcd0503}}, // _wana, сим_, vuri_, jzaž,
+ {{0x6d43ccca,0x2018419b,0xbebb00f1,0x34b803eb}}, // _tana, wuri_, _mbës, _अर्द,
+ {{0x20184ccb,0xdce3a19f,0x6b8d4ccc,0x6d4388ce}}, // turi_, _konč, _emag, _uana,
+ {{0xdfd200f7,0x3945cccd,0x645c956e,0xaca401bc}}, // فيس_, _mals_, ärit, _kpụr,
+ {{0x66043fbb,0x20183f30,0xab6600e8,0x32190690}}, // tsik, ruri_, _хвил, busy_,
+ {{0x644e0d11,0xfbd30051,0x20184cce,0x22400bb1}}, // [3d60] _srbi, _אתה_, suri_, _usik_,
+ {{0xf1bf0742,0x20184ccf,0x6b8d1840,0x7bdc032a}}, // ltán_, puri_, _zmag, _puru,
+ {{0x7bdc4cd0,0x65b001ec,0x7bd62c14,0x6bd606e2}}, // _quru, fähr, _kiyu, ستار,
+ {{0x7bdc369b,0x8fa6268a,0x7bd600fc,0x6282890d}}, // _vuru, _хаме, _jiyu, tzoo,
+ {{0x7d01ccd1,0x7bdc4cd2,0x7c3b0b67,0x7bd6469d}}, // kals, _wuru, _ćure, _miyu,
+ {{0x7bdc1922,0x40348a7c,0x239600eb,0x7d01ccd3}}, // _turu, декс, māju_, jals,
+ {{0x072085e8,0x64a3214d,0x3a26ccd4,0xdca31ddf}}, // _यादव_, _зара, stop_, _зари,
+ {{0x5ea580c8,0x23278abe,0xdce389d1,0x4c9b01c6}}, // খেছে, _носи_, _donč, _הבלו,
+ {{0xc6f787ac,0x6f008300,0x2cba826c,0x3945ccd5}}, // чных_, ramc, _špd_, _fals_,
+ {{0x6b8d4cd6,0x998680d5,0xc243a325,0x7bd60300}}, // _smag, _علاو, _янук, _aiyu,
+ {{0x7bd63a8f,0xb60381a9,0xe6b923bd,0x00000000}}, // _biyu, ēšam, _आर्ज, --,
+ {{0x7bc40330,0x18778039,0x7d018074,0xbebb0168}}, // _chiu, _מעבר_, aals, _mbër,
+ {{0x7d018029,0x637f020f,0x2aab1194,0x602a8085}}, // bals, _rënd, стно_, _məmm,
+ {{0x5e578051,0xd90f0416,0x82778158,0x7c3781df}}, // _מידע_, دید_, לעכע_, ñarí,
+ {{0x68fe11ee,0x2c82846d,0xdb1b80f7,0x6d58879a}}, // _depd, _dédé_, _shuí, _inva,
+ {{0x637f00f1,0xa8578039,0x6b8d4cd7,0x753a9919}}, // _qënd, הילה_, _umag, letz,
+ {{0x63af0f4c,0x9d5500d5,0x9f8a002a,0x2fc9067f}}, // [3d70] mocn, وچست, _añón_, rmag_,
+ {{0x61ed4cd8,0xdb0bb8b4,0x65b001ec,0x2fc94cd9}}, // _ital, mogé, währ, smag_,
+ {{0x29034cda,0x6b9d0d35,0xbddb346b,0x637f0168}}, // maja_, onsg, _près, _tënd,
+ {{0xf99f4cdb,0x290cae1f,0x6b9d1897,0x3d1910f7}}, // près_, _adda_, nnsg, _पाके_,
+ {{0xd945b51e,0x2d998065,0xdce38353,0x6145932a}}, // дели, ések_, _sonč, дела,
+ {{0x99678009,0xbddb00e7,0xeab080f7,0xa2c200d4}}, // ятел, _frèr, دعم_, रेट्,
+ {{0x7d01ccdc,0x753a8102,0x3945ccdd,0x61ed0420}}, // vals, detz, _vals_, _ltal,
+ {{0x29034cde,0x69d71d3a,0x6d588247,0x290cccdf}}, // haja_, _lixe, _anva, _edda_,
+ {{0x6d470d8b,0xa3d10105,0x3945cce0,0x66158035}}, // _kaja, वगन_, _tals_, ązko,
+ {{0x6d474c3c,0x29034ce1,0x7bc44ce2,0xdce3a944}}, // _jaja, jaja_, _shiu, _tonč,
+ {{0x6d474ce3,0x61ed4b14,0x6fbf035a,0x03a6257e}}, // _maja, _atal, ्षां, дино,
+ {{0x6d58cce4,0x6d470247,0x69d70722,0x61fb819d}}, // _enva, _laja, _aixe, _kwul,
+ {{0xa5098af2,0x753a8102,0x69d74ce5,0xf1bf338d}}, // _дела_, betz, _bixe, rtán_,
+ {{0x6d47003b,0xdb0b80f2,0x290328f2,0x69df01b4}}, // _naja, llgä, gaja_, _duqe,
+ {{0x2bd909a3,0x69d701df,0xdee60ba8,0x66e60847}}, // _बंगा, _dixe, доби, доба,
+ {{0xe29a233f,0x23960029,0x6443cce6,0x61fb9e54}}, // _май_, tāju_, _isni, _owul,
+ {{0x6d470733,0x6e29cce7,0x290301ca,0x754646a1}}, // [3d80] _baja, nteb, baja_, мназ,
+ {{0x7ae9cce8,0x29030025,0x6d474ce9,0xf1e18816}}, // _afet, caja_, _caja, _पढ़न,
+ {{0x53e5035f,0x6d4704c4,0x395a047f,0x6ad6816f}}, // еціа, _daja, _inps_, धश्र,
+ {{0x661bbd90,0xeb979bb1,0x6443ccea,0x6e29cceb}}, // kuuk, мих_, _msni, kteb,
+ {{0x6f0401e4,0x6d488514,0x09e387c4,0x628609e8}}, // maic, ydda, вочн, nzko,
+ {{0xc6930158,0x6443803b,0x6d470397,0x7ae98073}}, // _יאר_, _osni, _gaja, _efet,
+ {{0x61fbccec,0x7ae9cced,0x6e29ccee,0x443e0242}}, // _ewul, _ffet, eteb, _èt_,
+ {{0x6f040014,0x6d47368e,0x8b958a18,0xdb1b8362}}, // naic, _zaja, _зруч, _ghuà,
+ {{0x753a8102,0x6e2985b1,0x6d47019e,0x6616127a}}, // tetz, gteb, _yaja, nryk,
+ {{0x2366812b,0x628621a9,0x6f0401e4,0x28a982a8}}, // ljoj_, dzko, haic, _चुकि,
+ {{0x29030796,0x6f0404b9,0x6d4893b0,0x753a8102}}, // vaja_, kaic, rdda, retz,
+ {{0x753a8352,0x23668052,0x93bd002e,0x69d70609}}, // setz, njoj_, ptăm, _sixe,
+ {{0x290314c7,0x61e0a5f3,0x69d74cef,0xf8e28441}}, // taja_, _juml, _pixe, _पलाय,
+ {{0x66160a5c,0x6d58b25b,0x61e0ccf0,0x2d9307b6}}, // dryk, _unva, _muml, xixe_,
+ {{0x6d474cf1,0x69d704c3,0xdb0b80f2,0x80d080ab}}, // _raja, _vixe, llgå, _স্ত্,
+ {{0x6d474cf2,0x316cccf3,0x36d54cf4,0x69dd8037}}, // _saja, _lodz_, еобр, _èseg,
+ {{0x6d47167a,0x61ed4954,0x672d4cf5,0x69d70118}}, // [3d90] _paja, _utal, rfaj, _tixe,
+ {{0x2d9eb3d6,0x20050065,0x33248362,0xd5bb05a8}}, // nnte_, ális_, _acmx_, іса_,
+ {{0x6d474cf6,0x2d9eccf7,0x5b159354,0x79953c07}}, // _vaja, inte_, емет, mizw,
+ {{0x6d4707ba,0x79952486,0x6f044cf8,0x2d9e8192}}, // _waja, lizw, caic, hnte_,
+ {{0x34bb8441,0x6d4709ca,0x6fc58388,0xa3c20c2d}}, // _उर्द, _taja, nóce, ्गत_,
+ {{0x349516df,0x6025a7ae,0xc10581a8,0x645a15d8}}, // _загр, едка, روبي, _štie,
+ {{0xa3af2128,0xb4bd98b8,0x7d0500dd,0x25a621bf}}, // कवा_, _आरे_, mahs, éole_,
+ {{0x2d9eade9,0x6e29ae6c,0x661b8c56,0x2fc68706}}, // ente_, tteb, tuuk, _chog_,
+ {{0xee368b71,0xeab38872,0xf6519ddd,0x9f510019}}, // ены_, _شعر_, ائب_, lszó_,
+ {{0x661bccf9,0x316c809a,0xdb06016b,0x29014949}}, // ruuk, _godz_, doká, _neha_,
+ {{0x6e29ccfa,0x5fdb03eb,0x3eb90aa2,0xf1bf05bf}}, // steb, _मंगल, _lyst_, rtál_,
+ {{0x2b490580,0xf1bf0f09,0x2fd90362,0x22521407}}, // _caac_, stál_, _nisg_, _pryk_,
+ {{0x55599e25,0x29014a85,0x59c4864a,0x2b4902ed}}, // _надя_, _beha_, _लीडर, _daac_,
+ {{0x23960029,0x79953094,0x6609912e,0xdca585fa}}, // tājs_, gizw, msek, _пали,
+ {{0x2f562659,0xa0a30d46,0x62862448,0xa06a03bc}}, // нтас, гард, szko, чава_,
+ {{0x66160bc5,0x2252006a,0xdce700e1,0x613381cc}}, // tryk, _tryk_, _dojč, lılı,
+ {{0x6f040046,0x539b0051,0x63a44cfb,0xe61a4cfc}}, // [3da0] raic, _מיקו, _ijin, яда_,
+ {{0x6f040046,0xbd4600f7,0x6133861c,0x66098198}}, // saic, عناي, nılı, isek,
+ {{0x6b829532,0x6f040c41,0x660980ee,0x61e0ccfd}}, // thog, paic, hsek, _suml,
+ {{0x237f802e,0xc0530039,0x637f00f1,0x25fb0424}}, // _cluj_, _עזר_, _këna, _लौकी_,
+ {{0x63a4022e,0x7d051f32,0x6b828fb0,0x69ce4a0a}}, // _mjin, bahs, rhog, ombe,
+ {{0xe894976e,0x602a8201,0x26ddccfe,0xe29f008b}}, // каль, _həmi, _agwo_, naða_,
+ {{0xc30b00c8,0xe5a305c2,0x69ce4cff,0x2d9e83ba}}, // ষ্টি_, _сири, imbe, vnte_,
+ {{0x9634a64c,0x189780f7,0x2fc681c0,0x63a44d00}}, // вниц, عضوة_, _qhog_, _njin,
+ {{0x79950ab4,0x940a8085,0xe72f9ef7,0x929d8035}}, // yizw, əmək_, صصي_, _pełe,
+ {{0x44380072,0x63a4217b,0xdb060c83,0x2d9e8037}}, // _kpr_, _ajin, voká, unte_,
+ {{0x98be8087,0xbddb0176,0x2cb8bc10,0x4438010c}}, // _dată_, _asèl, ærd_, _jpr_,
+ {{0x69da84a6,0x443801bf,0x1e868c9d,0x290100ee}}, // _hite, _mpr_, _плем, _seha_,
+ {{0x69daa892,0xdb0f0e1b,0x29010748,0x2018011b}}, // _kite, nocé, _peha_, erri_,
+ {{0x613383bf,0x6f02cd01,0x6594a59a,0x38c6826b}}, // cılı, _heoc, _шалу, _gírí_,
+ {{0x69dab416,0xd8380048,0x3ebf0503,0x7995020c}}, // _mite, нэт_, _šut_, rizw,
+ {{0x6d4a831d,0x602a8201,0x63a400f1,0x6d5c3b99}}, // _hafa, _cəmi, _gjin, _anra,
+ {{0x6d4ab468,0x29010006,0xc8f50098,0x44384d02}}, // [3db0] _kafa, _teha_, _изпъ, _apr_,
+ {{0x6d4a930c,0x443800dd,0x69dacd03,0x656e0c41}}, // _jafa, _bpr_, _nite, _cobh,
+ {{0x6d4acb2e,0x7d054d04,0x628e00ce,0x76464d05}}, // _mafa, rahs, ćnoš, _esky,
+ {{0x3f670364,0x61338182,0x6d4acd06,0x4438061b}}, // _чтоб, zılı, _lafa, _dpr_,
+ {{0x69dacd07,0x6133880a,0xaa458198,0x3ebf00e1}}, // _bite, yılı, телл, _áut_,
+ {{0x69dacd08,0x6d4ab11e,0x3d1c8d14,0x61338201}}, // _cite, _nafa, _मागे_, xılı,
+ {{0x69da8458,0xdb0b8c15,0x5ba785e9,0xead18264}}, // _dite, logí, _зраз, াডেম,
+ {{0x70de1a3b,0x69dacd09,0x7f4d1953,0x6609c680}}, // _मल्ल, _eite, ndaq, tsek,
+ {{0x0ea9835f,0x6f02cd0a,0x613385c5,0x69dacd0b}}, // ький_, _deoc, tılı, _fite,
+ {{0x6609cd0c,0x7aed120f,0x63b62445,0x6d4a917d}}, // rsek, _afat, _skyn, _cafa,
+ {{0x2907b0ec,0x64a58e0b,0x61338182,0xdca5b51e}}, // mana_, тана, rılı, тани,
+ {{0x2907cd0d,0xe29a0972,0x69dacd0e,0x91e60811}}, // lana_, дам_, _zite, коме,
+ {{0x69c88364,0x6d4acd0f,0x61338457,0x4a430081}}, // _yhde, _fafa, pılı, аняв,
+ {{0x657acd10,0x6d4acd11,0x2907c899,0xc1740041}}, // ckth, _gafa, nana_, _kuɗi_,
+ {{0x2018279e,0xbbe880f7,0x7aed0428,0x290797c9}}, // urri_, _كريم_, _ffat, iana_,
+ {{0x2907b10d,0x6d4acd12,0x6d218105,0x7d03cd13}}, // hana_, _zafa, _माँग_, _hens,
+ {{0x2907cd14,0x7d038314,0x645500a4,0x44384d15}}, // [3dc0] kana_, _kens, _arzi, _spr_,
+ {{0x2907cd16,0x6455003a,0x7d03888a,0x2d85cd17}}, // jana_, _brzi, _jens, nhle_,
+ {{0x7d03cd18,0x2907ca8e,0x69da965e,0x602a8201}}, // _mens, dana_, _rite, _təmi,
+ {{0x69dacd19,0x7981831d,0x7bc981e4,0x59bc000d}}, // _site, _allw, _cheu, ोगकर,
+ {{0x6d21823c,0x61e403d3,0xdb158019,0x69dacd1a}}, // _मांग_, _juil, óbál, _pite,
+ {{0x644f8341,0x2abd0b4c,0x61e44d1b,0x25fb00c2}}, // ācij, lábú_, _muil, _लौटी_,
+ {{0x69dacd1c,0x26c08110,0xeb9a95fe,0x7bc98014}}, // _vite, _šio_, _жив_, _fheu,
+ {{0x78ba8a56,0x67244d1d,0xf1bf0019,0x69dacd1e}}, // _vytv, ngij, tták_, _wite,
+ {{0x7d03cd1f,0x2907cd20,0xe3b907d9,0x09e68a7f}}, // _bens, bana_, ktır_, _розн,
+ {{0x69da85f8,0x7aed002e,0x6d4a9669,0x444401b4}}, // _uite, _sfat, _qafa, ow_,
+ {{0x351b00be,0xfe6f815b,0x657a80f1,0xf4868729}}, // ווינ, مدی_, rkth, тупн,
+ {{0x63a2cd21,0x6d4acd22,0x44440b99,0x61e42488}}, // nnon, _wafa, iw_, _buil,
+ {{0x81be00c8,0x61e41536,0x6d4aa07b,0x7d0381ec}}, // েদন_, _cuil, _tafa, _fens,
+ {{0xf2d3893f,0x657d80f1,0x61e40046,0x7d03a419}}, // יער_, ësht, _duil, _gens,
+ {{0xb33b0073,0x394c928d,0x6fc59727,0xf4849e29}}, // ança, _mads_, lóca, _حاشی,
+ {{0x7d088343,0x7aed4d23,0x61e40219,0x3dca05ee}}, // lads, _ufat, _fuil, _ahbw_,
+ {{0x6ed60076,0x2907cd24,0x6e20b8b9,0xdfd80098}}, // [3dd0] _मृदु, yana_, numb, тър_,
+ {{0x7d088448,0x2907cd25,0x2713001c,0xbbcb800c}}, // nads, xana_, ện_, िष्क,
+ {{0x6e20b64e,0x98ba01d0,0x83198061,0x9f41010c}}, // humb, _mapě_, فقار_, _luhé_,
+ {{0x29078ef9,0x6e20afe0,0x3d1c835a,0x2613816f}}, // wana_, kumb, _माझे_, _दिली_,
+ {{0x2907b39d,0x6e20a479,0x471a0039,0xee870198}}, // tana_, jumb, _אורג, _рыно,
+ {{0x644732d6,0x660d13fa,0x6e20cd26,0x9aa481a8}}, // _tsji, msak, dumb, جمهو,
+ {{0x2907cd27,0x7d03cd28,0x01380039,0x7bc9cd29}}, // rana_, _rens, דרות_, _theu,
+ {{0x2907ca81,0x2f548364,0x6e209d1b,0x3ea09024}}, // sana_, атьс, fumb, _žita_,
+ {{0x4421059c,0x2907b6d9,0x6e2088a7,0x645c9f0a}}, // luh_, pana_, gumb, årin,
+ {{0x4d65cd2a,0x61e41699,0x394c81a9,0x89348290}}, // уков, _ruil, _gads_, _نعما,
+ {{0x44210393,0x7d0381eb,0xa3c7800f,0x290580eb}}, // nuh_, _vens, _उठा_, _iela_,
+ {{0x290584b8,0x660d4d2b,0x7d03a166,0x4fa480e8}}, // _hela_, ksak, _wens, _києв,
+ {{0x22490117,0x61e44d2c,0x2d8b82be,0x2d8314f1}}, // _csak_, _quil, èces_, _olje_,
+ {{0x44210587,0x2905b6d9,0x660d4d2d,0x61e41699}}, // kuh_, _jela_, dsak, _vuil,
+ {{0x6d41cd2e,0xa6db0125,0x2905cd2f,0x44210057}}, // mela, _maðu, _mela_, juh_,
+ {{0x6d41cd30,0x61e40ad0,0x44210587,0x8a960077}}, // lela, _tuil, duh_, _مشخص,
+ {{0x6724044a,0x1fa73650,0x660d28c0,0x6b843e21}}, // [3de0] rgij, укти_, gsak, _ilig,
+ {{0x290589b2,0x85064d31,0x5bdc8ebf,0x76440db1}}, // _nela_, اوان, यद्व, rwiy,
+ {{0x44440586,0x44210812,0x290a4d32,0x660d10e1}}, // uw_, guh_, maba_, asak,
+ {{0xd9cc8576,0x290a24e1,0x09e696be,0x501b0039}}, // ाष्ट, laba_, годн, _אוטו,
+ {{0x6d41cd33,0x63a2cd34,0x69de25fa,0x2fc00f06}}, // kela, rnon, _kipe, mlig_,
+ {{0xfce30e86,0x44210812,0x2905cd35,0x661bc603}}, // _горо, buh_, _cela_, druk,
+ {{0x2905cd36,0x6d41b067,0x442100dd,0x290a4d37}}, // _dela_, dela, cuh_, iaba_,
+ {{0x6d4e4d38,0xa3e7823c,0x2fc00f91,0xa8570039}}, // _haba, _बढ़_, nlig_, ריקה_,
+ {{0x6d4e4d39,0x6d41cd3a,0x290a21ea,0x7d088b81}}, // _kaba, fela, kaba_, tads,
+ {{0x6b844d3b,0x01d79a00,0x6d4e0b50,0x26190105}}, // _alig, _موقع_, _jaba, यूटी_,
+ {{0x6d4e4d3c,0x290a4d3d,0x6e2093b2,0xc445803d}}, // _maba, daba_, sumb, لیون_,
+ {{0x6d4e3615,0x7d0882ee,0x661bcd3e,0x2fc014a2}}, // _laba, sads, bruk, jlig_,
+ {{0x6d41abf2,0x2fc01e7c,0x6b8401e4,0x2905c732}}, // bela, dlig_, _dlig, _yela_,
+ {{0x29050307,0x6b840733,0x69de003a,0x22491df8}}, // úla_, _elig, _cipe, _vsak_,
+ {{0x69de0458,0x4cd280ab,0x97a722ea,0x645a0115}}, // _dipe, _দ্রু, грал, _štin,
+ {{0x2fc00f06,0x6f06008e,0x6b8401f4,0x5f942503}}, // glig_, _cekc, _glig, бист,
+ {{0xdb0f3930,0x6458826c,0x4421003d,0x98a503bf}}, // [3df0] nocí, _hrvi, wuh_, ılı_,
+ {{0x290a0207,0x6d4e2388,0x4421059c,0x31352ed9}}, // caba_, _caba, tuh_, _теор,
+ {{0x6add8e18,0x6d4e4d3f,0x2905810b,0x7bcd01c5}}, // यश्र, _daba, _rela_, _khau,
+ {{0x4421059c,0x6d41cd40,0x64588668,0x8f470cf9}}, // ruh_, zela, _mrvi, _сход,
+ {{0x290580a9,0x4421059c,0x7bdf009f,0x6d418234}}, // _pela_, suh_, _miqu, yela,
+ {{0x6d4e4d41,0x2d8300ce,0x6d418ece,0x44214d42}}, // _gaba, _ulje_, xela, puh_,
+ {{0x6602990b,0xddcb8110,0x2905cd43,0xa3e48740}}, // _awok, žiūr, _vela_, _भूल_,
+ {{0x6d4e4d44,0xee39838d,0x291b8201,0x290a4d45}}, // _zaba, вни_, şqa_, zaba_,
+ {{0x6d418890,0x6d4e4d46,0x2905cd47,0x6458cd48}}, // tela, _yaba, _tela_, _arvi,
+ {{0x6d4e4d49,0x290a4d4a,0x80bb016f,0x6b964d4b}}, // _xaba, xaba_, _शुभे, _smyg,
+ {{0x30a788cc,0x69de4d4c,0x63a9835f,0x7bdf0187}}, // _срав, _ripe, _kjen, _biqu,
+ {{0xe72e99b6,0x7bcd03d3,0x6d41cd4d,0xeb9a1935}}, // _ќе_, _chau, sela, тим_,
+ {{0x63a9cd4e,0x2fc00e23,0x7bcd4d4f,0x7bdf0333}}, // _mjen, vlig_, _dhau, _diqu,
+ {{0x6f06372c,0x7d07000d,0x7bdf03a8,0x63a98140}}, // _sekc, _nejs, _eiqu, _ljen,
+ {{0x63bbcd50,0x7bdf4d51,0x9c13819d,0x2fc04d52}}, // _okun, _fiqu, _kọmu, tlig_,
+ {{0x6d4e4d53,0x63a9cd54,0x290a0c6f,0x63bb9119}}, // _saba, _njen, saba_, _nkun,
+ {{0x7ae40578,0x2fc00f91,0x290a4d55,0xaa460b88}}, // [3e00] _igit, rlig_, paba_, _тепл,
+ {{0x6d4e381f,0x63a9acb1,0x929d809a,0x63bb86a0}}, // _qaba, _ajen, _pełn, _akun,
+ {{0x2d9a4d56,0x6d4e0006,0x7d074d57,0x2fc04d58}}, // ripe_, _vaba, _dejs, plig_,
+ {{0xfbc39056,0x6d4e06a0,0x63a982ce,0x2d9a0247}}, // обхо, _waba, _cjen, sipe_,
+ {{0x7ae4803e,0x7d070067,0x2d9a4d59,0x425481a8}}, // žito, _fejs, pipe_, إنتر,
+ {{0x63a9813c,0x2d9a00f1,0x93bd0087,0x799c020c}}, // _ejen, qipe_, ntăr, nirw,
+ {{0x64a30084,0x50668071,0xdc670071,0x76598114}}, // _дара, ртла, _кард_, _arwy,
+ {{0x63a9cd5a,0x7ae44a76,0x799c00b4,0x394301a9}}, // _gjen, _ngit, hirw, zejs_,
+ {{0xd0fb1513,0x7bcd022c,0x799c020c,0xe9da87b6}}, // ्रमण_, _rhau, kirw, _ака_,
+ {{0x7bdf4d5b,0x7bcd453f,0x76598114,0x637f0168}}, // _siqu, _shau, _drwy, _gënj,
+ {{0xdeef96fe,0xfe1887e6,0x2ec30770,0x409680d6}}, // _мы_, _दिवस_, _शर्त, _трат,
+ {{0x7bcd022c,0x63a981b9,0x645882d4,0x06ea8a27}}, // _qhau, _xjen, _vrvi, _ऑलिव_,
+ {{0xc3338039,0x7bdf009f,0x7762cd5c,0x7985022c}}, // חור_, _viqu, _inox, _plhw,
+ {{0x7ae400ad,0x799c020c,0x3f891670,0x7bdf0580}}, // _egit, girw, whau_, _wiqu,
+ {{0x7d074d5d,0x7bcd10af,0x3f890355,0x7bdf16dc}}, // _rejs, _thau, thau_, _tiqu,
+ {{0x386d2828,0xb8cf00c2,0x04db81c6,0x93bd0493}}, // ćer_, _कश_, _בקול, drăg,
+ {{0x6b8704c0,0x3f890114,0xfaff0168,0x799c36ed}}, // [3e10] _مشکل, rhau_, mbë_, birw,
+ {{0x63a9811f,0x6b9d033e,0x7bc283a8,0x78ad87c0}}, // _sjen, lisg, elou, şavi,
+ {{0x70550c48,0x63a98668,0xaab802e3,0x637f0168}}, // _انجا, _pjen, _مدیر_, _nënk,
+ {{0x2d9802d0,0x6b9d2937,0x6e243648,0xdce380c3}}, // _emre_, nisg, buib, _tonć,
+ {{0x63a9a1be,0x6f0d0079,0x4ec180ab,0x7f3a00be}}, // _vjen, maac, _উল্ল, _גערו,
+ {{0x5d850250,0x7762b6f8,0x3f868580,0x6b9d0bb1}}, // _السل, _anox, _alou_, hisg,
+ {{0x63a98a38,0x3f86867f,0x7bc2cd5e,0xb4c28778}}, // _tjen, _blou_, blou, ्धो_,
+ {{0x6d452b36,0x7bc2cd5f,0x63bba6bd,0x63a9cd60}}, // leha, clou, _ukun, _ujen,
+ {{0x799c00b4,0x332d816a,0x3f9900c3,0xf1bf0061}}, // yirw, _icex_, _mmsu_, ntás_,
+ {{0x51748013,0x41749e95,0x27178028,0x6d4545e8}}, // _والأ, _والس, ển_, neha,
+ {{0x61f60bd9,0x3b0a140b,0x61e98020,0xe7c703eb}}, // _styl, лено_, _huel, लताप,
+ {{0x61e98010,0x80d080ab,0x3fc9803d,0x03a60a0e}}, // _kuel, _স্ক্, _صدای_, симо,
+ {{0xeb97204a,0xf668803d,0x61e1cd61,0x799c0f8e}}, // сия_, _نحوه_, _kill, tirw,
+ {{0x645a1123,0x61e9cd62,0x61e1cd63,0x9f580388}}, // _štim, _muel, _jill, mpré_,
+ {{0x63b98d38,0xdca2867c,0x61e1938e,0x64a2b4fb}}, // kown, _наши, _mill, _наша,
+ {{0x4425cd64,0x61e1a280,0x93bd0087,0x799c0542}}, // mul_, _lill, stăr, sirw,
+ {{0x44258c6e,0x0d86196e,0x63b9cd65,0x06ca01e5}}, // [3e20] lul_, блан, down, угай_,
+ {{0xd9100416,0x6d45105d,0xbddb0032,0x4425cd66}}, // _زیر_, geha, _apèb, oul_,
+ {{0x4425cd67,0x98a78084,0xdb0f1727,0x224d8176}}, // nul_, menė_, locá, _asek_,
+ {{0x61e1cd68,0x6f0d02ed,0xf1bf066e,0x61e9811b}}, // _aill, caac, stát_, _buel,
+ {{0x44258006,0x61e19ed3,0x61e98e1b,0x20058609}}, // hul_, _bill, _cuel, _awli_,
+ {{0x61e980ad,0x4425ca13,0x61e19581,0x76498079}}, // _duel, kul_, _cill, dwey,
+ {{0x6c860013,0x61e1cd69,0x01338013,0x4425a3ee}}, // _الأم, _dill, سعود, jul_,
+ {{0xf1bf01ac,0x63b9809a,0x644e0079,0x645c3f3c}}, // ntár_, cown, _isbi, _irri,
+ {{0x61e1cd6a,0x35f7803d,0xb4c286ab,0x7c3b00fe}}, // _fill, یرید_, ्ध्_, _ćuri,
+ {{0x61e1cd6b,0x4425b309,0x61ef80e7,0x644e026c}}, // _gill, ful_, _écla, _ksbi,
+ {{0xb5fb0510,0x4425cd6c,0x22400077,0x61e98102}}, // _gráf, gul_, _apik_, _zuel,
+ {{0x29c98511,0xe29f007b,0x290ecd6d,0x60db80be}}, // núan_, laði_, lafa_, _עקאנ,
+ {{0x61e1cd6e,0x6b9d051e,0xfaff03ed,0x6d454d6f}}, // _yill, risg, rbë_, yeha,
+ {{0x44258850,0x645c011e,0x61e18079,0x644e08ae}}, // bul_, _orri, _xill, _osbi,
+ {{0x65750a56,0x44258012,0x6f0d30f9,0x644e0267}}, // _rozh, cul_, taac, _nsbi,
+ {{0x2d9ecd70,0x53a40251,0x27e200fc,0x290ecd71}}, // lite_, _најб, _cikn_, hafa_,
+ {{0x645c3260,0x55751677,0x6d454d72,0x6f0d01b4}}, // [3e30] _arri, огат, teha, raac,
+ {{0xae1e009a,0xf1bf0e06,0x2d8700e1,0x994d0084}}, // यंजन_, rtás_, óne_, ržų_,
+ {{0x6d454d73,0x61e985b4,0x58d5059d,0xe29f01fa}}, // reha, _suel, _фонт, daði_,
+ {{0x61e1c36d,0x2d9ecd74,0x6d451ad4,0x53c917a3}}, // _sill, hite_, seha, रतिश,
+ {{0x2d9e8a8e,0x4425cd75,0x2bb18063,0x764982a3}}, // kite_, zul_, jące_, xwey,
+ {{0x61e99099,0x61e19abf,0x2d9e8805,0x4425bd9b}}, // _vuel, _qill, jite_, yul_,
+ {{0x39530101,0x4425cd76,0x7dc69434,0x69dd81e8}}, // _maxs_, xul_, rðsl, _èsem,
+ {{0xe6199b78,0x533493b4,0x4425cd77,0x63ad27ce}}, // ади_, _нест, vul_, _njan,
+ {{0x61e1cd78,0x2d9e8578,0x69c380e1,0x44259cc5}}, // _till, fite_, plne, wul_,
+ {{0x4425cd79,0x2d9e80a9,0x161e000f,0xb5fb0510}}, // tul_, gite_, यूटर_, _tráf,
+ {{0x5baa168a,0x61fd2170,0x92e500ab,0xb5fb4d7a}}, // иком_, epsl, _পড়ে_, _frág,
+ {{0x44258c6e,0x3c31826f,0x394788f8,0xc169007c}}, // rul_, ráv_, lens_, _בח_,
+ {{0x4425ad7b,0x2d9ec453,0x63ad4d7b,0x0cc64252}}, // sul_, bite_, _djan, _वर्म,
+ {{0x6b89c339,0x4425cd7c,0x39478082,0x80d080ab}}, // _lleg, pul_, nens_, _স্ট্,
+ {{0x765d00f1,0x6b9b82ec,0xaf9a1ae1,0x6b89cd7d}}, // _arsy, _omug, атах_, _oleg,
+ {{0x82a600e8,0x290e8242,0x6b898133,0xfbc30081}}, // _майж, zafa_, _nleg, _общо,
+ {{0x1dc501fe,0xb6a604fa,0x7ae4826f,0x2240008e}}, // [3e40] वतंत, циал, žitk, _upik_,
+ {{0x6b89cd7e,0x6b9ba0ee,0x644e0267,0x26c20084}}, // _aleg, _amug, _psbi, _vyko_,
+ {{0x3947990e,0x66e62139,0x2db680be,0x29c98511}}, // dens_, _мона, קלען_, túan_,
+ {{0x3947913b,0x89360019,0xd62a0eef,0xdce18ec3}}, // eens_, _اٹھا, _коме_, eklī,
+ {{0x290ec219,0x28f8854c,0x3947cd7f,0x5f0301e2}}, // tafa_, бель_, fens_, _ізра,
+ {{0x3947cd80,0x753c003b,0xd01a95b7,0x201a841c}}, // gens_, _ubrz, афи_, ápio_,
+ {{0xcc2480ab,0x645c0102,0xda0b8105,0xe29f008b}}, // _বন্ধ_, _urri, सीबत_, raði_,
+ {{0x2d9ecd81,0xfc4683fb,0x93fb02f6,0x290ecd82}}, // wite_, šíku_, _כללי, safa_,
+ {{0xe29a80a9,0x7d188216,0x21a682bc,0xdcfa81d0}}, // _таа_, _pdvs, _диам, chtě,
+ {{0x3947cd83,0x66090214,0x63ad004a,0x2bb1866f}}, // cens_, _çeke, _sjan, rące_,
+ {{0xa3e4800f,0x63ad4d84,0x7d0a807b,0x290c80b9}}, // _भूख_, _pjan, _vefs, _ieda_,
+ {{0x3f8dcd85,0x78ad84e8,0x291a0db1,0xf7468956}}, // cheu_, ťový, _adpa_, _федо,
+ {{0x7fd78159,0x7d0ab66c,0xcb138039,0x753d8019}}, // _וואס_, _tefs, ולר_, ősza,
+ {{0x290ccd86,0x27fa00dd,0x15f1064a,0xad9b026b}}, // _jeda_, _btpn_, _अंबर_, _ewúj,
+ {{0x6d48829e,0xdfd50198,0xcf9b04ae,0x69d8a023}}, // meda, зовы, аја_, imve,
+ {{0x491703bb,0x290ccd87,0x7c2881bf,0x442105b9}}, // _भएको_, _leda_, mudr, vrh_,
+ {{0x3f848b99,0x394780e7,0x645a81a3,0xd6db0956}}, // [3e50] ikmu_, yens_, lvti, ртв_,
+ {{0x63bd0052,0x6d48cd88,0x6b89cd89,0x32090901}}, // losn, neda, _sleg, _iway_,
+ {{0x3947cd8a,0x6b89bbfe,0x29111acc,0xd5b8102a}}, // vens_, _pleg, maza_, ісу_,
+ {{0x61ed0590,0x291100fa,0x69c74292,0x6d488074}}, // _kual, laza_, llje, heda,
+ {{0x290ccd8b,0x6d48cd8c,0x61ed0057,0x645e8988}}, // _beda_, keda, _jual, _špil,
+ {{0x6d488025,0x88df00c8,0x29110ca3,0xaca3019d}}, // jeda, _ব্যক, naza_, _apục,
+ {{0x764285b4,0x290ccd8d,0x3947920b,0x764d0234}}, // _apoy, _deda_, rens_, lway,
+ {{0x6b9b8578,0x6d554d8e,0x3947a280,0x2911308f}}, // _umug, _haza, sens_, haza_,
+ {{0x6d554d8f,0x29114d90,0x3947800b,0xd7068009}}, // _kaza, kaza_, pens_, чные_,
+ {{0x61fb9904,0x69c70499,0xb998a1f6,0x9f420168}}, // _itul, jlje, _двух_, _pikë_,
+ {{0x6d554d91,0x1b0e80ab,0x3209279a,0x29110300}}, // _maza, _হাতে_, _away_, daza_,
+ {{0x61ed0c41,0x320902d6,0x29110087,0x09b4064a}}, // _bual, _bway_, eaza_, ंक्य,
+ {{0x61ed0693,0xed598767,0x69c180b9,0x69c7026c}}, // _cual, _гол_, _kkle, flje,
+ {{0x6d554d92,0x61ed0219,0x6e298264,0x291128e3}}, // _naza, _dual, mueb, gaza_,
+ {{0x61e50609,0x32091029,0x7ae982f9,0xfaff03ed}}, // _dihl, _eway_, _oget, gjës_,
+ {{0x7ae9b112,0x7ae481ac,0x09a8a0d8,0x61fba4f2}}, // _nget, žiti, _औद्य, _otul,
+ {{0x69c70ee1,0x6fc58073,0x61ed4d93,0x29111ea2}}, // [3e60] blje, góci, _gual, baza_,
+ {{0x2a604d94,0x6d554d95,0x637f0168,0x69c7026c}}, // _irib_, _caza, _dënu, clje,
+ {{0x981607bd,0x61fbb339,0x644380b9,0x6d554d96}}, // _عبدا, _atul, _jpni, _daza,
+ {{0x261c823c,0x290c8665,0x225f821e,0x764d0365}}, // _मिली_, _seda_, _bruk_, bway,
+ {{0x6f0439fd,0x11548c4f,0x6d4881f8,0x3b0d81b4}}, // mbic, дклю, yeda, _deeq_,
+ {{0x225f8b3c,0x6443cd97,0x6d55205e,0xb5fb007b}}, // _druk_, _opni, _gaza, _fráb,
+ {{0x8c434d98,0x6d48cd99,0xd90d8077,0xf7729e91}}, // _пере, veda, شین_, فاظ_,
+ {{0x69c1cd9a,0x6d554d9b,0x29110314,0x7c288687}}, // _ekle, _zaza, zaza_, vudr,
+ {{0x6d55448d,0x290ccd9c,0x6d48cd9d,0x6578822c}}, // _yaza, _teda_, teda, _povh,
+ {{0x63a2cd9e,0x61eb00e7,0x290c89c4,0xb33b041c}}, // lion, _égli, _ueda_, diça,
+ {{0xfe6f8bca,0x61ed378e,0xdb06026f,0xab5b02d0}}, // ندی_, _sual, soký, slüm,
+ {{0x63a2cd9f,0x291130a0,0x764d3392,0x637f00f1}}, // nion, waza_, yway, _nënt,
+ {{0x61ed2a4b,0x63bd4da0,0x6d48a81c,0x6282c423}}, // _qual, rosn, peda, nyoo,
+ {{0x63a29d15,0xbddb22c6,0x2a600176,0x26c6cda1}}, // hion, _apèn, _drib_, _iyoo_,
+ {{0x29114da2,0x6d554da3,0x63a2cda4,0x27e68176}}, // raza_, _raza, kion, _kion_,
+ {{0x69c723e3,0x15f1023c,0x764d4da5,0xf7730f24}}, // slje, _अंदर_, tway, راز_,
+ {{0x63a282f7,0x6d554da6,0x29114da7,0x672d04b9}}, // [3e70] dion, _paza, paza_, rgaj,
+ {{0x6d550201,0x7d1c0267,0xb5fb016a,0x26c701a9}}, // _qaza, _cdrs, _arác, āno_,
+ {{0x6b8d01eb,0x645e8024,0x80c280ab,0x61fbcda8}}, // _klag, _špij, ষেত্, _stul,
+ {{0x69c1cda9,0x6d552cc3,0xf65f006a,0x6e24117d}}, // _skle, _waza, _træ_, brib,
+ {{0x6e244daa,0x6d55207b,0x26c681c0,0x2fc903fc}}, // crib, _taza, _nyoo_, mlag_,
+ {{0xd90f8117,0x2fc94dab,0x6b8d023e,0xbd4501a8}}, // ایا_, llag_, _llag, _تنسي,
+ {{0x23690025,0x63a2809a,0x645a0042,0x6b8d4dac}}, // _onaj_, bion, _štiv, _olag,
+ {{0x225fcdad,0x7e64811f,0x6b8d01bc,0x69c18035}}, // _truk_, _šipa, _nlag, _wkle,
+ {{0xe3b0819f,0x6f0f35ae,0x645cb20d,0x673b9320}}, // _صرف_, _mecc, ærin, rfuj,
+ {{0x6b8d4dae,0x6f0f12d0,0x6e2985a4,0x2fc901ec}}, // _alag, _lecc, rueb, hlag_,
+ {{0x637f0168,0x27e69009,0x1b4a091e,0x753d8061}}, // _nëns, _fion_, озни_, őszo,
+ {{0xfaa601e5,0x6b8d0362,0x15ed064a,0x7bda80e5}}, // дамо, _clag, जदार_, _ètut,
+ {{0x6e29907c,0x7ae482d4,0xe29714ef,0x6e24029a}}, // queb, žitv, дая_, xrib,
+ {{0x63a2cdaf,0x6f1d353e,0xb33b03a7,0x27e68102}}, // zion, _adsc, riça, _zion_,
+ {{0x50b51b47,0x6f0f4db0,0x14d19130,0x91e30425}}, // есту, _becc, _हरिण, торе,
+ {{0x63a2a09c,0x201e0289,0x6f0f4db1,0x2905008b}}, // xion, štio_, _cecc, ðlar_,
+ {{0x6f044db2,0x26c681e9,0x2a60010c,0x7bc42aa0}}, // [3e80] rbic, _xyoo_, _urib_, _ikiu,
+ {{0x236902a5,0x6f044db3,0x63a2809a,0x6282804f}}, // _znaj_, sbic, wion, vyoo,
+ {{0x63a286f3,0xb5fb016b,0x5f06069b,0x2fc94db4}}, // tion, _prác, _озна, blag_,
+ {{0x6e24447b,0x7d0e326b,0x27f70efc,0x7d0501a8}}, // prib, _webs, žené_, abhs,
+ {{0xd0418085,0xb5fb01d0,0x25be8216,0x6fc5841c}}, // _izlə, _vrác, yotl_, nócu,
+ {{0x2fc000f7,0x6f0f01e8,0x14b70072,0x6d48016b}}, // loig_, _zecc, _अडचण, ímač,
+ {{0x03a689b8,0xb5fb4db5,0xa3c3073c,0xa2a51370}}, // _живо, _trác, ्तम_, _चेन्,
+ {{0x97a38d15,0xab5b1238,0x2269928a,0x44259502}}, // крыл, rlük, átký_, drl_,
+ {{0x7ae9025b,0x03a38087,0x91a6819d,0x6b8d0362}}, // žetk, _рито, gwọ_, _rlag,
+ {{0x6b8d44ef,0xe16695a9,0x7bc40c53,0xe123997b}}, // _slag, _تدري, _akiu, _амфи,
+ {{0x6b8d4db6,0xe046804a,0x291e9e8f,0x308687c3}}, // _plag, _інди, _idta_, _تلاف,
+ {{0x0a6b066a,0xa3c0b850,0xdcf504e8,0x76460123}}, // орги_, ुति_, _rozč, _opky,
+ {{0xfe7281bd,0x61e8977c,0xed5a8e8e,0x6b8d0805}}, // _جدا_, _kidl, _мод_, _vlag,
+ {{0x6f0f1883,0x7dcf8aa2,0x02a30135,0x7bc405ee}}, // _secc, løsn, _nrụọ, _ekiu,
+ {{0x61e8c62b,0x6f0f4db7,0x656a80f7,0xdc3e8ec3}}, // _midl, _pecc, _infh, rģēt,
+ {{0x7e6292f1,0x6d58cdb8,0x6b8d003a,0x442ccdb9}}, // _krop, _hava, _ulag, mud_,
+ {{0x442ccdba,0x6d58cdbb,0x4ad30076,0x2fc904fe}}, // [3e90] lud_, _kava, _सरिव, rlag_,
+ {{0x2fc9190e,0xca48003d,0x6d5acdbc,0x36d40087}}, // slag_, _کلیه_, ndta, тоур,
+ {{0x442ccdbd,0x2fc94dbe,0x6d58804f,0x3c388061}}, // nud_, plag_, _mava, név_,
+ {{0xad9b0dfa,0x61e88362,0x61fb0162,0x291e8e06}}, // _stúd, _aidl, ţulu, _adta_,
+ {{0x657c005f,0x645a003a,0x61e881b9,0xa2a52342}}, // _forh, _štit, _bidl, _चेम्,
+ {{0x442ccdbf,0x6d5887dd,0x7dd00106,0xdb1d0106}}, // kud_, _nava, läsn, llsä,
+ {{0xb8f79344,0x442cb55e,0x61e88915,0x7e628091}}, // _सर_, jud_, _didl, _arop,
+ {{0x7aed4dc0,0xa4d4035f,0x442ccdc1,0xa2f90540}}, // _ngat, _росі, dud_, ंडोज_,
+ {{0x6d58cdc2,0xe4e30b75,0x6e2d016a,0xaad3c765}}, // _bava, _कृति_, nuab, _तरिक,
+ {{0x6d58cdc3,0x7aed4dc4,0x777d0282,0xc8fb064a}}, // _cava, _agat, _losx, ्रोम_,
+ {{0x6d58cdc5,0x44258362,0x442ccdc6,0x2907807a}}, // _dava, url_, gud_, mbna_,
+ {{0x69c54dc7,0x2d8ecdc8,0x64470197,0x7853829a}}, // _akhe, _elfe_, _mpji, _nəvə,
+ {{0xbe878b76,0x6d5880e5,0x7e62cdc9,0x776b8234}}, // _تجاو, _fava, _grop, _ingx,
+ {{0x442ca09b,0x6d58cdca,0x201e025b,0x442582d4}}, // bud_, _gava, štim_, prl_,
+ {{0x1beb09a3,0x25a580f5,0x186744cc,0xa0670eef}}, // _जंगल_, mill_, нари_, нара_,
+ {{0x6d58984c,0x3f890867,0x9f468081,0x7bc4004f}}, // _zava, gkau_, _cioè_, _ukiu,
+ {{0xcf9280be,0x6e3bcdcb,0x63a609da,0x6d58cdcc}}, // [3ea0] אטן_, ntub, mikn, _yava,
+ {{0x63a609ca,0x25a5cdcd,0x09062927,0x7dc6053d}}, // likn, nill_, _япон, رقان,
+ {{0x03269d91,0x657c02af,0x399b00be,0x61e8839c}}, // еден, _vorh, _זייד, _ridl,
+ {{0xb5fb0019,0x61e8cdce,0x63a6026f,0x6e3b8a32}}, // _irán, _sidl, nikn, ktub,
+ {{0x6f630364,0x25a5807b,0x71a68e11,0xf1bf0511}}, // _связ, kill_, надз, irán_,
+ {{0x64a622d2,0xdca61507,0xf1bf00f7,0x442ccdcf}}, // _зага, _заги, hrán_, yud_,
+ {{0xb0da92c6,0x442c8085,0x63a64dd0,0xfdbe0035}}, // येंग, xud_, kikn, ्तीफ,
+ {{0xdb1d02be,0x395a0074,0x9fb8016b,0x6d58cdd1}}, // posé, _laps_, _očí_, _sava,
+ {{0x6d58cdd2,0x61e886be,0xf1bf05a4,0x569481e5}}, // _pava, _tidl, drán_, тапт,
+ {{0x442c8778,0x40968fbb,0x721b84de,0x6286219c}}, // tud_, _прет, _מומח, dyko,
+ {{0x46ea80a9,0x6fc58e15,0xf1bf0333,0x63a64dd3}}, // _еден_, tóct, frán_, fikn,
+ {{0x442c99f6,0xaca4019d,0xf1bf4dd4,0x63a600ee}}, // rud_, _apịr, grán_, gikn,
+ {{0x6d589ff8,0x7e62cdd5,0x32191d5e,0x442ccdd6}}, // _tava, _urop, ussy_, sud_,
+ {{0xf2d3893f,0xa02482af,0xdee38012,0x442ccdd7}}, // טער_, _größ, _сочи, pud_,
+ {{0xda1d05b3,0xc4f7893f,0x8d5b8039,0xf1bf016a}}, // _भटकत_, _אזוי_, _זכוי, brán_,
+ {{0x0aeb0f99,0x7aed4dd8,0x6e2d4dd9,0x317e8c00}}, // _عربي_, _ugat, tuab, _lotz_,
+ {{0x28a7881f,0x66041400,0x9f4680e5,0x8aa40a41}}, // [3eb0] _केनि, gpik, _cioé_, _брэд,
+ {{0x2249003a,0xb5fb4dda,0x3f894cfa,0x64550061}}, // _ipak_, _frán, skau_, _pszi,
+ {{0x6f1602bf,0xdcf8803a,0x26e200c8,0x645a0353}}, // bayc, _novč, গুলো_, _štir,
+ {{0xa3c3123a,0xae218592,0x28c4809a,0x20010234}}, // ्ति_, _मिलन_, _लुधि, _ithi_,
+ {{0xe1f890ac,0x6e3bab01,0xdb0825b3,0x2fd90101}}, // егі_, xtub, élék, _ihsg_,
+ {{0x6145bbae,0x25a5007b,0x63a600b9,0x7dcb02f1}}, // тека, _öll_, zikn, küsi,
+ {{0xa3c323bd,0x62861bae,0x25a5cddb,0x29134ddc}}, // ्ता_, zyko, vill_, _mexa_,
+ {{0x9ff300c8,0xf7719ddd,0xfe458073,0x6d8d8144}}, // _জবাব_, شاب_, внио, _cúan,
+ {{0xe80d81ce,0x63a6005c,0x25a5a51d,0x99558087}}, // _सबका_, vikn, till_, _икац,
+ {{0x6e3b9600,0x2913008e,0xb46580e8,0x62864b51}}, // rtub, _nexa_, вкол, vyko,
+ {{0x63a6085c,0x6e3b913c,0x20010282,0x22494ddd}}, // tikn, stub, _nthi_, _apak_,
+ {{0x6286185c,0x395a4dde,0x03a62ec1,0x661d0140}}, // tyko, _saps_, тимо, _ovsk,
+ {{0xeb9709b4,0xed4ecddf,0x63a6010b,0x25a5cde0}}, // тия_, _ко_, rikn, pill_,
+ {{0x412a035f,0x2fcdcb48,0x63a63286,0x7d1ccde1}}, // ього_, lleg_, sikn, órst,
+ {{0x6f09826f,0x661d0186,0xed52003d,0x21210144}}, // obec, _avsk, یپر_, _ddhh_,
+ {{0x2fcd8eb9,0x810d886a,0x626619a5,0xe4e480e8}}, // nleg_, हरुख_, _авиа, _січн,
+ {{0x69c399e0,0x63a43b20,0xee3a0c5c,0xb8c9199e}}, // [3ec0] kone, _imin, ьна_, _खे_,
+ {{0x66040072,0x69c3835f,0xb5fb001c,0x6d41cde2}}, // rpik, jone, _trán, nfla,
+ {{0x66044de3,0x69c3cde4,0xad9b026b,0x741315a9}}, // spik, done, _awús, لولا,
+ {{0x69ce05ec,0x09ca970c,0xb5fb001b,0x661b94c7}}, // llbe, ित्य, _král, ksuk,
+ {{0x69c3a280,0x27e04de5,0xaefb0362,0x2fcd821e}}, // fone, mmin_, _cnùu, dleg_,
+ {{0x29184de6,0xf5030198,0x2fc68687,0x39908362}}, // nara_, _взро, _skog_, _bàsa_,
+ {{0xa3d201ab,0x6da30f2e,0x6d5c04bc,0x656e0046}}, // वता_, _тира, _iara, _inbh,
+ {{0x27e013f2,0x368a87b6,0x588381e5,0x661b808e}}, // nmin_, дсон_, рыша, fsuk,
+ {{0x6d5c34b1,0x291831cc,0x6e299123,0x69c3c4de}}, // _kara, kara_, greb, bone,
+ {{0x63a44de7,0x29184de8,0x69c3cde9,0xe7ed864a}}, // _amin, jara_, cone, _चूका_,
+ {{0x92af150b,0x27e00859,0x6d5c4290,0x7c3e4dea}}, // _করে_, kmin_, _mara, ntpr,
+ {{0x6d5c4deb,0x2913062f,0x224900b9,0x6e299fd6}}, // _lara, _sexa_, _ppak_, breb,
+ {{0x27e04dec,0x29184ded,0x6fc59e00,0x6722cdee}}, // dmin_, fara_, pócr, _idoj,
+ {{0x29184def,0x63a40d6a,0x660290ab,0x7dc6808b}}, // gara_, _emin, _itok, rðss,
+ {{0x28f802c7,0x25ab80e7,0x291301df,0xdb1b80ff}}, // тесь_, ècle_, _vexa_, _khuô,
+ {{0x69c3cdf0,0x6d5c02b8,0xe4e30770,0x63a4009a}}, // zone, _aara, _कृषि_, _gmin,
+ {{0x29184df1,0x645a075f,0x6ff180eb,0x69c3cdf2}}, // [3ed0] bara_, _štip, _vāci, yone,
+ {{0x29180763,0x63a407d8,0x660902d0,0x5c070ba7}}, // cara_, _zmin, _çekm, вяда,
+ {{0x6d5c127b,0x80a2835a,0x6ec9800d,0x2001005d}}, // _dara, _केले, _हुनु, _uthi_,
+ {{0x6602b642,0x69c385f8,0xff040ba7,0x6d5c1c9d}}, // _otok, wone, _вярн, _eara,
+ {{0x6d5c05a3,0x69da88ae,0xa3ab21ef,0x1308804a}}, // _fara, _bhte, _गगन_, тній_,
+ {{0x6d5c4a2b,0x6e299edc,0xdb0600f1,0xdb1d0019}}, // _gara, vreb, tikë, dosí,
+ {{0x6602cdf3,0x2fcdcdf4,0x14c7003d,0x7c3e090d}}, // _atok, tleg_, _شهری_, ctpr,
+ {{0x6e29ba71,0x29183a2f,0xdb060168,0xdb1b80ff}}, // treb, zara_, rikë, _chuô,
+ {{0x6d5c4df5,0x29184df6,0x2fcd821e,0x7f5d4df7}}, // _yara, yara_, rleg_, _masq,
+ {{0x6e29cdf8,0x6d5c0be9,0x63a40ecb,0x27e04df9}}, // rreb, _xara, _smin, zmin_,
+ {{0x29182cdb,0x6d418e51,0x51848a42,0xb4b10035}}, // vara_, rfla, _тута, _ओशो_,
+ {{0x29184400,0xb5fb38b4,0x7f5d07f1,0xe81d00c2}}, // wara_, _arám, _nasq, _फिजा_,
+ {{0x29184dfa,0x6d4715f2,0x69da825d,0x9d4581e5}}, // tara_, _obja, _yhte, _белд,
+ {{0xed5681cf,0xf65281c6,0x21f980d7,0xf1a6914f}}, // _бош_, _לצד_, _mèh_, कोपन,
+ {{0x29184dfb,0xb5fb4dfc,0xf99f0247,0x7c3e2900}}, // rara_, _drám, nsèy_, xtpr,
+ {{0x29184dfd,0x2bbb18b8,0x6d474dfe,0x63a40461}}, // sara_, _उदया, _abja, _umin,
+ {{0x7bc6185b,0x29c98388,0x0e660110,0xb5fb0174}}, // [3ee0] moku, núas_, ыкан, _frám,
+ {{0x6d5c4dff,0x1a9b0158,0x3c3c458c,0x7bc98176}}, // _qara, _נייע, tív_, _akeu,
+ {{0x6d5c4e00,0xef198063,0x31570158,0x7f5d002a}}, // _vara, każ_, _גיין_, _fasq,
+ {{0x69da88cf,0x6d5c4e01,0x44261fc2,0x7bc60234}}, // _shte, _wara, čo_, noku,
+ {{0x6d5c4e02,0xef19809a,0x7bdb8079,0x69650fd3}}, // _tara, daż_, _dhuu, _صدیق,
+ {{0x1db801fe,0xdd920481,0x6d5c4e03,0x443e8357}}, // _आदित, _اوس_, _uara, ytt_,
+ {{0x7bc64e04,0x6b8092f1,0x3f824e05,0x443e03e7}}, // koku, _tomg, _koku_, _ët_,
+ {{0x3f820364,0x201e82f7,0xf09301c6,0x2d838198}}, // _joku_, _vvti_, מנו_, öjen_,
+ {{0xaab88f12,0x09368bbe,0xa91c80e1,0x01368199}}, // _अशिक, _مراج, _voľb, _معاد,
+ {{0xa2a50beb,0xd6570039,0x7981ce06,0x3b004e07}}, // _चेष्, ניית_, _golw, _afiq_,
+ {{0x3a200118,0x94d4bc7a,0x7e640100,0xdb1b827d}}, // _avip_, ронц, hvip, _thuô,
+ {{0xdb09820f,0xd4978c6e,0x3f8209b6,0x7dcb0965}}, // _vjeç, урь_, _noku_, lüst,
+ {{0x6602ce08,0x7f5d07f1,0x395e86ab,0x00000000}}, // _utok, _rasq, _kats_, --,
+ {{0xdcfc1024,0xad9b0032,0x365b81c6,0xba00819d}}, // _borč, _atún, _נכונ, _ọọ_,
+ {{0x6ab0ce09,0x7f5d4e0a,0x7d150061,0x43950110}}, // जपुर, _pasq, _rezs, _вайс,
+ {{0x7d1a8c0f,0x29c98125,0x998681d0,0x395ece0b}}, // lats, núar_, spoň_, _lats_,
+ {{0xb5fb02ba,0xaaa48023,0x61f61f1b,0x3f824e0c}}, // [3ef0] _trám, _खेलक, _guyl, _doku_,
+ {{0x442c81d8,0x7d1ace0d,0x7bc98a53,0x7bdbce0e}}, // ord_, nats, _skeu, _shuu,
+ {{0x320480b9,0xd5b81ac0,0x7f5d0722,0xdcfc011a}}, // _atmy_, уст_, _tasq, _gorč,
+ {{0x7d1ace0f,0xfaff00f1,0xc8ff80d4,0x2a694d7b}}, // hats, ncë_, ॉर्म_, _krab_,
+ {{0x6f0d0483,0x7d1ace10,0xee8717ae,0x7981a404}}, // mbac, kats, _сыно, _polw,
+ {{0x7d1abbce,0x7bc620b3,0xef19809a,0xe1f080d7}}, // jats, zoku, waż_, سسه_,
+ {{0x7bc6400f,0x7981837a,0x7d1ace11,0x6f0d4d10}}, // yoku, _volw, dats, obac,
+ {{0x2d8300a9,0x82f880f7,0x44334e12,0x6f0d1839}}, // _hoje_, _مركز_, mux_, nbac,
+ {{0x2d830025,0xe61687ac,0x442c9aee,0x399400f2}}, // _koje_, ады_, erd_, _läsa_,
+ {{0x7d1ace13,0x6f0d4e14,0x2b5f81c0,0x1c458b73}}, // gats, hbac, _hauc_, аном,
+ {{0x04458c8e,0x5f748077,0x69c70390,0xeb9a1593}}, // шенн, _گالر, doje, вин_,
+ {{0x6266007f,0x2d834e15,0x6e2d4e16,0x2b490286}}, // авма, _loje_, krab, _cbac_,
+ {{0x3f8209a4,0x7d1a81d4,0x442cce17,0x6f1bce18}}, // _roku_, bats, ard_, mauc,
+ {{0x6f1bce19,0x69c70824,0xdcfe00eb,0x48dc800d}}, // lauc, goje, āvīg, गेको_,
+ {{0xdca292b2,0x6f0d4e1a,0x0cb000c8,0x7bc6022e}}, // _маши, fbac, _কর্ত, poku,
+ {{0xa3c32ab7,0xcc3a80be,0x6441888b,0x4433022b}}, // ्तः_, _סעפט, mtli, dux_,
+ {{0x2d83003a,0x443300e7,0x644181ec,0x24f6002e}}, // [3f00] _boje_, eux_, ltli, ачер,
+ {{0xf21380ab,0x69c70144,0xf507002e,0xd24f00d7}}, // _সমূহ_, coje, инул_, _کنی_,
+ {{0x6441c582,0x3f824e1b,0x224dce1c,0x6f0d0041}}, // ntli, _toku_, _apek_, bbac,
+ {{0x7d1ace1d,0x21258359,0x395e92f1,0xdb0b8187}}, // zats, _adlh_, _sats_, ligê,
+ {{0x395e8df1,0x44332739,0x236001c0,0x644183e3}}, // _pats_, aux_, _maij_, htli,
+ {{0x6441ce1e,0x2360022c,0x44330197,0xa50713f7}}, // ktli, _laij_, bux_, _вера_,
+ {{0x7d1acbab,0x224da34d,0x7dcb497b,0xdb1d0061}}, // vats, _epek_, rüst, őzés,
+ {{0xf1bf0298,0x236001c0,0x7d1ace1f,0x7dcb0074}}, // nuár_, _naij_, wats, süst,
+ {{0xd6d78987,0x7d1ace20,0xb3558019,0x69c701f8}}, // ить_, tats, _دینا_, yoje,
+ {{0x64418456,0xddc2809a,0x71ea826a,0x6f0d012b}}, // ftli, _społ, _شفاف_, zbac,
+ {{0x7d1abfb3,0x442c80f3,0x1b190009,0x291c96dc}}, // rats, urd_, лжны_, mava_,
+ {{0x291cce21,0x7d1a9aed,0x236001e9,0x6f1b91b9}}, // lava_, sats, _caij_, cauc,
+ {{0x6b844e22,0x9f420364,0x48fd800d,0x69c704a9}}, // _doig, _mikä_, रुको_, toje,
+ {{0x660907d9,0x6e2d4e23,0x63ab98f1,0x98a5811c}}, // _çeki, vrab, zign, _zalı_,
+ {{0x6b96108c,0x97a72597,0x69c70e3f,0x628b809a}}, // _flyg, арал, roje, zygo,
+ {{0x6e2d02e8,0xcf980b30,0x291cce24,0xa3d20d72}}, // trab, ају_, hava_, वतः_,
+ {{0x69c70ac1,0x63abce25,0x645c003d,0x7bcd1400}}, // [3f10] poje, vign, _asri, _ikau,
+ {{0x6e2d4e26,0x6f0d1d31,0x44330197,0xf1bf0061}}, // rrab, sbac, tux_, dság_,
+ {{0x224d831e,0x63abce27,0x6b840216,0x201e01d0}}, // _spek_, tign, _yoig, átit_,
+ {{0x6e2d4e28,0xdea1003d,0x2360022c,0x4433300b}}, // prab, _فیزی, _xaij_, rux_,
+ {{0xd6db4718,0x644e026c,0xeab095a9,0x63ab85ed}}, // _што_, _epbi, وعه_, rign,
+ {{0x9f420364,0x27ef8051,0x98a582d0,0x7d189b40}}, // _eikä_, _sign_, _salı_, _nevs,
+ {{0x04950013,0x7c950013,0x6f1ba71c,0x6c850f99}}, // _الأح, _الأص, tauc, _الزم,
+ {{0x7bcd01e9,0x5ba48b88,0x28b80039,0x63688615}}, // _nkau, _друз, יפוש_, _круг_,
+ {{0x6f1bce29,0x2294a2b7,0xceb981d0,0xb5fb0174}}, // rauc, _дитя, kaře_, _brái,
+ {{0x291cce2a,0x7bcd4e2b,0x66099fe8,0x6f1bce2c}}, // cava_, _akau, rpek, sauc,
+ {{0x30a7a2d2,0x6b844e2d,0x200581b9,0x34c50035}}, // _трав, _poig, _utli_, _वडोद,
+ {{0x69de020f,0x64419690,0xf989010f,0x44213cc0}}, // _shpe, rtli, _אר_, ish_,
+ {{0x63a9bd8b,0xeab20065,0x66058a13,0xad9b04be}}, // _mmen, سٹس_, _упла, _atúm,
+ {{0x637f020f,0x63bbce2e,0x44234e2f,0x63a9ce30}}, // _mëny, _ljun, _ovj_, _lmen,
+ {{0xb9001cd4,0x63a9ce31,0xdcfc0029,0x442100f1}}, // _तर_, _omen, _norā, jsh_,
+ {{0xd00f12dc,0x291ca76e,0x07a30ba5,0xe3b90380}}, // علق_, zava_, пасн, lsın_,
+ {{0xa3bd0105,0xdd8f004e,0xea0080ff,0xdee32240}}, // [3f20] _आदत_, کول_, _đắm_, зоти,
+ {{0x63bba5d7,0x201e2ed8,0x291c87d0,0x627b03de}}, // _ajun, átis_, xava_, _ענינ,
+ {{0x291cbe3d,0x261986a7,0xbddb06c4,0x130a802e}}, // vava_, _मौसी_, _apès, гней_,
+ {{0xf3f980c8,0x201e0110,0xe29a8ba7,0x63a9ce32}}, // _আবার_, štis_, лае_, _cmen,
+ {{0x291a0978,0x63bbce33,0xe3b901cc,0xc6b28264}}, // _kepa_, _djun, ksın_, _চর্চ,
+ {{0x63a9ce34,0x55e301a0,0x6f0281e8,0x399400e1}}, // _emen, _нояб, _sfoc, _mäso_,
+ {{0xf1bf0019,0x6e228419,0xe04a803d,0xc8668081}}, // sság_, _svob, اشته_, стли,
+ {{0x4444005f,0xbb438009,0x291a04c4,0x3f9202f7}}, // mt_, _неск, _lepa_, nkyu_,
+ {{0xa3d5823c,0x76443d4f,0xf66a803d,0x877b03de}}, // हतर_, ntiy, احبه_, ראפי,
+ {{0x63a98038,0x7bcd4e35,0x7bdf00f1,0x4444396a}}, // _zmen, _skau, _shqu, ot_,
+ {{0xe6d50a74,0x2ed5000d,0x69ca8b80,0x769e826b}}, // _दर्ज, _दर्त, mofe, _bàyé,
+ {{0x44443c72,0xc7f78039,0x69ca8d35,0x7d1e4e36}}, // it_, _הזאת_, lofe, haps,
+ {{0x44444e37,0xb5fb4e38,0x7d1e225f,0x2aab0eef}}, // ht_, _trái, kaps, утно_,
+ {{0x44444e39,0x398f02f1,0x44214e0e,0x69cace3a}}, // kt_, _küsi_, ysh_, nofe,
+ {{0x44442ca8,0x291a4463,0x6f0d8087,0x251b8e82}}, // jt_, _depa_, _încâ, _גווא,
+ {{0xa3bd00a5,0x398b983d,0xd5b881a9,0x69cace3b}}, // _आदि_, _høst_, lnā_, hofe,
+ {{0xa3cc8076,0x63af31a7,0x88bc801b,0xaa591594}}, // [3f30] रकन_, micn, _sděl, _типу_,
+ {{0xa3dd05b3,0x44444e3c,0x63af0025,0x63a9ca52}}, // _तीत_, ft_, licn, _smen,
+ {{0x44443f82,0x490f000d,0xa91c80e1,0xc8a79993}}, // gt_, _त्यो_, _poľn, _केंट,
+ {{0x63af007d,0x2d980267,0x93bd0087,0x291a0118}}, // nicn, _elre_, crăt, _zepa_,
+ {{0x44441645,0x7f3a0158,0xa91c81ac,0x60c481cc}}, // at_, _דערו, _voľn, şiml,
+ {{0xa9699d8f,0x2903009a,0x44444e3d,0x69cac53e}}, // рика_, ncja_, bt_, gofe,
+ {{0x44444e3e,0x88bc801b,0x63bb81a1,0x93e981a8}}, // ct_, _uděl, _tjun, _نفسك_,
+ {{0x6d572363,0xdfd18013,0x63bb8e60,0x63a9c96b}}, // lexa, بيع_, _ujun, _umen,
+ {{0x7bcb803a,0x64450014,0x2903009a,0x660d4e3f}}, // mogu, lthi, kcja_, npak,
+ {{0x7bcbce40,0xdb0bce41,0x225f88f9,0x6d5703cd}}, // logu, digé, _isuk_, nexa,
+ {{0x64454e42,0xe3ba0009,0x7e648279,0x320922f9}}, // nthi, ибо_, _šipt, _atay_,
+ {{0x3ce10d38,0x61fb98c5,0x64454e43,0x7e6b826c}}, // _करने_, _kuul, ithi, _trgp,
+ {{0x61fb8074,0xb69b0087,0x644536a2,0x63bd0144}}, // _juul, _sfân, hthi, érna,
+ {{0x6445308b,0x225f8359,0x4444043c,0x660d2a52}}, // kthi, _msuk_, yt_, dpak,
+ {{0x61fba551,0x63af0289,0x7bcb8074,0x6d574e44}}, // _luul, bicn, kogu, dexa,
+ {{0x673b84e8,0x44440f06,0x657526ca,0x6563ce45}}, // nguj, vt_, _anzh, _hanh,
+ {{0x660d1066,0x7bcbce46,0x2a6d822c,0x44444e47}}, // [3f40] gpak, dogu, _nreb_, wt_,
+ {{0x44444e48,0x76444e49,0xd910026a,0x656389ab}}, // tt_, rtiy, _سیر_, _janh,
+ {{0x44444e4a,0x39584755,0x76444e4b,0xe45a1ad0}}, // ut_, mers_, stiy, рже_,
+ {{0x395801eb,0x2905ce4c,0xa3d4800f,0xa91c80e1}}, // lers_, _afla_, _सीख_, _poľo,
+ {{0x6f040b80,0x7dd00106,0x2a6002a6,0xdd1c936f}}, // mcic, fäst, _jsib_, váži,
+ {{0xb5fb0a56,0x39581989,0x082a80af,0x63af00fe}}, // _práv, ners_, ации_, zicn,
+ {{0x69cace4d,0x7bcb8032,0x444401b9,0xcf9a84ae}}, // rofe, bogu, qt_, ији_,
+ {{0x7d1c1591,0x656380c9,0x261101ce,0x39580039}}, // _hers, _aanh, दीकी_, hers_,
+ {{0x7d1c4e4e,0x65638073,0x61fb82a3,0x63af0b67}}, // _kers, _banh, _guul, vicn,
+ {{0x2d988063,0x48e6ba2d,0xb5fb026f,0x3958022b}}, // óre_, _дозв, _tráv, jers_,
+ {{0x2cfb0451,0x63af00d2,0x39581527,0x7d1c4e4f}}, // יליא, ticn, ders_, _mers,
+ {{0xb5fb003e,0xddc28029,0x236680f1,0x6f0401dd}}, // _krát, _droš, ndoj_, jcic,
+ {{0x7988829b,0xf1bf03fb,0x2d87ce50,0x7d1c2525}}, // _kodw, krát_, _fone_, _oers,
+ {{0x39584a3f,0x656380a9,0x63af02a5,0x628f2c18}}, // gers_, _ganh, sicn, ryco,
+ {{0x63af00d2,0x3494a853,0x37ab2481,0x6ca38081}}, // picn, _хайр, атан_, _оръж,
+ {{0x6720911b,0x22468461,0x660d27f2,0x644501c0}}, // namj, ktok_, tpak, vthi,
+ {{0x7d1c0fc7,0x63ad4e51,0x2b59002e,0x59ca8743}}, // [3f50] _bers, _iman, mesc_, ाकार,
+ {{0x660d4e52,0x7d1c3f21,0x93bd0087,0x79888114}}, // rpak, _cers, crăr, _nodw,
+ {{0x437596d9,0x6d574e53,0x7d1c0b0b,0x61fbce54}}, // зулт, rexa, _ders, _suul,
+ {{0x7d1c24de,0x64454e55,0x6d574e56,0x2b59002e}}, // _eers, rthi, sexa, nesc_,
+ {{0x7d1c0c82,0x27e94e57,0x644503e9,0x63ad4e58}}, // _fers, mman_, sthi, _mman,
+ {{0x27e94e59,0xa3dd146d,0x7d1c4e5a,0x8dfa0039}}, // lman_, _तीस_, _gers, _להשת,
+ {{0x63ad4e5b,0x7857817e,0x765908c5,0xb5fb002a}}, // _oman, تیاز_, şayı, _usáb,
+ {{0x27e94e5c,0x39584e5d,0x2a6d8e72,0x61fb8df8}}, // nman_, zers_, _treb_, _tuul,
+ {{0x28d2101b,0xb5fb4e5e,0x2d87a752,0x65638420}}, // _दुनि, _grát, _pone_, _panh,
+ {{0x63ad3f6d,0xddc2883d,0x27e90c9e,0x6f1d4e5f}}, // _aman, _proš, hman_, _lesc,
+ {{0xb5fb003e,0x65638364,0x27e9133b,0x1b2000ab}}, // _krás, _vanh, kman_, _ভাবে_,
+ {{0x27e906c0,0x6f1d4e60,0xdb0605bf,0x39580bbf}}, // jman_, _nesc, fiká, wers_,
+ {{0x27e94e61,0xa3cc9344,0x39583593,0x386100dd}}, // dman_, रकत_, ters_, _ashr_,
+ {{0x2d9ece62,0x63ad4e63,0xf1bf4e64,0x64a58539}}, // chte_, _eman, drás_, _нала,
+ {{0x6f1d2644,0xddc29408,0x22468019,0x2b590087}}, // _besc, _uroš, ztok_, besc_,
+ {{0x39584e65,0x27e93794,0xab9a0077,0x2fcd8b3c}}, // sers_, gman_, _دختر_, noeg_,
+ {{0x6f1d2a70,0x59cb800c,0x6e243953,0x41cb8c78}}, // [3f60] _desc, िवार, usib, िवास,
+ {{0x7d1c00f1,0x6b9b9cef,0x63ad0353,0x27e94e66}}, // _qers, _alug, _zman, aman_,
+ {{0x7d1c0faf,0x1b0d80c8,0x3ce1016f,0xa3c3016f}}, // _vers, _হয়ে_, _करणे_, ्तच_,
+ {{0x6f1d1736,0x8c4907d9,0x7d1c009a,0xb5fb4e67}}, // _gesc, daşı, _wers, _prát,
+ {{0x7d1c045c,0x236681f4,0xb5fb016a,0x7988809a}}, // _ters, rdoj_, _drás, _podw,
+ {{0xb5fb0a21,0x47360077,0x6f1d00d2,0x69ce148c}}, // _vrát, _برگز, _zesc, nobe,
+ {{0x6b9b8189,0x34a70652,0x2d8a0118,0xdb060c83}}, // _flug, _евро_, _iobe_, ziká,
+ {{0x3ce10d38,0xc91804e5,0xb5fb4e68,0x69ce4e69}}, // _करते_, थरुम_, _trát, hobe,
+ {{0xfaa39092,0xed588038,0x2d9ece6a,0x443a4e6b}}, // _зато, teľ_, thte_, lup_,
+ {{0x63ad003b,0xf64f8064,0x27e906c0,0xd9c6064a}}, // _sman, ائي_, zman_, लकोट,
+ {{0x27e94154,0x69ce1b3b,0x2b590087,0xdcfc0035}}, // yman_, dobe, tesc_, _gorą,
+ {{0x2d9e88cf,0x69d8ba01,0x291e8106,0xa3e286a7}}, // shte_, llve, _heta_, _नीम_,
+ {{0x2b59002e,0x27e906c0,0x291ece6c,0x2707801c}}, // resc_, vman_, _keta_, _ống_,
+ {{0x7e62cc16,0x23de823c,0x2b59002e,0x291e80f1}}, // _isop, _फीसद, sesc_, _jeta_,
+ {{0x291ec304,0xa3be805e,0xa3cc800d,0xdfd50d15}}, // _meta_, ीका_, रका_, довы,
+ {{0x291ece6d,0x6d5ab065,0x443a14ff,0x63ad0d02}}, // _leta_, leta, dup_, _uman,
+ {{0xd6db0b88,0x9f4c81ac,0x6f1d458e,0x27e933e1}}, // [3f70] ств_, _budú_, _vesc, rman_,
+ {{0x6d5a80ad,0x27e90242,0x69ce22d8,0x7bcf0087}}, // neta, sman_, cobe, locu,
+ {{0x4427128e,0x27e906c0,0x443a0590,0x7d0502af}}, // én_, pman_, gup_, rchs,
+ {{0x6d5ace6e,0x27e93752,0x245e001b,0xfc300bbe}}, // heta, qman_, _dům_, احل_,
+ {{0x6d5a95ee,0xe6670a14,0x6e22012b,0x2fcd81b0}}, // keta, _этно, šobr, voeg_,
+ {{0xac192a0e,0x3f8b0052,0x443a02f7,0x2d8a4e6f}}, // _могу_, _hocu_, bup_, _gobe_,
+ {{0x6b89a4de,0x6d5a98c1,0xb9058054,0x291ece70}}, // _toeg, deta, _नर_, _deta_,
+ {{0x69ce4e71,0x06099ad0,0x389c03c8,0xefc800ff}}, // zobe, оник_, מיונ, _địch_,
+ {{0x200c8201,0x291e881a,0x7e628019,0x2d8a0041}}, // _etdi_, _feta_, _csop, _yobe_,
+ {{0x291e8125,0xa3d5809a,0x1dbf4c5c,0x442781a9}}, // _geta_, हतक_, ्वित, _pvn_,
+ {{0xb8089e13,0x65670353,0x69ce0b80,0xf8e183eb}}, // _فيلم_, _majh, vobe, _फरिय,
+ {{0x7e6d026f,0x2d9cce72,0x3f8b025b,0x61c601cb}}, // kvap, _elve_, _nocu_, लक्ष,
+ {{0x6d5ace73,0x65a002af,0x1dbf0bbc,0x61ff0609}}, // beta, _höhe, ्वात, _cuql,
+ {{0x9f5ec64b,0x6d5ace74,0x443a4e75,0x64554e76}}, // _että_, ceta, yup_, _opzi,
+ {{0x69ce4e77,0xa067117e,0xdb008009,0x3f8b00ce}}, // robe, мара_, _ymmä, _bocu_,
+ {{0xe61a4e78,0x69ce0b24,0xafe31ef8,0x200c01a9}}, // жда_, sobe, восл, īdi_,
+ {{0xa1c61506,0x60c48214,0x69ce069f,0x64551d46}}, // [3f80] _обид, ğimi, pobe, _apzi,
+ {{0xca370039,0xa2ad016f,0x2257003d,0xee3700e8}}, // _קניה_, ीपर्, _کلید_, ьну_,
+ {{0x0b46a133,0x291ece79,0x07a62357,0x013500be}}, // днен, _reta_, _жанн, _אָרט_,
+ {{0x6d5ace7a,0x443a4e7b,0x291ece7c,0x3ebf012b}}, // zeta, rup_, _seta_, _žute_,
+ {{0x291e859c,0x6d5ace7d,0x2d8a4e7e,0xd250806b}}, // _peta_, yeta, _tobe_, انت_,
+ {{0x6d5aae24,0x7afb811b,0x6e3b81b4,0x672400b9}}, // xeta, _egut, duub, laij,
+ {{0x291ece7f,0x6d5ace80,0xa3d98740,0xf54e801c}}, // _veta_, veta, _ठीक_, _bụng_,
+ {{0x291e8133,0x6d5ace81,0x3320023e,0x3a268637}}, // _weta_, weta, _neix_, psop_,
+ {{0xf54e8104,0x2b44826c,0xb146802e,0x63a2ce82}}, // _dụng_, _icmc_, _онал, mhon,
+ {{0x2a648e72,0x60cd83bf,0xdcfc0bda,0x200c80b9}}, // _ismb_, şama, _dorć, _ttdi_,
+ {{0x6d5a888b,0x75370051,0x53ca02f1,0x7bcf28bf}}, // reta, ואיד_, रवंश, tocu,
+ {{0x69c002be,0x6ec9984a,0x6448ce83,0x67c181a9}}, // émen, _हुकु, rtdi, pēja,
+ {{0x6d5ace84,0x6448ce85,0x7bcf18f6,0x61f7019c}}, // peta, stdi, rocu, _pixl,
+ {{0x7c3acd42,0x7bcf4e2a,0x246c928a,0x6d439867}}, // putr, socu, _těm_, _gcna,
+ {{0x63a2829b,0x855784de,0xb5fb4e86,0x98480081}}, // khon, _חשוב_, _aráp, мята_,
+ {{0x1b1900c8,0x27e6814a,0x7e6d8390,0xab5b02d0}}, // _থাকে_, _jhon_, _šapi, ylüy,
+ {{0x7f4d00f1,0x63a2803d,0x6567022c,0x27e682f7}}, // [3f90] rfaq, dhon, _pajh, _mhon_,
+ {{0xd9e306b7,0xaad28105,0xdb0b8216,0x995400e1}}, // _गठित_, _तड़क, rigí, _päť_,
+ {{0x6567022c,0x27e68bb1,0x7c28ce87,0xddc2866f}}, // _vajh, _ohon_, nsdr, _spoś,
+ {{0xa3a98063,0x2b44802a,0xd6d00019,0x63a2ce88}}, // गों_, _ccmc_, اقے_, ghon,
+ {{0x6b8d4e89,0xdcf881d0,0xf54e8129,0x7d0ac4f2}}, // _moag, _pově, _rụng_, _offs,
+ {{0xa3e60fd5,0x2b44808e,0x2bb8826c,0x6845bc3d}}, // _बीन_, _ecmc_, dčcu_, енка,
+ {{0x27e68014,0x200102f1,0x442a0bfd,0x00000000}}, // _bhon_, _juhi_, _gvb_, --,
+ {{0xd0f80bea,0x7afbce8a,0x9e65a240,0x2bbb056b}}, // ומות_, _ugut, евид, _उदगा,
+ {{0xa3cc81fe,0xb8d6816f,0xa3be816f,0xea0080ff}}, // रकर_, _चे_, ीकर_, _đầm_,
+ {{0x2121008e,0x4c8586e9,0x89388dca,0x27e68362}}, // _nehh_, елов, _опус_, _ehon_,
+ {{0xe572093f,0x3f158009,0x33200722,0x6e3b82ec}}, // אַר_, _здес, _peix_, suub,
+ {{0xfe71815b,0x27f94e8b,0xf1bf0118,0xcf2780d7}}, // _مدت_, _nisn_, usán_, _آراي,
+ {{0x27edce8c,0xc33282f6,0x661d01c0,0x998401d0}}, // mmen_, נוך_, _nwsk, émů_,
+ {{0xc2128451,0x6e298234,0x20010420,0x74ca8a27}}, // רהם_, mseb, _buhi_, _सुकृ,
+ {{0xddc40289,0xa3c1ce8d,0x212102f7,0x9f5e82d6}}, // jviš, ुवा_, _dehh_, _butè_,
+ {{0x27ed8205,0x6f0983ac,0x442a05db,0x2121008e}}, // nmen_, ncec, _rvb_, _eehh_,
+ {{0x290502bb,0xee3a2f4b,0x6e29ce8e,0xdce3a355}}, // [3fa0] ılar_, яна_, nseb, _manč,
+ {{0x6d41ce8f,0x27ed8352,0x6e360683,0xdce38289}}, // ngla, hmen_, rryb, _lanč,
+ {{0x236901c0,0xb5fb016a,0x63a2ce90,0x273a8380}}, // _yaaj_, _usán, thon, nünü_,
+ {{0x2486003a,0x8c1a8039,0xb5fb016b,0x69dc4e91}}, // ćom_, פוני, _tráp, llre,
+ {{0xfeba08ca,0x45240264,0x6e298366,0x27ed8865}}, // _راحت_, _পাঠক_, jseb, dmen_,
+ {{0x63a2ce92,0x27e68c41,0x27eda0dd,0x6e29807a}}, // shon, _shon_, emen_, dseb,
+ {{0x63a2ce93,0xfce31383,0xb882001b,0xdce3807a}}, // phon, _боро, _říjn, _banč,
+ {{0x2fc04e94,0x6d5e4e95,0x69dc4161,0xdd258084}}, // nnig_, lepa, hlre, _rūši,
+ {{0x7c3e003e,0x2fc04e96,0x7ae42169,0x67c50084}}, // lupr, inig_, _nzit, nėja,
+ {{0x6d5e4e97,0x6d41ce98,0x7bd60c2e,0x7bc40c53}}, // nepa, ggla, _akyu, _ajiu,
+ {{0xd6da8065,0xd4978012,0xb5fb0032,0x27e6ce99}}, // _کوشش_, еря_, _asál, _thon_,
+ {{0x20012008,0x29180661,0x67c50084,0x27e68420}}, // _ruhi_, ebra_, kėja, _uhon_,
+ {{0xdfcf8077,0x4e93804e,0x20014319,0x7c28804a}}, // _اين_, _مشور, _suhi_, ssdr,
+ {{0x6d5e0654,0x67c50110,0xdb0f0187,0x7c3e4e9a}}, // jepa, dėja, micí, kupr,
+ {{0xb90905b3,0xbebb08cf,0xdb0f02ba,0x61fa9fba}}, // _बर_, _ndër, licí, _mitl,
+ {{0x443eb15d,0x987b8158,0x7c3e00fe,0x26020074}}, // mut_, האלט, dupr, _वंशी_,
+ {{0x443ece9b,0x660287ac,0xdb0f4e9c,0x391512bc}}, // [3fb0] lut_, _juok, nicí, _импр,
+ {{0x443e81e0,0x6d5e0ac5,0x66028009,0xad9b046d}}, // out_, gepa, _muok, _atúr,
+ {{0x443e99e0,0xe3be8182,0x66028009,0x656a8239}}, // nut_, ğına_, _luok, _mafh,
+ {{0x04c800f7,0xe3be817b,0xddc40da8,0x6e298114}}, // روني_, şına_, rviš, yseb,
+ {{0x443ece9d,0x21699541,0xa96994d6,0x61face9e}}, // hut_, дили_, дила_, _bitl,
+ {{0x443eb69d,0x2369812b,0xd8380db7,0x6d5e4e9f}}, // kut_, žaj_, šče_, cepa,
+ {{0xdce3a9a5,0x443ea5e0,0x6d5501c0,0x27ed8dc6}}, // _panč, jut_, _ibza, tmen_,
+ {{0x3f89372d,0x3994016d,0x776d4ea0,0x6d4181c0}}, // njau_, _häst_, ndax, wgla,
+ {{0x764d2ad9,0x6f0986cb,0x6458a52a,0x3ea94ea1}}, // ntay, rcec, _apvi, nzat_,
+ {{0x6e29845c,0xa8a78191,0x30a796d4,0x61fa82d5}}, // rseb, _прек, _прев, _gitl,
+ {{0x443ecea2,0x03a61ad0,0x6e29cea3,0xdce3cea4}}, // gut_, вино, sseb, _bonġ,
+ {{0x399400f2,0x6d41cea5,0x09da016f,0x7e6d80d2}}, // _läst_, sgla, णत्य, _šapu,
+ {{0xa2d503ca,0x3ea90168,0x6d5e10fa,0x98b00140}}, // _युद्, jzat_, yepa, _čađe_,
+ {{0x784a8085,0x39940106,0x67c50084,0xdb0604e8}}, // _səvi, _näst_, vėja, nikú,
+ {{0x443e8012,0x67228072,0x3f8f8074,0x64a59b67}}, // cut_, _yeoj, _kogu_, _шака,
+ {{0x3ebf01dd,0x212780ff,0xa0670073,0x764d4ea6}}, // _žuta_, hanh_, њата_, ftay,
+ {{0x3f8f803a,0x6d5e1958,0x399400f2,0x7523a62d}}, // [3fc0] _mogu_, tepa, _bäst_, _kenz,
+ {{0xdb0b8298,0x2fcd0805,0xa3e60105,0xa80601cf}}, // vigá, čega_, _बीत_, _изол,
+ {{0xdd100038,0x81d800ab,0x2fc00122,0x499b1101}}, // _týžd, িষদ_, pnig_, _חשיב,
+ {{0xeb97baa3,0x3f8f8a20,0x7523bee9,0x78a30085}}, // ких_, _nogu_, _lenz, _ünva,
+ {{0x443e8c3a,0x7c3e3079,0x425480a0,0x67228144}}, // zut_, supr, انتر, _reoj,
+ {{0x657c0355,0x66028364,0x7f958510,0x7f4400e7}}, // _unrh, _ruok, _máqu, lgiq,
+ {{0x9346867c,0x3f8f811f,0xa3c43d9c,0x61faa51d}}, // _инве, _bogu_, ्वा_, _vitl,
+ {{0x443ecea7,0x2d8e8390,0x61fa9de6,0xf76f81a8}}, // vut_, _sofe_, _witl, قاً_,
+ {{0x443ea1e5,0xa3c1881f,0x3f8f811b,0x9f590176}}, // wut_, ुवर_, _dogu_, _disè_,
+ {{0x66028364,0x61fa838e,0x7bc28362,0xb5fb026b}}, // _vuok, _uitl, lnou, _asáj,
+ {{0x3ea94ea8,0x35678087,0x7e6d86ec,0xd7ef8615}}, // zzat_, трын_, _šapt, _ву_,
+ {{0x443e8dab,0x040d801c,0x316c826c,0x752381ed}}, // rut_, _hưởn, _hadz_, _eenz,
+ {{0x443eadaf,0xe5c6bacb,0x161b82f1,0x3f8f008b}}, // sut_, _иско, पीआर_, ögum_,
+ {{0x656a806a,0x7bc281d0,0x7523cea9,0x3f8f83ed}}, // _uafh, hnou, _genz, _zogu_,
+ {{0x9f350221,0xd48fceaa,0x59f8002e,0x76408c2e}}, // леві, _тр_, теря_, lumy,
+ {{0x3ea929b8,0xb8db2bef,0x764d4eab,0x752381f6}}, // tzat_, _आइ_, ttay, _zenz,
+ {{0x8bb40b76,0x7c2e068f,0x5d551071,0x7bc281d0}}, // [3fd0] _خصوص, _avbr, лкат, dnou,
+ {{0x3ea92126,0x26058ad5,0x6b9d14ab,0x764d3843}}, // rzat_, _हंसी_, lksg, rtay,
+ {{0x764d4078,0xdce380eb,0x29010135,0x39940106}}, // stay, _kanā, _igha_, _väst_,
+ {{0x6b9d08df,0xed510065,0x9f4c8144,0x2013011b}}, // nksg, _گھر_, _mudó_, _itxi_,
+ {{0xdb1d26d5,0x6b9d01ed,0xf9900fd3,0x6f0d4eac}}, // losó, iksg, _طبی_, mcac,
+ {{0x75288029,0x644a0722,0xa5bb009a,0xca7682e3}}, // jadz, àfic, _dwóc, اموش_,
+ {{0x7523cead,0x65a0011c,0x442c890d,0x6e2d285e}}, // _renz, _köhn, jsd_, lsab,
+ {{0x7523cb67,0x6d451100,0x69d54eae,0x2d914eaf}}, // _senz, lgha, hoze, _hoze_,
+ {{0xaab79299,0x69d54eb0,0x2d9106c0,0x4ab78778}}, // _अधिक, koze, _koze_, _अधिव,
+ {{0x6d451904,0x63b9ceb1,0x4585a57e,0x6e2d40c5}}, // ngha, liwn, угов, isab,
+ {{0x2d9102a5,0x481587eb,0xfbb78c78,0x201301c0}}, // _moze_, _смес, _आगाम, _ntxi_,
+ {{0x7523a0b3,0xe72e8b30,0x2901082e,0x29cd01ac}}, // _wenz, _ће_, _agha_, hľad_,
+ {{0x69c382be,0x539a8158,0x6f1bceb2,0x2005809c}}, // onne, גירו, mbuc, _huli_,
+ {{0x957c8063,0x8c458a42,0x186a0098,0x20059f34}}, // gląd, _беке, наги_, _kuli_,
+ {{0x69c38c64,0x65618073,0xc4be00ab,0xdb1d002a}}, // inne, melh, _অর্জ, gosó,
+ {{0x6f0d4eb3,0x2005ceb4,0x6f1b82af,0xad9b01d6}}, // gcac, _muli_, nbuc, _stúp,
+ {{0x63a44eb5,0x6e2d043b,0x2005ceb6,0x69d54eb7}}, // [3fe0] _ilin, gsab, _luli_, boze,
+ {{0x51660607,0x63a4026f,0x7bc28637,0xc96632b8}}, // _своб, _hlin, wnou, _свой,
+ {{0x6441885c,0x26cb0748,0x200580d7,0xdce700e1}}, // nuli, _exco_, _nuli_, _fajč,
+ {{0xa22a9194,0x69c3ceb8,0x7c2e8e15,0x20183df8}}, // ежда_, enne, ábri, mpri_,
+ {{0xd00f04c0,0x75840077,0x2125826c,0x6441ceb9}}, // _ولی_, _پیام, _belh_, huli,
+ {{0x27e04eba,0x6441b374,0x20058c56,0xe8948364}}, // llin_, kuli, _buli_, иаль,
+ {{0x64418533,0x63a44ebb,0x66060406,0x27e04ebc}}, // juli, _olin, _hukk, olin_,
+ {{0x66060ef6,0x75288063,0x963485c2,0xa0678003}}, // _kukk, wadz, аниц, ѓања_,
+ {{0x2bb18063,0x040d8028,0x244e026f,0x752886cb}}, // jący_, _tưởn, lým_, tadz,
+ {{0x63a44883,0xa3b086b7,0x65618a34,0xdb0f2509}}, // _alin, टोर_, gelh, dicá,
+ {{0x244e0775,0x66063ca7,0x7528809a,0x2005cebd}}, // ným_, _lukk, radz, _guli_,
+ {{0x656e1a29,0xdb0f0c52,0x69d524f2,0x6726007a}}, // _labh, ficá, woze, _nekj,
+ {{0xe7f8097d,0x244e016b,0x69d5011b,0x6606156e}}, // ंगना_, hým_, toze, _nukk,
+ {{0x63a44ebe,0x6441b883,0x645c4ebf,0xf2d38158}}, // _elin, buli, _opri, מער_,
+ {{0x6f0d0362,0x6441bb7c,0x69d54ec0,0xb97b8158}}, // tcac, culi, roze, ענלי,
+ {{0x2a6901c5,0x2d8c8106,0x7ddd83ec,0x3f150071}}, // _tsab_, ljde_, vèsi, рдас,
+ {{0xa3c403b7,0x2d914ec1,0x645c3206,0x2a691341}}, // [3ff0] ्वर_, _poze_, _apri, _usab_,
+ {{0xd8380db7,0x6e2d4ec2,0x656e0cac,0x2d9ec255}}, // šča_, rsab, _cabh, nkte_,
+ {{0x6e2d4ec3,0x2d9100d2,0x2dd800f7,0x6d454ec4}}, // ssab, _voze_, ابقة_, rgha,
+ {{0xae048670,0xb767163f,0x61fe05b0,0x16049094}}, // रदान_, _стой, _zipl, रदार_,
+ {{0x2005856a,0x3ce1009a,0xfdf68105,0x644188bf}}, // _suli_, _करके_, ीदास_, zuli,
+ {{0x656e0ad0,0x2005cec5,0xe2968ae7,0x6f02cec6}}, // _gabh, _puli_, раш_, _agoc,
+ {{0xa0a581e5,0x18a58196,0x6561cec7,0x10a595a1}}, // райд, райм, velh, рийн,
+ {{0xe3b092dc,0xddc9cec8,0x6e3b816b,0x7f759033}}, // _فرق_, dveš, trub, рукц,
+ {{0xdb06020f,0x442301c5,0x6f1b8865,0x64418c56}}, // shkë, _hwj_, rbuc, wuli,
+ {{0x7ae9cec9,0x2005c404,0xdcf88029,0x4423022c}}, // _izet, _tuli_, _novē, _kwj_,
+ {{0x63a44eca,0x656189e9,0x61fe01e0,0x397a80be}}, // _plin, relh, _ripl, _שטענ,
+ {{0xe29a0471,0xddc28110,0x6441cecb,0x765d4ecc}}, // вам_, _grož, ruli, _opsy,
+ {{0x7ff48277,0x44230069,0x6561cecd,0x63a4110f}}, // _اسلا, _lwj_, pelh, _vlin,
+ {{0x64418a14,0x2d9ea9ed,0x1a9a8158,0x463a80be}}, // puli, ckte_, דישע, _יענע,
+ {{0x6606268d,0xa3d597a3,0xd70600b3,0x656e032f}}, // _sukk, सका_, изви, _rabh,
+ {{0x244e0ed7,0x63a44ece,0x656e4ecf,0x20184ed0}}, // vým_, _ulin, _sabh, rpri_,
+
+ {{0x28db0076,0x201806c0,0x27f24ed1,0xb69b0087}}, // [4000] _मुठि, spri_, rmyn_, _sfâr,
+ {{0x637b0009,0x244e026f,0x1a9b03c8,0x7bdbb7a8}}, // _tänä, tým_, _סייע, _akuu,
+ {{0x31570158,0xdd8f190c,0x44230069,0xb4664ed2}}, // _דיין_, بول_, _cwj_, ркел,
+ {{0xa3b0809a,0x244e05b9,0x3d160d14,0x3ce60006}}, // टों_, rým_, _प्रे_, _करीं_,
+ {{0x656e003c,0x2d9e854f,0x98a301a9,0x752708b2}}, // _tabh, ykte_, majā_, _fejz,
+ {{0xfe088028,0x4423022c,0xb33b00f1,0x76440010}}, // _nữa_, _fwj_, thçk, muiy,
+ {{0x97a6802e,0x58868196,0xdb0401ed,0xf1a9803d}}, // ирил, рыла, _clië, دانه_,
+ {{0x44444ed3,0x776407e2,0x6e228247,0x9f420198}}, // mu_, neix, _pwob, _eikö_,
+ {{0x44444ed4,0xfe0880ff,0xfaa3ced5,0xb5fb046d}}, // lu_, _bữa_, _дато, _aráy,
+ {{0x44444ed6,0x44230069,0x3d168072,0x9f970580}}, // ou_, _ywj_, पुढे_, nçò_,
+ {{0xb5fb003e,0x2d9e9254,0xa3c402f7,0x31630bda}}, // _práz, rkte_, ्वं_, vejz_,
+ {{0xa3cc800c,0x44444ed7,0x9ffa81a8,0x69d8b567}}, // रकट_, iu_, _شراء_, love,
+ {{0x77640722,0x6fb381a8,0x7e798114,0x387a0db1}}, // deix, _بمنا, _grwp, _arpr_,
+ {{0x6b5c006a,0x14b789a7,0xa5bb026b,0x1cba81a8}}, // _pågæ, ادیث_, _awón, _لاعب_,
+ {{0xa3d69278,0x351b83de,0x06d88264,0x69d88493}}, // हवा_, _יוגנ, দেশি, iove,
+ {{0x77640722,0x442301c0,0x34fb8039,0x3cfb8039}}, // geix, _rwj_, _בהוד, _בלונ,
+ {{0x44444ed8,0x69d8ced9,0x63bd4eda,0x7c3e1254}}, // [4010] eu_, kove, misn, erpr,
+ {{0x6d489341,0x444438fc,0x7bdbb3b0,0x443ecedb}}, // ngda, fu_, _skuu, ort_,
+ {{0x44441a7b,0x5455316c,0x69d8af06,0x4423022c}}, // gu_, свет, dove, _qwj_,
+ {{0x443e954c,0xb4e71130,0x63bd43d6,0x69c70353}}, // irt_, _परी_, nisn, lnje,
+ {{0x44444edc,0x443e8352,0x4df68d86,0x75272928}}, // au_, hrt_, ँगाई_, _tejz,
+ {{0xdca68307,0x6ab78076,0x32090358,0x69d89b39}}, // _فى_, _अधीर, _muay_, gove,
+ {{0x01380039,0x7d038118,0x260c86a7,0x6d488122}}, // ברות_, _rgns, _डूबी_, dgda,
+ {{0x63bd4411,0xfe08801c,0xfaff0168,0x1bd480e8}}, // jisn, _sữa_, ndës_, _дося,
+ {{0x443ecedd,0x7ae985f5,0x63bd0052,0x3ead80d2}}, // ert_, _uzet, disn, uzet_,
+ {{0xcda983f8,0x69c702a5,0x51848adb,0x4c698256}}, // اهده_, jnje, _муха, гион_,
+ {{0x69c7135a,0x64453f8c,0x672d07b8,0x232a1a66}}, // dnje, nuhi, kaaj, лони_,
+ {{0xe0d08277,0x63bd4ede,0x06b8803d,0x672d3252}}, // _غزل_, gisn, انیک_, jaaj,
+ {{0x443ec9fb,0xed59997b,0x44444edf,0xb5fb002a}}, // art_, _бол_, zu_, _fráx,
+ {{0x44444ee0,0x69c71b39,0x32090122,0x644513fa}}, // yu_, gnje, _duay_, kuhi,
+ {{0x3ce60d38,0x77641209,0x63bd02b1,0x68e9001b}}, // _करें_, teix, bisn, ředn,
+ {{0x81cd80c8,0x81bf80c8,0xaf064286,0x2451826f}}, // রতি_, ীতি_, спал, mám_,
+ {{0x44444ee1,0x7764009f,0x2451816b,0x823612c8}}, // [4020] wu_, reix, lám_, _اربا,
+ {{0xaadb82f1,0x67c181a9,0x69c700fe,0x6b9601b4}}, // _बड़क, mēju, cnje, _hoyg,
+ {{0x6445009c,0x7ae08106,0x69de0870,0x67c181a9}}, // guhi, _symt, _ikpe, lēju,
+ {{0x7f8601a8,0x7e698ca2,0x661b82d5,0x9f5d8032}}, // _الحن, kwep, kpuk, _fiwé_,
+ {{0x44444732,0x6d489c33,0x290582d5,0xca568073}}, // su_, ygda, _bgla_, стењ,
+ {{0x443ecee2,0x20020010,0x63bd026c,0x2451837d}}, // yrt_, _hiki_, zisn, kám_,
+ {{0x24778104,0x69d88b24,0xfaff020f,0x20020365}}, // _năm_, rove, ndër_, _kiki_,
+ {{0x24518e93,0xa6148750,0x69d8cee3,0xfaa6a2ea}}, // dám_, омич, sove, _лабо,
+ {{0x69d89734,0xa3d58540,0x2a60022c,0xddc281a1}}, // pove, सकर_, _npib_, _asoš,
+ {{0x6b8402a3,0x68e1813c,0x7afd826f,0xddc400eb}}, // _anig, _fyld, _ústa, stiņ,
+ {{0x63bd4ee4,0x443ea86b,0x68e18aa2,0x6d5c4ee5}}, // tisn, urt_, _gyld, _mbra,
+ {{0x63a2cee6,0x9f4003d3,0x20024ee7,0x69de008e}}, // nkon, blié_, _niki_, _akpe,
+ {{0x63bd0e78,0x32090028,0x69c701dd,0x5187395b}}, // risn, _quay_, tnje, бува,
+ {{0x6b84241e,0x200200a4,0x63bd35a8,0x24518c83}}, // _enig, _aiki_, sisn, bám_,
+ {{0x63a2c8d2,0x97a72357,0x681180eb,0x63bd4ee8}}, // kkon, брал, _kāda, pisn,
+ {{0x6d5c4ee9,0x02a70765,0xaada0f12,0x200200a4}}, // _abra, _гром, _युवक, _ciki_,
+ {{0x69c70369,0x6d5c008e,0x67c50110,0xdb0d0214}}, // [4030] pnje, _bbra, nėji, _amaç,
+ {{0x7aed00ad,0x64453bdd,0x672d2933,0xec0980ff}}, // _izat, tuhi, saaj, _mến_,
+ {{0x672d0280,0x247780ff,0x6d5c0362,0x5f950012}}, // paaj, _xăm_, _dbra, _динт,
+ {{0x63a2abb9,0x6d5c4eea,0x64454eeb,0x67c50110}}, // gkon, _ebra, ruhi, kėji,
+ {{0xe1f1803d,0x24518019,0xeb908eca,0x64452e7f}}, // وست_, zám_, عظم_, suhi,
+ {{0xe3cf00c8,0x9f420009,0x644526a6,0x67c50084}}, // রতিব, _ehkä_, puhi, dėji,
+ {{0xa3d9000f,0x319c00be,0x299c01c6,0x63a2ceec}}, // ़का_, רבונ, רסומ, bkon,
+ {{0xee398fe7,0x2d98ceed,0x6d5c070d,0x245185b9}}, // ани_, ören_, _zbra, vám_,
+ {{0xa3e2816f,0xbebb06a8,0x63a9ceee,0xe293a0bb}}, // _नीट_, _beëi, _ilen, _عذر_,
+ {{0xdcf500eb,0x29058b67,0x2451826f,0x31348294}}, // _nozī, _ugla_, tám_, _неур,
+ {{0x63a9ceef,0x7aed0314,0x63bb808e,0xdce381a1}}, // _klen, _azat, _kmun, _manć,
+ {{0xd8d6893f,0xf9890bea,0xe8d68158,0x22408253}}, // _וועט_, _בר_, _ווער_, šike_,
+ {{0x20024ef0,0x79970122,0xddcd026c,0x63a9cef1}}, // _riki_, _boxw, zvaš, _mlen,
+ {{0xd3780d38,0x63a9cef2,0x64a581e2,0xdca58ff7}}, // ść_, _llen, _мала, _мали,
+ {{0x929d8063,0x63a28a21,0x20024ef3,0x63a98100}}, // _ogło, ykon, _piki_, _olen,
+ {{0x6b84031d,0xa2d521e3,0xec0980ff,0x9f4680ff}}, // _unig, _युक्, _yến_, _khoá_,
+ {{0x672bcef4,0xe617017a,0x44278355,0x660392a5}}, // [4040] _megj, жду_, _hwn_, _kink,
+ {{0x200217fe,0x672bb8db,0x6603a003,0x63bbcef5}}, // _wiki_, _legj, _jink, _amun,
+ {{0x6603cef6,0x130a9d51,0xb5fb04e8,0x63a9cef7}}, // _mink, аней_, _spác, _blen,
+ {{0x6603cef8,0x6441832f,0x69dc4ef9,0x65a008dc}}, // _link, irli, lore, _möhi,
+ {{0x63a2cefa,0xadeb10c5,0x644181ec,0x442780ee}}, // rkon, _जीवन_, hrli, _lwn_,
+ {{0x63a2cefb,0x660382a3,0x44278039,0x6d5c07c6}}, // skon, _nink, _own_, _ubra,
+ {{0x672bcefc,0x6286905e,0xe5710158,0xbfaa93f1}}, // _begj, _škod, _אַן_, итие_,
+ {{0x2d984efd,0xdfd08013,0x69dc4efe,0x3ebf012b}}, // _hore_, عية_, hore, _žuti_,
+ {{0x69dc0578,0x2d98253d,0x7ae40fd3,0x64418faf}}, // kore, _kore_, _nyit, erli,
+ {{0x929d8063,0x69dc00ab,0xdd9aa482,0x161f1199}}, // _zgło, jore, рши_, मीटर_,
+ {{0x2d984eff,0x7ae4335a,0x6603cf00,0xdb1d02be}}, // _more_, _ayit, _dink, lisé,
+ {{0x6603c751,0x2d980295,0xb4ea8441,0x212c83ed}}, // _eink, _lore_, _मरी_, _hedh_,
+ {{0x6603811b,0xdb1d2538,0x64419384,0xdceb01f4}}, // _fink, nisé, arli, _čiča,
+ {{0xa184027e,0x200c84b9,0xbf9b03a7,0x66038084}}, // мысл, _kudi_, _ciên, _gink,
+ {{0x200c90e1,0x65689eb3,0xa3bc816f,0x69cacf01}}, // _judi_, medh, ेचा_, infe,
+ {{0x64488393,0x31678192,0x6603cf02,0x656880f1}}, // mudi, zenz_, _zink, ledh,
+ {{0x69dc3a2f,0x1ab40364,0x7bdd4f03,0x59bc800c}}, // [4050] bore, _объя, mosu, ्चार,
+ {{0xd90489a7,0x386100b9,0xb5fb0032,0x2d9835db}}, // _لی_, _tphr_, _asár, _core_,
+ {{0x2d984f04,0x63a9b3ce,0xdb008580,0x3319cf05}}, // _dore_, _plen, _almà, біля_,
+ {{0x98a30035,0x656880d7,0xdcee1ee0,0x00000000}}, // gają_, hedh, _gabč, --,
+ {{0x672bcf06,0x7e7d29ed,0x2d980b56,0x65689e29}}, // _regj, _ursp, _fore_, kedh,
+ {{0x2d9802a5,0x6568820f,0x672b8125,0xa9698cae}}, // _gore_, jedh, _segj, сика_,
+ {{0x7e62cf07,0x660381e2,0x6c542344,0x3f99003a}}, // _apop, _rink, _окру, _kosu_,
+ {{0x69dc011f,0x63bbcf08,0x2d9802a5,0x6603cf09}}, // zore, _umun, _zore_, _sink,
+ {{0xdd918bca,0x69dc4f0a,0xdb1d0661,0x65688c53}}, // فوظ_, yore, cisé, fedh,
+ {{0xc05b8cde,0x3f990e80,0x41b5845b,0x6568bd9b}}, // _від_, _losu_, _عمار, gedh,
+ {{0xbf9b0028,0x657505a3,0x6603cf0b,0xb0db016f}}, // _riên, _mazh, _vink, _मुलग,
+ {{0x6603c969,0x7bdd4292,0x320d80b9,0x3f994b1f}}, // _wink, gosu, _kuey_, _nosu_,
+ {{0x6603cf0c,0x213100dd,0x7bcbcf0d,0x6448cf0e}}, // _tink, fazh_, ingu, audi,
+ {{0x200c8a46,0xceb280be,0x7769c963,0x67e58661}}, // _yudi_, _איד_, leex, dóji,
+ {{0x03a6067c,0x69dc4f0f,0x2d87cf10,0xe7f8016f}}, // _нико, rore, _inne_, ंगला_,
+ {{0x3f8604c3,0x4c3a8039,0xdb1d0019,0xf43a8039}}, // ñou_, _התקב, yisé, _התקש,
+ {{0x69dc4573,0x7e6d00e4,0xa3d90035,0x00000000}}, // [4060] pore, gwap, ़कर_, --,
+ {{0xc27b0f60,0xf27b012a,0x67c50084,0x3f8f007b}}, // _פריי, _פריש, dėju, ögur_,
+ {{0x2d98021e,0x399b03c8,0x999b0039,0x319b01c6}}, // _vore_, _הייד, _הביט, _הבינ,
+ {{0x200ccf11,0x7bcb9217,0xdb1d07bc,0x320d8661}}, // _rudi_, gngu, tisé, _buey_,
+ {{0x200c89a6,0x2d983cee,0x6286b1a7,0x98a30035}}, // _sudi_, _tore_, _škob, rają_,
+ {{0xdb1d282c,0x7bcbcf12,0x67244f13,0xceb303de}}, // risé, angu, lbij, _טיר_,
+ {{0x64489613,0x7e62cf14,0xb4eaa0d5,0x7bdd3e2c}}, // xudi, _spop, _मरे_, yosu,
+ {{0x442a02f7,0x27e200ee,0x69ca8115,0x200c8088}}, // _iwb_, _jkkn_, snfe, _vudi_,
+ {{0x7bdd005c,0x442a2f54,0x35f48767,0x6568cf15}}, // vosu, _hwb_, мпир, tedh,
+ {{0x200ca52d,0x64489883,0x4444396a,0x644300eb}}, // _tudi_, tudi, or_, šnie,
+ {{0x92e900c8,0xb0db12c7,0x6568ad68,0xaac0816f}}, // _বলে_, _मुंग, redh, _शेतक,
+ {{0x4444435c,0x2d87cf16,0x6448cf17,0x68e9001b}}, // ir_, _enne_, rudi, ředk,
+ {{0x5187433d,0x444411e6,0x6448cf18,0x3f990087}}, // пува, hr_, sudi, _rosu_,
+ {{0x3f994f19,0x76440f33,0xddcd009a,0x628081a1}}, // _sosu_, driy, ztał, _mrmo,
+ {{0xaf12819d,0x557783c8,0x444415f6,0x971a019d}}, // _fụtụ, _מעגן_, jr_, _ọdụd,
+ {{0x5f940676,0x94738013,0x213100f1,0x6280cf1a}}, // нист, _أدوا, sazh_, _ormo,
+ {{0x63ad4f1b,0x443800e7,0x7bc2cf1c,0x442a4f1d}}, // [4070] _ilan, _avr_, diou, _awb_,
+ {{0x63ad01f6,0x645704b7,0x44444f1e,0x7e6d0cda}}, // _hlan, ttxi, fr_, rwap,
+ {{0x63ad4f1f,0x6575020f,0x442a255d,0xf9920039}}, // _klan, _vazh, _cwb_, _סרט_,
+ {{0x7988cf20,0xff2509d7,0x320581d6,0x61e183ed}}, // _andw, _تبدی, _sily_, _skll,
+ {{0x27e901e4,0xd25ba1d2,0x1ee7004e,0x63ad4f21}}, // mlan_, сце_, _روسی_, _mlan,
+ {{0x27e91a50,0x63ad4f22,0xddcd0063,0x44444f23}}, // llan_, _llan, stał, br_,
+ {{0x442a00fc,0x3205816b,0x7bcbcf24,0x27e94f25}}, // _gwb_, _vily_, pngu, olan_,
+ {{0xe6199b78,0x22408390,0x27e916fb,0x79888365}}, // оди_, šika_, nlan_, _endw,
+ {{0x66070613,0x6d4a1727,0xc2c481a8,0x27e94f26}}, // _kijk, ófag, _كيفي, ilan_,
+ {{0x63ad4f27,0x67240025,0x27e94f28,0xd838007a}}, // _alan, zbij, hlan_, šči_,
+ {{0x442a10af,0x27e94f29,0x69c3a776,0x673d15db}}, // _xwb_, klan_, mine, _odsj,
+ {{0x66070a0f,0xfc3f8efc,0x63ad4f2a,0x547a0039}}, // _lijk, čí_, _clan, קטרו,
+ {{0xeb970656,0x69c38c64,0x5f058048,0x6b8980dd}}, // фия_, oine, _ўзла, _mneg,
+ {{0x63ad4f2b,0xdcee0029,0x76444f2c,0x6b9b8e35}}, // _elan, _labā, vriy, _loug,
+ {{0x63ad04ce,0x661d2c62,0xc0e3217e,0x672400f3}}, // _flan, _atsk, ворк, tbij,
+ {{0x6d4300ab,0xa3e60fcc,0x69c3cf2d,0x76444f2e}}, // ónan, _बीच_, hine, triy,
+ {{0x69c3cf2f,0x444437f5,0x67244f30,0x442a0282}}, // [4080] kine, wr_, rbij, _swb_,
+ {{0x272c0104,0x27e909ca,0x44440a89,0x6ec204e5}}, // ến_, alan_, tr_, _रेणु,
+ {{0x44441a93,0x27e94f31,0xbc6a003d,0x442a01c0}}, // ur_, blan_, تمان_, _qwb_,
+ {{0x44444f32,0x76440385,0x27e901e4,0x29183550}}, // rr_, priy, clan_, lcra_,
+ {{0x7bc2a840,0x6b89808e,0x799a809a,0x27e022b2}}, // riou, _dneg, _potw, moin_,
+ {{0x442a01e9,0x7bc2b13d,0x644305e4,0x6b8993fe}}, // _twb_, siou, ánic, _eneg,
+ {{0xa3de1664,0x660f4f33,0xb4cc8740,0xec16003d}}, // तका_, _zuck, _रखे_, _آورد,
+ {{0x660701ed,0x6d5e09ff,0x27e007c5,0x69c3cf34}}, // _zijk, lfpa, noin_, aine,
+ {{0xaf128135,0x6ac80e70,0xb5fb04e8,0x056aaba7}}, // _bụrụ, रपोर, _opál, ојни_,
+ {{0x27e946b1,0x68e901d0,0xdc3e016b,0x69ce090d}}, // zlan_, ředi, _líče, jnbe,
+ {{0x69dd81a8,0x27e94f35,0x27e0062c,0x98a78493}}, // _ísea, ylan_, koin_, rană_,
+ {{0x69ce36c0,0xe717803d,0x61eacf36,0xf0b780be}}, // enbe, _قيمت_, llfl, _קלאר_,
+ {{0x27e92b61,0x672f01a1,0x5ede02f1,0x63ad01d6}}, // vlan_, _recj, _कुल्_, _vlan,
+ {{0xa4d4835f,0x44214f37,0x63ad4612,0x660f4f38}}, // _полі, yph_, _wlan, _ruck,
+ {{0x2eed0024,0x27e90065,0xdb0b81df,0x660700f3}}, // _šef_, tlan_, tigü, _rijk,
+ {{0x69c3cf39,0x63ad2bf2,0x2fc48609,0x660f4f3a}}, // zine, _ulan, nimg_, _puck,
+ {{0x2458cf3b,0x35f50171,0x69c3cb78,0x3ea4cf3c}}, // [4090] lém_, епар, yine, nymt_,
+ {{0x2d9c809f,0x6b898968,0xdce70035,0x660f0b80}}, // _jove_, _sneg, _zaję, _vuck,
+ {{0x27e901eb,0x645a8364,0x61e514ec,0xb4cc9344}}, // plan_, ntti, _ikhl, _रखो_,
+ {{0x2d9ccf3d,0x660700f3,0x290c8420,0x645a8198}}, // _love_, _wijk, _agda_, itti,
+ {{0x201ea35a,0x661d0b64,0xdcef8267,0x645a890d}}, // _atti_, _utsk, _čeča, htti,
+ {{0x68e88355,0x2458801b,0x69c38362,0x7bc08365}}, // _bydd, kém_, uine, _emmu,
+ {{0x3d1a053e,0xe739a18c,0x64740198,0x201e8580}}, // मुळे_, пей_, _игру, _ctti_,
+ {{0x69c3ae52,0x68e8831d,0xb4ab03b7,0x80be816f}}, // sine, _dydd, गनी_, _वेळे,
+ {{0x92ea00c8,0x645a8656,0x201e8214,0xb9068a0d}}, // _গল্প_, etti, _etti_, _यु_,
+ {{0x68e8831d,0x2fcd831d,0x69c3cf3e,0xdb0b816a}}, // _fydd, sneg_, qine, pigó,
+ {{0x64a58196,0x657881c0,0x69c1cf3f,0xdca59f72}}, // хана, _davh, _imle, хани,
+ {{0xe3b2045a,0x7f848013,0x61e54f40,0xdb04241f}}, // ارد_, _للمن, _akhl, _aliá,
+ {{0x645acf41,0x27e00198,0xda0f81cb,0x3ce60072}}, // atti, voin_, ादित_, _जुने_,
+ {{0xe9df008b,0x2018cf42,0x6d434f43,0x00000000}}, // trúa_, _éric_, ónal, --,
+ {{0xe3b200f7,0x69ce2cfb,0x24588187,0x7bcf01c0}}, // _مرة_, rnbe, cém_, bncu,
+ {{0x2d9c8025,0x201e00eb,0xafe318a2,0xb90802f1}}, // _zove_, īti_, госл, _बड_,
+ {{0x7ae99eb3,0x764d0388,0x7bc606a7,0x0d86249a}}, // [40a0] _nyet, guay, miku, нлан,
+ {{0x7bc62f9e,0x27e002be,0x3f9d826b,0x2d9c81df}}, // liku, soin_, _lowu_, _xove_,
+ {{0x28df901b,0x31570158,0x0b46885f,0xe8990065}}, // _पुलि, _איין_, енен, _ہمیں_,
+ {{0x7ae98c2e,0x7bc64f44,0x68e88b7f,0x224681e0}}, // _byet, niku, _rydd, trok_,
+ {{0x41e686b5,0x2458cf45,0x68e897db,0xdb1d041c}}, // ніка, zém_, _sydd, lisã,
+ {{0xd8380353,0x7ae9cf46,0x645acf47,0xdb1d09b2}}, // šču_, _dyet, ytti, visí,
+ {{0x232a8139,0x7bc630a0,0xab2a813a,0x7ae99896}}, // _нови_, kiku, _нова_, _eyet,
+ {{0x2d9ccf48,0x628425d5,0x2458801b,0x224686a4}}, // _sove_, _orio, vém_, prok_,
+ {{0x7bc61611,0x2d9c807a,0x765b8ce9,0x68e88114}}, // diku, _pove_, gtuy, _wydd,
+ {{0x2458cf49,0x98a78035,0x2ca58366,0x2b400db1}}, // tém_, waną_, fyld_, _adic_,
+ {{0x7bc64f4a,0x62844f4b,0x3a204f4c,0x7792881b}}, // fiku, _ario, _atip_, _میثا,
+ {{0x62840224,0x2458cf3b,0x7bc64f4d,0x645acf4e}}, // _brio, rém_, giku, rtti,
+ {{0x62841c9d,0x27e6cf4f,0x645a8bbd,0x69da01d0}}, // _crio, _ikon_, stti, čten,
+ {{0x2244026f,0xddd98176,0x6578804a,0x049481a8}}, // ámka_, _apwņ, _uavh, _للتح,
+ {{0x62844f50,0x28db12ee,0x7bc61f34,0x2fc202f7}}, // _erio, _मुखि, biku, _bmkg_,
+ {{0x62844f51,0x8d7700d7,0x17f801a8,0xddc405b2}}, // _frio, کارا, فرقة_, ttiş,
+ {{0x62844f52,0x6b8d0079,0xe61300f7,0x78a10106}}, // [40b0] _grio, _inag, اشر_, älvb,
+ {{0x64489412,0x7c3acf53,0x201e023e,0xddc98b80}}, // ordi, ostr, ític_, zvež,
+ {{0x69c70668,0xdb1d03a7,0x7ae99c46,0x776d0118}}, // mije, cisã, _syet, peax,
+ {{0x69c7111b,0x7ae9820f,0x53e1016f,0x7c3aa8c6}}, // lije, _pyet, नविश, istr,
+ {{0xbf360051,0xdb0f07a3,0xfe088028,0x853c8084}}, // _בארץ_, licó, _hữu_, rkėj,
+ {{0x69c70025,0x7c3acf54,0x27e69c33,0x64488699}}, // nije, kstr, _akon_, krdi,
+ {{0x7c3acf55,0xa3e1052a,0x7ae982e6,0xbddb0176}}, // jstr, थवा_, _wyet, _avèg,
+ {{0x7c3a9412,0x7c251106,0xa5bb0061,0xc6240264}}, // dstr, mphr, _utób, _পিতা_,
+ {{0x200b002e,0x7bc64f56,0x2a690282,0x69c72bd7}}, // _mici_, viku, _npab_, kije,
+ {{0x0d829663,0x6b8d4f57,0x200b0289,0x69c18c54}}, // альн, _anag, _lici_, _umle,
+ {{0x69c72368,0x7c3aaa33,0x40358790,0x6448817f}}, // dije, gstr, _реес, grdi,
+ {{0x200b0012,0xdb1d0073,0x62844f58,0x61e3b6b2}}, // _nici_, visã, _prio, lonl,
+ {{0x7bc60d7a,0x27ffcf59,0x64489384,0xe29716fe}}, // riku, mmun_, ardi, вая_,
+ {{0x27edbd2a,0x200b29de,0xa06a1597,0x69c70503}}, // llen_, _aici_, мага_, gije,
+ {{0xddd6003e,0x200b3340,0x29cd0858,0x6e3ba320}}, // jvyš, _bici_, džad_, lsub,
+ {{0x62844f5a,0x6f1b810b,0x4425cf5b,0x6a86002e}}, // _trio, ncuc, mpl_, _алиа,
+ {{0x69c7003a,0x200b0b9a,0x27edcf5c,0x62841041}}, // [40c0] bije, _dici_, ilen_, _urio,
+ {{0x27ed8352,0x69c7003a,0x75d300f7,0xca370039}}, // hlen_, cije, ليقا, _שניה_,
+ {{0x27ed813c,0x27ff80dd,0x61e3cf5d,0x44258176}}, // klen_, kmun_, donl, npl_,
+ {{0x61ee4f5e,0x27edcf5f,0xdb0f023e,0x8fa63c3d}}, // llbl, jlen_, licò, _раме,
+ {{0x7c3acf60,0x63a984b7,0x86432c3b,0x27edcf61}}, // zstr, _ċent, _княж, dlen_,
+ {{0x27edcf62,0x61e3c000,0x7c3aaaf9,0x415380e8}}, // elen_, gonl, ystr, ивіс,
+ {{0x2eca83b6,0x660a86cb,0xddc98035,0x27edcf63}}, // ाप्त, _rifk, steś, flen_,
+ {{0x6448805c,0x27edc235,0x6729a5b3,0x65ad8144}}, // vrdi, glen_, gbej, _búho,
+ {{0xb8e988fd,0x6e3b8c27,0x657c4f64,0x245c016b}}, // _ले_, gsub, _karh, lím_,
+ {{0x6b8d0025,0x7c3a8d35,0x657e015d,0x64488353}}, // _snag, tstr, ndph, trdi,
+ {{0x245c0a56,0x69c70582,0x63a0cf65,0xaefb026b}}, // ním_, vije, _komn, _abùl,
+ {{0xa3d5a9b7,0xd5b08077,0xd3a71541,0xa73a8f24}}, // िचय_, _گفت_, троп, _آثار_,
+ {{0xfe728bca,0xec0a0104,0x7af60063,0xf77304c0}}, // _خدا_, _nếu_, _czyt, یار_,
+ {{0x63a0cf02,0x7c3acf66,0x7bc405ee,0x6e2d9238}}, // _lomn, pstr, _emiu, _çaba,
+ {{0x69c7003a,0x6286bdbb,0x245c026f,0x999980e1}}, // rije, _škol, jím_, _ísť_,
+ {{0x245c026f,0x657c013c,0x02a70705,0x63a081f6}}, // dím_, _aarh, _аром, _nomn,
+ {{0x291e80f2,0x69c71351,0x62828a0f,0x61e3861c}}, // [40d0] _ofta_, pije, jvoo, yonl,
+ {{0x67298088,0x68ed801b,0xa5bb0032,0xdc4281d0}}, // zbej, řadi, _awór, _léče,
+ {{0x657c2054,0x63a0cf67,0x61e3862c,0x62829151}}, // _darh, _bomn, vonl, evoo,
+ {{0x27ff077f,0x6e22cf68,0x7a3881bc,0x764982c4}}, // _òun_, _otob, _ụtar, yrey,
+ {{0x63a091ec,0x657c0d8b,0xd945b93f,0x27ed9277}}, // _domn, _farh, гели, vlen_,
+ {{0xec0a0028,0x487994ed,0x245c016b,0x7f428661}}, // _hết_, есия_, bím_, _adoq,
+ {{0xec0a0104,0x61e386c7,0x27ed8065,0x245c027f}}, // _kết_, ronl, tlen_, cím_,
+ {{0x61e38019,0xec0a001c,0x869980a9,0x291ecf69}}, // sonl, _yếu_, етот_, _efta_,
+ {{0x67298022,0x7aed0397,0x3ea92380,0x27edcf6a}}, // rbej, _nyat, nyat_, rlen_,
+ {{0x6e3bcf6b,0x27edcf6c,0x6f1b8144,0x764981b4}}, // rsub, slen_, scuc, rrey,
+ {{0x7535139a,0x6e3bcf6d,0x7aed3efd,0xc10481a8}}, // _mezz, ssub, _ayat, _نوكي,
+ {{0xc209073a,0xddcd003a,0x3ea90455,0x7aed1b19}}, // _זה_, jvaž, kyat_, _byat,
+ {{0xdcf40279,0x29cd08ae,0x7aed00b4,0x290a8118}}, // _čača, džab_, _cyat, _ºbac_,
+ {{0x06e580ab,0x9d55815b,0x3ea94f6e,0x99d600f7}}, // _পৃথি, _صنعت, dyat_, اتحا,
+ {{0x18674f6f,0x64434f70,0xa3e38bb8,0xa0674a52}}, // лари_, ánil, नका_, лара_,
+ {{0x245c001b,0x645c8722,0x1fb50087,0xaefb026b}}, // vím_, àrie, асур, _ibùj,
+ {{0xa6860084,0x657c136e,0x3ea94f71,0x93fb007c}}, // [40e0] ылад, _parh, gyat_, _אלקי,
+ {{0x245c03cb,0x63a0881a,0x3ea08014,0xaf030087}}, // tím_, _somn, _àite_, спул,
+ {{0xe73a02ac,0x80a880c8,0xa0a616d9,0x63a089b3}}, // _пен_, _কেন্, _санд, _pomn,
+ {{0x65ad81ac,0x644a023e,0x245c4f72,0x99990084}}, // _súhl, àfiq, rím_, busų_,
+ {{0x245c0775,0x4423020f,0x657c0a6e,0xa29485e9}}, // sím_, _etj_, _tarh, ралі,
+ {{0x62828b3c,0xfce6117f,0x91e38198,0x3f8f8234}}, // rvoo, _бого, боче, _angu_,
+ {{0x98a70e78,0x628280bd,0xf1a9803d,0x6e22cf73}}, // čića_, svoo, خانه_, _stob,
+ {{0x6d43cf74,0x7c3e209b,0x3f804f75,0x753505ee}}, // _adna, mspr, ndiu_, _zezz,
+ {{0xb5fb178e,0x7c3e2a33,0x2487802a,0x36d4983a}}, // _spái, lspr, _arnm_, ропр,
+ {{0x3f8fcf76,0x7aed0314,0x660e0372,0xd90ecf77}}, // _engu_, _ryat, _jibk, _عیب_,
+ {{0x7aed07d5,0xd7efb314,0x6d4301a8,0x628f96f0}}, // _syat, _гу_, ónai, _àcor,
+ {{0x3ea90086,0xce358061,0x2fc9022c,0x317e9fa4}}, // yyat_, _چونک, xiag_, _hatz_,
+ {{0x65949401,0xdcf500eb,0x3a2480b9,0x2fc68122}}, // _валу, _mazā, _jtmp_, _imog_,
+ {{0x7aed08b1,0xa5bb4f78,0x7c3e0687,0x7002819d}}, // _vyat, _atón, kspr, _ọcha,
+ {{0xb76701e5,0xd48f88b0,0x351b83c8,0x3ea94f79}}, // лтай, _ур_, _אוונ, wyat_,
+ {{0x443ecf7a,0x7c3e04fe,0x753a811e,0x64434f7b}}, // mst_, dspr, latz, ánim,
+ {{0x75350098,0x260d0279,0x69cacf7c,0xe81b85fc}}, // [40f0] _pezz, _džon_, kife, _पूना_,
+ {{0x753a8102,0x44234f7d,0x443ecf7e,0x64430796}}, // natz, _ptj_, ost_, šnim,
+ {{0x443e8558,0x69cacf7f,0xec0a001c,0x3ea90359}}, // nst_, dife, _tết_, syat_,
+ {{0x443e8ae2,0x753aafa9,0xdb0086c0,0x63af04e8}}, // ist_, hatz, _komè, nkcn,
+ {{0x753a811e,0x672d4f80,0x2d8503a7,0x386c8118}}, // katz, mbaj, _óleo_, _apdr_,
+ {{0x644a1313,0x2169b0bc,0x443ecf81,0x60258c07}}, // áfic, тики_, kst_, адка,
+ {{0x443ecf82,0xdb1d0125,0x753a8102,0xf77f0214}}, // jst_, nnsó, datz, _kaç_,
+ {{0x07a5cf83,0xc1d20e00,0xf77f0580,0x443e9ed3}}, // раин, _सद्ग, _jaç_, dst_,
+ {{0xee368b71,0x764d4f84,0xdcf88353,0x9f5e8019}}, // аны_, nray, _davč, _autó_,
+ {{0x27e08805,0x7bcbcf85,0x27338028,0x443ecf86}}, // čina_, ligu, ản_, fst_,
+ {{0x443e888a,0xab2a134e,0x764d4e07,0x237f8282}}, // gst_, кона_, hray, _kauj_,
+ {{0x9991012b,0x7bcbcf87,0xb4d5998e,0x2d98816d}}, // _čašu_, nigu, _सखी_, örer_,
+ {{0x2d910586,0x237f8282,0xdb00cf88,0xa2c0001b}}, // _onze_, _mauj_, _comè, _लेख्,
+ {{0x764d002a,0x7bcb833e,0xdb00c649,0x237f8069}}, // dray, higu, _domè, _lauj_,
+ {{0x64430a20,0x52d1816f,0xaca40133,0x77640197}}, // šnij, तपुस, _kwụr, tfix,
+ {{0x673b8110,0x69cacf89,0x788081d0,0x3f800162}}, // nauj, zife, lává, udiu_,
+ {{0x3f802590,0x22404f8a,0xd007163e,0x77b80118}}, // [4100] rdiu_, _uvik_, рете_, _níxe,
+ {{0x7c3e0223,0x9d461194,0x77643803,0x644e802a}}, // tspr, режд, sfix, ábig,
+ {{0x6289cf8b,0x63a44f8c,0x14c8003d,0x98a7801b}}, // _areo, _koin, دهای_, raně_,
+ {{0x7c3e105d,0x764d4f8d,0x753a811e,0x63a44f8e}}, // rspr, bray, zatz, _join,
+ {{0x63a403d3,0x6289cf8f,0x63b64f90,0x02068009}}, // _moin, _creo, _mlyn, аздн,
+ {{0x443eaa4f,0x63a42ad0,0x77b8002a,0x7c3e20f0}}, // yst_, _loin, _díxe, pspr,
+ {{0x64a6130f,0xb9c38013,0x7b96835f,0x8c434f91}}, // _тава, تقوي, _краї, _мере,
+ {{0x22498052,0x237f81c0,0x9634a2b7,0x27f24f92}}, // šaka_, _gauj_, бниц, nlyn_,
+ {{0x753a80d3,0xa3e397ba,0x62338073,0x443e8428}}, // tatz, नकर_, _меѓу, wst_,
+ {{0xcd069895,0xdb00cf93,0x443e8a0f,0x550691c7}}, // ични, _nomé, tst_, ична,
+ {{0x753a80ad,0xdd9781e2,0x443ecf94,0x200f86c0}}, // ratz, ршы_, ust_, _figi_,
+ {{0x63a43c8d,0x443ecf95,0x753a8b90,0x200fc9c6}}, // _coin, rst_, satz, _gigi_,
+ {{0x443e9722,0x753a8102,0xc6769459,0x63a44f96}}, // sst_, patz, _خطاب, _doin,
+ {{0x463b8158,0x261a000f,0xdb00a72b,0xf77f0457}}, // _יעדע, _बढ़ी_, _comé, _saç_,
+ {{0xdb0084e8,0x63a400f7,0x63b60114,0xdcf50162}}, // _domé, _foin, _flyn, _mază,
+ {{0x63a40039,0x64431809,0x6d5c212b,0x57b412b2}}, // _goin, šnik, _acra, обст,
+ {{0x6b828144,0x2cac831d,0xdb008118,0x237fcf97}}, // [4110] ddog, lydd_, _fomé, _rauj_,
+ {{0xeb9f0bc5,0x717512c5,0x764d4f98,0x2d9e8168}}, // ljø_, _اهدا, rray, njte_,
+ {{0x2ca7002e,0x2cac831d,0x237f8282,0x6fb500d7}}, // ând_, nydd_, _pauj_, _همدا,
+ {{0x77b83b56,0x96c20540,0x6d5c05f7,0x764d09c4}}, // _píxe, _रेकॉ, _ecra, pray,
+ {{0x6b80cf99,0x59bb09a9,0x673b8110,0x60db007e}}, // _damg, _उतार, vauj, ğumu,
+ {{0xc69200be,0x2cac816d,0x75388255,0x27e94f9a}}, // _זאך_, kydd_, _nevz, loan_,
+ {{0x7bcb8077,0x673b80eb,0x237f822c,0x7880928a}}, // pigu, tauj, _tauj_, vává,
+ {{0x27e94f9b,0x62898013,0x2cac8114,0x63ad82f1}}, // noan_, _treo, dydd_, öand,
+ {{0x63a4061f,0xddc2807a,0xe3b080f7,0x673b8612}}, // _roin, _spoš, _غرف_, rauj,
+ {{0x63a44f9c,0x2cac8114,0x7afbcf9d,0x27e94f9e}}, // _soin, fydd_, _izut, hoan_,
+ {{0x63b6003e,0x63a40806,0x27e9011e,0xec0a001c}}, // _plyn, _poin, koan_, _bếp_,
+ {{0xe29a2b3f,0x5f779a37,0xdb00802a,0x645c823e}}, // гам_, _خاطر, _somé, ària,
+ {{0x6d471502,0x63a40a26,0x7981802f,0x27e9011b}}, // _odja, _voin, _kalw, doan_,
+ {{0xc6928039,0x6d470326,0x6fde81a9,0x2fcd800b}}, // _מאד_, _ndja, rīce, lieg_,
+ {{0x63a40009,0xb4e61344,0x3ead8019,0xddc2807a}}, // _toin, _पडे_, lyet_, _upoš,
+ {{0x27e94f9f,0x79818247,0x4427cfa0,0x6441aa4c}}, // goan_, _lalw, _itn_, msli,
+ {{0x244393da,0x3ead8642,0x442781c0,0x8fa30991}}, // [4120] lım_, nyet_, _htn_, чате,
+ {{0x6b80cfa1,0x8b08009a,0x7bc9a7d4,0x27f24fa2}}, // _samg, _cięż, _ameu, slyn_,
+ {{0x244382bb,0x6441cfa3,0xb4b212e0,0x64a000ce}}, // nım_, nsli, टने_, _ošiš,
+ {{0x69ce4fa4,0xec0a0028,0x80c100ab,0x4427cfa5}}, // libe, _xếp_, _শুক্, _mtn_,
+ {{0x7981bd16,0xdb040362,0x2443a2f8,0xab658ec3}}, // _balw, _pliù, hım_, daļā,
+ {{0x09e398a0,0x64419247,0x24438214,0x64439fd6}}, // почн, ksli, kım_, _ovni,
+ {{0xe4da1125,0x588681bb,0x3f8241eb,0x7e64160c}}, // _صورت_, сыла, _kaku_, ltip,
+ {{0x244382bb,0x27e08353,0xf0930039,0x6441a2ba}}, // dım_, čino_, לנו_, dsli,
+ {{0x2cac8355,0x3f824fa6,0xa3d5aa47,0x26c04fa7}}, // wydd_, _maku_, िचर_, nzio_,
+ {{0x3f820886,0x4427800e,0x27e9011b,0x7981b807}}, // _laku_, _btn_, zoan_, _galw,
+ {{0x6e2281bc,0x539900e8,0x644184dc,0x69ce0f3e}}, // _dịọk, авня_, gsli, dibe,
+ {{0x5f768875,0x3f824fa8,0x61e1223a,0x2cac831d}}, // _دائر, _naku_, पक्ष, rydd_,
+ {{0x7981b919,0x644181b0,0x6443913b,0xcb033c4f}}, // _yalw, asli, _evni, रेंड_,
+ {{0x65948c9d,0x3d9482df,0x8b08001b,0x27e909c4}}, // _факу, _фикр, _stří, woan_,
+ {{0x3f824fa9,0x27e94faa,0x442692c5,0x201a3f19}}, // _baku_, toan_, _ارتف, _cupi_,
+ {{0x1fa4067c,0xfd5901bc,0x7e7d00b9,0x9f5d8176}}, // пруг, _febụ, _pssp, _chwè_,
+ {{0x680a8063,0x3f82036e,0xdcfc01ac,0x7e6413d2}}, // [4130] _będz, _daku_, _darč, gtip,
+ {{0x6d5acfab,0x7bcf4e74,0x69ce0511,0xbddb4fac}}, // ngta, licu, cibe, _evèn,
+ {{0x4427038e,0xa3ad0beb,0x6ad1824c,0xb5fb4fad}}, // ën_, _गवा_, तप्र, _spás,
+ {{0x7bcf4fae,0x2d9c0106,0x7981cfaf,0x8264003d}}, // nicu, över_, _salw, _بهین,
+ {{0xe299917e,0x2443817b,0x69d8807b,0x3cea86a7}}, // рал_, zım_, fnve, _घुसे_,
+ {{0xa2cd853e,0x7bcf4fb0,0x244383bf,0x3f8204b9}}, // _देण्, hicu, yım_, _zaku_,
+ {{0x6e2d157a,0xe1f09a37,0x2fcd8192,0x7e62912e}}, // lpab, رسه_, tieg_, _aqop,
+ {{0x6d5ac78d,0x69ce4fb1,0x2d830423,0xce49a3e7}}, // egta, zibe, _haje_, азие_,
+ {{0xe6169006,0x69ce4fb2,0x6e2d0144,0x6286cfb3}}, // оды_, yibe, npab, _škov,
+ {{0x644183a6,0x2d83011f,0x244383bf,0x442790e4}}, // tsli, _jaje_, tım_, _ptn_,
+ {{0x87e78153,0x3a290763,0xd469c64f,0x628d1b1d}}, // _люде, _atap_, шине_, _arao,
+ {{0x244382bb,0x201a4fb4,0x7c2d8da8,0xe6958872}}, // rım_, _rupi_, _čard, _ولاد,
+ {{0x29cd05f5,0x628d0083,0x24438214,0x6441cfb5}}, // ržan_, _crao, sım_, ssli,
+ {{0x63b880eb,0x6441a651,0x628d0219,0x2d832e5a}}, // īvni, psli, _drao, _naje_,
+ {{0x3a294fb6,0x7bcf023b,0x673f0168,0xdcfc4fb7}}, // _etap_, bicu, faqj, _parč,
+ {{0xa3be073c,0x25fd8076,0x69ce4fb8,0xa6db007b}}, // _इति_, _रीती_, sibe, _meðf,
+ {{0xf772812a,0x628d4fb9,0x2d834fba,0xdcfc007a}}, // [4140] וקן_, _grao, _baje_, _varč,
+ {{0x6b840074,0x64570b6e,0xdd9207d2,0x26c0047f}}, // _haig, fuxi, بوط_, rzio_,
+ {{0x07a61056,0x7e641ffe,0x6b9601e2,0x649a0aac}}, // _данн, stip, _knyg, штар_,
+ {{0x2ca7016d,0x77b8002a,0xcb6a8af0,0x6d43008b}}, // ända_, _fíxa, раде_, ónar,
+ {{0x7e0301bc,0x00000000,0x00000000,0x00000000}}, // ọsar, --, --, --,
+ {{0x6b84032f,0x2d8308f2,0x7bcf4fbb,0x236d8609}}, // _laig, _gaje_, zicu, _ebej_,
+ {{0x2d85cfbc,0x29cd026c,0x628f83a8,0xdb1d0106}}, // ddle_, džal_, _ácon, mnsö,
+ {{0x44444fbd,0xca7696fe,0x753c43cc,0x6b844fbe}}, // ms_, _музы, _herz, _naig,
+ {{0x76444fbf,0x7bcf09c4,0xdb0d4fc0,0xdb00816b}}, // nsiy, vicu, _ilaç, _komí,
+ {{0x63a2a3d0,0x78a100f2,0x3f829c18,0x442a008e}}, // ljon, älvk, žku_, _ktb_,
+ {{0x6b841247,0x3a2900c9,0x753c4fc1,0x3d058075}}, // _baig, _stap_, _merz, हेले_,
+ {{0x76444fc2,0x63a2cbf2,0x6b844fc3,0x628d4fc4}}, // ksiy, njon, _caig, _prao,
+ {{0x7bcf44c6,0xaac54fc5,0x6b844fc6,0x9f49009f}}, // ricu, _लेखक, _daig, llaç_,
+ {{0x44444fc7,0x29cd0052,0x7bcf0098,0x442c8122}}, // ks_, ržao_, sicu, ppd_,
+ {{0x444448fc,0x6b840ad0,0x97a73d65,0x2d834fc8}}, // js_, _faig, орал, _raje_,
+ {{0x62808503,0x09b78519,0x628d1995,0x2d832d68}}, // _osmo, _अत्य, _trao, _saje_,
+ {{0x44444fc9,0x753c4014,0x44380114,0xa6db007b}}, // [4150] es_, _berz, _awr_, _meðg,
+ {{0x44444fca,0xc05b0a4c,0x6724044a,0xdb008825}}, // fs_, _кім_, acij, _comí,
+ {{0x44440a85,0x753c01ec,0x62808102,0xdb00a9c3}}, // gs_, _derz, _asmo, _domí,
+ {{0x207a0158,0x987a0158,0x2d834c0e,0x307a00be}}, // _פארא, _פארט, _waje_, _פארנ,
+ {{0x44444fcb,0x79850ad4,0xbf9a04de,0x442a011b}}, // as_, _kahw, _הירש, _etb_,
+ {{0xe7f90076,0x44444fcc,0x20094fcd,0xd83886c4}}, // ंतवा_, bs_, lmai_, _bwč_,
+ {{0x44444fce,0x6286842b,0x661d0b40,0xc5f20039}}, // cs_, _škot, _husk, _נדל_,
+ {{0xb4d71a3b,0x6615011e,0x63a2809a,0x661d4fcf}}, // ापी_, _hizk, cjon, _kusk,
+ {{0xe81b8fcc,0x63bbc82e,0xd14b87d2,0x661d4fd0}}, // _पूरा_, _ilun, _نشان_, _jusk,
+ {{0x6b840219,0x6b9600f2,0x7bcd4fd1,0x63a9867f}}, // _saig, _snyg, _amau, _hoen,
+ {{0x6b840074,0x752501ec,0x98b800eb,0x20090019}}, // _paig, ichz, karā_, kmai_,
+ {{0x2d8384c4,0x64454fd2,0x63a98198,0x764401b4}}, // žje_, ishi, _joen, ysiy,
+ {{0xed4e891c,0x7985035a,0x63a981b0,0x64450723}}, // _по_, _bahw, _moen, hshi,
+ {{0x63a284b7,0x7c29001b,0x63bbcfd3,0x764403bf}}, // zjon, _čern, _llun, vsiy,
+ {{0x6d41adaf,0x63bbcfd4,0x6b844fd5,0x63a28118}}, // mala, _olun, _taig, yjon,
+ {{0x6d41cfd6,0x76444fd7,0x444403ab,0x20093ff5}}, // lala, tsiy, vs_, gmai_,
+ {{0x44444fd8,0x6615011e,0xe7370009,0x442a374e}}, // [4160] ws_, _bizk, яет_, _stb_,
+ {{0x6d41843c,0x63bbcfd9,0x76444fda,0x443848e7}}, // nala, _alun, rsiy, _pwr_,
+ {{0x44444fdb,0x661d0379,0x753c4fdc,0x76440201}}, // us_, _eusk, _verz, ssiy,
+ {{0xdb008012,0x6d41cfdd,0x161b823c,0x661d00a4}}, // _româ, hala, नदार_, _fusk,
+ {{0x44444fde,0x753c4fdf,0x27ed874c,0x63a9a41f}}, // ss_, _terz, doen_, _doen,
+ {{0x63a2cfe0,0x6d418763,0x61ee473a,0x63bb80fa}}, // sjon, jala, nobl, _elun,
+ {{0x6d41cfe1,0x2d980106,0x44444fe2,0xa5bb026b}}, // dala, _inre_, qs_, _atów,
+ {{0x27ed811e,0xd6d9809a,0x7ae40118,0x6d5e0074}}, // goen_, wała_, _oxit, lgpa,
+ {{0x9f400019,0x7ae40168,0xa5bb0091,0x61ee27d1}}, // llió_, _nxit, _awóy, kobl,
+ {{0x6d5e4fe3,0xfe6f8829,0x61ee001b,0xf1b980fe}}, // ngpa, ادي_, jobl, _puše_,
+ {{0x7ae403a8,0x443889c4,0x3dbe00ab,0xb4b68105}}, // _axit, èr_, _আদাল, छने_,
+ {{0x79851a7b,0x9f5d8032,0x78fb81c6,0xd6d9866f}}, // _sahw, _diwó_, _פלאפ, sała_,
+ {{0x6d41cfe4,0xa3be035a,0xe61f00ff,0x59b0864a}}, // bala, _इतर_, _trôi_, _जकार,
+ {{0x201ecfe5,0x291a00ee,0x64454fe6,0xbf9b4c43}}, // _kuti_, _cgpa_, yshi, _chên,
+ {{0x661d4fe7,0x7ae439bd,0x213e9c33,0x66151142}}, // _susk, _exit, _meth_, _rizk,
+ {{0x657acfe8,0x201ecfe9,0x213e8014,0x77b803a8}}, // leth, _muti_, _leth_, _díxo,
+ {{0x645a8218,0x25aa031d,0x64454fea,0xaefb026b}}, // [4170] luti, _bobl_, wshi, _abùr,
+ {{0xd9048288,0x657a8234,0x64450234,0x77b801df}}, // _می_, neth, tshi, _fíxo,
+ {{0xdcfc0029,0x645acfeb,0x63a9b807,0x63bbcfec}}, // _parā, nuti, _poen, _plun,
+ {{0x6d41cfed,0x657a805d,0x661d4fee,0x7bdd4fef}}, // zala, heth, _tusk, nnsu,
+ {{0x6d41cff0,0x213e831d,0x6286cff1,0x201e9bcf}}, // yala, _beth_, _škor, _auti_,
+ {{0x63a985f8,0x64434ff2,0x645a9473,0x29cd0267}}, // _woen, ánis, kuti, džak_,
+ {{0x27edca85,0x6d4190ed,0x201e8239,0x63a9837a}}, // toen_, vala, _cuti_, _toen,
+ {{0x6d41cff3,0x645acff4,0x201ece8a,0x224909d1}}, // wala, duti, _duti_, _ovak_,
+ {{0x6d41cff5,0x27edca85,0x3867813c,0x3f869210}}, // tala, roen_, stnr_, _daou_,
+ {{0xe2908bbe,0x27edcff6,0x645acff7,0x657acff8}}, // _حذف_, soen_, futi, geth,
+ {{0x645a8e01,0x7af60009,0x9f4b01d0,0xdb008174}}, // guti, _syyt, _akcí_, _iomá,
+ {{0x6d419397,0x333f83d3,0xd6ce80d5,0x60158110}}, // sala, _jeux_, اقی_, _išmo,
+ {{0x6d418e01,0x61ee4ff9,0x3f89031d,0xe1ff0333}}, // pala, tobl, ddau_, rtón_,
+ {{0x6d41b7fc,0xe1ff0511,0xe61f00e1,0x321f8144}}, // qala, stón_, _osôb_, _muuy_,
+ {{0xc7b8801c,0xdb008118,0x645aac04,0x7e69a741}}, // _ttđt_, _momá, cuti, ltep,
+ {{0xee3a4f05,0x3e6602be,0x9b46003d,0x7af60198}}, // жна_, tôt_, _کنکو, _tyyt,
+ {{0x7dc01f0f,0x2d87cffa,0x25aa031d,0xa2cd8d86}}, // [4180] _löse, _kane_, _pobl_, _देल्,
+ {{0x3f890355,0xfd12045a,0x27e08805,0x62840870}}, // adau_, نجا_, čini_, _isio,
+ {{0x2d87cffb,0x93bd002e,0x3a2d86cb,0xdb1986c0}}, // _mane_, mpăr, _atep_, viwò,
+ {{0x333f82be,0x3e6600e7,0xf6260195,0x201ea637}}, // _ceux_, pôt_, _одмо, _ruti_,
+ {{0x333f83d3,0x657acffc,0x25aa026c,0x213e8114}}, // _deux_, yeth, _tobl_, _peth_,
+ {{0x201e8c27,0x2d87cb46,0x082a9508,0x3a2000b9}}, // _puti_, _nane_, оции_, _muip_,
+ {{0xdb00827f,0xab2a8d13,0x232a80e8,0x333f8036}}, // _domá, _мова_, _мови_, _feux_,
+ {{0x645abe18,0xf76f8124,0x657ab9f8,0xdb008722}}, // vuti, لاً_, weth, _tomà,
+ {{0x8e149b53,0x6e220013,0x2d87cffd,0xb4b6800d}}, // ндиц, íobh, _bane_, छन्_,
+ {{0x7bc29de6,0x2d87cffe,0x645aa4c0,0x65ad81d6}}, // lhou, _cane_, tuti, _súhr,
+ {{0xd7ef8193,0x62840009,0x333f80e7,0x657aceac}}, // _ау_, _asio, _yeux_, reth,
+ {{0x645acfff,0x7bc283b2,0xdb042509,0x657a8234}}, // ruti, nhou, _aliñ, seth,
+ {{0x6d4e0025,0xc33384de,0x7d1c02d4,0x6aa28118}}, // _odba, כור_, _ogrs, nxof,
+ {{0x79889142,0x2d8784b9,0x7d0304e8,0xdb1d03ca}}, // _jadw, _gane_, ýnsk, lisü,
+ {{0x798884b7,0x291880e1,0x7bc29699,0x2006d000}}, // _madw, _úrad_, khou, _khoi_,
+ {{0x80a00540,0x2d87d001,0x98a78503,0x7bc281ed}}, // _ऑपरे, _zane_, žiću_, jhou,
+ {{0x799aa40d,0x28cf000d,0x443c8282,0x63ad0087}}, // [4190] _ontw, _हेरि, _hwv_, _ioan,
+ {{0x8d7707bd,0x63ad001c,0x443c81c5,0x7bc2b32d}}, // بارا, _hoan, _kwv_, ehou,
+ {{0x333f82be,0x6005a868,0xdb00d002,0x2d8700e1}}, // _peux_, lóme, _romá, ľne_,
+ {{0x799a9ab0,0x63ad19fd,0x2d87378d,0x69d5079a}}, // _antw, _joan, žne_, lize,
+ {{0x63ad0bb1,0x600585e4,0x333f82be,0xdb0085b9}}, // _moan, nóme, _veux_, _pomá,
+ {{0x69d55003,0x9f40007b,0xd6d98035,0xf1bf5004}}, // nize, llið_, wało_, mpás_,
+ {{0x6d454378,0x79888079,0xc7b88115,0x6f028db1}}, // maha, _dadw, _suđa_, _azoc,
+ {{0xde888104,0x6d455005,0x7bc2a1bf,0x799abee1}}, // _bị_, laha, chou, _entw,
+ {{0x20190b20,0x2d87d006,0xdb0084e8,0x3eb99eca}}, // _misi_, _pane_, _tomá, ást_,
+ {{0x77b59313,0xde88b5b0,0x6d451efa,0x77bc862f}}, // _máxi, _dị_, naha, _véxa,
+ {{0x69c38083,0xf1b98bda,0x6f028caa,0xc5f204de}}, // mhne, _guša_, _ezoc, רדי_,
+ {{0x6d455007,0xceb40201,0x2d8784b9,0xc7b8811f}}, // haha, yyə_, _wane_, _tuđa_,
+ {{0x6d450ec9,0x63ad5008,0xde88b5b0,0x2d878315}}, // kaha, _doan, _gị_, _tane_,
+ {{0x20195009,0x443c8282,0x69d5130e,0xbed680be}}, // _aisi_, _fwv_, gize, _אונז_,
+ {{0x6d45500a,0x7dc002af,0xc0e30a42,0x201920af}}, // daha, _lösc, горк, _bisi_,
+ {{0x67299d24,0x66188bcf,0x200db686,0x93bd0162}}, // ncej, _zivk, nmei_, spăr,
+ {{0x6d45500b,0x7649d00c,0x6284004f,0x60058144}}, // [41a0] faha, nsey, _usio, cóme,
+ {{0x6b9bb4c3,0x69d535b8,0x443c822c,0x4d63098d}}, // _anug, cize, _ywv_, лкув,
+ {{0x19b7012a,0x7bc2d00d,0x443c8282,0x69c381e4}}, // _נפטר_, thou, _xwv_, dhne,
+ {{0xbea619b8,0xd8d700be,0x79888114,0x753b0088}}, // _замк, _טויט_, _sadw, _đuze,
+ {{0x6d451904,0x7bc28b3c,0x2006831d,0xf1b9a944}}, // baha, rhou, _rhoi_, _suša_,
+ {{0x6d450b20,0x28f895e3,0x7bc28848,0x69c3d00e}}, // caha, мель_, shou, ghne,
+ {{0x27e0500f,0xc5ec21e3,0x64580187,0x6f028061}}, // onin_, जकीय_, ávid, _szoc,
+ {{0xb4ac0e18,0x2d8a5010,0x27e05011,0x645e3143}}, // गही_, _habe_, nnin_, mupi,
+ {{0x7bc0d012,0x645e10d8,0x661894bd,0x69c38c5e}}, // _ilmu, lupi, _pivk, bhne,
+ {{0x69c3d013,0xde888028,0x657e5014,0x62829c11}}, // chne, _vị_, neph, mwoo,
+ {{0x61fc0713,0xa5bb0d38,0xa6db0125,0x81d600ab}}, // dlrl, _któr, _meða, িকা_,
+ {{0x2d8a4ff2,0xd5b88029,0x6282d015,0x205583c7}}, // _labe_, skā_, owoo, _штур,
+ {{0x6d451f24,0x27e0262f,0x628283b2,0x20190147}}, // yaha, dnin_, nwoo, _risi_,
+ {{0x27e08289,0x201925eb,0x7afd8aa2,0x60058333}}, // činu_, _sisi_, _øste, róme,
+ {{0x7bc09010,0xf8072f86,0x6d455016,0x657e1e8f}}, // _olmu, ечан, vaha, deph,
+ {{0x77b5c92f,0x6d455017,0x27e02320,0x645e5018}}, // _páxi, waha, gnin_, dupi,
+ {{0x2d8a5019,0x20191c1f,0x28c081a2,0x80d1064a}}, // [41b0] _babe_, _visi_, षैति, _डेरे,
+ {{0x9ce71ee7,0x2019023b,0x78a10106,0xa5bb026b}}, // нцел, _wisi_, älvs, _atór,
+ {{0x6d45501a,0x2d8a011b,0x6e22a05e,0xf99f8129}}, // raha, _dabe_, _luob, _đè_,
+ {{0x628280f3,0x6235b5b4,0x291e82c4,0x7f4287b6}}, // fwoo, веду, _agta_, _neoq,
+ {{0x628284df,0x6d45501b,0x69c38c41,0xaab303b6}}, // gwoo, paha, thne, ुनिक,
+ {{0x2d8a011e,0x98b80084,0xe296964f,0x7e6d501c}}, // _gabe_, sarą_, таш_, mtap,
+ {{0x4425002e,0x69c3826c,0x3f8b501d,0xdcf88196}}, // _îl_, rhne, _kacu_, _tavę,
+ {{0x3f8b00fe,0x2d8a04b9,0x889c00be,0x02d280d4}}, // _jacu_, _zabe_, לבוי, _देवभ,
+ {{0x7e6d501e,0x44314861,0x200d8087,0x26c9004f}}, // ntap, _htz_, rmei_, nzao_,
+ {{0x442301e9,0xdb008884,0xef1f501f,0x7649d020}}, // _kuj_, _domä, _saül_, rsey,
+ {{0x69c193af,0x27e00672,0x44235021,0x3947b396}}, // _ille, ynin_, _juj_, mans_,
+ {{0x3947d022,0x44235023,0x7d1a008b,0x2d9cbfc7}}, // lans_, _muj_, _útse, _enve_,
+ {{0xf4128158,0x44230282,0x7dc00338,0x2e47826b}}, // יפט_, _luj_, _lösa, _dìfá_,
+ {{0x3947d024,0xaf0480e8,0xdb1b823e,0x65c7826b}}, // nans_, _спіл, _lluï, _déhù,
+ {{0xa06708d5,0xd9430b5b,0x442301c5,0xb21b013c}}, // кара_, реси, _nuj_, tvær,
+ {{0x2d8a0510,0x6d43d025,0x3f8d8548,0x3f8b180c}}, // _sabe_, _hena, ndeu_, _cacu_,
+ {{0x6d43c586,0x27e05026,0x321a003e,0x3947d027}}, // [41c0] _kena, rnin_, _tipy_, kans_,
+ {{0xa5bb1b92,0x6d4384bf,0xb21b128d,0x3947d028}}, // _stór, _jena, svær, jans_,
+ {{0x6d43859c,0x3947d022,0x44235029,0x7afb8915}}, // _mena, dans_, _cuj_, _ayut,
+ {{0x62829aee,0x442301c0,0x657e502a,0x7c238706}}, // twoo, _duj_, seph, _munr,
+ {{0x394796b3,0x02d2800c,0xaad2801b,0x224db464}}, // fans_, _देशभ, _देशक, _cvek_,
+ {{0x62829337,0x3f9d81bc,0x7afb833e,0x657e036a}}, // rwoo, _anwu_, _dyut, qeph,
+ {{0x64a3181d,0x8c431e25,0x25aed02b,0x04433750}}, // _кара, _весе, _rofl_, _весн,
+ {{0x69c181a3,0xd90d826a,0x77bc8118,0x62962dcb}}, // _elle, سین_, _véxo, _oryo,
+ {{0x6d43d02c,0x661c502d,0x83fd0024,0x39479e9e}}, // _bena, _kirk, nuđe, bans_,
+ {{0x6d43d02e,0x68e9002e,0x661c502f,0x77b8002a}}, // _cena, şedi, _jirk, _díxi,
+ {{0x6d43cc4f,0x23b68006,0x3a24d030,0x224d8267}}, // _dena, _अकाद, _hump_, _zvek_,
+ {{0xccf30051,0x629609c4,0x3a2480b9,0x69d8ae52}}, // יכת_, _bryo, _kump_, live,
+ {{0xdfcf8624,0x7c2382f7,0x83fd190f,0x645d8084}}, // _بين_, _eunr, juđe, ąsia,
+ {{0x6d43d031,0x7c2384be,0x720500f7,0x661c09e1}}, // _gena, _funr, اوسم, _nirk,
+ {{0x7e60a34d,0x3f8b06cb,0x2fc20359,0x8a180087}}, // lump, _pacu_, _blkg_, торс_,
+ {{0xa5bb04c3,0x44235032,0x645a8390,0x39478bd6}}, // _atóp, _ruj_, mrti, zans_,
+ {{0x39478247,0x69d884dc,0x387a0db1,0x2ceb02f1}}, // [41d0] yans_, kive, _eppr_, _जुड़ल_,
+ {{0x7e6d3c51,0x394782e6,0x6b8d5033,0x6d4382a6}}, // rtap, xans_, _haag, _xena,
+ {{0x3947a62d,0x7e6d010a,0x6b8d2a22,0x7afb8e6d}}, // vans_, stap, _kaag, _syut,
+ {{0x83fd0668,0xa3cd8035,0x7e6d09c4,0x39478c53}}, // buđe, षों_, ptap, wans_,
+ {{0x6d48d034,0x3947d035,0xb5fd803e,0x7e60b6e2}}, // kada, tans_, luše, jump,
+ {{0x69d886be,0x44233592,0xe81b83eb,0x6b8d00bd}}, // give, _tuj_, _पूजा_, _laag,
+ {{0x39478205,0x6adf0996,0x2369017f,0x2d815036}}, // rans_, नपुर, _ocaj_, mehe_,
+ {{0x6d43d037,0x39478bd6,0x661c21a3,0x236901c5}}, // _sena, sans_, _zirk, _ncaj_,
+ {{0x224d8d11,0xe6198dea,0x3f8dabe1,0x6d48c3f0}}, // _uvek_, нди_, rdeu_, fada,
+ {{0x7dc000f2,0xb5fd935a,0xddcd0035,0xceb200be}}, // _lösn, kuše, jważ, ציי_,
+ {{0x69c7020f,0xddc41c24,0xa3c006a7,0x6b8d0365}}, // dhje, ntiš, ंघल_, _baag,
+ {{0x4e95806b,0x6d43d038,0xdb1b8722,0x62960e35}}, // _مشتر, _wena, _lluí, _pryo,
+ {{0x6d43b55e,0xee2e8934,0x6d48cd3d,0xe1ff009a}}, // _tena, _ын_, bada, ntów_,
+ {{0x7bc6020f,0x27ffa7be,0x2d81111b,0xa5948081}}, // shku, llun_, jehe_, _връщ,
+ {{0xc8b50084,0x6b8d5039,0x057489a7,0x50b51246}}, // асты, _faag, _خاند, асту,
+ {{0xe1ff009a,0x200b001b,0x69d8b8e6,0x6b8d11c9}}, // któw_, _chci_, zive, _gaag,
+ {{0x661c1608,0xaf060470,0x764d0ce9,0x69d8a499}}, // [41e0] _pirk, упал, gsay, yive,
+ {{0x59862550,0xf9921921,0x6b8d2ea8,0xdca60eef}}, // _слоб, _طبخ_, _zaag, лази,
+ {{0x661c05be,0x27e9d03a,0x3a2481e0,0x29cd02ce}}, // _virk, čanj_, _sump_, ržat_,
+ {{0x661c0352,0x4fa6165d,0x6d488510,0x926a91b3}}, // _wirk, _симв, zada, ерка_,
+ {{0x6d488be9,0xdb1bd03b,0x673b807a,0xbb3b0e82}}, // yada, _fluí, dbuj, געני,
+ {{0x7c29003e,0x7bc4009f,0xaca40135,0x644e026c}}, // _červ, _lliu, _atụr, _tvbi,
+ {{0x3945938d,0xa2db015c,0x6e26020d,0xfd690133}}, // _dels_, _पेन्, _hukb, hapụ,
+ {{0x69d8d03c,0x6005afb5,0x6d48d03d,0x57b48dc0}}, // sive, tóma, wada, рбит,
+ {{0x6d48d03e,0xda040072,0x6b8d01b4,0x2bb98075}}, // tada, ळतात_, _raag, _आवता,
+ {{0x7bc4503f,0x80ba00ab,0x6b8294f2,0x6b8d5040}}, // _aliu, _উইন্, leog, _saag,
+ {{0x7e60d041,0x6b8d1341,0x6e2602c4,0x7bc45042}}, // sump, _paag, _lukb, _bliu,
+ {{0x764d02b8,0x7e60d043,0x629d802a,0x645acb9d}}, // ysay, pump, _ásoc, rrti,
+ {{0x6d48d044,0x59b888fd,0x8f9b810f,0x7bc40706}}, // pada, _अवार, _רידי, _dliu,
+ {{0x6b8d5045,0x6d48afc7,0x645a807a,0x6b8282c4}}, // _waag, qada, prti, heog,
+ {{0x860791cc,0x6b8d02a3,0x4dfb8051,0xdb00840e}}, // _حقوق_, _taag, _רפוא, _comú,
+ {{0x69c700f1,0x764d00a4,0xb5fd85b9,0xdc9b8039}}, // shje, tsay, ruše, _טיול,
+ {{0x2d8eace1,0xa3ea035a,0x6b82cdb0,0x141c0039}}, // [41f0] _kafe_, _मदत_, deog, _אוהב,
+ {{0x9967014f,0x21671445,0x76428365,0xb8db00ab}}, // _стил, _стиг, _mwoy, _আই_,
+ {{0x80d300c8,0x2d81022e,0x628980b9,0xb6042306}}, // _দুর্, rehe_, _sseo, сятк,
+ {{0x6722a0b3,0x321e8176,0x39459a1f,0xa1340019}}, // _ngoj, _aity_, _sels_, _پریش,
+ {{0x394587e2,0x216994d6,0xa969817c,0xfaa5a84f}}, // _pels_, вили_, вила_, рало,
+ {{0x321ed046,0x9f5200e7,0xa85589a5,0x98558284}}, // _city_, voyé_, атиј, атиш,
+ {{0xe1ff0063,0x24980118,0x7642c713,0x394581a1}}, // stów_, _grrm_, _awoy, _vels_,
+ {{0x765bd047,0x98a58087,0x9b898065,0x656a8229}}, // truy, _află_, _جنرل_, _acfh,
+ {{0x6d47059c,0x673b8216,0xdee59156,0xe3b08591}}, // _keja, rbuj, ропи, _عرف_,
+ {{0x6d470812,0x201d930c,0x7bc800eb,0x7e7d00b9}}, // _jeja, _wiwi_, ījum, _mpsp,
+ {{0xa3ea101c,0x7983d048,0x9d2200ab,0x7bc40084}}, // _मदद_, menw, _পড়ুন_, _pliu,
+ {{0x6d475049,0x69c5504a,0x7e7d3ccd,0x79838114}}, // _leja, _alhe, _opsp, lenw,
+ {{0x9814803f,0x75d58065,0xe5a5bd93,0x6286090d}}, // _ابلا, _پيغا, _вили, uwko,
+ {{0x8cb58021,0x6d4703f2,0xed598779,0x3f8fd04b}}, // _всич, _neja, _рок_, _hagu_,
+ {{0x7aed03a7,0x6e260122,0x7e7d01a9,0x59b89d40}}, // _exat, _sukb, _apsp, _अवसर,
+ {{0xa0670003,0x6d5503d2,0x44278087,0x2fc90637}}, // јата_, _adza, _iun_, ghag_,
+ {{0x442793e1,0x6d47504c,0x7983aa52,0x2d8e8300}}, // [4200] _hun_, _beja, kenw, _yafe_,
+ {{0x4427d04d,0x442000f6,0x399a093f,0x3f8fcce6}}, // _kun_, _hii_, _ייִד, _lagu_,
+ {{0x6d47504e,0x6b82ac6c,0x4420504f,0x03269260}}, // _deja, teog, _kii_, аден,
+ {{0x4427d050,0x69dc5051,0x3f8f90b5,0x4420026b}}, // _mun_, lire, _nagu_, _jii_,
+ {{0x44205052,0x69dc00f7,0x83fd00ce,0x798395aa}}, // _mii_, oire, nuđa, fenw,
+ {{0xe8549301,0x79838d35,0x69dc5053,0x4427c051}}, // منتد, genw, nire, _oun_,
+ {{0xfce30c8e,0x25f517a3,0x4427b2c7,0x3f8f82c4}}, // _доро, ्तरी_, _nun_, _bagu_,
+ {{0x44200665,0x69dc5054,0x443a123c,0xf1b882a6}}, // _nii_, hire, mpp_, _diġa_,
+ {{0x4427d055,0x69dc1efb,0x3f8f8359,0x317a826a}}, // _aun_, kire, _dagu_, _لحاظ_,
+ {{0x77bc9fd1,0x4427bf80,0x44205056,0x69dc1b6d}}, // _méxi, _bun_, _aii_, jire,
+ {{0x442787f4,0x2fc000c9,0x442010ab,0x77bcaa3e}}, // _cun_, kkig_, _bii_, _léxi,
+ {{0x4427d057,0xe73a8dea,0x67228088,0xdcfe01a1}}, // _dun_, _ред_, _ugoj, jepč,
+ {{0x69dc3d27,0x4427d058,0x9b580098,0x2fc6820d}}, // fire, _eun_, шият_, _ilog_,
+ {{0x69dc5059,0x645e0d2f,0x44278039,0x2fc90122}}, // gire, krpi, _fun_, whag_,
+ {{0x44278046,0x44200012,0x2ee880ab,0xed578dc7}}, // _gun_, _fii_, _পরিষ, _тој_,
+ {{0x6d4714e5,0x3f84a254,0x69dc1dad,0x4420505a}}, // _seja, nemu_, aire, _gii_,
+ {{0x6d47059c,0xd2510013,0x69dc505b,0x61f88289}}, // [4210] _peja, _عند_, bire, kovl,
+ {{0x3f84822e,0x442782b8,0x7bdd505c,0x27070a3a}}, // hemu_, _yun_, lisu, _शरीर_,
+ {{0x44270104,0xe4750003,0x4420077f,0x3f84d05d}}, // ên_, слењ, _yii_, kemu_,
+ {{0x442008f1,0x0c242e2e,0x889a02f6,0x7c20d05e}}, // _xii_, імін, _עברי, _cimr,
+ {{0x3f8f8274,0x6d470661,0x3f8486b8,0x79838f67}}, // _ragu_, _teja, demu_, tenw,
+ {{0xb5fd812b,0x2b493625,0x2fc682d5,0x61f8811f}}, // kuša, _leac_, _alog_, govl,
+ {{0x2fc68213,0x7bdd505f,0xb5fd80eb,0x3a2900dd}}, // _blog_, kisu, juša, _luap_,
+ {{0xdce18029,0x7c2080b9,0x071281bc,0x44278039}}, // selī, _gimr, _gụkọ, _run_,
+ {{0x26cd812b,0x2d910025,0xef198364,0x442010ab}}, // uzeo_, _kaze_, ыми_, _rii_,
+ {{0x44279a67,0x4420504f,0x69dc002a,0x69ca8799}}, // _pun_, _sii_, xire, chfe,
+ {{0x2d9102a0,0x85118035,0x3f8480c3,0x69fa80be}}, // _maze_, डेंट_, bemu_, _עלעק,
+ {{0x44279029,0x7bdd0deb,0x69dc5060,0x671b83eb}}, // _vun_, gisu, wire, _पृथक_,
+ {{0x442044e0,0x44278032,0x3a2902f7,0x69dc5061}}, // _vii_, _wun_, _cuap_, tire,
+ {{0x4427d062,0x77bc862f,0xa06a5063,0x44201532}}, // _tun_, _réxi, лага_, _wii_,
+ {{0x2d85b7c5,0xa3e7816f,0x69dc4cc8,0x44205064}}, // mele_, यचा_, rire, _tii_,
+ {{0x2d85d065,0x0b4601a1,0x2cb8816d,0x236d8282}}, // lele_, _унин, ärde_, _ncej_,
+ {{0xb8ff26ee,0x2d912e4d,0xbb3b0158,0x63a45066}}, // [4220] _दे_, _baze_, _געפי, _inin,
+ {{0x2d85b238,0x99860307,0x3f84bf2d,0xa2b8864a}}, // nele_, _الدو, zemu_, ्नस्,
+ {{0x63a42bea,0x2d911c80,0xf1b9811f,0x46d9a724}}, // _knin, _daze_, _guši_, _बेसह,
+ {{0x2d85d067,0xc7b8811f,0x6e2d01b4,0xdcfa8493}}, // hele_, _tuđi_, bqab, letă,
+ {{0x2d85d068,0x7e641b01,0xaad28816,0x61f8807a}}, // kele_, quip, _देखक, tovl,
+ {{0x2d85d069,0x2fc6d06a,0xcaa580f7,0x2244016b}}, // jele_, _slog_, تصمي, ámky_,
+ {{0x2d85d06b,0x61f8d06c,0x3f84bc80,0xd010830f}}, // dele_, rovl, temu_, ولت_,
+ {{0x63b63bb0,0x96348374,0x9a29027d,0x63a40c2e}}, // _noyn, ониц, _lươn_, _nnin,
+ {{0x7bdd0a8d,0xcf930039,0x61f88042,0xa37b03a7}}, // visu, לטה_, povl, niõe,
+ {{0x442a01c5,0x63a4506d,0xf9c4804e,0x7bc282c4}}, // _kub_, _anin, _بحری, lkou,
+ {{0x26c2844a,0x2937893f,0xfe6f8bca,0x3a2909da}}, // ško_, _האבן_, ودی_, _suap_,
+ {{0x2fc68042,0xf1b98289,0xdb0d007b,0x290ca759}}, // _ulog_, _ruši_, _blað, _izda_,
+ {{0x442a506e,0x2d85d06f,0x15fa0fea,0xadfa0d5d}}, // _lub_, bele_, ्तार_, ्तान_,
+ {{0xf2d38158,0x2d85d070,0x236d009a,0xb5fd826f}}, // לער_, cele_, żej_, lušn,
+ {{0x442a022c,0xeb9f006a,0x44385071,0xcad2819d}}, // _nub_, rløb_, _ntr_, dịmị,
+ {{0x543b8158,0x7bdd0609,0x628d01a8,0x290c816b}}, // _געהא, qisu, _tsao, _mzda_,
+ {{0x442a5072,0x6d4ad073,0x2d9100d2,0xa37b03a7}}, // [4230] _aub_, _kefa, _paze_, giõe,
+ {{0x7c2a936f,0x6d4ad074,0xfbdf041c,0xcaae8264}}, // _kufr, _jefa, liê_, কনাফ,
+ {{0xb5fd8db7,0xd49b0196,0x7c2a8239,0x62808084}}, // kušn, ыра_, _jufr, _apmo,
+ {{0x442a5075,0x2d85d076,0x69c8d077,0xf8a9803d}}, // _dub_, zele_, _alde, یگاه_,
+ {{0x2d85c2f5,0x957c809a,0x27e931b3,0xddc9826c}}, // yele_, ciąg, mnan_, hteš,
+ {{0x867a007c,0xddc281d0,0x957c8035,0x7c388390}}, // _דרשו, _tvoř, niąd, _otvr,
+ {{0x2d85d078,0x3dce80ab,0xd90e8019,0x27e902db}}, // vele_, িচাল, ہیے_, onan_,
+ {{0x27e95079,0xdbf203bb,0x2d85bf60,0x6d58ae52}}, // nnan_, _přís, wele_, _adva,
+ {{0x2d85d07a,0x27e90590,0x6d4ad07b,0x7c2a81ec}}, // tele_, inan_, _befa, _aufr,
+ {{0x6d4ad07c,0x6e3b81a9,0xb5fd9809,0x79870314}}, // _cefa, rpub, krše, jejw,
+ {{0x6d4ad07d,0x6e3bd07e,0x2d85d07f,0x7bc99aee}}, // _defa, spub, rele_, _kleu,
+ {{0x2d85d080,0x645c83a7,0x6d589db4,0xe29a041c}}, // sele_, ária, _edva, аам_,
+ {{0xeb9709b4,0x7bdb826c,0x16090327,0x4a435081}}, // ция_, _mmuu, वतार_, мняв,
+ {{0x83fd005c,0x8c4598a0,0x38a0802e,0x6d4ad082}}, // vrđe, _деке, _fără_, _gefa,
+ {{0x6d41d083,0xab5b08c5,0xd83880ce,0x290c1235}}, // mbla, mkün, _trče_, åda_,
+ {{0x63a45084,0x442a3dd7,0x27e9280c,0x7c29026f}}, // _unin, _rub_, gnan_, _čerp,
+ {{0x3ebf8458,0x7c2a8398,0x6d41851e,0x443800ed}}, // [4240] nyut_, _zufr, obla, _str_,
+ {{0x442a01c5,0x7c241572,0x6d4a81df,0x27e9474f}}, // _pub_, _hiir, _xefa, anan_,
+ {{0x7c2409f8,0x442a01c5,0x6e23d085,0x7bc980e7}}, // _kiir, _qub_, _minb, _bleu,
+ {{0x2d0a016f,0x130682eb,0x61fc0162,0x395a02c4}}, // _वरील_, озем, lorl, _hdps_,
+ {{0xc7b30bea,0x7c2410ab,0x6d41874c,0x27e00a5b}}, // _חבר_, _miir, kbla, miin_,
+ {{0x442a1393,0x1a9b0158,0x4a9b00be,0x61fc5086}}, // _tub_, יינע, יינג, norl,
+ {{0xa9678fbb,0x7bc9d087,0x61edc1d1,0x6d4a9139}}, // ција_, _fleu, éala, _refa,
+ {{0x6d418e1b,0x6e2bd088,0x27e05089,0x7bc994cc}}, // ebla, _bugb, niin_, _gleu,
+ {{0x7c2a8b5a,0x61fc508a,0x7e76508b,0x4424d08c}}, // _sufr, korl, ntyp, _iim_,
+ {{0x27e002c1,0xb5fd80fe,0x6d41a7d1,0xd5ba9cd5}}, // hiin_, sušn, gbla, рси_,
+ {{0x27e01e38,0x69a7801b,0x4395151b,0x7c240079}}, // kiin_, चारी, панс, _biir,
+ {{0xb5fd803a,0x6d4a831d,0x6e2390f6,0x3e741743}}, // vrše, _wefa, _einb, kät_,
+ {{0x671685e8,0x4424d08d,0x6d41d08e,0x7c24508f}}, // देशक_, _mim_, bbla, _diir,
+ {{0x4424d090,0x6d58d091,0x35f78077,0xdb008feb}}, // _lim_, _udva, _ورود_, _homó,
+ {{0x7c38803a,0x61fd87ca,0x7e7601e0,0x27e05092}}, // _utvr, _ísle, etyp, fiin_,
+ {{0x4424c536,0xc7b8825b,0x395a0197,0xd9c28264}}, // _nim_, _tuđu_, _edps_, ্চিম,
+ {{0x27e921b4,0x443ec12d,0x7e765093,0x80dc0264}}, // [4250] rnan_, lpt_, gtyp, _মুদ্,
+ {{0xd49b0012,0x7bc9837a,0x27e95094,0x69ce2b29}}, // ире_, _sleu, snan_, chbe,
+ {{0x7bc9d095,0xc17201c6,0x98bc81d0,0x27e92f08}}, // _pleu, _אחי_, ravě_, pnan_,
+ {{0x442481c5,0x70fa0039,0x60fa0039,0x78fa0039}}, // _cim_, _להתח, _להתק, _לפתו,
+ {{0x4424d096,0x80d38063,0xe29984fa,0xbf3603c8}}, // _dim_, _भेजे, сал_, _מארץ_,
+ {{0x6e2bd097,0x76ba0051,0x2249004f,0x3f891a1f}}, // _rugb, _המשפ, _mwak_, leau_,
+ {{0x4424d098,0x69c39412,0x671f03eb,0x443e81ed}}, // _fim_, rkne, _मृतक_, jpt_,
+ {{0x7bdbd099,0x3f890cf8,0xdb0081a8,0x443e80ee}}, // _umuu, neau_, _comó, dpt_,
+ {{0x6005840e,0x645c8db0,0x61fc0214,0x72058019}}, // nómi, árin, yorl, _فورم,
+ {{0x4424b709,0xd1b88060,0x7c2435e2,0xb5fda58f}}, // _zim_, _والا_, _siir, dršc,
+ {{0x7c2401c2,0x442481e9,0x2249509a,0x6d41800b}}, // _piir, _yim_, _awak_, rbla,
+ {{0x6d41d09b,0x44248069,0x6e2bd09c,0x395a0088}}, // sbla, _xim_, _tugb, _sdps_,
+ {{0x74c300c8,0x3f89509d,0x61fc19db,0x2905802a}}, // ্পিউ, deau_, torl, _hyla_,
+ {{0x3e74025d,0x8e0880e8,0xab5b0019,0xf1b9c573}}, // vät_, _днів_, lkül, _gušt_,
+ {{0x27e00364,0x5502809a,0x61fc509e,0x77bc81df}}, // tiin_, _रुपए_, rorl, _téxt,
+ {{0x3e74509f,0x4abdd0a0,0x09b8b849,0x7e76226d}}, // tät_, ्नाव, _अव्य, ttyp,
+ {{0x4adb84e5,0x27e050a1,0x2905808e,0xc0768129}}, // [4260] _मेलव, riin_, _lyla_, _bướm_,
+ {{0x4424d0a2,0x27e02d48,0x3e741a3f,0x1a9b00be}}, // _sim_, siin_, rät_, _פייע,
+ {{0xa3e7816f,0x4424d0a3,0xc32a80f7,0x7e762aec}}, // यचं_, _pim_, مكان_, styp,
+ {{0x7c2e8187,0x3f891eed,0x7bc650a4,0x27e00df6}}, // íbri, ceau_, nkku, qiin_,
+ {{0x442481e9,0x6d5c8019,0xd0568085,0xf1b0109b}}, // _vim_, órak, əcək, ञापन,
+ {{0x443ed0a5,0x2905800d,0x1df78e5b,0x62840267}}, // ypt_, _byla_, ुतोष_, _mpio,
+ {{0x7dcb009a,0x6d5c50a6,0xddcd0087,0xdb008035}}, // yższ, _idra, staţ, _pomó,
+ {{0x6d4e0590,0xf09304de,0x394cb603,0x672201a9}}, // _heba, תנה_, _weds_, žoju,
+ {{0x6d4e059c,0x9f5f83a7,0x443e819d,0x69de50a7}}, // _keba, cluí_, wpt_, _ompe,
+ {{0x645c8003,0xddd6009a,0x7c2e50a8,0x60e7807b}}, // ário, jwyż, _kubr, ármá,
+ {{0x443e82af,0x6736009a,0x2249067f,0x9f530061}}, // upt_, kcyj, _swak_, ődés_,
+ {{0x6d4e1600,0x69de0cda,0x7e6450a9,0x7c2e010c}}, // _leba, _ampe, krip, _mubr,
+ {{0x3f8950aa,0x5fa99664,0xada68ff7,0xd7a98072}}, // veau_, कारल, _надл, कारच,
+ {{0x171b873a,0x6d4e1b75,0xddcd02a5,0x443ed0ab}}, // _הודע, _neba, ktaš, ppt_,
+ {{0x3cdc035a,0x3f890cf8,0x2905008b,0xa3be86ae}}, // _गेले_, teau_, æla_, ेसा_,
+ {{0x69de1a14,0x6d5c031d,0xb5fd81f4,0x6d4e0f3e}}, // _empe, _adra, mrša, _aeba,
+ {{0x6d4e0dab,0x443cd0ac,0x3f891030,0x7dc002af}}, // [4270] _beba, _htv_, reau_, _lösu,
+ {{0x20021c86,0x3f8902be,0x7c2e0024,0x6d4e0333}}, // _ekki_, seau_, _bubr, _ceba,
+ {{0x7bcd1247,0x7c2e02ba,0x3f8950ad,0x6d5c0428}}, // _klau, _cubr, peau_, _ddra,
+ {{0x7c2e1123,0xdfc680f7,0x2c750370,0x69c70163}}, // _dubr, _شي_, råd_, lkje,
+ {{0x7e6450ae,0xdb0d0187,0xd6844197,0x443c9d46}}, // crip, _doaç, _пурп, _ltv_,
+ {{0x6d4e1254,0x200950af,0xb5fd8024,0x6458d0b0}}, // _geba, llai_, krša, _ovvi,
+ {{0x6d4538ec,0x7c2e008e,0xa6db007b,0x443cd0b1}}, // mbha, _gubr, _veðu, _ntv_,
+ {{0x6d5c3a71,0xee398847,0x6d450046,0x6d4e1f4b}}, // _zdra, они_, lbha, _zeba,
+ {{0x442e8943,0x6458d0b2,0xb904a701,0x6b8b9917}}, // _auf_, _avvi, _पे_, megg,
+ {{0xa2b28063,0xf1a497a3,0x6b8b92af,0x6b960214}}, // _आपत्, _गोपन, legg, _sayg,
+ {{0x62843625,0x7bcd0d35,0x63bb8247,0x61e3d0b3}}, // _spio, _blau, _koun, minl,
+ {{0xe72ed0b4,0xf77083f8,0x7dc000f2,0x63bb8247}}, // _је_, _ماه_, _höst, _joun,
+ {{0xe56ed0b5,0x63a98db7,0x27388028,0x27ff8247}}, // _из_, _mnen, _ứng_, moun_,
+ {{0x443cd0b6,0x7bcd03ac,0x6b8b8081,0x27ed81ec}}, // _ftv_, _elau, hegg, lnen_,
+ {{0x27edc20c,0x957c809a,0x63a9c717,0xb5fd8699}}, // onen_, siąc, _onen, hušk,
+ {{0x6d4e045c,0x27edd0b7,0x63bba4df,0x7bcd4c33}}, // _seba, nnen_, _noun, _glau,
+ {{0x27edb416,0x22160171,0x61e383bf,0xe7df001b}}, // [4280] inen_, _офор, kinl, _नगरप,
+ {{0x3f4c0028,0xb5fdad9b,0x27ed82af,0x7e6450b8}}, // ếu_, dušk, hnen_, rrip,
+ {{0x61e3d0b9,0x6441d0ba,0x6d5c81a8,0xeb9aa328}}, // dinl, npli, órai, жие_,
+ {{0x63bb8051,0x27ed8a0f,0xfe79800d,0xab838009}}, // _coun, jnen_, ktů_, вушк,
+ {{0x6d4e02ec,0xddcd01dd,0x27ff8176,0x63a980c3}}, // _teba, staš, doun_, _dnen,
+ {{0x27edd0bb,0x61e3807b,0xc0aa830f,0x7c2e50bc}}, // enen_, ginl, قابل_, _tubr,
+ {{0xdcfc0029,0x63bbd0bd,0xd7a9816f,0xf1b884b7}}, // _varē, _foun, कांच, _jiġi_,
+ {{0xb5fd8067,0x442e9705,0x27ed8e3b,0x443ca76e}}, // vrša, _ruf_, gnen_, _rtv_,
+ {{0x2d9850be,0x7dc0007b,0xf1b884b7,0x442ea948}}, // _kare_, _föst, _liġi_, _suf_,
+ {{0x7dc00459,0xaa43802e,0x89169e91,0x2d9850bf}}, // _göst, _рефл, _قبائ, _jare_,
+ {{0x9cec0a49,0x7bcd50c0,0x2d9844ce,0x61fd87ca}}, // _করুন_, _plau, _mare_, _ísla,
+ {{0x2d9850c1,0x443c80ff,0xaec696cf,0x626f09ab}}, // _lare_, _vtv_, _обел, lɗoɗ,
+ {{0x501b80be,0x2d980087,0x83fd0904,0xdcf500eb}}, // _וואו, _oare_, nuđi, _pazī,
+ {{0x7c668f24,0x7bcd00dd,0xc9841505,0x6b8b81ed}}, // _قابل, _wlau, куси, zegg,
+ {{0x2d8cd0c2,0x6e2701ed,0x69c7021e,0x26c48084}}, // mede_, _rijb, rkje, lymo_,
+ {{0x2d8cd0c3,0x2d9850c4,0x69c7035f,0x0cca801b}}, // lede_, _aare_, skje, ानीम,
+ {{0xd904803d,0x63a980dd,0x2d9817af,0x7dcd808b}}, // [4290] _چی_, _rnen, _bare_, _húsa,
+ {{0x44290282,0x2d8cd0c5,0xaa88003d,0x2d980039}}, // _hia_, nede_, _کنیم_, _care_,
+ {{0x7dc000f2,0x442950c6,0x6d450083,0x5f05800d}}, // _röst, _kia_, rbha, _हुन्_,
+ {{0xe6670698,0x2d8c9fcd,0x26c48110,0xb5fd877b}}, // _отго, hede_, kymo_, tušk,
+ {{0x442950c7,0xa2af816f,0x6b8bd0c8,0x2d980ca9}}, // _mia_, ंमध्, regg, _fare_,
+ {{0x442950c9,0x2d9850ca,0x2d8c85f3,0x4095997b}}, // _lia_, _gare_, jede_, ерит,
+ {{0x63bb8247,0x442950cb,0x3f9950cc,0x2d8cd0cd}}, // _toun, _oia_, _kasu_, dede_,
+ {{0x6441aa30,0x442950ce,0x27edd0cf,0xb4bd0c87}}, // vpli, _nia_, unen_, ेनी_,
+ {{0x3f990a73,0x2ec98e70,0x27edc255,0x27ffd0d0}}, // _masu_, िनेत, rnen_, roun_,
+ {{0x7c298e61,0xe93884c0,0xe6168d15,0x2d8cd0d1}}, // _hier, _قسمت_, нды_, gede_,
+ {{0x442950d2,0x7c29d0d3,0xfe79801b,0xf3e6117f}}, // _bia_, _kier, stů_, ежно,
+ {{0x442950d4,0x6441890d,0x3f9950d5,0xeb9a0a7c}}, // _cia_, rpli, _nasu_, чим_,
+ {{0x442939fd,0x443110dd,0x2d8cd0d6,0x6441809e}}, // _dia_, _luz_, bede_, spli,
+ {{0xf2d2893f,0x2d8c8511,0x3e798247,0x395e00e7}}, // נעם_, cede_, mèt_, ûts_,
+ {{0xf505835f,0x3f9904b9,0xb5fd80eb,0x3e7983ec}}, // _язко, _basu_, juši, lèt_,
+ {{0x44290104,0x2d9850d7,0x7c29d0d8,0x35c58105}}, // _gia_, _sare_, _nier, _लकड़,
+ {{0x9cec00c8,0x7dd2013c,0x3e798247,0x6b8450d9}}, // [42a0] _করেন_, _læse, nèt_, _ibig,
+ {{0x442950da,0x22401600,0xf1b884b7,0x2b5f8087}}, // _zia_, _itik_, _tiġi_, _aduc_,
+ {{0x2d980370,0x224d83ac,0x3a3f8122,0x7c29d0db}}, // _vare_, _awek_, _atup_, _bier,
+ {{0x7c29d0dc,0xe9f90104,0x2d9850dd,0x7e7bd0de}}, // _cier, _trả_, _ware_, ktup,
+ {{0x2d9850df,0xb5fd803a,0xa2948d13,0x7c29acf1}}, // _tare_, vršn, талі, _dier,
+ {{0x98a58182,0xcb1300be,0x3f9904b9,0x764450e0}}, // _adlı_, אלד_, _zasu_, mpiy,
+ {{0x3ea032da,0x83fd011f,0x3f9900fc,0x7c298888}}, // lvit_, suđi, _yasu_, _fier,
+ {{0xdfd08013,0x26c48110,0xdb0083a8,0x2d8c820d}}, // شيخ_, tymo_, _enmá, wede_,
+ {{0xe1ff0feb,0xc6f8835f,0x66018370,0x2d8c8808}}, // drón_, ьних_, folk, tede_,
+ {{0x442950e1,0x998902a5,0x6b841714,0xa3e33ffa}}, // _sia_, _staž_, _abig, फोन_,
+ {{0xd7ef9ad0,0x442900f6,0x2d8cd0e2,0x22400247}}, // _бу_, _pia_, rede_, _atik_,
+ {{0x444450e3,0x87da8b76,0x2d8cd0e4,0x07da845b}}, // ip_, _عباس_, sede_, _عذاب_,
+ {{0x660182a3,0x51871507,0x3f992bb1,0x2d8cd0e5}}, // bolk, нува, _rasu_, pede_,
+ {{0x6aa413c2,0x80b503b6,0x6b842396,0x7dcd807b}}, // _drif, _उपदे, _ebig, _húsn,
+ {{0x224050e6,0x3f9950e7,0x44444c84,0x442950e8}}, // _etik_, _pasu_, jp_, _tia_,
+ {{0x645ad0e9,0x5f9410f8,0x442950ea,0x2614816f}}, // msti, лист, _uia_, _नीती_,
+ {{0xb9081404,0x645c2db2,0x645ad0eb,0x63ad3c15}}, // [42b0] _मे_, _avri, lsti, _inan,
+ {{0x3f9903c3,0x7c29d0ec,0xf41f0009,0x799a8314}}, // _wasu_, _sier, lmä_, _natw,
+ {{0x7c299ff1,0xf1b884b7,0x3a3f942f,0x7e6d00dd}}, // _pier, _jiġu_, _stup_, suap,
+ {{0xeaec8b9f,0x6b9d50a3,0x7c298216,0xf8b61101}}, // जपूत_, ndsg, _qier, _ספרי_,
+ {{0x7c2986ee,0x2bb88013,0xa30f0327,0x645a8229}}, // _vier, عامة_, _सरोज_, hsti,
+ {{0xc0e58381,0x7c29d0ed,0xf41f0198,0x443150ee}}, // колк, _wier, hmä_, _tuz_,
+ {{0x7c29d0ef,0x6601837a,0x645ad0f0,0x83fd0669}}, // _tier, volk, jsti, luđu,
+ {{0x644550f1,0xdb238077,0x645a9277,0xf41f01ac}}, // mphi, _موزی, dsti, jmä_,
+ {{0x799a8d92,0x645ad0f2,0x603180eb,0x3f8d8a53}}, // _fatw, esti, _māmi, reeu_,
+ {{0x63ad0077,0x3e798247,0x7e7bd0f3,0x6b9b80e8}}, // _anan, rèt_, rtup, _haug,
+ {{0x6b9bbefe,0xe1ff0511,0xcb6a0a42,0x22401791}}, // _kaug, trón_, мане_, _stik_,
+ {{0x6aa4475d,0xa3dc009a,0x798e50f4,0x957c8035}}, // _prif, णों_, webw, wnąt,
+ {{0xe1ff2fb5,0x6ec492c8,0x3cf511be,0xdb1b9a1f}}, // rrón_, _متوق, ्थडे_, _boué,
+ {{0xf505838d,0x63ad41e6,0x200db27d,0x27e20101}}, // _изло, _enan, llei_, _smkn_,
+ {{0x1c11800f,0x444450f5,0xdd921190,0x6e2d8037}}, // ़ताल_, xp_, یور_, _èabb,
+ {{0xa2b2853e,0x3ea0826c,0x6fde81a9,0x63ad0af8}}, // _आपल्, _šiti_, tūci, _gnan,
+ {{0x78a5013c,0x444400ee,0x35a68d86,0xd00702a4}}, // [42c0] _erhv, wp_, _कोड़, тете_,
+ {{0x64a616be,0xdca61697,0x08c650f6,0x69d5460f}}, // каза, кази, _абон, chze,
+ {{0x44443693,0x6b9bd0f7,0x9f4001a8,0x200dd0f8}}, // up_, _baug, iniú_, klei_,
+ {{0x3958009f,0xfdbd0540,0x799a81bf,0x9f4000f7}}, // lars_, ोसॉफ, _satw, hniú_,
+ {{0xdaba8077,0xe1ff0019,0x6b9ba612,0x799a83ec}}, // بهشت_, król_, _daug, _patw,
+ {{0x39584a23,0x4444088b,0x645ad0f9,0x7e69d0fa}}, // nars_, pp_, ysti, drep,
+ {{0x645c02ce,0x55e31bdc,0x38c88180,0x7d030192}}, // _uvri, _торб, عاتی_, ünst,
+ {{0x28ba170c,0x2ca70366,0x27f250fb,0x6fe7810c}}, // _उपनि, ænde_, nnyn_, _rècè,
+ {{0x7e69998b,0xb2268110,0x4b7b80be,0x645ad0fc}}, // grep, _амал, _קאמו, wsti,
+ {{0x2246d0fd,0x645a9be9,0x20e3816f,0x7bc085b0}}, // mpok_, tsti, _केवळ_, _homu,
+ {{0x5fa38076,0x81c900c8,0x1da6835a,0xc8ca803d}}, // _खोवल, লোড_, _कोणत, _توان_,
+ {{0xf74690ca,0x39530a76,0x7bc0bfae,0x68fa8609}}, // _редо, _sexs_, _jomu, _ixtd,
+ {{0x200484b9,0x6b8f026c,0x7dc48362,0xe737c19a}}, // momi_, recg, _bòst, _реч_,
+ {{0x395803ab,0x3cfd86a7,0x63ad5025,0x2004b5aa}}, // gars_, _रुके_, _wnan, lomi_,
+ {{0xf807257e,0xd90f003d,0x98c40198,0xcf9b0073}}, // вчан, تید_, астл, мја_,
+ {{0x2d9cd0fe,0x63ad02b8,0x2004d0ff,0x2618058c}}, // _have_, _unan, nomi_, _बीबी_,
+ {{0xa3b685e8,0x2d9ca7b1,0x6d575100,0xb4bd01a2}}, // [42d0] जान_, _kave_, taxa, ेन्_,
+ {{0xab5d8063,0x6b9b81e2,0x2d9ea382,0x7c2d5101}}, // _może, _saug, ndte_, _hiar,
+ {{0x7c2d004f,0x65945102,0x6d570be9,0x6b9bae8f}}, // _kiar, раху, raxa, _paug,
+ {{0x80dc00c8,0x2d9cd103,0xdb040317,0x7c2d0704}}, // _মুক্, _lave_, _uniã, _jiar,
+ {{0x7bc0d104,0x20048612,0x7c2d5105,0x290c807e}}, // _domu, domi_, _miar, _ayda_,
+ {{0x3f86d106,0x2d9cd107,0x27e93437,0x6b9bd108}}, // _abou_, _nave_, lian_, _waug,
+ {{0xe739b1a5,0xdb0084b8,0x7642d109,0x7c2d011b}}, // ней_, _anmä, _atoy, _oiar,
+ {{0x7e69d10a,0xccf20bea,0x7c3a02be,0xdb0081ac}}, // trep, _הכל_, _étra, _pomô,
+ {{0x5a348b88,0x200d9781,0x6d5507d5,0x51f982c7}}, // _внут, rlei_, _keza, _знаю_,
+ {{0x442dd10b,0xef168196,0xb5fb026f,0x27e91ad1}}, // _hie_, ымы_, _uvád, hian_,
+ {{0x27e92fb8,0x290c8355,0x6d55510c,0x7c2d07d5}}, // kian_, _gyda_, _meza, _biar,
+ {{0x27e90812,0x442d81e2,0x7c2d467c,0x61e5510d}}, // jian_, _jie_, _ciar, _amhl,
+ {{0x27e9510e,0x442d8b87,0x6fd58511,0x660500dd}}, // dian_, _mie_, _cáce, kohk,
+ {{0x6d551ee0,0x61e50706,0x2d9cd10f,0x69c19473}}, // _neza, _cmhl, _gave_, _jole,
+ {{0x69c1c6b2,0x27e95110,0xa3b6800f,0x660509ca}}, // _mole, fian_, जाम_, dohk,
+ {{0x442d9019,0xb8dc0d38,0x27e92fb8,0x7c2d0098}}, // _nie_, _आप_, gian_, _giar,
+ {{0x6d552deb,0xe61a05f1,0xa3d7a342,0x7bc0971c}}, // [42e0] _beza, еда_, िसन_, _romu,
+ {{0xb5fd8025,0x7c2d5111,0x2bcc150e,0x6d555112}}, // dršk, _ziar, ासका, _ceza,
+ {{0x6d555113,0x27e912bf,0xdd5680be,0x2004923c}}, // _deza, bian_, רבעט_, yomi_,
+ {{0x9f060277,0x2d9c0301,0x7c3ed114,0x2ca78dc5}}, // _موجو, žve_, _épre, _mrnd_,
+ {{0x442d96a1,0x69c1d115,0x1603d116,0x6fca0038}}, // _die_, _bole, रकार_, _rých,
+ {{0x442d879f,0x6d555117,0x69ce1080,0x38a382d0}}, // _eie_, _geza, nkbe, _bır_,
+ {{0x69c1d118,0x442d8012,0x7bc0d119,0x799e0122}}, // _dole, _fie_, _tomu, _mapw,
+ {{0x09d111be,0xaa86003e,0x442dd11a,0x2d9cd11b}}, // _हत्य, _zvýš, _gie_, _save_,
+ {{0x6fca003e,0x7c2d0cb5,0x6e2e41cb,0x7c229a21}}, // _vých, _riar, _kibb, mmor,
+ {{0x69c1d11c,0x442dd11d,0x7c2d511e,0x2004b0a0}}, // _gole, _zie_, _siar, somi_,
+ {{0x6fca01ac,0x442d81bc,0x645e511f,0x2db70e82}}, // _tých, _yie_, nspi, אלין_,
+ {{0x6e2e5120,0x69c1d121,0xdce383bf,0xdb1b8118}}, // _libb, _zole, _canı, _gluó,
+ {{0x7c2d1e72,0x644386a4,0xdce39014,0x27e6d122}}, // _viar, _etni, _danı, _imon_,
+ {{0x69d88125,0xa3ab998e,0x27e95123,0x869b8039}}, // nhve, _कोन_, wian_, _ניוז,
+ {{0x69cc823c,0x248d003a,0x7c2d5124,0xdcfc00eb}}, // _तकनी, _ćemo_, _tiar, _darī,
+ {{0x645e5125,0x69ce1c11,0x69c007b6,0x00000000}}, // dspi, akbe, êmer, --,
+ {{0x27e92fb8,0x5d550fbe,0xb5fd803a,0x442d8133}}, // [42f0] rian_, икат, pušt, _rie_,
+ {{0x442dd126,0x6d5ad127,0x27e934c6,0x3eb98009}}, // _sie_, nata, sian_, ästi_,
+ {{0xdce382bb,0x27e95128,0xa115003d,0x628e011f}}, // _yanı, pian_, _خودت, čnoš,
+ {{0x6d5ad129,0xd4980071,0x69c1d12a,0xdce38085}}, // hata, ыру_, _sole, _xanı,
+ {{0xe299b1db,0x6d5ab18b,0x69c1d12b,0x442dd12c}}, // тал_, kata, _pole, _vie_,
+ {{0x442d8df3,0x27e6d12d,0x6e2e1106,0x7c22d12e}}, // _wie_, _amon_, _gibb, amor,
+ {{0x442d95cb,0x69c1d12f,0x7d1c807b,0x7e6d03a6}}, // _tie_, _vole, ýrsl, lrap,
+ {{0x2d931139,0x166585c2,0x28b68f0f,0x225f8115}}, // lexe_, авим, _अपरि, _tvuk_,
+ {{0x6d5ab9b5,0x7e6d003d,0x78a88084,0x644f817f}}, // fata, nrap, _erdv, ćcim,
+ {{0x0d829a8f,0xdce38214,0x2d931eed,0x629b80e4}}, // ольн, _sanı, nexe_, _isuo,
+ {{0x6fd58118,0x7e6d4f7a,0x67d59ef3,0x6b9f022b}}, // _cácc, hrap, _кожу, _baqg,
+ {{0x6aa9d130,0xcc760039,0x6d5a82d5,0x7dcdd131}}, // _kref, סגרת_, aata, _kúsk,
+ {{0x6d5a8a98,0x6b89d132,0x7bdd0168,0xe72ea276}}, // bata, _mbeg, ërua, _фе_,
+ {{0x6d5a9afe,0x64a58425,0x6aa98010,0xb5fd95db}}, // cata, _вака, _mref, krši,
+ {{0xdce38459,0x7c22d133,0x90c31229,0x69ce1c11}}, // _tanı, ymor, обре, rkbe,
+ {{0x3ead00d2,0x32079c51,0x6ed5850a,0x6aa981e8}}, // _šeta_, hony_, मैथु, _oref,
+ {{0x1b0d00c8,0x2b4d04c3,0x7e6d3131,0x32078658}}, // [4300] সেবে_, ñece_, grap, kony_,
+ {{0x83fd005c,0x2d859f1b,0x998c81a9,0x6b898deb}}, // vrđi, nfle_, _vidū_, _abeg,
+ {{0x6aa99286,0xd838812b,0x3ea98b80,0xc1010129}}, // _aref, _trči_, _šatl_, _mượt_,
+ {{0x6d5ace1d,0xc1010028,0x9f91877f,0x5aca8071}}, // zata, _lượt_, _náà_, клем_,
+ {{0x32078023,0x7e6d1c33,0x6e2e327e,0x645e5134}}, // fony_, crap, _tibb, sspi,
+ {{0x20ba29b7,0x6aa98114,0xceb30039,0x64a6373a}}, // _उपाध, _dref, _ויש_, _кага,
+ {{0xa3c60b3b,0x6d5ad135,0x02b4000d,0x61ead136}}, // _एवं_, vata, ुहुन, rifl,
+ {{0x6d5ab155,0x61ea9286,0xbfa78198,0x7bd981ec}}, // wata, sifl, атье_, chwu,
+ {{0x6d5ad137,0x44381918,0x3207d138,0xe2979908}}, // tata, _hur_, bony_, _кат_,
+ {{0x44385139,0x63a08687,0x61ed80f7,0x62828a53}}, // _kur_, _hamn, éalt, mtoo,
+ {{0xd49782ff,0x6d5a81bf,0x63a0d13a,0x6282929d}}, // аря_, rata, _kamn, ltoo,
+ {{0x4438513b,0x6d5ad13c,0xc7c78198,0x63a0821e}}, // _mur_, sata, _выше_, _jamn,
+ {{0x6d5a80e4,0x44381fdc,0x63a081b4,0x3a741af1}}, // pata, _lur_, _mamn, оляр,
+ {{0xa2a78c78,0x4438004c,0x6d5ad13d,0x7e6d047f}}, // चित्, _our_, qata, vrap,
+ {{0x4438034a,0x19581c0e,0x6d5880eb,0x2904007e}}, // _nur_, расы_, _ieva, şman_,
+ {{0x7e6d513e,0x63a28019,0x63a0d13f,0x25e29370}}, // trap, jdon, _namn, टोरी_,
+ {{0x44380b65,0x51870791,0xba3d801b,0x6d588074}}, // [4310] _aur_, _куна, _prům, _keva,
+ {{0x7e6d5140,0x27058028,0x63a2847f,0x3207d141}}, // rrap, ồng_, edon, xony_,
+ {{0x4438003c,0xd49b0098,0x3207826b,0x7e6d22dc}}, // _cur_, ъра_, vony_, srap,
+ {{0x6d58af68,0x2d930373,0x5b2580f7,0x63a083ac}}, // _leva, sexe_, مفضل, _camn,
+ {{0x7cca8201,0xa3ab835a,0x69c55142,0x660883ac}}, // lərd, _कोण_, _kohe, kodk,
+ {{0xceb20159,0x7c38adb2,0x79951c40,0x81c900ab}}, // _זיך_, _ouvr, nezw, লোর_,
+ {{0x4438061f,0x32078063,0xf09400be,0x6fca016b}}, // _gur_, rony_, ַנץ_, _výcv,
+ {{0x6aa9d143,0x62828cd8,0xddcd0805,0x63a2c5e3}}, // _tref, btoo, ntaž, cdon,
+ {{0x4438034a,0x799520b3,0x7dcd807b,0x06098abe}}, // _zur_, kezw, _húsi, лник_,
+ {{0x58878a7f,0x6d5887d9,0x66088353,0x69c52bd7}}, // _выда, _ceva, godk, _nohe,
+ {{0x6d58d144,0xc3290451,0xc101001c,0x63a0d145}}, // _deva, _נו_, _vượt_, _yamn,
+ {{0xe9f90028,0x69d703a8,0xeb9f1277,0xd83882d6}}, // _trẻ_, _alxe, dløs_, _prčv_,
+ {{0xed4e91c7,0x69c55146,0x27edabc5,0x87078198}}, // _но_, _bohe, mien_, _тяже,
+ {{0x6d58a40d,0x69c507f1,0xd5b18872,0xed599c79}}, // _geva, _cohe, _رفع_, _сок_,
+ {{0x7dd2013c,0x3f6a1950,0x27ed81ed,0x87db86a7}}, // _sæso, ливо_, oien_, _बताऊ,
+ {{0x6ab78996,0xafe305d3,0x44385147,0xd83880fe}}, // _आपूर, посл, _rur_, _grču_,
+ {{0x44381c8d,0x4420082e,0xef1a0a9f,0x75468019}}, // [4320] _sur_, _ihi_, ума_, _érzé,
+ {{0x27edd148,0x63a0a055,0x7cca8085,0x62828069}}, // hien_, _samn, bərd, vtoo,
+ {{0x44200142,0x7dc00065,0xb8ed00c8,0x27edd149}}, // _khi_, _kösz, _শে_, kien_,
+ {{0x27ed8009,0x61ee011c,0x320a0a7a,0x6608b55a}}, // jien_, libl, loby_, zodk,
+ {{0x27eda187,0x63a28bf9,0x63a0822c,0xdcfc0bcf}}, // dien_, rdon, _vamn, _obrć,
+ {{0x4438514a,0x61ee10cd,0x63a2d14b,0x27ed80f3}}, // _tur_, nibl, sdon, eien_,
+ {{0x44381aee,0x6d58a1bf,0x6d5e514c,0x62828ebc}}, // _uur_, _reva, mapa, stoo,
+ {{0x6d5e5005,0x27edb665,0x4420001c,0x44321bb5}}, // lapa, gien_, _nhi_, _niy_,
+ {{0x52b403bb,0x6d588eef,0x7c38b6c5,0x7cca8085}}, // ुहोस, _peva, _suvr, zərd,
+ {{0x6d5e514d,0x4420514e,0x61ee514f,0x27ed80f3}}, // napa, _ahi_, jibl, aien_,
+ {{0xab5d8063,0x183683f8,0x27edd150,0x320a0e04}}, // _możn, _طراح, bien_, doby_,
+ {{0x44205151,0x27ed90cd,0x6d5e1c01,0x4426d152}}, // _chi_, cien_, hapa, nmo_,
+ {{0x6d5e049f,0x44322543,0x6d58d153,0x44205154}}, // kapa, _diy_, _teva, _dhi_,
+ {{0x6abb00c8,0x61ee5155,0x44200133,0x69dc0739}}, // উনলো, gibl, _ehi_, ghre,
+ {{0x58d429c9,0x84468277,0x6d5e0867,0x2fc69d14}}, // порт, _اختل, dapa, _koog_,
+ {{0x44200028,0x4426935a,0x36d50ada,0x2fc681b4}}, // _ghi_, jmo_, ообр, _joog_,
+ {{0xddcd1329,0x2fc6d156,0x69dc0c41,0xa3ce016f}}, // [4330] rtaž, _moog_, bhre, _शकत_,
+ {{0x69dc5157,0x6d5e0461,0x27edaffc,0x7175003d}}, // chre, gapa, zien_, _بهدا,
+ {{0x2bdd86bf,0x23690353,0x9f350558,0x6d9e84b7}}, // _मतदा, _kdaj_, _мені, _għaż,
+ {{0xedb5901c,0x6d4186a5,0x649e84a8,0x2fc68069}}, // _अच्छ, zcla, mšić, _noog_,
+ {{0x6b8d5158,0x27edd159,0x6d5e217b,0x7e7900be}}, // _mbag, vien_, bapa, _דאָז,
+ {{0x4426d15a,0x764d515b,0x7f5f3e5a,0x4a5c0e82}}, // amo_, mpay, laqq, נדוו,
+ {{0x27edb52f,0x64ac9487,0xdb0d01df,0x3ea9515c}}, // tien_, nđić, _moañ, lvat_,
+ {{0x2369020f,0x2249059c,0x7a20016d,0x5fa38d86}}, // _ndaj_, _otak_, möte, _खोखल,
+ {{0x27edd15d,0xa3b69c4f,0xee3684d9,0x2fc68952}}, // rien_, जार_, оны_, _doog_,
+ {{0x44200a73,0x27edd15e,0x27080028,0x6b8d1ea2}}, // _shi_, sien_, ởng_, _abag,
+ {{0x22494fb6,0x6d41d15f,0x44200028,0x27edd160}}, // _atak_, rcla, _phi_, pien_,
+ {{0xab2a102f,0x442001c0,0x232a10ca,0x6a8601e5}}, // иона_, _qhi_, иони_, олма,
+ {{0x69dc36b1,0xadfb08fd,0x6d5e3e75,0xfdfb073c}}, // thre, ्वान_, yapa, ्वास_,
+ {{0x6aad5133,0xb21b013c,0x7a202203,0x44268110}}, // _draf, stæn, köte, zmo_,
+ {{0x44200104,0x4426d161,0x320a5162,0x75e90201}}, // _thi_, ymo_, roby_, _gözə,
+ {{0xe7370364,0x6d5e23c9,0x6b8d19a8,0x442001e4}}, // жет_, wapa, _gbag, _uhi_,
+ {{0x6e46015b,0x63a45163,0x61ee06c0,0xe29a1073}}, // [4340] زنام, _iain, pibl, _тап_,
+ {{0x63a45164,0x23690353,0x05660110,0x604301bc}}, // _hain, _zdaj_, івен, ọmat,
+ {{0x63a45165,0x6d5e5166,0x3ea90009,0xa3b68f97}}, // _kain, rapa, avat_, जाल_,
+ {{0xddcd0087,0x63a41ffc,0xead2846d,0x2d855167}}, // tuaţ, _jain, _fẹ́_, _ölen_,
+ {{0x6d5e5168,0x63a45169,0x1a968875,0x62862d09}}, // papa, _main, تجاج, ntko,
+ {{0x442699c5,0xfce60847,0x63a4516a,0x7d068063}}, // smo_, _дого, _lain, ększ,
+ {{0x6d5c0029,0xa37b0073,0xfd078065,0xd4670009}}, // _iera, lhõe, _آج_, _мире_,
+ {{0x6d5c1705,0xef19809a,0xb4d6073c,0x6286119b}}, // _hera, leży_, िनी_, ktko,
+ {{0x6d5c16a7,0x7e64516b,0xab5d8063,0x6fdc82be}}, // _kera, nsip, _możl, _néce,
+ {{0x7c3c1c26,0x6b8d0081,0x06cd00ab,0x5c748cec}}, // _kurr, _sbag, _রেডি, плот,
+ {{0x63a4516c,0x6d5c1599,0x29188ede,0x77668098}}, // _bain, _mera, _ára_, _дълж,
+ {{0x63a40ad7,0x7bc283d3,0x6d5c1c11,0x69c880e8}}, // _cain, njou, _lera, _hode,
+ {{0x63a4516d,0x7c3c011e,0x6fe8809a,0x6448516e}}, // _dain, _lurr, jści, _édif,
+ {{0x6fdc82be,0xaca401bc,0x69c8d16f,0xdb0080e7}}, // _déce, _atọr, _jode, _camé,
+ {{0xe50286b7,0xdb00810c,0x62865170,0x68e20326}}, // _रुचि_, _damé, atko, _ƙoda,
+ {{0x6d5c5171,0x6b8d5172,0x6fd80333,0x63a42816}}, // _aera, _ubag, _píca, _gain,
+ {{0x6d5c2b15,0x7c3c00ad,0x224944a4,0x443c81c5}}, // [4350] _bera, _aurr, _utak_, _huv_,
+ {{0x63a45173,0x443c946a,0x7c3c5174,0x69c8d175}}, // _zain, _kuv_, _burr, _node,
+ {{0x6d5c088b,0x7c3c0051,0xd37180f7,0xa3b68a27}}, // _dera, _curr, فها_, जां_,
+ {{0xa3ab8063,0x69daaa70,0x7c3c00f1,0x7f5f0609}}, // _कोश_, _alte, _durr, qaqq,
+ {{0x443c9393,0x69c8d176,0x7d7712c8,0x29f68035}}, // _luv_, _bode, _واسط, kład_,
+ {{0xe570026a,0xfa780039,0xdb1b8187,0x1a9c00be}}, // _لطف_, קעות_, _louç, יידע,
+ {{0x69c8d177,0x443c8282,0xdd1c80e1,0x9878011f}}, // _dode, _nuv_, yšši, ršće_,
+ {{0x62860d38,0x6d4501e4,0x61c805fb,0x69dad178}}, // ytko, lcha, _रक्ष, _elte,
+ {{0x69c8d179,0x443c822c,0x7f5d517a,0x6d451e3f}}, // _fode, _auv_, _mesq, ocha,
+ {{0x6d5c35d7,0x6d45517b,0x6fdc82be,0x69c8d17c}}, // _xera, ncha, _réce, _gode,
+ {{0xcb09073a,0xb21b013c,0x2b408bc8,0x63a4517d}}, // _על_, rtæl, žice_, _pain,
+ {{0x29040214,0x6d45340d,0xe29a1986,0x22841eca}}, // şmak_, hcha, бам_, tök_,
+ {{0x98a78182,0x63a4517e,0xeb970750,0x7dd302d0}}, // manı_, _vain, чия_, _kısı,
+ {{0x7dcd8118,0x63a4517f,0x6e35011f,0x7dd2005f}}, // _cúst, _wain, _nizb, _væsk,
+ {{0x22841836,0x63a45180,0x6e3d02af,0x7dd30214}}, // sök_, _tain, _ausb, _mısı,
+ {{0x6d5c04bf,0x63a40c41,0x7e6412a5,0xfbdf0187}}, // _sera, _uain, tsip, chê_,
+ {{0x6d5c059c,0xdb00c92f,0x69cc8105,0x7c242bd7}}, // [4360] _pera, _tamé, _तकली, _ihir,
+ {{0x443c822c,0x7dcdb1bd,0x06c580ab,0x80ae80d4}}, // _yuv_, _gúst, এনপি, ंटमे,
+ {{0xc9530451,0x6d5c2fbc,0x31570158,0x7e645181}}, // _אמת_, _vera, _מיין_, ssip,
+ {{0xa3e68d38,0x6d450c64,0x69c88db7,0x7bc982be}}, // यों_, acha, _sode, _coeu,
+ {{0x6d4501c5,0x7bc28009,0x27e05182,0x6fd581d0}}, // bcha, rjou, mhin_, _václ,
+ {{0x27e01066,0x7c3c4f32,0xfd5901bc,0x7c2402c4}}, // lhin_, _turr, _mgbụ, _lhir,
+ {{0x6561d183,0x764b9ad5,0x7c240286,0x5fe001a2}}, // dalh, _atgy, _ohir, _नताल,
+ {{0xdfd08013,0x27e05184,0x79988247,0x2d9a5185}}, // ئية_, nhin_, sevw, lepe_,
+ {{0x69c88364,0x27e00364,0x442482af,0xa3e6d186}}, // _tode, ihin_, _ihm_, योः_,
+ {{0x7c242f13,0x69dad187,0x27ff2944,0x443c8069}}, // _ahir, _ulte, čuni_, _puv_,
+ {{0x395ea40d,0x443c810c,0xfd590135,0x3d0681ab}}, // _iets_, _quv_, _agbụ, _सुखे_,
+ {{0x395e8186,0x7dcdd188,0xdddb941f,0xddc98162}}, // _hets_, _súst, rtuš, treţ,
+ {{0x6561d189,0x6d45518a,0x7c241a79,0x27e0387b}}, // balh, ycha, _dhir, dhin_,
+ {{0x2d9a518b,0xa410001b,0x656199ae,0x398f861c}}, // jepe_, ětší_, calh, cısı_,
+ {{0x7f5d518c,0x69d501ec,0x395ed18d,0x27e001ec}}, // _pesq, rkze, _mets_, fhin_,
+ {{0x2d8c90f4,0x34fb8039,0x7c243648,0x27e00acd}}, // lfde_, _להוד, _ghir, ghin_,
+ {{0x6d451a21,0x9f40007b,0xa3b682f1,0x69c38035}}, // [4370] tcha, rnið_, जाई_, yjne,
+ {{0x6d45518e,0x06140009,0xf1d1000d,0x4424b4b1}}, // ucha, едую, _सकिन, _ahm_,
+ {{0xf8d1000f,0xd9180e02,0x6d4532e6,0x244180ff}}, // _हथिय, _нью_, rcha, _xóm_,
+ {{0x27e006c0,0x6d450367,0xf1b88609,0x7a200106}}, // chin_, scha, _anġ_, möta,
+ {{0x398f8214,0x59d10ebf,0xe8108a0d,0x6e3d518f}}, // yısı_, _सकार, ावना_, _uusb,
+ {{0x644a11b9,0x2d9a002e,0xe7d51bef,0x2d8c81ed}}, // ífic, cepe_, _yığı, jfde_,
+ {{0x395ed190,0x656183cd,0xf8928073,0x6e2d8037}}, // _dets_, valh, вајќ, _èabi,
+ {{0x656187fe,0x2a669fdb,0x2d8c9384,0x7ddf841c}}, // walh, bsob_, efde_, _mêse,
+ {{0xc91a3ed8,0x656180a9,0x395e8722,0x236d807a}}, // _धर्म_, talh, _fets_, _idej_,
+ {{0x7dcdd191,0x7c245192,0xddc98267,0x27e00168}}, // _rúss, _shir, freš, zhin_,
+ {{0x31630d11,0x6561d193,0x398f807e,0x601e8168}}, // dajz_, ralh, rısı_, sëma,
+ {{0x6fd815a0,0x656187f0,0x3078866e,0x2d8c9407}}, // _cícl, salh, _رحمة_, afde_,
+ {{0xe60e8a8e,0xbdf88065,0x656183a7,0x06cd00ab}}, // _ад_, _کرنا_, palh, _রেসি,
+ {{0x75e00065,0xc58e8028,0x2441c92b,0x2fdd8197}}, // _köze, _hồng_, _tóm_, _hlwg_,
+ {{0x27e033fe,0x7c2433fe,0x2fcb07f1,0x236d8af8}}, // thin_, _thir, _bocg_, _odej_,
+ {{0x6fdc88f9,0x442b5194,0x6144846d,0x26c002f9}}, // _kéca, cmc_, _kìló, txio_,
+ {{0xb4d6001b,0x442488ae,0x27e05195,0x2fcb00e5}}, // [4380] िन्_, _rhm_, rhin_, _docg_,
+ {{0x27e05196,0x270c8104,0x44248b99,0x7bc602a5}}, // shin_, ớng_, _shm_, ljku,
+ {{0xe73a0698,0x2d9a3d9b,0x4424d197,0x799c5198}}, // _мен_, repe_, _phm_, merw,
+ {{0xd36f8013,0x799c5199,0x2d9a519a,0xc58e80ff}}, // لهم_, lerw, sepe_, _nồng_,
+ {{0xa3e30fd5,0xdb040118,0xaaa7001b,0xdb008176}}, // फोट_, _baié, खिएक, _anmò,
+ {{0x799c1111,0xc3330039,0x9f468192,0x6aa40c93}}, // nerw, דוד_, _bloß_, _msif,
+ {{0x6b840253,0x3ea01384,0xc58e8129,0x22405033}}, // _ocig, lwit_, _bồng_, _luik_,
+ {{0x6bda803d,0x466a823f,0x3eadb644,0x7764023e}}, // _بورس_, ором_, fvet_, naix,
+ {{0xcebb8201,0x799c2eae,0xe2469a37,0x96349bc1}}, // _şərh_, kerw, _آخري, нниц,
+ {{0x7dcd80c8,0xc6f784d9,0xb21b0125,0x6b840118}}, // _búsq, ьных_, rtæk, _acig,
+ {{0xdb0088f1,0x05770039,0x75e0519b,0x2b490118}}, // _mamí, וגים_, _közb, _pfac_,
+ {{0x69de306a,0x22401699,0x9f9c026b,0x444401b4}}, // _alpe, _buik_, _bíí_, iq_,
+ {{0xf7d7810f,0x5187395b,0xfd6500ff,0x753c0035}}, // _קודש_, мува, _chuỗ, _ogrz,
+ {{0xa3ab8df4,0xe7ea85b3,0x20148063,0xddc9976d}}, // _कोई_, जोरा_, ścią_, preš,
+ {{0x6aa40234,0xd6fb0264,0x86379101,0x874c01bc}}, // _esif, _আরো_, _קרוב_, _ọdịg,
+ {{0x5f940e97,0xdc2b003d,0x69de519c,0x6fa9016f}}, // кист, _بسته_, _elpe, _चोखं,
+ {{0x644e0341,0xee3f0028,0x444404b7,0x799c519d}}, // [4390] _atbi, _quý_, eq_, berw,
+ {{0x645c0114,0x6b9d01ba,0x69d88bfd,0x39472795}}, // _bwri, lesg, jkve, _üns_,
+ {{0x7985010c,0x7764107f,0x61f503bf,0x7bcd011f}}, // _ichw, baix, mizl, _koau,
+ {{0x7764519e,0xe8df801c,0x64410036,0x6b9d519f}}, // caix, _niềm_, _élim, nesg,
+ {{0xdb00a670,0x3d0f05b3,0x444401b4,0xc58e80ff}}, // _famí, _तुरे_, aq_, _rồng_,
+ {{0x7ff58986,0x6fd584e8,0x92a9009a,0x27ff0796}}, // کستا, _zách, _załó, čunu_,
+ {{0x5fd305e8,0x444421bf,0x2b40811a,0x2618064a}}, // _सवाल, cq_, žica_, _बीटी_,
+ {{0x01380bea,0xe3ca062f,0x799c18e3,0x6d4aa4cf}}, // ורות_, _mañá_, zerw, _affa,
+ {{0xf390001c,0x63bb9afe,0xcdf58d15,0xddcd0162}}, // _bảng_, _inun, ечны, craţ,
+ {{0x7bdf51a0,0x644805db,0x2d8136f1,0x63a9d0fc}}, // _alqu, _édic, nghe_, _haen,
+ {{0x3ead8353,0x63a99eb3,0x799c1457,0xf7709ab3}}, // svet_, _kaen, verw, _چاه_,
+ {{0xff53845b,0x6d4ad1a1,0x1ac680ab,0x63a992bf}}, // _شخص_, _effa, _শেয়া, _jaen,
+ {{0x799c51a2,0x63a9d1a3,0x45d5a098,0x2bc793f5}}, // terw, _maen, _подс, _रचना,
+ {{0x63a98074,0x7bdf24a3,0x25a78144,0x2be203b7}}, // _laen, _elqu, _uanl_, _पतवा,
+ {{0x799c01d3,0xdb00d1a4,0x63bbd1a5,0x98aa080a}}, // rerw, _ramí, _onun, tabı_,
+ {{0x7dd2013c,0x27ff97ee,0x07a30a90,0x799c51a6}}, // _læst, nnun_, ласн, serw,
+ {{0xd707490d,0x3f8051a7,0xdee327cb,0xe810950e}}, // [43a0] енте_, rgiu_, готи, ावता_,
+ {{0x7dd21103,0x9f5d819d,0xe61734c4,0xf0a58129}}, // _næst, _akwü_, еду_, _đàng_,
+ {{0x6fd58216,0xeb9a9d7b,0x5ec80264,0x9f9503a6}}, // _tách, зие_, _রেখে, _såå_,
+ {{0x6fd5c02b,0x63a99193,0x656543cd,0x444451a8}}, // _fáci, _caen, aahh, rq_,
+ {{0xa3e690c5,0x7985031d,0xdbc70074,0x63a9cc2b}}, // योग_, _ychw, _tööl, _daen,
+ {{0xa8a691b3,0x644501c0,0x59c3064a,0x7bd98133}}, // ерик, bqhi, शायर, akwu,
+ {{0x660303c7,0x644e00f2,0xde03143b,0x63a9b9cb}}, // _спра, _utbi, _спри, _faen,
+ {{0xb4be142d,0x2000443e,0x69d8d1a9,0x61f500fe}}, // ेही_, nnii_, skve, zizl,
+ {{0x25aa0b67,0xd5b081a8,0x68050196,0x7dd251aa}}, // _kabl_, لفة_, nėda, _fæst,
+ {{0x7cca8086,0x7dd2013c,0x63a9d1ab,0x71eaaf7b}}, // bərl, _gæst, _zaen, _عفاف_,
+ {{0x8afd801b,0xe8df80ff,0x67fb0198,0x63a98f84}}, // _veře, _tiềm_, näjä, _yaen,
+ {{0x7985034a,0x6b82d1ac,0xab5d81b9,0x982b8061}}, // _schw, ngog, _pożi, _فتنہ_,
+ {{0x6b9d51ad,0xf39000ff,0x70c901a2,0xdb1b87b6}}, // resg, _tảng_, रह्ल, _anuê,
+ {{0x6d4ad025,0xdb040aae,0xd5b881a9,0xddcd0084}}, // _uffa, _anió, rmās_, kraš,
+ {{0x2d9ed1ae,0x3161026c,0x9f9c046d,0xc60c81cb}}, // mete_, _vehz_, _bíà_, सकीय_,
+ {{0x2d9ebada,0x61f5080a,0xe8df801c,0x656500dd}}, // lete_, sizl, _hiểm_, wahh,
+ {{0x442951af,0xe8df8028,0xb8c806b7,0x6fd83502}}, // [43b0] _iha_, _kiểm_, _खै_, _mích,
+ {{0x26c7005c,0x63a9d1b0,0x3954001b,0xaa640098}}, // ćno_, _saen, časí_, _стък,
+ {{0xf1bf8028,0x6fd800f7,0x63a9d1b1,0x2d813648}}, // _đá_, _oích, _paen, rghe_,
+ {{0xe8d6004c,0x2d9ed1b2,0x44290197,0x6fd5a620}}, // _יותר_, hete_, _jha_, _táci,
+ {{0xfaa592b2,0x442927e4,0x63a982f1,0xa3b686a7}}, // тало, _mha_, _vaen, जाज_,
+ {{0x4429003d,0x63a9d1b3,0x4b3800be,0xe9d881e2}}, // _lha_, _waen, ׂראל_, нкі_,
+ {{0x2d9e8b24,0x442905b0,0x63a9d1b4,0x628bc12d}}, // dete_, _oha_, _taen, stgo,
+ {{0x6fdc83d3,0x44292b74,0x7e698074,0x63bbd1b5}}, // _déco, _nha_, tsep, _unun,
+ {{0x6fdc9313,0x2d9ed1b6,0x7c29bde5,0xdee58fc8}}, // _técn, fete_, _iher, топи,
+ {{0x442951b7,0x7e69c3d6,0x6fdc8866,0x7c3b8362}}, // _aha_, rsep, _féco, _hiur,
+ {{0x44290782,0x201200a4,0x3ea680eb,0x7c298c53}}, // _bha_, yoyi_, _esot_, _kher,
+ {{0x442951b8,0x3a3a008e,0x443b00b9,0xe73f00ff}}, // _cha_, _sipp_, _ciq_, _ngõ_,
+ {{0x442951b9,0x2d9ed1ba,0x76428326,0xaca3019d}}, // _dha_, bete_, _guoy, faịd,
+ {{0x2d9e803e,0xdb0086a5,0x7c3b8362,0x2fcfd1bb}}, // cete_, _jamá, _liur, _hogg_,
+ {{0x7c29d1bc,0x98b800eb,0xdb00bb7a,0xfce58d70}}, // _oher, mbrī_, _mamá, _шоко,
+ {{0x64438012,0x44290870,0x20000087,0x65638257}}, // _iuni, _gha_, unii_, _henh,
+ {{0xee3a0676,0xa3d583bb,0x6443bd34,0x36670098}}, // [43c0] дна_, ाउन_, _huni, като_,
+ {{0x7a7b010f,0x67fb0364,0x2fcf835f,0x7c2980b4}}, // _קריס, täjä, _logg_, _aher,
+ {{0x7c3b8d12,0x6443ac04,0x6fd80580,0xada32b3f}}, // _biur, _juni, _líci, рахл,
+ {{0x7c29d1bd,0xdb040874,0x6fdcd1be,0x6563d1bf}}, // _cher, _unió, _réco, _lenh,
+ {{0x64438b65,0x69dc0503,0x6fdc82be,0x2d9eafea}}, // _luni, okre, _décl, yete_,
+ {{0x6563d1c0,0x7c2993d2,0xddcd02d7,0x2d9e01d0}}, // _nenh, _eher, praš, _úte_,
+ {{0x6443d1c1,0x2ca70366,0xdb0081a8,0x75e002d0}}, // _nuni, ændt_, _damá, _göza,
+ {{0x7c3b8330,0x27e200b9,0xd6daa306,0x7c299b79}}, // _giur, _jlkn_, дто_, _gher,
+ {{0xfaa3b23a,0x6563b0ba,0x7d1d8085,0x644386c1}}, // _като, _benh, əssi, _auni,
+ {{0x44294c0e,0x6443d1c2,0x7c3bd1c3,0xf77f02d0}}, // _sha_, _buni, _ziur, _ilçe_,
+ {{0xd82f8012,0x442951c4,0x7d7b0039,0x6e3c1d3f}}, // _вэ_, _pha_, וניו, _mirb,
+ {{0x6443d1c5,0x40ab8065,0x65638cfa,0xdb04002a}}, // _duni, _زخمی_, _eenh, _aniñ,
+ {{0xa3c9113d,0x80d000ab,0x44292419,0x6ab60428}}, // लॉग_, _হেল্, _vha_, _cryf,
+ {{0x442951c6,0xaca40133,0xc3338039,0xdb008118}}, // _wha_, _ahọp, יור_, _xamá,
+ {{0x44293b94,0x6568a5f3,0x6443d1c7,0x629a0087}}, // _tha_, madh, _guni, ătoa,
+ {{0x6568803d,0x3f849295,0xaca40870,0xd7c5816f}}, // ladh, ngmu_, _chọp, वायच,
+ {{0x6e3c03bf,0x6ab621f5,0x7c29808e,0x6443b2b5}}, // [43d0] _birb, _gryf, _rher, _zuni,
+ {{0xf41f464b,0x7c29d1c8,0x3f730104,0x69dc1230}}, // llä_, _sher, ệu_, ckre,
+ {{0x6e3c01e2,0x63ad26a6,0x61f8812b,0xdb1d0074}}, // _dirb, _kaan, divl, rksõ,
+ {{0x2d87009a,0x63ad10b5,0x6568d1c9,0x6e3c0706}}, // żne_, _jaan, hadh, _eirb,
+ {{0x7c3b809f,0xe3b0803d,0xdb0081d0,0x201900e4}}, // _viur, ترل_, _pamá, _iksi_,
+ {{0x7c298051,0x63ad51ca,0x7d1c0357,0x6fd59f5c}}, // _wher, _laan, _fyrs, _láct,
+ {{0x7c29804c,0x3da40698,0x2edb801b,0x2610016f}}, // _ther, _тряб, यन्त, तकरी_,
+ {{0x6563d1c0,0x63ad51cb,0xfe79801b,0x6443d1cc}}, // _senh, _naan, trů_, _runi,
+ {{0x6568822e,0xee398698,0x7e6d51cd,0x6443d1ce}}, // fadh, нни_, nsap, _suni,
+ {{0x6568815b,0x7a248247,0x2b400118,0x6fd8041c}}, // gadh, pòta, _sgic_, _víci,
+ {{0x63ad51cf,0x656383a7,0xa3df016f,0x66d9846d}}, // _baan, _venh, णसं_, _bùkú,
+ {{0xf1a90039,0x63ad0079,0x5bde064a,0x628f51d0}}, // _מס_, _caan, मसेव, ftco,
+ {{0x656380a9,0x63ad51d1,0x65688079,0x92f6102a}}, // _tenh, _daan, badh, учні,
+ {{0x201907c7,0x6443bc72,0x6fd5841c,0x7bdd51d2}}, // _aksi_, _tuni, _vácu, aksu,
+ {{0xb21b006a,0xe0d500e8,0x27e20748,0x63ad01b4}}, // kræf, ають, _plkn_, _faan,
+ {{0x63ad51d1,0x61f882a5,0x69dc44ef,0xdce701d0}}, // _gaan, zivl, skre, ůměr,
+ {{0x7e6d16fb,0x857a028b,0x628f0bfd,0x25a10192}}, // [43e0] gsap, есет_, ctco, fehl_,
+ {{0xe058003d,0x2d85d1d3,0x80b38105,0xcf2501a8}}, // _لیست_, ngle_, ंटाइ, شرقي,
+ {{0xb33b0073,0xf8a5819f,0x63ad3b0b,0xe5f680be}}, // meça, _بک_, _yaan, נזער_,
+ {{0x80a1009a,0x8afd81d0,0xb5fb01d0,0xa4f40072}}, // खबरे, _seřa, _svát, _आधीच_,
+ {{0x9f4480f1,0x61f8d1d4,0x6d5a81b4,0x7e6d0061}}, // shmë_, tivl, ybta, csap,
+ {{0x7e7bd1d5,0xf12680e8,0x9f52008b,0x6e3c0174}}, // drup, льйо, ábær_, _uirb,
+ {{0x20190009,0x61f8a944,0xf41f0198,0x7c3a80e5}}, // _yksi_, rivl, ylä_, _ètro,
+ {{0x61d1146d,0xddcb826c,0x9f49008b,0x6568d1d6}}, // _सक्ष, _ćiši, nnað_, wadh,
+ {{0x7e7b92f1,0x23af0076,0x7bc0d1d7,0x628f0a2a}}, // grup, _जोगद, _inmu, xtco,
+ {{0x7cca82bf,0x63ad51d8,0x211a80f7,0x35f49509}}, // ləri, _saan, _كتاب_, рпор,
+ {{0x63ad02b8,0x4d7b0158,0x6568c954,0x7c669ddd}}, // _paan, ערטע, radh, داخل,
+ {{0x53350758,0x201f83a7,0x63ad0df6,0xf74694d6}}, // иент, clui_, _qaan, _седо,
+ {{0x656880d7,0x2d980722,0x63ad0848,0x764605ee}}, // padh, _obre_, _vaan, _nuky,
+ {{0x63ad3512,0x7cca8085,0xe534812f,0x85568019}}, // _waan, həri, _кель, _حیدر_,
+ {{0x63ad1f61,0x6fd585e4,0x6fd8009f,0xe919004a}}, // _taan, _táct, _lícu, тові_,
+ {{0x2d980510,0xb8860187,0x69c8048d,0x76460365}}, // _abre_, _suíç, öder, _buky,
+ {{0x311b8bb8,0x7c2d0c03,0x35f516fc,0x6fc5816f}}, // [43f0] _पुनः_, _ihar, апар, वातं,
+ {{0x6b898098,0x2d8a0197,0x7c2d0706,0x6f1d136f}}, // _sceg, _ccbe_, _hhar, _vysc,
+ {{0x8d7400a0,0x7c2d51d9,0x7e6d51da,0x7cca8085}}, // صالا, _khar, ssap, fəri,
+ {{0x43959a0b,0x2d9851db,0x7c2d51dc,0x63a2d1dd}}, // радс, _ebre_, _jhar, geon,
+ {{0x27e91a29,0x7c2d4c36,0xe8df801c,0x75e00019}}, // mhan_, _mhar, _kiệm_, _közl,
+ {{0x291e80f2,0x27e90cf4,0x02a301bc,0xf67a03c8}}, // _byta_, lhan_, _gwụọ, _באשמ,
+ {{0xc0e584fa,0x7c2d0102,0x2258807a,0x7e628032}}, // ройк, _ohar, _črke_, _awop,
+ {{0x27e951de,0x201f8087,0xccf20496,0x3eb94824}}, // nhan_, ului_, _וכל_, _krst_,
+ {{0x442db5b0,0xda788098,0x6fdc8036,0xdce181a9}}, // _ihe_, _бях_, _méch, dalā,
+ {{0xdcf5084a,0x644700f6,0x67fe8029,0x443fd1df}}, // _hazı, _kuji, dīju, _hiu_,
+ {{0x7c2d09c5,0x443fd1e0,0x69c1d1e1,0xa5098ae7}}, // _bhar, _kiu_, _inle, тена_,
+ {{0x7c2d32c5,0x64470010,0x443f872a,0x6d5c9434}}, // _char, _muji, _jiu_, ðrar,
+ {{0xed4ed1e2,0x1615146d,0xe9ff8028,0x7c2d3ccf}}, // _мо_, तकार_, _luận_, _dhar,
+ {{0xd9e583b7,0xdcf51010,0x2fcd803a,0x442d8073}}, // कसित_, _lazı, ljeg_, _lhe_,
+ {{0xb33b07e0,0x7a2002af,0x7c2d0c49,0x0596803d}}, // reça, nöti, _fhar, _خانگ,
+ {{0x7c2d51e3,0x27e91341,0x2fcd8024,0x290c04b7}}, // _ghar, ghan_, njeg_, ħda_,
+
+ {{0x056612b2,0x8d660009,0x65672f9f,0x63a281e0}}, // [4400] иван, ивае, _bejh, veon,
+ {{0x75e01010,0x957c809a,0xdb0401df,0x443f8706}}, // _gözl, wiąz, _gaiá, _aiu_,
+ {{0x3eb902af,0x0b46a318,0xb97b0051,0x7663001b}}, // _erst_, анен, _בניי, _čtyř,
+ {{0x442dd1e4,0x27e951e5,0x9f060bca,0x61fc05c5}}, // _che_, chan_, _نوجو, lirl,
+ {{0x442d9674,0x443f809f,0x63a281a8,0x31c68098}}, // _dhe_, _diu_, reon, искв,
+ {{0x27e000a4,0x63a2d1e6,0xc5690065,0xed57490d}}, // lkin_, seon, _محفل_, _вор_,
+ {{0x443fb3a5,0x67fe80eb,0xb5fb03f2,0x9965804a}}, // _fiu_, zīju, _tvár, стіл,
+ {{0x27e051e7,0xd90d95e4,0x442dd1e8,0x443fd1e9}}, // nkin_, رین_, _ghe_, _giu_,
+ {{0x28a68b9f,0x61fc0059,0x41aa82d3,0x27e051ea}}, // _कैरि, kirl, _иван_, ikin_,
+ {{0x7c22d1eb,0x7c2d51ec,0xd9439537,0x6447008e}}, // llor, _shar, _дефи, _yuji,
+ {{0xdcf50afe,0x27e051ed,0xc8ca815b,0x61fc0965}}, // _yazı, kkin_, _جوان_, dirl,
+ {{0x75e00459,0x67fe8029,0x6440a1c7,0xdb040118}}, // _sözl, tīju, _jimi, _saiá,
+ {{0x64408a0d,0x27e00267,0x61fc51ee,0x2909807e}}, // _mimi, dkin_, firl, şaat_,
+ {{0x3d0f01fe,0xd9f8d1ef,0x27e00102,0x661c0267}}, // _तुझे_, ्चित_, ekin_, _nkrk,
+ {{0x27e90219,0x98480021,0x6c848013,0x7c2d14f2}}, // than_, ията_, _العم, _thar,
+ {{0x6440d1f0,0x7c2d51f1,0x27e042f3,0x645a90b6}}, // _nimi, _uhar, gkin_, mpti,
+ {{0x321821d5,0x443fd1f2,0xb33b03a7,0x06cd00ab}}, // [4410] bory_, _riu_, meço, _রেজি,
+ {{0x6fdcc6fc,0x442d8051,0x27e951f3,0x61fc4565}}, // _déci, _she_, shan_, cirl,
+ {{0xf8b10288,0x443f8098,0x33180117,0xe9ff801c}}, // _عکس_, _piu_, _مزید_, _quận_,
+ {{0x1b1080c8,0x98a10028,0x6447244c,0x442d822c}}, // ়েছে_, _nghĩ_, _vuji, _qhe_,
+ {{0x443fa7fb,0x6fd805dc,0xfaa78c40,0xe2998087}}, // _viu_, _víct, ршен, уал_,
+ {{0xa2580051,0x644751f4,0x22490359,0x27e6d1f5}}, // סבוק_, _tuji, _muak_, _alon_,
+ {{0x442dd1f6,0x78ba84c3,0x6ce4035f,0x22492914}}, // _the_, _crtv, _діте, _luak_,
+ {{0x61fc0201,0x32180831,0x6440d1f7,0x3f8951f8}}, // zirl, zory_, _gimi, ngau_,
+ {{0xe6199073,0x61fc08c5,0xa159818b,0xfe7181a8}}, // лди_, yirl, _базу_, أدب_,
+ {{0x3f538032,0x27e0011e,0x6440922e,0x3f7780ff}}, // ṣu_, zkin_, _zimi, ểu_,
+ {{0x3ea9010b,0x3218026f,0x6440cd3d,0x80d700ab}}, // hwat_, vory_, _yimi, _ডেস্,
+ {{0x3ea9330d,0x22492914,0x98648061,0x644082ed}}, // kwat_, _buak_, _بیٹے_, _ximi,
+ {{0x78ba82a5,0x61fc51f9,0x13a8003d,0x224900ee}}, // _zrtv, tirl, هنگی_, _cuak_,
+ {{0x6d43809c,0x6fdc80e7,0x765b95d7,0xdddb81b9}}, // _ugna, _réci, mpuy, ntuż,
+ {{0x27e00009,0x61fc51fa,0x25a59c6d,0x6b9ba396}}, // tkin_, rirl, mell_, _obug,
+ {{0x25a5d1fb,0x3ebf0390,0x61fc099b,0x037a0180}}, // lell_, _šuta_, sirl, _صحبت_,
+ {{0x27e051fc,0x62800065,0x7bc4020d,0x63a63286}}, // [4420] rkin_, ámod, _iniu, mekn,
+ {{0x25a5a906,0x64408ca9,0x6b9b860f,0x628622a3}}, // nell_, _simi, _abug, muko,
+ {{0x6440d1fd,0xc7a60785,0x628651fe,0x7bd602d0}}, // _pimi, _линк, luko, _koyu,
+ {{0x25a581ba,0x28bd83b6,0x6abbd1ff,0x3ea902d5}}, // hell_, ्मनि, _bruf, bwat_,
+ {{0x7c228be3,0x64409e72,0x62865200,0x9f91807b}}, // rlor, _vimi, nuko, _ráð_,
+ {{0x27e6990f,0xdb1b8118,0x6b9bbd7a,0xd46a08ca}}, // _slon_, _couñ, _ebug, _محرم_,
+ {{0x25a5874a,0x6286272f,0x7c22c793,0x27e68242}}, // dell_, huko, plor, _plon_,
+ {{0xa8570051,0x62865201,0x61ea81e0,0x64408049}}, // זיקה_, kuko, shfl, _uimi,
+ {{0xe9ff8028,0x25a5d202,0x69968544,0x6abb8428}}, // _quản_, fell_, _грех, _gruf,
+ {{0x25a5c5b8,0x6b8d5203,0x78bc83a7,0x2d998061}}, // gell_, _scag, _árvo, ősen_,
+ {{0xd4978009,0x7bd603bf,0x25f00ad5,0x1da989c1}}, // бря_, _boyu, _इतनी_, _कसरत,
+ {{0x224903ac,0x6286016b,0x7bd63749,0x24190a41}}, // _puak_, fuko, _coyu, ромы_,
+ {{0x25a5d204,0xb33b03a7,0x998c826c,0xa3d0815c}}, // bell_, reço, _midž_, _वचन_,
+ {{0x2d578009,0x25a5d205,0xe6778039,0x07378039}}, // _лишь_, cell_, _פתוח_, _האדם_,
+ {{0x63a6008e,0x6722808e,0x7c3f01e8,0xfc4980ff}}, // bekn, _hyoj, _èpro, _hận_,
+ {{0x63a6008e,0x672285ee,0xf7701ef5,0x3ea9288a}}, // cekn, _kyoj, ّال_, twat_,
+ {{0x6aad0010,0x9df91dc7,0x34e1001b,0x644ab0f9}}, // [4430] _usaf, анат_, पन्द, _hufi,
+ {{0xdce70063,0x644ad206,0x99671cad,0xa2c61c3b}}, // _zdję, _kufi, _утил, ामर्,
+ {{0x2127001c,0xfc498129,0x67d50162,0x656ad207}}, // _ánh_, _lận_, _дону, _mefh,
+ {{0x661a811f,0x69d7002a,0x7cca8085,0x644aa0b0}}, // hotk, _hoxe, lərs, _mufi,
+ {{0x644ab327,0xf42a0198,0x661aa551,0x3abc03de}}, // _lufi, lvää_, kotk, ָמונ,
+ {{0xd4e70d0e,0x5ce717d4,0x51878009,0x25a5823e}}, // _люби, _люба, суда, xell_,
+ {{0x25a5d208,0x644a8803,0x6286248d,0x661a84e8}}, // vell_, _nufi, zuko, dotk,
+ {{0x20091106,0x6b9b9d61,0xfc4980ff,0x40328073}}, // nnai_, _ubug, _bận_, мејс,
+ {{0x6458d209,0x25a5ac1d,0xdce382ee,0xfc49801c}}, // _atvi, tell_, _venč, _cận_,
+ {{0x7bd60059,0x661a812b,0x69d7473d,0x63a609e1}}, // _soyu, gotk, _noxe, wekn,
+ {{0x25a5d20a,0x200902f7,0x66838019,0xddc995d8}}, // rell_, knai_, _کیخل, prež,
+ {{0x25a5bbfe,0x6286520b,0x7bd60201,0x69c5520c}}, // sell_, tuko, _qoyu, _anhe,
+ {{0x8c1a8bea,0x25a5aebd,0x63a6520d,0x2b1703db}}, // שורי, pell_, rekn, _धुआँ_,
+ {{0xfce582da,0x6d47020f,0x27ffd20e,0x644a826c}}, // _моло, _ngja, liun_, _fufi,
+ {{0x61430a68,0x6286505c,0xd9431ed1,0xd5b881a9}}, // дера, suko, дери, klāj_,
+ {{0x3ea9826f,0x27ff846d,0x7bc40087,0x69c50f06}}, // _šaty_, niun_, _uniu, _enhe,
+ {{0x4420520f,0x76438a5a,0x9d2600c8,0x29f6809a}}, // [4440] _iki_, _kiny, _বলেন_, cław_,
+ {{0x6fdc957a,0x7643d210,0x7c261ff5,0x644a8326}}, // _héct, _jiny, llkr, _yufi,
+ {{0x7643abf2,0xdd7a8158,0x24758029,0xfecf80ab}}, // _miny, שטעל, nām_, িনিধ,
+ {{0x76439f60,0x661a812b,0x27ff80dd,0xeb9a8073}}, // _liny, zotk, jiun_, рид_,
+ {{0x27ff91ee,0x27edd211,0x1fb501e5,0xdc3700be}}, // diun_, dhen_, _эсэр, קאנט_,
+ {{0x7643932c,0x247580eb,0x27ed8a48,0x09e3876a}}, // _niny, kām_, ehen_, мочн,
+ {{0x24758341,0x44205212,0xe7868d9e,0xd7c5816f}}, // jām_, _oki_, оупо, वांच,
+ {{0x44205213,0x27ff810c,0x247580eb,0x644ace51}}, // _nki_, giun_, dām_, _rufi,
+ {{0xa3ce00cf,0x09c480ab,0x4426955c,0x7643ae4a}}, // रान_, ্সবা, mlo_, _biny,
+ {{0x4426a91e,0x44200065,0x44322ccd,0xfc49801c}}, // llo_, _aki_, _ahy_, _vận_,
+ {{0x6fdcd214,0x7643859c,0xa3ae816f,0x4426cb60}}, // _sécu, _diny, _कसा_, olo_,
+ {{0x27ed8943,0x44268b4c,0x27ff802e,0xfc49801c}}, // chen_, nlo_, ciun_, _tận_,
+ {{0x4420016f,0x4426d215,0x661ad216,0x62829336}}, // _dki_, ilo_, potk, nroo,
+ {{0x4426d217,0x9e658077,0x442034c2,0x247580eb}}, // hlo_, _راهن, _eki_, bām_,
+ {{0x6458835f,0x29f68035,0xd48f8a08,0x6282890d}}, // _utvi, sław_, _юр_, hroo,
+ {{0x35e08b85,0x44268088,0x187b80be,0x3166026c}}, // _पकड़, jlo_, יטלב, _đoze_,
+ {{0x4426d218,0xdce380eb,0x20095219,0x2d8c86a8}}, // [4450] dlo_, _ienā, rnai_, ogde_,
+ {{0x31352410,0x27ff9a89,0xdcbb50f6,0x4426d21a}}, // _непр, ziun_, ище_, elo_,
+ {{0x2fc68025,0x4439521b,0x321c826f,0x7a201743}}, // _onog_, oms_, dovy_, rött,
+ {{0xf53783de,0x62828901,0x0b588071,0xdb1b87b6}}, // קטאר_, froo, оршы_, _anuá,
+ {{0xa7560039,0x601304b7,0x628286a8,0x2a69008e}}, // _משרד_, għme, groo, _jwab_,
+ {{0xa96680a9,0x4426a223,0x21668c24,0x7f3a01c6}}, // пиша_, alo_, пиши_, _לערו,
+ {{0x4426d21c,0x628291fe,0x764bd21d,0x27edd21e}}, // blo_, aroo, _sugy, then_,
+ {{0x7643d21f,0x75ed8298,0x443914da,0x6282d220}}, // _siny, _múze, jms_, broo,
+ {{0x7643d221,0x27edd222,0xadc4046d,0x27ff8168}}, // _piny, rhen_, _afẹr, riun_,
+ {{0x27ed83c3,0x27ff88b3,0x24758029,0x44390110}}, // shen_, siun_, tām_, ems_,
+ {{0x44200c68,0x236d8282,0x7a2d8118,0x2a69010c}}, // _pki_, _keej_, cúta, _awab_,
+ {{0x3835a1f6,0x2b4903a8,0x247580eb,0xdce7001b}}, // _энер, _cgac_, rām_, _nejč,
+ {{0x76438870,0x236d81c5,0x67ea007b,0x247581a9}}, // _tiny, _meej_, _nýja, sām_,
+ {{0xceb29b9e,0x236d90af,0x44320039,0x985600e8}}, // _ניו_, _leej_, _why_, зташ,
+ {{0x4426d223,0xdca29056,0x31160076,0x44200b99}}, // ylo_, _защи, _दुखः_, _tki_,
+ {{0x236d81e9,0x6282d224,0xe0431d7b,0x8ca60105}}, // _neej_, yroo, енси, _टैगो,
+ {{0x4426bd3a,0x3ead9151,0xe7371adb,0x2c0b0061}}, // [4460] vlo_, nwet_, зет_, _دعوی_,
+ {{0x63b601b4,0xf8bd86ab,0x66019b40,0xd83882d6}}, // _hayn, ्माय, nilk, _apčl_,
+ {{0x63b607d9,0xddcd0353,0x89db04de,0x61fe24cf}}, // _kayn, braž, _החיי, _impl,
+ {{0x62829e87,0x205703c8,0x4426b722,0x224d80b9}}, // troo, _מיטל_, ulo_, _buek_,
+ {{0x4426957e,0x63b65099,0x66019eb1,0x224d9142}}, // rlo_, _mayn, kilk, _cuek_,
+ {{0x98b80b06,0x44269234,0x6282cab1,0x628690aa}}, // ları_, slo_, rroo, _åkom,
+ {{0xdca31878,0x6601d225,0x62828123,0x44395226}}, // _пари, dilk, sroo, yms_,
+ {{0x644e002e,0x6282c6c3,0x661e0796,0x6aa40037}}, // _iubi, proo, lopk, _opif,
+ {{0x644e5227,0x61fe009f,0x96349ad8,0x63a089c4}}, // _hubi, _ompl, мниц, _ibmn,
+ {{0x644e0c6f,0xf9c489d7,0x660182f7,0x6726021e}}, // _kubi, _تحری, gilk, _mykj,
+ {{0x69c8d228,0x236d90af,0xdcfc0214,0x6fdc82be}}, // _inde, _yeej_, _karı, _décr,
+ {{0xb21b013c,0x644e01d3,0x61fe15ad,0x661e042b}}, // græn, _mubi, _ampl, hopk,
+ {{0x44395229,0xe9ff8028,0x644e522a,0xddcd522b}}, // rms_, _quần_, _lubi, vraž,
+ {{0x4439522c,0x69da8364,0x6601b091,0x22450106}}, // sms_, _jote, cilk, ölk_,
+ {{0xddcd003a,0x644e522d,0x59f8522e,0x859b8039}}, // traž, _nubi, черя_, _השוו,
+ {{0xa3ce0778,0xe9ff8028,0x61fe4475,0x22960edd}}, // राण_, _tuần_, _empl, bæk_,
+ {{0x69c8933a,0x35b5522f,0xe9ff80ff,0x95d92748}}, // [4470] _onde, мбар, _huấn_, одат_,
+ {{0x69dad230,0x644e0f8e,0x236d8282,0x317100b9}}, // _note, _bubi, _seej_, qazz_,
+ {{0x644e01ca,0x682583a7,0xdcfc03bf,0x63a0808e}}, // _cubi, sódi, _barı, _bbmn,
+ {{0x236d822c,0x645c0174,0x22459ee5,0x3eb2062c}}, // _qeej_, _dtri, _silk_, äntä_,
+ {{0x69dab543,0xa3ce035a,0x69c8d231,0x7ae2d232}}, // _bote, रात_, _bnde, gyot,
+ {{0xed46803d,0x69da9139,0xdea4053d,0xbde5010c}}, // _لپ_, _cote, _سیمی, _dhèè,
+ {{0x644e003a,0x7c84490d,0x6601d233,0x69da83f2}}, // _gubi, _путе, vilk, _dote,
+ {{0x7e7d82b7,0x6d5883b2,0x560000ab,0x7ff5803d}}, // áspo, _afva, ্তৃক_, بستا,
+ {{0x644e3065,0x6601d234,0x76470314,0x91d00035}}, // _zubi, tilk, _kijy, दामै,
+ {{0xd7068364,0x63abae4c,0x644e5235,0x628ba2a3}}, // ьные_, legn, _yubi, mugo,
+ {{0xdcfc02bb,0x644802be,0x628bd236,0x6add809a}}, // _yarı, _édit, lugo, _मथुर,
+ {{0xd91a8159,0x6601d237,0x68e392fa,0xa3ce0af3}}, // _וועל, silk, mynd, राध_,
+ {{0x629989e5,0x0f568051,0x628b977c,0x68e3808b}}, // ntwo, _חינם_, nugo, lynd,
+ {{0x63b65238,0x22960366,0xbea2ca52,0xa2b08072}}, // _wayn, ræk_, _рашк, _आईस्,
+ {{0x32432659,0xa3ce00d4,0x628b82e8,0x80ba0035}}, // терг, राद_, hugo, ॉमें,
+ {{0xdfd200f7,0xc7b98019,0x764f0a5a,0x5d4483de}}, // خير_, ndő_, _bucy, _אָפּ_,
+ {{0x1dc909a3,0x644e0b87,0x386d0a20,0x645c5239}}, // [4480] रांत, _subi, ćera_, _stri,
+ {{0xdcfc03bf,0x644e523a,0x7ae28010,0x61fe0087}}, // _sarı, _pubi, vyot, _umpl,
+ {{0x7e760176,0x764702d6,0x7c240c2e,0xb4e81404}}, // ssyp, _dijy, _kkir, यने_,
+ {{0x656e523b,0x63abd23c,0xd90d0019,0xdcfc0085}}, // _webh, gegn, _ایف_, _qarı,
+ {{0x19588084,0x656e523d,0x245380ff,0x6684004e}}, // палы_, _tebh, _lãm_, _سیال,
+ {{0x644e268b,0x7ae29315,0x6fb4016f,0x98c69ef0}}, // _tubi, ryot, _उघडू, дсил,
+ {{0x68e3813c,0x7c24523e,0x63ab9108,0x645c523f}}, // gynd, _okir, begn, _utri,
+ {{0xdfd08013,0x628bd240,0xd7d1016f,0x5fd1016f}}, // اية_, bugo, हायच, हायल,
+ {{0x69dad241,0x6d431024,0x25b800ed,0x7c3f0037}}, // _tote, žnat, _karl_, _èpri,
+ {{0x69c8c33f,0x7c24300f,0xd49a8012,0x7bc9826f}}, // _unde, _akir, орм_, _zneu,
+ {{0x6448d242,0x442484fb,0x62805243,0x35f780f7}}, // _édis, _kkm_, ámon, بريد_,
+ {{0x4424af16,0x9f4901a8,0xa2c298b1,0xddc282d4}}, // _jkm_, thaí_, लिप्, _uvož,
+ {{0x2004973c,0x4424d244,0x6b9d5245,0x3dca0176}}, // mimi_, _mkm_, rfsg, _onbw_,
+ {{0x2004d246,0xe9ff801c,0x7c240c56,0x6abd0646}}, // limi_, _tuấn_, _ekir, rvsf,
+ {{0x26c2472e,0xa3ae8072,0x7647018f,0xdb160176}}, // _arko_, _कसं_, _rijy, _layè,
+ {{0x2d9ea8fd,0xdb00d247,0x23669320,0x27f20c0b}}, // lfte_, _jamó, jboj_, ghyn_,
+ {{0x98af02bb,0x26c2017f,0xdb00816a,0x63abd248}}, // [4490] ınız_, _crko_, _mamó, vegn,
+ {{0x2004c898,0x6eb186a7,0xdb0b8bdd,0xa2c2a4bd}}, // himi_, _अनसु, ndgå, लिन्,
+ {{0x2004d249,0x81c68264,0x7bc9bc72,0xe759004a}}, // kimi_, ৎসা_, _pneu, _нині_,
+ {{0x628bd24a,0x2004c96b,0xb5fb01a8,0x629981ec}}, // tugo, jimi_, _stád, ttwo,
+ {{0x27e90867,0x63ab8bc5,0x81d880ab,0xe94502e3}}, // mkan_, regn, াসন_, _ترکی,
+ {{0x27e929a9,0x63ab920c,0x7bc9831d,0x628bd24b}}, // lkan_, segn, _wneu, rugo,
+ {{0x54542155,0x63ab8098,0x27e90280,0x6594524c}}, // _авст, pegn, okan_, _шару,
+ {{0x27e93661,0x68e385ee,0x20048ff2,0x66050458}}, // nkan_, rynd, gimi_, lihk,
+ {{0x27e9408b,0x68e384dc,0xa3ce0526,0x200db7f0}}, // ikan_, synd, रास_, rnei_,
+ {{0x27e9045c,0xee3687ac,0x6d4ea828,0x7c2bd24d}}, // hkan_, нны_, žban, llgr,
+ {{0x27e9524e,0x64c980d4,0x20048168,0xdb00d24f}}, // kkan_, िमेश, bimi_, _famó,
+ {{0xca290051,0x66051600,0x8ca80c28,0xab5d8035}}, // _הם_, hihk, _घनघो, _poży,
+ {{0xc7d68f60,0x27e95250,0xd9e380c2,0x1ae18264}}, // _רוני_, dkan_, _औकात_, _খেতে_,
+ {{0xdb00809a,0x67e30074,0x7c2b948c,0xb8663b76}}, // _zamó, _mõju, hlgr, فارو,
+ {{0x27e90057,0xa3c186bf,0x6605010b,0x6e218369}}, // fkan_, ्ञा_, dihk, molb,
+ {{0x27e90b13,0x7c2420cf,0x628635b2,0xdb0083a8}}, // gkan_, _ukir, prko, _xamó,
+ {{0x44225251,0x63a45252,0xd7c40072,0x8d661dc7}}, // [44a0] mok_, _ibin, _लोणच, евзе,
+ {{0x44225253,0x27e9208b,0xa3ce419f,0x2004d254}}, // lok_, akan_, राव_, zimi_,
+ {{0x27e90393,0x442480dd,0x6449a590,0x225f8748}}, // bkan_, _pkm_, _biei, _atuk_,
+ {{0xe29a8ba5,0x27e904b8,0x44220298,0xd5c9090a}}, // зад_, ckan_, nok_, राउज,
+ {{0xdb00a509,0x5b2387ac,0x2004d255,0xd32382c7}}, // _ramó, льша, vimi_, льши,
+ {{0x44221066,0x69de4483,0x2a6000ee,0xdb008176}}, // hok_, _jope, _mtib_, _bamò,
+ {{0x2004d256,0x6d4e18b2,0x26c05257,0x63a404be}}, // timi_, _igba, lvio_, _obin,
+ {{0x69de5258,0x8c438bba,0x3ebf111b,0x3f920326}}, // _lope, _бесе, _šuti_, ngyu_,
+ {{0x2004d259,0x4422525a,0x7c22858f,0x62800019}}, // rimi_, dok_, moor, ámol,
+ {{0xdd908277,0x69de45c5,0x200490ad,0x63a4525b}}, // جود_, _nope, simi_, _abin,
+ {{0x6f66839d,0x4ac3035a,0x2d9eae68,0x6d4e082e}}, // _звез, शिभव, rfte_, _mgba,
+ {{0x4422525c,0x7c22b98f,0x2bc6d25d,0xcfaf80ab}}, // gok_, noor, _रोमा, কাউন,
+ {{0x81b38a49,0xa2be03dd,0x6d5c06c0,0x2bb20beb}}, // টার_, शिष्, _ofra, _जसरा,
+ {{0x7c228b3c,0x69de47ff,0x3c32027f,0xdb028e15}}, // hoor, _cope, ráva_, ndoñ,
+ {{0x27e9045c,0x4422525e,0x7c22d25f,0x69de127a}}, // tkan_, bok_, koor, _dope,
+ {{0x44220e60,0x6d4e5260,0x629a0087,0x2b498bda}}, // cok_, _agba, ător, žaci_,
+ {{0x27e9045c,0xf65316a5,0x66050359,0x7e6416fb}}, // [44b0] rkan_, ائر_, tihk, gpip,
+ {{0x27e90393,0x984b5261,0x6d5ad262,0x7bdf023e}}, // skan_, _няма_, ncta, _hoqu,
+ {{0x27e90393,0x225f838e,0x55778158,0x7c22a007}}, // pkan_, _stuk_, געבן_, foor,
+ {{0x6d5c2e4d,0x660515bd,0x7c2290b5,0x69de007a}}, // _efra, sihk, goor, _zope,
+ {{0x6449d263,0x629d5264,0x6d5c0114,0x660523be}}, // _viei, ntso, _ffra, pihk,
+ {{0x7bdf3bfe,0x44220e14,0x1e1a809a,0xdb0bbb7a}}, // _loqu, zok_, नकोष_, negé,
+ {{0x70bd85e8,0x5ba78a94,0x7c229e87,0xd3a786b5}}, // ्मेल, _праз, boor, _прап,
+ {{0x63af111b,0x6d4e811f,0x442b8129,0x7c22c3b6}}, // jecn, žbal, _óc_, coor,
+ {{0xee39867c,0x629d3c96,0x22849663,0x225f8359}}, // мни_, jtso, _бухг, _utuk_,
+ {{0x645a07a3,0x442208b3,0x628f47ab,0x63bbd265}}, // _étic, wok_, duco, _haun,
+ {{0x44225266,0x63bb8a14,0xa806017b,0x7bdf106c}}, // tok_, _kaun, lmış, _boqu,
+ {{0x63bbbb8a,0x32078e04,0x7bdf5267,0xa2be1344}}, // _jaun, liny_, _coqu, शिल्,
+ {{0x44225268,0x63bba34d,0xa806017b,0x21f402f1}}, // rok_, _maun, nmış, _lähe_,
+ {{0x44225269,0x32078e04,0x26c001e8,0x63bbd26a}}, // sok_, niny_, vvio_, _laun,
+ {{0x4422526b,0x64c5005c,0x21f401ec,0x44c10084}}, // pok_, nčić, _nähe_, _nė_,
+ {{0xb8da1094,0x63bb8cf4,0x63a40dc5,0x6d5c0d02}}, // _आई_, _naun, _ubin, _sfra,
+ {{0x539b0051,0xf7728158,0x7c228b3c,0x3207816b}}, // [44c0] _חיפו, יקן_, voor, kiny_,
+ {{0x7c2290f4,0x4d631adf,0xe737526c,0x3207826f}}, // woor, икув, теу_, jiny_,
+ {{0x32078efc,0xeb9a8e8e,0x7c22d26d,0x26c0041c}}, // diny_, дие_, toor, svio_,
+ {{0x7bdf0580,0x31780035,0x7e7b8100,0xe7279a10}}, // _xoqu, larz_, ksup, _نص_,
+ {{0x7c22a808,0x63bbd26e,0x32079075,0x8004819d}}, // roor, _daun, finy_, ịakọ_,
+ {{0x7c229e7e,0x7e7b8338,0xa3ce01a2,0xa3ae86a7}}, // soor, dsup, राँ_, _कसक_,
+ {{0xc9530039,0xfc4a001c,0x7c2286a8,0x629b8037}}, // סמה_, _hậu_, poor, _equo,
+ {{0x61df01fe,0x67f58038,0x63bb8084,0xb21b006a}}, // _नक्ष, _nájd, _gaun, træk,
+ {{0x6d4e80ce,0x7bdf526f,0x7e7b93d2,0x32078873}}, // žbam, _roqu, gsup, biny_,
+ {{0x63a2d270,0x63bb84b9,0x7bdf3666,0xdbd6046d}}, // lfon, _zaun, _soqu, _gùús,
+ {{0x7bdf2f30,0x63af5271,0xb4cc86ae,0x624b04b7}}, // _poqu, tecn, रमे_, _bżon,
+ {{0x601304b7,0xd5be81a9,0x629d0687,0xbf9b041c}}, // għml, _šāda_, ttso, _amên,
+ {{0x80c08b9f,0x61e1016d,0x63af095e,0x6825816a}}, // विवे, öllo, recn, pódr,
+ {{0x629d00ad,0x63af0503,0x7a32006a,0xdcfc02d6}}, // rtso, secn, sætn, _adrč,
+ {{0x7bdf428b,0xdb04002a,0x67f58061,0xa8060380}}, // _toqu, _baió, _fájd, zmış,
+ {{0xa3ce01a2,0x7bcd5272,0xdb04002a,0x0eb68beb}}, // राः_, _unau, _caió, _अनाड,
+ {{0x4429177c,0x63bb97ee,0xfc4a001c,0x2caa0b99}}, // [44d0] _ika_, _raun, _cậu_, _apbd_,
+ {{0x7c2f1a2e,0x2caa02f7,0xb5fb026b,0x201202ec}}, // llcr, _bpbd_, _awár, anyi_,
+ {{0x3207803e,0xb90800c8,0x63bb9fdb,0x9f9c008b}}, // viny_, _মে_, _paun, _tíð_,
+ {{0x7e62d273,0x64c5012b,0xa80607c0,0x44290176}}, // _otop, včić, tmış, _jka_,
+ {{0x53a5058c,0x3207826f,0x2009335d,0x26c68282}}, // ग्यश, tiny_, miai_, _nroo_,
+ {{0x20090a8e,0x6d4e992c,0xa8060380,0x644d5274}}, // liai_, žbaj, rmış, _liai,
+ {{0x7e62862f,0x63bb8cd5,0x3207d275,0xa3e4064a}}, // _atop, _taun, riny_, पॉड_,
+ {{0x20090a8e,0x44295276,0x7e7bd277,0x26c682f7}}, // niai_, _nka_, tsup, _broo_,
+ {{0xfc4a0028,0x7c29811e,0xa3d7123a,0x32079c18}}, // _mật_, _iker, सान_, piny_,
+ {{0x44295278,0xf99388ca,0xe616c3e1,0x64552479}}, // _aka_, _خبر_, лды_, _kuzi,
+ {{0xeb9a024f,0xd131845a,0x65753b65,0x7e7bcec3}}, // нин_, _اما_, _mezh, ssup,
+ {{0xa3ce0768,0x44290168,0x442f8118,0x65750168}}, // राइ_, _cka_, nlg_, _lezh,
+ {{0x61e1a4b6,0x6da5a40b,0xe5a59810,0xe2c70ea6}}, // _koll, _била, _били, гляд_,
+ {{0x44295279,0x61e18009,0x27eda28f,0x090500c8}}, // _eka_, _joll, lken_, _শুধু_,
+ {{0xfc4a0028,0x644d338d,0x6455032d,0x7c29d27a}}, // _bật_, _fiai, _nuzi, _oker,
+ {{0x27edd27b,0x20091450,0x7c29af0b,0x2d83527c}}, // nken_, giai_, _nker, _adje_,
+ {{0xee3a0951,0x7c26527d,0x27edd27e,0x645527a3}}, // [44e0] ена_, mokr, iken_, _auzi,
+ {{0x61e19ffb,0x6455020c,0xdd92006b,0x7c3bd27f}}, // _noll, _buzi, صوص_, _ahur,
+ {{0x27edd280,0x399b00be,0x65750609,0x67f5801b}}, // kken_, _אייד, _dezh, _záje,
+ {{0x27ed8613,0xa3d70aed,0x248601ac,0x2009335d}}, // jken_, साय_, čom_, ciai_,
+ {{0x0cb081cb,0xc6940158,0x7c3bd281,0x09e6935f}}, // _जन्म, _דאָ_, _dhur, лодн,
+ {{0x2d83030b,0x61e1914e,0x27ed8a0f,0x7c298135}}, // _gdje_, _coll, eken_, _eker,
+ {{0xee0980c8,0x64555282,0x7c264949,0x61e19d56}}, // রকাশ_, _guzi, kokr, _doll,
+ {{0xd6dad283,0xd252803d,0x6b840c2e,0xdb040118}}, // ето_, _هنر_, _ndig, _laiñ,
+ {{0x3ea05284,0x61e1d285,0xa3ce0bb8,0xb4cc9c7b}}, // ntit_, _foll, राई_, रम्_,
+ {{0x44291918,0x57e1035a,0x61e1d286,0x6b845287}}, // _ska_, _नव्ह, _goll, _adig,
+ {{0x2ed10af3,0x806696ba,0xc223803d,0xf0669a4a}}, // _सप्त, _свеж, _اکنو, _скеп,
+ {{0x764e054e,0x27ed92a1,0x3ea05288,0xab6203bf}}, // _liby, cken_, ktit_, şünd,
+ {{0xa3d705e8,0x61e18182,0x20090110,0xe3bf5289}}, // साब_, _yoll, viai_, goña_,
+ {{0x2fc03aa7,0x249c80ee,0x764e00b4,0xe80202f1}}, // edig_, _tqvm_, _niby, रोसा_,
+ {{0x657a8234,0xb21b006a,0x4426d28a,0xf55b81c6}}, // math, kræv, koo_, _אדומ,
+ {{0xb8dd113d,0x4426a1aa,0x657ab598,0x64550157}}, // _इन_, joo_, lath, _ruzi,
+ {{0x4426d28b,0x200901e2,0xfc4a0028,0x33a80698}}, // [44f0] doo_, riai_, _vật_, _също_,
+ {{0x78ba8025,0x200901e2,0x7c3bc45c,0x7c29d28c}}, // _ostv, siai_, _shur, _sker,
+ {{0x38c900d5,0x2ee92323,0xd7c4816f,0xfc4a00ff}}, // _شادی_, syaf_, वयाच, _tật_,
+ {{0x61e1d28d,0x442682a3,0x657aa388,0x200c00e7}}, // _soll, goo_, hath, édit_,
+ {{0x657aa890,0x63bf2fc7,0x61e1bf3c,0x3ea007f1}}, // kath, _maqn, _poll, ctit_,
+ {{0x17ed85e8,0x64551f34,0x2006836e,0x2d8304a8}}, // जस्व_, _tuzi, _amoi_, _udje_,
+ {{0x4426d28e,0x61e1c537,0x27edad94,0x657abd8f}}, // boo_, _voll, tken_, dath,
+ {{0x7c3bcb2e,0x3fe5a134,0x61e1b2c7,0x27edc12d}}, // _uhur, ржив, _woll, uken_,
+ {{0x27edc4bb,0xe7e68665,0x657aa78e,0x63a9d28f}}, // rken_, _ओकरा_, fath, _iben,
+ {{0x657ac8ed,0x80bb00ab,0x61e180b9,0x67ea01d0}}, // gath, _উপন্, _uoll, _výji,
+ {{0x21f40074,0xdb04002a,0xee368087,0x2fc02937}}, // _näha_, _raiñ, рнэ_, ydig_,
+ {{0x92d780ab,0xab5d8372,0xe3bf03a8,0x09aa8264}}, // ানী_, _dażg, toña_, _গোপা,
+ {{0x63a9803d,0x657a805d,0x63bf0609,0x59b9908a}}, // _mben, bath, _daqn, _इसार,
+ {{0x657ad290,0xe1ff01df,0x6fa18beb,0xb5fb016b}}, // cath, spón_, क्रू, _stán,
+ {{0xab5d8063,0x614611d0,0x7c262d68,0x412a05c2}}, // _każd, рева, pokr, тово_,
+ {{0x4426d291,0x2fc0038e,0x1f66096b,0x3ea00aa8}}, // xoo_, udig_, ркам, ttit_,
+ {{0x2fc0268d,0xed5a2028,0x3ea05292,0x4426c013}}, // [4500] rdig_, воз_, utit_, voo_,
+ {{0x3ea05293,0x63a9a1a3,0xc27b093f,0xa3d70996}}, // rtit_, _aben, _שריי, साथ_,
+ {{0x3ea019e0,0x8fa6130f,0xb5fb4f3b,0x7a2d807b}}, // stit_, _кане, _itál, nútu,
+ {{0xb5fb0019,0x03a6a481,0x657a8234,0xa87b00be}}, // _után, ридо, zath, _באטר,
+ {{0x4426d294,0xdce3801b,0x764e056c,0x657a81f6}}, // roo_, _peně, _tiby, yath,
+ {{0x442682c1,0x63a9c784,0x80aa80ab,0x645a0866}}, // soo_, _eben, কিস্, _étio,
+ {{0x44c580eb,0x218900be,0x44268198,0xdb0f2509}}, // _nē_, אָפּ, poo_, recé,
+ {{0x4fc69597,0x67f80144,0x657aa647,0x7afd50b1}}, // рска, _fíje, wath, yzst,
+ {{0x8f9b012a,0x7e6980e4,0x657ad295,0xd6d78c6e}}, // דיפי, gpep, tath, атэ_,
+ {{0x35f4a10d,0x26c08110,0x7bc0d296,0x7bc28bfd}}, // спор, _šios_, _hamu, ldou,
+ {{0x78baacc8,0xd7ef9d32,0x7d7b0496,0x6aa2d297}}, // _ustv, _ду_, כניו, ltof,
+ {{0x7bc0d298,0x0ed6000d,0x3869826c,0x657ad299}}, // _jamu, ठमाड, ćart_, sath,
+ {{0x7bc0c83c,0x6aa28d85,0x657ad29a,0x7a2d808b}}, // _mamu, ntof, path, rútv,
+ {{0x7bc09cc5,0x7a2d8177,0x657a85e7,0x44c58176}}, // _lamu, sútv, qath, _fē_,
+ {{0xfc64819f,0xf8073c7a,0x16b681a2,0xa3d71516}}, // _اختی, ачан, _अनुब, साद_,
+ {{0x7bc08763,0x74150bca,0x25aa04be,0xe802064a}}, // _namu, _دوبا, _abbl_, रोला_,
+ {{0x645889f8,0x7c2d529b,0xe7ee8665,0x65788282}}, // [4510] _huvi, _ikar, _जवना_, _kevh,
+ {{0x6458d29c,0x7bc0b481,0x63a980dd,0x6f0d80c3}}, // _kuvi, _aamu, _sben, _žack,
+ {{0x090500c8,0x7bc081d3,0x8d740e06,0xdcbb0081}}, // _শুরু_, _bamu, زالا, ъща_,
+ {{0x64588609,0xdb160144,0x97a70425,0x81b80264}}, // _muvi, _hayá, _трол, ছাঃ_,
+ {{0x7bc0d29d,0x61e5529e,0xfc4a0129,0xdb0602d0}}, // _damu, _kohl, _mập_, rekç,
+ {{0x65789dc1,0xfc4a0028,0x64588073,0xa4d50a18}}, // _nevh, _lập_, _ouvi, _логі,
+ {{0x92d780c8,0x7c2d529f,0x61e5027f,0x7bc0a316}}, // ানে_, _okar, _mohl, _famu,
+ {{0xe73994ef,0x43948009,0x7bc0d2a0,0x32558470}}, // лей_, _масс, _gamu, _гвар,
+ {{0x442db5b0,0x443fd2a1,0x6abbd2a2,0x601301b9}}, // _ike_, _ihu_, _usuf, għmi,
+ {{0x7c2d52a3,0x6e288132,0x7bc0b28b,0x7e69c8c8}}, // _akar, godb, _zamu, rpep,
+ {{0x443f8028,0x6458802e,0x7bc086b0,0xdb06072c}}, // _khu_, _cuvi, _yamu, ndkö,
+ {{0xb5fb027f,0x69c1d2a4,0xfc4a0028,0x645883a7}}, // _stál, _hale, _cập_, _duvi,
+ {{0xa3e9058c,0x442dd2a5,0x61e552a6,0xeb970098}}, // _मकर_, _mke_, _bohl, щия_,
+ {{0x7c2d0102,0x69c1a73c,0x200d8c7b,0x237d81c5}}, // _ekar, _jale, liei_, hawj_,
+ {{0x69c1d2a7,0x442dba48,0x443f8135,0x7c9580f7}}, // _male, _oke_, _ohu_, _العص,
+ {{0x2fdf8eef,0x442db5b0,0x200d8012,0x69c1d2a8}}, // njug_, _nke_, niei_, _lale,
+ {{0xed168986,0xe61a0364,0x0d860056,0x38a180f7}}, // [4520] _نہیں_, гда_, йлан, hór_,
+ {{0x442dd2a9,0x7bc090fe,0x69c1d2aa,0x443f8135}}, // _ake_, _samu, _nale, _ahu_,
+ {{0x315704de,0xa9bb007c,0x6458802a,0xe66281bc}}, // _ליין_, _גזיר, _xuvi, _mọọb,
+ {{0x6d5552ab,0x443f8b9d,0x037a8829,0x6aa2b399}}, // _egza, _chu_, احات_, ttof,
+ {{0x1db7864a,0x21788e11,0x7bc0838a,0x200d9b1f}}, // _असंत, айлы_, _vamu, diei_,
+ {{0x442d91d3,0x6aa2d2ac,0x27e04707,0xed5714bc}}, // _eke_, rtof, ljin_, _гор_,
+ {{0x6aa2d245,0x69c18ebf,0xf8c5816f,0x7bc0d2ad}}, // stof, _dale, विषय, _tamu,
+ {{0xb8d08665,0x74168c2a,0x27e02949,0x657e005d}}, // _ओह_, _اورا, njin_, laph,
+ {{0x64408364,0x69c1d2ae,0x6458d2af,0xa3d73ac3}}, // _ihmi, _fale, _suvi, सास_,
+ {{0x69c1d2b0,0xf1a98077,0x22f78039,0x628da4b2}}, // _gale, _نامه_, _הזמן_, šaoc,
+ {{0xa3bc835a,0x705684c1,0x62828079,0x11d781a8}}, // _असा_, _انصا, lsoo, _دولة_,
+ {{0x69c1d2b1,0x27e02003,0x61e552b2,0x200db296}}, // _zale, jjin_, _sohl, ciei_,
+ {{0x61e503fb,0x6578c282,0xa3ce090f,0x62829b98}}, // _pohl, _tevh, राज_, nsoo,
+ {{0x6458a824,0x6282826b,0x998301d0,0x27e0008e}}, // _tuvi, isoo, rojů_, ejin_,
+ {{0x16d10996,0xfc4a0104,0x37ab221f,0xdb160144}}, // सम्ब, _tập_, лтан_, _vayá,
+ {{0x27e00168,0x7c2d52b3,0x27e6d2b4,0x61e5529e}}, // gjin_, _ukar, _joon_, _wohl,
+ {{0x6d480a56,0x442b5275,0x63ad52b5,0x36d40aac}}, // [4530] _údaj, moc_, _iban, потр,
+ {{0x443fb6ba,0x9b950077,0xd0470201,0x442dd2b6}}, // _shu_, _الکت, _əmək, _ske_,
+ {{0x2bdd03eb,0xe7c406ce,0x442d8101,0x443f8947}}, // यापा, _लोकप, _pke_, _phu_,
+ {{0x62829c11,0xb21b006a,0x69c1b3e1,0xeb909ef7}}, // fsoo, præs, _sale, فظه_,
+ {{0x69c1d2b7,0x63ad52b8,0x8aa79628,0x6282d2b9}}, // _pale, _mban, сред, gsoo,
+ {{0x7e6d451c,0x69c1b2bc,0xda78a1f6,0xa2ba80d4}}, // mpap, _qale, сяц_, _एनर्,
+ {{0x443fd2ba,0x200d802e,0x69c38456,0x80c08cf0}}, // _thu_, tiei_, rdne, विगे,
+ {{0x1665ad6b,0x442d80e8,0x63ad01bc,0x57e98b26}}, // овим, _uke_, _nban, рдим_,
+ {{0xa3bf1d01,0x7c2bc2af,0x69c1d2bb,0x27e6bbc3}}, // ीयन_, mogr, _tale, _doon_,
+ {{0x63ad44de,0x200d8c7b,0x69c1d2bc,0xac858811}}, // _aban, siei_, _uale, огол,
+ {{0xca48a684,0x04460c8e,0xab5b52bd,0x27e6867f}}, // _الله_, женн, rdün, _foon_,
+ {{0x38858029,0x7e6d3286,0xeb9a023f,0x2bcf8054}}, // mēr_, kpap, шим_, _सोमा,
+ {{0xe72e8cde,0xce6b9cd0,0x7cff8032,0x645e8036}}, // _це_, _сред_, _báré, _épin,
+ {{0x63ad52be,0x27e680f3,0x0dcb84ae,0x644881e8}}, // _eban, _zoon_, _људи_, _èdiv,
+ {{0x27e690e4,0x2a69011b,0x9c6580d7,0xdfd21ef7}}, // _yoon_, _etab_, _بهبو, ويس_,
+ {{0x628038b5,0x6b89b79d,0xb4d5d2bf,0x63ad19a8}}, // ámos, _ndeg, समी_, _gban,
+ {{0xa3d71b7e,0x7e6d1882,0x7c2b805c,0xe57a013a}}, // [4540] सार_, gpap, dogr, иза_,
+ {{0x6440af9f,0x7bd6022c,0x0d861138,0x67f80061}}, // _shmi, _hnyu, ілен, _díja,
+ {{0x2eb68770,0xdddb82a5,0xb60780eb,0xd94640d9}}, // _अन्त, kruž, mašī, _фени,
+ {{0x926ab16c,0x2bdd0eb4,0xd36f80f7,0xdddb811f}}, // арка_, याया, نهم_, jruž,
+ {{0xdddb914d,0x7bc42e07,0x27e6d099,0x2bac364b}}, // druž, _maiu, _roon_, ट्या,
+ {{0x6282a1cf,0x6b89d2c0,0xc8c580d4,0xdce38b80}}, // rsoo, _edeg, विंट, _lenđ,
+ {{0x27e6d2c1,0x23ca01a2,0xfbca001b,0x2e2f0192}}, // _poon_, ियाद, ियाम, rüft_,
+ {{0x254e8201,0x657c52c2,0xeeaaa1f4,0x7bc402e8}}, // vəl_, _herh, аток_, _naiu,
+ {{0xdefa93cd,0x442b1100,0x645c3679,0xdde38087}}, // рый_, voc_, _huri, ârşi,
+ {{0x645c52c3,0x50d6806b,0x27e68cfa,0xd946917e}}, // _kuri, _وزار, _woon_, _дези,
+ {{0x26c9003b,0x27e6838e,0xa3d703b7,0xdb0f05e4}}, // zvao_, _toon_, साल_, recí,
+ {{0x645c52c4,0x7a3f80e7,0xab5d8372,0xb5fb128a}}, // _muri, rête, _każa, _pták,
+ {{0x2bdd000c,0x442b52c5,0x23dd016f,0x657c016d}}, // याबा, roc_, याबद, _oerh,
+ {{0x67fcd2c6,0x81d88264,0x645c03cd,0x7bd61896}}, // _déje, াসক_, _ouri, _enyu,
+ {{0x7c2bd2c7,0xba3d801b,0x957c8035,0xf65f006a}}, // yogr, _způs, ając, _træk_,
+ {{0xcb0b08cc,0x63ad52c8,0x6825809a,0x3e6086c0}}, // _вход_, _uban, wódz, _kòt_,
+ {{0x657c0393,0x645c52c9,0x81b38264,0x6ecd914f}}, // [4550] _berh, _auri, য়াত_, तियु,
+ {{0x7c2b8247,0xdce18110,0xf2d200be,0x2bac0035}}, // wogr, galė, _װעט_, ट्ठा,
+ {{0x3e608205,0x69d72194,0x7e6d2c6a,0x40352481}}, // _lòt_, _inxe, spap, _неос,
+ {{0x6b9d1839,0xf3ff0073,0x645c52ca,0x69c552cb}}, // ngsg, nhã_, _duri, _hahe,
+ {{0xa3bc85e8,0x657c1286,0x216982d3,0x69c552cc}}, // _असर_, _ferh, били_, _kahe,
+ {{0x69c752cd,0x645c52ce,0xf1cb0a27,0x61e8d2cf}}, // ndje, _furi, ायिन, _codl,
+ {{0x29032442,0x758204c0,0x645c52d0,0x69c552d1}}, // nzja_, _سینم, _guri, _mahe,
+ {{0x69c552d2,0xb5fb001b,0xe7a68a75,0x765d00ee}}, // _lahe, _stáh, क्लप, _kusy,
+ {{0x645c3a4d,0x41cb2b62,0x06099e25,0x3e6082d6}}, // _zuri, ायास, йник_, _còt_,
+ {{0x645c52d3,0x2d811849,0x765d10e1,0xc33283c8}}, // _yuri, nahe_, _musy, _טון_,
+ {{0x645c52d4,0xc3290039,0x645e8036,0x00000000}}, // _xuri, _עו_, _épil, --,
+ {{0xe6720059,0x69d72c88,0x75f302d0,0xb5fb01a8}}, // _ölçü, _anxe, _hızı, _dtái,
+ {{0x69c50a5a,0x75f30214,0x765d00ee,0xc5f58081}}, // _bahe, _kızı, _nusy, _някъ,
+ {{0xcefd8059,0x7c27007a,0x216707b6,0xa9670abe}}, // ığın_, čkra, зири_, зира_,
+ {{0x69c502af,0x31af83bf,0xf7721ddd,0xa3bcd2d5}}, // _dahe, mızı_, فاء_, _असल_,
+ {{0x69d7184f,0xb8e40aed,0x645c0314,0x26cf8b80}}, // _enxe, _एन_, _ruri, _krgo_,
+ {{0xa3df901b,0x35d0800f,0x645c52d6,0x44200a2c}}, // [4560] तान_, _थोड़, _suri, _iji_,
+ {{0x62860025,0x2c6183b0,0x31af85c5,0x3c3205b9}}, // msko, _kód_, nızı_, rávu_,
+ {{0x657c105d,0xd7bd853e,0x6fbd8b86,0xc1730051}}, // _verh, ्याच, ्यां, _אחת_,
+ {{0x2c61b38d,0xe363c7d4,0x26cf8118,0x5f070035}}, // _mód_, екци, _orgo_, _सेक्_,
+ {{0x6286007d,0x61e8a842,0x657c0855,0x44200010}}, // nsko, _podl, _terh, _mji_,
+ {{0x20120201,0x26c252d7,0x628652d8,0xb5fb016a}}, // liyi_, _isko_, isko, _cuád,
+ {{0x442052d9,0x645c0006,0x6286026c,0x777d0069}}, // _oji_, _uuri, hsko, _yesx,
+ {{0x442052da,0xab66981d,0x201252db,0x764380dd}}, // _nji_, _хвал, niyi_, _ahny,
+ {{0x62860025,0xb5fb00f7,0xfaa4804e,0x290301b9}}, // jsko, _stái, _تجزی, zzja_,
+ {{0x6286003b,0x720680d5,0x442052dc,0xa3da81ce}}, // dsko, _عوام, _aji_, ़ाव_,
+ {{0x43950972,0x2c6180f7,0xbebb0168,0x645652dd}}, // манс, _cód_, _nxën, _biyi,
+ {{0x64441009,0x67228069,0x6b82d2de,0x645605ee}}, // _chii, _txoj, naog, _ciyi,
+ {{0x62861309,0x644402a3,0x645652df,0x20120201}}, // gsko, _dhii, _diyi, diyi_,
+ {{0x4420082e,0x225986c0,0x6f04009a,0x44320c56}}, // _eji_, _èske_, dzic, _eky_,
+ {{0x69c50665,0xa3bc835a,0x26c2011e,0xa41c9a3b}}, // _vahe, _असं_, _asko_, पचाप_,
+ {{0x75378051,0x999e8110,0x2d9ea382,0x64561d19}}, // _מאוד_, _kitų_, lgte_, _giyi,
+ {{0x69c52a1f,0x656552e0,0x6b8d52e1,0xdb0880e7}}, // [4570] _tahe, tchh, _idag, _élèv,
+ {{0x443913a0,0x2d9eae9d,0xddc40289,0x317e81ec}}, // lls_, ngte_, tpiš, _netz_,
+ {{0x645782ba,0x76553a9c,0x999e8110,0x271e80ff}}, // _éxit, _vizy, _litų_, ịnh_,
+ {{0x7a32013c,0xa3d3086a,0x44cc800d,0x20000087}}, // sætt, _होम_, _mě_, chii_,
+ {{0x443952e2,0xa3df93f5,0xddc4025b,0x2d8701ca}}, // ils_, ताब_, spiš, ónes_,
+ {{0xf77f0201,0x44391705,0x75f30085,0x3ea93ac8}}, // _heç_, hls_, _qızı, mtat_,
+ {{0x63b9809a,0xd90f8065,0x2fc682d5,0x44cc81d0}}, // pewn, دیا_, _baog_, _ně_,
+ {{0x2fc91384,0xa009990c,0x6b8d52e3,0x443952e4}}, // ndag_, تقبل_, _ndag, jls_,
+ {{0x3ea952e5,0xa3da809a,0xdcf88115,0x44391670}}, // ntat_, ़ार_, _devč, dls_,
+ {{0x644452e6,0xee368084,0x20124f89,0x645652e7}}, // _shii, мны_, ziyi_, _siyi,
+ {{0x6f04047f,0x7983d2e8,0x39408326,0x64562168}}, // zzic, manw, ƙisa_, _piyi,
+ {{0x442f805c,0x6286012b,0x2fc90a0f,0x3ea952e9}}, // nog_, tsko, jdag_, ktat_,
+ {{0xee2e97a1,0x3ea900f1,0xe5a58d70,0x6fda80d4}}, // _ан_, jtat_, _жили, भारं,
+ {{0x42550396,0x442fd11e,0x7d288019,0xd2520019}}, // етст, hog_, épsé, ئنس_,
+ {{0x442f803a,0xd9ff05b3,0x628603ab,0x645652ea}}, // kog_, _उतरत_, ssko, _tiyi,
+ {{0x6286003b,0x2fc92692,0x942605e9,0xdb160020}}, // psko, gdag_, _імпе, _mayú,
+ {{0xe7370364,0x442f831d,0xf41380c8,0x79838435}}, // [4580] дет_, dog_, িকার_, kanw,
+ {{0x6f0452eb,0x200002a3,0x201252ec,0xfbc686a7}}, // rzic, shii_, siyi_, _रोजम,
+ {{0xdce18029,0xc486002e,0x6289826c,0xa3cf8424}}, // dalī, флек, _aveo, _वोह_,
+ {{0x442f8025,0xb4ae000f,0xab5d809a,0x7c650875}}, // gog_, _कही_, _ważn, ءالل,
+ {{0x3ea913df,0xaaba80b7,0x26c20ca1,0x2d9e8366}}, // ctat_, تدار_, _usko_, ygte_,
+ {{0xb5fb026f,0x7983d2ed,0xeb9f0257,0xbbca11be}}, // _stáv, ganw, niør_, ियुक,
+ {{0x442fbcef,0xada352ee,0x6da31860,0xe5a352ef}}, // bog_, _шарл, _шира, _шири,
+ {{0x442f80d7,0x232a942c,0xab5b0380,0xab2ad2f0}}, // cog_, _доби_, rdük, _доба_,
+ {{0x2bdd1880,0x79838870,0xfbdd1869,0x9f4902d0}}, // यासा, banw, यासम, rkaç_,
+ {{0x4439022b,0x67fcd2f1,0xdd908b8c,0xdb0f1243}}, // wls_, _déja, دود_, lecç,
+ {{0x5597010f,0x2fc901b0,0xdb0b9e7c,0x6b808135}}, // ודיע_, ydag_, regå, _kemg,
+ {{0x69caae6c,0x225f8573,0x63a40362,0x3ea91e1e}}, // ldfe, _yuuk_, _ccin, ytat_,
+ {{0xa3df853e,0x81b380c8,0xf7738051,0x65949b78}}, // तात_, য়ার_, _סקס_, _залу,
+ {{0x3f84bd8b,0x69c8d2f2,0xfd0f80d5,0x6d5c0077}}, // lamu_, _jade, اجی_, _ngra,
+ {{0x1958076a,0xa934054c,0xcec88028,0x442fd2f3}}, // дары_, верш, _hộ_, yog_,
+ {{0x765880f6,0x3ea952f4,0x3f84d2f5,0x69da833e}}, // _hivy, ttat_, namu_, _lnte,
+ {{0x442f803b,0x2fc91412,0x69dad2f6,0x63bd35aa}}, // [4590] vog_, rdag_, _onte, mesn,
+ {{0x3ea952f7,0x69c89989,0xa3d32e2b,0x629d52f8}}, // rtat_, _nade, _होत_, muso,
+ {{0x3ea91602,0x3f849f62,0x628982a5,0x629d0461}}, // stat_, kamu_, _sveo, luso,
+ {{0x69da84f0,0x3ea952f9,0xf41f0009,0x60c552fa}}, // _ante, ptat_, nnä_, _ishm,
+ {{0x3f84a479,0x3e644424,0x644b8502,0xfc4a00ff}}, // damu_, _göt_, rmgi, _cậy_,
+ {{0x442fd2fb,0xa2d0064a,0xfc4a001c,0xe29c0039}}, // sog_, दित्, _dậy_, קשור,
+ {{0x205583bd,0x28c2946d,0x69c8d2fc,0x236085f5}}, // нтир, _वैदि, _dade, žija_,
+ {{0xcec88104,0xa2ba8f12,0x63bd02fd,0x4439d2fd}}, // _bộ_, _शैक्, jesn, _ós_,
+ {{0xef198c8e,0x7bdbd2fe,0x63bd12b9,0x15f31b7e}}, // ями_, _inuu, desn, _आकार_,
+ {{0x69c8d2ff,0x629d5300,0x645a023e,0x6b8081a9}}, // _gade, duso, _ètic, _zemg,
+ {{0x3f84b0a0,0x03a61617,0xc984817c,0xfc4a027d}}, // bamu_, нино, _шути, _zậy_,
+ {{0x9f4b0003,0x69c88419,0xc4d200be,0x629d106c}}, // _você_, _zade, אגט_, fuso,
+ {{0x765881c0,0x69da8286,0x539a81c6,0x501a83de}}, // _fivy, _ynte, הירו, _צונו,
+ {{0x80cc016f,0x6da59ddf,0xa3e4064a,0xe5a58b26}}, // हिले, _чика, पॉज_, _чики,
+ {{0x60264cfc,0xb4ae0b85,0x63bd0904,0xd8260abe}}, // едва, _कहे_, besn, едви,
+ {{0x6d43803a,0x6d5c5301,0x63a45302,0x2d85d303}}, // _izna, _sgra, _ucin, lale_,
+ {{0x7c242d10,0xa3bf0072,0x2bdd2b12,0x38550081}}, // [45a0] _ijir, ीयर_, यारा, търс,
+ {{0x2d85867b,0xee370ff0,0x3f84d304,0x2bac03dd}}, // nale_, ену_, zamu_, ट्रा,
+ {{0x6d5c0353,0xdb060074,0x272180ff,0xdb0f0187}}, // _vgra, hekü, ỳnh_, tecç,
+ {{0x03269b47,0x69c8d305,0x2d85d306,0xb5fb001b}}, // нден, _sade, hale_, _stát,
+ {{0x69c8d307,0xc7b30039,0xba3b023e,0xdb0f041c}}, // _pade, _גבר_, buïd, recç,
+ {{0x6d5c003b,0xfc4a0028,0x2d8586ae,0xaefb0032}}, // _ugra, _vậy_, jale_, _afùw,
+ {{0x2d85d308,0x69c89384,0x3f84828d,0x15431073}}, // dale_, _vade, tamu_, _берм,
+ {{0x7c245309,0x3f820042,0x69c89579,0xf65f0366}}, // _njir, _jeku_, _wade, _træt_,
+ {{0x63bd246f,0x3f84d30a,0x2417812a,0xfe6f84c0}}, // vesn, ramu_, _נחמן_, یدی_,
+ {{0x69da834a,0x48e6b51e,0x3f848b3f,0x3f82530b}}, // _unte, _позв, samu_, _leku_,
+ {{0x29188006,0x29078063,0x63bd530c,0x3f848359}}, // _ära_, czna_, tesn, pamu_,
+ {{0x63a2d30d,0x3f82003a,0x629d530e,0x68f50110}}, // ngon, _neku_, tuso, vyzd,
+ {{0x1dd105e8,0x63bd4164,0x7f42836a,0x63a281e0}}, // _सफलत, resn, _uzoq, igon,
+ {{0x2d85d30f,0x63bd5310,0x7c240870,0x7d0680e7}}, // cale_, sesn, _ejir, _céré,
+ {{0x3f82085c,0x7d06c092,0x2c86846d,0x63bd5311}}, // _beku_, _déré, _dídá_, pesn,
+ {{0x7c240168,0x7e628d55,0x653b8039,0xdb1d03ed}}, // _gjir, _kuop, _מעוד, nesë,
+ {{0xa3da823c,0x6e940162,0x63a2d312,0x644f0326}}, // [45b0] ़ाई_, литу, dgon, mmci,
+ {{0x63a2b88d,0x4df4004a,0x7c3d20db,0x7d068866}}, // egon, ляєт, llsr, _géré,
+ {{0xd3719368,0xb7d90065,0x41de06ab,0xd49b2700}}, // لها_, _ہوگا_, मारस, яра_,
+ {{0x63a2d313,0x2cb80b99,0x2d85d314,0x30af80ab}}, // ggon, _dprd_, zale_, কিৎস,
+ {{0x2d85d315,0x16bc942d,0x69dd800d,0x7bcb8106}}, // yale_, ्टोब, पाली, rdgu,
+ {{0xe1f88196,0x3f820bcf,0xa3dfa724,0xb5fb041c}}, // нгі_, _zeku_, ताह_, _etár,
+ {{0x9f40008b,0x3f825316,0x99838084,0x26c690ba}}, // lkið_, _yeku_, _ųjų_, _asoo_,
+ {{0xe8c185b3,0x2d85d317,0x7981d318,0x2907d319}}, // _शनीच, wale_, _welw, rzna_,
+ {{0x32558470,0x2d83531a,0x1e8281e5,0x6d4800f7}}, // _авар, _keje_, алым, _údar,
+ {{0x7c36531b,0x2d83137a,0x388c801b,0xa3df89c1}}, // _skyr, _jeje_, běr_, ताव_,
+ {{0x2d83531c,0xeb9a1c79,0xfbdd025a,0x4e958019}}, // _meje_, мин_, यांम, رشکر,
+ {{0x2d83531d,0x0f158009,0x88c000ab,0x785c00fe}}, // _leje_, _имею, _উপলক, _očvr,
+ {{0xa3b3873c,0x27e92766,0x3f82428e,0x64498cb5}}, // ज्य_, djan_, _reku_, _mhei,
+ {{0xb82380ab,0xaa460081,0x27e90144,0x2d8581f6}}, // _পঠিত_, тегл, ejan_, qale_,
+ {{0x6d43896a,0xdcfc531e,0xb7bd8162,0x505a0a41}}, // _uzna, _perč, anţe, дшая_,
+ {{0x2fcdcc2b,0x27e9531f,0x73ec80ab,0x2b5f8129}}, // ndeg_, gjan_, _কষ্ট_, _nguc_,
+ {{0x3eadd320,0x2d8319fb,0x9f4f8722,0x3f821df0}}, // [45c0] ntet_, _beje_, _algú_, _veku_,
+ {{0x6b8401c6,0x601b0364,0x3ead82af,0x25f780d4}}, // _heig, tämä, itet_, एसटी_,
+ {{0x6449954c,0x26cf00f2,0x3f825321,0x6b840706}}, // _bhei, ågor_, _teku_, _keig,
+ {{0x6449d322,0x3eadd323,0xa3e503ca,0x6b8401e2}}, // _chei, ktet_, नाय_, _jeig,
+ {{0x64498c64,0xe7e591bc,0x67fc82be,0x2360807a}}, // _dhei, काना_, _séjo, žijo_,
+ {{0x6b845324,0xc33302f6,0x67f58019,0xb5fb0216}}, // _leig, בוד_, _máju, _huán,
+ {{0x64498068,0xfce30b88,0x3ea05325,0xa3e50fcc}}, // _fhei, _коро, luit_, नाम_,
+ {{0x6449925b,0x3ead920b,0x8c468009,0x765c00b4}}, // _ghei, ftet_, _ребе, _hiry,
+ {{0xa3df8128,0x3ea01699,0xa3dc0327,0x96348ae7}}, // तार_, nuit_, ठाई_, лниц,
+ {{0x25a7bf24,0x27e90372,0xa3da8ad5,0x20048168}}, // _ccnl_, zjan_, ़ाक_, shmi_,
+ {{0x6b8436dd,0xf1a9803d,0xd59b0039,0x659b0039}}, // _beig, _ساله_, וביל, וייק,
+ {{0xa806017b,0x7c22d326,0xd7ef80f7,0x765c479e}}, // nlığ, nnor, _وكل_, _liry,
+ {{0x51871132,0x5694a59a,0x6b841897,0xddc9d327}}, // кува, _салт, _deig, speš,
+ {{0x3ea003d3,0x7c2282af,0x5a340cb1,0x765c0a03}}, // duit_, hnor, инят, _niry,
+ {{0x27e946b8,0x925801e5,0xa80602d0,0x97a71444}}, // tjan_, тарт_, klığ, крал,
+ {{0xb5fb0693,0x23aa016f,0x28d3909b,0xf7730bbe}}, // _cuán, _कायद, थिति, شار_,
+ {{0x6449a06f,0x27e93b14,0x3ea05328,0x35b50568}}, // [45d0] _rhei, rjan_, guit_, лбар,
+ {{0x6449cb38,0x6b8402af,0x9f40008b,0xfc3f01a8}}, // _shei, _zeig, rkið_, llí_,
+ {{0xa3df923a,0x290a009a,0x2baa016f,0x23aa000d}}, // ताल_, czba_, _कामा, _कामद,
+ {{0x7c22d329,0x3ea03f91,0x200c0722,0x3eadbc95}}, // gnor, buit_, èdit_, ytet_,
+ {{0xee8413cd,0x02a78e8e,0x3ea00162,0x2d830168}}, // _высо, трем, cuit_, _teje_,
+ {{0x785c011f,0x7d1580e1,0xb6da00be,0x27ef80e8}}, // _učvr, úzsk, _אַלט, _sogn_,
+ {{0x6449c35c,0x89d681a8,0xa3ab864a,0xdb040362}}, // _thei, طوير_, _खाप_, _dbiù,
+ {{0x3ead8f06,0xb4c12539,0x44d32bea,0x79850326}}, // ttet_, ्टी_, _ić_, _lehw,
+ {{0x2a64915c,0xdbdd007b,0xf1a78f75,0xfc3f01d0}}, // _gumb_, _ráðs, _арен, dlí_,
+ {{0x3ead91e6,0x41c086b7,0x6b84532a,0x7bdf3a16}}, // rtet_, _एसएस, _seig, _anqu,
+ {{0x3eadd32b,0xed5881ac,0x6b841009,0x25a5007b}}, // stet_, teľa_, _peig, _öllu_,
+ {{0xe29a2154,0x3eadae6c,0x4c9a03c8,0x657aa3f0}}, // нам_, ptet_, עברו, abth,
+ {{0x6b8411a9,0xfbaa000d,0xa3abd32c,0xa806007e}}, // _veig, _काठम, _खान_, nlış,
+ {{0x6e928013,0x5e9280f7,0xfb168158,0x64a5b73a}}, // _الوا, _الوط, אַלט_, _бака,
+ {{0x63a987f5,0xfc3f0013,0x6b840110,0x3e69801c}}, // _ocen, blí_, _teig, _hút_,
+ {{0x3ea0532d,0x765c0a07,0xfc3f01a8,0xe3b201a8}}, // tuit_, _siry, clí_, _طرب_,
+ {{0x66e300af,0xdee3067c,0x2fc0532e,0xab5d8372}}, // [45e0] бота, боти, reig_, _fażi,
+ {{0xfdfc8996,0x3ea0532f,0xb4c11344,0x63a9b88f}}, // एसएस_, ruit_, ्टू_, _acen,
+ {{0x3ea00970,0x2a648748,0xa3df8894,0xb4de06ab}}, // suit_, _sumb_, तां_, तमे_,
+ {{0x9e6600f7,0x765c1bb5,0xa3da047d,0x289b03de}}, // _ماسن, _wiry, _डोम_, _איטא,
+ {{0xa3e55330,0xa8060059,0x7c228bcb,0x3e69801c}}, // नात_, rlığ, rnor, _nút_,
+ {{0xe9188084,0xddcd18ad,0xdd921c81,0xb5fb1075}}, // колі_, rpaţ, _دوس_, _utáp,
+ {{0x66031f3e,0xdb1d0065,0xab5b080a,0x67f580e1}}, // _упра, resé, ndür, _nájs,
+ {{0xa3d3000f,0xb4d0016f,0xa3df9acd,0x3e6980ff}}, // _हों_, वटी_, ताः_, _bút_,
+ {{0x8d868071,0x2d98802a,0x3e69827d,0xc326cf05}}, // лунд, óreo_, _cút_, _смак,
+ {{0x3207800d,0xb5fb01a8,0xb21b0366,0x6f1d8174}}, // chny_, _nuál, nsæt, úsca,
+ {{0x0ce280c8,0x2369803a,0x7bcd0bb6,0xddcd026c}}, // মন্ত, žaje_, _paau, npaš,
+ {{0xac1902c7,0xdb550009,0xdcfa80c3,0x7bc281c6}}, // тому_, ивны, hatć, neou,
+ {{0xa3d61094,0xb21b006a,0xdd9280d7,0xa8378e82}}, // _सफर_, ksæt, _هوش_, _גראד_,
+ {{0xc3338051,0xb5fb06a5,0xfc3f016b,0xa7fb02f9}}, // מור_, _cuál, slí_, _miñe,
+ {{0xb21b006a,0x3a3a008e,0xa7fb0118,0x2f7201bc}}, // dsæt, _dkpp_, _liñe, _ọtọm,
+ {{0xc864091c,0xd9048117,0x7bdf1c96,0xa2c5109b}}, // стри, _ہی_, _unqu, िबद्,
+ {{0xa3e8800f,0xdd9b1a19,0x44290091,0xa7fb0388}}, // [45f0] बान_, еше_, _ija_, _niñe,
+ {{0x27338104,0x628f054a,0x27218028,0xd90489a7}}, // ợng_, msco, ẳng_, _فی_,
+ {{0x644d5331,0x628f00f7,0x27208fea,0xb4c10f0a}}, // _khai, lsco, मपुर_, ्टे_,
+ {{0x907a03c8,0xd9e30fd5,0x44291a14,0xa7fb02f9}}, // _שטרי, खावत_, _jja_, _biñe,
+ {{0x644d0068,0x661a8057,0x628f5332,0x44295333}}, // _mhai, kitk, nsco, _mja_,
+ {{0x3d19035a,0xa7fb04c3,0x4095938c,0x3e69801c}}, // _येथे_, _diñe, арит, _rút_,
+ {{0x44293af6,0xa3d601c4,0x69ad40f2,0x661ad334}}, // _oja_, _सफल_, _जानी, ditk,
+ {{0x44295335,0x7bc29c33,0xe739844f,0x660380e1}}, // _nja_, ceou, кей_, _slnk,
+ {{0x0ab88416,0x3d19016f,0x4fe68162,0x249135ca}}, // _مطلب_, _येते_, рмын_, _hvzm_,
+ {{0x442931ff,0x7c298bc5,0x0f5803c8,0xed598a9f}}, // _aja_, _hjer, ניזם_, коп_,
+ {{0x644d0af9,0xe005053f,0x9d45a8df,0x628f284c}}, // _bhai, रसाद_, ренд, esco,
+ {{0x69c39fcf,0x3f898125,0x6b8bd336,0x644d435c}}, // lene, _þau_, nagg, _chai,
+ {{0x7c29d337,0x644d4ba7,0xeb97287d,0x7c3b8010}}, // _mjer, _dhai, шия_, _mkur,
+ {{0x412a0c8e,0x44290549,0x69c3b20e,0x602181a9}}, // вого_, _eja_, nene, dēmi,
+ {{0x644d0c64,0x26cb0388,0x7c298216,0x2d8781e8}}, // _fhai, _asco_, _ojer, _iene_,
+ {{0x7c29d338,0xb8de02f1,0x69c3d339,0x644d0f58}}, // _njer, _इह_, hene, _ghai,
+ {{0x69c3d33a,0xb4de0f0f,0x2d87d33b,0x46a611a8}}, // [4600] kene, तम्_, _kene_, разв,
+ {{0x7c3bd33c,0xdbdfa320,0xe5771541,0x7c29b7a3}}, // _akur, _síðs, изу_, _ajer,
+ {{0x2d87812b,0xb21b23a6,0x399b00be,0x7c29d33d}}, // _mene_, rsæt, _בייד, _bjer,
+ {{0xa7fb0feb,0x6b8bc577,0x27ff8279,0x61e19193}}, // _piñe, gagg, jkun_, _anll,
+ {{0x69c3d33e,0xe7fb05e8,0x27ed8b67,0xa3e88beb}}, // fene, _एकता_, djen_, बाब_,
+ {{0x200002a3,0xa3d30665,0x7c3bd33f,0xa7fb05e4}}, // lkii_, _होई_, _ekur, _viñe,
+ {{0xf8069878,0x7c298b40,0xf9930039,0x661a890c}}, // ичин, _fjer, קרה_, vitk,
+ {{0x44c80459,0x200002a3,0x61e1d340,0x7c29cd5a}}, // _iş_, nkii_, _enll, _gjer,
+ {{0xa3ab9d01,0x539b8051,0x2d87d341,0xa3cf800f}}, // _खात_, _שימו, _bene_, _वोट_,
+ {{0x2d87b931,0x64428364,0x644d4a28,0xe839801b}}, // _cene_, lloi, _shai, ější,
+ {{0x2dd9093f,0x644d2039,0xd5d90158,0x4426d342}}, // אַרב, _phai, אַרש, ono_,
+ {{0x4426d343,0xef1a80c4,0x20092928,0x92d880ab}}, // nno_, _име_, xhai_, াহী_,
+ {{0x4426c37b,0x200002a3,0x5457898a,0x661ad344}}, // ino_, dkii_, יסטן_, pitk,
+ {{0xa3e50105,0x628f5345,0x4426d346,0xedd384a3}}, // नाह_, rsco, hno_, _ويند,
+ {{0x644d2039,0x04948307,0x2d8cd347,0x20095348}}, // _thai, _البح, made_, thai_,
+ {{0x4426935a,0x2d8cd349,0x6f0d07e2,0x69c38fe0}}, // jno_, lade_, tzac, zene,
+ {{0x44268e3f,0x60d301e2,0x63ad534a,0xac07004e}}, // [4610] dno_, _žemė, _ican, _مذکو,
+ {{0x2d8cd34b,0xa3e5123a,0x200901c5,0x69c3c8ef}}, // nade_, नाव_, shai_, xene,
+ {{0x6b8bd34c,0x2a6901e9,0x20000079,0x201e023e}}, // tagg, _huab_, bkii_, ètic_,
+ {{0x4439534d,0x4426d34e,0x69c3d34f,0x2d8c8d35}}, // nos_, gno_, wene, hade_,
+ {{0x7c29830b,0x2d8cd350,0xf2c78098,0x44391cef}}, // _vjer, kade_, рсен, ios_,
+ {{0x44395351,0x2d8cd0c4,0x2a6910af,0x89a8835f}}, // hos_, jade_, _muab_, иків_,
+ {{0x2d8c87e1,0x44391d69,0x7c2988cf,0x69c3d352}}, // dade_, kos_, _tjer, rene,
+ {{0x443929bb,0x442682a5,0x7c3bd353,0x7c29a486}}, // jos_, cno_, _ukur, _ujer,
+ {{0xee398abe,0x27ed86a4,0x63bb8397,0xa9c78698}}, // лни_, rjen_, _ibun, _всек,
+ {{0x2d8cbeac,0x2d8780f1,0x27ed84b1,0x7988a26f}}, // gade_, _qene_, sjen_, _gedw,
+ {{0x44395354,0x2d87d355,0x6b898074,0x27edc292}}, // fos_, _vene_, _keeg, pjen_,
+ {{0xc0b280ff,0x27ed8168,0x26c93a2f,0xdbd60032}}, // _lưới_, qjen_, kwao_, _gùìs,
+ {{0x2a6901c5,0x6b8983b2,0x63bbd356,0x1dc720d8}}, // _cuab_, _meeg, _mbun, रजात,
+ {{0x7f3a893f,0x49990364,0x2a6901e9,0x5d8580f7}}, // _גענו, ится_, _duab_, _الثل,
+ {{0x44395357,0x4426d358,0x63bb8a40,0x20005359}}, // bos_, yno_, _obun, tkii_,
+ {{0x44391313,0x6b89b630,0x6e3c0132,0xb4fa8039}}, // cos_, _neeg, _skrb, _תפקי,
+ {{0x200002c1,0x7bc6022e,0x7c399670,0x629201d6}}, // [4620] rkii_, meku, dowr, ýkoľ,
+ {{0x7bc626b0,0x4426809a,0xc0b2801c,0xbd8a003d}}, // leku, wno_, _cưới_, انان_,
+ {{0xf8a59fbe,0xe0580077,0xc0b28028,0x78be1252}}, // _تک_, _نیست_, _dưới_, _uppv,
+ {{0x4426a6f9,0x44d7802e,0x7bc60074,0x2d8c89b2}}, // uno_, _mă_, neku, zade_,
+ {{0x6b8982a3,0xa91c8038,0x2ca58f67,0x2a69022c}}, // _deeg, _veľm, huld_, _xuab_,
+ {{0x4439535a,0xa2d902a8,0x3c320a56,0xfce63314}}, // zos_, मित्, rávy_, _лого,
+ {{0x7afd0d38,0xa3e8901b,0x4439535b,0x64a3229c}}, // zyst, बाद_, yos_, _нара,
+ {{0x44392be1,0x4fc68009,0x661e2b78,0xc7c6974f}}, // xos_, сска, lipk, сски,
+ {{0x2d8cd35c,0xdb1d0019,0x7e7b96fb,0x2d8a0300}}, // tade_, tesí, gpup, _kebe_,
+ {{0x63bb85f5,0x2737001c,0xa7fb01df,0x7afd0fe6}}, // _zbun, ủng_, _miña, vyst,
+ {{0x44392a70,0x44d78c6e,0xa7fb04c3,0x2d8a0133}}, // tos_, _că_, _liña, _mebe_,
+ {{0x7afd1405,0x2d8cd35d,0x7bc6004f,0xceb40085}}, // tyst, sade_, geku, ktə_,
+ {{0xa3ab89a3,0xa7fb05a4,0x2d8cd35e,0x2a69022c}}, // _खास_, _niña, pade_, _quab_,
+ {{0x7afd48e8,0x2d9c03a7,0xdb040118,0x2bd9116e}}, // ryst, óvel_, _abió, _बोरा,
+ {{0xa159535f,0x61fa0106,0x7bc603df,0x661e02f7}}, // раву_, ötla, beku, dipk,
+ {{0x2a690282,0xceb40085,0x3ea000ec,0x7bc65360}}, // _tuab_, ftə_, frit_, ceku,
+ {{0x60250110,0x6b898074,0xf41f0198,0x798e00b4}}, // [4630] nėmi, _reeg, miä_, babw,
+ {{0x7c2d2496,0xd5674872,0x6b899bfc,0xf41f0009}}, // _hjar, стап, _seeg, liä_,
+ {{0x2d8a5361,0x386200b9,0x7c2d007b,0x69c75362}}, // _debe_, _fikr_, _kjar, meje,
+ {{0x3ea04848,0x69c70646,0xf41f0009,0x2911026c}}, // brit_, leje, niä_, mzza_,
+ {{0xdefb01bb,0x3ea007fc,0x78678061,0x6d4e81d6}}, // _сын_, crit_, _műve, ýbaj,
+ {{0x6b89d363,0x7bc62143,0x600a01ac,0x69d55364}}, // _weeg, zeku, _výme, ndze,
+ {{0xb5fb1dc1,0x2bd48076,0xe7e581fe,0xf41f0009}}, // _otáz, _ठोका, काला_, kiä_,
+ {{0x63bb9267,0xdd0e03bf,0xfaff00f1,0x2ca580f3}}, // _ubun, lışa, ncës_, vuld_,
+ {{0x44d78c6e,0x443fd365,0x442dd366,0x3f8b0b67}}, // _să_, _iku_, _ije_, _mecu_,
+ {{0xa1949285,0x7c2d059c,0x69c75367,0x69d524ad}}, // _науч, _ajar, jeje, jdze,
+ {{0x7c2d0a35,0x1ee78077,0x442d8353,0xfbc610f8}}, // _bjar, _گوشی_, _kje_, обно,
+ {{0x3f8b0052,0x44d7802e,0x2baa016f,0x7e7b9c50}}, // _necu_, _vă_, _काळा, ppup,
+ {{0x7bc6414b,0x69c70019,0x7c2d30a6,0x798e0f8e}}, // reku, feje, _djar, tabw,
+ {{0x7bc63553,0xc8ad9251,0xa3da0105,0x69c75368}}, // seku, _टमाट, _डोर_, geje,
+ {{0x443fd369,0x442dd36a,0x7bc62266,0x7c2d007b}}, // _oku_, _oje_, peku, _fjar,
+ {{0x442d922b,0xfc3207bd,0x1dac0063,0x3ea001f7}}, // _nje_, _تحت_, _चाहत, trit_,
+ {{0xe61a2cd1,0x3f8b3363,0xe7fb0665,0xc60500c8}}, // [4640] ада_, _decu_, _एकरा_, োচনা_,
+ {{0x443f859c,0x442dd36b,0xe5a31afa,0xa7fb0e15}}, // _aku_, _aje_, дифи, _viña,
+ {{0xa91c8038,0x7e63d36c,0x246581a9,0x741300d7}}, // _veľk, _minp, _tēmu_, _رویا,
+ {{0x66e61285,0xdee61d32,0xa7fb04c3,0x3ea0536d}}, // _дома, _доми, _tiña, prit_,
+ {{0x2d8a02a5,0x442dd36e,0xa3ab999e,0xf773003d}}, // _tebe_, _dje_, _खार_, _تاپ_,
+ {{0x442dd36f,0x443fd370,0x6d43817b,0xddc2a7b1}}, // _eje_, _eku_, _oyna, _stož,
+ {{0x9d43171c,0x23aa016f,0xf1b2009a,0x41b20441}}, // _жерд, _कांद, _जानन, _जानस,
+ {{0x8ca385e8,0x3b868e8e,0x442dcd5a,0xdfda8081}}, // _कमजो, _флаг, _gje_, рък_,
+ {{0xc6f787ac,0x6b828542,0xf41f0009,0x6d4399d4}}, // жных_, mbog, viä_, _ayna,
+ {{0x6e289c33,0x7e6381c0,0xa2d900d4,0x6d43ca15}}, // undb, _cinp, मिस्, _byna,
+ {{0x6d4381e6,0xf41f0009,0x64748221,0x69c75371}}, // _cyna, tiä_, ягну, veje,
+ {{0x9f49008b,0x200dd372,0x69ad0072,0x7ee681a8}}, // kkað_, chei_, _जाही, _وكان,
+ {{0x934700f7,0x69c75373,0xf41f0198,0xe51a801b}}, // تخدم, teje, riä_, _फेरि_,
+ {{0xf41f0364,0x1fbc80ab,0x720500f7,0x7e638420}}, // siä_, _অফিস, أوسم, _ginp,
+ {{0x69c75374,0x29055375,0x6d438286,0x63ab98b6}}, // reje, ála_, _gyna, yggn,
+ {{0x7c2d0458,0x533517d4,0x99830110,0x69c75376}}, // _ujar, _нейт, tojų_, seje,
+ {{0x85569125,0x656d880a,0x70c6897d,0x356b046e}}, // [4650] _دیگر_, _şahi, लबुल, арен_,
+ {{0x6440808e,0x41b206a7,0x2bb10d14,0x6b8d01a8}}, // _akmi, _जायस, _झाडा, _heag,
+ {{0x6b8d0458,0x2d810427,0x27e69210,0xdd0e22f8}}, // _keag, rbhe_, _onon_, rışa,
+ {{0x7c3d1639,0x6b8290ab,0x6458023e,0xf8e480c2}}, // nosr, gbog, _èxit, _कपड़ा_,
+ {{0x7f76012a,0x6b8d3a18,0x236982d4,0x442b5377}}, // _מערב_, _meag, žajo_, inc_,
+ {{0x3ea90057,0x7e6b81e2,0x6d58016b,0x2452016b}}, // muat_, _rugp, ývac, máme_,
+ {{0xc5f2036b,0x3ea90a26,0x7e63826b,0x47d480ab}}, // _אדם_, luat_, _rinp, থানী,
+ {{0xd3e3819f,0x443fc66e,0xa7fb01df,0x6b8d0087}}, // _تقری, _uku_, _miño, _neag,
+ {{0xb4e38a27,0x7e638091,0xa3e8923a,0xe619af92}}, // नम्_, _pinp, बार_, йди_,
+ {{0xee368009,0x443dd378,0xa3ab8fb2,0x201f8196}}, // знь_, low_, _खाँ_, siui_,
+ {{0xa7fb05b4,0x044605e9,0x2452016b,0x0ee80035}}, // _niño, зенн, háme_, _ऐप्स_,
+ {{0x7e639217,0x3ea90c8f,0x6d43828f,0x38978087}}, // _winp, kuat_, _vyna, măr_,
+ {{0xe72ed379,0x6d43809a,0xdb1d01df,0x6b8d01e4}}, // _че_, _wyna, tesá, _deag,
+ {{0x443dcc98,0x2452026f,0xbb3a80be,0xddc380eb}}, // how_, dáme_, מערי, _minū,
+ {{0xdb1d016b,0x6b8d5163,0x38978162,0x443dd37a}}, // resá, _feag, năr_, kow_,
+ {{0x6b9bb79d,0x27e6165f,0x4c9a8039,0xf1c7816b}}, // _ndug, ñon_, _השקע, _hráč_,
+ {{0xe7ec82f1,0xdfcf01a8,0x6fbd92c6,0x443d99c2}}, // [4660] झाता_, ديق_, व्यू, dow_,
+ {{0x273a801c,0x6b9b846d,0x656902d0,0x9f49008b}}, // ừng_, _adug, _şehr, rkað_,
+ {{0x50461317,0x14c8003d,0x6b8d05ee,0x7d01839c}}, // _необ, تهای_, _yeag, nyls,
+ {{0x3ea90867,0x6aa2d37b,0x68e1006a,0x27f90b80}}, // buat_, trof, ælde, _gosn_,
+ {{0x7bd6537c,0x6b82c8ee,0x3f920b17,0x26c200b9}}, // _mayu, rbog, mayu_, _upko_,
+ {{0x3f921cbc,0x4422537d,0x7bd6537e,0xceb302f6}}, // layu_, hik_, _layu, _איש_,
+ {{0xa3ac2d05,0xa3cc0327,0xa3be0b9f,0x4422537f}}, // खला_, रजा_, ीजा_, kik_,
+ {{0x6aa2d380,0x44225381,0xaca4019d,0x443d9ba9}}, // prof, jik_, _chụp, cow_,
+ {{0x6b8d4fdc,0x44225382,0xfaa3cc3b,0x6e21874d}}, // _reag, dik_, _пато, filb,
+ {{0xddcb87d9,0x3f92059e,0x7bd6076d,0x4498804a}}, // ğişt, hayu_, _aayu, овою_,
+ {{0x61e8d383,0x4422133b,0x7bd62eae,0x6b8d02f1}}, // _indl, fik_, _bayu, _peag,
+ {{0x44225384,0x1016845b,0xa7fb0216,0x61fad385}}, // gik_, _عباد, _riño, _hotl,
+ {{0x7bd65386,0x3f9206cb,0x319f81a9,0x69ca800b}}, // _dayu, dayu_, bāzē_, oefe,
+ {{0x69d8d387,0xaa64038c,0xa7fb2511,0x2452001b}}, // ndve, етск, _piño, váme_,
+ {{0x6b8d154c,0x61fa837f,0x7c2b8114,0x443d81b4}}, // _teag, _motl, yngr, yow_,
+ {{0x44225388,0xb8e52cdd,0x3ea913f0,0x7bd6385b}}, // cik_, _एह_, tuat_, _gayu,
+ {{0x47340a7c,0x45d50162,0xdb1b807b,0x386680ee}}, // [4670] ентс, моас, _nauð, _mior_,
+ {{0x443dd389,0xa3e509f2,0x2d8ed38a,0x2452016b}}, // wow_, नाक_, _jefe_, ráme_,
+ {{0x69cad38b,0x7c228b57,0xf9fc0039,0x7ae0d38c}}, // defe, fior, _להזמ, _armt,
+ {{0x7c228698,0x4ab11344,0x61e8b8c0,0x320c82f7}}, // gior, _जमाव, _andl, _aldy_,
+ {{0x3ea90722,0xe3b0936d,0xa3d512ee,0xf42a0009}}, // quat_, ارم_, _सघन_, ltää_,
+ {{0x44223550,0xe8190076,0x443dd38d,0x69cad38e}}, // zik_, नोरा_, sow_, gefe,
+ {{0x4422538f,0xf9909e13,0xf42a0009,0x7c22809a}}, // yik_, ابه_, ntää_, bior,
+ {{0x69d71045,0x7c22d390,0xddc28110,0x2d935391}}, // _laxe, cior, _ruoš, laxe_,
+ {{0x7bd600a4,0x3e72802e,0x442217ab,0x6e2195f8}}, // _rayu, _cât_, vik_, tilb,
+ {{0x7bd63368,0x2d9304c3,0x49bb8077,0x7bcbd392}}, // _sayu, naxe_, _دارد_, legu,
+ {{0x7bd60590,0x69c1ad81,0x3866851e,0x3f8480dd}}, // _payu, _ible, _fior_, bbmu_,
+ {{0x7bcbb11d,0xea01001c,0x6e21d393,0xe825803d}}, // negu, _đạt_, silb, _غذای,
+ {{0x44220cdb,0x2480803a,0xa3ab92ee,0x2d8e8915}}, // rik_, _čime_, _खाई_, _fefe_,
+ {{0x4422061b,0x7c22d394,0x7bcba21e,0xa06a16d9}}, // sik_, zior, hegu, жава_,
+ {{0x442203d2,0xd94302a4,0x69c1cb84,0x7e7d1482}}, // pik_, вери, _mble, _atsp,
+ {{0xdee60dd3,0x7bcbace3,0x7b070061,0xdb0987f1}}, // моби, jegu, örté, _oceà,
+ {{0x44205395,0x69c19234,0x69d71031,0x69ca9ccc}}, // [4680] _imi_, _oble, _faxe, yefe,
+ {{0x2d8e81df,0x2d858247,0x44205396,0x69bf850a}}, // _xefe_, nble_, _hmi_, ल्मी,
+ {{0x2d85aeaa,0x442002d5,0x61fad397,0x136aa134}}, // ible_, _kmi_, _rotl, ошли_,
+ {{0xf7679301,0x7bcbd398,0x69c1d399,0x3f8fd39a}}, // _لا_, gegu, _able, _negu_,
+ {{0x7c22d271,0xab5b0352,0x44200744,0x2d930118}}, // rior, rfüg, _mmi_, baxe_,
+ {{0x7c22d39b,0xc5d506b5,0x2d858088,0x273e026b}}, // sior, _пісь, jble_, _ẹnì_,
+ {{0x442010ab,0x69cad39c,0x386683a7,0x69d8d39d}}, // _omi_, refe, _pior_, rdve,
+ {{0x69c1810c,0xa3ab8b75,0x4420046d,0xf8b30039}}, // _eble, _खाए_, _nmi_, דשה_,
+ {{0xf65f013c,0xdbdd026b,0x3f8f86a2,0x7c208706}}, // _især_, _dáìs, _degu_, _immr,
+ {{0x4420539e,0x6282d39f,0xf42a0198,0x6444373c}}, // _ami_, mpoo, ytää_, _akii,
+ {{0xf4be80c8,0xae1d8054,0xa3d280d4,0xd5e28032}}, // েম্ব, योधन_, _वसा_, _awọ,
+ {{0x2d9304c3,0xb5fb06a5,0x69d701b4,0x69c18af8}}, // zaxe_, _juár, _saxe, _zble,
+ {{0x27f7826f,0x442053a0,0x236d007a,0xfc3f01a8}}, // žená_, _dmi_, žejo_, roí_,
+ {{0x7e7db404,0x442053a1,0xf42a0364,0x3494b3d9}}, // íspe, _emi_, ttää_, _закр,
+ {{0x44202956,0xa3de8f97,0x7e6701ed,0x7bcb9267}}, // _fmi_, तया_, _rijp, yegu,
+ {{0x23dd835a,0x7a290267,0x26c68133,0xf42a0198}}, // _नोंद, džte, _kpoo_, rtää_,
+ {{0x2d930373,0x7bcbd3a2,0xf42a0009,0x7e6700f3}}, // [4690] taxe_, vegu, stää_, _pijp,
+ {{0x7bcbd3a3,0xa2dd80c2,0x6d4753a4,0xdd9107c3}}, // wegu, पिस्, _pyja, اوا_,
+ {{0x7bcbce8a,0x2d930b6e,0x79950234,0x7d0d80fc}}, // tegu, raxe_, mazw, _ƙasi,
+ {{0x2d9304c3,0x6d4703f2,0x628480eb,0x79952647}}, // saxe_, _vyja, ģion, lazw,
+ {{0x823403f8,0x62848012,0xc2cb8077,0x6f16009a}}, // _سریا, ţion, تبال_, czyc,
+ {{0x7bcbd3a5,0xa2f98b85,0x2cac804a,0x624d8118}}, // segu, ्नौज_, kudd_, cúol,
+ {{0xab5b011c,0xd498259a,0xc8d80a27,0xa966d3a6}}, // rdüy, _орт_, डिएट, ниша_,
+ {{0x200901e2,0xdb0d002a,0xb5fb0216,0x764382d5}}, // nkai_, _acaí, _guár, _skny,
+ {{0x5a35ac38,0x9f590144,0x0e659617,0x7995308f}}, // _знат, _bosé_, нкин, kazw,
+ {{0x273e0142,0xac859b67,0x272c0104,0x442f8ec9}}, // ững_, нгол, ếng_, lng_,
+ {{0x442fd3a7,0x2d9153a8,0x3f8fd3a9,0x656d81cc}}, // ong_, _meze_, _tegu_, _şahs,
+ {{0xafdb013c,0x442fcd7b,0xfaa6014c,0x99878084}}, // _skøn, nng_, намо, ponų_,
+ {{0xe2972afb,0x442fd3aa,0xef0e92b2,0x200fd3ab}}, // ная_, ing_, _см_, _ilgi_,
+ {{0x645d53ac,0xd25208ca,0x442f81e0,0x2fcdd3ad}}, // amsi, انس_, hng_, leeg_,
+ {{0x3ead82f7,0x76418061,0x2fd91bad,0x7e6009c4}}, // luet_, moly, _basg_, _èmpi,
+ {{0xad1a8051,0x442053ae,0xdb1b802a,0x76418106}}, // _מוצר, _umi_, _abuí, loly,
+ {{0xddc601e2,0xe7370009,0x2a7f8282,0xcfd980ab}}, // [46a0] _aukš, еет_, _ntub_, থাপন,
+ {{0x9f5904c3,0x7ae4011e,0x8c96021e,0x6e4608ca}}, // _xosé_, _irit, ерді, رنام,
+ {{0x2d911dbe,0x6f160063,0xe3bf01df,0xb0af01ce}}, // _deze_, szyc, liña_, जूदग,
+ {{0x69ce53af,0xe7df8996,0x69dc32f7,0x442f9400}}, // lebe, _खोला_, ldre, gng_,
+ {{0xd12f0077,0x76419e2b,0xe3bf03a8,0x2fcd9de6}}, // _نمی_, koly, niña_, deeg_,
+ {{0x442fd3b0,0x69dc0cf7,0x69ce11e6,0x4a9b007c}}, // ang_, ndre, nebe, לינג,
+ {{0x6282a432,0x69dc2cc4,0x03a691d0,0x442f82c4}}, // spoo, idre, _пиво, bng_,
+ {{0x7ae453b1,0x69ce53b2,0xd6d080f7,0x69dc0428}}, // _orit, hebe, لقب_, hdre,
+ {{0x61eb8065,0xcf268013,0x69ce23ba,0x27fdd3b3}}, // állí, عربي, kebe, _down_,
+ {{0x4426d3b4,0xa3e88b9f,0x7641d3b5,0xba3b13fc}}, // lio_, बाक_, goly, nuït,
+ {{0xd82f814c,0x7ae40102,0x69dc226d,0x5455066a}}, // _бэ_, _arit, ddre, квот,
+ {{0x1beb10c5,0x38ba80f1,0x8cb18035,0x2cac804a}}, // टाइल_, mër_, _अमरो, rudd_,
+ {{0x7ae433e1,0x69ce53b6,0x79950a5a,0x69dc0d35}}, // _crit, febe, tazw, fdre,
+ {{0x4426d3b7,0x7ae453b8,0x69ce53b9,0x69dad3ba}}, // hio_, _drit, gebe, _jate,
+ {{0x4426abd2,0x61fe0775,0x7ae435e2,0x2fc053bb}}, // kio_, _dopl, _erit, ffig_,
+ {{0x69dad00d,0x23698639,0x67d412d7,0x20090084}}, // _late, žaji_, лосу, ukai_,
+ {{0x4426aad1,0xe3bf04c3,0x7ae42827,0x23c10054}}, // [46b0] dio_, ciña_, _grit, ष्णद,
+ {{0x69ce00a9,0xe45f00f2,0x442f831d,0x2009189e}}, // cebe, lmö_, wng_, skai_,
+ {{0x2d91003a,0x4426d3bc,0x2a7f9d29,0xdcf880eb}}, // _veze_, fio_, _stub_, _ievē,
+ {{0xf1d98e70,0x4426bf24,0x7ae40364,0xe7fb101e}}, // _योजन, gio_, _yrit, _एकटा_,
+ {{0x69daba27,0x3f8912b0,0x442f8359,0xa3b918b8}}, // _bate, mbau_, rng_, _चान_,
+ {{0x69da9b7d,0xc6920158,0x3ea953bd,0x442f86cb}}, // _cate, _דאך_, mrat_, sng_,
+ {{0x6b758fe7,0x6299b261,0x69da885e,0x27fd80ee}}, // _албу, sswo, _date, _pown_,
+ {{0x44269f90,0xf094007c,0x3f8901ec,0x3ea9023e}}, // cio_, אנק_, nbau_, orat_,
+ {{0x69dad3be,0x7bcf3724,0x5334d3bf,0x69dc2347}}, // _fate, decu, _бетт, ydre,
+ {{0x7ae4020f,0x2fcd81e9,0xe3bf01df,0x38ba8168}}, // _rrit, seeg_, viña_, bër_,
+ {{0x6d4a8355,0x7c260110,0xa2d58d1c,0x7641d3c0}}, // _cyfa, tikr, _मैत्, roly,
+ {{0x3ea93e6b,0xe29a1a1a,0x61fe20ce,0xab2a05e9}}, // krat_, мам_, _sopl, дома_,
+ {{0x69dab6ed,0x69ce53c1,0x7bdb820d,0xe784d3c2}}, // _yate, tebe, _mauu, _сухо,
+ {{0x4426d3c3,0x8c3d0459,0xe3bf04c3,0x7c262c60}}, // zio_, duğu, riña_, sikr,
+ {{0x69ce0542,0x7ae40051,0x290c026f,0x69dc48a3}}, // rebe, _writ, áda_, rdre,
+ {{0x69ce029b,0x44268b6e,0xea010028,0xa3b9000f}}, // sebe, xio_, _đất_, _चाय_,
+ {{0x61fe53c4,0xcf928158,0x7c2453c5,0x1f660a08}}, // [46c0] _topl, יטן_, _imir, ткам,
+ {{0xee372466,0x44268114,0x9f590061,0x62860123}}, // вну_, wio_, _alsó_, mpko,
+ {{0x3ea953c6,0x4426d3c7,0x7c240267,0x69dad3c8}}, // arat_, tio_, _kmir, _rate,
+ {{0xdca353c9,0x3ea953ca,0x3e76013c,0x69da9d6f}}, // раци, brat_, _tæt_, _sate,
+ {{0x69da9247,0xc7b30bea,0x7c24082e,0x386053cb}}, // _pate, _דבר_, _mmir, mmir_,
+ {{0x38ba820f,0x4426d3cc,0xa158a2d0,0x38600085}}, // tër_, sio_, налу_, lmir_,
+ {{0xc2130051,0x69dad3cd,0x7bdb8362,0xed350162}}, // שהו_, _vate, _fauu, _бэтэ,
+ {{0xc7c68dbd,0x69dab593,0xf0930039,0x7d03016d}}, // тски, _wate, בנה_, änse,
+ {{0x8d868767,0x69dad3ce,0x09aa00ab,0x626687b6}}, // кунд, _tate, খ্যা, _аваа,
+ {{0x7c2453cf,0x38ba820f,0x5fae816f,0x602786c0}}, // _amir, për_, _टाकल, _pèmè,
+ {{0x2618800c,0xa8a58b76,0x43951fab,0x7ae2a351}}, // योगी_, _مصنو, ланс, lvot,
+ {{0xa3e883bb,0xd9f08076,0x6abd119b,0x442480b9}}, // बाट_, चावत_, ttsf, _jmm_,
+ {{0x9f590698,0x6d58016b,0x7bcf0162,0x7c2402a6}}, // _così_, ývaj, recu, _dmir,
+ {{0x7c240c56,0x7bcf3847,0x3ea9246d,0x6abd04e1}}, // _emir, secu, vrat_, rtsf,
+ {{0xb4d101ab,0xef19809a,0x4424822b,0x19940c07}}, // _वने_, daży_, _omm_, рася,
+ {{0xe3bf062f,0x3ea941a6,0x3d0e8075,0x6e288289}}, // miño_, trat_, ानीं_, lidb,
+ {{0x9f55395b,0x3ea953d0,0xe3bf01df,0x7ae2811f}}, // [46d0] увач, urat_, liño_, jvot,
+ {{0x3ea953d1,0x80b880ab,0xa567004e,0x28ce02f1}}, // rrat_, _আনন্, ندان, _हैकि,
+ {{0x629d094c,0x3ea953d2,0xd371880b,0x7bdb82d5}}, // lsso, srat_, مها_, _pauu,
+ {{0x3ea953d3,0xfbdf041c,0x2d9c0118,0x7640008b}}, // prat_, ndê_, óver_, _ímyn,
+ {{0x629d21b7,0x59c00bc2,0xeabf01e8,0xe3bf0118}}, // nsso, श्वर, ntù_, hiño_,
+ {{0x26d900a9,0x2a6c80ee,0x6e2889d1,0x2fc682c4}}, // _isso_, _cidb_, jidb, _abog_,
+ {{0x04678a28,0x74f70061,0xfbab0264,0x9f4001fa}}, // _атам, _جہاز_, গ্যত, ljið_,
+ {{0x31580039,0x629d0bcb,0xe3bf2825,0xdb1d1277}}, // כיון_, ksso, diño_, besø,
+ {{0x2f548c8e,0x601580e1,0x98be8084,0x2907927a}}, // итьс, _náme, _rytą_, syna_,
+ {{0x7c24011f,0x8ca48128,0x3201026f,0x629d2f9c}}, // _smir, _किशो, _nohy_, dsso,
+ {{0x25e88d86,0xdb00d3d4,0x629d1388,0x386053d5}}, // जाजी_, _admè, esso, ymir_,
+ {{0x629d251d,0x26d91a47,0xdd4103f7,0x6e95841c}}, // fsso, _osso_, _aŋŋa, _биду,
+ {{0x2bc5835a,0x645b9a29,0x8f9a8039,0xd90f8065}}, // ळ्या, _mhui, _ניסי, _دیں_,
+ {{0x2fc6803b,0x9f5907f1,0xad2481f9,0xddc38493}}, // _zbog_, _cosí_, اریو, _dinţ,
+ {{0xf6528051,0x07a605d3,0x2fdd81e9,0xe3bf01df}}, // _הצג_, лавн, _kawg_, ciño_,
+ {{0x27ef808e,0x2fdf8122,0x629d0bcb,0x62864503}}, // _jngn_, ndug_, bsso, ppko,
+ {{0x823603f8,0x6f09d3d6,0x63a453d7,0x60158144}}, // [46e0] _مردا, nyec, _idin, _gáme,
+ {{0x644653d8,0x645b819d,0xdcfc01a1,0xa37b041c}}, // loki, _ahui, _serđ, rdõe,
+ {{0x6b9653d9,0x645b925b,0x6015826f,0x26d90081}}, // _keyg, _bhui, _záme, _esso_,
+ {{0x645b9523,0x0ecf000f,0xdb19809a,0x69de53da}}, // _chui, _सैकड, jewó, _hape,
+ {{0x645b8ad0,0x244e827f,0x69c38252,0x141781a8}}, // _dhui, nými_, ffne, دينة_,
+ {{0x200200dd,0x27e053db,0x69de4f15,0xa3e491bc}}, // _hoki_, ldin_, _jape, _भोर_,
+ {{0xa3e405b3,0x69de53dc,0xed572481,0x644653dd}}, // नया_, _mape, _бор_, koki,
+ {{0x27e053de,0xb8cc81b6,0x69de53df,0x8c469c79}}, // ndin_, _कट_, _lape, _себе,
+ {{0x26c00114,0x644606a2,0x27ef89da,0x60e08110}}, // ntio_, doki, _dngn_, _žymė,
+ {{0x63a453e0,0xe3bf29c8,0x2d9a53e1,0x60158c83}}, // _adin, tiño_, nape_, _cámb,
+ {{0x85f70158,0x6e28d3e2,0x3d0e8035,0x1dc609c2}}, // אציע_, ridb, ानें_, र्यत,
+ {{0xe3bf0feb,0x64460102,0x629d2310,0x6e288503}}, // riño_, goki, tsso, sidb,
+ {{0x27e053e3,0xe60f8077,0x34d100bc,0x2d9a009e}}, // ddin_, زشی_, _सन्द, kape_,
+ {{0x629d21b7,0xeabf047f,0x628401a8,0x2fdd81c0}}, // rsso, rtù_, _dtio, _zawg_,
+ {{0x69de0072,0x200253e4,0x2fdd81c5,0x7d7b83de}}, // _dape, _boki_, _yawg_, _אנוו,
+ {{0x6c8380f7,0x6015802a,0xf7730250,0xa2ad0072}}, // _اليم, _táme, صار_, _जिप्,
+ {{0xc05801e5,0x200253e5,0x442b53e6,0xa3cb00d4}}, // [46f0] уір_, _doki_, mic_, _रॉय_,
+ {{0x442b53e7,0x6449c0de,0x645b808c,0x60da82f1}}, // lic_, _skei, _shui, _ostm,
+ {{0x5c0785e9,0xdcf500eb,0x2002039c,0x6f1b80e5}}, // ляда, _iezī, _foki_, zzuc,
+ {{0xd5642bd9,0x69de0942,0x7bdf16dc,0x1e01816f}}, // ступ, _zape, _jaqu, _लक्ष_,
+ {{0x26c053e8,0xfaa78425,0x68e88114,0x2fdd81c0}}, // ctio_, ушен, _ardd, _rawg_,
+ {{0x442b1c33,0x7bdf00e7,0x20022944,0x7c973026}}, // hic_, _laqu, _zoki_, _مشرا,
+ {{0x645bd3e9,0x20020176,0x6ce400e8,0x2fdd81c5}}, // _thui, _yoki_, _літе, _pawg_,
+ {{0x7bdf0548,0x442b1f27,0x2fdd81c0,0xa92804e8}}, // _naqu, jic_, _qawg_, loží,
+ {{0x442b53ea,0xf1a78a29,0xcdf581bb,0xddcb80eb}}, // dic_, _брен, ачны, _augš,
+ {{0x7c2bd3eb,0x3ebfd23a,0x7ae98091,0x244e803e}}, // ligr, rtut_, _iret, vými_,
+ {{0x69de1495,0x64462a1a,0x442b0927,0x62842cb9}}, // _rape, toki, fic_, _stio,
+ {{0x69de53ec,0x8c460dbd,0x0446197b,0x442b1b9f}}, // _sape, реме, ремн, gic_,
+ {{0x7bdf0548,0x200253ed,0x63a48174,0x32181075}}, // _daqu, _roki_, óini, thry_,
+ {{0xc332804c,0x200253ee,0x3ead8bbd,0x244e9d9a}}, // קום_, _soki_, mret_, rými_,
+ {{0x69de53ef,0x2d9a05f3,0x245c816b,0x7c2b8122}}, // _vape, vape_, líme_, kigr,
+ {{0x63a453f0,0x69de0c85,0x6ca4804e,0x6d4e0690}}, // _udin, _wape, _اصول, _ryba,
+ {{0x27e053f1,0xa0671289,0xdce18182,0x18672dea}}, // [4700] rdin_, рата_, malı, рати_,
+ {{0x66038364,0xe6172d0b,0x26c050fb,0xe5a60012}}, // _jonk, аду_, rtio_, рижи,
+ {{0xeb9a93f1,0x7ae9d3f2,0x2d9a53f3,0x200253f4}}, // вие_, _aret, rape_, _toki_,
+ {{0xa2ad01c4,0x6d4e027f,0x7ae98625,0x7d03016d}}, // _जिम्, _vyba, _bret, änsa,
+ {{0x245902be,0x60180019,0x3ead806a,0x60da80b9}}, // néma_, _címe, jret_, _sstm,
+ {{0x7ae9d340,0x5bd3123a,0x245c9c18,0x3ead8f06}}, // _dret, _तस्व, díme_, dret_,
+ {{0x64a31957,0x8ccd005e,0x7ae9d3f5,0x05c6016f}}, // _мара, _सहयो, _eret, र्णब,
+ {{0x8c438676,0x2d9800f8,0x442b02e6,0x7ae9d3f6}}, // _десе, _here_, xic_, _fret,
+ {{0x2d9853f7,0x6603805d,0x7bdf16dc,0x3ead8687}}, // _kere_, _bonk, _raqu, gret_,
+ {{0x59b206a7,0xa3b901ce,0x9a6a8872,0x2d98275b}}, // _जागर, _चाह_, _شمال_, _jere_,
+ {{0x2d9853f8,0x442b1b9f,0x660380c9,0xa17b83c8}}, // _mere_, tic_, _donk, _סטאט,
+ {{0x2d981741,0x3ea053f9,0x245c816b,0x3eadd3fa}}, // _lere_, ksit_, bíme_, bret_,
+ {{0x3ead8e22,0x6603d3fb,0x799c031d,0x60180118}}, // cret_, _fonk, farw, _oímb,
+ {{0x442b53fc,0x2d9853fd,0x3f8216ea,0x799c53fe}}, // sic_, _nere_, _afku_, garw,
+ {{0x442b53ff,0x6d5880eb,0x3ea000f1,0x76489106}}, // pic_, _uzva, esit_, mody,
+ {{0x34fb8bea,0x6603805d,0x2d985400,0x20d8801c}}, // _יהוד, _zonk, _aere_, _mũi_,
+ {{0x2d98149f,0x799c023b,0x6603805d,0x7c2b96f2}}, // [4710] _bere_, barw, _yonk, vigr,
+ {{0x7ae9820f,0x27338104,0x2d98002e,0xb8dd00ab}}, // _rret, ảng_, _cere_, _ইন_,
+ {{0x7ae9a3e3,0x69d55401,0x2d985402,0x245c801b}}, // _sret, meze, _dere_, zíme_,
+ {{0x69d55403,0x25fb2261,0x3eadc155,0x37ac80ab}}, // leze, लॉजी_, yret_, ক্তর,
+ {{0x44290a2c,0xf1ec023c,0x2d98434d,0x3ea00019}}, // _mma_, _छोड़_, _fere_, csit_,
+ {{0x2d983b07,0x2ba78028,0x409582a4,0x245c826f}}, // _gere_, ốc_, брит, víme_,
+ {{0x442932f4,0xb7bd802e,0x3f990da8,0x9f4b01ca}}, // _oma_, liţi, _kesu_, _tocó_,
+ {{0x01381a0f,0x3f995404,0x2d815405,0x44290135}}, // ירות_, _jesu_, lche_, _nma_,
+ {{0x69d55406,0x7c298a54,0x6015840e,0x7ae9a4fc}}, // keze, _imer, _cáma, _uret,
+ {{0x44295407,0x2d815408,0xe6169adb,0x644d5409}}, // _ama_, nche_, йды_, _akai,
+ {{0x2d813cde,0xeb9a2482,0x61e1d40a,0x660380e1}}, // iche_, лин_, _iall, _vonk,
+ {{0x61e1b2c7,0x3f9921b8,0x6603805d,0x6b9d1aa8}}, // _hall, _nesu_, _wonk, gasg,
+ {{0x61e1d40b,0x7c299292,0x799c04b9,0x8e85d40c}}, // _kall, _mmer, tarw, _угле,
+ {{0x4429540d,0x644d0102,0x27ff81b9,0x661a8f67}}, // _ema_, _ekai, ljun_, chtk,
+ {{0x2d98540e,0x7c29af9f,0xfbdf03a7,0x799c540f}}, // _rere_, _omer, glês_, rarw,
+ {{0x61e184b9,0x2d985410,0x3ea01743,0x3205802a}}, // _lall, _sere_, tsit_, _loly_,
+ {{0x2d985411,0x69d55412,0xee3a0d9d,0x6b845413}}, // [4720] _pere_, beze, гна_, _ifig,
+ {{0x82360077,0xc333010f,0x3ea05414,0x9f404a96}}, // _دربا, _מוח_, rsit_, ndié_,
+ {{0xd91009d7,0x44290355,0x2d98021e,0x3ea05415}}, // _غیر_, _yma_, _vere_, ssit_,
+ {{0x2d985416,0xa3cb0327,0xeb9a8591,0x3ea05417}}, // _were_, र्प_, اضات_, psit_,
+ {{0x7c3bd418,0x2d985419,0x6a6002af,0xdcfa81a9}}, // _djur, _tere_, röff, tatī,
+ {{0x2d810081,0x61e1d41a,0x7c29d41b,0x27e20326}}, // cche_, _call, _emer, _hakn_,
+ {{0x3f993433,0xa3b9146d,0x61e19140,0x845a97c8}}, // _yesu_, _चाल_, _dall, _брат_,
+ {{0x6c3684e3,0x61e18219,0x7c3b8168,0x68e100ec}}, // _افرا, _eall, _gjur, ældi,
+ {{0x628004c3,0xa3cb3b04,0x69d50019,0x7de68071}}, // ímol, र्फ_, yeze, бінд,
+ {{0x645f0b18,0x644d1d4c,0xa3cb04c5,0x2d98d41c}}, // _shqi, _skai, र्न_, úre_,
+ {{0x764881e2,0xb7bd802e,0x69d50019,0xf8ac8105}}, // rody, ziţi, veze, _घटिय,
+ {{0x69d53979,0x61e1d41d,0xdb04002a,0x25a78118}}, // weze, _zall, _aciñ, _ednl_,
+ {{0x69d533bc,0x4429541e,0x61e18326,0x6d5c00b9}}, // teze, _vma_, _yall, _lzra,
+ {{0x98dc83eb,0xf8dc83a4,0x61e1d41f,0x6b8402df}}, // _मनाए, _मनाय, _xall, _efig,
+ {{0x95838063,0x69d55420,0x5d9b8039,0x2d9ea0a5}}, // łącz, reze, _גבוה, mate_,
+ {{0x44295421,0xb90112ee,0x69d55422,0xdb008118}}, // _uma_, _धन_, seze, _admí,
+ {{0x200690f4,0x44395423,0x69d50fe0,0xd6d70264}}, // [4730] _mooi_, mns_, peze, সম্প,
+ {{0x44395424,0x7c2995d8,0xdb0402af,0x799a967c}}, // lns_, _smer, _heiß, _netw,
+ {{0x2d8105ed,0x4439368b,0x2264001b,0x9379846e}}, // rche_, ons_, řské_, абет_,
+ {{0x44395425,0x2d811736,0x61e185a3,0x7c2f002a}}, // nns_, sche_, _sall, nicr,
+ {{0xa3cb00d4,0x81e280c8,0x799a8051,0x44395426}}, // र्य_, ধান_, _betw, ins_,
+ {{0x2d9ed427,0x61e19953,0x7aed4ef1,0x20898081}}, // jate_, _qall, _mrat, айки_,
+ {{0xf8a98077,0x61e1a05a,0xb4c78006,0x799a83ec}}, // شگاه_, _vall, _उहे_, _detw,
+ {{0xa3cb00d4,0x7c29bf21,0x6b828081,0x20c1818d}}, // र्म_, _umer, ccog, rói_,
+ {{0x61e1d428,0xd5c6000c,0x442f9451,0xa3dd0b9f}}, // _tall, र्वज, mig_, ड़न_,
+ {{0x442fd429,0xdce508c5,0xf993830f,0xfc40016b}}, // lig_, lahı, _ربط_, čím_,
+ {{0x7aed542a,0x63a48125,0x6d468214,0xa3cb0cf0}}, // _arat, ðing, şkan, र्भ_,
+ {{0x7aed542b,0x442f843c,0xd7e48221,0x6b9b838e}}, // _brat, nig_, _нічо, _jeug,
+ {{0xab5b01ec,0x9346373a,0x29050106,0x2d9ed42c}}, // nfüh, інге, älan_, bate_,
+ {{0x2d9ed42d,0xca7a810f,0xe81c000c,0x6b9b8014}}, // cate_, ינשט, नसभा_, _leug,
+ {{0xa3b99d01,0x442f8747,0x63a9a5dd,0x5fddc970}}, // चला_, kig_, _oden, _मसाल,
+ {{0x63a9d42e,0x6b9b85f3,0x7aed1b82,0x601583a2}}, // _nden, _neug, _frat, _mámo,
+ {{0x442fd42f,0x562a283b,0x644b8114,0xaa7b016b}}, // [4740] dig_, ажам_, dogi, jvýh,
+ {{0x6607026c,0x7e560221,0x237f01d6,0xdd1c01d6}}, // _cojk, ітац, ľuje_, ráža,
+ {{0x442fd430,0x237f1ee0,0x16000540,0x6b9bd431}}, // fig_, žuje_, लॉगर_, _beug,
+ {{0x2d9ed432,0x442fd433,0xab5b0799,0x6aa2c3e1}}, // zate_, gig_, ffüh, tsof,
+ {{0x2d9ec71f,0x200681b0,0x6aa80105,0x63a9aaf1}}, // yate_, _rooi_, _कटोर, _dden,
+ {{0xaab2023c,0x6b82957a,0x6aa2d434,0x660701dd}}, // _जिनक, scog, rsof, _gojk,
+ {{0xa3cb0a3a,0x442f849f,0x6b9ba483,0x2d9ed435}}, // र्ड_, big_, _feug, vate_,
+ {{0x63a4808b,0x644bd436,0x442fa467,0x6015b8b5}}, // ðind, cogi, cig_, _dámo,
+ {{0x2d9eb499,0x601c8019,0x41ca052a,0x4439006a}}, // tate_, _néme, िभास, vns_,
+ {{0x70f6990c,0x44392b02,0x63a9936f,0x200680b9}}, // _وسائ, wns_, _zden, _wooi_,
+ {{0x7d7b0039,0xd7fa8d8e,0x7aed051e,0x69d888e0}}, // יניו, _вул_, _srat, meve,
+ {{0x2d9ed437,0x44394aac,0xd7ef80f7,0x24590118}}, // sate_, uns_, _لكم_, rémo_,
+ {{0x78758110,0x106980f7,0x47369459,0x44390cf7}}, // _išva, _احلى_, _براز, rns_,
+ {{0x442f8a0f,0x644bd438,0xdb0401ec,0x3ea6826c}}, // zig_, zogi, _weiß, _ivot_,
+ {{0xcf9b0fbb,0x69c307d9,0x442fd439,0x6018007b}}, // ија_, _önem, yig_, _síma,
+ {{0x69d8bffb,0x2ee9826c,0x442fd43a,0x2d9c8163}}, // heve, _šafi_, xig_, _heve_,
+ {{0xa3cb0b6f,0x7aed543b,0x7c2d0a5a,0x6607543c}}, // [4750] र्ण_, _urat, _imar, _pojk,
+ {{0x69d88988,0x442fd43d,0x63bd543e,0x2fc9183d}}, // jeve, wig_, lgsn, sfag_,
+ {{0x442f8c1b,0x69d89b2a,0x7c2d0295,0x628f474f}}, // tig_, deve, _kmar, lpco,
+ {{0x2d9cd43f,0x60180125,0x3eb2068f,0x601c887a}}, // _leve_, _tíma, bryt_, _lémb,
+ {{0x442f92af,0x38690687,0x61e5005d,0x7c2d5440}}, // rig_, mmar_, _kahl, _mmar,
+ {{0x69d8c7a5,0x442fc389,0x2d9c8e6c,0xaca301bc}}, // geve, sig_, _neve_, _ahụa,
+ {{0x442fadf3,0xb8820ed7,0xe4a40e97,0x7c2d5441}}, // pig_, žíva, _орто, _omar,
+ {{0x38695442,0x63a9813c,0xb8d383db,0x97d980e8}}, // nmar_, _uden, _झट_, ську_,
+ {{0x442dd443,0x443f8135,0x38695444,0x2d9c80e5}}, // _ime_, _iju_, imar_, _beve_,
+ {{0x7c2d5445,0x61e5003e,0x8c0800c8,0x69d8d446}}, // _amar, _nahl, _লগইন_, ceve,
+ {{0x2d9c98a0,0x442d82c4,0x443f81a1,0x7e755447}}, // _deve_, _kme_, _kju_, _mizp,
+ {{0xca29004c,0x38690088,0x63bd0711,0x88838019}}, // _גם_, jmar_, ggsn, _ٹیکن,
+ {{0x442dd448,0x7e7d5449,0x61e50234,0x5ed300ab}}, // _mme_, _nusp, _bahl, সিডে,
+ {{0x7e688122,0x60338493,0x61e517c9,0xbfa107b6}}, // cmdp, rămu, _cahl, ãªnc,
+ {{0xfe708288,0x442dd44a,0x443f90ab,0x7e7d544b}}, // _شده_, _ome_, _oju_, _ausp,
+ {{0x7e7d0088,0x0686373a,0x7e75544c,0xbb460110}}, // _busp, зган, _aizp, _нейк,
+ {{0x7e7d0083,0xd007433d,0x441b007c,0x69d8b14f}}, // [4760] _cusp, чете_, _לוקס, yeve,
+ {{0xa3e4acdd,0x442dd44d,0x443f8d62,0x9f42001b}}, // _भोज_, _ame_, _aju_, _jaké_,
+ {{0x7c2d0114,0xdcfc00eb,0x442dd44e,0xb33b041c}}, // _ymar, _ierī, _bme_, maça,
+ {{0x61e5544f,0xb33b5450,0x69d88039,0xe4529a37}}, // _zahl, laça, weve, رضا_,
+ {{0xcfc380c8,0x201f804f,0xfbc104c5,0x2bc101cb}}, // ্যান, dhui_, ष्टम, ष्टा,
+ {{0x442d8a2c,0x64a60221,0x443fd451,0xb33b1b01}}, // _eme_, _нага, _eju_, naça,
+ {{0x69d883ba,0x2d9ca1bf,0x67245452,0x7bd98c4d}}, // reve, _reve_, lzij, bewu,
+ {{0xa2ad2a18,0x69d8aeae,0xd01080f7,0x6011108c}}, // _जिल्, seve, ئلة_, _påmi,
+ {{0xc6f787ac,0x5eca00ab,0x63a29fb7,0xba3d801b}}, // зных_, রিকে, maon, _kvůl,
+ {{0x1dcf0c78,0xab5b1209,0x6018016a,0x63a2d453}}, // स्पत, ngüe, _oímo, laon,
+ {{0x386900fc,0xa3cb0006,0x442dd454,0x9f4701d0}}, // ymar_, _रउआ_, _yme_, ěné_,
+ {{0x63a28d4c,0x25a101ec,0x61e55455,0xbb940198}}, // naon, zahl_, _sahl, нающ,
+ {{0x61e51295,0x2d9c8073,0x601cd456,0xc905064a}}, // _pahl, _teve_, _témb, रह्म_,
+ {{0x27e681a8,0x2600104f,0x63a29b88,0x7e7d5457}}, // _haon_, राफी_, haon, _rusp,
+ {{0x2600090f,0x63a2d458,0x799e06c4,0x3d2288f9}}, // रानी_, kaon, _depw, _déwé_,
+ {{0x61e55459,0x7c2d545a,0x63a2d45b,0x25a10192}}, // _wahl, _umar, jaon, wahl_,
+ {{0x644f11b9,0xdce882bb,0x63ad545c,0xf77f26e1}}, // [4770] moci, madı, _idan, _caça_,
+ {{0x442d8038,0xdce883bf,0x443f8b64,0x628d0c53}}, // _sme_, ladı, _sju_, _itao,
+ {{0xf65016a5,0x443f808e,0xb88301d0,0x7c3d1647}}, // ائق_, _pju_, říbr, onsr,
+ {{0x644f545d,0xf77f0073,0xd3719368,0x63a2d45e}}, // noci, _faça_, نها_, gaon,
+ {{0x27e9545f,0x443f8088,0x91e5b7bf,0x9f4b02df}}, // mdan_, _vju_, доле, _cocô_,
+ {{0x2d850006,0x27e90b0b,0x200b00e1,0xdb1d0106}}, // _üle_, ldan_, _hoci_, besö,
+ {{0xa3dd101b,0x94d40dc0,0x200b23ac,0x2489826c}}, // ड़ा_, _порц, _koci_, _čamo_,
+ {{0x27e902fa,0x63ad3202,0x442dd460,0xd0f80039}}, // ndan_, _ndan, _ume_, למות_,
+ {{0x26c92b5c,0x200b5461,0xdce887c0,0x627980e1}}, // ntao_, _moci_, dadı, _dňoc,
+ {{0x63ad0d53,0x27e90046,0xa3cb2303,0x7b7481a8}}, // _adan, hdan_, र्स_, _أطفا,
+ {{0x1dcf000c,0x044611d2,0x672401b9,0x628d545b}}, // स्यत, денн, zzij, _atao,
+ {{0x200b07df,0x3f1595f7,0x629b81bc,0x443d8114}}, // _noci_, _одес, _kwuo, nnw_,
+ {{0x9f42000d,0x0cba0bb8,0xe72e9f6e,0x27e95462}}, // _také_, _उम्म, _ре_, ddan_,
+ {{0xf77f383c,0x6724026c,0x628d01a8,0x1dcf052a}}, // _raça_, vzij, _dtao, स्मत,
+ {{0x63a2d463,0x200b5464,0xb33b383c,0x6b89823e}}, // yaon, _boci_, raça, _ofeg,
+ {{0x27e923da,0x6f168063,0x644f0baf,0x998d0668}}, // gdan_, _życi, coci, đeš_,
+ {{0x200b0052,0x63a2c579,0xb33b5465,0x6da61fab}}, // [4780] _doci_, vaon, paça, диза,
+ {{0x6b898813,0x69cd03b7,0x7ae4005d,0x63a28010}}, // _afeg, द्दी, _isit, waon,
+ {{0x1dcf84e5,0xa3cb03dd,0x63a287fe,0x614603bd}}, // त्यत, र्व_, taon, _цена,
+ {{0x69dc22ba,0x200b0bda,0x9f42816b,0x69ce074c}}, // lere, _goci_, žké_, lfbe,
+ {{0x27e68083,0x34dad466,0xb3c600d4,0x78bc00b9}}, // _raon_, _बन्द, _वानख, lurv,
+ {{0x2bae8028,0x201d84bf,0x6b898133,0x7ae4004f}}, // ộc_, _alwi_, _efeg, _msit,
+ {{0x63a28b6d,0x6443816a,0x69dc2f84,0x27e6a6b9}}, // paon, énic, iere, _paon_,
+ {{0x69dc5467,0xb8d69664,0x2fc05468,0x7ae45469}}, // here, _जि_, ngig_, _osit,
+ {{0x69dc546a,0x6fd00b9f,0x6015826f,0x19c697c8}}, // kere, ड्यू, _zámk, _обам,
+ {{0x69dc546b,0x7c2280a9,0x660a82a3,0x601c82be}}, // jere, lhor, _qofk, _déma,
+ {{0xf9c78bca,0x45b70051,0x27e69882,0x27e9546c}}, // _تحقی, ופיל_, _taon_, ydan_,
+ {{0x61e882a3,0x6280d46d,0x63a0826f,0x7c22a32d}}, // _hadl, _kumo, _jemn, nhor,
+ {{0xdce8862a,0x6280d46e,0xa3cb123a,0x61ca0054}}, // radı, _jumo, र्ष_, िभूष,
+ {{0x69dc2235,0x200b3a68,0x644f3277,0x63a08087}}, // gere, _soci_, soci, _lemn,
+ {{0x7ae44837,0x6280d46f,0x27e90085,0x61e89a76}}, // _esit, _lumo, tdan_, _madl,
+ {{0xb907023c,0x63ad5470,0xe3d0001c,0x7c2283c1}}, // _मई_, _udan, _hằng_, jhor,
+ {{0x69dc5471,0x27e95472,0xfc3f000d,0x200b0330}}, // [4790] bere, rdan_, mní_, _voci_,
+ {{0xfc3f03bb,0x61e882a5,0x69dc5473,0xea010028}}, // lní_, _nadl, cere, _đặt_,
+ {{0x26c90fda,0x69c50870,0x29198201,0x27e95474}}, // stao_, _iche, _əsas_, pdan_,
+ {{0xfc3f5475,0x27e90085,0x68e55476,0x200b017f}}, // nní_, qdan_, _ishd, _uoci_,
+ {{0x1dc70105,0x320c8e25,0x63a0d477,0x62809495}}, // _लापत, _body_, _demn, _cumo,
+ {{0xaab20063,0x0d840fe6,0x11d981a8,0x260003dd}}, // _जिसक, _алён, سوعة_, राणी_,
+ {{0x69c5022e,0x3ea95478,0xe0d18019,0x61e8d479}}, // _mche, lsat_, ازت_, _dadl,
+ {{0x69dc547a,0x3866bcde,0xfc3f03fb,0x61fa81ec}}, // zere, _chor_, jní_, _entl,
+ {{0xfc3f03bb,0x69dc547b,0xe3d00104,0x7a2202be}}, // dní_, yere, _bằng_, _hôte,
+ {{0x6cea9094,0x69dc08f1,0x69c50870,0xe3b2026a}}, // टिंग_, xere, _nche, گرد_,
+ {{0x2600035a,0x25e0800d,0x69dc547c,0x60158511}}, // राती_, _कसरी_, vere, _lámi,
+ {{0x69c5547d,0xeb970364,0x601c801b,0xccf200be}}, // _ache, нию_, _téma, אכט_,
+ {{0x386d8bbd,0x6015d47e,0x81e28264,0x6d588380}}, // mmer_, _námi, ধার_, _eyva,
+ {{0x64a58721,0x386d8687,0x3ea9547f,0x442203ed}}, // _зака, lmer_, dsat_, shk_,
+ {{0xfc3f000d,0x25a5d480,0xa3cb00d4,0x3ea90706}}, // bní_, mall_, _रॉक_, esat_,
+ {{0x69c5082e,0x69dc532b,0x7bdd17ec,0x7ae40074}}, // _eche, sere, cesu, _tsit,
+ {{0x69dc5481,0x63a65482,0x9f420247,0x3ea90122}}, // [47a0] pere, makn, _ankò_, gsat_,
+ {{0x63a08012,0xa3c0073c,0x63a62d27,0xf77f03a7}}, // _semn, ुभव_, lakn, _faço_,
+ {{0x399b0451,0x44e181e2,0x3ea903ac,0x386d81ac}}, // _מייד, _jų_, asat_, kmer_,
+ {{0xf76791ac,0x63a65483,0xc5f880eb,0x628082c4}}, // _ما_, nakn, nmēr_, _pumo,
+ {{0xa3b80c1c,0x81e980ab,0x087700be,0xafdb004a}}, // _चयन_, যান_, _רעכט_, _kjøn,
+ {{0x7c22d2cb,0xc33300be,0x7bdd5484,0x63a6010b}}, // shor, נוג_, zesu, hakn,
+ {{0x44202c84,0x320c803e,0xe643035f,0xe3d00028}}, // _oli_, _vody_, _серп, _rằng_,
+ {{0xc6f8ad6b,0x63a609da,0xefb2803d,0x320c809a}}, // дних_, jakn, ایشگ, _wody_,
+ {{0xfc3f03bb,0x63a60500,0x25a58fb6,0xc24695da}}, // vní_, dakn, fall_, _знак,
+ {{0xceb3004c,0x25a5ae12,0x64470115,0x7ed381a8}}, // נית_, gall_, čkim, ازيا,
+ {{0x439519b8,0xfc3f03bb,0x4420179a,0x60180019}}, // канс, tní_, _bli_, _címk,
+ {{0x63768182,0x69c5547c,0x63a60886,0x6d58816b}}, // _günü, _sche, gakn, _vyva,
+ {{0x25a5d485,0xfc3f000d,0xb33b5486,0x69c50953}}, // ball_, rní_, raço, _pche,
+ {{0x44205487,0x171b8158,0xfc3f001b,0x7af68198}}, // _eli_, _צוגע, sní_, äyty,
+ {{0x9f47000d,0xa2ad01ce,0xfc3f03fb,0xb33b383c}}, // ění_, _जिक्, pní_, paço,
+ {{0x44200021,0x601c82be,0x63a6008e,0x36d52f92}}, // _gli_, _mémo, cakn, кобр,
+ {{0x2bc60c78,0x69c503ec,0x50645488,0xfc4001d0}}, // [47b0] _वाता, _tche, утра, čít_,
+ {{0x69c55489,0x4420012b,0x645d3936,0x63a4807b}}, // _uche, _zli_, llsi, ðinn,
+ {{0x09bf8a49,0xe1ff8104,0x3ea9548a,0x44200364}}, // _ইসলা, _đó_, ssat_, _yli_,
+ {{0xa3d4000f,0x69cd10c8,0xdb1b8511,0x3ea9001b}}, // स्प_, द्री, _acuá, psat_,
+ {{0xfbd306bf,0x2bd3024c,0x645d01a8,0xdb00810c}}, // ध्यम, ध्या, ilsi, _jemè,
+ {{0xdc138059,0xdb008168,0x1a9c00be,0xf2d403de}}, // rşıs, _zemë, לידע, רעס_,
+ {{0x63a60890,0x25a5880f,0x6d420087,0x2ee681b0}}, // yakn, vall_, şoar, _asof_,
+ {{0x601cd48b,0x8f75bf56,0x75288ec3,0x7f758c75}}, // _démo, купі, dzdz, купц,
+ {{0x386dd48c,0x25a5d48d,0x2459026f,0x63a6548e}}, // rmer_, tall_, nému_, vakn,
+ {{0x273e0028,0xa3d406b7,0x6b8d0032,0x386dd48f}}, // ắng_, स्न_, _afag, smer_,
+ {{0x44200247,0x63a600ce,0x25a35490,0x9d4581a1}}, // _pli_, takn, _mejl_, тенд,
+ {{0x66b5a84f,0x2459016b,0x3de180ab,0x44200037}}, // _абду, kému_, বাইল, _qli_,
+ {{0x8af002bf,0xe3b80182,0x27ed8584,0x25a5d491}}, // _azər, ğı_, mden_, pall_,
+ {{0xe3b80059,0x27edc537,0x63a612b7,0xe3d580ab}}, // şı_, lden_, sakn, _সোমব,
+ {{0x69c3d492,0x63a641eb,0xe9a32dc7,0x3ebf910f}}, // igne, pakn, расп, luut_,
+ {{0x27ed977a,0x44203a59,0x7848801b,0xf7721c12}}, // nden_, _uli_, pěve, لاء_,
+ {{0x27edd493,0xe7371269,0x26cd8511,0x3ebf8cfa}}, // [47c0] iden_, вет_, nteo_, nuut_,
+ {{0x63a43f5d,0x644383b0,0x27ed8009,0x200fd494}}, // _hein, énin, hden_, _logi_,
+ {{0x63a45495,0x60182d11,0xdb008168,0xafdb00e8}}, // _kein, _lími, _temë, _kjøl,
+ {{0x27ed8613,0x7af609a4,0xb4d2009a,0x69c48072}}, // jden_, _kryt, _वही_, _राही,
+ {{0xa3d40dbc,0x27edce47,0x63a411e6,0x7bc283c9}}, // स्य_, dden_, _mein, rgou,
+ {{0xbda507bd,0x27e03326,0x63a4394e,0x80db80c8}}, // _محفو, lein_, _lein, ভিন্,
+ {{0x7d0300f2,0x403486c8,0xdca314b8,0x7af60110}}, // änst, лекс, _васи, _lryt,
+ {{0x63a45496,0x6b8580f7,0x27edd497,0x601c802a}}, // _nein, _مشكل, gden_, _vémo,
+ {{0x7e7c0190,0xc8ae83eb,0x6d5c0aa8,0x291812b7}}, // _kirp, _टिकट, _hyra, hyra_,
+ {{0x44268003,0x7c86845d,0x4b558698,0x601ca02e}}, // lho_, куме, _съст, _témo,
+ {{0x63a45498,0x27e05499,0xa3e6816f,0xdb0083ec}}, // _bein, kein_, _बघा_, _semè,
+ {{0x44268003,0x61fe0247,0x64428009,0x7af6549a}}, // nho_, _anpl, nnoi, _bryt,
+ {{0x63a45013,0x59c99d17,0x27e01a95,0x34df0571}}, // _dein, _रामर, dein_, _नन्द,
+ {{0x2459026f,0x3eb980e1,0x200f8326,0x7bd60c2e}}, // vému_, áste_, _zogi_, _ebyu,
+ {{0x442691e8,0x63a4549b,0x195821f6,0x29183d9b}}, // kho_, _fein, вары_, gyra_,
+ {{0x442681ac,0xe7ed02f1,0x63a41151,0x61fe549c}}, // jho_, _चोखा_, _gein, _enpl,
+ {{0x4439549d,0x62840d76,0x4426d49e,0x26c002c4}}, // [47d0] mis_, _guio, dho_, guio_,
+ {{0x27ed87d9,0x2fdf82af,0x442681ac,0x2459016b}}, // zden_, zeug_, eho_, rému_,
+ {{0x27edd49f,0x44391390,0x9f4f8106,0x7e7c0286}}, // yden_, ois_, _ingå_, _dirp,
+ {{0x443954a0,0xc4d38158,0x4426d4a1,0x6d5c267f}}, // nis_, יגע_, gho_, _dyra,
+ {{0x1dc289a3,0x59a7000d,0x69c48651,0x200f88e4}}, // _शांत, _औजार, _राशी, _rogi_,
+ {{0x443954a2,0x78ba0025,0x21699541,0xa9699170}}, // his_, štve, нили_, нила_,
+ {{0x44393e80,0x205589b8,0x26000fb2,0x69c3d4a3}}, // kis_, лтир, रारी_, rgne,
+ {{0x4426d4a4,0x443954a5,0xe3b0830f,0x27edc5a8}}, // cho_, jis_, _ورق_, uden_,
+ {{0x27edd4a6,0x69d707f4,0x63a4293c,0xdb0603ed}}, // rden_, _obxe, _rein, rakë,
+ {{0x63a409af,0x26cd9cdc,0x27e00102,0xafdb021e}}, // _sein, rteo_, zein_, _sjøl,
+ {{0x7fd5ba8b,0x63a41b3a,0xdb00d4a7,0x26cd8216}}, // _білі, _pein, _remé, steo_,
+ {{0x601854a8,0x2d8e9384,0xdb063d9b,0x9ef581a8}}, // _tími, _effe_, laké, _مستش,
+ {{0x8c1a8051,0x62844e2a,0x27e054a9,0x2bd300c2}}, // גורי, _quio, vein_, ध्धा,
+ {{0x63a454aa,0x44392617,0x29180168,0x7c260168}}, // _wein, ais_, tyra_, shkr,
+ {{0x44395005,0x63a428cc,0x3ead829e,0x291e54ab}}, // bis_, _tein, lset_, áta_,
+ {{0x7ae9b72e,0x1dcfa0f2,0x58950425,0xd5cf816f}}, // _oset, त्रत, ршру, त्रज,
+ {{0x291e003a,0x26001344,0x7c2454ac,0x7e7c0267}}, // [47e0] šta_, राली_, _ilir, _pirp,
+ {{0x3ead8009,0x44268609,0xdb061ee0,0x683e8ec3}}, // iset_, who_, jaké, mūde,
+ {{0x4426d4ad,0xa3cb024c,0x7ae9a551,0x273380ff}}, // tho_, र्क_, _aset, ảnh_,
+ {{0x7bc601da,0x3ead825d,0x6d5c110d,0x69c30214}}, // ngku, kset_, _vyra, _öner,
+ {{0x4426d4ae,0x6d5c009a,0xf772015b,0x6442d4af}}, // rho_, _wyra, _داد_, rnoi,
+ {{0x44268774,0x443954b0,0xa3d401d2,0x38605202}}, // sho_, zis_, स्त_, llir_,
+ {{0x7ae9d4b1,0x44393a96,0x442691e8,0x60158125}}, // _eset, yis_, pho_, _náms,
+ {{0x4fc6844f,0xb4fb0051,0x79818114,0x6b808122}}, // уска, _אפלי, _eglw, _ugmg,
+ {{0x74138277,0x442482f1,0x67041e19,0x3ead9aed}}, // _اوقا, _ilm_, रमिक_, gset_,
+ {{0x443954b2,0x4424ae08,0x68ed809a,0x7c360176}}, // wis_, _hlm_, łada, _amyr,
+ {{0xdb0983b0,0xd6570039,0x97c68199,0xd7ef8adb}}, // _ideá, ריית_, _مقاو, _лу_,
+ {{0x6015803e,0x225a00ee,0x7c244e55,0x661d93de}}, // _dáms, _akpk_, _clir, ëskr,
+ {{0x443905d8,0xa3cb0305,0x2bc6009a,0xa3bc864a}}, // ris_, र्ग_, _वारा, _आयन_,
+ {{0x443954b3,0xa2cc800c,0x2c7b0032,0xf77381c6}}, // sis_, _समन्, _bàdì_, _פקס_,
+ {{0xbd558077,0x1959076a,0xd7fb01e5,0x667180d7}}, // _پروژ, тавы_, нуа_, _بگير,
+ {{0x5f950e8e,0x9f4b0388,0x6e3a8366,0x6d469238}}, // риат, _hacé_, litb, şkas,
+ {{0xd5bb117e,0x245c928a,0x9f590580,0x963554b4}}, // [47f0] есе_, nímu_, _rosó_, анац,
+ {{0x4424813c,0xafdb004a,0x236902d6,0xa3dd00c2}}, // _alm_, _kjøk, _izaj_, ड़क_,
+ {{0x386007fa,0x44248890,0x9f591d7e,0xb8670019}}, // blir_, _blm_, _posó_, _ماحو,
+ {{0x4424802a,0x69c7021e,0x7ae9817f,0xf41f0009}}, // _clm_, lgje, _pset, nkä_,
+ {{0x4424b72d,0x81e980ab,0x9f4201d6,0xcfaa8264}}, // _dlm_, যাস_, _taká_, ওলান,
+ {{0xd05a8086,0xa3cc0740,0x7ae98a21,0x69c754b5}}, // _istə, _लाभ_, _vset, ngje,
+ {{0x2bf506a7,0x44248168,0x6e3ad4b6,0xafdb004a}}, // इयां_, _flm_, ditb, _skøy,
+ {{0xb8cd823c,0x3ead8100,0x69d58105,0xe3b3806b}}, // _कब_, tset_, _मॉरी, _درس_,
+ {{0x53358847,0x600691e9,0x78a48a20,0xf7458a7c}}, // _теат, рным_, ćiva, репо,
+ {{0x63aba1b4,0x3eadbeb9,0x3f8d83a7,0x7c2400d7}}, // lagn, rset_, sceu_, _slir,
+ {{0x99849301,0x3ead82fe,0x61e3d2a4,0x7c242cb1}}, // _المو, sset_, menl, _plir,
+ {{0xf7708117,0x61e3ad46,0x60c3bdfc,0xda03a730}}, // _کام_, lenl, munm, लावत_,
+ {{0x2bd79880,0x60c38457,0x20d1801c,0x501a8039}}, // ण्या, lunm, _hơi_, _שונו,
+ {{0x672d21a9,0x7a2b13da,0xe297117e,0x61e3d4b7}}, // dzaj, _lütf, рар_, nenl,
+ {{0x28dd8d86,0x463a80be,0x98a61c79,0x6d468713}}, // _महफि, _בעסע, рибе, şkar,
+ {{0x63ab803a,0xa3d78105,0x7c243e21,0x61e38192}}, // jagn, ़्म_, _ulir, henl,
+
+ {{0x63ab8081,0x6f1b89ca,0x61e3d4b8,0x6b8402c4}}, // [4800] dagn, nyuc, kenl, _igig,
+ {{0x645bbda1,0xbda501a8,0x630694a0,0x63ab82c4}}, // _akui, محمو, یوال, eagn,
+ {{0x20d18028,0x61e3d4b9,0xdddd0087,0x20e78019}}, // _nơi_, denl, _susţ, _női_,
+ {{0xe29aa597,0x63ab8bcb,0x64462916,0x60c38041}}, // вад_, gagn, nnki, dunm,
+ {{0xa3cb229e,0xf1948009,0x99830110,0x672d009a}}, // र्ट_, биль, cijų_, czaj,
+ {{0x61e3d4ba,0x6b84026c,0x20d180ff,0x645bd4bb}}, // genl, _lgig, _bơi_, _ekui,
+ {{0x03a680e8,0x4424d4bc,0x63abd4bd,0x48a78190}}, // _вибо, _tlm_, bagn, атым_,
+ {{0xdfda8098,0xdb0b0214,0x63ab9268,0x4424d4be}}, // тък_, _çiçe, cagn, _ulm_,
+ {{0xde888135,0x2360022c,0xe61f00ff,0x61e3d297}}, // _bịa_, _nyij_, _muôn_, benl,
+ {{0xe61f0028,0x20d6835f,0x225854bf,0x6b8454c0}}, // _luôn_, _відс, jork_, _agig,
+ {{0xf41f0009,0x24800362,0x629605ee,0x6e3a83a6}}, // tkä_, _biim_, _atyo, ritb,
+ {{0xa3a981ce,0x6f0983a8,0x386081a8,0x6aa4018f}}, // _खड़ा_, bxec, _óir_, _bwif,
+ {{0x6e948013,0x81e980c8,0xe69480f7,0x27f2031d}}, // _التا, যার_, _التد, ddyn_,
+ {{0x20024ea8,0xa3cb01d2,0x64460a5b,0x6b8454c1}}, // _anki_, र्ज_, anki, _egig,
+ {{0x97a7197b,0x69c71832,0x26c49a99,0x27ef80dd}}, // ирал, rgje, lumo_, _yagn_,
+ {{0x61e387d9,0x02a70b9b,0xcf980b30,0x5d7b81c6}}, // zenl, _кром, ију_, _באוק,
+ {{0xf7730288,0x61e393f4,0x63ab8bcb,0x95d90b79}}, // [4810] زار_, yenl, vagn, лдат_,
+ {{0x60c3cfbf,0x645b81b0,0x99830110,0x2002471f}}, // yunm, _skui, rijų_, _enki_,
+ {{0xa3d40076,0x228b821e,0x63ab8625,0x99830084}}, // स्व_, _søk_, tagn, sijų_,
+ {{0xa3cb3849,0x601c80e7,0x20d1801c,0x7aed09e1}}, // र्च_, _fémi, _rơi_, _ksat,
+ {{0x61e3d4a6,0xe21402e3,0xadf8863a,0x7848801b}}, // tenl, _طبیع, ्ञान_, těvn,
+ {{0x63abd4c2,0x60c39040,0x26c4d4c3,0xdb008144}}, // sagn, tunm, dumo_, _gemí,
+ {{0x26091880,0x63abd4c4,0x61e3d4c5,0x601801d0}}, // साठी_, pagn, renl, _tímt,
+ {{0x9f950006,0x7aed54c6,0x60c3880a,0x7bcd01c5}}, // _nüüd_, _osat, runm, _ncau,
+ {{0x6e27026c,0xa3cb001b,0x26c48084,0x27efd4c7}}, // _kljb, र्छ_, gumo_, _vagn_,
+ {{0x6459d4c8,0x443dd4c9,0x2d83806a,0x60c3803d}}, // lowi, liw_, øje_, punm,
+ {{0x30858013,0x63a982a3,0x7aed4e58,0x90998779}}, // _الصف, _keen, _asat, _свет_,
+ {{0xe29a3b17,0x64598063,0x6281d4ca,0x04460749}}, // лам_, nowi, _hilo, семн,
+ {{0x7aed0065,0x63a9d033,0xe60e9d7b,0x05c70072}}, // _csat, _meen, _ед_, _लांब,
+ {{0x63a9d4cb,0x62818326,0x7aed0372,0x6ec101d0}}, // _leen, _jilo, _dsat, _लमजु,
+ {{0xb4e603bb,0x7aed1604,0x64598035,0x01662357}}, // _भने_, _esat, kowi, скво,
+ {{0x765c54cc,0x6281d4cd,0x9ac504b7,0x63a9d4ce}}, // _skry, _lilo, fiċċ, _neen,
+ {{0x6459809a,0x92b61e91,0x661554cf,0xa0670323}}, // [4820] dowi, _احبا, _bozk, сата_,
+ {{0x62819217,0x7bdb91b5,0xaca38133,0xa3d40035}}, // _nilo, _abuu, _ndịi, स्र_,
+ {{0x63a9d4d0,0x60de00f2,0xa63a80be,0x7afbd4d1}}, // _been, _uppm, נגער, _arut,
+ {{0x3ebfd4d2,0x6281be9f,0x63a98579,0xdc190035}}, // krut_, _ailo, _ceen, _błęd,
+ {{0x6281d4d3,0x7afb8068,0x63a990b0,0x6d5c8a21}}, // _bilo, _crut, _deen, ýraz,
+ {{0xed5a1d85,0x6d430013,0xa8a6b33d,0x59db0bb8}}, // _бог_, únac, брик, म्पर,
+ {{0x92e880c8,0x62819600,0x7afbd4d4,0x64598035}}, // যমে_, _dilo, _erut, bowi,
+ {{0x7afbd4d5,0x69cd9664,0xde88819d,0x63a9d4d6}}, // _frut, _साथी, _dịn_, _geen,
+ {{0x7afb8dba,0xfaa3a81c,0x91fc8362,0xdb0082df}}, // _grut, _нато, _bhāt, _demã,
+ {{0x6281d4d7,0x7bcd002e,0x26c4d4d8,0x63a9d4d9}}, // _gilo, _scau, rumo_, _zeen,
+ {{0xa3bc809a,0xa3cfc252,0x9f4f86c0,0x25aa0088}}, // _आया_, _शाम_, _lagè_, _mebl_,
+ {{0x7f868013,0x26c00a20,0x69d8800b,0x6281b8a8}}, // _الإن, krio_, lfve, _zilo,
+ {{0xd5a804e5,0xcc3b80be,0x7c3d1ab3,0x7c228f3e}}, // गराज, רעכט, risr, ikor,
+ {{0xab5b54da,0x6aa918e6,0x26c02843,0x6459809a}}, // ngüi, _कबीर, drio_, zowi,
+ {{0x463b80be,0x7c22d4db,0x661509a4,0xde8880ff}}, // _געגע, kkor, _rozk, _xịn_,
+ {{0x2c7b0341,0x7aed54dc,0x26c01670,0x3a3a1b88}}, // _līdz_, _tsat, frio_, _empp_,
+ {{0x442954dd,0x7aed54de,0x7c22bd7f,0x5a350049}}, // [4830] _ila_, _usat, dkor, _мнит,
+ {{0x44291393,0x63a981ab,0x644aabda,0x09b300ab}}, // _hla_, _seen, éfin, ঞ্জা,
+ {{0x26c0160a,0x6459d4df,0x443d876d,0x7afb8090}}, // ario_, towi, tiw_, _srut,
+ {{0x237f0052,0x7afb9303,0x8cc08f85,0xaa7b016b}}, // žuju_, _prut, _विनो, jvýz,
+ {{0x64598063,0x69c4d4e0,0xd90f82e3,0x248d083d}}, // rowi, _राखी, کیا_, _čemu_,
+ {{0x40959300,0x63a981b0,0xdb008118,0x56942118}}, // орит, _ween, _admó, _харт,
+ {{0x64598d38,0x442954e1,0x6281bac8,0x1b1780ab}}, // powi, _ola_, _vilo, _দেশে_,
+ {{0xe7399cd3,0x7c22816d,0x442910ab,0x682a001b}}, // ией_, ckor, _nla_, _týde,
+ {{0x7c3bd4e2,0x7afba668,0x7c29d4e3,0x6281d4e4}}, // _imur, _urut, _iler, _tilo,
+ {{0x442954e5,0xc8ab8698,0x644d0b20,0x7fb9803d}}, // _ala_, _бъде_, _ajai, _چهار_,
+ {{0x442954e6,0x5f9587eb,0xf3e61056,0x7c29d4e7}}, // _bla_, _диет, ожно, _kler,
+ {{0x7bcb859c,0x443b0081,0xc2090039,0x5f74826a}}, // nggu, _cmq_, _בה_, _کامر,
+ {{0x44290d38,0x69db0267,0x627981d6,0x7c29810c}}, // _dla_, đuen, _dňov, _mler,
+ {{0xa2cc80ba,0x442936c7,0x5ebe80c8,0x765a810c}}, // _समस्, _ela_, ্বশে, coty,
+ {{0x7c3bd4e8,0x7c29d4e9,0x442200b9,0x717601a8}}, // _omur, _oler, pkk_, زهرا,
+ {{0x78ba369f,0xfc3f0a21,0x59db54ea,0x601c816a}}, // štvo, cií_, म्बर, _fému,
+ {{0x3205cbc0,0xf99f801c,0x78a90bcf,0xdb1d0338}}, // [4840] _only_, _đèn_, ćeva, ggsä,
+ {{0xdb00803e,0x26c28025,0x442900d2,0x7c3b83c3}}, // _nemá, škog_, _zla_, _amur,
+ {{0x20f800d2,0x7c22a9c6,0x7c29d4eb,0xd30281a2}}, // _uči_, tkor, _bler, लम्ब_,
+ {{0xf8b3004c,0x26c00698,0xab66a10d,0x6f0d002a}}, // _קשר_, prio_, овел, bxac,
+ {{0x3ce600e1,0x7cf7819d,0x19949f96,0x8cc080c2}}, // ňov_, _ịru, паля, _विभो,
+ {{0x64a612b2,0x7c2286ee,0x7c29baf6,0xdca602a4}}, // _мага, skor, _eler, _маги,
+ {{0x7c298f06,0x628fd4ec,0xdb008693,0x082a8396}}, // _fler, _écon, _demá, иции_,
+ {{0xdfd09168,0xd6da8785,0xdb008722,0x920382f1}}, // ريخ_, ато_, _temà, लाउज_,
+ {{0x69968098,0x63a481fa,0x63af0b80,0xe297a481}}, // _дрех, ðinu, vacn, _мат_,
+ {{0xddd8803b,0x44290a0f,0xdb1d54ed,0x69da00d4}}, // _bivš, _sla_, ngså, _पॉली,
+ {{0x442954ee,0xa3cc0105,0x6563831d,0x81468065}}, // _pla_, _लाश_, _cynh, _بنان,
+ {{0x2d8784d6,0xccf8928a,0x9f4b136f,0x6a6487f1}}, // _egne_, rvě_, _sací_, dòfi,
+ {{0xfc3f026f,0x447b80be,0x442954ef,0x7a2b0380}}, // rií_, ינטע, _vla_, _rütb,
+ {{0x4429177c,0xe5348d15,0x6018023e,0xfc3f01d6}}, // _wla_, _нель, _símp, sií_,
+ {{0x4429011a,0x65638114,0x63a2d4f0,0x69de008e}}, // _tla_, _gynh, kbon, _dbpe,
+ {{0x1ddd05e8,0x36348077,0x4429549e,0xa3dc8105}}, // न्नत, _برچس, _ula_, त्म_,
+ {{0x36d40364,0x645d54f1,0x2d87007a,0xc98747d4}}, // [4850] мотр, mosi, žnem_, _муни,
+ {{0x645d54f2,0x60c70140,0xdd9b3aca,0xc33201c6}}, // losi, pujm, рша_, _נוי_,
+ {{0x63ad0359,0x69c48701,0xdb008118,0x89da0019}}, // _kean, _राजी, _remá, _اوپر_,
+ {{0x645d54f3,0x515a0039,0x63a2a5b3,0xdb009984}}, // nosi, _לכתו, gbon, _semá,
+ {{0xfaa58698,0x7c29d4f4,0x27e941ef,0x63ad0039}}, // чало, _vler, mean_, _mean,
+ {{0x27e954f5,0x628554f6,0x645d0114,0x786586a5}}, // lean_, _jiho, hosi, móvi,
+ {{0xa3cc09a3,0x645d22b3,0x628516f2,0x2484807b}}, // _लाल_, kosi, _miho, _fimm_,
+ {{0x27e92975,0x1bb88b76,0x7c3bd40f,0x62852916}}, // nean_, _واقع_, _umur, _liho,
+ {{0xdb008acf,0xee39920c,0x63bbd4f7,0xe293803d}}, // _temá, йни_, _idun, _آذر_,
+ {{0x628554f8,0xe8098054,0x27e90046,0x661881c0}}, // _niho, वाहा_, hean_, _covk,
+ {{0x63ad54f9,0x1ddd0a74,0xed59d4fa,0x80a480f7}}, // _bean, न्यत, сон_, _يمكن,
+ {{0x63ad154c,0x644bd4fb,0xf0f9800f,0x27e92956}}, // _cean, nngi, ्मीद_, jean_,
+ {{0x27e91f13,0xf1d28935,0x63ad0c49,0xda070540}}, // dean_, _सामन, _dean, शांत_,
+ {{0x1dc72e2b,0xf77084e3,0x877a80be,0xf67580be}}, // _लागत, _شان_, _פאסי, _צײַט_,
+ {{0x7641d4fc,0x62851600,0x63bbd0b9,0x63ad3040}}, // mily, _diho, _odun, _fean,
+ {{0x201954fd,0x27e954fe,0x63ad54ff,0x2bc6016f}}, // _cosi_, gean_, _gean, _वाटा,
+ {{0x0cdb815c,0x6f16809a,0x20195500,0x628501b9}}, // [4860] _बहुम, _życz, _dosi_, _fiho,
+ {{0x63bb9341,0x63a2d501,0xceb40085,0x0e630fe6}}, // _adun, wbon, rsə_, екун,
+ {{0x27e90cdb,0x629bd502,0x20190609,0xceb4011c}}, // bean_, _atuo, _fosi_, ssə_,
+ {{0x69dc5503,0x27e95504,0x20190135,0x9f4280e1}}, // lfre, cean_, _gosi_, ľkú_,
+ {{0x628f82be,0x41d304e5,0x7641d505,0x249f01a1}}, // _écol, _तामस, kily, _čume_,
+ {{0x7848800d,0x69dc2a1d,0x69d587d9,0x3ea00812}}, // pěvk, nfre, _özel, mpit_,
+ {{0xfce3069b,0x64a6084b,0x645d4dc7,0x2d8a2d10}}, // _поро, _хаба, yosi, _igbe_,
+ {{0x25ee8076,0x6ebd86b7,0x7d1c835f,0xafdb0aa2}}, // _असही_, _शिशु, ørsm, _hjør,
+ {{0xae20000f,0x2bd1835a,0x96f88a29,0xafdb035f}}, // _मकान_, _हाता, цепт_, _kjør,
+ {{0x4426d506,0x63ad0635,0x27e90102,0x824a830f}}, // lko_, _sean, zean_, _اشرف_,
+ {{0xa96a99f4,0x645d3b19,0x63ad24db,0x2d8a0a2c}}, // _امام_, tosi, _pean, _mgbe_,
+ {{0xb8658b76,0x9f401378,0x69dc816d,0x27e90102}}, // _قانو, ndió_, _öreb, xean_,
+ {{0x69dc42bb,0xe80980a5,0x4426d507,0x765e02e7}}, // ffre, वारा_, iko_, kopy,
+ {{0x4426824a,0xa3dc873c,0xdb040511,0x7a300106}}, // hko_, त्त_, _adió, _nätd,
+ {{0xa3cc9d01,0x69ce09a3,0x7ae45508,0x44269b02}}, // लला_, _तारी, _epit, kko_,
+ {{0xdc2b0077,0x2d8a0091,0x2d980176,0x6ee201bc}}, // _دسته_, _agbe_, _afre_, _ọbag,
+ {{0x44269ce9,0x62854b25,0x7c2d5509,0xafdb054f}}, // [4870] dko_, _tiho, _ilar, _bjør,
+ {{0x4426d50a,0x27e910a2,0xa3cf81c4,0x682a026f}}, // eko_, sean_, _शाह_, _výda,
+ {{0x7c2d0bfa,0x40350468,0xb83501a0,0xa3d40305}}, // _klar, _женс, _женщ, स्क_,
+ {{0x22430586,0x7641c154,0x644ba7a1,0x7bcf13e0}}, // lijk_, zily, ungi, ngcu,
+ {{0x38691739,0xdcf70085,0x245881e2,0x6493022b}}, // mlar_, raxı, чаць_, _għin,
+ {{0x4426d50b,0xdd868250,0x7c2d009f,0xafdb035f}}, // ako_, _لو_, _llar, _gjør,
+ {{0x7c2d0afe,0xe8099344,0x23bf0b85,0xe61f001c}}, // _olar, वाला_, ्लाद, _nuôi_,
+ {{0x38690b06,0xccf20051,0x44269f01,0xb69b02be}}, // nlar_, _בכל_, cko_, _grâc,
+ {{0x442db7be,0x22430613,0x997e802e,0x443fbd36}}, // _ile_, kijk_, tăţi_, _imu_,
+ {{0x38690380,0x442dd50c,0x7a30156e,0x20d7007e}}, // hlar_, _hle_, _käte, kçi_,
+ {{0x3869550d,0x7ae438b0,0xa2c98540,0xa3ea1597}}, // klar_, _spit, हूर्, одна_,
+ {{0x386907d9,0x4ad18c78,0xca290496,0xdd1c00e1}}, // jlar_, _समाव, _דם_, náša,
+ {{0x3869550e,0xdb0081ec,0x290109ab,0x7c2d010c}}, // dlar_, _gemä, _arha_, _dlar,
+ {{0x442dd50f,0x442680ad,0x68fc0b24,0x64428037}}, // _lle_, zko_, tvrd, zioi,
+ {{0x442dd510,0xa3cc0076,0x7a3000f2,0x443f8c56}}, // _ole_, _लाऊ_, _näte, _omu_,
+ {{0x98a7809a,0x443fd511,0x3869088b,0x3b0001b9}}, // czną_, _nmu_, glar_, _triq_,
+ {{0xe61a10fc,0x7ae4003a,0xf7430b9b,0x64428009}}, // [4880] ода_, _upit, вето, vioi,
+ {{0x442db72c,0x443fd512,0xf1d28dbc,0x7c2d0115}}, // _ale_, _amu_, _साधन, _zlar,
+ {{0x4426d513,0x38695514,0x442d8cde,0x938ab9c3}}, // tko_, blar_, _ble_, осла_,
+ {{0xdca30af2,0x0ea904e5,0x4426d515,0x68310370}}, // таци, _चौकड, uko_, _rådg,
+ {{0x25ee89a3,0x8db6035f,0x5f060196,0x442d801b}}, // _असली_, _осві, _ўзга, _dle_,
+ {{0x442d988e,0x20ee800d,0x443f8365,0x38603715}}, // _ele_, _při_, _emu_, loir_,
+ {{0x26119513,0x35a7800f,0xafdb035f,0xda6580f7}}, // तानी_, _ग़ज़, _kjøp, راهي,
+ {{0xd0108307,0x68e302af,0x442dd516,0x7d03013c}}, // الة_, _ände, _gle_, ænse,
+ {{0xc6f787ac,0x661c5517,0x78ad8067,0x5fbf86b7}}, // дных_, _kork, ćava, _एयरल,
+ {{0x3869017b,0x20ee801b,0x442dd518,0x7c2d0502}}, // zlar_, _tři_, _zle_, _slar,
+ {{0x38693881,0x44fe0110,0x442dd519,0x994a8060}}, // ylar_, _jį_, _yle_, _بلال_,
+ {{0x61f70201,0x224300f3,0x69daa539,0x3869551a}}, // _saxl, wijk_, प्री, xlar_,
+ {{0x6288d51b,0x386912a3,0xe80d0697,0x661c067f}}, // _kido, vlar_, हासा_, _oork,
+ {{0x20d703bf,0x6831006a,0x7d030106,0x78538162}}, // tçi_, _måde, ånsk, măva,
+ {{0x38690a82,0x22430a0f,0x6288d51c,0x64408372}}, // tlar_, rijk_, _mido, _ommi,
+ {{0x7c2d112e,0x6f02ac2d,0x6ce380e8,0x6288d51d}}, // _ular, _kroc, _ріше, _lido,
+ {{0x3869551e,0x78a20d11,0x6b8d551f,0x682a001b}}, // [4890] rlar_, _čove, _igag, _týdn,
+ {{0x6440939a,0x443f8e6d,0x09c600ab,0x24890069}}, // _ammi, _smu_, শ্বা, _hiam_,
+ {{0x24890176,0x442d809f,0x38695520,0x2c720c83}}, // _kiam_, _ple_, plar_, láda_,
+ {{0x38690201,0xdddc01e2,0x447c00be,0xdcfa880a}}, // qlar_, _virš, ַנגע, natı,
+ {{0xa3d40894,0xd6c692dc,0x6288d521,0x661c0bc5}}, // स्ट_, _حق_, _bido, _fork,
+ {{0x24893dd7,0x661c5522,0x863500be,0x6440d523}}, // _liam_, _gork, _טאָג_, _emmi,
+ {{0x9f40007b,0x6288d524,0x1b1780c8,0x22498042}}, // ldið_, _dido, _দেখে_, đak_,
+ {{0x443fb994,0x248901e9,0x6b8d5525,0x628883a8}}, // _umu_, _niam_, _ngag, _eido,
+ {{0xf62588cc,0x9f400125,0x3eb9b74e,0x23695526}}, // едпо, ndið_, ést_, _nyaj_,
+ {{0xa3e210c5,0x6b8d5527,0x644f1984,0x78ba00d2}}, // न्न_, _agag, enci, štvi,
+ {{0x1d09813a,0x6aad54d9,0xb7d58032,0xd46a2857}}, // чени_, _awaf, _aṣet, зине_,
+ {{0x62888503,0xce958698,0x24890282,0x6aad0365}}, // _zido, _задъ, _ciam_, _bwaf,
+ {{0xe72e8785,0x386003d3,0x27ed94d0,0x24895528}}, // _се_, voir_, meen_, _diam_,
+ {{0x27edd529,0x200b04b9,0xc33284de,0x4a750a41}}, // leen_, _anci_, רוך_, выст,
+ {{0xafe60b88,0x50b50e97,0x12d000ab,0x42d000ab}}, // новл, ксту, িবেদ, িবেশ,
+ {{0x27edd52a,0x661c552b,0x248900ff,0x7e61808e}}, // neen_, _sork, _giam_, lolp,
+ {{0x6446552c,0x7bd600b4,0x7a2b1bef,0x38600036}}, // [48a0] miki, _icyu, _kütl, roir_,
+ {{0x68310e23,0x27edd52d,0x7af60034,0x6b9b8365}}, // _råde, heen_, _isyt, _afug,
+ {{0x61d70159,0xf1c98bb8,0x27edd52e,0x41c99a87}}, // _אויף_, _राजन, keen_, _राजस,
+ {{0x6288927b,0x644610fa,0xe3b2830f,0x661c0051}}, // _sido, niki, ارا_, _work,
+ {{0xa3e2073c,0x27edd52f,0x59f88656,0xe803816f}}, // न्य_, deen_, деля_, _शोधा_,
+ {{0x64464c47,0x8cc080d4,0x61fc5530,0x61ee2aec}}, // hiki, _विलो, ndrl, nebl,
+ {{0x64465531,0xdcfa82bb,0xa2cc86b7,0x6288b6d9}}, // kiki, yatı, _समक्, _vido,
+ {{0xf1d28076,0x683e8029,0x27e05109,0x27edc866}}, // _सावन, līdz, nfin_, geen_,
+ {{0x64461d52,0xc5f88029,0x24890282,0x26018fd5}}, // diki, klēt_, _riam_, _वोही_,
+ {{0x2c7593c2,0x4375a59a,0x7e6181ca,0x61ee0140}}, // råde_, _руст, golp, jebl,
+ {{0xa3cc0e70,0x644610fa,0x6f02d532,0x27ed8079}}, // _लाख_, fiki, _wroc, been_,
+ {{0x6f02d533,0x644600b4,0xe80015fb,0x27ed8079}}, // _troc, giki, _लोला_, ceen_,
+ {{0x7d0396a1,0x61fad534,0xdcfa861c,0x248907b6}}, // _erns, _katl, ratı, _viam_,
+ {{0x61ee0799,0x7e6180e5,0xb69b0162,0x2c72016b}}, // gebl, colp, _brân, ráda_,
+ {{0x2bd1823c,0x248901e9,0x61fad535,0xe80d016f}}, // _हाला, _tiam_, _matl, हाला_,
+ {{0xb9090d38,0x63a901df,0x24890748,0x64465536}}, // _यह_, ñend, _uiam_, ciki,
+ {{0x2ba70f3d,0x9df915b1,0xa2cc8b84,0xe7e293ba}}, // [48b0] _क्या, мнат_, _समग्, क्या_,
+ {{0x27e05537,0xb69b0493,0x6b9b847f,0x04b78019}}, // afin_, _frân, _sfug, _تھیں_,
+ {{0x27ed82c1,0xbc6807d2,0x682302f1,0x92ad8264}}, // yeen_, _زمین_, _sõdu, _কমে_,
+ {{0x09c98d14,0x27edd538,0x442b0140,0x683e8196}}, // रल्य, xeen_, nkc_, būdi,
+ {{0x61fa878c,0x27edbb2d,0x321e81d0,0x9f5903ed}}, // _batl, veen_, _boty_, _masë_,
+ {{0x64465539,0xceb200be,0x321e81e0,0x9f44823e}}, // ziki, _דיך_, _coty_, demà_,
+ {{0x27edd53a,0x2055a606,0x260e1513,0x6446553b}}, // teen_, ктир, ठारी_, yiki,
+ {{0x2605491d,0x6e200162,0x7a302651,0x6446011b}}, // _होती_, _îmbr, _mäta, xiki,
+ {{0x27ed9322,0x03ec00ab,0x64462d5c,0x61fad53c}}, // reen_, য়ামী_, viki, _fatl,
+ {{0x27edd53d,0x38668057,0x26c38353,0x26c900d2}}, // seen_, _ekor_, čjo_, irao_,
+ {{0x6446553e,0x853c8110,0x7e61d53f,0x7a300338}}, // tiki, lbėj, rolp, _näta,
+ {{0x8c460087,0x61aa036d,0x61fad540,0x61e51995}}, // теме, कर्ष, _zatl, _abhl,
+ {{0x386dd541,0x26cd85e4,0xdb099727,0x6d588144}}, // mler_, queo_, _ideó, _exva,
+ {{0x64465542,0xa2c4858c,0x66828019,0x7ae68106}}, // siki, _रिस्, _پیپل, _äkte,
+ {{0x64d0023c,0x386d9486,0xa06a0323,0xa967164f}}, // _हमेश, oler_, дава_, вира_,
+ {{0x2c08000f,0x386d87d9,0x70e218b8,0x7ae98009}}, // षाओं_, nler_, _पहील, _opet,
+ {{0xa0670572,0xc7b2893f,0x18671abe,0x60dc5543}}, // [48c0] тата_, יבן_, тати_, strm,
+ {{0x386d82af,0xdee30256,0x44205544,0xa9261b67}}, // hler_, рофи, _hoi_, _адол,
+ {{0x386dd545,0xd49900e8,0x61fad546,0x387fb55e}}, // kler_, ері_, _ratl, kmur_,
+ {{0x4420002e,0xf7678c2a,0xad1a8158,0x351a80be}}, // _joi_, _نا_, ווער, ווענ,
+ {{0x80e2101b,0x44205547,0x386d93ca,0xe36391d5}}, // _पहुं, _moi_, dler_, акци,
+ {{0x44205548,0xa3e2005e,0x386dcac3,0x78758110}}, // _loi_, न्त_, eler_, _išvy,
+ {{0x6444194c,0xdb0f0626,0x386dd549,0x6e43024f}}, // _omii, vací, fler_, _терз,
+ {{0x4420554a,0x386d8f06,0xc6f8ad6b,0x7643be43}}, // _noi_, gler_, ених_, _amny,
+ {{0x09b800c8,0x61fabeab,0xa3cc0fb8,0x78a90024}}, // _জানা, _tatl, _लाज_, ćevi,
+ {{0xceb30039,0x442001a8,0x35e380e8,0x64442223}}, // סית_, _aoi_, ацюв, _amii,
+ {{0x386d82fe,0x4432079f,0xd7ef8193,0x29058503}}, // bler_, _bly_, _ку_, _grla_,
+ {{0x64440b99,0x4420554b,0x6d4300f7,0x07a684ae}}, // _cmii, _coi_, únai, _радн,
+ {{0xa3e200bc,0x27e6c040,0x290c0106,0x442001c6}}, // न्ध_, _ibon_, ådan_, _doi_,
+ {{0x442008f1,0x2c5600eb,0xb7d5846d,0x7c208f35}}, // _eoi_, rāda_, _iṣat, _lomr,
+ {{0x442007e1,0x44320367,0x6d459517,0x2905554c}}, // _foi_, _fly_, _žhav, íla_,
+ {{0xceb40086,0x26c902a5,0x44200c4b,0xa3e200a5}}, // yyət_, trao_, _goi_, न्द_,
+ {{0x41d283b7,0x09ca80c8,0x7a220036,0xda028072}}, // [48d0] _सांस, ল্যা, _vôtr, लयात_,
+ {{0xe7e28519,0x386d8457,0x387f81bf,0x0e3405a8}}, // क्ता_, zler_, zmur_, аную,
+ {{0x7ae9d54d,0x386d8214,0x2d8703e3,0xee843d31}}, // _spet, yler_, äne_, рыто,
+ {{0xa2c4823c,0xdb0f0187,0xd01101a8,0x7a3006ae}}, // _रिश्, cacã, _الح_, _jätn,
+ {{0x3e7980e7,0x7c2b9c11,0x386dd54e,0x7a300106}}, // lète_, rkgr, vler_, _mätn,
+ {{0x386dcfe2,0x27e6a1e5,0x853c8084,0xde888129}}, // wler_, _abon_, rbėj, _xịt_,
+ {{0x09b800c8,0x386dd54f,0x2166d550,0x3e7980e7}}, // _জামা, tler_, лиши_, nète_,
+ {{0xb8dba0d5,0x224789ff,0x8bbf1869,0x386dd551}}, // _अब_, sink_, ्लोअ, uler_,
+ {{0x44205552,0x386dd553,0x3b55c907,0x764381c0}}, // _roi_, rler_, _скар, _pmny,
+ {{0x69c1813c,0x386dd554,0x44205555,0x7c2080e1}}, // _udle, sler_, _soi_, _zomr,
+ {{0xc3291a63,0xb7d58b76,0x44200698,0x386dcd6e}}, // _צו_, _مقاب, _poi_, pler_,
+ {{0x22998205,0x41dfd556,0x248d822b,0xe7b78651}}, // _kèk_, प्रस, _jiem_, _इजिप,
+ {{0x44205557,0xe2970912,0x27fd831d,0x55e58071}}, // _voi_, лая_, _iawn_, _болб,
+ {{0xed598468,0x628f82be,0x27fd84b7,0x442000dd}}, // _пол_, _écou, _hawn_, _woi_,
+ {{0x68311fdd,0x44205558,0x7d1c805f,0xde8880ff}}, // _såda, _toi_, ørst, _vịt_,
+ {{0xc2f980c8,0xf7720064,0x6444009c,0x69cd83b7}}, // _আপনি_, ماء_, _umii, _साझी,
+ {{0x6729ba2f,0x27ffd559,0x63b997ac,0x7d030366}}, // [48e0] nyej, idun_, dawn, ænsn,
+ {{0xc3330051,0x3e79bafb,0x248200dd,0xe8fa1bf2}}, // _לוח_, bète_, lmkm_, _зло_,
+ {{0x7c208bcf,0x63b63cc0,0x200fc802,0x69db0b80}}, // _pomr, _keyn, _ongi_, đuet,
+ {{0x442087ca,0x629626bd,0x229986c4,0x981601a8}}, // ði_, _kuyo, _bèk_, _إبدا,
+ {{0x61fe2315,0x248dd55a,0x27f201b4,0x229988f9}}, // _kapl, _diem_, meyn_, _cèk_,
+ {{0x26d205b0,0x673b80e1,0x2ee002f7,0x63b6555b}}, // muyo_, dzuj, mtif_, _leyn,
+ {{0xc31c80ab,0x76488242,0x20000079,0x7c20d55c}}, // _দেখি_, vidy, odii_, _tomr,
+ {{0xba010076,0x26c2803a,0x61fe03ec,0x76aaaa98}}, // _ऊफ्फ_, škom_, _lapl, нтов_,
+ {{0x2ee024df,0x96348293,0x7648b24d,0x9f593762}}, // ntif_, иниц, tidy, _pasé_,
+ {{0x14ca8364,0xa3e203dd,0xf2978039,0x6fa48935}}, // ными_, न्स_, _עכבר_, _ग्रं,
+ {{0xb9939e95,0x63b6062a,0x6aa43f1e,0xd7e680e8}}, // _المب, _beyn, _atif, _біло,
+ {{0xc0a984e3,0x6f04011f,0x62961ea2,0x8706804e}}, // _شامل_, kvic, _buyo, _مبتل,
+ {{0xfe0405e8,0x200002c1,0x051480c8,0x629606a5}}, // रयास_, ddii_, ানের_, _cuyo,
+ {{0xad26803d,0x62962bc6,0x69351073,0x9f4b01a8}}, // _مربو, _duyo, ангу, _tacú_,
+ {{0x61fe1f2c,0xd5b88029,0x2611d55d,0xa3e24476}}, // _dapl, skās_, तारी_, न्ह_,
+ {{0x629600b9,0x3e798036,0x50674bfc,0x63b60df6}}, // _fuyo, rète_, утга, _geyn,
+ {{0x6296123c,0x3da7197b,0x248d9c11,0x26d21fb7}}, // [48f0] _guyo, греб, _riem_, guyo_,
+ {{0x7aed0637,0x61fe2f31,0x2d9e802e,0x63b607c0}}, // _ipat, _gapl, ncte_, _zeyn,
+ {{0x2ee00458,0xe817800d,0xb65a10ab,0xfbdf001c}}, // atif_, थामा_, nrẹ́, hiêm_,
+ {{0x7aed0870,0x81bc81a9,0x26d21fdb,0x3e9701a8}}, // _kpat, _slēp, buyo_, _مؤسس,
+ {{0x26c28a20,0x2ee0555e,0x63b9809a,0x248d80e1}}, // škoj_, ctif_, rawn, _viem_,
+ {{0x3ea90393,0x65943079,0x248d809a,0xdd940084}}, // mpat_, _гару, _wiem_, _гары,
+ {{0x248d80eb,0x4439555f,0x3b090122,0x3ea95560}}, // _tiem_, khs_, _lraq_, lpat_,
+ {{0xaec595d2,0x7aed1249,0x63ad85e4,0x2ba710be}}, // абил, _opat, ñand, कुमा,
+ {{0x63b60125,0xa3d923e6,0xdb268065,0x644bd561}}, // _reyn, ालन_, _پولی, migi,
+ {{0x644bd562,0xcee881a8,0x7d098eef,0xdb0f0118}}, // ligi, ارين_, вног_, xacá,
+ {{0xed598767,0x6aa40ed0,0x7aed4022,0x59dc1344}}, // тон_, _stif, _apat, _बाबर,
+ {{0xe29a34fb,0x69c55563,0x644bd564,0x63861071}}, // кам_, _adhe, nigi, игна,
+ {{0x61fe1482,0x683801d0,0x63bb008b,0x628f8866}}, // _papl, _jíde, ðuna, _écos,
+ {{0xa2cd80ba,0x644bc378,0x63b60079,0x27f20079}}, // _सिन्, higi, _weyn, weyn_,
+ {{0xa96a2481,0x60262005,0x69d7073c,0x644bd565}}, // тива_, адва, _बारी, kigi,
+ {{0x32040063,0x44392331,0x3db200ab,0xa3e580d4}}, // śmy_, chs_, _চাইল, _बॉल_,
+ {{0x6e23d566,0x6aa45567,0x645d5568,0xde8880ff}}, // [4900] _konb, _utif, érie, _kịp_,
+ {{0xee373fe7,0x09e400c8,0x35dd8cfd,0x2ee05569}}, // ану_, _ফোরা, _माफ़, rtif_,
+ {{0x644b8cb5,0x7c24113f,0xbef28f85,0x37e615b7}}, // figi, _koir, _अन्न_, _конг,
+ {{0xa3d9085d,0x644b82b8,0x7aed0110,0x2ee0556a}}, // ालय_, gigi, _ypat, ptif_,
+ {{0x7c24051e,0x69c1008b,0x7aed0bfd,0x539b01c6}}, // _moir, ðleg, _xpat, דינו,
+ {{0x7c24556b,0x98c68cec,0x6e238cdb,0x7a34851e}}, // _loir, асил, _nonb, _bàta,
+ {{0x644bd56c,0x7a3487f1,0x7afbd56d,0x26c00084}}, // bigi, _càta, _esut, lsio_,
+ {{0x58868048,0x7c2400e7,0xb8ce8665,0xb8fe864a}}, // шыла, _noir, _कऽ_, _तम_,
+ {{0xe7e28e70,0xe29aabca,0xfba70eed,0x4424d56e}}, // क्षा_, _раз_, _क्षम, _iom_,
+ {{0x4424d56f,0x26c00428,0x7c242305,0xb3b00264}}, // _hom_, isio_, _aoir, _কাগজ,
+ {{0x4424d570,0x7aed5571,0x7c241581,0x0d970039}}, // _kom_, _spat, _boir, יכים_,
+ {{0xdee6a410,0x4424d572,0x7c245573,0x66e6b317}}, // _води, _jom_, _coir, _вода,
+ {{0x7ae2d574,0x7c24032f,0x26cd8333,0xa3ce80c2}}, // ntot, _doir, creo_, _शयन_,
+ {{0x4424d575,0x05dd835a,0xfba72769,0xb7e299e8}}, // _lom_, _मायब, _क्रम, क्रम_,
+ {{0x7c245576,0x644b81d3,0x4424d577,0x6835816b}}, // _foir, yigi, _oom_, _háda,
+ {{0x4424d578,0x3ea95579,0x7c240014,0x5f4500f7}}, // _nom_, tpat_, _goir, منزل,
+ {{0xdb009ee6,0xe7f9873c,0x3ea9557a,0x7aed557b}}, // [4910] _memó, ्यता_, upat_, _upat,
+ {{0x63bd557c,0x313530c6,0x3ea9121e,0x69c5557d}}, // lasn, _геор, rpat_, _udhe,
+ {{0x4424a727,0x03ec00ab,0x26cd8699,0x78a9017f}}, // _bom_, য়ারী_, zreo_, ćevs,
+ {{0x3ea6859c,0x3ea91c9c,0x68380511,0xa2ce0cf0}}, // _otot_, ppat_, _píde, _तिम्,
+ {{0x4424d57e,0x81ae80c8,0x09df80c8,0xdb0081ec}}, // _dom_, _কাজ_, _যোগা, _gemü,
+ {{0x683803fd,0x644bd57f,0xc244902a,0x44c4846d}}, // _víde, sigi, онік, _ajọṣ,
+ {{0x63bd0025,0x644b87fe,0x4424a53d,0xd90f804e}}, // kasn, pigi, _fom_, بیا_,
+ {{0x2ba71370,0x9b898bbe,0x26cd8118,0x7afb80fc}}, // _क्ला, _منزل_, treo_, _tsut,
+ {{0x63bd0052,0x600681bb,0xdb00816a,0x68358118}}, // dasn, сным_, _demó, _cáda,
+ {{0x7c244d68,0x26cd960a,0xee368a7f,0x6e3a8aff}}, // _soir, rreo_, йны_, ghtb,
+ {{0x2fd98b76,0x44248069,0x7c241390,0x26cd8042}}, // _مواد_, _yom_, _poir, sreo_,
+ {{0xca29004c,0x3ebf978b,0xd79580f7,0x60d50289}}, // _אם_, psut_, _الإخ, duzm,
+ {{0x6e238247,0x7c2400e7,0x59dc1130,0x320102c4}}, // _tonb, _voir, _बादर, _bahy_,
+ {{0x6e3a8a11,0xe2970764,0xa2da873c,0x09b800ab}}, // chtb, сар_, _नमस्, _জাহা,
+ {{0x7c240ad0,0x63bd09da,0x64498118,0x27eb0326}}, // _toir, basn, _omei, _bbcn_,
+ {{0x5eeec825,0xdb008722,0x81ae8264,0x1cb681c6}}, // _अहम्_, _memò, _কাছ_, _פלפל_,
+ {{0x4424d580,0xe7e9853e,0x7a301e71,0x6f1d92d2}}, // [4920] _rom_, झ्या_, _jätk, äsch,
+ {{0x44248bfa,0x6449905f,0x6ecf800d,0x26c05581}}, // _som_, _amei, _दिनु, rsio_,
+ {{0x442490af,0xdcea8d11,0x26c00652,0x69fb007c}}, // _pom_, čiće, ssio_, _גליק,
+ {{0xe29a8196,0x746980e8,0x7ae2d582,0xf487853d}}, // гад_, арів_, ttot, مانی,
+ {{0x4424d583,0xa3d90996,0x853c8110,0xdb0083a8}}, // _vom_, ालत_, lbėt, _remó,
+ {{0x78a202fd,0x7ae2d584,0x2002022e,0xc5d5035f}}, // _čovj, rtot, _haki_, _місь,
+ {{0x2002059c,0x63a40136,0xf41f0009,0x69d8d0e9}}, // _kaki_, _ofin, yjä_, rgve,
+ {{0x6d5700be,0x2002009a,0x628f81e8,0x63a40144}}, // _הילף_, _jaki_, _ècon, _nfin,
+ {{0x368abd93,0x3b07932a,0x41e081af,0x501b0039}}, // лсон_, _лето_, _नापस, רופו,
+ {{0x20021dde,0x63a400a2,0x63bd0ec9,0x2a6a3192}}, // _laki_, _afin, wasn, jobb_,
+ {{0xe85384c0,0x63bd5585,0x69c89acf,0xdb00802a}}, // _کنند, tasn, _idde, _temó,
+ {{0x24805586,0x63a2d587,0x49178039,0x20025588}}, // _chim_, ncon, _לקבל_, _naki_,
+ {{0x7e68826f,0x63a40122,0xb8658250,0x3ea680e4}}, // podp, _dfin, _بالو, _utot_,
+ {{0x63a280b9,0x290c826c,0x63a45589,0xe5348ea6}}, // hcon, _krda_, _efin, _мель,
+ {{0x20021037,0x28d91094,0x25dd8074,0xdddc2c62}}, // _baki_, बंधि, _खाती_, _birž,
+ {{0x290c8bcf,0x2002558a,0x98a501cc,0x25be8122}}, // _mrda_, _caki_, çlı_, latl_,
+ {{0x69c89fd4,0x63a2b0b2,0x2002558b,0xf7730b53}}, // [4930] _odde, dcon, _daki_, سار_,
+ {{0x290cd58c,0x7a300338,0xdceb01d6,0x2007066f}}, // _orda_, _näth, žiči, śni_,
+ {{0x5f95002e,0x7f95174a,0xadea8074,0x59d28072}}, // _минт, _манх, ज्यन_, _साजर,
+ {{0x69d804b8,0x69dbae2b,0xfbd08077,0x69c88051}}, // _över, _नाही, فته_, _adde,
+ {{0x387e02be,0xdc3984b7,0x2be1103e,0x290cb25b}}, // ître_, għġb, _कानू_, _arda_,
+ {{0x290c82a5,0x27e9031d,0x2002558d,0x644f558e}}, // _brda_, lfan_, _zaki_, hici,
+ {{0xa3d90894,0x644f00a4,0x200200a4,0x6615190f}}, // ाला_, kici, _yaki_, _inzk,
+ {{0x2bdb09a9,0x63a28098,0x09ca80c8,0x27e903ec}}, // _भाषा, ccon, ল্লা, nfan_,
+ {{0xee39a80f,0xcdf5a1f6,0x25dd9344,0x290c8144}}, // ини_, очны, _खादी_, _erda_,
+ {{0x63a4558f,0x7bdf4d02,0xa6e2007b,0x24805590}}, // _sfin, _acqu, æðin, _shim_,
+ {{0xcb09073a,0x24805591,0x63bb8057,0x61558019}}, // _של_, _phim_, _keun, _بنائ,
+ {{0x63bb83d3,0x644f02be,0x59dc0321,0x645983ec}}, // _jeun, gici, _बाहर, nnwi,
+ {{0xe29a47ac,0xa3e19299,0x67d48221,0x41e092e0}}, // раг_, _दान_, _хочу, _नामस,
+ {{0x3ead91ee,0xa509a8a1,0x20025592,0x63bb8b2e}}, // mpet_, _века_, _saki_, _leun,
+ {{0x290c5593,0xf41f0364,0x24805594,0x27e929dc}}, // ída_, mmän_, _thim_, ffan_,
+ {{0x6603d595,0xf41f0c3c,0x670505fc,0x59dc00d4}}, // _hank, lmän_, रिंक_, _बावर,
+ {{0x66038f38,0x7e2615e0,0x63a2826c,0x3e6f0019}}, // [4940] _kank, одаж, vcon, yütt_,
+ {{0xe6172fc4,0x66038b48,0x63a2b907,0x2002054e}}, // оду_, _jank, wcon, _waki_,
+ {{0x69dc5596,0x6835826f,0x63bb9670,0x20025597}}, // mgre, _nádo, _beun, _taki_,
+ {{0x69dc11f8,0x6603d598,0xd00a0895,0x717b0051}}, // lgre, _lank, _теме_, _כניס,
+ {{0x6f0985f5,0xd838a0f9,0x63a2d599,0xda668098}}, // jvec, _juče_, rcon, овди,
+ {{0x628194f2,0x2486807d,0x683c82be,0x63a2a0ef}}, // _chlo, smom_, _méde, scon,
+ {{0x644f559a,0x63a2890d,0xed571a1b,0x6ab7559b}}, // yici, pcon, _дор_, _अब्र,
+ {{0x2120803b,0x3ec6b3d9,0x6603a1cf,0x63bb8d00}}, // ćih_, особ, _aank, _geun,
+ {{0xb5fc84b7,0x31698380,0x7c22a127,0x2be10105}}, // loġi, ğaza_, mjor, _काबू_,
+ {{0x69c88efa,0x66038823,0x644f009a,0x2002807a}}, // _udde, _cank, wici, žki_,
+ {{0x66038faf,0xa3e1800f,0xafe6835f,0x644f1b7d}}, // _dank, _दाम_, _можл, tici,
+ {{0x09d3035a,0x7c22d59c,0x1ee980d7,0x660d89ab}}, // तल्य, njor, جویی_, _ɗaki,
+ {{0x6603d59d,0x6756804e,0x261a847d,0xdddc01b9}}, // _fank, _بخار, बादी_, _birż,
+ {{0x644f2d11,0x7e04000c,0x38698024,0x69dc559e}}, // sici, रयोग_, čare_, ggre,
+ {{0x26050063,0x7a3f001b,0xe3bf03a8,0xf84b02a9}}, // _होगी_, vště, pañ_, ачай_,
+ {{0x26c2816b,0x60c901a9,0x2d98559f,0x68380144}}, // škou_, ņemo, _agre_, _pída,
+ {{0xdd9b0fbe,0x27e91a5a,0x9f4b0722,0x7c22d5a0}}, // [4950] аше_, rfan_, _racó_, djor,
+ {{0x44290104,0x61f8a4b2,0x26c4a29d,0x63bbd5a1}}, // _hoa_, jevl, ismo_, _seun,
+ {{0x6aa9d5a2,0x4429011b,0x201601ac,0x644d00ee}}, // _stef, _koa_, ácií_, _kmai,
+ {{0x7bdd55a3,0x442955a4,0x7c22b28c,0x29015128}}, // ngsu, _joa_, gjor, _isha_,
+ {{0x442955a5,0x78a2005c,0x62818a2a,0x68310aa2}}, // _moa_, _čovi, _phlo, _rådh,
+ {{0xceb23e22,0x442955a6,0x2c5601a9,0x6f0f0037}}, // _איך_, _loa_, rādi_, _ircc,
+ {{0x628f81e8,0x6603d5a7,0x644d0198,0x63bb9c11}}, // _ècom, _rank, _omai, _teun,
+ {{0x442955a8,0x59d789a3,0x66038a76,0x6f09816d}}, // _noa_, _ठाकर, _sank, tvec,
+ {{0x6603d5a9,0xef1697ae,0x69dc407e,0xf1a79ef3}}, // _pank, змы_, ygre, _дрен,
+ {{0xee39964f,0x644d195f,0x683c9a90,0x3eb901ec}}, // сно_, _amai, _réde, _mwst_,
+ {{0x4429156b,0x7c29c6f7,0x6603d5aa,0x7c3b9651}}, // _boa_, _koer, _vank, _klur,
+ {{0x442907f4,0x3ead904a,0x6603985b,0x7c29cff6}}, // _coa_, ppet_, _wank, _joer,
+ {{0x66039bb8,0x4429510e,0x69dc55ab,0xc05280be}}, // _tank, _doa_, tgre, _אזא_,
+ {{0x683c8019,0xd838812b,0xc60a06a7,0x2496803d}}, // _véde, _vuče_, _होंठ_, _کنيد_,
+ {{0x7c3ba192,0x28cf914f,0x3eb90114,0xe7e2815c}}, // _olur, सूचि, _awst_, क्का_,
+ {{0x7a30016d,0xd83881dd,0xb8f40cf0,0x644d55ac}}, // _nätv, _tuče_, _हट_, _gmai,
+ {{0xe7e3959a,0x7d0e05b7,0xee3a0c9b,0x2fc00224}}, // [4960] _गाना_, _srbs, бна_, raig_,
+ {{0x7c3bd5ad,0x2fc055ae,0xd6cf0199,0x3ea003ed}}, // _alur, saig_, اقل_, rqit_,
+ {{0x7c2990f4,0x399b012a,0xb3a982bb,0xa06a0071}}, // _boer, _לייד, ğını, _гана_,
+ {{0x386d89c4,0xb3a9817b,0xd6d9809a,0x7c29d5af}}, // joer_, şını, kuł_, _coer,
+ {{0x7c229c19,0x224c808e,0x320582f9,0x08e68264}}, // rjor, _pmdk_, _baly_, কিছু_,
+ {{0xd5b20b76,0xa2cdbb04,0x38a38059,0x6831128d}}, // _سفر_, _सिर्, _sıra_, _rådi,
+ {{0x490f275c,0x61f8d5b0,0x60c80174,0x764e0314}}, // डियो_, revl, ádmh, _imby,
+ {{0x61f8812b,0x97148698,0x8f468098,0xd6da80e8}}, // sevl, дмиц, зход, бто_,
+ {{0xbbd881a2,0xa2ce008e,0x26c48196,0x7a3d83ec}}, // डलीक, _तिष्, usmo_, _sète,
+ {{0xba29ca1c,0x2d98835f,0xceb30039,0x7bc0007b}}, // _وسلم_, øre_, עית_, ðmun,
+ {{0x25dd8076,0xfc3f00f7,0x386d9a40,0xe0d28019}}, // _खारी_, thí_, boer_, _سزا_,
+ {{0x2fc08125,0x897b80be,0x6249016b,0x644d0609}}, // _þig_, _פראצ, nžov, _qmai,
+ {{0x288b84c0,0x44290033,0x7afd00f3,0x764e01a3}}, // _اصلی_, _voa_, uwst, _omby,
+ {{0x26c28110,0x6f02b392,0xa2ce01d0,0x7de69ab3}}, // škos_, _isoc, _तिर्, _رستم,
+ {{0x0cd700c8,0x442955b1,0xae0a001b,0x61142133}}, // _সন্ত, _toa_, _होइन_, ндру,
+ {{0x644d3d80,0xa2cd815c,0x940b954b,0x764e471f}}, // _umai, _सिल्, _सोंच_, _amby,
+ {{0x7c299151,0x387e0036,0x7bcd02f9,0x92080ec3}}, // [4970] _roer, îtra_, _idau, _brāļ,
+ {{0xd3580039,0x290101c0,0x248482c4,0x6aad00b8}}, // _משחק_, _tsha_, _ahmm_, _itaf,
+ {{0x7c3bd5b2,0xd7dc853e,0x7c299ab3,0xdb04007b}}, // _plur, _यांच, _poer, _heið,
+ {{0x442987ca,0x628a81b0,0x683801d0,0x645d55b3}}, // ða_, emfo, _jídl, nnsi,
+ {{0x7c298b3c,0xa3e20ebf,0x25dd93ba,0x386d9ad7}}, // _voer, न्ट_, _खाली_, voer_,
+ {{0x20090110,0xae0a016f,0x629f008e,0x2484808e}}, // ldai_, _होऊन_, _muqo, _ehmm_,
+ {{0x6f02d5b4,0xdb040125,0x7c298b3c,0x2ee90125}}, // _asoc, _leið, _toer, ltaf_,
+ {{0x200900dd,0x77f981cb,0x7a3002af,0xd6d9809a}}, // ndai_, ्येक_, _hätt, tuł_,
+ {{0xfaf09368,0x2ee9031d,0x7a300074,0xd7068009}}, // _مثل_, ntaf_, _kätt, дные_,
+ {{0x7bcd0012,0xf1a79289,0x7a3055b5,0x629755b6}}, // _adau, _хран, _jätt, _nixo,
+ {{0x20068307,0x6607003b,0x69c39473,0x6f0d0042}}, // _faoi_, _majk, mane, hvac,
+ {{0x7a3000f2,0x69c383fd,0x6607005c,0x6fd6880a}}, // _lätt, lane, _lajk, rücü,
+ {{0x20090355,0x7a348a2a,0x261ab26c,0x26c28088}}, // ddai_, _bàth, बाशी_, škor_,
+ {{0x27ed82af,0x660728fc,0x20090110,0x7a301a50}}, // lfen_, _najk, edai_, _nätt,
+ {{0x63a9d5b7,0x261a890f,0xafe600e8,0x6f0d828f}}, // _ofen, बारी_, мовл, _šach,
+ {{0xf7721e95,0x6d4509ca,0x629716cb,0x6b9bd5b8}}, // ناء_, dzha, _eixo, _ngug,
+ {{0x7a3004b8,0x69c3d5b9,0x2cad02ee,0xf4130039}}, // [4980] _bätt, kane, _čeda_, _יפה_,
+ {{0x201955ba,0x69c3d5bb,0x63a9846d,0x6607111b}}, // _ensi_, jane, _afen, _cajk,
+ {{0x621b093f,0x61fc07d5,0xafdb0edd,0x290400eb}}, // _וויק, merl, _blød, āmas_,
+ {{0x25fa101c,0x61fc16fa,0x629f208e,0xa3e255bc}}, // _उसकी_, lerl, _yuqo, न्च_,
+ {{0x69c3829b,0xdb0081ac,0x62491c24,0x59d2238c}}, // fane, _nemô, ržov, _तयार,
+ {{0x69c38d23,0x27e055bd,0x27edaba9,0x320a422a}}, // gane, lgin_, efen_, ndby_,
+ {{0x27edc964,0x26f88576,0x2c0e0063,0x63a9bf3a}}, // ffen_, ्ट्र_, ियां_, _ffen,
+ {{0x27e02031,0x66072cf7,0x61e901dd,0x161c016f}}, // ngin_, _zajk, đeli, नावर_,
+ {{0xa3e203bb,0x644280f7,0x61fc2315,0x60dc010c}}, // न्छ_, mhoi, kerl, hurm,
+ {{0x38698025,0x4426ad6d,0x6838026f,0x61fc0669}}, // čara_, ljo_, _sídl, jerl,
+ {{0x61fc55be,0x7bc0bb0f,0xb143802e,0x6aad4ef7}}, // derl, _kemu, _инфл, _staf,
+ {{0x7bc08b17,0x6835826f,0x64428009,0x6aa0acc3}}, // _jemu, _nádh, nhoi, _kumf,
+ {{0x7bc09ca6,0xf047819f,0x61fc55bf,0x645d46be}}, // _memu, _تعلی, ferl, rnsi,
+ {{0x7bc0c980,0x60dc022b,0xdb04007b,0xc5d580ab}}, // _lemu, furm, _veið, ত্বপ,
+ {{0x7a3004b8,0xdb1d008b,0xa3e70f97,0x8c671071}}, // _rätt, masí, _पाप_, _отид,
+ {{0x7a302e6d,0x27e02475,0x7bc09517,0x6f0d00fe}}, // _sätt, ggin_, _nemu, tvac,
+ {{0x61fc07d9,0x4426854b,0x290c016d,0x6aad0010}}, // [4990] berl, djo_, ådar_, _utaf,
+ {{0x61fc0693,0x2ca704c3,0xab5dc6e9,0x60dc55c0}}, // cerl, índo_, _jeże, burm,
+ {{0x69c3d5c1,0xb3a98059,0x63a9d5c2,0x7bc08019}}, // vane, şımı, _pfen, _bemu,
+ {{0x44390367,0x69c3d5c3,0x7bc080ee,0x09d7816f}}, // nks_, wane, _cemu, ठल्य,
+ {{0x69c3afb9,0xe5c705c2,0x7bc0d5c4,0xa3e7035a}}, // tane, _осво, _demu, _पान_,
+ {{0xdd868bbe,0xd3380051,0x683c80e7,0xa2d720d8}}, // _مو_, ורדה_, _réda, _बिन्,
+ {{0xdb0b8013,0x27ed87d9,0x3e74016d,0x2eff8239}}, // tagó, tfen_, rätt_, wwuf_,
+ {{0x6442d5c5,0x44390a0f,0xb91a0a2c,0x7bc0978f}}, // choi, jks_, _anyị_, _gemu,
+ {{0x443fd5c6,0x27edd5c7,0xf70880ff,0x9f4b016b}}, // _ilu_, rfen_, _củ_, _obcí_,
+ {{0x442d81d8,0x443955c8,0x7bc082ee,0xab84a063}}, // _hoe_, eks_, _zemu, _руск,
+ {{0x442dd5c9,0x443fa08b,0x7af655ca,0x7c2d1bd4}}, // _koe_, _klu_, _spyt, _boar,
+ {{0x69c1d5cb,0x290582a3,0xd8389487,0xb91a01bc}}, // _hele, _isla_, _tuča_, _enyị_,
+ {{0x2bd0816f,0x442db2b3,0x7c2d0162,0x3f9d8133}}, // हणता, _moe_, _doar, _igwu_,
+ {{0x69c1d5cc,0x442d8006,0x97568039,0x60dc55cd}}, // _jele, _loe_, _אינו_, turm,
+ {{0x7c2d0012,0x69c18855,0x3da6835f,0x443fd5ce}}, // _foar, _mele, _якщо_, _olu_,
+ {{0x69c1d5cf,0x02df88fd,0x442d8cde,0x443955d0}}, // _lele, _नमून, _noe_, cks_,
+ {{0x7bc629b3,0x683585e4,0x27e055d1,0x068601cf}}, // [49a0] maku, _cádi, rgin_, еган,
+ {{0x7bc0845c,0x7bc60393,0x2d9c0bc5,0x69c1d5d2}}, // _semu, laku, øve_, _nele,
+ {{0x7bc0859c,0x442d957a,0x443f956f,0x644280f7}}, // _pemu, _boe_, _blu_, thoi,
+ {{0xdd8f12dc,0x09c8816f,0x4426d5d3,0xa06a0071}}, // حوم_, रण्य, ujo_, _хама_,
+ {{0x68e300f2,0x4426bf7e,0x443f89ca,0x7794026a}}, // _ändr, rjo_, _dlu_, _شیطا,
+ {{0x443fd5d4,0xfce63de7,0x78a1d5d5,0x3f9dd5d6}}, // _elu_, _пого, _bulv, _agwu_,
+ {{0x7bc63d5e,0x7bc0d5d7,0x443fa543,0x644281a8}}, // kaku, _temu, _flu_, phoi,
+ {{0x38603b8d,0x68e303ba,0xfba483eb,0x6b840135}}, // nnir_, _ånde, _ओलिम, _ozig,
+ {{0x6440d559,0x69c18065,0x7bc655d8,0x09d800ab}}, // _ilmi, _fele, daku, দ্যা,
+ {{0x69c1d5d9,0x6e2e0094,0x443f80d2,0x7c2d095d}}, // _gele, _jobb, _zlu_, _soar,
+ {{0x6e2e3fe4,0x78a18aa2,0x7c2d0087,0x3860007b}}, // _mobb, _gulv, _poar, knir_,
+ {{0x61e5034a,0x7bc606a0,0xf708801c,0xda1e8075}}, // _schl, gaku, _tủ_, पावत_,
+ {{0xa3e7146d,0x629ab22a,0x443955da,0x2aab0656}}, // _पाठ_, _kito, rks_, ятно_,
+ {{0x44392302,0x171b8039,0x6e2e228c,0x629ad5db}}, // sks_, _מודע, _nobb, _jito,
+ {{0xa3e183db,0x6b8452a1,0x7bc638ba,0x2cad017f}}, // _दाल_, _ezig, baku, _čedo_,
+ {{0x3860007b,0x4ea711d2,0x629ad5dc,0xc6a72138}}, // gnir_, ержа, _lito, ержи,
+ {{0x63ad031d,0x64438168,0x63bb008b,0x7c3d03ed}}, // [49b0] _ifan, ënim, ðunu, ërru,
+ {{0x629ac193,0x443fb644,0x6e2e1c33,0x6440d5dd}}, // _nito, _slu_, _cobb, _almi,
+ {{0x6e2e1277,0xf65380be,0x69c73f01,0x442dd5de}}, // _dobb, נצע_, maje, _poe_,
+ {{0x69c70dd1,0x13a78077,0x629ab673,0xe3b38829}}, // laje, رنتی_, _aito, ارض_,
+ {{0x629a9efb,0x63ad280d,0x09e580b3,0x91e5be80}}, // _bito, _mfan, волн, воле,
+ {{0x69c755df,0x660a8859,0x7bc60518,0x6440d5e0}}, // naje, _nafk, zaku, _elmi,
+ {{0xa2cd91be,0xfa1300c8,0x7bc63f6d,0x63ad5589}}, // _सिक्, _সকাল_, yaku, _ofan,
+ {{0xe81e8074,0x200b02a5,0x62888009,0x69c1d5e1}}, // भाषा_, _jaci_, _ehdo, _wele,
+ {{0x600691e9,0x629ad5e2,0x69c7029b,0xdcfc00eb}}, // тным_, _fito, kaje, _izrā,
+ {{0x7bc6400f,0x63ad55e3,0x660a84b9,0x7a300106}}, // waku, _afan, _cafk, _nätp,
+ {{0x69c755e4,0x04460364,0x7bc63d95,0x0d828987}}, // daje, венн, taku, ильн,
+ {{0x200b0052,0x25dd85fc,0x2af58074,0x3f9d8870}}, // _naci_, _खाकी_, _इहाँ_, _ugwu_,
+ {{0xe72e9401,0xde059860,0x38600176,0xdee5a796}}, // _те_, _апли, vnir_, _роли,
+ {{0xe2972afb,0xc3328051,0x7bc655e5,0xe5a2802e}}, // кая_, סום_, saku, _киши,
+ {{0xe29755e6,0x7bc62883,0x200b55e7,0xa2ce09c1}}, // тар_, paku, _baci_, _तिक्,
+ {{0x63a904c3,0x27ffca91,0x200dc0d9,0x200b55e8}}, // ñent, neun_, ndei_, _caci_,
+ {{0x38600125,0xe9f90104,0xe5a6067c,0x200b55e9}}, // [49c0] rnir_, _quả_, визи, _daci_,
+ {{0x69c70052,0x683803a7,0x200b0197,0x4f950087}}, // caje, _mídi, _eaci_, урсу,
+ {{0x27ff808e,0x69d90158,0x200b13df,0x629ad5ea}}, // keun_, _אַרו, _faci_, _rito,
+ {{0x629ad5eb,0x27ff826b,0x69d781df,0x7794015b}}, // _sito, jeun_, óxen, _میزا,
+ {{0x6e2e55ec,0x7c659a00,0x200d911d,0x6aa40036}}, // _tobb, دالل, ddei_, _juif,
+ {{0x5333c845,0x9586104b,0x6f040314,0x200b09ab}}, // решт, _алге, mwic, _zaci_,
+ {{0x2576003d,0x9c7d0289,0x645880e7,0x98c38a8e}}, // _شهرس, luče, évis, асыл,
+ {{0x629acd81,0x27ff82f7,0xdfd080f7,0x6d489e1e}}, // _wito, geun_, حية_, vzda,
+ {{0xe7e38076,0x629ad5ed,0xfaa6884b,0x26c7005c}}, // _गारा_, _tito, _разо, ćnog_,
+ {{0x4375827e,0x63ad0087,0x629a815f,0x200d8162}}, // _суст, _sfan, _uito, adei_,
+ {{0x248951c4,0x69c755ee,0xd5b881a9,0x63ad0192}}, // _pham_, vaje, rbā_, _pfan,
+ {{0xa3ea8076,0x5fe11c3b,0x6fa68072,0x26cf0061}}, // _माफ_, _फाइल, _ऑलिं, ágok_,
+ {{0xa3ea9344,0x660a8041,0xfe468d9e,0x1de24073}}, // _मान_, _tafk, _индо, _पांत,
+ {{0x7df3000d,0xaa641697,0x200b55ef,0xab5d8372}}, // _měsí, атск, _saci_, _jeża,
+ {{0x69c70bc1,0x24895591,0x200b55f0,0x0efd0072}}, // raje, _tham_, _paci_, ॉट्स_,
+ {{0x6ebf913d,0x69c755f1,0x63ad00b8,0x63a0808e}}, // लीवु, saje, _ufan, _ngmn,
+ {{0x81cd80c8,0x09d800ab,0x44ce8d42,0x22840106}}, // [49d0] ষ্ট_, দ্ধা, lę_, söka_,
+ {{0x7c3d55f2,0x7aed52fa,0x7a3d8980,0x41a703ca}}, // lksr, _iqat, _rèto, _खलिस,
+ {{0x44ce8d42,0x3cd515a6,0x723401a8,0x200b55f3}}, // nę_, ужес, _خريط, _taci_,
+ {{0xccf38158,0x1c1d8305,0x7a2b01ec,0xf34e81bc}}, // יכע_, फाइल_, _nütz, rụnụ_,
+ {{0x69c50057,0x62358152,0x41a71a3b,0x6458823e}}, // _kehe, леду, _खलास, èvia,
+ {{0x44ce8d42,0x2fc91c11,0x657c267f,0x683855f4}}, // kę_, laag_, _fyrh, _rídi,
+ {{0x69c555f5,0x44ce809a,0x27ffa4c2,0x63a4d5f6}}, // _mehe, ję_, teun_, žina,
+ {{0xd7099b93,0x69c555f7,0xead1077f,0x44ced5f8}}, // ение_, _lehe, _pẹlu_, dę_,
+ {{0x200dd5f9,0x27ff8706,0xe5a58dc0,0xe8e10129}}, // rdei_, reun_, липи, _ướp_,
+ {{0x9a87abca,0x7afb8637,0x41c7d5fa,0x69c555fb}}, // _рубл, _iput, _रजिस, _nehe,
+ {{0x25eb0076,0x6ed8858c,0x44ced5f8,0x3866804a}}, // _चानी_, _मिथु, gę_, _fjor_,
+ {{0xc209004c,0xdb040118,0x9f058019,0x999e8176}}, // _מה_, _afiá, _موصو, _antň_,
+ {{0xa3e1abef,0x69c54a2b,0x2fc955fc,0x628f81e8}}, // _दाई_, _behe, daag_, _ècos,
+ {{0x6446387b,0x387fb90f,0x9c7d005c,0x6fe1064a}}, // shki, llur_, vuče, _फाउं,
+ {{0x213e1ddd,0x69c51acf,0x44ce809a,0xd9430371}}, // áth_, _dehe, cę_, бери,
+ {{0x6f0402af,0x2b55003d,0x9c7d0289,0x69c51fdb}}, // twic, ایید_, tuče, _eehe,
+ {{0x44201dae,0x33260086,0x64599998,0x645600b4}}, // [49e0] _ini_, _çox_, diwi, _imyi,
+ {{0x443205a4,0x69c51781,0xa206433d,0x9c7d1249}}, // _hoy_, _gehe, _спод, ruče,
+ {{0x320c8e04,0x7afb81bc,0x442035fa,0x63a9219f}}, // _rady_, _aput, _kni_, žene,
+ {{0xf7678288,0x0b4684fa,0x320c826f,0xfaa6976e}}, // _ها_, лнен, _sady_, гадо,
+ {{0xa3e70fcc,0x4caa80c8,0x60c90029,0xa09b00be}}, // _पास_, _কিছু, ņemt, _ריכט,
+ {{0xa2d7000c,0x35a355fd,0xada30196,0x2d878088}}, // _बिस्, _гарг, _гарл, _ozne_,
+ {{0x6601a52d,0x7afbd5fe,0x629e0144,0x320c84e8}}, // delk, _eput, _mipo, _vady_,
+ {{0x04438318,0xc6f8a466,0x387f807b,0x4420026b}}, // _лесн, вних_, glur_, _nni_,
+ {{0xab668656,0x320c801b,0x44ce8035,0x627b8035}}, // _свал, _tady_, wę_, _słoń,
+ {{0x442055ff,0x44ce8d42,0x1ef78013,0x9e06a659}}, // _ani_, tę_, اعية_, учил,
+ {{0x44ee027f,0x69c53735,0x44200101,0x387fd600}}, // ný_, _rehe, _bni_, blur_,
+ {{0x69c55601,0xa2db9c3b,0x69cad397,0x443206cb}}, // _sehe, _निम्, lafe, _coy_,
+ {{0x44205602,0x660184e8,0x44320e1b,0x44cec702}}, // _dni_, belk, _doy_, sę_,
+ {{0x44205603,0x69cad604,0xdd948196,0x44ce9c28}}, // _eni_, nafe, _калы, pę_,
+ {{0x629e0b20,0x61f582d0,0x69c50061,0x765ad605}}, // _dipo, _özle, _vehe, mity,
+ {{0x765ad0d3,0xa2ce016f,0x7ae28110,0x57b53317}}, // lity, _तिच्, kuot, рбат,
+ {{0xcddb19e3,0x69c55606,0xf3f000f7,0x2fc90fb0}}, // [49f0] ење_, _tehe, _رأي_, raag_,
+ {{0x59e0800c,0x765a9bee,0x7ae28110,0x443d81bf}}, // _नागर, nity, duot, wkw_,
+ {{0x6459d607,0x2d87016d,0x660e01b4,0xc66803a7}}, // tiwi, åne_, _dabk, уште_,
+ {{0x6601809a,0x765a8009,0x64440706,0x629e038a}}, // zelk, hity, _xlii, _zipo,
+ {{0x34c18b6f,0x3ea90609,0x645981bf,0x660e0282}}, // _शब्द, qqat_, riwi, _fabk,
+ {{0x50b811fb,0x08d58656,0x6459a79e,0x6835826f}}, // _جدید_, ация, siwi, _nádr,
+ {{0x90c29b93,0x765ad5ad,0xdb0d002a,0x63bd02c4}}, // обще, dity, _meañ, kbsn,
+ {{0x44d10a8e,0x3ea682d5,0xc0a88199,0x38aa8362}}, // mą_, _buot_, داول_, _dùr_,
+ {{0x7afb803a,0xeab087bd,0x7bcb9f6b,0x41e791cc}}, // _uput, _فعل_, magu, _مساف,
+ {{0xee3995e0,0x44325608,0xee368364,0x7bcbcb14}}, // тно_, _soy_, ины_, lagu,
+ {{0x9967812f,0x44d11c30,0x629e5609,0x248d8640}}, // _стал, ną_, _ripo, _khem_,
+ {{0x2aab8022,0x68e3d60a,0x629e560b,0x7bcb85ac}}, // _køb_, mund, _sipo, nagu,
+ {{0xa3e71344,0x68e3d60c,0x443200ab,0x66018102}}, // _पार_, lund, _voy_, pelk,
+ {{0x7bcb8a8c,0x44d101e2,0x200fd60d,0x7f8480f7}}, // hagu, ką_, _hagi_, _الهن,
+ {{0x44d10d12,0x68e3d60e,0x20563ff1,0x4420061b}}, // ją_, nund, атар, _tni_,
+ {{0x44ee0a56,0x44d10d42,0x645d560f,0x7bcbaeae}}, // vý_, dą_, érit, jagu,
+ {{0xe7370364,0x7bcbd610,0x2ca79766,0x68e3d611}}, // [4a00] ает_, dagu, _hund_, hund,
+ {{0x68e3d612,0x200f8812,0x44ee003e,0x2ca7c6b6}}, // kund, _lagi_, tý_, _kund_,
+ {{0x9c7d0024,0x44d118eb,0x68e38074,0x89db0039}}, // luča, gą_, jund, _בחיי,
+ {{0x44ee0a56,0x2ca788cf,0xf09f5613,0x66e6210d}}, // rý_, _mund_, _già_, _кома,
+ {{0x69cad614,0x63a4b999,0x6b89809a,0x291a2e0a}}, // tafe, žino, _czeg, _hrpa_,
+ {{0x68e3d615,0x7ae281e2,0xc8da016f,0x09d800ab}}, // fund, ruot, _बटाट, দ্রা,
+ {{0x200f845c,0x68e3a6e7,0x7bcbcb5d,0x3ea6c15c}}, // _bagi_, gund, bagu, _suot_,
+ {{0x63a42eda,0xa2d71c4f,0x63ad8511,0x64a31597}}, // _ngin, _बिल्, ñant, _фата,
+ {{0x9c7d011f,0x3ea6c752,0xf1c605fc,0x09e6c019}}, // juča, _quot_, वरान, _возн,
+ {{0x63a4011e,0x9c7d02a5,0x68fe035f,0xd838811f}}, // _agin, duča, _oppd, _muči_,
+ {{0x24421220,0x69c8d616,0x63a4008e,0x765ad617}}, // _cómo_, _iede, _bgin, rity,
+ {{0x69c8d618,0x765a8364,0x200fd619,0x290c8cf4}}, // _hede, sity, _gagi_, _isda_,
+ {{0x69c8d61a,0xab8705e9,0xba7701a8,0x78a88338}}, // _kede, рунк, لاست, _hudv,
+ {{0x69c8b31e,0x2004d61b,0x63a400ad,0x2245007b}}, // _jede, lemi_, _egin, ólk_,
+ {{0x645d02be,0x6cd489a7,0x228981ac,0x7bcbcffd}}, // éris, _اقبا, núka_, yagu,
+ {{0x69c8a2ba,0x9c7d0301,0x2004d61c,0x44d10084}}, // _lede, buča, nemi_, vą_,
+ {{0x44d1009a,0xcf9380be,0x78a8d61d,0x69c88bfd}}, // [4a10] wą_, ַטע_, _ludv, _oede,
+ {{0x645d561e,0x38698025,0x44d11c30,0x68e3b36a}}, // lisi, čari_, tą_, yund,
+ {{0x7bcbd61f,0x6b898019,0x2004d620,0x248d81c5}}, // tagu, _szeg, kemi_, _phem_,
+ {{0x645d5621,0x44d10d42,0x68e3d622,0xdcef82a5}}, // nisi, rą_, vund, žeći,
+ {{0x7bcbd623,0x77640698,0x68e3d624,0xd8388279}}, // ragu, _държ, wund, _guči_,
+ {{0x27e95625,0x7bcbd626,0x78a8825b,0x21699541}}, // lgan_, sagu, _budv, лили_,
+ {{0x645d22b3,0x200f8867,0x248dd627,0x66050359}}, // kisi, _pagi_, _them_, mehk,
+ {{0x27e9059c,0x66050057,0x2ca782af,0xb8ff800c}}, // ngan_, lehk, _rund_, _ति_,
+ {{0x200fd628,0x2ca78ac6,0x27e95629,0x6b80d62a}}, // _vagi_, _sund_, igan_, _gymg,
+ {{0x69c89989,0x68e3d62b,0x27e90362,0x2ca7ad91}}, // _gede, pund, hgan_, _pund_,
+ {{0x645d009e,0x200fd62c,0x63b88187,0xda7a83de}}, // fisi, _tagi_, _àaná, _ענער,
+ {{0x4aab85b3,0x6aa18352,0x69c8d62d,0x69d703a8}}, // _चढाव, _hilf, _zede, _adxe,
+ {{0xd87480f7,0x660500ee,0x69c882d0,0x7bc99151}}, // _بالب, kehk, _yede, _meeu,
+ {{0x9c7d005c,0xe5a58081,0x7bc983b2,0x683c80e7}}, // ruča, _уики, _leeu, _rédi,
+ {{0x64b587bd,0x61430c99,0x6aa19106,0xd9430aac}}, // _احتر, пера, _milf, пери,
+ {{0x27e92973,0x63a40503,0x645d1b7d,0x7bd602c4}}, // ggan_, _ugin, cisi, _udyu,
+ {{0xef1a0110,0x7c244193,0x44f1d62e,0x2bb5103e}}, // [4a20] ыма_, _inir, lá_, ंडरा,
+ {{0xa3ea809a,0x27e90014,0x7ae6562f,0x7c360b0b}}, // _माह_, agan_, mukt, _hoyr,
+ {{0x68fe04b8,0x44f184ab,0xeafa0416,0x7ae64412}}, // _uppd, ná_, _شرکت_, lukt,
+ {{0x69c8b78d,0x69ce4717,0x2c21000f,0xd8388da8}}, // _sede, labe, याएं_, _tuči_,
+ {{0x69c882b1,0x44f1826f,0x06b200ab,0x26cf0019}}, // _pede, há_, _চিকি, ágot_,
+ {{0xfe678307,0x9c7d04c4,0x645d5630,0x660507cf}}, // _رد_, jučn, zisi, cehk,
+ {{0x69c8d631,0x7c242a02,0x2004d632,0xa3d80a16}}, // _vede, _onir, temi_, ाणा_,
+ {{0xdd8f00f7,0x44f19c2f,0x6d5c9c05,0x69c8a32e}}, // _سوق_, dá_, úram, _wede,
+ {{0x69ce4e7b,0x69c8a90f,0x20049a4d,0x6aa1aebd}}, // kabe, _tede, remi_, _filf,
+ {{0x2004d633,0x7c245634,0x6442b9a3,0x225a0748}}, // semi_, _anir, lkoi, _nmpk_,
+ {{0x645d5635,0x99869e95,0x667b0158,0xd7faa482}}, // tisi, _الاو, רטיק, _бул_,
+ {{0xdee6838d,0x3d000fea,0x9c7d050b,0x2ee05636}}, // _годи, _रहने_, krče, krif_,
+ {{0xd7968307,0x645d5637,0x073a8bbe,0xafdb0366}}, // _الشخ, risi, _حساب_, _fløj,
+ {{0x7c245638,0x69ce5639,0xa2db9516,0x44f1936f}}, // _enir, gabe, _निस्, bá_,
+ {{0x27e90428,0x645d0042,0x6e3509a4,0xd2581138}}, // tgan_, pisi, _rozb, сця_,
+ {{0x63a48042,0x25eb0074,0xdbf1801b,0x2ee00114}}, // žinj, _चाही_, _přík, frif_,
+ {{0x69ce563a,0x94120085,0x40340198,0x6e35563b}}, // [4a30] babe, _niyə_, четс, _pozb,
+ {{0x69ce0333,0x7bcf208b,0x6b8d1768,0x27e9563c}}, // cabe, lacu, _izag, sgan_,
+ {{0x40350087,0x9f5905a4,0x4424bab1,0x660523be}}, // _денс, _pasó_, _bnm_, rehk,
+ {{0x26cf0019,0x6aa1cb4f,0xbebb0168,0x7bcf563d}}, // ágos_, _silf, _rrëf, nacu,
+ {{0x7981ad12,0x38690408,0xa3e7000f,0x44f1c855}}, // _sylw, mnar_, _पाई_, zá_,
+ {{0x6ca708cc,0x6e3500d2,0xe2999677,0x41e780e8}}, // _граж, _uozb, шал_, сіда,
+ {{0x44f1841c,0x6443d63e,0xa2db8b99,0x7bc98bfd}}, // xá_, ënis, _निव्, _teeu,
+ {{0x44f18a56,0x69ce563f,0x38690357,0x7ae60122}}, // vá_, zabe, nnar_, yukt,
+ {{0x6aa1805f,0x38695640,0xac9481e5,0x09e100ab}}, // _tilf, inar_, _хатш, ম্যা,
+ {{0x44f18626,0xc0cbc7a8,0x3f8380eb,0xe81e816f}}, // tá_, _буде_, āju_, _मोठा_,
+ {{0x6449a32e,0x9c7d10d1,0x38692a4c,0x3207d641}}, // _klei, ručn, knar_, meny_,
+ {{0x78a28698,0x69ce5642,0x32079122,0x7ae61400}}, // _giov, wabe, leny_, tukt,
+ {{0x69ce5643,0x683ca6d5,0xe042835f,0x26c6819d}}, // tabe, _cédu, _інши, _gwoo_,
+ {{0x6449c5b8,0x38699487,0x2fd9002a,0x539a825f}}, // _llei, čaru_, _adsg_, יירו,
+ {{0x69ce5644,0x6449d645,0x6b8d011e,0x3ce08035}}, // rabe, _olei, _ezag, _किये_,
+ {{0x69ce5646,0x38692d85,0xa3e70996,0x44d58029}}, // sabe, gnar_, _पाए_, mā_,
+ {{0xa2db8b84,0x69ce43e3,0x44d580eb,0x24802d68}}, // [4a40] _निष्, pabe, lā_, _ikim_,
+ {{0x44248079,0x09e100ab,0x645b81a8,0xf65f3c10}}, // _snm_, ম্বা, _amui, _svær_,
+ {{0x44d58029,0x6449d647,0x6f1d02af,0x8fa60eb3}}, // nā_, _blei, _ersc, _мане,
+ {{0x9c830a21,0x984c801b,0x64498224,0x442480e5}}, // _účto, věď_, _clei, _qnm_,
+ {{0x64428364,0x4424ba98,0xb60582d4,0x3f821238}}, // rkoi, _vnm_, _kršč, _uyku_,
+ {{0xa2db885d,0x44d58029,0x6449b0a4,0x248000ee}}, // _निर्, kā_, _elei, _lkim_,
+ {{0x44d58341,0x64498357,0x8c468ab2,0xf65f007b}}, // jā_, _flei, _деве, _tvær_,
+ {{0x09b70c78,0x6449d648,0x44d580eb,0x24800282}}, // _अभ्य, _glei, dā_, _nkim_,
+ {{0xf09301db,0x7bc282be,0x32079517,0x466aa344}}, // ונה_, mbou, beny_, иром_,
+ {{0xa3ea800f,0x386d0503,0x683c82be,0x2486821e}}, // _माँ_, čeri_, _rédu, llom_,
+ {{0x64498364,0x7bcf3736,0x683c8036,0x3869099b}}, // _ylei, tacu, _sédu, ynar_,
+ {{0x7bc29377,0x9c7d016b,0x80b1025a,0x6b840314}}, // nbou, ručo, _आंबे, _byig,
+ {{0xfbd38051,0x25eb05b3,0x7e7a812b,0x61fe00b9}}, // ותר_, _चाली_, potp, _bbpl,
+ {{0xa3e70361,0x24800214,0x7bcf0216,0x6f02b2a3}}, // _पाक_, _ekim_, sacu, _ipoc,
+ {{0x24868503,0xa3ea809a,0xa2d70b04,0x63b60114}}, // klom_, _मां_, _बिक्, _ffyn,
+ {{0x765c031d,0x24868088,0x6f028133,0xd8388da8}}, // _amry, jlom_, _kpoc, _tuču_,
+ {{0x38695649,0xddab1071,0x69da875f,0x248680e1}}, // [4a50] rnar_, ател_, _odte, dlom_,
+ {{0xe81b0076,0x291e8059,0x7ae286aa,0xf1bf801c}}, // _पोरा_, _orta_, drot, _đáo_,
+ {{0x628705f5,0x6449d64a,0x3869564b,0xd49b846e}}, // mljo, _plei, pnar_, ард_,
+ {{0x248682a5,0x69da80ee,0x3cfb80be,0x442781a8}}, // glom_, _adte, שלאנ, غراف,
+ {{0x44c78065,0x7ae2ad54,0x291ead7b,0x644981b0}}, // ző_, grot, _arta_, _vlei,
+ {{0x6aad564c,0x6f0d0c53,0xab5d822b,0x64498428}}, // _muaf, mwac, _jeżi, _wlei,
+ {{0xa3eb023c,0x291e812b,0x3f89826c,0x768b02d0}}, // मला_, _crta_, _šau_, lüyo,
+ {{0x3207c640,0x3d000321,0x7ae2888b,0x44d580eb}}, // seny_, _रहते_, brot, vā_,
+ {{0x63a9d64d,0x768b0214,0x7ae2d64e,0x9c7d0289}}, // _igen, nüyo, crot, ručl,
+ {{0x44d58029,0x44c78065,0x24800ad4,0x5bb6024c}}, // tā_, tő_, _skim_, ूर्व,
+ {{0x7bcd24cc,0x6615564f,0x69c3807a,0x3eb980eb}}, // _beau, _mazk, mbne, ūst_,
+ {{0x44d580eb,0x801480e8,0x7bcd0087,0x66155650}}, // rā_, _офіц, _ceau, _lazk,
+ {{0x7ff4826a,0x44d580eb,0x63a9804f,0x7d1a81c0}}, // _آسما, sā_, _mgen, avts,
+ {{0x27ed892e,0xe29a0071,0x3ebf978f,0xdb0f0013}}, // lgen_, саг_, mput_, ódái,
+ {{0x929d8063,0x660a0086,0x63a9d651,0x20d704b7}}, // _usłu, _ölkə, _ogen, għi_,
+ {{0x27edbd2a,0x38b18117,0x63a9aeda,0x63a4b999}}, // ngen_, _már_, _ngen, žini,
+ {{0x628182a5,0x6d41a916,0x3ebf812b,0x27ed8e83}}, // [4a60] _oklo, lyla, nput_, igen_,
+ {{0x2d9612bc,0x7bdb8079,0x64460009,0x386001b4}}, // прес, _aduu, lkki, siir_,
+ {{0x60263317,0x6abbd652,0x2c21016f,0x6f0d0079}}, // _една, _atuf, याचं_, awac,
+ {{0x27ed8613,0x64460009,0xeb9a8396,0x63a98118}}, // jgen_, nkki, бие_, _cgen,
+ {{0x3ebf8088,0x937a9459,0x3a3a088b,0x7bdb9a14}}, // jput_, حصار_, _kopp_, _dduu,
+ {{0x26c05653,0x63a9aca0,0x27edc60f,0x38b18019}}, // mpio_, _egen, egen_, _bár_,
+ {{0xf6230b69,0x2bed016f,0x6abb81bc,0x40348a0e}}, // _ядро, _झालं_, _etuf, дейс,
+ {{0x27edba56,0x7e7e5654,0x291e811a,0x38b181a8}}, // ggen_, lopp, _vrta_, _dár_,
+ {{0x1be61344,0xab5d8035,0x7bcd17c9,0x68e38299}}, // _काजल_, _odży, _reau, arnd,
+ {{0x93258077,0x291eb1a7,0x7e7e3ca4,0x7bcd5655}}, // _ترفن, _trta_, nopp, _seau,
+ {{0xa3e0864a,0xc04f835f,0xab5d851c,0x41bb8039}}, // _दया_, _зі_, _reżi, _הצבע,
+ {{0x7e7e5656,0xf0448180,0x33205657,0x6608d658}}, // hopp, _تعطی, _brix_, sedk,
+ {{0xa3ea8006,0x9c7d0fda,0x7e618078,0x62870b80}}, // _माई_, lučk, bilp, tljo,
+ {{0x8f9b81c6,0x9fbf04e8,0x7ae00e06,0xa2c18a27}}, // _הידי, rčík_, ámta, रीट्,
+ {{0xd5fb8039,0x26c701dd,0x66150353,0x6d41d659}}, // _הפור, ćnom_, _razk, cyla,
+ {{0xb9050c78,0xb4d8816f,0xd4680b30,0x11f7003d}}, // _नि_, ाळी_, _није_, _کودک_,
+ {{0x4429565a,0x2bc30dbc,0xb8f409a3,0x644d565b}}, // [4a70] _ina_, _व्या, _सौ_, _ilai,
+ {{0x6f0d2ea4,0x42550077,0x644d0282,0xddab1b47}}, // rwac, _آندر, _hlai, стал_,
+ {{0x644d29fb,0x443b0cc0,0x58d40009,0x9c7d135a}}, // _klai, _koq_, _поэт, jučk,
+ {{0x9f9d80a9,0x44290052,0x26d98019,0xa3bf09c1}}, // nção_, _jna_, ások_, ेरि_,
+ {{0x66153f63,0x9f9d83a7,0x4429565c,0xddc8911b}}, // _tazk, ição_, _mna_, dodž,
+ {{0x38b18658,0x7e7e047f,0x443b0144,0x644d0428}}, // _pár_, copp, _loq_, _llai,
+ {{0x9c7d00d2,0x6446565d,0x25fd016f,0x27eda7e9}}, // ručj, ykki, र्णी_, tgen_,
+ {{0x44290870,0x63a9d65e,0x1c1d0074,0x27edd65f}}, // _nna_, _ugen, _बोलल_, ugen_,
+ {{0x27ed890d,0xf1bf2207,0x7e61d660,0x61fb00d2}}, // rgen_, ्रान, tilp, đulj,
+ {{0x44292dba,0x644d2b25,0x6281803b,0x3ebfd661}}, // _ana_, _alai, _uklo, rput_,
+ {{0x7c3b8247,0x644d06f6,0xac0a1d99,0x240a0081}}, // _kour, _blai, онна_, онни_,
+ {{0x332003d3,0x31c609a3,0x7c3b8809,0x26c734c1}}, // _prix_, वर्ध, _jour, ćnoj_,
+ {{0x7c3b8247,0x64460364,0x6d41d662,0x78a600fe}}, // _mour, rkki, syla, _rikv,
+ {{0x44295663,0x9f9d8073,0x386dd664,0x32180326}}, // _ena_, ação_, lner_, udry_,
+ {{0x386d8082,0x7c298ab3,0xa3e7016f,0x32180123}}, // oner_, _oner, _पाच_, rdry_,
+ {{0x644d5665,0x9f9d83a7,0x44290122,0xa2d7047d}}, // _glai, cção_, _gna_, _बिच्,
+ {{0xee3a21ae,0x3a3a03e5,0x7e7e088b,0x2d910c2e}}, // [4a80] она_, _topp_, topp, _azze_,
+ {{0x4429003a,0x2fcf821e,0x7c29d666,0x386db2a2}}, // _zna_, _legg_, _aner, hner_,
+ {{0x7e7e5667,0x78a6005c,0x4429031d,0x25fd2a47}}, // ropp, _tikv, _yna_, र्धी_,
+ {{0x7c3b8809,0x387f82be,0x26c015a6,0x7e7e21cb}}, // _cour, jour_, ppio_, sopp,
+ {{0x7c3b8548,0x386dc968,0xd12f803d,0x2905810c}}, // _dour, dner_, یمه_, _apla_,
+ {{0x2d8b8d38,0x93438098,0x3f86808e,0x386daa38}}, // ęcej_, ънче, _ryou_, ener_,
+ {{0xaca401bc,0xd5ad0065,0x7c3b90b6,0x2fcf8362}}, // _afọi, _اہم_, _four, _begg_,
+ {{0xd6dad668,0x8f469242,0x386da2c7,0x7c3b9b87}}, // ото_, дход, gner_, _gour,
+ {{0x6e3c3f12,0x3ea7953a,0x63a91b40,0x69de02d4}}, // _korb, _bint_, ženk, _odpe,
+ {{0x44290068,0x3eb9a6d5,0x644d4f73,0xe81e82f1}}, // _sna_, ísta_, _slai, _मोरा_,
+ {{0x9c7d003a,0x6e3c5669,0x644d06f8,0x248480b9}}, // luči, _morb, _plai, _kkmm_,
+ {{0x386dd66a,0x2121157a,0x628ac359,0x6e3c566b}}, // cner_, _rrhh_, llfo, _lorb,
+ {{0x3ea78082,0xccf8800d,0x9f9d83a7,0x9c7d0110}}, // _fint_, stě_, rção_, nuči,
+ {{0x6e3c566c,0x6aa88197,0x29188174,0xe46a062c}}, // _norb, _jidf, _áras_, ttöä_,
+ {{0x6aa8c52f,0x9f9d83a7,0xd83f00e1,0x30a715ff}}, // _midf, pção_, áčov_, драв,
+ {{0x4429566d,0x44f8d66e,0xa3ea801b,0xdb250162}}, // _una_, mé_, _माग_, ăpân,
+ {{0x6e3c0025,0x44f8aca7,0x63ad3665,0x81e680ab}}, // [4a90] _borb, lé_, _igan, য্য_,
+ {{0x3ea71836,0x7c3b8766,0x7c29a445,0xb6a580ff}}, // önt_, _sour, _sner, _nhậ,
+ {{0x44f8d66f,0xdcf88029,0x69d54aa1,0x7c3b90b6}}, // né_, _izvē, maze, _pour,
+ {{0x4429b1a3,0x25eb0076,0x337594bc,0x64838074}}, // ña_, _चाकी_, нгер, võis,
+ {{0x6e3c0e40,0x44f8bf0d,0x38b50082,0x2019002e}}, // _forb, hé_, _hår_, _iasi_,
+ {{0x201925a1,0x7bdf0fa4,0x386dd670,0x7aebd671}}, // _hasi_, _ldqu, wner_, rugt,
+ {{0x20195672,0x387fd673,0x386d888a,0x2b498289}}, // _kasi_, tour_, tner_, šac_,
+ {{0x67228025,0x63ad5674,0x3ea7d675,0x38b5016d}}, // _broj, _ngan, _sint_, _mår_,
+ {{0x69d50234,0xdb16008b,0x63bbd676,0x38b512f1}}, // kaze, _neyð, _ifun, _lår_,
+ {{0x201917cf,0x387f80b9,0x63ad019e,0x69d55677}}, // _lasi_, sour_, _agan, jaze,
+ {{0x38b5005f,0x63a48110,0x3ea7d678,0x6618807a}}, // _når_, žint, _vint_, _davk,
+ {{0xa01b00f2,0x201949ec,0x3ea780f3,0x2ca9051e}}, // _skön, _nasi_, _wint_, _ciad_,
+ {{0x63ad008e,0x63bbb41a,0x69d55679,0x6722d67a}}, // _dgan, _mfun, faze, _groj,
+ {{0x44f8d67b,0x63ad567c,0x69d51c40,0x3d000006}}, // bé_, _egan, gaze, _रहले_,
+ {{0x2019022e,0x7d020063,0x44f880e7,0x9c83007a}}, // _basi_, łosz, cé_, ščaj,
+ {{0x20192752,0x54360019,0x6e3c567d,0x628501f6}}, // _casi_, _سربر, _sorb, _ekho,
+ {{0x2cbf0668,0x201906cb,0x6b89d67e,0xc05283c8}}, // [4aa0] _čuda_, _dasi_, _nyeg, אזן_,
+ {{0x38b508f8,0xe617567f,0x63bba290,0xd91009a7}}, // _får_, нду_, _afun, _پیر_,
+ {{0x6e3c5680,0x38b508f8,0x0446c286,0x10a61d32}}, // _vorb, _går_, нежн, _пион,
+ {{0x61138201,0x27ffd681,0x20195682,0x2d95373a}}, // _gələ, jfun_, _gasi_, трус,
+ {{0xeb9a8087,0xbb3b00be,0x98a78084,0xab838469}}, // чид_, ּעצי, lynė_, лушк,
+ {{0x44f8d683,0xb866803d,0x63bbbebe,0xb42581f9}}, // yé_, کاتو, _efun, _یعقو,
+ {{0x321a0e04,0x6b89d684,0xa5070764,0x27ff8326}}, // _mapy_, _eyeg, _пера_, ffun_,
+ {{0x44f89db6,0x69c70fda,0x63a93a71,0xdfd080f7}}, // vé_, zbje, ženi, جية_,
+ {{0xd5bd9055,0x64ac07d9,0xe6668af2,0x25fd5685}}, // ोरंज, _eğit, етно, र्वी_,
+ {{0x44f8d686,0xa2d40076,0x2ca90013,0x28e10a43}}, // té_, _बौध्, _siad_, _फिलि,
+ {{0x9e038048,0x827783de,0xdb1601fa,0x4d98804a}}, // учыл, _טעמע_, _seyð, ькою_,
+ {{0x44f89dbb,0xe7ea80d4,0x2d8a00b9,0xe73a8198}}, // ré_, _टाटा_, _mybe_, _нее_,
+ {{0x44f8a739,0x63ad008e,0xfdf006a7,0x38b5006f}}, // sé_, _vgan, _चांस_, _sår_,
+ {{0x44f8c092,0x20195687,0x63ad82ba,0x160203eb}}, // pé_, _sasi_, ñanz, र्मर_,
+ {{0xdfd51006,0x2019020f,0x69d51ce9,0x67d503c7}}, // ковы, _pasi_, raze, кову,
+ {{0x63ad0545,0x38b5104d,0x2747077f,0x61138085}}, // _ugan, _vår_, _nínú_, _qələ,
+ {{0x8aa723e7,0x02a714b7,0x160206b7,0x20195688}}, // [4ab0] _прод, _пром, र्भर_, _vasi_,
+ {{0x4439016d,0x3b0901e0,0xb7140198,0xbebb03ed}}, // ljs_, _ipaq_, ыдущ, _frën,
+ {{0x61138201,0x20190704,0x70590fbf,0x63bbd689}}, // _tələ, _tasi_, _жанр_, _pfun,
+ {{0x7afd0b3c,0x2019568a,0x7af201d0,0x9fb581d0}}, // otst, _uasi_, šetř, třík_,
+ {{0x2fc93bc2,0x7de700e8,0xee3f0129,0x7c2d0c53}}, // mbag_, _півд, _buýt_, _mnar,
+ {{0x3eaa0352,0xdd868bca,0x2fc9568b,0xd2b8025f}}, // _gibt_, _نو_, lbag_, עלות_,
+ {{0xdd940110,0x9c7d0503,0x7c2d568c,0x65941ddf}}, // _бары, luču, _onar, _бару,
+ {{0x63bba0b3,0x7c2d019d,0x7afd226c,0x2fc901e0}}, // _ufun, _nnar, ktst, nbag_,
+ {{0x39498341,0x442dd68d,0xe803016f,0xc2210105}}, // ņas_, _ine_, ऱ्या_, मयाब_,
+ {{0x7aef568e,0x443f90f4,0xa01b18b6,0xb0e1064a}}, // duct, _hou_, _sköl, _फिंग,
+ {{0x443f8247,0x2bc3035a,0x2ee9568f,0x7afd1de6}}, // _kou_, _व्हा, hraf_, etst,
+ {{0x443fd690,0x442dd691,0x7afd5503,0xbebb0168}}, // _jou_, _jne_, ftst, _rrën,
+ {{0x443fcce4,0x7afd422a,0x442d826f,0xae0212e0}}, // _mou_, gtst, _mne_, र्डन_,
+ {{0x443f9f1b,0x62839152,0xdddc192c,0x26d9936f}}, // _lou_, nono, _okrš, ásou_,
+ {{0x442dd692,0x7afd33fc,0x389e8133,0x7c3f02a6}}, // _one_, atst, _ọria_, _foqr,
+ {{0x443fd693,0x442db86c,0x2fc9033e,0xb3a987c0}}, // _nou_, _nne_, gbag_, şıyı,
+ {{0x2ee95694,0x98a30758,0xa6861285,0x59d18006}}, // [4ac0] graf_, рите, влад, हरार,
+ {{0x442dd695,0x628386b9,0xfaa61baa,0xaf06026b}}, // _ane_, jono, _шапо, _ajùú,
+ {{0x443fd696,0x64a68221,0x2fc90326,0x77798609}}, // _bou_, важа, bbag_, _lxwx,
+ {{0x0b468221,0xb60900eb,0x3d19809a,0x443fbb7c}}, // кнен, _atšķ, _बनने_, _cou_,
+ {{0x442d9cf3,0xd36f80f7,0x443fd697,0x60c185ee}}, // _dne_, يهم_, _dou_, _atlm,
+ {{0xf5060758,0xfe678872,0x26c214ce,0xc4bc8264}}, // _изго, _ضد_, _itko_, _আয়োজ,
+ {{0x443fd698,0xd46a8e1d,0x9e630226,0xd13081a8}}, // _fou_, _живе_, _тврд, صمة_,
+ {{0x661c5699,0x44fc569a,0xdd8f0591,0x443fd69b}}, // _hark, mí_, _ذوق_, _gou_,
+ {{0x661c569c,0x44fc569d,0x6283b8b3,0xae0215bc}}, // _kark, lí_, bono, र्थन_,
+ {{0x7c2d12f1,0x6283d69e,0x443f8613,0xd9ee0076}}, // _snar, cono, _zou_, _जागत_,
+ {{0x661c0bde,0x6440d69f,0x443f873a,0x44fc2191}}, // _mark, _komi, _you_, ní_,
+ {{0xae02085d,0x69d8d6a0,0x443f007b,0x7afd035f}}, // र्तन_, lave, ðu_, ttst,
+ {{0x38b887ca,0xee3a8037,0x44fc01d6,0x7b6403a7}}, // _hér_, _онд_, hí_, атре,
+ {{0x6440d6a1,0x7afd0b2b,0x7aef4918,0x61bf21ef}}, // _lomi, rtst, ruct, ्रेष,
+ {{0x44fc000d,0x2cbf005c,0x7aef1486,0x7afd527b}}, // jí_, _čudo_, suct, stst,
+ {{0x38b887ca,0x44fc56a2,0x69d8a2ba,0x2ee906a8}}, // _mér_, dí_, have, traf_,
+ {{0x2fc956a3,0x661c0788,0x63ad9024,0x443fd6a4}}, // [4ad0] rbag_, _bark, žank, _rou_,
+ {{0x443fd6a5,0x9c7d56a6,0x6283bd27,0x6b8d02a3}}, // _sou_, ruču, xono, _iyag,
+ {{0x443fbc68,0xf3ff00a9,0x442d8706,0x44fc41cc}}, // _pou_, nião_, _pne_, gí_,
+ {{0x8c3d82bb,0x6283d6a7,0x6b8d0c2e,0x661c02f9}}, // _kişi, wono, _kyag, _eark,
+ {{0x661c56a8,0x443f8317,0xfaff00f1,0x644080ef}}, // _fark, _vou_, ntë_, _domi,
+ {{0xfe711fbe,0x443fd6a9,0x38690a46,0xf41f0364}}, // _اگر_, _wou_, liar_, hdä_,
+ {{0xfaff0b18,0x443f8205,0x44fc56aa,0x64ac8059}}, // htë_, _tou_, cí_, _işin,
+ {{0x442dd6ab,0x6440b866,0xef199593,0x6d48d6ac}}, // _une_, _gomi, еми_, dyda,
+ {{0x628386b9,0xfaff00f1,0x69d8827f,0x6b8d3de5}}, // pono, jtë_, bave, _nyag,
+ {{0x99679d7b,0x7bd9d6ad,0x69d89af9,0x69ca8c2d}}, // _ител, lawu, cave, _स्पी,
+ {{0xf3ff0073,0x6b8d0e05,0xe81e8fd5,0x6440d6ae}}, // gião_, _ayag, _मोका_, _yomi,
+ {{0xfaff00f1,0x6b8d020c,0x80d08264,0x8c3d8850}}, // ftë_, _byag, সংখ্, _bişi,
+ {{0xdee5879e,0x44fc03fb,0x66e58c9b,0x6d5a8102}}, // _соли, zí_, _сола, azta,
+ {{0x6a860aac,0x41a50beb,0x64b701a1,0x09e58264}}, // улга, _गृहस, _aćim, প্রা,
+ {{0x628e1c33,0x50b50c9d,0x09d880ab,0xe29704ae}}, // rlbo, исту, _সামা, уар_,
+ {{0x661c56af,0x201d8077,0x44fc03fb,0x69d8c949}}, // _sark, _jawi_, ví_, zave,
+ {{0x201db7cf,0x29050214,0x5baa00e8,0x9c7d04e8}}, // [4ae0] _mawi_, çlar_, _яким_, ručt,
+ {{0x44fc0a56,0x2bcc8c78,0x6440d6b0,0x533700be}}, // tí_, ार्थ, _somi, _ענין_,
+ {{0x661c4294,0x2d962a71,0x2cad81b0,0x69d8bca2}}, // _vark, урас, _bied_, vave,
+ {{0x44fc2057,0x629a80f1,0x661c56b1,0x3ce601ac}}, // rí_, _shto, _wark, čov_,
+ {{0x661c56b2,0x38b887ca,0x44fc56b3,0x20124482}}, // _tark, _sér_, sí_, meyi_,
+ {{0x201207c0,0x661c0695,0x44fc03c1,0x98aa0084}}, // leyi_, _uark, pí_, tybė_,
+ {{0x6440803a,0xdd9209d7,0xf41f0009,0x64ba8609}}, // _tomi, _طور_, ydä_, _aċin,
+ {{0xdb0284c3,0x64408081,0x06c880ab,0x6456189e}}, // scoñ, _uomi, _শিবি, ūzij,
+ {{0xd5b08013,0xc9f680f7,0x61f681a8,0x6f0701a9}}, // يفة_, _مساع, _مساء, cībā,
+ {{0x6aca80ab,0x3869011b,0x715a9b78,0x2489328c}}, // _রিপো, ziar_, _прес_, _skam_,
+ {{0x5fb405b3,0x201256b4,0xdd948110,0x5c750c9b}}, // ंखाल, keyi_, раны, илот,
+ {{0x6d48d6b5,0xdd9781e5,0x201d9ac4,0xc0ff80ff}}, // ryda, ышы_, _gawi_, _lướt_,
+ {{0x38690531,0x6d5ad6b6,0x6eb01251,0x4e1406a7}}, // viar_, szta, _अंगु, ड़ाई_,
+ {{0xfaff020f,0x290c80dd,0xc987031f,0x672406ec}}, // rtë_, _kpda_, губи, dvij,
+ {{0xfaff00f1,0x672400eb,0xaa7b016b,0x649601a8}}, // stë_, evij, krýv, máin,
+ {{0x60180160,0x649601a8,0x6b8d02d5,0x3d19d6b7}}, // лося_, láin, _tyag, _बनते_,
+ {{0x2cad822b,0xe8069862,0x38690558,0xe8158cfd}}, // [4af0] _ried_, व्या_, riar_, ढ़ता_,
+ {{0x386956b8,0x63a9016b,0xd01d80ab,0x201f93bd}}, // siar_, žens, থায়_, zdui_,
+ {{0x867b810f,0x2cadd6b9,0x7bd9cb40,0x9d152f92}}, // ערבו, _pied_, tawu, рдеч,
+ {{0x644f378d,0x3d05159a,0xe7871b47,0x69d756ba}}, // nkci, _रहीं_, _буго, _hexe,
+ {{0x1614800f,0x201d90e1,0x7bd9d6bb,0xb7da0039}}, // तजार_, _rawi_, rawu, _בקרי,
+ {{0xeab0803d,0x7bd9af4d,0x201d8642,0x62870612}}, // سعه_, sawu, _sawi_, nojo,
+ {{0x2486d6bc,0x20559505,0xa3c5009a,0x69d70187}}, // boom_, итир, एडा_, _mexe,
+ {{0xaae78154,0x9c7d0267,0xdb1d02f1,0xa6e59935}}, // _مسئو, brči, lasõ, ажил,
+ {{0x628756bd,0xfd100065,0x61e5016b,0x86998073}}, // kojo, _وجہ_, _odhl, ктот_,
+ {{0xe7088013,0x60c50c9e,0x7b748f99,0xb8db80ab}}, // اتين_, _othm, _اطفا, _অব_,
+ {{0xa3e9864a,0xa115af0a,0x481584fa,0x67290267}}, // _मया_, _خوات, _вмес, _šejl,
+ {{0xd46a1361,0x61e50219,0x20d38493,0x38698338}}, // тиме_, _adhl, mţi_, _öar_,
+ {{0xd6c384c0,0xe81e92ee,0x308480f7,0x20d3802e}}, // _امتی, _मोटा_, _النف, lţi_,
+ {{0x35a58084,0xe29a028e,0x2ba71199,0xe5a5a14d}}, // _калг, таг_, _गणरा, _кили,
+ {{0x20d3802e,0x290c56be,0x69d756bf,0x68460081}}, // nţi_, ïda_, _dexe, анва,
+ {{0x67240341,0x7ae90024,0x78af026c,0xe8e0001c}}, // tvij, šeta, _cicv, _liệt_,
+ {{0x7643d6c0,0x628721c5,0x44321e1e,0x33290216}}, // [4b00] _kony, bojo, _iny_, _grax_,
+ {{0x442056c1,0x672401a9,0x443202f7,0x6444010c}}, // _hai_, rvij, _hny_, _hoii,
+ {{0x442056c2,0x764390e1,0x67240654,0x248689ff}}, // _kai_, _mony, svij, toom_,
+ {{0x03269cce,0x69c1d6c3,0x09d880c8,0x442056c4}}, // иден, _afle, _সাধা, _jai_,
+ {{0x442056c5,0xe8e00028,0x2b168009,0x59da4765}}, // _mai_, _biệt_, рьер, _बजार,
+ {{0x442056c6,0x69dc56c7,0x5eeb064a,0x2eed8114}}, // _lai_, nare, _चित्_, dref_,
+ {{0x443256c8,0xdb1f26e1,0xe8e0001c,0xddc88084}}, // _ony_, _seqü, _diệt_, sidū,
+ {{0x64599f4c,0x38bc56c9,0xf106bac3,0x3fb48264}}, // chwi, _mír_, _शहीद_, ঘর্ষ,
+ {{0xa2b22e06,0x04f280ab,0x7643d6ca,0x20d3802e}}, // _इंग्, _জনের_, _bony, aţi_,
+ {{0x44320051,0x7c22ce3b,0x38bc0511,0x76439fd6}}, // _any_, ldor, _oír_, _cony,
+ {{0x442015d6,0x69d704c3,0x7c20d6cb,0x69dc56cc}}, // _bai_, _rexe, _kamr, dare,
+ {{0x442008ac,0x98be000d,0x7aed80cd,0x26060744}}, // _cai_, ště_, šate, स्ती_,
+ {{0x442056cd,0x69dc03a6,0x62872c28,0x629e026c}}, // _dai_, fare, tojo, _bhpo,
+ {{0x5ef3816f,0xdb1d0019,0x7c208326,0x3b000372}}, // ंढर्_, ncsé, _lamr, _dqiq_,
+ {{0x442056ce,0x3d05000f,0x69d704c3,0x628756cf}}, // _fai_, _रहें_, _vexe, rojo,
+ {{0x442056d0,0x7c208353,0x0854004a,0x69d70df6}}, // _gai_, _namr, овсю, _wexe,
+ {{0x7bdd1922,0x7c228114,0x628721c5,0x57a70081}}, // [4b10] masu, ddor, pojo, ршва,
+ {{0x69dc56d1,0x442003c3,0x7d0100f2,0x2cb80609}}, // care, _zai_, _älsk, _burd_,
+ {{0x442056d2,0x25fd0beb,0x26c902a5,0x60c506df}}, // _yai_, र्गी_, spao_, _uthm,
+ {{0x442056d3,0x68f5007e,0x8c3d8162,0x4c958198}}, // _xai_, muzd, _nişt, щиес,
+ {{0x69c181ec,0x7c20ce2f,0x26c681c5,0xda041834}}, // _pfle, _damr, _ntoo_, श्वत_,
+ {{0xf20791d2,0x98e580f7,0x27e68300,0x4422026c}}, // рядо, مكتو, _adon_, zdk_,
+ {{0xe8e00104,0x7bdd3d46,0x2a580039,0xa06996fc}}, // _việt_, kasu, שבון_, лака_,
+ {{0x69dc56d4,0x76438caa,0x2eedcf92,0x20d3802e}}, // zare, _sony, tref_, rţi_,
+ {{0x442056d5,0x3eb90051,0x76439a48,0x69dc56d6}}, // _rai_, _just_, _pony, yare,
+ {{0x442056d7,0x443256d8,0x3eb956d9,0x645d56da}}, // _sai_, _sny_, _must_, ërin,
+ {{0xb9958013,0x44201b16,0x200b026f,0x671c8466}}, // _الاب, _pai_, _obci_, _बैठक_,
+ {{0xf09f0142,0x69dc56db,0x7bdd386b,0x645d0110}}, // _nhà_, ware, gasu, ūrin,
+ {{0x442056dc,0x386dc73a,0x2245d6dd,0x249f810c}}, // _vai_, mier_, _kolk_, _mhum_,
+ {{0x386d8ee9,0x442004b9,0x6146017c,0x610a811c}}, // lier_, _wai_, бега, _həla,
+ {{0x442056de,0x69dc56df,0x443256e0,0x7bdd4d46}}, // _tai_, rare, _tny_, basu,
+ {{0xe3b2045b,0x44e38182,0x386d9a93,0x69dc1473}}, // _عرب_, mı_, nier_, sare,
+ {{0x44e3884a,0x8fa30d5f,0x69dc3131,0x127a0019}}, // [4b20] lı_, зате, pare, _محدث_,
+ {{0xcf928158,0x7c20d6e1,0x38bc00f7,0x386dd6e2}}, // לטן_, _samr, _tír_, hier_,
+ {{0x44e38459,0xf1bf8028,0x25fe12ee,0x6f1d56e3}}, // nı_, _đáp_, _लाठी_, _essc,
+ {{0xaae68013,0x249f8c49,0x7c209f79,0xa3d82724}}, // مستو, _chum_, _qamr, िरि_,
+ {{0x7c22820f,0x3eb94de9,0x386d93c2,0x31cf80ab}}, // rdor, _gust_, dier_, _রাজশ,
+ {{0x09d880c8,0x4df536ae,0x44e38201,0x7c2294fb}}, // _সাহা, _мяст, kı_, sdor,
+ {{0x386dd6e4,0xa3d82b62,0x8d630193,0x6b9b8133}}, // fier_, िरा_, _евре, _ezug,
+ {{0x44e382bb,0x8cd29305,0xc5630d15,0x7ae402d4}}, // dı_, _भौगो, _экск, _ovit,
+ {{0x2bce835a,0x64a381e5,0x6f0435dc,0x649601a8}}, // _ह्या, _хата, ntic, ráil,
+ {{0x59cfbbf1,0x7bdd1f62,0xb8fe80ab,0x14ca862c}}, // _स्पर, wasu, _তম_, лыми_,
+ {{0x25fd06b7,0x69da8029,0x7bdd00ad,0x386dd6e5}}, // र्टी_, _iete, tasu, bier_,
+ {{0x7bc2d6e6,0x6f041249,0x69da8094,0x386dd6e7}}, // ncou, ktic, _hete, cier_,
+ {{0x69da94e5,0x7ae48013,0x7ae409d1,0xab870f75}}, // _kete, áith, _cvit, сунк,
+ {{0x29031d84,0x7bdd3755,0x69da80f1,0x7ae2808e}}, // ttja_, sasu, _jete, isot,
+ {{0x7bdd45bb,0x44e38457,0x6f040037,0xa1581f96}}, // pasu, cı_, etic, бару_,
+ {{0x69da84e8,0x6f0417c9,0x6f1d00b9,0x2ca056e8}}, // _lete, ftic, _pssc, _ghid_,
+ {{0x78ba826c,0x25578085,0x95d906a1,0x2ca682d5}}, // [4b30] _lutv, vələ_, идат_, dmod_,
+ {{0x291e843d,0x69dad6e9,0x3a750e97,0x249f80f1}}, // _osta_, _nete, _флор, _shum_,
+ {{0xf09f220a,0x628a81e0,0x7d0d8bda,0x6f0417d6}}, // _thà_, dofo, _ćask, atic,
+ {{0xd3719a3c,0x3cfb80be,0x25fd0f0a,0x69da86ab}}, // وها_, רלאנ, र्जी_, _aete,
+ {{0x69dab301,0x386dd6ea,0x291e8012,0x6f04079a}}, // _bete, vier_, _asta_, ctic,
+ {{0x44e387d9,0x291e82d5,0x2557811c,0x610a811c}}, // yı_, _bsta_, sələ_, _səla,
+ {{0x386da02e,0x224584eb,0x69dabf55,0x67290858}}, // tier_, _volk_, _dete, _šejh,
+ {{0xdb2100e1,0xd6d19ddd,0xaec584ad,0x7bc2d6eb}}, // _štýl, وقع_, обил, ccou,
+ {{0x291eab72,0x386da280,0x69ca8ebf,0x9f400125}}, // _esta_, rier_, _स्वी, ngið_,
+ {{0x69da905d,0x44e38059,0xa01b008b,0x386d8a85}}, // _gete, tı_, _fjög, sier_,
+ {{0xe806835a,0xc5f204de,0xa5099285,0x1d0990ca}}, // व्हा_, ודי_, шена_, шени_,
+ {{0x44e3b6d4,0x69c523a6,0x69dad6ec,0x6abb8c53}}, // rı_, _afhe, _zete, _kuuf,
+ {{0x44e38b06,0x69da8214,0x8c1a84de,0x888c83de}}, // sı_, _yete, בורי, _גראַ,
+ {{0x1e8490ac,0x59cf861e,0x26cd8037,0xa3d806a7}}, // _елім, _स्मर, mpeo_, िरह_,
+ {{0x8c460b5b,0x81ab80ab,0xd8262796,0x26cd8144}}, // _меке, কুর_, одви, lpeo_,
+ {{0x7ae41c78,0x6f0456ed,0x645d56ee,0xa9278084}}, // _tvit, ttic, chsi, mažė,
+ {{0x6e23c78b,0xaa7b008b,0x76900009,0x6729d6ef}}, // [4b40] _kanb, msýn, käyt, nvej,
+ {{0xee37045d,0xa01b1252,0x7bc29916,0x6e238242}}, // ону_, _sköt, wcou, _janb,
+ {{0x69da9eed,0x41d003bb,0x6e23a53d,0x321821d5}}, // _rete, _त्यस, _manb, mery_,
+ {{0x69da8734,0x7bc282be,0x32184a15,0x51f6015b}}, // _sete, ucou, lery_, _اسکر,
+ {{0x7c241536,0xbb4387ac,0x7bc2d6e7,0x2ca6b5ad}}, // _mair, мецк, rcou, rmod_,
+ {{0x27e056f0,0x98c69594,0x7c2456f1,0xa905003d}}, // lain_, осил, _lair, _ببین,
+ {{0x7ae2c269,0x69da8e79,0x61e88037,0x41c68a0d}}, // ssot, _vete, _vddl, रुषस,
+ {{0x6e239aee,0x69daa65f,0xe8e08104,0x628a92f5}}, // _aanb, _wete, hiệp_, sofo,
+ {{0x69dabd9b,0x6e23d6f2,0x81ab80ab,0x645d0168}}, // _tete, _banb, কুল_, ërim,
+ {{0x44249277,0x27e00219,0x78ba8006,0x64428966}}, // _ham_, hain_, _tutv, ljoi,
+ {{0x4424d6f3,0x6e23d6f4,0x27e056f5,0x7c2445ab}}, // _kam_, _danb, kain_, _bair,
+ {{0x44268c6b,0x7c244f3f,0x2bb18105,0xfe788084}}, // ndo_, _cair, ुँचा, snį_,
+ {{0x4424c536,0x4426a706,0x27e045ff,0x7c2435dd}}, // _mam_, ido_, dain_, _dair,
+ {{0x4424d586,0x64428009,0x2ef201b0,0x4426a683}}, // _lam_, hjoi, dryf_, hdo_,
+ {{0x7c2410b6,0x4426d6f6,0x551e8035,0x6b51026b}}, // _fair, kdo_, _बनिए_, _bógú,
+ {{0x4424c402,0x7c240cc8,0x4426b78b,0x27e001ba}}, // _nam_, _gair, jdo_, gain_,
+ {{0xb90205e8,0x4426d6f7,0xd5bb01ae,0x321856f8}}, // [4b50] _नौ_, ddo_, асе_, bery_,
+ {{0x69ca80d4,0x551e9660,0x7c2456f9,0x2fdfcf97}}, // _स्ली, _बनाए_, _zair, zaug_,
+ {{0x4424d6fa,0x7c8781a4,0x9c830824,0x6abb8df6}}, // _bam_, оуде, ščuj, _suuf,
+ {{0x27e01627,0x7d051106,0x212100dd,0xfc3f20ce}}, // cain_, rths, _ishh_, zmín_,
+ {{0x2cbf00ce,0x76900198,0x083a03de,0x883a025f}}, // _čudu_, täyt, _הערל, _התרו,
+ {{0xdcfb003d,0x76ba01c6,0x01d90264,0x660e02f9}}, // _پرسش_, _למשפ, _তাঁদ, _gbbk,
+ {{0x6e23d6fb,0x4426d6fc,0x76b28035,0x2fdf81c0}}, // _ranb, bdo_, _płyn, taug_,
+ {{0x7cc18341,0x4424d6fd,0x397c098a,0x6e2386c0}}, // _bērn, _gam_, יטונ, _sanb,
+ {{0xf7718307,0x6e238bb1,0xdbd9007b,0x7c2456fe}}, // عاب_, _panb, væði, _rair,
+ {{0x4424d6ff,0x6729d700,0x7c240009,0x27e00102}}, // _zam_, rvej, _sair, zain_,
+ {{0x442490af,0xfc3f026f,0x27e05701,0x64499384}}, // _yam_, rmín_, yain_, _koei,
+ {{0x44248069,0x160b00d4,0x59cf8a0d,0x6e2392f9}}, // _xam_, स्तर_, _स्तर, _wanb,
+ {{0x7c243d3b,0x64498b3c,0xeb9f013c,0xdb080059}}, // _vair, _moei, lføj_, _ölüm,
+ {{0x19b9035f,0x7c242bb7,0x27e05702,0x44268110}}, // жуть_, _wair, wain_, zdo_,
+ {{0x20560eef,0x7c240cac,0x10a60615,0x4426d358}}, // птар, _tair, зивн, ydo_,
+ {{0x7c241a29,0xa01b18b6,0x442681b4,0xaec6171c}}, // _uair, _skör, xdo_, збал,
+ {{0x27e05703,0x44268824,0x32185704,0x1f6653c2}}, // [4b60] rain_, vdo_, pery_, чкам,
+ {{0x17790cde,0x4424d705,0x27e05706,0xea64003d}}, // ість_, _sam_, sain_, _آپدی,
+ {{0x4424d707,0x27e05708,0x628e5709,0x64499c11}}, // _pam_, pain_, mobo, _boei,
+ {{0x645b861f,0x4426d70a,0x69de484c,0x78a980f1}}, // _clui, udo_, _hepe, jmev,
+ {{0x6442825d,0x69de059c,0x27f7026f,0x38600834}}, // rjoi, _kepe, šené_, mhir_,
+ {{0x645b8d62,0xf7880085,0x26c281ac,0x69de0168}}, // _elui, _neçə_, íkom_, _jepe,
+ {{0x4424d70b,0xa3ac0a74,0x69de570c,0xbea31246}}, // _tam_, केत_, _mepe, _чарк,
+ {{0xdb160065,0x6449a4c2,0x50668196,0x09b9016f}}, // _egyé, _goei, ятла, _आल्य,
+ {{0xa41183bf,0x628e2eae,0x09e200ab,0x29078b7e}}, // lığı_, kobo, _বানা, atna_,
+ {{0x7ae9011f,0x69de570d,0xdbe503b7,0x649d0f35}}, // šetk, _nepe, कण्ड_, géin,
+ {{0x38600867,0x628e1fce,0xd7efa762,0xfbe10264}}, // khir_, dobo, _пу_, _ভাবত,
+ {{0x6ab50935,0x35678071,0x00bb8039,0x09c50072}}, // ंद्र, ярын_, _המאמ, वड्य,
+ {{0x69de03b2,0x38600f58,0x2121008e,0xd90e803d}}, // _bepe, dhir_, _sshh_, _شیک_,
+ {{0x69de4186,0x628e570e,0xbd0181df,0x2480010c}}, // _cepe, gobo, ñéce, _ejim_,
+ {{0x69de3da5,0x19580048,0x06b1928a,0x00000000}}, // _depe, пары_, řící_, --,
+ {{0xa41182bb,0xa01b1a50,0x661ad70f,0xf0b405a8}}, // dığı_, _sjöb, letk, ейсь,
+ {{0xead780c8,0xf7730b8c,0x06b70180,0x64499151}}, // [4b70] _সম্ম, رار_, سناک_, _roei,
+ {{0x645b85f8,0x69de1c11,0x13d58d1c,0x661ac312}}, // _slui, _gepe, दर्भ, netk,
+ {{0x38600046,0x645bd710,0x6449a32d,0xa25b00e1}}, // bhir_, _plui, _poei, spôs,
+ {{0x69c8d711,0x38605712,0x69de0fe2,0x765c038a}}, // _afde, chir_, _zepe, _elry,
+ {{0x7bdf0580,0x0dcb0c9b,0xcfab00d7,0x6ab70197}}, // _mequ, _губи_, _دادم_, _kixf,
+ {{0x7bdf00e7,0x200f80b9,0x672d5713,0x29078bcb}}, // _lequ, _sbgi_, mvaj, ttna_,
+ {{0x672d3b7a,0x661a844a,0x67228091,0xf49780d7}}, // lvaj, detk, _asoj, _فشرد,
+ {{0x7bdf5714,0x610a8085,0x628e3327,0x2907d715}}, // _nequ, _gəlm, zobo, rtna_,
+ {{0x628e0578,0x2907d716,0x26018740,0x649d0b6a}}, // yobo, stna_, _शादी_, téin,
+ {{0x661acdd0,0x7ae9a1c5,0x69c70140,0x628e34d3}}, // getk, _ivet, jcje, xobo,
+ {{0x61e3a2f8,0x628e5717,0xa787803d,0x7bdf5718}}, // manl, vobo, _کشاو, _bequ,
+ {{0x69de045c,0x61e38850,0x7ae98a21,0x7bdf0118}}, // _sepe, lanl, _kvet, _cequ,
+ {{0x69de5719,0xe8e0001c,0x25fe097d,0x3d1983db}}, // _pepe, _thịt_, _लाली_, _बनके_,
+ {{0x672d369f,0x661a8052,0x764a831d,0x61e3d71a}}, // dvaj, cetk, _gofy, nanl,
+ {{0x3860571b,0x63bbd71c,0x27ffc901,0xe297571d}}, // thir_, _ogun, ogun_, фар_,
+ {{0x27ffb996,0x628e0c56,0x63bbd71e,0xb8f60665}}, // ngun_, sobo, _ngun, _हऽ_,
+ {{0x6f0989b2,0x61e382fa,0x69de4502,0x628e4340}}, // [4b80] ntec, kanl, _tepe, pobo,
+ {{0x63bb8397,0x3860571f,0x6f09802a,0xe2920f24}}, // _agun, shir_, itec, _جذب_,
+ {{0x7ae989b5,0x67290858,0xdbc68074,0x61e3d720}}, // _avet, _šejt, _mööd, danl,
+ {{0xae021a46,0xed57004a,0x68fc5721,0x6aa6864a}}, // र्जन_, ьох_, murd, गग्र,
+ {{0x7ae98b89,0x671c8fea,0xaca4819d,0xb5e38264}}, // _cvet, _बैंक_, _haịp, _মানচ,
+ {{0x63bb80ad,0x7df986b7,0x61e3d722,0xbe16003d}}, // _egun, ्भोग_, ganl, _توسع,
+ {{0x661ad723,0x247601a9,0x200d8037,0xdd920180}}, // vetk, nāms_, ffei_, _شور_,
+ {{0xdfd08013,0x764a84e6,0x200282f1,0x2000019a}}, // دية_, _sofy, üki_, ngii_,
+ {{0x7bdf3f55,0xf0930039,0x41cf8aed,0xb6070197}}, // _requ, כנה_, _स्वस, _feġġ,
+ {{0x3f988029,0x64ac87c0,0x644601c0,0x7bdf5724}}, // āru_, _eşit, ejki, _sequ,
+ {{0x7bdf1313,0xe01e0df4,0x38c8804e,0xab5b08c5}}, // _pequ, _पसंद_, _راضی_, rcüm,
+ {{0x68fc031d,0x83fd8019,0xfbad00ab,0x3a3a00fc}}, // durd, _előf, _ক্ষত, _anpp_,
+ {{0xe66682dc,0xd62ab7cd,0x92bc80ab,0x661ad5ee}}, // _отко, _моде_, _আবু_, petk,
+ {{0x6cfe0441,0xe56f8180,0x8f9b81c6,0x38a1b38d}}, // _उमंग_, اطی_, _וידי, móra_,
+ {{0x19940098,0xfbde8697,0x69c70168,0x38a1d725}}, // варя, मराम, rcje, lóra_,
+ {{0x6e270b80,0x200001b4,0x27e49f0a,0x69c705f3}}, // _sajb, ggii_, namn_, scje,
+ {{0x61e383bf,0x59d03c4f,0x610a8085,0x3eb8008b}}, // [4b90] yanl, _त्वर, _bəlk, _birt_,
+ {{0x5ec800c8,0x7afd14c7,0x27e48186,0x44295726}}, // _লিখে, must, hamn_, _haa_,
+ {{0x7ae9ba71,0xb8f40a49,0x44295727,0x61e3d728}}, // _svet, _হয়_, _kaa_, vanl,
+ {{0x8c1c0158,0x44292551,0x61e380dd,0x2d9803ba}}, // ַווי, _jaa_, wanl, _dyre_,
+ {{0x44291312,0x7afd3ae2,0x271d800d,0x2d9801e0}}, // _maa_, nust, _भनेर_, _eyre_,
+ {{0x44295729,0x78ad572a,0xf2d400be,0x644d016a}}, // _laa_, lmav, דעס_, _loai,
+ {{0x61e385b2,0x7afd0074,0x85570019,0x6f098299}}, // ranl, hust, _تیار_, wtec,
+ {{0x61e3884a,0x4429572b,0x26019a3b,0x63bbd72c}}, // sanl, _naa_, _शाही_, _ugun,
+ {{0x61e3d72d,0xef168009,0x7c3b9998,0xe811016f}}, // panl, емы_, _inur, ड्या_,
+ {{0x7c29d72e,0x6f09d72f,0x7afd5730,0xe8df81bc}}, // _haer, rtec, dust, _alụm_,
+ {{0x6f098ebf,0x44295731,0xa90780d5,0xc6920039}}, // stec, _baa_, _زبان, ראי_,
+ {{0x7c2ba0f0,0xc2090039,0x6459a70c,0x3d07009a}}, // ndgr, _לה_, nkwi, _हमने_,
+ {{0x61e1d732,0x60c1a0ac,0x7c3bbd34,0x644d2404}}, // _kell, _hulm, _mnur, _doai,
+ {{0x60c1d733,0x61e1d734,0xf2d2898a,0x66f68039}}, // _kulm, _jell, רעם_, _נמצא_,
+ {{0x61e18558,0xf9928158,0xc5f283c8,0x44295735}}, // _mell, ָרן_, _עדה_, _faa_,
+ {{0x44290870,0x387f807b,0xe3da00ab,0x212590e1}}, // _gaa_, nnur_, _থাকব, _mslh_,
+ {{0x2000085d,0x3f8603a7,0xa3ac5736,0xdbd9808b}}, // [4ba0] rgii_, çou_, केश_, _ræðu,
+ {{0x61e18021,0x63a45737,0x44295738,0x78ad01e8}}, // _nell, _izin, _zaa_, amav,
+ {{0x44295739,0x7c29d73a,0xa3ac01d0,0x89db025f}}, // _yaa_, _baer, केर_, _מחיי,
+ {{0x7c29831d,0x5ed580ab,0x3eb8022b,0x649601a8}}, // _caer, _দিনে, _wirt_, máis,
+ {{0x7c2994e5,0x61e1a91e,0xe29a85c2,0x649600f7}}, // _daer, _bell, пад_, láis,
+ {{0x2d9808cf,0x61e18213,0x60c18214,0x63a40c53}}, // _tyre_, _cell, _bulm, _mzin,
+ {{0x61e1d1e4,0xada310ac,0x26c2022e,0xe5a300e5}}, // _dell, _барл, _huko_, _бири,
+ {{0x26c20578,0x661e279f,0x3b0a818b,0x7c298114}}, // _kuko_, lepk, _небо_, _gaer,
+ {{0x4429573b,0x7afd01df,0x61e1aad1,0x39a68032}}, // _raa_, xust, _fell, _bísí_,
+ {{0x442922d9,0x3eb8ba01,0x628004c3,0x61e1831d}}, // _saa_, ört_, émol, _gell,
+ {{0x4429573c,0x2d988bc5,0x764e573d,0x26c253b1}}, // _paa_, ære_, _moby, _luko_,
+ {{0x7afd18c5,0xe7869d7b,0x61e1cf33,0x63a4410d}}, // tust, _пуло, _zell, _bzin,
+ {{0x26c202a0,0x442b0db1,0x61e1d73e,0x291e02d0}}, // _nuko_, rdc_, _yell, çtan_,
+ {{0x44290962,0x7afd2551,0x63a401a9,0x764e0cf4}}, // _waa_, rust, _dzin, _noby,
+ {{0x44290d10,0x7afd14c7,0x63a4573f,0x6abad740}}, // _taa_, sust, _ezin, _mitf,
+ {{0x759b8496,0x442902f7,0x26c20c8b,0x645f0168}}, // _מיוח, _uaa_, _buko_, _ulqi,
+ {{0x78ad2ed0,0x26c20267,0xf41f0198,0x64960174}}, // [4bb0] rmav, _cuko_, meä_, ráit,
+ {{0x7c3bbfee,0x38a1807b,0xb8f700ab,0x4b7c01c6}}, // _snur, jórn_, _সি_, _מאחו,
+ {{0x61e1d741,0x649600f7,0x7aed5742,0x7c29d743}}, // _rell, cáis, _hvat, _paer,
+ {{0x610a8201,0x61e1d744,0x999a001b,0x7aed5745}}, // _gəli, _sell, _např_, _kvat,
+ {{0x60c180f1,0x442981ac,0x6459ae66,0x7c2bd333}}, // _sulm, ľa_, rkwi, rdgr,
+ {{0x60c1d746,0x4429a8fc,0x764e0cf4,0x61e18168}}, // _pulm, ža_, _goby, _qell,
+ {{0x6f0d3402,0x9d1819b5,0x26cf8144,0xf41f0198}}, // ltac, _порт_, _stgo_, keä_,
+ {{0x61e734c5,0x26c22fc0,0xa3ac0006,0x6f0d0229}}, // hajl, _yuko_, केँ_, otac,
+ {{0xb8c88076,0x61e1d747,0x7c8481a1,0x387fd748}}, // _खी_, _tell, _суре, rnur_,
+ {{0x2ca911d6,0x6285017f,0x9f5f9b01,0xd121064a}}, // _ahad_, _njho, ngué_, मबाण_,
+ {{0xc9599fbe,0x63a40065,0x423680be,0x69caaa2b}}, // _تلاش_, _szin, _שנעל_, _स्टी,
+ {{0x9055844f,0xed5680e8,0xa01b0106,0x91fc81a9}}, // _швец, тою_, _sjön, _glāb,
+ {{0x7aed00ce,0x032300e8,0xa85681c6,0x2ca90706}}, // _cvat, адян, _שינה_, _dhad_,
+ {{0x26c25749,0x2ca9026c,0x64960174,0xfaa60e63}}, // _ruko_, _ehad_, gáir, таго,
+ {{0xfbe100c8,0x26c281ac,0x4096197b,0x09e680ab}}, // _ভারত, íkov_, врат, _নামা,
+ {{0x249d81e9,0x20c7001c,0x26c200d2,0x4e1406b7}}, // jlwm_, _dõi_, _puko_, न्नै_,
+ {{0x764e0e04,0x6b9b8019,0x63a41fb6,0x2bd801d0}}, // [4bc0] _poby, _nyug, _uzin, _भ्या,
+ {{0x787b0364,0x7ae904b7,0x0e63259a,0x3f9c01a9}}, // tävä, ġett, акун, āvu_,
+ {{0xa06a0a42,0x7aed0024,0x6f0d051e,0xf8b384c5}}, // _жана_, _zvat, atac, ुदाय,
+ {{0xf53680be,0x26c2574a,0x6f0d09ab,0x6b9bc6d6}}, // כטער_, _tuko_, btac, _byug,
+ {{0xa01b1252,0x80af0035,0x6f0d0cf7,0x6b9b818e}}, // _mjöl, ंगरे, ctac, _cyug,
+ {{0x9f420168,0x98b80196,0xdce9928a,0x67240157}}, // _lekë_, tyrė_, áněn, mwij,
+ {{0x672401ed,0xda6581a8,0x6b8997c9,0x3e608706}}, // lwij, داني, _exeg, _nòta_,
+ {{0xc9530051,0x628fd74b,0xdca68c66,0x64a68254}}, // שמה_, _ícon, _раби, _раба,
+ {{0xa4918077,0xb22690ca,0xe3e100ab,0x672400f3}}, // _اینت, _имал, _ভালব, nwij,
+ {{0x291a016f,0x7aed0669,0x2bce8a74,0x6aba89ff}}, // _oppa_, _rvat, हुरा, _uitf,
+ {{0x765a80f2,0x4e350158,0x7aed574c,0x61e70bda}}, // rkty, אָרן_, _svat, vajl,
+ {{0x897983de,0x6724574d,0x2baf8075,0x017983de}}, // _אָנה, kwij, जेवा, _אָנל,
+ {{0x291a0b99,0xae0305fc,0x61e7574e,0xfb870071}}, // _appa_, _लालन_, tajl, тынн,
+ {{0x175781c6,0x97378538,0x25fe2743,0x6724090d}}, // _בסדר_, _שרגא_, _लागी_, dwij,
+ {{0xc6938159,0xa01b07ca,0x61e7574f,0x7ae081c0}}, // _דאס_, _fjöl, rajl, _lwmt,
+ {{0x20c70665,0x6f0d5750,0x61e707d9,0x9f9d007b}}, // _või_, ttac, sajl, _bæði_,
+ {{0x53350676,0xc62580ab,0x7aed0669,0x672437ce}}, // [4bd0] _септ, বারা_, _uvat, gwij,
+ {{0x6f0d266a,0x7c2d0a10,0x3a2cb6f8,0x81dd00ab}}, // rtac, _haar, _nadp_, _ডাক_,
+ {{0x7bf98993,0x9f5f876b,0x6f00b468,0x0b941ab3}}, // енер_, rgué_, humc, _مجید,
+ {{0x7c2d3ade,0x645d5751,0x6f0d5752,0x6f00c097}}, // _jaar, nksi, ptac, kumc,
+ {{0x27e90a5b,0x610a8086,0x6f00826c,0x7cc18ec3}}, // maan_, _məlu, jumc, _vēri,
+ {{0xdd868013,0x7c2d278d,0x2c62026f,0x3a2c8118}}, // _هو_, _laar, _móda_, _cadp_,
+ {{0xc6920051,0xa3e32631,0x645d2406,0x3a3e89c4}}, // _האם_, धरा_, kksi, _dntp_,
+ {{0x27e95753,0xccf20051,0x39498289,0x7c2d5754}}, // naan_, _לכל_, ćas_, _naar,
+ {{0x443f90ab,0x62950019,0xdd2e81d0,0xe3b398e0}}, // _inu_, kozo, měře, _ضرر_,
+ {{0x27e95755,0x442d847b,0xb7bd8087,0x83528065}}, // haan_, _hae_, deţe, _دھما,
+ {{0x27e95756,0x7c2d5757,0x69c1d758,0x2f0b813c}}, // kaan_, _baar, _igle, søg_,
+ {{0x27e95759,0x442d90e4,0x78bd575a,0x7c2d0df6}}, // jaan_, _jae_, _nisv, _caar,
+ {{0x442da065,0x27e9404b,0x7c2d01d8,0x81d50a49}}, // _mae_, daan_, _daar, _হয়_,
+ {{0x412a176a,0x7c2f3a21,0x442dd75b,0xc87987c0}}, // ного_, adcr, _lae_, liş_,
+ {{0x443fd75c,0xc9550071,0x8cca8ebf,0x7c2d0079}}, // _onu_, атты, _संयो, _faar,
+ {{0x27e9575d,0x7c2d575e,0xc8798182,0x6724575f}}, // gaan_, _gaar, niş_, twij,
+ {{0xee3a1ebd,0x44225760,0x98a3140b,0x69c18ee1}}, // [4be0] нна_, mek_, сите, _ogle,
+ {{0x443fd761,0x67240613,0x69c18154,0xb2260705}}, // _anu_, rwij, _ngle, _смол,
+ {{0xae069513,0x69dc10cd,0x27e935e4,0x442db4dd}}, // _शासन_, mbre, baan_, _bae_,
+ {{0x44225762,0x27e95763,0x442dd764,0xe4d7026a}}, // nek_, caan_, _cae_, اونت_,
+ {{0x442d879f,0x68e3013c,0x6d43016d,0xcec880ff}}, // _dae_, _ændr, änar, _mộc_,
+ {{0x443fd765,0x69dc0dc2,0x11548ada,0xcec880ff}}, // _enu_, nbre, иклю, _lộc_,
+ {{0x44225766,0x6e21852e,0x645d0009,0x7d0183a6}}, // kek_, delb, yksi, juls,
+ {{0x44225767,0x8cca81b6,0x7d01930c,0x62838114}}, // jek_, _संबो, duls, anno,
+ {{0x20570158,0xd4678003,0x7de685a8,0x6440d768}}, // טיקל_, _сите_, лінд, _inmi,
+ {{0x7c2d1eaf,0x6e21d769,0x91d4000f,0x27e90ad4}}, // _saar, gelb, _ब्लै, zaan_,
+ {{0x27e9576a,0xa3e889a3,0x7c2d576b,0x6295001b}}, // yaan_, _मजा_, _paar, vozo,
+ {{0x27e902c1,0x44220019,0x7c2d02a3,0x645d0009}}, // xaan_, gek_, _qaar, uksi,
+ {{0x7c2d26a3,0x27e95499,0x78bd047f,0xeb070198}}, // _vaar, vaan_, _risv, ычно,
+ {{0x7c2d01d8,0x27e91197,0x6e2e0c2e,0x45d4b134}}, // _waar, waan_, _nabb, _токс,
+ {{0xd49b2f92,0x27e902a1,0x7c2d576c,0x6440837a}}, // _при_, taan_, _taar, _onmi,
+ {{0x44220179,0x20ca801c,0x4fd52098,0x61e53670}}, // cek_, _mùi_, ржат, _wehl,
+ {{0x6283998a,0x7c22d76d,0x6e2e03c3,0x6d5a8019}}, // [4bf0] ynno, deor, _babb, lyta,
+ {{0x442dd76e,0x2be3000f,0xe45f016d,0x64408147}}, // _sae_, परवा, lkö_, _anmi,
+ {{0x27e93d44,0x6e2e1cee,0xf3ff03a7,0x7bcf0081}}, // paan_, _dabb, nhão_, occu,
+ {{0x27e90079,0x7c22d76f,0x628882d4,0x7afd5770}}, // qaan_, geor, _ajdo, orst,
+ {{0x6e2e235a,0x23af81b6,0x7e7a810b,0x38690cb5}}, // _fabb, जेंद, kitp, mhar_,
+ {{0x386900a9,0x261403a4,0x44225771,0x442d8077}}, // lhar_, न्दी_, zek_, _wae_,
+ {{0x291100ad,0x44223f51,0x442dd772,0x29035773}}, // ntza_, yek_, _tae_, nuja_,
+ {{0x443f8870,0x38695774,0x5d8400f7,0x7ccc03bf}}, // _unu_, nhar_, _الفل, _uğra,
+ {{0xc87982bb,0xf8bf0264,0x6e21d775,0xaec28e11}}, // riş_, _mié_, telb, обыл,
+ {{0xee36d67f,0x63a9d776,0x29035777,0x442200d4}}, // ань_, _izen, kuja_, wek_,
+ {{0x44220f87,0xc05b835f,0x6da59290,0x35a58071}}, // tek_, _під_, рина, ранг,
+ {{0x3e640106,0x6f1d01ed,0x6e21d778,0x76b28035}}, // _möta_, _opsc, selb, _błys,
+ {{0x44225779,0x6e21d77a,0x69dc1412,0x38690387}}, // rek_, pelb, tbre, dhar_,
+ {{0x442201f9,0x26c405db,0x320782f7,0x6d5ad77b}}, // sek_, _émoi_, ngny_, byta,
+ {{0x4422577c,0xdca60009,0x69dc10b9,0xc0e60ca0}}, // pek_, _каки, rbre, робк,
+ {{0x6e2e577d,0xad1a8039,0x3869577e,0x7cc50084}}, // _sabb, _יוצר, ghar_, _gėri,
+
+ {{0xd90d0077,0xf77f1b26,0x27fd009a,0x929d809a}}, // [4c00] ویی_, _peça_, ówna_, _wpły,
+ {{0x7df380a5,0x2bde8d72,0x45d609b5,0x2903577f}}, // _आयोग_, मर्थ, рцат, buja_,
+ {{0x38690ad0,0xa5260087,0x98a61fa5,0xa0a609f7}}, // bhar_, рмед, _вине, _ванд,
+ {{0x7ae402a0,0x1d168158,0x20ca80ff,0x3d070074}}, // _kwit, יקער_, _rùi_, _हमरे_,
+ {{0x6e2e00a4,0x7e600087,0x7bc48110,0x7c22d780}}, // _tabb, _împo, žiuo, reor,
+ {{0x7c228867,0x63a9b8de,0xb8f481a8,0x64588035}}, // seor, _dzen, مكتب, _ścią,
+ {{0x61ead1ee,0x6f040083,0x672b80f1,0x63a9d781}}, // rafl, luic, _asgj, _ezen,
+ {{0x14d702f6,0x6440d782,0x649d5783,0x5fc606a7}}, // _כולל_, _unmi, néit, लखिल,
+ {{0x7e7a9d5e,0x44f184b7,0x76aa8198,0x63a98115}}, // titp, eġ_, ктов_, _gzen,
+ {{0x7afd135a,0x6d5a8084,0x7bc40372,0x27e68144}}, // vrst, tyta, _agiu, _weon_,
+ {{0x160b0f0a,0x994a92c8,0x24868503,0xe45f0009}}, // स्टर_, _جلال_, onom_, tkö_,
+ {{0x6298abf8,0x6d5aabe9,0x2486821e,0x291e83ac}}, // lovo, ryta, nnom_, _ipta_,
+ {{0x20118085,0x3d08016f,0x7afd1412,0x3ed481a8}}, // əzi_, ांडे_, urst, _اقتر,
+ {{0x6d5a809a,0x61e8a304,0x48fd800d,0xda7b245b}}, // pyta, _jedl, रूको_, тям_,
+ {{0x61e88e23,0x3869003c,0x2bd80743,0x07e702de}}, // _medl, thar_, _भ्वा, ицам,
+ {{0x7cc18029,0x29030304,0x38600b99,0x16190e5b}}, // _vērt, ruja_, gkir_, न्नर_,
+ {{0x629887dd,0x395f00eb,0xab5b0085,0x6f045784}}, // [4c10] kovo, ņus_, rcüs, guic,
+ {{0x386910fe,0x61e88370,0x644f012b,0x1a650019}}, // shar_, _nedl, ljci, _ایسی_,
+ {{0x63a9d785,0x91fc8029,0x3866d786,0x2486817f}}, // _szen, _plān, _olor_, fnom_,
+ {{0xef1f8214,0x6280026f,0x2486d787,0x644f017f}}, // çük_, émov, gnom_, njci,
+ {{0x28c0000f,0x261413ba,0x649d1867,0x96f80162}}, // _शूटि, न्ही_, léis, _кеят_,
+ {{0xa3ac086a,0x3866c495,0x20095788,0x5ed580ab}}, // केट_, _alor_, lgai_, _দিলে,
+ {{0xceb20158,0x25a50019,0x5c078198,0x2486a8fc}}, // _מיך_, _áll_, _вяза, bnom_,
+ {{0x20092062,0x6add00c8,0x6c8580f7,0x4c94862c}}, // ngai_, _বিনো, _الرم, _дисс,
+ {{0x6298816b,0x63a9d789,0xf749803d,0x69c50135}}, // bovo, _uzen, اجعه_, _oghe,
+ {{0x6287011f,0x0e6582cb,0x69c52111,0x765503ec}}, // jnjo, скон, _nghe, _jozy,
+ {{0x62870025,0xc329010f,0x993592c8,0x7ae401c6}}, // dnjo, _רו_, _دفات, _swit,
+ {{0xdd380029,0x15f395a7,0x491c8b75,0x44e38061}}, // nāša, _आयकर_, _महतो_, rű_,
+ {{0x38a1807b,0x2fdf80d7,0x27edd78a,0x649d0118}}, // jóri_, mbug_, maen_, véit,
+ {{0x2486811f,0xfbd81305,0x27ed831d,0x2bd806ce}}, // znom_, _भ्रम, laen_, _भ्रा,
+ {{0x76438063,0xa06a0098,0x291e0722,0x649d1b88}}, // _inny, гава_, ïta_, téit,
+ {{0xb8d0578b,0x320c009a,0x69c501bc,0x7cd38162}}, // _ओठ_, żdy_, _eghe, _căre,
+ {{0xe61a3cb3,0x24868ee0,0x386029a6,0x6444009c}}, // [4c20] лда_, vnom_, rkir_, _inii,
+ {{0x4432578c,0x6f044fbe,0x3860008b,0xf41f062c}}, // _hay_, ruic, skir_, nnän_,
+ {{0x44324022,0x5bc680f7,0x61ee578d,0x200900dd}}, // _kay_, مقال, mabl, bgai_,
+ {{0xdd8f0872,0x64560136,0x61ee473a,0xd49980e8}}, // رون_, _joyi, labl, арі_,
+ {{0xa3c9823c,0x4432004c,0x6298a39d,0x27e0578e}}, // ोड़_, _may_, tovo, mbin_,
+ {{0x64560032,0x523a0158,0x3320001b,0x649d00f7}}, // _loyi, רײַנ, _mpix_, néir,
+ {{0x629e0135,0x61e8d78f,0x64a69c0e,0xed570c6e}}, // _mkpo, _vedl, _гава, _лор_,
+ {{0x74168277,0x7643d790,0x443235db,0xc6f8a466}}, // _دورا, _anny, _nay_, аних_,
+ {{0x33668d8e,0x7d1b026c,0x629e0135,0x254e8085}}, // _уваг, _ćusi, _okpo, məli_,
+ {{0x4426d791,0x78a4026f,0x61ee0144,0xbb3b80be}}, // leo_, hliv, jabl, _געמי,
+ {{0x78a45792,0xe0da8b30,0x64565793,0x44322574}}, // kliv, _све_, _boyi, _bay_,
+ {{0x4426d794,0x7643d795,0x7cd38087,0x629e01bc}}, // neo_, _enny, _bărb, _akpo,
+ {{0x44324bc0,0x628702a5,0x78a4016b,0xddc88369}}, // _day_, tnjo, dliv, midž,
+ {{0xfaff020f,0x61ee157a,0x60c280f7,0x69c800e1}}, // rrë_, gabl, _hiom, žden,
+ {{0x4426822e,0x44325796,0x7cd38087,0xf77f0187}}, // keo_, _fay_, _păre, _peço_,
+ {{0xc7d9012f,0x629e0135,0x4426d797,0x661c0115}}, // рмах_, _ekpo, jeo_, _abrk,
+ {{0x4426d730,0x62351584,0x7655009a,0x62870115}}, // [4c30] deo_, _депу, _pozy, pnjo,
+ {{0x63ad00ad,0x518719b8,0x44321b11,0xe1338301}}, // _izan, _гума, _zay_, ščić_,
+ {{0x81e780c8,0x27e004b9,0x78a45798,0x4426d799}}, // _ভাল_, bbin_, bliv, feo_,
+ {{0xac19c6a1,0x241981bb,0x4432579a,0xc1b5103e}}, // роду_, роды_, _xay_, ंधीग,
+ {{0xa3ea0f97,0x3de800ab,0x7eaf804a,0xdea40019}}, // यरत_, _পারল, løpe, _کیلی,
+ {{0x672f111b,0x7c2629bd,0x63ad1fb6,0xdcf422f8}}, // _iscj, zekr, _mzan, _çağl,
+ {{0x4426d79b,0x6d5e010c,0x7cd38162,0x27edb3d9}}, // beo_, cypa, _mărc, taen_,
+ {{0x26c305a4,0x61ee0a0c,0x63ad5207,0x60c2d79c}}, // _hijo_, zabl, _ozan, _ciom,
+ {{0x2fdfcf79,0x60c2d79d,0x27edd79e,0x656d8326}}, // rbug_, _diom, raen_, _ƙaho,
+ {{0x248d8b40,0x8cca81cb,0x4432579f,0x26cb0314}}, // _hjem_, _संशो, _say_, _muco_,
+ {{0x26c357a0,0x63ad57a1,0x7cd38087,0x248d821e}}, // _mijo_, _azan, _sărb, _kjem_,
+ {{0x2907d7a2,0xdb078789,0x16190cf0,0x3a27826c}}, // muna_, _ímót, न्तर_, lenp_,
+ {{0xd6db8abe,0x6d5e009a,0x61ee1b7d,0x443257a3}}, // _сте_, zypa, tabl, _vay_,
+ {{0x386d80a9,0x44320051,0xfce580b3,0x4426811f}}, // lher_, _way_, _доко, zeo_,
+ {{0x61ee57a4,0x78a404e8,0x44320104,0x29078850}}, // rabl, tliv, _tay_, nuna_,
+ {{0xe3bf00ab,0x248dd7a5,0x44268511,0x26cb00e5}}, // seña_, _njem_, xeo_, _buco_,
+ {{0x4426803b,0xa0670381,0x8fa30d31,0x290784a7}}, // [4c40] veo_, цата_, дате, huna_,
+ {{0x2907bc75,0xa3af2e2b,0x9f44bb50,0x78a404fe}}, // kuna_, _करत_, namá_, sliv,
+ {{0x4426d7a6,0x26c300ab,0x2907d7a7,0xfc3f03a8}}, // teo_, _dijo_, juna_, llía_,
+ {{0xf7461935,0x2907d7a8,0x629c1c7c,0x26cb002a}}, // _демо, duna_, loro, _fuco_,
+ {{0x26c3238a,0x386d81b9,0x9d0a00ab,0xda05950e}}, // _fijo_, dher_, রবেন_, _राखत_,
+ {{0x4426d7a9,0x29078a40,0x16190ebf,0x6f162c2d}}, // seo_, funa_, न्दर_, mtyc,
+ {{0x290795e8,0xa3ea0b75,0x60c2847f,0xddc88699}}, // guna_, यरा_, _piom, tidž,
+ {{0x629c57aa,0x3ea5816d,0x63a457ab,0x26c3026c}}, // horo, ellt_, _oyin, _zijo_,
+ {{0x63a432cf,0x629c57ac,0xf8b302f6,0x29078087}}, // _nyin, koro, ושה_, auna_,
+ {{0x2ca6d7ad,0x629c57ae,0xb5e80264,0x76aa83a7}}, // llod_, joro, _পাঁচ, јтив_,
+ {{0xa06aa66c,0x60c28cb5,0x63a4228d,0x63ad2448}}, // _кажа_, _tiom, _ayin, _szan,
+ {{0x386d837e,0x6f16009a,0x63a402a0,0x2ca6911e}}, // cher_, ktyc, _byin, nlod_,
+ {{0x09e200c8,0x629c40ef,0x63a40a03,0x7792881b}}, // _বাজা, foro, _cyin, _زیدا,
+ {{0x59dd800d,0x32d0801c,0x9c130133,0x628ac44b}}, // नुपर, _mày_, _nọve, nnfo,
+ {{0x58d41232,0x6e28d7af,0x628ad7b0,0xc0cb210d}}, // дост, medb, info, руге_,
+ {{0x6722819d,0x200f02f1,0x43950a18,0x62800866}}, // _kpoj, ügi_, _найс, émor,
+ {{0x32d08142,0x629c57b1,0x765881c0,0x29078102}}, // [4c50] _này_, boro, _kovy, zuna_,
+ {{0xe3bf0511,0x6e28b7e5,0x26cb082c,0x249fa280}}, // leño_, nedb, _vuco_, _skum_,
+ {{0x7d08ac76,0x1bf200d4,0x5fca9869,0x69d5066f}}, // nuds, _अजमल_, ाखाल, mcze,
+ {{0xa3af1880,0x81e780c8,0x2907a198,0x61fa8118}}, // _करा_, _ভাই_, vuna_, _adtl,
+ {{0x4c3c0158,0x9327803d,0x3ea5d7b2,0x32d08129}}, // יגאצ, تران, yllt_, _cày_,
+ {{0x6e2882ce,0x99910019,0x32d080ff,0xdc558019}}, // jedb, lező_, _dày_, _کرسک,
+ {{0x386d804c,0xd24e80f7,0x823480f7,0x67228032}}, // ther_, انى_, برنا, _apoj,
+ {{0x629c1ee0,0x628a81a1,0x25ed8072,0xe3bf0144}}, // zoro, anfo, _आजही_, jeño_,
+ {{0x2907d7b3,0x386d82af,0x629c0a5a,0xe3bf0333}}, // suna_, rher_, yoro, deño_,
+ {{0x670c0beb,0x7ae9d7b4,0x386d8367,0x6e3557b5}}, // िंतक_, _iwet, sher_, _jazb,
+ {{0xcb090051,0x629c4319,0xdca62098,0x64a61287}}, // _תל_, voro, зани, зана,
+ {{0x7ae9d642,0x629c1111,0x69d5009a,0x672d1f12}}, // _kwet, woro, ecze, kwaj,
+ {{0x629c29f4,0x7052a403,0x20d18129,0x87049df9}}, // toro, _سنوا, _hái_, _قبول,
+ {{0x70b40ee6,0xf77082e3,0x7ae9d7b6,0x2f140106}}, // ंग्ल, _جام_, _mwet, väg_,
+ {{0xd9430dc0,0x629c48e1,0x6da2bac1,0x213e01a8}}, // нери, roro, _пиша, íth_,
+ {{0x786a003e,0xfc3f57b7,0x20d1801c,0x629c391e}}, // _býva, llín_, _mái_, soro,
+ {{0x442057b8,0x629c57b9,0x20d1801c,0x3e6086c0}}, // [4c60] _ibi_, poro, _lái_, _sòti_,
+ {{0x7c3602d0,0x388581a9,0x6a700338,0x6f16066f}}, // _hayr, mēru_, _säff, rtyc,
+ {{0x6f160063,0x0c26b38c,0x6e3501a1,0x261383ca}}, // styc, змен, _dazb, _दानी_,
+ {{0x3d1c0403,0xc20e800f,0x9b6a8221,0x7ae98365}}, // _नहीं_, _साहब_, йшла_, _bwet,
+ {{0xe3638ab2,0x442057ba,0x09c500c8,0x7c3602b8}}, // нкци, _mbi_, ্রণা, _mayr,
+ {{0x8d660396,0x442057bb,0x628a8333,0x6f044d10}}, // _евге, _lbi_, unfo, mric,
+ {{0x67229249,0x20d18104,0x4420082e,0x6e28c319}}, // _spoj, _cái_, _obi_, vedb,
+ {{0x64a39c68,0x69d557bc,0x27f20079,0x2b40026c}}, // _заса, zcze, nayn_, _oric_,
+ {{0x213e8355,0x07f780f7,0x6f041cb6,0x290a0234}}, // _wrth_, سريع_, nric, huba_,
+ {{0x290a467d,0x442057bd,0x27f20079,0x6f040c5e}}, // kuba_, _abi_, hayn_, iric,
+ {{0x7c361010,0x6e28803a,0x20d18028,0x442057be}}, // _bayr, redb, _gái_, _bbi_,
+ {{0x2b208b3b,0x60c6059c,0x7d08806a,0x6e2882d4}}, // _यहाँ_, _nikm, ruds, sedb,
+ {{0xfc3f22d8,0xa5f882de,0xe3bf57bf,0xe8e00129}}, // blín_, _делу_, reño_, _thớt_,
+ {{0xe3bf040e,0x442057c0,0xf7bb81c6,0x7c361407}}, // seño_, _ebi_, _הזדמ, _eayr,
+ {{0x290a2676,0xe3bf0511,0xdd8f8048,0x6f040037}}, // guba_, peño_, _чш_, eric,
+ {{0x29030168,0x7c3602d0,0x672d0314,0x6e35128a}}, // rrja_, _gayr, twaj, _sazb,
+ {{0xa01b57c1,0xbfab21f6,0xcc3400f7,0x3ed901a8}}, // [4c70] _björ, стве_, _سريع, زواج_,
+ {{0x442b378d,0x672d1258,0x44393396,0x7bc98014}}, // lec_, rwaj, lds_, _sgeu,
+ {{0x7ae9d7c2,0x290a1267,0x6e3501d0,0x2dd801a8}}, // _swet, cuba_, _vazb, سبلة_,
+ {{0xc0ab07bd,0x44394f95,0x442b57c3,0x7c3657c4}}, // _داخل_, nds_, nec_, _xayr,
+ {{0x443957c5,0x6f0401df,0x1a658416,0x9f59861c}}, // ids_, cric, ویری_, üsü_,
+ {{0x78a2826c,0x442b57c6,0xbb4290ca,0xa01b008b}}, // _akov, hec_, тешк, _gjör,
+ {{0x442b4744,0xe1f09a3c,0x6594117c,0x261381d0}}, // kec_, اسه_, _зару, _दाबी_,
+ {{0xba77803d,0x443940d1,0x673b8bcf,0x7ae9d7c7}}, // _کارت, jds_, tvuj, _twet,
+ {{0x521581ae,0x290a00b4,0x200d86ab,0x7afb0140}}, // _одат, zuba_, rgei_, šutj,
+ {{0x443905f8,0x442034b1,0x20d1801c,0x7c2b9ff1}}, // eds_, _sbi_, _tái_, legr,
+ {{0x90e78077,0xfc3f57c8,0xb5798009,0x291808dc}}, // _رسان, rlín_, ющих_, xtra_,
+ {{0x442b0988,0x645b92b6,0x6f0457c9,0xff519e29}}, // gec_, _joui, yric, _لخت_,
+ {{0x201882bf,0x6f09d7ca,0x25aa10be,0x78a98b81}}, // əri_, quec, _कर्ण, mlev,
+ {{0x290a0a5a,0x29181b08,0xc332807c,0x60c657cb}}, // tuba_, ttra_, דוך_, _sikm,
+ {{0x442b0e04,0x3d0c009a,0x6449800b,0x6f0457cc}}, // bec_, _हमें_, _onei, wric,
+ {{0x44202a86,0x78a9d7cd,0x26c780b4,0x6add00ab}}, // _ubi_, nlev, _hino_, _বিরো,
+ {{0x26c7d7ce,0x98a30468,0x26cfd7cf,0x6283d7d0}}, // [4c80] _kino_, тите, _jugo_, kino,
+ {{0x26cfd7d1,0x6f0432c7,0x26c7d7d2,0x27f257d3}}, // _mugo_, rric, _jino_, sayn_,
+ {{0x6283d7d4,0x26cf862f,0x645bd0aa,0x6f045778}}, // dino, _lugo_, _boui, sric,
+ {{0x26c7d7d5,0x7c2b8faf,0x32d40009,0x50b621d2}}, // _lino_, gegr, _käy_, _існу,
+ {{0x78a99fb2,0x645b8866,0x93cb8019,0x2d828326}}, // dlev, _doui, زانہ_, ̄ke_,
+ {{0xfe6783f8,0x442b06b9,0x78a2813c,0x38cb00d7}}, // _شد_, zec_, _skov, _دایی_,
+ {{0x65c30110,0x7c2bd778,0xe5a6197b,0x443944e7}}, // _абра, begr, _циви, yds_,
+ {{0xcec88104,0x2900a8c4,0x765c3f52,0x2c62016b}}, // _hội_, čia_, _hory, _kódu_,
+ {{0x26c782ec,0x09b500ab,0x61fe23f9,0xd426811c}}, // _bino_, _জ্ঞা, _odpl, ışdı_,
+ {{0x628382d7,0x7cd38162,0x5d54a061,0x26c789c4}}, // cino, _căro, екот, _cino_,
+ {{0x26c7d7d6,0x78a9d7d7,0x7ae2d7d8,0x7e600162}}, // _dino_, blev, lpot, _împu,
+ {{0x27ef807b,0x765c57d9,0xa3d64073,0xfc3f002a}}, // _gegn_, _lory, ाँग_, rlío_,
+ {{0x443957da,0x26c7d7db,0x442b2f62,0x8cca8127}}, // rds_, _fino_, rec_, _संको,
+ {{0x26c7a3ff,0xb06184a2,0x9958001b,0x6594814c}}, // _gino_, _ääre, láře_, _палу,
+ {{0xcec88104,0x6d58062c,0x6aaa8428,0xf7730061}}, // _nội_, äval, ilff, ھار_,
+ {{0x9ed90071,0x26c78365,0xb8d702f1,0x2480008e}}, // змат_, _zino_, _छठ_, _gmim_,
+ {{0x69dad7dc,0xeb0d8bb8,0xb8d60576,0x7bcd00eb}}, // [4c90] _ofte, _समेत_, _ची_, _igau,
+ {{0x6283887a,0x6449d7dd,0xb90500ab,0x765c422a}}, // xino, _snei, _নি_, _cory,
+ {{0xd49b917e,0x8c3d8214,0xdb00802a,0x25de06a7}}, // орд_, _akşa, _gzmú, गड़ी_,
+ {{0x1ddf18a9,0xd5df0a0d,0x05df0e70,0x69dac33f}}, // _प्रत, _प्रज, _प्रब, _afte,
+ {{0x7c2b83a8,0xe8df0028,0x27ef8370,0x38690041}}, // regr, _khỏe_, _regn_, mkar_,
+ {{0x6e38d7de,0x38691a31,0xdcfb0077,0x7aed3f9c}}, // _navb, lkar_, _ورزش_, _mwat,
+ {{0x6283d7df,0x6f0d2e88,0x8cca8006,0x26c7b032}}, // rino, luac, _संगो, _rino_,
+ {{0x69da8786,0x26c7d7e0,0x38690687,0x7bcd2031}}, // _efte, _sino_, nkar_, _ngau,
+ {{0x6f0d2d11,0x78a9d7e1,0x261381ce,0x27ef852a}}, // nuac, rlev, _दादी_, _vegn_,
+ {{0x99678371,0x02cb9905,0x7bcd57e2,0x63a9912e}}, // _отел, ादीन, _agau, _iyen,
+ {{0x7aed2892,0x78a9a28f,0xd0118019,0x6f0d21fd}}, // _awat, plev, _ملا_, huac,
+ {{0xeb9a0f2e,0x3e6981ac,0x63a98365,0x7aed36ed}}, // зим_, _túto_, _kyen, _bwat,
+ {{0x3ea10006,0x386957e3,0x2f2301a9,0x63a9818f}}, // koht_, dkar_, nīgā_, _jyen,
+ {{0xd7748b8c,0x42798158,0x6f0d1883,0x7afb8c53}}, // _جامع, לאָג, duac, _mvut,
+ {{0xe2972f4b,0x628e21fb,0x48b60d70,0xd826046e}}, // хар_, rnbo, ещат, ндви,
+ {{0x786a003e,0x38691600,0x63a9d7e4,0xddca82d6}}, // _vývo, gkar_, _oyen, _anfň,
+ {{0x63a9d2b3,0x7c2402a0,0x6f0d57e5,0x06e380ab}}, // [4ca0] _nyen, _ibir, guac, _মিডি,
+ {{0xba760077,0xdb9b0496,0xa2d50e81,0x7bdbc577}}, // _ساخت, _מספר, _यंत्, _afuu,
+ {{0x9f420364,0x7afbd7e6,0x7c2404b7,0x7871128d}}, // _sekä_, _avut, _kbir, _såve,
+ {{0xdee61f25,0x386900f2,0x66e61a19,0x78710687}}, // _поми, ckar_, _пома, _påve,
+ {{0x7ae28067,0x6f0d11b9,0x2907a766,0x7c244b40}}, // upot, cuac, erna_, _mbir,
+ {{0x7ae28359,0xcec8801c,0x9f5206c0,0x3a3a0106}}, // rpot, _tội_, vayè_, _japp_,
+ {{0xdd920307,0xfd078277,0x7c2457e7,0x98c68087}}, // _صور_, _حج_, _obir, нсил,
+ {{0x7f428201,0x9d1b04de,0x61f502a5,0x7ae2d7e8}}, // _proq, _מולט, zazl, ppot,
+ {{0x63068c48,0x29078083,0x26ca2dcb,0x63a9d7e9}}, // _سوال, arna_, _kibo_, _gyen,
+ {{0xdbfb0a16,0x7c242a75,0xe0ce80ab,0x3b83a6b1}}, // ्ल्ड_, _abir, _রবীন, _слуг,
+ {{0xdb160065,0xdd948196,0xfc3f026f,0xf76f803d}}, // _együ, таны, slím_, لای_,
+ {{0x26ca0c27,0x7f428c1d,0x7ccc07c0,0x2486866f}}, // _libo_, _troq, _uğru, niom_,
+ {{0x4e10000d,0xfbab17ae,0x6d43d7ea,0x7eb00338}}, // ालाई_, ятой_, _erna, väpn,
+ {{0x7c240c56,0x442482c4,0x26ca57eb,0x2ba50d72}}, // _ebir, _lbm_, _nibo_, _गुणा,
+ {{0xe4a71d79,0x3e728087,0xe29f007b,0x290e8041}}, // _прио, _câte_, nuði_, nufa_,
+ {{0x2ea90054,0x7aed4b40,0x26ca01a1,0xd49801e2}}, // कत्त, _twat, _aibo_, _пры_,
+ {{0x644d57ec,0x7c2f009f,0x3869088b,0x26ca57ed}}, // [4cb0] _inai, mecr, rkar_, _bibo_,
+ {{0x386957ee,0x290eafc9,0x7c240805,0x26ca0081}}, // skar_, kufa_, _zbir, _cibo_,
+ {{0x4424d7ef,0x443b00f1,0x6287076a,0x9419a296}}, // _bbm_, _kaq_, mijo, джет_,
+ {{0x628757f0,0x7c3d02fe,0x6281801b,0x4424aaa0}}, // lijo, ndsr, _smlo, _cbm_,
+ {{0xa7fb07f4,0x27e957f1,0x26190f12,0x6f0d2bd5}}, // _coñe, mban_, _पापी_, quac,
+ {{0x78ad57f2,0x628712b9,0x26ca02c4,0x63a9804f}}, // llav, nijo, _gibo_, _vyen,
+ {{0x40959cd5,0x248688ae,0x644d5163,0x56942749}}, // крит, biom_, _onai, _сарт,
+ {{0x27e90065,0x7e600087,0x6f1bcdb0,0x7c3d0366}}, // nban_, _împr, ttuc, jdsr,
+ {{0x442fa00a,0x2ba88105,0x8ccb141b,0x5b158b73}}, // meg_, _छुपा, _संजो, _шмат,
+ {{0x442f8558,0x7c3bd7f3,0x644d2307,0x83fd8065}}, // leg_, _haur, _anai, _előt,
+ {{0x27e90065,0x2619123a,0x78ad3ef9,0x7c3b9cc5}}, // kban_, _पानी_, klav, _kaur,
+ {{0x442f8114,0x7c3b811e,0xddce001b,0x3201008e}}, // neg_, _jaur, _dobř, _adhy_,
+ {{0x7c3ba7f5,0x987a80be,0xe73981f3,0x3e641252}}, // _maur, _קאנט, _цел_, _kött_,
+ {{0xd2520c3b,0x62870110,0x6d4381ac,0x161886b7}}, // جنس_, gijo, _trna, _दायर_,
+ {{0xc21284de,0x78ad008b,0x78b981c0,0x7fd60221}}, // יהם_, flav, _khwv, візі,
+ {{0x442f803a,0x27e90019,0x7c3bd7f4,0x3eb802c4}}, // jeg_, gban_, _naur, _shrt_,
+ {{0x442fa10a,0x628757f5,0xee3a1a1a,0x984580eb}}, // [4cc0] deg_, bijo, мна_, dēļ_,
+ {{0x62870084,0x6d41876c,0x34ca8f12,0x60cb8901}}, // cijo, ovla, िद्द, _ligm,
+ {{0x27e957f6,0x5fa7016f,0x7c3b9500,0x26ca01e8}}, // bban_, _कुठल, _baur, _vibo_,
+ {{0x25410a8e,0xa7fb062f,0xb5098beb,0x7c3bd7f7}}, // _dėl_, _poñe, _विनय_, _caur,
+ {{0x7c3bd7f8,0x26ca57f9,0x332900b9,0x2c1a081f}}, // _daur, _tibo_, _xpax_, _भानू_,
+ {{0x6f09d7fa,0x5b7b00be,0xe18909a7,0x442480b9}}, // drec, ורנא, _آئین_, _wbm_,
+ {{0xcec88142,0x6e3c57fb,0x442483a7,0x7c3bd7fc}}, // _một_, _iarb, _tbm_, _faur,
+ {{0x442f8052,0x7c3bd7fd,0x62870110,0x656357fe}}, // ceg_, _gaur, zijo, änhe,
+ {{0x291c82a5,0x6e3c57ff,0x83fd8019,0x60cb809c}}, // stva_, _karb, _elős, _digm,
+ {{0x644d5800,0xa80603bf,0x7c3b9ffc,0xb90880ab}}, // _snai, ldığ, _zaur, _বি_,
+ {{0xa96a9fbe,0x6f09802e,0xb7bd8087,0x27e90061}}, // _تمام_, arec, deţu, yban_,
+ {{0x6f098eb9,0xa80603bf,0x9ffa8eca,0x70be0c33}}, // brec, ndığ, _آراء_, ्दोल,
+ {{0x6f09a509,0x62875801,0x63b6009a,0xcec880ff}}, // crec, tijo, _czyn, _bột_,
+ {{0x2be30063,0xc60800ab,0xcec880ff,0x3e640106}}, // _क्रं_, র্তা_, _cột_, _möts_,
+ {{0x62875802,0x442fbb1d,0x27e90065,0x236d01ac}}, // rijo, yeg_, tban_, šej_,
+ {{0x62870a28,0x27e95803,0x6e3c1d49,0x61f8826c}}, // sijo, uban_, _aarb, havl,
+ {{0xdef80b71,0x442fa055,0x2ca90a20,0xc0c8464f}}, // [4cd0] вых_, veg_, _ikad_, туре_,
+ {{0x78ad0b72,0x7c3b83c3,0x63ad5804,0x27e90019}}, // slav, _saur, _iyan, sban_,
+ {{0x6e3c3b88,0x7c22d805,0x7c3b8081,0x569403c7}}, // _darb, ffor, _paur, шафт,
+ {{0x7c22a7d1,0xfc3f5806,0x25410110,0x6e3c051e}}, // gfor, ndí_, _vėl_, _earb,
+ {{0x6e3c5807,0x442f92af,0x2911071f,0x4ea702df}}, // _farb, reg_, luza_, _арва,
+ {{0x442fd808,0x63ad3b27,0x7c3b8326,0x7afd39a9}}, // seg_, _myan, _waur, isst,
+ {{0x7c3ba614,0x29114c0f,0x7c22d809,0x63ad4439}}, // _taur, nuza_, bfor, _lyan,
+ {{0x20d8863e,0x6f098cc9,0x81d900c8,0x83fd8019}}, // _déi_, trec, ারি_, _előr,
+ {{0x62850613,0x63ad580a,0xb4bf8768,0x2911580b}}, // _omho, _nyan, ँगी_, huza_,
+ {{0x3d08035a,0x7afd0370,0x291123a8,0x6f098cf7}}, // ांचे_, dsst, kuza_, rrec,
+ {{0x63ad580c,0x6f09d778,0x81d900ab,0x60cb8bb1}}, // _ayan, srec, ারা_, _tigm,
+ {{0x67030aed,0x6d41a409,0x2911580d,0x2246080a}}, // _लिंक_, rvla, duza_, _şok_,
+ {{0x63ad02f4,0xd7f885e9,0x7afd21b4,0x9c1411d3}}, // _cyan, _рух_, gsst, _fọnt,
+ {{0xa3e40a74,0x454580d5,0x29110b5e,0x78710bbd}}, // _प्र_, _منتق, fuza_, _råva,
+ {{0x69dc02be,0x04462853,0x63ad556d,0x29112a8a}}, // rcre, _бейн, _eyan, guza_,
+ {{0x61f882a5,0x6e3c580e,0x388c801b,0x69dc2a70}}, // zavl, _sarb, běru_, scre,
+ {{0x6d47527c,0x386d83b2,0x7c2283ba,0x290a0267}}, // [4ce0] _arja, iker_, vfor, srba_,
+ {{0x2619035a,0x29111e98,0xe6171541,0x7ae40a63}}, // _पाणी_, buza_, лду_, _itit,
+ {{0x386d92f0,0x0446a84d,0x6e3c580f,0x787581ac}}, // kker_, лежн, _varb, _záve,
+ {{0xeb9a8991,0x6e3c02a3,0x386d8a0f,0x3ea59dba}}, // ние_, _warb, jker_, holt_,
+ {{0x6e3c5810,0x7c22ae52,0x5efe052a,0xa806080a}}, // _tarb, rfor, _लिट्_, rdığ,
+ {{0x7c229277,0xd13a8656,0x386d9151,0xfc3f016b}}, // sfor, ехи_, eker_, zdí_,
+ {{0x26190b9f,0x14cf816f,0x7c22adb7,0xe9df827d}}, // _पाती_, _संगण, pfor, _đút_,
+ {{0x64a6d5d3,0x63a90063,0x7ae45811,0x61f88d11}}, // _саба, żeni, _otit, savl,
+ {{0x44f181e2,0x6a169a00,0xeb979073,0xa3e482f1}}, // eš_, _مبار, _бит_, पुण_,
+ {{0x7afd0370,0x6ce6cf05,0x2ca6a960,0x073704de}}, // vsst, ліме, lood_, נאים_,
+ {{0x7ae45812,0x63ad5813,0x2bc7064a,0x628ad814}}, // _atit, _syan, रेना, mifo,
+ {{0x2ca6d815,0x386dbd20,0xd5b79adb,0x29111ad3}}, // nood_, cker_, ысы_, vuza_,
+ {{0xfc3f3bec,0x2f18a45b,0x657a8019,0x6d410661}}, // rdí_, _роль_, szth, _álav,
+ {{0x2ca6aa06,0x63ad5816,0x7afd5817,0x29110b5e}}, // hood_, _vyan, rsst, tuza_,
+ {{0x7ae40870,0x63ad5818,0x2ca6aa1f,0x9f4b07f1}}, // _etit, _wyan, kood_, _secà_,
+ {{0x29110c03,0x628ad819,0x7ae40609,0xda1703ca}}, // ruza_, hifo, _ftit, _तावत_,
+ {{0x26cecc19,0x209811e9,0x44294988,0x35f50790}}, // [4cf0] _kifo_, укты_, _iba_, ипар,
+ {{0xdd9b004a,0x32d986c0,0xa7fb016a,0x7cd38162}}, // нше_, _rèy_, _doña, _sări,
+ {{0xceb3893f,0x7cd38087,0x3cfe86cb,0x69c80d2f}}, // ציע_, _pări, nstv_, žder,
+ {{0xd3718013,0x2ca6d81a,0xae19001b,0x23bb026b}}, // يها_, good_, न्जन_, _gàjí_,
+ {{0x4429082e,0x291e8197,0x447c03de,0x7c3f0df6}}, // _mba_, _aqta_, ענדע, _maqr,
+ {{0xaa458ff0,0x61f70101,0x6d47008e,0x06e38264}}, // рекл, _jexl, _trja, _মিলি,
+ {{0x4429581b,0x60cf00d2,0x3a3e8118,0xc6920039}}, // _oba_, _kicm, _datp_, _ואם_,
+ {{0xf8b50ee6,0x6f0d0090,0x248b01a1,0xaf0584ad}}, // ंतिय, orac, jicm_, апил,
+ {{0x3255807f,0x7c29d81c,0x443f801f,0xcf8e8077}}, // _квар, _iber, _iau_, وژه_,
+ {{0x443fd81d,0x386dd81e,0x442946e5,0x628a802a}}, // _hau_, sker_, _aba_, cifo,
+ {{0x443f8274,0x7ae44480,0x4429581f,0x6f0d0219}}, // _kau_, _stit, _bba_, hrac,
+ {{0x443fbd3b,0x64a61597,0xcb6a01f3,0xdca63e3d}}, // _jau_, рама, ваме_, рами,
+ {{0x443f88b3,0x7c2988b8,0x27edd820,0x26ce846d}}, // _mau_, _mber, mben_, _fifo_,
+ {{0x443fd821,0x27edd822,0x877a80be,0x4429404e}}, // _lau_, lben_, _באני, _eba_,
+ {{0x7c29d823,0xa7fb0693,0x2effa1e5,0x257b8032}}, // _ober, _soña, lsuf_, _dálé_,
+ {{0x3fe628fe,0x4429077f,0x443f86c1,0x6f0d3cfe}}, // ржав, _gba_, _nau_, frac,
+ {{0x7e63d824,0x27ed8352,0xa6860791,0x78780073}}, // [4d00] _konp, iben_, алад, _níve,
+ {{0x7c29cc82,0x61408019,0xf5e7002e,0x60cf0037}}, // _aber, nálá, имул_, _eicm,
+ {{0x27ed8065,0x6f0d1a29,0x2ca6cc98,0x7e638bfe}}, // kben_, arac, tood_, _monp,
+ {{0x499b0f60,0x61fc3a32,0x0b469109,0x443fd825}}, // _ישיב, larl, инен, _cau_,
+ {{0x443fd826,0xa01b016d,0x2ca6857b,0x7c298642}}, // _dau_, _omöj, rood_, _dber,
+ {{0x61fc5827,0x443f82be,0x27ed8352,0x27e03468}}, // narl, _eau_, eben_, lcin_,
+ {{0xd1308013,0x628ad828,0xed571a1b,0x443f821e}}, // سمة_, rifo, _кор_, _fau_,
+ {{0x27e003c3,0x443fd829,0x7416815b,0x20548098}}, // ncin_, _gau_, _خورا, ския,
+ {{0x61fc3c41,0x6440a20e,0x27e03603,0x4426d82a}}, // karl, _iami, icin_, mfo_,
+ {{0x61fc582b,0xa3bd0bb8,0x443fa6b4,0x7c29824a}}, // jarl, _अरब_, _zau_, _zber,
+ {{0x27edd82c,0x443fd82d,0x61fc582e,0xa2b7000d}}, // bben_, _yau_, darl, ोगस्,
+ {{0x44269aed,0x6440a07b,0x443fbff5,0x746b0ba5}}, // nfo_, _jami, _xau_, тров_,
+ {{0x7b642aed,0xec718158,0x6140a5b3,0x61fc209b}}, // отре, פֿט_, bálá, farl,
+ {{0x28db1094,0x443f44a9,0x6f0d141f,0x7d03026f}}, // _मंदि, žu_, vrac, ánsk,
+ {{0x60c400b9,0x7c3f022b,0x753c0140,0x6f0d582f}}, // fmim, _taqr, _nsrz, wrac,
+ {{0x64408569,0x6d4a9286,0x6f0d5830,0x44292bd7}}, // _nami, _irfa, trac, _uba_,
+ {{0x443f946a,0xd7f85831,0x61fc5832,0xdddc0301}}, // [4d10] _rau_, рус_, barl, _smrš,
+ {{0x443fd833,0x7c2984c3,0x61fc229d,0xe45f016d}}, // _sau_, _sber, carl, ljö_,
+ {{0x443fa6e1,0x6440bf5e,0x6f0d0046,0xf8b107d2}}, // _pau_, _bami, srac, _فکر_,
+ {{0x6f0d1122,0x29360158,0x6f02826f,0x443fb592}}, // prac, _פארן_, _ovoc, _qau_,
+ {{0x6440d834,0x443fd835,0xa6db0125,0x91e58db4}}, // _dami, _vau_, _orði, боле,
+ {{0x443fd836,0x200680ee,0x64428118,0xe80f02f1}}, // _wau_, _adoi_, adoi, िलका_,
+ {{0x443f946a,0x27ed8065,0xdd940196,0x65940f25}}, // _tau_, tben_, _дары, _дару,
+ {{0x6440d837,0x26d15838,0x61fc5839,0x27edd83a}}, // _gami, _hizo_, zarl, uben_,
+ {{0x27edd83b,0xbb8580f7,0x7e63c78b,0x6d4ad83c}}, // rben_, _الذي, _ponp, _arfa,
+ {{0x64408ebf,0x2489583d,0x7875826f,0x60c400d2}}, // _zami, _amam_, _dáva, zmim,
+ {{0x41b58b76,0x26d90207,0xe5a58037,0x26d1583e}}, // _امار, _luso_, сини, _mizo_,
+ {{0x25458029,0xf8bf0028,0x672f009a,0x26d10118}}, // _vēl_, _nhé_, _opcj, _lizo_,
+ {{0x6d4ad83f,0x9f40807b,0xd7a70c28,0xfc3f0118}}, // _erfa, _þið_, _कुलच, moíl_,
+ {{0x7e7a8f67,0x63bb804f,0x91e318d1,0x09e31628}}, // chtp, _mzun, зоре, зорн,
+ {{0x61fc22f8,0x7875826f,0x6a6b080a,0xa7fb01ca}}, // rarl, _záva, _nüfu, _coño,
+ {{0x26d95840,0x61fc3a03,0xf8bf5841,0x27e00300}}, // _buso_, sarl, _ché_, ucin_,
+ {{0x44f501ac,0x60c45842,0x7bd6222e,0x2bc70054}}, // [4d20] eť_, rmim, _igyu, रेदा,
+ {{0x61fc208e,0xc7b283c8,0x3e76007b,0x27e000fc}}, // qarl, לבן_, _bæta_, scin_,
+ {{0x6440d843,0xa3df04e5,0x63a904b7,0xa0a60110}}, // _pami, दुक_, żent, _ганд,
+ {{0x628e02e8,0xf8bf00ff,0x26d9495b,0x78a98b80}}, // libo, _ghé_, _fuso_, joev,
+ {{0x8c1b02f6,0x28b500d4,0x38ba8168,0x26d14022}}, // רוני, ंतरि, jëra_, _fizo_,
+ {{0x66e30098,0x63bba44c,0xdee300ae,0x2cbfa008}}, // _хора, _dzun, _хори, _ehud_,
+ {{0x67245844,0x6440d845,0xb5099521,0x2ee68166}}, // ltij, _tami, _विषय_, _stof_,
+ {{0x04968064,0x3860059c,0xe45f016d,0x628e5476}}, // _الرح, njir_, xjö_, hibo,
+ {{0x628e0033,0x1ddc01a2,0x421a830f,0x3f8000e1}}, // kibo, _बलात, _مزاج_, nziu_,
+ {{0xa3e4923a,0x626580f7,0x8c448087,0x1ed781a8}}, // पुर_, _والق, ăşur, حبيب_,
+ {{0xd7efd846,0x6724111b,0x998c8858,0x628e1400}}, // _ну_, htij, _hadž_, dibo,
+ {{0x67244603,0x7d0305e4,0xac1900e8,0x6aaa800b}}, // ktij, ánsi, шому_, loff,
+ {{0xa7fb01df,0x628e5847,0x938b2482,0x0b8b302b}}, // _soño, fibo, усна_, усни_,
+ {{0x38668b3c,0x26d90333,0xb69b0087,0xe45f0106}}, // _hoor_, _ruso_, _avân, sjö_,
+ {{0x54e6806b,0x9f59079f,0x26d9535b,0x65939052}}, // _استق, _gesê_, _suso_, _нашу,
+ {{0x43953e41,0x26d95848,0x67245849,0x7602801b}}, // _майс, _puso_, ftij, bízí,
+ {{0x9df91fc1,0x628e0f8e,0x3866d84a,0x6aaac432}}, // [4d30] инат_, bibo, _moor_, koff,
+ {{0x88bc81d0,0x2cad8937,0x7875936f,0x32f6866f}}, // _zpět, _sked_, _návn, dły_,
+ {{0xd5bb2457,0x38740333,0xf8bf0036,0x6eff046d}}, // уса_, ñará_, _thé_, _bàbá,
+ {{0x3866d84b,0x2a6582d5,0x62989989,0x26d919b0}}, // _noor_, _solb_, envo, _tuso_,
+ {{0x44f53404,0x7c2d3149,0xe7840012,0x629a016b}}, // sť_, _mbar, _еуро, čtov,
+ {{0x60da88ae,0x6aaad84c,0x7c2d01a1,0xf99002e3}}, // _butm, goff, _lbar, _آبی_,
+ {{0x32f6809a,0x7875816b,0x48798081,0x386690b0}}, // ały_, _dávn, исия_, _boor_,
+ {{0xd7098991,0x38668118,0x4f0995b7,0x61fa9384}}, // ание_, _coor_, анин_, _eetl,
+ {{0x3866d84d,0x442dca40,0x63bb85b2,0x628e0518}}, // _door_, _ibe_, _uzun, yibo,
+ {{0x7c2d21ed,0x6aaa89c4,0x7ae9d84e,0x7645584f}}, // _abar, coff, _itet, ndhy,
+ {{0x628e02d6,0x7ae98088,0x7c2d0362,0x2d810174}}, // vibo, _htet, _bbar, nzhe_,
+ {{0x386681b4,0x69d7112e,0x7f758012,0x6f0e85b2}}, // _goor_, _agxe, _мулц, üncü,
+ {{0x28df91bc,0x628e5850,0xc61100c8,0x442dc5e1}}, // _पंडि, tibo, স্যা_, _mbe_,
+ {{0x7875816b,0x9c13019d,0x3e7b8036,0x27ff808b}}, // _kávo, _nọfe, _bête_, laun_,
+ {{0x628e2704,0x76439083,0xd9434b60,0x442dd851}}, // ribo, _iany, мери, _obe_,
+ {{0x7643d852,0x67245853,0x4fea15d2,0x628e5854}}, // _hany, ttij, рман_, sibo,
+ {{0x7643d855,0x442003d3,0x628e02c4,0x16660ea6}}, // [4d40] _kany, _ici_, pibo, овам,
+ {{0x442dd856,0x3e7b80e7,0x6456022c,0x764384b9}}, // _abe_, _fête_, _hnyi, _jany,
+ {{0x67242948,0x7643d857,0x7875803e,0xfe21864a}}, // stij, _many, _návo, _मानस_,
+ {{0x2bc70076,0x291800b4,0x61fad858,0x6143001b}}, // रेवा, mura_, _setl, sílá,
+ {{0x7d002067,0x6aaa94cf,0x29182909,0x61fa8503}}, // _bárá, toff, lura_, _petl,
+ {{0x442d8a2c,0x7643b188,0xa6e2008b,0x71250061}}, // _ebe_, _nany, áðun, _بریل,
+ {{0x69c302ba,0x6f041d15,0x291802a0,0x787c85db}}, // _únet, lsic, nura_, _léve,
+ {{0xb4d6023c,0x442d90ab,0x6444009c,0x61fac613}}, // सदी_, _gbe_, _naii, _wetl,
+ {{0x38669302,0x7643d859,0xf220023c,0x29180423}}, // _voor_, _bany, _बाढ़_, hura_,
+ {{0x291852cb,0x64562ab2,0x60da8059,0x35af0740}}, // kura_, _anyi, _tutm, _जुड़,
+ {{0x7643d85a,0x2fc7001c,0x9f5900f1,0x2918585b}}, // _dany, ̀ng_, _pesë_, jura_,
+ {{0x9c1301bc,0x629e074a,0x76439a14,0x9f590176}}, // _mọge, _ajpo, _eany, _fesè_,
+ {{0x7643d489,0x7875801b,0xfbca8e88,0xa09b8039}}, // _fany, _závo, िधिम, _דיאט,
+ {{0x787c83d3,0x7643d85c,0x2918585b,0x64562256}}, // _déve, _gany, fura_, _enyi,
+ {{0x60c2d85d,0x2905585e,0x867b8039,0x98ba0176}}, // _khom, éla_, _דרגו, _espč_,
+ {{0xda170b9f,0x290346e9,0x7c2d3ef1,0x787584e8}}, // _ताकत_, rsja_, _ubar, _návl,
+ {{0xcddb0fbb,0x2fcf00f2,0x6d4e585f,0x7643930e}}, // [4d50] ање_, ägg_, _arba, _yany,
+ {{0x3e7b82be,0x29185860,0x454b1cf8,0x69c806d4}}, // _tête_, bura_, ичем_, ýdec,
+ {{0x2918438a,0x7ae9d861,0x7e670088,0x27f20338}}, // cura_, _stet, _vojp, bbyn_,
+ {{0x2b4dd862,0xe7870eef,0x6f040799,0x91fc8ec3}}, // _prec_, _дуго, bsic, _glāz,
+ {{0x09e5a103,0x6d4e18d5,0x44395551,0x6f040203}}, // полн, _erba, ies_, csic,
+ {{0x44394780,0x69c303a8,0x27e68114,0x60c2d863}}, // hes_, _únes, _afon_, _ahom,
+ {{0x6d4e007d,0xc27c0039,0x27ff9ab3,0x649601d6}}, // _grba, שרוי, taun_, lšie,
+ {{0x44395864,0x60c2b311,0x76439855,0xd12e880b}}, // jes_, _chom, _sany, امي_,
+ {{0x60c2cce2,0x76439452,0x7ae98010,0x9c1301bc}}, // _dhom, _pany, _utet, _mọde,
+ {{0x458585c2,0x2fd9015d,0x7875d865,0x787cd568}}, // згов, _lgsg_, _távo, _réve,
+ {{0xc61100c8,0x7643d866,0x04461052,0x27ffd867}}, // স্থা_, _vany, пенн, paun_,
+ {{0x44394844,0x291800b4,0x7643cd22,0x2245d868}}, // ges_, vura_, _wany, _halk_,
+ {{0x764389ca,0xf0928039,0xeb971bba,0xb7da8039}}, // _tany, _אנא_, зия_, _הקני,
+ {{0x11da00f7,0x60c982d0,0xc6110264,0xa96a02df}}, // صورة_, lmem, স্তা_, бига_,
+ {{0x6601b091,0xfaa322cb,0x6d5c8074,0x6b82847f}}, // malk, харо, ärat, zzog,
+ {{0x44390806,0x83fd8065,0x44200699,0x29180a5a}}, // ces_, _előz, _uci_, rura_,
+ {{0x27fd8355,0x27f2031d,0x6d4e5869,0x60168457}}, // [4d60] _mewn_, rbyn_, _srba, lümü,
+ {{0x29180886,0x6f040352,0x6601afb8,0x44e1877f}}, // pura_, rsic, nalk, _kó_,
+ {{0x44e1d86a,0x6f044db0,0x89db04de,0x6016807e}}, // _jó_, ssic, _לחיי, nümü,
+ {{0x44e19c94,0x60c99af5,0x6d4e10d3,0xfe1e02f1}}, // _mó_, jmem, _vrba, _पावस_,
+ {{0x387f820f,0x44e18324,0x6601b30d,0x61fe586b}}, // dhur_, _ló_, kalk, _kepl,
+ {{0x4439586c,0x442081cd,0x63b60009,0x60c2c21f}}, // zes_, żi_, _myyn, _shom,
+ {{0x44e1d86d,0x4439586e,0x6601d52f,0x2245879f}}, // _nó_, yes_, dalk, _dalk_,
+ {{0x04439421,0x8c439b4b,0x787104d6,0x61fe586f}}, // _песн, _песе, _påvi, _lepl,
+ {{0x44390918,0x64960267,0x6601d870,0x26dd810c}}, // ves_, kšib, falk, _cuwo_,
+ {{0x44e1c46f,0x61468c0e,0x66018be6,0xd0918201}}, // _bó_, _неза, galk, _müəl,
+ {{0x44e1d871,0x70f69c12,0xb0db01ce,0x27fd831d}}, // _có_, _رسائ, _मूंग, _fewn_,
+ {{0x44e1d872,0x44392b90,0xf3f98087,0x6fbe5873}}, // _dó_, ues_, _poţi_, ्धां,
+ {{0x6601d874,0x61fe24c2,0x76805618,0xfbad0264}}, // balk, _bepl, _köyd, _গণহত,
+ {{0x44e184be,0x61fe5875,0xaa640c9b,0x442b0609}}, // _fó_, _cepl, нтск, sfc_,
+ {{0x61fe2823,0x44391098,0x79a4075a,0xa5d782e3}}, // _depl, pes_, ерте, _عبور_,
+ {{0x76800009,0x7d1a80b4,0xf3f98087,0x44393518}}, // _löyd, muts, _toţi_, qes_,
+ {{0xbed801cf,0x7d1ad876,0x7af0016b,0x9f34021e}}, // [4d70] здух_, luts, čitý, неті,
+ {{0x61fe0574,0xdcfc02bb,0xfbd00013,0x7aed1e8f}}, // _gepl, _ayrı, _حتى_, _itat,
+ {{0x947587d2,0x7d1a8314,0x06e380ab,0x38978162}}, // وگرا, nuts, _মিছি, sări_,
+ {{0x69da879f,0x254c801b,0x61e880ee,0x6a6f805f}}, // _agte, _měl_, _afdl, _løft,
+ {{0xfebb003d,0x7d1a8cb7,0xdb1b8129,0x2245d877}}, // _هاست_, huts, _nguô, _palk_,
+ {{0x7d1ad878,0x7aed2aa0,0x66018df6,0x6016a2f8}}, // kuts, _mtat, xalk, yümü,
+ {{0x6601d879,0xa3b4000d,0x7d1ac53e,0xc61906b7}}, // valk, _जुन_, juts, दलीय_,
+ {{0x443987e2,0x44e1d872,0x69da879f,0x31580039}}, // _ús_, _ró_, _egte, ליון_,
+ {{0x44e1d87a,0x60c9d87b,0x7bc083a8,0xa7fb0144}}, // _só_, rmem, _gzmu, _coñi,
+ {{0x44e1d87c,0x387f820f,0x649602a5,0x2caf80f3}}, // _pó_, shur_, kšic, logd_,
+ {{0xdca60a94,0x61fe587d,0xd9998013,0x7aed587e}}, // дани, _repl, _بنات_, _atat,
+ {{0x69c1d87f,0x6601beb6,0x61fe5880,0x7afb819d}}, // _izle, salk, _sepl, _kwut,
+ {{0xf745bacb,0x660180f4,0x44e18324,0x61fe02d6}}, // _цело, palk, _wó_, _pepl,
+ {{0x44e18324,0x7d1a82a0,0x201f8722,0x68fb00ce}}, // _tó_, buts, lgui_, ćudn,
+ {{0x7aed5881,0x2d948098,0x6729c9d8,0x629c048d}}, // _etat, _пръс, ltej, rnro,
+ {{0x201fd882,0x63a40102,0x7bdb8c2e,0xb90606a7}}, // ngui_, _txin, _nguu, _पढ_,
+ {{0xe4c71125,0x61fe027f,0x6729d883,0x258280e7}}, // [4d80] _تصوی, _tepl, ntej, _télé_,
+ {{0x80be0996,0x78a90353,0xaae18105,0xee3710ef}}, // _वीरे, čeva, _पढ़क, мну_,
+ {{0x787c83a2,0xa01b12d2,0xa06a04ae,0xf41f016d}}, // _léva, _blöd, _дана_, miär_,
+ {{0xf4878077,0x7875826f,0x6e3ad884,0xe81986af}}, // وانی, _dávk, zetb, _धागा_,
+ {{0x69c1bab1,0x76488114,0x63be8035,0xd0418300}}, // _azle, yddy, ępno, _hulɗ,
+ {{0x9c140133,0x3b06a08e,0x9f400b24,0x7bdb85ee}}, // _kọst, rsoq_, ncií_, _eguu,
+ {{0x28c30063,0xa01b0106,0x7afba970,0x26d85885}}, // _वीडि, _flöd, _ewut, _iiro_,
+ {{0xa3ae03b7,0x807b00be,0xa7fb0118,0x7d1a8314}}, // कथा_, ַניצ, _poñi, vuts,
+ {{0x3f84825b,0x26d81588,0xe29aa853,0x9f4000e1}}, // uzmu_, _kiro_, _даа_, kcií_,
+ {{0x44325162,0x7d1aaf90,0x44248028,0x26d80079}}, // _aby_, tuts, _hcm_, _jiro_,
+ {{0x7aed5886,0xb80e2303,0xc58a8009,0xeae680ab}}, // _stat, िणाम_, ющие_, _নিয়ম,
+ {{0x7d1a9c76,0x7ae0d887,0x291c805d,0xa7fb0118}}, // ruts, _kumt, muva_, _toñi,
+ {{0x4424d888,0xc0a9936d,0x291cd889,0x7ae081a9}}, // _mcm_, _عامل_, luva_, _jumt,
+ {{0x44320c56,0x78ad9f3a,0x7c24588a,0x7d1ad88b}}, // _eby_, čave, _ecir, puts,
+ {{0x7ae080f1,0x4432008e,0x4c950cec,0x8637807c}}, // _lumt, _fby_, _пикс, _חרוב_,
+ {{0x030e80cf,0x78780061,0xd5f40129,0x64a40115}}, // _सिंह_, _hívj, _đán, rđic,
+ {{0x26d852f8,0x7c3d0359,0x7ae0b353,0x7aed004f}}, // [4d90] _biro_, mesr, _numt, _utat,
+ {{0x2004d88c,0xd5bb0374,0x291cb7c1,0x752f8035}}, // hami_, осе_, kuva_, ńcze,
+ {{0x60c901b9,0x25b8010c,0x26d801b4,0xe3b00fd3}}, // ċemb, _cyrl_, _diro_, _مری_,
+ {{0x2004d88d,0x4424d88e,0x26d800eb,0x6f160035}}, // jami_, _ccm_, _eiro_, bryc,
+ {{0x20049c28,0x60cd588f,0xf8b20e82,0xfdbb864a}}, // dami_, mmam, _תשכ_, _उर्फ,
+ {{0x26d80ab3,0x60cd1d5a,0x65950dff,0x4424d890}}, // _giro_, lmam, _загу, _ecm_,
+ {{0x6605178f,0x4424d891,0x63ba0035,0x08d5847f}}, // mahk, _fcm_, ętni, нция,
+ {{0x409493cd,0x660513b8,0x27e95892,0x29078b67}}, // крыт, lahk, ncan_, rsna_,
+ {{0x290780f2,0x645bd893,0x6d5a1a90,0xb9068128}}, // ssna_, _inui, _àtar, _यू_,
+ {{0x443da358,0xef828e02,0x44320e6d,0x6729d743}}, // lew_, ульп, _sby_, rtej,
+ {{0x20048933,0x6729d894,0x50678ada,0x291c8b80}}, // bami_, stej, _отда, cuva_,
+ {{0x200486b9,0x443da9b9,0x7c3d016d,0x6729d895}}, // cami_, new_, gesr, ptej,
+ {{0x68e1cae8,0x6605010b,0x394589d1,0xa3e480d4}}, // _huld, kahk, _hsls_, पुट_,
+ {{0xddc3802e,0x68e1d896,0xe5a5bbae,0x660509ca}}, // _conţ, _kuld, _пили, jahk,
+ {{0x205616d4,0x26d80032,0x6605372d,0x19b900e8}}, // нтар, _riro_, dahk, дуть_,
+ {{0x6f1646ee,0x60cd02b8,0x26d85897,0xa01b016d}}, // tryc, gmam, _siro_, _smör,
+ {{0x6f09b724,0x91e60ca0,0x26d85898,0x443dd899}}, // [4da0] nsec, _попе, _piro_, dew_,
+ {{0x2004d89a,0x68e18176,0x98c0080a,0x7a338457}}, // zami_, _ould, ırır_, nıtı,
+ {{0xe820816f,0x2004d89b,0x2480074c,0x2c7ec5e6}}, // _याला_, yami_, _klim_, _aïda_,
+ {{0x27e9589c,0x644983a8,0x44220d3a,0xc4868a14}}, // ccan_, _caei, ngk_, элек,
+ {{0x26d843f0,0x66051eb1,0x26c7826c,0x2004a2fb}}, // _tiro_, bahk, _ohno_, vami_,
+ {{0x2004cdc4,0x291c9608,0x6605010b,0x68e1d89d}}, // wami_, tuva_, cahk, _buld,
+ {{0xa3d01c07,0x7875826f,0x2004d89e,0x46a30048}}, // वेत_, _závi, tami_, _сарв,
+ {{0x64498307,0x2002589f,0x291c8338,0x2b448338}}, // _gaei, _jeki_, ruva_, _tsmc_,
+ {{0x2004d8a0,0x9f4058a1,0x03a39fd0,0x7d1e03ba}}, // rami_, ncià_, _бито, lups,
+ {{0x68e1813c,0x6462d8a2,0x27e91b54,0x248058a3}}, // _fuld, štič, zcan_, _alim_,
+ {{0x27e902bf,0x68e1b64c,0x6d418114,0x04b80019}}, // ycan_, _guld, gwla, کھوں_,
+ {{0x7c22a0eb,0x66050359,0x6295009a,0x69de0362}}, // ngor, zahk, wizo, _agpe,
+ {{0x9f499c86,0x2918016a,0x66050359,0x7c3d473e}}, // _það_, erra_, yahk, resr,
+ {{0x56949285,0x7bc42337,0x443dd8a4,0x64960084}}, // _шалт, _dziu, zew_, ršia,
+ {{0xc17712c8,0x3e76007b,0x60cd58a5,0x64960b80}}, // ردست, _gæti_, tmam, mšin,
+ {{0x62952647,0x660500dd,0x200226ca,0x60cd58a6}}, // sizo, wahk, _ceki_, umam,
+ {{0x60cd3468,0x7c228da8,0xa3d00b9f,0x66050590}}, // [4db0] rmam, dgor, वेद_, tahk,
+ {{0x27e90333,0x06b7026a,0x644980eb,0x2480017f}}, // scan_, رناک_, _saei, _zlim_,
+ {{0x98738012,0x66050867,0xa3d384e5,0xddc380e1}}, // nţă_, rahk, हेन_, _konš,
+ {{0x66050057,0x7c22d8a7,0x6ac98da0,0x25f101ab}}, // sahk, ggor, िग्र, ेरही_,
+ {{0x6283831d,0x290c91b9,0x68e1d8a8,0xbb458323}}, // thno, _avda_, _suld, телк,
+ {{0x660500b9,0x672d58a9,0x38694d5a,0x7c228cdb}}, // qahk, mtaj, ljar_, agor,
+ {{0x672d4269,0x443d84b7,0x6f098bfd,0xecbba385}}, // ltaj, pew_, wsec, _فطرت_,
+ {{0xfd658104,0x38691e03,0x6f09d818,0x787c80e7}}, // _thuậ, njar_, tsec, _dévo,
+ {{0x672d58aa,0x3e76007b,0xfc3f016b,0x66158061}}, // ntaj, _sæti_, deí_, ózko,
+ {{0x68e1b8dc,0x78bbd8ab,0x63bbd8ac,0x672d58ad}}, // _tuld, sluv, _iyun, itaj,
+ {{0x62819f79,0xe5a59a34,0x63bb90e4,0x3207c640}}, // _illo, тини, _hyun, many_,
+ {{0xeb9a134e,0x63bb8708,0x6d55026c,0x386958ae}}, // дим_, _kyun, _mrza, jjar_,
+ {{0xc4d28158,0x28db058c,0xdb0b077f,0x6d4e919b}}, // יגט_, _मंजि, _ènìy, åbar,
+ {{0x3ce20076,0x6d5504c3,0x63bb8681,0x09d580ab}}, // _ओढले_, _orza, _myun, _স্যা,
+ {{0x2b918028,0x99878110,0x200202d0,0x11390a14}}, // ạch_, menų_, _peki_, няты_,
+ {{0x63bb8459,0x66038364,0xbb8600f7,0x43b3082e}}, // _oyun, _henk, _للبي, ụrụ_,
+ {{0xb8e9023c,0x6281878e,0x6d470006,0x63bbd8af}}, // [4dc0] _ली_, _ollo, _asja, _nyun,
+ {{0x32078690,0x6d550bcf,0x6603d8b0,0xe8e000ff}}, // jany_, _brza, _jenk, _chợt_,
+ {{0x63a9862f,0x63bb8511,0x6603990d,0x3207809a}}, // _axen, _ayun, _menk, dany_,
+ {{0x6d550025,0x74138064,0x7ae432d2,0x6603d8b1}}, // _drza, لوما, _kuit, _lenk,
+ {{0x4fa300e8,0x628180f1,0x38ba8168,0x7d1e5885}}, // _вияв, _bllo, qëri_, sups,
+ {{0x7ae458b2,0xc7b30051,0x6603d8b3,0x4ed51bcc}}, // _muit, _כבר_, _nenk, _бюст,
+ {{0x09d58a49,0x81b400c8,0x30a694b7,0x63a985e4}}, // _স্বা, জের_, крив, _exen,
+ {{0x628186a5,0xd01080f7,0xe8df819d,0x237183ed}}, // _ello, حلة_, _gbọm_, ënjë_,
+ {{0xf093078d,0x1ae6835f,0xa2e6835f,0x66039fb6}}, // ינה_, _розм, _розд, _benk,
+ {{0x78ad8025,0x6603d8b4,0xb0618009,0x78780ba3}}, // čava, _cenk, _ääne, _dívi,
+ {{0x66039ab0,0xfe6f80f7,0xf76f8077,0x7e7ad8b5}}, // _denk, تدي_, مای_, rktp,
+ {{0x7ae40ee8,0x62989ee0,0x2486d8b6,0x7d0303a7}}, // _buit, livo, nhom_, ânsi,
+ {{0x6603ad9b,0x649658b7,0x7ae458b8,0x6d5e80c3}}, // _fenk, pšin, _cuit, _špad,
+ {{0x7ae405f8,0x261c8074,0x629898f1,0x66038646}}, // _duit, _माटी_, nivo, _genk,
+ {{0x386958b9,0xfce38eef,0x6608d8ba,0x7ae40362}}, // tjar_, _којо, madk, _euit,
+ {{0x70bf05b3,0x672d0364,0x6603d8bb,0x6608d8bc}}, // ्षेल, ttaj, _zenk, ladk,
+ {{0x442958bd,0x3d050076,0x38692f9c,0xd9048065}}, // [4dd0] _ica_, हीने_, rjar_, _ڈی_,
+ {{0xd9049125,0xd04b8201,0x66088079,0x644d0cda}}, // _وی_, _digə, nadk, _haai,
+ {{0x3207816b,0xf1ab003d,0xdc4301d0,0x442902df}}, // vany_, _زاده_, _léčb, _kca_,
+ {{0x32078063,0x261c835a,0x5f13800f,0x660895e8}}, // wany_, _माझी_, _दिल्_, hadk,
+ {{0x644d18c5,0x3207c640,0x09cb853f,0x442930b2}}, // _maai, tany_, ाध्य, _mca_,
+ {{0x26dcd8be,0x6e95013a,0x62988358,0x2009067f}}, // _nivo_, _сигу, givo, laai_,
+ {{0x56940e9f,0x32078e04,0x60dd1380,0x6603d8bf}}, // _тарт, rany_, _kism, _renk,
+ {{0x442958c0,0x60dd02a6,0x6603906f,0x248685b9}}, // _nca_, _jism, _senk, chom_,
+ {{0x60dd58c1,0x660387ac,0x99bf00ab,0x32078690}}, // _mism, _penk, _আলোক, pany_,
+ {{0x6281802a,0x44290014,0x7ae414cc,0x66038168}}, // _ullo, _aca_, _ruit, _qenk,
+ {{0x442958c2,0x7c298042,0x6603d8c3,0x7ae458c4}}, // _bca_, _kcer, _venk, _suit,
+ {{0x09d580c8,0x4429070d,0x6f0d042b,0x6603c206}}, // _স্থা, _cca_, ksac, _wenk,
+ {{0xe7398012,0x660386cf,0xaca38135,0x6608ca1e}}, // _чел_, _tenk, _abịa, badk,
+ {{0x81bf00ab,0x7ae42bd5,0x1e848221,0xbf9a83c8}}, // েশন_, _vuit, _клім, _אינש,
+ {{0x63be809a,0xb606026f,0x32058019,0x64860118}}, // ępni, ntáž, _mely_, _póid,
+ {{0x27edd8c5,0x7ae444a0,0x26dc8042,0x60dd3dd5}}, // ncen_, _tuit, _zivo_, _cism,
+ {{0x6d5ec033,0x9b460199,0xa3d058c6,0x60dd1abd}}, // [4de0] _àpar, _صندو, वेश_, _dism,
+ {{0x7c29ab72,0x628719c9,0x60dd0084,0xe1fa0a8e}}, // _acer, chjo, _eism, _яго_,
+ {{0x2486d8c7,0x672082ce,0x6f0d0c41,0x29560098}}, // thom_, zumj, asac, _съпр,
+ {{0x7c2658c8,0x7641d8c9,0xac2580e8,0x236681c0}}, // ngkr, hely, ифік, txoj_,
+ {{0x62989313,0xe29aa466,0x68e558ca,0x2486d8cb}}, // tivo, над_, _zuhd, rhom_,
+ {{0x851c0076,0x55e3179e,0x7c2981bf,0x6f1bd8cc}}, // _पटपट_, _горб, _ecer, druc,
+ {{0x7641d8cd,0x649601a1,0x78ad82d4,0x6608d8ce}}, // dely, tšil, čavn, vadk,
+ {{0x764e2fc0,0x6298d8cf,0x3f149ad8,0x7521bb7a}}, // _haby, sivo, адис, dulz,
+ {{0x764e58d0,0x44268bb1,0x27e20b50,0xb606cc6b}}, // _kaby, mgo_, _mgkn_, лянк,
+ {{0x4426d8d1,0x7641d8d2,0x225e89ca,0x644281a8}}, // lgo_, gely, _untk_, leoi,
+ {{0x64964e1b,0x05ce816f,0x66088079,0x644d58d3}}, // mšij, हेंब, radk, _paai,
+ {{0x4426d612,0x69c8d8d4,0x6f1bbb52,0x27ed84b9}}, // ngo_, _izde, bruc, ccen_,
+ {{0x249958d5,0x6608d8d6,0x4426d8d7,0x6f1bb724}}, // rism_, padk, igo_, cruc,
+ {{0x764e023b,0x60dd191f,0x24992666,0x68e50198}}, // _naby, _sism, sism_, _puhd,
+ {{0xd918176e,0x648600f7,0x442928c9,0x02e1800d}}, // льт_, _dóib, _tca_, _पढ्न,
+ {{0x60dd011c,0x6f029415,0xa3e906b7,0x6d4a9f79}}, // _qism, _kwoc, _बलि_, _isfa,
+ {{0x6d588a20,0x764e2fbe,0x64428013,0x60dd00eb}}, // [4df0] _hrva, _baby, deoi, _vism,
+ {{0x4426d8d8,0x81b400ab,0x6f0d58d9,0x7c29ad08}}, // ego_, জেই_, rsac, _scer,
+ {{0x60dd4cc9,0x3e76008b,0x628558da,0x290c1be6}}, // _tism, _rætt_, _ilho, ádat_,
+ {{0x4426c50b,0x6442c1d1,0x6f0d0362,0x6f028035}}, // ggo_, geoi, psac, _owoc,
+ {{0xd5af0117,0xae1e03eb,0x442981a9,0x768002d0}}, // _کہا_, _पाचन_, ļa_, _köyl,
+ {{0x4426d8db,0x4429d8dc,0x7875803e,0x657a81e0}}, // ago_, ża_, _návr, kyth,
+ {{0x7641d8dd,0xfa7801c6,0x201982df,0x272f22f8}}, // vely, רעות_, ósio_, lün_,
+ {{0x6f1b9883,0x69d5803e,0x69c8009a,0x76418114}}, // truc, _územ, żdeg, wely,
+ {{0x6d58870a,0x272f0182,0x8fa59052,0x628558de}}, // _arva, nün_, рапе, _olho,
+ {{0x6f1bc99f,0x9c1301bc,0xbb8480f7,0x78a458df}}, // rruc, _dọme, _الكي, zniv,
+ {{0x6d588267,0x2ca90609,0x81eb0264,0x38ba83ed}}, // _crva, _bjad_, মরা_, tërt_,
+ {{0x752f809a,0x62855328,0x673d325c,0x6f1b81ec}}, // ńczo, _alho, _opsj, pruc,
+ {{0x6d589aee,0xa3d001b6,0x386d920e,0x768007d9}}, // _erva, वें_, mjer_, _böyl,
+ {{0x386d8e23,0x4426a215,0x471a80be,0x660701d0}}, // ljer_, zgo_, _יונג, _nejk,
+ {{0xaca4019d,0x628501ac,0x3d0503b7,0xd94618a2}}, // _agụi, _dlho, _हौले_, шеви,
+ {{0xe0df0098,0x386db3cc,0x661502f7,0x7d568039}}, // _ciò_, njer_, _adzk, _כיצד_,
+ {{0x753c0d38,0x272f05c5,0x387f81fa,0xe3b80085}}, // [4e00] _sprz, gün_, ikur_, şıb_,
+ {{0x98481010,0x290c01a8,0x764e01b4,0xed5a0fbf}}, // rşı_, ádas_, _qaby, коз_,
+ {{0x387f807b,0x644280f7,0x386dd8e0,0x920281a9}}, // kkur_, teoi, kjer_, šāka,
+ {{0xf5368158,0x629c58e1,0x386d81b9,0xe058004e}}, // יטער_, liro, jjer_, _حیرت_,
+ {{0x644280f7,0x386d8582,0x9c13019d,0x764e58e2}}, // reoi, djer_, _bọje, _taby,
+ {{0x4426d8e3,0x291c8d11,0x644281a8,0x8b5700be}}, // sgo_, trva_, seoi, יינס_,
+ {{0xe5a328df,0xddc708ae,0x7bc98326,0x64a401a1}}, // _дири, _tojš, _ezeu, rđij,
+ {{0x629c44ad,0x69c8886f,0x9f4f81ca,0x64a6816c}}, // hiro, _vzde, _negó_, _таба,
+ {{0xe666d22f,0x629c12e5,0x657a8114,0x087703de}}, // атно, kiro, wyth, שעפט_,
+ {{0x6d58a368,0x629c0326,0x291c811f,0x8f370538}}, // _prva, jiro, prva_, סטיג_,
+ {{0xd7ef8abe,0x629c1193,0x69c8a52a,0x6996854c}}, // _му_, diro, _uzde, _трах,
+ {{0x768002bb,0x629c1d3a,0xb17b8158,0xc19b8039}}, // _söyl, eiro, _שטאר, _בשבי,
+ {{0x6d5a011f,0x68e8bb15,0x6285022c,0x395a09c4}}, // _štal, _hudd, _plho, _arps_,
+ {{0x6d588efc,0x68e8d8e4,0x629c57e2,0x8af00085}}, // _trva, _kudd, giro, _irəl,
+ {{0x6d58d8e5,0xf8072482,0xfc3f0333,0xdd938084}}, // _urva, ичан, anía_, _машы,
+ {{0x272f0a0b,0x68e8d8e6,0x63ad0102,0x673d09c4}}, // tün_, _mudd, _txan, _ppsj,
+ {{0x7c2d58e7,0x68e8a9e4,0x660701f4,0x35e70bc7}}, // [4e10] _icar, _ludd, _pejk, ацев,
+ {{0xbebd9c1f,0x629c1727,0xc61f00ab,0x387f8196}}, // ltūr, ciro, ন্না_, zkur_,
+ {{0x2ca6831d,0x9c1301bc,0xab6282d0,0x6d5a0061}}, // fnod_, _nọke, _şüph, _átal,
+ {{0xdd868986,0xbebd8df1,0xa5d980be,0x940b0085}}, // _ہو_, ntūr, אַני, ticə_,
+ {{0x7bc0831d,0xb4c78105,0x64860187,0xbd05826b}}, // _cymu, _उठी_, _jóia, _atẹ́,
+ {{0xa2298b79,0x1df80e11,0x7bc08114,0x8e8501a8}}, // ужка_, _веры_, _dymu, _السه,
+ {{0xbebd8df1,0x68e8a78e,0x3cf001d6,0x3a2c9dc5}}, // ktūr, _cudd, ľové_, _dcdp_,
+ {{0x629c003a,0x68e8a1c7,0x3cf0016b,0xbca5866e}}, // ziro, _dudd, žové_, _امري,
+ {{0x387fd8e8,0x7bc08114,0x7c3baa3b,0x6a7d86c0}}, // rkur_, _gymu, _ibur, _lèfi,
+ {{0xe1f0bf7d,0xba748307,0x7c2d01f1,0x629c58e9}}, // _قسم_, _والت, _acar, xiro,
+ {{0x629c2ae9,0x68e882a3,0xa3d3816f,0x443b43e8}}, // viro, _gudd, हेर_, _bbq_,
+ {{0x7ae1d8ea,0xb4e7800d,0x64a602fb,0x629c58eb}}, // _hilt, _बढी_, сама, wiro,
+ {{0x7ae1d8ec,0x629c58ed,0x7c3bd8ee,0x27ffb3d2}}, // _kilt, tiro, _mbur, mbun_,
+ {{0x6d5a28e1,0x412a11d2,0x787c83e6,0x26d981d0}}, // _štam, лого_, _révi, ůsob_,
+ {{0x7c3bd8ef,0x7ae98009,0xada29c82,0x68e88df6}}, // _obur, _luet, _нашл, _xudd,
+ {{0x442d8069,0x629c0110,0x27ffd8f0,0x6a7d823e}}, // _nce_, siro, nbun_, _dèfi,
+ {{0xee3a0abe,0xa01b00f2,0x7ae98144,0x644658f1}}, // [4e20] лна_, _glöm, _nuet, meki,
+ {{0x644658f2,0x5986028b,0x5d6a08b0,0xcf2500f7}}, // leki, _глоб, лизм_, ترفي,
+ {{0x7bc0831d,0xa3dad8f3,0x09d60264,0xd9f88327}}, // _symu, डेन_, _ত্রা, ुरसत_,
+ {{0xa50726ad,0x6446482e,0x1d0720bf,0x186a4415}}, // сеца_, neki, сеци_, _сами_,
+ {{0x7ae1d8f4,0x7d1e02a5,0x205704de,0x7ae98144}}, // _bilt, srps, _היכל_, _cuet,
+ {{0x60c458f5,0x46160117,0x7c3b8133,0xdd20001b}}, // llim, _دوسر, _ebur, může,
+ {{0x5d860013,0x68e884b7,0x7ae183ed,0x7ae98580}}, // _الأل, _qudd, _dilt, _euet,
+ {{0x6d5c41bb,0x60c458f6,0x6d4e58f7,0x69c18114}}, // _irra, nlim, _isba, _dyle,
+ {{0xd6da8703,0x69c18214,0x661cd8f8,0xa01b0106}}, // лто_, _eyle, órko, _blöj,
+ {{0x7c2d1429,0x273090ab,0x7ae1d8f9,0x60c40a48}}, // _scar, ràn_, _gilt, hlim,
+ {{0xe72f8bca,0xb6bb0158,0x245f0009,0x6d5a1b39}}, // وصی_, רציי, _nämä_, _štaj,
+ {{0x320e8101,0x69c10214,0x60c4026c,0x996481d0}}, // lafy_, ülem, jlim, _kůže_,
+ {{0x7c2d0370,0x27e6b5ac,0x7ae98473,0xdc9b81c6}}, // _vcar, _igon_, _xuet, ריכל,
+ {{0x9964800d,0xa3da8b9f,0x442d0035,0x6d5c58fa}}, // _může_, डेय_, że_, _orra,
+ {{0x7e7509a4,0x6446013d,0x9f490174,0x29df822b}}, // _rozp, beki, rcaí_, _għaċ_,
+ {{0xc4c4850c,0x60c415d3,0x64a40dc7,0xb4c78105}}, // _سے_, glim, _наја, _उठे_,
+ {{0x29110065,0x6d5c2816,0xe7301921,0x6e99196e}}, // [4e30] ssza_, _arra, فصل_, авар_,
+ {{0x443910f4,0xf7700829,0x7bcd01a9,0x29110035}}, // lfs_, راق_, _izau, psza_,
+ {{0x44391393,0x7e750144,0x7ae9d8fb,0x24890123}}, // ofs_, _vozp, _suet, _klam_,
+ {{0x7ae190c0,0x27e6801c,0x7afd1699,0xccf380be}}, // _silt, _ngon_, opst, לכע_,
+ {{0x6d5c58fc,0x7ae18074,0xa3d38740,0x6d4e4bd8}}, // _erra, _pilt, हें_, _esba,
+ {{0x644658fd,0x27e6828c,0xd9fb170c,0x09d28264}}, // zeki, _agon_, ्रित_, াশনা,
+ {{0x68e2d8fe,0x442dd8ff,0xdcf581a9,0xaefb0362}}, // _biod, _tce_, ēcīg, _brùg,
+ {{0xef19895a,0x7c3b8578,0x2eb3816f,0x69c1826f}}, // ами_, _ubur, ुत्त, _vyle,
+ {{0x7ae18e51,0x68e2d900,0x7c2480f7,0x200b0087}}, // _tilt, _diod, óire, _meci_,
+ {{0x24895166,0xee3697d4,0x27e6811e,0x273b0087}}, // _alam_, онь_, _egon_, mână_,
+ {{0x2b400118,0x68e28229,0x24895901,0x660a80c3}}, // _spic_, _fiod, _blam_, _defk,
+ {{0x2d8b8063,0x7c2b923c,0xf9928039,0x6d5a00ce}}, // ące_, nggr, _קרא_, _štak,
+ {{0xe60e8fbb,0x64463cac,0x2d839491,0x24895902}}, // _од_, reki, šje_, _dlam_,
+ {{0xf8bf541c,0x64463d61,0x78bb801b,0xc61f00ab}}, // _aké_, seki, louv, ন্তা_,
+ {{0x38c804c0,0x7bcd0102,0x24890580,0x7f5d0706}}, // ماری_, _ezau, _flam_, _arsq,
+ {{0x200b5903,0x78bb902d,0x53a600e8,0x2247c853}}, // _ceci_, nouv, _майб, henk_,
+ {{0x60c40ad4,0x200b5904,0xe1ff801c,0x26c5a1ab}}, // [4e40] rlim, _deci_, _đón_, ollo_,
+ {{0x245f0364,0x26c583a8,0xd62601a8,0x66cf804a}}, // _tämä_, nllo_, تعري, bøke,
+ {{0x37e60adb,0x60c45905,0x26c5d906,0x78bb86c0}}, // _монг, plim, illo_, kouv,
+ {{0x629a82f7,0xb922819d,0x67df0196,0x6abc27d3}}, // _smto, akpọ_, _sąjū, lorf,
+ {{0x2cad9351,0x69c583e3,0xaefb0362,0x926a951b}}, // _djed_, ühen, _crùd, ирка_,
+ {{0x27345907,0x200b0087,0xdce981d0,0xaefb0706}}, // män_, _zeci_, íněn, _drùd,
+ {{0x6d5c27a2,0x27340198,0x660a826c,0x6d4e5908}}, // _urra, län_, _refk, _usba,
+ {{0xa7749878,0xb97b00be,0xdd8f045b,0x26c5991f}}, // злич, ינצי, _شوق_, ello_,
+ {{0x920a05e8,0xeeaa80e8,0x660a8bda,0x7bc40573}}, // वराज_, иток_, _pefk, _nyiu,
+ {{0x6283d909,0xd6d08875,0xe29781ae,0x69c12131}}, // ckno, عقد_, _дау_, ülek,
+ {{0x2734025d,0x2489590a,0xceb30158,0x994a8013}}, // hän_, _plam_, ויז_, _خلال_,
+ {{0x66e685da,0x78bb83d3,0xe3b80182,0xe78690ac}}, // _дода, couv, ğın_, _муло,
+ {{0x200b0025,0xe3b8017b,0x27340009,0x443910c9}}, // _reci_, şın_, jän_, rfs_,
+ {{0x27340364,0x9f590081,0xaaca801b,0x7afd3bd9}}, // dän_, _gesù_, ितिक, rpst,
+ {{0x7648d90b,0x31c415e0,0x7cf304b7,0xfc3f01d0}}, // medy, дств, _oħra, lním_,
+ {{0x7648d90c,0x24890cef,0x7afd20e4,0xda78816b}}, // ledy, _ulam_, ppst, _loď_,
+ {{0x200b01ac,0x66cf804a,0x41e70a4c,0xaaca81d0}}, // [4e50] _veci_, søke, _міна, िताक,
+ {{0x764882e7,0x2cbf85b1,0x78ad8904,0xadc3826b}}, // nedy, _skud_, čavi, _afẹh,
+ {{0x2ee300f3,0x92b400f7,0x7aed5101,0xea0000ff}}, // _vijf_, _تحيا, _huat, _hiếu_,
+ {{0xa3c41993,0xe3b382e3,0x7aed590d,0x6ac39a46}}, // _्रम_, ورش_, _kuat, षत्र,
+ {{0x395e90b6,0x76489ac4,0xdb078380,0xeab01e29}}, // _arts_, kedy, _ömür, _سعی_,
+ {{0x09df0a49,0xe1f18bca,0x7aed1295,0xfc3f001b}}, // _ব্যা, رست_, _muat, dním_,
+ {{0x7aed04bc,0x91e2838b,0x320c82f7,0x628389ff}}, // _luat, ноше, _dedy_, rkno,
+ {{0x7ae50006,0x7aed590e,0x6283a3a7,0x2cad8b80}}, // _liht, _ouat, skno, _ujed_,
+ {{0x443fd90f,0x3211003e,0x78a9bb39,0x600687ac}}, // _ibu_, kazy_, rnev, чным_,
+ {{0x7655003e,0x7afb876d,0x78a9a571,0x6eed81d6}}, // _jazy, _itut, snev, _hĺbk,
+ {{0x69c50082,0xc8ab8098,0x648601a8,0x26c5802a}}, // _nyhe, _къде_, _fóil, rllo_,
+ {{0x7aed459a,0x3f4d35ca,0xc86495a6,0xfc3f01d0}}, // _buat, _ožu_, _отчи, bním_,
+ {{0x7aed0c15,0x443f8f59,0x6abc5910,0x69c7806a}}, // _cuat, _mbu_, torf, _øjeb,
+ {{0x27345907,0xddc881a9,0x76550035,0x7afb8c93}}, // vän_, ņoša, _nazy, _mtut,
+ {{0x443fd911,0x186a1445,0xd13200f7,0xa06a0e17}}, // _obu_, бави_, عمر_, бава_,
+ {{0x386602ba,0x4fea174a,0x67360009,0x7aed4c36}}, // ñor_, сман_, ttyj, _fuat,
+ {{0xe61a2240,0x6abc03a7,0x44320314,0x1e860196}}, // [4e60] йда_, porf, _icy_, плам,
+ {{0x443fd912,0x8cc40035,0x7ae501f4,0x2734256d}}, // _abu_, रतको, _giht, rän_,
+ {{0x7afb9192,0x64562a8d,0x27340198,0x3f8603f2}}, // _atut, _kayi, sän_, šou_,
+ {{0x443f89da,0x66e614f6,0xdee61229,0x2b5fd913}}, // _cbu_, _нома, _номи, _bruc_,
+ {{0x64562a8d,0x648600f7,0x201234c2,0x6f630110}}, // _mayi, _nóim, mayi_, _звяз,
+ {{0x443fd914,0x64560201,0x6d43d387,0xfc3f001b}}, // _ebu_, _layi, _opna, vním_,
+ {{0x15430049,0x7afbd915,0x9d430dc0,0xd90d815b}}, // _перм, _etut, _перд, لیل_,
+ {{0x20123468,0x443fa36a,0xfc3f01d0,0x64561ea2}}, // nayi_, _gbu_, tním_, _nayi,
+ {{0x320c801b,0x7e788353,0x67245916,0x629e09b6}}, // _tedy_, _povp, nrij, _ompo,
+ {{0x2486d917,0x660e0029,0x46be001b,0x9f970039}}, // lkom_, _jebk, ्तिह, פדיה_,
+ {{0x67240613,0x7aed0867,0x645638ba,0x6b829fce}}, // hrij, _suat, _bayi, lyog,
+ {{0x68e6003e,0x3866802e,0x629e1e9e,0x7ae51e71}}, // _nikd, _unor_, _ampo, _siht,
+ {{0x7aed1e9e,0x787c82be,0x7648d813,0x6b829efb}}, // _quat, _févr, pedy, nyog,
+ {{0x67240613,0xdfa58013,0xdee381a1,0x34a98105}}, // drij, _تحمي, _зочи, _कद्द,
+ {{0xe63b8039,0xfc3f00e1,0x291e230a,0x321109a4}}, // _התוכ, nník_, štan_, razy_,
+ {{0x629e5918,0x7aed0014,0x24868024,0x3ebe8019}}, // _empo, _tuat, jkom_, lott_,
+ {{0x67245919,0xa2b514b7,0x98a7812b,0xe8d78039}}, // [4e70] grij, _обич, šiću_, לולר_,
+ {{0xcb12004c,0x6456591a,0x20c181a8,0x443fd91b}}, // _שלי_, _zayi, nóir_, _sbu_,
+ {{0x7afbd91c,0x6456591d,0x2d870298,0xaefb0706}}, // _stut, _yayi, áne_, _brùc,
+ {{0x672436c8,0xfc3f026f,0x20c180f7,0x317e01ec}}, // brij, dník_, hóir_, ätze_,
+ {{0x2d873e2a,0x61ec0019,0x2fc681c5,0x3ebe909a}}, // šne_, _állá, _nyog_, kott_,
+ {{0x60c2d91e,0x2b5f817f,0xa9698e8e,0x78ad4168}}, // _akom, _vruc_, жика_, lnav,
+ {{0xa2a0035a,0x3ebed91f,0x78ad55f4,0x6d5a0da8}}, // _ऑगस्, dott_, onav, _štav,
+ {{0x443f8578,0x78ad5920,0x24868ee0,0x2b5fd921}}, // _ubu_, nnav, ckom_, _truc_,
+ {{0x64565922,0x66d01cab,0x66cb061c,0x64960b80}}, // _rayi, läka, yükl, kšir,
+ {{0x644b8c39,0x60c2d56d,0x6459d923,0x20c180f7}}, // legi, _ekom, ldwi, góir_,
+ {{0xe3b803bf,0x672402ce,0x248da39e,0xa90787c3}}, // ğım_, zrij, _klem_, _ربان,
+ {{0x6459b0e5,0x644bd924,0xe3b803bf,0x03a612c0}}, // ndwi, negi, şım_, димо,
+ {{0xee2e8dca,0xeb97233f,0x6729d925,0xb7f406ae}}, // _ин_, дия_, quej, _अलबम_,
+ {{0x60c9a538,0xa50999b6,0x672415f5,0x23600a0f}}, // llem, _дека_, vrij, _vrij_,
+ {{0x2d1c0540,0x64565926,0xb9b50098,0xd5d184e5}}, // _पटेल_, _tayi, естъ, _हरिज,
+ {{0x76800364,0x20551baa,0x291e0289,0x60c9d927}}, // _löyt, нтур, štao_, nlem,
+ {{0x8fa30cb1,0xe7371285,0x24868988,0x200fd928}}, // [4e80] вате, мет_, vkom_, _megi_,
+ {{0x672452e4,0x200fd929,0x248d8b62,0x2012185b}}, // rrij, _legi_, _alem_, sayi_,
+ {{0x60c9d92a,0x6aa3d92b,0x78ad14cf,0x6724592c}}, // klem, dinf, bnav, srij,
+ {{0x3ebe8065,0xfc3f003e,0x44f38028,0x644bd92d}}, // zott_, vník_, _mã_, gegi,
+ {{0x60c98bfa,0x2486d92e,0x44f3d92f,0x4bda8fd3}}, // dlem, rkom_, _lã_, آباد_,
+ {{0x60c2d930,0xfc3f016b,0x44328035,0xa50a046e}}, // _skom, tník_, ży_, _мега_,
+ {{0x644b8ad4,0xa9678992,0x200f811b,0x44f3d931}}, // begi, дија_, _begi_, _nã_,
+ {{0x60c9a1bf,0x6496075f,0x2120881d,0x9f54b760}}, // glem, zšir, čih_, евич,
+ {{0x3ebe8065,0xb05b016d,0x200fd55b,0xdca3937b}}, // tott_, _okän, _degi_, _рати,
+ {{0xda669168,0x60c9d932,0x44f38129,0x78a42446}}, // _تاري, alem, _bã_, hiiv,
+ {{0x3ebed933,0x9986845b,0x6fd50006,0x20c181a8}}, // rott_, _تلاو, _दरभं, róir_,
+ {{0x25fe87e6,0x9f401f18,0x101680d5,0x60c2b094}}, // _श्री_, nció_, _آباد, _ukom,
+ {{0x78ad0353,0x69c8d934,0x3ebe8019,0x2fc68122}}, // vnav, _hyde, pott_, _uyog_,
+ {{0x44f383a7,0x60c0d935,0x09d580ab,0x81d800ab}}, // _fã_, lomm, _স্টা, িশন_,
+ {{0x7ae88bc5,0x442f9235,0x76800198,0x19580a14}}, // _midt, ygg_, _löys, мары_,
+ {{0x58d42386,0x60c0d4be,0x315783de,0x6287226d}}, // вост, nomm, וילן_, skjo,
+ {{0x69c8d936,0x644b92fa,0x6d5e80c3,0xb3d302f1}}, // [4e90] _lyde, vegi, _špah, _सरिख,
+ {{0x23480077,0x644b837a,0x3ce90282,0x248dd937}}, // _کلیپ_, wegi, _hiav_, _slem_,
+ {{0x644bd938,0x60c08a51,0x44f38028,0x7e7c13c7}}, // tegi, komm, _xã_, _dorp,
+ {{0x6ab9800c,0x7e7c0cb5,0x9475803d,0x7762802a}}, // ेत्र, _eorp, رگزا, _orox,
+ {{0x200fc485,0x6aa3d939,0x7e7c0aa2,0x6459a0ea}}, // _regi_, tinf, _forp, rdwi,
+ {{0x200f9cdf,0x9f40009f,0x644bc717,0x7658d93a}}, // _segi_, ació_, segi, _navy,
+ {{0x60c989bd,0x200f83ac,0x349590ca,0x6aa3c255}}, // tlem, _pegi_, _завр, rinf,
+ {{0x45d40364,0x9f400813,0x249f88de,0x3ce901c0}}, // _росс, cció_, _umum_, _niav_,
+ {{0x60c984e6,0x44f3d93b,0xf41f0198,0x7658d93c}}, // rlem, _sã_, tkän_, _bavy,
+ {{0xee399878,0xfe718013,0xb6cc817b,0xe3b80214}}, // чно_, ادة_, şünü, şık_,
+ {{0x200f90b5,0x765882d6,0x9f4001e8,0x78a400b9}}, // _tegi_, _davy, nciò_, yiiv,
+ {{0x3ce90282,0x60c0a4cc,0x7762802a,0x26c1501d}}, // _ciav_, comm, _frox, hoho_,
+ {{0xf74590bf,0x3ce9022c,0x26c1026f,0xdd8e826a}}, // _чело, _diav_, koho_, لوی_,
+ {{0x2739d93d,0x291e012b,0x09e380ab,0x3f7a83de}}, // mèn_, štam_, _ন্যা, _מאנס,
+ {{0x78a401c2,0x614312e9,0x04460a41,0xd943013a}}, // tiiv, лера, _зейн, лери,
+ {{0xd9c0103e,0x645a01a9,0x2ca02795,0xafe62f75}}, // _एडीट, ētis, _umid_, еобл,
+ {{0x4420593e,0x3cff0024,0x673bd93f,0x7643808e}}, // [4ea0] _idi_, _čuva_, ntuj, _kbny,
+ {{0x78bb003b,0x78a414c7,0xb7d71c81,0x3ea586ae}}, // čuva, siiv, رولا_, nilt_,
+ {{0x1a9b093f,0xa01b2298,0x69c10065,0x9f590144}}, // _זייע, _plöt, ület, _besó_,
+ {{0x673b8e04,0x69c8d940,0x3ce90282,0x6d5e02f1}}, // ktuj, _ryde, _xiav_, rvpa,
+ {{0x7e7c0085,0x3ea59375,0x26c1046d,0xa3da897d}}, // _torp, kilt_, boho_, डें_,
+ {{0xada60652,0x7d1cb680,0x2739d941,0x2d85d942}}, // _загл, ársk, dèn_, kyle_,
+ {{0x6f042509,0x442011d3,0x9f405943,0x60c0d944}}, // lpic, _odi_, pció_, tomm,
+ {{0x7762887a,0x7ae8d945,0x44205946,0x76588084}}, // _prox, _widt, _ndi_, _savy,
+ {{0x76588110,0x656380f3,0x7643808e,0x60c08eb9}}, // _pavy, _arnh, _bbny, romm,
+ {{0x69c8d947,0x60c0d948,0x3ce90282,0x6996860a}}, // _tyde, somm, _siav_, _прех,
+ {{0x291e003b,0x3b0000b9,0x3ce901c5,0xc6930039}}, // štaj_, _atiq_, _piav_, ראת_,
+ {{0x2ebe03eb,0xe1ff80ff,0x64440706,0x66cb58ce}}, // ्तुत, _đói_, _cbii, tükk,
+ {{0x366a8364,0x3ea58bbd,0x7643808e,0x8b671459}}, // _надо_, bilt_, _fbny, راحم,
+ {{0x2d85d106,0x44205949,0xa6aa8872,0x628aa085}}, // byle_, _edi_, _عاشق_, nkfo,
+ {{0xe5f78051,0x3cea8740,0xdd8f89f7,0x3ce901c5}}, // _אזור_, _घंटे_, _сш_, _tiav_,
+ {{0x672d0dd7,0x7d1a8b81,0xceb4011c,0x69d58b99}}, // tuaj, msts, zrət_, येटी,
+ {{0x66d0016d,0x6d47007b,0x16be0035,0xaefb026b}}, // [4eb0] räkn, _spja, ्तूब, _erùn,
+ {{0x644f3a69,0xc13b801c,0x4420368d,0x26d80706}}, // leci, _cướp_, _zdi_, _chro_,
+ {{0x09df00c8,0x7d1c0289,0x44200114,0x7d1ad778}}, // _ব্রা, _cvrs, _ydi_, nsts,
+ {{0x673b9bfe,0x645d594a,0x2bde2e06,0xe4e4a1d2}}, // ytuj, ndsi, नेवा, гічн,
+ {{0x645d01e4,0x69d800e1,0x20093ada,0x60cd594b}}, // idsi, _úver, mbai_, mlam,
+ {{0xaa459c79,0x78a2d94c,0x7d1a897a,0x7dee0061}}, // текл, _amov, ksts, rősí,
+ {{0x644f12ae,0xd49b8c6e,0xfd660135,0x45d40db3}}, // keci, _ера_, _mkpọ, _борс,
+ {{0x42d180c8,0x673b8009,0x44f70457,0x60cd594d}}, // াদেশ, ttuj, _iç_, nlam,
+ {{0x644f49fd,0x7528809a,0x3f8b81ac,0x3ea588c7}}, // deci, erdz, ácu_, tilt_,
+ {{0x2d850006,0x60cd179f,0x628ac432,0x2739d94e}}, // _üles_, hlam, ckfo, rèn_,
+ {{0x673ba9fd,0x4420594f,0xc5f202f6,0x3f8b8301}}, // stuj, _pdi_, ידי_, šcu_,
+ {{0x64a60b73,0xdca6138f,0x291e00d2,0xfd660870}}, // тама, тами, štak_, _akpọ,
+ {{0x60cd5950,0x290144b9,0x3ea5d951,0x8c1a825f}}, // dlam, _atha_, pilt_, וורי,
+ {{0xc33283de,0x68eb8bb1,0xcb128039,0x66d00198}}, // בוך_, _higd, שלם_, väko,
+ {{0xa56480f7,0x78bb0115,0x6f1b8799,0x7641a25b}}, // _مدين, čuvn, lsuc, mfly,
+ {{0x68fc8065,0x44204dd5,0x645b8326,0x68eb8197}}, // _érde, _udi_, _naui, _jigd,
+ {{0xd9ad85b3,0x68ebd952,0x7d1c0024,0x6f1b81ec}}, // [4ec0] टपुट, _migd, _svrs, nsuc,
+ {{0x60cd5101,0xaefb001c,0x7641870b,0xe6be0d5d}}, // alam, _trùn, nfly, ्तेज,
+ {{0x6729d953,0x60cd5954,0x6f1b81ec,0xe0da04ae}}, // hrej, blam, hsuc, _ово_,
+ {{0x6aca8c78,0x68eb87c6,0x6f041d58,0x3218309c}}, // ित्र, _nigd, ppic, lary_,
+ {{0xe29a996e,0x24800035,0x2ca6869d,0xda7a8162}}, // мад_, _moim_, riod_, дяй_,
+ {{0x644f0063,0x60c4061a,0x32185955,0x66032306}}, // zeci, loim, nary_, _впра,
+ {{0xbea35956,0x7d1c011a,0x7641806a,0xdbdc826b}}, // _тарк, _uvrs, dfly, _dáák,
+ {{0x7d5700be,0x765c2668,0x518395ff,0xc98399a4}}, // _בילד_, _hary, руша, руши,
+ {{0x765c5957,0x67299024,0x68eb89c4,0x644f5958}}, // _kary, grej, _digd, veci,
+ {{0x3aeb803d,0x7528826c,0x60c401a8,0xd337825f}}, // _قبلی_, trdz, hoim, _ברמה_,
+ {{0xb05b016d,0x644f5959,0x60c40198,0x245504a3}}, // _skäm, teci, koim, هندس,
+ {{0x4fc68ab2,0x52bd0526,0x78a280d2,0x321335db}}, // _исла, ोत्स, _umov, _sexy_,
+ {{0x24840106,0x645d595a,0x7d1ad95b,0x98a3066f}}, // ömma_, rdsi, ssts, nują_,
+ {{0x765c595c,0xc7d781c6,0x7afdd95d,0x31353383}}, // _nary, _סוגי_, _éste, легр,
+ {{0x60cd30d8,0x611416cf,0x63be8035,0xb6d980be}}, // tlam, идру, ępny, _אַקט,
+ {{0x2aad352c,0xf0b4102a,0x98a30035,0x6d4a8db1}}, // džbe_, айсь, kują_, _ipfa,
+ {{0x6d5a005c,0x765c260b,0x20090110,0xd7f83c3d}}, // [4ed0] _štap, _bary, rbai_, тус_,
+ {{0x60cd022e,0xd2510077,0xba5519e3,0x79a710ff}}, // slam, _اند_, _свој, _арме,
+ {{0x765c19b2,0x60cd22f8,0x67299b2c,0xf1df0576}}, // _dary, plam, zrej, फेशन,
+ {{0x7bcd00a4,0x60c40122,0x1b198198,0x6f02d95e}}, // _kyau, coim, ежды_, _otoc,
+ {{0x4c9c007c,0x291e82d4,0x0c24a1d2,0xa09a00be}}, // ובאו, _avta_, рмін, _נישט,
+ {{0x6d58825b,0xbbd38740,0x67299c67,0x6d4a806a}}, // _osva, _तरीक, vrej, _opfa,
+ {{0x81cb80c8,0xc69203de,0xa3aa103e,0x6f02d95f}}, // রেন_, _פאל_, _गेन_, _atoc,
+ {{0x66155960,0x6729c949,0xc1740039,0x88c5801b}}, // _hezk, trej, שחק_, _ověř,
+ {{0x6615011b,0x7641bfee,0x765c5961,0x6d4a818e}}, // _kezk, tfly, _yary, _apfa,
+ {{0x6f1b8352,0x6729ae12,0x82d88012,0x64ab128a}}, // rsuc, rrej, _адус_, yřic,
+ {{0x3218016b,0x764184e1,0xceb403c8,0x34aa1a19}}, // vary_, rfly, ייץ_, евно_,
+ {{0x6281b136,0x6729d962,0x6f1b8387,0xab2a18d1}}, // _holo, prej, psuc, нома_,
+ {{0x32185963,0x6281d964,0x3cedcb9b,0x4fa480e8}}, // tary_, _kolo, _miev_, _київ,
+ {{0x62818a87,0x78a9d965,0xb607ad59,0x6615128a}}, // _jolo, liev, _рядк, _nezk,
+ {{0x26c5d966,0x60c42551,0x6281d967,0x291e5968}}, // molo_, toim, _molo, éta_,
+ {{0x32185969,0x6281d96a,0x63bb8085,0x26c581f6}}, // sary_, _lolo, _oxun, lolo_,
+ {{0x7c240013,0xddd5027f,0x765c596b,0x648600f7}}, // [4ee0] _idir, _rozš, _pary, _dóit,
+ {{0x26c588f1,0x6281a683,0x20f802a5,0xa3c78105}}, // nolo_, _nolo, _učio_, _उड़_,
+ {{0x765c3ef9,0xa0a63c29,0x60c403a7,0xa2c1016f}}, // _vary, _банд, poim, लकर्,
+ {{0x2056095a,0xb4c08006,0x26c58234,0x317b00be}}, // _стор, ुते_, holo_, _נאכד,
+ {{0x38602608,0x26c5bbb8,0x765c0110,0x6442d96c}}, // mdir_, kolo_, _tary, rfoi,
+ {{0x62818ca9,0xc3330bea,0x3860596d,0x3b0693de}}, // _colo, מוד_, ldir_, rpoq_,
+ {{0x6281d96e,0x6f02d96f,0x26c5c1e6,0x2ca4808e}}, // _dolo, _stoc, dolo_, _tmmd_,
+ {{0x38602608,0x9958026f,0x7c241efb,0x65000135}}, // ndir_, káže_, _ndir, _ịkwụ,
+ {{0x62818012,0xa3cb83db,0x3860198d,0x26c5d970}}, // _folo, _रुप_, idir_, folo_,
+ {{0x0f570051,0xa2e6a103,0x7c245971,0x9b938013}}, // ניים_, _созд, _adir, _الكت,
+ {{0x38600850,0x442480b9,0x7bcd00dd,0x64bc8088}}, // kdir_, _kdm_, _syau, pčid,
+ {{0xd00faf0a,0x3a3a002a,0xfbcd00ab,0x628e0123}}, // _فلم_, _acpp_, রেফত, ekbo,
+ {{0x38605972,0x26c580e1,0x69c00214,0x6281d973}}, // ddir_, bolo_, şmel, _yolo,
+ {{0x8f9b893f,0x26c58698,0x7c240201,0x2eb181a2}}, // _אידי, colo_, _edir, ीकृत,
+ {{0x6d588052,0x661ad01e,0x9928012f,0x66155974}}, // _usva, matk, люта_, _rezk,
+ {{0x6947003b,0x661ad975,0x395a0122,0x6aaa81ec}}, // jčeš, latk, _dsps_, hiff,
+ {{0xb4c300d4,0xceb40085,0x2ba4a743,0x8e150cec}}, // [4ef0] ्ती_, rsən_, _खेला, идац,
+ {{0x645f00dd,0xd5bb2597,0xc7d78039,0x661ad976}}, // _haqi, нсе_, רומי_, natk,
+ {{0x5d554c3b,0x6281ceb2,0xf1ab003d,0x3860011c}}, // ркет, _rolo, _ساده_, bdir_,
+ {{0x44fa8b18,0x661a8057,0x3af98205,0x6281d977}}, // _më_, hatk, _pèp_, _solo,
+ {{0x661ac08b,0x09dd8b86,0x5bd301aa,0xa7fb0020}}, // katk, मध्य, _सरोव, _cañe,
+ {{0xb4c30aad,0xe8e0001c,0x44fa81b0,0x2ba4816f}}, // ्तु_, _đức_, _oë_, _खेळा,
+ {{0x661ad978,0x44fa9674,0xd1758791,0x78a9c8fc}}, // datk, _në_, рылы, tiev,
+ {{0x645f5979,0x5f759ef7,0x2eee9bad,0x63bb9fa4}}, // _naqi, لاخر, _aiff_, _txun,
+ {{0x6281d97a,0x26c5d97b,0x672d597c,0x78a9d97d}}, // _tolo, tolo_, nraj, riev,
+ {{0x99980019,0x661aa0d0,0x201b0d7a,0x3860080a}}, // zerű_, gatk, haqi_, zdir_,
+ {{0x26c5d97e,0x672d026f,0x6d5ea8f7,0x38600380}}, // rolo_, hraj, _špar, ydir_,
+ {{0x26c582ec,0x7f5981c0,0xb87b0118,0x2369010c}}, // solo_, _tswq, _xiít, _braj_,
+ {{0x661a8867,0x64498013,0x26c5ca10,0x5f7784c1}}, // batk, _mbei, polo_, _حاضر,
+ {{0x412a0c8e,0xee0f1a3b,0x6f098216,0x648600f7}}, // кого_, िरोध_, mpec, _cóir,
+ {{0x7c3b8693,0x91e60b5b,0x628e597f,0x38600085}}, // _ocur, рове, rkbo, tdir_,
+ {{0xf7720013,0x7c242e28,0x27ff9c33,0x7bcd80eb}}, // ياء_, _udir, ncun_, ļauj,
+ {{0x38605980,0x672d06e4,0xee3a0847,0xa3e52207}}, // [4f00] rdir_, graj, кна_, भेद_,
+ {{0x6449d981,0x44248b99,0x2bdc8fd5,0xaa460adb}}, // _abei, _sdm_, _बरबा, _теол,
+ {{0x64bc8499,0xc27b0051,0x6d41ac20,0x395a484d}}, // jčic, _סרטי, ntla, _usps_,
+ {{0x8c4691c7,0xdd8f0d4a,0x0446a05f,0x645f0079}}, // реде, بوم_, редн, _xaqi,
+ {{0x661aa08b,0x7d09007a,0x39ae8085,0x68e41923}}, // yatk, _čese, _səs_, mmid,
+ {{0xd36f80f7,0x6d41d982,0x68e402f3,0x7c3bd983}}, // بهه_, ktla, lmid, _ecur,
+ {{0x5bd30b6f,0x649382bb,0x6aaa8d02,0x661ad984}}, // _सर्व, _için, riff, vatk,
+ {{0x6b8ba280,0x8f468160,0x6d5c0e5a,0x224982a5}}, // bygg, аход, _isra, đaka_,
+ {{0x44fa88cf,0xd6da9bba,0x20050085,0x661ad985}}, // _së_, кто_, _əli_, tatk,
+ {{0x26c70025,0x3cf85986,0x7afd86a5,0x443b0118}}, // čnog_, _kurv_, _ésta, _scq_,
+ {{0x44fa8b18,0x661ad987,0x200001ac,0x2484977b}}, // _që_, ratk, kcii_, _komm_,
+ {{0xb4c30a16,0x661a810b,0x69dac102,0xdb0401df}}, // ्ते_, satk, _izte, _axiñ,
+ {{0x661a94ff,0x6d41a161,0xdce40b80,0x6d5c0122}}, // patk, atla, _grnč, _lsra,
+ {{0x44fad988,0x171b84de,0x236900e1,0xe80e8074}}, // _të_, _יודע, _vraj_, सरका_,
+ {{0x2bd30592,0x30a70ddc,0x3eacd297,0xddd89502}}, // _सरका, арав, midt_, _fovš,
+ {{0x1410800f,0x2366805c,0x88940009,0x68e4110f}}, // ारोह_, kvoj_, оисх, gmid,
+ {{0xe3b80059,0x6d5c11d6,0x661881c0,0xaa8912c8}}, // [4f10] şır_, _asra, _kevk, _منظم_,
+ {{0xde1907bd,0x42550c48,0x672d011e,0x4439005f}}, // اقات_, _اندر, rraj, lgs_,
+ {{0x6f0984e8,0x44390144,0x2bda00d7,0x672d2551}}, // zpec, ogs_, _لایک_, sraj,
+ {{0x44395989,0x672d598a,0x6618807a,0x315600be}}, // ngs_, praj, _levk, _פירן_,
+ {{0x69da811e,0x6b658a08,0xe3b084a3,0x6285598b}}, // _azte, скла, وره_, _koho,
+ {{0x6285598c,0x20191fa4,0x78ad0110,0x6b8bb860}}, // _joho, _hesi_, liav, rygg,
+ {{0x2019022e,0xb5e500c8,0x6285598d,0xf59500f7}}, // _kesi_, _প্রচ, _moho, _الأج,
+ {{0xb4c3000d,0x20191351,0x6ca484ad,0x6285598e}}, // ्तो_, _jesi_, _друж, _loho,
+ {{0xb8fe80cf,0x2019598f,0xb4c3000d,0x27ffd990}}, // _दी_, _mesi_, ्तै_, rcun_,
+ {{0x6f098359,0x27ffba68,0x2019005d,0x78ad5991}}, // rpec, scun_, _lesi_, hiav,
+ {{0x74ea0c9b,0x79a7b2f1,0xaefb0229,0xddd885b7}}, // лдог_, _трае, _drùi, _povš,
+ {{0x6d41d992,0x44390bcb,0x648600f7,0xc18c80be}}, // rtla, ggs_, _cóip, _שטאָ,
+ {{0x6d41d993,0x64bcb999,0x26d9809f,0xe60e8c0e}}, // stla, nčia, ïsos_, _нд_,
+ {{0x64bc8110,0xcb128039,0x2bde150e,0xd37a83de}}, // ičia, הלך_, नेगा, ארשט,
+ {{0x62851807,0x2019059c,0x40960171,0xf2d29101}}, // _doho, _besi_, брат, פעל_,
+ {{0x387f861b,0x60c9838e,0x10a301a4,0x2aad016b}}, // njur_, noem, писн, ržba_,
+ {{0xa0670698,0x201914a9,0x64bc80e1,0xac9705ff}}, // [4f20] щата_, _desi_, jčia, _دنيا_,
+ {{0x68e45994,0xe7f400d4,0xdc1080c2,0x20000037}}, // smid, _अलका_, ार्ड_, scii_,
+ {{0x3ce0022c,0x64bc8110,0x629c00f7,0x321c9075}}, // _khiv_, ečia, mhro, vavy_,
+ {{0xeb9aa33f,0x291e01e2,0x628504fb,0x78ad55f4}}, // лие_, štas_, _zoho, ciav,
+ {{0x387f8106,0x321c816b,0x69da8061,0x649385aa}}, // djur_, tavy_, _szte, _içil,
+ {{0x6e960307,0x6d5c01a8,0x6d178035,0xd9fa2a54}}, // _السا, _tsra, _दबंग_, ्डित_,
+ {{0x5fac035a,0x321c8efc,0x81cb80ab,0xd7fa8088}}, // _घेतल, ravy_, রেস_, луп_,
+ {{0x387fd928,0x2ed08a75,0xceb40085,0x831a8c2a}}, // gjur_, हत्त, ksək_, _مقرر_,
+ {{0xdfd08307,0x7f445995,0xdddc0087,0x6720911b}}, // بية_, ntiq, _forţ, usmj,
+ {{0xa7fb05b4,0x2d9e82af,0x69da826c,0xb87b00ff}}, // _maña, tzte_, _tzte, _khíc,
+ {{0x6285136f,0x69daab43,0x3eac928d,0xa7fb0118}}, // _roho, _uzte, ridt_, _laña,
+ {{0x67228ee0,0x3ce001c5,0x3eaa00b9,0xb87b01a8}}, // _tvoj, _chiv_, _lmbt_, _mhíc,
+ {{0x62850696,0x443903ab,0x661e0057,0x201916ce}}, // _poho, rgs_, kapk, _resi_,
+ {{0x20195996,0xdd948d15,0x65948087,0xdb0a1434}}, // _sesi_, _малы, _малу, ðnám,
+ {{0x661e09ca,0x78ad5997,0x66d00009,0x201900e5}}, // dapk, tiav, näku, _pesi_,
+ {{0xa7fb1f49,0xb7150081,0x2a615998,0x3ce6a39f}}, // _baña, ждащ, _wahb_, jmov_,
+ {{0x628503fb,0x201938be,0xa7fb1e4f,0x644d011b}}, // [4f30] _toho, _vesi_, _caña, _ibai,
+ {{0xa7fb06a5,0x6c35026a,0x661e00dd,0x43d401a8}}, // _daña, _افکا, gapk, _عزيز,
+ {{0x201912d0,0x291e2944,0x7f4400e7,0xb87b5999}}, // _tesi_, štar_, atiq, _chíc,
+ {{0x42550019,0x3e8501d0,0xb87b0174,0x44fe0123}}, // _انگر, _sítě_, _dhíc, _dï_,
+ {{0xa7fb062f,0xa03a0158,0x644d00f7,0x1dcf8105}}, // _gaña, _דערפ, _mbai, _सुनत,
+ {{0x661e0057,0x660d8668,0x64bc88fc,0x442902df}}, // capk, _đako, rčia, _lda_,
+ {{0x644d003c,0x44291fcf,0x60c99727,0x6f0d599a}}, // _obai, _oda_, toem, lpac,
+ {{0xa3aa2cdd,0xaf059baa,0x7afd86a5,0x3f42807b}}, // _गइल_, опил, _ésto, fðu_,
+ {{0x660482bb,0x7c29846d,0xb4d592c6,0x60c9910f}}, // _şika, _ider, हती_, roem,
+ {{0x4429599b,0x644d599c,0xda02800f,0x6d454772}}, // _ada_, _abai, रुआत_, otha,
+ {{0xa2c6909b,0x6d45599d,0x7afb8009,0x443f8267}}, // िवर्, ntha, _huut, _kcu_,
+ {{0x6d45031d,0x4429599e,0x7afbbf6c,0xa775964f}}, // itha, _cda_, _kuut, _млеч,
+ {{0x4429599f,0xfc4a026b,0x629c435c,0x7afba551}}, // _dda_, _adíá_, thro, _juut,
+ {{0x7afb8181,0x442959a0,0x64bc811f,0x201f8359}}, // _muut, _eda_, jčin, laui_,
+ {{0x81cb8a49,0x443f805c,0x04460098,0xfbde14fc}}, // রের_, _ocu_, _дейн, नेजम,
+ {{0x7c2997d5,0xa7fb06a5,0x443f822c,0x6f0d090d}}, // _nder, _paña, _ncu_, fpac,
+ {{0xe73a0c8e,0xe61a2481,0x661e0867,0x7e63d9a1}}, // [4f40] рез_, ида_, tapk, _kanp,
+ {{0x7c29cea9,0x443f8013,0x7e63826b,0x6d451433}}, // _ader, _acu_, _janp, ftha,
+ {{0x7f44295a,0x661e0867,0x22580cfa,0x91e659a2}}, // stiq, rapk, merk_, _фоне,
+ {{0x7e638102,0x82360019,0x69de49cd,0xe73a0198}}, // _lanp, _ارکا, _izpe, _кем_,
+ {{0x6d450083,0xdee60098,0x66090214,0x7c298114}}, // atha, _моми, _şeke, _dder,
+ {{0x7c29d9a3,0xb87b0104,0x6d4559a4,0x6da60b5b}}, // _eder, _thíc, btha, _дига,
+ {{0x67240c7f,0xb05b1a50,0xdd920019,0x6d454d01}}, // lsij, _fjäd, _غور_, ctha,
+ {{0x661c54b9,0xf8ba80c8,0x7e63838e,0xdddc272a}}, // _herk, ুষ্ঠ, _aanp, _gorš,
+ {{0x6724225d,0x661c289c,0xa3aa0006,0xda0b816f}}, // nsij, _kerk, _गेल_, _स्वत_,
+ {{0x442914c9,0x3f428125,0x63a2d9a5,0x09e6866c}}, // _sda_, rðu_, mzon, _дозн,
+ {{0x7c29813c,0x64bc81e2,0x80c782f1,0x6288a817}}, // _yder, nčio, ावशे, _iodo,
+ {{0x6f0d0953,0xceb8c702,0x06de00ab,0xe4a7004a}}, // ypac, snę_, _ভূমি, ірно,
+ {{0x63a2d9a6,0x67240289,0xa2a1954b,0x443f00eb}}, // nzon, jsij, कोक्, ļu_,
+ {{0x6288810b,0x6724026c,0x6d450428,0x443f17ac}}, // _jodo, dsij, ytha, żu_,
+ {{0x6288d9a7,0x2d83813c,0x68e289da,0x48ab1652}}, // _modo, øjet_, _khod, атам_,
+ {{0x62888e80,0x442912bf,0xd5b980e8,0x26cc89c4}}, // _lodo, _uda_, _усі_, lodo_,
+ {{0x661c0397,0x63a28a0f,0x64bc8b80,0x68e28090}}, // [4f50] _berk, jzon, tčin, _mhod,
+ {{0x6f0d59a8,0x62888029,0x7bdf1099,0x63a2809a}}, // rpac, _nodo, _izqu, dzon,
+ {{0x68e2826f,0x7afb84a2,0x6d450fe9,0xdddc0088}}, // _ohod, _suut, utha, _porš,
+ {{0x236dd9a9,0x6d4547c8,0xb4d58a16,0x7afb88e5}}, // _prej_, rtha, हते_, _puut,
+ {{0x0f79093f,0x64bc9487,0x66d00106,0x443f8609}}, // _האָב, mčil, täkt, _vcu_,
+ {{0x24893603,0x4efa01c6,0x60cd03e4,0x661c59aa}}, // _loam_, _להשו, loam, _gerk,
+ {{0x26ccd5ad,0x6288c18b,0x7e638247,0x68e29dfb}}, // dodo_, _dodo, _ranp, _bhod,
+ {{0x68e289a4,0x60cd25ca,0x64bc9807,0x63a2a91d}}, // _chod, noam, nčil, bzon,
+ {{0x63a28063,0x7e63d9ab,0x201f808e,0x6288a00a}}, // czon, _panp, raui_, _fodo,
+ {{0x26ccd9ac,0x6459d9ad,0x35d003b7,0x776981df}}, // godo_, lewi, _तुड़, rvex,
+ {{0x22583fa4,0x27f90118,0x68e29995,0xd46a04dd}}, // verk_, _ogsn_, _fhod, бине_,
+ {{0x225816a1,0x62838503,0x649a0cc1,0x7c3d0711}}, // werk_, ljno, стор_, ggsr,
+ {{0xee2e8554,0x7e638763,0x68e9805f,0x225859ae}}, // _пн_, _tanp, mmed, terk_,
+ {{0xa7fb05a4,0x68e9d9af,0x2cbf816d,0x68e29c18}}, // _baño, lmed, _ljud_, _zhod,
+ {{0x40961354,0x63a2aa4d,0xa7fb0020,0x5bb89b7e}}, // прат, zzon, _caño, _अश्व,
+ {{0x03261878,0xb05b00f2,0x6e21d9b0,0xa7fb2423}}, // _един, _skär, lalb, _daño,
+ {{0x442243db,0x661c0dab,0x6459c5fc,0x2258074c}}, // [4f60] mak_, _perk, dewi, perk_,
+ {{0x4422059f,0x67241ffe,0x68e98754,0xddc18019}}, // lak_, rsij, hmed, lelő,
+ {{0x672459b1,0x6288d357,0xa7fb01df,0x60cd59b2}}, // ssij, _rodo, _gaño, boam,
+ {{0x442259b3,0x661c0574,0x645996a1,0x6288d9b4}}, // nak_, _werk, gewi, _sodo,
+ {{0x661c0393,0x68e2831d,0xd7f200f7,0x6e218110}}, // _terk, _rhod, _سكس_, kalb,
+ {{0x44220455,0x63a2d9b5,0x62888079,0x68e2ba06}}, // hak_, rzon, _qodo, _shod,
+ {{0x442259b6,0x63a2b1d6,0x20070063,0x66d00009}}, // kak_, szon, śnie_, säks,
+ {{0x25e1009a,0x63a2aa13,0xc05ab73a,0x55bb01c6}}, // _करती_, pzon, сік_, _המלו,
+ {{0x44221dae,0x62888510,0x68e2803e,0x201d8cf1}}, // dak_, _todo, _vhod, _dewi_,
+ {{0x6e218666,0x7c228032,0x7bcd81a9,0x59a78074}}, // galb, laor, ļaut, _केकर,
+ {{0x7bd600a4,0x26cc8110,0x1dbe1299,0x68e980e1}}, // _ayyu, rodo_, ्थित, bmed,
+ {{0x442259b7,0x2d828d11,0x7c22d9b8,0xd1978039}}, // gak_, ćke_, naor, _מכבי_,
+ {{0x6e21d9b9,0x2d83806a,0xb921019d,0xa0c981a8}}, // balb, øjer_, basị_, _بذلك_,
+ {{0xb87b0104,0x1dbe23e6,0x2ec789a9,0x4422008e}}, // _chín, ्थात, रकृत, aak_,
+ {{0x4422592d,0x31c415e0,0xa9c40110,0x7c22d9ba}}, // bak_, еств, естк, kaor,
+ {{0x442259bb,0x64bca368,0x321ed9bc,0x50cc8072}}, // cak_, učil, _lety_, ाविष,
+ {{0xb90409a3,0x7c2d59bd,0x60cd2cfd,0x290c8cd4}}, // [4f70] _पी_, _idar, roam, _ltda_,
+ {{0xaefb0a2a,0x2cad97db,0x65950087,0x765ad9be}}, // _brùr, _smed_, мазу, nety,
+ {{0x753a8352,0x6459a5ad,0x64bc807a,0x6d48d9bf}}, // nutz, tewi, mčij, ntda,
+ {{0x765a8198,0x66054255,0xc435881b,0xfbe101d0}}, // hety, schk, بکست, _नराम,
+ {{0x753ac5cb,0x38694426,0x321ed9c0,0x38790db1}}, // hutz, mdar_, _bety_, _insr_,
+ {{0x44223065,0x9f960039,0x38690ba0,0x66d0016d}}, // zak_, _חדשה_, ldar_, säkr,
+ {{0x44224259,0x7c2d0b4c,0x776d01df,0x68e9d9c1}}, // yak_, _odar, lvax, tmed,
+ {{0x386959c2,0x7c2d59c3,0x2cbf9eb1,0x05d082f1}}, // ndar_, _ndar, _ujud_, _हड़ब,
+ {{0x64bc8582,0xcb348098,0x7e6716f2,0x53349dc7}}, // kčij, _петъ, _kajp, _петт,
+ {{0x442259c4,0x7c2d2266,0xdd1b816b,0x321e8123}}, // wak_, _adar, _váže, _gety_,
+ {{0x442259c5,0x442d85b9,0x6d48d9c6,0x753a8102}}, // tak_, _kde_, gtda, gutz,
+ {{0x442d801b,0x7e672009,0x4422011b,0xb87b01a8}}, // _jde_, _lajp, uak_, _shín,
+ {{0x44220781,0x3869407e,0x7c2d031d,0x765a951f}}, // rak_, ddar_, _ddar, bety,
+ {{0x7e6709b3,0x7ea059c7,0xb87b00f7,0x7c2d59c8}}, // _najp, _köpe, _bhío, _edar,
+ {{0x442dc5e1,0xa3d485e8,0xb87b1d72,0xa9671bab}}, // _ode_, _सुन_, _chío, мира_,
+ {{0x98a08d11,0x442dd9c9,0xb87b00f7,0x25d680be}}, // čić_, _nde_, _dhío, _קוקן_,
+ {{0x7f49d9ca,0x7e670b80,0x44200087,0x64bcb78b}}, // [4f80] nteq, _bajp, _iei_, učim,
+ {{0xf7679fbe,0x7c2d0e04,0x44200956,0x442dd9cb}}, // _یا_, _zdar, _hei_, _ade_,
+ {{0x7c2280ee,0xa0a602c0,0x68fe02c4,0x629e00e5}}, // taor, _жанд, _uupd, _ilpo,
+ {{0x442001e2,0x69c00214,0x321e8690,0xa3d20651}}, // _jei_, şmes, _sety_, _वडा_,
+ {{0x7c228d02,0x629e00b9,0x442dd9cc,0x26c38301}}, // raor, _klpo, _dde_, čjoj_,
+ {{0x442059cd,0x442dd9ce,0x38668068,0x7c228748}}, // _lei_, _ede_, _saor_, saor,
+ {{0xd90d84c0,0x2f0400f2,0x6d48d9cf,0x44200bfd}}, // میل_, _hög_, xtda, _oei_,
+ {{0x44201f96,0x442d8d11,0xd01081a8,0x00000000}}, // _nei_, _gde_, دلة_, --,
+ {{0x6f021301,0x765ad9be,0xb6f48a3d,0xc246aba7}}, // íoch, tety, нзиј, _онак,
+ {{0x24868052,0x442d800d,0x6996a3e7,0x753a89e8}}, // ljom_, _zde_, _орех, tutz,
+ {{0x442059d0,0xf76f83f8,0x2d988038,0x2f04007b}}, // _bei_, های_, áre_, _lög_,
+ {{0x44200012,0x765aa168,0x442d83ac,0x6fc980d4}}, // _cei_, sety, _xde_, _हुकू,
+ {{0x44201fcb,0xd7e2a9b7,0xe7e28107,0xfc3f00f7}}, // _dei_, _परिच, _परिप, rgí_,
+ {{0x44200118,0x7d033680,0x80dc00ab,0x7817097d}}, // _eei_, ínsk, _মূল্, तर्क_,
+ {{0xa2b1805e,0x44203aa8,0xd5afa355,0x480c019d}}, // _अगस्, _fei_, _рс_, _ọzịz,
+ {{0xdd8e8065,0x81cb80ab,0x660d8115,0x442030a3}}, // _موڈ_, রেক_, _đaki, _gei_,
+ {{0x386959d1,0x645d59d2,0x7af701b9,0x24868bcf}}, // [4f90] rdar_, mesi, _tixt, djom_,
+ {{0x76d50013,0x69c501e9,0x44200a0f,0xb87b00f7}}, // رياض, _txhe, _zei_, _thío,
+ {{0x386901bf,0x7e6701c0,0xaefb026b,0x752881a9}}, // pdar_, _vajp, _erùp, nsdz,
+ {{0x645d59d3,0x8b6b80eb,0x2aad012b,0xb69b0162}}, // nesi, ņēmē, džbi_, _atât,
+ {{0x68ed59d4,0x511f0074,0x628e00d2,0x7e670197}}, // mmad, _बबुआ_, žnoš, _tajp,
+ {{0xa9698676,0x290115d0,0x2d470187,0x21699af1}}, // зика_, _kuha_, põe_, зики_,
+ {{0x7c2080f1,0x645d59d5,0xac198e1d,0x6e25008e}}, // _femr, kesi, _нову_, mahb,
+ {{0x442dd9d6,0x290131a7,0x645d59d7,0x68ed22f8}}, // _ude_, _muha_, jesi, nmad,
+ {{0x442059d8,0xe8200076,0x68ed59d9,0x290116fb}}, // _rei_, बरता_, imad, _luha_,
+ {{0x44203528,0x39648364,0x7c2080f1,0x458590ca}}, // _sei_, ässä_, _zemr, егов,
+ {{0x044601d9,0x29010ad4,0x68ed59da,0xa6e2008b}}, // ненн, _nuha_, kmad, íðin,
+ {{0x859a0039,0x802780d7,0x64ca1299,0xd46a3296}}, // פשרו, _آرام, रवेश, чиме_,
+ {{0xe72ed9db,0x442059dc,0xaca38028,0x1dcf93e5}}, // _ще_, _vei_, _chứn, _सुरत,
+ {{0x249fd9dd,0x60dbd9de,0xa764804a,0x68ed03ca}}, // _llum_, llum, _шкід, emad,
+ {{0xf09f0362,0x442059df,0x2bdc91be,0xf3f98162}}, // _blà_, _tei_, _बर्थ, _daţi_,
+ {{0x645d05e4,0x290104a7,0x68ed02a3,0x248d9ad7}}, // cesi, _duha_, gmad, _noem_,
+ {{0x672985b4,0x7c260234,0xace900d7,0x8fa30ba5}}, // [4fa0] nsej, makr, _ترول_, гате,
+ {{0x6da62410,0xe5a601f3,0x7c263d9b,0x249f9916}}, // низа, низи, lakr, _alum_,
+ {{0x60db9cbc,0xe3bf04c3,0x249fd9e0,0x200601df}}, // klum, maña_, _blum_, ñois_,
+ {{0x64a38b69,0xac07d081,0x0ca980f7,0x61e11266}}, // лаха, енца_, وطني_, ülle,
+ {{0xc5f88029,0x7cfe8029,0xe3b28a47,0xeb978198}}, // ldēt_, _vīri, درا_, ниц_,
+ {{0x7d0d890c,0x6e250d8b,0x645d5105,0x9966959f}}, // _časn, cahb, zesi, етил,
+ {{0x645d59e1,0x55e3031f,0x1dcf8540,0x64a6cc6b}}, // yesi, _борб, _सुलत, _зава,
+ {{0x60dbd9e2,0xa3cb8105,0x62988084,0x7c2654a9}}, // glum, _रुक_, skvo, jakr,
+ {{0xb907853e,0x59d2035a,0xb4cc9130,0xdca38294}}, // _मी_, _दुसर, रकी_, _сати,
+ {{0x44268272,0x645d3d41,0xddd8801b,0x2ef8807b}}, // lao_, wesi, _zavř, örf_,
+ {{0x7d092191,0x645d59e3,0x5fe2835a,0x01368591}}, // _česk, tesi, _परवल, _سعاد,
+ {{0x7afa9f0a,0x4426d74a,0x2fc690af,0x6442a91f}}, // _hitt, nao_, _txog_, ngoi,
+ {{0x087780be,0x78a43df9,0x29013cd2,0x6e2500ee}}, // _רעדט_, dhiv, _suha_, yahb,
+ {{0x7afa81b9,0x877b80be,0xe3bf0661,0x3ce78054}}, // _jitt, _מאדי, gaña_, _छीने_,
+ {{0x7afad9e4,0x68ed36b2,0xf09f07f1,0x7cfe8196}}, // _mitt, tmad, _plà_, _būre,
+ {{0x44268025,0x7afad5d1,0x7c26008e,0x7bde809a}}, // jao_, _litt, cakr, ępuj,
+ {{0x4426803b,0x3d068b9f,0x6a151a02,0x9f340163}}, // [4fb0] dao_, _संडे_, _импу, леті,
+ {{0xa3aa1a3b,0x7afa8197,0x3f8701dd,0x249f8706}}, // _गेट_, _nitt, ćnu_, _slum_,
+ {{0xeab18bca,0xceb3898a,0x3ce90069,0x200900ee}}, // یعت_, ניע_, _khav_, pcai_,
+ {{0x44268025,0x2bdc8076,0x78a42c04,0x7c849631}}, // gao_, _बरखा, chiv, луче,
+ {{0x7afad9e5,0x69da85b0,0x69e3025b,0x6f02ad60}}, // _bitt, _ayte, šteć, _nuoc,
+ {{0xc61b00c8,0x7afa8698,0x69dad9e6,0x6d58c7a5}}, // _ঢাকা_, _citt, _byte, _opva,
+ {{0x4426a64b,0x7afaad08,0x7d0d9ee0,0x7f4d32aa}}, // bao_, _ditt, _časo, ltaq,
+ {{0x4426d9e7,0x7afa8125,0xa3c309a9,0x249f9600}}, // cao_, _eitt, ्था_, _ulum_,
+ {{0xc62400c8,0x60db8859,0x6f028081,0x7f4d011c}}, // _পাতা_, rlum, _cuoc, ntaq,
+ {{0x7afa87d9,0xf1a790ca,0x60dbd9e8,0x82378bbe}}, // _gitt, _прен, slum, _سرطا,
+ {{0xdca6119d,0x4df48153,0x6729ae12,0x64a60611}}, // вани, ляют, ssej, вана,
+ {{0x224980fe,0x7d04d9e9,0x7afacb19,0x3ce901c5}}, // đaku_, _čist, _zitt, _chav_,
+ {{0x26c7003b,0xccf280be,0xe3bf0511,0x7ea00106}}, // čnoj_, יכט_, taña_, _löpa,
+ {{0x386d806f,0xd02680c8,0x64410084,0xa7fb02f9}}, // lder_, _যায়_, ėlia, _gañi,
+ {{0xaca38142,0x26d30825,0xa01b016d,0x4426d9ea}}, // _nhữn, doxo_, _onöd, yao_,
+ {{0x2aad111b,0x3a2782c4,0x6e23d9eb,0xe3bf0333}}, // džbu_, hanp_, _henb, saña_,
+ {{0x4426803b,0xe3bf160a,0xb05b04b8,0x6e238247}}, // [4fc0] vao_, paña_, _tjän, _kenb,
+ {{0xb05b04b8,0x78a413fa,0x4426c53f,0x386d8428}}, // _hjäl, shiv, wao_, hder_,
+ {{0x7d0390e4,0x4432001b,0x6e2380dd,0x2d9c01ac}}, // _juns, _kdy_, _menb, áve_,
+ {{0x7afad9ec,0x386daa52,0x7d039b19,0xb4be8a16}}, // _sitt, jder_, _muns, ीके_,
+ {{0x4426803b,0x7c2407ca,0x93439b47,0x8c43a155}}, // rao_, _meir, инце, рече,
+ {{0x44268025,0xe8e00028,0x386d8ac5,0x386001e4}}, // sao_, _độc_, eder_, leir_,
+ {{0x69da820f,0x4426803b,0x7afa8d02,0x6f02d9ed}}, // _qyte, pao_, _vitt, _suoc,
+ {{0xfce68cde,0x7afa8ac5,0xe0cf80f7,0xf993025f}}, // _робо, _witt, يزي_, שרה_,
+ {{0xf8b30051,0x7afad9ee,0xc6f89fb4,0x7d038362}}, // ישה_, _titt, нних_, _auns,
+ {{0x7afa98c7,0x7d03d9ef,0x15e618a4,0x60cd81a1}}, // _uitt, _buns, _करार_, čama,
+ {{0x7c2459f0,0xceb30051,0x6e2392a2,0x4424d9f1}}, // _beir, שית_, _denb, _kem_,
+ {{0x80e080c8,0x79b78051,0x4424aed5,0x35b3a357}}, // _পূর্, _בלבד_, _jem_, рбюр,
+ {{0x7c24444b,0x4424d9f1,0x7d03808e,0xd007ca56}}, // _deir, _mem_, _euns, _рече_,
+ {{0x4424d9f2,0x28ae8074,0x6e23806a,0x78a28115}}, // _lem_, जोरि, _genb, _hlov,
+ {{0x81d480c8,0x7c24186c,0xe2f8004a,0xb05b0106}}, // সের_, _feir, тері_, _fjäl,
+ {{0x44248986,0xb4cc83bb,0xb4be83bb,0x4432050d}}, // _nem_, रको_, ीको_, _gdy_,
+ {{0xe3bf040e,0xf77307bd,0x64bc8110,0xfc3f0118}}, // [4fd0] maño_, جاز_, nčiu, chía_,
+ {{0xa3e805b3,0xed4d8986,0x7f4d07d5,0xe3bf01df}}, // _भरत_, _بھی_, rtaq, laño_,
+ {{0x442480a9,0xa9c40d70,0x386dd9f3,0x7f4d59f4}}, // _bem_, _всяк, yder_, staq,
+ {{0x4424d9f5,0xe7e2b4ec,0x6ba802d0,0x7c2403a8}}, // _cem_, _परंप, _örgü, _xeir,
+ {{0x4424d32b,0xe78423d7,0x386d804a,0x6f020174}}, // _dem_, _куро, vder_, íoct,
+ {{0xdce401d0,0x539c0e82,0x672d59f6,0x98b80ec3}}, // _brně, וידו, msaj, murā_,
+ {{0x27e68019,0x672d0338,0x79a78081,0x64bc8196}}, // _azon_, lsaj, ърде, ečiu,
+ {{0x2ca68265,0x6e23d9f7,0x386d80f3,0x78a2d9f8}}, // chod_, _senb, uder_, _clov,
+ {{0x386da815,0x672d0a49,0x68fd074c,0x7c2459f9}}, // rder_, nsaj, _misd, _reir,
+ {{0xb05b04b8,0x44248070,0x7c24061f,0x76453205}}, // _själ, _zem_, _seir, nghy,
+ {{0x391599b8,0x4424d9fa,0x236900f1,0x7c2459fb}}, // _смер, _yem_, _asaj_, _peir,
+ {{0x44248142,0x3a25d9fc,0xe3bf01ca,0x6e23b235}}, // _xem_, _help_, gaño_, _wenb,
+ {{0x2905d9fd,0x629c0668,0x7d0381ec,0xceb8809a}}, // _hula_, tkro, _wuns, bię_,
+ {{0x2905cd24,0x2d732bea,0x7d0393d7,0x68fd0706}}, // _kula_, _oće_, _tuns, _aisd,
+ {{0x29059f3a,0x7c2432c3,0x629c209b,0xa7fb0118}}, // _jula_, _teir, rkro, _mañu,
+ {{0x6d419dde,0x1c461289,0x2905d9fe,0x629c2c15}}, // mula, _сним, _mula_, skro,
+ {{0x2905cf67,0xe73715e0,0xe7c400ab,0x68fd1181}}, // [4fe0] _lula_, лет_, ্ধ্য, _disd,
+ {{0x4424bbed,0x6449d8cf,0xda6600af,0x7e61d779}}, // _sem_, _acei, _свои, nelp,
+ {{0x44248069,0x6d41a4ed,0x21761448,0x88bc801b}}, // _pem_, nula, _супр, _květ,
+ {{0x64460812,0x29180531,0x64bc803b,0x672d01b9}}, // ngki, mpra_, jčit, bsaj,
+ {{0x4424d9ff,0x2905cd2c,0x6d4197b7,0x6d4103a6}}, // _vem_, _aula_, hula, _ålan,
+ {{0x78a299a1,0x6d41da00,0x2905da01,0xaa58968a}}, // _slov, kula, _bula_, тику_,
+ {{0x4424da02,0x248005f5,0x2905807d,0xa3d48054}}, // _tem_, _onim_, _cula_, _सुर_,
+ {{0x4734bbae,0x6d4e0a76,0x644980f7,0x60c45a03}}, // анис, _iqba, _gcei, nnim,
+ {{0x7c938013,0x69d60105,0x29059400,0x6e28da04}}, // _القص, _मुसी, _eula_, tadb,
+ {{0x2480009c,0x6e989a19,0x2905da05,0x6d41da06}}, // _anim_, твор_, _fula_, fula,
+ {{0x2905b55e,0x6d41b09c,0x7afd8081,0x241917ae}}, // _gula_, gula, _èsta, коны_,
+ {{0x78a2da07,0xbd87803d,0x236901a1,0xa3c300bc}}, // _ulov, چنین_, _psaj_, ्थल_,
+ {{0x60c45a08,0x7e618be7,0x68fd09f4,0x6d41a416}}, // dnim, belp, _risd, aula,
+ {{0x6d5c1eee,0x7afe01da,0x24805a09,0x23690353}}, // _opra, _cipt, _enim_, _vsaj_,
+ {{0x19580071,0x6f065a0a,0x35d90b9f,0x6eeb5a0b}}, // лары_, _aukc, _बुढ़, nübe,
+ {{0x23690282,0x60c45a0c,0x30150b5b,0xa8151c79}}, // _tsaj_, gnim, рдар, рдаш,
+ {{0x6d5c5a0d,0x6d4e01b4,0x68fd0b82,0xa3c30072}}, // [4ff0] _apra, _aqba, _visd, ्थळ_,
+ {{0x325503c7,0x644980f7,0x88bc81d0,0xba552ba7}}, // _твор, _scei, _zvět, _твој,
+ {{0x68fd5a0e,0x672d3194,0x60c45a0f,0x6722838a}}, // _tisd, ssaj, bnim, _lwoj,
+ {{0xc8671b10,0x48670698,0xb4c18074,0xddc3912b}}, // _стои, _съоб, ुवे_, _janš,
+ {{0x6d41da10,0x2905acc4,0xcb69a762,0xa7fb0388}}, // zula, _sula_, тале_, _pañu,
+ {{0x290594ff,0xeb96a482,0x442b2179,0x6d41d60e}}, // _pula_, риш_, hac_, yula,
+ {{0x0dc8035f,0xe5098028,0xdd940196,0x442b5975}}, // _бути_, _mặc_, _лары, kac_,
+ {{0x38ba9482,0x27e6009a,0x64bca368,0x6d41da11}}, // tūra_, żone_, učit, vula,
+ {{0x8b588b8c,0x3a258072,0x6d5c001b,0x6d418573}}, // _مجلس_, _telp_, _zpra, wula,
+ {{0x88bc800d,0xcdf58009,0x2905809c,0xe8df819d}}, // _svět, ичны, _tula_, _idọm_,
+ {{0x6281bf38,0x3ced81c0,0x7afe09c4,0x442b52ce}}, // _inlo, _khev_, _ript, fac_,
+ {{0x6d419238,0xeb9a34d6,0x442b0079,0x7afe0b67}}, // rula, вим_, gac_, _sipt,
+ {{0x6d41b1fe,0x60c4007a,0x87e79562,0xe0d084c1}}, // sula, vnim, _сюже, _عزم_,
+ {{0x6e2701d0,0x7c2b8118,0xe5098129,0x7ea002d0}}, // _nejb, hagr, _cặc_, _röpo,
+ {{0xfce60dc0,0x60c41809,0x7bcd12ab,0x6f060035}}, // _коко, tnim, _exau, _sukc,
+ {{0xa3b10361,0x98b80162,0x661c82d0,0x80660a2e}}, // _टेक_, dură_, ırke, јваж,
+
+ {{0xa06718a0,0x6d5c1ff1,0x60c45a12,0x62819447}}, // [5000] шата_, _spra, rnim, _onlo,
+ {{0x6f06026c,0x611181a9,0x2d9a0c6b,0x29180338}}, // _vukc, _zāle, type_, ppra_,
+ {{0x05662451,0x69da816f,0x98b80162,0x05d903db}}, // рвен, _पुढी, gură_, _बुदब,
+ {{0x7d09003a,0xeb9a825d,0x6d5c0db7,0xed5680be}}, // _čest, кие_, _vpra, יבער_,
+ {{0x3ced81c0,0x78bb80e5,0xe8e20072,0x78a30162}}, // _dhev_, diuv, पतीच, _înva,
+ {{0x442b011f,0x7ea00338,0x3d2800d7,0x38721277}}, // zac_, _köpm, _متری_, ldyr_,
+ {{0x67228063,0x81d800ab,0x6d4e0748,0xc448803d}}, // _swoj, ়ের_, _uqba, _میان_,
+ {{0xc05a8163,0x8c438abe,0x6eeb02af,0x672286c0}}, // тік_, _месе, rübe, _pwoj,
+ {{0x442b003b,0x660907d9,0x6eeb0192,0xdb24010c}}, // vac_, _şeki, sübe, _èsèn,
+ {{0xf708d871,0x7d00da13,0x442b582f,0x62818a2a}}, // _của_, _hims, wac_, _gnlo,
+ {{0xa0a68364,0x442b5a14,0x7d00da15,0x69d6016f}}, // _кажд, tac_, _kims, _मुली,
+ {{0x67228063,0xd00f8b76,0x78a99ac6,0x395e8748}}, // _twoj, _قلم_, chev, _ipts_,
+ {{0xbf9b03a7,0x2007066f,0x7c360286,0xdfd28a19}}, // _agên, śnik_, _ddyr, _بيش_,
+ {{0x7c2bda16,0x799c0114,0xdddc02a6,0x1b038264}}, // zagr, fyrw, _borż, রীকে_,
+ {{0x64400511,0x45448061,0xddce01a9,0x61038e06}}, // ómic, _انعق, _dabū, _tőle,
+ {{0xd2510077,0xd5fb8051,0x7bcd01e9,0x7d00da17}}, // _کند_, _בפור, _txau, _nims,
+ {{0x28f80364,0xe5098129,0x7d0d82d4,0x644d0db1}}, // [5010] реть_, _tặc_, _časi, _icai,
+ {{0x7d008cac,0x44290006,0x98b8002e,0x4fc40b73}}, // _aims, _hea_, tură_, іста,
+ {{0x3ced81c0,0xa2b7853f,0x7cff0110,0xdef82462}}, // _phev_, ्चस्, _jūro, рыт_,
+ {{0x3ced8282,0x7d0081a1,0xdd398493,0xe43c928a}}, // _qhev_, _cims, lăţe, bříč,
+ {{0x7c2b8410,0x4429002e,0xa3dd009a,0xd7fb04ae}}, // ragr, _mea_, _तुम_, _јун_,
+ {{0x44294cc8,0x3e57001b,0x6e35011f,0x69158197}}, // _lea_, _dětí_, _udzb, _jġeg,
+ {{0x442903ec,0x3ced8282,0x69da80d4,0xb7bd8162}}, // _oea_, _thev_, _पुदी, maţi,
+ {{0xc31d00c8,0x6d455a18,0xb7bd802e,0x26c7026f}}, // _তিনি_, muha, laţi, čnou_,
+ {{0x6d451fdb,0x78a9bb2d,0xa9e4001b,0x00000000}}, // luha, rhev, _गर्छ_, --,
+ {{0x26e5146d,0x7c2982b5,0x644d5a19,0x78a9b08b}}, // _कठोर_, _heer, _acai, shev,
+ {{0x6d454259,0x7c29da1a,0x38720c0b,0x78a98163}}, // nuha, _keer, ydyr_, phev,
+ {{0x44290012,0xb4c42eff,0x7c299e3b,0x443e02af}}, // _cea_, ्वे_, _jeer, ßt_,
+ {{0x44295a1b,0x7c299151,0x61e1b723,0x6d455a1c}}, // _dea_, _meer, _hyll, huha,
+ {{0x61e18364,0x7c29bd8e,0x6d454c5a,0x442902a6}}, // _kyll, _leer, kuha, _eea_,
+ {{0x91e611c7,0x44290216,0x61e1806a,0x395e0722}}, // сове, _fea_, _jyll, ïts_,
+ {{0x6d452e03,0x7c299e7e,0xa9281c18,0x3eb810e8}}, // duha, _neer, _nižš, _smrt_,
+ {{0x4fea0a13,0x57ea3296,0xe29a01a1,0x3872226d}}, // [5020] уман_, удам_, _бап_, rdyr_,
+ {{0x645bda1d,0xb7bd8087,0xb87b046d,0x44295447}}, // _abui, gaţi, _akíd, _zea_,
+ {{0x7c29b3c3,0x6d455a1e,0x69158197,0x2b5f8493}}, // _beer, guha, _jġed, _apuc_,
+ {{0x0446c9a1,0x395e8118,0x308601a8,0x645b8706}}, // седн, _rpts_, _الحف, _cbui,
+ {{0xe29aa84d,0x61e18144,0x7c299f74,0x7f44076b}}, // лад_, _ayll, _deer, quiq,
+ {{0x6d4520d9,0xb7bd802e,0x29025a1f,0x7d1a816b}}, // buha, caţi, _hika_, ypts,
+ {{0xfce69628,0x29021c00,0x61e18114,0x6d450683}}, // _гово, _kika_, _cyll, cuha,
+ {{0x29020fc7,0x7c29927b,0x26da04be,0xb4c4114f}}, // _jika_, _geer, lopo_, ्वो_,
+ {{0x44295a20,0x29025559,0x644d5a21,0x7e2a902a}}, // _rea_, _mika_, _rcai, ліна_,
+ {{0x61e19808,0x2902101f,0xed4f0117,0x644d00f7}}, // _fyll, _lika_, _تھے_, _scai,
+ {{0x44290074,0x61e1a9dc,0x7c299a25,0xe0d6a306}}, // _pea_, _gyll, _yeer, _людь,
+ {{0x7c298079,0xb7bd8087,0xf1c000ff,0xb5fb00be}}, // _xeer, zaţi, _ương_, טליכ,
+ {{0x44295a22,0x26da074b,0xdd398087,0x5f94891e}}, // _vea_, kopo_, tăţe, _филт,
+ {{0x29025a23,0x6d45234d,0x44295a24,0x0db98065}}, // _aika_, yuha, _wea_, ائیں_,
+ {{0x51841980,0x290209d8,0x30a7310c,0x3211009a}}, // пута, _bika_, брав, rczy_,
+ {{0xfd4d0028,0x25ef146d,0x2bda0c1c,0x3ebeaebd}}, // _thoạ, _अरबी_, _युवा, litt_,
+ {{0x59df8c78,0x29020870,0x7cff00eb,0xb7bd8087}}, // [5030] _पुनर, _dika_, _jūrm, taţi,
+ {{0x6d4550fd,0x7c29da25,0x2aab813c,0xdef811e9}}, // tuha, _seer, _købe_, бых_,
+ {{0x6285110f,0xb7bd802e,0x29024b0b,0xb87b01a8}}, // _inho, raţi, _fika_, _dhír,
+ {{0xa3bd0bb8,0x29025a26,0x3ebeda27,0x27e60035}}, // _आधा_, _gika_, hitt_, żona_,
+ {{0x7c299316,0x6eef806a,0x2aab806a,0xb87b01a8}}, // _veer, løbe, _løbe_, _fhír,
+ {{0x7c298c11,0x61e1da28,0x78ad04dc,0x236d807a}}, // _weer, _pyll, lhav, _vsej_,
+ {{0xd9f90063,0xa2d28013,0x9d180463,0x7c29da29}}, // ंधित_, بيوت, _гост_, _teer,
+ {{0x78ad0022,0x7f598073,0xa3d48c28,0x6009a344}}, // nhav, ајот_, _सुक_, шним_,
+ {{0xdddc07d9,0xfa0100c8,0x7d0a82af,0x442fda2a}}, // _karş, ্রিল_, _aufs, mag_,
+ {{0x86998049,0x3ebe804a,0x61e1da2b,0xe5098129}}, // ртит_, gitt_, _tyll, _dặn_,
+ {{0x2ca91de6,0xaca380ff,0xdddc0380,0xf1a7917f}}, // _blad_, _chủn, _marş, _уран,
+ {{0x644b8cef,0x442fda2c,0x2b40ba71,0x2299da2d}}, // nggi, nag_, šice_, _tèks_,
+ {{0xee2e9c82,0x29025a2e,0x26da004f,0x7cd200eb}}, // _он_, _rika_, yopo_, vārd,
+ {{0xe5a59860,0x60c99412,0x6da5a748,0x290258f2}}, // _мили, lnem, _мила, _sika_,
+ {{0x40962155,0x29025a2f,0x442f833e,0xc61681c6}}, // орат, _pika_, kag_, _וחצי_,
+ {{0xd0071e7d,0x60c9da30,0x02b68eed,0x6f03da31}}, // жете_, nnem, _अग्न, _hinc,
+ {{0x39478032,0x60c980e7,0x62970118,0x29025a32}}, // [5040] huns_, inem, _foxo, _vika_,
+ {{0x290202b8,0xc9664cfc,0x41c9816f,0x7ea00106}}, // _wika_, _двой, िपास, _köpi,
+ {{0x29020029,0x7d043735,0x78ad30ba,0x60dbd31e}}, // _tika_, _kiis, bhav, koum,
+ {{0x23770bca,0x78ad1075,0x60c98933,0x26da15d0}}, // _امید_, chav, jnem, sopo_,
+ {{0xa3d483b7,0x60db8289,0x7d040079,0x26da010c}}, // _सुख_, doum, _miis, popo_,
+ {{0x6f038065,0x644b8081,0x7d041f61,0x7c2f01e8}}, // _ninc, aggi, _liis, zacr,
+ {{0xa2cd0b6f,0x3947855a,0x64a69878,0x40348973}}, // _सदस्, guns_, _дава, пейс,
+ {{0x7cf18e23,0x290280f2,0x60c9da33,0x7d04037b}}, // gård, öka_, gnem, _niis,
+ {{0x6f03da34,0xdb2480d5,0xe6669f72,0x49110074}}, // _binc, _روای, отно, _दूनो_,
+ {{0x7c229277,0x7d04008e,0x39478187,0x6f03af96}}, // lbor, _aiis, buns_, _cinc,
+ {{0x3ebeb611,0x867b03c8,0x6f03da35,0x291a816b}}, // ritt_, בריו, _dinc, ípad_,
+ {{0x7d0d803e,0x7c22a22f,0x1b1d80ab,0x6b8200eb}}, // _čast, nbor, _নিতে_, _šoga,
+ {{0x7c2f2eaa,0xfe6f803d,0x6d41da36,0x2ca92d7b}}, // racr, ردی_, arla, _vlad_,
+ {{0x7c2f0baf,0xaabc83eb,0xaca3da37,0x2ca90114}}, // sacr, ्चिक, _ihụn, _wlad_,
+ {{0x290cda38,0x442fa6bc,0x28c70f12,0x644bb6bd}}, // _juda_, yag_, रचलि, yggi,
+ {{0x261685b3,0x290ca5eb,0x6d4887d5,0xa3aa809a}}, // पुरी_, _muda_, muda, खना_,
+ {{0x9f59581e,0x6d48da39,0x6297002a,0x7c22b839}}, // [5050] _også_, luda, _toxo, dbor,
+ {{0xdddc0086,0x60c0d699,0x442f849f,0x62850118}}, // _qarş, himm, wag_, _unho,
+ {{0x442fda3a,0x6d48da3b,0x7c2d015d,0xd6d81138}}, // tag_, nuda, _kear, іту_,
+ {{0x7c22a294,0xb4d5809a,0x7d040079,0x60db8118}}, // gbor, सके_, _xiis, xoum,
+ {{0x442f9839,0xdfc68013,0x6d4884b9,0x7cf1816d}}, // rag_, _أي_, huda, vård,
+ {{0x38691e07,0x7ead81ac,0x6d48da3c,0x442fda3d}}, // lear_, _kúpe, kuda, sag_,
+ {{0x81dd00c8,0x6d48da3e,0x442f9882,0x6f03da3f}}, // তের_, juda, pag_, _rinc,
+ {{0x386945e3,0x290c965f,0x6f03da40,0x39478187}}, // near_, _duda_, _sinc, runs_,
+ {{0x60c9da41,0x7c3ba266,0xea0000ff,0x7d041987}}, // rnem, _idur, _phẫu_, _riis,
+ {{0x38690c49,0x0ea98364,0x7d045a42,0x60c9c5e8}}, // hear_, ской_, _siis, snem,
+ {{0x442dda43,0x290c80a4,0x7d041e71,0x7c2d5a44}}, // _kee_, _guda_, _piis, _bear,
+ {{0x7c2d061f,0x442d84cd,0x6d41da45,0x895a9101}}, // _cear, _jee_, rrla, _לדעת,
+ {{0x442dda46,0x7c2d061f,0xdd8e8bca,0x7d04043d}}, // _mee_, _dear, نوی_, _viis,
+ {{0x290c82ec,0xfaa6076a,0x7c2d0362,0x442d81c6}}, // _yuda_, чаго, _eear, _lee_,
+ {{0xd1790cde,0x2bdc1551,0x64a6140b,0x703a0364}}, // ості_, _गुरू_, _нака, йчас_,
+ {{0x7c2d003c,0x442d85f8,0x38691c9d,0xb4d58023}}, // _gear, _nee_, gear_, सको_,
+ {{0xee3a0973,0x237f8282,0x78a40687,0x7c22da47}}, // [5060] йна_, _nruj_, rkiv, vbor,
+ {{0x7c3b84be,0x6b7b010f,0x78a45a48,0x60c081ec}}, // _adur, _פרינ, skiv, zimm,
+ {{0x929b0051,0x7c2d4380,0xe29b0039,0x6abe1e5a}}, // _היית, _year, _השיר, _impf,
+ {{0x442dda49,0xb4d68076,0x38695a4a,0x2fcd0115}}, // _cee_, हवे_, cear_, _žege_,
+ {{0x442d8d10,0x290c8503,0x7c22da4b,0x1ddd816f}}, // _dee_, _suda_, rbor, _नुसत,
+ {{0x3ce6ad6d,0x6d48da4c,0x7c3bda4d,0x3f808110}}, // slov_, yuda, _edur, _šiuo_,
+ {{0x3ce682a5,0x395800eb,0x82338019,0x60c0da4e}}, // plov_, ntrs_, رروا, timm,
+ {{0x6e2e0586,0xb05b00f2,0x442dda4f,0x25ae0214}}, // _hebb, _hjär, _gee_, ıklı_,
+ {{0xd6da8698,0x60c0da50,0x7f441571,0x7d0e40c5}}, // йто_, rimm, nriq, _hubs,
+ {{0x7c2d5a51,0x6d48da52,0x60c08364,0x290c80d2}}, // _sear, tuda, simm, _tuda_,
+ {{0x7c2d0ad0,0x63a2da53,0x442dda54,0x6288b3e1}}, // _pear, lyon, _yee_, _indo,
+ {{0xd5b78196,0x0cab00b3,0x7cff0110,0x6d48905f}}, // ясы_, стои_, _kūri, ruda,
+ {{0x6d489283,0x629ad01e,0x7d0e5a55,0x7eadda56}}, // suda, _koto, _lubs, _súpe,
+ {{0x27e682b8,0x6e2e571d,0x7f445a57,0x629ada58}}, // _iyon_, _nebb, driq, _joto,
+ {{0x30a7091c,0x38690013,0x26de9fb6,0x7e750db7}}, // прав, tear_, moto_, _razp,
+ {{0x61e5003e,0x6abe0352,0x25e0009a,0x629ac374}}, // _vyhl, _empf, _कड़ी_, _loto,
+ {{0x4c3c0051,0x442dae28,0x628880ad,0xb4d90740}}, // [5070] _התחב, _ree_, _ondo, ़की_,
+ {{0x442d9d6f,0x38691995,0xcb1400be,0x63a2b36a}}, // _see_, sear_, _אלץ_, dyon,
+ {{0x38695a59,0x6e2e2d08,0x442dc5b4,0x27e69dac}}, // pear_, _debb, _pee_, _lyon_,
+ {{0x628899fd,0x7f445a5a,0x26deda5b,0x442d81c5}}, // _ando, briq, hoto_, _qee_,
+ {{0x6e2e0098,0x26deda5c,0xb05b016d,0xe29992ea}}, // _febb, koto_, _fjär, жал_,
+ {{0x442d882e,0x91e58ada,0x629ad94c,0xfc3f05bf}}, // _wee_, моле, _coto, chív_,
+ {{0x442d94c7,0x27e682b8,0xac1985e9,0x629ada5d}}, // _tee_, _ayon_, _мову_, _doto,
+ {{0x60cd3c2c,0x26ccda5e,0x92bf80c8,0x63a2a6e8}}, // nnam, endo_, ীতে_, byon,
+ {{0x26deda5f,0x2d98da60,0x60cd5a61,0xa8068661}}, // foto_, ären_, inam, _riñó,
+ {{0x2489198d,0xddc88612,0x68e2da62,0x6f0f047f}}, // _anam_, cedū, _ekod, _mucc,
+ {{0x04460b79,0x8c4610ee,0x6f0f0081,0xa8068333}}, // менн, мене, _lucc, _piñó,
+ {{0x6f0701c0,0x629ab6c4,0x98bc8110,0x38c3826b}}, // _lijc, _zoto, tuvą_, _dèrè_,
+ {{0x7ea0016d,0x68e984dc,0x26deda63,0x629ada64}}, // _köpt, mled, boto_, _yoto,
+ {{0x65c59383,0x68e9a862,0xddc590ef,0x24895a65}}, // _обла, lled, _обли, _enam_,
+ {{0x81e600c8,0x91e32885,0x09e30139,0x35fb82e3}}, // বেন_, воре, ворн, _ارشد_,
+ {{0x2a6a0065,0x60cd342c,0x20fa0a0d,0x6f0f2c5b}}, // sebb_, gnam, ्देश_, _bucc,
+ {{0x6f0f35ae,0x65690267,0x395800eb,0x63a00106}}, // [5080] _cucc, _ćeha, strs_, ämnd,
+ {{0x2489003a,0xee3a0110,0x290c1313,0x68e9800d}}, // _znam_, іне_, ídas_, hled,
+ {{0xd00a3de7,0xed5707a1,0xd82611d2,0x60264c54}}, // _мене_, дох_, _одни, _одна,
+ {{0x6e2e5a66,0x68e9813c,0x2eaa809a,0x60cd00f7}}, // _webb, jled, _छत्त, cnam,
+ {{0x41c383eb,0x68e9bb28,0x99878390,0xb05b1cab}}, // _वेबस, dled, vanš_, _tjär,
+ {{0x2ca6a699,0x6d4300f7,0x60c4254a,0x7d0e022c}}, // skod_, ánac, liim, _tubs,
+ {{0x63a2ba7f,0x629aa6de,0xde030676,0x6f0f0081}}, // syon, _voto, _апри, _zucc,
+ {{0x68e9805c,0x88bc801b,0xa77484fa,0x7cff00eb}}, // gled, _hvěz, елич, _tūri,
+ {{0x4426afe0,0x629aa698,0x26de822e,0xb4bc816f}}, // mbo_, _toto, toto_, ेची_,
+ {{0x60cd1807,0x4426da67,0x7e9880f7,0x60c401b4}}, // znam, lbo_, سنجر_, hiim,
+ {{0x68e9da68,0x26deb383,0x60c45a69,0x60cd5a6a}}, // bled, roto_, kiim, ynam,
+ {{0x4426bdc2,0xb4d9000f,0x26de948e,0xb87b0091}}, // nbo_, ़के_, soto_, _akín,
+ {{0x80c1104f,0x26dea726,0x78a30087,0x25088019}}, // रोफे, poto_, _învi, _کرتی_,
+ {{0xe7e2000f,0x45d48572,0x26de81b4,0x68e0913b}}, // _खड़ा_, _полс, qoto_, lomd,
+ {{0x31c40a29,0x6f0f3206,0x442697ea,0x60cd5a6b}}, // вств, _succ, kbo_, tnam,
+ {{0x2d5880e7,0x24895a6c,0x61e8d275,0x60cd31c2}}, // mée_, _unam_, _mydl, unam,
+ {{0x4426da6d,0x2d5882be,0x2ca40106,0xa3c2073c}}, // [5090] dbo_, lée_, ömde_, ्पण_,
+ {{0x05eb0003,0x60cd5a6e,0x44269262,0x7ead80e1}}, // офеи_, snam, ebo_, _kúpa,
+ {{0x2d5883d3,0x403504db,0x7ae509da,0x68e9da6f}}, // née_, _пенс, _ikht, yled,
+ {{0x6d5a8364,0x4426da70,0x3f820699,0xb4bd01ab}}, // itta, gbo_, _frku_, _इगो_,
+ {{0x291e8528,0x27ff0036,0x2d588036,0x6ef8019d}}, // _atta_, _àun_, hée_, ụbar,
+ {{0x61e8827f,0xef1f02d0,0x4426b3c3,0xa3c20beb}}, // _bydl, _itü_, abo_, ्पत_,
+ {{0x4426ca9a,0x68e9da71,0x387900fc,0x6d5ada72}}, // bbo_, tled, _kasr_, jtta,
+ {{0x2d5880e7,0x2aad04e8,0xd7ca8035,0x69d70609}}, // dée_, ržby_, ापंच, _mxxe,
+ {{0x68e9800b,0x443f803a,0x68fb89ec,0x41df873c}}, // rled, _idu_, rmud, _पुरस,
+ {{0xf1a79fb4,0x7d09da73,0x2d7e82a6,0x6d5a9388}}, // _орен, _hies, _bċe_, ftta,
+ {{0x7d0991d4,0x35a5a597,0x2d5880e7,0x6cc601e2}}, // _kies, ханг, gée_, ейна,
+ {{0x7cf61e1e,0x7c2d808b,0x7fd58084,0x443f928a}}, // káre, ðarh, _пілі, _jdu_,
+ {{0x7d09da74,0x7655009a,0x6d5a853b,0x386dc767}}, // _mies, _oczy, atta, meer_,
+ {{0x386d80c9,0x78a9d043,0x7cf625ce,0x44268824}}, // leer_, lkev, dáre, zbo_,
+ {{0x6d5abfc7,0x2d5880e7,0xddc3c573,0x60c43236}}, // ctta, cée_, _hanž, tiim,
+ {{0xef1a039d,0x7d09da75,0x7afc0503,0x78a9b1c2}}, // зма_, _nies, smrt, nkev,
+ {{0x60c40ec8,0x068601a1,0x2b4600f7,0x645600b4}}, // [50a0] riim, нган, _íoc_, _icyi,
+ {{0xddc3803e,0x386d9e94,0x60c45a76,0x443fc0bb}}, // _manž, heer_, siim, _adu_,
+ {{0x386d867f,0x443235db,0x78a983ba,0x6da318d1}}, // keer_, _key_, kkev, лифа,
+ {{0x6b840370,0x26c5bc47,0x3f820b67,0xb4bc816f}}, // _krig, hilo_, _trku_, ेचे_,
+ {{0x7d09da77,0x386dda78,0xaca3801c,0x07a34688}}, // _dies, deer_, _giọn, _баян,
+ {{0x443205b4,0xc3330051,0x4426da79,0x6d5aaf9c}}, // _ley_, רוג_, sbo_, ytta,
+ {{0x7d0986a5,0x2bc080d4,0x044680e8,0x09068162}}, // _fies, _शेरा, _певн, нчим,
+ {{0x386d9aee,0x2d5882be,0xa3c2016f,0x7d09da7a}}, // geer_, vée_, ्पा_, _gies,
+ {{0x26c58098,0x3f829d9a,0x68e3009f,0x68e0b45e}}, // filo_, íku_, _índe, romd,
+ {{0xc6f793cd,0x61e880f2,0x2d5882be,0x629e5a7b}}, // нных_, _tydl, tée_, _nopo,
+ {{0xceb30bea,0x386d90f4,0x2f16006a,0x6b845a7c}}, // רית_, beer_, _læg_, _arig,
+ {{0x2d5883d3,0x61e48214,0x6d5ac530,0xac1900e8}}, // rée_, ğild, rtta, ьому_,
+ {{0x2d5883d3,0xdcf58063,0x60c2da7d,0x629e5a7e}}, // sée_, _urzą, _imom, _bopo,
+ {{0xda1085b3,0x629e5a7f,0x2d9884d6,0x2d588036}}, // ाशित_, _copo, øres_, pée_,
+ {{0x81e28a49,0x629e5a80,0x6440a101,0x6b845a81}}, // নের_, _dopo, _odmi, _erig,
+ {{0x6b840f3b,0xb4da800d,0x61e58192,0x2e46826b}}, // _frig, ठको_, ühli, _fífà_,
+ {{0x7d09900a,0x6b8430c6,0xf77300d5,0x84640098}}, // [50b0] _ries, _grig, داز_, _съще,
+ {{0x443f8392,0x7ae500dd,0xf1b0856b,0x752d809a}}, // _sdu_, _ukht, जनान, _łazi,
+ {{0x7d09da82,0x387901ac,0x60c2ab5f,0x753a94cf}}, // _pies, _tasr_, _omom, nstz,
+ {{0x6b840144,0x7cf61727,0x38b18061,0xef1f06ae}}, // _yrig, márc, _márc_, _ttü_,
+ {{0x7d09da83,0x889a0bea,0x68ed55ee,0x386d85f8}}, // _vies, _חברי, mlad, veer_,
+ {{0x386d837a,0x7d09da84,0x290b01a1,0x236900b9}}, // weer_, _wies, _hica_, _mpaj_,
+ {{0x7d099e1f,0x26c5935a,0x386d837a,0x68ed5a85}}, // _ties, vilo_, teer_, olad,
+ {{0x443fda86,0xb7f514fc,0xd6c400ab,0x290b5a87}}, // _udu_, _आराम_, ্তৃপ, _jica_,
+ {{0x386dba61,0x290b5a88,0x68ed5a89,0x4432020a}}, // reer_, _mica_, ilad, _rey_,
+ {{0x290b003b,0x386d92af,0xa68299b8,0x38b514a2}}, // _lica_, seer_, альд, _hård_,
+ {{0x68ed1385,0x26c5be2a,0x19ab97ae,0x249f8176}}, // klad, rilo_, _этап_, _koum_,
+ {{0x629e0511,0x26c5bb4f,0x7cea81fa,0x68ed0efa}}, // _sopo, silo_, týri, jlad,
+ {{0xeb971bba,0x629e378d,0x2d988bc5,0x6d48da8a}}, // вия_, _popo, ører_, arda,
+ {{0xc3328039,0x61e48214,0x7cf8b49a,0x443201b4}}, // שום_, şile, míre, _wey_,
+ {{0x20563d5a,0x68ed5a8b,0x291301df,0x6cda1194}}, // ктар, flad, _cuxa_, _офис_,
+ {{0x2055116b,0x7ae3da8c,0xe3b2003d,0x290b330c}}, // лтур, hont, _نرخ_, _cica_,
+ {{0x629e5a8d,0x290b5a8e,0xb2261860,0x15f402f1}}, // [50c0] _topo, _dica_, _импл, _अरहर_,
+ {{0x68ed5a8f,0x3f8013bd,0x248d9b09,0x386011ee}}, // alad, rviu_, _anem_, sfir_,
+ {{0x200f8698,0x290b5a90,0x7ae38822,0x672405f3}}, // _oggi_, _fica_, dont, spij,
+ {{0x67242948,0x68ed0fba,0x673bcbf4,0xb7340019}}, // ppij, clad, ksuj, _مہرب,
+ {{0x7ae3da91,0x2bc8016f,0xdbcc8118,0x68e45a92}}, // font, रपटा, _póño, moid,
+ {{0x7ae38aa8,0x248dda93,0x29000009,0xe73704ae}}, // gont, _enem_, lmia_, _јер_,
+ {{0x2913041c,0xa9678195,0x2ab40799,0x290b0a03}}, // _xuxa_, вија_, _gäbe_, _yica_,
+ {{0x7e7c5a94,0x7cf881df,0x68e45a95,0x64a39f72}}, // _harp, fíre, noid, _каса,
+ {{0xb50c09a3,0x64a389c7,0x1306853b,0xdca38493}}, // _संजय_, _тата, _азам, _тати,
+ {{0xa3e605b3,0x3c320142,0x7ae39ee5,0x68e407b8}}, // _बुध_, _được_, cont, hoid,
+ {{0x68ed3843,0x6d489de6,0x68e45a96,0x2ab08032}}, // ylad, urda, koid, _gàba_,
+ {{0x249f07ca,0x7e7c002a,0x68e40198,0x7cf881d0}}, // ðum_, _larp, joid, bíre,
+ {{0xe0b78158,0xdb580d70,0x6b8280e5,0x7f4983a8}}, // _בלאט_, кюр_, nvog, creq,
+ {{0x68ed02c1,0x290b10e4,0x1ddd816f,0x29005a97}}, // wlad, _sica_, _नुकत, emia_,
+ {{0x79a4117e,0xb50c00c2,0xf1a40a08,0x623412b2}}, // арте, _संचय_, артн, ресу,
+ {{0x38b1d7bb,0x645504ae,0x6b828140,0x6f0d80e5}}, // _kára_, _свиђ, kvog, _èacc,
+ {{0x44395a98,0x68ed5a99,0x57f5079e,0xc6f90329}}, // [50d0] mas_, rlad, упат, енах_,
+ {{0x68ed386d,0xa3c2146d,0x7d0d2b36,0x29130118}}, // slad, ्पर_, _hias, _tuxa_,
+ {{0x68ed5a9a,0xe7e3000d,0x7d0d022e,0x7e7c031d}}, // plad, _कुरा_, _kias, _darp,
+ {{0x44390f29,0x73d994f6,0x442b151e,0x78b60110}}, // nas_, ндер_, nbc_, chyv,
+ {{0x44395a9b,0x7d0d0063,0xe3b080a0,0x7ae3ad71}}, // ias_, _mias, يره_, tont,
+ {{0x44394250,0x7ead80f7,0xddce0019,0x78ad0254}}, // has_, _cúpl, _ebbő, lkav,
+ {{0x44391db4,0x260212ee,0xceb200be,0x2d9c0edd}}, // kas_, वेदी_, _פיל_, øvet_,
+ {{0x4439252a,0x7ae3da9c,0x6e355a9d,0x7e7c0c43}}, // jas_, sont, _hezb, _zarp,
+ {{0xe50a0104,0xa6e58162,0x7ae3da9e,0x0fca809a}}, // _mặt_, лжил, pont, ापगढ,
+ {{0x63a48009,0xc7b98019,0x1ab795bc,0xe3b0853d}}, // äine, rző_, _अतिथ, _برف_,
+ {{0x64a619b8,0x3cca091e,0x78ad3ae2,0x60559a3c}}, // гана, елно_, kkav, _مناط,
+ {{0x44395a9f,0x7d0d5aa0,0xb4de016f,0x38b18b4e}}, // gas_, _cias, तके_, _fára_,
+ {{0x889a812a,0xf7459dc7,0x387fd1ee,0x60c9d96b}}, // _רבני, _село, mdur_, miem,
+ {{0x387f8125,0x7f3a810f,0x60c98791,0x3866daa1}}, // ldur_, _טענו, liem, _zbor_,
+ {{0x44395aa2,0xa06a1a1a,0x1a6602e3,0x3916003d}}, // bas_, нава_, نیکی_, اورز,
+ {{0x443907a3,0x387fdaa3,0x7d0d0420,0x2d5c06a5}}, // cas_, ndur_, _gias, víe_,
+ {{0x6e351487,0xa4fb0039,0x0e6600c4,0x44205413}}, // [50e0] _bezb, _אלקט, лкан, _ifi_,
+ {{0x7c364c6c,0x7643808e,0x387d8114,0x42560162}}, // _heyr, _jdny, _lawr_, _стит,
+ {{0xa9a60d9e,0x6e350087,0x60c99c28,0x7d0d022c}}, // _синд, _dezb, kiem, _yias,
+ {{0x6d5e5aa4,0x6adc8a27,0x7c360326,0x387d8114}}, // rtpa, मक्र, _jeyr, _nawr_,
+ {{0x7e7c0b71,0xfaa6a84d,0x387fcc6c,0x44200300}}, // _tarp, ладо, ddur_, _mfi_,
+ {{0x44395aa5,0x38b5035f,0x6b82daa6,0x442001e0}}, // zas_, _våre_, rvog, _lfi_,
+ {{0x6722c5f4,0x81e600c8,0x38b18b4c,0x44202b5f}}, // _stoj, বের_, _sára_, _ofi_,
+ {{0xb4de000d,0xa96780f7,0xfce6806d,0x3f868722}}, // तको_, اميم_, _собо, _prou_,
+ {{0xdfd080f7,0x2d87920b,0x7d0d0e6c,0xddcf811f}}, // تية_, _arne_, _rias, _češa,
+ {{0x44395aa7,0x44204a34,0x7d0d4bad,0x3f86879f}}, // was_, _afi_, _sias, _vrou_,
+ {{0xa9269b10,0x2d87803b,0x387d831d,0x8b03801b}}, // _сдел, _crne_, _fawr_, ířen,
+ {{0x60c9daa8,0xd5b797d4,0x3f86daa9,0xab5b0061}}, // ciem, усь_, _trou_, nzüg,
+ {{0x44392d2a,0x2d87daaa,0x9990012b,0x7c3610ea}}, // ras_, _erne_, _čaše_, _deyr,
+ {{0xbda688ca,0x78a29c1d,0x3ce69d43,0x8c3d8085}}, // _محبو, _hoov, hoov_, _xoşb,
+ {{0x7d0d01c0,0x2b40803e,0xc693807c,0x7cf65aab}}, // _tias, šich_, _לאס_, márn,
+ {{0x7cf603fb,0x6e3adaac,0x443904b7,0x7d028c83}}, // pára, latb, qas_, jmos,
+ {{0x96350e0b,0xe50a0028,0x611c007a,0x81e60264}}, // [50f0] инац, _vặt_, _včla, বেল_,
+ {{0xd251019f,0x78ad5aad,0x7cf60e93,0xda190ebf}}, // _بند_, skav, nárn, _दलित_,
+ {{0x7ae7003a,0x2d870df0,0x6e35025b,0xdd1e016b}}, // mojt, íne_, _vezb, _píše,
+ {{0x81cd80c8,0x611c0eef,0x387f8201,0x52dd903e}}, // রথম_, _učla, xdur_, यव्स,
+ {{0x60c9bdc9,0x2d9c0aa2,0xa85604de,0x7cf6026f}}, // viem, øver_, _דירה_, kárn,
+ {{0xddda8065,0xae048ebf,0x38a702f1,0x6925a5af}}, // hető, रधान_, _võru_, амка,
+ {{0x746b9980,0x60c9bbd9,0x59df8074,0xa5f80ba1}}, // _прав_, tiem, _पुखर, _беру_,
+ {{0x6aa180ed,0x52d20fb8,0xe7f08fb2,0xcf458037}}, // _wolf, _सद्स, _चरखा_, аний,
+ {{0x44205aae,0x60c9daaf,0x6e3a826c,0x387fd96d}}, // _rfi_, riem, fatb, rdur_,
+ {{0x4d658dc8,0x60c9dab0,0xb05b016d,0x7c361d5a}}, // аков, siem, _smäl, _seyr,
+ {{0xcc8a003d,0x9962816b,0xd9998290,0x60c9dab1}}, // ننده_, píše_, _جنات_, piem,
+ {{0x38589a37,0x7c360085,0x6b89ac00,0x2d8782d4}}, // _مشهد_, _qeyr, _kreg, _vrne_,
+ {{0x6e3a8420,0x7c290b5a,0x7643808e,0x290f81b4}}, // batb, ñera, _tdny, _iiga_,
+ {{0xceb8809a,0x7643806a,0xab272503,0x290f82c4}}, // chę_, _udny, рора_, _higa_,
+ {{0xe2972e15,0x7c360428,0x81138264,0x61ed89ab}}, // шар_, _teyr, _সবুজ_, ƙalu,
+ {{0x290f96ea,0xdd2f001b,0xa01b18b6,0x3ce68282}}, // _jiga_, _běžn, _snör, xoov_,
+ {{0xe8012e2b,0xc87f02af,0x1fa71cd5,0x7cf600e1}}, // [5100] लेला_, _daß_, акти_, láro,
+ {{0x645b9727,0xda660396,0x27f28163,0x63ab8338}}, // _acui, _твои, _øyne_, dygn,
+ {{0x7cf603b0,0x2480022c,0x6b89dab2,0x290f8216}}, // náro, _kaim_, _areg, _oiga_,
+ {{0xb4c42cdd,0x6b8992ed,0x232aa748,0x248000dd}}, // _एगो_, _breg, води_, _jaim_,
+ {{0x93778013,0x6b89dab3,0x24800282,0xf74604ae}}, // لصور_, _creg, _maim_, _њего,
+ {{0x78a28006,0x3ce681c5,0xddda8019,0x24800282}}, // _soov, soov_, zető, _laim_,
+ {{0x7cf603c1,0x6b898314,0x290fdab4,0x6e3a8061}}, // várn, _ereg, _biga_, vatb,
+ {{0x645b8013,0x7afd80eb,0x7cf8801b,0x63ab8edd}}, // _gcui, _īste, bíra, bygn,
+ {{0x290fb736,0x7c2ddab5,0x7cf60c83,0x6569026c}}, // _diga_, ñare, tárn, _ćehi,
+ {{0x4aaa85fc,0x290f8357,0x27ef8106,0x1b1d8264}}, // _कविव, _eiga_, _dygn_, _নিচে_,
+ {{0x7cf6026f,0x7cfd282c,0x290f8b57,0x2480123c}}, // rárn, hére, _figa_, _baim_,
+ {{0x290f84be,0x55770158,0x7cf60019,0x49110074}}, // _giga_, בעטן_, sárn, _दूगो_,
+ {{0x248001e9,0xe8d78039,0xa2bd8a27,0xbbd12730}}, // _daim_, _דואר_, वोक्, सपेक,
+ {{0x290f8133,0x7cfd0036,0x61ed8326,0x175783de}}, // _ziga_, dére, ƙalt, _כסדר_,
+ {{0x7ae700f1,0x6ac49885,0x4de6016f,0x290fdab6}}, // rojt, _वगैर, _जुलै_, _yiga_,
+ {{0x7cfd02be,0x2d850118,0x290f0f02,0x290f8df6}}, // fére, _álei_, öga_, _xiga_,
+ {{0x69de3f55,0x7d1881c0,0xb8cf03ca,0x7ae7016b}}, // [5110] _expe, _huvs, _कव_, pojt,
+ {{0x26cc9fd1,0x6b89820f,0x7c3d0387,0x7f4d0980}}, // nido_, _rreg, lasr, rraq,
+ {{0x68e3157a,0x641581a8,0xaadc01c6,0xafdb2419}}, // _índo, مواط, _בחזר, _idøm,
+ {{0x40950013,0x8c3d861c,0x2480022c,0x88bc81d0}}, // _الحر, _boşa, _xaim_, _stěn,
+ {{0x26cc8637,0x7cf65ab7,0x290fdab8,0x21258101}}, // kido_, záro, _riga_, _stlh_,
+ {{0x60cd4079,0x290f80a2,0x26cc86a5,0xe50a0129}}, // liam, _siga_, jido_, _lặp_,
+ {{0x26cc8510,0x290f8245,0x272380e1,0x7cf6002a}}, // dido_, _piga_, kčný_, xáro,
+ {{0x60cd1c28,0x6b89820f,0x3949c589,0x2602016f}}, // niam, _treg, čas_, वेळी_,
+ {{0x6b89dab9,0x443ddaba,0x290f82f1,0xea0000ff}}, // _ureg, maw_, _viga_, _thầu_,
+ {{0x443dd2fb,0xe8e00028,0x7cf60065,0x7ead81ac}}, // law_, _đọc_, táro, _kúpi,
+ {{0x290f8812,0x60cd01e2,0x3cff8069,0x248001c0}}, // _tiga_, kiam, _khuv_, _paim_,
+ {{0x6459dabb,0x62819b7b,0x7cf65abc,0x443d8948}}, // ngwi, _halo, ráro, naw_,
+ {{0x26cc9984,0x60cd15a4,0x24805abd,0x68e9b986}}, // bido_, diam, _vaim_, moed,
+ {{0x26cc9313,0x443ddabe,0x26c79083,0x68e98114}}, // cido_, haw_, _umno_, loed,
+ {{0x6281a6fd,0x443ddaba,0x60cd5abf,0x24805ac0}}, // _malo, kaw_, fiam, _taim_,
+ {{0x7cfd07bc,0xe50a0028,0x6281dac1,0x443d81b9}}, // tére, _gặp_, _lalo, jaw_,
+ {{0x7c245ac2,0x2d8a10d3,0x98a30758,0xddd5007a}}, // [5120] _ifir, _srbe_, чите, _razš,
+ {{0x38b504b8,0x6281c724,0x68e98355,0x81e280ab}}, // _våra_, _nalo, hoed, নেট_,
+ {{0x99860013,0x60cd0098,0xc27b00be,0x6d41dac3}}, // _الجو, biam, _גריי, nsla,
+ {{0x26cc83a7,0x443d8948,0x7cfd272b,0x6281dac4}}, // zido_, gaw_, mérc, _aalo,
+ {{0x44270086,0x68e9831d,0x44220326,0x7cf60376}}, // _ən_, doed, ick_, járm,
+ {{0x26cc862f,0x6d41dac5,0x2480883d,0x62838267}}, // xido_, ksla, žim_, gdno,
+ {{0x443dc341,0x26cc9820,0x6281c000,0x7c242f00}}, // baw_, vido_, _dalo, _ofir,
+ {{0xa3b81299,0x6d4184fe,0x236984b7,0x68e9dac6}}, // चना_, dsla, ħajr_, goed,
+ {{0x26cc8510,0x291a4518,0x6281dac7,0x6d418cab}}, // tido_, _lupa_, _falo, esla,
+ {{0x6d419e75,0x7cf60065,0x60cd5342,0x877b03de}}, // fsla, sárl, ziam, נאיי,
+ {{0x26cc8510,0x7c2905e4,0x7cf8b37c,0x7c3b0789}}, // rido_, ñero, níro, ðuro,
+ {{0x6281885d,0x7c22b17d,0x7aea9384,0x2b40992c}}, // _zalo, ncor, loft, šicu_,
+ {{0x940301b6,0x26cc80a2,0x60cd3340,0x6e38bb65}}, // रेंच_, pido_, viam, _tevb,
+ {{0x2b448118,0x6281dac8,0x7c2f0118,0x7c24029a}}, // _mvmc_, _xalo, sbcr, _efir,
+ {{0x443d843b,0x60cd5ac9,0x291a33a5,0x7c2402a6}}, // yaw_, tiam, _cupa_, _ffir,
+ {{0x291a0012,0x7cf882b7,0x776401b9,0x26ca5aca}}, // _dupa_, díro, ftix, _ambo_,
+ {{0x60cd130f,0x7bda07d9,0xfc3f00f7,0x2aab813c}}, // [5130] riam, ştur, maí_, _købt_,
+ {{0x765a8110,0xfc3f0013,0xd5bb0071,0x60cd5acb}}, // ngty, laí_, лсе_, siam,
+ {{0x443ddacc,0x7c240125,0x443b00f1,0x3cffc796}}, // taw_, _yfir, _keq_, _phuv_,
+ {{0xfc3f0307,0x6b8d5acd,0xaca3801c,0xb4e380dc}}, // naí_, _krag, _miện, नके_,
+ {{0xeb99838d,0x6281dace,0x68e9837a,0x20d3802e}}, // рил_, _palo, voed, nţie_,
+ {{0xfc3f0013,0x443d95d0,0x38695acf,0x7c22c9d7}}, // haí_, saw_, lfar_, acor,
+ {{0x442200f2,0x443ddad0,0x3cff822c,0xaa5684ae}}, // yck_, paw_, _thuv_, цију_,
+ {{0x7c22dad1,0x386900f7,0x81e600ab,0x7c3b008b}}, // ccor, nfar_, বেক_, ðurl,
+ {{0x6449c395,0x6281d089,0xef1691e9,0xfc3f00f7}}, // _idei, _talo, омы_, daí_,
+ {{0x7c3bdad2,0x823489d7,0xee39867c,0x6d419412}}, // _heur, _فرما, шно_, tsla,
+ {{0x6b8d14a3,0x7cd20029,0x7c3bdad3,0x6da595fe}}, // _arag, kārt, _keur, цина,
+ {{0x291a0886,0x6b8d51aa,0x44225ad4,0xfc3f01a8}}, // _rupa_, _brag, uck_, gaí_,
+ {{0x7c3bb92c,0xe7399980,0x6d41a0e4,0x2d98816d}}, // _meur, _тел_, ssla, ärer_,
+ {{0x291a0091,0x7c3b83d3,0x6b8d079a,0x6d4190aa}}, // _pupa_, _leur, _drag, psla,
+ {{0xed59b10c,0x6b8d011e,0x38695a05,0x64a64b60}}, // _той_, _erag, ffar_, _мака,
+ {{0x7c3bdad5,0xb4e383bb,0x7c2281df,0x6b8d4e3d}}, // _neur, नको_, xcor, _frag,
+ {{0xee3a05f1,0x3ea788c2,0xc31f80ab,0x20d3802e}}, // [5140] ина_, _kont_, নীতি_, cţie_,
+ {{0x6449831d,0x291a11ca,0x7cfd03e6,0x7764002a}}, // _adei, _tupa_, méra, rtix,
+ {{0x7c3ba088,0x6e3e5ad6,0x77645ad7,0x3ea7cf42}}, // _beur, rapb, stix, _mont_,
+ {{0x61fe4102,0x7c3bdad8,0x2fc05ad9,0x672d01c0}}, // _izpl, _ceur, pzig_, bpaj,
+ {{0x7c22dada,0x7cfd03d3,0x7c3bb99a,0x75248052}}, // rcor, néra, _deur, _čizm,
+ {{0x7c22dadb,0x7cf883b0,0x6d5701df,0x68f65adc}}, // scor, píro, buxa, llyd,
+ {{0x44248118,0x7c3bcd10,0x7cfd39c6,0x7aea92ec}}, // _tfm_, _feur, héra, roft,
+ {{0x6e3c10f5,0x8f468c3e,0xddd8826f,0xbebb00f1}}, // _herb, оход, _navš, _atëh,
+ {{0xd6da8021,0x629e8364,0x24868bb1,0x7ead92ca}}, // ито_, öpos, mdom_, _lúpu,
+ {{0x3ea78012,0x2366b592,0x7cfd26ae,0x3eb8bb69}}, // _cont_, mtoj_, déra, órt_,
+ {{0xfc3f0307,0x691583bb,0x3ea78220,0x6e3c5add}}, // taí_, _všec, _dont_, _merb,
+ {{0x2486afbc,0xef1f0192,0x92da00ab,0x672d0282}}, // ndom_, _früh_, ়তো_, ypaj,
+ {{0x3ea781b3,0xfc3f0013,0x2bc5035a,0x395800e7}}, // _font_, raí_, _लेखा, eurs_,
+ {{0x6b8d02d8,0xfc3f0013,0xd6d9809a,0x6e3c5ade}}, // _vrag, saí_, kuły_, _nerb,
+ {{0x298815b5,0x2486dadf,0xfc3f01a8,0xca140081}}, // осто_, kdom_, paí_, ддръ,
+ {{0x7cfd232b,0xa3e6086a,0x27208242,0x92bd00ab}}, // béra, _बुक_, _kòn_, েকে_,
+ {{0x6e3c010b,0x38695ae0,0xb4e700c2,0x7c3b9c8f}}, // [5150] _berb, rfar_, बकी_, _reur,
+ {{0x7c3b8364,0x2720dae1,0x612787d9,0x6e3c0114}}, // _seur, _mòn_, _yıld, _cerb,
+ {{0x628502c4,0x6e3c2ad1,0x7c3bdae2,0x6fdd06af}}, // _iaho, _derb, _peur, _यशवं,
+ {{0x645d04bf,0x7d1c3b19,0x248682fe,0x6abaa37e}}, // ngsi, _durs, gdom_, _altf,
+ {{0x62851efa,0x7c3b87e2,0x889a0039,0xd37b9a19}}, // _kaho, _veur, _הברי, рче_,
+ {{0x7d1c5ae3,0x6e3c2bee,0x63a0016d,0x29090198}}, // _furs, _gerb, ämni, lmaa_,
+ {{0xf2d40158,0x628522da,0x7c3b8a48,0x3ea798e8}}, // ועס_, _maho, _teur, _ront_,
+ {{0x3ea786e3,0x6e3c011e,0x6d452e14,0x38bc00f7}}, // _sont_, _zerb, msha, _tíre_,
+ {{0x27208104,0x6d4502a3,0x3ea7dae4,0x6e3c5ae5}}, // _còn_, lsha, _pont_, _yerb,
+ {{0x6285000d,0x92b780d5,0x7cf65ae6,0x260202f1}}, // _naho, _بحرا, mári, वेकी_,
+ {{0x6d455ae7,0x7cf631b0,0x3ea7dae8,0xbec301a9}}, // nsha, lári, _vont_, _ķīli,
+ {{0x7f4400e7,0xf2c900be,0x7cff0110,0x7cfd2d5d}}, // ysiq, _גע_, _kūry, téra,
+ {{0x7cf60907,0x62855ae9,0x68ed0101,0x2720801c}}, // nári, _baho, doad, _gòn_,
+ {{0x39585aea,0xe0df0032,0x6d4527d1,0xdd0100eb}}, // turs_, _ajò_, ksha, ītāj,
+ {{0x6285132b,0x272086c0,0x7641daeb,0xdddc0087}}, // _daho, _zòn_, maly, _marţ,
+ {{0x6f1d2a63,0x6e3c5aec,0x6d453e5b,0x7cfd5aed}}, // _busc, _serb, dsha, péra,
+ {{0x6e3c369d,0x7d1c5aee,0x6f1d00e5,0x6f15441b}}, // [5160] _perb, _surs, _cusc, _bizc,
+ {{0x1fb5035f,0x6f1d0004,0x7cf65aef,0x226501d6}}, // єстр, _dusc, dári, ľský_,
+ {{0xc27b004c,0x6e3c2fbc,0x68ed04c3,0x7f4400e7}}, // _פרטי, _verb, boad, ssiq,
+ {{0x6e3c02af,0x13b000ab,0x6285141f,0x6f1d5af0}}, // _werb, _চেয়, _zaho, _fusc,
+ {{0x6e3c045c,0x2486daf1,0x6aa88668,0x7d043f66}}, // _terb, rdom_, _podf, _mhis,
+ {{0x7d1c02a5,0x3f8202c1,0x66c0016d,0x46c01513}}, // _turs, _isku_, _sökf, _एतिह,
+ {{0x386b00f7,0x6f1d01ec,0x60dbd245,0xfe55004e}}, // _éirí_, _zusc, fnum, _زینب_,
+ {{0x78a4003a,0xfaff00f1,0x4ddb0039,0x23668168}}, // njiv, jtën_, _החלו, ptoj_,
+ {{0x4426daf2,0x2ca90039,0x289b03c8,0x7cf609b2}}, // mco_, _road_, מיקא, cári,
+ {{0x4426bbb2,0x7d042e0b,0x7d1602d6,0x02ce0035}}, // lco_, _ahis, _aiys, होमभ,
+ {{0x62855699,0x69c38063,0xfaff0168,0x442680e5}}, // _raho, czne, ftën_, oco_,
+ {{0x7d045af3,0x44269ee5,0x62850461,0x6442daf4}}, // _chis, nco_, _saho, naoi,
+ {{0x68e326d5,0x78a40052,0xaf078077,0x7d042be4}}, // _índi, djiv, تقیم_, _dhis,
+ {{0x764a806a,0x64428c41,0x6f1d5af5,0xb05b0192}}, // _udfy, haoi, _rusc, _kläg,
+ {{0x6f1d0018,0x291e8483,0x68ed3fb0,0x62874818}}, // _susc, _juta_, toad, rdjo,
+ {{0x291edaf6,0x9ddb8039,0xe8b50214,0x7cfd00e7}}, // _muta_, _לקוח, _dışı, méro,
+ {{0x44295af7,0x291edaf8,0x20d38087,0x6f150661}}, // [5170] _ifa_, _luta_, nţia_, _pizc,
+ {{0x6d450234,0x7cf60c83,0x290907b8,0x80c1170c}}, // tsha, vári, smaa_, रोजे,
+ {{0x6f154012,0x7c2d8511,0x3f820102,0x6e250192}}, // _vizc, ñaro, _esku_, rchb,
+ {{0xdd8680d5,0x7cf609e9,0x7bf98dae,0xe45f0106}}, // _یو_, tári, онер_, ngö_,
+ {{0x6d455af9,0x2d9c016d,0x6d5a94c7,0x68e483f2}}, // ssha, äver_, huta, čidl,
+ {{0x7cf60073,0x28ca8d86,0x4426dafa,0x1ee71e29}}, // rári, ़ोसि, aco_, _روزی_,
+ {{0x6d5ada65,0xaca381bc,0x7641dafb,0x7cf65afc}}, // juta, _akụn, valy, sári,
+ {{0x4426dafd,0x7cfd0b2c,0x291e859e,0x6d5adafe}}, // cco_, déro, _duta_, duta,
+ {{0x3eaa5aff,0x6d4ea35e,0xf70880ff,0x69c3900b}}, // _yobt_, ában, _hủy_, szne,
+ {{0x44295b00,0x7d044e0e,0x443f809f,0x644d5b01}}, // _afa_, _shis, _heu_, _adai,
+ {{0xb05b00f2,0x6d5ad20b,0x443f8970,0xa50a15b7}}, // _kläd, guta, _keu_, жена_,
+ {{0x443f83d3,0x2d9102a5,0x41748154,0x3f8f8805}}, // _jeu_, _mrze_, _سامس, _trgu_,
+ {{0x443fdb02,0xaca3801c,0xdddc1247,0x20d3802e}}, // _meu_, _chỉn, _marš, cţia_,
+ {{0x6d5abbc2,0x44295b03,0x442685a4,0x66c02522}}, // buta, _efa_, zco_, _köke,
+ {{0x7cfd087a,0x7d045942,0x3f8d8548,0x44295b04}}, // céro, _this, lveu_, _ffa_,
+ {{0x443f9f4c,0xe8fa05e9,0xf1b804b7,0x44269ee3}}, // _neu_, іла_, ġġ_, xco_,
+ {{0x2ab40006,0x98a6a0a7,0xf74327cb,0xa6860187}}, // [5180] _läbi_, žiće_, кето, млад,
+ {{0x6b84308f,0x2d910024,0x61e481cc,0x257e01d0}}, // _isig, _brze_, şili, _půl_,
+ {{0x329b0039,0x1a9b0039,0x4426db05,0x201e0087}}, // _וביד, _וייע, tco_, _ştiu_,
+ {{0x291e91b9,0x44268272,0x2d9102a5,0x386d80f3}}, // _ruta_, uco_, _drze_, jfer_,
+ {{0x443f8931,0x7c3b007b,0x64428090,0x291edb06}}, // _deu_, ðuri, raoi, _suta_,
+ {{0x291e803b,0x7c298133,0x44269441,0x2bd2816f}}, // _puta_, _efer, sco_, _देणा,
+ {{0x443f87fa,0x7cfd01df,0x2d8c0317,0x7c298114}}, // _feu_, xéro, _éde_, _ffer,
+ {{0x6b84003a,0x52bb0f85,0x386054a8,0x2724016d}}, // _osig, _उत्स, ngir_, _kön_,
+ {{0x4fc681bb,0x291e8337,0x27240019,0x776992b7}}, // ьска, _wuta_, _jön_, gtex,
+ {{0x64408b67,0x6d5a80ad,0xc6f781bb,0x442901e0}}, // _hemi, tuta, мных_, _sfa_,
+ {{0x629a980b,0x6b845b07,0x2724016d,0x443f80ff}}, // _into, _asig, _lön_, _yeu_,
+ {{0xe57100be,0x7e7a8f33,0x3f8d8bda,0x409380f7}}, // אַך_, setp, bveu_, _للمر,
+ {{0x6d5a8665,0x6440848f,0x6288db08,0x764e009a}}, // suta, _memi, _kado, _odby,
+ {{0x6440db09,0xdd909e91,0x6288db0a,0x628aa25b}}, // _lemi, _موت_, _jado, ndfo,
+ {{0x6b845b0b,0x29068028,0x44290037,0xaf0781a8}}, // _esig, _khoa_, _tfa_, _تقوم_,
+ {{0x6440db0c,0xd788801c,0x2b498bcf,0xe45f1647}}, // _nemi, _kể_, šaci_, riös_,
+ {{0x443fdb0d,0x6d48db0e,0x7c29d681,0x569501e5}}, // [5190] _reu_, lsda, _rfer, наат,
+ {{0x443f907f,0x27240457,0x629a808e,0xceb898eb}}, // _seu_, _dön_, _nnto, nkę_,
+ {{0x443fdb0f,0x6d48a4de,0x6d5a016d,0x6440d068}}, // _peu_, nsda, _åtal, _bemi,
+ {{0x629adb10,0x6440db11,0x6288846d,0x7769810c}}, // _anto, _cemi, _aado, ytex,
+ {{0x6288be12,0x66c000f2,0x443f9209,0xf8bf077f}}, // _bado, _söke, _veu_, _ilé_,
+ {{0x6288db12,0x6d488186,0x29190010,0x248901c0}}, // _cado, ksda, _hisa_, _laam_,
+ {{0x6288db13,0x443fdb14,0x29195b15,0xceb88035}}, // _dado, _teu_, _kisa_, dkę_,
+ {{0x2489240d,0x629a9883,0x6440d14b,0x26dedb16}}, // _naam_, _ento, _gemi, ento_,
+ {{0x386ddb17,0x2369022c,0x6288d025,0xb275a0bf}}, // rfer_, _nqaj_, _fado, _плаш,
+ {{0xd7068364,0x6440db18,0x386d9e03,0x7ce80085}}, // нные_, _zemi, sfer_, zırd,
+ {{0xab5b0065,0x7769cb95,0x64409790,0xf8bf5b19}}, // szül, stex, _yemi, _olé_,
+ {{0x291927be,0x672f0042,0x0fd0816f,0x6440db1a}}, // _nisa_, _otcj, _तेवढ, _xemi,
+ {{0x7f5b8079,0x2cad9384,0x68fb82d0,0x2bba01a8}}, // quuq, _moed_, mlud, ساحة_,
+ {{0xc3329a0f,0xf8bf0091,0x290da596,0xdbcd1434}}, // רום_, _alé_, lmea_, róðr,
+ {{0x29192fc5,0xf652810f,0x2724016d,0x3d1882f1}}, // _bisa_, _עצה_, _sön_, _बूझे_,
+ {{0x7cf88510,0xb05b016d,0xf8bf324c,0x6f1886ec}}, // píri, _smär, _clé_, _zivc,
+ {{0x2919020f,0x64460274,0x38605b1b,0x27e602be}}, // [51a0] _disa_, maki, rgir_, çon_,
+ {{0x644644a4,0x291e0ece,0x212100b9,0x2cbf8144}}, // laki, ítas_, _fuhh_, _alud_,
+ {{0x6440859c,0x62888b48,0x2cbf826c,0x68fb93c2}}, // _pemi, _rado, _blud_, klud,
+ {{0x9346a28e,0x8fa60f52,0x64465b1c,0x29195b1d}}, // енде, _заме, naki, _gisa_,
+ {{0x6288db1e,0x7cf600e1,0x691584e8,0xceb8db1f}}, // _pado, lárs, _všel, ykę_,
+ {{0x64465b20,0x764e006a,0x7645008e,0x9258896b}}, // haki, _udby, cahy, факт_,
+ {{0x62889d69,0x64465b21,0x6440db22,0x66e34b47}}, // _vado, kaki, _temi, _сора,
+ {{0x2cad98db,0x6288a69f,0x6d4e8511,0x7e7e5b23}}, // _goed_, _wado, ábam, lepp,
+ {{0x6d5e5b24,0x93aa8b76,0xdefa8071,0x64465b25}}, // lupa, _عارف_, зын_, daki,
+ {{0x2489079f,0x6d48848d,0x59b202f1,0x26de847f}}, // _saam_, tsda, ीनार, unto_,
+ {{0xd5b78a08,0x68fb8052,0x61db8f21,0x998c8b80}}, // еся_, blud, यपृष, _hedž_,
+ {{0x68fb8051,0x7cf601ac,0xb87b026b,0x27e60035}}, // clud, dárs, _ajín, żony_,
+ {{0x29191cb1,0x60c4135e,0x5ef55b26,0x7e7e008b}}, // _risa_, dhim, ्तम्_, kepp,
+ {{0x2919059c,0x8f9b83c8,0x8caa83b7,0xfd748162}}, // _sisa_, _מידי, _झकझो, _алкэ,
+ {{0xa2b3836d,0x64461c88,0x291918de,0xf8bf00f7}}, // _अवस्, baki, _pisa_, _plé_,
+ {{0x6d5e0867,0xb5fb810f,0x76450359,0x60c41341}}, // dupa, _מלוכ, wahy, ghim,
+ {{0x29195b27,0x3f8683bb,0xf1b900eb,0xddc8db28}}, // [51b0] _visa_, _jsou_, ņš_, nedž,
+ {{0x798501e9,0xdd950009,0xb05b016d,0x212100dd}}, // _tshw, казы, _bläc, _tuhh_,
+ {{0x7cf65b29,0x76455b2a,0x29195b2b,0xe2a7007b}}, // párt, rahy, _tisa_, æður_,
+ {{0x2bd29d17,0xec158077,0x2919008e,0x7c2d0609}}, // _देहा, نواد, _uisa_, _jfar,
+ {{0x68fbdb2c,0x45188fd3,0x80a492c6,0x2cad8a53}}, // vlud, رآمد_, काने, _voed_,
+ {{0x6d5e12fb,0x64465b2d,0xeb969577,0xb05b3a53}}, // bupa, zaki, тиш_, _fläc,
+ {{0x6446015d,0x3f8686c4,0x6d5e1140,0x7ae48118}}, // yaki, _asou_, cupa, éita,
+ {{0xa3c1053e,0x2bd287e6,0x2919816d,0xddc88b80}}, // ंना_, _देवा, ösa_, fedž,
+ {{0x776d0b6e,0x442dd811,0x644606ee,0x5b15af03}}, // ntax, _ife_, vaki, _амат,
+ {{0x7c2d1b6d,0x64465b2e,0x68fbdb2f,0x1e9581cf}}, // _afar, waki, slud, трир,
+ {{0x64465b30,0x7d1bb006,0x7f498037,0x3eae8338}}, // taki, _kius, sseq, _doft_,
+ {{0x7c2d00f1,0x893480f7,0x2247874c,0x60c403ed}}, // _cfar, _إعلا, lank_, xhim,
+ {{0x442d8870,0x78a9ad61,0x7d098010,0x387f829c}}, // _mfe_, mjev, _mhes, meur_,
+ {{0x387fdb31,0x64465b32,0x4804835f,0x3eae81c0}}, // leur_, saki, _спів, _goft_,
+ {{0x442ddb33,0x44393a65,0x64465b34,0x60c45b35}}, // _ofe_, bbs_, paki, thim,
+ {{0x78a984c4,0x442dad10,0x8c3d8214,0x387fdb36}}, // njev, _nfe_, _koşu, neur_,
+ {{0x7643db37,0x2fc92483,0x442010ab,0x60c4208b}}, // [51c0] _keny, gzag_, _igi_, rhim,
+ {{0x60c45b38,0x6d5e3e21,0x06e380ab,0x442ddb39}}, // shim, tupa, _নীতি, _afe_,
+ {{0x76438458,0x2b4d9c8d,0x78bb84a2,0x629e5b3a}}, // _meny, _avec_, kkuv, _inpo,
+ {{0x6d5e0393,0x7643c954,0x7e7e1e41,0x7523930e}}, // rupa, _leny, sepp, _munz,
+ {{0x2bd2816f,0x7d1b8458,0x6d5e43e3,0xfbd290be}}, // _देशा, _dius, supa, _देशम,
+ {{0x442ddb3b,0x628e5b3c,0x442009ca,0x3866db3d}}, // _efe_, ndbo, _lgi_, _scor_,
+ {{0xe5a30b01,0xada31bf2,0x67228009,0x442d82ec}}, // _кири, _карл, _suoj, _ffe_,
+ {{0x7d1b8698,0x442d808e,0x61fa83f2,0x2b4d0bda}}, // _gius, _gfe_, _vytl, šeci_,
+ {{0x7643854f,0xaca38028,0xb4be809a,0x6f1c5b3e}}, // _beny, _giốn, इसी_, _kirc,
+ {{0x7cfd5b3f,0x38b5016d,0x7c2d5b40,0xb87b026b}}, // méri, _hårt_, _sfar, _ajíl,
+ {{0x7643db41,0x7cfd0e14,0xceb3012a,0xb8e880c8}}, // _deny, léri, ייז_, _ঈদ_,
+ {{0x6b9604fe,0x776d0085,0xc5f30039,0xb86580d7}}, // _bryg, ytax, ידת_, _ماهو,
+ {{0x44395b42,0x60c28280,0x3f86a41f,0x317a4b77}}, // rbs_, _ilom, _usou_, _appz_,
+ {{0x752381dc,0x76438205,0x6b965b43,0x26c580e1}}, // _funz, _geny, _dryg, chlo_,
+ {{0x7c3b0125,0x349523d7,0x60c2c9a2,0x7c2901df}}, // ðurs, _байр, _klom, ñers,
+ {{0x6b96006a,0x7c2d1fb6,0x764380b8,0x629e03ec}}, // _fryg, _ufar, _zeny, _enpo,
+ {{0x7d1b8098,0x7643a73f,0x7d09831d,0x2247808e}}, // [51d0] _rius, _yeny, _rhes, yank_,
+ {{0x776d5b44,0xfe701ddd,0x6f1c3206,0x7648bba1}}, // rtax, قدم_, _circ, nady,
+ {{0x776d5b45,0x60c2db46,0x387f80e7,0x6f1c5b47}}, // stax, _olom, yeur_, _dirc,
+ {{0x8c3d8162,0xf4e18264,0xaf3700d7,0x387f9a1f}}, // _noşt, বত্ব, _لرست, xeur_,
+ {{0x7cfd5b48,0x42d58221,0x387f902d,0x7d1ba590}}, // géri, ліку, veur_, _vius,
+ {{0xf990026a,0x91e58391,0x6d581e1e,0x69158084}}, // _ابی_, лоле, ávac, _išei,
+ {{0xfa78073a,0x387fa5d2,0x7d098051,0x60c293c2}}, // דעות_, teur_, _thes, _blom,
+ {{0x6fd483b7,0x7cfd5b49,0x752385ee,0x88bc928a}}, // _बेहू, béri, _runz, _stěr,
+ {{0x7643859c,0x6d5adb4a,0x78a9cb0f,0x387fdb4b}}, // _peny, erta, rjev, reur_,
+ {{0xb7bd802e,0x61278214,0x387f9030,0x6b89db4c}}, // ncţi, _yıll, seur_, _iseg,
+ {{0x04460ca0,0x78a98654,0xb87b0032,0x387f8036}}, // ленн, pjev, _ajím, peur_,
+ {{0x76439e45,0x3f4f0029,0x248d8580,0x7cf60c1d}}, // _weny, kļu_, _jaem_, tárq,
+ {{0x20248038,0xeb971194,0x765c8063,0x6d5ada85}}, // ásiť_, гия_, óryc, arta,
+ {{0x7cf60207,0x60c2a19f,0x7cc40019,0x644400b9}}, // rárq, _zlom, nőrz, _weii,
+ {{0x6d5a805c,0x2729c0d2,0x2d87805c,0x22459b9b}}, // crta, _hún_, _usne_, _melk_,
+ {{0x6b96070b,0x291d8397,0x272984be,0x26c3026c}}, // _tryg, _jiwa_, _kún_, _aljo_,
+ {{0x2729c6fe,0x3f4f00eb,0x48065b4d,0x6f1c01a9}}, // [51e0] _jún_, gļu_, упав, _pirc,
+ {{0x6da6028b,0xe5a613f1,0x629e02e6,0x2729db4e}}, // лиза, лизи, _unpo, _mún_,
+ {{0x6b89b1a3,0x2d960048,0xe45aced5,0x2729db4f}}, // _aseg, ырас, еже_, _lún_,
+ {{0x7cfd5b50,0xa34a8a13,0x5fd5016f,0x9b6a867c}}, // téri, езда_, _ठेवल, ешка_,
+ {{0x38b50448,0x68e402f7,0xb05b0884,0x2d87006a}}, // _vårt_, mnid, _klän, æner_,
+ {{0x60c2811f,0x29005582,0x929491f3,0x88c60264}}, // _slom, llia_, ракц, _এদিক,
+ {{0x272985b4,0x6b89db51,0x80b803eb,0xa9678992}}, // _aún_, _eseg, _इकाइ, гија_,
+ {{0x7cfd02be,0x92e480c8,0x6d5a8390,0x68e443cb}}, // péri, নতে_, vrta, nnid,
+ {{0x291ddb52,0xd90d803d,0x68e40114,0xceba8326}}, // _diwa_, ذیه_, inid, _liƙe_,
+ {{0x27299ca9,0x672610e1,0x557486f9,0x22458609}}, // _dún_, _mukj, агот, _gelk_,
+ {{0x76489c59,0x65618187,0x2900004f,0xd6db025f}}, // rady, gulh, klia_, תחיל,
+ {{0x27298324,0x76488c27,0x6d5a8c49,0x7ce8080a}}, // _fún_, sady, rrta, tıra,
+ {{0xaab88076,0x6d5a80d2,0xf09f001c,0xb05b0338}}, // _अवाक, srta, _giày_, _blän,
+ {{0x248d0b5d,0x66c000f2,0x291d804f,0x59a8881f}}, // žem_, _sökn, _ziwa_, _कपार,
+ {{0x7af51124,0x291d84b9,0x68e40114,0x6234004a}}, // rozt, _yiwa_, fnid, сесу,
+ {{0x29000698,0x5455132a,0x38351b67,0x95539a00}}, // glia_, _твит, рнар, _اخوا,
+ {{0x1ee7003d,0x7d0d5b53,0xe8940198,0x2d985b54}}, // [51f0] هوری_, _ihas, щать, _arre_,
+ {{0x68e430c9,0x290056b2,0xaca380ff,0x248d841c}}, // anid, alia_, _phỏn, _saem_,
+ {{0x7f4d0372,0x7d0d332e,0x2900574a,0x6b89db55}}, // ssaq, _khas, blia_, _sseg,
+ {{0x63b98063,0x20070201,0x29005b56,0xddc880e1}}, // tywn, ənin_, clia_, radň,
+ {{0x2d980065,0x03a58012,0x9098120c,0x66c00380}}, // _erre_, рило, _свят_, _kökl,
+ {{0x2729db57,0x291db353,0x7d0d09ab,0x26da3d22}}, // _rún_, _siwa_, _lhas, cipo_,
+ {{0x61150110,0x27298091,0x387900b9,0x63b9866f}}, // _sąly, _sún_, _kbsr_, sywn,
+ {{0x78ad135a,0x249f8789,0x224580f3,0x6b89bf73}}, // njav, _unum_, _welk_, _tseg,
+ {{0x644bdb58,0x6a6795e4,0x29038029,0x09d2016f}}, // magi, _مطال, ēja_, _सध्य,
+ {{0x644bdb59,0x4905800f,0x81bb00c8,0x3f99026c}}, // lagi, _हीरो_, _আইন_, _mrsu_,
+ {{0x7d0d5b5a,0x64a60196,0x78ad30a6,0x68e4057b}}, // _bhas, аана, kjav, ynid,
+ {{0x628387f1,0x2729877f,0xb05b016d,0x6561a38f}}, // leno, _tún_, _slän, rulh,
+ {{0x7d0d549e,0xee2ebb4a,0xf7459821,0x78ad0088}}, // _dhas, _мн_, _тело, djav,
+ {{0x644ba0f4,0x60c9db5b,0x6283db5c,0x13ba83de}}, // hagi, lhem, neno, דזשע,
+ {{0xd9465b5d,0x66c00106,0x6726008e,0x644bdb5e}}, // реби, _söko, _sukj, kagi,
+ {{0x61fe0efc,0x92d600c8,0x60c98009,0x6283db5f}}, // _vypl, িকে_, nhem, heno,
+ {{0x644bdb60,0x443202f7,0x62838978,0x7c243de5}}, // [5200] dagi, _ify_, keno, _igir,
+ {{0xee3719fe,0xfd4c8028,0xdcf9001b,0x60db81e8}}, // ину_, _nhiệ, _osvě, hium,
+ {{0x628380ce,0x14268b9c,0x4a5b0039,0x26da2256}}, // deno, адем, _בדיו, ripo_,
+ {{0x644bdb61,0x7cfd3a97,0x672600b9,0x26da5b62}}, // gagi, sérv, _tukj, sipo_,
+ {{0x60c9b543,0x20d38087,0x66c0011c,0xd4678087}}, // dhem, cţii_, _hökm, риче_,
+ {{0x6283a0b5,0xb87b046d,0xd954866e,0x27e08084}}, // geno, _ajíh, منتخ, _žino_,
+ {{0x644bdb63,0x6d5594b5,0x8c46872a,0x69ce5b64}}, // bagi, ázal, _вебе, nzbe,
+ {{0x7c245353,0xb4c503ca,0x7c2d8216,0xf4c100ab}}, // _ngir, _एते_, ñart, _উদ্ব,
+ {{0xc6a68dc9,0x4ea6bb17,0x6283db65,0x752701c0}}, // арни, арна, beno, _zujz,
+ {{0x7d0d1581,0x7c242b39,0xfaff00f1,0x7520826c}}, // _shas, _agir, ntët_, _himz,
+ {{0x7aed80e7,0x06970039,0x7cfd0061,0xd82f8012}}, // éate, בדים_, kért, _мэ_,
+ {{0x7d0282af,0xf8da83db,0x9abc81b9,0x66c48176}}, // nlos, _बगिय, _diċe, _dòka,
+ {{0x66e6c4e0,0xd0d3db66,0x4ab40b99,0x9f4981fa}}, // _кода, _بينظ, ुसंव, _áað_,
+ {{0x7d02a3cf,0x60c65b67,0x644bc7e7,0x7764023e}}, // hlos, _alkm, zagi, duix,
+ {{0x1fe880ab,0x644b8e8c,0x7d0d5b68,0x7d02db69}}, // _পরিস, yagi, _thas, klos,
+ {{0x26d85b6a,0x7d02817f,0x7ce8007e,0x7d0d5b6b}}, // _amro_, jlos, nırl, _uhas,
+ {{0x78ad5b6c,0x6283db6d,0x6d580a21,0xdcf201b9}}, // [5210] rjav, yeno, ávan, _żgħi,
+ {{0x6b8d02c1,0xed4d8117,0x6283802a,0x644bb722}}, // _isag, _تھی_, xeno, wagi,
+ {{0x63bd08bd,0x20c70665,0x62838304,0x644bb06f}}, // lysn, _võib_, veno, tagi,
+ {{0x77640722,0xfc3f5b6e,0xb05b0106,0x6283cbcd}}, // buix, nbí_, _kläm, weno,
+ {{0x644ba135,0x6283db6f,0x307a00be,0x76470282}}, // ragi, teno, _גארנ, _tejy,
+ {{0x78a2b6f8,0x644bdb70,0xa5f90a41,0x6b8d5b71}}, // _anov, sagi, _бегу_, _msag,
+ {{0x60c9805d,0x644bdb72,0x6915826f,0x44248915}}, // them, pagi, _ošet, _fgm_,
+ {{0x38692a4c,0x6283db73,0x9abc84b7,0x6b8d011b}}, // ngar_, seno, _riċe, _osag,
+ {{0xfd4c8028,0x60dbdb74,0x60c9836e,0x68fd02f7}}, // _thiệ, rium, rhem, _mksd,
+ {{0x4d659273,0x60dbdb75,0xddb581a8,0x78a2b7fd}}, // бков, sium, محجب, _enov,
+ {{0x6b8d5b76,0x60c9db77,0x60db8037,0xa294804a}}, // _asag, phem, pium, _фахі,
+ {{0xe1f09459,0xf41f0198,0xe7998a47,0xfce60087}}, // _غسل_, ydät_, _مختص_, сомо,
+ {{0x80a492ee,0x66c02522,0xfaff0168,0x2d8a8366}}, // कारे, _sökm, jtës_, æber_,
+ {{0x78a29807,0x216a0f27,0xa96a0adb,0x6d5cdb78}}, // _znov, лиги_, лига_, árac,
+ {{0xb05b0364,0xcd358077,0x20560071,0x6b8d5b79}}, // _eläm, _ارتب, йтар, _esag,
+ {{0x25718125,0x3869088b,0xfaff00f1,0x7c240435}}, // mál_, ggar_, ftës_, _ugir,
+ {{0xc7a30ada,0x03e280ab,0x543b00be,0x8fa30198}}, // [5220] питк, _বর্ণ, _געפא, пате,
+ {{0x7ce81010,0x394597fe,0x6d5e011f,0x528600f7}}, // zırl, _owls_, trpa, _الدك,
+ {{0x7d028067,0x2571a792,0x6b9bdb7a,0xb8ce00ab}}, // tlos, nál_, _arug, _কত_,
+ {{0x6b9b8ae1,0xab2aa5b6,0xfaff00f1,0xed5680be}}, // _brug, года_, stët_, מבער_,
+ {{0x7d02db7b,0xa80501df,0x2bd2a743,0xb87b0032}}, // rlos, ruñé, _देखा, _ajíw,
+ {{0x6b9bdb7c,0x26d8003b,0x7d0284e8,0xd6270139}}, // _drug, _umro_, slos, _горе_,
+ {{0x7ce8017b,0xd2598221,0x6b9b8133,0xfc3f01d0}}, // tırl, рці_, _erug, ybí_,
+ {{0x4424861b,0x6b9b806a,0x644981ec,0x47348ab2}}, // _ugm_, _frug, _geei, онис,
+ {{0xd9a500d4,0x60c0db7d,0x307781a8,0x6b9bdb7e}}, // _ऑप्ट, rkmm, صحية_, _grug,
+ {{0x66fa09c8,0x8c3d83bf,0xa29f801b,0x24868bcf}}, // ्तिक_, _inşa, _गोप्, leom_,
+ {{0x7c2d9408,0xd24f9459,0x39458609,0xf76f80d7}}, // žare, _سنن_, _gwls_, وای_,
+ {{0x78a2807a,0x7cfd5b7f,0x7bcf0144,0x248680c3}}, // _unov, vérs, tzcu, neom_,
+ {{0x6915b404,0x66fa0076,0x06fa125f,0x38cb8180}}, // _všet, ्ताक_, ्ताव_, _حالی_,
+ {{0x77608713,0x6d4e81df,0x3da6a45b,0x7ae480f7}}, // mrmx, ábas, _гриб, éith,
+ {{0x26de98f1,0x20021040,0x69238bcf,0xf8bf0036}}, // mito_, _ayki_, _uđet, _liée_,
+ {{0x764a8355,0x6915826f,0x58d40c9d,0x320c816b}}, // _hefy, _ušet, пост, _mzdy_,
+ {{0xf7701e95,0x29048406,0x6d580029,0x386959f9}}, // [5230] فال_, ilma_, šval, rgar_,
+ {{0x6b9b820f,0xdddc0110,0x38690fe9,0x6efd80e7}}, // _rrug, _darž, sgar_, lèbr,
+ {{0x212237d7,0x62875b80,0xf60b2133,0xfaff0168}}, // _fikh_, mejo, ухай_, stës_,
+ {{0xe9a98077,0x6b9b805c,0xd5fb80be,0x26de8037}}, // یگان_, _prug, נפאר, hito_,
+ {{0x60cd5b81,0xfb8580d7,0x765884e8,0x69d8008b}}, // mham, تدای, _odvy, _þver,
+ {{0xe2998f04,0x62875b82,0x60cd5b83,0x6b9b81b0}}, // рак_, nejo, lham, _vrug,
+ {{0x26dedb84,0x96080a8e,0x32cb804a,0x00000000}}, // dito_, _гэта_, _nøye_, --,
+ {{0x60cd5b5b,0x25718061,0x7ce8061c,0xceba8300}}, // nham, vál_, tırm, _riƙa_,
+ {{0x6b9b82a0,0x28d8800f,0x62871ebe,0x26dec9d8}}, // _urug, बोधि, kejo, fito_,
+ {{0x673ba0d0,0x200380eb,0xd25184e3,0x6d46826c}}, // rpuj, āji_, _آنا_, ćkan,
+ {{0x34aa2bf3,0x60cd0234,0x80c903eb,0x2d9c81f4}}, // авно_, kham, रसाइ, _crve_,
+ {{0x644f353e,0x2360812b,0x7c2900fe,0x26cc81b4}}, // gaci, šije_, žern, ahdo_,
+ {{0x26dedadb,0xab2a0e02,0x98f4845b,0x60cd3b0b}}, // bito_, рога_, _عثما, dham,
+ {{0x26de9cdc,0x81cc80ab,0x68fb8198,0xb05b0106}}, // cito_, রপর_, loud, _fläk,
+ {{0x2d988370,0x200b8503,0x5d6980e5,0x691582d4}}, // året_, škić_, _тийм_, _ušes,
+ {{0x644f2d11,0x33660d0e,0x60cd15d0,0x672385af}}, // caci, _двиг, gham, _hinj,
+ {{0xe8f700e8,0x6723db85,0xfaff0168,0x464a197b}}, // [5240] ілу_, _kinj, rtër_, изан_,
+ {{0x2be00076,0x68fb82d8,0xdddc0110,0xfaff00f1}}, // _पेया, houd, _varž, stër_,
+ {{0xc27b012a,0x68fb8176,0x2d9c03c1,0x527b1101}}, // _דריי, koud, íve_, _דניא,
+ {{0x60cd5b86,0x6723c575,0xdd1c816b,0x2d9ab1cd}}, // cham, _linj, sážn, _špek_,
+ {{0x6d55026c,0x26de8a03,0x60c42aa0,0x248681f4}}, // _evza, yito_, mkim, reom_,
+ {{0x644f05e4,0x7afc5b87,0x60c45b88,0x75245b89}}, // zaci, nort, lkim, _liiz,
+ {{0x7c2f85b7,0x3f82008e,0x644f5b8a,0xed571a4a}}, // _širš, _hpku_, yaci, _мор_,
+ {{0x60c45b8b,0x764a831d,0x644f526f,0x3eb80722}}, // nkim, _sefy, xaci, _hort_,
+ {{0x3eb80d1a,0x26dea581,0x6723db8c,0xb05b016d}}, // _kort_, tito_, _binj, _släk,
+ {{0x67238052,0x644f04b9,0x7afc01a3,0xddde0087}}, // _cinj, waci, jort, cepţ,
+ {{0x644f11b9,0x3eb82477,0x672384b7,0x60c404a2}}, // taci, _mort_, _dinj, kkim,
+ {{0x26deade9,0x644282f9,0x62950035,0x26cc83ed}}, // sito_, nboi, wdzo, shdo_,
+ {{0x6d58003e,0x26de95a4,0x6723826b,0x752409ca}}, // ávaj, pito_, _finj, _diiz,
+ {{0x25478065,0x6723b413,0x7afc5b8d,0xe5728b8c}}, // ből_, _ginj, gort, _قطع_,
+ {{0x60cd2b16,0x644f05d8,0xdb1d0019,0x7c360114}}, // tham, paci, nysé, _ffyr,
+ {{0x26ca5b8e,0x628703a7,0x34b98f3d,0x47355b8f}}, // _albo_, sejo, _आवेद, знас,
+ {{0x3eb81816,0x44295b90,0x60cd2699,0x7afc5b91}}, // [5250] _bort_, _iga_, rham, bort,
+ {{0x60cd25f4,0x3eb85b92,0xdd9b3eb1,0xadfe9d40}}, // sham, _cort_, ише_, _उडान_,
+ {{0x3eb84161,0x68e9831d,0x60cd005d,0x644d0748}}, // _dort_, yned, pham, _keai,
+ {{0x442909ca,0x6b808133,0xf9fa01c6,0x224c82f9}}, // _jga_, _ịgag, _להתמ, _aedk_,
+ {{0x44291635,0x656888ce,0xd7d985fc,0xeb9993f1}}, // _mga_, hudh, _बेंच, сил_,
+ {{0x77ba0039,0xda7b9bc1,0x02c58935,0x6568db93}}, // _המשח, ряд_, वसेन, kudh,
+ {{0xceb2093f,0xca7c03c8,0x6723c898,0x200981a9}}, // _אים_, ינגט, _rinj, _šai_,
+ {{0x44295b94,0x25750082,0x6723a909,0xa3cb1a87}}, // _nga_, mål_, _sinj, लना_,
+ {{0x6723a08b,0xb4e7835a,0x7641831d,0x34c78105}}, // _pinj, यची_, tbly, लसीद,
+ {{0x44295b95,0x25478019,0xddde1807,0x7524026c}}, // _aga_, től_, lepš, _siiz,
+ {{0x7cfd26d5,0x6da58811,0x442901e0,0xb05b0009}}, // térp, чина, _bga_, _eläi,
+ {{0x25478065,0x7afc2331,0xdb0d00e1,0x44295b96}}, // ről_, wort, ľkýc, _cga_,
+ {{0x7afc5b97,0x67239083,0xa2d28670,0x8fa5a7ae}}, // tort, _tinj, भोक्, _хале,
+ {{0x44294802,0x386d8a38,0xf67580be,0x64428087}}, // _ega_, lger_, _זײַט_, zboi,
+ {{0x2f141918,0x7d110065,0x60c40009,0x44290118}}, // lägg_, üksé, tkim, _fga_,
+ {{0x386d8aa2,0xf992893f,0x7c29db98,0xddde120e}}, // nger_, ערן_, _nger, jepš,
+ {{0x60c45b99,0xb87b046d,0x386dc964,0x3eb824cc}}, // [5260] rkim, _ajís, iger_, _port_,
+ {{0x7c29db9a,0x645b80b9,0xd00f00f7,0x60c42764}}, // _ager, _adui, الك_, skim,
+ {{0x657701c0,0x3abb025f,0xf09f0a2a,0x7abb01c6}}, // gtxh, _המינ, _chàs_, _הציו,
+ {{0x27ed003a,0x3eb801ec,0xf53680be,0x386ddb9b}}, // _žene_, _wort_, לטער_, jger_,
+ {{0x29020006,0x64429ef3,0xa294902a,0x212c808e}}, // _ikka_, rboi, далі, _sudh_,
+ {{0x386d8a11,0x3205801b,0x6568b7c0,0x6577022c}}, // eger_, _byly_, yudh, btxh,
+ {{0x8c468293,0xb909001c,0x2480025b,0x7c2d8390}}, // _неве, _nghỉ_, _obim_, žara,
+ {{0x386d8082,0x3ea7db9c,0xf4849e29,0xc10481a8}}, // gger_, _annt_, _تاسی, بوعي,
+ {{0x644d1cef,0x36d4954f,0x7b09807b,0x4fc69f96}}, // _reai, допр, ðstö, яска,
+ {{0x692a83bb,0x3f1381e5,0x68e0bead,0x3ea78362}}, // _před, ндыс, simd, _cnnt_,
+ {{0xceb30051,0x598694bc,0xaab05b9d,0x28a998b8}}, // תית_, _хлеб, ञानक, काशि,
+ {{0x6abadb9e,0x6298db9f,0xc98581a8,0x6568d4bb}}, // _hotf, ldvo, _تشكي, rudh,
+ {{0x7bc282f7,0x66c00085,0x46a6a535,0xde588d8e}}, // nyou, _höku, _надв, _далі_,
+ {{0x765c08cf,0x753c0063,0x29025ba0,0x764e001b}}, // _ndry, _otrz, _akka_, _neby,
+ {{0x25dc016f,0x51840a41,0x27328129,0x867b81c6}}, // _गेली_, нута, _hân_, _הרוו,
+ {{0x44291c63,0x02d98c87,0xb87b026b,0x776403a8}}, // _uga_, _भग्न, _ajír, frix,
+ {{0xf77305ff,0xf09f0706,0x764e2280,0x76b94bfc}}, // [5270] عار_, _bhàr_, _beby, олар_,
+ {{0x20070ec3,0x2732a111,0xf09f0a2a,0x6abaaa1d}}, // āni_, _mân_, _chàr_, _notf,
+ {{0x764e0114,0x386d8b81,0x2732dba1,0x3915002e}}, // _deby, yger_, _lân_, _омор,
+ {{0xb4e7835a,0x765c031d,0x5fde0fb2,0x386d0366}}, // यचे_, _edry, _नेवल, _øer_,
+ {{0x68ed1042,0x05de86bf,0x889a0039,0x7aed8036}}, // mnad, _फेसब, _וברי, éato,
+ {{0x628aa91b,0x764e1277,0x63a08bcf,0x03a2960e}}, // gefo, _geby, _drmn, тишо,
+ {{0x68ed26e7,0x28a9801b,0x236084b7,0xd5b8002e}}, // onad, कालि, ġija_, _ест_,
+ {{0x68ed5ba2,0x27329e4e,0x23608289,0x5fc8800d}}, // nnad, _bân_, šija_, ाईंल,
+ {{0x6d58dba3,0x2732dba4,0x386dc780,0x68ed3c0b}}, // _avva, _cân_, rger_, inad,
+ {{0x27328104,0x057580f7,0x68ed4e96,0x2ca95ba5}}, // _dân_, واجد, hnad, _anad_,
+ {{0x7ae38fe6,0x68ed2cdb,0x6b8401e8,0x386d8106}}, // mint, knad, _spig, pger_,
+ {{0x7ae3dba6,0xcd02809a,0xf2c903c8,0x69c38428}}, // lint, _gość_, _דע_, myne,
+ {{0x1e0a0da0,0x7e0a0f85,0x7ae3b2be,0x2732a111}}, // _वर्ष_, _वर्ग_, oint, _gân_,
+ {{0x7d0700e7,0x672708ae,0x32008214,0xe0df5ba7}}, // térê, _nijj, şiye_, _amò_,
+ {{0x764e2dcb,0xb0c91299,0x6281dba8,0xdb26811c}}, // _reby, रसंग, _mblo, şqçi,
+ {{0x7ae3dba9,0x77645baa,0x629881dd,0x764e1088}}, // hint, trix, zdvo, _seby,
+ {{0x7ae3dbab,0xd0070185,0x6281dbac,0x753c009a}}, // [5280] kint, дете_, _oblo, _strz,
+ {{0x9d4610ff,0xbefa0f12,0x7ae3dbad,0x68ed5bae}}, // дежд, ्तेन_, jint, anad,
+ {{0x7ae3dbaf,0x6d41dbb0,0x7e7e80f2,0x6aba9388}}, // dint, npla, _öppe, _rotf,
+ {{0x040400ab,0x7c2d811a,0x69c3a337,0xf48381b5}}, // রেণী_, žarn, dyne, вушн,
+ {{0xe7e880c8,0x7ae3dbb1,0x6d670110,0x628ab703}}, // _পর্য, fint, _įraš, tefo,
+ {{0x7ae3dbb2,0x6d41af51,0x68e45bb3,0xd13aa306}}, // gint, kpla, liid, охи_,
+ {{0x2732801c,0x62988cfa,0x753c0035,0x7bc282d6}}, // _sân_, rdvo, _utrz, syou,
+ {{0x6281d847,0x7ae3851e,0x628a805f,0x692a81d0}}, // _eblo, aint, sefo, _přeb,
+ {{0x7c295bb4,0x692a928a,0x628a8299,0xb05b0799}}, // žerk, _břec, pefo, _gläu,
+ {{0x7d042fe0,0x2732801c,0x2ca9001b,0x2ab40106}}, // _akis, _vân_, _snad_, _täby_,
+ {{0x6d41dbb5,0x27e98125,0x62970b6e,0xb05b0106}}, // gpla, _þann_, _raxo, _klät,
+ {{0x7d165bb6,0x692a800d,0x2732a10e,0x66c05bb7}}, // _chys, _třeb, _tân_, _köks,
+ {{0x68e402ab,0xfeb7803d,0xf8bf0216,0x68ed088b}}, // diid, سایت_, _bién_, vnad,
+ {{0x7d040c56,0x629701b4,0x6d41bff4,0x057685ff}}, // _ekis, _qaxo, bpla, _قاعد,
+ {{0x6d558019,0x41e4102a,0xdb008168,0x68ed4575}}, // ázat, віта, _armë, tnad,
+ {{0x7ae3dbb8,0x5064096f,0xdce200eb,0x7c87102a}}, // zint, ктуа, pulā, _цуке,
+ {{0x7ae3a499,0x7c2d1c88,0x3cf286a7,0x97150087}}, // [5290] yint, _igar, ंचने_, емац,
+ {{0x20c70665,0x68ed5bb9,0x7ae38079,0x66c482d6}}, // _kõik_, snad, xint, _bòki,
+ {{0xa29f8b49,0x7ae3b4a3,0x25788019,0xe3b0803d}}, // _गोष्, vint, nél_, یره_,
+ {{0x809e86b7,0x6281b47b,0x66c02522,0x443927eb}}, // _खोले, _sblo, _sökt, ncs_,
+ {{0x6d419640,0x7ae3dbba,0x60d60bea,0x44395bbb}}, // zpla, tint, _נושא_, ics_,
+ {{0xdb258019,0x69c39c20,0x4439026c,0x2bd0056b}}, // _épül, tyne, hcs_, हनता,
+ {{0x6d41bb15,0x64460035,0xb05b0338,0x78bd5bbc}}, // xpla, ybki, _flät, _kosv,
+ {{0x7ae3cb78,0x7c2d5bbd,0x2f1402af,0xa3c10e88}}, // sint, _ngar, räge_, ूना_,
+ {{0x692a801b,0x7c3bba48,0x69c3dbbe,0x6d418bfd}}, // _přec, _ifur, syne, wpla,
+ {{0x7c2d5bbf,0x1dca816f,0x7d043d26,0x2900011b}}, // _agar, ानंत, _skis, zoia_,
+ {{0x75298a0f,0x1d0a091e,0xa50a16fc,0x7d16444e}}, // _kiez, зени_, зена_, _phys,
+ {{0x92bc00c8,0x55da00be,0x26cedbc0,0x6d418e79}}, // _আগে_, פֿינ, _elfo_, rpla,
+ {{0x7529dbc1,0x31c852bf,0x00d8803d,0x78abacfc}}, // _miez, रन्ध, _تبلت_, _ingv,
+ {{0x60c994e0,0x6d41dbc2,0x387fdbc3,0x7c2d5447}}, // lkem, ppla, lfur_, _egar,
+ {{0x442d8a2c,0x7c3bbe87,0x13bd801b,0x2cab00f7}}, // _oge_, _ofur, ्नुभ, áidí_,
+ {{0x26fa000c,0x75298063,0x442ddbc4,0x60c9dbc5}}, // ्त्र_, _niez, _nge_, nkem,
+ {{0xfaa61155,0xd00f0013,0xb6a62657,0x6e3b0214}}, // [52a0] _запо, ملف_, ниал, _şuba,
+ {{0x63a447fe,0xa29f8b84,0x7c3bdbc6,0x7c2d06da}}, // _irin, _गोल्, _afur, _zgar,
+ {{0x60c9dbc7,0x63a4007b,0x752981a9,0xb05b2821}}, // kkem, _hrin, _biez, _plät,
+ {{0x628e0f33,0x63a40357,0x2366936f,0xe45292c8}}, // lebo, _krin, troj_, اضا_,
+ {{0x26068105,0x7529dbc8,0x6abe01ec,0x63a200c3}}, // _सुनी_, _diez, _kopf, _šonj,
+ {{0x629c5bc9,0x442ddbca,0x63a400d7,0xb05b1f5b}}, // ndro, _ege_, _mrin, _fläs,
+ {{0xa3e7064a,0x6ec3800d,0x629c5424,0x7c3b831d}}, // _येन_, ाउनु, idro, _ffur,
+ {{0x628e5bcb,0x63a45bcc,0xf1b30039,0x25789eca}}, // hebo, _orin, רסה_, vél_,
+ {{0xb4be8c1c,0x7d02dbcd,0xb05b0192,0x67d4910e}}, // ेसी_, moos, _klär, топу,
+ {{0x7d0287f6,0x7c2d5bce,0x6d5c0115,0x628e04fe}}, // loos, _sgar, _kvra, jebo,
+ {{0x63a45bcf,0x2b400473,0x6d4e008e,0xa4d501b5}}, // _arin, _atic_, _jwba, воні,
+ {{0x629c0123,0x629adbd0,0xbebc81a9,0xe3a78061}}, // edro, _hato, kmīg, _تشہی,
+ {{0x629adbd1,0x44393b16,0x9b580698,0xddcb81dd}}, // _kato, rcs_, ният_, ćišt,
+ {{0x69a486bf,0x628e0c1b,0x443902e6,0x56938860}}, // किपी, gebo, scs_, _башт,
+ {{0x63a433dc,0x3a2504b8,0x629adbd2,0x30a70009}}, // _erin, älp_, _mato, нрав,
+ {{0x629ab13c,0x69d880d2,0x6d5acaae,0x7c2d5bd3}}, // _lato, izve, msta, _ugar,
+ {{0x63a425ed,0xe81c0076,0xe73307bd,0x2906822e}}, // [52b0] _grin, पेला_, اصر_, _mkoa_,
+ {{0xdef82afb,0x629adbd4,0x66c0016d,0x7c3b8372}}, // ных_, _nato, _sökr, _sfur,
+ {{0x7529dbd5,0x68e2dbd6,0x63a4345f,0x6c6a0065}}, // _piez, _omod, _zrin, _علیہ_,
+ {{0x9e040162,0x69a48a75,0x69d881a9,0x629a8100}}, // учул, किनी, dzve, _aato,
+ {{0xe2999878,0x60c982d4,0x7529dbd7,0x6d5c5bd8}}, // дал_, vkem, _viez, _evra,
+ {{0x91e5ae7b,0x6d5abe80,0x68e28114,0x629a9afe}}, // коле, ksta, _amod, _cato,
+ {{0x7d02dbd9,0x6f01dbda,0x212b22e4,0x68e28609}}, // boos, volc, _kich_, _bmod,
+ {{0x442d813c,0x6d450234,0x629a9a2e,0x212b01d0}}, // _uge_, mpha, _eato, _jich_,
+ {{0x212b0352,0x60c9a5e0,0x6d5adbdb,0x387fdbdc}}, // _mich_, rkem, esta, rfur_,
+ {{0x6d5aa023,0x2cbf80f3,0x2cad83f1,0xfbd700ab}}, // fsta, _houd_, _hned_, সপাত,
+ {{0xb9b5af0a,0x63a41916,0x2cbfdbdd,0x628e3f8b}}, // _جماع, _srin, _koud_, vebo,
+ {{0xfce612b2,0x7bc60063,0x212b5bde,0x83fd8061}}, // томо, tyku, _nich_, _időb,
+ {{0x6d5adbdf,0x629ab433,0x75658065,0x629c0422}}, // asta, _yato, ریکہ_, tdro,
+ {{0x3d12159a,0xceb88063,0x212b0a2a,0x66c48032}}, // _धीरे_, cję_, _aich_, _bòku,
+ {{0xab271628,0x628e1142,0x23270c24,0x6d5adbe0}}, // тора_, rebo, тори_, csta,
+ {{0x09e100ab,0x26d11d20,0xceba8326,0x27360366}}, // _মুহা, _alzo_, _riƙi_, _pæn_,
+ {{0x212b5be1,0x6e3b1487,0xc05280be,0x290ddbe2}}, // [52c0] _dich_, _đubr, יזן_, ilea_,
+ {{0x212b0355,0xb8c90665,0x7f440168,0x69a48b84}}, // _eich_, _गो_, rpiq, किमी,
+ {{0x225843f3,0x6b89a82b,0x212b02d6,0x290dabb7}}, // mark_, _apeg, _fich_, klea_,
+ {{0x6d5c8013,0x09f704de,0xa3cea1e3,0xcb6a9a19}}, // árai, _ימים_, शनल_, даде_,
+ {{0x629adbe3,0x6d5a8f28,0x693c026f,0x2bb7816f}}, // _pato, zsta, _učeb, _आपणा,
+ {{0x6d5adbe4,0x212b0613,0xdd920bca,0x6e432550}}, // ysta, _zich_, _پور_, _верз,
+ {{0x66e30adb,0x6b89826c,0x6d4506e7,0x610c01ec}}, // _тора, _epeg, cpha, eßli,
+ {{0x629a822e,0x2cbfac23,0x69d8dbe5,0x290d85f3}}, // _wato, _goud_, rzve, glea_,
+ {{0x6c7b80be,0x6d5a809a,0x24078012,0x69c7009a}}, // _קאמפ, wsta, _анти_, zyje,
+ {{0x2b468722,0x291f804f,0x70bf001b,0x2bdb9905}}, // mpoc_, amua_, _एक्ल, _मेटा,
+ {{0xd5b7835f,0x6d5adbe6,0x63a2dbe7,0x1e570039}}, // вся_, usta, lvon, כשיר_,
+ {{0x23698289,0x13c680ab,0xf09f0032,0x9ad3819d}}, // šaje_, _শেয়, _akàn_, _jịnj,
+ {{0x3f9880eb,0x212b5be8,0x29049502,0x21200824}}, // ārus_, _rich_, moma_, dmih_,
+ {{0x212b0943,0x6d5a8412,0x25fd8105,0x27ed1de3}}, // _sich_, psta, _रुकी_, _ženo_,
+ {{0x7aed82be,0x31c435cc,0x6d5a822b,0xe7178039}}, // éati, аств, qsta, _שחור_,
+ {{0x63a2c160,0xa3b2801b,0x257c01d0,0x68fb463a}}, // kvon, टमा_, díl_, čudo,
+ {{0x260681ab,0x6d454695,0xdb0400d7,0x212b0580}}, // [52d0] _सुती_, wpha, _oriè, _vich_,
+ {{0x257c01a8,0x20f30267,0x2cbf81d0,0x290d811b}}, // fíl_, pćio_, _soud_, zlea_,
+ {{0x2904dbe9,0xa3e7035a,0x6b89a310,0x765701b4}}, // koma_, _येत_, _speg, raxy,
+ {{0x0ce180c8,0xe7de001b,0x60d65bea,0x5fde081f}}, // যক্ত, _नेकप, chym, _नेकल,
+ {{0x2904dbeb,0xfd5381bc,0x692a801b,0x63858a41}}, // doma_, _begị, _přen, угла,
+ {{0xe3b08ea5,0x3f995bec,0x291f81c0,0x4999a3e7}}, // ترم_, _issu_, wmua_, етия_,
+ {{0xa3e38006,0x45d50009,0x49bb803d,0xc8798380}}, // _फेर_, _совс, باشد_, lişi_,
+ {{0x60cd5bed,0x2904c141,0xb92881bc,0xf99092c5}}, // nkam, goma_, _ikwụ_, تبه_,
+ {{0x2fc92123,0x645983f8,0x7d09dbee,0x59d1800d}}, // nyag_, mawi, _ikes, तनहर,
+ {{0x60cd5bef,0x6459dbf0,0x752d002a,0x1e95997b}}, // hkam, lawi, _aiaz, урир,
+ {{0x7d1b885c,0x60cd14c7,0x290d8216,0xf8758199}}, // _khus, kkam, plea_, _مهاج,
+ {{0xc3290051,0x7e7e80f2,0x2904bbce,0x753548b3}}, // _זו_, _öppn, coma_, _muzz,
+ {{0x60cd1151,0x75350ffa,0x22585b75,0xb3831273}}, // dkam, _luzz, tark_, блял,
+ {{0xb05b00f2,0x68e9dbf1,0x31a38214,0xdb2081ac}}, // _släp, lied, _kız_, _štúd,
+ {{0x6459dbf2,0xaca3ba48,0xb928819d,0x443f81bc}}, // kawi, _akọn, _nkwụ_, _ofu_,
+ {{0x60cd043b,0x6459809a,0x68e99a22,0x7d09862b}}, // gkam, jawi, nied, _nkes,
+ {{0xef1a3714,0xad1b010f,0x0686282d,0x9d1b0039}}, // [52e0] ема_, _אוקר, лган, _אוקט,
+ {{0x539b0051,0x68e9a5e5,0x7d0657d8,0x7d1b9bf4}}, // _טיפו, hied, loks, _ahus,
+ {{0xc27b0158,0x765506c0,0x64565bf3,0x629e0122}}, // _אריי, _dezy, _keyi, _iapo,
+ {{0x257c31a8,0x7d1bb30f,0x629e1049,0x6459dbf4}}, // ríl_, _chus, _hapo, gawi,
+ {{0x629e3286,0xaca38028,0x59b6b947,0xdb041a1f}}, // _kapo, _chốn, _अपहर, _priè,
+ {{0x2d51803a,0x6721803a,0x7d098133,0x63a28631}}, // kše_, jmlj, _ekes, rvon,
+ {{0x2d518db7,0x918680f7,0x2904dbf5,0x629e4a0a}}, // jše_, اجتم, toma_, _mapo,
+ {{0x629e1600,0x6459a73c,0xeb9a8f2e,0x6d5e1d6a}}, // _lapo, cawi, _пиб_, mspa,
+ {{0x2904dbf6,0x6d438110,0x752d002a,0x03a395b1}}, // roma_, _atna, _riaz, _лито,
+ {{0x2904a994,0xcaa680f7,0x672e0122,0x60cd5bf7}}, // soma_, _حصري, _jibj, zkam,
+ {{0x68e9dbf8,0x752d0098,0x58868048,0x443202c4}}, // bied, _piaz, _была, _bgy_,
+ {{0x68e99313,0x799a8205,0x60cd02a3,0x629e57ed}}, // cied, _istw, xkam, _aapo,
+ {{0x64560086,0x752d593a,0x69c81dd5,0x3eb88338}}, // _deyi, _viaz, øden, örts_,
+ {{0x44320986,0x945d8063,0x629e4043,0x6d5e26fa}}, // _egy_, _końc, _capo, kspa,
+ {{0x765adbf9,0x48ab22cb,0x160b9199,0x60c2dbfa}}, // maty, нтам_, _सुपर_, _koom,
+ {{0x64565bfb,0x7c2d8669,0x7d0600b9,0x765adbfc}}, // _geyi, žari, coks, laty,
+ {{0x62889234,0x7aea8082,0xce6b2306,0x6f1c0362}}, // [52f0] _obdo, kift, еред_, _bhrc,
+ {{0x60c290b5,0x7c294c4d,0x6b8d535b,0xc8798457}}, // _loom, žers, _ipag, rişi_,
+ {{0x6459dbfd,0x999e81e2,0x3d950d9d,0x6d5e048d}}, // tawi, _metų_, риер, gspa,
+ {{0xfc3f5bfe,0x6b8d0133,0x6456011c,0xc50b80d7}}, // ncí_, _kpag, _xeyi, رتال_,
+ {{0x64598063,0x3f8007e2,0xa3e5000d,0x20d880f7}}, // rawi, ctiu_, पछि_, _méid_,
+ {{0x68e98d38,0x629e01df,0x7aeaa28f,0x6459c000}}, // wied, _xapo, gift, sawi,
+ {{0x68e9936e,0x7d1bcecf,0xfc3f027f,0x31a38085}}, // tied, _thus, kcí_, _qız_,
+ {{0x2d5180d2,0xaca38028,0x6b8d0091,0x08c59a02}}, // vše_, _thốn, _opag, рбин,
+ {{0x68e9ca33,0xaac611bc,0x645606c0,0x60c2dbff}}, // ried, रॉडक, _reyi, _doom,
+ {{0x63a9a459,0x6b9b82a3,0x66d2007b,0x249f81c5}}, // _iren, _isug, _lækn, _haum_,
+ {{0x68e9b6bb,0x249f815d,0x7d060009,0x60c28952}}, // pied, _kaum_, toks, _foom,
+ {{0x2d810307,0x629e5c00,0xe1f08154,0x2d51941f}}, // ithe_, _sapo, _عسل_, rše_,
+ {{0x9f6b8098,0x07478f75,0x765adc01,0x249f8282}}, // _чрез_, _схем, baty, _maum_,
+ {{0xe5a59f6e,0x212f8051,0xdb0083a7,0x63a99eb3}}, // _вики, _high_, _irmã, _mren,
+ {{0x2056002e,0x64565c02,0x6d438122,0xf09f046d}}, // итар, _teyi, _utna, _baà_,
+ {{0xfc320872,0x249fdc03,0x236080ce,0x776d01df}}, // _بحث_, _naum_, šiji_, frax,
+ {{0x629e47c7,0x26c32254,0xb605819d,0xe737246c}}, // [5300] _tapo, _bojo_, _fláà, иет_,
+ {{0x6d5e0ac5,0x2ca0178f,0x3f802be1,0x26c30216}}, // tspa, _haid_, rtiu_, _cojo_,
+ {{0x63a9dc04,0x273b8104,0x3f803d5b,0x249fa761}}, // _aren, _lên_, stiu_, _baum_,
+ {{0x63a9cd5a,0x249f8069,0x98a60162,0x692a81d0}}, // _bren, _caum_, _тиме, _přem,
+ {{0x273b8104,0x249fdc05,0x7aea948c,0x63a9a2a0}}, // _nên_, _daum_, tift, _cren,
+ {{0x60c2dc06,0x63a9dc07,0x99668a13,0x809e94df}}, // _soom, _dren, атил, _खोजे,
+ {{0x7aea82fe,0xaca38028,0x291d80dd,0x212f8706}}, // rift, _chồn, _bhwa_, _bigh_,
+ {{0x273b8028,0x23698024,0x68f60114,0x63a9dc08}}, // _bên_, šaja_, nnyd, _fren,
+ {{0x69d58117,0xd90d8077,0x765ab6ff,0xbb43bdac}}, // _üzen, ریه_, taty, _метк,
+ {{0x249f81e9,0x2486826c,0xeeaa841c,0x212f8a2a}}, // _zaum_, lfom_, ттик_, _eigh_,
+ {{0x63a982ee,0x6b8d1180,0xdcfcc702,0x765ac702}}, // _zren, _spag, _sprę, raty,
+ {{0x20188086,0x765a8176,0x249f822c,0x57ea8cd0}}, // ərin_, saty, _xaum_, _идем_,
+ {{0x765ad9aa,0xd010803d,0x29120122,0x386682f9}}, // paty, _علت_, dlya_, _idor_,
+ {{0x81e800ab,0xfc3f01a8,0x2d815c09,0x6d489f0a}}, // _বড়_, scí_, ythe_, ppda,
+ {{0x68f60355,0xf1a42296,0x171b8158,0x26c315c7}}, // fnyd, ортн, _שווע, _rojo_,
+ {{0x776d5c0a,0x273b801c,0x40340198,0xfce42ba7}}, // trax, _yên_, жетс, _мојо,
+ {{0x645d0270,0x7d0d00ad,0x693c04ef,0x692a801b}}, // [5310] masi, _ikas, _učen, _přej,
+ {{0x2be004e5,0x249f81c5,0x2ca03a20,0x539c0039}}, // _पेटा, _saum_, _zaid_, _ייחו,
+ {{0x62955c0b,0x249f81c0,0x66d2007b,0x63a980d7}}, // mezo, _paum_, _tækn, _sren,
+ {{0x645d5c0c,0x69c4000d,0x76588009,0x62955c0d}}, // nasi, रहरी, _levy, lezo,
+ {{0x68ed503e,0x26c30118,0xd37b8ce6,0x7d0d57d2}}, // miad, _tojo_, тче_, _mkas,
+ {{0x645d4450,0x68ed5c0e,0x7658850f,0x629521ab}}, // hasi, liad, _nevy, nezo,
+ {{0x645d1600,0x20d18013,0x49938bca,0x7d0d0fc2}}, // kasi, _fáil_, تیار, _okas,
+ {{0x68ed5c0f,0x62955bee,0x645d5c10,0x6b9b8300}}, // niad, hezo, jasi, _tsug,
+ {{0x9267dc11,0x63a9925c,0xa49b06c0,0x386681a1}}, // _ندام, _uren, vlòp, _ddor_,
+ {{0x7d0d1f62,0x2ca05c12,0x68ed0114,0x6295026c}}, // _akas, _said_, hiad, jezo,
+ {{0xdb2684c0,0x68f6031d,0xdca61285,0x09e580ab}}, // _اولی, ynyd, бани, _পুরা,
+ {{0x273b8104,0x6aa1800b,0xdb0084c3,0x3981a380}}, // _tên_, _half, _irmá, mós_,
+ {{0x68ed0355,0xb05b00f2,0x2ca00006,0xa3d786ce}}, // diad, _poän, _vaid_, ानि_,
+ {{0x8c1a8039,0x62950d35,0x6449811c,0x645d05ee}}, // טורי, gezo, əmiş, aasi,
+ {{0x645d5c13,0x6aa1dc14,0xf8db8063,0xa06a221f}}, // basi, _malf, _बताय, лава_,
+ {{0xa3d79521,0x68ed0114,0x60db8073,0xaca380ff}}, // ाना_, giad, nhum, _khổn,
+ {{0xbb860013,0x62955c15,0x6d4700eb,0xdb0981ed}}, // [5320] _الخي, bezo, _atja, _creë,
+ {{0x692a801b,0xfca9db66,0x68f61407,0xe45f008b}}, // _přek, یانو_, snyd, gjöf_,
+ {{0x0d862dea,0x94128085,0x60db805d,0x39818177}}, // слен, ədən_, khum, jós_,
+ {{0x68ed282b,0x27038076,0x6b82b4d7,0x045a81bd}}, // ciad, रकुर_, ttog, اجات_,
+ {{0xdb00dc16,0xa3d28105,0x8c3d8214,0x69ce5c17}}, // _armá, हैं_, _başb, lybe,
+ {{0x645d5c18,0x78a41aed,0xd5d604d9,0x3981823e}}, // zasi, ldiv, _любы, fós_,
+ {{0x645d5c19,0x6b82c24d,0x60dbce12,0x6f08a304}}, // yasi, stog, fhum, vodc,
+ {{0x78a40698,0x27ed005c,0xa3e39993,0x6b82dc1a}}, // ndiv, _ženi_, _फेक_, ptog,
+ {{0xa92604e8,0x9ed8802e,0xd01081a8,0x629501f6}}, // mažď, имит_, بلة_, yezo,
+ {{0x645d11b7,0x693c0a20,0x68ed5c1b,0x3981a2c6}}, // wasi, _pčel, ziad, bós_,
+ {{0x645d5c1c,0xc04fa1d2,0x62951809,0xb80000ab}}, // tasi, _ні_, vezo, ্ধিত_,
+ {{0x691307d9,0x68ed04c3,0x6442bfde,0x60dbdc1d}}, // rçek, xiad, ncoi, chum,
+ {{0x6295011f,0x645d2d8d,0x68ed0510,0x78a423b9}}, // tezo, rasi, viad, ddiv,
+ {{0x68ed29a7,0xadfa03e8,0x27e0009a,0x7c360019}}, // wiad, ्थान_, dzin_, _egyr,
+ {{0x68ed031d,0x692a800d,0x36338f24,0xd5af8791}}, // tiad, _přeh, _عروس, _эс_,
+ {{0x957c8063,0x316c01ac,0x645d011c,0x32cb804a}}, // rząd, ádza_, qasi, _høyt_,
+ {{0x68ed5c1e,0x62955c1f,0x6d470a35,0x78a28c53}}, // [5330] riad, pezo, _stja, _maov,
+ {{0x63ad5c20,0x68ed48e8,0xb8d003db,0xa3ac8816}}, // _iran, siad, _टो_, गिन_,
+ {{0x68ed2706,0x403501e2,0x69ce00f3,0x6aa1dc21}}, // piad, _менс, cybe, _ralf,
+ {{0x63ad5c22,0x398182b7,0x81c100ab,0x764907c0}}, // _kran, vós_, ্ছদ_, _şeyd,
+ {{0x2d9c013c,0xd0068124,0x256387c0,0x987a00be}}, // æver_, _آل_, zıl_, _דארט,
+ {{0x2369807a,0x3981bd9f,0x63ad1ab3,0x2563899b}}, // šajo_, tós_, _mran, yıl_,
+ {{0x60db8234,0x6aa19be9,0xa4d580e8,0xfd13806b}}, // thum, _valf, _дові, _فجر_,
+ {{0x63ad045c,0x2b49003b,0x3981c10f,0x69d58019}}, // _oran, _otac_, rós_, _üzem,
+ {{0x3ea32009,0x6aa1a911,0x2b4903e4,0xbcb4936f}}, // _kajt_, _talf, _ntac_, šším,
+ {{0x60dba6bd,0x66e580e8,0x4d659bab,0x7bcf00b9}}, // shum, іона, оков, dycu,
+ {{0x60db805d,0x3f84815d,0x27e05c23,0x2b494650}}, // phum, atmu_, zzin_, _atac_,
+ {{0x7fd58d13,0xa50a1ad2,0x81c100ab,0x3ea301a1}}, // _мілі, рема_, ্ছা_, _lajt_,
+ {{0x256387d9,0xf8bf00e7,0x78a4016b,0x60c600e1}}, // sıl_, _liés_, vdiv, _rokm,
+ {{0x63ad20b5,0xe57192c8,0x645b8362,0x78a28b80}}, // _dran, _قطب_, _leui, _zaov,
+ {{0x63ad5c24,0x7c3b0669,0xdb09c64c,0xd7658019}}, // _eran, žura, _creé, _انڈی,
+ {{0x63ad5c25,0x26c78c0b,0x8b960b79,0x27e05c26}}, // _fran, _hono_, _дроч, tzin_,
+ {{0xeb9a2318,0x46ea3eb1,0xaec6522f,0x26c7803d}}, // [5340] _тип_, адан_, обал, _kono_,
+ {{0xafdb005f,0x27e05c27,0x26c78a14,0x3200a2f8}}, // _iføl, rzin_, _jono_, şiyi_,
+ {{0x63ad026f,0xa2a3016f,0x66d20215,0x765e5c28}}, // _zran, _छोट्, _hækk, rapy,
+ {{0xa3c1016f,0x83fd8061,0x645b8706,0xf8bf1b88}}, // ूनच_, _időj, _ceui, _chéd_,
+ {{0x693c0289,0x160b8076,0xfc3f05e4,0x7ed600e8}}, // _oček, _सुसर_, ndía_, _дівч,
+ {{0x38602176,0x44020063,0x6442a44e,0x29008196}}, // lair_, _लखनऊ_, scoi, čiam_,
+ {{0x66d24660,0xd2598a8e,0x645b902d,0x201d818f}}, // _lækk, сці_, _feui, _azwi_,
+ {{0x38604863,0xe81e8074,0x478a84dd,0xf4868061}}, // nair_, _परदा_, асом_, _گاڑی,
+ {{0x27e981f4,0xb60201d0,0x20ca8362,0x77640118}}, // _žanr_, řádn, _bùir_, nsix,
+ {{0x3860061f,0x753c5c29,0x26c7dc2a,0x66d2007b}}, // hair_, _kurz, _cono_, _sækj,
+ {{0x63ad57d6,0xa2be0592,0xd6d7835f,0x2b49474f}}, // _sran, शान्, іть_, _stac_,
+ {{0x63ad3f13,0x2ca6dc2b,0x386006cb,0xd05f0085}}, // _pran, ndod_, jair_, biqə,
+ {{0x3860082a,0x26c7a581,0x61358110,0xd0f980be}}, // dair_, _fono_, _išla, _פּער,
+ {{0x66d2006a,0xb4e78a27,0x8c3da2f8,0x248013de}}, // _dækk, _पष्_, _maşa, _ecim_,
+ {{0x692a801b,0x26f885fc,0x3ea324c5,0x63ad0428}}, // _dřev, ुवीर_, _rajt_, _wran,
+ {{0x63ad5c2c,0x3ea35c2d,0x38600857,0x765c02d6}}, // _tran, _sajt_, gair_, _aery,
+ {{0xb8e303b7,0x14ab8ebf,0x63ad4c50,0x765c5c2e}}, // [5350] _एच_, _घोषण, _uran, _bery,
+ {{0x6298a368,0x957c8063,0xddab1071,0x7f4d0085}}, // jevo, cząc, ител_, rpaq,
+ {{0x80c700ab,0x3ea32009,0x386044b9,0x765c5c2f}}, // রোগ্, _vajt_, bair_, _dery,
+ {{0x8c3d8214,0x38604720,0xd251026a,0x291e0722}}, // _başa, cair_, _تنگ_, ïtat_,
+ {{0x645b82be,0x6aa509ca,0x765c4154,0xd7f85c30}}, // _veui, _kahf, _fery, _дур_,
+ {{0x7f3a0051,0x62988b3c,0xe299a29c,0x212900ee}}, // _לעשו, gevo, сак_, lmah_,
+ {{0x645b89c4,0x6aa500ee,0x753c04b9,0x7c9781a8}}, // _teui, _mahf, _gurz, _وشرا,
+ {{0x26c78021,0x26de8198,0x01fc01c6,0x673d5c31}}, // _sono_, ehto_, יפול, _kusj,
+ {{0x73d986e6,0x26c78071,0x6298b560,0x753c0192}}, // _удар_, _pono_, bevo, _zurz,
+ {{0x76451c33,0x66d2013c,0x6298dc32,0xd25180f7}}, // nchy, _rækk, cevo, _أنا_,
+ {{0x2129478e,0x386000dd,0xdca60878,0x64a6130f}}, // kmah_, yair_, пани, пана,
+ {{0x8c3d8459,0x656e81a8,0x38600609,0x64410035}}, // _yaşa, ábha, xair_, ślin,
+ {{0xf7498307,0x69ce834a,0x692a801b,0xd05f0085}}, // _التي_, _über, _přev, qiqə,
+ {{0x26de803e,0xfc3f0511,0xfaff0168,0x66d20366}}, // chto_, rdía_, drës_, _vækk,
+ {{0x38604772,0xfaff03ed,0x76ba81c6,0xdb041b88}}, // tair_, erës_, _למספ, _criá,
+ {{0x765c5c33,0x68fb807b,0xa5070098,0x24800bcf}}, // _sery, nnud, _неща_, _ucim_,
+ {{0x38605c34,0x765c5c35,0x969610f8,0x2ca480b9}}, // [5360] rair_, _pery, зреш, _ramd_,
+ {{0xfc3f5c36,0x44205a5f,0x38600c41,0x7e61dc37}}, // ndín_, _izi_, sair_, nalp,
+ {{0x80db923a,0x1dcba207,0x2a5701c6,0xba9b01c6}}, // बसाइ, ाहित, _מבין_, _פסטי,
+ {{0xa3d78072,0x8c3da0ec,0x644618eb,0x6281913b}}, // ानं_, _paşa, ncki, _aclo,
+ {{0x7e618029,0x2ca6a87f,0x62988d02,0xaae5003d}}, // kalp, rdod_, tevo, _رسیو,
+ {{0x60c4003a,0x05cb8fcc,0x753c398c,0x62818362}}, // ljim, ाहाब, _turz, _cclo,
+ {{0x78a60025,0x6298dc38,0x628a831d,0x691302d0}}, // _kakv, revo, rffo, rçev,
+ {{0x60c40025,0x44200a2c,0x64a38bac,0x4034b4ba}}, // njim, _ozi_, _паса, мейс,
+ {{0x20d88307,0x6298d15a,0x44205c39,0xa3d7864a}}, // _féin_, pevo, _nzi_, ानः_,
+ {{0x7e618144,0xfa67a306,0xe8040035,0x7c208301}}, // galp, _наук_, _रखना_, _izmr,
+ {{0x44205c3a,0x3eb802f7,0xceb30039,0x78a65c3b}}, // _azi_, _mnrt_, גית_, _oakv,
+ {{0x98ab0162,0x48e3828e,0x60c41abf,0x75242aa0}}, // _mică_, _похв, jjim, _chiz,
+ {{0x60c40289,0x6b9d01ed,0x66d302d0,0x61359075}}, // djim, uwsg, _yıkı, _ušla,
+ {{0x7e618216,0x27f7816b,0x236d02d4,0x75240cdb}}, // calp, šená_, šejo_, _ehiz,
+ {{0x673d111b,0x44205c3c,0x78a60840,0xbd878124}}, // _susj, _ezi_, _bakv, _فنون_,
+ {{0xd5fb8039,0x60c40168,0x657a9a99,0xfaff03ed}}, // _לפור, gjim, muth, trës_,
+ {{0x6aa503ac,0x213e8a87,0x657aa0b3,0x67350144}}, // [5370] _tahf, _luth_, luth, _pizj,
+ {{0x21293e55,0xdb04188b,0xfaff0168,0xb99900e8}}, // smah_, _triá, rrës_, _двох_,
+ {{0xfc3f0333,0x76450428,0x26ca02d4,0x0c4a81bc}}, // ndío_, rchy, _dobo_, _ịdịl,
+ {{0xe4e48d13,0x66d30214,0xb4d7ad05,0x6615001b}}, // нічн, _sıkı, िसे_, _vyzk,
+ {{0x65655c3d,0xfaa59bdc,0x657a8234,0x6f020036}}, // sshh, дало, huth, éocc,
+ {{0x657a91e8,0xb87b002a,0xf2c78fe6,0x4fd601c6}}, // kuth, _emít, ясен, _חושב_,
+ {{0x26fb05e8,0x659404f6,0xdd940084,0x7d0f0061}}, // ्वीर_, _пару, _пары, kocs,
+ {{0x23690025,0x26dc8187,0x213e8362,0x318e811c}}, // _ovaj_, _alvo_, _duth_, _qəza_,
+ {{0x3f8907d5,0x7e61dc3e,0x75241916,0x7c3ba4fc}}, // ntau_, talp, _rhiz, _igur,
+ {{0x68fb8264,0xe3b08bbe,0xef168009,0xb87b01d0}}, // snud, _حرف_, ммы_, _hlíd,
+ {{0xfc3f4622,0x645f0ace,0x213e8c41,0x23690bcf}}, // rdín_, _beqi, _guth_, _avaj_,
+ {{0xb4ca0e5b,0x3f8900b9,0x2d6781dd,0x7e61af2d}}, // ोसे_, ktau_, rđe_, salp,
+ {{0xef0e8c6e,0x7e6182d5,0x78a602f1,0x2d67826c}}, // _ам_, palp, _rakv, sđe_,
+ {{0xb4ba81ce,0xc72480e8,0x657a8234,0x60dd00f3}}, // _अच्_, _здій, buth, _alsm,
+ {{0x26ca4bf7,0x692a801b,0x7c3bbf5e,0x78a60084}}, // _robo_, _třet, _ogur, _pakv,
+ {{0x387fdc3f,0x7c3bdc40,0x693c005c,0x2b4d826f}}, // ngur_, _ngur, _očev, _otec_,
+ {{0xf7720013,0x3ea7a5d0,0x26ca04c3,0x60c42daf}}, // [5380] ضاء_, _kant_, _pobo_, rjim,
+ {{0x63a43496,0x290081e2,0x7c3b86a2,0x6449818a}}, // _isin, čiai_, _agur, _afei,
+ {{0x78a6003a,0xdb0986a5,0x693c0088,0xa92615d1}}, // _takv, _creí, _ačev, _одол,
+ {{0x26ca0247,0xdb008019,0x60cb8fb0,0x628e5c41}}, // _wobo_, _ismé, _nogm, lfbo,
+ {{0xab9a826a,0x78a9aebd,0x3f890114,0xbb43b2f1}}, // تخار_, ddev, ctau_, вечк,
+ {{0xed5a4541,0x64498073,0x63a40010,0x78a9847f}}, // _мог_, _efei, _msin, edev,
+ {{0xa7fb01df,0x64498114,0x60cbdc42,0x78a99384}}, // _veñe, _ffei, _bogm, fdev,
+ {{0x63a42e4d,0x387fdc43,0x2b588019,0x657a81f6}}, // _osin, ggur_, _فیصد_, vuth,
+ {{0xa7fb062f,0x60cb82f7,0xb8f6a43f,0xe29284a3}}, // _teñe, _dogm, _हव_, _لذا_,
+ {{0x629c546b,0x20188029,0x692a800d,0x66cd81ac}}, // jero, āri_, _přes, _súkr,
+ {{0x9e770051,0x78a98279,0x58868048,0x6e3c00c3}}, // וגיה_, bdev, _жыла, _mgrb,
+ {{0x657a808c,0x63a40609,0xfc3f0333,0x6aa89670}}, // ruth, _bsin, rdío_, _hadf,
+ {{0x629c5c44,0x63a40065,0x3ea780e8,0x9b580081}}, // fero, _csin, _fant_, мият_,
+ {{0x3ea7dc45,0x81cd00c8,0x7f44011c,0x63a402f7}}, // _gant_, _শেষ_, dqiq, _dsin,
+ {{0x8c3d8afe,0x63a42370,0xf67b8039,0x23c500f1}}, // _başl, _esin, _ואומ, _bëjë_,
+ {{0x2369812b,0xe7d2a836,0x25f452d5,0xcd2b00d7}}, // šaji_, _तथाप, ्पनी_, _بستن_,
+ {{0x6d4e0029,0x6d5c1ab3,0x6aba800b,0x21268362}}, // [5390] _atba, _awra, _ontf, _mhoh_,
+ {{0xdef804d9,0x6d40017f,0x7c3b8229,0x4a5500e8}}, // мых_, _čmar, _sgur, _якос,
+ {{0x62850135,0x3f892091,0x2d8700e7,0x2ca91e3b}}, // _icho, stau_, îne_, _kaad_,
+ {{0xd0100065,0x20d881a8,0x245881e2,0x2a7f0326}}, // الے_, _béim_, даць_, ɗuba_,
+ {{0xe66700c4,0x20d881a8,0xdcfc81a9,0x007601c6}}, // _отво, _céim_, _aprē, _אתרי_,
+ {{0x213900b9,0x2ca95c46,0x66d2008b,0x394105ee}}, // _hish_, _laad_, _bæku, _kuhs_,
+ {{0x8c3d8182,0x7e650101,0xb87b001b,0xf99188ca}}, // _yaşl, mahp, _umís, ابت_,
+ {{0x3ea7dc47,0x3a380051,0x6aba82af,0x7e6509ca}}, // _sant_, ורום_, _entf, lahp,
+ {{0x629c2922,0x78a9c17e,0x692a81d0,0x213903ed}}, // yero, rdev, _přer, _mish_,
+ {{0xb4ca03bb,0x629c2e24,0x21393e5b,0x62850133}}, // ोस्_, xero, _lish_, _ncho,
+ {{0xa2be1299,0x63a403f7,0xa3d7873c,0x6738cb0f}}, // शास्, _ssin, ानक_, _divj,
+ {{0x62855c48,0x3ea78051,0x63a40267,0x629c5c49}}, // _acho, _want_, _psin, wero,
+ {{0x3ea7dc4a,0x36d5af85,0x629c5c4b,0x2ca910b8}}, // _tant_, _подр, tero, _daad_,
+ {{0xbea29677,0x21390101,0x693c007a,0xe0df03ec}}, // _башк, _aish_, _očet, _alò_,
+ {{0x09e60b9c,0xc5fa00ab,0x682a0019,0x7e650748}}, // ховн, _অর্থ_, ködé, dahp,
+ {{0x63a40637,0xe45f1e2b,0x6738dc4c,0xe0df01e4}}, // _tsin, ljön_, _zivj, _clò_,
+ {{0x629c25e0,0x63a40051,0x21395c4d,0xd00712ea}}, // [53a0] pero, _usin, _dish_, еете_,
+ {{0xa3b385e8,0xef172c38,0x3ce001c0,0x69c8006a}}, // टिन_, ему_, _hliv_, ødes,
+ {{0xe7f003eb,0x69c102f1,0x8c3d82d0,0xb917826b}}, // _घेरा_, älet, _taşl, _didẹ_,
+ {{0x20d1c92f,0x29023036,0xe73a2857,0x63f700d7}}, // _máis_, _ujka_, _нем_, _آفیس_,
+ {{0x883b0051,0x2912005d,0x98160250,0xdb098706}}, // _מתכו, moya_, _سبحا, _breà,
+ {{0x68e45c4e,0x9f4001ac,0x21268168,0x79a69b47}}, // lhid, nzií_, _shoh_, ерие,
+ {{0x7c969a84,0x6ee00065,0x3ce05c4f,0xdb040a53}}, // _прац, _több, _oliv_, _asië,
+ {{0x7c240353,0x3eaa01ec,0xb87b03aa,0x29002506}}, // _ozir, _habt_, _ilíc, nnia_,
+ {{0xd2528013,0x2ca92808,0x7c24331a,0x2900181c}}, // _منذ_, _raad_, _nzir, inia_,
+ {{0x15f40128,0x2ca95c50,0x7d045c51,0x63a2867d}}, // _आधार_, _saad_, _ajis, mwon,
+ {{0x3ce0013c,0x20d680e8,0x291205ee,0x68e45c52}}, // _bliv_, _підс, koya_, khid,
+ {{0x62855c53,0xa7fb016a,0x26c38106,0x26d983ed}}, // _scho, _leña, öjor_, ësor_,
+ {{0x29000063,0x21395c54,0x2912008e,0x63a2dc55}}, // dnia_, _rish_, doya_, nwon,
+ {{0x29008a8e,0x29000063,0x657e029b,0x2471001c}}, // čiau_, enia_, kuph, ễm_,
+ {{0x61258825,0x38622f9f,0x59a6850d,0x2912026b}}, // sóle, _bekr_, _कैमर, foya_,
+ {{0x63a2cbcd,0x29000081,0x291204b9,0xa3e08ef0}}, // kwon, gnia_, goya_, दना_,
+ {{0x2d980205,0x62850bfe,0xd251003d,0x442481a1}}, // [53b0] _apre_, _tcho, _پنج_, _nzm_,
+ {{0x62854c86,0x290003b7,0x7d1d0408,0x7d04001b}}, // _ucho, ania_, llss, _zjis,
+ {{0x29123129,0x68e45c56,0x7e6500dd,0x63a281ed}}, // boya_, bhid, rahp, ewon,
+ {{0x693c5c57,0xdb040176,0x68e4079a,0x2b4986ea}}, // _včet, _asiè, chid, íaco_,
+ {{0x03a5a209,0xdb09bbec,0x386f841c,0x5c14abca}}, // тило, _areá, _rdgr_, льшу,
+ {{0xe2969354,0x78ad2947,0xf8bf00ff,0x2ca9826c}}, // ваш_, ldav, _khéo_, žad_,
+ {{0x692a801b,0xdb099727,0xb87b0118,0x693c007a}}, // _přep, _creá, _glíc, _hčer,
+ {{0x78ad38e4,0x0c7280d7,0x693c0140,0x27e900fe}}, // ndav, _نگهد, _kčer, ozan_,
+ {{0x27e9224a,0x443fd287,0x93948199,0x64598314}}, // nzan_, _igu_, _مجلا, mbwi,
+ {{0x7d162133,0xdb098118,0x443f808e,0xf1b30ec5}}, // _skys, _freá, _hgu_, ुमान,
+ {{0x1d0a1a19,0xa50a067c,0x3ce001c0,0x77698187}}, // дени_, дена_, _pliv_, ssex,
+ {{0x2369805c,0x434618a0,0x764903bf,0x8e360039}}, // šaju_, кемв, _şeyl, כנתא_,
+ {{0x68e45c58,0xe72edc59,0x83fd8019,0x26ce92ab}}, // vhid, _ве_, _idős, _fofo_,
+ {{0x78bd0bfa,0x8c3d87d9,0x68e42cb1,0x6283a79e}}, // _ansv, _başk, whid, ngno,
+ {{0x3f8dc1b4,0x68e4051e,0x290029bd,0x443fdc5a}}, // lteu_, thid, tnia_, _ogu_,
+ {{0x443fdc5b,0xa7fb05b4,0x60dba05e,0x7e638a84}}, // _ngu_, _seña, nkum, _henp,
+ {{0x1e860002,0xa6861091,0x6d43dc0f,0x2d98002e}}, // [53c0] клам, клад, _huna, _spre_,
+ {{0x6d43dc5c,0xd37b012a,0x68e40b8f,0x5b7b04de}}, // _kuna, _קריט, shid, _קריא,
+ {{0x60db870f,0x181e805e,0xdb040118,0x64a68f9c}}, // kkum, _पर्व_, _oriú, кажа,
+ {{0x7ae1dc5d,0x752984cd,0x63a2dc5e,0x60db8267}}, // _allt, _chez, twon, jkum,
+ {{0x6d439364,0xa7fb04c3,0x31da801b,0xa8032306}}, // _luna, _teña, बन्ध, _взял,
+ {{0x7e638247,0x66d2013c,0x63a2dc5f,0xdb098333}}, // _nenp, _væks, rwon, _preá,
+ {{0x6d438041,0x693c0efd,0x99638071,0x63a2dc60}}, // _nuna, _učes, атыл, swon,
+ {{0x7c3b0042,0x63a280f3,0x442480b9,0x752986d8}}, // žuri, pwon, _tzm_, _ghez,
+ {{0x673c0181,0x68e30087,0x6d43dc61,0xb8fadc62}}, // _kirj, _înde, _auna, _ठक_,
+ {{0x7d02931b,0x6d4394f2,0x661c0687,0x6440026c}}, // lnos, _buna, _kyrk, žmil,
+ {{0x7e63b3bb,0x6d43ab69,0x867b0039,0x6d4480e1}}, // _denp, _cuna, וריו, _čias,
+ {{0x6d439341,0x7d02dc63,0x63b6195e,0x7ce6018a}}, // _duna, nnos, _bryn, _nórd,
+ {{0x69d587d9,0x7d1d3f7a,0x56948fbf,0x78ad0669}}, // _üzer, rlss, _балт, vdav,
+ {{0xdb0d062f,0x67da00eb,0x60cf5c64,0x68e28696}}, // _traí, _mājā, _socm, _hlod,
+ {{0x69d88019,0x68e2dc65,0xb87b016a,0x7d02c43d}}, // nyve, _klod, _alía, knos,
+ {{0x7ce61099,0x27e90102,0x20188085,0x63b62911}}, // _córd, tzan_, ərir_, _fryn,
+ {{0x78ad5c66,0x6d438353,0xb4df050d,0x3f80107f}}, // [53d0] rdav, _zuna, णसी_, guiu_,
+ {{0x6d43dc67,0xe71900f7,0x64490110,0x443f806a}}, // _yuna, ريات_, žeid, _sgu_,
+ {{0x693c1234,0x28c583db,0x60cf0087,0x673c5c68}}, // _včer, वामि, _tocm, _dirj,
+ {{0x657a8c49,0x661c0257,0x6125807b,0x7d028ca9}}, // irth, _dyrk, kóla, gnos,
+ {{0xdfc69368,0xf8bf01ac,0x2919026c,0x38691aa6}}, // _اي_, _iné_, _iksa_, maar_,
+ {{0x386905f8,0xa92781ac,0x8c3d802e,0x9f9c807b}}, // laar_, _obľú, _maşi, _ræða_,
+ {{0x69d89450,0x7d02c19d,0x68e2a280,0x2489026c}}, // gyve, bnos, _blod, _ocam_,
+ {{0x443f82c1,0xe9468077,0x7d0287df,0x38690b3c}}, // _ugu_, دروی, cnos, naar_,
+ {{0x776d062f,0x6d4385a3,0x60c9885c,0x60dbdc69}}, // nsax, _suna, rjem, rkum,
+ {{0x6d43d1d8,0xfe7180f7,0x3f8ddc6a,0xeb0e83eb}}, // _puna, حدة_, rteu_, ाकृत_,
+ {{0x386910f4,0x26d101df,0x68e29f94,0x2d81369f}}, // kaar_, _mozo_, _flod, nuhe_,
+ {{0x68e298f4,0x38690fb0,0x63b60114,0x24890580}}, // _glod, jaar_, _pryn, _ccam_,
+ {{0x657adc6b,0xd1108aec,0xf8bf00ff,0x38694b97}}, // arth, ावरण_, _chém_, daar_,
+ {{0x6d43dc6c,0x6449062f,0x7d02935a,0x43430e8e}}, // _tuna, ñeir, znos, серв,
+ {{0x09e3197b,0x2a658267,0x7d02a581,0x6d43dc6d}}, // борн, _melb_, ynos, _uuna,
+ {{0xc486024b,0x4c860652,0x673c4483,0x205506f9}}, // _слик, _слив, _sirj, итур,
+ {{0x7d02beaf,0x63a9a169,0x661c00ee,0xdd0401a9}}, // [53e0] vnos, _nsen, _syrk, šsēd,
+ {{0x7d02a1f5,0x3ea10192,0x661c0198,0x6b5b0e82}}, // wnos, geht_, _pyrk, _רדיפ,
+ {{0x386902b5,0x63a9cc37,0x6b9b819d,0xa2c29199}}, // baar_, _asen, _apug, लास्,
+ {{0x98ba0012,0x0b8ab511,0x938a82a4,0x5c068b79}}, // _după_, ески_, еска_, ляла,
+ {{0x628107a3,0x254c801b,0x63a98061,0x66741190}}, // ólog, _měla_, _csen, _نگار,
+ {{0x661c0e51,0x610881d0,0x68e2ae63,0x37cd0264}}, // _tyrk, děla, _slod, _রেকর,
+ {{0x63a9b328,0x7d029807,0x39459e9e,0xfd6501bc}}, // _esen, pnos, _culs_, _mesị,
+ {{0x58d484fa,0x02d1000d,0x6ad10107,0x2edc800d}}, // роит, _सक्न, _सक्र, यस्त,
+ {{0xdfd081a8,0x6cc38087,0x78a090ac,0x00000000}}, // زيد_, _уйта, remv, --,
+ {{0x2489151e,0x539b8039,0x83fd8019,0x6146b511}}, // _scam_, _אימו, _időp, _бежа,
+ {{0xdd948196,0x6b82dc6e,0x38690079,0xd5b78470}}, // шаны, luog, yaar_, гся_,
+ {{0xdb098003,0x13d880ab,0x657a80f7,0x705680f7}}, // _preç, _দেয়, rrth, _إنشا,
+ {{0x38691ad7,0xfd1f047f,0x7e6880e4,0x2d8100c3}}, // vaar_, rlì_, padp, zuhe_,
+ {{0xa7fb040e,0x3869037a,0xf41f0106,0x32d986c4}}, // _seño, waar_, ffär_, _pèyi_,
+ {{0x31c4017a,0x38695c6f,0x318e8085,0xa7fb0144}}, // бств, taar_, _bəzi_, _peño,
+ {{0x3eae8772,0x90551c57,0x6ad18072,0x6b82aaa0}}, // _haft_, рвац, _तक्र, kuog,
+ {{0x38690c11,0x81cd00ab,0x26d111b9,0x386680ee}}, // [53f0] raar_, _শেখ_, _pozo_, _meor_,
+ {{0xbd6b3760,0x386697ca,0xe945026a,0x38691bfc}}, // ерде_, _leor_, _پردی, saar_,
+ {{0x80ba85b3,0x386914fb,0x752d4022,0x7127006b}}, // _शोभे, paar_, _khaz, _مرحل,
+ {{0x2d815c70,0x63a98bcf,0xa8a70a0e,0xdb160176}}, // ruhe_, _psen, _срок, _aryè,
+ {{0x3cfd000f,0x60cd3d95,0xd7fb01a1,0xda6590f8}}, // _लगने_, mjam, _чун_, авли,
+ {{0x321e826f,0x450b00ab,0x6125809a,0xe299dc71}}, // _byty_, রতিক_, góln, так_,
+ {{0x60cd01e2,0xd49b8a8e,0x6ee008aa,0xdb009eec}}, // ojam, _пра_, _möbl, _armó,
+ {{0xdfd18013,0x44292c88,0x50b5a154,0x2cbf8122}}, // قيع_, _oza_, рску, _unud_,
+ {{0x7d1b80ad,0x6d4727ba,0x63a9dc72,0x82878829}}, // _ikus, _huja, _usen, _مجال,
+ {{0x6d475c73,0x59678698,0x7c29dc74,0x8c3d8214}}, // _kuja, _бъда, _izer, _başv,
+ {{0x28c581a2,0x44295c75,0xa3b3809a,0x6cc63832}}, // वादि, _aza_, टिव_, айна,
+ {{0x386987ca,0x6d475c76,0x752d5c77,0x6125818a}}, // _þar_, _muja, _chaz, mólo,
+ {{0x7d09830b,0x89348b76,0x68e9dc78,0x6125802a}}, // _mjes, _اعلا, mhed, lólo,
+ {{0x60cd00eb,0x7e67001b,0x7d0994ce,0x68e99de6}}, // ejam, _nejp, _ljes, lhed,
+ {{0x7d1b94ee,0xa06a08d5,0x44294ebb,0x6125a706}}, // _okus, кава_, _eza_, nólo,
+ {{0x68e9976f,0x752d3e5a,0x2a6a0019,0x0f1e01a2}}, // nhed, _ghaz, sabb_, भत्स_,
+
+ {{0xb8fe1499,0x0e661506,0x27ed9d77,0x03a627cb}}, // [5400] _तक_, икан, nzen_, _типо,
+ {{0xe9da0abe,0x44205c79,0x7d1bdc7a,0x6d472d68}}, // _ако_, _iyi_, _akus, _buja,
+ {{0x7c298201,0xc27b012a,0x6aa3a382,0xa6db007b}}, // _azer, _בריי, denf, _guðm,
+ {{0x7afc07d9,0x6ec406ab,0x44201bb7,0x81b98326}}, // lirt, राहु, _kyi_, _gaɓɓ,
+ {{0x27ed8613,0x7c298063,0x68e9cf15,0xdb008144}}, // jzen_, _czer, dhed, _asmá,
+ {{0x66e601f3,0x78a45c7b,0x7d1b8234,0x6aa3dc7c}}, // _тога, leiv, _ekus, genf,
+ {{0xdcfe80fe,0x386682ba,0x7c29dc7d,0x64a69597}}, // lupč, _peor_, _ezer, _кава,
+ {{0x68e9b51d,0xc9530451,0x44200135,0x68fb8081}}, // ghed, חמה_, _oyi_, giud,
+ {{0x4420217b,0x6125809a,0x6d470503,0x69dc5c7e}}, // _nyi_, póln, _zuja, hyre,
+ {{0x7d1b801b,0x60cd07d8,0x7522dc7f,0x64440706}}, // _zkus, zjam, lloz, _agii,
+ {{0x6d40c3b7,0x6125a82b,0x44205c80,0x75228904}}, // _kima, cólo, _ayi_, oloz,
+ {{0x6d40a2e9,0x68e9dc81,0x752d4732,0x68fb923d}}, // _jima, ched, _qhaz, ciud,
+ {{0xdcfc80eb,0x8f5486e2,0x3eae82f7,0x60c2c654}}, // _aprī, _انتش, _taft_, _inom,
+ {{0x3f84dc82,0x6e948250,0x35f7803d,0xdb0992f1}}, // mumu_, _طلبا, ارید_, _treå,
+ {{0x5d848013,0x4420077f,0x3f84c146,0x60cd0722}}, // _العل, _eyi_, lumu_, tjam,
+ {{0x6d5a8e99,0x6d40da6d,0x6d475c83,0x44200295}}, // mpta, _nima, _ruja, _fyi_,
+ {{0x60cd2cc7,0x6d472eb7,0x7afc473c,0x7d09911b}}, // [5410] rjam, _suja, birt, _rjes,
+ {{0x63ad343a,0x6d471b09,0x539a0039,0xc9871c8b}}, // _isan, _puja, _עיתו, _куми,
+ {{0x7c298986,0x7d09dc84,0xdb0d0214,0x6d40ae5f}}, // _szer, _pjes, _araç, _bima,
+ {{0x3f84a95e,0xbd188cde,0x4518835f,0x6d4090fe}}, // kumu_, ації_, ація_, _cima,
+ {{0x3f848341,0x6d40a8a7,0x7c20a065,0x889a0496}}, // jumu_, _dima, _cymr, _גברי,
+ {{0xd945a856,0x60c2dc85,0x3f84dc86,0xa9699ddf}}, // беки, _anom, dumu_, гика_,
+ {{0x7d0982ce,0x442981ac,0x6d4080fc,0xe05782e3}}, // _tjes, ťa_, _fima, لیات_,
+ {{0x27eddc87,0x7d1b805d,0x27ed003e,0x6d40b1ec}}, // tzen_, _ukus, _ženy_, _gima,
+ {{0x8aa785c2,0x7c208355,0x3f84dc88,0x68e9b51d}}, // _вред, _gymr, gumu_, rhed,
+ {{0x28c5946d,0x39b58029,0x2bcb06b7,0x27ed9d77}}, // वाहि, _tās_, ामना, rzen_,
+ {{0x44260029,0x63ad5c89,0x99d98013,0x44200590}}, // _šo_, _asan, _حواء_, _syi_,
+ {{0x3f84dc8a,0x7afc01ec,0x2b410176,0x7d198035}}, // bumu_, wirt, _aihc_, nows,
+ {{0x7ce600f7,0xeb9710ff,0x7afc2270,0x2b49002a}}, // _córa, бия_, tirt, _cuac_,
+ {{0x69dc460a,0x63ad008e,0x386d89ff,0x59bd8f0f}}, // tyre, _dsan, laer_, ्मपर,
+ {{0x63ad5c8b,0xf2d28158,0x69dc87d9,0x7afc0125}}, // _esan, לעך_, _üret, rirt,
+ {{0x7ce60307,0x63bb8091,0x26c30187,0xdcfb00eb}}, // _fóra, _orun, _anjo_, putā,
+ {{0x6d40dc8c,0x44260028,0x2d85cd30,0x7afc3518}}, // [5420] _rima, _áo_, lule_, pirt,
+ {{0x6d40dc8d,0x3ea599f6,0x69c829dd,0x7c5700d7}}, // _sima, nelt_, ädes, _پلیر_,
+ {{0xa0a65c8e,0x63bb905c,0x6d40dc8f,0x7522c895}}, // _ланд, _arun, _pima, tloz,
+ {{0x20d88013,0x237f077b,0xdb0d0187,0xfd508032}}, // _léir_, šuje_, _praç, _aadọ,
+ {{0x3ea5a28f,0x2d85dc90,0xd91b0039,0x26180ad5}}, // kelt_, hule_, _תוכל, _बुरी_,
+ {{0x2d85a0aa,0x291d8870,0x394201bf,0x6f1c011f}}, // kule_, _akwa_, _hiks_, _ukrc,
+ {{0xd1308117,0x63bbdc91,0x3ea5829e,0xa96784bd}}, // ومت_, _erun, delt_, бија_,
+ {{0x3f84dc92,0x63bbd6e8,0xdb0d5c93,0xa3a90006}}, // tumu_, _frun, _traç, _गईल_,
+ {{0x63bb95ce,0x39420006,0x3ea5805f,0x7b07bb69}}, // _grun, _miks_, felt_, órtá,
+ {{0x3f84dc94,0x3ea5dc95,0xb6c68009,0x675380f7}}, // rumu_, gelt_, ссий, أخير,
+ {{0x63ad0c2e,0x3860036e,0x2d85db89,0x2a6e808e}}, // _ssan, kbir_, gule_, mafb_,
+ {{0x6d5a8df1,0x39420b3c,0x63ad01d0,0xdb045c96}}, // rpta, _niks_, _psan, _orió,
+ {{0x3860284e,0x3ea584fe,0xddc18b67,0x28c58bb8}}, // dbir_, belt_, ralš, वारि,
+ {{0x2d85b328,0x628a84fe,0xde58a1d2,0xd84f01bc}}, // bule_, ngfo, _такі_, _lọta_,
+ {{0xa3a905e8,0x2d858f77,0x13c380ab,0x31c40992}}, // _गैर_, cule_, ্নয়, пств,
+ {{0x63ad5c97,0xdb0401df,0x3f805c98,0x7ce619ae}}, // _tsan, _brió, friu_, _tóra,
+ {{0x69c80884,0x7d0d008e,0x61050110,0x6d4ab2dc}}, // [5430] äder, _ijas, nėli, _hufa,
+ {{0x6d4aa80d,0x3ce925f7,0x7c2d5c99,0x7c26936f}}, // _kufa, _hlav_, _izar, _úkry,
+ {{0x957c809a,0x39420147,0xd84f11d3,0x386001b9}}, // cząt, _fiks_, _bọta_, bbir_,
+ {{0x6d4a8deb,0x6f1ac78b,0x6aa70267,0x3f802484}}, // _mufa, hotc, lejf, briu_,
+ {{0x3f80009f,0xd37ba762,0x7d19dc9a,0x68ed498e}}, // criu_, уче_, rows, mhad,
+ {{0x6d58803b,0x68ed5c9b,0x4ea7002e,0xa5e71ea4}}, // _otva, lhad, _урма, _любл,
+ {{0x20d88013,0xda788c5c,0x6f1a8295,0x28c5809a}}, // _réir_, сяч_, dotc, वालि,
+ {{0x63bb8add,0x68ed5c9c,0xe0d99cd5,0x6f0880ee}}, // _trun, nhad, рви_, endc,
+ {{0x4439dc9d,0x6d588bb6,0x3ea5c5ea,0x63bbdc9e}}, // _às_, _atva, telt_, _urun,
+ {{0x2d85db89,0x6d4a930e,0x7ce6002a,0x7d0d07b8}}, // tule_, _bufa, _córn, _ajas,
+ {{0x3cca0abe,0x7079819f,0x3ea5ad03,0x7c2d5c9f}}, // ално_, _نماز_, relt_, _azar,
+ {{0x3ea58665,0xf2c90039,0x6d4adca0,0x3ce9026c}}, // selt_, _בע_, _dufa, _clav_,
+ {{0xa3e984e5,0x7c2d009a,0xdb04282b,0x2d8598d0}}, // मनि_, _czar, _asiá, sule_,
+ {{0x764700f1,0x7c2d00dd,0x7b1f8187,0x6f1a8362}}, // _ngjy, _dzar, cêut, cotc,
+ {{0xc5d58d13,0x442d801b,0x7c2d011e,0x6d4a82a0}}, // _філь, _lze_, _ezar, _gufa,
+ {{0x68ed125b,0x442ddca1,0xa92600e8,0x7d0d00f1}}, // ghad, _oze_, ідал, _gjas,
+ {{0x442d82ec,0x6ecd8035,0x6d4adca2,0x272f1238}}, // [5440] _nze_, तापु, _zufa, züne_,
+ {{0x6125808b,0x63a0808e,0x98b901a9,0x1db0dca3}}, // fólk, _rpmn, _visā_, _जनमत,
+ {{0x68ed5bae,0x394200eb,0x442ddca4,0x412a004a}}, // bhad, _tiks_, _aze_, _воно_,
+ {{0x68ed0782,0x0c26831f,0x3f804650,0x764907c0}}, // chad, омен, priu_, _şeyt,
+ {{0x8c3d8201,0xf77212dc,0x442d8035,0x78b60048}}, // _başq, _تاج_, _cze_, mdyv,
+ {{0x6d441bda,0xe81d8035,0xf21f03db,0x69c30118}}, // _liia, _पड़ा_, _मुड़_, _ánec,
+ {{0xfce68af2,0x442d8870,0xa7fb016a,0x84e6804a}}, // _дово, _eze_, _reñi, _довж,
+ {{0x6d5e0009,0x26d85ca5,0x68e45339,0x6d4aa813}}, // mppa, _horo_, nkid, _rufa,
+ {{0x6d58805c,0x27e02b5f,0x7c240a5a,0x4ea6bff1}}, // _stva, nyin_, _nyir, орна,
+ {{0x232a8698,0xf5498028,0x7f43c58e,0xd6da8db3}}, // _този_, _vấn_, _cinq, рти_,
+ {{0x290902a3,0x3ce9358f,0x81df00ab,0x7c2d29bd}}, // ynaa_, _plav_, _দেন_, _szar,
+ {{0x26d85ca6,0xf549801c,0x98a68e8e,0x6d444963}}, // _loro_, _tấn_, _фиде, _ciia,
+ {{0xa7fb0216,0x7ce62363,0x6d4a808e,0x25a98b80}}, // _teñi, _tórn, _wufa, _ćale_,
+ {{0x26d85ca7,0x6d4a854e,0xdb041041,0x394c826c}}, // _noro_, _tufa, _briñ, _kuds_,
+ {{0x68ed12be,0x5b14035f,0x6d58a1ad,0x6d440706}}, // thad, ємст, _utva, _fiia,
+ {{0x02b783bb,0x7d0d1fb6,0xb05b0106,0x6ab78072}}, // _आफ्न, _ujas, _gnäl, _आफ्र,
+ {{0x68ed1a67,0x26d8236a,0x7d1d2f5d,0xf7730277}}, // [5450] rhad, _boro_, moss, باز_,
+ {{0x68ed5ca8,0x7d1d2ada,0xd5bb023a,0x26d85ca9}}, // shad, loss, исе_, _coro_,
+ {{0x26d80135,0x254808c5,0xdb044752,0x68ed5caa}}, // _doro_, _oğlu_, _griñ, phad,
+ {{0x7d1d2a49,0xe3b19459,0xe4e48d8e,0x44248035}}, // noss, فرت_, мічн, _bym_,
+ {{0x26d85a5e,0xef9309d7,0x6b8291fe,0x7ba780f7}}, // _foro_, _زیاد, grog, تصام,
+ {{0x6d020fea,0xa6db007b,0x26d82eb7,0xdb0d1243}}, // _लगभग_, _auðv, _goro_, _araú,
+ {{0x7d1d15c8,0xddc38087,0x26c980ff,0x1a9c03de}}, // koss, _menţ, _đao_, ייגע,
+ {{0x26d80133,0x7f43dcab,0x6b8297d6,0xf8bf00ff}}, // _zoro_, _sinq, brog, _ghét_,
+ {{0x6d0205b3,0x7d1d04fa,0x442daa0c,0x61258825}}, // _लगबग_, doss, _uze_, nóli,
+ {{0xb05b016d,0x394c81a1,0x68e4011b,0x6d4402f1}}, // _snäl, _fuds_, zkid, _siia,
+ {{0xa3b38aed,0x7c24010b,0x394c81a3,0xe165803d}}, // टिक_, _syir, _guds_, _رضای,
+ {{0x692c01ec,0x499a8039,0x27e031e0,0x629e01a1}}, // eßen, _לשעב, yyin_, _sbpo,
+ {{0xddd50019,0xeb9715a6,0x395a0144,0x6d442446}}, // _mező, пия_, _stps_, _viia,
+ {{0xf09f0091,0x78a98365,0x249f8216,0x273406ae}}, // _abà_, leev, _lbum_, mäng_,
+ {{0x26d832e2,0x6f01dcac,0xc3328039,0xf1bd9c7b}}, // _roro_, milc, גום_, ्मिन,
+ {{0x80a18063,0x26d82d10,0x7d1d4e2a,0x6125dcad}}, // _कॉमे, _soro_, coss, fóli,
+ {{0x26d85cae,0xdb04007b,0x68e45caf,0x290005ee}}, // [5460] _poro_, _frið, rkid, riia_,
+ {{0x63a45cb0,0x68e41611,0x7cf60612,0x41bd89c2}}, // _ipin, skid, ršru, ्मास,
+ {{0x629c3b07,0x32db01c6,0x27f20035,0x4424dcb1}}, // mfro, _החינ, szyn_, _sym_,
+ {{0x31570158,0xf7461264,0xf09f10ab,0xf54980ff}}, // _מיטן_, _немо, _gbà_, _nấm_,
+ {{0xaac7823c,0x61258272,0xf8bf816f,0xddc500e1}}, // लांक, cóli, _शोधय, rahš,
+ {{0x628e2c15,0xb4e90743,0xe7370098,0x249f8133}}, // ngbo, _यत्_, _нея_, _ebum_,
+ {{0xada3024f,0x394c8a84,0xf549801c,0x7ce60118}}, // _парл, _quds_, _bấm_, _pórl,
+ {{0x4424a19c,0x26c78698,0xf54980ff,0x249fb7ee}}, // _tym_, _anno_, _cấm_, _gbum_,
+ {{0x40538013,0x6d4e19b0,0x2b4599dc,0x3ce6a39f}}, // رئيس, _huba, _filc_, mkov_,
+ {{0x6d4e06b0,0x3ce68db7,0x7d02dcb2,0x6f1e0503}}, // _kuba, lkov_, lios, nopc,
+ {{0x63a42b40,0x06970051,0x2d78042b,0x7d1d1f5d}}, // _apin, הדים_, nče_, toss,
+ {{0x6d4e257c,0x3ce6dcb3,0x7afe067f,0x7d02dcb4}}, // _muba, nkov_, _ampt, nios,
+ {{0xa847806b,0x6d4e5cb5,0x629c5cb6,0xe81c01ce}}, // _علوم_, _luba, ffro, _भुला_,
+ {{0x6d5c5aa8,0xa77b8051,0x5693838c,0xdb1b8388}}, // _otra, _פרופ, _зашт, _prué,
+ {{0xd3088104,0x3a250022,0x39469142,0x2d7819ce}}, // yện_, ælp_, _kios_, jče_,
+ {{0x58d42386,0x612587a3,0x6aaa8c19,0x3ce68619}}, // ност, tóli, heff, jkov_,
+ {{0x7d02dcb7,0x3ce6875f,0x84981921,0x3946da4a}}, // [5470] dios, dkov_, _رئيس_, _mios_,
+ {{0xe8df019d,0x6d4e5cb8,0xceb89c28,0xac990019}}, // _ipọd_, _buba, ndę_, _اچھا_,
+ {{0x6d4e2d2a,0x3f528028,0x7bc0dcb9,0x94d58221}}, // _cuba, _lâu_, _armu, можц,
+ {{0x2ea9801b,0xeae586a7,0xa7fb0216,0xe81d8f85}}, // _कस्त, कस्त_, _señu, _पडला_,
+ {{0x212914ec,0x6d5c0ee7,0xddc3826f,0xa6db007b}}, // mlah_, _etra, _menš, _suðu,
+ {{0x212904aa,0x7b3981bc,0xf549827d,0xaac781a2}}, // llah_, _aňur, _sấm_, लाइक,
+ {{0x6d5c5579,0x6d4e4c45,0x6f01d02e,0x7bc089e8}}, // _gtra, _guba, vilc, _ermu,
+ {{0x21290867,0xb925046d,0xe8df81bc,0x27340338}}, // nlah_, _aipẹ_, maịa_, täng_,
+ {{0x3946b1a2,0x3f528028,0x6d4e5cba,0x21290859}}, // _dios_, _câu_, _zuba, ilah_,
+ {{0x60069006,0x21290057,0x3ceddcbb,0x3cff822c}}, // ьным_, hlah_, _hlev_, _hmuv_,
+ {{0x39468046,0x212914ff,0x6d5c4818,0x34aa140b}}, // _fios_, klah_, _xtra, овно_,
+ {{0x13e180c8,0x6f01807a,0xeb9a5cbc,0x59a68f3d}}, // _মেয়, silc, оим_, _कैटर,
+ {{0x4c9a8039,0x6f01dcbd,0xa2c28072,0x80098061}}, // חברו, pilc, लाच्, _براہ_,
+ {{0x7d02dcbe,0x66058c40,0xfe7180d5,0x6d5a00e7}}, // zios, _опла, _شدت_, _étag,
+ {{0x4343067c,0xa3b4800d,0x212900ee,0x7b090267}}, // терв, _छैन_, flah_, džud,
+ {{0x6d4e5cbf,0x7d0284c3,0x52da8074,0x2129036e}}, // _ruba, xios, _बक्स, glah_,
+ {{0x6d4e3bd5,0x3ce69fc2,0x27308362,0xd24f0065}}, // [5480] _suba, vkov_, làna_, انہ_,
+ {{0xe73701a1,0x212910e1,0x6c680065,0x61e381b0}}, // деу_, alah_, _بلکہ_, kynl,
+ {{0x3ce6a52d,0x3ced93c2,0x550206b7,0x2734016d}}, // tkov_, _blev_, _लगाए_, vänd_,
+ {{0x25d6893f,0x7e6e5cc0,0x2d85954c,0x2d538073}}, // ווען_, _webp, irle_, _mãe_,
+ {{0x69c1dc45,0x5f760eca,0x6b9981c0,0xb4ae83eb}}, // _arle, _تاجر, gtwg, _कसी_,
+ {{0x3ce6dcc1,0x98b90110,0x450600ab,0x2d780654}}, // skov_, _visą_, ৈতিক_, pče_,
+ {{0x6aaa949d,0x3f52801c,0x05660d8e,0xda001513}}, // reff, _sâu_, _іван, ोपित_,
+ {{0x3eb8115b,0x57b4891c,0x6b8bdcc2,0xceb8809a}}, // _hart_, ебит, bugg, wdę_,
+ {{0x69c1c751,0xb7db0158,0x394689da,0x3eb80b0b}}, // _erle, ַקטי, _vios_, _kart_,
+ {{0xddce0bb6,0x6a83896f,0x75241c40,0x81bc80eb}}, // _nebū, _алфа, _akiz, _dzēr,
+ {{0x3eb805b2,0xceb30039,0x2120026c,0x7ce60061}}, // _mart_, דית_, koih_, _kórh,
+ {{0x6ee4809f,0xa3b58074,0x3eb80168,0x0fb683b7}}, // _mòbi, _जनम_, _lart_, _अनपढ,
+ {{0x44320d38,0x29048025,0xd943b714,0x2d85900a}}, // _czy_, mima_, _реци, arle_,
+ {{0x2904dcc3,0x1eaa81ad,0x75240365,0x28e10075}}, // lima_, _ذاتي_, _ekiz, _नवहि,
+ {{0x39150381,0x21290867,0x98a68035,0x26cd00ff}}, // емвр, tlah_, _choć_, _đeo_,
+ {{0x212909ca,0xf8bf00ff,0x61258118,0x3eb85cc4}}, // ulah_, _chép_, sólv, _aart_,
+ {{0x2129372d,0xf7700bbe,0x2d8cdcc5,0x26dc8110}}, // [5490] rlah_, لال_, lude_, _kovo_,
+ {{0x44295cc6,0x21290057,0x2904dcc7,0x26dc8b80}}, // _iya_, slah_, hima_, _jovo_,
+ {{0x2d8c803b,0x61e100f2,0x2129010b,0x3ced81c0}}, // nude_, älld, plah_, _plev_,
+ {{0x29048025,0xd25109d7,0x44290c56,0xf8bf00ff}}, // jima_, _جنگ_, _kya_, _ghép_,
+ {{0x290482a5,0x80cc020e,0xb87b01a8,0x2d8cdcc8}}, // dima_, हारे, _dlít, hude_,
+ {{0x26dcdcc9,0x41b5a457,0x78a28039,0x659513bd}}, // _novo_, есит, _abov, _пагу,
+ {{0x60dd2f16,0x442902ec,0x2d8cdcca,0xdb098362}}, // _kosm, _lya_, jude_, _breò,
+ {{0x4429148e,0x290d802e,0x2d8cdccb,0x644d01a8}}, // _oya_, unea_, dude_, _ngai,
+ {{0x44295ccc,0x61e38aa2,0x290db349,0xf0768019}}, // _nya_, synl, rnea_, ریوں_,
+ {{0x4d6585c2,0x644d435c,0x26dc80e5,0x75245ccd}}, // нков, _agai, _covo_, _skiz,
+ {{0x44295cce,0x29048503,0x7c29ae3a,0x644d00dd}}, // _aya_, bima_, _hyer, _bgai,
+ {{0x29048025,0x442901d3,0x09bd84e5,0x998d009a}}, // cima_, _bya_, ्मीय, łeś_,
+ {{0x44290578,0x644d0580,0xb87b1243,0x78ad39ff}}, // _cya_, _dgai, _moíd, deav,
+ {{0x2d8c803e,0x67238289,0x68e9dccf,0x91fc80eb}}, // bude_, _uknj, lked, _teāt,
+ {{0x28d385e8,0xf8bf0028,0x672186ec,0x60dd1de6}}, // थापि, _phép_, molj, _bosm,
+ {{0x68e9dcd0,0xb4ae816f,0x672182ce,0xa3cf800c}}, // nked, _कसे_, lolj, षमा_,
+ {{0x7c29dcd1,0x3eb80809,0xa7a70912,0xf77201a8}}, // [54a0] _nyer, _part_, нкта_, طاء_,
+ {{0x290493b2,0x7d06388d,0x6721dcd2,0x78b981c0}}, // zima_, liks, nolj, _lawv,
+ {{0x7c29ab5f,0x3eb8021e,0x68e9928d,0xf8bf00ff}}, // _ayer, _vart_, kked, _thép_,
+ {{0x61e10c3c,0x78ad0749,0x7c2980b4,0xb87b3dcd}}, // älle, ceav, _byer, _coíd,
+ {{0x2904803b,0xa29485e9,0xd3268c4f,0xdce20713}}, // vima_, валі, ньки, ntlı,
+ {{0x60cb828c,0xd24f9ef7,0xdb098144,0x67218115}}, // _angm, منه_, _breñ, jolj,
+ {{0xfbd880c8,0x6721811f,0xe7370749,0x2b5f8197}}, // _দেখত, dolj, _чер_, _etuc_,
+ {{0xd3728077,0x859b0039,0x673e00f3,0x7bc45cd3}}, // _مهر_, _משלו, lmpj, _oriu,
+ {{0x7c298065,0x7d065cd4,0x6d5a02be,0x26dc8317}}, // _gyer, diks, _étab, _povo_,
+ {{0x2d8ca4c4,0x798e2169,0x442902a0,0x75228c2e}}, // tude_, kubw, _rya_, looz,
+ {{0x44295cd5,0x7bc45cd6,0x23605cd7,0x7d06377c}}, // _sya_, _ariu, _atij_, fiks,
+ {{0x399a80f1,0x2d8cdcd8,0x2ba78054,0x9c130135}}, // mës_, rude_, _गहमा, kọta,
+ {{0x66e6a80f,0x24800355,0xdee68ff7,0x399a80f1}}, // _пода, _ddim_, _поди, lës_,
+ {{0x442900f6,0x657c35ca,0xd5b0803d,0x26dc847f}}, // _vya_, _ovrh, _نفت_, _uovo_,
+ {{0x399a820f,0x28c58996,0x60dd0a25,0x51840adb}}, // nës_, वाचि, _posm, лута,
+ {{0x88bd8063,0x61f58019,0x28a680d4,0x7bc4047f}}, // mośc, _üzle, _कॉमि, _friu,
+ {{0x81df00c8,0x399a80f1,0x61e10106,0x4429036a}}, // [54b0] _দেশ_, hës_, ällb, _uya_,
+ {{0x399a820f,0x68e98061,0x7c29dcd9,0xb87b128a}}, // kës_, zked, _ryer, _plís,
+ {{0x88bd8063,0x6d4980eb,0xe31400f7,0x399a80f1}}, // nośc, _piea, _حبيب, jës_,
+ {{0x30790158,0x399a8168,0xa87903de,0x69c50362}}, // _קאָנ, dës_, _קאָר, _irhe,
+ {{0x38691295,0x06b780c8,0x98ab0035,0xeb998b5b}}, // mbar_, জাতি, _chcą_, фил_,
+ {{0x6aba9c33,0x88bd809a,0x7d065cda,0x78b98282}}, // _catf, kośc, ziks, _sawv,
+ {{0x399a80f1,0x752282ec,0x6ca78084,0x68e9d8ce}}, // gës_, booz, _праж, tked,
+ {{0x38695cdb,0xdddc0214,0x629c8106,0x610881d0}}, // nbar_, _herş, _öron, děls,
+ {{0x68e98a38,0x7d062818,0xa3b586ae,0x78bbd3a9}}, // rked, viks, _जनि_, rduv,
+ {{0x3f83dcdc,0x68e9dcdd,0x38695cde,0x0ab488ca}}, // čju_, sked, hbar_, _محمد,
+ {{0xee398987,0x7d061ffe,0x940c0201,0x399a8168}}, // ьно_, tiks, ədə_, cës_,
+ {{0x7bc4011f,0xe945803d,0xa3b5a743,0x60cba3a6}}, // _priu, _شرای, _जना_, _ungm,
+ {{0x67218067,0x3f8d8722,0x2ef59a19,0xde16803d}}, // polj, queu_, _издр, فقیت_,
+ {{0x212da88e,0x2d6e801b,0xe8ca001b,0x29020748}}, // lleh_, bře_, ाञ्च, _smka_,
+ {{0x6b9d115b,0x412a04db,0xb87b02df,0xd04d0326}}, // atsg, дово_, _alíq, nkaɗ,
+ {{0x63a9c755,0x3f895cdf,0x6ba50338,0x2fc05ce0}}, // _open, frau_, åtgä, tvig_,
+ {{0x62818db7,0x657c003a,0xd007091e,0x61430adb}}, // [54c0] _odlo, _svrh, вете_, рета,
+ {{0x75228c56,0x45140264,0x2fc05297,0xddc701f4}}, // wooz, _সঠিক_, rvig_, _sejš,
+ {{0x63a99820,0x386915f4,0x9d468098,0xdd8f0f99}}, // _apen, bbar_, вежд, قول_,
+ {{0x399a820f,0x7afd81e2,0x80a19d7c,0x62818428}}, // vës_, _įsta, _कॉले, _adlo,
+ {{0x29120393,0x8b9681a0,0x39405ce1,0xc1da928a}}, // mnya_, треч, mmis_, यङ्ग,
+ {{0x399a820f,0x291213b8,0x394014c7,0x68f65ce2}}, // tës_, lnya_, lmis_, lhyd,
+ {{0x7d0450a7,0x291230a0,0x63a9dce3,0x7f4a808e}}, // _omis, onya_, _epen, _rifq,
+ {{0x291223d5,0x399a820f,0x52df3852,0xfce38237}}, // nnya_, rës_, _नक्स, _косо,
+ {{0x88bd8063,0x8afd800d,0x8c43a7cb,0x29120057}}, // wośc, _stře, _вете, inya_,
+ {{0x29120393,0x7d045ce4,0x399a8168,0x310210f7}}, // hnya_, _amis, pës_, रोतः_,
+ {{0x2912048f,0x7bc293f2,0xfdd08264,0x38695ce5}}, // knya_, lvou, ান্ড, ybar_,
+ {{0x2912036e,0x6eed8118,0x6b9d0fb6,0x657adce6}}, // jnya_, _fúbo, ttsg, rsth,
+ {{0x29120867,0x7bc285f8,0x38695ce7,0xf54a001c}}, // dnya_, nvou, vbar_, _nấu_,
+ {{0x2caf80f3,0x2fc69fdb,0x29123bdf,0x6b9d27d1}}, // zegd_, _irog_, enya_, rtsg,
+ {{0x2912010b,0x394b5a44,0x386901ec,0x8c3da795}}, // fnya_, _pics_, tbar_, _daşy,
+ {{0x7d1600f1,0x291210e1,0x2fc6dce8,0x99bb8039}}, // _gjys, gnya_, _krog_, וזלט,
+ {{0xa2d00076,0x383509b4,0x38695ce9,0x386605e4}}, // [54d0] दाश्, унар, rbar_, ñora_,
+ {{0xf54a0028,0x29121a14,0x2d9e837a,0xb87b12ca}}, // _dấu_, anya_, otte_, _elíp,
+ {{0x29120057,0x69c515ec,0x63a9b7eb,0x272f0019}}, // bnya_, _urhe, _spen, lünk_,
+ {{0x7ce62660,0x29120101,0x27398036,0x7c2d2e22}}, // _fóru, cnya_, mène_, _kyar,
+ {{0xceeb09d7,0x2fc690af,0xc48481e5,0x27398036}}, // _قرآن_, _nrog_, рлік, lène_,
+ {{0x68ed0c9a,0x8f9a0039,0xe29680a9,0xf8d1016f}}, // lkad, _אישי, гаш_, हावय,
+ {{0x78bd0a92,0xf2d40158,0x6eeddcea,0xeaf38b9f}}, // _kasv, יעס_, _súbo, _अतीत_,
+ {{0xa502016f,0x272f0061,0x6d4d0bb1,0x7c2d5ceb}}, // _लगेच_, künk_, _niaa, _oyar,
+ {{0x27e95cec,0xf54a001c,0x7c2d4100,0x63a98010}}, // nyan_, _mất_, _nyar, _upen,
+ {{0x6d552221,0x442dc2f5,0x2fc69be9,0xc9879033}}, // _kuza, _iye_, _drog_, _ауди,
+ {{0x442dba5f,0xde05b0c3,0x2912015d,0x7c2d5ced}}, // _hye_, упни, ynya_, _ayar,
+ {{0x78bd0db7,0x6d555cee,0x7c2d02a0,0x27e9040a}}, // _nasv, _muza, _byar, kyan_,
+ {{0xbea59285,0xdb0d05e4,0x6d550102,0xe72ea6b1}}, // _балк, _arañ, _luza, _ге_,
+ {{0x442d835f,0xdb0d01df,0x29120101,0x27e95cef}}, // _mye_, _brañ, wnya_, dyan_,
+ {{0x7ae1dcf0,0x2912048f,0xf54a00ff,0x442ddcf1}}, // _molt, tnya_, _bất_, _lye_,
+ {{0x442dd4f7,0x61e1016d,0x7ce6002a,0xb4db0722}}, // _oye_, älla, _córt, rmàc,
+ {{0x29120393,0x7e7509a4,0x442dd0e2,0x27e95cf2}}, // [54e0] rnya_, _bezp, _nye_, gyan_,
+ {{0x29120393,0x4df60364,0x6d55505b,0x98a02663}}, // snya_, ляет, _buza, klić_,
+ {{0x291213b8,0x442dc2f5,0x4a9b0158,0x1a9b00be}}, // pnya_, _aye_, _אייג, _אייע,
+ {{0x442daea5,0x68ed00f2,0x753bdcf3,0x2d8705e4}}, // _bye_, ckad, _chuz, ánea_,
+ {{0x7ae1dcf4,0xc879807e,0x7fd50558,0x442d80b4}}, // _bolt, daş_, _кірі, _cye_,
+ {{0x2d9e8aa2,0x78bd141f,0x68e30087,0x6b84826c}}, // ytte_, _zasv, _îndr, šigo,
+ {{0xee370611,0x6abe428b,0x272f0061,0x98a006ec}}, // _бнр_, _mapf, zünk_, glić_,
+ {{0x8c469071,0x442d86c4,0x3ea7841c,0x7df3811c}}, // _бебе, _fye_, _abnt_, cəsə,
+ {{0x2ef4938c,0x6d55016b,0x442d8c56,0x98ab0035}}, // изир, _zuza, _gye_, _chcę_,
+ {{0x6d4d008e,0x7f4e01b9,0x68ed5cf5,0x7c2d00b4}}, // _siaa, _jibq, zkad, _ryar,
+ {{0x7c2d14ec,0x6d4d0122,0xd884803d,0x645a01d0}}, // _syar, _piaa, _نهای, ětin,
+ {{0x27e9068a,0x272f0019,0x3f925cf6,0x27399f1b}}, // yyan_, tünk_, kuyu_, vène_,
+ {{0xf54a0104,0x78bd5cf7,0x2bc7902e,0x2d9ed2a4}}, // _rất_, _rasv, लिया, stte_,
+ {{0x78bd0025,0xbebc8029,0x7ce600f7,0x88bd8035}}, // _sasv, glīt, _sórt, nośn,
+ {{0xc3338051,0x7ce62c6c,0x78bd5cf8,0x7e75008e}}, // מוש_, _pórt, _pasv, _rezp,
+ {{0x6d550314,0x68e2c6f7,0x27e95cf9,0x27398866}}, // _ruza, _jood, tyan_, rène_,
+ {{0x68ed5cfa,0x68e29f61,0x629ab4f8,0x7c2d0214}}, // [54f0] rkad, _mood, _octo, _uyar,
+ {{0x3f87003a,0xdef807ac,0x68ed5cfb,0x200d0125}}, // čnu_, лых_, skad, _þeim_,
+ {{0xf54a0028,0x54559a47,0x27e95cfc,0x442ddcfd}}, // _tất_, авет, syan_, _sye_,
+ {{0x68e280c9,0xa3e909a9,0x442d8247,0x7ce601df}}, // _nood, _यथा_, _pye_, _hórr,
+ {{0x69c89b6d,0xe2999ccf,0x6d5a02be,0x7ae1da96}}, // _arde, вал_, _étan, _polt,
+ {{0x63ad2479,0x6d55362a,0x442d86c0,0x084e8133}}, // _mpan, _tuza, _vye_, _kọfị_,
+ {{0x7ae1dcfe,0x68e2dcff,0xdb00806a,0x2ca900c3}}, // _volt, _bood, _opmæ, _obad_,
+ {{0x3f9f8009,0x442d867f,0x98a0076c,0x00e58087}}, // ttuu_, _tye_, rlić_, ржин,
+ {{0x68e29e94,0x2d8c85f8,0x442d8234,0x60dbbf72}}, // _dood, erde_, _uye_, rjum,
+ {{0x2ca9059e,0x7d0b9277,0x63bba266,0x26c100d7}}, // _abad_, ligs, _isun, ndho_,
+ {{0xa2d90e70,0x68e2bcc0,0x63ad5d00,0x3f9f8009}}, // मान्, _food, _apan, stuu_,
+ {{0x7bc9dd01,0x7d0bd83a,0x68e280ef,0x8c460249}}, // _kreu, nigs, _good, иене,
+ {{0x61e100f2,0x2d8cdd02,0x5fa9801b,0x2cbfa0db}}, // älln, arde_, _कहिल, _maud_,
+ {{0xf7708077,0x2cbfc950,0x386d82af,0x7ce61b07}}, // _راه_, _laud_, lber_, _córr,
+ {{0xf8bf08f9,0x63ad5d03,0x7d0b833e,0x68e28037}}, // _baé_, _epan, kigs, _yood,
+ {{0xc95501e2,0x7866259a,0x394f81e0,0x3f5b801c}}, // стры, рказ, _jigs_, _kêu_,
+ {{0xb8de0610,0x386d81ec,0x394f82c4,0xaf065d04}}, // [5500] _इस_, iber_, _migs_, апал,
+ {{0x394f80b9,0x8ca70035,0x657e01ec,0x543b03de}}, // _ligs_, ट्ठो, tsph, _בעפא,
+ {{0x386ddd05,0xd91009d7,0x63bbdd06,0x98a61ad2}}, // kber_, _فیس_, _asun, _кине,
+ {{0x64a6a549,0x0b8a9a19,0x7bc981ba,0xdca69fab}}, // рада, вски_, _breu, ради,
+ {{0x7bc9dd07,0x2cbf800e,0x93468294,0x68e2800b}}, // _creu, _daud_, анде, _rood,
+ {{0x7bc9dd08,0x3f8d8722,0x68e290b5,0xfc3f0511}}, // _dreu, dreu_, _sood, nfía_,
+ {{0x7e7a8b81,0xaca481bc,0x68e282f1,0xb05b0106}}, // vatp, _agụp, _pood, _knäp,
+ {{0x7bc9dd09,0xa7748193,0x386041ac,0x2d8c817f}}, // _freu, блич, ncir_, vrde_,
+ {{0x7bc9dd0a,0x5fc903ca,0x6fc906f0,0x68e282f1}}, // _greu, रियल, रियं, _vood,
+ {{0x2cbf8282,0x9ed88187,0x28a6864a,0x8544819d}}, // _zaud_, умот_, _कॉरि, _ịghọ_,
+ {{0xd5b7902a,0x386d93e1,0x68e28074,0x07370039}}, // ася_, bber_, _tood, שאים_,
+ {{0x186aa466,0xa06abfe7,0x6fc900d4,0x3f8d9e9e}}, // _ради_, _рада_, रिमं, breu_,
+ {{0x63a2bb4f,0xda788038,0x92d880ab,0x07da803d}}, // nton, _keď_, াসী_, _جذاب_,
+ {{0x6298bbd9,0x81c781a9,0x39b80176,0x63a282df}}, // ngvo, stēš, _bōs_, iton,
+ {{0xed578003,0xb6cb8065,0x58d51182,0x7c872118}}, // _кој_, _جانے_, бовт, _купе,
+ {{0x3f5b8104,0x63a2dd0b,0xa3d6902e,0x26de826c}}, // _yêu_, kton, िमा_, ljto_,
+ {{0x63ad5672,0xfb85003d,0xdddaa57b,0x752d3977}}, // [5510] _upan, _ادبی, katū, _ikaz,
+ {{0x6d58dd0c,0x28a69516,0x2cbf8069,0x2d8705e4}}, // _kuva, क्ति, _saud_, áneo_,
+ {{0xdfc68019,0x386ddd0d,0x2cbf8101,0x7d0bcac3}}, // _کي_, yber_, _paud_, tigs,
+ {{0x7c878af2,0x6d58dd0e,0x63a2dd0f,0x237f011f}}, // руже, _muva, fton, šuju_,
+ {{0x7ae5247f,0x672884a8,0xd3088028,0xe78419b8}}, // _koht, kodj, yệt_, _муро,
+ {{0x7ae50364,0x7bc99004,0xf54a0129,0x2d93002a}}, // _joht, _vreu, _lấp_, puxe_,
+ {{0x752d5d10,0xceb20039,0x386d808e,0x6d58dd11}}, // _okaz, _מים_, tber_, _nuva,
+ {{0x63bb9cc5,0x7bc9dd12,0x6ea9800d,0x3f8ddd13}}, // _tsun, _treu, ज्नु, treu_,
+ {{0x63bbb76e,0x7d09bb44,0x6edb06ab,0x394f867f}}, // _usun, _imes, नापु, _vigs_,
+ {{0x3f8d895b,0x6d58dd14,0x67288916,0x752d409f}}, // rreu_, _buva, godj, _akaz,
+ {{0x776983a8,0x69c702ce,0x2d8100a4,0x6d5894a9}}, // spex, jvje, nshe_, _cuva,
+ {{0xf54a0104,0x69c702ce,0xf1c38110,0x6d58dd15}}, // _cấp_, dvje, rašė_, _duva,
+ {{0x7052806b,0x67288699,0xa8568039,0x7d0980de}}, // _عنوا, bodj, _הינה_, _mmes,
+ {{0x7d1bdd16,0x7c3b8010,0x543a80be,0x626324a4}}, // _ljus, _mzur, טערא, звра,
+ {{0x27eddd17,0x5d858013,0xc33284de,0x63a28019}}, // lyen_, _البل, פול_, zton,
+ {{0xf54a001c,0x7d1b811f,0x7c3b8133,0xa5649ef7}}, // _gấp_, _njus, _ozur, _بدون,
+ {{0x27ed8205,0x7c3b8010,0x38601b01,0x27ff8300}}, // [5520] nyen_, _nzur, rcir_, nzun_,
+ {{0x7d09b517,0x7d1bb7dd,0x7e5623cd,0xf6560e02}}, // _ames, _ajus, стац, стаю,
+ {{0x7c3bdd18,0x2734016d,0x3f80023e,0x463a15a6}}, // _azur, länk_, ssiu_, учая_,
+ {{0x6723003a,0x63a2cdb0,0x6eed84c3,0x6d5a5d19}}, // čnja, tton, _dúbi, _étam,
+ {{0x2ca6831d,0x27ed90ed,0x611398ad,0x68fbdd1a}}, // rfod_, jyen_, căli, dhud,
+ {{0x7d09dd1b,0x27ed86c0,0x64410085,0x60c45d1c}}, // _emes, dyen_, əlid, ldim,
+ {{0xc05a8a4c,0xddda9482,0x6728cd57,0x7c3b81bc}}, // ліп_, ratū, vodj, _ezur,
+ {{0x60c45d1d,0xdca38d9e,0x64a3be80,0x63a2dd1e}}, // ndim, _наси, _наса, pton,
+ {{0x27ed8065,0x6d58a0a2,0xda78801b,0xe762819d}}, // gyen_, _suva, _teď_, _bọọs,
+ {{0xc6f7827e,0xf76f96a5,0xdb040118,0x3f8a816b}}, // йных_, راً_, _osiñ, čbu_,
+ {{0x67289024,0x6b845d1f,0x6eed8118,0x67fe8032}}, // rodj, _avig, _xúbi, _bàjé,
+ {{0x27ed8247,0x8af98201,0x67289455,0xa1578039}}, // byen_, rləş, sodj, _הבאה_,
+ {{0x7e7e079f,0x6b829a90,0xf54a0129,0xddd50176}}, // kapp, nsog, _vấp_, _egzň,
+ {{0x64560065,0x6b841752,0x6b890289,0x6d58dd20}}, // _egyi, _dvig, šego, _tuva,
+ {{0x6b840bbd,0x752d18ad,0xddc8b3b5,0x7e7e3ffb}}, // _evig, _tkaz, ladž, dapp,
+ {{0x752d1620,0x3e4e8085,0x61138162,0x79950435}}, // _ukaz, mət_, tăli, ruzw,
+ {{0x69c72b1f,0xe80303eb,0xddc8803b,0x14d623e6}}, // [5530] svje, _रेखा_, nadž, धारण,
+ {{0x6d40dd21,0x7d09dd22,0x7bcd0102,0x2d8101f6}}, // _ahma, _smes, _irau, tshe_,
+ {{0x6d408267,0xddc888ae,0x3e4e8085,0x2c5e8133}}, // _bhma, hadž, nət_, _ịda_,
+ {{0x60c2cba7,0x7bcd2ab6,0x2d815d23,0x27edaade}}, // _naom, _krau, rshe_, yyen_,
+ {{0x889a04de,0xddc88bda,0x7e7e5d24,0x7d09807a}}, // _דברי, jadž, bapp, _vmes,
+ {{0x28a68701,0x7e7e156f,0x395a02f7,0x776d03a8}}, // क्सि, capp, _yups_, mpax,
+ {{0xdd94076a,0x6aaa0352,0xa3b58054,0x659410f8}}, // _нары, _öffe, _जनक_, _нару,
+ {{0x7d09dd25,0x27ed86c0,0x27ff8102,0x7a40008b}}, // _umes, tyen_, tzun_, _hátí,
+ {{0xe8f686b5,0xb87b05b9,0x7bcd01e9,0x23690282}}, // іль_, _blíz, _nrau, _ntaj_,
+ {{0x0d85aded,0x27eddd26,0x05a997ae,0x45858d9e}}, // олон, ryen_, авой_, огов,
+ {{0x27eddd27,0x7bcd011e,0x26c35d28,0xc6920039}}, // syen_, _arau, _majo_, ואי_,
+ {{0x7bcd1db4,0x7a428019,0x03a60009,0x2d8b81d0}}, // _brau, síté, оимо, čce_,
+ {{0x28c40b9f,0xb87b0118,0xdd0c809a,0x7bcd01e0}}, // _लोगि, _coím, _późn, _crau,
+ {{0x7bcd5d29,0x366984ae,0x3e4e8085,0x61ee5d2a}}, // _drau, _тако_, bət_, tybl,
+ {{0x7bcd0cdb,0xd5ac8065,0x23695d2b,0x2d8700e1}}, // _erau, _یہی_, _etaj_, šnej_,
+ {{0x26c3046d,0x60c40110,0x290b00b9,0x44200069}}, // _aajo_, udim, _cmca_, _txi_,
+ {{0x26c305b4,0x6b8401df,0x6569026c,0x07a31a80}}, // [5540] _bajo_, _uvig, _čeha, матн,
+ {{0x249f8012,0x57ba000d,0x69c80106,0x6d408168}}, // _acum_, _उनीह, ådes, _shma,
+ {{0xbc6a8277,0xe45a9628,0xa2dd801b,0x57b68074}}, // لمان_, аже_, पान्, _अन्ह,
+ {{0x629c26a1,0x60c4011c,0x9b6a9ddf,0xe0df0229}}, // lgro, qdim, ашка_, _chòd_,
+ {{0xf746079e,0x8bb6803d,0x7e7e0bcb,0x63a601e0}}, // _мемо, تصاص, papp, ntkn,
+ {{0x629c5d2c,0x3e4e8201,0x3872004a,0x2fcf006a}}, // ngro, yət_, lbyr_, ægge_,
+ {{0x14ca009a,0x6b82cf69,0xaacb001b,0xe6170162}}, // ियाण, ssog, ायतक, _ддр_,
+ {{0x6d5c5d2d,0x37aaa21f,0x89db01c6,0x387fa476}}, // _iura, атон_, שחקי, gaur_,
+ {{0x28a6853f,0x6d5c15e8,0xd7fac4e0,0x63ad80f1}}, // क्षि, _hura, руй_, çant,
+ {{0xddc8876c,0x2018802e,0x60c28c53,0x2ca02093}}, // radž, ări_, _waom, _acid_,
+ {{0x7e7c0642,0x24890101,0x60c29581,0xb65b00be}}, // _merp, _pdam_, _taom, ידיש,
+ {{0xcd07035f,0x3e4e8201,0x30c500ab,0x7cff03ed}}, // ічни, rət_, _এক্স, _mërg,
+ {{0x81af00c8,0x7ac72410,0x38cb826a,0x6d5c1651}}, // ওয়া_, осве, _خالی_, _lura,
+ {{0x2d87000d,0x6eed846d,0x6d5c1d5e,0x629c0901}}, // ánek_, _búbu, _oura, ggro,
+ {{0x7ae88022,0xc0d1026b,0x7d0f01ed,0x0e7580e1}}, // _modt, _akíọ, pics, äčší,
+ {{0x58d41485,0x26c337d7,0x63a40168,0x7bcd0d88}}, // мост, _sajo_, _fqin, _trau,
+ {{0x7e7c0393,0x7cff020f,0x6d5c5d2e,0x6fbc81b6}}, // [5550] _berp, _përf, _aura, ्टिं,
+ {{0x6d5c448d,0x7e7c0b20,0x8ca70aed,0x539a0039}}, // _bura, _cerp, ट्रो, _פיתו,
+ {{0x6d5c5d2f,0x7e7c0ed0,0x2bc311be,0x7bc0dd30}}, // _cura, _derp, शिका, _asmu,
+ {{0x7cff00f1,0xf992010f,0xd7bc8aed,0xfd6281bc}}, // _dërg, _ארט_, ्टाच, _satọ,
+ {{0x39495d31,0x7d0d0135,0x3af58d15,0xe0df5d32}}, // mmas_, _mmas, зяйс, _fiòr_,
+ {{0x6d5c1fcc,0x39493b78,0x386681bc,0x8505881b}}, // _fura, lmas_, _afor_, _کورن,
+ {{0x39490207,0x6d5c5d33,0x2d8add34,0x7d0d5d35}}, // omas_, _gura, ábel_, _omas,
+ {{0x69cc016f,0x39495d36,0x28bd90a1,0x6b4b0372}}, // हिती, nmas_, ्यधि, _iżgu,
+ {{0x4439c92f,0x7f5d02be,0x2d9e016d,0x394901a9}}, // _ás_, _jusq, _äter_, imas_,
+ {{0x7d0d199f,0x6d5c5d37,0x387fdd38,0x443f8870}}, // _amas, _yura, saur_, _izu_,
+ {{0x6d5c09e3,0xdcfb0110,0xcee8803d,0x8cd803eb}}, // _xura, nutė, ترين_, यालो,
+ {{0x80a283b7,0x629c43e8,0x26c382d4,0x7d0d0706}}, // _गाये, wgro, žjo_, _cmas,
+ {{0x443f8267,0x629c1336,0x98e481a8,0x087683de}}, // _jzu_, tgro, _تكنو, _זענט_,
+ {{0xdcfb00eb,0x7d0d4c47,0x6569026c,0x254d01d0}}, // rstā, _emas, _čehn, _měly_,
+ {{0x629c110f,0xbbb88f97,0x867a81c6,0x91e600ae}}, // rgro, _इन्क, _הרצו, чове,
+ {{0x6d5a03d3,0x7f5d2eaa,0x443fdd39,0x629c06a8}}, // _étai, _busq, _ozu_, sgro,
+ {{0x6d5c0763,0x81b180ab,0x6d56076d,0x629c13fc}}, // [5560] _sura, টটি_, _iiya, pgro,
+ {{0x7cff08cf,0x6d5c3343,0xaa461677,0x6d560637}}, // _përg, _pura, _непл, _hiya,
+ {{0x7e7c1781,0xfc668698,0x6d5c5d3a,0x2561b2c4}}, // _verp, пълн, _qura, _jól_,
+ {{0x6fc9056b,0x6d5c07c0,0x6d56203d,0x39495d3b}}, // रिशं, _vura, _jiya, cmas_,
+ {{0x7e7c0393,0x6d5635fe,0x6d5c04b9,0x6d440c41}}, // _terp, _miya, _wura, _mhia,
+ {{0x29125d3c,0x6d5c5d3d,0x6d5643da,0x6ef680e7}}, // liya_, _tura, _liya, _câbl,
+ {{0x3f86801b,0x443f8135,0xdcfb0162,0x27f20198}}, // _svou_, _ezu_, nstă, lyyn_,
+ {{0x29125d3e,0x6d565c6d,0x290003a7,0x68e45d3f}}, // niya_, _niya, nhia_, njid,
+ {{0x60c6045f,0x69c1811b,0x7c2403ed,0x387d9bad}}, // _makm, _esle, _nxir, _dewr_,
+ {{0xd6da92e1,0x6d4401bc,0x29121066,0x6440dd40}}, // сти_, _ahia, hiya_, _izmi,
+ {{0x6d565d41,0x39490110,0x29125d42,0x2fc700ff}}, // _biya, ymas_, kiya_, ̉ng_,
+ {{0x6d445d43,0x6d564672,0x66010214,0x29121cee}}, // _chia, _ciya, _ülke, jiya_,
+ {{0x6d565d44,0x29121037,0x29005d45,0x6d445d46}}, // _diya, diya_, dhia_, _dhia,
+ {{0xdee39260,0x66e3835f,0x28da8aad,0x60c602c4}}, // _почи, _поча, भावि, _aakm,
+ {{0x7cff020f,0x6d5607d9,0x291200a4,0x6d440083}}, // _përd, _fiya, fiya_, _fhia,
+ {{0x29125d47,0x39495d48,0x6d4408d8,0x7d0d5d49}}, // giya_, umas_, _ghia, _umas,
+ {{0xb8e52539,0x60c615d7,0x80a1d59b,0x395e852a}}, // [5570] _एस_, _dakm, क्टे, _luts_,
+ {{0x6d5642e1,0x39495d4a,0xbbc906b7,0x98b201a9}}, // _ziya, smas_, िटीक, ībās_,
+ {{0x291216ea,0x6440883a,0x6d56380a,0x7d1d5d4b}}, // biya_, _azmi, _yiya, onss,
+ {{0x29005d4c,0x291204b9,0x7d1d00d9,0x7f5d0144}}, // chia_, ciya_, nnss, _tusq,
+ {{0xfbab8b88,0x8ca5016f,0x7d1d008b,0x27e0027d}}, // стей_, _कानो, inss, cxin_,
+ {{0x3eba44dc,0x2d8580e1,0x395e8036,0x78a28580}}, // cept_, ysle_, _buts_, _acov,
+ {{0xa9c480f7,0x2d9a0036,0xb457853d,0xceb203de}}, // فزيو, cupe_, _فیاض_, _שיל_,
+ {{0x2561866e,0x6ca49a12,0x395e8580,0x28da825a}}, // _ról_, _пруж, _duts_, भाषि,
+ {{0x443faa41,0x2561d8f8,0x6d565d4d,0x29038084}}, // _uzu_, _sól_, _riya, ėja_,
+ {{0x6d565d4e,0x09bc80c8,0x6d445d4f,0x29125d50}}, // _siya, _অপরা, _shia, ziya_,
+ {{0x57ba2261,0x6d563a0d,0x6d4401c5,0x69c1a35d}}, // _उन्ह, _piya, _phia, _usle,
+ {{0xd00e89d7,0x29003592,0x6d563dd5,0xfd660091}}, // الی_, xhia_, _qiya, _aapọ,
+ {{0x2d9a80e8,0xb4e6026b,0xbea58056,0x6eed81d6}}, // _åpen_, _apàà, _жалк, _dúbr,
+ {{0x6283dd51,0x25619eca,0x853d0084,0x6d565d52}}, // nano, _tól_, ntėj, _wiya,
+ {{0x6d44146a,0x248d8052,0x6d561d29,0xc3328039}}, // _thia, _odem_, _tiya, דום_,
+ {{0xb4bc853e,0x6283831d,0x78bba2b9,0x26c780b4}}, // _असे_, hano, neuv, _hano_,
+ {{0x29125d53,0x6283912e,0x26c7b174,0x236d81e9}}, // [5580] riya_, kano, _kano_, _ntej_,
+ {{0x29125d54,0x24802496,0x29005d55,0x68f65d56}}, // siya_, _heim_, shia_, skyd,
+ {{0x26c7dd57,0x6283dd58,0x60c65d59,0x291233ef}}, // _mano_, dano, _takm, piya_,
+ {{0x60c98259,0x2d870187,0x6abc5d5a,0x799c0314}}, // jdem, ânea_, lerf, murw,
+ {{0x62838b67,0x60c98365,0x69ce03ba,0x9f5281d0}}, // fano, ddem, lvbe, ždém_,
+ {{0xf96a9cad,0x7cff00f1,0x27e084b7,0x290f8122}}, // орий_, _kërc, _ħin_, _amga_,
+ {{0x200d07ca,0x68eba503,0xbebc80eb,0x65c31ef3}}, // _þeir_, _bogd, lnīg, _ибра,
+ {{0xe6431cad,0xa3de001b,0xb4c80697,0x2bbf864a}}, // _респ, णमा_, ईयो_, _शैवा,
+ {{0x68ebdd5b,0x6abc007b,0x26c782ec,0x7d02c8c8}}, // _dogd, kerf, _bano_, mhos,
+ {{0x6283a706,0x7d0283a7,0x752f009a,0x799c0314}}, // cano, lhos, wocz, kurw,
+ {{0x24800352,0x06970039,0x75d480f7,0x15150198}}, // _beim_, ודים_, ريحا, едня,
+ {{0xb4bc816f,0x7d02dd5c,0x290f8122,0x2ebb1a46}}, // _असो_, nhos, _gmga_, _उस्त,
+ {{0x7cff020f,0x26c7dd5d,0x27e0861c,0x752f0035}}, // _përb, _fano_, _çin_, rocz,
+ {{0xf54a0028,0xe6c81905,0x26c7c087,0x2fc046f8}}, // _mấy_, रयोज, _gano_, dwig_,
+ {{0xf54a0028,0x394681c0,0x752f009a,0x7d02805d}}, // _lấy_, _khos_, pocz, khos,
+ {{0x6aaa02af,0x69d85d5e,0x7e6d8115,0xc1570135}}, // _öffn, _švec, _đapi, _ụgbọ,
+ {{0x78ad3736,0x26c7833e,0x7d02bdf9,0x96db06a7}}, // [5590] rfav, _yano_, dhos, नालॉ,
+ {{0xa3be00d4,0xf3ff0187,0x6283dd5f,0x6b9d4bf9}}, // ीटर_, lcão_, xano, musg,
+ {{0x6b89803a,0x6283b5aa,0x2734016d,0x7d02c3f2}}, // _sveg, vano, vänt_, fhos,
+ {{0x26c717aa,0xf7735c11,0x69c52bd7,0xf54a0129}}, // žno_, _لاس_, _ishe, _bấy_,
+ {{0x4fc40087,0xf54a0129,0x3f9b011c,0x7e6a839c}}, // _исра, _cấy_, ququ_, _offp,
+ {{0x38691fd6,0x877a03c8,0x290f80dd,0x39468122}}, // lcar_, _מאשי, _smga_, _ahos_,
+ {{0x7d02936f,0x6b9d5d60,0x39468a2a,0x395927eb}}, // bhos, husg, _bhos_, _kiss_,
+ {{0x38693a66,0x26c7dd61,0xc7c88028,0x62838ca1}}, // ncar_, _sano_, _bố_, sano,
+ {{0x62838ff9,0x26c7dd62,0xc7c88028,0x39468282}}, // pano, _pano_, _cố_, _dhos_,
+ {{0x69c533cd,0x3ea10c53,0xeab081a8,0x39595d63}}, // _oshe, nght_, _نعم_, _liss_,
+ {{0x3ea11c33,0x6d5a00e7,0x2bc790be,0x26c7991f}}, // ight_, _état, लिका, _vano_,
+ {{0xed59800d,0x64ca1993,0x673a910f,0xfce6004a}}, // _již_, ियेश, altj, хомо,
+ {{0x394ddd64,0xdd0c8063,0x6281dd65,0x26c78010}}, // mmes_, _różn, _kelo, _tano_,
+ {{0x394d818d,0x7cff00f1,0x6281811f,0x799c5689}}, // lmes_, _përc, _jelo, turw,
+ {{0x395901cd,0x7d02c3cd,0x394d8722,0x28a6dd66}}, // _biss_, yhos, omes_, क्कि,
+ {{0x0d828098,0x6281dd67,0x39593fc7,0x799c00b4}}, // _слън, _lelo, _ciss_, rurw,
+ {{0x69c55d68,0x14dd06af,0x6abc05d8,0x799c5d69}}, // [55a0] _eshe, यारण, perf, surw,
+ {{0xf1c58028,0x69c500f1,0x64440db1,0x2fc02937}}, // _đáng_, _fshe, _izii, rwig_,
+ {{0x7d02dd6a,0x395902a6,0x394d8612,0xf54a0129}}, // thos, _fiss_, kmes_, _sấy_,
+ {{0xa6de8104,0x6281831d,0xe811016f,0x69ce8118}}, // _nhưn, _aelo, _ठेवा_, _ábei,
+ {{0x28c3800c,0x6281d87d,0x99bc80ab,0xe3af8a47}}, // वजनि, _belo, _অপেক, مرو_,
+ {{0x6281daa1,0x7d02b6ba,0xddda84e8,0x31c691f3}}, // _celo, shos, hatš, есив,
+ {{0x6281dd6b,0xc7c88104,0x7d02dd6c,0x3946dd6d}}, // _delo, _số_, phos, _phos_,
+ {{0x6d5e8036,0x3cff81a1,0x394dcf42,0xa6de8129}}, // _épai, _gluv_, gmes_, _chưn,
+ {{0xf76f803d,0xf77280f7,0x7d008074,0x26ca5d6e}}, // یای_, داع_, _ilms, _kabo_,
+ {{0x6281bbd3,0x09e69285,0x1ddb9094,0x80a286ae}}, // _gelo, _позн, यमंत, _गावे,
+ {{0x32078063,0xceb30039,0xa2dd800c,0x7bcf022c}}, // czny_, אית_, पार्, wvcu,
+ {{0x26ca5d6f,0xc7c8801c,0x6b9d5d70,0x28a7dd71}}, // _labo_, _tố_, tusg, _गायि,
+ {{0xd12f8065,0x7b09026c,0x258681d0,0xe0df0362}}, // _امن_, džur, _bílá_, _bhòn_,
+ {{0x26ca2896,0xd84f3a48,0x75240caa,0x02cb052a}}, // _nabo_, _dọla_, _ejiz, ायेन,
+ {{0x6b4002af,0x7d0082c4,0x69dc802a,0x318c81d0}}, // mögl, _olms, _áred, něz_,
+ {{0x3ebedd72,0x26ca5d73,0x2d9edd74,0xa63b81c6}}, // lett_, _aabo_, mute_, _מגור,
+ {{0xf770045b,0x2d9edd75,0x98170077,0x26ca1d61}}, // [55b0] مال_, lute_, ربرا, _babo_,
+ {{0x26ca1313,0x69c501e9,0x61e1016d,0x3959010c}}, // _cabo_, _tshe, älls, _wiss_,
+ {{0x21200805,0x3f89259f,0x69c537fc,0x4ade02f1}}, // bnih_, ssau_, _ushe, मारव,
+ {{0x6281dd76,0xd4e48607,0x5ce49ea4,0x62875d77}}, // _selo, лючи, люча, lajo,
+ {{0x62818152,0x60cd02b8,0x3ebec503,0xfaa58729}}, // _pelo, mdam, kett_, вало,
+ {{0x60cd5d78,0x2d9e8074,0x60dd008e,0x3869a358}}, // ldam, kute_, _hnsm, _żar_,
+ {{0x3ebe8eb9,0x60cd0110,0x09e5b3c2,0x394ddd79}}, // dett_, odam, тойн, tmes_,
+ {{0x2d9edd7a,0x60cd138e,0x26ca26bd,0x6281c631}}, // dute_, ndam, _zabo_, _welo,
+ {{0x394ddd7b,0xf771990c,0x3ebedd7c,0x5ef8085d}}, // rmes_, جاب_, fett_, _एवम्_,
+ {{0x394d9e9e,0x60cd5d7d,0x62875d7e,0x2d9edd7f}}, // smes_, hdam, jajo, fute_,
+ {{0x62870619,0xe8df81bc,0x2eee9cf9,0x7c3b82f7}}, // dajo, _agụm_, _doff_, _hyur,
+ {{0xc329010f,0x6d4983ac,0x2904b119,0x7c3bdd80}}, // _וו_, _jhea, chma_, _kyur,
+ {{0x6d498ad0,0x60cd5d81,0x68e982ce,0xd9ca052a}}, // _mhea, ddam, mjed, ाट्ट,
+ {{0x68e98d26,0xe61182e3,0x60dd3b99,0xa2d90651}}, // ljed, _پشت_, _ansm, माग्,
+ {{0x26ca43f0,0x5eca00ab,0x19b902c7,0x6b400106}}, // _rabo_, রামে, нуть_, sögo,
+ {{0x26ca5d82,0x60cd5837,0x60cb826c,0x20094619}}, // _sabo_, gdam, _jagm, fzai_,
+ {{0x6287238a,0x60cbb234,0xf77216a5,0x26ca5d83}}, // [55c0] bajo, _magm, شاء_, _pabo_,
+ {{0x21200824,0x60cb998b,0xb87b0118,0xbebc80eb}}, // snih_, _lagm, _boís, mnīc,
+ {{0x6d49803c,0xd6cf003f,0x65638073,0xc4868396}}, // _bhea, تقل_, _junh, влек,
+ {{0x3ebe8065,0x60cbdd84,0x6d49bcd7,0x26ca1d61}}, // zett_, _nagm, _chea, _wabo_,
+ {{0xa3b48160,0x6d498c64,0x4e12000d,0xdca391d5}}, // ублі, _dhea, _धेरै_, рачи,
+ {{0x69ce8074,0x934397d6,0xdb1b816a,0x78a4226d}}, // थिली, анче, _bruñ, lgiv,
+ {{0x656387f4,0x2904dd85,0x6d498ad0,0xe567806b}}, // _nunh, thma_, _fhea, _خط_,
+ {{0x6d49861f,0x68e98168,0x2d9e9b22,0x3ebedd86}}, // _ghea, gjed, vute_, wett_,
+ {{0x3ebe8065,0x60cbd625,0xaae286af,0xa3b683ca}}, // tett_, _dagm, पादक, _जहि_,
+ {{0x248481cd,0x81b900ab,0x2d9ea0a5,0x7522dd87}}, // _hemm_, চিত_, tute_, lnoz,
+ {{0x6563dd88,0x68e982fd,0xdb1bdd89,0x69d801e2}}, // _cunh, bjed, _usuá, _šven,
+ {{0xdddc0025,0x656387f4,0xa3b68768,0x3ebedd8a}}, // _održ, _dunh, _जहा_, sett_,
+ {{0x5f068676,0x78a40082,0x656386ff,0x62875d8b}}, // _изда, dgiv, _eunh, tajo,
+ {{0x2d9edd8c,0x68ef02a3,0x9b580098,0x52c0dd8d}}, // pute_, _socd, кият_, _एसोस,
+ {{0x3eab9e1e,0xbbbf864a,0x30a70a18,0x5bbf864a}}, // žitý_, _एन्क, крав, _एन्व,
+ {{0x62875d8e,0x44295d8f,0x6b8d8267,0x60c08198}}, // sajo, _txa_, šagi, nemm,
+ {{0x75228690,0xbed909c7,0x69d8002a,0x58840084}}, // [55d0] dnoz, ндах_, _áven, рыса,
+ {{0x65638708,0x20095d90,0xdf3900f7,0x60c0dd91}}, // _yunh, rzai_, ركات_, hemm,
+ {{0x7c3b9083,0x6d498083,0x62853b00,0x60c0dd92}}, // _syur, _phea, _ieho, kemm,
+ {{0x60c0dd93,0x6d5e8036,0x7699026b,0x21268168}}, // jemm, _épau, _bùyà, _njoh_,
+ {{0x289a012a,0x4429a31e,0x68e982ce,0xfd580133}}, // _אירא, ça_, vjed, _akaụ,
+ {{0x62855d94,0x93fc03c8,0x442981b9,0x05138264}}, // _jeho, ולדי, ħa_, িকের_,
+ {{0x60cb9066,0x628531a7,0x3c9382e3,0xdddc0259}}, // _pagm, _meho, _نیاز, _zdrž,
+ {{0x62855d95,0x6d5a80e7,0x50459f50,0x7c29811b}}, // _leho, _étap, лейб, _txer,
+ {{0x69c8809f,0x7b99803d,0x68fba6a6,0x213902f7}}, // _esde, _سپاس_, rkud, _mksh_,
+ {{0x26c15d96,0x68e982fd,0x62850e93,0x65638187}}, // neho_, sjed, _neho, _punh,
+ {{0x941e0201,0x63ad37fc,0xe0df0706,0x60cbd9c6}}, // ətə_, _aqan, _phòl_, _tagm,
+ {{0xeb9a19fe,0xf2c9012a,0xe0d088ca,0x97c5830f}}, // ним_, _מע_, _وزن_, _مقصو,
+ {{0x69d709e3,0x62855d97,0x5fb7800d,0x8ca502b4}}, // _arxe, _beho, _अहिल, _कारो,
+ {{0xe3630dca,0x63bbb00f,0xe5a58396,0x69dc802a}}, // скри, _mpun, _бики, _árec,
+ {{0xceb28039,0x021900e8,0x2c1a801b,0xbebc80eb}}, // _היו_, віть_, _मेनू_, snīc,
+ {{0x386ddd98,0xe0df0706,0xa2a8152c,0x63bbdd99}}, // ncer_, _cnò_, ञ्ज्, _opun,
+ {{0xd94315d1,0x7d045d9a,0x6b891c67,0x3f8ddd9b}}, // [55e0] сети, _ilis, šegr, nseu_,
+ {{0x62850e61,0x78a45d9c,0x4b7b1101,0x6d438866}}, // _geho, sgiv, _ראיו, înan,
+ {{0x63bbac92,0x386d82f7,0x3ce001a3,0x881a026b}}, // _apun, kcer_, _kniv_, _dírẹ,
+ {{0x163680be,0xa3c8052a,0x63bb8f35,0x00000000}}, // אנער_, _ईना_, _bpun, --,
+ {{0x925a0077,0x62852813,0xdddc05b9,0x26c10b5e}}, // _تشکر_, _yeho, _udrž, beho_,
+ {{0x39402446,0x7d0407e2,0x69c884c3,0x26c101ac}}, // llis_, _llis, _psde, ceho_,
+ {{0x7d040364,0x14aa0072,0x2f008176,0x5eca0264}}, // _olis, _काढण, _bògn_, রাদে,
+ {{0x2902dd9d,0xb87b0073,0x7643009a,0xed578c9b}}, // óka_, _iníc, żnyc, _бос_,
+ {{0x60c09485,0x7dea8085,0x1d078009,0x69d80084}}, // remm, məsi, _сети_, _švel,
+ {{0x7d0400f6,0xd24f8077,0x60c0dd9e,0x7dea8085}}, // _alis, _کنم_, semm, ləsi,
+ {{0x39401648,0x62855d9f,0xdbca8074,0xe814a539}}, // klis_, _reho, _võõr, _तेरा_,
+ {{0x386d951e,0x39401083,0x2ca684a7,0x6917802e}}, // ccer_, jlis_, ngod_, nţel,
+ {{0x3eb816f2,0x26c102a0,0xab6202d0,0x7d041867}}, // _obrt_, yeho_, şüne, _dlis,
+ {{0x7d045da0,0xe1f18065,0x628aa4dc,0x7dea8085}}, // _elis, _اسے_, nafo, həsi,
+ {{0x63a2aba4,0x7d045da1,0xe91900e8,0x26c15da2}}, // huon, _flis, вові_, veho_,
+ {{0x6f1adda3,0x63a2dda4,0x161a83eb,0x26c100b4}}, // litc, kuon, _फेयर_, weho_,
+ {{0x62855da5,0x26c15da6,0x3f150196,0x6d4d2676}}, // [55f0] _teho, teho_, адас, _ihaa,
+ {{0x394038dc,0x6d4d5da7,0x7d16219c,0x63a2dda8}}, // alis_, _hhaa, _zmys, duon,
+ {{0x63bb8278,0x69d704c3,0x8afc809a,0x26c15da9}}, // _spun, _urxe, _międ, reho_,
+ {{0x297a010f,0x26c10db1,0x1c1682f1,0x3940551e}}, // _שטרא, seho_, _देहल_, clis_,
+ {{0xdfc6880b,0x6a85a659,0x6d5f0d8b,0x63a2ddaa}}, // _بي_, алла, _miqa, guon,
+ {{0x68ed1f3a,0xa3b68105,0x6d5f5dab,0x2d91928a}}, // ljad, _जहर_, _liqa, ázek_,
+ {{0xa2ad001b,0x6f1a81ed,0x7dea8085,0x7e740019}}, // _जान्, ditc, bəsi, _چاہئ,
+ {{0x68ed048f,0x63a2ddac,0x7dea8085,0xe0d986e6}}, // njad, buon, cəsi, тви_,
+ {{0x386d9e9e,0x60cf117d,0x2f04016d,0x753bddad}}, // rcer_, _macm, _höga_, _ikuz,
+ {{0x6d4d02c1,0x5187b38c,0xc987a57e,0x386d83a7}}, // _ahaa, _буда, _буди, scer_,
+ {{0x7d045dae,0x1d0a0abe,0xa50a0abe,0x26ceddaf}}, // _plis, вени_, вена_, _dafo_,
+ {{0x7fd58160,0x6d4d5db0,0x443f8681,0x69dc8510}}, // _вікі, _chaa, _kyu_, _área,
+ {{0x6d4d02a3,0x290926df,0x888c80be,0x6d5f00ee}}, // _dhaa, dhaa_, _טראַ, _diqa,
+ {{0x6d4d1fa4,0xbea28196,0x39ae811c,0xdce4022b}}, // _ehaa, _дашк, _səsi_, _kunċ,
+ {{0xfc3f05a4,0xada5a3e7,0x6d5f00ee,0x753b8a40}}, // rgía_, _тайл, _fiqa, _okuz,
+ {{0x7529ab1f,0x61e1016d,0x92b380ab,0x7d041fb6}}, // _njez, ålla, য়ায়_, _ulis,
+ {{0x07a602a9,0x39405db1,0xa686004a,0x8fa603bd}}, // [5600] разн, rlis_, илад, разе,
+ {{0x753bddb2,0x39405db3,0xbe8500f7,0x6d5f5db4}}, // _akuz, slis_, مجمو, _épar,
+ {{0x39404fac,0x443fb996,0x7dea8085,0xd946341b}}, // plis_, _ayu_, təsi, _вени,
+ {{0x8fa62950,0x29090870,0xdddc026c,0x6b8402a0}}, // _каме, chaa_, _cerš, _kwig,
+ {{0xaae2800d,0xf863035f,0xb60700eb,0x7dea8085}}, // पालक, _євро, ekšē, rəsi,
+ {{0x60c42bb7,0x29005db5,0x6b840a5a,0x61138162}}, // leim, lkia_, _mwig, călz,
+ {{0xe81a05e8,0x79458063,0x6449ddb6,0x2ca682c4}}, // _नेता_, równ, _ezei, pgod_,
+ {{0x7cff020f,0x64a39908,0x60c4061a,0x3f428125}}, // _përm, _маса, neim, rðum_,
+ {{0x443fb286,0x28bd80d4,0x290006a2,0xfaa6a3d7}}, // _gyu_, ्यजि, ikia_, _вазо,
+ {{0x60c41794,0x6d4d0079,0x6d4292f5,0xd6da8293}}, // heim, _shaa, lloa, тти_,
+ {{0x29000009,0xb6d900be,0x3ed900be,0x628881a9}}, // kkia_, אַרט, אַרא, _iedo,
+ {{0x60c424b2,0x69da8102,0x215aa0bb,0x6b840314}}, // jeim, _irte, _شجاع_, _bwig,
+ {{0x6288c97c,0x6f1a80c3,0xdb040144,0x442d0168}}, // _kedo, pitc, _apiñ, çe_,
+ {{0x7cff020f,0x81c30341,0x628882af,0x6b845db7}}, // _kërk, ņēmu, _jedo, _dwig,
+ {{0x8d9480f7,0x60cf1c33,0x6b8428fd,0x48ab0b79}}, // _البش, _pacm, _ewig, ктам_,
+ {{0x7c2d0102,0x6d428087,0x6288ddb8,0x7cff03ed}}, // _txar, jloa, _ledo, _mërk,
+ {{0x68ed5db9,0x195817ae,0xa1581444,0xf7731a00}}, // [5610] rjad, раты_, рату_, تاز_,
+ {{0x7d1d5dba,0x69da848d,0x753bddbb,0x290901b4}}, // liss, _orte, _skuz, shaa_,
+ {{0x28a78540,0xdceb876c,0x23692d42,0x443f90e1}}, // _गालि, šiči, _huaj_, _syu_,
+ {{0x7d1d5dbc,0x23690069,0x291b0609,0xd1268bbe}}, // niss, _kuaj_, qiqa_, _رم_,
+ {{0x69da9473,0x628885f8,0xe2f990ac,0x236900f1}}, // _arte, _bedo, леді_, _juaj_,
+ {{0x2369146a,0x68e28114,0x8d77026a,0x7d1d05ed}}, // _muaj_, _anod, _نارا, hiss,
+ {{0x7d1d3726,0x236901c5,0xa3a90361,0x69da81dd}}, // kiss, _luaj_, खंड_, _crte,
+ {{0x753b829b,0x1dd5d8f3,0x26d100fc,0x7d1d5dbd}}, // _ukuz, धिसत, _hazo_, jiss,
+ {{0x443f82a0,0x2d9eddbe,0x6288c8a7,0x69daddbf}}, // _uyu_, erte_, _fedo, _erte,
+ {{0x6288a04d,0xaf059d79,0x29000102,0xb33b0118}}, // _gedo, спол, zkia_, duço,
+ {{0x7d1d5dc0,0x26d13905,0x24891357,0x2d918b24}}, // fiss, _mazo_, _beam_, éze_,
+ {{0x62888358,0x7bdbb2f4,0x41558f25,0x26d156ae}}, // _zedo, _kruu, _увес, _lazo_,
+ {{0x387fced0,0x2d9eddc1,0x23690282,0xb4c8009a}}, // mbur_, arte_, _cuaj_, _उसी_,
+ {{0x20a7800f,0x23693592,0x26d15dc2,0x291904b9}}, // _गाँध, _duaj_, _nazo_, _amsa_,
+ {{0x26c5bee9,0x6d428bb1,0x7d1d243c,0x7bcd047f}}, // melo_, yloa, biss, _esau,
+ {{0x26c5805d,0x236901c0,0x7d1d24eb,0x6b841e8f}}, // lelo_, _fuaj_, ciss, _twig,
+ {{0x69030074,0x29005dc3,0x3cf1026f,0x60c45dc4}}, // [5620] _tõen, rkia_, žová_, reim,
+ {{0xb8ff058c,0x26c5805d,0x3ce618b8,0xb33b0214}}, // _धो_, nelo_, टावे_, nuçl,
+ {{0xf9921e95,0x20a7a8b3,0x628e5dc5,0x25a59aa5}}, // _عبد_, _गांध, mabo, null_,
+ {{0x6288a1e5,0xe76a08ca,0x26c58234,0xfd4d8133}}, // _sedo, _محسن_, helo_, _ichọ,
+ {{0x26c5805d,0x69030074,0xb87b0118,0x236901c0}}, // kelo_, _mõel, _unía, _xuaj_,
+ {{0x26c582fd,0x7cff020f,0x628e5dc6,0x25a592ed}}, // jelo_, _përk, nabo, kull_,
+ {{0x64408063,0x62888a9e,0x7e7e8074,0x7bdb8c2e}}, // _wymi, _vedo, _õppi, _eruu,
+ {{0x1f748249,0x628e1221,0x78a99c11,0xa7748705}}, // олия, habo, ggev, олич,
+ {{0x628e0542,0x7d1d0a7c,0x7d02ddc7,0x6b40007b}}, // kabo, viss, mkos, lögu,
+ {{0x7d1d5dc8,0x23691085,0x63a6010b,0xa7149246}}, // wiss, _ruaj_, dukn, імні,
+ {{0xd6d79006,0x2002895e,0x69da80ad,0x7d1d5dc9}}, // сты_, ški_, _urte, tiss,
+ {{0x7d029a99,0xc5f30039,0xa06a84ae,0x2369022c}}, // nkos, לדת_, _сада_, _puaj_,
+ {{0x26c5ddca,0x7d1d5dcb,0x23690069,0x705687bd}}, // belo_, riss, _quaj_, _انشا,
+ {{0x7d1d5dcc,0x26c5ddcd,0x628e37ac,0x25a5ddce}}, // siss, celo_, gabo, bull_,
+ {{0x25a58722,0x248904e7,0x26d1002a,0x7d02ddcf}}, // cull_, _team_, _razo_, kkos,
+ {{0x236910af,0x7bcd5dd0,0xa3e8800d,0x673a8722}}, // _tuaj_, _tsau, यमा_, lotj,
+ {{0x02fa83b7,0x26d104c3,0x752d0859,0x628e06a0}}, // [5630] ्साह_, _pazo_, _ijaz, babo,
+ {{0x7d02867f,0x2fc901b0,0x628e2f5d,0x387f8088}}, // ekos, rwag_, cabo, zbur_,
+ {{0x7cff00f1,0x2fc92b05,0x7e6d9238,0x7d0289ff}}, // _përh, swag_, _şapk, fkos,
+ {{0xa8a711d2,0x30a70009,0x26c58234,0x7d02ddd1}}, // _урок, _уров, zelo_, gkos,
+ {{0x26d109ab,0x6265a57e,0x7af702f9,0x752d03e4}}, // _tazo_, овла, _koxt, _mjaz,
+ {{0x3866831d,0x49998656,0x69d816b5,0xe0df02d6}}, // _agor_, атия_, _švei, _chòv_,
+ {{0xdce4003b,0x945d8063,0x78a9838e,0x7cff00f1}}, // _sunč, _pańs, tgev, _tërh,
+ {{0xb4c80063,0x628e0a3b,0xe8f98dae,0xe800001b}}, // _उसे_, zabo, рли_, लैका_,
+ {{0x26c5a29d,0x78a98a0f,0x7d1bddd2,0x7d09826b}}, // telo_, rgev, _imus, _iles,
+ {{0x80d200c8,0xdcc206b7,0x78a986a8,0x212900ee}}, // হায্, षज्ञ, sgev, hnah_,
+ {{0x8afc8d38,0x7d09b7e5,0x6b52013c,0xe4c618ba}}, // _więc, _kles, lægg, ойни,
+ {{0x63a60359,0xa3ad8441,0x21295dd3,0x25a5bac8}}, // tukn, _कमा_, jnah_, rull_,
+ {{0x80d200c8,0x26c584c4,0xa34a22a7,0x1b4a1073}}, // হাম্, pelo_, изма_, изми_,
+ {{0x63a65037,0x27ffddd4,0x7d09ddd5,0x25a58168}}, // rukn, myun_, _lles, pull_,
+ {{0x628e5dd6,0x63a6015d,0x7d1b8c56,0xfd1200f7}}, // rabo, sukn, _omus, _يجب_,
+ {{0xb1460d13,0xa0670323,0xdfd25c11,0x7945866f}}, // інал, жата_, ويز_, dówk,
+ {{0xef1a2569,0x628e15d0,0x09a6952c,0x6f1e0669}}, // [5640] ама_, pabo, _गम्य, tipc,
+ {{0x69c1a862,0x7d098b91,0x27ed809f,0x6d465dd7}}, // _ople, _ales, ixen_, llka,
+ {{0x6f1e5dd8,0xb87b0013,0x27ff86ff,0xa2d4873c}}, // ripc, _gnío, hyun_, _योग्,
+ {{0xf7678624,0x0eac016f,0xd9461401,0xdd8f1459}}, // _يا_, _चावड, _феми, ئون_,
+ {{0x6b44809f,0x6d4615e8,0x60d60110,0x69c1d6e9}}, // lògi, ilka, mdym, _aple,
+ {{0x7d09ddd9,0x60d60110,0x69de0289,0x7d1ba27b}}, // _eles, ldym, _krpe, _emus,
+ {{0xaab201c4,0xa2ad035a,0x4ab2000f,0xd90d815b}}, // _जानक, _जास्, _जानव, گین_,
+ {{0xd7c88077,0xdcfc8024,0xa2ba8540,0x60d60084}}, // موعه_, _tvrđ, _शॉर्, ndym,
+ {{0xd90d8416,0xc9530039,0xb81d8beb,0x5ed38264}}, // دین_, ומה_, _बेदम_, তানে,
+ {{0x7b1002af,0x21295dda,0x28da847d,0x6d462525}}, // _häuf, znah_, _मोरि, elka,
+ {{0x673acb0f,0x6b400106,0x7b100192,0x76438114}}, // rotj, högs, _käuf, _cyny,
+ {{0x290ddddb,0xe9ff80ff,0x1c1f8035,0x7afa5ddc}}, // chea_, _hiếm_, _मेडल_, öttn,
+ {{0xe9ff8028,0x8d5b80be,0x9f4681ec,0x7763802a}}, // _kiếm_, יכטי, _groß_, _finx,
+ {{0x7b1002af,0x6d465ddd,0xbd6a898d,0x994382d0}}, // _läuf, alka, _крие_, _kış_,
+ {{0x59ce01fe,0x320503b0,0x63a2ddde,0x2129178f}}, // _हैदर, ály_, hron, tnah_,
+ {{0xf8bc86b7,0x8d1801bd,0x69a2864a,0x752d1fb6}}, // ्जिय, _وزير_, _गिनी, _ujaz,
+ {{0x21290393,0xc05b035f,0x63a2803d,0xf77006e2}}, // [5650] rnah_, рів_, jron, نال_,
+ {{0x21291600,0x7763b798,0x3f7b80be,0x629ea6e7}}, // snah_, _xinx, יאמס, ópol,
+ {{0x2d872db2,0x63a28fa3,0xe0df0229,0x2ef85ddf}}, // ène_, eron, _bhòt_, _dorf_,
+ {{0x63a2b340,0x629aa22e,0xf9da00be,0x613e8168}}, // fron, _adto, _פֿיל, këlq,
+ {{0x63a28e9c,0x35cc8105,0x69c1dde0,0x999e81d0}}, // gron, ़बड़, _sple, _bytů_,
+ {{0x7945809a,0x78ad00eb,0x6d4088dc,0x6595282d}}, // mówi, lgav, _ekma, _нагу,
+ {{0x290ddde1,0x63a2dde2,0x7d09c4e5,0x6d460df6}}, // thea_, aron, _tles, ylka,
+ {{0x6ca4941a,0x7763dde3,0x78ad5de4,0x7d1ba9da}}, // _оруж, _sinx, ngav, _umus,
+ {{0x6b520366,0xf8ae81f9,0x5b158162,0x77638580}}, // læge, اکم_, _емат, _pinx,
+ {{0x65640009,0xdce7826c,0x6b8981bc,0xd7098198}}, // _siih, _mujč, _iweg, сное_,
+ {{0x7bdf26f0,0x248d81c0,0x9d463160,0xc8ab0035}}, // _arqu, _keem_, зенд, _छांट,
+ {{0x753d0019,0x6b8986b0,0x236581a1,0x9cf98264}}, // gosz, _kweg, _hilj_, _আগুন_,
+ {{0x80278013,0x437512bc,0x65640009,0x248d81c5}}, // _برام, дуст, _viih, _meem_,
+ {{0x63a49bf2,0xf8bf04be,0x3d02009a,0x60c9dde5}}, // šing, _abé_, _mówi_, leem,
+ {{0x6721dde6,0x2fcdb3fc,0x2365caac,0xed5a002e}}, // milj, lweg_, _milj_, _лок_,
+ {{0x248d90f4,0x60c98a6f,0x6f1c0699,0x78ad2c19}}, // _neem_, neem, _smrc, ggav,
+ {{0x7cff00f1,0x8cbc0a27,0x6b898870,0x60d65de7}}, // [5660] _përv, ष्णो, _nweg, rdym,
+ {{0x63a28036,0x60c9800b,0x2120008e,0xe0df02d6}}, // vron, heem, siih_, _akòd_,
+ {{0x02fb025f,0x6b89838a,0x248d87b6,0x629e8118}}, // _הלימ, _aweg, _beem_, ópom,
+ {{0xccf9809a,0x61e1013c,0x248d8282,0xf8bf077f}}, // _coś_, ælle, _ceem_, _gbé_,
+ {{0x248ddde8,0xead48b79,0x1c168006,0x69dc12f1}}, // _deem_, доль, _देखल_, lvre,
+ {{0x67218503,0x291d8176,0xb3e9866e,0xa3ad8105}}, // jilj, _amwa_, تعمل_, _कमर_,
+ {{0x6721805c,0x248d81c5,0x6b898133,0x51868b69}}, // dilj, _feem_, _eweg, чула,
+ {{0x63a29dcc,0x8b668bbe,0x859b0039,0x248d81bc}}, // pron, _فاطم, _לשלו, _geem_,
+ {{0x6721b70c,0x68f98101,0xed57c653,0x2fcd9989}}, // filj, _dowd, _хот_, gweg_,
+ {{0x7522dde9,0xdce400eb,0x248d8282,0x0dca964f}}, // lioz, _runā, _zeem_, буми_,
+ {{0x7ae88022,0x248d81e9,0x69dc0042,0x908a80f7}}, // _indt, _yeem_, jvre, _هناك_,
+ {{0x248d81c5,0x76a28032,0x2902d8e5,0x7522c893}}, // _xeem_, _bíyà, ökad_, nioz,
+ {{0x67218025,0x753d5dea,0x656e5deb,0x66e6a82d}}, // bilj, rosz, _lubh, _нода,
+ {{0x7afa8009,0xb87b016b,0x4ab2016f,0x7b640073}}, // _jott, _vním, _जाणव, етсе,
+ {{0x31c412a0,0x2d9c0073,0x7afa90aa,0xceb383c8}}, // нств, ável_, _mott, _ניק_,
+ {{0xa3ad85e8,0x8e4780f7,0x6b52006a,0x7afaa91e}}, // _कमल_, _عليك_, væge, _lott,
+ {{0x383428df,0x78ad00f3,0x7bdf15a0,0x248d8122}}, // [5670] ентр, rgav, _urqu, _reem_,
+ {{0x7d0d003e,0x7afa8098,0x6b8982a0,0x3ce90069}}, // _hlas, _nott, _rweg, _hnav_,
+ {{0x28f90987,0x8afc0039,0x248d822c,0x78ad097a}}, // _день_, _להזי, _peem_, pgav,
+ {{0x752280d2,0x656e0c41,0x69c50234,0xd5b881a9}}, // gioz, _dubh, _iphe, itā_,
+ {{0x7afa8a85,0x39495c7e,0x656e0a2a,0x248d82df}}, // _bott, mlas_, _eubh, _veem_,
+ {{0x49b80077,0xd5b880eb,0x39495dec,0xe3b08199}}, // _باید_, ktā_, llas_, طره_,
+ {{0x7afa99aa,0x248d8069,0x39495ded,0x75229351}}, // _dott, _teem_, olas_, bioz,
+ {{0x7c3a009a,0x2cbf82f7,0x7ae88aa2,0x7afa82f7}}, // ętrz, _ubud_, _endt, _eott,
+ {{0x60c987e3,0x67218503,0xa2b58098,0x2d9e016d}}, // reem, tilj, _обач, _åter_,
+ {{0x69c53968,0x2fcd9781,0x39495dee,0x60c9b0c9}}, // _ophe, rweg_, hlas_, seem,
+ {{0xdca60391,0x64a635aa,0x394902d7,0x2fcd9f2e}}, // мани, мана, klas_, sweg_,
+ {{0x69dc0bda,0x63a48140,0x7afa94cf,0x92ea0264}}, // vvre, šine, _zott, _মতে_,
+ {{0x69c501f6,0x2bd50327,0x69d581a2,0xb87b026b}}, // _aphe, दिगा, भिकी, _eník,
+ {{0x2bc081ce,0xada181ac,0x39495def,0x88bd8035}}, // _एहसा, ľúbe, elas_, biśc,
+ {{0x6146132a,0x7cff0168,0x63abddf0,0x5ed38264}}, // дева, _përt, hugn, তাদে,
+ {{0x39495df1,0x7d0d5de0,0x69dc1bc2,0x69110366}}, // glas_, _glas, rvre, _nåed,
+ {{0x7cff020f,0x69c55df2,0x1f66012f,0x657c016b}}, // [5680] _vërt, _ephe, дкам, _strh,
+ {{0x39494f52,0xa2bf9344,0x2bd486a7,0x7d0d02d4}}, // alas_, ल्म्, _धनबा, _zlas,
+ {{0x7afaddf3,0x39495df4,0xdd8f0c3b,0xa06a3d93}}, // _rott, blas_, لول_, _мана_,
+ {{0x0c26825d,0x7afa81dc,0xb80d8c28,0x69dc808b}}, // ммен, _sott, िनाम_, _árei,
+ {{0x7522853c,0xe7d880ab,0x31c6925f,0x28a78035}}, // rioz, _তথ্য, लब्ध, _गाजि,
+ {{0x39405df5,0x7522ddf6,0xa2fa914f,0x656e0362}}, // lois_, sioz, ्सेज_, _tubh,
+ {{0xaac0000c,0x2d680214,0x96630fc8,0x7afaa406}}, // श्यक, _eğer_, _акре, _vott,
+ {{0x92ea00c8,0x39402e1c,0x6d44022c,0xa0220214}}, // _মতো_, nois_, _nkia, _şöyl,
+ {{0x629e09a4,0xb8d68935,0x26d85df7,0x7afa8009}}, // _odpo, _जा_, _karo_, _tott,
+ {{0x35f48abe,0x26d85df8,0x7ae8806a,0xa3cd06ab}}, // епор, _jaro_, _undt, रबस_,
+ {{0x7d0d5df9,0x2d98ce86,0x26d808e4,0xda6581a8}}, // _plas, ére_, _maro_, _كامي,
+ {{0xd5b880eb,0x26d8009c,0x6d560122,0x7e6d8162}}, // rtā_, _laro_, _chya, _şapt,
+ {{0xd5b88029,0x29121916,0x6d560122,0x394908dc}}, // stā_, dhya_, _dhya, vlas_,
+ {{0x7cff020f,0x17548e86,0x6594b3e4,0x6d44011b}}, // _përs, _явля, _заку, _ekia,
+ {{0x69d801dd,0x26ccddfa,0x394000e7,0xcda79ef7}}, // _šver, medo_, fois_, _قهوه_,
+ {{0x6ec1000d,0x26d82b40,0xa2bf00d4,0x69078144}}, // र्नु, _aaro_, _लॉर्, _añej,
+ {{0x394945be,0x20049988,0xd6db0098,0x2486812b}}, // [5690] rlas_, nymi_, жте_, dbom_,
+ {{0xb90603bb,0x39494f97,0x26cc802a,0x08540009}}, // _यो_, slas_, nedo_, твую,
+ {{0x26d82655,0x69c50234,0x60c2b77d,0x394013ff}}, // _daro_, _uphe, _obom, bois_,
+ {{0xa3d60fd5,0x63abddfb,0x6b8d5dfc,0x399a81a9}}, // _सनम_, tugn, _kwag, dīs_,
+ {{0x50641597,0x26d85dfd,0xc8641c8b,0x2fc68069}}, // _атра, _faro_, _атри, _npog_,
+ {{0x6295022e,0x2909153c,0x60cd0c5e,0x60c2ab81}}, // nazo, lkaa_, leam, _abom,
+ {{0x2fc680e4,0x61459da9,0x60c283f7,0x6b8d07fb}}, // _apog_, нека, _bbom, _lwag,
+ {{0x63abddfe,0x60cd14f2,0x26d80326,0x29090b8a}}, // pugn, neam, _zaro_, nkaa_,
+ {{0x83358160,0x62950619,0x26d85dff,0x7d048198}}, // _знах, kazo, _yaro_, öise,
+ {{0x6d4bde00,0x60cd0c5e,0x6d560314,0x6d445e01}}, // llga, heam, _shya, _skia,
+ {{0x29090364,0x62954c9e,0xd12e815b,0x6b8d33cd}}, // kkaa_, dazo, شمی_, _awag,
+ {{0x28df28ed,0x60c291d3,0xfd120250,0x29d18115}}, // _पोलि, _gbom, اجد_, _iša_,
+ {{0xeb9702bc,0x3f4287ca,0x65698364,0xe72e891e}}, // ния_, rður_, _mieh, _бе_,
+ {{0x3b0a0009,0x60dbafb3,0x2486abea,0x68fd0219}}, // чего_, ldum, zbom_, _aosd,
+ {{0xa18a0698,0xa50a10ca,0x68fd03a8,0x7f4186a5}}, // ябва_, _нека_, _bosd, molq,
+ {{0x60db9f6f,0x26d85e02,0x61e1013c,0x6569a70c}}, // ndum, _saro_, ælla, _nieh,
+ {{0x07a3351e,0x237f90af,0x8fa311b3,0x26d805e4}}, // [56a0] латн, _ntuj_, лате, _paro_,
+ {{0x60d99393,0x291202a0,0x62955e03,0x7bd6076d}}, // _lawm, shya_, cazo, _isyu,
+ {{0x394000a9,0x6b9b82a0,0xdcf48289,0x60cd5e04}}, // pois_, _avug, šači, beam,
+ {{0xe57280be,0xc8ae80bc,0x644985ee,0xb0ae85fb}}, // אַס_, _टाइट, _byei, _टाइग,
+ {{0x26d85e05,0x98a0009a,0x65698609,0x249f8197}}, // _taro_, enić_, _dieh, _ddum_,
+ {{0x399a80eb,0x68eb8637,0x6449d55f,0x290fd660}}, // tīs_, _angd, _dyei, _alga_,
+ {{0x2004809a,0x27e05e06,0x9966a927,0xdca6a481}}, // wymi_, lvin_, нтил, _пави,
+ {{0xa9678fbb,0x60d981c0,0x25bb0039,0x81b280ab}}, // нија_, _cawm, _מצלמ, টবল_,
+ {{0x26cca6b9,0x60d98609,0x399a81a9,0x212481a8}}, // tedo_, _dawm, sīs_, rimh_,
+ {{0xdcc90ebf,0x656981ec,0x2ca021ab,0x7afe0087}}, // िज्ञ, _zieh, _adid_, _nopt,
+ {{0x80bd816f,0x6b8d555f,0x26ccc458,0x7bd60886}}, // व्हे, _swag, redo_, _asyu,
+ {{0x26ccde07,0x7cff0168,0x2ca081a8,0x60d98326}}, // sedo_, _përq, óidh_, _gawm,
+ {{0x62955e08,0x04670009,0x68fd00dd,0xdceb8289}}, // tazo, этом, _rosd, šići,
+ {{0x27e05e09,0xdcfb0084,0x6d4286ae,0x7b100799}}, // dvin_, ystė, hooa, _räuc,
+ {{0x29090009,0x60d9822c,0x62340162,0xdb029727}}, // tkaa_, _yawm, геру, ntoñ,
+ {{0x58d41628,0x6b8d020c,0x62955e0a,0x290f01a8}}, // лост, _twag, sazo, óga_,
+ {{0x6295062f,0x29092683,0x6b8d18e3,0xbcfb360c}}, // [56b0] pazo, rkaa_, _uwag, rméd,
+ {{0x656982af,0x60cd4909,0x81d780ab,0x6d4b8079}}, // _sieh, seam, ামত_, wlga,
+ {{0x6b9b8052,0x6449a08b,0x63bd5e0b,0x60cd35f7}}, // _svug, _syei, ltsn, peam,
+ {{0x48ab0364,0x3946822c,0xeb099905,0x764a8114}}, // _этом_, _nkos_, वस्त_, _myfy,
+ {{0xc6a70dc7,0x4ea70195,0xe4a481e5,0x98a0066f}}, // _први, _прва, урхо, wnić_,
+ {{0xfe700154,0x9f3500e8,0x6569822b,0x8c1c01c6}}, // _هدف_, _регі, _wieh, הודי,
+ {{0x15a80098,0x9f3400e8,0xae1e016f,0x6569c269}}, // _пъти_, _сері, _येऊन_, _tieh,
+ {{0xf9940451,0x98a00fda,0x25fe0327,0x60d99953}}, // ארק_, rnić_, लहरी_, _qawm,
+ {{0x200b83fb,0xe0d6827e,0x60dbde0c,0x69c88144}}, // áci_, твы_, rdum, _epde,
+ {{0xb8e880d4,0x55e58188,0xa2ad0b84,0x394682df}}, // _लॉ_, вооб, _जाग्, _ekos_,
+ {{0x7cff020f,0x60d981c0,0xa2ba864a,0x290f876d}}, // _përp, _tawm, _शॉट्, _tlga_,
+ {{0xb4ac184a,0x2f04016d,0x68eba28f,0x7afe5e0d}}, // खले_, _högt_, _ungd, _sopt,
+ {{0x63af0052,0x7afe5e0e,0x2d478187,0x394da2ba}}, // gucn, _popt, lões_, mles_,
+ {{0x394dde0f,0xa3e283eb,0xe8ff00e7,0x395fde10}}, // lles_, धित_, _aoû_, lmus_,
+ {{0x320c026f,0xcb67002e,0x63bd090d,0x764a8428}}, // ády_, таре_, atsn, _gyfy,
+ {{0x25de8076,0x2d85b68b,0xdddc0110,0x8afc809a}}, // गिरी_, mple_, _perž, _pięk,
+ {{0x394d80e7,0x395901a1,0x7afe1f1a,0x75241b8f}}, // [56c0] iles_, _dhss_, _topt, _imiz,
+ {{0x320784e8,0xbcfb0036,0x395fab3a,0x3ce606ae}}, // kyny_, rmée, hmus_, _जोते_,
+ {{0x8afc809a,0x63bb913b,0xb4e6046d,0xe0df02d6}}, // _więk, _aqun, _gbàá, _ikòn_,
+ {{0xd8260dc8,0x64a6de11,0x6da335cc,0x63a923f9}}, // _адми, тада, рица, šene,
+ {{0x69030074,0x2f04007b,0xdd0c8035,0x290c01a9}}, // _võet, _sögu_, _półn, ūdas_,
+ {{0x394d8548,0x69c881df,0xf9c69fb4,0x39468933}}, // eles_, _ppde, вщин, _skos_,
+ {{0x3f8f930c,0x2d47841c,0x7bc10162,0x63bb912e}}, // _awgu_, gões_, ălui, _equn,
+ {{0xf9930051,0x395f856c,0x3cffa1bf,0x31ba80f1}}, // ברה_, gmus_, _gouv_, rëz_,
+ {{0xab66a29c,0xa2bf816f,0x2489816a,0x04468425}}, // _авал, ल्ह्, ñame_, _реан,
+ {{0x394dde12,0x75245e13,0x7d00de14,0x27ff0036}}, // ales_, _amiz, _homs, _àune_,
+ {{0x629884e8,0x394d90b6,0xceb30039,0xd6d7853b}}, // mavo, bles_, בית_, тты_,
+ {{0x394db68b,0x6298828e,0x4fc68791,0x60268103}}, // cles_, lavo, _аска, _идеа,
+ {{0xdcfb0029,0x6931803d,0xd00f80f7,0x7d00b417}}, // lstī, _دکور, _ولم_, _moms,
+ {{0x63af0052,0xb3458073,0x6298de15,0x75243bb8}}, // rucn, moçã, navo, _emiz,
+ {{0x1db69370,0x57578039,0x6728a28c,0x645605ee}}, // _अमित, _שבוע_, midj, _ezyi,
+ {{0x26dc81ed,0x3944de16,0x7524011f,0x6934373a}}, // _havo_, noms_, _gmiz, ансу,
+ {{0x26dc8e2d,0x2d478187,0xb8db052a,0x80be8264}}, // [56d0] _kavo_, zões_, _घा_, ্যপ্,
+ {{0xdcfb0029,0x6d4f02e6,0x3f141157,0x3f9b56b9}}, // kstī, llca, адус, rsqu_,
+ {{0x8d740250,0x7bc9de17,0x3944de18,0x2d4783a7}}, // لانا, _speu, koms_, xões_,
+ {{0x58840b71,0x394d04b7,0x656d0b7e,0x394496b5}}, // _выра, ċess_, _jiah, joms_,
+ {{0xad1c093f,0x21290587,0xaac010be,0x4ac001c4}}, // וואר, miah_, श्वक, श्वव,
+ {{0x2129059c,0x6298de19,0x2d4783a7,0x69030074}}, // liah_, gavo, tões_, _tões,
+ {{0x27e6b2db,0x80be80c8,0x60dd5e1a,0x395f8f09}}, // _aron_, ্যন্, _kasm, tmus_,
+ {{0x27e6a9d2,0x21295e1b,0xdfd180f7,0x2d47841c}}, // _bron_, niah_, ميع_, rões_,
+ {{0x20090393,0x60dd5e1c,0x2d4783a7,0x27e68706}}, // nyai_, _masm, sões_, _cron_,
+ {{0x26dc8081,0x2129036e,0x60dd5e1d,0x65750168}}, // _cavo_, hiah_, _lasm, _kuzh,
+ {{0xd2518013,0x4d662f42,0x88bd809a,0x6d5bc2ad}}, // _انا_, лков, liśm, _khua,
+ {{0x637f8087,0x644d05ee,0x39449532,0x27e6b807}}, // _tână, _byai, coms_, _fron_,
+ {{0x2129059c,0x657523ac,0xbebc81a9,0x8fa59246}}, // diah_, _luzh, rnīr, _сале,
+ {{0x8f9a893f,0x6d5b81c0,0x68fbad2e,0xe45680be}}, // _װיקי, _lhua, ljud, טירט_,
+ {{0x6d49de1e,0x21292b36,0x71761a3c,0x60dd5e1f}}, // _okea, fiah_, اهرا, _basm,
+ {{0xdd008029,0x656d4f24,0xdddc01b9,0x62988f87}}, // ītīb, _giah, _verż, zavo,
+ {{0x6d465e20,0x46ea49a1,0x27e60013,0xa2ac0054}}, // [56e0] moka, ндан_, íon_, _जयप्,
+ {{0x6d465e21,0x6b844378,0x6d5f00e5,0x6d5bc2c5}}, // loka, _itig, _èpar, _ahua,
+ {{0x6d5b8ad0,0x6937005c,0x20090590,0x21290359}}, // _bhua, mćen, ayai_, biah_,
+ {{0x6d5b861f,0xb87b1984,0x6d463718,0xd910081b}}, // _chua, _caíd, noka, _بیر_,
+ {{0x6298de22,0x44208028,0x6d5ba039,0x5b2681e2}}, // tavo, ài_, _dhua, лька,
+ {{0x6d465e23,0x63a482d7,0x9e6600d7,0xa2bf8072}}, // hoka, šino, _مازن, ल्ल्,
+ {{0x62988a56,0x29025e24,0x672882a5,0x6d465e25}}, // ravo, _koka_, vidj, koka,
+ {{0x26dc8a8e,0x2902025d,0xf9930039,0x6d5b8c41}}, // _savo_, _joka_, זרה_, _ghua,
+ {{0xe2928307,0x2fc01c11,0x35ba0105,0x6298de26}}, // _هذا_, ntig_, _उमड़, pavo,
+ {{0x29025e27,0x2fc002af,0x2129036e,0x6d5b80fc}}, // _loka_, itig_, ziah_, _zhua,
+ {{0x68fb80f2,0x644d0812,0x64410201,0xceb30039}}, // bjud, _syai, əliy, זית_,
+ {{0x69dade28,0x2ed10fe4,0x27e6de29,0x2fc00687}}, // _iste, _हस्त, _tron_, ktig_,
+ {{0x60dd0c9e,0xc62105fb,0xdb1d0019,0x26dc8110}}, // _rasm, _मध्य_, ltsé, _tavo_,
+ {{0x501b8051,0xe81d8b85,0x60dd5e2a,0x212900ee}}, // _אודו, _बेटा_, _sasm, wiah_,
+ {{0x6d464ee7,0x13cd80ab,0x212900dd,0x764e01a3}}, // boka, লিয়, tiah_, _nyby,
+ {{0x2fc01277,0x2d9ea39e,0x6d462168,0x70af0072}}, // ftig_, mste_, coka, टलेल,
+ {{0x2fc05e2b,0x21290590,0x290204b9,0x200f0019}}, // [56f0] gtig_, riah_, _doka_, ági_,
+ {{0x69da9711,0x6d5bde2c,0xb87b0e67,0x60dd1306}}, // _oste, _shua, _saíd, _wasm,
+ {{0x2d9e9989,0x60dd4f79,0x21290072,0x5c75af4b}}, // nste_, _tasm, piah_, алет,
+ {{0x2d9ede2d,0x39a101e2,0x7985112e,0x01ba80ab}}, // iste_, mės_, _ithw, ংবাদ,
+ {{0x39a10a8e,0xc05810ac,0x69daca2b,0x389a0538}}, // lės_, _бір_, _aste, _בירנ,
+ {{0x3f895e2e,0x2d9e92af,0x6b7c00be,0x6d464954}}, // mpau_, kste_, ערונ, zoka,
+ {{0x39a10746,0x6d5bde2f,0xd2b80039,0x6d46544e}}, // nės_, _thua, אלות_, yoka,
+ {{0x2d9e9610,0x629700f1,0xd131880b,0xb0f380ab}}, // dste_, _lexo, تمع_, জস্ব_,
+ {{0x2d9ede30,0xe8f68791,0x02a780b3,0x290d8bf0}}, // este_, рлы_, _брем, rkea_,
+ {{0x39a101e2,0x62971e00,0x26d303a8,0x81cc00ab}}, // kės_, _nexo, nexo_, লিত_,
+ {{0x6d465e31,0x2d9ea68d,0xdce9b2b2,0x7985022c}}, // toka, gste_, _lieč, _nthw,
+ {{0x39a10110,0xb91281bc,0x2b478088,0x00000000}}, // dės_, _mgbọ_, lonc_, --,
+ {{0xdce981ac,0x29020ed6,0x2d9ede32,0xdb1ba706}}, // _nieč, _roka_, aste_, _aqué,
+ {{0x6d463990,0x29025e33,0x69daaaef,0x26c78282}}, // soka, _soka_, _yste, _ubno_,
+ {{0x6d465e34,0x4096067c,0x29025e35,0xe2970ddc}}, // poka, ират, _poka_, рас_,
+ {{0x2fc050e9,0x387f978f,0x776e01c0,0x63a9011f}}, // ttig_, ncur_, _sibx, šena,
+ {{0x06d880c8,0x6937157c,0xfaa615ac,0xb912819d}}, // [5700] তারি, pćen, _капо, _agbọ_,
+ {{0x39a101e2,0xef170f25,0x2fc05e36,0x3ce001c5}}, // bės_, аму_, rtig_, _haiv_,
+ {{0x2fc03a6d,0x290210fa,0xab5b02af,0x7bdb8c2e}}, // stig_, _toka_, stüc, _asuu,
+ {{0x7d040009,0x6281a64f,0x66031a50,0xbfcd0032}}, // _jois, _aflo, änka, _dáṣọ_,
+ {{0x6562de37,0x3ce001c5,0xfbd2b026,0x28df0327}}, // rmoh, _maiv_, تتا_, _पोटि,
+ {{0xf1a6946c,0x7d045e38,0xa3ce32dd,0x6f03cdb0}}, // арин, _lois, _रहन_, _nonc,
+ {{0xbcfb0019,0x35a33c3d,0xe5a320bf,0x316f81a1}}, // dmén, _марг, _мири, _bigz_,
+ {{0x7d045e39,0xed5a9b2f,0x3b86ae65,0x6281de3a}}, // _nois, _роб_, _влаг, _eflo,
+ {{0x629c5e3b,0xd7fa8749,0x39a10110,0xc60e824c}}, // karo, туй_, zės_, ान्य_,
+ {{0x2d9e9aee,0x629c01ca,0xa9a68098,0x7b100192}}, // tste_, jaro, _вижд, _bäum,
+ {{0x69dacb16,0x7bcd01e2,0x6f03809f,0x7d045e3c}}, // _uste, _spau, _donc, _bois,
+ {{0x7d04599c,0x39a101e2,0x3ce00069,0x7bc29336}}, // _cois, vės_, _caiv_, otou,
+ {{0x6f0383d3,0x2d9e83dc,0x7d0402ce,0x7bc2de3d}}, // _fonc, sste_, _dois, ntou,
+ {{0x6e948307,0x39a10a8e,0x8f9b8158,0x629c0333}}, // _الثا, tės_, _יידי, garo,
+ {{0x7d04473d,0x7d161277,0x394b0144,0xba080081}}, // _fois, _flys, _pkcs_, ахте_,
+ {{0x39a10110,0x09c380ab,0x9875431c,0x7d045e0b}}, // rės_, ্মচা, слац, _gois,
+ {{0x39a101e2,0x25de800f,0x6d4d3162,0x63a496f2}}, // [5710] sės_, गिकी_, _ikaa, šinj,
+ {{0x02a72a71,0x629c3724,0x657881c0,0x39a10110}}, // _трим, caro, _kuvh, pės_,
+ {{0x5fd884e5,0x09f90013,0xdcfc8029,0xa3df0701}}, // _बैसल, _صفحة_, _strā, णिक_,
+ {{0xbcfb0019,0x26d35e3e,0xdb0b86a5,0xd7a68105}}, // zmén, sexo_, rugí, _खिंच,
+ {{0x657880ee,0x2bda873c,0xaca381bc,0xd7f8151a}}, // _luvh, _मैदा, _nnụn, _кур_,
+ {{0x9735053d,0xc324a1d2,0xe29681e5,0x7d1983e4}}, // _اکرا, омік, баш_, shws,
+ {{0x20028009,0x7582003d,0x3f868176,0xa0a5d3c2}}, // äkin_, _فیلم, _atou_, байд,
+ {{0x629c25ca,0x6f03807a,0xeb040aed,0x7b1001ec}}, // zaro, _sonc, _रक्त_, _räum,
+ {{0x7529c692,0x7bc285db,0xa3e28ec5,0x6f03989d}}, // _imez, ctou, धिः_, _ponc,
+ {{0x6d4d30a0,0x7d0401e4,0x7d160ff4,0x629c4382}}, // _akaa, _sois, _slys, xaro,
+ {{0x629c5e3f,0x3ce001c0,0x7d0427d3,0x395d81c0}}, // varo, _paiv_, _pois, _thws_,
+ {{0xcb090bea,0x7ae1de40,0x629c5e41,0x98b001d6}}, // _אל_, _halt, waro, _šaľa_,
+ {{0x7d0422b2,0x7f8580f7,0x7ae19473,0x66e5876a}}, // _vois, _التن, _kalt, _гола,
+ {{0x44630081,0x249d8069,0xf67980be,0x6f1ac9ee}}, // явяв, hawm_, _גאַמ, chtc,
+ {{0x7ae181cd,0x7d044663,0x629c5074,0x7b32809a}}, // _malt, _tois, raro, sług,
+ {{0x645a0086,0x629c13a8,0xb87b05e4,0x443f8085}}, // ətin, saro, _raíc, _oxu_,
+ {{0xc6760eca,0x66761650,0x66030106,0x39522cd4}}, // [5720] مطاب, مدار, änkn, rlys_,
+ {{0x1ea9080b,0xe646035f,0x7529de42,0x69c3d5ee}}, // لامي_, безп, _amez, ktne,
+ {{0x394901df,0x645b89b2,0x69c38b80,0x61460084}}, // boas_, _azui, jtne, _гена,
+ {{0xdca3a410,0x63a413c2,0x69de3333,0x39490216}}, // заци, _kvin, _ispe, coas_,
+ {{0xd9e082a8,0x7cff0168,0x7ae1de43,0xc30a85a8}}, // पट्ट, _përz, _balt, ледж_,
+ {{0x7bc282be,0x75298135,0x60d60965,0x7ae1c7ff}}, // rtou, _emez, leym, _calt,
+ {{0x7ae1a305,0x7bc2801b,0xf2d31101,0xe7e0064a}}, // _dalt, stou, פעה_, निकप,
+ {{0x7ae1c7cb,0x63a45e44,0xd13080f7,0x61e1006a}}, // _ealt, _ovin, ظمة_, ælli,
+ {{0xfaa68607,0x200d8061,0xaaba8bbe,0x17fa81a8}}, // _газо, gyei_, _مدار_, ارعة_,
+ {{0x69de5e45,0x6d4d01b0,0xd6daa05f,0xb7db03c8}}, // _ospe, _skaa, ути_, נקיי,
+ {{0x63a45e46,0x63a2de47,0x29e785f3,0x24f90190}}, // _avin, lson, _gđa_, анны_,
+ {{0x2d731123,0x39490118,0x629a83ed,0x7ae18bfd}}, // _kćer_, xoas_, _heto, _zalt,
+ {{0x629ade48,0xe0438b9c,0x07e98019,0x68e45e49}}, // _keto, _енци, _پرنٹ_, ddid,
+ {{0xf1dc81b6,0x629a80f1,0x63a91620,0xdc378158}}, // _मनान, _jeto, šeno, מאכט_,
+ {{0x63a4017b,0x80db8264,0xc33381c6,0x68e2de4a}}, // _evin, ভার্, לוש_, _kaod,
+ {{0x629a8ef1,0x995521d2,0x29000168,0x68e42b9c}}, // _leto, ікац, gjia_, gdid,
+ {{0xdb0f05e4,0x752f009a,0x394903a7,0xb87b016a}}, // [5730] lucí, micz, roas_, _caía,
+ {{0x291b0713,0x629a8243,0x752f009a,0x63a29c11}}, // shqa_, _neto, licz, dson,
+ {{0xf3cb0077,0x6eca8a0d,0xa282003d,0x63a45e4b}}, // _شبکه_, त्यु, _آیفو, _zvin,
+ {{0x752f0063,0x81cc00c8,0x54558098,0x6aa89670}}, // nicz, লিশ_, овет, _addf,
+ {{0x629aafbc,0x7ae1de4c,0xdcfc802e,0x03a5802e}}, // _beto, _palt, _stră, цило,
+ {{0x69c3dddc,0x629ade4d,0x539c00be,0x317a06cb}}, // ttne, _ceto, ניוו, _yupz_,
+ {{0x7ae1de4e,0x7bdf5e4f,0x56940196,0x63a2cbcd}}, // _valt, _lsqu, _хаст, ason,
+ {{0xdb0f0e15,0x77859fa2,0xe7a99033,0x753b804f}}, // ducí, олиз, авил_, _ujuz,
+ {{0xef199383,0x6d4b8019,0x69c3b78d,0x600681bb}}, // ими_, moga, stne, жным_,
+ {{0x629a83b2,0x6b8982a0,0x2ca95e50,0xc34b8110}}, // _geto, _iteg, _adad_, _сябе_,
+ {{0xceb20158,0x29190182,0x752f009a,0x7bdf0216}}, // ויי_, _olsa_, ficz, _asqu,
+ {{0x6d4bd826,0x752f009a,0xa3e60e5b,0x629ade51}}, // noga, gicz, फिल_, _zeto,
+ {{0xe72e8dac,0x636002af,0x27e90b80,0x2d5900e7}}, // _же_, höne, jvan_, péen_,
+ {{0x2ca9076f,0x63a48013,0xe29715fe,0x6d4bde52}}, // _edad_, áini, жар_, hoga,
+ {{0x7bdf0887,0x6ee48591,0xb87b2363,0x91e327cb}}, // _esqu, _رسول, _saía, море,
+ {{0x63a403a6,0xf8d981ce,0x63a4ac62,0x6b8992e5}}, // _tvin, ढ़िय, šini, _oteg,
+ {{0x68e410dd,0x6d4bde53,0x37060294,0xaf062597}}, // [5740] rdid, doga, опаг, опал,
+ {{0xab5b05c5,0x0cd68264,0x65608362,0x98a9066f}}, // ntül, _সচেত, _shmh, ynać_,
+ {{0x394619d9,0x69de5e54,0x6d4b89df,0xec7abd73}}, // _мног, _uspe, foga, апе_,
+ {{0xa3ce0d38,0xf09f0028,0x63a2b768,0x629a872f}}, // _रहा_, _hoàn_, tson, _seto,
+ {{0x6bd680d7,0x225880c3,0x934684db,0xd4038198}}, // تتار, _šrk_, онде, дящи,
+ {{0x63600019,0x47d900be,0xa3bbde55,0x37d900be}}, // lönb, _פֿרײ, _अमर_, _פֿרע,
+ {{0x65c32804,0x629aca58,0x63a29e68,0x6b898c2e}}, // _обра, _veto, sson, _eteg,
+ {{0x63ad8341,0x984f8059,0x8c46867c,0x442201a1}}, // šana, _ağır_, _дебе, kzk_,
+ {{0x629ade56,0xdfd080a0,0x4aca0107,0x50b7803d}}, // _teto, ريد_, िभाव, زديد_,
+ {{0xe5c68364,0xcf2680f7,0xbcfb0036,0x752f0035}}, // осмо, تربي, rmém, wicz,
+ {{0x667b010f,0xd5b781d9,0xa3e290c8,0x6d4380e7}}, // יטיק, ося_, धिक_, énag,
+ {{0x6eca03dd,0x7d02acc7,0x657c5e57,0x27e9007e}}, // स्तु, njos, _murh, yvan_,
+ {{0xdee69afa,0x1ae400c8,0x66e69577,0xa06a8eef}}, // _моди, খানে_, _мода, _тада_,
+ {{0xb87b0118,0xdce401a1,0x78a081f6,0x672a808b}}, // _caín, _dunđ, lamv, _umfj,
+ {{0x31c40b79,0x7bc60ad4,0x39838214,0x657c036e}}, // мств, atku, _kısa_, _nurh,
+ {{0x2eed1d81,0x6d4bddaf,0x2ee3022b,0x3ce695d8}}, // _şef_, xoga, _sajf_, jdov_,
+ {{0x752d120f,0x6d4b8317,0xdce400fe,0x7d028b80}}, // [5750] _imaz, voga, _gunđ, djos,
+ {{0x27e91384,0x368b01e5,0x7c228805,0xa92801d0}}, // rvan_, рсан_, dzor, _požá,
+ {{0x57b7853e,0x6d4bde58,0x657c0e6d,0x673a86a8}}, // _आम्ह, toga, _curh, intj,
+ {{0xbfa38129,0x7bf9a3d7,0x98a000c3,0x6f07066f}}, // _biế, йнер_, viić_, _wojc,
+ {{0x6d4bbc36,0x7ae50406,0x69c7480a,0x4fc40b26}}, // roga, _kaht, ltje, _осра,
+ {{0x37c280c8,0x31693f41,0x673a81ed,0x657c010c}}, // ্টার, lmaz_, jntj, _furh,
+ {{0x69c75e59,0x5ee100ab,0x34958767,0x7ae51bda}}, // ntje, নাদে, _навр, _maht,
+ {{0x7ae50406,0x3169080a,0x39a580eb,0x673a86a8}}, // _laht, nmaz_, rēs_, entj,
+ {{0x63a9003a,0x69c70582,0x60c9842b,0x6b89c736}}, // šenj, htje, rfem, _uteg,
+ {{0x1dd21516,0xa2b4b51e,0xef1996cf,0x752d5e5a}}, // _सहमत, _обуч, смо_, _amaz,
+ {{0x69c700f1,0x7d1bde5b,0xe8df81bc,0x69c501c0}}, // jtje, _klus, _azụm_, _nqhe,
+ {{0xb87b03a8,0xdce4042b,0x673a9151,0x69c70366}}, // _saín, _punđ, antj, dtje,
+ {{0x7d09de5c,0x2bdf0327,0x7ae53d77,0x69c700f3}}, // _moes, _पैदा, _baht, etje,
+ {{0xa3ae890a,0x752d3c4c,0x394dde5d,0x692c02d0}}, // _किस_, _emaz, loes_, rşem,
+ {{0x7d1bba3e,0x7bc61aed,0x7c2280e5,0xf77080d7}}, // _olus, stku, zzor, _شاه_,
+ {{0x04460081,0x752d082c,0x657a0036,0x64430035}}, // _нейн, _gmaz, _éthi, _śnie,
+ {{0x6843046a,0xe04311d5,0xf09f0104,0x80dd0076}}, // [5760] ента, енти, _toàn_, _पसरे,
+ {{0x7d1b81c2,0x752d01ac,0x394dde5e,0x8c3d82d0}}, // _alus, _zmaz, hoes_, _keşf,
+ {{0x7d09de5f,0xf29701c6,0xfebd8264,0x63a6209b}}, // _boes, _מכיר_, _অসাধ, lskn,
+ {{0xdd8f12dc,0x7d1ba543,0x2d870c52,0x7c228102}}, // اون_, _clus, ínea_, tzor,
+ {{0xf8b30bea,0x629e01f1,0x98a48110,0x63a62c15}}, // _אשר_, _kepo, nimą_, nskn,
+ {{0x7d1badf8,0x7c22de60,0xada60d45,0x657c0009}}, // _elus, rzor, _нагл, _turh,
+ {{0x7c228061,0x7d1d067f,0x394d8114,0x6b520366}}, // szor, ërsk, foes_, lægs,
+ {{0x629e095e,0xbcfb0065,0xfce38758,0x26da1024}}, // _lepo, rmék, _посо, lepo_,
+ {{0x98a48110,0x6d409a7b,0x44228106,0x61e18580}}, // jimą_, _ijma, _åk_, _esll,
+ {{0x629e258f,0x2486de61,0x65645e62,0x68e60326}}, // _nepo, lcom_, _ahih, _lakd,
+ {{0x2d9886e3,0x27208028,0x78a080b4,0x1cd90110}}, // ère_, _lòng_, pamv, одны_,
+ {{0x2486de63,0x394d8187,0x68e605b0,0x291fda58}}, // ncom_, coes_, _nakd, chua_,
+ {{0x61dc8aed,0x399a8110,0x65644584,0x60c2de64}}, // _मनुष, lūs_, _dhih, _icom,
+ {{0x26da02fd,0xbcfb00f7,0x65640135,0x629e5e65}}, // jepo_, iméi, _ehih, _cepo,
+ {{0x629e26e7,0x6588801b,0x68e612b7,0x5f000a27}}, // _depo, _běhe, _bakd, रान्_,
+ {{0x672e04b7,0x69c750e9,0xe63b8039,0x752d5e66}}, // _ambj, rtje, _לתוכ, _umaz,
+ {{0x7ae514c7,0x69c73fd6,0xc05b00e8,0x7d098cab}}, // [5770] _taht, stje, сів_, _roes,
+ {{0x6d4f1a47,0x27208028,0x629e0574,0x316900eb}}, // loca, _dòng_, _gepo, smaz_,
+ {{0x7d09a57f,0x692c1238,0x6f1c1b88,0x753d0799}}, // _poes, kşeh, _dlrc, onsz,
+ {{0x7d1d00f1,0x291f81c0,0x6ec1175d,0x6288a301}}, // ërsh, xhua_, र्जु, _afdo,
+ {{0x3b098396,0xdb1b8187,0x63601e2b,0x96eb2357}}, // жело_, _aquá, löna, _льва_,
+ {{0x63ad80eb,0x6d4f01a8,0x7d098cfa,0x60cd07f1}}, // šano, hoca, _woes, lfam,
+ {{0x7d0985f8,0x2fc95e67,0xe9d980e8,0x290b02d0}}, // _toes, ltag_, іки_, _koca_,
+ {{0xa3ce2cdd,0x2d83816b,0x7d1b8214,0x290b0088}}, // _रहल_, íjem_, _ulus, _joca_,
+ {{0xb8d583db,0x63a48013,0x2fc94255,0x6b8d5e68}}, // _जय_, áint, ntag_, _ntag,
+ {{0xee39aaed,0x290b349a,0x05a98009,0x656400f1}}, // жно_, _loca_, овой_, _shih,
+ {{0xdb268077,0x6aa38b50,0x6d4f5e69,0x6d47011a}}, // _تولی, manf, foca, čkan,
+ {{0x60c28013,0x629e5e6a,0x63a9807b,0x6b9bac18}}, // _gcom, _sepo, _kven, _kwug,
+ {{0xa2c4800d,0xeb970656,0x249f8176,0x629e57d2}}, // _राम्, мия_, _meum_, _pepo,
+ {{0x63a60082,0x68e98c4d,0x6aa391e6,0x06e180ab}}, // rskn, lded, nanf, বাহি,
+ {{0x6b8d5297,0x68e621e5,0x60cd5e6b,0xa50a1289}}, // _etag, _pakd, ffam, _мека_,
+ {{0x68e99f90,0x249f8da4,0x63a9a2ba,0x26da0010}}, // nded, _neum_, _oven, wepo_,
+ {{0x2fc9033e,0x8fa310f8,0xcece80ff,0x27edde6c}}, // [5780] gtag_, кате, _mộng_, nven_,
+ {{0x27208028,0x6d599849,0x3636003d,0xdb1b8118}}, // _vòng_, elwa, وراس, _opuñ,
+ {{0x68e65e6d,0xa3e60ebf,0x2486de6e,0x6aa38357}}, // _takd, फिक_, tcom_, danf,
+ {{0x290b5e6f,0x249f851e,0x68e98366,0x6b9b818e}}, // _goca_, _ceum_, jded, _bwug,
+ {{0x27ed8613,0x249f8197,0xc3da803d,0x68e9de70}}, // jven_, _deum_, تباط_, dded,
+ {{0x60c28081,0x6d59cbcd,0xbcfb0f35,0x290b0118}}, // _scom, alwa, rméi, _zoca_,
+ {{0x63a9c4fa,0x81cc00c8,0x27ed80f3,0x2486ccef}}, // _even, লিক_, even_, pcom_,
+ {{0xcece8028,0x63a901ac,0x2ca00006,0x78a40242}}, // _cộng_, šeni, _neid_, naiv,
+ {{0x27e05e71,0x5ee100ab,0x2d5c823e,0x64a38721}}, // nwin_, নারে, níem_, _шата,
+ {{0x2216801e,0x44268010,0x63a4de72,0xf4040264}}, // _офер, mzo_, šins, উনার_,
+ {{0x27e01083,0x1d350d8e,0x6b8d1e68,0x442681e8}}, // hwin_, ення, _stag, lzo_,
+ {{0x60db82b7,0x66033416,0x291d86c4,0x6b80c0c0}}, // ceum, änki, _glwa_, _kumg,
+ {{0x4426de73,0x7a13800d,0x290b2c92,0x1016806b}}, // nzo_, _větš, _roca_, _تباد,
+ {{0x27208176,0x290b16dc,0xbcfd8129,0xdb0b8106}}, // _bòne_, _soca_, _nhũn, stgö,
+ {{0xd90f04c0,0x290b3bfe,0xc1dc816f,0x09dc8054}}, // لید_, _poca_, _मनोग, _मनोय,
+ {{0x58d40c3e,0xa2d580d4,0x2fc91b48,0xa3ae8101}}, // кост, _मॉन्, ttag_, _किं_,
+ {{0x3fbc80c8,0x40342f7d,0x290b0bcf,0x60cd074d}}, // [5790] _অনুষ, ветс, _voca_, rfam,
+ {{0x63bd07ba,0x2d870207,0x7ae88953,0x4426de74}}, // musn, íneo_, _nadt, dzo_,
+ {{0xc1750c3e,0x63a98687,0xb8e500c8,0x68e98bff}}, // _площ, _sven, _এস_, yded,
+ {{0x61e55e75,0xb4b98935,0x7afada96,0x7d0d5e76}}, // _ishl, चले_, _antt, _joas,
+ {{0xdb1d013c,0xcece801c,0x7ae8de77,0x7d0d4633}}, // rtsæ, _rộng_, _badt, _moas,
+ {{0xfbb2009a,0x31668fe0,0xdcf58162,0x8f9a0538}}, // _जिसम, _ahoz_, _buză, _מישי,
+ {{0xe5c41697,0x24808052,0xaca3819d,0x6d5986c0}}, // _исто, žima_, _azụa, plwa,
+ {{0x7d0d0012,0x27ed80d2,0x3fe58b5b,0xfa7802f6}}, // _noas, tven_, ежив, ועות_,
+ {{0x6c54aa71,0x61f88038,0x68e9bc51,0x20198019}}, // _акту, _ďalš, rded, ási_,
+ {{0x60dbcdc6,0x26c110e4,0x2ca05e78,0x29028338}}, // seum, ngho_, _seid_, ökar_,
+ {{0xed5a0607,0xa2c4863a,0x7bcbde79,0x27e01415}}, // зон_, _रात्, ltgu, zwin_,
+ {{0x6aa19ab0,0xf2c9007c,0xa3e403db,0x67218b80}}, // _helf, _לע_, _ननद_, shlj,
+ {{0x2d9c3b50,0x63bd0c9a,0xdb06026f,0x7bcb881a}}, // ávez_, gusn, lské, ntgu,
+ {{0xada5a2d2,0xe9ff8028,0x5ecf00ab,0x316dbf41}}, // _закл, _giảm_, _হোসে, lmez_,
+ {{0x44268021,0x3ea5a947,0xdb06003e,0x216a490c}}, // zzo_, malt_, nské, зиви_,
+ {{0x63bd0988,0x3ea5de7a,0x316d82d0,0x79818c2e}}, // busn, lalt_, nmez_, _mulw,
+ {{0x63ad8ee1,0xd1750048,0x81d580c8,0x59750071}}, // [57a0] šanj, тыры, িটি_, тыру,
+ {{0xe9e58028,0x6fb312dc,0x27e05e7b,0xff7a80be}}, // _đúng_, املا, rwin_, פטעמ,
+ {{0x8236004e,0x2d9c102d,0xdd8f1a00,0xa0a61c79}}, // _سردا, ève_, مول_, _панд,
+ {{0x3ea5d95b,0x3ce9022c,0x442699fd,0xb81f8072}}, // halt_, _xaav_, tzo_, यनाम_,
+ {{0x39405d13,0x80c9016f,0x3ea597ab,0x6aa1de7c}}, // mnis_, _हॉटे, kalt_, _belf,
+ {{0x4426c2ee,0xf6510117,0x853d0110,0x7981de7d}}, // rzo_, ائے_, gsėj, _bulw,
+ {{0x6aa1c561,0x3f82022e,0x8cc1009a,0x6d565e7e}}, // _delf, _huku_, _वालो, _okya,
+ {{0x3f821e8c,0x2c578063,0xdcfc976d,0x394027d3}}, // _kuku_, _będą_, _kurč, nnis_,
+ {{0x9346938f,0x0b469bc1,0x63bd026f,0xdcfccec8}}, // _знае, _знан, xusn, _jurč,
+ {{0x3f8204b9,0x6d443353,0x6d470696,0x2ca6833e}}, // _muku_, _ajia, čkam, laod_,
+ {{0x39400cf6,0xd36f9a3c,0xf1d48074,0x613b0198}}, // knis_, _اهل_, _दहिन, nälä,
+ {{0x6aa18613,0x2724008b,0x39521e44,0x76589075}}, // _zelf, _hönd_, joys_, _vyvy,
+ {{0x8c3d87d9,0xa2d58074,0x3ea5cdc6,0x7d0d0087}}, // _teşe, _मॉड्, balt_, _voas,
+ {{0x63bd0867,0x394050f8,0x61230074,0x6d5602ec}}, // rusn, enis_, _mõle, _ekya,
+ {{0x63bd0057,0x3f8205a3,0xf09f00ff,0x3940464d}}, // susn, _auku_, _hoài_, fnis_,
+ {{0x3f82059c,0x365b8039,0x0e5b8039,0x63600019}}, // _buku_, _מכונ, _מדוב, lönl,
+ {{0xb4dd00cf,0xb90903bb,0x3f823f3f,0xdb1d1d31}}, // [57b0] ड़ी_, _यस_, _cuku_, ttsä,
+ {{0x2007003b,0x6d5d5e7f,0x26de9aae,0xc43c0039}}, // ćni_, llsa, neto_, _מתחי,
+ {{0xdb1d00f2,0x394002af,0xf09f00ff,0xd5bb06d2}}, // rtsä, bnis_, _loài_, яса_,
+ {{0x6aa1de80,0xdb06026f,0x26ded46a,0xdb1d0338}}, // _self, vské, heto_, stsä,
+ {{0x81d580c8,0x26de9c3d,0x490486ae,0xdb1d0192}}, // িয়া_, keto_, वानो_, ptsä,
+ {{0x26dede81,0x3ce98353,0xdb06016b,0x7bcba875}}, // jeto_, žav_, tské, rtgu,
+ {{0x3ea58006,0x6aa195f8,0x2d8500e7,0x7bcb87f1}}, // valt_, _velf, _île_, stgu,
+ {{0x68ed46f2,0xdb06026f,0x63ad0687,0x3ea581ec}}, // ndad, rské, _ovan, walt_,
+ {{0x3ea58bfa,0xa3ae809a,0x6aa1de82,0xf8bf0019}}, // talt_, _किए_, _telf, _elég_,
+ {{0x39401c56,0x6569de83,0x68ed0046,0x657b808e}}, // znis_, _hheh, hdad, _hiuh,
+ {{0x63ad0cf8,0x68fd0082,0xa3e98778,0xdc3401ac}}, // _avan, _onsd, यिक_, rúča,
+ {{0x3ea58006,0x2d83180c,0x38359715,0x78a290bc}}, // salt_, _muje_, _инер, _geov,
+ {{0x68ed59d4,0x26debcfc,0x3ea5de84,0x68eb8122}}, // ddad, beto_, palt_, _iagd,
+ {{0x68ed3d4c,0x26dede85,0x68eb9ac4,0x656f01c0}}, // edad, ceto_, _hagd, amch,
+ {{0x63ad270c,0x3f825e86,0xa2c495bc,0xfaa303c7}}, // _evan, _suku_, _रास्, расо,
+ {{0x3f820870,0x6d44059c,0x68ed0461,0xf1b280be}}, // _puku_, _ujia, gdad, יסל_,
+ {{0x68eb9ac4,0x3ea32e21,0x2fcdcc2b,0xf8bf07f1}}, // [57c0] _magd, _bejt_, nteg_, _amés_,
+ {{0x394054a0,0x3f820390,0x667602e3,0x68eba7d1}}, // snis_, _vuku_, ندار, _lagd,
+ {{0x63ad225d,0x644984c3,0x2d8302a5,0x2d559f0a}}, // _zvan, _axei, _cuje_, våer_,
+ {{0x3f820d7f,0x68eb9066,0x290fde87,0x6b843e18}}, // _tuku_, _nagd, _noga_, _kuig,
+ {{0xe7bc8a49,0x99418035,0x0fbc8264,0x69ce5e88}}, // _অন্য, kół_, _অন্ধ, ltbe,
+ {{0x6569de89,0x60c45e8a,0x6b8401e4,0x26de802a}}, // _eheh, lgim, _muig, xeto_,
+ {{0xf1a38084,0x6b841e41,0xe5a30ff7,0xbcfb0036}}, // арын, _luig, _виси, llée,
+ {{0x60c42f6f,0x23b200dc,0x26dede8b,0xe9ff827d}}, // ngim, _जिंद, weto_, _chằm_,
+ {{0x68ebbcb2,0x26dede8c,0xceba80fc,0x2fc0474f}}, // _dagd, teto_, _alƙa_, nuig_,
+ {{0xa3e7a43f,0x63ad0bda,0x24800168,0xd5b881a9}}, // _बनत_, _rvan, _agim_, trā_,
+ {{0x63ad2a4f,0x557501cf,0xc9d58fd3,0x68ed0438}}, // _svan, лгот, _مملک, ydad,
+ {{0xb4dd00cf,0x69da81bf,0x6b841412,0x43950d45}}, // ड़े_, _ipte, _buig, ламс,
+ {{0x26de82e7,0xe72f8077,0x02c98074,0xaac983b6}}, // peto_, صصی_, _रामभ, _रामक,
+ {{0x2d871be6,0x6b840229,0x59c6aa2b,0x68e0de8d}}, // ínek_, _duig, रंपर, lemd,
+ {{0x987e07d9,0xaad201d0,0x98c72218,0x98a90035}}, // _açık_, द्धक, ксал, niać_,
+ {{0x741508ca,0xbcfb3b79,0x60c45e8e,0x290f81df}}, // _موبا, rmés, ggim, _xoga_,
+ {{0x68ed25a7,0x691c80e7,0xadea897d,0x2fc03bb0}}, // [57d0] rdad, _réel, टिकन_, guig_,
+ {{0xa2c4a9b7,0x63a9003e,0x6569c941,0x68ed5e8f}}, // _राष्, šens, _sheh, sdad,
+ {{0x394685a4,0x6b8401ed,0x3cff8072,0x69ce090d}}, // _ojos_, _zuig, ळाले_, ctbe,
+ {{0x99f60051,0x2d590036,0x2fc05e90,0xa3d70072}}, // _עזרה_, mées_, buig_, _सहा_,
+ {{0xd0069168,0x63a0831d,0x2d5900e7,0x7bcf0980}}, // _ال_, _cwmn, lées_, ntcu,
+ {{0xe1f00065,0x569421d2,0x68eb813c,0x8c1c007c}}, // _اسی_, _варт, _sagd, וודי,
+ {{0x2d5902be,0x68eb97ef,0x1cbb80f7,0x38668176}}, // nées_, _pagd, _يارب_, _azor_,
+ {{0x00e58a7c,0xb4dd06a7,0x89d688ca,0x6b820037}}, // ужин, ड़ो_, اوير_, _èogg,
+ {{0x7ff5880b,0x7bcd01c0,0x6ec703db,0xa3e782f1}}, // اسبا, _nqau, ालपु, _बनि_,
+ {{0x3cff81c0,0x6b840014,0x38668122,0x8e09af75}}, // _hnuv_, _ruig, _dzor_, енов_,
+ {{0x290f8025,0xdca608cc,0x64a62549,0x68ebde91}}, // _toga_, лани, лана, _tagd,
+ {{0xa3e780cf,0x2f160125,0x6b840722,0xdcfc80eb}}, // _बना_, _hægt_, _puig, _kurā,
+ {{0xf7f4819f,0xdcf900eb,0x9f405e92,0xdb0407b6}}, // _پسند, _atvē, rviú_, _aviá,
+ {{0x69ce1722,0x78a982f1,0x79850deb,0x6fb29df9}}, // ttbe, laev, _buhw, _اموا,
+ {{0x321e026f,0x2d5900e7,0xc33281c6,0x395f832a}}, // áty_, gées_, מוך_, olus_,
+ {{0x69ce2e1d,0x6b840c41,0x2fc05e93,0x46cf9664}}, // rtbe, _tuig, tuig_, त्रह,
+ {{0x60c45e94,0x69ce4255,0x321e026f,0xe3b20019}}, // [57e0] rgim, stbe, šty_, _ارب_,
+ {{0x26c580ee,0x613e81a9,0x693a81b9,0x7536016b}}, // nglo_, rīli, nċer, _hmyz,
+ {{0x629c26d5,0x777c04c3,0x7524005c,0x2d590036}}, // mbro, _virx, _kliz, cées_,
+ {{0xe45f00f2,0x628197cf,0x291e01a9,0xab5b0192}}, // mför_, _aglo, ītas_, stüt,
+ {{0xbd2d0158,0x628f8087,0x6d42de95,0x98a90035}}, // וואָ, _şcoa, rnoa, wiać_,
+ {{0xbda60277,0xab5b02d0,0x7cfb062c,0x395f86ae}}, // _محرو, ltür, _kärä, elus_,
+ {{0xe45f00f2,0xe6079a37,0xdce2825b,0x26d80041}}, // nför_, _اش_, jmoć, _ibro_,
+ {{0x391495d1,0xdce280d2,0xf38a826b,0x60f88081}}, // рмир, dmoć, _aṣe_, тния_,
+ {{0xed358196,0xa3ae8540,0x68e0de96,0x629c3db4}}, // _тэрэ, _किट_, remd, kbro,
+ {{0x473494b7,0x68e0b6bd,0x7524348e,0x3866816b}}, // рнос, semd, _aliz, _vzor_,
+ {{0xd6d78e11,0x0f5704de,0x629c08dc,0x7bc2de97}}, // уты_, ריים_, dbro, luou,
+ {{0xd9fe0076,0x6281805c,0x395f8cf8,0x9e770039}}, // _उपजत_, _zglo, clus_, טגיה_,
+ {{0x38668668,0xbcfb0511,0x658881d0,0x26d85913}}, // _uzor_, eléc, _těho, _obro_,
+ {{0x75245e98,0x6aaaaeae,0x2d5900e7,0xa01b0106}}, // _eliz, naff, tées_, _oför,
+ {{0xa3ab823c,0x78a60292,0x349515a4,0xdd9081a8}}, // _कौन_, _bekv, _кайр, _بوب_,
+ {{0x6aaa9d77,0x316006c0,0x2d5900e7,0x5184002e}}, // haff, gliz_, rées_, сура,
+ {{0x473544ab,0x2d5902be,0x6aaaaa33,0x656d0f1d}}, // [57f0] анас, sées_, kaff, _ihah,
+ {{0x291e00f1,0xbcfb01df,0x2d590036,0x38bb046d}}, // ëtar_, bléc, pées_, _bàrà_,
+ {{0x395fde99,0x3f868118,0x61f700b9,0x656d0326}}, // ylus_, _luou_, _urxl, _khah,
+ {{0x661a8748,0x057482e3,0xb87b0118,0x26d85e9a}}, // iytk, ماند, _saíu, _ebro_,
+ {{0xd7e680f7,0x6aaa8041,0x63af01a8,0xab5b011c}}, // _بك_, faff, nscn, rrüf,
+ {{0x645a0087,0x212900dd,0x41b59e9d,0x6aaaadae}}, // ştig, lhah_, асит, gaff,
+ {{0xaa498098,0x2f16455b,0xdb0b8018,0x395f8074}}, // ъпка_, _vægt_, nsgé, tlus_,
+ {{0xa2d58540,0x63bd2bea,0xe8f9a3d7,0xc27c03de}}, // _मॉर्, krsn, тли_, ערהי,
+ {{0x68ef117d,0xa2cd81cb,0x69158118,0x2eee8176}}, // _macd, _साप्, _sáes, _baff_,
+ {{0xe80d05e8,0x62818b67,0x21291abf,0x2eee80e5}}, // _सपना_, _uglo, hhah_, _caff_,
+ {{0x52848013,0x60b5819f,0x75240247,0xed4628df}}, // _المك, _نمائ, _pliz, иноп,
+ {{0x7ae3de9b,0x69c3831d,0x69678591,0x31600101}}, // lent, mune, _صداق, xliz_,
+ {{0xa09a8158,0x656d0303,0x21290077,0x289a80be}}, // _סיסט, _dhah, dhah_, _סיסא,
+ {{0x661a8f4c,0xa3c70105,0x290d8042,0xfaa317f9}}, // bytk, _उमर_, ljea_, жаро,
+ {{0x63bd0014,0xa2cd8a0d,0x6d498216,0xbb431444}}, // arsn, _सान्, _ojea, церк,
+ {{0xe45f04b8,0x7ae3da96,0x629c2310,0x6d5b8282}}, // rför_, hent, sbro, _nkua,
+
+ {{0x7ae3c288,0x3ea7d2a4,0x69c3de9c,0xb8ee0c2d}}, // [5800] kent, _kent_, hune, _रा_,
+ {{0x2d87de9d,0x6d5bbc5a,0x69c3a2b3,0x656d00dd}}, // _kune_, _akua, kune, _zhah,
+ {{0x72080117,0x2d878039,0x6d4701f4,0x21294b38}}, // _صفحہ_, _june_, čkav, bhah_,
+ {{0x63a43623,0x69c39c10,0x63648722,0x47dc80ab}}, // _kwin, dune, tòno, বিতী,
+ {{0x7ae3c798,0x68e45e9e,0x290200b4,0xb92a801c}}, // fent, meid, _inka_, _thuế_,
+ {{0x7ae389af,0x68e41d7a,0x8c1b0051,0x63a41b74}}, // gent, leid, כוני, _mwin,
+ {{0x44209cbd,0x1bed159a,0x69c3de9f,0x19a78071}}, // ši_, _चैनल_, gune, атып_,
+ {{0xa3d70e18,0x6d461d14,0x68e43f5d,0x2eee8aee}}, // _सहल_, jnka, neid, _saff_,
+ {{0x8f46948d,0x673a8168,0x3ea7d47c,0x2fd20338}}, // иход, titj, _bent_, ntyg_,
+ {{0x68e40c72,0x656d5ea0,0x63bd003a,0x2d87802e}}, // heid, _shah, vrsn, _bune_,
+ {{0x69c3dea1,0x673a9001,0x6d9304b7,0x68e45ea2}}, // cune, ritj, _għad, keid,
+ {{0x673adea3,0x63a40f8e,0x6aa88428,0xb87b07b6}}, // sitj, _bwin, _hedf, _maír,
+ {{0x3ea7b86a,0x69de0c55,0xa2cd8aed,0xd79480f7}}, // _fent_, _appe, _साम्, _التخ,
+ {{0x3ea7dea4,0x63a406a8,0x3cfd86a7,0x80d2801b}}, // _gent_, _dwin, _रोने_, भ्रे,
+ {{0x68e400f7,0x2729801c,0x6aa88f06,0x63a41aa5}}, // feid, _súng_, _medf, _ewin,
+ {{0x644d01e9,0xdb1d05b9,0xd3a704cf,0xa3e78074}}, // _txai, musí, _кроп, _बनल_,
+ {{0x7ae39fdb,0xc05b0cde,0x64480085,0x69c38102}}, // [5810] yent, тів_, şdir, zune,
+ {{0x7ae3ae24,0x6d5bdea5,0x30778039,0x21290c9e}}, // xent, _skua, שתמש_, shah_,
+ {{0xb87b5ea6,0x7ae3dea7,0x68e422d4,0xa3c9835a}}, // _país, vent, beid, लून_,
+ {{0x7ae3c74d,0x21200052,0x6705063a,0x753d0019}}, // went, ckih_, राहक_, nisz,
+ {{0x7ae3dea8,0xc6510019,0x2d9a0037,0xdd020140}}, // tent, _کہاک, appe_, šući,
+ {{0x69c39673,0x97250fd3,0x8ccc8ad5,0x2ca901c6}}, // tune, _افسو, _हाथो, _lead_,
+ {{0xd2b801c6,0x6d46538d,0x3495a244,0xdbdd04e8}}, // בלות_, ynka, _габр, dšív,
+ {{0x69c39cf2,0x3ea7dea9,0x78ad3755,0xe0df03ec}}, // rune, _sent_, naav, _akòz_,
+ {{0x69c38cc7,0x6d59c4f0,0x2d878041,0x27e90915}}, // sune, mowa, _sune_, nwan_,
+ {{0x2d87deaa,0x69c38049,0x291fa91f,0x6d59b09c}}, // _pune_, pune, skua_, lowa,
+ {{0x99858307,0x2ca91c33,0x27e92992,0x79970069}}, // _الصو, _bead_, hwan_, _ntxw,
+ {{0x753d0065,0x6d598063,0xeb9a5eab,0x2ca9125b}}, // gisz, nowa, лим_, _cead_,
+ {{0x6b8980c8,0x68e40029,0xdcf580eb,0xef0ecc66}}, // _jueg, veid, _atzī, _ем_,
+ {{0x60c9d95b,0x6e9580f7,0x27e95d36,0x6b8181b9}}, // lgem, _الغا, dwan_, _jilg,
+ {{0x6d598063,0x6b8980ab,0x21200db7,0x68e42c6a}}, // kowa, _lueg, tkih_, teid,
+ {{0x6ed3a38c,0x628f8087,0x27e900dd,0x6d59b937}}, // ब्रु, _şcol, fwan_, jowa,
+ {{0x6d598063,0xa3c9800f,0x636486c0,0xb87b09b2}}, // [5820] dowa, लूम_, wònm, _saír,
+ {{0xd9f785b3,0x7bc652f8,0xbeaa003d,0x6b8198d5}}, // ंटात_, muku, اهان_, _nilg,
+ {{0x7bc65eac,0x248d026f,0x6d598f90,0x7d0404b7}}, // luku, žeme_, fowa, _knis,
+ {{0x6d59dead,0xab5b0182,0x2ee5deae,0x395d8282}}, // gowa, crüb, helf_, _nkws_,
+ {{0x6b818179,0x60c9c613,0x7d045eaf,0x23d6896b}}, // _bilg, dgem, _mnis, сцер,
+ {{0x81e180c8,0xff47826a,0xf1a69ddf,0x2d5c8144}}, // দিন_, _رخ_, брин, tíes_,
+ {{0x6d59ac0b,0x248980d2,0x7d040091,0x7bc640a6}}, // bowa, žama_, _onis, huku,
+ {{0x3eaa0613,0x7bc62d80,0x6b898020,0xf8ba0aed}}, // _hebt_, kuku, _fueg, ेलिय,
+ {{0x2ca93910,0x3eacdeb0,0x0c26a37e,0x7bc65eb1}}, // _read_, tadt_, _уман, juku,
+ {{0x75228065,0x2ca919c7,0x2d8a005d,0x6b81deb2}}, // lkoz, _sead_, _kube_, _gilg,
+ {{0xa2cd81ab,0x9f4006a5,0x2f5b03c8,0x27e900ee}}, // _सात्, lvió_, רדינ, zwan_,
+ {{0xf8bf01df,0x75229f4b,0x7bc6273f,0x6ed8001b}}, // _alén_, nkoz, fuku, न्तु,
+ {{0x7bc61a08,0x673e00c3,0x6d438866,0x753d0e06}}, // guku, kipj, énai, risz,
+ {{0x2fc05eb3,0x6d59deb4,0x7d045eb5,0xab5b0192}}, // drig_, zowa, _enis, drüc,
+ {{0x2d8a05e4,0x78ad37a8,0x6d599090,0x27e90326}}, // _nube_, taav, yowa, wwan_,
+ {{0x7bc61ea2,0x6d472944,0x61fa2320,0x7d0412f1}}, // buku, čkas, ætla, _gnis,
+ {{0x506734ba,0x78ad0364,0xc867038b,0x636d87e0}}, // [5830] ства, raav, стви, núnc,
+ {{0x78ad2e09,0x27e91bb7,0x6b8981ca,0x2d8a4f4d}}, // saav, rwan_, _rueg, _bube_,
+ {{0x6d59b239,0x6ed8001b,0xe7ea809a,0x6b898388}}, // towa, न्धु, _जैसा_, _sueg,
+ {{0x2ee58a0f,0x7ae75eb6,0xab5b0192,0x2d8a5360}}, // zelf_, lejt, brüc, _dube_,
+ {{0x6d598063,0x69d500ad,0x39495eb7,0x2d9802be}}, // rowa, ltze, mnas_, _etre_,
+ {{0x6d598063,0xa3aa923a,0x612a026f,0x6ed8123a}}, // sowa, _खबर_, _výle, न्दु,
+ {{0x69d500ad,0x80ac00c8,0x5ea780ab,0x7bc606a0}}, // ntze, _ছাত্, _খালে, zuku,
+ {{0x394938d2,0x3f8b007d,0x7bc60d7a,0x69d50102}}, // nnas_, _kucu_, yuku, itze,
+ {{0x60c9ad9c,0x6b81deb8,0x69c7136f,0x7ae7016b}}, // rgem, _tilg, huje, kejt,
+ {{0x60c9b3fc,0x3cfd80d4,0x3cdc001b,0x2caf9235}}, // sgem, _रोते_, ग्ने_, lagd_,
+ {{0xf1dda38c,0x3f8b5eb9,0x39495eba,0x69c70968}}, // _महिन, _lucu_, knas_, juje,
+ {{0xed5a023f,0x7bc65ebb,0x7d162b95,0x63a48087}}, // ром_, tuku, _qoys, ţine,
+ {{0xdbf406b7,0xbcfb5ebc,0xc178936d,0x75298c53}}, // इटेड_, plén, _اصلا_, _mlez,
+ {{0x59dd8935,0x41dd800c,0xf1dd800c,0xa3ab8eed}}, // _महार, _महास, _महान, कीय_,
+ {{0x69c7179d,0x6372006a,0x753b8365,0x2bb602f1}}, // guje, hæng, _omuz, _अटका,
+ {{0x3f8b00b9,0x752981bc,0x69820133,0x09be8264}}, // _bucu_, _nlez, _ọgbụ, _আহমা,
+ {{0x78ab8065,0x3f8b09ca,0x2d8a0876,0x2fc009e5}}, // [5840] _megv, _cucu_, _sube_, urig_,
+ {{0x69c7219f,0xe6b781c4,0x39495a4a,0x2fc04fa2}}, // buje, _आयोज, anas_, rrig_,
+ {{0x75228065,0xb87b026b,0x39495ebd,0x7529810c}}, // tkoz, _abíd, bnas_, _blez,
+ {{0x6364823e,0xed5a8198,0xdca3b1f0,0xab5b0192}}, // mòni, шое_, даци, prüc,
+ {{0x6364809f,0xfbcf8fc5,0xa2948a4c,0x0b938481}}, // lòni, نتو_, палі, وجود,
+ {{0x7529debe,0x9f400020,0x39403241,0x61fe0b80}}, // _elez, rvió_, liis_, _krpl,
+ {{0xcebf01d0,0x6364823e,0x9f400144,0xdd921190}}, // _úřad_, nòni, svió_, _آور_,
+ {{0x39404000,0x7529debf,0x315781a8,0x48149a19}}, // niis_, _glez, يجية_, змис,
+ {{0x8c4381a1,0xc17304de,0xfbd1801b,0x78ab81a9}}, // _жете, וחה_, _समयम, _degv,
+ {{0xa3df823c,0x6d4d01ed,0xdb061743,0x39495ec0}}, // _तहत_, _sjaa, nskä, znas_,
+ {{0x7ae71cf3,0x7bc2dec1,0x2d850019,0x61230074}}, // vejt, lrou, _élet_, _sõlm,
+ {{0xe4e7035f,0x69c70ac1,0xc0c782de,0x2480936f}}, // ціон, vuje, _душе_, žimu_,
+ {{0x39498289,0xdb060106,0x394001c7,0x7ae701d0}}, // čast_, kskä, diis_, tejt,
+ {{0x4f57806b,0x7c2f87d9,0x6364823e,0x98a38196}}, // _وجود_, _ücre, fòni, ėją_,
+ {{0xd13b0698,0x7ae75ec2,0x3a25007b,0x3f8b5ec3}}, // аха_, rejt, álp_, _sucu_,
+ {{0x69d50379,0x2d9edec4,0xdc2b0077,0x2d848074}}, // rtze, mpte_, _نسخه_, _nime_,
+ {{0x394938fa,0x7ae700f1,0x76b90071,0x69c758d6}}, // [5850] rnas_, pejt, йлар_, suje,
+ {{0x6d5d5ec5,0x2d8482be,0x26cccf24,0x3f8b017f}}, // losa, _aime_, ngdo_, _vucu_,
+ {{0xa2c48894,0x7529dec6,0x272980e1,0x8c3da75e}}, // _राज्, _plez, _júna_, _ieşi,
+ {{0x2d848699,0x5c7595ac,0x6f18811a,0x39405ec7}}, // _cime_, плет, _lovc, ciis_,
+ {{0x03a58790,0x7bc290b6,0xb11381bc,0x3cfc03c8}}, // чило, grou, _hụgh, ילאנ,
+ {{0x6f18803a,0x6b8d3ff5,0x60cd5ec8,0xdefb01e2}}, // _novc, _muag, lgam, _тым_,
+ {{0x6d5d018c,0x291936d9,0x2729807b,0x6b8d0122}}, // kosa, _kosa_, _núna_, _luag,
+ {{0x60cd308f,0x753b9d61,0x63720366,0x3cf50035}}, // ngam, _umuz, mæne, इयों_,
+ {{0x853d01e2,0xd90c8077,0x6d5d37c0,0x2572026f}}, // ipėd, شیو_, dosa, nále_,
+ {{0x248932d1,0xc61480ab,0x6b9bdec9,0x636d808b}}, // _agam_, াননা_, _itug, búna,
+ {{0xf8bf00a9,0xf9f9815b,0x98cb0035,0x63a982c4}}, // _além_, _دفاع_, िलाए, _hwen,
+ {{0x63a9cece,0x2919062f,0xbcfb5eca,0x60cd0168}}, // _kwen, _nosa_, blém, jgam,
+ {{0x63a98205,0x8c3d8457,0x0c233b9e,0x68e9ae46}}, // _jwen, _beşi, емян, meed,
+ {{0x63a9be1f,0xe2972118,0x68e98079,0x69c389ff}}, // _mwen, зар_, leed, orne,
+ {{0xc6928158,0x394009f1,0x63a98c6a,0x6d4b9400}}, // _טאג_, tiis_, _lwen, knga,
+ {{0x3991decb,0x291914e7,0x60cd1066,0x55ba8051}}, // _más_, _cosa_, ggam, _המקו,
+ {{0x8ca20063,0x2919059c,0x39405ecc,0x68f6031d}}, // [5860] _क्यो, _dosa_, riis_, rdyd,
+ {{0x68e98079,0x7bc2decd,0x63648722,0x6d4b9272}}, // heed, vrou, pòni, enga,
+ {{0x39918a56,0xceb28039,0x29192509,0x442201b0}}, // _nás_, גיב_, _fosa_, lyk_,
+ {{0x7bc29a93,0x63a98555,0x6b8d01c0,0x6372006a}}, // trou, _bwen, _yuag, sænd,
+ {{0x68e9d735,0x8c3d8214,0x63a9810c,0x44220196}}, // deed, _yeşi, _cwen, nyk_,
+ {{0x6d4bcac5,0xf1a6b314,0x2d9edece,0x6b9b80f7}}, // anga, прин, ypte_, _dtug,
+ {{0xdca69860,0x2d84804c,0xa2cd8ebf,0x64a69273}}, // _нави, _time_, _सार्, _нава,
+ {{0x44225e18,0x079884ae,0x79860133,0x68e981b4}}, // kyk_, овић_, _jikw, geed,
+ {{0x6d5d0284,0x63a9cd09,0xb8d48105,0x69c3decf}}, // vosa, _gwen, _जज_, arne,
+ {{0x539b8bea,0x798607d8,0x6d4298cb,0xb4bc86b7}}, // _לימו, _likw, lioa, _आयु_,
+ {{0x6d5d13ec,0x3991ba6f,0x7c2286c0,0x60cd02d0}}, // tosa, _gás_, lyor, ygam,
+ {{0xab5b01ec,0x6b8d01c0,0x6d429b1f,0x7aeaaad3}}, // grün, _puag, nioa, left,
+ {{0x7c22c219,0x645a0087,0x8c3d8214,0xf76f803d}}, // nyor, ştin, _peşi, ضای_,
+ {{0x6d5d12a5,0x6b8d01c0,0x79860362,0xb113819d}}, // sosa, _vuag, _aikw, _pụgh,
+ {{0xe9ff8104,0x29192f10,0x69d88422,0xd6db81c6}}, // _phẩm_, _posa_, ntve, _החול,
+ {{0x613101a3,0x79865ed0,0x3eae9cb6,0x7aea878b}}, // _målg, _cikw, _heft_, heft,
+ {{0x291901df,0x79865ed1,0x20540162,0x752d5ed2}}, // [5870] _vosa_, _dikw, ектя, _ilaz,
+ {{0x9f4dcdf9,0x69ca8ace,0xc987002e,0xb1138133}}, // rveç_, kufe, _нуми, _tụgh,
+ {{0x27ed8247,0x63a9ded3,0xe9ff801c,0x6b9bded4}}, // zwen_, _swen, _thẩm_, _stug,
+ {{0xe8eb0117,0x3eae8039,0x39918dbf,0x6d428c7b}}, // _اردو_, _left_, _rás_, gioa,
+ {{0x6d932f1e,0x68e98264,0x29d716dc,0xdb062417}}, // _għan, veed, lça_, rrkö,
+ {{0x3eae8085,0x69d8ded5,0x7aea806a,0x3991816b}}, // _neft_, ftve, geft, _pás_,
+ {{0x29d7231e,0x44221bae,0xdce436c5,0x68e9b7d3}}, // nça_, zyk_, _uhić, teed,
+ {{0x39918775,0x6d42802e,0x212b809a,0xc2e90264}}, // _vás_, cioa, óch_, খাটি_,
+ {{0x27ed8a0f,0x68e990b0,0x69dc816d,0xf1b804b7}}, // uwen_, reed, _ären, ġġi_,
+ {{0x27ed8a12,0x7d1b8051,0x752d5ed6,0x29d70214}}, // rwen_, _hous, _alaz, kça_,
+ {{0x752d0a0c,0x7bcbb722,0xc3328039,0x7d1bded7}}, // _blaz, lugu, _שוב_, _kous,
+ {{0xc3292afe,0x7d1bb2bb,0x3f9f8198,0x44225ed8}}, // _דו_, _jous, ppuu_, tyk_,
+ {{0x394dded9,0x7bcb977c,0xd90f8019,0x7d1b99c3}}, // mnes_, nugu, _لیں_, _mous,
+ {{0x7d1bdeda,0x6d4280ad,0x44225edb,0x395f8551}}, // _lous, zioa, ryk_, lous_,
+ {{0x394d9220,0xaca3819d,0x7bcb9f17,0x44222133}}, // ones_, _anọn, hugu, syk_,
+ {{0x752d0971,0x394ddedc,0x7d1b8009,0x3f8f8079}}, // _glaz, nnes_, _nous, _kugu_,
+ {{0xef1a0ff0,0xa06718a0,0x394ddedd,0xdb0b8019}}, // [5880] ома_, дата_, ines_, zsgá,
+ {{0x7bcbdede,0xe7e5809a,0x3f8f80fc,0xe6a80d86}}, // dugu, _कहना_, _mugu_, गरेज,
+ {{0x2d9c020f,0x7d1bcfac,0x3f878006,0x3f8f8ccf}}, // ëve_, _bous, _minu_, _lugu_,
+ {{0x7d1ba4c4,0x53d2058c,0x395f809f,0xdce280fe}}, // _cous, _तमाश, jous_, ploč,
+ {{0x6d428c76,0x7bcbad80,0x7d1bdedf,0x394da1c5}}, // rioa, gugu, _dous, dnes_,
+ {{0x394d9726,0x3f87877f,0x7d09a7e2,0x6d42dee0}}, // enes_, _ninu_, _enes, sioa,
+ {{0x7aeadee1,0x8e830bba,0x7c228ad4,0x7bcb80eb}}, // reft, _агре, syor, augu,
+ {{0x69d8a39e,0x394d8eb7,0x2d8e9471,0x69ca82af}}, // rtve, gnes_, _rufe_, rufe,
+ {{0x6d40aa4d,0x6f1c330c,0x3f8f80fe,0x69ca8db1}}, // _imma, _korc, _cugu_, sufe,
+ {{0x752d0052,0x3f8f811e,0x394d9b01,0x7d09807a}}, // _slaz, _dugu_, anes_, _znes,
+ {{0x6f1c0cf8,0xd6d78fe6,0x395f907c,0x645a0087}}, // _morc, фты_, bous_, ştil,
+ {{0x2ca6838e,0x394d817f,0x3f87807b,0xa00a80f7}}, // nbod_, cnes_, _einu_, _وقال_,
+ {{0x248d012b,0x68e29a90,0x7b67245b,0x27328028}}, // žemo_, _ibod, дтве, _nâng_,
+ {{0x61310370,0x6d9301cd,0xf0478416,0x2ef80118}}, // _måle, _bħal, _تعمی, _narf_,
+ {{0x6d40dee2,0x7af70201,0x20020b8a,0x6b88dee3}}, // _omma, _vaxt, _arki_, _midg,
+ {{0x29d726e1,0x752d003a,0x6376dee4,0x3944de16}}, // rça_, _ulaz, lând, nims_,
+ {{0x518422cb,0x6f1c0965,0xb8f715bc,0xc9840012}}, // [5890] тура, _borc, _सा_, тури,
+ {{0x394dd89a,0x6f1c5ee5,0xce6b0381,0x6d40d359}}, // znes_, _corc, оред_, _amma,
+ {{0x7d1bace0,0x6d932f1e,0x68e2882e,0x6f1c032f}}, // _pous, _għal, _obod, _dorc,
+ {{0x63ad40a6,0x629a816d,0x20020267,0x7bcba566}}, // _kwan, _afto, _frki_, tugu,
+ {{0x68ed0ece,0xe299a657,0x290b002e,0x5f11952c}}, // mead, пал_, _inca_, ताम्_,
+ {{0x63ad114c,0x68ed008c,0x26e380c8,0x68e2ab5f}}, // _mwan, lead, _কোনো_, _abod,
+ {{0x7d1bcfac,0xe0d18013,0x394ddee6,0x4429c4a9}}, // _tous, هزة_, tnes_, ša_,
+ {{0x21295ee7,0x3f879ea7,0x68ed14f2,0x394d82be}}, // nkah_, _sinu_, nead, unes_,
+ {{0x394d805f,0x8b678250,0xd7e68198,0x212900b9}}, // rnes_, _قائم, емые_, ikah_,
+ {{0x27e404b8,0x68ed261b,0x395fdee8,0x394dcf74}}, // _ämne_, head, sous_, snes_,
+ {{0x63ad028d,0x21290b20,0x3944bd80,0x395fdee9}}, // _awan, kkah_, bims_, pous_,
+ {{0x63ad0b15,0x3f8fdeea,0x68ed5eeb,0x67b9830f}}, // _bwan, _tugu_, jead, _فاتح_,
+ {{0x78279c12,0x69c1026f,0x3f87826b,0x236580b9}}, // _تعال, álen, _tinu_, _kklj_,
+ {{0x27328012,0x63ad242f,0x643a80be,0xf8bf04be}}, // _când_, _dwan, לערנ, _adé_,
+ {{0x63ad479e,0xc7b98019,0x21290859,0x291d84b9}}, // _ewan, lső_, fkah_, _kowa_,
+ {{0x68fb9922,0x6f1c1390,0x68ed0ce2,0x6376802e}}, // ndud, _sorc, gead, mâne,
+ {{0x63ad5eec,0x291d866f,0x68f9d462,0x63bb838a}}, // [58a0] _gwan, _mowa_, _mawd, _nvun,
+ {{0x248dbd1a,0x2732b7ad,0x399508bd,0x2d9116eb}}, // _agem_, _vâng_, _fås_, _buze_,
+ {{0xfad7093f,0x63ad0d35,0xaac983dd,0x02c983b7}}, // _אויך_, _zwan, _राजक, _राजभ,
+ {{0x8fa6130f,0x68ed0635,0x69dc1bc2,0x6d9304b7}}, // _паме, cead, mtre, _għam,
+ {{0x69dc2c42,0x6f1c5eed,0x69ce071f,0xead4abca}}, // ltre, _torc, lube, воль,
+ {{0x6d4624ec,0xbcfb0174,0x61310163,0x45160264}}, // hika, rléi, _våle, াসিক_,
+ {{0x69dc5eee,0x6d4634e8,0x3944809f,0x2d910669}}, // ntre, kika, tims_, _guze_,
+ {{0x6d46178f,0x6d40deef,0x63a2b4d7,0x3f8a5ef0}}, // jika, _umma, ppon, _hibu_,
+ {{0x6d46296d,0x69ce0234,0x69c70654,0x3944def1}}, // dika, hube, zrje, rims_,
+ {{0x63ad4685,0xb34580a9,0x442682e7,0x3f8a0010}}, // _rwan, riçã, myo_, _jibu_,
+ {{0x4426aad9,0xb3458073,0xa0a698a0,0x63ad55f2}}, // lyo_, siçã, _заед, _swan,
+ {{0x80e00a49,0x6d463afe,0x7afadef2,0x44268d10}}, // _পোস্, gika, _hatt, oyo_,
+ {{0x4426c9af,0x48fb0b04,0xb87b0091,0x27e68039}}, // nyo_, _लोगो_, _abín, _upon_,
+ {{0x25724533,0x7afa822b,0xa3e88e33,0x69c7007a}}, // rála_, _jatt, _बहन_, trje,
+ {{0x68ed4948,0xdb0405e4,0x4ac980d4,0x31c41354}}, // tead, _avió, _राघव, лств,
+ {{0x442682ec,0x69c700f1,0x212909ca,0x7bda002e}}, // kyo_, rrje, ukah_, ătur,
+ {{0x2129059c,0x2d91005c,0x63ad3c75,0xa3e50006}}, // [58b0] rkah_, _suze_, _uwan, _नहि_,
+ {{0x7afa8bfa,0x68ed5ef3,0x4426def4,0x21291ad4}}, // _natt, sead, dyo_, skah_,
+ {{0xceb38158,0x69ce08f1,0x69dc00f7,0x212914e4}}, // ריע_, cube, ctre, pkah_,
+ {{0xa567803d,0xa3e506a7,0x888300d7,0x7bdd06ae}}, // ردان, _नहा_, _میزن, otsu,
+ {{0x7bdd469a,0x27ff01df,0x4426def5,0x7afa9e68}}, // ntsu, _éun_, gyo_, _batt,
+ {{0x6d4621e0,0xbbd186ce,0x7bdd5ef6,0x68f9def7}}, // zika, _समीक, itsu, _sawd,
+ {{0x44268de2,0x62880279,0x7afadef8,0xe9ff80ff}}, // ayo_, ždov, _datt, _thậm_,
+ {{0x4426a1ea,0x25be007b,0x6009968a,0x63bbaca0}}, // byo_, _ætla_, дним_, _tvun,
+ {{0x7afadef9,0x4426ca82,0x69ce813c,0x43958098}}, // _fatt, cyo_, _åben, _разс,
+ {{0x7d0d3874,0x7afac02f,0x6d46020d,0xd2f78065}}, // _anas, _gatt, wika, _سکتا_,
+ {{0x64a60a4c,0xed5a2659,0x6ea78065,0x614789c4}}, // кана, дон_, ونکہ_, _cèlè,
+ {{0x2cb20201,0xd24e803d,0x63768187,0xceba80fc}}, // _qeyd_, _بچه_, râne, _roƙe_,
+ {{0x317f8214,0x7afade7e,0x78a9b30c,0x61358a09}}, // mmuz_, _yatt, mbev, _pálf,
+ {{0x03a30364,0x69dc5efa,0x8ca21199,0x316dad5d}}, // риро, ttre, _क्रो, llez_,
+ {{0x3f8a5efb,0x80e000ab,0xe2970972,0xf2d283de}}, // _ribu_, _পোষ্, тас_, יעם_,
+ {{0x69dc3807,0x78a983b2,0x3f8a37cf,0xdb0f00f7}}, // rtre, nbev, _sibu_, rscá,
+ {{0xf8b28158,0x7bcf4732,0x54a501a8,0x442681b4}}, // [58c0] ישן_, cucu, _صحيف, xyo_,
+ {{0x442680f6,0x61230074,0x9bb70039,0x69dc1db4}}, // vyo_, _sõlt, _שהיה_, ptre,
+ {{0x7afa92a5,0xb6068333,0x65628bb1,0x7bdb8580}}, // _ratt, _ibáñ, tooh, _aquu,
+ {{0x442681d3,0x7afa913f,0xb4ccb64b,0x2fc95efc}}, // tyo_, _satt, लले_, brag_,
+ {{0xeb978a7c,0xd843016b,0x7afa8ca9,0x30752503}}, // вич_, ročí_, _patt, _бурс,
+ {{0x4426defd,0x7afa822b,0x46a32597,0x6376841c}}, // ryo_, _qatt, _варв, dânc,
+ {{0x7afa84b8,0x442697ef,0x3cfd8074,0xf743197b}}, // _vatt, syo_, _रोके_, _теро,
+ {{0x7afadefe,0x4426820d,0x5ec700ab,0x63768187}}, // _watt, pyo_, র্দে, fânc,
+ {{0xe80505b3,0x798b831d,0x2bc9866b,0x63768187}}, // रिया_, _digw, _रिया, gânc,
+ {{0x61310186,0x7d0d1581,0xa2d70cf0,0x21ce8085}}, // _måla, _snas, _बाध्, _məhz_,
+ {{0x752081f6,0xb87b0032,0x7d0d008e,0x2d8c8061}}, // _komz, _abíl, _pnas, _édes_,
+ {{0xc4bf06bf,0x779289a7,0x64bf064a,0x255600eb}}, // ्लेख, _پیدا, ्लेश, tāla_,
+ {{0xd6d282e3,0xe9ff80ff,0xdb04016a,0x60c2b35d}}, // _نقش_, _thảm_, _aviñ, _idom,
+ {{0x7520deff,0x2d8792cc,0x7bdd1611,0x70cb000d}}, // _lomz, ínez_, rtsu, िल्ल,
+ {{0x442d003b,0x2736013c,0xa92300e1,0x7bdd5f00}}, // še_, _mænd_, áľov, stsu,
+ {{0x3998938d,0x2fc95f01,0x7520836a,0x7d0d004f}}, // _més_, trag_, _nomz, _unas,
+ {{0xaad2800f,0xdb0603c1,0x6aa25f02,0x60c28c53}}, // [58d0] _सांक, lský, _şofe, _mdom,
+ {{0x6d440063,0x656f5f03,0xa2f5004a,0x2cb90326}}, // _zmia, llch, _споч, lasd_,
+ {{0xdb06003e,0x60c2df04,0x2baf0072,0x39988866}}, // nský, _odom, जीरा, _nés_,
+ {{0xf65381c6,0xdcfc8085,0x60c2996c,0x6135826b}}, // בצע_, _vurğ, _ndom, _kále,
+ {{0x798b81b9,0xfbdf041c,0xd103970c,0xbcfb2538}}, // _rigw, ntê_, _शोषण_, llés,
+ {{0x6135807b,0xab6481e2,0x6376841c,0xf9901e29}}, // _mále, авіл, vânc, _دبی_,
+ {{0xa9699541,0x21698c8e,0x6145df05,0x68fd0110}}, // ника_, ники_, лека, _kasd,
+ {{0x637683a7,0xdb06016b,0xe9ff80ff,0x6c858019}}, // tânc, dský, _chạm_, _ملزم,
+ {{0x6135826f,0x1c07009a,0x78a981eb,0x68fd114b}}, // _nále, शिफल_, rbev, _masd,
+ {{0x656f151e,0x39493afc,0x60c2d56d,0x78a9874d}}, // elch, hias_, _edom, sbev,
+ {{0x63bd3d9c,0x458612ea,0x0d8608b0,0x2006b2a3}}, // essn, лгов, ллон, _eroi_,
+ {{0xaadf8744,0xb87b0032,0x629c0326,0x6135826b}}, // प्रक, _abím, wcro, _bále,
+ {{0x66e593f7,0x290f8408,0xeb971bba,0x39490625}}, // _бола, _inga_, лия_, dias_,
+ {{0xcc3a80be,0xa3d50074,0x656f051e,0xdd8e810c}}, // געשט, _हमर_, alch, _کوي_,
+ {{0xa2db82a8,0xbcfb009f,0x3949106c,0x69d4016f}}, // _नान्, glés, fias_, बंधी,
+ {{0x39491520,0x68fd0118,0x81b58264,0x6d930609}}, // gias_, _casd, _জমা_, _għai,
+ {{0xb4d00076,0x27eb0326,0x7d7b03c8,0x2fdfa476}}, // [58e0] शली_, _cpcn_, _אנקו, ntug_,
+ {{0x63a45f06,0x888310f8,0x6566029b,0x657bda01}}, // _itin, служ, lokh, _ahuh,
+ {{0x39495f07,0xf8bf02be,0x61359d9a,0x6b965f08}}, // bias_, _clés_, _zále, _huyg,
+ {{0x3949402b,0xa37b03a7,0xac97803d,0x68fd5ded}}, // cias_, rtõe, هنما_, _gasd,
+ {{0x77f28dbc,0x26f38076,0xa37b0073,0x7afe0eb9}}, // _अनेक_, _असूर_, stõe, _kapt,
+ {{0x81c900c8,0xf7460364,0x08fc00c8,0x6ed88540}}, // োবর_, _сего, _একটু_, _माधु,
+ {{0x27e03457,0x82970051,0xe9ff8028,0x41e680f7}}, // ltin_, נדקס_, _phạm_, استف,
+ {{0x29003a70,0xd01080f7,0xdb060e04,0x20068267}}, // ndia_, قلب_, vský, _proi_,
+ {{0x3d11835a,0x63a40135,0xfaa69bc1,0x473481a4}}, // णारे_, _ntin, _базо, инис,
+ {{0x138a80f7,0xdb06016b,0x4ad303eb,0x3998816a}}, // _أخرى_, tský, _ताइव, _tés_,
+ {{0x22938307,0x63a45f09,0x49ca8003,0xaa938829}}, // _المس, _atin, _член_, _المث,
+ {{0x39495f0a,0xdb062a5a,0x9f490125,0x27e02ad3}}, // xias_, rský, hvað_, ktin_,
+ {{0x39491e00,0x394684c3,0xbf9b03a7,0xa2db8a0d}}, // vias_, _imos_, _prêm, _नाम्,
+ {{0xa3c98076,0x39468282,0x6b960214,0x613581d0}}, // लूट_, _hmos_, _duyg, _vále,
+ {{0x2d9c00a9,0x63a40870,0x5fc70006,0x13d18b04}}, // ível_, _etin, _लिहल, _सम्भ,
+ {{0x27e05f0b,0x2900009f,0xddcb04b7,0xbcfb0061}}, // ftin_, gdia_, ċiżj, rlés,
+ {{0x39491313,0x7afe002e,0x3f8e85ee,0x3eba0493}}, // [58f0] rias_, _fapt, _kifu_, fapt_,
+ {{0x39495f0c,0xaadc016f,0x63600061,0x3cfd99ee}}, // sias_, _बायक, köny, _रोजे_,
+ {{0x39492b72,0x68fd5f0d,0x3946ddaf,0x06cc00ab}}, // pias_, _tasd, _omos_, র্ভি,
+ {{0x67d510ef,0xc56b8060,0xe7e582f1,0x68e40174}}, // _кому, رحال_, _कहला_, cfid,
+ {{0x7afe07d9,0x78ad30fc,0x61fa82f9,0x6e950749}}, // _yapt, mbav, _astl, _вигу,
+ {{0xa3d60576,0x26c3008e,0x290f8122,0xeb969577}}, // ांत_, _udjo_, _snga_, шиш_,
+ {{0x2bd185e8,0x657b80f1,0x316b0088,0x798f03f7}}, // _समका, _thuh, _kkcz_, _kicw,
+ {{0xa9258258,0xceba8326,0x5ec70264,0x613113c2}}, // рдил, _roƙa_, র্শে, _målo,
+ {{0x6d4bbc59,0x61fa9e5e,0x2f55ccf4,0x64c80196}}, // miga, _estl, итос, nčių,
+ {{0x3cff90af,0x5745a133,0xb87b0032,0xe9ff80ff}}, // _hauv_, рноб, _abík, _nhầm_,
+ {{0xd2518064,0xbb8480f7,0x6285826f,0x3cff822c}}, // _بنا_, _اللي, ýhod, _kauv_,
+ {{0xeb9a00bf,0xd0118013,0x6d4b8e01,0x290fdf0e}}, // ким_, _ولا_, niga, _unga_,
+ {{0xe72e8934,0xed570a08,0x3cff8282,0x3ce900b9}}, // _де_, роя_, _mauv_, _dbav_,
+ {{0xa3e885e8,0x2d9582a4,0x3cffdf0f,0x6d4bdf10}}, // _बहस_, _крис, _lauv_, higa,
+ {{0xc3328039,0x98a05f11,0x6d4bc9af,0xa2d706bf}}, // חום_, nkić_, kiga, _बार्,
+ {{0x290009e8,0x10a602bc,0x18a61246,0x7afe5f12}}, // udia_, риан, раам, _wapt,
+ {{0x6d4bdf13,0x6723df14,0x7afe102a,0x63a41267}}, // [5900] diga, _konj, _tapt, _utin,
+ {{0x613104b8,0x27e05f15,0xdb06008b,0x60d61388}}, // _håll, rtin_, rskó, sgym,
+ {{0xc0e601b5,0x48e60a95,0xdb2402e3,0x67239fd6}}, // _конк, _конв, _نوکی, _monj,
+ {{0xb64c8158,0xfce6a3fc,0x672386c0,0xb4c2800d}}, // עגאָ, родо, _lonj, ूले_,
+ {{0x638681a1,0x2ca982ba,0x3cff81c0,0x613801d0}}, // агда, ñada_, _dauv_, _cíle,
+ {{0x69ce0a0f,0x31c68d0e,0xa9c6a630,0x05c580d4}}, // orbe, асив, асик, _विंब,
+ {{0xd24f845a,0x6d4baeba,0xd90d826a,0xdce280c3}}, // ونه_, biga, کین_, looč,
+ {{0x3f8e80ee,0x75243de3,0xbcfb01d0,0x32cc8032}}, // _sifu_, _noiz, jlép, _bóyà_,
+ {{0x6723df16,0xd0e2873c,0x69c18214,0x14a70d72}}, // _bonj, क्षण_, _evle, _क्षण,
+ {{0x6723df17,0x80a6904f,0x257580f2,0x2d98298d}}, // _conj, _ट्रे, håll_, _kure_,
+ {{0x3eb80065,0x6723803b,0x6abc00e3,0x2fcddf18}}, // _mert_, _donj, darf, areg_,
+ {{0x7d02cb3d,0x7d0096f2,0x2d985f19,0x2fcd80fe}}, // ndos, _jams, _mure_, breg_,
+ {{0x6abc031d,0x69c85f1a,0x6723c0d4,0x69ce09e5}}, // farf, áden, _fonj, erbe,
+ {{0x6d4baa8d,0x7d009345,0x98a9050b,0x6d5d0036}}, // ziga, _lams, rkač_, érab,
+ {{0x6135c93e,0x48dd86ab,0x6d4babd7,0xff7b83de}}, // _hála, क्को_, yiga, _שטומ,
+ {{0x2d520024,0x75240102,0x67238168,0x7d00df1b}}, // všeg_, _goiz, _zonj, _nams,
+ {{0x3eb85497,0x2d985f1c,0x3cff822c,0x69ce5468}}, // [5910] _bert_, _aure_, _rauv_, arbe,
+ {{0x3eb852f7,0x6135df1d,0x2d985f1e,0x3cff822c}}, // _cert_, _mála, _bure_, _sauv_,
+ {{0xd0068416,0x2bc986b7,0x3cff81c5,0x7d00b6cb}}, // _کل_, _रिहा, _pauv_, _bams,
+ {{0xe1f01fbe,0x7d00df1f,0x2d985f20,0x3cff81c5}}, // _کسی_, _cams, _dure_, _qauv_,
+ {{0xa3b285b3,0x6d4ba653,0xfc3f001b,0x3eb85f21}}, // झील_, riga, nzí_, _fert_,
+ {{0x6d4bcd46,0x3eb85f22,0x2d520503,0x3da70073}}, // siga, _gert_, pšeg_, _враб,
+ {{0x2d98011e,0x6d4bdf23,0x7d008168,0x3cff822c}}, // _gure_, piga, _fams, _tauv_,
+ {{0x672386c0,0xe9a880f7,0x7d00ce6f,0xe8f99263}}, // _sonj, جدون_, _gams, ули_,
+ {{0xb8ffa128,0x6d498ac7,0x2d98011e,0x6d5b84cd}}, // _ता_, _imea, _zure_, _ijua,
+ {{0x3f990803,0x636d85e4,0x2fcddf24,0x257201fa}}, // _musu_, gúnt, rreg_, máli_,
+ {{0x3ea102af,0x37a800ab,0x6723d96e,0x752402d4}}, // icht_, _ওয়ার, _vonj, _poiz,
+ {{0xc329004c,0x69d5003b,0x3ce30076,0xb8958013}}, // _או_, duze, ट्ले_, _الإع,
+ {{0x394ddf25,0x6abc007b,0x3f99004f,0x291f9c40}}, // mies_, tarf, _nusu_, mjua_,
+ {{0xfaa31a47,0x673a80f3,0xeb971a1b,0x7af55382}}, // заро, chtj, сир_, gezt,
+ {{0x2bd1800f,0xbb431cad,0x69d512e5,0x200b01e8}}, // _समझा, черк, guze, _arci_,
+ {{0x3a2500f2,0x399c01fa,0x3eb85f26,0xa3e88dd2}}, // älpa_, _vís_, _sert_, _बहल_,
+ {{0x4e961301,0x46ea07b6,0x3d155f27,0x2d980039}}, // [5920] مشار, лдан_, धारे_, _sure_,
+ {{0x6d49ad7e,0x3f9904a8,0x60dd026c,0x69d52665}}, // _amea, _dusu_, _ecsm, buze,
+ {{0xd0f983b7,0x394dc613,0xa3d60006,0x3eb85f28}}, // ्याण_, kies_, ांव_, _vert_,
+ {{0x61fe003a,0x3ea10013,0x394ddf29,0xd9100077}}, // _ispl, acht_, jies_, _تیر_,
+ {{0x200b02a5,0x6ed89344,0x394dd87d,0xe607804e}}, // _grci_, _मालु, dies_, _کش_,
+ {{0x290203c3,0xf6d5035f,0x8fa30098,0x7d028168}}, // _haka_, _міся, _харе, sdos,
+ {{0x7d00df2a,0x394ddf2b,0x27298118,0x61359434}}, // _tams, fies_, _lúns_, _sála,
+ {{0x21200796,0xf99304de,0x68f635c2,0x29021c7c}}, // njih_, הרה_, neyd, _jaka_,
+ {{0x29025f2c,0x3d1c053e,0x74548065,0x7af50061}}, // _maka_, याने_, _بھائ, yezt,
+ {{0x61358986,0x19ba8765,0xcafc83eb,0x2d920091}}, // _vála, _будь_, _एसिड_, _aiye_,
+ {{0x394dba65,0xd9469b53,0x25a7802a,0xb99680f7}}, // bies_, _лежи, _ctnl_, _الصب,
+ {{0xd7fe0104,0x673a820f,0x29021b28,0x2d80019d}}, // _đăng_, shtj, _naka_, _chie_,
+ {{0x2d9207d9,0x7d7b8039,0x716580f7,0xdb09807b}}, // _diye_, טרטג, _بالك, _kveð,
+ {{0x799a93b2,0x29020032,0x637f8187,0x613cc28b}}, // _kutw, _aaka_, cênd, _bélg,
+ {{0x29025f2d,0x3f99059c,0xa2db9905,0x2d9200a4}}, // _baka_, _susu_, _नास्, _fiye_,
+ {{0x69d50b67,0xa3d60eed,0x799ab6ed,0x2904870e}}, // ruze, ांश_, _mutw, ndma_,
+ {{0x61fe5f2e,0x645a07d9,0x76b901a1,0x63b6031d}}, // [5930] _espl, ştir, илар_, _gwyn,
+ {{0xec380051,0x3ea102af,0x394dd6bd,0x6d49df2f}}, // _לאחר_, ucht_, zies_, _smea,
+ {{0x2ca982ba,0x3f9102a5,0xa3db816f,0x2902036a}}, // ñado_, _vizu_, ढून_, _faka_,
+ {{0x3ea102af,0x5c758758,0x200b1487,0x3d1c00c2}}, // scht_, олет, _trci_, याये_,
+ {{0xb6078a56,0x320c808e,0x3ebe919b,0x394dd47c}}, // _koší, _ardy_, katt_, vies_,
+ {{0x29025f30,0x85bb12c5,0x55bb00d7,0x394dbaf5}}, // _zaka_, _مارس_, _مطرح_, wies_,
+ {{0x394ddf31,0xb529026b,0x7aea01ec,0x3d1c052a}}, // ties_, _akọ̀, _öfte, यामे_,
+ {{0x5d549d8f,0x6d49df32,0x81ea80ab,0x672708dc}}, // _екст, _umea, মিং_, _hojj,
+ {{0x394d8b39,0x200b803e,0x320c82c4,0xe8df80ff}}, // ries_, íci_, _erdy_, hiện_,
+ {{0x394ddf33,0x63a98073,0x656bb4d7,0x6d93822b}}, // sies_, _iten, logh, _għar,
+ {{0x6d598901,0x2d923ba1,0x799a8b5e,0x637f841c}}, // onwa, _piye_, _gutw, gêne,
+ {{0x29038029,0xdb06016d,0x656bb271,0x6b9bdf34}}, // ējas_, nskö, nogh, _kuug,
+ {{0xeb9a01a1,0x3ebedf35,0xae17815b,0x4ae0816f}}, // риг_, batt_, _گذشت, _नामव,
+ {{0x29020288,0x3b0a0009,0x63a9804f,0x3ebe8037}}, // _saka_, шего_, _mten, catt_,
+ {{0xcb12878d,0x2902387e,0x68f60009,0xdb060106}}, // ולם_, _paka_, teyd, kskö,
+ {{0x60dbdf36,0x6f03df37,0x63a9a444,0x6138007b}}, // ngum, _hanc, _oten, _bíla,
+ {{0x6f03df38,0xfe780110,0x2120007a,0x29025f39}}, // [5940] _kanc, _šį_, rjih_, _vaka_,
+ {{0xef17176a,0x29021855,0x69c3df3a,0x6721817f}}, // ому_, _waka_, ksne, njlj,
+ {{0x29025f3b,0xe4d309a7,0x60db80b9,0x6a800032}}, // _taka_, _عقید, kgum, _dáfí,
+ {{0x7bc2951e,0x6b9b8079,0x186a0c4f,0xb4db023e}}, // tsou, _buug, _вами_, liàr,
+ {{0x7d045f3c,0x06cc00ab,0x25ad016b,0x69c101d0}}, // _mais, র্ষি, _čele_, šlet,
+ {{0xaff900be,0x79a690ff,0x6d4f1598,0x60db80eb}}, // _פּרי, орие, zica, egum,
+ {{0x63a9df3d,0x7bc2bd14,0x68e9df3e,0x613c8718}}, // _eten, ssou, ffed, _léle,
+ {{0x7d045f3f,0x2bd28592,0x442f8114,0x26c700e1}}, // _nais, _सिपा, byg_, ónov_,
+ {{0x6f0380ee,0xd83a8196,0x290280e1,0x2572016b}}, // _banc, рэй_, ľka_, nálu_,
+ {{0x29029807,0x6d4f5f40,0x6f03ac04,0x61358118}}, // žka_, wica, _canc, _fálo,
+ {{0x6d4f160c,0x7d045f41,0x3ebedf42,0x613c8019}}, // tica, _bais, ratt_, _péld,
+ {{0x7d0427b0,0x3ebea1ad,0x25a50118,0x6f038229}}, // _cais, satt_, _élle_, _eanc,
+ {{0x6d4f5f43,0x61359d9a,0x60c0df44,0x6442b24d}}, // rica, _zálo, mamm, nzoi,
+ {{0xdcfc81e2,0x491281d0,0x48f481d0,0x613cc042}}, // _turė, थाको_, ्यको_, _déle,
+ {{0x7d045576,0xd90f0077,0x656b8135,0x61380825}}, // _fais, نید_, yogh, _síla,
+ {{0x7d045660,0xa2d70768,0x6f038041,0x6edd0d14}}, // _gais, _बाक्, _zanc, _पाहु,
+ {{0xe572093f,0x6d4d5f45,0x6f0384b9,0x637f8187}}, // [5950] _אַז_, _imaa, _yanc, nênc,
+ {{0x7d0402a5,0x61310aa2,0xb87b0118,0x69d8a366}}, // _zais, _måli, _acíc, kuve,
+ {{0x69d8df46,0x2d9cdf47,0x6b9b8079,0x442fa937}}, // juve, _juve_, _suug, wyg_,
+ {{0x2fc68025,0x442f84b8,0x61358144,0x3ebca67f}}, // _ovog_, tyg_, _cáll, _levt_,
+ {{0x18350158,0x656bdf48,0x613583a8,0x60c081b4}}, // _זאָל_, rogh, _dáll, damm,
+ {{0x637f8073,0xd6d817d4,0x2d9ccc78,0x257c80e1}}, // dênc, _мтс_, _ouve_, ríla_,
+ {{0x6d4d2d62,0x69d8df49,0x6f039342,0xe5c4148d}}, // _omaa, guve, _ranc, _осто,
+ {{0x25720019,0x69c39151,0x26c15f4a,0x61358019}}, // nált_, rsne, laho_, _válo,
+ {{0x6f03b996,0x63a9df4b,0x798282a3,0x2bd2800f}}, // _panc, _uten, _dhow, _सिया,
+ {{0x7d045f4c,0x613100f2,0x6d4d5f4d,0x6f0381b4}}, // _sais, _dåli, _amaa, _qanc,
+ {{0x613c82be,0xe8020540,0x366999a4,0xb9e780e8}}, // _séle, _रैना_, сано_, _фізи,
+ {{0xed5a00e2,0x6f03df4e,0x60c0df4f,0x78bd35aa}}, // сом_, _wanc, camm, _nesv,
+ {{0x6f03df50,0x7d045f51,0xc95284de,0x26c12e5f}}, // _tanc, _vais, _אמא_, kaho_,
+ {{0x613c8065,0x200f8110,0xdb0600e1,0x6d4d5f52}}, // _véle, _irgi_, jskô, _emaa,
+ {{0x7d045f53,0x78bd0a33,0xaca4019d,0xd2520061}}, // _tais, _besv, _ahụw, رنر_,
+ {{0x25aa86a5,0x5fc70006,0x7d040c41,0x3f9da168}}, // íble_, _लिखल, _uais, _kuwu_,
+ {{0x78bd5f54,0xf8bf002a,0xaa8a004e,0xeaaf0019}}, // [5960] _desv, _toén_, _گندم_, ئٹہ_,
+ {{0xdfcf00a0,0xb87b0032,0x78a401e8,0x869a0c67}}, // ايل_, _abís, sciv, стат_,
+ {{0x1a9b0158,0x7bc60e51,0x4a9b00be,0x6edd0074}}, // _לייע, lsku, _לייג, _पारु,
+ {{0x63768012,0xc4868cdf,0xdb0d007b,0xbf9b00e7}}, // mâni, плек, _hvað, _prêt,
+ {{0x26c12087,0x61358065,0x7bc65f55,0xa806802a}}, // baho_, _váll, nsku, _mañó,
+ {{0x7982a79a,0x69d8a52a,0xfc3f0511,0x61358118}}, // _show, tuve, ncía_, _dálm,
+ {{0x2fc68025,0x60c0ab92,0xa50a8098,0x200f8102}}, // _svog_, tamm, _лева_, _argi_,
+ {{0xd13080f7,0x637f83a7,0x613101a3,0x7bc61d49}}, // ئمة_, tênc, _påli, ksku,
+ {{0x60c08e51,0x7af882d4,0x2d9c9adc,0x255601a9}}, // ramm, pevt, _suve_, nālu_,
+ {{0x60c0df56,0x637f80a9,0x6b6697ae,0x6d4d03b2}}, // samm, rênc, _экза, _smaa,
+ {{0x2cad0e15,0x2480083a,0x637f83a7,0x6d4d0122}}, // ñedo_, _azim_, sênc, _pmaa,
+ {{0x2fc68052,0xc27b810f,0x4a7b80be,0xa8068020}}, // _tvog_, _פראי, _פראב, _cañó,
+ {{0x7bc6126c,0x78bd08dc,0xdfcf81a8,0xddd380c3}}, // gsku, _resv, _مين_, čaše,
+ {{0x3b058085,0x2d9cdf57,0x661c8884,0x9abc84b7}}, // _xalq_, _tuve_, ärks, _soċj,
+ {{0x637683a7,0x81ea80ab,0x26c100b4,0x61310711}}, // gâni, মিক_, vaho_, _målv,
+ {{0x6d4d45bb,0x39400834,0x26c10a03,0xa3cd864a}}, // _umaa, ghis_, waho_, _शटल_,
+ {{0xb905184a,0x656f0063,0xb4d91299,0xb87b026b}}, // [5970] _ना_, moch, ाली_, _abír,
+ {{0xc6a40ba8,0x63ad5f58,0xd24e0065,0x09b280ab}}, // ерци, _itan, اچی_, _ঘটনা,
+ {{0x75299216,0x26c10578,0x20020214,0x6d5d5f59}}, // _poez, raho_, _eski_, onsa,
+ {{0x6d5d0046,0x656f5f5a,0xdced04a8,0x39401d8e}}, // nnsa, noch, _okač, chis_,
+ {{0xdfc68c3b,0x2919002e,0x2d520353,0x69c8868f}}, // _جي_, _insa_, kšen_, _avde,
+ {{0x68e2a509,0x63ad0010,0xb4d906b7,0x212b01ec}}, // _acod, _mtan, ालु_, _hoch_,
+ {{0x656f5f5b,0xd0158592,0x69c700e8,0x752980f3}}, // koch, तम्भ_, nsje, _toez,
+ {{0x56941e18,0x7bc65f5c,0x29090010,0x63ad1210}}, // _част, ysku, ndaa_, _otan,
+ {{0x600691e9,0x63ad23d2,0x69d502af,0x798980dd}}, // дным_, _ntan, hrze, rmew,
+ {{0x27e95f5d,0x212b3428,0x333984c0,0x02a79cf6}}, // itan_, _loch_, _بزرگ_, _храм,
+ {{0x63ad5f5e,0x7a3580f7,0x69d50035,0x291902c4}}, // _atan, _تفاص, jrze, _onsa_,
+ {{0x212b034a,0x6281812b,0x27e95f5f,0x69c75f60}}, // _noch_, _izlo, ktan_, dsje,
+ {{0x68ed00f7,0x27e92f9f,0x394fdf61,0x3d1e0424}}, // dfad, jtan_, _imgs_, _बचीं_,
+ {{0x6f0758d6,0x29192015,0xaca381bc,0xdce400d2}}, // _najc, _ansa_, _baịn, _okić,
+ {{0xe2971bc6,0x656f0870,0x39405f62,0xfc3f2d11}}, // дар_, boch, this_, rcía_,
+ {{0x656f1ed4,0xceba80fc,0x27e95f63,0x29190a2a}}, // coch, _roƙi_, ftan_, _cnsa_,
+ {{0x212b0352,0xa4940077,0x27e9033e,0xb8f6000d}}, // [5980] _doch_, _سیست, gtan_, _सय_,
+ {{0x39400282,0x613c80e7,0x3d1c016f,0x62818390}}, // shis_, _méla, यावे_, _ozlo,
+ {{0x27e90867,0xd946138f,0xa3d623e6,0x68fb8009}}, // atan_, _жени, ांक_, keud,
+ {{0x69dc1d36,0x7afc2d59,0xa2aa0105,0x27e926c2}}, // mure, lert, जुर्, btan_,
+ {{0x27e95f64,0x0edd816f,0x60c45f65,0x59830221}}, // ctan_, _मांड, maim, _шлюб,
+ {{0x656f39d9,0x7afc3e44,0x68e28037,0xbfa580ff}}, // zoch, nert, _scod, _việ,
+ {{0x656f386c,0x6298003e,0x63bbdf66,0x255601a9}}, // yoch, _úvod, _ewun, nāls_,
+ {{0x3f98011e,0x3d0692ee,0x60c45101,0x4ae0852a}}, // _hiru_, _सोचे_, naim, _नावव,
+ {{0xbcfb03d3,0x6d5d1ed4,0x69dc42d9,0x6f0701c0}}, // nnée, éram, hure, _yajc,
+ {{0x3ce10006,0x613c80e7,0x60c43d80,0x6446009a}}, // _काहे_, _déla, haim, dzki,
+ {{0x60c44612,0x68ed0214,0x6138002a,0xd5b78d15}}, // kaim, yfad, _píll, мся_,
+ {{0xb4d9000c,0x613c8125,0x2bd2816f,0x63a0c7ef}}, // ाले_, _féla, सळपा, _kumn,
+ {{0x7afc5f67,0x2d5220f9,0x656f5f68,0x69a4809a}}, // fert, pšen_, roch, _चंडी,
+ {{0xf64f8117,0x69d50063,0x7afc06a2,0xc87f81ec}}, // ائی_, trze, gert, üße_,
+ {{0x69dc5f69,0x656f5f6a,0x43940f04,0xda780607}}, // gure, poch, тарс, дят_,
+ {{0x60c40393,0x69c7401c,0x3f985f6b,0x61383b40}}, // gaim, rsje, _airu_, _fílm,
+ {{0xe811901b,0x3f98059c,0x68ed5f6c,0x27e902d6}}, // [5990] ़िया_, _biru_, rfad, utan_,
+ {{0x212b06c4,0x27e95f6d,0x69d50063,0xc8088028}}, // _woch_, rtan_, prze, _mở_,
+ {{0x212b0613,0x7bdd5f6e,0x7bc9abea,0x7ae54fb4}}, // _toch_, lusu, _sveu, _icht,
+ {{0x27e95f6f,0x29191341,0x613581d0,0x27ff8114}}, // ptan_, _unsa_, _válk, ywun_,
+ {{0x7bdd5f70,0x9f9a8009,0x27ff0081,0xc80880ff}}, // nusu, nään_, _èun_, _nở_,
+ {{0xfbd083f8,0x63a08012,0xc2c581a8,0x798d0326}}, // شته_, _dumn, ريبي, lmaw,
+ {{0xe1f19125,0xa4d5835f,0x18a585a8,0xe2968073}}, // است_, _пові, найм, наш_,
+ {{0x7afc33bc,0x9f9a8364,0x7bdd1fb6,0x6d42df71}}, // zert, kään_, kusu, choa,
+ {{0x7ae55f72,0x69dc80e8,0x0bd581a8,0x68fbbff4}}, // _ocht, _åren, _سياح, reud,
+ {{0x200dc3ff,0x7bdd5f73,0x6135807b,0x60c45f74}}, // rvei_, dusu, _máli, zaim,
+ {{0x7afc1527,0x06cc00c8,0x7d099400,0x06d500ab}}, // vert, র্কি, _kaes, স্থি,
+ {{0x7ae55bb0,0x7afc5013,0x613101a3,0xc0e9803d}}, // _acht, wert, _måls, _رفتن_,
+ {{0xd90f850c,0x752d0b6e,0x7afc5f75,0x3ce10beb}}, // _میں_, _doaz, tert, _कारे_,
+ {{0x69dc5f76,0x06d500ab,0xa8568039,0x66e5802e}}, // ture, স্তি, _אינה_, _чока,
+ {{0x60c43e95,0x7afc2dd7,0x63769fea,0x7d1bdf77}}, // taim, rert, mânt, _onus,
+ {{0x7ae525e5,0x395fdf78,0x7bdd5f79,0x26c585e7}}, // _echt, nnus_, busu, lalo_,
+ {{0x7afc010b,0x61359984,0x69dc05ef,0xfaa60254}}, // [59a0] pert, _cáli, sure, _папо,
+ {{0xe2058028,0x386604b7,0x60c45f7a,0xbcfb00e7}}, // _đóng_, ħor_, saim, rnée,
+ {{0x63a08025,0x1f3683c8,0x60c444ec,0x3f985f7b}}, // _sumn, ערער_, paim, _viru_,
+ {{0x80be823c,0x26c5ca76,0x9f40023e,0x395f81c0}}, // _एजें, halo_, stià_, jnus_,
+ {{0x26c5df7c,0x7d098683,0x3f9800dd,0x6135b355}}, // kalo_, _daes, _tiru_, _gáli,
+ {{0xdd318201,0x26c5df7d,0x4908000d,0x06d580ab}}, // _təşk, jalo_, ाएको_, ত্তি,
+ {{0xc8088028,0x3ce10035,0x6135816b,0xf77701c6}}, // _sở_, _काले_, _záli, _נעלי_,
+ {{0x7d1b826c,0x395f8687,0x2d52128a,0xab5b06ae}}, // _gnus, gnus_, ušel_, tsük,
+ {{0xb908ce09,0x6d40ad62,0x752d0706,0x9a6a804e}}, // _बा_, _ilma, _roaz, _کمال_,
+ {{0x4734845d,0x65641e8c,0x78a981e8,0x9f9a8198}}, // тнос, _ajih, acev, vään_,
+ {{0x0f570051,0x7d0981e0,0xd6d78fe6,0x613103ba}}, // תיים_, _yaes, хты_, _målr,
+ {{0x9f9a825d,0x3ce1016f,0x8fa69f72,0x7ae51a1f}}, // tään_, _काळे_, _заде, _scht,
+ {{0x26c5df7e,0x29578158,0xdb008168,0x6b9a838a}}, // balo_, _אסאך_, _numë, _kitg,
+ {{0xdb00816d,0x7bdd36b2,0xf8b8001c,0xd9f4816f}}, // _utmä, rusu, _đĩa_, _आहात_,
+ {{0x6d408b06,0x6b9a8352,0x2bd30006,0x0a680705}}, // _olma, _mitg, _तिवा, ерти_,
+ {{0x9f9a8009,0x7bdd5f7f,0x99980c51,0x798d008e}}, // sään_, pusu, екст_, umaw,
+ {{0x671c1094,0xa3d29a46,0x69d88192,0xdfd4062c}}, // [59b0] नांक_, _विष_, hrve, лоты,
+ {{0x6d40c7ea,0x61358907,0xa3d60697,0x4de486a7}}, // _alma, _váli, _हित_, _कमाई_,
+ {{0x78a204e8,0x7d098098,0x317200b9,0x316009c4}}, // žova, _paes, boyz_, bniz_,
+ {{0x60c29cf3,0x61358789,0x6285007a,0x2fcb0088}}, // _neom, _hálv, _izho, _tvcg_,
+ {{0x26c5bbb8,0x69d89f2e,0x9f4003a8,0x672e0197}}, // yalo_, erve, stiá_, _fobj,
+ {{0x2d52012b,0x69da0216,0x6d40df80,0x212902d4}}, // všem_, áteg, _elma, ljah_,
+ {{0x290b0288,0xa969a659,0x78a98087,0x63768087}}, // _kaca_, мика_, tcev, vânt,
+ {{0x290b02a5,0x26c5bebe,0x1a68803d,0x21295f81}}, // _jaca_, walo_, رینی_, njah_,
+ {{0x33f49e95,0x290b5f82,0x395fdf83,0xfbd0803d}}, // _مسلس, _maca_, rnus_, _هتل_,
+ {{0xee398fcb,0xddd88110,0xa2db93ba,0x78a9847f}}, // дно_, _gyvū, _नाट्, scev,
+ {{0x0d860bba,0x99d492dc,0x69d8812b,0x7bcd0198}}, // клон, _متنا, crve, _avau,
+ {{0xb4ac0e18,0x26c5df84,0x21295f85,0x7052826a}}, // गडे_, salo_, jjah_, انیا,
+ {{0x2129008e,0x06cc00ab,0x8afe80fc,0x00000000}}, // djah_, র্টি, moƙr, --,
+ {{0xd5d10076,0xeb970698,0x43755f86,0x290d811e}}, // _हिंज, кия_, густ, ldea_,
+ {{0x27ed834a,0x290b0867,0xeb971d91,0xdb0089c4}}, // lten_, _baca_, тир_, _dumè,
+ {{0x290db469,0x60c281df,0x27ed80c6,0x7bcb8338}}, // ndea_, _xeom, oten_, ksgu,
+ {{0x27ed861b,0x290b0c6e,0x672e02fd,0x31605679}}, // [59c0] nten_, _daca_, _pobj, rniz_,
+ {{0x27edcf62,0x5fd4816f,0x249f9142,0x6fd48035}}, // iten_, _दिसल, _agum_, _दिसं,
+ {{0x26c35f87,0xe7fe0740,0x290b3296,0x27eddf88}}, // _dejo_, _उनका_, _faca_, hten_,
+ {{0x27edd2bb,0x63a45f89,0x6b9adf8a,0x6dac07c0}}, // kten_, _kuin, _sitg, _ağac,
+ {{0x69da00d2,0x27eddf8b,0x5ed980ab,0x29001fea}}, // šted, jten_, ব্যে, meia_,
+ {{0x63a45f8c,0x60c29a26,0x27e023be,0x291d81bc}}, // _muin, _seom, muin_, _anwa_,
+ {{0x44208012,0x2d968048,0x27edd919,0x61ee081a}}, // ţi_, _арас, eten_, ntbl,
+ {{0x27edd468,0x69cac4e7,0x69d8bf11,0x29004e2a}}, // ften_, rsfe, rrve, neia_,
+ {{0x60c28efd,0x53a69878,0xc9530bea,0x27edb0c8}}, // _veom, _разб, ימה_, gten_,
+ {{0x6b9aa40d,0x7791826a,0x7792803d,0x69ca874d}}, // _uitg, مینا, جیتا, psfe,
+ {{0xdb0083d3,0x27e05f8d,0x27eddf8e,0xe514103e}}, // _numé, huin_, aten_, _तोहि_,
+ {{0x61359984,0xa9278110,0x63a4212b,0x27e05f8f}}, // _sálv, gužė, _buin, kuin_,
+ {{0x0ee23792,0x27ed8613,0x4426c5a6,0x63a445c6}}, // _पांड, cten_, nxo_, _cuin,
+ {{0x44269a7d,0x63a4061f,0x290b282b,0x6d4609ca}}, // ixo_, _duin, _saca_, ahka,
+ {{0x4f9b8039,0x671f800f,0x6135ca92,0x2ba50105}}, // _קבוצ, बारक_, _válv, _गंवा,
+ {{0x63a40cb5,0xe807816f,0x6285007a,0x68e45f52}}, // _fuin, वटचा_, _vzho, ggid,
+ {{0x40340364,0x27e0009f,0x21290ad4,0xdb00b59e}}, // [59d0] аетс, guin_, rjah_, _eumé,
+ {{0x7d0d2c67,0x44395f90,0xa6cc00ab,0x02b73852}}, // _haas, mys_, র্ঘট, _अभिभ,
+ {{0x7d0d5a42,0x44392bee,0xa3df1a3b,0x27ed811e}}, // _kaas, lys_, दूर_, zten_,
+ {{0x27ed9d49,0x656281a1,0x27e002c4,0x2d5202d4}}, // yten_, gnoh, buin_, všek_,
+ {{0x7d0d0eea,0x44393ece,0x27ed8884,0x26c35f91}}, // _maas, nys_, xten_, _tejo_,
+ {{0x7d0d43f1,0x7ae381c0,0x7bcb88dc,0x799d574d}}, // _laas, ugnt, rsgu, _hisw,
+ {{0x2b8981ac,0x799d0010,0x61385f92,0x7bcb839c}}, // júce_, _kisw, _síli, ssgu,
+ {{0x7d0d4348,0x09f680f7,0x3ce9022c,0x78ad4664}}, // _naas, افية_, _ncav_, ncav,
+ {{0x290dd53f,0x6d59be21,0xb8cb85fc,0x27ed9e0f}}, // rdea_, miwa, _खल_, uten_,
+ {{0x6d59b162,0x44395f93,0xfb87adc7,0x257c85b9}}, // liwa, dys_, _рыбн, víli_,
+ {{0x7d0d18dc,0xa01b0009,0x6135816a,0x3f9cdf94}}, // _baas, _hyöd, _bált, _divu_,
+ {{0x6d599629,0x63a40f56,0x10158591,0x6d462e35}}, // niwa, _puin, _مبتد, uhka,
+ {{0x63a41621,0x290007d0,0xd456004a,0x44392024}}, // _quin, veia_, утнь, gys_,
+ {{0x6d4600f1,0x6d59809c,0x03a30198,0x61ee1ff5}}, // shka, hiwa, сиро, ttbl,
+ {{0x6d59abae,0x6135ac6c,0xc5d580e8,0x32430e9f}}, // kiwa, _fált, _сіль, берг,
+ {{0x27e00ff9,0x63a45f95,0x2bdc0105,0x7d0d0dda}}, // tuin_, _tuin, _बिपा, _gaas,
+ {{0xe8168b9f,0x6d5602a0,0x6d440010,0x29005f96}}, // [59e0] तिया_, _imya, _ilia, reia_,
+ {{0x06d580ab,0x613c8061,0x60c9810c,0x27e034da}}, // ত্রি, _célj, haem, ruin_,
+ {{0x2bc68054,0x7d0d461f,0x290003cd,0x6d445eb7}}, // रीना, _yaas, peia_, _klia,
+ {{0x6d59c79e,0xd7d4800f,0x7d0d0079,0x6135807b}}, // giwa, _दिलच, _xaas, _máls,
+ {{0x44269704,0x69ce3958,0x27e04f42,0x9f400198}}, // uxo_, lsbe, quin_, ttiä_,
+ {{0x44268509,0x6f01911b,0x46a31f96,0xfbdc5f27}}, // rxo_, jelc, _гарв, _बिनम,
+ {{0x6d59c50b,0x3f9cdf97,0x6d445e16,0x26790162}}, // biwa, _rivu_, _olia, _астэ_,
+ {{0x3d0e0d38,0x69ce5f98,0x3f9c8009,0x4fd4a2ea}}, // ियों_, isbe, _sivu_, ажит,
+ {{0x3b868c91,0xddcfa7b1,0x44395b82,0x273b801c}}, // _благ, _češn, xys_, _kênh_,
+ {{0x6d4457d2,0x17f78013,0x69ce01eb,0xb1148870}}, // _alia, ارية_, ksbe, _gụny,
+ {{0x6d44154c,0xb52100ba,0x44395f99,0xab938013}}, // _blia, यालय_, wys_, _اللغ,
+ {{0x443913cd,0x6d440c5e,0x628880eb,0x3ce68619}}, // tys_, _clia, _izdo, ngov_,
+ {{0x61358065,0x799d00dd,0x7d0d10c3,0x69ce1151}}, // _vált, _risw, _vaas, esbe,
+ {{0x44395f9a,0x799d48e0,0x7d0d30f9,0x6d599f12}}, // rys_, _sisw, _waas, ziwa,
+ {{0x7d0d5f34,0x443f0029,0x613c8065,0x44395f9b}}, // _taas, šu_, _nélk, sys_,
+ {{0x6d445ded,0x78ad00d2,0x6288816b,0xeab00019}}, // _glia, ucav, _mzdo, قعہ_,
+ {{0xd6db19d9,0x2bdc150e,0x7d028869,0x78ad5f9c}}, // [59f0] ете_, _बिया, deos, rcav,
+ {{0x78ad5b02,0x248900b9,0x6d599495,0x628889a4}}, // scav, _izam_, wiwa, _ozdo,
+ {{0x78a28699,0x6d59809c,0x539a0039,0xa01b0198}}, // _ogov, tiwa, _שיתו, _syöd,
+ {{0xf8b10bca,0x613c801b,0x6138318a,0x637f82df}}, // _ذکر_, _délk, _sílv, mêni,
+ {{0x6d59df9d,0x9f4900f7,0xd0069c12,0x7bcf2cfd}}, // riwa, ltaí_, _بل_, nscu,
+ {{0x39490073,0x6d59df9e,0x78a2c4e8,0x2fcd8699}}, // lhas_, siwa, _agov, vseg_,
+ {{0xdea40077,0x2ba192c7,0x2d87837f,0x6925ae50}}, // _ایمی, _ओंका, ïnes_, имка,
+ {{0x3949156b,0x7c3986a9,0xb4f98158,0xa3d6064a}}, // nhas_, pywr, _שפּי, _हिल_,
+ {{0x9f490013,0x248900b9,0x657b8726,0xaae78a47}}, // htaí_, _nzam_, _ikuh, _مساو,
+ {{0xff51803f,0x6d445f9f,0x2d8d808e,0x777701b9}}, // تخب_, _slia, _hhee_, noxx,
+ {{0x6d445fa0,0x32679155,0x4d661ad8,0x23ab813c}}, // _plia, _став, иков, _høj_,
+ {{0xed5a2c38,0x20192f5d,0x2fcd826c,0x69ce03a6}}, // том_, _orsi_, pseg_, vsbe,
+ {{0x5ed000ab,0x6569b52c,0x212d86ec,0x39490387}}, // স্টে, _mjeh, mjeh_, dhas_,
+ {{0x290fdfa1,0x60c6449c,0x9af380fc,0x3ce6cfb3}}, // _haga_, _sekm, _ƙeƙe_, zgov_,
+ {{0x61e38059,0x290f82a3,0x7e7980be,0x637f8187}}, // nunl, _kaga_, _באַז, gêni,
+ {{0x6d5d02be,0x98a31194,0x69ce3e9d,0x39490cb5}}, // érat, жите, rsbe, ghas_,
+ {{0xb4e20076,0x6f0e0069,0x290f8537,0x69ce189c}}, // [5a00] दली_, _pabc, _maga_, ssbe,
+ {{0x290f92cf,0xdce981ac,0x61e3dfa2,0x69ce1c11}}, // _laga_, _cieľ, kunl, psbe,
+ {{0x26c7dfa3,0x60c60353,0x38698125,0x39490083}}, // _meno_, _tekm, _þarf_, bhas_,
+ {{0x39495fa4,0x2d8d8135,0x290fc69d,0x2d9f8362}}, // chas_, _chee_, _naga_, _ciue_,
+ {{0x7d02c3e3,0xb6a600e8,0xe3af9459,0x7c220118}}, // reos, _вигл, ورو_, _áord,
+ {{0x26c7dfa5,0x386080ce,0x68f60114,0xc60c8264}}, // _neno_, _žiro_, lfyd, ষমতা_,
+ {{0x290f988c,0xf1a38791,0x613ca738,0xf8aa103e}}, // _baga_, орын, _héli, _कलाय,
+ {{0x2d8d81bc,0x213f8590,0xb87b026b,0x39405fa6}}, // _ghee_, gkuh_, _abíy, nkis_,
+ {{0x8ad6990c,0x290f8a73,0x1ae68364,0x8c438a68}}, // _نتائ, _daga_, _возм, _дете,
+ {{0x61fe035f,0x6618826f,0x26c78353,0xaae0824c}}, // _oppl, _prvk, _ceno_, _नाटक,
+ {{0x290fdfa7,0x628881a9,0x2d68011f,0x33335fa8}}, // _faga_, _uzdo, rđen_, _roxx_,
+ {{0x78a2803a,0x26c7838a,0x7a1c8493,0x25a05fa9}}, // _ugov, _eeno_, _vătă, _ciil_,
+ {{0x61fe39ac,0xcb220b9f,0x2572016b,0x613c8118}}, // _appl, मांड_, nály_, _néli,
+ {{0x2904dfaa,0x394681c0,0x290fdfab,0x0eba876a}}, // lema_, _hlos_, _zaga_, _руды_,
+ {{0x26cc90dd,0x39491e66,0x98c714d6,0x7bcf5fac}}, // mado_, thas_, исал, sscu,
+ {{0x26cc8510,0x3ce101ab,0xa37b0187,0x39405fad}}, // lado_, _काटे_, drõe, gkis_,
+ {{0x6d5d0c0f,0xf0938158,0x613cb60c,0x9f4901a8}}, // [5a10] misa, ַנע_, _céli, staí_,
+ {{0x6d5d5fae,0x26cc8510,0x613c82be,0x20190042}}, // lisa, nado_, _déli, _vrsi_,
+ {{0x3d1c035a,0x2904d523,0x2b8981ac,0x6569826c}}, // याचे_, kema_, júca_, _pjeh,
+ {{0x26cca6f3,0x613cb884,0x2904dfaf,0x39405fb0}}, // hado_, _féli, jema_, ckis_,
+ {{0x26ccba19,0x290f90fe,0x29048c53,0x98a9011f}}, // kado_, _raga_, dema_, njač_,
+ {{0x60cd28fb,0xdb040013,0x26ccdfb1,0xfbb80039}}, // laam, _stiú, jado_, ספות_,
+ {{0x26cc90dd,0x3ce8035a,0xdb0405b4,0x45d40705}}, // dado_, _झाले_, _quié, _морс,
+ {{0x61e38214,0x6569829b,0x6b4b0019,0x23ab806a}}, // runl, _ujeh, _függ, _tøj_,
+ {{0x290f87d0,0x2019ca34,0x6d5d5fb2,0x25a00101}}, // _vaga_, ísi_, disa, _riil_,
+ {{0x26cca5a7,0xbb8580f7,0x60cd5fb3,0x98a90b67}}, // gado_, _الطي, haam, djač_,
+ {{0x212d920e,0x63a98010,0x6d5d30f9,0x290fd20b}}, // pjeh_, _huen, fisa, _taga_,
+ {{0x6d5d49af,0x63a9822e,0x60cd04a2,0x200904b9}}, // gisa, _kuen, jaam, kwai_,
+ {{0x814c893f,0xe72e8364,0x60cd02a3,0x517480f7}}, // _בעאַ, _ее_, daam, _بالأ,
+ {{0x98a002a5,0x7fb480d5,0x26cc95c1,0x25a00079}}, // ljić_, _بلوچ, cado_, _wiil_,
+ {{0x6d5d501d,0x25a00bb1,0x5ede0264,0x3940327d}}, // bisa, _tiil_, ন্ডে, tkis_,
+ {{0x6d5d438a,0x6281dfb4,0x98a00d11,0x60cd0079}}, // cisa, _mylo, njić_, gaam,
+ {{0x752401dc,0x7d060009,0x39405fb5,0x6735011f}}, // [5a20] _iniz, meks, rkis_, _bozj,
+ {{0x2bab800f,0xa3ab8b9f,0x324622ea,0xdb0e0009}}, // _चढ़ा, _गढ़_, _геог, äkär,
+ {{0x61fe57c1,0xa75a893f,0x27f22937,0xb8ee00ab}}, // _uppl, נדער, styn_, _রা_,
+ {{0x63a99783,0x26cc8510,0x7d065fb6,0xeabf80ff}}, // _buen, zado_, neks, _đùa_,
+ {{0x63a99220,0x26ccba19,0xdb040046,0x6b46807b}}, // _cuen, yado_, _stiù, _aðga,
+ {{0xf7678288,0x63a9811e,0x26cc8ba3,0x27e012ed}}, // _را_, _duen, xado_, mrin_,
+ {{0x26cc90dd,0x6d5d0dc4,0x2904aeae,0x68e994fb}}, // vado_, yisa, tema_, fged,
+ {{0x2be084e5,0x3d182701,0x63a986a5,0x7d0604e6}}, // _निपा, _बोले_, _fuen, jeks,
+ {{0x26cc8510,0x290484c4,0x27e0077f,0xe8e0001c}}, // tado_, rema_, nrin_, _ngọt_,
+ {{0x290480f6,0x60cd407f,0x3ae88077,0x26ca056f}}, // sema_, zaam, _خبری_, _jebo_,
+ {{0x26cc8228,0x6d5d5fb7,0x877b007c,0xa3e48576}}, // rado_, tisa, ראיי, पूर_,
+ {{0x26cca82b,0x27e05fb8,0xdb0284c3,0x26ca01ac}}, // sado_, krin_, mpoñ, _lebo_,
+ {{0x40968307,0xb8968013,0x60cd40fc,0x703a8277}}, // _الشر, _الشع, vaam, _حساس_,
+ {{0x26ca03bb,0x6d5d41af,0x27e05fb9,0x60cd2693}}, // _nebo_, sisa, drin_, waam,
+ {{0x6d5d5fba,0x69da003a,0x237e826c,0x6fa2199e}}, // pisa, šten, _mktj_, _कंजू,
+ {{0x8e838013,0x26d81e72,0x7d060214,0xbcfb0036}}, // _عليه, _adro_, ceks, fiée,
+ {{0x60cd5fbb,0x58870198,0xddd415d8,0xe894062c}}, // [5a30] raam, сыва, _čašn, зать,
+ {{0x60cd3755,0x2b5a0118,0x6b4b0799,0xed5980c3}}, // saam, _gmpc_, _büge, _smž_,
+ {{0x63a98e1b,0x26ca0333,0x6d4b8114,0x6d680118}}, // _suen, _debo_, thga, _ásaí,
+ {{0x63a1a08f,0x63a99783,0x27e05fbc,0xe4cb803d}}, // _siln, _puen, brin_, ابان_,
+ {{0x63a18612,0x25ad00d2,0x63a98548,0xe3b3803d}}, // _piln, _čelu_, _quen, سرش_,
+ {{0x41b58c3e,0x3da72118,0x7ae785e9,0xeb9980af}}, // осит, _граб, сюдж, шил_,
+ {{0x63a18a8e,0x68e98cfa,0xdb008511,0x26c10267}}, // _viln, tged, _atmó, mbho_,
+ {{0x13a7803d,0x63a9cace,0x628183c1,0x5ede00ab}}, // _هنری_, _tuen, _vylo, ন্দে,
+ {{0xe81c101b,0xfbe086bf,0x68e98c77,0xbcfb026f}}, // निया_, _नियम, rged, mnéh,
+ {{0xf77187bd,0x7524007d,0x68e9dfbd,0xbcfb026f}}, // باب_, _sniz, sged, lnéh,
+ {{0x2bdc12ee,0x27e04e07,0xdced00e1,0xa2ba03dd}}, // _बिहा, zrin_, _diaľ, ्रस्,
+ {{0x2bc6946d,0xc3290051,0xbcfb026f,0x2fdf8668}}, // रीवा, _בו_, nnéh, prug_,
+ {{0x613c8207,0x395fdfbe,0x656d09c4,0x66e00032}}, // _célu, mius_, _djah, _jákà,
+ {{0x395fc1af,0x7d065fbf,0x394d80a9,0x957d009a}}, // lius_, seks, lhes_, stąp,
+ {{0xfaa32c9f,0xe29a1f25,0x69dc904a,0x7d065fc0}}, // даро, рав_, _året, peks,
+ {{0x2bcf8327,0x27e03ae0,0x26ca5fc1,0xa2ba016f}}, // _सौभा, trin_, _sebo_, ्रह्,
+ {{0xdb01820f,0xbcfb0a21,0x60cb8019,0x79840c02}}, // [5a40] _cilë, dnéh, _megm, rliw,
+ {{0x27e05fc2,0x6d4992ec,0x25e0a1e3,0x2b5a01e0}}, // rrin_, _alea, _किमी_, _umpc_,
+ {{0x98a618ba,0x395f8110,0xb87b0174,0x248d83ed}}, // _мине, kius_, _scít, _azem_,
+ {{0x6d49b6d3,0x66e0026b,0x4c86c566,0x92a6866f}}, // _clea, _bákà, олев, zyłą,
+ {{0xa01b2732,0x394db543,0x6d498c41,0x26ca00dd}}, // _szöv, dhes_, _dlea, _tebo_,
+ {{0x6d42811b,0x248d826c,0x8c1b0039,0xb33c8609}}, // skoa, _dzem_, יוני, _biħa,
+ {{0x2d800640,0xbcfb1c18,0x6d49c4b9,0x8fa30d69}}, // _okie_, bnéh, _flea, _царе,
+ {{0x6d498c49,0x394ddfc3,0x684080f7,0xa01b0009}}, // _glea, ghes_, _nádú, _myön,
+ {{0xf993078d,0xdb0287f4,0x60cb82a3,0xff0684fa}}, // ורה_, spoñ, _degm, оянн,
+ {{0x656d06cb,0x9abc8609,0x35af0327,0x2d8682f9}}, // _sjah, _inċo, _जूड़, lloe_,
+ {{0x69da001b,0x2bc68701,0xceb301c6,0x3160019d}}, // átel, रीरा, וית_, kiiz_,
+ {{0xd90e80d5,0x394d9706,0x395fdfc4,0x6eba970c}}, // _شیخ_, ches_, cius_, _श्रु,
+ {{0xd7ef80f7,0x69da42f2,0x98a901ac,0xa6d500ab}}, // _لكن_, štel, tiaľ_, স্টট,
+ {{0xada580e1,0x2bdc001b,0x23b08866,0xbcfb4b7e}}, // _skúš, _बिरा, _màj_, inéi,
+ {{0xdb018f33,0x613c8e06,0x656d1cfe,0x68fd0362}}, // _dilè, _félt, _tjah, _pbsd,
+ {{0xa3e80072,0x98a901d6,0xed580a2e,0x656d5a17}}, // मूह_, siaľ_, _мој_, _ujah,
+ {{0xbcfb0a21,0x657d39d6,0x2ca901b4,0x27140264}}, // [5a50] vnéh, mosh, _igad_, ঠানো_,
+ {{0x657d5fc5,0x3ea70aa2,0xa29505e9,0x6d498834}}, // losh, ønt_, _напі, _slea,
+ {{0x6d49dfc6,0xbcfb026f,0xa01b0019,0x63ad453f}}, // _plea, tnéh, _gyön, _huan,
+ {{0x63ad00f6,0x395f8722,0xaae709a7,0x02b3801b}}, // _kuan, xius_, _اسکو, ँड्न,
+ {{0x69d5100b,0xbcfb026f,0x395f8084,0x58840048}}, // lsze, rnéh, vius_, _жыра,
+ {{0x27e909ca,0xbcfb016b,0x248480ee,0x2139008e}}, // muan_, snéh, _dymm_, _hosh_,
+ {{0x395f9b09,0x27e9059f,0x2bd7835a,0x4429802e}}, // tius_, luan_, _ठिका, ţa_,
+ {{0x2bdc000f,0x6d5b8135,0x26000035,0xe0df0176}}, // _बिला, _umua, _रहती_, _amòz_,
+ {{0x395fcf05,0x27e917a0,0x68ed0ece,0x657d39d6}}, // rius_, nuan_, igad, dosh,
+ {{0x6738dfc7,0x395f8110,0x2ca95fc8,0x69d50019}}, // _covj, sius_, _agad_, ksze,
+ {{0x27e90763,0x656b86e7,0x62852a30,0xdce98d26}}, // huan_, ongh, _nyho, _lječ,
+ {{0xa3d70063,0x27e95fc9,0x656bdfca,0x69d5100b}}, // ाओं_, kuan_, nngh, dsze,
+ {{0x63ad1220,0x9fd600c8,0x27e9010b,0x31b18019}}, // _cuan, _সঙ্গ, juan_, _ház_,
+ {{0x27e92dd3,0x63ad5fcb,0xa026807b,0x2d990fb0}}, // duan_, _duan, stöð, amse_,
+ {{0x6b5004b8,0x62850355,0x3cff826f,0x657d0234}}, // _lägg, _cyho, _obuv_, bosh,
+ {{0x25e0835a,0x68ed5a3e,0x55da8158,0x38608503}}, // _किती_, ggad, _פֿונ, _žiri_,
+ {{0x63ad2f30,0xb4d80bbc,0x200ddfcc,0xb9042730}}, // [5a60] _guan, ाणी_, nwei_, _नय_,
+ {{0x44205fcd,0xdce982fd,0x7d1602a3,0xa01b0009}}, // _iri_, _dječ, _hays, _työn,
+ {{0x7d1658f6,0x63bbdfce,0x62850114,0x76419106}}, // _kays, _atun, _gyho, nyly,
+ {{0x8fa6013a,0x27e909ca,0x6d465be7,0x92e880ab}}, // _наме, buan_, nkka, _বসে_,
+ {{0x293700be,0x6d460009,0x9f44c79c,0xb87b046d}}, // _זאכן_, ikka, sumé_, _adíf,
+ {{0x60c401e2,0x656b8013,0x7bc9831d,0x63bb808e}}, // lbim, angh, _dweu, _dtun,
+ {{0xf107823c,0xbcfb0013,0x657d341a,0x6f08dfcf}}, // वजूद_, snéi, yosh, vedc,
+ {{0x44205fd0,0xb6a31317,0x4ddb01c6,0x63bbb7f4}}, // _ori_, _числ, _לחלו, _ftun,
+ {{0x6738920e,0x4420082e,0xb904c765,0x6f088144}}, // _povj, _nri_, _भए_, tedc,
+ {{0x69d531d6,0x63ad059c,0x44270104,0xa8570051}}, // ysze, _ruan, _ăn_, פייה_,
+ {{0x44205fd1,0x657d5fd2,0x63ad5fd3,0x27e9316e}}, // _ari_, tosh, _suan, zuan_,
+ {{0x27e95fd4,0x4420337a,0xd843026f,0x63ad0214}}, // yuan_, _bri_, ničí_, _puan,
+ {{0x63ad20ef,0x657d5fd5,0x4420054e,0x9f400388}}, // _quan, rosh, _cri_, ntió_,
+ {{0xdce9920e,0x69d51529,0x44205fd6,0x6d460009}}, // _rječ, tsze, _dri_, akka,
+ {{0x44202444,0xb909801c,0x2bd91344,0xdce98654}}, // _eri_, _nghề_, _भटका, _sječ,
+ {{0x27e95fd7,0x6285003e,0x19580a14,0x69d55fd8}}, // tuan_, _vyho, пасы_, rsze,
+ {{0x6d4d02a3,0x69d50019,0x44205fd9,0xf4850180}}, // [5a70] _ilaa, ssze, _gri_, _تاثی,
+ {{0x27e95fda,0x2bcf81fe,0x68ed5fdb,0xdce982ce}}, // ruan_, सीफा, sgad, _vječ,
+ {{0xfbaf00c8,0x63bb8e20,0x6d4d26a1,0x27e93158}}, // _কবিত, _stun, _klaa, suan_,
+ {{0x27e9255f,0xb4e7aa85,0x65628115,0x798281bc}}, // puan_, पले_, gioh, _nkow,
+ {{0xe1f18077,0xfc46801c,0x26dc81a1,0x1be9804a}}, // چسب_, _đích_, _odvo_, ідки_,
+ {{0xaabf053f,0x6b50016d,0xdb0983bf,0xb87b026b}}, // ्रिक, _vägg, _önün, _adíg,
+ {{0xa3b409c8,0x60cf4160,0xe2969577,0x6b4f8366}}, // _जून_, _kecm, маш_, _nøge,
+ {{0xa0a597a1,0x3a3a8106,0x34958196,0x673c8106}}, // майд, äppa_, _жабр, örjn,
+ {{0x24c500ab,0x63bb8435,0xaabf1a46,0x7d163dd5}}, // ্লাহ, _utun, ्राक, _rays,
+ {{0x6d4d5fdc,0xf8aa073c,0x6b4f813c,0xe8aa03db}}, // _alaa, _कल्प, _bøge, _कल्च,
+ {{0x7529825b,0x6d4d000b,0xfce601a4,0x6ad380ab}}, // _knez, _blaa, моно, _সাপো,
+ {{0x44201fc2,0xb4d8035a,0x9f400511,0x6d4d000b}}, // _pri_, ाणे_, rrié_, _claa,
+ {{0x6b5000f2,0x6d460009,0x753b8118,0x6b4b02f1}}, // _läge, rkka, _mouz, _süga,
+ {{0x889a8039,0x753b83a8,0x7989dc81,0x06e300ab}}, // _ובני, _louz, llew, ন্তি,
+ {{0x03a301a0,0x6d4d019d,0x26c5d826,0xbcfb0388}}, // тиро, _flaa, mblo_, mién,
+ {{0x316d82be,0xbcfb0144,0x6d4d0bfd,0x61e10061}}, // nnez_, lién, _glaa, álla,
+ {{0x44205fdd,0xbf8a801c,0x60c45fde,0x7d0bb026}}, // [5a80] _uri_, _đoạn_, rbim, degs,
+ {{0x7529b08f,0xbcfb0020,0x6d4d026c,0x60c400b9}}, // _anez, nién, _zlaa, sbim,
+ {{0x753ba4be,0x2be08105,0x6b500106,0xe29a2c24}}, // _bouz, _निहा, _vägd, _фан_,
+ {{0x25e08063,0xe8df801c,0x2fc01c11,0x448a93f1}}, // _किसी_, _trộm_, ppig_, обен_,
+ {{0x753bace0,0xa2a09513,0x32058122,0x3d00016f}}, // _douz, गेन्, _aply_, ष्ये_,
+ {{0x9f401099,0x629c816b,0x316d811b,0x7989804f}}, // rtió_, _úrok, enez_, elew,
+ {{0x9f40009f,0xbcfb0333,0xdb00c255,0xdee32ba7}}, // stió_, dién, _rumä, _јоси,
+ {{0xd1308013,0x316d80e7,0x673c0079,0xa8149294}}, // امة_, gnez_, _horj, едиш,
+ {{0x6b4f8022,0x673c1bda,0x36d48a7c,0x61ea8162}}, // _søge, _korj, нопр, sufl,
+ {{0x6d4d10f4,0xeeeb001c,0x99898668,0x4b269a3c}}, // _slaa, _đừng_, ćaš_, _تعرف,
+ {{0x6d4d2409,0x673c0353,0x7989a418,0x6aba810c}}, // _plaa, _morj, blew, _iftf,
+ {{0xc86684fa,0x69d8dfdf,0xe3c701df,0xc5f880eb}}, // _отли, msve, íños_, rtē_,
+ {{0x63a8b24d,0x69d8a1b4,0x6d4d038e,0x7bc2dfe0}}, // _kidn, lsve, _vlaa, npou,
+ {{0xbcfb06a5,0x29020326,0x673c0198,0x69d8b610}}, // cién, _abka_, _norj, osve,
+ {{0x63a88079,0xe81684c5,0x442d0087,0xd13b2f4b}}, // _midn, तिका_, ţe_, оха_,
+ {{0x69d8deb0,0x6d4d4da1,0x5ec800ab,0xd90f004e}}, // isve, _ulaa, _লাগে, جیح_,
+ {{0x6b5004b8,0xb8e70076,0x2be085fc,0x753bdfe1}}, // [5a90] _säge, _उभ_, _निषा, _rouz,
+ {{0x7529bdbf,0x753bdfe2,0xa91d8796,0x3d00016f}}, // _snez, _souz, može, ष्ठे_,
+ {{0xa91da842,0x753b800d,0x7d0b8019,0x25a95fe3}}, // lože, _pouz, tegs, _kial_,
+ {{0x2be085e8,0x23e08076,0x6b502e6d,0x656f1581}}, // _निशा, _निशद, _väge, nnch,
+ {{0x7bc4802e,0x7d0b8412,0x656f5fe4,0x29190162}}, // ţiun, regs, inch, _iasa_,
+ {{0xb3e085b3,0xfbe084e5,0x98af8063,0x2919022e}}, // _निरख, _निरम, _mogą_, _hasa_,
+ {{0x291903c3,0x2369803a,0x2006802e,0x4b380039}}, // _kasa_, đaj_, _apoi_, ורגל_,
+ {{0x3949189e,0x5694153d,0x291914e5,0x2d788904}}, // nkas_, _раст, _jasa_, včeg_,
+ {{0x29195fe5,0x2d8b15ab,0x2489022c,0xbcfb01ca}}, // _masa_, dlce_, _nyam_, tién,
+ {{0x29195fe6,0x656f0073,0xa91d8289,0x27e2816b}}, // _lasa_, ench, dože, ákna_,
+ {{0x2489059c,0x2b898038,0x8d662296,0x62889ad5}}, // _ayam_, júci_, евне, _gydo,
+ {{0x92c100c8,0x63a8811f,0x2d8400e7,0xdb018c1d}}, // ুলো_, _zidn, ôme_, _milí,
+ {{0xf992812a,0x66f0223a,0x394911fe,0x2ca40106}}, // _ערד_, _चालक_, dkas_, ämd_,
+ {{0x212b0081,0x883a8039,0xa91da663,0x29194bf9}}, // _anch_, _מתנו, sožd, _aasa_,
+ {{0x291903f8,0xdb0f01a8,0xbcfb12ab,0x27ed8cdb}}, // _basa_, ádái, gnés, luen_,
+ {{0x61358019,0xa91d816b,0x386d026c,0xa3c186a7}}, // _pály, bože, _žera_, ूठा_,
+ {{0xd0070fbe,0xc72601e5,0x25bf005c,0x27eddfe7}}, // [5aa0] нете_, ндай, _čula_, nuen_,
+ {{0xe1fa025d,0x80ac05b3,0x65665fe8,0x2d8238b3}}, // _его_, _झलके, likh, loke_,
+ {{0x291905a3,0x64465fe9,0x63a883e2,0x394935b5}}, // _fasa_, lyki, _ridn, bkas_,
+ {{0x39495fea,0x3a25013c,0x29195feb,0x41bb0039}}, // ckas_, ælpe_, _gasa_, _מצטע,
+ {{0x60d60637,0x66150019,0x7d0431ca,0x9cd701c6}}, // maym, _eszk, _mbis, תונה_,
+ {{0x69d8d27b,0x60d62f3b,0x27ed8cdb,0xdb01dfec}}, // tsve, laym, duen_, _filí,
+ {{0x7d043929,0xfce6a410,0x63a8dfed,0x291931cc}}, // _obis, _пово, _vidn, _yasa_,
+ {{0x7afb0025,0x63a8a180,0x69d88f2c,0xeeeb001c}}, // đuti, _widn, rsve, _đứng_,
+ {{0x63a880f2,0x27edae88,0x25a959b2,0x2d823353}}, // _tidn, guen_, _rial_, doke_,
+ {{0xd6da88cc,0x4424bdd1,0x7d040c03,0xd130803d}}, // чти_, _irm_, _abis, امک_,
+ {{0x24890359,0xa3d31905,0x7c2457eb,0xfd4d8135}}, // _syam_, हीन_, _arir, _nghọ,
+ {{0xb4eb090a,0xeabf8129,0xa91d82d4,0x442488dc}}, // मले_, _đùi_, tože, _krm_,
+ {{0x291904aa,0xfd4d8135,0xf74681a4,0x9abc8197}}, // _rasa_, _aghọ, _педо, _anċi,
+ {{0x291900f6,0x7d0402ec,0x61e10061,0x7986022b}}, // _sasa_, _ebis, állo, _akkw,
+ {{0xb9e4035f,0x2d825fee,0xa01b0009,0x7c241292}}, // літи, boke_, _työk, _erir,
+ {{0xf99300f7,0x4424808c,0xa91d82a5,0xfc46801c}}, // ابس_, _orm_, pože, _đính_,
+ {{0xd3a73d65,0xb8d92701,0x39490288,0x7a138201}}, // [5ab0] _проп, _चल_, rkas_, _vətə,
+ {{0x39495fef,0x291900a4,0x7d1d00b2,0xb87b002a}}, // skas_, _wasa_, ldss, _adíc,
+ {{0x29195d18,0x26d10988,0x3f810168,0x27ed811b}}, // _tasa_, _vezo_, rohu_, zuen_,
+ {{0x7d1d23ea,0x59b61834,0x442481a1,0xbc68004e}}, // ndss, _आंतर, _brm_, _آمین_,
+ {{0x7d1d1377,0x752d0c53,0x8d749459,0x53e09a46}}, // idss, _mnaz, دالا, _निःश,
+ {{0x798d4da1,0x8c4291d5,0x6566005d,0x04428073}}, // llaw, реше, zikh, решн,
+ {{0x7bd99770,0x69da0353,0x56940a42,0x6b500589}}, // sswu, štev, _барт, _jäga,
+ {{0x27ed80ad,0x7d1d2a52,0x8afe80fc,0x752d03f7}}, // tuen_, jdss, riƙa, _nnaz,
+ {{0x5b159a19,0x2d821315,0x27e95ff0,0xa91d807a}}, // _имат, voke_, nran_, tožb,
+ {{0x27e941e3,0x7d1b8788,0x27edb665,0x752d2f0e}}, // iran_, _haus, ruen_, _anaz,
+ {{0x27e9198d,0x6b83840c,0x7d1bdff1,0x65663c5a}}, // hran_, mong, _kaus, tikh,
+ {{0x25e91344,0x64460d12,0x7d1bdff2,0xaa6499b8}}, // _चटनी_, tyki, _jaus, _стук,
+ {{0x2d825ff3,0x27eddff4,0x7d1bdff5,0x798d01e0}}, // roke_, quen_, _maus, dlaw,
+ {{0x2d825ff6,0x7d1b8b74,0x6b838c8b,0x27e95ff7}}, // soke_, _laus, nong, dran_,
+ {{0x27e920e1,0x798d0114,0xdb0888f9,0x2d820db1}}, // eran_, flaw, _didè, poke_,
+ {{0x7d041f3a,0x6b83dff8,0x0c26341b,0x27e95ff9}}, // _ubis, hong, _амин, fran_,
+ {{0xa0671a19,0x186747a8,0x68431273,0x6b83b490}}, // [5ac0] вата_, вати_, анта, kong,
+ {{0x6b83a499,0x998d003a,0x7986008e,0x03a61630}}, // jong, ćeš_, _wkkw, _шипо,
+ {{0x7d1bbe50,0x6b83d038,0xf2d30bea,0x27e95ffa}}, // _baus, dong, _דעת_, aran_,
+ {{0xfce69baa,0x44248abf,0x69de824a,0xdb0083ba}}, // тодо, _prm_, špek, _humø,
+ {{0x7d1bdd18,0x3f8c808b,0x69dc1dba,0x6d988144}}, // _daus, yldu_, lsre, víad,
+ {{0x6b83b31a,0xf8bf010c,0xf41301c6,0x232785d3}}, // gong, _anéh_, יפד_, кочи_,
+ {{0xd24f880b,0x69dc0ed0,0x672e0106,0xbebc80eb}}, // ينه_, nsre, _inbj, ldīg,
+ {{0xdce2803a,0x7d1bae2e,0x60c98b4e,0x6f1c5ffb}}, // dnoć, _gaus, gbem, _harc,
+ {{0x6d408ad0,0x6b838d48,0xdef884b7,0xdca390ef}}, // _ioma, bong, liċi_, _вати,
+ {{0x7d1b8025,0xa2c186bf,0x65642123,0x6b839267}}, // _zaus, रुद्, _amih, cong,
+ {{0x13d780ab,0x69dc0289,0xa4d50221,0xd00b00ab}}, // _সময়, jsre, лоні, রিয়_,
+ {{0xb4bf01b6,0x27e905b0,0x670203eb,0x6f1c107c}}, // ेरे_, yran_, र्तक_, _larc,
+ {{0x6d40dffc,0x201880eb,0xa3b88105,0x18369a37}}, // _moma, āris_, _घूम_, _جراح,
+ {{0x52bf000c,0x36d48d40,0x6d40dffd,0x2bb50035}}, // ्रेस, _сокр, _loma, ंदबा,
+ {{0x25bf0289,0x96f7830f,0x27e93a19,0x6d408df6}}, // _čulo_, _شعور_, wran_, _ooma,
+ {{0x6d40dffe,0x44f55fff,0x6b5004fd,0x6b839a08}}, // _noma, _спис, _väga, zong,
+ {{0x6f1c023e,0x9b450bca,0x6b8381d3,0x798d0590}}, // [5ad0] _barc, _منظو, yong, rlaw,
+ {{0x5b150364,0x27e96000,0x7d1b9247,0x7c228140}}, // _смот, rran_, _saus, dvor,
+ {{0x3ea700f2,0x2367842b,0x6b838176,0x6d40d84a}}, // änt_, vinj_, vong, _boma,
+ {{0x6d40e001,0x68e2af61,0x6b83e002,0x69c72948}}, // _coma, _ndod, wong, mpje,
+ {{0x6f1c6003,0xdfc6880b,0x2bdc0beb,0x6b4f8aa2}}, // _farc, _دي_, _बिछा, _døgn,
+ {{0x6f1c0333,0x68e29998,0x7bdd6004,0x6d408609}}, // _garc, _adod, issu, _eoma,
+ {{0xa3d3035a,0x3da7c4e0,0x6d408242,0x6a8597ae}}, // हीत_, _араб, _foma, ылка,
+ {{0x6d40ace1,0x6b83e005,0x6f1c6006,0x3169011b}}, // _goma, song, _zarc, niaz_,
+ {{0xa3e0800d,0x6b83e007,0x2367cb0f,0x6b548091}}, // _थिए_, pong, pinj_, _dàgb,
+ {{0xee398951,0xdb018118,0x6d40a81f,0xdce2876c}}, // ено_, _filá, _zoma, tnoć,
+ {{0xf8d30076,0x8c4287d9,0x9d460d8f,0x7bdf1072}}, // तराय, _öğre, генд, _avqu,
+ {{0x69dc0aa2,0x6d880144,0xbc1a0048,0xdce280fe}}, // vsre, eñar, нігі_, rnoć,
+ {{0x2d8f8307,0xdce2805c,0xafe581e5,0x27e981df}}, // ilge_, snoć, _солл, _ían_,
+ {{0x291da07b,0xf8bf0091,0x69dc189c,0x291f811b}}, // _hawa_, _afé_, tsre, ldua_,
+ {{0x291de008,0x20190211,0x7bdd0037,0xdea1853d}}, // _kawa_, _assi_, assu, _دیدی,
+ {{0xdb089b20,0x291d8854,0x291f847c,0x394d955f}}, // _vidé, _jawa_, ndua_, nkes_,
+ {{0x6d40a49a,0x27ffe009,0x291d803d,0x248d81c5}}, // [5ae0] _roma, ntun_, _mawa_, _nyem_,
+ {{0x6d40e00a,0x6b4f813c,0x291dc500,0xdef881b9}}, // _soma, _søgn, _lawa_, tiċi_,
+ {{0x25adc640,0x6ce68048,0x20190081,0x394d8257}}, // _biel_, гіле, _essi_, kkes_,
+ {{0x7c228b5d,0x6f1c28c6,0x25ad80e7,0x6d4086cb}}, // tvor, _warc, _ciel_, _qoma,
+ {{0x0b8a80c4,0x938a9285,0x3f8584b7,0x25ade00b}}, // нски_, нска_, kolu_, _diel_,
+ {{0x2bb7000f,0x7c228223,0x2912112e,0x6d40e00c}}, // _इंसा, rvor, leya_, _woma,
+ {{0x2be080ba,0x2741800d,0x39422c62,0x3f85e00d}}, // _निका, _méně_, _koks_, dolu_,
+ {{0xdb018065,0x2120478e,0xdca68d9e,0x25ad9c11}}, // _vilá, ndih_, _баби, _giel_,
+ {{0x2000600e,0xcaf69b9a,0x291d8d4c,0x2d680019}}, // ntii_, _مساب, _dawa_, tően_,
+ {{0xade58424,0x25adc60f,0x3f859c66,0x26da600f}}, // _किशन_, _ziel_, golu_, napo_,
+ {{0xd5b78364,0xa37b0073,0x1d3521d2,0x64400009}}, // лся_, nsõe, ання, ämin,
+ {{0xc27b8451,0xa91dcb1f,0x291de010,0x2b4100dd}}, // _ארבי, ložn, _gawa_, _sohc_,
+ {{0xfd1f0098,0x7bdd00e7,0x6fb00105,0x67d48087}}, // rdì_, ussu, _अंगू, _толу,
+ {{0x77f78051,0x6b4f806a,0xae050006,0x909780e8}}, // _עמוד_, _nøgl, _रहलन_, _світ_,
+ {{0x31c4085f,0xf993019f,0x291db174,0xa14381a9}}, // йств, مبر_, _yawa_, _šķir,
+ {{0x2019061b,0x69c700f1,0x386982d4,0x9f520198}}, // _pssi_, rpje, _žari_, htyä_,
+ {{0x69c76011,0xc05b00e8,0xbebc81a9,0x31690cdb}}, // [5af0] spje, хів_, mdīb, riaz_,
+ {{0xbebc8029,0x7a0a8201,0x2bcf9513,0x25ade012}}, // ldīb, _mətb, _सौगा, _siel_,
+ {{0xc3320159,0x69da011f,0x25ade013,0xcf938158}}, // _ווי_, šter, _piel_, צטע_,
+ {{0x69c56014,0x20190282,0x44296015,0x200001b4}}, // _ithe, _tssi_, _kra_, btii_,
+ {{0x7d1f19e7,0x291dc0bb,0x25ad9d77,0x26da022e}}, // _maqs, _rawa_, _viel_, bapo_,
+ {{0x69c500f1,0x291daef4,0x03a5d9dc,0xdcfbb52c}}, // _kthe, _sawa_, шило, _okuć,
+ {{0x25addbd7,0xba77803d,0x44296016,0x2d7880c3}}, // _tiel_, داشت, _lra_, mčen_,
+ {{0x44296017,0x6d98802a,0x27ffe018,0x6009ad6b}}, // _ora_, ríac, ttun_, вним_,
+ {{0x3f858964,0x656bbbb0,0x394d91b4,0xa91d85f3}}, // tolu_, migh, rkes_, božn,
+ {{0x69c5004c,0x753d0063,0x394d99f6,0x7c29e019}}, // _othe, ejsz, skes_, _irer,
+ {{0x442922c6,0x644bb30a,0x27ffd479,0x7bc42c66}}, // _ara_, lygi, stun_, _stiu,
+ {{0x44291c47,0xe1f0803f,0x6e210510,0x3f85ad9c}}, // _bra_, _رسم_, _álbu, solu_,
+ {{0xf7708fd3,0x7d09e01a,0x998480f7,0x69c50e5a}}, // _کان_, _mbes, _النو, _athe,
+ {{0x4429601b,0x60dbadde,0xbea58dc0,0x656ba756}}, // _dra_, laum, _калк, high,
+ {{0x29d38012,0xc5d5835f,0x44294899,0xfaa61269}}, // nţa_, _тіль, _era_, раво,
+ {{0x44291f26,0x92d600c8,0x90c60a41,0x1c461ccf}}, // _fra_, হলে_, рбае, _вним,
+ {{0x656ba770,0x657d0019,0x2912401d,0x4429601c}}, // [5b00] digh, érhe, reya_, _gra_,
+ {{0x2000601d,0x7d09c82e,0x7f43e01e,0x27edb20e}}, // rtii_, _abes, _jonq, iren_,
+ {{0x27ed8352,0x2000601f,0x26da3c1b,0x67020aed}}, // hren_, stii_, rapo_, र्षक_,
+ {{0x44290a8e,0x0c2689b0,0xa37b0187,0xe7ef016f}}, // _yra_, имен, rsõe, _चमचा_,
+ {{0x4ada18b8,0xa37b03a7,0x9f520009,0x6b4b0074}}, // _बजाव, ssõe, ttyä_, _sügi,
+ {{0x27e06020,0x27ede021,0x7d09836a,0x69dc804a}}, // msin_, dren_, _ebes, _ærek,
+ {{0x6d560065,0x27edd176,0x27e005b2,0x7c2991d3}}, // _olya, eren_, lsin_, _erer,
+ {{0xa01b0009,0x6d59e022,0xdb1600ff,0xf1269cf8}}, // _työt, chwa, _quyê, рько,
+ {{0x27e06023,0xdce98499,0x27ede024,0x06da80ab}}, // nsin_, _sjeć, gren_, _দাবি,
+ {{0x27e032c6,0x6d561061,0x26d800d7,0x4429488e}}, // isin_, _alya, _jero_, _rra_,
+ {{0xe8fac907,0x27e0083a,0x27ed9d6a,0xdb16001c}}, // _але_, hsin_, aren_, _tuyê,
+ {{0x44296025,0x99758364,0xa2ca8894,0x69ca82af}}, // _pra_, _лучш, _स्त्, mpfe,
+ {{0xae438077,0x69c5148f,0x442680b9,0x6b5001ec}}, // مپیو, _sthe, nvo_, _tägl,
+ {{0x656be026,0x4429079f,0x4426c34e,0x83870048}}, // zigh, _vra_, ivo_, рыме,
+ {{0xa3b88768,0xdcfb8289,0x7d1f01b9,0x99671b47}}, // _घूस_, _ukuć, _taqs, атал,
+ {{0x44296027,0x4426803a,0x6d444395,0x5d843b76}}, // _tra_, kvo_, _goia, _ولول,
+ {{0xd6db0073,0x60cd6028,0x26d823f8,0x61ee509b}}, // [5b10] вте_, rbam, _bero_, arbl,
+ {{0x26d805e4,0xbf0201a2,0x2be0800c,0x2d788267}}, // _cero_, र्लभ_, _निजा, učen_,
+ {{0x6d59e029,0x69c5602a,0x4426847f,0xa3d30072}}, // thwa, _uthe, evo_, हीर_,
+ {{0xa2cb0076,0x27eda911,0x6d4403a8,0x27e0602b}}, // _त्त्, yren_, _xoia, bsin_,
+ {{0x26d8602c,0x442982ba,0x2fc68282,0x27e05138}}, // _fero_, ía_, _ntog_, csin_,
+ {{0x67d4091c,0x6d59ddc2,0x26d82fa3,0xa3e88054}}, // _фору, shwa, _gero_, _मटर_,
+ {{0x21698544,0xa9699935,0x25b84125,0xa01b15c8}}, // лики_, лика_, _gurl_, _työs,
+ {{0xc692093f,0x27edbfae,0x7d09e02d,0x991580e8}}, // _זאל_, tren_, _ubes, ські,
+ {{0x6569ac07,0x27eda5e5,0x60db8e20,0x26d8602e}}, // _imeh, uren_, raum, _yero_,
+ {{0x27ed80ad,0x26c10207,0x75209a7b,0x33330118}}, // rren_, ncho_, _zamz, _nnxx_,
+ {{0x22958307,0xe81c8105,0xb87b001b,0xaa9580f7}}, // _الاس, _बैठा_, _sdíl, _الاث,
+ {{0x05561056,0x27e00009,0x27eda106,0x8d5627b9}}, // стоя, ysin_, pren_, сточ,
+ {{0x6c7980be,0xdb0d0144,0x65698870,0x7f43ce12}}, // ראָפ, _atañ, _mmeh, _tonq,
+ {{0x66018713,0x2be0816f,0x61f5029a,0x316d9139}}, // ptlk, _निघा, fuzl, liez_,
+ {{0x450d00c8,0x26d80578,0x44269d96,0xfbd200d5}}, // ষয়ক_, _rero_, zvo_, _کتب_,
+ {{0xf99f602f,0x27e06030,0x26d86031,0x31bc0019}}, // _chè_, tsin_, _sero_, _víz_,
+ {{0x26d86032,0x61431a34,0x41270698,0x4426241f}}, // [5b20] _pero_, чета, щото_, _éo_,
+ {{0x27e05618,0xa01b0009,0x7989874c,0x6569a0b3}}, // rsin_, _pyör, hoew, _ameh,
+ {{0x27e03798,0x26d847c0,0x14c8803d,0x869a0087}}, // ssin_, _vero_, لهای_, утат_,
+ {{0x44268ed6,0xdb08826f,0xb5fa80be,0xd5fa80be}}, // tvo_, _vidí, טלעכ, טפער,
+ {{0x2a6e026c,0x307501a1,0x6d988118,0x75208084}}, // _žfbh_, _дурс, bían, _vamz,
+ {{0x8c1b0039,0x2d8d81bc,0x6d9883a8,0xa3da064a}}, // טוני, _ekee_, cían, डीय_,
+ {{0x316d80e7,0x201d810c,0x9f5f8036,0x6d9d0174}}, // fiez_, _aswi_, ctué_, néac,
+ {{0x657b820f,0xa517035a,0xa3d82c79,0x394000b9}}, // _gjuh, _तसेच_, ाठा_, njis_,
+ {{0x25bf0052,0x64a7016f,0x38606033,0xeafa80f7}}, // _čuli_, _गणेश, nzir_, _مرات_,
+ {{0xdbd42511,0xa91d8088,0xb35680d5,0x161d001b}}, // _núñe, nožj, _دیتا_, फिचर_,
+ {{0xab5b07d0,0xb0b806ae,0x00000000,0x00000000}}, // nqüe, _अलंग, --, --,
+ {{0x9f470a56,0x316de034,0x386000b9,0x06971101}}, // čné_, ciez_, kzir_, מדים_,
+ {{0xa3d30665,0x589680d5,0x25a06035,0x06e300ab}}, // हीं_, _اجاز, _dhil_, ন্টি,
+ {{0x39468079,0x661c806a,0x386000dd,0x0eba8e11}}, // _hoos_, ærks, dzir_, _суды_,
+ {{0xe5c696d4,0x26de9aae,0x3946e036,0x6d988511}}, // _усло, mato_, _koos_, vían,
+ {{0x26dee037,0x25a00219,0x7a0a811c,0x3946a525}}, // lato_, _ghil_, _xəta, _joos_,
+ {{0x39468069,0x656f6038,0x26c102d6,0x7d0d20af}}, // [5b30] _moos_, mich, tcho_, _ibas,
+ {{0x656f6039,0x26de8dff,0x2d8b0081,0x26c1603a}}, // lich, nato_, loce_, ucho_,
+ {{0x6d98b1bd,0x26c1603b,0x21220326,0xf99f2d60}}, // rían, rcho_, _fakh_, _thè_,
+ {{0x656f603c,0xbcfb03d3,0x2d8b34a6,0x7ae8e03d}}, // nich, ciét, noce_, _addt,
+ {{0x26de9f05,0x7d0d603e,0xbcfb603f,0xdce601a9}}, // kato_, _mbas, liés, nikā,
+ {{0x656f1d1f,0xbcfb0019,0xdea4004e,0x752801a9}}, // hich, gnéz, _کیمی, ēdzi,
+ {{0x9f490307,0x7d0d39a8,0x656f6040,0x39468f1a}}, // nraí_, _obas, kich, _boos_,
+ {{0x7c2d6041,0x3946e042,0x656f588e,0x57e9a300}}, // _orar, _coos_, jich, адим_,
+ {{0x5fe2101c,0xa91d803e,0xf8ae8077,0x3946e043}}, // _पिछल, ložk, نکه_, _doos_,
+ {{0x7d0d2896,0x442d91d3,0x26dee044,0x8e099508}}, // _abas, _ire_, gato_, анов_,
+ {{0xb8ff80c8,0x656f6045,0x6e459980,0xe8d90a2c}}, // _তা_, fich, ценз, ghị_,
+ {{0xdb0d0125,0x63bbd777,0x656f6046,0xdce600eb}}, // _stað, _kuun, gich, fikā,
+ {{0x26deade6,0x66e58ff7,0xbcfb0333,0x63bb8074}}, // bato_, _дола, viét, _juun,
+ {{0x26de8698,0x63bbae05,0x78130105,0x7d0d0074}}, // cato_, _muun, डबैक_, _ebas,
+ {{0x9f4900f7,0x6d5d032f,0xed5a2097,0xa01b0198}}, // graí_, bhsa, _вой_, _syöp,
+ {{0x442d9429,0x68fbbb11,0x656f002a,0x3cff81c0}}, // _ore_, ngud, cich, _ncuv_,
+ {{0x6723829b,0x442de047,0xbcfb080d,0xdbd4002a}}, // [5b40] _kanj, _nre_, riét, _túñe,
+ {{0x44206048,0x672384bf,0x752408bf,0xb3580065}}, // _isi_, _janj, _haiz, _ایسا_,
+ {{0x6723ae4d,0x442d873a,0xe61a2762,0xbcfb03b0}}, // _manj, _are_, рде_, miér,
+ {{0x67238867,0x442da009,0x629e0353,0xa01b0009}}, // _lanj, _bre_, _izpo, _työp,
+ {{0x3946e049,0x442dad2c,0x25a6acbb,0x7524544c}}, // _roos_, _cre_, rmol_, _maiz,
+ {{0x394684df,0x442da50e,0x656f604a,0x60c4025b}}, // _soos_, _dre_, zich, lcim,
+ {{0x442de04b,0x26de8698,0x39469d14,0x2d78875f}}, // _ere_, vato_, _poos_, nček_,
+ {{0x4420604c,0xdb088acf,0x442d9f6c,0x26de985b}}, // _osi_, _didá, _fre_, wato_,
+ {{0x6723e04d,0xb4e6a0f2,0x442d8db7,0x656f1af7}}, // _banj, पणी_, _gre_, vich,
+ {{0x656f0e83,0x68fbda1d,0x672384b9,0x10370039}}, // wich, agud, _canj, קטים_,
+ {{0x26dee04e,0x656f604f,0x6723a53d,0xdd9501bb}}, // rato_, tich, _danj, заны,
+ {{0x26dee050,0x60c4012b,0xe7f38072,0x6d42e051}}, // sato_, jcim, _आमचा_, njoa,
+ {{0x656f6052,0x26dee053,0x6d988388,0xaade881f}}, // rich, pato_, víal, _नजाक,
+ {{0x656f6054,0x68e0e055,0x7c2d0168,0x6d989e00}}, // sich, lamd, _vrar, díam,
+ {{0x44206056,0x7524284e,0xf96b0b69,0x3f8ce057}}, // _esi_, _faiz, арай_, modu_,
+ {{0x9f490013,0xd1380470,0x7c2d3e3d,0x4cd500ab}}, // rraí_, оху_, _trar, _থাকু,
+ {{0x656d043b,0x7c2d1c88,0x6723e058,0x27f22cd4}}, // [5b50] _imah, _urar, _yanj, gryn_,
+ {{0x4e1e8996,0x68e09067,0x442de059,0x75240102}}, // _बनाई_, hamd, _rre_, _zaiz,
+ {{0x63bb83ff,0x7bc99ab0,0x442d8b89,0x672500f1}}, // _suun, _steu, _sre_, rdhj,
+ {{0x442de05a,0x3f8cc4dd,0x26dc84c4,0x6d98a706}}, // _pre_, hodu_, _levo_, bíam,
+ {{0x9f400876,0xdb0405e4,0xbcfb00e7,0x6d98818a}}, // guió_, _guió, chée, cíam,
+ {{0x442d8247,0x656d0122,0x777a81c0,0x67ba01c6}}, // _vre_, _lmah, jntx, _למשק,
+ {{0x7de00065,0x5694102a,0x656d003d,0x7c26a46d}}, // _vásá, _жарт, _omah, _škrt,
+ {{0x442daca0,0x627c012a,0x60dd0668,0xceba80fc}}, // _tre_, ינונ, _jesm, _baƙo_,
+ {{0x60dd156b,0x442de05b,0x67238d54,0x6d5b83ca}}, // _mesm, _ure_, _panj, _ilua,
+ {{0xf8b8150e,0x656d01d4,0x60dd5818,0x6d5b81e9}}, // _अल्प, _amah, _lesm, _hlua,
+ {{0x6723e05c,0x4420605d,0x26dca349,0x6d5b8590}}, // _vanj, _ssi_, _devo_, _klua,
+ {{0x7d19e05e,0x670b19e8,0x60dd3732,0x070b26ee}}, // news, स्तक_, _nesm, स्तव_,
+ {{0x6723e05f,0x752401e2,0x60c4005c,0xbcfb157a}}, // _tanj, _vaiz, vcim, tiér,
+ {{0x44200db7,0x8ccc06ab,0x656d01f6,0xef1881a9}}, // _vsi_, _थ्रो, _emah, ceļ_,
+ {{0xbcfb003e,0xb87b0264,0x60c40669,0x6d9d3dcd}}, // riér, _leíd, tcim, méan,
+ {{0x442001e9,0x629e3c49,0x216716df,0xa96703bc}}, // _tsi_, _vzpo, писи_, писа_,
+ {{0xb4d81094,0x4420180b,0x1dd78105,0x46ea0cf9}}, // [5b60] िरी_, _usi_, भीरत, йдан_,
+ {{0xf7721e95,0x60c41577,0xd7ef00f7,0xc2ea0065}}, // راء_, scim, اكل_, _اعظم_,
+ {{0x60c4005c,0x7abb0039,0x395f816d,0x9cca8fe6}}, // pcim, _לציו, khus_, была_,
+ {{0x6d5b832f,0xb4e6816f,0x60dd074c,0xeeeb00ff}}, // _clua, पणे_, _gesm, _đụng_,
+ {{0x395fe060,0x6d5bc88c,0x6d428009,0x29e80713}}, // dhus_, _dlua, rjoa, lğa_,
+ {{0xa09b03de,0x670300a5,0x6d5b82f1,0xa2948a4c}}, // _ליכט, _लायक_, _elua, малі,
+ {{0x3f8c9249,0x3ce2017f,0xc7a30087,0xceba89ab}}, // vodu_, kakv_, _чирк, _saƙo_,
+ {{0x6d5b808c,0xdb0401df,0x3f8c809a,0x6d498197}}, // _glua, _muiñ, wodu_, _goea,
+ {{0x68e082b8,0xb4ca8f97,0x36d4bcb3,0x3f8ce061}}, // ramd, ोरी_, мопр, todu_,
+ {{0x3eb88bc5,0xe29281a8,0x23a40338,0x2d83128a}}, // ørt_, _كذا_, nöjd_, čkem_,
+ {{0xe7e88074,0xd6d78198,0x7a0a829a,0x00000000}}, // _टटका_, чты_, _xətl, --,
+ {{0x395fe062,0xd5b78198,0x23a40338,0x6ecc8bbc}}, // chus_, ясь_, höjd_, _द्रु,
+ {{0x60dd6063,0x1b1f80ab,0x8ca48035,0x2d800176}}, // _resm, পারে_, _औरफो, _djie_,
+ {{0xda20816f,0x2fcb0267,0xa91d976d,0x7c2d8085}}, // _मनात_, _rtcg_, voži, çirə,
+ {{0x60dd1108,0x6e93b5b4,0xdb04002a,0x62340087}}, // _pesm, _пишу, _cuiñ, неру,
+ {{0x9f4703bb,0xf67b8039,0x656d13fa,0xa3da06a7}}, // ční_, _לאומ, _umah, डीह_,
+ {{0xb8d503eb,0x60dd1e1e,0x81bc81a9,0x57b5302b}}, // [5b70] _छः_, _vesm, _krēs, дбат,
+ {{0xb8f4023c,0x657d1c33,0x64d581cb,0x6d5b8c49}}, // _स्_, lnsh, दर्श, _slua,
+ {{0x6d5b8069,0x6d5a8110,0xdb040333,0x0ea700d4}}, // _plua, _įtar, _guiñ, केंड,
+ {{0x6b4fd9c6,0x7f94823e,0xa91d81d6,0x5c759ae1}}, // _løgt, màqu, poži, млет,
+ {{0x69da82a6,0x45270264,0x2bdd864a,0x6b500338}}, // _awte, যাপক_, _नौशा, _vägv,
+ {{0x97351e29,0x883a01c6,0xe7871a19,0x20090084}}, // _تکرا, _ותשו, _хумо, mtai_,
+ {{0x6fd5823c,0xc9a982de,0x395facd4,0x225480e1}}, // _मौजू, овке_, thus_, _čaká_,
+ {{0x2129059c,0x394b009f,0x67271369,0x17c99ccf}}, // ndah_, _jocs_, _hajj, огии_,
+ {{0x200916d5,0x395f90a9,0x7bcd01e9,0xf53f0aa2}}, // ntai_, rhus_, _ntau, rmål_,
+ {{0x2d8f84c4,0xf53f035f,0x2d92051e,0x395f8bbd}}, // loge_, smål_, _skye_, shus_,
+ {{0x7bcd34c6,0x7ae38d88,0x395fe064,0x67276065}}, // _atau, mant, phus_, _majj,
+ {{0x200901e2,0xa2cb0075,0x61f500d2,0x26c5802a}}, // ktai_, तुस्, drzl, sclo_,
+ {{0x21296066,0x26c7ad16,0x6b548362,0x660d80c3}}, // ddah_, _tfno_, _bàgh, _ćaku,
+ {{0xfaa60729,0x21290748,0x5eb780ab,0x2d995e14}}, // даго, edah_, _আজকে, alse_,
+ {{0x6dbe01ac,0xeb97087e,0xe0df01e4,0xdb040333}}, // _vďak, фир_, _beò_, _quiñ,
+ {{0x7ae3e067,0x29e8029a,0x2d1c816f,0x61ee806a}}, // hant, rğa_, _नसेल_, _æble,
+ {{0x62856068,0x490f801b,0x67276069,0xd00717c8}}, // [5b80] _exho, थ्यो_, _bajj, мете_,
+ {{0x7ae392b9,0x7c24606a,0x6d46011f,0x2d789024}}, // jant, _isir, ljka, nčev_,
+ {{0x4432003e,0x672707d5,0x5b5701c6,0x394b51fb}}, // _hry_, _dajj, _חייב_, _focs_,
+ {{0x4432079f,0x6e239c33,0x2b008054,0xb4ca9344}}, // _kry_, _msnb, _राहु_, ोरे_,
+ {{0x68e40739,0x7ae3bc72,0x7c83a481,0x6c1a8bbe}}, // maid, fant, куше, تتاح_,
+ {{0x68e4606b,0xf8bf11b9,0x7c2404cd,0x6aa582f1}}, // laid, _jaén_, _msir, गे्र,
+ {{0x7d0402a5,0x61fc606c,0x6f1a81a1,0x7bdb9a14}}, // _ocis, nurl, vetc, _ewuu,
+ {{0xbea32c9f,0x29002810,0x68e412be,0x2d8f86a5}}, // _раск, ngia_, naid, coge_,
+ {{0x7ae3e06d,0x02cf9299,0xdb088198,0x7306b511}}, // bant, _स्वभ, _pidä, _опаз,
+ {{0x68e4154c,0xb3568077,0xddc90087,0x61fc606e}}, // haid, _زیبا_, _înţe, kurl,
+ {{0x7bc0b3ef,0x7c24606f,0x36f70039,0x7bcd1831}}, // _humu, _asir, וצים_, _stau,
+ {{0x7bc09f34,0xc0a9817e,0x4432267f,0x61fc6070}}, // _kumu, _کامل_, _bry_, durl,
+ {{0x7bc08010,0x68e40014,0xa3da016f,0xe043a482}}, // _jumu, daid, डील_, _инци,
+ {{0xfe6f9125,0x289b8051,0x7bc0a2a3,0x4424bd93}}, // ادی_, _וידא, _mumu, _msm_,
+ {{0x443202ec,0x7c2450c2,0x68e41b7c,0x7bc0a34d}}, // _ery_, _esir, faid, _lumu,
+ {{0x7ae3e071,0x68e46072,0xbcfb0013,0x29000098}}, // zant, gaid, bhéa, ggia_,
+ {{0x7d1d6073,0x7ae3e074,0xc05b035f,0x4432009a}}, // [5b90] mess, yant, ців_, _gry_,
+ {{0x20096075,0x7d1d2b52,0x628501e9,0xdce9911b}}, // rtai_, less, _txho, _rjeđ,
+ {{0xb905150b,0x20092024,0xfad60051,0x8aa71bc1}}, // _না_, stai_, _אותך_, _юрид,
+ {{0x68e46076,0x7bc0c45b,0xe51000ff,0x67270c2e}}, // caid, _bumu, _lặng_, _wajj,
+ {{0xa3d3016f,0x67276077,0x4424e078,0x0c7580d7}}, // हीच_, _tajj, _csm_, رگرد,
+ {{0xe510001c,0x7bc08077,0xfaa595a4,0x27e957c4}}, // _nặng_, _dumu, нало, msan_,
+ {{0x27e92973,0x41b589e0,0xb87b016a,0x7d1d6079}}, // lsan_, нсит, _edít, kess,
+ {{0xd2b80051,0x349594b7,0x4424c264,0x27e9117d}}, // ולות_, _забр, _fsm_, osan_,
+ {{0x27e95c6d,0x7d1d03ff,0x7bc0ad80,0xa2ca8128}}, // nsan_, dess, _gumu, _स्क्,
+ {{0x27e95537,0x6b50016d,0xd7098009,0x6d59e07a}}, // isan_, _lägr, чное_, lkwa,
+ {{0x7bc0c675,0x7d1d3261,0x6dac83bf,0xcf9204de}}, // _zumu, fess, _işar, רטי_,
+ {{0x27e9607b,0x7ae1a3f8,0x26070665,0x90e7826a}}, // ksan_, _helt, _हमनी_, _آسان,
+ {{0x7ae18f52,0x649a13d7,0x6d4d00b9,0x6d598041}}, // _kelt, ятор_, _doaa, ikwa,
+ {{0x61fc0082,0x6b5487e2,0xdb1c0201,0x68e4022e}}, // turl, _pàgi, _özün, waid,
+ {{0x2d960b9c,0x68e40739,0x69c183ba,0x6d59e07c}}, // _прис, taid, _jule, kkwa,
+ {{0x7d1d20ef,0x60c9c73a,0xdb01a489,0x64401bda}}, // cess, ncem, _kiló, ämis,
+ {{0x68e40219,0x69c18aa8,0x27e902a3,0x6d598041}}, // [5ba0] raid, _lule, gsan_, dkwa,
+ {{0x68e4607d,0x6d59e07e,0x6d5d03ed,0x1aea0264}}, // said, ekwa, ërav, ঞ্জে_,
+ {{0x7bc0c18f,0xaadb0051,0x27e93d84,0x44248abf}}, // _sumu, _מחיר, asan_, _ssm_,
+ {{0x9f471dc1,0x27e90079,0x7bc0809c,0xac869cff}}, // čná_, bsan_, _pumu, нгел,
+ {{0x3d120b86,0xdca38aac,0xb87b0073,0xe505275c}}, // ध्ये_, гаци, _veíc, ष्टि_,
+ {{0x69c1e07f,0x6562ceb6,0x7d1d6080,0xf48782e3}}, // _bule, shoh, zess, رانی,
+ {{0x7ae18370,0x752980b9,0x69c1e081,0x1e950256}}, // _delt, _faez, _cule, _артр,
+ {{0x7bc0e082,0xd90e815b,0x69c1e083,0x44220573}}, // _tumu, صیت_, _dule, kwk_,
+ {{0x7ae1dccf,0x7d1d3666,0x442480ee,0x7c248059}}, // _felt, vess, _usm_, çird,
+ {{0x7d028118,0x7c22e084,0x7ae1e085,0x7d1d6086}}, // lgos, mwor, _gelt, wess,
+ {{0x26c7005c,0x1600809a,0x6dba8197,0x69c804e8}}, // žnog_, लंधर_, _aċar, ídel,
+ {{0x7d02e087,0x63ba803a,0x27e902a3,0x7a0a8201}}, // ngos, _hitn, ysan_, _nəti,
+ {{0xf4840abe,0xe5100028,0x69c182af,0x27e92fc7}}, // гурн, _tặng_, _zule, xsan_,
+ {{0xdbc78006,0x5b240b79,0xb4bb0180,0x6f1e0140}}, // _tööt, льса, _کاغذ_, jepc,
+ {{0xd0560201,0x7d1d2660,0xf1a73197,0x399a8168}}, // mayə, pess, еран, tësh_,
+ {{0x7c229328,0x673c06c4,0xceba8326,0x63ba9c77}}, // kwor, _anrj, _naƙi_, _litn,
+ {{0x442f1c86,0x6b50016d,0x399a8168,0x7d02e088}}, // [5bb0] _ég_, _vägr, rësh_, dgos,
+ {{0x27e93dd5,0x7f4e01c0,0x2d84b5ca,0xd0560085}}, // rsan_, _cobq, _ajme_, nayə,
+ {{0x7ae7179d,0xceba8300,0xf74801a8,0x6d59890d}}, // majt, _baƙi_, علمي_, tkwa,
+ {{0x7ae1e089,0x442f84c3,0x7a0a8201,0x9f400020}}, // _selt, tvg_, _gəti, frió_,
+ {{0x63ba803a,0x6d59800b,0xd0560085,0x7c22e08a}}, // _bitn, rkwa, kayə, gwor,
+ {{0x629a8831,0xe299891d,0x7ae7608b,0x69c1e08c}}, // _byto, мал_, najt, _pule,
+ {{0x7ae1afb1,0xc33404de,0x38693b40,0xee840198}}, // _velt, טוס_, lzar_, _быто,
+ {{0x7ae1e08d,0x9f40349a,0xdb17002a,0x6ecd00d4}}, // _welt, brió_, _dixé, दुरु,
+ {{0x212b285f,0x3869608e,0x7c228358,0x7ae70619}}, // _mach_, nzar_, cwor, kajt,
+ {{0x69c1a47f,0x25a9011e,0x63ba809c,0x7ae70796}}, // _tule, _ahal_, _gitn, jajt,
+ {{0x39158077,0xa2ca9053,0xdb01c649,0x799b96fb}}, // _لواز, _स्ट्, _filò, pluw,
+ {{0x212b1769,0x2019061b,0x25a9608f,0x2d7880c3}}, // _nach_, _opsi_, _chal_, lčer_,
+ {{0x0d8307a1,0x6e270338,0x644fe090,0x63ba89c4}}, // рлян, _lsjb, _àcid, _yitn,
+ {{0x93fa804c,0x987a80be,0x386900dd,0x7ae71924}}, // ולשי, _קאסט, dzar_, gajt,
+ {{0xe2970468,0x212b0114,0x27ff8009,0x200d8afc}}, // вар_, _bach_, luun_, ltei_,
+ {{0x212b4045,0x399a8168,0x25a901b9,0x68e2802a}}, // _cach_, tësi_, _ghal_, _xeod,
+ {{0xf5770013,0x200db296,0x8c464845,0x7ae735bf}}, // [5bc0] _جميع_, ntei_, _репе, bajt,
+ {{0x93fb0158,0x4438827f,0x212b36d3,0x7bfb0039}}, // _קליי, _čr_, _each_, _קפיצ,
+ {{0x212b278c,0xb87b0333,0x3869016a,0x63ba8bcb}}, // _fach_, _veía, azar_, _ritn,
+ {{0x212b1523,0x63ba803a,0x7c228367,0x27ff8009}}, // _gach_, _sitn, twor, kuun_,
+ {{0xa3dd816f,0x0b8a8abe,0x61eaba65,0x938a867c}}, // थील_, мски_, wsfl, мска_,
+ {{0x67138f97,0x9f401d24,0x61ea848d,0x629a839c}}, // त्मक_, rrió_, tsfl, _pyto,
+ {{0xf50696cf,0x212b0101,0x63baa406,0xd5b285ff}}, // _изво, _yach_, _vitn, _نفس_,
+ {{0xdb09a423,0xf98f80f7,0x2d540187,0xdb1703a8}}, // _dueñ, يبي_, _mães_, _vixé,
+ {{0xdefa8162,0xc17304de,0x6dac8085,0x290680b9}}, // мын_, יחה_, _uşaq, _vcoa_,
+ {{0x63ba8cfa,0x7d1601d0,0x5ba78012,0x473386d2}}, // _uitn, _abys, _акум_, аньс,
+ {{0xb9088a49,0x386927be,0x0f5701c6,0x6b7b83c8}}, // _বা_, zzar_, דיים_, _קרבנ,
+ {{0xdced026c,0x7bc45ad8,0x6d988118,0x9f4d802a}}, // _tmač, _buiu, mías, rreá_,
+ {{0x7ae70b48,0x212b6091,0x7bc418ad,0x06e800ab}}, // tajt, _rach_, _cuiu, _পানি,
+ {{0x27f980e1,0x69c30061,0x212b6092,0x7bc45ad8}}, // ásne_, _ének, _sach_, _duiu,
+ {{0xda780656,0xb87b0032,0x31c400c4,0x2d540187}}, // вят_, _adíp, иств, _cães_,
+ {{0x38696093,0x3f9ecc11,0x656647d0,0x99640012}}, // tzar_, lltu_, chkh, атул,
+ {{0x212b36b0,0x394902f1,0x7ae70968,0x6ca40162}}, // [5bd0] _vach_, rjas_, pajt, _врэж,
+ {{0x44296094,0xdb1a820f,0x386949d8,0xf77012c5}}, // _isa_, _ditë, rzar_, يال_,
+ {{0x3a8407ac,0x733a00be,0x212b6095,0xed5980c3}}, // _выяв, _װערס, _tach_, _liže_,
+ {{0x6d5d6096,0x6d98a363,0x38690144,0x867b81c6}}, // nksa, días, pzar_, ורבו,
+ {{0xdb0985b4,0xed598052,0x752d2c07,0x7ae50006}}, // _sueñ, _niže_, _maaz, _keht,
+ {{0x4209a964,0x69c500b8,0xb464804a,0x3d0882f1}}, // енко_, _kuhe, шкіл, _हाथे_,
+ {{0xe296bcb3,0x69c502f1,0x291f8c53,0xdb019ee0}}, // лаш_, _juhe, teua_, _uhlí,
+ {{0x4429043d,0x7ae51453,0x69c54141,0xc8798087}}, // _osa_, _leht, _muhe, laşi_,
+ {{0x7d098a03,0x69c503ed,0x7ae502f9,0xdce28965}}, // _ices, _luhe, _oeht, lioğ,
+ {{0x7c29e097,0xed59812b,0x200de098,0x7ae501d0}}, // _iser, _diže_, rtei_, _neht,
+ {{0x44296099,0x200d895d,0x394f8106,0x1d0a0081}}, // _asa_, stei_, _togs_, нени_,
+ {{0x25624b9b,0xd76382e3,0x200d995c,0xaca38133}}, // _bóla_, انگی, ptei_, _nrịa,
+ {{0xff53803d,0xe72ec2c4,0x60dbd88b,0xe8d9019d}}, // _پخش_, _ле_, mbum, akị_,
+ {{0x752f0063,0x27ed8ed0,0x60dbe09a,0x69c5609b}}, // adcz, msen_, lbum, _buhe,
+ {{0x4429040e,0x27ed8f06,0x6721dcdc,0x6b5dadb2}}, // _esa_, lsen_, melj, _règl,
+ {{0x69c508cf,0x660182a3,0x6d5d1647,0x98a0005c}}, // _duhe, mulk, cksa, ndić_,
+ {{0x27edd280,0x43761597,0xa067013a,0x999e0019}}, // [5be0] nsen_, _бунт, гата_, ítő_,
+ {{0x7d09e09c,0x27ed95ec,0x6459809a,0x324604ae}}, // _aces, isen_, dywi, _беог,
+ {{0x2776893f,0x7c29c6c7,0x670688fd,0x68e982c4}}, // נגען_, _aser, _शासक_, kaed,
+ {{0x27edbe74,0xd9460aac,0x98a00042,0x7d098118}}, // ksen_, _семи, jdić_, _cces,
+ {{0x5ee300c8,0x7c298019,0x4d7b00be,0xbebc80eb}}, // _পারে, _cser, ערנע, ldīt,
+ {{0x53348676,0x67218025,0x27ed8aaa,0x6be380f7}}, // рект, jelj, dsen_, حكوم,
+ {{0x6721e09d,0x7c29e09e,0x3d050d38,0x96660470}}, // delj, _eser, _वाले_, _скве,
+ {{0x68e9e09f,0x63be60a0,0x6d989d3a,0xaca42d10}}, // gaed, _lipn, rías, _akọw,
+ {{0xa3cb80cf,0x68e601a1,0xb6e685e9,0x6d40ba84}}, // _रूप_, _mekd, люнк, _inma,
+ {{0x66e40af3,0x25620019,0x75228118,0x656401f8}}, // गरिक_, _róla_, leoz, _alih,
+ {{0x49068f99,0x3eb8e0a1,0x6601808e,0x98a0026c}}, // _مواق, ärt_, gulk, bdić_,
+ {{0x69c529d6,0xdd9501e5,0xafe6be3d,0x27ed9433}}, // _ruhe, рамы, _бодл, bsen_,
+ {{0x6d5d1614,0xb99396a5,0xc99380f7,0x442690ab}}, // rksa, _النب, _النظ, nwo_,
+ {{0x69c50364,0x0703009a,0x442960a2,0xdcef0087}}, // _puhe, _लाइव_, _vsa_, fică,
+ {{0x60cd093d,0x6459809a,0x69c500f1,0xdb1ae0a3}}, // tcam, zywi, _quhe, _cité,
+ {{0x442901c5,0x2608035a,0x7aea82af,0x29ca8028}}, // _tsa_, ांनी_, haft, _mùa_,
+ {{0x442960a4,0x60cd157a,0x7ae560a5,0x9f4021f2}}, // [5bf0] _usa_, rcam, _teht, ksiä_,
+ {{0x442695ab,0xa3da1834,0x6e2a826c,0x69c5405e}}, // dwo_, डीओ_, _ksfb, _tuhe,
+ {{0x7aeae0a6,0x57a42306,0xe4d58290,0x539c01c6}}, // daft, сшта, لقاد, _איזו,
+ {{0x60c29281,0x89da007c,0x7c299fa4,0x6459866f}}, // _ngom, _שחרי, _pser, tywi,
+ {{0x1aee80c8,0x8c459af6,0x705801e5,0x29190118}}, // চ্ছে_, реле, _баяр_, _ibsa_,
+ {{0x6459ce06,0x6d4085e4,0xb60600cd,0x60c29f78}}, // rywi, _enma, _bišć, _agom,
+ {{0xdb0180f7,0x29190267,0x06e380ab,0x27ede0a7}}, // _chlá, _kbsa_, _মালি, wsen_,
+ {{0x44268578,0x7d09876c,0x7c2984b9,0xaca3026b}}, // bwo_, _uces, _tser, _ajọf,
+ {{0x84678698,0xdb0d0118,0x98a000ce,0x27ed8456}}, // _съве, _luañ, rdić_, usen_,
+ {{0xee39952a,0x27ed82fe,0x6601e0a8,0xf77188ca}}, // вно_, rsen_, tulk, تاب_,
+ {{0x27ed977a,0x3ea5816c,0x2ca90085,0x67218968}}, // ssen_, ринг, _azad_, relj,
+ {{0xa3a985e8,0x27ed9c50,0x51849444,0xc9849afa}}, // _गीत_, psen_, _луча, _лучи,
+ {{0x6721881d,0x6601a3be,0x452700ab,0xa3b1016f}}, // pelj, sulk, যাংক_, _ओळख_,
+ {{0x394dc2ed,0x68e660a9,0xb4e50105,0xdb1abd9b}}, // ljes_, _sekd, _पड़ी_, _pité,
+ {{0x09e6035f,0xa3da0540,0xc7c8801c,0x2d8b00e7}}, // робн, डीज_, _tốc_, ébec_,
+ {{0x629e026f,0x395f8110,0x394d88b2,0x61ee2a33}}, // _vypo, nkus_, njes_, rsbl,
+
+ {{0x629e0063,0x386d9f8e,0x3fc8004e,0x395f9d46}}, // [5c00] _wypo, nzer_, ادری_, ikus_,
+ {{0x629e5518,0x13cc80ab,0x386dd76f,0xe0df0176}}, // _typo, রীড়, izer_, _anòd_,
+ {{0x394da9af,0x395f8074,0x6d988118,0x7626072a}}, // kjes_, kkus_, fíap, рмез,
+ {{0xf1bf0142,0x44268063,0xe45a8364,0x61fc8f06}}, // _giá_, two_, кже_, _årli,
+ {{0x394dc5ba,0xac07c90d,0xd24f02e3,0x386d80f3}}, // djes_, инца_, _فنی_, jzer_,
+ {{0x4426e0aa,0x60c2847f,0x2ca000b9,0x395f81a9}}, // rwo_, _sgom, _myid_, ekus_,
+ {{0x661c1c67,0x7aeaafd7,0x25bf8087,0x46aa0074}}, // _uprk, raft, _fiul_, _करिह,
+ {{0x6d40ab02,0xb6060267,0x394daf34,0xed57a503}}, // _unma, _višć, gjes_, _кос_,
+ {{0xed578ca4,0x7f9d80e7,0x20120198,0x777a8cdb}}, // _тот_, hèqu, ntyi_, titx,
+ {{0xc3068061,0x38170039,0x200b809f,0x394d81ca}}, // _خبرو, סקים_, àcia_, ajes_,
+ {{0x56948d0f,0xed5980c3,0x777aac03,0xb87b0144}}, // сант, _hiža_, ritx, _reím,
+ {{0x60c28435,0xdb17002a,0xdb1e0580,0x69c8e0ab}}, // _ugom, _vixí, _hipè, _iude,
+ {{0xd007a6ad,0x80dd80c8,0xb7d49c12,0x69c8a069}}, // _вече_, _যাচ্, _اقتب, _hude,
+ {{0x69c88102,0x200480eb,0x93880198,0x2717128a}}, // _kude, mumi_, асса_, _mění_,
+ {{0x69c88012,0x2004bd3d,0x7ae8e0ac,0x2b099008}}, // _jude, lumi_, _medt, _सासु_,
+ {{0x69c8e0ad,0xeeeb00ff,0xd5430032,0x7ae8a9c6}}, // _mude, _đựng_, _dẹ́b, _ledt,
+ {{0x657d60ae,0xb4e303db,0x2d9960af,0x2004c561}}, // [5c10] mish, धरी_, mose_, numi_,
+ {{0x2d9960b0,0x69c885f8,0x7c2d3143,0x657d2840}}, // lose_, _oude, _isar, lish,
+ {{0x20049090,0x645d0039,0xcf9383de,0xa1950c99}}, // humi_, lysi, קטע_, _ганч,
+ {{0x2d9902a5,0x2004e0b1,0x657d3418,0xe57080f7}}, // nose_, kumi_, nish, وطن_,
+ {{0x20048341,0x68ed26dc,0x3d088105,0xa6be80ab}}, // jumi_, maad, _हारे_, োরিয়,
+ {{0x657d364e,0x68ed26dc,0xb5c90875,0x2004e0b2}}, // hish, laad, _موسم_, dumi_,
+ {{0x657d38ef,0x394d95ca,0x7d0d11b9,0xd5bb98d1}}, // kish, tjes_, _ocas, _аса_,
+ {{0x386de0b3,0x68ed429a,0x7d0d0388,0x290960b4}}, // tzer_, naad, _ncas, ngaa_,
+ {{0x25621220,0x2d990511,0x657d13a2,0x394d80f1}}, // _sólo_, dose_, dish, rjes_,
+ {{0x394da9af,0x395f9371,0x7d0d60b5,0x69c883a7}}, // sjes_, skus_, _acas, _fude,
+ {{0x386d8065,0x7c2d60b6,0x68ed60b7,0x394d9696}}, // szer_, _asar, kaad, pjes_,
+ {{0x657d02a0,0x7a7a8158,0x645d60b8,0x20120009}}, // gish, _גרעס, fysi, ytyi_,
+ {{0x7d1be0b9,0x7bc18e01,0x68ed02ab,0x69c881ec}}, // _mbus, _kilu, daad, _zude,
+ {{0x1dcf89a3,0x660507d5,0x6d4b0247,0xb47a80be}}, // _संपत, kuhk, _ògan, אָלי,
+ {{0x657d38ef,0x7bc194f0,0x7d1b8a40,0x69c88509}}, // bish, _milu, _obus, _xude,
+ {{0x442d820f,0x7c3b9998,0x66050b20,0x68ed60ba}}, // _ose_, _orur, duhk, gaad,
+ {{0x69d7011e,0x442d8091,0xef1a09a0,0x6d44076d}}, // [5c20] _etxe, _nse_, лма_, _inia,
+ {{0x6d440282,0x7bc1b496,0xed5996f2,0x9f4001a8}}, // _hnia, _nilu, _riža_, lsiú_,
+ {{0x442de0bb,0x6d560b8e,0xb3bb0039,0x6605010b}}, // _ase_, _koya, _תמיכ, guhk,
+ {{0x69c8e0bc,0xdb1b807b,0x6d5660bd,0xbcfb00f7}}, // _rude, _stuð, _joya, théi,
+ {{0x69c8b5aa,0x6d5610e1,0x69dc14fb,0x9f4001a8}}, // _sude, _moya, lpre, isiú_,
+ {{0x636b0459,0x6605208b,0x657d13a2,0x442060be}}, // _günd, buhk, zish, _mpi_,
+ {{0x657d1b8f,0x44202168,0x7ae880e8,0xdb088118}}, // yish, _lpi_, _vedt, _sidó,
+ {{0xf1268cde,0x442022cd,0x6d5660bf,0x2d9901df}}, // сько, _opi_, _noya, xose_,
+ {{0xa49484c0,0xdce9a3e3,0x96ea81d9,0x657d39d6}}, // _بیشت, _smeć, льна_, vish,
+ {{0x2004e0c0,0x543b8158,0x160c0665,0x7bc1e0c1}}, // rumi_, _געמא, _हमार_, _gilu,
+ {{0x657d60ae,0xa3e481fe,0x442009ca,0x69c88364}}, // tish, भीर_, _api_, _uude,
+ {{0xd567140b,0x69dc60c2,0x2d990110,0x6b5d8722}}, // стоп, dpre, uose_, _règi,
+ {{0x657d3149,0x6d56572d,0x9743825b,0x7d0d60c3}}, // rish, _doya, _šćep, _vcas,
+ {{0x657d60c4,0x68ed60c5,0x645d60c6,0x69dc2a1d}}, // sish, waad, rysi, fpre,
+ {{0x44200205,0x657d0010,0x69dc1400,0xa77b8039}}, // _epi_, pish, gpre, _תרופ,
+ {{0x7c2d03c3,0x657d00f1,0x44201142,0x636b0074}}, // _tsar, qish, _fpi_, _sünd,
+ {{0xb8d1101b,0x68ed26a5,0x7c2d60c7,0x7aee0079}}, // [5c30] _ओर_, raad, _usar, babt,
+ {{0x66050877,0x442de0c8,0x68ed60c9,0x80d91299}}, // tuhk, _rse_, saad, _फ्रे,
+ {{0x656b8c41,0x65628088,0x63a3816b,0x442de0ca}}, // thgh, fkoh, vlnn, _sse_,
+ {{0x442d820f,0xd126826a,0x9c39a155,0xe7ed0540}}, // _pse_, _غم_, ипет_, _चौथा_,
+ {{0xe3b18277,0x7bc98051,0x6aa18114,0x44200122}}, // ورت_, _queu, _sylf, _xpi_,
+ {{0x442da52d,0x31b38526,0xc1ed02f1,0x7d1b82f7}}, // _vse_, ुद्ध, _जबाब_, _wbus,
+ {{0x27e68324,0x81cb00c8,0x636b03bf,0x3e4e811c}}, // _awon_, রীর_, _güne, bəti_,
+ {{0x442d8069,0x7d1ba9da,0xc7c8801c,0x27ffce30}}, // _tse_, _ubus, _vốn_, trun_,
+ {{0x442d8051,0x7c3bb090,0x7d0481a8,0x69c2811b}}, // _use_, _urur, óise, _dioe,
+ {{0x6d5642e1,0x2d8b0019,0x69dc02d5,0x70c61834}}, // _soya, ében_, ypre, _वल्ल,
+ {{0xda02800f,0x69dc60cb,0xc7c88129,0x27e6a17b}}, // रूआत_, xpre, _uốn_, _ewon_,
+ {{0x80d90540,0x69dc007a,0x261a82f1,0x69c280e5}}, // _फ्ले, vpre, _बहरी_, _gioe,
+ {{0x6d5603d3,0x333301b9,0x9f35804a,0x00000000}}, // _voya, _baxx_, _меді, --,
+ {{0x4375333d,0xf99f0032,0x7989bcc0,0x69c28cdb}}, // пуст, _akè_, lnew, _zioe,
+ {{0xa3cb800f,0x65699295,0x6d56450b,0x06e800ab}}, // _रूस_, _oleh, _toya, _পালি,
+ {{0x44203cf2,0xe0da1289,0x7aee1801,0x6d441fd8}}, // _tpi_, рва_, sabt, _unia,
+ {{0x69dc60cc,0x46ea0088,0x98a3153d,0x88bd809a}}, // [5c40] spre, идан_, дите, ześn,
+ {{0xbf878104,0x7c248059,0x81df80ab,0x799b80f3}}, // _điện_, çirm, _তিন_, houw,
+ {{0x2018802e,0x316d8019,0x644100eb,0xdd8f003d}}, // ării_, khez_, ālie, _سوی_,
+ {{0xcbd580c8,0x96da01fe,0x68eb803a,0xdce40503}}, // _হয়েছ, _प्रॉ, _negd, _olič,
+ {{0xead493cd,0x656280f1,0x7989e0cd,0xc2f380ab}}, // поль, rkoh, dnew, চ্ছি_,
+ {{0x47c30698,0x65628100,0x69c302be,0x9c8305b9}}, // _обяв, skoh, _éner, _účel,
+ {{0xdce4026c,0xe1ef80f7,0x291d838a,0xf96a802e}}, // _alič, نسي_, _bbwa_, ирий_,
+ {{0xdced1c67,0x387f00d2,0x6b9c2126,0xf99f01e8}}, // _imać, _žuri_, horg, _xkè_,
+ {{0x6da390ee,0x6b9c60ce,0x38603798,0xe5a395e0}}, // _цита, korg, nyir_, _цити,
+ {{0x25620125,0x636b5a0b,0xdea38326,0x25a6e0cf}}, // _fólk_, _münc, _hanƙ, llol_,
+ {{0xe8a715fb,0x799b838e,0xdce43a98,0xe80c0ad5}}, // _खर्च, bouw, _amiđ, िंडा_,
+ {{0x69dae0d0,0x316d8036,0xd1308180,0xe45f0198}}, // _itte, chez_, _سمت_, työ_,
+ {{0xd5b802c7,0xbcfb0019,0x6b9c60d1,0x1ae20264}}, // юсь_, bkén, forg, _বাড়া,
+ {{0xa3b08063,0x7980914c,0x39468144,0xdea380fc}}, // _टीम_, limw, _hnos_, _lanƙ,
+ {{0x2d878019,0x0eba8084,0xda34014c,0x7ed39ef5}}, // ének_, _туды_, меры, _غزوا,
+ {{0x3495177f,0x6608dc52,0xd5cf86ae,0x26c701d6}}, // _найр, mudk, _संतज, ľnom_,
+ {{0x6b9c0bcb,0xb4d70074,0x656f00f7,0xc6f909c7}}, // [5c50] borg, िडी_, mhch, йнах_,
+ {{0xd6db11b1,0x672882fd,0x69daa3e7,0x6b9c047f}}, // рта_, nedj, _otte, corg,
+ {{0xcb1204de,0xdea38326,0x79808a5a,0x6569aed5}}, // _אלי_, _banƙ, kimw, _pleh,
+ {{0x8d74915f,0xa6e300ab,0xf653825f,0x7bcd05ee}}, // وانا, _পাওয়, וצע_, _kuau,
+ {{0x69da8a97,0x636b0214,0xa91d8390,0x67289e2b}}, // _atte, _günc, niže, kedj,
+ {{0x3946956b,0x6728856f,0x3c1e80eb,0xab29813a}}, // _anos_, jedj, dāvā_, сока_,
+ {{0xdce4003a,0x66088057,0x1df801bb,0x7afa007b}}, // _slič, judk, _меры_, ótti,
+ {{0x657b823d,0x6b9c0a0f,0x3f8160d2,0x32078690}}, // _umuh, zorg, lihu_, runy_,
+ {{0x4439e0d3,0x02a813d9,0x69da97ab,0xa91d8fda}}, // _és_, _गर्न, _ette, jiže,
+ {{0x3ced81e9,0x443fd3a1,0x7989e0d4,0x316d8019}}, // _heev_, _iru_, snew, shez_,
+ {{0x637029ed,0x443f826f,0x6da61631,0x6b9c10b9}}, // _häng, _hru_, зина, vorg,
+ {{0x2d9d809a,0x3f810267,0x69ca0187,0x7bc500fc}}, // nowe_, hihu_, _éfei, _aihu,
+ {{0x6b9c60d5,0xdce40904,0x7bc5376a,0x160d00d4}}, // torg, _ulič, _bihu, ांडर_,
+ {{0x637042ec,0x69da8448,0x26c70024,0x2d8b317f}}, // _mäng, _ytte, žnoj_, ance_,
+ {{0x637029ed,0x7bc50057,0x2d9de0d6,0xae1f8424}}, // _läng, _dihu, kowe_, _बहिन_,
+ {{0x3946160a,0x9f471dc1,0x443f815e,0x29d1877f}}, // ños_, čný_, _oru_, _máa_,
+ {{0x2d8260d7,0x2d9d809a,0x443f84b7,0x20092484}}, // [5c60] mike_, dowe_, _nru_, guai_,
+ {{0x2d8260d8,0x161f98e6,0x7bc560d9,0x7c244f67}}, // like_, _बहार_, _gihu, _ipir,
+ {{0x443fe0da,0x7bdb8365,0x3860036e,0x38660bda}}, // _aru_, _atuu, syir_, šor_,
+ {{0x3ced8069,0xed59801b,0xe80c01a2,0x200900ee}}, // _ceev_, _což_, िंदा_, buai_,
+ {{0xe8030063,0xdce60029,0x68e460db,0x3ced8282}}, // _लिया_, dokļ, mbid, _deev_,
+ {{0x27e0059c,0xdb1a84c3,0x88bd809a,0x68e4011e}}, // mpin_, _bitá, reśl, lbid,
+ {{0x443f87ca,0xfce6842f,0x2d9d9111,0xed59826c}}, // _eru_, _ново, bowe_, _fož_,
+ {{0x63701bc0,0x636b0201,0x68e460dc,0x443f84e1}}, // _händ, _müna, nbid, _fru_,
+ {{0x63700004,0x69c63fae,0x16040054,0x68e460dd}}, // _känd, _mike, शंकर_, ibid,
+ {{0x29d1ca34,0x69c660de,0x644098d5,0x138a81a8}}, // _fáa_, _like, _irmi, _اخرى_,
+ {{0x69c6025d,0xaadb8051,0x2d821cb5,0x672884a8}}, // _oike, _החבר, fike_, redj,
+ {{0x394684c9,0x63701bc0,0x2c208063,0x6440905e}}, // _unos_, _länd, _यहां_, _krmi,
+ {{0x98d2819d,0x63700338,0x9f520216,0x2b5a0580}}, // _dịtụ, _oänd, nuyó_, _bopc_,
+ {{0x69c65559,0xddda8019,0x7fb8803d,0x395906ab}}, // _aike, sztő, _بهتر_, _soss_,
+ {{0x395901e0,0x99670254,0x321802c4,0x7c2403ed}}, // _poss_, птал, gtry_, _epir,
+ {{0x3f9e9cf2,0x200907d5,0x44249b6c,0x26ca04be}}, // lotu_, tuai_, _opm_, _agbo_,
+ {{0xa2bcaa54,0x69c6059c,0x3015014c,0x3f815d5e}}, // [5c70] षेत्, _dike, ьдар, tihu_,
+ {{0x3ced8069,0x3f9e81a9,0x29038084,0x69c622d4}}, // _seev_, notu_, ėjai_, _eike,
+ {{0x7bdb81d8,0x20090393,0x6440e0df,0x63703c40}}, // _stuu, suai_, _armi, _säng,
+ {{0x443fd572,0x644080b9,0x26ca45e1,0x3f9e84e8}}, // _pru_, _brmi, _egbo_, hotu_,
+ {{0x98ab0012,0x2d9dd82f,0xa15602f6,0x240980e8}}, // _dacă_, rowe_, _חברה_, інки_,
+ {{0x7c948077,0x656d60e0,0x2d9d809a,0x8c4299a5}}, // رشنا, _llah, sowe_, теше,
+ {{0x656d08b3,0x3ced81e9,0x98ab0087,0x2d9d8035}}, // _olah, _teev_, _facă_, powe_,
+ {{0x636f8bc5,0x443fe0e1,0x9f520216,0x798d60e2}}, // _sønd, _tru_, buyó_, nnaw,
+ {{0x443fb5d1,0x2d820052,0x63700364,0x6d499995}}, // _uru_, vike_, _häne, _inea,
+ {{0x656d02e7,0x798d27e4,0xd00e853d,0x9e65d3c2}}, // _alah, hnaw, گلی_, двод,
+ {{0x442f831d,0x656d04e8,0xa2a48105,0x2004ade6}}, // lwg_, _blah, _चुप्, armi_,
+ {{0x44323504,0x6d5b8036,0xd00e9190,0xb33c8372}}, // _psy_, _joua, دلی_, _ikħa,
+ {{0xef0e964f,0x2eaa0a0d,0x2d822397,0xe6aa0a0d}}, // _им_, _कर्त, rike_, _कर्ज,
+ {{0x7c248086,0x69c660e3,0x366994d6,0x8e858abe}}, // çiri, _sike, _мало_, _огле,
+ {{0x69c63307,0x6fd580c8,0xfaa32dd9,0xbcfb3b7a}}, // _pike, _দিয়ে, варо, rhér,
+ {{0x6b83e0e4,0x140d8996,0x6370016d,0x683825b3}}, // hing, _समूह_, _sänd, júdá,
+ {{0xb4ea0128,0x321814e3,0xbcfb0036,0x6e2901a1}}, // [5c80] यरी_, stry_, phér, _ćeba,
+ {{0x69c61af7,0x6b83e0e5,0x68e423f8,0x656d0824}}, // _wike, jing, sbid, _zlah,
+ {{0x6b83b26a,0x2fc7aeb7,0x637004fd,0x4424af19}}, // ding, _ming_, _vänd, _spm_,
+ {{0xc486be80,0x9f520661,0x973c8669,0x27e00e20}}, // млек, tuyó_, moće, ppin_,
+ {{0xd24f8077,0x6370016d,0x6b83e0e6,0x69d781df}}, // ینه_, _tänd, fing, íxen,
+ {{0x6b83e0e7,0x2fc7e0e8,0x06e800ab,0x7c2dacfa}}, // ging, _ning_, _পাকি, çara,
+ {{0x3f9e803a,0x2eee9142,0xb87b0118,0x7f5c1243}}, // votu_, _reff_, _feís, _iorq,
+ {{0x3834b1db,0xed439285,0x2fc7808e,0x7f5c471b}}, // енир, _инсп, _aing_, _horq,
+ {{0x4424810a,0x26ca812b,0xc2f400ab,0xdce60084}}, // _upm_, žbom_, _জানি_, likė,
+ {{0x6b83e0e9,0xf41400c8,0x2fc786cb,0x69de0904}}, // cing, িবার_, _cing_, _otpe,
+ {{0xe0da867c,0x2fc7b4da,0x3f9ed6bd,0xc9568009}}, // _две_, _ding_, rotu_, _отды,
+ {{0x7bc894ff,0xc7c880ff,0x1bf58074,0x7afe07b6}}, // _hidu, _lối_, ुंचल_, _adpt,
+ {{0x7bc8e0ea,0x9f40009f,0xb87b0019,0xe73a846e}}, // _kidu, nsió_, _leír, _мед_,
+ {{0xb4af2b2d,0x2fc7a5e5,0xa91d80c3,0x56949a1a}}, // _करी_, _ging_, miža, _палт,
+ {{0xc7c8801c,0x3d0f8105,0xa2a480bc,0x7bc8e0eb}}, // _nối_, _डाले_, _चुम्, _midu,
+ {{0x6b839f12,0x69da0013,0x2fc7801c,0x798d4044}}, // zing, ítea, _zing_, unaw,
+ {{0x46d2800f,0xfe7000d7,0x442f822c,0xe50d82f1}}, // [5c90] _सलाह, یدم_, xwg_, हलसि_,
+ {{0x6d49a4db,0x673a816d,0xc7c880ff,0x63620187}}, // _snea, ndtj, _bối_, _bônu,
+ {{0xdb1a8364,0x6b83e0ec,0xc33203de,0xfc3f0216}}, // _mitä, ving, _גוי_, lví_,
+ {{0xceb3812a,0x46df000d,0x6b83e0ed,0xa4d580e8}}, // דיע_, _प्रह, wing, ході,
+ {{0xd0068b76,0xa91d82a5,0x7bc8e0ee,0x7f5c1af9}}, // _حل_, jiža, _bidu, _forq,
+ {{0x6738882c,0x442f8428,0xe1f0081b,0xc5f88ec3}}, // _navj, rwg_, _حسی_, ksē_,
+ {{0xc6a40c6e,0x7bc8e0ef,0x4ea4014c,0x21395fcc}}, // _арти, _didu, _арта, _kash_,
+ {{0x2fc78288,0x6b83828d,0x261883d3,0xb4af016f}}, // _sing_, sing, déo_, _करू_,
+ {{0x7bc8ad08,0x6b8385d1,0xed59812b,0x160d016f}}, // _fidu, ping, _niži_, ांवर_,
+ {{0x81e500c8,0x636b13d6,0xa3d4a8b3,0x7bc88bb1}}, // _নিন_, _sünn, _संत_, _gidu,
+ {{0xa29f0f1b,0xe61693bf,0x2fc7d55c,0x7af50f3e}}, // _खुल्, ндэ_, _ving_, dazt,
+ {{0x20028084,0xc692007c,0x224600c3,0xa91d8140}}, // škia_, ראט_, _čok_, biža,
+ {{0x2fc7e0f0,0xf53981ac,0x2d8f8c41,0xa2c786ce}}, // _ting_, _mať_, inge_, ाशक्,
+ {{0x2fc78362,0x7af50cdb,0x29038084,0xbcfb04e8}}, // _uing_, gazt, ėjau_, hkéh,
+ {{0x3f859142,0xdce9817f,0xeb971073,0x41b61092}}, // milu_, _aleč, хир_, есат,
+ {{0x673882ce,0xdce98699,0x3f85e0f1,0xc7c880ff}}, // _zavj, _omeđ, lilu_, _rối_,
+ {{0x7f5c10dd,0x752460f2,0xa3cb8c69,0x7af5011b}}, // [5ca0] _porq, _ibiz, _रंग_, bazt,
+ {{0x212db371,0xdce60110,0xe3b2066e,0x31691fa4}}, // heeh_, tikė, _حرب_, akaz_,
+ {{0xe8ac912a,0x7bc88330,0x69d78118,0x69de0687}}, // _चर्च, _ridu, íxel, _utpe,
+ {{0xd91000d5,0x64440406,0xd6e800ab,0x7bc89f61}}, // _خیر_, _krii, _পাওয, _sidu,
+ {{0xd467aa0e,0xf53980e1,0x68f62f3b,0xd90d09a7}}, // нице_, _dať_, mayd, _ذیل_,
+ {{0x9f40009f,0xc7c88028,0x68f62f3b,0x7c3600b9}}, // rsió_, _tối_, layd, _msyr,
+ {{0x9f4007e2,0x7bc89482,0xf9c6aa46,0x3f85811a}}, // ssió_, _vidu, нщин, dilu_,
+ {{0x673882fd,0x64a6bc3d,0xbcfb027f,0xdca6e0f3}}, // _savj, _заба, ckéh, _заби,
+ {{0x59d2101b,0xdb1a825d,0x7bc89a67,0xb4af0740}}, // _दूसर, _pitä, _tidu, _करे_,
+ {{0x261880e7,0x7524413e,0xed598140,0x7bdf816a}}, // téo_, _abiz, _riži_, íque,
+ {{0x7c3600ee,0x64441698,0x00ca917e,0x752405ee}}, // _asyr, _arii, _елек_, _bbiz,
+ {{0xa91d812b,0xfc3f03bb,0xe4a71bc1,0x68f660f4}}, // ližn, tví_, ерго, jayd,
+ {{0x3f85e0f5,0x68f60079,0x69da0019,0xb4fb02f1}}, // bilu_, dayd, íten, ्लौप_,
+ {{0x636b02bb,0xafdb5986,0x75243bb8,0x65b6128a}}, // _günl, _brøn, _ebiz, váhe,
+ {{0xb5a7035f,0x3da71240,0xdcfb8353,0x7af5011e}}, // _прий, _приб, _smuč, razt,
+ {{0x195801e5,0x6444209b,0x68f660f6,0x291202ec}}, // насы_, _frii, gayd, ggya_,
+ {{0x6d4d576a,0x64440915,0x31690326,0x20980fe6}}, // [5cb0] _inaa, _grii, rkaz_, екты_,
+ {{0x442960f7,0x81bc8029,0xa91d8132,0x69b31344}}, // _ipa_, _spēl, jižn, _आठवी,
+ {{0xafdb0b40,0xdce98668,0x21390168,0x6d4d1989}}, // _grøn, _smeđ, _tash_, _knaa,
+ {{0x753d0065,0x3f8580d2,0x80a203bb,0x2d8fe0f8}}, // ndsz, zilu_, _कुवे, unge_,
+ {{0x442960f9,0xb4af3716,0xaacc8d86,0xf0d281bc}}, // _jpa_, _करो_, ाशंक, _rịpọ,
+ {{0xbcfb026f,0x996c81d0,0x395d9b6c,0xb4af03db}}, // skéh, měř_, _sows_, _करै_,
+ {{0x2d878264,0xe29681f3,0x6d4d3ca6,0x533801c6}}, // énes_, каш_, _onaa, גנון_,
+ {{0x200dc395,0x6d4d0365,0xe7a9066f,0x752f066f}}, // tuei_, _nnaa, कतकप, jecz,
+ {{0x442957bb,0x26c701d6,0x63748980,0xa3d5804a}}, // _npa_, ľnou_, _bànd, _розч,
+ {{0x6d4d60fa,0x7c29bc36,0x2003836f,0x753babb7}}, // _anaa, _iper, čji_, _hauz,
+ {{0x4429028a,0x39158676,0x13e68a49,0xfce607c8}}, // _apa_, _амер, _নিয়, коно,
+ {{0x200901e2,0x44290fd2,0xd2568039,0x644460fb}}, // krai_, _bpa_, _בשעה_, _prii,
+ {{0x68e9e0fc,0x200de0fd,0xe72e8195,0x753b8c53}}, // mbed, quei_, _ке_, _mauz,
+ {{0x63a3e0fe,0x7c29d2e3,0x76438573,0x442960ff}}, // nonn, _mper, _trny, _dpa_,
+ {{0x69cba578,0xd2990558,0x561300ab,0xcb1283c8}}, // _kige, етті_, সবুক_, ילם_,
+ {{0x63a3b5ad,0x973c80d2,0x644402f1,0x1c460103}}, // honn, moća, _trii, _аним,
+ {{0x63a3b655,0x2fca0748,0x69cb8c03,0x41d211be}}, // [5cc0] konn, _pibg_, _mige, _दूरस,
+ {{0x69cbe100,0x63a3e101,0x68f6080a,0xafdb004a}}, // _lige, jonn, sayd, _trøn,
+ {{0x7c29bd36,0x81dc00c8,0x63a3e102,0x200912ae}}, // _aper, _ঠিক_, donn, arai_,
+ {{0x69cbe103,0x753b8012,0x598323e7,0x7c868dae}}, // _nige, _cauz, _влюб, куле,
+ {{0xed59811f,0x753b811b,0xf48780d7,0x68e9e104}}, // _nižu_, _dauz, پانی, dbed,
+ {{0x63a38d35,0x752f009a,0x973c8267,0x2d878298}}, // gonn, zecz, koća, éner_,
+ {{0x753b978f,0x69cb820c,0xa91d811a,0xc7c88129}}, // _fauz, _bige, rižn, _mốt_,
+ {{0x673c043d,0x753b811e,0x68e98c30,0xd90e8180}}, // _harj, _gauz, gbed, زیت_,
+ {{0xb42587bd,0x673c20a2,0x61e90042,0x34a78171}}, // _معلو, _karj, ćeli, _авто_,
+ {{0x69cbe105,0x753b803b,0x63a382be,0x442958eb}}, // _eige, _zauz, conn, _rpa_,
+ {{0x752f009a,0x69cbe106,0x3eaa806a,0x69da01d0}}, // tecz, _fige, æbt_, ítel,
+ {{0x7982809a,0x69cb917a,0x2912821e,0xeeeb0129}}, // _umow, _gige, _øya_, _đọng_,
+ {{0x6442a2b9,0x94a8245b,0x61e200d2,0xc4580198}}, // nvoi, втра_, _čola, виях_,
+ {{0xd90e8986,0xa5c7007b,0x69cbcb2f,0x636f84d6}}, // _ایک_, _bjóð, _zige, _lønn,
+ {{0x5fb78051,0xdced6107,0x5ec080ab,0x6b8700f3}}, // _שהוא_, _hlač, _শ্রে, rijg,
+ {{0x63a398e8,0x20096108,0x69cb8079,0x0eeb2a2b}}, // zonn, trai_, _xige, टर्स_,
+ {{0x63a3e109,0x7ff41e95,0x69d8610a,0x25a910e1}}, // [5cd0] yonn, لسلا, _éven, _ikal_,
+ {{0x68e98eef,0x63700006,0x65b6026f,0x20091a06}}, // zbed, _täna, máha, rrai_,
+ {{0x63700c3c,0xf41f025d,0x673c0ad4,0x4fc41170}}, // _känn, ltä_, _darj, рста,
+ {{0x65609523,0x63a3b31c,0x5bcba207,0x63700198}}, // _comh, wonn, ाद्व, _jänn,
+ {{0x637008aa,0x63a3e10b,0x65608068,0x69cbe10c}}, // _männ, tonn, _domh, _rige,
+ {{0x69cb805f,0xf41f0009,0x2d84e10d,0xed59882c}}, // _sige, itä_, _emme_, _rižu_,
+ {{0x63a3e10e,0x69cb8982,0x3869610f,0x261c1099}}, // ronn, _pige, lyar_, cío_,
+ {{0xf2df8028,0x63a3d94c,0x56943eb1,0xdce40904}}, // _đâu_, sonn, _таст, _ilić,
+ {{0x68e9e110,0x60dbe111,0x69cbe112,0x386957f8}}, // rbed, rcum, _vige, nyar_,
+ {{0x2608035a,0x25a9059c,0xc2e900c8,0x68e98c1b}}, // ांची_, _akal_, কৃতি_, sbed,
+ {{0x2f5481e5,0x391592c8,0x8f9c0039,0x25a9008e}}, // ртыс, _مواز, ויזי, _bkal_,
+ {{0xed5a19fe,0x5184a597,0xc7c880ff,0x6b854a30}}, // хом_, _куча, _sốt_, _amhg,
+ {{0x644f8acf,0x973c803a,0xdced09d1,0xa5c7008b}}, // _ácid, moćn, _glač, _sjóð,
+ {{0xdb1a8feb,0x3dcd80ee,0xe0df04be,0x50b8803d}}, // _sitú, _liew_, _afò_, _شدند_,
+ {{0x412a181f,0xafdb0bc5,0xe297182d,0x637002f1}}, // ново_, _drøm, гар_, _fänn,
+ {{0x973c812b,0x261c0c15,0x673c2551,0x213f911f}}, // noćn, vío_, _sarj, nduh_,
+ {{0x38690065,0xc7c88104,0x81bc8029,0x6560832f}}, // [5ce0] gyar_, _tốt_, _spēk, _romh,
+ {{0x6560dc56,0x636f854f,0x261c2509,0xe0df03ec}}, // _somh, _sønn, tío_, _efò_,
+ {{0x673c6113,0x9d468098,0xdb038187,0x69c0e114}}, // _varj, лежд, ponê, ymme,
+ {{0x64428358,0x261c05e4,0x38693384,0xdb03e115}}, // tvoi, río_, byar_, lonè,
+ {{0x673c6116,0x2d9ee117,0x6442a4df,0x3940008e}}, // _tarj, _ítem_, uvoi, mdis_,
+ {{0x6442e118,0x7bd66119,0x3940611a,0x261c016a}}, // rvoi, _muyu, ldis_, pío_,
+ {{0xa3d4931d,0xbf100778,0x6560e11b,0x7af8888b}}, // _सूर_, ालीन_, _tomh, ravt,
+ {{0xdd92850c,0xdced0517,0xf41f0364,0x6e931c12}}, // _اور_, _plač, ytä_, _الفا,
+ {{0xd6d08013,0x0443bd1f,0xd37f012b,0x63748722}}, // يقة_, _летн, šćih_, _cànc,
+ {{0x25a9005f,0x7d04478b,0x65b6611c,0x10370039}}, // _skal_, _adis, váha, נטים_,
+ {{0x4aa41344,0x7bd60326,0x213f80b9,0xb8068beb}}, // _खुलव, _auyu, bduh_, _शिवम_,
+ {{0x1a9b893f,0xf41f0364,0xdced027f,0x7bd60850}}, // _איבע, ttä_, _tlač, _buyu,
+ {{0x637000f2,0x69da8074,0x3940022b,0x7d042d50}}, // _vänn, _पंडी, ddis_, _ddis,
+ {{0x200b8038,0x25a69645,0x25bb0358,0x7bd60457}}, // ácie_, hool_, _whql_, _duyu,
+ {{0xf41f464b,0x25a6e11d,0x35d90105,0x63700198}}, // stä_, kool_, _बूढ़, _tänn,
+ {{0x810f11be,0x3f98022b,0x210f0035,0x65b6008b}}, // िलेख_, _ajru_, िलेश_, páha,
+ {{0x2ef5095d,0xdce40140,0x7bd64240,0xab87157b}}, // [5cf0] изар, _slić, _guyu, _бунк,
+ {{0x7c2d611e,0x26decb95,0x213fdf11,0x3869611f}}, // _ipar, ncto_, zduh_, ryar_,
+ {{0x6dbc0063,0x2cad83ac,0x3869036e,0x63a703ed}}, // _właś, _syed_, syar_, mojn,
+ {{0x4a5a0051,0x8af80201,0x7bd60a76,0x63a700f1}}, // _סדרו, _şəxs, _yuyu, lojn,
+ {{0x78fa004c,0x3dcd887b,0x68ed471f,0x7989ba4e}}, // _בפרו, _view_, mbad, view,
+ {{0x27e91d0f,0xfbd0803d,0x63a700f1,0xab298bba}}, // mpan_, سته_, nojn, тока_,
+ {{0xe0d18013,0x2d8b6120,0x798981b0,0xe4e78d8e}}, // يزة_, kice_, tiew, _бізн,
+ {{0x7c2d6121,0x25a696f0,0xa8059ad8,0x7c3e809a}}, // _opar, cool_, азил, łpra,
+ {{0x2d8b0b91,0x63a76122,0x23a50023,0x26110105}}, // dice_, kojn, ओवाद, दूरी_,
+ {{0x442dbdf3,0x7989802f,0x7c3bc7e7,0x63a70168}}, // _ipe_, siew, _isur, jojn,
+ {{0x201fadc7,0x6449807b,0x2d8b3499,0x63a70168}}, // stui_, _hrei, fice_, dojn,
+ {{0x6da622b7,0xe5a60adb,0xdb038077,0x7bd60590}}, // рима, рими, doné, _puyu,
+ {{0x7d0481a8,0x68ed01b4,0x7d1b8144,0x69d734d3}}, // óisi, dbad, _mcus, _auxe,
+ {{0x442de123,0x63a70ffc,0x27e90420,0xafdb0366}}, // _mpe_, gojn, dpan_, _trøj,
+ {{0x75299f3a,0xdb1a820f,0x0cb2873c,0xdb03e124}}, // _obez, _shtë, _जर्म, goné,
+ {{0x00e635cc,0x2d8b0024,0x68fb8110,0x7bd606cb}}, // ржан, cice_, naud, _tuyu,
+ {{0x68431814,0x69cf100a,0x442d81e9,0x63a70796}}, // [5d00] онта, _dice, _npe_, bojn,
+ {{0xc3fb04de,0x93fb007c,0x39400ce9,0x68ed6125}}, // _שליש, _שליי, sdis_, abad,
+ {{0x442de126,0x7afc6127,0x1fb501bb,0x7c3b81bf}}, // _ape_, mart, сстр, _asur,
+ {{0x6449e128,0xe29b0051,0x7763862f,0x61461878}}, // _brei, _ישיר, _lonx, _тема,
+ {{0x6449e129,0x69c4612a,0x25a6aa38,0x68fbd44c}}, // _crei, mmie, rool_, daud,
+ {{0x64498e5d,0x200de12b,0x69c4612c,0x2d8b612d}}, // _drei, drei_, lmie, zice_,
+ {{0x200d8ab3,0x442de12e,0x63748014,0x6449e12f}}, // erei_, _epe_, _càna, _erei,
+ {{0x644991e6,0x7afc2404,0x79961088,0x200d81ec}}, // _frei, hart, nnyw, frei_,
+ {{0x6449d1a9,0x7afc6130,0xa3b6016f,0x2d8b5de0}}, // _grei, kart, जता_, vice_,
+ {{0xe5680159,0x6d40e131,0x7763862f,0x2d8b6132}}, // _אַ_, _hama, _conx, wice_,
+ {{0x6d40e133,0x2d9e82be,0x7afc6134,0x6370016d}}, // _kama, _êtes_, dart, _vänl,
+ {{0x6d40b7cf,0x7af71820,0x68fb8020,0x6d42e135}}, // _jama, _sext, caud, ndoa,
+ {{0x69cf0021,0x6d40db21,0x69c40038,0x2d8b083d}}, // _rice, _mama, dmie, rice_,
+ {{0x6d40e136,0xc90f159a,0x7afc6137,0x69cf4545}}, // _lama, िल्म_, gart, _sice,
+ {{0xd90f0416,0x63a72ed5,0x69cf5257,0x439413f7}}, // وید_, rojn, _pice, царс,
+ {{0x6d40e138,0x29dc160a,0x63a71001,0x69d7016d}}, // _nama, _día_, sojn, _vuxe,
+ {{0xdefb0364,0x63a72f9f,0x7c2280f1,0x27e9423b}}, // [5d10] вые_, pojn, jtor, upan_,
+ {{0x7afc468a,0x68ed6139,0x7d1b8081,0x69c42581}}, // cart, sbad, _scus, amie,
+ {{0x20028824,0x6d40aa8d,0xd6d80729,0x27e960cc}}, // ških_, _bama, рту_, span_,
+ {{0x6449e13a,0x27e92317,0x2906e13b,0xbd1880e8}}, // _prei, ppan_, _ndoa_, иції_,
+ {{0x8c458bc7,0x92d900c8,0x4439613c,0x8d748065}}, // селе, ারী_, nws_, غاما,
+ {{0x200d9429,0x083a00be,0xe29996ba,0xa03a00be}}, // vrei_, _געשל, лал_, _געשפ,
+ {{0x7c22c4b2,0xf1bf001c,0x6d40e13d,0x27e6833e}}, // ator, _khá_, _fama, _aton_,
+ {{0x6d40e13e,0x7afc613f,0x798d0359,0x27ffe140}}, // _gama, zart, niaw, tsun_,
+ {{0x7c229b7d,0x68fbb014,0x7afc0041,0xb4b615fb}}, // ctor, raud, yart, _झरे_,
+ {{0x8aa7af84,0x6d408431,0xfbd08c2a,0x200dde45}}, // _кред, _zama, _قتل_, rrei_,
+ {{0x6d40b328,0x9a87b99f,0x27ffe141,0xb8dd80ab}}, // _yama, _туал, ssun_, _আল_,
+ {{0xbc1a01e5,0x29dc04c3,0x6d408079,0x200de142}}, // лігі_, _ría_, _xama, prei_,
+ {{0x7afc6143,0x5f0a016f,0x65b60061,0x644b0144}}, // tart, ाणम्_, sáho, _ágil,
+ {{0x3d1a18b8,0x3f8c8699,0xf1bf0032,0x29dc002a}}, // _भावे_, cidu_, _ahá_, _pía_,
+ {{0x7afc016f,0xf2d280be,0x09e6102f,0xa3d483b7}}, // rart, לעם_, собн, _सूई_,
+ {{0x29dc02ba,0xe8030321,0xf1bf0317,0x69c46144}}, // _vía_, _लिखा_, _chá_, umie,
+ {{0x69c41690,0xf1bf0013,0x386de145,0x75d38087}}, // [5d20] rmie, _dhá_, nyer_, _văzu,
+ {{0x7afc1c26,0x29dc2a6c,0x395f8074,0x69c46146}}, // qart, _tía_, hjus_, smie,
+ {{0x6d40e147,0x637000f2,0x671e005e,0x386d80f1}}, // _pama, _länk, _पाठक_, hyer_,
+ {{0x7c22b528,0x6d40b231,0x25bf86e7,0x89360872}}, // ttor, _qama, _chul_, _اعدا,
+ {{0x23658db7,0xcb6aa3e0,0x24588a08,0xd5af9a3c}}, // _bolj_, ладе_, раль_, رفه_,
+ {{0x6d40b957,0x98a20110,0xeb9f006a,0x386d8ec9}}, // _wama, _sakė_, rsøg_, dyer_,
+ {{0x6d40e148,0x3f8c8859,0x39420006,0x27e6e149}}, // _tama, vidu_, _kaks_, _ston_,
+ {{0x7a430065,0xf5968013,0x61ea81ec,0x637005ec}}, // lítá, _الرج, rpfl, _bänk,
+ {{0xdefa81e5,0x61ea8192,0x63700338,0x2bb900c2}}, // лын_, spfl, _vänj, _इठला,
+ {{0x8b658307,0xdce0cbab,0x200ba68c,0x74138b53}}, // _والم, _momč, ácia_, _روما,
+ {{0x3f8ce14a,0x68f98197,0xdb01928a,0x63748a2a}}, // ridu_, _fewd, _uklá, _hàno,
+ {{0x7afa9388,0xa1c68256,0x3f8ce14b,0x5fba98b8}}, // _hett, _убед, sidu_, _उठवल,
+ {{0x443907b3,0x39a10110,0xb87b41e3,0x07a68293}}, // uws_, nėse_, _afín, _ладн,
+ {{0x69dae14c,0xab840171,0x7afab729,0x7a430061}}, // _kute, пуск, _jett, jítá,
+ {{0x7afae14d,0xf1bf0028,0x798d0590,0x7de68b53}}, // _mett, _phá_, tiaw, _استم,
+ {{0x92d900c8,0x69da8542,0xdcfb80d2,0x3f8a09c4}}, // ারে_, _mute, _imuć, _ambu_,
+ {{0xa3d485e8,0x69dabc95,0xc6f959dc,0x798d0573}}, // [5d30] _संग_, _lute, инах_, riaw,
+ {{0x7afa8cde,0x644d0102,0x798d09ca,0x2d990122}}, // _nett, _irai, siaw, lnse_,
+ {{0x58d52f75,0x798d0b99,0x69dae14e,0xdb1a8118}}, // _монт, piaw, _nute, _litó,
+ {{0x644d614f,0x394200b9,0x3f8a2e5e,0x2d993f66}}, // _krai, _gaks_, _embu_, nnse_,
+ {{0x69dad0ad,0x7afa8039,0x2365e150,0x68ff0085}}, // _aute, _bett, _polj_, maqd,
+ {{0x7afa86e3,0x69da93c6,0x661d809a,0xc5fb00a0}}, // _cett, _bute, ński, _معرض_,
+ {{0x7afa9e7c,0xdb1e020f,0x63701af3,0xaca3819d}}, // _dett, _shpë, _sänk, _idọn,
+ {{0x212b9385,0x69da80ad,0x644d6151,0x3fe59594}}, // ých_, _dute, _orai, ожив,
+ {{0xb9018e70,0x7d09b950,0x776701c0,0x657d00f1}}, // _दल_, _ides, _kojx, dhsh,
+ {{0x2d8f81eb,0x443fcce6,0x69dadc03,0x752d29f7}}, // lige_, _isu_, _fute, _abaz,
+ {{0x69da9e5a,0x644d0356,0x3eb9809a,0x752d0197}}, // _gute, _arai, ęsto_, _bbaz,
+ {{0x637004b8,0x2d8fae67,0x7afa8a0f,0x29d1805c}}, // _tänk, nige_, _zett, _išao_,
+ {{0x69da811e,0x7afab4c2,0xbcfb0019,0x637d89c4}}, // _zute, _yett, rkép, _cèng,
+ {{0x2d9903b2,0x752d0102,0x289a807c,0x7ae50197}}, // anse_, _ebaz, _דינא, _cfht,
+ {{0x644d6152,0x27edbe84,0x7d099516,0xaa435d04}}, // _erai, lpen_, _odes, мерл,
+ {{0x644d0b6a,0x443f84be,0x7d09e153,0x752d01bc}}, // _frai, _osu_, _ndes, _gbaz,
+ {{0x2d8fc01c,0x1f661baa,0x6d5629da,0x42d60b73}}, // [5d40] dige_, окам, _inya, _міну,
+ {{0x7d09ba23,0xdb0385e4,0x6d560069,0x47c300e8}}, // _ades, foní, _hnya, дбув,
+ {{0x7afa86be,0x072181c4,0x443fe154,0x7f4382be}}, // _rett, _मानव_, _asu_, _manq,
+ {{0x7afacd4c,0x7bc1e155,0x7a430065,0x6d440102}}, // _sett, _ahlu, sítá, _jaia,
+ {{0x69dae156,0x6d44011e,0x7afae157,0x636b452b}}, // _sute, _maia, _pett, _güns,
+ {{0x69dae158,0xafdb005f,0x09b49b7e,0x290057e2}}, // _pute, _prøv, ुष्य, laia_,
+ {{0x27eda463,0x7e2304bd,0x443fe159,0x26d8007a}}, // epen_, _одрж, _esu_, _igro_,
+ {{0x7afa8dfb,0x2900615a,0x7d0981a1,0x2d8f811f}}, // _wett, naia_, _gdes, cige_,
+ {{0x7f43e15b,0x7afae15c,0xfc03c81b,0xdce98088}}, // _banq, _tett, _опто, _sleć,
+ {{0x6d56615d,0x4426b732,0x69da9917,0xdce9811f}}, // _anya, mto_, _tute, _pleć,
+ {{0x4426a623,0x644d615e,0x657d11c0,0x6d5609ca}}, // lto_, _srai, thsh, _bnya,
+ {{0x4426a060,0x644d4b8f,0x6562ad71,0x6d44615f}}, // oto_, _prai, njoh, _caia,
+ {{0x4426e160,0xf746818b,0x2900011b,0x98b90162}}, // nto_, _медо, daia_, _masă_,
+ {{0x44269820,0x644d02be,0x2d8fb9de,0x657d23ac}}, // ito_, _vrai, zige_, shsh,
+ {{0x4426c182,0x212900dd,0x644d0428,0x236c0106}}, // hto_, tfah_, _wrai, ödja_,
+ {{0x4426e161,0x644d0218,0x6d440102,0x29006162}}, // kto_, _trai, _gaia, gaia_,
+ {{0xd6db0f80,0x47d50013,0x2d8fa799,0x44268038}}, // [5d50] ате_, سيار, vige_, jto_,
+ {{0x44269341,0x2d8f874c,0x27e601a8,0x9f4901fa}}, // dto_, wige_, íona_, gsað_,
+ {{0x2d8f9ab0,0x69da0019,0x636b02af,0x95cb8071}}, // tige_, ítet, _wüns, ауда_,
+ {{0xb23a0158,0xdcef00eb,0x98b90087,0x7bc181e4}}, // _פערז, dicī, _casă_, _shlu,
+ {{0x2d8f8422,0x764e031d,0x4426b2b1,0x61e204a8}}, // rige_, _erby, gto_, _čoli,
+ {{0x2d8f88c9,0xdb0391b9,0x25a68267,0x63858196}}, // sige_, poní, čola_, ягла,
+ {{0x4426e163,0x443f8326,0xef2301a9,0x6440e164}}, // ato_, _wsu_, _ceļš_, _esmi,
+ {{0x443f81c0,0x7d09e165,0x44268079,0x9d189bdc}}, // _tsu_, _udes, bto_, _мост_,
+ {{0x4426961b,0x2d8de166,0x69e18042,0xf8b91305}}, // cto_, _imee_, _šveđ, _आरोप,
+ {{0x27edbe84,0x6d440102,0x63700106,0x68fd5c45}}, // rpen_, _saia, _hänv, _lesd,
+ {{0x2f54835f,0x29d18052,0x6d5600dd,0x27edb7dc}}, // єтьс, _ušao_, _pnya, spen_,
+ {{0xb2ba8039,0xd00e82e3,0xdca60adb,0x7aba81c6}}, // _המער, خلی_, зами, _הצעו,
+ {{0x69c9ad81,0x6d440ece,0xe6b80aed,0x2d8d819d}}, // mmee, _vaia, _अर्ज, _mmee_,
+ {{0xe820800f,0x69c98ebc,0xf99f0032,0x6d4403e4}}, // _ममता_, lmee, _ajè_, _waia,
+ {{0xaacba3e6,0x637d87fa,0x4426b1e3,0xa3d48744}}, // ाधिक, _gène, zto_, _संघ_,
+ {{0x6d561e7f,0xe0da0098,0x44269ba9,0x69c9a757}}, // _unya, сва_, yto_, nmee,
+ {{0x68fd1313,0x44269313,0x38c80077,0x68e46167}}, // [5d60] _desd, xto_, زاری_, rcid,
+ {{0x6f018067,0x68e46168,0xa3bf000d,0x69da03a2}}, // nalc, scid, ेषण_, ítes,
+ {{0x4426dce7,0x2900373d,0x657b8609,0x69c9aeee}}, // wto_, paia_, _bluh, kmee,
+ {{0x44268ca9,0xe45a80e8,0x21a60162,0x6374846d}}, // tto_, йже_, _химм, _gànj,
+ {{0x4426c4b2,0x69de3d95,0x6f01a8f7,0x7afe0084}}, // uto_, _hupe, kalc, _kept,
+ {{0x2b45e169,0x6f018353,0x69de616a,0xdce080eb}}, // _calc_, jalc, _kupe, _domā,
+ {{0x877b0158,0xdb1e04f0,0x6f0182d4,0x69de616b}}, // _פאלי, _hipó, dalc, _jupe,
+ {{0x44268511,0x7afe00d2,0xd90d819f,0x657ba368}}, // pto_, _lept, لیم_, _gluh,
+ {{0x6da3806d,0x6f01e16c,0x7d02e16d,0x63ae017f}}, // _чита, falc, maos, kobn,
+ {{0x43868277,0x25a01af7,0x3ce681d6,0x69c981b4}}, // _علاق, _ajil_, lcov_, amee,
+ {{0x69c98069,0x6283809a,0x69de616e,0xe8b90072}}, // bmee, czno, _nupe, _आर्च,
+ {{0x06970051,0x3ce681ac,0x7d02e16f,0xdced0140}}, // לדים_, ncov_, naos, _mlać,
+ {{0x68fd3e55,0x0cab0098,0x94ab15a6,0x69de0706}}, // _resd, стни_, стна_, _aupe,
+ {{0x9a870dca,0x39469f3a,0x6f01b1b6,0x7f589263}}, // публ, _haos_, calc, _факс_,
+ {{0x27e60307,0x82a46170,0x69de4168,0xa29505a8}}, // íonn_, еште, _cupe, фаві,
+ {{0xdb0a80f2,0x673ae0a2,0x2d9c8019,0x31978087}}, // llfä, letj, ével_, tăzi_,
+ {{0xa3cc85b3,0x40340af0,0x394680d7,0x52d080ab}}, // [5d70] रगा_, нетс, _maos_, _স্মৃ,
+ {{0x656981bf,0xdb039b2c,0xdced09d1,0xb88305b9}}, // _soeh, koná, _blać, vníč,
+ {{0x7bdf0713,0xcb1204de,0xb4bd000d,0x68fd23c1}}, // _huqu, _בלי_, _अरु_, _tesd,
+ {{0x6fd80540,0x660500f1,0x7afe001b,0x8c3d8085}}, // _भूकं, rshk, _zept, _axşa,
+ {{0xd0068060,0x673a8009,0xf8b1004e,0x661a9f38}}, // _جل_, ketj, _شکر_, hutk,
+ {{0x0721ab62,0x8c1c0051,0x661a8359,0x65bd016b}}, // _माधव_, מודי, kutk, kého,
+ {{0x6f018db7,0x661a810b,0x7d0d0214,0x39496171}}, // valc, jutk, _odas, odas_,
+ {{0xeeeb0104,0x7d0d601a,0x7d02b204,0x63700198}}, // _động_, _ndas, caos, _hänt,
+ {{0x62838117,0xfc3086e2,0x69c9a6a1,0xb8e585fc}}, // szno, _محل_, rmee, _एर_,
+ {{0x7d0d547b,0x378980e8,0x673a806a,0x69c9d294}}, // _adas, ібно_, getj, smee,
+ {{0x6da605c2,0x7afe1753,0x63a4d138,0xe5a63714}}, // дина, _rept, éine, дини,
+ {{0xcb09073a,0x7bdf0228,0xed5a34c4,0x823487d2}}, // _כל_, _buqu, цом_, _سرما,
+ {{0x39492af9,0x7bd70118,0x673a928d,0x63ae00e1}}, // ddas_, _bixu, betj, tobn,
+ {{0x7d0d0006,0x39496172,0x661a8057,0x7bd7002a}}, // _edas, edas_, butk, _cixu,
+ {{0x661abc5a,0x7bdd80e5,0x636f9277,0x2327047f}}, // cutk, _èsuf, _høns, ьори_,
+ {{0x291fe173,0x7bd70980,0x636f8366,0x7bdf6174}}, // ngua_, _eixu, _køns, _fuqu,
+ {{0x3ce680e1,0xdb1e023e,0xdced0088,0x6fd8064a}}, // [5d80] vcov_, _dipò, _slać, _भूखं,
+ {{0xdced003a,0x2be1800f,0x64446175,0x636f8aa2}}, // _plać, _पढ़ा, _isii, _møns,
+ {{0xdb1a8061,0x7d02a0f0,0x636f83ba,0xe6bb903e}}, // _kitö, taos, _løns, _उरोज,
+ {{0x6d91816b,0xdb1e0118,0x395902a6,0xa3dd0074}}, // sťan, _tipó, _gnss_, _तूँ_,
+ {{0x7bdf0079,0xe8f70b79,0x3ce6e176,0x394680d7}}, // _xuqu, _юля_, rcov_, _raos_,
+ {{0x3ce6a6b4,0x68f65986,0x7643808e,0x53338226}}, // scov_, lbyd, _osny, вешт,
+ {{0x3946e177,0x63700106,0x673a807a,0xd25081bd}}, // _paos_, _häns, vetj, انب_,
+ {{0xd91083f8,0x637000f2,0x3d1a0441,0x21203e55}}, // ایش_, _käns, _भागे_, ngih_,
+ {{0xb4bd1551,0xa3dd18b8,0x673ab30c,0x63748980}}, // _अरे_, _तूं_, tetj, _làni,
+ {{0x7d00b9e8,0x6370016d,0x661a8ca1,0x3e4c81d0}}, // _hems, _mäns, tutk, _pět_,
+ {{0x63701af3,0x3946e035,0x7643808e,0x6444466a}}, // _läns, _taos_, _csny, _asii,
+ {{0x661aab78,0x7d008088,0x2b5a0162,0x673ae178}}, // rutk, _jems, _anpc_, setj,
+ {{0x29049fb6,0x071e06a7,0x321c80e1,0x68f66179}}, // mama_, _पांव_, luvy_, dbyd,
+ {{0x2904e17a,0x661a956e,0xdb0704e8,0x7d0083ed}}, // lama_, putk, pojí, _lems,
+ {{0xd8488028,0x63701af3,0x03200105,0x64440009}}, // _họ_, _ränt, _बारह_, _esii,
+ {{0xf77003f8,0x21200077,0x7bdf05ee,0x2fd80362}}, // یال_, ggih_, _tuqu, _airg_,
+ {{0xe8940364,0x39493e80,0xb9070fea,0x2486817f}}, // [5d90] вать, rdas_, _पल_, jzom_,
+ {{0x2904e17b,0xd84890ab,0x753d0019,0x3949617c}}, // hama_, _mọ_, lesz, sdas_,
+ {{0x2904bd8b,0xd848877f,0x637000f2,0x68f6010c}}, // kama_, _lọ_, _vänt, bbyd,
+ {{0x29d1827f,0x3eb80370,0x2a7601c6,0xcdd983de}}, // _však_, _dyrt_, _נערך_, פֿער,
+ {{0xd848882e,0xc9160039,0xb60704b7,0xe800801b}}, // _nọ_, _אחרת_, _suġġ, लीमा_,
+ {{0x8c1c007c,0x59bf00d4,0xb87b0032,0x442900ee}}, // קווי, ्षवर, _agíg, _mqa_,
+ {{0x628704b7,0x6f050101,0x442b02f7,0x6d1f05fc}}, // nzjo, mahc, htc_, _भांग_,
+ {{0xd848c462,0x3f9e80eb,0x7dde80eb,0x291f8c93}}, // _bọ_, entu_, _mūsd, ugua_,
+ {{0x4f95a45b,0x442901c5,0x65948f25,0xaca381bc}}, // ерну, _nqa_, _рату, _mpịa,
+ {{0x9e6585d3,0x60c28b80,0x80c481a2,0x6d4ba25b}}, // евод, _dzom, ाइजे, ldga,
+ {{0x7c36617d,0xa3cc8105,0xc7b20039,0xf1db4130}}, // _spyr, रगर_, רבי_, _मंगन,
+ {{0x2904803a,0xf2df8104,0xc3290051,0x656d0388}}, // cama_, _đây_, _לו_, _coah,
+ {{0x61e1a674,0x7c2bbe06,0x636f83ba,0xb4e0064a}}, // _hull, ntgr, _uøns, _तली_,
+ {{0x61e1e17e,0x69c6020f,0x68e9a509,0xbea586d2}}, // _kull, _shke, lced, _палк,
+ {{0x61e190f4,0xfaa30110,0x69d9b0a0,0x6f0500dd}}, // _jull, гаро, _kiwe, dahc,
+ {{0x61e1e17f,0xe9d701cf,0x61430258,0x98a000c3}}, // _mull, екс_, леса, nfić_,
+ {{0x7d0637a5,0x61e18bce,0x68f627a0,0x7d0094cf}}, // [5da0] maks, _lull, rbyd, _rems,
+ {{0x7d064492,0x2904e180,0x6d5be181,0x637000f2}}, // laks, zama_, _anua, _väns,
+ {{0x290488a7,0x61e1c4b2,0x20090057,0xb87b046d}}, // yama_, _null, asai_, _agíd,
+ {{0x84e68221,0x2ac6801b,0x69d9e182,0x32180748}}, // ходж, _líbí_, _niwe, mrry_,
+ {{0xee49801c,0x61e1e183,0x6d498428,0xdb03862c}}, // _lẽ_, _aull, _daea, konä,
+ {{0x53349016,0x7d060406,0x753d009a,0xd848e184}}, // тект, haks, zesz, _rọ_,
+ {{0xd848d3a1,0x2d805711,0x60c28019,0x81e200ab}}, // _sọ_, _olie_, _szom, নীর_,
+ {{0x61e1cc86,0xd6570039,0xaca48135,0x7d06501e}}, // _dull, _אילת_, _arụp, jaks,
+ {{0x62873640,0x64a38190,0x69d9e185,0x753d0019}}, // zzjo, _бата, _diwe, vesz,
+ {{0x61e1870b,0x2904c230,0x75228065,0x6d628024}}, // _full, sama_, lgoz, štač,
+ {{0x61e18e5d,0x3eb895f8,0x9d191240,0xe0da8073}}, // _gull, ært_, монт_, _еве_,
+ {{0x29020025,0x7522a73f,0x7bda815d,0x68e98081}}, // _neka_, ngoz, _hitu, cced,
+ {{0x7bdacc21,0x753d100b,0x61e18a0f,0x79840010}}, // _kitu, resz, _zull, dhiw,
+ {{0x2d801af7,0x69d99f62,0x7bda8041,0x9f400037}}, // _elie_, _ziwe, _jitu, ppiù_,
+ {{0x61e187f4,0x7bdae186,0x6d960029,0x290203df}}, // _xull, _mitu, mšan, _beka_,
+ {{0x661e0057,0xdd9107d2,0xb6bb8039,0x7d066187}}, // dupk, _خوب_, _מצוי, caks,
+ {{0x69cd0bc5,0xe1340162,0x3dc902d5,0x7bdac893}}, // [5db0] rmae, ынты, _ihaw_, _oitu,
+ {{0xf7730b76,0x2009335d,0x6da41487,0xf53f016d}}, // دار_, rsai_, nđan, leå_,
+ {{0x3dc9022c,0xcf9380be,0x661e00dd,0x6d9180e1}}, // _khaw_, נטע_, gupk, sťam,
+ {{0x79846188,0x7bda826b,0x5c759e9d,0x7c2bacfc}}, // chiw, _aitu, клет, ttgr,
+ {{0x61e1ada0,0x04459adf,0x8c45d3c9,0x7bdad621}}, // _sull, телн, теле, _bitu,
+ {{0x7bda8df1,0x6d960353,0x7d065ee9,0x9f9401ec}}, // _citu, jšan, zaks, mäß_,
+ {{0x7bda95ee,0x85bb8bbe,0xfebb026a,0x61e1811c}}, // _ditu, _فارس_, _راست_, _qull,
+ {{0x205591d0,0xb4e01251,0x61e1e189,0x27ef80b9}}, // втор, _तले_, _vull, _ptgn_,
+ {{0xee498104,0x7bdae18a,0xf7718872,0x7d062138}}, // _sẽ_, _fitu, لاد_, vaks,
+ {{0x61e1aa37,0x7bda8959,0x7655009a,0x6d9601a9}}, // _tull, _gitu, _krzy, gšan,
+ {{0x7d060665,0x3ea5cb8d,0xbea58b26,0xab848d15}}, // taks, тинг, танк, _сутк,
+ {{0xa3e64823,0x3dc901e9,0x7bdaba27,0xb90680ab}}, // _बंद_, _chaw_, _zitu, _বড়_,
+ {{0xe8188d38,0x7d060341,0x2902618b,0x394de18c}}, // _दिया_, raks, _reka_, mdes_,
+ {{0xc8838afe,0x394de18d,0x2902618e,0x27e2026c}}, // ığı_, ldes_, _seka_, _rukn_,
+ {{0x7d0636e2,0x79842895,0x7bce0168,0x69dc8061}}, // paks, thiw, rmbu, _éret,
+ {{0x320a1be9,0xaad384c5,0xdb071e1e,0x3cb30870}}, // rsby_, धेयक, rojá, ụzụ_,
+ {{0x6f03859c,0x1b1700c8,0x394de18f,0xdb1a8009}}, // [5dc0] _kenc, দ্ধে_, ides_, _yhtä,
+ {{0x798420b3,0x7d044926,0x1acc80ab,0x2902074b}}, // shiw, _heis, রুয়া, _weka_,
+ {{0x6f03b9fd,0x29026190,0x7d040a4c,0x213f808e}}, // _menc, _teka_, _keis, keuh_,
+ {{0x6b8182af,0x394de191,0xdb0a804a,0x7d041fa4}}, // _allg, jdes_, llfø, _jeis,
+ {{0x3d07853e,0x7bda9bee,0xdb0a85e4,0x394dce2e}}, // हणजे_, _pitu, sofí, ddes_,
+ {{0xf6510019,0x39402fbc,0x7d043660,0x66e60251}}, // جئے_, leis_, _leis, _јова,
+ {{0x7bdae192,0x79a68f9c,0x76550035,0xcb1301c6}}, // _vitu, крие, _grzy, בלו_,
+ {{0x2902808b,0x394de193,0x32538081,0x7bda8db1}}, // úka_, gdes_, рвър, _witu,
+ {{0xf9931a0f,0xa3b22435,0xebe6835f,0x8c43cbfc}}, // ירה_, टका_, _розп, _кете,
+ {{0xd378e194,0x394d8722,0x394001e4,0x6f0396d6}}, // _biće_, ades_, heis_, _cenc,
+ {{0x7d0402af,0x82668077,0x6d96252a,0x6f03e195}}, // _beis, _جهان, ršan, _denc,
+ {{0xac190c8e,0xceb30051,0x7d0411cd,0x614684ae}}, // ному_, יית_, _ceis, _једа,
+ {{0x7d0420a4,0x394001ca,0x6d9600eb,0x2616864a}}, // _deis, deis_, pšan, पूजी_,
+ {{0x394b4105,0x6f0393a9,0xd37881a1,0x7dde81a9}}, // _sacs_, _genc, _fiće_, _bīst,
+ {{0xdcfb803a,0x9e348364,0x2d993b7a,0x7982b97d}}, // _sluč, _сейч, éres_, _klow,
+ {{0x7d0433e0,0x3dc9076d,0xdcfb80c3,0x6f03e196}}, // _geis, _uhaw_, _pluč, _zenc,
+ {{0x2d990665,0x6d5f6197,0x085521f6,0xd5b901a9}}, // [5dd0] mise_, _inqa, ываю, stās_,
+ {{0x76550d38,0x7d046198,0x2d99138e,0x9f4d841c}}, // _przy, _zeis, lise_, mpeã_,
+ {{0x39402509,0x2d996199,0x6d96011f,0xb924019d}}, // beis_, oise_, kšal, _jesụ_,
+ {{0x2d995b35,0x39401820,0xa97900be,0x6d9602d4}}, // nise_, ceis_, _נאָכ, jšal,
+ {{0xa2d58a27,0x394d8711,0x68ed0df6,0xe4ff128a}}, // येन्, vdes_, mcad, _štěň,
+ {{0x2d99585b,0x447c00be,0x61e5005d,0x69dd619a}}, // hise_, ונדע, _kuhl, _hise,
+ {{0x69dd1fce,0x6f03e19b,0xdb07007b,0x61e50009}}, // _kise, _renc, lljó, _juhl,
+ {{0x6f03aeaa,0x394de19c,0x61e527f6,0xca748196}}, // _senc, udes_, _muhl, рушы,
+ {{0x6f0395bd,0x7d044932,0x2d99619d,0x3f858114}}, // _penc, _reis, dise_, thlu_,
+ {{0x69dd0573,0x7d04619e,0xd3788067,0x6d5f01b9}}, // _lise, _seis, _piće_, _anqa,
+ {{0xf7718077,0x69dd3715,0x2d9931ca,0x7d043760}}, // لاگ_, _oise, fise_, _peis,
+ {{0x69dd4b0f,0x78a48b89,0x63ad80f7,0x7de881d0}}, // _nise, _žive, éanf, _těsn,
+ {{0x3940518c,0x7d04619f,0x442f8362,0x1b0680ab}}, // veis_, _veis, ntg_, _রাতে_,
+ {{0xade30af3,0x7d0402af,0x69dd3f66,0x68fb8aa2}}, // गदान_, _weis, _aise, lbud,
+ {{0x7d0461a0,0x69dd61a1,0x81e580ab,0x2d9961a2}}, // _teis, _bise, বীর_, bise_,
+ {{0x644985db,0x2d9961a3,0x69dd44b9,0xdebc0326}}, // _osei, cise_, _cise, _garƙ,
+ {{0x07a60071,0x6e2185a3,0x39401839,0x200d9243}}, // [5de0] гаан, lulb, reis_, nsei_,
+ {{0x44220886,0x69dd14cf,0x7dda801b,0x68ed61a4}}, // muk_, _eise, _půso, acad,
+ {{0x7c3bc395,0x7bde02c4,0x394003cd,0x4b7b03de}}, // _apur, _iipu, peis_, _נאטו,
+ {{0x69dd150f,0xc4868009,0xac0a95fc,0x1a9b03de}}, // _gise, ллек, енда_, _טייע,
+ {{0x4422336e,0x7bde4940,0x68fb9f40,0x6b98bbc8}}, // nuk_, _kipu, dbud, tivg,
+ {{0x66e3108d,0xdee30705,0x7c3b826c,0x2d9927f6}}, // _гора, _гори, _dpur, zise_,
+ {{0x442200d7,0xb4db0722,0x442f8122,0x6374e1a5}}, // huk_, ndàr, atg_, _bànr,
+ {{0x636b0a0b,0x7bde00e4,0x44220dc6,0x661a84e8}}, // _düny, _lipu, kuk_, vrtk,
+ {{0x442261a6,0x7f4e01c0,0xc17304de,0x656081a8}}, // juk_, _kabq, מחה_, _inmh,
+ {{0x2d9961a7,0x4422028a,0x3f9a010b,0x7bde61a8}}, // wise_, duk_, nipu_, _nipu,
+ {{0x7c22e1a9,0x06d780ab,0x2d9961aa,0x63b561ab}}, // luor, _দ্বি, tise_, vozn,
+ {{0x443b010c,0x61e50850,0x44222d93,0x7bde1fa4}}, // _ppq_, _ruhl, fuk_, _aipu,
+ {{0x2d995983,0x69dd61ac,0x7bde0133,0x68e28135}}, // rise_, _rise, _bipu, _igod,
+ {{0x2d9927f6,0x69dd2249,0x09bf06bf,0x7bde0573}}, // sise_, _sise, ्ष्य, _cipu,
+ {{0xf1a70a08,0x7bde08b3,0xf0b08117,0x637d810c}}, // гран, _dipu, _دیکھ, _bènj,
+ {{0x443b008e,0xeeeb00ff,0xd5a48019,0x2e139459}}, // _tpq_, _đống_, _رہ_, _ابوح,
+ {{0x69dd61ad,0x442261ae,0x6d42e1af,0x200f0019}}, // [5df0] _vise, cuk_, deoa, égi_,
+ {{0xe8ff03d3,0x78bd026f,0x7bde61b0,0xe71900f7}}, // _août_, _vysv, _gipu, ئيات_,
+ {{0x7f4e0952,0xdced26d2,0x4fc40150,0xe7ea0035}}, // _dabq, _hlađ, сста, _झूठा_,
+ {{0xe8ff00e7,0xdced012b,0xe8df019d,0x71472306}}, // _coût_, _klađ, _klọb_, _схож,
+ {{0x27e6e1b1,0xab29a481,0x7c228037,0x2329867c}}, // _nuon_, вола_, guor, воли_,
+ {{0xdced003a,0xe2999c79,0x3f83012b,0xaa1584bd}}, // _mlađ, кал_, _ulju_, афиј,
+ {{0x442233bc,0x442fdd86,0x25a90144,0x45d5862c}}, // zuk_, stg_, _ojal_, _вовс,
+ {{0x44220967,0x27e6d1e9,0x200d8074,0xe8ff00e7}}, // yuk_, _buon_, tsei_, _goût_,
+ {{0x77858049,0x7c8790ef,0x64498364,0x68fb9277}}, // илиз, _суве, _usei, rbud,
+ {{0x7d0be1b2,0x6c848013,0x25a90006,0x47599156}}, // lags, _الكم, _ajal_, трия_,
+ {{0x3915826a,0x8d6604fa,0x7bde365d,0x200d8152}}, // _نواز, авне, _ripu, ssei_,
+ {{0x44221dae,0x38608052,0x7d0b9066,0x200d8162}}, // tuk_, _šire_, nags, psei_,
+ {{0x0c230656,0x6e218420,0x26050072,0x660b8084}}, // омян, sulb, हीती_, _šokė,
+ {{0x68e28e8b,0x44221600,0x6a8647a7,0x7d0b807b}}, // _zgod, ruk_, алга, hags,
+ {{0x44220393,0xe2970037,0xf8bf077f,0xd19600a9}}, // suk_, аар_, _ayé_, ашањ,
+ {{0x44220397,0xd378ac08,0x394f9407,0xe9df1b88}}, // puk_, _mića_, _jags_, _ciú_,
+ {{0xe0da2bd9,0x55bb0051,0x7bde5b92,0x7d0ba1b4}}, // [5e00] тва_, _במקו, _tipu, dags,
+ {{0x6fc200e1,0x7c228198,0x7f4e01b4,0xdced0609}}, // môck, vuor, _qabq, _imaġ,
+ {{0xdd8f0077,0xe9df16a5,0x6f0708ae,0x81ab8264}}, // _روی_, _fiú_, _dejc, _গঠন_,
+ {{0x1636893f,0x2bb4023c,0x26170105,0x3f9a61b3}}, // ינער_, ंकवा, _पटरी_, sipu_,
+ {{0x6c54bbae,0x9346c055,0xd4038a0e,0xcb6aa0bf}}, // склу, инде, оящи, каде_,
+ {{0xd378805c,0xa6ca07b6,0xe45703de,0x5bb7902a}}, // _bića_, _алга_, יינט_, алія_,
+ {{0x80db00ab,0x6d960390,0xe81e06f0,0x7d0bb88d}}, // যুদ্, ršaj, यंका_, bags,
+ {{0x26ca0904,0x39524cfa,0x316d83ed,0x6379026b}}, // _izbo_, ndys_, gjez_, _hìnt,
+ {{0xab668389,0x394fa3fe,0x2cbf9341,0x7c2280e5}}, // _квал, _dags_, _gyud_, quor,
+ {{0x10370039,0x1be10fce,0xd3788088,0x25a91388}}, // סטים_, _गूगल_, _fića_, _sjal_,
+ {{0xdceb2cf7,0xdced192c,0x628e0bcf,0x9a6a8250}}, // šiča, _slađ, jzbo, _جمال_,
+ {{0x63b8e1b4,0xd5b80cde,0x68e2ab09,0xd6d78084}}, // lovn, ися_, _ugod, шты_,
+ {{0x28d28e70,0x0f1100ab,0x7d160114,0x8fa682ac}}, // देशि, স্টঃ_, _ddys, _каде,
+ {{0x2cad04b7,0x798600fc,0xa3cc864a,0x61e881f6}}, // ħed_, _alkw, रगट_, _kudl,
+ {{0xb3458073,0xdce0801b,0xda780081,0xdcef89d1}}, // leçã, _pomě, аят_, _šeče,
+ {{0x7de18029,0x63b887dd,0x3f986033,0x61e88197}}, // _vēst, hovn, _amru_, _mudl,
+ {{0x63b88ee1,0x0f150221,0x6b9c4165,0x26c5368d}}, // [5e10] kovn, ймаю, birg, _šlo_,
+ {{0x7d1d8117,0xeeeb0104,0x644d61b5,0x5eab00c8}}, // _össz, _đồng_, _isai, _করবে,
+ {{0xd6db0698,0x7d0b9614,0xdfd80098,0x6f07128a}}, // тта_, tags, рът_, _vejc,
+ {{0xe945003d,0x25a6e1b6,0xb6d980be,0x3ed983de}}, // _اردی, gnol_, אַנט, אַנא,
+ {{0xe81e0ee6,0xa3ae15bc,0xf0d281bc,0x7d0b90b9}}, // _पिया_, _कदम_, _dịkọ, rags,
+ {{0x7f850013,0xd3788024,0x63b8807a,0xdb0380e7}}, // _السن, _pića_, govn, onné,
+ {{0x752d002e,0x7989805d,0x7d0b97ea,0x799b808e}}, // _ocaz, thew, pags, tiuw,
+ {{0xe81889a3,0x2129078a,0x78a486c2,0x016580a9}}, // _दिशा_, ngah_, _živa, скио,
+ {{0xa3b7131d,0x2d9d8435,0x628e00b9,0x79898a53}}, // जवा_, miwe_, zzbo, rhew,
+ {{0x63b88a56,0x23748013,0x2d9daeed,0xab748013}}, // covn, _والح, liwe_, _والإ,
+ {{0xd90f8986,0x7d098359,0x1d0a1354,0x394f86f3}}, // _ہیں_, _kees, лени_, _tags_,
+ {{0x645b9384,0x443f861b,0x7d099572,0xaca380ff}}, // _krui, _kpu_, _jees, _ngừn,
+ {{0x7d0998ce,0x2b1b83bb,0xe739b4fb,0x753b93fa}}, // _mees, _नयाँ_, _бел_, _mbuz,
+ {{0xf1b9e1b7,0x7d099dab,0x443fdf91,0xd8d681c6}}, // loš_, _lees, _mpu_, _פוסט_,
+ {{0xa2bb0fce,0x15f5845b,0x25a6831d,0x2d9db64e}}, // _शुद्, _استح, ynol_, kiwe_,
+ {{0x69cf21d5,0x7d0980eb,0x2d8b026f,0x212937c0}}, // _chce, _nees, chce_, ggah_,
+ {{0xf1df81ce,0xa06701f3,0x6564009c,0x443fdcbb}}, // [5e20] _पूछन, бата_, _inih, _npu_,
+ {{0x753be1b8,0x4b260112,0x6d4661b9,0x9b4612c8}}, // _abuz, _умов, leka, _اندو,
+ {{0x6564003e,0x7d09d045,0xe61a3314,0xf1b98140}}, // _knih, _bees, уде_, koš_,
+ {{0x645b94cc,0x10a61ddf,0x7d098579,0xe818816f}}, // _brui, _гимн, _cees, _दिला_,
+ {{0x29000f84,0x63b89fd4,0xe3bf05e4,0x61e88bb1}}, // mbia_, tovn, luña_, _sudl,
+ {{0x645b9581,0x6d4661ba,0xdd92853d,0x3f98017f}}, // _drui, heka, _کور_, _umru_,
+ {{0x3ce98289,0x63b88a56,0x7d09838e,0x443f80ee}}, // žava_, rovn, _fees, _epu_,
+ {{0x6d460025,0x7d09e1bb,0xb3458073,0xf1b98088}}, // jeka, _gees, teçã, goš_,
+ {{0x645be1bc,0x63b8812b,0x61e8b26a,0xc95301c6}}, // _grui, povn, _wudl, למה_,
+ {{0x69c40352,0xb34583a7,0x752d0087,0x61e88ec9}}, // hlie, reçã, _scaz, _tudl,
+ {{0x7d098ef2,0xdceb3c6d,0x69c45896,0x61fc026c}}, // _yees, žičn, klie, jprl,
+ {{0x443f80b9,0x65760144,0x290dd23d,0xd3788140}}, // _ypu_, _coyh, caea_, _kićo_,
+ {{0x4426803b,0x69c461bd,0xb4e9154b,0x7c263327}}, // nuo_, dlie, _बली_, gukr,
+ {{0x2d9de1be,0xd3788390,0xf41f0198,0x69c40901}}, // ziwe_, _mićo_, rrä_, elie,
+ {{0x6d46063d,0x3f9ee1bf,0x60c290e4,0x98b001b9}}, // beka, mitu_, _hyom, _ħaġa_,
+ {{0x3f9eabf8,0x69c40698,0x644d61c0,0x6440808e}}, // litu_, glie, _tsai, _opmi,
+ {{0x644d00db,0x7d0f61c1,0x61e2aab5,0x442681bc}}, // [5e30] _usai, macs, _miol, juo_,
+ {{0x44268d46,0x3f9ede51,0x7d09e1c2,0x32110035}}, // duo_, nitu_, _sees, pszy_,
+ {{0x69c44a3f,0x29000081,0x2d9d8435,0x9f40008b}}, // blie, bbia_, tiwe_, rpið_,
+ {{0xd6d8286e,0x7d0f02a3,0x44268a54,0x05c9090f}}, // сту_, nacs, fuo_, रतिब,
+ {{0x60c28065,0x4426e1c3,0x78a4826f,0x2d9d82a0}}, // _nyom, guo_, _živn, riwe_,
+ {{0x6d460c5a,0x7d0992eb,0xe81e275c,0x2d9dbc07}}, // zeka, _wees, _पिता_, siwe_,
+ {{0x3f9ed0b8,0x2d820bc5,0x290b0b80,0x1dc9016f}}, // ditu_, ykke_, _keca_, रतात,
+ {{0xa3e90935,0x4426b537,0x753ba9da,0x798d0cf4}}, // यदा_, buo_, _ubuz, nhaw,
+ {{0xf1b98b67,0x61e2e1c4,0x44268568,0x52fb001b}}, // roš_, _diol, cuo_, ्राउ_,
+ {{0x3f9e8ee2,0x69c40029,0x6d46497a,0xf1b99f3a}}, // gitu_, zlie, weka, soš_,
+ {{0x27e30586,0x6d462be2,0xa119880b,0x798d4a73}}, // _mijn_, teka, _نقاط_, khaw,
+ {{0x27e300f3,0x69d08424,0x798d01e9,0x2bee081f}}, // _lijn_, _डीपी, jhaw, _इंदू_,
+ {{0x6d463a2f,0x98c78013,0x3f9ec5b0,0xef0ec90d}}, // reka, _اغان, bitu_, _ом_,
+ {{0x7c262414,0x2b47823e,0x3f9eb732,0x29190609}}, // rukr, nenc_, citu_, _adsa_,
+ {{0x44268135,0x7d0f61c5,0x290b1fd6,0x6d461aed}}, // zuo_, bacs, _beca_, peka,
+ {{0xe3bf07f4,0xddc3017a,0x290b1f3a,0xe8fa0f25}}, // ruña_, _общи, _ceca_, рла_,
+ {{0x290b61c6,0x387f89e1,0x386d8118,0xb4c88c28}}, // [5e40] _deca_, nyur_, nxer_, _उरे_,
+ {{0x290002d6,0x69c461c7,0x291900e4,0xbc6a9190}}, // sbia_, slie, _edsa_, یمان_,
+ {{0xdb18820f,0x69c40add,0xfa6a0329,0x290b076b}}, // sovë, plie, _танк_, _feca_,
+ {{0x4426e1c8,0x2e3a80be,0x63bc03d5,0x302a1fb4}}, // tuo_, יגענ, lorn, _комп_,
+ {{0x973c8024,0x27e300f3,0xdb188e93,0x2019816b}}, // liće, _fijn_, lové, ásil_,
+ {{0x44269c8c,0x63bc61c9,0xd7c5016f,0x925a80f7}}, // ruo_, norn, वतःच, لشعر_,
+ {{0x63ad8307,0x973c8668,0xdb18c72f,0x3f9ea46d}}, // éana, niće, nové, vitu_,
+ {{0x27e31dbe,0x63bc0c8d,0xb4e9052a,0x3ea70144}}, // _zijn_, horn, _बले_, únte_,
+ {{0x44268998,0x973c80d2,0xd90d80d7,0x3f9ee1ca}}, // quo_, hiće, ایه_, titu_,
+ {{0x4734867c,0x60c28010,0x3dc6e1cb,0xdfd080f7}}, // чнос, _vyom, llow_, عيد_,
+ {{0x63bc2c22,0x3f9ee1cc,0x973c8289,0xddc90084}}, // dorn, ritu_, jiće, rydž,
+ {{0x973c81dd,0x7d028cfa,0xdb18816b,0x9c1301bc}}, // diće, nbos, dové, _jọgb,
+ {{0x9ccb0084,0x290b61cd,0x160a0072,0xe7ea00c2}}, // рына_, _reca_, हीतर_, _झूला_,
+ {{0x63bc2766,0x3dc6808e,0x798d0362,0xdceb81f4}}, // gorn, hlow_, whaw, _čičo,
+ {{0xd3788d11,0x973c84a8,0x27e35b67,0x442227d1}}, // _moć_, giće, _rijn_, ark_,
+ {{0xdb18816d,0x442201f4,0x3f8a11ec,0xf7709ab3}}, // llvä, brk_, _albu_, _تاچ_,
+ {{0x63bc3dbb,0x27e300f3,0x12e70048,0x290b5ec0}}, // [5e50] born, _pijn_, _мінг, _veca_,
+ {{0xd3788025,0x798d407c,0x973c9487,0x2bd2063a}}, // _noć_, shaw, biće, _दीपा,
+ {{0x78a48db7,0x6e288db7,0xc332010f,0x6d96011f}}, // _živl, nudb, _אוי_, ršav,
+ {{0xf1a71fa9,0x3dc6dc52,0xb8d100ab,0x27e300f3}}, // _фрон, glow_, _ওর_, _wijn_,
+ {{0xdfdb0698,0xe3b004c0,0x6d9602a5,0x661d809a}}, // _във_, _سری_, pšav, ąski,
+ {{0x39495f64,0x9f4300f1,0x8cce0ee6,0x7d0d435c}}, // leas_, _mijë_, _सरसो, _leas,
+ {{0xa91b007b,0xd3788369,0x7d02aa96,0xb5a792c0}}, // _alþi, _doć_, bbos, _драй,
+ {{0x63bc0696,0xdb038e15,0x98af82a5,0x386d8722}}, // zorn, tonó, šače_, txer_,
+ {{0x3dd20114,0x637d80d7,0x973c8bcf,0x7d1b9267}}, // _rhyw_, _bènt, ziće, _idus,
+ {{0x39490cac,0x63bc1042,0xdb038333,0x6e288267}}, // heas_, xorn, ronó, fudb,
+ {{0xa2bb223a,0x98ab83bf,0x6d960668,0x637d810c}}, // _शुल्, ıcı_, kšat, _dènt,
+ {{0x973c803b,0x7d0d61ce,0x7c2480f7,0xdb0381a8}}, // viće, _ceas, áirg, inní,
+ {{0xd1b884c0,0x7d0d0046,0x394961cf,0x7bc70267}}, // _بالا_, _deas, deas_, flju,
+ {{0x27ff8077,0xff5187d2,0xdb18826f,0x973c8289}}, // mpun_, _سخت_, tové, tiće,
+ {{0x7d1b803a,0x63bc61d0,0x67210009,0x69d0864a}}, // _odus, rorn, _öljy, _डीडी,
+ {{0x973c81dd,0x31660b5a,0xdb18816b,0x7d0d49fe}}, // riće, ñoz_, rové, _geas,
+ {{0xa2a28bb8,0xef1a0596,0x27ff9ad4,0x1dc900d4}}, // [5e60] _केन्, йма_, npun_, रतरत,
+ {{0x6d56083a,0x98a68668,0xdb0a816d,0x7d1be1d1}}, // _haya, šiće_, llfö, _adus,
+ {{0x7d0d61d2,0xee3a34d6,0x27ff87d5,0xdd8f803d}}, // _yeas, сне_, hpun_, گون_,
+ {{0x39492483,0x27ff90e1,0x63ad81a8,0xdd9980e8}}, // ceas_, kpun_, éann, оші_,
+ {{0x6d5661d3,0x291261d4,0xdd8f8829,0xd37880c3}}, // _maya, maya_, دون_, _poć_,
+ {{0x6d560587,0x291261d5,0x7d1b8009,0xa3b200d4}}, // _laya, laya_, _edus, टकट_,
+ {{0x61e6050b,0xdb03823e,0x9c13019d,0x6da400c3}}, // _kikl, tonò, _tọgb, nđas,
+ {{0x29122b95,0x7de8800d,0x637d89c4,0x7bc18338}}, // naya_, _měst, _bèns, _eklu,
+ {{0x61e61de9,0x7d0d0039,0xdb03e1d6,0xdcfb85f3}}, // _mikl, _reas, ronò, _sluć,
+ {{0x1b0f8a49,0x29124450,0xdcfb8024,0xe7be0d72}}, // _সাথে_, haya_, _pluć, ्तिप,
+ {{0x6d5631b3,0x7bc7003a,0x64443353,0xb3471243}}, // _baya, vlju, _apii, _liçõ,
+ {{0x29120397,0x6d560079,0x61e651e1,0xb7d78065}}, // jaya_, _caya, _nikl, _ہونا_,
+ {{0x6d56395c,0x6fbe0e1a,0x29121ad4,0x6e28e1d7}}, // _daya, ्तां, daya_, sudb,
+ {{0x7dda801b,0x9f4303ed,0xe3bf0118,0x237a00e5}}, // _zůst, _vijë_, ruño_, _copj_,
+ {{0x7d0d14f2,0x6d408289,0x20070668,0x39490834}}, // _teas, _obma, ćnim_, teas_,
+ {{0x291261d8,0x25a22828,0x5eab00ab,0x31c40073}}, // gaya_, cikl_, _করলে, есув,
+ {{0x394961d9,0x78a4a368,0x61e661da,0xdb1a8298}}, // [5e70] reas_, _živj, _dikl, _aktí,
+ {{0x3949517b,0x44395424,0x442b1498,0x6d565998}}, // seas_, mts_, muc_, _zaya,
+ {{0x291261db,0x6d56534f,0x443913a0,0x394905e4}}, // baya_, _yaya, lts_, peas_,
+ {{0x2912469d,0x7ddf189e,0x210082d4,0x6d563dd5}}, // caya_, _būsi, nčič_, _xaya,
+ {{0x44390b39,0x442b61dc,0x63ada509,0x657d0168}}, // nts_, nuc_, éano, ërhy,
+ {{0x44391098,0x6f1c0118,0x69d8047f,0xbb428081}}, // its_, _gdrc, _èver, вешк,
+ {{0x443961dd,0x6d86002a,0x442b555a,0xdce89238}}, // hts_, _sóam, huc_, _çoğa,
+ {{0x44390029,0xdb1c00f1,0x27ff8359,0x80658ba5}}, // kts_, torë, tpun_, движ,
+ {{0x349489c7,0x27ff8867,0x6d4b8057,0x442b02a5}}, // _затр, upun_, mega, juc_,
+ {{0x291261de,0x27ff87d5,0x442b61df,0x6d598417}}, // zaya_, rpun_, duc_, ldwa,
+ {{0x63a3b3d9,0x291261e0,0x25a20115,0x4439547c}}, // minn, yaya_, tikl_, ets_,
+ {{0x63a3e1e1,0x6d4be1e2,0x7c248024,0x64a601e5}}, // linn, nega, šire, дама,
+ {{0x6d560f03,0x61e661e3,0xe72e846e,0x7c2bb7c0}}, // _vaya, _rikl, _ие_, nugr,
+ {{0x69c9a551,0xa3cd81c4,0x6d4be1e4,0xa3bf81c4}}, // llee, रति_, hega, ीति_,
+ {{0x0caa823c,0x2912336a,0x6d5643b7,0x28e006b7}}, // _कश्म, taya_, _taya, पेनि,
+ {{0x63a3e1e5,0x6d4be1e6,0x69db8216,0x69c9e1e7}}, // hinn, jega, nmue, nlee,
+ {{0xb906053e,0x44390051,0x6d4be1c4,0x63a3e1e8}}, // [5e80] _पण_, cts_, dega, kinn,
+ {{0x291261e9,0x78a48d11,0x63a3e1ea,0x65699ad4}}, // saya_, _živk, jinn, _aneh,
+ {{0x27e7e1eb,0x63a3e1ec,0x69c9cc05,0x27ef8106}}, // _minn_, dinn, klee, _lugn_,
+ {{0x27e7e1ed,0x290fe1ee,0x3ea70013,0x6d4be1ef}}, // _linn_, _nega_, únta_, gega,
+ {{0x6f0e01c5,0x63a3af9c,0x6aa6b4c7,0xc7b98061}}, // _tebc, finn, गप्र, tről_,
+ {{0xf626038c,0x290f8074,0x69c2d947,0x6f1c08ae}}, // _одго, _aega_, _skoe, _udrc,
+ {{0x6d4be1f0,0xd2578a14,0xe5a68b9c,0x13398019}}, // bega, дцы_, _живи, ستوں_,
+ {{0x290f8052,0xd90d80d5,0xdb1c016d,0xaca4819d}}, // _cega_, میم_, llrä, _asụp,
+ {{0x290f99eb,0x274a8009,0x63a38ed4,0x27e78219}}, // _dega_, очно_, binn, _binn_,
+ {{0x27e78cac,0x61fe005c,0xdb150019,0xfc67803d}}, // _cinn_, _otpl, kozá, _تخفی,
+ {{0xd12f8f2f,0x69c9da1a,0xdce08029,0xe5350678}}, // _их_, blee, _tomē, нень,
+ {{0x4426c5e1,0x27e78125,0x38698503,0x443924c0}}, // oro_, _einn_, _šare_, tts_,
+ {{0x20070024,0x27e7821e,0x5c7402c7,0x61fe02d7}}, // ćnik_, _finn_, влят, _atpl,
+ {{0x4426956b,0x443961f1,0xcf2680d7,0xf8ad8019}}, // iro_, rts_, _آرشي, _چکی_,
+ {{0x443961f2,0xeb9180be,0x44268428,0x7d0d81fa}}, // sts_, אָן_, hro_, ðask,
+ {{0xd378811f,0xdce4016b,0x63a3e1f3,0xceb3807c}}, // _dići_, _znič, zinn, _שיק_,
+ {{0x629a00e1,0x777c0168,0x90990081,0x2e388118}}, // [5e90] útor, _borx, чват_, léf_,
+ {{0x69c0e1f4,0x657bba4b,0x2d9f80b9,0x6d4b8365}}, // home, _souh, _smue_, wega,
+ {{0x69c0b063,0x63a3d47e,0x4426a752,0x6d4b9044}}, // kome, vinn, ero_, tega,
+ {{0x63a39c48,0x4426e1f5,0x69c08035,0xdb1c03c9}}, // winn, fro_, jome, voré,
+ {{0x69c0e1f6,0x290f9686,0x777c03a8,0x69c981ed}}, // dome, _rega_, _forx, vlee,
+ {{0x290fdcca,0x42558767,0x6d4bc721,0x7c39951e}}, // _sega_, етит, sega, rtwr,
+ {{0x63a3d245,0x4426ade6,0x6d4b8359,0x290f84f0}}, // rinn, aro_, pega, _pega_,
+ {{0x27e7e1f7,0x3cf90035,0x6569b553,0x973c8140}}, // _sinn_, ंडों_, _uneh, vića,
+ {{0x69c9c0f1,0x20198019,0xb7bd802e,0x63a392a5}}, // rlee, ési_, luţi, pinn,
+ {{0x2007005c,0x290f81bc,0x69c9caad,0x39591690}}, // ćnih_, _wega_, slee, _lass_,
+ {{0x290f8db7,0x27e79bb8,0xaa9580f7,0xd13181a8}}, // _tega_, _vinn_, _ثلاث, _مما_,
+ {{0x69c0a5a7,0xdb0a003e,0xceb202f6,0x6a538201}}, // come, čník, ריט_, _nəfə,
+ {{0x394d8006,0xd378817f,0x27e7e1f8,0xa2838019}}, // mees_, _sići_, _tinn_, _سیکو,
+ {{0x33d50d13,0xe51f81a2,0x394d81b0,0x399183a8}}, // віст, मृति_, lees_, _dáse_,
+ {{0x6da6117e,0x1a6602e3,0x3243062c,0xfb8501e2}}, // _пика, ریحی_, лерг, тыўн,
+ {{0x394dca3f,0x7989b6ba,0xdb152792,0x4426e1f9}}, // nees_, nkew, rozá, yro_,
+ {{0x3959034a,0xf992893f,0x1f660470,0xe3b207d2}}, // [5ea0] _dass_, ארן_, нкам, _مرد_,
+ {{0xdb18e1fa,0x44268073,0xdc3b0039,0x69c093fa}}, // toví, vro_, _צעיר, zome,
+ {{0x7dca808b,0x3959048d,0x69c0e1fb,0x61fe1be9}}, // lýsi, _fass_, yome, _utpl,
+ {{0x4426dd43,0x395914e0,0xf6510019,0x69c08118}}, // tro_, _gass_, گئے_, xome,
+ {{0x4426e1fc,0x69c08823,0xdceb011f,0x7dd60118}}, // uro_, vome, šića, cáse,
+ {{0xdb18801b,0x69c0d2ea,0xb7bd8087,0x9f478168}}, // poví, wome, buţi, _dinë_,
+ {{0x1c1c823c,0x394d867f,0xc33303de,0x60e58085}}, // _निकल_, fees_, אוו_, ərmə,
+ {{0xd25981e2,0x394006ab,0xdb03810c,0x2b5a1670}}, // ьці_, nfis_, hinè, _lapc_,
+ {{0xdcef803a,0xcfa68758,0xf9930039,0xd84e81bc}}, // _šeće, ешни, טרה_, _gọlf_,
+ {{0x69c0a892,0x25a6d340,0x6d444689,0x661c08ae}}, // some, liol_, _abia, _kvrk,
+ {{0x69c086d4,0x6456178f,0x394da79e,0x6d44026c}}, // pome, _asyi, bees_, _bbia,
+ {{0xceb3004c,0x394dbd3e,0x25a6c03e,0xac19054c}}, // טית_, cees_, niol_, мому_,
+ {{0xa3bd109b,0x6fcb0192,0xdb1c0428,0x395961fd}}, // इकल_, büch, gorï, _rass_,
+ {{0x6fcb02af,0x6a538201,0x25a68114,0x69c6000b}}, // rück, _səfə, hiol_, _akke,
+ {{0x2d9903d3,0x1c1d0074,0x7ddf0110,0xdb0a9727}}, // ères_, _भटकल_, _būst, lofó,
+ {{0x6298803b,0xdb038168,0xf1a40a4c,0x2b5a07f1}}, // izvo, tinë, грун, _eapc_,
+ {{0x656d2dde,0x25a6831d,0x6d4f178f,0x2bd2009a}}, // [5eb0] _inah, diol_, meca, _दीवा,
+ {{0x7dd611a9,0x69c60125,0x6d4f0063,0x6d5d2c15}}, // ráse, _ekke, leca, ldsa,
+ {{0x1b0f80ab,0x765a01ac,0xf770045b,0x7c2f3cfe}}, // _সালে_, _štyr, حاق_, lucr,
+ {{0xe821823c,0x6d5d0d1a,0x6d4f4fd9,0x63a72ea4}}, // _मिला_, ndsa, neca, lijn,
+ {{0x76550d38,0xf8d50105,0xbde5826b,0x2fd900f7}}, // _wszy, _दरिय, _abèé, _يوجد_,
+ {{0xfaa59986,0xe61361fe,0x63a74171,0x394d81b0}}, // кало, _مشر_, nijn, wees_,
+ {{0x25a682e6,0xbd0580e7,0x69cd0114,0x41b59155}}, // biol_, _créé, olae, ксит,
+ {{0x6d4f02fd,0x63a71696,0x290901b4,0x25a6e1ff}}, // jeca, hijn, nbaa_, ciol_,
+ {{0xdd948196,0x6d4f3e1e,0xc8db8361,0x6594802e}}, // _саты, deca, मेंट, _сату,
+ {{0xfd4d8028,0x656d6200,0xd378825b,0x66e581a4}}, // _ngoạ, _anah, _biću_, копа,
+ {{0x9f47801b,0x63a7016b,0x6d5be201,0x6fcb01ec}}, // _jiné_, dijn, _kaua, rüch,
+ {{0xe5a61e25,0x7bc3e202,0xdb03c533,0xb33c81b9}}, // тими, lonu, diné, _imħa,
+ {{0x6d5b80f6,0x399a8158,0x69cd031d,0x1fb58b5b}}, // _maua, _קינד, dlae, _искр,
+ {{0x6d5be203,0xd37881f4,0x7bc3e204,0x63a75c9a}}, // _laua, _fiću_, nonu, gijn,
+ {{0xc3328bea,0x442fe205,0xdb18e206,0x6d4f347d}}, // יום_, hug_, nová, beca,
+ {{0x38608503,0x3a25008b,0x64499ea8,0xdb1c34cd}}, // _širi_, álpa_, _opei, morí,
+ {{0x61ebe207,0x09e3176e,0xdb1883fb,0xdb1c386d}}, // [5ec0] _migl, ротн, hová, lorí,
+ {{0x38601b01,0xcfb500c8,0xe0d71a12,0xdb18801b}}, // rvir_, _জীবন, тву_, ková,
+ {{0xe9da04ae,0xdb1c157a,0x1fb50615,0xe3b223f7}}, // _око_, norí, устр, _مرگ_,
+ {{0x5bcaa38c,0xdb1883fb,0x2d800036,0x8ab701c6}}, // ित्व, dová, _joie_, _יהיו_,
+ {{0x7bc3b349,0x25a68114,0x6449802a,0x6d5be208}}, // fonu, riol_, _cpei, _daua,
+ {{0xdceb1123,0xd7030098,0x661aa7d1,0x7bc3c480}}, // žićn, _взри, ystk, gonu,
+ {{0x5ee000c8,0x61ebe209,0xa6e000ab,0xdb1c16f3}}, // _প্রে, _bigl, _প্রয়, jorí,
+ {{0x442f833e,0xdb1c620a,0x69d9831d,0x2fc78637}}, // bug_, dorí, _chwe, _akng_,
+ {{0x6d4f00d2,0x61eb80d2,0xdb348bca,0xbcfb0036}}, // veca, _digl, _مذاک, ndée,
+ {{0x656d016b,0x81d800ab,0xd3788390,0x2d92620b}}, // _snah, াদি_, _piću_, _alye_,
+ {{0xdb1c160a,0x7f5c3274,0x6d4f620c,0x61eb8098}}, // gorí, _marq, teca, _figl,
+ {{0x891a87d2,0x661ae20d,0x63a7620e,0x61eb81e8}}, // _آغاز_, rstk, wijn, _gigl,
+ {{0x2019800d,0x6dbc94ce,0xae068cfd,0x5f76803d}}, // ásit_, mčad, वीजन_, _لاغر,
+ {{0x8fa39056,0xdb0700f1,0xdb03a538,0x2d9202d6}}, // _каче, mijë, tiné, _elye_,
+ {{0x248d1874,0x7c2f347a,0x442f81ec,0x63a74502}}, // šem_, sucr, zug_, rijn,
+ {{0x7bc3e20f,0x6dbc825b,0x656d6210,0xe5c400e8}}, // zonu, nčad, _unah, _всьо,
+ {{0x98a20201,0x3dc9049f,0x7bc38059,0xdb18801b}}, // [5ed0] _bakı_, _ikaw_, yonu, zová,
+ {{0x2fc75871,0x3b5512b2,0x24840198,0x7f5c6211}}, // ông_, _скор, ämme_, _carq,
+ {{0xa2a2835a,0xd00684c0,0x6449e212,0xcf9380be}}, // _केल्, _گل_, _spei, סטע_,
+ {{0xdb1881d0,0x7bc8810c,0x6dbc81a1,0x442fe213}}, // vová, _akdu, jčad, tug_,
+ {{0xd006803f,0x7c24803b,0xa3b80077,0xfc3f6214}}, // _دل_, širn, _سایر_, ntí_,
+ {{0x442f8166,0x61ebe215,0xdb1c0693,0xdb189be6}}, // rug_, _sigl, yorí, tová,
+ {{0x442f8aff,0x05bb8c48,0x61eb80e5,0xa9a5a81c}}, // sug_, _قدرت_, _pigl, ликд,
+ {{0xdb18800d,0x2fc780b9,0x61fe816b,0x386681a3}}, // rová, _skng_, ípla, _bror_,
+ {{0xdb1881d0,0x61ebd1ff,0x7bc3e216,0xf94a03de}}, // sová, _vigl, ponu, געפֿ,
+ {{0xdb1c2848,0x2d9900f3,0x68fbd03e,0xdb1881d0}}, // torí, ëren_, scud, pová,
+ {{0x6f1505a4,0x386692bf,0x1bec82f1,0x61ebe217}}, // _mezc, _eror_, _छूटल_, _tigl,
+ {{0x3ce98052,0xed5700e8,0x6b81cb70,0x78b981c0}}, // žavu_, гою_, _holg, _txwv,
+ {{0x711a804c,0xdb1c05e4,0x2d8052c5,0xf7459612}}, // _נוספ, sorí, _voie_, _бело,
+ {{0xf77387bd,0x395faf89,0xe739859d,0x6dbc8b80}}, // _خاص_, ldus_, _чек_, pčag,
+ {{0x395d8282,0x6b81b003,0x442b0635,0x69c41a06}}, // _kaws_, _molg, arc_, toie,
+ {{0x395fd747,0x798d221b,0x7d198035,0x6d4e136f}}, // ndus_, gkaw, jaws, íhač,
+ {{0x7f5c26d5,0xdb018019,0xa3e38327,0x38660174}}, // [5ee0] _parq, _emlé, नगा_, íor_,
+ {{0xe3b21a37,0x7d160079,0xe7370088,0xb8cc02f1}}, // _درب_, _heys, леу_, _कइ_,
+ {{0x11d609a7,0x6fd00338,0x798d0326,0x7c2b8299}}, // _متحد, läck, bkaw, ergr,
+ {{0x7bda80f1,0x7f5c0079,0xa80602d0,0xc51699ea}}, // _shtu, _warq, ttığ, ابات,
+ {{0xd8760250,0x395f831d,0x7f5c00e5,0x7d1610ea}}, // _صاحب, ddus_, _tarq, _meys,
+ {{0x6b818511,0x7c24817f,0x39525778,0x9326803d}}, // _colg, širo, leys_, ارشن,
+ {{0x6b81e218,0x2d9c80e7,0x2611807a,0x3d0b8072}}, // _dolg, èves_, jšo_, ारखे_,
+ {{0x2bdb11bc,0x3160478b,0x395de219,0xa8060214}}, // _मीना, ndiz_, _caws_, ptığ,
+ {{0x6b81adad,0x395d81e9,0xa3c4000d,0x5b368872}}, // _folg, _daws_, एका_, _معار,
+ {{0xa2bf86b7,0xa8570039,0x973c8042,0x75240f3e}}, // _लुक्, נייה_, mićk, _adiz,
+ {{0x9f4a00f7,0xd6d084a3,0x3f6381e5,0x35b5148d}}, // _cibé_, تقد_, стыб, ибор,
+ {{0x3866904a,0xd6d781bb,0x386080d2,0x6d468106}}, // _tror_, ыты_, _širu_, _ökad,
+ {{0x98a38676,0x5b270c73,0xe4a7018b,0x5eab00ab}}, // _лице, льва, урно, _করছে,
+ {{0x7524621a,0x44200110,0xbe8680d5,0xfc3f621b}}, // _ediz, _dvi_, _مجبو, rtí_,
+ {{0xfc3f0626,0xdcfb800d,0x6f1ae21c,0x442020fb}}, // stí_, _souč, matc, _evi_,
+ {{0x19580196,0x7d160bde,0x6f1ae21d,0x6fd04255}}, // ласы_, _geys, latc, läch,
+ {{0x798d621e,0x2d9900f7,0x0f150c4f,0x97152e65}}, // [5ef0] rkaw, mhse_, имаю, имац,
+ {{0x6fd001ec,0x973c826c,0x644d0637,0x7d198122}}, // näch, dićk, _ipai, waws,
+ {{0xb8cc0403,0xb87b07d0,0x941f8085,0x644e007a}}, // _के_, _egíp, ərək_, čniš,
+ {{0x6b818257,0x7bc7621f,0x29f8011f,0xa2bf90f7}}, // _solg, loju, _očaj_, _लुग्,
+ {{0xf41f0364,0x6d5f02a3,0x58870196,0x395d81e9}}, // nsä_, _maqa, _сына, _raws_,
+ {{0x20090393,0x395dabf4,0x6a85853b,0x7bc7252a}}, // mpai_, _saws_, илла, noju,
+ {{0x6b8198db,0x395dbdd7,0x2fc580c9,0x52758012}}, // _volg, _paws_, volg_, рулу,
+ {{0x91fc80eb,0xaca38133,0x395dcbb6,0xd18580e5}}, // _grām, _agụn, _qaws_, рлий,
+ {{0x3f83012b,0x5d8496a5,0x395fd660,0x20096220}}, // _koju_, _القل, rdus_, npai_,
+ {{0x6f1a9502,0x44200267,0xdb1c0298,0x20548081}}, // gatc, _rvi_, korá, стър,
+ {{0x44200025,0x6d5f6221,0x7bc76222,0x3f8328fc}}, // _svi_, _baqa, doju, _moju_,
+ {{0x61ef556e,0x629c0353,0x8afc809a,0x657d0168}}, // _nicl, vzro, _wnęt, gjsh,
+ {{0x6d498013,0x290d8010,0x7d09002a,0x3952062c}}, // _mbea, mbea_, ñesa, veys_,
+ {{0xdee59628,0x66e59ed1,0x6fd000f2,0x61ef41ef}}, // _коли, _кола, räck, _aicl,
+ {{0x6d5f19e7,0x629c007d,0x644d0102,0xf53f006a}}, // _faqa, uzro, _epai, rnår_,
+ {{0xd1321921,0x2d990192,0x6026004a,0x66761190}}, // تمر_, chse_, идба, یدار,
+ {{0x7dd60065,0x3f83011f,0x7bc74b20,0x80dd80d4}}, // [5f00] láso, _boju_, boju, _परफे,
+ {{0xdb1c002a,0x6d49802a,0x753b802e,0x973c8503}}, // borá, _abea, _acuz, vićk,
+ {{0xed5a835f,0x6d5f3a14,0xee3a34d6,0x7641e223}}, // кож_, _yaqa, тне_, ntly,
+ {{0x973c842b,0x6d498362,0x61ef1a1f,0x3998826b}}, // tićk, _cbea, _gicl, _kése_,
+ {{0x61e46224,0x63aae225,0x6d498706,0x3f830091}}, // mmil, tifn, _dbea, _foju_,
+ {{0xbcfb00f7,0xb4d69094,0x7dd60019,0x61e43718}}, // idéa, _हरी_, káso, lmil,
+ {{0x2b4707d9,0x7ddd0019,0xd25080f7,0x443f8609}}, // _önce_, lése, سنة_, _equ_,
+ {{0x61e46226,0x63aa80dd,0x7dd60061,0x39918032}}, // nmil, sifn, dáso, _dáso_,
+ {{0x973c856f,0x7df38087,0x290001ca,0x61e4008b}}, // lići, _măsu, icia_, imil,
+ {{0x2d849645,0x63ad80f7,0x6d5f6227,0x656081a8}}, // _home_, éant, _saqa, _hamh,
+ {{0xa3e3873c,0x290001ac,0x2d84a255,0x61e46228}}, // नगर_, kcia_, _kome_, kmil,
+ {{0x6560b22b,0x61e402a5,0x7c2d8042,0x656280f1}}, // _jamh, jmil, šare, ndoh,
+ {{0x64429413,0x7bc72a24,0x2d84812b,0x94a82e72}}, // ntoi, toju, _mome_, атра_,
+ {{0x29003993,0xc0a9845b,0x2d848390,0x2484016d}}, // ecia_, _حامل_, _lome_, ämma_,
+ {{0xf41f464b,0xdced003a,0xf1a700c5,0x79a71287}}, // ssä_, _inač, аран, арае,
+ {{0x2d84e229,0x80dd836d,0x3d10809a,0x973c82a5}}, // _nome_, _परमे, ारों_, dići,
+ {{0x7d1d2551,0xc10400f7,0x6f18822c,0x7bc7622a}}, // [5f10] mass, توقي, _kevc, poju,
+ {{0xe7190013,0xe733045b,0x9951826f,0x68f5801b}}, // ايات_, تصر_, _máš_, ězdi,
+ {{0x212682a5,0x6f188214,0x2d84e22b,0x973c8140}}, // _odoh_, _mevc, _bome_, gići,
+ {{0x29000098,0x2d84cd02,0x7d1d2764,0xd256007c}}, // ccia_, _come_, nass, _פשרה_,
+ {{0x9951803e,0x7dd60019,0x6560a305,0xe81600c2}}, // _náš_, záso, _damh, दीदा_,
+ {{0x2bbb8013,0x7d1d622c,0xdb0aa4df,0x212680d7}}, // خاصة_, hass, nifè, _adoh_,
+ {{0xd6e500c8,0xfee500c8,0x06e500c8,0xe69500f7}}, // _প্রয, _প্রধ, _প্রি, _الأد,
+ {{0xd2b80bea,0x7dd88038,0x78b800d2,0x753b8380}}, // ילות_, písa, šavš, _ucuz,
+ {{0xf8a98077,0x6442e22d,0x60068a14,0x7d1d4c37}}, // دگاه_, ctoi, бным_, dass,
+ {{0x7dd60065,0x29192055,0x25a90859,0x6f1881a1}}, // táso, _lesa_, _amal_, _cevc,
+ {{0x201f8073,0x7ddd0061,0x7d1d622e,0xdce401a1}}, // ssui_, zése, fass, _knić,
+ {{0xceb2010f,0x2919622f,0x7d1d6230,0x442fe231}}, // ליי_, _nesa_, gass, org_,
+ {{0x515a8039,0xa5c701fa,0x7c2902d4,0x7ddd0118}}, // _תכנו, _slóð, šern, xése,
+ {{0x39988118,0x799b8326,0x394b07b6,0x69c4819d}}, // _vése_, lhuw, _abcs_, ịstị_,
+ {{0xe29749a1,0x29196232,0xf8bf16f0,0x6996281c}}, // бар_, _besa_, _axé_, брах,
+ {{0xc2433832,0x7ddd0019,0xe9df0028,0x973c807d}}, // янск, tése, _chú_, vići,
+ {{0xe0da18a0,0x2900368e,0x291903d2,0x4ca600c8}}, // [5f20] ува_, rcia_, _desa_, _গুরু,
+ {{0x6560e233,0xe47b0051,0x2d848051,0x29000098}}, // _samh, _עריכ, _some_, scia_,
+ {{0x44220370,0xc7d68039,0xdb038722,0x2e238214}}, // lsk_, צועי_, dinà, nıf_,
+ {{0x4a5b0051,0xdb1c1b26,0x631480ab,0x291909ca}}, // _עדכו, forç, _সাইট_, _gesa_,
+ {{0x44220d1a,0xfe678077,0x628e6234,0x996c81d0}}, // nsk_, _کد_, lybo, těž_,
+ {{0x6442e235,0xd00f04c1,0x973c8115,0x7d1d0061}}, // rtoi, _سلف_, pići, zass,
+ {{0x2d84803a,0x6442acf0,0x7d046236,0xf3f1e237}}, // _tome_, stoi, _ofis, _dị_,
+ {{0xed5a835f,0x7d1d02df,0x6f1e1c18,0x65608c41}}, // _щоб_, xass, lapc, _uamh,
+ {{0x7d1d5dcb,0x628e016b,0x6f1882d4,0xdb1c0980}}, // vass, hybo, _pevc, corç,
+ {{0x99518a56,0x7d046238,0x7d028098,0x7c2d84a8}}, // _váš_, _afis, lcos, šarc,
+ {{0xf8d5146d,0x59c81a3b,0x63ae01b4,0x7ddd0061}}, // _दर्प, लवार, dibn, vésb,
+ {{0x4424ba4c,0x7d02e239,0x628e623a,0x799b80f3}}, // _kvm_, ncos, dybo, chuw,
+ {{0x29194716,0xdb03b3a7,0x6f1e0019,0x3ced02f1}}, // _resa_, liná, kapc, äev_,
+ {{0x7d043413,0x7c240110,0x29190168,0x7df38087}}, // _efis, _dvir, _sesa_, _păst,
+ {{0x29193b44,0xe9df001c,0x1b1d80ab,0xdb0384e8}}, // _pesa_, _phú_, _নামে_, niná,
+ {{0x7d1d0372,0x61fae23b,0x26dc8858,0x7c228d23}}, // qass, _mutl, _izvo_, ksor,
+ {{0x79860133,0x442480f3,0x9f940198,0xf8bf0216}}, // [5f30] _dokw, _nvm_, kää_, _edén_,
+ {{0x32550bba,0x1ae380ab,0x6fdd80e7,0x7c229a40}}, // _двор, নুয়া, pèce, dsor,
+ {{0xe9df1c03,0xd6db2355,0xdb0383f2,0x9f4781d0}}, // _thú_, ута_, jiná, _jiná_,
+ {{0xdb03936f,0xfe7000f7,0x3cd59ccf,0x9b450019}}, // diná, عدل_, ожес, _انھو,
+ {{0x442fb4d3,0x7c2284dc,0x913a00be,0x61fa81a1}}, // urg_, gsor, _דערק, _autl,
+ {{0x61faa36f,0x7e620035,0xdb038f09,0x42098ff7}}, // _butl, łopa, finá, анко_,
+ {{0xdb03827f,0xe2998973,0x38695b5b,0x65958071}}, // giná, рай_, lvar_, _даву,
+ {{0x80dd82a8,0xe8f9a10d,0x4422623c,0x7d02e23d}}, // _परदे, шли_, ysk_, ccos,
+ {{0x2eb90e5b,0x7529e23e,0x38690085,0x7c2293ec}}, // _आशुत, _idez, nvar_, csor,
+ {{0x6dbc812b,0x6d4d0e05,0x7d1b94cf,0x80c600ab}}, // nčan, _abaa, _heus, লেক্,
+ {{0x7c240025,0x98b98182,0x61fa80d2,0xbcfb0118}}, // _svir, ısı_, _gutl, rdén,
+ {{0x3cf08105,0x442204dc,0x24ea16d9,0xe29483de}}, // ुँचे_, tsk_, имни_, _צװײ_,
+ {{0x4424623f,0x764508f9,0x628e0428,0x7d1bddf5}}, // ém_, hthy, wybo, _meus,
+ {{0x69cf6240,0x4422021e,0x628e0110,0xf8bf0032}}, // _akce, rsk_, tybo, _adéo_,
+ {{0x79864eb1,0x394908dc,0x6f1e026c,0x7d0401b9}}, // _pokw, ffas_, vapc, _tfis,
+ {{0x7c24805c,0xbcfb6241,0x63ae0359,0x7d044d60}}, // širi, ndél, sibn, _ufis,
+ {{0xef1a6242,0x3f879a45,0x3869524d,0xe7aa01cf}}, // [5f40] има_, _konu_, gvar_, авал_,
+ {{0xafdb06be,0x2d82025b,0x65646243,0x75298d19}}, // _spør, ljke_, _haih, _adez,
+ {{0x9f940364,0x2b580da8,0x442481a1,0x2eee016d}}, // vää_, merc_, _svm_, äff_,
+ {{0xdb03826f,0x442481bb,0x2d8216f2,0x893612c8}}, // viná, _pvm_, njke_, _بعدا,
+ {{0x9f94025d,0x65643e32,0x64465681,0x7d1be244}}, // tää_, _maih, ntki, _deus,
+ {{0x7c22e245,0x7d02e246,0x752981bc,0x65640198}}, // rsor, scos, _edez, _laih,
+ {{0xd6270652,0x7bcace2a,0x644602f1,0x9f940198}}, // _море_, rofu, htki, rää_,
+ {{0x6f1c056f,0x9f940364,0x81d800c8,0x6eca86ab}}, // _herc, sää_, াদক_, _सुषु,
+ {{0xb9028dd2,0xa3da009a,0x46cf86ae,0x9f94156e}}, // _धर_, ़ता_, _सुबह, pää_,
+ {{0x7dd65968,0x39988077,0x3f98808b,0xff988eef}}, // lásh, _désa_, óru_, ској_,
+ {{0x6f1c10b6,0x614685e9,0x65642992,0x4efb00be}}, // _merc, _межа, _baih, יליג,
+ {{0x65640362,0xdced0372,0xbcfb1a1f,0x386d1075}}, // _caih, _jlaħ, rdéo, _šerm_,
+ {{0x65643413,0x7b671ccf,0x6eca8b49,0x61e2a2b3}}, // _daih, отве, _सुरु, _ihol,
+ {{0xa0078060,0x76450114,0x7763823e,0x8c48899b}}, // _بقول_, ythy, _ganx, mağı,
+ {{0x6dbc803a,0x8f9b84de,0x92681612,0x61e2e247}}, // včan, _דיגי, орта_, _khol,
+ {{0x60f911e9,0x65c4038c,0x1a9b83de,0x2bdb0305}}, // сная_, _обја, בילע, _मीरा,
+ {{0xbcfb6248,0x6f1c29ac,0x7d1bb04c,0x61e2a7e7}}, // [5f50] ndém, _berc, _reus, _mhol,
+ {{0x6f1c1f65,0xe94509d7,0xdfd10013,0x7d1b815d}}, // _cerc, _کردی, ريا_, _seus,
+ {{0x44396249,0xd5758c8e,0x386902fe,0x3ea70032}}, // lus_, зуль, svar_, ànt_,
+ {{0x753d624a,0x76450114,0xdce083bf,0xbc681190}}, // ngsz, rthy, _almı, _امین_,
+ {{0x6dbc811f,0x6f1c0114,0x3d0a86a7,0x7d1b802a}}, // pčan, _ferc, ाड़े_, _veus,
+ {{0xf1bf07ca,0x2d9db64e,0x61e2e24b,0x291f804f}}, // _hjá_, shwe_, _ahol, waua_,
+ {{0x6fd4823e,0x6fba3616,0x69c4833b,0xdced0609}}, // càci, ्वरू, čien, _flaħ,
+ {{0xd24e9168,0x7763aeec,0x6dbc8968,0x98ab07c0}}, // اني_, _sanx, nčal, _hacı_,
+ {{0x443955d5,0xa3bd03bb,0x25ade24c,0x65640b20}}, // jus_, ेका_, _imel_, _raih,
+ {{0x4439624d,0xee39a748,0x61e2b937,0x25bf81a3}}, // dus_, бно_, _ehol, _hjul_,
+ {{0x59bd0a0d,0xb90180ab,0x4439624e,0x2b580037}}, // ्कार, _দল_, eus_, verc_,
+ {{0x6d59e24f,0x61e2e250,0x995181d6,0xdb150061}}, // newa, _ghol, _báť_, kozó,
+ {{0xea000104,0x65640364,0x61e995ce,0x8c4882bb}}, // _biết_, _vaih, mmel, cağı,
+ {{0x61e9e251,0x24441a5b,0x291d804f,0xf1bf04be}}, // lmel, röm_, _hewa_, _ajá_,
+ {{0x64460d38,0x6721803b,0xf7708bca,0x443901e2}}, // stki, malj, _جان_, aus_,
+ {{0x61e9d0e9,0xe8fa333d,0x6f1c139f,0xf2d283de}}, // nmel, сла_, _serc, געל_,
+ {{0x6f1c0ca9,0x6d59d360,0x21200b50,0x671f800f}}, // [5f60] _perc, dewa, raih_, _मजाक_,
+ {{0x25adbb19,0x7c265aa6,0xea00001c,0x61e9e252}}, // _amel_, mskr, _giết_, hmel,
+ {{0x6d59a2f5,0xd90d0019,0x212000b9,0x61e9e253}}, // fewa, _چیف_, paih_, kmel,
+ {{0x7dd64c16,0x7bce6254,0x7e62009a,0x6d59b560}}, // fási, lobu, łopo, gewa,
+ {{0x7c2612fa,0x938a8ba7,0x0b8a8098,0x6b88e255}}, // nskr, йска_, йски_, _podg,
+ {{0x61e2e256,0x25ad8ad4,0x5334b7bf,0x7bce01f6}}, // _shol, _emel_, фект, nobu,
+ {{0x6721b7fd,0x9f478009,0x61e2e257,0x6d59e258}}, // dalj, _minä_, _phol, bewa,
+ {{0x291d8a73,0x6d59df37,0x8c4685fa,0x8c4887c0}}, // _cewa_, cewa, _небе, tağı,
+ {{0x7df40012,0x291d81da,0xdb0a80f7,0x64a3b16c}}, // _răsp, _dewa_, lifí, _зата,
+ {{0x44395fa6,0x7c262a33,0x08e900ab,0x9f9d077f}}, // vus_, dskr, টুকু_, yìí_,
+ {{0x44396259,0x61e2e25a,0xe5350110,0x3f8a0bb1}}, // wus_, _thol, мень, _mobu_,
+ {{0x7c24811f,0x3f9e820f,0xa2cd0eb4,0xa44b816b}}, // širu, shtu_, _दुष्, lášť_,
+ {{0x7c262445,0x6721a582,0xf09f026b,0x69cd03a8}}, // gskr, balj, _afàn_, voae,
+ {{0x200be25b,0xf1bf0125,0x4426a6e8,0xdeb4c3e1}}, // ície_, _sjá_, iso_, _облы,
+ {{0xea000104,0x4439625c,0x15ec83b7,0x7ac41ed1}}, // _viết_, sus_, जगार_, есте,
+ {{0x61fe1520,0x4426e25d,0xdb0a9727,0x7c264be9}}, // _dupl, kso_, difí, bskr,
+ {{0xa2cd090f,0xea000028,0x91fc8029,0x752d011e}}, // [5f70] _दुर्, _tiết_, _krās, _idaz,
+ {{0x44290d02,0x4c9c0039,0x7bce1de6,0xade10074}}, // _iva_, _לבחו, cobu, _गीतन_,
+ {{0x44290cde,0x3f8a026f,0xb81e80a5,0x44268114}}, // _hva_, _dobu_, _मौसम_, eso_,
+ {{0xdb1ac6fe,0x672180d2,0x4429021e,0xf53f00e8}}, // _októ, zalj, _kva_, ngå_,
+ {{0x291de25e,0xf8c1123a,0x6e3ae25f,0x4426bcb2}}, // _rewa_, _एशिय, hutb, gso_,
+ {{0x6d599142,0x291d8458,0x6dbcb1a7,0x44295611}}, // sewa, _sewa_, nčaj, _mva_,
+ {{0x752d007d,0xbe880012,0x442682ec,0x61e9e260}}, // _odaz, _есте_, aso_, tmel,
+ {{0x44290025,0xd9e3050d,0x7d04a5ca,0xdb18816b}}, // _ova_, _कीमत_, ñist, mový,
+ {{0xdb18826f,0x7c2480f7,0xf53f013c,0x4426e261}}, // lový, áirt, dgå_, cso_,
+ {{0x61e985d1,0x91fc81a9,0x7c29e262,0x7c2604dc}}, // smel, _prāt, _iver, vskr,
+ {{0x44292dda,0x7c29e263,0x6721d8cb,0xdb1c01ac}}, // _ava_, _hver, ralj, porú,
+ {{0x67218052,0x61368059,0x9f478009,0x262781a1}}, // salj, rülü, _sinä_, rđo_,
+ {{0x61fe4e9f,0x6fbf016f,0x893480f7,0x4c830098}}, // _supl, ्वां, _أعما, вляв,
+ {{0x99808038,0xdb18826f,0x28e28c28,0x9fb58201}}, // čiť_, kový, _परति, dəçi_,
+ {{0xaa430037,0xdb18816b,0x386d9b7d,0x0eac064a}}, // керл, jový, lver_, _टेंड,
+ {{0x7c29e264,0x442682a3,0xe9da1a84,0x3f8a005c}}, // _over, yso_, іка_, _sobu_,
+ {{0x7984085c,0x1f66012f,0x44293f1d,0xd54901a8}}, // [5f80] rjiw, мкам, _gva_, _رجيم_,
+ {{0x1b1800ab,0x7d09826b,0xe8de00c2,0x61fe0198}}, // _তাকে_, _afes, _मरीच, _tupl,
+ {{0xdfcf0307,0x9f5f87e2,0x7c29b206,0x386d8257}}, // جيل_, rquè_, _aver, hver_,
+ {{0x4426e265,0xe57a8fe6,0x6fd48980,0x7c298799}}, // tso_, юзе_, làct, _bver,
+ {{0x44268ba7,0xa3e306b7,0x4197003d,0x045a80f7}}, // uso_, _फीड_, _آشپز, تجات_,
+ {{0x7c2981ac,0xa3a8809a,0x7d09dc72,0xdb1883c1}}, // _dver, खों_, _efes, bový,
+ {{0x4426d668,0x7c298051,0x394dbf8b,0x6fdd823e}}, // sso_, _ever, ffes_, tècn,
+ {{0x6d44022c,0x32538098,0x386d0267,0xd91a804a}}, // _ncia, твър, _šeri_, цьк_,
+ {{0x660d9fe7,0x7c2981b9,0x6dbc80e1,0x798b8326}}, // _čaka, _gver, yčaj, _dogw,
+ {{0x83fd8019,0x7dd8b86d,0xada40035,0x39950338}}, // _erős, dísi, óźni, _påsk_,
+ {{0x7c2981ac,0x3869826c,0x6fd4823e,0xa3cb8072}}, // _zver, _šart_, dàct, ळकर_,
+ {{0x5693a45b,0x6e3a8711,0x6d4400e5,0x4ad200c2}}, // тают, rutb, _ccia, _दुधव,
+ {{0x2edb023c,0xdb050307,0xf8e2890a,0x50e28778}}, // _भर्त, _amhá, _परिय, _परिष,
+ {{0x21249a29,0x237a03a7,0x6d440609,0x69c2d9c6}}, // lamh_, _cnpj_, _ecia, _hjoe,
+ {{0xe2f810ac,0xb90c01bc,0xdce281d6,0x00000000}}, // дері_, _ọbịb, jkoľ, --,
+ {{0x21248af9,0x75208065,0x44292ca4,0xd5b8053b}}, // namh_, _nemz, _tva_, _осы_,
+ {{0xa3ce9d01,0x6d5d3188,0xb4dd8d86,0x4429191f}}, // [5f90] रवा_, mesa, _डरे_, _uva_,
+ {{0xdb18803e,0xd6db1594,0x399c01fa,0x7c2989ed}}, // tový, оте_, _vísa_, _rver,
+ {{0x7c298687,0x63b5007a,0x19951ea4,0xfbdf0187}}, // _sver, mizn, _заня, ntêm_,
+ {{0x6d5d6266,0x68e2e267,0xdb18826f,0x9f5fd1be}}, // nesa, _ozod, rový, rqué_,
+ {{0x61ed5559,0x802712dc,0x7dd602b7,0xdb18816b}}, // mmal, _ترجم, mást, sový,
+ {{0x61ed6268,0x6d5d51d6,0xd7c900d7,0xdb1884e8}}, // lmal, hesa, _روزه_, pový,
+ {{0x6d5d288e,0xd90f87d2,0xf99f06c0,0x61ed6269}}, // kesa, لیا_, _klè_, omal,
+ {{0x6d5d142f,0x9f59027f,0x61ed626a,0x2d9900f3}}, // jesa, _musí_, nmal, jkse_,
+ {{0x386d83d3,0x7c29a9a5,0xa2d60f12,0x6d5d1142}}, // uver_, _uver, _मुन्, desa,
+ {{0x6d44626b,0x7520bc2d,0x2d990cfa,0x6569e26c}}, // _scia, _zemz, ekse_, _haeh,
+ {{0x7d04c824,0xc7a881a8,0x386de26d,0x81d60264}}, // žise, بديل_, sver_, িগত_,
+ {{0xb8e58a49,0x7dd8a511,0x6569d867,0x3991970b}}, // _এর_, tísi, _jaeh, _pásu_,
+ {{0xdb070009,0xbdf98065,0x61ed01a9,0x7c3d02f1}}, // kijä, _کرتا_, dmal, gusr,
+ {{0x7dd8816a,0x69db8168,0xf99f626e,0xa066898d}}, // rísi, llue, _alè_, _чаша_,
+ {{0x6d5d0890,0x8f9a810f,0x21764a56,0x6fd4809f}}, // besa, _מיסי, нуар, ràct,
+ {{0x6d5d626f,0x61ed1fdb,0x600a1a19,0x443dae5e}}, // cesa, gmal, знам_, kuw_,
+ {{0x26de003a,0x39406270,0x3fe64a52,0x63b56271}}, // [5fa0] _što_, rgis_, ежав, bizn,
+ {{0x63b50503,0x2d9f8009,0x657b82c4,0x443d81ed}}, // cizn, _alue_, _anuh, duw_,
+ {{0x7520826c,0x443d81ed,0x2d9f8add,0xdee60198}}, // _pemz, euw_, _blue_, хожи,
+ {{0x9426be3d,0x9f5d8077,0x61bd0519,0x7dd60118}}, // емде, _duwé_, ्कृष, cást,
+ {{0x20040087,0x6569808e,0x7dd60061,0xc9536272}}, // _îmi_, _daeh, láss, _גמר_,
+ {{0x9f58020f,0xaa669ac1,0xa3c0150e,0x69c2beea}}, // _mirë_, етик, ंचि_, _sjoe,
+ {{0xdee314b7,0x66e30251,0x9f580168,0xa2d6123a}}, // _бори, _бора, _lirë_, _मुम्,
+ {{0xea000104,0x20026273,0xdcf60176,0x244981fa}}, // _nhật_, _kuki_, _anyč, búm_,
+ {{0xa3c0035a,0xdced0d26,0x82fa804e,0x6d5d6274}}, // ंचा_, _inać, _فراز_, vesa,
+ {{0xa3c78076,0x27f886db,0x200be275,0x999f80eb}}, // _उदय_, örn_, ícia_, ārši_,
+ {{0xdd91136d,0x7054803d,0x61ed6276,0x63b52771}}, // لود_, رنگا, ymal, vizn,
+ {{0x6d428102,0x7afe002a,0x29d70722,0x69dbe277}}, // ngoa, _agpt, nçat_, clue,
+ {{0x2002026c,0x2d9942cf,0x6d4f010c,0x19b783de}}, // _nuki_, rkse_, rfca, _אפאר_,
+ {{0xcf2680f7,0x27e78362,0x0ce1064a,0x2d84048d}}, // _أرشي, _ghnn_, _नरीम, ömer_,
+ {{0xdb0e2509,0x7cf683bf,0x66738154,0x20026278}}, // cibí, _yürü, _مدير, _auki_,
+ {{0x26db84de,0x3f8e804f,0x20024ffd,0x250900d7}}, // _מקומ, _hofu_, _buki_, _غربی_,
+ {{0x7d0d20af,0x69c0d47c,0x7dd64ae5,0xdb1503ed}}, // [5fb0] _ifas, inme, rást, nizë,
+ {{0x200f0029,0x200201a1,0x7c2d1db4,0xab840249}}, // īgi_, _duki_, _ivar, _буюк,
+ {{0x7c2d6279,0x4fc40544,0x44390176,0x394683a8}}, // _hvar, уста, lrs_, _ocos_,
+ {{0x7c2d0558,0xdce08dba,0x442b627a,0x200200fc}}, // _kvar, _samā, osc_, _fuki_,
+ {{0x7d0d45b7,0x84eb80c2,0x7c2d02a6,0xc8af8a27}}, // _mfas, जेंट_, _jvar, _जेंट,
+ {{0x7cf6817b,0x443d85ee,0x6d42811b,0x644be27b}}, // _sürü, ruw_, agoa, rtgi,
+ {{0x660de27c,0x6569808e,0xdb0a807b,0xdce40b80}}, // _čako, _taeh, kifæ, _raič,
+ {{0x44390301,0xa3e30105,0x96f81a19,0x644b890d}}, // krs_, _फीस_, _чест_, ptgi,
+ {{0x29d70187,0x442b0035,0x4439627d,0x7dd60061}}, // lças_, jsc_, jrs_, záss,
+ {{0x8aa78463,0x02a78951,0x7d0d627e,0x93e00105}}, // _пред, _прем, _afas, _खींच_,
+ {{0x44394543,0x7c2d20a2,0x29d70073,0x6da60676}}, // ers_, _avar, nças_, вина,
+ {{0xaad50105,0x7bc5008e,0xdb0e1727,0x2d9617c7}}, // _धड़क, _ajhu, ribí, трос,
+ {{0xb87b0065,0x7c2be27f,0xfc3f6280,0x5fd10af3}}, // _szín, nsgr, guí_, तकाल,
+ {{0x33d50160,0x395f9aa5,0x6a4a8085,0x7d0d6281}}, // гіст, leus_, _səfi, _efas,
+ {{0x25e3023c,0xea000028,0x7c2d2637,0x9f430118}}, // _टीवी_, _thật_, _evar, _lijó_,
+ {{0x442d8025,0x316d8866,0x7dd8c1bf,0x200252c1}}, // _ove_, ndez_, níst, _puki_,
+ {{0x6d566282,0x7c2d3dbf,0x316d9e00,0xa3e49261}}, // [5fc0] _ibya, _gvar, idez_, नतम_,
+ {{0x4420026b,0xdced02a5,0x672381bf,0x6603bebe}}, // _iwi_, _snać, _jenj, _kunk,
+ {{0x6723845c,0x442002f7,0x75246283,0x395f8198}}, // _menj, _hwi_, _keiz, keus_,
+ {{0x66038eb9,0x442058fd,0xdd99821e,0x6723a956}}, // _munk, _kwi_, нші_, _lenj,
+ {{0x395fe284,0x7dd8e285,0xcfaa12dc,0x6d42e286}}, // deus_, díst, _قاسم_, rgoa,
+ {{0x442d9117,0x39468087,0x6dbc81a1,0x9f43026b}}, // _dve_, _scos_, jčav, _dijó_,
+ {{0x6603e287,0x395f87f1,0x26068072,0x6d565a54}}, // _nunk, feus_, _संधी_, _obya,
+ {{0x7dd8e288,0x7524285e,0x2d9c80eb,0xd13080f7}}, // gíst, _neiz, īves_, حمة_,
+ {{0xf1b30bea,0x4fd49354,0x44202b5f,0x69c0ad57}}, // יסה_, лжит, _nwi_, rnme,
+ {{0x29d72670,0x6603e289,0xdb1ad0de,0x9989811a}}, // nçar_, _bunk, _aktö, čaš_,
+ {{0x7c2d628a,0xa3d602a8,0xdd950791,0x65950087}}, // _svar, िकन_, ганы, гану,
+ {{0x236ce28b,0x6603e28c,0x25a6e28d,0x443900eb}}, // _hadj_, _dunk, nhol_, trs_,
+ {{0x443906e3,0x4fc6891c,0x7ddd0077,0x3866802e}}, // urs_, _иска, nési, _usor_,
+ {{0xc0e38652,0x4420031d,0x09bd0651,0x6d560c56}}, // _точк, _dwi_, ्क्य, _ebya,
+ {{0x75fa801b,0x6fddace0,0x66039037,0x7ddd00e7}}, // _různ, dèch, _gunk, hési,
+ {{0x7c2d8132,0x7c2d628e,0x39a41819,0x7d0d0a5a}}, // šari, _tvar, ашув, _ufas,
+ {{0xfc3f433a,0xdb1c628f,0x1075076a,0x66038326}}, // [5fd0] quí_, goró, ылаю, _zunk,
+ {{0x2d538324,0x67288b67,0x660384b9,0x63b88267}}, // _ṣe_, nadj, _yunk, jivn,
+ {{0x442d803a,0xdfd80098,0x7c2be290,0xd6db01cf}}, // _sve_, тът_, wsgr, фта_,
+ {{0x7dd8e291,0x6728e292,0xf65383c8,0x7c2b9ff5}}, // xíst, hadj, יצע_, tsgr,
+ {{0x656d6293,0x21290812,0x395f87f1,0x40841073}}, // _maah, maah_, veus_, _курб,
+ {{0xdd03884a,0x63b89e6b,0x212957d3,0xbcfb0019}}, // ırıl, givn, laah_, ldés,
+ {{0x61fd6294,0x672381b9,0x395f91ce,0x425281a8}}, // _kisl, _renj, teus_, منور,
+ {{0x45d5817a,0x67238590,0x200b8073,0x25a6e295}}, // _собс, _senj, ício_, chol_,
+ {{0x316daee0,0x75246296,0x672395bd,0x6603e297}}, // rdez_, _reiz, _penj, _sunk,
+ {{0x6728b4c5,0x6603e298,0x21296299,0x395fe090}}, // gadj, _punk, haah_, seus_,
+ {{0x656d02a3,0x442002d6,0x395fe29a,0x6723e29b}}, // _baah, _swi_, peus_, _venj,
+ {{0x8aa48002,0x2d9d81b9,0x399c016b,0xe81e923a}}, // _труд, nkwe_, _písm_, _मौका_,
+ {{0x69d98135,0xa3c00006,0x656d0079,0x21290748}}, // _ikwe, ंचल_, _daah, daah_,
+ {{0xd00e9301,0xf1b9a7b1,0x61fd517b,0x660385a3}}, // _على_, liš_, _aisl, _tunk,
+ {{0xfaa30c73,0x32058051,0x15f5a279,0x44201c33}}, // баро, _july_, ेदार_, _wwi_,
+ {{0x2fd8010b,0x290d8087,0x7641c50b,0xd2e41199}}, // _skrg_, ncea_, muly, _गरीब_,
+ {{0xa2da835a,0x57ea067c,0x316000eb,0x6a4a8085}}, // [5fe0] _पुन्, едам_, reiz_, _həft,
+ {{0x6d498c6e,0x61fd4d10,0xf8bf0ba4,0x33662466}}, // _acea, _eisl, _adés_, _свог,
+ {{0x29d726e1,0x69d994ee,0x2d80115b,0x25a68114}}, // rçar_, _okwe, _knie_, thol_,
+ {{0x6d460458,0x6dbc816b,0x69d99292,0xdb1c172b}}, // ngka, jčat, _nkwe, poró,
+ {{0x69c41a1d,0x63b89620,0x2d800063,0xa3ab02f1}}, // mnie, tivn, _mnie_, _खतम_,
+ {{0x69c4629c,0x7641e29d,0x437600be,0x0a6a891c}}, // lnie, kuly, רײַן_, ерии_,
+ {{0x63b8bf88,0xbcfb2190,0x25a6e29e,0x645b9eed}}, // rivn, ndér, phol_, _epui,
+ {{0x38348468,0x7bc3a19b,0x6d498013,0xc0348dc7}}, // анир, annu, _gcea, аниј,
+ {{0xfce39033,0xf9930039,0x80dc80ab,0x320582c4}}, // _гото, מרה_, মেন্, _euly_,
+ {{0x69d9882e,0x385780f7,0x656d629f,0xbcfb62a0}}, // _ekwe, اشيد_, _saah, zdés,
+ {{0x67288b67,0x69c42445,0xf1b98796,0x2d920041}}, // radj, knie, biš_, _boye_,
+ {{0xceb30039,0x69c462a1,0x67288b80,0xf1b9811f}}, // מית_, jnie, sadj, ciš_,
+ {{0x69c41a15,0x7bd562a2,0x589692c8,0xa3c0016f}}, // dnie, tozu, _حجاز, ंचं_,
+ {{0x765c62a3,0x6d460282,0x69c41a22,0x7f968866}}, // _opry, agka, enie, _pâqu,
+ {{0x61fd62a4,0x212915e8,0x6dbcc573,0xea0000ff}}, // _pisl, taah_, nčas, _phạt_,
+ {{0x69c462a5,0xb115174a,0x39152f84,0x80d300ab}}, // gnie, рмаш, рмар, _দৃষ্,
+ {{0xa3e485e8,0xbcfb0065,0x60f921f6,0x644d0748}}, // [5ff0] नता_, rdés, тная_, _uqai,
+ {{0x3f9e969e,0xb4c31299,0x6d49c1d1,0x9f4101a8}}, // nktu_, ्थी_, _scea, lmhú_,
+ {{0x69c4118c,0x6dbc826f,0x645bdf95,0x62670199}}, // bnie, jčas, _spui, _صادق,
+ {{0x7bdae2a6,0x61fd0010,0x6dbc8259,0x531402e3}}, // _aktu, _uisl, dčas, _پذیر,
+ {{0x6dbc83f2,0x8b258a0e,0xa5c5826b,0x7a1181a9}}, // včat, адле, _atóè, _māte,
+ {{0x6fdd8722,0xd7f8028e,0x0ed0016f,0x69d9b937}}, // pèci, _бус_, _तुकड, _skwe,
+ {{0xa3d60076,0xfe09801c,0xaadb06a7,0x98bf88ae}}, // िकत_, _mắc_, _मुमक, rguč_,
+ {{0x798d20c8,0xfe0980ff,0x34958256,0x2006e2a7}}, // njaw, _lắc_, _кабр, _buoi_,
+ {{0x0cd9803d,0xf09200be,0x290d8087,0x78ad02d6}}, // _شارژ_, ַנט_, rcea_, nzav,
+ {{0x69c4350e,0x81bc8029,0x442fd908,0x7641e2a8}}, // znie, _svēt, msg_, tuly,
+ {{0x6dbc84a8,0xf1b9d717,0xdee5971c,0x672701b9}}, // mčar, piš_, рони, _mejj,
+ {{0x58d58698,0x69d9ae57,0x287581bb,0x66070267}}, // _коет, _ukwe, _выкр, _mujk,
+ {{0x69d603d3,0xef0e9c20,0x2d920247,0x69c400eb}}, // voye, _мм_, _voye_, vnie,
+ {{0x69c40063,0x6dbca944,0xb92b00ff,0x78ad0619}}, // wnie, nčar, _thuỷ_, dzav,
+ {{0x69c41a1d,0xa2d6023c,0x412a0225,0x69d61dac}}, // tnie, _मुश्, ково_, toye,
+ {{0x7c2f13a0,0x798d00ee,0x2d805ed7,0xe1ff07f1}}, // bscr, gjaw, _unie_, _ció_,
+
+ {{0x69c462a9,0x7d0400b4,0x316f808e,0x21672466}}, // [6000] rnie, _igis, _magz_, шити_,
+ {{0x69c40029,0x998d0796,0x61e481b9,0xef171bb1}}, // snie, češ_, ċili, иму_,
+ {{0x63bc003a,0x672704b7,0xe8e283eb,0x69c42e63}}, // mirn, _dejj, _पर्च, pnie,
+ {{0x1e568158,0xe1ff001c,0x7c2400b4,0x6b818176}}, // ישער_, _gió_, _kwir, _anlg,
+ {{0x2ca08307,0x7ddd58cf,0x9f4f81ca,0x629c0706}}, // áid_, mést, _jugó_, lyro,
+ {{0xd25a80c4,0x63bc62aa,0x7c24150f,0x3998808b}}, // вци_, nirn, _mwir, _lést_,
+ {{0x20068098,0xfe6788ca,0x27ff047f,0xf1a6e2ab}}, // _suoi_, _بد_, _èuna_, ирин,
+ {{0x63bc0867,0x20068698,0xa2da835a,0x7c2f1c33}}, // hirn, _puoi_, _पुण्, yscr,
+ {{0xbee6035a,0x200682be,0x63bc62ac,0x629c2af9}}, // _करुन_, _quoi_, kirn, hyro,
+ {{0x20068098,0x47348ada,0x7bc8826c,0x7d0462ad}}, // _vuoi_, щнос, _ujdu, _agis,
+ {{0x442481c5,0x78ad2bd7,0xc98682eb,0x21268034}}, // _hwm_, zzav, _вули, _teoh_,
+ {{0x99808a8e,0x2006d1e9,0xfe098028,0x44248069}}, // čių_, _tuoi_, _sắc_, _kwm_,
+ {{0xa2da9513,0xb4c301d0,0x6ce7021e,0xe1ff0144}}, // _पुत्, ्थे_, ріне, _rió_,
+ {{0xbee6035a,0x09b1035a,0x69c802be,0x7c2f01a8}}, // _करून_, ीच्य, éden, rscr,
+ {{0x442481e9,0xff178039,0x79828039,0x7ddd03a8}}, // _lwm_, _דקות_, _know, fést,
+ {{0x7bd8e2ae,0x4efb8039,0x78ad023e,0x9f4f8144}}, // hovu, _בלוג, tzav, _fugó_,
+ {{0x63bc399e,0x798d085c,0x7c240114,0x64400722}}, // [6010] birn, rjaw, _gwir, àmit,
+ {{0x78ad0052,0x63bc5c2a,0xed5980c3,0x9f58026b}}, // rzav, cirn, _sbž_, _firí_,
+ {{0x41d6800f,0x27e984b9,0xdb188198,0x17f801a8}}, // डकास, _ɗan_, invä, شركة_,
+ {{0xeef784de,0x7bc702d4,0x3dc6a9f8,0x3cee06ae}}, // במבר_, lnju, gnow_, _घरमे_,
+ {{0x65a6007b,0x6600d5d6,0x4424e2af,0x3d0f801b}}, // _jóha, _dimk, _cwm_, ाडौं_,
+ {{0xdcfb811f,0xfc3f0013,0x9cd80039,0x39495ac0}}, // _unuč, irí_, בודה_, lgas_,
+ {{0x52758087,0x38695025,0x356b9ddf,0x926481a8}}, // сулу, lwar_, _иран_, قديم,
+ {{0x394962b0,0x4424822c,0x63bc0669,0x26c980d2}}, // ngas_, _fwm_, zirn, _ćao_,
+ {{0x386900fc,0x79828122,0x629c0035,0xa2e4052a}}, // nwar_, _dnow, zyro, _गर्ज_,
+ {{0x6d4d01a1,0xf8b201c6,0x7c2d826c,0xfc3f07f1}}, // _acaa, פשי_, šars, drí_,
+ {{0x9f47809f,0xdce08668,0x63bc0796,0xb8fe99e8}}, // _sinó_, _pamć, virn, _दु_,
+ {{0xcb090039,0x44248282,0x6d4d038a,0x629c1bfe}}, // _טל_, _ywm_, _ccaa, vyro,
+ {{0x291f8010,0x442481e9,0xfc3f023e,0x63bc62b1}}, // mbua_, _xwm_, grí_, tirn,
+ {{0x38691acf,0x629c1c85,0xdb1880e7,0x3cdc052a}}, // dwar_, tyro, tivé, _गुणे_,
+ {{0x4ad205b3,0x68e30201,0xd29900e8,0x61ef0267}}, // _दुखव, əndi, атті_, _bhcl,
+ {{0x09060f04,0xdb1880e7,0xa06a316c,0x394962b2}}, // спан, rivé, лаба_, ggas_,
+ {{0xea000104,0xa3d7999e,0x249d81c0,0xeb9a0249}}, // [6020] _nhất_, सवा_, jywm_, _бип_,
+ {{0x69dd054f,0x6446130e,0x6d5b80dd,0x6609017f}}, // _ekse, muki, _abua, _čekr,
+ {{0x644622b3,0xe29a26b1,0x3dc68197,0x60cf1727}}, // luki, _сан_, tnow_, _excm,
+ {{0x44248282,0x998600f7,0x3f8303f7,0x7ddd0061}}, // _pwm_, _الحو, _enju_, lésr,
+ {{0xea000028,0x644662b3,0x61e44f89,0xa3bc02f1}}, // _chất_, nuki, mlil, _आगा_,
+ {{0x61e462b4,0x44248069,0x2a6c841c,0x29de0428}}, // llil, _vwm_, _psdb_, rïau_,
+ {{0x6e96003f,0x320206cb,0x7bd8e2b5,0x7bde0870}}, // _الزا, _kiky_, rovu, _mkpu,
+ {{0xd90e80d5,0x61e45a53,0x64463d90,0x4814aed9}}, // ریت_, nlil, kuki, омис,
+ {{0xa01b01ec,0x64460c2e,0x7bde0870,0x7bd8aaa0}}, // _eröf, juki, _okpu, povu,
+ {{0x644662b6,0x61e462b7,0xc32280ab,0x7bde0135}}, // duki, hlil, _নাকি_, _nkpu,
+ {{0x8c0000c8,0xdb18816d,0x2120026c,0x11d68c4f}}, // ্ঠান_, rnvä, kbih_, _людя,
+ {{0x69c80019,0xc6930039,0x7bde0133,0xfc3f01ac}}, // édel, לאת_, _akpu, trí_,
+ {{0xe8948e02,0x2120026c,0x2ec40a27,0x64460f45}}, // _фаль, dbih_, वपुत, guki,
+ {{0x7ddd0061,0x58f781c6,0x18378e82,0x68e2a6f3}}, // téss, _המדע_, _פאול_, _iyod,
+ {{0xf1a7067c,0x0f578bea,0xfe09801c,0x39490573}}, // бран, _היום_, _hắn_, tgas_,
+ {{0x61e402b8,0x644662b8,0x7bde0135,0x39490110}}, // glil, buki, _ekpu, ugas_,
+ {{0xa2da8b6f,0x752f009a,0x23ab806a,0x752980b9}}, // [6030] _पुस्, macz, _høje_, _reez,
+ {{0x386904b9,0xb17b016d,0x6d5b80dd,0x394915d0}}, // rwar_, _skåd, _sbua, sgas_,
+ {{0x61e462b9,0x38692bb7,0x2120026c,0x9f5812d0}}, // blil, swar_, bbih_, _dirà_,
+ {{0x752f0063,0xcd2b803d,0x85300326,0x69dd02f1}}, // nacz, اسان_, _koɗa, _ukse,
+ {{0x6738973d,0x212b1670,0x27ff01e8,0x7ae1806f}}, // _odvj, _iech_, _èuno_, _sylt,
+ {{0x80dc800f,0xe2998012,0x212b0713,0x65663d8f}}, // _पड़े, иал_, _hech_, zekh,
+ {{0x64465282,0x68e2d038,0x65662499,0x539c03de}}, // zuki, _ayod, yekh, ריוו,
+ {{0x200b0052,0xea000028,0xfe09801c,0xdb1c0168}}, // _kuci_, _thất_, _bắn_, tirë,
+ {{0xb4e80105,0x9f4700ff,0x660a81ec,0xaadb0fb2}}, // _भरे_, _điên_, _aufk, _मुसक,
+ {{0x53349445,0x25a93e95,0x200b00fe,0x6d4a83bf}}, // _неут, _alal_, _muci_, ılaş,
+ {{0x20032ef4,0x02a7bd93,0xdb1c0168,0x93888951}}, // _miji_, _урам, sirë, _уста_,
+ {{0x64465542,0x82d680be,0x20190074,0xe9df3f87}}, // tuki, _וועג_, _otsi_, _nkú_,
+ {{0x22478019,0xfe09801c,0x2019022c,0x92e600ab}}, // lunk_, _gắn_, _ntsi_, _ফলে_,
+ {{0xbea59927,0x98a20029,0x6446300f,0xb6a581a0}}, // _малк, _nekā_, ruki, _милл,
+ {{0x752f009a,0x69c9e2ba,0x61e462bb,0x212b0620}}, // bacz, onee, tlil, _bech_,
+ {{0x69c98a52,0x62878036,0xdce4011f,0x64463735}}, // nnee, _àjou, _naić, puki,
+ {{0x212b2257,0x20030590,0x200b010b,0xe9d705e9}}, // [6040] _dech_, _biji_, _cuci_, іку_,
+ {{0x200302a5,0x21200239,0x69c9e004,0x200b2fdb}}, // _ciji_, sbih_, hnee, _duci_,
+ {{0xa6ca00f7,0x2247e2bc,0x20190198,0x21200267}}, // جوال_, junk_, _etsi_, pbih_,
+ {{0xed5a90fc,0xa2da800c,0x69c981c0,0xa01b016d}}, // род_, _पुष्, jnee, _bröd,
+ {{0xdb153cdf,0xb87b0019,0x0b8a8188,0x798645b7}}, // lizá, _szív, иски_, _inkw,
+ {{0x660418c5,0xdce0801b,0x7bdc44e0,0xfe0980ff}}, // _liik, _zamě, noru, _rắn_,
+ {{0xdb1538bb,0xada68198,0xe5a685fa,0xa2da8054}}, // nizá, _давл, _диви, _पुश्,
+ {{0x8c4693f1,0xf1268d8e,0xa9c380e8,0x69c9e2bd}}, // _мебе, цько, дськ, gnee,
+ {{0x80dc00c8,0x7522a742,0xa2da824c,0x180482f1}}, // _মৃত্, mboz, _पुर्, रदेव_,
+ {{0x6d40008b,0x69c98df6,0x9178846d,0x752f0035}}, // óman, anee, _fé̟_, wacz,
+ {{0x3f980a20,0xf09f62be,0x7a1181a9,0x69c98069}}, // _moru_, _ngàn_, _tāta, bnee,
+ {{0xdced0503,0xd83f016b,0x79860133,0xfe09827d}}, // _snađ, íčku_, _nnkw, _tắn_,
+ {{0xf76f8077,0x66040057,0x69c08009,0xb87b026b}}, // رای_, _diik, mime, _ayín,
+ {{0xbebc8029,0x212b010c,0xfbbca9b7,0x200b005c}}, // rbīb, _sech_, ्चिम, _ruci_,
+ {{0x200b0587,0x6d4b8186,0xda780ada,0xdce0801b}}, // _suci_, ygga, оят_, _pamě,
+ {{0x3015002d,0xdb18928c,0x20030077,0x7ddd0144}}, // здар, diví, _siji_, césp,
+ {{0x9abc84b7,0x3f980965,0x22478061,0x6ab8864a}}, // [6050] _deċi, _boru_, zunk_, ेपुर,
+ {{0xa2d60a3a,0x442904be,0x7bdc62bf,0x5ba71a19}}, // _मुक्, _iwa_, coru, _фриз,
+ {{0x752d0abf,0x69c0e2c0,0xa2da847d,0x69c98a5b}}, // _keaz, kime, _पुल्, ynee,
+ {{0x442962c1,0x671b1993,0x69c0b9d6,0x48c900ab}}, // _kwa_, _प्रक_, jime, _শরীর,
+ {{0x6442a1aa,0x69c98069,0x442906c4,0x3f980102}}, // groi, vnee, _jwa_, _foru_,
+ {{0x442947f0,0xb09a0039,0x22478019,0x3f980289}}, // _mwa_, _הישר, tunk_, _goru_,
+ {{0x69c0c51b,0x44295cf1,0x3f89801c,0xe29993bd}}, // fime, _lwa_, _đau_, сай_,
+ {{0x4429376b,0x7bdc62c2,0xa2ce04c5,0x22478061}}, // _owa_, zoru, _सशस्, runk_,
+ {{0x7bdc0179,0xdb0e0331,0x2019820f,0x44293318}}, // yoru, ribú, ësi_, _nwa_,
+ {{0x69c9e2c3,0x7c29e2c4,0xb113019d,0x9c138133}}, // snee, _iwer, _tụwa, họkw,
+ {{0x69c0e2c5,0x67219de3,0x7bdc18ad,0x7a3081a8}}, // bime, rblj, voru, _vótá,
+ {{0x69c09092,0x7c29af0b,0x44292f14,0xf09f00ff}}, // cime, _kwer, _bwa_, _ngào_,
+ {{0x7bdc0012,0x66040364,0x44295a64,0xb88681fa}}, // toru, _viik, _cwa_, rtíð,
+ {{0x44290773,0xf771990c,0x394d99f6,0xaca3e237}}, // _dwa_, _سات_, lges_, _ngọn,
+ {{0x753b803a,0xa2d60b6f,0x6da61fab,0x44291e54}}, // _oduz, _मुख्, _ника, _ewa_,
+ {{0x7d760277,0x7d09829b,0x44290247,0xaca3819d}}, // _رابط, _nges, _fwa_, _agọn,
+ {{0x7c298a2c,0x6ed40105,0x661b0279,0xb9288133}}, // [6060] _nwer, _बुजु, _čuka, _agwụ_,
+ {{0x7d09b353,0xe4570158,0xfe098028,0x69c0c3d8}}, // _ages, _צייט_, _lắm_, zime,
+ {{0xfe050086,0xdfcf0013,0x3f980125,0x386d81ec}}, // _üçün_, ديل_, _voru_, hwer_,
+ {{0x248006c4,0xfe0980ff,0x7c29e2c6,0x69c0e2c7}}, // _krim_, _nắm_, _bwer, xime,
+ {{0x65765eae,0xdca385c2,0x3f980054,0x394da7d3}}, // _mayh, наци, _toru_, dges_,
+ {{0x7fd60d13,0x6442971c,0x7c299412,0xdbd6826b}}, // _хімі, rroi, _dwer, _bùáy,
+ {{0x69c0c953,0x7c298135,0x27f701d0,0x89d884a3}}, // time, _ewer, řené_, يوتر_,
+ {{0x394d84d6,0xfe0980ff,0x386d80b9,0x9f5c8216}}, // gges_, _cắm_, fwer_, _viví_,
+ {{0x752d1b68,0x7c29ad50,0x8c439033,0xb87b0019}}, // _reaz, _gwer, _пете, _nyíl,
+ {{0x442902a0,0x7648843b,0xb113019d,0x2c5e80eb}}, // _rwa_, tudy, _hụta, rādā_,
+ {{0x45d50003,0x442906c0,0x6b88813c,0xd7f88087}}, // донс, _swa_, _indg, rmă_,
+ {{0x442903ec,0x6b9a8129,0x7ae50198,0x248062c8}}, // _pwa_, _hotg, _ryht, _brim_,
+ {{0x61e284b7,0xdb018118,0x2480023e,0xb1130133}}, // _ikol, _illá, _crim_, _mụta,
+ {{0x083b8051,0x75ff0029,0xb4bd1834,0x442906c0}}, // _פעול, _mūzi, _आधी_, _vwa_,
+ {{0xdb0ae2c9,0xb91a0a2c,0xcfc400ab,0xd6cf8c4f}}, // lifó, _ọtụt, ্তান, _чт_,
+ {{0xf77309d7,0x44292e48,0x61e284b7,0x24800748}}, // کار_, _twa_, _jkol, _frim_,
+ {{0xa3dc816f,0x442962ca,0x661c00c3,0x248062cb}}, // [6070] ठवा_, _uwa_, _atrk, _grim_,
+ {{0xdefb0198,0xedc583ca,0x7c298a03,0x61e2822b}}, // бые_, लच्छ, _rwer, _lkol,
+ {{0x7c29e2cc,0xdb0183a8,0x363401a8,0xf77020bb}}, // _swer, _ollá, فرنس, داق_,
+ {{0x25a68a84,0x6d4f62cd,0x61e28365,0x23490fd3}}, // gkol_, ngca, _nkol, _اسکی_,
+ {{0x69cd00d2,0x76430061,0xeb9980af,0x65608362}}, // mnae, ányb, жил_, _dbmh,
+ {{0xfe09801c,0x61e291ad,0xbd05826b,0xa2e58162}}, // _sắm_, _akol, _apéé, молд,
+ {{0x394d8813,0x386de2ce,0x61e28609,0xe3b081a8}}, // tges_, wwer_, _bkol, درن_,
+ {{0x69cd1a5a,0x2360a663,0x394de2cf,0x7d09db0c}}, // nnae, đija_, uges_, _uges,
+ {{0xbf0583dd,0x394de2d0,0x386d81ed,0x50b599b8}}, // रश्न_, rges_, uwer_, ьску,
+ {{0xee39c4cc,0x6615826f,0xdb018019,0x7999810c}}, // оно_, ízko, _ellá, _woww,
+ {{0x7bc3e2d1,0x644bc739,0x9d46117c,0x0f58010f}}, // minu, lugi, менд, ליזם_,
+ {{0x2d890333,0x9f45a9d5,0x656b80e5,0x386d80e4}}, // _cnae_, illé_, negh, pwer_,
+ {{0xe73980a9,0x61fb8365,0x69cd0bda,0x6459c538}}, // _дел_, mmul, dnae, ntwi,
+ {{0x61e98006,0x61fb8087,0x7bc3e2d2,0x68e60a53}}, // llel, lmul, ninu, _rykd,
+ {{0xea000028,0x4adb0105,0x09e610ca,0x25bf8087}}, // _chết_, _मुआव, довн, _omul_,
+ {{0x61e9a5b3,0x7bc384a7,0x200783e4,0x644b9efb}}, // nlel, hinu, _hini_, kugi,
+ {{0x2007e2d3,0x91e31697,0x09e3102a,0xdb1c0144}}, // [6080] _kini_, тоте, тотн, lirí,
+ {{0x6281d41e,0xdfcf0013,0x2007e2d4,0x68e60110}}, // _orlo, ليق_, _jini_, _vykd,
+ {{0x7e56245b,0x7bc393bd,0x200f9761,0xb1130870}}, // ьтац, dinu, _lugi_, _pụta,
+ {{0x644be2d5,0xea000028,0x26138073,0x645981bc}}, // fugi, _đặc_, _mão_, ftwi,
+ {{0x4ac38076,0x3ea60029,0x61e9829b,0x6281e2d6}}, // _वेबव, ņot_, dlel, _arlo,
+ {{0x61e2a673,0x20078ca2,0x6ac98107,0xbf9b0036}}, // _skol, _nini_, िपूर, _quêt,
+ {{0x261399b6,0x661c02ce,0x79960069,0x644b8084}}, // _não_, _utrk, ojyw, augi,
+ {{0x2007e2d7,0x30a6869b,0x200fe2d8,0x61fb809c}}, // _aini_, дров, _bugi_, gmul,
+ {{0x20079953,0xa3c00a61,0xc05a80e8,0xe11281bc}}, // _bini_, ूचा_, цій_, _bụrị,
+ {{0x200785f5,0xb4aa8b9f,0x6b88813c,0x61e284b7}}, // _cini_, खनी_, _undg, _wkol,
+ {{0x2007e2d9,0x7c22e2da,0x26138187,0xdb1c349a}}, // _dini_, lpor, _cão_, girí,
+ {{0x61e2c707,0x2007c1be,0x26138187,0xf64f8019}}, // _ukol, _eini_, _dão_, گئی_,
+ {{0x2007e2db,0x7c22e2dc,0x61e08122,0xd90f003d}}, // _fini_, npor, moml, یید_,
+ {{0x2007e2dd,0x09c7016f,0xf1bf007b,0xdb1c0333}}, // _gini_, रच्य, _smá_, birí,
+ {{0x69cd0052,0xdb1c0333,0xa27801ae,0x9f4e826b}}, // tnae, cirí, мбру_, _fifò_,
+ {{0x260710c5,0x250900d5,0x2007e2de,0x7bc3995c}}, // _सूची_, _عربی_, _zini_, zinu,
+ {{0x2007829b,0x656b8087,0x7d0d62df,0x7bc3e2e0}}, // [6090] _yini_, vegh, _igas, yinu,
+ {{0x7c229807,0xc8088104,0x69cd0699,0x656b882e}}, // dpor, _bởi_, snae, wegh,
+ {{0x2bdd86bf,0x656b8133,0x61e0e2e1,0x20ca816f}}, // यवसा, tegh, koml, ापुढ,
+ {{0x7c2d62e2,0x3f9c8639,0x4439010c,0x644baeae}}, // _kwar, _lovu_, oss_, tugi,
+ {{0x7c22b394,0x628a063e,0x7d0d0122,0x443962e3}}, // gpor, _àfor, _mgas, nss_,
+ {{0x3f9c803a,0x200f8b20,0x7c2d0435,0xe6670656}}, // _novu_, _rugi_, _mwar, _отбо,
+ {{0xc8088028,0x38668091,0x61fbe2e4,0x2ca98267}}, // _gởi_, _apor_, tmul, šad_,
+ {{0x20078393,0x7d0d24ff,0x2bdd86bf,0x443900eb}}, // _sini_, _ngas, यवहा, kss_,
+ {{0x26138003,0x31790063,0x2007e2e5,0x61e9ad27}}, // _são_, _masz_, _pini_, rlel,
+ {{0x442db44e,0x7d0d62e6,0x61fb9430,0x61e98f09}}, // _iwe_, _agas, smul, slel,
+ {{0x2007e2e7,0x6609e2e8,0x3f9c8858,0x7d0d019d}}, // _vini_, _kiek, _dovu_, _bgas,
+ {{0xb17b108c,0x26138073,0x200782d6,0x442de2e9}}, // _skån, _vão_, _wini_, _kwe_,
+ {{0xa8a795e4,0x2007bd9b,0x442d8247,0x5e56803d}}, // _تصاو, _tini_, _jwe_, ولیس_,
+ {{0x6609ac28,0x7c2d05ad,0x26138073,0x442da4e7}}, // _liek, _dwar, _tão_, _mwe_,
+ {{0x36668098,0x80ec00ab,0x387f807b,0x442d98b7}}, // _защо_, _কলেজ_, lvur_, _lwe_,
+ {{0x6609e2ea,0x442d9c00,0x3f9c8024,0x15e812c6}}, // _niek, _owe_, _zovu_, _टीचर_,
+ {{0xa0675668,0x7c2d62eb,0x18670847,0x656402a0}}, // [60a0] ната_, _gwar, нати_, _ibih,
+ {{0x442062ec,0x5c5b010f,0x7d1be2ed,0xf99f010c}}, // _iti_, _צדיק, _afus, _hièl_,
+ {{0x7c2d46f6,0x442002f7,0x34a880c2,0x660989ff}}, // _zwar, _hti_, कन्द, _biek,
+ {{0x7c22cbab,0x442dc314,0x6609809a,0x14c8803d}}, // tpor, _bwe_, _ciek, وهای_,
+ {{0xfb0b0102,0xb4aa8063,0x660990e1,0x442db26a}}, // _óëñ_, खने_, _diek, _cwe_,
+ {{0x442d8205,0xa01b016d,0x7c22b437,0xa3d60a74}}, // _dwe_, _krön, rpor, िकट_,
+ {{0x442de2ee,0x38668808,0x92948110,0x69058201}}, // _ewe_, _spor_, тайц, ərdə,
+ {{0x442062ef,0xd13080f7,0xdee31b47,0x44392f9c}}, // _oti_, جمة_, _соси, yss_,
+ {{0x44204314,0xa3e001ab,0x442dbe82,0x6d408085}}, // _nti_, तवा_, _gwe_, _idma,
+ {{0x66098a0f,0x316003ac,0x200a10fe,0x7c2d00b4}}, // _ziek, hfiz_, _jibi_, _rwar,
+ {{0x442062f0,0x7c2d2044,0xb907916e,0xffb700be}}, // _ati_, _swar, _मु_, גליש_,
+ {{0x7bc181ac,0x7173803d,0xf41f016d,0xdd12061c}}, // _zmlu, _مهما, nväg_, rüşm,
+ {{0x07a6b317,0x69c10036,0x499b81c6,0x38668824}}, // _падн, èlem, _משאב, _upor_,
+ {{0x443962f1,0x3872031d,0x61e60609,0x3f9c9c40}}, // rss_, dwyr_, _akkl, _uovu_,
+ {{0x6d40803a,0x442062f2,0xa785803d,0x81c900ab}}, // _odma, _eti_, _جشنو, _লীগ_,
+ {{0x7d0d2e96,0x7c2d18e3,0x443b01c0,0x200a00fc}}, // _ugas, _twar, _tvq_, _aibi_,
+ {{0x656f62f3,0x7ddd0019,0x7c2d62f4,0x38720114}}, // [60b0] mech, kész, _uwar, gwyr_,
+ {{0x6609d8d3,0xdb018009,0x6d40ddf0,0x200a047f}}, // _siek, _yllä, _adma, _cibi_,
+ {{0x6609b50e,0xa01b00f2,0x7bc70f27,0x645d62f5}}, // _piek, _grön, miju, ltsi,
+ {{0x7bc762f6,0x657d62f7,0xdb1c02b7,0x656f1c18}}, // liju, ndsh, mirá, nech,
+ {{0x2129059c,0x645d62f8,0x6ac9824c,0x61ed62f9}}, // mbah_, ntsi, िप्र, mlal,
+ {{0x200a0179,0x61ed45ea,0x7bc762fa,0x64a2a29c}}, // _gibi_, llal, niju, раша,
+ {{0x22568288,0x644f0661,0x248480ee,0x6609e2fb}}, // نلود_, huci, _ermm_, _tiek,
+ {{0x645d0074,0x9f4100f1,0x61ed0cf4,0x644f1a99}}, // ktsi, kohë_, nlal, kuci,
+ {{0x442dcdd5,0x644f0042,0x645d3592,0x4439877f}}, // _uwe_, juci, jtsi, _ìs_,
+ {{0x61ed029b,0x81d700ab,0x657bab06,0xa283803d}}, // hlal, িতি_, _hauh, _میتو,
+ {{0x656f62fc,0x273c001b,0x6285022c,0x212900dd}}, // fech, lání_, _nrho, kbah_,
+ {{0x2d9f902d,0xdb1c0511,0x657be2fd,0x645d11e6}}, // _joue_, dirá, _jauh, ftsi,
+ {{0x81d700c8,0x61ed005d,0x853a8039,0x273c001b}}, // িতা_, dlal, וגרי, nání_,
+ {{0x8fa28ce6,0xf7458811,0x2d990168,0x657be2fe}}, // _ваше, _реко, ajse_, _lauh,
+ {{0x645d1e7a,0xdb1c4bce,0x39988061,0x62850144}}, // atsi, girá, _rész_, _crho,
+ {{0x61ed62ff,0x200a6300,0x656f2997,0x442062e0}}, // glal, _sibi_, cech, _tti_,
+ {{0x644f2d11,0x26170073,0x44203c92,0x2d5a0353}}, // [60c0] cuci, _aço_, _uti_, _všeč_,
+ {{0x273c000d,0x61ed20df,0xdb1c05e4,0x3872031d}}, // dání_, alal, birá, rwyr_,
+ {{0x1dc620d5,0x21295f85,0x25a00573,0x7ddd0019}}, // _लगात, bbah_, _koil_, vész,
+ {{0x290f8072,0x71a6284f,0xe9a60b5b,0x200a010c}}, // _ngga_, _рамз, _рамп, _wibi_,
+ {{0x200a130c,0x779100d5,0x2d9f8473,0xf41f0106}}, // _tibi_, _گیلا, _doue_, rväg_,
+ {{0x656f0063,0x61e40198,0xf41f0338,0x69c28cab}}, // zech, loil, sväg_, _smoe,
+ {{0x645b8cf7,0x644f0035,0x877b03de,0x7ddd0061}}, // _equi, zuci, _קאלי, rész,
+ {{0x28c70aad,0x80e080ab,0x16770039,0x68eb835f}}, // _रेडि, _পৃষ্, _בגלל_, _bygd,
+ {{0x656f6301,0x394200ee,0x645d6302,0x6da3a3d7}}, // vech, _mdks_, xtsi, _тита,
+ {{0xaadb01ce,0xdce400ce,0x644f042b,0x656f6303}}, // _मुझक, _naiđ, vuci, wech,
+ {{0xe5351a8f,0xdd9107bd,0x777c173e,0x4426c2fc}}, // лень, مود_, _marx, lpo_,
+ {{0x23698024,0xa01b016d,0xb5a704fa,0x6846a2f6}}, // đaje_, _bröl, трой, _анда,
+ {{0x656f6304,0xceb88063,0x4426e305,0x5c741676}}, // rech, szę_, npo_, алят,
+ {{0x4426840e,0x645d6306,0x656f6307,0xdb1c188b}}, // ipo_, rtsi, sech, tirá,
+ {{0x656f1c56,0x764f8063,0x21290b50,0x644f036e}}, // pech, ącyc, tbah_, suci,
+ {{0x1e978051,0x7bc701e2,0x44268135,0xdb1c0511}}, // _יכול_, siju, kpo_, rirá,
+ {{0x61ed0483,0x657b8009,0x7bc70110,0x2d9f8036}}, // [60d0] rlal, _rauh, piju, _roue_,
+ {{0x273c03bb,0x76430019,0x21290748,0x2ca40106}}, // vání_, ányo, sbah_, ämde_,
+ {{0x660d6308,0xe45f0009,0xdbdc807b,0x212901a1}}, // _kiak, ltö_, _ráðh, pbah_,
+ {{0x79951baa,0xa439908d,0x61e401a8,0x7d0901a9}}, // _симф, езду_, coil, _tīrī,
+ {{0x660d022e,0x4426820d,0x798f04ee,0x754485a8}}, // _miak, gpo_, _incw, рніз,
+ {{0x273c01d0,0x60cd80c3,0xfe0a027d,0xdce40372}}, // rání_, _žamo, _hắt_, _jniġ,
+ {{0x657bd2c1,0x68eb806a,0x75f60061,0x4426e309}}, // _tauh, _sygd, háza, apo_,
+ {{0x75f6003e,0x16199094,0x387900ee,0x77ad83a8}}, // káza, _नंबर_, _kssr_, _púxo,
+ {{0xfe0a0028,0x76b3811c,0x60099af1,0x7bc5630a}}, // _mắt_, məyə, нним_, _omhu,
+ {{0x777c07e2,0xe8f68196,0x443fddad,0x53b7816f}}, // _xarx, ылы_, _ivu_, _अतिश,
+ {{0xada601a1,0x6da600b3,0x660d054a,0xadc3801c}}, // ганл, гина, _biak, _giản,
+ {{0x644bd9a5,0x7bc50706,0x660d00e5,0x63a18061}}, // orgi, _amhu, _ciak, _holn,
+ {{0x660d0397,0x443d82f7,0x61e4630b,0xa7858bbe}}, // _diak, nsw_, voil, _مشغو,
+ {{0xb88283c1,0x317f811b,0xc2f68264,0x69c9a3df}}, // čítk, lduz_, _চলতি_, liee,
+ {{0xd94611b3,0xfe0a0028,0xa01b00f2,0x63a1ae6c}}, // леви, _bắt_, _dröm, _moln,
+ {{0x443f8025,0xfe0a001c,0x660d0420,0x317fe30c}}, // _ovu_, _cắt_, _giak, nduz_,
+ {{0xbfa3077f,0xe9d72f42,0x1f660b79,0x75f60333}}, // [60e0] _àwọn_, укт_, лкам, cáza,
+ {{0x7c240850,0x777c01b4,0x6b561133,0x649701c6}}, // _itir, _qarx, _стих, _עדיף_,
+ {{0xaac39344,0x4d4a8be2,0xc8f081a2,0x2b5a02d6}}, // _वेलक, епен_, _चर्म_, _icpc_,
+ {{0x35e59138,0x63a18362,0x3879010c,0xfe0a0129}}, // аців, _aoln, _fssr_, _gắt_,
+ {{0x777c630d,0x69c6630e,0x4426c4ec,0x4c94954f}}, // _tarx, _imke, upo_, рилс,
+ {{0x66e60767,0x44320355,0xdb08802a,0xdee6110e}}, // _бога, _mwy_, _aldá, _боги,
+ {{0x63a1a08f,0x27ea0748,0x443fb0a6,0x644ba4b5}}, // _doln, _ikbn_, _evu_, argi,
+ {{0x44268698,0xdb08816d,0x7c2400fe,0x6d4400f7}}, // ppo_, _omdö, _otir, _ndia,
+ {{0x63a199b7,0x7c240314,0x661b0088,0x661504e8}}, // _foln, _ntir, _čuki, _zuzk,
+ {{0x4424dad8,0x6d44438a,0x660d09ca,0x66028326}}, // _itm_, _adia, _siak, lmok,
+ {{0x7c244c78,0x051680ab,0xb2269229,0x4432026b}}, // _atir, ারীর_, _смел, _awy_,
+ {{0xac192964,0x38601a45,0x7bcac00f,0xe9da80e8}}, // кому_, ktir_, lifu, _яке_,
+ {{0x4424cfd1,0xdb1e15a0,0xe973803d,0x6d440428}}, // _jtm_, _empí, _دهند, _ddia,
+ {{0xfe0a001c,0x44320114,0x69c80036,0x46b40072}}, // _sắt_, _dwy_, èden, ंनाह,
+ {{0x7c240085,0x171b80be,0xe45f0009,0x62354f91}}, // _etir, _אווע, stö_, ребу,
+ {{0x1619835a,0x4432031d,0x38600b97,0x75f60f09}}, // _नंतर_, _fwy_, ftir_, lázn,
+ {{0x98170154,0x7bca8309,0x2a7a01a1,0x200e8300}}, // [60f0] ابرا, kifu, _espb_, _kifi_,
+ {{0xd6db490d,0x62889eed,0x7bca8010,0xd101000c}}, // нте_, _ordo, jifu, लेषण_,
+ {{0x443f8052,0xd6db0ddc,0xfe0a001c,0x38600748}}, // _svu_, хта_, _tắt_, atir_,
+ {{0x63a18b81,0x24891b4a,0x673ae30f,0x00000000}}, // _soln, _kram_, hatj, --,
+ {{0x63a1a8f2,0x6288b3ab,0x22838214,0x493a0039}}, // _poln, _ardo, _sık_, _בגרו,
+ {{0x62888042,0x8c1c03de,0x443fd542,0x7bca8c6f}}, // _brdo, עווי, _vvu_, gifu,
+ {{0xe2998c07,0x63a19be6,0x5b580051,0xea0000ff}}, // тай_, _voln, אשון_, _chặt_,
+ {{0x63a1809a,0x62838b80,0x200e930c,0x48fc001b}}, // _woln, rvno, _aifi_, लेको_,
+ {{0xa91d8067,0x6288e310,0x24890069,0x69c9e311}}, // jdže, _erdo, _nram_, riee,
+ {{0x69c981b0,0xb06880f7,0x3f810077,0x9e6581e2}}, // siee, اصيل_, ndhu_, авод,
+ {{0xf772004c,0x2c1d80c8,0x75f884c3,0x7bfa8158}}, // חקי_, _নিয়ে_, líza, _ספעצ,
+ {{0x24896312,0x6933804e,0x44320114,0x4424b5a4}}, // _bram_, _اکثر, _pwy_, _ytm_,
+ {{0x200e84be,0xd1b8803d,0x75f883a8,0xb4c003b7}}, // _fifi_, _حالا_, níza, ंछे_,
+ {{0xf77196a5,0xbcfb007b,0xa01b0106,0x24896313}}, // _ذات_, mfél, _tröj, _dram_,
+ {{0x24891fea,0x6569e314,0xdb250019,0xa01b18b6}}, // _eram_, _obeh, ásár, _frök,
+ {{0x38603816,0x24890357,0x13d286ab,0x31260048}}, // ttir_, _fram_, _तद्भ, рдаг,
+ {{0xfbdf0548,0xa3e58074,0x2d920870,0x00830037}}, // [6100] guês_, नवा_, _inye_, олто,
+ {{0xee0e81ab,0x6d5be315,0x320f816b,0xf77201a8}}, // िद्ध_, _acua, _ligy_, غاء_,
+ {{0x38606316,0x2fc780dd,0x660286cb,0xdb1e0580}}, // stir_, _mmng_, tmok, _impà,
+ {{0x38606317,0x9325003d,0x75f604e8,0xb8cc0264}}, // ptir_, _پرین, zázn, _কই_,
+ {{0x69d66318,0x2722801b,0x6e29007a,0x66028cf9}}, // mnye, nční_, _čebe, rmok,
+ {{0x051680c8,0x6d5bb724,0x81d700ab,0x2a6ca404}}, // ারের_, _ecua, িতঃ_, _ppdb_,
+ {{0x2d96885f,0x7bcae319,0x2d920a2c,0xa59680e8}}, // _крас, rifu, _onye_, _кращ,
+ {{0x2369803b,0xdca6a318,0x69d61fc5,0x7bca805d}}, // đaja_, _каби, nnye, sifu,
+ {{0x442480ee,0x673a8019,0x73768fe6,0x62830118}}, // _utm_, tatj, _выех, _ánov,
+ {{0x07b88307,0x2d925b41,0x8146804e,0x69d600ee}}, // اهدة_, _anye_, _چنان, hnye,
+ {{0x2d8004df,0x2486813c,0xc2248065,0x673ae31a}}, // _baie_, lvom_, _اکتو, ratj,
+ {{0x58868a8e,0x200e9532,0x2fc782f7,0xc8c79e29}}, // _выка, _wifi_, _emng_, اوین_,
+ {{0xa3ca901c,0x2d800748,0x3a2600c3,0x2d920176}}, // _लगा_, _daie_, _čopa_, _dnye_,
+ {{0x2d9252a1,0x9f5844d0,0xa91d80ce,0xfd1f01e8}}, // _enye_, _miró_, rdže, scì_,
+ {{0x31c7076a,0xf9c70098,0x19950098,0xdb018036}}, // асав, ащан, бавя, _colè,
+ {{0x2486803a,0xbf0700d4,0x4e1d0105,0x391513bd}}, // kvom_, _वृषभ_, _बंबई_, смар,
+ {{0xa3d6853e,0xd7aa8fd5,0x4059053d,0x3dc902c4}}, // [6110] ाचा_, _चकाच, طلاح_, _imaw_,
+ {{0x753d0117,0x2fc70104,0xdf3900f7,0x2d80011b}}, // lasz, òng_, اكات_, _zaie_,
+ {{0x75158307,0x3f810dab,0x39468084,0x3ea74991}}, // مواض, rdhu_, _odos_, ânt_,
+ {{0x241981bb,0xb99500f7,0xdb1e002a,0xbcfb0061}}, // годы_, _الحب, _impá, yfél,
+ {{0xb8cc00c8,0x320f8326,0x2d588087,0x629a01d6}}, // _কে_, _rigy_, ричь_, ätov,
+ {{0x6738920e,0x75f881df,0x3946e31b,0xfe0a001c}}, // _nevj, ríza, _ados_, _lắp_,
+ {{0xe1ff0028,0xdb01e31c,0x21390168,0x753d5dea}}, // _khó_, _kolé, _kesh_, kasz,
+ {{0x2249803b,0x20110010,0x69cd031d,0x63a501ec}}, // čak_, _hizi_, niae, _lohn,
+ {{0xdb01887a,0x78ad3e2e,0x9f59041c,0x0dc88bc7}}, // _molé, nyav, _fusô_, _тури_,
+ {{0x3fb780c8,0xbcfb007b,0x2019631d,0x291f8511}}, // ীক্ষ, rfél, _musi_, scua_,
+ {{0xbebc8029,0x66e5961a,0x20190f33,0x6b9c8722}}, // ecīb, сона, _lusi_, _òrga,
+ {{0x6d59a6bd,0x75f61307,0xe1ff4462,0x6b818079}}, // ngwa, rázo, _nhó_, _halg,
+ {{0x3f69831f,0x69cd0114,0xdb0186c0,0xb0c706a7}}, // _било_, diae, _solè, _रेलग,
+ {{0x6b818074,0xe1ff046d,0xdb018722,0x272281d0}}, // _jalg, _ahó_, _polè, rční_,
+ {{0x6b81e31e,0x2d9200ee,0x2d800162,0x213903ed}}, // _malg, _tnye_, _taie_, _besh_,
+ {{0x2019190d,0xe1ff00ff,0xdb01e31f,0x69cd4850}}, // _busi_, _chó_, _colé, giae,
+ {{0x201157b3,0x69d60eb9,0x21200214,0x442b0144}}, // [6120] _bizi_, rnye, rcih_, cpc_,
+ {{0x6b818feb,0x61e98c0b,0xc49b01c6,0x2a69866f}}, // _nalg, hoel, _עשית, łaby_,
+ {{0x6b7b0f60,0x20110214,0x7bce01f6,0x0e66072f}}, // _גרינ, _dizi_, mibu, скен,
+ {{0x24868ee0,0x20196320,0x7e4b0019,0x395d8326}}, // tvom_, _fusi_, اسلہ_, _ncws_,
+ {{0x61e9838e,0x201910e1,0xeb108105,0x9f5801e8}}, // doel, _gusi_, ाश्त_, _dirò_,
+ {{0x6b8182e6,0x2011061b,0xf99f0247,0x2d809e1e}}, // _calg, _gizi_, _chèf_, žie_,
+ {{0xb28691e9,0x75240102,0xe7dd016f,0x7d160114}}, // сылк, _ofiz, _मदतप, _ogys,
+ {{0x75d69a37,0x7bce0a4f,0x61e98102,0x79843328}}, // _ايرا, hibu, goel, ndiw,
+ {{0xf48483f8,0xa3c90b3b,0xa3e81664,0x3ce001c0}}, // _بازی, लोड_, यका_, _nxiv_,
+ {{0x310483b7,0xfe0a001c,0x2fca0042,0x6b81d625}}, // शेषः_, _sắp_, _jmbg_, _galg,
+ {{0x7bdf82be,0x63a509f4,0xbcfb016b,0xe21696a5}}, // éque, _rohn, rfém, _طباع,
+ {{0xd6d78987,0x8cb50023,0x64428009,0x604181bc}}, // сть_, उनलो, osoi, _ịmer,
+ {{0xac1903c7,0xb0c7131d,0x753d5dea,0x6ce70048}}, // рогу_, _रेंग, rasz, сіне,
+ {{0x7d160065,0x442b0953,0x69da00e7,0x9f5800f7}}, // _egys, rpc_, éten, _bhrí_,
+ {{0x7f3b8159,0x20190475,0x753d0019,0xe1ff001c}}, // _געוו, _susi_, pasz, _phó_,
+ {{0x38350160,0x63a50352,0xdcfb9c67,0x23ab806a}}, // жнар, _wohn, _sauč, _højt_,
+ {{0x270e8086,0xdb05020f,0xa91dd869,0x69cd0355}}, // [6130] lən_, _kohë, ndža, riae,
+ {{0x44292778,0x161d00d4,0x78ad038a,0x61e9b847}}, // _ita_, _बंदर_, ryav, zoel,
+ {{0x163b0013,0xb8f02261,0x270e8085,0xdcfb9487}}, // اسطة_, _वे_, nən_, _vauč,
+ {{0x4ac3816f,0x2011004f,0x20196321,0x7afa5b64}}, // _वेगव, _wizi_, _tusi_, ütte,
+ {{0xa3bd491d,0x04679240,0x201903ff,0x61e983b2}}, // _आता_, стем, _uusi_, voel,
+ {{0x1ae280ab,0x270e8085,0x62830118,0x75228037}}, // গেছে_, kən_, _ános, acoz,
+ {{0x6b81e322,0x61e99c11,0xb16e008b,0x9f580037}}, // _valg, toel, _þýði, _tirò_,
+ {{0x270e8086,0x44296323,0x7bce29f7,0x6b819337}}, // dən_, _ota_, zibu, _walg,
+ {{0x44296324,0x98a0017f,0x9f458198,0x7bce31ba}}, // _nta_, rbić_, yllä_, yibu,
+ {{0x7c2982a0,0x3e7a8029,0x656d409f,0x20118019}}, // _iter, _būt_, _abah, özi_,
+ {{0x44296325,0x753b8a0f,0xd9fb016f,0xa3d686ab}}, // _ata_, _keuz, ्षात_, ाचर_,
+ {{0x7c2983bb,0x3f831600,0x960400d4,0xa50a0073}}, // _kter, _laju_, रगेट_, иена_,
+ {{0x7bce6326,0xf7459285,0x7641d64b,0xccfa0221}}, // tibu, _дело, ssly, рхні_,
+ {{0xf8078cd0,0x7643808e,0x248d8b80,0x69dd0140}}, // _учен, _tvny, _mrem_, _ajse,
+ {{0x44296327,0x9c870efc,0x316900ee,0x201f8144}}, // _eta_, _počí, ffaz_, lqui_,
+ {{0x7c29a467,0x3ce010af,0x753bd660,0x7bce3328}}, // _oter, _txiv_, _neuz, sibu,
+ {{0xa2ca03bb,0x3f830867,0x7c29e328,0x201fbb40}}, // [6140] _हेर्, _baju_, _nter, nqui_,
+ {{0x3f85805d,0x46f60098,0x24800214,0x75f601d0}}, // ndlu_, очет, _isim_, kázk,
+ {{0x69cb8b3c,0x7c29e329,0xe29a0f27,0xdd1c816b}}, // _omge, _ater, _тан_, rážo,
+ {{0xf53f0bc5,0x213f810b,0x44290711,0x7c2989d1}}, // lkår_, jauh_, _yta_, _bter,
+ {{0xac0aae15,0x240a9b78,0xdca38dc9,0x270e8085}}, // анда_, анди_, маци, zən_,
+ {{0x270e8201,0x7522b7b7,0xa01b016d,0xdb0501b3}}, // yən_, scoz, _pröv, _bohè,
+ {{0x64429dbd,0x248d87f1,0x96631bb1,0x3f85ad50}}, // ssoi, _erem_, _окре, ddlu_,
+ {{0x2480003a,0x394009b2,0x248d8b40,0xdd92803d}}, // _osim_, nais_, _frem_, _تور_,
+ {{0x673c059c,0x248d807a,0x28d00ec5,0x6010046d}}, // _kerj, _grem_, _सेमि, _gómì,
+ {{0x39402039,0x661c632a,0x2d84e32b,0xa91d826c}}, // hais_, _kurk, _hame_, rdža,
+ {{0x39402338,0x24801087,0x2d84e32c,0x673c632d}}, // kais_, _asim_, _kame_, _merj,
+ {{0x63a8a191,0x69da0065,0x270e8201,0x39400df1}}, // _hodn, étel, rən_, jais_,
+ {{0x270e8201,0x442902f7,0x7c298609,0x39405042}}, // sən_, _qta_, _xter, dais_,
+ {{0x673c3f01,0x63a881a1,0xd0078087,0x98a382ee}}, // _nerj, _jodn, _дече_, _циље,
+ {{0x63a8bc6d,0xd13b0098,0x24800009,0xf1a70705}}, // _modn, иха_, _esim_, оран,
+ {{0x2d84e32e,0x224d632f,0x2d8500eb,0x656d20cf}}, // _name_, ček_, ēles_, _ubah,
+ {{0x673c14ff,0x44296330,0x661c00ad,0x2bdba261}}, // [6150] _berj, _uta_, _aurk, _बदला,
+ {{0xf7730872,0x59ca8105,0x656080f7,0xc72b00e8}}, // بار_, ़ोतर, _acmh, ріал_,
+ {{0x661c4573,0x248d8bda,0x673c5d01,0x2d84cf3e}}, // _curk, _srem_, _derj, _bame_,
+ {{0x7c298358,0x7e9b8051,0x394009b2,0x427a00be}}, // _pter, יסבו, cais_, _פארג,
+ {{0xc86405a5,0x506404fa,0xdb170118,0xa3d6816f}}, // _отри, _отра, _alxé, ाचं_,
+ {{0x32096331,0xd0fa0b04,0x447c00be,0x248d8087}}, // lmay_, ्धरण_, ינדע, _vrem_,
+ {{0x61e48353,0x661c048d,0xb113019d,0x63a8816b}}, // čiln, _gurk, _bụga, _dodn,
+ {{0x75f6003e,0x32091a03,0x2d848039,0x248de332}}, // rázk, nmay_, _game_, _trem_,
+ {{0xdb1e0009,0xc21b8424,0xb3678098,0x661c00fe}}, // _ympä, _पूरब_, _мъжк, _zurk,
+ {{0x6d5d0079,0x2d84e333,0x394026c5,0xaec59bdc}}, // egsa, _zame_, zais_, збол,
+ {{0xe9df0324,0x25a90118,0xdb01816b,0x3f85808e}}, // _ojú_, _boal_, _kolí, rdlu_,
+ {{0x6281e334,0x69cb90f6,0xdb1c00f7,0x24800d56}}, // _islo, _umge, mhré, _psim_,
+ {{0x39400e37,0x6b855c52,0x60020187,0x6459cc2b}}, // vais_, _bahg, lôme, nuwi,
+ {{0xe81f0816,0xe9df4bbe,0x2c641e2b,0x201d82d5}}, // _बढ़ा_, _ajú_, röd_, _iuwi_,
+ {{0x39406335,0x60020187,0x8f9a81c6,0x3af50198}}, // tais_, nôme, _ליסי, дятс,
+ {{0x248001e9,0x673c01b9,0x201d83f8,0x3209033e}}, // _tsim_, _serj, _kuwi_, gmay_,
+ {{0x673c6336,0x24800748,0x61f605ee,0x55bb01c6}}, // [6160] _perj, _usim_, rlyl, _למקו,
+ {{0x2d84bada,0x39400e37,0xdee6171c,0x32090079}}, // _same_, sais_, пози, amay_,
+ {{0x64466337,0x673c6338,0xa01b016d,0xdb01b49a}}, // lski, _verj, _brös, _bolí,
+ {{0x63a88353,0x271901d0,0x320901b4,0x6002041c}}, // _sodn, třní_, cmay_, dôme,
+ {{0x673c0855,0xc95304de,0xa2d40a74,0x645980e4}}, // _terj, _אמר_, णपक्, guwi,
+ {{0x64466339,0x60d68084,0x60c40234,0x79860a03}}, // iski, _žymo, mzim, _hakw,
+ {{0x63a8905e,0xdee3196e,0xdb018825,0xd35700be}}, // _vodn, _зори, _folí, ליסי_,
+ {{0xcb130051,0xa01b0004,0x61e482d4,0x63a88035}}, // עלה_, _tröt, čilo, _wodn,
+ {{0x60c427f6,0x7986633a,0x5ea980ab,0x1b1b8264}}, // nzim, _makw, _ছেলে, _নজরে_,
+ {{0x25a90500,0x6604633b,0x9178826b,0x60021243}}, // _soal_, _ahik, _bẹ́_, lômb,
+ {{0xb8f39880,0x32090637,0x66041cfe,0x79861b6c}}, // _हे_, ymay_, _bhik, _oakw,
+ {{0x6604633c,0x798620c4,0xd5bb1cad,0x1a9b80be}}, // _chik, _nakw, ссо_, _ליבע,
+ {{0xafdb0aa2,0xdcef0087,0x6446633d,0x66045d7f}}, // _svøm, decă, gski, _dhik,
+ {{0x60c4633e,0xa01b0338,0x7f4196f0,0xea000129}}, // dzim, _orör, calq, _đạc_,
+ {{0x61ed633f,0x79866340,0x2b4c826c,0xd37800e8}}, // toal, _bakw, _mddc_, ччя_,
+ {{0x52142804,0x65bf020f,0x777a8102,0x765a83f7}}, // едст, _bëhe, letx, muty,
+ {{0x28c70076,0x79866341,0x32093dd0,0x6d4d5735}}, // [6170] _रेखि, _dakw, rmay_, _idaa,
+ {{0x7c2d00b4,0xdb01816a,0x32093cc0,0x61ed4395}}, // _itar, _solí, smay_, soal,
+ {{0x4fc41b47,0xf77001a8,0x752d8333,0x1a650061}}, // хста, طال_, ñazo, _تیزی_,
+ {{0xf65007bd,0x6459809c,0x25a9807b,0x79866342}}, // ائل_, tuwi, ðal_, _gakw,
+ {{0x69c0931a,0x8ccd809a,0xf3ff0187,0xdb01e343}}, // dhme, _देशो, ntão_, _volí,
+ {{0x38696344,0x79863937,0x765a818c,0xeb969a19}}, // mtar_, _zakw, kuty, диш_,
+ {{0xf7720039,0x79866345,0xa01b1614,0xdb0502df}}, // _שקל_, _yakw, _trös, _ilhó,
+ {{0xdb08a246,0x3a2c8118,0x69c0822b,0x64466346}}, // _modè, _dtdp_, ghme, yski,
+ {{0x7c2d02a0,0x69dbe347,0x7d1bcb06,0xc5e99d40}}, // _ntar, rnue, _igus, टकीय_,
+ {{0x442da2cd,0x6604020f,0x644647c4,0x3869383c}}, // _ite_, _shik, vski, itar_,
+ {{0x6281a8e1,0x7c2d24fc,0x6446009a,0x90e782e3}}, // _uslo, _atar, wski, _کسان,
+ {{0x6446003a,0xe9458077,0xd7f88087,0x3869343f}}, // tski, _آرای, ală_, ktar_,
+ {{0xb4b84823,0x644601bb,0xdb1e002a,0x201d876d}}, // चने_, uski, _impú, _uuwi_,
+ {{0x79866348,0xee399fab,0x395fd3a9,0x38696349}}, // _sakw, _днк_, lgus_, dtar_,
+ {{0x644606b1,0x386903a7,0xca7514ef,0x7c2d634a}}, // sski, etar_, нуты, _etar,
+ {{0x64460289,0x38690455,0x395fe34b,0x7d1be34c}}, // pski, ftar_, ngus_, _ngus,
+ {{0x60c4003b,0xa0670819,0x386900f7,0x18671132}}, // [6180] uzim, мата_, gtar_, мати_,
+ {{0x7d1bd298,0xe45703c8,0x79862198,0x69cf3392}}, // _agus, _קייט_, _wakw, _emce,
+ {{0x442dceb6,0x3f87e34d,0x4420634e,0x7986634f}}, // _ate_, _manu_, _hui_, _takw,
+ {{0x44202cdd,0x89361125,0x3f878032,0x442d80b9}}, // _kui_, _تعدا, _lanu_, _bte_,
+ {{0x75e803bf,0x7989aaf1,0x44200036,0x0c7a99ea}}, // mızd, ddew, _jui_, تصاب_,
+ {{0x28c709a3,0x44200681,0x7529e350,0x442de351}}, // _रेटि, _mui_, _efez, _dte_,
+ {{0x44203d57,0x442db4c0,0xfd6500ff,0x69c0e352}}, // _lui_, _ete_, _chuố, thme,
+ {{0x31600380,0x442003d3,0x3f8782a3,0x75e803bf}}, // ngiz_, _oui_, _aanu_, nızd,
+ {{0xf4d900c8,0x6d40e353,0x30148cdf,0x6e2d8279}}, // _সর্ব, _iema, ндир, _čaba,
+ {{0x2018082e,0x6d40e354,0x69c0a5ee,0x909b03c8}}, // _jiri_, _hema, shme, קסיק,
+ {{0x6d408458,0x2018322e,0x05c28996,0x0f570039}}, // _kema, _miri_, _शताब, ויים_,
+ {{0x6d40e355,0x25a6e356,0xa3d684e5,0x20186357}}, // _jema, njol_, ाचक_, _liri_,
+ {{0x6d40845c,0x44200698,0xf9c782e3,0x39448110}}, // _mema, _cui_, _تحلی, mams_,
+ {{0xf3ff00a9,0xa3c90063,0x6d40878a,0xb3458073}}, // rtão_, लों_, _lema, maçã,
+ {{0xb34580a9,0xf3ff00a9,0xf1b280a5,0xd6cf99b8}}, // laçã, stão_, _जवान, _рт_,
+ {{0x23698e78,0x386903e5,0x44206358,0x39448110}}, // đaji_, ttar_, _fui_, nams_,
+ {{0x7c20b432,0x20186359,0xb3458073,0x5f952b98}}, // [6190] _numr, _biri_, naçã, ниат,
+ {{0x645d41cb,0x3869635a,0x2018059c,0xdbd68406}}, // musi, rtar_, _ciri_, _jään,
+ {{0x6449e35b,0x2018208b,0x5d558425,0x6d409a23}}, // _svei, _diri_, екет, _bema,
+ {{0x6285635c,0x3869635d,0x3f870024,0x3944989e}}, // _isho, ptar_, žnu_, jams_,
+ {{0x6d4090b6,0x3944a57b,0x645d19ad,0x1c008074}}, // _dema, dams_, nusi, _लीहल_,
+ {{0x7bd52ed0,0x657d37b8,0xb34583a7,0x3a210101}}, // nizu, hesh, daçã, _kuhp_,
+ {{0x645d022e,0x387f80b9,0x63a80106,0xadc38129}}, // husi, wwur_, ödni, _giến,
+ {{0x6d40844e,0x657d1cc8,0x201852e3,0x645d2bc0}}, // _gema, jesh, _ziri_, kusi,
+ {{0xe0d9920c,0xb3458073,0x657d3d1c,0x20180f50}}, // яви_, gaçã, desh, _yiri_,
+ {{0xfd650028,0x3fcd80ab,0x75f88e14,0x60068150}}, // _thuố, রক্ষ, vízi, нным_,
+ {{0x44204db7,0xf1bf2ed8,0xf77200be,0x62852221}}, // _sui_, _olá_, נקט_, _nsho,
+ {{0x4420002e,0x3219052a,0x6d40e35e,0x394481a9}}, // _pui_, _nisy_, _xema, cams_,
+ {{0x4420635f,0xb34580a9,0x7fd5b73a,0x645d6360}}, // _qui_, caçã, _пікі, gusi,
+ {{0xdee5cddf,0x61fba40a,0x44200104,0x66e59d85}}, // _поли, llul, _vui_, _пола,
+ {{0x61e90025,0x645d0a8e,0xe2971b67,0xf770819f}}, // čeln, ausi, нар_, _خان_,
+ {{0x20180a54,0x645d3ebe,0x63738059,0x44206361}}, // _siri_, busi, lını, _tui_,
+ {{0x6d463167,0x628504e8,0x6d40960c,0x394fc32a}}, // [61a0] maka, _esho, _rema, _mdgs_,
+ {{0x04790077,0x7d042a0c,0x6373817b,0x61fb805d}}, // _کلیک_, _izis, nını, hlul,
+ {{0xb34580a9,0x6d408812,0x2018007a,0x61460294}}, // zaçã, _pema, _viri_, _пена,
+ {{0x10a60ada,0x201802a6,0x291d882e,0xadc380ff}}, // _зимн, _wiri_, _ngwa_, _viến,
+ {{0x25ad837a,0x6373880a,0xb3458187,0x20186362}}, // _doel_, kını, xaçã, _tiri_,
+ {{0xf09f0142,0x69d66363,0x6d460c9e,0xadc38104}}, // _ngày_, liye, haka, _tiến,
+ {{0x6d40e364,0x6d461868,0x39448df1,0x657d0c53}}, // _tema, kaka, tams_, yesh,
+ {{0x6d46111f,0xb34580a9,0x7bc381ac,0xdbd68009}}, // jaka, taçã, ahnu, _sään,
+ {{0x6d461fb9,0x657d6365,0x61e2a316,0x3944989e}}, // daka, vesh, _vjol, rams_,
+ {{0xb3458003,0x7bc3c431,0x645d1202,0x3dc0076d}}, // raçã, chnu, vusi, _aliw_,
+ {{0x657d6366,0xd6da835f,0x6d463f60,0xd3378039}}, // tesh, яти_, faka, _נראה_,
+ {{0x6d4636ba,0xa3cc83b7,0xb34583a7,0x6562cf3e}}, // gaka, शोर_, paçã, ngoh,
+ {{0x69d63843,0x7afa854f,0x6ad91107,0x6373861c}}, // diye, _hytt, णप्र, bını,
+ {{0xbcfb03d3,0xf99f602f,0x7d04009a,0x657d6367}}, // ffér, _chèn_, _dzis, sesh,
+ {{0x645d6368,0xda780ac8,0x657d122b,0x7d040316}}, // susi, нят_, pesh, _ezis,
+ {{0x657d285d,0x69c436f1,0xceb38039,0x61e9463a}}, // qesh, ghie, _תיק_, čelo,
+ {{0x3f8a00a4,0x7afa8aa2,0x2129008e,0x25ad8bfd}}, // [61b0] _babu_, _lytt, rcah_, _roel_,
+ {{0x628501c5,0x320d80b9,0x752d00fc,0x6002041c}}, // _tsho, zmey_, _hfaz, tôma,
+ {{0x7afa821e,0x69d60b8f,0x62853406,0x394203ba}}, // _nytt, biye, _usho, _feks_,
+ {{0x69c41c1e,0x6373861c,0x69d62fc1,0x3eae0106}}, // chie, zını, ciye, äfta_,
+ {{0x25ad90f4,0x637383bf,0xc86700e8,0x7dc600a2}}, // _voel_, yını, _ятни, _fósf,
+ {{0x6d466369,0x7afa8370,0xa91d9ff9,0x823400d7}}, // zaka, _bytt, mdži, _جریا,
+ {{0xa91d8110,0xda628c4f,0x68fc8085,0x61fbe36a}}, // ldži, авщи, ərdi, tlul,
+ {{0x798d3ebe,0x25bf80ee,0xa224803d,0xdce405a2}}, // ndaw, _ulul_, دروه, _ubić,
+ {{0x63738059,0xa91d8110,0x61fbe36b,0xa3d100c2}}, // tını, ndži, rlul, वफा_,
+ {{0x6d462917,0x69d65207,0x661b8009,0xddc900d2}}, // waka, ziye, _hiuk, _ćošk,
+ {{0x63738a0b,0x6d461999,0x644d344c,0xed5a1bdc}}, // rını, taka, _avai, мон_,
+ {{0x63738a0b,0x2369803a,0x69d60079,0x443f81bc}}, // sını, đaju_, xiye, _kwu_,
+ {{0x6d460859,0xc8d001fe,0x798d3fb0,0xdb1c007b}}, // raka, _सेंट, ddaw, firð,
+ {{0x69e3856f,0x799d0039,0x386de36c,0x661b8d15}}, // šteđ, _answ, mter_, _liuk,
+ {{0x6d461904,0x3f8a0274,0x43430098,0x60c98214}}, // paka, _rabu_, ресв, lzem,
+ {{0x3f8a636d,0x798d0bb1,0x75f60061,0x644ba475}}, // _sabu_, gdaw, rázs, ksgi,
+ {{0x386dc33f,0x7bc18364,0x69d6636e,0xbcfb04e8}}, // [61c0] nter_, _ollu, riye, sfér,
+ {{0x44320e35,0x386de36f,0x69d66370,0x798b86a9}}, // _ity_, iter_, siye, _lagw,
+ {{0x386d8613,0xbcfb6371,0x69c46352,0x644d00eb}}, // hter_, ngén, phie, _zvai,
+ {{0x386de372,0x66098a54,0x7afa81a3,0x798bad80}}, // kter_, _chek, _rytt, _nagw,
+ {{0x672300d2,0x3f8a6373,0x661b9295,0x386db839}}, // žnja, _tabu_, _diuk, jter_,
+ {{0xa3cc809a,0xa3c20075,0x38601a2e,0x60c9af28}}, // शों_, ंसि_, muir_, dzem,
+ {{0xe9e6b1db,0x443f815e,0x38602216,0x798bb1ba}}, // кцио, _ewu_, luir_, _bagw,
+ {{0x386d8bfa,0xdb050118,0xe1ef92c5,0x661b8122}}, // fter_, _johá, يسي_, _giuk,
+ {{0x386dc4ea,0xbc6a936d,0x38600531,0xa3c24638}}, // gter_, _زمان_, nuir_, ंसा_,
+ {{0x61ed9148,0x7afa8009,0x2cc4880a,0xdb0881ac}}, // čala, _tytt, ırdı_, _hodí,
+ {{0x443200f1,0x06d580ab,0xa91d8110,0x644d0612}}, // _aty_, _সুবি, zdži, _svai,
+ {{0x4424dc05,0x6e239a2e,0x798b838a,0x7c241581}}, // _kum_, _dunb, _gagw, _buir,
+ {{0x386da71d,0x7c240068,0x442496b2,0x6d440102}}, // cter_, _cuir, _jum_, _deia,
+ {{0x442482ec,0x386045c6,0x9f9a0074,0x452880ab}}, // _mum_, duir_, _jääb_, মরিক_,
+ {{0x4424a991,0xac180258,0x241821f6,0x69c2a525}}, // _lum_, тору_, торы_, _kloe,
+ {{0x201c822e,0x11d90013,0x31c42aee,0x7c240014}}, // _hivi_, روءة_, асув, _fuir,
+ {{0x4424e374,0xa91d8110,0x7bd8b071,0x201c92a5}}, // [61d0] _num_, udži, kivu, _kivi_,
+ {{0x6609d196,0xdb1a8c1d,0x69c28114,0xa91de375}}, // _shek, _altí, _lloe, rdži,
+ {{0xa91dcd57,0xa3c2097d,0xd7f844e0,0x60c9e376}}, // leže, ्फर_, тут_, zzem,
+ {{0x38601115,0x1eea026a,0x644b816d,0x386d8b64}}, // buir_, رونی_, tsgi, yter_,
+ {{0x44248c6e,0x386de377,0x33d58a18,0xad9b04c3}}, // _cum_, xter_, лікт, clúe,
+ {{0x4424b773,0x3949038e,0xadc3801c,0x60c9b102}}, // _dum_, laas_, _thắn, vzem,
+ {{0x69c290f4,0x4424dd36,0x6609e378,0xb3648098}}, // _bloe, _eum_, _thek, ръчк,
+ {{0x386d806f,0x2489003b,0xceb200be,0x60c9b11d}}, // tter_, _osam_, _פין_, tzem,
+ {{0x386d83d3,0x44248014,0x6e23c1de,0x6d444576}}, // uter_, _gum_, _sunb, _reia,
+ {{0x798b84df,0xee3995b5,0x7bc1e379,0x60c9d6bd}}, // _wagw, нно_, _ullu, rzem,
+ {{0x4424834a,0x24893996,0xb8fa8f0a,0x61e92bea}}, // _zum_, _asam_, _डे_, čelj,
+ {{0xb2ba8158,0xf41f0009,0x64a60071,0xaee38032}}, // _אמער, yvät_, гама, _aiṣi,
+ {{0x7c24209c,0x3a258b3c,0x394902a3,0x4424e37a}}, // _quir, _hulp_, daas_, _xum_,
+ {{0x7052845a,0xf745e37b,0xa0668adb,0x3f81111b}}, // _انوا, _секо, _саша_, jehu_,
+ {{0xfc3f04c3,0xdb0503b0,0x248900eb,0x656982f7}}, // rxía_, _pohá, _esam_, _oceh,
+ {{0x201c80d2,0x056623d7,0x39490079,0x7c240090}}, // _zivi_, _свин, gaas_, _tuir,
+ {{0xbcfb637c,0x98a30103,0x600a017d,0x4432326a}}, // [61e0] ngél, бите, енам_, _uty_,
+ {{0xb8dc0a27,0x44249d8b,0x351b0051,0x2d82637d}}, // _अथ_, _rum_, _אופנ, leke_,
+ {{0x44248a64,0xdb08e37e,0xfc3f0118,0x3949637f}}, // _sum_, _podí, nxín_, baas_,
+ {{0x2d82082e,0x65660359,0x6ce6a133,0x7dc60118}}, // neke_, ngkh, ліле, _fóse,
+ {{0x7bd8995c,0x61e44453,0x7e61915b,0x320001c0}}, // tivu, mnil, hulp, mliy_,
+ {{0x61e427aa,0x2d826380,0xa91d8669,0x69c29699}}, // lnil, heke_, ndžu, _sloe,
+ {{0x69c2c38b,0x2d826381,0x201ce382,0x79808545}}, // _ploe, keke_, _rivi_, temw,
+ {{0x61e46383,0x48aabaa3,0x2d82120e,0x7bd883a0}}, // nnil, етом_, jeke_, sivu,
+ {{0x69c28b3c,0x442480ee,0x61e40b74,0x76aa8085}}, // _vloe, _uum_, inil, məyi,
+ {{0x53b8863a,0x76aa8085,0x61e46384,0x307780f7}}, // _अविश, ləyi, hnil, احية_,
+ {{0x7413845b,0x200280eb,0x201ce385,0xa91d84c4}}, // _اولا, ēki_, _vivi_, veže,
+ {{0xb11301bc,0x61e4016b,0x25a00036,0xdcf6011c}}, // _bụla, jnil, _cnil_, _qayğ,
+ {{0xdfda8098,0x201ce386,0xa91d8968,0x5c741af1}}, // _пък_, _tivi_, teže, блят,
+ {{0x39490079,0x00000000,0x00000000,0x00000000}}, // waas_, --, --, --,
+ {{0x39492692,0x2d820234,0x539b8039,0x7e618e65}}, // taas_, beke_, עילו, culp,
+ {{0xa91d807a,0x248901c5,0x61e46387,0xf99f00ff}}, // seže, _tsam_, gnil, _thèm_,
+ {{0x361c0051,0x3f8e8b67,0x06b200ab,0x24890187}}, // [61f0] _אוהד, _kafu_, _টেলি, _usam_,
+ {{0xb4c1053e,0x4439368b,0x600b02d0,0x39496388}}, // ंनी_, mps_, küme, saas_,
+ {{0x6723012b,0xa3c2009a,0x37071ef8,0x44393437}}, // žnjo, ंसर_, учав, lps_,
+ {{0x645d8186,0x39468088,0x29e82795,0xe80b8fd5}}, // _åsik, _neos_, lğan_, _सीना_,
+ {{0x64a5981d,0xdca5b725,0xdcfd0110,0x7e61811b}}, // рала, рали, kesč, zulp,
+ {{0x4255867c,0x44390722,0x644f0834,0x2d8201f6}}, // атит, ips_, isci, zeke_,
+ {{0xdce9803a,0x02d88ec5,0xadc3827d,0xfbb801c6}}, // _obeć, _भेदभ, _chản, רפות_,
+ {{0x3946bd27,0x020580a9,0x76aa8085,0x645d026c}}, // _ceos_, јзин, cəyi, krsi,
+ {{0x6d4bc8e1,0x798f0358,0x394687b6,0x3f8e8c2e}}, // maga, _macw, _deos_, _bafu_,
+ {{0x6d4bc1a8,0x2d826389,0x660d56f9,0x61e40968}}, // laga, weke_, _ahak, znil,
+ {{0x2d82638a,0x660d326a,0x61e4638b,0x645d115b}}, // teke_, _bhak, ynil, ersi,
+ {{0x6d4be38c,0x6b838c90,0x660d022e,0x58d98b33}}, // naga, leng, _chak, _идея_,
+ {{0xe72e8ae7,0x2d82130e,0x660d638d,0x7e61bbb0}}, // _ме_, reke_, _dhak, sulp,
+ {{0x6b83e38e,0x6d4ba607,0x60cd1a1d,0x2d8206a0}}, // neng, haga, dzam, seke_,
+ {{0x6d4bc040,0x2bb88697,0x2d8239c7,0x645d1b98}}, // kaga, _अवसा, peke_, arsi,
+ {{0xdb1c03b0,0x7bc51124,0x6b83c100,0x61e40087}}, // nkré, _elhu, heng, unil,
+ {{0x6b839efb,0x75240435,0x798f0079,0x78a9802a}}, // [6200] keng, _igiz, _dacw, nxev,
+ {{0xe80b83db,0x6b83e38f,0xf41f0106,0xeb9a0256}}, // _सीमा_, jeng, svär_, _рио_,
+ {{0x7bdc5f84,0x76aa8201,0x6d4be390,0xa5268071}}, // miru, təyi, faga, амед,
+ {{0x6d4bdc6d,0xed5a8425,0x644409b6,0x25a08216}}, // gaga, тод_, _kwii, ñil_,
+ {{0x69c98079,0x67238968,0x39468661,0x69c66391}}, // dhee, _ognj, _reos_, _ilke,
+ {{0x6b83e1e8,0x7bdc6392,0x35a80d86,0x78bb8216}}, // geng, niru, गाड़, dyuv,
+ {{0x6d4b94ec,0xdee3002e,0x237a00b9,0x6e270300}}, // baga, _дори, _mbpj_, _gujb,
+ {{0x6d4be393,0x3f8e804f,0x7524005d,0x69c98bfd}}, // caga, _safu_, _ngiz, ghee,
+ {{0xdb088efc,0x6b83e394,0x387201a3,0x2d97816b}}, // _dodá, beng, ntyr_, ádež_,
+ {{0x645d05f5,0x61e90754,0x75242221,0x6b8381d4}}, // vrsi, čeli, _agiz, ceng,
+ {{0x6e20822e,0x7c3600f1,0x7bdc6395,0x37a793bf}}, // _kimb, _atyr, diru, ртын_,
+ {{0x6e20e396,0x6602a79e,0x69c9a7f2,0xaadb81c6}}, // _jimb, nlok, chee, _בחבר,
+ {{0xb4c10063,0xb3b401ab,0x76430019,0x6e20e397}}, // ंने_, ुसंख, ányz, _mimb,
+ {{0x660d6398,0x75244a6c,0x6d4be399,0x6602805d}}, // _whak, _egiz, zaga, hlok,
+ {{0x6d4bda00,0x88e880ab,0xd6278b30,0x6e20826c}}, // yaga, _পরিক, _које_, _oimb,
+ {{0xa91de39a,0x4439639b,0x6e20c5b2,0x6b838555}}, // leža, pps_, _nimb, zeng,
+ {{0x6b83e39c,0x6602cba9,0x7bdc0deb,0xad9b002a}}, // [6210] yeng, dlok, biru, clúa,
+ {{0xc60f8935,0x60cd639d,0x6d4be39e,0x960f824c}}, // िष्य_, rzam, waga, िष्ट_,
+ {{0x6d4bc8a9,0xb8fe0d38,0x6e20e39f,0x6b839f96}}, // taga, _थे_, _bimb, veng,
+ {{0x4429568a,0x6b83c50b,0xb113019d,0x656d01e0}}, // _kua_, weng, _jụka, _jcah,
+ {{0x6d4be3a0,0x442963a1,0x6e20c07c,0x24f881bb}}, // raga, _jua_, _dimb, анцы_,
+ {{0x442963a2,0x6d4bc8a9,0x442107dd,0xa91db5b2}}, // _mua_, saga, _jih_, ježa,
+ {{0x6d4b82e8,0x0d678698,0x5b7c0051,0xb5a78056}}, // paga, _възм, דרוא, _край,
+ {{0x6b838c6f,0x2bd988d4,0x6e20e3a3,0x7bdc166a}}, // seng, _भगवा, _gimb, ziru,
+ {{0x44290307,0x6b83e3a4,0x3f9100d2,0x69c9ac52}}, // _nua_, peng, _kazu_, rhee,
+ {{0x69c9e3a5,0x248d84b7,0xdb1c170b,0x656d0db1}}, // shee, _isem_, skré, _acah,
+ {{0x7c360e51,0xb4e783eb,0x7c2996f3,0x442963a6}}, // _styr, _पशु_, _huer, _aua_,
+ {{0xc329010f,0x3f91017f,0x442963a7,0x442163a8}}, // _טו_, _lazu_, _bua_, _aih_,
+ {{0x442105f5,0x44295586,0x248d83bb,0x213f859c}}, // _bih_, _cua_, _jsem_, mbuh_,
+ {{0x442963a9,0x7c2985b4,0xf7718019,0x44211705}}, // _dua_, _muer, _رات_, _cih_,
+ {{0xdd11003e,0x657b80fa,0x442936c2,0x442163aa}}, // _výšk, _obuh, _eua_, _dih_,
+ {{0xe80b946d,0x7bdc5a76,0x7c2981b0,0xb11301bc}}, // _सीता_, siru, _ouer, _gụka,
+ {{0x44290057,0x0ce280c8,0x6e208d02,0x3e71d968}}, // [6220] _gua_, _বর্ত, _rimb, lát_,
+ {{0xbb860013,0x44215b2a,0x657bd34a,0xab6623e7}}, // _الدي, _gih_, _abuh, _увол,
+ {{0x3f910352,0x38720bc5,0x2d923753,0x600203a7}}, // _dazu_, styr_, _kaye_, nômi,
+ {{0x237f003a,0x4429022c,0xa2d1064a,0x7c298865}}, // đuje_, _yua_, _डेट्, _buer,
+ {{0x3f91012b,0x7c2985b4,0x89d88077,0x6602a1e5}}, // _fazu_, _cuer, یوتر_, rlok,
+ {{0x7c29e3ab,0x3f8582fd,0x2d9254f7,0x6e208010}}, // _duer, jelu_, _laye_, _wimb,
+ {{0x69a49513,0x3e718065,0x3f858933,0xe80b959a}}, // काली, ját_, delu_, _सीधा_,
+ {{0x7c299220,0x2d9263ac,0x38349878,0x2fc78637}}, // _fuer, _naye_, онир, _alng_,
+ {{0xdce41487,0x8c439b53,0x2fc7808e,0x25a48144}}, // _obiđ, _нете, _blng_, _inml_,
+ {{0x442963ad,0x7c2f82be,0xb7fa0b04,0x2d9210ab}}, // _rua_, _écra, ्तिम_, _aaye_,
+ {{0x44291fd0,0xd3378051,0x38603a20,0x3e718019}}, // _sua_, _הרבה_, hrir_, gát_,
+ {{0x4429568a,0xceb30051,0x4421035a,0x6fc0016d}}, // _pua_, לית_, _sih_, _böck,
+ {{0x44290104,0x3f85e3ae,0xa91d895e,0xdb018176}}, // _qua_, belu_, ležn, _anlè,
+ {{0x4429001c,0x3e71a792,0xd9b30035,0xa3be83db}}, // _vua_, bát_, ीस्ट, ँघट_,
+ {{0x442163af,0x69d9831d,0xd5bb0098,0x3f910035}}, // _vih_, _ymwe, _бсп_, _razu_,
+ {{0x442963b0,0x463b8158,0x2d9203ec,0x386051be}}, // _tua_, _געהע, _gaye_, frir_,
+ {{0x7bc88afe,0x4421003a,0xb3eb0013,0x6d4f3cd0}}, // [6230] _oldu, _tih_, _معدل_, maca,
+ {{0x6d4f3185,0x2fc7001c,0x6aa88035,0x29023fd3}}, // laca, óng_, _कपूर, _dyka_,
+ {{0x6fc00352,0x7c29e3b1,0xa91d82ce,0x2d9236ba}}, // _möch, _suer, ježn, _yaye_,
+ {{0x6d4f63b2,0x7c29e3b3,0x7bc8e3b4,0x29d38087}}, // naca, _puer, _aldu, nţat_,
+ {{0x7c298510,0x99801010,0x6d49e3b5,0x7bc68196}}, // _quer, lmiş_, _veea, _įkur,
+ {{0xeb999baa,0x248d8353,0x6d4f117d,0x3dc90114}}, // рик_, _vsem_, haca, _llaw_,
+ {{0x998003bf,0x6d4f2fd7,0x853000fc,0x2d99816a}}, // nmiş_, kaca, _haɗe, _óseo_,
+ {{0x3e718065,0x6d4f0503,0x7c299ed4,0xa6b200ab}}, // vát_, jaca, _tuer, _টেকট,
+ {{0x6d4f63b6,0x6e2a81ec,0x2d924dee,0x7c21e3b7}}, // daca, _aufb, _raye_, _tilr,
+ {{0xeb9b89d7,0x3e718019,0x290380eb,0x2d9263b8}}, // _حضرت_, tát_, āja_, _saye_,
+ {{0x6b9c8da1,0x59c90eed,0x28d99513,0x85ea1677}}, // _órga, रसार, _बेरि, идов_,
+ {{0x3e7183b0,0x6d4f3a14,0x69d9c2d6,0x3f85d12e}}, // rát_, gaca, _umwe, selu_,
+ {{0x7d0d0063,0x3e71b74e,0xb8fe80ab,0x2fc780dd}}, // _czas, sát_, _দু_, _tlng_,
+ {{0xa3ac8076,0x386002be,0xe7398226,0x2d92315e}}, // गाय_, vrir_, _тек_, _waye_,
+ {{0xdde90077,0x61e9e3b9,0x7d0d3ebe,0x394b4f42}}, // _گروه_, onel, _ezas, _becs_,
+ {{0x6d4f155e,0x3e5581a9,0x394b07f1,0x3dc90428}}, // caca, māt_, _cecs_, _glaw_,
+ {{0x20c98778,0x186a028b,0xf8c9b13a,0x3e5580eb}}, // [6240] िनिध, рази_, िनिय, lāt_,
+ {{0x61e98352,0x62819699,0x869a2bf3,0x853009ab}}, // hnel, _oplo, итет_, _daɗe,
+ {{0x3e558029,0x628181e9,0x61e98110,0x61fbe3ba}}, // nāt_, _nplo, knel, koul,
+ {{0xa3c2000f,0x98a61860,0xc8c980d4,0x7e7e8106}}, // ंसक_, _диме, िनाट, _äppe,
+ {{0x69c404b7,0x61e98279,0x61fb9c7d,0x64c48035}}, // mkie, dnel, doul, वनेश,
+ {{0xd25a90ca,0x69c463bb,0x629e011f,0x3e5581a9}}, // аци_, lkie, _krpo, kāt_,
+ {{0x6d4f0459,0x04fc80c8,0x6fc4858d,0x61fb93ff}}, // yaca, েশের_, _dòcl, foul,
+ {{0x69c463bc,0x61e98aa2,0xa91d807a,0x6e3888ae}}, // nkie, gnel, režn, _rtvb,
+ {{0x7d0463bd,0x224980d2,0xc2130039,0x628181a1}}, // _nyis, ćaka_, והה_, _eplo,
+ {{0x61e9802e,0xdd0901ac,0xc07b00be,0x69df1cb5}}, // anel, _pôži, סטיש, ziqe,
+ {{0x69c432bb,0x7d0463be,0x6d4f63bf,0xc9070105}}, // kkie, _ayis, taca, _शर्म_,
+ {{0x6d42e3c0,0x6e240362,0x61fbe3c1,0x7dc6026b}}, // nboa, _ciib, coul, _dóso,
+ {{0x6e240d92,0x61e0cf4c,0x69c40035,0x7d040a03}}, // _diib, miml, dkie, _cyis,
+ {{0x60cc8182,0x61e0880a,0x69c400eb,0x39590115}}, // ılmı, liml, ekie, _sdss_,
+ {{0x656b919b,0x2e4a8081,0x69cd63c2,0x7dc607f1}}, // yggh, _тяло_, thae, _góso,
+ {{0x61e0803a,0x99800182,0x225e03c1,0x3c2f0192}}, // niml, rmiş_, átka_, _tüv_,
+ {{0x7d0d0ce7,0x657f01c5,0x69cd45fc,0xfb2700d7}}, // [6250] _uzas, _ibqh, rhae, _وردپ,
+ {{0xdb188125,0xdb21826f,0x61e0e3c3,0x7bce0799}}, // mkvæ, _štít, himl, chbu,
+ {{0x7c2d123d,0x61e0880a,0x6d4d00dd,0x68e481d0}}, // _huar, kiml, _keaa, _židl,
+ {{0x69c463c4,0xb17b04e1,0xb50f06a7,0x60c0c30b}}, // ckie, _småf, _सराय_, kymm,
+ {{0x62818db7,0x7c2d0590,0x18350158,0x69dd01b9}}, // _splo, _juar, _מאָל_, _imse,
+ {{0x443b0a84,0x764701c0,0x38690168,0x7c2d63c5}}, // _mtq_, _twjy, muar_, _muar,
+ {{0x386963c6,0x7c2d63c7,0xe7fa0054,0xf1bf001c}}, // luar_, _luar, ्तरा_, _giác_,
+ {{0x3e75016d,0xdb1e00e7,0x7a1c0176,0x61e98493}}, // måt_, _impô, _pōti, unel,
+ {{0x61fbe3c8,0xa3b88019,0x75298314,0x3e750b81}}, // roul, _ڈالر_, _igez, låt_,
+ {{0x69c43836,0x7c3be3c9,0x200586d8,0x61e9bbd9}}, // zkie, _itur, ulli_, snel,
+ {{0x6281e3ca,0x61fbe3cb,0xbea59652,0x69dd12af}}, // _uplo, poul, чанк, _omse,
+ {{0x3869020f,0x442d88b3,0x3eb8816d,0x44259272}}, // kuar_, _kue_, ärta_, _hil_,
+ {{0x7c2d3d25,0x442d8264,0x386900f1,0xa0a58071}}, // _cuar, _jue_, juar_, _халд,
+ {{0x69dd031d,0x442589da,0x7c2d27ef,0x38690168}}, // _amse, _jil_, _duar, duar_,
+ {{0x442d8364,0xfaa349a1,0xa3ac816f,0xf09f026b}}, // _lue_, наро, गात_, _arà_,
+ {{0x442581cd,0x7c3b83bf,0x7529829b,0xba230d45}}, // _lil_, _otur, _ngez, едск,
+ {{0x69c4149d,0x7c2d0ca9,0x2d8002f7,0x7c2500fc}}, // [6260] rkie, _guar, _ibie_, _fihr,
+ {{0x69c4002f,0x61e08059,0x6d5b8ad4,0x7d0497ad}}, // skie, ziml, _adua, üism,
+ {{0x6d42e3cc,0x7c3b81bf,0x3bbb0039,0x61e0880a}}, // tboa, _atur, _המיד, yiml,
+ {{0x4425831d,0x38691832,0x600b02d0,0xf09f0037}}, // _ail_, buar_, lüml, _frà_,
+ {{0x4425e3cd,0x7bde0a84,0x2a6a119b,0x61e08380}}, // _bil_, _kmpu, lubb_, viml,
+ {{0x69cbbfa6,0x442de3ce,0xa91d817f,0x6d428f3e}}, // _alge, _due_, režl, sboa,
+ {{0x4425b95c,0x249f807b,0x7bde00dd,0x61e081cc}}, // _dil_, _erum_, _mmpu, timl,
+ {{0x442d9220,0x89d8ca1c,0x44258046,0x11d880f7}}, // _fue_, توبر_, _eil_, توبة_,
+ {{0x64498355,0x442d816f,0x4425c575,0x7dc60207}}, // _gwei, _gue_, _fil_, _cósm,
+ {{0x69c5820f,0x30138196,0x61e087d9,0xbfaa94ed}}, // ëher, ндыр, siml, стие_,
+ {{0x3869020f,0x7c2d0500,0x644982af,0xfbb00107}}, // zuar_, _suar, _zwei, ञानम,
+ {{0x7c252f9f,0xd6d79dfe,0x63ba8087,0x2ca01823}}, // _sihr, яты_, _hotn, _brid_,
+ {{0x44259238,0x8fa6c36c,0x629c2c08,0x7c2d20ef}}, // _yil_, _наде, evro, _quar,
+ {{0xe10b8158,0x649210d3,0x442581b4,0x481380e8}}, // _פּאָ, džić, _xil_, _зміс,
+ {{0x63ba8353,0x7c250198,0xfc3f628f,0x7c2d0326}}, // _motn, _vihr, spí_, _wuar,
+ {{0x386908cf,0x7c2d0cb5,0x63ba8035,0x225f0267}}, // tuar_, _tuar, _lotn, čuk_,
+ {{0x290f04b8,0x2d8b63cf,0x7bde0118,0x600b1238}}, // [6270] åga_, mece_, _fmpu, büml,
+ {{0x442de3d0,0x386908cf,0x2d8b03cd,0x290701a9}}, // _rue_, ruar_, lece_, āna_,
+ {{0x38692b42,0x7c3bcf7a,0xf55991cc,0xfe788110}}, // suar_, _stur, قلاب_, ntį_,
+ {{0x2d9905f8,0x21670364,0x69dd63d1,0x2d8b2a63}}, // ndse_, _этог, _umse, nece_,
+ {{0x61ed0087,0x53d102f1,0x386926bf,0x63ba80d9}}, // mnal, _हताश, quar_, _botn,
+ {{0x442d82be,0xfe788110,0x32093cd0,0x3e750106}}, // _vue_, ktį_, llay_, påt_,
+ {{0x4425805f,0x994d01ac,0x6e2e267f,0xdb188106}}, // _vil_, _môžu_, _gubb, jkvä,
+ {{0x442581d8,0x61ed63d2,0xa2b505e8,0x63a8813c}}, // _wil_, nnal, _उपन्, _endn,
+ {{0x442595f8,0x2d8b05c5,0x16dd3792,0x7c3bafc0}}, // _til_, dece_, _मधुब, _utur,
+ {{0x600697ae,0x4425e3d3,0x628545f8,0x2d991412}}, // мным_, _uil_, _opho, edse_,
+ {{0xd1758196,0x6285022c,0x320900e4,0x59758048}}, // _жылы, _npho, klay_, _жылу,
+ {{0xfce61fb4,0x2ca0011a,0x9f458037,0xdb1c128a}}, // домо, _prid_, golò_, jkrá,
+ {{0xb886807b,0xdcfb8024,0x320963d4,0xdbd787b8}}, // rsíð, _obuč, dlay_, pääs,
+ {{0x660e01ac,0x39520039,0x853000fc,0x225e1e1e}}, // _ďaku, ways_, _daɗa, átko_,
+ {{0xe2973210,0x628e00f3,0xdb188106,0xab270e63}}, // мар_, uwbo, ckvä, дора_,
+ {{0x61ed47ab,0x32095099,0x2ca004b7,0xb6868065}}, // gnal, glay_, _trid_, _کھیل_,
+ {{0x66040870,0x628563d5,0x629c1384,0xed598140}}, // [6280] _ikik, _epho, svro, _možd_,
+ {{0x6e2e09ba,0x77fa0076,0xbcfb4d5c,0x7e7b1f3a}}, // _pubb, ्तेक_, rgét, _ćupr,
+ {{0x32094000,0x3dcdab02,0xb9050441,0xef1881a9}}, // blay_, _blew_, _भे_, beļu_,
+ {{0xa91d807a,0x7f4e0363,0x85300326,0xdb1c04e8}}, // režj, _webq, _yaɗa, ckrá,
+ {{0x6d460748,0x63ba807a,0xc95304de,0x61e463d6}}, // ibka, _potn, _שמש_, miil,
+ {{0xdb2181ac,0x1ae980ab,0x61e44d76,0x3dc68c2d}}, // _štát, য়েছে_, liil, skow_,
+ {{0xa93487ac,0xf1a9816f,0xbcfb00d7,0x2d99067f}}, // нейш, कांन, ngér, ydse_,
+ {{0xa9c3835f,0xa3ac800f,0x61e40e68,0xdb01e115}}, // вськ, गाह_, niil, _polò,
+ {{0x2d8b0098,0x60d61c33,0x3f981f6f,0x92be8264}}, // vece_, nzym, _karu_, ঁছে_,
+ {{0x8c4381ae,0x660463d7,0x853009ab,0x6442e3d8}}, // _рефе, _akik, _raɗa, mpoi,
+ {{0x61e40079,0x61ed63d9,0x6442e3da,0xdd9115a9}}, // kiil, ynal, lpoi, هود_,
+ {{0xfe788110,0xdb188106,0xa3ac8072,0x2bbe8072}}, // rtį_, rkvä, गाव_, ्सवा,
+ {{0x2d8b282b,0x657d4a9c,0x61ed05b7,0x61e40e68}}, // rece_, rfsh, vnal, diil,
+ {{0xab840ac8,0x2d8b0796,0x66042396,0x80d901a2}}, // куск, sece_, _ekik, _नेगे,
+ {{0x53341194,0x3f8c82a5,0x61e463db,0x7bc7007b}}, // тест, medu_, fiil, rkju,
+ {{0x3e788019,0xdb1a83a7,0x6604008e,0x68290bcf}}, // mét_, _potê, _gkik, _ažda,
+ {{0x3f980fc7,0x3209151e,0x2aff0e88,0x752d54b1}}, // [6290] _baru_, rlay_, _शुरु_, _igaz,
+ {{0xed59803a,0xb4cb146d,0xdb1c007b,0x320963dc}}, // _može_, लनी_, skrá, slay_,
+ {{0x61e45a42,0x3f98489a,0x6285114a,0x7982809a}}, // biil, _daru_, _upho, _obow,
+ {{0x25a985e4,0x61e4002e,0x644d63dd,0xafdb128d}}, // ñal_, ciil, _kwai, _stød,
+ {{0x7c289e7c,0x3f9804b9,0xed59816b,0x394f867f}}, // _bidr, _faru_, _nože_, _regs_,
+ {{0x3f8c86d3,0x7c28c092,0x394fe3de,0x79828915}}, // jedu_, _cidr, _segs_, _abow,
+ {{0xf220800f,0x3e788019,0x79829a14,0x45d5804a}}, // मगढ़_, jét_, _bbow, _зовс,
+ {{0xa91d9bfe,0x752d20b3,0xed59811f,0x6010016d}}, // leži, _ngaz, _bože_, nämn,
+ {{0xb4be035a,0x60258012,0xed598b80,0xdb1a83ec}}, // _इथे_, ндиа, _vožd_, _lotè,
+ {{0x443f9dae,0x7c288267,0x752d391a,0xdce9ac08}}, // _itu_, _gidr, _agaz, _ubeđ,
+ {{0x2bac816f,0x7d0982f7,0x3e788019,0xd5e60993}}, // चारा, _hyes, gét_, ежни,
+ {{0x20010041,0x58d58bc7,0x443fd70d,0x644d1a14}}, // hohi_, _поет, _ktu_, _bwai,
+ {{0x3f8c82ee,0x3156810f,0x2d9e001b,0x707580be}}, // bedu_, _ניסן_, ěte_, _נײַע_,
+ {{0x443f80f6,0x6e298352,0x69c99e0a,0x799980fc}}, // _mtu_, _lieb, lkee, _haww,
+ {{0x61e4238b,0x386d80e7,0x78a281d0,0x7d099a14}}, // tiil, luer_, _zrov, _lyes,
+ {{0x443f8a2c,0x3f9863df,0x6e29809a,0x692604ae}}, // _otu_, _saru_, _nieb, емба,
+ {{0x443f8069,0x61e4002e,0x644d031d,0x7d099dd5}}, // [62a0] _ntu_, riil, _gwai, _nyes,
+ {{0x61e44c90,0x60d60063,0xe47b0039,0x752d0289}}, // siil, rzym, _צריכ, _zgaz,
+ {{0x443fd5d6,0xf1bf0028,0xd9100bca,0x61e4002e}}, // _atu_, _giáo_, _چیز_, piil,
+ {{0xd91004c0,0x644281e0,0x3f9801bf,0x6e29809a}}, // _نیز_, tpoi, _waru_, _cieb,
+ {{0x63be0503,0xdca38aac,0xdb08b24c,0x3f9863e0}}, // _kopn, лаци, _indé, _taru_,
+ {{0x44321220,0xdb1a83a7,0x7d09adcb,0x443fe3e1}}, // _muy_, _hoté, _dyes, _dtu_,
+ {{0x644287f4,0xfe67803f,0x443f8870,0x7c28b7dd}}, // spoi, _حد_, _etu_, _vidr,
+ {{0x7dc62511,0x69c99384,0x3e788061,0x799b8122}}, // _fósi, gkee, vét_, gduw,
+ {{0x386d9b01,0x63ab801b,0x443f8b99,0x3f8c811f}}, // guer_, ální, _gtu_, tedu_,
+ {{0x3e788019,0xdefa8071,0x629c81d0,0xdb1ab4b2}}, // tét_, зым_, _šrou, _loté,
+ {{0x3f8cca33,0x644d63e2,0x7999e3e3,0x69c981b4}}, // redu_, _swai, _faww, bkee,
+ {{0x78a2e3e4,0x44320039,0xdce08085,0x3e788537}}, // _trov, _buy_, _hamı, rét_,
+ {{0xb4cb0063,0x61e2946f,0x3e788019,0x94a8141e}}, // लने_, _imol, sét_, нтра_,
+ {{0x7bcaa026,0x4432001c,0x6fe680e7,0xdb08816a}}, // nkfu, _duy_, _décè, _andé,
+ {{0xd6cfbbc9,0x44321142,0x6602a256,0xa91de3e5}}, // _ст_, _euy_, kook, veži,
+ {{0x6440a828,0x6b9a81a9,0x9b1781c6,0x660282f1}}, // _otmi, _latg, תחלה_, jook,
+ {{0x765a81e2,0x644d2992,0x6e29e3e6,0xb6068369}}, // [62b0] lsty, _uwai, _rieb, _mošć,
+ {{0x7c2f83d3,0x6e29e3e7,0x69c9811b,0x62889989}}, // _écri, _sieb, zkee, _opdo,
+ {{0x61e2e3e8,0xf653190c,0x64448012,0x443face4}}, // _omol, ائط_, ţiil, _stu_,
+ {{0xcb1204de,0x248901bc,0x37040073,0x41a52539}}, // _כלי_, _kpam_, учув, _ऑफिस,
+ {{0x62888110,0x248900b9,0xa3ac852a,0xeb999ccf}}, // _apdo, _jpam_, गां_, дил_,
+ {{0xf1bf001c,0x394963e9,0xdb1703a8,0x765aabe9}}, // _hoá_, lbas_, _loxí, ksty,
+ {{0x64409010,0x6b9a831d,0x386d810c,0xbe1200ab}}, // _etmi, _datg, wuer_, _সংসদ_,
+ {{0x765aa419,0x386d8866,0x56942d59,0x799981b9}}, // dsty, tuer_, _щаст, _qaww,
+ {{0x69c982b5,0x24890282,0x443fe3ea,0x25ad8162}}, // rkee, _npam_, _utu_, _inel_,
+ {{0xf3ff0073,0xee3990bc,0x2d8f91e6,0x69c985d1}}, // drão_, мно_, lege_, skee,
+ {{0x9d4618b0,0x4432001c,0x248900b9,0x7e7e8106}}, // кенд, _suy_, _apam_, _äppl,
+ {{0x443263eb,0x386922f8,0xb6068042,0xdb170118}}, // _puy_, krar_, _gošć, _coxí,
+ {{0x386d85ce,0x44320028,0x320d9c33,0x39490858}}, // quer_, _quy_, mley_, dbas_,
+ {{0x38690186,0x39491971,0xd705a46c,0x5f058237}}, // drar_, ebas_, _изли, _изла,
+ {{0x2d9b00f1,0x62888118,0xa6ec0264,0x63be01fa}}, // _faqe_, _xpdo, _কর্ম_, _vopn,
+ {{0x25bf802e,0x44320028,0xa3be00d4,0x78a90b67}}, // _noul_, _tuy_, _आवक_, _ševa,
+ {{0xf1bf001c,0x63be63ec,0x31721c80,0x2d8f80b4}}, // [62c0] _giám_, _topn, rgyz_, dege_,
+ {{0x46a62244,0x25ad8317,0x7bd501ec,0x644663ed}}, // казв, _anel_, chzu, mpki,
+ {{0x39491606,0xe57a8a8e,0x25bf83ec,0x6b9a81ec}}, // bbas_, дзе_, _boul_, _ratg,
+ {{0xceb28451,0x8c46934e,0x600fd986,0x0446a549}}, // ייב_, веде, dømm, ведн,
+ {{0xdb1c12d2,0xdf74003d,0x25bf8176,0x2c0e8072}}, // skrä, _مگاب, _doul_, ितलं_,
+ {{0xf7430d69,0x765ae3ee,0x25adc7ff,0x2b580362}}, // _веро, ysty, _enel_, narc_,
+ {{0x37e30153,0x25bf83ec,0x66e60fe6,0x6b9a80ff}}, // _торг, _foul_, _рова, _vatg,
+ {{0xa2b50c78,0x2b580c5e,0x200780ce,0x320de3ef}}, // _उपस्, harc_, _akni_, gley_,
+ {{0x1426a597,0x2ecaa3a7,0xa06784bd,0x0c2693f7}}, // _адам, ान्त, вања_, _аман,
+ {{0x78a62c5d,0xb4ce95bc,0x0eeaa306,0x765abd26}}, // _mrkv, शनी_, дьми_, tsty,
+ {{0xdb01806a,0xa01b0106,0x765a8e11,0x442c90ba}}, // _anlæ, _spök, usty, _iid_,
+ {{0x61469240,0x25ad0118,0x25bf1434,0x32068122}}, // _реда, ñel_, ðul_, _ukoy_,
+ {{0x7d16009a,0x765a84fe,0xed59850b,0x442c8833}}, // _czys, ssty, _joža_, _kid_,
+ {{0xf3ff0187,0x4ea6802e,0x38695656,0x6fc48980}}, // rrão_, _ариа, vrar_, _còct,
+ {{0x442ce3f0,0xf2c404fa,0x3e7c000d,0x59b68ebf}}, // _mid_, астн, jít_, _आचार,
+ {{0x442cae66,0x3869383c,0xa3cf06af,0x3204e3f1}}, // _lid_, trar_, _शतक_, nomy_,
+ {{0x78a6003a,0x3f9ce3f2,0x39492a48,0x21f704b7}}, // [62d0] _crkv, _kavu_, rbas_, għha_,
+ {{0x386963f3,0x6fcd8acf,0x25ad8613,0x442c831d}}, // rrar_, _núcl, _snel_, _nid_,
+ {{0x25bfe3f4,0x2d8fe3f5,0x69dc8110,0xc33204de}}, // _poul_, tege_, _įren, _לוי_,
+ {{0x6e2d054e,0x386963f6,0xb87b002a,0x3f9c81f4}}, // _jiab, prar_, _oxíx, _lavu_,
+ {{0x442ca543,0x645d0009,0x50f40c4f,0x2d8fe3f7}}, // _bid_, nssi, _взят, rege_,
+ {{0x442c8079,0x2d8fe3f8,0x799d63f9,0x69cd00b9}}, // _cid_, sege_, _hasw, lkae,
+ {{0x36d51d2f,0x442c8039,0x799d1400,0x7d0d450b}}, // _совр, _did_, _kasw, _lyas,
+ {{0x2c1a02f1,0x442ce3fa,0x600f8257,0x6e2d0c61}}, // _मीनू_, _eid_, rømm, _niab,
+ {{0x44f58a14,0x320d8295,0x442c84b7,0x6d59e3fb}}, // _спас, rley_, _fid_, mawa,
+ {{0x6d59d9c4,0x320d8e35,0x442ce3fc,0xdb08802a}}, // lawa, sley_, _gid_, _codó,
+ {{0xc7d60039,0x7f55157a,0x645d5397,0x7d0d1267}}, // רותי_, _mezq, essi, _ayas,
+ {{0x6d598c9e,0x8c460c66,0x6e2d00e5,0x799d63ea}}, // nawa, леме, _ciab, _nasw,
+ {{0x7bc180ee,0x40798158,0x61e9e3bc,0x1ae10264}}, // _kolu, _נאַװ, miel, _খুলে_,
+ {{0x61e9e3fd,0x6d59e136,0xadc380ff,0x387f8168}}, // liel, hawa, _khẳn, mtur_,
+ {{0x35a60037,0x6d599cc5,0xada60037,0x2005e3fe}}, // _байг, kawa, _байл, moli_,
+ {{0xdb019918,0x6d59e3ff,0x61e9cd43,0xa2a48aed}}, // _inlä, jawa, niel, कित्,
+ {{0x6d59e400,0xa06719d9,0x60c989ca,0x7d16009a}}, // [62e0] dawa, лата_, nyem, _uzys,
+ {{0x66062c27,0x2005e401,0x7bc18d4e,0x61e9e402}}, // lokk, noli_, _nolu, hiel,
+ {{0x6d5609ca,0x61e9e403,0x25b7003d,0x6da68bba}}, // _keya, kiel, _شهید_, лижа,
+ {{0x442ce404,0x661bb86c,0x6d59e405,0x2005b553}}, // _sid_, _chuk, gawa, holi_,
+ {{0x100385b3,0x7bc1d3db,0x387f820f,0x6d562bab}}, // रकाश_, _bolu, jtur_, _meya,
+ {{0xafe62103,0xa3de809a,0x6609a914,0x7bc1aa70}}, // _согл, दों_, _ekek, _colu,
+ {{0x442ce406,0x61e9cb72,0xadc3801c,0x6606365d}}, // _vid_, fiel, _chẳn, kokk,
+ {{0x3f9c8029,0x6564020f,0xb3a481c4,0x61e9809a}}, // _savu_, _ndih, _गोरख, giel,
+ {{0x442c8082,0xdca3bb4c,0xdb018004,0x6e2d6407}}, // _tid_, _лати, _anlä, _riab,
+ {{0x2005d2fa,0x987b0158,0xa09700be,0xdb01e408}}, // goli_, עריק, נדיק_, _polô,
+ {{0xe5b511e9,0x61e9e409,0x6d561d19,0x387fe40a}}, // айны, biel, _beya, atur_,
+ {{0x61e9e40b,0xd6d78153,0x60c9db5e,0x645d4968}}, // ciel, уть_, byem, tssi,
+ {{0x7bc1e40c,0xb4c10540,0x1c160074,0xdb1a8298}}, // _yolu, ूने_, _दीहल_, _motí,
+ {{0x20058698,0x6d59854e,0x645d156e,0x6b9e6195}}, // coli_, zawa, rssi, _capg,
+ {{0x799d1b66,0x45d51287,0x6d59cdcb,0xcb378039}}, // _pasw, _войс, yawa, ראלי_,
+ {{0xdb1aa670,0x660611ee,0x29d881a8,0x6d5626e8}}, // _notí, cokk, _déag_, _geya,
+ {{0x6e350052,0xac9501a1,0x9f5505da,0xb4ce83ca}}, // [62f0] _suzb, _тапш, ивач, शनो_,
+ {{0xdefb025d,0x61e98920,0x661b86df,0x629ae40d}}, // ные_, ziel, _shuk, _osto,
+ {{0xa3d89513,0x6d59c1a8,0x799d1f33,0x7bc1e40e}}, // ासन_, tawa, _tasw, _rolu,
+ {{0xdb1a802a,0x6e22cf24,0x387fbf11,0x200589d8}}, // _cotí, gmob, ytur_, zoli_,
+ {{0x7bc18289,0x6d59c230,0x2d89010c,0x2005836a}}, // _polu, rawa, _mbae_, yoli_,
+ {{0x6d59885c,0x03a59814,0x61e9e40f,0x6e35017f}}, // sawa, рико, wiel, _tuzb,
+ {{0x2005e410,0x6d5995d0,0x2919026c,0x661bde86}}, // voli_, pawa, _hzsa_, _thuk,
+ {{0x387fa7dd,0xc9a988cc,0x7bc1d34f,0x60c996fb}}, // ttur_, твие_, _wolu, tyem,
+ {{0xa9228698,0x61e9cd28,0x7bc1e411,0x20059aae}}, // одъл, riel, _tolu, toli_,
+ {{0x387fa3ea,0x61e9e412,0xadc3801c,0xb17b0502}}, // rtur_, siel, _thẳn, _smål,
+ {{0x387f8125,0x612b85c5,0x2005e413,0x61e9e414}}, // stur_, _bölü, roli_, piel,
+ {{0xa2c413e5,0xa3b38697,0xb2ba8039,0x29d881a8}}, // रहस्, टाव_, _במער, _céad_,
+ {{0x4b2300c8,0x7c38011f,0x20059fcb,0x66065e59}}, // _ব্লগ_, _čvrs, poli_, rokk,
+ {{0x394d9031,0xb6a5835f,0x629a831d,0x78a4047f}}, // lbes_, _викл, _ysto, vviv,
+ {{0x39460511,0xe7fb0697,0x76439235,0x6d5656a7}}, // ñoso_, ्वना_, _utny, _teya,
+ {{0xb14619b8,0x213f8748,0x386d80e7,0xf2d280be}}, // рнал, ncuh_, orer_, קען_,
+ {{0x6b9e026b,0xaca48133,0xe73a45dd,0x64446415}}, // [6300] _tapg, _kwụp, вез_, _utii,
+ {{0x82360077,0x657b808e,0x25a02b02,0x3cf1016b}}, // _پردا, _acuh, _hail_, šová_,
+ {{0x386d82af,0xed5ab4a3,0x25a06416,0x248de417}}, // hrer_, вое_, _kail_, _apem_,
+ {{0xa2a49c4f,0x386d8aa2,0x32ee8085,0x629a8115}}, // किस्, krer_, _rəy_, _rsto,
+ {{0x25a01706,0x386d8366,0x61e15bbc,0x2d9f82f9}}, // _mail_, jrer_, ëlle, _daue_,
+ {{0xaa669519,0x3860032f,0x386d8257,0x7fd505a8}}, // атик, msir_, drer_, _лісі,
+ {{0x15f80076,0x1606023c,0xfe06142d,0x4e063c4f}}, // ीवार_, रवार_, रवास_, रवाई_,
+ {{0x394001bf,0x386d83a7,0x61f63384,0x629a8419}}, // ncis_, frer_, nnyl, _vsto,
+ {{0xb9c38013,0xdb1a8073,0x38606418,0x80b097ba}}, // تقيي, _botã, nsir_, जमें,
+ {{0x4ea6b210,0x386002be,0x1ae6891e,0x3d1b83b7}}, // арма, isir_, _козм, _बरते_,
+ {{0x35ab81ce,0x386dbb15,0x2ca907b6,0x0737025f}}, // _घोड़, arer_, _srad_, יאים_,
+ {{0x386d87e2,0xb33c84b7,0x29188085,0x25a00229}}, // brer_, _waħd, əran_, _cail_,
+ {{0x25a06419,0xb33c81b9,0x224601a1,0x386d8866}}, // _dail_, _taħd, _čoka_, crer_,
+ {{0x25a00122,0xf99f08f9,0xdb1e0286,0x3940285e}}, // _eail_, _saè_, _copï, ecis_,
+ {{0x9b5817c8,0x9f5e019d,0x29d88174,0x25a00833}}, // рист_, _ótù_, _téad_, _fail_,
+ {{0x386010e1,0xdce08176,0xb33c82a6,0xed598af8}}, // fsir_, _admč, _jaħb, _rožo_,
+ {{0x660d454a,0x7afe1727,0x2ca9641a,0x7a358084}}, // [6310] _ikak, _expt, _urad_, _ište,
+ {{0xd7590bbe,0xf773006b,0x6ab6b738,0x53990ac8}}, // الات_, تار_, _अपूर, _своя_,
+ {{0x248d8609,0x6fcd81a8,0x853000fc,0x29070035}}, // _spem_, _dúch, _raɗi, łna_,
+ {{0x237f003a,0x6d5d2487,0xf2d200be,0x386d821e}}, // đuju_, nasa, _זעט_, yrer_,
+ {{0x64a5814c,0xdff891be,0x7bc50006,0x61ed0198}}, // сала, ुवाद_, _kohu, mial,
+ {{0x82340416,0x386d8036,0xf677003d,0xf77881b9}}, // _دریا, vrer_, _پاسخ, jgħ_,
+ {{0x6d5d0d1f,0x7a35803b,0xdcfb803b,0xf21c000f}}, // kasa, _ošte, _obuć, _भीड़_,
+ {{0x96f81505,0x7bc53af6,0xadc3801c,0x82f80039}}, // _тест_, _lohu, _thằn, יצוב_,
+ {{0x60cd3051,0x6d5d641b,0xe73996dd,0x394dbb7c}}, // nyam, dasa, леп_, rbes_,
+ {{0x386da6e1,0x660d33cd,0x61ed641c,0x60100009}}, // rrer_, _akak, hial, tämi,
+ {{0xee36802e,0xed5a2927,0x61ed0ff9,0x6d5d1ce5}}, // йнэ_, лон_, kial, fasa,
+ {{0xfbc9073a,0x63a1a478,0x60cd0365,0xed5981d0}}, // _את_, _haln, kyam, _jež_,
+ {{0x61ed641d,0x63a18df1,0x22818019,0x7bc503c1}}, // dial, _kaln, lók_, _bohu,
+ {{0x434336ab,0x60cd02e9,0x660d2460,0x69db82df}}, // зерв, dyam, _ekak, lhue,
+ {{0xc3328bea,0x15f781a2,0xa3e9123a,0x91dbb2dd}}, // לום_, ंचार_, _मगर_, _बताई,
+ {{0xed5983bb,0xa22a1541,0x61ed47ff,0x463a80be}}, // _než_, ужба_, gial, _געקע,
+ {{0x395939e8,0x6c8600f7,0xdb0501ec,0x3940641e}}, // [6320] _dess_, _الخم, _anhä, rcis_,
+ {{0x3860008e,0x63a180e4,0x66da0190,0x6eca811c}}, // rsir_, _naln, льер_, bəbd,
+ {{0x3860641f,0xdb1e026f,0x61ed31c4,0x3e6382d0}}, // ssir_, _kopí, bial, nıt_,
+ {{0x629e003a,0x250b8416,0xed5a81a8,0x22818061}}, // _ispo, گرمی_, اشات_, dók_,
+ {{0x77940077,0x78ad81a1,0xed59895e,0x69db83ed}}, // _ویرا, _šavn, _dež_, dhue,
+ {{0x25a09234,0x6d5d57ac,0x628381f4,0xf99f0077}}, // žil_, zasa, ftno, _akèh_,
+ {{0x6d5d6420,0x69c66421,0xdb1aa511,0x6da69ddf}}, // yasa, _koke, _botá, _лива,
+ {{0x853009ab,0x66f701c6,0x69c632e9,0x26cc8196}}, // _haɗu, _המלא_, _joke, vydo_,
+ {{0xa3d8853e,0x6d5d2c28,0x69c60110,0x637d026b}}, // ासद_, vasa, _moke, _bàǹ,
+ {{0x61ed6422,0x629e0052,0x660d079f,0x2281807b}}, // zial, _ospo, _skak, bók_,
+ {{0x66fa053f,0x9f5ee423,0x79960706,0xd36f81a8}}, // ्थिक_, gotá_, keyw, _أهم_,
+ {{0xca37810f,0x2bd10c28,0x69dbc28b,0x7bc802f1}}, // _הנאה_, हस्थ, chue, ödun,
+ {{0x6d5d0859,0x63a19010,0x629e03f2,0x7a35c63a}}, // rasa, _yaln, _aspo, _všte,
+ {{0x6d5d6424,0x39593105,0x69c08da8,0x8fa3828b}}, // sasa, _sess_, ljme, _наче,
+ {{0x61ed1532,0xd7f88087,0x69c61235,0xcf2680f7}}, // tial, mnă_, _boke, _ارشي,
+ {{0x7a358a20,0x69c637d1,0x60cd197a,0x98a4861c}}, // _ušte, _coke, tyam, lamı_,
+ {{0x629e6425,0xa2b5016f,0x35f8026a,0x7bc56426}}, // [6330] _espo, _उपक्, _درود_, _tohu,
+ {{0x61ed6427,0x67eb04b7,0x60cd0314,0xd6db0012}}, // sial, rżjo, ryam, лте_,
+ {{0xa9350d8e,0x61ed2447,0x3d3e81bc,0x6e994436}}, // _менш, pial, _ịwa_, рвер_,
+ {{0x443b0168,0x44336428,0xdb019243,0x69c61502}}, // _kuq_, _hix_, _falê, _goke,
+ {{0xd7fb035f,0xa87900be,0xa5bd8110,0x853009ab}}, // _був_, _דאָר, _siųs, _faɗu,
+ {{0x22818065,0x3e638459,0x78ad811f,0x69c6113b}}, // tók_, yıt_, _šavo, _zoke,
+ {{0x657a8013,0x41e0103e,0xdb1aa13c,0x6eca811c}}, // úchá, _नतमस, _potá, həbb,
+ {{0x62838503,0x644b816d,0x49c9917e,0x3cff81c0}}, // rtno, ppgi, рлин_, _txuv_,
+ {{0x62838f20,0xf77180a0,0x63a1e429,0x69db8580}}, // stno, واد_, _taln, rhue,
+ {{0x9e659383,0x443301ec,0x1665a306,0xf1bf01a8}}, // овод, _nix_, овом, _fhág_,
+ {{0x6d5b8e60,0x7c3b9306,0xdb1aa8e5,0x224601f4}}, // _keua, _huur, _totá, _čoko_,
+ {{0x8c3d8214,0xd7f88087,0x7c3be42a,0x44333b7c}}, // _arşi, ană_, _kuur, _aix_,
+ {{0x7c3b98c5,0x395fe42b,0x443302a6,0xdffb1299}}, // _juur, maus_, _bix_, ्वाद_,
+ {{0x395f82f8,0x69c60a5b,0x7c3bac67,0xa3e40035}}, // laus_, _soke, _muur, पों_,
+ {{0xfaa303c7,0x443300e7,0x2fc7c74f,0xf09f0032}}, // маро, _dix_, _iong_, _asà_,
+ {{0x443b2f1e,0x6d5ba06f,0x395fe42c,0x7bdc0079}}, // _fuq_, _neua, naus_, shru,
+ {{0x2fc79882,0xdfd20624,0x57ea1652,0x629e009a}}, // [6340] _kong_, وير_, адам_, _wspo,
+ {{0x2fc79384,0x395fa761,0x65698091,0x6e3b0699}}, // _jong_, haus_, _adeh, _čubr,
+ {{0x2fc7e42d,0x6449e42e,0x35af0fea,0x69d9bbb8}}, // _mong_, _atei, _जोड़, _olwe,
+ {{0x7c3bb062,0x2fc7d1d3,0x395f827e,0x2cad93c2}}, // _buur, _long_, jaus_, _bred_,
+ {{0x2cad802e,0xdb018168,0x395f8084,0x7de40061}}, // _cred_, _falë, daus_, lőss,
+ {{0x44331c4b,0xe0fb0039,0x7c3b82b5,0x66e609ea}}, // _xix_, _כלכל, _duur, _дога,
+ {{0x98a48059,0x291d80b9,0x9f4580e5,0x7c218706}}, // vamı_, _azwa_, dilà_, _dhlr,
+ {{0x395f8110,0xe57785a8,0x224602d4,0xb33c822b}}, // gaus_, _мзс_, _čokl_, _daħa,
+ {{0xdfd08013,0x7c3b82a3,0x2cade42f,0x6e3c0102}}, // فية_, _guur, _gred_, _hurb,
+ {{0x6e3c153e,0x2fc7801c,0xd7f88087,0xa5bb018a}}, // _kurb, _cong_, ună_, rmóm,
+ {{0xdefa8071,0x2fc7d0c6,0x2ca04463,0x2127001c}}, // рып_, _dong_, _asid_, ̀nh_,
+ {{0x395fa6b4,0x6e3c5a3e,0x44336430,0x7bc8e431}}, // caus_, _murb, _six_, _hodu,
+ {{0x7bc88006,0x2fc7e432,0x6fda0072,0xd7da0072}}, // _kodu, _fong_, णसां, णसाच,
+ {{0xfc3f2509,0x2fc7821e,0x61ed80e1,0xa3b383eb}}, // svío_, _gong_, ďale, टाए_,
+ {{0x38668a38,0x69d80110,0x200cc4bc,0x6fcd8388}}, // _hvor_, _įver, modi_, _cúcu,
+ {{0x25a48a84,0x2fc7b962,0x248684ef,0x39448118}}, // _naml_, _zong_, ktom_, ncms_,
+ {{0x25a68355,0x2d99108a,0x2fc7bf8c,0xdcfd00eb}}, // [6350] ddol_, mese_, _yong_, resē,
+ {{0x6e3c6433,0x2a78151e,0x7bc8b47d,0x2fc7801c}}, // _burb, burb_, _nodu, _xong_,
+ {{0x7c3ba986,0x64498e5d,0xfdff11bc,0x2cad8289}}, // _suur, _stei, _उदास_, _sred_,
+ {{0x2cadba71,0x6e3c6434,0xee3805a8,0x1a9a00be}}, // _pred_, _durb, іні_, _פירע,
+ {{0x2fc70104,0x63a501e0,0x200caef4,0x395f8d15}}, // ưng_, _kahn, kodi_, vaus_,
+ {{0xe0198105,0xfaa5998c,0x7bc8802e,0x7c3b837a}}, // _नींद_, зако, _codu, _vuur,
+ {{0x7bc88052,0x2fc7e435,0x6e3c6436,0x63a56437}}, // _dodu, _rong_, _gurb, _mahn,
+ {{0x249f81e9,0x7c3b9e89,0x2fc78039,0x24868069}}, // _tsum_, _tuur, _song_, btom_,
+ {{0x2d996438,0x2fc7e439,0xb4ea016f,0x2cad920e}}, // dese_, _pong_, _मधे_, _ured_,
+ {{0xb4d78592,0x200ca0c4,0xa3e786a7,0x78af0140}}, // ानी_, godi_, मोश_, _mrcv,
+ {{0x2d99451b,0xf77200be,0x2fc780ff,0x395f8198}}, // fese_, עקט_, _vong_, paus_,
+ {{0x2fc783f8,0x2d99534f,0xa3e78f21,0xe8df8133}}, // _wong_, gese_, मोर_, nrịa_,
+ {{0x621a812a,0x61fb802e,0x63a5643a,0xb9c481a8}}, // _פונק, mnul, _bahn, _تقني,
+ {{0x8fa58d46,0xc87983bf,0xe1ff026b,0x63a5023e}}, // _хаке, muş_, _ajó_, _cahn,
+ {{0x2d9941e3,0xc8798b16,0x25a6c489,0x8c1a81c6}}, // bese_, luş_, ydol_, מושי,
+ {{0x61fb8b74,0xdb0181b3,0xdddc8699,0x9f4c8388}}, // nnul, _valè, _sprž, vidé_,
+ {{0x7f5c07e2,0x910300e5,0xdce981a1,0x395dd55f}}, // [6360] _perq, епте, _oceđ, _mews_,
+ {{0x2b8f8214,0x004681bc,0x7bd50192,0x7b070061}}, // nıcı_, _jụụ_, ckzu, érté,
+ {{0x6e3c12b5,0x61460258,0xe357010f,0x61fbcbab}}, // _qurb, _нена, _משיח_, knul,
+ {{0x395da840,0xdb018298,0x200c812b,0x63a54431}}, // _news_, _galé, zodi_, _zahn,
+ {{0x7bc8e43b,0xc1730051,0x29370158,0x78a908ae}}, // _podu, _אחר_, _מאכן_, _ševv,
+ {{0x69c43e4a,0x6e3c4dd1,0x2d990c2e,0x261c8216}}, // ljie, _turb, zese_, cíon_,
+ {{0x200c803b,0x80d100c8,0x29d88013,0x395d8609}}, // vodi_, _সেপ্, _déan_, _bews_,
+ {{0x6da39628,0x3f9a009a,0x2d9901df,0x61fb85f3}}, // _писа, lepu_, xese_, gnul,
+ {{0x2d99234a,0x200cce7c,0x290280f1,0x68e44219}}, // vese_, todi_, çka_, nzid,
+ {{0xf1bf0104,0xfaa691a8,0x6e242676,0x2d99643c}}, // _khác_, _назо, _ahib, wese_,
+ {{0xf119023c,0x2d99643d,0xadf383dd,0x63a55244}}, // _दर्द_, tese_, _आगमन_, _rahn,
+ {{0x26c28289,0x67e0016d,0x63a51266,0x69c401b9}}, // ćko_, _höjd, _sahn, jjie,
+ {{0x2d99643e,0x6e2402a3,0x80d100ab,0x69c46250}}, // rese_, _dhib, _সেন্, djie,
+ {{0x2d99643f,0x3f9a02ce,0xdb01b4d3,0x78a2e440}}, // sese_, jepu_, _salé, _isov,
+ {{0x3a292a23,0x2d996441,0xba0806a7,0x05dd83ca}}, // tmap_, pese_, वकूफ_, _मतलब,
+ {{0x6e24051e,0x5a340652,0xc2c401a8,0x067b9101}}, // _ghib, енст, ريقي, _הנהל,
+ {{0x628704b7,0x63a54ce6,0x7e7a839c,0x7a0181a9}}, // [6370] stjo, _tahn, lutp, nēta,
+ {{0x660f1230,0x301433e4,0xe9e2809a,0x61fb812b}}, // lock, едур, leźć_, znul,
+ {{0x3a296442,0xdb1e00e1,0x3a3e8748,0x61fbe443}}, // pmap_, _topá, _nutp_, ynul,
+ {{0xfe700154,0x78a28289,0xe81d0006,0xf1bf00ff}}, // ادل_, _osov, _बीरा_, _giáp_,
+ {{0xb4d7c70b,0x0db880f7,0x61fb8904,0x2b8f807e}}, // ाने_, ثالث_, vnul, yıcı_,
+ {{0xa3e7809a,0x660f048d,0x9c8703fb,0xb17b12f1}}, // मों_, hock, _začí, _blåg,
+ {{0x61fb82a5,0x660f2e6c,0x6d460106,0x2b5e8122}}, // tnul, kock, ycka, _detc_,
+ {{0x656d17d5,0x61fbd4c0,0x60101c50,0x2003b114}}, // _ndah, unul, lämp, čjim_,
+ {{0x61fbd682,0x644d0282,0x2b8f8457,0x753b8135}}, // rnul, _ntai, tıcı_, _iguz,
+ {{0x6e246444,0x79828300,0x26288087,0x656d6445}}, // _shib, _ecow, _uşor_, _adah,
+ {{0x443f822e,0x69dd0aa2,0x9f840074,0x7d160009}}, // _huu_, _olse, _töö_, _syys,
+ {{0x443fe446,0x9f4781a8,0x3eb88338,0x645b8176}}, // _kuu_, _imní_, årta_, _kwui,
+ {{0x443fa83a,0x4425e447,0x60103b13,0x67e00106}}, // _juu_, _khl_, kämp, _höje,
+ {{0xe7879bc1,0x443fc1ec,0x69cbaffc,0x644d01a8}}, // _худо, _muu_, _hoge, _dtai,
+ {{0x160d8e70,0x69cbe448,0x69c46449,0xfebb803d}}, // िकार_, _koge, tjie, _داشت_,
+ {{0xed59811f,0x1de20a27,0x753be44a,0x7d04011b}}, // _kožu_, _पतित, _nguz, _txis,
+ {{0x69cb87b3,0x6b9300f2,0x3eb101ec,0x2007007a}}, // [6380] _moge, ädgå, _arzt_, čnin_,
+ {{0x249703f8,0x69cbcc9a,0xb17b644b,0x69c4067f}}, // _کنید_, _loge, _småt, sjie,
+ {{0x6b9c644c,0x67e0016d,0x69c404b7,0x29d880f7}}, // merg, _nöje, pjie, _béal_,
+ {{0x69cb8ae1,0x443f8079,0xdc488133,0x93bc80ff}}, // _noge, _buu_, _aṅụ_, _chăn,
+ {{0xdca3abf3,0x249a8416,0x8c4393f1,0x95880110}}, // каци, تخاب_, вече, _grąž,
+ {{0xdb0887e0,0x753bbbb5,0xa91d8110,0xe73a11c7}}, // _indú, _eguz, igžd, _фев_,
+ {{0x69cbe44d,0x645d81b9,0xdced0380,0xb17b0646}}, // _boge, _ħsie, _ocağ, _flåd,
+ {{0x78a285f5,0x69cbe44e,0x6b9c644f,0xdb1a81d6}}, // _psov, _coge, herg, _kotú,
+ {{0x7a35c599,0x645b8300,0xfc3f008b,0x60f8a3e7}}, // _ešto, _gwui, rvík_, яния_,
+ {{0x6d4481ac,0xb8e6925f,0x291880eb,0x7a0180eb}}, // žiad, _उप_, āra_, rēta,
+ {{0x644d0427,0x6440e450,0x78ad00fe,0x656d008e}}, // _stai, _humi, zvav, _pdah,
+ {{0x6440e451,0x7bde02f7,0x69cbd025,0x2cb20114}}, // _kumi, _alpu, _goge, _bryd_,
+ {{0x3a2680f2,0x443f0028,0x7c84117e,0xa3b38a0d}}, // _ihop_, êu_, куре, _जोड_,
+ {{0x6440a05e,0x63a88a20,0x69cb8f67,0x660f6452}}, // _mumi, _jadn, _zoge, sock,
+ {{0x6440c290,0x69cbe453,0x9e07302b,0xa195004a}}, // _lumi, _yoge, _очил, навч,
+ {{0xb0d28a27,0x656d2e96,0xdb1a8174,0x2cb26454}}, // _तथाग, _udah, _botú, _fryd_,
+ {{0x92c100c8,0x644093bf,0x9be700e8,0x7c3719e7}}, // [6390] ্ছে_, _numi, _жінк, _tixr,
+ {{0xf77007bd,0x25a900c9,0x443f8101,0x6b9c0748}}, // لام_, _haal_, _ruu_, cerg,
+ {{0xb4d781d0,0x25a92e5c,0x4425808e,0x443fe455}}, // ान्_, _kaal_, _rhl_, _suu_,
+ {{0x6440df4a,0x69dd20b1,0x443fe456,0x44258106}}, // _bumi, _ulse, _puu_, _shl_,
+ {{0x6440c73d,0xf3ff0073,0x25a96457,0x63a88079}}, // _cumi, nsão_, _maal_, _badn,
+ {{0x7afc8052,0x6440ab6c,0x69cbe458,0x38695a05}}, // _žrta, _dumi, _soge, msar_,
+ {{0x443f8079,0x59d300bc,0x93bc80ff,0xb60603c1}}, // _wuu_, _सवार, _thăn, _hláš,
+ {{0x6440e459,0x29dc0118,0x443fb735,0xb606016b}}, // _fumi, _lían_, _tuu_, _kláš,
+ {{0x39490207,0xe8f99bc1,0x69cbe45a,0x38694359}}, // icas_, яли_, _voge, nsar_,
+ {{0x38690073,0xeb90845b,0x2d9de45b,0x394900b9}}, // isar_, _نظم_, lewe_, hcas_,
+ {{0x25a919eb,0x644081ec,0x9f4c8e6f,0x628a81ed}}, // _baal_, _zumi, cidí_, ctfo,
+ {{0x63a88e3f,0xed57035f,0x2d9d80b4,0xa95480e8}}, // _zadn, ною_, newe_, _акці,
+ {{0x522c8158,0x67d5948d,0xdb068019,0x25a95a78}}, // _וואַ, _поду, ümöl, _daal_,
+ {{0xdb0181df,0x7bc700f1,0xc0588180,0xbf1f0072}}, // _lalí, gjju, _آشنا_, _भरून_,
+ {{0x6b9c09ff,0xb7da81c6,0xcb1280be,0x59d382f1}}, // rerg, _מקסי, ָלן_, _तवार,
+ {{0xe9df01a8,0x25a9645c,0x672385f3,0x6b9c645d}}, // _clú_, _gaal_, _iznj, serg,
+ {{0x34b300f7,0x6566005d,0x66040135,0x69c0816f}}, // [63a0] _مميز, makh, _ijik, वाती,
+ {{0x64408886,0x75240633,0x25a901ed,0x65663598}}, // _rumi, _iziz, _zaal_, lakh,
+ {{0x63a890e8,0x24460028,0xee3a4249,0xdb01826f}}, // _radn, _hôm_, чне_, _balí,
+ {{0x7ddf020f,0xafdb0b40,0x6440809c,0xdb018118}}, // _kësa, _støt, _pumi, _calí,
+ {{0x443a645e,0xf1bf001c,0x211e80d4,0x61f602d0}}, // _kip_, _khán_, _परेश_, miyl,
+ {{0x628ae45f,0x3a26885e,0x443a6460,0xfaa681ae}}, // ttfo, _shop_, _jip_, ешин,
+ {{0xed5a8abe,0x2d96a549,0x63a8d8df,0x764e21ad}}, // _нов_, _прас, _vadn, _utby,
+ {{0x6440b5fc,0x61f66391,0xdb01823e,0xf1e000d4}}, // _tumi, niyl, _galí, नसिन,
+ {{0x7e7e235a,0x628a82fe,0x65660a84,0x2bd1052a}}, // lupp, stfo, dakh, _सकला,
+ {{0x443a08c4,0x39490388,0x66040091,0x25a96461}}, // _nip_, zcas_, _ajik, _saal_,
+ {{0x75242388,0x7e7e0589,0x11d680e8,0x59e0150e}}, // _aziz, nupp, _підр, नसार,
+ {{0x7c2e6462,0x7c3a8db7,0x6d4002a5,0x78a60140}}, // embr, _hitr, žman, _oskv,
+ {{0x61e41d9f,0x99e98019,0x660401a1,0x68fb00e1}}, // dhil, _تعلق_, _djik, _ľudo,
+ {{0xf8b3812a,0xf3ff0073,0x443a3343,0x66040870}}, // _תשס_, rsão_, _cip_, _ejik,
+ {{0xf3ff00a9,0x25a92808,0x3f9e9ba4,0xdce98353}}, // ssão_, _taal_, metu_, _rdeč,
+ {{0x7c3ae463,0x61e46464,0x38696465,0xda10923a}}, // _litr, ghil, tsar_, ावित_,
+ {{0x39490510,0xc4c48065,0x209817ae,0x38696466}}, // [63b0] rcas_, _اے_, нкты_, usar_,
+ {{0x7c3a8fa6,0x251c0158,0x661d1808,0x3949402b}}, // _nitr, _צוזא, llsk, scas_,
+ {{0xf7700c3b,0x98141e13,0x38694359,0x2d9d80b4}}, // صال_, ابقا, ssar_, tewe_,
+ {{0x61e401ed,0x38696467,0x7c3ab901,0x3a3900e5}}, // chil, psar_, _aitr, _uisp_,
+ {{0xdca5bf21,0x64a59a0b,0x2d9dba9a,0xdb01816a}}, // тали, тала, rewe_, _valí,
+ {{0x3f9e8499,0x69cf0052,0x65660234,0xe2998be4}}, // jetu_, _hoce, zakh, дак_,
+ {{0x7c3a911f,0x9ad381bc,0x3f9e811b,0xdb05016a}}, // _ditr, _bịak, detu_, _inhó,
+ {{0x69cf0bfe,0x65958071,0xf4858019,0x6d4d1a14}}, // _joce, _шабу, _لائی, _nfaa,
+ {{0x7c3a8b50,0x3c24016d,0xdb018187,0x6d598737}}, // _fitr, höva_, _balã, mbwa,
+ {{0x6d4d4577,0x69cf01a9,0x7c3a8bb1,0xd00e80a0}}, // _afaa, _loce, _gitr, يلي_,
+ {{0x656610e1,0xba74815b,0x442080fc,0x443a225c}}, // takh, _مانت, ɓi_, _rip_,
+ {{0x7d1b9a14,0x11f9803d,0xdd9982d6,0x7c3ae468}}, // _kyus, _کوچک_, _akňz_, _zitr,
+ {{0x65660393,0xe0ce8c9b,0x60c4022b,0xf1bf00ff}}, // rakh, _тв_, xxim, _phán_,
+ {{0x656626f3,0x2eb58d9e,0x6b83e469,0xe81986a7}}, // sakh, _асис, ngng, _नीचा_,
+ {{0x61e4005d,0xf1bf0129,0x61f63b4e,0xdee581cf}}, // thil, _cháo_, tiyl, _шоки,
+ {{0x244600ff,0xdb08810c,0x6e3b8b57,0x69c9e46a}}, // _tôm_, _dadè, _niub, njee,
+ {{0xa0670381,0x69cf646b,0x7d1bbd9b,0xdb1a8247}}, // [63c0] ката_, _doce, _nyus, _entè,
+ {{0x7643b00f,0xafdb005f,0x61e4646c,0x3fe31454}}, // _kuny, _stør, shil, ажув,
+ {{0x7c3a81c1,0x61e4029b,0x26dc05b9,0x7d098388}}, // _ritr, phil, ávo_, _axes,
+ {{0x6e29db41,0x76438f45,0x973c81dd,0x3f9e81f4}}, // _cheb, _muny, meće, zetu_,
+ {{0xdb1ae46d,0xdd8f915f,0x6e3b87d5,0x973c8503}}, // _inté, تون_, _diub, leće,
+ {{0xdb0e0125,0x6564646e,0x387fc2cf,0x57e08035}}, // _þjón, _leih, duur_, _फतेह,
+ {{0x3f9e805c,0x628e267f,0x7d098144,0x973c8699}}, // vetu_, ntbo, _exes, neće,
+ {{0x6e3be46f,0x7c3a809a,0x7984310d,0x7bdc01f7}}, // _giub, _witr, ngiw, hkru,
+ {{0x64a3ab3f,0x3f9e803b,0x7c3ae470,0x3014bd93}}, // _ката, tetu_, _titr, лдир,
+ {{0xdefa8a14,0xa3e68fea,0x7c3a8cfa,0x236780d2}}, // дым_, _पति_, _uitr, canj_,
+ {{0xdef487ac,0xddc90e78,0x973c8052,0x628e2009}}, // упны, rudž, jeće, jtbo,
+ {{0x7643dc67,0x973c8024,0x3f9ed41e,0xe0da84ae}}, // _duny, deće, setu_, _ове_,
+ {{0xa3e6901b,0x661d016d,0x628e24a3,0x7643808e}}, // _पता_, rlsk, etbo, _euny,
+ {{0xdb01c58c,0x20180101,0x6602e471,0xdb1ae472}}, // _halá, _nkri_, hnok, _anté,
+ {{0x56949485,0x7643e473,0x6d4086c0,0xd6cf8254}}, // _сайт, _guny, _ogma, _тт_,
+ {{0x29188214,0x66028019,0x6d4082c4,0x1f3781c6}}, // ğraf_, jnok, _ngma, _ברור_,
+ {{0x69cf0baf,0x62341fea,0x201e80e5,0x764380fc}}, // [63d0] _voce, рету, olti_, _zuny,
+ {{0xb8cb101b,0xa5bb0013,0xdefb0009,0xdb1abb40}}, // _कई_, slód, мые_, _enté,
+ {{0xf773810f,0x2367e474,0x6d4080dd,0x628e09c4}}, // שקע_, vanj_, _bgma, ctbo,
+ {{0x20180247,0xf1bf001c,0x7ddf00f1,0x98650065}}, // _ekri_, _khám_, _mëso, _میرے_,
+ {{0xdb050118,0x7a050084,0x629aa717,0x7a2581fa}}, // _mahí, lėto, _apto, jóta,
+ {{0xe50405e8,0x61e2e475,0x7a358110,0xa3b38fcc}}, // रपति_, _alol, _išti, _जोश_,
+ {{0xe2998221,0x23679a4d,0xfb8481e2,0xdb01e476}}, // хай_, ranj_, рычн, _balá,
+ {{0x387fa409,0xf8a99cad,0xa3b39199,0x2367826c}}, // tuur_, евик_, _जोर_, sanj_,
+ {{0x2367e477,0x69c9953c,0xdb01a5b3,0x7643e066}}, // panj_, rjee, _dalá, _suny,
+ {{0xab4a011b,0x68e98d38,0x76439a67,0x973c8289}}, // _kaмe, rzed, _puny, zeće,
+ {{0x200704c4,0xe3b0936d,0xdb0526de,0xa2d580f7}}, // čnih_, _برق_, _bahí, _حيات,
+ {{0x261903d3,0xdb01e478,0x2d890580,0x8d760174}}, // déos_, _galá, _ccae_, دادا,
+ {{0x2245e479,0x973c825b,0xe1f98084,0x236581f4}}, // _hulk_, veće, mių_, _kelj_,
+ {{0xe1f98a8e,0x394ddb80,0x656401ec,0x386de47a}}, // lių_, lces_, _weih, mser_,
+ {{0xe29717f4,0x386d8370,0x973c803b,0x7bdc1c11}}, // лар_, lser_, teće, rkru,
+ {{0xe1f98a8e,0x394d9098,0x601d8722,0x98a62097}}, // nių_, nces_, dèmi, либе,
+ {{0x973c805c,0x628e0687,0xa6ca61fe,0xa9260523}}, // [63e0] reće, stbo, کوال_, удал,
+ {{0xd8488104,0x25bf8012,0x973c8279,0xa3b38d86}}, // _học_, _anul_, seće, _जोल_,
+ {{0xe1f981e2,0xb8cb2435,0xe57a8ab2,0x973c81a1}}, // kių_, _कै_, езе_, peće,
+ {{0xc27b093f,0x25ad8355,0x386d84d6,0x9ad381bc}}, // _ארטי, _cael_, kser_, _pịnk,
+ {{0x386d813c,0x7c3e647b,0xd84880ff,0x23658176}}, // jser_, _kipr, _mọc_, _belj_,
+ {{0x386d813c,0xdb01e47c,0xd848801c,0x394de47d}}, // dser_, _salá, _lọc_, eces_,
+ {{0xdb01936f,0xdee6002e,0x291d810c,0x386de47e}}, // _palá, _сови, _aywa_, eser_,
+ {{0xdd928277,0x25ad8355,0xef17091d,0xaca4819d}}, // _دور_, _gael_, _смс_, _atụp,
+ {{0xdfd08013,0x2edc800d,0x69dc064a,0xa5079ddf}}, // قية_, मन्त, _नवनी, _бета_,
+ {{0x08978307,0x749b810f,0x189780f7,0xb4e08105}}, // اضيع_, _אימפ, اضية_, दनी_,
+ {{0xdb018117,0x6c338013,0xe1f98110,0x3a203117}}, // _talá, _افلا, bių_, klip_,
+ {{0x394dd919,0x6146828b,0x443e81c6,0xd8488129}}, // cces_, _седа, _hit_, _cọc_,
+ {{0xd84880ff,0x3a2002d5,0x443ea640,0xf8a886ae}}, // _dọc_, dlip_, _kit_, _कनिय,
+ {{0x9f34a133,0x7c3e17d6,0xdce0875f,0x25ad001b}}, // _келі, _cipr, _nemč, žel_,
+ {{0x443ed83b,0x7c3e0397,0xda780656,0x2d890118}}, // _mit_, _dipr, лят_, _tcae_,
+ {{0xf1bf00ff,0x3f980609,0x7a0181a9,0xf99f0176}}, // _thám_, _abru_, vētk, _ejèn_,
+ {{0x7c3e002a,0x2ca938dc,0x6e2d2bd7,0x2bc79a46}}, // [63f0] _fipr, _usad_, _ihab, लामा,
+ {{0x443ee47f,0x7c3e0bb1,0x1e0d8035,0x316682f9}}, // _nit_, _gipr, िक्ष_, _leoz_,
+ {{0x6e2d6480,0xa195218c,0x6d5d02ed,0x6d4f00e5}}, // _khab, _канч, obsa, occa,
+ {{0x443ee481,0x386da171,0x6595bb17,0x0dc8988f}}, // _ait_, yser_, раду, лучи_,
+ {{0xe1f981e2,0xd2640029,0x442c8ad4,0x6b658084}}, // vių_, _viņš_, _bhd_, акла,
+ {{0xeb999baa,0x987a00be,0xa87a03c8,0xd7e4004a}}, // тик_, _באשט, _באשר, _лісо,
+ {{0x443e93c7,0x67e004b8,0xfa340bca,0x3cfc009a}}, // _dit_, _möjl, _خرید, _लेने_,
+ {{0x386dc377,0x25bf8012,0x443e821e,0x4095bb4a}}, // tser_, _unul_, _eit_, арот,
+ {{0xe1f98a8e,0x68ed02ba,0xdb1a82ba,0xe450987e}}, // rių_, nzad, _botó, _فضل_,
+ {{0x386d92f1,0xe1f981e2,0x2cb88277,0x9989017b}}, // rser_, sių_, _حافظ_, ylaş_,
+ {{0xed5a4249,0x63a3cae5,0x7c3e6482,0xa2b88beb}}, // кон_, menn, _ripr, ्मण्,
+ {{0x6283e483,0x63a3e484,0x443e8613,0x6e2d2305}}, // muno, lenn, _zit_, _chab,
+ {{0x660980f1,0xdb1aa660,0x6e2d60c5,0x628382e8}}, // _mjek, _fotó, _dhab, luno,
+ {{0x6609920e,0xdb01855a,0x61e98ed0,0x656b8135}}, // _ljek, _calç, lhel, hagh,
+ {{0x661bc40c,0xa96a0098,0x76475213,0x6fc900d4}}, // _okuk, кива_, _nujy, रानं,
+ {{0x63a3e485,0x6e2d0ad0,0x65bb0364,0x61e9e486}}, // henn, _ghab, _vähä, nhel,
+
+ {{0x63a3a921,0x60db8737,0x6283e487,0x973c8a20}}, // [6400] kenn, nyum, huno, meća,
+ {{0x65bb0364,0x2f562749,0x63a3e488,0x628389b6}}, // _tähä, ртас, jenn, kuno,
+ {{0xd37b0451,0x5b7b0051,0x63a3aaeb,0x61e9e489}}, // _בריט, _בריא, denn, khel,
+ {{0x62838420,0x629e2d08,0xdcf60214,0x7bc182c4}}, // duno, _ippo, _hayı, _anlu,
+ {{0xdcf607d9,0x63a3831d,0x61e9cf15,0x61fb8087}}, // _kayı, fenn, dhel, diul,
+ {{0x6609e48a,0x3eaa00dd,0x75298088,0x3e6800d7}}, // _ejek, _tsbt_, _dzez, _مجری_,
+ {{0x443ee48b,0x6283e48c,0xdcf6017b,0xdce6026f}}, // _vit_, guno, _mayı, dakč,
+ {{0x973c82fd,0x98b20214,0x91fc80eb,0x6609821e}}, // jeća, layı_, _avār, _gjek,
+ {{0x443e9a48,0xc7c6a57e,0x4fc689a8,0xfce38dae}}, // _tit_, асни, асна, _дото,
+ {{0x443e933a,0x6e2d648d,0x6d444f9b,0x629e24cf}}, // _uit_, _shab, _agia, _oppo,
+ {{0x09e68160,0x6283802e,0x6e2d06e7,0x61e9829b}}, // _кожн, cuno, _phab, bhel,
+ {{0x5183997b,0x61fb8087,0x6e2d523d,0x6d4462d8}}, // _душа, ciul, _qhab, _cgia,
+ {{0x629e19cc,0x53470eef,0x3eb981a9,0xdcf6007e}}, // _appo, ихва, āsta_, _bayı,
+ {{0x764086c0,0x6e22805d,0x6d440102,0x26f78180}}, // _limy, hlob, _egia, اریخ_,
+ {{0x6e2d0013,0x25b00198,0x996747d4,0x9f5e81a8}}, // _thab, öllä_, итал, nntí_,
+ {{0x68ed2bfa,0x827780be,0x31c400e8,0xf1bf00ff}}, // tzad, יעלע_, осув, _khái_,
+ {{0x63a3e48e,0x7c9500f7,0x7767022c,0x656be48f}}, // [6410] yenn, _الخص, _pejx, vagh,
+ {{0x3d1a809a,0xdb1ab8ce,0x68ed6490,0x7a0180eb}}, // _बड़े_, _antí, rzad, rēti,
+ {{0x656b8870,0xdb0512d2,0xf77000f7,0x95cb8009}}, // tagh, _anhö, باق_, куда_,
+ {{0x3618835f,0x63a3e491,0x261c8020,0xdcf602d0}}, // ацію_, wenn, díos_, _zayı,
+ {{0xdcf60459,0x656be492,0x66099351,0x22b580eb}}, // _yayı, ragh, _vjek, _sāk_,
+ {{0x656bb3b0,0x261c8511,0xdb0501d0,0x2bc7864a}}, // sagh, fíos_, _zahá, लाधा,
+ {{0x61e9829b,0x656b91c2,0x63a389af,0x66098343}}, // thel, pagh, renn, _tjek,
+ {{0x63a3e493,0x62838ff9,0xc6920158,0x661b805d}}, // senn, runo, _מאל_, _ukuk,
+ {{0x973c803b,0x6283e494,0x61e9e495,0x20d5a1d2}}, // veća, suno, rhel, сійс,
+ {{0x61e9e496,0xddc9026c,0x59c5801b,0x261c8118}}, // shel, grdž, वाहर, bíos_,
+ {{0xee3a091e,0x7ddf020f,0x61e9829b,0x38ca803d}}, // лно_, _kësh, phel, نایی_,
+ {{0x64419fdb,0xdcf602bb,0xd12e853d,0x682901d0}}, // _hili, _sayı, ظمی_, _vždy,
+ {{0x6441cb46,0x49b896a5,0x60c484b7,0xdea18019}}, // _kili, _خالد_, _ġimg, _کیجی,
+ {{0xf8b68051,0xb05b1bc0,0xdcf60201,0x6441d08f}}, // _לפני_, _kräf, _qayı, _jili,
+ {{0x973c8bda,0x6449802a,0xd8488135,0x12b800ab}}, // lećn, _luei, _jọn_, ীন্দ,
+ {{0x6441842a,0xa5bb062f,0x68ec8182,0x7989e497}}, // _lili, llón, _öldü, ngew,
+ {{0x644180f7,0x249f81c0,0xb4db0722,0xdce40084}}, // [6420] _oili, _npum_, scàr, _keič,
+ {{0x6441b3e7,0x7640b4c2,0xe0df02d6,0xd12f1ef5}}, // _nili, _simy, _afòs_, رمل_,
+ {{0x6d448110,0x6e22b110,0xdb08802a,0x3cfc0035}}, // žiam, tlob, _padí, _लेते_,
+ {{0x261c8693,0x6441e498,0x798980f3,0xdfd261fe}}, // víos_, _aili, jgew, _کيس_,
+ {{0x6441c1ae,0x7ddf020f,0x973c82ce,0xa13600f7}}, // _bili, _dësh, jećn, _دردش,
+ {{0x6441b543,0x62810207,0x741680ab,0x69c2bf61}}, // _cili, álog, াষ্ট_, _snoe,
+ {{0xf99f0242,0x764082c4,0xddc90b80,0xb7b5819d}}, // _akèy_, _timy, vrdž, kọb,
+ {{0x6441e499,0x6281005c,0xf1bf001c,0x9054864f}}, // _eili, šlog, _phái_, овиц,
+ {{0x8b969cad,0x6441e49a,0xdd1703db,0xa3b3ab12}}, // _греч, _fili, _धुंध_, _जोक_,
+ {{0x64419a0d,0x6569890d,0xd848826b,0x6abc8e06}}, // _gili, _zeeh, _fọn_, _árfo,
+ {{0xd848801c,0x501b0039,0xf41f0106,0x200b9243}}, // _gọn_, רויו, ktär_, écia_,
+ {{0x6441822e,0xdb1a80a9,0xf1bf0028,0x387f8239}}, // _zili, _entã, _thái_, brur_,
+ {{0x999f880a,0x64418d6a,0x24868267,0x394680e4}}, // lmuş_, _yili, nuom_, _igos_,
+ {{0x6441e49b,0x7ddf00f1,0xa5bb2509,0xf64f8061}}, // _xili, _mësi, clón, بئی_,
+ {{0x81ab80c8,0x999f88c5,0x7a358084,0x201a5712}}, // খার_, nmuş_, _ištr, dopi_,
+ {{0x22420019,0x2a8e8085,0xd25781c6,0xb33c8197}}, // _cikk_, _həbs_, משלה_, _jaħq,
+ {{0x656f649c,0x7a0881d0,0x02a5d59b,0x4fd51073}}, // [6430] mach, větl, _खन्न, ожат,
+ {{0x656f0af9,0x752d0289,0x6449887a,0xe9df00f7}}, // lach, _izaz, _ruei, _siúd_,
+ {{0x543c0158,0x39469cbc,0x7bc5649d,0x2242035f}}, // _געזא, _ogos_, _inhu, _fikk_,
+ {{0x656f28b5,0x63a7649e,0x224200e8,0x249f80b9}}, // nach, lejn, _gikk_, _ppum_,
+ {{0x61ed178e,0x6449907f,0x7a35805c,0x7ddf00f1}}, // mhal, _quei, _oštr, _vësh,
+ {{0x6441e49f,0x61ed4cf5,0x656f2305,0xdb03e4a0}}, // _qili, lhal, hach, lené,
+ {{0xdce982ee,0x6441c18b,0x973c8289,0x79898e61}}, // _odeć, _vili, dećo, tgew,
+ {{0x61ed0558,0x64418a07,0x65949501,0x320964a1}}, // nhal, _wili, _насу, nnay_,
+ {{0x656f64a2,0x7989b66b,0x2cad8051,0x395f9482}}, // dach, rgew, _used_, rbus_,
+ {{0xb05b00f2,0x660d0074,0x7989a32e,0x973c8904}}, // _träf, _ajak, sgew, rećn,
+ {{0x46f59194,0x61ed005d,0x63a71cf3,0x752d2bd7}}, // ючит, khal, dejn, _azaz,
+ {{0x656f1dfb,0xfbc90051,0xb33c81b9,0xd848819d}}, // gach, _בת_, _taħr, _kọl_,
+ {{0x61ed3a51,0x3e480214,0x6830016d,0x660d64a3}}, // dhal, _işte_, läde, _djak,
+ {{0xdee598a2,0x316030b2,0x66e59662,0x8af70085}}, // _моли, wbiz_, _мола, ğənn,
+ {{0x656f64a4,0xed5a0698,0xc7448154,0x25a68681}}, // bach, _кой_, _عضوي, yeol_,
+ {{0x61ed64a5,0x660d00f1,0x32093bc3,0xb22605a8}}, // ghal, _gjak, gnay_, імал,
+ {{0x6fdf80e7,0x68300192,0x201a1502,0x6d4481d6}}, // [6440] _pêch, häde, topi_, žiak,
+ {{0xb4e61c4f,0x62870264,0x320901b4,0x63a701d0}}, // पनी_, bujo, anay_, cejn,
+ {{0x444464a6,0x61ed029b,0x7e670282,0x6146263e}}, // _ii_, bhal, _cwjp, _мена,
+ {{0x444464a7,0x2486826c,0xdb0883c1,0xb05b0106}}, // _hi_, tuom_, _nadá, _bräd,
+ {{0x44440838,0x69c6539c,0xdb1e2511,0xad9b00f7}}, // _ki_, _inke, _topó, liúc,
+ {{0x444464a8,0x2ca991dd,0x61e43a92,0xd848819d}}, // _ji_, çada_, lkil, _dọl_,
+ {{0x26cc020f,0xb05b12d2,0x68300106,0x656f1fb6}}, // _çdo_, _präg, rädd, yach,
+ {{0x444464a9,0x672e02fd,0xdb089eec,0x999f807e}}, // _li_, _izbj, _cadá, rmuş_,
+ {{0x656289ca,0xb05b0106,0x61e400dd,0x63a70168}}, // mboh, _gräd, ikil, yejn,
+ {{0x444420b6,0xc7b8a8e1,0x656f64aa,0x109b0039}}, // _ni_, kođe_, wach, סביב,
+ {{0xb05b1b48,0x245080ff,0xf1bf00ff,0x656f435c}}, // _träg, _hàm_, _khát_, tach,
+ {{0x69a181fe,0x2fd80c64,0x752400b4,0x76440326}}, // _कॉपी, _lorg_, _byiz, _ciiy,
+ {{0xf7728019,0xdb03816b,0x75240314,0x6723838a}}, // _ہاں_, vené, _cyiz, _eynj,
+ {{0x656f0c64,0x6b9b005c,0x62872254,0xdb08826f}}, // sach, đugo, tujo, _zadá,
+ {{0x444464ab,0x24508142,0x656f64ac,0x61ed64ad}}, // _di_, _làm_, pach, thal,
+ {{0x444464ae,0x244f0179,0x63a72f9f,0x62870bcf}}, // _ei_, _tüm_, sejn, rujo,
+ {{0x444464af,0x61ed2e67,0x2fd864b0,0x320964b1}}, // [6450] _fi_, rhal, _borg_, rnay_,
+ {{0x444464b2,0x69c64e39,0x61ed64b3,0x320963dc}}, // _gi_, _enke, shal, snay_,
+ {{0x61ed64b4,0x656d0458,0x644d0834,0x2fd800ee}}, // phal, _keah, _huai, _dorg_,
+ {{0x29dc160a,0x444416eb,0x6445165a,0xdb1e0722}}, // _días_, _zi_, _hihi, _topò,
+ {{0x44442778,0x69ce8023,0xe7bc8b84,0x64456415}}, // _yi_, थानी, ्ञाप, _kihi,
+ {{0x444401cd,0xf8bf64b5,0x656d0706,0x493a0039}}, // _xi_, _iré_, _leah, _לגרו,
+ {{0x81b480c8,0x4cd380c8,0x64454703,0x644d008c}}, // ঞান_, _দেখু, _mihi, _luai,
+ {{0x798d2444,0xb05b00f2,0x2fd80a0f,0x5fc90540}}, // ngaw, _träd, _zorg_, रावल,
+ {{0x644d154c,0xd84881bc,0xdb088061,0xafdb004a}}, // _nuai, _fọm_, _vadá, _støy,
+ {{0x1e858ff7,0x764402a3,0xf1b980d2,0x7ddf00f1}}, // плом, _siiy, doše_, _mësu,
+ {{0x44442b66,0x61fd2081,0xdb1a8187,0x98ab861c}}, // _ri_, _omsl, _botõ, ımız_,
+ {{0x644d1a29,0x200164b6,0xd1b89ddd,0xcfe9803d}}, // _buai, hihi_, _وانا_, _گفته_,
+ {{0x44440205,0x644d0068,0xf1b9817f,0x64454005}}, // _pi_, _cuai, goše_, _bihi,
+ {{0x644d0083,0x69d98039,0xf99f0032,0x225a00b9}}, // _duai, _howe, _abè_, _ptpk_,
+ {{0x44440082,0x64450057,0x61e43b4e,0xab2a0539}}, // _vi_, _dihi, tkil, сова_,
+ {{0x44443db1,0x644d003c,0xad9b0013,0x29dc01df}}, // _wi_, _fuai, riúc, _rías_,
+ {{0x444464b7,0x61e464b8,0x98a30758,0x7a2592ca}}, // [6460] _ti_, rkil, ните, góti,
+ {{0x644504a7,0x444464b9,0x29dc03a8,0xaabd8f1b}}, // _gihi, _ui_, _pías_, ्माक,
+ {{0xf1bf1c03,0xf8bf10ab,0xba760481,0x998da329}}, // _phát_, _eré_, _عادت, zmeš_,
+ {{0x29dc05e4,0x69d9c4f0,0x2cbfa25b,0x645b8362}}, // _vías_, _nowe, _brud_, _btui,
+ {{0x644502a3,0x2001208b,0x649a9232,0x657b8122}}, // _yihi, bihi_, стер_, _dduh,
+ {{0x38600590,0x660bc36a,0x9f5e80f1,0x657b8133}}, // mpir_, angk, ritë_, _eduh,
+ {{0x6fc901fe,0x69d9e4ba,0xa3cd016f,0x46a30b69}}, // रारं, _bowe, ळात_, _парв,
+ {{0x9f5ee4bb,0x2d9f8133,0x3a2200ee,0x69d9e4bc}}, // mité_, _gbue_, _jkkp_, _cowe,
+ {{0x9f5eb347,0x3cfc18b8,0x764e2676,0x69d9e3c2}}, // lité_, _लेले_, _huby, _dowe,
+ {{0xb4d88b6f,0x6602b10e,0x656d1c33,0x644d0c49}}, // ाही_, liok, _seah, _ruai,
+ {{0x47348987,0x2ab5877f,0x644d032f,0xbcfb0b4c}}, // ьнос, rùbá_, _suai, gbén,
+ {{0x7bdac508,0x69d98573,0x63aa807b,0x973c88ae}}, // _hotu, _gowe, lefn, lećk,
+ {{0xd7e68d13,0x644d0073,0x7bda84b9,0x44210101}}, // _ніко, _quai, _kotu, _pkh_,
+ {{0x493b8bea,0x764e0247,0x69d98a0f,0x63aa807b}}, // _הגדו, _ouby, _zowe, nefn,
+ {{0x7bdad68b,0x201ee4bd,0x628a84b9,0x661d136f}}, // _motu, moti_, nufo, posk,
+ {{0x644d0cac,0xf8bf272b,0x82341e91,0x9f5e80e7}}, // _tuai, _pré_, فرقا, dité_,
+ {{0x798d01b0,0x2c2700e8,0x644564be,0x6d403e83}}, // [6470] rgaw, _ньог, _tihi, žmar,
+ {{0x44270013,0x7bdae4bf,0x201eac62,0x9f47826f}}, // _ón_, _notu, noti_, _plné_,
+ {{0x200164c0,0x645be2ce,0x0ef9001b,0x63aa8114}}, // rihi_, _stui, ्पेस_, defn,
+ {{0x7bc8dcca,0x764e07cd,0x200125eb,0x2ca98187}}, // _andu, _duby, sihi_, çado_,
+ {{0xee38835f,0x3e4c800d,0x69d9cbe9,0x6283831d}}, // їні_, _děti_, _rowe, wrno,
+ {{0x69d9c174,0x0fe780ab,0xfaa5a597,0x7a050084}}, // _sowe, _পদ্ধ, дако, rėtu,
+ {{0x9f5ee4c1,0x69d9b910,0x201e9202,0x7528009a}}, // cité_, _powe, doti_, ędzi,
+ {{0x24808024,0xceb2010f,0x60c4e4c2,0x200c82f1}}, // šima_, _ביל_, _šima, endi_,
+ {{0xf8a9803d,0x6b9c031d,0xdb03b680,0x645b82c4}}, // تگاه_, yfrg, mení, _utui,
+ {{0xdb03a792,0x201ee4c3,0xb95b0032,0x69d9913b}}, // lení, goti_, _bnìk, _wowe,
+ {{0x3a2964c4,0xe7f50321,0xff5601c6,0x69d9e4c5}}, // klap_, _इतना_, ובתך_, _towe,
+ {{0xf3e90bea,0x2d8f8077,0x200c80e5,0xfb1a80be}}, // _דף_, ngge_, andi_, _וועמ,
+ {{0x6d44b999,0x3a29222e,0x518490ac,0xee371273}}, // žiav, dlap_, _чуха, ьню_,
+ {{0x33d50112,0x67d4a3fc,0xf77180a0,0x6602811b}}, // ніст, _почу, _شات_, ziok,
+ {{0x04431814,0x8c43153d,0x320d8e35,0xef1f8085}}, // терн, тере, oney_, şüb_,
+ {{0x9f5e83d3,0x3d19116e,0x764e0314,0xad9b01a8}}, // vité_, _युके_, _ruby, liún,
+ {{0xd378803b,0xd7c9035a,0x765c4fa2,0x6e2402a0}}, // [6480] _hoće_, रांच, _stry, _ikib,
+ {{0xdfa600f7,0xdee602ee,0x66e6571d,0x9f5eb789}}, // بحري, мози, моза, tité_,
+ {{0x86c60013,0x386007f1,0x644f8144,0x4e02005e}}, // بيان, spir_, _écij, _लगाई_,
+ {{0x9f5e83d3,0x452a8758,0x7bdaa9f1,0xad9b01a8}}, // rité_, ожен_, _sotu, hiún,
+ {{0x3ead003a,0xa3b2101c,0x68ed809a,0xb4d89344}}, // _ćete_, _ऐसा_, _żadn, ाहे_,
+ {{0x660284cd,0x2d8f847f,0xd25a80c4,0x320dabf0}}, // siok, agge_, оци_, eney_,
+ {{0x160e2a25,0x7bdaa4a6,0x765c64c6,0xf74324c8}}, // ाचार_, _votu, _utry, _аеро,
+ {{0x628a80b9,0x2d95bd5a,0x6298d778,0x80b50035}}, // rufo, _прус, rtvo, _उनमे,
+ {{0x629884a8,0x7bda8790,0x68e464c7,0x628ac4ad}}, // stvo, _totu, nyid, sufo,
+ {{0x6e240590,0x7bc8d038,0x973c80d2,0xa5c70118}}, // _akib, _undu, leći, _opóñ,
+ {{0xd8488028,0x3c24016d,0xdd950084,0x68e42daf}}, // _mọi_, hövs_, наны, hyid,
+ {{0x6e362158,0x6d448110,0x7c2a9ff5,0x2366026c}}, // _chyb, žiau, llfr, _đoja_,
+ {{0x201e8010,0xdb01826f,0x6448c53f,0xdb0e64c8}}, // poti_, _malý, _kidi, ndbæ,
+ {{0x6e240c56,0xdf0a814c,0xd7c881a8,0x7a138087}}, // _ekib, _мэйл_, _روعه_, măta,
+ {{0x6448e4c9,0x60c2b732,0xf1b98088,0x2d8f8370}}, // _midi, _hrom, loša_, ygge_,
+ {{0x1de3000c,0x60c287dd,0x973c805c,0x8cc60beb}}, // _पवित, _krom, jeći, ामखो,
+ {{0x973c803a,0x60f91006,0x7a138087,0xdb03936f}}, // [6490] deći, чная_, năta, vení,
+ {{0x3a29451c,0x2486bfe5,0x5fc91a3b,0xceeb0199}}, // slap_, drom_, राइल, يران_,
+ {{0x9f4c8144,0xb17b64ca,0xf4148264,0xdb0397a8}}, // lidó_, _blåt, িতার_, tení,
+ {{0x6448ce07,0x60c2e4cb,0x68300106,0x7bde808b}}, // _aidi, _orom, räda, öpun,
+ {{0x6448b22b,0xb4e981d2,0x7ae38114,0xd4699860}}, // _bidi, मने_, wynt, чиле_,
+ {{0xc6a73bae,0x644881b4,0x69dd64cc,0xdb03816b}}, // _орби, _cidi, _hose, sení,
+ {{0xd8488028,0x6d4d02a3,0x69dd64cd,0x387e01a9}}, // _gọi_, _ogaa, _kose, _ātri_,
+ {{0x69dd64ce,0x60c2bdbc,0x6d4d3368,0xc7b882a5}}, // _jose, _brom, _ngaa, vođa_,
+ {{0x6448d039,0x69dd64cf,0x7a258125,0xa5bb00f7}}, // _fidi, _mose, dótt, clói,
+ {{0x60c2e4d0,0x6d4d257a,0x6448e4d1,0x6e2464d2}}, // _drom, _agaa, _gidi, _skib,
+ {{0x66e58048,0xad9b00f7,0xd62a2902,0x60c2c3b5}}, // хона, riún, зоне_, _erom,
+ {{0x69dd463a,0xad9b0013,0xd3788a20,0x7c2502a3}}, // _nose, siún, _voće_, _akhr,
+ {{0x764980b9,0x61e9bd8f,0x8cc2850a,0xe8eec718}}, // _miey, mkel, लियो, _сл_,
+ {{0x973c8289,0xe51082f1,0x764980b9,0x6e3d1151}}, // zeći, ़पति_, _liey, amsb,
+ {{0x2005bdfa,0xf1d3853f,0xf1bf0104,0x161f88fd}}, // mili_, थापन, _pháp_, यकार_,
+ {{0xf2d28159,0x61e99699,0x69dd37a0,0x3ebe007b}}, // נען_, nkel, _cose, _átti_,
+ {{0x69dd2ffa,0xa84a1a00,0x045700f7,0xb4e9852a}}, // [64a0] _dose, سلام_, _كلية_, मनो_,
+ {{0x660614c7,0xb17b0687,0x5fb783eb,0x61ef0144}}, // likk, _blås, _असफल, _elcl,
+ {{0x69cb8a52,0x61e9ae9d,0x69dd64d3,0x0d8682a4}}, // _onge, kkel, _fose, елен,
+ {{0x66060100,0x63ae64d4,0x628e445b,0x7ae13294}}, // nikk, lebn, mubo, älte,
+ {{0x2005b874,0x628e64d5,0x7bde64d6,0x6448e4d7}}, // kili_, lubo, _kopu, _pidi,
+ {{0x69cbc369,0x031f0b85,0x2005d791,0x2480e4d8}}, // _ange, _मुँह_, jili_, šimo_,
+ {{0x6448be05,0x660664d9,0x628e177c,0x69cb82f7}}, // _vidi, kikk, nubo, _bnge,
+ {{0xe643aaed,0x7bde0009,0x629c64da,0x7e9b0039}}, // _бесп, _lopu, itro, _מסלו,
+ {{0x644884b8,0x8c958160,0xdb01e4db,0x6c8688ca}}, // _tidi, _архі, _baló, _سلام,
+ {{0x69cbe4dc,0x200587d9,0x6e22bcb2,0x628e005d}}, // _enge, gili_, loob, kubo,
+ {{0x211a95e4,0x031f000f,0x660602fe,0x629c0088}}, // _کتاب_, _मुंह_, fikk, jtro,
+ {{0x61e98b2f,0x629c64dd,0x628e00fc,0xc33381c6}}, // ckel, dtro, dubo, _עוף_,
+ {{0x2005c96b,0x37e68ab5,0x7a25807b,0xafe68878}}, // bili_, _подг, rótt, _подл,
+ {{0x69dd596e,0xb05b16d2,0x63ae02af,0xdb018144}}, // _sose, _brän, gebn, _galó,
+ {{0xdeef905c,0x660602ec,0xdbc78074,0x09c500ab}}, // _сы_, bikk, _töös, _একবা,
+ {{0xf1a681fe,0x3cfc0006,0xb05b1614,0x59a680d4}}, // _कॉमन, _लेके_, _drän, _कॉमर,
+ {{0x6e228079,0x764980b9,0x9f5e816a,0x41b8864a}}, // [64b0] doob, _riey, hití_, _आसनस,
+ {{0xa0c400f7,0x7c25036e,0xf7700124,0xdb08826b}}, // ديكو, _ukhr, مام_, _dadú,
+ {{0x44230025,0x63a9003b,0xb05b016d,0x57a40110}}, // moj_, đend, _grän, ышта,
+ {{0xaf3401a8,0x75298176,0x127a03de,0x2b1a01d0}}, // اركت, _pyez, _קארע, _मुटु_,
+ {{0x44258681,0x973c8904,0xb4bd8c28,0x672a807b}}, // _pkl_, meću, ेमे_, _lyfj,
+ {{0x4423003a,0x03a58c6e,0xed158065,0x63baaad6}}, // noj_, тико, _یہاں_, _catn,
+ {{0x61e9e4de,0xb5a7876a,0x9f34021e,0xdea4003d}}, // tkel, _прай, _бері, _میلی,
+ {{0x2005e4df,0x442364e0,0xdb018333,0x63ae050a}}, // wili_, hoj_, _saló, zebn,
+ {{0x4423003a,0x61e9a2ba,0x6d4480e1,0x80c44823}}, // koj_, rkel, žiar, रिये,
+ {{0x4423003a,0x61e98f52,0x63ba81fa,0x6606037a}}, // joj_, skel, _gatn, wikk,
+ {{0x2005e4e1,0x66060558,0xdb01b74e,0x63ae027f}}, // rili_, tikk, _való, vebn,
+ {{0x3ddf8a0f,0x7a13802e,0x63ba80ee,0xdce0801b}}, // _jouw_, măto, _zatn, _nemě,
+ {{0x66062bc5,0xf41f1a50,0x63ae001b,0x7a138087}}, // rikk, rrän_, tebn, lăto,
+ {{0x629c139a,0x660664e2,0x628e0461,0x3ddf8a53}}, // ttro, sikk, tubo, _louw_,
+ {{0x23270abe,0xab272300,0x3dcd8b99,0xe29736ab}}, // вори_, вора_, _onew_, кар_,
+ {{0x629c17c5,0x212b000d,0x628e018e,0x7c23e4e3}}, // rtro, _bych_, rubo, honr,
+ {{0x4423135a,0x660409ae,0x7bde3349,0x25bd831d}}, // [64c0] boj_, _imik, _topu, ddwl_,
+ {{0xb05b04b8,0x98a31e25,0x629c64e4,0xdb1c0168}}, // _trän, рифе, ptro, ndrë,
+ {{0x224b04b8,0x63ba8025,0xc815035f,0x6e2290b5}}, // _fick_, _ratn, уєть, toob,
+ {{0x224b04b8,0x63ba8668,0x7c2e1ff5,0x98b181d0}}, // _gick_, _satn, llbr, íběh_,
+ {{0x63ba9061,0x6fc90361,0x2b060321,0x224002f1}}, // _patn, राखं, _हेतु_, mmik_,
+ {{0x3a26883b,0x22400d86,0xdce081d0,0xdb07128a}}, // _skop_, lmik_, _země, zejí,
+ {{0xfba41094,0x63ba8357,0x7a138087,0x6604519a}}, // ख्यम, _vatn, găto, _omik,
+ {{0xada689c7,0xbbdf0b86,0xa9348e11,0x442335bf}}, // _шабл, _नक्क, лейш, zoj_,
+ {{0xdb1a80f1,0x63baa9cb,0xdca680a9,0x1c468791}}, // _katë, _tatn, _јази, _анам,
+ {{0x66040065,0x1da51130,0xef1f82d0,0x442364e5}}, // _amik, ग्नत, ğün_, xoj_,
+ {{0x7a13802e,0x2ca900b9,0xadc3826b,0xef1f8380}}, // căto, _spad_, _ajẹn, şün_,
+ {{0x6f030073,0x3a268267,0x2489e4e6,0x7e7a94cf}}, // únci, _ukop_, šame_, rstp,
+ {{0x5c070098,0x6fb681a8,0xf8b68107,0xdb070216}}, // вява, _لمشا, _अनिय, rejí,
+ {{0xdb1a80f1,0xdb07001b,0x66040a40,0x443a02d0}}, // _natë, sejí, _emik, _chp_,
+ {{0x442364e7,0x9b580703,0x9f5eaa4d,0xaad28bb8}}, // roj_, тист_, mità_, थमिक,
+ {{0xed598025,0x9f5e89ba,0x6616020f,0x442300f1}}, // _kaže_, lità_, _gjyk, soj_,
+ {{0x973c82a5,0x4423246d,0x6d5d0061,0x10178019}}, // [64d0] reću, poj_, mcsa, _سکیں_,
+ {{0x9f5e81c1,0x224b1a2e,0x973c85a2,0x25ff090f}}, // nità_, _wick_, seću, शोरी_,
+ {{0xdb0387e2,0xdb1a8168,0x212b0428,0xf77021df}}, // menç, _datë, _wych_, زال_,
+ {{0x212b0dee,0xed58835f,0xdb03bd5b,0x7c3a9611}}, // _tych_, вої_, lenç, _ahtr,
+ {{0x69cf0a1e,0x61ed209b,0xdca5db5d,0xb05b05ec}}, // _ince, mkal, уали, _kräm,
+ {{0x59d100c2,0x7a138162,0x320933c7,0x0c248a4c}}, // हावर, tăto, liay_, амін,
+ {{0x9f5eaa4d,0xa2c0809a,0x36d40c4f,0x3ddf890d}}, // dità_, विष्, _корр, _touw_,
+ {{0x61ed64e8,0xd3788e78,0xdb1ae4e9,0x7c23d15f}}, // nkal, _voća_, _matè, sonr,
+ {{0x7a138087,0x6e3b8234,0x61ed64ea,0x37758190}}, // săto, _ihub, ikal, лынс,
+ {{0x7982e4eb,0xe8f69e18,0x660400c3,0xa2c083dd}}, // _edow, уль_, _smik, विश्,
+ {{0x764d016f,0xb99580f7,0x61ed64ec,0x752d0d4e}}, // _biay, _ملاب, kkal, _ayaz,
+ {{0x7655009a,0x2459801c,0x61ed1279,0x3a24e4ed}}, // _muzy, _kèm_, jkal, comp_,
+ {{0xe0ce91cf,0x764d1ab3,0x61ed2765,0x2d9e0035}}, // _ув_, _diay, dkal, ęte_,
+ {{0x9f5e8698,0x61ed64ee,0x2d9e01d0,0xdb1a8242}}, // cità_, ekal, řte_, _batè,
+ {{0x65798372,0xf8bf0019,0xdb1a8580,0x3eb981a9}}, // kawh, _szép_, _catè, āsts_,
+ {{0x61ed1066,0x764d4f24,0x69db816a,0x6e298133}}, // gkal, _giay, njue, _nkeb,
+ {{0x68e98867,0xb05b00f2,0x224064ef,0x3eaa00b9}}, // [64e0] nyed, _främ, rmik_, _spbt_,
+ {{0xb8dc3792,0xdb038073,0x67f2007b,0x6e3b82a0}}, // _आन_, cenç, _bæja, _ahub,
+ {{0xa2e61289,0x7d1b81df,0xd84881bc,0xbd8a81a8}}, // _сонд, _axus, _kọr_, فنان_,
+ {{0x7c3a820f,0x61ed0668,0x645664f0,0xed5aaeab}}, // _shtr, ckal, _kuyi, ход_,
+ {{0x645604b9,0xa3a900d4,0x6e3b90eb,0x63be0061}}, // _juyi, _गॉड_, _dhub, _kapn,
+ {{0x63a48267,0xdb1a8061,0x69a1836d,0x64561c61}}, // đino, _haté, क्सी, _muyi,
+ {{0x9f5e8a3f,0x60c464f1,0xe7372255,0xdb1aa190}}, // vità_, lvim, _бер_, _katé,
+ {{0x24800025,0x6e3b8c93,0xbc7680d7,0x245983ec}}, // _ovim_, _ghub, _مهرب, _fèm_,
+ {{0xdb1ae4f2,0x9f5e9b68,0x26c7e4f3,0x463b80be}}, // _maté, tità_, _arno_, _טעלע,
+ {{0x52768a08,0x26c7801b,0x09e10d14,0xed5984a8}}, // _буду, _brno_, _नव्य, _važe_,
+ {{0x9f5ea35a,0x25fb84e5,0x26c7803b,0x29dc0d76}}, // rità_, लोकी_, _crno_, _díaz_,
+ {{0x645664f4,0x9f5e8081,0xdb0a83a7,0x244f0609}}, // _buyi, sità_, nefí, _ażma_,
+ {{0xdb038073,0xcd073751,0x55073b17,0xae0401cb}}, // tenç, учни, учна, शोधन_,
+ {{0x61e28006,0x6c542596,0xab9504ae,0xdb1a8f33}}, // _hool, скру, јављ, _patè,
+ {{0xc4c4e4f5,0xdb03e4f6,0x61ed5fff,0xdb1a80e1}}, // _کے_, renç, tkal, _baté,
+ {{0x67388499,0xdb1a83d3,0xdb0383a7,0x777ae4f7}}, // _izvj, _caté, senç, latx,
+ {{0xb60681f4,0x6e3bdc45,0x9f4a07f1,0x478b1957}}, // [64f0] _mašć, _rhub, _albà_, есем_,
+ {{0x61ed64f8,0x6e3bd1f3,0x61e2b0da,0xad9b01a8}}, // skal, _shub, _lool, diúi,
+ {{0x6579833e,0x1dc6809a,0x60c40110,0xb05b58e5}}, // tawh, _रोहत, avim, _kräk,
+ {{0x63be026f,0x61e2ae46,0x777a8cdb,0x70950048}}, // _zapn, _nool, hatx, _камф,
+ {{0x6aba8012,0xad9b00f7,0x6579e4f9,0x66f38085}}, // _astf, giúi, rawh, rəkə,
+ {{0x3206820d,0x62658878,0x3f830b67,0x68fb811c}}, // _amoy_, авка, _udju_, vzud,
+ {{0x61e2e0ba,0x69c080f3,0x6e3bc7cb,0x25a9016b}}, // _bool, fdme, _thub, _obal_,
+ {{0x09e58256,0x245986c0,0x61e2c4dc,0x9f5e84e8}}, // роин, _tèm_, _cool, vitá_,
+ {{0x61e2c86f,0xad9b01a8,0x20548048,0x660bb4e0}}, // _dool, ciúi, йтэр, migk,
+ {{0x25a900dd,0xdb0392a5,0x59d3801b,0x660b9887}}, // _abal_, tenä, थाहर, ligk,
+ {{0x24800025,0x02d989d7,0x61e2a556,0x3255a45b}}, // _svim_, _جواب_, _fool, _твер,
+ {{0x6cc601e5,0x660bb4e0,0x78b98069,0x61e2d731}}, // айма, nigk, _tswv, _gool,
+ {{0x442789f8,0x7ae101ec,0xf8a982f1,0x200b8036}}, // oon_, ältn, _कहिय, écis_,
+ {{0x4427e4fa,0xdb1a92ca,0x1ef980f7,0x69a18aed}}, // non_, _saté, _لعبة_, क्री,
+ {{0xab2a0db3,0xdb01801b,0xdb1a8825,0x232a0ae7}}, // това_, _oblí, _paté, тови_,
+ {{0x4427e4fb,0x60c40025,0x24809b3b,0x61e28079}}, // hon_, tvim, šimi_, _xool,
+ {{0xa80614ef,0xdfd200a0,0x422300e8,0x660b8192}}, // [6500] азал, يير_, ідув, digk,
+ {{0x4427b217,0x6443809a,0x387f80b9,0x60c464fc}}, // jon_, jmni, isur_, rvim,
+ {{0x25bfe4fd,0xdfcf0c3b,0x71a6259a,0x69c081b4}}, // _baul_, ظيم_, _канз, ydme,
+ {{0x6da6a503,0xad9b00f7,0xe5a6802e,0x387fcdbb}}, // рида, tiúi, риди, ksur_,
+ {{0xa3cb00d4,0x62818711,0x61e284a2,0x248080f7}}, // _रोड_, _avlo, _rool, éim_,
+ {{0x44279fb0,0x61e2a986,0xad9b01a8,0x628e0299}}, // gon_, _sool, riúi, orbo,
+ {{0x66e60987,0x81b900c8,0xad9b00f7,0x660be4fe}}, // _това, চার_, siúi, bigk,
+ {{0x9874818b,0x25bf859e,0x30a6be80,0x24899ccb}}, // блиц, _gaul_, аров, šama_,
+ {{0x4427d592,0xdfd08013,0x25a90362,0xf1bf001c}}, // bon_, كية_, _rbal_, _cháy_,
+ {{0x200780b9,0x4427b260,0x57f48284,0xb60584e8}}, // _cmni_, con_, спот, _višň,
+ {{0xdefa8196,0x8f7b010f,0x2019809f,0x61e2d5d0}}, // тып_, כניק, ésia_, _tool,
+ {{0x98a69821,0x10a69033,0x28c90f1b,0xf99f06c4}}, // _виде, _видн, रिमि, _amèn_,
+ {{0x442cd8ca,0x443ec51c,0x69fb83de,0x00d78fd3}}, // _kkd_, _kht_, _בלאק, _نبوت_,
+ {{0x9989009a,0x26fc00ab,0x442c80c3,0xf3ff041c}}, // znań_, _এখনো_, _jkd_, rpão_,
+ {{0x7ac41383,0xf8b680c2,0xf2c40ada,0x200cbd61}}, // осте, _अनुप, остн, midi_,
+ {{0x4427a574,0x33f40106,0x443ec3cd,0x7bda2abd}}, // zon_, _växa_, _lht_, ötur,
+ {{0x4427c1b5,0x657d64ff,0x6e2d00b4,0x80d100d4}}, // [6510] yon_, mash, _ikab, समें,
+ {{0x657d4d3d,0xb767044f,0x3866808e,0x28c902f1}}, // lash, ртай, _mtor_, रिबि,
+ {{0x4427b360,0x63b50353,0x660b82af,0x656f026f}}, // von_, mezn, tigk, obch,
+ {{0x63b51640,0x59a68076,0x200c822e,0x657d25f4}}, // lezn, _कॉलर, hidi_, nash,
+ {{0xd49b8c6e,0x443e80cd,0x9f439931,0x442c8683}}, // тре_, _bht_, ójú_, _bkd_,
+ {{0x657d5ae9,0x200c85ee,0x660b81ec,0xdd118197}}, // hash, jidi_, sigk, _iżża,
+ {{0x657d3623,0x68ed19b0,0x443e81e0,0xd90f8065}}, // kash, lyad, _dht_, ڈیا_,
+ {{0x4427e500,0xdb1ae501,0x657d00f1,0x68ed0079}}, // son_, _antó, jash, oyad,
+ {{0x68ed6502,0x657d5961,0x6609e503,0x4427b1ee}}, // nyad, dash, _imek, pon_,
+ {{0x753b803b,0x31790065,0x6e2d1ea2,0x44278079}}, // _izuz, _lesz_, _akab, qon_,
+ {{0x657d0578,0x387fce30,0x7980819d,0x7bc1e504}}, // fash, ssur_, _ịweb, _ialu,
+ {{0x7bc1aad7,0x7c3e020f,0xdb1a84c3,0x657d39af}}, // _halu, _shpr, _entó, gash,
+ {{0x66098135,0x661b834e,0x200ce505,0x61fb8b81}}, // _mmek, _mjuk, bidi_, mhul,
+ {{0xe29a0791,0x7bc1832e,0x200c8b57,0xe7088065}}, // лаг_, _jalu, cidi_, اتین_,
+ {{0x6e95a684,0x657d6506,0x7bc19dde,0x2ba9835a}}, // _العا, bash, _malu, च्या,
+ {{0x80c40076,0x7bc19083,0x661be1f0,0x628e0106}}, // रिले, _lalu, _njuk, rrbo,
+ {{0x6143104b,0x63b5007a,0x7a138087,0xc862819d}}, // [6520] зета, bezn, găti, _kọọd,
+ {{0x7bc1ad80,0x66098010,0x2f560972,0xdb1c349a}}, // _nalu, _amek, стас, ldrí,
+ {{0x442a6507,0x61fb91e8,0x5b5700be,0xd37894ce}}, // mob_, khul, _לייב_, _reć_,
+ {{0xdb1c11b9,0x442a6508,0x200c8010,0xd719047d}}, // ndrí, lob_, zidi_, _दशरथ_,
+ {{0x7bc1b59a,0x61fbe389,0x6281001b,0x68ed0079}}, // _balu, dhul, álov, cyad,
+ {{0x6609e509,0x7bc185ae,0x29000010,0x657d0314}}, // _emek, _calu, mzia_, zash,
+ {{0xd378803a,0x38668082,0x200c8698,0x657d5ae9}}, // _već_, _stor_, vidi_, yash,
+ {{0xada3872f,0x442a01e9,0x61fbe50a,0x63b51660}}, // _насл, hob_, ghul, zezn,
+ {{0x2900650b,0x78a401e8,0x442c826c,0xc86281bc}}, // nzia_, ntiv, _tkd_, _bọọd,
+ {{0x6e2d013c,0x657d40a6,0x442a3ff4,0x7bc18662}}, // _skab, wash, job_, _galu,
+ {{0x657d4d12,0x5f38835f,0x200ce50c,0x6e4581fa}}, // tash, рпня_, ridi_, _útbú,
+ {{0x7bc1b4fd,0x61fb9cb6,0x61e600eb,0x60c485f3}}, // _zalu, chul, _nokl, _šimu,
+ {{0x657d1419,0x69c404b7,0xd90e8bca,0x2d992176}}, // rash, ddie, _سید_, rgse_,
+ {{0x63a90025,0x657d03c3,0x6d561eb7,0xb05b00f2}}, // đeno, sash, _egya, _kräv,
+ {{0x6fd205b3,0xec340cde,0x63b5650d,0xcfa9803d}}, // साईं, їнсь, rezn, _خانم_,
+ {{0x245d0104,0x6e2d2fc0,0xf1b980eb,0x657d4a03}}, // _tìm_, _ukab, toši_, qash,
+ {{0x660f003e,0x7c97006b,0x998900c3,0x69c2810c}}, // [6530] mick, _اشعا, glaš_, _maoe,
+ {{0xd5a5016f,0x661b8687,0xa8a417ae,0x1da5140d}}, // ग्रज, _sjuk, ярск, ग्रत,
+ {{0x76b901e5,0x2900650e,0x68ed3bd5,0x41c403dd}}, // рлер_, azia_, syad, _लोकस,
+ {{0x660f0a56,0x7bc1ad2a,0xd2468077,0x99990110}}, // nick, _salu, _زن_, _visų_,
+ {{0x78a4650f,0x7bc1cc89,0xdb18806a,0xd1268591}}, // ctiv, _palu, ndvæ, _آم_,
+ {{0xdfdb0698,0x660f4431,0x2489e510,0x547c03de}}, // _съм_, hick, šamo_, נטוו,
+ {{0x7bc1b9ac,0x61fb805d,0x7bc381ec,0xdb1e0187}}, // _valu, thul, rdnu, _papé,
+ {{0x7bc1c79e,0xa5f897ae,0x6609804f,0x6b8885ee}}, // _walu, _лесу_, _umek, _eddg,
+ {{0x2aa18063,0x442a0282,0x660f026f,0x7bc1e511}}, // sób_, yob_, dick, _talu,
+ {{0x6e2b8b4c,0x61fba76b,0xdb1ae512,0x600982de}}, // logb, shul, _zatí, рном_,
+ {{0xe7399289,0x660f003e,0x623595ac,0xb05b0106}}, // рен_, fick, _дежу, _gräv,
+ {{0x660f003e,0x58d58323,0x9f4581d0,0xc862819d}}, // gick, _моет, chlý_, _wọọd,
+ {{0x442a6513,0xc0e58be2,0x6283d497,0xb05b0192}}, // tob_, _фолк, lsno, _kräu,
+ {{0x386d9277,0x61e626a6,0xdce981d6,0x2b4081d0}}, // mper_, _sokl, _oceľ, řice_,
+ {{0x60c9e514,0x660f40dd,0xf8bf2c8a,0xfbd281c6}}, // lvem, bick, _asé_, ותם_,
+ {{0x78a40211,0x6d560065,0x2900011e,0xbcfb00f7}}, // ttiv, _ugya, tzia_, scéa,
+ {{0x98a31d32,0x69c46515,0x27e78247,0x600a3b17}}, // [6540] мите, rdie, _konn_, анам_,
+ {{0x69c42c28,0x25ada00b,0x290001ac,0xdb1ae516}}, // sdie, _abel_, rzia_, _satí,
+ {{0x91e61264,0xbcfb6517,0xdb1ae518,0x27e78242}}, // _доне, ncén, _patí, _monn_,
+ {{0x645ba487,0x5eac00ab,0x386d80b9,0xad9b01a8}}, // _buui, ছিলে, kper_, liúr,
+ {{0x6441803c,0xa3b6800f,0xa01b0125,0x5046002e}}, // _bhli, ज़ल_, _kvöl, _мемб,
+ {{0x660f07df,0x6562856f,0x326690f8,0x6441a305}}, // zick, scoh, отив, _chli,
+ {{0xfc3f02ba,0xa2bda38c,0x2bcb001b,0x64418c49}}, // ntía_, _वैद्, ायता, _dhli,
+ {{0xd378803a,0xab5c80eb,0x48aa968a,0x9874e519}}, // _moći_, _daļa, атом_, плиц,
+ {{0x27e7b903,0xb7bc8029,0x660f136f,0x41a6a207}}, // _bonn_, loģi, vick, क्षस,
+ {{0x274a8676,0xc6a69814,0xb4d692ee,0x765c5b5e}}, // ично_, орми, िमी_, _kury,
+ {{0x660f0a56,0xd3788025,0x1f5b010f,0x27e7bb42}}, // tick, _noći_, ידיא, _donn_,
+ {{0x765c540f,0x27e7808e,0x3dc6a093,0xc7b88503}}, // _mury, _eonn_, ndow_, vođu_,
+ {{0x660f0a56,0xf8ad8077,0x27e7bb42,0x2ca6d18f}}, // rick, _یکی_, _fonn_, ntod_,
+ {{0x660f003e,0x3dc4822c,0x63b8e51a,0x7980c50b}}, // sick, _lamw_, nevn, lamw,
+ {{0xc1ee823c,0x3d118063,0x660f026f,0x320b0037}}, // _जवाब_, _देने_, pick, ахан_,
+ {{0xd378803a,0x2ca68420,0x91fc81a9,0xb05b0338}}, // _doći_, ktod_, _stāj, _orät,
+ {{0xdcfb805c,0x27e78247,0xf8bc809a,0x7a138162}}, // [6550] _iduć, _yonn_, ्टिय, cătu,
+ {{0x63b8e51b,0x7980823d,0x765c02a0,0x2cbf8101}}, // jevn, hamw, _bury, _rsud_,
+ {{0xd6db49a1,0xdb150065,0xa2cc016f,0x7bd7651c}}, // шта_, kezé, हिण्, _inxu,
+ {{0xa91d8052,0x6281003a,0x64418c41,0x386d81a3}}, // laže, šlos, _shli, yper_,
+ {{0x7bc5058e,0x9f4c81a8,0x660d3fb5,0xb7b5819d}}, // _kahu, ghdú_, _mmak, kọp,
+ {{0x2249651d,0x26ce802a,0x395909d1,0xa91d9874}}, // lmak_, _orfo_, _hgss_, naže,
+ {{0x7bc52fbb,0xdb1a8722,0x058781a1,0x660d651e}}, // _mahu, _catà, _муам, _omak,
+ {{0xe9d98785,0x40958cd0,0xdb1c3930,0x3f81651f}}, // ски_, прот, ndrá, lahu_,
+ {{0x63a90025,0x6283856c,0xfbcb1905,0xb05b0192}}, // đenj, ssno, ायाम, _träu,
+ {{0x660d44de,0x7bc533ef,0x3f81000e,0x386d8b2b}}, // _amak, _nahu, nahu_, rper_,
+ {{0xdb0aa9ed,0x386dc5f8,0xed5a4b8d,0xf529025f}}, // gefä, sper_, йон_, _עץ_,
+ {{0xd1318013,0x2d9db9fa,0x224900d2,0x386d97af}}, // _كما_, ngwe_, jmak_, pper_,
+ {{0x3f81026c,0x7bc502a0,0xf1b9811a,0x27e78e20}}, // kahu_, _bahu, meš_, _tonn_,
+ {{0xdb1a8065,0xa91d856f,0x660d6520,0x33d51138}}, // _hatá, ražd, _emak, міст,
+ {{0x7bc5208b,0x34b8a0d8,0x0f7a8039,0x6b9c008e}}, // _dahu, _इन्द, _הרצל, rgrg,
+ {{0x09e312a0,0xdddcb5ca,0x63b88da8,0xfc3f0511}}, // носн, _ovrš, zevn, rtía_,
+ {{0x2d822cb1,0x765c0cc0,0xfc3f05e4,0xa3bd881f}}, // [6560] make_, _sury, stía_, ेया_,
+ {{0x2d821110,0xfc3f00f7,0x7bc560d9,0x9ce70019}}, // lake_, ltín_, _gahu, _روپے_,
+ {{0x4444003a,0x41bd835a,0xf1bd8b86,0x59bd800c}}, // _ih_, ्यास, ्यान, ्यार,
+ {{0xfc3f6521,0xdb1aa13c,0x7bc56522,0xa3a9886a}}, // ntín_, _natá, _zahu, ग्स_,
+ {{0x7bc54ffd,0x63b885b9,0xb4d682f1,0xf1b99ee0}}, // _yahu, tevn, िमे_, deš_,
+ {{0x62988a8e,0x69c66523,0x2d8258af,0x443803ac}}, // tuvo, _hake, hake_, nlr_,
+ {{0x80c41053,0x44444ad2,0x69c6155f,0x2d8203f8}}, // रिके, _mh_, _kake, kake_,
+ {{0x69c66524,0xdb1e0038,0xdb1a8510,0x44440252}}, // _jake, _napí, _catá, _lh_,
+ {{0xfc648077,0x6b569381,0x69c60e9e,0x7c2e31cd}}, // _آخری, _فضائ, _make, kobr,
+ {{0x6d408341,0x660d1be9,0x1dda8076,0xdb03826f}}, // _izma, _smak, णावत, lený,
+ {{0xdd950a14,0x7bc50074,0x69c6151e,0x659515ac}}, // маны, _rahu, _oake, ману,
+ {{0x44446525,0xdb1e023e,0x7bc54081,0xaac29834}}, // _ah_, _capí, _sahu, _वैयक,
+ {{0x44446526,0x7bc55885,0x7c2e0118,0x76560300}}, // _bh_, _pahu, fobr, _diyy,
+ {{0x444406f6,0x7c2e0052,0x41da852a,0x60c2e527}}, // _ch_, gobr, भावस, _isom,
+ {{0x69c60c03,0x44440046,0xa91d8289,0x883b8051}}, // _bake, _dh_, raže, _התגו,
+ {{0x44446528,0x69c60642,0x2486a67f,0xa7fb06a5}}, // _eh_, _cake, ksom_, _muñe,
+ {{0x7bc5048f,0x3f815237,0x44443a5c,0xdb03826f}}, // [6570] _tahu, tahu_, _fh_, dený,
+ {{0xd6db0d0e,0xa3d40beb,0xdb1e03f2,0x60c280b8}}, // йте_, _सोप_, _zapí, _msom,
+ {{0x69c66529,0xdddc8503,0xb05b0b2f,0xe9df001c}}, // _fake, _svrš, _präs, _chúc_,
+ {{0x661d36bd,0x3f81003e,0xe9df0028,0x645701df}}, // nnsk, sahu_, _giúp_, _hixi,
+ {{0x44441fd8,0x64a58ddc,0x628723b1,0x661d0412}}, // _yh_, фала, lsjo, insk,
+ {{0x4444652a,0x69c60a0f,0x2d82652b,0x3d11816f}}, // _xh_, _zake, zake_, _देणे_,
+ {{0xea000028,0xdb03803e,0xddcd009a,0xd3788024}}, // _hoạt_, bený, _miał, _noću_,
+ {{0xa3d405fc,0x7c2e26fd,0xdb03e52c,0x661d23f9}}, // _सोन_, zobr, menü, jnsk,
+ {{0x34949448,0x2d8de52d,0x9f4780f1,0x3a2d85f8}}, // _патр, _idee_, _tonë_, roep_,
+ {{0x2d82022e,0x6287035f,0x764404b9,0xdb1e0187}}, // wake_, ksjo, _shiy, _japã,
+ {{0x4ac180d4,0x2d82652e,0xdb1e2aa8,0x3d118c69}}, // _शनिव, take_, _papí, _देते_,
+ {{0x661d0aa2,0x645708f1,0xa3a98c78,0x60cd00ce}}, // gnsk, _aixi, ग्र_, kvam,
+ {{0x69d9e52f,0x7c2e6530,0x2d824f15,0x69c62551}}, // _inwe, tobr, rake_, _rake,
+ {{0x6b83859e,0x69c633a9,0x2d826531,0x661d01ac}}, // nang, _sake, sake_, ansk,
+ {{0x69c66532,0x2d826533,0xdb03bdcd,0xfaa32c24}}, // _pake, pake_, menó, каро,
+ {{0x6b83bcc3,0x69c9e534,0x645f00f1,0x7c2e1400}}, // hang, ndee, _fuqi, sobr,
+ {{0x78a9aeec,0x6b83a0e7,0x2fc788b3,0x61ebacdb}}, // [6580] ntev, kang, _kang_, _mogl,
+ {{0x26d1003a,0x6b83bb5b,0x6d5bb7dd,0xe9df001c}}, // _brzo_, jang, _agua, _phúc_,
+ {{0x2fc78681,0x6b83c083,0x69d9e535,0x78a9e536}}, // _mang_, dang, _onwe, htev,
+ {{0x61eb8022,0xdd8f845b,0x63bc29c6,0x2fc7843c}}, // _nogl, لوم_, lern, _lang_,
+ {{0x442e9de6,0x2d800006,0xdb098efc,0x6b83aa1d}}, // tof_, _meie_, čnýc, fang,
+ {{0x2fc7e537,0x63bc29c6,0x24868687,0x69d9ab29}}, // _nang_, nern, rsom_, _anwe,
+ {{0x9f5e820f,0x96630607,0x998901ac,0x61ebaf65}}, // shtë_, _аксе, slať_, _bogl,
+ {{0x63a9003b,0x442eb2b3,0x61eb81a7,0x80b280ab}}, // đeni, sof_, _cogl, জিস্,
+ {{0x6b8385c6,0x6d40e538,0x63bc0f06,0xdb1a8187}}, // bang, _uzma, kern, _antô,
+ {{0x63bc1277,0x69d98a2c,0x629c6539,0x6b83e53a}}, // jern, _enwe, kuro, cang,
+ {{0x7bdae53b,0x61eb8429,0xdb0e00f2,0xa5bb04c3}}, // _intu, _fogl, nebä, olóx,
+ {{0x72169381,0x61eb831d,0x629c0114,0x2fc78114}}, // _غذائ, _gogl, duro, _eang_,
+ {{0x7bc8e53c,0xe9f780e8,0x79840010,0x661d06b1}}, // _kadu, _інші_, daiw, rnsk,
+ {{0x63bc17db,0x7bc88ad4,0xf8bc8105,0x2fc7c33f}}, // gern, _jadu, ्टीप, _gang_,
+ {{0x6457653d,0x62a2826f,0xa3b0816f,0xa3e69513}}, // _vixi, _útoč, ट्य_, _बचन_,
+ {{0x645702a3,0xa91d825b,0xf1bf0019,0x6b838a40}}, // _wixi, laža, _akár_, zang,
+ {{0x2fc79dae,0x63bc653e,0x628701cd,0x90990098}}, // [6590] _yang_, bern, ssjo, яват_,
+ {{0x60cd2d28,0x6d5be53f,0x60c900cd,0xf1d803eb}}, // rvam, _sgua, _šems, डाउन,
+ {{0xa3e2a9b7,0x20130201,0x629c072a,0x44316540}}, // धान_, rixi_, curo, moz_,
+ {{0x6b83e541,0x7bdae542,0x7bc88032,0x44316543}}, // wang, _antu, _aadu, loz_,
+ {{0x6b83afe9,0x7bc89c88,0x7bda80dd,0x61eb8796}}, // tang, _badu, _bntu, _rogl,
+ {{0x61ebc1a1,0x7bc88b57,0x22469142,0x44316544}}, // _sogl, _cadu, _ahok_, noz_,
+ {{0x6b83999f,0xeab18065,0x6b7c0051,0xe2998c07}}, // rang, اعت_, חרונ, чай_,
+ {{0x44310065,0xdb1c0073,0x2fc7e545,0x201ec7ff}}, // hoz_, ferê, _sang_, enti_,
+ {{0x6b838ec9,0x69c985f8,0x61ebc17c,0x2fc784d2}}, // pang, rdee, _vogl, _pang_,
+ {{0xe3b083f8,0x7bc880eb,0x443101ca,0x78a9e546}}, // _نرم_, _gadu, joz_, rtev,
+ {{0x6811801b,0x61ebe53f,0x2ca900ee,0xc95980a0}}, // vědě, _togl, _aqad_, _فلاش_,
+ {{0x6b81b860,0x7bc88025,0x91fc80eb,0xdb1e026f}}, // _helg, _zadu, _stāv, _napá,
+ {{0x5184902a,0x6b8183ca,0x2fc7e547,0x7bc8d40f}}, // _руха, _kelg, _tang_, _yadu,
+ {{0x2fc7816f,0x224d93f4,0x629c195c,0xe1ff6548}}, // _uang_, lmek_, turo, _aló_,
+ {{0x63bc6549,0x2d800074,0x3f8581d0,0xdce40609}}, // rern, _teie_, malu_, _ddiġ,
+ {{0x3f858867,0xe1f981e2,0x63bc0f96,0x224d8214}}, // lalu_, nkų_, sern, nmek_,
+ {{0x63ad803a,0x63bc654a,0xf8c8801c,0x629c654b}}, // [65a0] đanj, pern, _xứ_, suro,
+ {{0x3f85825b,0xdb018207,0x44310511,0xd9bd86de}}, // nalu_, _eclé, coz_, ्युट,
+ {{0x225902fe,0x539b0039,0x6602b8ec,0xc0498129}}, // _fisk_, _ציטו, thok, _sườn_,
+ {{0xf5398038,0x7bc8e54c,0x9ccaa1f6,0x9f5c86c4}}, // _byť_, _sadu, зыка_, _elvè_,
+ {{0x6b81e54d,0x3f85e54e,0x201e8198,0x3ebf0b67}}, // _belg, kalu_, ynti_, _ćute_,
+ {{0x3f8580d2,0x6b8183a8,0x66028abd,0xc049801c}}, // jalu_, _celg, shok, _vườn_,
+ {{0xd9bd8996,0x3f85e54f,0x6b818333,0x9f47826f}}, // ्यूट, dalu_, _delg, _plný_,
+ {{0x3d118074,0x44316550,0xdb1c6551,0x68e450a3}}, // _देवे_, zoz_, terê, oxid,
+ {{0xdfd08307,0x80bc835a,0xc8c93792,0xbddb00e7}}, // لية_, ्टें, रिएट, llèg,
+ {{0x7bda9dae,0x6e246552,0xa2e696ba,0x201ee553}}, // _untu, _ajib, _позд, unti_,
+ {{0xa3cb0ebf,0x44311b77,0xa3df016f,0x290b8cd4}}, // _रोग_, voz_, णास_, úcar_,
+ {{0x63a50352,0x645acb3a,0xe9df026b,0xf8c880ff}}, // _schn, _hiti, _ahún_, _tứ_,
+ {{0x645ab06c,0x3f85d4a5,0x44316554,0x6e2457d3}}, // _kiti, balu_, toz_, _djib,
+ {{0x6d440d38,0x6e24019d,0x645aa054,0xbcfb6555}}, // _dzia, _ejib, _jiti, ncéi,
+ {{0x645ae556,0xed598253,0x2fdf026c,0x03d781c6}}, // _miti, _važi_, _đug_, עולם_,
+ {{0xa3b08076,0x644e6557,0x28b786a7,0x7522139c}}, // ट्त_, ambi, _अहमि, _žoze,
+ {{0xab871525,0x0841046d,0x21390168,0x645ae558}}, // [65b0] _пунк, _adìẹ_, _qysh_, _oiti,
+ {{0x63ad90d3,0x6e3d0840,0xa91d80ce,0x9f5e8216}}, // đank, llsb, kažn, mitó_,
+ {{0x22591cf3,0xa7fb0511,0x9f5e8661,0xa91d8968}}, // _tisk_, _cuña, litó_, jažn,
+ {{0x6b818310,0xdd958196,0x2eee016d,0x7afa04d6}}, // _selg, тады, äffa_, øtte,
+ {{0x69dd1429,0x645acdcc,0x3d1183ca,0x629881ed}}, // _inse, _biti, _देशे_, ervo,
+ {{0x741591cc,0xdb1c6559,0x3fcb80ab,0x645adde0}}, // ضوعا, ferè, _লক্ষ, _citi,
+ {{0x645a8587,0xe9d18013,0x78ad655a,0x6b81c0df}}, // _diti, لغة_, ltav, _velg,
+ {{0x3f85e1ff,0xe1f98110,0x645a81a8,0xe4e780e8}}, // walu_, ukų_, _eiti, _пізн,
+ {{0x224dbf41,0x78ad0f52,0x6b81a9ba,0x645ae55b}}, // rmek_, ntav, _telg, _fiti,
+ {{0x24558077,0x3f830683,0x645a833e,0x765b8420}}, // _شناس, _jeju_, _giti, _hiuy,
+ {{0x69dd0454,0x248d8b40,0x78ad156e,0x6d440019}}, // _onse, _hvem_, htav, _szia,
+ {{0x61ef3795,0x78ad0612,0x2612176d,0x645ae55c}}, // _nocl, ktav, pšom_, _ziti,
+ {{0x64488009,0x61e985f3,0xb6060032,0x78ad0168}}, // _yhdi, mjel, _aráà, jtav,
+ {{0x69dd444b,0x61e9e55d,0xd3720154,0x69cb97ab}}, // _anse, ljel, شهر_, _hage,
+ {{0x69cb81bf,0x3a2001e0,0x66d7819d,0x9f47816b}}, // _kage, tnip_, _ịko, _koní_,
+ {{0x61fb91e8,0x61e9abf2,0xba232597,0x3eaa801b}}, // nkul, njel, адск, žití_,
+ {{0xed598025,0x229b077f,0x3eb800dd,0x59a683dd}}, // [65c0] _kažu_, tàkì_, _sprt_, क्टर,
+ {{0x69cbcd52,0x09cd80c8,0x61e9b3d9,0xa7fb0511}}, // _lage, িউটা, hjel, _puña,
+ {{0x0d868c40,0xa3df035a,0x61fbdb89,0x248d802e}}, // влен, णार_, kkul, _avem_,
+ {{0x645a8511,0xa2d58bb8,0xc1bd8ebf,0x61ef639a}}, // _siti, भिन्, ्योग, _gocl,
+ {{0x61e9830b,0x645a8205,0xdca3a33f,0xe5a38a95}}, // djel, _piti, иаци, ричи,
+ {{0xdb1c51db,0x628a84dc,0xa7fb0118,0x7982808e}}, // terè, tsfo, _tuña, _seow,
+ {{0xdee33751,0x645ae55e,0x69cb9f57,0x66e32457}}, // _кори, _viti, _bage, _кора,
+ {{0x61e9e55f,0xa91d8b67,0x628a9c11,0x61fb8637}}, // gjel, gažo, rsfo, gkul,
+ {{0x69cbce39,0x6e2281bc,0x91fc80eb,0x38ab806a}}, // _dage, mnob, _stās, gør_,
+ {{0xdb188e14,0x2d849916,0x61fbe560,0x9f5e8388}}, // leví, _heme_, akul, vitó_,
+ {{0x25df01ab,0x69cbb5ce,0x61e9b25c,0x656d1705}}, // गाली_, _fage, bjel, _pfah,
+ {{0xa3cb1551,0x69cbe561,0x61e9911b,0x60c40dc5}}, // _रोज_, _gage, cjel, kwim,
+ {{0x69c09790,0xa3e68105,0x2d84e562,0xe89497d4}}, // meme, _बचा_, _meme_, _саль,
+ {{0x69c0e563,0xac242a15,0x69cbe564,0x200584e8}}, // leme, афск, _zage, chli_,
+ {{0xdeef93cd,0xe8e08028,0x9f5eb699,0x69cb820c}}, // _ты_, _đội_, sitó_, _yage,
+ {{0x78ad025d,0x63ad8025,0xd9e5800f,0x0f578039}}, // ttav, đani, कायत_, _ביום_,
+ {{0xc4c48065,0x5184065c,0xa91d817f,0x57b51dc7}}, // [65d0] _بے_, руса, jažl, лбат,
+ {{0xb8cb0105,0xcbbd80ab,0x673c0357,0x7649e565}}, // _गम_, _ইচ্ছ, _byrj, _shey,
+ {{0xcf938158,0xa3d68744,0xf7700591,0x7c351b2c}}, // שטע_, ियन_, تاق_, mozr,
+ {{0x69dd0352,0xa3b08e00,0x69c0bf7e,0x3a2682f7}}, // _unse, ट्स_, jeme, _njop_,
+ {{0xe9df10ab,0x69c0da9e,0xa91d826f,0x6aba808e}}, // _inú_, deme, važo, _aptf,
+ {{0x69cbe566,0xdb056567,0x30da00be,0xb4fc01c6}}, // _sage, _aché, _אַלע, ופדי,
+ {{0xdb1c0020,0x69cb9850,0x39468118,0x61e9e568}}, // teré, _page, _azos_, tjel,
+ {{0x69c09412,0xf1da8076,0xb05b016d,0x683d8722}}, // geme, भागन, _osäk, mèdi,
+ {{0x61e9e569,0x20559d51,0x28c90aed,0x69cbe56a}}, // rjel, лтор, रिटि, _vage,
+ {{0x61fbe56b,0xdb052ce0,0xa7fb1f49,0x248980eb}}, // skul, _tchè, _muño, šams_,
+ {{0x69cbe56c,0x61e9920e,0x69c0c71c,0x92000032}}, // _tage, pjel, beme, _bọ́,
+ {{0x69c0e567,0x8afa83c8,0x2005e56d,0xcf4982d0}}, // ceme, _אלעז, shli_, _ışık_,
+ {{0x81cc0a49,0xa7fb0144,0xa3df0c2d,0x92000032}}, // লাম_, _nuño, णां_, _dọ́,
+ {{0x409280f7,0x3a3f92a5,0x7bce0216,0xd91a9101}}, // _الور, llup_, udbu, _אונל,
+ {{0x9987017f,0xab274b60,0x3ea102f1,0x60c400b9}}, // _šiša_, гора_, juht_, wwim,
+ {{0x316de56e,0xa3d406ae,0x9200026b,0xf3f18032}}, // ncez_, _सोर_, _gọ́, _lọ_,
+ {{0xc0e30396,0x6604022e,0xb1463c0e,0xa3d6825e}}, // [65e0] ботк, _ilik, унал, ियम_,
+ {{0x69c0e56f,0x2d8496c9,0x3a3948ee,0xdee620bf}}, // zeme, _seme_, _eksp_, лози,
+ {{0x69c0afc6,0x645e02c4,0x81cc0264,0xdb15016a}}, // yeme, _iipi, লাব_, pezá,
+ {{0x69c087e2,0xc9760277,0x0caa8652,0x94aa9878}}, // xeme, _حادث, етки_, етка_,
+ {{0x645e022e,0x22406570,0x628e03d9,0x69c0a4cc}}, // _kipi, mlik_, lsbo, veme,
+ {{0xa91d80d2,0x22403566,0x443a025b,0xdb055686}}, // gažm, llik_, _jkp_, _sché,
+ {{0x660457fe,0x443a107a,0xd25a838d,0x69c0ac02}}, // _olik, _mkp_, нци_, teme,
+ {{0x22403586,0x201a5dc2,0x79865b41,0x777a81c0}}, // nlik_, lipi_, _jekw, wbtx,
+ {{0x224001b0,0xdd8f8a47,0x79860135,0x2df781c6}}, // ilik_, _رول_, _mekw, _עצמך_,
+ {{0x69c0cb31,0x78a09f17,0x2240208e,0x79860133}}, // seme, rumv, hlik_, _lekw,
+ {{0x10370051,0x22406571,0x69c0c19b,0xf1bf00e1}}, // רטים_, klik_, peme, ššie_,
+ {{0xa3b0be98,0x25a6e572,0x628e209b,0x0f570039}}, // ट्र_, ngol_, dsbo, טיים_,
+ {{0x22406573,0x645e165c,0x25a907f1,0x628e1c11}}, // dlik_, _bipi, _vcal_, esbo,
+ {{0x66046574,0x443a03ac,0x22405e14,0x60000106}}, // _elik, _ckp_, elik_, _sömn,
+ {{0x645e059e,0x3ffb8158,0x22406575,0x66042c15}}, // _dipi, _אפגע, flik_, _flik,
+ {{0xdb0ae576,0x6fa70540,0x22406577,0xa7fb0661}}, // lefó, _चाहू, glik_, _puño,
+ {{0x48151052,0x80df00d4,0x30153a17,0x2d8b1620}}, // [65f0] амас, नमें, адар, mace_,
+ {{0x645e6578,0x64468084,0x2d8b6579,0xdb0a8118}}, // _gipi, _ūkin, lace_, nefó,
+ {{0xf7700bbe,0x2240657a,0x764d657b,0xdb0a81ec}}, // سال_, blik_, _khay, gefü,
+ {{0x8f3783c8,0xe51686ae,0x79861384,0x248d368d}}, // נטאג_, _देहि_, _gekw, šemu_,
+ {{0x26d836c7,0xfce59170,0xa2bd9305,0x6385a1ae}}, // _erro_, роло, _वैज्, агла,
+ {{0x69cf270d,0xb09a0051,0x201a2f10,0x61ed28f2}}, // _hace, _בישר, cipi_, ljal,
+ {{0x2d8b657c,0x69cf3fbe,0x6d9c80e1,0x5fce109b}}, // kace_, _kace, _sťaž, _होटल,
+ {{0x61ed657d,0xe9d99485,0x69cf3f52,0xe516a5e8}}, // njal, тки_, _jace, _देवि_,
+ {{0x97c59bcc,0xade400d4,0x38af07c0,0xdb038036}}, // айпе, गासन_, nür_, igné,
+ {{0x764d02c1,0x66042cdb,0x22400182,0x998d8063}}, // _ahay, _slik, zlik_, wień_,
+ {{0x66040d38,0xb4de2ab7,0xf7459541,0xa3e2902e}}, // _plik, णम्_, рено, धार_,
+ {{0x69cf2509,0x645e657e,0x7bc3e57f,0x2d8b6580}}, // _nace, _sipi, lenu, gace_,
+ {{0x6e3b8010,0x32090079,0x7986022b,0x9f4a00e1}}, // _mkub, dhay_, _rekw, _robí_,
+ {{0x38af0059,0x7bc38074,0x656f047f,0x79866581}}, // dür_, nenu, acch, _sekw,
+ {{0x6e3bc40c,0x68fb8747,0x645e0010,0x7e6701c0}}, // _okub, lyud, _vipi, _nujp,
+ {{0x69260a42,0x38af3ea8,0x69cf6582,0x628e303e}}, // амба, für_, _cace, rsbo,
+ {{0xe8f7218c,0x22403586,0x65640578,0xdb1c188b}}, // [6600] алт_, rlik_, _igih, merí,
+ {{0xdb1c040e,0x224013d6,0x60048247,0x32090079}}, // lerí, slik_, _kòma, ahay_,
+ {{0xa3e98768,0x69cf30cc,0x05669a1a,0x32460329}}, // यान_, _face, авен, _венг,
+ {{0x44386583,0x61ed46e9,0x320903ec,0xdb1c05e4}}, // lor_, cjal, chay_, nerí,
+ {{0xfbe2800d,0x25a687f1,0x0cd48d0e,0x8cd90ad5}}, // पालम, rgol_, солю, मियो,
+ {{0x69c40412,0x2d8b001b,0x7bc3e584,0x2c6b8b40}}, // leie, zace_, genu, _død_,
+ {{0x44208052,0x3f98808b,0x6b8a9c11,0xdb1c0187}}, // đi_, órum_, rafg, lerâ,
+ {{0x44385ea5,0xada38a94,0xdb1c0876,0x69cf01df}}, // hor_, _масл, jerí, _xace,
+ {{0x443804a6,0xdb1c0a96,0xed579b10,0x2d8b1620}}, // kor_, derí, _вот_, vace_,
+ {{0xdefa9006,0x61ed42ac,0x764d571f,0x7e60e585}}, // вым_, zjal, _shay, _himp,
+ {{0xdb1c1984,0x2d8b01d0,0x4fd900be,0x7e60e586}}, // ferí, tace_, אַרד, _kimp,
+ {{0xa5bb3cd4,0x7e6081b9,0x80a70035,0x7af78338}}, // cnól, _jimp, टूडे, äxte,
+ {{0x7e60859c,0x61e2de45,0x2d8b2014,0x614381f3}}, // _mimp, _inol, race_, _деца,
+ {{0x443831d1,0x69cf6587,0xa3e98107,0x7c38e588}}, // gor_, _sace, याय_, novr,
+ {{0x69cf6589,0xd8388db7,0x2d8b454c,0xa3ae946d}}, // _pace, moč_, pace_, _कान_,
+ {{0xdb1c05e4,0x3f8c8e2e,0x20180010,0x3f879fb6}}, // cerí, ladu_, _amri_, _zenu_,
+ {{0xa3d68f85,0x661d658a,0xa3e98f0a,0x3f87a486}}, // [6610] िया_, misk, याम_, _yenu_,
+ {{0x320902a3,0x6e29aa81,0x38af080a,0x6e3be58b}}, // shay_, _sjeb, rür_, _skub,
+ {{0xe8e08028,0xb5fd817f,0xf1b98253,0xa3bc82f1}}, // _đời_, _liše, liše_, ेजन_,
+ {{0x661d03ab,0x201800f1,0xb7c600eb,0xd6d8658c}}, // nisk, _emri_, _apģē, шту_,
+ {{0x3d1195a7,0x81d580c8,0xf1b98253,0xd838807a}}, // _देखे_, _সকল_, niše_, koč_,
+ {{0x5f09852a,0x7c388da8,0x798d257a,0x00f604de}}, // ानम्_, govr, laaw, _המשך_,
+ {{0x569423e0,0xdb1c0e1b,0xa91d8084,0xe2a89ef7}}, // _март, yerí, maži, لاين_,
+ {{0x6e3b91e8,0x290d80ad,0x00858544,0xa3d903b7}}, // _ukub, tzea_, блио, _ठोस_,
+ {{0x7bc3b72d,0xfc3f12c9,0x4438658d,0x03c78009}}, // penu, ntís_, yor_, _всем,
+ {{0xa3ae853e,0x63ad8025,0xf1b980d2,0x4438658e}}, // _काय_, đans, diše_, xor_,
+ {{0xdb1c02ba,0x3f878036,0x798d3722,0x44f98264}}, // terí, _venu_, kaaw, _অধিক_,
+ {{0x661d0bfa,0x6443e58f,0x83fc8024,0x44386590}}, // gisk, llni, _viđe, wor_,
+ {{0xa3ae8a43,0x443849df,0xa91d9024,0x44278083}}, // _काम_, tor_, kaži, onn_,
+ {{0x2d9e1b97,0xd9e581ab,0xdd998176,0x9f430032}}, // şte_, कावत_, _blňk_, _dojú_,
+ {{0x4427bc69,0x09e611b3,0x443824c0,0xdb1c6591}}, // inn_, бовн, ror_, derã,
+ {{0xc332804c,0x63bb0025,0x661d1a22,0x3ea58957}}, // פון_, đuna, cisk, mult_,
+ {{0x44383804,0xe0da0a14,0xf1b9826c,0x69c46592}}, // [6620] por_, ыва_, ciše_, reie,
+ {{0xa91da5ef,0x7e690353,0x25ad8087,0x69c412af}}, // gaži, _čepr, _acel_, seie,
+ {{0x6abe23fe,0x3ea58006,0x7e6091d6,0x8d1a803d}}, // _uppf, nult_, _pimp, گزار_,
+ {{0x6da6846e,0xad9b00f7,0xdb1c0722,0x60c98518}}, // сида, thúi, merà, kwem,
+ {{0x7c388025,0x78a40e67,0x91a6826b,0xb87b0032}}, // tovr, quiv, _ako̟_, _aríf,
+ {{0x3ea5a5d3,0x661d6593,0x6d40826f,0x20180010}}, // kult_, zisk, _vyma, _umri_,
+ {{0x7e60c909,0x6d40809a,0xbea30425,0xd2508077}}, // _timp, _wyma, _нарк, ونت_,
+ {{0x44278af9,0x30a69f3e,0xa07484fa,0x187495a6}}, // ann_, бров, огич, огия,
+ {{0xd8388d26,0x661d6594,0x050a00ab,0x3f8a01a1}}, // toč_, visk, রপুর_, _kebu_,
+ {{0xb5fd803b,0xf1b9803a,0x661d009a,0x224f8118}}, // _više, više_, wisk, _ehgk_,
+ {{0x661d4362,0x44270459,0x50d684c0,0x28cf000c}}, // tisk, _ın_, _گزار, _सैनि,
+ {{0xf1b98503,0xd838807a,0xa2d5952c,0x6f0901d0}}, // tiše_, soč_, भिर्, _řeck,
+ {{0x661d6595,0x2ca69ac4,0xdddb189e,0x05aa01d0}}, // risk, nuod_, lptū, _कारब,
+ {{0x661d02d7,0x3f8a00d2,0xf1b984a8,0x443e8d89}}, // sisk, _nebu_, riše_, _jkt_,
+ {{0xa3e9853e,0x201e973d,0xdb1a82af,0x6602e596}}, // यात_, miti_, _natü, kkok,
+ {{0x201eb30d,0x26df0052,0xdb1a8061,0x7a3e8168}}, // liti_, _čuo_, _ható, pëtu,
+ {{0x443ee597,0x3f8a005c,0xa5bb2e88,0x44279106}}, // [6630] _okt_, _bebu_, riód, ynn_,
+ {{0xb76733c2,0x261b8074,0xfc3f0899,0x3f6718a3}}, // стай, _मगही_, buía_, стаб,
+ {{0x3f8a218d,0x6e2d026c,0xb87b3bfe,0xfc3f023e}}, // _debu_, _kjab, _oríg, stís_,
+ {{0x7afa224c,0xa9c70077,0x7bd54159,0x442c812b}}, // ätte, _ازدو, ldzu, _ajd_,
+ {{0x69a2800f,0x201ee598,0xd49bc64f,0xa91d8110}}, // _गाजी, kiti_, уре_, paži,
+ {{0x201e80ce,0x3eb30706,0x3f8a1600,0xdb188198}}, // jiti_, ptxt_, _gebu_, kevä,
+ {{0x59da9d01,0x81cc00c8,0x442781e4,0x93fc00be}}, // _मोहर, লার_, rnn_, רלוי,
+ {{0xdb1c037d,0x4095940b,0x1dd90576,0xdb1a8032}}, // nerá, орот, _बोलत, _aató,
+ {{0x6609ba48,0x201e8010,0x661b8280,0x7bc712ae}}, // _ilek, fiti_, _imuk, keju,
+ {{0xdb1a87a3,0x39438029,0x2d8f91e6,0x2ca080f7}}, // _cató, ājs_, lage_, áide_,
+ {{0x65c61287,0xa3d6a0d5,0x1dd5016f,0xbddb24df}}, // обна, ियर_, धयंत, rlèn,
+ {{0x26dc8067,0xed5a013a,0xd5aa016f,0x361600e8}}, // _drvo_, ион_, _काळज, ієнт,
+ {{0xdb1c6599,0x6609e59a,0x89378872,0x67d5a3d7}}, // derá, _mlek, _اعضا, _моду,
+ {{0x201e82a5,0xdb1c0722,0x2d8f80e8,0xbddb0036}}, // citi_, terà, hage_, llèl,
+ {{0x66098006,0xdd920117,0x8c431a34,0xe363035f}}, // _olek, _ہوں_, фере, дкри,
+ {{0x91e30d9e,0x66098870,0x3a290061,0xdb1c38b4}}, // мосе, _nlek, gnap_, gerá,
+ {{0x2bb2090a,0x2d8fe59b,0x81cc00ab,0xa7fb016a}}, // [6640] _जापा, dage_, লাল_, _zuñi,
+ {{0x661b9efb,0x39420084,0xdfa601a8,0x7f8c83de}}, // _amuk, _vyks_, تحري, ָנאַ,
+ {{0x06ca1287,0x15fe0076,0xdb1c08a4,0x6609a120}}, // ргей_, _उकार_, berá, _blek,
+ {{0x201e812b,0x3f8a027f,0xdb1c188b,0x7c3c0c43}}, // ziti_, _webu_, cerá, lorr,
+ {{0x61e654e6,0x660280a4,0xb12480ab,0x3f8a059e}}, // _inkl, rkok, _পর্ব_, _tebu_,
+ {{0xe7ae8ebf,0x60048362,0x661bb328,0x38b44255}}, // ज्ञप, _còml, _emuk, mär_,
+ {{0x38b40884,0x3d1a8063,0xe8e08028,0xdb1a86a5}}, // lär_, _मेरे_, _đối_, _rató,
+ {{0x443e82f7,0x201e803d,0x7c3c0cdb,0x2d8f9dbd}}, // _wkt_, witi_, horr, cage_,
+ {{0x201e803a,0x6d4410af,0x7c3c659c,0x38b40884}}, // titi_, _nyia, korr, när_,
+ {{0xdb1c0a56,0x2d82067b,0x6e2d2ca2,0xb8f282f1}}, // zerá, ebke_, _sjab, _हई_,
+ {{0x443c824a,0x7c3c659d,0x38b40106,0xdb1ae59e}}, // mov_, dorr, här_, _obté,
+ {{0xae1609a3,0x443ce59f,0x200ce5a0,0x78b60110}}, // दोलन_, lov_, shdi_, ktyv,
+ {{0xdb1c016a,0x7bc7365d,0xdb18b568,0x7c3c65a1}}, // verá, teju, sevä, forr,
+ {{0x443ce5a2,0x61e60e3b,0xdb0565a3,0x7c3c0102}}, // nov_, _ankl, _achá, gorr,
+ {{0x2d8f82be,0x7bc72b40,0xf1b982ce,0xa2a2864a}}, // yage_, reju, liša_, _गिफ्,
+ {{0x38b400f2,0xceeb003d,0x200a65a4,0x7bc7588b}}, // fär_, یران_, _albi_, seju,
+ {{0x443c9fc2,0x7c3c19fd,0xf1b9842b,0xb5fd8b67}}, // [6650] kov_, borr, niša_, _niša,
+ {{0xd6db0ae7,0x61e61be9,0x443c8038,0x661be5a5}}, // ите_, _enkl, jov_, _smuk,
+ {{0x2d8fe5a6,0xa3e9912a,0x443c824a,0x6609858f}}, // tage_, यास_, dov_, _plek,
+ {{0xd2468b76,0x38b400f2,0x24890a0f,0xe9df001c}}, // _سن_, bär_, _kwam_, _chút_,
+ {{0xd12680f7,0x2d8fe5a7,0x600d8020,0x27f84ec3}}, // _أم_, rage_, _húme, _forn_,
+ {{0x798be5a8,0x2d8fe5a9,0x60cd2479,0x6447276e}}, // _segw, sage_, mwam, nlji,
+ {{0x27e980ff,0x2c5d82d6,0x60c297c9,0x6e3d106f}}, // _đan_, _jňdi_, _apom, kosb,
+ {{0x661b82a0,0xa3e98424,0x83fc812b,0x7c3c0fe0}}, // _umuk, याह_, _viđa, zorr,
+ {{0x7e640009,0x443ce5aa,0xd24e84a3,0x64470796}}, // _riip, bov_, دني_, klji,
+ {{0x443ce5ab,0x798b9f2e,0x2a6c81a1,0x62955dea}}, // cov_, _wegw, _fudb_, kszo,
+ {{0xb8f28403,0xe7398185,0x2489372d,0x26158038}}, // _है_, сен_, _awam_, sťou_,
+ {{0xa3e985b3,0xbddb03d3,0x60cd1e7f,0x62955e59}}, // याव_, blèm, kwam, dszo,
+ {{0x7c3c65ac,0xa8798158,0x5fac016f,0x998d8035}}, // torr, _פאַר, _चालल, zieś_,
+ {{0x387fb4c3,0x987a810f,0x600d8118,0x64470353}}, // mpur_, _האסט, _búme, glji,
+ {{0x63bc8214,0x91e61ddf,0x09e600e8,0x61f981e0}}, // _örne, пове, повн, _kowl,
+ {{0x443ce5ad,0xfc3f0d76,0x78b60009,0x249f00eb}}, // zov_, quín_, ttyv, šuma_,
+ {{0x78a98020,0x7e7b26d2,0x7c3c65ae,0xdb0a83a7}}, // [6660] nuev, _čupa, porr, lefô,
+ {{0x443c8069,0x81cc00c8,0x13a80416,0xa3d403a4}}, // xov_, লাই_, _بندی_, _सोच_,
+ {{0xe9df0028,0x443c875f,0x290000b9,0x78b60198}}, // _phút_, vov_, syia_, styv,
+ {{0xe7e28074,0x2bca20f2,0xb5fd811f,0x7d0682f1}}, // _कोना_, ाजवा, _piša, _üksk,
+ {{0x443c9fc2,0xe516a743,0xdd8f8199,0x798463ea}}, // tov_, _देखि_, اوه_, mbiw,
+ {{0xb5fd811f,0x25bfc411,0xf1b99024,0xa7fb63ab}}, // _viša, _ebul_, viša_, _buñu,
+ {{0x443ce5af,0x60c2beaf,0x6458be5b,0x78a40a53}}, // rov_, _spom, rmvi, lriv,
+ {{0x443ca39f,0x30a68a7c,0xb5fd807a,0x64471809}}, // sov_, пров, _hišn, zlji,
+ {{0x61f982c1,0x443c886f,0xb5fd812b,0x3ebf025b}}, // _dowl, pov_, _kišn, _ćuti_,
+ {{0xdb1e05a4,0x6e98997b,0xdcef01a9,0x779280b7}}, // _japó, овор_, sacī, ریبا,
+ {{0xa3e980cf,0x6f01831d,0xbb4a9368,0x61f9e5b0}}, // यार_, gylc, _الان_, _fowl,
+ {{0x6e22b49a,0x6a168872,0x60cd0035,0x61f981b9}}, // niob, _عبار, ywam, _gowl,
+ {{0x7e6e022c,0x35470c99,0x78a98144,0x6e3d0061}}, // _lubp, яхов, cuev, rosb,
+ {{0x78a403a6,0x2bb2016f,0x394682c4,0x62950061}}, // driv, _जाणा, _iyos_, tszo,
+ {{0x9f5800f1,0x7ac41b23,0x897a0135,0x60cd0300}}, // _dorë_, нсте, _ọgụg, wwam,
+ {{0x7afa016d,0x3ce04741,0x6447007a,0x60cd65b1}}, // ätta, _friv_, slji, twam,
+ {{0x69340db6,0x62950019,0xe1340048,0x660d568b}}, // [6670] енту, sszo, енты, _ilak,
+ {{0xb87b01ac,0xf7701a3c,0xdb1e2509,0x60cd020c}}, // _príb, هام_, _capó, rwam,
+ {{0x2bb22539,0x6d41017b,0xdb1c01ec,0x59f90098}}, // _जाता, şlad, gerä, _деня_,
+ {{0xe0df0028,0x78a462b9,0x442365b2,0x64418ffe}}, // _trò_, briv, lij_, _skli,
+ {{0x78a41621,0x660d5f82,0xa3ae8105,0x20cd025b}}, // criv, _mlak, _काश_, _uži_,
+ {{0xe9df0174,0xb4ad81d2,0x44230e8b,0xce6b060a}}, // _thús_, _कमी_, nij_, _град_,
+ {{0x80db0592,0x660d003a,0xa3ae9a87,0x394696fb}}, // निवे, _olak, _कार_, _ayos_,
+ {{0x44230069,0x2ca08267,0xdb1c041c,0x00e59d7b}}, // hij_, šida_, serç, джин,
+ {{0x9f5e8013,0x81cf80ab,0x200e0084,0xdcfb80d7}}, // chtú_, শাল_, škių_, _ارزش_,
+ {{0x660d65b3,0x22491600,0xdb1e01df,0xb5fd8668}}, // _alak, hlak_, _xapó, _kišo,
+ {{0xe5a60dc8,0x6da618f6,0x660d5660,0x60c48029}}, // мини, мина, _blak, _ģime,
+ {{0x3f9c826c,0x387fbac5,0xb8050264,0x63ad8326}}, // _edvu_, ppur_, োচিত_, ɗana,
+ {{0x224902d5,0x44233e6b,0x21010110,0x2bb202f1}}, // dlak_, fij_, nčią_, _जादा,
+ {{0xb05b00f2,0x660d2388,0x442301c8,0x9f580242}}, // _spän, _elak, gij_, _forè_,
+ {{0x81b480ab,0xbb430b69,0x3f85e5b4,0xa3ae86ae}}, // _ছোট_, верк, mblu_, कलत_,
+ {{0x78a40012,0xa3e982b4,0x3cee835a,0xc332836b}}, // triv, याँ_, _आपले_, דול_,
+ {{0xa3ae9094,0x7e7c005c,0x44234191,0x2bae816f}}, // [6680] _काल_, _strp, bij_, _घाला,
+ {{0x44230db7,0xa5bb04c3,0x60048014,0x7d02804f}}, // cij_, nión, _còmh, vyos,
+ {{0x44441dbe,0xb5fd8301,0x216a0652,0x22493d9b}}, // _ik_, _višn, _мини_, blak_,
+ {{0x78a4003b,0xe9d9809a,0xa3e60beb,0x7bc1e5b5}}, // priv, jdź_, फाक_, _ablu,
+ {{0xa3e9c252,0x444465b6,0x09bc80c8,0x6e229e3f}}, // यां_, _kk_, _অফলা, riob,
+ {{0xfc3f062f,0x444465b7,0x7d02b3ef,0x6aaade97}}, // oría_, _jk_, ryos, tuff,
+ {{0xb87b65b8,0x2bc00519,0x7e7c1b40,0xa06751e2}}, // _tríc, श्या, _utrp, _цара_,
+ {{0x44443463,0x6aaa9bea,0x44232c00,0x3f8e8b80}}, // _lk_, ruff, zij_, _sefu_,
+ {{0xb5fd8025,0x6aaaddf5,0x2d86c54d,0xfa1c027d}}, // _mišl, suff, mboe_, _đất_,
+ {{0x444465b9,0x224921a2,0x764447fe,0xa3e9e5ba}}, // _nk_, zlak_, _akiy, याः_,
+ {{0xe8e08028,0x2fd854fb,0xf8ab0fea,0x7c3d81fa}}, // _đổi_, _marg_, _चम्प, _ísra,
+ {{0x444465bb,0xfc3f05b4,0x2fd8376c,0xb5fd8279}}, // _ak_, dría_, _larg_, _nišl,
+ {{0x442365bc,0x7afa00f2,0xfc3f0333,0x224909ff}}, // tij_, ättn, ería_, vlak_,
+ {{0xa5bb492f,0x3fd900ab,0x68e28019,0xfc3f0333}}, // ción, _দক্ষ, _irod, fría_,
+ {{0x444465bd,0x44232c00,0xfc3f05e4,0x27ed001c}}, // _dk_, rij_, gría_, _đen_,
+ {{0x44442674,0xb5fd826c,0x7bd70079,0xa3e58beb}}, // _ek_, _rišo, _waxu, _बोन_,
+ {{0xdb0502af,0xa3ae8744,0xfc3f0511,0x224965be}}, // [6690] _schä, कला_, aría_, rlak_,
+ {{0xfc3f05a4,0xb87b2509,0x68e2e5bf,0xb5fd811a}}, // bría_, _cría, _mrod, _pišo,
+ {{0x23d98af3,0xdb1c032b,0x2904951e,0x60048722}}, // _योगद, terå, hyma_, _còmi,
+ {{0x68e2d1f4,0x1cb98013,0xa01b1252,0x78a2027f}}, // _orod, كاتب_, _stöd, šova,
+ {{0x629ae5c0,0xe00e835a,0x2fd80314,0xdb0983fb}}, // _avto, िसाद_, _farg_, čníc,
+ {{0xa5bb04c3,0x444465c1,0xe9d9809a,0x2fd85b9c}}, // xión, _xk_, wdź_, _garg_,
+ {{0x4429803a,0x7c23b6c0,0xbb429a19,0xaad8001b}}, // đa_, rinr, лешк, डिएक,
+ {{0x68e2812b,0xe7d7ce09,0x656d01d4,0x200eab78}}, // _brod, _भोजप, _ngah, _alfi_,
+ {{0xa5bb0118,0x61fd1c11,0x78ad12d0,0x44211995}}, // tión, _mosl, nuav, _omh_,
+ {{0x6445022c,0xb5fdb1a7,0x61fd1254,0x656d00b4}}, // _nkhi, _mišm, _losl, _agah,
+ {{0xf6260951,0x68e2ba65,0x99918019,0x3f910102}}, // едно, _erod, őző_, _mezu_,
+ {{0x444433e6,0x644514ec,0xa5bb062f,0x61fd0029}}, // _sk_, _akhi, sión, _nosl,
+ {{0x8fa5baca,0x78ad1ae3,0x657b8db1,0x68e2e5c2}}, // _зале, juav, _mfuh, _grod,
+ {{0x7e698df1,0x69d9c584,0xfc3f05e4,0xb5fd817f}}, // _liep, _hawe, tría_, _sišl,
+ {{0x42560f13,0xa5bb2a63,0x69d99588,0x48bf00ab}}, // етат, gnós, _kawe, উটোর,
+ {{0xe7ed85b3,0x7e69809a,0x78bb81e2,0x69dbd15d}}, // जावा_, _niep, otuv, ndue,
+ {{0x78bb81bb,0x98a30103,0x16c8140d,0x656d80e1}}, // [66a0] ntuv, лите, रब्ब, ľahl,
+ {{0x7d060009,0x69d98114,0x657b819d,0x44440039}}, // myks, _lawe, _afuh, _uk_,
+ {{0xa3e988fd,0x39ea1927,0xb4e892ce,0x7bce03af}}, // याई_, _едно_, यम्_, mebu,
+ {{0x69d9cbe9,0x186a11d2,0x32090358,0x7bdc0c42}}, // _nawe, _нами_, ckay_, ldru,
+ {{0x7e6980c9,0x2fd806da,0x2d923753,0xdb15016a}}, // _diep, _targ_, _meye_, bezó,
+ {{0x7bdc2ed0,0x6e260a0f,0x229a80eb,0x6298e5c3}}, // ndru, hikb, tīk_, tsvo,
+ {{0x68e28503,0x69d9e5c4,0xada6d22f,0xe805016f}}, // _srod, _bawe, _павл, _शकता_,
+ {{0x20bd8063,0xd25787ac,0x7bce65c5,0x6298844e}}, // ्बंध, нцы_, hebu, rsvo,
+ {{0x7bce65c6,0x600d88f1,0x24800069,0xe3b282e3}}, // kebu, _súma, _ntim_, _مرا_,
+ {{0x25a00886,0x61eba1cd,0x76428079,0x4c9b8039}}, // _adil_, _engl, looy, _משמע,
+ {{0x8f34879e,0x91e68a7c,0xddc78353,0x442109da}}, // тенц, _поже, _hujš, _rmh_,
+ {{0x7bdae5c7,0xa5bb0207,0x68e28f06,0x69d9c9a2}}, // _hatu, ciól, _trod, _gawe,
+ {{0x7bdabbc2,0x68e2809a,0x61fd65c8,0xb87b65c9}}, // _katu, _urod, _rosl, _arín,
+ {{0x7bda8057,0x25a0016a,0x7bce07c1,0x69d9e5ca}}, // _jatu, _edil_, gebu, _zawe,
+ {{0x7bdae5cb,0x8ff78bca,0x53340190,0x69d99f62}}, // _matu, _سرور_, герт, _yawe,
+ {{0x7bdae5cc,0x80ca00ab,0xfc3f0118,0x79964500}}, // _latu, রিন্, crín_, gayw,
+ {{0x7e699c5d,0x97a722b7,0x32090079,0x9c390081}}, // [66b0] _riep, ерел, rkay_, _опит_,
+ {{0x60db00d2,0x320906c0,0xc02e801c,0x442100b9}}, // _šums, skay_, _điệu_, _umh_,
+ {{0x7e69e5cd,0x4fc40110,0x2d92022e,0x69cd65ce}}, // _piep, ыста, _yeye_, peae,
+ {{0xc8a91513,0x78bb8110,0x7bda9761,0xa64782e3}}, // _छटपट, ytuv, _aatu, رخان,
+ {{0x7bda8559,0x3f910042,0x629a016b,0xddc08087}}, // _batu, _tezu_, átov, _simţ,
+ {{0x7bda8d6d,0x99dd801b,0x69d98077,0xfaa59a36}}, // _catu, plňk, _sawe, вако,
+ {{0xf7718bca,0x629a070d,0x7e69b64f,0x764281b4}}, // یاد_, štov, _tiep, booy,
+ {{0xb5fd92b9,0xf99f0247,0x7c2715e8,0x7bce009a}}, // _mišk, _elèv_, hijr, zebu,
+ {{0x69db811e,0x7bdae5cf,0x290b81ac,0x78bb97ae}}, // rdue, _fatu, áca_, utuv,
+ {{0xa2a98cce,0x7bdaac22,0x78bb9482,0x69d980b8}}, // _टिप्, _gatu, rtuv, _wawe,
+ {{0x4427e5d0,0x6443a536,0x78bb8b0c,0xb5fd8279}}, // min_, moni, stuv, _nišk,
+ {{0x644385e7,0x7d060364,0x442780ef,0x61ebe5d1}}, // loni, tyks, lin_, _ungl,
+ {{0x64438006,0xed57035f,0x44278ad0,0x51848329}}, // ooni, кою_, oin_, _суха,
+ {{0x4427e5d2,0x6443e5d3,0x8fa58bba,0xb87b65d4}}, // nin_, noni, _раке, _prín,
+ {{0x7bdc044e,0xb5fd8353,0xae1f102e,0x78a9b3f9}}, // rdru, _višj, मोहन_, lrev,
+ {{0x7bce04bf,0x6443e5d5,0xb87b00f7,0x7ae5012b}}, // sebu, honi, _crío, _drht,
+ {{0x4427e5d6,0xf992893f,0x41271927,0x6443a397}}, // [66c0] kin_, ירן_, вото_, koni,
+ {{0x6443af1e,0x4427e5d7,0x6d5600b9,0xb87b01a8}}, // joni, jin_, _izya, _trín,
+ {{0x78a98a0f,0x76428079,0xb87b1eec,0xaad080c2}}, // hrev, tooy, _frío, _थैंक,
+ {{0xf53f0bbd,0x225f8748,0x6ea488af,0x78a9a7d1}}, // mgår_, jmuk_, _किसु, krev,
+ {{0x4427e5d8,0xa3ae8074,0x764281b4,0xcd7703de}}, // fin_, कलल_, rooy, דענק_,
+ {{0x625307fa,0x78a9a7f2,0xe1f98110,0xc44880d7}}, // nçon, drev, ejų_, ریان_,
+ {{0x3cee8063,0x224d808e,0xd25a89a0,0xf53f004a}}, // _आपके_, flek_, мци_, ngår_,
+ {{0x7bda822e,0x44278068,0xc3330158,0x76428079}}, // _watu, ain_, יוו_, qooy,
+ {{0x4427ab89,0xdfd08307,0xd83a8084,0x6443e5d9}}, // bin_, مية_, дэн_, boni,
+ {{0x44278a73,0xad1b8051,0x224de5da,0x212be5db}}, // cin_, _חומר, alek_, úcha_,
+ {{0xa3c81513,0x645a8214,0x201980f1,0x224da168}}, // ोजन_, _ihti, ësia_, blek_,
+ {{0x78a98646,0xf53f006a,0xe7e28c28,0xd3788390}}, // brev, dgår_, _कोरा_, _daće_,
+ {{0x78a980a9,0x660085f5,0xf1b38039,0xd01aa1ae}}, // crev, _momk, _כסף_, _офк_,
+ {{0xb5fda52d,0x9f589931,0x3cf5816f,0xa3a404ae}}, // _pišk, óró_, ्हते_, ијск,
+ {{0x2bc49cd4,0xdb2307d9,0xb87b0013,0x442d003a}}, // ल्या, _ürün, _prío, đe_,
+ {{0x9f5c8a56,0x5a340cd0,0xb5fd80ce,0x6443bc95}}, // _nové_, анст, _višk, zoni,
+ {{0x4427e5dc,0x2d9965dd,0x995555e6,0x9f58046d}}, // [66d0] yin_, mase_, укац, _dorí_,
+ {{0x4427e5de,0x4427360f,0x6d4d65df,0x2bc48105}}, // xin_, _ön_, _iyaa, ल्मा,
+ {{0x4427e5e0,0x6d4d008e,0x62670eca,0x2a6001c0}}, // vin_, _hyaa, _لاحق, bmib_,
+ {{0x6443e5e1,0x4427b4b1,0x2af2001b,0x6d4d0c2e}}, // woni, win_, ीहरु_, _kyaa,
+ {{0x2bc0053f,0x44278e79,0x61ef3f55,0x62818267}}, // श्वा, tin_, _incl, _stlo,
+ {{0xe7e280c2,0x2d9965e2,0x1c4485a8,0x4427e5e3}}, // _कोला_, hase_, анім, uin_,
+ {{0x44278788,0x69dd65e4,0x6443b09c,0x63b501a9}}, // rin_, _kase, roni, igzn,
+ {{0x4427a776,0x645ae5e5,0xe0d996cf,0xda13001b}}, // sin_, _ehti, еви_, ठसित_,
+ {{0x69dd65e6,0x44279b7b,0x224da203,0x6d4d3371}}, // _mase, pin_, rlek_, _nyaa,
+ {{0x69dd3553,0x4427c4e2,0x7d048364,0x224de5e7}}, // _lase, qin_, äise, slek_,
+ {{0x6d4d02a3,0x61ef4f42,0x765b8028,0x63b8016d}}, // _ayaa, _oncl, _khuy, _övni,
+ {{0x69dd00fa,0x6d4105c5,0x7c250013,0xb87b01ac}}, // _nase, ğlan, _amhr, _príl,
+ {{0x6d410182,0xa0a58084,0xe8eeb45a,0x10a5891e}}, // şlan, _салд, _ул_, _силн,
+ {{0x44258358,0x61ef1726,0xb87b01ca,0x6d4d02c4}}, // _jml_, _ancl, _crím, _dyaa,
+ {{0x69dd4a4c,0xdceb81b9,0x2fc782c4,0x6253360c}}, // _base, bagħ, _ibng_, rçon,
+ {{0xb5fb0e14,0xceb28051,0x61fbe5e8,0x7e6d31c1}}, // rmác, _היה_, njul, _giap,
+ {{0x57ea0254,0x76460084,0x62530866,0x6d4d03f7}}, // [66e0] ндам_, moky, pçon, _gyaa,
+ {{0x67160a14,0xb90c8032,0x61fbe5e9,0x7649caf5}}, // льеф, _aimọ_, hjul, _akey,
+ {{0x2c6f04b7,0x55e690ca,0x2ca0006a,0x7bde09da}}, // _iżda_, ложб, _hvid_, _iapu,
+ {{0x765b8142,0x442a65ea,0x69dd65eb,0x7bde65ec}}, // _chuy, lib_, _gase, _hapu,
+ {{0x7bde5094,0xa4268e02,0x7bc381ec,0x80d386a7}}, // _kapu, лькл, ffnu, _बैले,
+ {{0x442a65ed,0x69dd65ee,0x2d9904c3,0xf74615ac}}, // nib_, _zase, zase_, _бего,
+ {{0x7bde4032,0x68e412bd,0x0cb1326f,0x69dd1167}}, // _mapu, lvid, _जम्म, _yase,
+ {{0x2d994a25,0x69c41ed4,0x7bde4f25,0x442a65ef}}, // xase_, nfie, _lapu, hib_,
+ {{0x68e44d2c,0x442a65f0,0x48149baa,0x13b6803d}}, // nvid, kib_, имис, _مصاح,
+ {{0x7e6d1a67,0x442a07d5,0xa3ae800f,0xd07381a8}}, // _siap, jib_, _काट_, حديث,
+ {{0x2d994774,0x6306af0a,0x6fc6035a,0x442a2b95}}, // tase_, _حوال, र्यं, dib_,
+ {{0x90e68f24,0xb5fd8805,0x7bde02d5,0x3f9a01bc}}, // _مستن, _piši, _aapu, hapu_,
+ {{0x2d9965f1,0xa3e9835a,0x2ed11250,0x69dd17ab}}, // rase_, याच_, _सन्त, _rase,
+ {{0x68e40395,0x7bde65f2,0x2d99002a,0xb5fd8353}}, // dvid, _capu, sase_, _viši,
+ {{0x3a291a67,0x2d991c56,0x7bde0057,0x179784de}}, // tiap_, pase_, _dapu, _גדול_,
+ {{0x69dd2d68,0x59df2743,0xb5fd8968,0x2bc48035}}, // _qase, _पोखर, _tiši, ल्ता,
+ {{0x442a65f3,0x69dd47d4,0xaca3801c,0x290f2491}}, // [66f0] bib_, _vase, _trứn, ága_,
+ {{0xf1b9b69f,0x69dd3f60,0x3a2900dd,0x7bde00d7}}, // riši_, _wase, siap_, _gapu,
+ {{0xa3ae82f1,0x69dd2446,0x76b9002e,0x09ac80ab}}, // _काज_, _tase, тлер_, ক্যা,
+ {{0x7bc8e5f4,0x3ce90082,0xf1b9a7b1,0x644765f5}}, // _abdu, _krav_, piši_, loji,
+ {{0x798d4492,0x44258358,0x563800be,0x7ae381c0}}, // mbaw, _pml_, נאדע_, wvnt,
+ {{0x64470bb6,0x3ce900d2,0xb5fdac08,0xab298993}}, // noji, _mrav_, _nišv, кола_,
+ {{0x765b801c,0x3d958a7c,0x628508ae,0xf10d852a}}, // _thuy, _вибр, _jtho, िन्द_,
+ {{0x442a1086,0x64470010,0x0205a796,0x212b8187}}, // zib_, hoji, рзин, úcho_,
+ {{0x20030025,0x442a1072,0x3ce901c0,0x644765f6}}, // _koji_, yib_, _nrav_, koji,
+ {{0x442a65f7,0xb87b01ac,0x644700a4,0x600982de}}, // xib_, _príj, joji, тном_,
+ {{0xe7399927,0x64471d57,0x7bde365d,0x6285022c}}, // тен_, doji, _rapu, _ntho,
+ {{0x25e603dd,0x200303ac,0x6e2b846d,0x60168187}}, // _जोशी_, _loji_, nigb, _câmb,
+ {{0x7bde048c,0x32020efc,0x628565f8,0x7646472c}}, // _papu, _roky_, _atho, roky,
+ {{0x78ad2944,0x92d780ab,0x6447588d,0xd3788bda}}, // drav, ামী_, goji, _kaća_,
+ {{0x442a65f9,0xeb97117e,0xe2970087,0x7bde18d5}}, // rib_, рис_, иар_, _vapu,
+ {{0x442a5abd,0x660f11ee,0x224b008e,0x99dd816b}}, // sib_, ckck, _ckck_, plňu,
+ {{0x69c420e3,0x3ce92823,0x2ca00052,0xa3d5e5fa}}, // [6700] rfie, _grav_, _uvid_, िजा_,
+ {{0x8fa610f8,0x68e4186c,0x644765fb,0x07a611b1}}, // раже, rvid, coji, ражн,
+ {{0x81cb0a49,0x6b9c0e56,0x23fb04de,0x799b876d}}, // লয়_, marg, _ספטמ, kauw,
+ {{0x3f9a65fc,0x38710088,0x68e408dc,0x1a9b03de}}, // sapu_, _žorž_, pvid, _ביטע,
+ {{0xf1b981dd,0x2ca080f7,0x78ad0ba3,0x59df0075}}, // nišu_, éid_, crav, _पोटर,
+ {{0x66e60698,0xdb1c0019,0x8aa6af75,0xfaa69ac9}}, // _кога, kerü, арид, ашин,
+ {{0xbea3013a,0x7c2aa67f,0x28ac03eb,0xc7a3242e}}, // _марк, rifr, _चिडि, _фиск,
+ {{0x6b9c085c,0x3f9802f7,0x4aa780d4,0xc4868ae7}}, // harg, _heru_, _गिरव, _влак,
+ {{0x6b9c1ad1,0x6447004f,0x9a6781a8,0xc9148264}}, // karg, yoji, جميل_, াপ্ত_,
+ {{0x6016802e,0xf806a503,0x66040362,0x3f980326}}, // _sâmb, рчин, _aoik, _jeru_,
+ {{0x08570051,0xd6d78278,0x6d41017b,0xb87b00e1}}, // שבים_, атя_, ğlam, _prík,
+ {{0x3ce90db7,0x47dc80ab,0x6d41017b,0xf1b98b80}}, // _prav_, বাধী, şlam, fišu_,
+ {{0x644765fd,0x0ca48035,0x6b9c0625,0x66040f3e}}, // toji, _ऑटोम, farg, _doik,
+ {{0x224b008e,0x78a281a1,0x6b9c47f7,0xddc401a1}}, // _skck_, _ivov, garg, _viiš,
+ {{0xda78001e,0xbebc8029,0xdb1c4640,0x99dd826f}}, // ият_, dzīg, deró, plňt,
+ {{0x78ad65fe,0x52141ccf,0x200304b9,0x3ce92417}}, // trav, одст, _soji_, _trav_,
+ {{0x6b9c0876,0x798d1067,0x644705b9,0x443a00b9}}, // [6710] barg, rbaw, poji, _fjp_,
+ {{0x6b9c1f5c,0x96da83bb,0x3ea706a5,0xb5fd816b}}, // carg, _बनाउ, ánto_, _lišt,
+ {{0xfc3f04c3,0x8ae4035f,0x628565ff,0x9f5803a8}}, // buír_, _діял, _utho, _porá_,
+ {{0xb5fd803a,0x7d0b816d,0x26c10176,0x09ac80ab}}, // _ništ, tygs, rtho_, ক্তা,
+ {{0x26c1010c,0xdb1c016a,0xd37880c3,0x2249011b}}, // stho_, beró, _saća_, moak_,
+ {{0xda78826f,0xb87b0118,0x7982826b,0x5fc603eb}}, // _buď_, _asíg, _afow, र्दल,
+ {{0x9f5c87f1,0x60cf09c4,0x9f580c1d,0x7a3e8ec3}}, // _boví_, _hpcm, _torá_, zīte,
+ {{0xe9d9a341,0x7c28c22a,0xb5fd817f,0x22490102}}, // уки_, _emdr, _pišu, noak_,
+ {{0x6e29b86c,0x600de600,0x2bc4816f,0x7bc70267}}, // _imeb, _húmi, ल्हा, kfju,
+ {{0x4394b160,0x764d2bd7,0xaffe80ff,0x2d9db328}}, // _маус, _akay, _phướ, lawe_,
+ {{0x224900ad,0xb5fb0065,0x66040364,0x2eb68aec}}, // koak_, rmán, _poik, _अमृत,
+ {{0xf4090451,0x645e020f,0x6b9c003d,0xa01b18b6}}, // _אפ_, _shpi, warg, _stöl,
+ {{0x308580f7,0x66040198,0x443a008e,0x6e298135}}, // _التف, _voik, _pjp_, _mmeb,
+ {{0xf1b98da8,0xaffe801c,0x2d9d80b4,0x16040072}}, // rišu_, _thướ, hawe_, _लवकर_,
+ {{0xfc3f01df,0x2d9de0d6,0x6b9c6601,0x22491fa4}}, // tuír_, kawe_, rarg, foak_,
+ {{0x3f98105a,0xf1b98042,0x6b9c16b5,0x22490102}}, // _seru_, pišu_, sarg, goak_,
+ {{0x51560991,0x1b2000ab,0x6b9c6602,0xfc3f03a8}}, // [6720] ству, _মুখে_, parg, ruír_,
+ {{0x3cfb04de,0x6e29e603,0x3a2d80ff,0xfc3f03a8}}, // _עלינ, _ameb, hiep_, suír_,
+ {{0x3f984e30,0x3879026c,0x6d5bad2a,0xe9ff80ff}}, // _veru_, _eusr_, _azua, _ngắm_,
+ {{0x7c2e3e45,0xe5a38adb,0xceb28158,0xb05b2603}}, // libr, зици, ליב_, _spät,
+ {{0xdd998038,0x69d60214,0x6e3b8390,0xa5bb0825}}, // _deň_, meye, _djub, miót,
+ {{0x69cb8352,0x69d60214,0x61e46604,0x6e3baf00}}, // _abge, leye, ldil, _ejub,
+ {{0x8ae6804a,0x81e18264,0xb05b062c,0x8cda066f}}, // сілл, দান_, _epäs, _पैसो,
+ {{0x61e44f89,0x69d66605,0x8f370833,0xb5fb01fa}}, // ndil, neye, _האלו_, gmál,
+ {{0xdb0502af,0xd90e815b,0x7c2e008e,0x79998197}}, // _schü, عیت_, kibr, _deww,
+ {{0x4734a4c8,0x765f0168,0x22490102,0xa2088380}}, // жнос, _shqy, zoak_, ılış_,
+ {{0x661d00f1,0x60c40406,0x601683a7,0x644ae606}}, // ërko, htim, _câma, mofi,
+ {{0x442e8850,0x0f570039,0x63a8808e,0x6b9ae607}}, // lif_, מיים_, _hddn, _hetg,
+ {{0x60c400f1,0x61e46608,0x7bd56609,0xdb05010c}}, // jtim, ddil, tezu, _idhé,
+ {{0x4adc9d01,0xdb0500f1,0x9f5e80f1,0x4e93803d}}, // _मनाव, _udhë, njtë_, _کشور,
+ {{0x3f9e8778,0x1dc8e60a,0x60c40110,0x6b9ae60b}}, // matu_, रभात, etim, _metg,
+ {{0x53340991,0x3f9e811e,0x60c40168,0x644ae60c}}, // чест, latu_, ftim, hofi,
+ {{0x60c4660d,0x62532f69,0x22490102,0x61e294f2}}, // [6730] gtim, nçoi, roak_, _maol,
+ {{0x3f9ee60e,0x24890748,0x61e2e60f,0x22490102}}, // natu_, _itam_, _laol, soak_,
+ {{0x7ac71285,0xa3e21a3b,0x442ec818,0x69d62b5f}}, // _успе, _नोट_, dif_, beye,
+ {{0x3f9ea03c,0xdebb83c8,0x6aa1007b,0x3f8101e4}}, // hatu_, רמאל, álfu, schu_,
+ {{0x3f9e811e,0xcc340065,0x644a84b9,0x789302e3}}, // katu_, _ذریع, fofi, _ریاض,
+ {{0x2bc4800d,0xd7fb00a9,0xdb0500e7,0x644a8428}}, // ल्ला, _сум_, _adhé, gofi,
+ {{0x61e2e610,0xdc380158,0xb4d61e0b,0x3f9e811e}}, // _baol, זאגט_, _सन्_, datu_,
+ {{0x20c28125,0x6e298c5a,0x61e28c41,0x80a9b750}}, // fði_, _umeb, _caol, авив_,
+ {{0x20c28125,0xb5fb0375,0x644aaf70,0x248901c0}}, // gði_, rmál, bofi, _ntam_,
+ {{0xbebc8029,0xd82580e8,0x3f9ed522,0x764babf0}}, // dzīb, ідни, gatu_, logy,
+ {{0x61e28f35,0x09c380c8,0xee3a0468,0x02d987d2}}, // _faol, ্যবা, ино_, _خواب_,
+ {{0x92b4804e,0x61e281a8,0x69c912c6,0x3cede611}}, // _رحما, _gaol, _हॉली, _krev_,
+ {{0x8fa5975e,0x95561b9a,0x3f9ee612,0xdb0a8106}}, // _дале, تخدا, batu_, uffö,
+ {{0xf1bf0e1b,0xdb1c007b,0x3f9ee613,0x69c9844e}}, // _acá_, ferð, catu_, lfee,
+ {{0xdb1c0125,0xcb6a5488,0x69d60214,0xe7e29a87}}, // gerð, раве_, teye, _कोटा_,
+ {{0x60c415a6,0x661d0865,0x20078102,0xe9da0dc7}}, // ttim, chsk, _honi_, јка_,
+ {{0x20078e80,0xd910026a,0xe8fa0721,0x69d66614}}, // [6740] _koni_, ریس_, шла_, reye,
+ {{0x2f5600ae,0xe29a0b30,0x998901ac,0x7afa0106}}, // отес, _као_, vnať_, ätts,
+ {{0x764bade8,0x20078198,0x69d603ec,0x25bf8362}}, // fogy, _moni_, peye, _bcul_,
+ {{0x2007b649,0x60c46615,0x3ced8370,0x3f9edbe2}}, // _loni_, ptim, _brev_, zatu_,
+ {{0x973c8024,0x64a3974a,0x16b6825e,0x68e9d6a6}}, // maće, дача, _अम्ब, jved,
+ {{0x973c8024,0x70d900be,0xdb1c007b,0xb87b00e1}}, // laće, _פֿרו, rfræ, _prív,
+ {{0x644ae616,0x61e2e617,0x69c9d778,0x6b9a9c11}}, // rofi, _paol, ffee, _vetg,
+ {{0x30a69631,0x9f5c8efc,0x3f9ee618,0x6b9a8cfa}}, // оров, _nová_, watu_, _wetg,
+ {{0x3f9ee619,0xdcf88065,0x644a804f,0xd7fab93f}}, // tatu_, _دہشت_, pofi, аун_,
+ {{0x20c287ca,0x2018bfd9,0xaaab0424,0x973c8bda}}, // rði_, óri_, _छिलक, haće,
+ {{0x3f9ee61a,0x09ac80c8,0x8f7b012a,0xdb188006}}, // ratu_, ক্ষা, יניק, tevõ,
+ {{0x3f9ee61b,0x7e7c3140,0xdb1c007b,0x973c8b67}}, // satu_, _murp, verð, jaće,
+ {{0x3f9eaf39,0x9ccb0196,0x973c8088,0x91368019}}, // patu_, шына_, daće, _براؤ,
+ {{0x224982a5,0x68fb0722,0x3ceb852a,0x7bd882f1}}, // čake_, _àudi, चिते_, nevu,
+ {{0x442c8915,0x799f0d92,0x5fb78039,0x764bb7c0}}, // _mmd_, taqw, _והוא_, yogy,
+ {{0x20078668,0x25a90102,0x973c812b,0xe1ff002a}}, // _zoni_, _udal_, gaće, _riós_,
+ {{0x7afa016d,0xa01b016d,0x64400018,0x442c8176}}, // [6750] ättr, _utök, émic, _omd_,
+ {{0x6aa10125,0x478b0fbf,0x2007008b,0x7bd881d0}}, // álfs, рсам_, ðni_, jevu,
+ {{0x44310459,0x7bd8e2f1,0x80a703eb,0xb87b0580}}, // miz_, devu, _चिके, _arít,
+ {{0x3f9ca46f,0xb87b026b,0x3cfd822c,0x27e30326}}, // _levu_, _brít, txwv_, _wajn_,
+ {{0x443eb204,0xb5fd8115,0xe0df0037,0xb4c902f1}}, // _bjt_, _dišp, _usò_, _ईहो_,
+ {{0x443136d4,0x7bd8e61c,0x68e9ad5e,0x69c98299}}, // niz_, gevu, vved, wfee,
+ {{0x4ea41ddf,0x659581e2,0x60d9007e,0x7e7c0cdb}}, // _орта, _наву, şamı, _gurp,
+ {{0x2007b1ca,0xdce8809a,0x44310102,0xb87b008b}}, // _soni_, _śląs, hiz_, _frít,
+ {{0x4431661d,0x69c9b02e,0xe8f68fe6,0xb87b0032}}, // kiz_, rfee, злы_, _irís,
+ {{0x87259056,0x6e2d0dbb,0x799d2e4d,0x660980c9}}, // змож, _amab, _lesw, _hoek,
+ {{0x6c6a8117,0x661b83cf,0x4431661e,0x660998c7}}, // _اللہ_, _kluk, diz_, _koek,
+ {{0x82378019,0x6609c44d,0x2a6901c0,0x3ebe004a}}, // _برطا, _joek, jmab_, _åtte_,
+ {{0x317a810f,0x973c8024,0x44312d8d,0xa3df816f}}, // _האנד, vaće, fiz_, _तसा_,
+ {{0x201eb119,0x44311c0a,0x6609a6a6,0x6e2d3bb8}}, // chti_, giz_, _loek, _emab,
+ {{0x661be61f,0x6b630103,0xdce603bf,0xa3cd8c9a}}, // _oluk, екра, rakı, ष्य_,
+ {{0x3ce00282,0x78a416e1,0x661b838a,0x15460225}}, // _tsiv_, tsiv, _nluk, _нейм,
+ {{0x973c8052,0x799d01ec,0x7e7c22c3,0xfc3f11a9}}, // [6760] raće, _desw, _surp, grís_,
+ {{0xa3cd83b7,0xddc9b2b2,0x600d8ece,0x6d5604b9}}, // ष्म_, _rieš, _túmu, _iyya,
+ {{0x660990f4,0x224dcaad,0x973c812b,0x442c88f9}}, // _boek, koek_, paće, _rmd_,
+ {{0xddc9b2c8,0x25a20748,0x644e0f2a,0xb87b6620}}, // _pieš, nakl_, lobi, _prít,
+ {{0x20170065,0x01c380ab,0xd90d2181,0x224d8769}}, // áció_, ্যাদ, _پیل_, doek_,
+ {{0x7e7c6621,0xddc981e2,0x61e60214,0x661b8074}}, // _turp, _vieš, _hakl, _eluk,
+ {{0x60120789,0x61e665f6,0x661b8d23,0x290003a8}}, // _næmi, _kakl, _fluk, lxia_,
+ {{0xddc98029,0x2d8081ac,0x44316622,0x644e03df}}, // _tieš, žieb_, ziz_, hobi,
+ {{0xdfd08307,0x76438065,0x61e66623,0x443153fb}}, // نية_, énye, _makl, yiz_,
+ {{0x66098586,0x251b810f,0x644e012b,0xe1ff001c}}, // _zoek, _הומא, jobi, _khóc_,
+ {{0x224dc0f1,0x644e239f,0x6d560300,0x7e760122}}, // boek_, dobi, _ayya, _biyp,
+ {{0xa3cd8b3b,0xcb13893f,0xf27b8051,0x8afb8039}}, // ष्ठ_, _אלע_, _הראש, _להבי,
+ {{0x44316624,0xa9c58077,0x2ca6820d,0x63bc8106}}, // tiz_, _آزمو, nsod_, _örns,
+ {{0x9a871a02,0x68e2b129,0x7e6416fb,0xb3472359}}, // зуал, _isod, _ehip, _naçõ,
+ {{0x44314d6e,0x25a201f4,0xdb1c03e6,0x268a81a8}}, // riz_, bakl_, ngrè, _اختي_,
+ {{0x44310182,0x6e2d5deb,0x61e60140,0x7f3b83de}}, // siz_, _umab, _cakl, _לעוו,
+ {{0x61e6003a,0x200a6625,0x443101ca,0xb95b0032}}, // [6770] _dakl, _bobi_, piz_, _abìr,
+ {{0x224d8a0f,0x6609e626,0xdb1a9727,0x200a4e28}}, // zoek_, _soek, _actí, _cobi_,
+ {{0xb87b3404,0x2ca90ae1,0x3ea70065,0x200a3733}}, // _prís, _hvad_, ént_, _dobi_,
+ {{0x3a203962,0x61e600b9,0x5d358019,0x9f4a041c}}, // chip_, _gakl, وفائ, _robô_,
+ {{0x629ae451,0xdb1c016d,0xe8eb01a8,0x200a033a}}, // _awto, rfrå, _ارجو_, _fobi_,
+ {{0x61e607dd,0xa3cd0beb,0xdb1c0711,0x76438061}}, // _zakl, रभि_, sfrå, ényb,
+ {{0x66098b3c,0x61e60214,0x6281026f,0x68e2e627}}, // _toek, _yakl, íloh, _asod,
+ {{0x601f80e7,0xf99f0242,0x6e3d0bfd,0x201c862c}}, // _même, _alèz_, jnsb, _alvi_,
+ {{0x68e28019,0x6820016d,0x224de628,0x25ade629}}, // _csod, _född, roek_, _idel_,
+ {{0x44318201,0x6e3d1c33,0x224de62a,0xdb050118}}, // _öz_, ensb, soek_, _adhí,
+ {{0x63a3a055,0x644e10d3,0xa3cd83dd,0x1f6580e8}}, // mann, vobi, ष्ण_, ьком,
+ {{0xd12e9125,0xcb128051,0x320b0035,0xf99f0176}}, // امی_, _שלא_, _nocy_, _elèz_,
+ {{0xfd59846d,0x61e9e62b,0x63a38037,0x00000000}}, // _agbẹ, mdel, oann, --,
+ {{0xb4ce0df4,0x61e98370,0x61e6662c,0xddcd0087}}, // _रही_, ldel, _sakl, _piaţ,
+ {{0x60c9aa33,0x69cd484a,0x61e602d7,0x63a38359}}, // ltem, ffae, _pakl, iann,
+ {{0x27e787ca,0x2d9f8352,0x644e662d,0xddcd002e}}, // _hann_, _neue_, sobi, _viaţ,
+ {{0x27e7834a,0x63a39203,0x61e994d1,0x600a0fbf}}, // [6780] _kann_, kann, idel, онам_,
+ {{0x60c98ca5,0xb87b01ac,0x63a3e62e,0x61e98009}}, // item, _prír, jann, hdel,
+ {{0x63a3e62f,0xdb050352,0x7659831d,0x27e791e6}}, // dann, _schö, flwy, _mann_,
+ {{0x61e9e630,0x6b670019,0x60c986c0,0xa3c80b6f}}, // jdel, ségé, ktem, ोजक_,
+ {{0x61e98422,0x3cfa8063,0x200a6631,0x63a3e632}}, // ddel, ्हें_, _tobi_, fann,
+ {{0x7bdc07d5,0xb5fb00f7,0x61e99254,0xb4de8074}}, // neru, rmái, edel, _तनी_,
+ {{0x59e20c28,0x61e9d6bc,0x2a7e9a7b,0x25a000e7}}, // _पसार, fdel, _qutb_, _oeil_,
+ {{0xe1ff0028,0x7bdc6633,0x31620282,0x63a3d62f}}, // _khóa_, heru, _kzkz_, aann,
+ {{0x63a3e634,0x27e7e635,0x301484fa,0xada38196}}, // bann, _bann_, едпр, _шатл,
+ {{0x2249803b,0x63a3a305,0x68204323,0x7bdc1fb6}}, // čaka_, cann, _föde, jeru,
+ {{0x27e7834a,0x7bdc1655,0x741380d5,0x80bd80c8}}, // _dann_, deru, _جولا, ্টগ্,
+ {{0xd378803a,0xa01b016d,0x200581c0,0x25a04a30}}, // _naći_, _utöv, ajli_, _ceil_,
+ {{0x60c982be,0xadc380ff,0xb87b41e3,0x27e78e20}}, // ctem, _hoản, _asín, _fann_,
+ {{0x201980f1,0xe72f803d,0xe6300085,0x27e7e636}}, // ësit_, اصی_, _təşə, _gann_,
+ {{0x6adcd59b,0x450700c8,0x81e180ab,0x2a668282}}, // _मनोर, _লেখক_, দার_, _khob_,
+ {{0x63a3e637,0x69c09c77,0x25a05778,0x68ed8140}}, // zann, ngme, _geil_, _šadi,
+ {{0x7bdc148e,0xee3f003e,0x30bc80c8,0x48bc80ab}}, // [6790] beru, mný_, _অনুস, _অনুর,
+ {{0xee3f003e,0x61e99c3d,0x7bdc0efa,0x9c380198}}, // lný_, zdel, ceru, _опыт_,
+ {{0x61e9e638,0x68ed42a9,0xb2751662,0x53350012}}, // ydel, svad, _плош, _чент,
+ {{0x7ae508cf,0xee3f003e,0x63a3985b,0x61e98144}}, // _isht, nný_, wann, xdel,
+ {{0x63a3e639,0x2ca98013,0x682000f2,0x24f98a41}}, // tann, éad_, _söde, янды_,
+ {{0xc7ce8104,0x6b65945a,0x660d08f2,0x200502f1}}, // _sống_, нкла, _loak, _õli_,
+ {{0xe7e585b3,0x27e7e63a,0xee3f026f,0x7bc500b8}}, // _कसबा_, _rann_, kný_, _mchu,
+ {{0x63a3e639,0x60c985a7,0x2a66822c,0x27e7c456}}, // sann, ttem, _chob_, _sann_,
+ {{0xb4ce1499,0xee3f0775,0x61e987b3,0x2919ca89}}, // _रहे_, dný_, rdel, ása_,
+ {{0x60c98625,0x2480663b,0xddc981ac,0x7bc5663c}}, // rtem, _ruim_, _dieť, _nchu,
+ {{0x69d00076,0x98ab87d9,0x27e7a253,0x24800090}}, // ड्डी, ğlık_, _vann_, _suim_,
+ {{0xfbc90051,0x27e781ec,0xc7ce8028,0x7bdc534f}}, // _לת_, _wann_, _uống_, weru,
+ {{0x9f4788cf,0x27e786c0,0x7bdc663d,0x973c8052}}, // _kanë_, _tann_, teru, raća,
+ {{0x9f4788cf,0x25a00352,0x6281dbc5,0x33d5035f}}, // _janë_, _weil_, _kulo, кіст,
+ {{0x78a9809f,0xee3f0efc,0x25a0663e,0x7bdc663f}}, // lsev, bný_, _teil_, reru,
+ {{0xb87b0038,0x62818365,0x798980a4,0x91e33760}}, // _príp, _mulo, ncew, лосе,
+ {{0x7ae508cf,0x3946231e,0x6d410459,0x6e3b0039}}, // [67a0] _esht, ços_, şlar, _מתקד,
+ {{0x57e744e0,0x78a9e640,0x6564132e,0x7bdc03ed}}, // ндум_, isev, _izih, qeru,
+ {{0x28ac0aed,0x6e228234,0x44446641,0x2cbd81ed}}, // _चिकि, thob, _ij_, euwd_,
+ {{0x25a08307,0xa01b016d,0x2a790748,0xb87b526f}}, // úil_, _stöt, _gisb_, _tríp,
+ {{0x22406642,0x98b880eb,0xb97b0039,0xb608816b}}, // mnik_, ārī_, _מרכז, _nešť,
+ {{0x2240371a,0x6e228578,0x6281b722,0xee3f016b}}, // lnik_, shob, _bulo, zný_,
+ {{0x2bd223e6,0x6281a189,0x6e2280f7,0x63a1a704}}, // द्या, _culo, phob, _deln,
+ {{0xfce694d6,0x6281820d,0x63a18074,0x58d48e97}}, // _побо, _dulo, _eeln, воит,
+ {{0xee3f0efc,0xc05a81e2,0xdd8f8bbe,0xd7f080f7}}, // vný_, зін_, _طول_, لكة_,
+ {{0x764404b9,0x22403430,0x62818326,0x38678362}}, // _ajiy, hnik_, _fulo, _chnr_,
+ {{0xee3f003e,0xe297a597,0xdd950048,0x659529d7}}, // tný_, _зах_, каны, кану,
+ {{0x444433e6,0x7bc53243,0x39469d67,0x5fc70072}}, // _aj_, _schu, _txos_, _लाभल,
+ {{0xee3f027f,0x60168187,0xba2981a8,0x444400ed}}, // rný_, _lâmi, _تسلم_, _bj_,
+ {{0xee3f026f,0x7a2302f1,0x65640140,0x9be42133}}, // sný_, _mõte, _dzih, ліск,
+ {{0xee3f026f,0x2456007b,0x973c8289,0x81c500ab}}, // pný_, væmt_, maćo, _এসব_,
+ {{0x444403a6,0xb5fb26d5,0xa75b81c6,0x9b58004a}}, // _ej_, gmát, _מדור, хист_,
+ {{0x200ee643,0x9f5c81ac,0x26d8100a,0x3ebe804a}}, // [67b0] _kofi_, _novú_, _apro_, lutt_,
+ {{0xb9090d86,0x7bc5022e,0x29070067,0x66199d08}}, // _मन_, _uchu, ćna_, wkwk,
+ {{0xf3ff8142,0x7ae5020f,0x22400353,0xdb0380f1}}, // _đã_, _usht, bnik_, ranë,
+ {{0x63a71024,0x2b0e001b,0x6281e644,0xee38804a}}, // lajn, ठहरु_, _rulo, ннє_,
+ {{0xfce594d6,0x6281c7c7,0x61fd6645,0x63a196b5}}, // толо, _sulo, _insl, _peln,
+ {{0xe3b384c0,0x6281d24a,0x29360158,0xd7208054}}, // ارش_, _pulo, _יארן_, मनाथ_,
+ {{0x81ea8a49,0x44335056,0x4421008e,0x60cd5f05}}, // মান_, _mmx_, _mlh_, ltam,
+ {{0x78a98406,0x32090e35,0x63a18299,0xdb1c226d}}, // tsev, njay_, _weln, rfrø,
+ {{0xe3b39381,0x9f43001b,0x5c758a2e,0x60cd05d8}}, // _مرض_, _mají_, _алат, ntam,
+ {{0x6281e646,0x224004c4,0x78a98508,0x60cd1243}}, // _tulo, znik_, rsev, itam,
+ {{0x63a70165,0x444403a7,0xb5fb001b,0x61ed44cf}}, // dajn, _rj_, klád, kdal,
+ {{0x60cd6647,0x4421054e,0x61ed04a6,0x9f47e648}}, // ktam, _alh_, jdal, _jané_,
+ {{0x61ed6649,0x442107d5,0xd3788b67,0xdb0384e8}}, // ddal, _blh_, _daću_, dané,
+ {{0x320f8986,0x22400063,0x63a70805,0x212900dd}}, // _hogy_, wnik_, gajn, dzah_,
+ {{0xe80505e8,0x03a31429,0xfe460087,0x44331867}}, // राना_, рисо, _инко, _dmx_,
+ {{0xc24312a0,0x61ed0ec9,0x61eb9d49,0xdb038661}}, // анск, gdal, _jagl, gané,
+ {{0x444402a5,0x61ebe64a,0x6d5b81e9,0x60cd13d2}}, // [67c0] _tj_, _magl, _nyua, gtam,
+ {{0xa01b23d8,0x2240664b,0xb5fb26d5,0x224d012b}}, // _stör, snik_, smát, čeka_,
+ {{0x36890071,0x22400988,0x3d65053d,0x6fd38035}}, // есін_, pnik_, _لطیف, ब्यू,
+ {{0x4438571a,0x61eb977c,0x7c21ab0d,0x6820016d}}, // lir_, _nagl, _allr, _döda,
+ {{0x60cd05dc,0x44380013,0x7e7b885c,0x9f47816b}}, // ctam, oir_, _diup, _dané_,
+ {{0x4438664c,0xa7da03f8,0x69c43ad2,0x93bc8162}}, // nir_, _توسط_, lgie, _slăb,
+ {{0x02a38a41,0xaa58baa3,0x61ebe64d,0x62349a1a}}, // арым, нику_, _bagl, леку,
+ {{0xe3b2803d,0x261403a7,0x61ebc52c,0xdbdf808b}}, // _چرا_, _mãos_, _cagl, síðu,
+ {{0xfce3825d,0x4438664e,0x61ebe64f,0x48c280c8}}, // _кото, kir_, _dagl, ্ট্র,
+ {{0xd7f79006,0x61ed6650,0xb5fb0019,0x44385625}}, // вую_, zdal, ymás, jir_,
+ {{0x44386651,0x81d78a49,0x61ed0182,0x61eb95f8}}, // dir_, ায়_, ydal, _fagl,
+ {{0x7c38a1bf,0x442100dd,0x61ebbe3d,0x200e80fc}}, // livr, _slh_, _gagl, _wofi_,
+ {{0xdfd48071,0x67d48bda,0xdb03826f,0x9f4701ac}}, // _болы, _болу, vané, ľné_,
+ {{0x9f470a56,0x44380357,0x61e08214,0xdfd081a8}}, // žné_, gir_, leml, _طيب_,
+ {{0x2bd203eb,0xd8389024,0xfbd201d0,0x23d2097d}}, // द्धा, mič_, द्धम, द्धद,
+ {{0x60cd2f47,0x69c46652,0x4438125b,0x63a70b80}}, // ttam, ggie, air_, sajn,
+ {{0x44386653,0x60c0a09b,0xe7cf8bb8,0x61ed6654}}, // [67d0] bir_, numm, त्वप, rdal,
+ {{0x4438040e,0xb5fb03a2,0x2bd20bc2,0x661d0cab}}, // cir_, slád, द्दा, lksk,
+ {{0x60cd2b1e,0x64556655,0xd371003d,0x12bd80ab}}, // stam, mozi, _دهد_, _আন্দ,
+ {{0x60cd002e,0x60c0e02b,0x69df0234,0x661d074d}}, // ptam, kumm, qeqe, nksk,
+ {{0xe8df0104,0x60c0e656,0x91ba0039,0xa8a400a9}}, // _thực_, jumm, _אמרי, _врск,
+ {{0x61ebc6bc,0xcb6992bc,0xbfb80077,0x60c0e657}}, // _sagl, нале_, _لطفا_, dumm,
+ {{0x61eb84d2,0x7e7b9600,0x7bc3812b,0x2484808e}}, // _pagl, _tiup, rgnu, _eumm_,
+ {{0x44386658,0x62856659,0x93bc8087,0x3eda00be}}, // zir_, _muho, _plăc, _אַמא,
+ {{0x44380201,0x6012013c,0x64553327,0xe1ff0028}}, // yir_, _kæmp, kozi, _nhóm_,
+ {{0x4438665a,0x63a501ac,0xdb018b4c,0x61eba79e}}, // xir_, _nehn, _lelé, _wagl,
+ {{0xe5a60e8e,0x61ebb645,0x35a63134,0x6da61878}}, // лини, _tagl, ланг, лина,
+ {{0x4438665b,0xdb018036,0xe8f581a8,0x60c0e65c}}, // wir_, _relè, _مستخ, bumm,
+ {{0x443830aa,0x518481e5,0x78ad0b67,0xd838a3f9}}, // tir_, _туха, jsav, bič_,
+ {{0x6455665d,0x6285242d,0xd91a80be,0x201100ce}}, // gozi, _buho, _רוסל, _nozi_,
+ {{0x4438665e,0x6443b2be,0xdb018019,0xe6d20424}}, // rir_, inni, _belé, _सहेज,
+ {{0x44384e07,0xe7cf835a,0x69c40029,0xdb019e1c}}, // sir_, त्रप, ugie, _celé,
+ {{0x6e242735,0x9f5c86c4,0x64553997,0x78ad2ca0}}, // [67e0] _ilib, _anvè_, bozi, gsav,
+ {{0xdb01e65f,0x225f9ad4,0x69c40d35,0x2484852a}}, // _telè, hluk_, sgie, _rumm_,
+ {{0x225fe660,0x20116661,0x09b000ab,0xdb018019}}, // kluk_, _dozi_, ঙ্গা, _felé,
+ {{0xe1ff00f7,0x6443809a,0x175680be,0x52d700be}}, // _gnó_, enni, אסער_, _גוטע_,
+ {{0x7a3e81a9,0x7642e662,0x649a8878,0x9f5807b6}}, // nūte, rnoy, хтер_, _poró_,
+ {{0x6e2407e2,0x26c1515b,0x2a606663,0x998de664}}, // _llib, buho_, llib_, vieš_,
+ {{0xd838e665,0x81e700ab,0x6e246666,0xa5bb0c83}}, // vič_, পার_, _olib, rióz,
+ {{0x611a00eb,0x64438717,0x521481cf,0x7afa0198}}, // _tālā, anni, рдит, ätty,
+ {{0x61e0895e,0x02e1101c,0x463b8158,0x68ed826c}}, // reml, _पन्न, _נעמע, _šadr,
+ {{0x6e240b3f,0x60c0afff,0x6a65a3ca,0x61e0b327}}, // _alib, rumm, rófa, seml,
+ {{0x60c092a5,0xb33c84b7,0x645ae667,0xd838bc35}}, // summ, _ieħo, _ikti, rič_,
+ {{0xd10402a8,0x10a682df,0x98a680a9,0x6e2400f7}}, // रमाण_, _бидн, _биде, _clib,
+ {{0x62850988,0x661d2a33,0x81ea8264,0xd838877b}}, // _suho, rksk, মাদ_, pič_,
+ {{0x6e245076,0x6285050b,0xa2ab8072,0x6298890d}}, // _elib, _puho, ींच्, opvo,
+ {{0x443f0025,0x501b84de,0xfe70053d,0x656f9e1e}}, // đu_, _אוגו, تدی_, _úchv,
+ {{0x3ce901c5,0x973c8754,0x78ad6668,0x64556669}}, // _tsav_, haćk, tsav, sozi,
+ {{0xbf1d03b7,0x645511ec,0x6443e66a,0x2011666b}}, // [67f0] पन्न_, pozi, ynni, _pozi_,
+ {{0x201ea8d1,0x6285666c,0x27ee808b,0x225fe66d}}, // nkti_, _tuho, _jafn_, zluk_,
+ {{0xdb01840e,0x78ad4e2a,0xb87b0298,0xd6db0e11}}, // _telé, ssav, _kríz, эта_,
+ {{0x7afa013c,0x6448af9f,0xeef784de,0x51840088}}, // ætte, _ajdi, טמבר_, _гура,
+ {{0xfaf88db5,0x2bd20bb3,0x225f80d2,0x7e6d1f4d}}, // _arī_, द्वा, vluk_, _mhap,
+ {{0x7bc7008b,0x27ee807b,0x3dc981b9,0x6443862c}}, // ngju, _nafn_, żaw_, unni,
+ {{0xdb1c12ca,0xa3498098,0x1b498098,0x44fd0264}}, // ográ, ъзка_, ъзки_, ুনিক_,
+ {{0xe1ff8333,0x4b3801c6,0x61ef10ba,0xdb1c0537}}, // ñó_, טרול_, _jacl, ngrá,
+ {{0x61ef666e,0x2d9da578,0xdb038333,0xd9458c40}}, // _macl, mbwe_, maní, репи,
+ {{0xaaae816f,0x7e6d0135,0x66db8009,0x1bab8019}}, // _टिचक, _ahap, _näkö, _سائٹ_,
+ {{0x43848013,0xbb8480f7,0xb87b39f0,0x4e198d86}}, // _المق, _المي, _epíg, _दवाई_,
+ {{0x7e6d24f0,0x1c038beb,0x6a658333,0xdddc9c34}}, // _chap, लावल_, mófo, _kurš,
+ {{0xe0c000c8,0x59678081,0x7e6d003d,0x98b88162}}, // _উন্ন, _съжа, _dhap, ără_,
+ {{0x7bc7008b,0xe7878c0e,0x6d5f0168,0x557683de}}, // ggju, _судо, _dyqa, יערן_,
+ {{0xfaa60396,0x765ba18e,0x4425e66f,0x26c7023e}}, // рабо, _okuy, _mll_, ànol_,
+ {{0xc48605c2,0xceb8809a,0x2fd800b9,0x61ef0362}}, // _клик, rzęt_, _sbrg_, _cacl,
+
+ {{0xfce60e6b,0xddc980eb,0x2d8080e1,0xdb03bb81}}, // [6800] _топо, _biež, žiek_, daní,
+ {{0x442581c0,0x7a230074,0xb5fb016b,0x2c7c0032}}, // _nll_, _võta, plác, _dádì_,
+ {{0x0d8682a9,0x3bbb0039,0x61ef0014,0x69d982ec}}, // ален, _במיד, _facl, _obwe,
+ {{0x4425804c,0x9f5c83cb,0x3a290a0f,0x2bd201cb}}, // _all_, _nový_, chap_, द्रा,
+ {{0x61e46670,0x9d2480ab,0xdb0a948c,0x261787b6}}, // meil, বপ্ন_, ngfö, _aços_,
+ {{0x18a30972,0x7c3c00dd,0x65698267,0x69d981ec}}, // _фарм, nirr, _dzeh, _abwe,
+ {{0x7f430765,0x442580dd,0xdb03c1bf,0x7aed8035}}, // _держ, _dll_, baní, łatn,
+ {{0x4425809f,0x78a28247,0xb95b026b,0x61e40009}}, // _ell_, _pwov, _abìy, neil,
+ {{0x973c803b,0x2d8482c4,0x9f47816b,0x2aac826b}}, // maći, _igme_, _daní_, _bóbá_,
+ {{0x61e46671,0x92d580c8,0x201e989e,0x973c811f}}, // heil, িয়ে_, ukti_, laći,
+ {{0x61e40009,0x442a00f7,0x7bda81c0,0x09e695e0}}, // keil, dhb_, _ibtu, _воен,
+ {{0x443ca069,0x7e62a6a3,0xd5e4027d,0xdb18d8e5}}, // liv_, nlop, _phí, rgvä,
+ {{0xb8948307,0x40948307,0x61e46672,0x60c41148}}, // _التع, _التر, deil, juim,
+ {{0x645881dd,0x6288e673,0xbebc8029,0xb4d71199}}, // novi, _kudo, dzīv, _सही_,
+ {{0xf1a70be2,0x63a893cf,0xdb1a85e4,0x61e40114}}, // иран, _medn, _actú, feil,
+ {{0x7e6d28e1,0x443ce674,0x63a89602,0x6288e675}}, // _uhap, hiv_, _ledn, _mudo,
+ {{0x64588289,0x60c42c92,0x443cbda8,0x7bdabcd4}}, // [6810] kovi, guim, kiv_, _obtu,
+ {{0x9f47001b,0x6e3d06b1,0x6458ba06,0x7e6281ed}}, // žní_, lisb, jovi, elop,
+ {{0xa18707ac,0x6447003b,0x248901c5,0xcfe180ab}}, // _выгл, mnji, _huam_, বাচন,
+ {{0x24893f73,0xd2468a47,0xdb050019,0x068410ac}}, // _kuam_, _رن_, _nehé, ігін,
+ {{0x9ce78b87,0x7afa82d0,0x65608114,0x63a8e676}}, // рцел, _artt, _cymh, _bedn,
+ {{0x6288e677,0xdb03b88a,0xb4e78740,0x24890069}}, // _budo, saní, _बनी_, _muam_,
+ {{0x248901e9,0x6013001b,0x200304b9,0x99848084}}, // _luam_, _výmě, _inji_, jimų_,
+ {{0xa2d88076,0x09f980f7,0x765b8234,0xddc4007a}}, // _महर्, افظة_, _ukuy, _ohiš,
+ {{0x6458825b,0x7659809a,0x248901c0,0x2baa150e}}, // bovi, mowy, _nuam_, करमा,
+ {{0x64588a00,0x998d81ac,0x4425d743,0x6447011f}}, // covi, dieť_, _ull_, jnji,
+ {{0x628882a3,0xdb050019,0x62970069,0x26d3011b}}, // _gudo, _fehé, _ntxo, ntxo_,
+ {{0x81b180c8,0x08f9826a,0xddc9a358,0xb87b0118}}, // ঞ্জ_, _مرتب_, _nież, _eríx,
+ {{0x61e46678,0x248901c5,0xee370c5c,0xd7c70424}}, // veil, _cuam_, сня_, _लांच,
+ {{0x61e402af,0x24890168,0x64470b80,0x68e99e26}}, // weil, _duam_, gnji, mwed,
+ {{0xff5391fb,0x81ea80c8,0x4e958bbe,0x61e46679}}, // _بخش_, মার_, _اشتر, teil,
+ {{0xbb431b93,0x645880d2,0x69c9cbcd,0x26c5e67a}}, // ческ, zovi, ngee, mulo_,
+ {{0xe8fa01bb,0x61e4667b,0x7e628247,0xf653007c}}, // [6820] ыла_, reil, vlop, _מצה_,
+ {{0x443c81e9,0x61e43673,0x60c45f95,0xf20986a7}}, // xiv_, seil, ruim, वाड़_,
+ {{0x63a8c8db,0x645880d2,0xd00f1ddd,0x443ce67c}}, // _redn, vovi, علم_, viv_,
+ {{0x63a8e67d,0x452aa2f3,0xa5bb00f7,0x601683a7}}, // _sedn, лжен_, thói, _lâmp,
+ {{0x27e59f0f,0x98a0009a,0x7e629412,0x973c9487}}, // keln_, dzić_, rlop, raći,
+ {{0x26c59f60,0x9cd70039,0x69c98079,0x68e9ce06}}, // kulo_, מונה_, egee, dwed,
+ {{0x443c8082,0x8bf200c8,0x63a88db7,0x6604667e}}, // riv_, ঞাপন_, _vedn, _onik,
+ {{0xae0c81fe,0x4c868dbd,0x443c96eb,0xe45f50de}}, // _सचिन_, _глав, siv_, ntör_,
+ {{0x6458803b,0x0f2081fe,0xdca39d99,0x63a88353}}, // povi, मन्स_, _нати, _tedn,
+ {{0x6288e67f,0x24890069,0x27e58b2f,0x66044505}}, // _tudo, _ruam_, geln_, _anik,
+ {{0x8104326f,0xe45f50de,0x24896680,0x66166681}}, // रमुख_, ktör_, _suam_, _boyk,
+ {{0x24890069,0xde590b73,0x99848110,0xe1ff00ff}}, // _puam_, рамі_, rimų_, _thói_,
+ {{0x6e3d2475,0x27e592d2,0x64470253,0xb8d3803d}}, // risb, beln_, tnji, _فتوش,
+ {{0x66046682,0x26c5e683,0x24890069,0x7d1d2127}}, // _enik, bulo_, _vuam_, ryss,
+ {{0x752f0d38,0x26c59f5c,0xa3cd8c78,0x629c2a66}}, // szcz, culo_, ष्ट_, gpro,
+ {{0x248901c5,0x6447007d,0x99990249,0x6e4400a9}}, // _tuam_, snji, ркат_, _нејз,
+ {{0x301512b2,0x63ae010b,0x7c3aa39e,0xb4e78fcc}}, // [6830] одар, babn, _omtr, _बने_,
+ {{0x656d4945,0x2cb4001b,0xdced00fe,0x2baa0072}}, // _izah, řadí_, _ugađ, करणा,
+ {{0xdb019f5c,0x7659809a,0xb87b1727,0x4f6b16dd}}, // _pelí, towy, _apíc, ушам_,
+ {{0xe3b88182,0x3e7b01e2,0x9f4a0122,0x68e98114}}, // _adı_, _būti_, _babâ_, ywed,
+ {{0xa509a659,0x63858251,0x7659809a,0x22490061}}, // рела_, огла, rowy, mnak_,
+ {{0x22490019,0x973c9487,0x27e585ec,0x2a6f808e}}, // lnak_, laću, xeln_, _shgb_,
+ {{0x8c1c0f60,0x6d4103bf,0xdb07001b,0x2bdb6684}}, // רווי, ğlay, kají, म्या,
+ {{0x6d411010,0x7c28a496,0x22490065,0x3ced81e9}}, // şlay, _eldr, nnak_, _tsev_,
+ {{0x69c9858f,0xb5fb02b7,0x22491124,0x26c5b1ba}}, // rgee, tlán, inak_, wulo_,
+ {{0x6e298904,0x764d1e8c,0x225d9142,0x68e98e8a}}, // _hleb, _ajay, _wkwk_, rwed,
+ {{0x22490065,0x6e298204,0x656d6685,0x9b58846e}}, // knak_, _kleb, _azah, _лифт_,
+ {{0x443a0e60,0xb5fb6686,0x63ae010b,0xceb203c8}}, // _smp_, slán, wabn, ויט_,
+ {{0x2249295b,0x63ae0239,0x2eee826c,0xf1bf026f}}, // dnak_, tabn, _esff_, _plán_,
+ {{0x26c5d24a,0xaadd8af3,0x656d0359,0x02dd8aed}}, // pulo_, _महाक, _dzah, _महाभ,
+ {{0x63ae252d,0x366a0fbb,0x6e3b82ec,0x656d01f6}}, // rabn, _како_, _omub, _ezah,
+ {{0x443a1b11,0x9fa3007b,0x3a3f8359,0x22490019}}, // _wmp_, _síðu_, niup_, gnak_,
+ {{0x1005000d,0x6440063e,0xe7aa0b5b,0x7bdc807b}}, // [6840] रांश_, émis, ивал_, _örug,
+ {{0x6e29b404,0x629c04b7,0x69cf0098,0x443a6687}}, // _aleb, ppro, _ecce, _ump_,
+ {{0x46ea1092,0x06ea0087,0x9f510176,0x1c0386ae}}, // рден_, рмек_, _jazè_, लाईल_,
+ {{0x05668193,0xcbc680ab,0x645c22c3,0xd49abc3d}}, // овен, _এসেছ, lori, арк_,
+ {{0xe8f7117c,0x6e229142,0x6ec08c28,0x9f4700e1}}, // _для_, rkob, _विभु, ľná_,
+ {{0x9f47003e,0x6e29bbb5,0x628300e7,0x7c83e688}}, // žná_, _eleb, _énor, муще,
+ {{0xa3dc0fce,0xb4c70424,0x60d60a14,0xdb03e689}}, // ण्य_, _उम्_, ltym, taná,
+ {{0x201800ad,0x645c260f,0xa2cb0576,0xb33c8372}}, // _hori_, hori, तंत्, _jeħi,
+ {{0xdb03b680,0xfdee0035,0x60d64bf4,0xdb0701d0}}, // raná, ज़नस_, ntym, vají,
+ {{0x22490019,0xd01a917e,0x60d60084,0x4734ae65}}, // znak_, рфи_, itym, знос,
+ {{0x645c361a,0x2018668a,0x224913f4,0xe297abd9}}, // dori, _mori_, ynak_, _дах_,
+ {{0x2018077f,0x46db83eb,0x629ae68b,0x65640314}}, // _lori_, _बहरह, _itto, _byih,
+ {{0x61f60428,0x65640a03,0x20a90054,0x938b047f}}, // ddyl, _cyih, _चौंध, исва_,
+ {{0x2018668c,0x9faf080a,0x7a230074,0x9f34a133}}, // _nori_, _işçi_, _võtm, _нелі,
+ {{0x22490019,0xb024801c,0x644a81a8,0x973c8267}}, // tnak_, _giườ, infi, taću,
+ {{0x786f8aa2,0x224002d5,0x2abb8e82,0xddeb00d7}}, // røve, giik_, ימלא, _ضربه_,
+ {{0x2018668d,0x765d1ad6,0x69dd2307,0x224912de}}, // [6850] _bori_, mosy, _vbse, rnak_,
+ {{0x629a8698,0x22491403,0xe6168353,0x68e2826c}}, // _otto, snak_, _čišč, _mpod,
+ {{0x98ac00eb,0x2018668e,0x2ca90326,0x199502a9}}, // ālās_, _dori_, _hwad_, _наня,
+ {{0xdb03d465,0x69cf047f,0x765d074d,0x2ca90326}}, // lanç, _ucce, nosy, _kwad_,
+ {{0x629acdb0,0xeb999300,0x64a5976e,0x2018047f}}, // _atto, бил_, чала, _fori_,
+ {{0x9f4301ac,0xdb0391dd,0x3e72026f,0x320682c4}}, // _majú_, nanç, máte_, _anoy_,
+ {{0x68e2957a,0x68ed599f,0x765d668f,0x3a3f90e1}}, // _apod, lwad, kosy, tiup_,
+ {{0x6e3b82a0,0x69cd6690,0x290d8102,0x68e28f35}}, // _umub, ngae, txea_, _bpod,
+ {{0xa3dc0bb8,0x645c4a96,0x629a9793,0x20186691}}, // ण्ड_, yori, _etto, _yori_,
+ {{0x25ad9aee,0xe8df0028,0x645c6692,0xb87b0061}}, // _heel_, _chức_, xori, _zsír,
+ {{0xf1cb04e5,0x961d8029,0xdb0383a7,0x25ad9316}}, // िलान, _ziņa, danç, _keel_,
+ {{0xfe72045b,0xfce6035f,0x765d1066,0x68ed6693}}, // عدد_, помо, gosy, kwad,
+ {{0x2bd2885d,0x645c0888,0xc1048013,0x25ad82a3}}, // _सामा, tori, _يومي, _meel_,
+ {{0xa09a9b9e,0x5fc7035a,0x3e72026f,0xa7fc8059}}, // _היסט, _लागल, dáte_, nlıl,
+ {{0x2007e694,0x2018130c,0x765d076d,0x645c00ef}}, // _inni_, _rori_, bosy, rori,
+ {{0x645c6695,0x1dc6035a,0x20186696,0x3e7b0029}}, // sori, _वाटत, _sori_, _būtu_,
+ {{0x20185c28,0x2edb9051,0xdd9982d6,0x22401f61}}, // [6860] _pori_, _बहुत, _enňk_, riik_,
+ {{0x645c0085,0xdb0383a7,0x61e9a805,0x60d66697}}, // qori, canç, heel, rtym,
+ {{0x60d60fe6,0x9f4a4c16,0x61e9e2f5,0xdb0e010c}}, // stym, _babá_, keel, kabé,
+ {{0xd6cf1a37,0x6820016d,0xdd8f99ea,0x25ad81b4}}, // طقه_, _nödv, هوم_, _ceel_,
+ {{0x25ad90f4,0x61e9838e,0xd5af8154,0x68ed2751}}, // _deel_, deel, افه_, cwad,
+ {{0x7bce6698,0x961d8029,0x02a6b45a,0x7bdc0087}}, // ngbu, _viņa, прим, nfru,
+ {{0x68e286b9,0x66e33408,0x628630a0,0x61e981bc}}, // _spod, _пора, _kiko, feel,
+ {{0x20078698,0xa9c3835f,0x8cc485fb,0x25adcb97}}, // _anni_, нськ, _रिपो, _geel_,
+ {{0x62866699,0x60c98333,0x1dc6016f,0x248590b6}}, // _miko, guem, _वाजत, _film_,
+ {{0x60c40013,0x2018807b,0x2cb7801b,0x6b669fa5}}, // irim, ðri_, ředí_, _екза,
+ {{0xfaa395e3,0x61e9b062,0x443e842b,0xe1ff00f7}}, // _захо, beel, _imt_, _mhór_,
+ {{0x60c4669a,0x61e9cd76,0x7c3e23ee,0x998206c4}}, // krim, ceel, _ampr, _ankō_,
+ {{0xe8df0028,0xdb0502ba,0x5c070ba7,0xdb0387d0}}, // _thức_, _vehí, пява, tanç,
+ {{0x3cef8105,0x68fb03a7,0x62860009,0x60c43ffd}}, // _इनसे_, _áudi, _aiko, drim,
+ {{0x7e8683bb,0xdb03855a,0x628646ed,0x60c40006}}, // íspě, ranç, _biko, erim,
+ {{0x6286669b,0x7d009581,0x3f9826ca,0xb87b026b}}, // _ciko, _orms, _afru_, _asíp,
+ {{0x62860455,0xa6ae80ab,0x60c4669c,0x442c8039}}, // [6870] _diko, _কমিট, grim, _old_,
+ {{0x44ef58dc,0xd7f8802e,0x68ed669d,0xb87b4e86}}, // _iż_, ndă_, rwad, _epín,
+ {{0x6e2d3916,0x6a65a6d5,0x6286669e,0x9f45801b}}, // _klab, sófi, _fiko, telé_,
+ {{0x1dad0e18,0x6286669f,0x3f8a0135,0xd77412c8}}, // चरित, _giko, _egbu_, رالع,
+ {{0x25ad80bd,0x248d80a9,0x442cbb07,0x61e99e7e}}, // _veel_, _quem_, _bld_, veel,
+ {{0x61e9e6a0,0x2a693aa8,0xb95b0032,0x6f010187}}, // weel, llab_, _adìg, _álco,
+ {{0xe3b08d4a,0x6e2d66a1,0x61e9e6a2,0x25ade6a3}}, // اره_, _olab, teel, _teel_,
+ {{0x09e080c8,0x02dd901c,0x3ede8870,0x442c90cc}}, // _মোবা, _महीन, _chọọ_, _eld_,
+ {{0x4900000d,0x61e9dea2,0xdb0e0548,0xf1bf026b}}, // रियो_, reel, rabé, _alák_,
+ {{0xb8d59499,0xa5bb0125,0x8afc809a,0x386300ee}}, // _जब_, skól, _częs, _jkjr_,
+ {{0x6e2d080d,0x2a695d0d,0xe0200105,0x661be6a4}}, // _blab, klab_, _मकसद_, _kouk,
+ {{0x1b168a49,0x6443e6a5,0x661b8009,0x4427836e}}, // _থেকে_, lini, _jouk, lkn_,
+ {{0x62864269,0x60c9a738,0xb4bb86a7,0x64438090}}, // _riko, quem, _आटो_, oini,
+ {{0x7afc003b,0x6443e6a6,0x628666a7,0x224d8019}}, // tvrt, nini, _siko, lnek_,
+ {{0xa3df89c2,0xe2970071,0x628666a8,0x660983df}}, // ध्य_, дас_, _piko, _onek,
+ {{0x7bdc1984,0x2a6901c0,0x224d8019,0xe1f98110}}, // sfru, glab_, nnek_, ndų_,
+ {{0x6443e6a9,0xd94310bf,0x2bdb2e06,0x614311a8}}, // [6880] kini, вети, म्हा, вета,
+ {{0x6443c5d8,0x6609a9fb,0x2bd28526,0x60c466aa}}, // jini, _anek, _साधा, rrim,
+ {{0x6443e6ab,0x224d8065,0x2a693371,0x628666ac}}, // dini, knek_, blab_, _tiko,
+ {{0x81e280c8,0xf54f082e,0xb33c84b7,0x7afc818a}}, // _ফোন_, pụta_, _meħt, _árti,
+ {{0xdb0aa509,0xa7fc83bf,0x05b606a7,0x224d8061}}, // rafí, rmız, _अजनब, dnek_,
+ {{0x6443e202,0xdb0a85e4,0xe3b8880a,0x442780dd}}, // gini, safí, _alır_, gkn_,
+ {{0xbddb009f,0x644e0d35,0x7e6406c0,0xb5fb659e}}, // dièn, inbi, _okip, noác,
+ {{0xf2d3004c,0x26d8008e,0xaca40091,0x7d00808e}}, // דעה_, _iqro_, _afọw, _trms,
+ {{0x64439f96,0x961d8029,0x442780ee,0xdfd081a8}}, // bini, _ziņo, bkn_, هية_,
+ {{0xdb0192d2,0x629e003b,0x3f1484fa,0x29020088}}, // _belä, _otpo, едос, _mrka_,
+ {{0x3eb88117,0x6e2d08f9,0xd7f88162,0xb5fb01a8}}, // ért_, _plab, rdă_, llái,
+ {{0x26c7005c,0xa5f90009,0x644e66ad,0x2902524d}}, // šnog_, чему_, enbi, _orka_,
+ {{0x26c5e6ae,0xdb088207,0xdd318085,0xa7fc82d0}}, // arlo_, _medí, _məşh, nlık,
+ {{0xe94789a7,0xbddb0722,0x7a3100e8,0x69d58061}}, // _ترمی, cièn, _måte, őzet,
+ {{0x7a3139e8,0x98a38110,0x29022fd7,0xe7f3816f}}, // _låte, ąją_, _arka_, _असता_,
+ {{0x6443b77b,0x290208ae,0x90990098,0x3e5600eb}}, // zini, _brka_, зват_, tāte_,
+ {{0x64438850,0x26ccaa0b,0x61e2da58,0x27f83e88}}, // [6890] yini, ludo_, _mbol, _barn_,
+ {{0xe3c600c8,0x661be6af,0x224d8019,0x27f866b0}}, // শ্বব, _souk, znek_, _carn_,
+ {{0x6443e6b1,0x661be6b2,0x61e284c4,0x201c8144}}, // vini, _pouk, _obol, _movi_,
+ {{0x63b50289,0x290202a5,0x2bdb0aed,0x6443e6b3}}, // lazn, _frka_, म्रा, wini,
+ {{0x6443e6b4,0xeb998e8e,0x290200d2,0xdb088bd3}}, // tini, пил_, _grka_, _dedí,
+ {{0x201c8289,0x36d50391,0x6820016d,0x7640d8e2}}, // _novi_, _погр, _döds, _emmy,
+ {{0x6443b5dd,0x224d8019,0x4427836e,0x661b8009}}, // rini, tnek_, rkn_, _touk,
+ {{0x64438380,0x442780ee,0x6609e6b5,0x999f81d6}}, // sini, skn_, _unek, knuť_,
+ {{0x63b5003e,0xa3c10076,0x6443e6b6,0x25bfb413}}, // kazn, ुला_, pini, _idul_,
+ {{0x7e642406,0x61ed0083,0x63b506b9,0x6443829b}}, // _skip, heal, jazn, qini,
+ {{0xfe460cb1,0xbddb009f,0x366a02a4,0x201c8267}}, // енно, rièn, зано_, _dovi_,
+ {{0xed5a1594,0x6ec086bf,0x60cd004f,0xd5ed0129}}, // зом_, _विरु, kuam, _phá,
+ {{0x61ed5ad8,0x61fb8057,0x63b50968,0x2d8403ca}}, // deal, mdul, fazn, äme_,
+ {{0x09ba035a,0x61f9b7f5,0x60dbd03f,0x68320366}}, // ेल्य, _hawl, mtum, _kæde,
+ {{0x80c20076,0x26cc8207,0x91e613f1,0x60dbe6b7}}, // _लिहे, cudo_, нове, ltum,
+ {{0x61fba499,0x3a290ae6,0x2bdf8519,0xed5a08b0}}, // ndul, gkap_, प्ता, _ной_,
+ {{0x63b50052,0x60cd49d7,0x2bd29199,0x6832006a}}, // [68a0] bazn, guam, सलमा, _læde,
+ {{0x25dc1a3b,0x76465515,0x3c2b8aa2,0x6c340019}}, // ग्री_, liky, _søvn_, _شہزا,
+ {{0xf09f139a,0x60db837b,0x7d042503,0x2bc99299}}, // _età_, htum, _hris, _राजा,
+ {{0x7d0466b8,0xa3d289a3,0x321d809a,0x29020da8}}, // _kris, _वाह_, _nowy_, _trka_,
+ {{0xc3c909a7,0x27f866b9,0x61fbe6ba,0x26cc87b6}}, // _عظیم_, _tarn_, ddul, zudo_,
+ {{0xf1bf016b,0x7d04004f,0xa7fc82d0,0x61e28037}}, // _zdá_, _mris, rlık, _sbol,
+ {{0x2bd3016f,0x201c8037,0x78a40037,0xa3df8a27}}, // _तासा, _rovi_, lpiv, ध्ध_,
+ {{0x7d0466bb,0x93bc8087,0x28c74638,0x998d8035}}, // _oris, _alăt, _लिमि, cież_,
+ {{0x29029d9a,0x61f982a3,0x60db87cd,0x2900023e}}, // íka_, _dawl, gtum, nvia_,
+ {{0x81c900c8,0x26cc8548,0xdb06802e,0x7e62c7dd}}, // ল্প_, tudo_, ămân, loop,
+ {{0x28c28996,0x61fbe6bc,0x6f03992c,0x2ca000b9}}, // _शिवि, bdul, _crnc, _atid_,
+ {{0x26c2812b,0x61e2ace4,0x6d5180c3,0x0ae981a8}}, // čko_, _ubol, škaš, _تركي_,
+ {{0x7a230074,0x645a026f,0x63b50f09,0x9f4e826b}}, // _mõtt, čtin, tazn, _dafá_,
+ {{0xaad4800f,0x7d0418f4,0x998d8035,0x3e7204e8}}, // _धमाक, _dris, zież_, ráta_,
+ {{0x7d043755,0x63b51249,0xf09f01e8,0x2cea01fe}}, // _eris, razn, _stà_, टबॉल_,
+ {{0xfd268028,0xa3d60740,0x7d0427eb,0x60cd66bd}}, // _đình_, _हाथ_, _fris, tuam,
+ {{0x660d66be,0x865c0039,0xb5fb041c,0xf09f00e5}}, // [68b0] _inak, _מדהי, fláv, _qtà_,
+ {{0x61ed05ae,0x3a2966bf,0x6820016d,0x60cd2a83}}, // seal, skap_, _södr, ruam,
+ {{0xdcfd0459,0xa3d6142d,0x64472003,0xc5d59138}}, // ması, _हात_, miji, віль,
+ {{0x644766c0,0xdcfd07c0,0x7a3e9247,0x660d0580}}, // liji, lası, būti, _jnak,
+ {{0x3e5600eb,0x60cd00e5,0x68323c10,0x660d0c61}}, // nāta_, quam, _sæde, _mnak,
+ {{0x64472f1e,0xdcfd0059,0x3e878032,0x661f03ed}}, // niji, nası, _bìtì_, _loqk,
+ {{0x660d0025,0xdce40063,0x2bd2800d,0x61f9c2ac}}, // _onak, _dzię, _सारा, _pawl,
+ {{0x05d880d4,0x60dba645,0x6824823e,0x61fbe6c1}}, // _डायब, ttum, _mòdu, udul,
+ {{0x212b8a65,0x3495b51e,0xdcfd1010,0x61fb93ec}}, // ách_, _разр, kası, rdul,
+ {{0x60dbe6c2,0x7c2ae6c3,0x64472813,0x4ae206a7}}, // rtum, ckfr, jiji, _पहलव,
+ {{0x34d18c78,0x2ca0056f,0x76462916,0x47c700ab}}, // _समुद, _stid_, tiky, র্থী,
+ {{0x270101e2,0xddc280d2,0xbbaa1130,0x9f5802d6}}, // _mėn_, ološ, कर्क, _darè_,
+ {{0x9f5808cf,0xdcfd22f8,0xdb0e66c4,0xee372325}}, // _parë_, fası, rabí, тня_,
+ {{0x660d66c5,0x628b82c4,0x64471a99,0x290000e5}}, // _enak, _higo, giji, vvia_,
+ {{0x2bd31905,0x628ba169,0x9cff80ab,0xc7d69101}}, // _तारा, _kigo, ্নয়ন_, _יוסי_,
+ {{0x290066c6,0x628be6c7,0xdb0880e1,0xc3329101}}, // tvia_, _jigo, _jedá, בול_,
+ {{0x7bc1803a,0xf1b28158,0x64472949,0xddc28289}}, // [68c0] _odlu, יסן_, biji, jloš,
+ {{0x25e301ce,0x628b8110,0x9f580388,0x29002853}}, // ट्ठी_, _ligo, _haré_, rvia_,
+ {{0x44444ca3,0x99990065,0xbddb13ff,0xfaa60a18}}, // _im_, _első_, zièm, _шано,
+ {{0xdb08826f,0x78bb87b8,0xdb018061,0x471a80be}}, // _nedá, ksuv, _felú, וועג,
+ {{0x7e6287b8,0x7a230006,0xbddb00e7,0x9f430388}}, // roop, _võtt, xièm, _bajó_,
+ {{0x07a3325a,0x628b8101,0x8fa32659,0x682d80e1}}, // _варн, _aigo, _варе, _súdn,
+ {{0xf746110b,0x628be6c8,0x779100d5,0x4444037e}}, // _рево, _bigo, _ایما, _mm_,
+ {{0x60f89b93,0xf506838d,0xda09a3e6,0xa3e28540}}, // ения_, _избо, वागत_, _फॉर_,
+ {{0x44446193,0xdcfd0201,0x628be6c9,0xfbb800ab}}, // _om_, yası, _digo, _জানত,
+ {{0x6e2084a1,0x660d0257,0x88bd809a,0x76441af7}}, // _homb, _snak, myśl, _amiy,
+ {{0x64470503,0x61e61807,0x7a310711,0x60c9816a}}, // viji, _obkl, _låta, arem,
+ {{0x444404e7,0x291180f7,0x6e20861b,0xc0aa92c8}}, // _am_, ónaí_, _jomb, _قاتل_,
+ {{0xdd1188c5,0x9f5103a8,0x78700106,0xdcfd061c}}, // _müşa, _mazá_, tävl, tası,
+ {{0x09cb238c,0x628bdb1d,0x764466ca,0x22988061}}, // िल्य, _zigo, _emiy, _kék_,
+ {{0x64474ea1,0xdcfd0182,0x6e2081b0,0x44440252}}, // riji, rası, _oomb, _dm_,
+ {{0x444441d2,0x6e20e6cb,0xe5e90009,0x79a40009}}, // _em_, _nomb, ämää, ируе,
+ {{0x444466cc,0xb3d28540,0xa3d2e6cd,0x7d0288ae}}, // [68d0] _fm_, _सांख, वला_, jvos,
+ {{0x63b88353,0x7d0280d2,0x5347259a,0x2ca6bbb0}}, // javn, dvos, _ахма, dpod_,
+ {{0x63b8803b,0xdb05016d,0x58841dfe,0x425500b7}}, // davn, _behå, рыта, _مندر,
+ {{0xdb0700e1,0x13b80264,0xdb01816a,0x29068106}}, // kajú, _জায়, _telú, _oroa_,
+ {{0x444466ce,0x44212d68,0x6e20e6cf,0x4433008e}}, // _ym_, _koh_, _domb, _klx_,
+ {{0x60c9803b,0x27fc8b40,0x44214206,0x2249011b}}, // vrem, _navn_, _joh_, liak_,
+ {{0xe816835a,0x6e208176,0xf41402f6,0x212900dd}}, // ताना_, _fomb, יפס_, lyah_,
+ {{0xe8f999d9,0x22491cf2,0x61fd4b25,0x442154d2}}, // ели_, niak_, _jasl, _loh_,
+ {{0xb5fb1389,0x21290b50,0x656d52c1,0x60c981b3}}, // hlás, nyah_, _nyah, urem,
+ {{0x6e2086a0,0x60c9ca6c,0xa2cabe98,0xef19c249}}, // _zomb, rrem, संख्, емо_,
+ {{0x44441cbc,0x656d09ca,0xe7f60770,0x2249011e}}, // _rm_, _ayah, ीयता_, kiak_,
+ {{0x61fd036f,0x64a65c8e,0xdca60294,0xe80a009a}}, // _nasl, лама, лами, _होना_,
+ {{0x44440c6b,0x37e58a7c,0xf1b9a7b1,0x7a358511}}, // _pm_, _болг, maš_, _cáte,
+ {{0xf1b9e6d0,0x9f058077,0xdb018aa2,0x44440de4}}, // laš_, _موتو, _belø, _qm_,
+ {{0x442166d1,0x61fd36b2,0x60dd0609,0x22490fe0}}, // _doh_, _basl, _aqsm, fiak_,
+ {{0x63b8807d,0xe811800f,0x2249011e,0x44212404}}, // zavn, ़ावा_, giak_, _eoh_,
+ {{0xeb1f8076,0x6e20836e,0x443300ee,0xf1bf046d}}, // [68e0] बहुत_, _romb, _flx_, _alás_,
+ {{0x444466d2,0xe73a2469,0x442166d3,0xe8f701e5}}, // _um_, нез_, _goh_, ылу_,
+ {{0x0dca81a0,0xb5fb0207,0x22490fe0,0x6e20e6d4}}, // елей_, clás, biak_, _pomb,
+ {{0xed5a825d,0xe1f20277,0x7e7bb736,0x7d0281f4}}, // ное_, _اسد_, _chup, tvos,
+ {{0x442166d5,0xe816801b,0xa92801d0,0x7e7be6d6}}, // _yoh_, तामा_, _držá, _dhup,
+ {{0x7e69819d,0xdb0700e1,0x7d028e6c,0x6e20836a}}, // _ekep, vajú, rvos, _womb,
+ {{0x78a2b6dc,0xd6ae80c8,0x533491d2,0x05d4016f}}, // _stov, _কম্প, рейт, _थांब,
+ {{0x48aa80e2,0x2906e516,0xf7530199,0x69d666d7}}, // нтом_, _proa_, _انفج, ngye,
+ {{0x38348328,0x2611816f,0x7c21823e,0x25fdbac3}}, // инир, णारी_, _folr, _रोटी_,
+ {{0xe45f016d,0x2249011e,0xf1a4909b,0x7c2182c4}}, // tröm_, ziak_, _ग्रन, _golr,
+ {{0x9b938307,0x33938013,0x44210587,0xab938013}}, // _المت, _المز, _roh_, _المغ,
+ {{0x21291600,0x7a35801b,0x2cb20114,0x442143b0}}, // yyah_, _páte, _bwyd_, _soh_,
+ {{0x442144cf,0xf09f026b,0x78a2e6d8,0x349513d1}}, // _poh_, _aràn_, _utov, рагр,
+ {{0xb4e50063,0x61fd46bc,0x9f5c8168,0xe09e819d}}, // _नही_, _sasl, _javë_, _gọọ_,
+ {{0x0cd185e8,0x61fd449c,0x28c7035a,0xf8bf000d}}, // _सम्म, _pasl, _लिहि, _své_,
+ {{0x53341056,0xb5fb0019,0x442100d7,0x229180c3}}, // щест, rlás, _woh_, _uške_,
+ {{0x7bdae6d9,0x224900ad,0x68f6031d,0x44211a92}}, // [68f0] _octu, riak_, gwyd, _toh_,
+ {{0x7e69aa33,0x25fd835a,0xf1bf00f7,0x22495394}}, // _skep, _रोजी_, _clár_, siak_,
+ {{0xf1b98353,0x2129010b,0x81cc80ab,0x22493fae}}, // ljše_, syah_, শ্য_, piak_,
+ {{0x61fd00f7,0xddc28035,0x7c21a280,0xd5fb827d}}, // _uasl, mnoś, _solr, _khủ,
+ {{0xddc28063,0x7c21e6da,0x21290359,0x644a854e}}, // lnoś, _polr, qyah_, fifi,
+ {{0x628f00ee,0xd90e0065,0x60cd09c4,0x644ae6db}}, // _hico, _کیے_, mram, gifi,
+ {{0xf1b9811f,0xf99180f7,0x60cd0174,0xff040081}}, // taš_, يبة_, lram, _лятн,
+ {{0x60cd0081,0x6d5b81c5,0x26c380e1,0x26d30118}}, // oram, _txua, ájom_, muxo_,
+ {{0x20558628,0x26cc85b4,0xa3d600a5,0x661d01a1}}, // итор, erdo_, _हाल_, djsk,
+ {{0x2001003d,0x644ae6dc,0xe3b0803d,0x764b84a7}}, // ndhi_, cifi, _فرم_, ligy,
+ {{0x2bab835a,0x60cd66dd,0x2bdc1299,0x69a690a1}}, // _घ्या, hram, _बाबा, _ट्वी,
+ {{0x7d0988e7,0x81cc80ab,0xddc2809a,0x7bc52718}}, // _kres, শ্ব_, dnoś, _adhu,
+ {{0x60cd1069,0x9f4c8081,0x99838084,0xe09e81bc}}, // jram, ledì_, ėjų_, _tọọ_,
+ {{0x69cb3cd4,0x62972795,0x26cc80d2,0x4fa59a19}}, // ógen, _buxo, brdo_, _тикв,
+ {{0x61e45b9e,0x290f00f2,0xae1e000f,0x9f4c8081}}, // tfil, ågan_, _बचपन_, nedì_,
+ {{0x60cd070b,0x644ad2b2,0x98a28110,0xc33284de}}, // fram, zifi, šką_, קון_,
+ {{0xdb03803e,0x628f66de,0x9f4701ac,0xada900f7}}, // [6900] vaný, _dico, ľný_, _صديق_,
+ {{0x68f65f99,0x644ab075,0x9f47026f,0xf09f00ff}}, // rwyd, xifi, žný_, _tràn_,
+ {{0x628f66df,0x91e64a58,0x103680be,0x09e609a8}}, // _fico, _коне, סטעם_, _конн,
+ {{0xa3d6000f,0x2011004f,0x0e66b3d9,0x270580eb}}, // _हाँ_, _enzi_, шкен, _mēn_,
+ {{0x25a99e4f,0x644aa4cf,0x7afa826c,0x7d098888}}, // ñala_, tifi, _sstt, _cres,
+ {{0x63bc20d0,0x0ec581fe,0x26d301df,0x3e7c826f}}, // narn, _विंड, buxo_, díte_,
+ {{0x764b809c,0x15e780d4,0xb5fb04e8,0xdb1e1e1e}}, // bigy, _टॉवर_, kláp, _odpí,
+ {{0xe80a113d,0x628f023e,0xdb088338,0x62830037}}, // _होता_, _xico, _nedå, _ènot,
+ {{0x1da7223a,0xa3d6064d,0xd01080f7,0x64a39ea6}}, // _क्षत, हला_, كلة_, _мата,
+ {{0x6600e6e0,0x63bc19f0,0x69a68361,0x44e090ab}}, // _hamk, jarn, _ट्री, mò_,
+ {{0x63bc29dc,0x645a89da,0x44e09b72,0x44e280ff}}, // darn, _ijti, lò_, _hư_,
+ {{0x60cd0182,0x6602e6e1,0x6600e6e2,0x6e244ac7}}, // yram, ndok, _jamk, _coib,
+ {{0x628f0698,0x40969506,0x62840063,0x44e0946f}}, // _rico, _трет, dmio, nò_,
+ {{0x63bc18b6,0xddc28063,0x78a903bf,0xcfcc00ab}}, // garn, tnoś, _çevi, র্তন,
+ {{0x78a6008b,0xdddd0035,0x6e3627d1,0x8ff78019}}, // _atkv, rosł, _flyb, _ضرور_,
+ {{0x80c20321,0x0dcb0c4f,0xddc28035,0xdce98b67}}, // _लिखे, _руки_, rnoś, _uzeć,
+ {{0x5fdd901b,0x63b70201,0x51871507,0x9f581c72}}, // [6910] _मामल, _texn, рува, _darí_,
+ {{0x60cd26d5,0x9f4786a5,0x7d0980f1,0x44e0bfd1}}, // rram, _ganó_, _rres, dò_,
+ {{0xfbdc035a,0xf7700277,0x7d0982a5,0x7a358b4c}}, // _बातम, وام_, _sres, _láta,
+ {{0x60c285f5,0x9f4c8081,0x7a3c8019,0x6600808e}}, // _ovom, vedì_, _hétf, _camk,
+ {{0xaca38028,0x44e2801c,0x60cd0201,0x6600982f}}, // _trọn, _cư_, qram, _damk,
+ {{0x26d301df,0x2291826c,0xdb050edd,0x7d09c5ca}}, // puxo_, _aška_, _behø, _vres,
+ {{0x9725003d,0xad9d809a,0xa3dc0f12,0x7a2a026f}}, // _افزو, _paźd, ण्ट_, _výtv,
+ {{0xe1f980ae,0x61e90074,0xb87b0118,0x3e7c816b}}, // уги_, _öeld, _epít, títe_,
+ {{0x63bc09da,0x7649a2b3,0x7d09e6e3,0x44e080e5}}, // yarn, _imey, _ures, cò_,
+ {{0x7e6d0b3f,0x6600809a,0x60c2824a,0x2bdc03eb}}, // _akap, _zamk, _dvom, _बाधा,
+ {{0x78a98db7,0x3e7c803e,0x6e240013,0x6fd8816f}}, // spev, síte_, _poib, _मारू,
+ {{0x63bc3996,0x44259104,0xdb150e93,0x80ad00ab}}, // warn, _hol_, gazí, _চিন্,
+ {{0x4425e6e4,0x682d88f1,0x998580f7,0x224d9151}}, // _kol_, _lúdi, _الشو, miek_,
+ {{0x53a30791,0x224de6e5,0x03a32098,0x7e6d66e6}}, // зарб, liek_, зиро, _ekap,
+ {{0x63bc66e7,0x09a900ab,0x8c432097,0x7c253dbb}}, // rarn, _ওয়া, чере, _dohr,
+ {{0x224ddbd7,0x9f45839c,0x442590b6,0x7a2a01fa}}, // niek_, relä_, _lol_, _nýtt,
+ {{0xdb188073,0x19b901e2,0x4fea1777,0x7a2e8014}}, // [6920] ravé, русь_, лман_, _dùth,
+ {{0x66008125,0xdb018019,0x80cf8526,0x9588009a}}, // _samk, _belü, _दिने, _ciąż,
+ {{0xd9f9835a,0x93fb0158,0x644e5a91,0x44e2801c}}, // ्यात_, _גליי, mibi, _sư_,
+ {{0x0d86884b,0x44e0946f,0x05ba9a00,0x6d03e6e8}}, // блен, tò_, ادات_, लिंग_,
+ {{0x4425e6e9,0xe8df0028,0x6fd8800f,0x290b01a1}}, // _bol_, _phục_, _मालू, _grca_,
+ {{0x4425af10,0x68e40082,0x6fd5035f,0xa0a3217e}}, // _col_, mtid, _місц, _хард,
+ {{0x60c28052,0x4425e6ea,0x68e41aed,0xc5ed80c8}}, // _svom, _dol_, ltid, ওয়া_,
+ {{0x44e28104,0x644e66eb,0x5fa4016f,0x644d8036}}, // _tư_, hibi, खुरल, éair,
+ {{0x644e34c2,0x55548065,0xdced005c,0xa5078b02}}, // kibi, _بھار, _izać, _лета_,
+ {{0x7e6d088b,0xa3d28d14,0x644e1a9b,0x3202016b}}, // _skap, _वाट_, jibi, _laky_,
+ {{0x644e13d4,0xb05b0106,0x68e402f1,0xc7b301c6}}, // dibi, _ovän, htid, הבת_,
+ {{0x90e68077,0x60c280d2,0x68e40074,0xdb1c3089}}, // _هستن, _tvom, ktid, parê,
+ {{0x44259010,0x412a835f,0x7e93802e,0x6018a5af}}, // _yol_, _щодо_, _săpt, _воля_,
+ {{0x629aa0d4,0x3a268b3c,0x80a4803d,0x394001a9}}, // _kuto, _hoop_, _همچن, dzis_,
+ {{0x3a2685f8,0xa3e086b7,0x4ea68523,0xf1b9807a}}, // _koop_, _थान_, _урла, ljša_,
+ {{0x63bad9e9,0x629aa0ee,0xb4e88105,0x7a358718}}, // _letn, _muto, _बहू_, _látn,
+ {{0x644e66ec,0x3207001b,0xf1b9807a,0x629e816b}}, // [6930] bibi, ěny_, njša_, ípoj,
+ {{0x644e160a,0x629ae6ed,0x224daffc,0x7d0d66ee}}, // cibi, _outo, ziek_, _iras,
+ {{0x2baf0dbc,0x4425831e,0xe7ea816f,0x3ea703b2}}, // _ज्या, _rol_, ज्या_, ënt_,
+ {{0x25e1023c,0x7bc8e6ef,0x9f5801e8,0xed51803d}}, // _काफी_, _addu, _darà_, وپا_,
+ {{0x4425979d,0x68e408f1,0x25e113e5,0x224d80e1}}, // _pol_, ctid, _कानी_, viek_,
+ {{0x9f58394c,0x63ba807d,0x629ae6f0,0xdb1c1b48}}, // _farà_, _cetn, _buto, rgrö,
+ {{0x224ddd2c,0xdb018073,0xdb1c66f1,0x3a2682df}}, // tiek_, _reló, varë, _boop_,
+ {{0x4425ac23,0x200328ab,0x7d0d66f2,0x644e66f3}}, // _wol_, _haji_, _oras, zibi,
+ {{0xdb1c020f,0x224d811e,0x200366f4,0x3a26874c}}, // tarë, riek_, _kaji_, _doop_,
+ {{0x9f5805a4,0x224d9c59,0x442583a7,0x20030010}}, // _hará_, siek_, _uol_, _jaji_,
+ {{0x7d0d448d,0x2003022e,0x629ae6f5,0x644e047f}}, // _aras, _maji_, _guto, vibi,
+ {{0x7d0d16cc,0x76598355,0x68e412cf,0x29188174}}, // _bras, nnwy, ytid, ófaí_,
+ {{0x1ddb0853,0x61e9d589,0x2be084c5,0xe81c000d}}, // _भारत, mfel, _नामा, नामा_,
+ {{0x7d0d4b38,0x69dbd26f,0xdb1c11a9,0x61e98357}}, // _dras, lgue, laré, lfel,
+ {{0x644e66f6,0x7d0d1611,0x26c7012b,0xe8168c2d}}, // ribi, _eras, šnoj_, तारा_,
+ {{0x644e1598,0x61e9bfcb,0x69dbb274,0x68e466f7}}, // sibi, nfel, ngue, ttid,
+ {{0x9f580698,0x7a358298,0x7a380118,0x602581d0}}, // [6940] _sarà_, _láto, _cíta, _témě,
+ {{0x9f58146f,0x20034941,0x2005e6f8,0x39400162}}, // _aará_, _caji_, ndli_, rzis_,
+ {{0x6604025d,0xbddb03d3,0x63ba8bc5,0x3202001b}}, // _kaik, mièr, _retn, _taky_,
+ {{0xac9719f4,0xbddb02be,0x0caa891d,0x61e980f3}}, // _دنیا_, lièr, атки_, jfel,
+ {{0xb05b00f2,0x660466f9,0x9f582a63,0xdb170118}}, // _kväl, _maik, _dará_, _rexé,
+ {{0x66043d3b,0x629a8289,0x48f90158,0x7bdc66fa}}, // _laik, _puto, _פּרא, ngru,
+ {{0x752f0063,0x3f980a20,0x443a66fb,0x7a3c8019}}, // zycz, _igru_, _mlp_, _léte,
+ {{0x61e98065,0x1cba9381,0x660429fb,0x80ad00ab}}, // gfel, _نائب_, _naik, _চিত্,
+ {{0x60c43328,0x7bdc232f,0x320281ac,0x3e72016b}}, // nsim, kgru, ľky_, nátu_,
+ {{0x3202803e,0xdb1a820f,0x7b1680f7,0x59dc816f}}, // žky_, _jetë, _وظائ, _यावर,
+ {{0x91e690ee,0x907b0451,0xe7e20076,0x660434c6}}, // _може, שטיי, _पानप, _baik,
+ {{0x7d0d2c28,0x752f0d38,0x98b20063,0xdb1a80f1}}, // _pras, tycz, czyć_, _letë,
+ {{0x3ce6886f,0x08fa826a,0x66040110,0x7bdc01ec}}, // ntov_, _خراب_, _daik, fgru,
+ {{0xe80a65fa,0xdb08813c,0x7a3c82be,0x7d0d00f1}}, // _होला_, _bedø, _déte, _vras,
+ {{0x7c28c31e,0x443a0358,0x628281e9,0x200366fc}}, // _modr, _dlp_, _khoo, _raji_,
+ {{0x7d0d3528,0xdb188125,0x3ce6886f,0xaca30133}}, // _tras, javí, ktov_, _ahụg,
+ {{0x7c3a8698,0xdb1885b4,0x7bdc002a,0x60c4009c}}, // [6950] _oltr, daví, bgru, gsim,
+ {{0x69c0e6fd,0x7c288029,0x6282b58e,0x38a08014}}, // hame, _nodr, _lhoo, _mòr_,
+ {{0xe7e285e8,0x69c0d3fa,0x660403f8,0x629a0019}}, // _खाना_, kame, _yaik, ítot,
+ {{0x7c3ae6fe,0x69c0ca77,0x6e228019,0x3e5600eb}}, // _altr, jame, gjob, tāti_,
+ {{0x7ec02067,0x03a59e25,0x7c28e66d,0x43d40180}}, // _pápá, пило, _bodr, _عزیز,
+ {{0xf1bf04c3,0x7c288162,0x96d3064a,0xb09a01c6}}, // _xoán_, _codr, _डिपॉ, _לישר,
+ {{0x68f903bf,0x7c289ee0,0x69c0e2c8,0x9f5c816b}}, // şadı, _dodr, fame, _baví_,
+ {{0x69c09092,0x6e229b88,0x3cff81c0,0x62828039}}, // game, cjob, _tsuv_, _choo,
+ {{0xc3cb803f,0x69db9fd6,0x62828079,0x61e9a66f}}, // _نظام_, rgue, _dhoo, rfel,
+ {{0x61e991d9,0x66040077,0x68fb87d5,0x764d36ed}}, // sfel, _saik, rwud, _amay,
+ {{0x660403ff,0x69c0d643,0xfd4d0028,0xdb238077}}, // _paik, bame, _triệ, _توضی,
+ {{0x7c2880f3,0x20038ee1,0xb05b0106,0x62828362}}, // _zodr, žji_, _sväl, _ghoo,
+ {{0x66042afb,0xbddb02be,0xf1b98353,0xc19a8039}}, // _vaik, tièr, ljšo_, _השני,
+ {{0xb068845b,0xf745840d,0x2bdc0072,0x44e41743}}, // _وصول_, _нело, _बाळा, rö_,
+ {{0x660407ac,0x7bdc0456,0xbddb00e7,0xd9e28105}}, // _taik, rgru, rièr, क्कत_,
+ {{0x7bdc04fe,0xee3f026f,0x60c45afe,0x160b80d4}}, // sgru, chý_, tsim, _सोलर_,
+ {{0x60c401e2,0xf1bf0028,0xdb1566ff,0x6f0e08ae}}, // [6960] usim, _toán_, razá, _srbc,
+ {{0x3e72003e,0x88bd80c8,0x6e3b8136,0xa3d61513}}, // rátu_, _আমাক, _alub, _हाट_,
+ {{0x60c4452c,0x7c288c52,0xdb1a88cf,0x9f5807e2}}, // ssim, _rodr, _vetë, _març_,
+ {{0x69c0cd49,0x7c28e700,0x0d868436,0x6aa30511}}, // xame, _sodr, плен, _énfa,
+ {{0x60c401b9,0xdb18816b,0x6e229375,0x629e6701}}, // qsim, raví, rjob, _hupo,
+ {{0x29024672,0x044395b9,0x26c58110,0x68e48009}}, // _iska_, нечн, kslo_, _äidi,
+ {{0xd9f98bf5,0x628281c5,0x78a9017b,0x3ce6886f}}, // ्यंत_, _phoo, _çevr, stov_,
+ {{0x7584804e,0x224000e4,0x3ce6a39f,0xaf5b01c6}}, // _قیام, nhik_, ptov_, _הכלכ,
+ {{0xa3dd000f,0x629e15d0,0x26da6702,0x3e5600eb}}, // _डाल_, _lupo, lupo_, nātu_,
+ {{0x4734a05f,0xdefa97ae,0x645c6703,0x69cbaf96}}, // днос, бым_, jnri, _edge,
+ {{0x69c0e704,0x628281c5,0x290fe705,0x5fdd93ba}}, // pame, _thoo, _erga_, _मारल,
+ {{0x0f570039,0x6b7b80be,0x290202f1,0xc1e38c69}}, // ליים_, _פראנ, _oska_, _गायब_,
+ {{0x94058085,0x290f85f3,0x786f983d,0x6382e706}}, // _ailə_, _grga_, røvr, _fíní,
+ {{0xa3dd0072,0x629e038a,0x26da02c4,0x8aa71501}}, // _डाळ_, _bupo, kupo_, зрад,
+ {{0x7b64171c,0x629e3736,0x2d800870,0x63be008e}}, // етте, _cupo, _ezie_, _depn,
+ {{0x764d6707,0x20183da9,0xeb3b83de,0x645c0901}}, // _umay, _anri_, זעלש, anri,
+ {{0x236d009a,0x320680e4,0xa3d60072,0x2007041c}}, // [6970] łej_, _maoy_, _हाच_, ônio_,
+ {{0x80cf816f,0xb60680c3,0x638707bc,0x6e29e708}}, // _दिसे, _lešć, géné, _soeb,
+ {{0xf1dc835a,0xfbd2036b,0x48f91e0b,0x26c7000d}}, // _यांन, _בתי_, _उनको_, éno_,
+ {{0xb8d30a49,0x224002d6,0xcf9380be,0xdd918829}}, // _টি_, chik_, גטע_, نوا_,
+ {{0xfce595b1,0xdb018019,0xf1bf0118,0xa3b9864a}}, // фоло, _jelö, _joám_, चरल_,
+ {{0x61e2e709,0x3e480019,0xdb1703a8,0x61ed670a}}, // _acol, lőtt_, _lexí, lfal,
+ {{0xdcfb817f,0x753c8085,0x6e29910f,0xb60680c3}}, // _izuč, _ərzi, _toeb, _bešć,
+ {{0x5f9400e9,0x394d07ca,0x61ed10f6,0x6e3b809a}}, // _чист, _þess_, nfal, _ulub,
+ {{0x7fd5a1d2,0xa3dd016f,0xa4d480e8,0x6289e70b}}, // міні, डला_, _поті, rmeo,
+ {{0xb02480ff,0x80dc8035,0xb5fb0216,0x2c760366}}, // _phườ, पढ़े, zoát, pædi_,
+ {{0x7bc3e70c,0xfe708019,0xfbe300ab,0xf771bb76}}, // manu, _شدہ_, য়াত, ضاد_,
+ {{0x200b8610,0xddc2877b,0x7bc3e70d,0x26c58110}}, // ści_, zlož, lanu, rslo_,
+ {{0x320952e6,0x7a359c18,0x290380eb,0x61ed670e}}, // dday_, _látk, ājas_, dfal,
+ {{0xb0248028,0x61ed1849,0x765d02f7,0xe29a3750}}, // _thườ, efal, ansy, жав_,
+ {{0xeb970b5b,0xe7e28770,0x03a32659,0xa2e3186c}}, // дир_, _खाता_, тисо, корд,
+ {{0xed5a07c4,0x7bc3e70f,0x25bf898b,0x2007e710}}, // _мой_, hanu, _neul_, _hani_,
+ {{0xdb1c2509,0x2007bcb2,0x68e9e711,0xf77884b7}}, // [6980] marí, _kani_, nted, mgħa_,
+ {{0xdb1c1727,0x7a38009f,0x7a3c80e7,0x32090079}}, // larí, _títo, _méta, aday_,
+ {{0x8c1b0159,0x2007e712,0x441b0158,0x49ba0b8c}}, // _וויי, _mani_, _וויס, _واحد_,
+ {{0x60db8e05,0x68e987d9,0x2ca00006,0xdb1c2509}}, // kuum, kted, _kuid_, narí,
+ {{0x25e309a3,0x6e3d00f1,0x7bc3ba5c,0x68e9e165}}, // ट्टी_, ërbi, fanu, jted,
+ {{0x2ca06713,0x2007a486,0x7bc3e714,0x2ca9cd2c}}, // _muid_, _nani_, ganu, íada_,
+ {{0x5f030139,0x160e05e8,0xae0e085d,0xddc691b3}}, // _изра, ियार_, ियान_, _обви,
+ {{0x38a41836,0x7d04015e,0x2be0816f,0xdb1c1e09}}, // _kör_, _osis, _नावा, jarí,
+ {{0x2007e715,0x7bc382a0,0xdb1701df,0xdb1c0333}}, // _bani_, banu, _rexí, darí,
+ {{0x25e1053e,0x7a358064,0x7a3c83d3,0x2007e716}}, // _काही_, _láth, _déta, _cani_,
+ {{0x9343917e,0x38a4016d,0x32096717,0x61fbdad8}}, // _инфе, _lör_, yday_, beul,
+ {{0x442c826f,0xdb1c0333,0x32090079,0x99800084}}, // _hod_, garí, xday_, nkių_,
+ {{0x442c803a,0x2ca00ad0,0x7a310370,0x68e99916}}, // _kod_, _cuid_, _mått, cted,
+ {{0x2007c508,0x2ca006cb,0x32090079,0x628602d5}}, // _gani_, _duid_, wday_, _ahko,
+ {{0x442ce718,0x7d046719,0x61ed39d7,0xc1088028}}, // _mod_, _esis, tfal, _hỗ_,
+ {{0x442ce71a,0x38a400f2,0x7c3e3644,0xdb1c05e4}}, // _lod_, _bör_, _elpr, carí,
+ {{0x20078214,0xf09f001c,0x261c1199,0x764289e1}}, // [6990] _yani_, _quà_, यारी_, dhoy,
+ {{0xe61089d7,0x25bf82be,0x61ed3ff4,0x47d080ab}}, // یشن_, _seul_, sfal, ত্রী,
+ {{0x6455640c,0x249f8176,0x2cad8b40,0x2ca000f3}}, // mizi, _suum_, _sted_, _zuid_,
+ {{0x38a4671b,0x80cf816f,0x32090079,0x7bc3b0a0}}, // _för_, _दिले, qday_, wanu,
+ {{0x38a404b8,0x442c8355,0x7bc394c7,0x6e2d0bb1}}, // _gör_, _bod_, tanu, _moab,
+ {{0xd3788063,0x6385b383,0xdb85802e,0xe299e71c}}, // _być_, нгла, нгли, пак_,
+ {{0x2be0853f,0x442ca87f,0x7bc3e71d,0x2ba70c28}}, // _नारा, _dod_, ranu, _खलना,
+ {{0x200780a4,0x68e99dd5,0x645518e9,0x443ee71e}}, // _sani_, tted, hizi, _elt_,
+ {{0x442c8355,0x64550883,0xdb1c08a4,0x661b9fb7}}, // _fod_, kizi, varí, _inuk,
+ {{0x44e9a980,0x68e9e71f,0x442c8a85,0x9bf48103}}, // mú_, rted, _god_, _изуч,
+ {{0x68e984c6,0x64556720,0xdb1c388a,0x2007911b}}, // sted, dizi, tarí, _vani_,
+ {{0x20078a73,0x2bb8116e,0xa3ab8fcc,0xdd1282d0}}, // _wani_, अर्थ, कुर_, nüşt,
+ {{0x7bc18812,0x2007e721,0x64556722,0x442ce723}}, // _kelu, _tani_, fizi, _yod_,
+ {{0x628608cf,0x38a4016d,0xdb1c1f5c,0x64552290}}, // _shko, _rör_, sarí, gizi,
+ {{0x6d460063,0xe80a0063,0x7bc1a7df,0x60c9ac1d}}, // szka, _होगा_, _melu, lsem,
+ {{0x7bc1c23b,0x7d041827,0xdb260065,0x629980dd}}, // _lelu, _tsis, _épít, _jiwo,
+ {{0x69c4631b,0xe0da1367,0x44e9e724,0x64556725}}, // [69a0] raie, ява_, jú_, bizi,
+ {{0x64550249,0x661be726,0x60c9a2b9,0x7bc1e064}}, // cizi, _anuk, isem, _nelu,
+ {{0x6443ce6a,0x66098708,0xe7e28424,0x671f8701}}, // dhni, _baek, _खासा_, यमिक_,
+ {{0x7a35cc16,0xdb1e6727,0x442ce728,0x60c98100}}, // _fáti, _repè, _sod_, ksem,
+ {{0x7bc193b8,0xd250803d,0x62980036,0xdb1c3666}}, // _belu, ینت_, _évoq, barã,
+ {{0xa3ab9513,0xdee602de,0xa2a489c8,0x66e6280f}}, // कुल_, _поги, _चंद्, _пога,
+ {{0x7bc1895e,0x81e980c8,0x25e10bb8,0x442ce729}}, // _delu, য়ন_, _काली_, _vod_,
+ {{0x6fdd80d4,0x62998041,0x998900e1,0x64553556}}, // _माउं, _ciwo, skať_, zizi,
+ {{0x200a0c6a,0x0d868b30,0x7bc1e72a,0xe21380f7}}, // _kabi_, _члан, _felu, طبيع,
+ {{0x7bc185f8,0x63b3826f,0x6e2d002a,0x64a3a853}}, // _gelu, čení, _soab, _сафа,
+ {{0x8ad68eca,0x60c9956e,0x085701c6,0x998d80c3}}, // _ستائ, asem, תבים_, rkeš_,
+ {{0x200a672b,0x11e980f7,0x2f5b007c,0x7bc1e72c}}, // _labi_, _يعني_, לדינ, _zelu,
+ {{0x6455672d,0x6aa18359,0xe0d480be,0xda6f8019}}, // tizi, _zulf, _סײַ_, _اُن_,
+ {{0x200a672e,0x7e640242,0xdb1acf45,0xda1e86ae}}, // _nabi_, _ejip, _letí, भावत_,
+ {{0x64551917,0xb05b0338,0xdb1c16dc,0x3e641238}}, // rizi, _kväv, garà, sıta_,
+ {{0x6455665d,0xa3dd000f,0x26de826f,0x2d84826c}}, // sizi, _डाक_, muto_, _ozme_,
+ {{0x26ded7e8,0xdb1c0187,0xa3e092c6,0x628d365d}}, // [69b0] luto_, tarã, थला_, rmao,
+ {{0x7e7d088b,0xd2510416,0x44e981ac,0xb881801b}}, // llsp, _چند_, vú_, _říze,
+ {{0xd6db1367,0x26dee72f,0x7bc76730,0xdced0024}}, // ята_, nuto_, maju, _izađ,
+ {{0xdb0504b8,0x7bc1a6db,0x7bc70abf,0x60c9b2ce}}, // _behö, _selu, laju, ysem,
+ {{0x78a28021,0x7bc194ff,0xdb1c04ab,0x64438c41}}, // _nuov, _pelu, mará, thni,
+ {{0x200a5c6d,0x2ef500c4,0x6299e731,0x44e9bfe5}}, // _gabi_, _изгр, _siwo, rú_,
+ {{0x68ed0510,0x26debd0f,0x44e9cdfc,0xf1b98353}}, // ltad, juto_, sú_, ljši_,
+ {{0x26de8003,0x60c9e732,0x661b8289,0x6443e733}}, // duto_, tsem, _unuk, shni,
+ {{0x39492511,0xe664919d,0x7bc1ac17,0xdb0502af}}, // nzas_, _стро, _telu, _gehö,
+ {{0xe7398188,0x60c99922,0x68ed0ece,0x7bc700d2}}, // део_, rsem, itad, jaju,
+ {{0x60c9e734,0x7bc70025,0x9f458388,0x68ed6735}}, // ssem, daju, veló_, htad,
+ {{0xed5a3aa3,0x68ed59af,0x657b816f,0xdb1c6736}}, // дом_, ktad, _kyuh, jará,
+ {{0xdb1c6737,0xc692807c,0xda1e8035,0x26dee738}}, // dará, יאט_, भारत_, auto_,
+ {{0x26dee739,0x98798158,0x307980be,0x09ca0072}}, // buto_, _קאַט, _קאַנ, िण्य,
+ {{0x200a1cc5,0x39490144,0xfaa60dae,0xf8bf217b}}, // _rabi_, ezas_, табо, _awé_,
+ {{0x200a02b8,0x68ed673a,0xdb1c157a,0x3b0700a9}}, // _sabi_, ftad, gará, њето_,
+ {{0x65698168,0x64418198,0xe9df046d,0x68ed673b}}, // [69c0] _nxeh, _olli, _amús_, gtad,
+ {{0x7a38433a,0x161a13e5,0x2d9f8362,0x25ade73c}}, // _míti, धाकर_, _ague_, _afel_,
+ {{0x7a380125,0x290f016d,0x657ba914,0x39492720}}, // _líti, ågar_, _ayuh, azas_,
+ {{0xee3a8160,0x75d38013,0x947380d5,0xe9a6013a}}, // ьне_, ليما, یدوا, _шамп,
+ {{0x200a673d,0x68ed11b9,0xd4e38009,0x200c831d}}, // _tabi_, ctad, ающи, yddi_,
+ {{0x61e407d9,0x07a6038c,0x5c7385e9,0x7d028559}}, // lgil, _јавн, аліт, rwos,
+ {{0x78a2e73e,0x7a358177,0x15f40054,0x969681e5}}, // _suov, _látu, ेजार_, _араш,
+ {{0x61e449de,0x26de8081,0xd7e2001b,0x2281807b}}, // ngil, vuto_, _पाँच, sókn_,
+ {{0x2d848052,0x7e6281f4,0x7a3803f2,0x64a6865c}}, // _uzme_, mnop, _cíti, _раза,
+ {{0x26ded7e8,0xdb1c05e4,0x7eb881a1,0x9f458037}}, // tuto_, zará, нгос_, velò_,
+ {{0x7bc70025,0xa3e7e73f,0x2bdc1094,0x481500be}}, // vaju, _भात_, _बाजा, אַרן_,
+ {{0xf1e10996,0xf7730039,0x7d16245f,0xd00b00ab}}, // _फाइन, יקת_, _brys, রায়_,
+ {{0xe0469b47,0x0fe2016f,0x320b45e3,0x26de9a14}}, // _инди, _पांढ, _racy_, suto_,
+ {{0x7a358e14,0x7e62e740,0x26deca10,0x6458875e}}, // _dátu, hnop, puto_, nivi,
+ {{0x7bc74f7d,0xdb1c6741,0xe81f95bc,0x82a42ba7}}, // raju, tará, बारा_, иште,
+ {{0x68ed6742,0xcbe300c8,0x7bc7002e,0x1fcb001b}}, // ttad, য়েছ, saju, ाण्ड,
+ {{0xdb1c2a8c,0x68e084a7,0x6458b65d,0x3a2902f7}}, // [69d0] rará, numd, kivi, rjap_,
+ {{0x1621053e,0x394902ba,0xdb1c188b,0x6e3d41c2}}, // यावर_, rzas_, sará, lksb,
+ {{0x660d6743,0xdb1c4028,0x533549e3,0x657b9600}}, // _kaak, pará, _сент, _syuh,
+ {{0xe7ff0321,0xb90900ab,0x2ca482f7,0xaee3026b}}, // _उसका_, _মন_, _bumd_, _aiṣa,
+ {{0xd1268bbe,0x660d6744,0x58d40698,0x89da036b}}, // _ام_, _maak, _коят, _אחרי,
+ {{0x660d28fb,0x7a359eca,0x2a668122,0x69c681fa}}, // _laak, _látt, _ajob_, ðken,
+ {{0x629d3d90,0x7a380187,0xed59807a,0x7bc56745}}, // _kiso, _síti, _težo_, _mehu,
+ {{0x20016746,0x660d1328,0x7a35807b,0x629d236a}}, // lehi_, _naak, _nátt, _jiso,
+ {{0x629d5dc6,0x68e0bbce,0x3d94ac81,0xe81f92c6}}, // _miso, gumd, _витр, बाला_,
+ {{0x7659864c,0x7b088009,0x7a380548,0x7bc56747}}, // liwy, ästä, _víti, _nehu,
+ {{0x8eb2803d,0x660d0365,0xe2030072,0x7a359b88}}, // یمیش, _baak, _लसूण_, _bátt,
+ {{0x60cd3456,0x7d166748,0x20cf00eb,0x68e0ac8f}}, // ksam, _prys, kļi_, bumd,
+ {{0x7bc503b2,0x61e403bf,0x7d0982f9,0xee37002e}}, // _behu, vgil, _jses, уня_,
+ {{0xd0068381,0x60dbadd4,0x629d6749,0x7d1601b0}}, // _беше_, mrum, _aiso, _vrys,
+ {{0x60dbb7e4,0xdea1815b,0xcb672d55,0x7bc50168}}, // lrum, _دیجی, гаре_, _dehu,
+ {{0x41b5522f,0x7d161670,0x645880fa,0x629d0d56}}, // исут, _trys, zivi, _ciso,
+ {{0xdb1a8207,0x7644022e,0x60cd1066,0x7e62805c}}, // [69e0] _metá, _iliy, gsam, vnop,
+ {{0x660d674a,0xdb1a816b,0x7bc506a8,0x629d0114}}, // _zaak, _letá, _gehu, _eiso,
+ {{0x4444674b,0x7d09e74c,0x6458e74d,0x7a358019}}, // _il_, _ases, vivi, _játs,
+ {{0x64a695e3,0x5d6a8009,0xb05b00e1,0x629d0314}}, // гада, нием_, _svät, _giso,
+ {{0x444415f8,0x81e98a49,0x7bc501bf,0x7a358061}}, // _kl_, য়া_, _yehu, _láts,
+ {{0x4444674e,0x07a304db,0x69c61989,0x83f882a4}}, // _jl_, _гарн, _heke, текс_,
+ {{0x69c6674f,0xb5fb001b,0x76441a14,0xa3d50072}}, // _keke, dnán, _oliy, हणत_,
+ {{0x6458e750,0x5a349878,0xdd0881ac,0x44444ff8}}, // sivi, рнит, môže, _ll_,
+ {{0x44446751,0xb05b016d,0xdb1a802a,0x660d2c52}}, // _ol_, _tvät, _detá, _raak,
+ {{0x44446752,0x76446753,0x660d1e89,0x68e0817b}}, // _nl_, _aliy, _saak, rumd,
+ {{0x764406c0,0x2a7f822c,0x049381a8,0x7bca81f6}}, // _bliy, blub_, _اللح, mafu,
+ {{0x69c66754,0x7bc5010b,0xdb08816d,0x7bcae690}}, // _neke, _sehu, _bedö, lafu,
+ {{0x444454e6,0x60dbba21,0x629d6755,0x3c398205}}, // _bl_, crum, _riso, _sèvi_,
+ {{0x7bcaac91,0x660d26e8,0x76446756,0x9f5e80e7}}, // nafu, _waak, _eliy, heté_,
+ {{0x69c66757,0x629d2eaa,0x43950c6c,0xe2f810ac}}, // _beke, _piso, равс, лері_,
+ {{0x44446758,0xf127025d,0x200ee759,0x78a610d3}}, // _el_, льзо, _hafi_, _bukv,
+ {{0xd49b0765,0x44442f1e,0x7bcaae5e,0x69c6675a}}, // [69f0] _про_, _fl_, kafu, _deke,
+ {{0x4444675b,0x60cd07d5,0xee3f001b,0x629d018e}}, // _gl_, rsam, lký_, _wiso,
+ {{0x200e80a4,0x69c61464,0x629d463a,0xa2a48d86}}, // _mafi_, _feke, _tiso, _चूल्,
+ {{0x69c60574,0x2001059e,0x60cd675c,0xb5fb01d0}}, // _geke, sehi_, psam, znán,
+ {{0x1d098d31,0xa5099017,0xb05b0338,0x7bcae75d}}, // тели_, тела_, _dvär, fafu,
+ {{0x69c60613,0x7bcaaaf6,0xab5c80eb,0x200e80ee}}, // _zeke, gafu, _ceļo, _nafi_,
+ {{0xb5fb001b,0x645700b9,0x249e8176,0x290b0580}}, // vnán, _mmxi, _ditm_, _hsca_,
+ {{0xa3e78665,0xd5f98158,0x6eaa064a,0xe8f988ed}}, // _भाव_, _שפּר, _जंतु, вли_,
+ {{0x7a3c80e7,0xdb1e02b7,0x681b00eb,0x7bcac67d}}, // _méth, _repí, _tādē, bafu,
+ {{0xb4c18076,0xb8fd800f,0x60dbc929,0x200eb631}}, // ंठी_, _तट_, rrum, _cafi_,
+ {{0xb5fb02ba,0x3f6982dc,0x69cf8333,0x200e9325}}, // rnán, тино_, óces, _dafi_,
+ {{0x44210104,0x7ae3b946,0x64451a9b,0x60db9bb3}}, // _anh_, munt, _alhi, prum,
+ {{0x4444675e,0x69c61543,0x64a61330,0x44330039}}, // _pl_, _reke, рага, _box_,
+ {{0x7e6982fd,0x69c6675f,0x998480f7,0x93460d46}}, // _ljep, _seke, _الهو, анге,
+ {{0x69c627ba,0x7e7b85e7,0x42560328,0x7ae3e760}}, // _peke, _okup, атат, nunt,
+ {{0xee3f0a56,0xc2430af2,0x76440a6c,0x200e8041}}, // cký_, онск, _uliy, _zafi_,
+ {{0x44446761,0xe2972262,0x2fc7e762,0x61ebcb77}}, // [6a00] _tl_, рат_, _keng_, _mcgl,
+ {{0x44441717,0x3ea78613,0x7ae39413,0x69c60a0f}}, // _ul_, _kunt_, kunt, _weke,
+ {{0xeaf4853e,0x69c606dc,0x645c4b40,0x7ae3aeaa}}, // _आहेत_, _teke, miri, junt,
+ {{0x645c5e7e,0x320f8065,0x7e6994ce,0x13a8026a}}, // liri, _nagy_, _cjep, _جنگی_,
+ {{0x7bcad542,0xcfaa006b,0x44330085,0x9f5e8036}}, // tafu, _لازم_, _yox_, reté_,
+ {{0x645c1efb,0x2fc78077,0x67210699,0x394d80eb}}, // niri, _neng_, _šlje, dzes_,
+ {{0x7ae3d938,0xa3e78054,0x68e42805,0xb05b016d}}, // gunt, _भार_, luid, _tvär,
+ {{0x7a380510,0x645c4b2e,0xfb198065,0x200eba5c}}, // _títu, hiri, اروں_, _safi_,
+ {{0x645c4b40,0xe8df0104,0xfbc604e5,0x68e41493}}, // kiri, _việc_, वराम, nuid,
+ {{0x645c6763,0x7ae3e764,0x7a3c82be,0x249e83ac}}, // jiri, bunt, _méti, _uitm_,
+ {{0x645c6765,0x2fc7e766,0xcb679c79,0x2d8000ee}}, // diri, _deng_, _кафе_, _ayie_,
+ {{0x8c3c87d9,0xe7ea9513,0x60c2bf1e,0xd6d80264}}, // _diğe, ज्जा_, _twom, _সহায,
+ {{0x7bc89a67,0x645c0b3f,0x443300b9,0x261c05fc}}, // _kedu, firi, _pox_, याजी_,
+ {{0x645c5ab6,0xee3f0400,0xe0fb8039,0x2fc7e767}}, // giri, ský_, _סלול, _geng_,
+ {{0x224903f8,0xeaf4816f,0x224685e6,0x31c7157b}}, // thak_, _आहोत_, _klok_, исав,
+ {{0x645c00f7,0x7bc897cf,0x3ea002c4,0x3e72016b}}, // airi, _ledu, _giit_, káty_,
+ {{0xe29f1c86,0x645c29ef,0x765d3a7f,0x22490057}}, // [6a10] _við_, biri, misy, rhak_,
+ {{0x7e7b836f,0x2fc70104,0x765d6768,0x645c585b}}, // _skup, úng_, lisy, ciri,
+ {{0x261c1880,0x7ae381df,0x2246978f,0x81de80ab}}, // याची_, xunt, _olok_, দ্য_,
+ {{0x32048063,0x765d0247,0x68e4435e,0x7d0d0326}}, // jemy_, nisy, buid, _ksas,
+ {{0xb4c18076,0x7bc8e769,0x38c9004e,0x68e40333}}, // ंठे_, _bedu, _حاجی_, cuid,
+ {{0x7ae3e76a,0xfaa582cb,0x2246ac11,0x1cb80039}}, // tunt, бако, _alok_, סלול_,
+ {{0x25e8035a,0x8b948098,0xdb15016a,0x2ca902c4}}, // _झाली_, оръч, mazó, _luad_,
+ {{0x7ae3e76b,0x7e7b80fa,0x3ea780f2,0x2fc7e060}}, // runt, _ukup, _runt_, _seng_,
+ {{0x3ea78c6e,0x320f8117,0x20111b74,0x26de9965}}, // _sunt_, _vagy_, _kazi_, erto_,
+ {{0x3ea7e76c,0x7bc8e76d,0x22468ad4,0x3ea00074}}, // _punt_, _gedu, _elok_, _siit_,
+ {{0x394d8065,0x2246e76e,0x2011676f,0xa3ac923a}}, // szes_, _flok_, _mazi_, _गलत_,
+ {{0x2909386c,0x7bd704c3,0x7d1b9db4,0x2ca90239}}, // kwaa_, _adxu, _krus, _buad_,
+ {{0x645c6359,0xba748013,0xa8a7a856,0x2fc795d7}}, // tiri, _بالت, _трек, _teng_,
+ {{0x61e98b11,0x81ae80ab,0xaf49845b,0x3dd804b7}}, // lgel, করণ_, _مشعل_, _żewġ_,
+ {{0x2005a733,0x7d0d0d6a,0x765d2588,0x28ddbffa}}, // meli_, _esas, bisy, _मिडि,
+ {{0x61e98ded,0x645c1253,0x68e426d5,0x2005e770}}, // ngel, siri, tuid, leli_,
+ {{0x645c6771,0x201143fe,0x6e246772,0xdb170118}}, // [6a20] piri, _bazi_, _inib, _xexú,
+ {{0xdb1507f4,0x66062e1d,0x68e45a4a,0x2240067f}}, // gazó, lekk, ruid, skik_,
+ {{0x7afe23fe,0x7d1be773,0xb5fb016b,0x61f63860}}, // _uppt, _arus, znám, pfyl,
+ {{0x7bc8e774,0x7bce6775,0x80d893f5,0x7a3ce776}}, // _sedu, labu, _मिले, _hétv,
+ {{0x7bc8c45d,0x2005cebe,0x644a80f7,0x61e981b4}}, // _pedu, keli_, thfi, dgel,
+ {{0x200582ce,0x7bce4674,0x765d142a,0x3e72027f}}, // jeli_, nabu, zisy, ráty_,
+ {{0x6e246777,0x753d009a,0x61e9e778,0x66e30a42}}, // _onib, zysz, fgel, _нора,
+ {{0x2618057a,0x61e9bb39,0x9f58047f,0x7bce6016}}, // _बोली_, ggel, _farò_, habu,
+ {{0x7bce1dde,0x765d4463,0x7bc88359,0x7d1be779}}, // kabu, visy, _tedu, _grus,
+ {{0x6b81b6bd,0x2005dafe,0x7bce005d,0x27178129}}, // _fylg, geli_, jabu, _băn_,
+ {{0x43868064,0x907b012a,0x765d0247,0x28dde5fa}}, // _الاق, רטיי, tisy, _मिथि,
+ {{0xdb1a8198,0x2ca900b9,0xf99f010c,0x3f8281a9}}, // _vetä, _puad_, _kaèt_, āku_,
+ {{0xb9968013,0x765d60f6,0x91e382c7,0xdb1e002a}}, // _الشب, risy, _хоче, _repá,
+ {{0x765d10c1,0x26de8353,0x9f5ee77a,0x2005e77b}}, // sisy, prto_, letí_, celi_,
+ {{0x765d5123,0xa3d0aeff,0x66060357,0x9f4ca4a3}}, // pisy, _वजन_, bekk, redó_,
+ {{0x62840698,0x2aa8148d,0x2909004f,0x7f8381a8}}, // glio, стро_, twaa_, _علين,
+ {{0x51871507,0x38351afa,0x7bce467d,0x621b83de}}, // [6a30] сува, онар, babu, נומק,
+ {{0x6aa28706,0x7d1b83ed,0x9998002e,0x81ae8264}}, // _liof, _rrus, скут_, করি_,
+ {{0xdb153bb4,0x7d1b8042,0xdcfb816b,0x9f58047f}}, // razó, _srus, _vyuč, _sarò_,
+ {{0x2005b327,0x6448811e,0x20112486,0xceb380be}}, // zeli_, _aldi, _wazi_, ליע_,
+ {{0x2005805d,0x7ae71b3b,0x7e6d405e,0x61e9826c}}, // yeli_, lujt, _mjap, vgel,
+ {{0x42d98750,0x987900be,0x26c28390,0x20059598}}, // афия_, _כאָט, ćkoj_, xeli_,
+ {{0x61e98c72,0xa3b886a7,0xfd4a18b2,0xa2299cce}}, // tgel, चड़_, _bulọ, ижка_,
+ {{0xe0d980ae,0x8c0980c8,0x2005a486,0x6448df39}}, // ави_, লাইন_, weli_, _eldi,
+ {{0x61e9e77c,0x2d99c033,0x7bce2290,0xdb18816b}}, // rgel, _àse_, yabu, mavý,
+ {{0x7ae72009,0x61e994fb,0x6e36151e,0xd7660065}}, // kujt, sgel, _soyb, _انہی,
+ {{0x81de80c8,0x61e99989,0x6443809a,0x61fd0831}}, // দ্ধ_, pgel, mkni, _obsl,
+ {{0x27178104,0x4425973d,0x64438082,0x7ae7026f}}, // _văn_, _hnl_, lkni, dujt,
+ {{0xa5bb0125,0x7bce0542,0x6606401c,0xe8ee8b69}}, // tjór, tabu, rekk, _юл_,
+ {{0xf7459814,0x64438bbd,0x2d8d8010,0x660627d6}}, // _мело, nkni, _mzee_, sekk,
+ {{0xa3e7901b,0x7bce4cb3,0x69cb8057,0x4911800d}}, // _भाई_, rabu, _kege, तिको_,
+ {{0x6e245fda,0x7bce2025,0xa2cc80d4,0x69cb8eb9}}, // _unib, sabu, _सबस्, _jege,
+ {{0x69cbe1ad,0x7bce242d,0xb33c822b,0x6284677d}}, // [6a40] _mege, pabu, _agħl, rlio,
+ {{0x61ef1ac6,0x69cbca2b,0xb90203ca,0x6721011f}}, // _eccl, _lege, _धि_, _šlja,
+ {{0xeda682de,0x62842dee,0x1eda00f7,0xdb0500ff}}, // ошло, plio, شباب_, _nghè,
+ {{0x69cbe77e,0x5efa8158,0x0d86c197,0x5bb4b54d}}, // _nege, יפעד, олен, ुर्व,
+ {{0xc27b0051,0x764280dd,0x2c0f016f,0xb33c8197}}, // _כרטי, rkoy, ायचं_, _egħl,
+ {{0x764984be,0x2bbf1a46,0x8cdb873c,0x442587f1}}, // _eley, ्रया, _निरो, _cnl_,
+ {{0x68f60a53,0x2d8d85ee,0x7649e77f,0x290f82ed}}, // ltyd, _ezee_, _fley, _asga_,
+ {{0x7649807b,0xb95b0091,0x26d88298,0x4425d8e5}}, // _gley, _adìy, árom_, _enl_,
+ {{0xed5a8003,0x69cbe780,0x2bbf0d14,0x661d807b}}, // _тоа_, _dege, ्रमा, _óska,
+ {{0x644380f2,0xdced080a,0x290f8037,0x291d8176}}, // ckni, _ayağ, _dsga_, _drwa_,
+ {{0xc7b30051,0x3eb88168,0x41aa1b7e,0xd7f88162}}, // ובת_, ërt_, कशास, rfă_,
+ {{0x69cb8c1b,0x8b65ca1c,0xdb1f2320,0x78a389e7}}, // _gege, _عالم, ðvík, _einv,
+ {{0xb5fb00f7,0xa06a84ae,0x225787d2,0xb86582e3}}, // nnái, _када_, _بلند_, _کانو,
+ {{0x7ae7230a,0x61ef0122,0x69cbe781,0xaaa986a7}}, // tujt, _sccl, _zege, _चूँक,
+ {{0x3a268cab,0xd25b0a9f,0xb5fb0061,0x69cb8314}}, // _knop_, аца_, riáb, _yege,
+ {{0x7ae76782,0x42db80be,0xdb1a8144,0x78a3890d}}, // rujt, יקלע, _betú, _zinv,
+ {{0x37ab1182,0x25a90074,0x7ae742e4,0xc69400be}}, // [6a50] атен_, _igal_, sujt, _קאפ_,
+ {{0x7ae7136f,0x7a3c8061,0xb33c822b,0x7bde8144}}, // pujt, _kéts, _agħm, ópul,
+ {{0x7649e783,0xaaa98105,0x64800009,0xb5fb3fe5}}, // _pley, _चूंक, röit, ciác,
+ {{0x9f58007b,0x8f9b8039,0x2d9984e8,0xdb05027d}}, // _varð_, ביבי, _úsek_, _nghé,
+ {{0x69cb87b3,0x5184012f,0x44258087,0xc984002e}}, // _rege, _жура, _pnl_, _жури,
+ {{0x69cb89ca,0x61ed6784,0x7e7d6785,0x78ab80c3}}, // _sege, lgal, hosp, _rugv,
+ {{0x64438bfa,0x81ce00c8,0xfe7392dc,0x224dc8c7}}, // rkni, _রাত_, _قدر_, thek_,
+ {{0x765b82a0,0x6443cf3a,0xefc8001c,0x00c98e8e}}, // _umuy, skni, _đỉnh_, блик_,
+ {{0x7e7d5fac,0x7a3c8125,0x225fc97f,0xddc4017f}}, // dosp, _rétt, riuk_, _njiš,
+ {{0xa2dc123a,0x69cb9cb6,0x25a9002a,0x25b6175d}}, // _फिल्, _wege, _agal_, ूर्ण,
+ {{0x69cbe786,0x2cad8609,0x224da00b,0x2019928a}}, // _tege, _hued_, phek_, ěsi_,
+ {{0x3ea400f2,0x63b5009a,0xaca3801c,0x291d8035}}, // ömt_, eczn, _trồn, _trwa_,
+ {{0x61ed6787,0x78a382a6,0xe16781a8,0xed598bcf}}, // dgal, _tinv, _بداي, _težu_,
+ {{0x25a96788,0xe1ff0091,0xd47980be,0x78a3808e}}, // _egal_, _abó_, מאָל, _uinv,
+ {{0xd00e8307,0x68e98c0b,0x8b2327a9,0x7e6b82d5}}, // _إلى_, lued, ндре, kngp,
+ {{0xcb12893f,0x61ed3722,0x9f96807c,0x57568039}}, // עלן_, ggal, _צדקה_, _מבצע_,
+ {{0xdb1c0e15,0x80a62b7e,0x9abc82a6,0xb5fb15d8}}, // [6a60] mbrí, سمان, _irċe, siác,
+ {{0x61ed0365,0x6f0388ae,0x3ea4e789,0x68f61a23}}, // agal, _kpnc, _simt_, rtyd,
+ {{0x645e4b67,0x443a3fd2,0xe4570158,0x8cb20540}}, // _impi, _iop_, _זייט_, _इंडो,
+ {{0xe1ff077f,0x7a3c8019,0x3a390639,0x2d848661}}, // _gbó_, _létr, _gosp_, _pyme_,
+ {{0x69c41ed4,0x2ca58cb4,0x443a3ccd,0xdb1c00f7}}, // mbie, _bild_, _kop_, ibrí,
+ {{0x69c4678a,0xb5fb00f7,0x7d040c2e,0xe81b82f1}}, // lbie, rnái, _mpis, _भोला_,
+ {{0x443a02ee,0x6ada06a7,0x60230071,0xaab89190}}, // _mop_, _बटोर, _ядра, زگار_,
+ {{0x7d04678b,0x68e407a3,0x69c433fc,0x443a0129}}, // _opis, orid, nbie, _lop_,
+ {{0x63b50063,0xfce39232,0x588681e5,0x68e98114}}, // yczn, _пото, зына, gued,
+ {{0x645d8110,0x7e6285fe,0x68e41581,0xbc639033}}, // _įsig, liop, irid, _звук,
+ {{0x68e4678c,0x7a3c80e7,0xa2b1462c,0xb914819d}}, // hrid, _détr, _आंध्, _yadị_,
+ {{0x645e22ac,0x648d80f7,0x68e43a4c,0x69c4051c}}, // _ampi, núin, krid, jbie,
+ {{0x6d42e78d,0x7aea93c2,0x69c4678e,0x51839bdc}}, // nyoa, luft, dbie, _пуша,
+ {{0x2ca51de9,0x7e62e78f,0x69c43e5b,0x9f580216}}, // öld_, hiop, ebie, _abrí_,
+ {{0xb5fb003e,0x1dcc8b84,0x61ed1482,0xd5cc9b7e}}, // dnáv, ारात, tgal, ाराज,
+ {{0x81de80c8,0x7c3ad400,0x61ed6790,0x62340073}}, // দ্র_, _lotr, ugal, несу,
+ {{0x61ed6791,0x68e46792,0x7c2884e8,0x3eae82f7}}, // [6a70] rgal, grid, _ondr, _huft_,
+ {{0x7c3ae793,0x37ab1537,0x61ed110a,0x3014002e}}, // _notr, стан_, sgal, ндур,
+ {{0x69c424c5,0x442b801c,0x68e402f1,0x212b8129}}, // bbie, _đc_, arid, ̣ch_,
+ {{0x7c288082,0x28dd946d,0x7c3a8229,0xf2d200be}}, // _andr, _मिलि, _aotr, _בעט_,
+ {{0x2ab187a3,0x26d8609d,0xa3b1131d,0x1c22009a}}, // _sáb_, _evro_, _ओला_, माचल_,
+ {{0x6282e794,0x7c3a8493,0xd7f81860,0x05748180}}, // _akoo, _cotr, _нус_, راند,
+ {{0x69cf059c,0xfbd080f7,0x764d2192,0x2ca5806a}}, // _kece, اته_, _olay, _vild_,
+ {{0x7c28a40f,0x7e628706,0x69cf0079,0x2fce8176}}, // _endr, ciop, _jece, _aefg_,
+ {{0x61fba3ea,0x2cad8074,0x7a3cb4d3,0x7c3ad913}}, // rful, _uued_, _pétr, _fotr,
+ {{0x09aa1344,0x764d6795,0xddc280fe,0xefc80104}}, // _कल्य, _alay, mnoš, _định_,
+ {{0x69dd1024,0x7d04054f,0x6e3bc741,0x443a6796}}, // _odse, _spis, _koub, _rop_,
+ {{0x69cf2c04,0xb5fb026f,0x2b4f8214,0x443a584a}}, // _nece, znáv, ımcı_, _sop_,
+ {{0x205595e0,0x68e9a34a,0x6e3b8bb1,0x629b80e5}}, // _откр, qued, _moub, mmuo,
+ {{0x69dd0358,0x7d040353,0x6e3be797,0x6289e798}}, // _adse, _vpis, _loub, lleo,
+ {{0xdca30f2f,0x7d040063,0x64a30a8e,0xddcb005c}}, // тари, _wpis, тара, žišn,
+ {{0x7ae10065,0xd0480086,0x69cf6799,0x2a6dd877}}, // álta, ələr, _cece, nneb_,
+ {{0x443a0add,0x7d04003a,0x78a70a0f,0xda6515a9}}, // [6a80] _top_, _upis, _bijv, رافي,
+ {{0xb8f64a4d,0xddc2805c,0x69c4679a,0x645e3d44}}, // _सब_, dnoš, sbie, _umpi,
+ {{0x6e3b9c7d,0x6289e79b,0x69cf5d5d,0xe61ab7cd}}, // _boub, kleo, _fece, жде_,
+ {{0xa3d0823c,0x69cf679c,0x7c288359,0x93ee0bb8}}, // _वजह_, _gece, _sndr, _जाँच_,
+ {{0x6da3a482,0xc8798380,0xb33c8372,0xe5a39092}}, // вица, luşu_, _igħi, вици,
+ {{0x6282879f,0xf1bf8e15,0x7e629ae3,0xdb07251d}}, // _skoo, ñán_, siop, rbjö,
+ {{0x7c3a86e3,0x161985b3,0x6289831d,0xdb1c016a}}, // _votr, _नोकर_, fleo, maró,
+ {{0x2018672e,0x37e300ab,0x41e68221,0xb33c84b7}}, // _hari_, ন্দর, фіка, _jgħi,
+ {{0x93ee023c,0x95839401,0x3f878079,0x6282808e}}, // _जांच_, _алте, _aynu_, _vkoo,
+ {{0x201833d2,0x7c288bcb,0x47348fe7,0xdb1c01a8}}, // _jari_, _undr, енос, naró,
+ {{0x764d279a,0x2bbf04c5,0x26d88866,0x9b468c3b}}, // _play, ्रसा, éro_, _تناو,
+ {{0x2018679d,0x81e400ab,0x21298267,0xbddb00e7}}, // _lari_, প্ত_, _šahe_, thèq,
+ {{0xdc3a02bb,0x6280dc0d,0x981380d7,0x25ad07dd}}, // _açıl, momo, ربیا, želo_,
+ {{0x6280bc9b,0x2018130e,0xb5fb016a,0x28b5064a}}, // lomo, _nari_, cián, _अंबि,
+ {{0x200cc09e,0x69cf3bfe,0xd6cf98ed,0x8aa7373a}}, // medi_, _pece, _шт_, драд,
+ {{0x764d0010,0x1da71834,0xdb1200f7,0x2018020d}}, // _ulay, _गणपत, ógái, _aari_,
+ {{0x20180f69,0x69cf158d,0x6e3b89b2,0xb33c8197}}, // [6a90] _bari_, _vece, _roub, _dgħi,
+ {{0x20180393,0x6e3b800d,0xdfd10013,0x26000006}}, // _cari_, _soub, ايا_, ष्ठी_,
+ {{0x20181dae,0x628086a0,0x3ea954d2,0xb8d6199e}}, // _dari_, komo, _kiat_, _चू_,
+ {{0xb05b1bc0,0xb93500a9,0x69dd006a,0x2bbf0aed}}, // _städ, _земј, _udse, ्रवा,
+ {{0x6280ce32,0x20180ba0,0x3ce6e79e,0xe3a51190}}, // domo, _fari_, grov_, _تشکی,
+ {{0x20184508,0x200c82fd,0x3ea90877,0xf8bf679f}}, // _gari_, jedi_, _liat_, _crée_,
+ {{0x27f8026f,0xe784432b,0x6e3b9f1b,0x68ed6478}}, // žený_, _ауто, _toub, luad,
+ {{0xe9d99285,0x3ea9059c,0x62808110,0x201800b4}}, // чки_, _niat_, gomo, _zari_,
+ {{0x20180578,0x5c75860a,0x9f5e8009,0x629b8110}}, // _yari_, _злат, hetä_, rmuo,
+ {{0x1bee05b3,0xc05200be,0xb5fb016b,0x271e8609}}, // _जाइल_, ָזט_, znát, _bċn_,
+ {{0x6289e7a0,0xb5fb0c83,0x22b867a1,0x7bd567a2}}, // pleo, rián, _očka_, dazu,
+ {{0xf772010f,0xf6521a00,0x248582c4,0xee37373a}}, // רקט_, ائد_, _hklm_, еню_,
+ {{0x61d68039,0x200cb275,0xe1ff007b,0xb7d58032}}, // _נוסף_, bedi_, _ljós_, _aṣer,
+ {{0x68ed26d5,0x200ce7a3,0x6f150035,0x26dc01d6}}, // duad, cedi_, _oszc, ávok_,
+ {{0xf8bf38a7,0xa3c2823c,0x20184792,0xaca3819d}}, // _até_, ूरत_, _rari_, _apịn,
+ {{0xc3328bea,0x2cbf80d2,0xf2d280be,0x629c8037}}, // נון_, _otud_, טעל_, _èrot,
+ {{0x2018010a,0x66040a6d,0x26d881ac,0x68ed2eaa}}, // [6aa0] _pari_, _ibik, árov_, guad,
+ {{0x6d465c1f,0xb4fb0051,0x7e661a92,0x201867a4}}, // myka, _צפיי, likp, _qari_,
+ {{0x20180a45,0x6d460190,0xb33c81b9,0x75240088}}, // _vari_, lyka, _tgħi, _hriz,
+ {{0x3ce6dcb3,0xee3ab10c,0x201802a0,0x6723817f}}, // trov_, яне_, _wari_, _mrnj,
+ {{0x7c3e0988,0x68ed02ba,0x6280e7a5,0xb5fb67a6}}, // _kopr, cuad, vomo, onár,
+ {{0x28b51094,0x7c3e00eb,0x60c41269,0x2bfb81fe}}, // _अंति, _jopr, mpim, ल्लू_,
+ {{0x6fd6823c,0x68fbe516,0x6280cdd1,0xe8e000ff}}, // _मजबू, etud, tomo, _ruột_,
+ {{0xd257d67f,0xdff00b85,0x69d62b82,0x98c68fbf}}, // ець_, _चाँद_, naye, нсол,
+ {{0x68fbbb1d,0x50669597,0xc8668ff7,0x998004b7}}, // gtud, етна, етни, jjiż_,
+ {{0x6280bd8b,0x66040121,0x672380d2,0x3ea967a7}}, // somo, _abik, _brnj, _siat_,
+ {{0x442c8ae1,0x200cb9d0,0x69d667a8,0x6280e7a9}}, // _ind_, redi_, kaye, pomo,
+ {{0xb5fb03b0,0x2120003a,0x200c9a4d,0x443e8039}}, // ciál, kvih_, sedi_, _hot_,
+ {{0x443ea52d,0x200cc154,0x38b600b2,0x5c070098}}, // _kot_, pedi_, _nær_, нява,
+ {{0x62862231,0x31578158,0xddc9920e,0x7c3e24eb}}, // _akko, _אידן_, _mješ, _copr,
+ {{0x443e919b,0x7c3e267b,0x7bd543fe,0xc4c48117}}, // _mot_, _dopr, razu, _گے_,
+ {{0x69d637fc,0x98a7026c,0x443e824d,0xdb1a8168}}, // gaye, _điđa_, _lot_, _aftë,
+ {{0x60f91006,0x442c8355,0x660f003e,0x6ca71980}}, // [6ab0] ьная_, _ond_, meck, _прож,
+ {{0x443e804c,0x28e20076,0x661d67aa,0x660f4a65}}, // _not_, _पिरि, ldsk, leck,
+ {{0xbbbf0bb8,0x68ed05ee,0x69d667ab,0xe8020006}}, // ्रीक, suad, baye, _रउरा_,
+ {{0x442ce7ac,0x661d2280,0x660f026f,0x38b667ad}}, // _and_, ndsk, neck, _fær_,
+ {{0xed588cde,0x68ed67ae,0x442c82f7,0x38b6007b}}, // ної_, quad, _bnd_, _gær_,
+ {{0xe52267af,0x442ce7b0,0x7e66026c,0x660f4538}}, // मिति_, _cnd_, zikp, heck,
+ {{0x998d8bcf,0x6f150035,0xa7fc811c,0x443eca2b}}, // dješ_, _uszc, hnıs, _dot_,
+ {{0x442c8039,0x68fba131,0x78aa8037,0x661d184e}}, // _end_, ttud, _aifv, jdsk,
+ {{0xb5fb1c18,0x443ee7b1,0x660f1d4e,0x442c8362}}, // riál, _fot_, deck, _fnd_,
+ {{0x6e2d67b2,0x661be7b3,0x68fbe7b4,0x7d09e7b5}}, // _anab, _hauk, rtud, _ipes,
+ {{0xdddb801b,0x661b8f52,0xaca30133,0xdb1c007b}}, // _zkuš, _kauk, _chọg, jarð,
+ {{0x7c3e67b6,0x2f548e86,0x6723e7b7,0x661b8612}}, // _sopr, утьс, _vrnj, _jauk,
+ {{0x69c99a25,0x0d990fe6,0x443e8176,0x9599143d}}, // mbee, етры_, _yot_, етру_,
+ {{0x661ba57b,0xe73184c0,0x69c98079,0x69d61c38}}, // _lauk, _نصب_, lbee, waye,
+ {{0xe739835f,0x38b627d1,0xdb1c008b,0xaca3019d}}, // _цей_, _sær_, garð, _ghọg,
+ {{0x26c5a6d5,0x661be7b8,0x69c9b29a,0x628d67b9}}, // mplo_, _nauk, nbee, glao,
+ {{0x69d63670,0xa3c1886a,0x7aee2f70,0x7c3e0214}}, // [6ac0] raye, ंडा_, subt, _topr,
+ {{0xddc982fd,0x0b460002,0x69d667ba,0x38b60b40}}, // _rješ, _анон, saye, _vær_,
+ {{0x7d0983a7,0x68e9831d,0x443ee7bb,0xdddb8796}}, // _apes, hred, _rot_, _skuš,
+ {{0x443ee7bc,0x38b667bd,0xddc9920e,0x60db87b8}}, // _sot_, _tær_, _pješ, ksum,
+ {{0x661bafd5,0x443e9603,0x9cb380f7,0x68e9e7be}}, // _dauk, _pot_, لمنت, jred,
+ {{0xddc9920e,0x8d5b0039,0xddc2816b,0x7d090315}}, // _vješ, וכני, dnoť, çesi,
+ {{0x443eba5d,0xdb1a8c52,0x442c801c,0x68e9e7bf}}, // _vot_, _retó, _vnd_, ered,
+ {{0x68e9a9dc,0x69c9d039,0xddc98d26,0xf2d301c6}}, // fred, gbee, _tješ, בעה_,
+ {{0x660f67c0,0x6fd6823c,0x6da39a47,0xd0108013}}, // veck, _मजदू, _вита, ملة_,
+ {{0x442ce7c1,0x6e2d04b8,0x67210024,0x7054804e}}, // _und_, _snab, _šlji, اندا,
+ {{0x660f1ee0,0x1df901bb,0x7640c500,0x7bd8e7c2}}, // teck, нены_, _komy, mavu,
+ {{0xe80217a3,0x26f2800f,0x68e993c2,0xc8e200d4}}, // र्ता_, _अमीर_, bred, _पिंट,
+ {{0x660f5c29,0x2cac81e0,0x3dd780ab,0xb884001b}}, // reck, _kidd_, _হামল, _zvíř,
+ {{0x7bd8c71c,0x1dc318b1,0x26d88061,0x60cd82d0}}, // navu, _व्रत, áros_, _çama,
+ {{0xe5c71c82,0x07f789d7,0x7e64008e,0xa3d4016f}}, // _испо, _شروع_, _fmip, _हजर_,
+ {{0x35f50b87,0x2c6d026c,0x201c8061,0xf8bf1ab3}}, // _спир, džde_, _havi_, _aréa_,
+ {{0x51871507,0x7bd8e7c3,0x201ca7b1,0x661b8a41}}, // [6ad0] тува, kavu, _kavi_, _rauk,
+ {{0x201c80ce,0x661bd001,0xdbcd007b,0x75228259}}, // _javi_, _sauk, _góðu, dvoz,
+ {{0x7d09e7c4,0x61e29ee0,0x661ba63b,0x97a75812}}, // _spes, _odol, _pauk, трал,
+ {{0x60c29a9f,0x201c8247,0x7bca81c0,0x61e2b1ca}}, // _otom, _lavi_, ebfu, _ndol,
+ {{0xf1bf077f,0x66d90032,0x752280c3,0x76408f39}}, // _ifá_, _bùká, gvoz, _domy,
+ {{0x68e98289,0x61e2e7c5,0x291904b7,0x7bd8c722}}, // vred, _adol, _issa_, gavu,
+ {{0x60c2e7c6,0x9f3400e8,0x8c428ada,0x661bb014}}, // _atom, _кері, сеще, _tauk,
+ {{0x60db92a5,0x2126808e,0x76408176,0xe81b82f1}}, // tsum, _broh_, _gomy, _भोजा_,
+ {{0x69c9bade,0x201c803b,0x39491400,0x7fd58a4c}}, // rbee, _bavi_, nyas_, ліні,
+ {{0xf4848d70,0x0495803f,0x2a6901e9,0x201c847f}}, // _кухн, _صلاح, hiab_, _cavi_,
+ {{0x68e9935a,0x60dbe02b,0x201c82df,0x212689c4}}, // sred, ssum, _davi_, _eroh_,
+ {{0x29195819,0x6441b437,0x3949512d,0x21268192}}, // _ossa_, _holi, kyas_, _froh_,
+ {{0x3ead822b,0x28dd9130,0x62581b88,0x49dabd9b}}, // _miet_, _मिटि, _híog, _سانگ_,
+ {{0xfbe580c8,0x64418866,0x3eadaa13,0xfba5036d}}, // প্রত, _joli, _liet_, _गृहम,
+ {{0x91e60ba5,0x09e62964,0x29193194,0x3ce90072}}, // лове, ловн, _assa_, _जिथे_,
+ {{0x3ead9dbe,0x42551519,0x6441da17,0x7bd8b77f}}, // _niet_, атут, _loli, zavu,
+ {{0x0e9b0039,0x7bd8abd7,0x2d8d819d,0x9e650e06}}, // [6ae0] _אשקל, yavu, _nyee_, _خاون,
+ {{0x6441e7c7,0x69cb008b,0x201ce7c8,0x25bf8326}}, // _noli, ðger, _xavi_, _aful_,
+ {{0x291918a0,0x93fb00be,0x7640809a,0x224d8019}}, // _essa_, _אליי, _pomy, kkek_,
+ {{0x201c011f,0xd01c80ab,0x64418326,0xdb1c05ec}}, // žvi_, তায়_, _aoli, rbrä,
+ {{0x6441928d,0x6602ad27,0x044690ca,0xa0760198}}, // _boli, rfok, ледн, _бывш,
+ {{0x224d808e,0x61f60114,0x29001c40,0x644e67c9}}, // ekek_, lgyl, mtia_, nkbi,
+ {{0x7bd8e7ca,0x3d1609c8,0x29000722,0x9696e7cb}}, // ravu, _पैसे_, ltia_, _браш,
+ {{0x3eada70c,0x77910416,0x6441b648,0x998907c0}}, // _giet_, _بیما, _eoli, ndaş_,
+ {{0x290027f1,0xa2bb0f0f,0x286a95e0,0xd7fac3e1}}, // ntia_, _शून्, ерно_, нун_,
+ {{0x0d759006,0x3ead8a0f,0x29005df6,0x64a68791}}, // _тыся, _ziet_, itia_, _саза,
+ {{0xa3c181fe,0x29000198,0xde5900e8,0x201c867f}}, // ंडर_, htia_, вані_, _vavi_,
+ {{0x22421786,0x2a69022c,0xe7e0801b,0x644e0299}}, // _nokk_, xiab_, _गएका_, ekbi,
+ {{0x60c2e7cc,0x80b002f1,0xf992bb76,0x201ce7cd}}, // _utom, _अंगे, _ابا_, _tavi_,
+ {{0x2a6681c0,0x6441ca4e,0x6458e7ce,0x29190f3e}}, // _hmob_, _xoli, nhvi, _rssa_,
+ {{0x56950698,0xce950098,0x7ac4004a,0x2d840192}}, // _какт, _какъ, йсте, ümer_,
+ {{0x1fdd00ba,0x7ae85138,0xea019029,0x26dee7cf}}, // मण्ड, ádta, _đẫy_, msto_,
+ {{0x52d18b9f,0x3f980267,0x19580a14,0x3eadd5c8}}, // [6af0] _सब्स, _azru_, гаты_, _riet_,
+ {{0x7d0d3162,0x2a6901c5,0x26dec43e,0x3eadb4cd}}, // _ipas, siab_, osto_, _siet_,
+ {{0x64418aa8,0xdb1e0510,0x3ead8cfa,0x290067d0}}, // _roli, _depó, _piet_, atia_,
+ {{0x2bbf0778,0x6441af96,0x51870992,0x7d0d01bc}}, // ्रका, _soli, _буга, _kpas,
+ {{0xa3c1800f,0x2900002e,0x3ead801c,0x62580473}}, // ंडल_, ctia_, _viet_, _díod,
+ {{0x26deccc5,0x80c380c8,0x68ed01e4,0x6721192c}}, // ksto_, _শিল্, mrad, _šlju,
+ {{0x64418b5d,0x7bd70118,0x68ed67d1,0x26deb1a7}}, // _voli, _mexu, lrad, jsto_,
+ {{0xc245a6b1,0x7bd74d5a,0x69cd0683,0x68ed0c41}}, // рник, _lexu, nbae, orad,
+ {{0x68ed34e7,0x6441e7d2,0xe0d68a7f,0x26dea536}}, // nrad, _toli, авы_, esto_,
+ {{0x7d1be7d3,0x7e6b83ba,0xaf059bcc,0x6fe001d0}}, // _isus, ligp, апол, mácí,
+ {{0x68ed0775,0xd37180f7,0x7d0d2aa4,0xfb15826a}}, // hrad, _بها_, _apas, _خواج,
+ {{0x68ed1e1d,0xd13180f7,0x7529b8e9,0x76598114}}, // krad, _وما_, _krez, nhwy,
+ {{0x68ed246d,0x463a83de,0x528481a8,0xb0640799}}, // jrad, דערע, _النك, nmäß,
+ {{0x8fa5a29c,0xb05b29ed,0x752981dd,0x7e6b820d}}, // _кале, _stän, _mrez, higp,
+ {{0x05d581fe,0x6609e7d4,0xdb1e67d5,0x7e6b8bb1}}, // दराब, _obek, _repó, kigp,
+ {{0xbbd1800d,0x29002f47,0x68ed67d6,0x212d0267}}, // हरुक, ttia_, frad, _šeha_,
+ {{0x68ed2872,0x7d1b8146,0xe3b20829,0x98e601a8}}, // [6b00] grad, _nsus, _ورد_, شكاو,
+ {{0x60c982be,0x69db82af,0x290067d7,0xdb1e03ec}}, // ipem, haue, rtia_, _depò,
+ {{0x44440698,0x7d1bd5d6,0x7bdc33f5,0x7529e7d8}}, // _io_, _asus, maru, _arez,
+ {{0x7529be6b,0x7bc1b0f8,0x8f9a8039,0x3ced011f}}, // _brez, _aflu, דיעי, ševe_,
+ {{0x44445252,0x6aa40682,0x68910065,0xbbd1800d}}, // _ko_, mmif, _کیلئ, हरूक,
+ {{0x26de8079,0x24078162,0x3ce9009a,0xd90d003d}}, // ysto_, инчи_, _जिसे_, _سیم_,
+ {{0x444467d9,0x66e33eb1,0xdb1a8019,0x7529e7da}}, // _mo_, _мора, _letö, _erez,
+ {{0x444467db,0x3ce9803b,0x7659ad12,0x7bdc49c6}}, // _lo_, šava_, chwy, haru,
+ {{0x44446525,0x7bdc67dc,0x7529e7dd,0x7aee007a}}, // _oo_, karu, _grez, hrbt,
+ {{0x44442c04,0x6609ae56,0xb8fe901b,0x7bdc06df}}, // _no_, _zbek, _तब_, jaru,
+ {{0x7d0d0289,0x7bd701df,0x68ed0450,0x61e60259}}, // _spas, _rexu, zrad, _odkl,
+ {{0x444407e1,0x6e20cbad,0xf76f87bd,0xe1ff0118}}, // _ao_, _jamb, باً_, _amón_,
+ {{0x444467de,0x6e20e7df,0x7d0281f9,0x7bdc443f}}, // _bo_, _mamb, ntos, faru,
+ {{0x6e209cc5,0x44440039,0x7bdc67e0,0x7c8712b2}}, // _lamb, _co_, garu, руме,
+ {{0x4444004c,0x883b8039,0xc5d780ab,0x6fcc8a27}}, // _do_, _בתגו, _হাসপ, _द्यू,
+ {{0x6e20a9b6,0x444446dd,0x09aa80ab,0x0f5781c6}}, // _namb, _eo_, _গ্যা, _כיום_,
+ {{0x44444208,0x7bdc3e4c,0x7d0d67e1,0x69cd00ee}}, // [6b10] _fo_, baru, _upas, rbae,
+ {{0x44441301,0x752980f1,0xd7f88087,0x7bdc0087}}, // _go_, _rrez, ngă_, caru,
+ {{0x4ea781e5,0xd12699f4,0x6e20e7e2,0x68ed016d}}, // арда, _کم_, _bamb, srad,
+ {{0x444467e3,0x68ed4088,0x6e22adf3,0x6e209b7d}}, // _zo_, prad, gdob, _camb,
+ {{0x444467e4,0x6e20e7e5,0xf8bf0c83,0xe7f00105}}, // _yo_, _damb, _krém_, _चाचा_,
+ {{0x44442056,0x44210074,0x3eb82152,0x7529ac00}}, // _xo_, _jah_, _furt_, _vrez,
+ {{0x44213d34,0x644567e6,0x03a58198,0x69db89af}}, // _mah_, _mohi, щико, taue,
+ {{0x6e20845f,0x4421059c,0xe8f993b4,0xeb780039}}, // _gamb, _lah_, гли_, יעוץ_,
+ {{0x7e69e7e7,0x0fd9827e,0x69dbc5cb,0x7bdc0a5a}}, // _imep, льны_, raue, yaru,
+ {{0x4421016f,0x69dbb20e,0xdb1c67e8,0xf1bf128a}}, // _nah_, saue, scré, _znát_,
+ {{0x444467e9,0x9b958013,0xab9580f7,0x6e20c141}}, // _ro_, _الات, _الاغ, _yamb,
+ {{0x44441964,0xb05b04b8,0x60c9e7ea,0x7bdc20c5}}, // _so_, _stäl, ppem, waru,
+ {{0x442167eb,0x7e698135,0x69d9867f,0x64454e21}}, // _bah_, _mmep, _iewe, _bohi,
+ {{0x6e228d26,0x442100d7,0x7e7b811f,0x69d989c4}}, // zdob, _cah_, _ljup, _hewe,
+ {{0x444467ec,0x442114ff,0x7d028065,0x7bdc28d6}}, // _vo_, _dah_, ztos, raru,
+ {{0x444467ed,0x7bdc1d36,0xe1f98110,0xc3328039}}, // _wo_, saru, ngų_, הול_,
+ {{0x444467ee,0x6e208b50,0x394db86a,0x3eb8081a}}, // [6b20] _to_, _ramb, nyes_, _surt_,
+ {{0x69d9879f,0xd12f0f99,0x7e69804f,0xe73a1ef3}}, // _lewe, عمل_, _amep, лез_,
+ {{0x6e20a34d,0xbc680065,0xfa6a084b,0xb5fb0907}}, // _pamb, _مجھے_, _банк_, riáv,
+ {{0x7d029a53,0x291d8247,0x6e2290dc,0x212b00c3}}, // ttos, _oswa_, udob, _frch_,
+ {{0xd6258307,0x6e22e7ef,0x4421016f,0x7e7b8687}}, // تعلي, rdob, _yah_, _djup,
+ {{0x70560277,0x7d02ce28,0x6f1301df,0x68460d9d}}, // _انسا, rtos, ñecí, _унга,
+ {{0x69d98df3,0xa3d90305,0x7d029ff1,0xa6f280ab}}, // _bewe, ारत_, stos, _জন্ম_,
+ {{0x69d9816f,0x3ce9009a,0xd25781bb,0xa3e800d4}}, // _cewe, _जिले_, йцы_, मृत_,
+ {{0xdfd08307,0xed508117,0x69d99d10,0x6fe2800d}}, // وية_, _تھا_, _dewe, jící,
+ {{0x2bcf886a,0xd7f8802e,0x3ea68bb1,0x3ea04412}}, // _स्पा, ugă_, lmot_, _ahit_,
+ {{0xe2ca8021,0x20028063,0x3aeb804e,0x3ea00362}}, // _след_, ęki_, _مبنی_, _bhit_,
+ {{0x69d9905d,0x44212c17,0x644500dd,0x291a89a7}}, // _gewe, _sah_, _sohi, _مقصد_,
+ {{0x7bdae7f0,0xa9679285,0xb05b00f2,0x442167f1}}, // _ketu, _лица_, _stäm, _pah_,
+ {{0x7af500ad,0x645c2323,0x3a22019d,0xe9478065}}, // tuzt, ghri, _aakp_, _درمی,
+ {{0x7bdae7f2,0xb8659a37,0x2246858f,0x78a90037}}, // _metu, _بانو, _kook_, _èevi,
+ {{0x442167f3,0x7bdae7f4,0x78ba001b,0x2bcf81a2}}, // _wah_, _letu, _čtvr, _स्ना,
+ {{0x442167f5,0x644567f6,0x31342306,0x625801a8}}, // [6b30] _tah_, _tohi, петр, _síoc,
+ {{0x645c1d77,0x7bdab1c2,0x22468806,0x261380ff}}, // chri, _netu, _look_, _ảo_,
+ {{0x625c83e6,0xe1f98084,0x38ad00c3,0x752d04e8}}, // _géog, ygų_, _hžrk_, _hraz,
+ {{0x3e721601,0x752d06c0,0x9f5e8144,0x395f80b9}}, // pšte_, _kraz, letó_, yzus_,
+ {{0x7bdae7f7,0x8c458284,0x660d534f,0x22be81bc}}, // _betu, желе, _mbak, _ọkpa_,
+ {{0x7bdae0f9,0x752d0696,0x69d9d5f2,0x386a0239}}, // _cetu, _mraz, _sewe, _gmbr_,
+ {{0x7bdae7f8,0x44d68063,0xda598568,0x22468039}}, // _detu, ał_, ириш_, _book_,
+ {{0xe1f98110,0x752d00df,0x7e69804f,0x69df0168}}, // ugų_, _oraz, _umep, naqe,
+ {{0x212be7f9,0x7bdae7fa,0x60cd5b34,0x2a6de7fb}}, // ích_, _fetu, npam, rieb_,
+ {{0x7bdae7fc,0x660d2896,0x60cd3666,0x69d9b7c0}}, // _getu, _abak, ipam, _wewe,
+ {{0xe80b0e70,0x2bcf801b,0x752d67fd,0xa5bb1427}}, // स्या_, _स्या, _araz, rdón,
+ {{0xd02200ab,0x68ee88c5,0xf5061f5e,0x59a9064a}}, // নায়_, ündü, озмо, कधार,
+ {{0x80c38a49,0x645c3337,0x37e59d2f,0x2bc68b6f}}, // _শিক্, thri, _долг, रुवा,
+ {{0x2bcf9094,0x61fbe7fe,0xe1ff0118,0xf1b283c8}}, // _स्मा, lgul, _acó_, _חסד_,
+ {{0x69df0168,0x3ea04d01,0x752d67ff,0x645c008b}}, // faqe, _thit_, _eraz, rhri,
+ {{0x61fbb3cd,0x3ced01dd,0x660d0135,0x49ca03a7}}, // ngul, ševa_, _gbak, рлан_,
+ {{0x752d6800,0x645c6801,0x3d19800d,0x60cd1066}}, // [6b40] _graz, phri, _मैले_, gpam,
+ {{0xd378826c,0x320901b4,0xb5fb0118,0x7d040372}}, // _heće_, afay_, siát, _iqis,
+ {{0x2bd01880,0xef1a1330,0x6e2402b8,0x7bda879a}}, // _त्या, рме_, _kaib, _retu,
+ {{0x7bdabb5b,0xb5fb5188,0x1ea984a3,0xb87b008b}}, // _setu, chád, ماني_, _hvíl,
+ {{0x7bda859c,0x6e24221b,0x224690c7,0x290403ed}}, // _petu, _maib, _rook_, _ëma_,
+ {{0x68e40448,0x6e243af6,0x98a6801b,0xc1d2816f}}, // msid, _laib, _proč_, सर्ग,
+ {{0xd25a8abe,0x7bdae802,0x68e4209b,0x2246820d}}, // ици_, _vetu, lsid, _pook_,
+ {{0xd378803a,0x61fb82ec,0x6e240c61,0x69c40cf7}}, // _neće_, ggul, _naib, ncie,
+ {{0x68e46803,0x7bda9588,0xdddd00e1,0x22b581a9}}, // nsid, _tetu, nosť, _sākt_,
+ {{0x224690e4,0x31af880a,0x2904e804,0x61fbd4c0}}, // _wook_, _sözü_, rtma_, agul,
+ {{0x38170051,0x6e246805,0xa3d912ee,0xd7bb03c8}}, // חקים_, _baib, ारस_, יציר,
+ {{0x752d6806,0x6448bc8a,0x6e240834,0x6fd68072}}, // _praz, _hodi, _caib, _मजकू,
+ {{0x61e08457,0x6e24051e,0x6448bf3c,0xa3be8a27}}, // maml, _daib, _kodi, _इला_,
+ {{0x6448ca96,0xd14980d5,0xd00f80f7,0xc1d30c2d}}, // _jodi, _دشمن_, _لله_, तर्ग,
+ {{0x9f470182,0x6448bc72,0x68e42a83,0x926b041c}}, // ünü_, _modi, esid, араа_,
+ {{0xddc407d9,0xa75b8039,0x95cb00a9,0x21290353}}, // _iliş, _כדור, _тука_, tvah_,
+ {{0x50f50364,0xb5fb6807,0x69df0168,0x68e46808}}, // [6b50] язат, siás, raqe, gsid,
+ {{0x6448a26f,0xf72b0790,0xb5fd8267,0xb5fb241f}}, // _nodi, ицей_, _imši, ciár,
+ {{0x87e40009,0xe3d880ab,0xdefb01e5,0x501b8039}}, // ьюте, _সাংব, рыз_, שובו,
+ {{0x69c40c9f,0x645ace17,0x64488706,0x60cd6809}}, // ccie, _alti, _aodi, ppam,
+ {{0x672e00f2,0x2600035a,0x7c2502d0,0x28dad2d5}}, // _erbj, ष्टी_, _kahr, _मौलि,
+ {{0x7c250352,0x6448c58e,0x645500f3,0x2bc6801b}}, // _jahr, _codi, nkzi, रुला,
+ {{0x69dd0812,0x64488025,0xd6c30077,0x999900e1}}, // _kese, _dodi, _آمری, _dosť_,
+ {{0x625800f7,0x6aa2e80a,0x6448808e,0x61e0d91b}}, // _líon, _chof, _eodi, gaml,
+ {{0x2bcf90c5,0x69dd3e55,0xe80b053f,0x6e240307}}, // _स्था, _mese, स्था_, _raib,
+ {{0x6448803a,0x7c250696,0x6e242a4e,0x7e6d64f0}}, // _godi, _nahr, _saib, _amap,
+ {{0xe7bc8424,0x69c4009a,0xb5fb0187,0x44258197}}, // ोड़प, ycie, viár, _ial_,
+ {{0x64438289,0x4425e80b,0x69dd3ef9,0xe80b0aad}}, // ljni, _hal_, _nese, स्ता_,
+ {{0xd378803b,0x7c2505a3,0x4425e80c,0x4a9a83c8}}, // _veće_, _bahr, _kal_, _פינג,
+ {{0xdb1c680d,0xddc98582,0xb042801c,0x78b500d2}}, // scrí, _njež, _thưở, _nizv,
+ {{0x44258e7c,0x69dd37fd,0x765be80e,0x42561cc7}}, // _mal_, _bese, _oluy, птат,
+ {{0xb21b065d,0x78b500eb,0x69dd680f,0x69c400eb}}, // _skær, _aizv, _cese, ucie,
+ {{0x7c250352,0x69c46810,0xddc982ce,0x672e01a1}}, // [6b60] _fahr, rcie, _bjež, _srbj,
+ {{0xb4d81880,0x68e40d1a,0x69c44db0,0x3979035f}}, // ाठी_, rsid, scie, істю_,
+ {{0x68e42416,0x69dd6811,0x61eb812b,0x3bbb0039}}, // ssid, _fese, _odgl, _למיד,
+ {{0x6448acc7,0x69dd3577,0x7c2503cb,0x442581b4}}, // _sodi, _gese, _zahr, _aal_,
+ {{0x7bde085c,0x27e1583a,0x225fe812,0x61e08380}}, // _kepu, bahn_, dhuk_, vaml,
+ {{0x44259209,0x7bde1083,0x6aa2e813,0x6448b32e}}, // _cal_, _jepu, _shof, _qodi,
+ {{0x4425e814,0x765b8216,0x7bde01bc,0xdb1c5d2a}}, // _dal_, _fluy, _mepu, mbró,
+ {{0x201a0968,0x7bde6815,0x22b800c3,0x9f58041c}}, // lepi_, _lepu, _učku_, _ecrã_,
+ {{0xd90e9125,0x61eb03a8,0x4425d128,0x6448b5f7}}, // ایت_, ógli, _fal_, _todi,
+ {{0x4425e816,0x645ac3b1,0x61e0aa4f,0xe786bc7a}}, // _gal_, _ulti, saml, зумо,
+ {{0xd9a9020e,0x09db80ab,0x21270129,0x6abe018e}}, // _कण्ट, _ধারা, ̉nh_, _nupf,
+ {{0x4425e729,0x7c250b67,0x61e0829a,0x225fe817}}, // _zal_, _sahr, qaml, chuk_,
+ {{0x69dd6818,0x09aa80c8,0x4425e20b,0x7c2500b9}}, // _rese, _গ্রা, _yal_, _pahr,
+ {{0x69dd0cbb,0x3ced02a5,0x201a02ce,0x44258be9}}, // _sese, ševo_, jepi_, _xal_,
+ {{0x7bde6819,0x69dd059e,0x78b51087,0x9f628009}}, // _depu, _pese, _rizv, ävän_,
+ {{0x7c2502af,0xdddbb5ca,0xd5c103eb,0x7e6d02e8}}, // _wahr, _skuž, _एलिज, _umap,
+ {{0x69dd681a,0xda1e243f,0x66e2826b,0x64412446}}, // [6b70] _vese, _पसरत_, _fíká, ölis,
+ {{0x69dd09af,0x7bde03b2,0x2fc7001c,0x3a2689c4}}, // _wese, _gepu, ùng_, _laop_,
+ {{0xddc9ab1f,0xe71900f7,0xc05383c8,0x69dd22f8}}, // _vjež, تيات_, עזע_, _tese,
+ {{0x4425e81b,0x2a601af7,0x7e7d681c,0xa3cf816f}}, // _sal_, chib_, nnsp, वुन_,
+ {{0xf1ab0077,0x5184053b,0x3ce681a1,0xece8804a}}, // _داده_, _зура, gsov_, зділ_,
+ {{0x5fe1159a,0x442581b9,0x224b00ee,0x201a681d}}, // _फ़िल, _qal_, _hock_, cepi_,
+ {{0x4425c3be,0x29090198,0x224b2a33,0x64438338}}, // _val_, ltaa_, _kock_, rjni,
+ {{0x5694baca,0x3a290642,0xddc41bcf,0xf8bf08f9}}, // _заст, ndap_, _omiš, _kué_,
+ {{0x29091e0a,0x4425c575,0x44278101,0x7c878162}}, // ntaa_, _tal_, pdn_, _еуже,
+ {{0xaec596c7,0xb95b026b,0x2a6000b9,0x6fdd12c6}}, // мбол, _afìk, zhib_, यरिं,
+ {{0x29090009,0x7bde681e,0x3ce90176,0xcb558290}}, // htaa_, _repu, _avav_, _تناظ,
+ {{0x7bde01f1,0x2a603592,0x1ee78065,0x2cbf8074}}, // _sepu, xhib_, _ہوتی_, _kuud_,
+ {{0x625800f7,0x7bde358b,0x25df02f1,0x2d84681f}}, // _díol, _pepu, _कजरी_, çme_,
+ {{0x78600065,0x2cbf8074,0x68fbb252,0x3940022c}}, // _köve, _muud_, muud, vxis_,
+ {{0x41d1853e,0xa3e58fd5,0x2cbfae07,0xa3c59a3b}}, // _सभास, _भजन_, _luud_, _एलन_,
+ {{0x09b180ab,0xe80b15bc,0x7bde0133,0xcb1281c6}}, // _ট্যা, स्सा_, _wepu, כלל_,
+ {{0x7bde0458,0x68fbe820,0xf8bf0216,0x201a54bb}}, // [6b80] _tepu, nuud, _cué_, tepi_,
+ {{0x2a60208e,0x39428009,0xd37885f3,0xa3d91c7b}}, // shib_, äksi_, _leća_, ारः_,
+ {{0x78600019,0xdb1c00f7,0xb6c50d8e,0x201a6821}}, // _növe, rbró, есій, repi_,
+ {{0x90c60e12,0xe8020327,0x201a2c17,0x7afc36b2}}, // _обме, र्जा_, sepi_, murt,
+ {{0x61e4271b,0x92d600c8,0x443a09da,0x69d66822}}, // mail, _সময়_, _knp_, mbye,
+ {{0x13e200c8,0x3ddf80f3,0x61e46823,0xa6e2007b}}, // _বাড়, _eeuw_, lail, æðsl,
+ {{0x60c40168,0xe80306a7,0x645d8084,0x62580174}}, // lqim, _लाता_, _įsit, _líom,
+ {{0x61e41581,0x30ea8fdd,0xc7a30ada,0x69d603ec}}, // nail, офон_, _риск, nbye,
+ {{0x2bcfa207,0x60c20013,0x6723820f,0x2cbf82a3}}, // _स्वा, íomh, _asnj, _guud_,
+ {{0x7afc6824,0x61e414f2,0xe8020778,0x66044b12}}, // kurt, hail, र्चा_, _acik,
+ {{0x61e46825,0x7c3ab499,0x60c400f1,0xe810016f}}, // kail, _intr, hqim, ठ्या_,
+ {{0x443a1bca,0x7afc6826,0x7c2892cf,0x7e629345}}, // _anp_, durt, _hadr, nhop,
+ {{0x506697f9,0x68469fab,0x224b01c6,0x68fb81b4}}, // _отка, _онда, _rock_, cuud,
+ {{0x7c2895f2,0xe57180be,0x7c2ae827,0x6d42802a}}, // _jadr, אַל_, ndfr, ixoa,
+ {{0x7afc4f8c,0x61e44c86,0xc61400ab,0x7c2895c1}}, // gurt, fail, িয়া_, _madr,
+ {{0x2909025d,0x61e410a8,0x78600201,0xe80203eb}}, // ttaa_, gail, _növb, र्घा_,
+ {{0xf8bf1220,0xa3cf913d,0x661d0069,0x7c3ae828}}, // [6b90] _qué_, वुड_, mesk, _ontr,
+ {{0x5c740554,0x29091e0a,0xd9bb88fd,0x7afc6085}}, // _альт, rtaa_, _उल्ट, burt,
+ {{0x2909025d,0x0c740dd0,0x8bd60051,0x83350158}}, // staa_, _جديد, _אותו_, _װאָס_,
+ {{0x28df90be,0x7c3ae829,0x61e40cb5,0x68f6682a}}, // पीडि, _antr, cail, bryd,
+ {{0x7c28a699,0x644e02d4,0x6aad08dc,0xe50481a8}}, // _badr, žniš, mmaf, نبوي,
+ {{0x7c28e82b,0x03a59fbc,0xfe10001c,0xd37885a2}}, // _cadr, нило, _nắng_, _seća_,
+ {{0x661d043d,0x60cf1d7e,0xd37880c3,0x7c28d0fc}}, // kesk, _htcm, _peća_, _dadr,
+ {{0x7c3ab0e3,0x7e6284e8,0xe8100c28,0x659581d9}}, // _entr, chop, ठ्ठा_, _забу,
+ {{0xd378803a,0x59c18105,0xf1b9911b,0x395981fa}}, // _veća_, शखबर, ješe_, áss_,
+ {{0x62580013,0x61e4011e,0x1cb88077,0x20010081}}, // _ríom, zail, _قالب_, nghi_,
+ {{0xe7399289,0xbcfb0065,0x68fb83ff,0xe3b38b76}}, // чен_, szél, suud, _شرط_,
+ {{0x661d44f3,0x6e2982f7,0x3f9981a9,0x443a682c}}, // gesk, _jaeb, āsu_, _snp_,
+ {{0x61e4682d,0xee370d70,0xdddb9a14,0xc7b880c3}}, // vail, хня_, _aluŋ, veđe_,
+ {{0x7afc1d46,0xe0068740,0xfe1000ff,0x629b8114}}, // turt, _शायद_, _gắng_, lluo,
+ {{0x1e960b9c,0x61e444df,0xa69619e3,0x68e9a67f}}, // _прир, tail, _приј, lsed,
+ {{0x3dda00ab,0x69c9c0d1,0x2bd0073c,0x68f6682e}}, // _থাকল, ncee, _त्रा, tryd,
+ {{0x61e4682f,0xe8fa09c7,0x6a830be2,0x68e9983d}}, // [6ba0] rail, яла_, улта, nsed,
+ {{0xb8820efc,0x7afc6830,0x61e40102,0x2bcf8540}}, // _čísl, purt, sail, _स्ला,
+ {{0x91e614b7,0x69cbe831,0xe61a84a9,0xdfcf00f7}}, // _поне, _ofge, зде_, صين_,
+ {{0x7c28803a,0x98a0017f,0x69c981c0,0xfce6a3e7}}, // _sadr, kvić_, jcee, еодо,
+ {{0x7e62816d,0x6e298101,0xd469004e,0xad9b016a}}, // rhop, _daeb, _رحیم_, lfúr,
+ {{0x68e99108,0x69cba40d,0x8c4391c7,0x7c28df52}}, // dsed, _afge, лече, _qadr,
+ {{0xc79501e2,0x8fa31a19,0x645890c9,0x4f953383}}, // _арты, _баре, rkvi, _арту,
+ {{0x179b03c8,0x5a349354,0xa50aa29c,0x7c288326}}, // _זילב, тнит, _неба_, _wadr,
+ {{0x81e500c8,0xe0cf80d5,0x661d1474,0x98a000c3}}, // _নাম_, یزی_, vesk, gvić_,
+ {{0x473485c2,0xbc6a8c3b,0x66028061,0x6e29838a}}, // внос, _عمان_, lgok, _zaeb,
+ {{0x81df8a49,0x1df911e9,0x661d6832,0x45d50389}}, // _তার_, мены_, tesk, конс,
+ {{0x6602829b,0xea018028,0xff7b04de,0xd3788267}}, // ngok, _đầy_, לטימ, _hećo_,
+ {{0xfe6e0bca,0x661d6833,0xa32080c2,0x78b8822b}}, // تگو_, resk, _मनुज_, _tivv,
+ {{0xb5fb56c9,0x661d27d1,0x212688f9,0x61e2b4e7}}, // chán, sesk, _isoh_, _heol,
+ {{0x9388024f,0x60c28364,0xfaa714bc,0xab952d6b}}, // еста_, _huom, ешан, тиві,
+ {{0x201ee834,0x60c2e835,0x66e701d0,0x998681d6}}, // leti_, _kuom, _léká, _množ_,
+ {{0x2cff8aed,0x60c28198,0xddc98493,0x62808123}}, // [6bb0] _ईमेल_, _juom, _aleş, inmo,
+ {{0x201ee836,0x61e2b0e4,0x6e3be837,0x3b55002e}}, // neti_, _leol, _snub, _акор,
+ {{0x3ea937ee,0x20010081,0x60c28198,0xf41201c6}}, // _khat_, rghi_, _luom, _דפי_,
+ {{0xa3d90076,0xfb1a0986,0xd5758c5c,0x201ee838}}, // ारक_, _کریں_, куль, heti_,
+ {{0x60c281e2,0x5a341cf8,0xddc9e839,0x3a2b0037}}, // _nuom, _снят, _eleş, _iacp_,
+ {{0x2249003b,0x201e82ce,0x3ea900e4,0x6e298c93}}, // ljak_, jeti_, _lhat_, _waeb,
+ {{0xf8bf0036,0xe7840705,0x21268176,0x2bac863a}}, // _trés_, _буто, _asoh_, टेला,
+ {{0x5f941980,0x3ea9001c,0x68e9e83a,0x61e281a8}}, // _сист, _nhat_, tsed, _ceol,
+ {{0x69c981ed,0x60c28037,0x201ee662,0xd24e95a9}}, // rcee, _cuom, feti_, تني_,
+ {{0x68e98867,0x857499b8,0x60c28110,0x69c98299}}, // rsed, _слух, _duom, scee,
+ {{0xf7718277,0x2608800d,0x68e991b4,0x200b809a}}, // شاد_, _हामी_, ssed, ęci_,
+ {{0x26c3005c,0x27e304b7,0x3ea935db,0x3ce98115}}, // _mujo_, _lejn_, _chat_, šavu_,
+ {{0xceb2893f,0x201ee83b,0x26c30264,0x26fe0321}}, // ייט_, beti_, _lujo_, _उम्र_,
+ {{0x5ed200c8,0x201ee83c,0x64a60196,0x68ff011c}}, // _হিসে, ceti_, тага, duqd,
+ {{0xfe0a0740,0x160a016f,0x09e62748,0xe29a004a}}, // _वापस_, _वापर_, ковн, дав_,
+ {{0xc3328039,0x61e281df,0x2c6d0267,0xc0b981d0}}, // סון_, _xeol, dždu_, řádá,
+ {{0xe2971d8f,0x290d8c39,0x27e304b7,0xad9b04c3}}, // [6bc0] тат_, ntea_, _bejn_, lgún,
+ {{0xb21b0ae1,0xb05b3a53,0xa2a2015c,0x291902d5}}, // _hjæl, _stär, _कीर्, _dpsa_,
+ {{0xe813835a,0xb8e90424,0x7c3e0102,0x645c683d}}, // त्या_, _लू_, _inpr, mkri,
+ {{0x201ee83e,0x75360035,0x3ce00242,0xc6088e1b}}, // zeti_, _kryz, _kwiv_, _রোজা_,
+ {{0x27e301cd,0x61e28114,0x201e8c12,0x2d8f006a}}, // _fejn_, _reol, yeti_, _øget_,
+ {{0x61e2e83f,0x645c5994,0x201e0ede,0x2900004f}}, // _seol, nkri, _úti_, muia_,
+ {{0x60c2825d,0x201e979d,0x290d8144,0xf74345a5}}, // _suom, veti_, etea_, _керо,
+ {{0x3ced0289,0x753600b9,0x78bc0009,0xc7b8911b}}, // ševi_, _oryz, _hirv, jeđa_,
+ {{0x201ee840,0xfe918077,0x6280816d,0x30a6a927}}, // teti_, _اینج, rnmo, кров,
+ {{0xea01801c,0x27e304b7,0xb42587d2,0x3dc002c4}}, // _đấy_, _xejn_, _معمو, _agiw_,
+ {{0x442c954c,0xadc3826b,0xb5fb00f7,0x3ea900ff}}, // _iad_, _abẹn, bhál, _phat_,
+ {{0x442cba4e,0x60c2a1f6,0xb87b007b,0x7c3e0edc}}, // _had_, _tuom, _hvít, _anpr,
+ {{0x442ce532,0x201ee841,0x06e282f1,0xd0480085}}, // _kad_, peti_, _कौरव_, şlər,
+ {{0x3ea9004c,0xd0109a00,0x491783c8,0x442ce842}}, // _what_, _طلب_, _סקול_, _jad_,
+ {{0x3ea9073a,0x7ac4072a,0xa3208006,0xefb08077}}, // _that_, исте, _मनोज_, _دیدگ,
+ {{0x442c813c,0x7c3e59a1,0x75360035,0xb113819d}}, // _lad_, _enpr, _fryz, _mụnw,
+ {{0x443ee843,0x30150037,0x2d8f00e7,0xdddb9123}}, // [6bd0] _ont_, лдар, _âge_, _njuš,
+ {{0x443e80ee,0xf1b98612,0xc05801e2,0x442e8144}}, // _nnt_, neša_, кіх_, jdf_,
+ {{0x6e2d18d0,0x2bd90ebf,0x290d8102,0x442e8114}}, // _kaab, _ब्या, ztea_, ddf_,
+ {{0x442c82c1,0x443ea9fb,0x765d06c0,0x29000087}}, // _aad_, _ant_, nksy, buia_,
+ {{0x6e2d45bb,0x442cb2c7,0xa5bb0333,0x76880380}}, // _maab, _bad_, cdót, nıya,
+ {{0x442c84f9,0x81df80c8,0x443e88f1,0x260881ce}}, // _cad_, _তাই_, _cnt_, _हाथी_,
+ {{0xeb968d9e,0xe299e844,0x68ed15d0,0xaca301bc}}, // лиш_, нак_, lsad, _nkọg,
+ {{0x0b1680f7,0x645c4c4f,0xb87b0118,0x78bc07c0}}, // اقية_, zkri, _evít, _zirv,
+ {{0x68ed6845,0x442c861f,0x6abd04c7,0x3e6201a8}}, // nsad, _fad_, _misf, _nóta_,
+ {{0x290d8c76,0x442ce846,0xb21b013c,0xe8f681bb}}, // rtea_, _gad_, _sjæl, ыль_,
+ {{0x290d8c76,0x6e2d0e05,0xfc309e91,0x29000010}}, // stea_, _baab, _بحق_, zuia_,
+ {{0x6abd1c6c,0x290d8087,0x98ad807a,0x68ed0687}}, // _nisf, ptea_, mveč_, ksad,
+ {{0x09b180ab,0x69dbe847,0x6d59e848,0x6e2d5735}}, // _ট্রা, mbue, nywa, _daab,
+ {{0x68ed0079,0x69dbe395,0x442c81b4,0x68fb85fe}}, // dsad, lbue, _xad_, mrud,
+ {{0x66098052,0x645c50e9,0xe739802e,0x7c2e6849}}, // _ocek, rkri, _чей_, rdbr,
+ {{0x645c120b,0x78bc0020,0x6e2d0ec8,0xe29709b1}}, // skri, _sirv, _gaab, гас_,
+ {{0x6abd1d24,0x68fbbb07,0x7e645d49,0x386300dd}}, // [6be0] _disf, nrud, _ilip, _bljr_,
+ {{0x7bc18199,0x61e9e84a,0x20c980ff,0x29000087}}, // _nglu, hael, _múi_, ruia_,
+ {{0x7e6437e4,0xddc9c30c,0xb87b3b56,0x68fbc50f}}, // _klip, _smeš, _avís, hrud,
+ {{0xe813a3e6,0x68ed0079,0x7bc1bb40,0xb87b01d0}}, // त्ता_, bsad, _aglu, _svít,
+ {{0x29002511,0x442ce84b,0x68fbe84c,0x24f68f80}}, // quia_, _pad_, jrud, учер,
+ {{0x7bdc13ca,0x61e646be,0xac188e1d,0x273384be}}, // nbru, _hekl, току_, _ẹni_,
+ {{0x442ce84d,0x260500cf,0x1fb50009,0x2c160c28}}, // _vad_, _वाली_, _встр, द्यं_,
+ {{0x442ca1c7,0x32548e0b,0x61e6007a,0x61e98739}}, // _wad_, рвир, _jekl, gael,
+ {{0x442ce84e,0x61e60029,0xddc99024,0x20180573}}, // _tad_, _mekl, _umeš, _kbri_,
+ {{0xdefaaafb,0x7e6449a0,0x6e2d1bf1,0x7d028110}}, // ным_, _alip, _saab, muos,
+ {{0xb8cba2db,0x60c60e59,0xf8c8801c,0x6e2d2e07}}, // _की_, _lukm, _bức_, _paab,
+ {{0x6e2d0079,0x6e9500e8,0x69dd807b,0x68fbe84f}}, // _qaab, риму, ðset, brud,
+ {{0x22938013,0x550701e2,0x6284009a,0x249a008e}}, // _النس, ычна, dnio, _bkpm_,
+ {{0x6e2d0ec8,0x7e640234,0x3e6201a8,0x5b271273}}, // _waab, _elip, _vóta_, льба,
+ {{0x61e60808,0x309b8051,0x6f01816a,0x6e2d57d3}}, // _bekl, _רשומ, culc, _taab,
+ {{0x60c64a3c,0x68ed22ab,0x20180eba,0x7d028e11}}, // _bukm, tsad, _abri_, kuos,
+ {{0x61e66850,0x9875024f,0x7bdc1031,0x201805ee}}, // [6bf0] _dekl, алац, bbru, _bbri_,
+ {{0x68ed61c1,0x09d185e8,0x7e64026c,0x2c663a2c}}, // rsad, _सभ्य, _zlip, _pôde_,
+ {{0x68ed6851,0x97a714f6,0x6d59a9a7,0x7e640198}}, // ssad, урал, wywa, _ylip,
+ {{0x4423475a,0x61e604eb,0x3ced056f,0xa49b009f}}, // lej_, _gekl, ševu_, ctòr,
+ {{0xd126990c,0x68ed0df6,0x58870d15,0x68e28326}}, // _بم_, qsad, _быва, _nwod,
+ {{0x442321a9,0xa8790158,0xc484a1d2,0x69c29142}}, // nej_, _יאָר, блік, _agoe,
+ {{0xfd580133,0x6d598035,0x68e28915,0x9f51861c}}, // _braụ, sywa, _awod, üzü_,
+ {{0x4423600b,0x7f840013,0x2ce500d4,0x68fb9188}}, // hej_, _الفن, _कबूल_, trud,
+ {{0x44233f63,0x61e9e852,0x7d1b80d2,0x394901df}}, // kej_, rael, _upus, nxas_,
+ {{0x442321a9,0x7ae7940b,0x39496853,0xf8c88028}}, // jej_, _бюдж, ixas_, _sức_,
+ {{0x8fa580e9,0x366a0729,0x44231517,0x62846854}}, // ране, вано_, dej_, ynio,
+ {{0xed5a00bf,0x20c9801c,0x443100eb,0x249a00b9}}, // вом_, _túi_, edz_, _skpm_,
+ {{0xc0e587eb,0x9f430388,0xf8bf09c4,0xa69681c6}}, // _толк, _dejé_, _asém_, שכרה_,
+ {{0x61e66855,0x224d807a,0x6f01ac2c,0x38ab8aa2}}, // _sekl, ljek_, pulc, føre_,
+ {{0x46a61cd0,0xeb9a143d,0x78600086,0xf8c88028}}, // _какв, тив_, _dövl, _tức_,
+ {{0x7bdc2aec,0x76880214,0x7e645b2b,0xf8c880ff}}, // sbru, mıyo, _ulip, _hứa_,
+
+ {{0x44231683,0x76880214,0x6f1c026f,0x62960c06}}, // [6c00] bej_, lıyo, _sprc, royo,
+ {{0xa49b07e2,0x26c7e856,0x44231d66,0x27e786c0}}, // stòr, _kuno_, cej_, _jenn_,
+ {{0x27e7a496,0x76880214,0x61e66857,0x7c23d994}}, // _menn_, nıyo, _tekl, denr,
+ {{0xed5a95b9,0x27e798e8,0x6441848d,0x3ebf9495}}, // кое_, _lenn_, _anli, _ciut_,
+ {{0xe53484cf,0x3ebf81bc,0x26c78365,0x1bd48081}}, // сель, _diut_, _luno_, боля,
+ {{0x7c2397fb,0x2d579d51,0x76880214,0x7d02e858}}, // genr, ришь_, kıyo, ruos,
+ {{0x2480cbab,0xaa668d9e,0xb5fb027f,0x26c7e859}}, // đim_, итик, nkác, _nuno_,
+ {{0x442358d6,0x68e28247,0xd2578009,0xe8138aed}}, // zej_, _pwod, ицы_, त्सा_,
+ {{0x26da0198,0x48aa9594,0x29120c8b,0x7c23bff4}}, // lppo_, ктом_, ntya_, benr,
+ {{0x2a69208b,0xd7aa00d4,0xb5fb00f7,0x32ca801c}}, // zhab_, _कराच, mhái, _tùy_,
+ {{0x4423685a,0x27e78352,0x50b780f7,0x224d9600}}, // vej_, _denn_, جديد_, bjek_,
+ {{0x44230d38,0x6f03009f,0x2a693592,0x625801a8}}, // wej_, ànci, xhab_, _míos,
+ {{0x44230eb0,0x2a668122,0x249832c7,0x768802d0}}, // tej_, _ilob_, form_, mıyl,
+ {{0x290480eb,0x2a6681c5,0xb87b0032,0x25a904e8}}, // muma_, _hlob_, _awín, _vzal_,
+ {{0xeabf0021,0xdb580364,0x4423086f,0x29048418}}, // _più_, ают_, rej_, luma_,
+ {{0xe81905fc,0x44233f28,0xd7bb8039,0x7afa1de9}}, // न्ना_, sej_, _בצור, átta,
+ {{0xe8138beb,0x442301b9,0x7e7d07b8,0x3ebf851e}}, // [6c10] त्वा_, pej_, misp, _riut_,
+ {{0x1c16016f,0x2a69008e,0x6ecb00d4,0xb5fb01a8}}, // द्दल_, shab_, _तंदु, dhái,
+ {{0x2c008540,0x29049fb6,0x92950048,0xc5d5a1d2}}, // _राजू_, huma_, _ганц, біль,
+ {{0x2904e85b,0x7e7d3a2f,0xf2d20039,0x625801a8}}, // kuma_, nisp, _מעט_, _díos,
+ {{0x61ed5c06,0x29048029,0x224d8499,0x51842dc7}}, // maal, juma_, vjek_, _дура,
+ {{0x61ed486f,0xa3be08fd,0x2904e85c,0x7e7d685d}}, // laal, ुँच_, duma_, hisp,
+ {{0x212900b9,0x7ebf01a9,0x7c23c538,0x27e7821e}}, // lwah_, _rūpn, renr, _renn_,
+ {{0xb5fb0307,0x569481e2,0x7c23e85e,0x61ed14c7}}, // bhái, _даст, senr, naal,
+ {{0x2904df6b,0xb5fb685f,0x7e7d12ab,0x661bc5b2}}, // guma_, chái, disp, _ibuk,
+ {{0x44238117,0x753b80ad,0xb8ff8665,0x61ed6860}}, // _új_, _iruz, _तऽ_, haal,
+ {{0x2bdd8c78,0x61ed2df0,0x5434826a,0x21291600}}, // _न्या, kaal, _برقر, hwah_,
+ {{0x27e7834a,0x2129372d,0xb5fb016b,0x6b7a80be}}, // _wenn_, kwah_, vkác, _גרענ,
+ {{0xd378803a,0x61ed3bdf,0x1e3a80be,0x9f478168}}, // _reći_, daal, אגרא, _kenë_,
+ {{0x320902c1,0xa3d317ba,0x629d28dc,0x9f4780f1}}, // egay_, हुल_, _akso, _jenë_,
+ {{0x661be861,0xd378a828,0x7e7d0333,0x9359229c}}, // _obuk, _peći_, bisp, арту_,
+ {{0x61ed0962,0xe81383eb,0x753b90d3,0x575681c6}}, // gaal, त्रा_, _oruz, _לבצע_,
+ {{0xd3788025,0x2f118019,0x39460187,0xddcd09d1}}, // [6c20] _veći_, lág_, ãos_, _umaš,
+ {{0x7d060364,0x29120019,0x62580174,0x661be862}}, // muks, rtya_, _píos, _abuk,
+ {{0x44446863,0x61ed4577,0x2904ae96,0xa3b4000c}}, // _in_, baal, zuma_, जेश_,
+ {{0x290481d3,0x44446864,0xb5fb01a8,0x69da0087}}, // yuma_, _hn_, thái, _ştef,
+ {{0x44443e92,0xc0b28028,0x8936045b,0x7d060ff9}}, // _kn_, _cười_, _اعجا, nuks,
+ {{0x2904b328,0xa3d8001b,0x7e7d3327,0x661bdc72}}, // vuma_, ाडि_, zisp, _ebuk,
+ {{0x907b00be,0x2a66822c,0xf99f08f9,0x753be865}}, // _עטלי, _plob_, _acèh_, _eruz,
+ {{0x60f882bc,0x2904a221,0x29006866,0xb5fb3502}}, // ания_, tuma_, oria_, phái,
+ {{0x4444073a,0xc05a8a18,0x26ca0733,0x76880457}}, // _on_, гін_, _hubo_, rıyl,
+ {{0x44445511,0x7d066867,0x2ef4828b,0x76880457}}, // _nn_, duks, озор, sıyl,
+ {{0xa09b010f,0x6aa40059,0x61ed0ef2,0x889b03c8}}, // רייט, klif, yaal, רביי,
+ {{0x44446868,0x2904c28b,0xe1ff026b,0x6d5d0198}}, // _an_, puma_, _alón_, tysa,
+ {{0x7e7d34f7,0xa3cf8006,0xe8190c87,0x249e80b9}}, // risp, वुक_, न्डा_, _kktm_,
+ {{0x26d81ea9,0x92d400c8,0x81e500c8,0x5ed900c8}}, // _otro_, _হয়ে_, _নাই_, _ডিসে,
+ {{0xa3d8000f,0x21290239,0xa87b81c6,0xf3f18129}}, // ाड़_, wwah_, _תאור, _học_,
+ {{0x2bd903eb,0x6aa40a2b,0x7d066869,0xe5c728a1}}, // _ब्रा, glif, buks, _уско,
+ {{0x61ed1572,0x7e628fb0,0x44446352,0x29003666}}, // [6c30] raal, dkop, _fn_, gria_,
+ {{0x61ed254a,0x4444686a,0x69d90540,0x9f4782d6}}, // saal, _gn_, _फ्री, _benè_,
+ {{0x61ed1699,0x29005d5d,0x37ab02ee,0x8a19002e}}, // paal, aria_, утан_, _конс_,
+ {{0xa3ae000f,0x61ed02a3,0x2900462f,0x753b8503}}, // _औरत_, qaal, bria_, _pruz,
+ {{0x4444686b,0x2f118019,0x81c380ab,0x672e006a}}, // _yn_, zág_, ্রম_, _esbj,
+ {{0x9f47820f,0x6ab381cb,0x58d43d65,0xb5fb016b}}, // _qenë_, ुद्र, _хорт, cháv,
+ {{0x64571c33,0xb1138133,0xae0a0074,0xfcb281d6}}, // _moxi, _tụkw, _वालन_, úšať_,
+ {{0xe8f9a05f,0xd378803b,0x661bad3c,0x9f43001b}}, // али_, _neću_, _ubuk, _její_,
+ {{0x26ca173d,0x44330144,0x753b8314,0x7e7be86c}}, // _zubo_, _oax_, _uruz, _imup,
+ {{0x2bd9159a,0xe3b38277,0x6aa4686d,0xef198eef}}, // _ब्ला, _فرض_, zlif, амо_,
+ {{0xbba803bb,0x81e880c8,0x7e69cb1f,0xf626006d}}, // _गरेक, _বার_, _klep, одно,
+ {{0xa3bf03b7,0x7d06025d,0x3cf0800f,0x9f4a03a7}}, // ेशन_, tuks, ुंचे_, _bebê_,
+ {{0x2f118065,0x4444686e,0x4427decd,0x44331b37}}, // ság_, _pn_, oen_, _bax_,
+ {{0x66e59289,0xe7398767,0x7d06171c,0x645702df}}, // _дока, _век_, ruks, _coxi,
+ {{0x44440104,0xa3b40063,0x60cb807b,0x76562914}}, // _vn_, जें_, _hugm, _toyy,
+ {{0x4427e86f,0x29006870,0x6289e871,0xf1d186a7}}, // hen_, tria_, nneo, _हलफन,
+ {{0x61ebb5ae,0x60cb8bda,0x26ca64fd,0xbcfb0019}}, // [6c40] _megl, _jugm, _subo_, szér,
+ {{0x290000ad,0x61ebda16,0x4433008e,0x394d8722}}, // rria_, _legl, _gax_, ixes_,
+ {{0xbc6a0bca,0x25ad826c,0xe5c68c9d,0xa3188105}}, // _ممکن_, _azel_, осло, _दहेज_,
+ {{0x290019ab,0x61460993,0x4427a450,0x60c3808e}}, // pria_, _дема, een_, _linm,
+ {{0x4427dddf,0x7e62b3fc,0xff0a00c8,0xa2b90cf0}}, // fen_, rkop, রবেশ_, ्दर्,
+ {{0x4427d27b,0x66e626ad,0xe819000d,0xdee60a7c}}, // gen_, _мога, न्दा_, _моги,
+ {{0x61ebe872,0x26d803ba,0x25ade873,0x7e6286aa}}, // _begl, _utro_, _ezel_, pkop,
+ {{0x60c38ad0,0x41e68221,0x2c6b8257,0xf99f0176}}, // _ainm, ціка, _røde_, _abèy_,
+ {{0x4427abba,0x61eb8698,0x3fc8826a,0x2c6b806a}}, // ben_, _degl, یدگی_, _søde_,
+ {{0x44278511,0x60cb90d3,0x7e69826f,0x7d0282f7}}, // cen_, _dugm, _zlep, mros,
+ {{0xd838807a,0x60c3a008,0x7bc88bb1,0x7d02a0db}}, // joče_, _dinm, _igdu, lros,
+ {{0x60c390f6,0x3ea682f7,0x62898cb5,0x44336874}}, // _einm, nlot_, cneo, _sax_,
+ {{0xd378805c,0x3ea0008e,0x6280e875,0x60cb8bb1}}, // _veću_, _dkit_, mimo, _gugm,
+ {{0x35cc83b7,0x7d029243,0x2717128a,0x9f45e876}}, // ़खड़, iros, něná_, balà_,
+ {{0xd8388353,0x7c276877,0x2122008e,0x3ea682c4}}, // goče_, rejr, _apkh_, klot_,
+ {{0x4427bcf9,0x26c4b2db,0x478985a8,0x2605016f}}, // zen_, _nimo_, _усім_, _वाटी_,
+ {{0x4427e878,0x1ad680ab,0xf4e580ab,0x3eb9232a}}, // [6c50] yen_, সওয়া, _নম্ব, mmst_,
+ {{0x4427e879,0x6280e87a,0x442710ab,0x7e698e20}}, // xen_, himo, _ún_, _slep,
+ {{0x442781ed,0x26c48032,0xc5d8819d,0xd5d881bc}}, // ven_, _bimo_, _ịrụb, _ịrụr,
+ {{0x44279377,0xa3dd98a4,0x1daf0072,0xb5fb03c1}}, // wen_, थुन_, _घरात, chát,
+ {{0x61e20693,0xb3bd00ab,0x26c482c4,0xa5bb0e06}}, // ñole, _অভিজ, _dimo_, deók,
+ {{0x4427e87b,0x61ebe87c,0x224680b9,0x25ad82d4}}, // uen_, _segl, _anok_, _vzel_,
+ {{0x61eb805c,0x29090009,0x60c383ec,0x7d02e87d}}, // _pegl, luaa_, _rinm, aros,
+ {{0x6d5c8201,0x78600019,0x60c3e87e,0x394d8722}}, // _əraz, _rövi, _sinm, txes_,
+ {{0x4427be84,0x13a80aed,0xe8068778,0xa3eb0fea}}, // pen_, _गर्भ, _शाखा_, मरा_,
+ {{0x9f4587e2,0xe5c486cf,0x442780f1,0x3ea0687f}}, // talà_, _істо, qen_, _skit_,
+ {{0x62808333,0x38ab8366,0x786d808b,0xf745a80f}}, // bimo, børn_, _núve, чено,
+ {{0x628085e4,0x60cbb234,0x316000b9,0xb5fb128a}}, // cimo, _tugm, yyiz_, skán,
+ {{0x80de8264,0x09e28264,0x3ce90362,0xe79a928a}}, // _মিথ্, _যাচা, _cwav_, _měří,
+ {{0xc1728051,0x6ab4313a,0x3e62007b,0xf1b982ce}}, // _אחד_, ंग्र, _móti_, ješi_,
+ {{0x09e612ea,0x22590609,0xbb4345a5,0x07a28087}}, // зобн, _bosk_, нерк, _пашн,
+ {{0x32d18142,0x49ca0b79,0x9f458511,0x3ea00637}}, // _máy_, слан_, calá_, _ukit_,
+ {{0x7af50019,0x648908ae,0xb5fb1de9,0xe7f280c2}}, // [6c60] aszt, džif, rhát, _आजमा_,
+ {{0x44e9809a,0x628094e6,0x26c4e880,0x6e3601b4}}, // dź_, zimo, _simo_, _hayb,
+ {{0xa7fc87d9,0x6e360214,0xd00f0124,0x212de881}}, // kkın, _kayb, الم_, hweh_,
+ {{0x442a29b7,0x8ca4866f,0x93250061,0x9f431a1f}}, // leb_, _चीजो, _کرین, _dejà_,
+ {{0x64a682a9,0x61e46882,0xe8193e24,0x6280c2b0}}, // пада, mbil, न्हा_, vimo,
+ {{0x44386883,0x442a22de,0x3ea6bd58,0x260886a7}}, // ndr_, neb_, slot_, _हाकी_,
+ {{0x6280a6d5,0x3ea6cd05,0x26c4e884,0x9f5881cc}}, // timo, plot_, _timo_, ürü_,
+ {{0x442a35b1,0x1dbe016f,0x46a3ba8b,0x752400c3}}, // heb_, ोशात, _засв, _opiz,
+ {{0x62808558,0xb21b00e8,0x61e4047f,0x442a5d86}}, // rimo, _kjær, ibil, keb_,
+ {{0x6d408ece,0x6280aed0,0x442a06ec,0x645a0198}}, // _irma, simo, jeb_, ötil,
+ {{0x1faa03a4,0x9f4a1fd1,0x6e363d85,0x442a0114}}, // _करोड, _bebé_, _bayb, deb_,
+ {{0x7c2aa45f,0x645ad958,0x200281cc,0xfb878061}}, // lefr, _hoti, şki_, _عدلی,
+ {{0x645a825d,0x44380088,0x61e42875,0x2fca010c}}, // _koti, fdr_, dbil, _ogbg_,
+ {{0x27e98104,0x91bb8039,0xfd65826b,0x442a5d86}}, // _đang_, _המדי, _aisọ, geb_,
+ {{0x752402a5,0x8557003d,0x644886cb,0xe81001a2}}, // _epiz, زیگر_, _mndi, ालना_,
+ {{0x6d40e885,0x645ae886,0xfbdf8028,0x61e40461}}, // _orma, _loti, _đêm_, gbil,
+ {{0xd49b0c8e,0xa3cc9664,0x6448d27c,0x44380115}}, // [6c70] _грн_, रखा_, _ondi, bdr_,
+ {{0x645ae887,0x61e41ba2,0xb5fb6888,0x2f150338}}, // _noti, abil, chár, tåg_,
+ {{0x61e401cd,0x06e080c8,0x6d40938e,0x7c2abf8b}}, // bbil, _ভিডি, _arma, defr,
+ {{0x6448cfd0,0x6d4080dd,0x645a8362,0x2bd901d0}}, // _andi, _brma, _aoti, बुरा,
+ {{0xc7b88025,0x645ae889,0x2f150106,0x628d08ae}}, // među_, _boti, såg_, mnao,
+ {{0xdfc68013,0x7c2ae88a,0x6aa2826b,0x03a59170}}, // _لي_, gefr, _akof, мило,
+ {{0xff0407ac,0x645ac7c4,0x65628122,0xe0df0706}}, // _пятн, _doti, ayoh, _bròg_,
+ {{0xe0d9c4cc,0xac778077,0x4421010b,0x442a009a}}, // ови_, _کارش, _lbh_, zeb_,
+ {{0x645a9ee0,0x44212f65,0xa7fc807e,0x320d8713}}, // _foti, _obh_, rkın, rgey_,
+ {{0x7e6d0065,0xa3bf17ba,0x68ed81b9,0x60c703ec}}, // _alap, ेशा_, _ħadd, _jijm,
+ {{0x752f009a,0xa2cd06a7,0x752400c3,0x61e41277}}, // ewcz, _दूल्, _spiz, ybil,
+ {{0x6e3602a3,0x645a80f1,0x68f60114,0x60c7090d}}, // _qayb, _zoti, ysyd, _lijm,
+ {{0x442a688b,0x20188722,0x7e6d2ce4,0x38b40799}}, // teb_, òric_, _dlap, läre_,
+ {{0x6aa983a6,0x4e0a0105,0x60c700f3,0x645a82df}}, // llef, _वाकई_, _nijm, _xoti,
+ {{0x61e45424,0x442a5657,0x64890267,0x6e3601e0}}, // tbil, reb_, džid, _tayb,
+ {{0x442a688c,0x4cdd00ab,0x60cf00c3,0x7d0b8122}}, // seb_, _বিরু, _bucm, kugs,
+ {{0x61e409e8,0x8ffa003f,0x6e2b9931,0xfaa315fc}}, // [6c80] rbil, قرار_, degb, тато,
+ {{0x61e44255,0x68f626fa,0x76498176,0x9c8704e8}}, // sbil, rsyd, _aney, _vyčí,
+ {{0x69cbe88d,0x765bcfac,0x68eb81c0,0x38b40106}}, // _ogge, _bouy, _lwgd, värd_,
+ {{0x0d868ea6,0x9f47800d,0x645a8790,0xdee31630}}, // млен, _není_, _soti, тоци,
+ {{0xb90300c8,0xdddb81a1,0x28af10c5,0x6aa3041c}}, // _নয়_, _fjuž, _जीवि, _ênfa,
+ {{0x69d982b5,0x69cbe88e,0x7b4008ae,0x6aa28123}}, // _afwe, _agge, pćuć, _skof,
+ {{0x645acbe5,0x765b86c0,0x65628ec9,0xd83882d4}}, // _voti, _fouy, syoh, joča_,
+ {{0x32b78013,0xd25080f7,0xfe4387b6,0x7c2a8c54}}, // ودية_, انة_, _инсо, sefr,
+ {{0x6d40e88f,0x645a9517,0x442a83a8,0x64400ec3}}, // _urma, _toti, _ºb_, ēmij,
+ {{0x6448e890,0x7e6d3bd9,0x0c239a19,0x15f89055}}, // _undi, _slap, _имун, ुलकर_,
+ {{0xd8388353,0xd9ee0361,0xf4140264,0x765be891}}, // goča_, जरात_, াজার_, _youy,
+ {{0x4ad20076,0xa40e8d86,0x442100b9,0x940e82f1}}, // _दूधव, _साँप_, _sbh_, _साँच_,
+ {{0x1613852a,0x9f410174,0xb17d81d6,0x63a480fc}}, // ण्टर_, rbhé_, saťr, ƙins,
+ {{0x212683ac,0x26ced970,0x5fd286ae,0x80d580ab}}, // _ipoh_, _tufo_, _सलिल, _দিচ্,
+ {{0x20198086,0x3d0881ce,0x260f8105,0xdeef8162}}, // əsi_, _समझे_, _डाली_, _шы_,
+ {{0x6aa0901f,0x442100b9,0x7e6d47cb,0xb5fb016b}}, // nomf, _wbh_, _ulap, cháp,
+ {{0xa40e8b9f,0x61e205e4,0x4421051e,0x940e8074}}, // [6c90] _सांप_, ñola, _tbh_, _सांच_,
+ {{0xa7fc82d0,0x44210174,0x37ab302b,0x765b86c4}}, // kkım, _ubh_, отен_, _souy,
+ {{0x61ef6892,0x6aa0b327,0x91c003b7,0x45dc81bc}}, // _tecl, komf, लेबै, _ịpụ_,
+ {{0xd7df23bd,0x7bda8125,0x645e80f2,0x5fdf2207}}, // _प्रच, _aftu, öpin, _प्रल,
+ {{0x6e2ba5b3,0x2a690b99,0xd5fb819d,0x26c96893}}, // regb, mkab_, _akụ, _miao_,
+ {{0x7d0bc5f8,0x26c900fc,0xa3d30054,0x947580d7}}, // rugs, _liao_, हुच_, رگذا,
+ {{0x765b8247,0x2489812b,0xfaa58811,0xa7fc82d0}}, // _touy, đam_, нако, rkıl,
+ {{0xa3b40701,0x2a6943e3,0x3a3902d6,0x26c9010c}}, // जेट_, nkab_, _jasp_, _niao_,
+ {{0x26d881df,0x9f478144,0x3a3907b6,0x9f4a026b}}, // íron_, _vení_, _masp_, _lebì_,
+ {{0x26d10234,0x6aa9a127,0x6913811f,0x6eca81d0}}, // _kuzo_, slef, jžeš, _संखु,
+ {{0x201887e2,0x2a690074,0x25a902f7,0x44ed20f6}}, // òria_, kkab_, _ayal_, ež_,
+ {{0x26c96894,0xd838807a,0x3e62007b,0x49758523}}, // _ciao_, roča_, _nótt_, _плес,
+ {{0x64890267,0x7872006a,0xddc40af8,0xf48400d7}}, // džib, _hæve, _bliš, ناسی,
+ {{0xe1ff370f,0x3a2d8a85,0x00000000,0x00000000}}, // _adó_, leep_, --, --,
+ {{0xd00e8013,0x29191a7b,0xe0df3742,0xf1b981a9}}, // _الى_, _aqsa_, _atò_, rešu_,
+ {{0x26c90028,0xe0da310c,0x09c380ab,0x3d0083eb}}, // _giao_, зва_, _এ্যা, _रिले_,
+ {{0x41271927,0xd5b20829,0x6e2402a0,0x26d1011b}}, // [6ca0] ното_, _حفظ_, _ibib, _auzo_,
+ {{0x6d446895,0x80a623f7,0xddc40088,0x26d10144}}, // _iria, رمان, _gliš, _buzo_,
+ {{0xb4be1834,0x3a2db32d,0xe0df026b,0xdd929e29}}, // ेदी_, keep_, _etò_, گوش_,
+ {{0xb8f600c8,0xe29a01e5,0x443a6896,0x2bdd8519}}, // _সব_, _хам_, _hap_, नुसा,
+ {{0xe5348e86,0x443a6897,0x78600201,0x3a2d8609}}, // тель, _kap_, _dövr, deep_,
+ {{0x443a6898,0x61f66899,0x645e689a,0x3d208305}}, // _jap_, layl, _jopi, _यहीं_,
+ {{0x394003a6,0x443a0039,0x26d14dd5,0x645e01e0}}, // lvis_, _map_, _guzo_, _mopi,
+ {{0xada6a84f,0x443a689b,0x645e689c,0x6d44689d}}, // _забл, _lap_, _lopi, _oria,
+ {{0x3940689e,0x6d4401c0,0x3ce00282,0x9f4c9e1e}}, // nvis_, _nria, _ntiv_, radí_,
+ {{0x443a5d17,0x7c2e689f,0x645e00eb,0x5f048fd5}}, // _nap_, jebr, _nopi, _विद्_,
+ {{0x6d44031d,0x61f668a0,0xdef78a8e,0x7f9481e5}}, // _aria, kayl, ныя_, танх,
+ {{0x442ed3e8,0x63a487d9,0xddc4007a,0x394068a1}}, // lef_, ğind, _sliš, kvis_,
+ {{0x2f188065,0x6d4400a9,0x7c3ae587,0x39400bda}}, // ség_, _cria, _katr, jvis_,
+ {{0x443a41b4,0x7c2e044e,0x628290af,0x645e3499}}, // _cap_, gebr, _hmoo, _copi,
+ {{0x083b812a,0x443a2322,0x2ba401ce,0x43940226}}, // _מעגל, _dap_, _खुदा, гарс,
+ {{0x7c3ae8a2,0x6d4468a3,0x442ea25b,0x26d1026c}}, // _latr, _fria, hef_, _ruzo_,
+ {{0x39400bc5,0x764d26df,0x6d44615e,0xf8072341}}, // [6cb0] gvis_, _inay, _gria, ечен,
+ {{0x7c3a911b,0x2a69547f,0x7c2e3cbb,0x6e3d2875}}, // _natr, skab_, cebr, ldsb,
+ {{0x61f668a4,0xe8188768,0x3a2d0118,0x442ee8a5}}, // bayl, _दाना_, _ºep_, def_,
+ {{0xa2d61094,0x6e3d23ea,0x78a1b819,0x443a16f0}}, // _मंत्, ndsb, zolv, _zap_,
+ {{0x442ed8b6,0x443a4022,0x64cb9a46,0x6e3d06a8}}, // fef_, _yap_, ादेश, idsb,
+ {{0x7c3ae8a6,0x6aad68a7,0x62828915,0x26d1004f}}, // _catr, llaf, _amoo, _tuzo_,
+ {{0xe2998f04,0x764d0214,0x7c3a8428,0xfbd401c6}}, // мак_, _onay, _datr, נתק_,
+ {{0xc245a386,0x7d1d070b,0x60ca8197,0x31258081}}, // тник, ktss, _aifm, вдиг,
+ {{0x38b4016d,0x7c3ae8a8,0x3a2d86a8,0xdb0883c1}}, // lära_, _fatr, reep_, _vzdá,
+ {{0x2903dcdc,0x764d0a72,0x7c3a81bf,0x6aad01ec}}, // čja_, _anay, _gatr, hlaf,
+ {{0x6e3b8dd8,0x25c70085,0x68e40102,0x764d0326}}, // _kaub, ətlə_, zpid, _bnay,
+ {{0x6d440038,0x645e0364,0x7d1d434a,0xf5ec046d}}, // _pria, _sopi, ftss, _bẹẹ_,
+ {{0x61e9e8a9,0x443a0247,0x645e68aa,0xba748591}}, // mbel, _pap_, _popi, _حالت,
+ {{0xab2a0221,0x232a00e8,0x69dd0343,0x68fbe8ab}}, // мога_, моги_, _afse, msud,
+ {{0x638301e2,0xf8c88028,0x61f60214,0x889a8039}}, // агра, _cứu_, tayl, _קבצי,
+ {{0x443a602f,0x69dbcaf6,0xf2d280be,0x394068ac}}, // _wap_, ncue, שען_, tvis_,
+ {{0x443a68ad,0x645e68ae,0x6d440c7b,0x69a201fe}}, // [6cc0] _tap_, _topi, _uria, _कुशी,
+ {{0x443a01bf,0x68e40102,0x61f606d5,0x61e980ee}}, // _uap_, rpid, sayl, hbel,
+ {{0x8a060fe7,0xa3bf01a2,0xd6d9004a,0x39400082}}, // _изне, ेशः_, еті_, svis_,
+ {{0x7c3abe50,0x68fbb55e,0x9f4c87a3,0x6e3ba056}}, // _satr, ksud, nadá_, _caub,
+ {{0xdd8f8013,0x786d8722,0xceb304de,0xa2d61a46}}, // يوم_, _núvo, ביא_, _मूत्,
+ {{0x38b40106,0x68fb97bf,0x61e9d778,0x44259867}}, // bära_, dsud, ebel, _cbl_,
+ {{0x442e831d,0x7c3a803a,0x26130768,0x442597c9}}, // ref_, _vatr, _थाली_, _dbl_,
+ {{0x442ee8af,0x61e9e8b0,0x32638198,0x5a34964f}}, // sef_, gbel, итыв, унит,
+ {{0x28d80e70,0x7c3acc30,0x7e6080f7,0x9f4c81df}}, // _भूमि, _tatr, _iomp, dadá_,
+ {{0xdefa87ac,0x7e608282,0x3ce6dcbb,0x9f4c823e}}, // мым_, _homp, mpov_, tadà_,
+ {{0x81cb00ab,0xdeb58326,0xe4758162,0xbebd0ec3}}, // রুপ_, _arzƙ, _сутэ, ntīg,
+ {{0x64890a8e,0x09e682a9,0x83fc805c,0x91e680b3}}, // džia, _родн, _dođe, _роде,
+ {{0x39408013,0x6e3d0cd7,0x02be01d0,0xf8c8827d}}, // _éis_, rdsb, ्दैन, _bứt_,
+ {{0xdddb87d9,0x7e60a388,0x67d38f51,0xf00b0129}}, // _oluş, _lomp, _кошу, _đắng_,
+ {{0x81e400c8,0x248d0a20,0x47c080ab,0xf8c880ff}}, // _নয়_, đem_, _শ্রী, _dứt_,
+ {{0x764d00b8,0xb5fd8ee0,0x2018373d,0x7e60b8ec}}, // _unay, _koše, _acri_, _nomp,
+ {{0x2ba98bb8,0xd759015b,0x3ea909ca,0xc05b0b73}}, // [6cd0] _चुना, کلات_, _ikat_, між_,
+ {{0x61e9e292,0x6e3bd399,0x38b40106,0xb7672f92}}, // zbel, _saub, tära_, ктей,
+ {{0x2fc700f2,0x443168b1,0x7529e8b2,0x7eb668b3}}, // äng_, mez_, _spez, lápa,
+ {{0x443107e8,0xacf99abe,0x38b98866,0x3946e8b4}}, // lez_, енду_, mère_, _oros_,
+ {{0x7eb64227,0xfd1f146f,0xb5fdd717,0x3946822c}}, // nápa, ntì_, _noše, _nros_,
+ {{0x443168b5,0x6738920e,0x261a0105,0xa3b70fce}}, // nez_, _osvj, _भाभी_, _जरा_,
+ {{0x4fc40c8e,0x6d42802e,0x6e3be8b6,0x39468114}}, // _вста, avoa, _taub, _aros_,
+ {{0x83fc803b,0x44314042,0x6d4100eb,0x61e98cab}}, // _rođe, hez_, ālaj, ubel,
+ {{0x4425d9ba,0x4431420d,0x69dbc5e6,0x3946c507}}, // _tbl_, kez_, rcue, _cros_,
+ {{0x61e687d9,0x39468355,0x46e9867c,0x83fc817f}}, // ıkla, _dros_, ндон_, _pođe,
+ {{0x443168b7,0x68e2826c,0x6ec786ae,0x6aa40c93}}, // dez_, _etod, ़गपु, yoif,
+ {{0x83fc8052,0x394680b9,0x38b98036,0xcee88180}}, // _vođe, _fros_, dère_, کرین_,
+ {{0x3946e8b8,0x443155d6,0x91058ba8,0x3ea900dd}}, // _gros_, fez_, _спле, _dkat_,
+ {{0x6abe0ebf,0x443168b9,0x26d189b2,0x38b98036}}, // ्द्र, gez_, ízos_, fère_,
+ {{0x5a960af2,0xe8188074,0x9f4c801b,0x161486a7}}, // граф, _दाता_, padá_, _डालर_,
+ {{0x6d4696f2,0x20032168,0x64892284,0xa3bb8074}}, // _škam, _adji_, ržia, _घरन_,
+ {{0x291fb673,0x394668ba,0xe2970087,0xd02601e8}}, // [6ce0] ntua_, íos_, уат_, лмай,
+ {{0x64891752,0x7e60e8bb,0x291f8bf0,0x443168bc}}, // lžin, _somp, itua_, cez_,
+ {{0x441b093f,0x141b00be,0x5c3703c8,0xe7e306a7}}, // _אויס, _אויב, _גרין_, गड़ा_,
+ {{0x291f8102,0xbebd00eb,0x23650106,0xfdd280ab}}, // ktua_, rtīg, älje_, ার্ড,
+ {{0x7c3e4eef,0x5bcc864a,0xbebd01a9,0x291f83ed}}, // _kapr, ाश्व, stīg, jtua_,
+ {{0x9f478214,0x2003026c,0xdca380e8,0x64a3e844}}, // _genç_, _gdji_, бачи, бача,
+ {{0x7c3e24ec,0x6fb802f1,0x291d81b9,0x7eb6016b}}, // _mapr, _अरिं, _aqwa_, zápa,
+ {{0x44310d38,0x7c3e0d29,0x22405650,0x660468bd}}, // zez_, _lapr, ndik_, _ndik,
+ {{0xe818ac2a,0xa9c3835f,0x443102be,0x64890b80}}, // _दादा_, йськ, yez_, džin,
+ {{0x660410e1,0x3ea90343,0xb8dc81a2,0x3ea6c2cf}}, // _adik, _skat_, _आठ_, loot_,
+ {{0x443103d3,0x2c1b159a,0xa3d805fc,0x443ec1ef}}, // vez_, _बाबू_, िशन_, _iat_,
+ {{0x443e8367,0x3946cf74,0x1efb00be,0x38af0214}}, // _hat_, _tros_, עלכע, türk_,
+ {{0x443168be,0x443ec463,0x5c072244,0x9c83807a}}, // tez_, _kat_, лява, _ščit,
+ {{0xdfcf8013,0x66040247,0x443ee8bf,0x3ea6e8c0}}, // _فيه_, _edik, _jat_, hoot_,
+ {{0x443e9be9,0xfe148105,0x7c3e0247,0x8cd50f3d}}, // _mat_, _डांस_, _dapr, _यूरो,
+ {{0x443107e8,0x443e8063,0xddc2c2e4,0x5a3503a7}}, // sez_, _lat_, zkoš, јнат,
+ {{0x4431157a,0x443eb0b2,0x7c288259,0x20cd0b67}}, // [6cf0] pez_, _oat_, _obdr, _džin_,
+ {{0x38b980e7,0x13cc80ab,0x752d0037,0x25ad8176}}, // père_, রুয়, _ipaz, _ryel_,
+ {{0x47350087,0x291f811b,0x63680081,0xf1bf026b}}, // рнес, ztua_, _бряг_, _gbán_,
+ {{0x7c3e68c1,0xa3ca81ce,0x4fc40009,0x63a90214}}, // _zapr, ोशन_, ссуа, ğend,
+ {{0x6e3f0372,0xddc2abea,0x443eca2b,0x7c3e02d0}}, // _maqb, tkoš, _bat_, _yapr,
+ {{0x6b659594,0xa3d8016f,0x20030b80,0x443e81c6}}, // икла, िशय_, _udji_, _cat_,
+ {{0x443ee8c2,0x61ed54bc,0x224f80ee,0x5c0484cf}}, // _dat_, obal, _pngk_, сяча,
+ {{0x291f9b10,0x752d0353,0xa3a983dd,0x78720366}}, // ttua_, _opaz, _गुण_, _jævn,
+ {{0xe8f684d9,0xaca381bc,0x6e2982d5,0x291f8198}}, // алы_, _awụn, _ibeb, utua_,
+ {{0xb5fb000d,0x443ec826,0x291f9e80,0x64890110}}, // cház, _gat_, rtua_, džio,
+ {{0x291f8bf0,0x61ed64da,0x7c3e3fc7,0x3a3d80b9}}, // stua_, kbal, _rapr, _tawp_,
+ {{0xf1c5146d,0x7c3e68c3,0x7872006a,0x5f080424}}, // वधान, _sapr, _nævn, _हिस्_,
+ {{0x61ed28e1,0x7c3e68c4,0x61fbe8c5,0x38ab8aa2}}, // dbal, _papr, maul, ført_,
+ {{0x317a83c8,0x443e823e,0x18669d79,0xf745997b}}, // _באנד, _xat_, _ваши_, _веко,
+ {{0x6b632481,0x6a78008b,0x68e98687,0xe3631ad8}}, // окра, _lífe, lped, окри,
+ {{0x61fbdeee,0x61ed68c6,0x7c3e21b0,0x2ca79384}}, // naul, gbal, _wapr, hond_,
+ {{0x2ca78006,0xa2d668c7,0xe9d70c0e,0x7c3e0e2d}}, // [6d00] kond_, _मूर्, рку_, _tapr,
+ {{0x6e29bebe,0xf00b001c,0xe0df00ff,0x68e98084}}, // _abeb, _đảng_, _tròn_, iped,
+ {{0x21200ee1,0x61ed4577,0x3ea69384,0x6d49b9ff}}, // stih_, bbal, toot_, _area,
+ {{0x0bb704de,0x6d49bcd7,0x75468e97,0xa116003d}}, // _כלים_, _brea, инез, _پوست,
+ {{0x443ee8c8,0x6d49b474,0xe81e0105,0x62963759}}, // _pat_, _crea, _पापा_, mnyo,
+ {{0x23ba000f,0x6d49ac9b,0x6e29912e,0xb5fb0298}}, // _इराद, _drea, _ebeb, nkár,
+ {{0x443ed88a,0x2ba9809a,0xe45700be,0x3ea6e8c9}}, // _vat_, _चुदा, ייסט_, poot_,
+ {{0x443e9db8,0x6d498af9,0x629602ec,0x7f3b04de}}, // _wat_, _frea, nnyo, _בעלו,
+ {{0x443e81cd,0xfce39878,0x6d49dfc6,0xd5a511bc}}, // _tat_, _мото, _grea, _गुंज,
+ {{0x26d868ca,0x29000009,0x2ca7c918,0x61ed04e8}}, // _kuro_, isia_, cond_, zbal,
+ {{0x764086c7,0x1df907ac,0xa2d60aad,0x75228065}}, // _kamy, лены_, _मूल्, ltoz,
+ {{0xb5fd9070,0x29000364,0x61fbe8cb,0x26d85107}}, // _koša, ksia_, caul, _muro_,
+ {{0x2900218a,0x61e2d122,0xb5fd8b80,0x26d80144}}, // jsia_, _ifol, _joša, _luro_,
+ {{0x26d80548,0x7ebd00e7,0xd8388968,0xe0df02d6}}, // _ouro_, dépe, koči_, _dròl_,
+ {{0xbebd0029,0xe2870073,0xd838807a,0x83f80198}}, // rtīb, аѓан, joči_, ресс_,
+ {{0xbebd0029,0x78a8e8cc,0x2ca780f3,0x277781c6}}, // stīb, lodv, zond_, _כגון_,
+ {{0xdebc0039,0xc1b953bf,0x27f868cd,0xf8baa539}}, // [6d10] _במהל, улах_, _bern_, _उठाय,
+ {{0x085400e8,0x6296008a,0x06de0264,0xa5bb0144}}, // овую, anyo, _ডিজি, nfóm,
+ {{0xec360051,0x62963c96,0x2ca78a0f,0x6d498083}}, // _האתר_, bnyo, vond_, _srea,
+ {{0x26d864b5,0x83fc8052,0x6d49e8ce,0x2ca7910f}}, // _duro_, _rođa, _prea, wond_,
+ {{0xf8db0424,0x290068cf,0x2ca7e8d0,0x764082d6}}, // _बढिय, csia_, tond_, _damy,
+ {{0xb5fd8025,0x6d49802e,0xe81e01fe,0x27f801ec}}, // _doša, _vrea, _पाया_, _gern_,
+ {{0xe7ed035a,0xa3d817a3,0x2ca783b2,0x26d868d1}}, // _च्या_, िशत_, rond_, _guro_,
+ {{0xeb980039,0x6d49b910,0x76408110,0x290dc975}}, // ידור_, _trea, _gamy, trea_,
+ {{0x61fb9716,0xa2ba354d,0x2ca780e7,0x6d49a7d1}}, // raul, ्षत्, pond_, _urea,
+ {{0x2055916b,0x290dae2f,0xcea900be,0xd3a7a66d}}, // стир, rrea_, _װי_, _креп,
+ {{0x61fbe8d2,0xa5bb007b,0xb8fe8006,0xe945803d}}, // paul, sfól, _दू_, _گرای,
+ {{0x644384e1,0x290016a7,0xed5a19fe,0x68e9e8d3}}, // ldni, ysia_, гом_, pped,
+ {{0xdd9204e3,0xe945803d,0x3f8b8035,0x627b8035}}, // سوس_, _درای, ńcu_, głoś,
+ {{0x28db101b,0x9f47b618,0x64439614,0x6441e8d4}}, // _बढ़ि, _menú_, ndni, _jali,
+ {{0x394b1e9e,0xb5fb34cd,0x786dc6b8,0x7e6468d5}}, // _arcs_, rkár, _núvi, _toip,
+ {{0x225f8812,0x6441e8d6,0x2900252c,0x20cd01a1}}, // njuk_, _lali, tsia_, _džim_,
+ {{0x290068d7,0x26d807f1,0xbebd01a9,0x76408196}}, // [6d20] usia_, _suro_, stīc, _ramy,
+ {{0x26d200f6,0x644183f8,0x26d802e8,0x62965439}}, // _hiyo_, _nali, _puro_, snyo,
+ {{0x6443816d,0x3ead82c4,0xe81e06ae,0x27f8021e}}, // ddni, _bket_, _पाठा_, _vern_,
+ {{0xb5fd8d11,0x6443e8d8,0x9f58020f,0x786d80a9}}, // _poša, edni, _herë_, _dúvi,
+ {{0x78600201,0x64890da8,0x3ead808e,0x9f459c18}}, // _mövz, džij, _dket_, valý_,
+ {{0x75228019,0x6441879a,0x26d868d9,0x6a758144}}, // rtoz, _cali, _turo_, _ráfa,
+ {{0x6441e8da,0x26d2010c,0x087703de,0x26c00833}}, // _dali, _oiyo_, _פעלט_, _ohio_,
+ {{0x6e968013,0x7d04352c,0xe6968013,0x25ad009a}}, // _الرا, _ovis, _الرد, żeli_,
+ {{0x81aa00c8,0xa3d80466,0x9f5a09b2,0xd7fa9577}}, // গের_, िशा_, dapé_, лун_,
+ {{0x7c658013,0x7d02955f,0x6441960c,0x290a8259}}, // _والل, msos, _gali, čba_,
+ {{0x7d043472,0x26d21cee,0x7d028711,0x26c00362}}, // _avis, _biyo_, lsos, _bhio_,
+ {{0xc3b780be,0x26d20704,0x8f9b025f,0xe0df0362}}, // _פלאץ_, _ciyo_, ריטי, _sròm_,
+ {{0x92d50a49,0x261a09a3,0x98a6872a,0x64419af2}}, // _হবে_, _भारी_, _лиде, _yali,
+ {{0x2a66822c,0x64418be9,0xf4840098,0x9f458216}}, // _hoob_, _xali, пусн, galó_,
+ {{0x25a068db,0x2a6681c5,0xeabf001c,0x5f93ca56}}, // _exil_, _koob_, _phù_, _ништ,
+ {{0x644386b9,0x7d02a551,0x9f5a34d3,0x00000000}}, // zdni, ksos, capé_, --,
+ {{0xb5fd8fda,0x786d81ac,0x6e2d52b5,0x2a668282}}, // [6d30] _bošn, _súvi, _ibab, _moob_,
+ {{0x44276184,0x2a66e8dc,0x3eade8dd,0x81bd80ab}}, // _ùn_, _loob_, _sket_, _আলম_,
+ {{0xeabf00ff,0x6441e8de,0x656f68df,0x273a81a9}}, // _thù_, _rali, lych, _jūn_,
+ {{0x2a66bdd7,0x6d4d1316,0x442ce667,0x81cb00ab}}, // _noob_, _kraa, _abd_, রুষ_,
+ {{0x6441861b,0xa3cb9a87,0xed59a262,0x656f0428}}, // _pali, रेन_, рол_, nych,
+ {{0x61ff029a,0x68ed68e0,0x442caad6,0x644189ac}}, // laql, mpad, _cbd_, _qali,
+ {{0x64418744,0x2a66e8e1,0x64438082,0x8c1c098a}}, // _vali, _boob_, rdni, אווי,
+ {{0x6441a6fb,0xe9d9c436,0x2a6681c5,0x6d4d68e2}}, // _wali, шки_, _coob_, _oraa,
+ {{0x2ba980c2,0x6609bbb5,0x68ed0ff5,0xe0d68a8e}}, // _चुरा, _idek, npad, овы_,
+ {{0xaf0594b8,0x6e2d64f0,0x39493efc,0x26d20010}}, // опол, _abab, ivas_, _siyo_,
+ {{0x2a668282,0x7d0931ce,0x6609816b,0x7d0468e3}}, // _foob_, šesl, _kdek, _svis,
+ {{0x6d4d14d1,0x2a6681b4,0x63ad8850,0xddcd0824}}, // _braa, _goob_, ğand, _slaš,
+ {{0xddcd68e4,0x763a8158,0x2c6b8257,0xb5fd811f}}, // _plaš, בערג, _født_, _lošo,
+ {{0x6d4d05f8,0x7d0480f7,0xdfdb8081,0x6e2d036a}}, // _draa, áisi, _съд_, _ebab,
+ {{0x81b100ab,0xbb432154,0xd2520591,0x6d4d4ab0}}, // টেম_, мерк, ونس_, _eraa,
+ {{0x66098135,0x6d4d1e7a,0x6e2d68e5,0x7d040840}}, // _ndek, _fraa, _gbab, _tvis,
+ {{0x6d4d42cf,0x25df000f,0x68ed1341,0x2a79008e}}, // [6d40] _graa, _गलती_, gpad, _dlsb_,
+ {{0x83fc81a1,0x68e4e8e6,0x1fb6004a,0x6ecca2f8}}, // _rođo, _éide, осер, _işbi,
+ {{0x4444369d,0xe821809a,0x81cb00ab,0x224200d9}}, // _ia_, _माना_, রুল_, _takk_,
+ {{0x44443f47,0x61ff0085,0xd62a8371,0x87ac83de}}, // _ha_, caql, роде_, נשאַ,
+ {{0x2a66e8e7,0x764468e8,0x6ef002d6,0x66099a14}}, // _roob_, _maiy, _bòbò, _ddek,
+ {{0x444468e9,0x443868ea,0x66098133,0x83fc817f}}, // _ja_, ner_, _edek, _vođo,
+ {{0x444468eb,0x443868ec,0x7d02baa8,0x9f4305a4}}, // _ma_, ier_, ssos, _dejó_,
+ {{0x44384979,0x61e43a03,0xfbd30039,0xead38009}}, // her_, ncil, ותו_, мощь,
+ {{0x44383ff7,0xa2da8894,0x444468ed,0xe51c06b7}}, // ker_, _पूर्, _oa_, भूमि_,
+ {{0x6d40e8ee,0x7e62803b,0x443868ef,0xddc4378d}}, // _isma, ljop, jer_, _bliž,
+ {{0xb7db03c8,0x7e7d1a2e,0x76440041,0x673a81ed}}, // יקיי, thsp, _baiy, uwtj,
+ {{0x444401b4,0x6d4d058f,0x368b11cf,0x39490110}}, // _aa_, _praa, рсон_, yvas_,
+ {{0x44445a5c,0x4438273d,0x200a026c,0x1dca84c5}}, // _ba_, fer_, _odbi_, िधित,
+ {{0x444468f0,0x443817db,0x6d4d1aee,0x656f68f1}}, // _ca_, ger_, _vraa, rych,
+ {{0x76440326,0x9f4100e5,0x6d4d1384,0xd8388b80}}, // _faiy, cchè_, _wraa, meč_,
+ {{0x444468f2,0x6d40e8f3,0xd838c759,0x6d4d23df}}, // _ea_, _osma, leč_, _traa,
+ {{0x444453a5,0xe82181ab,0x61ff0201,0xb5fd87dd}}, // [6d50] _fa_, _मामा_, raql, _došl,
+ {{0x44445738,0x7c388052,0x68ed39b3,0x61e40118}}, // _ga_, jevr, rpad, acil,
+ {{0x44214157,0x6d408573,0x1eea026a,0x4ea7e8f4}}, // _ich_, _asma, مولی_, орда,
+ {{0x51872355,0x68ed016d,0x9f4147ab,0x236c0106}}, // _дуга, ppad, nché_, ädje_,
+ {{0x44442af7,0x644504d2,0x73e58698,0x6609820f}}, // _ya_, _kahi, полз, _vdek,
+ {{0x444468f5,0x61fd0775,0xd83882fd,0x644510fe}}, // _xa_, _hesl, ječ_, _jahi,
+ {{0x64451856,0x6d40c9cf,0x2001061b,0x290683a8}}, // _mahi, _esma, mahi_, _avoa_,
+ {{0x4438623f,0x60d507d9,0x442101a8,0xb87b016b}}, // zer_, _hizm, _lch_, _stíh,
+ {{0x4421671b,0x44380806,0xc1b9035a,0xdb08801b}}, // _och_, yer_, _आरोग, _vydá,
+ {{0x26dc8a8e,0x443868f6,0x644568f7,0x44214fbe}}, // _buvo_, xer_, _nahi, _nch_,
+ {{0x443804c7,0x444468f8,0xe8218076,0x7e7b803a}}, // ver_, _ra_, _माठा_, _klup,
+ {{0x44441f2c,0x44211523,0x443828be,0x9f410698}}, // _sa_, _ach_, wer_, rchè_,
+ {{0x64452100,0x20013f8c,0x57b8064a,0x1c200f3d}}, // _bahi, kahi_, _अर्ह, _बादल_,
+ {{0x3cfd8076,0x443868f9,0x2001123c,0x644568fa}}, // रीये_, uer_, jahi_, _cahi,
+ {{0x444413f0,0x44382267,0x61fd2a65,0xb5fda19f}}, // _va_, rer_, _besl, _pošl,
+ {{0x444468fb,0xe9da0e17,0x61fd017f,0xd5a70d86}}, // _wa_, ска_, _cesl, _टुकड़,
+ {{0x444468fc,0x6445152b,0x61fd00a2,0x61e45fd9}}, // [6d60] _ta_, _fahi, _desl, rcil,
+ {{0x4444146a,0xb90411bc,0x8fa60098,0x7e7ba256}}, // _ua_, _पं_, пазе, _alup,
+ {{0x25bf8510,0xc2f080ab,0x59b00035,0x7d19890d}}, // _azul_, _টিভি_, _फ़रवर, euws,
+ {{0x61fd6338,0x64451982,0x0dca8009,0x3e7b00f1}}, // _gesl, _zahi, блей_, _këta_,
+ {{0xa25b0142,0x48e312c0,0x64452abe,0x7c389dcc}}, // _khôn, _появ, _yahi, tevr,
+ {{0x25bf80dd,0x91bb0039,0x7e62b945,0xb87b0216}}, // _dzul_, _למכי, sjop, _cuíd,
+ {{0x8fa30c40,0x5e969a00,0xd8388353,0x07a30791}}, // _заре, _فلسط, več_, _зарн,
+ {{0x7e7b803a,0x60dd00dd,0xb95480e8,0x26dc8037}}, // _glup, _yusm, двищ, _ruvo_,
+ {{0x6d40b1e0,0x7984005d,0x26dc8904,0x2d828106}}, // _usma, nziw, _suvo_, _åke_,
+ {{0x443887ca,0x44e281fa,0x6c8692dc,0x2fd883ba}}, // _úr_, _ið_, _علام, ørg_,
+ {{0x644549ec,0x09b906bf,0xd838e150,0x7bda8420}}, // _rahi, _आर्य, reč_, _igtu,
+ {{0x644537f1,0xd6d78987,0xe821b792,0x442168fd}}, // _sahi, ять_, _माथा_, _sch_,
+ {{0x9f412b3c,0x644547c7,0x61fd68fe,0x200168ff}}, // rché_, _pahi, _resl, yahi_,
+ {{0x753d0063,0x6ab51094,0x26dc8693,0x29028110}}, // rwsz, ंत्र, _tuvo_, škas_,
+ {{0x64456900,0x3d12016f,0x2491080a,0xe821ab51}}, // _vahi, _तिथे_, rizm_, _माता_,
+ {{0x200160ea,0x60f911e9,0x644563ea,0x69da0087}}, // wahi_, жная_, _wahi, _şter,
+ {{0x200124ec,0xf9930077,0x644541a8,0xd2500065}}, // [6d70] tahi_, ربر_, _tahi, ھنے_,
+ {{0x201e81e2,0x26c48267,0x61fd0a15,0x3eb9232a}}, // ngti_, _ahmo_, _wesl, llst_,
+ {{0x44e2e901,0xb9040076,0x61fd4da6,0x7e7b80e7}}, // _að_, _पू_, _tesl, _plup,
+ {{0xa159259a,0x20010a4f,0xddd08110,0x19590a14}}, // _даму_, sahi_, _šešt, _дамы_,
+ {{0x8c459ccf,0x04459354,0x6d468805,0xb87b002a}}, // деле, делн, _škar, _puíd,
+ {{0x7e7b80b9,0x7eb60061,0x22496902,0xb5fd8619}}, // _wlup, rápi, ldak_, _košk,
+ {{0x7e6985f8,0x9f5ee903,0x9f3400e8,0xb5fd836f}}, // _toep, baté_, _пері, _jošk,
+ {{0xb5fd8db7,0x660d6904,0xcf9200be,0x7e7be905}}, // _mošk, _ndak, _סטן_, _ulup,
+ {{0x81b58a49,0x08c58328,0x21290812,0x7d0480f7}}, // ছেন_, мбин, ntah_, áist,
+ {{0x660d20f4,0x7d0484b7,0x2a7f9705,0xa8899138}}, // _adak, ġist, shub_, ійна_,
+ {{0xa25b00e7,0xaabf14a7,0x628081c0,0xb21b00ec}}, // _rhôn, ्षाक, bhmo, _klæd,
+ {{0x7bc18668,0x21290d8b,0x2249231b,0x38b40106}}, // _izlu, ktah_, jdak_, lärt_,
+ {{0xddc08efd,0xe81e1344,0xd945813a,0xd009a857}}, // _komš, _पाला_, _цели, _деле_,
+ {{0xb5fd8503,0x2a7d8069,0x741601ad,0x7afb0b80}}, // _bošk, _hlwb_, بوسا, ćutn,
+ {{0x09e61b4b,0x2129226e,0x394da0a5,0x41eb019d}}, // добн, etah_, lves_, _ụsọ_,
+ {{0xc3328051,0x64890110,0x7d09a3e7,0x212933bf}}, // כול_, džiu, _oves, ftah_,
+ {{0xa25b0104,0x63a487d9,0x3ced81c5,0xe29702df}}, // [6d80] _thôn, ğini, _ntev_, фат_,
+ {{0x6d44236a,0x63a48b16,0xa2be06f0,0xb5fb01fa}}, // _isia, şini, _वीर्, hjál,
+ {{0x7d09a0dc,0xa3b506a7,0x7eb883a8,0x9f5eb4d3}}, // _aves, _छुप_, lípi, taté_,
+ {{0x7c3c0102,0x20072320,0xdddb81d0,0x386a0428}}, // lerr, ónir_, _kouř, _wobr_,
+ {{0xa3cb890f,0x6489005c,0x9f5eaad5,0x98bd82d6}}, // रेस_, rživ, raté_, _aswč_,
+ {{0x394d8019,0x7c3c1473,0x38b40338,0x6d442aa0}}, // dves_, nerr, gärt_, _msia,
+ {{0x628080f1,0x6489026f,0xf743087e,0x9f5ee906}}, // thmo, mžit, _иеро, paté_,
+ {{0x7c3c5245,0x6d4462eb,0xb21b013c,0x7c2931ce}}, // herr, _osia, _glæd, đeri,
+ {{0x7c3c0bf0,0x14268187,0xa2daa0d8,0xdb088198}}, // kerr, _одам, _पंक्, _sydä,
+ {{0x6a7c80e7,0x62808168,0x7c3c1832,0x63ad807e}}, // _défa, shmo, jerr, şana,
+ {{0x6d446907,0x381704de,0x7d09af82,0xdb0b07c0}}, // _asia, וקים_, _zves, ünüz,
+ {{0x64488a98,0x443c8982,0x442ee908,0x787f80e7}}, // _hadi, lev_, lff_, _rêve,
+ {{0x6448927b,0x645a8122,0x7c3c6909,0x63a902d0}}, // _kadi, _knti, ferr, ğenm,
+ {{0x64488be6,0xb5fdd7c3,0x442ee90a,0x443c8074}}, // _jadi, _pošk, nff_, nev_,
+ {{0xa4d4035f,0x46a70009,0x62810110,0xf8378039}}, // торі, _ошиб, ūlom, _חנות_,
+ {{0xe0df0698,0x83fc812b,0x3204c75a,0x660d690b}}, // _può_, _dođi, namy_, _udak,
+ {{0x7c3c690c,0x443ce90d,0x7eb883a7,0x645a8980}}, // [6d90] berr, kev_, cípi, _onti,
+ {{0x3e7b020f,0x443c8805,0x7d1d530e,0x7c3c58cf}}, // _këto_, jev_, muss, cerr,
+ {{0x7d098067,0x21291a4d,0x7d1d2551,0xd148801c}}, // _sves, stah_, luss, _lễ_,
+ {{0xb5fd9dc1,0xc3320158,0x6aa280f1,0x6e3d4b48}}, // _koši, _גוט_, _njof, nesb,
+ {{0x6448e90e,0xe3b8880a,0x7e6d0420,0xb5fd8b80}}, // _badi, _ayı_, _moap, _joši,
+ {{0xdfc695a8,0x645a80dd,0x443c82f1,0x251c0e82}}, // _مي_, _cnti, gev_, קווא,
+ {{0xb5fd811f,0x6448e90f,0x1ee700d7,0x6600d497}}, // _loši, _dadi, _آوری_, _femk,
+ {{0x7c3c1124,0xb1459d32,0x7e6d002e,0x7d1d12a5}}, // zerr, енил, _noap, kuss,
+ {{0x7c252158,0x81b100c8,0x644884b9,0x9f410013}}, // _ochr, টের_, _fadi, ichí_,
+ {{0x6448e910,0xd1488028,0x7d1d62f1,0x443c807a}}, // _gadi, _dễ_, duss, cev_,
+ {{0x3ce04fac,0xa3cb83dd,0xe7ee86a7,0x394de911}}, // _suiv_, रेश_, छड़ा_, sves_,
+ {{0x29090009,0x290b0669,0x32048035,0x7c255377}}, // ksaa_, _ovca_, camy_, _achr,
+ {{0x7ae1e912,0xb87b03a8,0xa25b0036,0x7d1d6913}}, // _hult, _xuíc, _chôm, guss,
+ {{0x8fa5b4fb,0x61e9e914,0x64488be9,0x83fc80fe}}, // _пале, lcel, _xadi, _pođi,
+ {{0x2005e915,0x4256117e,0x7c3c2cd7,0x4425d0b1}}, // mali_, нтат, rerr, _mcl_,
+ {{0x61e9e916,0x7ae1e583,0x6d4401c5,0x7c3c09cb}}, // ncel, _mult, _tsia, serr,
+ {{0x443c8086,0x66066917,0x563700be,0x08c6393f}}, // [6da0] yev_, makk, _טאקע_, ебан,
+ {{0x2005e918,0x660660fe,0x3940009a,0xf1bf0032}}, // nali_, lakk, rwis_, _abás_,
+ {{0xabfb0051,0x66008b99,0xf7721ddd,0x7ae181f4}}, // _מהיר, _pemk, داء_, _nult,
+ {{0x6448e919,0x2005c0a6,0x66061f5d,0x645a80b9}}, // _sadi, hali_, nakk, _snti,
+ {{0x443ca52d,0x2005e3ff,0x4abf0076,0x6448e91a}}, // tev_, kali_, ्षरव, _padi,
+ {{0x6296691b,0x2005cb71,0x7ae19fdc,0x44e6001c}}, // liyo, jali_, _bult, _mô_,
+ {{0x443c92f1,0x2005e91c,0x1fb50ddc,0x64488ddc}}, // rev_, dali_, _астр, _vadi,
+ {{0xcb130051,0x443c9d43,0x6296691d,0x660622dc}}, // אלה_, sev_, niyo, jakk,
+ {{0x6448e91e,0x66061b6b,0xada39cfc,0x261a016f}}, // _tadi, dakk, _батл, _भाजी_,
+ {{0x2005e91f,0xd7f796fe,0x62961b28,0x320480ee}}, // gali_, ную_, hiyo, samy_,
+ {{0xb4cc93e5,0x62965ced,0x660602a6,0x61e9b603}}, // रगी_, kiyo, fakk, bcel,
+ {{0x7c2507b4,0x6e22829b,0x7d1d23b6,0x290902a3}}, // _schr, ngob, tuss, ysaa_,
+ {{0x44e60104,0x2005e920,0x62963be6,0x44336921}}, // _cô_, bali_, diyo, _pbx_,
+ {{0x20059429,0x6606008e,0xbcfb002a,0x7ae201a1}}, // cali_, aakk, bxén, _čotr,
+ {{0x66065bc7,0x7d1d6922,0x39ee0a2c,0xd7f88087}}, // bakk, suss, _ọzọ_, mbă_,
+ {{0xb5fd8390,0x7d1d0c4f,0xb0ec00ab,0x66e50084}}, // _toši, puss, _ওয়েব_, _dėkl,
+ {{0x38b400f2,0x68e2827e,0x51873678,0x320204e8}}, // [6db0] värr_, _juod, хува, _deky_,
+ {{0x7c25122e,0x68e28009,0x9f5807f1,0x7d0d17c9}}, // _uchr, _muod, _berà_, _ivas,
+ {{0x62961040,0x29090198,0x26c96923,0x44e60129}}, // biyo, ssaa_, _khao_, _zô_,
+ {{0x2005e924,0x628429d1,0x39468baf,0x9f5e80e1}}, // zali_, chio, _osos_, jatí_,
+ {{0xe8218697,0x200582ec,0x272a84be,0x44e600ff}}, // _माला_, yali_, hùn_, _xô_,
+ {{0x76498101,0xd11604de,0x44232f3e,0x224b1e44}}, // _waey, _בקשה_, ngj_, _hack_,
+ {{0xa3cb8063,0x76498708,0xb87b05e4,0x6606028d}}, // रें_, _taey, _guía, yakk,
+ {{0x2005e925,0xa562803d,0xb4bf008e,0x6a7c8036}}, // wali_, _چگون, ेषु_, _défo,
+ {{0x66064269,0x200304be,0xe9df157a,0x61e985d8}}, // vakk, _keji_, _raúl_, rcel,
+ {{0x7ebd03d3,0x61e9cdb0,0x68e28084,0x629661fb}}, // léph, scel, _duod, ziyo,
+ {{0x2005bae4,0x07a58112,0xf42700c8,0x394685b4}}, // rali_, танн, য়ার_, _esos_,
+ {{0x26c96926,0xd7f88087,0xfce61ac1,0x44e65931}}, // _chao_, abă_, комо, _pô_,
+ {{0x2005b22e,0xb6a339c3,0x629602d0,0xfd1f00e5}}, // pali_, риял, viyo, guì_,
+ {{0x44e60028,0x2005836a,0x44230789,0x9f580118}}, // _vô_, qali_, ggj_, _lerá_,
+ {{0x224b3624,0x629607d9,0x62843424,0x7d0d6927}}, // _back_, tiyo, thio, _evas,
+ {{0x224de928,0x44e61762,0xb5fd805c,0xcfb700ab}}, // ndek_, _tô_, _košu, জধান,
+ {{0x62966929,0x9f5807e2,0x290c01a9,0xbcfb002a}}, // [6dc0] riyo, _serà_, _ādas_, nxél,
+ {{0x92dc80c8,0x6296692a,0x66043416,0x628401c6}}, // তীয়_, siyo, _heik, shio,
+ {{0x394d0289,0x3a3fe92b,0x7d09005c,0x6a78007b}}, // _šest_, keup_, šest, _lífi,
+ {{0x6a7c80e7,0x644e5c09,0x9407811c,0x2003026b}}, // _réfo, ldbi, minə_, _eeji_,
+ {{0x6e228242,0x3cfd801b,0x63ad861c,0x6604692c}}, // rgob, रीले_, şanl, _meik,
+ {{0x66040357,0xdb1a8019,0x644e4f36,0x3a268087}}, // _leik, _aztá, ndbi, _scop_,
+ {{0xa0370bea,0xfbd580ab,0x3860c861,0x02a68087}}, // _שאלה_, _স্বত, _đir_, крим,
+ {{0xeaaf80f7,0x60dc07b8,0x6d5610ed,0x6a78016a}}, // يعي_, _hirm, _orya, _bífi,
+ {{0x60dc692d,0x2498008e,0x63a902d0,0xfce69133}}, // _kirm, kirm_, ğeni, _розо,
+ {{0x68e28364,0xe7ed81d0,0x63a9692e,0x00000000}}, // _vuod, चुला_, şeni, --,
+ {{0x7d0d0052,0x25be0201,0x6d56692f,0xba268bba}}, // _svas, ətli_, _arya, _адек,
+ {{0x645e0205,0x6f030073,0xc7b304de,0xa3b601ce}}, // _anpi, ânci, יבת_, _जड़_,
+ {{0x40348110,0xbebd00eb,0x3946c33a,0xceb384de}}, // _белс, ktīv, _usos_, שיר_,
+ {{0x628281e9,0x8f54803d,0x66e50110,0xf75480f7}}, // _hloo, _منتش, _sėkm, _منتج,
+ {{0xb5fd803b,0x69c600ad,0x26c90028,0x6d560ec8}}, // _košt, _azke, _thao_, _erya,
+ {{0x28c686b7,0x442d001b,0xb87b0580,0x645e332c}}, // _रीति, ře_, _atíp, _enpi,
+ {{0x628281e9,0xaca3019d,0x6a7c80e7,0xf80700e8}}, // [6dd0] _mloo, _alụg, _réfl, вчен,
+ {{0x9f580510,0x7e7d0123,0xa2c98054,0x764d01b4}}, // _será_, lksp, _हीन्, _haay,
+ {{0x224b04b8,0x764d2575,0x62828091,0x69c60102}}, // _tack_, _kaay, _oloo, _ezke,
+ {{0x628281c0,0x4b7a007c,0x60dc0a2a,0xed59816b}}, // _nloo, _נארו, _eirm, _lyže_,
+ {{0xed588cde,0x764d1341,0x9f58282b,0x7ae50009}}, // кої_, _maay, _verá_, _huht,
+ {{0x60dc6930,0x764d5d73,0x66e8800d,0x6282a57a}}, // _girm, _laay, _něko, _aloo,
+ {{0x7ae50006,0x9f580e67,0x224d837a,0x6282c7af}}, // _juht, _terá_, tdek_, _bloo,
+ {{0x2369803b,0x2018846d,0x7ae5421d,0x66e8801b}}, // ćaj_, órin_, _muht, _pěkn,
+ {{0x66042f4c,0x3a3f82f7,0x20c2807b,0x6e299c96}}, // _reik, reup_, rðin_, _iceb,
+ {{0x94078201,0x6d49c3cb,0x7989809a,0x91bc0039}}, // yinə_, _isea, rzew, ומחי,
+ {{0xa2f58278,0x764d4577,0x628281c6,0x79898035}}, // _спеч, _baay, _floo, szew,
+ {{0xddcd026f,0x764d486f,0x89b4819d,0xf38c83de}}, // _dlaž, _caay, ịtọs_, _קראָ,
+ {{0x61fbbf6d,0x660425e4,0xb87b0ece,0x22400110}}, // mbul, _veik, _ruín, veik_,
+ {{0x61fbcb9d,0x753b82a0,0x7e7d6931,0x7ae5050b}}, // lbul, _mpuz, ërpr, _buht,
+ {{0x387880f2,0x88838077,0x66041db4,0x30da80be}}, // örre_, _میکن, _teik, _אַהע,
+ {{0x94078201,0xb87b00f7,0x7e7d048d,0x6721e932}}, // rinə_, _buío, cksp, mulj,
+ {{0x60dc3d3b,0x94078201,0x22402ca2,0x98a00289}}, // [6de0] _pirm, sinə_, reik_, ntić_,
+ {{0x61ed1268,0x224033f9,0x764d0c2e,0xa84a0174}}, // acal, seik_, _zaay, علام_,
+ {{0xe61a828b,0x61fb883a,0x6d49e933,0xeb9a4ddf}}, // еде_, kbul, _asea, _чин_,
+ {{0x61ed6934,0x95ca9444,0xa96a0256,0xdfcf00f7}}, // ccal, нула_, _зима_, سين_,
+ {{0x6aa40087,0x98a005a2,0xe5a3951b,0x63a90799}}, // mnif, jtić_, бици, ßenv,
+ {{0x1aeb80c8,0x62829699,0xafe31ae1,0xbebd01a9}}, // _টিজে_, _sloo, _горл, ltīt,
+ {{0xb5fd9874,0x6282a29a,0x6721817f,0x6d49913b}}, // _pošt, _ploo, julj, _esea,
+ {{0x6721d464,0x98a00754,0x61fb8ce9,0x391481cf}}, // dulj, ftić_, gbul, амир,
+ {{0xddcd0a20,0x83fc8e78,0x201883a7,0x628286a8}}, // _slaž, _anđe, ório_, _vloo,
+ {{0xddcd1011,0x83fc9f3a,0x473495b7,0x764d26e8}}, // _plaž, _vođs, анос, _saay,
+ {{0x61fb8365,0x764d0637,0xf65f006a,0x2fd88106}}, // bbul, _paay, nlæg_, ärg_,
+ {{0x7bc88d11,0x91e68009,0x61ed0388,0x45d50c9b}}, // _izdu, _соде, xcal, ионс,
+ {{0x7ae501c2,0xd90e87d2,0x6d4f8118,0x25f403db}}, // _suht, _عید_, _ácat, ंड़ी_,
+ {{0x764d02a3,0x67d492bc,0x7ae504a2,0x2c7986c0}}, // _waay, _колу, _puht, _pèdi_,
+ {{0x8aa70951,0xb87b0013,0xd25b1fa9,0xddcd0d11}}, // град, _suío, нца_, _ulaž,
+ {{0xf65f0ae1,0x6aa41162,0x5faf064a,0x48fb801b}}, // dlæg_, gnif, _जुगल, लीको_,
+ {{0x61ed058d,0x98a91432,0x3ea900b9,0x940a0085}}, // [6df0] rcal, stač_, _ijat_, hibə_,
+ {{0x61fba008,0xf00b001c,0x98a90267,0x68e600fc}}, // zbul, _đẳng_, ptač_, _dukd,
+ {{0x61e2d527,0x2fc700f2,0x753b8140,0x828b8032}}, // _ogol, ång_, _spuz, _látọ,
+ {{0x61e2dbc5,0x5d55a262,0x635c8087,0x6aa4209c}}, // _ngol, икет, _mănă, cnif,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x61e2a256,0x4d7c00be,0x98a00088,0xf1bf046d}}, // _agol, ערגע, vtić_, _iyá_,
+ {{0x43698071,0x9f5c8580,0x961d0264,0x7d1d4255}}, // тайн_, _nevà_, ধ্যম_, hrss,
+ {{0x6d498013,0x3f8c826c,0x3dc900b9,0xbb3c0e82}}, // _tsea, dzdu_, _ozaw_, געהי,
+ {{0xb42787d2,0x6d498009,0xd5388872,0x798d09ab}}, // _معاو, _usea, _مثلا_, nzaw,
+ {{0x5d848013,0x25bf8201,0x290d922d,0x660ba414}}, // _الكل, _iyul_, rsea_, lagk,
+ {{0x205481bb,0x98a00d11,0x6443cdd1,0x5ba78009}}, // стыр, stić_, meni, _сраз,
+ {{0x6443c75a,0x6721e935,0x2cba0101,0x660b809c}}, // leni, rulj, _skpd_, nagk,
+ {{0x05d0016f,0x6cc600e8,0xfce61033,0x920801a9}}, // सेंब, ийма, рого, ntāž,
+ {{0x6443e936,0x629b8110,0x442790e1,0x6721cb25}}, // neni, liuo, ngn_, pulj,
+ {{0xe29a395b,0xa09a83c8,0xdca30572,0xf1bf026b}}, // вав_, _ביסט, цари, _ayá_,
+ {{0x6443bfae,0x63be026f,0x629b8110,0x6f1c9727}}, // heni, _vypn, niuo, árce,
+ {{0xe9da00e9,0x6443b301,0x2007820f,0x673c02ce}}, // [6e00] тка_, keni, _keni_, _sprj,
+ {{0x64438b5d,0xbebd0029,0x200780f1,0x7e608101}}, // jeni, stīt, _jeni_, _snmp,
+ {{0x6443e937,0xb4b205fc,0x2cb804fe,0x629b8084}}, // deni, टकी_, mord_, kiuo,
+ {{0x7d046938,0xb17b00f2,0x2a6d84b7,0x2cb83807}}, // _kwis, _igån, jjeb_, lord_,
+ {{0x6443ba6d,0x9c7c82d4,0xe80106a7,0x9f4e8242}}, // feni, _liče, _लड़का_, _jefò_,
+ {{0x7d041b74,0x2007e939,0xd7f200f7,0x2cb80687}}, // _mwis, _neni_, _ذكر_, nord_,
+ {{0x2d80b7f6,0x9c7cd717,0xd838a58f,0x8fa3693a}}, // šie_, _niče, niče_, _даре,
+ {{0x7bc89807,0xd257e93b,0x2a7f81c0,0xddcd17ac}}, // _vzdu, аць_, gkub_, _plaż,
+ {{0x30a69b93,0x6443e93c,0x2007e93d,0x506681e2}}, // иров, beni, _beni_, атна,
+ {{0x2007e93e,0x9da5803d,0x213d82c4,0x2cb81e7c}}, // _ceni_, _اصفه, _dpwh_, jord_,
+ {{0x6d4f8207,0xb5fd826c,0x2007c8a7,0x7d04022b}}, // _ácar, _košp, _deni_, _awis,
+ {{0xd83881d0,0x442c8229,0x3ea6e907,0x7761002a}}, // diče_, _hcd_, nnot_, _álxe,
+ {{0x6a78007b,0xb87b00f7,0x2007deea,0x443a693f}}, // _lífs, _suím, _feni_, _bbp_,
+ {{0xb8cb80c8,0x2007b4dd,0x68ed80f7,0x443a43b6}}, // _কী_, _geni_, _éada, _cbp_,
+ {{0x200ce940,0x443a0748,0x66e8928a,0x26c204e8}}, // madi_, _dbp_, _měkk, blko_,
+ {{0x200ce941,0x64439d21,0x20078042,0x32641454}}, // ladi_, zeni, _zeni_, отув,
+ {{0x20078a0b,0x2cb8209b,0x1bf88105,0x7c3a81a1}}, // [6e10] _yeni_, bord_, ुराल_, _obtr,
+ {{0x200cc06a,0x6d4d6942,0x6a7ce943,0x3eb910d1}}, // nadi_, _isaa, _défi, lost_,
+ {{0x7d040352,0x6443e944,0x3ce9022c,0xed59811f}}, // _zwis, veni, _huav_, _srž_,
+ {{0xa2830bca,0x200c8c47,0x3ce901c0,0x63bb03bf}}, // _بیرو, hadi_, _kuav_, ğund,
+ {{0x644398cc,0xe618821e,0x200cd1d6,0xa3d4cc5c}}, // teni, рді_, kadi_, सेस_,
+ {{0x3eb96945,0x6d4d022e,0x200c8239,0x308500f7}}, // host_, _msaa, jadi_, _السف,
+ {{0x6443e946,0x26cd8104,0x62898013,0x3eb907dd}}, // reni, _theo_, theo, kost_,
+ {{0x20078a8f,0x3e7b00f1,0x3eb90668,0x3a2944cf}}, // _seni_, _këtu_, jost_, ngap_,
+ {{0x629b949e,0x6e3bc1a8,0x3eb92771,0x3ce90282}}, // riuo, _ibub, dost_, _nuav_,
+ {{0x14748013,0x7c2d8067,0xac748013,0x92e200ab}}, // _والج, đars, _والش, নীয়_,
+ {{0x160b853f,0x6d4d0ed4,0x25f409c8,0x2007d5e8}}, // _हजार_, _asaa, ंडली_, _veni_,
+ {{0x443a00ee,0x394904b9,0x9c7c817f,0x8b9a81c6}}, // _sbp_, kwas_, _viče, _גבעת,
+ {{0x200c8587,0x6609e947,0x3ce90069,0x2007ab5f}}, // badi_, _meek, _cuav_, _teni_,
+ {{0xd838803b,0xa956812a,0x6609e948,0xab2a47f9}}, // tiče_, _לינק_, _leek, лога_,
+ {{0x8fa292e1,0xc0528039,0x9ef580f7,0x6e3b82ec}}, // _наше, _מזג_, _استش, _obub,
+ {{0x4f168159,0x3a29010b,0x2731d002,0x6609aef3}}, // _פֿון_, ggap_, mán_, _neek,
+ {{0x2731e949,0x7e64694a,0x49ca0bda,0x6d5b81c5}}, // [6e20] lán_, _inip, улан_, _nrua,
+ {{0xe9d71cf6,0x6e3b83c3,0x764604dc,0x81ac8264}}, // ску_, _abub, leky, _গরম_,
+ {{0x6609e94b,0x7e645cc4,0x2731d138,0xef1a4eaa}}, // _beek, _knip, nán_, уме_,
+ {{0xdddb82a5,0x3ce9146a,0x62860364,0xe821835a}}, // _sluš, _yuav_, _ulko, _माझा_,
+ {{0x27318013,0x6d5b8083,0x200cd038,0x3ce9022c}}, // hán_, _crua, yadi_, _xuav_,
+ {{0x6e3b8135,0x2b4c80b9,0x76460122,0x62998545}}, // _ebub, _psdc_, heky, _amwo,
+ {{0x33178013,0x7f860013,0x200ce510,0x7e64046d}}, // مزيد_, _الأن, vadi_, _onip,
+ {{0x50f4910b,0x200cab16,0x2731e94c,0x9695a30e}}, // озит, wadi_, dán_, _друш,
+ {{0x3eb95717,0x200ce94d,0x29121066,0x6d5be94e}}, // vost_, tadi_, nsya_, _grua,
+ {{0xdefa827e,0x7ae88019,0xb5fd8267,0x97148be2}}, // лым_, _tudt, _inša, омоц,
+ {{0x200ccd16,0xb8ef89a3,0x2731b38d,0x3ce901e9}}, // radi_, _वी_, gán_, _suav_,
+ {{0x200cb51c,0x3ce90069,0x7c2a83ba,0xddc401a9}}, // sadi_, _puav_, lgfr, _maiņ,
+ {{0xa1338077,0xddc09dd7,0x3ce90069,0x6d408176}}, // _فروش, _lomž, _quav_, _jpma,
+ {{0x657d08cf,0xc9178039,0x27318019,0x200cb697}}, // rysh, _פחות_, bán_, qadi_,
+ {{0x657d694f,0xd5ed0129,0x2731ab7e,0xd6db0163}}, // sysh, _toà, cán_, _йти_,
+ {{0x6d4d4950,0x78ba812b,0x139b80be,0x46a404bd}}, // _tsaa, lotv, אבלע, _најв,
+ {{0x660f05f5,0x90991354,0x66098cfa,0x3a2902f7}}, // [6e30] mack, ават_, _reek, rgap_,
+ {{0x660f6950,0x7ae29ffb,0x297b807c,0x68ed01b4}}, // lack, _liot, אטמא, rqad,
+ {{0x200a2b5f,0x645521a2,0x6d40a52a,0x394901e0}}, // _debi_, mdzi, _apma, swas_,
+ {{0x46c90074,0x644706c1,0x67250748,0xddc082d4}}, // रतिह, leji, ruhj, _domž,
+ {{0xd12687bd,0x2731e951,0x628d00f7,0x6489026c}}, // _تم_, zán_, mhao, džiz,
+ {{0x2731a5b3,0x6455629c,0x1bfb238c,0x6609e952}}, // yán_, ndzi, ्रिल_, _week,
+ {{0x78ba856f,0xa3d4816f,0x6609ce5b,0x0ec903eb}}, // dotv, सेल_, _teek, रताड,
+ {{0x2731aaa8,0x6d5b893a,0x539c00be,0x7ae2cb43}}, // ván_, _trua, טיוו, _ciot,
+ {{0x3ebf8867,0xc42b8277,0x940e8201,0x6f0701c0}}, // _ikut_, _مثال_, hifə_, _hwjc,
+ {{0x2731e953,0x487988cc,0x6455009a,0x78ba825b}}, // tán_, рсия_, jdzi, gotv,
+ {{0xbebd0029,0xcdc9012a,0xe7399a19,0xfe460c40}}, // drīb, _סך_, шен_, онно,
+ {{0x2731e954,0x9f586955,0x660f0bcf,0x7ae29de8}}, // rán_, _perú_, gack, _giot,
+ {{0x2731e956,0x2d84003e,0x44ef0201,0x3eadc3f7}}, // sán_, áme_, _cü_, _mjet_,
+ {{0x27318290,0x7ae2811b,0x2b410176,0x764601e0}}, // pán_, _ziot, _bphc_, seky,
+ {{0x68e3836a,0x44ef58ce,0x7ebd0866,0x63a90799}}, // _iind, _eü_, répr, ßenp,
+ {{0x68e3e957,0x7e6415d0,0x201800dd,0x21201809}}, // _hind, _unip, _sdri_, trih_,
+ {{0x3200008e,0x68e3b96a,0x81e200ab,0x2018008e}}, // [6e40] rbiy_, _kind, নুন_, _pdri_,
+ {{0x3eada69d,0x28cf8c1c,0x68e3e958,0x394f82df}}, // _ajet_, _सीमि, _jind, _msgs_,
+ {{0x68e3dccf,0x91e62f84,0x3ce306a7,0x201881d6}}, // _mind, _моне, _टूटे_, órii_,
+ {{0x68e38d23,0x628d311a,0x200a43f0,0x6a7c810c}}, // _lind, chao, _webi_, _yéfu,
+ {{0x7ae2bd27,0x3ead826c,0x395de959,0x8afc8035}}, // _riot, _djet_, _nrws_, _kręg,
+ {{0x68e3e95a,0x9c7cb3b5,0x2018011f,0x3ead8646}}, // _nind, _biča, _udri_, _ejet_,
+ {{0x44208179,0x78ba86ec,0xa2c6816f,0x7ae28035}}, // ği_, votv, ातल्, _piot,
+ {{0x68e3e95b,0x1fcf80c8,0x4420a987,0x628401bb}}, // _aind, রশাস, şi_, nkio,
+ {{0xfe43835f,0xf2d304de,0x48aa86f1,0x5ca6a3e7}}, // _інфо, ועה_, итом_, _диаб,
+ {{0x66028102,0x3878032e,0x44388014,0x6a7c8036}}, // lbok, _jorr_, _ùr_, _réfu,
+ {{0x44f08104,0x7ae2e95c,0x67218024,0x38781abf}}, // _hà_, _tiot, grlj, _morr_,
+ {{0x68e3905d,0x6455009a,0xd25080d5,0xa3c983b7}}, // _eind, wdzi, _سنت_, ोधर_,
+ {{0x61a58a16,0xd6d80cde,0x68e3e95d,0xa3dd2594}}, // खपृष, ють_, _find, तेन_,
+ {{0x44f0e95e,0xbf148077,0x443e8042,0x68e3cc49}}, // _mà_, _فوتب, _jbt_, _gind,
+ {{0x44f08142,0x645506d7,0x644742df,0x629f09ca}}, // _là_, rdzi, reji, tiqo,
+ {{0x68e3e95f,0x443ee960,0x672881a1,0x44ef4955}}, // _zind, _lbt_, mudj, _tü_,
+ {{0x44f0877f,0x629f6961,0x672880fe,0x628d6962}}, // [6e50] _nà_, riqo, ludj, rhao,
+ {{0x660d02f1,0x38780219,0x628d6963,0x27e700e8}}, // _heak, _corr_, shao, ønn_,
+ {{0x660d09ca,0x67288da8,0x44f0826b,0x7d161e1e}}, // _keak, nudj, _aà_, _zvys,
+ {{0x44f09b5b,0x6602e89f,0x9c7c8c95,0xf7738538}}, // _bà_, gbok, _riča, וקע_,
+ {{0x3ead820f,0x63ad0029,0x44f0801c,0x7522debf}}, // _vjet_, šinā, _cà_, groz,
+ {{0xe3df0a49,0x317a0051,0x44f0e964,0x2249011e}}, // _ব্যব, _הרשמ, _dà_, leak_,
+ {{0x8c440201,0x3ebf80dd,0x68eb8420,0x212900b9}}, // əşdi, _tkut_, _sugd, luah_,
+ {{0x44f090ab,0x6d5f0085,0x6721957c,0x2249011b}}, // _fà_, _orqa, vrlj, neak_,
+ {{0x6aa980b2,0x44f0e2be,0x273501a3,0xe52052bf}}, // rnef, _gà_, lån_, _बिधि_,
+ {{0x6721812b,0x6abbe965,0x68e38168,0x62840102}}, // trlj, souf, _qind, zkio,
+ {{0x6d5f15e8,0x68e3e966,0x7bc56967,0x629d1e7c}}, // _arqa, _vind, _nyhu, _omso,
+ {{0x7d09b312,0x68e3a093,0xee3f016b,0x64900074}}, // _kwes, _wind, bným_, _käig,
+ {{0x2249011e,0x81a79e91,0x660d4968,0x44f08129}}, // deak_, _بحال, _deak, _xà_,
+ {{0x9c7ca8e1,0x629d63dd,0x6721825b,0x7d09820c}}, // _ličn, _amso, prlj, _mwes,
+ {{0x3d1805fc,0x93590ef5,0x6728817f,0x394256e3}}, // _भटके_, орту_, cudj, _spks_,
+ {{0x6486807b,0xc7b284de,0x2249011b,0x7d098c6a}}, // _aðil, _רבה_, geak_, _owes,
+ {{0xd94315b7,0x66e8801b,0x2167205f,0xa96709a0}}, // [6e60] нети, _děku, пити_, пита_,
+ {{0x3ea203ab,0x44f0ca48,0xbcfb04c3,0x13e380ab}}, // likt_, _rà_, rxét, নুয়,
+ {{0xb8f31094,0x44f084be,0x443e86cb,0x76566968}}, // _ही_, _sà_, _rbt_, _kayy,
+ {{0x21291a67,0x44f0c462,0xe7ee0105,0xddcf9bf2}}, // buah_, _pà_, _जलवा_, _šeše,
+ {{0x38786969,0x443e80ee,0x78a1807b,0x7d098114}}, // _torr_, _pbt_, kilv, _cwes,
+ {{0x44f0d871,0x628b8558,0x3ea20a0f,0x290058aa}}, // _và_, _algo, hikt_, mpia_,
+ {{0x44f0877f,0x3af180ff,0x5f273099,0x6602e8a1}}, // _wà_, _cáp_, _норм_, pbok,
+ {{0x44f0e96a,0xee3f026f,0x443e80ee,0x60f895a6}}, // _tà_, tným_, _wbt_, ония_,
+ {{0x7ae64ea8,0x85068416,0x6d4401c0,0x629c8084}}, // _mikt, _روان, _npia, _įrod,
+ {{0x672302a5,0xb0be04c5,0x50be06b7,0x660d1922}}, // šnje, ्तिग, ्तिष, _seak,
+ {{0x660d0006,0x765603c3,0x66e8801b,0x3d0f016f}}, // _peak, _bayy, _někt, तीने_,
+ {{0x7ae6696b,0x290002f7,0x6458e96c,0x2249011b}}, // _nikt, kpia_, ldvi, xeak_,
+ {{0x6728b4c1,0xf8be052a,0x765600fc,0x78a1a26c}}, // sudj, ्ताय, _dayy, bilv,
+ {{0xb9db8051,0x64900406,0x644ab261,0x7ae6696d}}, // _החדש, _näid, nefi, _aikt,
+ {{0x083b82f6,0x2249011e,0x3edf01bc,0x7ae6696e}}, // _העול, teak_, _amụọ_, _bikt,
+ {{0xe8df801c,0x212909da,0x644a8114,0x765604b9}}, // ược_, tuah_, hefi, _gayy,
+ {{0x69c6032b,0x22490102,0x260b86a7,0x2eeee96f}}, // [6e70] _cyke, reak_, ारसी_, _huff_,
+ {{0x644ad869,0x212903ac,0x69c60106,0x200e89ab}}, // jefi, ruah_, _dyke, _jefi_,
+ {{0x21290ad4,0x3f871c2f,0x6d5d183d,0xa5bb0118}}, // suah_, ánu_, lvsa, rgóu,
+ {{0x273500f2,0x629d248d,0x3f8f009a,0x39520198}}, // rån_, _umso, ągu_, änsä_,
+ {{0x3f870052,0x9c7c80c3,0x3af18129,0x64576970}}, // šnu_, _vičn, _sáp_, _kaxi,
+ {{0xd7e68013,0xfd6201bc,0x644ae971,0x2eee8118}}, // _لك_, _akwọ, gefi, _ouff_,
+ {{0xe2998084,0x6a7c8036,0x93bc8493,0xe738062c}}, // жай_, _réfr, _scăr, _неё_,
+ {{0xe8f983c5,0x6aad6972,0x94110085,0x7d09820c}}, // оли_, nnaf, rizə_, _twes,
+ {{0x81b100c8,0x44330358,0x765600b9,0xd12e80f7}}, // য়ের_, _ocx_, _rayy, لمي_,
+ {{0x76561f37,0x7659c609,0x644ae973,0x9f5a0216}}, // _sayy, ldwy, cefi, capó_,
+ {{0x7e7be974,0x6d440098,0x200e8242,0x7e6992f1}}, // _koup, _spia, _defi_, _knep,
+ {{0x29000358,0x76560359,0xaf9897ae,0xe5a65102}}, // ypia_, _qayy, ятых_, дими,
+ {{0x7ae66975,0xdb1e016b,0x3ea22d00,0x61ebe02b}}, // _rikt, _vypí, rikt_, _iggl,
+ {{0xd9429928,0x2bda8c28,0x7ae66976,0x765600fc}}, // _реши, येवा, _sikt, _wayy,
+ {{0xfce335f7,0x64570201,0xa3d8009a,0x6441b035}}, // торо, _daxi, ़ें_, _mbli,
+ {{0x764b8019,0x6458816b,0xfa671a4a,0x644ae977}}, // jegy, zdvi, маск_, zefi,
+ {{0x7ae6088b,0x3ea00118,0x68e700f3,0x7659831d}}, // [6e80] _vikt, _imit_, _bijd, ddwy,
+ {{0xf42500c8,0xeb0d8105,0xc5f40326,0x7ae6309c}}, // ম্বর_, _सबूत_, _daɓe_, _wikt,
+ {{0xd70a10ac,0x7ae64180,0xbcfb0019,0x78be1699}}, // інде_, _tikt, tvég, ropv,
+ {{0x29001194,0x6441e978,0x7e7ba4cc,0xb87b0187}}, // ppia_, _abli, _coup, _suít,
+ {{0x6aa4004f,0x649002f1,0x64418362,0x644a8425}}, // miif, _täid, _bbli, tefi,
+ {{0x7414845b,0x44f40009,0xe1ff85e4,0x6fd6073c}}, // روبا, _mä_, ñón_, _भरपू,
+ {{0x17790364,0x3ea017fe,0x6b83831d,0x200ecc01}}, // _есть_, _omit_, gyng, _refi_,
+ {{0xf1bf00f7,0x68e701ed,0x5a348081,0x2eee8901}}, // _gcás_, _zijd, хнит, _ruff_,
+ {{0x7984400f,0xb87b002a,0x44f41a50,0x248c80ee}}, // nyiw, _guís, _nä_, _tldm_,
+ {{0x3ea00065,0x9c7c8289,0x7d02960c,0x7e7b8118}}, // _amit_, _kičm, mpos, _zoup,
+ {{0x7d0299dd,0x3ce78d86,0x98a9009a,0x9346804a}}, // lpos, _छूटे_, stać_, _інде,
+ {{0xc5f90074,0xbd6b08b0,0x645701b4,0x2ca301ed}}, // ंडेय_, орге_, _saxi, wijd_,
+ {{0x2ca30613,0x7d02d19f,0x200581cd,0x44331b11}}, // tijd_, npos, bbli_, _pcx_,
+ {{0x44f454b6,0x7d028b80,0x2eee8106,0xd7ef8a3d}}, // _dä_, ipos, _tuff_, _ју_,
+ {{0x68e70a0f,0x2ca300f3,0x6457002a,0x7d02808e}}, // _rijd, rijd_, _vaxi, hpos,
+ {{0x2738c92f,0xbcfb0019,0x645701b4,0x320f0061}}, // mén_, tvéd, _waxi, úgy_,
+ {{0x7d02e979,0x7e7b9cef,0x6aad19f0,0x5ea400f7}}, // [6e90] jpos, _roup, rnaf, _جميل,
+ {{0x6fa0000f,0xd01112dc,0x7e7ba7b5,0xd7f8802e}}, // _गेहू, الا_, _soup, ncă_,
+ {{0x7e7be31f,0x764b9fce,0x2738a190,0x2fc7006a}}, // _poup, tegy, nén_, æng_,
+ {{0x7d0d2425,0x29190858,0x35b5831a,0x44310118}}, // _kwas, _tvsa_, нбер, lgz_,
+ {{0x68e70613,0x7d0295d0,0x8498003d,0x7996009a}}, // _tijd, gpos, _رئیس_, czyw,
+ {{0x64900006,0x7d0d0fb7,0x2738804e,0x752b890d}}, // _täie, _mwas, kén_, rugz,
+ {{0x4fc4176e,0x7e7b86c0,0x27388019,0xce958098}}, // _аста, _toup, jén_, _завъ,
+ {{0xd24e80f7,0xe3b09ef7,0x20111fa4,0x2738881b}}, // منى_, جره_, _hezi_, dén_,
+ {{0x2011026c,0x9f4a0032,0x63538085,0xbebd0ec3}}, // _kezi_, _agbè_, _mənə, trīn,
+ {{0x20110247,0x3ea01de6,0x6441972b,0x81b10264}}, // _jezi_, _smit_, _ubli, _ওরা_,
+ {{0x394684df,0x2011697a,0x3dc91066,0x7d0d4c3e}}, // _epos_, _mezi_, _ayaw_, _awas,
+ {{0x28cf800f,0xb4d8000f,0xdd1e0087,0x2011005d}}, // _सीरि, िष्_, _câşt, _lezi_,
+ {{0x20c28125,0x5fd1901e,0x3eaf81a8,0x224d8cdb}}, // rðir_, _हरवल, ingt_, meek_,
+ {{0x27388333,0x7afe0748,0x224d8102,0x20113275}}, // bén_, _ptpt, leek_, _nezi_,
+ {{0x2738957a,0xcb672762,0x7d028f28,0x21391a89}}, // cén_, наре_, zpos, _aqsh_,
+ {{0x7d029c59,0x9f5e84e8,0x44f4697b,0xa3d786a7}}, // ypos, natý_, _tä_, _सरन_,
+ {{0x95560013,0x7d0d0355,0x26c20c03,0x764400b4}}, // [6ea0] _أخبا, _gwas, moko_, _ibiy,
+ {{0x1fb659a2,0x6d5641ac,0x26c25854,0x628f0661}}, // нсер, _isya, loko_, _elco,
+ {{0x4444146a,0x03a60729,0x7d1be97c,0xbcfb0036}}, // _ib_, _чино, _avus, rvée,
+ {{0x44441cbc,0x7d02803a,0x60c18502,0x81e200c8}}, // _hb_, tpos, holm, নুষ_,
+ {{0x64a6a549,0xe89b0059,0x27388c83,0xdca68294}}, // нада, _çağı, zén_, нади,
+ {{0x67230025,0xa8028201,0x98a48029,0x26c2697d}}, // šnja, _çıxa, kumā_, hoko_,
+ {{0x26c2697e,0x98a48029,0x7d02a91e,0x44440c6b}}, // koko_, jumā_, spos, _mb_,
+ {{0x644e697f,0x65640cf4,0x66e38a2e,0x26c26980}}, // hebi, _orih, _боса, joko_,
+ {{0x44446981,0x26c24901,0x644e6982,0xc05ab73a}}, // _ob_, doko_, kebi, пін_,
+ {{0x273893af,0x644e0c70,0x7644313c,0x8b96917e}}, // tén_, jebi, _abiy, _преч,
+ {{0x6d563c8e,0x56956983,0xd7f88087,0x9c7c8669}}, // _asya, вант, rcă_, _ličk,
+ {{0x44446984,0x273882b7,0x645ae985,0xd7f8802e}}, // _ab_, rén_, _hati, scă_,
+ {{0x645aa0d4,0x83fc8267,0x225181d0,0x22593ca7}}, // _kati, _anđi, ánků_, _rask_,
+ {{0x60c1a34a,0x9f58e986,0x645ad4d2,0x63538201}}, // colm, úró_, _jati, _sənə,
+ {{0xaa671908,0x645a9b7b,0x5b241663,0xb86589a7}}, // нтак, _mati, льта, _جانو,
+ {{0x645ab3ce,0x65640f06,0xd2582902,0x7d0d66db}}, // _lati, _frih, нцу_, _twas,
+ {{0x7e6d6987,0xdc3c826c,0x2b52809a,0x7ebd0061}}, // [6eb0] _inap, _ašči, łych_, képz,
+ {{0x645ae988,0xd91b810f,0x644e6989,0x655601c6}}, // _nati, פובל, cebi, _סבתא_,
+ {{0x2011698a,0x7e6d03a6,0x4ab28076,0xceeb026a}}, // _vezi_, _knap, ुकाव, سران_,
+ {{0x9f583d45,0x765d006a,0x531a00be,0x9f5ebb7a}}, // _però_, ndsy, _קורצ, lató_,
+ {{0x3209535b,0xddc40087,0xdfc688f9,0x760c00be}}, // mbay_, _iniţ, _چي_, פּאַ,
+ {{0x645ae98b,0x7d1c8029,0x224d9ad7,0x2129698c}}, // _cati, _ārst, week_, mrah_,
+ {{0x26c22221,0x60c19fe4,0x224de98d,0xdfd401e5}}, // yoko_, volm, teek_, _боры,
+ {{0x9f5e8019,0x7d1b826c,0x752480ce,0x644e698e}}, // ható_, _tvus, šiza, zebi,
+ {{0x6e94bd65,0xb3eb8f24,0x224d874c,0x644e0214}}, // _ситу, _فعال_, reek_, yebi,
+ {{0x7e6d698f,0x224d82d6,0x212909ca,0x4421012b}}, // _anap, seek_, irah_, _ndh_,
+ {{0x60c1be5a,0x644e04e6,0x3209009c,0xb4cd904f}}, // rolm, vebi, kbay_, रती_,
+ {{0x65646990,0x645ae991,0x644552b2,0x2129288e}}, // _prih, _zati, _abhi, krah_,
+ {{0x50b5932a,0xe1f08829,0x26c216d8,0x21290455}}, // _иску, _اسم_, roko_, jrah_,
+ {{0x61fd6992,0x44443c32,0x64948782,0x9c7c826c}}, // _afsl, _qb_, _gàid, _ričk,
+ {{0xaac61b9a,0x645c22ba,0x26c2013e,0x7aeb8122}}, // نترن, rdri, poko_, _kigt,
+ {{0x444401c5,0x7afc8065,0x2ca7b278,0x644e6993}}, // _wb_, _érte, hind_, sebi,
+ {{0x61fb8272,0x2ca7949d,0x212909e1,0xc5f400fc}}, // [6ec0] icul, kind_, grah_, _gaɓa_,
+ {{0x44446994,0xe9d70c0e,0x395201b0,0x7aebe995}}, // _ub_, тку_, rwys_, _ligt,
+ {{0xe29a268a,0x645aa840,0xee3ac249,0x6d498118}}, // _жан_, _rati, дне_, _apea,
+ {{0x26c3003a,0x9bb70039,0x68ed0110,0x787e801b}}, // čkog_, _יהיה_, _žodž, _závě,
+ {{0x645aa612,0x3ea6e996,0xa2bf1a46,0x2ba7a5e8}}, // _pati, riot_, वकर्, _केबा,
+ {{0x9f58008b,0x031606a7,0x9c7c81a1,0x53d601a2}}, // _ferð_, _तबाह_, _kiči, _धराश,
+ {{0x645ae997,0x18a301e5,0x07a62098,0x7aebbbb0}}, // _vati, _тарм, _равн, _bigt,
+ {{0x69cb81b0,0x6728876c,0xcb130039,0x645ae3ea}}, // _byge, vrdj, בלה_, _wati,
+ {{0x645ac18b,0x273c1867,0x7aebe998,0x2ca79407}}, // _tati, lín_, _digt, bind_,
+ {{0x2bbb8105,0x2ca7db3d,0xc5f40326,0xdb1e062c}}, // _ऊँचा, cind_, _raɓa_, _kypä,
+ {{0x273c03b3,0x320917ea,0x212900b9,0x9c7ca8fc}}, // nín_, ybay_, zrah_, _niči,
+ {{0x44216999,0xb283bd73,0x290201a1,0x6f1ac432}}, // _sdh_, _вышк, _otka_, rstc,
+ {{0xa2d30074,0x3857826a,0x9f5e8019,0x7e6d02d4}}, // _भीष्, رشید_, tató_, _vnap,
+ {{0x3af8e99a,0x273c0333,0x9c7c8084,0x98a383a7}}, // _kép_, kín_, _biči, _киче,
+ {{0xa3dd0321,0x9f5e8722,0x273c00e1,0x64450144}}, // तें_, rató_, jín_, _vbhi,
+ {{0x31668025,0x21291999,0x273c01ac,0x9c7c817f}}, // _kroz_, trah_, dín_, _diči,
+ {{0x2246803d,0x2ab701b9,0xb5fb0118,0x9f5e8216}}, // [6ed0] _mbok_, għba_, rdád, pató_,
+ {{0x66df1482,0x44210072,0x7d1b1f90,0x765b84b9}}, // _tūks, _udh_, áusu, _sauy,
+ {{0x2ca7e99b,0xb8fa2303,0x2129059e,0xdb1e80e7}}, // vind_, _डी_, srah_, _évèn,
+ {{0x6723003a,0x2ca78a15,0x212901bf,0x9f58007b}}, // šnjo, wind_, prah_, _verð_,
+ {{0x7aebe99c,0x454580f7,0x2ca7b760,0x961d81a9}}, // _rigt, انتق, tind_, _noņe,
+ {{0xb4cd89a3,0x6acb8f97,0x273c4bbe,0x7aeb868b}}, // रते_, ात्र, bín_, _sigt,
+ {{0x44299014,0x2ca7801f,0x447c0158,0x69cb806a}}, // ğa_, rind_, ונגע, _syge,
+ {{0x4429880a,0x3166812b,0x2ca7e99d,0x649007b8}}, // şa_, _broz_, sind_, _näin,
+ {{0x7aeb8022,0x83fc8088,0x101584cf,0x2d99804a}}, // _vigt, _anđu, льня, _åse_,
+ {{0xd7098009,0x69cb84e8,0x20558185,0x44f9810c}}, // нное_, _vyge, утир, _iè_,
+ {{0x7c3800f2,0x44f9801c,0x9eaa15fe,0x16aa09a0}}, // _övri, _hè_, евна_, евни_,
+ {{0x44f9bdb1,0xb8038ebf,0x69cbac15,0x00000000}}, // _kè_, _श्रम_, _tyge, --,
+ {{0x273c03b0,0x25e68035,0xa3e6064a,0x5e5683de}}, // zín_, जेपी_, बेन_, נישע_,
+ {{0x6aa994f9,0x44f9c048,0x2eed8e61,0x6cd600f7}}, // lief, _mè_, _lief_, اقسا,
+ {{0x44f986fe,0xeb9a1194,0x60c5008e,0x273c235e}}, // _lè_, хив_, dohm, xín_,
+ {{0x225f859c,0x273c00e1,0x628d04a7,0xfaa3259a}}, // nduk_, vín_, gkao, басо,
+ {{0x41271adf,0xe0da2300,0x7989822e,0x213f8057}}, // [6ee0] лото_, ева_, nyew, ntuh_,
+ {{0x273c08a9,0x459b02f6,0x7d04018e,0x6aa9d2b2}}, // tín_, _נסיע, _itis, hief,
+ {{0xe4570158,0x645e0420,0x3dcd8326,0xfaa60dfd}}, // _גייט_, _iapi, _ayew_, _бано,
+ {{0x44f9c46f,0x273c64db,0x645e509c,0x225fe99e}}, // _bè_, rín_, _hapi, jduk_,
+ {{0x63b60029,0x76428239,0x225f80b9,0xd838816b}}, // šanā, rfoy, dduk_, siči_,
+ {{0x6adcdd8d,0x44f9bdc2,0x10a60652,0x645e00f1}}, // मग्र, _dè_, _сигн, _japi,
+ {{0x645e699f,0x394069a0,0x201a02c4,0x439481e2}}, // _mapi, ltis_, mapi_, райс,
+ {{0x44f98f23,0x7d0400d2,0x201a1066,0x6e2436ed}}, // _fè_, _otis, lapi_, _ndib,
+ {{0x394069a1,0xc61086b7,0x225d84b7,0xcc368591}}, // ntis_, ारीय_, _dawk_, _مراع,
+ {{0x645e5fba,0x31570051,0x6e240102,0x39570039}}, // _napi, ניין_, _adib, נשים_,
+ {{0x7d045fcc,0xb866803d,0x394002f1,0xc6000264}}, // _atis, ناگو, htis_, ্ণনা_,
+ {{0x394029fb,0x41b40072,0x645e3735,0x68ee10ba}}, // ktis_, ंपास, _aapi, _libd,
+ {{0x201a0500,0xb5fd886f,0xeda700ca,0x443a0036}}, // kapi_, _inšt, ушно, _bcp_,
+ {{0x645e3206,0x6e240085,0x313784de,0x66d61de9}}, // _capi, _edib, _ערוך_, ráka,
+ {{0x201a0867,0x645e033e,0x7d04006f,0xf53f01a3}}, // dapi_, _dapi, _etis, stå_,
+ {{0x68ed80f7,0x9258181f,0x67008135,0x443a07b6}}, // _éadr, ласт_, _ịkwa, _ecp_,
+ {{0xf8070e49,0x6448816b,0x39400084,0x497502cb}}, // [6ef0] гчен, _obdi, gtis_, шлас,
+ {{0xddc40025,0x44f98324,0x201a1dde,0x645e584a}}, // _uniš, _rè_, gapi_, _gapi,
+ {{0x44f9c051,0x501b8039,0x23fa0039,0x68ee020d}}, // _sè_, גובו, _בהתא, _dibd,
+ {{0x645e68c1,0x44f9c051,0x6448b08a,0xd838b5ca}}, // _zapi, _pè_, _abdi, miču_,
+ {{0xa3a915a7,0xbcfb0019,0xfd1f3d36,0xed599bdc}}, // _खेत_, zvén, nsì_, тол_,
+ {{0x29090009,0x201a1eb1,0x63858198,0x1abd00ab}}, // mpaa_, capi_, игла, _আওয়া,
+ {{0x44f986fe,0x6aa98e19,0x3f870074,0xe8d60039}}, // _wè_, tief, änud_, _כושר_,
+ {{0x44f9c051,0xca8580e5,0xa92594b7,0x31258037}}, // _tè_, ргий, адил, адиг,
+ {{0x81a880c8,0x66160125,0x6aa9aa33,0x2b4c80ee}}, // _খুব_, _reyk, rief, _epdc_,
+ {{0xe8e00028,0xa3b569a2,0x69cf2817,0x656982c4}}, // _địa_, जपा_, _myce, _ireh,
+ {{0x7d045717,0x6d4d69a3,0x32559cf8,0xe73984ae}}, // _stis, _apaa, _ввер, њем_,
+ {{0x645e3f06,0x6b7a8158,0xbcfb0019,0xd5e60294}}, // _sapi, _ברענ, rvén, ижни,
+ {{0x2d9d8578,0x15ba01e5,0x7649804f,0xc8b48fd3}}, // nzwe_, тыны_, _mbey, _گلوک,
+ {{0xafe58009,0xab2a47d1,0x2aba81a9,0xc5f400fc}}, // _колл, кога_, mība_, _taɓo_,
+ {{0x63831541,0xc7a2a28e,0x7649e9a4,0x201a05f3}}, // огра, _мишк, _obey, vapi_,
+ {{0x394069a5,0x4425b0b2,0x645e0010,0x602800eb}}, // ttis_, _ldl_, _wapi, zīmē,
+ {{0x201a14ff,0x7d04025b,0x2aba80eb,0x7aef10b6}}, // [6f00] tapi_, _utis, nība_, _dict,
+ {{0x394069a6,0x6e3b802a,0x60260ba7,0xed4f003d}}, // rtis_, _acub, рджа, _کپی_,
+ {{0x394069a7,0x547b0039,0x324617c8,0x41d50816}}, // stis_, _סטטו, _кенг, _दरअस,
+ {{0x201a17a0,0xdfcf00f7,0x39400662,0x0566a05f}}, // sapi_, ذين_, ptis_, ивен,
+ {{0x68ee04b7,0x83fc81a1,0x7ae42776,0x09e40264}}, // _tibd, _lađe, mmit, _ফ্রা,
+ {{0x656981ec,0x7ae412a5,0xc7b8812b,0x2aba81a9}}, // _dreh, lmit, lađe_, dība_,
+ {{0x83fc8067,0x2018287d,0x2348826a,0x66dd0019}}, // _nađe, _ieri_, استی_, léke,
+ {{0xe8df8104,0x7ae469a8,0x6490037b,0xc7b88289}}, // ước_, nmit, _säil, nađe_,
+ {{0x65698836,0x7ae46004,0x5ecc8264,0x3afc0a05}}, // _greh, imit, িষদে, _líp_,
+ {{0x2018130c,0x2ba785fc,0x7ae469a9,0x7e6099ad}}, // _jeri_, _केवा, hmit, _hamp,
+ {{0xb8fd8d38,0x501b0051,0x20185715,0x6d4d03b2}}, // _थी_, כויו, _meri_, _spaa,
+ {{0x2d8b009a,0x212003f8,0x44380114,0x614681e2}}, // tyce_, ksih_, egr_, _веда,
+ {{0x7e608812,0x2aba80eb,0xa3e2a23a,0x3a2680b9}}, // _mamp, cība_, नेर_, _idop_,
+ {{0x442d02af,0xd8388024,0x634a8201,0x2018499f}}, // ße_, tiču_, _gənc, _neri_,
+ {{0x0b882344,0x938815d2,0x7aef494b,0x83fc8699}}, // асти_, аста_, _pict, _gađe,
+ {{0x7e60e9aa,0x7ae40bb1,0xbea40992,0xd838817f}}, // _namp, gmit, _мајк, riču_,
+ {{0x661d69ab,0x20185996,0x51f801bb,0x3ea9120e}}, // [6f10] mask, _beri_, рную_, _imat_,
+ {{0xdb1a80e7,0x6d4286a9,0x7af70216,0x7e6082d5}}, // _exté, dtoa, _tuxt, _aamp,
+ {{0x20182b42,0xd7f801a1,0x7ce0016d,0x6d5b80dd}}, // _deri_, руу_, förf, _ssua,
+ {{0xd2469301,0x44fd446f,0x65698ed6,0x64556376}}, // _عن_, _kì_, _preh, lezi,
+ {{0x7e60e9ac,0x442594ed,0x20183550,0x44fd6184}}, // _damp, _pdl_, _feri_, _jì_,
+ {{0x44fd1b4c,0x64555380,0x2018571a,0x661d4495}}, // _mì_, nezi, _geri_, hask,
+ {{0x44fd69ad,0xd7950013,0x1fe500c8,0x7e609670}}, // _lì_, _الأخ, _প্রস, _famp,
+ {{0x163493cd,0x6d5b81e9,0x7e6088b3,0x83fc81dd}}, // _меся, _tsua, _gamp, _rađe,
+ {{0x83fc80fe,0x44fd10ab,0x1c0b8770,0x20180059}}, // _sađe, _nì_, _स्थल_, _yeri_,
+ {{0x2001379b,0xdce40b5d,0x3ea9372d,0xf1b982a5}}, // nchi_, _prič, _amat_, jaše_,
+ {{0xb4e392c7,0xd3a79ebd,0xaec61ebd,0xf1b9817f}}, // नगी_, _трап, йбол, daše_,
+ {{0x1d0a2aee,0x661d1ce5,0x7e60e648,0x83fc812b}}, // леми_, gask, _xamp, _vađe,
+ {{0x24858074,0x645569ae,0x41299810,0xddcb8035}}, // _kolm_, fezi, _соло_, legł,
+ {{0x44fd69af,0xdce4026f,0x645569b0,0xfc0314c4}}, // _dì_, _trič, gezi, зпро,
+ {{0x66e64a38,0x634a8085,0xeb970568,0x7ae469b1}}, // _лока, _kəna, бир_, tmit,
+ {{0x66dd0065,0x20184022,0x2723817b,0xed5a02dc}}, // téke, _seri_, mın_, _бои_,
+ {{0x7e60c214,0x44fd0104,0xe9da05c2,0x27238182}}, // [6f20] _ramp, _gì_, ука_, lın_,
+ {{0x21200867,0xc6108ebf,0x81bc00c8,0x7e60b3e1}}, // rsih_, ार्य_, েইল_, _samp,
+ {{0x27238a0b,0x20186662,0x2a7f89ca,0x98a00267}}, // nın_, _veri_, kjub_, hrić_,
+ {{0xc3330051,0x98a002ee,0xb87b01df,0x26df8010}}, // _צור_, krić_, _xuíz, _chuo_,
+ {{0xf767850c,0x7bda8063,0x2018450b,0x9878b4d5}}, // _کا_, _sztu, _teri_, _ušću_,
+ {{0xb8960013,0x40960013,0x20010098,0x272393da}}, // _السع, _السر, cchi_, kın_,
+ {{0x62863126,0xa3e0023c,0xa3e29370,0x6d42c893}}, // _koko, _तरफ_, नें_, stoa,
+ {{0x2723884a,0x645569b2,0x628669b3,0xe643a09a}}, // dın_, zezi, _joko, _несп,
+ {{0xd01080f7,0x661d2d03,0x62860048,0x6455314f}}, // ولة_, vask, _moko, yezi,
+ {{0x62863c6d,0x660454f7,0x44fd04be,0xb87b01df}}, // _loko, _afik, _rì_, _suíz,
+ {{0x44fd077f,0x661d69b4,0x66d8826f,0x443eb0b2}}, // _sì_, task, níko, _ict_,
+ {{0x64551b66,0x6602b62e,0x7d160114,0x7ce01b48}}, // wezi, ncok, _bwys, hörd,
+ {{0x645534d7,0xf1b9812b,0xf1bf2fb5,0x60c8ac90}}, // tezi, taše_, _adán_, podm,
+ {{0x6723003b,0x44fd0104,0x66040b67,0xc0aa819f}}, // šnji, _vì_, _efik, _حاصل_,
+ {{0x201ee9b5,0x645524fc,0x628669b6,0x7afa90a6}}, // mati_, rezi, _boko, _jutt,
+ {{0x201e97f7,0x3ea914ec,0x44fd04be,0x7afaa776}}, // lati_, _umat_, _tì_, _mutt,
+ {{0x645509e8,0x7afae9b7,0x443ee9b8,0x7ce03a53}}, // [6f30] pezi, _lutt, _oct_, förd,
+ {{0x798d2fc5,0x26cb69b9,0x656d5d52,0x81e580ab}}, // ryaw, loco_, _irah, বুক_,
+ {{0x23a79094,0x20010098,0x7afa9333,0x798d07cf}}, // _केंद, rchi_, _nutt, syaw,
+ {{0x3eb94751,0x200169ba,0x656d00f1,0xf770003f}}, // nnst_, schi_, _krah, عال_,
+ {{0x201e874b,0x272382bb,0x2a3a0158,0x823a0158}}, // kati_, zın_, _דערמ, _דערצ,
+ {{0x272382bb,0xa3bd035a,0x98a00289,0x7afaa4cf}}, // yın_, _अशा_, vrić_, _butt,
+ {{0xeb999cd3,0xddc9826f,0x27238085,0x62863392}}, // рий_, _dneš, xın_, _yoko,
+ {{0xcfce80c8,0x7afae9bb,0x656d0668,0x386369bc}}, // রধান, _dutt, _orah, _hajr_,
+ {{0x443e8118,0xaca3019d,0xe8f6a1f6,0x6e298133}}, // _fct_, _alọg, олы_, _ideb,
+ {{0x201ee9bd,0x3eafa50b,0x7d048029,0x272382bb}}, // gati_, ligt_, ģist, tın_,
+ {{0xa3a90d38,0x656d69be,0x661b8a0f,0x7524452c}}, // _खेल_, _arah, _keuk, _sviz,
+ {{0x27238a0b,0x656d4f15,0x7d16031d,0x25f08d86}}, // rın_, _brah, _pwys, ुँची_,
+ {{0x62860038,0xdce0802e,0x2723817b,0x7ce001ec}}, // _roko, _urmă, sın_, höre,
+ {{0x201eb2a3,0x628605a3,0x656d1ab8,0x272383bf}}, // cati_, _soko, _drah, pın_,
+ {{0x6286085d,0xe51985e8,0x6e298cf0,0xaca380ff}}, // _poko, नीति_, _odeb, _huỳn,
+ {{0x83fc803b,0xb5fb69bf,0x6e29d840,0x661ba8c8}}, // _mađa, ndál, _ndeb, _neuk,
+ {{0x3eaf9b4a,0xed5a133c,0x614312a0,0xd94312e1}}, // [6f40] digt_, _топ_, мета, мети,
+ {{0x6e29930c,0x7ce0016d,0x7e6469c0,0xc7b8811f}}, // _adeb, före, _haip, lađa_,
+ {{0x628669c1,0x3169026c,0x7e640d55,0x661b9412}}, // _toko, avaz_, _kaip, _beuk,
+ {{0x823607d2,0x7afae9c2,0x7bde3102,0xddc981a1}}, // _بردا, _rutt, _izpu, _sneš,
+ {{0x7e64043b,0xa3e60f12,0x661be0a7,0x634a811c}}, // _maip, बेर_, _deuk, _cənn,
+ {{0x6e29e509,0x6d46036e,0x7e6469c3,0xc7b880d2}}, // _edeb, itka, _laip, hađa_,
+ {{0x7d09e726,0x46ca09c2,0x6d465033,0xddc99502}}, // _etes, रवाह, htka, _vneš,
+ {{0x09de1880,0x201eb55e,0x3a206532,0x7e643791}}, // _मराठ, wati_, naip_, _naip,
+ {{0x443e86e7,0x6d4401c0,0x26cb0037,0x6d462c2d}}, // _tct_, _nqia, voco_, jtka,
+ {{0x7afad1e4,0xb4c403bb,0xdb1e02be,0x7e7643cd}}, // _tutt, एको_, _expé, _anyp,
+ {{0xd7d0035a,0x83fc8289,0xdc3c8858,0x7afa8009}}, // _तुमच, _gađa, _ašći, _uutt,
+ {{0x656d09e6,0xc7b88a20,0xb5fd82a5,0x56941fb4}}, // _prah, gađa_, _kaša, дают,
+ {{0x201e8b50,0xb5fd82a5,0xfaf880eb,0x6fcae9c4}}, // pati_, _jaša, _trīs_, _सुरू,
+ {{0xcce70307,0x6458e9c5,0xb5fd8267,0x290002f7}}, // تسجي, nevi, _maša, dqia_,
+ {{0x6d460867,0xf1b98699,0xc7b88042,0x7ae281bf}}, // atka, maša_, bađa_, _khot,
+ {{0x656d38b9,0x201c8b80,0x764d69c6,0x3eaf8338}}, // _trah, _hevi_, _ubay, xigt_,
+ {{0xb5fd8067,0x7ae28c41,0x64588a41,0x656d1ae4}}, // [6f50] _naša, _mhot, kevi, _urah,
+ {{0x6458a771,0x7ce00884,0xf1b98796,0x80782218}}, // jevi, töre, naša_, обус_,
+ {{0x7d09e9c7,0x3eafba53,0x65608362,0x2bd20072}}, // _stes, tigt_, _asmh, _दुपा,
+ {{0x6e3d4255,0x201c9a4d,0x83fc812b,0xb5fd8bcf}}, // ngsb, _levi_, _rađa, _baša,
+ {{0x3eafb64c,0x260a800f,0x41e721d2,0x3d0f016f}}, // rigt_, ाड़ी_, _фіна, तीचे_,
+ {{0x64589699,0x3eaf94a2,0xf1b980ce,0x7ae2e9c8}}, // gevi, sigt_, jaša_, _ahot,
+ {{0x661b91ee,0x68fd0bbd,0xfd64882e,0x7ae28219}}, // _teuk, _husd, _karị, _bhot,
+ {{0x7ae28219,0x201c8267,0x3bbc0039,0x644a80e5}}, // _chot, _aevi_, ימוד, affi,
+ {{0xdb0a003e,0x7d098186,0x6458a7d1,0xf2d400be}}, // čnéh, _utes, bevi, ועק_,
+ {{0xd24e80d5,0x48798009,0x6458d257,0xf1b98390}}, // پنی_, ссия_, cevi, gaša_,
+ {{0x201ca367,0x7e6469c9,0x4abd1130,0x7ae2e9ca}}, // _devi_, _paip, ्काव, _fhot,
+ {{0x7ae2b861,0x98ad8353,0x2bc49344,0xed5a2c38}}, // _ghot, mreč_, _लुटा, бом_,
+ {{0xdd920154,0xf1b9817f,0x239081a9,0x6d465454}}, // روس_, baša_, _vējš_, utka,
+ {{0x8fa58c0e,0x3ebf812b,0x66dd003d,0x7ae2816b}}, // _нале, _ljut_, réka, _zhot,
+ {{0x68fd02af,0x67029993,0x3a200110,0x7e6469cb}}, // _ausd, _रूपक_, taip_, _taip,
+ {{0xe61f00e1,0x68f500eb,0x80d40035,0x7afebb40}}, // skôr_, _aizd, _बीजे, ípti,
+ {{0x68f5080a,0x64588201,0xdeb70039,0x66160267}}, // [6f60] _bizd, yevi, _מפקח_, čakč,
+ {{0xd12f05ff,0x3eade9cc,0x3ebfdb92,0x7ae9cc5f}}, // امل_, _amet_, _ajut_, hmet,
+ {{0x68f50508,0x7ae9d896,0xd4c504a3,0xabfb025f}}, // _dizd, kmet, _تغيي, _להיר,
+ {{0xde03a657,0xb5fd8df1,0x7ae9e9cd,0xdfcf15a9}}, // епци, _paša, jmet, ديم_,
+ {{0x7ae9c088,0x68fd02f7,0x244f8214,0x20079a5a}}, // dmet, _gusd, kımı_, _ofni_,
+ {{0x64a3a84d,0x7afe020f,0xb4d68576,0xb5fd876c}}, // нача, _kupt, सते_, _vaša,
+ {{0x7ae2cf8e,0x289b010f,0x2f018ff4,0x201c8242}}, // _phot, _פילא, _nóg_, _revi_,
+ {{0x6458e9ce,0x201ce9cf,0x2007808e,0x7ae9e9d0}}, // sevi, _sevi_, _afni_, gmet,
+ {{0x81ce00c8,0x7afe002e,0x3ea7874c,0x224200b9}}, // ষের_, _lupt, ënte_, _mckk_,
+ {{0x26c3003a,0x2bd204e5,0x67220029,0x2db780be}}, // čkom_, _दुभा, ņoju, _זלמן_,
+ {{0x7ae2820f,0x7afe214a,0x442ccb19,0x16350cb1}}, // _thot, _nupt, _idd_, меня,
+ {{0x20078125,0x7ae985b0,0x3f82811f,0x78ba8267}}, // _efni_, cmet, ćku_, sntv,
+ {{0x201cdebf,0x3ea40168,0x6d5d01ed,0x442c8144}}, // _tevi_, ëjta_, uwsa, _kdd_,
+ {{0xe7be2207,0x26d202ec,0x3ae180e1,0x68fd69d1}}, // ्थाप, _ekyo_, rópe_, _rusd,
+ {{0x67d39ae5,0x442c84b9,0x7afe0087,0x66d8807b}}, // _пошу, _mdd_, _cupt, ríkj,
+ {{0x628687b8,0x68f5061c,0xd7bb81c6,0x62840168}}, // ökoh, _sizd, _לצור, gjio,
+ {{0xdce98353,0x51871f84,0x83fc80d2,0x68f55d99}}, // [6f70] _sreč, чува, _nađo, _pizd,
+ {{0xdce98709,0x7d0d5b2e,0x3eadd0a3,0xb4d68072}}, // _preč, _itas, _smet_, सतो_,
+ {{0x6490025d,0x44231a8b,0x59f90364,0xf0938158}}, // _päiv, maj_, _меня_, ענע_,
+ {{0x442c8051,0x940c8201,0xe7300250,0xdce9807a}}, // _add_, _ildə_, اصل_, _vreč,
+ {{0x394681c0,0x290b0019,0xb5fd8115,0x629d00e5}}, // _nqos_, _utca_, _fašn, _ilso,
+ {{0x44230cb2,0x2a690006,0xdce98110,0x394926ff}}, // naj_, ldab_, _treč, mtas_,
+ {{0x26c3003a,0x39490219,0x6e2d003a,0x644183ac}}, // čkoj_, ltas_, _odab, _vcli,
+ {{0x442310af,0x442c9503,0x2a690006,0x7d0d05ee}}, // haj_, _edd_, ndab_, _otas,
+ {{0x44232dc8,0x394969d2,0x7ae9ddf5,0x6abb82fe}}, // kaj_, ntas_, rmet, rnuf,
+ {{0x26cfe9d3,0x6e2d69d4,0x442306e4,0xe8d90a2c}}, // logo_, _adab, jaj_, kwọ_,
+ {{0xb9048403,0x229c0125,0xd25180f7,0x244f8457}}, // _भी_, _líka_, _لنا_, rımı_,
+ {{0x394926ff,0x7c23adaf,0xddcd012b,0x26cfded0}}, // ktas_, lanr, _snaš, nogo_,
+ {{0xa3e000cf,0x20bf0ebf,0x95830009,0x7afe012b}}, // _तरह_, ्वाध, вляе, _supt,
+ {{0x649001c2,0x442ce9d5,0x7ce0016d,0x629d00f3}}, // _näit, _xdd_, föra, _also,
+ {{0xcee911fb,0x38ad0086,0x316da336,0xb5fd8b48}}, // ترین_, _görə_, lvez_, _našo,
+ {{0x628bad90,0xa3e29199,0x7c238ad4,0x3a3f90dc}}, // _jogo, नेट_, hanr, ngup_,
+ {{0x628b8db7,0x26cf822e,0x39494fc6,0xb5fde9d6}}, // [6f80] _mogo, dogo_, gtas_, _pašn,
+ {{0x44230025,0x6609c78b,0xddc41c18,0x65640234}}, // caj_, _afek, _kniž, _isih,
+ {{0x645c296d,0xfaa60084,0x39494bf9,0x26cfa41f}}, // meri, _жано, atas_, fogo_,
+ {{0x645c69d7,0x628bb8a3,0x26cf93a2,0xbc3a0061}}, // leri, _nogo, gogo_, _اسکا_,
+ {{0xfee98013,0x39492706,0x64a6a21f,0x6e228c41}}, // أعلى_, ctas_, мада, raob,
+ {{0x16bb02f1,0x7c2381e0,0x6e2281a1,0x628b83e4}}, // _उद्ब, ganr, saob, _aogo,
+ {{0x628be9d8,0x7529a499,0xbcfb0019,0x645c69d9}}, // _bogo, _evez, lvét, ieri,
+ {{0x4423024a,0x628b9727,0xa7fc83bf,0x63a30019}}, // zaj_, _cogo, rdım, _ünne,
+ {{0xa80287d9,0x041d80ab,0x7c23c462,0x442369da}}, // _çıka, _নারী_, banr, yaj_,
+ {{0x645c03c3,0x66dd0065,0x6e20e9db,0x7644593c}}, // jeri, téko, _hemb, _aciy,
+ {{0x7529de54,0x6e2094e5,0x442338e9,0x7d0d2b28}}, // _zvez, _kemb, vaj_, _stas,
+ {{0x44442065,0xe7e60063,0x628b811e,0x6e2088b3}}, // _ac_, _करना_, _gogo, _jemb,
+ {{0x6e20be91,0x44233e1d,0x41e283dd,0xf1e2847d}}, // _memb, taj_, _परिस, _परिन,
+ {{0x6e2089ca,0xa3e58697,0xdb1e0118,0x3a249e7a}}, // _lemb, _नरम_, _expí, lamp_,
+ {{0x4423036f,0x4444555a,0xdfd080f7,0xb5fd826c}}, // raj_, _dc_, _حيث_, _bašl,
+ {{0x59e284e5,0x44443a65,0x628bca25,0x39491cab}}, // _परार, _ec_, _xogo, ttas_,
+ {{0x44230db7,0xdced003e,0x7c23e9dc,0xddc4024a}}, // [6f90] paj_, _hrač, yanr, _zniž,
+ {{0x645c69dd,0x7bc510af,0xfe70803d,0x7bc022f8}}, // ceri, _txhu, یدن_, ğmur,
+ {{0x64450caa,0xe7ea0076,0x3a24a171,0x9f041368}}, // _ichi, झेला_, kamp_, نولو,
+ {{0x6e2090e1,0xdced0668,0x39493689,0xb5fd87aa}}, // _cemb, _mrač, ptas_, _vašo,
+ {{0x7c238648,0x64900074,0x442141a8,0x6e20e9de}}, // tanr, _täit, _keh_, _demb,
+ {{0xf41d00ab,0x4421423b,0x649008e5,0xd91a0039}}, // _তাঁর_, _jeh_, _häir, _מושל,
+ {{0x628b836f,0x442169df,0x765d69e0,0x7ce01e2b}}, // _pogo, _meh_, kesy, körn,
+ {{0xc1e3146d,0x442102c1,0xc24595e0,0x6e20859c}}, // _खराब_, _leh_, хник, _gemb,
+ {{0x644510ec,0x316d82be,0x628b8858,0x7e7be9e1}}, // _ochi, uvez_, _vogo, _inup,
+ {{0x644500f6,0xdced0a20,0x44210573,0x645c69e2}}, // _nchi, _brač, _neh_, xeri,
+ {{0x7aed0590,0x765d53fb,0x645c0e79,0xc0a89368}}, // kmat, fesy, veri, بايل_,
+ {{0x64451154,0xeb96893f,0x645c69e3,0xa5bb65a3}}, // _achi, _אדער_, weri, raón,
+ {{0x753d0065,0x7aed69e4,0x44215862,0x4444037e}}, // gusz, dmat, _beh_, _pc_,
+ {{0x44441532,0xb4da0816,0x64450037,0x26101a46}}, // _qc_, ़ते_, _cchi, _थ्री_,
+ {{0x444400a9,0x4421016f,0xe815835a,0xfddc8540}}, // _vc_, _deh_, _द्या_, _बर्फ,
+ {{0x644510ec,0x442169e5,0x28ab81a2,0x444469e6}}, // _echi, _eeh_, टोरि, _wc_,
+ {{0x6e20daed,0xdced27b1,0x59d58006,0x6e265f4a}}, // [6fa0] _remb, _zrač, _डुमर, makb,
+ {{0x6e20e9e7,0x6e264f71,0x7aed69e8,0x7e7b809c}}, // _semb, lakb, amat, _anup,
+ {{0x6e2094ec,0x7d00c225,0x6f0b8087,0xb5fd826c}}, // _pemb, _sums, _încă, _tašl,
+ {{0x64900074,0x212900b9,0x291d82c4,0xd7ef01a8}}, // _täis, bsah_, _owwa_, حكم_,
+ {{0x69d9d1b3,0x7c2182d6,0x6fd00ad5,0xbcfb69e9}}, // _nywe, _belr, _तुरं, lvér,
+ {{0xf366a4c8,0x765d009a,0x7e7b91d3,0x34ca86a7}}, // етин, zesy, _enup, ाकेद,
+ {{0x6e20e9ea,0x61e28065,0xbcfb01df,0x7e698118}}, // _temb, _szol, nvér, _faep,
+ {{0x290269eb,0x2d4984c3,0x7d00e9ec,0xb5fb07f0}}, // _kuka_, búe_, _tums, ldáv,
+ {{0x5bbd0107,0x634a8201,0x6e2609da,0x3a24aa33}}, // _ईश्व, _məni, dakb, ramp_,
+ {{0x290269ed,0x69d9831d,0x3ea0003d,0x3ae1af8a}}, // _muka_, _dywe, _alit_, rópa_,
+ {{0x59e28105,0x44212709,0x2129016b,0x93aa8290}}, // _परवर, _reh_, zsah_, _عاطف_,
+ {{0x644569ee,0x6f0307a3,0x44213d76,0xdced06c2}}, // _schi, ínci, _seh_, _vrač,
+ {{0x765d69ef,0x7aed61f6,0x656281ec,0xf8bf01d0}}, // resy, vmat, nwoh, _svém_,
+ {{0xe04a8077,0x753d009a,0xb4bd06ae,0x7aed242f}}, // _رشته_, rusz, ेके_, wmat,
+ {{0x765d0e87,0xdced0289,0x7aed18d6,0x75f68214}}, // pesy, _urač, tmat, _yüzü,
+ {{0x29020590,0x212900dd,0x753d29bd,0x55752481}}, // _buka_, tsah_, pusz, _агит,
+ {{0x7aed1140,0x442169f0,0x60f907ac,0x200b8063}}, // [6fb0] rmat, _teh_, дная_, ście_,
+ {{0x29022db6,0x2ec982f1,0x26c48668,0x290f00eb}}, // _duka_, िक्त, _ajmo_, īga_,
+ {{0x3209008e,0x2aba81a9,0xdce40115,0x9c7c8176}}, // pcay_, lību_, _srić, _chčm,
+ {{0x2d4984c3,0x6aa469f1,0xd7c81c81,0x6d4bcf38}}, // túe_, chif, رونه_, ttga,
+ {{0x2aba80eb,0x8c458152,0x386a69f2,0x660d0372}}, // nību_, веле, _fabr_, _mfak,
+ {{0x01360039,0x2298807b,0x64cbd556,0x34cb801b}}, // _ברשת_, _fékk_, िवेश, िवेद,
+ {{0x7bda998a,0x2d4984c3,0xdca29242,0x64a28009}}, // _cytu, súe_, ращи, раща,
+ {{0xe7e628b3,0x6d4b9770,0xb5fdac90,0xb5fb0019}}, // _करता_, ptga, _mašk, ldáu,
+ {{0x7ce050de,0xfc33804e,0x44f5a2f0,0xb5fd82d4}}, // förl, _محض_, _апас, _lašk,
+ {{0x5ba78ab2,0xb4bd13d9,0xd3a7813a,0x660d69f3}}, // _през, ेको_, _преп, _afak,
+ {{0x752d1087,0xb5fd80d2,0x6e26020d,0x09de1299}}, // _avaz, _našk, takb, _मर्य,
+ {{0x98a9009a,0x395900b9,0x96962bf3,0x644388e0}}, // brać_, _opss_, трош, lgni,
+ {{0xd94581ae,0x05631570,0x61459860,0x39590bda}}, // _рели, авян, _рела, _npss_,
+ {{0x634a8085,0x2a6d8114,0x394de9f4,0x752d0115}}, // _səni, ldeb_, mtes_, _dvaz,
+ {{0x29022dc4,0x394da962,0x4427b4c6,0x6e260072}}, // _suka_, ltes_, ian_, pakb,
+ {{0x628f16e8,0xdd921e13,0x3ea0009c,0x394d81b3}}, // _coco, _نوع_, _ulit_, otes_,
+ {{0x4427859c,0x394d821e,0x412701f3,0x2aba8029}}, // [6fc0] kan_, ntes_, кото_, cību_,
+ {{0x6f03885c,0x6aa469f5,0x38cb0fd3,0x6e240428}}, // _kunc, shif, والی_, _heib,
+ {{0x649b0f60,0x7d0405f8,0x394de9f6,0x6e24036e}}, // _חסיד, _huis, htes_, _keib,
+ {{0x6f03e9f7,0x7d040010,0x394d8412,0x2902203d}}, // _munc, _kuis, ktes_, _tuka_,
+ {{0x4427e9f8,0x7d040a0f,0x7bda809a,0x76428b0b}}, // fan_, _juis, _sytu, rgoy,
+ {{0x7d041e31,0x6e245bdf,0x4427c290,0x644380f2}}, // _muis, _leib, gan_, ggni,
+ {{0x2d808012,0x6f038510,0x7d040a52,0x61f78085}}, // ţie_, _nunc, _luis, əblə,
+ {{0x68fc0065,0x394ddbbc,0x4427e9f9,0xdee698c6}}, // _hird, ftes_, aan_, _роби,
+ {{0x442780ef,0x394db055,0x3940352d,0xd83a814c}}, // ban_, gtes_, nuis_, мэн_,
+ {{0x4427cc98,0x38a400f2,0x7bda809a,0xa786803d}}, // can_, _före_, _tytu, نشجو,
+ {{0x38a407d9,0x6f038509,0xc2469baa,0x2bd20b75}}, // _göre_, _cunc, _анек, _दुला,
+ {{0xc7b88b6d,0x6f03a756,0x52769e91,0x6e242509}}, // nađi_, _dunc, _جائز, _ceib,
+ {{0x394d87e2,0x98c69d2f,0x7d045d64,0x6f038580}}, // ctes_, _искл, _cuis, _eunc,
+ {{0x09e38a94,0x628f02a3,0x7d0416a1,0xb5fdc2f2}}, // _точн, _soco, _duis, _pašk,
+ {{0x628f69fa,0x7d0469fb,0x6f03e9fc,0x2aba80eb}}, // _poco, _euis, _gunc, sību_,
+ {{0x44278695,0x5a3419d9,0x7d041d7e,0x68fc0c5e}}, // zan_, инст, _fuis, _aird,
+ {{0x3eb90665,0x7d043b40,0x49750e11,0x394069fd}}, // [6fd0] mist_, _guis, ылас, guis_,
+ {{0x3eb91c1d,0x4427e9fe,0xb5fdbbbe,0x26c3026f}}, // list_, xan_, _tašk, čkou_,
+ {{0x4427e68f,0x7e6d32bb,0x6aa2e9ff,0xb5fd8da8}}, // van_, _kaap, _olof, _haši,
+ {{0xb5fd825b,0xdbdc808b,0x7e6d2f89,0x394dbe80}}, // _kaši, _ráði, _jaap, ytes_,
+ {{0x4427ea00,0x7e6d1998,0x518422f6,0x6d4f01b3}}, // tan_, _maap, _кура, ntca,
+ {{0xb5fd9bf2,0xdfc68013,0x4427a5e0,0x6aa28609}}, // _maši, _هي_, uan_, _alof,
+ {{0x7c25461d,0x225f8b99,0x3eb96a01,0xddc2b7f6}}, // _mehr, teuk_, kist_, rdoš,
+ {{0x4427c231,0x394dea02,0x569481e2,0x36d59f5e}}, // san_, ttes_, _каст, _собр,
+ {{0x6f038289,0x6e949860,0x394dc105,0xb4db0722}}, // _sunc, _титу, utes_, ntàs,
+ {{0x6f03ea03,0xb5fdea04,0x4427d13d,0xf9920039}}, // _punc, _naši, qan_, צרי_,
+ {{0x394dea05,0x5b158e17,0x7ea62d11,0x69dd0370}}, // stes_, _смет, _lópe, _lyse,
+ {{0x3eb90b74,0x4425ac15,0x394dea06,0xbddb0722}}, // gist_, _hel_, ptes_, rgèn,
+ {{0x4425ea07,0x7d045e81,0xb5fd9351,0x69dd46f0}}, // _kel_, _quis, _baši, _nyse,
+ {{0x4425803a,0x7afd615e,0x3ebe0065,0xe8bf000c}}, // _jel_, _aist, őtt_, ्वोच,
+ {{0x42560139,0x7c2501b9,0xc0528039,0x61e60035}}, // лтат, _dehr, _הזו_, _szkl,
+ {{0x7d046a08,0xb3d210be,0x68fc6a09,0x4425ea0a}}, // _tuis, _दुःख, _sird, _lel_,
+ {{0x7afd30cc,0x6d4f0bfd,0x3edb00be,0x6d5b8069}}, // [6fe0] _dist, ctca, _אַזא, _npua,
+ {{0x44258021,0xe9d76844,0x3b86148d,0x7afd031d}}, // _nel_, уку_, _блог, _eist,
+ {{0xb4c0053e,0xee3aea0b,0x7afd24be,0x6ac612dc}}, // ंची_, ене_, _fist, _مقدم,
+ {{0x394003d3,0x7afd6a0c,0x442a15e8,0x5f068b85}}, // puis_, _gist, lab_, _संस्_,
+ {{0xe7e60105,0x68fc00eb,0x39403b7c,0x69dd647e}}, // _करवा_, _tird, quis_, _gyse,
+ {{0x3ea6ea0d,0x4425ea0e,0x442a6a0f,0x7afd01ac}}, // shot_, _cel_, nab_, _zist,
+ {{0x4425d66d,0xb5fb6a10,0xc8db0039,0x8fa65c71}}, // _del_, ndár, _נקלט, _саве,
+ {{0x442a5abd,0xbea38e02,0xb4db0722,0xd91ab139}}, // hab_, _ласк, ntàr, ньо_,
+ {{0x4425d3b5,0x6e938064,0x442a6a11,0xe6938013}}, // _fel_, _القا, kab_, _القد,
+ {{0x2e240104,0x442a363c,0x7e6d156e,0x78748061}}, // _đườn, jab_, _saap, küvő,
+ {{0x324692dc,0xd7f781bb,0x442a6a12,0x7e6d10ab}}, // _مناف, лую_, dab_, _paap,
+ {{0x7c2ac6b8,0x59af8006,0x7e62ea13,0x80b68074}}, // lafr, _जेकर, neop, _ऋगवे,
+ {{0x7afd15a4,0x99bf00ab,0x212b80f7,0xb5fdac28}}, // _rist, _আর্ক, ácht_, _paši,
+ {{0x442a6a14,0x2d996a15,0x7c2aea16,0x673a82d4}}, // gab_, ryse_, nafr, prtj,
+ {{0x3eb96a17,0x447b80be,0x7e6d57d8,0x709b83de}}, // pist_, ענלע, _taap, _אױגו,
+ {{0xadf6800f,0x78ba8805,0x7afd00ee,0x386101ec}}, // ेशान_, litv, _qist, wehr_,
+ {{0x442a3091,0xf1b9c573,0xddc9904c,0x7afd291e}}, // [6ff0] bab_, taši_, _snež, _vist,
+ {{0xdddb8a56,0x78ba807a,0x442a0be9,0x1ae1a43f}}, // _použ, nitv, cab_, _पठ्ठ,
+ {{0x7afd6a18,0x69dd1c59,0x3a2900e4,0xa9240084}}, // _tist, _wyse, paap_, _įžei,
+ {{0xdce9803b,0x7afd05f3,0xaa671289,0x9f4a0091}}, // _kreć, _uist, _сток, _agbó_,
+ {{0x80bf0a49,0x44258813,0xa2b3809a,0xb5fd990f}}, // ্তব্, _pel_, इफस्, _lašv,
+ {{0x64475fc2,0x78ba807a,0xa3bc052a,0xc7b881a1}}, // ngji, jitv, _अधम_, lađu_,
+ {{0x442580b2,0x78ba8353,0xd4980039,0x66dd3d9b}}, // _vel_, ditv, רכות_, léku,
+ {{0x4425a64f,0xd6c3819f,0xc7b88289,0xddc40110}}, // _wel_, _امری, nađu_, _laiš,
+ {{0x844792dc,0x38a4016d,0x442a1061,0x6d5b96fb}}, // _مخال, _höra_, yab_, _upua,
+ {{0x6e2b8091,0x38a400f2,0x4425ea19,0xc62500ab}}, // lagb, _köra_, _uel_, _ভাষা_,
+ {{0x442a0a1b,0xaec60198,0x2902808b,0x5fcf90f7}}, // vab_, ибол, íkar_, _सुखल,
+ {{0xfd4c8104,0x442a14ec,0x6e2b820d,0x64400110}}, // _nhiề, wab_, nagb, ėmim,
+ {{0x442a032a,0x2bdb000f,0x9fa2808b,0x78ba82d4}}, // tab_, _मुबा, síða_, bitv,
+ {{0xb4c0053e,0x49b8806b,0x61e68214,0x659a80be}}, // ंचे_, _حامد_, ükle, דישק,
+ {{0x8a0612b2,0x3a2680ee,0x6e2bea1a,0x661d1be9}}, // изве, _yeop_, kagb, bbsk,
+ {{0x442a6a1b,0xfd4c801c,0x7bde016b,0xfd630133}}, // sab_, _chiề, _vypu, netị,
+
+ {{0x442a0205,0x6e2bea1c,0x7ce03a53,0x799b911f}}, // [7000] pab_, dagb, höri, nyuw,
+ {{0xdbdf008b,0x3ea48359,0xddc41d46,0x442a6a1d}}, // _tíði, _slmt_, _gaiš, qab_,
+ {{0x09e28aed,0xa7fc82bb,0x6e36001b,0x9f4a0091}}, // _पर्य, ldır, _kdyb, _agbò_,
+ {{0x98a01487,0x2eede9e6,0x752401d4,0xe7371508}}, // ksić_, _chef_, _kwiz, рец_,
+ {{0x25a09301,0xa7fc884a,0xa8060198,0x7c2a81fa}}, // áil_, ndır, _взгл, tafr,
+ {{0x7ce0456f,0x75242fc0,0x38a40106,0xe71a04a3}}, // föri, _mwiz, _föra_, _ليست_,
+ {{0xf767896c,0x38a404b8,0x78ba8db7,0x25a09807}}, // _با_, _göra_, vitv, šil_,
+ {{0xf1bf00f7,0x66040234,0xcb130039,0xb5fb0174}}, // _meán_, _ngik, חלה_, deán,
+ {{0x395201e2,0xe5a3835f,0x4c868171,0x78baea1e}}, // ntys_, _дити, _клав, titv,
+ {{0xb5fd8029,0x92c880ab,0xf1b98858,0xb4c010f7}}, // _pašv, োগে_, jašu_, ंचो_,
+ {{0xdce9803b,0x78ba8db7,0x6f00dd36,0x787900be}}, // _sreć, ritv, _kimc, אָרו,
+ {{0xdce982a5,0x7ceda49c,0x26c6ea1f,0x6f0081b4}}, // _preć, túrg, nnoo_, _jimc,
+ {{0x2a64807a,0x628281b4,0x78bac949,0x1cbb05de}}, // memb_, _inoo, pitv, ناسب_,
+ {{0x7c288988,0xdce9805c,0x66c80214,0x6282822c}}, // _jedr, _vreć, lıkl, _hnoo,
+ {{0x644708cf,0x7c28ea20,0xfd4c80ff,0x533412b2}}, // rgji, _medr, _phiề, жест,
+ {{0xdce9803a,0xb3458073,0xb5fd976d,0xf1bf2c6c}}, // _treć, luçã, _kašt, _deán_,
+ {{0xf8071bc1,0x645aea21,0xb5fd8fee,0x62828088}}, // [7010] ачен, _obti, _jašt, _mnoo,
+ {{0x2bd209a3,0x26d939fe,0xb5fd805c,0x80d1016f}}, // _दुका, loso_, _mašt, तकें,
+ {{0x6e2bac22,0x62828365,0xb5fdb464,0xfd4c8129}}, // tagb, _onoo, _lašt, _thiề,
+ {{0x39448029,0x63ad00eb,0xf1d82076,0x645aea22}}, // kums_, ģinā, _भुवन, _abti,
+ {{0x7c289682,0x39448341,0x6c7800d5,0xb5fd85b7}}, // _bedr, jums_, _صحیح_, _našt,
+ {{0x6282c577,0x645a8216,0x38a40106,0x6f0702d4}}, // _anoo, _cbti, _hörn_, _tujc,
+ {{0xb34580a9,0x26d904be,0x1cb88875,0x6e2be6f9}}, // duçã, koso_, جانب_, pagb,
+ {{0xb5fdea23,0x224b82af,0x26d91520,0x6f00a36f}}, // _bašt, ück_, joso_, _gimc,
+ {{0x26d9282b,0x394480eb,0x7c2893b0,0x5fcf80d4}}, // doso_, gums_, _fedr, _सुजल,
+ {{0x7c28905d,0x10a5a804,0x6aa99267,0x6282ea24}}, // _gedr, сион, shef, _enoo,
+ {{0x6e298cd8,0x7d099e4f,0x656d0a63,0x26d901e8}}, // _keeb, _hues, _asah, foso_,
+ {{0x7d018aa2,0x7d09808e,0xba7480f7,0xa7fc8085}}, // _hils, _kues, _وانت, tdır,
+ {{0x2d9db623,0xf1b98904,0x765bea25,0xc10880ff}}, // nywe_, tašu_, _mbuy, _hỗn_,
+ {{0x7d0985b4,0x98a7000d,0xb34583a7,0xb5fb68e6}}, // _mues, čně_, cuçã, rdáp,
+ {{0xb5fd803a,0x38a4007b,0x26d96a26,0x7d01c3a4}}, // _zašt, _börn_, boso_, _mils,
+ {{0x22490072,0x7d0980e7,0xa20623cd,0x26d9462f}}, // ggak_, _oues, спад, coso_,
+ {{0x7d099220,0x644107a3,0x8c4607a1,0xb5fb01a8}}, // [7020] _nues, ólic, _лепе, neál,
+ {{0x6d46127c,0x7d019be9,0x765b8478,0x39524cfa}}, // muka, _nils, _abuy, rtys_,
+ {{0x69cf2a70,0x6e29db89,0x6d4b016d,0x39520150}}, // _exce, _beeb, _ägar, stys_,
+ {{0x7c2e42ee,0xdfcf0624,0x528600f7,0x213fea27}}, // labr, رين_, _الحك, kruh_,
+ {{0x7d09840e,0x7d01a08e,0x6d466a28,0x7ae40197}}, // _cues, _bils, nuka, mlit,
+ {{0x657b8a56,0x7ae40fc0,0xb5fd8110,0x7c2e6a29}}, // _druh, llit, _rašt, nabr,
+ {{0x6d461600,0x26d902e7,0x6f0081e4,0x3e7f0198}}, // huka, yoso_, _timc, _jätä_,
+ {{0x6d460393,0xb5fd81e2,0x6b8302ba,0x3944ea2a}}, // kuka, _pašt, _ánge, tums_,
+ {{0x6d4634c3,0x7d01861b,0xddcd0b24,0x7ae4198d}}, // juka, _fils, _snaž, ilit,
+ {{0xdb038117,0x6d461922,0x7ae428c6,0x39400083}}, // szná, duka, hlit, iris_,
+ {{0xb4e480dc,0xdb1e0118,0x201e8110,0x7c2e0084}}, // नती_, _expú, rbti_, dabr,
+ {{0x442eea2b,0xdced005c,0x6d463174,0x6e29ea2c}}, // laf_, _krać, fuka, _xeeb,
+ {{0xf1948364,0xd012803d,0xe9ff80ff,0x6d466a2d}}, // _филь, _فلش_, _trạm_, guka,
+ {{0x442ed14b,0x39406a2e,0x7c2e01fa,0x7ae46a2f}}, // naf_, dris_, gabr, elit,
+ {{0x38710358,0x443f02bb,0x26d949d7,0x5695041c}}, // _razr_, ğu_, poso_, _макт,
+ {{0x6d466a30,0xe821058c,0x1dda0b04,0x442eab3d}}, // buka, यर्थ_, _युवत, haf_,
+ {{0x656d15bd,0x442e879f,0xa3df8768,0x6e29c86f}}, // [7030] _usah, kaf_, _धुन_, _reeb,
+ {{0x51f817ae,0xf8070a14,0x442e80fc,0x7ae43c72}}, // сную_, счан, jaf_, alit,
+ {{0xf7098104,0x7ae46a31,0x7d09ea32,0x39406a33}}, // _lần_, blit, _sues, aris_,
+ {{0x7d0985b4,0x394003ab,0xdced003b,0x657b816b}}, // _pues, bris_, _brać, _pruh,
+ {{0x7d09cadc,0x7d018029,0x24f98a14,0x6e298006}}, // _ques, _pils, анды_, _veeb,
+ {{0x2ecf00c8,0xcc898077,0x6e29ce58,0x7d0985a4}}, // রতিষ, شنبه_, _weeb, _vues,
+ {{0x61e2ea34,0x6e298069,0x212689c4,0x3ea2008b}}, // _ayol, _teeb, _awoh_, ykkt_,
+ {{0x290301cd,0x290b09cf,0x6d4600a4,0x765ba9da}}, // _hija_, _kuca_, yuka, _ubuy,
+ {{0xf7098104,0x7d018e1c,0xa3e58a74,0x657b82a0}}, // _cần_, _tils, _नरक_, _uruh,
+ {{0x3ea9059c,0xb5fb338d,0xd90c803d,0xf709801c}}, // _alat_, reál, دیو_, _dần_,
+ {{0x29031abf,0x3ea949d8,0x290b47ff,0x3e8a1242}}, // _mija_, _blat_, _luca_, айно_,
+ {{0x764bb286,0x29031abf,0x271780ff,0x291902f1}}, // nggy, _lija_, _lăng_, _otsa_,
+ {{0xf7098028,0x77e60e5b,0x6aad1bb7,0x29190282}}, // _gần_, _कर्क_, dhaf, _ntsa_,
+ {{0x27178104,0x3f77802e,0x3f9e8690,0x8fa59e18}}, // _năng_, _tău_, bytu_, _мале,
+ {{0x6d461904,0x7ae46a35,0x20078176,0x6fd90035}}, // suka, tlit, _igni_, _बुलं,
+ {{0x39404d7b,0x7c2e2475,0x442edfe3,0x290b2741}}, // tris_, sabr, zaf_, _buca_,
+ {{0x29030341,0x7ae44b4f,0x442e831d,0x2717801c}}, // [7040] _bija_, rlit, yaf_, _băng_,
+ {{0x3e7f0009,0x290300d2,0x7a3f077f,0x271780ff}}, // _tätä_, _cija_, _pàtà, _căng_,
+ {{0xb4d60bb8,0x7ae46a36,0x29030168,0xadc38032}}, // िकी_, plit, _dija_, _ifẹn,
+ {{0x6aad6a37,0x2f5a8158,0xdced005c,0x7ce48722}}, // chaf, ידענ, _prać, mòri,
+ {{0x628637b3,0x20078698,0x29036a38,0x200382d4}}, // _inko, _ogni_, _fija_, žjih_,
+ {{0xdced003a,0x54b78d13,0xb4e48740,0x6aa40d1b}}, // _vrać, огія_, नते_, lkif,
+ {{0x443a6a39,0xbea68162,0xb649026b,0x644a8123}}, // _mdp_, _давк, _emẹ̀, rgfi,
+ {{0x5a3487eb,0x443a6a3a,0x442e831d,0x2007a168}}, // чнит, _ldp_, saf_, _agni_,
+ {{0x2104901b,0x80bf00c8,0x6e2284b9,0x50669287}}, // रदेश_, ্তর্, mbob, отна,
+ {{0xa3bb0006,0x61e286c0,0x3ea91581,0x271780ff}}, // _आइल_, _vyol, _slat_, _xăng_,
+ {{0x4ada016f,0xa2e695b9,0x73368019,0x987b0538}}, // ढवाव, _дожд, _ذرائ, בריק,
+ {{0x6d4297fe,0x443a20ae,0xadc38032,0xf70980ff}}, // oroa, _adp_, _afẹn, _tần_,
+ {{0x442c8038,0x443a6a3b,0xf99f09c4,0x6f040162}}, // _ked_, _bdp_, _agèn_, _diic,
+ {{0x62866a3c,0xdc0880eb,0x442c941f,0xa0679ef8}}, // _anko, rēķi, _jed_, _мача_,
+ {{0x442c8f06,0x6f0410da,0x6d42b88d,0x2717801c}}, // _med_, _fiic, hroa, _răng_,
+ {{0x290b02a5,0x7ce01f0f,0x3ea902e8,0x6d42ea3d}}, // _puca_, förs, _ulat_, kroa,
+ {{0xb80e858c,0x2007011f,0x81dc80ab,0x442c831d}}, // [7050] _सलाम_, žnim_, ণের_, _oed_,
+ {{0x6d4292f5,0x8fe733e4,0x628614ee,0x6aad6a3e}}, // droa, оцед, _enko, shaf,
+ {{0x443125d8,0xdce29487,0x6e2d178f,0x6aad007b}}, // maz_, jvođ, _keab, phaf,
+ {{0x7d0d1083,0xdce282a5,0x41ca816f,0x59ca9905}}, // _kuas, dvođ, ापास, ापार,
+ {{0x27178028,0xa56792c8,0x25fe00d4,0x290333a0}}, // _tăng_, تدان, रेपी_, _tija_,
+ {{0x6e2d154c,0x9f350558,0x44316a3f,0x984817ae}}, // _leab, _негі, naz_, _мяса_,
+ {{0x394909ca,0x7ced8020,0x6e228041,0x95058019}}, // luas_, gúra, bbob, _کہتے_,
+ {{0x26cd8b80,0x443136b2,0xe3b08199,0x442cea40}}, // _ujeo_, haz_, خره_, _eed_,
+ {{0x057783f8,0x44311ee0,0x8d7784c0,0x7d0d0013}}, // _بازد, kaz_, _بازا, _nuas,
+ {{0x50db035a,0xdce40063,0x26ddbd66,0x442c8014}}, // भविष, _ksią, lowo_, _ged_,
+ {{0x2bdb34ec,0x25ee0076,0xd90f8065,0x39490282}}, // _मुला, _घरही_, ریں_, huas_,
+ {{0x443a111b,0x7529aeb1,0x7d0d4f74,0x39490359}}, // _sdp_, _kwez, _buas, kuas_,
+ {{0x443a130c,0x44314022,0x66dd0019,0x777a81c0}}, // _pdp_, faz_, rékp, avtx,
+ {{0xb4d612c7,0x75299af2,0x711a8039,0x44311c88}}, // िके_, _mwez, _הוספ, gaz_,
+ {{0x6e2d0013,0x7cf25986,0x75298234,0x07a58087}}, // _feab, værd, _lwez, _хайн,
+ {{0x6609baa0,0x7d0d082a,0xa5bb0118,0x2bdb0072}}, // _ngek, _fuas, mbón, _मुळा,
+ {{0xf2d28158,0x26c25df6,0x39492706,0xe297138c}}, // [7060] גען_, miko_, guas_, чат_,
+ {{0x629985c6,0x6aa40aa2,0xb06301ac,0x60c1880a}}, // _lowo, skif, jväč, nilm,
+ {{0xdc9b0451,0xc5f68bb8,0x7d1bd34f,0x9e6690ee}}, // _הייל, ेश्य_, _atus, звед,
+ {{0x442cea41,0x6299ea42,0x26c23cfc,0x6d428ea4}}, // _sed_, _nowo, niko_, troa,
+ {{0xe5a385f1,0x6da38e17,0x7e760069,0xfce69d7b}}, // ници, ница, _mayp, подо,
+ {{0x442c81cd,0x6609907a,0x26c236d0,0x6d42811e}}, // _qed_, _egek, hiko_, rroa,
+ {{0x442cd81e,0x26c2376a,0x26dd8573,0x7d1b8009}}, // _ved_, kiko_, bowo_, _etus,
+ {{0xfa970158,0x62998e60,0xb909001c,0xf709801c}}, // ִדיש_, _cowo, _nghị_, _cầm_,
+ {{0x25fe146d,0xd90d84c0,0x26c26a43,0x3ea68101}}, // रेमी_, لین_, diko_, mkot_,
+ {{0xf48495e4,0xb4d64765,0x7c3a813c,0x3ea681a9}}, // _تاری, िको_, _udtr, lkot_,
+ {{0x7d0d0057,0x656402c4,0x26c21ffc,0x08570039}}, // _suas, _apih, fiko_, הבים_,
+ {{0x7d0d0867,0xeeab0470,0x6299ea44,0x76ab0991}}, // _puas, сток_, _gowo, стов_,
+ {{0x7d0d1155,0x60c1ea45,0x645c0c9f,0x2d800073}}, // _quas, bilm, ffri, _crie_,
+ {{0x2d8010f4,0xb5fb00f7,0x6f1c017f,0x645c0019}}, // _drie_, neái, _otrc, gfri,
+ {{0x44316a46,0x20073c6d,0x26c21768,0x7d0d0144}}, // raz_, žnik_, biko_, _wuas,
+ {{0x39490c52,0x6fbd0074,0x7cf20bc5,0x66e40226}}, // tuas_, ्पसं, være, _поја,
+ {{0x44310510,0x2d8002d6,0x25a203eb,0x00000000}}, // [7070] paz_, _grie_, cykl_, --,
+ {{0x6e3b826c,0xb21b0366,0x7cf203ba,0x7529818e}}, // _sdub, _knæk, tære, _rwez,
+ {{0x8d1809d7,0x26ddb30b,0x7ae2ea47,0x0675a357}}, // _وزیر_, towo_, _okot, дуля,
+ {{0x3ea6ea48,0x394910e1,0x7529ea49,0x60c1ea4a}}, // gkot_, puas_, _pwez, zilm,
+ {{0xf65087bd,0xb60980eb,0x60c18850,0x26ddea4b}}, // ائم_, višķ, yilm, rowo_,
+ {{0x26c26a4c,0x2cac826c,0x7b0800f7,0x7ae2dba7}}, // ziko_, _cldd_, _óstá, _akot,
+ {{0x6299809a,0xfc3081a8,0x2fc50106,0x61e643bc}}, // _powo, احه_, _älg_, _zykl,
+ {{0x26c223f8,0x6e3bea4d,0x75298deb,0x00000000}}, // xiko_, _udub, _twez, --,
+ {{0x645c278e,0x7529822e,0x7d1bbb5b,0x3ebfc501}}, // yfri, _uwez, _utus, _imut_,
+ {{0xb4bc00cf,0xb5fb0013,0x6d4b820c,0x6299ea4e}}, // _आगे_, ceái, muga, _wowo,
+ {{0x60c18a0b,0x6d4b849f,0xf709801c,0x9a1281bc}}, // rilm, luga, _tầm_, _pọtụ,
+ {{0x2d8001e2,0x60c1ea4f,0xfd4b019d,0x291fac6b}}, // _prie_, silm, _uloọ, ppua_,
+ {{0x26c26725,0x6d59ea50,0x7afb8100,0x6d4bbd4f}}, // riko_, ntwa, mmut, nuga,
+ {{0x7ae9a7eb,0xdcfb826c,0x3ead8722,0x7afbd8ce}}, // llet, _oruč, _llet_, lmut,
+ {{0x6d4b8125,0x26c21fe8,0x3ead8009,0x290f807b}}, // huga, piko_, _olet_, _huga_,
+ {{0x2907ea51,0x6d4ba016,0x645c0ed0,0x7afb816a}}, // _hina_, kuga, sfri, nmut,
+ {{0x290f848f,0x2907ea52,0x68f5026c,0xd24f0019}}, // [7080] _juga_, _kina_, _bhzd, شنل_,
+ {{0x2907822e,0x6d4bea53,0x25fe0128,0x212000dd}}, // _jina_, duga, रेणी_, rpih_,
+ {{0x29078589,0xe29a0eef,0x3ea68747,0xb4ca13ba}}, // _mina_, _дан_, tkot_, लची_,
+ {{0x9f968039,0x6d5996f7,0x6d4b819e,0x3d0a83eb}}, // ודעה_, ftwa, fuga, ादले_,
+ {{0x23c389c8,0xb5fb00f7,0x3ea6ea54,0x7ae9906f}}, // _वेबद, teái, rkot_, dlet,
+ {{0x6d4600dd,0x7ae2af9c,0x63a38114,0x3ebf9ab3}}, // irka, _skot, fynn, _emut_,
+ {{0x290fea55,0x7ae98d85,0xb5fb00f7,0x3ead80f1}}, // _auga_, flet, reái, _flet_,
+ {{0x7ae9a4df,0x2907b2d2,0xb5fb00f7,0x290fc8bb}}, // glet, _aina_, seái, _buga_,
+ {{0x7c3e0f28,0x286aabf3,0x2ee4826c,0xb5fb6a56}}, // _odpr, орно_, _ikmf_, peái,
+ {{0x26c0221e,0x63a38114,0x7ae98ab3,0x634a8085}}, // _amio_, bynn, alet, _mənz,
+ {{0x2907ea57,0xc1088028,0x291d82af,0xf21281bc}}, // _dina_, _mỗi_, _etwa_, _dọrọ,
+ {{0x2907ea58,0xc1088028,0x6d460639,0x7ae2ea59}}, // _eina_, _lỗi_, frka, _ukot,
+ {{0x2907ea5a,0xd9e30076,0xf21291d3,0xe4e700e8}}, // _fina_, _कुढत_, _fọrọ, дівн,
+ {{0x67d4838b,0x290784b9,0xdce4009a,0x443e8695}}, // _полу, _gina_, _księ, _jdt_,
+ {{0x6d4609ca,0xc108801c,0xad9b00e1,0x6d4b80c6}}, // arka, _nỗi_, ncúz, zuga,
+ {{0x6d4b818c,0x7d0881d0,0x6d46011f,0x7ae08096}}, // yuga, _lids, brka, nomt,
+ {{0x387800f7,0x64482e88,0xdcfb817f,0x19580d55}}, // [7090] _barr_, ódic, _sruč, маты_,
+ {{0x3ead813c,0x29070bd7,0x1b1e00ab,0x6b750d9e}}, // _slet_, öna_, _ফিরে_, _злоу,
+ {{0x7ae9ea5b,0x7ae091ba,0x3eadc22a,0xae1c0c32}}, // ylet, komt, _plet_, भुवन_,
+ {{0x752d3d61,0x6d59822b,0xdb1e03a8,0x644303a7}}, // _kwaz, ttwa, _expó, _ônib,
+ {{0xdce981dd,0x7ae981cc,0x212b0114,0x660d0637}}, // _sređ, vlet, _uwch_, _mgak,
+ {{0x6d4b9c88,0xdce981dd,0x629d3529,0x6ef60118}}, // ruga, _pređ, _hoso, mábe,
+ {{0x629d20ed,0xdcfb8052,0x7ae9ea5c,0x6d4bbcb2}}, // _koso, _uruč, tlet, suga,
+ {{0x660d2e95,0xdce982ee,0x7982d5d1,0x60c500dd}}, // _ngak, _vređ, _brow, lihm,
+ {{0x2bdf83eb,0x7ae98558,0x7afbea5d,0x212b86c0}}, // _पुरा, rlet, rmut, èch_,
+ {{0x660d6a5e,0x26c0142f,0x3f588187,0x7ae9ea5f}}, // _agak, _smio_, féu_, slet,
+ {{0xdce9803a,0x752d3f6d,0x44278144,0x07a58a18}}, // _uređ, _awaz, mbn_, ханн,
+ {{0x629d1e24,0x6721805c,0x290f8503,0x79829106}}, // _noso, rplj, _tuga_, _frow,
+ {{0x628bea60,0xd7568065,0x0d8306c8,0x79828039}}, // _ingo, ولیت_, глян, _grow,
+ {{0x6f0980eb,0xfd4e81bc,0x9a1281bc,0x3f58b49a}}, // _liec, _alkọ, _họpụ, béu_,
+ {{0x7c3e8073,0x629d0573,0x68e1b3bc,0xeb9a1016}}, // ópri, _boso, mold, чив_,
+ {{0x6f09a19c,0xe29704ae,0x26c0026c,0xc3329101}}, // _niec, нас_, _umio_, טול_,
+ {{0x61430294,0xe9d72262,0xd94302d3,0x4427808e}}, // [70a0] лета, ект_, лети, kbn_,
+ {{0x9c7c826f,0x68e1ea61,0x2bdb0128,0x2a6d8069}}, // _akčn, nold, _मुका, heeb_,
+ {{0x54570051,0x628b9c96,0xf457007c,0xe666845e}}, // _אביב_, _ongo, _אייר_, етло,
+ {{0x7d088022,0x68e18a38,0x629d0114,0xdce40084}}, // _sids, hold, _goso, _priė,
+ {{0xf9920bca,0x7e646a62,0x1a9b00be,0x6f09ea63}}, // _صبح_, _mbip, _זיכע, _diec,
+ {{0x628b8e6c,0x653b00be,0x661b838a,0x34bc8a27}}, // _ango, לענד, _efuk, ्चेद,
+ {{0x6f098012,0x7d08806a,0xd62aad6b,0x629d36ba}}, // _fiec, _vids, _нове_, _yoso,
+ {{0x7982809a,0x7e640133,0xb5fb0144,0x290a0061}}, // _prow, _nbip, nfác, _hiba_,
+ {{0x7d089277,0x68e1c235,0xf9928154,0x290a3f9c}}, // _tids, fold, ابع_, _kiba_,
+ {{0x628bbebe,0x68e18019,0xdfda8098,0x2d848ca3}}, // _engo, gold, зъм_, _irme_,
+ {{0xc5fb13e5,0x690900d2,0x752d3da9,0x65760a84}}, // ृश्य_, _ožen, _swaz, _asyh,
+ {{0x234ba3f7,0x290a071f,0x2a6d822c,0x7ae602c4}}, // _رسمی_, _liba_, ceeb_, _nkkt,
+ {{0x25a6ea64,0x38cbbd9b,0x68e1ea65,0x6ef60118}}, // nyol_, _کانی_, bold, xábe,
+ {{0x629d01bf,0x290a02a0,0x7e645309,0x752d06c0}}, // _soso, _niba_, _ebip, _vwaz,
+ {{0x629d1640,0xfbdb06b7,0x9a871156,0x1bd4228e}}, // _poso, _मुखम, нуал, горя,
+ {{0x629d19e7,0x290a0087,0x752d6a66,0x2d84ea67}}, // _qoso, _aiba_, _twaz, _orme_,
+ {{0x690924b2,0x629d05e4,0x290a020c,0x752d035c}}, // [70b0] _džen, _voso, _biba_, _uwaz,
+ {{0x7e7d3839,0x6f09809a,0x6ef601df,0x5b151071}}, // ldsp, _siec, rábe, _имот,
+ {{0xe70b15e4,0x290a585c,0xdfd100f7,0x6f09ea68}}, // ستان_, _diba_, جيا_, _piec,
+ {{0x6ee781a8,0x2a6d81c0,0x26cfca6c,0x52a701a8}}, // رسال, xeeb_, ungo_, _أصدق,
+ {{0x6d5d6208,0x3a29036e,0x7e7d5d97,0xe8d90870}}, // ntsa, mbap_, idsp, ntị_,
+ {{0x7aed6a69,0xe7330250,0xdced005c,0x6f09809a}}, // llat, _مصر_, _krađ, _wiec,
+ {{0x0ee10076,0xe2998973,0x8c1c0051,0x6d5d0006}}, // नवाड, дай_, לוגי, htsa,
+ {{0x44336a6a,0x7ea600a9,0x7aed6a6b,0x7e7d013c}}, // _lex_, _tópi, nlat, jdsp,
+ {{0x6d4f0052,0x6aa98125,0xe9d99fb4,0x442782f7}}, // juca, rkef, ьки_, pbn_,
+ {{0x39490e67,0x7e7bea6c,0x6d4f6a6d,0x7aed425a}}, // iras_, _haup, duca, hlat,
+ {{0x7e7b9184,0x6ef60065,0x64419400,0x645e8722}}, // _kaup, vább, _idli, òpie,
+ {{0x38a41bc0,0x68e19753,0x39496a6e,0x628ba0b4}}, // _hört_, sold, kras_, _ungo,
+ {{0x394da95a,0x7e7b816f,0x6724807d,0xee3f003e}}, // ques_, _maup, _čije, ckým_,
+ {{0x7e7b91de,0x6ef88118,0x10a2816c,0x98a282ee}}, // _laup, míbe, _вишн, _више,
+ {{0x39496a6f,0x1d072659,0x6ef8801b,0x6ef60019}}, // eras_, тери_, líbe, rább,
+ {{0xc2433acb,0x394907a3,0x1b1380ab,0x7e7bb162}}, // инск, fras_, _সবাই_, _naup,
+ {{0x9426117e,0x39496a70,0x6d5b8069,0x6d4f6a71}}, // [70c0] _импе, gras_, _nqua, cuca,
+ {{0xb5fb65a3,0xdd310085,0xf53703de,0x44331d5e}}, // reát, ləşd, _שניי_, _gex_,
+ {{0x44386a72,0xdced0025,0xb4c00076,0x63a70063}}, // mar_, _građ, ूची_, cyjn,
+ {{0x39492b72,0x7aed6a73,0xfaa61bcc,0x76a980e8}}, // bras_, clat, _рамо, нтів_,
+ {{0x7ae4025d,0x290a25f3,0xdce080eb,0x7e7b8036}}, // moit, _tiba_, _apmā, _daup,
+ {{0x55bb0039,0x7ae40009,0xf4d08264,0x5453804a}}, // ומנו, loit, িত্ব, ивіт,
+ {{0x3ea000c9,0xf70a001c,0xf1bf0118,0x38a402d0}}, // _ooit_, _hầu_, _feás_, _dört_,
+ {{0x44380695,0xf7770bea,0xee3f01ac,0x7ae46a74}}, // har_, _בעלי_, tkým_, noit,
+ {{0xb6a38f5a,0x26c4d6eb,0xbea38f9c,0x63a70035}}, // _титл, _immo_, _татк, zyjn,
+ {{0x44386a75,0xdd8f8013,0x7ae40009,0x7e7b807a}}, // jar_, _بوك_, hoit, _zaup,
+ {{0xd7f79a8f,0x7ae42d33,0xf8370039,0xbcfb00e7}}, // кую_, koit, כנית_, ntée,
+ {{0x673a807b,0x7c3880a9,0x443335db,0x7ae40364}}, // rstj, lavr, _sex_, joit,
+ {{0x44383a20,0x3ea002be,0x2bdb0074,0x673a807b}}, // far_, _doit_, _मुजा, sstj,
+ {{0x64d20076,0x403491b3,0x394900a9,0x6d4f6a27}}, // _सदृश, _секс, vras_, ruca,
+ {{0x9f588009,0x6909056f,0x25ad1cf3,0xc339801b}}, // ärä_, _džel, šel_, žívá,
+ {{0x39491864,0x6d4f0052,0xd838842b,0xf70a001c}}, // tras_, puca, lač_, _bầu_,
+ {{0xf70a0028,0x7aed1d2c,0x7ea60118,0x60f90e11}}, // [70d0] _cầu_, rlat, _cópu, еная_,
+ {{0x2edb8c78,0xf70a001c,0x7e7bc092,0xdce9876c}}, // यक्त, _dầu_, _saup, _iseć,
+ {{0xf4120051,0x20070364,0xdced1c67,0x7ae426b9}}, // _לפי_, äni_, _urađ, boit,
+ {{0x60c89cbc,0x7ae404c3,0x4ea78098,0xa295102a}}, // hidm, coit, ърза, _самі,
+ {{0x3cdf85b3,0xe7e50076,0xd12692dc,0xd838979d}}, // गवले_, _कडवा_, _دم_, kač_,
+ {{0x6aad052a,0x387c82f9,0x6f0d03e1,0x26c48299}}, // lkaf, _navr_, _liac, _emmo_,
+ {{0x2ca11083,0x7e7b82d7,0x9f3400e8,0x69d980b9}}, // _mohd_, _taup, _весі, _sxwe,
+ {{0xdce9b4c1,0x6aad6a76,0x6ef8802a,0x6ef60061}}, // _oseć, nkaf, tíbe, kába,
+ {{0x44386a77,0x1017000f,0x2f1a8029,0x38a4016d}}, // yar_, _तलाश_, _rīga_, _görs_,
+ {{0x4438519e,0x3ea003d3,0x6ef885e4,0x7ae44395}}, // xar_, _soit_, ríbe, zoit,
+ {{0x1eca1927,0xb5fd81e2,0x61eb809a,0x6ef884c3}}, // елни_, _rašy, _wygl, síbe,
+ {{0x44380a73,0xaca38129,0x7fb3811c,0xba3983de}}, // war_, _ruộn, nəqə, _װײַט,
+ {{0x3ea04666,0x7ae46a78,0x6f0d6a79,0xe8ee81e2}}, // _voit_, voit, _diac, _гл_,
+ {{0x628f0698,0x2a7d822c,0xd83880d2,0x4438236f}}, // _anco, _hawb_, cač_, uar_,
+ {{0x3ea04a2c,0x2a7d81c0,0x6f0d0090,0x395f82d7}}, // _toit_, _kawb_, _fiac, ltus_,
+ {{0x44382dc4,0x670c0063,0x6909225d,0x6f0d0081}}, // sar_, _सूचक_, _džem, _giac,
+ {{0xceb402bf,0xe8fa1a0b,0x44384359,0x38b60aa2}}, // [70e0] _ilə_, ела_, par_, _lære_,
+ {{0x48c40a49,0x628f19c3,0x47bf00ab,0x2a7d822c}}, // ্ত্র, _enco, ্থনী, _lawb_,
+ {{0x44446a7a,0x03a60993,0x6d4b9aa6,0x395f82f1}}, // _id_, _сино, erga, htus_,
+ {{0xd838805c,0x395f9247,0x2a7d81c5,0x44441706}}, // zač_, ktus_, _nawb_, _hd_,
+ {{0x8e570f60,0x64a68e02,0xdca6997b,0x395d81c0}}, // רינג_, лада, лади, _nqws_,
+ {{0xa3e812ee,0xd37a8991,0x38b603ba,0x6aa30162}}, // _बडा_, ечи_, _bære_, _înfr,
+ {{0x38a4016d,0x38b6006a,0x2a7d8114,0x6e398122}}, // _förr_, _værd_, _bawb_, bawb,
+ {{0x7644022e,0x66e6c81b,0xdee6ca58,0x7c3880fe}}, // _ndiy, _соба, _соби, savr,
+ {{0xd0108013,0xc05a902a,0x6f0d0013,0x2a7d81e9}}, // يلة_, нін_, _riac, _dawb_,
+ {{0x2ee680c9,0x2bdfbac3,0x444401c6,0x2ee95e5f}}, // loof_, _पुका, _nd_, _skaf_,
+ {{0x2a668021,0x6f0d2b38,0xd838803b,0x3f878024}}, // _бълг, _piac, rač_, _crnu_,
+ {{0x44446a7b,0xa3e80006,0xdce980ce,0x918692c5}}, // _ad_, _बड़_, _pseć, _مجتم,
+ {{0x44440f77,0x6f0d01ac,0x69092c08,0xe8948a08}}, // _bd_, _viac, _džej, _валь,
+ {{0x6aa2a4de,0x764407d9,0xceb40201,0xb17b810f}}, // _hoof, _ediy, _elə_, _נטור,
+ {{0xb8658077,0x32671fb4,0x6ef60019,0x6aa2ea7c}}, // _خانو, лтав, sába, _koof,
+ {{0x44446a7d,0x6e2487f4,0xb6c90019,0x2a7d81c0}}, // _ed_, ñibl, _جاتے_, _xawb_,
+ {{0x38350aac,0x3f8780d2,0xf84b1cad,0xaca3819d}}, // [70f0] инар, _zrnu_, ечей_, _skọl,
+ {{0x26cb6a7e,0x201e82d6,0x3d18800f,0x6aad04fe}}, // lico_, ncti_, _पढ़े_, skaf,
+ {{0x3f8749b3,0x6d40a67f,0x64456a7f,0xe1ff0032}}, // ínu_, _avma, _idhi, _gbón_,
+ {{0x26cb1cdc,0x17f80307,0x1e0f816f,0x4444011a}}, // nico_, اركة_, िशेष_, _zd_,
+ {{0x395fea80,0xa56480a0,0xe618821e,0x2a7d81c0}}, // xtus_, مدون, уді_, _rawb_,
+ {{0x21290587,0xd4698604,0x6f02e53f,0x26cb00e5}}, // mpah_, тике_, amoc, hico_,
+ {{0xeb9982da,0x2a7d8114,0x6aa2838a,0x64450c93}}, // тий_, _pawb_, _boof, _mdhi,
+ {{0xf53f04b8,0x38b6581e,0xb1458ba8,0xe8e0001c}}, // kså_, _være_, анил, _đứa_,
+ {{0xe0d98ae7,0x81e700c8,0x20240125,0xd6d8902f}}, // кви_, যের_, _árið_, _стр_,
+ {{0x395fa8d1,0x05dea23a,0x229c001b,0x1f65960f}}, // rtus_, _फुटब, _díky_, аком,
+ {{0x395f8744,0x752401dd,0x2a7d8282,0x3160011b}}, // stus_, _stiz, _tawb_, ztiz_,
+ {{0x26cb1ce3,0x64451eb3,0x6e360079,0x66e605c2}}, // gico_, _adhi, _qeyb, рома,
+ {{0x59a5016f,0x6ef8e7c8,0xe9a89459,0x2d8925dc}}, // _गॅलर, níba, _مدون_, _crae_,
+ {{0x7cf2013c,0x7ae49640,0x44210122,0x69096a81}}, // mærk, čite, _cfh_, _džek,
+ {{0x44446a82,0x394dc3ff,0xdc0881a9,0x46b58526}}, // _vd_, lres_, mēģi, ंसाह,
+ {{0x3eb902af,0x394d8e67,0x3a2607b6,0x76441a73}}, // chst_, ores_, рмаг, _udiy,
+ {{0x394d862f,0x3d159344,0x3b070323,0x213f80ee}}, // [7100] nres_, _फूले_, рето_, nsuh_,
+ {{0x394dd673,0x44446a83,0x270e8f12,0xd25981a9}}, // ires_, _ud_, िद्र_, miņu_,
+ {{0x7c3c6a84,0xee3aad6b,0x657b9258,0x69dd6a85}}, // marr, вне_, _asuh, _exse,
+ {{0x0d869505,0x7c3c0102,0x9f47801b,0x33db04de}}, // илен, larr, _nyní_, _יחיד,
+ {{0x6aa2a5ad,0x2ee69af4,0x9784003d,0xd2598ec3}}, // _roof, roof_, _گیاه, niņu_,
+ {{0x394d92af,0x7c3c0e9b,0x394001c5,0x78a3acb1}}, // dres_, narr, msis_, _nonv,
+ {{0x394d80c8,0x9f5c0364,0xa69684bd,0xf743351e}}, // eres_, ävä_, _крај, _меро,
+ {{0x26cb0511,0x7c3c2975,0x99668470,0x39406a86}}, // xico_, harr, атол, osis_,
+ {{0x7c3c00ad,0x26cb290f,0x62809819,0x39406a87}}, // karr, vico_, rdmo, nsis_,
+ {{0x78a393a0,0x7c3c6a88,0xa0a6a240,0x394067ea}}, // _conv, jarr, ршил, isis_,
+ {{0x3ea48613,0x7c3c2564,0x381702f6,0x69090858}}, // _komt_, darr, יקים_, _džeh,
+ {{0x394d9e9e,0xa1868a7f,0xf53f0bc5,0x39404761}}, // bres_, _выкл, tså_, ksis_,
+ {{0x26cb6a89,0x394d809f,0x7c3c011e,0x290e993a}}, // rico_, cres_, farr, _tifa_,
+ {{0x7c3c00ad,0x6458a133,0x443cea8a,0x2d8901b0}}, // garr, ngvi, nav_, _vrae_,
+ {{0x26cb1820,0xa3cc835a,0xdce984b7,0x39400915}}, // pico_, रपट_, _speċ, esis_,
+ {{0x443c81c5,0x2d891cdc,0x25608247,0x68e89e1a}}, // hav_, _trae_, kòl_, lodd,
+ {{0x26c90025,0x443c803a,0xae0301fe,0x6fd9000f}}, // [7110] _imao_, kav_, रेशन_, _मशहू,
+ {{0x7c3c07d0,0x92d700c8,0x1a65003d,0x37ab302b}}, // carr, িতে_, _گیری_, ктен_,
+ {{0x443cea8b,0x61ef0242,0x3e860061,0xd0180b69}}, // dav_, _wycl, sítő_, рфу_,
+ {{0x22950b53,0x394001c5,0x394dc8a3,0x6ef8802a}}, // _الحس, bsis_, yres_, tíba,
+ {{0x98b30085,0x443cd55f,0x2bdf9c7b,0x3ea48609}}, // şdır_, fav_, _पुजा, _domt_,
+ {{0x394dd4ec,0x7cf2006a,0xb4db0722,0x6ef8ea8c}}, // vres_, værk, cràt, ríba,
+ {{0x3f8a017f,0x7ced81a8,0xd25981a9,0x291956c5}}, // _grbu_, dúrt, ziņu_, _husa_,
+ {{0x394dbab9,0x7c3c4395,0x6b830511,0x291959fd}}, // tres_, zarr, _ángu, _kusa_,
+ {{0x443c803a,0x394dad9c,0x29116a8d,0x7c3c0f3e}}, // bav_, ures_, _kiza_, yarr,
+ {{0x394d9e9e,0x29196a8e,0xdcf98591,0x26c96a1a}}, // rres_, _musa_, _صفات_, _amao_,
+ {{0x29116a8f,0x29196a90,0xb69b3296,0x14d69101}}, // _miza_, _lusa_, rmân, _פועל_,
+ {{0x394dae3a,0x6e3d033e,0x6ef60207,0x39402616}}, // pres_, gasb, rábo, ysis_,
+ {{0xe7f40c78,0x7c3c011e,0x81ac00c8,0x29190e6d}}, // _अर्थ_, tarr, _কেন_, _nusa_,
+ {{0x6efd6a91,0x78ba8a11,0x2911012b,0xdce9a3f9}}, // vébe, chtv, _niza_, _opeč,
+ {{0x7c3c04b9,0x6d4f1581,0x29190bb1,0x24810122}}, // rarr, arca, _ausa_, _dahm_,
+ {{0x7c3c1b54,0x29193394,0x25608247,0x6efd0019}}, // sarr, _busa_, yòl_, tébe,
+ {{0xe1fa013a,0x7c3c159d,0xa3c9897d,0xbcfb01a8}}, // [7120] уга_, parr, _लेस_, stéa,
+ {{0xeb9a04ae,0x7c3c022b,0x2ca582c4,0x3ae180e1}}, // _био_, qarr, _nold_, rópy_,
+ {{0xbb8600f7,0x39406a92,0x25608247,0x443c9024}}, // _الجي, ssis_, wòl_, vav_,
+ {{0xe29a2f86,0xe7370abe,0x443a6a93,0xee3aa28e}}, // _сам_, сец_, _hep_, унд_,
+ {{0x443ca571,0x443a0d29,0x291902a0,0x291109a6}}, // tav_, _kep_, _gusa_, _fiza_,
+ {{0x443a5288,0x29113469,0x6562ea94,0x62841914}}, // _jep_, _giza_, rtoh, ldio,
+ {{0x961d8029,0x672482a5,0x6562ea95,0x3ea4e44b}}, // _saņe, _čijo, stoh, _tomt_,
+ {{0x443a2354,0xd00f00f7,0x65628168,0x7ae9811b}}, // _lep_, _ألف_, ptoh, goet,
+ {{0xd83a8196,0xd9fa001b,0x9c5aa3f7,0x98b8802e}}, // лэн_, ्धित_, _مجاز_, илит_,
+ {{0x2ca5b2c7,0x26c90140,0xa3c986ae,0x98ad81d6}}, // _gold_, _smao_, _लेव_, ateľ_,
+ {{0x6448ea96,0x68e8831d,0x389b007c,0xd01081a8}}, // _iddi, rodd, איינ, بلد_,
+ {{0x6e22816f,0xfaa384fa,0x6abb8511,0x68e88114}}, // ncob, _нахо, chuf, sodd,
+ {{0x62840355,0x7c3aa168,0x3a391c18,0xc0e6963f}}, // ddio, _ketr, _resp_, _лодк,
+ {{0xdcfb803a,0x443a6a97,0x68e8808e,0x66cf026b}}, // _vruć, _cep_, qodd, _bòkè,
+ {{0x443a08b6,0x872691cc,0x6aa60711,0x29196a98}}, // _dep_, _اعتم, _bokf, _susa_,
+ {{0x2919188c,0x8aa40a08,0x272481bc,0x443a02f9}}, // _pusa_, пруд, _ịnwa_, _eep_,
+ {{0xa1590221,0x19590a14,0x443a276f,0x6448ea99}}, // [7130] разу_, разы_, _fep_, _oddi,
+ {{0x7ae98cdb,0x628406ae,0xa3b881a2,0x3a390299}}, // zoet, adio, ङना_, _wesp_,
+ {{0xb0658009,0x613f026b,0x836701a8,0x6282838a}}, // llää, _bàlá, _ادخل, _oaoo,
+ {{0x35f5943d,0x6448d5d1,0xdced0110,0x29192305}}, // опер, _addi, _praė, _tusa_,
+ {{0x7c3a8b2b,0x64a5a59a,0xee388d8e,0x5d678098}}, // _betr, жала, сні_, стез,
+ {{0x76598114,0xfbd009a7,0x61fd00eb,0x81cc80ab}}, // sgwy, _آتی_, _izsl, _শুভ_,
+ {{0x2ca58aa2,0x7ae9ea9a,0x045680f7,0x68fa01c6}}, // _vold_, toet, زلية_, _כלשה,
+ {{0xa92580e2,0x6448d7e9,0x6e228037,0x26d90866}}, // одил, _eddi, ccob, érol_,
+ {{0x76498ec9,0x26cfd8cf,0x69091148,0x7ae9cff6}}, // _idey, migo_, _džev, roet,
+ {{0x7ae48025,0x7c3ab66b,0x26cfdef5,0x25ed01ce}}, // čita, _getr, ligo_, _घड़ी_,
+ {{0x6d4400e1,0xf2e78a08,0x7ae9e0a7,0xe7398eef}}, // _svia, _людо, poet, јем_,
+ {{0x7c3aa1a2,0x7d1ba2d9,0x26cfc23d,0x2a6901c0}}, // _zetr, _kuus, nigo_, jfab_,
+ {{0xbcfb0065,0x443a53d4,0x21a58676,0x6e3b829c}}, // rtén, _pep_, _филм, _meub,
+ {{0x7d1bda42,0x6c360013,0x26cfae84,0x212d80ee}}, // _muus, تفسا, higo_, mpeh_,
+ {{0x25fe05e8,0x4425872c,0xc22701bc,0x644d80fc}}, // रेजी_, _mfl_, _ụkọc, ɗait,
+ {{0x6d405d34,0x7e6d3a48,0x26cf807a,0x6e3bb31e}}, // ámas, _gbap, jigo_, _neub,
+ {{0x26cfea9b,0x7d1bea9c,0x7d01b2a0,0xa5bb002a}}, // [7140] digo_, _nuus, _ohls, lcón,
+ {{0x7649d039,0x25e302f1,0x3a3f8115,0x6ab99670}}, // _adey, _टुटी_, haup_, _llwf,
+ {{0xe61a96d4,0x7c3a90b6,0x2d8d8081,0xe7ef8dd2}}, // аде_, _retr, _aree_, _घुमा_,
+ {{0xa3e3064a,0x6d42ea9d,0x7d1bea9e,0x7d019388}}, // _फुट_, tsoa, _buus, _ahls,
+ {{0x7c3a9598,0x2d8d8020,0x6e22861b,0x857696a5}}, // _petr, _cree_, rcob, تدائ,
+ {{0x6d42b7b7,0x645c45ca,0x6e22ea9f,0x9b26b51e}}, // rsoa, ngri, scob, офил,
+ {{0x6d4280a9,0xa2c10054,0x7c3ab42c,0xb4ca809a}}, // ssoa, रोक्, _vetr, _लगी_,
+ {{0xc2988895,0x22400239,0xf96a97ae,0x442581e0}}, // ских_, naik_, арой_, _efl_,
+ {{0x7d1bdd73,0x69c08503,0x25642b0d,0x26d901d6}}, // _guus, uzme, jöl_, érom_,
+ {{0x6f1c6aa0,0x7af60428,0x92ca8264,0x6e3b82f9}}, // _kurc, hlyt, লকে_, _zeub,
+ {{0xb4d6853e,0x22406292,0x645c0140,0x2d806aa1}}, // ाची_, kaik_, dgri, _asie_,
+ {{0x6f1c5f64,0x7d1b82a3,0x216a8ff7,0xa96a8d5f}}, // _murc, _yuus, _види_, _вида_,
+ {{0x291f81e8,0x2ab8810c,0x2d801b88,0x256419db}}, // cqua_, _géba_, _csie_, göl_,
+ {{0x645c0854,0x26cf9d1b,0xa2a68a27,0x6f1c1d5e}}, // ggri, zigo_, टॉर्, _ourc,
+ {{0x0b8812e1,0xaca38028,0x93884872,0x7af60b81}}, // ости_, _xuốn, оста_, flyt,
+ {{0x6d480063,0x201820bc,0x2d80002a,0xf1bf81d6}}, // ądar, _agri_, _fsie_, žšej_,
+ {{0x6e3bda4a,0x26cfa925,0xa2c120e9,0xb9550081}}, // [7150] _reub, vigo_, रोग्, яващ,
+ {{0xa2ca81ab,0x6f1c3824,0xddc98087,0x3ea900ff}}, // तोत्, _burc, _aceş, _hoat_,
+ {{0x26cf984f,0x78a71487,0x2fc70028,0x2240510e}}, // tigo_, _vojv, àng_, baik_,
+ {{0x6f1c034a,0x4b7a0158,0xef180110,0x98a90bda}}, // _durc, _פארו, імі_, mpač_,
+ {{0x26cf83cd,0xceb400be,0xd83b0196,0x798901b9}}, // rigo_, ויף_, _вэб_, _ġeww,
+ {{0x7d7c010f,0x7aed6aa2,0xdced3dbb,0x26cf9c61}}, // ינדו, loat, _opač, sigo_,
+ {{0x7ae48353,0x29030267,0x5c380039,0x69090b80}}, // čitn, _ihja_, חרון_, _džet,
+ {{0xc2458171,0x63a803ba,0x3b09011c,0x7bdc03de}}, // чник, ødni, nmaq_, יקוו,
+ {{0xddc41809,0x3a3f82f7,0x84678081,0xdced0196}}, // _obiš, raup_, _мъже, _apač,
+ {{0x6457a6d5,0x6d59eaa3,0x44258176,0xdce401b9}}, // óxid, muwa, _ufl_, _oriġ,
+ {{0xf7718277,0xc5e90bea,0x6d59ae7f,0x291fc58e}}, // راد_, _עד_, luwa, squa_,
+ {{0x7e7982d5,0x195981e2,0xe1ff07f1,0x248584fe}}, // newp, _гады_, _ozó_, _halm_,
+ {{0x6d59b6ba,0x2485a70c,0x645c6aa4,0xeab08c3b}}, // nuwa, _kalm_, tgri, _جعل_,
+ {{0xa50a00b3,0x2eb88d14,0xc21281c6,0x98a001a1}}, // сега_, _आत्त, _נהג_, mpić_,
+ {{0x291d81cd,0x6d599e60,0xddcf0087,0xfce32306}}, // _huwa_, huwa, tecţ, хоро,
+ {{0x291d9a9b,0xa2cc035a,0xdbd788e5,0xee3a02ac}}, // _kuwa_, _सगळ्, määr, _уни_,
+ {{0x3e5b0039,0x6f0401bc,0x201811ee,0x6f1c6aa5}}, // [7160] _הדפס, _ihic, _pgri_, _surc,
+ {{0xb4ca901b,0x6d46209b,0x6d59e1d8,0x6f1c0039}}, // _लगे_, mska, duwa, _purc,
+ {{0xb4e80076,0x6d46006f,0x7c3e0029,0xe807184a}}, // यकी_, lska, _iepr, वेला_,
+ {{0x2721eaa6,0x127a00f7,0xeafa026a,0x6d598041}}, // _món_, _احدث_, _درخت_, fuwa,
+ {{0x6d46088b,0x7c3e6aa7,0xdbce080a,0x6d5984b9}}, // nska, _kepr, _bıça, guwa,
+ {{0xb4d6853e,0xf7461a19,0x62866aa8,0x6d466aa9}}, // ाचे_, _него, _hako, iska,
+ {{0x2ca51bd9,0x62866aaa,0x248580dd,0x69c402a6}}, // öldi_, _kako, _dalm_, lzie,
+ {{0x6286085d,0x6d599855,0x6d464485,0x6aa4313d}}, // _jako, buwa, kska, njif,
+ {{0x628654dd,0x69c44ac3,0x58868048,0xdced6aab}}, // _mako, nzie, пына, _spač,
+ {{0x6d466aac,0x62860d8b,0xfca98481,0x27218129}}, // dska, _lako, _والو_, _bón_,
+ {{0xd0418201,0x443e80eb,0x6f040229,0x3d1583b7}}, // nilə, _iet_, _bhic, _फूटे_,
+ {{0x443e9302,0x6286005c,0xa3cd06b7,0x6f043f55}}, // _het_, _nako, _शेष_, _chic,
+ {{0xbcfb003e,0x50b78077,0x443ed70d,0xf0b78077}}, // stém, زدید_, _ket_, زایش_,
+ {{0x69c44094,0x6f0401bc,0x212b81a8,0x272181a8}}, // dzie, _ehic, íche_, _fón_,
+ {{0x443e9302,0x291d83c3,0x6d460359,0x62864100}}, // _met_, _zuwa_, aska, _bako,
+ {{0x443eeaad,0x6d5980a4,0xcff783c8,0xd0418085}}, // _let_, yuwa, _מצוה_, dilə,
+ {{0xdd9103f8,0x764d5d3f,0x3b090085,0xa3cd00c2}}, // [7170] _شود_, _iday, rmaq_, _शेर_,
+ {{0x7c3e105d,0x443eb2c7,0xdddbac90,0x26cd8904}}, // _gepr, _net_, _nauš, _smeo_,
+ {{0x6d5980a4,0x62866aae,0xdd02011f,0x69c4180b}}, // wuwa, _fako, _žućk, azie,
+ {{0xae01000f,0x6d59e1d3,0x7c289151,0x2485eaaf}}, // _ईरान_, tuwa, _afdr, _salm_,
+ {{0x69091fa6,0x443ec6ce,0xe945004e,0x24858833}}, // _džer, _bet_, _ترجی, _palm_,
+ {{0x443eb1f6,0x291d80a4,0x6d59cffd,0x752d004f}}, // _cet_, _ruwa_, ruwa, _mtaz,
+ {{0x443e88f8,0x6d5980a4,0x628620d7,0x764d0079}}, // _det_, suwa, _yako, _oday,
+ {{0x443e80c9,0x6d461f9f,0x7bc3812b,0x764d331a}}, // _eet_, yska, rznu, _nday,
+ {{0x27218813,0x443ee1d6,0xaca30135,0x31696ab0}}, // _són_, _fet_, _anọg, ntaz_,
+ {{0x443eeab1,0xd90f8065,0x764d6ab2,0x6f040df6}}, // _get_, ھیں_, _aday, _shic,
+ {{0xdee60003,0x66e60049,0x64438888,0x752d203c}}, // дони, дона, mani, _ataz,
+ {{0x6443eab3,0x443e8a0f,0x7c3e123c,0x6d462fbc}}, // lani, _zet_, _sepr, tska,
+ {{0xe8ee8193,0x628607dd,0x2b40805c,0x6299837a}}, // _ал_, _rako, ćice_, _inwo,
+ {{0x6d466ab4,0x6f04004c,0x6443eab5,0x628606c1}}, // rska, _whic, nani, _sako,
+ {{0x7c3e020f,0x6d461277,0x62861619,0x752d0247}}, // _vepr, sska, _pako, _etaz,
+ {{0xe297002e,0x6d46267f,0x6289eab6,0x69c41d9d}}, // мас_, pska, ndeo, tzie,
+ {{0x6443eab7,0xf2d2812a,0x672482a5,0x20e184c5}}, // [7180] kani, דען_, _čiji, _पदाध,
+ {{0x7ae9003b,0x69c41a62,0x62863e4c,0xfd5f001c}}, // četa, rzie, _wako, _luyệ,
+ {{0x6443c75a,0x62861620,0x443eeab8,0x6d498087}}, // dani, _tako, _ret_, _avea,
+ {{0xd0418201,0x443eae52,0xd6d98558,0x2d920168}}, // rilə, _set_, пті_, _krye_,
+ {{0x6443c0a6,0xdce287aa,0xdddba46d,0x1c2206a7}}, // fani, stoč, _pauš, _मलाल_,
+ {{0x62998edc,0x3e4e801c,0x843880f7,0x1603223a}}, // _anwo, ột_, أكثر_, रेटर_,
+ {{0x443e8711,0xf487853d,0xd7f201a8,0xd90d0019}}, // _vet_, بانی, _شكر_, _ایل_,
+ {{0x3e868110,0x29180041,0x225f8b20,0xed5adc30}}, // _būtų_, _hira_, gguk_, _моб_,
+ {{0x29185912,0x443eeab9,0x6457a132,0xfd5f001c}}, // _kira_, _tet_, óxic, _duyệ,
+ {{0xd90d8416,0x291802a3,0xb4e82a18,0x47348c40}}, // مین_, _jira_, यको_, ннос,
+ {{0xf98f803d,0x085704de,0x90570039,0x205700be}}, // هبی_, ובים_, וסיף_, וייל_,
+ {{0x569411d2,0x29181f1a,0x3ea6eaba,0x7d1700b9}}, // вают, _lira_, njot_, _sixs,
+ {{0x2d84800d,0x6ef880f7,0xe9da9ef5,0x2f1a8ec3}}, // _jsme_, ríbh, _بذات_, _rīgu_,
+ {{0x29180091,0x7e640101,0xbca501a8,0x00000000}}, // _nira_, _ecip, رمزي, --,
+ {{0x36d52748,0xbcfb0174,0x2cac8114,0x38cb8fd3}}, // _покр, ntéi, _modd_, _بانی_,
+ {{0xe1888028,0x34db816f,0x5f040992,0x7640828f}}, // _kỳ_, _बद्द, _изја, _nemy,
+ {{0x7e7d0a15,0x34951e18,0x2bcf0327,0x752d547a}}, // [7190] mesp, _запр, _हेरा, _utaz,
+ {{0x6d5d2a8a,0x4427007e,0x31696244,0x6443eabb}}, // musa, _ün_, rtaz_, xani,
+ {{0x6443b404,0x291800ad,0x69090024,0xdfd100f7}}, // vani, _dira_, _džep, ديا_,
+ {{0x64438610,0xcf9380be,0x7e7d0b82,0x6b856abc}}, // wani, כטע_, nesp, _ishg,
+ {{0x64438d78,0x6d5d2e07,0x29186abd,0x6ef61243}}, // tani, nusa, _fira_, cábu,
+ {{0x29186abe,0xd0068065,0xf5928019,0x39496abf}}, // _gira_, _مل_, الوج, msas_,
+ {{0xa3c9825e,0x64438b5d,0x6d5d535b,0x2cac9106}}, // _लेख_, rani, husa, _dodd_,
+ {{0x715984fa,0x3a290239,0x29180b8e,0x7e7d453e}}, // прос_, ncap_, _zira_, jesp,
+ {{0x7e7d1a40,0xe9d99fb4,0x2cac8114,0x6ea3064a}}, // desp, яки_, _fodd_, गानु,
+ {{0xa3c1229e,0x29180509,0x39496ac0,0x7e645171}}, // ्छा_, _xira_, isas_, _scip,
+ {{0x65760358,0xfd5f001c,0x644181a9,0xe80a86ae}}, // _spyh, _tuyệ, _ieli, हेला_,
+ {{0x39492338,0x6441c2a9,0x7e7d1781,0xee370d8e}}, // ksas_, _heli, gesp, нню_,
+ {{0x3ead81d8,0x6441eac1,0x6d5d6ac2,0x249a02f7}}, // _moet_, _keli, gusa, _pnpm_,
+ {{0x7afbb65d,0x3156810f,0x6441866b,0x2d92067f}}, // llut, _ניצן_, _jeli, _vrye_,
+ {{0x64419a67,0xd5b800eb,0xeabf6ac3,0xa2a6864a}}, // _meli, ņā_, _blù_, टॉक्,
+ {{0xef198063,0x291815d7,0xfd4a0098,0x7afbeac4}}, // _niż_, _sira_, _език_, nlut,
+ {{0x6d5d1a47,0x29186190,0x2056142e,0xeabf0706}}, // [71a0] cusa, _pira_, етер, _dlù_,
+ {{0x6441ac6a,0xdcfb8042,0x7afbeac5,0x6604005d}}, // _neli, _oruđ, hlut, _izik,
+ {{0x29186015,0x7afbeac6,0x44380286,0xef1982a6}}, // _vira_, klut, mbr_, _biż_,
+ {{0x291804bf,0x9f580168,0x75208c53,0x3ead87f1}}, // _wira_, _hyrë_, _pumz, _coet_,
+ {{0x6441eac7,0x3ead8a0f,0xef198609,0x26c00c53}}, // _beli, _doet_, _diż_, _mlio_,
+ {{0x6441eac8,0x224204b7,0x7e7d5207,0xdca391d5}}, // _celi, _hekk_, zesp, лачи,
+ {{0xc5f180c8,0x26c06ac9,0x7afb808b,0x764629e4}}, // জেলা_, _olio_, flut, kaky,
+ {{0x224201cd,0x7afb8ce9,0x78200006,0x6d5d020d}}, // _jekk_, glut, _बलुक_, yusa,
+ {{0x63b5009a,0xbcfb5783,0x50668a30,0xed579810}}, // zyzn, stéi, нтна, _пот_,
+ {{0x6441eaca,0xd6fb34c7,0x2918bebf,0x78ae6acb}}, // _geli, ृष्ठ_, öra_, _jobv,
+ {{0x7e7d2127,0x0f23a357,0x26c00362,0x7e628c53}}, // tesp, льям, _blio_, ogop,
+ {{0x6d5d103b,0x7d1abd5e,0x7afbb49a,0x22420176}}, // tusa, _hits, clut, _nekk_,
+ {{0xa2c41344,0x7d1aeacc,0x6441b7f2,0x2746826b}}, // रसन्, _kits, _yeli, _bíná_,
+ {{0x26c000e5,0x7d1d8580,0x2abc046d,0x660402ad}}, // _elio_, _èsse, _bíbo_, _dzik,
+ {{0xf76f8077,0x387eeacd,0x6d5d6397,0x7d1aeace}}, // _هاي_, metr_, susa, _mits,
+ {{0x6d5d2a69,0x7d1aeacf,0x39496ad0,0x442c8af8}}, // pusa, _lits, tsas_, _lfd_,
+ {{0x645a10dd,0xddc98968,0x38c90019,0x224200d9}}, // [71b0] ótic, _obeš, وائی_, _dekk_,
+ {{0x7d1aead1,0x90280a8e,0x7b158088,0x81e28264}}, // _nits, ецца_, _ršum, _ধরা_,
+ {{0x39495b5b,0x2242021e,0x798611fd,0x64476ad2}}, // ssas_, _fekk_, _eskw, maji,
+ {{0x64472c6a,0xc5d580e8,0x4615803d,0x2242008b}}, // laji, кіль, نوار, _gekk_,
+ {{0x6441b126,0x7d1aead3,0x3ead837a,0x09db00ab}}, // _peli, _bits, _voet_, _দরকা,
+ {{0x442c88f1,0xd0450201,0x644734f5,0x64418168}}, // _cfd_, yihə, naji, _qeli,
+ {{0x61fc8085,0x7d1a8901,0x672d816b,0x3eada525}}, // ərli, _dits, _čajo, _toet_,
+ {{0x6441ead4,0x442cead5,0x628d311a,0x1db300c2}}, // _weli, _efd_, ndao, ुनात,
+ {{0x64418dc6,0x7afb82f7,0x7d1a9f6f,0x2b40805c}}, // _teli, rlut, _fits, ćica_,
+ {{0x7afb8e23,0x7d1ab637,0x40959c79,0x8c3c87c0}}, // slut, _gits, трит, _boğu,
+ {{0x81d700ab,0x629d4742,0x6f1bead6,0x76466ad7}}, // িথি_, _onso, _kiuc, taky,
+ {{0x38b60125,0x6d598a0f,0x8c3c817b,0x623598ba}}, // _væri_, orwa, _doğu, _реду,
+ {{0x95990c6e,0x0d9901bb,0x0d832005,0x764600dd}}, // нтру_, нтры_, алян, raky,
+ {{0x76466ad8,0x629d6ad9,0x395f8a5b,0x628beada}}, // saky, _anso, muus_, _hago,
+ {{0x8c431814,0x04430811,0x628beadb,0x316d80e7}}, // ресе, ресн, _kago, ltez_,
+ {{0x628b9eb9,0x3afa80f1,0xd5b212c8,0x7db604ad}}, // _jago, tëpi_, _نفع_, _асоц,
+ {{0x628b9b52,0x64476adc,0x69c9aea8,0x316d8cf8}}, // [71c0] _mago, baji, nzee, ntez_,
+ {{0x7ae4803b,0x629d02ec,0x81ac00c8,0x6723eadd}}, // čiti, _enso, _কেউ_, _kunj,
+ {{0x752400f3,0x6723836e,0x7d1aeade,0x6fc1864a}}, // _huiz, _junj, _rits, शनिं,
+ {{0x6f09eadf,0x6aa98024,0x672d826c,0x7d1ac7b8}}, // _chec, jjef, _čajl, _sits,
+ {{0x7d1acdd7,0x6f1b9295,0x6d4beae0,0x64a69a12}}, // _pits, _diuc, gsga, када,
+ {{0x628b82c4,0x752468c0,0xdce982a6,0xc0450e06}}, // _aago, _muiz, _preġ, _مخفو,
+ {{0x44446ae1,0x628bc45b,0x07a307a1,0x67238642}}, // _me_, _bago, _карн, _nunj,
+ {{0x44446ae2,0xbcfb0019,0x661b2bea,0xe7eb06a7}}, // _le_, ltét, žuki, _जुटा_,
+ {{0xf2d30bea,0x628b811e,0x44442ba9,0xc05a8a18}}, // יעה_, _dago, _oe_, мін_,
+ {{0x7d1aa40d,0x1bed8006,0x7aed826c,0x628b85ee}}, // _uits, _चुकल_, čata, _eago,
+ {{0xa0a38196,0x6aa98144,0x2fc98326,0x64476ae3}}, // ршыл, bjef, _ƙaga_, vaji,
+ {{0x67238699,0x628be4bd,0x44446ae4,0x26c6808e}}, // _dunj, _gago, _ae_, nhoo_,
+ {{0x4444073a,0x64476ae5,0x316d82be,0x6280eae6}}, // _be_, taji, ctez_, memo,
+ {{0x44446ae7,0x628b912b,0x6280dd03,0x752400f3}}, // _ce_, _zago, lemo, _duiz,
+ {{0x64475791,0xa3cd01aa,0x56950b69,0x628bb104}}, // raji, _शेख_, _байт, _yago,
+ {{0x44445c06,0x6280bde1,0x64476ae8,0xf2c405a8}}, // _ee_, nemo, saji, асун,
+ {{0x44446ae9,0x8cb3801b,0x26d9005d,0x75242008}}, // [71d0] _fe_, _अवलो, miso_, _guiz,
+ {{0x44446aea,0x26d9140a,0x6f09826c,0x628081e5}}, // _ge_, liso_, _shec, hemo,
+ {{0x6280cba5,0x15f4053d,0x2d9d0118,0xa5c7008b}}, // kemo, _مسیح, _áweb_, _fróð,
+ {{0xf7738158,0x26d9029b,0x291ceaeb,0x6d4b874d}}, // יקע_, niso_, _liva_, tsga,
+ {{0x44446aec,0x6445426b,0x2249079f,0xed5991b3}}, // _ye_, _kehi, maak_, хол_,
+ {{0x40341525,0x44440104,0x628b8461,0x395f8198}}, // _верс, _xe_, _sago, vuus_,
+ {{0x628be4fd,0x58d40767,0x6f09eaed,0xdce080eb}}, // _pago, _торт, _thec, _apmē,
+ {{0xc2070b85,0xc3340051,0x64456aee,0x316d80e7}}, // _शराब_, בוק_, _lehi, ttez_,
+ {{0xbcfb0065,0xdb150019,0x291c9fca,0x26d91268}}, // ntés, gyzé, _biva_, diso_,
+ {{0x61fd016b,0x316de31b,0x628ba5f4,0x69c9890d}}, // _mysl, rtez_, _wago, rzee,
+ {{0x44440051,0x628beaef,0xca758a41,0x628090dd}}, // _re_, _tago, _булы, bemo,
+ {{0xed5a3aa3,0x628090dd,0xcfc500c8,0x44211c83}}, // ном_, cemo, _এখান, _agh_,
+ {{0x44446af0,0x75240018,0x64451d6a,0xe0df06c4}}, // _pe_, _quiz, _behi, _gwòg_,
+ {{0x44440b18,0x6723eaf1,0xbfb5bb76,0x291cb194}}, // _qe_, _tunj, _محبت, _giva_,
+ {{0x4444658d,0xb5fb0065,0x394deaf2,0x7ae48353}}, // _ve_, lgál, lses_, čitv,
+ {{0x4444004c,0x26d91a47,0x394d82e6,0x0e660cec}}, // _we_, ciso_, oses_, _скин,
+ {{0x44446af3,0x3ea06af4,0x394d85ef,0x64456af5}}, // [71e0] _te_, _init_, nses_, _fehi,
+ {{0x44446af6,0x7e69928e,0x644500ad,0xe1f73aa3}}, // _ue_, _acep, _gehi, угу_,
+ {{0x3ea01c33,0x657b82d5,0x22491a62,0xf8bf0580}}, // _knit_, _apuh, baak_, _atés_,
+ {{0xfaa61240,0xf2a618f6,0x394d81bf,0x628084c3}}, // _само, _симп, kses_, xemo,
+ {{0x62808548,0x7ae46af7,0x3ea0008e,0x7d1e14c7}}, // vemo, mnit, _mnit_, _kips,
+ {{0x7ae44453,0xbcfb0061,0x53348110,0x690b002a}}, // lnit, ltér, аект, güed,
+ {{0x6280a4ba,0x27f7003e,0x09dc903e,0x394dd26f}}, // temo, čené_, मप्य, eses_,
+ {{0x7ae40a92,0x7d1e002e,0x969585c2,0x8c3c880a}}, // nnit, _lips, _круш, _coğr,
+ {{0x8c3c82bb,0x26d90098,0x200780b9,0x7ae46798}}, // _doğr, viso_, _azni_, init,
+ {{0x7ae46872,0x22490fbc,0x20c0851e,0x3ea06af8}}, // hnit, zaak_, _eòin_, _anit_,
+ {{0x7ae40bc8,0x64450cf4,0x644ad9a5,0x290280eb}}, // knit, _rehi, mafi, ēka_,
+ {{0x64451a67,0x2f9703de,0x656f0799,0x61469073}}, // _sehi, אכטן_, ttch, _беда,
+ {{0x442102c4,0x25bf0019,0x26d96af9,0x7ae46afa}}, // _pgh_, ául_, riso_, dnit,
+ {{0x40349515,0x5f948e8e,0x644a85a3,0x656f6afb}}, // _текс, _викт, nafi, rtch,
+ {{0xd6d10c2a,0x60da8458,0x7792815b,0x2246808e}}, // _وقت_, mitm, _ایجا, _keok_,
+ {{0xfebb0077,0xa6ab0277,0x5a35013a,0x59d98540}}, // _ساعت_, _سابق_, рнат, _बेडर,
+ {{0x224900c9,0x644a9b52,0x442103ac,0x200b809a}}, // [71f0] raak_, kafi, _tgh_, ęcie_,
+ {{0x04671b47,0x44211a2e,0x61fd026f,0x7ae46afc}}, // _стим, _ugh_, _vysl, anit,
+ {{0x64578510,0x644ad235,0xf80706d2,0x672d9320}}, // óxim, dafi, учан, _čajk,
+ {{0x66f70bb8,0xbcfb54ec,0x6d5d0998,0x394d84dc}}, // ीतिक_, ctér, orsa, yses_,
+ {{0xe5a59a34,0x6d4f00f7,0xa2ba0935,0x212900dd}}, // рили, nsca, _एकत्, qqah_,
+ {{0x644aeafd,0xd7fb4688,0x00000000,0x00000000}}, // gafi, _зуб_, --, --,
+ {{0x38b6035f,0x628f0704,0x6d5d0799,0x60daa6fd}}, // _vært_, _kaco, hrsa, ditm,
+ {{0x290101cd,0x6d5d0042,0x6f0d6afe,0x394dcddb}}, // llha_, krsa, _nhac, tses_,
+ {{0x91e5a28e,0x645e8228,0x672704b9,0x394deaff}}, // ропе, ópic, _hujj, uses_,
+ {{0x764beb00,0x394d8b39,0x3a2d81a1,0x6f0d018e}}, // lagy, rses_, scep_, _ahac,
+ {{0x394d8806,0x7d1e14c7,0x6f0d0219,0x672709ab}}, // sses_, _rips, _bhac, _jujj,
+ {{0x6f0d6b01,0x89d695e4,0xe1ff0144,0x7d1e6b02}}, // _chac, اویر_, _oyó_, _sips,
+ {{0x6f0d6b03,0xcb128051,0x7ae4826c,0xf50611d5}}, // _dhac, _ולא_, čitt, измо,
+ {{0x764bd8c9,0xb4c20f85,0x6609912e,0x60da8965}}, // hagy, ्फे_, _mzek, citm,
+ {{0x6d5d5bae,0xbcfb282c,0x6f0d0c49,0x6d4f6b04}}, // arsa, ttér, _fhac, asca,
+ {{0x6f0d6b05,0xd13201a8,0x3ea001c6,0x6aad008b}}, // _ghac, قمر_, _unit_, gjaf,
+ {{0x7d1e0502,0x628f02b7,0x2d8c00eb,0x7ae40ca9}}, // [7200] _tips, _daco, ādes_, rnit,
+ {{0xe3ba0112,0xbcfb637c,0x67270c2e,0xa5bb2620}}, // _або_, stér, _bujj, rcót,
+ {{0x5c5b0051,0x628f6b06,0xb4c22311,0xbcfb0c83}}, // _בדיק, _faco, ंसी_, ptér,
+ {{0x539b0039,0x628f10e4,0x6d440035,0x644ab73c}}, // _סיכו, _gaco, _kwia, wafi,
+ {{0x66098063,0x7aed82a5,0x644aeb07,0x7bce35f5}}, // _czek, čatl, tafi, lzbu,
+ {{0xd37adc30,0x224682f7,0xc448803d,0xd9430f80}}, // вчи_, _seok_, ایان_, _фери,
+ {{0x6609eb08,0x69dd81ac,0xf2c681a1,0x672703f7}}, // _ezek, úsen, исин, _gujj,
+ {{0x644abdc1,0xbcfb6567,0x628f01df,0x6e242eda}}, // safi, prég, _xaco, _ngib,
+ {{0x60f88098,0xaca40032,0x644aace1,0x63bc18b6}}, // лния_, _arọw, pafi, hyrn,
+ {{0x6d5d02a5,0x6f02a7f5,0x8c66826a,0x7ae901f4}}, // vrsa, lloc, _خطرن, četi,
+ {{0x627e0035,0xe811864a,0x6f0d6b09,0x3f98816b}}, // _złoż, डेला_, _phac, íru_,
+ {{0xaca4019d,0x6448eb0a,0x98ab80eb,0x7bdb81c6}}, // _drọw, _hedi, īcā_, _בקבו,
+ {{0x628f2477,0xe8948b73,0x9934806b,0xa1588bda}}, // _raco, _галь, _افتت, _салу_,
+ {{0x644890d1,0x628f2934,0x6d4f0f35,0x91bb81c6}}, // _jedi, _saco, rsca, _במדי,
+ {{0x6f0d0219,0x628f3089,0x6aa2eb0b,0xd9e880ab}}, // _thac, _paco, _knof, _পরিম,
+ {{0x644882fe,0x1b1911e9,0x8aa415b5,0x6f028087}}, // _ledi, ужбы_, оруд, jloc,
+ {{0x6d44009a,0x51871454,0x672d826c,0x200542e4}}, // [7210] _gwia, шува, _čaji, _šli_,
+ {{0x92d700c8,0x6448eb0c,0xd9e880ab,0x4ddc0039}}, // াকে_, _nedi, _পরাম, _שחזו,
+ {{0x33950013,0x628f6921,0x66098061,0xb60804e8}}, // _الجز, _taco, _szek, _tuší,
+ {{0x2b5e128a,0x99890824,0x628402df,0x66c66b0d}}, // átce_, mbaž_, ceio, _kóka,
+ {{0x6448a32e,0x9a128133,0x764b997a,0x356b9156}}, // _bedi, _dọkụ, ragy, трен_,
+ {{0xdfc6eb0e,0x04428767,0x6448ac92,0x8c42898d}}, // _في_, пешн, _cedi, пеше,
+ {{0xde678098,0xbcfb0866,0xe8d90133,0x7afd0af8}}, // _възп, crée, gwụ_, _kkst,
+ {{0x6f0286e7,0xa7fc82d0,0x66098cdb,0x3a98862c}}, // cloc, rgıl, _tzek, атью_,
+ {{0x7e7b8caa,0xe8f9a74b,0xa2ba152c,0x627e066f}}, // _ibup, лли_, _एकस्, _ułoż,
+ {{0x6448c0f4,0x26dd82ec,0x60d883a8,0x66d6007a}}, // _gedi, liwo_, _cmvm, mški,
+ {{0x25db85e8,0x34948b69,0x7ae481a1,0x2cf58072}}, // _खेती_, _фахр, čitr, ेतील_,
+ {{0x66c6007b,0x26dd8e80,0x5b158226,0x7bce016b}}, // _bóka, niwo_, _умет, vzbu,
+ {{0xa2a48ebf,0x7529e3ab,0x7e7b8133,0x6448d94b}}, // कार्, _juez, _mbup, _yedi,
+ {{0x764982a3,0x66d60110,0x7bce39c2,0xa5da80be}}, // _leey, iški, tzbu, אַלי,
+ {{0xdce2805c,0x7e7beb0f,0x78a3b33f,0xa3aa8697}}, // stoć, _obup, _innv, खमय_,
+ {{0x443301c0,0x86461d7b,0xd49180ff,0x7c2500b9}}, // _efx_, _униж, _là_, _eghr,
+ {{0x95560bbe,0x6d44574a,0x463b03de,0x26dd8326}}, // [7220] _اخبا, _uwia, _געפע, diwo_,
+ {{0x7afd6b10,0x6566076d,0xb4db0722,0x6c8600f7}}, // _ekst, mukh, ssàr, _الدم,
+ {{0x78b50289,0x6448d02d,0x644e6b11,0xee3a9a19}}, // _dozv, _redi, mabi, гне_,
+ {{0x644e5e7e,0x644887d5,0x61e904c4,0xf4130051}}, // labi, _sedi, _žele, _מפת_,
+ {{0x1dd2800f,0x673b005c,0x6448a7eb,0x22406b12}}, // _देखत, _čuje, _pedi, mbik_,
+ {{0x6fca823c,0x644e6b13,0x7e7ba970,0x6f02be3d}}, // ानमं, nabi, _ebup, sloc,
+ {{0x6448d15a,0x09b580ab,0xdb09826f,0x6f02d0a3}}, // _vedi, _জেলা, žnýc, ploc,
+ {{0x6566005d,0x4425eb14,0x76498def,0xdd2d0084}}, // kukh, _egl_, _geey, vėžy,
+ {{0x644e6b15,0x41de001b,0x75298118,0x39524c86}}, // kabi, _नेमस, _guez, nsys_,
+ {{0x7e6d249a,0xd90d80d5,0xfbc80540,0xa2d80a27}}, // _scap, نین_, रनाम, नोत्,
+ {{0x2d800a8e,0x644e4a6c,0xda7a8081,0xa507c987}}, // _apie_, dabi, _бял_, _уефа_,
+ {{0x490f0d86,0xcebb0326,0xb5fb01a8,0x41d9a5e8}}, // _तीनो_, _kuƙi_, lgái, _बेवस,
+ {{0x200b8063,0x3a268580,0xd7c780d7,0x26c783ed}}, // ęcia_, _igop_, لویه_, ënoi_,
+ {{0x1fa69109,0x6ce7373a,0x316015a0,0x26cb18bd}}, // _григ, сіме, driz_, shco_,
+ {{0x78b5003e,0xf8db8039,0x2a69008e,0x2419076a}}, // _rozv, _תחומ, tgab_, ровы_,
+ {{0x7e6d09ca,0x65660234,0x7b1585f3,0xada601d6}}, // _ucap, bukh, _ušut, _spúš,
+ {{0x7c2500b9,0x2d99841c,0xa2da001b,0xcfd60264}}, // [7230] _tghr, _àsex_, _नगर्, _তুলন,
+ {{0x644e32a3,0x660d1768,0x764f0035,0x9055004a}}, // cabi, _izak, lacy, івец,
+ {{0x7bde92ca,0xddcd0035,0x752988dc,0x224013ec}}, // úpul, _udał, _suez, bbik_,
+ {{0x764f3f52,0x8f9b81c6,0x3f9c80fe,0x395f8084}}, // nacy, טיבי, _mrvu_, yrus_,
+ {{0x98c50214,0x316001e8,0x94258721,0x61e4cb1f}}, // ştır_, criz_, омле, _žiln,
+ {{0x76499a25,0x66d60110,0x60c701a1,0x224b0252}}, // _weey, rški, _uljm, _heck_,
+ {{0x3ea49727,0xd49180ff,0x764f009a,0x44259839}}, // _fnmt_, _và_, kacy, _vgl_,
+ {{0x644e6b16,0xcfb200ab,0x7b158267,0x6b830187}}, // zabi, _টেকন, _ašur, _ângu,
+ {{0x4425861b,0x644e1382,0x0595803d,0x7e6481a8}}, // _tgl_, yabi, _بازگ, óipe,
+ {{0xf77187bd,0x212b0051,0xe0d9a349,0x4431811c}}, // واج_, _much_, рво_, _üz_,
+ {{0x644e0e7b,0xd0098193,0x16aa038d,0xb69b0187}}, // vabi, рене_, авни_, rlân,
+ {{0x644e2d84,0x764f230d,0x395feb17,0x3f9c80c3}}, // wabi, gacy, prus_, _drvu_,
+ {{0x644e3c36,0x7afb8e35,0x61ed8503,0xa50a15a6}}, // tabi, mout, _žalf, иема_,
+ {{0x7ae9eb18,0x660d01a1,0x2eb68035,0x66d0026b}}, // lnet, _dzak, _अक्त, _bòkù,
+ {{0x212b0943,0xa5bb25a7,0x36e90019,0x660d3ebe}}, // _auch_, scóp, _گروپ_, _ezak,
+ {{0x7ae990c3,0x316026de,0x212b01ec,0x644e4290}}, // nnet, triz_, _buch_, sabi,
+ {{0xe0da14b7,0x644e6b19,0x7ae992a5,0x2b40811f}}, // [7240] ава_, pabi, inet, ćici_,
+ {{0x3160011e,0x66046b1a,0x212b0a7a,0x7afbeb1b}}, // rriz_, _iyik, _duch_, hout,
+ {{0x212b02af,0x2905eb1c,0x7afbeb1d,0xd7ff0162}}, // _euch_, illa_, kout, _ţări_,
+ {{0x28d99094,0xe29a1269,0x2eb9816f,0xbcfb00e7}}, // योति, _там_, _आवृत, préc,
+ {{0x7ae9936f,0x94aaa7cb,0xddcd009a,0x7afbeb1e}}, // dnet, итка_, _gdań, dout,
+ {{0xd0e8004e,0x7ae9811b,0x659b0e82,0x764f6b1f}}, // _اکرم_, enet, יינק, zacy,
+ {{0xf76791fb,0xd4678b30,0x7afbeb20,0x1fb55c30}}, // _تا_, ције_, fout, _естр,
+ {{0x2905eb21,0x7afba1bf,0x6738826c,0x62965207}}, // ella_, gout, _rtvj, ndyo,
+ {{0xcb131a0f,0x045a826a,0x764f00f3,0x68fa8087}}, // ולה_, _نجات_, vacy, totd,
+ {{0x3f9c803a,0x1d0a8698,0x26c902a5,0xb17b03de}}, // _prvu_, _тези_, _slao_, סטיר,
+ {{0x660d0065,0x7afbe3dc,0x7ae9d501,0xa09b00be}}, // _szak, bout, bnet, בייט,
+ {{0x99e880ab,0x29058009,0xd6db3ca1,0x7642eb22}}, // _পরীক, alla_, рто_, nboy,
+ {{0x764f1111,0xf6538039,0x67240590,0x2bde950e}}, // racy, וצר_, _diij, _केतू_,
+ {{0x7ae0e487,0x61ed8110,0x02b800d4,0x6fb681a8}}, // limt, _žalg, _इकॉन, _ومشا,
+ {{0x57f3838c,0x212b6b23,0x53340adb,0x224b6b24}}, // _општ, _such_, дест, _peck_,
+ {{0x690b009f,0x25a6007a,0x7982819d,0xb88281d0}}, // güen, _šole_, _kpow, řídi,
+ {{0x69c0d62a,0x660d6b25,0x645a80e1,0xd52d027d}}, // [7250] nyme, _uzak, _odti, _dựn,
+ {{0xf8070f16,0xcf9b0b30,0x7ae08e9d,0x7ae9801b}}, // очен, ије_, himt, znet,
+ {{0x764d00b9,0x7d07026c,0x7ae9a45f,0x37ab1a1b}}, // _keay, mljs, ynet, штан_,
+ {{0x0f7b8039,0x7982809a,0x645a8748,0xe8a78c2d}}, // שראל, _opow, _adti, _कच्च,
+ {{0xa3df0e1a,0x29059a21,0xdca58468,0xf53f01a3}}, // _देत_, ylla_, зали, npå_,
+ {{0x91ba0039,0x69c0b3ab,0xd7ca8072,0xab9800d7}}, // _המשי, dyme, ानाच, _اخیر_,
+ {{0x447c0158,0x7982826b,0x7afb814f,0xdcfb8140}}, // ינגע, _apow, tout, _upuč,
+ {{0xd2a98364,0xfbd0803d,0xe7fe8105,0xdffe83b7}}, // ские_, خته_, _उड़ा_, _उड़द_,
+ {{0xa3db8b85,0xaca381bc,0x213980f1,0x2905eb26}}, // _ढेर_, _ahụl, ësh_, tlla_,
+ {{0x7ae9eb27,0x6d4980fc,0x88c98389,0x98bd65ba}}, // snet, _iwea, слов_, ोसिए,
+ {{0xb8e392e0,0x07a58b73,0x660400b9,0x7ae9cb1f}}, // _एक_, чанн, _syik, pnet,
+ {{0xe298835f,0x752d05ee,0x6a94804a,0x3f830af8}}, // _має_, _buaz, _оріє, _mpju_,
+ {{0xe0cea5bc,0x6b5080f7,0x7ebf03ed,0x3eab136f}}, // _зв_, _tógá, _nëpu, _šité_,
+ {{0x213f859c,0x40878992,0x913a8039,0x69d490f7}}, // mpuh_, _фудб, _העסק, _बेटी,
+ {{0x68e1eb28,0xf99f5ba7,0x6e298326,0x99dd816b}}, // mild, _arè_, _ogeb, raňu,
+ {{0x8b23039d,0x6e29eb29,0x61e96b2a,0x28de103e}}, // едсе, _ngeb, _žela, फोनि,
+ {{0xf9831bf2,0x798d6b2b,0x753b862b,0xf99f010c}}, // [7260] нгто, gwaw, _ntuz, _crè_,
+ {{0x765beb2c,0x6e29838a,0xb4660d15,0x68e1b6b2}}, // _aduy, _ageb, _экол, nild,
+ {{0x753bb071,0xf99f03ec,0x752d1fa4,0x66e68468}}, // _atuz, _erè_, _zuaz, пожа,
+ {{0x68e18d35,0x690b07e0,0xf99f0247,0x798d0365}}, // hild, qüen, _frè_, bwaw,
+ {{0x68e19739,0xa50a867c,0x1d0a8ae7,0xe29b01c6}}, // kild, беда_, беди_, _השכר,
+ {{0x3940267f,0x7afb007a,0x2d9203ec,0x6562eb2d}}, // mpis_, čute, _msye_, rroh,
+ {{0x6fb60b76,0x79828035,0xbcfb0061,0x68e1a2f8}}, // _عمرا, _spow, krén, dild,
+ {{0x994c0038,0x78b88d02,0x66c6008b,0x6562eb2e}}, // _môže_, _sovv, _bókm, proh,
+ {{0x672e026c,0x64438106,0x7ae0d70d,0x2d9feb2f}}, // _hubj, bbni, rimt, _grue_,
+ {{0xbf0505fc,0x26c6eb30,0x7ae0b76c,0xd90e803d}}, // रत्न_, lkoo_, simt, دیت_,
+ {{0xb4e8816f,0x25a000b9,0xb2268049,0x7d070267}}, // मची_, _aril_, _емел, vljs,
+ {{0x6b9c012b,0x25a0110f,0x0b0000ab,0xe0df0176}}, // dvrg, _bril_, ্দুল_, _awòl_,
+ {{0x68e1e27f,0x91d4a701,0x68fe0a0f,0xaaa9801b}}, // bild, _बेचै, kopd, कारक,
+ {{0x013783c8,0xf53f01a3,0x690b0118,0x2ceb6b31}}, // _צרות_, rpå_, güel, сьмо_,
+ {{0xa87b8039,0x187b8039,0xd25b0fe6,0xe0df0242}}, // _האור, _הטוב, йца_, _dwòl_,
+ {{0x59de0fce,0x3d23046d,0xbcfb0061,0x31690cdb}}, // _नेहर, _béwì_, ntéz, tuaz_,
+ {{0x44f510f8,0xc2c400f7,0x394000e4,0x98b9041c}}, // [7270] _опис, ريكي, gpis_, олат_,
+ {{0x7e7d1254,0x798d14d9,0x66f201a9,0x6e298362}}, // lfsp, rwaw, māka, _sgeb,
+ {{0x2fc70104,0x66f200eb,0x753beb32,0x41c881d0}}, // áng_, lāka, _stuz, ाईँस,
+ {{0xa63480e8,0xf99f06c0,0xd0920085,0x68e18965}}, // енці, _trè_, _müəs, zild,
+ {{0x7aed03c3,0xfe70803f,0x3d09816f,0x66f200eb}}, // mnat, ادم_, ितले_, nāka,
+ {{0x2fc70104,0x2bde8006,0x83399156,0x39590216}}, // ơng_, _केहू_, очит_, _ivss_,
+ {{0x6443eb33,0x20c9807b,0xddc2c2e3,0xe45081a8}}, // rbni, _búin_, rgoš, اضه_,
+ {{0x7aed20f5,0x5694a749,0x2480805c,0x6d49c3e8}}, // nnat, _паст, đima_, _twea,
+ {{0x61ed805c,0xf4879b10,0xac278d94,0xa3df0074}}, // _žalb, _нужн, _ефек, _देस_,
+ {{0x629d81ac,0x3ea90722,0x3b090359,0x7aed0428}}, // ôsob, _anat_, hlaq_, hnat,
+ {{0x68e1817b,0x3e8a15a6,0x656b9fb7,0x394027aa}}, // rild, ойно_, lugh, zpis_,
+ {{0x32e386c0,0x68e1eb34,0x3940008b,0xee370d55}}, // _dèyè_, sild, ypis_, мню_,
+ {{0x68e18612,0x7bc3828f,0x66f201a9,0x7afc8061}}, // pild, lynu, gāka, _írta,
+ {{0xa3df01b6,0xe8f8b73a,0x7aed6b35,0x2bd1975d}}, // _देह_, _елі_, enat, तनपा,
+ {{0x02c90076,0xeb97280f,0xbcfb6b36,0x26cdb736}}, // रसंभ, мир_, prén, _oleo_,
+ {{0x7aed20dc,0x3940005c,0x2127801c,0x66f200eb}}, // gnat, tpis_, _hinh_, bāka,
+ {{0x21278104,0x09e30391,0xa3e48540,0x672e33fe}}, // [7280] _kinh_, вотн, _पेन_, _subj,
+ {{0xdcfb803b,0x7aed0a4f,0xa3df0744,0x3ea90639}}, // _osuđ, anat, _देव_, _znat_,
+ {{0x21278028,0x443a6b37,0x68fe0bfd,0x212feb38}}, // _minh_, _ifp_, ropd, _lugh_,
+ {{0x21278028,0x67000bb8,0x273380ff,0x39406668}}, // _linh_, ैतिक_, _mãn_, ppis_,
+ {{0x7ae46b39,0xb5fb0019,0x471b03de,0x20561e7d}}, // miit, lgár, קונג, _отвр,
+ {{0x7ae4043d,0x2127801c,0xb4e8816f,0xed59016b}}, // liit, _ninh_, मचे_, áži_,
+ {{0x201e0324,0x9f88007b,0x6f0406ec,0xd823393f}}, // _àti_, _góða_, _okic, _адри,
+ {{0x6b830106,0xdce6016b,0x78bc333a,0x21278362}}, // _änge, dukč, _horv, _ainh_,
+ {{0x645e06b9,0x78bc156e,0x2127801c,0x69c46b3a}}, // _odpi, _korv, _binh_, nyie,
+ {{0x7aed003a,0x7ae46b3b,0x443a377d,0x6f043a2f}}, // znat, hiit, _nfp_, _akic,
+ {{0x20c980f7,0x2127801c,0x442c808e,0x7aed6b3c}}, // _iúil_, _dinh_, _igd_, ynat,
+ {{0xa1c687eb,0x25a60353,0x752880cd,0x66f200eb}}, // _обед, _šola_, _hidz, tāka,
+ {{0x7aed0988,0x6282eb3d,0x20078085,0x78bc067f}}, // vnat, _iboo, _eyni_, _oorv,
+ {{0x66f200eb,0xf76f80d7,0x61e902d4,0xf4840469}}, // rāka, دای_, _želo, кусн,
+ {{0xa3df0935,0x3b0902a6,0x25ad0493,0xc059b73a}}, // _देश_, tlaq_, ţel_, _хіі_,
+ {{0x7ce908ae,0x2bb6805e,0x3ea902d5,0x32646b3e}}, // džre, _अपना, _unat_, ктув,
+ {{0x7aed6b3f,0x2bff0fcc,0xaca30870,0xa3df0740}}, // [7290] rnat, _शुरू_, _abụg, _देर_,
+ {{0x21278028,0x273704b7,0x7528d619,0x62828122}}, // _xinh_, _aħna_, _nidz, _lboo,
+ {{0x64553f2b,0x7d0510e1,0x78bc0bfe,0x6d4d1cfe}}, // mazi, _ikhs, _dorv, _hwaa,
+ {{0xa29e800d,0x6d4d4150,0x7c3a9989,0x7528808e}}, // _खोल्, _kwaa, _aftr, _aidz,
+ {{0x78bc005f,0xca240d9e,0x442ceb40,0x6e2d0122}}, // _forv, _афри, _bgd_, _mgab,
+ {{0x6455499f,0xeb99dc59,0x6282a57a,0x8b958eef}}, // nazi, зик_, _aboo, ерич,
+ {{0x780d8cf0,0xa3ca8327,0x21278129,0x6d4d0c2e}}, // _हरेक_, रैल_, _rinh_, _lwaa,
+ {{0x21278104,0x6e2d1e98,0x2d4d027f,0x2901026f}}, // _sinh_, _ngab, kže_, loha_,
+ {{0x15f3b281,0x645536d0,0x68ef8511,0x6abd2f3e}}, // _आखिर_, kazi, écdo, _mosf,
+ {{0x6f041918,0x2d4d0fee,0xb4be0006,0x6e2d13b6}}, // _skic, dže_, _आवे_, _agab,
+ {{0x64556ac9,0x6d4d466a,0x2127801c,0x6609808e}}, // dazi, _awaa, _vinh_, _hyek,
+ {{0x6282846d,0x7ac488ed,0x61ea8061,0xa5bb0216}}, // _gboo, _исче, ámlá, lcóy,
+ {{0x67298063,0x21278028,0x6d59800b,0xe9ce9b47}}, // _miej, _tinh_, nswa, _ск_,
+ {{0x6d4d0cda,0xbebd81e2,0x6f1d64da,0x2b91826c}}, // _dwaa, _siūl, amsc, _kšc_,
+ {{0x62998f33,0x6abd0b80,0xdee62306,0x443a0037}}, // _kawo, _bosf, _поки, _vfp_,
+ {{0x78bc4e7c,0x629beb41,0x61e481ac,0x6729a19c}}, // _sorv, nduo, _žili, _niej,
+ {{0xe9da067c,0x64a66b42,0xdca61240,0x64551898}}, // [72a0] чка_, _запа, _запи, bazi,
+ {{0x64550698,0xc05900e8,0x6299e6a7,0xdce281b9}}, // cazi, дії_, _lawo, rroċ,
+ {{0x6d4d0a0f,0x75288abf,0x9e66916b,0x66098234}}, // _zwaa, _ridz, евед, _ayek,
+ {{0x62998953,0xd0418201,0x20c980f7,0x481680be}}, // _nawo, rhlə, _súil_, ײַבן_,
+ {{0xe5a39285,0x442ce3cd,0xdd94853b,0x6594a5fc}}, // лици, _pgd_, талы, талу,
+ {{0x2480817f,0x6e3b838a,0x66098176,0xf99f0242}}, // đimo_, _efub, _dyek, _myèl_,
+ {{0x7ac68009,0x2ca7002a,0x6299eb43,0x752881a9}}, // еспе, _ónde_, _bawo, _vidz,
+ {{0x7528809a,0x645536d0,0xd25080f7,0x629b87cd}}, // _widz, zazi, جنة_, gduo,
+ {{0x6299b753,0x64556b44,0x4c86a714,0x6d598c93}}, // _dawo, yazi, _плав, cswa,
+ {{0xa3c2005e,0xeb9a89a5,0xbcfb1db6,0xd7fa8ce5}}, // ्ना_, _низ_, trém, зум_,
+ {{0xa3df0063,0x64551aae,0x6d4d01b0,0x2caceb45}}, // _दें_, vazi, _swaa, _indd_,
+ {{0x645511b7,0x56941af1,0x6299bfb0,0xf99f02d6}}, // wazi, гают, _gawo, _byèl_,
+ {{0x64556b46,0x7ae28aba,0x2918022b,0x6f1d0865}}, // tazi, _imot, _ohra_, rmsc,
+ {{0x7ae2803e,0x2d4d0042,0x6299809a,0xdce2826f}}, // _hmot, rže_, _zawo, kroč,
+ {{0x645527a1,0x6299801d,0xa73781a8,0x7b058118}}, // razi, _yawo, _كثير_, cóus,
+ {{0xdce28353,0x645567a2,0x291802f7,0x290ceb47}}, // droč, sazi, _ahra_, llda_,
+ {{0x6455499f,0xe9f80163,0x29016b48,0x69d682d0}}, // [72b0] pazi, енті_, toha_, _üyed,
+ {{0x629b8084,0xe4e5909b,0x67299c28,0x657d6b49}}, // zduo, _औषधि_, _siej, mtsh,
+ {{0xec360051,0x660987cf,0x6d4088db,0xe81c150e}}, // _באתר_, _syek, _atma, भेला_,
+ {{0x6d59eb4a,0xf4120039,0x291802c4,0xb8c826ee}}, // tswa, _כפי_, _ehra_, _खो_,
+ {{0x9f5c8364,0x672985a4,0x657d005d,0x656f054a}}, // _hyvä_, _viej, ntsh, nuch,
+ {{0xdce2d792,0x6299ab5f,0x6d59cfa2,0xbcfb0144}}, // broč, _sawo, rswa, mués,
+ {{0x6d59eb4b,0x09c9816f,0x6299d941,0x6d40eb4c}}, // sswa, िन्य, _pawo, _etma,
+ {{0x656f2647,0xada680e1,0xeab180f7,0x290ceb4d}}, // kuch, _slúž, شعب_, elda_,
+ {{0x629b81e2,0x36d48009,0x3eadeb4e,0x656f019d}}, // rduo, _сотр, _inet_, juch,
+ {{0xa3d103b7,0x656f003e,0x3ebf8fb0,0x25a6007a}}, // वना_, duch, _hout_, _šolo_,
+ {{0x366a1511,0x644bb52c,0x3ebf86c0,0xf99f06c0}}, // мано_, čnič, _kout_, _syèl_,
+ {{0x656f6b4f,0xed5a2c38,0xdee60bc7,0xfe7201a8}}, // fuch, мом_, томи, جدد_,
+ {{0x316d82be,0x3eadeb50,0xf1d98035,0x3ebf8299}}, // quez_, _mnet_, _बेचन, _mout_,
+ {{0x7d0397bb,0x71d6807c,0x7ae28f28,0x31696b51}}, // nons, _בונד_, _zmot, draz_,
+ {{0xe7e1035a,0xe7b282e3,0x3eadeb52,0x6d400177}}, // _गेला_, _نمود, _onet_, ímab,
+ {{0xc3328bea,0x656f52b2,0x212a125b,0x657d1d14}}, // שון_, buch, _sibh_, btsh,
+ {{0x656f00ab,0x3b0701f3,0xeabf00ff,0x0eaf064a}}, // [72c0] cuch, тето_, _giùm_, टारड,
+ {{0x07a605e9,0x7d0380e8,0x3ebf92b6,0xe0431777}}, // казн, jons, _aout_, инуи,
+ {{0x7d03b327,0x3ebf9b27,0x6d4082a6,0xa3df0b49}}, // dons, _bout_, _stma, _देऊ_,
+ {{0xcfa90013,0x316921d5,0xdd1101d0,0x3ebfeb53}}, // عالم_, braz_, mýšl, _cout_,
+ {{0x29078e3a,0x7d03d77c,0x3ebf83ec,0x6f0282ed}}, // _okna_, fons, _dout_, rooc,
+ {{0x7d03bf88,0x26c00118,0x7ae2a8fc,0x50435c30}}, // gons, _loio_, _smot, _верб,
+ {{0x3ebfdbdd,0xa3e803b7,0x7e0f01a2,0x656f686e}}, // _fout_, _मेम_, _सर्ग_, zuch,
+ {{0xb8650065,0xf8ca853f,0x49748073,0x3ebf9c25}}, // _لاہو, िस्प, глис, _gout_,
+ {{0x0d7783f8,0x6d408186,0xa3df243f,0xc8788214}}, // _لینک_, _utma, _देई_, liği_,
+ {{0x30a68750,0x52bf0e00,0x1be394df,0xa8a69baa}}, // трив, _एक्स, _केवल_, трик,
+ {{0x6458a5ed,0x08570039,0x656f6b54,0x443e9dd5}}, // mavi, כבים_, wuch, _ift_,
+ {{0x6458aed0,0xe24680f7,0xdce600eb,0x656f6b55}}, // lavi, _شخصي, vukā, tuch,
+ {{0x2002809a,0x443e8019,0xbcfb0019,0x28a68072}}, // łki_, _kft_, nség, _कोथि,
+ {{0x645899b8,0x656f45f2,0x657d6b56,0x315783de}}, // navi, ruch, rtsh, _יידן_,
+ {{0x68e881f2,0x656f0352,0x6d466b57,0x657d6b58}}, // midd, such, apka, stsh,
+ {{0x656f0870,0x63a527b1,0xc8788214,0x68e8eb59}}, // puch, _vrhn, diği_, lidd,
+ {{0x5a342386,0x3ebf8242,0x22494a85,0x7d03aeae}}, // [72d0] рнут, _rout_, rbak_, yons,
+ {{0x64588b48,0x660d1c38,0x7d1a81a1,0x3ebf867f}}, // javi, _iyak, _nhts, _sout_,
+ {{0x7d03eb5a,0x3ebf8bfe,0x672d0c53,0x32d18091}}, // vons, _pout_, _kiaj, _láya_,
+ {{0xbcfb428b,0x248587d5,0x660d4087,0xadc4046d}}, // dréi, _sblm_, _kyak, _abẹw,
+ {{0x629d27e4,0xbcfb3ca8,0x3cf807d2,0x6d5d6b5b}}, // _iaso, gség, _سعید_, nssa,
+ {{0xb97a0051,0x6d5d6b5c,0x6458eb5d,0x629d0019}}, // _אנשי, issa, gavi, _haso,
+ {{0x3ebfeb5e,0x7d03ad73,0x629d486f,0x68e8e11f}}, // _tout_, rons, _kaso, didd,
+ {{0x629d3a74,0x81de80c8,0x7d03c926,0x672d3759}}, // _jaso, _দুই_, sons, _niaj,
+ {{0x7d03eb5f,0x660d3623,0x629d24f1,0x35f5a133}}, // pons, _nyak, _maso, _спар,
+ {{0x2ecc01cb,0x629d057b,0x68e88ec8,0x26c00118}}, // ास्त, _laso, gidd, _soio_,
+ {{0xd2518013,0x660d6196,0x28d995bc,0x26c003a8}}, // _هنا_, _ayak, योगि, _poio_,
+ {{0xc332804c,0x28b18074,0x656b8087,0x660d020c}}, // _הוא_, जाहि, orgh, _byak,
+ {{0x672d0812,0x01658a13,0x7ae980e1,0x68e8c577}}, // _diaj, _скло, miet, bidd,
+ {{0x7ae9a57b,0x515a8039,0x660d1061,0x6abb82d4}}, // liet, _טכנו, _dyak, ljuf,
+ {{0x629d2135,0xbea60a08,0xbcfb0019,0x2905aef3}}, // _baso, _байк, zség, mola_,
+ {{0xd00e8013,0x2905a9f0,0x7ae9eb60,0xe2972710}}, // _علي_, lola_, niet, лас_,
+ {{0x660d1fc5,0x6458eb61,0x3f8a0101,0x6da602ac}}, // [72e0] _gyak, yavi, _spbu_, _сипа,
+ {{0x7ae9eb62,0xc8788214,0x317f811b,0x69c981b4}}, // hiet, tiği_, ituz_, iyee,
+ {{0x7ae9eb63,0x629d6b64,0x3f5180eb,0x2f190019}}, // kiet, _faso, nšu_, sége_,
+ {{0x629d0247,0xbcfb6b65,0x2905eb66,0x443ed454}}, // _gaso, tség, hola_, _sft_,
+ {{0x2905eb67,0x7ae9a798,0x69c981c0,0xbddb0866}}, // kola_, diet, jyee, ccèd,
+ {{0x629d58eb,0x2f158106,0x628401e8,0x75360144}}, // _zaso, råga_, lfio, _muyz,
+ {{0xbcfb0065,0xa3e4923a,0x929d809a,0x629d2fdd}}, // sség, _पेश_, woła, _yaso,
+ {{0xa3bc0665,0x645882d7,0x7e640010,0x7ae9cff6}}, // _आपन_, savi, _ndip, giet,
+ {{0x3f518029,0x6d4401e9,0x6da39860,0x62840114}}, // ešu_, _ntia, _лита, ifio,
+ {{0x7e64123c,0xb603826f,0xe97b00be,0x64588085}}, // _adip, ýšen, עניש, qavi,
+ {{0x7ae98920,0xbec280f1,0x2286e8f4,0x68e88e05}}, // biet, _çësh, _булг, ridd,
+ {{0x68e89e1a,0x7ae9b277,0x6d5d04fe,0xbcfb1a1f}}, // sidd, ciet, tssa, isée,
+ {{0x672d3b50,0x2905eb68,0x6d5d6b69,0x62fc0264}}, // _viaj, bola_, ussa, ্ষায়_,
+ {{0x290596cb,0x660d004f,0x629d1fdc,0x6722ea94}}, // cola_, _vyak, _saso, hmoj,
+ {{0xd5a48986,0xa3df101b,0x6d4402f9,0x629d06a4}}, // _کہ_, _देख_, _etia, _paso,
+ {{0x3d1a0105,0x63a88503,0x6d5d3f82,0x6b9b066f}}, // _मीठे_, _mrdn, pssa, ługo,
+ {{0xdcfb8025,0x085502c7,0x20180748,0x286b1715}}, // [72f0] _upuć, иваю, _azri_, ерео_,
+ {{0x63a8aebd,0xe6c0803a,0x629d341a,0x7ae9811b}}, // _ordn, _čišć, _waso, ziet,
+ {{0x5f9509b5,0x629d6b6a,0x963592e9,0x7a2b82d0}}, // риет, _taso, анец, _kötü,
+ {{0xbcfb0036,0xa283003d,0x290583df,0x69c9eb6b}}, // sséd, _دیرو, zola_, yyee,
+ {{0x7ae99ffe,0xe80b800f,0x29058365,0x4e7892c8}}, // viet, _सुना_, yola_, احمد_,
+ {{0x64a5b38c,0x7ae9809a,0x690b002a,0x93459b2f}}, // рака, wiet, güet, анке,
+ {{0xe2999052,0x7ae9c9d4,0x273a80f1,0x39495660}}, // вай_, tiet, _nën_, lpas_,
+ {{0x05158a49,0x2bb68bb8,0x64573c6e,0x69db811e}}, // াদের_, _अपरा, _lexi, tzue,
+ {{0x2905eb32,0xe8f994b7,0x50b5bb17,0xc8b58d15}}, // tola_, кли_, асну, асны,
+ {{0x9f859269,0x273a80f1,0x69db816a,0x317f8102}}, // агод, _bën_, rzue, rtuz_,
+ {{0x7ae9dbbb,0x6d442167,0x25a9009c,0x63a880d2}}, // piet, _stia, _aral_, _grdn,
+ {{0x28d98054,0x2905b328,0x2d8d81bc,0x25a92461}}, // योजि, sola_, _kpee_, _bral_,
+ {{0x224d8573,0x20d580e8,0x66f200eb,0x25a90037}}, // mbek_, _війс, nāki, _cral_,
+ {{0xccf28158,0xc5f28039,0x20f4313a,0x2905eb6c}}, // ַכן_, קדם_, _आदेश_, qola_,
+ {{0x42562657,0xfbc30d9e,0x6b830106,0x395fa7a5}}, // итат, обро, _ängl, lsus_,
+ {{0x7a2b8182,0xaa465b5d,0x4a462597,0x3a3fd62f}}, // _götü, реал, рнав, ncup_,
+ {{0x7d0705f5,0xe2971182,0x00e61597,0x60c38bd6}}, // [7300] bojs, шат_, ажан, _konm,
+ {{0x38c80416,0xf1bf04c3,0xe0d73fe7,0x61e6807b}}, // داری_, _agás_, аву_, úkli,
+ {{0x39491d7e,0x81cb8264,0xdb019b88,0x3ea06b6d}}, // apas_, রছি_, _arlí, _kait_,
+ {{0x4aca01fe,0x395fe5e9,0xaaca02f1,0xe5c69fb4}}, // _रविव, ksus_, _रविक, исло,
+ {{0x3a3f9b11,0x6722eb6e,0x224d81a1,0xbcfb00e7}}, // dcup_, rmoj, dbek_, ssée,
+ {{0xa29e800d,0x60c383ec,0xf366828b,0x3ea02b92}}, // _खोज्, _nonm, атин, _lait_,
+ {{0x316ddb48,0xbcfb0866,0xee3a961a,0x316022f8}}, // erez_, prév, _анб_, lsiz_,
+ {{0x645c3490,0x8f9b098a,0x22401495,0x2fc73a53}}, // hari, _בילי, ncik_, änge_,
+ {{0xee0aa155,0x31600457,0x66f200eb,0x63a8d579}}, // _реал_, nsiz_, cāki, _wrdn,
+ {{0x645c3a5c,0x60c382ba,0x28a69c4f,0xf48484c0}}, // jari, _conm, _कोशि, _داری,
+ {{0x645707f4,0x224003ac,0x26c48041,0x10370039}}, // _rexi, kcik_, _komo_, יטים_,
+ {{0x25a90205,0x28a6a23a,0x3160007e,0x3ea00219}}, // _pral_, _कोरि, ksiz_, _cait_,
+ {{0x25a69989,0xe5c423e7,0x316d8162,0x60c3eb6f}}, // nvol_, осро, crez_, _fonm,
+ {{0x645c0423,0x7d020307,0x26c4e4b5,0x60c382d6}}, // gari, _íosl, _lomo_, _gonm,
+ {{0x3ea006e3,0xa3bc035a,0xd0f580d4,0x7d07017f}}, // _fait_, _आपण_, ीकरण_, rojs,
+ {{0x5a3500b3,0x66e40073,0xdbdc816b,0x645c02f1}}, // снат, _моја, jším, aari,
+ {{0x765d4463,0x7afa0125,0x7c874b60,0xbcfb008b}}, // [7310] masy, étta, _куне, frét,
+ {{0x44990153,0x765d0e7e,0x394928c6,0x3ea00102}}, // _свою_, lasy, rpas_, _zait_,
+ {{0x26c48db7,0x442704be,0x9295272e,0xfc46016b}}, // _bomo_, _àn_, _ланц, číte_,
+ {{0x26c48510,0x765d2ac5,0x61e9005c,0x39490687}}, // _como_, nasy, _želu, ppas_,
+ {{0x8c459a1a,0x6da5835f,0x395f811c,0xd05a8085}}, // беле, сила, xsus_, sitə,
+ {{0x316d82be,0x7c8782ff,0xbcfb00e7,0x64418122}}, // vrez_, руде, crét, _qfli,
+ {{0x765d0e87,0x3946eb70,0x6f0d6b71,0x22594955}}, // kasy, _atos_, _okac, _kesk_,
+ {{0x7aed511e,0x645c6b72,0x2139020f,0x395f8074}}, // niat, zari, _kush_, tsus_,
+ {{0x645c5531,0x20028059,0x3a3fe4da,0xf99180f7}}, // yari, şkil_, rcup_, سبة_,
+ {{0x14778c48,0x316de31b,0x6f0d4dd5,0x645c43f0}}, // _خارج, rrez_, _akac, xari,
+ {{0x395f82be,0x85048875,0x645c6966,0x3ea015d7}}, // ssus_, _بولن, vari, _pait_,
+ {{0x765d1f10,0xc4c901c6,0x31606b73,0x2b818035}}, // gasy, _אג_, ysiz_, móc_,
+ {{0x7d1e0af8,0x7aed0be5,0xa969813a,0x95830fe6}}, // _phps, diat, _сила_, пляе,
+ {{0x2d82173d,0x2ed4863a,0x60daeb74,0x22938019}}, // rtke_, _दत्त, chtm, _دلچس,
+ {{0x4adb8074,0x2d826b75,0x7af61be9,0x799d05ee}}, // _बताव, stke_, tnyt, _assw,
+ {{0x645c42b0,0x7aed6b76,0x7f3a8158,0x31606b77}}, // sari, giat, _געקו, tsiz_,
+ {{0x5d860013,0xe8fa2355,0xe80b83ca,0x39468122}}, // [7320] _الخل, гла_, _सुता_, _xtos_,
+ {{0x3160080a,0x645c5128,0x7af6119b,0x26c4e8fb}}, // rsiz_, qari, snyt, _somo_,
+ {{0x4444004c,0xbbb68744,0x7aed0ab3,0xbcfb008b}}, // _if_, _अपेक, biat, rrét,
+ {{0x667680f7,0x7aed160c,0xe8e08028,0x673b00c3}}, // إدار, ciat, _thủy_, _čujt,
+ {{0x2cea825d,0x63f70fd3,0x21390db1,0xb33c8372}}, // лько_, _نفیس_, _gush_, _irħi,
+ {{0xc9530bea,0x765d0247,0x3ea0800d,0x6d56031d}}, // _ממש_, zasy, žit_, _mwya,
+ {{0x25a6a4c2,0x765d06c0,0xdbdc816b,0x0ec80075}}, // svol_, yasy, pším, _लवंड,
+ {{0x26c48098,0x6aa4243c,0x656f031d,0x3946816a}}, // _uomo_, ndif, yrch, _ptos_,
+ {{0x444467ac,0xc05a8221,0x765d3a9c,0x66f200eb}}, // _of_, лін_, vasy, nāku,
+ {{0x7aed6ac9,0xdd8f82e3,0x656f1c18,0x7afb007a}}, // ziat, _سوم_, vrch, čuti,
+ {{0x765d2046,0xa3e82303,0x48a78a14,0x60da83e4}}, // tasy, _मेल_, стым_, rhtm,
+ {{0x44444305,0x645ac292,0xe3b18065,0x60da8168}}, // _af_, _heti, _کرے_, shtm,
+ {{0x765d3a7f,0x645a8812,0x7aed1aae,0xd24f80f7}}, // rasy, _keti, viat, _أنه_,
+ {{0x98a38009,0x765d3971,0x897b8039,0x7aed0035}}, // _ниче, sasy, _ערוצ, wiat,
+ {{0x765d2588,0x2f120061,0x7aed4a2b,0xd00f81a8}}, // pasy, zági_, tiat, _كله_,
+ {{0x44446b78,0x645a877b,0x61ed943c,0xbcfb0019}}, // _ef_, _leti, _žali, trés,
+ {{0x6aa2804f,0x7984076d,0x1ef801a8,0x273e1d5e}}, // [7330] _maof, gtiw, _دعوة_, _aïn_,
+ {{0x4aa90076,0x7aed6b79,0xb33c8197,0xa3abb850}}, // _चोरव, siat, _erħi, खिम_,
+ {{0x661b8063,0x7aed4cc8,0x2d921ba4,0x629637ac}}, // _szuk, piat, čneš_, beyo,
+ {{0xbcfb02be,0x645a8706,0xe8b586ab,0x6aa28174}}, // prés, _aeti, _अच्च, _naof,
+ {{0x645ab665,0x2bd10074,0x356bb02b,0x7afd0bfd}}, // _beti, सनका, урен_, _ijst,
+ {{0xa3e801ce,0x1b498d70,0x2489825b,0xf99f6b7a}}, // _मेँ_, азки_, đamo_, _dyèt_,
+ {{0x2f120019,0xc7c40790,0xdb09816b,0xfd108199}}, // sági_, _ести, žníc, رجه_,
+ {{0xa3bc0105,0xcf9204de,0x6d4b804a,0x66f201a9}}, // _आपस_, _קטן_, ppga, nākt,
+ {{0x69d687d9,0xe0d994b7,0x7e6981bc,0x661beb7b}}, // _üyel, иви_, _idep, _uzuk,
+ {{0xe6c5005c,0x645aeb7c,0x1f65968a,0x6d49851e}}, // _češć, _geti, оком, _itea,
+ {{0xa3e80403,0xdbf2000d,0x69100006,0xb8d190d0}}, // _में_, _příp, päev, _ऑफ_,
+ {{0x753b8010,0x60c70bd6,0x823480d7,0xd62a02de}}, // _kuuz, _lojm, _فرها, ионе_,
+ {{0x645a8182,0x69cb013c,0xdd0e861c,0x753b85ee}}, // _yeti, øgel, nışl, _juuz,
+ {{0x7afd026c,0x394dbb7c,0x753bdc86,0xf77180d7}}, // _ajst, mpes_, _muuz, _قاب_,
+ {{0xda5a1182,0x62960242,0xf99f313c,0x44441532}}, // ираш_, teyo, _asè_, _vf_,
+ {{0x7d0a80d2,0x25b506a5,0x8c3c83bf,0x225f89d1}}, // zofs, ñaló_, _mağa, nauk_,
+ {{0x02ca0a74,0x62965ba7,0xe0df06c0,0x44441532}}, // [7340] _रवीन, reyo, _pwòp_, _tf_,
+ {{0x78a3eb7d,0x26c2003a,0x0ef4eb7e,0x394dd568}}, // _janv, ljko_, ेक्स_, ipes_,
+ {{0xee3ace78,0x79846a2b,0x66f20029,0x60c72bf8}}, // ане_, stiw, sāku, _dojm,
+ {{0x268a8307,0x645a9a67,0x2fc7016d,0x78a39f1b}}, // شخصي_, _seti, änga_, _lanv,
+ {{0x645aaab9,0x659680f7,0x226501d0,0xba480084}}, // _peti, تجار, řský_, grįž,
+ {{0x53348729,0x6d4980f7,0x7e698135,0x6562ad68}}, // пект, _dtea, _edep, rsoh,
+ {{0xdfd20013,0x39400239,0xf4878fd3,0x645aeb7f}}, // _غير_, lqis_, تانی, _veti,
+ {{0x78a3a40d,0x249800f3,0x7d0a826c,0x25a00118}}, // _aanv, herm_, sofs, _osil_,
+ {{0x645abffc,0x78a3b194,0x673c0198,0x2c110074}}, // _teti, _banv, _hurj, _दुनू_,
+ {{0x23650353,0x2a600069,0x673c27a5,0x320f0019}}, // šlji_, haib_, _kurj, ügyi_,
+ {{0x4a468dbd,0x2a668098,0x25a029c5,0xd7fa82df}}, // _днев, _дълг, _asil_, руп_,
+ {{0x3ea6c51c,0xfd57846d,0xd6d9066f,0xb3578061}}, // ndot_, _alaṣ, ół_, _بیٹا_,
+ {{0x2d86867f,0xdb0181a8,0xc33381c6,0xda1e908a}}, // ntoe_, _urlá, _חוף_, _परित_,
+ {{0x3ea68198,0xd12f81e2,0xe57180be,0x98a78162}}, // hdot_, _іх_, ײַך_, _vină_,
+ {{0xd25b2748,0x92c712c0,0x66fc9911,0xac190eef}}, // ица_, ощад, jčko, сову_,
+ {{0xa3ab8f12,0x290ca876,0x7a390071,0xda1e8075}}, // खित_, loda_, спар_, _परात_,
+ {{0x497517d4,0x60c710d1,0x78a38085,0xdd0e811c}}, // [7350] ялас, _pojm, _yanv, xışl,
+ {{0x7e7d128d,0x37ab098d,0xa9bb01a2,0xd2500019}}, // lgsp, итен_, _ईपीए, ٹنے_,
+ {{0x2a600359,0x765b808e,0xf99f06c0,0x6d49da51}}, // baib_, _peuy, _pyès_, _stea,
+ {{0x41ca95fb,0x7e7d1277,0x59ca8054,0x539a02f6}}, // िहास, ngsp, िहार, _פירו,
+ {{0xdd9b804a,0x290c8816,0x64a10609,0x8c3c8380}}, // рше_, koda_, _eġiż, _sağa,
+ {{0xdd0e8085,0x03a5af2f,0x673c02a6,0x7a412294}}, // rışl, зико, _furj, _fátó,
+ {{0x60080a14,0x290c805d,0x673c6b80,0x54580039}}, // _днём_, doda_, _gurj, חסון_,
+ {{0xc7b2004c,0x78a3eb81,0x93fc0039,0x6d4980b9}}, // _אבל_, _sanv, ילוי, _ttea,
+ {{0x394d80e7,0x7bd88754,0xf4878a4c,0xd49b81ae}}, // upes_, _čauš, _мужн, _вре_,
+ {{0x395902d5,0xdea38019,0xedb48290,0xae2080c2}}, // _mwss_, _ریڈی, _تحفظ, _बरतन_,
+ {{0x9f450009,0xb5fda7aa,0x78a3a419,0x26c900fc}}, // _älä_, _odšk, _vanv, _boao_,
+ {{0xfaff0b18,0xa3e485e8,0x394de5e9,0x61ed87dd}}, // _një_, _पेट_, ppes_, _žalu,
+ {{0x290c83c3,0xbcfb016a,0xbddb1b01,0xf1a7b6ab}}, // boda_, trép, scèn, _френ,
+ {{0x6cd60013,0x7afbeb82,0x386101ec,0x290cd100}}, // أقسا, lnut, jahr_, coda_,
+ {{0xd141866f,0xf65f0646,0x00000000,0x00000000}}, // snąć_, rkær_, --, --,
+ {{0x39461570,0xb1461263,0x32d1826b,0x7afbe92c}}, // знаг, знал, _báyi_, nnut,
+ {{0x3ea4806a,0x25a011ee,0x386101ec,0x867b01c6}}, // [7360] _ramt_, _usil_, fahr_, _הרפו,
+ {{0xe9d72344,0x3ea493c2,0x7afb9dc1,0x673c07b8}}, // чку_, _samt_, hnut, _purj,
+ {{0x7afb98a6,0x645e00eb,0x98aa0162,0x443a6b83}}, // knut, _iepi, _aibă_, _igp_,
+ {{0x290c9351,0xfaff00f1,0x59db82f1,0x645e02d0}}, // zoda_, _gjë_, यनार, _hepi,
+ {{0x7afb803e,0xa3e4a076,0x3ea6d64f,0x1ea980f7}}, // dnut, _पेज_, rdot_, تالي_,
+ {{0x645e06a4,0x2d868cfa,0xeaf68aed,0x5fcb92c6}}, // _jepi, rtoe_, ुक्त_, ाहाल,
+ {{0x290c803b,0x2d868a53,0xbc6781a8,0x443a226d}}, // voda_, stoe_, سمين_, _mgp_,
+ {{0x645e30e6,0x7afb803a,0x290ceb84,0xbea392e9}}, // _lepi, gnut, woda_, _паск,
+ {{0x61e46b85,0x2ca5813c,0x7d0e2a4f,0xfe208074}}, // nzil, _fald_, kobs, _बरिस_,
+ {{0x81ae0a49,0x645e3761,0xf98f80a0,0x2ed500c2}}, // _কথা_, _nepi, ابي_, दस्त,
+ {{0xb4d88fe4,0x3f638009,0xa3e48beb,0x7c3a8122}}, // ासी_, нтяб, _पेच_, _igtr,
+ {{0xd6db067c,0x290ceb86,0x7e62804f,0x7afb8140}}, // сто_, soda_, naop, cnut,
+ {{0x290ca17b,0x628281ed,0x7e7d18a1,0x690f8366}}, // poda_, _icoo, rgsp, røer,
+ {{0x293780be,0x248d0668,0x645e6b87,0xdcef0087}}, // _זאגן_, đemo_, _cepi, ducă,
+ {{0x645e2823,0x9b580087,0x7e629ae3,0x6aa6555c}}, // _depi, зист_, kaop, _bakf,
+ {{0x38178bea,0x60cab19c,0x68ee0722,0x443a4f3a}}, // _מקום_, _hofm, _ambd, _egp_,
+ {{0xa2ce064a,0x7e6d1312,0x3ced809a,0x69c08122}}, // [7370] _सवर्, _idap, _अगले_, nxme,
+ {{0x6d4d38dc,0x6f0f047f,0x7d0e04dc,0x645e1e5b}}, // _itaa, locc, cobs, _gepi,
+ {{0x6aa6008e,0xdb08816a,0xee4a819d,0xf79901f9}}, // _fakf, _ardí, _ịdịn, تناب_,
+ {{0x6f0f047f,0xfaff00f1,0xf40300ab,0x7e62802a}}, // nocc, _ujë_, _এরপর_, gaop,
+ {{0xdca5a28e,0x64a589f7,0x64d1016f,0xa2c08f1b}}, // дали, дала, _सकाळ, वान्,
+ {{0x6d4d004f,0xe8d90133,0x71d60e82,0x777a9fa4}}, // _mtaa, ftụ_, _חושד_, kutx,
+ {{0x7afb9ee0,0xb0b080d4,0x78698061,0x2ca597ab}}, // tnut, जयनग, _jövő, _vald_,
+ {{0xe9d98607,0x612200e7,0x7e6d6b88,0x2ca5ce3b}}, // жки_, rôle, _ndap, _wald_,
+ {{0xc69283c8,0xc8c880d4,0x6e3b8420,0x3f418580}}, // _באן_, ाउंट, _igub, _sóu_,
+ {{0x672400f1,0x6f19831d,0xbddb0866,0x65698db1}}, // _shij, llwc, rcèl, _iveh,
+ {{0x645e6b89,0x6d4d3d8b,0x7afb803e,0x61e40c9f}}, // _repi, _ataa, pnut, zzil,
+ {{0x645e003d,0x443a0ae6,0x3f678279,0x0378804e}}, // _sepi, _sgp_, rđu_, _رحمت_,
+ {{0x186998a0,0xa0698f27,0xb33c8197,0x6e3b912e}}, // _дали_, _дала_, _isħa, _mgub,
+ {{0x78a70b48,0xee3701e2,0x79898a53,0x7e6d09c4}}, // _najv, ьня_, mtew, _edap,
+ {{0xa3e80076,0xdc4581e2,0x7d0e37e4,0x6f0f01e8}}, // _मेख_, jūči, robs, bocc,
+ {{0x7b3c05f5,0x66046b8a,0x2a7fa676,0xdce40390}}, // _očuv, _txik, ngub_, _ivič,
+ {{0x8c3c8a0b,0x7989eb8b,0x6aa61238,0x2bba0c9a}}, // [7380] _bağl, ntew, _vakf, _उपका,
+ {{0xddc40087,0x7649dd3f,0xe6c98bcf,0x6aa66b8c}}, // _ediţ, _afey, _čašć, _wakf,
+ {{0x8c3c87c0,0x28c40540,0x130a0d15,0x6aa612cf}}, // _dağl, लॉगि, _мной_, _takf,
+ {{0xe61a8895,0xf42a0009,0x61e41d5e,0x7e6d00ee}}, // оде_, _enää_, pzil, _xdap,
+ {{0xdd908bca,0x6aa98080,0x83fc8699,0xfce6c5a5}}, // اوت_, ddef, _međe, модо,
+ {{0xb4d88076,0x6e3bb6d0,0xa533804a,0x7e6287b6}}, // ासे_, _egub, вніч, saop,
+ {{0xf76789d7,0x7791015b,0x7e6282c4,0xbb4795a9}}, // _جا_, _میلا, paop, ولون_,
+ {{0xf96a8198,0x291a0144,0xff24cf77,0x416a82ee}}, // орой_, llpa_, _سبزی, ојом_,
+ {{0xcb130039,0x6d5b819d,0x26c682f7,0x8c3c807e}}, // כלה_, _gwua, mjoo_, _yağl,
+ {{0x6e3b9ce9,0x41bb00be,0xba3b07f1,0x7b1303ed}}, // _zgub, קציע, _raïm, nçur,
+ {{0x6d4d33fc,0x7e60ac17,0xdce4026f,0xd838a368}}, // _staa, _kemp, _cvič, ječe_,
+ {{0x6d40b71b,0x7e608455,0xb8d586ae,0x25a4ba92}}, // _kuma, _jemp, _चो_, _ksml_,
+ {{0x7e60845c,0x6d40bf6c,0x94190085,0xbcfb0866}}, // _memp, _juma, əyən_, ssém,
+ {{0x6d40b6ed,0x7e60a63b,0x6f0f047f,0x777aa5d5}}, // _muma, _lemp, socc, putx,
+ {{0x3d16823c,0x6d40ab7c,0xbbc9000d,0x78a708ae}}, // _पीछे_, _luma, रहेक, _rajv,
+ {{0x8c3c8afe,0x7e60e064,0x6d40eb8d,0x3e880e06}}, // _sağl, _nemp, _ouma, rító_,
+ {{0x6d40eb8e,0x26d901e2,0xeabf00ff,0xb5fd8dcc}}, // [7390] _numa, ūros_, _chùa_, _keše,
+ {{0x657d09ae,0x3ea9074c,0x7e60aeaa,0xf0fb20e9}}, // mush, _haat_, _aemp, ्वेद_,
+ {{0x657d5bee,0x7e60eb8f,0x6d406950,0x8c4403a7}}, // lush, _bemp, ímav, _шеќе,
+ {{0x6d40e6ec,0x7e60cfb4,0xa3e80beb,0xb5fd8450}}, // _buma, _cemp, _मेट_, _leše,
+ {{0x6d40eb90,0x5067028b,0x3ea9038e,0x657d10fa}}, // _cuma, _отва, _maat_, nush,
+ {{0x20182320,0x3ea9240d,0x6d40eb91,0xa3ab873c}}, // _fyri_, _laat_, _duma, खिल_,
+ {{0xeb91826a,0x657d364e,0xd6e00035,0x6d40eb92}}, // اظت_, hush, ółki_, _euma,
+ {{0x657d005d,0x7e60859c,0x32190009,0x3ea93da9}}, // kush, _gemp, _kysy_, _naat_,
+ {{0x6d40bbd5,0x7afa0125,0x6aa9848d,0x7d049b88}}, // _guma, étti, rdef, éisc,
+ {{0x7e60eb93,0x657d471c,0x47c58009,0x629b8114}}, // _zemp, dush, ебов, seuo,
+ {{0x32558aca,0x6d40eb94,0x79898a15,0x81e780ab}}, // _звер, _zuma, stew, _মুখ_,
+ {{0x6d40cfbf,0x2249026c,0x657d390d,0xe0bf8264}}, // _yuma, jcak_, fush, ইসেন,
+ {{0x6d408079,0x657d150f,0x58d584fa,0x5d670198}}, // _xuma, gush, _подт, ниям_,
+ {{0x332deb95,0x26d903ed,0x09f681c6,0x98a2bcb3}}, // lmex_, ëror_, _אמנם_, _бише,
+ {{0x69cb1d31,0xd838cb1f,0x232a0be2,0x141a01a8}}, // ägen, teče_, зови_, سيرة_,
+ {{0x3ea90613,0x657d6b96,0x8c3c8214,0x2fd8928d}}, // _gaat_, bush, _rağm, ørge_,
+ {{0xc3329a0f,0x7e60bab9,0x8c460dc0,0xa2c08b84}}, // [73a0] רון_, _remp, _пепе, वात्,
+ {{0x7e60eb97,0x6d4094ff,0x7e666b98,0x7d5703de}}, // _semp, _ruma, lakp, _רייד_,
+ {{0x6d40eb99,0x7e60903b,0x7c3e0122,0x7d1c00b9}}, // _suma, _pemp, _igpr, mlrs,
+ {{0x040b0104,0x6d40dd84,0x8c1580ab,0x28c4064a}}, // _lượn, _puma, াধান_, लॉजि,
+ {{0x683408ae,0x6d40bdd5,0x7ae4311a,0x13d50264}}, // dždž, _quma, mhit, _হইয়,
+ {{0xdb0501ec,0x66df0168,0x5ba68425,0x49180327}}, // _erhä, _mëka, ериз, _बीचो_,
+ {{0xb5fd979d,0x910310ca,0x969680a9,0x2f190019}}, // _reše, _спре, _праш, ségi_,
+ {{0x6d40ad80,0x6b83016d,0x657d0314,0xada6a21f}}, // _tuma, _ånge, yush, _пабл,
+ {{0x6d40c000,0x7e660359,0xdee3be3d,0x66e3814c}}, // _uuma, dakp, _соти, _сота,
+ {{0x3ea96b9a,0xc05785a8,0x63b7823e,0xbcfb0061}}, // _saat_, нію_, çanè, rsék,
+ {{0x443eeb9b,0x273a8084,0x60ce0122,0x7ae413fa}}, // _igt_, _būna_, _mobm, khit,
+ {{0x753a811e,0x09e68003,0x657d365d,0x6e2405ee}}, // _hitz, _поен, tush, _bzib,
+ {{0xe9738077,0x1af780be,0x7ae46b9c,0xc2e9866e}}, // _مهند, _אמאל_, dhit, _تعلم_,
+ {{0x657d2e3f,0x3ea92525,0x6e24026c,0x2edd8f1b}}, // rush, _waat_, _dzib, _मत्त,
+ {{0x3ea9178f,0x25bf002e,0x6e240316,0xad1b8039}}, // _taat_, ţul_, _ezib, פולר,
+ {{0x41e71ae5,0x3da70112,0x753a8102,0x657d4f80}}, // ніза, _зроб, _litz, push,
+ {{0x22490573,0x5a351354,0x6e240af8,0xfc300061}}, // [73b0] rcak_, тнат, _gzib, لحہ_,
+ {{0x2bba000c,0x394f0029,0xf8070f16,0x6f1d2e76}}, // _उपचा, īgs_, нчен, llsc,
+ {{0xc10401a8,0x7ae401f6,0x6e2405ee,0x2ae7826b}}, // دولي, bhit, _zzib, _lákọ_,
+ {{0x443e867f,0x753a8102,0x7ae43f47,0xb1841e1e}}, // _agt_, _aitz, chit, šťas,
+ {{0x443e8072,0x6da59775,0xada58150,0x753a8c45}}, // _bgt_, тила, талл, _bitz,
+ {{0xd378003a,0xeb999d32,0x443ecb23,0x3ea9b6f0}}, // šće_, дик_, _cgt_, žat_,
+ {{0xa3c30074,0xc1c9801b,0x753a8102,0x443e816a}}, // ्हि_, ाङ्ग, _ditz, _dgt_,
+ {{0x3942008e,0x443e8bfd,0x9f348163,0x753a8f3e}}, // _yuks_, _egt_, _бесі, _eitz,
+ {{0xceb2878d,0x798d2949,0x443e802a,0x2360817f}}, // _דין_, ntaw, _fgt_, ćija_,
+ {{0xa3c30576,0x24858144,0x2ae7826b,0x76598114}}, // ्हा_, _uclm_, _dákọ_, lbwy,
+ {{0x661bc7d0,0x672981c0,0xe80b903e,0x29010187}}, // _hyuk, _khej, _सुखा_, inha_,
+ {{0x92eb92dc,0x753a8102,0x798d177c,0x6134011c}}, // _عراق_, _zitz, ktaw, rülə,
+ {{0x040b0028,0x6aad3efd,0xfdc000ab,0x09d600ab}}, // _tượn, ddaf, উন্ড, _সেনা,
+ {{0xe9d88112,0x6aaba51d,0x661b8deb,0x764d112e}}, // _які_, _hagf, _myuk, _efay,
+ {{0x2d9e026f,0x7ae40199,0x398b8aa2,0x5f060081}}, // ňte_, thit, løs_, _изка,
+ {{0x06f60077,0x69c40102,0x6f0402f7,0xe297309d}}, // _جستج, txie, _ujic, кас_,
+ {{0x61e9a48d,0x261904e5,0x7ae40744,0xf2d280be}}, // [73c0] nzel, _युपी_, rhit, בען_,
+ {{0x7d7b0bea,0x7ae43a2f,0xbebb0168,0x9ffa01ad}}, // _קניו, shit, smët, وراء_,
+ {{0x7ae45589,0xcc770039,0x7f43823e,0xdd1c80e1}}, // phit, _בעיה_, _junq, _nášh,
+ {{0x753a82af,0x798d3f73,0x6d440c53,0xcf9283de}}, // _sitz, btaw, _kuia, שטא_,
+ {{0xa2c08bb8,0x673b8458,0x753aeb9d,0x61e981ed}}, // वास्, _diuj, _pitz, jzel,
+ {{0x7e646004,0xd90d0065,0x2b5e8748,0x62840037}}, // _leip, _ٹیم_, _pwtc_, lgio,
+ {{0x6609eb9e,0x443e80ff,0x61e98bfd,0xb5fd8824}}, // _exek, _vgt_, ezel, _pešc,
+ {{0xbcfb6b9f,0x78aa8267,0xbebb0168,0x753a8192}}, // rséi, _safv, hmër, _witz,
+ {{0x7f438c15,0x6da38002,0xbd8a8077,0x6aabc6be}}, // _aunq, _кита, _زنان_, _dagf,
+ {{0x753a8a0f,0x6729baa8,0x443ebe06,0xbebb0168}}, // _uitz, _zhej, _ugt_, jmër,
+ {{0x04439980,0x47350d5f,0x7f4381df,0x03a3835f}}, // _техн, лнос, _cunq, _вихо,
+ {{0x2cac9a49,0x7f438081,0x73369e91,0x7e6408f1}}, // _hadd_, _dunq, _شرائ, _ceip,
+ {{0x2918056c,0xe8948e02,0xd90e8065,0x6d44041c}}, // _okra_, _баль, _ویب_, _cuia,
+ {{0x6fb487bd,0x40a8853d,0x291800ee,0x79808e8c}}, // _ممتا, _سختی_, _nkra_, mumw,
+ {{0xa3abbb04,0xb5fd895e,0x629f0085,0xac181bb1}}, // खिए_, _meša, teqo, вору_,
+ {{0x62840698,0x291ebffd,0x3863006a,0x798d607c}}, // ggio, llta_, _sejr_, ttaw,
+ {{0x6f028023,0x6aad09ca,0x533453bf,0x291ee4e3}}, // [73d0] dnoc, rdaf, ретт, olta_,
+ {{0x798d6ba0,0x273b0110,0x661b89c4,0x7ae2e7f1}}, // rtaw, _kūno_, _ryuk, _llot,
+ {{0x798d6ba1,0x661b859c,0x0675a15c,0x61e9eba2}}, // staw, _syuk, гуля, zzel,
+ {{0xf7700c2a,0x8f148221,0xcf9380be,0x201c83ac}}, // زام_, афіч, יטע_, _myvi_,
+ {{0x442583ec,0xa4d580e8,0x83fc8088,0x356b8087}}, // _szl_, лоді, _ređa, френ_,
+ {{0xd0068013,0x057480f7,0xa2db001b,0x6aabeba3}}, // _هل_, شاهد, नसक्, _sagf,
+ {{0xeb99a9c9,0xb5fd8efd,0x7ae2b03e,0xf4848a67}}, // хий_, _deša, _blot, ручн,
+ {{0x61e98a11,0xdd1c81ac,0xd1858a0e,0x6f02c3ca}}, // tzel, _vášh, глий, cnoc,
+ {{0x61fb050b,0xa91d81d0,0x3f814584,0xd83882d4}}, // _žulj, huže, luhu_, veča_,
+ {{0x7e644a30,0x61e9e86e,0xdce98824,0xbc5695d8}}, // _seip, rzel, _kveč, čšíc,
+ {{0x7ae2eba4,0x25a90587,0x6441eba5,0xa91d817f}}, // _flot, _asal_, _igli, juže,
+ {{0xa91d803b,0x291ea130,0xa68304fa,0x61e98061}}, // duže, alta_, блюд, pzel,
+ {{0xa2c09a46,0x3f811a7b,0xd12e803d,0xd838890c}}, // वार्, huhu_, تمی_, reča_,
+ {{0xd83882d4,0x717280d7,0x7cf4011c,0xdce9b732}}, // seča_, _اهوا, _kürə, _oveč,
+ {{0x3bd50009,0x75ea8019,0xe29a06e6,0x7e645c28}}, // аютс, _közé, нав_, _teip,
+ {{0x425505fa,0xe7f9073c,0xef1f0085,0x3f8101b4}}, // итут, ्पना_, clü_, duhu_,
+ {{0x612b0019,0xf2d280be,0xb2260b79,0x316dd2c5}}, // [73e0] küld, זען_, умал, nsez_,
+ {{0x6441803d,0x3ea24ddb,0x26d26ba6,0x63b70144}}, // _ngli, lekt_, _hoyo_, _frxn,
+ {{0x2a692c54,0xb5fd9432,0x645c079a,0x26d26ba7}}, // baab_, _reša, mbri, _koyo_,
+ {{0xa3c31d01,0x645c6ba8,0x6441a117,0x26d21cfe}}, // ्हर_, lbri, _agli, _joyo_,
+ {{0xb5fdac08,0x26d20010,0x3ead8358,0x2d826ba9}}, // _peša, _moyo_, _daet_, nuke_,
+ {{0x50432133,0x3f8116f2,0x291e0019,0xd9b2064a}}, // _герб, buhu_, _óta_, ीमीट,
+ {{0xb5fd9c78,0x2d8f8722,0x645c01a8,0x7ae2b3fe}}, // _veša, atge_, ibri, _plot,
+ {{0x7980823d,0x5fbb0039,0xe73795d1,0x6441ebaa}}, // tumw, _אצלנ, _бес_, _egli,
+ {{0xb5fd8858,0xdce99b39,0xa91d9f3a,0x3ea2412d}}, // _teša, _zveč, dužb, dekt_,
+ {{0x26c002f7,0xc7af0065,0x2d826064,0x78a1ad00}}, // _anio_, _بڑے_, duke_, gelv,
+ {{0xba3b21bf,0x645c6bab,0x26d20f33,0xc91701c6}}, // _haït, dbri, _boyo_, רחית_,
+ {{0x7c2582af,0x7ae2ebac,0xc9868087,0x5186a2f0}}, // ühre, _ulot, _буки, _бука,
+ {{0xef1f0214,0x612b0285,0x78a1ebad,0xb5fd8968}}, // rlü_, lüle, belv, _lešn,
+ {{0xa3c30076,0xa91d8067,0x1fa68c9d,0x26c06bae}}, // ्हल_, tuže, _бриг, _enio_,
+ {{0xe8140576,0x612b1238,0x241914ef,0xac191515}}, // _तुला_, nüle, товы_, тову_,
+ {{0x32551444,0xddc0801b,0x4a550ca0,0x2d822bd7}}, // рвар, _nemů, ркас, buke_,
+ {{0x645c235a,0xdce9c24d,0x39468110,0x50f52386}}, // [73f0] bbri, _sveč, _juos_, азат,
+ {{0xd37805f5,0x270d80d4,0x656f4964,0x0f0d83b6}}, // šća_, िक्र_, msch, िक्स_,
+ {{0x92952749,0x656f0e83,0x33200580,0x351c01c6}}, // _канц, lsch, blix_, _אוזנ,
+ {{0x8aa7181d,0x7d1a8074,0x35b5824f,0x3f816baf}}, // _срод, _akts, ибер, ruhu_,
+ {{0x656f6bb0,0x7c289087,0x298b0d70,0x7cf40085}}, // nsch, _azdr, _ясно_, _sürə,
+ {{0x2ee201cb,0x88bc801b,0x656f583a,0x61ed01f6}}, // _पत्त, změr, isch, mzal,
+ {{0xdce9b4c1,0x656f01ec,0x25fc06a7,0xa2dc0816}}, // _uveč, hsch, _लेनी_, _नवम्,
+ {{0x645c0669,0x656f5ec1,0xc245c168,0x61ed2aa0}}, // zbri, ksch, шник, ozal,
+ {{0x9c879168,0x61ed43b5,0xa48780f7,0x6aaf0229}}, // _مشاه, nzal, _مجان, _macf,
+ {{0xe0d9872f,0x656f3d2a,0xf77180f7,0x644181e0}}, // тво_, dsch, ياج_, _ugli,
+ {{0xa91d8067,0x316d82be,0x672d0e35,0x26d200ee}}, // tužb, ssez_, _bhaj, _poyo_,
+ {{0x672d4e70,0x645c151e,0x7e6b96fb,0x2d820074}}, // _chaj, wbri, nagp, tuke_,
+ {{0x6b83a2bb,0x3ea212d2,0x7ae98448,0xb5fd8bda}}, // lung, rekt_, mhet, _mešo,
+ {{0x2d820d11,0x7ae9a660,0xed57002e,0x28c90a0d}}, // ruke_, lhet, рор_, राफि,
+ {{0x6b83979b,0x660d0004,0x2d823553,0x7f470282}}, // nung, _exak, suke_, _nujq,
+ {{0x7ae98711,0x2d8269e5,0x645c05f8,0x656f01ec}}, // nhet, puke_, sbri, bsch,
+
+ {{0x6b838a5a,0xe9d70767,0x786610ca,0xfd4e81bc}}, // [7400] hung, акт_, иказ, _dakọ,
+ {{0x6b839281,0x205622f0,0x6d562c01,0x7f470168}}, // kung, ртар, _itya, _bujq,
+ {{0x44440747,0x6299ebb1,0x6b8381e2,0x7ae9805d}}, // _ig_, _obwo, jung, khet,
+ {{0x6b8389ed,0x5a960003,0xfd4e819d,0x656400b4}}, // dung, _троф, _gakọ, _kwih,
+ {{0x6f09830b,0x7ae9ebb2,0x7ceb02af,0x69c98282}}, // _djec, dhet, _dürf, jxee,
+ {{0x6b838a8c,0x6f0985a4,0x6e29826c,0xb5fd8754}}, // fung, _ejec, _dzeb, _tešn,
+ {{0x6b83ebb3,0x444445e3,0x39468176,0x7afb8081}}, // gung, _mg_, _suos_, fiut,
+ {{0x7ae9904d,0x612b60e3,0xa31088fd,0x7c288353}}, // ghet, rüle, ावेज_, _vzdr,
+ {{0x444415f8,0xe1ff02ba,0xc05a8a4c,0x612b45ea}}, // _og_, _león_, кін_, süle,
+ {{0x44446bb4,0x6b838859,0x0cc100c8,0x8c4398ba}}, // _ng_, bung, _উত্ত, _дефе,
+ {{0x7d18816b,0x6b8380b4,0x61ed499f,0x7afb9c28}}, // movs, cung, zzal, biut,
+ {{0x44440a1c,0x656f6039,0x7ae99eed,0x7afb9f50}}, // _ag_, tsch, chet, ciut,
+ {{0x44442dbe,0xd9549125,0x2369812b,0xca9784de}}, // _bg_, _انتخ, ćaje_, _כדאי_,
+ {{0x656f1736,0x44443915,0xdb0d8009,0x3990823e}}, // rsch, _cg_, äköi, màs_,
+ {{0x656f2644,0x44446bb5,0x7f848013,0x67228084}}, // ssch, _dg_, _العن, kloj,
+ {{0x4444240f,0xd2580604,0x083b8051,0x61ed0102}}, // _eg_, ицу_, _בעול, tzal,
+ {{0x6b83a479,0xa91debb6,0x90991cc7,0x6f0985f3}}, // [7410] zung, luža, лват_, _rjec,
+ {{0x261a8740,0x6f09920e,0x6b838b5e,0x7ae98019}}, // _बड़ी_, _sjec, yung, zhet,
+ {{0xba3b6bb7,0x2ba6836d,0x7e6b809c,0x64456bb8}}, // _païs, _कैला, tagp, _ighi,
+ {{0x44444573,0x7ceb01ec,0x7ae98168,0x6b83b08f}}, // _zg_, _bürg, xhet, vung,
+ {{0x4444045c,0x6b83a290,0x6f09ebb9,0xb5fd8b67}}, // _yg_, wung, _vjec, _vešo,
+ {{0x588401e5,0xb4e18035,0x2fc70106,0x7d18ad6d}}, // _мыса, दसे_, ängs_, govs,
+ {{0x7ae9ebba,0xe299ebbb,0x7e6b9049,0xbcfb00e7}}, // thet, гай_, pagp, nséq,
+ {{0x9f058591,0x6f1b8234,0xa91d825b,0x69c9811b}}, // _اورو, _ukuc, duža, txee,
+ {{0x60d517aa,0x7ae9b43f,0x7afbebbc,0xdce9816b}}, // _kozm, rhet, riut, _uveď,
+ {{0x6b83cef1,0x64450104,0x1c1ba30f,0xdce400c3}}, // pung, _nghi, _पुथल_, _ivić,
+ {{0x7ae98234,0x69c69a50,0x6d41820d,0x7d07026c}}, // phet, äker, _iila, dnjs,
+ {{0xed5a3aa3,0x6d41d9c8,0x6717a0f2,0x629982a0}}, // лом_, _hila, तविक_, _ubwo,
+ {{0x6d41a34d,0x6d4b03d3,0x225f8458,0x249a00b9}}, // _kila, _égal, mbuk_, _pbpm_,
+ {{0x3a368039,0x7bdc1ba1,0xe1ff2509,0xdff90035}}, // _פרסם_, tyru, _peón_, ्पाद_,
+ {{0x6d419b25,0xd3b50009,0xcb031862,0xf99f2b5f}}, // _mila, ость, रचंड_, _apè_,
+ {{0x612f8bc5,0x44446bbd,0x6d41ebbe,0x9e660371}}, // følg, _wg_, _lila, _увид,
+ {{0x44445171,0x3f858365,0xa3a80b9f,0x6d418cdb}}, // [7420] _tg_, lulu_, _खैर_, _oila,
+ {{0x44446bbf,0x6d41ebc0,0xb4e68105,0xac8619a5}}, // _ug_, _nila, _पते_, _угол,
+ {{0x6b9e23fe,0x7e69890c,0x7d188140,0xe3b887c0}}, // _uppg, _beep, vovs, _ayın_,
+ {{0x6d41ebc1,0x612f9277,0xb5fd8196,0x2bbd864a}}, // _aila, bølg, _tešl, ्मना,
+ {{0x6d419dde,0x7e69bbb0,0x3f85bed0,0x7ce6807b}}, // _bila, _deep, hulu_, _aðra,
+ {{0x3f8594ee,0x6d41820f,0x28c90701,0x0ee203b7}}, // kulu_, _cila, राणि, _पतझड,
+ {{0x6d4187ba,0x7d18ebc2,0xcec381d0,0x25ada671}}, // _dila, rovs, áří_, _esel_,
+ {{0x7d18a0a7,0xa3b9073c,0xcfd080c8,0x68fe033e}}, // sovs, चित_, ানমন, lipd,
+ {{0x6d419fcc,0xddcd06ec,0x26c48144,0x7d189f27}}, // _fila, _odaš, _inmo_, povs,
+ {{0x6d41ea1a,0x3990809f,0x291a6b81,0x3f859a14}}, // _gila, ràs_, nopa_, fulu_,
+ {{0xf98f89d7,0x3f85ebc3,0x8f35143d,0x61301614}}, // وبی_, gulu_, женц, mäld,
+ {{0xa91d8052,0x6d41ebc4,0xa25b0187,0x3990823e}}, // ruža, _zila, _anôn, pàs_,
+ {{0x6f0388f1,0x317febc5,0x44210748,0x2fc700ff}}, // énci, cruz_, _syh_, ̣ng_,
+ {{0x44270104,0x6448173d,0xa2d492c6,0x6d418438}}, // _ơn_, ždin, यॉर्, _xila,
+ {{0x2bbd8a16,0x60d509a4,0xd82f8087,0x56952f92}}, // ्मया, _rozm, _хэ_, _дайт,
+ {{0x925900a9,0x7ceb007e,0x6ee60177,0x00000000}}, // раат_, _sürd, _tóba, --,
+ {{0x60f90110,0x3ea6c36d,0x60d56bc6,0x7e6994cf}}, // [7430] аная_, deot_, _pozm, _reep,
+ {{0x28c901c4,0x291a4302,0x7e69ebc7,0xfc4701d0}}, // राधि, gopa_, _seep, číst_,
+ {{0x44276bc8,0x6d41ebc9,0x7ae30087,0xf77011cc}}, // _án_, _rila, _înto, فان_,
+ {{0x6d419600,0x7ceb6bca,0xdb1f0198,0x60c48326}}, // _sila, _würd, äväk, _ɗima,
+ {{0xb5fd81e2,0x3f85ab16,0x7aed451c,0xa91d812b}}, // _iešk, zulu_, mhat, dužn,
+ {{0x7aed58c9,0x6d41bdd5,0x23c492c7,0x87e71374}}, // lhat, _qila, वमंद, _любе,
+ {{0x6d41894d,0x2ee95c81,0x6f0d017f,0xaac583eb}}, // _vila, _olaf_, _ojac, वालक,
+ {{0x6d418c9e,0xa2cc016f,0x3b090197,0x25ad808e}}, // _wila, हाय्, nnaq_, _tsel_,
+ {{0x6d41ebcb,0xed598067,0x225f8859,0x2a7900b9}}, // _tila, _brže_, rbuk_, _mdsb_,
+ {{0x10a59a34,0x3eb20085,0x2ee900ee,0x225f9b6b}}, // цион, _sayt_, _alaf_, sbuk_,
+ {{0x7aed029b,0x1eca0abe,0xa6ca0abe,0x25a0065d}}, // khat, ални_, ална_, _spil_,
+ {{0x7d1e06ab,0xa91d8945,0x2ca7da4b,0xf77200be}}, // _skps, drže, lend_, וקט_,
+ {{0x94040085,0x7aed081b,0x6f0d0b67,0x3f85eb6c}}, // əmə_, dhat, _djac, sulu_,
+ {{0x628f114e,0x3f85ebcc,0x6e2d0253,0x64919f3a}}, // _acco, pulu_, _dzab, _užič,
+ {{0x0443079e,0x8c431c79,0xb5fdbdbf,0x6e2d6bcd}}, // дерн, дере, _bešk, _ezab,
+ {{0x7aed2123,0xb4e686b7,0x2ca7ebce,0x0d860009}}, // ghat, _पत्_, hend_, _длин,
+ {{0xe8fa141e,0x7ceb07d9,0x25a006cb,0x2ca7d00d}}, // [7440] ала_, _süre, _upil_, kend_,
+ {{0x6ab683eb,0xddc08353,0x628f047f,0x394b107c}}, // _अफ्र, _nemš, _ecco, _ducs_,
+ {{0x7d1c68c1,0x232a035f,0xab2a035f,0x32461f96}}, // mors, _вони_, _вона_, _менг,
+ {{0xe5c68198,0x6da6a481,0x68fe00c3,0x7d1c0e06}}, // жско, жида, sipd, lors,
+ {{0xc27b025f,0x2b5a6bcf,0x82361ab3,0x2ca7b2c7}}, // _דרכי, _htpc_, _گرگا, fend_,
+ {{0xd37a85f1,0x7d1c035f,0x7ae40f52,0x2ca7a7d0}}, // ачи_, nors, lkit, gend_,
+ {{0xaac585b3,0x613000f2,0xa91d8052,0x248087d9}}, // वांक, räld, tužn, ğim_,
+ {{0x7ae40e11,0x248087d9,0x6e2420c4,0x6ab61670}}, // nkit, şim_, _oyib, ndyf,
+ {{0xc3330bea,0x23698067,0x83fcc579,0x3994016d}}, // ווה_, ćaja_, _međi, näs_,
+ {{0x7aed0019,0x83fc8668,0x7d1c127a,0x20070035}}, // zhat, _leđi, jors, źnie_,
+ {{0x6e2d0065,0x64488122,0x224680ee,0x7ae40198}}, // _szab, _igdi, _tgok_, kkit,
+ {{0x7996009a,0xd9469fab,0x224682c4,0x6e2400b4}}, // ktyw, _деди, _ugok_, _byib,
+ {{0x7d1c02fe,0xd62701a8,0x6e240a03,0x6a9b81c6}}, // fors, تعدي, _cyib, _כשאנ,
+ {{0x7d1c025b,0x99fb8039,0xe3b30060,0x395982df}}, // gors, _דפדפ, ورز_, ísse_,
+ {{0x7aed6bd0,0x3edf082e,0xb5fd8da8,0x63be01f4}}, // that, _abụọ_, _pešk, _crpn,
+ {{0xd838a368,0x25b00364,0xb606920e,0x7ae413d2}}, // ječi_, ällä_, _kršć, gkit,
+ {{0x7aed6bd1,0x6f1d5c1f,0xb5fd801b,0x6e2d00b4}}, // [7450] rhat, mosc, _vešk, _uzab,
+ {{0x7aed33ec,0xa2a181fe,0x7d1c0081,0x1601064a}}, // shat, _कॉन्, cors, _लेयर_,
+ {{0xb5fd803a,0x7aed6bd2,0x2ca7ebd3,0x7e6d00dd}}, // _tešk, phat, vend_, _keap,
+ {{0x6f1d0819,0x6d456bd4,0x2ca78cfa,0x1dc69344}}, // nosc, _hiha, wend_, लमंत,
+ {{0x7cf000f2,0x63a1826f,0x7ceb0085,0x7cef8edd}}, // _därf, _spln, _gürc, _jørg,
+ {{0x6d455d23,0xf1bf2336,0x7e6d2305,0xb5fd99ce}}, // _jiha, _irá_, _leap, _ješi,
+ {{0x6d4501ae,0xb5fda648,0x2ca787b3,0x0442ebd5}}, // _miha, _meši, rend_, нешн,
+ {{0x6d452e35,0x7e6d6bd6,0x29016bd7,0x2ca78c4d}}, // _liha, _neap, liha_, send_,
+ {{0xceb2be22,0x7d1c07d9,0xe8f99eef,0x44458019}}, // _אין_, yors, йли_, _فراہ,
+ {{0xa3cb000d,0xb5fd865c,0xb4ea016f,0x6d456bd8}}, // लमा_, _neši, _मते_, _niha,
+ {{0x7ae401e2,0x7afd000b,0x7d1c1a69,0x63a1816b}}, // ykit, _omst, vors, _upln,
+ {{0x7e6d0ae3,0x6f1d59ed,0x7d1c4a15,0x25bf89ff}}, // _ceap, gosc, wors, _krul_,
+ {{0x7cf00006,0x6d456bd9,0x63be574c,0xb5fd8390}}, // _järg, _biha, _srpn, _beši,
+ {{0x78b5011f,0xe1f0830f,0x6d4503bf,0x6d4d00b9}}, // _nazv, _جسم_, _ciha, _duaa,
+ {{0x6d4507d5,0x7d1c0065,0xb5fd8110,0xe73a0ae7}}, // _diha, rors, _deši, _лек_,
+ {{0x7d1c34d7,0x9ad38135,0x934318f6,0x6f1d00e5}}, // sors, _dịgh, енсе, cosc,
+ {{0x6d450091,0x7ae43e66,0x6d5b8282,0x612b0061}}, // [7460] _fiha, rkit, _ntua, rüln,
+ {{0x7ae458ec,0x6d450959,0x7e7b8c30,0xf6170039}}, // skit, _giha, _adup, _מחפש_,
+ {{0x6d5b80a9,0xc2c60013,0x2d8f016d,0x09a88128}}, // _atua, _فيدي, _äger_, _कन्य,
+ {{0xf1bf07ca,0x61460fcb,0x7cef8bc5,0xe737813a}}, // _frá_, _нема, _lørd, пех_,
+ {{0xd8389024,0x29016bda,0xf1bf01a8,0xd1388084}}, // reči_, biha_, _grá_, ymą_,
+ {{0x4425800d,0x6d5b81a8,0xd83882d4,0x612b22f8}}, // _byl_, _dtua, seči_, nüll,
+ {{0xdc3600be,0x07a32eab,0xab38854c,0xb90c8032}}, // האַט_, _парн, опку_, _eemọ_,
+ {{0x7cf00106,0x7ceb0085,0x6d5b8168,0xd25081a8}}, // _färg, _müra, _ftua, خنة_,
+ {{0x25fc06ae,0xb60601f4,0x6f1d6bdb,0xf65f1277}}, // _लेली_, nošč, vosc, kjær_,
+ {{0x60f8835f,0x3ea00242,0x261582f1,0x6f02c300}}, // іння_, _abit_, _फुटी_, lioc,
+ {{0xb5fdebdc,0x6d452625,0xd1388d42,0xdceb8267}}, // _reši, _riha, rmą_, _šićk,
+ {{0x6d456bdd,0x6f0281dd,0x6d4d008e,0x7cf00106}}, // _siha, nioc, _puaa, _lärd,
+ {{0x6d45098c,0xb5fda46f,0x612b02af,0x290113c8}}, // _piha, _peši, füll, yiha_,
+ {{0x2ed9800c,0xd12f835f,0x317b898a,0x6f028037}}, // _भक्त, _їх_, _פרומ, hioc,
+ {{0x291e8503,0x50b780f7,0x6d4509fa,0x290c9235}}, // mota_, _ردود_, _viha, mnda_,
+ {{0x7c250a56,0x628d0b80,0xf1bf03cd,0x2d9f6bde}}, // _vyhr, ugao, _prá_, çues_,
+ {{0x7cef84d6,0xdb240061,0x69c00118,0xb5fdebdf}}, // [7470] _høre, írás, _ámel, _teši,
+ {{0x291eebe0,0x7cef813c,0x3cf8809a,0x3eab0039}}, // nota_, _køre, ्चों_, lect_,
+ {{0x6d5b998d,0x29012637,0x2d8b1b25,0x5eb00264}}, // _stua, riha_, luce_, টারে,
+ {{0x291eebe1,0xfbbd8768,0x3eb9013c,0x6f028081}}, // hota_, ्मसम, ndst_, gioc,
+ {{0x83fc8025,0xe81d000f,0x7cf0016d,0x3eab002e}}, // _među, _बुरा_, _färd, iect_,
+ {{0xa2d900d4,0x6aa9ca3e,0x69d682d0,0x03a5910e}}, // फॉर्, weef, _üyey, дико,
+ {{0xeb999fbc,0x291eebe2,0x3a268122,0x290c8326}}, // ций_, dota_, _ayop_, dnda_,
+ {{0xa91d8b5d,0x290c9b22,0x6130016d,0x3eab0e9e}}, // drža, enda_, mäla, ject_,
+ {{0x6aa9ebe3,0x6ed1064a,0x386e8c0b,0x7cf0039c}}, // reef, धानु, _befr_, _häre,
+ {{0xd378005c,0x26c92420,0xddc40110,0x2d8b079a}}, // šći_, _anao_, _neiš, duce_,
+ {{0x273580f2,0x7cf00006,0x16aa0d69,0x3eab44fa}}, // gång_, _järe, овни_, fect_,
+ {{0x7cef8aa2,0xd838b5b2,0x98a280eb,0x441a80be}}, // _døre, ječu_, ēkā_, _וועס,
+ {{0xb8e38a49,0x2d8b0025,0xdce982ee,0xba74936d}}, // _এক_, guce_, _cveć, _سانت,
+ {{0x58d484fa,0x7cef821e,0x291ec69d,0xb606007a}}, // _почт, _føre, cota_, vošč,
+ {{0x9d431ddf,0x1543002e,0xe81d06a7,0x7cef806a}}, // терд, терм, _बुला_, _gøre,
+ {{0x91d183bb,0x2d8b6be4,0x7d03ebe5,0x99e39c18}}, // _तपाई, buce_, hins, žňuj,
+ {{0x7d0380a4,0x39462005,0x45360198,0xb0c9086a}}, // [7480] kins, днаг, _охот, रांग,
+ {{0x26c90025,0x3947808e,0x394febe6,0x49990009}}, // _znao_, _jins_, _mugs_, ятся_,
+ {{0x7cf000f2,0x6f029601,0x09d600ab,0x26c90122}}, // _värd, tioc, _সেখা, _ynao_,
+ {{0x7cf000f2,0x6fb08bb8,0xc6a38226,0x3947dd63}}, // _däre, _जनसं, врши, _lins_,
+ {{0x6f02ebe7,0x290cad85,0x7d0384b9,0x395d8069}}, // rioc, ynda_, fins, _ntws_,
+ {{0xdb0a8bc5,0x7d03800b,0x6f0290d3,0xa3e506a7}}, // lvfø, gins, sioc, बैक_,
+ {{0x291e95d8,0x612b0074,0xe2080035,0x3eab0144}}, // vota_, külj, _wróć_, yect_,
+ {{0x27e70257,0x7cef83ba,0x7ceb5b64,0x291e8609}}, // ønne_, _røre, _nürn, wota_,
+ {{0x60dc6be8,0x7cefd986,0x7d049a1f,0x2d5407b8}}, // _korm, _søre, éiss, _näe_,
+ {{0xf6518117,0x1604a0d5,0x2d8b0042,0x6562ad68}}, // _لئے_, _शेयर_, vuce_, mpoh,
+ {{0x3947809f,0xdce9cbab,0x56950294,0x645a9764}}, // _dins_, _sveć, нант, _ifti,
+ {{0x394787ca,0x291ea6e8,0x6d48a5e0,0xfbd38039}}, // _eins_, sota_, _hida, _שתף_,
+ {{0x39479209,0x6d48854e,0xf1bf002a,0x3eab4918}}, // _fins_, _kida, _ayán_, rect_,
+ {{0x28d21344,0x2d993177,0x67240c2e,0x6d48ebe9}}, // साफि, rtse_, _ekij, _jida,
+ {{0x6d48bf2a,0xdceb81cd,0x7cf005ec,0x28d2064a}}, // _mida, stgħ, _bärb, सानि,
+ {{0x612b09ce,0x6d4889ca,0x7d03ebea,0x69c2800b}}, // rülm, _lida, zins, _kroe,
+ {{0x60dc3ab0,0xb5fd800d,0xdce98024,0x7d03e3dd}}, // [7490] _borm, _ješt, _uveć, yins,
+ {{0xb5fd8301,0xc8c92724,0x6d488079,0xa3ce801b}}, // _mešt, राइट, _nida, शमा_,
+ {{0xdb1a9099,0x60dc1520,0x3f8c803a,0xb5fd816b}}, // _artí, _dorm, nudu_, _lešt,
+ {{0x7d038157,0x6d48ebeb,0x656d3c07,0xff25803d}}, // wins, _aida, _kwah, ربای,
+ {{0xb5fd803a,0x6d48d9b7,0xdca5bbae,0x7cf001ec}}, // _nešt, _bida, еали, _wäre,
+ {{0x6d48956b,0x394febec,0x60dc6bed,0x3f8c8359}}, // _cida, _rugs_, _gorm, kudu_,
+ {{0xa3c106b7,0x75cb016f,0x2735816d,0x764d01b4}}, // ूमि_, ामुळ, tånd_, _ogay,
+ {{0x645a87ca,0x764d6bee,0x69c292af,0xdb0501ec}}, // _efti, _ngay, _broe, _erhö,
+ {{0x31798065,0x7d03d482,0x69c2831d,0x80b60035}}, // ész_, pins, _croe, _उसमे,
+ {{0xe8f69fc8,0xf1b2010f,0x6d48ebef,0x6724005c}}, // ель_, נסי_, _gida, _skij,
+ {{0x78b88098,0x3947ebf0,0x3f8c8cb7,0xd4988071}}, // _davv, _vins_, gudu_, _юрт_,
+ {{0x3947c613,0xb5fdabea,0xa3bc8768,0x9ad381bc}}, // _wins_, _fešt, इटर_, _dịch,
+ {{0x69c2a26f,0xc332804c,0xfc46016b,0xa91d81d6}}, // _groe, _עוד_, číme_, huži,
+ {{0x6ee604c3,0x3999c649,0x7ae9ebf1,0x3f8cebf2}}, // _móbi, mès_, lket, budu_,
+ {{0x3999ebf3,0x03a30009,0xfaa66bf4,0x656d1ab3}}, // lès_, лиро, наво, _ewah,
+ {{0x7ae988bf,0xa91d8024,0x60dc6bf5,0xb5e8826b}}, // nket, duži, _sorm, _bẹ́r,
+ {{0x3999ebf6,0x60dc221b,0x6e29ae03,0xbbbd83eb}}, // [74a0] nès_, _porm, _nyeb, ्मुक,
+ {{0x2905ebf7,0xb223228e,0x26f0097d,0xa2a180d4}}, // nila_, умул, _चतुर_, _कॉस्,
+ {{0x60dc387f,0x7ae9a948,0xd12f00f7,0x6d489b98}}, // _vorm, kket, حمل_, _rida,
+ {{0x6d4892cf,0x6e29ebf8,0x6b7b0039,0x7ae98088}}, // _sida, _byeb, _צרכנ, jket,
+ {{0x60dc43cb,0x6d489039,0x2905ead1,0x7f49ebf9}}, // _torm, _pida, kila_, _dieq,
+ {{0xd378003a,0xa91debfa,0xb5fd81ac,0x3999ebfb}}, // šću_, buži, _rešt, dès_,
+ {{0x6d488687,0xfd690135,0xa3d48ada,0x3dd880ab}}, // _vida, _bipụ, тойч, _দেখল,
+ {{0x69c294c1,0xd00f0124,0x7c28826f,0xb5fd8b67}}, // _proe, _الف_, _vydr, _pešt,
+ {{0x6d488852,0xa92a8048,0x6e9380f7,0x3999e115}}, // _tida, зіне_, _حلقا, gès_,
+ {{0x69c28b3c,0xb5fdb251,0x6722b5b1,0x290584b9}}, // _vroe, _vešt, looj, gila_,
+ {{0x28d2823c,0xa0a381e5,0xe1ef803d,0x612f9dd5}}, // ताबि, ушыл, لسی_, møll,
+ {{0x3f8c9f19,0x69c2ebfc,0x7ae9ce3c,0x3999ebfd}}, // rudu_, _troe, cket, bès_,
+ {{0x2905ebfe,0xc0e688cc,0x399982be,0x3f8c805c}}, // bila_, _подк, cès_, sudu_,
+ {{0x63a880ee,0x3f8c8115,0x6722822c,0xfd6901bc}}, // _kpdn, pudu_, hooj, _zipụ,
+ {{0xd7efebff,0x9f47826f,0x80cd86a7,0xe3b00019}}, // _су_, šné_, तारे, یرہ_,
+ {{0x656d0435,0xe7fa02f1,0x7cf00799,0x27478032}}, // _twah, _एइजा_, _pärc, _mìnì_,
+ {{0xfeb80077,0x02e10894,0x7f49ad08,0xa91d817f}}, // [74b0] _سايت_, _नवीन, _rieq, kržl,
+ {{0xa91d8052,0x7ae9811e,0x25a9008e,0x6d4f9a1f}}, // tuži, zket, _ipal_, _écai,
+ {{0x7d07003a,0x2d8081ac,0x61300198,0xa91d8968}}, // mijs, šiel_, mäll, držl,
+ {{0x7d076c00,0xa91d8796,0x39b202d6,0x7bc50197}}, // lijs, ruži, _fņs_, _irhu,
+ {{0xa3b9058c,0x7cf00338,0x7c2d8326,0xb27603de}}, // चिक_, _hära, ƙark, _בערג_,
+ {{0x7d070a20,0x2a690072,0x2905ac88,0xf77304e3}}, // nijs, lbab_, xila_, _مار_,
+ {{0x7ae9ec01,0x947480d7,0xb4aa8327,0x6114047f}}, // tket, ادها, गड़े_, _сдру,
+ {{0x63ba006a,0x3999ec02,0x635181a9,0x799b80fc}}, // ætni, tès_, _jāni, ttuw,
+ {{0x7cf000f2,0x7ae9a816,0x26cd8216,0x61301cab}}, // _lära, rket, _ineo_, käll,
+ {{0x3eafc751,0x3999c5dc,0x61301c50,0x7ae9ca2b}}, // legt_, rès_, jäll, sket,
+ {{0x2d8f803b,0x12e780e8,0x25a96c03,0x7bc501c0}}, // luge_, _підг, _apal_, _nrhu,
+ {{0xb8e89c4f,0x752394c6,0x3999d3d4,0x7cf48362}}, // _लो_, lonz, pès_, _eàrd,
+ {{0x2905eba5,0xdce4003a,0xd7f88012,0x61300cb4}}, // pila_, _sviđ, ază_, fäll,
+ {{0xda1c03b7,0xbcfb4aa6,0x7c24b349,0x7cf00338}}, // _भुगत_, spén, şire, _bära,
+ {{0xb4b20a16,0x232a44cc,0x25a91868,0xab2a1ba5}}, // _ऐसी_, дови_, _epal_, дова_,
+ {{0x6a8307b6,0x7cf00106,0x6134823e,0xb95b0362}}, // илса, _dära, màla, _crìo,
+ {{0xee3a0698,0x7523979f,0x7d070b67,0x9c7c807a}}, // [74c0] _юни_, konz, bijs, _hoče,
+ {{0x7d070805,0xb95b0104,0x9c7cd717,0xa2a18f85}}, // cijs, _trìn, _koče, क्त्,
+ {{0xdce404a8,0xdb01a509,0x752380e5,0x2a690300}}, // _uviđ, _aplí, donz, bbab_,
+ {{0x2d8f8fa3,0x2d808029,0x69c48110,0x224f82f7}}, // fuge_, šiem_, _šiem, _nggk_,
+ {{0x80b2835a,0x8e5703c8,0x6b830106,0x9c7c807a}}, // _असले, דינג_, _ångr, _loče,
+ {{0x26cd822e,0x7989ec04,0x7523aeae,0x645c6c05}}, // _eneo_, drew, gonz, ncri,
+ {{0x69ddd95b,0x6f04047f,0x9c7c82d4,0x79898037}}, // äsen, _omic, _noče, erew,
+ {{0x66e3867c,0x2732001b,0x501b0039,0x2fc7006a}}, // _коса, dáno_, _חולו, ænge_,
+ {{0x66e38f2e,0x09e400ab,0x78bc16e1,0x50669bcc}}, // _тота, _নেতা, _karv, ктна,
+ {{0x6f041f65,0x48aa8162,0x30a3804a,0x75238db1}}, // _amic, фтим_, арюв, conz,
+ {{0x61300009,0x7d07026c,0x6ee60333,0x0fd08264}}, // väll, vijs, _lóbu, ান্ধ,
+ {{0x9c7c803a,0x78bc6c06,0x6f0400c3,0x7d0701ed}}, // _doče, _larv, _cmic, wijs,
+ {{0x613004b8,0x442c8355,0xf0b78077,0x7d07115c}}, // täll, _hyd_, رایش_, tijs,
+ {{0x7cf00006,0x78bc3447,0x3ebb04b7,0x6f046c07}}, // _pära, _narv, _waqt_, _emic,
+ {{0x2bc592ee,0x2b4b00ee,0xa2b894fc,0x013781c6}}, // विया, _picc_, ्यप्, _שרות_,
+ {{0x7b641b47,0x59b68075,0x61300009,0x32641454}}, // ртре, _अनार, säll, итув,
+ {{0x78bc1cf3,0x442c8aa2,0x23698668,0x645c0144}}, // [74d0] _barv, _lyd_, ćaji_, bcri,
+ {{0x7bc50267,0x7cf00106,0x3eaf8192,0x6e2d308f}}, // _trhu, _härn, wegt_, _iyab,
+ {{0x69c62d9d,0x7cef8022,0x7cf01d31,0x1dcb06a7}}, // _erke, _børn, _kärn, ामकत,
+ {{0x7cf0016d,0x7af6026f,0x959994bc,0x98b901a9}}, // _järn, chyt, етку_, _pusē_,
+ {{0x78bc3c10,0xa2b884c5,0x2732001b,0x690b01b9}}, // _farv, ्यन्, záno_, rżeb,
+ {{0x2d8fec08,0x7aed6c09,0x442c831d,0xb4bd81ce}}, // ruge_, lkat, _byd_, इये_,
+ {{0xeab180f7,0x67d42139,0x7523ec0a,0x212780b9}}, // معة_, _кору, ronz, _rknh_,
+ {{0x7aed6c0b,0x69c60186,0x442caa30,0x2489817b}}, // nkat, _yrke, _dyd_, şam_,
+ {{0x7aed01e2,0xe0d1804e,0x6e2d5a5f,0x62829a14}}, // ikat, مزد_, _nyab, _ddoo,
+ {{0x9c7c90d1,0x6ff38201,0xb4b20063,0xdb1a816b}}, // _poče, ləcə, _ऐसे_, _vrtá,
+ {{0x6e360063,0x7aed6c0c,0xdee6393f,0xd70a01e2}}, // _szyb, kkat, гони, енне_,
+ {{0x7c2582af,0x6e2d20d7,0x630589a7,0x7cf4823e}}, // ühru, _byab, _حوصل, _vàre,
+ {{0xe9ce86d2,0xc8788201,0x98a595e0,0x2718807a}}, // _ук_, bağ_, _биле, nčna_,
+ {{0xa91d8289,0x9c7ca3f9,0xdc4381d0,0x6e2d6c0d}}, // tužu, _toče, _léči, _dyab,
+ {{0x9c7c80ce,0xc2610135,0x6e2d060e,0x833a0185}}, // _uoče, _ụdịr, _eyab, ечат_,
+ {{0x645c0218,0x7cf000f2,0x7aed12e8,0x78bc2d5c}}, // scri, _gärn, gkat, _sarv,
+ {{0x61e9c450,0x4a750a8e,0x10a60dc8,0xf8b7816f}}, // [74e0] nyel, рыст, лиан, _असाय,
+ {{0x4a431819,0x7aed038a,0x45fb01c6,0xb5fd81d6}}, // снув, akat, _נהיג, _rešp,
+ {{0x749b0051,0x7e76007b,0x7af64092,0x78bc58e5}}, // _אייפ, _keyp, phyt, _varv,
+ {{0xe61ac90d,0x6d561e7f,0x7aed03d5,0x66769650}}, // нде_, _kuya, ckat, ادار,
+ {{0x78bc03ff,0xf7678117,0xc27b02f6,0xd17517ae}}, // _tarv, _گا_, _ארכי, _высы,
+ {{0x7ae30012,0x2fc794e4,0x412a00e8,0x6564004f}}, // _într, _orng_, _чого_, _mtih,
+ {{0xf767803f,0x7cf00106,0x746980e8,0xae048054}}, // _دا_, _läro, трів_, _शेरन_,
+ {{0x7cefa26d,0x6d5603e4,0x41b49ef7,0x6466011c}}, // _føro, _ouya, لمبر, əriş,
+ {{0x69c41b3a,0x61e98065,0xdce40063,0x442c9d33}}, // nvie, gyel, _zwią, _wyd_,
+ {{0x7f42a08e,0x442c879f,0xdd8f8c48,0x2fc7808e}}, // lmoq, _tyd_, _روم_, _brng_,
+ {{0xd9d1035a,0x7cf00006,0x7aed00f1,0x6e2d03ac}}, // _सप्ट, _pärn, ykat, _syab,
+ {{0x7ceb0afe,0x69c401e2,0x63ba809a,0x6d562290}}, // _türk, kvie, _istn, _buya,
+ {{0x2d8b26b9,0x6d560661,0x7cf00589,0x7a7b8e82}}, // urce_, _cuya, _värn, _טראס,
+ {{0x6130016d,0x69c46c0e,0xe81d03db,0x6d5649b5}}, // sälj, dvie, _बुझा_, _duya,
+ {{0x7aed6c0f,0x66f4800f,0x65643690,0x6abd6c10}}, // tkat, _आतंक_, _etih, _pasf,
+ {{0x7cf0016d,0x81ae00ab,0x7bc88140,0xb5fb136f}}, // _härl, কিন_, _mrdu, dbáv,
+ {{0x7aed6c11,0x6d566c12,0x9f4783bb,0x7cf0016d}}, // [74f0] rkat, _guya, ání_, _kärl,
+ {{0x7aed6c13,0x7bc8ec14,0x98a7a6d2,0x6729a582}}, // skat, _ordu, čiću_, _skej,
+ {{0xb8dd0fd5,0x7c2902af,0x9f47801b,0x61fb8c9f}}, // _आस_, ßerd, šní_, zzul,
+ {{0xf7700307,0xb8ec023c,0xf9900019,0x29180144}}, // سام_, _शो_, تبہ_, _ejra_,
+ {{0x6ff38201,0x7bc88102,0x127a00be,0x7bc381c0}}, // rəcə, _ardu, _בארע, wvnu,
+ {{0x2eb2000d,0xbf020576,0x61fb811c,0x7bc88362}}, // _जस्त, _लग्न_, vzul, _brdu,
+ {{0x6ed8035a,0x2bc58bb8,0x3ea90854,0x7ae2b2c7}}, // यामु, विधा, _obat_, _boot,
+ {{0x61e982d6,0xa2e58767,0x61fba822,0xd1858037}}, // tyel, роид, tzul, алий,
+ {{0x69c00118,0x61fbd9f6,0x7ae282f9,0x88d581e2}}, // _ámet, uzul, _doot, аўні,
+ {{0x3ebfec15,0x20558162,0x3ea91e9e,0x94558326}}, // _haut_, штир, _abat_, _zān,
+ {{0x3dc91882,0x3ebf8029,0x69c437f6,0x6d56441a}}, // _araw_, _kaut_, zvie, _suya,
+ {{0xed5a00bf,0x798d0c2d,0xfe46245b,0x6d56114b}}, // ком_, kraw, инно, _puya,
+ {{0x2369803b,0x3ebf810b,0xdce2826f,0xed59812b}}, // ćaju_, _maut_, zpoč, _brži_,
+ {{0x6ee6002a,0x394ec28b,0xddc2826c,0x317f82d0}}, // _cóbr, _gifs_, nboš, msuz_,
+ {{0x8c4630bc,0x317f899b,0x04461485,0x6d560041}}, // реве, lsuz_, ревн, _wuya,
+ {{0x68e3d1d2,0x69c40029,0x1601101c,0xf1a980cf}}, // _hond, tvie, _लेकर_, _कहान,
+ {{0x68e3c001,0x09060c07,0x317f880a,0x26d2009c}}, // [7500] _kond, апан, nsuz_, _inyo_,
+ {{0xe8f73031,0xa2cc816f,0x26d201c0,0x27391dbb}}, // алу_, _दोन्, _hnyo_, ména_,
+ {{0x68e3a7eb,0xd90d00d5,0x69c46b5b,0x3ebfec16}}, // _mond, ریف_, svie, _baut_,
+ {{0x46a69f96,0x3ebf8087,0xbea68110,0x26c0011b}}, // радв, _caut_, радк, _jaio_,
+ {{0x26c00e67,0x82360077,0x7f42ec17,0x287621f6}}, // _maio_, _خردا, rmoq, _выбр,
+ {{0x6b818183,0x68e38102,0x051480ab,0xa0670087}}, // _bvlg, _nond, িত্র_, _вара_,
+ {{0x3ebf83d3,0x7cf000f2,0xdb01826f,0x7ae29c11}}, // _faut_, _närm, _splá, _poot,
+ {{0xbab88895,0xf1a68a8e,0x38781722,0x9c7c9ee0}}, // угих_, арон, _herr_, _doča,
+ {{0x68e38205,0xdcfb803a,0x7cf026f5,0x635c01f4}}, // _bond, _zvuč, _pärl, _očno,
+ {{0x9c7ca4b2,0x26d2020d,0x7ae2a892,0x290780b9}}, // _foča, _anyo_, _woot, _cmna_,
+ {{0x68e3ec18,0x7cf004b8,0x7ae28006,0x63ba9de4}}, // _dond, _värl, _toot, _ustn,
+ {{0x7cf0016d,0xd36f80d7,0x6d4f80e7,0x387800b9}}, // _därm, _آهن_, _écar, _lerr_,
+ {{0x6f038813,0xae01035a,0xed598699,0x443a1fa4}}, // ènci, _लेखन_, _prži_, _azp_,
+ {{0x68e38065,0x2bbd84c5,0x29078101,0x26d26c19}}, // _gond, ्मचा, _gmna_, _enyo_,
+ {{0x26c00118,0xc1b68cf0,0x81cd80ab,0x61b6bed8}}, // _faio_, _अनुग, রহণ_, _अनुष,
+ {{0x290ce845,0x68e38613,0x7cf003ff,0x3ea91083}}, // lida_, _zond, _järj, _ubat_,
+ {{0xdddb803a,0x4c9514ed,0x3ebf8359,0x6d43948c}}, // [7510] _oduš, _липс, _raut_, ymna,
+ {{0x290ce845,0xd8790250,0x26c00102,0x798d00fc}}, // nida_, امات_, _zaio_, rraw,
+ {{0x25ad8422,0x3ebfa914,0xdead09ab,0x902801e2}}, // _spel_, _paut_, _shaƙ, ацца_,
+ {{0x798d0063,0x290cb08a,0x02a71401,0x7e7d1ae3}}, // praw, hida_, _тром, nasp,
+ {{0x51840254,0x3ebf80e7,0x28b786a7,0x443a01ed}}, // _мура, _vaut_, _असलि, _zzp_,
+ {{0x9c7caf62,0xa91d992c,0x61ed01f6,0x438581a8}}, // _poča, prži, myal, ملتق,
+ {{0x290ca6d5,0x68e38e9c,0x3b0980bf,0x316682f7}}, // dida_, _rond, леко_, _atoz_,
+ {{0x68e3aba9,0x5694a154,0x7e7d6c1a,0xfbd08154}}, // _sond, _маст, jasp, بته_,
+ {{0x61ed1c9a,0x6f098a54,0x290c8c9f,0x224b816d}}, // nyal, _imec, fida_, äck_,
+ {{0x39590352,0x290caeaa,0xa3d786b7,0x672d300f}}, // _muss_, gida_, ामद_, _akaj,
+ {{0x9c7c812b,0xfbd080f7,0x26c06c1b,0x68e3ec1c}}, // _kočn, _يتم_, _paio_, _vond,
+ {{0x7cf000f2,0x1efa00f7,0x7e7d020d,0x613d80e7}}, // _värm, اعدة_, gasp, dèle,
+ {{0x290c8c52,0xd25682f6,0x9c7c8353,0x7cf001ec}}, // bida_, _השנה_, _močn, _wärm,
+ {{0x290ca6d5,0x69cb82af,0xed5994f0,0x68fa8f67}}, // cida_, _irge, _muž_, chtd,
+ {{0x6f958307,0x7f858013,0xa3d7a836,0xf74620bf}}, // _العض, _البن, ामि_, _леко,
+ {{0x6ab58076,0x9c7c9234,0x39596c1d,0x7cf00198}}, // _अघोर, _nočn, _buss_, _kärk,
+ {{0x7cf00009,0x9f460061,0x6e3b8135,0xd5b10129}}, // [7520] _järk, szló_, _nzub, ắc_,
+ {{0x20562749,0xa3d79308,0x7cf00879,0x2d823eaf}}, // стар, ामा_, _märk, mske_,
+ {{0x9c7ca46d,0x7afb805d,0xe45703c8,0x2a7900c3}}, // _bočn, khut, _לייט_, _fesb_,
+ {{0x290c82a0,0x6e9380f7,0xf7439821,0xf72980e8}}, // zida_, كلما, пехо, ацій_,
+ {{0x94128201,0xd90d0117,0xa2b8bed8,0x290c82ed}}, // əyə_, ریہ_, ्यस्, yida_,
+ {{0x26c0a597,0x290cd9b0,0xed598289,0x6f098135}}, // žio_, xida_, _duž_, _emec,
+ {{0x69cbec1e,0x290ccc3b,0x9f47826f,0x6e3b81bc}}, // _arge, vida_, šná_, _ezub,
+ {{0xbea3a659,0xbf168875,0x290c87d8,0x27322294}}, // _наск, _اورب, wida_, báni_,
+ {{0x290cb472,0x7e7d62b9,0xed598bcf,0x7ceb5868}}, // tida_, vasp, _bržu_, _kürt,
+ {{0x2d82003a,0x7e7d0573,0x7e64046d,0x3ce58711}}, // dske_, wasp, _afip, _golv_,
+ {{0x33f6965d,0xdeb507ac,0x69cbc853,0xe4d784c0}}, // _учас, обны, _erge, _دولت_,
+ {{0x7afba85a,0x290c97ab,0x7ae666dd,0xd6db1ddf}}, // chut, sida_, _nokt, уто_,
+ {{0xeb14823c,0xa2d581aa,0xfa368277,0x69cb80c3}}, // तचीत_, भार्, _اراد, _grge,
+ {{0x290c90ba,0x51628135,0x2a790695,0x9c7c816b}}, // qida_, _gọọm, _sesb_, _močo,
+ {{0xe7e300c8,0x3cfa0105,0x672d0088,0xdb1aad5d}}, // _মধ্য, _उतरे_, _tkaj, _asté,
+ {{0x61ed4d7b,0x290a534f,0x39592127,0x672d0a5a}}, // tyal, _amba_, _puss_, _ukaj,
+ {{0x9c7c9807,0x6f0f00e5,0x2d820e06,0x00000000}}, // [7530] _ročn, micc, cske_, --,
+ {{0x61ed1fe0,0x7cf000f2,0x6f0f6c1f,0xed5980ce}}, // ryal, _närh, licc, _ruž_,
+ {{0x61ed6c20,0xaac42d05,0xc5e680ab,0xdfd100f7}}, // syal, _लोकक, _খেলা_, بيا_,
+ {{0xed598503,0x7cef83ba,0x635c0bcf,0x6f0f0037}}, // _puž_, _tørk, _očnj, nicc,
+ {{0x7cef83ba,0x1a9a00be,0x9c7c80c3,0x7ceb5c7c}}, // _høri, _דירע, _vočn, _fürt,
+ {{0x92d580c8,0x80b7864a,0x273225ce,0x6f0f0037}}, // _হতে_, _असें, ráni_, hicc,
+ {{0x9c7cc587,0x09d1835a,0x7ae60214,0x7afb805d}}, // _točn, तम्य, _yokt, thut,
+ {{0x2d823c10,0x442100ff,0x7e6280e5,0x7ceb325b}}, // yske_, _mxh_, ccop, _kürs,
+ {{0xe8f99541,0x44298063,0x7afb8859,0x6f0f433a}}, // или_, ła_, rhut, dicc,
+ {{0xa3cc0a74,0x672b820f,0x9f85a57e,0x7afbb094}}, // लिम_, logj, огод, shut,
+ {{0xf0920bea,0xef1a34d6,0x6d41a733,0x7e7b8ad4}}, // וני_, имо_, _ihla, _keup,
+ {{0x2d822cdb,0x7cf00009,0x2f5491d2,0x6d5bbb44}}, // tske_, _tärk, ятьс, _kuua,
+ {{0xa2b88105,0xed5a1b47,0x2d8418cc,0x7cf00106}}, // ्यर्, рог_, éme_, _häri,
+ {{0xa069aaee,0x2d8205fa,0xfd6481bc,0x2a7f81ec}}, // _така_, rske_, _afrị, laub_,
+ {{0xf0b8815b,0x69c9cf0e,0x6d418234,0x28c286a7}}, // _دانش_, lvee, _mhla, _वोटि,
+ {{0x2d820289,0xed5a0003,0xf26a04fa,0xa3cc00d4}}, // pske_, _кои_, риал_, लिब_,
+ {{0x6d41cb89,0xe9da1232,0xb5fb0236,0x60c3caf5}}, // [7540] _ohla, шка_, lcán, _kanm,
+ {{0xe0d70ef5,0x60c396ce,0x3eadc500,0x2a7f8282}}, // ову_, _janm, _abet_, haub_,
+ {{0x14d70158,0x7cf02e6d,0x09b6825e,0x60c38247}}, // _וויל_, _näri, _अन्य, _manm,
+ {{0x60c38247,0x6d419790,0x27e7016d,0x7ceb6c21}}, // _lanm, _ahla, änna_, _fürs,
+ {{0xf5b68bbe,0x60c3839c,0x61303a53,0x6d418834}}, // تصاد, _oanm, fält, _bhla,
+ {{0x6d41ec22,0xf366828b,0x7e62879a,0x7af98176}}, // _chla, отин, scop, _alwt,
+ {{0xbcfb02be,0x439486d2,0x46ca01d0,0x8fa30162}}, // mpét, чайс, ियाह, _оаре,
+ {{0x60c3838e,0x7cf0016d,0x656981b9,0x6d418234}}, // _aanm, _däri, _fteh, _ehla,
+ {{0xe5a68ab2,0xaaa68c78,0x60c38970,0x2cb81699}}, // _физи, क्षक, _banm, kerd_,
+ {{0x6d418068,0xa3ba0b9f,0x2d80a52a,0x80d0009a}}, // _ghla, _आईं_, šies_, _डोमे,
+ {{0x4438c936,0x60c3ec23,0x26c4a00f,0xfaa6910b}}, // _ár_, _danm, _kamo_, _мажо,
+ {{0x74138077,0x26c6ec24,0x2cb8090d,0x7cfdec25}}, // _توما, ndoo_, eerd_, _pèrg,
+ {{0x60c38205,0xdced0024,0x25a6d826,0x6280e415}}, // _fanm, _svađ, ntol_, mamo,
+ {{0x4aa6b011,0x6f0f2d60,0x2cb84c84,0x80a186a7}}, // क्रव, sicc, gerd_, _खाये,
+ {{0x26118105,0x6f0f246c,0x613005ec,0xd7e69138}}, // _देनी_, picc, häls, _філо,
+ {{0x62808662,0xda0c0063,0x60c38247,0x26c4ec26}}, // namo, _सेहत_, _zanm, _namo_,
+ {{0x68e72f82,0x3eb911de,0x67ff8019,0x798402e8}}, // [7550] _pojd, mest_, _tájé, gsiw,
+ {{0x4ad18076,0x3eb90778,0x8b08001b,0x2d993fe0}}, // _दोनव, lest_, _pořá, muse_,
+ {{0x65698352,0x6280ec27,0x74150c48,0x2d990074}}, // _steh, kamo, _روحا, luse_,
+ {{0x3eb9245f,0xf8b78bb8,0x6280ec28,0x2d9901df}}, // nest_, _अस्प, jamo, ouse_,
+ {{0x443e8117,0xd5bb99b5,0xb0658009,0xd469879e}}, // _azt_, рсе_, nkää, биле_,
+ {{0x9f44450e,0x7cf00006,0x9c7c8042,0x3ac78129}}, // ájú_, _päri, _uočl, _ốp_,
+ {{0x2a7f85b6,0x60c3ec29,0x3eb90074,0x2d996c2a}}, // taub_, _ranm, kest_, huse_,
+ {{0xa3cc0a74,0x3eb902fd,0x8c1c012a,0x61300364}}, // लित_, jest_, יווי, sält,
+ {{0x443e8065,0x25bf8886,0xdce41408,0x60c38bb1}}, // _ezt_, _usul_, _stič, _panm,
+ {{0x6f0d6c2b,0x2d99688f,0xdb1e45dc,0xe8f995e0}}, // _amac, duse_, _espè, сло_,
+ {{0xccf2804c,0x395fec2c,0xf092093f,0x628082ba}}, // _רכב_, squs_, ענט_, bamo,
+ {{0x7cf00406,0x69cf03a8,0x628086ea,0x2ca1051e}}, // _järv, _orce, camo, _achd_,
+ {{0xcb128039,0x60c3ec2d,0x2d990074,0x0d830b79}}, // _אלא_, _tanm, guse_, нлян,
+ {{0x6f0d088e,0xb606816b,0x7bc18122,0xa3cf92c6}}, // _emac, _ukáž, _kslu, शिप_,
+ {{0xdce40efd,0xa50718f6,0xb5fd939c,0x1d071e9d}}, // _utič, чера_, _ofšo, чери_,
+ {{0xaabd8540,0x7cf026f5,0x877a83de,0x612f9277}}, // ्यिक, _närv, _פאפי, pøls,
+ {{0xc3328039,0xe8fa0dc0,0x26c4bef3,0xe71701c6}}, // [7560] תון_, бла_, _ramo_, _מחקר_,
+ {{0x7bc1811f,0x49990364,0x79840114,0x9c7c8115}}, // _oslu, ются_, rsiw, _počm,
+ {{0x4abd9b7e,0xaabd8c78,0x628095e6,0x69cf01f4}}, // ्याव, ्याक, yamo, _erce,
+ {{0x6280ba3b,0x273204c3,0xe73a8009,0x628fbcd4}}, // xamo, máns_, щее_, ócol,
+ {{0x237f003a,0x273203a2,0x69cf0699,0xbcfb00e7}}, // ćuje_, láns_, _grce, mpér,
+ {{0x7ae401dd,0x7f5c5983,0x4c949d51,0x3f8781a1}}, // ljit, _turq, фикс, _ovnu_,
+ {{0x628090dd,0x26c4803a,0x60230abe,0x66028065}}, // tamo, _tamo_, _здра, szok,
+ {{0x7ae42ce1,0xada68056,0x7af60110,0x25a68115}}, // njit, _набл, nkyt, ptol_,
+ {{0x3f9a1249,0x7cef9277,0x7cfd89c4,0xd7f88493}}, // lupu_, _rørv, _sère, rcăm_,
+ {{0x6f0d330f,0x2d990074,0x7cef804a,0xfcaa80d7}}, // _smac, vuse_, _sørv, _دارو_,
+ {{0x3eb91c1d,0x6280ec2e,0x6f1d05a4,0xdb1e6c2f}}, // test_, pamo, érco, _espé,
+ {{0x2d990006,0x7f440cd4,0x273204c3,0xb0658198}}, // tuse_, _chiq, dáns_, tkää,
+ {{0x3eb920a5,0x7ae404a8,0x6d4aec30,0x60db00fc}}, // rest_, djit, omfa, _ɗumb,
+ {{0x3eb90665,0x69c2da1d,0x2d99032a,0x48e393f1}}, // sest_, _isoe, ruse_, _почв,
+ {{0x3eb90065,0x6e2400a9,0x2d992249,0x8fa384ae}}, // pest_, _exib, suse_, _заје,
+ {{0x78ba8065,0x7ae400f1,0x6f0d2ce4,0x2d990087}}, // letv, gjit, _umac, puse_,
+ {{0x442d009a,0x672f009a,0x7cef806a,0x27320061}}, // [7570] łe_, mocj, _kørt, ránt_,
+ {{0x51f811e9,0x81ae00ab,0x9c7c816b,0x656d0a5a}}, // чную_, কিং_, _dočk, _itah,
+ {{0x6f1d6c31,0xdb039c18,0x2732002a,0x09a9b26c}}, // onsc, stné, cáns_, _कह्य,
+ {{0x69cf026c,0x6f1d2e76,0x645aec32,0x6d5f2751}}, // _trce, nnsc, _agti, _kuqa,
+ {{0x6f1d526f,0x7cf042ec,0xd1388110,0x4993004e}}, // insc, _värv, klą_, _سیار,
+ {{0x6d5f393e,0x3f85ec33,0x11da0039,0x3af8826b}}, // _muqa, yslu_, _וחשב, _bépo_,
+ {{0x6d570a50,0x2366026c,0x6d5f4d3d,0x68e3008b}}, // _mixa, _čojk_, _luqa, öndu,
+ {{0x80db0076,0x60c726b8,0x752e0bcf,0x629f005d}}, // नावे, _hajm, zobz, ngqo,
+ {{0xb1459d51,0x656d2fc0,0x765b93a4,0x6594c1ce}}, // мнил, _ntah, _iguy, _пату,
+ {{0x248c8122,0x271882d4,0x7f446c34,0x6f1d14cf}}, // _gddm_, včni_, _shiq, ensc,
+ {{0x7bc1803b,0x7cf00074,0x656d0a5a,0x26118035}}, // _uslu, _märt, _atah, _देती_,
+ {{0x60c7020f,0xa3cc0935,0x7c2907d9,0x6441ec35}}, // _lajm, लिस_, ğerl, _izli,
+ {{0x7cef8aa2,0x443c0084,0x6d450362,0x2bc598a9}}, // _ført, _šv_, _bhha, विका,
+ {{0x60c715d8,0x3e7c0104,0x273203a2,0x7afd4c84}}, // _najm, ật_, táns_, _alst,
+ {{0x656d113b,0x7f4400ee,0xa3e086ab,0xa7fc82d0}}, // _etah, _thiq, दमय_, ncıl,
+ {{0x765bec36,0x6d5f426c,0x27325d34,0x6d57023e}}, // _nguy, _fuqa, ráns_, _eixa,
+ {{0x7af607ac,0x60c72009,0x273203a8,0x442587b6}}, // [7580] rkyt, _bajm, sáns_, _lxl_,
+ {{0x7af661ec,0x6441b6c5,0x7afd6c37,0x3f9a6c38}}, // skyt, _ozli, _elst, tupu_,
+ {{0xa2a6001b,0x59ba0074,0x6ea50f3d,0x60c73e83}}, // _छाप्, _उनकर, _कामु, _dajm,
+ {{0x9c7c8805,0xee3aa920,0x7aeb8a53,0x78ba8061}}, // _točk, оне_, _nogt, zetv,
+ {{0x628412f5,0xd5b084e3,0x6441ad8d,0x6b9c6c39}}, // maio, افت_, _azli, lurg,
+ {{0x3f9a3661,0x29130118,0x7cf00192,0x6d4aec3a}}, // pupu_, cixa_, _gärt, umfa,
+ {{0x78ba819c,0x2bd104e5,0x9c7c81a1,0xa2a506d4}}, // vetv, हिया, _joči, ङ्क्,
+ {{0x7cef8aa2,0x2005af16,0x60c7009a,0x7cf0329f}}, // _børs, dzli_, _zajm, _kärs,
+ {{0x7aeb81b0,0x9c7c807a,0x224200b9,0xa2a6001b}}, // _dogt, _loči, _jzkk_, _छान्,
+ {{0x7c2d807e,0x6b9c011b,0x7cf00338,0x9404011c}}, // şara, kurg, _märs, şmə_,
+ {{0x656d6c3b,0x78ba920e,0x9c7c807a,0xb5fb0118}}, // _stah, retv, _noči, rcám,
+ {{0x7cef8a38,0x656080b8,0x79964be9,0x6b9c008b}}, // _førs, _kumh, kryw, durg,
+ {{0xdb1a80a9,0x7792803d,0x6560e066,0x7cf00338}}, // _estã, ریکا, _jumh, _närs,
+ {{0xa2b8928a,0x9c7c8669,0x672f2358,0x7996002f}}, // ्यक्, _boči, socj, dryw,
+ {{0x7c340c5c,0xd00fa707,0x6b9c0ba0,0x7aeb822c}}, // верх, _рф_, gurg, _yogt,
+ {{0x60c70067,0xa3cf9199,0xaf0801a8,0xcf2701bd}}, // _sajm, शित_, _يقوم_, _ترتي,
+ {{0x6aad04b9,0x656d1e8f,0x6560802a,0x291e933e}}, // [7590] rfaf, _utah, _numh, onta_,
+ {{0x7b6704fa,0x272403bf,0xa3cc06af,0xf1c880ff}}, // _отме, mına_, लिश_, _mỡ_,
+ {{0x291e8e71,0x6b45ec3c,0x2724080a,0x6f0280f7}}, // inta_, lóge, lına_, fhoc,
+ {{0x7bc50423,0x7cf0016d,0x7cef806a,0x7afd0d1a}}, // _ishu, _färs, _nørr, _ulst,
+ {{0x6560b956,0x27240182,0x60c70390,0xe57881e2}}, // _cumh, nına_, _tajm, дзі_,
+ {{0xa3c3801b,0x7d156c3d,0xdb1a8118,0x7cef821e}}, // _září_, nizs, _ortó, _rørs,
+ {{0xe3b19fbe,0xeb99d9dc,0x4ada8076,0xcfa40162}}, // ارت_, чий_, भांव, _ишти,
+ {{0x6f02835c,0x2b46adca,0x84580039,0x442594a6}}, // choc, _choc_, ובוס_, _vxl_,
+ {{0x9f52001b,0xf1c880ff,0x7d15026c,0xb5290032}}, // ází_, _cỡ_, kizs, _atọ̀,
+ {{0x2eed8cfa,0xdb1a809f,0x2724061c,0x6b45a620}}, // _hoef_, _està, dına_, dóge,
+ {{0xe73a11b3,0x7996009a,0x644181a9,0x3ea60103}}, // зен_, zryw, _uzli, нинг,
+ {{0x291e94f2,0x7b030074,0x272407c0,0x60db09ab}}, // anta_, _jõud, fına_, _ɗuma,
+ {{0x9c7c9bf2,0xf1c880ff,0x7bc55a17,0xdb03928a}}, // _soči, _gỡ_, _ashu, ntní,
+ {{0xb0ce009a,0x25bf02df,0x2eed8901,0xdb038174}}, // _होंग, çula_, _loef_, itní,
+ {{0x7cf000f2,0xb602000d,0x628433ab,0x261180dc}}, // _särs, _žádn, taio, _देसी_,
+ {{0x7b030074,0x272407c0,0x27f88aa2,0x39580197}}, // _nõud, bına_, ørne_, _sirs_,
+ {{0x7989a948,0x409601cf,0x46dd01a2,0x6b458144}}, // [75a0] nsew, ерет, यारह, cóge,
+ {{0x7cf00879,0xa0a30087,0x6b9c5093,0x02bd800c}}, // _värs, лауд, purg, ्युन,
+ {{0xb9080105,0x9c7c8668,0x7cf00884,0x6f02ec3e}}, // _मच_, _uoči, _färr, thoc,
+ {{0x261185b3,0xb5aa8013,0x3cde02f1,0xdb1abfe5}}, // _देही_, كاتك_, खावे_, _ostá,
+ {{0x291e82a3,0x9c7c8639,0x39586c3f,0x2d9d80b4}}, // ynta_, _močv, _tirs_, guwe_,
+ {{0x69c65c23,0x636783bf,0x6f02ec40,0xf1c880ff}}, // _iske, _sına, shoc, _rỡ_,
+ {{0x6f02810c,0x44441502,0x52bd80c2,0x67243a48}}, // phoc, _jz_, ्यूस, _ojij,
+ {{0xe6ca090a,0xc7d70051,0x6e968872,0x26118128}}, // ियोज, _אולי_, _سلطا, _देवी_,
+ {{0xc1b78fe6,0x645a0267,0x25660428,0x76442eae}}, // елых_, žtih, _nôl_, _nziy,
+ {{0xf2df0104,0xa2d5816f,0x3958a317,0xf1c880ff}}, // _nhân_, _मोठ्, örs_, _vỡ_,
+ {{0xdb1ac07d,0x7cef8aa2,0x76444b1b,0x80ca00ab}}, // _está, _tørr, _aziy, রাপ্,
+ {{0x27240182,0xaaa788af,0xa3bd92ee,0xdbc78074}}, // tına_, _गायक, इबर_, _pöör,
+ {{0x44446c41,0x2b5913f0,0x7c3a8114,0x443a1061}}, // _az_, _risc_, _hytr, _ayp_,
+ {{0x27240459,0xf2df0028,0x672401bc,0x2b5900ee}}, // rına_, _chân_, _ejij, _sisc_,
+ {{0x44442191,0x6d5aec42,0x2724084a,0xdb1e0510}}, // _cz_, _mita, sına_, _espí,
+ {{0x7c3a8081,0x083b8039,0xdbc782f1,0x53341697}}, // _mytr, _מעול, _töör, гест,
+ {{0x44441738,0x80ca00c8,0x325512a0,0x0f5800be}}, // [75b0] _ez_, রান্, твар, _איהם_,
+ {{0x7bc501e9,0x6d5aa8aa,0x68ee07f1,0x673d2157}}, // _tshu, _nita, _cobd, llsj,
+ {{0x3f9e811e,0x2d8b009a,0x69c6011e,0x3cde1d70}}, // nutu_, lsce_, _eske, खारे_,
+ {{0x6d5ac802,0x3ee48098,0xa3cf99ee,0x2d9da235}}, // _aita, лючв, शिस_, tuwe_,
+ {{0x6d5acb22,0x64a584f6,0x3f9e823d,0xa2a603a4}}, // _bita, вала, hutu_, _छात्,
+ {{0x6d5aec43,0x3f9ea256,0x26c9805c,0xa3bd801b}}, // _cita, kutu_, žao_, इबल_,
+ {{0x6d5a878a,0x6282838e,0xdb03827f,0x261181ce}}, // _dita, _beoo, stní, _देरी_,
+ {{0x02bd8076,0xe9d99317,0x8e8480f7,0x0ed0016f}}, // ्येन, дки_, _ملفه, _तोंड,
+ {{0x6d5a80a4,0x2d8b009a,0xa2a28aed,0x79898176}}, // _fita, jsce_, _गार्, usew,
+ {{0x6d5aec44,0x79898295,0xd12e80a0,0x2bc2a594}}, // _gita, rsew, يمي_, _वैवा,
+ {{0x3f9ee3f7,0x79898609,0x2d9909ff,0x52b581a8}}, // gutu_, ssew, erse_, _لماذ,
+ {{0x6d5aec45,0x7aef3b40,0x6806810c,0x6e3b808e}}, // _zita, _noct, _séjé, _kyub,
+ {{0xf2df0028,0x7ae9ec46,0x44446c47,0x644509da}}, // _phân_, mjet, _sz_, _azhi,
+ {{0x7ae9ec48,0x3f9e854e,0x44445862,0xdddb825b}}, // ljet, butu_, _pz_, _oduž,
+ {{0xed570dc8,0x28a6acdd,0x6d4e00f1,0x2d990333}}, // тор_, _खाति, rmba, arse_,
+ {{0x38c80bca,0x7afb8575,0x61e42bfa,0x2fc782c4}}, // کاری_, nkut, txil, _isng_,
+ {{0xf2df0028,0x7aef0be5,0x7d0181a8,0x5a633760}}, // [75c0] _thân_, _doct, _olls, ркуб,
+ {{0x443a6c49,0x44446c4a,0x29058234,0x61e4434e}}, // _typ_, _tz_, nhla_, rxil,
+ {{0x444441f3,0xc8b50110,0x7afbe690,0x248786c0}}, // _uz_, ысты, kkut, danm_,
+ {{0x6d5aec4b,0x7d01872c,0x7f49ec4c,0x7ae9969b}}, // _sita, _alls, _cheq, jjet,
+ {{0x6d5a8025,0x7ae981c8,0x6f1d0acf,0x2905bb07}}, // _pita, djet, érci, khla_,
+ {{0xe0464566,0x684600c4,0x1d3385a8,0x65640198}}, // _анги, _анга, анія, _muih,
+ {{0x9fc70767,0x17c7091e,0x69c4009a,0xb5fb01a8}}, // _игра_, _игри_, lwie, rcái,
+ {{0x6d5aec4d,0x7afbea48,0x6aa4164d,0xb5fb00f7}}, // _wita, gkut, ngif, scái,
+ {{0x44328063,0x6d5abe85,0x7c3a8035,0xee378048}}, // ły_, _tita, _wytr, _ант_,
+ {{0xa3cf823c,0x6d5a802e,0x9c7cad7d,0x7afbdb89}}, // शिश_, _uita, _poču, akut,
+ {{0xa09b0158,0x27396c4e,0x69c401ec,0x2d803c51}}, // וייט, ként_, hwie, _awie_,
+ {{0x3f9e88bf,0x973d00ce,0x7d1890aa,0x6722b7f7}}, // rutu_, kuće, livs, nnoj,
+ {{0x973d0024,0xf19482c7,0x6564010c,0x3f9eec4f}}, // juće, _силь, _cuih, sutu_,
+ {{0x973d02a5,0x3f9eec50,0x77638118,0x2d800035}}, // duće, putu_, _funx, _dwie_,
+ {{0xd7efec51,0x9388039d,0x386e8118,0x7ceb01ec}}, // _ту_, кста_, _iffr_, _kürz,
+ {{0x99800035,0x905781c6,0x7d18811a,0x2cad01d0}}, // dził_, _בסוף_, hivs, řed_,
+ {{0xdb1a80f2,0x973d0a20,0xdb1e0118,0x45d50992}}, // [75d0] _istä, guće, _arpó, _сопс,
+ {{0xc7ad8065,0x3dc902d5,0x7f498168,0x2739016b}}, // _بڑی_, _isaw_, _sheq, rénu_,
+ {{0xccf20051,0x6e3b87d5,0x753501b9,0x248786c0}}, // _הכי_, _syub, mozz, vanm_,
+ {{0x07a4240f,0x75352e6c,0x395ca280,0x7bd70609}}, // раїн, lozz, _livs_, _irxu,
+ {{0x3da7867c,0x7ae98e54,0xa18401bb,0xa6349138}}, // треб, vjet, _высл, анці,
+ {{0x7535623d,0x6b558084,0xa3cf9107,0xf773066e}}, // nozz, _išgi, शिल_, _نار_,
+ {{0xa3cc0321,0x22468019,0x3ea90369,0x7ae9ec52}}, // लिए_, _azok_, _ocat_, tjet,
+ {{0x24878205,0x05c3064a,0x7cfd810c,0xf36680d7}}, // sanm_, षबाब, _tèro, دئوی,
+ {{0x7ae9c41c,0x9c7c9c18,0xdced0bda,0x7afbec53}}, // rjet, _počt, _atač, rkut,
+ {{0x7ae9d337,0x0cce00ab,0x7763bc3f,0x6f198428}}, // sjet, _রক্ত, _punx, liwc,
+ {{0x3ce70076,0x7ae9ec54,0xb4cf816f,0x65640198}}, // ञाने_, pjet, षयी_, _suih,
+ {{0x290581a1,0x6f198428,0x24858252,0x7cf48362}}, // shla_, niwc, _helm_, _fàrs,
+ {{0xe0ce8021,0x29036c55,0x6aa40186,0x59b793e5}}, // _лв_, _olja_, vgif, _अहिर,
+ {{0x333f82a6,0xc99280f7,0xdb1a8198,0x753500df}}, // llux_, _الوظ, _estä, gozz,
+ {{0x656406cb,0x798d0428,0x6aa407b3,0x3ea90242}}, // _wuih, esaw, tgif, _fcat_,
+ {{0xdce4005c,0x69c446e9,0x2365817f,0x3169011c}}, // _otić, twie, _mulj_, fqaz_,
+ {{0x09e311b3,0x91e301f3,0x6aa40be7,0x75352d08}}, // [75e0] ботн, боте, rgif, bozz,
+ {{0x26c22bc0,0x27390548,0x69c4686e,0x51560c4f}}, // leko_, béns_, rwie, утбу,
+ {{0x6f041bc0,0x6aa400f2,0x28a69344,0xdb1a8009}}, // _klic, pgif, _खासि, _ystä,
+ {{0x2bd1035a,0xdb9b036b,0x26c2011e,0x973d00d2}}, // हिरा, _אסטר, neko_, puće,
+ {{0xa3cc03eb,0x6d5e4572,0x63ba82f7,0x6f042aa0}}, // लिक_, _kipa, _sptn, _mlic,
+ {{0x60c18364,0x6f04009f,0xa7860124,0x236580c3}}, // jelm, _llic, _مشرو, _bulj_,
+ {{0x60c1a976,0x628610e1,0x6d5e0010,0x7d188b81}}, // delm, _keko, _mipa, rivs,
+ {{0x6d5e6c56,0x26c2120e,0x7cf4809f,0x291a6c57}}, // _lipa, jeko_, _càrr, lipa_,
+ {{0xdced0efc,0x58868071,0x60ce0282,0x26c2011e}}, // _stač, лына, _kabm, deko_,
+ {{0x57b48396,0x6d5e2ece,0x62866c58,0x60c1ec59}}, // абот, _nipa, _leko, gelm,
+ {{0xdb03826f,0x6f046c5a,0xeb9789a0,0x394280eb}}, // stná, _blic, _бих_, ēks_,
+ {{0x62860052,0x26c20578,0x291a002e,0x6d5e011e}}, // _neko, geko_, hipa_, _aipa,
+ {{0x60c1ec5b,0xf2df001c,0x7b030074,0x291a6c5c}}, // belm, _giây_, _nõua, kipa_,
+ {{0x6f04347d,0x656f80e7,0x6d5e6c5d,0x273c84e8}}, // _elic, _éche, _cipa, mínu_,
+ {{0x6d5e078a,0x753524c5,0x6286171a,0x26c20102}}, // _dipa, rozz, _beko, beko_,
+ {{0x7b640193,0x41e705e9,0x443ea699,0x628609c4}}, // стре, ліза, _myt_, _ceko,
+ {{0x161c01fe,0xad2704c1,0x6f1d0118,0xb11501e5}}, // [75f0] _पेपर_, _مردو, ércu, амаш,
+ {{0x4a541a1a,0x0edd035a,0x6fa88540,0x6d5e1341}}, // скус, याकड, _कमां, _gipa,
+ {{0x443eec5e,0xdce4012b,0xf77000f7,0x6eac06a7}}, // _nyt_, _stić, كان_, _झामु,
+ {{0x6286105d,0x2fca00b9,0x1b7a00be,0x28d506ab}}, // _geko, _dsbg_, _שטרע, _डोरि,
+ {{0xa684835f,0x443eec5f,0x60c198e8,0x7aed5953}}, // слід, _ayt_, yelm, mjat,
+ {{0x7aed6b4d,0x26c200ad,0x443e9e1e,0x2903003a}}, // ljat, zeko_, _byt_, _ulja_,
+ {{0x2bd80f1b,0x60c183a7,0x26c20737,0x1d06847f}}, // डिया, velm, yeko_, рещи_,
+ {{0x41b584fa,0x16168105,0x26c2011b,0xe6108180}}, // асот, _देवर_, xeko_, _کشف_,
+ {{0x60c195c8,0x54a78b8c,0x6289831d,0x31af0457}}, // telm, _محاف, raeo, nüz_,
+ {{0xa2d58778,0x6f16151f,0xbc6880f7,0x6b45818a}}, // _मोर्, _smyc, _ممكن_, cógn,
+ {{0x26c26c60,0x6d5e063f,0x28a6800f,0x60c1ec61}}, // teko_, _ripa, _खारि, relm,
+ {{0x6d5e5cda,0x7f4d4a94,0x7aed1369,0x63a3c1cb}}, // _sipa, _chaq, jjat, munn,
+ {{0x7f4d02c1,0x6d5e4b20,0xd8f58077,0x63a3ec62}}, // _dhaq, _pipa, _پزشک, lunn,
+ {{0x62860859,0x26c243b5,0xdca604bd,0x4e96003d}}, // _seko, seko_, јави, دشگر,
+ {{0x07a58b33,0x6d5e6c63,0x63a3808b,0xa3d5847d}}, // _тайн, _vipa, nunn, िटि_,
+ {{0x2b40151e,0x7c3e026f,0x672980f1,0xaabda9b7}}, // ulic_, _vypr, _njej, ्यटक,
+ {{0x61fb871f,0x61e99daf,0x62863251,0x63a38c9a}}, // [7600] nyul, nxel, _veko, hunn,
+ {{0xa3d585b3,0xe9d72344,0x63a3abed,0xc8e006b7}}, // िटा_, шку_, kunn, नाइट,
+ {{0x62866c64,0x0fd917ae,0x63a39bd0,0x9e668f04}}, // _teko, льцы_, junn, авед,
+ {{0x7d1c10d7,0xe9a655e6,0xf9878077,0x2055079e}}, // lirs, _камп, _وب_, стур,
+ {{0x70c58105,0x6286807b,0x63be00b9,0xae168006}}, // _वसूल, ðkom, _hppn, _देशन_,
+ {{0x80ca00c8,0x26070327,0x63a3ec65,0x67298144}}, // রাহ্, हनती_, funn, _ejej,
+ {{0xb5fb0073,0xb4e68105,0x69d98114,0x63a3ec66}}, // ncár, _बची_, _arwe, gunn,
+ {{0xb6a39a19,0xbea39052,0x672980f1,0x7b030074}}, // _мисл, _маск, _gjej, _lõun,
+ {{0x9f5910ab,0xdb070061,0x38c8803d,0x443e8428}}, // árí_, ntjá, هادی_, _wyt_,
+ {{0x7f4d02c1,0xa3e603bb,0xd90e8bca,0x63a3c1cb}}, // _shaq, पमा_, بیت_, bunn,
+ {{0x644880d2,0x69d982af,0x473550f6,0x7d1c6c67}}, // _uzdi, _erwe, йнос, dirs,
+ {{0x88bc81d0,0x273ccb9b,0xd8388088,0xd041811c}}, // cněn, míns_, ngči_, ndlə,
+ {{0x973d003b,0xceb38039,0x7ae2ec68,0x7d1c2f21}}, // guća, חיר_, _inot, firs,
+ {{0xb0d50072,0x7d1c5a98,0xd90e80d7,0x31af22f8}}, // _डोंग, girs, _آید_, vüz_,
+ {{0x27320019,0xd7fa84ae,0x7aed6c69,0x9b680a2e}}, // hány_, _јул_, tjat, ишта_,
+ {{0x29185a21,0xe70509a7,0x63be00b9,0xdb1a9af9}}, // _amra_, _دستی, _dppn, _astú,
+ {{0x7aed6c6a,0x7d1c0511,0x6f1d0e83,0xb5fb002a}}, // [7610] rjat, birs, misc, acár,
+ {{0x6f1d6c6b,0x7d1c0333,0x7bdaec6c,0x7c290192}}, // lisc, cirs, _ortu, ßers,
+ {{0x7aed375d,0x83f939c3,0x8e3800d7,0xa267047f}}, // pjat, _сенс_, _مسیر_, _въгл,
+ {{0xb8fe101c,0x6f1d6c6d,0xec360051,0x63a3ec6e}}, // _दो_, nisc, _לאתר_, vunn,
+ {{0x261185e8,0xd0870198,0x88bc81d0,0x63a380fc}}, // _देखी_, _выби, vněn, wunn,
+ {{0x63a3a3c6,0x2bd3b2dd,0x7ae28039,0xfbd180f7}}, // tunn, थिला, _anot, كتب_,
+ {{0x672980f2,0xaca3082e,0x7f428036,0x88bc81d0}}, // _tjej, _adịg, bloq, tněn,
+ {{0x2bd805b3,0x63a3955f,0x7bda9665,0xcac98009}}, // डिता, runn, _drtu, угие_,
+ {{0x6f1d6c6f,0x88bc81d0,0x80ca00ab,0x7d1c0085}}, // disc, rněn, রার্, yirs,
+ {{0x09059109,0x60c502af,0x7ae2ec70,0x63a3ec71}}, // спон, nehm, _enot, punn,
+ {{0x68f502a5,0x6f1d527b,0x83aba21f,0x7e640122}}, // _mozd, fisc, _штаб_, _pgip,
+ {{0x6d4380f2,0x81c680c8,0x6f1d0e83,0x0a188077}}, // llna, _উপর_, gisc, افیک_,
+ {{0x2d8403d3,0xe8ee9a34,0x2f55035f,0xa3cf83dd}}, // ème_, _ел_, ютьс, शिक_,
+ {{0xd37e9487,0x4aa78424,0x7ae2b102,0x6f1d01a8}}, // šćen_, _गाँव, _znot, aisc,
+ {{0x65618073,0x7d1c55bf,0x68fca651,0xe29a03c5}}, // _milh, rirs, örde, лав_,
+ {{0x6441c702,0x6aa9ec72,0xadf9897d,0x27f883ca}}, // _myli, ngef, ्नान_, ärne_,
+ {{0xe9da07ac,0x7d1c6c73,0xb5fb0020,0x64a6174a}}, // [7620] ыка_, pirs, scár, _лапа,
+ {{0x26d226df,0x343a0277,0xb4e6800f,0x27322aa8}}, // _hayo_, _مسجد_, _बचे_, vány_,
+ {{0x26d2049f,0x63be094c,0x6441a28f,0xdb07007b}}, // _kayo_, _uppn, _nyli, stjá,
+ {{0x7b030074,0xdd8f003d,0x69c9864e,0x26d25998}}, // _jõul, _توی_, kwee, _jayo_,
+ {{0x26d231a2,0xdd8f9368,0x6561ec74,0x644a017f}}, // _mayo_, فون_, _bilh, šlić,
+ {{0x6441972b,0xd1321a37,0x26d23cbf,0x68f5007a}}, // _byli, _رمز_, _layo_, _gozd,
+ {{0x973d0025,0x8fa3067c,0xb6060353,0x64419532}}, // dućn, _наре, lišč, _cyli,
+ {{0x26d26c75,0x64418514,0x68e3ec76,0x497486f9}}, // _nayo_, _dyli, _annd, олис,
+ {{0x65618073,0xdce2805c,0xe29f807b,0x32029e1e}}, // _filh, spođ, óða_, áky_,
+ {{0xa2da00d4,0x973d003a,0x656f82be,0xdd8f80f7}}, // _पोस्, gućn, _écha, _أول_,
+ {{0x6f1d43a6,0x3202803e,0x61e48110,0x06e412c6}}, // tisc, šky_, _šild, गांव_,
+ {{0xb7db007c,0x26d24a96,0x656980fc,0x2d498333}}, // אקטי, _cayo_, _yueh, núen_,
+ {{0xe89482c7,0x7afda9dd,0x7cfd809f,0xb606007a}}, // _даль, öste, _sèri, jišč,
+ {{0xb6060db7,0xf76f8077,0x6f1d6c6d,0x3ceb83ca}}, // dišč, بای_, sisc, चाये_,
+ {{0x68f50e04,0x6f1d6c77,0x249f03bf,0xb4fb83c8}}, // _rozd, pisc, ğum_, נפלי,
+ {{0x6ec48fd5,0x26d215d4,0xae9b01a8,0xaca3026b}}, // _रसगु, _gayo_, _إضغط_, _idọg,
+ {{0x291e8009,0x50f51a47,0x6f0281d6,0xb4fa852a}}, // [7630] oita_, озат, dkoc, ्साय_,
+ {{0x291e95bd,0x29076c78,0xb88200e1,0x26d21d61}}, // nita_, óna_, _šírk, _zayo_,
+ {{0xf84b0656,0x672d026c,0x26d21d61,0x68f51f3a}}, // учай_, _kjaj, _yayo_, _vozd,
+ {{0x291eec79,0x2a6681c0,0x17f801a8,0x7d1a9dba}}, // hita_, _ngob_, حركة_, _amts,
+ {{0x291e84d2,0xa3aba38c,0x65618187,0x290c8079}}, // kita_, कून_, _pilh, khda_,
+ {{0x672d235d,0x21ba00be,0xdb0389b9,0x973d0669}}, // _ljaj, _דזשא, punë, kućo,
+ {{0x051680f7,0x973d01dd,0x6561a3ea,0x81bb00ab}}, // تيوب_, jućo, _vilh, _আছি_,
+ {{0x69c98051,0x8c1c00be,0x6f02826c,0x973d00d2}}, // twee, טווי, ckoc, dućo,
+ {{0x291ecc49,0x6441864c,0x656195f8,0x6aa980e3}}, // fita_, _wyli, _tilh, rgef,
+ {{0x69c9a808,0x26d26c7a,0xc10580f7,0x6aa981ec}}, // rwee, _sayo_, زوجي, sgef,
+ {{0xb17b04b8,0x69c9800b,0x26d2009c,0x973d0503}}, // _fråg, swee, _payo_, gućo,
+ {{0xe73a0767,0x050600ab,0x69cf3020,0x69dd29d1}}, // рем_, _ঈদের_, _osce, _orse,
+ {{0x291ed85b,0xee3705e9,0x95830fe6,0x200382d4}}, // bita_, жня_, мляе, šji_,
+ {{0x973d012b,0x628b811b,0x290c8229,0x26d26340}}, // vrće, _hego, chda_, _wayo_,
+ {{0x26d202b8,0xc7460199,0x628b8642,0x69cf5dec}}, // _tayo_, _وضعي, _kego, _asce,
+ {{0x28ac0b3b,0xa22a0323,0x7bc18118,0xb606007a}}, // _चाहि, ажба_, _mplu, tišč,
+ {{0x628b8065,0x3b0700b3,0xdce08087,0x256f061c}}, // [7640] _mego, цето_, _jumă, _gül_,
+ {{0x628bbd9b,0xa2da000c,0xf53f0082,0xa3e9800d}}, // _lego, _पोर्, står_, ममा_,
+ {{0x69cf2e88,0x2d4981df,0x7bc18069,0x69dd6c7b}}, // _esce, túen_, _nplu, _erse,
+ {{0x91e65b5d,0x444413c8,0x63a101ac,0x628b9fb0}}, // _доме, _iy_, álne, _nego,
+ {{0x44442674,0x6f028a0f,0x7bc18358,0xf2df0028}}, // _hy_, rkoc, _aplu, _châu_,
+ {{0x44446c7c,0x6f099b7d,0x291e8102,0x6f02ec7d}}, // _ky_, _elec, xita_, skoc,
+ {{0x44442674,0x261a850a,0x628b8a58,0x0eaa00d4}}, // _jy_, _मेरी_, _bego, _कांड,
+ {{0x444400f8,0xaec39317,0x3945809f,0x1e869bcc}}, // _my_, _обсл, ells_, _флам,
+ {{0x291edb92,0x753c0063,0x628bc2df,0x61e481a1}}, // tita_, korz, _dego, _šilb,
+ {{0x672d003b,0xeb9a8698,0x44444521,0x644102f1}}, // _sjaj, _виж_, _oy_, ülik,
+ {{0x44440082,0x291e87d5,0xdce982a5,0xe1ff00ff}}, // _ny_, rita_, _steć, _ngón_,
+ {{0x290cd7c4,0x628bd5f2,0xa2da2769,0x76440a03}}, // shda_, _gego, _पोल्, _byiy,
+ {{0x44446c7e,0x753c6c7f,0x291eec80,0xe0cf803d}}, // _ay_, forz, pita_, رزی_,
+ {{0x44446c81,0x628b8102,0x224b81ec,0x387f00ce}}, // _by_, _zego, ücke_, _đuro_,
+ {{0x44440578,0x987b83c8,0xcb1b0073,0x7644071f}}, // _cy_, _דרוק, аќа_, _eyiy,
+ {{0x44446c82,0x290a0046,0x539b807c,0x63a70289}}, // _dy_, _alba_, יילו, rujn,
+ {{0x44444577,0x88bc801b,0xf53f3552,0x5d8401a8}}, // [7650] _ey_, vněj, tråd_, _قليل,
+ {{0xd49b0676,0x4444031d,0x6f098a0f,0x2bcb016f}}, // _три_, _fy_, _slec, ाबदा,
+ {{0x6f0986da,0x6d47135a,0x444402ec,0xb8d31d70}}, // _plec, mlja, _gy_, _टा_,
+ {{0xdb0383f8,0x69dd00dd,0x6d47007b,0x2be00105}}, // puné, _trse, llja, _पछता,
+ {{0x628b81c1,0x237f005c,0x7bc1b0a6,0xdb070106}}, // _rego, ćuju_, _splu, rtjä,
+ {{0x628bd1db,0xdb07016d,0x88bc81d0,0x929d8035}}, // _sego, stjä, sněj, dkła,
+ {{0x6b4595d8,0xe299b3c2,0x656d03ac,0x4900016f}}, // lógi, бай_, _luah, _शकतो_,
+ {{0xb17b228f,0x753c5c1f,0x6aad1aa4,0x6f1b82a0}}, // _tråd, zorz, ngaf, _umuc,
+ {{0x6d47011a,0x672b80e8,0x673d082c,0x25708580}}, // klja, mngj, dosj, _gàl_,
+ {{0xa3bb87bd,0x6565326a,0xbef600c2,0x635c0242}}, // _ناصر_, _nihh, ुघ्न_, _dčny,
+ {{0x6d4194e5,0x753c6c83,0x644501c0,0x60d56c84}}, // _ikla, vorz, _nyhi, _mazm,
+ {{0x44445cd9,0x656d0458,0x753c009a,0x291c81f6}}, // _ry_, _buah, worz, _emva_,
+ {{0x44446c85,0x91e58dae,0x753c6c86,0x7b0e826b}}, // _sy_, _холе, torz, _dùud,
+ {{0x2d9e026f,0x657b8010,0x91f681c6,0xb97a825f}}, // čte_, _mtuh, _ממנו_, _הנסי,
+ {{0xe8df001c,0x987a83de,0x7af9b3c7,0x63860110}}, // _ngực_, _פאקט, _kowt, ягва,
+ {{0x6b63522f,0x63ae813c,0xe3630d9e,0x7e69a4ff}}, // екса, _åbne, екси, _ngep,
+ {{0x6d47252d,0x753c0211,0x5bb888fd,0x6ebc10be}}, // [7660] blja, porz, _आह्व, ष्णु,
+ {{0x44446c87,0x20561ddf,0x3ebf833e,0x7e69d413}}, // _ty_, птер, _abut_, _agep,
+ {{0x44446c88,0x78a3007b,0x212600d2,0x36c6a84f}}, // _uy_, ónva, đoh_, збег,
+ {{0x60c88063,0x6d41809c,0x48dd8fb2,0x248c928a}}, // zedm, _akla, _कोसो_, _sedm_,
+ {{0x6284040e,0x26c0004f,0xae16a724,0xa4d4804a}}, // mbio, _mbio_, _देखन_, долі,
+ {{0xb4d698e6,0x60d536c4,0x29058bda,0x673d0140}}, // िये_, _gazm, jkla_, zosj,
+ {{0x69c2837a,0xf21486a7,0x7af9b603,0x644500b9}}, // _spoe, _तेज़_, _bowt, _xyhi,
+ {{0x6d41ec89,0x79a69bc1,0x7cfd810c,0x62840f3e}}, // _ekla, прое, _bèrs, nbio,
+ {{0x6da38ff0,0x60d5017b,0xd5c08006,0xada3db4d}}, // _пита, _yazm, _एहिज, _патл,
+ {{0x6722949e,0x26c00091,0xdce406ec,0x290582d5}}, // lioj, _abio_, _otiđ, gkla_,
+ {{0x6602aaab,0x8f35228e,0x673d6c8a,0xe1ef804e}}, // lyok, денц, tosj, نسی_,
+ {{0x60c8ad9b,0xe7ed82f1,0xb903ac4f,0x67228084}}, // sedm, _छपरा_, _पो_, nioj,
+ {{0xb0ac035a,0xa2d58076,0x6602ec8b,0x673d4d5a}}, // _चांग, _मोट्, nyok, rosj,
+ {{0x2ec993ba,0x2905816d,0xeb05125f,0x38c88019}}, // _रस्त, ckla_, रस्त_, _ذاتی_,
+ {{0x58bb8039,0x6d470669,0x00bb8039,0x673d05f3}}, // _המוצ, rlja, _המומ, posj,
+ {{0x6d470025,0x656d6c8c,0x5bb8062c,0x66029a14}}, // slja, _tuah, ялся_, kyok,
+ {{0xb606016b,0xd2340081,0x2bcb0035,0x26cb6c8d}}, // [7670] _práš, ентъ, ाबहा, meco_,
+ {{0xb4d68c33,0x26cb4a96,0x2bce0105,0x8af00085}}, // ियो_, leco_, _हैवा, lməl,
+ {{0x20070b4c,0x6f0d6c8e,0x628401e8,0x60d50115}}, // áni_, _klac, bbio, _vazm,
+ {{0x6d41c31e,0x26cb02df,0xe9df492b,0x2121008e}}, // _skla, neco_, _trú_, sihh_,
+ {{0x20070805,0xd5bbadc7,0x9f5c801b,0x3f983076}}, // šni_, ссе_, ává_, _evru_,
+ {{0xee38835f,0x6f0d51fb,0x1dde00ba,0x26cb07b6}}, // чні_, _llac, मितत, heco_,
+ {{0x6f0d084a,0x628f0812,0x6d41816b,0x26cb09c4}}, // _olac, _keco, _vkla, keco_,
+ {{0x64410063,0x07098013,0x36d49017,0x2ef581e5}}, // żliw, _بيري_, _поср, _ўзар,
+ {{0x59e006af,0x290584e8,0xdb1a9b88,0x6d4187b6}}, // नियर, tkla_, _astó, _tkla,
+ {{0x27390065,0x48058cde,0xf1d28063,0x6f0d13a9}}, // mény_, дпов, तिजन, _alac,
+ {{0x6f0d087b,0x628f6c73,0x26cb0216,0xbea6259a}}, // _blac, _oeco, feco_, манк,
+ {{0x6f0d01e4,0x628f16eb,0x7523da17,0x2b490267}}, // _clac, _neco, linz, jlac_,
+ {{0x6f0d009a,0x213ecfa2,0x7bc501f6,0xee3a0a18}}, // _dlac, both_, _aphu, інг_,
+ {{0xd8258992,0x332d808e,0x628f0580,0xd009802e}}, // _одли, lnex_, _aeco, _меле_,
+ {{0x04431b53,0x8c432457,0x6f0d6c8f,0x628f0039}}, // верн, вере, _flac, _beco,
+ {{0x6f0d6c90,0x26c00025,0x25ee816f,0x752382a0}}, // _glac, _ubio_, _आपली_, hinz,
+ {{0xe8fa069b,0x628f4793,0x31679100,0x62846c91}}, // [7680] ола_, _deco, _kinz_, rbio,
+ {{0x8cbf80dc,0x66028010,0x629d5502,0x9c7c82d6}}, // ल्मो, vyok, _edso, _enčj,
+ {{0x7a6a07b6,0x628f00e5,0x6b45ca09,0x8af0011c}}, // _минг_, _feco, lógu, lməm,
+ {{0xe5c6941e,0xe80e83b7,0xa3e1890f,0x628f00f3}}, // дско, ानता_, दिन_, _geco,
+ {{0x67228110,0x27390061,0x333fddf5,0x8af0029a}}, // rioj, gény_, doux_, nməm,
+ {{0xd37aaa15,0x6722949e,0x9f5f04e8,0x7afdd8e5}}, // очи_, sioj, ryté_, östn,
+ {{0x69dd813c,0x22468590,0x68fc365a,0xbebb01ed}}, // æsen, _syok_, _iord, diën,
+ {{0x68fc6c92,0x63aa80dd,0x2b4064bc,0xc2988eef}}, // _hord, rufn, noic_, чких_,
+ {{0x68fc3f2a,0x26cb5f6b,0xbebd8084,0x7523a6fd}}, // _kord, veco_, _smūg, binz,
+ {{0x68fc6c93,0x6f0d5345,0xcce680f7,0x6d5c017f}}, // _jord, _slac, _تسري, jmra,
+ {{0x68fc3d7f,0x6f0d4918,0x3f98826f,0x7c6580f7}}, // _mord, _plac, éru_, _كامل,
+ {{0x6d4aa911,0x656880f1,0x522680a9,0xdb1aec94}}, // llfa, _hidh, _офла, _estò,
+ {{0x628f0c8c,0x68fc1ad7,0x26cb3e3d,0x22468122}}, // _reco, _oord, reco_, _uyok_,
+ {{0x68fc006f,0x628f114e,0x656896bb,0xbebb01ed}}, // _nord, _seco, _jidh, ciën,
+ {{0x6f0d6c95,0x3eaf9252,0x628f01e8,0xd6db245b}}, // _tlac, yggt_, _peco, _дтп_,
+ {{0x6568820f,0x973d0582,0x6448e7a7,0x35b54add}}, // _lidh, kući, _mydi, _збир,
+ {{0x68fc24c0,0x973d0067,0x6568925b,0x6448ec96}}, // [7690] _bord, jući, _oidh, _lydi,
+ {{0x973d0289,0x2b490669,0x7b178087,0x6568ec97}}, // dući, slac_, nţur, _nidh,
+ {{0xf77007bd,0x44271918,0x68fc4695,0xb4cb016f}}, // لان_, _än_, _dord, ळजी_,
+ {{0x61e2ec98,0x6568ec99,0x629d0366,0x25a6ec9a}}, // _orol, _aidh, _udso, frol_,
+ {{0x68fc3b39,0x973d003a,0x656888ce,0x427a03c8}}, // _ford, gući, _bidh, _מארג,
+ {{0x7afd1838,0x78718019,0x6568a4ee,0x2d9967ea}}, // _host, _művé, _cidh, isse_,
+ {{0x7523ec9b,0xc61f809a,0xe2998254,0x61e28114}}, // rinz, _मेरठ_, пай_, _arol,
+ {{0x7e6d3aa0,0x25a68cc0,0x7afd2707,0x7523a4e7}}, // _ngap, brol_, _jost, sinz,
+ {{0x68fc6c9c,0xbebb00f3,0x61e2ec9d,0x7523804f}}, // _yord, tiën, _crol, pinz,
+ {{0x7afd6c9e,0x68fc04c3,0x6d450282,0x7e6d6c9f}}, // _lost, _xord, _nkha, _agap,
+ {{0xb8d78b9f,0x7afd038e,0xff2611d0,0x2d8d88f9}}, // _छा_, _oost, емно, _hwee_,
+ {{0x7afd3a68,0x6da60ba5,0x7769808e,0x6d456ca0}}, // _nost, нима, _jiex, _akha,
+ {{0x656882a3,0x7e7b804f,0x7aeb810c,0xa3ab8072}}, // _yidh, _mfup, _ingt, कूर_,
+ {{0x257404b8,0x656882a3,0x7afd6ca1,0xd1968039}}, // _väl_, _xidh, _aost, _טכני_,
+ {{0xa3e70f04,0xdddb88fc,0xd3721a3c,0x7afd5de0}}, // ндра_, _neuž, اهر_, _bost,
+ {{0x7afd07b5,0x6d45005d,0x68fc6ca2,0xdb0384e8}}, // _cost, _ekha, _sord, ytný,
+ {{0x4a750196,0xcb1280be,0x68fc5819,0xdc3c0390}}, // [76a0] тыст, אלן_, _pord, _išča,
+ {{0x46c1000d,0x38c8004e,0x290e0dfa,0x4f960087}}, // ष्ठह, باری_, ófa_, ерду,
+ {{0x8cd906bf,0x7afd6ca3,0x7649eca4,0x66060e34}}, // _फोटो, _fost, _ayey, lykk,
+ {{0x7afd6ca5,0x68fc0e9e,0xb907058c,0x216a245b}}, // _gost, _word, _मो_, _ними_,
+ {{0x6f1d0722,0x63ae0690,0x45d4a0bf,0x2bd801d0}}, // èrci, lubn, толс, डिका,
+ {{0x7afd21a9,0x62962692,0x25a684c3,0x9f590013}}, // _zost, layo, rrol_, árú_,
+ {{0xf3668ab2,0xa3abeca6,0x1fb50226,0xdb039b2c}}, // нтин, कूल_, _истр, stný,
+ {{0xa067138c,0x6e938013,0x6296022e,0xdd92dc11}}, // _часа_, _ملفا, nayo, _مور_,
+ {{0x88bc81d0,0x6568b00b,0xe737e246,0xb53a10ab}}, // knět, _tidh, _чет_, _atọ́,
+ {{0x3ea009da,0x92cc00ab,0x61e2910f,0x7b15816a}}, // _adit_, লায়_, _vrol, _náuf,
+ {{0xf53f00f2,0x62964a11,0x2d8000b9,0x4034a2ea}}, // från_, kayo, _atie_, тенс,
+ {{0x61e28687,0xbebb00f3,0x6fb18cfd,0x7b0306ae}}, // _trol, ciël, _अमरू, _jõut,
+ {{0x7afd6ca7,0x629604a7,0xc33382f6,0x2d996ca8}}, // _rost, dayo, _גוף_, usse_,
+ {{0x7afd380b,0xd24f80f7,0x3ea00039,0x63a105bf}}, // _sost, _انه_, _edit_, álno,
+ {{0x63ae0042,0xd00f8dc0,0xa29501e2,0xf0939101}}, // gubn, _сф_, каві, _ענק_,
+ {{0xa3e19a87,0x62966ca9,0x3958008e,0xdb1880e1}}, // दित_, gayo, _ahrs_, ftvé,
+ {{0x7afd3a68,0x4ac281ab,0x9f5f0061,0x30a70dfd}}, // [76b0] _vost, व्यव, sztó_, ерев,
+ {{0x273c816b,0xaa6437cd,0x7afd00b9,0xb17b0106}}, // míny_, утск, _wost, _från,
+ {{0x28150416,0x7afd2c6a,0x56958009,0x62966caa}}, // خواس, _tost, вает, bayo,
+ {{0x7afd424d,0x62966cab,0xfe708199,0x316a02f9}}, // _uost, cayo, مده_, _eibz_,
+ {{0x4ea489f7,0x02bc82f1,0xdb1e2ab5,0xdb07128a}}, // урха, ्जैन, _espó, nují,
+ {{0xf9920bea,0xfaff01b0,0x7c3402d0,0x03a5a964}}, // _דרך_, _hoë_, _çerç, вико,
+ {{0xdce5007a,0x776980b9,0xdb07128a,0xf53f267f}}, // _nihč, _tiex, hují, yrån_,
+ {{0x2d8da40d,0x2bc2816f,0x7e7b8c53,0xb4ce03ca}}, // _twee_, _शहरा, _ufup, _रस्_,
+ {{0x3dc901c0,0x0edd809a,0xbebb110f,0x7e648503}}, // _npaw_, _नोएड, riël, žipr,
+ {{0x26cfc92f,0x60099634,0xdb07001b,0x3ea0008e}}, // lego_, дном_, dují, _sdit_,
+ {{0xe73a0dcd,0x2d800087,0x7b030074,0x3dc902d5}}, // ден_, _stie_, _jõus, _apaw_,
+ {{0x26cf8063,0xe2908013,0xfbd284de,0x26dd81ec}}, // nego_, _هذه_, _כתב_, ndwo_,
+ {{0xe0cea296,0xc3328039,0x273c816b,0x00e3853d}}, // _кв_, _דוא_, gíny_, _متغی,
+ {{0xa3b589a3,0x290b80eb,0xa5a986a7,0x62961855}}, // _जमा_, īcas_, _घिनौ, wayo,
+ {{0x6296132f,0x7b030074,0x66061af1,0x6726330c}}, // tayo, _nõus, sykk, pikj,
+ {{0x6d43bed0,0x2d80802e,0x3f85ccae,0x29116183}}, // hona, ţiei_, mplu_, _alza_,
+ {{0x6d43af16,0x973d0668,0x26cf8cc7,0x62966cac}}, // [76c0] kona, guću, dego_, rayo,
+ {{0x6d4394e0,0x62966cad,0x2eed808e,0x224b01e0}}, // jona, sayo, _anef_, _dyck_,
+ {{0xf9f980ab,0x6296033e,0x224b0118,0x3f9c00e7}}, // েছিল_, payo, _eyck_, évu_,
+ {{0x6d4e636c,0x8fa30d46,0x443a002a,0xd7ef00f7}}, // llba, раце, _ixp_, تكم_,
+ {{0xdb1e0063,0x9db7845a,0x6d43abee,0xe7699c12}}, // _wspó, ندوز_, fona, رحمن_,
+ {{0x80c10bb8,0x6d43e1cd,0x260e82f1,0x6b558084}}, // र्दे, gona, तैषी_, _išgy,
+ {{0x25ee809a,0x61e6026c,0x60dc0834,0x59e00035}}, // _आपकी_, _hrkl, _iarm, निवर,
+ {{0x26cf809a,0x61d895a6,0x61e600fe,0x6d4e0192}}, // cego_, емия_, _krkl, hlba,
+ {{0x6d439e98,0x0566917e,0x6f008083,0x6b4584f0}}, // bona, _аван, _iomc, tógr,
+ {{0x6d43da8f,0x61e6017f,0x998d8035,0x70ac8072}}, // cona, _mrkl, dzeń_, चलेल,
+ {{0xd0078003,0xf6518065,0xb4ae8935,0x3cdb81ab}}, // веќе_, _نئے_, कली_, _गोटे_,
+ {{0x60dc6629,0x75270a0f,0x6f0080b9,0x7c3a82a6}}, // _larm, wijz, _jomc, _ixtr,
+ {{0x6f0380a9,0x6f008024,0x6b45816a,0x7527090d}}, // ênci, _momc, pógr, tijz,
+ {{0x26cfecae,0x25f5816f,0x062423e7,0x6f00ba16}}, // zego_, ्हती_, рфюм, _lomc,
+ {{0x212ba246,0x752700f3,0x26cf82e7,0x57d106ae}}, // èche_, rijz, yego_, _सनेह,
+ {{0x6d4e0998,0xdb0701d0,0x26cfecaf,0x6d48810c}}, // alba, pují, xego_, _lkda,
+ {{0x998d809a,0x61e4811f,0xc093046d,0x2fca05ee}}, // [76d0] czeń_, _šilj, _aláṣ, _apbg_,
+ {{0x26cfecb0,0x67240025,0x2bab0105,0x764d04b9}}, // wego_, _zmij, _छिपा, _iyay,
+ {{0x3d080063,0x26cfecb1,0x60dc6cb2,0x61e66cb3}}, // _सकते_, tego_, _darm, _erkl,
+ {{0x6d48ecb4,0x2bf2001b,0x6d43ecb5,0x7982ecb6}}, // _akda, ीहरू_, wona, _otow,
+ {{0x6d43ecb7,0x26cf823b,0xddc581a0,0xdca595b7}}, // tona, rego_, убли, гали,
+ {{0xe79500d5,0x7b030074,0x26cfecb8,0x3f4782f1}}, // _مارک, _tõus, sego_, tõus_,
+ {{0x5f946cb9,0x6f0080b9,0x7982d413,0xe7870251}}, // _вист, _fomc, _atow, _југо,
+ {{0xe9d99eef,0xf1b78105,0x799b8300,0x6d43ca2b}}, // еки_, _आमदन, tsuw, sona,
+ {{0x6d43ecba,0x764d0a5a,0x60dc6cbb,0x7aef0573}}, // pona, _nyay, _yarm, _mnct,
+ {{0x672402fd,0x32090079,0x6d438079,0x29118c83}}, // _smij, iyay_, qona, óza_,
+ {{0xd8258103,0x26d983a8,0x60259bcc,0xe4f08054}}, // удни, _ósos_, удна, चारि_,
+ {{0xa3e705b3,0x9c1481bc,0x7b0a03c1,0x290c82c4}}, // पिन_, _kọnz, _výuk, akda_,
+ {{0x64400085,0x24980197,0x61e901a1,0xdb1c03ed}}, // əmiy, tarm_, _šeln, strë,
+ {{0x69d999e7,0x7afbad2e,0x998d8035,0x5887a462}}, // _iswe, ljut, rzeń_, _рыда,
+ {{0xf99f0205,0x6d4e0101,0x32090079,0x61e60267}}, // _avè_, rlba, eyay_, _srkl,
+ {{0x7afb8500,0x672406ec,0x28c6170c,0x80c1559b}}, // njut, _umij, र्नि, र्से,
+ {{0x60dc6cbc,0xdb1c19a7,0x7f3b03de,0x387f011c}}, // [76e0] _parm, ntré, _רעפו, _şura_,
+ {{0xe9d701bb,0xfd690870,0x7f49913b,0x61e600c3}}, // ыку_, _mkpụ, _akeq, _vrkl,
+ {{0x60dc2924,0xa68689b8,0xdee6b51e,0xbeaa803d}}, // _varm, глед, ложи, فهان_,
+ {{0x69cba40d,0x6d5a820f,0xee3a8a8e,0x7f5b8144}}, // _opge, _shta, нне_, _chuq,
+ {{0x7d018510,0x7afb90d3,0x2579823e,0x1c1c82f1}}, // _bols, djut, _cèl_, _भेजल_,
+ {{0x7d01ecbd,0x394587f1,0x25798f33,0x6ab66cbe}}, // _cols, jols_, _dèl_, lgyf,
+ {{0xfd690135,0x69d9ecbf,0x61e46cc0,0x6e3b913b}}, // _akpụ, _aswe, lvil, _exub,
+ {{0xdced011c,0x29038338,0x6ab60428,0x25798242}}, // _otağ, öjan_, ngyf, _fèl_,
+ {{0x7d01ae6c,0x62348cdf,0xee378081,0x3914ccf4}}, // _fols, репу, _бнт_, имир,
+ {{0x41bb00be,0x64568085,0x973d026c,0x91bb03de}}, // עציע, əyin, mrći, עמיי,
+ {{0xbf10035a,0x473514d6,0x5ede00ab,0x58f700be}}, // ासून_, инос, যানে, אמיע_,
+ {{0x7bdaecc1,0xa3e19094,0xd6db0221,0x61e46cc2}}, // _istu, दिर_, хто_, kvil,
+ {{0x776d062f,0x3fc8804e,0x2bd08035,0x160c01a2}}, // _viax, ردگی_, थौरा, हनगर_,
+ {{0x61db84de,0x61e4623a,0x69c08084,0xdb1a841c}}, // _נקוד, dvil, mtme, _estô,
+ {{0x94058201,0x61e4573d,0xa87b8039,0x69c0ecc3}}, // _belə_, evil, _ארוח, ltme,
+ {{0x2bac8d1c,0x2d848699,0x69c08cda,0xd5d48072}}, // _घटना, _otme_, otme, _धनंज,
+ {{0x69c0ecc4,0x3f9ec003,0xa5f90012,0xdb03841c}}, // [76f0] ntme, lstu_, _репу_, sunç,
+ {{0x69c0d21b,0x7bda8074,0x3209057b,0xa3b5916e}}, // itme, _ostu, ryay_, _जमल_,
+ {{0x2579d93d,0x7d0186a8,0xff07a133,0x6d47014c}}, // _sèl_, _rols, лядн, moja,
+ {{0x7d01ecc5,0x2d9d82a0,0x3f9ed7d8,0xd5fb819d}}, // _sols, tswe_, istu_, _ndụ,
+ {{0x7d01909b,0xdb070125,0x6cf086af,0x51841501}}, // _pols, stjó, चांग_, _гуса,
+ {{0x3f9eaec0,0x6d471ffe,0x290f00eb,0x63a5801b}}, // kstu_, noja, īgas_, áhno,
+ {{0xaa0d800d,0x9992009a,0x7d01c549,0x3945823e}}, // िन्छ_, czył_, _vols, vols_,
+ {{0x29030f84,0x7d01ecc6,0x3d10826b,0x69c08e20}}, // _hoja_, _wols, _fàwo_, ftme,
+ {{0x29030025,0x7afb8006,0xf4d98ae7,0xa2dd0105}}, // _koja_, rjut, _имаш_, _पसन्,
+ {{0x672bb518,0xdb1c0c83,0x7d0180b9,0x7afb94c7}}, // ligj, rtré, _uols, sjut,
+ {{0x2903264b,0x6d471482,0xdb1c6cc7,0x7b15802a}}, // _moja_, doja, stré, _cáuc,
+ {{0x290300a9,0xf746264e,0x3945907c,0x96661bb6}}, // _loja_, ремо, sols_, икне,
+ {{0xe7374b8d,0x6d47441a,0xf1d9801b,0x69d9ec75}}, // рея_, foja, _भनिन, _uswe,
+ {{0x2bdc9344,0x2bda903e,0x2ed280a5,0x7c3883ed}}, // _मनमा, भिचा, _सस्त, _çarç,
+ {{0xe29a05c2,0xfc031033,0xab2a004a,0x5ecb0264}}, // кав_, опро, вова_, িয়ে,
+ {{0x672b820f,0x25a0004a,0x628d02d5,0x6a864bfc}}, // jigj, _tvil_, gbao, рлаа,
+ {{0xdca63b4c,0x64a60b01,0xcb670328,0xa9261343}}, // [7700] _капи, _капа, рате_, адал,
+ {{0x0ce080ab,0xa0a61cce,0x29030dd7,0x236f8168}}, // _বক্ত, ражд, _coja_, _ligj_,
+ {{0x9c7c811f,0x29030168,0x98be8ec3,0x629b83e4}}, // _jače, _doja_, notā_, kauo,
+ {{0xeab2003f,0x539b0051,0x52753cb3,0x61e4345e}}, // _بعد_, _ביטו, _гуру, pvil,
+ {{0xd838941f,0x80c100d4,0x25a080f7,0x6f040035}}, // mače_, _रॉके, éil_, _moic,
+ {{0xd8388669,0xdc19009a,0x290303ed,0xa4d4902a}}, // lače_, _włąc, _goja_, сокі,
+ {{0x6f16016d,0xf8c61664,0x28c6073c,0xed578eef}}, // _olyc, र्णय, र्णि, јој_,
+ {{0xa3e705e8,0x2ba7800f,0x31c6a155,0xd838c8f0}}, // पित_, _गिरा, асов, nače_,
+ {{0x8af00201,0x7d01007b,0xd257943b,0x28c62b51}}, // lməs, ölsk, иць_, र्थि,
+ {{0xdde98077,0x9c7c8289,0x69c08039,0xc8669ddf}}, // _حرفه_, _bače, rtme, итни,
+ {{0x6d476cc8,0x6f046cc9,0x7bdadb5d,0x69c0a4cf}}, // voja, _boic, _ustu, stme,
+ {{0x28c6000c,0x3f9e9ab2,0x6d476cca,0x6f0428c9}}, // र्ति, rstu_, woja, _coic,
+ {{0x6d4706f7,0x2ee02d68,0x6f0400f7,0xd838936f}}, // toja, _daif_, _doic, dače_,
+ {{0x6d470084,0x7afd8338,0x644d89ab,0xf806804a}}, // uoja, östv, ƙait, _вчин,
+ {{0x6d476ccb,0x9f591931,0x29036ccc,0x8af00201}}, // roja, áró_, _roja_, ymət,
+ {{0x29036ccd,0xd8388353,0x200f826f,0x3a751957}}, // _soja_, gače_, íliš_, слар,
+ {{0x9c7c8db7,0x6d47365d,0x212c8090,0xcad781c6}}, // [7710] _zače, poja, oidh_, סומת_,
+ {{0x6cd50bca,0x777881c0,0x7c3e00b9,0x27ea0706}}, // _اقدا, _kuvx, _fxpr, _brbn_,
+ {{0xd8388904,0x20070087,0x39498118,0x29030088}}, // bače_, âni_, _íase_, _voja_,
+ {{0x63678019,0x7d05026c,0xb95b026b,0x35f5902a}}, // _tűni, _hohs, _apìt, йпер,
+ {{0x8af0011c,0x645a9fa4,0xe4ec108a,0x7c3b0850}}, // rmət, _azti, _छोडि_, ğuru,
+ {{0x28c606bf,0x69dd3975,0x62828091,0x973d026c}}, // र्दि, _isse, _afoo, mrću,
+ {{0xab29979e,0x443e8118,0x3edf0032,0x6ee181bc}}, // лока_, _cxt_, _agọọ_, _ịbuw,
+ {{0x5c380039,0x443e8118,0x2bb88035,0xd43801c6}}, // ירון_, _dxt_, _इमरा, יטוי_,
+ {{0x3ce28076,0x672984cd,0x9c7c90d3,0x212c80f7}}, // _टोके_, _imej, _sače, fidh_,
+ {{0x2903807b,0xa3c906af,0x6f0401a8,0xa2c934ec}}, // ðja_, ोबा_, _soic, _हॉस्,
+ {{0x6f040081,0x311d914f,0x66e662ab,0x2011b835}}, // _poic, योगः_, бона, ázi_,
+ {{0x6d5e00f1,0x212c8068,0x69dd1fe4,0x27320353}}, // _shpa, aidh_, _osse, kšne_,
+ {{0x99858013,0xd83887df,0xda788698,0x7d0500ee}}, // _التو, vače_, _тях_, _bohs,
+ {{0x30798158,0x332d81b9,0x9c7c81a1,0xd5fa825f}}, // _גאַנ, liex_, _tače, אפשר,
+ {{0xd838826f,0x69dd3277,0x67298353,0x62999a03}}, // tače_, _asse, _omej, _kewo,
+ {{0x673ba0a7,0x62998247,0xf7431fcb,0x9f485292}}, // _njuj, _jewo, _нещо, _orné_,
+ {{0xd83884c4,0x6299db41,0x61fb913b,0x7bc386ae}}, // [7720] rače_, _mewo, nxul, htnu,
+ {{0x673b8870,0x320d8079,0x6729804f,0x644802f1}}, // _ajuj, iyey_, _amej, üdis,
+ {{0x69dd2a4b,0xd8388369,0xa1568039,0xa2d8903e}}, // _esse, pače_, צבעה_, _मसल्,
+ {{0x7bde003a,0xe61a8088,0xdfcf1ddd,0x63ae928d}}, // _ispu, лде_, عين_, _åbni,
+ {{0xdb1c282b,0xa50a89a0,0x1d0aa0bf,0x67298609}}, // ntrí, леда_, леди_, _dmej,
+ {{0x7ae46cce,0x69c40612,0x6729b861,0x62998242}}, // ldit, mtie, _emej, _aewo,
+ {{0x62998574,0xdee68cdf,0x320d8079,0x61eb8428}}, // _bewo, _лови, eyey_, _argl,
+ {{0xef91003d,0xa03701c6,0x5214a3e7,0x2b4082d4}}, // _آیند, _האלה_, ждит, čico_,
+ {{0x64a6a18c,0x80dc80c8,0x629983ec,0x249a0388}}, // _газа, বাস্, _dewo, _oepm_,
+ {{0xf8d80321,0x69c46ccf,0xdce981b9,0x7ae46cd0}}, // ड़िय, itie, _tweġ, hdit,
+ {{0x32ca8b71,0x212c8ad0,0x332d84b7,0x6d4ac184}}, // _было_, ridh_, biex_, mofa,
+ {{0x62998574,0x69c444a9,0xab66838b,0x61e48110}}, // _gewo, ktie, _увел, _šilu,
+ {{0x5fda8e1a,0x7ae41e41,0x7bde6cd1,0xf8d806a7}}, // _बनवल, ddit, _aspu, ड़ाय,
+ {{0x80dc80ab,0x5f9495b7,0xa3ac86a7,0x63b5017f}}, // বাহ্, _дикт, _कटा_, tuzn,
+ {{0x7ae28074,0x93880009,0xdb1c0333,0xac181027}}, // _kaot, йста_, atrí, бору_,
+ {{0x69c4270c,0x15fa92c7,0xd138d5f8,0x28c60cf0}}, // ftie, ्हार_, lną_, र्सि,
+ {{0x7bde3ef3,0x7ae29581,0x90991ef8,0xd0438326}}, // [7730] _espu, _maot, иват_, _banɗ,
+ {{0x6729c28e,0x672f009a,0x29c2807b,0x7bc3826f}}, // _smej, licj, iða_, ytnu,
+ {{0x660f00f2,0x69c420b5,0x7ae40609,0xb901123a}}, // lyck, atie, bdit, _दस_,
+ {{0x69c420e3,0xb65a0091,0x636f026b,0xd366a3c8}}, // btie, _abẹ́, _dònì, _زه_,
+ {{0xd24691fb,0x69c444c8,0xf77092dc,0xf8a9003d}}, // _آن_, ctie, رام_, _نگاه_,
+ {{0x212a02af,0x629f008e,0x25a9ecd2,0xd0438326}}, // _gmbh_, laqo, šalo_, _ganɗ,
+ {{0x7ae286e7,0xd138809a,0xff538060,0xfd59826b}}, // _baot, dną_, _فخر_, _kubẹ,
+ {{0x17689109,0x130980e8,0xd1388110,0x3eb98257}}, // _груп_, рний_, eną_, _øst_,
+ {{0x659499b5,0x7ae2833e,0x28c61c42,0xdc0501a2}}, // _нату, _daot, र्वि, रहुड_,
+ {{0x660f01d0,0x77c997c7,0xaca40133,0x61eb8824}}, // dyck, алог_, twọk, _vrgl,
+ {{0x69c4011e,0x672f051c,0x61e488fc,0x7ae2851e}}, // ztie, ficj, _šilt, _faot,
+ {{0x7ae28635,0x798d00e4,0xfbd081f9,0x657b9ae3}}, // _gaot, kpaw, _ستم_, _kuuh,
+ {{0xa3e18778,0x69c404b7,0x98a591d5,0xd54a01a8}}, // दिक_, xtie, _диле, لجسم_,
+ {{0x9c7c8b67,0x3ea90722,0x3f6600e7,0xdd7a80be}}, // _kača, _edat_, _cœur_, יטשל,
+ {{0x61e98364,0x9c7c8052,0xeb9a0558,0x68e39149}}, // lvel, _jača, риб_, _iand,
+ {{0x6f1d01ec,0xa3ea890f,0x29078102,0x16098c28}}, // chsc, मित_, _hona_, वहार_,
+ {{0x68e3ecd3,0xfce6095a,0x2907ecd4,0xf2d2898a}}, // [7740] _kand, _допо, _kona_, וען_,
+ {{0x68e3a823,0x29078c30,0xe8f73de7,0xd838b8b7}}, // _jand, _jona_, олу_, lača_,
+ {{0x68e3dc68,0x2907a900,0x28c606bf,0x7bde005c}}, // _mand, _mona_, र्षि, _uspu,
+ {{0x69c46cd5,0xa3c90b86,0x2907c60c,0x645e6cd6}}, // ptie, ोबर_, _lona_, _izpi,
+ {{0x6d4a8541,0x212a00dd,0xbebb0168,0xe5a3802e}}, // tofa, _tmbh_, shëg, дичи,
+ {{0x68e3ecd7,0x62841f65,0x7ae28c5e,0x28c64476}}, // _nand, lcio, _saot, र्शि,
+ {{0x26c002ba,0x61e99de6,0x7ae297c9,0xa4fb35c4}}, // _ocio_, evel, _paot, ילסט,
+ {{0xf1a68c8e,0x68e390f4,0x9c7cb78b,0xe73a8098}}, // орон, _aand, _dača, _теб_,
+ {{0x2907ecd8,0xd8388503,0x628406a5,0x80ca016f}}, // _bona_, dača_, icio, स्थे,
+ {{0x68e3c089,0x29c28125,0x2918ecd9,0x26c00118}}, // _cand, rða_, óra_, _acio_,
+ {{0x68e3ecda,0x2907ecdb,0x7ae28074,0x6f1d5b9e}}, // _dand, _dona_, _taot, thsc,
+ {{0x660f151f,0xd8388669,0xd1388035,0x80ca0b84}}, // tyck, gača_, sną_, स्ते,
+ {{0x9c7c9807,0x68e3ecdc,0xfa3680d5,0x645e0102}}, // _zača, _fand, _براد, _azpi,
+ {{0x32d50028,0x62861412,0x660f016d,0x29078041}}, // _ấy_, _afko, ryck, _gona_,
+ {{0xc04f9a84,0x7d08813c,0xd838842b,0x9c83816b}}, // _ці_, _mods, bača_, _účes,
+ {{0x68e3d74d,0xd83880fe,0x6aa081f8,0x398082d6}}, // _zand, cača_, namf, _kòs_,
+ {{0x68e3ecdd,0x29078418,0xbebb0a53,0x290105e7}}, // [7750] _yand, _yona_, biër, tjha_,
+ {{0x6284349a,0x68e3ecde,0x26d96cdf,0x98ac8110}}, // acio, _xand, leso_, rodė_,
+ {{0x974300ce,0x3cfe81fe,0x61e9ece0,0x657ba551}}, // šćuj, लाने_, zvel, _suuh,
+ {{0x62846ce1,0x7d1a8bc5,0x63b8a35d,0x9c7cc573}}, // ccio, _alts, duvn, _rača,
+ {{0xfe7083f8,0x672d3462,0x291e84bf,0x2369008e}}, // نده_, _mmaj, khta_, mmaj_,
+ {{0xd8388024,0x039582bc,0x7d088079,0xb4d582f1}}, // zača_, прия, _cods, िजे_,
+ {{0x8ccd000f,0xeb99aa46,0x7ed792dc,0x629d0359}}, // द्यो, щий_, _وزرا, _keso,
+ {{0x11d980f7,0x27320353,0x629d00e5,0x61e9cc05}}, // موعة_, kšna_, _jeso, tvel,
+ {{0xdb1c4b7e,0xd83882a5,0x629d236f,0x29078915}}, // ntrá, vača_, _meso, _pona_,
+ {{0x2ba58029,0x68e3bdd5,0x7d08a171,0x9c7c9c67}}, // pēc_, _qand, _gods, _tača,
+ {{0xd838842b,0x6f1b94df,0x777c01df,0x61e99c77}}, // tača_, _kluc, _xurx, svel,
+ {{0x68e383c3,0xe73a284f,0x3ce58082,0xe6460e8e}}, // _wand, сем_, _halv_, земп,
+ {{0xe9ce9439,0xd838803a,0x2907ece2,0x645e009a}}, // _як_, rača_, _tona_, _szpi,
+ {{0x291ebfe5,0x81c100ab,0xb4c802f1,0x6f1bc994}}, // chta_, ংবা_, ोजे_, _lluc,
+ {{0x629d6ce3,0xd83882a5,0xbebb1699,0x26d902c4}}, // _beso, pača_, riër, beso_,
+ {{0x26d9160a,0x98a5802e,0x629d3003,0x26c000d2}}, // ceso_, bilă_, _ceso, _ucio_,
+ {{0x28c2901b,0x61ed803a,0x25a982a5,0xf1b9ece4}}, // [7760] _शामि, _šalj, šalj_, nuš_,
+ {{0x672d369f,0x62843227,0x78a1a25b,0xdb1c023e}}, // _zmaj, scio, nalv, strà,
+ {{0x629d0980,0x6f1b8037,0xa5bb4aa6,0x6f09dc60}}, // _feso, _bluc, nzón, _boec,
+ {{0x444404b7,0x629d079f,0x78a1dcc4,0x3f8707d0}}, // _ix_, _geso, halv, ínua_,
+ {{0x6f029770,0x7d08890f,0x5fdda0d5,0x50bd81a2}}, // rjoc, _pods, _फैसल, ्भाष,
+ {{0x3ea2092e,0xa06a046e,0x78a18144,0xdb0ab45e}}, // hakt_, _гага_, jalv, ntfö,
+ {{0xe7b680ab,0x7d08ab09,0x3dc6ece5,0xdb0701d6}}, // _জন্য, _vods, stow_, vujú,
+ {{0xdee3ece6,0x6120017b,0x66e3a80f,0xa03b00be}}, // _поси, _bölg, _поса, _העלפ,
+ {{0x6d4e28de,0xcad7010f,0x7ae60687,0x63b885b9}}, // koba, לוקת_, _makt, suvn,
+ {{0x672d14da,0x9c7c905e,0x78a19482,0x290a0091}}, // _smaj, _začn, galv, _joba_,
+ {{0x6d4e6ce7,0x26d96553,0x25a682d5,0xdb0700e1}}, // doba, teso_, lsol_, rujú,
+ {{0x3f8a8341,0x7ae61c1f,0x291eb87b,0x200282a5}}, // ību_, _nakt, shta_, ćki_,
+ {{0x629d30cc,0x6d4e6ce8,0xdb0700e1,0x9c7c80fe}}, // _reso, foba, pujú, _jačo,
+ {{0x6d4e156c,0x60188098,0x26d92087,0x629d6ce9}}, // goba, _моля_, seso_, _seso,
+ {{0x629d6cea,0x7ae647ea,0x26d96183,0x291ceacb}}, // _peso, _bakt, peso_, _ilva_,
+ {{0xe7f00a0d,0x7ae600f1,0x083b8496,0x60dad868}}, // घटना_, _cakt, _לעול, letm,
+ {{0x7ae60f6e,0x9f479a07,0x6d4e2f26,0x629d02d4}}, // [7770] _dakt, íní_, boba, _veso,
+ {{0x290a0854,0x629d3937,0x60da8214,0x236901a1}}, // _coba_, _weso, netm, smaj_,
+ {{0xdb1c6ceb,0xdbdc0144,0xa3e1108a,0x59ac0c28}}, // strá, báñe, _धनि_, _चिअर,
+ {{0x25a90b81,0x61e2936f,0xde1981a8,0x657f01f6}}, // _kval_, _osol, يقات_, _kuqh,
+ {{0x6f1b80f3,0x84e58073,0x63658118,0xa5098185}}, // _vluc, долж, lóng, жела_,
+ {{0x3ea21799,0xd83882d4,0x65770372,0x7ae65c9a}}, // zakt_, jačo_, _jixh, _zakt,
+ {{0x44446cec,0x9c7c9601,0x67d4204c,0x61e2ac88}}, // _xx_, _tačn, _пору, _asol,
+ {{0x68e76b5a,0x7b1581ac,0x6d4e3327,0x63a8808e}}, // _hajd, _záuj, zoba, _dvdn,
+ {{0xe29f1c86,0x78a19753,0x61200019,0xc245a45b}}, // _með_, talv, _föld, ьник,
+ {{0x80bd816f,0xdb0380f7,0x36e606a7,0xaca381bc}}, // _शाळे, isné, _जोड़ी_, _maịl,
+ {{0xf7460b26,0x3ea254c2,0x2ba780d4,0xa9c4803d}}, // дено, takt_, _गिटा, وزیو,
+ {{0x61200019,0x272200ff,0x291c97ab,0x09f981a8}}, // _zöld, _móng_, _elva_, _وفاة_,
+ {{0x6d4e1f3a,0x7ae66ced,0x3ea204fe,0x44446cee}}, // toba, _rakt, rakt_, _sx_,
+ {{0x68e71eee,0x7ae9ecef,0x7ae654bb,0x61ed6cf0}}, // _najd, ldet, _sakt, dval,
+ {{0x6d4e3724,0x290a1b2a,0x27220028,0x7ae600f1}}, // roba, _roba_, _nóng_, _pakt,
+ {{0x6d4e0cb7,0x7ae9ecf1,0xc0528039,0x8f9a8496}}, // soba, ndet, _שזה_, _ויקי,
+ {{0x69c9c1ec,0x26d20578,0x7ae60357,0xc3328039}}, // [7780] ntee, _ibyo_, _vakt, דון_,
+ {{0x7ae60393,0x27220028,0x3ead8214,0x68e701c0}}, // _wakt, _bóng_, _adet_, _cajd,
+ {{0x28cf95fb,0x7ae66cf2,0x69c98364,0x3ce706af}}, // त्यि, _takt, htee, _छोटे_,
+ {{0x7ae9bc8a,0x69c9ecf3,0x3a3f2bea,0xa25b00e7}}, // jdet, ktee, _župe_, _icôn,
+ {{0xd6e080c8,0x68e74411,0xa5da915f,0x7ae9ecf4}}, // ণালয, _fajd, تبار_, ddet,
+ {{0xa3ea901b,0x3ea0007b,0x09bc80c8,0x27320353}}, // मिल_, _leit_, _অনলা, kšno_,
+ {{0x25a6bda4,0xdb1c006a,0x63bc01fa,0x2d800bfd}}, // ssol_, rtræ, nurn, _luie_,
+ {{0xf8ce801c,0x69c9aa06,0xdb1c4660,0x8af00085}}, // _cứng_, ftee, stræ, zlən,
+ {{0xdb1c0efa,0x63bc6cf5,0x69c981b4,0x67228c53}}, // mträ, hurn, gtee, mhoj,
+ {{0x6aa403ae,0x63bc6cf6,0xdb1c05ec,0x26d21088}}, // haif, kurn, lträ, _abyo_,
+ {{0x8c3c87d9,0x2018ecf7,0x9c8383fb,0x60da8214}}, // _beğe, ári_, _účas, retm,
+ {{0xdb1c0b2f,0x63bc6cf8,0x7d1e1b39,0x69c98079}}, // nträ, durn, _alps, btee,
+ {{0x8c3c87d9,0x69c98a0f,0x673d1be9,0x61e28234}}, // _değe, ctee, rnsj, _usol,
+ {{0x61ed564f,0x26d202ec,0x63bc6cf9,0x63b5066f}}, // vval, _ebyo_, furn, trzn,
+ {{0x63bc003a,0xc60700c8,0x3ea02ca2,0x69a41513}}, // gurn, _লেখা_, _feit_, _चौबी,
+ {{0x61ed81e2,0xe2f801e5,0x61ed07b3,0xa3b68035}}, // _šali, месі_, tval, _जिन_,
+ {{0x62340134,0x80c800c8,0x68e76cfa,0x2722001c}}, // [7790] вету, রজন্, _pajd, _sóng_,
+ {{0x3ea00352,0x7ae98019,0x644501e9,0x3f871243}}, // _zeit_, zdet, _txhi, ínuo_,
+ {{0x61ed0f52,0x753540ba,0x69c98102,0xf6500019}}, // sval, mizz, ztee, _مئی_,
+ {{0x753509ba,0x19951006,0xd2500117,0x6f0d23ed}}, // lizz, _памя, انے_, _joac,
+ {{0x25a98fcf,0x2ee90867,0x68e700ee,0xa3e7063a}}, // éal_, _maaf_, _tajd, पिक_,
+ {{0x753501c1,0x2edc0996,0x2366826f,0x7bca84e1}}, // nizz, _बस्त, _ahoj_, ftfu,
+ {{0xc7c416d4,0x4fc40191,0x78a502f7,0x43758162}}, // _исти, _иста, mahv, нуит,
+ {{0x69c98364,0x23668069,0x753501e8,0x67228c53}}, // ttee, _choj_, hizz, choj,
+ {{0x7ae99d6b,0x28cf18b1,0xe1f1806b,0x26dd8247}}, // rdet, स्थि, اسب_, mewo_,
+ {{0x3ea00352,0x7ae9b119,0x26ddecfb,0x6aa46cfc}}, // _seit_, sdet, lewo_, zaif,
+ {{0x753504b7,0x69c9938e,0x637581a1,0x6f0d6cfd}}, // dizz, stee, _ušno, _boac,
+ {{0x69c9e744,0x28cf000c,0x61200061,0x501a01c6}}, // ptee, स्ति, _kölc, פורו,
+ {{0x4a9a80be,0x3ea00357,0x6b819bc8,0xdb1c0aaa}}, // _זינג, _veit_, _hulg, ntrå,
+ {{0x3ea001ec,0x2b4200b9,0xc0e2beb1,0x6b8186ae}}, // _weit_, _sjkc_, _бошк, _kulg,
+ {{0x6aa400f6,0x6b81c9cf,0x680a8085,0xf8b2825f}}, // taif, _julg, nədl, _עשה_,
+ {{0xf7d68039,0x0cdc8743,0x2ee91306,0x6289ecfe}}, // _נופש_, _यस्म, _gaaf_, nceo,
+ {{0xe9d74365,0x6df300f7,0x6aa42bbe,0x75356b4b}}, // [77a0] нкт_, _مكيا, raif, bizz,
+ {{0x7535047f,0x63bc6cff,0x7e6423a0,0xebe61383}}, // cizz, purn, _izip, _зооп,
+ {{0xf1ac0540,0xe50380c2,0x28c283eb,0x749b01c6}}, // _चिकन, लानि_, _शासि, _מייפ,
+ {{0x28cf8b3b,0xe5c6a804,0x26c23f06,0x73e32306}}, // त्ति, еско, ngko_, _союз,
+ {{0xdb1c3a53,0xceb304de,0xed5a8eef,0x127b03de}}, // rträ, ריד_, под_, _מאכע,
+ {{0x61e6003a,0x53e6817a,0xdb1c6d00,0x09d880ab}}, // _iskl, ециа, strä, তিমা,
+ {{0xd9430a94,0x99bc80c8,0x61430110,0xdb1c0106}}, // _сери, _অনেক, _сера, pträ,
+ {{0x200d0059,0x9c7ca30a,0x2b401c83,0x28c281fe}}, // çmiş_, _kačk, nnic_, _शाहि,
+ {{0x7e6401bc,0xf98f9190,0x2b404909,0x2bac8072}}, // _nzip, یبی_, inic_, _घटका,
+ {{0x2b40237a,0x9c7c931b,0x6fc4016f,0x6b81805f}}, // hnic_, _mačk, ांपू, _fulg,
+ {{0x4438e71b,0x6f0d555f,0x7535022b,0x23668069}}, // _är_, _poac, vizz, _thoj_,
+ {{0x3cfe9d01,0x78a500d2,0x29038106,0x09d880ab}}, // लावे_, zahv, öjar_, তিবা,
+ {{0x7535235a,0xd6e300ab,0xe33780be,0x657a8c14}}, // tizz, যালয, _אראפ_, _kith,
+ {{0x636580ab,0x2b40805c,0x645a816b,0x22590338}}, // róne, čicu_, _kyti, _rysk_,
+ {{0x753540ba,0x6b8184c3,0xd90e826a,0xf3670705}}, // rizz, _xulg, _نیک_, етан,
+ {{0x67d3000d,0x9c7cdbb4,0x75356b4b,0xdbdc016a}}, // nějš, _bačk, sizz, _cáña,
+ {{0x3a75196e,0x7b64160f,0x290e8041,0x680a8085}}, // [77b0] тлар, утре, _kofa_, zədl,
+ {{0x657aed01,0xb4df816f,0x98be81d0,0x52dd864a}}, // _nith, _तसे_, votě_, _मस्स,
+ {{0x7b1c82be,0x61e66d02,0x43950993,0x78a518c2}}, // _réun, _eskl, _шанс, rahv,
+ {{0xf77088ca,0x657a954c,0x8ccd0996,0xdb01803e}}, // مان_, _aith, द्रो, _ovlá,
+ {{0x6463003b,0x645a8176,0x657a8c49,0x6b8182f1}}, // štić, _ayti, _bith, _sulg,
+ {{0x7aed0dde,0x271e090f,0x6b81ed03,0x657a803d}}, // ldat, _पत्र_, _pulg, _cith,
+ {{0x657a81e4,0xdb1c0bbd,0x7aed0e8e,0xfaf181a8}}, // _dith, strå, odat, لثة_,
+ {{0x6b81d787,0xf99f0247,0xb4cd0bb8,0x657aa73d}}, // _vulg, _kwè_, रभु_, _eith,
+ {{0x657a9995,0xf99f06c0,0x645a9761,0x628988d8}}, // _fith, _jwè_, _eyti, rceo,
+ {{0xdb1c0168,0x7982d818,0x9f5f04e8,0x3f83304e}}, // turë, _duow, rytý_, _kuju_,
+ {{0x3f83063e,0x9783803d,0x7afd000b,0x2ca7b96a}}, // _juju_, دیبه, _onst, mand_,
+ {{0xa29580e8,0x657a8234,0x765b808e,0x2d9f8133}}, // _раді, _zith, _kyuy, _kwue_,
+ {{0xd00e853d,0x2b404453,0x290ea32d,0xf99f06c4}}, // ملی_, vnic_, _fofa_, _nwè_,
+ {{0x7afd29ed,0x2ca79598,0xe0d985a8,0xdbdc01fa}}, // _anst, nand_, _цвк_, _báða,
+ {{0x64a327ae,0xdb0183fb,0xfaa6096b,0xdca31509}}, // раса, _zvlá, каво, раси,
+ {{0x78a38bc5,0x2b0486b7,0x7aed009c,0x2ca7ed04}}, // _henv, वायु_, gdat, hand_,
+ {{0x7aebacf5,0xe8ea0087,0x21a610ef,0x02e20105}}, // [77c0] _magt, ммад_, тизм, _पसीन,
+ {{0x7afd0bd6,0x31230196,0x2ca7b962,0x7aeba67f}}, // _enst, адуг, jand_, _lagt,
+ {{0xb4663fe7,0x657abc69,0x3f8300d2,0x25ad9f6c}}, // _школ, _rith, _cuju_, _avel_,
+ {{0x7aeb9066,0x9c7c9408,0x28c60af3,0x657abaa0}}, // _nagt, _tačk, र्जि, _sith,
+ {{0x776982a3,0x54360416,0x67d3001b,0x657a8387}}, // _dhex, _سرگر, vějš, _pith,
+ {{0xd9461e7d,0x2ca7ed05,0x61466d06,0x61e40c53}}, // _реги, gand_, _рега, mwil,
+ {{0xc7a32596,0x9c7c8668,0x8fa6373a,0x7bce5743}}, // _виск, _jači, _шаве, ntbu,
+ {{0xdd92806b,0x657a8051,0x645a850f,0x9f4a8722}}, // _نور_, _with, _vyti, _urbà_,
+ {{0x78a3ed07,0xe737e2ab,0xd8389b39,0x9966bbae}}, // _benv, _рет_, mači_, ктол,
+ {{0xd1050a74,0xd838811f,0xd83a81e5,0x8af00085}}, // रायण_, lači_, дэм_, rməy,
+ {{0x61edc24d,0x94069ef7,0x2d9205ee,0x61e46d08}}, // _šalt, _جواه, _atye_, hwil,
+ {{0xb8f989a3,0x78a385f8,0xd838a944,0x65950087}}, // _डॉ_, _eenv, nači_, ламу,
+ {{0xa3ea83eb,0x61e400f3,0xa3de809a,0x290ee639}}, // मिक_, jwil, दौर_, _tofa_,
+ {{0xc27b8f60,0x78a3832b,0x2d84804f,0xba3b0cfa}}, // _חרדי, _genv, _mume_, _beïn,
+ {{0x69c0ed09,0x2d84ed0a,0xd8389502,0x46b48697}}, // lume, _lume_, kači_, ुलाह,
+ {{0x28c60076,0xc8c63852,0x3ea4813c,0xd1389f80}}, // र्घि, र्घट, _nemt_, lią_,
+ {{0x9c7c8eef,0x2d84802e,0x60f913cd,0x7aed1b25}}, // [77d0] _dači, _nume_, нная_, rdat,
+ {{0x7bda803b,0x7aed3e4b,0xdce8801b,0xdbde8118}}, // _optu, sdat, _vidě, _líña,
+ {{0x7aed0186,0x76b9ed0b,0xba3b03b2,0x69cd64e3}}, // pdat, елер_, _geïn, stae,
+ {{0x9c13819d,0xd838811f,0x63ba00e1,0x9c7c85f3}}, // _bọnk, gači_, átne, _gači,
+ {{0x2d84abcb,0x3cfe826c,0x7bda80eb,0xd1388110}}, // _cume_, _bntv_, _aptu, kią_,
+ {{0x9c7ced0c,0x8c45bcb3,0x7aebe8f9,0x69c0ed0d}}, // _zači, леле, _sagt, dume,
+ {{0x7aeb97ef,0x2d8481df,0x31340009,0x78a380e7}}, // _pagt, _eume_, _терр, _renv,
+ {{0x7b1587a3,0x2d848b2c,0x6d551d02,0xd838826c}}, // _náut, _fume_, hoza, cači_,
+ {{0xa3e3800f,0x2ca7c168,0x78b2007b,0x61200106}}, // _पैर_, pand_, _ákvö, _möln,
+ {{0x7aeb9a69,0x6009968a,0x80e100ab,0x41058198}}, // _wagt, еном_, নাক্, узов,
+ {{0x29110a2b,0xd05c0201,0xec158154,0x3ea61229}}, // _moza_, _barə, _حواد, линг,
+ {{0xc9f587bd,0x51f5804e,0x69c0c100,0x61e4002f}}, // _مستع, _مستر, bume, ywil,
+ {{0x6d439627,0x69c08918,0x2d8400e7,0xd1388035}}, // onna, cume, ême_, bią_,
+ {{0x9c7c807d,0x2722009f,0xd83881dd,0x26cf016d}}, // _sači, _dóna_, zači_, _ögon_,
+ {{0x2ca594d1,0x68e1d503,0x39898dfa,0x9c7c8110}}, // _meld_, meld, _hús_, _pači,
+ {{0x28cf8eb4,0xa3b6809a,0xba3b009f,0x7d1c1c67}}, // त्रि, _जिस_, _veïn, skrs,
+ {{0xd838803b,0x6724220e,0xb1460652,0x626600a9}}, // [77e0] vači_, _ilij, гнал, уваа,
+ {{0x28cf0128,0xdcfb812b,0x68e1b6b2,0x2d848088}}, // स्लि, _otuđ, neld, _rume_,
+ {{0x9c7c81e2,0x67246d0e,0x69c0b931,0x9b460eca}}, // _tači, _klij, zume, _مندو,
+ {{0x68e1d033,0x6d43ed0f,0x69c0c8a9,0x78a880c3}}, // held, enna, yume, zadv,
+ {{0x6724120e,0xd838803b,0x363680f7,0xdd8f80f7}}, // _mlij, rači_, دراس, كون_,
+ {{0xd838811f,0x69c0a4d3,0x9c7c8bda,0xb5fd82d4}}, // sači_, vume, _mačv, _izšl,
+ {{0xd37aad55,0xd838817f,0xa0672b59,0x7ae0a254}}, // нчи_, pači_, _бара_, temt,
+ {{0x6d4391cd,0xd3788da4,0x5b148703,0x79862e5e}}, // anna, _opće_, рмит, _kukw,
+ {{0x79860010,0x6aa6008d,0x6d550533,0xdbde8118}}, // _jukw, _mekf, yoza, _víña,
+ {{0x2ca5905d,0x69c0b060,0x67245797,0x68e1d033}}, // _geld_, rume, _alij, geld,
+ {{0x67240613,0x6d5a84b7,0x6ac689a7,0xd1388d42}}, // _blij, _ikta, _مقام, rią_,
+ {{0x68ee4954,0x78a8c3e8,0xd1388110,0x9c7cabea}}, // _labd, sadv, sią_, _bačv,
+ {{0x6282ecbf,0x672400c3,0x6f1d5778,0x395e890d}}, // _igoo, _dlij, rksc, plts_,
+ {{0x672421ab,0x7e7d81ac,0xceb40085,0x25ec81ab}}, // _elij, _úspe, _edə_, _आईनी_,
+ {{0x6d550019,0x291140e6,0xa3ea92c6,0xaca3019d}}, // roza, _roza_, मिट_, _ngụg,
+ {{0x290c803a,0x57fb8051,0x798652e3,0xab6203bf}}, // ljda_, _טלוו, _bukw, şünc,
+ {{0x291149da,0xaca3019d,0x6d5a8d77,0x6d488390}}, // [77f0] _poza_, _agụg, _okta, _ojda,
+ {{0x68ee02c1,0x291eed10,0xb2740cec,0x657e0122}}, // _cabd, nkta_, слуш, _giph,
+ {{0x72d529c9,0xa2d00a16,0x29110b67,0x1a65003d}}, // _конф, _डॉक्, _voza_, _چیزی_,
+ {{0x539a0051,0x6d5a81cd,0xfaff020f,0xef1f0457}}, // _שירו, _akta, _unë_, nkü_,
+ {{0xdbde87ca,0x6605884b,0xd838abea,0x636580e1}}, // _síða, рпла, maču_, fónn,
+ {{0x5bb5d9a2,0x68ee0079,0x273209d1,0x6282bbdf}}, // асиф, _gabd, ršni_, _agoo,
+ {{0x2ca5837a,0x68e1a5ed,0x39898118,0x61204503}}, // _veld_, veld, _sús_, _möll,
+ {{0xe9d98607,0x7c2b8d11,0x2ca5831d,0x68e19412}}, // вки_, _đorđ, _weld_, weld,
+ {{0x13de00c8,0x63a4807d,0x7aef0229,0x13068009}}, // ডিয়, ćina, _mact, рный_,
+ {{0xe8f68e02,0x9c7c80fe,0x672402ce,0x7d7980d5}}, // аль_, _račv, _slij, _شمار_,
+ {{0x6c848013,0x3eab1c33,0x68e18e20,0xd706060a}}, // _المم, fact_, reld, азни,
+ {{0x7bc3bc50,0x398980f7,0x539a8039,0x25a0802e}}, // munu, _tús_, _ייעו, ţile_,
+ {{0x7b7491fb,0x7bc3d71a,0x776d0079,0xa9a5996e}}, // _اطلا, lunu, _dhax, _тилд,
+ {{0x59a9108a,0x78a7000d,0xdca30cae,0x4a44a1d2}}, // _चौधर, _nejv, жари, _унів,
+ {{0xbb5601a8,0x68ee0162,0x2336161a,0x79860326}}, // لنسب, _rabd, ахар, _sukw,
+ {{0x7aef6d11,0x958611c7,0xf7720117,0x68ee045f}}, // _cact, _клие, _بات_, _sabd,
+
+ {{0x7bc3a916,0x67f004be,0x09b580be,0x645e0035}}, // [7800] hunu, kójá, יפּט_, _wypi,
+ {{0x3f87b6c8,0x9c7c9f27,0x68ee1fd7,0x201a0168}}, // _kunu_, _začu, _qabd, typi_,
+ {{0x4ea6d1ff,0x2ba5000f,0x78a756a6,0x3f879c67}}, // арла, गीदा, _dejv, _junu_,
+ {{0x7bc3dcca,0xe80686ce,0x7a2c0036,0xa6e2007b}}, // dunu, षमता_, _côté, áðhe,
+ {{0xcd06831f,0x55069dc7,0x1a9b00be,0x68ee0326}}, // ички, ичка, _ביכע, _tabd,
+ {{0x21a302a4,0x7ae44926,0x7e6984a8,0x636580e1}}, // _фирм, leit, _dzep, fóno,
+ {{0x7bc3ed12,0x66e69ad8,0x237f026f,0xb226807b}}, // gunu, _кова, ňuje_, kvæð,
+ {{0xb0248142,0x7ae46d13,0x3f9a0198,0xdb1c13de}}, // _ngườ, neit, mppu_, drrë,
+ {{0x9c7c836f,0x69c423ed,0x7bc3838a,0xacea8187}}, // _raču, nuie, aunu, _имаа_,
+ {{0x3f878182,0x9c7c803b,0x7bde120c,0x7ae42d8e}}, // _bunu_, _saču, _oppu, heit,
+ {{0x7ae46d14,0x776281df,0x61eb809f,0x291eed15}}, // keit, olox, _esgl, rkta_,
+ {{0x7ae41f5e,0xe29a8ff0,0xdc3b81c6,0x6124a5d9}}, // jeit, _рад_, _בעבר, _dòla,
+ {{0x7bde22c9,0xd8388024,0x6b88d55f,0x9f658019}}, // _appu, vaču_, _hudg, ítés_,
+ {{0x80c22c4f,0x61200201,0x2d9943e8,0x7c2b87c0}}, // _लागे, _bölm, rpse_, _ömrü,
+ {{0x7ae453bc,0x06bc80c8,0x7aef05dc,0x80aa1055}}, // feit, _ইউনি, _pact, टरने,
+ {{0x6d58ed16,0x6b88a79e,0x69dd026c,0x7ae4011b}}, // hova, _mudg, _ppse, geit,
+ {{0x69c41220,0x6d58a842,0xd838812b,0x673d0d86}}, // [7810] guie, kova, raču_, misj,
+ {{0x2b498289,0x6d5885b9,0x7bc3807e,0x3ea901c6}}, // čaci_, jova, yunu, _heat_,
+ {{0x7ae42b29,0x6d47003a,0x7e698019,0x3ea900ee}}, // beit, mnja, _szep, _keat_,
+ {{0x926b0196,0x7ae40548,0x14c9816f,0xef19822b}}, // _арга_, ceit, _राहण, _biża_,
+ {{0x6aad09ca,0x69c40087,0x6d5884e8,0x7e60810c}}, // maaf, cuie, fova, _cymp,
+ {{0x3ea90af9,0x6365ed17,0x7bc3ce5b,0xfce58171}}, // _leat_, tóno, tunu, соко,
+ {{0xd7c9003d,0x628681d6,0x25640338,0x2d810af8}}, // _حوزه_, ľkon, följ_, _mihe_,
+ {{0x7bc385b2,0x68e50009,0x6aad26e8,0x7f5b805d}}, // runu, lehd, naaf, _ukuq,
+ {{0x6d589807,0x7bc3d044,0x65949ac9,0x3d948c9d}}, // bova, sunu, _мату, _митр,
+ {{0x7bc3e940,0x6d588efc,0x3f87811f,0x7ae402af}}, // punu, cova, _punu_, zeit,
+ {{0x6d470289,0xbb858013,0x673d026c,0x141981a8}}, // dnja, _الصي, fisj, _حياة_,
+ {{0x7ae404c3,0x2d810198,0x643a83de,0x3f8780fe}}, // xeit, _aihe_, _געענ, _vunu_,
+ {{0x7ae46d18,0xcb128039,0xaa088591,0x6aad0079}}, // veit, _מלא_, _نزول_, daaf,
+ {{0x2d9e003e,0x409580f7,0x7ae427f9,0x395c8267}}, // ďte_, _الغر, weit, _gkvs_,
+ {{0x612004b8,0x7ae45ad3,0x61e9ed19,0xab2a197b}}, // _följ, teit, lwel, гова_,
+ {{0xe7ec95bc,0x69c46d1a,0x6aad2525,0x935a186e}}, // _जनता_, tuie, gaaf, ургу_,
+ {{0x7ae46d1b,0x61e9d2b2,0x6d47005c,0x81d58264}}, // [7820] reit, nwel, bnja, হিক_,
+ {{0x7ae46d1c,0x6d470024,0x2d810578,0xd00a1182}}, // seit, cnja, _gihe_, лезе_,
+ {{0x7ae44f25,0x61e9ed08,0xa2e60c9b,0x6d5e019d}}, // peit, hwel, _лонд, _ikpa,
+ {{0x61e9c206,0xd3788d26,0x8cdac1c6,0x290300c3}}, // kwel, _opća_, प्नो, _enja_,
+ {{0x69c41e09,0x9c7c8353,0x612101bc,0x25640338}}, // quie, _načr, _ịlek, jölk_,
+ {{0x40ab1e29,0x256981a8,0x6f161a14,0xef1982a6}}, // _مخفی_, túla_, _loyc, _viża_,
+ {{0x6f046d1d,0x63a4805c,0x661d009a,0x6d5e0870}}, // _onic, ćino, zysk, _mkpa,
+ {{0x070c85e8,0x6d58ed1e,0x6d4702a5,0x4900001b}}, // _सचिव_, sova, znja, राको_,
+ {{0x6d588f20,0x5e4a9541,0x28cf23e6,0x442080eb}}, // pova, _спам_, स्कि, ņi_,
+ {{0x3f8a10e1,0x6f042d2d,0x6722ed1f,0x644f8035}}, // _kubu_, _anic, lkoj, _ście,
+ {{0xa3e70d38,0x6d4704c4,0xb5fd85b9,0xf1b980e1}}, // _मैं_, vnja, _vyše, vyše_,
+ {{0x0dcb1ccf,0x6d5e0870,0x95cb028b,0x6aad0079}}, // луги_, _akpa, луга_, xaaf,
+ {{0xe287853d,0x673d4a9c,0x61e98234,0xdb0a8118}}, // _اطمی, risj, cwel, nsfí,
+ {{0x92c500ab,0x3039803d,0x673d00e8,0x3f9801a9}}, // _এসে_, اتیک_, sisj, _otru_,
+ {{0xc2eb80ab,0x67228009,0x6aad004f,0x4034d0f6}}, // কারি_, kkoj, taaf, _фейс,
+ {{0x6d470fda,0x1fce00ab,0x69c0ac5b,0xc29919b8}}, // snja, রবাস, orme, рках_,
+ {{0x8af00201,0x3f8a00e4,0x6aad1b98,0xb1150951}}, // [7830] vlət, _aubu_, raaf, омаш,
+ {{0x6d4002be,0x69c08c41,0x213edc45,0xa3e880d4}}, // émat, irme, nith_, यबा_,
+ {{0xdbe30118,0x61e98234,0x61248176,0x201e8196}}, // _téña, zwel, _lòlo, nyti_,
+ {{0x95998638,0x3f8a00a4,0x7bc7580a,0x61e9acce}}, // атку_, _dubu_, muju, ywel,
+ {{0xd5bbbd73,0x7bd50cdb,0x7bc7365d,0xa2270a19}}, // усе_, ltzu, luju, _طرحه,
+ {{0x201e81e2,0xa1942485,0x0395a3e7,0x61ef07f1}}, // kyti_, _харч, ория, _iscl,
+ {{0xeb99d9dc,0xdb1c160a,0x251c0158,0x213eb52a}}, // ший_, ctró, רווא, dith_,
+ {{0x201e81e2,0x8af00201,0x7d17002a,0xb0658009}}, // dyti_, llər, _loxs, hdää,
+ {{0x6b82e89d,0x7bc76d20,0x61e99699,0xe61380f7}}, // _diog, huju, uwel, _عشر_,
+ {{0x8af00201,0x61e9949d,0x7bc76d21,0x6f041388}}, // nlər, rwel, kuju, _snic,
+ {{0xa0698e17,0xfbdd83b7,0x18698abe,0x61e9ed22}}, // рана_, _महिम, рани_, swel,
+ {{0x81e000ab,0x6b82eac3,0x2b490088,0xdddb80c3}}, // থিত_, _giog, jnac_, _zguš,
+ {{0x92678077,0x8af00201,0x7d1714cf,0x20d5804a}}, // _ادام, klər, _boxs, _дійс,
+ {{0x53dd800c,0x6b82826c,0xfbdd8519,0x2bc69b7e}}, // _महाश, _ziog, _महाम, रूवा,
+ {{0x48ee835a,0xa87980be,0x672989a4,0x8af00085}}, // _असतो_, _דאַר, _olej, dlər,
+ {{0xe29a03a7,0x798382af,0x3f8a2bea,0xab660221}}, // раа_, _hinw, _rubu_, івал,
+ {{0x6aab8065,0x8af00085,0x79838a63,0x3f9800c3}}, // [7840] _megf, flər, _kinw, _stru_,
+ {{0x6729b7d7,0x6aab8019,0x798390e4,0xd91007d2}}, // _alej, _legf, _jinw, ریز_,
+ {{0x4fea0dc0,0x61ef1621,0x6d5c1ee5,0xdd998176}}, // рмен_, _escl, mora, _atň_,
+ {{0x9e669383,0xd6d980e8,0x2c17001b,0xadc30091}}, // овед, йті_, नहरू_, _afẹf,
+ {{0x672299ff,0x672981c0,0x8af00085,0x6b82851e}}, // rkoj, _dlej, blər, _riog,
+ {{0x412a0c8e,0xdb1c07e2,0x7af60110,0x07a61133}}, // _того_, ctrò, ldyt, _магн,
+ {{0xdcfa80eb,0x6b82847f,0xdfd25b66,0x6729ed23}}, // _citā, _piog, _تيز_, _flej,
+ {{0x6d5c1044,0x81bd00ab,0x213e8428,0x798b83f7}}, // hora, _আহত_, with_, _bugw,
+ {{0x7bc70668,0x69d63a92,0xaca4046d,0xb0658198}}, // zuju, ntye, _atọw, ydää,
+ {{0x6d5c3e45,0x201e81e2,0xdddb825b,0xdce880eb}}, // jora, tyti_, _uguš, _vidē,
+ {{0xe51f808e,0x28cf0128,0x213e9995,0xf0930039}}, // _भवति_, स्टि, rith_, ונת_,
+ {{0x201e8110,0x8af00201,0x798381ec,0x3f848326}}, // ryti_, zlər, _einw, _jimu_,
+ {{0x7bc7059c,0xceb38039,0x6d5c6d24,0x8af00085}}, // wuju, ויר_, fora, ylər,
+ {{0x6d5c463d,0x7bc725f3,0x291845b9,0x61e284be}}, // gora, tuju, _nora_, _ipol,
+ {{0x68e8ed25,0x8af00085,0x44256d26,0x389b81c6}}, // medd, vlər, ál_, _דיונ,
+ {{0x7bc769aa,0x68e8ed27,0x61fd025b,0x7bd50102}}, // ruju, ledd, _prsl, rtzu,
+ {{0x8c3c8179,0x2cac8355,0x29186d28,0x9487002e}}, // [7850] _deği, _oedd_, _bora_, _мынд,
+ {{0x25fe858c,0x6d5c4793,0x68e8cd09,0xc8671c79}}, // लिनी_, cora, nedd, отеи,
+ {{0x8af00086,0x6729ed29,0x29184941,0xddc40654}}, // rlər, _plej, _dora_, _iziš,
+ {{0x61e2ed2a,0xd3668077,0xf7708872,0x8af00085}}, // _opol, _سه_, نان_, slər,
+ {{0xd2468307,0x29180931,0x7de78013,0xc33200be}}, // _أن_, _fora_, تسام, _טוט_,
+ {{0xd0068307,0x636583a2,0x291816c9,0x3eafc56c}}, // _كل_, móni, _gora_, sagt_,
+ {{0x61ed6d2b,0x2bc6801b,0x61e28d88,0x6365b4cd}}, // lwal, रूला, _apol, lóni,
+ {{0x130980e8,0x7e7d826f,0xc8788214,0x6d5c07c6}}, // сний_, _úspo, ceği_, zora,
+ {{0x61ed1175,0x636585e4,0x68f55162,0x7b1c8036}}, // nwal, nóni, _kazd, _réut,
+ {{0x68f521a9,0x68e88365,0x3ead8fb0,0x2cb13135}}, // _jazd, gedd, _heet_, jazd_,
+ {{0xb4e78076,0x6d5c1429,0x25a90812,0x61ed242a}}, // यजी_, vora, _awal_, hwal,
+ {{0x61ed55d6,0x6d5c08c4,0xd5b800eb,0xbebb0168}}, // kwal, wora, ņām_, dhës,
+ {{0x68e8a223,0x2d85ed2c,0x7ae98074,0x6365807b}}, // bedd, _kile_, meet, jóni,
+ {{0x61ed52fa,0x3ead88ae,0x7ae9cc1a,0x6365816a}}, // dwal, _leet_, leet, dóni,
+ {{0xf0928039,0x6d5c6d2d,0x69c9bf18,0xe1ff04be}}, // _אנו_, rora, luee, _aró_,
+ {{0x6d5c4a24,0x63658511,0xa0269e71,0x2d85ed2e}}, // sora, fóni, stöö, _lile_,
+ {{0x6d5c11d6,0x26d20578,0x29186532,0x61fb83b2}}, // [7860] pora, _icyo_, _pora_, nvul,
+ {{0x3f848010,0x26df8135,0x0e5702f6,0x29180df6}}, // _simu_, _abuo_, _משפט_, _qora_,
+ {{0x7ae998d0,0xe9ff00ff,0x29186d2f,0x2cac804a}}, // keet, _tiếc_, _vora_, _redd_,
+ {{0x2d85ed30,0x68e8b16b,0x63658118,0x7bc1026c}}, // _aile_, zedd, bóni, šluc,
+ {{0x3ead81e0,0x7ae9ed31,0xaad50beb,0x63658118}}, // _deet_, deet, _डॉटक, cóni,
+ {{0x2b918028,0x68f513ec,0x6d4a9984,0x644f809a}}, // _bác_, _gazd, unfa, _ścia,
+ {{0x61e28259,0x2b918142,0x3f84822e,0x6d4a9d31}}, // _spol, _các_, _timu_, rnfa,
+ {{0x2d85832f,0xa3ad85b3,0x2907a1cb,0xb998835f}}, // _eile_, _कबर_, _anna_, ових_,
+ {{0x2d85862d,0x68f5017b,0x05769e91,0x68e8ed32}}, // _file_, _yazd, _فائد, tedd,
+ {{0x7d1a016d,0x3a3704de,0x859b8039,0xfe45815b}}, // ötsl, פרים_, _לשמו, _تکمی,
+ {{0x7d088022,0x69c46d33,0x7ae9a256,0xbebb0168}}, // _inds, hrie, beet, dhër,
+ {{0x2d85ed34,0x69c43b52,0x68e8ed35,0x290781e8}}, // _zile_, krie, sedd, _enna_,
+ {{0xc3338039,0x7d1aa42d,0x68e885ee,0x61e2ed36}}, // _אוף_, _kots, pedd, _upol,
+ {{0x14df8cce,0xc16f835f,0x69c43aea,0xbebb00f1}}, // प्पण, _її_, drie, thës,
+ {{0x7d1ae465,0x63a90067,0x2b918028,0x61ed2fe2}}, // _mots, ćeno, _xác_, wwal,
+ {{0x69c400ed,0x61ed6069,0x6b860ad4,0x63659e00}}, // frie, twal, _cikg, tóni,
+ {{0x69c4564f,0x239580eb,0x6b866d37,0x2907ed38}}, // [7870] grie, _māja_, _dikg, _ynna_,
+ {{0x6365960a,0x7bca8418,0x61ed6d39,0x98b90071}}, // róni, kufu, rwal, ілет_,
+ {{0x68f51601,0x29070459,0x61ed2693,0x69c400c6}}, // _vazd, ına_, swal, arie,
+ {{0x2d8584be,0x7bc38024,0x63658e15,0x2b91801c}}, // _sile_, vrnu, póni, _rác_,
+ {{0x2a6690af,0xa3cf835a,0x6e228065,0x7d1a8417}}, // _nyob_, वून_, gyob, _bots,
+ {{0x3eada40d,0x85e8835f,0x7ae9ed3a,0xf99203c8}}, // _weet_, ядів_, weet, _ברך_,
+ {{0x2d85822e,0x7ae99711,0xdd918117,0xdbf78424}}, // _vile_, teet, روں_, ंटेड_,
+ {{0x69c9a6a1,0xf773815b,0x394c8039,0xfd4e81bc}}, // tuee, _ساز_, ends_, _kekọ,
+ {{0x1dd19516,0x4cb300c8,0x7d1a9be9,0x20188009}}, // _समित, _জানু, _fots, ärin_,
+ {{0x2b918104,0x672d512e,0x2d858ad0,0x7ae992e5}}, // _tác_, _alaj, _uile_, seet,
+ {{0x61200125,0x672d6d3b,0x6f1b86c0,0x69c401ac}}, // _tölv, _blaj, _kouc, zrie,
+ {{0xe28e8fbb,0x98ad9010,0x26cf8288,0xf8dd03ca}}, // _ја_, ının_, nggo_, न्तय,
+ {{0x6f1ba1bf,0xee37054c,0x61200019,0x799d0069}}, // _mouc, дня_, _költ, _ntsw,
+ {{0x684592b2,0x69c45b48,0x6f1b9243,0x290780e8}}, // _онла, vrie, _louc, _unna_,
+ {{0x3cf0809a,0xeb971073,0x23695400,0x69c46d3c}}, // _इससे_, дис_, flaj_, wrie,
+ {{0x6d439066,0x23690e72,0x69c40367,0x61ed8110}}, // hina, glaj_, trie, _šaly,
+ {{0x63a4856f,0x798700f3,0x2a6681c0,0xf2d280be}}, // [7880] ćini, _bijw, _xyob_, כען_,
+ {{0x69c68353,0x69c44a2b,0x672d0042,0xfd4e81bc}}, // škeg, rrie, _zlaj, _dekọ,
+ {{0x6f1ba246,0x23696d3d,0x7f428264,0x28dd0b04}}, // _bouc, blaj_, tioq, न्धि,
+ {{0x69c41edd,0x7d1a8074,0x6fc5816f,0x9f5d00e1}}, // prie, _sots, _विनं, _prvé_,
+ {{0x7d1ad397,0xdd8f8c3b,0x9f658019,0x80ad00ab}}, // _pots, لون_, ítás_, _চার্,
+ {{0x6d43b3e1,0x7bcaed3e,0xb0dd52d5,0x6f09bee9}}, // gina, tufu, न्दग, _enec,
+ {{0xfbcf80a0,0x7d1a81a1,0x395eed3f,0x6d4e4538}}, // لتي_, _vots, vots_, inba,
+ {{0x1b7780f7,0x7bcaed40,0x6600808e,0xba3b1412}}, // وصية_, rufu, _trmk, _geïs,
+ {{0x6d43ed41,0x7bd89f3a,0x3ea6ed42,0x9663932a}}, // bina, stvu, mbot_, _акте,
+ {{0xb5fd85b9,0x3ea68428,0x6120007b,0x7d08806a}}, // _myšl, lbot_, _tölu, _unds,
+ {{0xcb6a9b10,0x672d275e,0x395e81a9,0x9982928a}}, // _даже_, _plaj, rots_, šků_,
+ {{0x6d4e6d43,0x613200ec,0x3a269bdc,0x61e66d44}}, // enba, _fælg, _омег, _opkl,
+ {{0x7ae2b496,0x61320366,0x672d18f1,0x6664047f}}, // _ibot, _kæld, _vlaj, върз,
+ {{0xb3470073,0xfd4eba48,0xdb150061,0x63538493}}, // _opçõ, _rekọ, mszé, măna,
+ {{0x61e60110,0xd30f80ff,0xfd4e81bc,0x3ea680e4}}, // _apkl, _mệnh_, _sekọ, kbot_,
+ {{0x68f88022,0x9f4c877f,0x290a0247,0x91f28105}}, // _havd, _èdè_, _anba_, _अनाज_,
+ {{0x7ae28077,0x497510fc,0x291c9619,0x6365ac6c}}, // [7890] _mbot, елас, _kova_, mónt,
+ {{0x6d43b5d7,0xeb1b835a,0x3dc680f5,0x6f1bed45}}, // xina, _फक्त_, drow_, _souc,
+ {{0xa3d600a5,0x6f1b956b,0xe610803d,0xa3e7009a}}, // _समय_, _pouc, یشه_, _मैच_,
+ {{0x6d43ed46,0x68f88168,0x41c7064a,0x291c94cb}}, // wina, _lavd, _लिपस, _lova_,
+ {{0x6d43ed47,0xd30f8028,0xfce5a8df,0x6f1bc480}}, // tina, _bệnh_, еоло, _vouc,
+ {{0x7aed467c,0x0d84835f,0x68f8807a,0x7ae29bb7}}, // leat, влін, _navd, _abot,
+ {{0x61320022,0x3ea6ed48,0x6f1b824d,0xf8b2007c}}, // _sælg, bbot_, _touc, _תשל_,
+ {{0xdb1c6d49,0x3dc6b32d,0x20030326,0x6f09ed4a}}, // strö, brow_, _irji_, _unec,
+ {{0x6d43d8a0,0xa2d08ebf,0x61200019,0x2734001c}}, // pina, _धार्, _tölt, _hãng_,
+ {{0x63a90e78,0x6132013c,0x291c88e9,0x63ba007a}}, // ćenj, _vælg, _cova_, štni,
+ {{0x291c8392,0x3f91185b,0x6d418197,0x7aed31c4}}, // _dova_, _juzu_, _imla, keat,
+ {{0xdb1c02be,0x6565008e,0x06b000ab,0x6fc0a207}}, // ntrô, _akhh, _কাহি, _विरू,
+ {{0x7aed0102,0xa6db007b,0x273400ff,0x281280d7}}, // deat, miðl, _lãng_, نویس,
+ {{0x7afb8102,0x621a807c,0x61320366,0xdd8f0290}}, // ldut, _מונק, _kæle, صوف_,
+ {{0x6d4e04fe,0x64570118,0x69c213ba,0x187580be}}, // rnba, _cxxi, _लिही, _אײַך_,
+ {{0x3ebf86cb,0x636583a8,0x291c81a1,0x23b10935}}, // _ndut_, cónt, _zova_, _जबरद,
+ {{0x6fc59d01,0xb0b6800f,0x6462009a,0x6286826f}}, // [78a0] _विडं, _आजमग, _świę, žkov,
+ {{0x25a003ec,0x20566cfd,0x3ea69916,0xb5fd816b}}, // _itil_, нтер, wbot_, _vyšl,
+ {{0x9c7c9807,0xb4ac0996,0xe29a0638,0x27e782f7}}, // _obča, खरी_, _нан_, _jpnn_,
+ {{0xbbe900d5,0x3f91011e,0x200c04b7,0x66046d4b}}, // _کریم_, _duzu_, _ġdid_, _krik,
+ {{0x3ea6b975,0x7d1e6d4c,0x0cd49242,0x69db80f1}}, // rbot_, _kops, волю, jtue,
+ {{0x7afb8118,0x3dc681c6,0x6604003d,0x2d926d4d}}, // edut, rrow_, _mrik, _muye_,
+ {{0x26c06d4e,0x7bce6d4f,0x3f9126d2,0x3ea68362}}, // _odio_, nubu, _guzu_, pbot_,
+ {{0x26c0022e,0x291ca67f,0x68f88ec3,0x66046d50}}, // _ndio_, _sova_, _savd, _orik,
+ {{0x3202803e,0x7bce0867,0x3f896b76,0x3945ed51}}, // íky_, hubu, _riau_, fils_,
+ {{0x26c06d52,0x7bce6394,0xc05a8196,0xadb781a8}}, // _adio_, kubu, зім_, _أهلا_,
+ {{0x39960ae1,0x59c281ab,0x6604376a,0x2d920326}}, // _læs_, _शिवर, _arik, _auye_,
+ {{0xe2928013,0xd378b6c5,0x38c6846d,0xceeb01a8}}, // _إذا_, _opći_, _bírà_, أردن_,
+ {{0xae0383db,0xa3d3035a,0x3945809f,0x69db8f0c}}, // लियन_, हून_, bils_, ctue,
+ {{0x66040bc5,0x291c847f,0x3945ed53,0xd7f080f7}}, // _drik, _uova_, cils_, _نكت_,
+ {{0x81dc80c8,0x66041b02,0x63658118,0x4034917f}}, // ডিও_, _erik, cóns, _шекс,
+ {{0x66046d54,0x2b098035,0xd05c0326,0xdb07021e}}, // _frik, वाएँ_, _harɗ, nsjø,
+ {{0xf9930c2b,0x98c709b8,0x3f91011f,0x58d5249a}}, // [78b0] ابر_, فغان, _suzu_, коат,
+ {{0x7bce6d55,0x7aed4893,0x33290df6,0x6f0d136f}}, // bubu, seat, skax_, _hnac,
+ {{0x80d88a74,0x6d476d56,0xf8b383c8,0x853c0039}}, // _माने, mija, רשע_, _מגזי,
+ {{0x23668069,0x6d418267,0xdcfe0162,0x998901d6}}, // _nkoj_, _smla, _tipă, dzať_,
+ {{0xf7708277,0x6f0d6d57,0x7ed4dc11,0x69db9a1f}}, // حال_, _mnac, ازما, xtue,
+ {{0x8c458254,0x518401a1,0xcd980039,0xfce5e519}}, // келе, _буса, עדות_, токо,
+ {{0x3945907c,0xaca301bc,0x63ad825b,0x6f0d0bfd}}, // vils_, _akịd, ćano, _onac,
+ {{0x4905035a,0x64410035,0x533283a7,0x05c9975d}}, // _होतो_, źliw, иејт, _रिपब,
+ {{0xc6928051,0x65958f04,0x44298029,0x3945c7ce}}, // _כאן_, _разу, ņa_, tils_,
+ {{0x6d418fc4,0x6f0d66bd,0x95ee001b,0x7afb8037}}, // _umla, _anac, _छनौट_, sdut,
+ {{0x69dbbfae,0x7d1e007d,0xf09204de,0x39459e9e}}, // stue, _rops, יני_, rils_,
+ {{0x7d1e1601,0x0caa0ebf,0x4427ed58,0x69dbed59}}, // _sops, कर्म, myn_, ptue,
+ {{0x693592bc,0x49bb9381,0xb6ba8039,0x7d1e6d5a}}, // _инду, _راشد_, וצרי, _pops,
+ {{0x6da31927,0x2360803b,0x7bce028d,0xb4ac0540}}, // рира, čije_, tubu, खरे_,
+ {{0x628f3cc0,0x4427c07e,0x6b8bed5b,0x7d1e0087}}, // _bgco, nyn_, _kigg, _vops,
+ {{0x7bce3a8f,0x6b8b822b,0x26c0111b,0x628f09ab}}, // rubu, _jigg, _udio_, _cgco,
+ {{0x6d476d5c,0xcb670c6e,0xe9da035f,0x7bce1152}}, // [78c0] bija, тате_, ька_, subu,
+ {{0x6b8ba280,0x64590063,0x7d1e0efd,0x442781b0}}, // _ligg, _świe, _uops, kyn_,
+ {{0x69c98613,0x8cd70035,0x2cb80866,0xfaff03ed}}, // hree, _बातो, mard_, _ikën_,
+ {{0x4427831d,0xe5a68cc1,0xe5c6a857,0x69c9a6a5}}, // dyn_, види, вско, kree,
+ {{0xf2d31a63,0x527502fb,0xca750a41,0xdce18176}}, // _דער_, _буру, _буры, _eklč,
+ {{0x2cb81009,0x69c9802e,0x44278114,0xb9070264}}, // nard_, dree, fyn_, _মো_,
+ {{0xd9433d65,0x61431155,0x44278428,0x6b8bcbf0}}, // _тери, _тера, gyn_, _bigg,
+ {{0x236d8353,0x51f88009,0x96a200d4,0x9a86904b}}, // glej_, ению_, _क्लॉ, купл,
+ {{0x7de584c1,0x63a9003a,0x25ec809a,0x87558098}}, // _مسلم, ćeni, _आईटी_, _сърц,
+ {{0x68fc6347,0x6f0d3dbc,0x25a6a26e,0x4427831d}}, // _jard, _snac, mpol_, byn_,
+ {{0x44388082,0x68fc4b55,0x63a48025,0x6b8bc8c8}}, // _år_, _mard, ćins, _figg,
+ {{0xfaa6a028,0x68fc6968,0x6d470ae9,0x69c9a023}}, // _радо, _lard, wija, bree,
+ {{0xa2d790a1,0x628681ac,0x6602c9e9,0x2d8c9ffc}}, // _यात्, ľkos, nvok, _kide_,
+ {{0x2d8ced5d,0x6b8bcab0,0x6143c184,0x02cb001b}}, // _jide_, _zigg, _кеча, िल्न,
+ {{0xa5cc800f,0x25698061,0x2d8ced5e,0xb8208264}}, // _समझौ, múlt_, _mide_, _ফেরত_,
+ {{0x2d8ce3f4,0x68fc058f,0x6d475f83,0x656e0083}}, // _lide_, _aard, sija, albh,
+ {{0x68fc6d5f,0x3a753b5f,0x3eb9103d,0x27ea0101}}, // [78d0] _bard, улар, mast_, _apbn_,
+ {{0x44f527b9,0x44278198,0x3da40081,0x660284dc}}, // _спос, yyn_, _тръб, dvok,
+ {{0x68fc5128,0x6d456d60,0x5184096f,0x628f01e8}}, // _dard, _imha, рута, _tgco,
+ {{0x3eb96d61,0x2d8c83d3,0x68fc0362,0x6602143c}}, // nast_, _aide_, _eard, _šoki,
+ {{0x4427ce06,0x2d8c9beb,0x2d9c007a,0x6720d464}}, // wyn_, _bide_, _čvek_, _domj,
+ {{0x4427bf5d,0x68fc24d5,0x6b8ba1fb,0x7afd15c1}}, // tyn_, _gard, _sigg, _hast,
+ {{0xe2998a8e,0x7afd6d62,0x2d8c8091,0x6b8ba127}}, // най_, _kast, _dide_, _pigg,
+ {{0x4427c285,0x69c9a6a1,0x3eb91796,0x2b9880e7}}, // ryn_, tree, jast_, _déc_,
+ {{0x3eb92e6d,0x3a29208b,0x7afd2872,0xc3340039}}, // dast_, nyap_, _mast, יוק_,
+ {{0x68fc1613,0x69c98a62,0x2d8cc154,0x629b847f}}, // _xard, rree, _gide_, scuo,
+ {{0x6b8bde84,0x777b81c0,0x5eab00ab,0x2cb86d63}}, // _tigg, _khux, _কাজে, vard_,
+ {{0x6d4511cd,0x69c9a32d,0x752181ec,0x3eb92a4c}}, // _amha, pree, _holz, gast_,
+ {{0x2d4d0267,0xc0e58323,0xd49180ff,0x7e6d018e}}, // džeb_, _болк, _làm_, _cyap,
+ {{0x7afd0665,0x3f8dd548,0x78b51b40,0x6ed60006}}, // _aast, _lieu_, _nezv, _भावु,
+ {{0x66028824,0x68fc593c,0x3eb906ae,0x7e6d3328}}, // zvok, _rard, bast_, _eyap,
+ {{0x68fc3340,0x7afd05d8,0x765b8085,0x2cb80036}}, // _sard, _cast, _oxuy, sard_,
+ {{0x68fc277d,0x9f5e83d3,0x7afd6a7c,0xa3b380dc}}, // [78e0] _pard, _été_, _dast, जीत_,
+ {{0x63ad803a,0x68fc1b37,0x98be8087,0x7afd27d3}}, // ćanj, _qard, mită_, _east,
+ {{0x68fc6d64,0x8caa1094,0xddcd003b,0x78b5002e}}, // _vard, _ज्यो, _izaš, _dezv,
+ {{0x5fc90540,0x25a68b17,0x2d8c8039,0x3fb90019}}, // ांजल, tpol_, _side_, ئپنگ_,
+ {{0x2d8c82ba,0x3f8ded65,0x7ae400dd,0xe4e68221}}, // _pide_, _dieu_, mfit, гійн,
+ {{0x7ae45d43,0x25a6ed66,0x7521907c,0x61461dc7}}, // lfit, rpol_, _colz, _сега,
+ {{0xc61c80c8,0x0b8a891d,0x25a6dce7,0x75219502}}, // _দেখা_, нсии_, spol_, _dolz,
+ {{0x3cf08063,0x7afd002a,0x436780a9,0xfbac1993}}, // _इसके_, _xast, лајн_, टीएम,
+ {{0x64a6e8f4,0x5f11835a,0x3eb92938,0xdca69bdc}}, // _база, णार्_, vast_, _бази,
+ {{0x200780c3,0xc23701c6,0xa6db008b,0x98be8493}}, // _brni_, טרפו_, miði, dită_,
+ {{0x04938307,0x7c938013,0x20078067,0x3eb90357}}, // _المح, _المص, _crni_, tast_,
+ {{0x6d4a8c93,0x7bc1017f,0x6d8b0609,0x798e038a}}, // lifa, šluk, _eżal, _libw,
+ {{0x3eb96d67,0x7afd2397,0xdb0a8aa2,0x88cc00ab}}, // rast_, _rast, dsfø, র্যক,
+ {{0x6d4aed68,0xb4c104c5,0x260a06a7,0xdb01d3d4}}, // nifa, ुले_, ामजी_, _atlè,
+ {{0x8ca4000f,0x3eb95a4b,0x3cec801b,0xd00f982d}}, // कड़ो, past_, _आउने_, _уф_,
+ {{0x6d4a8010,0xa2a00fd5,0xe2f80558,0x98be8162}}, // hifa, गुल्, лесі_, bită_,
+ {{0x7afd0181,0x6d4a914c,0x2b496d69,0x98be8162}}, // [78f0] _vast, kifa, riac_, cită_,
+ {{0x2b4901ac,0x6d4a804f,0xf1c801a8,0x3f8d8129}}, // siac_, jifa, اولى_, _sieu_,
+ {{0x200757bb,0x63adabea,0x6d4abcc0,0x3a2902c4}}, // íni_, ćank, difa, syap_,
+ {{0x672f009a,0x7521ceca,0x7afd01a8,0xa2d08072}}, // nkcj, _solz, _uast, _धाग्,
+ {{0xfad6093f,0xeef800be,0x6d4a80dd,0x7521c9d8}}, // _דורך_, טמאר_, fifa, _polz,
+ {{0x69cd0355,0x85300300,0x6d4a9267,0x78ba8824}}, // mrae, _kuɗa, gifa, jatv,
+ {{0x3f8d80ff,0xc2938019,0x98be8162,0x2911010c}}, // _tieu_, میاب, zită_, _inza_,
+ {{0x1309a45b,0x3cfe8c93,0x798e0deb,0x6d4a8901}}, // тний_, _eatv_, _zibw, aifa,
+ {{0x65158019,0x20078b67,0x6d4a8b5e,0x798e0a03}}, // _پوائ, _srni_, bifa, _yibw,
+ {{0x2360825b,0x39459383,0x29016d6a,0x60098604}}, // čija_, рног, ndha_, вном_,
+ {{0xe73a0381,0xa2f591c7,0xb4c38bbc,0x2eff81ec}}, // вен_, _впеч, ्ली_, _kauf_,
+ {{0x6609ed6b,0xc9f58019,0x9f5d00e1,0x6d55623d}}, // _krek, _نستع, _prvá_, enza,
+ {{0x3f8224b2,0x2911016a,0x63a501a8,0x6d8b022b}}, // rmku_, _onza_, _athn, _eżam,
+ {{0x64590063,0x69cd4e06,0x98be8087,0x6abb81ec}}, // _świa, drae, rită_, lauf,
+ {{0x29012eb7,0x8883803d,0xdb01c28b,0xd46a1860}}, // ddha_, _دیدن, _atlé, тиве_,
+ {{0x97c4803d,0x6d550511,0x6d4ab41a,0x9f5d016b}}, // _اتوم, anza, zifa, _trvá_,
+ {{0x63a55e99,0xe0da0fe7,0xb1461908,0xe297105c}}, // [7900] _ethn, ква_, анал, рау_,
+ {{0x399b8073,0xf09280be,0x78bb00e7,0x6abbed6c}}, // _mês_, ַנד_, _œuvr, hauf,
+ {{0xa2e6087e,0x28dd800d,0x6abb81ec,0x6609ed6d}}, // _конд, _मानि, kauf, _arek,
+ {{0x66e68a08,0xdee6c249,0x7c2e82af,0x66098357}}, // рода, роди, _übri, _brek,
+ {{0x6b840b3c,0x22a681d0,0x3ea9816a,0x6d4a9d56}}, // mmig, níků_, ñata_, tifa,
+ {{0x6609ed6e,0x6b8401b4,0x80dd6d6f,0x03c680ae}}, // _drek, lmig, _पाये, асим,
+ {{0x6d4a8cf6,0x6d8b0609,0xdb150061,0xb6060824}}, // rifa, _bżaj, lszá, ljšč,
+ {{0x6609ed70,0x6b840876,0x6d4a805d,0x3ce9811f}}, // _frek, nmig, sifa, đava_,
+ {{0x24f68d31,0x3f980010,0xe5a3bbae,0x6609cd5a}}, // _вчер, _huru_, _мити, _grek,
+ {{0x3f980cd6,0x6b8401ec,0x637e8168,0xf8b303de}}, // _kuru_, hmig, këng, ֿשר_,
+ {{0x3f986d71,0xb4d287e6,0x6d489c66,0x6f00ed72}}, // _juru_, वली_, _imda, _kamc,
+ {{0x09e6a53e,0x6f02d862,0x91e68abe,0x141781a8}}, // _водн, ndoc, _воде, _خيمة_,
+ {{0x51f692c8,0x6f0086ec,0x2bc48133,0x291181a8}}, // _خسار, _mamc, _ịche_, ánaí_,
+ {{0xa3b3a539,0x69c0c0de,0x58d381a1,0x3d198054}}, // जीव_, msme, _дошт, पाये_,
+ {{0x3f986d73,0xb17b83de,0x397b83de,0xf99f0176}}, // _nuru_, _שטור, _שטונ, _erèl_,
+ {{0x69cd0197,0x394c82e6,0x9e670019,0x6b8402d5}}, // trae, lids_, _پابن, gmig,
+ {{0x645e4480,0x6d48dd97,0x69c0ed74,0x63ad8668}}, // [7910] _expi, _omda, nsme, ćani,
+ {{0x3f986d75,0x2eff80e7,0xa0a71501,0x69cd0114}}, // _buru_, _sauf_, ршал, rrae,
+ {{0x3f980369,0x69cd6798,0x29016d76,0x63a90140}}, // _curu_, srae, rdha_, ćens,
+ {{0x6609bfd7,0x6f00be5b,0x6d488f70,0x69c080eb}}, // _prek, _camc, _amda, ksme,
+ {{0x6605aaed,0x64a5ae15,0xdef8a1f6,0x69c0869f}}, // спла, бала, рых_, jsme,
+ {{0xa3be023c,0xee388221,0xdb1c0511,0xb4c381a2}}, // ुंच_, шні_, buró, ्ले_,
+ {{0xadc38028,0x237d81c0,0xdb1c0118,0x6abb8192}}, // _trắn, _phwj_, curó, tauf,
+ {{0x66098de8,0x6d48e350,0x612d9de9,0xcc5801c6}}, // _trek, _emda, _túlk, יסוק_,
+ {{0x6abb81ec,0x69c0a3ea,0x798501c0,0x13068198}}, // rauf, gsme, omhw, сный_,
+ {{0x9be7835f,0x23e780e8,0x27fc80b9,0x6f008088}}, // _відк, _відв, _bsvn_, _zamc,
+ {{0x612d8019,0xe8df0028,0x40498c6c,0x237d8069}}, // _júli, _ngọc_, упно_, _thwj_,
+ {{0x22958013,0x7d0183a6,0xaa9581a8,0xe73a0d15}}, // _الإس, _hals, _الإث, уем_,
+ {{0xa3da035a,0x528580f7,0xf99f0176,0x2b4d866f}}, // डून_, _الشك, _brèm_, miec_,
+ {{0xdce39234,0xdca30e17,0x7bd56d77,0x394ccc46}}, // konč, зари, fuzu, cids_,
+ {{0x61fd00dd,0x7d01ed78,0xd4918129,0x20188198}}, // _assl, _mals, _bài_, ärit_,
+ {{0x2b4da1a9,0x3f980870,0xdce3856f,0x2b9c001b}}, // niec_, _ruru_, donč, _víc_,
+ {{0x200a003b,0x6132013c,0x3f986d79,0x3b070081}}, // [7920] _srbi_, _fæll, _suru_, щето_,
+ {{0xdb150065,0x3f980133,0xb4c3800d,0x6b843241}}, // rszá, _puru_, ्लो_, smig,
+ {{0x6d4e62b8,0xdb150019,0x6f008bcf,0xa4fb0e82}}, // miba, sszá, _pamc, _גליט,
+ {{0x1e868012,0xee3a8eef,0x7d01ed7a,0x98b8061c}}, // блем, лне_, _aals, ızın_,
+ {{0x7d01ed7b,0x81e700ab,0x239581a9,0x3f9801bc}}, // _bals, ভিউ_, _māju_, _wuru_,
+ {{0xb4d28f22,0x6d4e4040,0x3f983c50,0x69c08186}}, // वले_, niba, _turu_, vsme,
+ {{0x69d60216,0x7d01ed7c,0x4adc9107,0xa13703de}}, // luye, _dals, _यादव, _פאלק_,
+ {{0xe44f8013,0x6d4e0ddd,0xe73781bb,0x7af6007b}}, // عضو_, hiba, _лес_, neyt,
+ {{0xa92a8071,0x394c83a6,0x69c0ed7d,0x69d6189d}}, // гіне_, tids_, usme, nuye,
+ {{0x6d4e2ebc,0xa3d30074,0x69c0afb1,0xce379101}}, // jiba, हूँ_, rsme, _האמת_,
+ {{0x9c7c9fe7,0x3f9203c3,0x394c8106,0x5eaa00ab}}, // _obči, _biyu_, rids_, _কয়ে,
+ {{0xe29a9697,0x61e412a5,0x973d0d26,0x3f8686c0}}, // _сад_, ktil, opći, nmou_,
+ {{0x8b658013,0x518681e2,0xe66704fa,0x637e80f1}}, // _بالم, _лука, стно, rënd,
+ {{0x61e423b1,0x7bd56d7e,0x6d4e5c0d,0x394a090d}}, // dtil, tuzu, giba, _nmbs_,
+ {{0xa3d30b85,0x6b9a8722,0x0ed89513,0xf2c70048}}, // हूं_, _jutg, _डांड, йсан,
+ {{0xe616007a,0xdb038106,0x387c80c3,0x7bd56d7f}}, // žišč, ssnö, _izvr_, ruzu,
+ {{0x388e82bf,0x6d4e0b5e,0x7bd500b4,0x25ac016b}}, // [7930] lər_, biba, suzu, čilý_,
+ {{0x660d661a,0xaa7b026f,0x2be20740,0x27058129}}, // _irak, _obýv, _पहचा, _ồn_,
+ {{0xd0110277,0xa295102a,0xaa950dc0,0x61e46415}}, // بلا_, _мані, _минч, atil,
+ {{0x660d6d80,0x69d60234,0x61e46d81,0xb4c382f1}}, // _krak, buye, btil, ्ल्_,
+ {{0x5fc50076,0x2cb200f2,0x388e8201,0x7d01d01e}}, // _लटकल, _ändå_, hər_, _pals,
+ {{0xc3010a49,0x78ad8289,0x2b4d809a,0x6d588a35}}, // _একটি_, _đavo, wiec_, gnva,
+ {{0x7d01bd3b,0xeab180f7,0xe3b28065,0xcda9803d}}, // _vals, وعة_, ورٹ_, _چهره_,
+ {{0x388e8201,0x29032420,0x6d4e5ca4,0x6d5898c7}}, // dər_, _haja_, ziba, anva,
+ {{0x7d01eba8,0x799980dd,0x6d4e01d4,0x612d8061}}, // _tals, _quww, yiba, _múlv,
+ {{0x388e8201,0x95578277,0x4fe98b69,0x47c596df}}, // fər_, _اخرا, амон_, обов,
+ {{0x290303b7,0x660d6d6d,0x388e8201,0x25a94457}}, // _maja_, _arak, gər_, _atal_,
+ {{0x7c2e016b,0x61e43b53,0x7bc38502,0x6d4e6d82}}, // vybr, ytil, msnu, wiba,
+ {{0xc3328039,0x5fc59305,0x3ead25ca,0x683f846d}}, // _קוד_, _विकल, ñeta_, _bádé,
+ {{0x388e8201,0x290301ec,0x7ae9c555,0x2cba0144}}, // bər_, _naja_, lfet, _qepd_,
+ {{0x69cb00ab,0x6d4e6d83,0x660d6d84,0xe7b580f7}}, // ágen, riba, _erak, _بمعد,
+ {{0x660d20e4,0xe2971092,0x61e44530,0x4911800d}}, // _frak, жат_, ttil, ताको_,
+ {{0x29033b7a,0x2d930cf8,0x7af6008b,0x6b5e81a9}}, // [7940] _baja_, _fixe_, reyt, nīga,
+ {{0x38cb19f4,0x2905829b,0x29030264,0xb17b016d}}, // لامی_, ndla_, _caja_, _spår,
+ {{0x660d1784,0xb4d6000f,0x22400061,0x69d60fa3}}, // _zrak, हली_, szik_, suye,
+ {{0x6b9ac3b6,0x644800e1,0x69d60036,0x9411811c}}, // _rutg, údiu, puye, _üzə_,
+ {{0x388e8085,0x29033b7a,0x6f046d85,0xee838162}}, // zər_, _faja_, _maic, мышо,
+ {{0x69c4489a,0x442e8123,0x61320366,0x3f868118}}, // msie, tyf_, _mælk, smou_,
+ {{0x7ae9ad08,0x69c4270c,0x6d5886b1,0x6f160176}}, // ffet, lsie, rnva, _onyc,
+ {{0x4420803a,0x442e879f,0x78bc0223,0x31c691d2}}, // ći_, ryf_, _herv, осов,
+ {{0xb99784d9,0x316e80b9,0x2903453f,0x78bc6d86}}, // овых_, _pkfz_, _yaja_, _kerv,
+ {{0x2ee0026c,0x6b9a8362,0x69c44543,0x388e811c}}, // _acif_, _tutg, isie, tər_,
+ {{0x80d000c8,0xc05a90ac,0x25a91ee0,0x2005007b}}, // স্থ্, ріп_, _stal_, _álit_,
+ {{0x69c46d87,0x752888ae,0x6722d559,0x7bd88f8e}}, // ksie, _hodz, njoj, muvu,
+ {{0x7528d619,0x76429149,0x95d80009,0x7bd8a49b}}, // _kodz, nzoy, одят_, luvu,
+ {{0x69c4368e,0x2369811a,0x3af50129,0x660d6d88}}, // dsie, čaje_, _ấp_, _vrak,
+ {{0x2903059e,0x69c41c28,0x6f0401e4,0x4fb381a8}}, // _raja_, esie, _faic, _قصير,
+ {{0x290313b8,0xc2990fbf,0x31a083ec,0x7528d6bd}}, // _saja_, сках_, _kòz_, _lodz,
+ {{0x78bc6d89,0x660d6d8a,0x7f4d21e7,0xa2dc20e9}}, // [7950] _berv, _urak, _imaq, _फार्,
+ {{0x7bd8b883,0x48ab02cb,0xc17800e8,0x7528ae63}}, // kuvu, ртам_, оїх_, _nodz,
+ {{0x29030074,0x28dd824c,0x5f9509b5,0xbf73846d}}, // _vaja_, _मासि, чиет, _apàṣ,
+ {{0x29031eb3,0x80dd02f1,0x67228168,0x753a811b}}, // _waja_, _पावे, gjoj, _altz,
+ {{0xe4f781ce,0x5f1c11c6,0x64a5814c,0x78bc29c3}}, // ंजलि_, यात्_, пала, _ferv,
+ {{0x6b828019,0x64a58ba7,0x75288035,0x7d05336e}}, // _ahog, чака, _codz, _jahs,
+ {{0x9c7c8353,0x6b828635,0x0edb016f,0x7d056d8b}}, // _obču, _bhog, _भांड, _mahs,
+ {{0x28dd853e,0x6b828083,0xdce70dcc,0x31a082d6}}, // _माहि, _chog, dojč, _bòz_,
+ {{0xe4e78160,0x7ae9939a,0xda0e0054,0x6f045a4a}}, // _міжн, rfet, सियत_, _raic,
+ {{0x75288063,0xa2cb04c5,0x7ae9bac8,0x6b5e81a9}}, // _godz, तृत्, sfet, rīga,
+ {{0x3ea08b7c,0x6b828083,0x63ad82ce,0x1869ca52}}, // žite_, _fhog, ćans, сани_,
+ {{0x3d1985b3,0x290384c4,0x2576007b,0xe3b08a47}}, // पावे_, žja_, mæli_, _شرم_,
+ {{0x7d056667,0x61330214,0x6b89a26d,0x799d1ab3}}, // _bahs, _kılı, mmeg, _nusw,
+ {{0xb4d632dd,0xcfe98077,0x200e826c,0x6b89ed8c}}, // हले_, _هفته_, _frfi_, lmeg,
+ {{0x799d02af,0x7d050be6,0x6f0401e4,0x60c1ed8d}}, // _ausw, _dahs, _taic, malm,
+ {{0x3f670098,0x799d46c7,0xe29a07b6,0x69c422ab}}, // _нищо_, _busw, саа_, tsie,
+ {{0x6abd072a,0x78bc570d,0x26c21855,0xa8238065}}, // [7960] _desf, _perv, mako_, _رکھن,
+ {{0x01bd8a49,0x26c26d8e,0x69c40412,0x6aa46d8f}}, // _আমাদ, lako_, rsie, scif,
+ {{0x78bc24de,0x75288063,0x80dd01d0,0x79950242}}, // _verv, _rodz, _पारे, _dizw,
+ {{0x26c24b14,0xe61aad59,0x6b8980f3,0x8f9a807c}}, // nako_, йде_, jmeg, ציני,
+ {{0x78bc4123,0x75288b04,0x60c18065,0xddcb8214}}, // _terv, _podz, kalm, ğişi,
+ {{0x6d5c394e,0x6b82ed90,0x3ae80065,0x7bd8ed91}}, // nnra, _shog, _ابھی_, tuvu,
+ {{0x06c700c8,0x8af00201,0x673b86a5,0x60c18019}}, // ষ্টি, ynəl, _fluj, dalm,
+ {{0xdce7003b,0xd90d803d,0x320f80e4,0x26c26d92}}, // vojč, یین_, _brgy_, jako_,
+ {{0xa3d6023c,0x26c26d93,0xadc3801c,0x2576803d}}, // _समझ_, dako_, _trạn, _فهرس,
+ {{0xe1ef880b,0x60c1ed94,0xd7fa80c4,0x6d5c1d14}}, // اسي_, galm, бум_, jnra,
+ {{0x64438063,0x7d051083,0x6b82925b,0x06e380ab}}, // czni, _rahs, _thog, _নোটি,
+ {{0x7d0541f6,0x6c7b80be,0x0ea706af,0x26c24b1a}}, // _sahs, _פראד, कुंड, gako_,
+ {{0x61330214,0x7d050122,0x6abd428b,0x27340129}}, // _yılı, _pahs, _resf, _mãnh_,
+ {{0xe6b9941b,0x2734001c,0x5ba707a1,0x26c26d95}}, // _उज्ज, _lãnh_, зраз, aako_,
+ {{0x0fd700ab,0x7e6401df,0x799d126a,0xdb18807b}}, // _সন্ধ, _exip, _susw, rsvæ,
+ {{0xb7db81c6,0x44316d96,0x7d055f52,0x26c20f3e}}, // _הקהי, ryz_, _wahs, cako_,
+ {{0x69bf80d4,0xd0470085,0x433b80be,0x7d056d97}}, // [7970] लीपी, əməs, צעמב, _tahs,
+ {{0x673b8087,0x6729808e,0xdb188338,0x55bc025f}}, // _sluj, _soej, lsvä, _המזו,
+ {{0x67210a74,0x2fc71ab3,0x389b80be,0x673b8722}}, // यापक_, éng_, ציאנ, _pluj,
+ {{0xf7709a37,0xdb18816d,0x0675abca,0x6d8b022b}}, // هان_, nsvä, муля, _eżat,
+ {{0x28dd83b7,0xc1788084,0x2fc70129,0x7bc73c43}}, // _मालि, klė_, ũng_, lsju,
+ {{0x25de016f,0x26c26d98,0x7ae2957a,0x80d8a3bd}}, // कंती_, zako_, _acot, _मागे,
+ {{0xdb018feb,0xbbe88013,0x60c184a2,0x26c20365}}, // _atlá, كريم_, valm, yako_,
+ {{0x7aed2417,0x3b0900b9,0xeeb98081,0x7c2a02df}}, // nfat, ndaq_, _флаш_, _àfre,
+ {{0x26cd807a,0x26c212e5,0x7aed0114,0x6b89ed99}}, // _ideo_, vako_, ifat, rmeg,
+ {{0x26c2400f,0x9f4181a8,0xe125804a,0x3ea903ed}}, // wako_, ithí_, ємни, _afat_,
+ {{0x26c20379,0xfbd084c0,0xf7708065,0xf99f0242}}, // tako_, _متن_, _شاہ_, _grèv_,
+ {{0xd24e95e4,0x60c1d885,0x201102a5,0x994f07c0}}, // انی_, salm, _mrzi_, yüş_,
+ {{0x26c26d9a,0x60c19d3f,0x68faa08e,0x7afb83e4}}, // rako_, palm, betd, meut,
+ {{0x25ad8267,0x61e9e9f2,0x26c26d9b,0x3ebf81a1}}, // _mtel_, mtel, sako_, _leut_,
+ {{0x26c26d9c,0x212a0046,0x7aed15a4,0x64a300c5}}, // pako_, _robh_, ffat, таса,
+ {{0x2907ed9d,0x7afbdc28,0xb0dd8105,0x25aded9e}}, // _hana_, neut, _माँग, _otel_,
+ {{0x61e98cc9,0x2907ed9f,0x2d858101,0x20116da0}}, // [7980] ntel, _kana_, _ohle_, _arzi_,
+ {{0x66046da1,0x2907eda2,0x23608503,0x994f080a}}, // _isik, _jana_, čiji_, rüş_,
+ {{0x2907dd37,0x351b0451,0x251b0158,0xe29a1ad8}}, // _mana_, _ווינ, _וויא, _ман_,
+ {{0x2907e90e,0x201102a5,0x25a0059e,0x48fa0b04}}, // _lana_, _drzi_, _kuil_, _उसको_,
+ {{0x7bdc1a67,0x26c00073,0x7afbeda3,0x25a002be}}, // luru, _meio_, deut, _juil_,
+ {{0x29079bb7,0xdb0a816d,0x6604004f,0x7ae2c35c}}, // _nana_, nsfö, _msik, _scot,
+ {{0x7bdc0393,0x61e9eda4,0x69cf81d0,0xa43889c7}}, // nuru, etel, ácen, язку_,
+ {{0x290791c9,0x69bf8035,0x63ba01d6,0xbebb03ed}}, // _aana_, लीभी, átny, gjël,
+ {{0x2907eda5,0x7bdc31ca,0x05768060,0x61e9eda6}}, // _bana_, huru, _قائد, gtel,
+ {{0x3ea90162,0xdb189be9,0x0d838087,0x2d858a2a}}, // _sfat_, dsvå, _алун, _ghle_,
+ {{0x29078025,0x66043129,0x7d1aeda7,0x7bdc0abf}}, // _dana_, _asik, _ints, juru,
+ {{0x81c400c8,0x7bdc5456,0xf09301c6,0x25a0051e}}, // _এমন_, duru, כנת_, _buil_,
+ {{0x61e9802e,0xdb188106,0x25a0198d,0xceb38039}}, // ctel, rsvä, _cuil_, כיר_,
+ {{0x961d80eb,0xd0109190,0x7bc7267f,0xdb0a8106}}, // _uzņe, _ملت_, tsju, gsfö,
+ {{0xdd940110,0x7bdc6da8,0x660458f2,0x9f590065}}, // тары, guru, _esik, író_,
+ {{0x2907b8f6,0x25a00635,0x7bc73fee,0xd138a337}}, // _zana_, _fuil_, rsju, lką_,
+ {{0x2907eda9,0x7d1a90f4,0x7aed0370,0x0dbb0bca}}, // [7990] _yana_, _onts, rfat, _باعث_,
+ {{0x7bdc6daa,0xd1388d42,0x7d0885b7,0x7aed00df}}, // buru, nką_, _nads, sfat,
+ {{0x3ebf83d3,0x25ad90f4,0x395eedab,0x61e9edac}}, // _peut_, _stel_, ints_, ztel,
+ {{0xf1a71094,0x7d1aedad,0x61e9b0bd,0x69cf825a}}, // _कंपन, _ants, ytel, _दिली,
+ {{0xd2468013,0x3ebf82be,0x6b8d6dae,0x31a403bf}}, // _إن_, _veut_, mmag, _göz_,
+ {{0x3878007b,0x6b8d6daf,0xdd918019,0xf99f02d6}}, // _fyrr_, lmag, ھوں_, _drèt_,
+ {{0x290780a4,0x36d5824b,0x60c50590,0x7afb8009}}, // _rana_, _повр, mahm, teut,
+ {{0x2907dc6c,0x61e98956,0x7d1ad44f,0x395ec7e3}}, // _sana_, ttel, _ents, ents_,
+ {{0x2907edb0,0x61e98cfa,0x6d55125c,0xda0880ff}}, // _pana_, utel, kiza, _cỏ_,
+ {{0x7fd585a8,0x60c502af,0x7d0880eb,0x7bdc061c}}, // німі, nahm, _gads, yuru,
+ {{0x61e9edb1,0x6d438ad0,0x2907edb2,0xdee6024f}}, // stel, mhna, _vana_, нони,
+ {{0x61e9edb3,0x290785fe,0x395ebb51,0x7bdc6db4}}, // ptel, _wana_, ants_, vuru,
+ {{0x290780a4,0xee3713bf,0xf6521c12,0x26c003a7}}, // _tana_, еня_, ائج_, _veio_,
+ {{0x7bdc0faa,0x60c509ca,0x6d555df6,0x25a006a8}}, // turu, jahm, giza, _vuil_,
+ {{0x31a40182,0x60c5036e,0x6b8d008b,0x629c81d0}}, // _söz_, dahm, fmag, ěrov,
+ {{0x28e2035a,0x7bdc4ffd,0x660438b9,0xab2722a7}}, // _पाहि, ruru, _tsik, носа_,
+ {{0x2d96042f,0x66046db5,0x64a644e0,0xe9da34ba}}, // [79a0] _прос, _usik, _папа, яка_,
+ {{0x7bdc3fae,0x6d552503,0x8cdb80d4,0x7cf38457}}, // puru, ciza, _नागो, nırı,
+ {{0x6d43dbae,0x313680be,0x7d08edb6,0x3d1d0072}}, // dhna, ענעם_, _rads, मारे_,
+ {{0x69c98364,0xf99f06c0,0x78bb0bcf,0x81cf0264}}, // ksee, _prèt_, _đuve, _শহর_,
+ {{0x6a9b0039,0x6f09810c,0x25760366,0x7cf382d0}}, // _משכנ, _daec, bælt_, kırı,
+ {{0xafe61fab,0xdd8f806b,0x6d438c5e,0xa3d40035}}, // _погл, مون_, ghna, _हटा_,
+ {{0x76968179,0x01630615,0x69c98009,0x69cb88dc}}, // _büyü, _скро, esee, _avge,
+ {{0x290a4004,0x2d9a0669,0xf99f1f1b,0x6d5564f0}}, // _haba_, _lipe_, _grès_, ziza,
+ {{0x0566acd1,0xd250803d,0x6d5543a9,0x853000fc}}, // _зван, شند_, yiza, _huɗu,
+ {{0x6d43ec40,0xda0880ff,0x290a003d,0x6602a892}}, // chna, _vỏ_, _jaba_, mwok,
+ {{0xdd8f806b,0xd138c702,0x6bd688ca,0x2360009a}}, // _اول_, rką_, _ستار, knij_,
+ {{0x290a6db7,0xd1388d42,0xda0880ff,0x2d9a0032}}, // _laba_, ską_, _tỏ_, _aipe_,
+ {{0xe1ff0118,0x6b4302f1,0x82339a27,0x68469d5e}}, // _axón_, _jõge, _مروا, _bédé,
+ {{0x236081dd,0x290a20b0,0x2d9a6db8,0x2c0d0072}}, // čiju_, _naba_, _cipe_, हिलं_,
+ {{0x99828110,0xe6df0135,0x291f001c,0x85300326}}, // škų_, _agụụ_, _đua_, _nuɗu,
+ {{0x6d555f79,0x23605a55,0x6b9c802a,0x7c258192}}, // siza, gnij_, _érgu, ähri,
+ {{0x290a0a87,0x6b8d5819,0xdb1c016d,0x684680e7}}, // [79b0] _baba_, rmag, dsrä, _fédé,
+ {{0x3eb90352,0x6f1b8706,0x290cd479,0x602a811c}}, // lbst_, _snuc, ndda_, həmm,
+ {{0xb909491d,0x3f892056,0xf99f102d,0x290a6db9}}, // _या_, _khau_, _près_, _daba_,
+ {{0xe522001b,0x69c98df6,0xcb35047f,0x7cf385b2}}, // माथि_, ysee, _ремъ, zırı,
+ {{0x1d09a134,0xa5098468,0xdd9b8009,0x22840098}}, // дели_, дела_, чше_, _бург,
+ {{0x61ed23ea,0x290a03c3,0x3f8901c0,0x063800be}}, // mtal, _gaba_, _lhau_, ענגט_,
+ {{0xf99f03d3,0x3ce90fea,0xdfd80698,0x6f0990e4}}, // _très_, _जाने_, _път_, _taec,
+ {{0xe299af8b,0x3f890028,0x23698353,0x6f1b80d2}}, // май_, _nhau_, čajo_, _unuc,
+ {{0x339280f7,0x7cf3817b,0x13068009,0x290a6dba}}, // جليز, tırı, тный_, _yaba_,
+ {{0x69c9edbb,0x2ba985fc,0x61ed4a6a,0x63a182f1}}, // rsee, _चंपा, ital, _tuln,
+ {{0x6d41c43e,0xef862139,0xf746181f,0x61ed1a4b}}, // _illa, клоп, вено, htal,
+ {{0x61ed1fcf,0xbebb00f1,0x2d9a0136,0x7e7b82f7}}, // ktal, njëh, _sipe_, _kyup,
+ {{0xcb368051,0x3f8901e9,0x69c6801b,0x61ed6dbc}}, // _שאני_, _dhau_, šker, jtal,
+ {{0xa3bf0035,0x98a2a306,0x18a281e5,0x3afc027d}}, // ुओं_, _кише, _кашм, _ập_,
+ {{0x290a614a,0x61ed1447,0x03a66dbd,0x00000000}}, // _raba_, etal, тибо, --,
+ {{0x290a243d,0xdb01816d,0x61ed6dbe,0xf8b90129}}, // _saba_, _utlä, ftal, _đũa_,
+ {{0x2d9a08b3,0xc33282f6,0xdb1c016d,0x60c39c11}}, // [79c0] _tipe_, בון_, vsrä, _kenm,
+ {{0x3ce90b85,0x290a01b4,0xe0d70eef,0x236d0088}}, // _जाये_, _qaba_, кву_, čeja_,
+ {{0x838a035f,0x40964b8d,0x60c383ec,0x290a0074}}, // _обов_, трат, _menm, _vaba_,
+ {{0x60c398e8,0xa3d7001b,0x290a1e98,0xc29402e3}}, // _lenm, _सित_, _waba_, _میزب,
+ {{0x290a31bb,0x290cedbf,0xdee3af49,0x26d202c4}}, // _taba_, ydda_, рочи, _mdyo_,
+ {{0x628613cf,0x660281bf,0x7ae40074,0xcdb70039}}, // _izko, rwok, lgit, דפסה_,
+ {{0x6d41810c,0xd47a03de,0x7e7b810c,0xd0070162}}, // _dlla, ואַל, _eyup, _иере_,
+ {{0x6d41b6bb,0xd5b2803d,0x7bca8b81,0x7ae43437}}, // _ella, _نفر_, rsfu, ngit,
+ {{0xada694b7,0x35a694b7,0xe5a6942c,0x44380114}}, // _разл, _разг, _ризи, hyr_,
+ {{0x3ea000e7,0x799c0326,0x2cac8326,0xc05a90ac}}, // _agit_, _jirw, _ufdd_, дім_,
+ {{0x60c399eb,0x3f8901c5,0x41d48f85,0x26c48392}}, // _denm, _phau_, _दिवस, _kemo_,
+ {{0x290a97aa,0x44386dc0,0x60c38fb0,0x200782f7}}, // žba_, dyr_, _eenm, _dsni_,
+ {{0x3eb90192,0xe7e481ab,0x26d20365,0x60c386c4}}, // rbst_, _कमवा_, _ddyo_, _fenm,
+ {{0xb4dd0d14,0x8e209246,0x61ed21ad,0xc693807c}}, // डले_, лiк_, vtal, יאר_,
+ {{0x3f892056,0x2d4d10d3,0x60c8839c,0x1c0281ab}}, // _thau_, džet_, madm, लबिल_,
+ {{0x61ed6dc1,0xeab30013,0x26c49bea,0x2249011e}}, // ttal, شعر_, _nemo_, tzak_,
+ {{0x43944566,0x799c2f56,0xdb019f0a,0x661603ed}}, // [79d0] ратс, _birw, _utlå, _gryk,
+ {{0x61ed0eb9,0x6d588025,0x58b80013,0x60c8af77}}, // rtal, jiva, نامج_, nadm,
+ {{0x66740077,0x6286011b,0x26c49cfe,0x61e08380}}, // _مدیر, _ezko, _bemo_, huml,
+ {{0x26c48052,0x2366803a,0x61ed62c8,0x673d00e8}}, // _cemo_, _njoj_, ptal, nksj,
+ {{0x6f0d6dc2,0x61e0d52c,0x799c1bad,0x22da025f}}, // _maac, juml, _firw, _אחרא,
+ {{0x6d5897a6,0x6b9d0637,0x236680c3,0x6f0d6dc3}}, // giva, _hisg, _ajoj_, _laac,
+ {{0x60c38247,0x29010088,0x764b0019,0x60c88144}}, // _renm, meha_, _ügyf, dadm,
+ {{0x4429803a,0x2901598e,0x60c38242,0x6b9d0197}}, // ća_, leha_, _senm, _jisg,
+ {{0x6d58a92b,0x6b9d3cdb,0x69cd0428,0x60c3edc4}}, // biva, _misg, nsae, _penm,
+ {{0x6d588025,0x6d41a66a,0x8145803d,0x9f5886c0}}, // civa, _ulla, اندن, _aprè_,
+ {{0x25b2109b,0x68e50c5e,0x6ec7897d,0x6b650196}}, // _styl_, ighd, ारपु, jėgo,
+ {{0xe1f18077,0x29cf00eb,0xe7e48105,0x8af0011c}}, // _هست_, kļa_, _कमरा_, nnət,
+ {{0xcb128051,0x44380355,0x60c38cab,0x7f3a80be}}, // _ללא_, wyr_, _tenm, גערו,
+ {{0x290102ce,0x44386dc5,0x6da31a19,0xe816073c}}, // jeha_, tyr_, сира, णिमा_,
+ {{0x62860063,0x04430554,0x8c431878,0x443e6dc6}}, // _szko, берн, бере, át_,
+ {{0x6d588025,0x66160bc5,0x3ea002c4,0x6b9d0037}}, // ziva, _tryk, _ugit_, _cisg,
+ {{0xc3328496,0x443808dc,0xdb1883ba,0xdb018580}}, // [79e0] זון_, syr_, rsvø, _culé,
+ {{0x6d58e1d6,0x88d58264,0x26c48037,0x7ae46dc7}}, // xiva, ত্রক, _semo_, sgit,
+ {{0x3ce92e06,0x6d58e34b,0x6609b328,0xcae38035}}, // _जाते_, viva, _asek, _गांड_,
+ {{0x6aad00e5,0x5276053b,0x69dbedc8,0x59d48a27}}, // ccaf, _бугу, krue, _दिलर,
+ {{0xee3a876a,0x26c4807a,0x7bce0b81,0x62860ec3}}, // энд_, _vemo_, lsbu, _uzko,
+ {{0x18a30554,0xa0a31e9d,0x27e100ee,0x6b8b85ee}}, // _гарм, _гард, buhn_, _ahgg,
+ {{0x6d58c4b2,0x26c48353,0x7bce14fb,0xec3600be}}, // riva, _temo_, nsbu, טאַר_,
+ {{0x660606cb,0x395a6966,0x20184a85,0x53349a47}}, // kwkk, lips_, _irri_, реит,
+ {{0x0d8689c7,0x6f0d02a3,0x9e668b9b,0x6d589bda}}, // _блан, _raac, _свад, piva,
+ {{0x61e090d7,0x7f4419d7,0x6f0d52e6,0x7bce6dc9}}, // ruml, _aliq, _saac, ksbu,
+ {{0x7bce1601,0x637e80f1,0x9f58b7ba,0xd8db03de}}, // jsbu, hëni, _apré_, עקטר,
+ {{0x7f442cd0,0xfaa69016,0x2ee68a53,0x7bce04dc}}, // _cliq, _садо, ngof_, dsbu,
+ {{0x66e60592,0x2d4d025b,0x3ce90074,0x6b9d6dca}}, // _कारक_, džer_, _जादे_, _risg,
+ {{0xe8949a8f,0x237900b9,0x3d0f0035,0x20186dcb}}, // _каль, _sksj_, ायें_, _orri_,
+ {{0xa3e40a27,0x7a488029,0x27f7826f,0x2d9e837f}}, // _नमः_, sūtī, čená_, _mite_,
+ {{0x2d9e8186,0x290e87ca,0xa2b54365,0x236d007a}}, // _lite_, _hafa_, рбач, čejo_,
+ {{0x290e80a4,0xc299176e,0x1dc68778,0x26cb00e5}}, // [79f0] _kafa_, тках_, रीमत, maco_,
+ {{0xf1da0fb2,0x26cb016a,0x290ea3c0,0xdb150061}}, // यंजन, laco_, _jafa_, tszó,
+ {{0x6d450201,0xa3a9858c,0x29016dcc,0x6d570372}}, // _ilha, _गंध_, reha_, _imxa,
+ {{0x7ff40013,0x273b0168,0x2d9e851e,0x290e9e1d}}, // إسلا, _bëni_, _aite_, _lafa_,
+ {{0x6cc59c0e,0x3d0a035a,0x29010b44,0x398b8aa2}}, // айла, ायचे_, peha_, løse_,
+ {{0xabd599e3,0xdfc684a3,0x6609807a,0x8cba12e0}}, // ациј, _وي_, _vsek, ्रयो,
+ {{0x2d9eedcd,0x2d99810c,0x00000000,0x00000000}}, // _dite_, _èsem_, --, --,
+ {{0x6609d885,0xb33b07d9,0x613f82df,0x2b49027d}}, // _tsek, _hiçb, _pêlo, nhac_,
+ {{0x6d450073,0xaca30032,0x290e9896,0xb1458cf3}}, // _olha, _ajọd, _bafa_, инил,
+ {{0xe9ff00ff,0x69dbded2,0x2d9e85ed,0x290e82d6}}, // _giấc_, rrue, _gite_, _cafa_,
+ {{0xa0699c79,0x8c679c68,0x290e8300,0x1869831f}}, // тана_, _стад, _dafa_, тани_,
+ {{0x7d03c23a,0x6d450300,0x61fd47a5,0x3a75c4e0}}, // lens, _alha, _opsl, _клер,
+ {{0x6d4501c0,0x290e8915,0x7d038cfa,0x93948019}}, // _blha, _fafa_, oens, _اجلا,
+ {{0x7d03edce,0x82348077,0xc0e28ca0,0x60c701d0}}, // nens, _درما, _дошк, _nejm,
+ {{0xb7b000c8,0x30798158,0x207980be,0x32190359}}, // _চট্ট, _באַנ, _באַא, _arsy_,
+ {{0x60d500b9,0x6d456dcf,0xa3d70ad5,0x200a02f7}}, // _adzm, _elha, _सिर_, _rsbi_,
+ {{0xeb971156,0x395a03a6,0x3b0718a0,0xdcfa801b}}, // [7a00] рит_, tips_, шето_, _chtě,
+ {{0x7bce01e0,0x1aee00ab,0xa63b01c6,0x777b85e7}}, // psbu, _চোখে_, _סגיר, _akux,
+ {{0x7d03edd0,0xc7968110,0x2d9eedd1,0x395a43e8}}, // dens, арды, _rite_, rips_,
+ {{0x91e614b7,0x6d5c5f84,0x2d9ebc72,0x28e21c4f}}, // _коме, lira, _site_, _पाकि,
+ {{0x7d038cf7,0xd64e8133,0x3f8db936,0x613f8123}}, // fens, mọkụ_, _dheu_, _pêll,
+ {{0x7d03d778,0x60c70088,0x764b0061,0x2b40026c}}, // gens, _gejm, _ügye, lkic_,
+ {{0x2d9ecdb6,0x0fe080c8,0x9b5898ba,0xe4ee154b}}, // _vite_, _বন্ধ, ликт_, _जानि_,
+ {{0x6d5c6dd2,0xcfb70051,0x290eafe2,0x60c7001b}}, // hira, _כללי_, _safa_, _zejm,
+ {{0x6d5c6dd3,0x7d03a26b,0x2d9eedd4,0xd5a50105}}, // kira, bens, _tite_, _गूंज,
+ {{0x6d5c1d95,0x7d03a41a,0x2d9e8087,0xa3d740f2}}, // jira, cens, _uite_, _सिल_,
+ {{0x94068077,0x61e4010a,0x13068098,0xb8ce8128}}, // _خواه, huil, _взем, _कल_,
+ {{0x644e0b67,0x6d5c0ba3,0xd49180ff,0x290ec7ef}}, // dzbi, eira, _này_, _wafa_,
+ {{0x26cb6dd5,0x224d809a,0x6d450282,0xee870fe6}}, // raco_, czek_, _plha, рыно,
+ {{0x657d8b18,0x6d5c24fc,0x97a69ad8,0x26cb041c}}, // _ësht, gira, _крил, saco_,
+ {{0xf2c710f8,0x443c8019,0x939701a8,0x26cb07b6}}, // исан, nyv_, لجرا, paco_,
+ {{0x442d003a,0x7d03a7c1,0x60c76dd6,0x4fb3936d}}, // će_, zens, _sejm, _بصور,
+ {{0x6d5c1efb,0x443f0029,0x61e46aa5,0x443c8144}}, // [7a10] bira, ņu_, guil, hyv_,
+ {{0x2b4687e2,0x7d039045,0x660d6381,0x23698805}}, // _lloc_, xens, _isak, čaji_,
+ {{0x7d03edd7,0x2d8302a5,0x69c00168,0xe81986a7}}, // vens, mlje_, _çmen, धिया_,
+ {{0x7d0384b2,0xd3668199,0x2d834292,0xdb01804a}}, // wens, _ره_, llje_, _utlø,
+ {{0x25a913b8,0x60c7269d,0x61e405ae,0x88de80ab}}, // _jual_, _tejm, cuil, ব্যক,
+ {{0x6d4aedd8,0x25a90890,0xb4d1816f,0xd4699860}}, // ghfa, _mual_, वणे_, лиле_,
+ {{0x2b46e27a,0xbebb0168,0x58d40087,0x320927d3}}, // _bloc_, njës, _фост, lway_,
+ {{0x660d4e10,0x386981a1,0x13098087,0x224d8cdb}}, // _osak, _ćar_, уний_, tzek_,
+ {{0x6d5c4a82,0x29114a05,0x7d0390b6,0x660d0c8b}}, // yira, _kaza_, pens, _nsak,
+ {{0x5d8580f7,0x39458ca0,0x661b948e,0x6d4ad377}}, // _الطل, сног, _iruk, chfa,
+ {{0xe73a044f,0x224da422,0x29116dd9,0x93278019}}, // ген_, szek_, _maza_, _پران,
+ {{0x6d5c3be0,0x32090637,0x25a909c5,0x26c90118}}, // wira, kway_, _bual_, _ceao_,
+ {{0xf1a79fab,0x25a905b4,0x9b748013,0xe28e9401}}, // _трен, _cual_, _بالص, _ха_,
+ {{0x61e40009,0x7c3c03ba,0x9f5880e5,0x32090c6b}}, // vuil, tyrr, _aprì_, dway_,
+ {{0x6d5c6dda,0x2905912e,0x8c430765,0x32091c33}}, // rira, mela_, пере, eway_,
+ {{0x290591e8,0x3ea0812b,0xaec6196e,0xce5a00d7}}, // lela_, žiti_, _убил, _مشخص_,
+ {{0x29110745,0x3209435f,0x2d8300ce,0x612d8019}}, // [7a20] _baza_, gway_, blje_, _súly,
+ {{0x2905c3b5,0xe2970d46,0x291105e4,0xeeeb001c}}, // nela_, сау_, _caza_, ượng_,
+ {{0x3ead267b,0x61e415ba,0x32093a19,0x29110326}}, // žete_, suil, away_, _daza_,
+ {{0x661b979a,0x6f0602a5,0x29058234,0x80c6001b}}, // _bruk, nekc, hela_, िरहे,
+ {{0x61e42321,0x6b8400f2,0x2905829b,0x63a88754}}, // quil, mlig, kela_, _sudn,
+ {{0x29058582,0x2911054e,0x28e20540,0xbebb03ed}}, // jela_, _gaza_, _पाटि, njër,
+ {{0x68e36ddb,0x60262597,0x7ae98192,0x661b9c00}}, // ónde, _удва, fget, _eruk,
+ {{0x6b840bfa,0x661b8448,0x1de0852a,0x7ae9a91e}}, // nlig, _fruk, _निपत, gget,
+ {{0xdb018118,0x3ce902f1,0x2905dc72,0xcb1301c6}}, // _gulí, _जाले_, fela_, ללה_,
+ {{0x3a370051,0x290584bb,0x63a88019,0x795894ed}}, // צרים_, gela_, _tudn, риор_,
+ {{0x2d83003b,0x6c868013,0x6b845bc2,0x7f5d19c5}}, // vlje_, _الام, klig, tisq,
+ {{0x6b845eb3,0x8f47160f,0x09e6ab3f,0x398f0192}}, // jlig, сход, _годн, müse_,
+ {{0x25a92670,0x2905cf67,0x7f5d05db,0x6b840f06}}, // _qual_, bela_, risq, dlig,
+ {{0x29059845,0x660d0db7,0x80de00c8,0x63a280f7}}, // cela_, _vsak, ন্ত্, _hion,
+ {{0x63a28010,0x7bda192c,0x853b84de,0x291126ef}}, // _kion, štun, נגלי, _raza_,
+ {{0x660d03c3,0x6b840f06,0x2d8306ec,0x63a2804f}}, // _tsak, glig, slje_, _jion,
+ {{0x6d488117,0x63a2eddc,0x660d0726,0x6d5a8f06}}, // [7a30] _olda, _mion, _usak, _omta,
+ {{0x394748a0,0x63a29581,0x210303b7,0x32093260}}, // óns_, _lion, रजेश_, rway_,
+ {{0xe4ee0b75,0x320900dd,0x6b84139a,0x4c950af0}}, // _जाति_, sway_, blig, _финс,
+ {{0x6d4880ad,0x2905c82e,0xc7b380be,0x3cfb801b}}, // _alda, zela_, לבע_, ल्ने_,
+ {{0xbf470324,0x628281c5,0x29110388,0x2905926f}}, // únṣe_, _nyoo, _taza_, yela_,
+ {{0x81b780c8,0xfe708fc5,0xf2b38f12,0x7d071b40}}, // _ছিল_, ردن_, ीर्घ, nejs,
+ {{0x2905c080,0x395e815d,0x7ae98c4d,0xf8ae07d2}}, // vela_, dits_, tget, _شکل_,
+ {{0x80de00c8,0xe9d99541,0x63a2808c,0x661beddd}}, // ন্দ্, аки_, _cion, _truk,
+ {{0x63a2911b,0xc9a681a0,0x661b82a0,0x7f498197}}, // _dion, овые_, _uruk, _ileq,
+ {{0x92e98a49,0x672415f5,0x26cf92e5,0x395ee8f6}}, // য়ে_, _snij, lago_, gits_,
+ {{0xa3cb01fe,0x2905b5be,0x5f060af2,0x1cb887bd}}, // रीम_, rela_, озна, _طالب_,
+ {{0x2905edde,0x26cfe96e,0x7c2a023e,0xbf8780ff}}, // sela_, nago_, _àfri, _điền_,
+ {{0x6b840e23,0x80ba8054,0x23788061,0xaa58a134}}, // vlig, _श्वे, _írja_, _лицу_,
+ {{0x2369803a,0x7afd0367,0x395ecf42,0x7e9a807c}}, // čaju_, _abst, cits_, יסרו,
+ {{0x6b840687,0x64a30be4,0x26cf9210,0xdca35f05}}, // tlig, дара, kago_, дари,
+ {{0x63a2822c,0x236915ab,0x96ba0073,0xeeeb00ff}}, // _xion, gnaj_, _туку_, ưỡng_,
+ {{0x6b840f06,0x26cf9ffc,0x628281c5,0xa3cb066f}}, // [7a40] rlig, dago_, _xyoo, रीब_,
+ {{0x7afd02c4,0x6d43cc84,0xf99f0176,0x0903274b}}, // _ebst, jkna, _krèy_, мпун,
+ {{0xb9020063,0x6b84257d,0xcc8a0077,0xc6a6a659}}, // _नए_, plig, _خنده_, орли,
+ {{0x34b490f7,0xe61a917e,0x1e8680a9,0x4eba80ab}}, // ुर्द, иде_, олем, ুল্ল,
+ {{0x63a29d15,0xdd8f80d7,0x929d866f,0x2b4718ad}}, // _rion, چون_, wełn, _înca_,
+ {{0xdd8f806b,0x6d4e6ddf,0xf99f0176,0x26cf838a}}, // نون_, nhba, _orèy_, aago_,
+ {{0xf746932a,0x26cfb537,0x22400019,0x395ea651}}, // _дево, bago_, lyik_, vits_,
+ {{0x7d070933,0x3ce082d0,0x657c0122,0x5a349577}}, // zejs, şiv_, horh, знит,
+ {{0x63a2822e,0x395e8557,0x6f1d91b9,0x6d4e5c52}}, // _vion, tits_, _ósca, khba,
+ {{0x3f86e567,0x929d809a,0x321d831d,0xdb08816a}}, // llou_, pełn, _drwy_, _mudé,
+ {{0xe81d08fd,0x63a2cf3f,0x6d4e00f7,0x213809d1}}, // फिया_, _tion, dhba, _morh_,
+ {{0x80ba885d,0x2b5f8162,0x201c8824,0xa3df852a}}, // _श्रे, ciuc_, _vrvi_, _तित_,
+ {{0x3326ec8d,0xfd5784be,0x657c0aa2,0x079b83c8}}, // _inox_, _adaṣ, forh, יסטל,
+ {{0x2369009a,0xb4d5016f,0x22400637,0x26cf8cdb}}, // wnaj_, हणे_, dyik_, zago_,
+ {{0x7d076de0,0x26cfab6b,0x93bc8162,0x3f86cd18}}, // rejs, yago_, _drăg, klou_,
+ {{0x67198327,0x27f395d8,0x26cf9fa4,0x00000000}}, // _नोंक_, žaní_, xago_, --,
+ {{0xaf4b07d2,0x26cfb2b5,0x2369016b,0x7d070035}}, // [7a50] _مشکل_, vago_, rnaj_, pejs,
+ {{0x3495138c,0x6d4e02af,0xcd00009a,0xdb088144}}, // _напр, chba, ęści_, _dudé,
+ {{0x26cf8637,0xb8d30540,0x213809d1,0x2fc700d7}}, // tago_, _ऑल_, _dorh_, èng_,
+ {{0xaca301bc,0x4758804a,0xbf58804a,0xa3cb0b99}}, // _chụd, орія_, орії_, रीण_,
+ {{0x7bc8831d,0x26cf9c88,0xdd1481ac,0x59e22bef}}, // _awdu, rago_, _dĺžk, _पियर,
+ {{0x7aed58ce,0xd0a28091,0x2ea81d40,0xa50986e9}}, // lgat, _gẹgẹ, _गल्त, сека_,
+ {{0x26cfede1,0x6d43a32f,0x2b4b0748,0x26c2009a}}, // pago_, rkna, _klcc_, ybko_,
+ {{0x7aed4450,0x3f868722,0x05dc0697,0x3f9901a1}}, // ngat, clou_, _बिलब, jmsu_,
+ {{0xa3cb0744,0x5d558db4,0x02028162,0x2b5f8493}}, // रीत_, _екат, езын, riuc_,
+ {{0x3ea90013,0x2eb61305,0xa3ab02f1,0xe3b39ef7}}, // _agat_, ूर्त, खदा_, _قرص_,
+ {{0xf3f080f7,0x48fd085d,0x657c6de2,0xb33b1a1f}}, // _لأن_, र्यो_, vorh, _niço,
+ {{0x321d831d,0x3f6a06f9,0x23fa81c6,0x2d810234}}, // _trwy_, симо_, _לפעמ, _akhe_,
+ {{0x657c10c9,0xf1a70105,0x7afb8c7e,0xdb0184e8}}, // torh, _कंगन, mfut, _gulá,
+ {{0x7aed6de3,0x61e9ede4,0xb226807b,0x7bda0110}}, // egat, muel, stæð, štum,
+ {{0xf746144c,0xa066a29c,0x18668dbd,0x93bc8087}}, // _неко, _наша_, _наши_, _grăd,
+ {{0x25a581cd,0x23b18e14,0x7aed364f,0x8b95658c}}, // _lill_, _máj_, ggat, друч,
+ {{0xe2973e11,0x7afb83e4,0x6b650196,0x00000000}}, // [7a60] зат_, ifut, jėgu, --,
+ {{0xf8bd9513,0x7aed011b,0x9f4600d7,0xe9d70a4c}}, // ोरिय, agat, dulé_, яку_,
+ {{0x61e9ede5,0x2252009a,0x31660267,0xdb240019}}, // huel, szyk_, đoz_, _ہوگی,
+ {{0x61e9ede6,0x25a58219,0x4422a329,0x2d87ede7}}, // kuel, _aill_, _čk_, elne_,
+ {{0x539b010f,0xd6cf8154,0x61e98216,0xceb304de}}, // _ליטו, اقه_, juel, גיד_,
+ {{0xf9878117,0x25a58635,0x6604314f,0x61e9d4be}}, // _سب_, _cill_, _mpik, duel,
+ {{0x69dd8038,0xb4ac8105,0x200c047f,0x60260190}}, // ásen, _गली_, _èdi_, _эдва,
+ {{0xe3af8077,0x2d87b0e6,0x25ada79a,0x98a30956}}, // درو_, alne_, _fuel_, _чисе,
+ {{0x6d5e6de8,0x64430214,0xdcf88c22,0xdb1c3b51}}, // _ompa, _üniv, lovč, rprè,
+ {{0x25fb85b3,0x63a60687,0x7aed0019,0x6d5e00ee}}, // ोबरी_, _likn, zgat, _nmpa,
+ {{0xc05a81e5,0x66046de9,0xdcf8842b,0x764280fc}}, // сіп_, _apik, novč, lyoy,
+ {{0x6d5e0db7,0x69d66dea,0x6f1606e7,0x61e9923d}}, // _ampa, ksye, _cayc, buel,
+ {{0x61e985b4,0x764280a4,0x5fce0768,0x62860711}}, // cuel, nyoy, _हौसल, _nyko,
+ {{0x61e42e2f,0xdcf889d1,0x753a8102,0x7aed0362}}, // dril, kovč, _jotz, wgat,
+ {{0x0133990c,0x66046deb,0xd4040c40,0x1dd900d4}}, // _سعود, _epik, няти, _भटकत,
+ {{0x7aed0087,0x3ce90074,0x3ea916fb,0x6286067f}}, // ugat, _जाके_, _ugat_, _byko,
+ {{0x7aed420d,0x7bd50019,0xd7f88087,0x63a64556}}, // [7a70] rgat, sszu, ltă_, _dikn,
+ {{0x7aed243a,0x48ab1986,0x69c481b9,0xa41c86a7}}, // sgat, стам_, _ħiel, पटाप_,
+ {{0xd7f88012,0x60ce0144,0x61e40100,0x2ef599b8}}, // ntă_, _debm, aril, дзер,
+ {{0xf77396a5,0xf1dd809a,0x25a5edec,0xadc4046d}}, // راض_, _मिलन, _sill_, _ayẹw,
+ {{0x25ada4c1,0x61e9eded,0x764282d5,0x7f4d0197}}, // _quel_, xuel, gyoy, _mlaq,
+ {{0x61e985a4,0xc2120039,0x61e2007a,0x6b828032}}, // vuel, _שהם_, šolc, _akog,
+ {{0x25a5d425,0xe9ff001c,0x753ab11d,0x3b9583a7}}, // _vill_, _khắc_, _dotz, мјит,
+ {{0x25a59964,0xb2259509,0x7528de88,0x7f4d0609}}, // _will_, дмил, _endz, _nlaq,
+ {{0x25a584b5,0x6d47473f,0xf77089a7,0x44210115}}, // _till_, kkja, _صاف_, _lrh_,
+ {{0x6b82edee,0xdb1c2d1a,0x48f2001b,0x753a8cdb}}, // _ekog, rpré, ुभयो_, _gotz,
+ {{0xdee60dc0,0xdb1c07e2,0x65638073,0x6b4e846d}}, // мони, spré, minh, _dùgb,
+ {{0xd90f8117,0x6563edef,0x501a0039,0x6d5e09ca}}, // ئیں_, linh, קורו, _smpa,
+ {{0x61e989b2,0x236da19c,0xa2948221,0xe8ee817c}}, // quel, mnej_, _заці, _ил_,
+ {{0x236d9988,0x6b89a0a5,0x61e40850,0x6563d193}}, // lnej_, lleg, vril, ninh,
+ {{0x6da30abe,0x628f8024,0xe5a31155,0xd7f88087}}, // тира, škoć, тири, ctă_,
+ {{0x6b898357,0xe9ff0028,0x65639217,0x236dedf0}}, // nleg, _chắc_, hinh, nnej_,
+ {{0xe1f981e2,0x6563cddc,0x6f19831d,0x6d5e0359}}, // [7a80] ntų_, kinh, ddwc, _tmpa,
+ {{0x6d5e0397,0x69d602e7,0x62860a21,0x65638187}}, // _umpa, rsye, _vyko, jinh,
+ {{0x62860063,0x65638073,0xa00a05ff,0x6b89b6bd}}, // _wyko, dinh, تقال_, kleg,
+ {{0x61e4003a,0xe1f981e2,0xdcf88088,0x6443d5f8}}, // pril, ktų_, rovč, dyni,
+ {{0x236da1a9,0x7d17029a,0x6b89821e,0x60ce01c0}}, // dnej_, _yaxs, dleg, _tebm,
+ {{0x6b828687,0x6563b171,0xf8b2052a,0x64438114}}, // _skog, ginh, _जलिय, fyni,
+ {{0x6b89b9d7,0x69cb81b4,0x64431238,0x212a0706}}, // fleg, _awge, _ünit, _inbh_,
+ {{0x27f7000d,0x521492b2,0xd0418085,0x6b898357}}, // žení_, едит, qalə, gleg,
+ {{0x29186df1,0x6d470125,0x6563d93b,0x26c6e0cb}}, // _kara_, ykja, binh, mboo_,
+ {{0x29186df2,0xd8f880e8,0x270e8424,0x656383a7}}, // _jara_, чної_, _ससुर_, cinh,
+ {{0x29182c07,0x6b89cc4e,0x236dedf3,0x7f4d473a}}, // _mara_, bleg, bnej_, _plaq,
+ {{0x29186df4,0xd7f8802e,0xb7d88065,0x6b89809a}}, // _lara_, rtă_, _ہوتا_, cleg,
+ {{0xd7f8802e,0xdb08b88a,0x29180162,0x56941af1}}, // stă_, _judí, _oara_, кают,
+ {{0x29185a62,0xd7f88087,0x77608118,0x6b80edf5}}, // _nara_, ptă_, _lmmx, lomg,
+ {{0xe7e58d38,0x5ba722ea,0x6d476df6,0xdb1a8866}}, // _किया_, драз, rkja, _atté,
+ {{0x44210bc1,0x6563d1c0,0x290ca92b,0x6b80901f}}, // _vrh_, zinh, leda_, nomg,
+ {{0x29184225,0x60c90088,0x64438035,0x851a0054}}, // [7a90] _bara_, _đemb, zyni, _फोकट_,
+ {{0x29180763,0x290cd746,0x4421026f,0x6563d93b}}, // _cara_, neda_, _trh_, xinh,
+ {{0x29186df7,0x656383a7,0xe9ff001c,0x248980fc}}, // _dara_, vinh, _thắc_, ƙami_,
+ {{0x05e084e5,0x290c8098,0xdb08816b,0xe1f98084}}, // _निशब, heda_, _budí, ytų_,
+ {{0x29186df8,0x65638073,0x236d81ac,0x16220ebf}}, // _fara_, tinh, vnej_, मियर_,
+ {{0x29184f9d,0x290c920e,0x61ed1600,0x6443edf9}}, // _gara_, jeda_, mual, tyni,
+ {{0x673ba252,0x6563edfa,0x6b89cb48,0x236da1a9}}, // _touj, rinh, tleg, tnej_,
+ {{0x291835be,0x65639677,0x38b50b4c,0x6b51016d}}, // _zara_, sinh, _fúró_, _fåge,
+ {{0x236da1a9,0x29186dfb,0x6b89caae,0x656383a7}}, // rnej_, _yara_, rleg, pinh,
+ {{0x6b89a055,0xdb0600f1,0xe1f98110,0x290c82f1}}, // sleg, _pikë, rtų_, geda_,
+ {{0xe1f981e2,0x6b899c0f,0x61ed2d2a,0xef1981b9}}, // stų_, pleg, hual, _każ_,
+ {{0x44263404,0xa822b026,0xb33b00f1,0x61ed0102}}, // _čo_, اکون, _diçk, kual,
+ {{0x290cedfc,0x3ea4016d,0xdb0883a8,0x25bf8609}}, // beda_, ämt_, _xudí, _jtul_,
+ {{0x290c8333,0x98aa001b,0xdb050019,0x61ed137c}}, // ceda_, _době_, _ruhá, dual,
+ {{0xf1bf0307,0x291837a9,0x61fbc3e3,0xa3cb016f}}, // _atá_, _rara_, ltul, रील_,
+ {{0xdbd60074,0x3eadc6eb,0x61fbb82d,0xa3c28a27}}, // _rääg, _nget_, otul, ्ठत_,
+ {{0x29186dfd,0x61fb8abf,0x3f821c8c,0x6f0400b4}}, // [7aa0] _para_, ntul, moku_, _ibic,
+ {{0x5e5700be,0x3f826dfe,0x29180085,0xdce38110}}, // _נייע_, loku_, _qara_, monė,
+ {{0x29182666,0x3ead808e,0x25bf8326,0x6e2402d4}}, // _vara_, _bget_, _atul_, _hrib,
+ {{0x291801cd,0x60c3017b,0x25bf8239,0x3ae80065}}, // _wara_, ınma, _btul_, _کبھی_,
+ {{0x7981ac0b,0x61ed0867,0xef198372,0x8af0011c}}, // kolw, cual, _daż_, rhəd,
+ {{0x59b51094,0x69c46dff,0x3ead904a,0x3f822817}}, // _अंतर, mpie, _eget_, hoku_,
+ {{0x290cee00,0xf8a70701,0xdee69950,0x66e69511}}, // veda_, _कृपय, _пови, _пова,
+ {{0x31c69242,0x2d858214,0xe805050d,0x6b510efa}}, // нсов, _ekle_, _रहता_, _tåge,
+ {{0x290c805c,0x61fbbd52,0x69c46e01,0x49748615}}, // teda_, gtul, npie, клис,
+ {{0x2ee030f8,0x6f042fea,0x09f70039,0xdb06010c}}, // _adif_, _abic, עמים_, _diké,
+ {{0xb4cc8701,0x61ed1717,0xf65f8125,0x6e2400b4}}, // लरी_, zual, _þær_, _arib,
+ {{0x7d1aee02,0x3ead0aa2,0x98aa01d0,0x3f8208f9}}, // _hats, øet_, _sobě_, goku_,
+ {{0x7d1aee03,0x614686c8,0x290c8072,0xd9469fa2}}, // _kats, _педа, peda_, _педи,
+ {{0x0217810f,0xc69383c8,0xd90e803d,0x2d850036}}, // _נחום_, טאר_, _اید_, ôle_,
+ {{0x7d1a83c3,0xef1f03bf,0x6e240c2e,0xc88581ec}}, // _mats, nlük_, _erib, ößte_,
+ {{0xb33b0085,0xa3e895b2,0x26d26e04,0xfaff03ed}}, // _kiçi, _बिन_, _geyo_, _ajër_,
+ {{0x3da71878,0x6e246e05,0xb5a71663,0x672d6e06}}, // [7ab0] _проб, _grib, _прой, _inaj,
+ {{0x6f0f2d11,0x7d1aa3be,0x6f1d1991,0xa3c280c2}}, // lecc, _nats, ldsc, ्ठा_,
+ {{0x61ed1441,0xfe702385,0x2d830984,0x69c401e8}}, // sual, _ندی_, moje_, apie,
+ {{0x6458aec0,0x2d831d73,0xdc3600be,0x6f0f5bc0}}, // dzvi, loje_, _סארט_, necc,
+ {{0x7d1ad80b,0x6b8d54fb,0x5f749a00,0x645880e1}}, // _bats, mlag, دالر, ezvi,
+ {{0x2d831d73,0x7d1a9e03,0x6b8d10b6,0xdb088580}}, // noje_, _cats, llag, _audà,
+ {{0x7d1a84bb,0x2ef58992,0xef1981b9,0x4cca80ab}}, // _dats, _извр, _taż_, _রাখু,
+ {{0x6b8d2709,0x61fbee07,0xb33b0315,0x6f1d00f3}}, // nlag, ttul, _biçi, jdsc,
+ {{0xfe738065,0x2d835ed8,0x7d1acc05,0x11d680f7}}, // _صدر_, koje_, _fats, نوية_,
+ {{0x2d8301e2,0x98a58aac,0x61fbc1f6,0x6d4a8edc}}, // joje_, кипе, rtul, ckfa,
+ {{0x69c40063,0xf8b202f6,0x6f1bee08,0x6f0f12cc}}, // zpie, ישי_, _kauc, fecc,
+ {{0x66162280,0x7981cee1,0x6b8d40d1,0x61fb8087}}, // _psyk, solw, jlag, ptul,
+ {{0x780d000c,0x6b8d6e09,0xa3d411bc,0x7d1aee0a}}, // सबुक_, dlag, सीन_, _yats,
+ {{0x0cb000c8,0x4d15054c,0x3f82012b,0x2d836e0b}}, // কর্ত, льст, soku_, goje_,
+ {{0x69dd0186,0x6b8d4255,0xdbcc002a,0x6b6788f9}}, // _avse, flag, _póñe, _gègè,
+ {{0x6f042eaa,0x6609d438,0x6b8d1fc9,0x6f1b87c6}}, // _ubic, _opek, glag, _nauc,
+ {{0xf2d28158,0x03a32ed9,0xef1f0214,0x69dbd94c}}, // [7ac0] יען_, рито, zlük_, nsue,
+ {{0x672d3e1d,0x25d7093f,0x63abee0c,0xd5b7000f}}, // _znaj, _שוין_, _lign, _इंतज,
+ {{0x69c41f8b,0x6f1b96a0,0x7d1a8336,0x07a681b5}}, // spie, _bauc, _rats, важн,
+ {{0x7d1a8bfa,0xdee68a7c,0x0bb70051,0x69db8be7}}, // _sats, тоди, _שלכם_, ksue,
+ {{0x6b8400e7,0x7d1acb61,0x61e9817f,0xed5a9539}}, // moig, _pats, jrel, мод_,
+ {{0xa8061a19,0x44258187,0x61e998ad,0x63abaad0}}, // _изгл, _brl_, drel, _aign,
+ {{0x6609843b,0x6f0f0876,0x63abd7d9,0x3f6a8ae7}}, // _epek, yecc, _bign, _живо_,
+ {{0x7d1ab6ba,0x6f0f04c3,0x6f1b82be,0x5b14895d}}, // _wats, xecc, _gauc, умит,
+ {{0x7d1aee0d,0x6f0f6e0e,0xf74383a7,0x9cf6802e}}, // _tats, vecc, _тето, _ачеш,
+ {{0x63ab8357,0x35dc0035,0x6b8d007a,0x7d1a822c}}, // _eign, _बिज़, zlag, _uats,
+ {{0x6f0f2d11,0x2d836e0f,0x61e9a6a8,0x63abee10}}, // tecc, voje_, arel, _fign,
+ {{0x6b8400e7,0xf99f03ec,0x2d5402f1,0x69db8661}}, // joig, _apèl_, _näeb_, bsue,
+ {{0x6f0f11b9,0x2d831d73,0x61e99238,0x7ae2c5e1}}, // recc, toje_, crel, _idot,
+ {{0xe9ff0104,0x644581ec,0xac190073,0x0cbf0f21}}, // _nhạc_, ähig, ногу_, ्रेम,
+ {{0x2d83028f,0x6b8d20e4,0x7bdc2417,0x1af58264}}, // roje_, tlag, gsru, _আসতে_,
+ {{0x291c8182,0x97a411e9,0x672d0010,0x2d832ed0}}, // _hava_, _крыл, _unaj, soje_,
+ {{0x49751878,0x6b8d0456,0x291c9ba4,0x20186e11}}, // [7ad0] глас, rlag, _kava_, _asri_,
+ {{0x6b8d3dbc,0x6f1bee12,0x29cf42ac,0x291e8019}}, // slag, _sauc, rża_, ndta_,
+ {{0x6c350c2b,0x6b8d10cc,0x7ae28009,0x30a7180f}}, // _صفحا, plag, _odot, трав,
+ {{0x7bc72991,0x61e98aaa,0x291cee13,0xc2e880ab}}, // mpju, yrel, _lava_, ক্তি_,
+ {{0xdef887ac,0x4425ee14,0xfce5a45b,0x201e00d2}}, // тых_, _srl_, голо, _štit_,
+ {{0x63ab8213,0x7ae2ee15,0xe3b0004e,0x0d84902a}}, // _sign, _adot, _دری_, алін,
+ {{0x97a78048,0x26d944ec,0x63aba7f5,0xdb088333}}, // _арал, kaso_, _pign, _sudá,
+ {{0x61e98003,0xe4e308fd,0xb8821dc1,0x3f8901c0}}, // trel, कृति_, číta, _nkau_,
+ {{0x2249048f,0x63abe14d,0x291ccb40,0x28a70ebf}}, // nyak_, _vign, _bava_, _कृति,
+ {{0x61e9ca6c,0x291c92d0,0x3f8900dd,0x7ae28102}}, // rrel, _cava_, _akau_, _edot,
+ {{0x69db9b56,0x63ab820d,0x3a2680f3,0x291cb6b2}}, // ssue, _tign, _erop_, _dava_,
+ {{0x69c11807,0x61e9ee16,0x628b809a,0x26d90c2e}}, // _člen, prel, _tygo, gaso_,
+ {{0xcdf60a7f,0x09e58009,0x55f60e11,0x291c8037}}, // ычны, _солн, ычну, _fava_,
+ {{0x6d41bfae,0x60d50259,0x3f8900b9,0x7bdc6e17}}, // _jola, _nezm, _ekau_, tsru,
+ {{0x6b510502,0x5faf0e10,0xf992807c,0x26d96e18}}, // _såga, _जंगल, _הרג_, baso_,
+ {{0x26d905e4,0x7c2eee19,0x6d4193fa,0x13d1801b}}, // caso_, _ábre, _lola, _सबैभ,
+ {{0x0eaa035f,0x60d55434,0xa3d40540,0x7bdc1388}}, // [7ae0] _який_, _bezm, _सौर_, ssru,
+ {{0x6b510186,0x6d41ee1a,0x291c8118,0x2ecc836d}}, // _våga, _nola, _xava_, ार्त,
+ {{0xd0488201,0x40961ba2,0x46ea8e17,0x60d5002e}}, // fadə, урат, еден_, _dezm,
+ {{0xf1bf0333,0x291c0390,0x27e7066e,0x6d41c1ef}}, // _irán_, žva_, ánna_, _aola,
+ {{0x6d41ee1b,0x1634af92,0xdee38323,0xe9ff0129}}, // _bola, реля, сочи, _thạc_,
+ {{0xa3cb073c,0xa3b81993,0x7af60084,0x3f8f82f1}}, // रीक_, _चढा_, lgyt, algu_,
+ {{0x4ab8023c,0xab5c8029,0x69c283b2,0xfbaa8049}}, // _अलाव, _atļa, _stoe, нтий_,
+ {{0x7d1e0181,0x6d418307,0xb6a6bf21,0x33203a65}}, // _laps, _eola, _библ, ndix_,
+ {{0x6d41e737,0xdd8f881b,0x316a813a,0x26d900e5}}, // _fola, _کول_, ешно_, vaso_,
+ {{0x6d41a63d,0x7d1e000d,0x6d4e0a0f,0xdb0b823e}}, // _gola, _naps, jkba, _ligè,
+ {{0xb4c2823c,0x31370051,0x893700be,0x291c82d6}}, // ूरी_, אריך_, אריע_, _vava_,
+ {{0x3a2683d3,0x60c487d9,0x6d41ac89,0xa3b80105}}, // _trop_, _şimd, _zola, _चढ़_,
+ {{0x26d9044b,0xe29280f7,0x291cdf6b,0x246e8201}}, // raso_, _اذا_, _tava_, _həm_,
+ {{0x040e00c8,0x74ca9c7b,0x6d58ee1c,0xf65f0366}}, // হিনী_, _स्मृ, nhva, lbæk_,
+ {{0x070486ce,0x26d96e1d,0x88bb80be,0x06f68a27}}, // रभाव_, paso_, _אזוי, ीभाव_,
+ {{0x60daa8cd,0x98bc8176,0xae221370,0xe31502ee}}, // latm, _movč_, मिशन_, јмањ,
+ {{0x5a3500b3,0x23692823,0xdb0bc092,0xce68259a}}, // [7af0] шнат, riaj_, _digè, _брэд_,
+ {{0x7c28ee1e,0x660d1629,0x2369009a,0xdce380c3}}, // _ordr, _ipak, siaj_, tinč,
+ {{0xa3cb09a3,0x8ccb3b04,0xd011026a,0xdb1c1c18}}, // रीख_, _त्यो, تلا_, sprá,
+ {{0x398b8aa2,0x660d0133,0xdce38be4,0x644a8035}}, // løst_, _kpak, rinč, dyfi,
+ {{0x6d41a7d3,0xa91c80e1,0x7c28ee1f,0x8ca78075}}, // _pola, voľn, _ardr, टेनो,
+ {{0x660d38fc,0xa5098c9d,0x6d4181b4,0x60da80dd}}, // _mpak, тека_, _qola, jatm,
+ {{0xeab180f7,0x8cc19391,0x60da8380,0x36d58084}}, // يعة_, रुषो, datm, _вобр,
+ {{0x0b4587b6,0x05b500d4,0x660d09a4,0x7e7b8cdb}}, // рнин, ंदाब, _opak, _txup,
+ {{0x5f448c2b,0x660d02d5,0x6d4e067f,0x63af01b4}}, // _انقل, _npak, ykba, _micn,
+ {{0x63af1601,0xe61088ca,0x291308f1,0x6d58816b}}, // _licn, _عشق_, nexa_, chva,
+ {{0x6b511918,0x660d1600,0x2d87ee20,0x200103f8}}, // _någo, _apak, mone_, nthi_,
+ {{0x63af0706,0x6e29e43a,0x3f6a13b4,0x00000000}}, // _nicn, _kreb, тимо_, --,
+ {{0x7bc56ac5,0x43848013,0x7f3a8158,0x0ab48c48}}, // _athu, _النق, דערו, _احمد,
+ {{0x2d87ee21,0x69c9affb,0xa0a5c1da,0x660d00dd}}, // none_, mpee, _калд, _dpak,
+ {{0x6f09a08f,0xdca61285,0x64a623cd,0xdfd180f7}}, // _obec, рави, рава, _بيع_,
+ {{0x6e298140,0x3a2d026c,0x2d87b5db,0x589481a8}}, // _oreb, _šepa_, hone_, _اجهز,
+ {{0xe8fa5d22,0x52aa130f,0x2d8786ee,0xc33282f6}}, // [7b00] кла_, твам_, kone_, חון_,
+ {{0x2d87b2e7,0x7c2a002a,0xf1bf0129,0x38cb01f9}}, // jone_, _áfro, _trán_, نامی_,
+ {{0x2d87ee22,0x661b94c7,0x46ba0fcc,0xd10203b6}}, // done_, _asuk, _इलाह, र्षण_,
+ {{0x61ed3655,0x6e29af78,0xe5c6bc20,0x661b8748}}, // bral, _breb, аско, _bsuk,
+ {{0x2d87caa1,0x6e299193,0x63a4547c,0x0cd1801b}}, // fone_, _creb, mmin, हरूम,
+ {{0x2d87b204,0xb5fb0019,0xcbe6be11,0xb4c280c2}}, // gone_, szág, ации, ूरे_,
+ {{0x6ca38081,0xa9670249,0xad9b016a,0x60daa0db}}, // оръж, _тира_, ntúa, vatm,
+ {{0x6d5880fe,0x644aee23,0xa3cb1370,0xf1bf0f09}}, // shva, ryfi, रीट_, _orál_,
+ {{0x63a464e3,0x2d879b8f,0x644ad8f8,0x9982801b}}, // imin, bone_, syfi, íků_,
+ {{0x6602cef1,0x63a42fd7,0x67208c53,0x717481a8}}, // mtok, hmin, _hamj, إهدا,
+ {{0x6602ec09,0x60da8850,0x63a46e24,0xeb36b026}}, // ltok, ratm, kmin, _پراخ,
+ {{0x8cca81ab,0xdce38029,0x6722ee25,0x61ed6e26}}, // _स्तो, minā, ndoj, yral,
+ {{0x60dad3fb,0x6602ee27,0xdce380eb,0x661b80b9}}, // patm, ntok, linā, _xsuk,
+ {{0x6b5810ab,0x61ed0965,0x672080c3,0x61e2913b}}, // _nígb, vral, _lamj, _ivol,
+ {{0x06d780ab,0x6b55bb7a,0x6b510106,0x394301f4}}, // _হাসি, _hága, _fågl, _rojs_,
+ {{0x672082ce,0x63a43b1d,0x2d87ee28,0x63b7016d}}, // _namj, gmin, zone_, _vuxn,
+ {{0xa3cb00d4,0x660d1eb3,0x2d87b624,0x2913002a}}, // [7b10] रीज_, _upak, yone_, texa_,
+ {{0x61ed0dde,0x63a4402d,0x98c7361c,0x60d8826c}}, // rral, amin, рсал, _jevm,
+ {{0x2913062f,0x61ed1325,0x63a4094d,0x7bc5314f}}, // rexa_, sral, bmin, _uthu,
+ {{0x6e29a39f,0x61e2807d,0xdce38029,0x2d87bbd3}}, // _preb, _ovol, dinā, wone_,
+ {{0x6d450006,0x539a010f,0xfe709a37,0x1af580ab}}, // _koha, _תירו, وده_, _আসলে_,
+ {{0x6d455a91,0x6e299487,0xb4c1c19f,0x2abd026b}}, // _joha, _vreb, ंडे_, _fábó_,
+ {{0x6d453b6a,0xb5fb0118,0x0445ee29,0x2d87a91e}}, // _moha, nzáb, сейн, rone_,
+ {{0x6e29ee2a,0xe299ab3f,0x661bee2b,0x2d879fcb}}, // _treb, лай_, _tsuk, sone_,
+ {{0x2d87ee2c,0x316c8859,0x26ddee2d,0x661b8234}}, // pone_, fidz_, mawo_, _usuk,
+ {{0x672082fd,0x6d4503f2,0x69c98009,0xdce380eb}}, // _zamj, _noha, rpee, binā,
+ {{0x1b1f80c8,0x04958013,0x69c9837a,0xdce38029}}, // ভাবে_, _الاح, spee, cinā,
+ {{0x2f566983,0x8c490085,0x09cd00ab,0x63a4011c}}, // стос, _maşı, _লিনা, xmin,
+ {{0x6d452158,0xa3b0800f,0x7521ee2e,0x877a80be}}, // _boha, _टूट_, _kalz, רארי,
+ {{0x04461a19,0xada62597,0x6d456e2f,0x6b898299}}, // бегн, сагл, _coha, loeg,
+ {{0x61e2803e,0x63a40074,0x224d8019,0x75218214}}, // _zvol, tmin, lyek_, _malz,
+ {{0x67560077,0x236d809a,0xaca38133,0x6b899384}}, // _دختر, niej_, _adịl, noeg,
+ {{0xeb970ca4,0xdce38029,0x224d8019,0x26dd8234}}, // [7b20] сит_, zinā, nyek_, dawo_,
+ {{0xa3e8864a,0x8c49017b,0xb4e68697,0xad9b0118}}, // _बिल_, _başı, _भये_, ptúa,
+ {{0x236d8063,0x6d5c0ad0,0x672080f1,0xd8da80be}}, // kiej_, mhra, _pamj, רקער,
+ {{0x8c490201,0x656e3496,0x6602b4e1,0x6d4500e1}}, // _daşı, libh, ttok, _zoha,
+ {{0x6d4508b1,0xa3d403eb,0xca750071,0x644e423d}}, // _yoha, सीर_, _дуры, lybi,
+ {{0x7521a784,0xdb1c01ec,0x7a0a00eb,0x6602ee30}}, // _calz, sprä, rētā, rtok,
+ {{0xf86316d4,0xf36695b5,0x61e2ee31,0x06d780ab}}, // _евро, йтин, _svol, _হারি,
+ {{0xf0633bea,0x2b400754,0x60d8880a,0xdce380eb}}, // _эксп, njic_, _sevm, rinā,
+ {{0xdce38029,0x75218197,0x224d8019,0x2d8a3ebe}}, // sinā, _falz, gyek_, kobe_,
+ {{0x8c4902bb,0x26c2009a,0x644e0084,0x290a80e1}}, // _yaşı, ecko_, kybi, ýba_,
+ {{0xaab8058c,0x61f601e0,0x23810110,0x2d8a6e32}}, // _अलंक, huyl, lėje_, dobe_,
+ {{0x224d861b,0x442cee33,0x2fc7808e,0x290281d0}}, // byek_, _ird_, _dtng_, řka_,
+ {{0x238101e2,0x442c82f7,0x6e2200eb,0x8af00085}}, // nėje_, _hrd_, _šobr, rhəl,
+ {{0x67d4845d,0x6d5c02cd,0xb4e6800d,0x26dd83f7}}, // _доку, ghra, _भयो_, zawo_,
+ {{0x60de00dd,0x64b906a7,0x93bc8162,0x4fc69246}}, // dapm, _आलीश, _brăi, _усла,
+ {{0x443f003a,0xa91c81ac,0x63ba811f,0x442c91e6}}, // ću_, koľk, _mutn, _mrd_,
+ {{0x6d5c0ad0,0xe820923a,0x3f8b00d2,0xdb0881ac}}, // [7b30] bhra, यिका_, mocu_, _budú,
+ {{0x6d5c06f6,0x236d8063,0x442c93c2,0x6e2d1171}}, // chra, ziej_, _ord_, _irab,
+ {{0x44275ccf,0x63ba83fb,0x6e2d10e8,0x6e220118}}, // _én_, _nutn, _hrab, _áobr,
+ {{0xdddb8a56,0x75218799,0x2a60022c,0x3cfe8037}}, // _využ, _salz, bzib_, _actv_,
+ {{0x442c9bfb,0x6b8982d8,0xf1272325,0x63ba826c}}, // _ard_, voeg, _льво, _autn,
+ {{0x67d54571,0x8c490214,0x442cee34,0x63ba8bb1}}, // _могу, _taşı, _brd_, _butn,
+ {{0x58d40256,0x6b583b7a,0xdb1c1235,0x3f8b0b80}}, // _хост, _híga, språ, kocu_,
+ {{0x225f811e,0x6d5c016b,0xc1788084,0x6d8d81df}}, // tzuk_, zhra, enė_, túas,
+ {{0x24898063,0x656e029b,0x3dc9022c,0xa3e586a7}}, // łam_, yibh, _ntaw_, _भटक_,
+ {{0x6f0d3051,0x6009968a,0x764f009a,0xb4b80327}}, // _abac, аном_, dycy, _छले_,
+ {{0x236d809a,0x442cc456,0x3dc9008e,0x6e2d4a2b}}, // piej_, _grd_, _ataw_, _arab,
+ {{0x6e2d3516,0x5ea800ab,0x16aa0ae7,0x9eaa2482}}, // _brab, _ক্ষে, ивни_, ивна_,
+ {{0x6d5c11cd,0x200a8214,0x656e0197,0x25b28214}}, // thra, çbir_, tibh, _ünlü_,
+ {{0x6e2d6e35,0x644e0d42,0x2fc7808e,0x6f0d59a5}}, // _drab, tybi, _ttng_, _ebac,
+ {{0x6e2d00ad,0xa34a0081,0x656e2305,0xd3ba0048}}, // _erab, азва_, ribh, ругі_,
+ {{0x7afbee36,0x42ca0254,0x6b55a363,0xe81e8105}}, // ngut, рган_, _mágo, _बनता_,
+ {{0xb1460a95,0xe0da0b01,0xc8788085,0x39460084}}, // [7b40] онал, ива_, mağa_, онаг,
+ {{0x409611cf,0xaca3819d,0x6724004f,0x6f0d0b80}}, // орет, _adọl, _haij, _zbac,
+ {{0x93bc8087,0xb17b0106,0x3cfe8144,0x443c8118}}, // _trăi, _tvån, _rctv_, xxv_,
+ {{0x3cfe89e1,0x25b7003d,0x11d6102a,0x23810084}}, // _sctv_, _دهید_, _мігр, tėje_,
+ {{0x672400eb,0x442c88ae,0xe53481e2,0x63ba8699}}, // _maij, _srd_, зель, _sutn,
+ {{0x442cee37,0x61e4080a,0x6b5583a8,0x6b966e38}}, // _prd_, msil, _bágo, llyg,
+ {{0x6b5c80e7,0x200581a1,0x39940198,0x23810084}}, // _lége, jtli_, jäsi_, sėje_,
+ {{0x8cca8ebf,0x61e46e39,0x672418b2,0x7afbd25e}}, // _स्रो, osil, _naij, ggut,
+ {{0x3947a0c6,0x60dc2ada,0x645801b9,0x61e43f3c}}, // _bons_, _kerm, _żvil, nsil,
+ {{0x60dc0886,0x3a370039,0x6b558118,0x442cee3a}}, // _jerm, קרים_, _fágo, _trd_,
+ {{0x6d5a8009,0x61e41bef,0x3947cf42,0x5f950087}}, // _ilta, hsil, _dons_, зинт,
+ {{0xd6db0fbb,0x6e2d061b,0x672401c0,0x61e46e3b}}, // што_, _prab, _caij, ksil,
+ {{0x6d48ee3c,0x3947809f,0xdb060009,0x67240267}}, // _koda, _fons_, _mikä, _daij,
+ {{0x60dc6e3d,0x6ad3000f,0xc5f382f6,0xd5b801e2}}, // _nerm, तर्र, ודש_, ўся_,
+ {{0x63a2aeea,0x29c08028,0x5c046e3e,0x6b966e3f}}, // _khon, _hòa_, мята, flyg,
+ {{0x6f0d0025,0xa3dc853f,0x7ae0ee40,0x53341c79}}, // _ubac, तीय_, namt, мест,
+ {{0x60dc2b15,0x776d07f4,0x63a29e66,0x61e40c27}}, // [7b50] _berm, _imax, _mhon, gsil,
+ {{0x6d48ee41,0x60dc010b,0x7ae098b3,0x8cca84c5}}, // _noda, _cerm, hamt, _स्लो,
+ {{0x60dc1277,0x3f159d3d,0x63a28114,0xdce700eb}}, // _derm, здес, _ohon, cijā,
+ {{0x441a0051,0xdcf8800d,0x4421020f,0x6b5c8b4c}}, // _פורס, pově, _ish_, _légb,
+ {{0x60dc6e42,0x6d48da4a,0xbc68003d,0x5f0000d4}}, // _ferm, _boda, _همین_, ष्ट्_,
+ {{0x6d48ee43,0x8d659317,0x60dc079a,0xdb0bce2a}}, // _coda, явле, _germ, _vigí,
+ {{0x6d48885d,0x63a28090,0x7aef8118,0x00000000}}, // _doda, _bhon, ócto, --,
+ {{0x63a2ee44,0x44216e45,0xe9d99bc1,0xc245b3d9}}, // _chon, _msh_, бки_, зник,
+ {{0x63a291e7,0xe1f180f7,0xddc6009a,0x95c88b26}}, // _dhon, رسة_, zykł, _духа_,
+ {{0x395e8051,0x60dc01df,0x7afb833e,0xc69280be}}, // ghts_, _xerm, sgut, _טאן_,
+ {{0x63a281a8,0x7d7a80be,0xad9b023e,0x67242168}}, // _fhon, _פרעג, rtúl, _paij,
+ {{0x7c2a07a3,0x6d488a0f,0xed598a20,0x6b8d008e}}, // _áfri, _zoda, _duže_, joag,
+ {{0xc60980ab,0x2005801b,0x539a81c6,0x899f026b}}, // লিকা_, stli_, _פינו, _díẹ̀,
+ {{0x69d98372,0x395eee46,0x6b5c8019,0x63a2ee47}}, // _iwwe, chts_, _vége, _zhon,
+ {{0x68e1ed67,0x913a8039,0x63a98084,0x7e628037}}, // mald, _לעסק, omen, zzop,
+ {{0x68e1b665,0xbb430dc7,0x63a9a574,0x3f788088}}, // lald, неск, nmen, rčuk_,
+ {{0x63a9ee48,0x80de80ab,0xb18381d0,0xdfa512c5}}, // [7b60] imen, _মাত্, šťuj, _تحوي,
+ {{0xdca617f9,0x68e1ee49,0x64a612a0,0x63a9d95b}}, // _напи, nald, _напа, hmen,
+ {{0x63a9ee4a,0x60dc1d56,0xf99f0032,0x249f0326}}, // kmen, _verm, _apès_, ƙuma_,
+ {{0x6d48e911,0x63a992d3,0x2d981836,0x61e46e4b}}, // _soda, jmen, llre_, psil,
+ {{0xa3b5000f,0x68e1a22f,0x63a2ee4c,0x63a981b5}}, // _छूट_, kald, _rhon, dmen,
+ {{0x63be231b,0x63a9a5d2,0x63a294f2,0xd5b9821e}}, // _kupn, emen, _shon, ісі_,
+ {{0x63a29fb8,0x7d18ee4d,0x7ae0b5fc,0x6d5a81d0}}, // _phon, revs, tamt, _vlta,
+ {{0x80de80c8,0x987b00be,0x5a3480b3,0x291a06c2}}, // _মাধ্, _האלט, днит, lepa_,
+ {{0xa3dc823c,0x6d48ac04,0x7ae084b9,0x68e18a64}}, // _तौर_, _toda, ramt, fald,
+ {{0x63a9c58e,0x7ae090f6,0x6d5acf3f,0x68e18dde}}, // amen, samt, _ulta, gald,
+ {{0x7ae08a20,0x69d981cd,0xa91c81ac,0x80de80ab}}, // pamt, _ewwe, koľv, _মাদ্,
+ {{0x389b00be,0x29c0801c,0x628281c5,0x63a9ee4e}}, // מיינ, _tòa_, _txoo, cmen,
+ {{0x7d0300f2,0x68e1811e,0xa3c2809a,0xf1bf0028}}, // _önsk, bald, _एंड_, _trái_,
+ {{0x61438a94,0x68e185dc,0x5e578158,0x291a120e}}, // _печа, cald, ליכע_, jepa_,
+ {{0xdb1a85e4,0x6d5500f3,0x439500e8,0xab95004a}}, // _auté, rkza, давс, диві,
+ {{0x6b58002a,0xa87b84de,0x69be02f1,0x88bc81d0}}, // _dígo, _לאור, _वंशी, zděl,
+ {{0x248d0063,0xa3dc816f,0x752500b9,0x6b8d011b}}, // [7b70] łem_, तीत_, _tahz, roag,
+ {{0x92cd00ab,0xa8a700ae,0xaca3019d,0x30a717d6}}, // রণে_, прек, _akụd, прев,
+ {{0xb4d9085d,0xf990019f,0xaf2000ab,0xa3e89370}}, // ारी_, ابق_, ধারণ_, _बिग_,
+ {{0x60c502af,0x99800699,0x68e1811b,0xe7fe0327}}, // schm, rviš_, zald, _उमरा_,
+ {{0xe2ab003d,0x7bda81b9,0x63be050b,0x6b84811f}}, // _دادن_, _awtu, _zupn, čigl,
+ {{0x628001e2,0xdb1704c3,0x213836f8,0x518720bf}}, // _žmog, _muxí, _gnrh_, _хума,
+ {{0xea008104,0x63a9d020,0x68e1be80,0xa3d78074}}, // _đại_, tmen, vald, ़ीं_,
+ {{0xb4d91008,0x63a9837f,0x5f948162,0x7f5b913b}}, // ारु_, umen, _зист, _uluq,
+ {{0x32091f14,0x68e1811e,0x3f8f811f,0x4ea784bd}}, // ntay_, tald, mogu_, _хрва,
+ {{0x57e99594,0xdb0b816a,0xd5b187c3,0xdb1c1277}}, // одом_, _digá, _شفا_, sprø,
+ {{0x68e1ee4f,0x63a9ee50,0xdb170187,0x2455806b}}, // rald, pmen, _auxí, _تناس,
+ {{0x68e18dde,0x8367819f,0xdb170118,0xe73a22ea}}, // sald, _عدال, _buxí, цем_,
+ {{0x68e1ee51,0xb4d90074,0xb606811f,0x25bf99cf}}, // pald, ारू_, _gušć, _juul_,
+ {{0xdb0f01df,0x64a601e2,0x61fbee52,0x63be6e53}}, // _dicí, дага, muul, _pupn,
+ {{0x3f8f8006,0xdbf2128a,0xaad1885d,0x9c7c8af8}}, // kogu_, přít, _सभाक, _večg,
+ {{0xf0928051,0xe9da11b1,0xf77209a7,0xdb170118}}, // _שנה_, жка_, _جات_, _fuxí,
+ {{0x2fcd003a,0xe2970381,0xb5fb02ba,0x32090079}}, // [7b80] _čega_, дат_, nzál, gtay_,
+ {{0x6e244bf2,0x6b5cee54,0x23810084,0x6606a551}}, // _isib, _méga, rėja_, _äkki,
+ {{0x7d1c4cca,0x6b5c82be,0x9c7c8267,0xb8f60006}}, // mers, _léga, _keče, _सभ_,
+ {{0x320902a3,0x61fb9e89,0x2bc48105,0x3d008beb}}, // btay_, kuul, _लंबा, _राहे_,
+ {{0x291a019d,0x9c7c8e63,0x7ae46e55,0x6b5c83e6}}, // pepa_, _meče, mait, _néga,
+ {{0xe81e8e18,0x7ae464e3,0x9c7c82ee,0xdfd200f7}}, // _बनला_, lait, _leče, _ليس_,
+ {{0xab94835f,0x3f8f80fe,0xc10e80ff,0x139495a6}}, // никі, bogu_, _bỗng_, никю,
+ {{0x7ae45f53,0x18a34bfc,0x9c7c883d,0xb6068115}}, // nait, _расм, _neče, _pušć,
+ {{0x8bb380f7,0x25bf81b4,0x2127a2e4,0x6e2457c7}}, // خصوص, _guul_, _banh_, _nsib,
+ {{0x7ae4335f,0x2127801c,0x224b816d,0x6b5c8036}}, // hait, _canh_, äcka_, _déga,
+ {{0x7ae46e56,0x21278028,0xb2268125,0x9cd78039}}, // kait, _danh_, fræð, _חובה_,
+ {{0x7528acf7,0x2ee0026c,0x32094000,0xf1bf1c18}}, // _hadz, _ceif_, ytay_, _krát_,
+ {{0x7ae46e57,0x99669f72,0x7d1c6e58,0x09c88264}}, // dait, _откл, fers, _শিকা,
+ {{0xb4d93ed8,0x88bc801b,0xdce380c3,0x6d58888b}}, // ारे_, zděj, rinć, nkva,
+ {{0x7ae451a1,0x6e246e59,0x63a6026c,0xa3d902f1}}, // fait, _esib, _bhkn, ठीं_,
+ {{0xf1c300d4,0x799a8114,0x64430722,0x7ae4134c}}, // _वंदन, lltw, _ànim, gait,
+ {{0xf1bf0028,0x06ad00ab,0x753abcf9,0x9f46016b}}, // [7b90] _quá_, _ক্রি, _ontz, nulý_,
+ {{0x752886e4,0x51f807ac,0x6f1d12a7,0x320902a3}}, // _nadz, ьную_, lesc, rtay_,
+ {{0x6ec3023c,0x9cd60039,0x32090079,0x7ae46e5a}}, // रखपु, _אותה_, stay_, bait,
+ {{0x6f1d6e5b,0x7c250efd,0x0fd5009a,0x753a811e}}, // nesc, _ishr, डीगढ, _antz,
+ {{0x8f9c0039,0x63ad1063,0x23810110,0xc10e827d}}, // קידי, mman, lėjo_, _rỗng_,
+ {{0x11da0051,0x64a5a7af,0xdca5a482,0x237f005c}}, // _מחשב, нала, нали, čuju_,
+ {{0x68e52b1b,0xaf078098,0x6837819d,0x21278129}}, // mahd, _очак, _ọda, _ranh_,
+ {{0x63ad4154,0x753a8b90,0x61fb8eea,0xed5980d2}}, // nman, _entz, tuul, _duža_,
+ {{0x2d910e4d,0x63ad0dba,0x8ccab4c7,0x75288748}}, // koze_, iman, _स्को, _fadz,
+ {{0xb5fb03b0,0x29010461,0xb4d96e5c,0x9c7cd6a6}}, // rzál, ngha_, ारो_, _seče,
+ {{0x61fb8e05,0x63ad6e5d,0x06ad00c8,0x6f1d0144}}, // suul, kman, _ক্লি, fesc,
+ {{0x6d890038,0x6f1d0e83,0x6729c7d0,0x238101e2}}, // hľad, gesc, _jaej, dėjo_,
+ {{0x80de80c8,0x7ae40a7f,0x7d1c340e,0x3f9e0029}}, // _মার্, vait, ters, ētu_,
+ {{0x61e9ee5e,0x6b9bc9d8,0x7bc1cc37,0x7ae46e5f}}, // msel, llug, _kulu, wait,
+ {{0x7ae464e3,0x61e9929d,0x7bc1859e,0xe946803d}}, // tait, lsel, _julu, _سروی,
+ {{0x7bc1ee60,0x63ad05b0,0x28d200d4,0xdc0380e1}}, // _mulu, gman, दुनि, jčít,
+ {{0x61e988e9,0x7ae46e61,0x7bc18886,0xf2d2812a}}, // [7ba0] nsel, rait, _lulu, טען_,
+ {{0x7ae46e62,0x63ad6865,0x7bc18009,0x61e9aadb}}, // sait, aman, _oulu, isel,
+ {{0xdfcf0dd0,0x5ed180c8,0x7bc1ee63,0x7528ee64}}, // ميل_, _হয়ে, _nulu, _radz,
+ {{0xeaa78307,0xaa460ca0,0x61e9d49f,0x7528b3f6}}, // _مع_, _земл, ksel, _sadz,
+ {{0xee3a81e5,0x4425ee65,0x61e9aa52,0x75288ec3}}, // янд_, _asl_, jsel, _padz,
+ {{0x7bc1ee66,0x61e981eb,0x6b8282d6,0xb60602d4}}, // _bulu, dsel, _sjog, mešč,
+ {{0xa0a3284f,0x7bc18079,0x6b9bd1aa,0x6d58ed67}}, // _бард, _culu, flug, rkva,
+ {{0xa3e211be,0x7bc1c818,0x67c8800d,0x6d58ee67}}, // नीय_, _dulu, _něja, skva,
+ {{0x36679634,0x61e9929d,0x6f1d5862,0xd13080f7}}, // _зато_, gsel, vesc, لمة_,
+ {{0x63ad6e68,0x65a001ec,0x7bc1838a,0x04188264}}, // zman, wöhn, _fulu, থিবী_,
+ {{0x741384c1,0x63ad6e69,0xa3dc8816,0x61e9ee6a}}, // _مولا, yman, तीश_, asel,
+ {{0xb5fb0019,0xb4d9016f,0x6f02ee6b,0x63630074}}, // szám, ार्_, ngoc, _kõne,
+ {{0x28d183b7,0x6f1d0328,0xf710001c,0x2d91136f}}, // _द्वि, resc, _tầng_, toze_,
+ {{0xb5240b71,0x6b5104b8,0x6f1d002e,0x201807de}}, // льск, _någr, sesc, _opri_,
+ {{0x63ad07e2,0x6f1d180b,0x394e8106,0x7f9896dc}}, // tman, pesc, _iofs_, líqu,
+ {{0x6b55de81,0x23810110,0xdb17002a,0x409b81c6}}, // _pági, rėjo_, _buxá, _מבוס,
+ {{0x2d9106c0,0x2018047f,0x60db00ce,0xb5fb0061}}, // [7bb0] poze_, _apri_, _đumb, zzáj,
+ {{0xfbd00065,0x9998002e,0xbebb0123,0x00000000}}, // شتہ_, екут_, geëe, --,
+ {{0x63ad6e6c,0x2d540074,0x6b5c8036,0x9f460144}}, // pman, _päev_, _négo, culó_,
+ {{0x291e8135,0x7ff41a00,0x572780f7,0x61e9af06}}, // heta_, اسلا, عراق, ysel,
+ {{0x291e96d8,0x7bc1afbb,0x5e5880e8,0xdb1a82df}}, // keta_, _sulu, ниця_, _cutí,
+ {{0x4a5a004c,0x291e9351,0x2b490024,0x672980b9}}, // _חדשו, jeta_, ljac_, _waej,
+ {{0xa3dc853e,0xdb1e00e7,0xf1bf66df,0x9c7c826c}}, // तील_, _supé, _trás_, _rečc,
+ {{0x61e98100,0x2b490052,0x7f988333,0x7ae28362}}, // tsel, njac_, fíqu, _ceot,
+ {{0xdcbb8364,0x291eee6d,0xdb0600f1,0x6b9bee6e}}, // _еще_, feta_, _shkë, rlug,
+ {{0x7bc1ee6f,0xdb1a841c,0x04678087,0x69c2a7d1}}, // _tulu, _cutâ, _птем, _duoe,
+ {{0x6b9bee70,0x8cca8305,0xf772006b,0x44258087}}, // plug, _स्टो, لاح_, _usl_,
+ {{0x61e994c7,0x78ba0177,0x2ac78032,0x69c00037}}, // psel, _útve, _díbó_, _èmeg,
+ {{0x291eaa09,0xc7b280be,0xd7f88087,0xdb1c01ec}}, // beta_, יבט_, ară_, rprü,
+ {{0x5e568158,0x291e9520,0xb6060353,0xdb1c01ec}}, // דישע_, ceta_, vešč, sprü,
+ {{0xf990990c,0xeb9a3650,0x69cb1fd6,0x9c7c8bcf}}, // _ابن_, див_, ígen, _keča,
+ {{0x68e3e9ab,0x332dbe9d,0x7ae2802a,0x9c7c81f4}}, // _hend, ndex_, _xeot, _ječa,
+ {{0x68e3b804,0xeb970dc8,0x98bc81d0,0x6b51183d}}, // [7bc0] _kend, тит_, _nově_, _pågr,
+ {{0x20561d91,0x3d1101fe,0x68e3af39,0xa3c1a1e3}}, // ктер, द्दे_, _jend, ंदा_,
+ {{0xd12f187e,0xdb1a8118,0x38608326,0xe9df026b}}, // تمل_, _sutí, _ƙiri_, _ewú_,
+ {{0x291ebc95,0xed5988ae,0x9c7c826f,0x20070722}}, // zeta_, _bužo_, _neča, ània_,
+ {{0x291e8ce9,0x33200722,0x332d8079,0x660f8106}}, // yeta_, meix_, ddex_, _äckl,
+ {{0x68e3ed21,0xcbcd80ab,0x7ae28074,0x3320023e}}, // _nend, _রয়েছ, _seot, leix_,
+ {{0x291e803b,0x8af0011c,0x1fb681e5,0xd7068c9b}}, // veta_, hkəm, _асар, _изви,
+ {{0x040d8028,0x291e882e,0x3320009f,0x68e3ee71}}, // _hướn, weta_, neix_, _aend,
+ {{0x291e82a5,0x9c7c8eef,0x1d268fe7,0xa3e2064a}}, // teta_, _deča, _имам, नीत_,
+ {{0x68e3b55e,0xd5ae8117,0x29079b11,0xd7f8802e}}, // _cend, _بہت_, _ccna_, tră_,
+ {{0x68e38b6e,0x39570051,0x9f5f0009,0x93469229}}, // _dend, דשים_, yttä_, _инже,
+ {{0x68e38cfa,0x320d81b4,0x28a73262,0xa5f9062c}}, // _eend, btey_, केटि, нему_,
+ {{0x64588110,0x3320009f,0x3a2680b9,0xf1bf026b}}, // lyvi, deix_, _usop_, _asán_,
+ {{0x68e38430,0xd12f80f7,0xa2d5578b,0x6fcabbf1}}, // _gend, _ومن_, युन्, _संपू,
+ {{0x68e882a3,0x0713abef,0x30a38081,0xc04f80e8}}, // madd, ण्डव_, _кръв, _ті_,
+ {{0x3320009f,0x68e8ee72,0x68e3ee73,0x490e8cf0}}, // geix_, ladd, _zend, त्रो_,
+ {{0x6b5c82be,0x68e3b106,0x6e364c68,0x4423022c}}, // [7bd0] _régl, _yend, _gryb, swj_,
+ {{0x6d5c148c,0x68e891c9,0x7bc10061,0x645894ef}}, // ckra, nadd, ílus, kyvi,
+ {{0x2d830024,0x1aef00ab,0xc7f50eef,0xe8d781c6}}, // mnje_, ঙ্গে_, узећ, דובר_,
+ {{0x290746e9,0xfaff0168,0xf40880ab,0x68e8ee74}}, // żna_, _emër_, লবার_, hadd,
+ {{0xd246987e,0xb90900c8,0x61ff0201,0x68e8ee75}}, // _ان_, _যা_, quql, kadd,
+ {{0x7bc500f6,0xac0992b2,0x672d0009,0x6b580531}}, // _kuhu, енка_, _laaj, _rígi,
+ {{0xeb998b9c,0x68e3a7eb,0x4c158013,0x7bc53e18}}, // ник_, _rend, ابتس, _juhu,
+ {{0xbebb020f,0x320d8358,0x7bc51e8f,0x15f592c7}}, // ndës, ttey_, _muhu, ुंदर_,
+ {{0x68e3810b,0x63bd5324,0x61ed6e76,0x7bc501da}}, // _pend, _misn, nsal, _luhu,
+ {{0x68e3820f,0x2d830052,0x63bd192c,0x68e88365}}, // _qend, jnje_, _lisn, gadd,
+ {{0x68e38e79,0x20010867,0xdee60aac,0x9c7c8132}}, // _vend, nuhi_, лони, _teča,
+ {{0x68e3ce69,0x2121358e,0xfc46928a,0x3ce586b1}}, // _wend, hehh_, žích_, _helv_,
+ {{0x68e88e05,0xfa3482e3,0x7ae98f3e,0x9f60008b}}, // badd, _پرند, maet, _þrír_,
+ {{0x7ae9831d,0x68e88079,0x68e3ee77,0x7bc56e78}}, // laet, cadd, _uend, _buhu,
+ {{0x6d5c6e79,0x63bd1ad4,0x332007e2,0x49b8880b}}, // rkra, _bisn, teix_, _شاهد_,
+ {{0x6e29a52d,0x7ae98355,0x629d0101,0x7bc56e7a}}, // _oseb, naet, _byso, _duhu,
+ {{0x61ed043b,0x3320009f,0xa3ab8bb8,0xd38702c7}}, // [7be0] gsal, reix_, _गठन_, уйте_,
+ {{0x6f099cb1,0x61fb80ee,0x29c980ff,0xdb1988f9}}, // _acec, irul, _múa_, _diwé,
+ {{0x5ecd80ab,0x61ed6e7b,0x7bc502a0,0x2d9a0870}}, // ারনে, asal, _guhu, _ikpe_,
+ {{0x61fb80ee,0x672d0079,0x6db980e1,0x238580eb}}, // krul, _xaaj, _dňam, dējo_,
+ {{0xe505823c,0x7ae98355,0x80e300c8,0x68e88ef2}}, // _राशि_, daet, _পার্, yadd,
+ {{0x2001339d,0xad9b6e7c,0x16c10996,0x3a3ac1f4}}, // buhi_, ltúr, _एल्ब, _čpp_,
+ {{0xbebb020f,0x50b511d2,0x98a300eb,0xa92801a9}}, // ndër, _всту, _tajā_, režģ,
+ {{0x29c9ee7d,0x69c60b3f,0xf3638110,0x2d83007d}}, // _búa_, _kuke, йтын, znje_,
+ {{0xee3aee3e,0xa686891c,0x68e8ee7e,0x7f75813a}}, // _яна_, _слад, tadd, _турц,
+ {{0xd13080d5,0xb426826a,0x7764002a,0x301482fb}}, // آمد_, _معصو, _alix, адор,
+ {{0x68e8d462,0x69c60bc6,0xad9b0e14,0xddd0192c}}, // radd, _luke, ktúr, _češl,
+ {{0xa3c1ae06,0x70e101ce,0x68e89a25,0x7bc56e7f}}, // ंदर_, _फ़्ल, sadd, _ruhu,
+ {{0x62800a8e,0x9f5f026f,0x69c66e80,0x9b9380f7}}, // _žmon, nuté_, _nuke, _النت,
+ {{0x7bc5043d,0x776404c3,0xa903009a,0x672d60c5}}, // _puhu, _elix, _लाइफ_, _waaj,
+ {{0x69c6011e,0x2d832944,0x7ae63df9,0x77644fe2}}, // _auke, rnje_, _bekt, _flix,
+ {{0x61ed65d6,0x2d8300d2,0x69c61f34,0x256600e7}}, // tsal, snje_, _buke, _rôle_,
+ {{0x49750e02,0x6722aeaa,0x63bd2280,0x2d83115c}}, // [7bf0] алас, deoj, _visn, pnje_,
+ {{0x629d0a56,0x61ed1600,0x69c657ba,0xeb21d330}}, // _vyso, rsal, _duke, _मस्त_,
+ {{0x629d0063,0x63bd5fac,0xa0a701cf,0x30a735aa}}, // _wyso, _tisn, ушал, урав,
+ {{0x3ce58a38,0x61ed58e5,0x9c7c99b7,0x6b55c28b}}, // _selv_, psal, _pečn, _lágr,
+ {{0x3d00b2dd,0x1d098b9c,0x2001059e,0x2d768035}}, // _राखे_, вели_, suhi_, ałem_,
+ {{0x6e298db7,0x29c9862f,0xf1a90592,0x9c7cd327}}, // _vseb, _rúa_, _कठिन, _večn,
+ {{0x29c9c92f,0x67d40009,0xfd6201bc,0xe05700d5}}, // _súa_, _госу, _agwọ, _حیات_,
+ {{0x9c7c876c,0xe2998a8e,0x23693091,0x78ba49b3}}, // _tečn, кай_, nhaj_, _útva,
+ {{0xe6678ef5,0x7ae98114,0xa3e58054,0x6e298234}}, // _ство, raet, _बौर_, _useb,
+ {{0x7ae980c6,0xdb0a00e1,0x29058b80,0x50b5869b}}, // saet, ľnéh, ugla_, исну,
+ {{0xa0a5a659,0x68e71abf,0x29058b80,0x670a05fc}}, // ранд, _mejd, rgla_, _वादक_,
+ {{0x29c987f4,0x7523c952,0x68e74c70,0xe8168074}}, // _túa_, lenz, _lejd, _तहरा_,
+ {{0xd945b4d6,0xfd4e846d,0x61e2bf2d,0x91e5951b}}, // _вели, _afiṣ, _gwol, _толе,
+ {{0x68e703fb,0x75239d20,0x3ea90886,0x69c60a03}}, // _nejd, nenz, _dzat_, _ruke,
+ {{0xad9b007b,0x69c60662,0x63630074,0x3ea901a1}}, // ttúr, _suke, _sõna, _ezat_,
+ {{0x7523979f,0xdb02801c,0xe363046e,0x38ac10ab}}, // henz, _khoá, окси, _lórí_,
+
+ {{0x6d418850,0x6b843cc0,0xdb1d01b3,0x7ae66e81}}, // [7c00] _onla, rnig, _misè, _vekt,
+ {{0x2fc79635,0xb17b04b8,0x7ae603f8,0x69c601f4}}, // _kung_, _svår, _wekt, _vuke,
+ {{0x2d9843f7,0x752381dc,0x03a62659,0xb4fb0051}}, // more_, denz, _вино, _בפיי,
+ {{0x6d41a993,0x2fc78077,0x69c60009,0x66028110}}, // _anla, _mung_, _tuke, tuok,
+ {{0x645c423d,0x443a10dc,0xdce182d0,0x38ac0032}}, // lyri, _hrp_, _aklı, _bórí_,
+ {{0x7523b00f,0x660287ac,0x443a602b,0x291802c4}}, // genz, ruok, _krp_, _ubra_,
+ {{0x2fc7809c,0xf8aa00dc,0xa6ca8568,0x7b668113}}, // _nung_, _करिय, _алба_, итие,
+ {{0xe3af85ff,0x9f5f0036,0xaac60035,0x6d41b474}}, // وري_, puté_, वशिक, _enla,
+ {{0x752391e8,0x2d986e82,0x25be8699,0x3f86816b}}, // benz, kore_, _titl_, mnou_,
+ {{0xf8aa2e06,0x2fc78681,0x6b5c8019,0x3f8685b9}}, // _कराय, _bung_, _mégi, lnou_,
+ {{0x2fc78028,0x2d982887,0x6b5ca82c,0x01370039}}, // _cung_, dore_, _légi, ברית_,
+ {{0x2fc78028,0x3f868eb2,0x236903ed,0xdcf88196}}, // _dung_, nnou_, xhaj_, tovė,
+ {{0x7bc8c9f6,0x2d980051,0x2fc7808e,0x7c2200d2}}, // _hudu, fore_, _eung_, _ćora,
+ {{0x2d980578,0x443a6e83,0x236681c0,0x645c3b8d}}, // gore_, _brp_, _hloj_, fyri,
+ {{0x7bc889e1,0xdcfa80eb,0xe516123a,0x2fc789c4}}, // _judu, _aktī, द्धि_, _gung_,
+ {{0x7523e3d1,0x443a0748,0x7bc88b8f,0x88bb81c6}}, // zenz, _drp_, _mudu, _במוז,
+ {{0x3f868400,0x98a3009a,0x78eb0790,0x75239b8f}}, // [7c10] dnou_, _mają_, вьев_, yenz,
+ {{0xb4e58076,0x2d986e84,0x6e2d2bd7,0x2fc782b8}}, // _भजे_, core_, _isab, _yung_,
+ {{0x752392bb,0xd87907bd,0x2fc70028,0x442716ce}}, // venz, دمات_, êng_, _èn_,
+ {{0xf7708872,0x3f990025,0x7523ee85,0x7bd7011b}}, // وان_, nosu_, wenz, _itxu,
+ {{0x7523a3b9,0x7aed3252,0x442cd819,0xd33603c8}}, // tenz, maat, _asd_, _פרשה_,
+ {{0x7bc8812b,0x7aed6e86,0x200e0722,0x645a01ec}}, // _budu, laat, àfic_, ätig,
+ {{0x7523ad08,0x7bc88079,0xf1b20039,0x3f86816b}}, // renz, _cudu, _מסך_, bnou_,
+ {{0x7aed530e,0x5f949d8f,0x7bc895bd,0x7523e9ae}}, // naat, _дист, _dudu, senz,
+ {{0x7523ee87,0x6e2d0555,0x7c3aee88,0x5ecd8264}}, // penz, _nsab, _ertr, ারদে,
+ {{0x6d418461,0x0fd9a1f6,0x2fc79a76,0x6e3bba48}}, // _unla, льмы_, _pung_, _irub,
+ {{0x7bc8dd23,0x6e3b826f,0x6e2d2e5f,0x315800be}}, // _gudu, _hrub, _asab, ייזן_,
+ {{0x32120364,0xdb02801c,0x3204a19c,0x2fc780ff}}, // ytyy_, _thoá, bumy_, _vung_,
+ {{0x2d98063f,0x98a587d9,0x657c1b03,0x443a10d1}}, // tore_, rklı_, tirh, _srp_,
+ {{0xceb28051,0x645c6e89,0x6b5cee8a,0x61d68051}}, // פים_, tyri, _régi, _הוסף_,
+ {{0x2d986e8b,0x2d879a29,0xa3e20006,0x645c8074}}, // rore_, inne_, नीं_, _ürit,
+ {{0x2d986e8c,0x2005ee8d,0x256b8bc5,0x7aed254a}}, // sore_, muli_, _følg_, gaat,
+ {{0x3f86826f,0xa1c311d0,0x2005b14f,0x7f440037}}, // [7c20] vnou_, збуд, luli_, _iniq,
+ {{0x9c7c82ee,0x66063a9f,0x6f1bee8e,0x41df864a}}, // _dečj, mukk, _abuc, पीएस,
+ {{0x20058025,0xb17a893f,0x66060a52,0x7aed02a3}}, // nuli_, נטער, lukk, baat,
+ {{0x7aed6e8f,0x442c82ee,0x7bc8a060,0x2d87ee90}}, // caat, _rsd_, _rudu, enne_,
+ {{0xf2d3093f,0x63a42280,0xb1480077,0x7bc8c9c6}}, // _מער_, mlin, _پیام_, _sudu,
+ {{0x442cee91,0x78ba0019,0xf9879a00,0xbbe900d7}}, // _psd_, _útvo, _رب_, _حریم_,
+ {{0x3d0981fe,0x8884026a,0x6e3bba48,0x63a42f4b}}, // _सारे_, _بیان, _erub, olin,
+ {{0xd3268cde,0x66062460,0xbf16803d,0x51f88009}}, // ськи, kukk, _دورب, анию_,
+ {{0x6e3bee92,0x66062d27,0x7bc8a88e,0x637e8110}}, // _grub, jukk, _wudu, gūna,
+ {{0xb5fb0117,0x7bc8b550,0x63a452b2,0xa3a88127}}, // zzás, _tudu, hlin, _खीर_,
+ {{0x63a40e51,0x319b00be,0x20059598,0x63b6007b}}, // klin, רבינ, guli_, kmyn,
+ {{0xb76702a9,0xd0418085,0x66065998,0xad9b01d6}}, // стой, nclə, fukk, krúc,
+ {{0x63a40c63,0x7aed2b56,0x7a368875,0x88c000ab}}, // dlin, vaat, _دعائ, েরিক,
+ {{0x200582ec,0x7aed138a,0x9c7c8b67,0xb90c019d}}, // buli_, waat, _rečj, rahụ_,
+ {{0x2005b08f,0xd0b28085,0x317b81c6,0xa3dc8072}}, // culi_, yğəm, _תרומ, तीच_,
+ {{0x63a46e93,0xe3b0190c,0x63b6007b,0x317eee94}}, // glin, فرق_, gmyn, litz_,
+ {{0x6e2d2bd7,0x3d098e5b,0x9955110b,0x9c7c812b}}, // [7c30] _usab, _साले_, окац, _bečk,
+ {{0x9c7c8353,0x7aed04a2,0xf77f27e9,0xed59992c}}, // _večj, saat, liç_, _tuži_,
+ {{0x9c7c8d2f,0x63a44968,0x6d450367,0x7aed4c37}}, // _dečk, blin, _inha, paat,
+ {{0x2d8c8d2f,0xceeb8077,0x7aed01b4,0x657ac7fd}}, // _ajde_, اران_, qaat, _amth,
+ {{0x2005ee95,0xf8ae053d,0x44211cfe,0x799ad9f6}}, // zuli_, _یکم_, _hph_, kotw,
+ {{0x9989009a,0x637e8084,0x81d40b69,0x23d5bacb}}, // stał_, vūna, _хорх, оцир,
+ {{0x4c6980e5,0x7afd82b7,0x6b5c80e7,0x442100b9}}, // рийн_, ósto, _légu, _jph_,
+ {{0x6fcfa6ee,0xe1f9a3fc,0x6f1b9d61,0x657a8234}}, // _संबं, аги_, _ubuc, _emth,
+ {{0x6e3b82a0,0xc8da816f,0x291cc64c,0x7769802a}}, // _urub, _म्हट, _bbva_, _ilex,
+ {{0x3eadee96,0x2005c86b,0x69dbc752,0x7f56011c}}, // _izet_, tuli_, rpue, _soyq,
+ {{0x69db80c8,0xcea90451,0xdb0400f7,0xfd1080f7}}, // spue, _סי_, rmiú, _سجل_,
+ {{0x60dc884a,0xae0d81a2,0x6d452b29,0x63a46e97}}, // ırma, _समान_, _anha, xlin,
+ {{0x3f9f80f6,0x63a46e98,0xe8ee951b,0xd1098077}}, // _mkuu_, vlin, _ол_, _نقشه_,
+ {{0x660606d9,0x6b898e8b,0xafe59cce,0x63a43ada}}, // rukk, lneg, _докл, wlin,
+ {{0x66060d7a,0x68e19fa6,0x63a432c7,0x6b5c846d}}, // sukk, mbld, tlin, _eégu,
+ {{0x44210a56,0xe80d035a,0x69cbabd4,0xf8b28039}}, // _dph_, ांना_, _kuge, _קשה_,
+ {{0x7aeb8065,0x7bdc1614,0x69cbee99,0xfaa317cb}}, // [7c40] _megt, spru, _juge, дато,
+ {{0x69cbc141,0x7aeb9529,0x3f9f808e,0xdb0b8722}}, // _muge, _legt, _akuu_, _aigü,
+ {{0x69cbee9a,0x3ead81ac,0x1c1e0074,0x9c7c807a}}, // _luge, _azet_, _पहिल_, _večk,
+ {{0xee3a8374,0xe29a2386,0x69c39140,0xed598669}}, // ине_, _вам_, _line, _mužu_,
+ {{0x6b898e8b,0xe297102a,0x69c3811b,0x7c218176}}, // dneg, _маю_, _oine, _aplr,
+ {{0xafe614d6,0x7bc2ee9b,0x77699c4b,0x463b00be}}, // _могл, _siou, _elex, יענע,
+ {{0x317e9ebf,0x69cbc56c,0xdcf50087,0x77699ed3}}, // vitz_, _auge, liză, _flex,
+ {{0x395802be,0x69cbdb07,0x69c3a551,0x6b89ddf0}}, // _hors_, _buge, _aine, gneg,
+ {{0x69c3ee9c,0x914aa5af,0xf77f6e9d,0x39586e9e}}, // _bine, ачна_, viç_, _kors_,
+ {{0x55038153,0x6b5cd568,0x9c7c8bda,0x657a805d}}, // _опуб, _régu, _leči, _umth,
+ {{0x69c3aeaa,0x6b899840,0xcb6a9fab,0x6b5c887a}}, // _dine, bneg, _каже_, _ségu,
+ {{0x69c38943,0x2d9c803b,0x395802be,0x6b9b85db}}, // _eine, love_, _lors_, coug,
+ {{0x443ec3c9,0x3ead013c,0x69cbc50b,0x224b8106}}, // _hrt_, ået_, _guge, äckt_,
+ {{0x7d1b005c,0x395801e2,0x4421008e,0x27692181}}, // đuso, _nors_, _pph_, _گشتی_,
+ {{0xd37b045d,0x69cb82af,0x9c7c80d2,0xdea40326}}, // ача_, _zuge, _beči, _maiƙ,
+ {{0x56950364,0x69c3c717,0x443eee9f,0x3a2202f7}}, // _найт, _zine, _mrt_, _bpkp_,
+ {{0x9c7c82ee,0x60f901bb,0x39586ea0,0x4fb40a47}}, // [7c50] _deči, йная_, _bors_, _تصور,
+ {{0x443eeea1,0xad9b0125,0x7bda81b9,0xc4c48065}}, // _ort_, brúa, _ottu, _لے_,
+ {{0x2d9ca8fc,0x4421008e,0x39586551,0xced88133}}, // dove_, _uph_, _dors_, _ịzụr,
+ {{0xe80c0665,0x6b5c8061,0x6602e4d2,0xf1bf1a5d}}, // _हमरा_, _mégs, frok, _spád_,
+ {{0x6b898db7,0x7bdaeea2,0x7aeb879f,0x662780f7}}, // vneg, _attu, _regt, تراك,
+ {{0xa3e2009a,0x69cb80b4,0x9c7c8301,0x6b898035}}, // नीक_, _ruge, _zeči, wneg,
+ {{0x6b899840,0x69c3dbda,0xdb1d0118,0x81cd00ab}}, // tneg, _rine, _lisí, _রবি_,
+ {{0x69c381bf,0x3f9fac07,0x66029a62,0x69cbbb11}}, // _sine, _ukuu_, brok, _puge,
+ {{0x8cd883bb,0x34a803bb,0x6b899ce9,0x443e86db}}, // नुहो, _गर्द, rneg, _ert_,
+ {{0x69c38a2a,0x8af00085,0x290c8106,0x6b89eea3}}, // _qine, nkər, ggda_, sneg,
+ {{0xe73a00b3,0xb4e7d78b,0xc7a60dc7,0x225f87d5}}, // бен_, परी_, зинк, syuk_,
+ {{0x69cbdafe,0x88d100ab,0x224b8338,0x52758087}}, // _tuge, িরিক, äcks_, _нулу,
+ {{0x69c3ad7b,0x1dcb8074,0x9c7ceea4,0xe28ea762}}, // _tine, ादित, _reči, _ча_,
+ {{0xe80c023c,0x2eeda4c2,0xd25684de,0xad9b008b}}, // _हमला_, _leef_, _משנה_, trúa,
+ {{0x98a28d5f,0x80aa009a,0x70aa0006,0x2b590722}}, // _пише, _करें, _करेल, _bosc_,
+ {{0x2d9c8024,0x39586ea5,0x1dcb80d4,0x3a2200ee}}, // zove_, _sors_, ादात, _ppkp_,
+ {{0x3f826ea6,0xe8fa02a9,0x9c7c8353,0x020604d9}}, // [7c60] miku_, йла_, _veči, язан,
+ {{0xdce38110,0xe1f705e9,0x59ce864a,0x9f5f04e8}}, // minė, ягу_, _हंसर, vrté_,
+ {{0x2d9c85f5,0xdce38110,0xb4d823bd,0x64a61246}}, // vove_, linė, ाडी_, _хана,
+ {{0x6602eea7,0xe5c684ae,0x79818bb1,0xed5881d6}}, // trok, пско, hilw, _soľ_,
+ {{0x3958364c,0xdce38110,0xe5348009,0x6736008e}}, // _tors_, ninė, дель, _mayj,
+ {{0x6602d149,0x7bc601e0,0x09da80ab,0x237f1ee0}}, // rrok, _hiku, _দিলা, ďuje_,
+ {{0xdee69b95,0x66e69597,0x7bc6426d,0x3f823f12}}, // _нови, _нова, _kiku, kiku_,
+ {{0x6602eea8,0x07a38638,0xe76a9381,0x443ee9cd}}, // prok, _засн, _احسن_, _vrt_,
+ {{0xa2a20592,0x0e6681e2,0xa6aa990c,0x7ae46ea9}}, // _कुम्, _экан, _طارق_, ibit,
+ {{0x7bc66eaa,0x4973eb31,0xdce38084,0x443eb6b2}}, // _liku, ельс, dinė, _trt_,
+ {{0x3f822316,0xa3ac0327,0x9ad301bc,0x6b5c8036}}, // fiku_, गता_, _kịta, _dégr,
+ {{0x290283bf,0x6d5aeeab,0xdb0400e7,0x69da001b}}, // şka_, _hota, blié, _čten,
+ {{0x63a2eeac,0x61469a0b,0xd05c0201,0x8f9b83c8}}, // _ikon, _неда, _verə, מיטי,
+ {{0x6d5a8364,0x7bc60009,0xe5c40198,0xdb1d29c3}}, // _jota, _aiku, ессо, _visí,
+ {{0x6d5a86c1,0xaeec00c8,0x7bc6020c,0x7f592318}}, // _mota, _কারণ_, _biku, _макс_,
+ {{0x637104b8,0xdb1d003e,0x26c91f3a,0x7bc60573}}, // _mång, _tisí, _ugao_, _ciku,
+ {{0x2aab0758,0x637104b8,0x6d489c35,0x63a2822e}}, // [7c70] ство_, _lång, _onda, _mkon,
+ {{0x6d5ae70d,0xd1388110,0x636302f1,0x6f1d14cf}}, // _nota, ndą_, _mõni, lfsc,
+ {{0x2d832e96,0x7bc66ead,0x7bc300e5,0xb9c401a8}}, // mije_, _fiku, _ènuo, رقمي,
+ {{0x6d48eeae,0x7bc60959,0xd36680d7,0x6d4f0699}}, // _anda, _giku, _شه_, škač,
+ {{0x6d5ac395,0x44330683,0x6b8d0362,0x80c58e1b}}, // _bota, _hsx_, mnag, লুপ্,
+ {{0x64a5bb17,0x63a2849f,0x6d5aa2a0,0xdca58256}}, // мала, _akon, _cota, мали,
+ {{0x6d5a8b91,0x26d287d9,0x7c22017f,0x764085ee}}, // _dota, ıyor_, _ćork, _ermy,
+ {{0x6b8d6eaf,0xc24584dd,0x2d836eb0,0xe9d992b2}}, // nnag, дник, hije_, оки_,
+ {{0x2d8300b4,0x69cf02ce,0x69c731ac,0x6b8d473a}}, // kije_, _juce, _kije, inag,
+ {{0x63710370,0x3f8202f7,0x2d830c97,0xdce38084}}, // _hånd, wiku_, jije_, vinė,
+ {{0x2d8302a5,0x637104b8,0x69c702fd,0x69cf0baf}}, // dije_, _gång, _mije, _luce,
+ {{0x69c7030b,0xdce381e2,0xdb0f0118,0x69cf0118}}, // _lije, tinė, _micó, _ouce,
+ {{0x27e0803a,0x63710448,0x69dd10af,0x7bc66eb1}}, // _čine_, _månd, _ntse, _riku,
+ {{0x2d836eb2,0x69c71eb9,0x7bc66eb3,0xdce38110}}, // gije_, _nije, _siku, rinė,
+ {{0x3f8207d8,0xdca312b2,0x64a30eab,0x69dd2a6b}}, // piku_, вари, вара, _atse,
+ {{0x63a9b816,0x69cf2934,0x6b8d6eb4,0x672b80f1}}, // nlen, _buce, gnag, jegj,
+ {{0x2d833f0f,0x69c71351,0x7ae43a70,0x7bc66eb5}}, // [7c80] bije_, _bije, rbit, _viku,
+ {{0x69c7030b,0x63a9d2b2,0x69cf08e4,0x657e005d}}, // _cije, hlen, _duce, _umph,
+ {{0x69c76eb6,0xb8f608fd,0x63a9eeb7,0x443c0019}}, // _dije, _हल_, klen, _év_,
+ {{0xdb1d03a7,0xadeb03b6,0x6d5aeeb8,0x69cf00e5}}, // _visã, जीवन_, _sota, _fuce,
+ {{0x2bd210be,0xf1af0701,0x64419c80,0x4425eeb9}}, // _दूता, _जीवन, _arli, _apl_,
+ {{0x63a2eeba,0x6b843bf0,0x7e62810c,0x64418115}}, // _skon, liig, ryop, _brli,
+ {{0xa2a21094,0x63a9ab3d,0x20058267,0x7e6282d6}}, // _कुत्, flen, jrli_, syop,
+ {{0xdb0d0003,0x5a3497f9,0x63bbeebb,0x6d5a0074}}, // rmaç, енит, gmun, ötaj,
+ {{0x64419d6a,0x4425b04e,0x200580e5,0x6eaa0072}}, // _erli, _epl_, erli_, _घरगु,
+ {{0x6d48eebc,0x7bde0024,0x213800dd,0x58868196}}, // _unda, _otpu, _jarh_, дыма,
+ {{0x2d83003b,0x63a9eebd,0x9c7ceebe,0x776d002a}}, // vije_, blen, _peču, _plax,
+ {{0x6d0e8076,0x63a284e8,0x224982a5,0xd7cf8aad}}, // _साँग_, _ukon, _šake_, _संरच,
+ {{0x2d8300ce,0x9f6580e7,0x200581e8,0x7aef05d8}}, // tije_, étés_, arli_, _rect,
+ {{0x63710a38,0x7aef4918,0xe894917c,0x60178870}}, // _måne, _sect, _паль, ụmah,
+ {{0x69c7030b,0xd7ef8088,0xe5718158,0x63710370}}, // _rije, _шу_, אַן_, _låne,
+ {{0x2d8302a5,0x69c7111b,0x200ceebf,0x69cf34d3}}, // sije_, _sije, mudi_, _puce,
+ {{0x9ccb0087,0xe1f00064,0x7afdaa63,0x2eba816f}}, // [7c90] _мына_, لسل_, ósti, ेशोत,
+ {{0x9f45877f,0x63bbeec0,0x69cf0144,0x63a9eec1}}, // _ìlú_, zmun, _vuce, zlen,
+ {{0x69c7030b,0x200c803b,0xb4eb0054,0x2fc901c5}}, // _vije, nudi_, मरी_, _kiag_,
+ {{0xf8df085d,0x20df0bb8,0x28df0aed,0xb0df06bf}}, // _प्रय, _प्रध, _प्रि, _प्रग,
+ {{0x69c7030b,0x200cc9ec,0xf773804e,0x69dd0b64}}, // _tije, hudi_, _یار_, _utse,
+ {{0xee389439,0x660b837a,0x2fc923f0,0x212c8168}}, // ьні_, rugk, _liag_, jedh_,
+ {{0x63a9eec2,0x43698607,0x03a596df,0x2d8100b9}}, // tlen, зайн_, нико, _mmhe_,
+ {{0x644180d2,0x2fc90069,0xdb1e0118,0x42559c79}}, // _vrli, _niag_, _supú, етот,
+ {{0x63a9eec3,0xcad281bc,0xa3b60035,0x0e8e882e}}, // rlen, _pịrị, _चीफ_, sịrị_,
+ {{0xa3b605fb,0x7d798eca,0x63bbeec4,0xa3cf0072}}, // _चीन_, _عمار_, smun, शदा_,
+ {{0x3ea914ec,0xe73a302b,0x63bbe193,0x2fc902d5}}, // _ayat_, пен_, pmun, _biag_,
+ {{0xe73a33d9,0x660004e8,0xd6d201a8,0x9c7c8af8}}, // чем_, ámko, طقس_, _večt,
+ {{0x2b1486b7,0x2fc96ec5,0x15ba0009,0xd7f88087}}, // _धातु_, _diag_, зывы_, asă_,
+ {{0x636302f1,0x200582d4,0x332d857b,0xfe7f0580}}, // _mõnu, prli_, leex_, taïr_,
+ {{0x6ea00eed,0xc4d281c6,0x3f6a0eef,0xa5bb066f}}, // _गुरु, רגל_, _нико_, twór,
+ {{0x92c380c8,0xe9da09c7,0x636a016b,0x645c87f1}}, // ্রী_, зка_, _výno, _àrie,
+ {{0x6b840074,0xe1f98084,0x67240314,0xe2972934}}, // [7ca0] riig, nsų_, _ibij, еат_,
+ {{0x850e8fea,0xdff38441,0x2fc9022c,0x76440314}}, // _साइट_, _आबाद_, _ziag_, _iriy,
+ {{0x2fc9022c,0xaadf801b,0x63a0b327,0xaf068162}}, // _yiag_, नडाक, yomn, епел,
+ {{0x444462fb,0x09e60912,0x91e61232,0x68e3017b}}, // _ir_, _помн, _поме, ında,
+ {{0x6d5e3730,0xa3bd0105,0x163492c0,0x67244959}}, // _kopa, इगर_, теля, _mbij,
+ {{0x44444f3c,0x61e425cb,0x256f02f1,0x26cd8037}}, // _kr_, mpil, _küla_, _egeo_,
+ {{0x6724017f,0x6d5e445f,0x69d600f1,0x63a0eec6}}, // _obij, _mopa, lqye, tomn,
+ {{0x31c8c476,0xd3779d51,0x44440039,0x25740338}}, // रद्ध, ечь_, _mr_, _fälg_,
+ {{0xc2988765,0xdb040118,0x6d4b81a8,0x22406ec7}}, // ьких_, nmiñ, álaí, nvik_,
+ {{0x4444073a,0xc27b0451,0x200ceec8,0x63a080e1}}, // _or_, פריי, tudi_, somn,
+ {{0x63a0cda9,0x76445f58,0xb4cc8744,0xe8948110}}, // pomn, _ariy, रखी_, тань,
+ {{0xc6a7067c,0x200c98c3,0x4ea73d73,0xd7f88162}}, // ерги, rudi_, ерга, rsă_,
+ {{0x44446ec9,0x753a81ec,0x3d178074,0x200cb6d9}}, // _ar_, _katz, _नामे_, sudi_,
+ {{0x44443eb7,0x238c81d0,0x22402127,0xb4eb12c6}}, // _br_, ději_, dvik_, मरे_,
+ {{0x97a716d9,0x77930bca,0x850e858c,0x3d12058c}}, // _прил, _میدا, _साईट_, _तारे_,
+ {{0x53340785,0x644305e4,0xe3b305ff,0x9b580dc0}}, // лест, _ánim, ارس_, нист_,
+ {{0x444415f8,0x764401bf,0x61e41066,0x69cab1ac}}, // [7cb0] _er_, _griy, gpil, _kife,
+ {{0x7c28eeca,0x23690933,0x93bc8162,0x7cd90198}}, // _opdr, skaj_, _spăl, _юмор_,
+ {{0x44445fdf,0x6b82895e,0x63630074,0x661d2b3a}}, // _gr_, _omog, _sõnu, ltsk,
+ {{0xdddd009a,0xb4bf06bf,0x661d5db3,0x673d1388}}, // mysł, ेशी_, otsk, ndsj,
+ {{0x753a80ad,0x63ad4482,0x660f1c33,0x7c28a52a}}, // _batz, mlan, nuck, _apdr,
+ {{0x44442065,0x63ad0b23,0x69ca831d,0xa2c48c1c}}, // _yr_, llan, _nife, ाइन्,
+ {{0xeb9980d6,0x32090e35,0x63ad6ecb,0x69aa0beb}}, // мик_, lray_, olan, _जीटी,
+ {{0x3f86eecc,0xdb009f1b,0x0b4582df,0x661d0687}}, // ciou_, nomè, тнин, ktsk,
+ {{0x63ad2bd3,0xdb1aeecd,0x660f02a5,0x27e08042}}, // ilan, _autó, juck, _čina_,
+ {{0x63ad029b,0x673b81e9,0x7f4d016a,0x672401a1}}, // hlan, _hauj, _anaq, _sbij,
+ {{0x2d87acdd,0x66e606cf,0x644501e9,0xdee62748}}, // mine_, кона, _nrhi, кони,
+ {{0x63ad2126,0x4444378a,0x6d5e3a66,0x764405cb}}, // jlan, _rr_, _sopa, _priy,
+ {{0x4444239f,0x2d87803c,0x7ae9eece,0x69ca9f19}}, // _sr_, oine_, mbet, _fife,
+ {{0xdb1d025d,0x444408f7,0x7ae98b81,0x673bbf73}}, // _lisä, _pr_, lbet, _lauj,
+ {{0xdca60284,0x64a62549,0x61fd1f20,0x68f70079}}, // тави, тава, _avsl, daxd,
+ {{0x673b8a8e,0x6724003b,0x2d87eecf,0x320919eb}}, // _nauj, _ubij, hine_, fray_,
+ {{0x6d5e3fae,0x7bcbcef1,0x644501ec,0x3209057b}}, // [7cc0] _topa, _migu, _erhi, gray_,
+ {{0x44446ed0,0x63ad3f8c,0x2d87eed1,0x7af602c4}}, // _tr_, alan, jine_, sayt,
+ {{0x4444477d,0x2d878ee0,0x61e46ed2,0x61fb8085}}, // _ur_, dine_, spil, hsul,
+ {{0x6a860436,0x63ad2aa4,0x7bcbb787,0xe809a3bd}}, // _алма, clan, _nigu, _विना_,
+ {{0x9f594bac,0x63a42b92,0x63758c58,0x7ae984fe}}, // éré_, moin, _mánd, dbet,
+ {{0x63a4271b,0x7bcb87fc,0x2d879e68,0xb4cc816f}}, // loin, _aigu, gine_, रखे_,
+ {{0x69ca939a,0xbef2016f,0xe9df001c,0x291808c5}}, // _rife, _अजून_, _trúc_, _icra_,
+ {{0x442084b7,0xdb1a8722,0xdcee00e1,0x63a40198}}, // ċi_, _autò, jobľ, noin,
+ {{0x7bcb8859,0x171b8158,0x61fb97ef,0x75828fd3}}, // _digu, _קומע, gsul, _پیغم,
+ {{0x63ad6ed3,0x673b9dc1,0x63710687,0x717480f7}}, // zlan, _zauj, _måna, اهدا,
+ {{0x63ad6ed4,0x6375eed5,0x63a40009,0x7bda016a}}, // ylan, _bánd, koin, ítul,
+ {{0x7ae99a2e,0x7bcbc2c6,0x6375a509,0x63ad0085}}, // cbet, _gigu, _cánd, xlan,
+ {{0xa2a2159a,0x5f9450f6,0x6375a848,0x673d03ba}}, // _कुल्, рият, _dánd, rdsj,
+ {{0x661d003b,0x61e2eed6,0x63ad6ed7,0x58d3b031}}, // rtsk, _itol, wlan, _кошт,
+ {{0x63ad3aaa,0x661d2a07,0xb4bf0c28,0x7f5f011c}}, // tlan, stsk, ेशे_, _toqq,
+ {{0x63ad1793,0x7988c742,0x637583a8,0x7f4d07f1}}, // ulan, lidw, _gánd, _unaq,
+ {{0x80d70424,0x2d878169,0x29180144,0xdb04008b}}, // [7cd0] _बलदे, yine_, _bcra_, rmið,
+ {{0xdb1d025d,0xd7e7035f,0xe9df002a,0xdb0085ec}}, // _sisä, відо, _crúa_, llmä,
+ {{0x31c42410,0x201e8009,0x7ae98214,0x77b60118}}, // рств, ntti_, ybet, láxa,
+ {{0x201e8364,0x63ad011c,0x61e2eed8,0x2d87dd03}}, // itti_, qlan, _otol, wine_,
+ {{0xf770819f,0x7bcb8098,0xfe7080a0,0x63b9831d}}, // غام_, _rigu, يده_, _rhwn,
+ {{0x2cac8355,0xe9df0020,0x7ae98428,0xa97903de}}, // _bydd_, _grúa_, wbet, _טאָכ,
+ {{0x2d878315,0x7ae99c11,0xdb040019,0x61e2eed9}}, // rine_, tbet, lliá, _atol,
+ {{0x2d87eeda,0x2cac831d,0x61fbcbf9,0x2246d994}}, // sine_, _dydd_, tsul, _brok_,
+ {{0x7ae98422,0x2d87c319,0x68f50065,0xe7849a02}}, // rbet, pine_, _kezd, _туро,
+ {{0x2cac831d,0x61fbafdb,0x68f51d40,0x7bcb8d56}}, // _fydd_, rsul, _jezd, _wigu,
+ {{0x7bcb833e,0x61e28133,0x61fb8170,0x378a05c2}}, // _tigu, _etol, ssul, ебно_,
+ {{0xeeeb0104,0x2018809f,0xd24e826a,0x77b88216}}, // ường_, ària_, آنی_, píxe,
+ {{0x5fcf8701,0x6561a651,0x79889a72,0x63a46a78}}, // _संकल, _kolh, bidw, voin,
+ {{0x6371016d,0x6b89eedb,0x39588106,0x5bbe0c32}}, // _råna, lieg, örst_, ्द्व,
+ {{0x232a3408,0xab2a013a,0x63a46edc,0x656183a7}}, // нови_, нова_, toin, _molh,
+ {{0xf0928051,0x69c18214,0x6b89d6bd,0x29030019}}, // _לנו_, lmle, nieg, _adja_,
+ {{0xf2d28159,0x63a464e3,0x68f5188a,0x256f0006}}, // [7ce0] מען_, roin, _bezd, _küll_,
+ {{0x63a42cf0,0xdb00ad5d,0x6b89811b,0x764b0e06}}, // soin, romé, hieg, _ágya,
+ {{0x6b898063,0x9d1b00be,0x776081c0,0xf3f100ff}}, // kieg, _אויט, _pomx, ật_,
+ {{0x2d8a6edd,0xe9df016a,0xdfcf01a8,0x6b89eede}}, // libe_, _irún_, سيه_, jieg,
+ {{0x2cac8355,0x65618db7,0x6f0400b9,0xdb07002a}}, // _sydd_, _bolh, _mdic, _émái,
+ {{0x656183a7,0x660480e1,0xad9b01a8,0x201eb095}}, // _colh, šiko, trúi, ytti_,
+ {{0x3a202cb1,0x798881b9,0x50b8064a,0xd90d00b7}}, // ltip_, vidw, _अरिष, _زین_,
+ {{0x40930013,0x69ce6edf,0x3f84ba20,0x2246808e}}, // _الفر, _hibe, _ummu_, _prok_,
+ {{0x69ce620b,0x49748153,0x65618073,0xc174d0f6}}, // _kibe, илис, _folh, илищ,
+ {{0x6f04241f,0x2ee000dd,0xdb1e016b,0x753e008e}}, // _adic, _afif_, _kupó, _napz,
+ {{0xa3c40063,0x69ce6ee0,0x6b89eee1,0x7d1a80b9}}, // एगा_, _mibe, bieg, _icts,
+ {{0xa3d81d40,0x7c3ab642,0x236d81ac,0x201eecd0}}, // ादन_, _istr, ckej_, rtti_,
+ {{0x11548607,0x61e28019,0xb8cb9d17,0xf1ba801c}}, // _вклю, _utol, _कु_, _thơ_,
+ {{0x6f0405dc,0xda7b8051,0x442ca08b,0x1c18bc4f}}, // _edic, _אנדר, _kpd_, _धमाल_,
+ {{0x443a6ee2,0x68faeee3,0x80db81c6,0x753e6ee4}}, // _csp_, matd, _אחוז, _dapz,
+ {{0x1dd48996,0x68fa829a,0xdb198176,0xdb2181d6}}, // ददात, latd, _chwè, _štúr,
+ {{0x69ce3806,0xa2c0053e,0x8d870adb,0x50f51ed1}}, // [7cf0] _bibe, लेल्, _бунд, изат,
+ {{0x637807f4,0x2d8a040e,0x3f8b003a,0xa5bb11b9}}, // _aínd, cibe_, licu_, stób,
+ {{0x69ce0812,0x443a3f21,0xdb1e01ca,0xb0353d0a}}, // _dibe, _gsp_, _cupó, рнеш,
+ {{0x3f8b28fc,0x68fa829a,0x3ead8168,0x3d1b0074}}, // nicu_, hatd, _pyet_, _बाडे_,
+ {{0x69ce3ada,0xe8010105,0x63780032,0x68fae49f}}, // _fibe, _लटका_, _dínd, katd,
+ {{0x69ce12e5,0x7aed0c39,0x6b89b975,0x6443826c}}, // _gibe, lbat, wieg, tvni,
+ {{0x636a007b,0x3f8b00ce,0x6b898f67,0x6561874c}}, // _sýni, kicu_, tieg, _volh,
+ {{0x442ca9fb,0x3f8b003a,0x7aed0102,0x6443d5ee}}, // _dpd_, jicu_, nbat, rvni,
+ {{0x7c3ab83c,0x3f8b0067,0x64488102,0x7bcf6ee5}}, // _estr, dicu_, _erdi, _micu,
+ {{0x7aed008e,0x236d81ac,0x7bcf3bfe,0x636a00e1}}, // hbat, skej_, _licu, _výni,
+ {{0x6e2d14ff,0xb8dd904f,0x637585a4,0x6b898f7d}}, // _apab, _आर_, _cánc, pieg,
+ {{0x76498205,0x443a6ee6,0xc7a600e8,0x3f8b05f3}}, // _krey, _rsp_, римк, gicu_,
+ {{0x6d890025,0x68fa829a,0xee3733c2,0x6b5c8061}}, // ržan, batd, аня_, _négy,
+ {{0x61e9eee7,0x7afb8364,0xeeeb0028,0xb17b0106}}, // mpel, laut, ưởng_, _stån,
+ {{0x2d8a3ec3,0x69dbeee8,0x69ce4d2c,0x63786ee9}}, // ribe_, lque, _ribe, _líne,
+ {{0x7afb8e37,0x6f042cf7,0x69ce083a,0x7aed31f2}}, // naut, _udic, _sibe, gbat,
+ {{0x69db9e9e,0xdb1e04c3,0x7bcf0458,0x69ce1e2c}}, // [7d00] nque, _supó, _dicu, _pibe,
+ {{0x60e71505,0x7aed11c9,0x6f1bbcd4,0x6729da98}}, // имум_, abat, _acuc, _abej,
+ {{0x7aed00a4,0x20560f04,0x7afbde4e,0x645883a8}}, // bbat, штар, kaut, xxvi,
+ {{0x76498125,0xf8b28039,0x70d2d59b,0x7afbac28}}, // _brey, חשב_, _सल्ल, jaut,
+ {{0x69ce3f49,0xee7a95a8,0x28b80540,0x76498216}}, // _tibe, خصات_, _अरवि, _crey,
+ {{0x442cae43,0x3f8b012b,0x7649a445,0x7bd7011c}}, // _ppd_, zicu_, _drey, _yuxu,
+ {{0x7afb822e,0x998d809a,0x6e3b9793,0x6d89026c}}, // faut, steś_, _esub, džal,
+ {{0x200c8289,0x7649808b,0x7afbeeea,0x61e6008e}}, // vrdi_, _frey, gaut, _ktkl,
+ {{0x764990c9,0x637585e4,0x32000176,0x7e60016d}}, // _grey, _sánc, nsiy_, ämpl,
+ {{0x7aed0087,0xd90e804e,0x6448807a,0x200c82d4}}, // zbat, ذیب_, _trdi, trdi_,
+ {{0x7c3a9de3,0x68fa811c,0x7afbeeeb,0x6448eeec}}, // _ustr, ratd, baut, _urdi,
+ {{0x61e60067,0x44380114,0x69db8363,0x77640980}}, // _otkl, dwr_, bque, _coix,
+ {{0xa11683f8,0xdb00840e,0x3f8b003a,0x69db93ff}}, // _نوشت, nomí, ricu_, cque,
+ {{0x7bcf0698,0x3f8b00fe,0x61e90315,0xac191170}}, // _sicu, sicu_, _çeli, логу_,
+ {{0x61e60029,0x999980e1,0x44380114,0x7bcf0957}}, // _atkl, ísť_, gwr_, _picu,
+ {{0x88778039,0x8c3c8085,0x539800e8,0x7f40913b}}, // _כתוב_, _uyğu, ався_, _namq,
+ {{0x7bd702c1,0x7aed6eed,0x7bcf38b4,0x49750110}}, // [7d10] _wuxu, rbat, _vicu, блас,
+ {{0x6375807b,0x7aed6692,0x9f588081,0x2fd86eee}}, // _mána, sbat, _avrà_, _burg_,
+ {{0x7bdea5a7,0x69db8feb,0xd246815b,0x75356eef}}, // ípul, zque, _کن_, mezz,
+ {{0x6e44003d,0x753567b6,0x7649eef0,0x68f89235}}, // _انیم, lezz, _prey, _levd,
+ {{0x6375807b,0xfce582a4,0x6b8d2763,0x0356007c}}, // _nána, боло, miag, _הירש_,
+ {{0xa80501df,0x290a91b9,0x75354555,0x063803de}}, // _coñé, óbal_, nezz, ינדט_,
+ {{0x7afb9608,0x69c520ad,0x28c303eb,0x65650372}}, // taut, mmhe, वेशि, _mohh,
+ {{0x6b8d4bbd,0x75350081,0x20011f33,0xd49802f6}}, // niag, hezz, mshi_, יכות_,
+ {{0x8e8580f7,0x637581a8,0x656502d5,0x81df8264}}, // _الذه, _cána, _oohh, ধীন_,
+ {{0x69db9ebb,0x6b8d00ad,0x61e9eef1,0x320deef2}}, // rque, hiag, rpel, rrey_,
+ {{0x69dba321,0xa0a5a597,0x6d419cbc,0x200167e0}}, // sque, санд, _iala, nshi_,
+ {{0x6d41b492,0x61e987b3,0xb17b01a3,0x2001004f}}, // _hala, ppel, _stål, ishi_,
+ {{0x6d419904,0xaaa7016f,0x75353333,0x200105ee}}, // _kala, _कुलक, fezz, hshi_,
+ {{0x6d41ad3a,0x6d43eef3,0x63a98fca,0x3d1200c2}}, // _jala, ndna, loen, ़लें_,
+ {{0xdd8f0b76,0x69d98c85,0xa3b3016f,0xf3f10129}}, // روف_, _huwe, टतं_, ập_,
+ {{0x6d41c786,0x69d9c9a0,0x660f026c,0xda030074}}, // _lala, _kuwe, brck, _लिखत_,
+ {{0x69d9a0b5,0x7af982a6,0x3de200ab,0x64430115}}, // [7d20] _juwe, _mewt, _বিকল, _šnit,
+ {{0x63a980f3,0x865b0039,0x69d9eef4,0x3a3d80ee}}, // hoen, _עדיי, _muwe, _mswp_,
+ {{0xa2b40701,0x63a99fdc,0xa3b780d4,0x69d9c50b}}, // _आरक्, koen, छता_, _luwe,
+ {{0x7c3e003a,0x6d41d099,0x69c5037a,0x66040a35}}, // _ispr, _aala, amhe, _kvik,
+ {{0x471b0158,0x63a98cfa,0xa805002a,0x61e60711}}, // רונג, doen, _poñé, _utkl,
+ {{0xf8dc08fd,0xdb00eef5,0x7ae2b227,0xd2b701c6}}, // _बलिय, romí, _sfot, _ללכת_,
+ {{0x6d418595,0x6e22eef6,0x6fb68199,0xdc2a9ef7}}, // _dala, stob, _همسا, استه_,
+ {{0x5f968307,0x6d419a29,0x68f895dc,0xdb0083fb}}, // _الرئ, _eala, _sevd, pomí,
+ {{0x25be8748,0x2fdf01a1,0x8d1a87c3,0x637c89c4}}, // _thtl_, _čugi_, _مزار_, _léng,
+ {{0x6d41eef7,0xe80d035a,0xa3d81094,0x69d9eef8}}, // _gala, ांचा_, ादा_, _duwe,
+ {{0xa3df800f,0x2c0b826a,0x63a983a6,0x69d99407}}, // _धूप_, _معنی_, boen, _euwe,
+ {{0x443e8943,0x63a9ae83,0x289b03c8,0x656501a1}}, // _ist_, coen, ריטא, _sohh,
+ {{0x753501c1,0x6d41eef9,0x4aa709f2,0x0ab684e3}}, // tezz, _yala, _कुंव, _احاد,
+ {{0x7bda8d4f,0x6d41ab95,0x2d8e826c,0x236681c0}}, // _kutu, _xala, nife_, _hooj_,
+ {{0x753523b9,0x7bda8406,0x7aed802e,0x2001065f}}, // rezz, _jutu, _întâ, xshi_,
+ {{0xa5bb2511,0x25740106,0x23668140,0x7bda80ef}}, // ctón, _häll_, _jooj_, _mutu,
+ {{0x7c3e240a,0x6b8d07e8,0x75353333,0x2366822c}}, // [7d30] _espr, riag, pezz, _mooj_,
+ {{0x7bda8e67,0x23668282,0x63a9eefa,0x443e8252}}, // _outu, _looj_, zoen, _ost_,
+ {{0x69c50013,0x6d41c022,0x7bdaa1b8,0x610b00eb}}, // rmhe, _rala, _nutu, vēlē,
+ {{0x20014941,0x63781f90,0xdb0d00f7,0x637c89c4}}, // rshi_, _vínc, olaí, _jénd,
+ {{0x672d00f1,0x637cb749,0x200102f7,0x787f8061}}, // _mbaj, _ménd, sshi_, _kávé,
+ {{0x7bda9efb,0x61ed6a30,0x6d4192b5,0x69d9a168}}, // _butu, mpal, _qala, _ruwe,
+ {{0x6d41dc17,0x6d43a67f,0x7bdae11f,0xdcf500eb}}, // _vala, rdna, _cutu, lizē,
+ {{0x6d41dc6d,0x236681c0,0x69d9809c,0xa2c0170c}}, // _wala, _cooj_, _puwe, लेक्,
+ {{0x443e9c8d,0x6d41cdeb,0x63a9eefb,0xe299af75}}, // _est_, _tala, roen, рап_,
+ {{0x672d6efc,0x2d8e83a7,0x3f8feefd,0xe8f984a9}}, // _abaj, cife_, ligu_, шло_,
+ {{0x25a08013,0x764d448d,0x7bdacb22,0x201e0106}}, // éile_, _aray, _gutu, _ätit_,
+ {{0x69d9eefe,0xd6d98035,0x637c88f9,0x764d4d9d}}, // _tuwe, _mało_, _cénd, _bray,
+ {{0x99d79a3c,0x764d1532,0x61ed2a90,0xa5bb6eff}}, // _اتصا, _cray, jpal, rtón,
+ {{0x764d02e8,0x9f4600f7,0x29cd01a1,0x2366822c}}, // _dray, mplí_, _džak_, _zooj_,
+ {{0x3d1b00cf,0x8c4335cc,0x6f09d619,0x2fc0125b}}, // _बारे_, мере, _odec, _thig_,
+ {{0x3f8f803a,0xdb0400f7,0x0aea01e5,0x66048bcf}}, // jigu_, lliú, рдай_, šiki,
+ {{0xe8fa1f3e,0x27e0803a,0xc33282f6,0x61ed1066}}, // [7d40] ила_, _čini_, וון_, gpal,
+ {{0x6f09e26f,0x61e480f7,0x9f5f01ca,0xa5bb018a}}, // _adec, _éile, butó_, ntól,
+ {{0xb17a80be,0x6d890754,0xdb0401a8,0x9f5f0144}}, // סטער, džah, iliú, cutó_,
+ {{0xd378803b,0x7bdae7ca,0x68fe2df3,0x39430122}}, // _kuće_, _rutu, sapd, _eajs_,
+ {{0xe5c6a10d,0x236681c5,0x6f098114,0x7bda93a8}}, // оско, _rooj_, _ddec, _sutu,
+ {{0x1dbc0bb8,0x63a9075f,0x6f098214,0xdb008019}}, // ोगित, čene, _edec, domá,
+ {{0x475a8656,0xd00702de,0x6602e02b,0x66e686b5}}, // _края_, _мере_, ssok, _мова,
+ {{0x68fc6f00,0x637581df,0x7ae43648,0x63b6031d}}, // _herd, _dáno, ncit, nlyn,
+ {{0xde03a927,0xa5f78073,0x637c8118,0xdb00d26f}}, // _опти, _меѓу_, _rénd, gomá,
+ {{0x7bda8bb0,0x7ae40101,0x6f098035,0x637c8118}}, // _tutu, hcit, _zdec, _sénd,
+ {{0x236681c5,0x61ed6f01,0x443ee86e,0x25740106}}, // _tooj_, zpal, _ust_, _väll_,
+ {{0xdbf080ab,0x656883ed,0x68fc6f02,0x764d33e1}}, // _টিপস_, _hodh, _lerd, _pray,
+ {{0x6568ef03,0x61e200f7,0x637c8333,0xdb008144}}, // _kodh, íoll, _vénd, comá,
+ {{0x68fc02d0,0x656888f9,0xdb0d01a8,0xd49b0a4c}}, // _nerd, _jodh, rlaí, арб_,
+ {{0x9f47820f,0x7bc2bd3e,0x65688199,0x6378008b}}, // ënë_, _khou, _modh, _sína,
+ {{0xe517023c,0x764d6f04,0x6568c959,0x20070968}}, // _ताकि_, _tray, _lodh, šnik_,
+ {{0x68fc208b,0x637cb699,0x49751baa,0x29d500f7}}, // [7d50] _berd, _géne, плас, سياس,
+ {{0x68fc6f05,0xdcf500eb,0x799a8edc,0xf84b0087}}, // _cerd, rizē, nntw, ичей_,
+ {{0x2d916f06,0x68fc1dcc,0x4735965d,0x61ed6f07}}, // mize_, _derd, знес, spal,
+ {{0x68fc05f8,0x61ed2b49,0x6f1d4c84,0x7ae42d60}}, // _eerd, ppal, ngsc, ccit,
+ {{0x6d452e8c,0x637c862f,0x65688387,0x7afd0029}}, // _kaha, _xéne, _bodh, _iest,
+ {{0x68fc6f08,0x2d916f09,0x6d4758ce,0x7afd05be}}, // _gerd, nize_, ndja, _hest,
+ {{0x6d4506df,0x290102a3,0x7afd238d,0x69dd13c2}}, // _maha, maha_, _kest, _huse,
+ {{0x6d456f0a,0x7afd085d,0x29016f0b,0xdb0090dd}}, // _laha, _jest, laha_, tomá,
+ {{0x68fc0182,0x69dd355d,0x9ad30133,0xdb0e80f7}}, // _yerd, _juse, _dịga, ódál,
+ {{0x6d453a19,0x61e6803e,0x6568803d,0x29013090}}, // _naha, íkla, _godh, naha_,
+ {{0xa3bd101c,0xfbdb081f,0x69d5019a,0x69dd6f0c}}, // ेगा_, _मूलम, _mize, _luse,
+ {{0x69d50b90,0x7afd6f0d,0xdb00816b,0x63ad0580}}, // _lize, _nest, pomá, joan,
+ {{0x2d9106c0,0x290141ab,0xa5bb0118,0x7bc9cd28}}, // fize_, kaha_, ptól, mmeu,
+ {{0x6d4527df,0x2d9102a0,0x69d50823,0x637c9753}}, // _caha, gize_, _nize, _véne,
+ {{0x7afd6f0e,0x290102c1,0x69c3ef0f,0x6d4519b0}}, // _best, daha_, _ihne, _daha,
+ {{0x7afd0259,0x63ad4183,0x6d570899,0x69dd0d54}}, // _cest, goan, _enxa, _buse,
+ {{0x7afd6f10,0x6d456480,0x7ae432a3,0x2d8402be}}, // [7d60] _dest, _faha, rcit, èmes_,
+ {{0x7afd2cdd,0x7ae41137,0x0f148540,0x6441812b}}, // _eest, scit, डल्स_, _osli,
+ {{0x7afd05d8,0x69d559e7,0x656889c4,0x443c8069}}, // _fest, _dize, _rodh, xwv_,
+ {{0x68fc1736,0x6d452822,0x68e50046,0x69dd28bf}}, // _werd, _zaha, achd, _fuse,
+ {{0x68fc14ff,0x6d456f11,0x69d5164b,0x6441c18e}}, // _terd, _yaha, _fize, _asli,
+ {{0x7bc28051,0x7afd5105,0x69d5169d,0x25740106}}, // _shou, _zest, _gize, _sälj_,
+ {{0x777ba2a0,0x7bde4dfa,0x645812ca,0x7bc282c4}}, // _flux, _jupu, _ávid, _phou,
+ {{0x7afd07f4,0x63a48796,0xf743a516,0x8c1b007c}}, // _xest, čino, _песо, צופי,
+ {{0x62840698,0x257400f2,0xc3330051,0x7bde0642}}, // nzio, _välj_, תוח_, _lupu,
+ {{0xad9b0013,0x04468767,0x4427810c,0x63710711}}, // gsúl, _незн, btn_, _våni,
+ {{0xac768875,0x7bc28051,0x2d916f12,0x63ad004f}}, // _عائش, _thou, vize_, yoan,
+ {{0x6d456f13,0x62840102,0x929d809a,0x29011e8f}}, // _saha, kzio, wałe, zaha_,
+ {{0x29016f14,0x6e3d0a0f,0x2d916f15,0x0ec70540}}, // yaha_, uwsb, tize_, रेंड,
+ {{0xa2cd0bbc,0x69dd585b,0x7bde6f16,0x29010079}}, // _दृष्, _ruse, _bupu, xaha_,
+ {{0x2d911efb,0x92b900c8,0x6d476f17,0x63ad6f18}}, // rize_, _চলে_, rdja, toan,
+ {{0x2d9106a0,0x69dd2c28,0x7bde24df,0x1fa40110}}, // size_, _puse, _dupu, _прыг,
+ {{0x7afd05fa,0x6d45326e,0xa2cd0eb4,0x6375a5b3}}, // [7d70] _vest, _taha, _दृश्, _bánj,
+ {{0x645801e2,0x9327003d,0x7afd27d0,0x291eae2e}}, // _švie, _کردن, _west, ngta_,
+ {{0x29016f19,0xb4bb901e,0xf1c800f7,0x62845e45}}, // raha_, _घरे_, أولى_, azio,
+ {{0x69dd343f,0x78b5026f,0x27f88c83,0x6f02826c}}, // _tuse, _vyzv, árny_, gaoc,
+ {{0xa3e30540,0xa3aa009a,0x091480ab,0x69d50061}}, // _फंड_, खकर_, ত্যু_, _tize,
+ {{0xdd9185ff,0x2901461f,0xed56bd93,0x361c01c6}}, // ضوع_, qaha_, дош_, מודד,
+ {{0x403429c9,0x201a00e5,0x98b81c28,0x8c428198}}, // _перс, cupi_, _parą_, веще,
+ {{0x291eea09,0x2b496f1a,0x77b60118,0xdce181a9}}, // egta_, ndac_, páxi, _dolā,
+ {{0x3a290057,0x7bc99de6,0x2bdf82f1,0x0eac0035}}, // ntap_, rmeu, _पंवा, _टुंड,
+ {{0x44278748,0x98bc80eb,0x60099594,0x6f0d0db1}}, // ptn_, _savā_, оном_, _adac,
+ {{0x7d03809e,0x07a60c5c,0xc7a61bc1,0x64418499}}, // mans, данн, динк, _usli,
+ {{0x22498301,0x54349ab3,0x00000000,0x00000000}}, // _šaku_, _سرمر, --, --,
+ {{0x7bde51d7,0xe28edd22,0xfaff0a53,0x2eff8866}}, // _supu, _ра_, _voël_, _meuf_,
+ {{0xa3b611bc,0x7bde234d,0x01c88019,0xdd998176}}, // _चीज_, _pupu, _فونٹ_, _dwňg_,
+ {{0xb4bb83b7,0x6ec200c2,0x2eff92b6,0x2aeb0133}}, // _घरो_, रेजु, _oeuf_, _ụkọ_,
+ {{0x05dd800f,0xd378803a,0x3947b487,0x2eff82be}}, // मदाब, _kuća_, _hans_, _neuf_,
+ {{0x3947dad3,0x6f029c67,0x27ed0088,0x7d0380ef}}, // [7d80] _kans_, vaoc, _čena_, kans,
+ {{0x201a6f1b,0x62846f1c,0x63759931,0x1de18105}}, // tupi_, rzio, _bánk, _पढ़त,
+ {{0x7d03ef1d,0x3947a0bc,0x6f029c67,0x83c60468}}, // dans, _mans_, taoc, _обож,
+ {{0x4444073a,0x394795ab,0x9ad3019d,0x20070187}}, // _is_, _lans_, _dịba, ânia_,
+ {{0x6d89003a,0x7d03ef1e,0x66098b67,0x6f028b80}}, // ržav, fans, _cvek, raoc,
+ {{0x7d03a255,0x3947ec94,0x753c6f1f,0xa3e32539}}, // gans, _nans_, nerz, _फूड_,
+ {{0x67240499,0xac9795a9,0x63758032,0x63781b88}}, // _ocij, ونها_, _kánh, _línm,
+ {{0x7644113b,0x753c6f20,0xd3788267,0x5b7b00be}}, // _osiy, herz, _buća_, ַריא,
+ {{0x44440341,0xc298a306,0x753c4145,0x6f0d0a2a}}, // _ls_, яких_, kerz, _rdac,
+ {{0x44446f21,0x7d03ba16,0xd37880c3,0x636e8362}}, // _os_, cans, _duća_, _cùnr,
+ {{0x39479c8d,0x6d5ad80a,0x753c2644,0x7644054e}}, // _dans_, _inta, derz, _asiy,
+ {{0x6d48cf98,0x20118f09,0xf99f02d6,0x657e4290}}, // _hada, ázia_, _avèl_, _alph,
+ {{0x4444073a,0xdb0d07e2,0xa5bb00f7,0x753c01ec}}, // _as_, llaç, ltói, ferz,
+ {{0x6d4a9dba,0x394789ff,0x63adef22,0x637102fe}}, // ndfa, _gans_, čane, _lånt,
+ {{0x45d41f3e,0x1fa71928,0xa5bb00f7,0x6d488fa9}}, // торс, _ориг, ntói, _mada,
+ {{0x6d48ef23,0x645838a3,0x44442863,0x7d03ba16}}, // _lada, _švic, _ds_, zans,
+ {{0x44446f24,0x7d03854e,0x2eff826c,0x69d8a976}}, // [7d90] _es_, yans, _reuf_, _kive,
+ {{0xc1788110,0x7c2aa67f,0x2eaa00be,0xca4880ff}}, // nkė_, ktfr, _משפּ, _mờ_,
+ {{0x63a4803a,0x69d88019,0x661d2d5f,0x7d03c6b2}}, // činj, _mive, lusk, vans,
+ {{0x69d8ad08,0x7d03ef25,0x8c1a0158,0x644557ba}}, // _live, wans, _צורי, _ishi,
+ {{0x6d4895e8,0x42790158,0xaaa7081f,0xdd9581e5}}, // _bada, _זאָג, _कुचक, нады,
+ {{0x6d48ef26,0x32091a2e,0x7bcd2daf,0xdb0d0187}}, // _cada, msay_, lmau, flaç,
+ {{0x7d0390fe,0xfc3184e3,0xabd58226,0x69c71dd5}}, // rans, احت_, нциј, _ihje,
+ {{0x3947a264,0x6d5a9a7e,0x7d03ef27,0x661d4f25}}, // _sans_, _enta, sans, kusk,
+ {{0x32096f28,0x7d03843c,0x6d48ef29,0xca48801c}}, // nsay_, pans, _fada, _cờ_,
+ {{0x6d48ef2a,0x64456f2b,0xf99180f7,0x661d6f2c}}, // _gada, _oshi, طبخ_, dusk,
+ {{0x2d83020f,0x7b6784fa,0x644502a0,0x673d6f2d}}, // dhje_, _отде, _nshi, fesj,
+ {{0x7d01a1cb,0x673d02be,0x92bd80ab,0xa3c28128}}, // _hels, gesj, _আলী_, ूषण_,
+ {{0x69c70364,0x753c06da,0x64456e0a,0x44446f2e}}, // _ohje, terz, _ashi, _ss_,
+ {{0x69d8ef2f,0x63bbb6d6,0x7bc6020f,0x44446f30}}, // _give, llun, _shku, _ps_,
+ {{0x44440057,0x64a31b1c,0xdca3188e,0x2905eb68}}, // _qs_, гара, гари, mala_,
+ {{0x2905da00,0x38788bc5,0xbb431597,0x753c0081}}, // lala_, ørre_, леск, serz,
+ {{0x44445a55,0xfce63296,0x644501f6,0x63bb82af}}, // [7da0] _ws_, _попо, _eshi, ilun,
+ {{0x2905ef31,0x63bba3cf,0x27e98279,0x64450168}}, // nala_, hlun, _čanj_, _fshi,
+ {{0x44440fa9,0x6d89011f,0x69c700f1,0xc7b90668}}, // _us_, ržat, _dhje, suđe_,
+ {{0xdee695b5,0x6d48b7a5,0xdd200592,0x66e68470}}, // ходи, _sada, _बाँध_, хода,
+ {{0x6d48a9fb,0x63bbef32,0x7d018019,0x61e40372}}, // _pada, dlun, _bels, qqil,
+ {{0x2905a6fd,0x6d48ef33,0xd00a0bda,0xa3d80f97}}, // jala_, _qada, _неге_, ादक_,
+ {{0xc8b50071,0x63a28b67,0x6d48ef34,0x69d88ca9}}, // _асты, _pjon, _vada, _rive,
+ {{0x6d48a480,0x32669e7d,0x69d8e350,0x5a349bb6}}, // _wada, етов, _sive, внит,
+ {{0x6f06188a,0xe1ef84c0,0x7d01ef35,0x7bd99295}}, // dakc, یسی_, _fels, _diwu,
+ {{0x2905ef36,0xfd6901bc,0xdb0d1b01,0x7d01cb65}}, // gala_, _dapụ, plaç, _gels,
+ {{0x69d8c38a,0x63bbef37,0x26c489d1,0x387f0300}}, // _vive, blun, _hzmo_, _ƙuri_,
+ {{0x661d53ac,0x3209472e,0x6722aed0,0xf1b9c861}}, // tusk, ysay_, ngoj, vuše_,
+ {{0x69d88e67,0x2d8300f1,0x7d0194cf,0x6b840079}}, // _tive, thje_, _yels, dhig,
+ {{0xae028076,0x661d098c,0xceb383c8,0xbf0c816f}}, // रीयन_, rusk, ריש_, हणुन_,
+ {{0x61e28364,0x645c8207,0x7bd980a4,0x41c39a37}}, // _huol, _árid, _yiwu, _حقوق,
+ {{0x63a9011f,0x61e2d8ad,0x661d6f38,0x2d8300f1}}, // čeno, _kuol, pusk, shje_,
+ {{0x201eaad4,0x9ad301bc,0xdb1ba359,0x4fd80039}}, // [7db0] luti_, _bịaa, fluê, _אוהב_,
+ {{0x644501e9,0x3d1b0006,0x660d113b,0x32093cc0}}, // _tshi, _बाटे_, _ivak, rsay_,
+ {{0xbf0c853e,0x6445310d,0x4ac50076,0x39a4230e}}, // हणून_, _ushi, लेजव, уштв,
+ {{0x29059ce8,0x7d018bc5,0x660d0669,0x29fd80e1}}, // zala_, _sels, _kvak, yňa_,
+ {{0x69c700f1,0x61e2827e,0x2905d019,0x201ea55a}}, // _thje, _nuol, yala_, huti_,
+ {{0x201e8867,0xe4e7835f,0x236900d2,0x29fd80e1}}, // kuti_, хідн, ljaj_, vňa_,
+ {{0xae0286a7,0x2246a190,0xa4938fd3,0x63bbb8c0}}, // रीबन_, _asok_, دیات, tlun,
+ {{0x660d003a,0x2905a01c,0xd9c9800c,0x201e9b68}}, // _ovak, wala_, रष्ट, duti_,
+ {{0x63bb9a21,0x7ae98493,0x7d01ef39,0x29cd0668}}, // rlun, rcet, _tels, _užas_,
+ {{0x63bb80e3,0x7ae98dff,0x3a3900ee,0x201eef3a}}, // slun, scet, _mpsp_, futi_,
+ {{0x2905e6b8,0x7d07026f,0xf99f0205,0x201eb394}}, // rala_, dajs, _avèk_, guti_,
+ {{0x213e831d,0x236900b9,0xc62600ab,0xf42700ab}}, // aeth_, jjaj_, যবসা_, মবার_,
+ {{0x213ea4dc,0x23690253,0xe9ce8fbf,0x81e600ab}}, // beth_, djaj_, _вк_, _যৌন_,
+ {{0x201eef3b,0x2905829b,0x660d219f,0x333f8036}}, // buti_, qala_, _dvak, leux_,
+ {{0x5c150e02,0xab2a1501,0x201e9d36,0x1c3902c7}}, // льту, мова_, cuti_, нять_,
+ {{0x26df804f,0x333f8036,0xbebb0168,0x236f810c}}, // _nguo_, neux_, rfër, _jogj_,
+ {{0xe2975063,0x91e3102f,0xe9da0721,0xbb432457}}, // [7dc0] ват_, иоте, дка_, шетк,
+ {{0x6b843403,0x8c960221,0x63a48743,0x29030216}}, // shig, _армі, čini, _ceja_,
+ {{0x29036f3c,0x2d980751,0x63786f3d,0x6d5e2454}}, // _deja_, mire_, _líni, _inpa,
+ {{0x8a062028,0x2d986f3e,0x6d4e0a64,0x637c80e7}}, // _изме, lire_, ldba, _réno,
+ {{0x25fd8b9f,0x25740106,0x6722ef3f,0x29030168}}, // रीली_, _fält_, rgoj, _feja_,
+ {{0x2d986f40,0x6f046f41,0x63758125,0x61e28081}}, // nire_, _leic, _mánu, _ruol,
+ {{0xa2d623bd,0x50c61513,0xdb0401df,0x7c2e6f42}}, // _मृत्, _वरिष, lliñ, ntbr,
+ {{0x61e28364,0x2d982668,0x201eef43,0xe3af80a0}}, // _puol, hire_, vuti_, يري_,
+ {{0x69dc4141,0x2d980b5e,0x2fc90114,0x443a6f44}}, // _kire, kire_, _rhag_, _lpp_,
+ {{0x443a021e,0x61e28098,0x69dc02a3,0xa5458060}}, // _opp_, _vuol, _jire, _مضمو,
+ {{0x660d0025,0x99638071,0x213eef45,0x534681e5}}, // _svak, ртыл, reth_, _ахла,
+ {{0x6d5e1d31,0x61e28009,0xa3e6000f,0x3f86ef46}}, // _anpa, _tuol, _पढ़_, nhou_,
+ {{0x2d983d27,0x69dc2dfb,0x6f040083,0x6b828136}}, // fire_, _oire, _deic, _ilog,
+ {{0xe8549301,0x69dc2266,0x2d9846d6,0xdb11808b}}, // _منتد, _nire, gire_, ðgön,
+ {{0x2903020f,0x6f046f47,0x403482cb,0x6b82806a}}, // _reja_, _feic, _сейс, _klog,
+ {{0x290300a9,0x69dc5163,0x6d5e332c,0x2d9867e8}}, // _seja_, _aire, _enpa, aire_,
+ {{0x28ce8076,0x69dc3637,0x2d98505b,0xf1b98fee}}, // [7dd0] हेरि, _bire, bire_, luša_,
+ {{0x69dc1142,0x7c3a93c7,0x6b82c5f7,0x2d983e61}}, // _cire, _optr, _llog, cire_,
+ {{0x290300a9,0xe7198013,0x6b8284be,0x443a1142}}, // _veja_, فيات_, _olog, _gpp_,
+ {{0x4aae0076,0x3f9926cc,0xf770880b,0xdb0401df}}, // _झुकव, nisu_, يان_, bliñ,
+ {{0x7bdd6f48,0x333f80e7,0xb28721f6,0x3ea98722}}, // _iisu, veux_, _рынк, çat_,
+ {{0x64a5896b,0xf1b9807d,0x69dc6f49,0x6b828ab3}}, // лала, kuša_, _gire, _alog,
+ {{0x7bdd0b3f,0xe8d9801c,0x637800f7,0x7aed0037}}, // _kisu, _đỏ_, _síni, ocat,
+ {{0x69dc25a1,0x7aed6f4a,0x2d98620b,0x2a6d009a}}, // _zire, ncat, zire_, _żeby_,
+ {{0xe9d99383,0x7bdd61cd,0x2d9835a7,0x333f82be}}, // нки_, _misu, yire_, reux_,
+ {{0x7bdd3b8b,0x6f0447c8,0x6b82ef4b,0x3b093a46}}, // _lisu, _seic, _elog, haaq_,
+ {{0x93f592b2,0xa3e31513,0x6b82ef4c,0x2d980d19}}, // _спец, _फूल_, _flog, vire_,
+ {{0x2d980e8c,0x7bdd01f7,0x63758511,0xdebb8300}}, // wire_, _nisu, _cánt, _sauƙ,
+ {{0x6f046f4d,0x64576f4e,0x7afb8590,0x7aed6f4f}}, // _veic, _arxi, mbut, dcat,
+ {{0xceb2804c,0x1a9a8158,0x3f86871d,0x4a9a80be}}, // צים_, _אינע, zhou_, _אינג,
+ {{0x2d986f50,0x6f04300a,0xc7b90668,0x7bdd1efb}}, // rire_, _teic, suđa_, _bisu,
+ {{0xe5200074,0x6e3b8035,0x7afbef51,0x25740338}}, // _बाकि_, _opub, nbut, _päls_,
+ {{0x69dc6f52,0x7bdd059c,0x320def53,0x49ca1a02}}, // [7de0] _pire, _disu, nsey_, елан_,
+ {{0x6d5e0295,0x7aed0661,0x2d87807a,0x7afb810c}}, // _unpa, acat, jhne_, hbut,
+ {{0x443a5425,0xef1a8ae7,0x3f86879f,0x7649d4f7}}, // _upp_, еме_, thou_, _asey,
+ {{0x7aed0098,0xdc9b012a,0x77bd01df,0x068681a1}}, // ccat, _טייל, téxi, лген,
+ {{0xd5ba8a7f,0x63a40e20,0x3f86867f,0x69dc10b6}}, // нск_, mnin, rhou_, _tire,
+ {{0x63a46f54,0xf9878872,0xc0e31cff,0xa5bb6f55}}, // lnin, _طب_, _горк, ltór,
+ {{0xf98302de,0x6b82ef56,0x7649e9a4,0xf99f02d6}}, // _угро, _plog, _esey, _avèw_,
+ {{0x63a46f57,0x5214867c,0x08770158,0xf1b98668}}, // nnin, адит, _געלט_, vuša_,
+ {{0xe8d98104,0x3f84859c,0x6375c50e,0xa92a81e5}}, // _đề_, _ilmu_, _ránt, ніне_,
+ {{0xdd8f9b9a,0x442c8144,0x7afb9761,0x2d988338}}, // _فوق_, _tqd_, abut, _öre_,
+ {{0x63a4088b,0xc0578d13,0x7aed4f79,0x9f5f0009}}, // knin, гія_, ycat, ystä_,
+ {{0x6b82803b,0xe81c023c,0x63a41a16,0xf1b98289}}, // _ulog, _पटना_, jnin, ruša_,
+ {{0x63a40e23,0x7bdd0698,0x3f990289,0x957c809a}}, // dnin, _risu, risu_, _ciąg,
+ {{0x091a0a49,0xceb3812a,0x7bdd355b,0x63a418d6}}, // ন্তু_, מיר_, _sisu, enin,
+ {{0x7bdd4680,0x64581984,0x7ae2913b,0x63a4007b}}, // _pisu, _ávil, _kgot, fnin,
+ {{0x63a43417,0x98138b76,0x9ad301bc,0x7bc080ee}}, // gnin, _ابوا, _fịla, nlmu,
+ {{0x7aed6f58,0x3171007d,0x7bdd0ca9,0xbda7006b}}, // [7df0] rcat, _pozz_, _visu, _محدو,
+ {{0x7aed17d6,0x7bdd6f59,0xdb04007b,0xa5bb6f5a}}, // scat, _wisu, slið, dróg,
+ {{0x7bdd24a0,0x31c40009,0x63a46f5b,0x7aed026c}}, // _tisu, сств, bnin, pcat,
+ {{0xa5bb0c52,0x7ae2e064,0xc7b9026c,0x3b090df6}}, // ctór, _ngot, brđe_, qaaq_,
+ {{0xd2469a00,0x11d900f7,0x44311fdc,0x2d878083}}, // _بن_, _لوحة_, ltz_, thne_,
+ {{0x7ae295c7,0x394e80dd,0x2715801c,0x3f84b36e}}, // _agot, _nafs_, ền_, _elmu_,
+ {{0x44316f5c,0x8bf080ab,0x799aab67,0x7afbb417}}, // ntz_, _টিউন_, ditw, tbut,
+ {{0x63ad95d8,0x69ca92c6,0x61e60380,0x4431011b}}, // čano, _सीबी, _yukl, itz_,
+ {{0xa3d604c5,0x7afb89bf,0x2d87d6a3,0xfaa5ab3f}}, // िगत_, rbut, phne_, рапо,
+ {{0x44236f5d,0x6375803e,0x661d0916,0x7ae28102}}, // kuj_, _páns, drsk, _egot,
+ {{0x63a44729,0xd7a9816f,0x661d1c59,0x2d85ad21}}, // ynin, _कदाच, ersk, _ille_,
+ {{0x201889e9,0x6d43beb0,0x3ebf8637,0x62358c4f}}, // ária_, lena, _kyut_, _веду,
+ {{0x63a46f5e,0x3984016d,0x59c51993,0x443116ee}}, // vnin, _lösa_, वतार, etz_,
+ {{0x6d43803a,0xe2c880e8,0x399b8032,0x443103e1}}, // nena, _слід_, _dásè_, ftz_,
+ {{0x44230063,0xa5070b9c,0x661d368e,0x6da31a19}}, // guj_, реса_, arsk, цира,
+ {{0x61e60503,0x6d4389e8,0x2907a8ee,0x6b9b847f}}, // _pukl, hena, _hena_, niug,
+ {{0x6d43cd15,0x2907ef5f,0x63a42d9a,0x6da600e8}}, // [7e00] kena, _kena_, rnin, _випа,
+ {{0x63a4006f,0xa5bb3a2c,0x9f5f0bfa,0x61e61487}}, // snin, stór, rstå_, _vukl,
+ {{0x69c19da2,0x29079f1f,0x63a43f88,0x63780125}}, // ille, _mena_, pnin, _sínu,
+ {{0x2d858d1a,0x2907ef60,0x26178076,0x61e652d7}}, // _alle_, _lena_, _निशी_, _tukl,
+ {{0x2bdf80dc,0x7d0a90fd,0x3a3d82f7,0xf1b981a9}}, // _पंजा, tafs, _npwp_, jušo_,
+ {{0x5e5700be,0x7b3e0035,0x2907ef61,0x660408b1}}, // יינע_, _dłuż, _nena_, _mwik,
+ {{0x98a316d9,0xa0a322f6,0x18a3373a,0xa0670081}}, // _дире, _дард, _дарм, _кара_,
+ {{0x27e0025d,0x2d8586e3,0x6f0b913b,0xd378803b}}, // _niin_, _elle_, cagc, _kući_,
+ {{0x6d4394ff,0xfe0281ce,0x2907aebd,0x0566a21f}}, // bena, रीशस_, _bena_, _кван,
+ {{0x6d4391b9,0x63a90ed6,0x7c3e00e8,0x27e00362}}, // cena, čeni, _oppr, _aiin_,
+ {{0xe8d98142,0x2bdf90d0,0x2907811e,0x3ceb8740}}, // _để_, _पंचा, _dena_, _चलते_,
+ {{0x27e78358,0x7ae28353,0xf1b982a5,0x443e8a87}}, // _dunn_, _ugot, vrše_, _ipt_,
+ {{0x7c3e1162,0x27e02a22,0x2907cd8f,0x6f160428}}, // _appr, _diin_, _fena_, _ddyc,
+ {{0xe7e70076,0x443e80b9,0x442322d5,0x6f160035}}, // _ओढला_, _kpt_, tuj_, _edyc,
+ {{0x290cef62,0x7d08ef63,0xdb09a509,0x25b981a8}}, // mada_, _meds, cleó, _ísle_,
+ {{0x290cbf9a,0x6d43ef64,0x44230f66,0x052700c8}}, // lada_, zena, ruj_, য়ের_,
+ {{0x44236f65,0x29078234,0x6d43e350,0x637c802a}}, // [7e10] suj_, _yena_, yena, _céni,
+ {{0x645aa5ca,0x637c83f8,0x63786f66,0x7d088082}}, // _orti, _déni, _sínt, _neds,
+ {{0x6f16031d,0x7b3e009a,0xa3c88fd5,0x6378002a}}, // _ydyc, _służ, ोगा_, _pínt,
+ {{0x290cef67,0x6d4391ee,0x637cdf1a,0x30d980be}}, // hada_, wena, _féni, אַנע,
+ {{0x7d088022,0x98a70459,0xf77091cc,0xbb3a00be}}, // _beds, ını_, بال_, _גערי,
+ {{0xd90f0bca,0x99850013,0x2d9cef68,0x2bdfa4bd}}, // _آیا_, _السو, give_, _पूजा,
+ {{0xeb998ba8,0x290cb241,0x29079651,0x645a9351}}, // лик_, dada_, _rena_, _crti,
+ {{0x2907ef69,0x443e8057,0x672d022c,0x1b49891d}}, // _sena_, _dpt_, _ncaj, узии_,
+ {{0x290cef6a,0x6d43bf83,0x645a810b,0x69c1809f}}, // fada_, pena, _erti, tlle,
+ {{0x8d748307,0x05748250,0x27e010b5,0x6f098708}}, // _والا, _والد, _siin_, _heec,
+ {{0x69c1831d,0x66e659dc,0x6d8a8019,0xb8ef8a27}}, // rlle, йона, _حملہ_, _शर_,
+ {{0x4cd080ab,0x29078234,0x69c1d03e,0x07a60162}}, // _স্কু, _wena_, slle, самн,
+ {{0x290cbf9a,0x2907822e,0x9ad30135,0x201ed819}}, // bada_, _tena_, _dịka, arti_,
+ {{0x61fb8859,0x225e03c1,0x6f09ef6b,0x27e78338}}, // mpul, _čtk_, _leec, _tunn_,
+ {{0x7ff5936d,0xf7f58591,0x673b8365,0x637cc0f7}}, // _استا, _استد, _obuj, _séni,
+ {{0x087680be,0x2d9c92ec,0x637c87bc,0xd3788b80}}, // _לעצט_, zive_, _péni, _pući_,
+ {{0xae1c825e,0x61fbb117,0x224902c4,0x66040ce7}}, // [7e20] _निधन_, npul, gwak_, _uwik,
+ {{0x673bda7d,0x186a0eef,0xd378811f,0x39458806}}, // _abuj, лази_, _vući_, nels_,
+ {{0x63ad8805,0x7c3e0ffd,0x6ff38162,0x63780032}}, // čanj, _uppr, _făce, _dínr,
+ {{0x290c8510,0x443e802a,0xdc3b00be,0xcb6a02eb}}, // zada_, _rpt_, _געטר, _саме_,
+ {{0x290c9764,0x2d9cb057,0x39458e61,0x443e810b}}, // yada_, tive_, kels_, _spt_,
+ {{0xa1160416,0x860780f7,0x2d9a00b9,0x13bd00ab}}, // _دوست, حقوق_, _jmpe_, _আওয়,
+ {{0x308600f7,0x2d9c97af,0x3945ef6c,0x6e2602c4}}, // _الأف, rive_, dels_, hukb,
+ {{0x645aa944,0x7d08838e,0x290caad2,0x799e004f}}, // _vrti, _weds, wada_, lipw,
+ {{0x290c9f61,0xdb1be501,0xdb0403d3,0x3945ef6d}}, // tada_, cluí, rniè, fels_,
+ {{0x20188003,0x2166867c,0x99669ad8,0x301495ac}}, // ário_, стиг, стил, одор,
+ {{0xdb0f0019,0x443e86cb,0xc32a80f7,0x645ab187}}, // ődés, _upt_, _مكان_, _urti,
+ {{0x2d9a2444,0x290a6f6e,0x3a200bb1,0xe61f0187}}, // _ampe_, _leba_, krip_, trô_,
+ {{0x290c8852,0x8f9b8039,0x29186431,0x3d0501d0}}, // pada_, ריכי, _odra_, वरले_,
+ {{0x3a205b9e,0x291c0019,0xc69383c8,0x290cab95}}, // drip_, óval_, לאר_, qada_,
+ {{0x499b9a0f,0x3f9d8326,0xa2db54ea,0x96f92f42}}, // _תשוב, yiwu_, पेन्, _кейт_,
+ {{0x764d00a4,0x07a40992,0xec3400e8,0x26120072}}, // _tsay, _најн, янсь, _तिची_,
+ {{0x290a00ce,0x7dcdd03e,0x3a200cfa,0x6f0f6504}}, // [7e30] _beba_, núsc, grip_, macc,
+ {{0x3f890069,0x6f098118,0x6f0f4338,0x290a6f6f}}, // _hlau_, _seec, lacc, _ceba_,
+ {{0x290a478b,0xa5bb5dea,0x7bc40196,0x2606a539}}, // _deba_, prób, bliu, सीसी_,
+ {{0xd7c904e5,0xd378803b,0x6d475896,0x657700f1}}, // रतिच, _kuću_, leja, _hoxh,
+ {{0xf7708064,0x7dcd8722,0x6db58b79,0x61ed029a}}, // عام_, júsc, ойду, qqal,
+ {{0xed59b24f,0x6d556f70,0xdb028125,0xce380039}}, // рок_, ndza, _skoð, צאות_,
+ {{0xe7c91199,0x25fd8054,0xb4e58032,0xd7c9016f}}, // रताप, रीजी_, _afàì, रताच,
+ {{0xa2d01d01,0x765b8104,0x69c52906,0x7dc03416}}, // _दरम्, _truy, llhe, löst,
+ {{0x6d472b25,0x6f0f2e88,0x6925943d,0x6458a81c}}, // keja, dacc, омпа, avvi,
+ {{0xe8df8104,0x6d4700dd,0x61fbef71,0x2d8301ed}}, // _giới_, jeja, rpul, jkje_,
+ {{0xa3d9146d,0x6f0f2b10,0x3945bb7c,0x3f895718}}, // ाषा_, facc, rels_, _blau_,
+ {{0x4427ef72,0x6f0f6f73,0x3945ef74,0x3f890722}}, // mun_, gacc, sels_, _clau_,
+ {{0x63bbb24d,0x3945ef75,0x70c61094,0x3f8901c0}}, // moun, pels_, _वर्ल, _dlau_,
+ {{0x6d4723c7,0x63a9ef76,0x63bbef77,0x637c8866}}, // geja, lnen, loun, _dénu,
+ {{0x44279c99,0xfce330bc,0x63a9a391,0x6e26222e}}, // nun_, доро, onen, sukb,
+ {{0x63a9e105,0x6f0f571d,0x6b8d26bc,0xbea634ba}}, // nnen, cacc, ghag, _майк,
+ {{0x44278260,0x6d476f78,0x61ebef79,0x7bc46f7a}}, // [7e40] hun_, beja, _jugl, rliu,
+ {{0x44278e59,0x61e384be,0x64418cab,0x602605a8}}, // kun_, _jinl, _opli, жджа,
+ {{0x61eb8698,0x61e3ef7b,0xa2e60012,0x25e30105}}, // _lugl, _minl, _монд, _टूटी_,
+ {{0xe73a9bb1,0x6b8d6f7c,0xddc98063,0x4427be85}}, // ред_, chag, _treś, dun_,
+ {{0x63bb82ba,0x63a980e1,0x290a00e1,0x637c89c4}}, // doun, dnen, _teba_, _mént,
+ {{0xa2dabca9,0x4427ef7d,0x8c430214,0x7c668591}}, // _पृष्, fun_, ışve, قابل,
+ {{0x61eb8125,0x4427a25c,0x69b90a0d,0x0b8a9092}}, // _augl, gun_, ्तरी, исии_,
+ {{0x63bb85db,0x63a98257,0x2fc0022c,0x38608754}}, // goun, gnen, _nkig_, _čir_,
+ {{0x6f02b0f8,0xfbd30039,0x6f0f6f7e,0x224200b9}}, // mboc, ותה_, vacc, _jpkk_,
+ {{0x224200b9,0xba9b007c,0x2fc6a00a,0x63a985ee}}, // _mpkk_, יספי, llog_, anen,
+ {{0x4427d94c,0x6f0f6f7f,0x6c4a8065,0x2fc6d619}}, // cun_, tacc, _خلاف_, olog_,
+ {{0x63bbcc79,0x61eb95f8,0x61e390f6,0x91e68dc7}}, // coun, _fugl, _einl, _доде,
+ {{0x8bd78158,0xa2d686de,0x6d471afe,0xdb0981ec}}, // _וואו_, मेश्, teja, ließ,
+ {{0x63a0d34a,0x3cdc03ca,0x6f0f0653,0x26c500eb}}, // limn, _गृहे_, sacc, ālo_,
+ {{0x672ba496,0x6d476f80,0xfe6e07d2,0x2d836f81}}, // yggj, reja, نگی_, rkje_,
+ {{0x63a0a368,0x6d471d60,0xb4f886b7,0x27fe8101}}, // nimn, seja, ुरुप_, mptn_,
+ {{0x4427d7b3,0x6ce70221,0x51f801bb,0x6d470d8b}}, // [7e50] zun_, _міне, жную_, peja,
+ {{0x6e22a19f,0x442790fe,0x63a9abea,0x6b8d6f82}}, // drob, yun_, znen, shag,
+ {{0x63bbbd34,0x7dc012d2,0x2366805c,0x6616026f}}, // youn, röst, _onoj_, _zvyk,
+ {{0x4427e5e5,0x63a9003e,0x2fc6817f,0x4fe80048}}, // vun_, čens, glog_, імін_,
+ {{0xaaf60451,0x4427b4dd,0x63bb81df,0x63a9ef83}}, // _מזרח_, wun_, voun, vnen,
+ {{0x4427ef84,0x6f0d1581,0xf2d203c8,0x61ebef85}}, // tun_, _leac, _נעם_, _rugl,
+ {{0x61e38091,0x1fa7cddf,0x61ebef86,0xfd500133}}, // _rinl, _драг, _sugl, _achụ,
+ {{0x4427ef87,0x6f0d0046,0x64418353,0x61eb8081}}, // run_, _neac, _vpli, _pugl,
+ {{0x26cd8025,0xe29987b6,0x13068009,0x6f1b86ec}}, // _uzeo_, сап_, чный_, _iduc,
+ {{0x442781da,0x61eb817f,0x63a980e1,0x637c802a}}, // pun_, _vugl, snen, _sént,
+ {{0x61e3ef88,0x637c8019,0x7c59803d,0x7dc00019}}, // _vinl, _pént, _دلار_, zöss,
+ {{0x6609d56d,0xe73a2796,0x7b648991,0x6f0d01a8}}, // _kwek, щем_, _отче, _ceac,
+ {{0x6f0d1a29,0xee3722b7,0x987782d4,0x1e832796}}, // _deac, пня_, _išče_, елям,
+ {{0x386a8013,0x6609b0a0,0xf1b981a9,0x645e00c3}}, // óirí_, _mwek, kuši_, _prpi,
+ {{0xaffe8104,0xf1b98029,0x637c81df,0x6f0d032f}}, // _trướ, juši_, _tént, _feac,
+ {{0x6d86807b,0xeb970b9c,0x6f0d6f89,0x66168110}}, // _aðal, пис_, _geac, šyki,
+ {{0xe8d98104,0x66098870,0x6b9d0609,0x6b846f8a}}, // [7e60] _độ_, _nwek, _dmsg, rkig,
+ {{0x6f1b802e,0x63a0ef8b,0x645e0bc8,0x7dc48722}}, // _aduc, zimn, _trpi, pòsi,
+ {{0x757b0039,0x98a3116b,0x660993fa,0xdefa862c}}, // _הטיפ, нифе, _awek, был_,
+ {{0x91e61317,0x442a0074,0xdb24004e,0x444401c6}}, // _номе, mub_, _توحی, _ip_,
+ {{0x2fc680c9,0x44380b04,0x645c6f8c,0x83fc80c3}}, // rlog_, ltr_, lvri, _mrđe,
+ {{0x6f1b93a0,0x6f0288f1,0x6e22ef8d,0x44446f8e}}, // _educ, sboc, rrob, _kp_,
+ {{0x443840b2,0x442a0201,0x6609ef8f,0x645c0cab}}, // ntr_, nub_, _ewek, nvri,
+ {{0x44440039,0x20188267,0x660982d6,0xa7a78162}}, // _mp_, šrik_, _fwek, _екса_,
+ {{0x442a4850,0x6f0d1645,0xbebb00f1,0x3f86816b}}, // hub_, _reac, ngët, mkou_,
+ {{0x44440166,0x6f0d154c,0xb603800d,0x442a5658}}, // _op_, _seac, ášen, kub_,
+ {{0x6568ef90,0x4444009a,0xdd8f80f7,0x442a6f91}}, // _indh, _np_, _حول_, jub_,
+ {{0x3f868205,0x442a10b5,0x6d4aef92,0x637c8118}}, // nkou_, dub_, lefa, _véns,
+ {{0x44440f23,0xf1be0a74,0x657a8077,0x54f083eb}}, // _ap_, ्तान, _koth, _चलाए_,
+ {{0x6d4ade84,0xcaa580f7,0x44380789,0x657a85ee}}, // nefa, _تصمي, ftr_, _joth,
+ {{0x69da81ec,0x6f0d3cd7,0x442a2128,0x69c781b9}}, // mmte, _teac, gub_, ċjet,
+ {{0x69c8881a,0x657a8c54,0x6d4ade3a,0xa5bb0ce0}}, // llde, _loth, hefa, crón,
+ {{0x26dd0324,0x290eef93,0x201c0b67,0x2b4904bc}}, // [7e70] _àwo_, _kefa_, švil_, reac_,
+ {{0x290e84b9,0x3a2900b9,0x657aca4c,0x442a06ae}}, // _jefa_, ruap_, _noth, bub_,
+ {{0x7bc2c59b,0xdb0d007b,0x3a290359,0x47359a83}}, // _okou, blað, suap_, днес,
+ {{0xb9040592,0x6568803d,0x2d8c811e,0x290eef94}}, // _पृ_, _andh, _alde_, _lefa_,
+ {{0x05960077,0x63ad5646,0xddc9802e,0xe6188558}}, // _رایگ, mnan, _creş, зді_,
+ {{0x657a8ad0,0x290e912e,0x44446f95,0x33f181df}}, // _coth, _nefa_, _yp_, _páx_,
+ {{0x6d5702c1,0x5fbd0076,0x63ad4ac4,0xeb999cad}}, // _maxa, ईवाल, onan, жий_,
+ {{0x63ad6f96,0xe1f999fe,0x3f86827f,0x69c8ccc3}}, // nnan, оги_, ckou_, elde,
+ {{0x6d4a8b40,0x6d570e1b,0x63ad2a1d,0x8e778591}}, // befa, _oaxa, inan, _عارض,
+ {{0x6d570079,0x64455b0c,0xdb00807b,0x442a6f97}}, // _naxa, _ophi, rnmá, yub_,
+ {{0x6d4180f2,0x8af98698,0x61e7002e,0x63ad088b}}, // _ibla, _днес_, _mijl, knan,
+ {{0x4444053e,0x69c8c384,0x637583a8,0x6b830162}}, // _rp_, alde, _lánz, _îngh,
+ {{0x6d572b95,0xceb40201,0xa3b88875,0x2bb880f7}}, // _baxa, _edən_, _عامر_, _عامة_,
+ {{0x7bc983d3,0x442a5f00,0xf0b8803d,0x7bc2801b}}, // lleu, tub_, _کاهش_, _zkou,
+ {{0xef1f0059,0x2905809f,0xf8d18fea,0x61fd00eb}}, // mkün_, mbla_, _हरिय, _atsl,
+ {{0x6d5882a5,0x442a6f98,0x645c1151,0x7e620118}}, // zdva, rub_, rvri, _áopo,
+ {{0x44386f99,0x442a26dc,0xc3328051,0x61e700f3}}, // [7e80] str_, sub_, כון_, _bijl,
+ {{0x2617890a,0x44446f9a,0xe9d70c5c,0xf8d186b7}}, // _निजी_, _tp_, дку_, _हराय,
+ {{0x44440add,0x657aa305,0x3f8d80e7,0x3f86826f}}, // _up_, _roth, _bleu_, tkou_,
+ {{0x6d41ef9b,0xddc9802e,0x657aef9c,0x6d4abfb0}}, // _abla, _preş, _soth, wefa,
+ {{0x63a41572,0x6d4aef9d,0x3f86816b,0x69c88257}}, // miin, tefa, rkou_, ylde,
+ {{0xf98784c0,0x7bc2867f,0x3f86cd18,0x63a422d9}}, // _شب_, _skou, skou_, liin,
+ {{0x7bc98114,0x6d58b03e,0xb17680ff,0xf1b9826c}}, // fleu, rdva, _nhượ, nušu_,
+ {{0x290ee841,0x63a41e0e,0x63758b4e,0x6d41d2bc}}, // _sefa_, niin, _kány, _ebla,
+ {{0x657a809f,0x6d4aa3b5,0x998900e1,0x914aa17e}}, // _toth, pefa, stať_, очна_,
+ {{0x63ad09b3,0x66029cb8,0x63a40b8a,0x442a802a}}, // znan, mpok, hiin, _ªb_,
+ {{0x69c884b8,0xb5fd8699,0x63a4443e,0x63758019}}, // rlde, _vrše, kiin, _lány,
+ {{0x59d0000f,0x7bc99c33,0x39580082,0x6d5702a3}}, // _तीसर, cleu, _lars_, _saxa,
+ {{0x63ad19a1,0x63a430e0,0x395802c4,0x6d5701df}}, // vnan, diin, _oars_, _paxa,
+ {{0xea000104,0x395816fb,0xd6d9809a,0x7bc0ef9e}}, // _xuất_, _nars_, _cały_, lomu,
+ {{0x63ad6f9f,0x63a40087,0xb5fd826c,0x69cb8035}}, // tnan, fiin, _kršc, हतरी,
+ {{0x6d576525,0x69d40cce,0x63a419b0,0x7bc0efa0}}, // _waxa, _बीबी, giin, nomu,
+ {{0x6d576fa1,0xdce881d0,0x61e701ed,0xb60401d0}}, // [7e90] _taxa, _podě, _pijl, tvář,
+ {{0xc4c4850c,0xef198063,0x66028831,0x63ad6fa2}}, // _نے_, _może_, dpok, snan,
+ {{0x31c41485,0xfe708077,0x27e9010b,0x63a402ec}}, // тств, یده_, _kian_, biin,
+ {{0x660d3c07,0xea00001c,0x8af00085,0x27e96fa3}}, // _kwak, _suất_, ndəl, _jian_,
+ {{0x27e900f7,0xdb0080f7,0x61e70282,0x61fd0b64}}, // _mian_, cimé, _tijl, _utsl,
+ {{0x660d2169,0x6e2bb784,0xc10500f7,0x39586fa4}}, // _mwak, rugb, _سوري, _gars_,
+ {{0x660d0a40,0xf99201c6,0x6fca9e1e,0x6e2b82c4}}, // _lwak, _גרם_, kých, sugb,
+ {{0x58d41e18,0x7bc9efa5,0xa5bb016a,0x29110a03}}, // _рост, uleu, dróm, _heza_,
+ {{0x7bc9efa6,0xb5c98117,0xa5bb2a63,0x26c902d5}}, // rleu, _فورم_, tról, _nyao_,
+ {{0x6d41825b,0x291131a7,0xf99200be,0x2cfb1513}}, // _ubla, _jeza_, ָרט_, ्रोल_,
+ {{0x2cfb023c,0x63a402a3,0x29115e42,0x27e93581}}, // ्रैल_, yiin, _meza_, _bian_,
+ {{0x2b5900f7,0x63ad9620,0xf8b200be,0x1cb8830f}}, // _nasc_, čans, רשט_, _غالب_,
+ {{0xe28eefa7,0x29c28125,0x27e944b9,0x3dc580b9}}, // _са_, rðar_, _dian_, _kklw_,
+ {{0xef1f0065,0x29110578,0x63758b4c,0x44278fb0}}, // lkül_, _neza_, _rány, orn_,
+ {{0xf8c68935,0x205488b0,0x8fa601cf,0x8c4311d2}}, // _रुपय, _атыр, _паке, лере,
+ {{0x27e90104,0x69c1efa8,0x44278834,0xe1fa1b3f}}, // _gian_, lole, irn_, зга_,
+ {{0xa3c08076,0x2911580a,0x63a40df8,0x39586b53}}, // [7ea0] ंकत_, _beza_, riin, _pars_,
+ {{0x63bb036f,0x69c1a250,0x63a46fa9,0x291102d0}}, // čuna, nole, siin, _ceza_,
+ {{0x64a65d22,0x39586193,0x6d4e51d6,0x7bc0be7f}}, // _чана, _vars_, meba, yomu,
+ {{0x69c1bee9,0x6d5c2f9c,0x7e6454bb,0xd00f0199}}, // hole, ldra, _krip, صله_,
+ {{0x69c1e5c4,0xe5c6a028,0x7bc664de,0xb8f600c8}}, // kole, нско, _ikku, _হল_,
+ {{0xf3f181bc,0x6d4e6faa,0x7e644ef1,0x29116d4a}}, // _bụ_, neba, _mrip, _geza_,
+ {{0x961d06b7,0x68460009,0x53e68ada,0x7c2e42fc}}, // _भट्ट_, _янва, нциа, nubr,
+ {{0x6d4e6fab,0xddcd0087,0xf99f1ab3,0x29116ce2}}, // heba, _oraş, _awèh_, _zeza_,
+ {{0x7fd780be,0x4427b5ad,0xb5fd8bda,0x6fca816b}}, // _טולס_, arn_, _vršc, vých,
+ {{0x7c96824f,0x6d4e0639,0x69c1efac,0x27e935ad}}, // _прец, jeba, gole, _sian_,
+ {{0x7bc60125,0xa3d005e8,0xddcd0182,0x394ca1f3}}, // _okku, षता_, _araş, reds_,
+ {{0x6d5aefad,0x7e640573,0xb907950e,0xddcd0087}}, // _hata, _brip, _मृ_, _braş,
+ {{0x8f9b8f60,0x63a2b2db,0x3cfd856b,0x657e4206}}, // ליטי, _imon, रुवे_, _boph,
+ {{0x69c1efae,0x63a28069,0x6d5a8b17,0x7bc64575}}, // cole, _hmon, _jata, _akku,
+ {{0x6d5aefaf,0x3ce504b8,0xb5fd8067,0x27e93a20}}, // _mata, älv_, _mrša, _tian_,
+ {{0xa3c0901b,0xa3c285e8,0x5ba4054c,0x29112e4d}}, // ंकि_, ृति_, груз, _seza_,
+ {{0x29116fb0,0x660d3094,0xe1f30bbe,0x63a28609}}, // [7eb0] _peza_, _uwak, اسر_, _mmon,
+ {{0x442ebb4f,0x63a28122,0x7bc602ec,0x661b80c3}}, // kuf_, _lmon, _ekku, _rvuk,
+ {{0x63a2efb1,0x2911003a,0x7c2e0511,0x442704be}}, // _omon, _veza_, cubr, _ín_,
+ {{0x6d5ac612,0x69de0580,0x69c1da17,0xda218074}}, // _aata, ampe, zole, _मिलत_,
+ {{0x7bcd3759,0x69c19f62,0xed59b160,0x3b0900b9}}, // mlau, yole, дол_, mbaq_,
+ {{0x64a58bac,0xdca5879e,0x6d5aaa92,0x83fc90d3}}, // кала, кали, _cata, _srđa,
+ {{0x6d5aefb2,0x69c18775,0x7bc6007b,0xe73f02f1}}, // _data, vole, _ykku, lnõu_,
+ {{0x69c19e54,0xc245a2fd,0x7bcd6fb3,0x44278a51}}, // wole, вник, nlau, rrn_,
+ {{0x69c1efb4,0xe9d9b0bc,0x6d4547cb,0x6d5c1a21}}, // tole, мки_, _obha, ydra,
+ {{0x6d5aefb5,0x6b89efb6,0xe8f68b79,0x63a2efb7}}, // _gata, skeg, кль_, _emon,
+ {{0x7bcd3d38,0x443caa23,0xe0cf80ab,0x69c19342}}, // klau, ctv_, রশ্ন, role,
+ {{0x7e643e15,0x6d456d01,0xf0ee009a,0x3984016d}}, // _prip, _abha, _जल्द_, _höst_,
+ {{0x6d5ac478,0x69c18b91,0x98b1817b,0x6d5c1345}}, // _yata, pole, ızı_, tdra,
+ {{0x7afd6fb8,0x443a0187,0xf1b9826c,0x9f4a823e}}, // _afst, _pqp_, krši_, _rubí_,
+ {{0x6d4e6fb9,0x64a301e5,0x6d5c1c11,0x6e3d33fc}}, // reba, аара, rdra, atsb,
+ {{0x7bcd09ed,0x2005efba,0x7afb8abf,0x39841614}}, // glau, mpli_, ncut, _löst_,
+ {{0x6f0907f4,0x6a831ea6,0xcb128158,0xf5060698}}, // [7ec0] ñece, алта, ילן_, _изпо,
+ {{0x4c860ddc,0x3cf08035,0x443c9029,0x7afd008b}}, // _алов, _चलें_, ytv_, _efst,
+ {{0x8af00085,0x200586c0,0x7bcd5778,0x38cb0019}}, // ndək, npli_, blau, وامی_,
+ {{0xe7d080c8,0x7bcd6fbb,0x7bc60326,0x34b701c6}}, // িদ্য, clau, _ukku, _יפים_,
+ {{0x6d5ab955,0x442e8859,0xa3bf0105,0x753b2663}}, // _pata, wuf_, इवर_, đuzv,
+ {{0xdb0d02be,0x6d5a865f,0x63a2847f,0x3cfd801b}}, // nnaî, _qata, _smon, रुले_,
+ {{0x6d5a8214,0xb5fd8904,0x71a3087e,0x61ea8ca9}}, // _vata, _vrša, _тарз, _rifl,
+ {{0x6d5aaef4,0x7c839980,0x442e96b2,0x5a349928}}, // _wata, руше, ruf_, гнит,
+ {{0x6d5a9dde,0x442eb26e,0xa92a81e5,0x443c9434}}, // _tata, suf_, міне_, stv_,
+ {{0x69c30353,0xce3782f6,0x443cefbc,0x0e9b8039}}, // čneg, _באמת_, ptv_, _חשמל,
+ {{0x442e80dd,0xb42588ca,0x7d189e1e,0x7e62a2dc}}, // quf_, _معنو, mavs, lvop,
+ {{0x331a8416,0x7f428144,0x7d18d5ee,0x83838198}}, // _آزاد_, nfoq, lavs, _выше,
+ {{0x61eab2bc,0x95cb0073,0x07a401e2,0xdd9101a8}}, // _tifl, дува_, раўн, موح_,
+ {{0x7d188c79,0x6e3d04dc,0xc1be06a7,0x39840338}}, // navs, rtsb, ्तोग, _möss_,
+ {{0x7bcd1a88,0x4992803d,0x3a296fbd,0x6fc28072}}, // tlau, _میکر, wrap_, षकां,
+ {{0xf1b98052,0x69c7035f,0x63bb0805,0x3a29584a}}, // vrši_, _skje, čuno, trap_,
+ {{0x7bcd00e3,0x25f4016f,0x7d188669,0x7f5bbd3e}}, // [7ed0] rlau, ंदणी_, kavs, _rauq,
+ {{0x7bcd2270,0xa2a7016f,0xdb00816b,0x661d005f}}, // slau, _घेण्, dimí, mssk,
+ {{0xa2c983bb,0x7d1880d2,0x395cb6f8,0x39840106}}, // _हुन्, davs, _mavs_, _röst_,
+ {{0x7e622944,0x443103bf,0x569595a6,0x2d8101ec}}, // _šopi, muz_, лает, _hohe_,
+ {{0x2d813e8a,0x2a6681c0,0x661d0b81,0x7afb82d0}}, // _kohe_, _nrob_, nssk, vcut,
+ {{0xaac6901c,0xbbcb8ebf,0x69c7021e,0xef1982a6}}, // _लड़क, ात्क, _ukje, _doża_,
+ {{0x44311d3c,0x03a5b33d,0x2d8100b9,0x628683ba}}, // nuz_, лико, _mohe_, økon,
+ {{0x69c500f1,0x2fc901c5,0xdb0402df,0x3869ebdb}}, // lohe, _nkag_, rniç, _čar_,
+ {{0x99159ae5,0xab5b019d,0x7dcd808b,0xe8d980ff}}, // льні, _akük, húsi, _đố_,
+ {{0x7afbb3c4,0x69c500f1,0x661d0370,0x27ed80ff}}, // scut, nohe, dssk, _hien_,
+ {{0xe73a310c,0x27ed81cd,0x5fb797a3,0x3cf0826f}}, // нен_, _kien_, _अदाल, ňové_,
+ {{0x2a66b0f8,0x44310a82,0x27ed81b9,0x39898118}}, // _frob_, duz_, _jien_, _músi_,
+ {{0x63a9efbe,0xa3c089a3,0x61458746,0x69c56fbf}}, // mien, ंकर_, _бела, kohe,
+ {{0x27edd548,0x69c500f1,0x2bcb83eb,0x6289ca91}}, // _lien_, johe, ातका, myeo,
+ {{0x6b96031d,0x44310cdb,0x41cf92c6,0xb5fd8140}}, // thyg, guz_, _सीएस, _oršo,
+ {{0x09be238c,0xe7bb0b04,0x6b8d221b,0x7f860144}}, // ्त्य, _उदयप, gkag, _póqu,
+ {{0xe29719ab,0x6f198063,0x3996013c,0x62898708}}, // [7ee0] гат_, dawc, _læse_, nyeo,
+ {{0x63a9e422,0x7a470029,0x44314cc8,0x6604453f}}, // hien, dītā, buz_, _itik,
+ {{0x27edefc0,0x63a986a2,0x6f161c33,0x6bd60872}}, // _bien_, kien, _keyc, _متحر,
+ {{0x63a9efc1,0x6d5e5d7f,0xb5fd812b,0x4b560098}}, // jien, _hapa, _vršn, _съот,
+ {{0x6d5e6fc2,0x63a9de43,0x27ed92f9,0x9cb3003d}}, // _kapa, dien, _dien_, _نمیت,
+ {{0x8e57010f,0x69c54c46,0x6b830087,0x63a9910f}}, // טינג_, cohe, _îngr, eien,
+ {{0x69dc0352,0x6d5e6fc3,0x63a99ed4,0x672402ce}}, // _ihre, _mapa, fien, _odij,
+ {{0x6d5e6fc4,0x27ed8640,0xa2db063a,0x6f0b826c}}, // _lapa, _gien_, पेक्, cbgc,
+ {{0x22400763,0x61ee0239,0x38678118,0x44315447}}, // ntik_, _kibl, _arnr_, zuz_,
+ {{0x6d5e6fc5,0x291a0587,0x6e938013,0x27edaffc}}, // _napa, napa_, _الكا, _zien_,
+ {{0x66041bb7,0xeaaf815b,0x99638190,0x672405f3}}, // _atik, قعی_, стыл, _bdij,
+ {{0x645a8850,0x63a9bf55,0x69c500f1,0x661d20e4}}, // _isti, cien, zohe, tssk,
+ {{0x6d5e0397,0x628990e4,0x4ea7284f,0x395c80eb}}, // _bapa, cyeo, урна, _tavs_,
+ {{0x6b829333,0x2fc68613,0xbf108076,0x4431011e}}, // _hoog, hoog_, ारुन_, tuz_,
+ {{0x6d5e0595,0x66046544,0x6fd6026f,0x291a6fc6}}, // _dapa, _etik, mácn, dapa_,
+ {{0x44315b8d,0x6b82a6dc,0x9b581ac1,0x61ee5e0b}}, // ruz_, _joog, лист_, _aibl,
+ {{0x27ed83d3,0x69c5020f,0x69dc008c,0x6d48efc7}}, // [7ef0] _rien_, tohe, _bhre, _obda,
+ {{0x6b8282c1,0x7f5f0201,0x645a981c,0x27ed84df}}, // _loog, _haqq, _osti, _sien_,
+ {{0x69c557ba,0x61ee273c,0x69dc0c41,0x629b81e8}}, // rohe, _dibl, _dhre, zzuo,
+ {{0x69c500f1,0x6d48bd34,0x63a981df,0x547a007c}}, // sohe, _abda, xien, _סטרו,
+ {{0x63a9efc8,0x6d5e07d9,0x27edefc9,0x291a03ac}}, // vien, _yapa, _vien_, bapa_,
+ {{0x201ea638,0x63a98063,0x320917ea,0x61ee63aa}}, // ksti_, wien, mpay_, _gibl,
+ {{0x27edbe28,0x6b82efca,0x63a9c475,0x7a4700eb}}, // _tien_, _boog, tien, sītā,
+ {{0x5f94a80f,0x3b8582ac,0x7f5f02a6,0x4421120b}}, // _лист, рлиг, _naqq, _mvh_,
+ {{0x201ead5c,0xe2999f84,0x320903ec,0x4999835f}}, // esti_, тап_, npay_, втня_,
+ {{0x63a98a4c,0xa9a5997b,0x7e69d68b,0xb5fb0019}}, // sien, риод, _irep, tván,
+ {{0xe8d98104,0x637c8019,0x6fd8a509,0x2240011b}}, // _đồ_, _lény, líca, ztik_,
+ {{0x66e60b01,0x6d5e17b9,0x7e69efcb,0xdee60ab2}}, // иона, _sapa, _krep, иони,
+ {{0x6fd886a5,0x291a1761,0x2d9800f7,0xcaf48065}}, // níca, yapa_, thre_, _اسمب,
+ {{0xceb2804c,0xc0e58381,0xf2d28496,0x32090c0b}}, // קים_, _колк, _ועד_, dpay_,
+ {{0x6d5e0364,0x61ee0024,0x291a0267,0xfce62c38}}, // _vapa, _ribl, vapa_, рово,
+ {{0x6b8282a3,0x6d5e0010,0x291a0010,0x7e698118}}, // _xoog, _wapa, wapa_, _orep,
+ {{0x6d5e6fcc,0x57250013,0xdb0083b0,0x291a25e0}}, // [7f00] _tapa, _طريق, nimá, tapa_,
+ {{0x6fd8a509,0x2240011b,0x9d463b4c,0xa06a038b}}, // díca, rtik_, _теод, каза_,
+ {{0x291a0393,0xb5fb026f,0x7e69efcd,0xef1a8073}}, // rapa_, hvál, _arep, вме_,
+ {{0x637c8019,0x291a085c,0x7e2a86b5,0x6fd89984}}, // _fény, sapa_, віка_, fíca,
+ {{0x5de688cc,0x69dc3910,0x764d00b9,0x7f420144}}, // ажда, _thre, _xpay, _ñoqu,
+ {{0x7e69efce,0xe0da0d5f,0x23ba80eb,0xac26983a}}, // _drep, _авг_, dīja_, афик,
+ {{0x20b90364,0x7e69efcf,0xdb09816b,0xe6d3170c}}, // _быть_, _erep, šnýc, _सर्ज,
+ {{0x7d1c6fd0,0x291812ed,0x7ac68f27,0x201e9fb2}}, // hars, _hera_, испе, vsti_,
+ {{0x3a206fd1,0x29186fd2,0x7e699be9,0x1dc61a87}}, // nsip_, _kera_, _grep, लकात,
+ {{0x64a6c3f9,0x7f5f51a8,0x7d1c6b28,0x79838428}}, // _каза, _saqq, jars, _conw,
+ {{0x3f84efd3,0x29180292,0x645aefd4,0xf4878019}}, // _komu_, _mera_, _usti, _پالی,
+ {{0x201eeb17,0x29185122,0x320900e4,0x7f5f2795}}, // rsti_, _lera_, ypay_, _qaqq,
+ {{0x7d1c6fd5,0x6d8b0085,0xdb008118,0x23ba81a9}}, // fars, _müav, cimá, cīja_,
+ {{0x29182005,0x91e3817c,0xdb0401a8,0xd5b80198}}, // _nera_, _доче, cniú, асс_,
+ {{0xb05780f7,0x5fb88651,0x5f7712c8,0xd5b80081}}, // _نشيط_, ेकाल, _ناظر, рся_,
+ {{0x5bb80009,0xa2dd8bb8,0x987b81c6,0x386d01d0}}, // ался_, _परम्, _ירוק, _čer_,
+ {{0x764d105a,0xa90720bb,0x6f1d1d58,0x29181d6a}}, // [7f10] _upay, لبان, masc, _bera_,
+ {{0x6f1d1d15,0x69c8c850,0xd1388110,0x69c782d4}}, // lasc, node, ngą_, čjeg,
+ {{0x6d5537a6,0x32090242,0x6ff3802e,0xd37101a8}}, // meza, spay_, _făcu, _فهد_,
+ {{0x7e698c6b,0x637c8019,0x6d5520e2,0x6f1d6fd6}}, // _prep, _tény, leza, nasc,
+ {{0xd37a0158,0x6db28063,0xdb00803e,0x63ad6fd7}}, // _ערשט, kład, ximá, mian,
+ {{0x2918497e,0x63ad00f8,0x98778904,0x6d554881}}, // _gera_, lian, _ušće_, neza,
+ {{0x88e50a49,0xf8e50a49,0xb8950013,0x40950013}}, // _প্রক, _প্রথ, _الأع, _الأر,
+ {{0x1fa496d4,0x63ad0c5a,0x61b183eb,0x7d1c0333}}, // _друг, nian, ीक्ष, zars,
+ {{0x7d1c6fd8,0x6f1d0722,0x6d554d22,0x957c809a}}, // yars, dasc, keza, _piąt,
+ {{0x290110dd,0x63ad6fd9,0x38978201,0x6fd604c3}}, // ncha_, hian, lərə_, táco,
+ {{0xa3d71370,0x63ad1f05,0x2d858042,0x6f1d6fda}}, // िता_, kian, _iole_, fasc,
+ {{0x7d1c6fdb,0x63ad325d,0xd498a276,0x6561efdc}}, // wars, jian, _тру_, _halh,
+ {{0x2d85efdd,0x29012e56,0x69c8efde,0x63ad2ad1}}, // _kole_, kcha_, bode, dian,
+ {{0xb8dd8a49,0x69c89c33,0x799aa5e5,0x6d550542}}, // _আর_, code, chtw, geza,
+ {{0x63ad1390,0x2d85c34d,0x232a44cc,0xab2a1895}}, // fian, _mole_, лови_, лова_,
+ {{0x29186fdf,0x7d1c1ed8,0x73990f88,0x63ad1aae}}, // _sera_, sars, атус_, gian,
+ {{0xa92630e1,0x29186fe0,0x6d556fe1,0x3f848091}}, // [7f20] йдал, _pera_, beza, _romu_,
+ {{0x7d1c02ed,0x6443a67f,0x59c7998e,0xf2d28158}}, // qars, ktni, रकिर, לען_,
+ {{0x29180357,0xaaceb2dd,0x237f80ee,0x6443efe2}}, // _vera_, _हुनक, kjuj_, jtni,
+ {{0x29010ac7,0x2d858032,0x291845b7,0x764280e7}}, // acha_, _aole_, _wera_, ttoy,
+ {{0x59c7824c,0x6561a22e,0xdfcf80a0,0x6443d0d8}}, // रकार, _balh, ريه_, etni,
+ {{0xb9960013,0x64438e23,0x29010609,0x656182df}}, // _السب, ftni, ccha_, _calh,
+ {{0x2d858da6,0x3f84827f,0x65618673,0x69c8a771}}, // _dole_, _tomu_, _dalh, vode,
+ {{0x6fb6845b,0x6d551088,0x6b9b9ac4,0x09d08c2d}}, // _امرا, zeza, ghug, हत्य,
+ {{0x656183a7,0x27e68039,0x6b866d37,0x6d555a17}}, // _falh, mmon_, _mokg, yeza,
+ {{0x6f046fe3,0x2918bf95,0x63ad6fe4,0x645e08d8}}, // _afic, úra_, zian, _ospi,
+ {{0x6d55095e,0x69c8efe5,0xb915019d,0x2b8e00e1}}, // veza, rode, _agbụ_, _tých_,
+ {{0x6b9befe6,0x69c8efe7,0x6d555542,0x7d1ae6bf}}, // chug, sode, weza, _hets,
+ {{0x6f1d6fe8,0x41e680e8,0x98b88214,0xeabf046d}}, // rasc, _ліка, ırı_, _adùn_,
+ {{0x63ad009a,0xb606026c,0x27e6ea94,0xe894854c}}, // wian, dašč, hmon_, _маль,
+ {{0x63ad5101,0x291ee5d7,0x7d1a843d,0xfbdf001c}}, // tian, mata_, _mets, _phê_,
+ {{0x291ed5d6,0x27e681a1,0x52150d70,0x6d553328}}, // lata_, jmon_, ждат, seza,
+ {{0x63ad22bf,0x6d550511,0x29016fe9,0x75288035}}, // [7f30] rian, peza, tcha_, _oddz,
+ {{0x63ad6835,0x6443efea,0x7e6d6feb,0xf99f0176}}, // sian, ytni, _irap, _etèn_,
+ {{0x290108a9,0x63ad063f,0x6616009a,0x98a781cc}}, // rcha_, pian, _zwyk, _yanı_,
+ {{0x291ec3e3,0x29015297,0x9be40221,0xdcee81d0}}, // hata_, scha_, _ніяк, žděn,
+ {{0x28cf8778,0x15f911bc,0xdce88110,0xbb3a00be}}, // _सुनि, ंदिर_, _kodė, _דערי,
+ {{0x6443efec,0xac09c7f9,0xdb0094c7,0x998001d6}}, // ttni, анка_, himä, triť_,
+ {{0x291ed5fe,0xef198063,0x7d1aefed,0x2fdf8ad0}}, // data_, _też_, _dets, _thug_,
+ {{0x2d8588c4,0x6561836e,0x3163026c,0x6f029bea}}, // _wole_, _walh, _hajz_, ccoc,
+ {{0x0cd606ce,0x291eefee,0x7bc983c9,0x2d85c20b}}, // _धर्म, fata_, roeu, _tole_,
+ {{0x291ea81c,0x6609c9d5,0x6b9b8010,0x89a99505}}, // gata_, _itek, shug, рков_,
+ {{0x6e246477,0x8af00201,0xa2c981d0,0xadf3066f}}, // _svib, ndər, _हुर्, _इंजन_,
+ {{0xe73a3391,0x7e6d09ca,0x35af8105,0x4425826c}}, // шем_, _brap, _झगड़, _ivl_,
+ {{0x291ec400,0x06cd00ab,0x7e6d4480,0xbddb6fef}}, // bata_, রেসি, _crap, rtèn,
+ {{0x7e6d009e,0x291ea87d,0xbddb009f,0x6f1bd399}}, // _drap, cata_, stèn, _leuc,
+ {{0xe8d980ff,0x67298e3a,0x5fc981d0,0xe9b88019}}, // _đỗ_, _odej, िकाल, _دنوں_,
+ {{0x7e6d2246,0xaace816f,0x130a0110,0x6609eff0}}, // _frap, _हुडक, рнай_, _otek,
+ {{0x442580eb,0xeb972d55,0x7e6d10b6,0x26c00118}}, // [7f40] _lvl_, цит_, _grap, _uxio_,
+ {{0x3f82005c,0x67298032,0xbddb24df,0xb60655f6}}, // ljku_, _adej, ntèl, rašč,
+ {{0x645e120e,0x7d1a8982,0x44258683,0x6f1b8362}}, // _uspi, _rets, _nvl_, _beuc,
+ {{0x44380cf6,0x291ea9b2,0x7d1ab392,0x6e22eff1}}, // mur_, zata_, _sets, tsob,
+ {{0x291ecca9,0x91e61878,0x443810f5,0x7d1a8461}}, // yata_, _моме, lur_, _pets,
+ {{0x44446ff2,0x27e68286,0x798181c0,0x2007023e}}, // _kq_, smon_, jjlw, ínic_,
+ {{0x44386ff3,0x6f1ba85f,0x645c555f,0x7d1aeff4}}, // nur_, _feuc, nwri, _vets,
+ {{0x2480803a,0x44440098,0x291eeff5,0x7d1a9de6}}, // ćim_, _mq_, wata_, _wets,
+ {{0x44382d68,0x291ed187,0x7d1a8065,0xd2508077}}, // hur_, tata_, _tets, رند_,
+ {{0x44386890,0x7e6d2316,0x44440187,0x59dd86a7}}, // kur_, _rrap, _oq_, _नीदर,
+ {{0x2618090a,0x291edd43,0x7764107f,0x6d589024}}, // बीसी_, rata_, _baix, meva,
+ {{0x7764107f,0x291e96e8,0x7e6d6ff6,0xd36f80f7}}, // _caix, sata_, _prap, _لهم_,
+ {{0x291ee505,0x4444618a,0x35df800f,0x443802be}}, // pata_, _aq_, _पीड़, eur_,
+ {{0x44386ff7,0x6d588006,0x95cb35cc,0x0dcb0767}}, // fur_, neva, рума_, руми_,
+ {{0x44386ff8,0xfbab02a9,0x77641a7d,0x44250333}}, // gur_, атай_, _faix, él_,
+ {{0x2b918104,0x442d1b77,0x69a68c1c,0x81c380ab}}, // _cách_, če_, टोरी, ্ষণ_,
+ {{0xdce881e2,0x8d873cb3,0x291c8088,0x6d58c013}}, // [7f50] _todė, _дунд, _keva_, keva,
+ {{0x44380763,0xa2dd8a0d,0x35df800f,0xa5bb002a}}, // bur_, _परस्, _पीढ़, cróp,
+ {{0x6d5899cf,0x291c809f,0xa3cac765,0x2b9c00e1}}, // deva, _meva_, लका_, _síce_,
+ {{0x65656ff9,0x291ceffa,0x9ee980f7,0xb8f30074}}, // _hahh, _leva_, مفضل_, _हँ_,
+ {{0x1d09891c,0xddc45f11,0xa3ab0af3,0x6447067f}}, // бели_, _hriš, कोण_, ltji,
+ {{0x2b9c03bb,0x6d5898d6,0xed599c79,0xce380039}}, // _více_, geva, сок_, קאות_,
+ {{0x6f1b8118,0x44442eaa,0x644701b0,0x61fd0686}}, // _teuc, _xq_, ntji, _husl,
+ {{0x2b91801c,0x291c8118,0x212100dd,0x61f5267c}}, // _xách_, _aeva_, lahh_, _hizl,
+ {{0x6609ba2f,0x60d55958,0x6d58d942,0x44385ed2}}, // _utek, _hyzm, beva, zur_,
+ {{0x291c8012,0x39458765,0x6fd60298,0x6d58a7f5}}, // _ceva_, чног, máci, ceva,
+ {{0x6fd601ac,0x64450069,0x77640ba3,0x6d418085}}, // láci, _nqhi, _paix, _icla,
+ {{0x44386ffb,0xf09200be,0x656543cd,0x6db28035}}, // vur_, גנט_, _aahh, płac,
+ {{0xa5bb6ffc,0x6fd60e14,0x44380077,0x77640722}}, // tróp, náci, wur_, _vaix,
+ {{0x44380cf6,0x2b918028,0x44446ffd,0xa50735aa}}, // tur_, _sách_, _pq_, зера_,
+ {{0x61fd02af,0x444416f0,0x3e5d86c0,0x64590035}}, // _ausl, _qq_, _lňt_, _ćwic,
+ {{0x6fd60e14,0x443812ed,0x61f50029,0x501a8039}}, // káci, rur_, _aizl, _פופו,
+ {{0x44386ffe,0x44440247,0x63bba2a5,0x61f5007e}}, // [7f60] sur_, _wq_, inun, _bizl,
+ {{0x44386fff,0x63bb82af,0xdb19819d,0xddc416f2}}, // pur_, hnun, _akwü, _friš,
+ {{0x443800f1,0x6d58d71d,0x9f520032,0xb4fb0039}}, // qur_, veva, _aiyé_, _לפיי,
+ {{0xd9c784e5,0x6d41957a,0xa3d7152c,0x63bb84b7}}, // रकुट, _acla, ितः_, jnun,
+ {{0x6fd66e7c,0x6d58cddb,0xf1bf188b,0xc33302f6}}, // gáci, teva, _iván_, _אור_,
+ {{0xe5a38ba8,0x2fc002ee,0x61f5017b,0x6d4a80e7}}, // ничи, _ljig_, _gizl, uffa,
+ {{0x63bb91f8,0x6d5884c4,0x81c380c8,0x09e321d2}}, // fnun, reva, ্ষা_, _чорн,
+ {{0x291c8813,0x58bb0039,0x48e1016f,0x6d588100}}, // _seva_, _המלצ, _करतो_, seva,
+ {{0xacf68013,0x6d58f000,0x291cb4d5,0x8d1a803d}}, // _فسات, peva, _peva_, _هزار_,
+ {{0xe7d600ab,0x63bbf001,0xd90d803d,0x7d1e6906}}, // _সংগঠ, anun, لیه_, _neps,
+ {{0xd250826a,0xf8be03eb,0x68050084,0x291cbf88}}, // رنگ_, ्थिय, _kėda, _veva_,
+ {{0xd838a329,0x2d8c089c,0x48a78a14,0x6722804f}}, // ruč_, öden_, ятым_, naoj,
+ {{0xbddb02be,0x291c809f,0xb886802a,0xe1ff027d}}, // stèm, _teva_, _baíñ, _trói_,
+ {{0xddc41734,0x7d1e110a,0x25f400dc,0x6aa4026c}}, // _priš, _ceps, ंदगी_, dzif,
+ {{0x6fd60e14,0x58d3acaf,0x28ac54ea,0x38c880d7}}, // záci, _пошт, _टेलि, _باشی_,
+ {{0xb4bd035a,0x61fd01e2,0x644701b0,0x61f503bf}}, // _अशी_, _pusl, rtji, _sizl,
+ {{0x6fdd00e7,0xfb340071,0x672d0091,0x6fd8928a}}, // [7f70] léco, энтэ, _idaj, mích,
+ {{0x660d1cb0,0x61f5011c,0x127b80be,0x6fd60298}}, // _itak, _qizl, זאמע, váci,
+ {{0xf9938158,0xe2f900e8,0x4af91317,0x2121008e}}, // ערע_, _мені_, _меню_, rahh_,
+ {{0x6fd60e14,0x290586a5,0x63bb1ba4,0xa5bb0118}}, // táci, zcla_, čuns, isóm,
+ {{0xb8d40663,0x8d7482e3,0x7c288711,0x59bf0a27}}, // _जे_, کانا, _avdr, ्वदर,
+ {{0x6fd60e14,0x660d0010,0xddc98035,0x236900c3}}, // ráci, _mtak, _speł, ldaj_,
+ {{0x22492b24,0x1b2180c8,0x2a6fb59e,0x66e38032}}, // ltak_, _যাবে_, _srgb_, _bìkí,
+ {{0x660d5ee2,0x23697002,0x2b9880e7,0xdfd180f7}}, // _otak, ndaj_, _déco_, _شيء_,
+ {{0x22497003,0x63bb80e3,0x660d5c39,0x6fdd26d5}}, // ntak_, rnun, _ntak, técn,
+ {{0x672d5ba7,0x2fcf90cc,0x38715908,0xf1bf532c}}, // _adaj, logg_, _krzr_, ्विन,
+ {{0x7523b623,0x660d6d2c,0xb88681df,0xa3bf01cb}}, // manz, _atak, _raíñ, ेवा_,
+ {{0x7d1e020f,0x2614800f,0x7523e4cc,0x661b8133}}, // _seps, नीकी_, lanz, _kwuk,
+ {{0xf1bf08af,0x614588cc,0x41bf0fb8,0x59bf0bc2}}, // ्वान, _жела, ्वास, ्वार,
+ {{0x7523f004,0x61fd803e,0xe9ff0104,0xe1ff0063}}, // nanz, íslu, _hoặc_, _osób_,
+ {{0xb886802a,0x660d4b14,0x330b01bc,0x8c43028e}}, // _vaíñ, _etak, _ụbọ_, кере,
+ {{0xee3a0656,0xb4ae035a,0x75238314,0x6b8bafbc}}, // _дни_, _कधी_, hanz, _jogg,
+ {{0x75238e8c,0x6d4f008b,0x6b8be2aa,0x6808928a}}, // [7f80] kanz, únað, _mogg, _věde,
+ {{0x6b8b92f1,0x7e647005,0x2366012b,0x99890140}}, // _logg, _isip, žoj_, traš_,
+ {{0x6fd8b074,0x77668098,0x7523f006,0x757b0039}}, // líci, дълж, danz, _וטיפ,
+ {{0x7c3c47f7,0x6b8b8106,0x661b8db1,0x9ec680d7}}, // murr, _nogg, _bwuk, وزشه,
+ {{0x6f09062f,0x7523f007,0xe5c68284,0x5e5a8019}}, // ñeci, fanz, мско, تجاج_,
+ {{0x7523af56,0x6db2809a,0x2b404c46,0x61c780c2}}, // ganz, ałan, lgic_, रकोष,
+ {{0x3e8508cf,0x6b8b8079,0xd94317c8,0x6d5c7008}}, // _këtë_, _bogg, _шери, iera,
+ {{0x16198105,0x2b40026c,0xb5fd81f4,0xc0e314ef}}, // _नौकर_, ngic_, _pršt, _роск,
+ {{0x6d5c6725,0x7523a9da,0x7c3c3fae,0x6fd6002a}}, // kera, banz, hurr, nácu,
+ {{0x7523a784,0xc27b012a,0x7c3c011e,0x3014a59a}}, // canz, קריי, kurr, ндор,
+ {{0x31371a0f,0xe8df8104,0x130a8009,0x7e647009}}, // ונים_, _thời_, _дней_, _asip,
+ {{0x6568bb6a,0x7c3c6504,0x6b8bf00a,0x3f8683ec}}, // _hadh, durr, _gogg, njou_,
+ {{0x4ae28bb8,0x6d5c5866,0xaae28c78,0x6568906a}}, // _परिव, fera, _परिक, _kadh,
+ {{0x6d5c3c42,0x31678352,0x6600973c,0x28cf873c}}, // gera, _ganz_, _mumk, _सुवि,
+ {{0x6568b419,0x0cd400e8,0x7e640234,0x2d8c86f3}}, // _madh, ворю, _esip, _mode_,
+ {{0x69daaa6d,0x22492b24,0x672085f3,0x7523804f}}, // llte, ttak_, _nemj, zanz,
+ {{0x6d5c2aff,0x657a90f4,0x45d51b10,0xe8df801c}}, // [7f90] bera, _onth, _поис, _khởi_,
+ {{0x6d5c04b8,0x22490289,0xf77091fb,0x443cdabd}}, // cera, rtak_, یان_, kuv_,
+ {{0xb9070403,0x75238211,0x2249700b,0x69da8014}}, // _पर_, vanz, stak_, ilte,
+ {{0x2d9ed680,0xa3ab1053,0x69da81ec,0x75238314}}, // _alte_, कोश_, hlte, wanz,
+ {{0x75238a5a,0x2d8cdf04,0x6568f00c,0x1d09bb8f}}, // tanz, _bode_, _badh, пели_,
+ {{0x7bc2a264,0x2d8cb5db,0x6b8b8098,0x6db2809a}}, // _ajou, _code_, _sogg, słan,
+ {{0x75238d02,0x6d45022e,0x6fd88e14,0x6b8bb4d7}}, // ranz, _mcha, zíci, _pogg,
+ {{0x61c7885d,0x6d5c0863,0x22158012,0x75239b19}}, // रक्ष, zera, нфор, sanz,
+ {{0x6568f00d,0x7c3c4a6c,0x3e3588ca,0x7523e522}}, // _fadh, zurr, _سفار, panz,
+ {{0x2d8c805f,0x6d45700e,0x6568f00c,0x6d5c700f}}, // _gode_, _ncha, _gadh, xera,
+ {{0x61f8803b,0x6d5c24c0,0xd00e815b,0x7afd0122}}, // _divl, vera, یلی_, _ogst,
+ {{0x6fd8f010,0x6d452738,0x6d5c571c,0x2bac866f}}, // tíci, _acha, wera, yści_,
+ {{0x6fd60feb,0x6e3d0747,0xdb040216,0xa1958110}}, // dáct, gusb, xniñ, _падч,
+ {{0x6145942c,0x7bdbc43e,0x75218061,0x6fd8b666}}, // _чека, lluu, _jelz, ríci,
+ {{0x6d5c7011,0xc4d280be,0x7f5d7012,0x6d453aaa}}, // rera, ָגן_, besq, _dcha,
+ {{0x6d5c7013,0xb4e0035a,0x6d454a96,0xd00e80f7}}, // sera, _तरी_, _echa, _الي_,
+ {{0xfaa34168,0x211a00f7,0xdb0f0061,0x5a0c83de}}, // [7fa0] гато, كتاب_, ájár, ַלאַ,
+ {{0xe9d73de7,0x7c3c7014,0x6720df55,0x752181d0}}, // еку_, purr, _semj, _nelz,
+ {{0x3f9fac07,0x6568f015,0xe8df801c,0xd00f00d7}}, // _bluu_, _radh, _khối_, ملل_,
+ {{0xd6d9809a,0xa3de8006,0x67263b0f,0x69d53a8c}}, // _był_, _दीं_, nakj, _ekze,
+ {{0x2d8c956b,0x65688077,0x69d5026c,0xaace81af}}, // _pode_, _padh, _fkze, _हुंक,
+ {{0x63a47016,0xceb300be,0x644af017,0x6568c04b}}, // lhin, ניג_, ttfi, _qadh,
+ {{0x8fa68002,0x2d8ca6fd,0x6fd60019,0xb0cf86a7}}, // _заве, _vode_, nács, _सुलग,
+ {{0x65688f49,0x443cd8e5,0x63a47018,0xddc98087}}, // _wadh, ruv_, nhin, _preţ,
+ {{0x644ad4c2,0x8fa38764,0x6568f019,0x0a0a8133}}, // stfi, _сате, _tadh, _bụụr,
+ {{0xe4d680f7,0x7a5380f7,0x200e808e,0xa2c1001b}}, // _كتاب, اضيا, _ptfi_, लपत्,
+ {{0x63a4701a,0x893703de,0xc95301c6,0x3202136f}}, // khin, ערטע_, אמת_, _luky_,
+ {{0x6d45177a,0xe8df80ff,0x7c2aad91,0x7bdb81b4}}, // _scha, _chối_, msfr, cluu,
+ {{0xd6db50f6,0x63a451c6,0x7c2ad4b6,0x7f5d47e3}}, // ято_, dhin, lsfr, resq,
+ {{0xddc9f01b,0x99800084,0x127b80be,0x31348993}}, // _kreš, irių_, _מאדע, _рекр,
+ {{0x6fd8c07d,0xe0d48158,0x395e9e5e,0x0133853d}}, // lícu, _בײַ_, mets_, _صعود,
+ {{0x395ec923,0xa3ab0063,0x7c33826f,0x3a290144}}, // lets_, कों_, _údrž, tsap_,
+ {{0x27e90364,0x6fd602ba,0x44380083,0x6d450eba}}, // [7fb0] _ihan_, ráct, arr_, _tcha,
+ {{0x6d45701c,0x395ef01d,0xa3e791bc,0x442cad38}}, // _ucha, nets_, _मीन_, _ovd_,
+ {{0x6fd882ba,0xbb1b02be,0x3a29701e,0x63a4701f}}, // hícu, _boît, ssap_, bhin,
+ {{0x63a41645,0x2a6681d0,0x7c2ac753,0xdb070198}}, // chin, _osob_, dsfr, ämät,
+ {{0x395ea09b,0x442cb43f,0x9f58823e,0x6d9d8706}}, // kets_, _avd_, _durà_, _dèan,
+ {{0x395e83d3,0x200e0722,0xddc980fe,0x442c8299}}, // jets_, ífic_, _breš, _bvd_,
+ {{0x395e8370,0x7c2aad91,0x27e95885,0xd46685a5}}, // dets_, gsfr, _ohan_, вище_,
+ {{0x58d42103,0x77d780f7,0x27e9001c,0x752181ed}}, // _сост, _أغسط, _nhan_, _welz,
+ {{0x35f58b87,0x272000c8,0xdcfe24a0,0x395e80e7}}, // _апар, _ভালো_, _uopć, fets_,
+ {{0x6fdd03d3,0xe4a7828b,0x6f0d002e,0x395e83a6}}, // léch, _прео, _afac, gets_,
+ {{0x07a60ea6,0xe73a1878,0xddc98025,0x8fa60698}}, // ванн, мен_, _greš, ване,
+ {{0x27e909c5,0x212580dd,0x63a40168,0x6fd88144}}, // _chan_, salh_, xhin, bícu,
+ {{0xe28ea40f,0x27e95ffa,0x59d10c28,0x395ea9c6}}, // _та_, _dhan_, तकार, bets_,
+ {{0x6d43a487,0x3202026f,0x2606041c,0x395ec7e3}}, // ngna, _ruky_, _vôo_, cets_,
+ {{0x63a426c8,0x44380176,0xb5fb0298,0x6d43a639}}, // thin, urr_, tvár, igna,
+ {{0x25e8823c,0x224d8370,0x6b89f020,0x6fd60019}}, // _चीनी_, otek_, njeg, rács,
+ {{0x224d8019,0x442a09d1,0xb5fb0061,0xb1461a47}}, // [7fc0] ntek_, ssb_, rvár, кнал,
+ {{0x409614d6,0x63a47021,0xd5af00f7,0x63b6031d}}, // крет, shin, _وفي_, siyn,
+ {{0x69c1dc09,0x98b10214,0xdca609a0,0x657e01a8}}, // inle, _bazı_, _рани, _inph,
+ {{0x224db990,0xee0c8133,0x66040c85,0xd00f0199}}, // ktek_, _aṅụr, _kuik, زله_,
+ {{0x224df022,0x442c816d,0xe5348c73,0x67240a6f}}, // jtek_, _svd_, гель, _meij,
+ {{0x6d4380f2,0xdd8f80f7,0xddc9f023,0x7c2a848d}}, // ggna, يون_, _preš, tsfr,
+ {{0x395e84fe,0x8bc6ad55,0x20034f98,0x6604027c}}, // vets_, ксид, _guji_, _luik,
+ {{0x6fd89220,0x68e33ea8,0x6b89f024,0x442c81ed}}, // tícu, ünde, gjeg, _vvd_,
+ {{0x27e9031d,0x60270019,0x0566813a,0x395e84fe}}, // _rhan_, _témá, _иван, tets_,
+ {{0x27e91cee,0x4973976e,0xddc980eb,0x6fd89727}}, // _shan_, альс, _treš, rícu,
+ {{0x67242151,0x395eecc5,0x6b8982fd,0xb90a800f}}, // _beij, rets_, bjeg, _मर_,
+ {{0x2a668069,0x66042de7,0x69c1f025,0x395ebe88}}, // _tsob_, _buik, anle, sets_,
+ {{0x28d9000f,0x539b82f6,0x395e980a,0x6f0d9727}}, // _बुनि, _קידו, pets_, ñaci,
+ {{0xbbbd0076,0x7bc6076c,0x6604110f,0x61fc00d7}}, // ्क्क, _ajku, _duik, _nirl,
+ {{0x6724184f,0x27e94bc0,0xd40404fa,0xb33b479b}}, // _feij, _than_, ияти, _alça,
+ {{0x200300dd,0x21230267,0x6e2d8267,0xdcfa8609}}, // _ruji_, _sejh_, šabi, _antċ,
+ {{0x61fc0459,0x68e68201,0x98b107c0,0xe5c701e2}}, // [7fd0] _birl, əkda, _razı_, _асно,
+ {{0x645ace3c,0x61bf000d,0x20030458,0x6fdd0333}}, // _opti, ्वेष, _puji_, déci,
+ {{0xa5bb01df,0x351a0039,0x224dd382,0xd47900be}}, // dróx, _אותנ, ztek_, _װאָל,
+ {{0xff25803d,0x224d885d,0x2240279e,0x5ecc80ab}}, // ابای, ytek_, buik_, ়েছে,
+ {{0xdd9587ac,0xf77088ca,0x26c98073,0x22840b79}}, // лады, تال_, çao_, _кург,
+ {{0xdce885c5,0x63a2f026,0x6d43820d,0x61fc7027}}, // _aldı, _alon, ugna, _girl,
+ {{0x6d43978e,0x20110102,0x26d282d0,0x6b89f028}}, // rgna, _utzi_, üyor_, tjeg,
+ {{0x2489803a,0x04568013,0x224d8019,0x160300d4}}, // ćam_, الية_, ttek_, लदार_,
+ {{0xe1f9a927,0x63a28197,0x236df029,0x6724702a}}, // нги_, _dlon, rdej_, _reij,
+ {{0x6724702b,0x63a2b392,0x224df02c,0x44210359}}, // _seij, _elon, rtek_, _owh_,
+ {{0x224df02d,0x6b89f02e,0x68458084,0x69c186b1}}, // stek_, pjeg, ынна, rnle,
+ {{0x660406f7,0x290ec26a,0x224d8061,0x6fd88118}}, // _puik, _effa_, ptek_, lícr,
+ {{0x7bd98135,0x4421010c,0x9b74bb76,0x2bc70af3}}, // _ikwu, _awh_, _خالص, रचना,
+ {{0xde031814,0x660335aa,0x8c460187,0x44210239}}, // спри, спра, леге, _bwh_,
+ {{0x672421aa,0x64a31662,0xdca32927,0x657e1029}}, // _teij, бара, бари, _vnph,
+ {{0x644e0004,0x3878813c,0x3996455b,0x61fc702f}}, // rtbi, ærre_, _læst_, _sirl,
+ {{0x3a2d8b50,0x644e364c,0xade3800f,0x77a60118}}, // [7fe0] nsep_, stbi, गतान_, _cóxe,
+ {{0x8d6624a4,0x2240038e,0xa3e41a3b,0xc4862e65}}, // _свое, ruik_, _पीर_, _блок,
+ {{0x7e69b13c,0x765badcb,0xf99f023e,0x6fd88118}}, // _asep, _apuy, _atès_, dícr,
+ {{0x7bd98135,0xdd2f81d0,0x69c77030,0x7c2e06b1}}, // _nkwu, měři, _ejje, msbr,
+ {{0x6d410177,0x52e286ae,0xa5bb03a8,0x7e698061}}, // ólag, _परेस, tróx, _csep,
+ {{0x7bc401e2,0x61fc00f7,0xddcd0984,0x6fdd0b2c}}, // lniu, _uirl, _kraš, réci,
+ {{0x7794853d,0x2d9c87dd,0x7e69f031,0x2b9c1029}}, // _پیشا, rkve_, _esep, _níck_,
+ {{0x5a3497f9,0x69d881ac,0x6fdd03a7,0xa3e782f1}}, // анит, _skve, péci, _मीद_,
+ {{0xf1268987,0x6f9380f7,0x399601fa,0x63a2f032}}, // льно, _القض, _fæst_, _vlon,
+ {{0x865b83c8,0xddcd012b,0x776d0079,0x7bd98870}}, // _אדמי, _oraš, _saax, _ekwu,
+ {{0x3f92080a,0x63a280dd,0x61d700be,0x6df381a8}}, // _boyu_, _tlon, רויף_, _اكلا,
+ {{0xb8ec800f,0x7c2e013c,0xdb0d0187,0xdfd50049}}, // _रख_, dsbr, miaç, ромы,
+ {{0xdb0d00a9,0x2216cb8d,0x9f92007b,0xef1f0061}}, // liaç, _сфер, ráð_, ljük_,
+ {{0x290e80e5,0x776d01b4,0x2bcc8f1b,0xcad781c6}}, // _uffa_, _waax, ावका, _עוגת_,
+ {{0x2b9c0028,0xd37b47f9,0x7c2e21b4,0x11db8039}}, // _kích_, нча_, gsbr, _רחוב,
+ {{0xddcd0390,0xdce884e8,0xc2c501a8,0x7bc4524c}}, // _draš, _nadč, ليزي, gniu,
+ {{0x7762c03b,0x52e286a7,0x0ce1a207,0x5edc066f}}, // [7ff0] deox, _परोस, _फर्म, _गुप्_,
+ {{0xa2c5801b,0xdb0081a8,0xdcfa82d6,0x5f5d0135}}, // ापत्, dhmí, _antč, _ọnụọ,
+ {{0xddcd2944,0x765b808e,0x315783de,0xe1e50072}}, // _graš, _spuy, ריבן_, _कठीण_,
+ {{0x2d93062f,0xbddb00e7,0x0eb9827e,0xdb0d3007}}, // _hoxe_, ctèr, кулы_, diaç,
+ {{0x366986eb,0xe80b8a27,0xddcd0af8,0x752a8299}}, // тало_, _संभा_, _zraš, fafz,
+ {{0xed598a29,0x6d476890,0x51872240,0x2d93011b}}, // ток_, ngja, _сума, _joxe_,
+ {{0x6fdd087a,0xcd980039,0x2b9c00ff,0x69d703a8}}, // lécu, רדות_, _bích_, moxe,
+ {{0x6b8d010b,0x69d7002a,0xa0c401a8,0x69c68061}}, // njag, loxe, _ايفو, éken,
+ {{0x69c51585,0x7c24826c,0x386081a1,0x49c9917e}}, // onhe, ćira, _ćira_, клин_,
+ {{0x69c5236c,0x499594c4,0x672bf033,0x6b8d440f}}, // nnhe, ршит, lagj, hjag,
+ {{0xdd7a8158,0xdb0d03a7,0x27ed801c,0x6563c590}}, // _שטעל, ciaç, _khen_, menh,
+ {{0xc5e90039,0x6e2d826c,0x7bd98870,0xdd2f81d0}}, // _חד_, šabu, _ukwu, věři,
+ {{0x6443a250,0x539a8039,0x2d937034,0x63a9808b}}, // luni, _שינו, _boxe_, mhen,
+ {{0x7c2e0448,0xddcd12b9,0x64a601e5,0x6563f035}}, // tsbr, _praš, аага, nenh,
+ {{0xab2a3210,0x1c3902c7,0x6443d958,0x232a20bf}}, // кова_, лять_, nuni, кови_,
+ {{0x920712c7,0x63a98956,0x63bbf036,0xda0880ff}}, // _वंशज_, nhen, niun, _hỏa_,
+
+ };
+ // table_hash = 1305-8978, unused_entries = 116 (0.07%)
+
+static const uint32 kQuadChrome0122_16SizeOne = 28727; // One-langprob count
+static const uint32 kQuadChrome0122_16IndSize = 28727; // Largest subscript
+static const uint32 kQuadChrome0122_16Ind[kQuadChrome0122_16IndSize] = {
+ // [0000]
+ 0x00000000, 0x00000000, 0x08040709, 0x00000a24, // -- -- bg.ru.uk_444 mk.un.un_900
+ 0x06000c13, 0x1a6b1cee, 0x0000131c, 0x04523508, // sv.de.un_650 id.ceb.tl_422 et.un.un_800 zu.ha.fi_443
+ 0x236e05ec, 0x00000415, 0x2d162908, 0x03003f08, // fr.hmn.ca_644 fi.un.un_700 sl.hr.sk_443 af.nl.un_430
+ 0x1c090daf, 0x00000d1c, 0x281e1c05, 0x13000914, // ne.hi.mr_655 ne.un.un_800 id.ms.sw_333 hi.bh.un_660
+ // [0010]
+ 0x00002815, 0x21520ea4, 0x0000111c, 0x0000181c, // sw.un.un_700 is.ha.jw_433 ro.un.un_800 ga.un.un_800
+ 0x0000271c, 0x02212013, 0x06030ca4, 0x1f2864af, // gd.un.un_800 sq.jw.da_665 sv.nl.de_433 lg.sw.cy_655
+ 0x05190b08, 0x00001215, 0x25131a07, 0x00000d15, // es.gl.fr_443 hu.un.un_700 tl.et.eu_432 cs.un.un_700
+ 0x00002b15, 0x64005219, 0x11000721, 0x11001f18, // vi.un.un_700 ha.lg.un_750 it.ro.un_860 cy.ro.un_740
+ // [0020]
+ 0x19000b08, 0x00000724, 0x0000021c, 0x09000d21, // es.gl.un_430 bg.un.un_900 da.un.un_800 ne.hi.un_860
+ 0x3216170e, 0x17163214, 0x11356805, 0x211611ee, // sr.hr.bs_555 bs.hr.sr_666 ig.zu.ro_333 ro.hr.jw_422
+ 0x00002b1c, 0x00000f1c, 0x00001903, 0x64075204, // vi.un.un_800 lv.un.un_800 gl.un.un_300 ha.it.lg_332
+ 0x0309060c, 0x040810ee, 0x00001115, 0x09003f0d, // de.pl.nl_543 be.uk.ru_422 ro.un.un_700 af.pl.un_540
+ // [0030]
+ 0x16092d5a, 0x0e0307a0, 0x00004a06, 0x55005308, // sk.pl.hr_553 it.nl.is_322 yo.un.un_400 ht.rw.un_430
+ 0x21001e05, 0x0000090f, 0x0000050a, 0x00000701, // ms.jw.un_330 pl.un.un_600 fr.un.un_500 it.un.un_200
+ 0x00002d1c, 0x00000115, 0x32171614, 0x32161714, // sk.un.un_800 iw.un.un_700 hr.sr.bs_666 sr.hr.bs_666
+ 0x18002723, 0x00002115, 0x2d000d1b, 0x211812af, // gd.ga.un_880 fa.un.un_700 cs.sk.un_770 ur.ar.fa_655
+ // [0040]
+ 0x64351aad, 0x0000520f, 0x171632a4, 0x25196ba0, // tl.zu.lg_643 ha.un.un_600 bs.hr.sr_433 ceb.gl.eu_322
+ 0x08030208, 0x532b1f0c, 0x00002724, 0x1b321704, // da.nl.no_443 cy.vi.ht_543 gd.un.un_900 sr.bs.tr_332
+ 0x00001006, 0x0400110c, 0x0000080f, 0x2a1220a7, // be.un.un_400 ro.ru.un_530 no.un.un_600 sq.hu.mt_532
+ 0x00000124, 0x0e210804, 0x21001213, 0x0000280f, // en.un.un_900 no.jw.is_332 ur.fa.un_650 sw.un.un_600
+ // [0050]
+ 0x06030cad, 0x0000011c, 0x1716320e, 0x35060a0c, // sv.nl.de_643 en.un.un_800 bs.hr.sr_555 pt.de.zu_543
+ 0x09001307, 0x3b3117a0, 0x08040708, 0x1e001c14, // bh.hi.un_420 sr.az.so_322 bg.ru.uk_443 id.ms.un_660
+ 0x2d092507, 0x1b00311a, 0x6b1011ec, 0x52352808, // eu.pl.sk_432 az.tr.un_760 ro.lt.ceb_644 sw.zu.ha_443
+ 0x3217160e, 0x0000351c, 0x130d0912, 0x02000808, // hr.sr.bs_555 zu.un.un_800 hi.ne.bh_654 no.da.un_430
+ // [0060]
+ 0x211812ad, 0x0000120f, 0x042125ee, 0x0000091c, // ur.ar.fa_643 hu.un.un_600 eu.jw.fi_422 pl.un.un_800
+ 0x12001822, 0x0000121c, 0x4a006e08, 0x321617af, // ar.ur.un_870 hu.un.un_800 hmn.yo.un_430 sr.hr.bs_655
+ 0x27001822, 0x00006e15, 0x0000020f, 0x181221af, // ga.gd.un_870 hmn.un.un_700 da.un.un_600 fa.ur.ar_655
+ 0x10110408, 0x17080408, 0x11253508, 0x0c08020d, // ru.ro.be_443 ru.uk.sr_443 zu.eu.ro_443 da.no.sv_554
+ // [0070]
+ 0x0f006e0c, 0x00001001, 0x00001c0f, 0x00000a15, // hmn.lv.un_530 be.un.un_200 mr.un.un_600 pt.un.un_700
+ 0x00001315, 0x1c001307, 0x1c001305, 0x0000211c, // bh.un.un_700 bh.mr.un_420 bh.mr.un_330 jw.un.un_800
+ 0x080225ee, 0x00003b15, 0x0000290f, 0x00000e15, // eu.da.no_422 so.un.un_700 sl.un.un_600 is.un.un_700
+ 0x0100240d, 0x171632a9, 0x31001b13, 0x0704080d, // yi.iw.un_540 bs.hr.sr_544 tr.az.un_650 uk.ru.bg_554
+ // [0080]
+ 0x1f002a13, 0x0000070f, 0x0c080214, 0x1800271a, // mt.cy.un_650 bg.un.un_600 da.no.sv_666 gd.ga.un_760
+ 0x0000100f, 0x00003115, 0x00003124, 0x0000110f, // be.un.un_600 az.un.un_700 az.un.un_900 ro.un.un_600
+ 0x00001703, 0x311b03a9, 0x64006e07, 0x00000e0f, // sr.un.un_300 nl.tr.az_544 hmn.lg.un_420 is.un.un_600
+ 0x2700181b, 0x17003204, 0x00001c01, 0x11040705, // ga.gd.un_770 bs.sr.un_320 id.un.un_200 bg.ru.ro_333
+ // [0090]
+ 0x27001814, 0x00004a0f, 0x2a281804, 0x31080c08, // ga.gd.un_660 yo.un.un_600 ga.sw.mt_332 sv.no.az_443
+ 0x120c08af, 0x011a29a4, 0x080c3507, 0x520164ee, // no.sv.hu_655 sl.tl.en_433 zu.sv.no_432 lg.en.ha_422
+ 0x00000715, 0x2a3b5307, 0x00000915, 0x16320d0c, // bg.un.un_700 ht.so.mt_432 pl.un.un_700 cs.bs.hr_543
+ 0x00001a15, 0x320f0305, 0x08005308, 0x00002315, // tl.un.un_700 nl.lv.bs_333 ht.no.un_430 ca.un.un_700
+ // [00a0]
+ 0x21001819, 0x080a1707, 0x190b0aa4, 0x552132a0, // ar.fa.un_750 sr.mk.uk_432 pt.es.gl_433 bs.jw.rw_322
+ 0x0000521c, 0x130d09af, 0x04253508, 0x10041ca0, // ha.un.un_800 hi.ne.bh_655 zu.eu.fi_443 id.fi.lt_322
+ 0x0827180c, 0x00000a1c, 0x05042aa0, 0x00000b15, // ga.gd.no_543 pt.un.un_800 mt.fi.fr_322 bn.un.un_700
+ 0x10080c0c, 0x00002524, 0x0a1707a9, 0x070a04af, // sv.no.lt_543 eu.un.un_900 bg.sr.mk_544 ru.mk.bg_655
+ // [00b0]
+ 0x3f03280d, 0x101120a9, 0x08020eaf, 0x07000a1a, // sw.nl.af_554 sq.ro.lt_544 is.da.no_655 mk.bg.un_760
+ 0x00005515, 0x2d130855, 0x04110712, 0x21001218, // rw.un.un_700 no.et.sk_442 bg.ro.ru_654 ur.fa.un_740
+ 0x35002812, 0x00001e01, 0x13090da9, 0x1a003522, // sw.zu.un_640 ms.un.un_200 ne.hi.bh_544 zu.tl.un_870
+ 0x13090dec, 0x3f130313, 0x00002415, 0x080417af, // ne.hi.bh_644 nl.et.af_665 yi.un.un_700 sr.ru.uk_655
+ // [00c0]
+ 0x272821a9, 0x2508060c, 0x13000905, 0x00001601, // jw.sw.gd_544 de.no.eu_543 hi.bh.un_330 hr.un.un_200
+ 0x07170a13, 0x070a10ee, 0x06002504, 0x2d003b04, // mk.sr.bg_665 be.mk.bg_422 eu.de.un_320 so.sk.un_320
+ 0x00000b1c, 0x03003f1a, 0x17040aec, 0x181a6bad, // bn.un.un_800 af.nl.un_760 mk.ru.sr_644 ceb.tl.ga_643
+ 0x3f040512, 0x16003207, 0x321716a4, 0x13000922, // fr.fi.af_654 bs.hr.un_420 hr.sr.bs_433 hi.bh.un_870
+ // [00d0]
+ 0x20004a0e, 0x28055304, 0x17163205, 0x20002522, // yo.sq.un_550 ht.fr.sw_332 bs.hr.sr_333 eu.sq.un_870
+ 0x1c000909, 0x2100121a, 0x04001119, 0x0000210f, // hi.mr.un_440 ur.fa.un_760 ro.ru.un_750 fa.un.un_600
+ 0x03023fa0, 0x0e000813, 0x532801a4, 0x20252804, // af.da.nl_322 no.is.un_650 en.sw.ht_433 sw.eu.sq_332
+ 0x1c1309ec, 0x1e001c02, 0x536428ee, 0x2a000705, // hi.bh.mr_644 id.ms.un_220 sw.lg.ht_422 it.mt.un_330
+ // [00e0]
+ 0x10040107, 0x00002d0f, 0x17040813, 0x06000e08, // en.fi.lt_432 sk.un.un_600 uk.ru.sr_665 is.de.un_430
+ 0x00001a06, 0x00000703, 0x3510010c, 0x0000050f, // tl.un.un_400 it.un.un_300 en.lt.zu_543 fr.un.un_600
+ 0x00000815, 0x04070a09, 0x0e25100c, 0x00000f15, // uk.un.un_700 mk.bg.ru_444 lt.eu.is_543 lv.un.un_700
+ 0x0e00020d, 0x01000608, 0x00001e03, 0x52000104, // da.is.un_540 de.en.un_430 ms.un.un_300 en.ha.un_320
+ // [00f0]
+ 0x1b3531ad, 0x00002015, 0x00000c15, 0x0000030f, // az.zu.tr_643 sq.un.un_700 sv.un.un_700 nl.un.un_600
+ 0x25042907, 0x18006b04, 0x00002824, 0x00001815, // sl.fi.eu_432 ceb.ga.un_320 sw.un.un_900 ar.un.un_700
+ 0x28000104, 0x13641a0c, 0x32003507, 0x6b061fa0, // en.sw.un_320 tl.lg.et_543 zu.bs.un_420 cy.de.ceb_322
+ 0x00005203, 0x1800352c, 0x32171602, 0x00002b0f, // ha.un.un_300 zu.ga.un_990 hr.sr.bs_222 vi.un.un_600
+ // [0100]
+ 0x04001309, 0x00001c06, 0x00002515, 0x0a070407, // et.fi.un_440 id.un.un_400 eu.un.un_700 ru.bg.mk_432
+ 0x00002b24, 0x1300090e, 0x00000c0a, 0x1c090da4, // vi.un.un_900 hi.bh.un_550 sv.un.un_500 ne.hi.mr_433
+ 0x35285509, 0x1a556ba6, 0x04001e04, 0x1e001c0e, // rw.sw.zu_444 ceb.rw.tl_521 ms.fi.un_320 id.ms.un_550
+ 0x00002101, 0x282a180d, 0x556410a4, 0x01002413, // jw.un.un_200 ga.mt.sw_554 lt.lg.rw_433 yi.iw.un_650
+ // [0110]
+ 0x00001015, 0x4a1b1007, 0x10000822, 0x0a0407ec, // lt.un.un_700 lt.tr.yo_432 uk.be.un_870 bg.ru.mk_644
+ 0x00001f15, 0x00001603, 0x1c320da0, 0x00001224, // cy.un.un_700 hr.un.un_300 cs.bs.id_322 ur.un.un_900
+ 0x00001901, 0x10641104, 0x16002904, 0x0000250f, // gl.un.un_200 ro.lg.lt_332 sl.hr.un_320 eu.un.un_600
+ 0x0000310f, 0x1c556804, 0x0000251c, 0x32171605, // az.un.un_600 ig.rw.id_332 eu.un.un_800 hr.sr.bs_333
+ // [0120]
+ 0x31080ea0, 0x641355ad, 0x00006b06, 0x00003f03, // is.no.az_322 rw.et.lg_643 ceb.un.un_400 af.un.un_300
+ 0x211218af, 0x00000e1c, 0x64003514, 0x091c13a4, // ar.ur.fa_655 is.un.un_800 zu.lg.un_660 bh.mr.hi_433
+ 0x13000908, 0x00002b0a, 0x01002419, 0x32171609, // hi.bh.un_430 vi.un.un_500 yi.iw.un_750 hr.sr.bs_444
+ 0x311b0408, 0x1b1f1a04, 0x27351b07, 0x040810a9, // fi.tr.az_443 tl.cy.tr_332 tr.zu.gd_432 be.uk.ru_544
+ // [0130]
+ 0x350e1b08, 0x1c352105, 0x16002919, 0x0000680f, // tr.is.zu_443 jw.zu.id_333 sl.hr.un_750 ig.un.un_600
+ 0x0a0411af, 0x00006815, 0x35004a05, 0x1e0208a4, // ro.ru.mk_655 ig.un.un_700 yo.zu.un_330 no.da.ms_433
+ 0x01321704, 0x0a071714, 0x170a0708, 0x55686407, // sr.bs.en_332 sr.bg.mk_666 bg.mk.sr_443 lg.ig.rw_432
+ 0x00000215, 0x1b25350b, 0x25006b09, 0x0e351b12, // da.un.un_700 zu.eu.tr_542 ceb.eu.un_440 tr.zu.is_654
+ // [0140]
+ 0x32001602, 0x2a186b0c, 0x00002b2d, 0x0706310c, // hr.bs.un_220 ceb.ga.mt_543 vi.un.un_A00 az.de.it_543
+ 0x00000b01, 0x270e1b60, 0x64006807, 0x20005304, // es.un.un_200 tr.is.gd_664 ig.lg.un_420 ht.sq.un_320
+ 0x2d123b07, 0x0d01030c, 0x1c000b02, 0x55131005, // so.hu.sk_432 nl.en.cs_543 es.id.un_220 lt.et.rw_333
+ 0x04001007, 0x281a130c, 0x1b0e2509, 0x08110407, // be.ru.un_420 et.tl.sw_543 eu.is.tr_444 fi.ro.no_432
+ // [0150]
+ 0x10000419, 0x13000b04, 0x04000a07, 0x0400081b, // fi.lt.un_750 es.et.un_320 mk.ru.un_420 uk.ru.un_770
+ 0x1800211a, 0x0c251b0c, 0x0c052a05, 0x03005507, // fa.ar.un_760 tr.eu.sv_543 mt.fr.sv_333 rw.nl.un_420
+ 0x0000241c, 0x0000242d, 0x35051b0c, 0x1200211a, // yi.un.un_800 yi.un.un_A00 tr.fr.zu_543 fa.ur.un_760
+ 0x09000d05, 0x1e001c07, 0x4a006822, 0x4a3f030c, // ne.hi.un_330 id.ms.un_420 ig.yo.un_870 nl.af.yo_543
+ // [0160]
+ 0x08001022, 0x0d172dee, 0x0000110a, 0x0000080a, // be.uk.un_870 sk.sr.cs_422 ro.un.un_500 no.un.un_500
+ 0x20283507, 0x172d29a4, 0x023f035a, 0x25351b12, // zu.sw.sq_432 sl.sk.sr_433 nl.af.da_553 tr.zu.eu_654
+ 0x0000200f, 0x311b3504, 0x19000b02, 0x2d000d0e, // sq.un.un_600 zu.tr.az_332 es.gl.un_220 cs.sk.un_550
+ 0x0a040804, 0x00000c0f, 0x3f003b02, 0x00001c15, // uk.ru.mk_332 sv.un.un_600 so.af.un_220 mr.un.un_700
+ // [0170]
+ 0x13040ea0, 0x07080408, 0x0d1b0e0c, 0x556b2807, // is.fi.et_322 ru.uk.bg_443 is.tr.cs_543 sw.ceb.rw_432
+ 0x0000180a, 0x06001809, 0x00005301, 0x12000e0d, // ar.un.un_500 ga.de.un_440 ht.un.un_200 is.hu.un_540
+ 0x04001711, 0x00001b24, 0x04000723, 0x31001b1b, // sr.ru.un_630 tr.un.un_900 bg.ru.un_880 tr.az.un_770
+ 0x0a0417a4, 0x10000a0c, 0x12002120, 0x17163202, // sr.ru.mk_433 pt.lt.un_530 fa.ur.un_850 bs.hr.sr_222
+ // [0180]
+ 0x12002113, 0x1300042b, 0x1b00311b, 0x4a6b6804, // fa.ur.un_650 fi.et.un_980 az.tr.un_770 ig.ceb.yo_332
+ 0x090a13ee, 0x04070a0c, 0x08000c13, 0x00000a0a, // et.pt.pl_422 mk.bg.ru_543 sv.no.un_650 pt.un.un_500
+ 0x08040a0c, 0x0c060e07, 0x0a190b04, 0x04081705, // mk.ru.uk_543 is.de.sv_432 es.gl.pt_332 sr.uk.ru_333
+ 0x12006408, 0x12000a09, 0x00005503, 0x00005506, // lg.hu.un_430 pt.hu.un_440 rw.un.un_300 rw.un.un_400
+ // [0190]
+ 0x04001008, 0x17000a0d, 0x0000060a, 0x0000112d, // be.ru.un_430 mk.sr.un_540 de.un.un_500 ro.un.un_A00
+ 0x112a010b, 0x17000a19, 0x0000100a, 0x00002a03, // en.mt.ro_542 mk.sr.un_750 be.un.un_500 mt.un.un_300
+ 0x0000040f, 0x18002113, 0x11002804, 0x0e080cad, // fi.un.un_600 fa.ar.un_650 sw.ro.un_320 sv.no.is_643
+ 0x2d000c04, 0x00006806, 0x64002805, 0x21001221, // sv.sk.un_320 ig.un.un_400 sw.lg.un_330 ur.fa.un_860
+ // [01a0]
+ 0x1100041a, 0x00001701, 0x0d000905, 0x0c08020e, // ru.ro.un_760 sr.un.un_200 hi.ne.un_330 da.no.sv_555
+ 0x040a1704, 0x311e1c04, 0x641e2807, 0x2100070d, // sr.mk.ru_332 id.ms.az_332 sw.ms.lg_432 it.jw.un_540
+ 0x0000180f, 0x00000f0f, 0x091c1312, 0x1c001308, // ar.un.un_600 lv.un.un_600 bh.mr.hi_654 bh.mr.un_430
+ 0x00002d15, 0x18001212, 0x1700110d, 0x1c0913ac, // sk.un.un_700 ur.ar.un_640 ro.sr.un_540 bh.hi.mr_632
+ // [01b0]
+ 0x00003f15, 0x0b230507, 0x0a0408ad, 0x05002305, // af.un.un_700 fr.ca.es_432 uk.ru.mk_643 ca.fr.un_330
+ 0x00003b0f, 0x08001009, 0x1c0913af, 0x033f13ad, // so.un.un_600 be.uk.un_440 bh.hi.mr_655 et.af.nl_643
+ 0x041005a4, 0x00002a15, 0x27001f05, 0x04001013, // fr.lt.fi_433 mt.un.un_700 cy.gd.un_330 be.ru.un_650
+ 0x0000680a, 0x122118ec, 0x07642a0e, 0x21001c08, // ig.un.un_500 ar.fa.ur_644 mt.lg.it_555 id.jw.un_430
+ // [01c0]
+ 0x00006e06, 0x07002a22, 0x04001322, 0x6400352a, // hmn.un.un_400 mt.it.un_870 et.fi.un_870 zu.lg.un_970
+ 0x13090d14, 0x00006e1c, 0x0000010f, 0x13003b12, // ne.hi.bh_666 hmn.un.un_800 en.un.un_600 so.et.un_640
+ 0x321629a9, 0x352018a4, 0x19000b07, 0x090d1caf, // sl.hr.bs_544 ga.sq.zu_433 es.gl.un_420 mr.ne.hi_655
+ 0x31001b12, 0x00002a24, 0x09001313, 0x040a0705, // tr.az.un_640 mt.un.un_900 bh.hi.un_650 bg.mk.ru_333
+ // [01d0]
+ 0x00000d0f, 0x1f3f27a4, 0x13091ca9, 0x55006422, // cs.un.un_600 gd.af.cy_433 mr.hi.bh_544 lg.rw.un_870
+ 0x35005513, 0x122b200c, 0x00002d0a, 0x10000835, // rw.zu.un_650 sq.vi.hu_543 sk.un.un_500 uk.be.un_A90
+ 0x03003f2b, 0x04100812, 0x1e1c21ec, 0x2400012b, // af.nl.un_980 uk.be.ru_654 jw.id.ms_644 iw.yi.un_980
+ 0x2a000723, 0x321617a9, 0x3b006e07, 0x0000190a, // it.mt.un_880 sr.hr.bs_544 hmn.so.un_420 gl.un.un_500
+ // [01e0]
+ 0x00006b03, 0x3f0325a7, 0x0000101c, 0x0e0608a4, // ceb.un.un_300 eu.nl.af_532 lt.un.un_800 no.de.is_433
+ 0x00002715, 0x00001003, 0x09001f12, 0x04002314, // gd.un.un_700 be.un.un_300 cy.pl.un_640 ca.fi.un_660
+ 0x00000706, 0x00006e24, 0x040613a0, 0x3f080208, // it.un.un_400 hmn.un.un_900 et.de.fi_322 da.no.af_443
+ 0x0000060f, 0x0000030a, 0x0f645513, 0x200304ee, // de.un.un_600 nl.un.un_500 rw.lg.lv_665 fi.nl.sq_422
+ // [01f0]
+ 0x1304200c, 0x1e211c12, 0x643f0307, 0x07000a1b, // sq.fi.et_543 id.jw.ms_654 nl.af.lg_432 mk.bg.un_770
+ 0x17001602, 0x201904ee, 0x0000350f, 0x13002019, // hr.sr.un_220 fi.gl.sq_422 zu.un.un_600 sq.et.un_750
+ 0x35002807, 0x2100120d, 0x00000e0a, 0x1e211c11, // sw.zu.un_420 ur.fa.un_540 is.un.un_500 id.jw.ms_653
+ 0x55642509, 0x530523a7, 0x1c000914, 0x29002a19, // eu.lg.rw_444 ca.fr.ht_532 hi.mr.un_660 mt.sl.un_750
+ // [0200]
+ 0x05022d0c, 0x0000311c, 0x010c060c, 0x1f1218ee, // sk.da.fr_543 az.un.un_800 de.sv.en_543 ga.hu.cy_422
+ 0x0600210c, 0x00005324, 0x0b133ba4, 0x0b0a1905, // jw.de.un_530 ht.un.un_900 so.et.es_433 gl.pt.es_333
+ 0x0723190c, 0x2a1716eb, 0x3f190b09, 0x351a27ad, // gl.ca.it_543 hr.sr.mt_662 es.gl.af_444 gd.tl.zu_643
+ 0x6400551a, 0x00001a0f, 0x0d0913a0, 0x0000201c, // rw.lg.un_760 tl.un.un_600 bh.hi.ne_322 sq.un.un_800
+ // [0210]
+ 0x190a08a4, 0x07002a13, 0x27003508, 0x0705010c, // no.pt.gl_433 mt.it.un_650 zu.gd.un_430 en.fr.it_543
+ 0x00001b15, 0x02000e1a, 0x00000b03, 0x080220ee, // tr.un.un_700 is.da.un_760 es.un.un_300 sq.da.no_422
+ 0x1105010b, 0x1800270d, 0x2d0a2302, 0x13071fa0, // en.fr.ro_542 gd.ga.un_540 ca.pt.sk_222 cy.it.et_322
+ 0x1a0435ac, 0x1a20530c, 0x00000806, 0x12006b19, // zu.fi.tl_632 ht.sq.tl_543 uk.un.un_400 ceb.hu.un_750
+ // [0220]
+ 0x0100051b, 0x08001014, 0x55110507, 0x033f0608, // fr.en.un_770 be.uk.un_660 fr.ro.rw_432 de.af.nl_443
+ 0x18271faf, 0x040708a9, 0x0a00171b, 0x06002009, // cy.gd.ga_655 uk.bg.ru_544 sr.mk.un_770 sq.de.un_440
+ 0x0b0a19a9, 0x0000270a, 0x130704a7, 0x00002a0f, // gl.pt.es_544 gd.un.un_500 fi.it.et_532 mt.un.un_600
+ 0x00006e0a, 0x0b0a2aee, 0x0000281c, 0x1a1c21a4, // hmn.un.un_500 mt.pt.es_422 sw.un.un_800 jw.id.tl_433
+ // [0230]
+ 0x213f1c04, 0x072004a6, 0x0b0723a4, 0x180e27ad, // id.af.jw_332 fi.sq.it_521 ca.it.es_433 gd.is.ga_643
+ 0x00003515, 0x072a0da4, 0x180b1907, 0x070a17ad, // zu.un.un_700 cs.mt.it_433 gl.es.ga_432 sr.mk.bg_643
+ 0x0c642805, 0x1c001e07, 0x170411ec, 0x09005508, // sw.lg.sv_333 ms.id.un_420 ro.ru.sr_644 rw.pl.un_430
+ 0x1300091b, 0x28005522, 0x0000230a, 0x17080414, // hi.bh.un_770 rw.sw.un_870 ca.un.un_500 ru.uk.sr_666
+ // [0240]
+ 0x1801270c, 0x06005319, 0x0000530a, 0x1000020d, // gd.en.ga_543 ht.de.un_750 ht.un.un_500 da.lt.un_540
+ 0x1b316b11, 0x28005314, 0x53211c55, 0x0000531c, // ceb.az.tr_653 ht.sw.un_660 id.jw.ht_442 ht.un.un_800
+ 0x0c350807, 0x0700110c, 0x29002d13, 0x17000a23, // no.zu.sv_432 ro.bg.un_530 sk.sl.un_650 mk.sr.un_880
+ 0x090d1c12, 0x53050111, 0x2d0d1755, 0x17071104, // mr.ne.hi_654 en.fr.ht_653 sr.cs.sk_442 ro.bg.sr_332
+ // [0250]
+ 0x21121812, 0x0a00171a, 0x01000604, 0x1632175a, // ar.ur.fa_654 sr.mk.un_760 de.en.un_320 sr.bs.hr_553
+ 0x080410a0, 0x1b2d0d08, 0x04070a05, 0x02000813, // be.ru.uk_322 cs.sk.tr_443 mk.bg.ru_333 no.da.un_650
+ 0x08041705, 0x292d0d0d, 0x09001c12, 0x321617a4, // sr.ru.uk_333 cs.sk.sl_554 mr.hi.un_640 sr.hr.bs_433
+ 0x0c000202, 0x00000424, 0x1c0d0912, 0x24000112, // da.sv.un_220 ru.un.un_900 hi.ne.mr_654 iw.yi.un_640
+ // [0260]
+ 0x4a1e1c13, 0x06002305, 0x13001b09, 0x1b002502, // id.ms.yo_665 ca.de.un_330 tr.et.un_440 eu.tr.un_220
+ 0x00000b0f, 0x1f2d0d60, 0x132911ee, 0x00003203, // bn.un.un_600 cs.sk.cy_664 ro.sl.et_422 bs.un.un_300
+ 0x20211b12, 0x1b132512, 0x21001214, 0x00004a03, // tr.jw.sq_654 eu.et.tr_654 ur.fa.un_660 yo.un.un_300
+ 0x00003201, 0x521203ac, 0x1b004a19, 0x2d000d14, // bs.un.un_200 nl.hu.ha_632 yo.tr.un_750 cs.sk.un_660
+ // [0270]
+ 0x351310a4, 0x0e35640c, 0x0b0a19ee, 0x0308020c, // lt.et.zu_433 lg.zu.is_543 gl.pt.es_422 da.no.nl_543
+ 0x521e1c12, 0x11236808, 0x3500021a, 0x18211212, // id.ms.ha_654 ig.ca.ro_443 da.zu.un_760 ur.fa.ar_654
+ 0x07001120, 0x0000170a, 0x290f03a0, 0x0d003511, // ro.bg.un_850 sr.un.un_500 nl.lv.sl_322 zu.cs.un_630
+ 0x04000302, 0x00002b06, 0x04001019, 0x2d000d1a, // nl.fi.un_220 vi.un.un_400 be.ru.un_750 cs.sk.un_760
+ // [0280]
+ 0x04004a09, 0x030964a0, 0x00006e0f, 0x2b050311, // yo.fi.un_440 lg.pl.nl_322 hmn.un.un_600 nl.fr.vi_653
+ 0x04001708, 0x1b001213, 0x00001f0a, 0x3f05030c, // sr.ru.un_430 hu.tr.un_650 cy.un.un_500 nl.fr.af_543
+ 0x0000212d, 0x32161709, 0x211e1caf, 0x0a0717a4, // jw.un.un_A00 sr.hr.bs_444 id.ms.jw_655 sr.bg.mk_433
+ 0x20006b04, 0x64001e04, 0x17000704, 0x102d0d0e, // ceb.sq.un_320 ms.lg.un_320 bg.sr.un_320 cs.sk.lt_555
+ // [0290]
+ 0x1800120d, 0x0f310d0b, 0x17000c08, 0x08040aa4, // ur.ar.un_540 cs.az.lv_542 sv.sr.un_430 mk.ru.uk_433
+ 0x0a171105, 0x01001c04, 0x290d3202, 0x3f003511, // ro.sr.mk_333 id.en.un_320 bs.cs.sl_222 zu.af.un_630
+ 0x12002d0d, 0x00000301, 0x0000310a, 0x00003524, // sk.hu.un_540 nl.un.un_200 az.un.un_500 zu.un.un_900
+ 0x3f0305ec, 0x1c090607, 0x080213a4, 0x120b2d0c, // fr.nl.af_644 de.pl.id_432 et.da.no_433 sk.es.hu_543
+ // [02a0]
+ 0x0000551c, 0x3f3b04a4, 0x20230aee, 0x00003b1c, // rw.un.un_800 fi.so.af_433 pt.ca.sq_422 so.un.un_800
+ 0x040a07a4, 0x17163209, 0x00002a0a, 0x04001a19, // bg.mk.ru_433 bs.hr.sr_444 mt.un.un_500 tl.fi.un_750
+ 0x0d1c1308, 0x07041007, 0x6b0403ee, 0x13003b1b, // bh.mr.ne_443 be.ru.bg_432 nl.fi.ceb_422 so.et.un_770
+ 0x070a17a0, 0x1e1c09a4, 0x6b3f04ad, 0x00000615, // sr.mk.bg_322 pl.id.ms_433 fi.af.ceb_643 de.un.un_700
+ // [02b0]
+ 0x042b13ee, 0x11001c04, 0x2d29520c, 0x13033ba4, // et.vi.fi_422 id.ro.un_320 ha.sl.sk_543 so.nl.et_433
+ 0x130d0960, 0x3b3f0312, 0x161e1c04, 0x12001905, // hi.ne.bh_664 nl.af.so_654 id.ms.hr_332 gl.hu.un_330
+ 0x00001a1c, 0x016b3bad, 0x19000b14, 0x31001b22, // tl.un.un_800 so.ceb.en_643 es.gl.un_660 tr.az.un_870
+ 0x11070414, 0x64133fa4, 0x00000515, 0x0000312d, // ru.bg.ro_666 af.et.lg_433 fr.un.un_700 az.un.un_A00
+ // [02c0]
+ 0x0a0717ee, 0x00003b24, 0x03000522, 0x2d0d3baf, // sr.bg.mk_422 so.un.un_900 fr.nl.un_870 so.cs.sk_655
+ 0x00001a01, 0x55354a04, 0x1e4a1c60, 0x0800041a, // tl.un.un_200 yo.zu.rw_332 id.yo.ms_664 ru.uk.un_760
+ 0x3f684a12, 0x163f0355, 0x2800272a, 0x08041005, // yo.ig.af_654 nl.af.hr_442 gd.sw.un_970 be.ru.uk_333
+ 0x2b0535ee, 0x1f001822, 0x3200160e, 0x036e640e, // zu.fr.vi_422 ga.cy.un_870 hr.bs.un_550 lg.hmn.nl_555
+ // [02d0]
+ 0x00001b0f, 0x23000304, 0x643b1ca4, 0x0a1707a4, // tr.un.un_600 nl.ca.un_320 id.so.lg_433 bg.sr.mk_433
+ 0x0000290a, 0x00001a03, 0x00005303, 0x0f001007, // sl.un.un_500 tl.un.un_300 ht.un.un_300 lt.lv.un_420
+ 0x3f000321, 0x1e1c31af, 0x11040813, 0x190b0405, // nl.af.un_860 az.id.ms_655 uk.ru.ro_665 fi.es.gl_333
+ 0x04070a11, 0x683f0408, 0x04001713, 0x00000a03, // mk.bg.ru_653 fi.af.ig_443 sr.ru.un_650 mk.un.un_300
+ // [02e0]
+ 0x043b1aac, 0x522b3b05, 0x32080c0e, 0x12002119, // tl.so.fi_632 so.vi.ha_333 sv.no.bs_555 fa.ur.un_750
+ 0x043b6b07, 0x1a000702, 0x01002304, 0x1a006b05, // ceb.so.fi_432 it.tl.un_220 ca.en.un_320 ceb.tl.un_330
+ 0x6b001a09, 0x64002102, 0x00003137, 0x17000819, // tl.ceb.un_440 jw.lg.un_220 az.un.un_B00 uk.sr.un_750
+ 0x0000641c, 0x00003b01, 0x0000170f, 0x09001c09, // lg.un.un_800 so.un.un_200 sr.un.un_600 mr.hi.un_440
+ // [02f0]
+ 0x100f55ec, 0x0000130f, 0x17212a07, 0x3b0313a4, // rw.lv.lt_644 et.un.un_600 mt.jw.sr_432 et.nl.so_433
+ 0x0000552d, 0x1e531a0b, 0x2400011a, 0x00001c03, // rw.un.un_A00 tl.ht.ms_542 iw.yi.un_760 id.un.un_300
+ 0x0e001009, 0x00002501, 0x311b1ea0, 0x17001002, // lt.is.un_440 eu.un.un_200 ms.tr.az_322 be.sr.un_220
+ 0x53033fac, 0x3200161b, 0x0c080255, 0x040711ad, // af.nl.ht_632 hr.bs.un_770 da.no.sv_442 ro.bg.ru_643
+ // [0300]
+ 0x0000520a, 0x32171604, 0x2a200807, 0x213b2813, // ha.un.un_500 hr.sr.bs_332 no.sq.mt_432 sw.so.jw_665
+ 0x16001e04, 0x0d1c090c, 0x64002a05, 0x00001824, // ms.hr.un_320 hi.mr.ne_543 mt.lg.un_330 ar.un.un_900
+ 0x03121607, 0x55286408, 0x112507a4, 0x32001623, // hr.hu.nl_432 lg.sw.rw_443 it.eu.ro_433 hr.bs.un_880
+ 0x12032011, 0x04101704, 0x25203ba4, 0x211812ec, // sq.nl.hu_653 sr.be.ru_332 so.sq.eu_433 ur.ar.fa_644
+ // [0310]
+ 0x13000819, 0x171107a0, 0x09033fad, 0x20282507, // no.et.un_750 it.ro.sr_322 af.nl.pl_643 eu.sw.sq_432
+ 0x0000550f, 0x20001b12, 0x64003521, 0x19000a12, // rw.un.un_600 tr.sq.un_640 zu.lg.un_860 pt.gl.un_640
+ 0x040a0760, 0x08041711, 0x0a171002, 0x041310a7, // bg.mk.ru_664 sr.ru.uk_653 be.sr.mk_222 lt.et.fi_532
+ 0x060a070d, 0x00001f1c, 0x3f001c04, 0x070a17af, // it.pt.de_554 cy.un.un_800 id.af.un_320 sr.mk.bg_655
+ // [0320]
+ 0x0700171b, 0x13000919, 0x02005207, 0x0a000714, // sr.bg.un_770 hi.bh.un_750 ha.da.un_420 bg.mk.un_660
+ 0x00004a2d, 0x290d53af, 0x00005201, 0x09001304, // yo.un.un_A00 ht.cs.sl_655 ha.un.un_200 bh.hi.un_320
+ 0x070a1108, 0x07041004, 0x21001307, 0x0c00021a, // ro.mk.bg_443 be.ru.bg_332 et.jw.un_420 da.sv.un_760
+ 0x251c1a04, 0x105507a4, 0x13211c04, 0x18002714, // tl.id.eu_332 it.rw.lt_433 id.jw.et_332 gd.ga.un_660
+ // [0330]
+ 0x11000712, 0x2d001914, 0x27001833, 0x19000b05, // it.ro.un_640 gl.sk.un_660 ga.gd.un_A70 es.gl.un_330
+ 0x10000512, 0x203b21ec, 0x0c041304, 0x21005219, // fr.lt.un_640 jw.so.sq_644 et.fi.sv_332 ha.jw.un_750
+ 0x00000c06, 0x2a5510ec, 0x1b000c02, 0x2d001011, // sv.un.un_400 lt.rw.mt_644 sv.tr.un_220 lt.sk.un_630
+ 0x0d16290c, 0x4a0a07a7, 0x00006b15, 0x23006807, // sl.hr.cs_543 it.pt.yo_532 ceb.un.un_700 ig.ca.un_420
+ // [0340]
+ 0x121729a4, 0x00000f24, 0x166e320c, 0x3f000212, // sl.sr.hu_433 lv.un.un_900 bs.hmn.hr_543 da.af.un_640
+ 0x03004a07, 0x1800350e, 0x2b280608, 0x0c121fa9, // yo.nl.un_420 zu.ga.un_550 de.sw.vi_443 cy.hu.sv_544
+ 0x0c000836, 0x1f523b0b, 0x00000624, 0x1025070c, // no.sv.un_AA0 so.ha.cy_542 de.un.un_900 it.eu.lt_543
+ 0x2b3f0308, 0x05131eee, 0x28080c0b, 0x20170307, // nl.af.vi_443 ms.et.fr_422 sv.no.sw_542 nl.sr.sq_432
+ // [0350]
+ 0x0b000702, 0x2b230e08, 0x0000061c, 0x00002915, // it.es.un_220 is.ca.vi_443 de.un.un_800 sl.un.un_700
+ 0x0900291b, 0x00001f24, 0x1f182707, 0x0e000807, // sl.pl.un_770 cy.un.un_900 gd.ga.cy_432 no.is.un_420
+ 0x6e6b6804, 0x1e001c05, 0x00001c1c, 0x64521f14, // ig.ceb.hmn_332 id.ms.un_330 mr.un.un_800 cy.ha.lg_666
+ 0x09002807, 0x32001723, 0x01001e08, 0x0000081c, // sw.pl.un_420 sr.bs.un_880 ms.en.un_430 uk.un.un_800
+ // [0360]
+ 0x29000f21, 0x1c000919, 0x00002701, 0x23192504, // lv.sl.un_860 hi.mr.un_750 gd.un.un_200 eu.gl.ca_332
+ 0x0000041c, 0x00006415, 0x0000020a, 0x0600010d, // fi.un.un_800 lg.un.un_700 da.un.un_500 en.de.un_540
+ 0x315535a7, 0x32001607, 0x0000350a, 0x24000120, // zu.rw.az_532 hr.bs.un_420 zu.un.un_500 iw.yi.un_850
+ 0x08023fee, 0x091c0dec, 0x1c001e04, 0x1600290d, // af.da.no_422 ne.mr.hi_644 ms.id.un_320 sl.hr.un_540
+ // [0370]
+ 0x0c080213, 0x0a1704ec, 0x00002a06, 0x05001909, // da.no.sv_665 ru.sr.mk_644 mt.un.un_400 gl.fr.un_440
+ 0x0411170c, 0x0e120d07, 0x12000e09, 0x321208ac, // sr.ro.ru_543 cs.hu.is_432 is.hu.un_440 no.hu.bs_632
+ 0x021808a4, 0x0000252d, 0x03003f14, 0x1300041b, // no.ga.da_433 eu.un.un_A00 af.nl.un_660 fi.et.un_770
+ 0x21525507, 0x122d0d14, 0x050601af, 0x3f002304, // rw.ha.jw_432 cs.sk.hu_666 en.de.fr_655 ca.af.un_320
+ // [0380]
+ 0x1b00310e, 0x07000a23, 0x2a1f1a0c, 0x0b132355, // az.tr.un_550 mk.bg.un_880 tl.cy.mt_543 ca.et.es_442
+ 0x23000619, 0x21005318, 0x12000413, 0x27002104, // de.ca.un_750 ht.jw.un_740 fi.hu.un_650 jw.gd.un_320
+ 0x00000b06, 0x08170a08, 0x00006403, 0x1707040d, // es.un.un_400 mk.sr.uk_443 lg.un.un_300 ru.bg.sr_554
+ 0x0a001723, 0x070a1714, 0x3f00031a, 0x6b006805, // sr.mk.un_880 sr.mk.bg_666 nl.af.un_760 ig.ceb.un_330
+ // [0390]
+ 0x321617a0, 0x04170a07, 0x02003204, 0x1e001c23, // sr.hr.bs_322 mk.sr.ru_432 bs.da.un_320 id.ms.un_880
+ 0x1e182707, 0x170229a0, 0x07040a12, 0x1e1c21af, // gd.ga.ms_432 sl.da.sr_322 mk.ru.bg_654 jw.id.ms_655
+ 0x11000612, 0x07130407, 0x06350eec, 0x2a0c0207, // de.ro.un_640 fi.et.it_432 is.zu.de_644 da.sv.mt_432
+ 0x00000c01, 0x04071712, 0x100f5208, 0x2700060e, // sv.un.un_200 sr.bg.ru_654 ha.lv.lt_443 de.gd.un_550
+ // [03a0]
+ 0x3500041b, 0x0e2910ad, 0x12001907, 0x64005213, // fi.zu.un_770 lt.sl.is_643 gl.hu.un_420 ha.lg.un_650
+ 0x0d130960, 0x5200281a, 0x08020ca4, 0x00000a0f, // hi.bh.ne_664 sw.ha.un_760 sv.da.no_433 pt.un.un_600
+ 0x00001906, 0x10000818, 0x190b0a5a, 0x0c000f08, // gl.un.un_400 uk.be.un_740 pt.es.gl_553 lv.sv.un_430
+ 0x00001e0a, 0x31086b02, 0x28005219, 0x08022102, // ms.un.un_500 ceb.no.az_222 ha.sw.un_750 jw.da.no_222
+ // [03b0]
+ 0x122d0d13, 0x1700160e, 0x3f000314, 0x2d0d18a4, // cs.sk.hu_665 hr.sr.un_550 nl.af.un_660 ga.cs.sk_433
+ 0x32171bee, 0x53643b11, 0x1c090d0e, 0x09001308, // tr.sr.bs_422 so.lg.ht_653 ne.hi.mr_555 bh.hi.un_430
+ 0x1b114a02, 0x17002307, 0x0800020e, 0x00000d24, // yo.ro.tr_222 ca.sr.un_420 da.no.un_550 cs.un.un_900
+ 0x1700071a, 0x040a0713, 0x3f0902ec, 0x31001b1a, // bg.sr.un_760 bg.mk.ru_665 da.pl.af_644 tr.az.un_760
+ // [03c0]
+ 0x214a1c0c, 0x2d000d12, 0x3f0c0855, 0x00005224, // id.yo.jw_543 cs.sk.un_640 no.sv.af_442 ha.un.un_900
+ 0x01236b04, 0x071708a4, 0x2d0e0c04, 0x0408100e, // ceb.ca.en_332 uk.sr.bg_433 sv.is.sk_332 be.uk.ru_555
+ 0x01002412, 0x05001902, 0x00001306, 0x2d000d21, // yi.iw.un_640 gl.fr.un_220 bh.un.un_400 cs.sk.un_860
+ 0x3f082daf, 0x19000a05, 0x0f0b10ac, 0x0d000e19, // sk.no.af_655 pt.gl.un_330 lt.es.lv_632 is.cs.un_750
+ // [03d0]
+ 0x050911a7, 0x351a55ac, 0x1e211c08, 0x0000051c, // ro.pl.fr_532 rw.tl.zu_632 id.jw.ms_443 fr.un.un_800
+ 0x211735a4, 0x17000c07, 0x09200aad, 0x170413ec, // zu.sr.jw_433 sv.sr.un_420 pt.sq.pl_643 et.fi.sr_644
+ 0x0f003f04, 0x080c02ad, 0x3b010604, 0x0900130c, // af.lv.un_320 da.sv.no_643 de.en.so_332 bh.hi.un_530
+ 0x0c08060c, 0x0d091caf, 0x0000240f, 0x35002505, // de.no.sv_543 mr.hi.ne_655 yi.un.un_600 eu.zu.un_330
+ // [03e0]
+ 0x280c21a0, 0x28001807, 0x08020c04, 0x06001313, // jw.sv.sw_322 ga.sw.un_420 sv.da.no_332 et.de.un_650
+ 0x00002801, 0x0e0c08ad, 0x2100050d, 0x00002003, // sw.un.un_200 no.sv.is_643 fr.jw.un_540 sq.un.un_300
+ 0x0d00090c, 0x03002108, 0x0a285507, 0x0d000909, // hi.ne.un_530 jw.nl.un_430 rw.sw.pt_432 hi.ne.un_440
+ 0x0000530f, 0x0000200a, 0x01641211, 0x0b1101a4, // ht.un.un_600 sq.un.un_500 hu.lg.en_653 en.ro.es_433
+ // [03f0]
+ 0x203229ad, 0x2d000d18, 0x0d002d12, 0x53070aa4, // sl.bs.sq_643 cs.sk.un_740 sk.cs.un_640 pt.it.ht_433
+ 0x64003f1a, 0x082d1307, 0x354a280c, 0x0000640a, // af.lg.un_760 et.sk.no_432 sw.yo.zu_543 lg.un.un_500
+ 0x00002124, 0x11070209, 0x531a1ca0, 0x2d000d19, // fa.un.un_900 da.it.ro_444 id.tl.ht_322 cs.sk.un_750
+ 0x0c0e3f0c, 0x23000d04, 0x1b645508, 0x13000422, // af.is.sv_543 cs.ca.un_320 rw.lg.tr_443 fi.et.un_870
+
+ // [0400]
+ 0x2d000d20, 0x1e005309, 0x0900060d, 0x13000935, // cs.sk.un_850 ht.ms.un_440 de.pl.un_540 hi.bh.un_A90
+ 0x1f001a02, 0x0e2d3f0c, 0x0400131a, 0x53080209, // tl.cy.un_220 af.sk.is_543 et.fi.un_760 da.no.ht_444
+ 0x080e0ca9, 0x351f1860, 0x216b1aa9, 0x25131209, // sv.is.no_544 ga.cy.zu_664 tl.ceb.jw_544 hu.et.eu_444
+ 0x1c3521ee, 0x0a0711ee, 0x19000b1b, 0x080c6e04, // jw.zu.id_422 ro.bg.mk_422 es.gl.un_770 hmn.sv.no_332
+ // [0410]
+ 0x2a00210c, 0x2d0e0da4, 0x08003f08, 0x6b3b520c, // jw.mt.un_530 cs.is.sk_433 af.no.un_430 ha.so.ceb_543
+ 0x12061e04, 0x20115508, 0x12002122, 0x03063fa0, // ms.de.hu_332 rw.ro.sq_443 fa.ur.un_870 af.de.nl_322
+ 0x286435af, 0x29000d13, 0x350713a7, 0x1a115508, // zu.lg.sw_655 cs.sl.un_650 et.it.zu_532 rw.ro.tl_443
+ 0x00000a06, 0x0d2718a4, 0x01230708, 0x0f171602, // pt.un.un_400 ga.gd.cs_433 it.ca.en_443 hr.sr.lv_222
+ // [0420]
+ 0x00006b0a, 0x080c3ba7, 0x3f030ca4, 0x2000550d, // ceb.un.un_500 so.sv.no_532 sv.nl.af_433 rw.sq.un_540
+ 0x09001312, 0x07040804, 0x0e20550c, 0x2700180e, // bh.hi.un_640 uk.ru.bg_332 rw.sq.is_543 ga.gd.un_550
+ 0x00001f0f, 0x07001212, 0x211a280c, 0x17001605, // cy.un.un_600 hu.it.un_640 sw.tl.jw_543 hr.sr.un_330
+ 0x04212855, 0x280e5508, 0x2d0d06a4, 0x0817040c, // sw.jw.fi_442 rw.is.sw_443 de.cs.sk_433 ru.sr.uk_543
+ // [0430]
+ 0x64211ea0, 0x1b521ea0, 0x2a131ea0, 0x061e1c05, // ms.jw.lg_322 ms.ha.tr_322 ms.et.mt_322 id.ms.de_333
+ 0x13000121, 0x28005513, 0x100804a4, 0x522a5508, // en.et.un_860 rw.sw.un_650 ru.uk.be_433 rw.mt.ha_443
+ 0x3b003114, 0x04551ea0, 0x10550fa4, 0x1a006b1b, // az.so.un_660 ms.rw.fi_322 lv.rw.lt_433 ceb.tl.un_770
+ 0x1a000804, 0x0400131b, 0x27010ba0, 0x186b08a0, // no.tl.un_320 et.fi.un_770 es.en.gd_322 no.ceb.ga_322
+ // [0440]
+ 0x02070860, 0x0d09130c, 0x29000604, 0x6b551ea4, // no.it.da_664 bh.hi.ne_543 de.sl.un_320 ms.rw.ceb_433
+ 0x1b3f0802, 0x21001f12, 0x1e046402, 0x686b1a08, // no.af.tr_222 cy.jw.un_640 lg.fi.ms_222 tl.ceb.ig_443
+ 0x08000c1b, 0x250e0707, 0x161029a4, 0x1a00550c, // sv.no.un_770 it.is.eu_432 sl.lt.hr_433 rw.tl.un_530
+ 0x35313bad, 0x07190e02, 0x06033fa4, 0x07081004, // so.az.zu_643 is.gl.it_222 af.nl.de_433 be.uk.bg_332
+ // [0450]
+ 0x322d0d04, 0x0100241a, 0x1f033ba9, 0x2d0c1a04, // cs.sk.bs_332 yi.iw.un_760 so.nl.cy_544 tl.sv.sk_332
+ 0x01003f11, 0x211c1eec, 0x080206a4, 0x31001b19, // af.en.un_630 ms.id.jw_644 de.da.no_433 tr.az.un_750
+ 0x211e1c0d, 0x31001b23, 0x18002121, 0x21181212, // id.ms.jw_554 tr.az.un_880 fa.ar.un_860 ur.ar.fa_654
+ 0x1e001c2c, 0x04170808, 0x040a1707, 0x1c211e12, // id.ms.un_990 uk.sr.ru_443 sr.mk.ru_432 ms.jw.id_654
+ // [0460]
+ 0x113155a4, 0x6b001a07, 0x6b3f1aad, 0x041707ec, // rw.az.ro_433 tl.ceb.un_420 tl.af.ceb_643 bg.sr.ru_644
+ 0x210f100c, 0x0e1104a4, 0x1c0d0960, 0x0a1008a4, // lt.lv.jw_543 fi.ro.is_433 hi.ne.mr_664 uk.be.mk_433
+ 0x17040a07, 0x04170a04, 0x11170408, 0x213b1e04, // mk.ru.sr_432 mk.sr.ru_332 ru.sr.ro_443 ms.so.jw_332
+ 0x0c0b2909, 0x00004a0a, 0x04070a04, 0x1b0c120c, // sl.es.sv_444 yo.un.un_500 mk.bg.ru_332 hu.sv.tr_543
+ // [0470]
+ 0x04081004, 0x100a070c, 0x3f0f2312, 0x23001902, // be.uk.ru_332 bg.mk.be_543 ca.lv.af_654 gl.ca.un_220
+ 0x0e00080e, 0x6b041aa4, 0x19120a0b, 0x1e162902, // no.is.un_550 tl.fi.ceb_433 pt.hu.gl_542 sl.hr.ms_222
+ 0x1e003504, 0x0100231a, 0x0d002a19, 0x1c000412, // zu.ms.un_320 ca.en.un_760 mt.cs.un_750 fi.id.un_640
+ 0x28002521, 0x0d001302, 0x11006b13, 0x0000070a, // eu.sw.un_860 bh.ne.un_220 ceb.ro.un_650 it.un.un_500
+ // [0480]
+ 0x3f002518, 0x211812a4, 0x322a16ec, 0x1e001c1a, // eu.af.un_740 ur.ar.fa_433 hr.mt.bs_644 id.ms.un_760
+ 0x17080a05, 0x536b250c, 0x0f21130c, 0x2a120804, // mk.uk.sr_333 eu.ceb.ht_543 et.jw.lv_543 no.hu.mt_332
+ 0x090711ee, 0x311b2d07, 0x3f000619, 0x04112108, // ro.it.pl_422 sk.tr.az_432 de.af.un_750 jw.ro.fi_443
+ 0x1f001c07, 0x06000c04, 0x351f2107, 0x1e001c2b, // id.cy.un_420 sv.de.un_320 jw.cy.zu_432 id.ms.un_980
+ // [0490]
+ 0x0c0123af, 0x2d0d090c, 0x21000b08, 0x00001106, // ca.en.sv_655 pl.cs.sk_543 es.jw.un_430 ro.un.un_400
+ 0x01002320, 0x13002920, 0x24000118, 0x01002312, // ca.en.un_850 sl.et.un_850 iw.yi.un_740 ca.en.un_640
+ 0x28002b13, 0x17321660, 0x1329110c, 0x083f6407, // vi.sw.un_650 hr.bs.sr_664 ro.sl.et_543 lg.af.no_432
+ 0x53002102, 0x3200210d, 0x550652a4, 0x6b001a22, // jw.ht.un_220 jw.bs.un_540 ha.de.rw_433 tl.ceb.un_870
+ // [04a0]
+ 0x0e6b07ad, 0x19000b20, 0x04001314, 0x21001813, // it.ceb.is_643 es.gl.un_850 et.fi.un_660 ar.fa.un_650
+ 0x0e132aa4, 0x210e0811, 0x29001212, 0x00006b1c, // mt.et.is_433 no.is.jw_653 hu.sl.un_640 ceb.un.un_800
+ 0x16321708, 0x17000408, 0x521c1e0b, 0x0a2d0d11, // sr.bs.hr_443 fi.sr.un_430 ms.id.ha_542 cs.sk.pt_653
+ 0x2700060d, 0x0a0708a4, 0x0000171c, 0x0e640255, // de.gd.un_540 uk.bg.mk_433 sr.un.un_800 da.lg.is_442
+ // [04b0]
+ 0x0a07110e, 0x08002019, 0x1a033fa4, 0x080c060d, // ro.bg.mk_555 sq.no.un_750 af.nl.tl_433 de.sv.no_554
+ 0x0e3b3f04, 0x00000c2d, 0x170f290c, 0x00002a1c, // af.so.is_332 sv.un.un_A00 sl.lv.sr_543 mt.un.un_800
+ 0x00000c1c, 0x00005215, 0x0f0413ad, 0x35005205, // sv.un.un_800 ha.un.un_700 et.fi.lv_643 ha.zu.un_330
+ 0x11182712, 0x0a001722, 0x00004a15, 0x1c211e0c, // gd.ga.ro_654 sr.mk.un_870 yo.un.un_700 ms.jw.id_543
+ // [04c0]
+ 0x12002121, 0x182112ad, 0x060c02a4, 0x0000190f, // fa.ur.un_860 ur.fa.ar_643 da.sv.de_433 gl.un.un_600
+ 0x17002908, 0x1c090dec, 0x08021fa9, 0x01080208, // sl.sr.un_430 ne.hi.mr_644 cy.da.no_544 da.no.en_443
+ 0x2325190b, 0x16190b0d, 0x131e1c14, 0x3b00071b, // gl.eu.ca_542 es.gl.hr_554 id.ms.et_666 it.so.un_770
+ 0x03001904, 0x28006808, 0x010553a0, 0x040810ad, // gl.nl.un_320 ig.sw.un_430 ht.fr.en_322 be.uk.ru_643
+ // [04d0]
+ 0x0e08025a, 0x080c1fee, 0x6b001a2b, 0x03053f07, // da.no.is_553 cy.sv.no_422 tl.ceb.un_980 af.fr.nl_432
+ 0x071c640c, 0x110d0f12, 0x02000819, 0x281801ee, // lg.id.it_543 lv.cs.ro_654 no.da.un_750 en.ga.sw_422
+ 0x2a002922, 0x1000041b, 0x03042509, 0x040708a4, // sl.mt.un_870 ru.be.un_770 eu.fi.nl_444 uk.bg.ru_433
+ 0x0c080204, 0x0804170c, 0x24000119, 0x00003f24, // da.no.sv_332 sr.ru.uk_543 iw.yi.un_750 af.un.un_900
+ // [04e0]
+ 0x2500011a, 0x08020ca9, 0x1c000312, 0x18211211, // en.eu.un_760 sv.da.no_544 nl.id.un_640 ur.fa.ar_653
+ 0x29000c0b, 0x1c001302, 0x1b001c04, 0x0600011a, // sv.sl.un_520 bh.mr.un_220 id.tr.un_320 en.de.un_760
+ 0x2d000d09, 0x2d103207, 0x202a030c, 0x06033faf, // cs.sk.un_440 bs.lt.sk_432 nl.mt.sq_543 af.nl.de_655
+ 0x09001f09, 0x25001314, 0x1f003519, 0x32002d04, // cy.pl.un_440 et.eu.un_660 zu.cy.un_750 sk.bs.un_320
+ // [04f0]
+ 0x190b0a0e, 0x0d0b1908, 0x211b0c04, 0x32294aad, // pt.es.gl_555 gl.es.cs_443 sv.tr.jw_332 yo.sl.bs_643
+ 0x3f001b19, 0x2723050c, 0x080410ec, 0x0d051104, // tr.af.un_750 fr.ca.gd_543 be.ru.uk_644 ro.fr.cs_332
+ 0x2d000a2b, 0x3b001821, 0x0700041a, 0x2d001e04, // pt.sk.un_980 ga.so.un_860 ru.bg.un_760 ms.sk.un_320
+ 0x160c2907, 0x13000c13, 0x0c080205, 0x28551faf, // sl.sv.hr_432 sv.et.un_650 da.no.sv_333 cy.rw.sw_655
+ // [0500]
+ 0x211e1cad, 0x3b1e1ca0, 0x08020c0c, 0x32161705, // id.ms.jw_643 id.ms.so_322 sv.da.no_543 sr.hr.bs_333
+ 0x321e0304, 0x063f35ad, 0x52001c02, 0x1e5528ad, // nl.ms.bs_332 zu.af.de_643 id.ha.un_220 sw.rw.ms_643
+ 0x20321705, 0x3b00190d, 0x0d000912, 0x29001602, // sr.bs.sq_333 gl.so.un_540 hi.ne.un_640 hr.sl.un_220
+ 0x00001237, 0x13000918, 0x352831a7, 0x102d0d13, // ur.un.un_B00 hi.bh.un_740 az.sw.zu_532 cs.sk.lt_665
+ // [0510]
+ 0x190b0a14, 0x19000b09, 0x090d1cac, 0x4a020d07, // pt.es.gl_666 es.gl.un_440 mr.ne.hi_632 cs.da.yo_432
+ 0x0c001f19, 0x131f070c, 0x523528ec, 0x10002913, // cy.sv.un_750 it.cy.et_543 sw.zu.ha_644 sl.lt.un_650
+ 0x55003514, 0x090d1c13, 0x35000a05, 0x1e1c350e, // zu.rw.un_660 mr.ne.hi_665 pt.zu.un_330 zu.id.ms_555
+ 0x09002a0c, 0x035505a4, 0x0000270f, 0x11355508, // mt.pl.un_530 fr.rw.nl_433 gd.un.un_600 rw.zu.ro_443
+ // [0520]
+ 0x0b20550c, 0x216b2aad, 0x5300041b, 0x040a1702, // rw.sq.es_543 mt.ceb.jw_643 fi.ht.un_770 sr.mk.ru_222
+ 0x2d120da4, 0x08210e55, 0x0d001c0c, 0x2d0d1812, // cs.hu.sk_433 is.jw.no_442 mr.ne.un_530 ga.cs.sk_654
+ 0x52006413, 0x2a321708, 0x00001303, 0x2d0d12ac, // lg.ha.un_650 sr.bs.mt_443 bh.un.un_300 hu.cs.sk_632
+ 0x352d1705, 0x1f000618, 0x06030ca0, 0x3b25030b, // sr.sk.zu_333 de.cy.un_740 sv.nl.de_322 nl.eu.so_542
+ // [0530]
+ 0x08040aa0, 0x230b0a0d, 0x21080e0b, 0x352864a4, // mk.ru.uk_322 pt.es.ca_554 is.no.jw_542 lg.sw.zu_433
+ 0x32162912, 0x08060ca0, 0x080407ec, 0x0e001207, // sl.hr.bs_654 sv.de.no_322 bg.ru.uk_644 hu.is.un_420
+ 0x01002408, 0x07101707, 0x29202aa0, 0x00000401, // yi.iw.un_430 sr.be.bg_432 mt.sq.sl_322 ru.un.un_200
+ 0x11252009, 0x2100120e, 0x00001c24, 0x13000d09, // sq.eu.ro_444 ur.fa.un_550 mr.un.un_900 ne.bh.un_440
+ // [0540]
+ 0x1c00090e, 0x0e6428a4, 0x5500640d, 0x270e0813, // hi.mr.un_550 sw.lg.is_433 lg.rw.un_540 no.is.gd_665
+ 0x0704170c, 0x28556408, 0x09553fec, 0x522a6455, // sr.ru.bg_543 lg.rw.sw_443 af.rw.pl_644 lg.mt.ha_442
+ 0x19000a14, 0x20004a08, 0x00006803, 0x29002a12, // pt.gl.un_660 yo.sq.un_430 ig.un.un_300 mt.sl.un_640
+ 0x100804af, 0x190a1f05, 0x28005207, 0x0200080d, // ru.uk.be_655 cy.pt.gl_333 ha.sw.un_420 no.da.un_540
+ // [0550]
+ 0x04550509, 0x180104a7, 0x066e6ba9, 0x05255313, // fr.rw.fi_444 fi.en.ga_532 ceb.hmn.de_544 ht.eu.fr_665
+ 0x04081013, 0x28556412, 0x12520412, 0x2300051a, // be.uk.ru_665 lg.rw.sw_654 fi.ha.hu_654 fr.ca.un_760
+ 0x00000803, 0x55001c08, 0x23000a1a, 0x23000808, // no.un.un_300 id.rw.un_430 pt.ca.un_760 no.ca.un_430
+ 0x1e1c0ca6, 0x52281f0c, 0x2d1c21ee, 0x2d0d2911, // sv.id.ms_521 cy.sw.ha_543 jw.id.sk_422 sl.cs.sk_653
+ // [0560]
+ 0x4a001913, 0x31080ca0, 0x1c006422, 0x12202aad, // gl.yo.un_650 sv.no.az_322 lg.id.un_870 mt.sq.hu_643
+ 0x3f006408, 0x6e2a05ee, 0x051310ee, 0x09060302, // lg.af.un_430 fr.mt.hmn_422 lt.et.fr_422 nl.de.pl_222
+ 0x07000a04, 0x1a002d04, 0x12006b05, 0x091c1307, // mk.bg.un_320 sk.tl.un_320 ceb.hu.un_330 bh.mr.hi_432
+ 0x00006e03, 0x103b1ea0, 0x112a0508, 0x1716320c, // hmn.un.un_300 ms.so.lt_322 fr.mt.ro_443 bs.hr.sr_543
+ // [0570]
+ 0x17005312, 0x13090d05, 0x04070a0d, 0x21001c04, // ht.sr.un_640 ne.hi.bh_333 mk.bg.ru_554 id.jw.un_320
+ 0x063f0313, 0x046435a0, 0x13091cec, 0x10020fa4, // nl.af.de_665 zu.lg.fi_322 mr.hi.bh_644 lv.da.lt_433
+ 0x00005524, 0x3b001905, 0x090d13af, 0x00003b06, // rw.un.un_900 gl.so.un_330 bh.ne.hi_655 so.un.un_400
+ 0x290c0812, 0x06016e08, 0x3b2a1c07, 0x100f0608, // no.sv.sl_654 hmn.en.de_443 id.mt.so_432 de.lv.lt_443
+ // [0580]
+ 0x00002301, 0x0501060c, 0x1732165a, 0x28556ead, // ca.un.un_200 de.en.fr_543 hr.bs.sr_553 hmn.rw.sw_643
+ 0x06001b0c, 0x0e000508, 0x00000324, 0x1e1c2114, // tr.de.un_530 fr.is.un_430 nl.un.un_900 jw.id.ms_666
+ 0x35551a13, 0x13000c0d, 0x6b641a0c, 0x1c001604, // tl.rw.zu_665 sv.et.un_540 tl.lg.ceb_543 hr.id.un_320
+ 0x1c09130e, 0x23002702, 0x356b1aa9, 0x13033faf, // bh.hi.mr_555 gd.ca.un_220 tl.ceb.zu_544 af.nl.et_655
+ // [0590]
+ 0x211e1c08, 0x2118120d, 0x0d09130e, 0x100a13a4, // id.ms.jw_443 ur.ar.fa_554 bh.hi.ne_555 et.pt.lt_433
+ 0x04281908, 0x1a1e1c14, 0x07041005, 0x2100680e, // gl.sw.fi_443 id.ms.tl_666 be.ru.bg_333 ig.jw.un_550
+ 0x551a64a9, 0x29001b19, 0x3f080c09, 0x04284a07, // lg.tl.rw_544 tr.sl.un_750 sv.no.af_444 yo.sw.fi_432
+ 0x211e1c13, 0x08040aee, 0x1e1c21a9, 0x25001e0d, // id.ms.jw_665 mk.ru.uk_422 jw.id.ms_544 ms.eu.un_540
+ // [05a0]
+ 0x210c080c, 0x21002714, 0x32001707, 0x52001e08, // no.sv.jw_543 gd.jw.un_660 sr.bs.un_420 ms.ha.un_430
+ 0x19000b12, 0x040708ec, 0x32170daf, 0x132a075a, // es.gl.un_640 uk.bg.ru_644 cs.sr.bs_655 it.mt.et_553
+ 0x0800100e, 0x1e000f21, 0x31001b0c, 0x091c0da7, // be.uk.un_550 lv.ms.un_860 tr.az.un_530 ne.mr.hi_532
+ 0x3b551a09, 0x033f2a0c, 0x1100270d, 0x55003204, // tl.rw.so_444 mt.af.nl_543 gd.ro.un_540 bs.rw.un_320
+ // [05b0]
+ 0x1b006b04, 0x02003f0e, 0x1b003109, 0x1c001309, // ceb.tr.un_320 af.da.un_550 az.tr.un_440 bh.mr.un_440
+ 0x19000b19, 0x23052d07, 0x06006e07, 0x2d0d2905, // es.gl.un_750 sk.fr.ca_432 hmn.de.un_420 sl.cs.sk_333
+ 0x236e0508, 0x2d000d13, 0x271f0107, 0x182709a4, // fr.hmn.ca_443 cs.sk.un_650 en.cy.gd_432 pl.gd.ga_433
+ 0x230105ad, 0x12255508, 0x0e020811, 0x120d2d12, // fr.en.ca_643 rw.eu.hu_443 no.da.is_653 sk.cs.hu_654
+ // [05c0]
+ 0x09271eee, 0x1c1a1ea0, 0x070a1708, 0x53001819, // ms.gd.pl_422 ms.tl.id_322 sr.mk.bg_443 ga.ht.un_750
+ 0x55316809, 0x31001b21, 0x35641ca4, 0x0235080c, // ig.az.rw_444 tr.az.un_860 id.lg.zu_433 no.zu.da_543
+ 0x556b640d, 0x2300050c, 0x080621a0, 0x31532108, // lg.ceb.rw_554 fr.ca.un_530 jw.de.no_322 jw.ht.az_443
+ 0x5264080c, 0x55311ca4, 0x230a0513, 0x011a2704, // no.lg.ha_543 id.az.rw_433 fr.pt.ca_665 gd.tl.en_332
+ // [05d0]
+ 0x2d001220, 0x13000804, 0x55083513, 0x0a07170c, // hu.sk.un_850 no.et.un_320 zu.no.rw_665 sr.bg.mk_543
+ 0x191a6b11, 0x041c2112, 0x31353baf, 0x3f0f10a9, // ceb.tl.gl_653 jw.id.fi_654 so.zu.az_655 lt.lv.af_544
+ 0x23000104, 0x04200804, 0x0a081713, 0x05001904, // en.ca.un_320 no.sq.fi_332 sr.uk.mk_665 gl.fr.un_320
+ 0x190b23af, 0x2a200704, 0x1218210c, 0x20080607, // ca.es.gl_655 it.sq.mt_332 fa.ar.ur_543 de.no.sq_432
+ // [05e0]
+ 0x00001b03, 0x3f060808, 0x04211c5a, 0x19090a07, // tr.un.un_300 no.de.af_443 id.jw.fi_553 pt.pl.gl_432
+ 0x19000b0e, 0x29063508, 0x0c3f0305, 0x00003506, // es.gl.un_550 zu.de.sl_443 nl.af.sv_333 zu.un.un_400
+ 0x0d091314, 0x0800101a, 0x552305ac, 0x3f00290b, // bh.hi.ne_666 be.uk.un_760 fr.ca.rw_632 sl.af.un_520
+ 0x06000c0c, 0x07000504, 0x00006401, 0x2305010c, // sv.de.un_530 fr.it.un_320 lg.un.un_200 en.fr.ca_543
+ // [05f0]
+ 0x236e0607, 0x07170aa9, 0x093b1f05, 0x32001605, // de.hmn.ca_432 mk.sr.bg_544 cy.so.pl_333 hr.bs.un_330
+ 0x283b1f0d, 0x171632af, 0x081f0608, 0x05001113, // cy.so.sw_554 bs.hr.sr_655 de.cy.no_443 ro.fr.un_650
+ 0x3f00031b, 0x6e06280c, 0x17000808, 0x0d13095a, // nl.af.un_770 sw.de.hmn_543 no.sr.un_430 hi.bh.ne_553
+ 0x1c0913a4, 0x09121813, 0x2100280c, 0x122118af, // bh.hi.mr_433 ga.hu.pl_665 sw.jw.un_530 ar.fa.ur_655
+ // [0600]
+ 0x2a07010b, 0x080c02a4, 0x286e2b07, 0x16080208, // en.it.mt_542 da.sv.no_433 vi.hmn.sw_432 da.no.hr_443
+ 0x0400171a, 0x130608a7, 0x35072507, 0x08040713, // sr.ru.un_760 no.de.et_532 eu.it.zu_432 bg.ru.uk_665
+ 0x11000813, 0x00002a01, 0x0a170714, 0x3f1206ad, // no.ro.un_650 mt.un.un_200 bg.sr.mk_666 de.hu.af_643
+ 0x011a2112, 0x12001f1b, 0x35006414, 0x21006807, // jw.tl.en_654 cy.hu.un_770 lg.zu.un_660 ig.jw.un_420
+ // [0610]
+ 0x0000092d, 0x07001012, 0x10000f13, 0x0000031c, // hi.un.un_A00 be.bg.un_640 lv.lt.un_650 nl.un.un_800
+ 0x0b04050c, 0x040a1705, 0x040810a6, 0x1e1c04a9, // fr.fi.es_543 sr.mk.ru_333 be.uk.ru_521 fi.id.ms_544
+ 0x351c210e, 0x2d002905, 0x04002713, 0x21001c0d, // jw.id.zu_555 sl.sk.un_330 gd.fi.un_650 id.jw.un_540
+ 0x31001b14, 0x64556ba4, 0x090d1c07, 0x27001823, // tr.az.un_660 ceb.rw.lg_433 mr.ne.hi_432 ga.gd.un_880
+ // [0620]
+ 0x23080208, 0x314a3507, 0x05250107, 0x043f3109, // da.no.ca_443 zu.yo.az_432 en.eu.fr_432 az.af.fi_444
+ 0x21001821, 0x05000804, 0x182d0d14, 0x190b04ad, // ar.fa.un_860 no.fr.un_320 cs.sk.ga_666 fi.es.gl_643
+ 0x0a041108, 0x01033f60, 0x1b003120, 0x55006819, // ro.ru.mk_443 af.nl.en_664 az.tr.un_850 ig.rw.un_750
+ 0x0000040a, 0x2b6b01a9, 0x181b2007, 0x00001915, // fi.un.un_500 en.ceb.vi_544 sq.tr.ga_432 gl.un.un_700
+ // [0630]
+ 0x1f2912af, 0x120407a0, 0x0d1219a0, 0x35006819, // hu.sl.cy_655 it.fi.hu_322 gl.hu.cs_322 ig.zu.un_750
+ 0x6b3b1c07, 0x1800270e, 0x35645355, 0x1a006b08, // id.so.ceb_432 gd.ga.un_550 ht.lg.zu_442 ceb.tl.un_430
+ 0x17081008, 0x29321604, 0x0d091cec, 0x0701270c, // be.uk.sr_443 hr.bs.sl_332 mr.hi.ne_644 gd.en.it_543
+ 0x18002808, 0x064a3505, 0x05004a04, 0x2000070c, // sw.ga.un_430 zu.yo.de_333 yo.fr.un_320 it.sq.un_530
+ // [0640]
+ 0x2b006e07, 0x0602070c, 0x1e1c21a0, 0x551153ee, // hmn.vi.un_420 it.da.de_543 jw.id.ms_322 ht.ro.rw_422
+ 0x35000a12, 0x050b10a0, 0x00000206, 0x1b3b310c, // pt.zu.un_640 lt.es.fr_322 da.un.un_400 az.so.tr_543
+ 0x06001e04, 0x312518a4, 0x1c000905, 0x101a0c12, // ms.de.un_320 ga.eu.az_433 hi.mr.un_330 sv.tl.lt_654
+ 0x09001f13, 0x091c13a9, 0x3f64030d, 0x0a071707, // cy.pl.un_650 bh.mr.hi_544 nl.lg.af_554 sr.bg.mk_432
+ // [0650]
+ 0x64001314, 0x1c000d13, 0x0a0407a4, 0x01000704, // et.lg.un_660 ne.mr.un_650 bg.ru.mk_433 it.en.un_320
+ 0x17321655, 0x32162509, 0x04000719, 0x0b13040c, // hr.bs.sr_442 eu.hr.bs_444 bg.ru.un_750 fi.et.es_543
+ 0x2d0d1214, 0x120b040c, 0x2b4a0714, 0x071708ec, // hu.cs.sk_666 fi.es.hu_543 it.yo.vi_666 uk.sr.bg_644
+ 0x17001018, 0x0e000219, 0x6b020811, 0x3b003104, // be.sr.un_740 da.is.un_750 no.da.ceb_653 az.so.un_320
+ // [0660]
+ 0x02211c0c, 0x19000b04, 0x21001008, 0x0d1c1311, // id.jw.da_543 es.gl.un_320 lt.jw.un_430 bh.mr.ne_653
+ 0x190853ee, 0x00001324, 0x3b001021, 0x21026b07, // ht.no.gl_422 bh.un.un_900 lt.so.un_860 ceb.da.jw_432
+ 0x321716a9, 0x321716a0, 0x0a000712, 0x09000d07, // hr.sr.bs_544 hr.sr.bs_322 bg.mk.un_640 ne.hi.un_420
+ 0x17000a12, 0x1b252aa4, 0x12001812, 0x0000090a, // mk.sr.un_640 mt.eu.tr_433 ar.ur.un_640 pl.un.un_500
+ // [0670]
+ 0x1c090d60, 0x19110bee, 0x1b313bec, 0x52001a0c, // ne.hi.mr_664 es.ro.gl_422 so.az.tr_644 tl.ha.un_530
+ 0x1b1e1c02, 0x0b0a0cee, 0x070a1709, 0x17033f04, // id.ms.tr_222 sv.pt.es_422 sr.mk.bg_444 af.nl.sr_332
+ 0x080410ad, 0x1c005207, 0x32311b55, 0x3f000708, // be.ru.uk_643 ha.id.un_420 tr.az.bs_442 it.af.un_430
+ 0x17070a08, 0x55642807, 0x13071212, 0x00003f0f, // mk.bg.sr_443 sw.lg.rw_432 hu.it.et_654 af.un.un_600
+ // [0680]
+ 0x0e000812, 0x2b001c07, 0x12002a07, 0x2b001c02, // no.is.un_640 id.vi.un_420 mt.hu.un_420 id.vi.un_220
+ 0x53355208, 0x31121b07, 0x2d080204, 0x0c000808, // ha.zu.ht_443 tr.hu.az_432 da.no.sk_332 no.sv.un_430
+ 0x1a0b6b08, 0x0b521e07, 0x1e005207, 0x0c0e02ee, // ceb.es.tl_443 ms.ha.es_432 ha.ms.un_420 da.is.sv_422
+ 0x35522a05, 0x351c21a7, 0x1e1c35ee, 0x0c00081a, // mt.ha.zu_333 jw.id.zu_532 zu.id.ms_422 no.sv.un_760
+ // [0690]
+ 0x092d0d0e, 0x161f2702, 0x6e080eec, 0x19000b13, // cs.sk.pl_555 gd.cy.hr_222 is.no.hmn_644 es.gl.un_650
+ 0x35646b07, 0x25001e04, 0x292d0d55, 0x0d091305, // ceb.lg.zu_432 ms.eu.un_320 cs.sk.sl_442 bh.hi.ne_333
+ 0x0000071c, 0x171632a0, 0x55643504, 0x04081708, // it.un.un_800 bs.hr.sr_322 zu.lg.rw_332 sr.uk.ru_443
+ 0x04170aa0, 0x32000108, 0x28526b08, 0x2d001704, // mk.sr.ru_322 en.bs.un_430 ceb.ha.sw_443 sr.sk.un_320
+ // [06a0]
+ 0x645535a4, 0x080a07a4, 0x10002508, 0x044a2502, // zu.rw.lg_433 bg.mk.uk_433 eu.lt.un_430 eu.yo.fi_222
+ 0x20001c05, 0x19000b0c, 0x531a6b07, 0x13000909, // id.sq.un_330 es.gl.un_530 ceb.tl.ht_432 hi.bh.un_440
+ 0x03003f0e, 0x53006b04, 0x0c000f04, 0x00001301, // af.nl.un_550 ceb.ht.un_320 lv.sv.un_320 et.un.un_200
+ 0x2500521a, 0x53292da4, 0x0000130a, 0x09001c13, // ha.eu.un_760 sk.sl.ht_433 bh.un.un_500 mr.hi.un_650
+ // [06b0]
+ 0x355564af, 0x0c000e04, 0x256413a0, 0x1f001e13, // lg.rw.zu_655 is.sv.un_320 et.lg.eu_322 ms.cy.un_650
+ 0x285329a0, 0x08001021, 0x130153a0, 0x0d00090e, // sl.ht.sw_322 be.uk.un_860 ht.en.et_322 hi.ne.un_550
+ 0x35286404, 0x09002908, 0x051f0111, 0x642868ee, // lg.sw.zu_332 sl.pl.un_430 en.cy.fr_653 ig.sw.lg_422
+ 0x1e55280c, 0x312d0907, 0x08000223, 0x091c0daf, // sw.rw.ms_543 pl.sk.az_432 da.no.un_880 ne.mr.hi_655
+ // [06c0]
+ 0x00005315, 0x25005208, 0x1700290b, 0x18120e5a, // ht.un.un_700 ha.eu.un_430 sl.sr.un_520 is.hu.ga_553
+ 0x00005306, 0x210c1f0c, 0x2527180c, 0x1b001904, // ht.un.un_400 cy.sv.jw_543 ga.gd.eu_543 gl.tr.un_320
+ 0x100408a4, 0x190d0b0c, 0x1300270c, 0x21001c02, // uk.ru.be_433 es.cs.gl_543 gd.et.un_530 id.jw.un_220
+ 0x0e001b14, 0x051f010c, 0x1c0d0909, 0x10000821, // tr.is.un_660 en.cy.fr_543 hi.ne.mr_444 uk.be.un_860
+ // [06d0]
+ 0x060e0f0c, 0x201b2ba4, 0x08041007, 0x0d321604, // lv.is.de_543 vi.tr.sq_433 be.ru.uk_432 hr.bs.cs_332
+ 0x00000d06, 0x31006b07, 0x19200b12, 0x0f000919, // ne.un.un_400 ceb.az.un_420 es.sq.gl_654 pl.lv.un_750
+ 0x07002a02, 0x3f040313, 0x09001107, 0x0c000e1a, // mt.it.un_220 nl.fi.af_665 ro.pl.un_420 is.sv.un_760
+ 0x033f040d, 0x25121807, 0x1c000d0c, 0x28001e04, // fi.af.nl_554 ga.hu.eu_432 ne.mr.un_530 ms.sw.un_320
+ // [06e0]
+ 0x092555a7, 0x2d090d0c, 0x121821ec, 0x00000524, // rw.eu.pl_532 cs.pl.sk_543 fa.ar.ur_644 fr.un.un_900
+ 0x16092907, 0x0e020fa9, 0x08070a05, 0x2b002704, // sl.pl.hr_432 lv.da.is_544 mk.bg.uk_333 gd.vi.un_320
+ 0x532520a4, 0x07041707, 0x190b0a05, 0x0a041707, // sq.eu.ht_433 sr.ru.bg_432 pt.es.gl_333 sr.ru.mk_432
+ 0x17321604, 0x23181f04, 0x04002d08, 0x23070ba4, // hr.bs.sr_332 cy.ga.ca_332 sk.fi.un_430 es.it.ca_433
+ // [06f0]
+ 0x1c0d09ec, 0x0804170e, 0x250723a0, 0x06050111, // hi.ne.mr_644 sr.ru.uk_555 ca.it.eu_322 en.fr.de_653
+ 0x072a19ee, 0x31001f05, 0x27001f0d, 0x0f001021, // gl.mt.it_422 cy.az.un_330 cy.gd.un_540 lt.lv.un_860
+ 0x1f05010c, 0x170407a4, 0x0a001021, 0x110504ee, // en.fr.cy_543 bg.ru.sr_433 be.mk.un_860 fi.fr.ro_422
+ 0x5300550c, 0x0a071702, 0x4a00532a, 0x2b001c12, // rw.ht.un_530 sr.bg.mk_222 ht.yo.un_970 id.vi.un_640
+ // [0700]
+ 0x0d122d55, 0x1c0d09af, 0x1b063f5a, 0x0a071104, // sk.hu.cs_442 hi.ne.mr_655 af.de.tr_553 ro.bg.mk_332
+ 0x3b005202, 0x080407a4, 0x00002703, 0x213b1f08, // ha.so.un_220 bg.ru.uk_433 gd.un.un_300 cy.so.jw_443
+ 0x2b001c0c, 0x290f2dad, 0x043113af, 0x0c080e0c, // id.vi.un_530 sk.lv.sl_643 et.az.fi_655 is.no.sv_543
+ 0x2d2920af, 0x292d0da4, 0x53001308, 0x2a0413ad, // sq.sl.sk_655 cs.sk.sl_433 et.ht.un_430 et.fi.mt_643
+ // [0710]
+ 0x35080214, 0x08000c0d, 0x0c0413a4, 0x00003106, // da.no.zu_666 sv.no.un_540 et.fi.sv_433 az.un.un_400
+ 0x3b0955af, 0x3213040c, 0x100964a4, 0x3b001f05, // rw.pl.so_655 fi.et.bs_543 lg.pl.lt_433 cy.so.un_330
+ 0x0e001213, 0x281129af, 0x55683505, 0x1b312d08, // hu.is.un_650 sl.ro.sw_655 zu.ig.rw_333 sk.az.tr_443
+ 0x160f13a4, 0x282a5207, 0x095525a4, 0x35006405, // et.lv.hr_433 ha.mt.sw_432 eu.rw.pl_433 lg.zu.un_330
+ // [0720]
+ 0x106e06a4, 0x081004a4, 0x0000230f, 0x1f002702, // de.hmn.lt_433 ru.be.uk_433 ca.un.un_600 gd.cy.un_220
+ 0x642835a7, 0x09312008, 0x35006b04, 0x0b0a3ba4, // zu.sw.lg_532 sq.az.pl_443 ceb.zu.un_320 so.pt.es_433
+ 0x0e2d090c, 0x17040807, 0x0a001108, 0x1b292d07, // pl.sk.is_543 uk.ru.sr_432 ro.mk.un_430 sk.sl.tr_432
+ 0x0e000c02, 0x2a00180d, 0x13122aa4, 0x17000a09, // sv.is.un_220 ga.mt.un_540 mt.hu.et_433 mk.sr.un_440
+ // [0730]
+ 0x0c083fee, 0x202d0d14, 0x2d1f2708, 0x35000b08, // af.no.sv_422 cs.sk.sq_666 gd.cy.sk_443 es.zu.un_430
+ 0x231e1c0c, 0x0e1a6b08, 0x121013a7, 0x2855640b, // id.ms.ca_543 ceb.tl.is_443 et.lt.hu_532 lg.rw.sw_542
+ 0x071013ee, 0x1f00270d, 0x0000012d, 0x352d0d13, // et.lt.it_422 gd.cy.un_540 en.un.un_A00 cs.sk.zu_665
+ 0x09001c08, 0x3b5552a7, 0x2d0413ad, 0x6e00532a, // mr.hi.un_430 ha.rw.so_532 et.fi.sk_643 ht.hmn.un_970
+ // [0740]
+ 0x1300091a, 0x03005212, 0x1200180e, 0x00000d03, // hi.bh.un_760 ha.nl.un_640 ga.hu.un_550 cs.un.un_300
+ 0x13001c08, 0x095511ee, 0x0000102d, 0x12006b09, // id.et.un_430 ro.rw.pl_422 lt.un.un_A00 ceb.hu.un_440
+ 0x00001e06, 0x08001113, 0x2a002304, 0x64002813, // ms.un.un_400 ro.uk.un_650 ca.mt.un_320 sw.lg.un_650
+ 0x03003f12, 0x08020c02, 0x012d0d0e, 0x17551155, // af.nl.un_640 sv.da.no_222 cs.sk.en_555 ro.rw.sr_442
+ // [0750]
+ 0x070411af, 0x11645513, 0x060c07a0, 0x033f2707, // ro.ru.bg_655 rw.lg.ro_665 it.sv.de_322 gd.af.nl_432
+ 0x171632ee, 0x0c232b08, 0x1100550e, 0x13000513, // bs.hr.sr_422 vi.ca.sv_443 rw.ro.un_550 fr.et.un_650
+ 0x0a040708, 0x0e04060c, 0x080411af, 0x20216412, // bg.ru.mk_443 de.fi.is_543 ro.ru.uk_655 lg.jw.sq_654
+ 0x0e041e07, 0x033f1f0c, 0x04000e04, 0x2d002913, // ms.fi.is_432 cy.af.nl_543 is.fi.un_320 sl.sk.un_650
+ // [0760]
+ 0x27183507, 0x55110a05, 0x28643504, 0x211e1c0c, // zu.ga.gd_432 pt.ro.rw_333 zu.lg.sw_332 id.ms.jw_543
+ 0x07171105, 0x04000821, 0x2805010c, 0x17070aa4, // ro.sr.bg_333 uk.ru.un_860 en.fr.sw_543 mk.bg.sr_433
+ 0x0d0913a9, 0x253f0307, 0x0400100c, 0x05000b02, // bh.hi.ne_544 nl.af.eu_432 be.ru.un_530 es.fr.un_220
+ 0x16321755, 0x00001a0a, 0x68203555, 0x1a0b6b0c, // sr.bs.hr_442 tl.un.un_500 zu.sq.ig_442 ceb.es.tl_543
+ // [0770]
+ 0x0d00091a, 0x280611ee, 0x0e020c14, 0x09005321, // hi.ne.un_760 ro.de.sw_422 sv.da.is_666 ht.pl.un_860
+ 0x28353b0c, 0x2d000d22, 0x052b0c08, 0x6e003f08, // so.zu.sw_543 cs.sk.un_870 sv.vi.fr_443 af.hmn.un_430
+ 0x13000d08, 0x10170a08, 0x0c00082c, 0x2d0d29a4, // ne.bh.un_430 mk.sr.be_443 no.sv.un_990 sl.cs.sk_433
+ 0x2304010c, 0x3b0e11a4, 0x12682a08, 0x00004a24, // en.fi.ca_543 ro.is.so_433 mt.ig.hu_443 yo.un.un_900
+ // [0780]
+ 0x05003504, 0x251b1e0c, 0x0000272d, 0x3120010c, // zu.fr.un_320 ms.tr.eu_543 gd.un.un_A00 en.sq.az_543
+ 0x6e1f1913, 0x07170a14, 0x0c00022c, 0x2902160c, // gl.cy.hmn_665 mk.sr.bg_666 da.sv.un_990 hr.da.sl_543
+ 0x52000804, 0x00000e01, 0x1e211caf, 0x060e08a9, // no.ha.un_320 is.un.un_200 id.jw.ms_655 no.is.de_544
+ 0x17002308, 0x24000122, 0x0c001912, 0x0c082307, // ca.sr.un_430 iw.yi.un_870 gl.sv.un_640 ca.no.sv_432
+ // [0790]
+ 0x04001113, 0x04001004, 0x280c550c, 0x25170aa0, // ro.ru.un_650 be.ru.un_320 rw.sv.sw_543 pt.sr.eu_322
+ 0x1c6b55ad, 0x06006e12, 0x16002907, 0x230a19a0, // rw.ceb.id_643 hmn.de.un_640 sl.hr.un_420 gl.pt.ca_322
+ 0x01296bee, 0x00000606, 0x11000108, 0x55521e0c, // ceb.sl.en_422 de.un.un_400 en.ro.un_430 ms.ha.rw_543
+ 0x0f002302, 0x21283511, 0x170a11a4, 0x00003f1c, // ca.lv.un_220 zu.sw.jw_653 ro.mk.sr_433 af.un.un_800
+ // [07a0]
+ 0x3216290e, 0x04071004, 0x0e020c13, 0x0b0a190e, // sl.hr.bs_555 be.bg.ru_332 sv.da.is_665 gl.pt.es_555
+ 0x05522aad, 0x286404ad, 0x20003f0d, 0x040c08a4, // mt.ha.fr_643 fi.lg.sw_643 af.sq.un_540 no.sv.fi_433
+ 0x53001a0c, 0x2900110d, 0x17002d04, 0x0d2911a7, // tl.ht.un_530 ro.sl.un_540 sk.sr.un_320 ro.sl.cs_532
+ 0x0400101a, 0x201c1ea0, 0x6b276e0c, 0x10062005, // be.ru.un_760 ms.id.sq_322 hmn.gd.ceb_543 sq.de.lt_333
+ // [07b0]
+ 0x1e1c1fa9, 0x2d0d1909, 0x55251c0c, 0x03000804, // cy.id.ms_544 gl.cs.sk_444 id.eu.rw_543 no.nl.un_320
+ 0x0600032c, 0x230701a4, 0x00000a01, 0x0b5223a0, // nl.de.un_990 en.it.ca_433 mk.un.un_200 ca.ha.es_322
+ 0x04001313, 0x2d0d12a0, 0x3b1c1eee, 0x21131ca6, // et.fi.un_650 hu.cs.sk_322 ms.id.so_422 id.et.jw_521
+ 0x1200050d, 0x21181213, 0x033f050c, 0x05006419, // fr.hu.un_540 ur.ar.fa_665 fr.af.nl_543 lg.fr.un_750
+ // [07c0]
+ 0x1b003113, 0x08000611, 0x05282b07, 0x1821120c, // az.tr.un_650 de.no.un_630 vi.sw.fr_432 ur.fa.ar_543
+ 0x07100413, 0x6b000412, 0x32000908, 0x1b1e1c05, // ru.be.bg_665 fi.ceb.un_640 pl.bs.un_430 id.ms.tr_333
+ 0x0a040809, 0x31006b02, 0x00000e24, 0x10033f08, // uk.ru.mk_444 ceb.az.un_220 is.un.un_900 af.nl.lt_443
+ 0x11050112, 0x10006b04, 0x0e5235ad, 0x211e1cee, // en.fr.ro_654 ceb.lt.un_320 zu.ha.is_643 id.ms.jw_422
+ // [07d0]
+ 0x0a002309, 0x131b31b3, 0x1200211b, 0x2d0d0e0e, // ca.pt.un_440 az.tr.et_743 fa.ur.un_770 is.cs.sk_555
+ 0x06020c14, 0x1c001e0c, 0x313f010c, 0x1c090da0, // sv.da.de_666 ms.id.un_530 en.af.az_543 ne.hi.mr_322
+ 0x09002a04, 0x00001b1c, 0x1710010c, 0x0f002314, // mt.pl.un_320 tr.un.un_800 en.lt.sr_543 ca.lv.un_660
+ 0x13230c08, 0x29000d08, 0x11002a0e, 0x322d0d08, // sv.ca.et_443 cs.sl.un_430 mt.ro.un_550 cs.sk.bs_443
+ // [07e0]
+ 0x0a002313, 0x19000a2c, 0x0000231c, 0x13013b04, // ca.pt.un_650 pt.gl.un_990 ca.un.un_800 so.en.et_332
+ 0x07001602, 0x040501ec, 0x13001c09, 0x050a230e, // hr.it.un_220 en.fr.fi_644 mr.bh.un_440 ca.pt.fr_555
+ 0x25000512, 0x0500012b, 0x030208a4, 0x04070a13, // fr.eu.un_640 en.fr.un_980 no.da.nl_433 mk.bg.ru_665
+ 0x1e111caf, 0x0c0523ec, 0x0a310eac, 0x2d0d0c09, // id.ro.ms_655 ca.fr.sv_644 is.az.pt_632 sv.cs.sk_444
+ // [07f0]
+ 0x12000a02, 0x00002303, 0x0a08040d, 0x050c23ad, // pt.hu.un_220 ca.un.un_300 ru.uk.mk_554 ca.sv.fr_643
+ 0x0000191c, 0x29092da4, 0x3b003f05, 0x3f3b0313, // gl.un.un_800 sk.pl.sl_433 af.so.un_330 nl.so.af_665
+ 0x11120807, 0x250b53a0, 0x05002313, 0x3500640c, // no.hu.ro_432 ht.es.eu_322 ca.fr.un_650 lg.zu.un_530
+ 0x05002319, 0x32000d0d, 0x28001a13, 0x08100a05, // ca.fr.un_750 cs.bs.un_540 tl.sw.un_650 mk.be.uk_333
+
+ // [0800]
+ 0x06003113, 0x10033f0c, 0x0a25010c, 0x10005219, // az.de.un_650 af.nl.lt_543 en.eu.pt_543 ha.lt.un_750
+ 0x0d320ba0, 0x16002908, 0x0500010d, 0x0a006402, // es.bs.cs_322 sl.hr.un_430 en.fr.un_540 lg.pt.un_220
+ 0x08021b0c, 0x05000122, 0x1b003114, 0x2100181a, // tr.da.no_543 en.fr.un_870 az.tr.un_660 ar.fa.un_760
+ 0x17272307, 0x2100050c, 0x0207080c, 0x0c002311, // ca.gd.sr_432 fr.jw.un_530 no.it.da_543 ca.sv.un_630
+ // [0810]
+ 0x0120680c, 0x0a080408, 0x211c1e11, 0x00002324, // ig.sq.en_543 ru.uk.mk_443 ms.id.jw_653 ca.un.un_900
+ 0x6800080d, 0x160802a4, 0x13000913, 0x0703080c, // no.ig.un_540 da.no.hr_433 hi.bh.un_650 no.nl.it_543
+ 0x250c2308, 0x110a0712, 0x0c002308, 0x21001208, // ca.sv.eu_443 bg.mk.ro_654 ca.sv.un_430 ur.fa.un_430
+ 0x0a53680c, 0x1600290b, 0x130c2307, 0x091c1304, // ig.ht.pt_543 sl.hr.un_520 ca.sv.et_432 bh.mr.hi_332
+ // [0820]
+ 0x6408020d, 0x0200080c, 0x20002908, 0x29005502, // da.no.lg_554 no.da.un_530 sl.sq.un_430 rw.sl.un_220
+ 0x00002906, 0x0b0a1902, 0x1b003111, 0x68005523, // sl.un.un_400 gl.pt.es_222 az.tr.un_630 rw.ig.un_880
+ 0x32000d04, 0x12211812, 0x181f2712, 0x0207110c, // cs.bs.un_320 ar.fa.ur_654 gd.cy.ga_654 ro.it.da_543
+ 0x32001604, 0x1f1729ee, 0x00006824, 0x060e3f55, // hr.bs.un_320 sl.sr.cy_422 ig.un.un_900 af.is.de_442
+ // [0830]
+ 0x20110f5a, 0x092d0d60, 0x55190bad, 0x0000010a, // lv.ro.sq_553 cs.sk.pl_664 es.gl.rw_643 en.un.un_500
+ 0x27001813, 0x53311b0c, 0x32172909, 0x281827a4, // ga.gd.un_650 tr.az.ht_543 sl.sr.bs_444 gd.ga.sw_433
+ 0x29005313, 0x08020714, 0x1b001e04, 0x3f001e0d, // ht.sl.un_650 it.da.no_666 ms.tr.un_320 ms.af.un_540
+ 0x2818270e, 0x16000d04, 0x290668a9, 0x13002a1a, // gd.ga.sw_555 cs.hr.un_320 ig.de.sl_544 mt.et.un_760
+ // [0840]
+ 0x080c0e07, 0x13642a0c, 0x080255ec, 0x643207ee, // is.sv.no_432 mt.lg.et_543 rw.da.no_644 it.bs.lg_422
+ 0x68002519, 0x0607080c, 0x1a6b1c0c, 0x07080a08, // eu.ig.un_750 no.it.de_543 id.ceb.tl_543 mk.uk.bg_443
+ 0x043f0308, 0x075368a9, 0x1b003123, 0x10040855, // nl.af.fi_443 ig.ht.it_544 az.tr.un_880 uk.ru.be_442
+ 0x07102708, 0x20000619, 0x19050b07, 0x203f0807, // gd.lt.it_443 de.sq.un_750 es.fr.gl_432 no.af.sq_432
+ // [0850]
+ 0x1b00310d, 0x03172007, 0x1c001e34, 0x0d1c09a9, // az.tr.un_540 sq.sr.nl_432 ms.id.un_A80 hi.mr.ne_544
+ 0x21001c1a, 0x121e1c55, 0x2b27010c, 0x1f1827af, // id.jw.un_760 id.ms.hu_442 en.gd.vi_543 gd.ga.cy_655
+ 0x0000320a, 0x211c1e0c, 0x2b641ca4, 0x6b033f0d, // bs.un.un_500 ms.id.jw_543 id.lg.vi_433 af.nl.ceb_554
+ 0x211e1c09, 0x09000d0d, 0x06000113, 0x1007040c, // id.ms.jw_444 cs.pl.un_540 en.de.un_650 ru.bg.be_543
+ // [0860]
+ 0x08171112, 0x1b090ea4, 0x010a23a0, 0x55002d0d, // ro.sr.uk_654 is.pl.tr_433 ca.pt.en_322 sk.rw.un_540
+ 0x6b3b31a9, 0x00000603, 0x00000506, 0x1e001c1b, // az.so.ceb_544 de.un.un_300 fr.un.un_400 id.ms.un_770
+ 0x521603a0, 0x31131b08, 0x0d1c09ad, 0x0e0164ee, // nl.hr.ha_322 tr.et.az_443 hi.mr.ne_643 lg.en.is_422
+ 0x110604a7, 0x112009ee, 0x050d2107, 0x29002d1a, // fi.de.ro_532 pl.sq.ro_422 jw.cs.fr_432 sk.sl.un_760
+ // [0870]
+ 0x0000681c, 0x031e3fa0, 0x12182113, 0x2d090d07, // ig.un.un_800 af.ms.nl_322 fa.ar.ur_665 cs.pl.sk_432
+ 0x12190b13, 0x18001213, 0x19000b0d, 0x1e001c19, // es.gl.hu_665 ur.ar.un_650 es.gl.un_540 id.ms.un_750
+ 0x0a040705, 0x0c00131b, 0x05001908, 0x2b1801a6, // bg.ru.mk_333 et.sv.un_770 gl.fr.un_430 en.ga.vi_521
+ 0x0f095508, 0x1600290e, 0x0a04110c, 0x081f120c, // rw.pl.lv_443 sl.hr.un_550 ro.ru.mk_543 hu.cy.no_543
+ // [0880]
+ 0x122005a4, 0x11001f05, 0x1a080a12, 0x28556407, // fr.sq.hu_433 cy.ro.un_330 pt.no.tl_654 lg.rw.sw_432
+ 0x06000c12, 0x131f35ee, 0x1e1c21a4, 0x190a2309, // sv.de.un_640 zu.cy.et_422 jw.id.ms_433 ca.pt.gl_444
+ 0x11000104, 0x01033f02, 0x06080208, 0x0c000804, // en.ro.un_320 af.nl.en_222 da.no.de_443 no.sv.un_320
+ 0x52001320, 0x202135ec, 0x18003504, 0x21042bee, // et.ha.un_850 zu.jw.sq_644 zu.ga.un_320 vi.fi.jw_422
+ // [0890]
+ 0x1e001c0c, 0x250418ad, 0x0f4a0a07, 0x4a52350c, // id.ms.un_530 ga.fi.eu_643 pt.yo.lv_432 zu.ha.yo_543
+ 0x09001c0d, 0x0400171b, 0x292d03ec, 0x2b0e2104, // mr.hi.un_540 sr.ru.un_770 nl.sk.sl_644 jw.is.vi_332
+ 0x0f1f5205, 0x190a23a0, 0x04060c67, 0x3f00061b, // ha.cy.lv_333 ca.pt.gl_322 sv.de.fi_775 de.af.un_770
+ 0x04060c0c, 0x06001114, 0x02000809, 0x23190a02, // sv.de.fi_543 ro.de.un_660 no.da.un_440 pt.gl.ca_222
+ // [08a0]
+ 0x08200cad, 0x1b4a2504, 0x1b2906a4, 0x04130c55, // sv.sq.no_643 eu.yo.tr_332 de.sl.tr_433 sv.et.fi_442
+ 0x2d190b07, 0x08022012, 0x1900050e, 0x55356404, // es.gl.sk_432 sq.da.no_654 fr.gl.un_550 lg.zu.rw_332
+ 0x1a044a12, 0x18190b09, 0x13060c0c, 0x1b645504, // yo.fi.tl_654 es.gl.ga_444 sv.de.et_543 rw.lg.tr_332
+ 0x2b006e2a, 0x1c55210c, 0x00003206, 0x0d1c1355, // hmn.vi.un_970 jw.rw.id_543 bs.un.un_400 bh.mr.ne_442
+ // [08b0]
+ 0x04001112, 0x28005509, 0x32002008, 0x21001c13, // ro.ru.un_640 rw.sw.un_440 sq.bs.un_430 id.jw.un_650
+ 0x0f202912, 0x3f02040c, 0x08002b13, 0x2b0a6807, // sl.sq.lv_654 fi.da.af_543 vi.no.un_650 ig.pt.vi_432
+ 0x6800552a, 0x12006e23, 0x172923a4, 0x68040aee, // rw.ig.un_970 hmn.hu.un_880 ca.sl.sr_433 pt.fi.ig_422
+ 0x27002b34, 0x0c0802ac, 0x53001022, 0x0f002507, // vi.gd.un_A80 da.no.sv_632 lt.ht.un_870 eu.lv.un_420
+ // [08c0]
+ 0x0b1f13ec, 0x6b190a11, 0x2a00531b, 0x2a536e5a, // et.cy.es_644 pt.gl.ceb_653 ht.mt.un_770 hmn.ht.mt_553
+ 0x09004a12, 0x1b003119, 0x190b18ee, 0x1308180c, // yo.pl.un_640 az.tr.un_750 ga.es.gl_422 ga.no.et_543
+ 0x0e0a0107, 0x02006404, 0x18122112, 0x0802050e, // en.pt.is_432 lg.da.un_320 fa.ur.ar_654 fr.da.no_555
+ 0x0400071b, 0x17161a0c, 0x2100280d, 0x00002024, // bg.ru.un_770 tl.hr.sr_543 sw.jw.un_540 sq.un.un_900
+ // [08d0]
+ 0x4a2a06ec, 0x026408af, 0x6b120e05, 0x056e3f0c, // de.mt.yo_644 no.lg.da_655 is.hu.ceb_333 af.hmn.fr_543
+ 0x0d13090d, 0x17071008, 0x06002013, 0x0a063f07, // hi.bh.ne_554 be.bg.sr_443 sq.de.un_650 af.de.pt_432
+ 0x18000708, 0x1f002a29, 0x1c10210c, 0x0f001b05, // it.ga.un_430 mt.cy.un_960 jw.lt.id_543 tr.lv.un_330
+ 0x00000c03, 0x20002d1a, 0x1c211eaf, 0x01006e02, // sv.un.un_300 sk.sq.un_760 ms.jw.id_655 hmn.en.un_220
+ // [08e0]
+ 0x080213a0, 0x11006e1a, 0x1268640c, 0x2d6413a4, // et.da.no_322 hmn.ro.un_760 lg.ig.hu_543 et.lg.sk_433
+ 0x11003b08, 0x1300041a, 0x2b3b1704, 0x212d0d55, // so.ro.un_430 fi.et.un_760 sr.so.vi_332 cs.sk.jw_442
+ 0x041a11af, 0x2300190d, 0x2d13100c, 0x2d1064a4, // ro.tl.fi_655 gl.ca.un_540 lt.et.sk_543 lg.lt.sk_433
+ 0x1f003208, 0x04000a13, 0x1b20180c, 0x6e00272a, // bs.cy.un_430 mk.ru.un_650 ga.sq.tr_543 gd.hmn.un_970
+ // [08f0]
+ 0x28060b04, 0x23001904, 0x1c005204, 0x1f080c05, // es.de.sw_332 gl.ca.un_320 ha.id.un_320 sv.no.cy_333
+ 0x09233108, 0x011a6ba0, 0x3b3119a9, 0x10080209, // az.ca.pl_443 ceb.tl.en_322 gl.az.so_544 da.no.lt_444
+ 0x020c08a4, 0x00002106, 0x200f29a4, 0x08003202, // no.sv.da_433 fa.un.un_400 sl.lv.sq_433 bs.no.un_220
+ 0x2d001014, 0x0d0913af, 0x27061aa0, 0x1f2519a9, // lt.sk.un_660 bh.hi.ne_655 tl.de.gd_322 gl.eu.cy_544
+ // [0900]
+ 0x07001a09, 0x00003f01, 0x131b2008, 0x02000a19, // tl.it.un_440 af.un.un_200 sq.tr.et_443 pt.da.un_750
+ 0x321617ee, 0x093f0308, 0x1a041812, 0x12000a0c, // sr.hr.bs_422 nl.af.pl_443 ga.fi.tl_654 pt.hu.un_530
+ 0x1c000104, 0x271c04a0, 0x0d00091b, 0x10291a12, // en.id.un_320 fi.id.gd_322 hi.ne.un_770 tl.sl.lt_654
+ 0x29321605, 0x00000306, 0x023b1f0d, 0x0d000908, // hr.bs.sl_333 nl.un.un_400 cy.so.da_554 hi.ne.un_430
+ // [0910]
+ 0x1b0c06ec, 0x680f0107, 0x07041008, 0x1c000d0e, // de.sv.tr_644 en.lv.ig_432 be.ru.bg_443 ne.mr.un_550
+ 0x17166ba4, 0x00004a01, 0x29321704, 0x2900170c, // ceb.hr.sr_433 yo.un.un_200 sr.bs.sl_332 sr.sl.un_530
+ 0x0523010c, 0x031b2807, 0x1a080ea0, 0x283b0904, // en.ca.fr_543 sw.tr.nl_432 is.no.tl_322 pl.so.sw_332
+ 0x0a0407a9, 0x07040a08, 0x170a0712, 0x252b20ec, // bg.ru.mk_544 mk.ru.bg_443 bg.mk.sr_654 sq.vi.eu_644
+ // [0920]
+ 0x09000614, 0x296b1a08, 0x2a126e14, 0x0a100708, // de.pl.un_660 tl.ceb.sl_443 hmn.hu.mt_666 bg.be.mk_443
+ 0x55643b14, 0x32170f02, 0x4a0c200e, 0x23110107, // so.lg.rw_666 lv.sr.bs_222 sq.sv.yo_555 en.ro.ca_432
+ 0x27556460, 0x25050704, 0x1c054a12, 0x190a53af, // lg.rw.gd_664 it.fr.eu_332 yo.fr.id_654 ht.pt.gl_655
+ 0x020c11af, 0x05190b0e, 0x0c060308, 0x05000e22, // ro.sv.da_655 es.gl.fr_555 nl.de.sv_443 is.fr.un_870
+ // [0930]
+ 0x28110513, 0x190a2314, 0x68311b13, 0x09002904, // fr.ro.sw_665 ca.pt.gl_666 tr.az.ig_665 sl.pl.un_320
+ 0x00001137, 0x13091caf, 0x0a1923ad, 0x1a000c04, // ro.un.un_B00 mr.hi.bh_655 ca.gl.pt_643 sv.tl.un_320
+ 0x110a1faf, 0x0723080c, 0x182027a4, 0x68071f0c, // cy.pt.ro_655 no.ca.it_543 gd.sq.ga_433 cy.it.ig_543
+ 0x190b2dee, 0x2a006b07, 0x080a1014, 0x00002424, // sk.es.gl_422 ceb.mt.un_420 be.mk.uk_666 yi.un.un_900
+ // [0940]
+ 0x1e1c6ea0, 0x120f5211, 0x29000909, 0x0000062d, // hmn.id.ms_322 ha.lv.hu_653 pl.sl.un_440 de.un.un_A00
+ 0x07080e0b, 0x0d00291a, 0x32230e07, 0x6e002b19, // is.no.it_542 sl.cs.un_760 is.ca.bs_432 vi.hmn.un_750
+ 0x2a6b1aaf, 0x3b28520d, 0x1f05530b, 0x0300251a, // tl.ceb.mt_655 ha.sw.so_554 ht.fr.cy_542 eu.nl.un_760
+ 0x0e000c0e, 0x1900230e, 0x03001208, 0x0a192307, // sv.is.un_550 ca.gl.un_550 hu.nl.un_430 ca.gl.pt_432
+ // [0950]
+ 0x3f1304ad, 0x070a170e, 0x3b006e08, 0x09006b04, // fi.et.af_643 sr.mk.bg_555 hmn.so.un_430 ceb.pl.un_320
+ 0x0f4a3113, 0x2d100da4, 0x0400080d, 0x12001105, // az.yo.lv_665 cs.lt.sk_433 uk.ru.un_540 ro.hu.un_330
+ 0x2b1605ec, 0x55006b1a, 0x0a0408af, 0x190a23ec, // fr.hr.vi_644 ceb.rw.un_760 uk.ru.mk_655 ca.pt.gl_644
+ 0x102b050c, 0x0a001119, 0x1700290d, 0x01524a07, // fr.vi.lt_543 ro.mk.un_750 sl.sr.un_540 yo.ha.en_432
+ // [0960]
+ 0x05280ba9, 0x0f160da0, 0x00003b2d, 0x011f120c, // es.sw.fr_544 cs.hr.lv_322 so.un.un_A00 hu.cy.en_543
+ 0x13002a19, 0x1b003108, 0x0400170c, 0x311b1c07, // mt.et.un_750 az.tr.un_430 sr.ru.un_530 id.tr.az_432
+ 0x17002904, 0x4a2b01ee, 0x2d0d09af, 0x04081007, // sl.sr.un_320 en.vi.yo_422 pl.cs.sk_655 be.uk.ru_432
+ 0x00002137, 0x13071f0c, 0x29080202, 0x08101107, // fa.un.un_B00 cy.it.et_543 da.no.sl_222 ro.be.uk_432
+ // [0970]
+ 0x2b005304, 0x03001607, 0x170a10a4, 0x08100708, // ht.vi.un_320 hr.nl.un_420 be.mk.sr_433 bg.be.uk_443
+ 0x09110d07, 0x03291208, 0x0a102507, 0x525506a4, // cs.ro.pl_432 hu.sl.nl_443 eu.lt.pt_432 de.rw.ha_433
+ 0x29002504, 0x2a094a08, 0x030208ec, 0x20042902, // eu.sl.un_320 yo.pl.mt_443 no.da.nl_644 sl.fi.sq_222
+ 0x1b310e08, 0x09001302, 0x322907a9, 0x0c1b0e05, // is.az.tr_443 bh.hi.un_220 it.sl.bs_544 is.tr.sv_333
+ // [0980]
+ 0x00002306, 0x040764a9, 0x1300020e, 0x53000d04, // ca.un.un_400 lg.it.fi_544 da.et.un_550 cs.ht.un_320
+ 0x2900101a, 0x072327a4, 0x0000122d, 0x08000422, // lt.sl.un_760 gd.ca.it_433 ur.un.un_A00 ru.uk.un_870
+ 0x29001605, 0x1f0853ee, 0x01002418, 0x2b00270c, // hr.sl.un_330 ht.no.cy_422 yi.iw.un_740 gd.vi.un_530
+ 0x041c1e12, 0x07000a0e, 0x29110f07, 0x072a11af, // ms.id.fi_654 mk.bg.un_550 lv.ro.sl_432 ro.mt.it_655
+ // [0990]
+ 0x27003b1a, 0x0a070412, 0x17000a1b, 0x08170aa4, // so.gd.un_760 ru.bg.mk_654 mk.sr.un_770 mk.sr.uk_433
+ 0x32311b09, 0x3100090d, 0x0d000914, 0x04000718, // tr.az.bs_444 pl.az.un_540 hi.ne.un_660 bg.ru.un_740
+ 0x3f002704, 0x1c0913a7, 0x063f0304, 0x1b003112, // gd.af.un_320 bh.hi.mr_532 nl.af.de_332 az.tr.un_640
+ 0x29311ba0, 0x18002104, 0x2a093b0d, 0x2a311b0c, // tr.az.sl_322 jw.ga.un_320 so.pl.mt_554 tr.az.mt_543
+ // [09a0]
+ 0x0a0717af, 0x3f0e06a4, 0x07311b0d, 0x1c091314, // sr.bg.mk_655 de.is.af_433 tr.az.it_554 bh.hi.mr_666
+ 0x2d0d0914, 0x17000a1a, 0x32001e04, 0x21001219, // pl.cs.sk_666 mk.sr.un_760 ms.bs.un_320 ur.fa.un_750
+ 0x0704100c, 0x1c0d09a4, 0x0e0b0a05, 0x00005206, // be.ru.bg_543 hi.ne.mr_433 pt.es.is_333 ha.un.un_400
+ 0x2a3b3112, 0x01203fa0, 0x3500550d, 0x06000804, // az.so.mt_654 af.sq.en_322 rw.zu.un_540 no.de.un_320
+ // [09b0]
+ 0x110417a4, 0x07040a02, 0x19000a09, 0x09292da0, // sr.ru.ro_433 mk.ru.bg_222 pt.gl.un_440 sk.sl.pl_322
+ 0x11040713, 0x07001119, 0x64003504, 0x04110607, // bg.ru.ro_665 ro.bg.un_750 zu.lg.un_320 de.ro.fi_432
+ 0x07170a0d, 0x05002012, 0x2a00072a, 0x2000051a, // mk.sr.bg_554 sq.fr.un_640 it.mt.un_970 fr.sq.un_760
+ 0x131b0ca0, 0x1b001319, 0x28002018, 0x08022aee, // sv.tr.et_322 et.tr.un_750 sq.sw.un_740 mt.da.no_422
+ // [09c0]
+ 0x1a0c290d, 0x1c0d1302, 0x091c0d0c, 0x35005222, // sl.sv.tl_554 bh.ne.mr_222 ne.mr.hi_543 ha.zu.un_870
+ 0x00002103, 0x27001e07, 0x0e061f07, 0x040810a4, // jw.un.un_300 ms.gd.un_420 cy.de.is_432 be.uk.ru_433
+ 0x09001c1a, 0x0f1c1fa4, 0x1e001c09, 0x2300251a, // mr.hi.un_760 cy.id.lv_433 id.ms.un_440 eu.ca.un_760
+ 0x07113512, 0x0f000519, 0x121b3112, 0x16321713, // zu.ro.it_654 fr.lv.un_750 az.tr.hu_654 sr.bs.hr_665
+ // [09d0]
+ 0x356b68ad, 0x00001606, 0x04351f0b, 0x055303a0, // ig.ceb.zu_643 hr.un.un_400 cy.zu.fi_542 nl.ht.fr_322
+ 0x10283512, 0x171305ad, 0x1e282a04, 0x21001222, // zu.sw.lt_654 fr.et.sr_643 mt.sw.ms_332 ur.fa.un_870
+ 0x12006407, 0x10682108, 0x1e001c04, 0x2a681fad, // lg.hu.un_420 jw.ig.lt_443 id.ms.un_320 cy.ig.mt_643
+ 0x03202d07, 0x01026ba0, 0x6827180d, 0x0c00121a, // sk.sq.nl_432 ceb.da.en_322 ga.gd.ig_554 hu.sv.un_760
+ // [09e0]
+ 0x070a11af, 0x1c002109, 0x31121ca0, 0x31001912, // ro.mk.bg_655 jw.id.un_440 id.hu.az_322 gl.az.un_640
+ 0x4a286409, 0x3f060308, 0x112d0d0d, 0x080e0608, // lg.sw.yo_444 nl.de.af_443 cs.sk.ro_554 de.is.no_443
+ 0x06002507, 0x12000a19, 0x040717ad, 0x011e2107, // eu.de.un_420 pt.hu.un_750 sr.bg.ru_643 jw.ms.en_432
+ 0x131e1c05, 0x06006b08, 0x28002014, 0x35271804, // id.ms.et_333 ceb.de.un_430 sq.sw.un_660 ga.gd.zu_332
+ // [09f0]
+ 0x06033fee, 0x3b6b1a07, 0x0d091311, 0x082102a4, // af.nl.de_422 tl.ceb.so_432 bh.hi.ne_653 da.jw.no_433
+ 0x08001e05, 0x1b000811, 0x35211814, 0x08001007, // ms.no.un_330 no.tr.un_630 ga.jw.zu_666 be.uk.un_420
+ 0x04001321, 0x104a0408, 0x1312040c, 0x10071208, // et.fi.un_860 fi.yo.lt_443 fi.hu.et_543 hu.it.lt_443
+ 0x1f0705ec, 0x0f090da4, 0x080c02ee, 0x03003f04, // fr.it.cy_644 cs.pl.lv_433 da.sv.no_422 af.nl.un_320
+ // [0a00]
+ 0x112d0d12, 0x1a001b22, 0x3f003202, 0x0000550a, // cs.sk.ro_654 tr.tl.un_870 bs.af.un_220 rw.un.un_500
+ 0x32052302, 0x2b000d04, 0x19231807, 0x1f001a04, // ca.fr.bs_222 cs.vi.un_320 ga.ca.gl_432 tl.cy.un_320
+ 0x080410a4, 0x12002d09, 0x1c002b05, 0x1b00312c, // be.ru.uk_433 sk.hu.un_440 vi.id.un_330 az.tr.un_990
+ 0x01002907, 0x1c000d08, 0x07000414, 0x00000315, // sl.en.un_420 ne.mr.un_430 ru.bg.un_660 nl.un.un_700
+ // [0a10]
+ 0x3f0603a4, 0x06000314, 0x03001f0d, 0x040a17a4, // nl.de.af_433 nl.de.un_660 cy.nl.un_540 sr.mk.ru_433
+ 0x0400100d, 0x01003f04, 0x1c00091b, 0x120519a6, // be.ru.un_540 af.en.un_320 hi.mr.un_770 gl.fr.hu_521
+ 0x10000814, 0x1800210c, 0x0f030cee, 0x13003119, // uk.be.un_660 fa.ar.un_530 sv.nl.lv_422 az.et.un_750
+ 0x1f2718af, 0x1a352707, 0x1b1101a4, 0x110710ad, // ga.gd.cy_655 gd.zu.tl_432 en.ro.tr_433 be.bg.ro_643
+ // [0a20]
+ 0x321716af, 0x0d002d19, 0x072a05a7, 0x321723a0, // hr.sr.bs_655 sk.cs.un_750 fr.mt.it_532 ca.sr.bs_322
+ 0x2a3555a9, 0x32170faf, 0x11000413, 0x1c000902, // rw.zu.mt_544 lv.sr.bs_655 ru.ro.un_650 hi.mr.un_220
+ 0x04001011, 0x08040a08, 0x00002706, 0x20001904, // be.ru.un_630 mk.ru.uk_443 gd.un.un_400 gl.sq.un_320
+ 0x0000682d, 0x12290407, 0x0a001714, 0x100f17a4, // ig.un.un_A00 fi.sl.hu_432 sr.mk.un_660 sr.lv.lt_433
+ // [0a30]
+ 0x040710ee, 0x0f3f3507, 0x1b2a31ad, 0x080c0212, // be.bg.ru_422 zu.af.lv_432 az.mt.tr_643 da.sv.no_654
+ 0x03060cee, 0x08020eec, 0x25001604, 0x28010c55, // sv.de.nl_422 is.da.no_644 hr.eu.un_320 sv.en.sw_442
+ 0x02000822, 0x1b6b1a12, 0x1c0d090c, 0x17005504, // no.da.un_870 tl.ceb.tr_654 hi.ne.mr_543 rw.sr.un_320
+ 0x123204ee, 0x0a001719, 0x0b0a23af, 0x2a000721, // fi.bs.hu_422 sr.mk.un_750 ca.pt.es_655 it.mt.un_860
+ // [0a40]
+ 0x35006422, 0x04001005, 0x07001004, 0x0d00130d, // lg.zu.un_870 be.ru.un_330 be.bg.un_320 bh.ne.un_540
+ 0x31006b04, 0x2a0f0714, 0x0a001c04, 0x182112a9, // ceb.az.un_320 it.lv.mt_666 id.pt.un_320 ur.fa.ar_544
+ 0x04000612, 0x00000b24, 0x64001b20, 0x17000119, // de.fi.un_640 bn.un.un_900 tr.lg.un_850 en.sr.un_750
+ 0x0800100d, 0x17070d07, 0x166b2dee, 0x6b1e1c05, // be.uk.un_540 cs.it.sr_432 sk.ceb.hr_422 id.ms.ceb_333
+ // [0a50]
+ 0x0c003104, 0x0c00060d, 0x04033f5a, 0x00003f0a, // az.sv.un_320 de.sv.un_540 af.nl.fi_553 af.un.un_500
+ 0x28006812, 0x0a23010c, 0x2d000d23, 0x252d5305, // ig.sw.un_640 en.ca.pt_543 cs.sk.un_880 ht.sk.eu_333
+ 0x32000308, 0x19530ba7, 0x2800550d, 0x3b000414, // nl.bs.un_430 es.ht.gl_532 rw.sw.un_540 fi.so.un_660
+ 0x0e0d2d12, 0x043b2555, 0x641a53a7, 0x29210909, // sk.cs.is_654 eu.so.fi_442 ht.tl.lg_532 pl.jw.sl_444
+ // [0a60]
+ 0x283b03ee, 0x091c1311, 0x3f253bad, 0x55001a02, // nl.so.sw_422 bh.mr.hi_653 so.eu.af_643 tl.rw.un_220
+ 0x0e080205, 0x2d0d2baf, 0x281c3107, 0x080a170b, // da.no.is_333 vi.cs.sk_655 az.id.sw_432 sr.mk.uk_542
+ 0x07110408, 0x10285208, 0x092a280c, 0x25643b11, // ru.ro.bg_443 ha.sw.lt_443 sw.mt.pl_543 so.lg.eu_653
+ 0x556428a4, 0x4a005520, 0x04005205, 0x04000304, // sw.lg.rw_433 rw.yo.un_850 ha.fi.un_330 nl.fi.un_320
+ // [0a70]
+ 0x100b1307, 0x551c5208, 0x1b002819, 0x0000522d, // et.es.lt_432 ha.id.rw_443 sw.tr.un_750 ha.un.un_A00
+ 0x0d001308, 0x1c0d13a0, 0x31001c04, 0x642d6807, // bh.ne.un_430 bh.ne.mr_322 id.az.un_320 ig.sk.lg_432
+ 0x55006b18, 0x110d3f04, 0x092d0d09, 0x0c2808a0, // ceb.rw.un_740 af.cs.ro_332 cs.sk.pl_444 no.sw.sv_322
+ 0x070408a4, 0x2528520c, 0x4a25550c, 0x04001021, // no.fi.it_433 ha.sw.eu_543 rw.eu.yo_543 be.ru.un_860
+ // [0a80]
+ 0x686407a4, 0x3f130408, 0x31000804, 0x0c002d12, // it.lg.ig_433 fi.et.af_443 no.az.un_320 sk.sv.un_640
+ 0x211e1c02, 0x3f000108, 0x2d0d35ec, 0x4a001e04, // id.ms.jw_222 en.af.un_430 zu.cs.sk_644 ms.yo.un_320
+ 0x292a5512, 0x09100d08, 0x172a0c08, 0x2d0d3514, // rw.mt.sl_654 cs.lt.pl_443 sv.mt.sr_443 zu.cs.sk_666
+ 0x55002823, 0x0b231908, 0x00001024, 0x211b1ea4, // sw.rw.un_880 gl.ca.es_443 lt.un.un_900 ms.tr.jw_433
+ // [0a90]
+ 0x0a10080c, 0x284a2105, 0x13000423, 0x28211cad, // uk.be.mk_543 jw.yo.sw_333 fi.et.un_880 id.jw.sw_643
+ 0x0a040709, 0x1708110c, 0x120b190c, 0x010705a0, // bg.ru.mk_444 ro.uk.sr_543 gl.es.hu_543 fr.it.en_322
+ 0x1e521ca4, 0x1a282714, 0x523f1c04, 0x180b280c, // id.ha.ms_433 gd.sw.tl_666 id.af.ha_332 sw.es.ga_543
+ 0x1000311b, 0x1b000813, 0x042d0d08, 0x101117ee, // az.lt.un_770 no.tr.un_650 cs.sk.fi_443 sr.ro.lt_422
+ // [0aa0]
+ 0x231811a4, 0x04522013, 0x08000214, 0x072519a0, // ro.ga.ca_433 sq.ha.fi_665 da.no.un_660 gl.eu.it_322
+ 0x2d005207, 0x040c1c04, 0x1e252007, 0x02001b18, // ha.sk.un_420 id.sv.fi_332 sq.eu.ms_432 tr.da.un_740
+ 0x20000c08, 0x03233b13, 0x080c020c, 0x11200408, // sv.sq.un_430 so.ca.nl_665 da.sv.no_543 fi.sq.ro_443
+ 0x0a17110c, 0x13090d09, 0x2d001904, 0x31321bec, // ro.sr.mk_543 ne.hi.bh_444 gl.sk.un_320 tr.bs.az_644
+ // [0ab0]
+ 0x250623a0, 0x0b0a290c, 0x0a171109, 0x25000705, // ca.de.eu_322 sl.pt.es_543 ro.sr.mk_444 it.eu.un_330
+ 0x35005519, 0x07040a09, 0x08310ca4, 0x0d2d2912, // rw.zu.un_750 mk.ru.bg_444 sv.az.no_433 sl.sk.cs_654
+ 0x1b0e2d07, 0x3b1b1aa9, 0x1600350c, 0x04201fa4, // sk.is.tr_432 tl.tr.so_544 zu.hr.un_530 cy.sq.fi_433
+ 0x52353b0c, 0x3528550c, 0x170a0713, 0x11001e04, // so.zu.ha_543 rw.sw.zu_543 bg.mk.sr_665 ms.ro.un_320
+ // [0ac0]
+ 0x160e0da4, 0x042d0d13, 0x2b20110c, 0x1e1c4aa0, // cs.is.hr_433 cs.sk.fi_665 ro.sq.vi_543 yo.id.ms_322
+ 0x3b0e0211, 0x063f03a4, 0x0e08020d, 0x2800181a, // da.is.so_653 nl.af.de_433 da.no.is_554 ga.sw.un_760
+ 0x080407ad, 0x68182712, 0x100408a7, 0x131035a0, // bg.ru.uk_643 gd.ga.ig_654 uk.ru.be_532 zu.lt.et_322
+ 0x680464a9, 0x27111807, 0x20003513, 0x0b0a19af, // lg.fi.ig_544 ga.ro.gd_432 zu.sq.un_650 gl.pt.es_655
+ // [0ad0]
+ 0x18002722, 0x25131bec, 0x21072aec, 0x2520190c, // gd.ga.un_870 tr.et.eu_644 mt.it.jw_644 gl.sq.eu_543
+ 0x00001e0f, 0x13000912, 0x35552512, 0x11271811, // ms.un.un_600 hi.bh.un_640 eu.rw.zu_654 ga.gd.ro_653
+ 0x282b6b02, 0x25000623, 0x04000714, 0x0a1711a4, // ceb.vi.sw_222 de.eu.un_880 bg.ru.un_660 ro.sr.mk_433
+ 0x041f2013, 0x2b00010c, 0x061e1ca9, 0x111f200c, // sq.cy.fi_665 en.vi.un_530 id.ms.de_544 sq.cy.ro_543
+ // [0ae0]
+ 0x042520a4, 0x00000224, 0x062718a7, 0x271118af, // sq.eu.fi_433 da.un.un_900 ga.gd.de_532 ga.ro.gd_655
+ 0x2309290c, 0x21190b0e, 0x03001c07, 0x17070a13, // sl.pl.ca_543 es.gl.jw_555 id.nl.un_420 mk.bg.sr_665
+ 0x1b352104, 0x091c210c, 0x1f000914, 0x23643509, // jw.zu.tr_332 jw.id.pl_543 pl.cy.un_660 zu.lg.ca_444
+ 0x0d1c0911, 0x1c0d0914, 0x1f002a07, 0x28553560, // hi.mr.ne_653 hi.ne.mr_666 mt.cy.un_420 zu.rw.sw_664
+ // [0af0]
+ 0x0a041712, 0x285268a4, 0x040a1709, 0x1c090d0c, // sr.ru.mk_654 ig.ha.sw_433 sr.mk.ru_444 ne.hi.mr_543
+ 0x01061fa7, 0x20351104, 0x271118ad, 0x0d0913a6, // cy.de.en_532 ro.zu.sq_332 ga.ro.gd_643 bh.hi.ne_521
+ 0x00002901, 0x2700182b, 0x02182711, 0x2818110c, // sl.un.un_200 ga.gd.un_980 gd.ga.da_653 ro.ga.sw_543
+ 0x04110aa4, 0x684a55ec, 0x31001b2b, 0x6e006b05, // mk.ro.ru_433 rw.yo.ig_644 tr.az.un_980 ceb.hmn.un_330
+ // [0b00]
+ 0x161a2d12, 0x0a071108, 0x041710a4, 0x1b3101a4, // sk.tl.hr_654 ro.bg.mk_443 be.sr.ru_433 en.az.tr_433
+ 0x09000d1a, 0x01000d0d, 0x31001b2c, 0x2b040707, // ne.hi.un_760 cs.en.un_540 tr.az.un_990 it.fi.vi_432
+ 0x1b5225a7, 0x25001a04, 0x294a255a, 0x1b000804, // eu.ha.tr_532 tl.eu.un_320 eu.yo.sl_553 no.tr.un_320
+ 0x041310af, 0x1152350c, 0x1225530d, 0x0e0f2a08, // lt.et.fi_655 zu.ha.ro_543 ht.eu.hu_554 mt.lv.is_443
+ // [0b10]
+ 0x0c1b310c, 0x1b0208a4, 0x25101a0c, 0x1e1c1a05, // az.tr.sv_543 no.da.tr_433 tl.lt.eu_543 tl.id.ms_333
+ 0x1307530b, 0x64285560, 0x11311bad, 0x1c002105, // ht.it.et_542 rw.sw.lg_664 tr.az.ro_643 jw.id.un_330
+ 0x0000202d, 0x251b4a5a, 0x0c1a6b0c, 0x1e1c520c, // sq.un.un_A00 yo.tr.eu_553 ceb.tl.sv_543 ha.id.ms_543
+ 0x1e1c6405, 0x0e2553a0, 0x11136ba4, 0x27350d11, // lg.id.ms_333 ht.eu.is_322 ceb.et.ro_433 cs.zu.gd_653
+ // [0b20]
+ 0x211e1c05, 0x31000b1b, 0x04211c08, 0x1b000104, // id.ms.jw_333 es.az.un_770 id.jw.fi_443 en.tr.un_320
+ 0x2d000d08, 0x282a010c, 0x07001704, 0x172902a7, // cs.sk.un_430 en.mt.sw_543 sr.bg.un_320 da.sl.sr_532
+ 0x353b0dad, 0x1e1c2aa9, 0x251206ad, 0x3f0306a4, // cs.so.zu_643 mt.id.ms_544 de.hu.eu_643 de.nl.af_433
+ 0x05001905, 0x2a2d6404, 0x3f032507, 0x0c00061a, // gl.fr.un_330 lg.sk.mt_332 eu.nl.af_432 de.sv.un_760
+ // [0b30]
+ 0x00001724, 0x52121807, 0x6b0a01a4, 0x04110713, // sr.un.un_900 ga.hu.ha_432 en.pt.ceb_433 bg.ro.ru_665
+ 0x201107a9, 0x27000e21, 0x05000112, 0x293216ec, // it.ro.sq_544 is.gd.un_860 en.fr.un_640 hr.bs.sl_644
+ 0x316b1baf, 0x052301af, 0x1e1c6b0c, 0x0d000922, // tr.ceb.az_655 en.ca.fr_655 ceb.id.ms_543 hi.ne.un_870
+ 0x03003f1b, 0x162d1209, 0x25285508, 0x28006407, // af.nl.un_770 hu.sk.hr_444 rw.sw.eu_443 lg.sw.un_420
+ // [0b40]
+ 0x0200081a, 0x0817045a, 0x25311b0d, 0x2a002d1a, // no.da.un_760 ru.sr.uk_553 tr.az.eu_554 sk.mt.un_760
+ 0x2900170e, 0x25311b13, 0x32251b13, 0x1a011bad, // sr.sl.un_550 tr.az.eu_665 tr.eu.bs_665 tr.en.tl_643
+ 0x17002d08, 0x130d1cac, 0x64002818, 0x53120cad, // sk.sr.un_430 mr.ne.bh_632 sw.lg.un_740 sv.hu.ht_643
+ 0x12004a08, 0x0d001909, 0x12004a05, 0x21011b08, // yo.hu.un_430 gl.cs.un_440 yo.hu.un_330 tr.en.jw_443
+ // [0b50]
+ 0x211e1ca4, 0x131a10a7, 0x28640507, 0x211218ec, // id.ms.jw_433 lt.tl.et_532 fr.lg.sw_432 ar.ur.fa_644
+ 0x18190a04, 0x0b001b0d, 0x16001f08, 0x23000705, // pt.gl.ga_332 tr.es.un_540 cy.hr.un_430 it.ca.un_330
+ 0x2500170d, 0x170d0f08, 0x25190b55, 0x07170aa4, // sr.eu.un_540 lv.cs.sr_443 es.gl.eu_442 mk.sr.bg_433
+ 0x050601ec, 0x16002d08, 0x64005512, 0x07170a04, // en.de.fr_644 sk.hr.un_430 rw.lg.un_640 mk.sr.bg_332
+ // [0b60]
+ 0x0c000e02, 0x211c1108, 0x1b000a04, 0x646b12ad, // is.sv.un_220 ro.id.jw_443 pt.tr.un_320 hu.ceb.lg_643
+ 0x0c000814, 0x11001f0e, 0x270118a7, 0x32001705, // no.sv.un_660 cy.ro.un_550 ga.en.gd_532 sr.bs.un_330
+ 0x231c550b, 0x07001002, 0x1800050d, 0x05190bad, // rw.id.ca_542 be.bg.un_220 fr.ga.un_540 es.gl.fr_643
+ 0x190405a0, 0x17001614, 0x2500190c, 0x0d001c0d, // fr.fi.gl_322 hr.sr.un_660 gl.eu.un_530 mr.ne.un_540
+ // [0b70]
+ 0x251f17a0, 0x04001022, 0x17002d0d, 0x08001019, // sr.cy.eu_322 be.ru.un_870 sk.sr.un_540 be.uk.un_750
+ 0x13000e08, 0x090d13ec, 0x18211260, 0x521c1eee, // is.et.un_430 bh.ne.hi_644 ur.fa.ar_664 ms.id.ha_422
+ 0x3f066ba0, 0x080410a9, 0x2b002d0c, 0x111a4aa0, // ceb.de.af_322 be.ru.uk_544 sk.vi.un_530 yo.tl.ro_322
+ 0x29172da4, 0x080704ec, 0x2a001c02, 0x08021faf, // sk.sr.sl_433 ru.bg.uk_644 id.mt.un_220 cy.da.no_655
+ // [0b80]
+ 0x32001702, 0x08020ca0, 0x08020fee, 0x0a040702, // sr.bs.un_220 sv.da.no_322 lv.da.no_422 bg.ru.mk_222
+ 0x091c0d08, 0x0900131a, 0x0d001c21, 0x07001109, // ne.mr.hi_443 bh.hi.un_760 mr.ne.un_860 ro.it.un_440
+ 0x0800041b, 0x17002914, 0x04003b12, 0x13040cad, // ru.uk.un_770 sl.sr.un_660 so.fi.un_640 sv.fi.et_643
+ 0x12211813, 0x281b53a4, 0x1b005214, 0x55003b07, // ar.fa.ur_665 ht.tr.sw_433 ha.tr.un_660 so.rw.un_420
+ // [0b90]
+ 0x06002519, 0x07000d08, 0x07171104, 0x19112308, // eu.de.un_750 cs.it.un_430 ro.sr.bg_332 ca.ro.gl_443
+ 0x64205355, 0x20005319, 0x11230760, 0x1b000e13, // ht.sq.lg_442 ht.sq.un_750 it.ca.ro_664 is.tr.un_650
+ 0x27001119, 0x00001c0a, 0x32000707, 0x170a04af, // ro.gd.un_750 id.un.un_500 it.bs.un_420 ru.mk.sr_655
+ 0x0a081708, 0x18002b07, 0x52003204, 0x09001319, // sr.uk.mk_443 vi.ga.un_420 bs.ha.un_320 bh.hi.un_750
+ // [0ba0]
+ 0x52000e08, 0x170804a4, 0x080235ec, 0x19000a0e, // is.ha.un_430 ru.uk.sr_433 zu.da.no_644 pt.gl.un_550
+ 0x23004a07, 0x0a07040b, 0x1e1c0ea0, 0x07001013, // yo.ca.un_420 ru.bg.mk_542 is.id.ms_322 be.bg.un_650
+ 0x170411ee, 0x1f00012a, 0x0b0c0812, 0x27006e08, // ro.ru.sr_422 en.cy.un_970 no.sv.es_654 hmn.gd.un_430
+ 0x0708100c, 0x040918a4, 0x2d010d07, 0x11190b04, // be.uk.bg_543 ga.pl.fi_433 cs.en.sk_432 es.gl.ro_332
+ // [0bb0]
+ 0x1b311ca0, 0x00006b0f, 0x1b07310c, 0x1c130d08, // id.az.tr_322 ceb.un.un_600 az.it.tr_543 ne.bh.mr_443
+ 0x1a006e12, 0x1b3b0708, 0x10000f1a, 0x0f001705, // hmn.tl.un_640 it.so.tr_443 lv.lt.un_760 sr.lv.un_330
+ 0x13090daf, 0x09121112, 0x07041705, 0x010a2304, // ne.hi.bh_655 ro.hu.pl_654 sr.ru.bg_333 ca.pt.en_332
+ 0x090d1c0c, 0x08020c0e, 0x12182112, 0x3f00011a, // mr.ne.hi_543 sv.da.no_555 fa.ar.ur_654 en.af.un_760
+ // [0bc0]
+ 0x1f070fa4, 0x2900320d, 0x091c0d11, 0x0428250c, // lv.it.cy_433 bs.sl.un_540 ne.mr.hi_653 eu.sw.fi_543
+ 0x1325040b, 0x0800021b, 0x2500041b, 0x070a08a4, // fi.eu.et_542 da.no.un_770 fi.eu.un_770 uk.mk.bg_433
+ 0x162d29a4, 0x2b001319, 0x2100121b, 0x0e000c08, // sl.sk.hr_433 et.vi.un_750 ur.fa.un_770 sv.is.un_430
+ 0x2b0421a4, 0x12054a07, 0x03005204, 0x32161702, // jw.fi.vi_433 yo.fr.hu_432 ha.nl.un_320 sr.hr.bs_222
+ // [0bd0]
+ 0x042825ad, 0x011308ee, 0x28042508, 0x0b2d1907, // eu.sw.fi_643 no.et.en_422 eu.fi.sw_443 gl.sk.es_432
+ 0x04282555, 0x03180612, 0x1b005312, 0x13000c12, // eu.sw.fi_442 de.ga.nl_654 ht.tr.un_640 sv.et.un_640
+ 0x280425a9, 0x01000d08, 0x00001706, 0x3b3f01ad, // eu.fi.sw_544 cs.en.un_430 sr.un.un_400 en.af.so_643
+ 0x28192512, 0x08020c55, 0x0e003b12, 0x55001b13, // eu.gl.sw_654 sv.da.no_442 so.is.un_640 tr.rw.un_650
+ // [0be0]
+ 0x351a1bad, 0x08171009, 0x170a07af, 0x063f0ca0, // tr.tl.zu_643 be.sr.uk_444 bg.mk.sr_655 sv.af.de_322
+ 0x17001013, 0x110501a4, 0x3b1e1c0e, 0x08023f05, // lt.sr.un_650 en.fr.ro_433 id.ms.so_555 af.da.no_333
+ 0x52003121, 0x31003b14, 0x24000121, 0x1c091305, // az.ha.un_860 so.az.un_660 iw.yi.un_860 bh.hi.mr_333
+ 0x25042812, 0x061f010b, 0x55256409, 0x27003514, // sw.fi.eu_654 en.cy.de_542 lg.eu.rw_444 zu.gd.un_660
+ // [0bf0]
+ 0x0400250d, 0x130f64a0, 0x216b1aec, 0x042825ec, // eu.fi.un_540 lg.lv.et_322 tl.ceb.jw_644 eu.sw.fi_644
+ 0x6b3b52ee, 0x09001c20, 0x051123af, 0x063f2512, // ha.so.ceb_422 mr.hi.un_850 ca.ro.fr_655 eu.af.de_654
+ 0x1f00351b, 0x112305a4, 0x08020c14, 0x552d0d13, // zu.cy.un_770 fr.ca.ro_433 sv.da.no_666 cs.sk.rw_665
+ 0x123f3baf, 0x00000303, 0x05005304, 0x1b001f13, // so.af.hu_655 nl.un.un_300 ht.fr.un_320 cy.tr.un_650
+
+ // [0c00]
+ 0x3f002109, 0x351a55a4, 0x1f002108, 0x25005507, // jw.af.un_440 rw.tl.zu_433 jw.cy.un_430 rw.eu.un_420
+ 0x06000419, 0x0f135504, 0x0b001c02, 0x040710ad, // fi.de.un_750 rw.et.lv_332 id.es.un_220 be.bg.ru_643
+ 0x2300030c, 0x213b1c02, 0x52000504, 0x00001f01, // nl.ca.un_530 id.so.jw_222 fr.ha.un_320 cy.un.un_200
+ 0x12001f23, 0x551b6bac, 0x17100808, 0x35001308, // cy.hu.un_880 ceb.tr.rw_632 uk.be.sr_443 et.zu.un_430
+ // [0c10]
+ 0x0d290a0b, 0x3f033baf, 0x55001b19, 0x20013fa0, // pt.sl.cs_542 so.nl.af_655 tr.rw.un_750 af.en.sq_322
+ 0x2100351a, 0x19000b1a, 0x110a10ad, 0x05555304, // zu.jw.un_760 es.gl.un_760 lt.pt.ro_643 ht.rw.fr_332
+ 0x32161707, 0x063f0307, 0x555305a4, 0x3f0306a9, // sr.hr.bs_432 nl.af.de_432 fr.ht.rw_433 de.nl.af_544
+ 0x09000d14, 0x190b0aa0, 0x03003519, 0x033f3bee, // ne.hi.un_660 pt.es.gl_322 zu.nl.un_750 so.af.nl_422
+ // [0c20]
+ 0x0c07010c, 0x02200807, 0x292d32a0, 0x32125505, // en.it.sv_543 no.sq.da_432 bs.sk.sl_322 rw.hu.bs_333
+ 0x080a0708, 0x3b09290c, 0x310420a7, 0x1a006b14, // bg.mk.uk_443 sl.pl.so_543 sq.fi.az_532 ceb.tl.un_660
+ 0x1c001304, 0x1a120fa4, 0x182112ec, 0x18122160, // bh.mr.un_320 lv.hu.tl_433 ur.fa.ar_644 fa.ur.ar_664
+ 0x1309270b, 0x09001c05, 0x0000640f, 0x07081709, // gd.pl.et_542 mr.hi.un_330 lg.un.un_600 sr.uk.bg_444
+ // [0c30]
+ 0x3f004a04, 0x170d29a4, 0x13000d14, 0x09130d12, // yo.af.un_320 sl.cs.sr_433 ne.bh.un_660 ne.bh.hi_654
+ 0x1f001113, 0x18203b08, 0x321701a4, 0x232501a4, // ro.cy.un_650 so.sq.ga_443 en.sr.bs_433 en.eu.ca_433
+ 0x07552a08, 0x11002508, 0x25001120, 0x122118ad, // mt.rw.it_443 eu.ro.un_430 ro.eu.un_850 ar.fa.ur_643
+ 0x04000c21, 0x092d08a0, 0x07040813, 0x182701a4, // sv.fi.un_860 no.sk.pl_322 uk.ru.bg_665 en.gd.ga_433
+ // [0c40]
+ 0x070804af, 0x18002713, 0x3f060eee, 0x25190b02, // ru.uk.bg_655 gd.ga.un_650 is.de.af_422 es.gl.eu_222
+ 0x120c08a0, 0x11002502, 0x090d1c14, 0x28355207, // no.sv.hu_322 eu.ro.un_220 mr.ne.hi_666 ha.zu.sw_432
+ 0x18122111, 0x18002719, 0x25111c12, 0x2b002513, // fa.ur.ar_653 gd.ga.un_750 id.ro.eu_654 eu.vi.un_650
+ 0x0604050c, 0x06003f05, 0x19120a07, 0x08000413, // fr.fi.de_543 af.de.un_330 pt.hu.gl_432 ru.uk.un_650
+ // [0c50]
+ 0x25001114, 0x170a1107, 0x0b0a19a4, 0x0000280a, // ro.eu.un_660 ro.mk.sr_432 gl.pt.es_433 sw.un.un_500
+ 0x06003505, 0x070501a9, 0x00006424, 0x06002304, // zu.de.un_330 en.fr.it_544 lg.un.un_900 ca.de.un_320
+ 0x12190b55, 0x533f0304, 0x0900280d, 0x5504280c, // es.gl.hu_442 nl.af.ht_332 sw.pl.un_540 sw.fi.rw_543
+ 0x04100813, 0x64216b08, 0x2700181a, 0x0d0f040b, // uk.be.ru_665 ceb.jw.lg_443 ga.gd.un_760 fi.lv.cs_542
+ // [0c60]
+ 0x3f2b35a0, 0x6b002809, 0x321e1c55, 0x01003508, // zu.vi.af_322 sw.ceb.un_440 id.ms.bs_442 zu.en.un_430
+ 0x1800272a, 0x522032a0, 0x041711ee, 0x07110a14, // gd.ga.un_970 bs.sq.ha_322 ro.sr.ru_422 mk.ro.bg_666
+ 0x1e211c04, 0x1c1309af, 0x64003519, 0x00000103, // id.jw.ms_332 hi.bh.mr_655 zu.lg.un_750 en.un.un_300
+ 0x040a1713, 0x17000a0c, 0x00001124, 0x3564550d, // sr.mk.ru_665 mk.sr.un_530 ro.un.un_900 rw.lg.zu_554
+ // [0c70]
+ 0x09321604, 0x211c5204, 0x3f031fee, 0x040810ec, // hr.bs.pl_332 ha.id.jw_332 cy.nl.af_422 be.uk.ru_644
+ 0x52002d1a, 0x17000807, 0x25001121, 0x033f1f55, // sk.ha.un_760 uk.sr.un_420 ro.eu.un_860 cy.af.nl_442
+ 0x091c0d13, 0x32002d07, 0x552853a4, 0x25001112, // ne.mr.hi_665 sk.bs.un_420 ht.sw.rw_433 ro.eu.un_640
+ 0x530b1112, 0x13090d13, 0x28355212, 0x100f1707, // ro.es.ht_654 ne.hi.bh_665 ha.zu.sw_654 sr.lv.lt_432
+ // [0c80]
+ 0x53110b07, 0x3235090c, 0x0e081f07, 0x122d0d0e, // es.ro.ht_432 pl.zu.bs_543 cy.no.is_432 cs.sk.hu_555
+ 0x552164a9, 0x033f28a4, 0x081102ec, 0x1c0d13ad, // lg.jw.rw_544 sw.af.nl_433 da.ro.no_644 bh.ne.mr_643
+ 0x4a52060c, 0x5521350c, 0x08270607, 0x1a006404, // de.ha.yo_543 zu.jw.rw_543 de.gd.no_432 lg.tl.un_320
+ 0x192301a4, 0x06080e07, 0x04000822, 0x201e1c07, // en.ca.gl_433 is.no.de_432 uk.ru.un_870 id.ms.sq_432
+ // [0c90]
+ 0x28211cee, 0x17040709, 0x050135a0, 0x00002806, // id.jw.sw_422 bg.ru.sr_444 zu.en.fr_322 sw.un.un_400
+ 0x081f0204, 0x170f100c, 0x52000d13, 0x5300551a, // da.cy.no_332 lt.lv.sr_543 cs.ha.un_650 rw.ht.un_760
+ 0x13210fa4, 0x0807100c, 0x13001c04, 0x170a0705, // lv.jw.et_433 be.bg.uk_543 id.et.un_320 bg.mk.sr_333
+ 0x250605a7, 0x080a17a4, 0x28001e08, 0x07002a04, // fr.de.eu_532 sr.mk.uk_433 ms.sw.un_430 mt.it.un_320
+ // [0ca0]
+ 0x10040812, 0x32000408, 0x3500280d, 0x32000b04, // uk.ru.be_654 fi.bs.un_430 sw.zu.un_540 es.bs.un_320
+ 0x0400111a, 0x08000512, 0x08021fa4, 0x6b18270d, // ro.ru.un_760 fr.no.un_640 cy.da.no_433 gd.ga.ceb_554
+ 0x68521ea0, 0x07000108, 0x35006807, 0x3f000307, // ms.ha.ig_322 en.it.un_430 ig.zu.un_420 nl.af.un_420
+ 0x27001821, 0x52280b07, 0x0a041105, 0x0c1204ad, // ga.gd.un_860 es.sw.ha_432 ro.ru.mk_333 fi.hu.sv_643
+ // [0cb0]
+ 0x13001814, 0x080704ad, 0x112953a0, 0x1a2a1e0c, // ga.et.un_660 ru.bg.uk_643 ht.sl.ro_322 ms.mt.tl_543
+ 0x0c00061b, 0x27001819, 0x07081009, 0x35005512, // de.sv.un_770 ga.gd.un_750 be.uk.bg_444 rw.zu.un_640
+ 0x01000618, 0x55642105, 0x550b530c, 0x211c1ead, // de.en.un_740 jw.lg.rw_333 ht.es.rw_543 ms.id.jw_643
+ 0x532d3bee, 0x530b11ec, 0x532921ee, 0x230e5255, // so.sk.ht_422 ro.es.ht_644 jw.sl.ht_422 ha.is.ca_442
+ // [0cc0]
+ 0x1c002113, 0x04071104, 0x210c03ee, 0x0800170b, // jw.id.un_650 ro.bg.ru_332 nl.sv.jw_422 sr.no.un_520
+ 0x0c070eee, 0x2d290d0e, 0x0c550611, 0x0900250d, // is.it.sv_422 cs.sl.sk_555 de.rw.sv_653 eu.pl.un_540
+ 0x2723180c, 0x0b00110d, 0x1c130902, 0x2d0d0caf, // ga.ca.gd_543 ro.es.un_540 hi.bh.mr_222 sv.cs.sk_655
+ 0x0f050408, 0x044a230c, 0x0d000923, 0x213b13ad, // fi.fr.lv_443 ca.yo.fi_543 hi.ne.un_880 et.so.jw_643
+ // [0cd0]
+ 0x04071709, 0x03053fee, 0x322117a0, 0x161710a6, // sr.bg.ru_444 af.fr.nl_422 sr.jw.bs_322 lt.sr.hr_521
+ 0x0b000a0e, 0x3f002105, 0x1b000f0b, 0x020c0807, // pt.es.un_550 jw.af.un_330 lv.tr.un_520 no.sv.da_432
+ 0x3b006e0c, 0x2d290d07, 0x03003f0c, 0x0000250a, // hmn.so.un_530 cs.sl.sk_432 af.nl.un_530 eu.un.un_500
+ 0x1000292a, 0x27101812, 0x00000824, 0x08041104, // sl.lt.un_970 ga.lt.gd_654 no.un.un_900 ro.ru.uk_332
+ // [0ce0]
+ 0x2d0d1905, 0x2800211a, 0x0a182712, 0x04211f05, // gl.cs.sk_333 jw.sw.un_760 gd.ga.pt_654 cy.jw.fi_333
+ 0x056e0aee, 0x080a1005, 0x07040aa9, 0x09005504, // pt.hmn.fr_422 be.mk.uk_333 mk.ru.bg_544 rw.pl.un_320
+ 0x17291f0c, 0x1a006b13, 0x2013075a, 0x0c001121, // cy.sl.sr_543 ceb.tl.un_650 it.et.sq_553 ro.sv.un_860
+ 0x0a041704, 0x1f520eec, 0x6b011aa4, 0x1a211e07, // sr.ru.mk_332 is.ha.cy_644 tl.en.ceb_433 ms.jw.tl_432
+ // [0cf0]
+ 0x09000d19, 0x1c1f21ad, 0x02000818, 0x07081708, // ne.hi.un_750 jw.cy.id_643 no.da.un_740 sr.uk.bg_443
+ 0x6b001a14, 0x293120ee, 0x0e001c08, 0x23000108, // tl.ceb.un_660 sq.az.sl_422 id.is.un_430 en.ca.un_430
+ 0x11000508, 0x041008a4, 0x03003f13, 0x080b1aa0, // fr.ro.un_430 uk.be.ru_433 af.nl.un_650 tl.es.no_322
+ 0x0f2a1f08, 0x09001311, 0x171011a0, 0x536410a7, // cy.mt.lv_443 bh.hi.un_630 ro.be.sr_322 lt.lg.ht_532
+ // [0d00]
+ 0x1c00250b, 0x06005314, 0x2a000708, 0x01641aee, // eu.id.un_520 ht.de.un_660 it.mt.un_430 tl.lg.en_422
+ 0x16111e04, 0x08022105, 0x3b132808, 0x19003f0c, // ms.ro.hr_332 jw.da.no_333 sw.et.so_443 af.gl.un_530
+ 0x182753a4, 0x1c1f23a7, 0x2a1629a4, 0x012b1a07, // ht.gd.ga_433 ca.cy.id_532 sl.hr.mt_433 tl.vi.en_432
+ 0x6b230b08, 0x3f0753a0, 0x08070413, 0x04111705, // es.ca.ceb_443 ht.it.af_322 ru.bg.uk_665 sr.ro.ru_333
+ // [0d10]
+ 0x3b00680d, 0x321617ec, 0x1000091b, 0x0800101b, // ig.so.un_540 sr.hr.bs_644 pl.lt.un_770 be.uk.un_770
+ 0x0d001c19, 0x10000414, 0x1629120c, 0x181e1c09, // mr.ne.un_750 ru.be.un_660 hu.sl.hr_543 id.ms.ga_444
+ 0x1b3f3107, 0x5300110b, 0x02000804, 0x1c211eee, // az.af.tr_432 ro.ht.un_520 no.da.un_320 ms.jw.id_422
+ 0x090d1c11, 0x2a091f12, 0x52552355, 0x6b1a210c, // mr.ne.hi_653 cy.pl.mt_654 ca.rw.ha_442 jw.tl.ceb_543
+ // [0d20]
+ 0x0e6411a7, 0x2b00050c, 0x27230d07, 0x20000804, // ro.lg.is_532 fr.vi.un_530 cs.ca.gd_432 no.sq.un_320
+ 0x3b00252a, 0x3b0908a4, 0x32001608, 0x6b1a2511, // eu.so.un_970 no.pl.so_433 hr.bs.un_430 eu.tl.ceb_653
+ 0x2a032807, 0x12005312, 0x3f250ea4, 0x09350808, // sw.nl.mt_432 ht.hu.un_640 is.eu.af_433 no.zu.pl_443
+ 0x551c1a0c, 0x1b0e2304, 0x212801a0, 0x32171655, // tl.id.rw_543 ca.is.tr_332 en.sw.jw_322 hr.sr.bs_442
+ // [0d30]
+ 0x2a3201ad, 0x040a0714, 0x1a520ead, 0x0900351a, // en.bs.mt_643 bg.mk.ru_666 is.ha.tl_643 zu.pl.un_760
+ 0x2d0a05a4, 0x06000308, 0x2d0d1ea0, 0x1a0a64a0, // fr.pt.sk_433 nl.de.un_430 ms.cs.sk_322 lg.pt.tl_322
+ 0x00000924, 0x1c214a11, 0x2b002102, 0x19645513, // pl.un.un_900 yo.jw.id_653 jw.vi.un_220 rw.lg.gl_665
+ 0x0917350c, 0x2a07520c, 0x1b281308, 0x556e210c, // zu.sr.pl_543 ha.it.mt_543 et.sw.tr_443 jw.hmn.rw_543
+ // [0d40]
+ 0x11040a13, 0x551f2809, 0x0900101a, 0x0b214a0c, // mk.ru.ro_665 sw.cy.rw_444 lt.pl.un_760 yo.jw.es_543
+ 0x5500041b, 0x100a17a4, 0x0a001004, 0x5235090d, // fi.rw.un_770 sr.mk.be_433 be.mk.un_320 pl.zu.ha_554
+ 0x2128350c, 0x52000808, 0x1800212a, 0x4a00202a, // zu.sw.jw_543 no.ha.un_430 fa.ar.un_970 sq.yo.un_970
+ 0x21002819, 0x32082da4, 0x641b35a4, 0x1b64280c, // sw.jw.un_750 sk.no.bs_433 zu.tr.lg_433 sw.lg.tr_543
+ // [0d50]
+ 0x355513ad, 0x21682a07, 0x52281705, 0x1b521c07, // et.rw.zu_643 mt.ig.jw_432 sr.sw.ha_333 id.ha.tr_432
+ 0x21000804, 0x10000412, 0x09001c02, 0x4a00551a, // no.jw.un_320 ru.be.un_640 id.pl.un_220 rw.yo.un_760
+ 0x11131a08, 0x3f1f09a4, 0x28114a05, 0x3f02080c, // tl.et.ro_443 pl.cy.af_433 yo.ro.sw_333 no.da.af_543
+ 0x0e1b2a12, 0x1c0d0955, 0x0e1f2a5a, 0x17040708, // mt.tr.is_654 hi.ne.mr_442 mt.cy.is_553 bg.ru.sr_443
+ // [0d60]
+ 0x53124a04, 0x1b3508a4, 0x13006819, 0x07531112, // yo.hu.ht_332 no.zu.tr_433 ig.et.un_750 ro.ht.it_654
+ 0x281b3513, 0x211c4a04, 0x2a1c1e0c, 0x04110a5a, // zu.tr.sw_665 yo.id.jw_332 ms.id.mt_543 mk.ro.ru_553
+ 0x4a2504a4, 0x0a071708, 0x1b00350d, 0x181f2713, // fi.eu.yo_433 sr.bg.mk_443 zu.tr.un_540 gd.cy.ga_665
+ 0x13020812, 0x1e1c21ee, 0x25071ca4, 0x0e2a0d07, // no.da.et_654 jw.id.ms_422 id.it.eu_433 cs.mt.is_432
+ // [0d70]
+ 0x08040712, 0x1300351b, 0x090d1cec, 0x1a6b5513, // bg.ru.uk_654 zu.et.un_770 mr.ne.hi_644 rw.ceb.tl_665
+ 0x28033509, 0x2a351f12, 0x23190b0d, 0x081b12a9, // zu.nl.sw_444 cy.zu.mt_654 es.gl.ca_554 hu.tr.no_544
+ 0x1700090c, 0x292808a0, 0x641e1c08, 0x08020aa4, // pl.sr.un_530 no.sw.sl_322 id.ms.lg_443 pt.da.no_433
+ 0x3b1b060d, 0x2d05210c, 0x3b1b3202, 0x2d0d210c, // de.tr.so_554 jw.fr.sk_543 bs.tr.so_222 jw.cs.sk_543
+ // [0d80]
+ 0x1b0452ad, 0x0f1b12a7, 0x35041eee, 0x4a2052ec, // ha.fi.tr_643 hu.tr.lv_532 ms.fi.zu_422 ha.sq.yo_644
+ 0x1a286455, 0x2a001108, 0x0900130d, 0x2d04680c, // lg.sw.tl_442 ro.mt.un_430 bh.hi.un_540 ig.fi.sk_543
+ 0x0f000104, 0x21281c0c, 0x0652350c, 0x21001e04, // en.lv.un_320 id.sw.jw_543 zu.ha.de_543 ms.jw.un_320
+ 0x2d6435a4, 0x1e091ca0, 0x1000081a, 0x10070a04, // zu.lg.sk_433 id.pl.ms_322 uk.be.un_760 mk.bg.be_332
+ // [0d90]
+ 0x07040a0c, 0x08041105, 0x3b1e1c04, 0x352821a0, // mk.ru.bg_543 ro.ru.uk_333 id.ms.so_332 jw.sw.zu_322
+ 0x11080705, 0x321b3155, 0x2568060c, 0x1b090d07, // bg.uk.ro_333 az.tr.bs_442 de.ig.eu_543 cs.pl.tr_432
+ 0x101c2ba9, 0x0912350c, 0x21534aa7, 0x11001602, // vi.id.lt_544 zu.hu.pl_543 yo.ht.jw_532 hr.ro.un_220
+ 0x4a100aa7, 0x0a0711af, 0x070a1705, 0x12351b60, // pt.lt.yo_532 ro.bg.mk_655 sr.mk.bg_333 tr.zu.hu_664
+ // [0da0]
+ 0x1c0d090d, 0x0b001919, 0x3b1025a4, 0x3f031208, // hi.ne.mr_554 gl.es.un_750 eu.lt.so_433 hu.nl.af_443
+ 0x1600320d, 0x201e2104, 0x2d0d520c, 0x2d0d1005, // bs.hr.un_540 jw.ms.sq_332 ha.cs.sk_543 lt.cs.sk_333
+ 0x16321704, 0x111b12a9, 0x13124aa4, 0x1c211ea9, // sr.bs.hr_332 hu.tr.ro_544 yo.hu.et_433 ms.jw.id_544
+ 0x170804ec, 0x1b112aaf, 0x08070a04, 0x121b0107, // ru.uk.sr_644 mt.ro.tr_655 mk.bg.uk_332 en.tr.hu_432
+ // [0db0]
+ 0x0e004a0e, 0x00005501, 0x0c120802, 0x081707a4, // yo.is.un_550 rw.un.un_200 no.hu.sv_222 bg.sr.uk_433
+ 0x070a04ad, 0x00000f2d, 0x0708110c, 0x0000291c, // ru.mk.bg_643 lv.un.un_A00 ro.uk.bg_543 sl.un.un_800
+ 0x233555ee, 0x52133ba0, 0x00000f03, 0x6435550e, // rw.zu.ca_422 so.et.ha_322 lv.un.un_300 rw.zu.lg_555
+ 0x09001c0c, 0x0a170409, 0x200f1308, 0x18000e14, // mr.hi.un_530 ru.sr.mk_444 et.lv.sq_443 is.ga.un_660
+ // [0dc0]
+ 0x0a071107, 0x00000f01, 0x3f060305, 0x0f3b100b, // ro.bg.mk_432 lv.un.un_200 nl.de.af_333 lt.so.lv_542
+ 0x55643508, 0x55002813, 0x1e1c1ba4, 0x17000a22, // zu.lg.rw_443 sw.rw.un_650 tr.id.ms_433 mk.sr.un_870
+ 0x07041108, 0x110a1705, 0x0a1711ad, 0x052d0d04, // ro.ru.bg_443 sr.mk.ro_333 ro.sr.mk_643 cs.sk.fr_332
+ 0x2d001702, 0x17070a5a, 0x101c1ea0, 0x32173ba0, // sr.sk.un_220 mk.bg.sr_553 ms.id.lt_322 so.sr.bs_322
+ // [0dd0]
+ 0x21001829, 0x11002104, 0x1c091307, 0x0a0704a9, // ar.fa.un_960 jw.ro.un_320 bh.hi.mr_432 ru.bg.mk_544
+ 0x01230b05, 0x3b020fa0, 0x35215505, 0x11000b05, // es.ca.en_333 lv.da.so_322 rw.jw.zu_333 es.ro.un_330
+ 0x13006b19, 0x030553a0, 0x4a133b55, 0x122813ec, // ceb.et.un_750 ht.fr.nl_322 so.et.yo_442 et.sw.hu_644
+ 0x0a001007, 0x3b006b2b, 0x2500130d, 0x1117080c, // be.mk.un_420 ceb.so.un_980 et.eu.un_540 uk.sr.ro_543
+ // [0de0]
+ 0x11000518, 0x0e02080c, 0x3b6b6412, 0x1a6e200c, // fr.ro.un_740 no.da.is_543 lg.ceb.so_654 sq.hmn.tl_543
+ 0x06003b14, 0x08001c05, 0x1c081e08, 0x1b3b1c55, // so.de.un_660 id.no.un_330 ms.no.id_443 id.so.tr_442
+ 0x3f03080c, 0x270e0812, 0x070a0808, 0x64005504, // no.nl.af_543 no.is.gd_654 uk.mk.bg_443 rw.lg.un_320
+ 0x130e1b55, 0x3500080d, 0x2d000919, 0x64003b18, // tr.is.et_442 no.zu.un_540 pl.sk.un_750 so.lg.un_740
+ // [0df0]
+ 0x122d1812, 0x10000f1b, 0x0e030604, 0x033f06af, // ga.sk.hu_654 lv.lt.un_770 de.nl.is_332 de.af.nl_655
+ 0x1300092a, 0x080c2107, 0x00003b0a, 0x033f25a4, // hi.bh.un_970 jw.sv.no_432 so.un.un_500 eu.af.nl_433
+ 0x133b04ec, 0x10093f05, 0x12000e13, 0x033f06ee, // fi.so.et_644 af.pl.lt_333 is.hu.un_650 de.af.nl_422
+ 0x023f080c, 0x08041707, 0x12000e0e, 0x0a00070c, // no.af.da_543 sr.ru.uk_432 is.hu.un_550 bg.mk.un_530
+ // [0e00]
+ 0x0d1c095a, 0x10001a08, 0x04081012, 0x03286404, // hi.mr.ne_553 tl.lt.un_430 be.uk.ru_654 lg.sw.nl_332
+ 0x092d0d13, 0x3b006422, 0x0000120a, 0x2d0d01af, // cs.sk.pl_665 lg.so.un_870 hu.un.un_500 en.cs.sk_655
+ 0x212305ec, 0x03643ba4, 0x0706010c, 0x11070a09, // fr.ca.jw_644 so.lg.nl_433 en.de.it_543 mk.bg.ro_444
+ 0x0b0d2dad, 0x281c21a0, 0x64136b07, 0x20280111, // sk.cs.es_643 jw.id.sw_322 ceb.et.lg_432 en.sw.sq_653
+ // [0e10]
+ 0x0d091ca4, 0x04001012, 0x07040860, 0x110c08a0, // mr.hi.ne_433 lt.fi.un_640 uk.ru.bg_664 no.sv.ro_322
+ 0x12002d1a, 0x0b00190c, 0x31002a1a, 0x17070a0c, // sk.hu.un_760 gl.es.un_530 mt.az.un_760 mk.bg.sr_543
+ 0x1c00130e, 0x3f060304, 0x1c00131b, 0x00000b0a, // bh.mr.un_550 nl.de.af_332 bh.mr.un_770 es.un.un_500
+ 0x02000807, 0x08001714, 0x3b2127ac, 0x2d18120c, // no.da.un_420 sr.uk.un_660 gd.jw.so_632 hu.ga.sk_543
+ // [0e20]
+ 0x0e000804, 0x2a080605, 0x050123ee, 0x08020caf, // no.is.un_320 de.no.mt_333 ca.en.fr_422 sv.da.no_655
+ 0x0e122d0b, 0x2d0d010c, 0x0f00530d, 0x0e002a2b, // sk.hu.is_542 en.cs.sk_543 ht.lv.un_540 mt.is.un_980
+ 0x32170c05, 0x07000e0e, 0x210812ad, 0x3b18640e, // sv.sr.bs_333 is.it.un_550 hu.no.jw_643 lg.ga.so_555
+ 0x0e0807af, 0x2900530d, 0x3b290da4, 0x53005202, // it.no.is_655 ht.sl.un_540 cs.sl.so_433 ha.ht.un_220
+ // [0e30]
+ 0x041128a9, 0x190a1111, 0x092811ec, 0x0d1309ad, // sw.ro.fi_544 ro.pt.gl_653 ro.sw.pl_644 hi.bh.ne_643
+ 0x0e080260, 0x01005304, 0x28091112, 0x10000507, // da.no.is_664 ht.en.un_320 ro.pl.sw_654 fr.lt.un_420
+ 0x016b1ba0, 0x11003b29, 0x29090da4, 0x060c0204, // tr.ceb.en_322 so.ro.un_960 cs.pl.sl_433 da.sv.de_332
+ 0x0e093ba9, 0x281168a0, 0x2a0c3b60, 0x16290da4, // so.pl.is_544 ig.ro.sw_322 so.sv.mt_664 cs.sl.hr_433
+ // [0e40]
+ 0x18080213, 0x17000608, 0x3b2a09ec, 0x131b0c08, // da.no.ga_665 de.sr.un_430 pl.mt.so_644 sv.tr.et_443
+ 0x0d091108, 0x09113baf, 0x080203af, 0x10115208, // ro.pl.cs_443 so.ro.pl_655 nl.da.no_655 ha.ro.lt_443
+ 0x23002519, 0x0400110d, 0x170208a4, 0x28316b08, // eu.ca.un_750 ro.ru.un_540 no.da.sr_433 ceb.az.sw_443
+ 0x290c0da4, 0x64005522, 0x3b2d115a, 0x03000222, // cs.sv.sl_433 rw.lg.un_870 ro.sk.so_553 da.nl.un_870
+ // [0e50]
+ 0x3f071208, 0x0e080208, 0x08090207, 0x162a5507, // hu.it.af_443 da.no.is_443 da.pl.no_432 rw.mt.hr_432
+ 0x0832165a, 0x0e3b0814, 0x13000e05, 0x5303050c, // hr.bs.no_553 no.so.is_666 is.et.un_330 fr.nl.ht_543
+ 0x03193513, 0x4a001c04, 0x35000804, 0x1c0913a9, // zu.gl.nl_665 id.yo.un_320 no.zu.un_320 bh.hi.mr_544
+ 0x0c0206af, 0x06080e08, 0x321935a4, 0x25020708, // de.da.sv_655 is.no.de_443 zu.gl.bs_433 it.da.eu_443
+ // [0e60]
+ 0x21001c12, 0x06033f12, 0x08026eac, 0x10001704, // id.jw.un_640 af.nl.de_654 hmn.da.no_632 sr.lt.un_320
+ 0x1b100aaf, 0x19001107, 0x29080260, 0x19000a1b, // pt.lt.tr_655 ro.gl.un_420 da.no.sl_664 pt.gl.un_770
+ 0x043b1112, 0x07041002, 0x02061802, 0x04170a05, // ro.so.fi_654 be.ru.bg_222 ga.de.da_222 mk.sr.ru_333
+ 0x07001208, 0x21001c0c, 0x09110d11, 0x19230b07, // hu.it.un_430 id.jw.un_530 cs.ro.pl_653 es.ca.gl_432
+ // [0e70]
+ 0x09000d09, 0x04113ba9, 0x11003202, 0x090411ad, // ne.hi.un_440 so.ro.fi_544 bs.ro.un_220 ro.fi.pl_643
+ 0x3f190a14, 0x284a68a9, 0x1805535e, 0x1c1a0a07, // pt.gl.af_666 ig.yo.sw_544 ht.fr.ga_652 pt.tl.id_432
+ 0x32171613, 0x20000104, 0x06003b12, 0x11072905, // hr.sr.bs_665 en.sq.un_320 so.de.un_640 sl.it.ro_333
+ 0x2a00060e, 0x1f211aee, 0x6b1a53ec, 0x020b08a4, // de.mt.un_550 tl.jw.cy_422 ht.tl.ceb_644 no.es.da_433
+ // [0e80]
+ 0x09004a04, 0x13091c60, 0x0100240c, 0x0300061a, // yo.pl.un_320 mr.hi.bh_664 yi.iw.un_530 de.nl.un_760
+ 0x083b0c04, 0x02080e5a, 0x08000421, 0x1a6b5312, // sv.so.no_332 is.no.da_553 ru.uk.un_860 ht.ceb.tl_654
+ 0x090d1312, 0x0c041313, 0x3f031fec, 0x2900091a, // bh.ne.hi_654 et.fi.sv_665 cy.nl.af_644 pl.sl.un_760
+ 0x64005505, 0x06001f22, 0x07041107, 0x0f2a53a0, // rw.lg.un_330 cy.de.un_870 ro.ru.bg_432 ht.mt.lv_322
+ // [0e90]
+ 0x1c0d09ac, 0x3b000c13, 0x0d181209, 0x122d0d60, // hi.ne.mr_632 sv.so.un_650 hu.ga.cs_444 cs.sk.hu_664
+ 0x05070aa0, 0x253528a7, 0x0e0c1107, 0x0a0411a4, // pt.it.fr_322 sw.zu.eu_532 ro.sv.is_432 ro.ru.mk_433
+ 0x0c270ea9, 0x0500231b, 0x1e1c20a4, 0x0e00251a, // is.gd.sv_544 ca.fr.un_770 sq.id.ms_433 eu.is.un_760
+ 0x23033f07, 0x52006b1a, 0x0300011a, 0x10001702, // af.nl.ca_432 ceb.ha.un_760 en.nl.un_760 sr.lt.un_220
+ // [0ea0]
+ 0x06005514, 0x53002d04, 0x07171107, 0x19062709, // rw.de.un_660 sk.ht.un_320 ro.sr.bg_432 gd.de.gl_444
+ 0x11002507, 0x18211214, 0x041008af, 0x532d0dad, // eu.ro.un_420 ur.fa.ar_666 uk.be.ru_655 cs.sk.ht_643
+ 0x0c070e08, 0x162d0d64, 0x271808a0, 0x0a10170d, // is.it.sv_443 cs.sk.hr_762 no.ga.gd_322 sr.be.mk_554
+ 0x05012a07, 0x2d3f0d11, 0x532d0dec, 0x0e1306a4, // mt.en.fr_432 cs.af.sk_653 cs.sk.ht_644 de.et.is_433
+ // [0eb0]
+ 0x09202d0c, 0x53002d13, 0x532d0d0c, 0x04100a02, // sk.sq.pl_543 sk.ht.un_650 cs.sk.ht_543 mk.be.ru_222
+ 0x0d091c14, 0x522d53a4, 0x0d000904, 0x02050807, // mr.hi.ne_666 ht.sk.ha_433 hi.ne.un_320 no.fr.da_432
+ 0x0e080609, 0x12000807, 0x0a000504, 0x3b09110c, // de.no.is_444 no.hu.un_420 fr.pt.un_320 ro.pl.so_543
+ 0x13043ba4, 0x2d0d3fa9, 0x2d290909, 0x09000d08, // so.fi.et_433 af.cs.sk_544 pl.sl.sk_444 ne.hi.un_430
+ // [0ec0]
+ 0x190a2d14, 0x3f005313, 0x0c081308, 0x00000f0a, // sk.pt.gl_666 ht.af.un_650 et.no.sv_443 lv.un.un_500
+ 0x1000521a, 0x09000d04, 0x043552a4, 0x1e080c02, // ha.lt.un_760 ne.hi.un_320 ha.zu.fi_433 sv.no.ms_222
+ 0x3b00641a, 0x1a006b0d, 0x1800120e, 0x0c1208a4, // lg.so.un_760 ceb.tl.un_540 ur.ar.un_550 no.hu.sv_433
+ 0x170304ad, 0x03050cee, 0x0a001908, 0x32092da4, // fi.nl.sr_643 sv.fr.nl_422 gl.pt.un_430 sk.pl.bs_433
+ // [0ed0]
+ 0x06080205, 0x171635a0, 0x2d3f3bec, 0x013502a0, // da.no.de_333 zu.hr.sr_322 so.af.sk_644 da.zu.en_322
+ 0x4a3b640d, 0x250b19ec, 0x16292d0c, 0x0d002d22, // lg.so.yo_554 gl.es.eu_644 sk.sl.hr_543 sk.cs.un_870
+ 0x18080209, 0x09251fad, 0x190a5309, 0x53200aa4, // da.no.ga_444 cy.eu.pl_643 ht.pt.gl_444 pt.sq.ht_433
+ 0x06005308, 0x08000213, 0x12000e19, 0x230535ee, // ht.de.un_430 da.no.un_650 is.hu.un_750 zu.fr.ca_422
+ // [0ee0]
+ 0x32002d08, 0x1600290c, 0x251e1c11, 0x3f291b55, // sk.bs.un_430 sl.hr.un_530 id.ms.eu_653 tr.sl.af_442
+ 0x07010b08, 0x0a052aee, 0x1c0913ad, 0x051b53ec, // es.en.it_443 mt.fr.pt_422 bh.hi.mr_643 ht.tr.fr_644
+ 0x103f0312, 0x1103050b, 0x130464ad, 0x190a0507, // nl.af.lt_654 fr.nl.ro_542 lg.fi.et_643 fr.pt.gl_432
+ 0x0a080208, 0x1c090d14, 0x526421af, 0x00001715, // da.no.pt_443 ne.hi.mr_666 jw.lg.ha_655 sr.un.un_700
+ // [0ef0]
+ 0x1c130905, 0x17290d07, 0x64003b21, 0x0b1918a9, // hi.bh.mr_333 cs.sl.sr_432 so.lg.un_860 ga.gl.es_544
+ 0x201b3109, 0x171008a9, 0x13046413, 0x6e311ba9, // az.tr.sq_444 uk.be.sr_544 lg.fi.et_665 tr.az.hmn_544
+ 0x20236e12, 0x5535640c, 0x02000c0e, 0x091f6b04, // hmn.ca.sq_654 lg.zu.rw_543 sv.da.un_550 ceb.cy.pl_332
+ 0x0d002d1a, 0x16321760, 0x201b1c0c, 0x201025a4, // sk.cs.un_760 sr.bs.hr_664 id.tr.sq_543 eu.lt.sq_433
+ // [0f00]
+ 0x0d0f10a9, 0x08021214, 0x0c00130e, 0x19530b0c, // lt.lv.cs_544 hu.da.no_666 et.sv.un_550 es.ht.gl_543
+ 0x0a171007, 0x3f060c09, 0x0c020812, 0x02000520, // be.sr.mk_432 sv.de.af_444 no.da.sv_654 fr.da.un_850
+ 0x55321602, 0x122d0d04, 0x0d1c09a4, 0x1b040e04, // hr.bs.rw_222 cs.sk.hu_332 hi.mr.ne_433 is.fi.tr_332
+ 0x03110511, 0x55526408, 0x090d13a6, 0x091c0d07, // fr.ro.nl_653 lg.ha.rw_443 bh.ne.hi_521 ne.mr.hi_432
+ // [0f10]
+ 0x030205af, 0x11000519, 0x0d001304, 0x0a071112, // fr.da.nl_655 fr.ro.un_750 bh.ne.un_320 ro.bg.mk_654
+ 0x4a0811ee, 0x181f27ee, 0x04081109, 0x271f1008, // ro.no.yo_422 gd.cy.ga_422 ro.uk.ru_444 lt.cy.gd_443
+ 0x21556411, 0x0f046b0c, 0x3b033fa9, 0x091c0dad, // lg.rw.jw_653 ceb.fi.lv_543 af.nl.so_544 ne.mr.hi_643
+ 0x2a350702, 0x6b551aa4, 0x1c100fec, 0x521c6407, // it.zu.mt_222 tl.rw.ceb_433 lv.lt.id_644 lg.id.ha_432
+ // [0f20]
+ 0x292d0d09, 0x090d1cad, 0x1c001323, 0x0000532d, // cs.sk.sl_444 mr.ne.hi_643 bh.mr.un_880 ht.un.un_A00
+ 0x181221ec, 0x17081007, 0x2d1e1c04, 0x04001704, // fa.ur.ar_644 be.uk.sr_432 id.ms.sk_332 sr.ru.un_320
+ 0x0900290c, 0x0f001c04, 0x0d002505, 0x1805270c, // sl.pl.un_530 id.lv.un_320 eu.cs.un_330 gd.fr.ga_543
+ 0x033f0eee, 0x063225a0, 0x1707110c, 0x17000423, // is.af.nl_422 eu.bs.de_322 ro.bg.sr_543 ru.sr.un_880
+ // [0f30]
+ 0x1c09120c, 0x04182712, 0x050a2305, 0x53002104, // hu.pl.id_543 gd.ga.fi_654 ca.pt.fr_333 jw.ht.un_320
+ 0x27101f0e, 0x00001803, 0x0c110713, 0x180127ec, // cy.lt.gd_555 ga.un.un_300 it.ro.sv_665 gd.en.ga_644
+ 0x35001c07, 0x2d0d09ee, 0x09001c11, 0x071108a0, // id.zu.un_420 pl.cs.sk_422 mr.hi.un_630 no.ro.it_322
+ 0x1c1035a0, 0x0d000913, 0x00002503, 0x01091fa9, // zu.lt.id_322 pl.cs.un_650 eu.un.un_300 cy.pl.en_544
+ // [0f40]
+ 0x09001f2a, 0x3b18550c, 0x25521b09, 0x21551c12, // cy.pl.un_970 rw.ga.so_543 tr.ha.eu_444 id.rw.jw_654
+ 0x1b0c555a, 0x2864550c, 0x3b21030c, 0x0c4a1105, // rw.sv.tr_553 rw.lg.sw_543 nl.jw.so_543 ro.yo.sv_333
+ 0x2d0d04a0, 0x3b28210d, 0x0d092d11, 0x2d2305ad, // fi.cs.sk_322 jw.sw.so_554 sk.pl.cs_653 fr.ca.sk_643
+ 0x092d0d14, 0x23520705, 0x32002909, 0x2300191a, // cs.sk.pl_666 it.ha.ca_333 sl.bs.un_440 gl.ca.un_760
+ // [0f50]
+ 0x68003b22, 0x10170aa9, 0x10000409, 0x09531212, // so.ig.un_870 mk.sr.be_544 ru.be.un_440 hu.ht.pl_654
+ 0x0d0805ad, 0x071008ec, 0x04002707, 0x11035508, // fr.no.cs_643 uk.be.bg_644 gd.fi.un_420 rw.nl.ro_443
+ 0x1e18270c, 0x286468ad, 0x17071112, 0x1f1101a7, // gd.ga.ms_543 ig.lg.sw_643 ro.it.sr_654 en.ro.cy_532
+ 0x523f3baf, 0x21002021, 0x105232a0, 0x250428a9, // so.af.ha_655 sq.jw.un_860 bs.ha.lt_322 sw.fi.eu_544
+ // [0f60]
+ 0x01002421, 0x521f2509, 0x35212304, 0x552052a0, // yi.iw.un_860 eu.cy.ha_444 ca.jw.zu_332 ha.sq.rw_322
+ 0x1a5213ec, 0x282d09af, 0x2d0d09a9, 0x0600030d, // et.ha.tl_644 pl.sk.sw_655 pl.cs.sk_544 nl.de.un_540
+ 0x6b1e1ca9, 0x523b55af, 0x121b2804, 0x28076e08, // id.ms.ceb_544 rw.so.ha_655 sw.tr.hu_332 hmn.it.sw_443
+ 0x3f062da0, 0x23052aa4, 0x28001002, 0x110c070c, // sk.de.af_322 mt.fr.ca_433 lt.sw.un_220 it.sv.ro_543
+ // [0f70]
+ 0x1b001f21, 0x321a6b0c, 0x04002822, 0x2a5327a0, // cy.tr.un_860 ceb.tl.bs_543 sw.fi.un_870 gd.ht.mt_322
+ 0x2d293ba4, 0x080704a4, 0x5232170c, 0x011105a4, // so.sl.sk_433 ru.bg.uk_433 sr.bs.ha_543 fr.ro.en_433
+ 0x230725a9, 0x2a072509, 0x070c110c, 0x033553ac, // eu.it.ca_544 eu.it.mt_444 ro.sv.it_543 ht.zu.nl_632
+ 0x53000804, 0x062507a4, 0x11130707, 0x3f000909, // no.ht.un_320 it.eu.de_433 it.et.ro_432 pl.af.un_440
+ // [0f80]
+ 0x0a0711ad, 0x1308020d, 0x063f0305, 0x250409af, // ro.bg.mk_643 da.no.et_554 nl.af.de_333 pl.fi.eu_655
+ 0x0b00280c, 0x13001c0c, 0x1e033f55, 0x12000d0d, // sw.es.un_530 mr.bh.un_530 af.nl.ms_442 cs.hu.un_540
+ 0x10040a07, 0x09005204, 0x3f0608af, 0x0c2a0b09, // mk.ru.be_432 ha.pl.un_320 no.de.af_655 es.mt.sv_444
+ 0x05201160, 0x1c0d1312, 0x64005514, 0x090b06a7, // ro.sq.fr_664 bh.ne.mr_654 rw.lg.un_660 de.es.pl_532
+ // [0f90]
+ 0x09003513, 0x0c0208ec, 0x1107250d, 0x16100da7, // zu.pl.un_650 no.da.sv_644 eu.it.ro_554 cs.lt.hr_532
+ 0x21251ca0, 0x060b095a, 0x080c02af, 0x13000d05, // id.eu.jw_322 pl.es.de_553 da.sv.no_655 ne.bh.un_330
+ 0x09110b09, 0x211218ad, 0x286b4a0b, 0x1c3b1eac, // es.ro.pl_444 ar.ur.fa_643 yo.ceb.sw_542 ms.so.id_632
+ 0x07000a19, 0x684a09a9, 0x21002708, 0x11282da9, // mk.bg.un_750 pl.yo.ig_544 gd.jw.un_430 sk.sw.ro_544
+ // [0fa0]
+ 0x1e1c200c, 0x2d0d68af, 0x64095307, 0x05006402, // sq.id.ms_543 ig.cs.sk_655 ht.pl.lg_432 lg.fr.un_220
+ 0x20002705, 0x12641109, 0x21002d0c, 0x0f09060c, // gd.sq.un_330 ro.lg.hu_444 sk.jw.un_530 de.pl.lv_543
+ 0x64355204, 0x3b000104, 0x55002109, 0x3f0b0608, // ha.zu.lg_332 en.so.un_320 jw.rw.un_440 de.es.af_443
+ 0x1e000223, 0x0f0506a4, 0x04001c02, 0x03063f0e, // da.ms.un_880 de.fr.lv_433 id.fi.un_220 af.de.nl_555
+ // [0fb0]
+ 0x3f000313, 0x110608a0, 0x1c0913ee, 0x0b06255a, // nl.af.un_650 no.de.ro_322 bh.hi.mr_422 eu.de.es_553
+ 0x2d2b0c02, 0x102b1707, 0x060c0808, 0x2864550e, // sv.vi.sk_222 sr.vi.lt_432 no.sv.de_443 rw.lg.sw_555
+ 0x0d1c13a4, 0x231f1902, 0x190b0a08, 0x17000a2b, // bh.mr.ne_433 gl.cy.ca_222 pt.es.gl_443 mk.sr.un_980
+ 0x0f00030d, 0x1a004a22, 0x11070a0d, 0x08041004, // nl.lv.un_540 yo.tl.un_870 mk.bg.ro_554 be.ru.uk_332
+ // [0fc0]
+ 0x0e13040c, 0x1f1a250c, 0x136b1a04, 0x0e086ba4, // fi.et.is_543 eu.tl.cy_543 tl.ceb.et_332 ceb.no.is_433
+ 0x06003514, 0x18002118, 0x052101a4, 0x1c001e2b, // zu.de.un_660 fa.ar.un_740 en.jw.fr_433 ms.id.un_980
+ 0x0a081705, 0x0a0810a0, 0x28001305, 0x080a17a9, // sr.uk.mk_333 be.uk.mk_322 et.sw.un_330 sr.mk.uk_544
+ 0x0d130911, 0x051f270d, 0x0d091c0c, 0x0500181a, // hi.bh.ne_653 gd.cy.fr_554 mr.hi.ne_543 ga.fr.un_760
+ // [0fd0]
+ 0x16520ea7, 0x2900201a, 0x1e002707, 0x21001212, // is.ha.hr_532 sq.sl.un_760 gd.ms.un_420 ur.fa.un_640
+ 0x2d002019, 0x1c09130c, 0x18002a02, 0x2a076860, // sq.sk.un_750 bh.hi.mr_543 mt.ga.un_220 ig.it.mt_664
+ 0x080c09a4, 0x1b113107, 0x171632ec, 0x0b0a52a4, // pl.sv.no_433 az.ro.tr_432 bs.hr.sr_644 ha.pt.es_433
+ 0x281020ee, 0x070411a0, 0x4a0f53ee, 0x1325090b, // sq.lt.sw_422 ro.ru.bg_322 ht.lv.yo_422 pl.eu.et_542
+ // [0fe0]
+ 0x1200250d, 0x03020807, 0x53000b02, 0x050e06a0, // eu.hu.un_540 no.da.nl_432 es.ht.un_220 de.is.fr_322
+ 0x0d001309, 0x100e0fad, 0x04001009, 0x17070a14, // bh.ne.un_440 lv.is.lt_643 be.ru.un_440 mk.bg.sr_666
+ 0x1f130414, 0x1f002707, 0x130d09ec, 0x0b001912, // fi.et.cy_666 gd.cy.un_420 hi.ne.bh_644 gl.es.un_640
+ 0x230725a4, 0x3f112d0c, 0x2d003204, 0x05002705, // eu.it.ca_433 sk.ro.af_543 bs.sk.un_320 gd.fr.un_330
+ // [0ff0]
+ 0x0408170d, 0x09002d35, 0x20002909, 0x080725a6, // sr.uk.ru_554 sk.pl.un_A90 sl.sq.un_440 eu.it.no_521
+ 0x09000e12, 0x0f004a04, 0x0f09010b, 0x080a1708, // is.pl.un_640 yo.lv.un_320 en.pl.lv_542 sr.mk.uk_443
+ 0x680f3f11, 0x04001a08, 0x2a006407, 0x3f0b2d0c, // af.lv.ig_653 tl.fi.un_430 lg.mt.un_420 sk.es.af_543
+ 0x3229200c, 0x0e000c14, 0x0d102904, 0x29322008, // sq.sl.bs_543 sv.is.un_660 sl.lt.cs_332 sq.bs.sl_443
+
+ // [1000]
+ 0x3f050da4, 0x29002019, 0x2d002007, 0x2d072511, // cs.fr.af_433 sq.sl.un_750 sq.sk.un_420 eu.it.sk_653
+ 0x03113faf, 0x2d6809ad, 0x0400101b, 0x09002d2b, // af.ro.nl_655 pl.ig.sk_643 be.ru.un_770 sk.pl.un_980
+ 0x1c0d130c, 0x05002704, 0x0b000708, 0x09001219, // bh.ne.mr_543 gd.fr.un_320 it.es.un_430 hu.pl.un_750
+ 0x3f1a6b07, 0x1e1c2514, 0x09070da4, 0x091e25a4, // ceb.tl.af_432 eu.id.ms_666 cs.it.pl_433 eu.ms.pl_433
+ // [1010]
+ 0x1b003122, 0x17162909, 0x052d01a4, 0x0600092b, // az.tr.un_870 sl.hr.sr_444 en.sk.fr_433 pl.de.un_980
+ 0x1b003121, 0x2d11030d, 0x0a0408a4, 0x041707a4, // az.tr.un_860 nl.ro.sk_554 uk.ru.mk_433 bg.sr.ru_433
+ 0x29003108, 0x2d093f12, 0x3f090d07, 0x13000923, // az.sl.un_430 af.pl.sk_654 cs.pl.af_432 hi.bh.un_880
+ 0x13000921, 0x13251904, 0x1c001319, 0x35000c0c, // hi.bh.un_860 gl.eu.et_332 bh.mr.un_750 sv.zu.un_530
+ // [1020]
+ 0x080c0ea9, 0x040a0805, 0x05120a04, 0x6b1c1e0c, // is.sv.no_544 uk.mk.ru_333 pt.hu.fr_332 ms.id.ceb_543
+ 0x29001704, 0x19000e05, 0x1b1a4a13, 0x04170807, // sr.sl.un_320 is.gl.un_330 yo.tl.tr_665 uk.sr.ru_432
+ 0x08170aad, 0x00002b03, 0x08001013, 0x0e1b1304, // mk.sr.uk_643 vi.un.un_300 be.uk.un_650 et.tr.is_332
+ 0x1f004a02, 0x53000512, 0x1c0d0907, 0x070417a4, // yo.cy.un_220 fr.ht.un_640 hi.ne.mr_432 sr.ru.bg_433
+ // [1030]
+ 0x03000508, 0x06002a04, 0x1a0e6405, 0x070804a4, // fr.nl.un_430 mt.de.un_320 lg.is.tl_333 ru.uk.bg_433
+ 0x1a1b0504, 0x311b1fa9, 0x2b3b52a0, 0x3100520d, // fr.tr.tl_332 cy.tr.az_544 ha.so.vi_322 ha.az.un_540
+ 0x3b2001a4, 0x13001c14, 0x3b0419a0, 0x1a1e1c08, // en.sq.so_433 mr.bh.un_660 gl.fi.so_322 id.ms.tl_443
+ 0x1f1e1c60, 0x0e001319, 0x1c0913a0, 0x1c080c05, // id.ms.cy_664 et.is.un_750 bh.hi.mr_322 sv.no.id_333
+ // [1040]
+ 0x1b005204, 0x25001902, 0x0c00190d, 0x076412ad, // ha.tr.un_320 gl.eu.un_220 gl.sv.un_540 hu.lg.it_643
+ 0x1f00550d, 0x0c001904, 0x080c5304, 0x4a3f1aa7, // rw.cy.un_540 gl.sv.un_320 ht.sv.no_332 tl.af.yo_532
+ 0x0e000a09, 0x6b001a1a, 0x020c08af, 0x040810a0, // pt.is.un_440 tl.ceb.un_760 no.sv.da_655 be.uk.ru_322
+ 0x172d290c, 0x0c000823, 0x1e0f1ca0, 0x0d1c0908, // sl.sk.sr_543 no.sv.un_880 id.lv.ms_322 hi.mr.ne_443
+ // [1050]
+ 0x2b4a1fa7, 0x1c130907, 0x040710a9, 0x0d1c0913, // cy.yo.vi_532 hi.bh.mr_432 be.bg.ru_544 hi.mr.ne_665
+ 0x092d0d55, 0x131c0912, 0x07000422, 0x011f6b02, // cs.sk.pl_442 hi.mr.bh_654 ru.bg.un_870 ceb.cy.en_222
+ 0x131e0e12, 0x0a1f1955, 0x1e211cec, 0x091218a7, // is.ms.et_654 gl.cy.pt_442 id.jw.ms_644 ga.hu.pl_532
+ 0x1000110b, 0x06033f13, 0x2d0d2914, 0x27002807, // ro.be.un_520 af.nl.de_665 sl.cs.sk_666 sw.gd.un_420
+ // [1060]
+ 0x2a3f3b04, 0x1a005304, 0x1c033f11, 0x050c01a0, // so.af.mt_332 ht.tl.un_320 af.nl.id_653 en.sv.fr_322
+ 0x170a0702, 0x0704170d, 0x1a006b22, 0x521e1c08, // bg.mk.sr_222 sr.ru.bg_554 ceb.tl.un_870 id.ms.ha_443
+ 0x213b35ec, 0x20003214, 0x3b2128a9, 0x4a002021, // zu.so.jw_644 bs.sq.un_660 sw.jw.so_544 sq.yo.un_860
+ 0x190b0aee, 0x5300201b, 0x0700682a, 0x12000804, // pt.es.gl_422 sq.ht.un_770 ig.it.un_970 no.hu.un_320
+ // [1070]
+ 0x171629a9, 0x0a00071a, 0x53003119, 0x07000a02, // sl.hr.sr_544 bg.mk.un_760 az.ht.un_750 mk.bg.un_220
+ 0x080c0eee, 0x2d000d02, 0x09061f07, 0x280768ec, // is.sv.no_422 cs.sk.un_220 cy.de.pl_432 ig.it.sw_644
+ 0x120c0809, 0x0c282508, 0x12006804, 0x29200d07, // no.sv.hu_444 eu.sw.sv_443 ig.hu.un_320 cs.sq.sl_432
+ 0x05002304, 0x0553110b, 0x6800030e, 0x23190a13, // ca.fr.un_320 ro.ht.fr_542 nl.ig.un_550 pt.gl.ca_665
+ // [1080]
+ 0x033f12a4, 0x52002018, 0x06076807, 0x00001e15, // hu.af.nl_433 sq.ha.un_740 ig.it.de_432 ms.un.un_700
+ 0x31283b0e, 0x20006e19, 0x31005304, 0x31003202, // so.sw.az_555 hmn.sq.un_750 ht.az.un_320 bs.az.un_220
+ 0x55006402, 0x172d090c, 0x0d00130c, 0x00003537, // lg.rw.un_220 pl.sk.sr_543 bh.ne.un_530 zu.un.un_B00
+ 0x08020cec, 0x0417100c, 0x310452ad, 0x1c291ead, // sv.da.no_644 be.sr.ru_543 ha.fi.az_643 ms.sl.id_643
+ // [1090]
+ 0x35522812, 0x0410085a, 0x07000a0d, 0x07005322, // sw.ha.zu_654 uk.be.ru_553 mk.bg.un_540 ht.it.un_870
+ 0x1c130914, 0x031b1211, 0x0b1223ec, 0x32172a12, // hi.bh.mr_666 hu.tr.nl_653 ca.hu.es_644 mt.sr.bs_654
+ 0x0f050107, 0x23190bec, 0x130c12a4, 0x09000d13, // en.fr.lv_432 es.gl.ca_644 hu.sv.et_433 ne.hi.un_650
+ 0x311b0caf, 0x03643f08, 0x683f53ec, 0x284a5304, // sv.tr.az_655 af.lg.nl_443 ht.af.ig_644 ht.yo.sw_332
+ // [10a0]
+ 0x07000402, 0x09000d0e, 0x251827ad, 0x1a04250c, // ru.bg.un_220 ne.hi.un_550 gd.ga.eu_643 eu.fi.tl_543
+ 0x4a2d12a9, 0x2006120c, 0x1213040c, 0x353f5207, // hu.sk.yo_544 hu.de.sq_543 fi.et.hu_543 ha.af.zu_432
+ 0x1025270d, 0x0c080211, 0x0c000812, 0x00004a1c, // gd.eu.lt_554 da.no.sv_653 no.sv.un_640 yo.un.un_800
+ 0x00000801, 0x3100200d, 0x09001f0c, 0x00006e2d, // no.un.un_200 sq.az.un_540 cy.pl.un_530 hmn.un.un_A00
+ // [10b0]
+ 0x3b033f09, 0x532105ec, 0x121b0ca7, 0x1a2728a4, // af.nl.so_444 fr.jw.ht_644 sv.tr.hu_532 sw.gd.tl_433
+ 0x21131cee, 0x3b001319, 0x05000108, 0x6e202a05, // id.et.jw_422 et.so.un_750 en.fr.un_430 mt.sq.hmn_333
+ 0x033b3f05, 0x08000608, 0x00003b03, 0x100f25a9, // af.so.nl_333 de.no.un_430 so.un.un_300 eu.lv.lt_544
+ 0x080a07a0, 0x3f033b12, 0x091c0d12, 0x11000422, // bg.mk.uk_322 so.nl.af_654 ne.mr.hi_654 ru.ro.un_870
+ // [10c0]
+ 0x04000f1a, 0x6b001a1b, 0x28521a12, 0x031304a7, // lv.fi.un_760 tl.ceb.un_770 tl.ha.sw_654 fi.et.nl_532
+ 0x0f314aa4, 0x0d00090d, 0x524a1fa9, 0x4a033f12, // yo.az.lv_433 hi.ne.un_540 cy.yo.ha_544 af.nl.yo_654
+ 0x13090d0c, 0x3f000e05, 0x070a1713, 0x53080208, // ne.hi.bh_543 is.af.un_330 sr.mk.bg_665 da.no.ht_443
+ 0x0e080ca4, 0x230b050e, 0x0504250c, 0x0d1c13af, // sv.no.is_433 fr.es.ca_555 eu.fi.fr_543 bh.mr.ne_655
+ // [10d0]
+ 0x09001c1b, 0x16000d08, 0x3b2a0fec, 0x1632170d, // mr.hi.un_770 cs.hr.un_430 lv.mt.so_644 sr.bs.hr_554
+ 0x02080c04, 0x0e001f1b, 0x100a0813, 0x31001b29, // sv.no.da_332 cy.is.un_770 uk.mk.be_665 tr.az.un_960
+ 0x04131aec, 0x01080ea4, 0x11003b1a, 0x1e1c3baf, // tl.et.fi_644 is.no.en_433 so.ro.un_760 so.id.ms_655
+ 0x1e006b04, 0x190a0b14, 0x03001313, 0x12082104, // ceb.ms.un_320 es.pt.gl_666 et.nl.un_650 jw.no.hu_332
+ // [10e0]
+ 0x2b001e02, 0x211e1c04, 0x25230112, 0x1a12680e, // ms.vi.un_220 id.ms.jw_332 en.ca.eu_654 ig.hu.tl_555
+ 0x2b001c04, 0x0e002808, 0x0b000713, 0x1c0d130e, // id.vi.un_320 sw.is.un_430 it.es.un_650 bh.ne.mr_555
+ 0x17000d04, 0x03281355, 0x3b001f0d, 0x18002711, // cs.sr.un_320 et.sw.nl_442 cy.so.un_540 gd.ga.un_630
+ 0x1100681b, 0x1b00530d, 0x070a0408, 0x0817110c, // ig.ro.un_770 ht.tr.un_540 ru.mk.bg_443 ro.sr.uk_543
+ // [10f0]
+ 0x21001114, 0x03113f0c, 0x271718ec, 0x03071808, // ro.jw.un_660 af.ro.nl_543 ga.sr.gd_644 ga.it.nl_443
+ 0x03003f22, 0x0e001e04, 0x0e000619, 0x0d001307, // af.nl.un_870 ms.is.un_320 de.is.un_750 bh.ne.un_420
+ 0x17070408, 0x081f0607, 0x2000280d, 0x021b0e05, // ru.bg.sr_443 de.cy.no_432 sw.sq.un_540 is.tr.da_333
+ 0x04171008, 0x1e002818, 0x52001c04, 0x040711ec, // be.sr.ru_443 sw.ms.un_740 id.ha.un_320 ro.bg.ru_644
+ // [1100]
+ 0x18002a08, 0x24000113, 0x55202813, 0x02000e22, // mt.ga.un_430 iw.yi.un_650 sw.sq.rw_665 is.da.un_870
+ 0x0800120c, 0x06002302, 0x01001f04, 0x1c000d12, // hu.no.un_530 ca.de.un_220 cy.en.un_320 ne.mr.un_640
+ 0x2900170d, 0x070811a4, 0x0f001904, 0x080a1107, // sr.sl.un_540 ro.uk.bg_433 gl.lv.un_320 ro.mk.uk_432
+ 0x6b100755, 0x2d0d10ec, 0x080417a0, 0x3f00030e, // it.lt.ceb_442 lt.cs.sk_644 sr.ru.uk_322 nl.af.un_550
+ // [1110]
+ 0x1a00211b, 0x09005512, 0x52004a0c, 0x0e001f13, // jw.tl.un_770 rw.pl.un_640 yo.ha.un_530 cy.is.un_650
+ 0x125204ac, 0x0b0a23a9, 0x1f0b2307, 0x29172daf, // fi.ha.hu_632 ca.pt.es_544 ca.es.cy_432 sk.sr.sl_655
+ 0x0900530d, 0x2a00550d, 0x25000521, 0x32001609, // ht.pl.un_540 rw.mt.un_540 fr.eu.un_860 hr.bs.un_440
+ 0x0a001713, 0x2a000702, 0x1e211ca0, 0x1c002112, // sr.mk.un_650 it.mt.un_220 id.jw.ms_322 jw.id.un_640
+ // [1120]
+ 0x110827ad, 0x0d001c20, 0x092d0d08, 0x3217160c, // gd.no.ro_643 mr.ne.un_850 cs.sk.pl_443 hr.sr.bs_543
+ 0x12002512, 0x12002123, 0x25000b08, 0x1300122b, // eu.hu.un_640 fa.ur.un_880 es.eu.un_430 hu.et.un_980
+ 0x1c001f1a, 0x28081f04, 0x13001c0e, 0x16002911, // cy.id.un_760 cy.no.sw_332 id.et.un_550 sl.hr.un_630
+ 0x3f1f13ee, 0x64041ca0, 0x00003503, 0x23000419, // et.cy.af_422 id.fi.lg_322 zu.un.un_300 fi.ca.un_750
+ // [1130]
+ 0x1c0d1305, 0x0a002818, 0x070a0812, 0x07000405, // bh.ne.mr_333 sw.pt.un_740 uk.mk.bg_654 ru.bg.un_330
+ 0x0a00042a, 0x06351aa0, 0x051f0408, 0x0500070c, // ru.mk.un_970 tl.zu.de_322 fi.cy.fr_443 it.fr.un_530
+ 0x10000819, 0x11000505, 0x2a04020c, 0x00003501, // uk.be.un_750 fr.ro.un_330 da.fi.mt_543 zu.un.un_200
+ 0x13000305, 0x1c000922, 0x12082055, 0x04000808, // nl.et.un_330 hi.mr.un_870 sq.no.hu_442 no.fi.un_430
+ // [1140]
+ 0x0700010d, 0x08021aa0, 0x21001c07, 0x193b0bad, // en.it.un_540 tl.da.no_322 id.jw.un_420 es.so.gl_643
+ 0x4a28640c, 0x291b280c, 0x081f0e08, 0x12071ba4, // lg.sw.yo_543 sw.tr.sl_543 is.cy.no_443 tr.it.hu_433
+ 0x0000320f, 0x28006b04, 0x0100350e, 0x23001a04, // bs.un.un_600 ceb.sw.un_320 zu.en.un_550 tl.ca.un_320
+ 0x556428ad, 0x29003213, 0x0507010c, 0x0d000902, // sw.lg.rw_643 bs.sl.un_650 en.it.fr_543 hi.ne.un_220
+ // [1150]
+ 0x130410a9, 0x3f000308, 0x64551a08, 0x04091fa9, // lt.fi.et_544 nl.af.un_430 tl.rw.lg_443 cy.pl.fi_544
+ 0x01001109, 0x0a000709, 0x070411ad, 0x08110707, // ro.en.un_440 it.pt.un_440 ro.ru.bg_643 it.ro.no_432
+ 0x0e0752ee, 0x06031b08, 0x53100908, 0x06033f0d, // ha.it.is_422 tr.nl.de_443 pl.lt.ht_443 af.nl.de_554
+ 0x16002912, 0x050c6ba0, 0x2a290aaf, 0x18122114, // sl.hr.un_640 ceb.sv.fr_322 pt.sl.mt_655 fa.ur.ar_666
+ // [1160]
+ 0x2a096807, 0x06321709, 0x07050107, 0x01106ba0, // ig.pl.mt_432 sr.bs.de_444 en.fr.it_432 ceb.lt.en_322
+ 0x1f010604, 0x040601a4, 0x231b0607, 0x2855350d, // de.en.cy_332 en.de.fi_433 de.tr.ca_432 zu.rw.sw_554
+ 0x2100182a, 0x351a08a4, 0x070604ad, 0x110a1709, // ar.fa.un_970 no.tl.zu_433 fi.de.it_643 sr.mk.ro_444
+ 0x2021280d, 0x052352ac, 0x0d1c1304, 0x25000a1a, // sw.jw.sq_554 ha.ca.fr_632 bh.mr.ne_332 pt.eu.un_760
+ // [1170]
+ 0x04081704, 0x5500251b, 0x0711040c, 0x28256ba4, // sr.uk.ru_332 eu.rw.un_770 ru.ro.bg_543 ceb.eu.sw_433
+ 0x271828a9, 0x06002a0d, 0x53014aee, 0x2a27350c, // sw.ga.gd_544 mt.de.un_540 yo.en.ht_422 zu.gd.mt_543
+ 0x25003504, 0x2868180d, 0x6b006408, 0x311b09a4, // zu.eu.un_320 ga.ig.sw_554 lg.ceb.un_430 pl.tr.az_433
+ 0x10080460, 0x3b002704, 0x070a11a4, 0x0a0817a0, // ru.uk.be_664 gd.so.un_320 ro.mk.bg_433 sr.uk.mk_322
+ // [1180]
+ 0x68110708, 0x1c000705, 0x07000a14, 0x0429130d, // it.ro.ig_443 it.id.un_330 mk.bg.un_660 et.sl.fi_554
+ 0x130e04ad, 0x0e00101b, 0x060501af, 0x356e20ad, // fi.is.et_643 lt.is.un_770 en.fr.de_655 sq.hmn.zu_643
+ 0x16000907, 0x07001828, 0x3b684aa4, 0x13290913, // pl.hr.un_420 ar.bg.un_950 yo.ig.so_433 pl.sl.et_665
+ 0x2a090f12, 0x2700350e, 0x1c00042a, 0x162a04a4, // lv.pl.mt_654 zu.gd.un_550 fi.id.un_970 fi.mt.hr_433
+ // [1190]
+ 0x12002114, 0x55281ea0, 0x4a096407, 0x1f001904, // fa.ur.un_660 ms.sw.rw_322 lg.pl.yo_432 gl.cy.un_320
+ 0x0400071a, 0x3b130908, 0x04643b0c, 0x1a003b0d, // bg.ru.un_760 pl.et.so_443 so.lg.fi_543 so.tl.un_540
+ 0x081704ad, 0x1c130908, 0x6e6b01a4, 0x08000c0e, // ru.sr.uk_643 hi.bh.mr_443 en.ceb.hmn_433 sv.no.un_550
+ 0x3f3b645a, 0x080704a9, 0x2b1f2704, 0x04112355, // lg.so.af_553 ru.bg.uk_544 gd.cy.vi_332 ca.ro.fi_442
+ // [11a0]
+ 0x0e20060b, 0x0a684a08, 0x29092512, 0x015528a4, // de.sq.is_542 yo.ig.pt_443 eu.pl.sl_654 sw.rw.en_433
+ 0x1c133b07, 0x04643f0c, 0x08534aa6, 0x1f003f21, // so.et.id_432 af.lg.fi_543 yo.ht.no_521 af.cy.un_860
+ 0x17041007, 0x0e001908, 0x1a643b08, 0x0409290d, // be.ru.sr_432 gl.is.un_430 so.lg.tl_443 sl.pl.fi_554
+ 0x1800212b, 0x4a006420, 0x64003b2b, 0x03280507, // fa.ar.un_980 lg.yo.un_850 so.lg.un_980 fr.sw.nl_432
+ // [11b0]
+ 0x0e5209a9, 0x10040708, 0x06356804, 0x04070a08, // pl.ha.is_544 bg.ru.be_443 ig.zu.de_332 mk.bg.ru_443
+ 0x08021ba4, 0x64003b1b, 0x1b290914, 0x6435280c, // tr.da.no_433 so.lg.un_770 pl.sl.tr_666 sw.zu.lg_543
+ 0x03003b0b, 0x23190b09, 0x2d003513, 0x2d082908, // so.nl.un_520 es.gl.ca_444 zu.sk.un_650 sl.no.sk_443
+ 0x1c0913ec, 0x2d000d11, 0x1c090d5a, 0x1b000c07, // bh.hi.mr_644 cs.sk.un_630 ne.hi.mr_553 sv.tr.un_420
+ // [11c0]
+ 0x1800201a, 0x641a3b0c, 0x6b1a6812, 0x1929230c, // sq.ga.un_760 so.tl.lg_543 ig.tl.ceb_654 ca.sl.gl_543
+ 0x28643b60, 0x31001114, 0x090d1ca0, 0x0a040713, // so.lg.sw_664 ro.az.un_660 mr.ne.hi_322 bg.ru.mk_665
+ 0x643b1a09, 0x3b00640d, 0x046b1aa4, 0x070f68a4, // tl.so.lg_444 lg.so.un_540 tl.ceb.fi_433 ig.lv.it_433
+ 0x18122113, 0x1f271813, 0x3f000a05, 0x0a0811a0, // fa.ur.ar_665 ga.gd.cy_665 pt.af.un_330 ro.uk.mk_322
+ // [11d0]
+ 0x04080aa4, 0x6b0a1f08, 0x0400081a, 0x4a00680d, // mk.uk.ru_433 cy.pt.ceb_443 uk.ru.un_760 ig.yo.un_540
+ 0x093f0304, 0x17040a05, 0x211c1ea4, 0x17080704, // nl.af.pl_332 mk.ru.sr_333 ms.id.jw_433 bg.uk.sr_332
+ 0x05070112, 0x12000e05, 0x0e1918a0, 0x2311100c, // en.it.fr_654 is.hu.un_330 ga.gl.is_322 lt.ro.ca_543
+ 0x231119a4, 0x0a002319, 0x0f001318, 0x35001a07, // gl.ro.ca_433 ca.pt.un_750 et.lv.un_740 tl.zu.un_420
+ // [11e0]
+ 0x2d1009a9, 0x2800682a, 0x08042712, 0x271304ad, // pl.lt.sk_544 ig.sw.un_970 gd.fi.no_654 fi.et.gd_643
+ 0x0807120c, 0x270e0655, 0x06000808, 0x21182712, // hu.it.no_543 de.is.gd_442 no.de.un_430 gd.ga.jw_654
+ 0x0000352d, 0x04001014, 0x096b35a0, 0x27202114, // zu.un.un_A00 be.ru.un_660 zu.ceb.pl_322 jw.sq.gd_666
+ 0x11000d08, 0x0a07100e, 0x21001c05, 0x4a202dee, // cs.ro.un_430 be.bg.mk_555 id.jw.un_330 sk.sq.yo_422
+ // [11f0]
+ 0x1168270c, 0x101123ac, 0x023b070c, 0x17110a04, // gd.ig.ro_543 ca.ro.lt_632 it.so.da_543 mk.ro.sr_332
+ 0x0c19230c, 0x04080aad, 0x1e191108, 0x11092da9, // ca.gl.sv_543 mk.uk.ru_643 ro.gl.ms_443 sk.pl.ro_544
+ 0x06000e1a, 0x0c091007, 0x09201702, 0x1200212a, // is.de.un_760 lt.pl.sv_432 sr.sq.pl_222 fa.ur.un_970
+ 0x160712a7, 0x536b1aaf, 0x3b006b05, 0x1a6b0705, // hu.it.hr_532 tl.ceb.ht_655 ceb.so.un_330 it.ceb.tl_333
+ // [1200]
+ 0x132801a0, 0x182721af, 0x350f10af, 0x061e1c08, // en.sw.et_322 jw.gd.ga_655 lt.lv.zu_655 id.ms.de_443
+ 0x203b28ad, 0x551a20a4, 0x2d111005, 0x31002023, // sw.so.sq_643 sq.tl.rw_433 lt.ro.sk_333 sq.az.un_880
+ 0x010535a0, 0x19002319, 0x35312055, 0x0c02080c, // zu.fr.en_322 ca.gl.un_750 sq.az.zu_442 no.da.sv_543
+ 0x08000719, 0x10231108, 0x32001614, 0x20005514, // bg.uk.un_750 ro.ca.lt_443 hr.bs.un_660 rw.sq.un_660
+ // [1210]
+ 0x53006407, 0x20351812, 0x1168230c, 0x1b1853a0, // lg.ht.un_420 ga.zu.sq_654 ca.ig.ro_543 ht.ga.tr_322
+ 0x32166ba0, 0x1e110ba0, 0x20000d04, 0x2b001a04, // ceb.hr.bs_322 es.ro.ms_322 cs.sq.un_320 tl.vi.un_320
+ 0x2010110d, 0x0a011a07, 0x3555520d, 0x232d100c, // ro.lt.sq_554 tl.en.pt_432 ha.rw.zu_554 lt.sk.ca_543
+ 0x1b05350c, 0x023f08a4, 0x0d000f08, 0x1b132007, // zu.fr.tr_543 no.af.da_433 lv.cs.un_430 sq.et.tr_432
+ // [1220]
+ 0x19000b21, 0x6b551a08, 0x1a096b09, 0x2b1a6ba0, // es.gl.un_860 tl.rw.ceb_443 ceb.pl.tl_444 ceb.tl.vi_322
+ 0x03012aee, 0x0e2a1004, 0x02000404, 0x021a6ba9, // mt.en.nl_422 lt.mt.is_332 fi.da.un_320 ceb.tl.da_544
+ 0x1c2952a4, 0x17040705, 0x3f552a55, 0x28352014, // ha.sl.id_433 bg.ru.sr_333 mt.rw.af_442 sq.zu.sw_666
+ 0x3531640c, 0x01001e07, 0x09003504, 0x200b1313, // lg.az.zu_543 ms.en.un_420 zu.pl.un_320 et.es.sq_665
+ // [1230]
+ 0x01060c04, 0x311a10a4, 0x170a0409, 0x05010605, // sv.de.en_332 lt.tl.az_433 ru.mk.sr_444 de.en.fr_333
+ 0x292d0d14, 0x0c000813, 0x1f003513, 0x64002704, // cs.sk.sl_666 no.sv.un_650 zu.cy.un_650 gd.lg.un_320
+ 0x00001b0a, 0x09003f02, 0x130d0914, 0x290d02a0, // tr.un.un_500 af.pl.un_220 hi.ne.bh_666 da.cs.sl_322
+ 0x1c002107, 0x25000b02, 0x4a1a6ba0, 0x2a033fa0, // jw.id.un_420 es.eu.un_220 ceb.tl.yo_322 af.nl.mt_322
+ // [1240]
+ 0x07080409, 0x101e1c05, 0x04070813, 0x19000a07, // ru.uk.bg_444 id.ms.lt_333 uk.bg.ru_665 pt.gl.un_420
+ 0x0d091c0b, 0x0411170d, 0x08001002, 0x0f00100d, // mr.hi.ne_542 sr.ro.ru_554 be.uk.un_220 lt.lv.un_540
+ 0x19270aa4, 0x162d0d09, 0x040f18a0, 0x05000702, // pt.gd.gl_433 cs.sk.hr_444 ga.lv.fi_322 it.fr.un_220
+ 0x521e25a9, 0x1e1c350c, 0x3b001013, 0x35003b09, // eu.ms.ha_544 zu.id.ms_543 lt.so.un_650 so.zu.un_440
+ // [1250]
+ 0x09130d11, 0x0d091307, 0x0e000c13, 0x5500100e, // ne.bh.hi_653 bh.hi.ne_432 sv.is.un_650 lt.rw.un_550
+ 0x06033f04, 0x040e0ca7, 0x053211ee, 0x072705a0, // af.nl.de_332 sv.is.fi_532 ro.bs.fr_422 fr.gd.it_322
+ 0x551e1c05, 0x532a0508, 0x170711af, 0x18002721, // id.ms.rw_333 fr.mt.ht_443 ro.bg.sr_655 gd.ga.un_860
+ 0x28005505, 0x1e101c12, 0x27186eee, 0x1c090d08, // rw.sw.un_330 id.lt.ms_654 hmn.ga.gd_422 ne.hi.mr_443
+ // [1260]
+ 0x080a0709, 0x13090d11, 0x10000b02, 0x07170804, // bg.mk.uk_444 ne.hi.bh_653 es.lt.un_220 uk.sr.bg_332
+ 0x08170a09, 0x1a645504, 0x06001b13, 0x35005504, // mk.sr.uk_444 rw.lg.tl_332 tr.de.un_650 rw.zu.un_320
+ 0x3b000702, 0x10070409, 0x28001c02, 0x08020c11, // it.so.un_220 ru.bg.be_444 id.sw.un_220 sv.da.no_653
+ 0x0c0208a7, 0x23190aa0, 0x1e0f18a0, 0x68003522, // no.da.sv_532 pt.gl.ca_322 ga.lv.ms_322 zu.ig.un_870
+ // [1270]
+ 0x29100411, 0x2d1b0d55, 0x1f002512, 0x0408100c, // fi.lt.sl_653 cs.tr.sk_442 eu.cy.un_640 be.uk.ru_543
+ 0x35536812, 0x0c321ca0, 0x180427af, 0x08000209, // ig.ht.zu_654 id.bs.sv_322 gd.fi.ga_655 da.no.un_440
+ 0x1c0d1307, 0x0f2920a4, 0x09000804, 0x3b001c08, // bh.ne.mr_432 sq.sl.lv_433 no.pl.un_320 id.so.un_430
+ 0x551e1c0c, 0x3b000d22, 0x1004640c, 0x2d0d0a0c, // id.ms.rw_543 cs.so.un_870 lg.fi.lt_543 pt.cs.sk_543
+ // [1280]
+ 0x2d0d3bec, 0x55351c08, 0x040e640c, 0x531e1c04, // so.cs.sk_644 id.zu.rw_443 lg.is.fi_543 id.ms.ht_332
+ 0x172d0d55, 0x07170a08, 0x1b003204, 0x07041012, // cs.sk.sr_442 mk.sr.bg_443 bs.tr.un_320 be.ru.bg_654
+ 0x046408a0, 0x170a070c, 0x00000d0a, 0x0f006409, // no.lg.fi_322 bg.mk.sr_543 cs.un.un_500 lg.lv.un_440
+ 0x0d000a08, 0x08000212, 0x19000b22, 0x04086407, // pt.cs.un_430 da.no.un_640 es.gl.un_870 lg.no.fi_432
+ // [1290]
+ 0x07080a05, 0x211c1aa4, 0x6400681b, 0x08000a12, // mk.uk.bg_333 tl.id.jw_433 ig.lg.un_770 mk.uk.un_640
+ 0x0a1707ad, 0x1e1c210e, 0x0c000d1a, 0x1700030c, // bg.sr.mk_643 jw.id.ms_555 cs.sv.un_760 nl.sr.un_530
+ 0x2d005304, 0x0d001c08, 0x131c1a04, 0x0f321705, // ht.sk.un_320 mr.ne.un_430 tl.id.et_332 sr.bs.lv_333
+ 0x030e12af, 0x13003f07, 0x09250d07, 0x110b5507, // hu.is.nl_655 af.et.un_420 cs.eu.pl_432 rw.es.ro_432
+ // [12a0]
+ 0x100a1708, 0x0c0601af, 0x2a3b25ad, 0x1b000c04, // sr.mk.be_443 en.de.sv_655 eu.so.mt_643 sv.tr.un_320
+ 0x190d0aa7, 0x04001307, 0x0e10640b, 0x050111ee, // pt.cs.gl_532 et.fi.un_420 lg.lt.is_542 ro.en.fr_422
+ 0x2a523b12, 0x2d290dec, 0x2d0d0aaf, 0x05000a08, // so.ha.mt_654 cs.sl.sk_644 pt.cs.sk_655 pt.fr.un_430
+ 0x11204aa0, 0x04120308, 0x101e1c04, 0x3f000808, // yo.sq.ro_322 nl.hu.fi_443 id.ms.lt_332 no.af.un_430
+ // [12b0]
+ 0x061e1c04, 0x3200010c, 0x08070408, 0x280f1105, // id.ms.de_332 en.bs.un_530 ru.bg.uk_443 ro.lv.sw_333
+ 0x21252a04, 0x3b00311b, 0x5300050b, 0x0c002105, // mt.eu.jw_332 az.so.un_770 fr.ht.un_520 jw.sv.un_330
+ 0x100711a7, 0x1700100b, 0x02000612, 0x07002a14, // ro.bg.be_532 be.sr.un_520 de.da.un_640 mt.it.un_660
+ 0x170a1108, 0x12190a0c, 0x1f1827ec, 0x25001c04, // ro.mk.sr_443 pt.gl.hu_543 gd.ga.cy_644 id.eu.un_320
+ // [12c0]
+ 0x08070412, 0x0c0e0da4, 0x3f030204, 0x0c0208ee, // ru.bg.uk_654 cs.is.sv_433 da.nl.af_332 no.da.sv_422
+ 0x040f1c04, 0x21001814, 0x1c000912, 0x091c13ad, // id.lv.fi_332 ar.fa.un_660 hi.mr.un_640 bh.mr.hi_643
+ 0x1821120d, 0x0b23190c, 0x0b0a19a0, 0x3f072013, // ur.fa.ar_554 gl.ca.es_543 gl.pt.es_322 sq.it.af_665
+ 0x23190b0e, 0x09131c10, 0x0d091c05, 0x3b000804, // es.gl.ca_555 mr.bh.hi_642 mr.hi.ne_333 no.so.un_320
+ // [12d0]
+ 0x07002308, 0x190b1fec, 0x06000c0e, 0x120d2d11, // ca.it.un_430 cy.es.gl_644 sv.de.un_550 sk.cs.hu_653
+ 0x280f31a4, 0x553b1a0e, 0x19530507, 0x110408af, // az.lv.sw_433 tl.so.rw_555 fr.ht.gl_432 uk.ru.ro_655
+ 0x11041760, 0x041353a0, 0x2d0d0aec, 0x52060eee, // sr.ru.ro_664 ht.et.fi_322 pt.cs.sk_644 is.de.ha_422
+ 0x182112af, 0x0c0e29ad, 0x1e1c12a4, 0x080e0408, // ur.fa.ar_655 sl.is.sv_643 hu.id.ms_433 fi.is.no_443
+ // [12e0]
+ 0x0d1c09ec, 0x0a1704a9, 0x17292808, 0x3f032909, // hi.mr.ne_644 ru.sr.mk_544 sw.sl.sr_443 sl.nl.af_444
+ 0x0c1e2504, 0x25006404, 0x080c1fa0, 0x0635080c, // eu.ms.sv_332 lg.eu.un_320 cy.sv.no_322 no.zu.de_543
+ 0x216b1aad, 0x0a081004, 0x07040a0d, 0x033f6407, // tl.ceb.jw_643 be.uk.mk_332 mk.ru.bg_554 lg.af.nl_432
+ 0x11002007, 0x0e002007, 0x0d0913ec, 0x17100811, // sq.ro.un_420 sq.is.un_420 bh.hi.ne_644 uk.be.sr_653
+ // [12f0]
+ 0x3f0208a9, 0x08020c09, 0x16000312, 0x01060ca6, // no.da.af_544 sv.da.no_444 nl.hr.un_640 sv.de.en_521
+ 0x18013ba0, 0x25001904, 0x121b1807, 0x2d132912, // so.en.ga_322 gl.eu.un_320 ga.tr.hu_432 sl.et.sk_654
+ 0x01532707, 0x033f2b07, 0x08020e0c, 0x1c002122, // gd.ht.en_432 vi.af.nl_432 is.da.no_543 jw.id.un_870
+ 0x29321ca0, 0x21526bad, 0x061c2104, 0x12286405, // id.bs.sl_322 ceb.ha.jw_643 jw.id.de_332 lg.sw.hu_333
+ // [1300]
+ 0x11070a08, 0x0000182d, 0x3f000335, 0x0d1a6b08, // mk.bg.ro_443 ar.un.un_A00 nl.af.un_A90 ceb.tl.cs_443
+ 0x013b5311, 0x1c0d0913, 0x3b3f030c, 0x0d122dad, // ht.so.en_653 hi.ne.mr_665 nl.af.so_543 sk.hu.cs_643
+ 0x09000d2b, 0x3f080c08, 0x06310fa4, 0x0d2d1211, // ne.hi.un_980 sv.no.af_443 lv.az.de_433 hu.sk.cs_653
+ 0x52004a04, 0x033b3fa4, 0x64005508, 0x0a07100c, // yo.ha.un_320 af.so.nl_433 rw.lg.un_430 be.bg.mk_543
+ // [1310]
+ 0x292d25ec, 0x1c0516a0, 0x13004a0d, 0x0b0a1914, // eu.sk.sl_644 hr.fr.id_322 yo.et.un_540 gl.pt.es_666
+ 0x1203530c, 0x01005312, 0x033f1313, 0x07080413, // ht.nl.hu_543 ht.en.un_640 et.af.nl_665 ru.uk.bg_665
+ 0x290919ee, 0x050a19a0, 0x18002014, 0x16292d07, // gl.pl.sl_422 gl.pt.fr_322 sq.ga.un_660 sk.sl.hr_432
+ 0x64001b21, 0x091c135a, 0x2b5206a4, 0x10645304, // tr.lg.un_860 bh.mr.hi_553 de.ha.vi_433 ht.lg.lt_332
+ // [1320]
+ 0x2d0d1702, 0x3b081aa9, 0x033b01a9, 0x530208af, // sr.cs.sk_222 tl.no.so_544 en.so.nl_544 no.da.ht_655
+ 0x0a080705, 0x1e005204, 0x2921120c, 0x1a250e08, // bg.uk.mk_333 ha.ms.un_320 hu.jw.sl_543 is.eu.tl_443
+ 0x3f0364a0, 0x10003209, 0x17040aaf, 0x4a1a6bec, // lg.nl.af_322 bs.lt.un_440 mk.ru.sr_655 ceb.tl.yo_644
+ 0x6b281a07, 0x2d000714, 0x55003519, 0x1a6b64a4, // tl.sw.ceb_432 it.sk.un_660 zu.rw.un_750 lg.ceb.tl_433
+ // [1330]
+ 0x170410af, 0x68061f07, 0x1f0453ee, 0x133f035a, // be.ru.sr_655 cy.de.ig_432 ht.fi.cy_422 nl.af.et_553
+ 0x1f062305, 0x011f5355, 0x013f0305, 0x01033fa0, // ca.de.cy_333 ht.cy.en_442 nl.af.en_333 af.nl.en_322
+ 0x12006423, 0x190b1109, 0x3f00032c, 0x53001c04, // lg.hu.un_880 ro.es.gl_444 nl.af.un_990 id.ht.un_320
+ 0x08070a55, 0x1e1c6b0e, 0x18003b12, 0x352b4aa0, // mk.bg.uk_442 ceb.id.ms_555 so.ga.un_640 yo.vi.zu_322
+ // [1340]
+ 0x28006820, 0x00006b24, 0x1e1c01a0, 0x08171008, // ig.sw.un_850 ceb.un.un_900 en.id.ms_322 be.sr.uk_443
+ 0x1c091309, 0x0c033fa0, 0x07100405, 0x07002a19, // bh.hi.mr_444 af.nl.sv_322 ru.be.bg_333 mt.it.un_750
+ 0x101f18a9, 0x1f194aa4, 0x283552a0, 0x191f1011, // ga.cy.lt_544 yo.gl.cy_433 ha.zu.sw_322 lt.cy.gl_653
+ 0x10522555, 0x120b25ee, 0x04170708, 0x041b3bad, // eu.ha.lt_442 eu.es.hu_422 bg.sr.ru_443 so.tr.fi_643
+ // [1350]
+ 0x0b124aaf, 0x17321608, 0x12006e11, 0x0b4a120c, // yo.hu.es_655 hr.bs.sr_443 hmn.hu.un_630 hu.yo.es_543
+ 0x0a00071b, 0x19120b07, 0x04025204, 0x186b01a7, // bg.mk.un_770 es.hu.gl_432 ha.da.fi_332 en.ceb.ga_532
+ 0x100305a0, 0x3b356405, 0x32002908, 0x120b4aec, // fr.nl.lt_322 lg.zu.so_333 sl.bs.un_430 yo.es.hu_644
+ 0x0b001221, 0x07190a5a, 0x2800201b, 0x10080414, // hu.es.un_860 pt.gl.it_553 sq.sw.un_770 ru.uk.be_666
+ // [1360]
+ 0x202a07a4, 0x0a0811af, 0x12230b08, 0x0d002d2b, // it.mt.sq_433 ro.uk.mk_655 es.ca.hu_443 sk.cs.un_980
+ 0x1c2164a7, 0x3216095a, 0x080c3ba4, 0x10000712, // lg.jw.id_532 pl.hr.bs_553 so.sv.no_433 bg.be.un_640
+ 0x2100181b, 0x642a52a4, 0x32172a08, 0x2d0d21a0, // ar.fa.un_770 ha.mt.lg_433 mt.sr.bs_443 jw.cs.sk_322
+ 0x6800101a, 0x121821ad, 0x1f000414, 0x2d000d05, // lt.ig.un_760 fa.ar.ur_643 fi.cy.un_660 cs.sk.un_330
+ // [1370]
+ 0x1c00090c, 0x1c1304ee, 0x190a1fa4, 0x1f0410ad, // hi.mr.un_530 fi.et.id_422 cy.pt.gl_433 lt.fi.cy_643
+ 0x08040707, 0x02080c0c, 0x012d0d0d, 0x53000308, // bg.ru.uk_432 sv.no.da_543 cs.sk.en_554 nl.ht.un_430
+ 0x12190bac, 0x080f040c, 0x4a230b05, 0x08171104, // es.gl.hu_632 fi.lv.no_543 es.ca.yo_333 ro.sr.uk_332
+ 0x09000113, 0x120b2313, 0x20005523, 0x2a00091b, // en.pl.un_650 ca.es.hu_665 rw.sq.un_880 pl.mt.un_770
+ // [1380]
+ 0x12003b19, 0x18001214, 0x55001b0c, 0x0704080c, // so.hu.un_750 ur.ar.un_660 tr.rw.un_530 uk.ru.bg_543
+ 0x03003f0d, 0x2d000d2c, 0x292a0f55, 0x23060313, // af.nl.un_540 cs.sk.un_990 lv.mt.sl_442 nl.de.ca_665
+ 0x08000c04, 0x0d002d2a, 0x283b6407, 0x312a295a, // sv.no.un_320 sk.cs.un_970 lg.so.sw_432 sl.mt.az_553
+ 0x040a07a9, 0x0000232d, 0x13000104, 0x04070a14, // bg.mk.ru_544 ca.un.un_A00 en.et.un_320 mk.bg.ru_666
+ // [1390]
+ 0x05001804, 0x0d1c0912, 0x3f2506ad, 0x01006e07, // ga.fr.un_320 hi.mr.ne_654 de.eu.af_643 hmn.en.un_420
+ 0x0e0a23ee, 0x180f2712, 0x03190a04, 0x1a1c1ea9, // ca.pt.is_422 gd.lv.ga_654 pt.gl.nl_332 ms.id.tl_544
+ 0x0f1017a4, 0x2d0d090e, 0x2a00071b, 0x13553504, // sr.lt.lv_433 pl.cs.sk_555 it.mt.un_770 zu.rw.et_332
+ 0x0f001704, 0x526b3508, 0x0a100808, 0x1f000912, // sr.lv.un_320 zu.ceb.ha_443 uk.be.mk_443 pl.cy.un_640
+ // [13a0]
+ 0x2300010d, 0x28350908, 0x55352808, 0x0a2755a0, // en.ca.un_540 pl.zu.sw_443 sw.zu.rw_443 rw.gd.pt_322
+ 0x35556ba4, 0x1a3555af, 0x641f07ee, 0x554a35ac, // ceb.rw.zu_433 rw.zu.tl_655 it.cy.lg_422 zu.yo.rw_632
+ 0x52001004, 0x23001b07, 0x21556ba0, 0x1c1b1e0b, // lt.ha.un_320 tr.ca.un_420 ceb.rw.jw_322 ms.tr.id_542
+ 0x0c00060c, 0x1300292a, 0x3b286b09, 0x2300120c, // de.sv.un_530 sl.et.un_970 ceb.sw.so_444 hu.ca.un_530
+ // [13b0]
+ 0x08001f12, 0x32061ea0, 0x28645560, 0x292d0d07, // cy.no.un_640 ms.de.bs_322 rw.lg.sw_664 cs.sk.sl_432
+ 0x07081707, 0x196b64a7, 0x556864a4, 0x64552804, // sr.uk.bg_432 lg.ceb.gl_532 lg.ig.rw_433 sw.rw.lg_332
+ 0x1e001c21, 0x29351605, 0x13001c19, 0x35110baf, // id.ms.un_860 hr.zu.sl_333 mr.bh.un_750 es.ro.zu_655
+ 0x3f5564a4, 0x10001108, 0x11070808, 0x1000110c, // lg.rw.af_433 ro.lt.un_430 uk.bg.ro_443 ro.be.un_530
+ // [13c0]
+ 0x250b08a4, 0x071801a0, 0x0c0208a4, 0x25132907, // no.es.eu_433 en.ga.it_322 no.da.sv_433 sl.et.eu_432
+ 0x52002519, 0x081f0d0c, 0x10645504, 0x02033f13, // eu.ha.un_750 cs.cy.no_543 rw.lg.lt_332 af.nl.da_665
+ 0x3b00550c, 0x2511550c, 0x06080213, 0x100d0508, // rw.so.un_530 rw.ro.eu_543 da.no.de_665 fr.cs.lt_443
+ 0x0e352108, 0x10000422, 0x04131ea0, 0x0f002919, // jw.zu.is_443 ru.be.un_870 ms.et.fi_322 sl.lv.un_750
+ // [13d0]
+ 0x19050b08, 0x08101705, 0x1a006b19, 0x190b1a55, // es.fr.gl_443 sr.be.uk_333 ceb.tl.un_750 tl.es.gl_442
+ 0x6b0f1a07, 0x101b0aa0, 0x311b13a7, 0x0804115a, // tl.lv.ceb_432 pt.tr.lt_322 et.tr.az_532 ro.ru.uk_553
+ 0x3509250c, 0x00000d2d, 0x31001b20, 0x68643507, // eu.pl.zu_543 ne.un.un_A00 tr.az.un_850 zu.lg.ig_432
+ 0x07682ba6, 0x181f35ec, 0x00002006, 0x2300111b, // vi.ig.it_521 zu.cy.ga_644 sq.un.un_400 ro.ca.un_770
+ // [13e0]
+ 0x2b003512, 0x0302080c, 0x092d0dad, 0x35283ba4, // zu.vi.un_640 no.da.nl_543 cs.sk.pl_643 so.sw.zu_433
+ 0x092d19a7, 0x091c1308, 0x2b003519, 0x0e00351b, // gl.sk.pl_532 bh.mr.hi_443 zu.vi.un_750 zu.is.un_770
+ 0x3f122811, 0x11001902, 0x272b11ad, 0x3f002104, // sw.hu.af_653 gl.ro.un_220 ro.vi.gd_643 jw.af.un_320
+ 0x1100120d, 0x2a001319, 0x32171660, 0x062504ec, // hu.ro.un_540 et.mt.un_750 hr.sr.bs_664 fi.eu.de_644
+ // [13f0]
+ 0x11002314, 0x0a0407af, 0x033f53a4, 0x1b123112, // ca.ro.un_660 bg.ru.mk_655 ht.af.nl_433 az.hu.tr_654
+ 0x12001b1a, 0x0d130912, 0x172d2908, 0x0a001705, // tr.hu.un_760 hi.bh.ne_654 sl.sk.sr_443 sr.mk.un_330
+ 0x23005307, 0x121b2d0c, 0x28003504, 0x1b1629a4, // ht.ca.un_420 sk.tr.hu_543 zu.sw.un_320 sl.hr.tr_433
+ 0x033f23a9, 0x1e00180d, 0x356829ad, 0x53000504, // ca.af.nl_544 ga.ms.un_540 sl.ig.zu_643 fr.ht.un_320
+
+ // [1400]
+ 0x1a006b04, 0x170711a4, 0x0400120e, 0x2500121b, // ceb.tl.un_320 ro.bg.sr_433 hu.fi.un_550 hu.eu.un_770
+ 0x1c13090d, 0x09000421, 0x0c3f0312, 0x00003f06, // hi.bh.mr_554 fi.pl.un_860 nl.af.sv_654 af.un.un_400
+ 0x29321708, 0x19230aa0, 0x043b3507, 0x04070808, // sr.bs.sl_443 pt.ca.gl_322 zu.so.fi_432 uk.bg.ru_443
+ 0x182b27a7, 0x0d091302, 0x05110aa0, 0x0612200c, // gd.vi.ga_532 bh.hi.ne_222 pt.ro.fr_322 sq.hu.de_543
+ // [1410]
+ 0x09005308, 0x55283b04, 0x3f000309, 0x2500040d, // ht.pl.un_430 so.sw.rw_332 nl.af.un_440 fi.eu.un_540
+ 0x0d192d0b, 0x09005309, 0x0a001812, 0x24000134, // sk.gl.cs_542 ht.pl.un_440 ga.pt.un_640 iw.yi.un_A80
+ 0x110203ee, 0x20553b07, 0x0a041713, 0x1c09130b, // nl.da.ro_422 so.rw.sq_432 sr.ru.mk_665 bh.hi.mr_542
+ 0x3b551307, 0x09001c0b, 0x10041709, 0x2d0d29ee, // et.rw.so_432 mr.hi.un_520 sr.ru.be_444 sl.cs.sk_422
+ // [1420]
+ 0x17000719, 0x10040a09, 0x553b2113, 0x20553b12, // bg.sr.un_750 mk.ru.be_444 jw.so.rw_665 so.rw.sq_654
+ 0x643b310c, 0x03090708, 0x2a000719, 0x120b19ee, // az.so.lg_543 it.pl.nl_443 it.mt.un_750 gl.es.hu_422
+ 0x311b3b04, 0x1100070d, 0x1b005321, 0x1206020c, // so.tr.az_332 it.ro.un_540 ht.tr.un_860 da.de.hu_543
+ 0x17080aaf, 0x0d001c1b, 0x04170a55, 0x08321604, // mk.uk.sr_655 mr.ne.un_770 mk.sr.ru_442 hr.bs.no_332
+ // [1430]
+ 0x080211a4, 0x04081011, 0x163217ad, 0x06080255, // ro.da.no_433 be.uk.ru_653 sr.bs.hr_643 da.no.de_442
+ 0x00000e03, 0x3b1b6404, 0x520d1a0d, 0x55001c0d, // is.un.un_300 lg.tr.so_332 tl.cs.ha_554 id.rw.un_540
+ 0x3f001b12, 0x10000823, 0x1f002013, 0x0800111a, // tr.af.un_640 uk.be.un_880 sq.cy.un_650 ro.uk.un_760
+ 0x16001007, 0x040811a4, 0x131c09ec, 0x0a1b2304, // lt.hr.un_420 ro.uk.ru_433 hi.mr.bh_644 ca.tr.pt_332
+ // [1440]
+ 0x04132013, 0x0a070107, 0x040105ee, 0x08200e0c, // sq.et.fi_665 en.it.pt_432 fr.en.fi_422 is.sq.no_543
+ 0x17081005, 0x0a1707ec, 0x2a006434, 0x03003f02, // be.uk.sr_333 bg.sr.mk_644 lg.mt.un_A80 af.nl.un_220
+ 0x1710110d, 0x29122a14, 0x1032160d, 0x131b2507, // ro.be.sr_554 mt.hu.sl_666 hr.bs.lt_554 eu.tr.et_432
+ 0x04170a12, 0x110825ee, 0x29200e0c, 0x1c120e0c, // mk.sr.ru_654 eu.no.ro_422 is.sq.sl_543 is.hu.id_543
+ // [1450]
+ 0x1200101b, 0x1f001a08, 0x28532111, 0x13042013, // lt.hu.un_770 tl.cy.un_430 jw.ht.sw_653 sq.fi.et_665
+ 0x08000a1a, 0x12292aa4, 0x31040e0c, 0x3f0301a0, // mk.uk.un_760 mt.sl.hu_433 is.fi.az_543 en.nl.af_322
+ 0x052311a4, 0x2118120c, 0x081011ee, 0x01552855, // ro.ca.fr_433 ur.ar.fa_543 ro.be.uk_422 sw.rw.en_442
+ 0x20000821, 0x6404250c, 0x29091faf, 0x533b4aad, // no.sq.un_860 eu.fi.lg_543 cy.pl.sl_655 yo.so.ht_643
+ // [1460]
+ 0x202a0908, 0x06251b13, 0x092512ec, 0x09120e07, // pl.mt.sq_443 tr.eu.de_665 hu.eu.pl_644 is.hu.pl_432
+ 0x3b00121a, 0x2a1229a4, 0x122a1b0c, 0x112718ad, // hu.so.un_760 sl.hu.mt_433 tr.mt.hu_543 ga.gd.ro_643
+ 0x4a216ba0, 0x1f000c13, 0x00006e37, 0x233b1308, // ceb.jw.yo_322 sv.cy.un_650 hmn.un.un_B00 et.so.ca_443
+ 0x041708a4, 0x09001305, 0x2a0708a4, 0x07004a08, // uk.sr.ru_433 bh.hi.un_330 no.it.mt_433 yo.it.un_430
+ // [1470]
+ 0x64110f08, 0x1100521a, 0x09202aa9, 0x25000804, // lv.ro.lg_443 ha.ro.un_760 mt.sq.pl_544 no.eu.un_320
+ 0x101308a0, 0x2a2012ec, 0x1c1f1eee, 0x05110807, // no.et.lt_322 hu.sq.mt_644 ms.cy.id_422 no.ro.fr_432
+ 0x13030607, 0x11006e22, 0x3b231f07, 0x3125230c, // de.nl.et_432 hmn.ro.un_870 cy.ca.so_432 ca.eu.az_543
+ 0x27006e18, 0x051827a9, 0x1c351e0c, 0x2102080c, // hmn.gd.un_740 gd.ga.fr_544 ms.zu.id_543 no.da.jw_543
+ // [1480]
+ 0x08110414, 0x231e1c08, 0x0f00101b, 0x083f6e07, // ru.ro.uk_666 id.ms.ca_443 lt.lv.un_770 hmn.af.no_432
+ 0x3213230c, 0x0708040c, 0x00006b01, 0x3216170c, // ca.et.bs_543 ru.uk.bg_543 ceb.un.un_200 sr.hr.bs_543
+ 0x09211f04, 0x0527180d, 0x230b08a0, 0x19233b08, // cy.jw.pl_332 ga.gd.fr_554 no.es.ca_322 so.ca.gl_443
+ 0x06000c07, 0x081704a4, 0x55004a04, 0x35006e04, // sv.de.un_420 ru.sr.uk_433 yo.rw.un_320 hmn.zu.un_320
+ // [1490]
+ 0x1f000c19, 0x321629ec, 0x170a12a4, 0x0b002704, // sv.cy.un_750 sl.hr.bs_644 hu.pt.sr_433 gd.es.un_320
+ 0x0a5329ee, 0x09002104, 0x06130314, 0x1f213b11, // sl.ht.pt_422 jw.pl.un_320 nl.et.de_666 so.jw.cy_653
+ 0x3b002304, 0x1300092c, 0x6823130c, 0x113507ec, // ca.so.un_320 hi.bh.un_990 et.ca.ig_543 it.zu.ro_644
+ 0x0e3f1a07, 0x03063f07, 0x28001013, 0x4a2025a7, // tl.af.is_432 af.de.nl_432 lt.sw.un_650 eu.sq.yo_532
+ // [14a0]
+ 0x00001206, 0x0c126ba0, 0x0c000214, 0x23553ba4, // ur.un.un_400 ceb.hu.sv_322 da.sv.un_660 so.rw.ca_433
+ 0x64100f0c, 0x20003519, 0x2b000c04, 0x1c090dad, // lv.lt.lg_543 zu.sq.un_750 sv.vi.un_320 ne.hi.mr_643
+ 0x1200180c, 0x32001104, 0x28352113, 0x06033fa7, // ga.hu.un_530 ro.bs.un_320 jw.zu.sw_665 af.nl.de_532
+ 0x230a1ea0, 0x070f1ea0, 0x1c351e11, 0x0a10110c, // ms.pt.ca_322 ms.lv.it_322 ms.zu.id_653 ro.be.mk_543
+ // [14b0]
+ 0x313f0307, 0x1f001902, 0x070a0405, 0x230b2107, // nl.af.az_432 gl.cy.un_220 ru.mk.bg_333 jw.es.ca_432
+ 0x3b211e04, 0x2d0d12af, 0x0c024aad, 0x0a170708, // ms.jw.so_332 hu.cs.sk_655 yo.da.sv_643 bg.sr.mk_443
+ 0x170708a4, 0x0e082a12, 0x03023f08, 0x11200212, // uk.bg.sr_433 mt.no.is_654 af.da.nl_443 da.sq.ro_654
+ 0x17041004, 0x29002a04, 0x553564a0, 0x1e351c07, // be.ru.sr_332 mt.sl.un_320 lg.zu.rw_322 id.zu.ms_432
+ // [14c0]
+ 0x093b0da4, 0x0a3f03a4, 0x3f060a09, 0x03040608, // cs.so.pl_433 nl.af.pt_433 pt.de.af_444 de.fi.nl_443
+ 0x040807a9, 0x04132012, 0x35286407, 0x0400130d, // bg.uk.ru_544 sq.et.fi_654 lg.sw.zu_432 et.fi.un_540
+ 0x53020b05, 0x32006419, 0x20080c08, 0x293208a4, // es.da.ht_333 lg.bs.un_750 sv.no.sq_443 no.bs.sl_433
+ 0x033f270c, 0x045331ee, 0x3200160d, 0x06000305, // gd.af.nl_543 az.ht.fi_422 hr.bs.un_540 nl.de.un_330
+ // [14d0]
+ 0x3f040308, 0x08033faf, 0x0308200c, 0x1f190bad, // nl.fi.af_443 af.nl.no_655 sq.no.nl_543 es.gl.cy_643
+ 0x06286405, 0x1c000d14, 0x080417a4, 0x550464a9, // lg.sw.de_333 ne.mr.un_660 sr.ru.uk_433 lg.fi.rw_544
+ 0x20000b19, 0x1e1c64a0, 0x2a001704, 0x0f081011, // es.sq.un_750 lg.id.ms_322 sr.mt.un_320 lt.no.lv_653
+ 0x2d0d3b14, 0x1106190d, 0x016e0cad, 0x0d000919, // so.cs.sk_666 gl.de.ro_554 sv.hmn.en_643 hi.ne.un_750
+ // [14e0]
+ 0x08002a0c, 0x18000521, 0x521c21a4, 0x0900010d, // mt.no.un_530 fr.ga.un_860 jw.id.ha_433 en.pl.un_540
+ 0x291e1c05, 0x211e1cec, 0x123525a4, 0x0b2307ad, // id.ms.sl_333 id.ms.jw_644 eu.zu.hu_433 it.ca.es_643
+ 0x0e0c3f04, 0x29200908, 0x092010ac, 0x5300021a, // af.sv.is_332 pl.sq.sl_443 lt.sq.pl_632 da.ht.un_760
+ 0x211e1c60, 0x07001114, 0x35006412, 0x1000040d, // id.ms.jw_664 ro.it.un_660 lg.zu.un_640 fi.lt.un_540
+ // [14f0]
+ 0x172d0d08, 0x08002919, 0x2700180d, 0x032008a0, // cs.sk.sr_443 sl.no.un_750 ga.gd.un_540 no.sq.nl_322
+ 0x2b001119, 0x18273fa4, 0x170a11ee, 0x043509a4, // ro.vi.un_750 af.gd.ga_433 ro.mk.sr_422 pl.zu.fi_433
+ 0x28100f05, 0x03063f09, 0x08200309, 0x06003f08, // lv.lt.sw_333 af.de.nl_444 nl.sq.no_444 af.de.un_430
+ 0x1c130955, 0x2920030c, 0x1b1168a7, 0x1c001e22, // hi.bh.mr_442 nl.sq.sl_543 ig.ro.tr_532 ms.id.un_870
+ // [1500]
+ 0x06000a04, 0x08171005, 0x00002903, 0x1f006b04, // pt.de.un_320 be.sr.uk_333 sl.un.un_300 ceb.cy.un_320
+ 0x11070509, 0x07040a05, 0x08070a09, 0x07080a13, // fr.it.ro_444 mk.ru.bg_333 mk.bg.uk_444 mk.uk.bg_665
+ 0x040a0712, 0x17040804, 0x3555310c, 0x00000b2d, // bg.mk.ru_654 uk.ru.sr_332 az.rw.zu_543 bn.un.un_A00
+ 0x551805a4, 0x01000c13, 0x1c00130c, 0x2800550c, // fr.ga.rw_433 sv.en.un_650 bh.mr.un_530 rw.sw.un_530
+ // [1510]
+ 0x0a111708, 0x0a081109, 0x0506110b, 0x0d091309, // sr.ro.mk_443 ro.uk.mk_444 ro.de.fr_542 bh.hi.ne_444
+ 0x1e1c4a09, 0x0800170e, 0x09000d0c, 0x2d000d0c, // yo.id.ms_444 sr.uk.un_550 cs.pl.un_530 cs.sk.un_530
+ 0x0d09130b, 0x170811ee, 0x08070aa0, 0x0a170705, // bh.hi.ne_542 ro.uk.sr_422 mk.bg.uk_322 bg.sr.mk_333
+ 0x08110704, 0x1b6431a0, 0x01006b07, 0x09000c0d, // bg.ro.uk_332 az.lg.tr_322 ceb.en.un_420 sv.pl.un_540
+ // [1520]
+ 0x0b000a09, 0x1c1309a9, 0x12001f1a, 0x2700182c, // pt.es.un_440 hi.bh.mr_544 cy.hu.un_760 ga.gd.un_990
+ 0x1f136ea0, 0x10081108, 0x0d2d01a7, 0x3f03010c, // hmn.et.cy_322 ro.uk.be_443 en.sk.cs_532 en.nl.af_543
+ 0x55645311, 0x0600121a, 0x080a1709, 0x1b3152ad, // ht.lg.rw_653 hu.de.un_760 sr.mk.uk_444 ha.az.tr_643
+ 0x00000d01, 0x53521aee, 0x231901a4, 0x18002729, // ne.un.un_200 tl.ha.ht_422 en.gl.ca_433 gd.ga.un_960
+ // [1530]
+ 0x1e0c1cee, 0x035206a4, 0x01000505, 0x3f032aa9, // id.sv.ms_422 de.ha.nl_433 fr.en.un_330 mt.nl.af_544
+ 0x05121155, 0x1f065307, 0x05182711, 0x17110a08, // ro.hu.fr_442 ht.de.cy_432 gd.ga.fr_653 mk.ro.sr_443
+ 0x063f2a04, 0x080411a7, 0x2a000a08, 0x2353010c, // mt.af.de_332 ro.ru.uk_532 pt.mt.un_430 en.ht.ca_543
+ 0x04003b1b, 0x070a0409, 0x20321bee, 0x06030b02, // so.fi.un_770 ru.mk.bg_444 tr.bs.sq_422 es.nl.de_222
+ // [1540]
+ 0x3f082a07, 0x040817a4, 0x0c001c07, 0x29033f0c, // mt.no.af_432 sr.uk.ru_433 id.sv.un_420 af.nl.sl_543
+ 0x01215302, 0x2b000b02, 0x0e000622, 0x3f211011, // ht.jw.en_222 es.vi.un_220 de.is.un_870 lt.jw.af_653
+ 0x25685507, 0x06216ea0, 0x08132aa0, 0x090d130c, // rw.ig.eu_432 hmn.jw.de_322 mt.et.no_322 bh.ne.hi_543
+ 0x1800272c, 0x2d0c02a9, 0x0b005507, 0x070417a0, // gd.ga.un_990 da.sv.sk_544 rw.es.un_420 sr.ru.bg_322
+ // [1550]
+ 0x021e64ad, 0x091c1313, 0x1f1e64a7, 0x1f000419, // lg.ms.da_643 bh.mr.hi_665 lg.ms.cy_532 fi.cy.un_750
+ 0x0c022a04, 0x1e1c64ee, 0x351c31a0, 0x110901a4, // mt.da.sv_332 lg.id.ms_422 az.id.zu_322 en.pl.ro_433
+ 0x10002a13, 0x031e1fec, 0x190a1aa9, 0x080264af, // mt.lt.un_650 cy.ms.nl_644 tl.pt.gl_544 lg.da.no_655
+ 0x08001f19, 0x6e3f1ea4, 0x210a1e07, 0x08001c04, // cy.no.un_750 ms.af.hmn_433 ms.pt.jw_432 id.no.un_320
+ // [1560]
+ 0x321004a9, 0x3f6e2012, 0x07080460, 0x190b6e60, // fi.lt.bs_544 sq.hmn.af_654 ru.uk.bg_664 hmn.es.gl_664
+ 0x29006422, 0x083b1f07, 0x640e1308, 0x05000802, // lg.sl.un_870 cy.so.no_432 et.is.lg_443 no.fr.un_220
+ 0x03062007, 0x10001b2a, 0x3f036eac, 0x19000a23, // sq.de.nl_432 tr.lt.un_970 hmn.nl.af_632 pt.gl.un_880
+ 0x351f6407, 0x11042505, 0x13000413, 0x01000705, // lg.cy.zu_432 eu.fi.ro_333 fi.et.un_650 it.en.un_330
+ // [1570]
+ 0x0407105a, 0x0b190a0d, 0x13043b12, 0x04091013, // be.bg.ru_553 pt.gl.es_554 so.fi.et_654 lt.pl.fi_665
+ 0x4a0e2307, 0x1c072102, 0x036e3fa9, 0x17070a07, // ca.is.yo_432 jw.it.id_222 af.hmn.nl_544 mk.bg.sr_432
+ 0x31006e0c, 0x06353ba4, 0x23190b04, 0x08041702, // hmn.az.un_530 so.zu.de_433 es.gl.ca_332 sr.ru.uk_222
+ 0x171632a7, 0x1c3168a7, 0x0b000723, 0x216b0660, // bs.hr.sr_532 ig.az.id_532 it.es.un_880 de.ceb.jw_664
+ // [1580]
+ 0x08025205, 0x18002708, 0x0200640e, 0x10000609, // ha.da.no_333 gd.ga.un_430 lg.da.un_550 de.lt.un_440
+ 0x071108a4, 0x6e003f11, 0x053f0d0c, 0x25080c08, // uk.ro.bg_433 af.hmn.un_630 cs.af.fr_543 sv.no.eu_443
+ 0x21006412, 0x102305ec, 0x1b1e1ca9, 0x06092104, // lg.jw.un_640 fr.ca.lt_644 id.ms.tr_544 jw.pl.de_332
+ 0x20091ba0, 0x29190b5a, 0x100f6eec, 0x4a1219ec, // tr.pl.sq_322 es.gl.sl_553 hmn.lv.lt_644 gl.hu.yo_644
+ // [1590]
+ 0x2a002507, 0x03063fa9, 0x2b172102, 0x0a070809, // eu.mt.un_420 af.de.nl_544 jw.sr.vi_222 uk.bg.mk_444
+ 0x17040812, 0x0c311b08, 0x1f12100d, 0x070a1007, // uk.ru.sr_654 tr.az.sv_443 lt.hu.cy_554 be.mk.bg_432
+ 0x35001107, 0x1b1e1c0d, 0x09001321, 0x2923190c, // ro.zu.un_420 id.ms.tr_554 bh.hi.un_860 gl.ca.sl_543
+ 0x072311ee, 0x2300250d, 0x27281a0c, 0x071704a4, // ro.ca.it_422 eu.ca.un_540 tl.sw.gd_543 ru.sr.bg_433
+ // [15a0]
+ 0x23190b02, 0x0704080e, 0x232b1807, 0x1c106404, // es.gl.ca_222 uk.ru.bg_555 ga.vi.ca_432 lg.lt.id_332
+ 0x04000707, 0x2d021112, 0x04000713, 0x0d0913ad, // bg.ru.un_420 ro.da.sk_654 bg.ru.un_650 bh.hi.ne_643
+ 0x18002120, 0x21001812, 0x06031fee, 0x09005302, // fa.ar.un_850 ar.fa.un_640 cy.nl.de_422 ht.pl.un_220
+ 0x17041105, 0x231901a0, 0x1b321705, 0x0f003f02, // ro.ru.sr_333 en.gl.ca_322 sr.bs.tr_333 af.lv.un_220
+ // [15b0]
+ 0x112801ee, 0x04081104, 0x1c130904, 0x53016ba0, // en.sw.ro_422 ro.uk.ru_332 hi.bh.mr_332 ceb.en.ht_322
+ 0x311828ee, 0x080704af, 0x1a000212, 0x080a1704, // sw.ga.az_422 ru.bg.uk_655 da.tl.un_640 sr.mk.uk_332
+ 0x1b4a04ee, 0x100a04af, 0x04004a0c, 0x270b6e04, // fi.yo.tr_422 ru.mk.be_655 yo.fi.un_530 hmn.es.gd_332
+ 0x0d130908, 0x211e1c5a, 0x070408a0, 0x0a170405, // hi.bh.ne_443 id.ms.jw_553 uk.ru.bg_322 ru.sr.mk_333
+ // [15c0]
+ 0x0c040ba7, 0x0b000104, 0x070403af, 0x23353f13, // es.fi.sv_532 en.es.un_320 nl.fi.it_655 af.zu.ca_665
+ 0x21000905, 0x0a2007a0, 0x35002514, 0x25190bee, // pl.jw.un_330 it.sq.pt_322 eu.zu.un_660 es.gl.eu_422
+ 0x12000419, 0x033f5304, 0x20000323, 0x102d0f14, // fi.hu.un_750 ht.af.nl_332 nl.sq.un_880 lv.sk.lt_666
+ 0x356b6404, 0x011105a0, 0x0602080c, 0x640a2aa4, // lg.ceb.zu_332 fr.ro.en_322 no.da.de_543 mt.pt.lg_433
+ // [15d0]
+ 0x6b001a05, 0x04170a08, 0x040a110c, 0x08006b07, // tl.ceb.un_330 mk.sr.ru_443 ro.mk.ru_543 ceb.no.un_420
+ 0x21001a07, 0x1f21640c, 0x25005214, 0x1a002104, // tl.jw.un_420 lg.jw.cy_543 ha.eu.un_660 jw.tl.un_320
+ 0x00002d06, 0x2a321604, 0x08100409, 0x1732160d, // sk.un.un_400 hr.bs.mt_332 ru.be.uk_444 hr.bs.sr_554
+ 0x32311b60, 0x0900040c, 0x0b532502, 0x201101a4, // tr.az.bs_664 fi.pl.un_530 eu.ht.es_222 en.ro.sq_433
+ // [15e0]
+ 0x0a070409, 0x11321608, 0x520605a7, 0x04081060, // ru.bg.mk_444 hr.bs.ro_443 fr.de.ha_532 be.uk.ru_664
+ 0x1200212b, 0x11321704, 0x1a000b04, 0x531b6b05, // fa.ur.un_980 sr.bs.ro_332 es.tl.un_320 ceb.tr.ht_333
+ 0x3b001e08, 0x110231ee, 0x35073207, 0x53094a08, // ms.so.un_430 az.da.ro_422 bs.it.zu_432 yo.pl.ht_443
+ 0x0600041a, 0x121b13a7, 0x1e1c25ec, 0x3f040e14, // fi.de.un_760 et.tr.hu_532 eu.id.ms_644 is.fi.af_666
+ // [15f0]
+ 0x2511130c, 0x2504135a, 0x29162da4, 0x4a640713, // et.ro.eu_543 et.fi.eu_553 sk.hr.sl_433 it.lg.yo_665
+ 0x080c5204, 0x03321614, 0x2a00020d, 0x1704080b, // ha.sv.no_332 hr.bs.nl_666 da.mt.un_540 uk.ru.sr_542
+ 0x0e080213, 0x12080260, 0x6b211a0c, 0x1c0d1309, // da.no.is_665 da.no.hu_664 tl.jw.ceb_543 bh.ne.mr_444
+ 0x11080408, 0x170a0760, 0x07000a05, 0x10040704, // ru.uk.ro_443 bg.mk.sr_664 mk.bg.un_330 bg.ru.be_332
+ // [1600]
+ 0x1c001e08, 0x32001714, 0x0c000d08, 0x231101a4, // ms.id.un_430 sr.bs.un_660 cs.sv.un_430 en.ro.ca_433
+ 0x0764250c, 0x1b252802, 0x0c005214, 0x32291609, // eu.lg.it_543 sw.eu.tr_222 ha.sv.un_660 hr.sl.bs_444
+ 0x040f10af, 0x1f5301a4, 0x19000b23, 0x082523a4, // lt.lv.fi_655 en.ht.cy_433 es.gl.un_880 ca.eu.no_433
+ 0x19000108, 0x09122509, 0x07081107, 0x17000419, // en.gl.un_430 eu.hu.pl_444 ro.uk.bg_432 ru.sr.un_750
+ // [1610]
+ 0x3f0802a7, 0x13002508, 0x07170408, 0x3b00190c, // da.no.af_532 eu.et.un_430 ru.sr.bg_443 gl.so.un_530
+ 0x06000c0d, 0x0a11010c, 0x09250e55, 0x0a0417a0, // sv.de.un_540 en.ro.pt_543 is.eu.pl_442 sr.ru.mk_322
+ 0x1c1e2d07, 0x10000408, 0x04081002, 0x19000b2c, // sk.ms.id_432 ru.be.un_430 be.uk.ru_222 es.gl.un_990
+ 0x3525680e, 0x3b2d0d55, 0x03002313, 0x230a25a9, // ig.eu.zu_555 cs.sk.so_442 ca.nl.un_650 eu.pt.ca_544
+ // [1620]
+ 0x32000d07, 0x0a23075a, 0x120d03a4, 0x203503a7, // cs.bs.un_420 it.ca.pt_553 nl.cs.hu_433 nl.zu.sq_532
+ 0x081017ad, 0x1211070e, 0x0d3135ad, 0x27000507, // sr.be.uk_643 it.ro.hu_555 zu.az.cs_643 fr.gd.un_420
+ 0x07041708, 0x6b001a21, 0x0a001c02, 0x2b000702, // sr.ru.bg_443 tl.ceb.un_860 id.pt.un_220 it.vi.un_220
+ 0x03062508, 0x21011302, 0x2b006b2a, 0x06030a09, // eu.de.nl_443 et.en.jw_222 ceb.vi.un_970 pt.nl.de_444
+ // [1630]
+ 0x07040aa4, 0x0a0804a4, 0x0e531b0c, 0x1c2055a4, // mk.ru.bg_433 ru.uk.mk_433 tr.ht.is_543 rw.sq.id_433
+ 0x04001719, 0x1a006b36, 0x061b0312, 0x10042011, // sr.ru.un_750 ceb.tl.un_AA0 nl.tr.de_654 sq.fi.lt_653
+ 0x0a070ba0, 0x04003204, 0x20281305, 0x1e2b1ca0, // es.it.pt_322 bs.fi.un_320 et.sw.sq_333 id.vi.ms_322
+ 0x0d162d07, 0x1e313b05, 0x110a070c, 0x10040713, // sk.hr.cs_432 so.az.ms_333 bg.mk.ro_543 bg.ru.be_665
+ // [1640]
+ 0x2d0d290c, 0x1000042b, 0x3b311fa9, 0x2a3b07a4, // sl.cs.sk_543 ru.be.un_980 cy.az.so_544 it.so.mt_433
+ 0x011321a9, 0x0300010c, 0x08163ba0, 0x06000c09, // jw.et.en_544 en.nl.un_530 so.hr.no_322 sv.de.un_440
+ 0x13100f5a, 0x073b1307, 0x0600130e, 0x0a001222, // lv.lt.et_553 et.so.it_432 et.de.un_550 hu.pt.un_870
+ 0x17080a04, 0x03003513, 0x2d5509ee, 0x17070a0d, // mk.uk.sr_332 zu.nl.un_650 pl.rw.sk_422 mk.bg.sr_554
+ // [1650]
+ 0x12211860, 0x21000c07, 0x080a1007, 0x0602010c, // ar.fa.ur_664 sv.jw.un_420 be.mk.uk_432 en.da.de_543
+ 0x0d040c0c, 0x0f000608, 0x130b2aee, 0x27033fac, // sv.fi.cs_543 de.lv.un_430 mt.es.et_422 af.nl.gd_632
+ 0x06171605, 0x1f311b0b, 0x2b001a09, 0x2a0d20ad, // hr.sr.de_333 tr.az.cy_542 tl.vi.un_440 sq.cs.mt_643
+ 0x25645508, 0x07040814, 0x0f000709, 0x19250b07, // rw.lg.eu_443 uk.ru.bg_666 it.lv.un_440 es.eu.gl_432
+ // [1660]
+ 0x0d000918, 0x3100080d, 0x0a00101a, 0x10040813, // hi.ne.un_740 no.az.un_540 be.mk.un_760 uk.ru.be_665
+ 0x1c000d1a, 0x31001a07, 0x01171602, 0x2a001705, // ne.mr.un_760 tl.az.un_420 hr.sr.en_222 sr.mt.un_330
+ 0x0d5525a4, 0x313b2a0c, 0x28556405, 0x3b2303a0, // eu.rw.cs_433 mt.so.az_543 lg.rw.sw_333 nl.ca.so_322
+ 0x2a00180c, 0x132d0d0d, 0x3f2821a4, 0x3f121fa0, // ga.mt.un_530 cs.sk.et_554 jw.sw.af_433 cy.hu.af_322
+ // [1670]
+ 0x00001f03, 0x210c1708, 0x251101a4, 0x11080405, // cy.un.un_300 sr.sv.jw_443 en.ro.eu_433 ru.uk.ro_333
+ 0x00002037, 0x03005202, 0x0710040d, 0x04000a08, // sq.un.un_B00 ha.nl.un_220 ru.be.bg_554 mk.ru.un_430
+ 0x070901a7, 0x1325050c, 0x101c2108, 0x110810a4, // en.pl.it_532 fr.eu.et_543 jw.id.lt_443 be.uk.ro_433
+ 0x6b2a01a4, 0x52032d0d, 0x1b0812af, 0x10060fad, // en.mt.ceb_433 sk.nl.ha_554 hu.no.tr_655 lv.de.lt_643
+ // [1680]
+ 0x030412ad, 0x1b12250c, 0x080203a9, 0x202d290c, // hu.fi.nl_643 eu.hu.tr_543 nl.da.no_544 sl.sk.sq_543
+ 0x292d0d5a, 0x100f0713, 0x532a2107, 0x11040860, // cs.sk.sl_553 it.lv.lt_665 jw.mt.ht_432 uk.ru.ro_664
+ 0x1712010c, 0x120f2907, 0x04081712, 0x04110813, // en.hu.sr_543 sl.lv.hu_432 sr.uk.ru_654 uk.ro.ru_665
+ 0x172a10a4, 0x3f100309, 0x02002902, 0x0a0501a9, // lt.mt.sr_433 nl.lt.af_444 sl.da.un_220 en.fr.pt_544
+ // [1690]
+ 0x13000608, 0x1b001021, 0x2d0d1fee, 0x2a000f1a, // de.et.un_430 lt.tr.un_860 cy.cs.sk_422 lv.mt.un_760
+ 0x04033f55, 0x3f000318, 0x20000313, 0x040a170c, // af.nl.fi_442 nl.af.un_740 nl.sq.un_650 sr.mk.ru_543
+ 0x4a001a02, 0x3f00030d, 0x0103070c, 0x32002a09, // tl.yo.un_220 nl.af.un_540 it.nl.en_543 mt.bs.un_440
+ 0x1803270d, 0x251b3112, 0x10090f12, 0x12001304, // gd.nl.ga_554 az.tr.eu_654 lv.pl.lt_654 et.hu.un_320
+ // [16a0]
+ 0x06005208, 0x03063faf, 0x25041012, 0x21531e07, // ha.de.un_430 af.de.nl_655 lt.fi.eu_654 ms.ht.jw_432
+ 0x11103baf, 0x1200181a, 0x0f292a60, 0x00001e24, // so.lt.ro_655 ar.ur.un_760 mt.sl.lv_664 ms.un.un_900
+ 0x032718af, 0x032513a4, 0x0d092d55, 0x3f23030c, // ga.gd.nl_655 et.eu.nl_433 sk.pl.cs_442 nl.ca.af_543
+ 0x250c08af, 0x25001221, 0x1e005209, 0x6b081aec, // no.sv.eu_655 hu.eu.un_860 ha.ms.un_440 tl.no.ceb_644
+ // [16b0]
+ 0x18276ba4, 0x684a53a4, 0x2a001c08, 0x53001f07, // ceb.gd.ga_433 ht.yo.ig_433 id.mt.un_430 cy.ht.un_420
+ 0x0b193ba7, 0x0f001014, 0x1e211cac, 0x061125a7, // so.gl.es_532 lt.lv.un_660 id.jw.ms_632 eu.ro.de_532
+ 0x292a0f13, 0x1253250c, 0x070a04a4, 0x3b002a1a, // lv.mt.sl_665 eu.ht.hu_543 ru.mk.bg_433 mt.so.un_760
+ 0x0400291a, 0x52681ca4, 0x0710040c, 0x19052012, // sl.fi.un_760 id.ig.ha_433 ru.be.bg_543 sq.fr.gl_654
+ // [16c0]
+ 0x071827ad, 0x290e2aa9, 0x521f2aad, 0x3b531a13, // gd.ga.it_643 mt.is.sl_544 mt.cy.ha_643 tl.ht.so_665
+ 0x13292a09, 0x05012304, 0x290f2a12, 0x0a1711ec, // mt.sl.et_444 ca.en.fr_332 mt.lv.sl_654 ro.sr.mk_644
+ 0x292117a0, 0x17002508, 0x291721a4, 0x190a23a9, // sr.jw.sl_322 eu.sr.un_430 jw.sr.sl_433 ca.pt.gl_544
+ 0x11000a12, 0x130910a4, 0x53002109, 0x070a1712, // pt.ro.un_640 lt.pl.et_433 jw.ht.un_440 sr.mk.bg_654
+ // [16d0]
+ 0x2a211755, 0x210d200d, 0x04130ca9, 0x033f28af, // sr.jw.mt_442 sq.cs.jw_554 sv.et.fi_544 sw.af.nl_655
+ 0x0a0717a9, 0x101e1c08, 0x211e5202, 0x02310804, // sr.bg.mk_544 id.ms.lt_443 ha.ms.jw_222 no.az.da_332
+ 0x2900250c, 0x0a071709, 0x25001022, 0x04000a22, // eu.sl.un_530 sr.bg.mk_444 lt.eu.un_870 mk.ru.un_870
+ 0x23000a04, 0x10070aa0, 0x07041060, 0x080a04a4, // pt.ca.un_320 mk.bg.be_322 be.ru.bg_664 ru.mk.uk_433
+ // [16e0]
+ 0x101c1105, 0x10130404, 0x2a53010c, 0x35321602, // ro.id.lt_333 fi.et.lt_332 en.ht.mt_543 hr.bs.zu_222
+ 0x07180407, 0x1c005319, 0x19350ba7, 0x0300211b, // fi.ga.it_432 ht.id.un_750 es.zu.gl_532 jw.nl.un_770
+ 0x11211c07, 0x04131ba4, 0x3b005208, 0x2000110c, // id.jw.ro_432 tr.et.fi_433 ha.so.un_430 ro.sq.un_530
+ 0x6b000913, 0x16202da0, 0x0306250d, 0x13681b09, // pl.ceb.un_650 sk.sq.hr_322 eu.de.nl_554 tr.ig.et_444
+ // [16f0]
+ 0x05000a04, 0x201b350b, 0x29001604, 0x23190ba0, // pt.fr.un_320 zu.tr.sq_542 hr.sl.un_320 es.gl.ca_322
+ 0x1b003f21, 0x07641b08, 0x03120f12, 0x6e6b010c, // af.tr.un_860 tr.lg.it_443 lv.hu.nl_654 en.ceb.hmn_543
+ 0x1c521e08, 0x4a111255, 0x3f031b0e, 0x6b001a13, // ms.ha.id_443 hu.ro.yo_442 tr.nl.af_555 tl.ceb.un_650
+ 0x0a071004, 0x123b13ad, 0x10000423, 0x062827ad, // be.bg.mk_332 et.so.hu_643 ru.be.un_880 gd.sw.de_643
+ // [1700]
+ 0x06001f23, 0x01253f08, 0x1f230509, 0x641b5508, // cy.de.un_880 af.eu.en_443 fr.ca.cy_444 rw.tr.lg_443
+ 0x0a3b19ee, 0x06001c04, 0x050601a4, 0x0600272b, // gl.so.pt_422 id.de.un_320 en.de.fr_433 gd.de.un_980
+ 0x211b1cee, 0x10213507, 0x0e000d14, 0x0e2d0d05, // id.tr.jw_422 zu.jw.lt_432 cs.is.un_660 cs.sk.is_333
+ 0x1c0d090e, 0x0f002702, 0x170903a4, 0x2a2032ee, // hi.ne.mr_555 gd.lv.un_220 nl.pl.sr_433 bs.sq.mt_422
+ // [1710]
+ 0x642006af, 0x04132507, 0x07201b0b, 0x5220040c, // de.sq.lg_655 eu.et.fi_432 tr.sq.it_542 fi.sq.ha_543
+ 0x1300550d, 0x040711ee, 0x050723a4, 0x0900110c, // rw.et.un_540 ro.bg.ru_422 ca.it.fr_433 ro.pl.un_530
+ 0x683f030c, 0x1b520409, 0x3f000620, 0x102b1107, // nl.af.ig_543 fi.ha.tr_444 de.af.un_850 ro.vi.lt_432
+ 0x04000804, 0x0a00060d, 0x520e0409, 0x1b004a1b, // no.fi.un_320 de.pt.un_540 fi.is.ha_444 yo.tr.un_770
+ // [1720]
+ 0x64211ca7, 0x35002914, 0x080c06a4, 0x130435a4, // id.jw.lg_532 sl.zu.un_660 de.sv.no_433 zu.fi.et_433
+ 0x211c68af, 0x19040705, 0x19000b0b, 0x0b001904, // ig.id.jw_655 it.fi.gl_333 es.gl.un_520 gl.es.un_320
+ 0x55071b0c, 0x1c000204, 0x110b6e04, 0x092d0d05, // tr.it.rw_543 da.id.un_320 hmn.es.ro_332 cs.sk.pl_333
+ 0x0b231911, 0x550f1ba4, 0x2b002821, 0x233b010c, // gl.ca.es_653 tr.lv.rw_433 sw.vi.un_860 en.so.ca_543
+ // [1730]
+ 0x120428ee, 0x270c010c, 0x52282b0b, 0x0d0506ad, // sw.fi.hu_422 en.sv.gd_543 vi.sw.ha_542 de.fr.cs_643
+ 0x17292d12, 0x11000a0e, 0x0300062b, 0x2b280a60, // sk.sl.sr_654 pt.ro.un_550 de.nl.un_980 pt.sw.vi_664
+ 0x12002504, 0x08311b0d, 0x0118060b, 0x020618ec, // eu.hu.un_320 tr.az.no_554 de.ga.en_542 ga.de.da_644
+ 0x1b003105, 0x0000160f, 0x3b00230d, 0x31556413, // az.tr.un_330 hr.un.un_600 ca.so.un_540 lg.rw.az_665
+ // [1740]
+ 0x061f5255, 0x20006804, 0x2a3b04a4, 0x04000c07, // ha.cy.de_442 ig.sq.un_320 fi.so.mt_433 sv.fi.un_420
+ 0x03003f34, 0x316b1a08, 0x1b0401a4, 0x2331520c, // af.nl.un_A80 tl.ceb.az_443 en.fi.tr_433 ha.az.ca_543
+ 0x1b0c2aa4, 0x13200414, 0x040710a0, 0x31000522, // mt.sv.tr_433 fi.sq.et_666 be.bg.ru_322 fr.az.un_870
+ 0x0b3b2d07, 0x1a6b0808, 0x25096b07, 0x0811040c, // sk.so.es_432 no.ceb.tl_443 ceb.pl.eu_432 ru.ro.uk_543
+ // [1750]
+ 0x030608a7, 0x3f0c230c, 0x10002914, 0x12002304, // no.de.nl_532 ca.sv.af_543 sl.lt.un_660 ca.hu.un_320
+ 0x281e2a09, 0x03040807, 0x1e282912, 0x0200031b, // mt.ms.sw_444 no.fi.nl_432 sl.sw.ms_654 nl.da.un_770
+ 0x64000421, 0x0f006b12, 0x0a002b29, 0x29001b0d, // fi.lg.un_860 ceb.lv.un_640 vi.pt.un_960 tr.sl.un_540
+ 0x3f0307a4, 0x1c090d09, 0x17041009, 0x53001608, // it.nl.af_433 ne.hi.mr_444 be.ru.sr_444 hr.ht.un_430
+ // [1760]
+ 0x0b0a1fec, 0x64001a02, 0x0a002b18, 0x2b001a2a, // cy.pt.es_644 tl.lg.un_220 vi.pt.un_740 tl.vi.un_970
+ 0x1b313b12, 0x643f3bad, 0x060c0255, 0x2a3b09af, // so.az.tr_654 so.af.lg_643 da.sv.de_442 pl.so.mt_655
+ 0x255535ad, 0x180627ec, 0x0400082c, 0x110f10a4, // zu.rw.eu_643 gd.de.ga_644 uk.ru.un_990 lt.lv.ro_433
+ 0x6b3127a4, 0x2d001604, 0x08041012, 0x3f021fa4, // gd.az.ceb_433 hr.sk.un_320 be.ru.uk_654 cy.da.af_433
+ // [1770]
+ 0x00000601, 0x2a3b31af, 0x2300060d, 0x09080c0d, // de.un.un_200 az.so.mt_655 de.ca.un_540 sv.no.pl_554
+ 0x131c09ac, 0x08041704, 0x061335ad, 0x170a11ec, // hi.mr.bh_632 sr.ru.uk_332 zu.et.de_643 ro.mk.sr_644
+ 0x05010708, 0x1b313513, 0x030608a4, 0x081306a4, // it.en.fr_443 zu.az.tr_665 no.de.nl_433 de.et.no_433
+ 0x6b001a0e, 0x05000621, 0x21321704, 0x040708a6, // tl.ceb.un_550 de.fr.un_860 sr.bs.jw_332 uk.bg.ru_521
+ // [1780]
+ 0x1a64040c, 0x3f0603af, 0x03082508, 0x25190bec, // fi.lg.tl_543 nl.de.af_655 eu.no.nl_443 es.gl.eu_644
+ 0x32162914, 0x01021302, 0x13080e07, 0x13000821, // sl.hr.bs_666 et.da.en_222 is.no.et_432 no.et.un_860
+ 0x02550512, 0x06000819, 0x1c2120ad, 0x04001902, // fr.rw.da_654 no.de.un_750 sq.jw.id_643 gl.fi.un_220
+ 0x1b133204, 0x08060302, 0x0e00181a, 0x211e1c55, // bs.et.tr_332 nl.de.no_222 ga.is.un_760 id.ms.jw_442
+ // [1790]
+ 0x1b003513, 0x2900020e, 0x13170855, 0x07006404, // zu.tr.un_650 da.sl.un_550 no.sr.et_442 lg.it.un_320
+ 0x08060ea0, 0x131c0907, 0x08130e12, 0x186b1a08, // is.de.no_322 hi.mr.bh_432 is.et.no_654 tl.ceb.ga_443
+ 0x311b5502, 0x20000305, 0x0c00082b, 0x1a216bad, // rw.tr.az_222 nl.sq.un_330 no.sv.un_980 ceb.jw.tl_643
+ 0x35000619, 0x29001708, 0x040811ec, 0x06003512, // de.zu.un_750 sr.sl.un_430 ro.uk.ru_644 zu.de.un_640
+ // [17a0]
+ 0x20001a07, 0x081011a6, 0x1e0e0607, 0x09000d1b, // tl.sq.un_420 ro.be.uk_521 de.is.ms_432 ne.hi.un_770
+ 0x1b0f0ea0, 0x3b250e0c, 0x08130c11, 0x27161ba0, // is.lv.tr_322 is.eu.so_543 sv.et.no_653 tr.hr.gd_322
+ 0x0b002d07, 0x2d0952a4, 0x16292da4, 0x13000808, // sk.es.un_420 ha.pl.sk_433 sk.sl.hr_433 no.et.un_430
+ 0x2a000913, 0x23001909, 0x0400100e, 0x080201a4, // pl.mt.un_650 gl.ca.un_440 be.ru.un_550 en.da.no_433
+ // [17b0]
+ 0x060e1ea9, 0x1f002719, 0x10030fee, 0x0c200fa4, // ms.is.de_544 gd.cy.un_750 lv.nl.lt_422 lv.sq.sv_433
+ 0x0e130405, 0x520c1f0c, 0x1801050c, 0x351a6ba7, // fi.et.is_333 cy.sv.ha_543 fr.en.ga_543 ceb.tl.zu_532
+ 0x05182bad, 0x6b0a1a07, 0x1c130960, 0x530c08ad, // vi.ga.fr_643 tl.pt.ceb_432 hi.bh.mr_664 no.sv.ht_643
+ 0x28005202, 0x1e2d0d0c, 0x0f111205, 0x2d0d02a9, // ha.sw.un_220 cs.sk.ms_543 hu.ro.lv_333 da.cs.sk_544
+ // [17c0]
+ 0x183b1307, 0x0c086411, 0x0a0f110c, 0x27181a04, // et.so.ga_432 lg.no.sv_653 ro.lv.pt_543 tl.ga.gd_332
+ 0x0a3f110c, 0x07033f09, 0x20103fec, 0x07080405, // ro.af.pt_543 af.nl.it_444 af.lt.sq_644 ru.uk.bg_333
+ 0x07170a05, 0x00001101, 0x27001820, 0x11080aa7, // mk.sr.bg_333 ro.un.un_200 ga.gd.un_850 pt.no.ro_532
+ 0x17290fa4, 0x10123fa4, 0x030b28ad, 0x11000f08, // lv.sl.sr_433 af.hu.lt_433 sw.es.nl_643 lv.ro.un_430
+ // [17d0]
+ 0x0900160d, 0x021b5307, 0x27351a04, 0x10122508, // hr.pl.un_540 ht.tr.da_432 tl.zu.gd_332 eu.hu.lt_443
+ 0x0804100e, 0x55002012, 0x07001104, 0x17001b0c, // be.ru.uk_555 sq.rw.un_640 ro.it.un_320 tr.sr.un_530
+ 0x0a3f060c, 0x2d3b6b0c, 0x0f0a11a4, 0x0c08020c, // de.af.pt_543 ceb.so.sk_543 ro.pt.lv_433 da.no.sv_543
+ 0x6b0d2dee, 0x09000313, 0x0f6b100c, 0x2d0d310e, // sk.cs.ceb_422 nl.pl.un_650 lt.ceb.lv_543 az.cs.sk_555
+ // [17e0]
+ 0x531b310c, 0x1b0f190d, 0x6b551a13, 0x0b0a070b, // az.tr.ht_543 gl.lv.tr_554 tl.rw.ceb_665 it.pt.es_542
+ 0x063f08a0, 0x1b004a14, 0x102a4aa9, 0x080302ad, // no.af.de_322 yo.tr.un_660 yo.mt.lt_544 da.nl.no_643
+ 0x180227a6, 0x03000b04, 0x6b001a19, 0x19110a5a, // gd.da.ga_521 es.nl.un_320 tl.ceb.un_750 pt.ro.gl_553
+ 0x100f11a4, 0x033b64ac, 0x0e00521b, 0x1a006b2b, // ro.lv.lt_433 lg.so.nl_632 ha.is.un_770 ceb.tl.un_980
+ // [17f0]
+ 0x3f0352a0, 0x0c203f55, 0x2a556b11, 0x0c3b6b0d, // ha.nl.af_322 af.sq.sv_442 ceb.rw.mt_653 ceb.so.sv_554
+ 0x071711a4, 0x120f11a4, 0x06016ba0, 0x071211a4, // ro.sr.bg_433 ro.lv.hu_433 ceb.en.de_322 ro.hu.it_433
+ 0x1e1c0e14, 0x04070aa9, 0x20090805, 0x06001f12, // is.id.ms_666 mk.bg.ru_544 no.pl.sq_333 cy.de.un_640
+ 0x02131f60, 0x6b292a04, 0x01004a05, 0x312d0d13, // cy.et.da_664 mt.sl.ceb_332 yo.en.un_330 cs.sk.az_665
+
+ // [1800]
+ 0x10072a07, 0x3b006b13, 0x213b4aee, 0x1f2d0d13, // mt.it.lt_432 ceb.so.un_650 yo.so.jw_422 cs.sk.cy_665
+ 0x08230ca0, 0x08120ead, 0x0a1105ec, 0x292d0d13, // sv.ca.no_322 is.hu.no_643 fr.ro.pt_644 cs.sk.sl_665
+ 0x0e080caf, 0x32002907, 0x130c0807, 0x07001107, // sv.no.is_655 sl.bs.un_420 no.sv.et_432 ro.bg.un_420
+ 0x55003202, 0x0f052304, 0x2a072312, 0x071710ee, // bs.rw.un_220 ca.fr.lv_332 ca.it.mt_654 be.sr.bg_422
+ // [1810]
+ 0x0a1711a0, 0x55126ba7, 0x06080cee, 0x21002704, // ro.sr.mk_322 ceb.hu.rw_532 sv.no.de_422 gd.jw.un_320
+ 0x08041108, 0x1c0d09a7, 0x08000c23, 0x53644aee, // ro.ru.uk_443 hi.ne.mr_532 sv.no.un_880 yo.lg.ht_422
+ 0x2a190a0d, 0x08000a14, 0x352832a0, 0x08002a1b, // pt.gl.mt_554 mk.uk.un_660 bs.sw.zu_322 mt.no.un_770
+ 0x070425a4, 0x07171008, 0x17000c19, 0x0a170713, // eu.fi.it_433 be.sr.bg_443 sv.sr.un_750 bg.sr.mk_665
+ // [1820]
+ 0x190b0a0c, 0x17040a0c, 0x1b321702, 0x1f005304, // pt.es.gl_543 mk.ru.sr_543 sr.bs.tr_222 ht.cy.un_320
+ 0x6b1a0404, 0x17270e04, 0x25030607, 0x6b1a6ea9, // fi.tl.ceb_332 is.gd.sr_332 de.nl.eu_432 hmn.tl.ceb_544
+ 0x526b31a4, 0x0b190a0c, 0x0c080304, 0x6e000808, // az.ceb.ha_433 pt.gl.es_543 nl.no.sv_332 no.hmn.un_430
+ 0x13003b1e, 0x08040a02, 0x6e6b1a0d, 0x0c005204, // so.et.un_830 mk.ru.uk_222 tl.ceb.hmn_554 ha.sv.un_320
+ // [1830]
+ 0x0200121a, 0x080206ee, 0x08002012, 0x27190a0d, // hu.da.un_760 de.da.no_422 sq.no.un_640 pt.gl.gd_554
+ 0x09001c19, 0x12030655, 0x12000c12, 0x3b286ead, // mr.hi.un_750 de.nl.hu_442 sv.hu.un_640 hmn.sw.so_643
+ 0x2d0d01a4, 0x3f000608, 0x071104a4, 0x110208a7, // en.cs.sk_433 de.af.un_430 ru.ro.bg_433 no.da.ro_532
+ 0x016e1ca0, 0x02000812, 0x1f190a13, 0x09290a08, // id.hmn.en_322 no.da.un_640 pt.gl.cy_665 pt.sl.pl_443
+ // [1840]
+ 0x09002918, 0x6e000314, 0x04001020, 0x12060d0c, // sl.pl.un_740 nl.hmn.un_660 be.ru.un_850 cs.de.hu_543
+ 0x120e0a0b, 0x2d0d35af, 0x1106230b, 0x1c12040c, // pt.is.hu_542 zu.cs.sk_655 ca.de.ro_542 fi.hu.id_543
+ 0x6e200d0c, 0x06006b04, 0x0d1c13ad, 0x06002502, // cs.sq.hmn_543 ceb.de.un_320 bh.mr.ne_643 eu.de.un_220
+ 0x0d12290d, 0x27190a60, 0x0c030207, 0x0a001913, // sl.hu.cs_554 pt.gl.gd_664 da.nl.sv_432 gl.pt.un_650
+ // [1850]
+ 0x530501a0, 0x060a0da9, 0x032506a4, 0x6e193bad, // en.fr.ht_322 cs.pt.de_544 de.eu.nl_433 so.gl.hmn_643
+ 0x063f02a4, 0x64005212, 0x1a006b35, 0x13210aad, // da.af.de_433 ha.lg.un_640 ceb.tl.un_A90 pt.jw.et_643
+ 0x4a1b5205, 0x6e005308, 0x033f0c55, 0x64005208, // ha.tr.yo_333 ht.hmn.un_430 sv.af.nl_442 ha.lg.un_430
+ 0x2d090daf, 0x1f3f1a60, 0x0a230d08, 0x112d0d07, // cs.pl.sk_655 tl.af.cy_664 cs.ca.pt_443 cs.sk.ro_432
+ // [1860]
+ 0x170a110c, 0x1f0820a6, 0x09001c18, 0x55100cee, // ro.mk.sr_543 sq.no.cy_521 mr.hi.un_740 sv.lt.rw_422
+ 0x10190a5a, 0x312d0dee, 0x03001802, 0x00001806, // pt.gl.lt_553 cs.sk.az_422 ga.nl.un_220 ga.un.un_400
+ 0x1a001e0d, 0x0d001c12, 0x64523513, 0x64190a5a, // ms.tl.un_540 mr.ne.un_640 zu.ha.lg_665 pt.gl.lg_553
+ 0x0a000804, 0x020e0c60, 0x04171007, 0x0a0d2307, // no.pt.un_320 sv.is.da_664 be.sr.ru_432 ca.cs.pt_432
+ // [1870]
+ 0x18683fee, 0x0a290daf, 0x0c160d07, 0x131a3555, // af.ig.ga_422 cs.sl.pt_655 cs.hr.sv_432 zu.tl.et_442
+ 0x17290da4, 0x1a642809, 0x032a0cec, 0x19006b0c, // cs.sl.sr_433 sw.lg.tl_444 sv.mt.nl_644 ceb.gl.un_530
+ 0x040a0709, 0x20001705, 0x11070805, 0x29060da4, // bg.mk.ru_444 sr.sq.un_330 uk.bg.ro_333 cs.de.sl_433
+ 0x64683507, 0x352564a4, 0x21181211, 0x110a0412, // zu.ig.lg_432 lg.eu.zu_433 ur.ar.fa_653 ru.mk.ro_654
+ // [1880]
+ 0x00001c2d, 0x19130ba7, 0x00001a24, 0x190b2309, // mr.un.un_A00 es.et.gl_532 tl.un.un_900 ca.es.gl_444
+ 0x3f2528ad, 0x09131cad, 0x06005302, 0x06080209, // sw.eu.af_643 mr.bh.hi_643 ht.de.un_220 da.no.de_444
+ 0x250564ec, 0x2d0d5209, 0x2d0d0fa4, 0x12190b08, // lg.fr.eu_644 ha.cs.sk_444 lv.cs.sk_433 es.gl.hu_443
+ 0x11001a04, 0x292d0d02, 0x11000a0d, 0x170a0755, // tl.ro.un_320 cs.sk.sl_222 pt.ro.un_540 bg.mk.sr_442
+ // [1890]
+ 0x0f001a04, 0x216435af, 0x351764ad, 0x16290908, // tl.lv.un_320 zu.lg.jw_655 lg.sr.zu_643 pl.sl.hr_443
+ 0x31521304, 0x17070808, 0x3500640e, 0x08002708, // et.ha.az_332 uk.bg.sr_443 lg.zu.un_550 gd.no.un_430
+ 0x256468ee, 0x27112312, 0x1135640c, 0x1c2a6407, // ig.lg.eu_422 ca.ro.gd_654 lg.zu.ro_543 lg.mt.id_432
+ 0x0c000604, 0x0b000504, 0x0f001013, 0x00002b01, // de.sv.un_320 fr.es.un_320 lt.lv.un_650 vi.un.un_200
+ // [18a0]
+ 0x07000a22, 0x0f080ca0, 0x080a170c, 0x08170aa0, // mk.bg.un_870 sv.no.lv_322 sr.mk.uk_543 mk.sr.uk_322
+ 0x1c1309a4, 0x35553112, 0x160d2dee, 0x08020e02, // hi.bh.mr_433 az.rw.zu_654 sk.cs.hr_422 is.da.no_222
+ 0x09006411, 0x0d001c14, 0x12180ea9, 0x0400072a, // lg.pl.un_630 mr.ne.un_660 is.ga.hu_544 bg.ru.un_970
+ 0x04001a02, 0x00001103, 0x1e1c3b05, 0x2d200907, // tl.fi.un_220 ro.un.un_300 so.id.ms_333 pl.sq.sk_432
+ // [18b0]
+ 0x07170a0e, 0x091c0da9, 0x68004a12, 0x3b006b0c, // mk.sr.bg_555 ne.mr.hi_544 yo.ig.un_640 ceb.so.un_530
+ 0x0c000907, 0x110a0fa7, 0x0e000c0d, 0x533564ec, // pl.sv.un_420 lv.pt.ro_532 sv.is.un_540 lg.zu.ht_644
+ 0x1c001313, 0x2528110c, 0x04071105, 0x085523a0, // bh.mr.un_650 ro.sw.eu_543 ro.bg.ru_333 ca.rw.no_322
+ 0x11252812, 0x2b001702, 0x0b2511a9, 0x190906ee, // sw.eu.ro_654 sr.vi.un_220 ro.eu.es_544 de.pl.gl_422
+ // [18c0]
+ 0x685301a4, 0x042513a4, 0x04001312, 0x2500281a, // en.ht.ig_433 et.eu.fi_433 et.fi.un_640 sw.eu.un_760
+ 0x0d1125a9, 0x04001323, 0x171108ad, 0x04033fad, // eu.ro.cs_544 et.fi.un_880 uk.ro.sr_643 af.nl.fi_643
+ 0x0b033fa0, 0x1e251107, 0x030113a9, 0x252811af, // af.nl.es_322 ro.eu.ms_432 et.en.nl_544 ro.sw.eu_655
+ 0x05002d0d, 0x4a001122, 0x3f3b0308, 0x3f3b01ad, // sk.fr.un_540 ro.yo.un_870 nl.so.af_443 en.so.af_643
+ // [18d0]
+ 0x13006405, 0x07001702, 0x1a000120, 0x6b6811a6, // lg.et.un_330 sr.bg.un_220 en.tl.un_850 ro.ig.ceb_521
+ 0x041a0e0c, 0x1b002a04, 0x3f031307, 0x031364a9, // is.tl.fi_543 mt.tr.un_320 et.nl.af_432 lg.et.nl_544
+ 0x4a2529a0, 0x1b203bad, 0x016b09ad, 0x3f00032b, // sl.eu.yo_322 so.sq.tr_643 pl.ceb.en_643 nl.af.un_980
+ 0x133b64a9, 0x091c2104, 0x190b07ee, 0x12102108, // lg.so.et_544 jw.id.pl_332 it.es.gl_422 jw.lt.hu_443
+ // [18e0]
+ 0x121821a9, 0x28250d0c, 0x350a110c, 0x55000919, // fa.ar.ur_544 cs.eu.sw_543 ro.pt.zu_543 pl.rw.un_750
+ 0x530868a0, 0x2d043205, 0x1c091312, 0x04100fee, // ig.no.ht_322 bs.fi.sk_333 bh.hi.mr_654 lv.lt.fi_422
+ 0x12005304, 0x35001113, 0x28251160, 0x10000914, // ht.hu.un_320 ro.zu.un_650 ro.eu.sw_664 pl.lt.un_660
+ 0x25001134, 0x11080412, 0x09006402, 0x23132a0c, // ro.eu.un_A80 ru.uk.ro_654 lg.pl.un_220 mt.et.ca_543
+ // [18f0]
+ 0x3b000307, 0x2d000d07, 0x3f031308, 0x1923530d, // nl.so.un_420 cs.sk.un_420 et.nl.af_443 ht.ca.gl_554
+ 0x29001f05, 0x131f0c08, 0x040a11a4, 0x090306ad, // cy.sl.un_330 sv.cy.et_443 ro.mk.ru_433 de.nl.pl_643
+ 0x3f0364ec, 0x2d0d0912, 0x25031308, 0x2b3f0112, // lg.nl.af_644 pl.cs.sk_654 et.nl.eu_443 en.af.vi_654
+ 0x3b03135a, 0x250411a4, 0x130364a9, 0x110407a9, // et.nl.so_553 ro.fi.eu_433 lg.nl.et_544 bg.ru.ro_544
+ // [1900]
+ 0x132d290c, 0x0c2d0d13, 0x03136412, 0x25192aa4, // sl.sk.et_543 cs.sk.sv_665 lg.et.nl_654 mt.gl.eu_433
+ 0x1a1e1c5a, 0x1c000d05, 0x0c131a02, 0x12002912, // id.ms.tl_553 ne.mr.un_330 tl.et.sv_222 sl.hu.un_640
+ 0x0a071008, 0x0d0a23a0, 0x681202ad, 0x09004a02, // be.bg.mk_443 ca.pt.cs_322 da.hu.ig_643 yo.pl.un_220
+ 0x1800121a, 0x10001c04, 0x0c3f08ee, 0x16003204, // ur.ar.un_760 id.lt.un_320 no.af.sv_422 bs.hr.un_320
+ // [1910]
+ 0x03010605, 0x0d00290c, 0x043b0c04, 0x281a2904, // de.en.nl_333 sl.cs.un_530 sv.so.fi_332 sl.tl.sw_332
+ 0x25000c04, 0x2d120c12, 0x01002104, 0x0700640d, // sv.eu.un_320 sv.hu.sk_654 jw.en.un_320 lg.it.un_540
+ 0x00000c24, 0x2500061a, 0x050c2104, 0x55121007, // sv.un.un_900 de.eu.un_760 jw.sv.fr_332 lt.hu.rw_432
+ 0x1b002912, 0x55524a07, 0x110c1607, 0x0b000705, // sl.tr.un_640 yo.ha.rw_432 hr.sv.ro_432 it.es.un_330
+ // [1920]
+ 0x1f003b19, 0x21001818, 0x131e1c55, 0x2a001307, // so.cy.un_750 ar.fa.un_740 id.ms.et_442 et.mt.un_420
+ 0x16122907, 0x17200dee, 0x4a122d12, 0x0a000722, // sl.hu.hr_432 cs.sq.sr_422 sk.hu.yo_654 bg.mk.un_870
+ 0x07000409, 0x25002713, 0x6e2301ad, 0x182325a4, // ru.bg.un_440 gd.eu.un_650 en.ca.hmn_643 eu.ca.ga_433
+ 0x32171608, 0x0725180c, 0x2a006b21, 0x02232907, // hr.sr.bs_443 ga.eu.it_543 ceb.mt.un_860 sl.ca.da_432
+ // [1930]
+ 0x6e190bad, 0x12004a12, 0x521323ad, 0x071806a7, // es.gl.hmn_643 yo.hu.un_640 ca.et.ha_643 de.ga.it_532
+ 0x1000121a, 0x08111707, 0x3b280304, 0x182725af, // hu.lt.un_760 sr.ro.uk_432 nl.sw.so_332 eu.gd.ga_655
+ 0x28680411, 0x04102a11, 0x07003204, 0x32051ea4, // fi.ig.sw_653 mt.lt.fi_653 bs.it.un_320 ms.fr.bs_433
+ 0x05236eec, 0x3f07040c, 0x063f090c, 0x27251812, // hmn.ca.fr_644 fi.it.af_543 pl.af.de_543 ga.eu.gd_654
+ // [1940]
+ 0x190a06af, 0x55002d07, 0x3b686e08, 0x28046814, // de.pt.gl_655 sk.rw.un_420 hmn.ig.so_443 ig.fi.sw_666
+ 0x276e5308, 0x250627a4, 0x09000f14, 0x18001114, // ht.hmn.gd_443 gd.de.eu_433 lv.pl.un_660 ro.ga.un_660
+ 0x1b1a6b09, 0x03683b07, 0x0e130f0c, 0x09350807, // ceb.tl.tr_444 so.ig.nl_432 lv.et.is_543 no.zu.pl_432
+ 0x04004a0d, 0x01035202, 0x1b316eec, 0x090407ee, // yo.fi.un_540 ha.nl.en_222 hmn.az.tr_644 it.fi.pl_422
+ // [1950]
+ 0x070a08a9, 0x3b003f21, 0x21530412, 0x3b002a0c, // uk.mk.bg_544 af.so.un_860 fi.ht.jw_654 mt.so.un_530
+ 0x06090faf, 0x190a130c, 0x350e01a4, 0x07171007, // lv.pl.de_655 et.pt.gl_543 en.is.zu_433 be.sr.bg_432
+ 0x21131ca0, 0x21001704, 0x12002812, 0x3f3b1104, // id.et.jw_322 sr.jw.un_320 sw.hu.un_640 ro.so.af_332
+ 0x0f001108, 0x10200511, 0x080e1fa4, 0x1827250c, // ro.lv.un_430 fr.sq.lt_653 cy.is.no_433 eu.gd.ga_543
+ // [1960]
+ 0x2a3b6eec, 0x03020e05, 0x0113250c, 0x3f040704, // hmn.so.mt_644 is.da.nl_333 eu.et.en_543 it.fi.af_332
+ 0x0600012a, 0x04190b0c, 0x271801ad, 0x110328a6, // en.de.un_970 es.gl.fi_543 en.ga.gd_643 sw.nl.ro_521
+ 0x0f2a6e14, 0x07000f21, 0x01003b19, 0x35136ba7, // hmn.mt.lv_666 lv.it.un_860 so.en.un_750 ceb.et.zu_532
+ 0x21001802, 0x02030607, 0x0a001704, 0x1a3f68a7, // ar.fa.un_220 de.nl.da_432 sr.mk.un_320 ig.af.tl_532
+ // [1970]
+ 0x6b00090d, 0x10190bec, 0x3f133b09, 0x17081002, // pl.ceb.un_540 es.gl.lt_644 so.et.af_444 be.uk.sr_222
+ 0x190b2313, 0x060b04a4, 0x04076807, 0x4a190bee, // ca.es.gl_665 fi.es.de_433 ig.it.fi_432 es.gl.yo_422
+ 0x1f1b0eec, 0x520e3512, 0x12006402, 0x070a17a4, // is.tr.cy_644 zu.is.ha_654 lg.hu.un_220 sr.mk.bg_433
+ 0x08290c08, 0x6b1a0b0d, 0x0b002a07, 0x52120eaf, // sv.sl.no_443 es.tl.ceb_554 mt.es.un_420 is.hu.ha_655
+ // [1980]
+ 0x07040809, 0x03016b07, 0x1e315208, 0x08120407, // uk.ru.bg_444 ceb.en.nl_432 ha.az.ms_443 fi.hu.no_432
+ 0x0a190b08, 0x3f2801a4, 0x040a1005, 0x0813040b, // es.gl.pt_443 en.sw.af_433 be.mk.ru_333 fi.et.no_542
+ 0x2d00091a, 0x03003f09, 0x09001f1a, 0x0c086b09, // pl.sk.un_760 af.nl.un_440 cy.pl.un_760 ceb.no.sv_444
+ 0x070817a0, 0x18002705, 0x091c130d, 0x03011f07, // sr.uk.bg_322 gd.ga.un_330 bh.mr.hi_554 cy.en.nl_432
+ // [1990]
+ 0x53524a07, 0x020306ad, 0x3f0b6b02, 0x1c091302, // yo.ha.ht_432 de.nl.da_643 ceb.es.af_222 bh.hi.mr_222
+ 0x036b06ee, 0x18002704, 0x4a03680e, 0x191f0b0c, // de.ceb.nl_422 gd.ga.un_320 ig.nl.yo_555 es.cy.gl_543
+ 0x4a006804, 0x281c1ea4, 0x2a000214, 0x03004a08, // ig.yo.un_320 ms.id.sw_433 da.mt.un_660 yo.nl.un_430
+ 0x28215204, 0x68015307, 0x091c130c, 0x6400550e, // ha.jw.sw_332 ht.en.ig_432 bh.mr.hi_543 rw.lg.un_550
+ // [19a0]
+ 0x1c1b1e08, 0x0d292d12, 0x52286eee, 0x092d1007, // ms.tr.id_443 sk.sl.cs_654 hmn.sw.ha_422 lt.sk.pl_432
+ 0x04070805, 0x07000413, 0x191f2308, 0x18000519, // uk.bg.ru_333 fi.it.un_650 ca.cy.gl_443 fr.ga.un_750
+ 0x4a006814, 0x10080c04, 0x08070c09, 0x0a1107af, // ig.yo.un_660 sv.no.lt_332 sv.it.no_444 bg.ro.mk_655
+ 0x09292507, 0x1e1c01a4, 0x18000a04, 0x1b0e25ad, // eu.sl.pl_432 en.id.ms_433 pt.ga.un_320 eu.is.tr_643
+ // [19b0]
+ 0x1a003b07, 0x1a006e1a, 0x3b00101b, 0x1227070d, // so.tl.un_420 hmn.tl.un_760 lt.so.un_770 it.gd.hu_554
+ 0x2b076b07, 0x04101107, 0x00000a2d, 0x29171602, // ceb.it.vi_432 ro.be.ru_432 pt.un.un_A00 hr.sr.sl_222
+ 0x04081008, 0x3b00530e, 0x190a0baf, 0x3b641f0d, // be.uk.ru_443 ht.so.un_550 es.pt.gl_655 cy.lg.so_554
+ 0x13190b0c, 0x524a3b07, 0x3f1c1aa0, 0x23000719, // es.gl.et_543 so.yo.ha_432 tl.id.af_322 it.ca.un_750
+ // [19c0]
+ 0x0f2a3b08, 0x09531f04, 0x3b00010c, 0x050a0107, // so.mt.lv_443 cy.ht.pl_332 en.so.un_530 en.pt.fr_432
+ 0x6e1f1a12, 0x0a001e05, 0x1f1807ec, 0x13003204, // tl.cy.hmn_654 ms.pt.un_330 it.ga.cy_644 bs.et.un_320
+ 0x0c3510a4, 0x0c003202, 0x2d100aee, 0x182d6e02, // lt.zu.sv_433 bs.sv.un_220 pt.lt.sk_422 hmn.sk.ga_222
+ 0x07052a07, 0x1b1231ec, 0x0d001704, 0x02001307, // mt.fr.it_432 az.hu.tr_644 sr.cs.un_320 et.da.un_420
+ // [19d0]
+ 0x1e100a07, 0x061b31af, 0x17090d07, 0x0e4a19a9, // pt.lt.ms_432 az.tr.de_655 cs.pl.sr_432 gl.yo.is_544
+ 0x311b3b13, 0x1b002102, 0x18013bad, 0x35000705, // so.tr.az_665 jw.tr.un_220 so.en.ga_643 it.zu.un_330
+ 0x253b1f0c, 0x040a075a, 0x2d2a0d0c, 0x12311ba4, // cy.so.eu_543 bg.mk.ru_553 cs.mt.sk_543 tr.az.hu_433
+ 0x12002902, 0x071004ec, 0x6b681204, 0x3f2a07a0, // sl.hu.un_220 ru.be.bg_644 hu.ig.ceb_332 it.mt.af_322
+ // [19e0]
+ 0x04000d0d, 0x2d000c0c, 0x310f1ba9, 0x0a00172a, // cs.fi.un_540 sv.sk.un_530 tr.lv.az_544 sr.mk.un_970
+ 0x1c1e12ad, 0x09132aa4, 0x2a1f6eec, 0x3b002a12, // hu.ms.id_643 mt.et.pl_433 hmn.cy.mt_644 mt.so.un_640
+ 0x0d001c09, 0x0f180a04, 0x1800210e, 0x3b005304, // mr.ne.un_440 pt.ga.lv_332 fa.ar.un_550 ht.so.un_320
+ 0x232a01a4, 0x04000c19, 0x090d13a7, 0x01533b0c, // en.mt.ca_433 sv.fi.un_750 bh.ne.hi_532 so.ht.en_543
+ // [19f0]
+ 0x1e1c0ea4, 0x4a255311, 0x122a53a9, 0x0a100707, // is.id.ms_433 ht.eu.yo_653 ht.mt.hu_544 bg.be.mk_432
+ 0x21001223, 0x080a0414, 0x13080208, 0x11001e02, // ur.fa.un_880 ru.mk.uk_666 da.no.et_443 ms.ro.un_220
+ 0x110a0714, 0x0a2301a4, 0x100711a0, 0x2000100e, // bg.mk.ro_666 en.ca.pt_433 ro.bg.be_322 lt.sq.un_550
+ 0x1b123105, 0x23002508, 0x04081713, 0x041020ad, // az.hu.tr_333 eu.ca.un_430 sr.uk.ru_665 sq.lt.fi_643
+ // [1a00]
+ 0x21121814, 0x20321b0e, 0x07041104, 0x2b005305, // ar.ur.fa_666 tr.bs.sq_555 ro.ru.bg_332 ht.vi.un_330
+ 0x03171604, 0x090d13ac, 0x05002504, 0x4a001819, // hr.sr.nl_332 bh.ne.hi_632 eu.fr.un_320 ga.yo.un_750
+ 0x643555ee, 0x1b005322, 0x252a0708, 0x04101707, // rw.zu.lg_422 ht.tr.un_870 it.mt.eu_443 sr.be.ru_432
+ 0x533b280b, 0x106b1aa4, 0x52002704, 0x2400012a, // sw.so.ht_542 tl.ceb.lt_433 gd.ha.un_320 iw.yi.un_970
+ // [1a10]
+ 0x211218ac, 0x030f0911, 0x100417a4, 0x0f002b0c, // ar.ur.fa_632 pl.lv.nl_653 sr.ru.be_433 vi.lv.un_530
+ 0x00006406, 0x0f000923, 0x02000c09, 0x1b006419, // lg.un.un_400 pl.lv.un_880 sv.da.un_440 lg.tr.un_750
+ 0x200f10a4, 0x17070a12, 0x10070a07, 0x08001702, // lt.lv.sq_433 mk.bg.sr_654 mk.bg.be_432 sr.uk.un_220
+ 0x090d13a9, 0x09000f1a, 0x043119ee, 0x00000503, // bh.ne.hi_544 lv.pl.un_760 gl.az.fi_422 fr.un.un_300
+ // [1a20]
+ 0x0f000c04, 0x1f000c04, 0x0f000914, 0x0c083fa4, // sv.lv.un_320 sv.cy.un_320 pl.lv.un_660 af.no.sv_433
+ 0x29000704, 0x64003b1a, 0x27001812, 0x1218210e, // it.sl.un_320 so.lg.un_760 ga.gd.un_640 fa.ar.ur_555
+ 0x2b2d10ee, 0x1800271b, 0x211310a0, 0x520131ee, // lt.sk.vi_422 gd.ga.un_770 lt.et.jw_322 az.en.ha_422
+ 0x1a003204, 0x111c2804, 0x01002704, 0x0f002d1a, // bs.tl.un_320 sw.id.ro_332 gd.en.un_320 sk.lv.un_760
+ // [1a30]
+ 0x3100101a, 0x080c1ca0, 0x0a4a1fad, 0x6b255307, // lt.az.un_760 id.sv.no_322 cy.yo.pt_643 ht.eu.ceb_432
+ 0x0717110c, 0x291632a7, 0x071710ad, 0x18002119, // ro.sr.bg_543 bs.hr.sl_532 be.sr.bg_643 fa.ar.un_750
+ 0x0a001919, 0x1c3b6407, 0x0c041211, 0x0d0913a4, // gl.pt.un_750 lg.so.id_432 hu.fi.sv_653 bh.hi.ne_433
+ 0x18002114, 0x3b1f0c02, 0x3f2a010c, 0x04000618, // fa.ar.un_660 sv.cy.so_222 en.mt.af_543 de.fi.un_740
+ // [1a40]
+ 0x3f080205, 0x1b043204, 0x0c001213, 0x060e1304, // da.no.af_333 bs.fi.tr_332 hu.sv.un_650 et.is.de_332
+ 0x521e1b07, 0x0e001b21, 0x1c090da9, 0x07000a09, // tr.ms.ha_432 tr.is.un_860 ne.hi.mr_544 mk.bg.un_440
+ 0x02005307, 0x12002a0c, 0x0a001002, 0x29130407, // ht.da.un_420 mt.hu.un_530 be.mk.un_220 fi.et.sl_432
+ 0x04060c0d, 0x29005304, 0x061e1c0d, 0x111f0e0c, // sv.de.fi_554 ht.sl.un_320 id.ms.de_554 is.cy.ro_543
+ // [1a50]
+ 0x04000c0d, 0x072a1f0b, 0x2a1119a4, 0x042a0708, // sv.fi.un_540 cy.mt.it_542 gl.ro.mt_433 it.mt.fi_443
+ 0x19311ba4, 0x29000e12, 0x1b0221ee, 0x25131fee, // tr.az.gl_433 is.sl.un_640 jw.da.tr_422 cy.et.eu_422
+ 0x16103bee, 0x0b002d08, 0x0e001f08, 0x04120c0c, // so.lt.hr_422 sk.es.un_430 cy.is.un_430 sv.hu.fi_543
+ 0x2112180e, 0x182d0d02, 0x080b1ea0, 0x1a28520c, // ar.ur.fa_555 cs.sk.ga_222 ms.es.no_322 ha.sw.tl_543
+ // [1a60]
+ 0x2a0413a4, 0x6b1e1a0d, 0x0f000307, 0x00002437, // et.fi.mt_433 tl.ms.ceb_554 nl.lv.un_420 yi.un.un_B00
+ 0x2a080c07, 0x0435520b, 0x081707a0, 0x1e001c22, // sv.no.mt_432 ha.zu.fi_542 bg.sr.uk_322 id.ms.un_870
+ 0x08311b55, 0x06003f0c, 0x52282104, 0x0735550c, // tr.az.no_442 af.de.un_530 jw.sw.ha_332 rw.zu.it_543
+ 0x25000923, 0x556406ec, 0x1a6b01af, 0x6b3128ad, // pl.eu.un_880 de.lg.rw_644 en.ceb.tl_655 sw.az.ceb_643
+ // [1a70]
+ 0x080c1214, 0x12110505, 0x646b1a5a, 0x3b005207, // hu.sv.no_666 fr.ro.hu_333 tl.ceb.lg_553 ha.so.un_420
+ 0x20003b1b, 0x28002909, 0x02001a05, 0x55521aa9, // so.sq.un_770 sl.sw.un_440 tl.da.un_330 tl.ha.rw_544
+ 0x5228255a, 0x3b00210e, 0x521b2509, 0x3b001e04, // eu.sw.ha_553 jw.so.un_550 eu.tr.ha_444 ms.so.un_320
+ 0x112d0d08, 0x23190a11, 0x350a64a4, 0x0e005209, // cs.sk.ro_443 pt.gl.ca_653 lg.pt.zu_433 ha.is.un_440
+ // [1a80]
+ 0x17080aa0, 0x271e1c0d, 0x0925120e, 0x10080a07, // mk.uk.sr_322 id.ms.gd_554 hu.eu.pl_555 mk.uk.be_432
+ 0x0800102b, 0x2100520e, 0x645507a9, 0x1300090c, // be.uk.un_980 ha.jw.un_550 it.rw.lg_544 hi.bh.un_530
+ 0x06080e0c, 0x00002001, 0x12001114, 0x20295307, // is.no.de_543 sq.un.un_200 ro.hu.un_660 ht.sl.sq_432
+ 0x682852a4, 0x28524a12, 0x1b043ba4, 0x08041013, // ha.sw.ig_433 yo.ha.sw_654 so.fi.tr_433 be.ru.uk_665
+ // [1a90]
+ 0x05004a07, 0x2952350d, 0x1a211c0b, 0x033f05af, // yo.fr.un_420 zu.ha.sl_554 id.jw.tl_542 fr.af.nl_655
+ 0x09211aee, 0x1c002704, 0x0a131908, 0x53192304, // tl.jw.pl_422 gd.id.un_320 gl.et.pt_443 ca.gl.ht_332
+ 0x201f0e05, 0x1000350c, 0x0e1b0413, 0x3b5228af, // is.cy.sq_333 zu.lt.un_530 fi.tr.is_665 sw.ha.so_655
+ 0x03051f0e, 0x2a033ba9, 0x201b09a9, 0x211b1c08, // cy.fr.nl_555 so.nl.mt_544 pl.tr.sq_544 id.tr.jw_443
+ // [1aa0]
+ 0x042916a0, 0x53003f21, 0x321617a6, 0x0e131baf, // hr.sl.fi_322 af.ht.un_860 sr.hr.bs_521 tr.et.is_655
+ 0x0e00351a, 0x1f002704, 0x3f3b03a4, 0x06640811, // zu.is.un_760 gd.cy.un_320 nl.so.af_433 no.lg.de_653
+ 0x1f006b02, 0x06120eaf, 0x4a2b1c08, 0x013f050c, // ceb.cy.un_220 is.hu.de_655 id.vi.yo_443 fr.af.en_543
+ 0x070f1312, 0x06000112, 0x10000708, 0x32163bee, // et.lv.it_654 en.de.un_640 bg.be.un_430 so.hr.bs_422
+ // [1ab0]
+ 0x3f0306af, 0x0c09100e, 0x0f000e19, 0x0000210a, // de.nl.af_655 lt.pl.sv_555 is.lv.un_750 jw.un.un_500
+ 0x1b0f2955, 0x530d190c, 0x1b1f0612, 0x1a215309, // sl.lv.tr_442 gl.cs.ht_543 de.cy.tr_654 ht.jw.tl_444
+ 0x062d0d5a, 0x5300210c, 0x0f2d0e05, 0x640e07ec, // cs.sk.de_553 jw.ht.un_530 is.sk.lv_333 it.is.lg_644
+ 0x16095304, 0x230b010c, 0x170408a9, 0x20002a04, // ht.pl.hr_332 en.es.ca_543 uk.ru.sr_544 mt.sq.un_320
+ // [1ac0]
+ 0x0a1711a7, 0x04110704, 0x5209640c, 0x53000909, // ro.sr.mk_532 bg.ro.ru_332 lg.pl.ha_543 pl.ht.un_440
+ 0x1a006b0e, 0x05531904, 0x010507a0, 0x0618520c, // ceb.tl.un_550 gl.ht.fr_332 it.fr.en_322 ha.ga.de_543
+ 0x233f0507, 0x08170a05, 0x6e285512, 0x05045307, // fr.af.ca_432 mk.sr.uk_333 rw.sw.hmn_654 ht.fi.fr_432
+ 0x323555a4, 0x1c0d09a0, 0x083f040c, 0x1f002a1a, // rw.zu.bs_433 hi.ne.mr_322 fi.af.no_543 mt.cy.un_760
+ // [1ad0]
+ 0x041711a0, 0x1a6b25a4, 0x11170a08, 0x35005514, // ro.sr.ru_322 eu.ceb.tl_433 mk.sr.ro_443 rw.zu.un_660
+ 0x1e211c0c, 0x10001f13, 0x6b1b5308, 0x03003f19, // id.jw.ms_543 cy.lt.un_650 ht.tr.ceb_443 af.nl.un_750
+ 0x08071705, 0x190b4a14, 0x31091bec, 0x10000404, // sr.bg.uk_333 yo.es.gl_666 tr.pl.az_644 ru.be.un_320
+ 0x17001312, 0x0a162804, 0x0c001218, 0x0a000721, // et.sr.un_640 sw.hr.pt_332 hu.sv.un_740 bg.mk.un_860
+ // [1ae0]
+ 0x23075555, 0x1004080e, 0x07001805, 0x04002804, // rw.it.ca_442 uk.ru.be_555 ga.it.un_330 sw.fi.un_320
+ 0x35552812, 0x08001023, 0x04110a13, 0x52642808, // sw.rw.zu_654 be.uk.un_880 mk.ro.ru_665 sw.lg.ha_443
+ 0x092d1607, 0x2d4a0e05, 0x0f071007, 0x08020d0c, // hr.sk.pl_432 is.yo.sk_333 lt.it.lv_432 cs.da.no_543
+ 0x2d120d12, 0x0c001308, 0x3f000322, 0x132a0755, // cs.hu.sk_654 et.sv.un_430 nl.af.un_870 it.mt.et_442
+ // [1af0]
+ 0x2d070d04, 0x04000812, 0x64002822, 0x04000c13, // cs.it.sk_332 uk.ru.un_640 sw.lg.un_870 sv.fi.un_650
+ 0x01003f07, 0x2d0d6eee, 0x0a0411a0, 0x28005304, // af.en.un_420 hmn.cs.sk_422 ro.ru.mk_322 ht.sw.un_320
+ 0x2d045304, 0x190a23ee, 0x0417110c, 0x0a1f23af, // ht.fi.sk_332 ca.pt.gl_422 ro.sr.ru_543 ca.cy.pt_655
+ 0x6b235508, 0x551a21a4, 0x23001104, 0x07001a11, // rw.ca.ceb_443 jw.tl.rw_433 ro.ca.un_320 tl.it.un_630
+ // [1b00]
+ 0x03005504, 0x0500230c, 0x6400040d, 0x0e001a08, // rw.nl.un_320 ca.fr.un_530 fi.lg.un_540 tl.is.un_430
+ 0x55001a22, 0x111e1ca0, 0x04130cee, 0x190a230c, // tl.rw.un_870 id.ms.ro_322 sv.et.fi_422 ca.pt.gl_543
+ 0x0c000508, 0x2100230c, 0x6b6428ad, 0x1e1c55a9, // fr.sv.un_430 ca.jw.un_530 sw.lg.ceb_643 rw.id.ms_544
+ 0x07285505, 0x031c01a4, 0x071e1c02, 0x2d000c0d, // rw.sw.it_333 en.id.nl_433 id.ms.it_222 sv.sk.un_540
+ // [1b10]
+ 0x07000421, 0x2b006b04, 0x063101a0, 0x20230a0b, // fi.it.un_860 ceb.vi.un_320 en.az.de_322 pt.ca.sq_542
+ 0x21270bee, 0x0b6b55a4, 0x11190a13, 0x11001319, // es.gd.jw_422 rw.ceb.es_433 pt.gl.ro_665 et.ro.un_750
+ 0x1b201309, 0x64005521, 0x1b351309, 0x0e07640d, // et.sq.tr_444 rw.lg.un_860 et.zu.tr_444 lg.it.is_554
+ 0x0a1710ee, 0x252718ad, 0x5228200c, 0x2500110c, // be.sr.mk_422 ga.gd.eu_643 sq.sw.ha_543 ro.eu.un_530
+ // [1b20]
+ 0x1200051a, 0x17351355, 0x2800070c, 0x080a1104, // fr.hu.un_760 et.zu.sr_442 it.sw.un_530 ro.mk.uk_332
+ 0x310106ee, 0x07000d04, 0x23000a14, 0x53010507, // de.en.az_422 cs.it.un_320 pt.ca.un_660 fr.en.ht_432
+ 0x526b1aa4, 0x11030709, 0x32002308, 0x2006010c, // tl.ceb.ha_433 it.nl.ro_444 ca.bs.un_430 en.de.sq_543
+ 0x0d002d18, 0x080213ec, 0x1f11230c, 0x070a10a0, // sk.cs.un_740 et.da.no_644 ca.ro.cy_543 be.mk.bg_322
+ // [1b30]
+ 0x110a23ec, 0x281a21a4, 0x213555a6, 0x2d0f0dec, // ca.pt.ro_644 jw.tl.sw_433 rw.zu.jw_521 cs.lv.sk_644
+ 0x3b1f0ca4, 0x1200091b, 0x5500061a, 0x3b003119, // sv.cy.so_433 pl.hu.un_770 de.rw.un_760 az.so.un_750
+ 0x522013a9, 0x17002907, 0x0b00050c, 0x2d0d290e, // et.sq.ha_544 sl.sr.un_420 fr.es.un_530 sl.cs.sk_555
+ 0x0f350908, 0x21000c04, 0x1b6420ec, 0x0a1704a4, // pl.zu.lv_443 sv.jw.un_320 sq.lg.tr_644 ru.sr.mk_433
+ // [1b40]
+ 0x292d0d05, 0x02080c5a, 0x1c281ea0, 0x351301a0, // cs.sk.sl_333 sv.no.da_553 ms.sw.id_322 en.et.zu_322
+ 0x21021c07, 0x2b274a0c, 0x25211011, 0x04001108, // id.da.jw_432 yo.gd.vi_543 lt.jw.eu_653 ro.ru.un_430
+ 0x0c000612, 0x52116ba7, 0x06020ca4, 0x04100712, // de.sv.un_640 ceb.ro.ha_532 sv.da.de_433 bg.be.ru_654
+ 0x2b004a14, 0x6b101aa0, 0x6b100c07, 0x523b1ea4, // yo.vi.un_660 tl.lt.ceb_322 sv.lt.ceb_432 ms.so.ha_433
+ // [1b50]
+ 0x172d0da4, 0x04004a0e, 0x64285255, 0x080411a9, // cs.sk.sr_433 yo.fi.un_550 ha.sw.lg_442 ro.ru.uk_544
+ 0x1b190b0c, 0x11002819, 0x01000a07, 0x0d005221, // es.gl.tr_543 sw.ro.un_750 pt.en.un_420 ha.cs.un_860
+ 0x2b00270e, 0x32236e0c, 0x35010aa6, 0x4a002b21, // gd.vi.un_550 hmn.ca.bs_543 pt.en.zu_521 vi.yo.un_860
+ 0x2d0d1609, 0x05075505, 0x1b00230b, 0x106455ec, // hr.cs.sk_444 rw.it.fr_333 ca.tr.un_520 rw.lg.lt_644
+ // [1b60]
+ 0x0a000c05, 0x2d0f17a9, 0x052801a4, 0x0c0864a9, // sv.pt.un_330 sr.lv.sk_544 en.sw.fr_433 lg.no.sv_544
+ 0x20283b11, 0x05010607, 0x03006804, 0x171011a4, // so.sw.sq_653 de.en.fr_432 ig.nl.un_320 ro.be.sr_433
+ 0x07002a0e, 0x0804105a, 0x02016404, 0x1e1c2105, // mt.it.un_550 be.ru.uk_553 lg.en.da_332 jw.id.ms_333
+ 0x01001a02, 0x3b001108, 0x552812a4, 0x3f312008, // tl.en.un_220 ro.so.un_430 hu.sw.rw_433 sq.az.af_443
+ // [1b70]
+ 0x6411200c, 0x1a6b530c, 0x07004a0c, 0x351255ad, // sq.ro.lg_543 ht.ceb.tl_543 yo.it.un_530 rw.hu.zu_643
+ 0x55002821, 0x35106407, 0x322d1760, 0x29160da4, // sw.rw.un_860 lg.lt.zu_432 sr.sk.bs_664 cs.hr.sl_433
+ 0x170a0809, 0x072a11a4, 0x103568ee, 0x1a000d04, // uk.mk.sr_444 ro.mt.it_433 ig.zu.lt_422 cs.tl.un_320
+ 0x27281808, 0x190b01a4, 0x0d091c12, 0x0f0a3bee, // ga.sw.gd_443 en.es.gl_433 mr.hi.ne_654 so.pt.lv_422
+ // [1b80]
+ 0x681806ee, 0x6e2028a6, 0x2a110704, 0x55283560, // de.ga.ig_422 sw.sq.hmn_521 it.ro.mt_332 zu.sw.rw_664
+ 0x0a00310c, 0x31101b0e, 0x55646808, 0x02000504, // az.pt.un_530 tr.lt.az_555 ig.lg.rw_443 fr.da.un_320
+ 0x00001801, 0x1b196bec, 0x53231c04, 0x133b1855, // ga.un.un_200 ceb.gl.tr_644 id.ca.ht_332 ga.so.et_442
+ 0x0c1c3f07, 0x556408a0, 0x1a55230c, 0x5500351b, // af.id.sv_432 no.lg.rw_322 ca.rw.tl_543 zu.rw.un_770
+ // [1b90]
+ 0x061b2507, 0x2a55075a, 0x18000e19, 0x0700042b, // eu.tr.de_432 it.rw.mt_553 is.ga.un_750 ru.bg.un_980
+ 0x08110e07, 0x1708070e, 0x3b001813, 0x1b001122, // is.ro.no_432 bg.uk.sr_555 ga.so.un_650 ro.tr.un_870
+ 0x13003b08, 0x0d292d04, 0x18002122, 0x03083fa9, // so.et.un_430 sk.sl.cs_332 fa.ar.un_870 af.no.nl_544
+ 0x3b0c1ca0, 0x060c0412, 0x01002422, 0x112301a9, // id.sv.so_322 fi.sv.de_654 yi.iw.un_870 en.ca.ro_544
+ // [1ba0]
+ 0x04254aad, 0x10002013, 0x0a110707, 0x033f1e02, // yo.eu.fi_643 sq.lt.un_650 bg.ro.mk_432 ms.af.nl_222
+ 0x16002905, 0x100817a4, 0x0802070c, 0x326b280c, // sl.hr.un_330 sr.uk.be_433 it.da.no_543 sw.ceb.bs_543
+ 0x2d0d295a, 0x09003b05, 0x080411a4, 0x17080707, // sl.cs.sk_553 so.pl.un_330 ro.ru.uk_433 bg.uk.sr_432
+ 0x0c103b12, 0x00001f06, 0x0d092d12, 0x28041a09, // so.lt.sv_654 cy.un.un_400 sk.pl.cs_654 tl.fi.sw_444
+ // [1bb0]
+ 0x010629a0, 0x041708af, 0x55136ba0, 0x0f1011ec, // sl.de.en_322 uk.sr.ru_655 ceb.et.rw_322 ro.lt.lv_644
+ 0x3b1e1c60, 0x55001c02, 0x08070a0c, 0x21005204, // id.ms.so_664 id.rw.un_220 mk.bg.uk_543 ha.jw.un_320
+ 0x0c00530d, 0x2d0d1007, 0x110407af, 0x05002802, // ht.sv.un_540 lt.cs.sk_432 bg.ru.ro_655 sw.fr.un_220
+ 0x0c3b130c, 0x1a132804, 0x1a0e1ca4, 0x20251c07, // et.so.sv_543 sw.et.tl_332 id.is.tl_433 id.eu.sq_432
+ // [1bc0]
+ 0x06000c14, 0x070408af, 0x03083f05, 0x351f27ad, // sv.de.un_660 uk.ru.bg_655 af.no.nl_333 gd.cy.zu_643
+ 0x0d2d4aad, 0x07010507, 0x17100a55, 0x3b351a08, // yo.sk.cs_643 fr.en.it_432 mk.be.sr_442 tl.zu.so_443
+ 0x6b001318, 0x063b1e04, 0x190a03ee, 0x552164af, // et.ceb.un_740 ms.so.de_332 nl.pt.gl_422 lg.jw.rw_655
+ 0x070410a0, 0x52002a21, 0x2d0302a4, 0x3200160b, // be.ru.bg_322 mt.ha.un_860 da.nl.sk_433 hr.bs.un_520
+ // [1bd0]
+ 0x04000e12, 0x0b322807, 0x28171608, 0x25010c0c, // is.fi.un_640 sw.bs.es_432 hr.sr.sw_443 sv.en.eu_543
+ 0x066b01a4, 0x2a01250c, 0x25211c07, 0x11013fa0, // en.ceb.de_433 eu.en.mt_543 id.jw.eu_432 af.en.ro_322
+ 0x0f172912, 0x12000e14, 0x13000419, 0x023b1a0c, // sl.sr.lv_654 is.hu.un_660 fi.et.un_750 tl.so.da_543
+ 0x17070a05, 0x21001123, 0x3b00680c, 0x0e3f0612, // mk.bg.sr_333 ro.jw.un_880 ig.so.un_530 de.af.is_654
+ // [1be0]
+ 0x09060d14, 0x21003b20, 0x2b3f03ad, 0x02080c11, // cs.de.pl_666 so.jw.un_850 nl.af.vi_643 sv.no.da_653
+ 0x251a10a4, 0x1000170d, 0x12000d13, 0x0925350c, // lt.tl.eu_433 sr.be.un_540 cs.hu.un_650 zu.eu.pl_543
+ 0x20311eee, 0x08000c09, 0x07005204, 0x1b002519, // ms.az.sq_422 sv.no.un_440 ha.it.un_320 eu.tr.un_750
+ 0x23001704, 0x1a210704, 0x04002112, 0x1b003118, // sr.ca.un_320 it.jw.tl_332 jw.fi.un_640 az.tr.un_740
+ // [1bf0]
+ 0x2b001e13, 0x04133b0d, 0x10001705, 0x6b1a180c, // ms.vi.un_650 so.et.fi_554 sr.lt.un_330 ga.tl.ceb_543
+ 0x25004a04, 0x05190eee, 0x0e0d0aa9, 0x0b0a13a0, // yo.eu.un_320 is.gl.fr_422 pt.cs.is_544 et.pt.es_322
+ 0x0e0c1c07, 0x062a3b12, 0x0a4a18a9, 0x06271813, // id.sv.is_432 so.mt.de_654 ga.yo.pt_544 ga.gd.de_665
+ 0x3b001312, 0x4a0e18ec, 0x102d0d08, 0x1b001111, // et.so.un_640 ga.is.yo_644 cs.sk.lt_443 ro.tr.un_630
+
+ // [1c00]
+ 0x64004a07, 0x21351ca4, 0x040b20a7, 0x18002b21, // yo.lg.un_420 id.zu.jw_433 sq.es.fi_532 vi.ga.un_860
+ 0x2a051faf, 0x2d184a5a, 0x07020812, 0x1c000d1b, // cy.fr.mt_655 yo.ga.sk_553 no.da.it_654 ne.mr.un_770
+ 0x4a180ead, 0x2a0501ec, 0x1b002513, 0x4a001822, // is.ga.yo_643 en.fr.mt_644 eu.tr.un_650 ga.yo.un_870
+ 0x2b0c10ee, 0x0e1125ad, 0x0400100b, 0x08250e0c, // lt.sv.vi_422 eu.ro.is_643 be.ru.un_520 is.eu.no_543
+ // [1c10]
+ 0x1f00250c, 0x3f000304, 0x1200181b, 0x4a000e1b, // eu.cy.un_530 nl.af.un_320 ga.hu.un_770 is.yo.un_770
+ 0x180d4a13, 0x0c00040d, 0x2b0e0a08, 0x060810a4, // yo.cs.ga_665 fi.sv.un_540 pt.is.vi_443 lt.no.de_433
+ 0x0d002d13, 0x08022aa0, 0x0a3b11a4, 0x03023513, // sk.cs.un_650 mt.da.no_322 ro.so.pt_433 zu.da.nl_665
+ 0x31522312, 0x0100130d, 0x03070607, 0x0f001022, // ca.ha.az_654 et.en.un_540 de.it.nl_432 lt.lv.un_870
+ // [1c20]
+ 0x11100408, 0x131201a7, 0x1b105507, 0x2000030d, // ru.be.ro_443 en.hu.et_532 rw.lt.tr_432 nl.sq.un_540
+ 0x2d0d10af, 0x53000502, 0x2a00200c, 0x252d0d0d, // lt.cs.sk_655 fr.ht.un_220 sq.mt.un_530 cs.sk.eu_554
+ 0x0900100d, 0x17000a11, 0x552d01a7, 0x292025ac, // lt.pl.un_540 mk.sr.un_630 en.sk.rw_532 eu.sq.sl_632
+ 0x2d002107, 0x55281008, 0x2d0e0c02, 0x4a0d2d0c, // jw.sk.un_420 lt.sw.rw_443 sv.is.sk_222 sk.cs.yo_543
+ // [1c30]
+ 0x09001021, 0x2b001f05, 0x2500641b, 0x01006b05, // lt.pl.un_860 cy.vi.un_330 lg.eu.un_770 ceb.en.un_330
+ 0x0f1710ad, 0x31002509, 0x2b1801ee, 0x1b002509, // lt.sr.lv_643 eu.az.un_440 en.ga.vi_422 eu.tr.un_440
+ 0x5535520c, 0x2d081aa0, 0x04080aaf, 0x1c090d11, // ha.zu.rw_543 tl.no.sk_322 mk.uk.ru_655 ne.hi.mr_653
+ 0x0800022c, 0x102d29ee, 0x120c0213, 0x05013fa0, // da.no.un_990 sl.sk.lt_422 da.sv.hu_665 af.en.fr_322
+ // [1c40]
+ 0x53002808, 0x04001f02, 0x090d1c0d, 0x04090b55, // sw.ht.un_430 cy.fi.un_220 mr.ne.hi_554 es.pl.fi_442
+ 0x0e282a05, 0x030f3fa4, 0x1a211ca0, 0x08000c2c, // mt.sw.is_333 af.lv.nl_433 id.jw.tl_322 sv.no.un_990
+ 0x0600091b, 0x3f2901a4, 0x0e003207, 0x25001907, // pl.de.un_770 en.sl.af_433 bs.is.un_420 gl.eu.un_420
+ 0x090f035a, 0x17162902, 0x0c3f2a02, 0x1c13095a, // nl.lv.pl_553 sl.hr.sr_222 mt.af.sv_222 hi.bh.mr_553
+ // [1c50]
+ 0x0c00040c, 0x012d0d04, 0x55093f12, 0x08005505, // fi.sv.un_530 cs.sk.en_332 af.pl.rw_654 rw.no.un_330
+ 0x12020ca6, 0x10645308, 0x2d005308, 0x100711a4, // sv.da.hu_521 ht.lg.lt_443 ht.sk.un_430 ro.bg.be_433
+ 0x2a060713, 0x09003f07, 0x2d102a07, 0x0900201a, // it.de.mt_665 af.pl.un_420 mt.lt.sk_432 sq.pl.un_760
+ 0x3f000908, 0x07000f11, 0x4a005319, 0x091c21a4, // pl.af.un_430 lv.it.un_630 ht.yo.un_750 jw.id.pl_433
+ // [1c60]
+ 0x040f2d11, 0x55006419, 0x09000622, 0x3b00212a, // sk.lv.fi_653 lg.rw.un_750 de.pl.un_870 jw.so.un_970
+ 0x01312aac, 0x09211a12, 0x1b002a07, 0x3200170e, // mt.az.en_632 tl.jw.pl_654 mt.tr.un_420 sr.bs.un_550
+ 0x170708a0, 0x18033fad, 0x1f2a0e0e, 0x5512640c, // uk.bg.sr_322 af.nl.ga_643 is.mt.cy_555 lg.hu.rw_543
+ 0x1c2a1e04, 0x23121f07, 0x053f10a0, 0x100223ec, // ms.mt.id_332 cy.hu.ca_432 lt.af.fr_322 ca.da.lt_644
+ // [1c70]
+ 0x640328ad, 0x130c10a0, 0x2d004a0e, 0x55080205, // sw.nl.lg_643 lt.sv.et_322 yo.sk.un_550 da.no.rw_333
+ 0x010e3fa7, 0x2a11180c, 0x0c0855a9, 0x0e003f08, // af.is.en_532 ga.ro.mt_543 rw.no.sv_544 af.is.un_430
+ 0x0f001707, 0x04070a07, 0x10002312, 0x1c0d0905, // sr.lv.un_420 mk.bg.ru_432 ca.lt.un_640 hi.ne.mr_333
+ 0x21000907, 0x05005204, 0x12032309, 0x133b640d, // pl.jw.un_420 ha.fr.un_320 ca.nl.hu_444 lg.so.et_554
+ // [1c80]
+ 0x01005202, 0x1800210d, 0x0a1704af, 0x09002702, // ha.en.un_220 fa.ar.un_540 ru.sr.mk_655 gd.pl.un_220
+ 0x122318ad, 0x10002012, 0x00000e2d, 0x0a001019, // ga.ca.hu_643 sq.lt.un_640 is.un.un_A00 lt.pt.un_750
+ 0x55002508, 0x4a00182b, 0x1c100405, 0x080411ee, // eu.rw.un_430 ga.yo.un_980 fi.lt.id_333 ro.ru.uk_422
+ 0x10006807, 0x0000052d, 0x050823a6, 0x03002304, // ig.lt.un_420 fr.un.un_A00 ca.no.fr_521 ca.nl.un_320
+ // [1c90]
+ 0x232b4aaf, 0x1e1c25af, 0x4a211ca9, 0x1200010d, // yo.vi.ca_655 eu.id.ms_655 id.jw.yo_544 en.hu.un_540
+ 0x18004a2a, 0x111e1cec, 0x01003507, 0x1e3f1ca4, // yo.ga.un_970 id.ms.ro_644 zu.en.un_420 id.af.ms_433
+ 0x1c21130c, 0x1b311ea0, 0x231e1c07, 0x090d1cee, // et.jw.id_543 ms.az.tr_322 id.ms.ca_432 mr.ne.hi_422
+ 0x2a000c12, 0x1800270b, 0x1b682807, 0x072b6b0d, // sv.mt.un_640 gd.ga.un_520 sw.ig.tr_432 ceb.vi.it_554
+ // [1ca0]
+ 0x100a1713, 0x12006822, 0x210e4aa0, 0x2d002814, // sr.mk.be_665 ig.hu.un_870 yo.is.jw_322 sw.sk.un_660
+ 0x350b5508, 0x521c530c, 0x1b1e1c12, 0x21006b05, // rw.es.zu_443 ht.id.ha_543 id.ms.tr_654 ceb.jw.un_330
+ 0x28182713, 0x18004a21, 0x6429110c, 0x04000c0c, // gd.ga.sw_665 yo.ga.un_860 ro.sl.lg_543 sv.fi.un_530
+ 0x4a0e1812, 0x080411ad, 0x322d0908, 0x0d4a230d, // ga.is.yo_654 ro.ru.uk_643 pl.sk.bs_443 ca.yo.cs_554
+ // [1cb0]
+ 0x554a28a4, 0x55000b04, 0x07000a07, 0x3b1b2a11, // sw.yo.rw_433 es.rw.un_320 pt.it.un_420 mt.tr.so_653
+ 0x030602ee, 0x2000350e, 0x0300060c, 0x100a0507, // da.de.nl_422 zu.sq.un_550 de.nl.un_530 fr.pt.lt_432
+ 0x281c2108, 0x18000921, 0x0f20350c, 0x10001c1a, // jw.id.sw_443 pl.ga.un_860 zu.sq.lv_543 id.lt.un_760
+ 0x00001e1c, 0x17290fa9, 0x3b002a0b, 0x1100310d, // ms.un.un_800 lv.sl.sr_544 mt.so.un_520 az.ro.un_540
+ // [1cc0]
+ 0x13001f21, 0x17163211, 0x0e040f0c, 0x523b280d, // cy.et.un_860 bs.hr.sr_653 lv.fi.is_543 sw.so.ha_554
+ 0x202a0e0c, 0x21005208, 0x53212a0c, 0x0700110e, // is.mt.sq_543 ha.jw.un_430 mt.jw.ht_543 ro.bg.un_550
+ 0x28002022, 0x272111a4, 0x016b11a4, 0x32100f08, // sq.sw.un_870 ro.jw.gd_433 ro.ceb.en_433 lv.lt.bs_443
+ 0x1b00680b, 0x070b23a9, 0x04080708, 0x0a070413, // ig.tr.un_520 ca.es.it_544 bg.uk.ru_443 ru.bg.mk_665
+ // [1cd0]
+ 0x170a07ad, 0x100721a4, 0x21091004, 0x04001122, // bg.mk.sr_643 jw.it.lt_433 lt.pl.jw_332 ro.fi.un_870
+ 0x0d001c2a, 0x0a0807a9, 0x0c000113, 0x2821640c, // mr.ne.un_970 bg.uk.mk_544 en.sv.un_650 lg.jw.sw_543
+ 0x230129a9, 0x120e0c08, 0x01000d04, 0x23016b04, // sl.en.ca_544 sv.is.hu_443 cs.en.un_320 ceb.en.ca_332
+ 0x07190b09, 0x4a2b0e04, 0x35076408, 0x1c0e1ea4, // es.gl.it_444 is.vi.yo_332 lg.it.zu_443 ms.is.id_433
+ // [1ce0]
+ 0x0d004a1b, 0x0b184a5a, 0x1e002702, 0x070b0a14, // yo.cs.un_770 yo.ga.es_553 gd.ms.un_220 pt.es.it_666
+ 0x3b073511, 0x0e1e1c04, 0x18004a35, 0x32091e0b, // zu.it.so_653 id.ms.is_332 yo.ga.un_A90 ms.pl.bs_542
+ 0x1600250c, 0x09002909, 0x110e0c0d, 0x1e1c0a05, // eu.hr.un_530 sl.pl.un_440 sv.is.ro_554 pt.id.ms_333
+ 0x06020ca0, 0x680b520c, 0x3b00521a, 0x19000a1a, // sv.da.de_322 ha.es.ig_543 ha.so.un_760 pt.gl.un_760
+ // [1cf0]
+ 0x1b1f31af, 0x2b184aec, 0x09002508, 0x29000d1a, // az.cy.tr_655 yo.ga.vi_644 eu.pl.un_430 cs.sl.un_760
+ 0x2b184aa7, 0x0d002919, 0x08101708, 0x1b3555a4, // yo.ga.vi_532 sl.cs.un_750 sr.be.uk_443 rw.zu.tr_433
+ 0x08000412, 0x1c003204, 0x2d684a11, 0x68002813, // ru.uk.un_640 bs.id.un_320 yo.ig.sk_653 sw.ig.un_650
+ 0x040a1002, 0x191064ad, 0x1c002102, 0x170410a0, // be.mk.ru_222 lg.lt.gl_643 jw.id.un_220 be.ru.sr_322
+ // [1d00]
+ 0x091b530c, 0x1c001314, 0x64125509, 0x181a6b12, // ht.tr.pl_543 bh.mr.un_660 rw.hu.lg_444 ceb.tl.ga_654
+ 0x313b5204, 0x6800252a, 0x31190a60, 0x3f0410a0, // ha.so.az_332 eu.ig.un_970 pt.gl.az_664 lt.fi.af_322
+ 0x1c00210e, 0x23184aad, 0x12106804, 0x2d0d03a4, // jw.id.un_550 yo.ga.ca_643 ig.lt.hu_332 nl.cs.sk_433
+ 0x1a6b28ee, 0x07531955, 0x6b681ea0, 0x0c1e1c5a, // sw.ceb.tl_422 gl.ht.it_442 ms.ig.ceb_322 id.ms.sv_553
+ // [1d10]
+ 0x09002113, 0x0d002b1a, 0x00004a37, 0x2a6b29a0, // jw.pl.un_650 vi.cs.un_760 yo.un.un_B00 sl.ceb.mt_322
+ 0x03006e07, 0x07002707, 0x196b01ad, 0x0d1c1307, // hmn.nl.un_420 gd.it.un_420 en.ceb.gl_643 bh.mr.ne_432
+ 0x1629680c, 0x64001b18, 0x0a00200e, 0x552864ec, // ig.sl.hr_543 tr.lg.un_740 sq.pt.un_550 lg.sw.rw_644
+ 0x2a0e1008, 0x27002519, 0x0a001e02, 0x06006812, // lt.is.mt_443 eu.gd.un_750 ms.pt.un_220 ig.de.un_640
+ // [1d20]
+ 0x03000704, 0x2d0d09ec, 0x28005514, 0x126b1aa0, // it.nl.un_320 pl.cs.sk_644 rw.sw.un_660 tl.ceb.hu_322
+ 0x23190ba9, 0x0b554a07, 0x0e3216ad, 0x281c1ea0, // es.gl.ca_544 yo.rw.es_432 hr.bs.is_643 ms.id.sw_322
+ 0x1c212855, 0x1a00210c, 0x0e120c07, 0x0b080c05, // sw.jw.id_442 jw.tl.un_530 sv.hu.is_432 sv.no.es_333
+ 0x1b1208a4, 0x2d0c0da4, 0x11121008, 0x04000a1b, // no.hu.tr_433 cs.sv.sk_433 lt.hu.ro_443 mk.ru.un_770
+ // [1d30]
+ 0x0d092d5a, 0x06000c08, 0x04171107, 0x1f003f0d, // sk.pl.cs_553 sv.de.un_430 ro.sr.ru_432 af.cy.un_540
+ 0x04070aad, 0x4a2921a0, 0x1100550d, 0x0a006e04, // mk.bg.ru_643 jw.sl.yo_322 rw.ro.un_540 hmn.pt.un_320
+ 0x52211c55, 0x2d0d2109, 0x0a00190d, 0x19110b04, // id.jw.ha_442 jw.cs.sk_444 gl.pt.un_540 es.ro.gl_332
+ 0x31351baf, 0x070417ee, 0x10041755, 0x53002a04, // tr.zu.az_655 sr.ru.bg_422 sr.fi.lt_442 mt.ht.un_320
+ // [1d40]
+ 0x09000d12, 0x52101e0c, 0x3b3f6ea0, 0x13006e09, // cs.pl.un_640 ms.lt.ha_543 hmn.af.so_322 hmn.et.un_440
+ 0x20123f04, 0x520f100d, 0x10000f12, 0x1f1b3b07, // af.hu.sq_332 lt.lv.ha_554 lv.lt.un_640 so.tr.cy_432
+ 0x13201b07, 0x08003f05, 0x076411a0, 0x100f1ea9, // tr.sq.et_432 af.no.un_330 ro.lg.it_322 ms.lv.lt_544
+ 0x0f00102c, 0x110113a4, 0x2d0d0614, 0x28521ea4, // lt.lv.un_990 et.en.ro_433 de.cs.sk_666 ms.ha.sw_433
+ // [1d50]
+ 0x281052a4, 0x0400110e, 0x101e1c60, 0x28001e02, // ha.lt.sw_433 ro.ru.un_550 id.ms.lt_664 ms.sw.un_220
+ 0x11001b1a, 0x19230d04, 0x31000104, 0x520f10ec, // tr.ro.un_760 cs.ca.gl_332 en.az.un_320 lt.lv.ha_644
+ 0x0700060b, 0x0f120ca0, 0x3b311b12, 0x640417a0, // de.it.un_520 sv.hu.lv_322 tr.az.so_654 sr.fi.lg_322
+ 0x1811320c, 0x29122a55, 0x00000501, 0x06000a19, // bs.ro.ga_543 mt.hu.sl_442 fr.un.un_200 pt.de.un_750
+ // [1d60]
+ 0x1e1c0aa4, 0x3500551a, 0x08190a55, 0x06136ba0, // pt.id.ms_433 rw.zu.un_760 pt.gl.no_442 ceb.et.de_322
+ 0x521012a4, 0x28121ea4, 0x292d09ad, 0x25006e07, // hu.lt.ha_433 ms.hu.sw_433 pl.sk.sl_643 hmn.eu.un_420
+ 0x1c1e52ad, 0x120f10ad, 0x25000808, 0x25001222, // ha.ms.id_643 lt.lv.hu_643 no.eu.un_430 hu.eu.un_870
+ 0x521008a4, 0x190a010c, 0x1000520d, 0x3f000104, // no.lt.ha_433 en.pt.gl_543 ha.lt.un_540 en.af.un_320
+ // [1d70]
+ 0x091c1355, 0x11202308, 0x18001911, 0x20001012, // bh.mr.hi_442 ca.sq.ro_443 gl.ga.un_630 lt.sq.un_640
+ 0x0b6b1a0d, 0x31001104, 0x280335a0, 0x03000619, // tl.ceb.es_554 ro.az.un_320 zu.nl.sw_322 de.nl.un_750
+ 0x03070aa0, 0x170a04a4, 0x10000309, 0x040711a7, // pt.it.nl_322 ru.mk.sr_433 nl.lt.un_440 ro.bg.ru_532
+ 0x13091c14, 0x0b2827ee, 0x18000b05, 0x3b2728ad, // mr.hi.bh_666 gd.sw.es_422 es.ga.un_330 sw.gd.so_643
+ // [1d80]
+ 0x0564550e, 0x311b110d, 0x2d0f0913, 0x073f03a4, // rw.lg.fr_555 ro.tr.az_554 pl.lv.sk_665 nl.af.it_433
+ 0x120c2a11, 0x0a101709, 0x03122a07, 0x272a1112, // mt.sv.hu_653 sr.be.mk_444 mt.hu.nl_432 ro.mt.gd_654
+ 0x4a0252a0, 0x1c1309ad, 0x1c285207, 0x06020c09, // ha.da.yo_322 hi.bh.mr_643 ha.sw.id_432 sv.da.de_444
+ 0x18190b02, 0x0723190d, 0x531811ee, 0x070a110c, // es.gl.ga_222 gl.ca.it_554 ro.ga.ht_422 ro.mk.bg_543
+ // [1d90]
+ 0x1f0518a7, 0x04000809, 0x1f0518ec, 0x68005519, // ga.fr.cy_532 uk.ru.un_440 ga.fr.cy_644 rw.ig.un_750
+ 0x3f001e02, 0x283b52af, 0x2d321602, 0x08530eee, // ms.af.un_220 ha.so.sw_655 hr.bs.sk_222 is.ht.no_422
+ 0x28050aee, 0x07000814, 0x0e2d0d60, 0x05041fa0, // pt.fr.sw_422 uk.bg.un_660 cs.sk.is_664 cy.fi.fr_322
+ 0x350e280e, 0x030625ad, 0x041c4a04, 0x1e3b2807, // sw.is.zu_555 eu.de.nl_643 yo.id.fi_332 sw.so.ms_432
+ // [1da0]
+ 0x1b3f6ba0, 0x0f095255, 0x18270513, 0x093b130c, // ceb.af.tr_322 ha.pl.lv_442 fr.gd.ga_665 et.so.pl_543
+ 0x07294a04, 0x522705a9, 0x3b524aee, 0x2827180b, // yo.sl.it_332 fr.gd.ha_544 yo.ha.so_422 ga.gd.sw_542
+ 0x521b530c, 0x1700110e, 0x1a2125a0, 0x123f0309, // ht.tr.ha_543 ro.sr.un_550 eu.jw.tl_322 nl.af.hu_444
+ 0x53000507, 0x180527ad, 0x1e001c36, 0x35001913, // fr.ht.un_420 gd.fr.ga_643 id.ms.un_AA0 gl.zu.un_650
+ // [1db0]
+ 0x1e1c0fa4, 0x0c2b1ca7, 0x1800051a, 0x1e1c310c, // lv.id.ms_433 id.vi.sv_532 fr.ga.un_760 az.id.ms_543
+ 0x0f000804, 0x1b3b1f13, 0x052d0d13, 0x2327180c, // no.lv.un_320 cy.so.tr_665 cs.sk.fr_665 ga.gd.ca_543
+ 0x03003f36, 0x17311bad, 0x080206a0, 0x212d0d0e, // af.nl.un_AA0 tr.az.sr_643 de.da.no_322 cs.sk.jw_555
+ 0x1b532d07, 0x0300050d, 0x0000032d, 0x09020c07, // sk.ht.tr_432 fr.nl.un_540 nl.un.un_A00 sv.da.pl_432
+ // [1dc0]
+ 0x05001823, 0x0d002d21, 0x103f1aa0, 0x1e1c050c, // ga.fr.un_880 sk.cs.un_860 tl.af.lt_322 fr.id.ms_543
+ 0x1b1a530c, 0x19005504, 0x120427a4, 0x07000a13, // ht.tl.tr_543 rw.gl.un_320 gd.fi.hu_433 mk.bg.un_650
+ 0x1a316ea4, 0x2d0d3b0d, 0x312b0aa0, 0x12080207, // hmn.az.tl_433 so.cs.sk_554 pt.vi.az_322 da.no.hu_432
+ 0x20033f07, 0x1c2a2104, 0x24000133, 0x022a0bee, // af.nl.sq_432 jw.mt.id_332 iw.yi.un_A70 es.mt.da_422
+ // [1dd0]
+ 0x3b061012, 0x3200210c, 0x052d0d11, 0x17006e04, // lt.de.so_654 jw.bs.un_530 cs.sk.fr_653 hmn.sr.un_320
+ 0x4a005204, 0x0800020d, 0x211e1ca7, 0x10003202, // ha.yo.un_320 da.no.un_540 id.ms.jw_532 bs.lt.un_220
+ 0x19211f08, 0x0f1613a7, 0x090f10a4, 0x6b1c11ee, // cy.jw.gl_443 et.hr.lv_532 lt.lv.pl_433 ro.id.ceb_422
+ 0x32001209, 0x12001819, 0x1a001c07, 0x0a071705, // hu.bs.un_440 ar.ur.un_750 id.tl.un_420 sr.bg.mk_333
+ // [1de0]
+ 0x171668ee, 0x1e1c2702, 0x0d0802a0, 0x3216290c, // ig.hr.sr_422 gd.id.ms_222 da.no.cs_322 sl.hr.bs_543
+ 0x09002914, 0x093f030c, 0x03003f05, 0x3f523205, // sl.pl.un_660 nl.af.pl_543 af.nl.un_330 bs.ha.af_333
+ 0x27001807, 0x12000e12, 0x3f1101a4, 0x21100ca4, // ga.gd.un_420 is.hu.un_640 en.ro.af_433 sv.lt.jw_433
+ 0x0a070805, 0x03063508, 0x0e0b19a7, 0x2400010e, // uk.bg.mk_333 zu.de.nl_443 gl.es.is_532 iw.yi.un_550
+ // [1df0]
+ 0x17002d12, 0x1f1a6ba4, 0x1b2008a0, 0x19110bec, // sk.sr.un_640 ceb.tl.cy_433 no.sq.tr_322 es.ro.gl_644
+ 0x0f2d0912, 0x6b190b04, 0x02120455, 0x520e10ee, // pl.sk.lv_654 es.gl.ceb_332 fi.hu.da_442 lt.is.ha_422
+ 0x2d002918, 0x1812210e, 0x026408ac, 0x3518270c, // sl.sk.un_740 fa.ur.ar_555 no.lg.da_632 gd.ga.zu_543
+ 0x27005302, 0x0e1918a7, 0x04001018, 0x03132904, // ht.gd.un_220 ga.gl.is_532 be.ru.un_740 sl.et.nl_332
+ // [1e00]
+ 0x190a0b05, 0x192302a4, 0x350152ee, 0x21002304, // es.pt.gl_333 da.ca.gl_433 ha.en.zu_422 ca.jw.un_320
+ 0x0f1710a9, 0x09080213, 0x23000713, 0x1f002705, // lt.sr.lv_544 da.no.pl_665 it.ca.un_650 gd.cy.un_330
+ 0x2804130d, 0x23190ba4, 0x3b000423, 0x13090dad, // et.fi.sw_554 es.gl.ca_433 fi.so.un_880 ne.hi.bh_643
+ 0x2b012aee, 0x0b0a18ec, 0x6b043ba9, 0x25030608, // mt.en.vi_422 ga.pt.es_644 so.fi.ceb_544 de.nl.eu_443
+ // [1e10]
+ 0x281b1ea0, 0x1000040c, 0x03090d12, 0x1800211b, // ms.tr.sw_322 ru.be.un_530 cs.pl.nl_654 fa.ar.un_770
+ 0x3b280412, 0x061209ad, 0x0f280412, 0x18190ba7, // fi.sw.so_654 pl.hu.de_643 fi.sw.lv_654 es.gl.ga_532
+ 0x10080408, 0x1c090da7, 0x02006407, 0x4a002821, // ru.uk.be_443 ne.hi.mr_532 lg.da.un_420 sw.yo.un_860
+ 0x192d0d13, 0x52003b05, 0x0d002d0d, 0x04100f12, // cs.sk.gl_665 so.ha.un_330 sk.cs.un_540 lv.lt.fi_654
+ // [1e20]
+ 0x1104100c, 0x0a190ba7, 0x4a683b0c, 0x09000619, // be.ru.ro_543 es.gl.pt_532 so.ig.yo_543 de.pl.un_750
+ 0x1f190b0e, 0x04071107, 0x03001f04, 0x1c00250d, // es.gl.cy_555 ro.bg.ru_432 cy.nl.un_320 eu.id.un_540
+ 0x10213b04, 0x12002112, 0x1800122a, 0x12000c0d, // so.jw.lt_332 fa.ur.un_640 ur.ar.un_970 sv.hu.un_540
+ 0x02001f02, 0x211b5302, 0x0a0128a7, 0x033f21a0, // cy.da.un_220 ht.tr.jw_222 sw.en.pt_532 jw.af.nl_322
+ // [1e30]
+ 0x0c00022a, 0x28000421, 0x4a3b6808, 0x0a001814, // da.sv.un_970 fi.sw.un_860 ig.so.yo_443 ga.pt.un_660
+ 0x0d17160e, 0x283b040d, 0x11002b11, 0x0b00180e, // hr.sr.cs_555 fi.so.sw_554 vi.ro.un_630 ga.es.un_550
+ 0x04003b22, 0x20532513, 0x18190ba0, 0x3b006809, // so.fi.un_870 eu.ht.sq_665 es.gl.ga_322 ig.so.un_440
+ 0x2b182812, 0x04004a22, 0x521e3ba4, 0x281827ec, // sw.ga.vi_654 yo.fi.un_870 so.ms.ha_433 gd.ga.sw_644
+ // [1e40]
+ 0x2b271811, 0x2a000707, 0x2a1f2b04, 0x20311ba7, // ga.gd.vi_653 it.mt.un_420 vi.cy.mt_332 tr.az.sq_532
+ 0x00000106, 0x355528ad, 0x1800170c, 0x1c311ead, // en.un.un_400 sw.rw.zu_643 sr.ga.un_530 ms.az.id_643
+ 0x03045304, 0x07010602, 0x640e1b0c, 0x111e6b0c, // ht.fi.nl_332 de.en.it_222 tr.is.lg_543 ceb.ms.ro_543
+ 0x1b3564ec, 0x28522a12, 0x2b001907, 0x25190ba9, // lg.zu.tr_644 mt.ha.sw_654 gl.vi.un_420 es.gl.eu_544
+ // [1e50]
+ 0x161328a9, 0x020c0813, 0x040e2aa4, 0x321752ec, // sw.et.hr_544 no.sv.da_665 mt.is.fi_433 ha.sr.bs_644
+ 0x09006412, 0x0c000e2b, 0x0e1a52af, 0x20210755, // lg.pl.un_640 is.sv.un_980 ha.tl.is_655 it.jw.sq_442
+ 0x070b110c, 0x27001836, 0x06005513, 0x063f03a0, // ro.es.it_543 ga.gd.un_AA0 rw.de.un_650 nl.af.de_322
+ 0x68005522, 0x0800200e, 0x0c1308a0, 0x6b680bee, // rw.ig.un_870 sq.no.un_550 no.et.sv_322 es.ig.ceb_422
+ // [1e60]
+ 0x6b005208, 0x1b3111a9, 0x08043f05, 0x074a68ad, // ha.ceb.un_430 ro.az.tr_544 af.fi.no_333 ig.yo.it_643
+ 0x11101805, 0x641207a7, 0x1f18270d, 0x55000b02, // ga.lt.ro_333 it.hu.lg_532 gd.ga.cy_554 es.rw.un_220
+ 0x050701a4, 0x2128680c, 0x205232a0, 0x08020c12, // en.it.fr_433 ig.sw.jw_543 bs.ha.sq_322 sv.da.no_654
+ 0x282a27ee, 0x06202307, 0x4a556812, 0x11002521, // gd.mt.sw_422 ca.sq.de_432 ig.rw.yo_654 eu.ro.un_860
+ // [1e70]
+ 0x1f311e12, 0x04001319, 0x07001908, 0x1e1c6b02, // ms.az.cy_654 et.fi.un_750 gl.it.un_430 ceb.id.ms_222
+ 0x31283b07, 0x030e3f07, 0x0f001f07, 0x28133b55, // so.sw.az_432 af.is.nl_432 cy.lv.un_420 so.et.sw_442
+ 0x03060808, 0x200e1f11, 0x3f130308, 0x0e521aa9, // no.de.nl_443 cy.is.sq_653 nl.et.af_443 tl.ha.is_544
+ 0x020c08ec, 0x170407a9, 0x13033fa4, 0x5528350c, // no.sv.da_644 bg.ru.sr_544 af.nl.et_433 zu.sw.rw_543
+ // [1e80]
+ 0x1e1c250c, 0x12002919, 0x21001a1b, 0x022d0e12, // eu.id.ms_543 sl.hu.un_750 tl.jw.un_770 is.sk.da_654
+ 0x04033b04, 0x0c00091a, 0x214a68ad, 0x3b033f55, // so.nl.fi_332 pl.sv.un_760 ig.yo.jw_643 af.nl.so_442
+ 0x050a2802, 0x04133b07, 0x4a113f0c, 0x131f2704, // sw.pt.fr_222 so.et.fi_432 af.ro.yo_543 gd.cy.et_332
+ 0x28004a08, 0x082a0107, 0x68023b08, 0x28005508, // yo.sw.un_430 en.mt.no_432 so.da.ig_443 rw.sw.un_430
+ // [1e90]
+ 0x0a041305, 0x18001219, 0x3f2b1cee, 0x17001304, // et.fi.pt_333 ur.ar.un_750 id.vi.af_422 et.sr.un_320
+ 0x033f3baf, 0x12001821, 0x0e126407, 0x190a0505, // so.af.nl_655 ar.ur.un_860 lg.hu.is_432 fr.pt.gl_333
+ 0x6455350d, 0x2a212805, 0x122a6407, 0x09000707, // zu.rw.lg_554 sw.jw.mt_333 lg.mt.hu_432 it.pl.un_420
+ 0x190a25a9, 0x04170705, 0x05002307, 0x0c0501a4, // eu.pt.gl_544 bg.sr.ru_333 ca.fr.un_420 en.fr.sv_433
+ // [1ea0]
+ 0x2a1b5208, 0x2b533b05, 0x35645512, 0x530d2d11, // ha.tr.mt_443 so.ht.vi_333 rw.lg.zu_654 sk.cs.ht_653
+ 0x08100413, 0x52122aa7, 0x170a10ee, 0x4a001321, // ru.be.uk_665 mt.hu.ha_532 be.mk.sr_422 et.yo.un_860
+ 0x04033f09, 0x190f0bec, 0x036813a4, 0x17231fad, // af.nl.fi_444 es.lv.gl_644 et.ig.nl_433 cy.ca.sr_643
+ 0x081017a4, 0x3f033b08, 0x64522a60, 0x04133bec, // sr.be.uk_433 so.nl.af_443 mt.ha.lg_664 so.et.fi_644
+ // [1eb0]
+ 0x641201a4, 0x1c211e04, 0x070116a0, 0x28002104, // en.hu.lg_433 ms.jw.id_332 hr.en.it_322 jw.sw.un_320
+ 0x29203107, 0x0d000a02, 0x3b64520b, 0x12006414, // az.sq.sl_432 pt.cs.un_220 ha.lg.so_542 lg.hu.un_660
+ 0x183f1fad, 0x17005207, 0x550b18a4, 0x23000509, // cy.af.ga_643 ha.sr.un_420 ga.es.rw_433 fr.ca.un_440
+ 0x042835ee, 0x08071012, 0x022d0d60, 0x180153ee, // zu.sw.fi_422 be.bg.uk_654 cs.sk.da_664 ht.en.ga_422
+ // [1ec0]
+ 0x250328a0, 0x28001705, 0x64082aad, 0x2a210eee, // sw.nl.eu_322 sr.sw.un_330 mt.no.lg_643 is.jw.mt_422
+ 0x19230bec, 0x07522aa4, 0x0a006408, 0x321655a0, // es.ca.gl_644 mt.ha.it_433 lg.pt.un_430 rw.hr.bs_322
+ 0x2d170d14, 0x190b6b05, 0x0e001219, 0x291f3ba7, // cs.sr.sk_666 ceb.es.gl_333 hu.is.un_750 so.cy.sl_532
+ 0x6b685305, 0x18092711, 0x55136408, 0x063f08a4, // ht.ig.ceb_333 gd.pl.ga_653 lg.et.rw_443 no.af.de_433
+ // [1ed0]
+ 0x25001219, 0x0a07110c, 0x2b002308, 0x02000108, // hu.eu.un_750 ro.bg.mk_543 ca.vi.un_430 en.da.un_430
+ 0x05000b04, 0x27000912, 0x0e3f0804, 0x0f285255, // es.fr.un_320 pl.gd.un_640 no.af.is_332 ha.sw.lv_442
+ 0x29005208, 0x1a6b0c0c, 0x20132807, 0x06050104, // ha.sl.un_430 sv.ceb.tl_543 sw.et.sq_432 en.fr.de_332
+ 0x08000718, 0x0a070f04, 0x19050b55, 0x071017ee, // bg.uk.un_740 lv.it.pt_332 es.fr.gl_442 sr.be.bg_422
+ // [1ee0]
+ 0x0d002d08, 0x1c2128a4, 0x292d1007, 0x2b000b05, // sk.cs.un_430 sw.jw.id_433 lt.sk.sl_432 es.vi.un_330
+ 0x100a0712, 0x0a000104, 0x12000a14, 0x0a171160, // bg.mk.be_654 en.pt.un_320 pt.hu.un_660 ro.sr.mk_664
+ 0x2a203560, 0x01000208, 0x1a002709, 0x07005202, // zu.sq.mt_664 da.en.un_430 gd.tl.un_440 ha.it.un_220
+ 0x18190b05, 0x0500110d, 0x2d290d0c, 0x04080711, // es.gl.ga_333 ro.fr.un_540 cs.sl.sk_543 bg.uk.ru_653
+ // [1ef0]
+ 0x07000813, 0x0964350c, 0x07002712, 0x07040aa0, // uk.bg.un_650 zu.lg.pl_543 gd.it.un_640 mk.ru.bg_322
+ 0x056b0aa0, 0x18001209, 0x2d001223, 0x18002112, // pt.ceb.fr_322 ur.ar.un_440 hu.sk.un_880 fa.ar.un_640
+ 0x17000712, 0x64000119, 0x3b1a6baf, 0x55006409, // bg.sr.un_640 en.lg.un_750 ceb.tl.so_655 lg.rw.un_440
+ 0x040817a7, 0x231f3f02, 0x0b2319ec, 0x6b3b01a4, // sr.uk.ru_532 af.cy.ca_222 gl.ca.es_644 en.so.ceb_433
+ // [1f00]
+ 0x322518ee, 0x322d0da4, 0x092a040c, 0x1a003b35, // ga.eu.bs_422 cs.sk.bs_433 fi.mt.pl_543 so.tl.un_A90
+ 0x0f080c55, 0x10002813, 0x3f042aa4, 0x1b1e3111, // sv.no.lv_442 sw.lt.un_650 mt.fi.af_433 az.ms.tr_653
+ 0x3b000122, 0x27006402, 0x0c00080e, 0x285201af, // en.so.un_870 lg.gd.un_220 no.sv.un_550 en.ha.sw_655
+ 0x07041702, 0x3b531a0d, 0x126b1a02, 0x06000c19, // sr.ru.bg_222 tl.ht.so_554 tl.ceb.hu_222 sv.de.un_750
+ // [1f10]
+ 0x53001a14, 0x2800641a, 0x642835ad, 0x25002721, // tl.ht.un_660 lg.sw.un_760 zu.sw.lg_643 gd.eu.un_860
+ 0x6b1a3b13, 0x2a3b64a4, 0x106429ec, 0x0e005512, // so.tl.ceb_665 lg.so.mt_433 sl.lg.lt_644 rw.is.un_640
+ 0x0b002319, 0x1e004a04, 0x20001b04, 0x05005307, // ca.es.un_750 yo.ms.un_320 tr.sq.un_320 ht.fr.un_420
+ 0x00000906, 0x5500290e, 0x1000291a, 0x2d00230d, // hi.un.un_400 sl.rw.un_550 sl.lt.un_760 ca.sk.un_540
+ // [1f20]
+ 0x0c00081b, 0x016b1a14, 0x31121307, 0x0f530408, // no.sv.un_770 tl.ceb.en_666 et.hu.az_432 fi.ht.lv_443
+ 0x6b1a3ba9, 0x17080a09, 0x02000835, 0x32002904, // so.tl.ceb_544 mk.uk.sr_444 no.da.un_A90 sl.bs.un_320
+ 0x0800122c, 0x25181b0c, 0x3b000113, 0x1a003b23, // hu.no.un_990 tr.ga.eu_543 en.so.un_650 so.tl.un_880
+ 0x531a6ba9, 0x0f2a1012, 0x3f0603a6, 0x1e000607, // ceb.tl.ht_544 lt.mt.lv_654 nl.de.af_521 de.ms.un_420
+ // [1f30]
+ 0x12530e07, 0x282110a6, 0x32001e07, 0x2800520e, // is.ht.hu_432 lt.jw.sw_521 ms.bs.un_420 ha.sw.un_550
+ 0x64005509, 0x1f3b08ec, 0x3b1204a4, 0x281e52a4, // rw.lg.un_440 no.so.cy_644 fi.hu.so_433 ha.ms.sw_433
+ 0x04211c02, 0x08002813, 0x32001708, 0x1f050107, // id.jw.fi_222 sw.no.un_650 sr.bs.un_430 en.fr.cy_432
+ 0x0f191107, 0x52206404, 0x08071709, 0x28171307, // ro.gl.lv_432 lg.sq.ha_332 sr.bg.uk_444 et.sr.sw_432
+ // [1f40]
+ 0x020c29a4, 0x12250da0, 0x524a1aad, 0x270c0ea0, // sl.sv.da_433 cs.eu.hu_322 tl.yo.ha_643 is.sv.gd_322
+ 0x1c5209a4, 0x10182aad, 0x23194aee, 0x0e000d1a, // pl.ha.id_433 mt.ga.lt_643 yo.gl.ca_422 cs.is.un_760
+ 0x033f0c5a, 0x25190b09, 0x280f1fa4, 0x35006404, // sv.af.nl_553 es.gl.eu_444 cy.lv.sw_433 lg.zu.un_320
+ 0x06001f21, 0x181f2707, 0x3f0e08af, 0x6e001f14, // cy.de.un_860 gd.cy.ga_432 no.is.af_655 cy.hmn.un_660
+ // [1f50]
+ 0x10000707, 0x010825ad, 0x2329120c, 0x0d12040c, // it.lt.un_420 eu.no.en_643 hu.sl.ca_543 fi.hu.cs_543
+ 0x1b005202, 0x552035a0, 0x3f030eee, 0x2164550c, // ha.tr.un_220 zu.sq.rw_322 is.nl.af_422 rw.lg.jw_543
+ 0x04122a07, 0x012a1213, 0x1764520c, 0x060c135a, // mt.hu.fi_432 hu.mt.en_665 ha.lg.sr_543 et.sv.de_553
+ 0x0a190b5a, 0x12000408, 0x04000a1a, 0x2b00060d, // es.gl.pt_553 fi.hu.un_430 mk.ru.un_760 de.vi.un_540
+ // [1f60]
+ 0x351a6b0b, 0x3b001308, 0x35286409, 0x25315204, // ceb.tl.zu_542 et.so.un_430 lg.sw.zu_444 ha.az.eu_332
+ 0x0d1c135a, 0x1123070c, 0x20000612, 0x081a6b0b, // bh.mr.ne_553 it.ca.ro_543 de.sq.un_640 ceb.tl.no_542
+ 0x17005502, 0x101f27ee, 0x3b000c0e, 0x35645504, // rw.sr.un_220 gd.cy.lt_422 sv.so.un_550 rw.lg.zu_332
+ 0x53000c05, 0x1b256b05, 0x0a1711af, 0x13005208, // sv.ht.un_330 ceb.eu.tr_333 ro.sr.mk_655 ha.et.un_430
+ // [1f70]
+ 0x28641ea0, 0x2a103b0d, 0x170704a4, 0x644a27ad, // ms.lg.sw_322 so.lt.mt_554 ru.bg.sr_433 gd.yo.lg_643
+ 0x3f003b05, 0x534a3ba4, 0x6b1a3b0b, 0x06255507, // so.af.un_330 so.yo.ht_433 so.tl.ceb_542 rw.eu.de_432
+ 0x21005519, 0x3b002a04, 0x2b3108a0, 0x07100b08, // rw.jw.un_750 mt.so.un_320 no.az.vi_322 es.lt.it_443
+ 0x101a5307, 0x52112a0c, 0x19120707, 0x29080ca9, // ht.tl.lt_432 mt.ro.ha_543 it.hu.gl_432 sv.no.sl_544
+ // [1f80]
+ 0x09001018, 0x050c2aa4, 0x642a0b08, 0x1a070aa4, // lt.pl.un_740 mt.sv.fr_433 es.mt.lg_443 pt.it.tl_433
+ 0x08100aa4, 0x12684aad, 0x551e1c5a, 0x18016ba0, // mk.be.uk_433 yo.ig.hu_643 id.ms.rw_553 ceb.en.ga_322
+ 0x6b2a07ee, 0x6410550c, 0x641a6b05, 0x0f00060d, // it.mt.ceb_422 rw.lt.lg_543 ceb.tl.lg_333 de.lv.un_540
+ 0x3b641108, 0x01023ba0, 0x06001b12, 0x640c2a0c, // ro.lg.so_443 so.da.en_322 tr.de.un_640 mt.sv.lg_543
+ // [1f90]
+ 0x190a0ba9, 0x0a1110ad, 0x12233b07, 0x3f133b07, // es.pt.gl_544 be.ro.mk_643 so.ca.hu_432 so.et.af_432
+ 0x1f0c0204, 0x0d002912, 0x07001008, 0x6b010304, // da.sv.cy_332 sl.cs.un_640 lt.it.un_430 nl.en.ceb_332
+ 0x0c004a07, 0x102829a4, 0x0e002502, 0x290b07a7, // yo.sv.un_420 sl.sw.lt_433 eu.is.un_220 it.es.sl_532
+ 0x12160d07, 0x190a6402, 0x3f03070c, 0x3b000908, // cs.hr.hu_432 lg.pt.gl_222 it.nl.af_543 pl.so.un_430
+ // [1fa0]
+ 0x170a10a6, 0x17000809, 0x040a11a0, 0x290d2d11, // be.mk.sr_521 uk.sr.un_440 ro.mk.ru_322 sk.cs.sl_653
+ 0x00002506, 0x07081112, 0x100f1705, 0x250853a9, // eu.un.un_400 ro.uk.bg_654 sr.lv.lt_333 ht.no.eu_544
+ 0x530d1004, 0x17101108, 0x12290da0, 0x0a001709, // lt.cs.ht_332 ro.be.sr_443 cs.sl.hu_322 sr.mk.un_440
+ 0x0d00180d, 0x0b072907, 0x096b1f04, 0x1f3b550b, // ga.cs.un_540 sl.it.es_432 cy.ceb.pl_332 rw.so.cy_542
+ // [1fb0]
+ 0x6b000104, 0x2910070c, 0x02001305, 0x07291aa7, // en.ceb.un_320 it.lt.sl_543 et.da.un_330 tl.sl.it_532
+ 0x04000819, 0x0d00020b, 0x35002808, 0x28001a04, // uk.ru.un_750 da.cs.un_520 sw.zu.un_430 tl.sw.un_320
+ 0x2b000113, 0x1e1c3509, 0x06002a14, 0x1e4a6402, // en.vi.un_650 zu.id.ms_444 mt.de.un_660 lg.yo.ms_222
+ 0x04001121, 0x1f272da4, 0x2100122b, 0x0e042a08, // ro.ru.un_860 sk.gd.cy_433 ur.fa.un_980 mt.fi.is_443
+ // [1fc0]
+ 0x122a070c, 0x070a11ad, 0x29002d22, 0x311b200c, // it.mt.hu_543 ro.mk.bg_643 sk.sl.un_870 sq.tr.az_543
+ 0x066b1aee, 0x64001219, 0x1a106bad, 0x2a041208, // tl.ceb.de_422 hu.lg.un_750 ceb.lt.tl_643 hu.fi.mt_443
+ 0x10080411, 0x05001a04, 0x55006404, 0x07000808, // ru.uk.be_653 tl.fr.un_320 lg.rw.un_320 no.it.un_430
+ 0x28003b09, 0x03073fa0, 0x1200640d, 0x12001b08, // so.sw.un_440 af.it.nl_322 lg.hu.un_540 tr.hu.un_430
+ // [1fd0]
+ 0x11070aaf, 0x190a0bec, 0x13002011, 0x070305a0, // mk.bg.ro_655 es.pt.gl_644 sq.et.un_630 fr.nl.it_322
+ 0x2d002909, 0x55281ca4, 0x0b002304, 0x3b312a0c, // sl.sk.un_440 id.sw.rw_433 ca.es.un_320 mt.az.so_543
+ 0x2b001f07, 0x25000622, 0x08020112, 0x1a006b09, // cy.vi.un_420 de.eu.un_870 en.da.no_654 ceb.tl.un_440
+ 0x3f002507, 0x0c000221, 0x04170813, 0x6b1a5214, // eu.af.un_420 da.sv.un_860 uk.sr.ru_665 ha.tl.ceb_666
+ // [1fe0]
+ 0x1b1a6ba4, 0x321603ec, 0x0c033f55, 0x052a01a0, // ceb.tl.tr_433 nl.hr.bs_644 af.nl.sv_442 en.mt.fr_322
+ 0x3f2a0707, 0x2835640e, 0x3f080ca4, 0x2d00291a, // it.mt.af_432 lg.zu.sw_555 sv.no.af_433 sl.sk.un_760
+ 0x251a6b12, 0x321b06ee, 0x0a001112, 0x0e3f4a0c, // ceb.tl.eu_654 de.tr.bs_422 ro.mk.un_640 yo.af.is_543
+ 0x3b005322, 0x06006814, 0x060a23a4, 0x0a311b13, // ht.so.un_870 ig.de.un_660 ca.pt.de_433 tr.az.pt_665
+ // [1ff0]
+ 0x0c0e13a0, 0x09000104, 0x52310c07, 0x04521a04, // et.is.sv_322 en.pl.un_320 sv.az.ha_432 tl.ha.fi_332
+ 0x321116a0, 0x06080c05, 0x3f042a0c, 0x0752290c, // hr.ro.bs_322 sv.no.de_333 mt.fi.af_543 sl.ha.it_543
+ 0x1213040d, 0x10003207, 0x11521e0b, 0x0427180c, // fi.et.hu_554 bs.lt.un_420 ms.ha.ro_542 ga.gd.fi_543
+ 0x52002512, 0x2a0f2813, 0x10000f0d, 0x3b0807a9, // eu.ha.un_640 sw.lv.mt_665 lv.lt.un_540 it.no.so_544
+
+ // [2000]
+ 0x29090dad, 0x55080213, 0x4a2718a4, 0x52002a1a, // cs.pl.sl_643 da.no.rw_665 ga.gd.yo_433 mt.ha.un_760
+ 0x08024aee, 0x07001014, 0x6b081005, 0x040313a9, // yo.da.no_422 be.bg.un_660 lt.no.ceb_333 et.nl.fi_544
+ 0x31005204, 0x20001704, 0x12001f05, 0x3f001a02, // ha.az.un_320 sr.sq.un_320 cy.hu.un_330 tl.af.un_220
+ 0x060d3f0c, 0x1e181c0c, 0x2a00312a, 0x16006b08, // af.cs.de_543 id.ga.ms_543 az.mt.un_970 ceb.hr.un_430
+ // [2010]
+ 0x193f1f0c, 0x29121604, 0x4a16310c, 0x321607a4, // cy.af.gl_543 hr.hu.sl_332 az.hr.yo_543 it.hr.bs_433
+ 0x09520d0b, 0x521807a4, 0x553564af, 0x11231905, // cs.ha.pl_542 it.ga.ha_433 lg.zu.rw_655 gl.ca.ro_333
+ 0x312319a4, 0x1b0f6eee, 0x12114aa4, 0x13001e1a, // gl.ca.az_433 hmn.lv.tr_422 yo.ro.hu_433 ms.et.un_760
+ 0x6b1a640c, 0x27251155, 0x051255ad, 0x08230560, // lg.tl.ceb_543 ro.eu.gd_442 rw.hu.fr_643 fr.ca.no_664
+ // [2020]
+ 0x21002505, 0x23122905, 0x55211e08, 0x08033f07, // eu.jw.un_330 sl.hu.ca_333 ms.jw.rw_443 af.nl.no_432
+ 0x1f00100d, 0x1a646b12, 0x01000607, 0x0e130608, // lt.cy.un_540 ceb.lg.tl_654 de.en.un_420 de.et.is_443
+ 0x0a04170c, 0x0e002a1a, 0x55001114, 0x05006e34, // sr.ru.mk_543 mt.is.un_760 ro.rw.un_660 hmn.fr.un_A80
+ 0x0d0952a0, 0x23060512, 0x05002d04, 0x1709070c, // ha.pl.cs_322 fr.de.ca_654 sk.fr.un_320 it.pl.sr_543
+ // [2030]
+ 0x040e13ec, 0x21001a0d, 0x6e00231a, 0x3f1f0c04, // et.is.fi_644 tl.jw.un_540 ca.hmn.un_760 sv.cy.af_332
+ 0x0a0155ee, 0x09072a08, 0x033568a9, 0x2535550c, // rw.en.pt_422 mt.it.pl_443 ig.zu.nl_544 rw.zu.eu_543
+ 0x683b52ec, 0x18276eaf, 0x3f1b050c, 0x2504130c, // ha.so.ig_644 hmn.gd.ga_655 fr.tr.af_543 et.fi.eu_543
+ 0x642555ee, 0x4a005205, 0x31206ea7, 0x31536407, // rw.eu.lg_422 ha.yo.un_330 hmn.sq.az_532 lg.ht.az_432
+ // [2040]
+ 0x031b5504, 0x2d0b29a4, 0x3f03190c, 0x53551c0b, // rw.tr.nl_332 sl.es.sk_433 gl.nl.af_543 id.rw.ht_542
+ 0x21003f1b, 0x05006e11, 0x6b1a53a4, 0x033f52ee, // af.jw.un_770 hmn.fr.un_630 ht.tl.ceb_433 ha.af.nl_422
+ 0x2a3555ad, 0x03003b08, 0x04071113, 0x13002821, // rw.zu.mt_643 so.nl.un_430 ro.bg.ru_665 sw.et.un_860
+ 0x1108170c, 0x3b033fec, 0x3f212aac, 0x52286404, // sr.uk.ro_543 af.nl.so_644 mt.jw.af_632 lg.sw.ha_332
+ // [2050]
+ 0x52065512, 0x521e1fad, 0x1f1e1c12, 0x530c23ee, // rw.de.ha_654 cy.ms.ha_643 id.ms.cy_654 ca.sv.ht_422
+ 0x2a002813, 0x08000e0d, 0x2b006e0c, 0x4a182dad, // sw.mt.un_650 is.no.un_540 hmn.vi.un_530 sk.ga.yo_643
+ 0x130f0602, 0x06101fa9, 0x231301a0, 0x0e080707, // de.lv.et_222 cy.lt.de_544 en.et.ca_322 it.no.is_432
+ 0x550c08a0, 0x06281fad, 0x10005208, 0x0717080c, // no.sv.rw_322 cy.sw.de_643 ha.lt.un_430 uk.sr.bg_543
+ // [2060]
+ 0x10004a05, 0x08070a0d, 0x1e1c10af, 0x07101708, // yo.lt.un_330 mk.bg.uk_554 lt.id.ms_655 sr.be.bg_443
+ 0x0655520e, 0x00001f2d, 0x201b3112, 0x12004a09, // ha.rw.de_555 cy.un.un_A00 az.tr.sq_654 yo.hu.un_440
+ 0x23001f1a, 0x08020da4, 0x4a6e6ba0, 0x1b162908, // cy.ca.un_760 cs.da.no_433 ceb.hmn.yo_322 sl.hr.tr_443
+ 0x23111f13, 0x356420a4, 0x1b315504, 0x06001f1a, // cy.ro.ca_665 sq.lg.zu_433 rw.az.tr_332 cy.de.un_760
+ // [2070]
+ 0x3229170b, 0x5564130c, 0x2d000205, 0x311a280c, // sr.sl.bs_542 et.lg.rw_543 da.sk.un_330 sw.tl.az_543
+ 0x2d0d0214, 0x1a001f1b, 0x1c0d09ad, 0x05013f07, // da.cs.sk_666 cy.tl.un_770 hi.ne.mr_643 af.en.fr_432
+ 0x11170808, 0x1300040e, 0x64045512, 0x5200280d, // uk.sr.ro_443 fi.et.un_550 rw.fi.lg_654 sw.ha.un_540
+ 0x13350460, 0x64292807, 0x101a130c, 0x64000a07, // fi.zu.et_664 sw.sl.lg_432 et.tl.lt_543 pt.lg.un_420
+ // [2080]
+ 0x68004a14, 0x0c033f04, 0x211e1f12, 0x52101fa4, // yo.ig.un_660 af.nl.sv_332 cy.ms.jw_654 cy.lt.ha_433
+ 0x29351307, 0x02000c04, 0x2700180c, 0x551a6b12, // et.zu.sl_432 sv.da.un_320 ga.gd.un_530 ceb.tl.rw_654
+ 0x0300270d, 0x3f062307, 0x6b060cad, 0x1c001e12, // gd.nl.un_540 ca.de.af_432 sv.de.ceb_643 ms.id.un_640
+ 0x5535680c, 0x1e1c1f0c, 0x00003103, 0x2d090d12, // ig.zu.rw_543 cy.id.ms_543 az.un.un_300 cs.pl.sk_654
+ // [2090]
+ 0x0d033fa4, 0x10251fa7, 0x3f2821a0, 0x6b6801ee, // af.nl.cs_433 cy.eu.lt_532 jw.sw.af_322 en.ig.ceb_422
+ 0x07080260, 0x08121b0c, 0x645228ac, 0x070411a6, // da.no.it_664 tr.hu.no_543 sw.ha.lg_632 ro.ru.bg_521
+ 0x07170412, 0x092d3f08, 0x0a170805, 0x08020c05, // ru.sr.bg_654 af.sk.pl_443 uk.sr.mk_333 sv.da.no_333
+ 0x190b2305, 0x18000713, 0x1b001313, 0x013b0ca0, // ca.es.gl_333 it.ga.un_650 et.tr.un_650 sv.so.en_322
+ // [20a0]
+ 0x1328350c, 0x2d2913ee, 0x041310a4, 0x17000a0e, // zu.sw.et_543 et.sl.sk_422 lt.et.fi_433 mk.sr.un_550
+ 0x1f27180c, 0x13000108, 0x130c2aa4, 0x16321707, // ga.gd.cy_543 en.et.un_430 mt.sv.et_433 sr.bs.hr_432
+ 0x10521aad, 0x04311304, 0x640813a4, 0x201801af, // tl.ha.lt_643 et.az.fi_332 et.no.lg_433 en.ga.sq_655
+ 0x02006b04, 0x06002a0c, 0x01003104, 0x4a005504, // ceb.da.un_320 mt.de.un_530 az.en.un_320 rw.yo.un_320
+ // [20b0]
+ 0x64005507, 0x1e006b05, 0x061f08a4, 0x2800351a, // rw.lg.un_420 ceb.ms.un_330 no.cy.de_433 zu.sw.un_760
+ 0x1a003512, 0x063f0308, 0x55284aa7, 0x1c132a10, // zu.tl.un_640 nl.af.de_443 yo.sw.rw_532 mt.et.id_642
+ 0x1f3f2aa0, 0x091b3fa0, 0x35005202, 0x18211209, // mt.af.cy_322 af.tr.pl_322 ha.zu.un_220 ur.fa.ar_444
+ 0x0f003f05, 0x2a1b53a0, 0x64321604, 0x07170aaf, // af.lv.un_330 ht.tr.mt_322 hr.bs.lg_332 mk.sr.bg_655
+ // [20c0]
+ 0x1b11530c, 0x2a1b08a4, 0x0e070807, 0x1e1c64a9, // ht.ro.tr_543 no.tr.mt_433 no.it.is_432 lg.id.ms_544
+ 0x3500680c, 0x645528ee, 0x230a05ec, 0x3b125355, // ig.zu.un_530 sw.rw.lg_422 fr.pt.ca_644 ht.hu.so_442
+ 0x1c641eaf, 0x1e1c640c, 0x072a1fee, 0x532a11a4, // ms.lg.id_655 lg.id.ms_543 cy.mt.it_422 ro.mt.ht_433
+ 0x35123b05, 0x64551c07, 0x2d000b04, 0x1e1c55a0, // so.hu.zu_333 id.rw.lg_432 es.sk.un_320 rw.id.ms_322
+ // [20d0]
+ 0x091e1c08, 0x13000304, 0x4a00211a, 0x211a55a4, // id.ms.pl_443 nl.et.un_320 jw.yo.un_760 rw.tl.jw_433
+ 0x0000282d, 0x0d130913, 0x012a6ea0, 0x64005523, // sw.un.un_A00 hi.bh.ne_665 hmn.mt.en_322 rw.lg.un_880
+ 0x13090da4, 0x1a556b08, 0x170a04ad, 0x08000c02, // ne.hi.bh_433 ceb.rw.tl_443 ru.mk.sr_643 sv.no.un_220
+ 0x0c000708, 0x06040312, 0x53052aa0, 0x04131a07, // it.sv.un_430 nl.fi.de_654 mt.fr.ht_322 tl.et.fi_432
+ // [20e0]
+ 0x0c0308a0, 0x0b00250c, 0x3500280c, 0x033f2aee, // no.nl.sv_322 eu.es.un_530 sw.zu.un_530 mt.af.nl_422
+ 0x0e080c04, 0x082502a0, 0x200f1009, 0x3564550c, // sv.no.is_332 da.eu.no_322 lt.lv.sq_444 rw.lg.zu_543
+ 0x08092a07, 0x0d1c09af, 0x6b6803a4, 0x1e1c55a4, // mt.pl.no_432 hi.mr.ne_655 nl.ig.ceb_433 rw.id.ms_433
+ 0x111b31af, 0x1700200c, 0x1255640c, 0x070a01a4, // az.tr.ro_655 sq.sr.un_530 lg.rw.hu_543 en.pt.it_433
+ // [20f0]
+ 0x0e000c04, 0x5509680d, 0x1c090d13, 0x1c0d13af, // sv.is.un_320 ig.pl.rw_554 ne.hi.mr_665 bh.ne.mr_655
+ 0x1c551e0c, 0x07130455, 0x0f002d13, 0x041b310d, // ms.rw.id_543 fi.et.it_442 sk.lv.un_650 az.tr.fi_554
+ 0x1b11290c, 0x16321711, 0x1e1c55ec, 0x531b3160, // sl.ro.tr_543 sr.bs.hr_653 rw.id.ms_644 az.tr.ht_664
+ 0x1e641c0c, 0x1828090c, 0x23001e13, 0x02041304, // id.lg.ms_543 pl.sw.ga_543 ms.ca.un_650 et.fi.da_332
+ // [2100]
+ 0x55006b11, 0x29092dad, 0x1c5221a0, 0x0a000422, // ceb.rw.un_630 sk.pl.sl_643 jw.ha.id_322 ru.mk.un_870
+ 0x55000104, 0x1f001221, 0x20002902, 0x6b6853a4, // en.rw.un_320 hu.cy.un_860 sl.sq.un_220 ht.ig.ceb_433
+ 0x066b1a07, 0x170a07ee, 0x19211fa4, 0x2d3b1108, // tl.ceb.de_432 bg.mk.sr_422 cy.jw.gl_433 ro.so.sk_443
+ 0x012a07ad, 0x17040855, 0x1f002b1b, 0x3f020ea4, // it.mt.en_643 uk.ru.sr_442 vi.cy.un_770 is.da.af_433
+ // [2110]
+ 0x2a1118ee, 0x2b001f0d, 0x2b002302, 0x0c005302, // ga.ro.mt_422 cy.vi.un_540 ca.vi.un_220 ht.sv.un_220
+ 0x291b0ba4, 0x1f3b3f12, 0x31091b12, 0x31000707, // es.tr.sl_433 af.so.cy_654 tr.pl.az_654 it.az.un_420
+ 0x100a0709, 0x64012704, 0x0c1b3108, 0x113129a0, // bg.mk.be_444 gd.en.lg_332 az.tr.sv_443 sl.az.ro_322
+ 0x113b0f07, 0x0c530802, 0x23072da4, 0x53005214, // lv.so.ro_432 no.ht.sv_222 sk.it.ca_433 ha.ht.un_660
+ // [2120]
+ 0x080e0c02, 0x013153ee, 0x18052709, 0x1a126bec, // sv.is.no_222 ht.az.en_422 gd.fr.ga_444 ceb.hu.tl_644
+ 0x05533b05, 0x04130c05, 0x12002a05, 0x08000c05, // so.ht.fr_333 sv.et.fi_333 mt.hu.un_330 sv.no.un_330
+ 0x1c00130b, 0x10020caf, 0x55531fa7, 0x18002707, // bh.mr.un_520 sv.da.lt_655 cy.ht.rw_532 gd.ga.un_420
+ 0x070811ad, 0x060a1ba4, 0x100a08a4, 0x06052304, // ro.uk.bg_643 tr.pt.de_433 uk.mk.be_433 ca.fr.de_332
+ // [2130]
+ 0x2704180c, 0x12001309, 0x0a001912, 0x08001008, // ga.fi.gd_543 et.hu.un_440 gl.pt.un_640 be.uk.un_430
+ 0x0400170e, 0x2555640c, 0x09061f0d, 0x55296405, // sr.ru.un_550 lg.rw.eu_543 cy.de.pl_554 lg.sl.rw_333
+ 0x08000411, 0x110817a4, 0x03173fa0, 0x0e0802ad, // ru.uk.un_630 sr.uk.ro_433 af.sr.nl_322 da.no.is_643
+ 0x0a2d0d0e, 0x110a07a4, 0x041107ec, 0x111e1c0d, // cs.sk.pt_555 bg.mk.ro_433 bg.ro.ru_644 id.ms.ro_554
+ // [2140]
+ 0x0c2301a4, 0x0a230611, 0x28006419, 0x0900200e, // en.ca.sv_433 de.ca.pt_653 lg.sw.un_750 sq.pl.un_550
+ 0x320917a4, 0x1f003b13, 0x64550705, 0x010653a0, // sr.pl.bs_433 so.cy.un_650 it.rw.lg_333 ht.de.en_322
+ 0x09290da4, 0x1103250c, 0x05001c02, 0x352011a4, // cs.sl.pl_433 eu.nl.ro_543 id.fr.un_220 ro.sq.zu_433
+ 0x5201050d, 0x0a080708, 0x254a11a7, 0x0c0552a0, // fr.en.ha_554 bg.uk.mk_443 ro.yo.eu_532 ha.fr.sv_322
+ // [2150]
+ 0x16066855, 0x280a520c, 0x2d0d11a4, 0x022a070c, // ig.de.hr_442 ha.pt.sw_543 ro.cs.sk_433 it.mt.da_543
+ 0x0a04100c, 0x08070aa4, 0x6b2707ad, 0x080e0c07, // be.ru.mk_543 mk.bg.uk_433 it.gd.ceb_643 sv.is.no_432
+ 0x092d0d12, 0x213f1c07, 0x18012504, 0x033f0ca0, // cs.sk.pl_654 id.af.jw_432 eu.en.ga_332 sv.af.nl_322
+ 0x10040809, 0x1132165a, 0x3f192707, 0x0a292d07, // uk.ru.be_444 hr.bs.ro_553 gd.gl.af_432 sk.sl.pt_432
+ // [2160]
+ 0x171b2107, 0x1b001804, 0x0f043202, 0x12553b08, // jw.tr.sr_432 ga.tr.un_320 bs.fi.lv_222 so.rw.hu_443
+ 0x215564ad, 0x01113507, 0x353f0313, 0x11072da4, // lg.rw.jw_643 zu.ro.en_432 nl.af.zu_665 sk.it.ro_433
+ 0x1c002104, 0x64552812, 0x0700521b, 0x1e001f2a, // jw.id.un_320 sw.rw.lg_654 ha.it.un_770 cy.ms.un_970
+ 0x1c003f04, 0x171625ee, 0x0e023fa0, 0x021e1c02, // af.id.un_320 eu.hr.sr_422 af.da.is_322 id.ms.da_222
+ // [2170]
+ 0x35000307, 0x08023fa4, 0x1c1b29ee, 0x0e0c120c, // nl.zu.un_420 af.da.no_433 sl.tr.id_422 hu.sv.is_543
+ 0x25182713, 0x1a35550c, 0x03002707, 0x290d1baf, // gd.ga.eu_665 rw.zu.tl_543 gd.nl.un_420 tr.cs.sl_655
+ 0x01052aa4, 0x32003b05, 0x01000907, 0x21004a07, // mt.fr.en_433 so.bs.un_330 pl.en.un_420 yo.jw.un_420
+ 0x201f55a4, 0x09251108, 0x0a0710a4, 0x20253b05, // rw.cy.sq_433 ro.eu.pl_443 be.bg.mk_433 so.eu.sq_333
+ // [2180]
+ 0x09002a0e, 0x21001205, 0x050602a0, 0x0d05010c, // mt.pl.un_550 ur.fa.un_330 da.de.fr_322 en.fr.cs_543
+ 0x0e231e04, 0x07001823, 0x06353f0c, 0x033f0f0c, // ms.ca.is_332 ga.it.un_880 af.zu.de_543 lv.af.nl_543
+ 0x252868a7, 0x0b00110b, 0x2d006e0e, 0x0e53040c, // ig.sw.eu_532 ro.es.un_520 hmn.sk.un_550 fi.ht.is_543
+ 0x1000111a, 0x531e1ca9, 0x1b356412, 0x12035204, // ro.be.un_760 id.ms.ht_544 lg.zu.tr_654 ha.nl.hu_332
+ // [2190]
+ 0x12002102, 0x2d000d2a, 0x4a311b11, 0x5535210c, // jw.hu.un_220 cs.sk.un_970 tr.az.yo_653 jw.zu.rw_543
+ 0x3500190e, 0x106b1a11, 0x13640e09, 0x214a1aad, // gl.zu.un_550 tl.ceb.lt_653 is.lg.et_444 tl.yo.jw_643
+ 0x28553512, 0x28291905, 0x02006809, 0x27071f0c, // zu.rw.sw_654 gl.sl.sw_333 ig.da.un_440 cy.it.gd_543
+ 0x09002d12, 0x2d093bec, 0x061b03ad, 0x2d0d29af, // sk.pl.un_640 so.pl.sk_644 nl.tr.de_643 sl.cs.sk_655
+ // [21a0]
+ 0x17110704, 0x1c2b0307, 0x09003204, 0x06002509, // bg.ro.sr_332 nl.vi.id_432 bs.pl.un_320 eu.de.un_440
+ 0x0d321608, 0x0f020807, 0x08006b0e, 0x3b4a3fad, // hr.bs.cs_443 no.da.lv_432 ceb.no.un_550 af.yo.so_643
+ 0x1007110c, 0x09002d19, 0x04001908, 0x53000b04, // ro.bg.be_543 sk.pl.un_750 gl.fi.un_430 es.ht.un_320
+ 0x040a2108, 0x0c000819, 0x07001709, 0x32171309, // jw.pt.fi_443 no.sv.un_750 sr.bg.un_440 et.sr.bs_444
+ // [21b0]
+ 0x28351c07, 0x250d2a07, 0x0655645a, 0x52082aa0, // id.zu.sw_432 mt.cs.eu_432 lg.rw.de_553 mt.no.ha_322
+ 0x0c000e08, 0x06680312, 0x0f2852a0, 0x0c000e1b, // is.sv.un_430 nl.ig.de_654 ha.sw.lv_322 is.sv.un_770
+ 0x10002108, 0x10090f0c, 0x28121b05, 0x100f32a0, // jw.lt.un_430 lv.pl.lt_543 tr.hu.sw_333 bs.lv.lt_322
+ 0x07130411, 0x32000708, 0x20321607, 0x05005308, // fi.et.it_653 it.bs.un_430 hr.bs.sq_432 ht.fr.un_430
+ // [21c0]
+ 0x08040ea7, 0x25192305, 0x292d1604, 0x210435ec, // is.fi.no_532 ca.gl.eu_333 hr.sk.sl_332 zu.fi.jw_644
+ 0x52642a07, 0x0f2d0d04, 0x32171611, 0x3b005204, // mt.lg.ha_432 cs.sk.lv_332 hr.sr.bs_653 ha.so.un_320
+ 0x1a256bee, 0x6b251ca0, 0x08042aad, 0x0c040807, // ceb.eu.tl_422 id.eu.ceb_322 mt.fi.no_643 no.fi.sv_432
+ 0x03001608, 0x1f27010c, 0x64005511, 0x3b3f030e, // hr.nl.un_430 en.gd.cy_543 rw.lg.un_630 nl.af.so_555
+ // [21d0]
+ 0x13040514, 0x2d0d1605, 0x1000081b, 0x0800010d, // fr.fi.et_666 hr.cs.sk_333 uk.be.un_770 en.no.un_540
+ 0x1e4a6812, 0x090d2d11, 0x35551e0c, 0x18002111, // ig.yo.ms_654 sk.cs.pl_653 ms.rw.zu_543 fa.ar.un_630
+ 0x130c04a4, 0x131064ee, 0x042a0713, 0x0c130ea9, // fi.sv.et_433 lg.lt.et_422 it.mt.fi_665 is.et.sv_544
+ 0x13001707, 0x060364a0, 0x2d0c0d55, 0x1221180c, // sr.et.un_420 lg.nl.de_322 cs.sv.sk_442 ar.fa.ur_543
+ // [21e0]
+ 0x100f1e07, 0x2d0d2aee, 0x040901a4, 0x0d091c11, // ms.lv.lt_432 mt.cs.sk_422 en.pl.fi_433 mr.hi.ne_653
+ 0x0e645511, 0x1c002108, 0x0c050707, 0x3b002a02, // rw.lg.is_653 jw.id.un_430 it.fr.sv_432 mt.so.un_220
+ 0x64041311, 0x211e1cac, 0x3b6455ad, 0x0531010c, // et.fi.lg_653 id.ms.jw_632 rw.lg.so_643 en.az.fr_543
+ 0x0e06180c, 0x64685512, 0x3f120205, 0x13000d07, // ga.de.is_543 rw.ig.lg_654 da.hu.af_333 ne.bh.un_420
+ // [21f0]
+ 0x3f13080c, 0x13001b18, 0x0e000419, 0x0c0102a0, // no.et.af_543 tr.et.un_740 fi.is.un_750 da.en.sv_322
+ 0x08040a14, 0x09001f0d, 0x1000041a, 0x0e101b08, // mk.ru.uk_666 cy.pl.un_540 ru.be.un_760 tr.lt.is_443
+ 0x01281fee, 0x2b004a23, 0x07040c08, 0x0c000e07, // cy.sw.en_422 yo.vi.un_880 sv.fi.it_443 is.sv.un_420
+ 0x07682107, 0x27000b04, 0x4a2b2713, 0x3b190b55, // jw.ig.it_432 es.gd.un_320 gd.vi.yo_665 es.gl.so_442
+ // [2200]
+ 0x271f29a4, 0x23312504, 0x35002802, 0x12000c13, // sl.cy.gd_433 eu.az.ca_332 sw.zu.un_220 sv.hu.un_650
+ 0x11231f0c, 0x3100230e, 0x27002b21, 0x091c0d0e, // cy.ca.ro_543 ca.az.un_550 vi.gd.un_860 ne.mr.hi_555
+ 0x19072508, 0x0a0411ee, 0x2b002714, 0x55250e0c, // eu.it.gl_443 ro.ru.mk_422 gd.vi.un_660 is.eu.rw_543
+ 0x08000b07, 0x551c2108, 0x28003205, 0x553b6404, // es.no.un_420 jw.id.rw_443 bs.sw.un_330 lg.so.rw_332
+ // [2210]
+ 0x060355a9, 0x532925a0, 0x353f080c, 0x3b64130c, // rw.nl.de_544 eu.sl.ht_322 no.af.zu_543 et.lg.so_543
+ 0x0f28090b, 0x17190b05, 0x190b0a04, 0x1100270e, // pl.sw.lv_542 es.gl.sr_333 pt.es.gl_332 gd.ro.un_550
+ 0x081710a0, 0x19170a05, 0x202d35a4, 0x1a006b1a, // be.sr.uk_322 pt.sr.gl_333 zu.sk.sq_433 ceb.tl.un_760
+ 0x2b004a1b, 0x091f12ad, 0x02001904, 0x0a171005, // yo.vi.un_770 hu.cy.pl_643 gl.da.un_320 be.sr.mk_333
+ // [2220]
+ 0x520425a0, 0x6428550d, 0x0452530c, 0x64003b08, // eu.fi.ha_322 rw.sw.lg_554 ht.ha.fi_543 so.lg.un_430
+ 0x531109a4, 0x3f070fa4, 0x13001817, 0x4a002b14, // pl.ro.ht_433 lv.it.af_433 ga.et.un_730 vi.yo.un_660
+ 0x35556ba0, 0x1b312aa4, 0x10020f05, 0x3f001902, // ceb.rw.zu_322 mt.az.tr_433 lv.da.lt_333 gl.af.un_220
+ 0x092855ad, 0x19091fa4, 0x12006b08, 0x0802250c, // rw.sw.pl_643 cy.pl.gl_433 ceb.hu.un_430 eu.da.no_543
+ // [2230]
+ 0x290723a0, 0x03001212, 0x03112705, 0x12000921, // ca.it.sl_322 hu.nl.un_640 gd.ro.nl_333 pl.hu.un_860
+ 0x20116e08, 0x036455a4, 0x1700190e, 0x190b1fa4, // hmn.ro.sq_443 rw.lg.nl_433 gl.sr.un_550 cy.es.gl_433
+ 0x3f00551a, 0x170f3502, 0x1c090d12, 0x1f190b0c, // rw.af.un_760 zu.lv.sr_222 ne.hi.mr_654 es.gl.cy_543
+ 0x282d4aad, 0x056b1104, 0x1b001112, 0x18120805, // yo.sk.sw_643 ro.ceb.fr_332 ro.tr.un_640 no.hu.ga_333
+ // [2240]
+ 0x04080705, 0x033f0aa6, 0x0400531a, 0x1b0804ee, // bg.uk.ru_333 pt.af.nl_521 ht.fi.un_760 fi.no.tr_422
+ 0x10000714, 0x0f5304ee, 0x0500531a, 0x6b1b0805, // bg.be.un_660 fi.ht.lv_422 ht.fr.un_760 no.tr.ceb_333
+ 0x0a0553ad, 0x35001319, 0x52190b02, 0x5300052b, // ht.fr.pt_643 et.zu.un_750 es.gl.ha_222 fr.ht.un_980
+ 0x04060c04, 0x10004a04, 0x011606a0, 0x1b1911a7, // sv.de.fi_332 yo.lt.un_320 de.hr.en_322 ro.gl.tr_532
+ // [2250]
+ 0x25001f0c, 0x0e2a0708, 0x05005322, 0x53000807, // cy.eu.un_530 it.mt.is_443 ht.fr.un_870 no.ht.un_420
+ 0x29002108, 0x17000805, 0x4a006407, 0x090d1c05, // jw.sl.un_430 uk.sr.un_330 lg.yo.un_420 mr.ne.hi_333
+ 0x01072aa0, 0x190b01af, 0x0f00201a, 0x0c000204, // mt.it.en_322 en.es.gl_655 sq.lv.un_760 da.sv.un_320
+ 0x4a000104, 0x0f3217a4, 0x080c2aa4, 0x3f0c08a4, // en.yo.un_320 sr.bs.lv_433 mt.sv.no_433 no.sv.af_433
+ // [2260]
+ 0x020c3fee, 0x131c09ad, 0x0a110708, 0x0d530514, // af.sv.da_422 hi.mr.bh_643 bg.ro.mk_443 fr.ht.cs_666
+ 0x53000523, 0x0c3f0309, 0x25004a07, 0x0508025a, // fr.ht.un_880 nl.af.sv_444 yo.eu.un_420 da.no.fr_553
+ 0x6b553505, 0x2a1b13ee, 0x200e0608, 0x1600060c, // zu.rw.ceb_333 et.tr.mt_422 de.is.sq_443 de.hr.un_530
+ 0x020c0804, 0x08000202, 0x3f006b08, 0x1f033f0d, // no.sv.da_332 da.no.un_220 ceb.af.un_430 af.nl.cy_554
+ // [2270]
+ 0x0e00100c, 0x3b2a6b07, 0x641055a4, 0x2d1f0ba0, // lt.is.un_530 ceb.mt.so_432 rw.lt.lg_433 es.cy.sk_322
+ 0x1a0e2112, 0x1a1031ad, 0x17001111, 0x200e06a7, // jw.is.tl_654 az.lt.tl_643 ro.sr.un_630 de.is.sq_532
+ 0x356b1a12, 0x131c09af, 0x091f0e07, 0x0135640c, // tl.ceb.zu_654 hi.mr.bh_655 is.cy.pl_432 lg.zu.en_543
+ 0x13080ca4, 0x0e000b02, 0x2a32165a, 0x0704050c, // sv.no.et_433 es.is.un_220 hr.bs.mt_553 fr.fi.it_543
+ // [2280]
+ 0x0c080209, 0x1b53520c, 0x21531ca0, 0x3228550c, // da.no.sv_444 ha.ht.tr_543 id.ht.jw_322 rw.sw.bs_543
+ 0x10002d0d, 0x0a1707ee, 0x0c081207, 0x35282a02, // sk.lt.un_540 bg.sr.mk_422 hu.no.sv_432 mt.sw.zu_222
+ 0x286425a0, 0x20070460, 0x13122a04, 0x08645512, // eu.lg.sw_322 fi.it.sq_664 mt.hu.et_332 rw.lg.no_654
+ 0x0c002a07, 0x5235640b, 0x080711a4, 0x020c0812, // mt.sv.un_420 lg.zu.ha_542 ro.bg.uk_433 no.sv.da_654
+ // [2290]
+ 0x553564a4, 0x0f291b0b, 0x1b081307, 0x2a000c0b, // lg.zu.rw_433 tr.sl.lv_542 et.no.tr_432 sv.mt.un_520
+ 0x12004a04, 0x532555a9, 0x04080760, 0x040613a4, // yo.hu.un_320 rw.eu.ht_544 bg.uk.ru_664 et.de.fi_433
+ 0x060c0e12, 0x55282308, 0x3f006e04, 0x6400282a, // is.sv.de_654 ca.sw.rw_443 hmn.af.un_320 sw.lg.un_970
+ 0x081710a4, 0x07190b04, 0x1c13090c, 0x1700112a, // be.sr.uk_433 es.gl.it_332 hi.bh.mr_543 ro.sr.un_970
+ // [22a0]
+ 0x11190a04, 0x0200052b, 0x25080e07, 0x1a5564af, // pt.gl.ro_332 fr.da.un_980 is.no.eu_432 lg.rw.tl_655
+ 0x25280ea0, 0x08060e0d, 0x52555308, 0x070417af, // is.sw.eu_322 is.de.no_554 ht.rw.ha_443 sr.ru.bg_655
+ 0x080c0260, 0x645235ad, 0x535535a0, 0x25001304, // da.sv.no_664 zu.ha.lg_643 zu.rw.ht_322 et.eu.un_320
+ 0x53000708, 0x09050407, 0x130e0c04, 0x18000419, // it.ht.un_430 fi.fr.pl_432 sv.is.et_332 fi.ga.un_750
+ // [22b0]
+ 0x0d001c0b, 0x11004a0c, 0x05000413, 0x04003508, // mr.ne.un_520 yo.ro.un_530 fi.fr.un_650 zu.fi.un_430
+ 0x522a6bad, 0x041305af, 0x273b3fee, 0x1100081a, // ceb.mt.ha_643 fr.et.fi_655 af.so.gd_422 uk.ro.un_760
+ 0x0c083f04, 0x05000412, 0x03080255, 0x6400350e, // af.no.sv_332 fi.fr.un_640 da.no.nl_442 zu.lg.un_550
+ 0x03061307, 0x05003f1a, 0x1153290c, 0x1f000d04, // et.de.nl_432 af.fr.un_760 sl.ht.ro_543 cs.cy.un_320
+ // [22c0]
+ 0x031306a4, 0x08023f0e, 0x040e1307, 0x0a110107, // de.et.nl_433 af.da.no_555 et.is.fi_432 en.ro.pt_432
+ 0x115308a9, 0x07050112, 0x23004a08, 0x050208a4, // no.ht.ro_544 en.fr.it_654 yo.ca.un_430 no.da.fr_433
+ 0x050a3b12, 0x2a0507ec, 0x06000823, 0x041110a4, // so.pt.fr_654 it.fr.mt_644 no.de.un_880 be.ro.ru_433
+ 0x080c0255, 0x04006807, 0x0c010eee, 0x6b0c0812, // da.sv.no_442 ig.fi.un_420 is.en.sv_422 no.sv.ceb_654
+ // [22d0]
+ 0x1708105a, 0x321b0e0b, 0x0410080c, 0x0a080205, // be.uk.sr_553 is.tr.bs_542 uk.be.ru_543 da.no.pt_333
+ 0x3f0308ac, 0x6e00090b, 0x29040da0, 0x041305a4, // no.nl.af_632 pl.hmn.un_520 cs.fi.sl_322 fr.et.fi_433
+ 0x0b2319a0, 0x28130455, 0x21552804, 0x00000937, // gl.ca.es_322 fi.et.sw_442 sw.rw.jw_332 hi.un.un_B00
+ 0x04080205, 0x6e016b04, 0x1f001320, 0x07000822, // da.no.fi_333 ceb.en.hmn_332 et.cy.un_850 uk.bg.un_870
+ // [22e0]
+ 0x07231108, 0x535528ad, 0x3f1f0212, 0x02030e07, // ro.ca.it_443 sw.rw.ht_643 da.cy.af_654 is.nl.da_432
+ 0x53002b0d, 0x17162d05, 0x1b003514, 0x01000f0b, // vi.ht.un_540 sk.hr.sr_333 zu.tr.un_660 lv.en.un_520
+ 0x1e271ca0, 0x21521e04, 0x04170805, 0x18003514, // id.gd.ms_322 ms.ha.jw_332 uk.sr.ru_333 zu.ga.un_660
+ 0x2818270c, 0x04081fee, 0x09291b55, 0x04170aa6, // gd.ga.sw_543 cy.no.fi_422 tr.sl.pl_442 mk.sr.ru_521
+ // [22f0]
+ 0x10001112, 0x016b3507, 0x213f0311, 0x0a000419, // ro.be.un_640 zu.ceb.en_432 nl.af.jw_653 ru.mk.un_750
+ 0x1a0953a0, 0x27005204, 0x0a1011a4, 0x1f292d55, // ht.pl.tl_322 ha.gd.un_320 ro.be.mk_433 sk.sl.cy_442
+ 0x31001b0d, 0x1b6b1a08, 0x080717a4, 0x0d292d07, // tr.az.un_540 tl.ceb.tr_443 sr.bg.uk_433 sk.sl.cs_432
+ 0x1c005502, 0x041708a9, 0x1b532108, 0x3110110c, // rw.id.un_220 uk.sr.ru_544 jw.ht.tr_443 ro.lt.az_543
+ // [2300]
+ 0x040717a9, 0x3f1c03a4, 0x01000f0c, 0x1c130909, // sr.bg.ru_544 nl.id.af_433 lv.en.un_530 hi.bh.mr_444
+ 0x290d2dec, 0x27001808, 0x04000813, 0x28001808, // sk.cs.sl_644 ga.gd.un_430 uk.ru.un_650 ga.sw.un_430
+ 0x1e2d1c0c, 0x120a2304, 0x2d0d17a0, 0x180e1fa7, // id.sk.ms_543 ca.pt.hu_332 sr.cs.sk_322 cy.is.ga_532
+ 0x1c181ea4, 0x09553ba9, 0x0a001721, 0x0d0913a7, // ms.ga.id_433 so.rw.pl_544 sr.mk.un_860 bh.hi.ne_532
+ // [2310]
+ 0x0e000c05, 0x1c00090b, 0x29120312, 0x2d1129a7, // sv.is.un_330 hi.mr.un_520 nl.hu.sl_654 sl.ro.sk_532
+ 0x530a1108, 0x1b002d04, 0x20002a13, 0x0e120ca9, // ro.pt.ht_443 sk.tr.un_320 mt.sq.un_650 sv.hu.is_544
+ 0x04110708, 0x01001f05, 0x1704070b, 0x09001607, // bg.ro.ru_443 cy.en.un_330 bg.ru.sr_542 hr.pl.un_420
+ 0x051a3555, 0x03013f02, 0x23000a23, 0x09011f04, // zu.tl.fr_442 af.en.nl_222 pt.ca.un_880 cy.en.pl_332
+ // [2320]
+ 0x00000e06, 0x23050b07, 0x2b001e08, 0x211c1eee, // is.un.un_400 es.fr.ca_432 ms.vi.un_430 ms.id.jw_422
+ 0x190a6b02, 0x041008ec, 0x1f1118af, 0x180c06ad, // ceb.pt.gl_222 uk.be.ru_644 ga.ro.cy_655 de.sv.ga_643
+ 0x11000414, 0x2d0d1602, 0x0e000612, 0x4a000512, // ru.ro.un_660 hr.cs.sk_222 de.is.un_640 fr.yo.un_640
+ 0x130306ad, 0x3f000a08, 0x033f0613, 0x0c000822, // de.nl.et_643 pt.af.un_430 de.af.nl_665 no.sv.un_870
+ // [2330]
+ 0x3100530e, 0x0100060b, 0x6b3f0212, 0x29000313, // ht.az.un_550 de.en.un_520 da.af.ceb_654 nl.sl.un_650
+ 0x1b002a0c, 0x0c0217a4, 0x190b0aec, 0x10000913, // mt.tr.un_530 sr.da.sv_433 pt.es.gl_644 pl.lt.un_650
+ 0x10000f23, 0x13643b07, 0x210b075a, 0x21000105, // lv.lt.un_880 so.lg.et_432 it.es.jw_553 en.jw.un_330
+ 0x53000713, 0x20002b14, 0x0a2319ee, 0x04071111, // it.ht.un_650 vi.sq.un_660 gl.ca.pt_422 ro.bg.ru_653
+ // [2340]
+ 0x0b112da4, 0x040a08a9, 0x0d001312, 0x25521f13, // sk.ro.es_433 uk.mk.ru_544 bh.ne.un_640 cy.ha.eu_665
+ 0x08041709, 0x0a133f04, 0x1e1c1902, 0x01080205, // sr.ru.uk_444 af.et.pt_332 gl.id.ms_222 da.no.en_333
+ 0x0a110f0c, 0x0a000719, 0x07001904, 0x06021ea9, // lv.ro.pt_543 bg.mk.un_750 gl.it.un_320 ms.da.de_544
+ 0x08030555, 0x21001a08, 0x4a1f5307, 0x091253ee, // fr.nl.no_442 tl.jw.un_430 ht.cy.yo_432 ht.hu.pl_422
+ // [2350]
+ 0x080212af, 0x0f0410af, 0x00001b2d, 0x071704ec, // hu.da.no_655 lt.fi.lv_655 tr.un.un_A00 ru.sr.bg_644
+ 0x17002919, 0x10001707, 0x53190a13, 0x08041055, // sl.sr.un_750 sr.lt.un_420 pt.gl.ht_665 be.ru.uk_442
+ 0x09002a08, 0x19000a0b, 0x07002a1a, 0x201953a9, // mt.pl.un_430 pt.gl.un_520 mt.it.un_760 ht.gl.sq_544
+ 0x01001f19, 0x32001712, 0x2d001907, 0x0f001119, // cy.en.un_750 sr.bs.un_640 gl.sk.un_420 ro.lv.un_750
+ // [2360]
+ 0x0a0125a0, 0x1c1e1b0d, 0x1e1c2d0c, 0x0a001904, // eu.en.pt_322 tr.ms.id_554 sk.id.ms_543 gl.pt.un_320
+ 0x64000a04, 0x0a553509, 0x0e00350d, 0x1c000711, // pt.lg.un_320 zu.rw.pt_444 zu.is.un_540 it.id.un_630
+ 0x29321608, 0x10352d0c, 0x1c006804, 0x0f1004a7, // hr.bs.sl_443 sk.zu.lt_543 ig.id.un_320 fi.lt.lv_532
+ 0x0e0608ec, 0x3f0e1c04, 0x111b1211, 0x20002307, // no.de.is_644 id.is.af_332 hu.tr.ro_653 ca.sq.un_420
+ // [2370]
+ 0x13003513, 0x1b644a13, 0x203f0107, 0x101304a6, // zu.et.un_650 yo.lg.tr_665 en.af.sq_432 fi.et.lt_521
+ 0x1a3f03ee, 0x1b053b05, 0x2d132905, 0x210a06ee, // nl.af.tl_422 so.fr.tr_333 sl.et.sk_333 de.pt.jw_422
+ 0x03173f02, 0x06003f21, 0x0100110c, 0x10211aa9, // af.sr.nl_222 af.de.un_860 ro.en.un_530 tl.jw.lt_544
+ 0x0d002d0b, 0x0d003519, 0x08001119, 0x2d0a0da4, // sk.cs.un_520 zu.cs.un_750 ro.uk.un_750 cs.pt.sk_433
+ // [2380]
+ 0x12002312, 0x0a2d190c, 0x06020811, 0x1c020f55, // ca.hu.un_640 gl.sk.pt_543 no.da.de_653 lv.da.id_442
+ 0x21001811, 0x2100120c, 0x1708040c, 0x071b010c, // ar.fa.un_630 ur.fa.un_530 ru.uk.sr_543 en.tr.it_543
+ 0x35001e08, 0x68553513, 0x25190ba7, 0x130411ec, // ms.zu.un_430 zu.rw.ig_665 es.gl.eu_532 ro.fi.et_644
+ 0x0d001c1a, 0x1b13040c, 0x01210cee, 0x2a000a07, // mr.ne.un_760 fi.et.tr_543 sv.jw.en_422 pt.mt.un_420
+ // [2390]
+ 0x4a251b07, 0x06020c05, 0x06000d13, 0x643523ec, // tr.eu.yo_432 sv.da.de_333 cs.de.un_650 ca.zu.lg_644
+ 0x1220130c, 0x08000e0e, 0x68006422, 0x20002d04, // et.sq.hu_543 is.no.un_550 lg.ig.un_870 sk.sq.un_320
+ 0x130b0c05, 0x2b553504, 0x080c0313, 0x1a3564a9, // sv.es.et_333 zu.rw.vi_332 nl.sv.no_665 lg.zu.tl_544
+ 0x0f132907, 0x072d0d55, 0x03083f08, 0x29002d0d, // sl.et.lv_432 cs.sk.it_442 af.no.nl_443 sk.sl.un_540
+ // [23a0]
+ 0x68003514, 0x64001608, 0x0a0c1e04, 0x010802a4, // zu.ig.un_660 hr.lg.un_430 ms.sv.pt_332 da.no.en_433
+ 0x130928a0, 0x0a0908a0, 0x02000e13, 0x13000d0c, // sw.pl.et_322 no.pl.pt_322 is.da.un_650 ne.bh.un_530
+ 0x55642855, 0x6b251b11, 0x0e0827ad, 0x21645504, // sw.lg.rw_442 tr.eu.ceb_653 gd.no.is_643 rw.lg.jw_332
+ 0x20005205, 0x16000a05, 0x1a2d0a07, 0x2513640c, // ha.sq.un_330 pt.hr.un_330 pt.sk.tl_432 lg.et.eu_543
+ // [23b0]
+ 0x1a28355a, 0x08022a02, 0x64552107, 0x323f030d, // zu.sw.tl_553 mt.da.no_222 jw.rw.lg_432 nl.af.bs_554
+ 0x03003204, 0x190b07a0, 0x3f0413a4, 0x321613a4, // bs.nl.un_320 it.es.gl_322 et.fi.af_433 et.hr.bs_433
+ 0x04163f04, 0x2a00070e, 0x08006412, 0x05524aee, // af.hr.fi_332 it.mt.un_550 lg.no.un_640 yo.ha.fr_422
+ 0x0704100d, 0x1c000d0d, 0x04001c04, 0x180827ee, // be.ru.bg_554 ne.mr.un_540 id.fi.un_320 gd.no.ga_422
+ // [23c0]
+ 0x17005202, 0x31001a02, 0x0725130c, 0x25006807, // ha.sr.un_220 tl.az.un_220 et.eu.it_543 ig.eu.un_420
+ 0x041f01a4, 0x526b1a04, 0x0e130408, 0x3f001318, // en.cy.fi_433 tl.ceb.ha_332 fi.et.is_443 et.af.un_740
+ 0x21001807, 0x28001e1a, 0x0e001802, 0x12091602, // ar.fa.un_420 ms.sw.un_760 ga.is.un_220 hr.pl.hu_222
+ 0x21641c0c, 0x0a1710ec, 0x2b3f21a0, 0x3500061a, // id.lg.jw_543 be.sr.mk_644 jw.af.vi_322 de.zu.un_760
+ // [23d0]
+ 0x0c0f13a4, 0x074a10a7, 0x645568ac, 0x0b190a55, // et.lv.sv_433 lt.yo.it_532 ig.rw.lg_632 pt.gl.es_442
+ 0x19002113, 0x641e1ca9, 0x032552a0, 0x08040705, // jw.gl.un_650 id.ms.lg_544 ha.eu.nl_322 bg.ru.uk_333
+ 0x060e0c60, 0x1a1c6ba9, 0x1a116b07, 0x23251902, // sv.is.de_664 ceb.id.tl_544 ceb.ro.tl_432 gl.eu.ca_222
+ 0x521e1c07, 0x2752280c, 0x64171aa4, 0x033f13a0, // id.ms.ha_432 sw.ha.gd_543 tl.sr.lg_433 et.af.nl_322
+ // [23e0]
+ 0x17041108, 0x030701a4, 0x52250607, 0x17321613, // ro.ru.sr_443 en.it.nl_433 de.eu.ha_432 hr.bs.sr_665
+ 0x0d000412, 0x11001034, 0x1c000d09, 0x0400070d, // fi.cs.un_640 be.ro.un_A80 ne.mr.un_440 bg.ru.un_540
+ 0x08070a02, 0x25072a14, 0x08020ea4, 0x04282aee, // mk.bg.uk_222 mt.it.eu_666 is.da.no_433 mt.sw.fi_422
+ 0x036b21ee, 0x06001108, 0x13001107, 0x1a101c07, // jw.ceb.nl_422 ro.de.un_430 ro.et.un_420 id.lt.tl_432
+ // [23f0]
+ 0x18006e07, 0x060c0708, 0x1c210f0c, 0x21121aa9, // hmn.ga.un_420 it.sv.de_443 lv.jw.id_543 tl.hu.jw_544
+ 0x6b1f07a4, 0x17130ea7, 0x31001319, 0x12002118, // it.cy.ceb_433 is.et.sr_532 et.az.un_750 fa.ur.un_740
+ 0x0c002504, 0x2d0d29a0, 0x0c000220, 0x1f0c64a4, // eu.sv.un_320 sl.cs.sk_322 da.sv.un_850 lg.sv.cy_433
+ 0x040a0809, 0x1c311ea9, 0x0e000c1a, 0x07006412, // uk.mk.ru_444 ms.az.id_544 sv.is.un_760 lg.it.un_640
+
+ // [2400]
+ 0x1c13010c, 0x040a11ac, 0x11070a14, 0x12001818, // en.et.id_543 ro.mk.ru_632 mk.bg.ro_666 ar.ur.un_740
+ 0x27001c04, 0x291325a4, 0x08000e13, 0x2a011fec, // id.gd.un_320 eu.et.sl_433 is.no.un_650 cy.en.mt_644
+ 0x032923ad, 0x133f0312, 0x2a050707, 0x0a111705, // ca.sl.nl_643 nl.af.et_654 it.fr.mt_432 sr.ro.mk_333
+ 0x062a0307, 0x03003f23, 0x01521eee, 0x0000082d, // nl.mt.de_432 af.nl.un_880 ms.ha.en_422 uk.un.un_A00
+ // [2410]
+ 0x0a041709, 0x06001e1a, 0x06006421, 0x553f35a7, // sr.ru.mk_444 ms.de.un_760 lg.de.un_860 zu.af.rw_532
+ 0x12001a02, 0x09003b19, 0x13000a07, 0x08020cee, // tl.hu.un_220 so.pl.un_750 pt.et.un_420 sv.da.no_422
+ 0x01093fee, 0x08000208, 0x0c070107, 0x2a3f35ee, // af.pl.en_422 da.no.un_430 en.it.sv_432 zu.af.mt_422
+ 0x253b050c, 0x130a08a0, 0x033f08ad, 0x19000a0c, // fr.so.eu_543 no.pt.et_322 no.af.nl_643 pt.gl.un_530
+ // [2420]
+ 0x0a002807, 0x6b2928a9, 0x0900121a, 0x19000b11, // sw.pt.un_420 sw.sl.ceb_544 hu.pl.un_760 es.gl.un_630
+ 0x13002b11, 0x5200350d, 0x55180508, 0x133b0112, // vi.et.un_630 zu.ha.un_540 fr.ga.rw_443 en.so.et_654
+ 0x0b001107, 0x1f001c0d, 0x06093512, 0x6800641a, // ro.es.un_420 id.cy.un_540 zu.pl.de_654 lg.ig.un_760
+ 0x03283fa7, 0x556b1a12, 0x0a07170e, 0x03002a04, // af.sw.nl_532 tl.ceb.rw_654 sr.bg.mk_555 mt.nl.un_320
+ // [2430]
+ 0x6b5520a4, 0x27283207, 0x033f1308, 0x11001604, // sq.rw.ceb_433 bs.sw.gd_432 et.af.nl_443 hr.ro.un_320
+ 0x10041704, 0x1c0d1314, 0x174a1ca0, 0x061132a4, // sr.ru.be_332 bh.ne.mr_666 id.yo.sr_322 bs.ro.de_433
+ 0x06122514, 0x21282da7, 0x6b000c04, 0x55351c02, // eu.hu.de_666 sk.sw.jw_532 sv.ceb.un_320 id.zu.rw_222
+ 0x13006404, 0x352852a4, 0x04281807, 0x1c00130d, // lg.et.un_320 ha.sw.zu_433 ga.sw.fi_432 bh.mr.un_540
+ // [2440]
+ 0x06000f19, 0x20642807, 0x09002a19, 0x311255ec, // lv.de.un_750 sw.lg.sq_432 mt.pl.un_750 rw.hu.az_644
+ 0x64001c07, 0x3f000e07, 0x0400130c, 0x130409a4, // id.lg.un_420 is.af.un_420 et.fi.un_530 pl.fi.et_433
+ 0x12000919, 0x06040112, 0x2a045208, 0x08110708, // pl.hu.un_750 en.fi.de_654 ha.fi.mt_443 bg.ro.uk_443
+ 0x2b001704, 0x19271811, 0x190a18ec, 0x2a0604a4, // sr.vi.un_320 ga.gd.gl_653 ga.pt.gl_644 fi.de.mt_433
+ // [2450]
+ 0x03000419, 0x0807100d, 0x042a0808, 0x17000a20, // fi.nl.un_750 be.bg.uk_554 no.mt.fi_443 mk.sr.un_850
+ 0x253f0304, 0x1c2025a7, 0x05060707, 0x040717a4, // nl.af.eu_332 eu.sq.id_532 it.de.fr_432 sr.bg.ru_433
+ 0x043b0e07, 0x10552104, 0x0d172d07, 0x04000814, // is.so.fi_432 jw.rw.lt_332 sk.sr.cs_432 uk.ru.un_660
+ 0x090c0da7, 0x0f090613, 0x64003522, 0x1f080204, // cs.sv.pl_532 de.pl.lv_665 zu.lg.un_870 da.no.cy_332
+ // [2460]
+ 0x04006405, 0x2d0d2909, 0x0410110c, 0x2a00030c, // lg.fi.un_330 sl.cs.sk_444 ro.be.ru_543 nl.mt.un_530
+ 0x2b001214, 0x040102a0, 0x1700081b, 0x17001905, // hu.vi.un_660 da.en.fi_322 uk.sr.un_770 gl.sr.un_330
+ 0x091c130b, 0x040a11ee, 0x08042a12, 0x040910a4, // bh.mr.hi_542 ro.mk.ru_422 mt.fi.no_654 lt.pl.fi_433
+ 0x0400070c, 0x162d0d04, 0x6408040c, 0x2d001705, // it.fi.un_530 cs.sk.hr_332 fi.no.lg_543 sr.sk.un_330
+ // [2470]
+ 0x2855520c, 0x1c3b1e0c, 0x20122a0c, 0x2312050c, // ha.rw.sw_543 ms.so.id_543 mt.hu.sq_543 fr.hu.ca_543
+ 0x23072504, 0x0e006b04, 0x0e006b07, 0x11052307, // eu.it.ca_332 ceb.is.un_320 ceb.is.un_420 ca.fr.ro_432
+ 0x1e121c0c, 0x6455280c, 0x322752ee, 0x0b251f0c, // id.hu.ms_543 sw.rw.lg_543 ha.gd.bs_422 cy.eu.es_543
+ 0x0f101ea4, 0x23002709, 0x0813020c, 0x0400132b, // ms.lt.lv_433 gd.ca.un_440 da.et.no_543 et.fi.un_980
+ // [2480]
+ 0x3b005223, 0x04071705, 0x170a0707, 0x27001904, // ha.so.un_880 sr.bg.ru_333 bg.mk.sr_432 gl.gd.un_320
+ 0x23190a04, 0x07100813, 0x3500281b, 0x27001a08, // pt.gl.ca_332 uk.be.bg_665 sw.zu.un_770 tl.gd.un_430
+ 0x182701a7, 0x19120baf, 0x6b3f0307, 0x1100132a, // en.gd.ga_532 es.hu.gl_655 nl.af.ceb_432 et.ro.un_970
+ 0x091f350c, 0x0600350e, 0x2a2835ad, 0x55182708, // zu.cy.pl_543 zu.de.un_550 zu.sw.mt_643 gd.ga.rw_443
+ // [2490]
+ 0x521e1cad, 0x4a001219, 0x0b0107a0, 0x1b1a640c, // id.ms.ha_643 hu.yo.un_750 it.en.es_322 lg.tl.tr_543
+ 0x29162da0, 0x55121f12, 0x08000e1a, 0x6e321604, // sk.hr.sl_322 cy.hu.rw_654 is.no.un_760 hr.bs.hmn_332
+ 0x2d0d2902, 0x1200350d, 0x0700110d, 0x64041305, // sl.cs.sk_222 zu.hu.un_540 ro.bg.un_540 et.fi.lg_333
+ 0x0b0a23a4, 0x091c1e11, 0x1e1f0fee, 0x09110a0c, // ca.pt.es_433 ms.id.pl_653 lv.cy.ms_422 pt.ro.pl_543
+ // [24a0]
+ 0x3200161a, 0x0c3f02a0, 0x320625a7, 0x23000b02, // hr.bs.un_760 da.af.sv_322 eu.de.bs_532 es.ca.un_220
+ 0x070a04a7, 0x01251fee, 0x20001113, 0x120c1ea4, // ru.mk.bg_532 cy.eu.en_422 ro.sq.un_650 ms.sv.hu_433
+ 0x1b006804, 0x190b12ec, 0x20030807, 0x2111070c, // ig.tr.un_320 hu.es.gl_644 no.nl.sq_432 it.ro.jw_543
+ 0x051201a4, 0x0f321702, 0x06080f04, 0x10120911, // en.hu.fr_433 sr.bs.lv_222 lv.no.de_332 pl.hu.lt_653
+ // [24b0]
+ 0x050401a4, 0x07552807, 0x17003207, 0x0e00050e, // en.fi.fr_433 sw.rw.it_432 bs.sr.un_420 fr.is.un_550
+ 0x1b12030c, 0x25003f04, 0x130c2aa7, 0x172a0808, // nl.hu.tr_543 af.eu.un_320 mt.sv.et_532 no.mt.sr_443
+ 0x3101050c, 0x21171604, 0x1300210d, 0x08120208, // fr.en.az_543 hr.sr.jw_332 jw.et.un_540 da.hu.no_443
+ 0x354a6805, 0x1c000d07, 0x53001907, 0x0c231004, // ig.yo.zu_333 ne.mr.un_420 gl.ht.un_420 lt.ca.sv_332
+ // [24c0]
+ 0x0c000108, 0x0700051b, 0x03003f18, 0x05193f08, // en.sv.un_430 fr.it.un_770 af.nl.un_740 af.gl.fr_443
+ 0x0105190c, 0x12002a0d, 0x052301ee, 0x09110613, // gl.fr.en_543 mt.hu.un_540 en.ca.fr_422 de.ro.pl_665
+ 0x17080aa4, 0x12053107, 0x09200f08, 0x02093202, // mk.uk.sr_433 az.fr.hu_432 lv.sq.pl_443 bs.pl.da_222
+ 0x05000113, 0x101b28a0, 0x091f68a7, 0x2a000108, // en.fr.un_650 sw.tr.lt_322 ig.cy.pl_532 en.mt.un_430
+ // [24d0]
+ 0x20132107, 0x27351aa0, 0x045319ee, 0x64003523, // jw.et.sq_432 tl.zu.gd_322 gl.ht.fi_422 zu.lg.un_880
+ 0x284a0a12, 0x050801a4, 0x311b5511, 0x0c1827a7, // pt.yo.sw_654 en.no.fr_433 rw.tr.az_653 gd.ga.sv_532
+ 0x13212512, 0x010d1904, 0x09005214, 0x011827a4, // eu.jw.et_654 gl.cs.en_332 ha.pl.un_660 gd.ga.en_433
+ 0x08001f08, 0x21061fad, 0x3f000323, 0x05005305, // cy.no.un_430 cy.de.jw_643 nl.af.un_880 ht.fr.un_330
+ // [24e0]
+ 0x531e1704, 0x683564a9, 0x28036402, 0x030b3f5a, // sr.ms.ht_332 lg.zu.ig_544 lg.nl.sw_222 af.es.nl_553
+ 0x3b5335ee, 0x6452090c, 0x52000918, 0x55006421, // zu.ht.so_422 pl.ha.lg_543 pl.ha.un_740 lg.rw.un_860
+ 0x29002004, 0x282a21ec, 0x285564ac, 0x230507a4, // sq.sl.un_320 jw.mt.sw_644 lg.rw.sw_632 it.fr.ca_433
+ 0x281a6b04, 0x6b1a1b0c, 0x3b00270e, 0x1b5228af, // ceb.tl.sw_332 tr.tl.ceb_543 gd.so.un_550 sw.ha.tr_655
+ // [24f0]
+ 0x055301a4, 0x645528a4, 0x0900640e, 0x2a281ea0, // en.ht.fr_433 sw.rw.lg_433 lg.pl.un_550 ms.sw.mt_322
+ 0x310c28a4, 0x02002714, 0x1a52350c, 0x080c1f0c, // sw.sv.az_433 gd.da.un_660 zu.ha.tl_543 cy.sv.no_543
+ 0x28352a13, 0x09003514, 0x1a553b11, 0x04005223, // mt.zu.sw_665 zu.pl.un_660 so.rw.tl_653 ha.fi.un_880
+ 0x2500550d, 0x1b1c52ad, 0x09005213, 0x1c2135a7, // rw.eu.un_540 ha.id.tr_643 ha.pl.un_650 zu.jw.id_532
+ // [2500]
+ 0x1e3b0ca0, 0x2852090b, 0x6e641a12, 0x17001108, // sv.so.ms_322 pl.ha.sw_542 tl.lg.hmn_654 ro.sr.un_430
+ 0x09521ca4, 0x216b550c, 0x130408a0, 0x2d0d25a0, // id.ha.pl_433 rw.ceb.jw_543 no.fi.et_322 eu.cs.sk_322
+ 0x110a1313, 0x0b001907, 0x13174aa4, 0x0c00021b, // et.pt.ro_665 gl.es.un_420 yo.sr.et_433 da.sv.un_770
+ 0x28001e22, 0x1f112713, 0x1a001f07, 0x55281ca0, // ms.sw.un_870 gd.ro.cy_665 cy.tl.un_420 id.sw.rw_322
+ // [2510]
+ 0x13552108, 0x0b00190d, 0x0c000523, 0x13001221, // jw.rw.et_443 gl.es.un_540 fr.sv.un_880 hu.et.un_860
+ 0x25281ca0, 0x200b07a4, 0x04000a09, 0x2a281e07, // id.sw.eu_322 it.es.sq_433 mk.ru.un_440 ms.sw.mt_432
+ 0x1f055305, 0x1b0553a7, 0x07250aad, 0x040a11a6, // ht.fr.cy_333 ht.fr.tr_532 pt.eu.it_643 ro.mk.ru_521
+ 0x29002d14, 0x0c000e13, 0x32110da0, 0x100721a0, // sk.sl.un_660 is.sv.un_650 cs.ro.bs_322 jw.it.lt_322
+ // [2520]
+ 0x520507a7, 0x3b640413, 0x1b000c12, 0x081711a0, // it.fr.ha_532 fi.lg.so_665 sv.tr.un_640 ro.sr.uk_322
+ 0x0f201155, 0x3f000302, 0x0d171ead, 0x4a2d0d60, // ro.sq.lv_442 nl.af.un_220 ms.sr.cs_643 cs.sk.yo_664
+ 0x04643ba7, 0x21641a02, 0x10000f19, 0x09181fad, // so.lg.fi_532 tl.lg.jw_222 lv.lt.un_750 cy.ga.pl_643
+ 0x0413255a, 0x00002924, 0x08130eee, 0x05002321, // eu.et.fi_553 sl.un.un_900 is.et.no_422 ca.fr.un_860
+ // [2530]
+ 0x1b0d350c, 0x1a003b19, 0x05001222, 0x533105a9, // zu.cs.tr_543 so.tl.un_750 hu.fr.un_870 fr.az.ht_544
+ 0x1b096ea0, 0x08100aa9, 0x190b0709, 0x043b680b, // hmn.pl.tr_322 mk.be.uk_544 it.es.gl_444 ig.so.fi_542
+ 0x12000508, 0x1c000908, 0x0b006e04, 0x121b310c, // fr.hu.un_430 hi.mr.un_430 hmn.es.un_320 az.tr.hu_543
+ 0x19000119, 0x52005308, 0x100708a4, 0x11040808, // en.gl.un_750 ht.ha.un_430 uk.bg.be_433 uk.ru.ro_443
+ // [2540]
+ 0x01003513, 0x0b0512ad, 0x013f0504, 0x01001c08, // zu.en.un_650 hu.fr.es_643 fr.af.en_332 id.en.un_430
+ 0x23120511, 0x0a006e0b, 0x0b000a21, 0x5305010c, // fr.hu.ca_653 hmn.pt.un_520 pt.es.un_860 en.fr.ht_543
+ 0x230b0504, 0x1707100c, 0x043b640c, 0x283519ee, // fr.es.ca_332 be.bg.sr_543 lg.so.fi_543 gl.zu.sw_422
+ 0x64133b0c, 0x0b1219ee, 0x0e686b12, 0x524a3bee, // so.et.lg_543 gl.hu.es_422 ceb.ig.is_654 so.yo.ha_422
+ // [2550]
+ 0x110a1712, 0x1300040d, 0x190a3f05, 0x092d02ee, // sr.mk.ro_654 fi.et.un_540 af.pt.gl_333 da.sk.pl_422
+ 0x642055a0, 0x116418ad, 0x13013ba0, 0x192d1ca0, // rw.sq.lg_322 ga.lg.ro_643 so.en.et_322 id.sk.gl_322
+ 0x320829a0, 0x0500122c, 0x13005513, 0x0a006e0c, // sl.no.bs_322 hu.fr.un_990 rw.et.un_650 hmn.pt.un_530
+ 0x03050111, 0x0a006e08, 0x05001219, 0x1a1e1c04, // en.fr.nl_653 hmn.pt.un_430 hu.fr.un_750 id.ms.tl_332
+ // [2560]
+ 0x202925a7, 0x070a2513, 0x4a00251a, 0x0b2107ee, // eu.sl.sq_532 eu.pt.it_665 eu.yo.un_760 it.jw.es_422
+ 0x2700250e, 0x532a0704, 0x6b1a2508, 0x640a19a4, // eu.gd.un_550 it.mt.ht_332 eu.tl.ceb_443 gl.pt.lg_433
+ 0x072a20a4, 0x0717100e, 0x07000e13, 0x11050ea4, // sq.mt.it_433 be.sr.bg_555 is.it.un_650 is.fr.ro_433
+ 0x013b3f08, 0x060c045a, 0x2a190a08, 0x356828a4, // af.so.en_443 fi.sv.de_553 pt.gl.mt_443 sw.ig.zu_433
+ // [2570]
+ 0x28046e07, 0x32001308, 0x0e130c04, 0x32172aa4, // hmn.fi.sw_432 et.bs.un_430 sv.et.is_332 mt.sr.bs_433
+ 0x53000107, 0x1a006b20, 0x0b35130c, 0x1e1c20af, // en.ht.un_420 ceb.tl.un_850 et.zu.es_543 sq.id.ms_655
+ 0x28645512, 0x25072a08, 0x3b006414, 0x10000f0e, // rw.lg.sw_654 mt.it.eu_443 lg.so.un_660 lv.lt.un_550
+ 0x64551ca0, 0x3f020ca9, 0x0a17080b, 0x19000804, // id.rw.lg_322 sv.da.af_544 uk.sr.mk_542 no.gl.un_320
+ // [2580]
+ 0x311b2507, 0x10000b05, 0x17002a04, 0x160103ee, // eu.tr.az_432 es.lt.un_330 mt.sr.un_320 nl.en.hr_422
+ 0x190b21ee, 0x1c215307, 0x25003204, 0x6432280c, // jw.es.gl_422 ht.jw.id_432 bs.eu.un_320 sw.bs.lg_543
+ 0x6b1a5312, 0x6425550d, 0x0a0408a0, 0x1f001b0c, // ht.tl.ceb_654 rw.eu.lg_554 uk.ru.mk_322 tr.cy.un_530
+ 0x3f0a06a0, 0x19010e0c, 0x29002705, 0x322d0d0d, // de.pt.af_322 is.en.gl_543 gd.sl.un_330 cs.sk.bs_554
+ // [2590]
+ 0x23001908, 0x182311ee, 0x350e2a0d, 0x13010ead, // gl.ca.un_430 ro.ca.ga_422 mt.is.zu_554 is.en.et_643
+ 0x1c090d05, 0x06685555, 0x04001105, 0x17001005, // ne.hi.mr_333 rw.ig.de_442 ro.ru.un_330 be.sr.un_330
+ 0x0409250d, 0x2a001c04, 0x04001002, 0x25352855, // eu.pl.fi_554 id.mt.un_320 be.ru.un_220 sw.zu.eu_442
+ 0x23180eaf, 0x0c1102a0, 0x3100522b, 0x280652a4, // is.ga.ca_655 da.ro.sv_322 ha.az.un_980 ha.de.sw_433
+ // [25a0]
+ 0x05004a1a, 0x55002521, 0x1107520c, 0x552a5213, // yo.fr.un_760 eu.rw.un_860 ha.it.ro_543 ha.mt.rw_665
+ 0x080a1f04, 0x0b182308, 0x531028a0, 0x190b0a09, // cy.pt.no_332 ca.ga.es_443 sw.lt.ht_322 pt.es.gl_444
+ 0x23121307, 0x0b0a20a4, 0x250a23af, 0x0b0a11ee, // et.hu.ca_432 sq.pt.es_433 ca.pt.eu_655 ro.pt.es_422
+ 0x1a211108, 0x03013fa0, 0x3f092da4, 0x080710a4, // ro.jw.tl_443 af.en.nl_322 sk.pl.af_433 be.bg.uk_433
+ // [25b0]
+ 0x32002a05, 0x2d3f09a9, 0x230e1907, 0x12004a0c, // mt.bs.un_330 pl.af.sk_544 gl.is.ca_432 yo.hu.un_530
+ 0x11200c11, 0x06002322, 0x041108af, 0x68083fa4, // sv.sq.ro_653 ca.de.un_870 uk.ro.ru_655 af.no.ig_433
+ 0x190b3f12, 0x52002823, 0x06003f18, 0x0b093f08, // af.es.gl_654 sw.ha.un_880 af.de.un_740 af.pl.es_443
+ 0x11100812, 0x0b090f04, 0x12080caf, 0x2d0d05ec, // uk.be.ro_654 lv.pl.es_332 sv.no.hu_655 fr.cs.sk_644
+ // [25c0]
+ 0x182d0d1a, 0x030f0408, 0x03090613, 0x3f000f09, // cs.sk.ga_776 fi.lv.nl_443 de.pl.nl_665 lv.af.un_440
+ 0x010c3fa7, 0x0c1101a0, 0x1100101a, 0x190b060c, // af.sv.en_532 en.ro.sv_322 be.ro.un_760 de.es.gl_543
+ 0x1e1c0a09, 0x0407530c, 0x25190b04, 0x35211c55, // pt.id.ms_444 ht.it.fi_543 es.gl.eu_332 id.jw.zu_442
+ 0x10006b18, 0x17080908, 0x120d2da9, 0x3b0620ad, // ceb.lt.un_740 pl.no.sr_443 sk.cs.hu_544 sq.de.so_643
+ // [25d0]
+ 0x2a033f08, 0x1b0b3507, 0x03000513, 0x12001319, // af.nl.mt_443 zu.es.tr_432 fr.nl.un_650 et.hu.un_750
+ 0x2a0e64ad, 0x25002309, 0x1c5521ad, 0x11002321, // lg.is.mt_643 ca.eu.un_440 jw.rw.id_643 ca.ro.un_860
+ 0x0f311b5a, 0x2700230d, 0x3b3f07a4, 0x29230509, // tr.az.lv_553 ca.gd.un_540 it.af.so_433 fr.ca.sl_444
+ 0x27002302, 0x3f00020c, 0x1c12210d, 0x12190a09, // ca.gd.un_220 da.af.un_530 jw.hu.id_554 pt.gl.hu_444
+ // [25e0]
+ 0x041e1c55, 0x0500191a, 0x12190a13, 0x64005220, // id.ms.fi_442 gl.fr.un_760 pt.gl.hu_665 ha.lg.un_850
+ 0x0e0f1055, 0x06000319, 0x2d0d0fee, 0x031205a0, // lt.lv.is_442 nl.de.un_750 lv.cs.sk_422 fr.hu.nl_322
+ 0x090d13ee, 0x07101faf, 0x102d1ea0, 0x1e1c28af, // bh.ne.hi_422 cy.lt.it_655 ms.sk.lt_322 sw.id.ms_655
+ 0x320e2d08, 0x0e001007, 0x01002005, 0x10321605, // sk.is.bs_443 lt.is.un_420 sq.en.un_330 hr.bs.lt_333
+ // [25f0]
+ 0x17041355, 0x190a1305, 0x0f08100c, 0x281e1c08, // et.fi.sr_442 et.pt.gl_333 lt.no.lv_543 id.ms.sw_443
+ 0x5500280c, 0x556452ec, 0x1a6b3f11, 0x0d006e07, // sw.rw.un_530 ha.lg.rw_644 af.ceb.tl_653 hmn.cs.un_420
+ 0x1a04520c, 0x53016ba9, 0x1c04280c, 0x132a1055, // ha.fi.tl_543 ceb.en.ht_544 sw.fi.id_543 lt.mt.et_442
+ 0x11080aa4, 0x3f2d1f12, 0x1304050c, 0x2b050107, // mk.uk.ro_433 cy.sk.af_654 fr.fi.et_543 en.fr.vi_432
+ // [2600]
+ 0x64556ba9, 0x1f002d0e, 0x030908ec, 0x2d00061b, // ceb.rw.lg_544 sk.cy.un_550 no.pl.nl_644 de.sk.un_770
+ 0x1a133bad, 0x16006805, 0x0a07040d, 0x08005521, // so.et.tl_643 ig.hr.un_330 ru.bg.mk_554 rw.no.un_860
+ 0x1b310e0c, 0x55133b5a, 0x323f2da0, 0x1a553b08, // is.az.tr_543 so.et.rw_553 sk.af.bs_322 so.rw.tl_443
+ 0x041319a4, 0x28135505, 0x120e010c, 0x1f002d0c, // gl.et.fi_433 rw.et.sw_333 en.is.hu_543 sk.cy.un_530
+ // [2610]
+ 0x0c643b09, 0x01040e04, 0x1000010d, 0x202d0d60, // so.lg.sv_444 is.fi.en_332 en.lt.un_540 cs.sk.sq_664
+ 0x0f52100c, 0x0b19550c, 0x010e1008, 0x10190a13, // lt.ha.lv_543 rw.gl.es_543 lt.is.en_443 pt.gl.lt_665
+ 0x1a6b5207, 0x2521100c, 0x0a6827a4, 0x6e182711, // ha.ceb.tl_432 lt.jw.eu_543 gd.ig.pt_433 gd.ga.hmn_653
+ 0x270f10af, 0x01005508, 0x13683b0c, 0x08021805, // lt.lv.gd_655 rw.en.un_430 so.ig.et_543 ga.da.no_333
+ // [2620]
+ 0x190a0b02, 0x16193bee, 0x120c0ead, 0x0b00070c, // es.pt.gl_222 so.gl.hr_422 is.sv.hu_643 it.es.un_530
+ 0x1b280909, 0x200f55a4, 0x190b0a12, 0x083f0614, // pl.sw.tr_444 rw.lv.sq_433 pt.es.gl_654 de.af.no_666
+ 0x273128a4, 0x3b000213, 0x12201704, 0x55000107, // sw.az.gd_433 da.so.un_650 sr.sq.hu_332 en.rw.un_420
+ 0x130c3b04, 0x0c005304, 0x2a0713ee, 0x2d0d2a0e, // so.sv.et_332 ht.sv.un_320 et.it.mt_422 mt.cs.sk_555
+ // [2630]
+ 0x080711a0, 0x091c1360, 0x130c3ba9, 0x64001b0b, // ro.bg.uk_322 bh.mr.hi_664 so.sv.et_544 tr.lg.un_520
+ 0x0d062504, 0x553b6408, 0x2d0d12a6, 0x16005504, // eu.de.cs_332 lg.so.rw_443 hu.cs.sk_521 rw.hr.un_320
+ 0x04130f0c, 0x05000e13, 0x0e05080c, 0x21041007, // lv.et.fi_543 is.fr.un_650 no.fr.is_543 lt.fi.jw_432
+ 0x06031812, 0x1f003b21, 0x170a10af, 0x1206080c, // ga.nl.de_654 so.cy.un_860 be.mk.sr_655 no.de.hu_543
+ // [2640]
+ 0x070501ec, 0x011e28a7, 0x09101aa4, 0x13005222, // en.fr.it_644 sw.ms.en_532 tl.lt.pl_433 ha.et.un_870
+ 0x06000322, 0x0e1304af, 0x3f3b02af, 0x28003513, // nl.de.un_870 fi.et.is_655 da.so.af_655 zu.sw.un_650
+ 0x2d0d32a0, 0x02040eec, 0x1a2b3507, 0x1600280d, // bs.cs.sk_322 is.fi.da_644 zu.vi.tl_432 sw.hr.un_540
+ 0x0a07085a, 0x231f1213, 0x0411080b, 0x1f3f0312, // uk.bg.mk_553 hu.cy.ca_665 uk.ro.ru_542 nl.af.cy_654
+ // [2650]
+ 0x040813af, 0x13000c04, 0x64041a0c, 0x3b190a04, // et.no.fi_655 sv.et.un_320 tl.fi.lg_543 pt.gl.so_332
+ 0x0b351a0c, 0x10003b08, 0x4a6e6b08, 0x0704110c, // tl.zu.es_543 so.lt.un_430 ceb.hmn.yo_443 ro.ru.bg_543
+ 0x0f1b31af, 0x04000a05, 0x2d052507, 0x2b00271b, // az.tr.lv_655 mk.ru.un_330 eu.fr.sk_432 gd.vi.un_770
+ 0x312164ee, 0x1205530c, 0x02000f14, 0x213f0307, // lg.jw.az_422 ht.fr.hu_543 lv.da.un_660 nl.af.jw_432
+ // [2660]
+ 0x0a001208, 0x011307a0, 0x1a043b12, 0x16001707, // hu.pt.un_430 it.et.en_322 so.fi.tl_654 sr.hr.un_420
+ 0x520e3b60, 0x0d003504, 0x11000c0c, 0x53133505, // so.is.ha_664 zu.cs.un_320 sv.ro.un_530 zu.et.ht_333
+ 0x21005508, 0x1c1e21ad, 0x2300270d, 0x081206a4, // rw.jw.un_430 jw.ms.id_643 gd.ca.un_540 de.hu.no_433
+ 0x0a071013, 0x170704ad, 0x2d0d1904, 0x0e060805, // be.bg.mk_665 ru.bg.sr_643 gl.cs.sk_332 no.de.is_333
+ // [2670]
+ 0x0a00231b, 0x08063f05, 0x06040355, 0x0c0f080c, // ca.pt.un_770 af.de.no_333 nl.fi.de_442 no.lv.sv_543
+ 0x00003f2d, 0x23200b02, 0x3b006b08, 0x033f270e, // af.un.un_A00 es.sq.ca_222 ceb.so.un_430 gd.af.nl_555
+ 0x1a001318, 0x12023f60, 0x2900030e, 0x172d0d0c, // et.tl.un_740 af.da.hu_664 nl.sl.un_550 cs.sk.sr_543
+ 0x121b250d, 0x0b050308, 0x05002502, 0x08000c07, // eu.tr.hu_554 nl.fr.es_443 eu.fr.un_220 sv.no.un_420
+ // [2680]
+ 0x180227a0, 0x07190b02, 0x250e0ca7, 0x04003b19, // gd.da.ga_322 es.gl.it_222 sv.is.eu_532 so.fi.un_750
+ 0x21001834, 0x043f3b0c, 0x022b010c, 0x3b553504, // ar.fa.un_A80 so.af.fi_543 en.vi.da_543 zu.rw.so_332
+ 0x2d120d09, 0x3b123208, 0x170710a0, 0x556b1aa7, // cs.hu.sk_444 bs.hu.so_443 be.bg.sr_322 tl.ceb.rw_532
+ 0x0a122d11, 0x08023f09, 0x05210eb2, 0x6b351aee, // sk.hu.pt_653 af.da.no_444 is.jw.fr_732 tl.zu.ceb_422
+ // [2690]
+ 0x04022aa4, 0x31203b55, 0x3b6b1aad, 0x6400281b, // mt.da.fi_433 so.sq.az_442 tl.ceb.so_643 sw.lg.un_770
+ 0x230d0aee, 0x3f031aaf, 0x09080e0c, 0x190a12ac, // pt.cs.ca_422 tl.nl.af_655 is.no.pl_543 hu.pt.gl_632
+ 0x11001a22, 0x0c001e08, 0x6b284a07, 0x3b1a3504, // tl.ro.un_870 ms.sv.un_430 yo.sw.ceb_432 zu.tl.so_332
+ 0x1c3b520c, 0x2000320c, 0x230219a6, 0x3b2821af, // ha.so.id_543 bs.sq.un_530 gl.da.ca_521 jw.sw.so_655
+ // [26a0]
+ 0x553b6baf, 0x133f0307, 0x18076bee, 0x033f0409, // ceb.so.rw_655 nl.af.et_432 ceb.it.ga_422 fi.af.nl_444
+ 0x131617ad, 0x3f133b0c, 0x13002105, 0x35002721, // sr.hr.et_643 so.et.af_543 jw.et.un_330 gd.zu.un_860
+ 0x050711a4, 0x526b2da4, 0x04005218, 0x35644a0c, // ro.it.fr_433 sk.ceb.ha_433 ha.fi.un_740 yo.lg.zu_543
+ 0x1b2b1ca7, 0x17000721, 0x19000512, 0x3b321604, // id.vi.tr_532 bg.sr.un_860 fr.gl.un_640 hr.bs.so_332
+ // [26b0]
+ 0x08132504, 0x08001705, 0x21004a11, 0x0a2307a4, // eu.et.no_332 sr.uk.un_330 yo.jw.un_630 it.ca.pt_433
+ 0x11006e07, 0x181e0413, 0x2a002708, 0x53182707, // hmn.ro.un_420 fi.ms.ga_665 gd.mt.un_430 gd.ga.ht_432
+ 0x20123205, 0x05006b07, 0x05124a0c, 0x211f1107, // bs.hu.sq_333 ceb.fr.un_420 yo.hu.fr_543 ro.cy.jw_432
+ 0x121a6b5a, 0x55003522, 0x55000d13, 0x23002012, // ceb.tl.hu_553 zu.rw.un_870 cs.rw.un_650 sq.ca.un_640
+ // [26c0]
+ 0x020535a4, 0x1e1c2705, 0x523b6b12, 0x2d0d1314, // zu.fr.da_433 gd.id.ms_333 ceb.so.ha_654 et.cs.sk_666
+ 0x201206ad, 0x10190f13, 0x0d050aa9, 0x1b0a0507, // de.hu.sq_643 lv.gl.lt_665 pt.fr.cs_544 fr.pt.tr_432
+ 0x35000121, 0x2b002508, 0x20005202, 0x031c5307, // en.zu.un_860 eu.vi.un_430 ha.sq.un_220 ht.id.nl_432
+ 0x0d321705, 0x04094aa9, 0x033f0ca9, 0x0d1c130b, // sr.bs.cs_333 yo.pl.fi_544 sv.af.nl_544 bh.mr.ne_542
+ // [26d0]
+ 0x3b0b35a4, 0x1a006804, 0x3217165a, 0x640a350c, // zu.es.so_433 ig.tl.un_320 hr.sr.bs_553 zu.pt.lg_543
+ 0x1a056ba7, 0x0b0a1909, 0x52003511, 0x1e1c010c, // ceb.fr.tl_532 gl.pt.es_444 zu.ha.un_630 en.id.ms_543
+ 0x23044aaf, 0x122d0d1b, 0x1b215308, 0x351e1c0e, // yo.fi.ca_655 cs.sk.hu_777 ht.jw.tr_443 id.ms.zu_555
+ 0x13003b20, 0x64283bec, 0x190a0ba4, 0x3b002821, // so.et.un_850 so.sw.lg_644 es.pt.gl_433 sw.so.un_860
+ // [26e0]
+ 0x53643513, 0x0a00230d, 0x0b2b19a0, 0x353b32a0, // zu.lg.ht_665 ca.pt.un_540 gl.vi.es_322 bs.so.zu_322
+ 0x0b0a12b2, 0x3b2152ee, 0x2b4a64ad, 0x190b0aa9, // hu.pt.es_732 ha.jw.so_422 lg.yo.vi_643 pt.es.gl_544
+ 0x3b006407, 0x053b1c04, 0x2a001e02, 0x1e0728a0, // lg.so.un_420 id.so.fr_332 ms.mt.un_220 sw.it.ms_322
+ 0x25556b0c, 0x0b1a3b12, 0x1c130913, 0x190b110e, // ceb.rw.eu_543 so.tl.es_654 hi.bh.mr_665 ro.es.gl_555
+ // [26f0]
+ 0x23190a08, 0x21001235, 0x2a5268ad, 0x3b003507, // pt.gl.ca_443 ur.fa.un_A90 ig.ha.mt_643 zu.so.un_420
+ 0x171664a0, 0x0c001314, 0x0e3b05ad, 0x03000212, // lg.hr.sr_322 et.sv.un_660 fr.so.is_643 da.nl.un_640
+ 0x116420a4, 0x64001904, 0x0c083fa0, 0x3b6428ec, // sq.lg.ro_433 gl.lg.un_320 af.no.sv_322 sw.lg.so_644
+ 0x643b3fa9, 0x00002d03, 0x13250204, 0x0c0f1014, // af.so.lg_544 sk.un.un_300 da.eu.et_332 lt.lv.sv_666
+ // [2700]
+ 0x08071008, 0x1c091311, 0x0e0206a4, 0x050716a0, // be.bg.uk_443 bh.hi.mr_653 de.da.is_433 hr.it.fr_322
+ 0x1000290c, 0x100b4a55, 0x0a190b55, 0x0800040c, // sl.lt.un_530 yo.es.lt_442 es.gl.pt_442 ru.uk.un_530
+ 0x0a0b2908, 0x06002104, 0x0802130c, 0x1c211ea7, // sl.es.pt_443 jw.de.un_320 et.da.no_543 ms.jw.id_532
+ 0x3f002a04, 0x19000b29, 0x104a2913, 0x0c0b200c, // mt.af.un_320 es.gl.un_960 sl.yo.lt_665 sq.es.sv_543
+ // [2710]
+ 0x0a1017ec, 0x25121fa7, 0x10000a21, 0x29104a07, // sr.be.mk_644 cy.hu.eu_532 mk.be.un_860 yo.lt.sl_432
+ 0x08170405, 0x07006b12, 0x10041aa4, 0x190b0a02, // ru.sr.uk_333 ceb.it.un_640 tl.fi.lt_433 pt.es.gl_222
+ 0x21282011, 0x3f091ca0, 0x281327a0, 0x18042707, // sq.sw.jw_653 id.pl.af_322 gd.et.sw_322 gd.fi.ga_432
+ 0x0600520e, 0x190b05a4, 0x09001904, 0x01050da0, // ha.de.un_550 fr.es.gl_433 gl.pl.un_320 cs.fr.en_322
+ // [2720]
+ 0x10001904, 0x31042505, 0x3b2820a0, 0x11232704, // gl.lt.un_320 eu.fi.az_333 sq.sw.so_322 gd.ca.ro_332
+ 0x0d0913ee, 0x32090511, 0x211029a4, 0x29000a13, // bh.hi.ne_422 fr.pl.bs_653 sl.lt.jw_433 pt.sl.un_650
+ 0x1e1c21a6, 0x294a0b07, 0x32291602, 0x05000a12, // jw.id.ms_521 es.yo.sl_432 hr.sl.bs_222 pt.fr.un_640
+ 0x3b1a21a4, 0x20351ea0, 0x100811a0, 0x6b131a08, // jw.tl.so_433 ms.zu.sq_322 ro.uk.be_322 tl.et.ceb_443
+ // [2730]
+ 0x091c13a7, 0x191009a7, 0x04001212, 0x1b003519, // bh.mr.hi_532 pl.lt.gl_532 hu.fi.un_640 zu.tr.un_750
+ 0x023b2155, 0x1a352807, 0x32522907, 0x08002722, // jw.so.da_442 sw.zu.tl_432 sl.ha.bs_432 gd.no.un_870
+ 0x0a000509, 0x21000518, 0x040127a0, 0x2164280d, // fr.pt.un_440 fr.jw.un_740 gd.en.fi_322 sw.lg.jw_554
+ 0x3b002109, 0x1f00011a, 0x162932a7, 0x643528ad, // jw.so.un_440 en.cy.un_760 bs.sl.hr_532 sw.zu.lg_643
+ // [2740]
+ 0x0f01070c, 0x07001b04, 0x64283508, 0x13000d1b, // it.en.lv_543 tr.it.un_320 zu.sw.lg_443 ne.bh.un_770
+ 0x0c3f2507, 0x35526455, 0x4a53050c, 0x072501a0, // eu.af.sv_432 lg.ha.zu_442 fr.ht.yo_543 en.eu.it_322
+ 0x070a170c, 0x0a1710a7, 0x0413100e, 0x0800040e, // sr.mk.bg_543 be.sr.mk_532 lt.et.fi_555 ru.uk.un_550
+ 0x050625ad, 0x1a6428ad, 0x1a00281a, 0x080a07ad, // eu.de.fr_643 sw.lg.tl_643 sw.tl.un_760 bg.mk.uk_643
+ // [2750]
+ 0x17002702, 0x3b003519, 0x19070b12, 0x292d1602, // gd.sr.un_220 zu.so.un_750 es.it.gl_654 hr.sk.sl_222
+ 0x123b21a7, 0x2a272107, 0x6b682704, 0x0f033fa0, // jw.so.hu_532 jw.gd.mt_432 gd.ig.ceb_332 af.nl.lv_322
+ 0x29201111, 0x16292aee, 0x351a2708, 0x526853a0, // ro.sq.sl_653 mt.sl.hr_422 gd.tl.zu_443 ht.ig.ha_322
+ 0x130d0913, 0x2000251a, 0x1b001114, 0x252a04af, // hi.ne.bh_665 eu.sq.un_760 ro.tr.un_660 fi.mt.eu_655
+ // [2760]
+ 0x10090408, 0x6e00060d, 0x17001118, 0x13001902, // fi.pl.lt_443 de.hmn.un_540 ro.sr.un_740 gl.et.un_220
+ 0x0e1304a4, 0x3b000205, 0x2a000c04, 0x100f1602, // fi.et.is_433 da.so.un_330 sv.mt.un_320 hr.lv.lt_222
+ 0x1f20275a, 0x091c0d5a, 0x041e1cad, 0x28553505, // gd.sq.cy_553 ne.mr.hi_553 id.ms.fi_643 zu.rw.sw_333
+ 0x6e0e0ba0, 0x21040ca0, 0x321729a4, 0x53001902, // es.is.hmn_322 sv.fi.jw_322 sl.sr.bs_433 gl.ht.un_220
+ // [2770]
+ 0x0c3f0204, 0x17000d07, 0x210e1211, 0x20292a60, // da.af.sv_332 cs.sr.un_420 hu.is.jw_653 mt.sl.sq_664
+ 0x13041b55, 0x132029ad, 0x04000104, 0x2904530c, // tr.fi.et_442 sl.sq.et_643 en.fi.un_320 ht.fi.sl_543
+ 0x4a00522a, 0x011b6ba0, 0x091f0405, 0x043b1bac, // ha.yo.un_970 ceb.tr.en_322 fi.cy.pl_333 tr.so.fi_632
+ 0x25201c05, 0x0d001012, 0x3f250ba4, 0x08001604, // id.sq.eu_333 lt.cs.un_640 es.eu.af_433 hr.no.un_320
+ // [2780]
+ 0x2a070caf, 0x01000d19, 0x191a0a0d, 0x04064aa0, // sv.it.mt_655 cs.en.un_750 pt.tl.gl_554 yo.de.fi_322
+ 0x190b07af, 0x080a2705, 0x1c321605, 0x3b1b0e05, // it.es.gl_655 gd.pt.no_333 hr.bs.id_333 is.tr.so_333
+ 0x254a3ba4, 0x08070ba0, 0x29051f0b, 0x011b3ba4, // so.yo.eu_433 es.it.no_322 cy.fr.sl_542 so.tr.en_433
+ 0x06001f14, 0x03004a1a, 0x01001f0c, 0x1908100c, // cy.de.un_660 yo.nl.un_760 cy.en.un_530 lt.no.gl_543
+ // [2790]
+ 0x083502a4, 0x0b0a35ee, 0x2d0d12ec, 0x20523208, // da.zu.no_433 zu.pt.es_422 hu.cs.sk_644 bs.ha.sq_443
+ 0x120d0e07, 0x00003101, 0x070804ad, 0x53071111, // is.cs.hu_432 az.un.un_200 ru.uk.bg_643 ro.it.ht_653
+ 0x07002507, 0x03005313, 0x6b00010c, 0x3102080c, // eu.it.un_420 ht.nl.un_650 en.ceb.un_530 no.da.az_543
+ 0x190a01a9, 0x18002734, 0x3f006b04, 0x2d290d14, // en.pt.gl_544 gd.ga.un_A80 ceb.af.un_320 cs.sl.sk_666
+ // [27a0]
+ 0x02001f14, 0x2500070c, 0x182527af, 0x11002513, // cy.da.un_660 it.eu.un_530 gd.eu.ga_655 eu.ro.un_650
+ 0x250a0107, 0x10001305, 0x640d2d07, 0x0d530507, // en.pt.eu_432 et.lt.un_330 sk.cs.lg_432 fr.ht.cs_432
+ 0x64351fee, 0x170a11a6, 0x2d0d29ec, 0x23253ba9, // cy.zu.lg_422 ro.mk.sr_521 sl.cs.sk_644 so.eu.ca_544
+ 0x533f030b, 0x32176409, 0x080710a0, 0x081007a0, // nl.af.ht_542 lg.sr.bs_444 be.bg.uk_322 bg.be.uk_322
+ // [27b0]
+ 0x18052713, 0x29001609, 0x1f100408, 0x25072a12, // gd.fr.ga_665 hr.sl.un_440 fi.lt.cy_443 mt.it.eu_654
+ 0x185532a0, 0x53050d0c, 0x5264040c, 0x53052912, // bs.rw.ga_322 cs.fr.ht_543 fi.lg.ha_543 sl.fr.ht_654
+ 0x02060d08, 0x1008040c, 0x281e1c60, 0x1f251807, // cs.de.da_443 ru.uk.be_543 id.ms.sw_664 ga.eu.cy_432
+ 0x0d000920, 0x132053ee, 0x52002a08, 0x2725180b, // hi.ne.un_850 ht.sq.et_422 mt.ha.un_430 ga.eu.gd_542
+ // [27c0]
+ 0x1e1c20a6, 0x0600290c, 0x182725ad, 0x1f001021, // sq.id.ms_521 sl.de.un_530 eu.gd.ga_643 lt.cy.un_860
+ 0x3f0b5504, 0x23001122, 0x07010ba0, 0x12000621, // rw.es.af_332 ro.ca.un_870 es.en.it_322 de.hu.un_860
+ 0x534a05ad, 0x230525a9, 0x282023ee, 0x08040a04, // fr.yo.ht_643 eu.fr.ca_544 ca.sq.sw_422 mk.ru.uk_332
+ 0x203b2a11, 0x06041313, 0x28352104, 0x3f080213, // mt.so.sq_653 et.fi.de_665 jw.zu.sw_332 da.no.af_665
+ // [27d0]
+ 0x3f0601a4, 0x08000205, 0x0d0913ac, 0x18000104, // en.de.af_433 da.no.un_330 bh.hi.ne_632 en.ga.un_320
+ 0x05002808, 0x550135a0, 0x31121b05, 0x64321704, // sw.fr.un_430 zu.en.rw_322 tr.hu.az_333 sr.bs.lg_332
+ 0x6413060c, 0x1700270c, 0x0c1a6b07, 0x19310aad, // de.et.lg_543 gd.sr.un_530 ceb.tl.sv_432 pt.az.gl_643
+ 0x281a2aa0, 0x2a000e21, 0x1704100e, 0x311e1c09, // mt.tl.sw_322 is.mt.un_860 be.ru.sr_555 id.ms.az_444
+ // [27e0]
+ 0x02212a07, 0x6b0b07ad, 0x130208a7, 0x1c101ea0, // mt.jw.da_432 it.es.ceb_643 no.da.et_532 ms.lt.id_322
+ 0x1f006b07, 0x08202aad, 0x06035307, 0x35271813, // ceb.cy.un_420 mt.sq.no_643 ht.nl.de_432 ga.gd.zu_665
+ 0x2b050a5a, 0x1b002307, 0x2a060408, 0x12000104, // pt.fr.vi_553 ca.tr.un_420 fi.de.mt_443 en.hu.un_320
+ 0x28311ba4, 0x13040609, 0x030413ad, 0x0a00200c, // tr.az.sw_433 de.fi.et_444 et.fi.nl_643 sq.pt.un_530
+ // [27f0]
+ 0x2d003202, 0x11040a0c, 0x080203a0, 0x28006b0b, // bs.sk.un_220 mk.ru.ro_543 nl.da.no_322 ceb.sw.un_520
+ 0x3f3b1a07, 0x05000704, 0x2000350c, 0x6b5301ec, // tl.so.af_432 it.fr.un_320 zu.sq.un_530 en.ht.ceb_644
+ 0x041a3b0e, 0x1f00061a, 0x6b270111, 0x110a2308, // so.tl.fi_555 de.cy.un_760 en.gd.ceb_653 ca.pt.ro_443
+ 0x2d102913, 0x06643f05, 0x53001f23, 0x07192312, // sl.lt.sk_665 af.lg.de_333 cy.ht.un_880 ca.gl.it_654
+
+ // [2800]
+ 0x06050fee, 0x25000702, 0x1a0768a6, 0x13002518, // lv.fr.de_422 it.eu.un_220 ig.it.tl_521 eu.et.un_740
+ 0x071704a9, 0x3f0403a4, 0x1b2a31a4, 0x070a08ec, // ru.sr.bg_544 nl.fi.af_433 az.mt.tr_433 uk.mk.bg_644
+ 0x3b033f12, 0x04061008, 0x133f03ec, 0x19005521, // af.nl.so_654 lt.de.fi_443 nl.af.et_644 rw.gl.un_860
+ 0x05001a0d, 0x35002822, 0x53074a0c, 0x0a1708a4, // tl.fr.un_540 sw.zu.un_870 yo.it.ht_543 uk.sr.mk_433
+ // [2810]
+ 0x10002811, 0x060e1a07, 0x0e35520c, 0x55002819, // sw.lt.un_630 tl.is.de_432 ha.zu.is_543 sw.rw.un_750
+ 0x211352ee, 0x053f03a9, 0x2500010d, 0x01002102, // ha.et.jw_422 nl.af.fr_544 en.eu.un_540 jw.en.un_220
+ 0x081304a4, 0x683b3104, 0x0d352d13, 0x11032dee, // fi.et.no_433 az.so.ig_332 sk.zu.cs_665 sk.nl.ro_422
+ 0x08000708, 0x1c0d13a6, 0x09001102, 0x35000311, // bg.uk.un_430 bh.ne.mr_521 ro.pl.un_220 nl.zu.un_630
+ // [2820]
+ 0x201125a0, 0x130c06a7, 0x1100250b, 0x11005308, // eu.ro.sq_322 de.sv.et_532 eu.ro.un_520 ht.ro.un_430
+ 0x190f0ba4, 0x0b2519a7, 0x200603ec, 0x190b0a55, // es.lv.gl_433 gl.eu.es_532 nl.de.sq_644 pt.es.gl_442
+ 0x3217160d, 0x19111804, 0x13033f60, 0x190a0b09, // hr.sr.bs_554 ga.ro.gl_332 af.nl.et_664 es.pt.gl_444
+ 0x12000513, 0x04000802, 0x550464a0, 0x1100530d, // fr.hu.un_650 no.fi.un_220 lg.fi.rw_322 ht.ro.un_540
+ // [2830]
+ 0x641955ad, 0x11120107, 0x0f090760, 0x193f0308, // rw.gl.lg_643 en.hu.ro_432 it.pl.lv_664 nl.af.gl_443
+ 0x283152a9, 0x530413a4, 0x091c0da0, 0x25126bad, // ha.az.sw_544 et.fi.ht_433 ne.mr.hi_322 ceb.hu.eu_643
+ 0x0802060b, 0x55531aa4, 0x68002820, 0x0a041007, // de.da.no_542 tl.ht.rw_433 sw.ig.un_850 be.ru.mk_432
+ 0x0b070209, 0x53002821, 0x1e180705, 0x1b273b07, // da.it.es_444 sw.ht.un_860 it.ga.ms_333 so.gd.tr_432
+ // [2840]
+ 0x2800010d, 0x2806030c, 0x292d0da9, 0x07190bee, // en.sw.un_540 nl.de.sw_543 cs.sk.sl_544 es.gl.it_422
+ 0x0900270e, 0x280627ee, 0x64080707, 0x2d096407, // gd.pl.un_550 gd.de.sw_422 it.no.lg_432 lg.pl.sk_432
+ 0x4a190b08, 0x23531f0c, 0x2a081ba0, 0x03070b09, // es.gl.yo_443 cy.ht.ca_543 tr.no.mt_322 es.it.nl_444
+ 0x190b23a9, 0x07081055, 0x31001e0d, 0x0a0410a0, // ca.es.gl_544 be.uk.bg_442 ms.az.un_540 be.ru.mk_322
+ // [2850]
+ 0x644a5505, 0x1012110c, 0x040a21ee, 0x04000702, // rw.yo.lg_333 ro.hu.lt_543 jw.pt.fi_422 bg.ru.un_220
+ 0x0a284a07, 0x283503ad, 0x08041111, 0x17000413, // yo.sw.pt_432 nl.zu.sw_643 ro.ru.uk_653 ru.sr.un_650
+ 0x201101a0, 0x64321fa7, 0x2d0d06af, 0x08520e12, // en.ro.sq_322 cy.bs.lg_532 de.cs.sk_655 is.ha.no_654
+ 0x0b00121a, 0x2000351b, 0x00000f06, 0x0600271a, // hu.es.un_760 zu.sq.un_770 lv.un.un_400 gd.de.un_760
+ // [2860]
+ 0x070601a4, 0x355206ad, 0x3f030212, 0x06010508, // en.de.it_433 de.ha.zu_643 da.nl.af_654 fr.en.de_443
+ 0x05102807, 0x272501a4, 0x232719ec, 0x21531008, // sw.lt.fr_432 en.eu.gd_433 gl.gd.ca_644 lt.ht.jw_443
+ 0x0e190b60, 0x11072508, 0x20001123, 0x3f050313, // es.gl.is_664 eu.it.ro_443 ro.sq.un_880 nl.fr.af_665
+ 0x0b55520c, 0x11252dad, 0x10081708, 0x01060808, // ha.rw.es_543 sk.eu.ro_643 sr.uk.be_443 no.de.en_443
+ // [2870]
+ 0x1c1e2560, 0x080c25ee, 0x17000104, 0x251107a9, // eu.ms.id_664 eu.sv.no_422 en.sr.un_320 it.ro.eu_544
+ 0x28000c0e, 0x0c080202, 0x12000f04, 0x19001104, // sv.sw.un_550 da.no.sv_222 lv.hu.un_320 ro.gl.un_320
+ 0x1a350712, 0x53001a07, 0x0e2d4a07, 0x52552809, // it.zu.tl_654 tl.ht.un_420 yo.sk.is_432 sw.rw.ha_444
+ 0x1c2123a0, 0x1100071a, 0x0b271904, 0x0f001f19, // ca.jw.id_322 it.ro.un_760 gl.gd.es_332 cy.lv.un_750
+ // [2880]
+ 0x022108a4, 0x31122805, 0x1e1c12af, 0x1a286b07, // no.jw.da_433 sw.hu.az_333 hu.id.ms_655 ceb.sw.tl_432
+ 0x070a11a0, 0x0a1708a9, 0x1a12285a, 0x07202507, // ro.mk.bg_322 uk.sr.mk_544 sw.hu.tl_553 eu.sq.it_432
+ 0x07000612, 0x1a286b0c, 0x2a033f5a, 0x0f092d12, // de.it.un_640 ceb.sw.tl_543 af.nl.mt_553 sk.pl.lv_654
+ 0x116864ee, 0x11252dee, 0x21001e0d, 0x55354a07, // lg.ig.ro_422 sk.eu.ro_422 ms.jw.un_540 yo.zu.rw_432
+ // [2890]
+ 0x35000808, 0x130b1aa0, 0x2800640d, 0x0207055a, // no.zu.un_430 tl.es.et_322 lg.sw.un_540 fr.it.da_553
+ 0x21000305, 0x1f003521, 0x35556413, 0x08553508, // nl.jw.un_330 zu.cy.un_860 lg.rw.zu_665 zu.rw.no_443
+ 0x07642a14, 0x0e18190d, 0x0627180d, 0x21001809, // mt.lg.it_666 gl.ga.is_554 ga.gd.de_554 ar.fa.un_440
+ 0x033f2007, 0x0c3b1ea4, 0x3f1123a0, 0x231110a4, // sq.af.nl_432 ms.so.sv_433 ca.ro.af_322 lt.ro.ca_433
+ // [28a0]
+ 0x17131ca0, 0x07041760, 0x01036b04, 0x250b64a4, // id.et.sr_322 sr.ru.bg_664 ceb.nl.en_332 lg.es.eu_433
+ 0x13002722, 0x182127a0, 0x190b6409, 0x051e1c0c, // gd.et.un_870 gd.jw.ga_322 lg.es.gl_444 id.ms.fr_543
+ 0x0e0c04a9, 0x1b02230c, 0x53002819, 0x641c1e0c, // fi.sv.is_544 ca.da.tr_543 sw.ht.un_750 ms.id.lg_543
+ 0x311c0bee, 0x3b1025a9, 0x070b6455, 0x1e1c1aaf, // es.id.az_422 eu.lt.so_544 lg.es.it_442 tl.id.ms_655
+ // [28b0]
+ 0x1a216b05, 0x0b271812, 0x550764a4, 0x131c0913, // ceb.jw.tl_333 ga.gd.es_654 lg.it.rw_433 hi.mr.bh_665
+ 0x530121a0, 0x18272813, 0x0f002722, 0x2552530c, // jw.en.ht_322 sw.gd.ga_665 gd.lv.un_870 ht.ha.eu_543
+ 0x2a000e1a, 0x6b0e275a, 0x554a070c, 0x4a645207, // is.mt.un_760 gd.is.ceb_553 it.yo.rw_543 ha.lg.yo_432
+ 0x210103a0, 0x01532712, 0x1f3f010c, 0x05001105, // nl.en.jw_322 gd.ht.en_654 en.af.cy_543 ro.fr.un_330
+ // [28c0]
+ 0x3f1a6b11, 0x3f102509, 0x3f1235a0, 0x04531e07, // ceb.tl.af_653 eu.lt.af_444 zu.hu.af_322 ms.ht.fi_432
+ 0x10002d14, 0x232b1ca0, 0x18001f08, 0x55524a12, // sk.lt.un_660 id.vi.ca_322 cy.ga.un_430 yo.ha.rw_654
+ 0x25000307, 0x18005305, 0x27131ca0, 0x01193b04, // nl.eu.un_420 ht.ga.un_330 id.et.gd_322 so.gl.en_332
+ 0x27041307, 0x12311bad, 0x68000a1b, 0x0e3f0807, // et.fi.gd_432 tr.az.hu_643 pt.ig.un_770 no.af.is_432
+ // [28d0]
+ 0x296455a4, 0x0f13100b, 0x0c005307, 0x2d0d6ea4, // rw.lg.sl_433 lt.et.lv_542 ht.sv.un_420 hmn.cs.sk_433
+ 0x0b55645a, 0x18090d0c, 0x5208550c, 0x28051e07, // lg.rw.es_553 cs.pl.ga_543 rw.no.ha_543 ms.fr.sw_432
+ 0x64000b05, 0x3f311ba9, 0x12132708, 0x17043f08, // es.lg.un_330 tr.az.af_544 gd.et.hu_443 af.fi.sr_443
+ 0x101b2108, 0x211309a0, 0x17001c08, 0x081711ee, // jw.tr.lt_443 pl.et.jw_322 id.sr.un_430 ro.sr.uk_422
+ // [28e0]
+ 0x190a1814, 0x3200171b, 0x20533511, 0x286455ec, // ga.pt.gl_666 sr.bs.un_770 zu.ht.sq_653 rw.lg.sw_644
+ 0x1c0720ee, 0x2d120d0e, 0x2d0f2904, 0x0a0553ee, // sq.it.id_422 cs.hu.sk_555 sl.lv.sk_332 ht.fr.pt_422
+ 0x32172955, 0x1b0931a4, 0x2d0d1f0c, 0x062d0309, // sl.sr.bs_442 az.pl.tr_433 cy.cs.sk_543 nl.sk.de_444
+ 0x2305211d, 0x0d091cac, 0x20002a09, 0x2a001212, // jw.fr.ca_852 mr.hi.ne_632 mt.sq.un_440 hu.mt.un_640
+ // [28f0]
+ 0x080c0204, 0x55061305, 0x13002908, 0x2d0920ec, // da.sv.no_332 et.de.rw_333 sl.et.un_430 sq.pl.sk_644
+ 0x6b080cec, 0x12206e04, 0x12052108, 0x171629a0, // sv.no.ceb_644 hmn.sq.hu_332 jw.fr.hu_443 sl.hr.sr_322
+ 0x64043bee, 0x03060508, 0x041b3107, 0x04643bad, // so.fi.lg_422 fr.de.nl_443 az.tr.fi_432 so.lg.fi_643
+ 0x16002d05, 0x06003f13, 0x170708a9, 0x3b022712, // sk.hr.un_330 af.de.un_650 uk.bg.sr_544 gd.da.so_654
+ // [2900]
+ 0x0c002704, 0x3b046404, 0x100417ad, 0x10000429, // gd.sv.un_320 lg.fi.so_332 sr.ru.be_643 ru.be.un_960
+ 0x210518ac, 0x190a07af, 0x0c081f07, 0x290c08ee, // ga.fr.jw_632 it.pt.gl_655 cy.no.sv_432 no.sv.sl_422
+ 0x19001804, 0x20002a0d, 0x0c12170c, 0x0f120c0c, // ga.gl.un_320 mt.sq.un_540 sr.hu.sv_543 sv.hu.lv_543
+ 0x6e1720a4, 0x010c0aa6, 0x20006e13, 0x0700290d, // sq.sr.hmn_433 pt.sv.en_521 hmn.sq.un_650 sl.it.un_540
+ // [2910]
+ 0x2a0c25a0, 0x08001f02, 0x120c2a07, 0x08010c04, // eu.sv.mt_322 cy.no.un_220 mt.sv.hu_432 sv.en.no_332
+ 0x21001e02, 0x08053f08, 0x04006b04, 0x281a6407, // ms.jw.un_220 af.fr.no_443 ceb.fi.un_320 lg.tl.sw_432
+ 0x20001318, 0x3b131e07, 0x06190504, 0x0608020d, // et.sq.un_740 ms.et.so_432 fr.gl.de_332 da.no.de_554
+ 0x2d0f100c, 0x08021bee, 0x07000104, 0x04002513, // lt.lv.sk_543 tr.da.no_422 en.it.un_320 eu.fi.un_650
+ // [2920]
+ 0x10170405, 0x3f0604a4, 0x0b006404, 0x350107ee, // ru.sr.be_333 fi.de.af_433 lg.es.un_320 it.en.zu_422
+ 0x080204a9, 0x19000709, 0x27186ba7, 0x08070405, // fi.da.no_544 it.gl.un_440 ceb.ga.gd_532 ru.bg.uk_333
+ 0x20006e0c, 0x1c5535a9, 0x213f23a4, 0x29001304, // hmn.sq.un_530 zu.rw.id_544 ca.af.jw_433 et.sl.un_320
+ 0x35001302, 0x04181760, 0x06001b21, 0x040853a4, // et.zu.un_220 sr.ar.ru_664 tr.de.un_860 ht.no.fi_433
+ // [2930]
+ 0x1b110a07, 0x520e3b07, 0x083f06a0, 0x13043bec, // pt.ro.tr_432 so.is.ha_432 de.af.no_322 so.fi.et_644
+ 0x11000a07, 0x0b113107, 0x08523b12, 0x3f001f04, // pt.ro.un_420 az.ro.es_432 so.ha.no_654 cy.af.un_320
+ 0x0803130c, 0x0f2864ee, 0x10256404, 0x230511a4, // et.nl.no_543 lg.sw.lv_422 lg.eu.lt_332 ro.fr.ca_433
+ 0x080601a0, 0x2d1720a6, 0x23100509, 0x130412ec, // en.de.no_322 sq.sr.sk_521 fr.lt.ca_444 hu.fi.et_644
+ // [2940]
+ 0x0f2d0908, 0x1f122713, 0x0b00641a, 0x28082007, // pl.sk.lv_443 gd.hu.cy_665 lg.es.un_760 sq.no.sw_432
+ 0x29171605, 0x2300101a, 0x10230c08, 0x0e00130c, // hr.sr.sl_333 lt.ca.un_760 sv.ca.lt_443 et.is.un_530
+ 0x03002a08, 0x2a005207, 0x230e0aee, 0x1013230c, // mt.nl.un_430 ha.mt.un_420 pt.is.ca_422 ca.et.lt_543
+ 0x085320ec, 0x05230355, 0x170a1fad, 0x05012308, // sq.ht.no_644 nl.ca.fr_442 cy.pt.sr_643 ca.en.fr_443
+ // [2950]
+ 0x0a00100d, 0x640701a0, 0x1b136407, 0x1e1b31a0, // be.mk.un_540 en.it.lg_322 lg.et.tr_432 az.tr.ms_322
+ 0x0f1b1a0c, 0x2300252b, 0x53001104, 0x682864af, // tl.tr.lv_543 eu.ca.un_980 ro.ht.un_320 lg.sw.ig_655
+ 0x1f5305a4, 0x10041e05, 0x23000523, 0x12000923, // fr.ht.cy_433 ms.fi.lt_333 fr.ca.un_880 pl.hu.un_880
+ 0x6b4a680c, 0x64120f0c, 0x28000f22, 0x13283b60, // ig.yo.ceb_543 lv.hu.lg_543 lv.sw.un_870 so.sw.et_664
+ // [2960]
+ 0x13003b19, 0x03041308, 0x3f0523a9, 0x52282012, // so.et.un_750 et.fi.nl_443 ca.fr.af_544 sq.sw.ha_654
+ 0x0a04080c, 0x2b014a0b, 0x1123050c, 0x20285508, // uk.ru.mk_543 yo.en.vi_542 fr.ca.ro_543 rw.sw.sq_443
+ 0x121304ec, 0x3b060514, 0x0a0419a0, 0x01090cee, // fi.et.hu_644 fr.de.so_666 gl.fi.pt_322 sv.pl.en_422
+ 0x3b00282a, 0x1e1c08a0, 0x52202808, 0x31015505, // sw.so.un_970 no.id.ms_322 sw.sq.ha_443 rw.en.az_333
+ // [2970]
+ 0x35006813, 0x034a3ba4, 0x0828350c, 0x0c003b08, // ig.zu.un_650 so.yo.nl_433 zu.sw.no_543 so.sv.un_430
+ 0x0d0929ad, 0x27002522, 0x04001214, 0x1c00352a, // sl.pl.cs_643 eu.gd.un_870 hu.fi.un_660 zu.id.un_970
+ 0x1a0e3b08, 0x1b040eaf, 0x11050355, 0x5220550c, // so.is.tl_443 is.fi.tr_655 nl.fr.ro_442 rw.sq.ha_543
+ 0x1002080c, 0x11183baf, 0x6800520e, 0x35641ca0, // no.da.lt_543 so.ga.ro_655 ha.ig.un_550 id.lg.zu_322
+ // [2980]
+ 0x12184a04, 0x1101130c, 0x550f6404, 0x01130455, // yo.ga.hu_332 et.en.ro_543 lg.lv.rw_332 fi.et.en_442
+ 0x03133b08, 0x10251faf, 0x3b041311, 0x31111ba9, // so.et.nl_443 cy.eu.lt_655 et.fi.so_653 tr.ro.az_544
+ 0x28096bad, 0x6b356855, 0x311b4a11, 0x310764ee, // ceb.pl.sw_643 ig.zu.ceb_442 yo.tr.az_653 lg.it.az_422
+ 0x051b3bad, 0x52205511, 0x170229a9, 0x056435a6, // so.tr.fr_643 rw.sq.ha_653 sl.da.sr_544 zu.lg.fr_521
+ // [2990]
+ 0x23110508, 0x20002a07, 0x521e1c04, 0x06311baf, // fr.ro.ca_443 mt.sq.un_420 id.ms.ha_332 tr.az.de_655
+ 0x55642811, 0x2d096e08, 0x09003f19, 0x0d001902, // sw.lg.rw_653 hmn.pl.sk_443 af.pl.un_750 gl.cs.un_220
+ 0x05126804, 0x06256405, 0x214a5204, 0x0e04135a, // ig.hu.fr_332 lg.eu.de_333 ha.yo.jw_332 et.fi.is_553
+ 0x3f2016a0, 0x06110704, 0x0b121908, 0x071b110c, // hr.sq.af_322 it.ro.de_332 gl.hu.es_443 ro.tr.it_543
+ // [29a0]
+ 0x25120412, 0x0864130c, 0x0f0625a4, 0x21050ea4, // fi.hu.eu_654 et.lg.no_543 eu.de.lv_433 is.fr.jw_433
+ 0x121304a4, 0x2d00170d, 0x210e1c0c, 0x1f000919, // fi.et.hu_433 sr.sk.un_540 id.is.jw_543 pl.cy.un_750
+ 0x07060dad, 0x3b1e1c14, 0x171029a9, 0x2300112a, // cs.de.it_643 id.ms.so_666 sl.lt.sr_544 ro.ca.un_970
+ 0x1f1c1eec, 0x030f0502, 0x3b026e11, 0x2000031a, // ms.id.cy_644 fr.lv.nl_222 hmn.da.so_653 nl.sq.un_760
+ // [29b0]
+ 0x2d0f090c, 0x3f320702, 0x1200110c, 0x642855ad, // pl.lv.sk_543 it.bs.af_222 ro.hu.un_530 rw.sw.lg_643
+ 0x100419ad, 0x53321708, 0x35282155, 0x13000d0e, // gl.fi.lt_643 sr.bs.ht_443 jw.sw.zu_442 ne.bh.un_550
+ 0x23002521, 0x6b002a05, 0x13002a02, 0x0b0f1012, // eu.ca.un_860 mt.ceb.un_330 mt.et.un_220 lt.lv.es_654
+ 0x12640713, 0x12000913, 0x190b0c05, 0x23062560, // it.lg.hu_665 pl.hu.un_650 sv.es.gl_333 eu.de.ca_664
+ // [29c0]
+ 0x640503ee, 0x4a2155a0, 0x3f006e08, 0x0b190a04, // nl.fr.lg_422 rw.jw.yo_322 hmn.af.un_430 pt.gl.es_332
+ 0x0e001902, 0x1b00210c, 0x0c000208, 0x0f000d12, // gl.is.un_220 jw.tr.un_530 da.sv.un_430 cs.lv.un_640
+ 0x0b25190c, 0x040811a9, 0x03010602, 0x2a004a05, // gl.eu.es_543 ro.uk.ru_544 de.en.nl_222 yo.mt.un_330
+ 0x0e042aa0, 0x255228ad, 0x040e4a12, 0x2b0801ad, // mt.fi.is_322 sw.ha.eu_643 yo.is.fi_654 en.no.vi_643
+ // [29d0]
+ 0x0c0801a7, 0x07001f0c, 0x3f031f0d, 0x2000680e, // en.no.sv_532 cy.it.un_530 cy.nl.af_554 ig.sq.un_550
+ 0x6b113fa0, 0x5300050e, 0x06205505, 0x040a11ec, // af.ro.ceb_322 fr.ht.un_550 rw.sq.de_333 ro.mk.ru_644
+ 0x06003f02, 0x036e13ad, 0x35005521, 0x0507030b, // af.de.un_220 et.hmn.nl_643 rw.zu.un_860 nl.it.fr_542
+ 0x0c001f08, 0x06040c08, 0x18001119, 0x2008520c, // cy.sv.un_430 sv.fi.de_443 ro.ga.un_750 ha.no.sq_543
+ // [29e0]
+ 0x1008040e, 0x2d0d1faf, 0x351a6b08, 0x3216290d, // ru.uk.be_555 cy.cs.sk_655 ceb.tl.zu_443 sl.hr.bs_554
+ 0x0c006412, 0x232b19ee, 0x28522a0c, 0x08526407, // lg.sv.un_640 gl.vi.ca_422 mt.ha.sw_543 lg.ha.no_432
+ 0x2a131ea4, 0x192a1ea0, 0x0b003521, 0x350b6ba9, // ms.et.mt_433 ms.mt.gl_322 zu.es.un_860 ceb.es.zu_544
+ 0x19020b08, 0x06000c1b, 0x18252705, 0x4a55640d, // es.da.gl_443 sv.de.un_770 gd.eu.ga_333 lg.rw.yo_554
+ // [29f0]
+ 0x211c6409, 0x041011ee, 0x081207a7, 0x0e642504, // lg.id.jw_444 ro.be.ru_422 it.hu.no_532 eu.lg.is_332
+ 0x0d122d0c, 0x3b2a10a9, 0x0328350c, 0x556435ec, // sk.hu.cs_543 lt.mt.so_544 zu.sw.nl_543 zu.lg.rw_644
+ 0x3b000a04, 0x64531008, 0x130e4a04, 0x10001c08, // pt.so.un_320 lt.ht.lg_443 yo.is.et_332 id.lt.un_430
+ 0x0805115a, 0x170d2d0c, 0x212a01ac, 0x13110ca4, // ro.fr.no_553 sk.cs.sr_543 en.mt.jw_632 sv.ro.et_433
+ // [2a00]
+ 0x116435a9, 0x13211ea4, 0x25004a19, 0x3f2808a0, // zu.lg.ro_544 ms.jw.et_433 yo.eu.un_750 no.sw.af_322
+ 0x01212da0, 0x13321602, 0x01003b13, 0x0c2d0d08, // sk.jw.en_322 hr.bs.et_222 so.en.un_650 cs.sk.sv_443
+ 0x4a002a13, 0x11000c07, 0x292d25a7, 0x0a000b07, // mt.yo.un_650 sv.ro.un_420 eu.sk.sl_532 es.pt.un_420
+ 0x325535a4, 0x100f3f05, 0x04001721, 0x3b001b19, // zu.rw.bs_433 af.lv.lt_333 sr.ru.un_860 tr.so.un_750
+ // [2a10]
+ 0x6b0f13a0, 0x013f28a0, 0x13001021, 0x10000307, // et.lv.ceb_322 sw.af.en_322 lt.et.un_860 nl.lt.un_420
+ 0x020b0a0d, 0x0a0717ad, 0x3104010c, 0x6b5305ec, // pt.es.da_554 sr.bg.mk_643 en.fi.az_543 fr.ht.ceb_644
+ 0x1c000d22, 0x353b2808, 0x04251008, 0x101302a7, // ne.mr.un_870 sw.so.zu_443 lt.eu.fi_443 da.et.lt_532
+ 0x0e021307, 0x0e000608, 0x2b003b09, 0x3b001314, // et.da.is_432 de.is.un_430 so.vi.un_440 et.so.un_660
+ // [2a20]
+ 0x03643b08, 0x4a002102, 0x1a3b6b12, 0x016b6804, // so.lg.nl_443 jw.yo.un_220 ceb.so.tl_654 ig.ceb.en_332
+ 0x13100fad, 0x0d00092c, 0x0f0313a6, 0x35043f04, // lv.lt.et_643 hi.ne.un_990 et.nl.lv_521 af.fi.zu_332
+ 0x03133ba4, 0x0c040eee, 0x32173fee, 0x0d1c0955, // so.et.nl_433 is.fi.sv_422 af.sr.bs_422 hi.mr.ne_442
+ 0x55126409, 0x08111013, 0x2d0c1fa4, 0x3f031aee, // lg.hu.rw_444 be.ro.uk_665 cy.sv.sk_433 tl.nl.af_422
+ // [2a30]
+ 0x02006e04, 0x2d0c09ec, 0x3f3b035a, 0x0c003f04, // hmn.da.un_320 pl.sv.sk_644 nl.so.af_553 af.sv.un_320
+ 0x12003205, 0x2a0129a6, 0x3f07230c, 0x31130407, // bs.hu.un_330 sl.en.mt_521 ca.it.af_543 fi.et.az_432
+ 0x033f13a4, 0x6b0523a0, 0x0b03050c, 0x6800551b, // et.af.nl_433 ca.fr.ceb_322 fr.nl.es_543 rw.ig.un_770
+ 0x3f00131a, 0x04020812, 0x0b0a190c, 0x68002d05, // et.af.un_760 no.da.fi_654 gl.pt.es_543 sk.ig.un_330
+ // [2a40]
+ 0x11100107, 0x2a00680e, 0x162d29a0, 0x10190b13, // en.lt.ro_432 ig.mt.un_550 sl.sk.hr_322 es.gl.lt_665
+ 0x046b25a0, 0x275231a4, 0x081104ec, 0x0d1c13ee, // eu.ceb.fi_322 az.ha.gd_433 ru.ro.uk_644 bh.mr.ne_422
+ 0x19001008, 0x12060407, 0x2307125a, 0x0a0701a9, // lt.gl.un_430 fi.de.hu_432 hu.it.ca_553 en.it.pt_544
+ 0x0c0e08a0, 0x2a000713, 0x18000a08, 0x08020e05, // no.is.sv_322 it.mt.un_650 pt.ga.un_430 is.da.no_333
+ // [2a50]
+ 0x100b0a08, 0x2d0d060e, 0x0300020c, 0x030c06a0, // pt.es.lt_443 de.cs.sk_555 da.nl.un_530 de.sv.nl_322
+ 0x13090d0e, 0x121b2512, 0x23050313, 0x133f3b07, // ne.hi.bh_555 eu.tr.hu_654 nl.fr.ca_665 so.af.et_432
+ 0x2b3b010c, 0x0f041355, 0x0e2d0daf, 0x52102814, // en.so.vi_543 et.fi.lv_442 cs.sk.is_655 sw.lt.ha_666
+ 0x10000a09, 0x321755af, 0x0f100807, 0x521a210b, // pt.lt.un_440 rw.sr.bs_655 no.lt.lv_432 jw.tl.ha_542
+ // [2a60]
+ 0x0e100f0c, 0x131011a4, 0x0a1a010c, 0x190a0b0e, // lv.lt.is_543 ro.lt.et_433 en.tl.pt_543 es.pt.gl_555
+ 0x25000f2b, 0x0c033fee, 0x08026ba4, 0x1e3f25ec, // lv.eu.un_980 af.nl.sv_422 ceb.da.no_433 eu.af.ms_644
+ 0x10252313, 0x0e1a6b12, 0x2a0810a4, 0x25000f1a, // ca.eu.lt_665 ceb.tl.is_654 lt.no.mt_433 lv.eu.un_760
+ 0x2b190b09, 0x271806a4, 0x04003f07, 0x2723010c, // es.gl.vi_444 de.ga.gd_433 af.fi.un_420 en.ca.gd_543
+ // [2a70]
+ 0x0a000108, 0x0807110c, 0x53211ca9, 0x31202a0c, // en.pt.un_430 ro.bg.uk_543 id.jw.ht_544 mt.sq.az_543
+ 0x3b061f09, 0x2864555a, 0x25646b0c, 0x062552ec, // cy.de.so_444 rw.lg.sw_553 ceb.lg.eu_543 ha.eu.de_644
+ 0x3f230607, 0x521b2a0c, 0x01531e02, 0x3f230505, // de.ca.af_432 mt.tr.ha_543 ms.ht.en_222 fr.ca.af_333
+ 0x0e06075a, 0x1b21550c, 0x12080e13, 0x05645307, // it.de.is_553 rw.jw.tr_543 is.no.hu_665 ht.lg.fr_432
+ // [2a80]
+ 0x1a1b64a7, 0x3f321604, 0x0c1329a4, 0x13002702, // lg.tr.tl_532 hr.bs.af_332 sl.et.sv_433 gd.et.un_220
+ 0x21002522, 0x131c0d12, 0x1e1c68ec, 0x091155a7, // eu.jw.un_870 ne.mr.bh_654 ig.id.ms_644 rw.ro.pl_532
+ 0x21646bad, 0x210609a9, 0x5255640c, 0x3b13120d, // ceb.lg.jw_643 pl.de.jw_544 lg.rw.ha_543 hu.et.so_554
+ 0x0e190b5a, 0x35556407, 0x3b282507, 0x55091aee, // es.gl.is_553 lg.rw.zu_432 eu.sw.so_432 tl.pl.rw_422
+ // [2a90]
+ 0x0d001702, 0x122164a9, 0x23000113, 0x55521ea4, // sr.cs.un_220 lg.jw.hu_544 en.ca.un_650 ms.ha.rw_433
+ 0x12012307, 0x6b230108, 0x12006404, 0x060325a7, // ca.en.hu_432 en.ca.ceb_443 lg.hu.un_320 eu.nl.de_532
+ 0x0a00041b, 0x120123ee, 0x050123a7, 0x0f0410a4, // ru.mk.un_770 ca.en.hu_422 ca.en.fr_532 lt.fi.lv_433
+ 0x110f1055, 0x1f00170c, 0x3f2d250c, 0x0803010c, // lt.lv.ro_442 sr.cy.un_530 eu.sk.af_543 en.nl.no_543
+ // [2aa0]
+ 0x00002803, 0x04252105, 0x08100f05, 0x1600020c, // sw.un.un_300 jw.eu.fi_333 lv.lt.no_333 da.hr.un_530
+ 0x230b11a4, 0x1e1c6ea4, 0x20356404, 0x0e060f13, // ro.es.ca_433 hmn.id.ms_433 lg.zu.sq_332 lv.de.is_665
+ 0x12000d18, 0x25132355, 0x55182704, 0x12006419, // cs.hu.un_740 ca.et.eu_442 gd.ga.rw_332 lg.hu.un_750
+ 0x25001a02, 0x080e0207, 0x12211c02, 0x250711a7, // tl.eu.un_220 da.is.no_432 id.jw.hu_222 ro.it.eu_532
+ // [2ab0]
+ 0x1f000a04, 0x08051e04, 0x64006802, 0x6b012307, // pt.cy.un_320 ms.fr.no_332 ig.lg.un_220 ca.en.ceb_432
+ 0x1c1e2a08, 0x19000a02, 0x060f10ad, 0x091c0d02, // mt.ms.id_443 pt.gl.un_220 lt.lv.de_643 ne.mr.hi_222
+ 0x12641aa0, 0x2300050e, 0x0b2d0207, 0x6b1a0212, // tl.lg.hu_322 fr.ca.un_550 da.sk.es_432 da.tl.ceb_654
+ 0x01036ba4, 0x0e04130c, 0x283b55ad, 0x030113a7, // ceb.nl.en_433 et.fi.is_543 rw.so.sw_643 et.en.nl_532
+ // [2ac0]
+ 0x0b1827ad, 0x191c0aee, 0x0d091c0e, 0x1e00550e, // gd.ga.es_643 pt.id.gl_422 mr.hi.ne_555 rw.ms.un_550
+ 0x64555313, 0x6b1a53a9, 0x1f0729ee, 0x2d1b3111, // ht.rw.lg_665 ht.tl.ceb_544 sl.it.cy_422 az.tr.sk_653
+ 0x351a6bad, 0x5568350e, 0x5500522a, 0x3f5364ad, // ceb.tl.zu_643 zu.ig.rw_555 ha.rw.un_970 lg.ht.af_643
+ 0x062718a0, 0x1b6b53a6, 0x53351c0c, 0x1200641b, // ga.gd.de_322 ht.ceb.tr_521 id.zu.ht_543 lg.hu.un_770
+ // [2ad0]
+ 0x05271808, 0x1f000104, 0x284a3ba7, 0x0e00200d, // ga.gd.fr_443 en.cy.un_320 so.yo.sw_532 sq.is.un_540
+ 0x0e072a08, 0x05002109, 0x1c006b04, 0x1e1c04ec, // mt.it.is_443 jw.fr.un_440 ceb.id.un_320 fi.id.ms_644
+ 0x16004a07, 0x3b6b1a09, 0x3f030408, 0x133f040c, // yo.hr.un_420 tl.ceb.so_444 fi.nl.af_443 fi.af.et_543
+ 0x3b55520e, 0x170f13ee, 0x1b31520d, 0x551b530e, // ha.rw.so_555 et.lv.sr_422 ha.az.tr_554 ht.tr.rw_555
+ // [2ae0]
+ 0x533564ee, 0x212d1107, 0x042153a4, 0x2b00680d, // lg.zu.ht_422 ro.sk.jw_432 ht.jw.fi_433 ig.vi.un_540
+ 0x0f092aa4, 0x55356413, 0x645352a9, 0x20005322, // mt.pl.lv_433 lg.zu.rw_665 ha.ht.lg_544 ht.sq.un_870
+ 0x0c2168a0, 0x0100050e, 0x13006b0e, 0x2d0d04a4, // ig.jw.sv_322 fr.en.un_550 ceb.et.un_550 fi.cs.sk_433
+ 0x03080205, 0x170a040b, 0x080a0713, 0x08003f13, // da.no.nl_333 ru.mk.sr_542 bg.mk.uk_665 af.no.un_650
+ // [2af0]
+ 0x112d0d05, 0x1f006413, 0x1b006821, 0x04321702, // cs.sk.ro_333 lg.cy.un_650 ig.tr.un_860 sr.bs.fi_222
+ 0x071101a4, 0x11001c0d, 0x2b003504, 0x685228ec, // en.ro.it_433 id.ro.un_540 zu.vi.un_320 sw.ha.ig_644
+ 0x192528ad, 0x0c001f05, 0x0c051fee, 0x0400102b, // sw.eu.gl_643 cy.sv.un_330 cy.fr.sv_422 be.ru.un_980
+ 0x536b2012, 0x0e0521a0, 0x01002429, 0x2d1e1c08, // sq.ceb.ht_654 jw.fr.is_322 yi.iw.un_960 id.ms.sk_443
+ // [2b00]
+ 0x203552ad, 0x3f1c3105, 0x01001f08, 0x0a2a520c, // ha.zu.sq_643 az.id.af_333 cy.en.un_430 ha.mt.pt_543
+ 0x4a1152a4, 0x3f006b0c, 0x0a005204, 0x282008a0, // ha.ro.yo_433 ceb.af.un_530 ha.pt.un_320 no.sq.sw_322
+ 0x1b213508, 0x321629af, 0x0e3506a9, 0x312018a7, // zu.jw.tr_443 sl.hr.bs_655 de.zu.is_544 ga.sq.az_532
+ 0x0300200e, 0x0c000e0d, 0x130e1855, 0x2100201b, // sq.nl.un_550 is.sv.un_540 ga.is.et_442 sq.jw.un_770
+ // [2b10]
+ 0x23070b02, 0x1c002119, 0x1c090d0d, 0x3f0306ac, // es.it.ca_222 jw.id.un_750 ne.hi.mr_554 de.nl.af_632
+ 0x2b0601a6, 0x251e1c13, 0x2800350c, 0x033f1bee, // en.de.vi_521 id.ms.eu_665 zu.sw.un_530 tr.af.nl_422
+ 0x2a1a6b12, 0x291304ad, 0x200401a4, 0x04001c0d, // ceb.tl.mt_654 fi.et.sl_643 en.fi.sq_433 id.fi.un_540
+ 0x2a6b20ee, 0x17005512, 0x0f04130d, 0x17321611, // sq.ceb.mt_422 rw.sr.un_640 et.fi.lv_554 hr.bs.sr_653
+ // [2b20]
+ 0x190b4a05, 0x05040709, 0x05002902, 0x53130808, // yo.es.gl_333 it.fi.fr_444 sl.fr.un_220 no.et.ht_443
+ 0x0800121b, 0x1e1c4aa4, 0x06010407, 0x050704a9, // hu.no.un_770 yo.id.ms_433 fi.en.de_432 fi.it.fr_544
+ 0x21081ca4, 0x1f000613, 0x3b205212, 0x1207045a, // id.no.jw_433 de.cy.un_650 ha.sq.so_654 fi.it.hu_553
+ 0x17100805, 0x1c001321, 0x200852a0, 0x0d641308, // uk.be.sr_333 bh.mr.un_860 ha.no.sq_322 et.lg.cs_443
+ // [2b30]
+ 0x20106804, 0x29004a0d, 0x11202aa4, 0x12070408, // ig.lt.sq_332 yo.sl.un_540 mt.sq.ro_433 fi.it.hu_443
+ 0x135525af, 0x21005514, 0x1c211ea4, 0x521b3109, // eu.rw.et_655 rw.jw.un_660 ms.jw.id_433 az.tr.ha_444
+ 0x12000721, 0x64552555, 0x06001308, 0x06071b04, // it.hu.un_860 eu.rw.lg_442 et.de.un_430 tr.it.de_332
+ 0x05000722, 0x1b001f19, 0x13041aa4, 0x04071007, // it.fr.un_870 cy.tr.un_750 tl.fi.et_433 be.bg.ru_432
+ // [2b40]
+ 0x04004a05, 0x1b211e0c, 0x1b002019, 0x10002512, // yo.fi.un_330 ms.jw.tr_543 sq.tr.un_750 eu.lt.un_640
+ 0x05641b07, 0x311a6bec, 0x041207a4, 0x2a063fa4, // tr.lg.fr_432 ceb.tl.az_644 it.hu.fi_433 af.de.mt_433
+ 0x6b081a0d, 0x071204ec, 0x12000814, 0x35003112, // tl.no.ceb_554 fi.hu.it_644 no.hu.un_660 az.zu.un_640
+ 0x2a000c09, 0x013b2005, 0x072a08a0, 0x032d0d05, // sv.mt.un_440 sq.so.en_333 no.mt.it_322 cs.sk.nl_333
+ // [2b50]
+ 0x081e25a4, 0x130d0909, 0x2a040709, 0x32290908, // eu.ms.no_433 hi.ne.bh_444 it.fi.mt_444 pl.sl.bs_443
+ 0x061331a0, 0x1e68200c, 0x3f04130b, 0x3b4a290c, // az.et.de_322 sq.ig.ms_543 et.fi.af_542 sl.yo.so_543
+ 0x2a070214, 0x07000a0b, 0x20311bec, 0x31005211, // da.it.mt_666 mk.bg.un_520 tr.az.sq_644 ha.az.un_630
+ 0x19005207, 0x082a4a04, 0x2a0705ad, 0x53004a04, // ha.gl.un_420 yo.mt.no_332 fr.it.mt_643 yo.ht.un_320
+ // [2b60]
+ 0x233f0207, 0x03080ca4, 0x1c0d13a4, 0x27050da0, // da.af.ca_432 sv.no.nl_433 bh.ne.mr_433 cs.fr.gd_322
+ 0x061b12a4, 0x08021ba0, 0x204a27ec, 0x06002102, // hu.tr.de_433 tr.da.no_322 gd.yo.sq_644 jw.de.un_220
+ 0x6b066ea0, 0x20003b04, 0x4a250504, 0x1a6b55ee, // hmn.de.ceb_322 so.sq.un_320 fr.eu.yo_332 rw.ceb.tl_422
+ 0x3b00110c, 0x0b051904, 0x04080507, 0x2a0412ec, // ro.so.un_530 gl.fr.es_332 fr.no.fi_432 hu.fi.mt_644
+ // [2b70]
+ 0x1708045a, 0x52313b0c, 0x0a190b13, 0x523b2113, // ru.uk.sr_553 so.az.ha_543 es.gl.pt_665 jw.so.ha_665
+ 0x68002b21, 0x32291712, 0x2a001b0c, 0x31521aa9, // vi.ig.un_860 sr.sl.bs_654 tr.mt.un_530 tl.ha.az_544
+ 0x2d001c02, 0x216b0a07, 0x230f1304, 0x3b1a52a9, // id.sk.un_220 pt.ceb.jw_432 et.lv.ca_332 ha.tl.so_544
+ 0x1c211aa9, 0x5321520c, 0x18001204, 0x0c0a08ee, // tl.jw.id_544 ha.jw.ht_543 ur.ar.un_320 no.pt.sv_422
+ // [2b80]
+ 0x52003b1a, 0x4a643505, 0x31523b0c, 0x17164a0c, // so.ha.un_760 zu.lg.yo_333 so.ha.az_543 yo.hr.sr_543
+ 0x282b04ec, 0x313b52ec, 0x5221310c, 0x2d00040c, // fi.vi.sw_644 ha.so.az_644 az.jw.ha_543 fi.sk.un_530
+ 0x2b04280c, 0x4a3b5208, 0x2b000421, 0x111f070c, // sw.fi.vi_543 ha.so.yo_443 fi.vi.un_860 it.cy.ro_543
+ 0x0e1308a4, 0x07001a23, 0x530e1ca4, 0x355231a4, // no.et.is_433 tl.it.un_880 id.is.ht_433 az.ha.zu_433
+ // [2b90]
+ 0x06053f04, 0x10002b1a, 0x0400050c, 0x206b070c, // af.fr.de_332 vi.lt.un_760 fr.fi.un_530 it.ceb.sq_543
+ 0x110410a4, 0x31003b19, 0x091f52a4, 0x09075508, // be.ru.ro_433 so.az.un_750 ha.cy.pl_433 rw.it.pl_443
+ 0x0704115a, 0x531b3109, 0x0807110e, 0x3b005519, // ro.ru.bg_553 az.tr.ht_444 ro.bg.uk_555 rw.so.un_750
+ 0x08001a0b, 0x3b1a31ec, 0x08530408, 0x01681f04, // tl.no.un_520 az.tl.so_644 fi.ht.no_443 cy.ig.en_332
+ // [2ba0]
+ 0x13000d2a, 0x68280755, 0x042b0712, 0x0f521355, // ne.bh.un_970 it.sw.ig_442 it.vi.fi_654 et.ha.lv_442
+ 0x2b000419, 0x020e08ad, 0x28042b13, 0x17000a14, // fi.vi.un_750 no.is.da_643 vi.fi.sw_665 mk.sr.un_660
+ 0x133b06a7, 0x06003f0e, 0x06180e04, 0x311e1c5a, // de.so.et_532 af.de.un_550 is.ga.de_332 id.ms.az_553
+ 0x2917070c, 0x21101fee, 0x642809ad, 0x0a0728ad, // it.sr.sl_543 cy.lt.jw_422 pl.sw.lg_643 sw.it.pt_643
+ // [2bb0]
+ 0x23200a14, 0x2a0f52ec, 0x27060207, 0x00000a37, // pt.sq.ca_666 ha.lv.mt_644 da.de.gd_432 pt.un.un_B00
+ 0x0a190b12, 0x1a0755a4, 0x0b09060c, 0x28005205, // es.gl.pt_654 rw.it.tl_433 de.pl.es_543 ha.sw.un_330
+ 0x1e1c100c, 0x1a126ba7, 0x521206ad, 0x2d0a23ee, // lt.id.ms_543 ceb.hu.tl_532 de.hu.ha_643 ca.pt.sk_422
+ 0x0103250c, 0x0f0a4aa0, 0x18001f1a, 0x1e3b04ee, // eu.nl.en_543 yo.pt.lv_322 cy.ga.un_760 fi.so.ms_422
+ // [2bc0]
+ 0x35000d04, 0x07041f5a, 0x2100550c, 0x1a0f35ad, // cs.zu.un_320 cy.fi.it_553 rw.jw.un_530 zu.lv.tl_643
+ 0x29170aaf, 0x080204a4, 0x1e006b07, 0x35006b21, // pt.sr.sl_655 fi.da.no_433 ceb.ms.un_420 ceb.zu.un_860
+ 0x17002921, 0x52311ea4, 0x081004af, 0x1e001904, // sl.sr.un_860 ms.az.ha_433 ru.be.uk_655 gl.ms.un_320
+ 0x28000805, 0x082b010c, 0x19070aa0, 0x190b200c, // no.sw.un_330 en.vi.no_543 pt.it.gl_322 sq.es.gl_543
+ // [2bd0]
+ 0x02002913, 0x3b2355a0, 0x10002821, 0x686b1a0d, // sl.da.un_650 rw.ca.so_322 sw.lt.un_860 tl.ceb.ig_554
+ 0x6400552a, 0x01002307, 0x066e0405, 0x35005507, // rw.lg.un_970 ca.en.un_420 fi.hmn.de_333 rw.zu.un_420
+ 0x251e1ca7, 0x1708100c, 0x0e002108, 0x0a135207, // id.ms.eu_532 be.uk.sr_543 jw.is.un_430 ha.et.pt_432
+ 0x6e001c13, 0x130823a9, 0x12001e0d, 0x07190f04, // id.hmn.un_650 ca.no.et_544 ms.hu.un_540 lv.gl.it_332
+ // [2be0]
+ 0x52042008, 0x190a23af, 0x292555a9, 0x551c1e11, // sq.fi.ha_443 ca.pt.gl_655 rw.eu.sl_544 ms.id.rw_653
+ 0x21003b22, 0x6b556412, 0x04002022, 0x3b293204, // so.jw.un_870 lg.rw.ceb_654 sq.fi.un_870 bs.sl.so_332
+ 0x64002922, 0x09001008, 0x321716ee, 0x29200d0c, // sl.lg.un_870 lt.pl.un_430 hr.sr.bs_422 cs.sq.sl_543
+ 0x090501a4, 0x080e040c, 0x1f001008, 0x090d13ad, // en.fr.pl_433 fi.is.no_543 lt.cy.un_430 bh.ne.hi_643
+ // [2bf0]
+ 0x01001f02, 0x17114aa0, 0x351e1c04, 0x07170a0b, // cy.en.un_220 yo.ro.sr_322 id.ms.zu_332 mk.sr.bg_542
+ 0x1f006e0c, 0x253216ee, 0x520412a9, 0x0f1b1307, // hmn.cy.un_530 hr.bs.eu_422 hu.fi.ha_544 et.tr.lv_432
+ 0x2d0d16a4, 0x08061f0c, 0x25002314, 0x21002329, // hr.cs.sk_433 cy.de.no_543 ca.eu.un_660 ca.jw.un_960
+ 0x0a1704a7, 0x0300252a, 0x2a002321, 0x1f183b0c, // ru.sr.mk_532 eu.nl.un_970 ca.mt.un_860 so.ga.cy_543
+
+ // [2c00]
+ 0x29000307, 0x35005502, 0x1b05010c, 0x23002513, // nl.sl.un_420 rw.zu.un_220 en.fr.tr_543 eu.ca.un_650
+ 0x0b000108, 0x3b003511, 0x010b18ee, 0x2800680e, // en.es.un_430 zu.so.un_630 ga.es.en_422 ig.sw.un_550
+ 0x3200170c, 0x1e1c13af, 0x04080e05, 0x35096412, // sr.bs.un_530 et.id.ms_655 is.no.fi_333 lg.pl.zu_654
+ 0x13100412, 0x1328030b, 0x03040ca4, 0x0b3b28af, // fi.lt.et_654 nl.sw.et_542 sv.fi.nl_433 sw.so.es_655
+ // [2c10]
+ 0x070817ee, 0x21001a13, 0x280364ee, 0x21203fa0, // sr.uk.bg_422 tl.jw.un_650 lg.nl.sw_422 af.sq.jw_322
+ 0x55286404, 0x3f000c07, 0x1a354a07, 0x21001e07, // lg.sw.rw_332 sv.af.un_420 yo.zu.tl_432 ms.jw.un_420
+ 0x55006807, 0x03006402, 0x03061b07, 0x0d2d190d, // ig.rw.un_420 lg.nl.un_220 tr.de.nl_432 gl.sk.cs_554
+ 0x1a3b4a0b, 0x13080c04, 0x534a52a0, 0x521a1e07, // yo.so.tl_542 sv.no.et_332 ha.yo.ht_322 ms.tl.ha_432
+ // [2c20]
+ 0x312a3b0d, 0x13000118, 0x0c006b07, 0x033f530c, // so.mt.az_554 en.et.un_740 ceb.sv.un_420 ht.af.nl_543
+ 0x10040a05, 0x1a4a52a6, 0x1e1c2d02, 0x3f040ea0, // mk.ru.be_333 ha.yo.tl_521 sk.id.ms_222 is.fi.af_322
+ 0x10000f09, 0x52003104, 0x091c13af, 0x04001329, // lv.lt.un_440 az.ha.un_320 bh.mr.hi_655 et.fi.un_960
+ 0x190b0fee, 0x09000c07, 0x3255350b, 0x13030407, // lv.es.gl_422 sv.pl.un_420 zu.rw.bs_542 fi.nl.et_432
+ // [2c30]
+ 0x1e1c5214, 0x3b136e05, 0x06201ea4, 0x080211ec, // ha.id.ms_666 hmn.et.so_333 ms.sq.de_433 ro.da.no_644
+ 0x27181f05, 0x0c0827a4, 0x281e3bac, 0x13190a13, // cy.ga.gd_333 gd.no.sv_433 so.ms.sw_632 pt.gl.et_665
+ 0x08170413, 0x2d000614, 0x080e6ba0, 0x070810a0, // ru.sr.uk_665 de.sk.un_660 ceb.is.no_322 be.uk.bg_322
+ 0x4a685208, 0x04060cee, 0x036b68ec, 0x4a6413ec, // ha.ig.yo_443 sv.de.fi_422 ig.ceb.nl_644 et.lg.yo_644
+ // [2c40]
+ 0x64552114, 0x522a4a55, 0x053f230d, 0x642713a9, // jw.rw.lg_666 yo.mt.ha_442 ca.af.fr_554 et.gd.lg_544
+ 0x6800031a, 0x641c2104, 0x1c0d13ec, 0x08026b02, // nl.ig.un_760 jw.id.lg_332 bh.ne.mr_644 ceb.da.no_222
+ 0x0c101f07, 0x2b121ca0, 0x0e100855, 0x6e1f53ec, // cy.lt.sv_432 id.hu.vi_322 no.lt.is_442 ht.cy.hmn_644
+ 0x316407a9, 0x16190a55, 0x1e003119, 0x1c130daf, // it.lg.az_544 pt.gl.hr_442 az.ms.un_750 ne.bh.mr_655
+ // [2c50]
+ 0x1b1c1e07, 0x0664035a, 0x033f0414, 0x2a0a1802, // ms.id.tr_432 nl.lg.de_553 fi.af.nl_666 ga.pt.mt_222
+ 0x28003b20, 0x090b3507, 0x1a4a6b02, 0x1e1b2aee, // so.sw.un_850 zu.es.pl_432 ceb.yo.tl_222 mt.tr.ms_422
+ 0x0b283512, 0x190a1105, 0x0704180c, 0x6b000704, // zu.sw.es_654 ro.pt.gl_333 ga.fi.it_543 it.ceb.un_320
+ 0x0206085a, 0x2d321605, 0x23000a12, 0x3f0e080b, // no.de.da_553 hr.bs.sk_333 pt.ca.un_640 no.is.af_542
+ // [2c60]
+ 0x080210a4, 0x27646ba7, 0x0f001019, 0x286b52ee, // lt.da.no_433 ceb.lg.gd_532 lt.lv.un_750 ha.ceb.sw_422
+ 0x16352d55, 0x17003504, 0x11002719, 0x3f03040c, // sk.zu.hr_442 zu.sr.un_320 gd.ro.un_750 fi.nl.af_543
+ 0x2800552a, 0x04102511, 0x04000f04, 0x64000413, // rw.sw.un_970 eu.lt.fi_653 lv.fi.un_320 fi.lg.un_650
+ 0x18001905, 0x536b0102, 0x134a350c, 0x29000713, // gl.ga.un_330 en.ceb.ht_222 zu.yo.et_543 it.sl.un_650
+ // [2c70]
+ 0x643f0305, 0x3f3b4aa0, 0x09100412, 0x1e1c6ba0, // nl.af.lg_333 yo.so.af_322 fi.lt.pl_654 ceb.id.ms_322
+ 0x13271104, 0x6b181108, 0x02006b0d, 0x13000b07, // ro.gd.et_332 ro.ga.ceb_443 ceb.da.un_540 es.et.un_420
+ 0x190b28ec, 0x13091cad, 0x55280ba4, 0x35060eaf, // sw.es.gl_644 mr.hi.bh_643 es.sw.rw_433 is.de.zu_655
+ 0x1b230aee, 0x6400031b, 0x04006422, 0x5321070c, // pt.ca.tr_422 nl.lg.un_770 lg.fi.un_870 it.jw.ht_543
+ // [2c80]
+ 0x3b060ea4, 0x1107080c, 0x21054a04, 0x11001607, // is.de.so_433 uk.bg.ro_543 yo.fr.jw_332 hr.ro.un_420
+ 0x64130412, 0x64046804, 0x12311b55, 0x19001f1b, // fi.et.lg_654 ig.fi.lg_332 tr.az.hu_442 cy.gl.un_770
+ 0x35001904, 0x31000308, 0x18004a07, 0x53000517, // gl.zu.un_320 nl.az.un_430 yo.ga.un_420 fr.ht.un_730
+ 0x2500531a, 0x1c001a02, 0x321710a4, 0x08021902, // ht.eu.un_760 tl.id.un_220 lt.sr.bs_433 gl.da.no_222
+ // [2c90]
+ 0x2d0d16a0, 0x64002820, 0x190b23a4, 0x320a0304, // hr.cs.sk_322 sw.lg.un_850 ca.es.gl_433 nl.pt.bs_332
+ 0x090627a7, 0x0a001605, 0x041a6ba0, 0x1c1e0655, // gd.de.pl_532 hr.pt.un_330 ceb.tl.fi_322 de.ms.id_442
+ 0x4a00250e, 0x181127a4, 0x35004a0d, 0x11270108, // eu.yo.un_550 gd.ro.ga_433 yo.zu.un_540 en.gd.ro_443
+ 0x6b190b0d, 0x1c001f05, 0x231811a7, 0x0a041055, // es.gl.ceb_554 cy.id.un_330 ro.ga.ca_532 be.ru.mk_442
+ // [2ca0]
+ 0x020c080c, 0x016b18ee, 0x08033f0c, 0x073f0308, // no.sv.da_543 ga.ceb.en_422 af.nl.no_543 nl.af.it_443
+ 0x0500111a, 0x2d180da4, 0x64001104, 0x21000d04, // ro.fr.un_760 cs.ga.sk_433 ro.lg.un_320 cs.jw.un_320
+ 0x032a2013, 0x0e1f640d, 0x056b5204, 0x072710ad, // sq.mt.nl_665 lg.cy.is_554 ha.ceb.fr_332 lt.gd.it_643
+ 0x05042005, 0x016b6ea0, 0x05041307, 0x080a17ad, // sq.fi.fr_333 hmn.ceb.en_322 et.fi.fr_432 sr.mk.uk_643
+ // [2cb0]
+ 0x31041312, 0x21005304, 0x01530c09, 0x0c061313, // et.fi.az_654 ht.jw.un_320 sv.ht.en_444 et.de.sv_665
+ 0x13251107, 0x31020807, 0x09001602, 0x1300070e, // ro.eu.et_432 no.da.az_432 hr.pl.un_220 it.et.un_550
+ 0x1813040c, 0x04182708, 0x0f254aec, 0x3f000b02, // fi.et.ga_543 gd.ga.fi_443 yo.eu.lv_644 es.af.un_220
+ 0x11001b13, 0x07033f13, 0x29032da9, 0x063f230c, // tr.ro.un_650 af.nl.it_665 sk.nl.sl_544 ca.af.de_543
+ // [2cc0]
+ 0x2829130b, 0x0e3f06a4, 0x190e0107, 0x553528af, // et.sl.sw_542 de.af.is_433 en.is.gl_432 sw.zu.rw_655
+ 0x08001807, 0x13001914, 0x01043b04, 0x2a002908, // ga.no.un_420 gl.et.un_660 so.fi.en_332 sl.mt.un_430
+ 0x3200290c, 0x0f1e3f0c, 0x2900551b, 0x050a23ec, // sl.bs.un_530 af.ms.lv_543 rw.sl.un_770 ca.pt.fr_644
+ 0x2a031f07, 0x3b000b05, 0x08041fec, 0x553b100b, // cy.nl.mt_432 es.so.un_330 cy.fi.no_644 lt.so.rw_542
+ // [2cd0]
+ 0x05000a21, 0x0810170c, 0x055255a4, 0x4a6b6813, // pt.fr.un_860 sr.be.uk_543 rw.ha.fr_433 ig.ceb.yo_665
+ 0x08021fa0, 0x29090fa4, 0x05001005, 0x080225a0, // cy.da.no_322 lv.pl.sl_433 lt.fr.un_330 eu.da.no_322
+ 0x0e002922, 0x0f3b550b, 0x5529130c, 0x16000804, // sl.is.un_870 rw.so.lv_542 et.sl.rw_543 no.hr.un_320
+ 0x07104a0c, 0x0000132d, 0x6e04680c, 0x13552908, // yo.lt.it_543 bh.un.un_A00 ig.fi.hmn_543 sl.rw.et_443
+ // [2ce0]
+ 0x0500530e, 0x20005308, 0x04032aee, 0x16005507, // ht.fr.un_550 ht.sq.un_430 mt.nl.fi_422 rw.hr.un_420
+ 0x09001604, 0x190527ec, 0x0c12060c, 0x13003f02, // hr.pl.un_320 gd.fr.gl_644 de.hu.sv_543 af.et.un_220
+ 0x04074a0c, 0x190a0414, 0x036404ad, 0x2a645204, // yo.it.fi_543 fi.pt.gl_666 fi.lg.nl_643 ha.lg.mt_332
+ 0x1b033b07, 0x07000a34, 0x02085307, 0x100b5507, // so.nl.tr_432 mk.bg.un_A80 ht.no.da_432 rw.es.lt_432
+ // [2cf0]
+ 0x04000514, 0x3f002d08, 0x05640709, 0x553b52af, // fr.fi.un_660 sk.af.un_430 it.lg.fr_444 ha.so.rw_655
+ 0x23050708, 0x3f1a6b0c, 0x1c132a04, 0x2d003207, // it.fr.ca_443 ceb.tl.af_543 mt.et.id_332 bs.sk.un_420
+ 0x0506520c, 0x1c0935ad, 0x311b0aa0, 0x0c020608, // ha.de.fr_543 zu.pl.id_643 pt.tr.az_322 de.da.sv_443
+ 0x0c080e04, 0x08190b04, 0x030c3f04, 0x31001904, // is.no.sv_332 es.gl.no_332 af.sv.nl_332 gl.az.un_320
+ // [2d00]
+ 0x030c08a4, 0x1f2708a4, 0x130752ad, 0x13080205, // no.sv.nl_433 no.gd.cy_433 ha.it.et_643 da.no.et_333
+ 0x2a0e05a4, 0x0d1c130c, 0x032a060c, 0x3b3f040c, // fr.is.mt_433 bh.mr.ne_543 de.mt.nl_543 fi.af.so_543
+ 0x07002a09, 0x3f080c04, 0x032d2a04, 0x08101713, // mt.it.un_440 sv.no.af_332 mt.sk.nl_332 sr.be.uk_665
+ 0x1b0131ee, 0x2a063f0c, 0x1632170b, 0x0c086ba0, // az.en.tr_422 af.de.mt_543 sr.bs.hr_542 ceb.no.sv_322
+ // [2d10]
+ 0x4a006819, 0x23190b13, 0x09001f22, 0x2a006e19, // ig.yo.un_750 es.gl.ca_665 cy.pl.un_870 hmn.mt.un_750
+ 0x030c3f02, 0x060d05a0, 0x250b19ee, 0x5200230d, // af.sv.nl_222 fr.cs.de_322 gl.es.eu_422 ca.ha.un_540
+ 0x20102912, 0x130d09a9, 0x2319050c, 0x290d1602, // sl.lt.sq_654 hi.ne.bh_544 fr.gl.ca_543 hr.cs.sl_222
+ 0x284a5514, 0x2d1029ad, 0x0c052704, 0x28211ca0, // rw.yo.sw_666 sl.lt.sk_643 gd.fr.sv_332 id.jw.sw_322
+ // [2d20]
+ 0x20190bee, 0x27000802, 0x281e6ba0, 0x1a28640c, // es.gl.sq_422 no.gd.un_220 ceb.ms.sw_322 lg.sw.tl_543
+ 0x05083ba0, 0x13171608, 0x530405ad, 0x121e1c04, // so.no.fr_322 hr.sr.et_443 fr.fi.ht_643 id.ms.hu_332
+ 0x290413a4, 0x09001613, 0x0b001e04, 0x29130911, // et.fi.sl_433 hr.pl.un_650 ms.es.un_320 pl.et.sl_653
+ 0x2a001904, 0x07001604, 0x16001308, 0x0e003b1b, // gl.mt.un_320 hr.it.un_320 et.hr.un_430 so.is.un_770
+ // [2d30]
+ 0x4a25520c, 0x1f100fa0, 0x352a075a, 0x04002522, // ha.eu.yo_543 lv.lt.cy_322 it.mt.zu_553 eu.fi.un_870
+ 0x4a6b10a4, 0x3252080c, 0x18522813, 0x100f1eee, // lt.ceb.yo_433 no.ha.bs_543 sw.ha.ga_665 ms.lv.lt_422
+ 0x64003202, 0x1b533255, 0x131c1e07, 0x2a5220a7, // bs.lg.un_220 bs.ht.tr_442 ms.id.et_432 sq.ha.mt_532
+ 0x35005523, 0x050307a6, 0x161364a0, 0x10066e04, // rw.zu.un_880 it.nl.fr_521 lg.et.hr_322 hmn.de.lt_332
+ // [2d40]
+ 0x3f022aec, 0x080419a0, 0x6e002019, 0x06032702, // mt.da.af_644 gl.fi.no_322 sq.hmn.un_750 gd.nl.de_222
+ 0x0f291ca4, 0x2d0f10ec, 0x1b00121b, 0x060e0c05, // id.sl.lv_433 lt.lv.sk_644 hu.tr.un_770 sv.is.de_333
+ 0x3b000419, 0x0e1b3bad, 0x1b214a05, 0x0f0313a0, // fi.so.un_750 so.tr.is_643 yo.jw.tr_333 et.nl.lv_322
+ 0x07046408, 0x030c13a4, 0x0d1309a7, 0x291703af, // lg.fi.it_443 et.sv.nl_433 hi.bh.ne_532 nl.sr.sl_655
+ // [2d50]
+ 0x2a001f1a, 0x0b0323ee, 0x230a6e07, 0x063f0e05, // cy.mt.un_760 ca.nl.es_422 hmn.pt.ca_432 is.af.de_333
+ 0x12033fa4, 0x0a0711a0, 0x2520040b, 0x28000119, // af.nl.hu_433 ro.bg.mk_322 fi.sq.eu_542 en.sw.un_750
+ 0x6b55640c, 0x08000713, 0x641e290c, 0x1c1e2712, // lg.rw.ceb_543 bg.uk.un_650 sl.ms.lg_543 gd.ms.id_654
+ 0x10041304, 0x190b05ee, 0x082a07af, 0x13001e05, // et.fi.lt_332 fr.es.gl_422 it.mt.no_655 ms.et.un_330
+ // [2d60]
+ 0x2b000704, 0x20321605, 0x3b1304ad, 0x193223a6, // it.vi.un_320 hr.bs.sq_333 fi.et.so_643 ca.bs.gl_521
+ 0x1f211ea0, 0x281752a0, 0x070a2809, 0x211a1ea7, // ms.jw.cy_322 ha.sr.sw_322 sw.pt.it_444 ms.tl.jw_532
+ 0x20001e04, 0x033f0ea9, 0x23002d19, 0x0800171b, // ms.sq.un_320 is.af.nl_544 sk.ca.un_750 sr.uk.un_770
+ 0x06231108, 0x3200290e, 0x103f06ac, 0x20001f12, // ro.ca.de_443 sl.bs.un_550 de.af.lt_632 cy.sq.un_640
+ // [2d70]
+ 0x2b000e05, 0x20000413, 0x646b53a4, 0x17192907, // is.vi.un_330 fi.sq.un_650 ht.ceb.lg_433 sl.gl.sr_432
+ 0x13015304, 0x0e0c31af, 0x18255204, 0x232007a4, // ht.en.et_332 az.sv.is_655 ha.eu.ga_332 it.sq.ca_433
+ 0x070f25ad, 0x13310eb2, 0x10684a55, 0x21001107, // eu.lv.it_643 is.az.et_732 yo.ig.lt_442 ro.jw.un_420
+ 0x281168a6, 0x172d2912, 0x190a28af, 0x0a106b04, // ig.ro.sw_521 sl.sk.sr_654 sw.pt.gl_655 ceb.lt.pt_332
+ // [2d80]
+ 0x64001a08, 0x3b006e04, 0x4a0b2507, 0x100a19a4, // tl.lg.un_430 hmn.so.un_320 eu.es.yo_432 gl.pt.lt_433
+ 0x35555205, 0x0c080e08, 0x0c000418, 0x0802060c, // ha.rw.zu_333 is.no.sv_443 fi.sv.un_740 de.da.no_543
+ 0x531b52a6, 0x6800070e, 0x1e250704, 0x032805a7, // ha.tr.ht_521 it.ig.un_550 it.eu.ms_332 fr.sw.nl_532
+ 0x0b003b21, 0x31001e04, 0x040806a0, 0x6435120c, // so.es.un_860 ms.az.un_320 de.no.fi_322 hu.zu.lg_543
+ // [2d90]
+ 0x12000a20, 0x08020ea0, 0x0a1811a4, 0x1e2d0d55, // pt.hu.un_850 is.da.no_322 ro.ga.pt_433 cs.sk.ms_442
+ 0x3f000412, 0x1b310cec, 0x21134aa0, 0x061332ad, // fi.af.un_640 sv.az.tr_644 yo.et.jw_322 bs.et.de_643
+ 0x23251904, 0x0b0103ad, 0x0c0e0107, 0x2d0d32a4, // gl.eu.ca_332 nl.en.es_643 en.is.sv_432 bs.cs.sk_433
+ 0x13000509, 0x063f1bee, 0x081e3155, 0x193b1212, // fr.et.un_440 tr.af.de_422 az.ms.no_442 hu.so.gl_654
+ // [2da0]
+ 0x13000712, 0x203117a0, 0x0c076ba0, 0x2a005321, // it.et.un_640 sr.az.sq_322 ceb.it.sv_322 ht.mt.un_860
+ 0x3f1a6ba0, 0x0b052108, 0x022835ec, 0x1b2d0d05, // ceb.tl.af_322 jw.fr.es_443 zu.sw.da_644 cs.sk.tr_333
+ 0x231107af, 0x2d12205a, 0x02080c60, 0x252d6807, // it.ro.ca_655 sq.hu.sk_553 sv.no.da_664 ig.sk.eu_432
+ 0x323b0808, 0x19000618, 0x0c006408, 0x04001e08, // no.so.bs_443 de.gl.un_740 lg.sv.un_430 ms.fi.un_430
+ // [2db0]
+ 0x2d122912, 0x290e1905, 0x5300051b, 0x13000518, // sl.hu.sk_654 gl.is.sl_333 fr.ht.un_770 fr.et.un_740
+ 0x09553512, 0x321620ad, 0x212852ee, 0x120208a4, // zu.rw.pl_654 sq.hr.bs_643 ha.sw.jw_422 no.da.hu_433
+ 0x522128a7, 0x64285211, 0x52002114, 0x130f21a4, // sw.jw.ha_532 ha.sw.lg_653 jw.ha.un_660 jw.lv.et_433
+ 0x6b190a0d, 0x1e281c04, 0x17002a07, 0x1c211e11, // pt.gl.ceb_554 id.sw.ms_332 mt.sr.un_420 ms.jw.id_653
+ // [2dc0]
+ 0x1f000121, 0x051109ad, 0x1c2113ad, 0x17000814, // en.cy.un_860 pl.ro.fr_643 et.jw.id_643 uk.sr.un_660
+ 0x1e1c52ec, 0x3f090f08, 0x11093fa0, 0x100411ad, // ha.id.ms_644 lv.pl.af_443 af.pl.ro_322 ro.ru.be_643
+ 0x09002912, 0x0419210c, 0x070905a4, 0x53001a02, // sl.pl.un_640 jw.gl.fi_543 fr.pl.it_433 tl.ht.un_220
+ 0x351a4a0c, 0x1c211bad, 0x2a25350c, 0x19680b0c, // yo.tl.zu_543 tr.jw.id_643 zu.eu.mt_543 es.ig.gl_543
+ // [2dd0]
+ 0x1704100c, 0x0a1c21a7, 0x091217a0, 0x25001e13, // be.ru.sr_543 jw.id.pt_532 sr.hu.pl_322 ms.eu.un_650
+ 0x0c021ca0, 0x032511ee, 0x12050907, 0x08001e0d, // id.da.sv_322 ro.eu.nl_422 pl.fr.hu_432 ms.no.un_540
+ 0x553b20ec, 0x0a100408, 0x135564a0, 0x6b132aa0, // sq.so.rw_644 fi.lt.pt_443 lg.rw.et_322 mt.et.ceb_322
+ 0x05070b08, 0x0b311b1d, 0x28006b0d, 0x0e031bec, // es.it.fr_443 tr.az.es_852 ceb.sw.un_540 tr.nl.is_644
+ // [2de0]
+ 0x212035a4, 0x100a520d, 0x550e350c, 0x082331ad, // zu.sq.jw_433 ha.pt.lt_554 zu.is.rw_543 az.ca.no_643
+ 0x113f6404, 0x030618a9, 0x3b000707, 0x643f030c, // lg.af.ro_332 ga.de.nl_544 it.so.un_420 nl.af.lg_543
+ 0x12001f14, 0x070b0a09, 0x08001709, 0x35062511, // cy.hu.un_660 pt.es.it_444 sr.uk.un_440 eu.de.zu_653
+ 0x1a070e0d, 0x07081104, 0x10190b02, 0x042028a0, // is.it.tl_554 ro.uk.bg_332 es.gl.lt_222 sw.sq.fi_322
+ // [2df0]
+ 0x04136408, 0x04004a19, 0x13060c04, 0x12006b0d, // lg.et.fi_443 yo.fi.un_750 sv.de.et_332 ceb.hu.un_540
+ 0x0e1206ec, 0x1e1c2014, 0x10201107, 0x2a112014, // de.hu.is_644 sq.id.ms_666 ro.sq.lt_432 sq.ro.mt_666
+ 0x13001f1a, 0x011e68a0, 0x080253a0, 0x0400181a, // cy.et.un_760 ig.ms.en_322 ht.da.no_322 ga.fi.un_760
+ 0x312a21a0, 0x061a2808, 0x21061c07, 0x6b2d0dad, // jw.mt.az_322 sw.tl.de_443 id.de.jw_432 cs.sk.ceb_643
+ // [2e00]
+ 0x641c21a0, 0x285206af, 0x2a292704, 0x6b00210d, // jw.id.lg_322 de.ha.sw_655 gd.sl.mt_332 jw.ceb.un_540
+ 0x64280da0, 0x04002812, 0x09001c22, 0x13006b08, // cs.sw.lg_322 sw.fi.un_640 mr.hi.un_870 ceb.et.un_430
+ 0x051e1c04, 0x13640408, 0x17321607, 0x041355a4, // id.ms.fr_332 fi.lg.et_443 hr.bs.sr_432 rw.et.fi_433
+ 0x5564200c, 0x296435ee, 0x20551c07, 0x040313ee, // sq.lg.rw_543 zu.lg.sl_422 id.rw.sq_432 et.nl.fi_422
+ // [2e10]
+ 0x062832a0, 0x64003f08, 0x23002a08, 0x19000d19, // bs.sw.de_322 af.lg.un_430 mt.ca.un_430 cs.gl.un_750
+ 0x52283513, 0x10170a05, 0x2a003533, 0x1e555208, // zu.sw.ha_665 mk.sr.be_333 zu.mt.un_A70 ha.rw.ms_443
+ 0x25352807, 0x04293507, 0x5204100c, 0x201235a0, // sw.zu.eu_432 zu.sl.fi_432 lt.fi.ha_543 zu.hu.sq_322
+ 0x01000511, 0x12003f05, 0x02031ca0, 0x64523b08, // fr.en.un_630 af.hu.un_330 id.nl.da_322 so.ha.lg_443
+ // [2e20]
+ 0x03001f20, 0x320d2aa4, 0x28526405, 0x050f55a0, // cy.nl.un_850 mt.cs.bs_433 lg.ha.sw_333 rw.lv.fr_322
+ 0x25001913, 0x08001029, 0x55200c05, 0x17202a04, // gl.eu.un_650 be.uk.un_960 sv.sq.rw_333 mt.sq.sr_332
+ 0x3b006813, 0x350664a0, 0x190b2308, 0x13001c22, // ig.so.un_650 lg.de.zu_322 ca.es.gl_443 mr.bh.un_870
+ 0x113218ee, 0x551228a4, 0x0800100b, 0x0b002507, // ga.bs.ro_422 sw.hu.rw_433 be.uk.un_520 eu.es.un_420
+ // [2e30]
+ 0x1653280b, 0x3b285204, 0x530c1208, 0x28531309, // sw.ht.hr_542 ha.sw.so_332 hu.sv.ht_443 et.ht.sw_444
+ 0x0b0a05a9, 0x041e1c5a, 0x01270511, 0x0b321704, // fr.pt.es_544 id.ms.fi_553 fr.gd.en_653 sr.bs.es_332
+ 0x29172a07, 0x206455ad, 0x05001c04, 0x06111704, // mt.sr.sl_432 rw.lg.sq_643 id.fr.un_320 sr.ro.de_332
+ 0x1e1c09ec, 0x190b050e, 0x355355a9, 0x20285511, // pl.id.ms_644 fr.es.gl_555 rw.ht.zu_544 rw.sw.sq_653
+ // [2e40]
+ 0x3f5228ad, 0x55351cad, 0x4a133b5a, 0x68001e04, // sw.ha.af_643 id.zu.rw_643 so.et.yo_553 ms.ig.un_320
+ 0x6800090d, 0x4a0313a4, 0x64133b11, 0x64092a0d, // pl.ig.un_540 et.nl.yo_433 so.et.lg_653 mt.pl.lg_554
+ 0x55005319, 0x160f0da7, 0x686455a9, 0x680603a4, // ht.rw.un_750 cs.lv.hr_532 rw.lg.ig_544 nl.de.ig_433
+ 0x07080205, 0x35005308, 0x03556405, 0x230128ee, // da.no.it_333 ht.zu.un_430 lg.rw.nl_333 sw.en.ca_422
+ // [2e50]
+ 0x040a07a7, 0x3f1f030e, 0x0802010c, 0x250301a6, // bg.mk.ru_532 nl.cy.af_555 en.da.no_543 en.nl.eu_521
+ 0x1f063fa4, 0x3f6b0304, 0x21001902, 0x55283514, // af.de.cy_433 nl.ceb.af_332 gl.jw.un_220 zu.sw.rw_666
+ 0x553509a4, 0x0c061302, 0x5500530e, 0x1f006e2a, // pl.zu.rw_433 et.de.sv_222 ht.rw.un_550 hmn.cy.un_970
+ 0x03133f12, 0x190512ac, 0x28006405, 0x52645512, // af.et.nl_654 hu.fr.gl_632 lg.sw.un_330 rw.lg.ha_654
+ // [2e60]
+ 0x092835a0, 0x28351c0c, 0x531a2813, 0x09000f0c, // zu.sw.pl_322 id.zu.sw_543 sw.tl.ht_665 lv.pl.un_530
+ 0x1c351e12, 0x0a0704a0, 0x2a3f03a9, 0x033f06a9, // ms.zu.id_654 ru.bg.mk_322 nl.af.mt_544 de.af.nl_544
+ 0x033f06ad, 0x311b2a17, 0x033f6b02, 0x3b00211b, // de.af.nl_643 mt.tr.az_753 ceb.af.nl_222 jw.so.un_770
+ 0x12000c04, 0x13000c19, 0x55285202, 0x236b0e04, // sv.hu.un_320 sv.et.un_750 ha.sw.rw_222 is.ceb.ca_332
+ // [2e70]
+ 0x23200b13, 0x2a6b1a0c, 0x041017ad, 0x19641355, // es.sq.ca_665 tl.ceb.mt_543 sr.be.ru_643 et.lg.gl_442
+ 0x011f2aee, 0x3b3508a4, 0x18000611, 0x204a1a0b, // mt.cy.en_422 no.zu.so_433 de.ga.un_630 tl.yo.sq_542
+ 0x080c1fec, 0x645255a4, 0x4a526807, 0x11040708, // cy.sv.no_644 rw.ha.lg_433 ig.ha.yo_432 bg.ru.ro_443
+ 0x3f044a09, 0x216b1a05, 0x095213a0, 0x216b1a08, // yo.fi.af_444 tl.ceb.jw_333 et.ha.pl_322 tl.ceb.jw_443
+ // [2e80]
+ 0x08000c21, 0x0b032ba7, 0x3f321cee, 0x0a006b05, // sv.no.un_860 vi.nl.es_532 id.bs.af_422 ceb.pt.un_330
+ 0x3b285513, 0x1c1f1aa4, 0x2535640c, 0x3b130e0c, // rw.sw.so_665 tl.cy.id_433 lg.zu.eu_543 is.et.so_543
+ 0x190b2314, 0x2a2007ee, 0x0e351c07, 0x2b0c0507, // ca.es.gl_666 it.sq.mt_422 id.zu.is_432 fr.sv.vi_432
+ 0x6b211a13, 0x1f061a07, 0x680d06a7, 0x6b002108, // tl.jw.ceb_665 tl.de.cy_432 de.cs.ig_532 jw.ceb.un_430
+ // [2e90]
+ 0x11000e13, 0x111a6bad, 0x1f2a550c, 0x20285204, // is.ro.un_650 ceb.tl.ro_643 rw.mt.cy_543 ha.sw.sq_332
+ 0x643f35ad, 0x2100352b, 0x32005504, 0x553568ec, // zu.af.lg_643 zu.jw.un_980 rw.bs.un_320 ig.zu.rw_644
+ 0x09522a11, 0x6418275a, 0x0410530b, 0x535535af, // mt.ha.pl_653 gd.ga.lg_553 ht.lt.fi_542 zu.rw.ht_655
+ 0x01093507, 0x3f080308, 0x01070513, 0x68556408, // zu.pl.en_432 nl.no.af_443 fr.it.en_665 lg.rw.ig_443
+ // [2ea0]
+ 0x0d063b0c, 0x093b285a, 0x27001321, 0x283b64a9, // so.de.cs_543 sw.so.pl_553 et.gd.un_860 lg.so.sw_544
+ 0x09000309, 0x5500642a, 0x0000292d, 0x16640955, // nl.pl.un_440 lg.rw.un_970 sl.un.un_A00 pl.lg.hr_442
+ 0x0300640d, 0x16530ca0, 0x23000b07, 0x07100409, // lg.nl.un_540 sv.ht.hr_322 es.ca.un_420 ru.be.bg_444
+ 0x13061ea0, 0x0d001f1a, 0x55006407, 0x03520107, // ms.de.et_322 cy.cs.un_760 lg.rw.un_420 en.ha.nl_432
+ // [2eb0]
+ 0x3b231907, 0x5500352b, 0x0807045a, 0x18002b2b, // gl.ca.so_432 zu.rw.un_980 ru.bg.uk_553 vi.ga.un_980
+ 0x102531a0, 0x232a0da7, 0x10000d13, 0x52002104, // az.eu.lt_322 cs.mt.ca_532 cs.lt.un_650 jw.ha.un_320
+ 0x25521313, 0x21063f12, 0x55251a12, 0x1300121a, // et.ha.eu_665 af.de.jw_654 tl.eu.rw_654 hu.et.un_760
+ 0x3b211c07, 0x08002a05, 0x643f55af, 0x1c001904, // id.jw.so_432 mt.no.un_330 rw.af.lg_655 gl.id.un_320
+ // [2ec0]
+ 0x32000f08, 0x07110405, 0x0d005208, 0x043b03ee, // lv.bs.un_430 ru.ro.bg_333 ha.cs.un_430 nl.so.fi_422
+ 0x0a110f11, 0x033f0205, 0x063f2b0b, 0x1f190704, // lv.ro.pt_653 da.af.nl_333 vi.af.de_542 it.gl.cy_332
+ 0x09003202, 0x05013b02, 0x3f350da7, 0x101117a0, // bs.pl.un_220 so.en.fr_222 cs.zu.af_532 sr.ro.lt_322
+ 0x1f090d07, 0x0c001b19, 0x28004a21, 0x201b1008, // cs.pl.cy_432 tr.sv.un_750 yo.sw.un_860 lt.tr.sq_443
+ // [2ed0]
+ 0x20001008, 0x1c1e2111, 0x53281602, 0x2b001f09, // lt.sq.un_430 jw.ms.id_653 hr.sw.ht_222 cy.vi.un_440
+ 0x1b10200b, 0x29002007, 0x21004a23, 0x6b2b280d, // sq.lt.tr_542 sq.sl.un_420 yo.jw.un_880 sw.vi.ceb_554
+ 0x4a000a19, 0x17110808, 0x55213511, 0x13052008, // pt.yo.un_750 uk.ro.sr_443 zu.jw.rw_653 sq.fr.et_443
+ 0x233f6b07, 0x2700051b, 0x2a084aa0, 0x2d1b20ee, // ceb.af.ca_432 fr.gd.un_770 yo.no.mt_322 sq.tr.sk_422
+ // [2ee0]
+ 0x05001914, 0x19230f5a, 0x5255350d, 0x0e2a0faf, // gl.fr.un_660 lv.ca.gl_553 zu.rw.ha_554 lv.mt.is_655
+ 0x3f180b5a, 0x0a55280c, 0x16110da4, 0x19116407, // es.ga.af_553 sw.rw.pt_543 cs.ro.hr_433 lg.ro.gl_432
+ 0x05230b02, 0x6b6428a4, 0x2b003521, 0x162b32ee, // es.ca.fr_222 sw.lg.ceb_433 zu.vi.un_860 bs.vi.hr_422
+ 0x2000190d, 0x09283507, 0x13033f05, 0x0d12090c, // gl.sq.un_540 zu.sw.pl_432 af.nl.et_333 pl.hu.cs_543
+ // [2ef0]
+ 0x20101304, 0x116b35a9, 0x06093f02, 0x0f006404, // et.lt.sq_332 zu.ceb.ro_544 af.pl.de_222 lg.lv.un_320
+ 0x52002813, 0x201013a9, 0x182705af, 0x1a002813, // sw.ha.un_650 et.lt.sq_544 fr.gd.ga_655 sw.tl.un_650
+ 0x3b121107, 0x2500111a, 0x3b1e550e, 0x12642811, // ro.hu.so_432 ro.eu.un_760 rw.ms.so_555 sw.lg.hu_653
+ 0x16192307, 0x202a07a7, 0x0c1306ac, 0x0d091c08, // ca.gl.hr_432 it.mt.sq_532 de.et.sv_632 mr.hi.ne_443
+ // [2f00]
+ 0x64003204, 0x68352804, 0x11100412, 0x070810ee, // bs.lg.un_320 sw.zu.ig_332 ru.be.ro_654 be.uk.bg_422
+ 0x0f2a0d07, 0x551321ad, 0x08022da4, 0x520e09a7, // cs.mt.lv_432 jw.et.rw_643 sk.da.no_433 pl.is.ha_532
+ 0x1a0e6ba4, 0x17000e08, 0x18001221, 0x645568a4, // ceb.is.tl_433 is.sr.un_430 ur.ar.un_860 ig.rw.lg_433
+ 0x0112130c, 0x2300052a, 0x64352855, 0x32100ea7, // et.hu.en_543 fr.ca.un_970 sw.zu.lg_442 is.lt.bs_532
+ // [2f10]
+ 0x0700230c, 0x53001921, 0x35001e02, 0x201b32a0, // ca.it.un_530 gl.ht.un_860 ms.zu.un_220 bs.tr.sq_322
+ 0x5364550c, 0x281e35ad, 0x09001e04, 0x55680aa4, // rw.lg.ht_543 zu.ms.sw_643 ms.pl.un_320 pt.ig.rw_433
+ 0x6b5325a0, 0x08021eee, 0x071108a7, 0x3f530511, // eu.ht.ceb_322 ms.da.no_422 uk.ro.bg_532 fr.ht.af_653
+ 0x05002112, 0x18000e08, 0x00002a2d, 0x0e1025a6, // jw.fr.un_640 is.ga.un_430 mt.un.un_A00 eu.lt.is_521
+ // [2f20]
+ 0x4a0e130d, 0x32000e08, 0x18001604, 0x191355a0, // et.is.yo_554 is.bs.un_430 hr.ga.un_320 rw.et.gl_322
+ 0x0d4a1902, 0x03003202, 0x3b000304, 0x55351bee, // gl.yo.cs_222 bs.nl.un_220 nl.so.un_320 tr.zu.rw_422
+ 0x290f0d07, 0x060a25ad, 0x21311b5a, 0x2d1e1c02, // cs.lv.sl_432 eu.pt.de_643 tr.az.jw_553 id.ms.sk_222
+ 0x1c0f01ee, 0x10005305, 0x3b275508, 0x08170aaf, // en.lv.id_422 ht.lt.un_330 rw.gd.so_443 mk.sr.uk_655
+ // [2f30]
+ 0x0b00230d, 0x1f1c21a0, 0x100a0805, 0x1800170e, // ca.es.un_540 jw.id.cy_322 uk.mk.be_333 sr.ga.un_550
+ 0x03002013, 0x1e1c230c, 0x64001021, 0x2a071ca9, // sq.nl.un_650 ca.id.ms_543 lt.lg.un_860 id.it.mt_544
+ 0x100e64a4, 0x25001c13, 0x2d292008, 0x1b003b04, // lg.is.lt_433 id.eu.un_650 sq.sl.sk_443 so.tr.un_320
+ 0x05011e07, 0x2a1168a4, 0x20000e05, 0x52284a0b, // ms.en.fr_432 ig.ro.mt_433 is.sq.un_330 yo.sw.ha_542
+ // [2f40]
+ 0x1a6b1e0c, 0x1c2d0d14, 0x080407a9, 0x13110707, // ms.ceb.tl_543 cs.sk.id_666 bg.ru.uk_544 it.ro.et_432
+ 0x06040305, 0x2a103b04, 0x0d11230e, 0x2a070413, // nl.fi.de_333 so.lt.mt_332 ca.ro.cs_555 fi.it.mt_665
+ 0x2a1264ad, 0x071117a4, 0x101225ad, 0x04100707, // lg.hu.mt_643 sr.ro.bg_433 eu.hu.lt_643 bg.be.ru_432
+ 0x0e001022, 0x1e1c3505, 0x64282a12, 0x0b000a29, // lt.is.un_870 zu.id.ms_333 mt.sw.lg_654 pt.es.un_960
+ // [2f50]
+ 0x52136e04, 0x3f030c04, 0x1e001012, 0x08066b02, // hmn.et.ha_332 sv.nl.af_332 lt.ms.un_640 ceb.de.no_222
+ 0x1f006e19, 0x083f25a0, 0x2a645509, 0x196e1a12, // hmn.cy.un_750 eu.af.no_322 rw.lg.mt_444 tl.hmn.gl_654
+ 0x061252a4, 0x13072a5a, 0x31002704, 0x6e001f22, // ha.hu.de_433 mt.it.et_553 gd.az.un_320 cy.hmn.un_870
+ 0x1a0b6ea7, 0x12000705, 0x1108040c, 0x120802ee, // hmn.es.tl_532 it.hu.un_330 ru.uk.ro_543 da.no.hu_422
+ // [2f60]
+ 0x310e1a07, 0x3500201a, 0x0d292daf, 0x1a0e270c, // tl.is.az_432 sq.zu.un_760 sk.sl.cs_655 gd.is.tl_543
+ 0x640a3507, 0x02002704, 0x1600090c, 0x1f04050c, // zu.pt.lg_432 gd.da.un_320 pl.hr.un_530 fr.fi.cy_543
+ 0x0b190a08, 0x6e00050d, 0x5500111b, 0x0d000a12, // pt.gl.es_443 fr.hmn.un_540 ro.rw.un_770 pt.cs.un_640
+ 0x313b1ea0, 0x113f25ad, 0x072916a9, 0x1000130c, // ms.so.az_322 eu.af.ro_643 hr.sl.it_544 et.lt.un_530
+ // [2f70]
+ 0x11004a02, 0x123f6ba4, 0x103127ad, 0x2d1307a0, // yo.ro.un_220 ceb.af.hu_433 gd.az.lt_643 it.et.sk_322
+ 0x35080c0d, 0x04000705, 0x1e00640d, 0x03006b08, // sv.no.zu_554 bg.ru.un_330 lg.ms.un_540 ceb.nl.un_430
+ 0x051c21a0, 0x18032307, 0x190d2d0c, 0x18002109, // jw.id.fr_322 ca.nl.ga_432 sk.cs.gl_543 fa.ar.un_440
+ 0x2b2a25a0, 0x0a0704ec, 0x2d0d0ba0, 0x2d0f0704, // eu.mt.vi_322 ru.bg.mk_644 es.cs.sk_322 it.lv.sk_332
+ // [2f80]
+ 0x2a072013, 0x4a5325ad, 0x2d002911, 0x1b0464a4, // sq.it.mt_665 eu.ht.yo_643 sl.sk.un_630 lg.fi.tr_433
+ 0x08001108, 0x0717045a, 0x0410170c, 0x191f0b12, // ro.uk.un_430 ru.sr.bg_553 sr.be.ru_543 es.cy.gl_654
+ 0x53552811, 0x0f001313, 0x2d001214, 0x10001122, // sw.rw.ht_653 et.lv.un_650 hu.sk.un_660 ro.be.un_870
+ 0x0d0f2807, 0x526428ad, 0x0e6b64a0, 0x211204a9, // sw.lv.cs_432 sw.lg.ha_643 lg.ceb.is_322 fi.hu.jw_544
+ // [2f90]
+ 0x132855af, 0x0b001004, 0x0804070d, 0x1f005502, // rw.sw.et_655 lt.es.un_320 bg.ru.uk_554 rw.cy.un_220
+ 0x19000302, 0x111355a9, 0x19000104, 0x190b1005, // nl.gl.un_220 rw.et.ro_544 en.gl.un_320 lt.es.gl_333
+ 0x32000808, 0x1a352804, 0x03001804, 0x200552a4, // no.bs.un_430 sw.zu.tl_332 ga.nl.un_320 ha.fr.sq_433
+ 0x0e0c0807, 0x03055508, 0x0f28130c, 0x20003204, // no.sv.is_432 rw.fr.nl_443 et.sw.lv_543 bs.sq.un_320
+ // [2fa0]
+ 0x050311a4, 0x062a250c, 0x0f5210a4, 0x10002520, // ro.nl.fr_433 eu.mt.de_543 lt.ha.lv_433 eu.lt.un_850
+ 0x05003b05, 0x521b2860, 0x3b002519, 0x6b28350c, // so.fr.un_330 sw.tr.ha_664 eu.so.un_750 zu.sw.ceb_543
+ 0x35100f08, 0x06002513, 0x213f06ee, 0x4a100f11, // lv.lt.zu_443 eu.de.un_650 de.af.jw_422 lv.lt.yo_653
+ 0x20321ca0, 0x1b1013a6, 0x1e4a52ac, 0x0805230c, // id.bs.sq_322 et.lt.tr_521 ha.yo.ms_632 ca.fr.no_543
+ // [2fb0]
+ 0x0f1335a4, 0x100e0f09, 0x2a2855ec, 0x130f25a4, // zu.et.lv_433 lv.is.lt_444 rw.sw.mt_644 eu.lv.et_433
+ 0x10282104, 0x12190b04, 0x1c35210c, 0x1e1c64a4, // jw.sw.lt_332 es.gl.hu_332 jw.zu.id_543 lg.id.ms_433
+ 0x251e1c0d, 0x52006822, 0x1c1b10a4, 0x6b001e08, // id.ms.eu_554 ig.ha.un_870 lt.tr.id_433 ms.ceb.un_430
+ 0x3f000804, 0x1b3b5512, 0x080255a4, 0x551352a9, // no.af.un_320 rw.so.tr_654 rw.da.no_433 ha.et.rw_544
+ // [2fc0]
+ 0x2800551a, 0x551b3b08, 0x52001a22, 0x641a52ad, // rw.sw.un_760 so.tr.rw_443 tl.ha.un_870 ha.tl.lg_643
+ 0x101708a4, 0x52211cac, 0x4a1b5512, 0x31003b12, // uk.sr.be_433 id.jw.ha_632 rw.tr.yo_654 so.az.un_640
+ 0x130f1013, 0x5228350c, 0x1b005521, 0x1a552105, // lt.lv.et_665 zu.sw.ha_543 rw.tr.un_860 jw.rw.tl_333
+ 0x6b001a35, 0x1100130c, 0x1a001b20, 0x523b1bad, // tl.ceb.un_A90 et.ro.un_530 tr.tl.un_850 tr.so.ha_643
+ // [2fd0]
+ 0x55281e0b, 0x550c64ec, 0x211a1e07, 0x1107685a, // ms.sw.rw_542 lg.sv.rw_644 ms.tl.jw_432 ig.it.ro_553
+ 0x110f2104, 0x25005221, 0x0f11285a, 0x3b001b08, // jw.lv.ro_332 ha.eu.un_860 sw.ro.lv_553 tr.so.un_430
+ 0x2811680c, 0x21356eee, 0x3b520f08, 0x12001107, // ig.ro.sw_543 hmn.zu.jw_422 lv.ha.so_443 ro.hu.un_420
+ 0x28521b08, 0x35645560, 0x64041055, 0x35531a08, // tr.ha.sw_443 rw.lg.zu_664 lt.fi.lg_442 tl.ht.zu_443
+ // [2fe0]
+ 0x556428a9, 0x0418130d, 0x2a005205, 0x1e21280c, // sw.lg.rw_544 et.ga.fi_554 ha.mt.un_330 sw.jw.ms_543
+ 0x1b041ead, 0x210f520c, 0x0812185a, 0x0528010c, // ms.fi.tr_643 ha.lv.jw_543 ga.hu.no_553 en.sw.fr_543
+ 0x1c0f1e0c, 0x281a55a4, 0x1b5355ad, 0x181125ad, // ms.lv.id_543 rw.tl.sw_433 rw.ht.tr_643 eu.ro.ga_643
+ 0x08170a02, 0x030f10a9, 0x120f3b0c, 0x3b1b55ec, // mk.sr.uk_222 lt.lv.nl_544 so.lv.hu_543 rw.tr.so_644
+ // [2ff0]
+ 0x09642811, 0x351b550c, 0x1204525a, 0x11090709, // sw.lg.pl_653 rw.tr.zu_543 ha.fi.hu_553 it.pl.ro_444
+ 0x1e1c030c, 0x522a3bec, 0x1318040c, 0x2700042a, // nl.id.ms_543 so.mt.ha_644 fi.ga.et_543 fi.gd.un_970
+ 0x281e68a4, 0x2009520c, 0x29001c13, 0x033f6404, // ig.ms.sw_433 ha.pl.sq_543 id.sl.un_650 lg.af.nl_332
+ 0x2500031a, 0x182702a0, 0x0518040c, 0x1b0c0208, // nl.eu.un_760 da.gd.ga_322 fi.ga.fr_543 da.sv.tr_443
+
+ // [3000]
+ 0x02123f0c, 0x180768a0, 0x181f23a4, 0x07001902, // af.hu.da_543 ig.it.ga_322 ca.cy.ga_433 gl.it.un_220
+ 0x0f1a04ad, 0x100e3ba4, 0x281304ad, 0x31000a0d, // fi.tl.lv_643 so.is.lt_433 fi.et.sw_643 pt.az.un_540
+ 0x3f2527a0, 0x133f2904, 0x0f1827ec, 0x2a003b14, // gd.eu.af_322 sl.af.et_332 gd.ga.lv_644 so.mt.un_660
+ 0x131e0ea0, 0x07045208, 0x12055205, 0x642855a4, // is.ms.et_322 ha.fi.it_443 ha.fr.hu_333 rw.sw.lg_433
+ // [3010]
+ 0x0a2a070e, 0x0d1c0907, 0x1200251a, 0x10182704, // it.mt.pt_555 hi.mr.ne_432 eu.hu.un_760 gd.ga.lt_332
+ 0x10040f08, 0x0c0229a4, 0x111e21a9, 0x211c52ec, // lv.fi.lt_443 sl.da.sv_433 jw.ms.ro_544 ha.id.jw_644
+ 0x03136e05, 0x3b202aa7, 0x2b001113, 0x32055212, // hmn.et.nl_333 mt.sq.so_532 ro.vi.un_650 ha.fr.bs_654
+ 0x11081708, 0x253f100c, 0x12290e12, 0x230f0aa4, // sr.uk.ro_443 lt.af.eu_543 is.sl.hu_654 pt.lv.ca_433
+ // [3020]
+ 0x53000702, 0x12132a08, 0x100f0413, 0x20115504, // it.ht.un_220 mt.et.hu_443 fi.lv.lt_665 rw.ro.sq_332
+ 0x0f052709, 0x06040f0b, 0x00001201, 0x0a2b010c, // gd.fr.lv_444 lv.fi.de_542 ur.un.un_200 en.vi.pt_543
+ 0x05232907, 0x35000421, 0x29643504, 0x0a00070e, // sl.ca.fr_432 fi.zu.un_860 zu.lg.sl_332 bg.mk.un_550
+ 0x3b003f09, 0x0f0428ee, 0x3f3b0304, 0x3b046ba9, // af.so.un_440 sw.fi.lv_422 nl.so.af_332 ceb.fi.so_544
+ // [3030]
+ 0x6e00010d, 0x1017080c, 0x07002105, 0x530c1ba4, // en.hmn.un_540 uk.sr.be_543 jw.it.un_330 tr.sv.ht_433
+ 0x2d090d0d, 0x20001819, 0x20001702, 0x3f1a03a9, // cs.pl.sk_554 ga.sq.un_750 sr.sq.un_220 nl.tl.af_544
+ 0x0b0a03a4, 0x0a250ead, 0x060b270c, 0x016b31a0, // nl.pt.es_433 is.eu.pt_643 gd.es.de_543 az.ceb.en_322
+ 0x0b3b5512, 0x1f53080b, 0x3f000c04, 0x6b1a550d, // rw.so.es_654 no.ht.cy_542 sv.af.un_320 rw.tl.ceb_554
+ // [3040]
+ 0x031827a4, 0x100725a7, 0x133b070c, 0x05190b13, // gd.ga.nl_433 eu.it.lt_532 it.so.et_543 es.gl.fr_665
+ 0x3b211308, 0x321c2107, 0x2d0d23a0, 0x1a201208, // et.jw.so_443 jw.id.bs_432 ca.cs.sk_322 hu.sq.tl_443
+ 0x0b0c64a4, 0x53272012, 0x216b1a5a, 0x091f5304, // lg.sv.es_433 sq.gd.ht_654 tl.ceb.jw_553 ht.cy.pl_332
+ 0x27001108, 0x1e1c10ec, 0x17001308, 0x1e1c11ec, // ro.gd.un_430 lt.id.ms_644 et.sr.un_430 ro.id.ms_644
+ // [3050]
+ 0x551a530d, 0x6435550c, 0x321725ec, 0x21003b05, // ht.tl.rw_554 rw.zu.lg_543 eu.sr.bs_644 so.jw.un_330
+ 0x080c3ba9, 0x06023fa9, 0x0711100c, 0x1107010c, // so.sv.no_544 af.da.de_544 lt.ro.it_543 en.it.ro_543
+ 0x0602230c, 0x21521aa4, 0x0c083107, 0x0511180c, // ca.da.de_543 tl.ha.jw_433 az.no.sv_432 ga.ro.fr_543
+ 0x10002809, 0x04031ca0, 0x1709520c, 0x281053a4, // sw.lt.un_440 id.nl.fi_322 ha.pl.sr_543 ht.lt.sw_433
+ // [3060]
+ 0x0c0708a0, 0x0710230c, 0x3f3b03af, 0x106455a4, // no.it.sv_322 ca.lt.it_543 nl.so.af_655 rw.lg.lt_433
+ 0x061225ad, 0x25001e08, 0x1e130409, 0x0800290d, // eu.hu.de_643 ms.eu.un_430 fi.et.ms_444 sl.no.un_540
+ 0x2310110c, 0x5212070c, 0x06000508, 0x16113107, // ro.lt.ca_543 it.hu.ha_543 fr.de.un_430 az.ro.hr_432
+ 0x10006407, 0x0f0a100d, 0x03006e19, 0x2564550d, // lg.lt.un_420 lt.pt.lv_554 hmn.nl.un_750 rw.lg.eu_554
+ // [3070]
+ 0x06055311, 0x28645505, 0x0f1a130d, 0x3f351307, // ht.fr.de_653 rw.lg.sw_333 et.tl.lv_554 et.zu.af_432
+ 0x2d000a09, 0x6e686b05, 0x0e001707, 0x1e1c130c, // pt.sk.un_440 ceb.ig.hmn_333 sr.is.un_420 et.id.ms_543
+ 0x1b282a55, 0x11001009, 0x04101c07, 0x11050404, // mt.sw.tr_442 lt.ro.un_440 id.lt.fi_432 fi.fr.ro_332
+ 0x070a2355, 0x08023fa0, 0x08115308, 0x16530408, // ca.pt.it_442 af.da.no_322 ht.ro.no_443 fi.ht.hr_443
+ // [3080]
+ 0x1768130c, 0x6b0913a4, 0x04133508, 0x1a64280b, // et.ig.sr_543 et.pl.ceb_433 zu.et.fi_443 sw.lg.tl_542
+ 0x04081702, 0x09131c11, 0x17002109, 0x31321708, // sr.uk.ru_222 mr.bh.hi_653 jw.sr.un_440 sr.bs.az_443
+ 0x32080c04, 0x21000a08, 0x32003b08, 0x35002011, // sv.no.bs_332 pt.jw.un_430 so.bs.un_430 sq.zu.un_630
+ 0x0b6b3ba0, 0x213b1ca4, 0x0a003b19, 0x5500350c, // so.ceb.es_322 id.so.jw_433 so.pt.un_750 zu.rw.un_530
+ // [3090]
+ 0x55003b18, 0x3b1e1c08, 0x210c13af, 0x6e00210c, // so.rw.un_740 id.ms.so_443 et.sv.jw_655 jw.hmn.un_530
+ 0x35285512, 0x0e000412, 0x4a6e6804, 0x28553b60, // rw.sw.zu_654 fi.is.un_640 ig.hmn.yo_332 so.rw.sw_664
+ 0x232811a7, 0x1004085a, 0x32170fec, 0x0b2d0d0d, // ro.sw.ca_532 uk.ru.be_553 lv.sr.bs_644 cs.sk.es_554
+ 0x09000108, 0x0a071002, 0x6e016ba7, 0x0a524aee, // en.pl.un_430 be.bg.mk_222 ceb.en.hmn_532 yo.ha.pt_422
+ // [30a0]
+ 0x28006414, 0x0d00041a, 0x0d2928a6, 0x13001f04, // lg.sw.un_660 fi.cs.un_760 sw.sl.cs_521 cy.et.un_320
+ 0x25000a07, 0x3b0b210c, 0x0e002a04, 0x112305ad, // pt.eu.un_420 jw.es.so_543 mt.is.un_320 fr.ca.ro_643
+ 0x211c1113, 0x2d0d01ee, 0x0e001c1a, 0x35213b08, // ro.id.jw_665 en.cs.sk_422 id.is.un_760 so.jw.zu_443
+ 0x2b170ba0, 0x1e1c0ea9, 0x3f20090c, 0x32170ead, // es.sr.vi_322 is.id.ms_544 pl.sq.af_543 is.sr.bs_643
+ // [30b0]
+ 0x08022014, 0x1b680e09, 0x01001e04, 0x4a643b08, // sq.da.no_666 is.ig.tr_444 ms.en.un_320 so.lg.yo_443
+ 0x182b20a4, 0x2d00190e, 0x13643b0b, 0x0a000e2a, // sq.vi.ga_433 gl.sk.un_550 so.lg.et_542 is.pt.un_970
+ 0x081f0c0c, 0x2d0d28a0, 0x02003504, 0x643b13a4, // sv.cy.no_543 sw.cs.sk_322 zu.da.un_320 et.so.lg_433
+ 0x0a040855, 0x04000908, 0x133b6407, 0x1031110c, // uk.ru.mk_442 pl.fi.un_430 lg.so.et_432 ro.az.lt_543
+ // [30c0]
+ 0x012d0d55, 0x27002018, 0x06110302, 0x0a170811, // cs.sk.en_442 sq.gd.un_740 nl.ro.de_222 uk.sr.mk_653
+ 0x64133baf, 0x0335285a, 0x10110708, 0x32171ca0, // so.et.lg_655 sw.zu.nl_553 it.ro.lt_443 id.sr.bs_322
+ 0x02000619, 0x3b136408, 0x316b1ba4, 0x2d0d01a9, // de.da.un_750 lg.et.so_443 tr.ceb.az_433 en.cs.sk_544
+ 0x1900010c, 0x19122d55, 0x0e2a200c, 0x6b000919, // en.gl.un_530 sk.hu.gl_442 sq.mt.is_543 pl.ceb.un_750
+ // [30d0]
+ 0x531c0e04, 0x1f11070b, 0x680d04ad, 0x53050e05, // is.id.ht_332 it.ro.cy_542 fi.cs.ig_643 is.fr.ht_333
+ 0x02000a04, 0x0e1b010c, 0x13280e0c, 0x201b3b07, // pt.da.un_320 en.tr.is_543 is.sw.et_543 so.tr.sq_432
+ 0x1b001e13, 0x23002b2b, 0x4a643b0c, 0x072a1309, // ms.tr.un_650 vi.ca.un_980 so.lg.yo_543 et.mt.it_444
+ 0x043b6408, 0x2b1c1e12, 0x2a550e0c, 0x230a0b5a, // lg.so.fi_443 ms.id.vi_654 is.rw.mt_543 es.pt.ca_553
+ // [30e0]
+ 0x13643b13, 0x10000807, 0x12290fad, 0x230a0108, // so.lg.et_665 uk.be.un_420 lv.sl.hu_643 en.pt.ca_443
+ 0x13121f12, 0x6b010607, 0x29091309, 0x20120304, // cy.hu.et_654 de.en.ceb_432 et.pl.sl_444 nl.hu.sq_332
+ 0x0e641aec, 0x6b1f06ac, 0x3f1e01a7, 0x52355504, // tl.lg.is_644 de.cy.ceb_632 en.ms.af_532 rw.zu.ha_332
+ 0x1e1c5509, 0x310e2da4, 0x192d1205, 0x2d1b53ad, // rw.id.ms_444 sk.is.az_433 hu.sk.gl_333 ht.tr.sk_643
+ // [30f0]
+ 0x1c2120af, 0x180e12b4, 0x3b1355af, 0x0e071c0c, // sq.jw.id_655 hu.is.ga_754 rw.et.so_655 id.it.is_543
+ 0x032b1cee, 0x11132813, 0x07130e55, 0x1f0e2512, // id.vi.nl_422 sw.et.ro_665 is.et.it_442 eu.is.cy_654
+ 0x0b231904, 0x3b002808, 0x29072da0, 0x2a180911, // gl.ca.es_332 sw.so.un_430 sk.it.sl_322 pl.ga.mt_653
+ 0x13002805, 0x28094a0d, 0x1a645311, 0x13111aee, // sw.et.un_330 yo.pl.sw_554 ht.lg.tl_653 tl.ro.et_422
+ // [3100]
+ 0x2d3b550c, 0x0e092907, 0x0f002913, 0x253555ad, // rw.so.sk_543 sl.pl.is_432 sl.lv.un_650 rw.zu.eu_643
+ 0x3b64550c, 0x0e002a13, 0x1b553507, 0x641a55a4, // rw.lg.so_543 mt.is.un_650 zu.rw.tr_432 rw.tl.lg_433
+ 0x6421550c, 0x53000719, 0x130e55a4, 0x6e25040d, // rw.jw.lg_543 it.ht.un_750 rw.is.et_433 fi.eu.hmn_554
+ 0x0810070c, 0x35285514, 0x042528a9, 0x25002823, // bg.be.uk_543 rw.sw.zu_666 sw.eu.fi_544 sw.eu.un_880
+ // [3110]
+ 0x2a00290e, 0x644a68a4, 0x1c352112, 0x25282308, // sl.mt.un_550 ig.yo.lg_433 jw.zu.id_654 ca.sw.eu_443
+ 0x171629ee, 0x4a28680c, 0x080e1011, 0x02002504, // sl.hr.sr_422 ig.sw.yo_543 lt.is.no_653 eu.da.un_320
+ 0x2a00291a, 0x06005304, 0x18002804, 0x282025af, // sl.mt.un_760 ht.de.un_320 sw.ga.un_320 eu.sq.sw_655
+ 0x6e280407, 0x2500230d, 0x28251e0c, 0x29190707, // fi.sw.hmn_432 ca.eu.un_540 ms.eu.sw_543 it.gl.sl_432
+ // [3120]
+ 0x17071002, 0x07291604, 0x1b125204, 0x202825ad, // be.bg.sr_222 hr.sl.it_332 ha.hu.tr_332 eu.sw.sq_643
+ 0x2104250c, 0x25130707, 0x04001c08, 0x2b00191b, // eu.fi.jw_543 it.et.eu_432 id.fi.un_430 gl.vi.un_770
+ 0x2d00180e, 0x35004a09, 0x162a2904, 0x521e4aa7, // ga.sk.un_550 yo.zu.un_440 sl.mt.hr_332 yo.ms.ha_532
+ 0x080a02a0, 0x1f006e09, 0x29100faf, 0x162905a7, // da.pt.no_322 hmn.cy.un_440 lv.lt.sl_655 fr.sl.hr_532
+ // [3130]
+ 0x133116ee, 0x6b0501a4, 0x201a6b12, 0x1f0918a4, // hr.az.et_422 en.fr.ceb_433 ceb.tl.sq_654 ga.pl.cy_433
+ 0x08000a02, 0x2d000914, 0x2d0d120e, 0x09213f07, // mk.uk.un_220 pl.sk.un_660 hu.cs.sk_555 af.jw.pl_432
+ 0x23002902, 0x07000819, 0x1c0d0908, 0x55286b04, // sl.ca.un_220 uk.bg.un_750 hi.ne.mr_443 ceb.sw.rw_332
+ 0x4a005308, 0x28002a04, 0x130802ee, 0x120a1fee, // ht.yo.un_430 mt.sw.un_320 da.no.et_422 cy.pt.hu_422
+ // [3140]
+ 0x18011f04, 0x19180bee, 0x1f520b04, 0x1a005502, // cy.en.ga_332 es.ga.gl_422 es.ha.cy_332 rw.tl.un_220
+ 0x10202504, 0x111c1ead, 0x20523fa4, 0x20216b60, // eu.sq.lt_332 ms.id.ro_643 af.ha.sq_433 ceb.jw.sq_664
+ 0x683520ee, 0x5528200c, 0x0a0711a6, 0x031101a4, // sq.zu.ig_422 sq.sw.rw_543 ro.bg.mk_521 en.ro.nl_433
+ 0x09103fa4, 0x0b0603af, 0x2809040c, 0x20003507, // af.lt.pl_433 nl.de.es_655 fi.pl.sw_543 zu.sq.un_420
+ // [3150]
+ 0x2500201a, 0x100904ac, 0x180711af, 0x072025ee, // sq.eu.un_760 fi.pl.lt_632 ro.it.ga_655 eu.sq.it_422
+ 0x1b1120a6, 0x28521e0c, 0x553f20ec, 0x050401a6, // sq.ro.tr_521 ms.ha.sw_543 sq.af.rw_644 en.fi.fr_521
+ 0x1e2025a9, 0x09022d0c, 0x1b523507, 0x051f2308, // eu.sq.ms_544 sk.da.pl_543 zu.ha.tr_432 ca.cy.fr_443
+ 0x252307a7, 0x21006b09, 0x52004a13, 0x07092d0b, // it.ca.eu_532 ceb.jw.un_440 yo.ha.un_650 sk.pl.it_542
+ // [3160]
+ 0x04000a02, 0x2d070ba4, 0x286b1a08, 0x0e3b52a0, // mk.ru.un_220 es.it.sk_433 tl.ceb.sw_443 ha.so.is_322
+ 0x213b1e11, 0x6b1a1fad, 0x3f010704, 0x1c1a1ea9, // ms.so.jw_653 cy.tl.ceb_643 it.en.af_332 ms.tl.id_544
+ 0x2b0128a0, 0x1a003b20, 0x213b1e08, 0x1200640b, // sw.en.vi_322 so.tl.un_850 ms.so.jw_443 lg.hu.un_520
+ 0x170a1004, 0x03060912, 0x25002013, 0x521a6b5a, // be.mk.sr_332 pl.de.nl_654 sq.eu.un_650 ceb.tl.ha_553
+ // [3170]
+ 0x083f27a0, 0x0e6b1a07, 0x1c3568ee, 0x52280960, // gd.af.no_322 tl.ceb.is_432 ig.zu.id_422 pl.sw.ha_664
+ 0x64005222, 0x2d0a050c, 0x0b072304, 0x03133f04, // ha.lg.un_870 fr.pt.sk_543 ca.it.es_332 af.et.nl_332
+ 0x103f0412, 0x640c520c, 0x09110f14, 0x0400062b, // fi.af.lt_654 ha.sv.lg_543 lv.ro.pl_666 de.fi.un_980
+ 0x1c521e0b, 0x200a07a4, 0x03000929, 0x0d0105a4, // ms.ha.id_542 it.pt.sq_433 pl.nl.un_960 fr.en.cs_433
+ // [3180]
+ 0x18120e08, 0x2d042a0c, 0x531a2107, 0x010608a0, // is.hu.ga_443 mt.fi.sk_543 jw.tl.ht_432 no.de.en_322
+ 0x25071807, 0x18311b0d, 0x04131104, 0x20002504, // ga.it.eu_432 tr.az.ga_554 ro.et.fi_332 eu.sq.un_320
+ 0x35281c08, 0x20050204, 0x0e00230e, 0x1a521e0c, // id.sw.zu_443 da.fr.sq_332 ca.is.un_550 ms.ha.tl_543
+ 0x2d0d1105, 0x100e120c, 0x1e210107, 0x0c0711a7, // ro.cs.sk_333 hu.is.lt_543 en.jw.ms_432 ro.it.sv_532
+ // [3190]
+ 0x0a050112, 0x122a2008, 0x12080c0d, 0x2a6425a9, // en.fr.pt_654 sq.mt.hu_443 sv.no.hu_554 eu.lg.mt_544
+ 0x0c006404, 0x180a0ca9, 0x6b3b1aec, 0x17111055, // lg.sv.un_320 sv.pt.ga_544 tl.so.ceb_644 be.ro.sr_442
+ 0x116820a9, 0x3f0902a0, 0x080223a0, 0x06006408, // sq.ig.ro_544 da.pl.af_322 ca.da.no_322 lg.de.un_430
+ 0x03063fee, 0x3f120c07, 0x1a080205, 0x111e1c04, // af.de.nl_422 sv.hu.af_432 da.no.tl_333 id.ms.ro_332
+ // [31a0]
+ 0x53351204, 0x0c6408a0, 0x6b1a0ba4, 0x25190b0d, // hu.zu.ht_332 no.lg.sv_322 es.tl.ceb_433 es.gl.eu_554
+ 0x050a3f08, 0x110410af, 0x06005204, 0x29003204, // af.pt.fr_443 be.ru.ro_655 ha.de.un_320 bs.sl.un_320
+ 0x2d000e20, 0x1a311ead, 0x12001e13, 0x162d68a0, // is.sk.un_850 ms.az.tl_643 ms.hu.un_650 ig.sk.hr_322
+ 0x28001219, 0x12071108, 0x281a180c, 0x5200641a, // hu.sw.un_750 ro.it.hu_443 ga.tl.sw_543 lg.ha.un_760
+ // [31b0]
+ 0x120a2d0d, 0x12072a12, 0x1b0e0c0c, 0x6b1a5212, // sk.pt.hu_554 mt.it.hu_654 sv.is.tr_543 ha.tl.ceb_654
+ 0x252164a6, 0x0b182707, 0x0b071107, 0x1c2155a9, // lg.jw.eu_521 gd.ga.es_432 ro.it.es_432 rw.jw.id_544
+ 0x11251ea0, 0x09251813, 0x553564ad, 0x6b1a520d, // ms.eu.ro_322 ga.eu.pl_665 lg.zu.rw_643 ha.tl.ceb_554
+ 0x551a64a4, 0x0e00190e, 0x2a0f2155, 0x6b645509, // lg.tl.rw_433 gl.is.un_550 jw.lv.mt_442 rw.lg.ceb_444
+ // [31c0]
+ 0x272d0d0e, 0x6b000712, 0x64001008, 0x040f25af, // cs.sk.gd_555 it.ceb.un_640 lt.lg.un_430 eu.lv.fi_655
+ 0x04002504, 0x254a6407, 0x293b520c, 0x13002022, // eu.fi.un_320 lg.yo.eu_432 ha.so.sl_543 sq.et.un_870
+ 0x64555211, 0x322a1712, 0x20005504, 0x64551ba9, // ha.rw.lg_653 sr.mt.bs_654 rw.sq.un_320 tr.rw.lg_544
+ 0x211b520c, 0x0d001604, 0x1600170c, 0x35084aee, // ha.tr.jw_543 hr.cs.un_320 sr.hr.un_530 yo.no.zu_422
+ // [31d0]
+ 0x110625a4, 0x1c001f08, 0x1335520b, 0x046425a7, // eu.de.ro_433 cy.id.un_430 ha.zu.et_542 eu.lg.fi_532
+ 0x023f1f12, 0x1c281ea9, 0x09001222, 0x0b0407ec, // cy.af.da_654 ms.sw.id_544 hu.pl.un_870 it.fi.es_644
+ 0x0f101c0c, 0x0c071f07, 0x1e3b2a11, 0x04071109, // id.lt.lv_543 cy.it.sv_432 mt.so.ms_653 ro.bg.ru_444
+ 0x130f100d, 0x28204aa0, 0x1f3b52a9, 0x1e1c3109, // lt.lv.et_554 yo.sq.sw_322 ha.so.cy_544 az.id.ms_444
+ // [31e0]
+ 0x1e1c52ee, 0x13041ba9, 0x210a0b05, 0x252d0d60, // ha.id.ms_422 tr.fi.et_544 es.pt.jw_333 cs.sk.eu_664
+ 0x1b00320c, 0x0a2319a0, 0x20001f02, 0x0f254aee, // bs.tr.un_530 gl.ca.pt_322 cy.sq.un_220 yo.eu.lv_422
+ 0x2d010209, 0x06006e22, 0x13250d0c, 0x1c0e6ead, // da.en.sk_444 hmn.de.un_870 cs.eu.et_543 hmn.is.id_643
+ 0x6b001c13, 0x0c311b13, 0x6b1a0107, 0x01250307, // id.ceb.un_650 tr.az.sv_665 en.tl.ceb_432 nl.eu.en_432
+ // [31f0]
+ 0x1100171a, 0x10040e08, 0x1a6b4a13, 0x3b4a070c, // sr.ro.un_760 is.fi.lt_443 yo.ceb.tl_665 it.yo.so_543
+ 0x6b1a1e0c, 0x1f001214, 0x18050807, 0x1c3f030c, // ms.tl.ceb_543 hu.cy.un_660 no.fr.ga_432 nl.af.id_543
+ 0x08031ca0, 0x52216b13, 0x2d1806a7, 0x021208ee, // id.nl.no_322 ceb.jw.ha_665 de.ga.sk_532 no.hu.da_422
+ 0x161b5255, 0x203202ad, 0x216b1a11, 0x13211ca6, // ha.tr.hr_442 da.bs.sq_643 tl.ceb.jw_653 id.jw.et_521
+ // [3200]
+ 0x1b0705a9, 0x271218a6, 0x5521280c, 0x4a64040c, // fr.it.tr_544 ga.hu.gd_521 sw.jw.rw_543 fi.lg.yo_543
+ 0x2b002104, 0x2b211c08, 0x110701a4, 0x08271307, // jw.vi.un_320 id.jw.vi_443 en.it.ro_433 et.gd.no_432
+ 0x071209a7, 0x0b232da4, 0x21041c11, 0x2d0d20af, // pl.hu.it_532 sk.ca.es_433 id.fi.jw_653 sq.cs.sk_655
+ 0x321019ee, 0x0e0208af, 0x08002504, 0x1e1b3111, // gl.lt.bs_422 no.da.is_655 eu.no.un_320 az.tr.ms_653
+ // [3210]
+ 0x170710ee, 0x31201c07, 0x1c5255a6, 0x52002502, // be.bg.sr_422 id.sq.az_432 rw.ha.id_521 eu.ha.un_220
+ 0x190b11a6, 0x080321a0, 0x0c216ea7, 0x122008a9, // ro.es.gl_521 jw.nl.no_322 hmn.jw.sv_532 no.sq.hu_544
+ 0x316e3b12, 0x2b3b120c, 0x530a05a9, 0x10002a21, // so.hmn.az_654 hu.so.vi_543 fr.pt.ht_544 mt.lt.un_860
+ 0x11074aa0, 0x07112304, 0x0100080d, 0x1a000808, // yo.it.ro_322 ca.ro.it_332 no.en.un_540 no.tl.un_430
+ // [3220]
+ 0x29005205, 0x28270ba4, 0x6b1c1aa4, 0x52351a13, // ha.sl.un_330 es.gd.sw_433 tl.id.ceb_433 tl.zu.ha_665
+ 0x162307a0, 0x1a522113, 0x0e3b0da0, 0x09000705, // it.ca.hr_322 jw.ha.tl_665 cs.so.is_322 it.pl.un_330
+ 0x092852ad, 0x03071105, 0x2864100c, 0x3b285212, // ha.sw.pl_643 ro.it.nl_333 lt.lg.sw_543 ha.sw.so_654
+ 0x290f32ee, 0x0600640b, 0x68002a08, 0x68005214, // bs.lv.sl_422 lg.de.un_520 mt.ig.un_430 ha.ig.un_660
+ // [3230]
+ 0x52281aa9, 0x313b2a08, 0x533b28a0, 0x2d0d100c, // tl.sw.ha_544 mt.so.az_443 sw.so.ht_322 lt.cs.sk_543
+ 0x0e6b1aa7, 0x3f005205, 0x13043b0c, 0x02090512, // tl.ceb.is_532 ha.af.un_330 so.fi.et_543 fr.pl.da_654
+ 0x3f113507, 0x285209ad, 0x0810110c, 0x3b3521ec, // zu.ro.af_432 pl.ha.sw_643 ro.be.uk_543 jw.zu.so_644
+ 0x27282504, 0x211a2807, 0x2b040804, 0x020906a0, // eu.sw.gd_332 sw.tl.jw_432 no.fi.vi_332 de.pl.da_322
+ // [3240]
+ 0x64354a07, 0x13003b0d, 0x21095212, 0x2d0306ec, // yo.zu.lg_432 so.et.un_540 ha.pl.jw_654 de.nl.sk_644
+ 0x281a520c, 0x52002804, 0x0c1c31a0, 0x230205ad, // ha.tl.sw_543 sw.ha.un_320 az.id.sv_322 fr.da.ca_643
+ 0x215535ee, 0x553f2dee, 0x2d090607, 0x35643ba7, // zu.rw.jw_422 sk.af.rw_422 de.pl.sk_432 so.lg.zu_532
+ 0x18000514, 0x01005308, 0x010335a0, 0x110408a4, // fr.ga.un_660 ht.en.un_430 zu.nl.en_322 uk.ru.ro_433
+ // [3250]
+ 0x31536405, 0x2d001707, 0x3b130408, 0x0c350205, // lg.ht.az_333 sr.sk.un_420 fi.et.so_443 da.zu.sv_333
+ 0x0413110c, 0x20071b55, 0x03060204, 0x311b0107, // ro.et.fi_543 tr.it.sq_442 da.de.nl_332 en.tr.az_432
+ 0x64321708, 0x051364af, 0x10110712, 0x31001b05, // sr.bs.lg_443 lg.et.fr_655 it.ro.lt_654 tr.az.un_330
+ 0x08321609, 0x2a52280c, 0x53132a04, 0x2a6b010c, // hr.bs.no_444 sw.ha.mt_543 mt.et.ht_332 en.ceb.mt_543
+ // [3260]
+ 0x3b000108, 0x2a00010d, 0x0d1c0904, 0x081f28a9, // en.so.un_430 en.mt.un_540 hi.mr.ne_332 sw.cy.no_544
+ 0x530d3fee, 0x07005313, 0x0f6b01ad, 0x106b55ee, // af.cs.ht_422 ht.it.un_650 en.ceb.lv_643 rw.ceb.lt_422
+ 0x23070508, 0x050c0809, 0x35001c02, 0x686b010c, // fr.it.ca_443 no.sv.fr_444 id.zu.un_220 en.ceb.ig_543
+ 0x091c13ee, 0x13002a13, 0x3b001c0d, 0x1c0d095a, // bh.mr.hi_422 mt.et.un_650 id.so.un_540 hi.ne.mr_553
+ // [3270]
+ 0x281c6402, 0x18006b09, 0x0727010c, 0x686b350c, // lg.id.sw_222 ceb.ga.un_440 en.gd.it_543 zu.ceb.ig_543
+ 0x0a2305a4, 0x35001704, 0x0c2a01a4, 0x072301a4, // fr.ca.pt_433 sr.zu.un_320 en.mt.sv_433 en.ca.it_433
+ 0x130108a4, 0x181127a7, 0x122a0708, 0x160932ee, // no.en.et_433 gd.ro.ga_532 it.mt.hu_443 bs.pl.hr_422
+ 0x1c001329, 0x04003f04, 0x2a003114, 0x05001702, // bh.mr.un_960 af.fi.un_320 az.mt.un_660 sr.fr.un_220
+ // [3280]
+ 0x523b2855, 0x0d091312, 0x051a01a4, 0x1e1c1714, // sw.so.ha_442 bh.hi.ne_654 en.tl.fr_433 sr.id.ms_666
+ 0x05090707, 0x191729a9, 0x12001c04, 0x211e290c, // it.pl.fr_432 sl.sr.gl_544 id.hu.un_320 sl.ms.jw_543
+ 0x08100aec, 0x21352711, 0x3b0464a4, 0x35002908, // mk.be.uk_644 gd.zu.jw_653 lg.fi.so_433 sl.zu.un_430
+ 0x08022005, 0x64352111, 0x1f3f0408, 0x1635290c, // sq.da.no_333 jw.zu.lg_653 fi.af.cy_443 sl.zu.hr_543
+ // [3290]
+ 0x0b190a13, 0x3b00352a, 0x1e3b0d12, 0x08170712, // pt.gl.es_665 zu.so.un_970 cs.so.ms_654 bg.sr.uk_654
+ 0x06130c0c, 0x0f131005, 0x0a00110c, 0x190b0c09, // sv.et.de_543 lt.et.lv_333 ro.mk.un_530 sv.es.gl_444
+ 0x641e2505, 0x29000f05, 0x03003b18, 0x171b250b, // eu.ms.lg_333 lv.sl.un_330 so.nl.un_740 eu.tr.sr_542
+ 0x080e1f05, 0x554a6460, 0x16291ca0, 0x061304ec, // cy.is.no_333 lg.yo.rw_664 id.sl.hr_322 fi.et.de_644
+ // [32a0]
+ 0x04080c0c, 0x640a3b11, 0x23000609, 0x11000709, // sv.no.fi_543 so.pt.lg_653 de.ca.un_440 it.ro.un_440
+ 0x27132dad, 0x0b0c0255, 0x310c080c, 0x0e6b2714, // sk.et.gd_643 da.sv.es_442 no.sv.az_543 gd.ceb.is_666
+ 0x29003519, 0x1c111e0c, 0x31002a13, 0x10090fac, // zu.sl.un_750 ms.ro.id_543 mt.az.un_650 lv.pl.lt_632
+ 0x12002721, 0x3f120207, 0x2017290c, 0x20322a02, // gd.hu.un_860 da.hu.af_432 sl.sr.sq_543 mt.bs.sq_222
+ // [32b0]
+ 0x02066bee, 0x1a6b3b12, 0x10002d19, 0x1e000305, // ceb.de.da_422 so.ceb.tl_654 sk.lt.un_750 nl.ms.un_330
+ 0x27555209, 0x06000702, 0x0c0a04ec, 0x11000c1b, // ha.rw.gd_444 it.de.un_220 fi.pt.sv_644 sv.ro.un_770
+ 0x10070413, 0x041b3f08, 0x523b550c, 0x04003f1a, // ru.bg.be_665 af.tr.fi_443 rw.so.ha_543 af.fi.un_760
+ 0x31002a0e, 0x0f310c02, 0x04271813, 0x080e29a4, // mt.az.un_550 sv.az.lv_222 ga.gd.fi_665 sl.is.no_433
+ // [32c0]
+ 0x020611ee, 0x13030c05, 0x2a1e5205, 0x101827ad, // ro.de.da_422 sv.nl.et_333 ha.ms.mt_333 gd.ga.lt_643
+ 0x0e001222, 0x2705010c, 0x04001822, 0x06000104, // hu.is.un_870 en.fr.gd_543 ga.fi.un_870 en.de.un_320
+ 0x2d100faf, 0x1c2b640c, 0x11002809, 0x1f091855, // lv.lt.sk_655 lg.vi.id_543 sw.ro.un_440 ga.pl.cy_442
+ 0x3b2b04a7, 0x16291704, 0x08006b02, 0x21552860, // fi.vi.so_532 sr.sl.hr_332 ceb.no.un_220 sw.rw.jw_664
+ // [32d0]
+ 0x4a113b04, 0x68271813, 0x28000423, 0x1f5311a4, // so.ro.yo_332 ga.gd.ig_665 fi.sw.un_880 ro.ht.cy_433
+ 0x681a6b12, 0x130410a4, 0x08003f02, 0x071a100c, // ceb.tl.ig_654 lt.fi.et_433 af.no.un_220 lt.tl.it_543
+ 0x271107a4, 0x0b00520d, 0x11002a04, 0x00006b2d, // it.ro.gd_433 ha.es.un_540 mt.ro.un_320 ceb.un.un_A00
+ 0x3b002814, 0x0d001318, 0x180e2702, 0x0e003204, // sw.so.un_660 bh.ne.un_740 gd.is.ga_222 bs.is.un_320
+ // [32e0]
+ 0x062a680b, 0x64130f08, 0x1c214aa0, 0x3507640c, // ig.mt.de_542 lv.et.lg_443 yo.jw.id_322 lg.it.zu_543
+ 0x04201305, 0x0f111313, 0x052701a4, 0x2100100d, // et.sq.fi_333 et.ro.lv_665 en.gd.fr_433 lt.jw.un_540
+ 0x1f6b35ee, 0x041801a4, 0x35130f09, 0x011827ad, // zu.ceb.cy_422 en.ga.fi_433 lv.et.zu_444 gd.ga.en_643
+ 0x28133514, 0x073b27ad, 0x171a5255, 0x281f200c, // zu.et.sw_666 gd.so.it_643 ha.tl.sr_442 sq.cy.sw_543
+ // [32f0]
+ 0x0f136408, 0x07100a07, 0x6b2a2713, 0x1e04130c, // lg.et.lv_443 mk.be.bg_432 gd.mt.ceb_665 et.fi.ms_543
+ 0x68041312, 0x0a28350c, 0x021b1808, 0x080201af, // et.fi.ig_654 zu.sw.pt_543 ga.tr.da_443 en.da.no_655
+ 0x1000011a, 0x533f010c, 0x180506a9, 0x0c00030c, // en.lt.un_760 en.af.ht_543 de.fr.ga_544 nl.sv.un_530
+ 0x32160909, 0x27061e05, 0x17232102, 0x533f13ad, // pl.hr.bs_444 ms.de.gd_333 jw.ca.sr_222 et.af.ht_643
+ // [3300]
+ 0x20000219, 0x25033f09, 0x023f13a4, 0x0f642805, // da.sq.un_750 af.nl.eu_444 et.af.da_433 sw.lg.lv_333
+ 0x521328ad, 0x1a0c6ba4, 0x3f1327a4, 0x0420130c, // sw.et.ha_643 ceb.sv.tl_433 gd.et.af_433 et.sq.fi_543
+ 0x19001012, 0x11000121, 0x10001f23, 0x09004a08, // lt.gl.un_640 en.ro.un_860 cy.lt.un_880 yo.pl.un_430
+ 0x12002008, 0x2a001e07, 0x0c2552a4, 0x091827af, // sq.hu.un_430 ms.mt.un_420 ha.eu.sv_433 gd.ga.pl_655
+ // [3310]
+ 0x19530b08, 0x09271812, 0x35006822, 0x3b3f1b08, // es.ht.gl_443 ga.gd.pl_654 ig.zu.un_870 tr.af.so_443
+ 0x04111708, 0x051f18a4, 0x0d001e07, 0x10070a13, // sr.ro.ru_443 ga.cy.fr_433 ms.cs.un_420 mk.bg.be_665
+ 0x5300682a, 0x041355ec, 0x642855af, 0x065301a4, // ig.ht.un_970 rw.et.fi_644 rw.sw.lg_655 en.ht.de_433
+ 0x0603530c, 0x2a182709, 0x2d0d06a9, 0x3f1320a9, // ht.nl.de_543 gd.ga.mt_444 de.cs.sk_544 sq.et.af_544
+ // [3320]
+ 0x18092713, 0x35002313, 0x040d2dad, 0x0600280e, // gd.pl.ga_665 ca.zu.un_650 sk.cs.fi_643 sw.de.un_550
+ 0x09270605, 0x18272a0c, 0x0306270c, 0x12003508, // de.gd.pl_333 mt.gd.ga_543 gd.de.nl_543 zu.hu.un_430
+ 0x64003508, 0x195306a4, 0x0f006819, 0x06003b19, // zu.lg.un_430 de.ht.gl_433 ig.lv.un_750 so.de.un_750
+ 0x2500530c, 0x01003f02, 0x1e1c3ba4, 0x270918af, // ht.eu.un_530 af.en.un_220 so.id.ms_433 ga.pl.gd_655
+ // [3330]
+ 0x091827a9, 0x21000c0c, 0x6805060c, 0x07002a0c, // gd.ga.pl_544 sv.jw.un_530 de.fr.ig_543 mt.it.un_530
+ 0x32172aa9, 0x0e020c0c, 0x0603285a, 0x181f270c, // mt.sr.bs_544 sv.da.is_543 sw.nl.de_553 gd.cy.ga_543
+ 0x4a1e1ca4, 0x64006814, 0x16001208, 0x2a001f34, // id.ms.yo_433 ig.lg.un_660 hu.hr.un_430 cy.mt.un_A80
+ 0x181327ee, 0x08170408, 0x21002011, 0x0e000818, // gd.et.ga_422 ru.sr.uk_443 sq.jw.un_630 no.is.un_740
+ // [3340]
+ 0x23000708, 0x02131a08, 0x250e52ee, 0x2a001e04, // it.ca.un_430 tl.et.da_443 ha.is.eu_422 ms.mt.un_320
+ 0x080a1109, 0x0f1f2aa4, 0x644a2aa4, 0x2000051f, // ro.mk.uk_444 mt.cy.lv_433 mt.yo.lg_433 fr.sq.un_840
+ 0x2b093f07, 0x1b001108, 0x550964a0, 0x68172aa4, // af.pl.vi_432 ro.tr.un_430 lg.pl.rw_322 mt.sr.ig_433
+ 0x131923ad, 0x68052b05, 0x0328090c, 0x080e2504, // ca.gl.et_643 vi.fr.ig_333 pl.sw.nl_543 eu.is.no_332
+ // [3350]
+ 0x08101711, 0x4a002312, 0x4a3f090b, 0x21004a04, // sr.be.uk_653 ca.yo.un_640 pl.af.yo_542 yo.jw.un_320
+ 0x64004a2c, 0x2d001902, 0x05104a09, 0x02002718, // yo.lg.un_990 gl.sk.un_220 yo.lt.fr_444 gd.da.un_740
+ 0x32121707, 0x11132aa4, 0x6400532a, 0x3b065305, // sr.hu.bs_432 mt.et.ro_433 ht.lg.un_970 ht.de.so_333
+ 0x0a0407ac, 0x12001012, 0x010809a7, 0x18000512, // bg.ru.mk_632 lt.hu.un_640 pl.no.en_532 fr.ga.un_640
+ // [3360]
+ 0x1204060d, 0x116b1a07, 0x13041155, 0x32001719, // de.fi.hu_554 tl.ceb.ro_432 ro.fi.et_442 sr.bs.un_750
+ 0x351b31ee, 0x0406110c, 0x640428a4, 0x1f1a3507, // az.tr.zu_422 ro.de.fi_543 sw.fi.lg_433 zu.tl.cy_432
+ 0x64006b08, 0x3f0428ac, 0x1b001a09, 0x641e5207, // ceb.lg.un_430 sw.fi.af_632 tl.tr.un_440 ha.ms.lg_432
+ 0x180627a7, 0x033f3b05, 0x2a002108, 0x05292aee, // gd.de.ga_532 so.af.nl_333 jw.mt.un_430 mt.sl.fr_422
+ // [3370]
+ 0x3f003b1b, 0x1a006e04, 0x53001913, 0x25136bec, // so.af.un_770 hmn.tl.un_320 gl.ht.un_650 ceb.et.eu_644
+ 0x643528a4, 0x04100eec, 0x1e002021, 0x291107ee, // sw.zu.lg_433 is.lt.fi_644 sq.ms.un_860 it.ro.sl_422
+ 0x0f002a29, 0x2a0752ec, 0x1c005307, 0x045203a0, // mt.lv.un_960 ha.it.mt_644 ht.id.un_420 nl.ha.fi_322
+ 0x2d121902, 0x11521008, 0x2d000e13, 0x03001b21, // gl.hu.sk_222 lt.ha.ro_443 is.sk.un_650 tr.nl.un_860
+ // [3380]
+ 0x1c041aa0, 0x111735a7, 0x0e000a0c, 0x10000a04, // tl.fi.id_322 zu.sr.ro_532 pt.is.un_530 mk.be.un_320
+ 0x08002102, 0x283b1a07, 0x040e23a6, 0x533f03a4, // jw.no.un_220 tl.so.sw_432 ca.is.fi_521 nl.af.ht_433
+ 0x0d35040e, 0x0e001004, 0x02041308, 0x03533fa4, // fi.zu.cs_555 lt.is.un_320 et.fi.da_443 af.ht.nl_433
+ 0x170a100c, 0x12001808, 0x0c00110c, 0x040f2aec, // be.mk.sr_543 ga.hu.un_430 ro.sv.un_530 mt.lv.fi_644
+ // [3390]
+ 0x1310115a, 0x111704a4, 0x01003504, 0x3b1712ec, // ro.lt.et_553 ru.sr.ro_433 zu.en.un_320 hu.sr.so_644
+ 0x55006b04, 0x2a060112, 0x0e000104, 0x1b533504, // ceb.rw.un_320 en.de.mt_654 en.is.un_320 zu.ht.tr_332
+ 0x08020ea9, 0x080207a0, 0x080223ac, 0x0503640c, // is.da.no_544 it.da.no_322 ca.da.no_632 lg.nl.fr_543
+ 0x09000321, 0x28006b20, 0x19050b0c, 0x190a05a6, // nl.pl.un_860 ceb.sw.un_850 es.fr.gl_543 fr.pt.gl_521
+ // [33a0]
+ 0x20002802, 0x045528a0, 0x070f5207, 0x190a13ee, // sw.sq.un_220 sw.rw.fi_322 ha.lv.it_432 et.pt.gl_422
+ 0x08013fa7, 0x27001112, 0x082913a4, 0x2d0d0a05, // af.en.no_532 ro.gd.un_640 et.sl.no_433 pt.cs.sk_333
+ 0x5313350c, 0x2a0c08a4, 0x352513a4, 0x18002504, // zu.et.ht_543 no.sv.mt_433 et.eu.zu_433 eu.ga.un_320
+ 0x29096804, 0x04323507, 0x68131f07, 0x25202109, // ig.pl.sl_332 zu.bs.fi_432 cy.et.ig_432 jw.sq.eu_444
+ // [33b0]
+ 0x3f00680d, 0x3b136413, 0x06350c08, 0x230d1213, // ig.af.un_540 lg.et.so_665 sv.zu.de_443 hu.cs.ca_665
+ 0x354a5207, 0x10003204, 0x2064130c, 0x03553555, // ha.yo.zu_432 bs.lt.un_320 et.lg.sq_543 zu.rw.nl_442
+ 0x040a19ee, 0x093125a9, 0x2a4a28ad, 0x25001c08, // gl.pt.fi_422 eu.az.pl_544 sw.yo.mt_643 id.eu.un_430
+ 0x12002508, 0x53002004, 0x28000404, 0x28002a02, // eu.hu.un_430 sq.ht.un_320 fi.sw.un_320 mt.sw.un_220
+ // [33c0]
+ 0x35202507, 0x55125309, 0x08071004, 0x644a3baf, // eu.sq.zu_432 ht.hu.rw_444 be.bg.uk_332 so.yo.lg_655
+ 0x0a051107, 0x130c35a7, 0x3b23250c, 0x3b001a02, // ro.fr.pt_432 zu.sv.et_532 eu.ca.so_543 tl.so.un_220
+ 0x641f28ad, 0x282a6ba4, 0x25041108, 0x1a00011a, // sw.cy.lg_643 ceb.mt.sw_433 ro.fi.eu_443 en.tl.un_760
+ 0x020c0808, 0x2835640c, 0x53000104, 0x02100807, // no.sv.da_443 lg.zu.sw_543 en.ht.un_320 no.lt.da_432
+ // [33d0]
+ 0x0e081fad, 0x011a2114, 0x1e1c52a4, 0x130427ad, // cy.no.is_643 jw.tl.en_666 ha.id.ms_433 gd.fi.et_643
+ 0x211e04a4, 0x3f030113, 0x18000622, 0x1300641a, // fi.ms.jw_433 en.nl.af_665 de.ga.un_870 lg.et.un_760
+ 0x0600101a, 0x08000408, 0x030f3f08, 0x3f1a010b, // lt.de.un_760 ru.uk.un_430 af.lv.nl_443 en.tl.af_542
+ 0x1300640e, 0x321617ad, 0x640413ad, 0x2b001a0d, // lg.et.un_550 sr.hr.bs_643 et.fi.lg_643 tl.vi.un_540
+ // [33e0]
+ 0x0e061faf, 0x21000108, 0x23031908, 0x4a005518, // cy.de.is_655 en.jw.un_430 gl.nl.ca_443 rw.yo.un_740
+ 0x040807a4, 0x3b530555, 0x00002d2d, 0x1a002821, // bg.uk.ru_433 fr.ht.so_442 sk.un.un_A00 sw.tl.un_860
+ 0x03080113, 0x290b530c, 0x031a0112, 0x25216bad, // en.no.nl_665 ht.es.sl_543 en.tl.nl_654 ceb.jw.eu_643
+ 0x282055a4, 0x28005229, 0x550a2307, 0x551a6b08, // rw.sq.sw_433 ha.sw.un_960 ca.pt.rw_432 ceb.tl.rw_443
+ // [33f0]
+ 0x1a01210c, 0x3b5528ec, 0x2a104aa9, 0x06190a09, // jw.en.tl_543 sw.rw.so_644 yo.lt.mt_544 pt.gl.de_444
+ 0x3b20520c, 0x1e001119, 0x092d0faf, 0x040609b3, // ha.sq.so_543 ro.ms.un_750 lv.sk.pl_655 pl.de.fi_743
+ 0x55251a05, 0x12000302, 0x52552012, 0x1c6425a0, // tl.eu.rw_333 nl.hu.un_220 sq.rw.ha_654 eu.lg.id_322
+ 0x063f030c, 0x6b013ba7, 0x2000010d, 0x03250a02, // nl.af.de_543 so.en.ceb_532 en.sq.un_540 pt.eu.nl_222
+
+ // [3400]
+ 0x3b015208, 0x083f12a9, 0x10182755, 0x52553ba9, // ha.en.so_443 hu.af.no_544 gd.ga.lt_442 so.rw.ha_544
+ 0x00002d24, 0x53050c11, 0x283555ac, 0x6b001a29, // sk.un.un_900 sv.fr.ht_653 rw.zu.sw_632 tl.ceb.un_960
+ 0x07080aa4, 0x051f28ee, 0x121c6405, 0x036406a0, // mk.uk.bg_433 sw.cy.fr_422 lg.id.hu_333 de.lg.nl_322
+ 0x255213ec, 0x2b1827af, 0x0c060107, 0x1113320d, // et.ha.eu_644 gd.ga.vi_655 en.de.sv_432 bs.et.ro_554
+ // [3410]
+ 0x6b3b2007, 0x680f20a6, 0x0c060407, 0x1e211c07, // sq.so.ceb_432 sq.lv.ig_521 fi.de.sv_432 id.jw.ms_432
+ 0x07203b07, 0x20683f05, 0x06000412, 0x080c02a9, // so.sq.it_432 af.ig.sq_333 fi.de.un_640 da.sv.no_544
+ 0x552801a4, 0x2800200d, 0x5535280c, 0x070a11a6, // en.sw.rw_433 sq.sw.un_540 sw.zu.rw_543 ro.mk.bg_521
+ 0x114a18a0, 0x0e062705, 0x19280aa0, 0x18005312, // ga.yo.ro_322 gd.de.is_333 pt.sw.gl_322 ht.ga.un_640
+ // [3420]
+ 0x1f0306ad, 0x2d1b0107, 0x1b0f52ee, 0x12051f0c, // de.nl.cy_643 en.tr.sk_432 ha.lv.tr_422 cy.fr.hu_543
+ 0x27181f0c, 0x161732a7, 0x2a0653ec, 0x6b182708, // cy.ga.gd_543 bs.sr.hr_532 ht.de.mt_644 gd.ga.ceb_443
+ 0x061827ec, 0x213f6807, 0x29002a1b, 0x12003f08, // gd.ga.de_644 ig.af.jw_432 mt.sl.un_770 af.hu.un_430
+ 0x0e000707, 0x09130408, 0x063b55a4, 0x1e1c2a14, // it.is.un_420 fi.et.pl_443 rw.so.de_433 mt.id.ms_666
+ // [3430]
+ 0x13002905, 0x05640107, 0x20002a22, 0x55286413, // sl.et.un_330 en.lg.fr_432 mt.sq.un_870 lg.sw.rw_665
+ 0x2a2029af, 0x070501a0, 0x09080e07, 0x6b000108, // sl.sq.mt_655 en.fr.it_322 is.no.pl_432 en.ceb.un_430
+ 0x53010ca0, 0x12180804, 0x00001a2d, 0x01006419, // sv.en.ht_322 no.ga.hu_332 tl.un.un_A00 lg.en.un_750
+ 0x20092d11, 0x03006422, 0x0a18050c, 0x0c00080c, // sk.pl.sq_653 lg.nl.un_870 fr.ga.pt_543 no.sv.un_530
+ // [3440]
+ 0x2518270e, 0x31182709, 0x0a002814, 0x29071805, // gd.ga.eu_555 gd.ga.az_444 sw.pt.un_660 ga.it.sl_333
+ 0x0200060c, 0x1b091ca0, 0x0e3b1fa4, 0x081013a4, // de.da.un_530 id.pl.tr_322 cy.so.is_433 et.lt.no_433
+ 0x55356e0c, 0x08020f05, 0x31191fec, 0x1f68200c, // hmn.zu.rw_543 lv.da.no_333 cy.gl.az_644 sq.ig.cy_543
+ 0x04050112, 0x053f060c, 0x28684aaf, 0x08040c04, // en.fr.fi_654 de.af.fr_543 yo.ig.sw_655 sv.fi.no_332
+ // [3450]
+ 0x0f0e52a9, 0x061e1ca4, 0x521a6414, 0x681f4aa9, // ha.is.lv_544 id.ms.de_433 lg.tl.ha_666 yo.cy.ig_544
+ 0x042a0e11, 0x1304290c, 0x04132107, 0x20042a0c, // is.mt.fi_653 sl.fi.et_543 jw.et.fi_432 mt.fi.sq_543
+ 0x18030655, 0x13290413, 0x11070408, 0x041312a4, // de.nl.ga_442 fi.sl.et_665 ru.bg.ro_443 hu.et.fi_433
+ 0x283b130c, 0x3f3b09ec, 0x12000c08, 0x1732160c, // et.so.sw_543 pl.so.af_644 sv.hu.un_430 hr.bs.sr_543
+ // [3460]
+ 0x1e1c53ee, 0x1a643511, 0x16006808, 0x2b001308, // ht.id.ms_422 zu.lg.tl_653 ig.hr.un_430 et.vi.un_430
+ 0x0d001602, 0x1b3b2804, 0x2b0301ad, 0x5200282c, // hr.cs.un_220 sw.so.tr_332 en.nl.vi_643 sw.ha.un_990
+ 0x1b005208, 0x28002513, 0x21532508, 0x21005312, // ha.tr.un_430 eu.sw.un_650 eu.ht.jw_443 ht.jw.un_640
+ 0x10002102, 0x126e1b04, 0x322d1702, 0x1b0129a0, // jw.lt.un_220 tr.hmn.hu_332 sr.sk.bs_222 sl.en.tr_322
+ // [3470]
+ 0x103f5211, 0x28531cad, 0x0a000809, 0x1e0c1ca0, // ha.af.lt_653 id.ht.sw_643 no.pt.un_440 id.sv.ms_322
+ 0x190b01a9, 0x12064a07, 0x35002134, 0x180a0560, // en.es.gl_544 yo.de.hu_432 jw.zu.un_A80 fr.pt.ga_664
+ 0x0802210e, 0x1e006812, 0x19230b08, 0x1f00070b, // jw.da.no_555 ig.ms.un_640 es.ca.gl_443 it.cy.un_520
+ 0x351a21a9, 0x11003505, 0x6b091a07, 0x321701ee, // jw.tl.zu_544 zu.ro.un_330 tl.pl.ceb_432 en.sr.bs_422
+ // [3480]
+ 0x1b000420, 0x3b00041b, 0x2b016b0c, 0x0a112704, // fi.tr.un_850 fi.so.un_770 ceb.en.vi_543 gd.ro.pt_332
+ 0x1f2718a4, 0x551b1aec, 0x20211ca4, 0x020e08a4, // ga.gd.cy_433 tl.tr.rw_644 id.jw.sq_433 no.is.da_433
+ 0x1b1a1ea9, 0x21183555, 0x110b0907, 0x1c000a04, // ms.tl.tr_544 zu.ga.jw_442 pl.es.ro_432 pt.id.un_320
+ 0x1b351ea4, 0x3528520c, 0x3564280c, 0x0c201f0c, // ms.zu.tr_433 ha.sw.zu_543 sw.lg.zu_543 cy.sq.sv_543
+ // [3490]
+ 0x28551e07, 0x0a236b02, 0x1a521ea0, 0x13103bec, // ms.rw.sw_432 ceb.ca.pt_222 ms.ha.tl_322 so.lt.et_644
+ 0x1b022907, 0x061c6ea6, 0x35001a09, 0x530c0208, // sl.da.tr_432 hmn.id.de_521 tl.zu.un_440 da.sv.ht_443
+ 0x5328050c, 0x1100010c, 0x23000b04, 0x06056eee, // fr.sw.ht_543 en.ro.un_530 es.ca.un_320 hmn.fr.de_422
+ 0x21642809, 0x20131eee, 0x2b6452a0, 0x13121b0c, // sw.lg.jw_444 ms.et.sq_422 ha.lg.vi_322 tr.hu.et_543
+ // [34a0]
+ 0x011852a4, 0x1f006e12, 0x1b35550c, 0x101104ad, // ha.ga.en_433 hmn.cy.un_640 rw.zu.tr_543 ru.ro.be_643
+ 0x3b203112, 0x0b0501a4, 0x2d0d0ba4, 0x25070cec, // az.sq.so_654 en.fr.es_433 es.cs.sk_433 sv.it.eu_644
+ 0x100413ad, 0x121e25a0, 0x2a1e310c, 0x08000220, // et.fi.lt_643 eu.ms.hu_322 az.ms.mt_543 da.no.un_850
+ 0x6b110c07, 0x132820ad, 0x3511550c, 0x0b4a1908, // sv.ro.ceb_432 sq.sw.et_643 rw.ro.zu_543 gl.yo.es_443
+ // [34b0]
+ 0x55271ea4, 0x1a001c04, 0x0a002d0d, 0x312b1c0c, // ms.gd.rw_433 id.tl.un_320 sk.pt.un_540 id.vi.az_543
+ 0x353b1e04, 0x0f0c1304, 0x21356408, 0x0b0f0ca4, // ms.so.zu_332 et.sv.lv_332 lg.zu.jw_443 sv.lv.es_433
+ 0x085327a4, 0x27180c0b, 0x1004070c, 0x3f1b13af, // gd.ht.no_433 sv.ga.gd_542 bg.ru.be_543 et.tr.af_655
+ 0x18283fa9, 0x3f1b1260, 0x12000a07, 0x17110aa4, // af.sw.ga_544 hu.tr.af_664 pt.hu.un_420 mk.ro.sr_433
+ // [34c0]
+ 0x08004a04, 0x321617ac, 0x1b006404, 0x1c211eec, // yo.no.un_320 sr.hr.bs_632 lg.tr.un_320 ms.jw.id_644
+ 0x100417a9, 0x12001707, 0x251e1c08, 0x0d091c60, // sr.ru.be_544 sr.hu.un_420 id.ms.eu_443 mr.hi.ne_664
+ 0x6e2718ad, 0x0a000307, 0x082706ad, 0x0a1e25ad, // ga.gd.hmn_643 nl.pt.un_420 de.gd.no_643 eu.ms.pt_643
+ 0x270d0607, 0x12002d04, 0x1c1e2511, 0x3b000619, // de.cs.gd_432 sk.hu.un_320 eu.ms.id_653 de.so.un_750
+ // [34d0]
+ 0x210a25ee, 0x4a2d18af, 0x12131b08, 0x21000507, // eu.pt.jw_422 ga.sk.yo_655 tr.et.hu_443 fr.jw.un_420
+ 0x03211b0c, 0x163217a7, 0x0708170c, 0x12000708, // tr.jw.nl_543 sr.bs.hr_532 sr.uk.bg_543 it.hu.un_430
+ 0x06130a05, 0x191327a9, 0x031a3f04, 0x06000521, // pt.et.de_333 gd.et.gl_544 af.tl.nl_332 fr.de.un_860
+ 0x1e002514, 0x1f00210e, 0x03001f19, 0x130627a0, // eu.ms.un_660 jw.cy.un_550 cy.nl.un_750 gd.de.et_322
+ // [34e0]
+ 0x06001a04, 0x2a00040c, 0x053217a0, 0x31135204, // tl.de.un_320 fi.mt.un_530 sr.bs.fr_322 ha.et.az_332
+ 0x13000e18, 0x033f1b08, 0x13000b02, 0x1f001807, // is.et.un_740 tr.af.nl_443 es.et.un_220 ga.cy.un_420
+ 0x521b1ea4, 0x19001a02, 0x23070e0c, 0x0f001b12, // ms.tr.ha_433 tl.gl.un_220 is.it.ca_543 tr.lv.un_640
+ 0x13091c12, 0x081107a9, 0x1732520c, 0x1c292aaf, // mr.hi.bh_654 bg.ro.uk_544 ha.bs.sr_543 mt.sl.id_655
+ // [34f0]
+ 0x04061311, 0x08020e09, 0x13060508, 0x2b1e1c0d, // et.de.fi_653 is.da.no_444 fr.de.et_443 id.ms.vi_554
+ 0x2b0221a0, 0x0f002813, 0x171629af, 0x1b072a09, // jw.da.vi_322 sw.lv.un_650 sl.hr.sr_655 mt.it.tr_444
+ 0x05110111, 0x19211a0d, 0x2b115204, 0x070a10a7, // en.ro.fr_653 tl.jw.gl_554 ha.ro.vi_332 be.mk.bg_532
+ 0x6b003513, 0x32005207, 0x070c0412, 0x050201ee, // zu.ceb.un_650 ha.bs.un_420 fi.sv.it_654 en.da.fr_422
+ // [3500]
+ 0x170f0404, 0x0100070e, 0x0d001818, 0x29132a12, // fi.lv.sr_332 it.en.un_550 ga.cs.un_740 mt.et.sl_654
+ 0x2d0d0905, 0x0b231208, 0x646b1a14, 0x213b2804, // pl.cs.sk_333 hu.ca.es_443 tl.ceb.lg_666 sw.so.jw_332
+ 0x05073f08, 0x13041207, 0x0d2a5312, 0x180427a0, // af.it.fr_443 hu.fi.et_432 ht.mt.cs_654 gd.fi.ga_322
+ 0x07003512, 0x18000408, 0x09000f20, 0x643b28a4, // zu.it.un_640 fi.ga.un_430 lv.pl.un_850 sw.so.lg_433
+ // [3510]
+ 0x1a040fec, 0x041707a9, 0x28003b1a, 0x3b21520c, // lv.fi.tl_644 bg.sr.ru_544 so.sw.un_760 ha.jw.so_543
+ 0x19120b0c, 0x6b160807, 0x2703180e, 0x2511285a, // es.hu.gl_543 no.hr.ceb_432 ga.nl.gd_555 sw.ro.eu_553
+ 0x2a002012, 0x2d002a09, 0x10000e18, 0x1218270d, // sq.mt.un_640 mt.sk.un_440 is.lt.un_740 gd.ga.hu_554
+ 0x28003114, 0x033f02af, 0x170704a9, 0x030a07a0, // az.sw.un_660 da.af.nl_655 ru.bg.sr_544 it.pt.nl_322
+ // [3520]
+ 0x0e0906a0, 0x27062a55, 0x0f1629a9, 0x03011855, // de.pl.is_322 mt.de.gd_442 sl.hr.lv_544 ga.en.nl_442
+ 0x642810ad, 0x23285502, 0x35092805, 0x061e1c14, // lt.sw.lg_643 rw.sw.ca_222 sw.pl.zu_333 id.ms.de_666
+ 0x07000804, 0x32002108, 0x64201ea4, 0x0f060704, // uk.bg.un_320 jw.bs.un_430 ms.sq.lg_433 it.de.lv_332
+ 0x1732160b, 0x0500270c, 0x09033f11, 0x5304050c, // hr.bs.sr_542 gd.fr.un_530 af.nl.pl_653 fr.fi.ht_543
+ // [3530]
+ 0x16290d12, 0x20171ea0, 0x2a1e3108, 0x2d0d4aa9, // cs.sl.hr_654 ms.sr.sq_322 az.ms.mt_443 yo.cs.sk_544
+ 0x1a2564a4, 0x1b3268a4, 0x11000f19, 0x68001a13, // lg.eu.tl_433 ig.bs.tr_433 lv.ro.un_750 tl.ig.un_650
+ 0x030f2a07, 0x135525a4, 0x0b2d1008, 0x35033f0c, // mt.lv.nl_432 eu.rw.et_433 lt.sk.es_443 af.nl.zu_543
+ 0x06130855, 0x0d122d08, 0x0b2319a9, 0x062b27a6, // no.et.de_442 sk.hu.cs_443 gl.ca.es_544 gd.vi.de_521
+ // [3540]
+ 0x351b64ec, 0x21102d07, 0x52000208, 0x2000210d, // lg.tr.zu_644 sk.lt.jw_432 da.ha.un_430 jw.sq.un_540
+ 0x06085308, 0x20356408, 0x2a071ea4, 0x12002a19, // ht.no.de_443 lg.zu.sq_443 ms.it.mt_433 mt.hu.un_750
+ 0x1604530c, 0x11041312, 0x1e6807ad, 0x52000221, // ht.fi.hr_543 et.fi.ro_654 it.ig.ms_643 da.ha.un_860
+ 0x211a4a0c, 0x0d1c0960, 0x1304640c, 0x29641b0c, // yo.tl.jw_543 hi.mr.ne_664 lg.fi.et_543 tr.lg.sl_543
+ // [3550]
+ 0x12001e04, 0x2d0d1ba4, 0x020c080d, 0x1300350c, // ms.hu.un_320 tr.cs.sk_433 no.sv.da_554 zu.et.un_530
+ 0x35551308, 0x53002b14, 0x286435a4, 0x2a003107, // et.rw.zu_443 vi.ht.un_660 zu.lg.sw_433 az.mt.un_420
+ 0x0600021b, 0x27083f0c, 0x0d092d08, 0x35041307, // da.de.un_770 af.no.gd_543 sk.pl.cs_443 et.fi.zu_432
+ 0x2a230c5a, 0x0c001704, 0x1e1c2109, 0x050418ad, // sv.ca.mt_553 sr.sv.un_320 jw.id.ms_444 ga.fi.fr_643
+ // [3560]
+ 0x033f0605, 0x10002a0c, 0x093216a4, 0x0c1b1304, // de.af.nl_333 mt.lt.un_530 hr.bs.pl_433 et.tr.sv_332
+ 0x04131b0c, 0x1c1e2155, 0x3f311ba4, 0x080201a0, // tr.et.fi_543 jw.ms.id_442 tr.az.af_433 en.da.no_322
+ 0x0c13040c, 0x132a3f09, 0x0425030c, 0x0e002121, // fi.et.sv_543 af.mt.et_444 nl.eu.fi_543 jw.is.un_860
+ 0x35081355, 0x1013350c, 0x25120709, 0x4a232107, // et.no.zu_442 zu.et.lt_543 it.hu.eu_444 jw.ca.yo_432
+ // [3570]
+ 0x1a6b3f13, 0x013f060c, 0x08004a02, 0x53231f55, // af.ceb.tl_665 de.af.en_543 yo.no.un_220 cy.ca.ht_442
+ 0x072501a4, 0x2a000121, 0x04000b07, 0x033f060d, // en.eu.it_433 en.mt.un_860 es.fi.un_420 de.af.nl_554
+ 0x1c272107, 0x253f0655, 0x64286860, 0x0428130c, // jw.gd.id_432 de.af.eu_442 ig.sw.lg_664 et.sw.fi_543
+ 0x53000322, 0x642868af, 0x07042a0c, 0x071868a0, // nl.ht.un_870 ig.sw.lg_655 mt.fi.it_543 ig.ga.it_322
+ // [3580]
+ 0x212a20ad, 0x27006b05, 0x080e2012, 0x6404680e, // sq.mt.jw_643 ceb.gd.un_330 sq.is.no_654 ig.fi.lg_555
+ 0x103f13a7, 0x20000e1b, 0x311b3f13, 0x3f01130c, // et.af.lt_532 is.sq.un_770 af.tr.az_665 et.en.af_543
+ 0x112a01a4, 0x114a6407, 0x533f2aa9, 0x041321a7, // en.mt.ro_433 lg.yo.ro_432 mt.af.ht_544 jw.et.fi_532
+ 0x16004a04, 0x046821ad, 0x21006e07, 0x171632a6, // yo.hr.un_320 jw.ig.fi_643 hmn.jw.un_420 bs.hr.sr_521
+ // [3590]
+ 0x1f103b0c, 0x64011f05, 0x20006e08, 0x033f01a7, // so.lt.cy_543 cy.en.lg_333 hmn.sq.un_430 en.af.nl_532
+ 0x033f6ead, 0x0b0f29ee, 0x6e00202b, 0x07100411, // hmn.af.nl_643 sl.lv.es_422 sq.hmn.un_980 ru.be.bg_653
+ 0x1a003509, 0x080e235a, 0x352164a4, 0x6e033f13, // zu.tl.un_440 ca.is.no_553 lg.jw.zu_433 af.nl.hmn_665
+ 0x0e0f1fad, 0x53001605, 0x19002104, 0x644a21ec, // cy.lv.is_643 hr.ht.un_330 jw.gl.un_320 jw.yo.lg_644
+ // [35a0]
+ 0x033f6ea4, 0x172a1104, 0x3b003f19, 0x1f6828a9, // hmn.af.nl_433 ro.mt.sr_332 af.so.un_750 sw.ig.cy_544
+ 0x04001e02, 0x10280405, 0x101f1105, 0x1200551b, // ms.fi.un_220 fi.sw.lt_333 ro.cy.lt_333 rw.hu.un_770
+ 0x10001c02, 0x27001f19, 0x17001007, 0x08250411, // id.lt.un_220 cy.gd.un_750 lt.sr.un_420 fi.eu.no_653
+ 0x25006b07, 0x27001f07, 0x12000713, 0x4a1235ec, // ceb.eu.un_420 cy.gd.un_420 it.hu.un_650 zu.hu.yo_644
+ // [35b0]
+ 0x00006837, 0x13006e13, 0x173216a7, 0x1c3b1e07, // ig.un.un_B00 hmn.et.un_650 hr.bs.sr_532 ms.so.id_432
+ 0x08040a11, 0x0f006e02, 0x0f0e1005, 0x1f030ca0, // mk.ru.uk_653 hmn.lv.un_220 lt.is.lv_333 sv.nl.cy_322
+ 0x1b351105, 0x64135202, 0x080c1bec, 0x01180eee, // ro.zu.tr_333 ha.et.lg_222 tr.sv.no_644 is.ga.en_422
+ 0x0e4a0609, 0x55211e05, 0x68002508, 0x16002004, // de.yo.is_444 ms.jw.rw_333 eu.ig.un_430 sq.hr.un_320
+ // [35c0]
+ 0x073f6e0c, 0x27002a08, 0x311b3b0d, 0x32002804, // hmn.af.it_543 mt.gd.un_430 so.tr.az_554 sw.bs.un_320
+ 0x0000240a, 0x271e1fa4, 0x1e1b0609, 0x080205af, // yi.un.un_500 cy.ms.gd_433 de.tr.ms_444 fr.da.no_655
+ 0x1f232512, 0x1f060812, 0x0000160a, 0x2768530c, // eu.ca.cy_654 no.de.cy_654 hr.un.un_500 ht.ig.gd_543
+ 0x170407af, 0x0f2b0308, 0x080252a4, 0x20000f14, // bg.ru.sr_655 nl.vi.lv_443 ha.da.no_433 lv.sq.un_660
+ // [35d0]
+ 0x19181104, 0x5500682a, 0x4a001a2b, 0x0f1a530b, // ro.ga.gl_332 ig.rw.un_970 tl.yo.un_980 ht.tl.lv_542
+ 0x0c3b3fa0, 0x271808a9, 0x1b250c07, 0x3b001921, // af.so.sv_322 no.ga.gd_544 sv.eu.tr_432 gl.so.un_860
+ 0x13352905, 0x0c1153ad, 0x03133f07, 0x2b000108, // sl.zu.et_333 ht.ro.sv_643 af.et.nl_432 en.vi.un_430
+ 0x190701a4, 0x10311b5a, 0x20136807, 0x0a0723a9, // en.it.gl_433 tr.az.lt_553 ig.et.sq_432 ca.it.pt_544
+ // [35e0]
+ 0x1a311b09, 0x09023fa0, 0x6413040c, 0x1b001012, // tr.az.tl_444 af.da.pl_322 fi.et.lg_543 lt.tr.un_640
+ 0x1a3b1e12, 0x04106eee, 0x101b3113, 0x3b6b1a0c, // ms.so.tl_654 hmn.lt.fi_422 az.tr.lt_665 tl.ceb.so_543
+ 0x0d5553ee, 0x6e00531a, 0x050729a0, 0x3b6b1ca4, // ht.rw.cs_422 ht.hmn.un_760 sl.it.fr_322 id.ceb.so_433
+ 0x184a6e04, 0x6b080108, 0x12316ead, 0x3b1a1c07, // hmn.yo.ga_332 en.no.ceb_443 hmn.az.hu_643 id.tl.so_432
+ // [35f0]
+ 0x53311b14, 0x036b3fa0, 0x21003b13, 0x12002819, // tr.az.ht_666 af.ceb.nl_322 so.jw.un_650 sw.hu.un_750
+ 0x2a0452ad, 0x29000607, 0x12211aa0, 0x0a000412, // ha.fi.mt_643 de.sl.un_420 tl.jw.hu_322 ru.mk.un_640
+ 0x643b1a0c, 0x13270eec, 0x16006b04, 0x2a101b13, // tl.so.lg_543 is.gd.et_644 ceb.hr.un_320 tr.lt.mt_665
+ 0x28211a0c, 0x1e1a03ec, 0x52003b0c, 0x521a1b09, // tl.jw.sw_543 nl.tl.ms_644 so.ha.un_530 tr.tl.ha_444
+ // [3600]
+ 0x0d090bba, 0x1e6b1a13, 0x06002119, 0x01002702, // bn.hi.ne_843 tl.ceb.ms_665 jw.de.un_750 gd.en.un_220
+ 0x27060ead, 0x081805ad, 0x1e3b1cec, 0x321b53a0, // is.de.gd_643 fr.ga.no_643 id.so.ms_644 ht.tr.bs_322
+ 0x25001b22, 0x52123104, 0x230a010c, 0x01686b04, // tr.eu.un_870 az.hu.ha_332 en.pt.ca_543 ceb.ig.en_332
+ 0x0a00050c, 0x1716290c, 0x055507a4, 0x1b00120d, // fr.pt.un_530 sl.hr.sr_543 it.rw.fr_433 hu.tr.un_540
+ // [3610]
+ 0x29001702, 0x0c0608a4, 0x10061f0b, 0x64031aa7, // sr.sl.un_220 no.de.sv_433 cy.de.lt_542 tl.nl.lg_532
+ 0x18270eaf, 0x103b520c, 0x09000d02, 0x27180ead, // is.gd.ga_655 ha.so.lt_543 ne.hi.un_220 is.ga.gd_643
+ 0x0b231913, 0x0e2a270c, 0x0a1f25a4, 0x271811a0, // gl.ca.es_665 gd.mt.is_543 eu.cy.pt_433 ro.ga.gd_322
+ 0x081011a0, 0x1e554a04, 0x20020555, 0x19001307, // ro.be.uk_322 yo.rw.ms_332 fr.da.sq_442 et.gl.un_420
+ // [3620]
+ 0x3f0a01ad, 0x13201005, 0x1e3b1ca4, 0x2835550c, // en.pt.af_643 lt.sq.et_333 id.so.ms_433 rw.zu.sw_543
+ 0x53000119, 0x1118270c, 0x23201ca0, 0x2a1b13a7, // en.ht.un_750 gd.ga.ro_543 id.sq.ca_322 et.tr.mt_532
+ 0x11202304, 0x523b1e05, 0x1b005519, 0x201b5312, // ca.sq.ro_332 ms.so.ha_333 rw.tr.un_750 ht.tr.sq_654
+ 0x3f002119, 0x176e20ad, 0x17002102, 0x06000f02, // jw.af.un_750 sq.hmn.sr_643 jw.sr.un_220 lv.de.un_220
+ // [3630]
+ 0x13006e04, 0x3b001909, 0x210520ad, 0x3f0b23a4, // hmn.et.un_320 gl.so.un_440 sq.fr.jw_643 ca.es.af_433
+ 0x27310c02, 0x1c000920, 0x52001702, 0x1b005513, // sv.az.gd_222 hi.mr.un_850 sr.ha.un_220 rw.tr.un_650
+ 0x2d0d21af, 0x1600080c, 0x6b0607ee, 0x20285208, // jw.cs.sk_655 no.hr.un_530 it.de.ceb_422 ha.sw.sq_443
+ 0x1e1c13a4, 0x0b05210b, 0x0e001b2b, 0x28202108, // et.id.ms_433 jw.fr.es_542 tr.is.un_980 jw.sq.sw_443
+ // [3640]
+ 0x00002a37, 0x551b53ad, 0x16001c04, 0x11051707, // mt.un.un_B00 ht.tr.rw_643 id.hr.un_320 sr.fr.ro_432
+ 0x0c003204, 0x1a000713, 0x3f1f0707, 0x35061faf, // bs.sv.un_320 it.tl.un_650 it.cy.af_432 cy.de.zu_655
+ 0x11000704, 0x0d004a08, 0x1b0d31a0, 0x130d1c11, // it.ro.un_320 yo.cs.un_430 az.cs.tr_322 mr.ne.bh_653
+ 0x02000c13, 0x1b3f0311, 0x28355512, 0x3f000704, // sv.da.un_650 nl.af.tr_653 rw.zu.sw_654 it.af.un_320
+ // [3650]
+ 0x0a0807af, 0x100e1aee, 0x07282008, 0x0e311b11, // bg.uk.mk_655 tl.is.lt_422 sq.sw.it_443 tr.az.is_653
+ 0x20070ca4, 0x2d0d130c, 0x64205508, 0x162d29a9, // sv.it.sq_433 et.cs.sk_543 rw.sq.lg_443 sl.sk.hr_544
+ 0x0213290c, 0x21051007, 0x11003105, 0x19060a0c, // sl.et.da_543 lt.fr.jw_432 az.ro.un_330 pt.de.gl_543
+ 0x322a53a0, 0x04001305, 0x6e00062a, 0x52211ea4, // ht.mt.bs_322 et.fi.un_330 de.hmn.un_970 ms.jw.ha_433
+ // [3660]
+ 0x10060107, 0x4a1e1c08, 0x076e27ad, 0x113f0312, // en.de.lt_432 id.ms.yo_443 gd.hmn.it_643 nl.af.ro_654
+ 0x1b53120c, 0x080225a4, 0x23000a08, 0x1e2535ee, // hu.ht.tr_543 eu.da.no_433 pt.ca.un_430 zu.eu.ms_422
+ 0x0c68120c, 0x03096ead, 0x21001904, 0x03063f13, // hu.ig.sv_543 hmn.pl.nl_643 gl.jw.un_320 af.de.nl_665
+ 0x201b320c, 0x19110b12, 0x0f6e11ad, 0x6e000512, // bs.tr.sq_543 es.ro.gl_654 ro.hmn.lv_643 fr.hmn.un_640
+ // [3670]
+ 0x3b00350d, 0x27000419, 0x28352dad, 0x0400251a, // zu.so.un_540 fi.gd.un_750 sk.zu.sw_643 eu.fi.un_760
+ 0x17250302, 0x122b2107, 0x132355ee, 0x04004a11, // nl.eu.sr_222 jw.vi.hu_432 rw.ca.et_422 yo.fi.un_630
+ 0x0a0708ad, 0x55283ba4, 0x122a6e02, 0x2a00051b, // uk.bg.mk_643 so.sw.rw_433 hmn.mt.hu_222 fr.mt.un_770
+ 0x1f52070d, 0x060e04a9, 0x4a2d0d0e, 0x043b2aa4, // it.ha.cy_554 fi.is.de_544 cs.sk.yo_555 mt.so.fi_433
+ // [3680]
+ 0x0e2d0d0e, 0x08030e0c, 0x27531c07, 0x640e2a05, // cs.sk.is_555 is.nl.no_543 id.ht.gd_432 mt.is.lg_333
+ 0x0408020d, 0x172a0f04, 0x2b002702, 0x313b640c, // da.no.fi_554 lv.mt.sr_332 gd.vi.un_220 lg.so.az_543
+ 0x170f29ad, 0x0b001008, 0x0d160fa0, 0x230501af, // sl.lv.sr_643 lt.es.un_430 lv.hr.cs_322 en.fr.ca_655
+ 0x041011ec, 0x29000d0d, 0x09002d07, 0x070e5208, // ro.be.ru_644 cs.sl.un_540 sk.pl.un_420 ha.is.it_443
+ // [3690]
+ 0x52001704, 0x27001a05, 0x0e080ca7, 0x0553010c, // sr.ha.un_320 tl.gd.un_330 sv.no.is_532 en.ht.fr_543
+ 0x03056407, 0x17552907, 0x2a352d09, 0x313b3507, // lg.fr.nl_432 sl.rw.sr_432 sk.zu.mt_444 zu.so.az_432
+ 0x040f27a9, 0x12190ba4, 0x022a0808, 0x281b3112, // gd.lv.fi_544 es.gl.hu_433 no.mt.da_443 az.tr.sw_654
+ 0x180e27a9, 0x201e1c0c, 0x0e3b5209, 0x29003208, // gd.is.ga_544 id.ms.sq_543 ha.so.is_444 bs.sl.un_430
+ // [36a0]
+ 0x20111b0d, 0x050713a4, 0x21002702, 0x35641ea4, // tr.ro.sq_554 et.it.fr_433 gd.jw.un_220 ms.lg.zu_433
+ 0x05312aa6, 0x07081208, 0x033f52ad, 0x0f1f5309, // mt.az.fr_521 hu.no.it_443 ha.af.nl_643 ht.cy.lv_444
+ 0x095564ad, 0x281b3204, 0x0e6408a0, 0x171107a4, // lg.rw.pl_643 bs.tr.sw_332 no.lg.is_322 bg.ro.sr_433
+ 0x530b1ca4, 0x0b0e2a07, 0x10000719, 0x3f2106af, // id.es.ht_433 mt.is.es_432 it.lt.un_750 de.jw.af_655
+ // [36b0]
+ 0x2b005308, 0x271f1813, 0x31001b07, 0x27180e0c, // ht.vi.un_430 ga.cy.gd_665 tr.az.un_420 is.ga.gd_543
+ 0x18000c1a, 0x18002b1a, 0x191c1e0c, 0x100429a0, // sv.ga.un_760 vi.ga.un_760 ms.id.gl_543 sl.fi.lt_322
+ 0x1f003202, 0x13092504, 0x35005208, 0x0f190ba4, // bs.cy.un_220 eu.pl.et_332 ha.zu.un_430 es.gl.lv_433
+ 0x16000e19, 0x08000e21, 0x171c0807, 0x320f0a08, // is.hr.un_750 is.no.un_860 no.id.sr_432 pt.lv.bs_443
+ // [36c0]
+ 0x03004a04, 0x081a2111, 0x19230aaf, 0x17092d09, // yo.nl.un_320 jw.tl.no_653 pt.ca.gl_655 sk.pl.sr_444
+ 0x2000530e, 0x32001612, 0x281035ad, 0x25190aad, // ht.sq.un_550 hr.bs.un_640 zu.lt.sw_643 pt.gl.eu_643
+ 0x2a001604, 0x3b201fa7, 0x03000202, 0x35080205, // hr.mt.un_320 cy.sq.so_532 da.nl.un_220 da.no.zu_333
+ 0x182d3507, 0x202a06ad, 0x27212a07, 0x13003511, // zu.sk.ga_432 de.mt.sq_643 mt.jw.gd_432 zu.et.un_630
+ // [36d0]
+ 0x3500250c, 0x17162aa0, 0x18013f07, 0x2700011a, // eu.zu.un_530 mt.hr.sr_322 af.en.ga_432 en.gd.un_760
+ 0x31001b35, 0x2d0d0e14, 0x1f23060c, 0x270f1ea0, // tr.az.un_A90 is.cs.sk_666 de.ca.cy_543 ms.lv.gd_322
+ 0x110a05af, 0x16002808, 0x271f0ea0, 0x0a192312, // fr.pt.ro_655 sw.hr.un_430 is.cy.gd_322 ca.gl.pt_654
+ 0x2d0d10a4, 0x06000f12, 0x553507af, 0x182711ee, // lt.cs.sk_433 lv.de.un_640 it.zu.rw_655 ro.gd.ga_422
+ // [36e0]
+ 0x08190aa0, 0x1c216411, 0x041e1c05, 0x1f00062a, // pt.gl.no_322 lg.jw.id_653 id.ms.fi_333 de.cy.un_970
+ 0x016b5307, 0x28002d0d, 0x0e3f0812, 0x6b086404, // ht.ceb.en_432 sk.sw.un_540 no.af.is_654 lg.no.ceb_332
+ 0x17071011, 0x28176407, 0x3f3135af, 0x28556b04, // be.bg.sr_653 lg.sr.sw_432 zu.az.af_655 ceb.rw.sw_332
+ 0x35092113, 0x5500641b, 0x182d0d20, 0x641035ee, // jw.pl.zu_665 lg.rw.un_770 cs.sk.ga_875 zu.lt.lg_422
+ // [36f0]
+ 0x32291604, 0x2b000707, 0x2d0d6e0e, 0x12101a08, // hr.sl.bs_332 it.vi.un_420 hmn.cs.sk_555 tl.lt.hu_443
+ 0x3f1a6bec, 0x0f6435a6, 0x05012bee, 0x1b0612ee, // ceb.tl.af_644 zu.lg.lv_521 vi.en.fr_422 hu.de.tr_422
+ 0x01001902, 0x0911120c, 0x0d001904, 0x23060a02, // gl.en.un_220 hu.ro.pl_543 gl.cs.un_320 pt.de.ca_222
+ 0x07110aa4, 0x05002122, 0x060d0960, 0x64001012, // mk.ro.bg_433 jw.fr.un_870 pl.cs.de_664 lt.lg.un_640
+ // [3700]
+ 0x12002914, 0x0f2d2912, 0x3f1827ad, 0x080205ec, // sl.hu.un_660 sl.sk.lv_654 gd.ga.af_643 fr.da.no_644
+ 0x3f1168a4, 0x27002a13, 0x31006e14, 0x2a0720a4, // ig.ro.af_433 mt.gd.un_650 hmn.az.un_660 sq.it.mt_433
+ 0x01083bee, 0x3f006e14, 0x120b1911, 0x31056eee, // so.no.en_422 hmn.af.un_660 gl.es.hu_653 hmn.fr.az_422
+ 0x12002a14, 0x3f030208, 0x0d091205, 0x4a001212, // mt.hu.un_660 da.nl.af_443 hu.pl.cs_333 hu.yo.un_640
+ // [3710]
+ 0x3b1e31a7, 0x23056ea0, 0x2700061a, 0x21192312, // az.ms.so_532 hmn.fr.ca_322 de.gd.un_760 ca.gl.jw_654
+ 0x110717a4, 0x27000513, 0x1c130912, 0x3f00270e, // sr.bg.ro_433 fr.gd.un_650 hi.bh.mr_654 gd.af.un_550
+ 0x12000404, 0x09293508, 0x3209290c, 0x64351a08, // fi.hu.un_320 zu.sl.pl_443 sl.pl.bs_543 tl.zu.lg_443
+ 0x0c2d0d0c, 0x09041313, 0x071108ad, 0x353128ee, // cs.sk.sv_543 et.fi.pl_665 uk.ro.bg_643 sw.az.zu_422
+ // [3720]
+ 0x6e005321, 0x21640408, 0x1a006408, 0x04080cec, // ht.hmn.un_860 fi.lg.jw_443 lg.tl.un_430 sv.no.fi_644
+ 0x11190b08, 0x11170aa4, 0x04000e1a, 0x080602af, // es.gl.ro_443 mk.sr.ro_433 is.fi.un_760 da.de.no_655
+ 0x03003b12, 0x05000205, 0x18002102, 0x190b53a0, // so.nl.un_640 da.fr.un_330 jw.ga.un_220 ht.es.gl_322
+ 0x092d0d0c, 0x1c001e1a, 0x35001708, 0x6b041a0c, // cs.sk.pl_543 ms.id.un_760 sr.zu.un_430 tl.fi.ceb_543
+ // [3730]
+ 0x170929a4, 0x092d29a4, 0x0f2d0d08, 0x32162905, // sl.pl.sr_433 sl.sk.pl_433 cs.sk.lv_443 sl.hr.bs_333
+ 0x4a110709, 0x04002808, 0x0b000a07, 0x11071005, // it.ro.yo_444 sw.fi.un_430 pt.es.un_420 lt.it.ro_333
+ 0x1c000d18, 0x13072007, 0x08001005, 0x0d6b1a0c, // ne.mr.un_740 sq.it.et_432 be.uk.un_330 tl.ceb.cs_543
+ 0x64002807, 0x11000702, 0x28683bec, 0x1a290ea0, // sw.lg.un_420 it.ro.un_220 so.ig.sw_644 is.sl.tl_322
+ // [3740]
+ 0x32175307, 0x2d120aac, 0x4a00530e, 0x1a203bee, // ht.sr.bs_432 pt.hu.sk_632 ht.yo.un_550 so.sq.tl_422
+ 0x1a043b09, 0x6421685a, 0x09006421, 0x075552a4, // so.fi.tl_444 ig.jw.lg_553 lg.pl.un_860 ha.rw.it_433
+ 0x0b2d0aa0, 0x21190ba4, 0x05000b0e, 0x53000916, // pt.sk.es_322 es.gl.jw_433 es.fr.un_550 pl.ht.un_720
+ 0x2a351a0c, 0x0d091c5a, 0x0d001219, 0x0305070c, // tl.zu.mt_543 mr.hi.ne_553 hu.cs.un_750 it.fr.nl_543
+ // [3750]
+ 0x04080a08, 0x080a1714, 0x1c311ea0, 0x53005204, // mk.uk.ru_443 sr.mk.uk_666 ms.az.id_322 ha.ht.un_320
+ 0x130d1c0d, 0x0413640c, 0x642a350d, 0x12046408, // mr.ne.bh_554 lg.et.fi_543 zu.mt.lg_554 lg.fi.hu_443
+ 0x091a6bec, 0x28006e07, 0x291613a4, 0x6b042a04, // ceb.tl.pl_644 hmn.sw.un_420 et.hr.sl_433 mt.fi.ceb_332
+ 0x13200f07, 0x0d002a04, 0x120b1814, 0x351a4aee, // lv.sq.et_432 mt.cs.un_320 ga.es.hu_666 yo.tl.zu_422
+ // [3760]
+ 0x0800110c, 0x10000f21, 0x21000b04, 0x120b23af, // ro.uk.un_530 lv.lt.un_860 es.jw.un_320 ca.es.hu_655
+ 0x015305ad, 0x0802055a, 0x25000912, 0x00002337, // fr.ht.en_643 fr.da.no_553 pl.eu.un_640 ca.un.un_B00
+ 0x03043f08, 0x68001222, 0x25005522, 0x4a006412, // af.fi.nl_443 hu.ig.un_870 rw.eu.un_870 lg.yo.un_640
+ 0x11002012, 0x2a29120c, 0x35000907, 0x55001c1a, // sq.ro.un_640 hu.sl.mt_543 pl.zu.un_420 id.rw.un_760
+ // [3770]
+ 0x27524a55, 0x062308a4, 0x551b1a0c, 0x1100680d, // yo.ha.gd_442 no.ca.de_433 tl.tr.rw_543 ig.ro.un_540
+ 0x1f520d07, 0x3f0d53a9, 0x110a230e, 0x1a526ba4, // cs.ha.cy_432 ht.cs.af_544 ca.pt.ro_555 ceb.ha.tl_433
+ 0x201b530c, 0x1a3f2912, 0x080c0d0c, 0x0f103507, // ht.tr.sq_543 sl.af.tl_654 cs.sv.no_543 zu.lt.lv_432
+ 0x0c021008, 0x2d003505, 0x08201f12, 0x11105507, // lt.da.sv_443 zu.sk.un_330 cy.sq.no_654 rw.lt.ro_432
+ // [3780]
+ 0x6b3b21a4, 0x0504530c, 0x531a100c, 0x20110a07, // jw.so.ceb_433 ht.fi.fr_543 lt.tl.ht_543 pt.ro.sq_432
+ 0x033f6807, 0x31201e04, 0x322a070b, 0x64135508, // ig.af.nl_432 ms.sq.az_332 it.mt.bs_542 rw.et.lg_443
+ 0x271118ee, 0x0d000508, 0x180127ee, 0x29003202, // ga.ro.gd_422 fr.cs.un_430 gd.en.ga_422 bs.sl.un_220
+ 0x0a1f19af, 0x29002d08, 0x31002819, 0x2b005205, // gl.cy.pt_655 sk.sl.un_430 sw.az.un_750 ha.vi.un_330
+ // [3790]
+ 0x0b1819ee, 0x286b1aec, 0x091c13ec, 0x681827a7, // gl.ga.es_422 tl.ceb.sw_644 bh.mr.hi_644 gd.ga.ig_532
+ 0x531b6b55, 0x27000919, 0x03190bad, 0x1e3168a0, // ceb.tr.ht_442 pl.gd.un_750 es.gl.nl_643 ig.az.ms_322
+ 0x23001e04, 0x0f64130c, 0x2a005219, 0x07002819, // ms.ca.un_320 et.lg.lv_543 ha.mt.un_750 sw.it.un_750
+ 0x1f090ba4, 0x6855280c, 0x683b280c, 0x35061fee, // es.pl.cy_433 sw.rw.ig_543 sw.so.ig_543 cy.de.zu_422
+ // [37a0]
+ 0x0b072312, 0x033f640d, 0x250e1a04, 0x28004a04, // ca.it.es_654 lg.af.nl_554 tl.is.eu_332 yo.sw.un_320
+ 0x0d531f0b, 0x0f001c08, 0x55001907, 0x531b680c, // cy.ht.cs_542 id.lv.un_430 gl.rw.un_420 ig.tr.ht_543
+ 0x04006413, 0x53214aa7, 0x12076409, 0x1a6b2102, // lg.fi.un_650 yo.jw.ht_532 lg.it.hu_444 jw.ceb.tl_222
+ 0x53003507, 0x11002b13, 0x0d1b1ca4, 0x0c0e64a7, // zu.ht.un_420 vi.ro.un_650 id.tr.cs_433 lg.is.sv_532
+ // [37b0]
+ 0x0306050d, 0x115306a4, 0x0b19535a, 0x0c553507, // fr.de.nl_554 de.ht.ro_433 ht.gl.es_553 zu.rw.sv_432
+ 0x21001e13, 0x0a190b0d, 0x03001119, 0x1100190c, // ms.jw.un_650 es.gl.pt_554 ro.nl.un_750 gl.ro.un_530
+ 0x55282009, 0x53202812, 0x05005302, 0x550c08ad, // sq.sw.rw_444 sw.sq.ht_654 ht.fr.un_220 no.sv.rw_643
+ 0x08020ca6, 0x1c311e0b, 0x4a001b2b, 0x0700040d, // sv.da.no_521 ms.az.id_542 tr.yo.un_980 ru.bg.un_540
+ // [37c0]
+ 0x1c00210c, 0x136404ec, 0x17000d0d, 0x6400041a, // jw.id.un_530 fi.lg.et_644 cs.sr.un_540 fi.lg.un_760
+ 0x1c2b1eee, 0x35001121, 0x12556408, 0x25553b12, // ms.vi.id_422 ro.zu.un_860 lg.rw.hu_443 so.rw.eu_654
+ 0x17100460, 0x1c080ea0, 0x1b686455, 0x52122860, // ru.be.sr_664 is.no.id_322 lg.ig.tr_442 sw.hu.ha_664
+ 0x2d0d0a0e, 0x1704110e, 0x55000307, 0x21001e09, // pt.cs.sk_555 ro.ru.sr_555 nl.rw.un_420 ms.jw.un_440
+ // [37d0]
+ 0x4a350e0d, 0x1e211cee, 0x28352aa0, 0x3f3b13a9, // is.zu.yo_554 id.jw.ms_422 mt.zu.sw_322 et.so.af_544
+ 0x4a0b1aee, 0x680128a6, 0x1c1621ee, 0x20003208, // tl.es.yo_422 sw.en.ig_521 jw.hr.id_422 bs.sq.un_430
+ 0x0a0623af, 0x121819a0, 0x0817070c, 0x6b1a0faf, // ca.de.pt_655 gl.ga.hu_322 bg.sr.uk_543 lv.tl.ceb_655
+ 0x060325ee, 0x230b0a08, 0x1729280c, 0x07170860, // eu.nl.de_422 pt.es.ca_443 sw.sl.sr_543 uk.sr.bg_664
+ // [37e0]
+ 0x283153a4, 0x1b110709, 0x20550707, 0x3b1f3555, // ht.az.sw_433 it.ro.tr_444 it.rw.sq_432 zu.cy.so_442
+ 0x0c001c04, 0x082d0d05, 0x21053ba4, 0x0b18190c, // id.sv.un_320 cs.sk.no_333 so.fr.jw_433 gl.ga.es_543
+ 0x172935a0, 0x3b122107, 0x3564310d, 0x0e0801a0, // zu.sl.sr_322 jw.hu.so_432 az.lg.zu_554 en.no.is_322
+ 0x0a5227a4, 0x6e290fa4, 0x2b001e04, 0x09003213, // gd.ha.pt_433 lv.sl.hmn_433 ms.vi.un_320 bs.pl.un_650
+ // [37f0]
+ 0x0a110705, 0x28311b5a, 0x311b3509, 0x3520310c, // bg.ro.mk_333 tr.az.sw_553 zu.tr.az_444 az.sq.zu_543
+ 0x2b001602, 0x3b001f1a, 0x2d000f13, 0x04000c05, // hr.vi.un_220 cy.so.un_760 lv.sk.un_650 sv.fi.un_330
+ 0x2a2d1f07, 0x09190eee, 0x122111a7, 0x0d2d0e11, // cy.sk.mt_432 is.gl.pl_422 ro.jw.hu_532 is.sk.cs_653
+ 0x3b003505, 0x29000804, 0x1f002d1a, 0x31005214, // zu.so.un_330 no.sl.un_320 sk.cy.un_760 ha.az.un_660
+
+ // [3800]
+ 0x536b1aad, 0x09131012, 0x6b6823ee, 0x0c001902, // tl.ceb.ht_643 lt.et.pl_654 ca.ig.ceb_422 gl.sv.un_220
+ 0x1b001c08, 0x09286408, 0x551b4a07, 0x1f000808, // id.tr.un_430 lg.sw.pl_443 yo.tr.rw_432 no.cy.un_430
+ 0x0a1f2da4, 0x6b01110c, 0x55004a1a, 0x232a0709, // sk.cy.pt_433 ro.en.ceb_543 yo.rw.un_760 it.mt.ca_444
+ 0x312855ee, 0x311c52ad, 0x0c0901ad, 0x09250307, // rw.sw.az_422 ha.id.az_643 en.pl.sv_643 nl.eu.pl_432
+ // [3810]
+ 0x0f1c1ea4, 0x112d1207, 0x1b1f530c, 0x1a063b07, // ms.id.lv_433 hu.sk.ro_432 ht.cy.tr_543 so.de.tl_432
+ 0x3f100f09, 0x1b066baf, 0x1b000e1a, 0x1f2a53ec, // lv.lt.af_444 ceb.de.tr_655 is.tr.un_760 ht.mt.cy_644
+ 0x2d1a230c, 0x1200111a, 0x683f100c, 0x521b0ea0, // ca.tl.sk_543 ro.hu.un_760 lt.af.ig_543 is.tr.ha_322
+ 0x0a005507, 0x1b0a0e0c, 0x350c16a0, 0x2a313bad, // rw.pt.un_420 is.pt.tr_543 hr.sv.zu_322 so.az.mt_643
+ // [3820]
+ 0x234a28a7, 0x351a11a4, 0x350613af, 0x08021309, // sw.yo.ca_532 ro.tl.zu_433 et.de.zu_655 et.da.no_444
+ 0x1b003b1b, 0x0f006e13, 0x530b21a9, 0x13062a12, // so.tr.un_770 hmn.lv.un_650 jw.es.ht_544 mt.de.et_654
+ 0x2a6425ad, 0x18041155, 0x6e000113, 0x18005321, // eu.lg.mt_643 ro.fi.ga_442 en.hmn.un_650 ht.ga.un_860
+ 0x0a2923a0, 0x28001104, 0x2a0320ee, 0x23000d0e, // ca.sl.pt_322 ro.sw.un_320 sq.nl.mt_422 cs.ca.un_550
+ // [3830]
+ 0x2a0f6e12, 0x203217a4, 0x040710af, 0x6e00011a, // hmn.lv.mt_654 sr.bs.sq_433 be.bg.ru_655 en.hmn.un_760
+ 0x012005ee, 0x0d001213, 0x100925ad, 0x06110da0, // fr.sq.en_422 hu.cs.un_650 eu.pl.lt_643 cs.ro.de_322
+ 0x212b010c, 0x02000c07, 0x29172da9, 0x1b200a05, // en.vi.jw_543 sv.da.un_420 sk.sr.sl_544 pt.sq.tr_333
+ 0x23000a0c, 0x0510270c, 0x68552813, 0x101c1e0c, // pt.ca.un_530 gd.lt.fr_543 sw.rw.ig_665 ms.id.lt_543
+ // [3840]
+ 0x6b003109, 0x1a283bac, 0x1e1c25ee, 0x311b3b08, // az.ceb.un_440 so.sw.tl_632 eu.id.ms_422 so.tr.az_443
+ 0x201b0f04, 0x282a2307, 0x11011f0c, 0x01002807, // lv.tr.sq_332 ca.mt.sw_432 cy.en.ro_543 sw.en.un_420
+ 0x3f005505, 0x0d091ca9, 0x040d2d0c, 0x321608a4, // rw.af.un_330 mr.hi.ne_544 sk.cs.fi_543 no.hr.bs_433
+ 0x0d202d12, 0x6800251b, 0x3b1f210c, 0x1e00530c, // sk.sq.cs_654 eu.ig.un_770 jw.cy.so_543 ht.ms.un_530
+ // [3850]
+ 0x130d090e, 0x25210405, 0x13090d12, 0x1320040d, // hi.ne.bh_555 fi.jw.eu_333 ne.hi.bh_654 fi.sq.et_554
+ 0x0c00210c, 0x13002505, 0x090164ee, 0x2d040daf, // jw.sv.un_530 eu.et.un_330 lg.en.pl_422 cs.fi.sk_655
+ 0x0b271a12, 0x12355555, 0x55001b22, 0x21001a19, // tl.gd.es_654 rw.zu.hu_442 tr.rw.un_870 tl.jw.un_750
+ 0x210d0908, 0x02083f07, 0x08002d0d, 0x641325a7, // pl.cs.jw_443 af.no.da_432 sk.no.un_540 eu.et.lg_532
+ // [3860]
+ 0x0c080e13, 0x2a006807, 0x0b55680c, 0x6b001e04, // is.no.sv_665 ig.mt.un_420 ig.rw.es_543 ms.ceb.un_320
+ 0x1b120c0c, 0x06036ba0, 0x1f004a08, 0x0b1e25a0, // sv.hu.tr_543 ceb.nl.de_322 yo.cy.un_430 eu.ms.es_322
+ 0x0e061207, 0x23001c02, 0x1200231a, 0x6b551a04, // hu.de.is_432 id.ca.un_220 ca.hu.un_760 tl.rw.ceb_332
+ 0x2800681a, 0x0e190b08, 0x1b0e0cad, 0x2a2016a4, // ig.sw.un_760 es.gl.is_443 sv.is.tr_643 hr.sq.mt_433
+ // [3870]
+ 0x1c21100c, 0x17521a12, 0x126b1faf, 0x6b1f1a60, // lt.jw.id_543 tl.ha.sr_654 cy.ceb.hu_655 tl.cy.ceb_664
+ 0x311b28a9, 0x1e272007, 0x291f0d07, 0x6b1a1e12, // sw.tr.az_544 sq.gd.ms_432 cs.cy.sl_432 ms.tl.ceb_654
+ 0x55311b11, 0x20002819, 0x1a216ba9, 0x3b002018, // tr.az.rw_653 sw.sq.un_750 ceb.jw.tl_544 sq.so.un_740
+ 0x121a6bad, 0x05001022, 0x642853ad, 0x3f031313, // ceb.tl.hu_643 lt.fr.un_870 ht.sw.lg_643 et.nl.af_665
+ // [3880]
+ 0x356423a4, 0x310c1b12, 0x1a6b120c, 0x5564350e, // ca.lg.zu_433 tr.sv.az_654 hu.ceb.tl_543 zu.lg.rw_555
+ 0x0b120505, 0x3564135a, 0x205535ee, 0x0264010c, // fr.hu.es_333 et.lg.zu_553 zu.rw.sq_422 en.lg.da_543
+ 0x6431250d, 0x0600200d, 0x2d190b0d, 0x08060fee, // eu.az.lg_554 sq.de.un_540 es.gl.sk_554 lv.de.no_422
+ 0x25071b0c, 0x3f001a08, 0x31125304, 0x1f190a05, // tr.it.eu_543 tl.af.un_430 ht.hu.az_332 pt.gl.cy_333
+ // [3890]
+ 0x250c0307, 0x03000214, 0x6b6468a7, 0x131f6b0d, // nl.sv.eu_432 da.nl.un_660 ig.lg.ceb_532 ceb.cy.et_554
+ 0x2800352a, 0x31110dee, 0x1f1a1ea0, 0x01205255, // zu.sw.un_970 cs.ro.az_422 ms.tl.cy_322 ha.sq.en_442
+ 0x190b25a0, 0x072555a7, 0x1110310c, 0x20351c05, // eu.es.gl_322 rw.eu.it_532 az.lt.ro_543 id.zu.sq_333
+ 0x68002019, 0x2d0d29ac, 0x351b3114, 0x1c101e07, // sq.ig.un_750 sl.cs.sk_632 az.tr.zu_666 ms.lt.id_432
+ // [38a0]
+ 0x5320110d, 0x036b20a7, 0x64280208, 0x29321614, // ro.sq.ht_554 sq.ceb.nl_532 da.sw.lg_443 hr.bs.sl_666
+ 0x20282d07, 0x19120ba4, 0x100208a4, 0x19000a2a, // sk.sw.sq_432 es.hu.gl_433 no.da.lt_433 pt.gl.un_970
+ 0x0f003504, 0x25122aa0, 0x6b0b1a05, 0x121a6b05, // zu.lv.un_320 mt.hu.eu_322 tl.es.ceb_333 ceb.tl.hu_333
+ 0x121a0cad, 0x0b006412, 0x120c1aa7, 0x3b1b355a, // sv.tl.hu_643 lg.es.un_640 tl.sv.hu_532 zu.tr.so_553
+ // [38b0]
+ 0x06201107, 0x180e12ad, 0x12194a04, 0x64004a02, // ro.sq.de_432 hu.is.ga_643 yo.gl.hu_332 yo.lg.un_220
+ 0x12000b02, 0x2d0d19a0, 0x02063f05, 0x17162905, // es.hu.un_220 gl.cs.sk_322 af.de.da_333 sl.hr.sr_333
+ 0x196468ad, 0x1a001308, 0x3555640d, 0x0d122dec, // ig.lg.gl_643 et.tl.un_430 lg.rw.zu_554 sk.hu.cs_644
+ 0x55000c07, 0x2d3520a4, 0x350413af, 0x131c04ad, // sv.rw.un_420 sq.zu.sk_433 et.fi.zu_655 fi.id.et_643
+ // [38c0]
+ 0x080c0ead, 0x120c1313, 0x281213a4, 0x09131c13, // is.sv.no_643 et.sv.hu_665 et.hu.sw_433 mr.bh.hi_665
+ 0x0c1e0ba4, 0x2d1355a7, 0x29071cee, 0x110108a0, // es.ms.sv_433 rw.et.sk_532 id.it.sl_422 no.en.ro_322
+ 0x13041e12, 0x133b1a09, 0x2b005521, 0x0a0c0607, // ms.fi.et_654 tl.so.et_444 rw.vi.un_860 de.sv.pt_432
+ 0x0c061f0c, 0x01192304, 0x0e231905, 0x52006e0e, // cy.de.sv_543 ca.gl.en_332 gl.ca.is_333 hmn.ha.un_550
+ // [38d0]
+ 0x643f68a4, 0x2d033504, 0x0f0c13a4, 0x682835a4, // ig.af.lg_433 zu.nl.sk_332 et.sv.lv_433 zu.sw.ig_433
+ 0x0811100c, 0x2d10130c, 0x1b085308, 0x55103ba0, // be.ro.uk_543 et.lt.sk_543 ht.no.tr_443 so.lt.rw_322
+ 0x31086811, 0x05002317, 0x35551e0e, 0x2000121b, // ig.no.az_653 ca.fr.un_730 ms.rw.zu_555 hu.sq.un_770
+ 0x13001a08, 0x00001c37, 0x0f003204, 0x31521b08, // tl.et.un_430 mr.un.un_B00 bs.lv.un_320 tr.ha.az_443
+ // [38e0]
+ 0x10286813, 0x0e1218af, 0x52281fa6, 0x28356807, // ig.sw.lt_665 ga.hu.is_655 cy.sw.ha_521 ig.zu.sw_432
+ 0x13000e0e, 0x642868ad, 0x291735ee, 0x32291707, // is.et.un_550 ig.sw.lg_643 zu.sr.sl_422 sr.sl.bs_432
+ 0x01646ba4, 0x17005304, 0x21001105, 0x290828ee, // ceb.lg.en_433 ht.sr.un_320 ro.jw.un_330 sw.no.sl_422
+ 0x21003512, 0x012518a4, 0x210420a4, 0x2055280d, // zu.jw.un_640 ga.eu.en_433 sq.fi.jw_433 sw.rw.sq_554
+ // [38f0]
+ 0x311168a4, 0x161f4aa6, 0x09072aee, 0x20681a07, // ig.ro.az_433 yo.cy.hr_521 mt.it.pl_422 tl.ig.sq_432
+ 0x191a6b08, 0x170708a6, 0x205228a4, 0x21112707, // ceb.tl.gl_443 uk.bg.sr_521 sw.ha.sq_433 gd.ro.jw_432
+ 0x28136809, 0x21003509, 0x0c001e04, 0x53683bad, // ig.et.sw_444 zu.jw.un_440 ms.sv.un_320 so.ig.ht_643
+ 0x6864285a, 0x216420a4, 0x1131280c, 0x1c020e12, // sw.lg.ig_553 sq.lg.jw_433 sw.az.ro_543 is.da.id_654
+ // [3900]
+ 0x1f0c06a9, 0x18102755, 0x551b31a9, 0x06182708, // de.sv.cy_544 gd.lt.ga_442 az.tr.rw_544 gd.ga.de_443
+ 0x64684a12, 0x17000f08, 0x081f270c, 0x09002102, // yo.ig.lg_654 lv.sr.un_430 gd.cy.no_543 jw.pl.un_220
+ 0x292d170c, 0x64311bad, 0x121b100c, 0x316828af, // sr.sk.sl_543 tr.az.lg_643 lt.tr.hu_543 sw.ig.az_655
+ 0x19040b07, 0x282a3504, 0x3b18010b, 0x2a200e0c, // es.fi.gl_432 zu.mt.sw_332 en.ga.so_542 is.sq.mt_543
+ // [3910]
+ 0x1800010c, 0x100b070c, 0x21002829, 0x2b092a04, // en.ga.un_530 it.es.lt_543 sw.jw.un_960 mt.pl.vi_332
+ 0x18031311, 0x1700550c, 0x28002a1a, 0x642868a9, // et.nl.ga_653 rw.sr.un_530 mt.sw.un_760 ig.sw.lg_544
+ 0x13002119, 0x52006409, 0x5264680c, 0x292d35af, // jw.et.un_750 lg.ha.un_440 ig.lg.ha_543 zu.sk.sl_655
+ 0x1e6b1aaf, 0x0a0923a0, 0x2d001208, 0x10045308, // tl.ceb.ms_655 ca.pl.pt_322 hu.sk.un_430 ht.fi.lt_443
+ // [3920]
+ 0x1304110c, 0x1e551fa4, 0x1e2864a4, 0x3f0913a4, // ro.fi.et_543 cy.rw.ms_433 lg.sw.ms_433 et.pl.af_433
+ 0x18273ba4, 0x114a1704, 0x02080ca7, 0x68170911, // so.gd.ga_433 sr.yo.ro_332 sv.no.da_532 pl.sr.ig_653
+ 0x35120708, 0x1100291a, 0x0e1310ec, 0x1a1e5507, // it.hu.zu_443 sl.ro.un_760 lt.et.is_644 rw.ms.tl_432
+ 0x270521ad, 0x16003b08, 0x0f2a250c, 0x646b1a08, // jw.fr.gd_643 so.hr.un_430 eu.mt.lv_543 tl.ceb.lg_443
+ // [3930]
+ 0x0d190b08, 0x2d17290d, 0x4a3b1a0c, 0x042d0d5a, // es.gl.cs_443 sl.sr.sk_554 tl.so.yo_543 cs.sk.fi_553
+ 0x251135a0, 0x52092d08, 0x27002007, 0x09003507, // zu.ro.eu_322 sk.pl.ha_443 sq.gd.un_420 zu.pl.un_420
+ 0x13110413, 0x6b061a02, 0x0f2a280c, 0x1e0a5204, // fi.ro.et_665 tl.de.ceb_222 sw.mt.lv_543 ha.pt.ms_332
+ 0x551628a7, 0x3121520c, 0x1e003b07, 0x070a1104, // sw.hr.rw_532 ha.jw.az_543 so.ms.un_420 ro.mk.bg_332
+ // [3940]
+ 0x271805a4, 0x0410280c, 0x1f0e0b04, 0x07111705, // fr.ga.gd_433 sw.lt.fi_543 es.is.cy_332 sr.ro.bg_333
+ 0x32031ca0, 0x0e003202, 0x551a350c, 0x1c090d55, // id.nl.bs_322 bs.is.un_220 zu.tl.rw_543 ne.hi.mr_442
+ 0x18201e07, 0x3b135304, 0x030635ad, 0x211a270c, // ms.sq.ga_432 ht.et.so_332 zu.de.nl_643 gd.tl.jw_543
+ 0x07002313, 0x20553507, 0x06002707, 0x190b68a4, // ca.it.un_650 zu.rw.sq_432 gd.de.un_420 ig.es.gl_433
+ // [3950]
+ 0x20002305, 0x4a3f03a4, 0x12000f1a, 0x01006404, // ca.sq.un_330 nl.af.yo_433 lv.hu.un_760 lg.en.un_320
+ 0x35100708, 0x10004a0d, 0x1b182712, 0x642835a9, // it.lt.zu_443 yo.lt.un_540 gd.ga.tr_654 zu.sw.lg_544
+ 0x060302ee, 0x05120aa4, 0x112a070c, 0x0a000819, // da.nl.de_422 pt.hu.fr_433 it.mt.ro_543 uk.mk.un_750
+ 0x3b1b31a4, 0x12350308, 0x271f2a12, 0x0e001122, // az.tr.so_433 nl.zu.hu_443 mt.cy.gd_654 ro.is.un_870
+ // [3960]
+ 0x0c002007, 0x11530b05, 0x03005307, 0x21311b12, // sq.sv.un_420 es.ht.ro_333 ht.nl.un_420 tr.az.jw_654
+ 0x53311b07, 0x3f1264a4, 0x2a3f1a08, 0x022a03ec, // tr.az.ht_432 lg.hu.af_433 tl.af.mt_443 nl.mt.da_644
+ 0x033f35a9, 0x1b315312, 0x033f01a4, 0x1800521a, // zu.af.nl_544 ht.az.tr_654 en.af.nl_433 ha.ga.un_760
+ 0x5521350e, 0x04063f05, 0x0a0810a9, 0x0e001f05, // zu.jw.rw_555 af.de.fi_333 be.uk.mk_544 cy.is.un_330
+ // [3970]
+ 0x080e270c, 0x6b531a0c, 0x280255a7, 0x120335af, // gd.is.no_543 tl.ht.ceb_543 rw.da.sw_532 zu.nl.hu_655
+ 0x081b0c0b, 0x06002a07, 0x110155a4, 0x25005512, // sv.tr.no_542 mt.de.un_420 rw.en.ro_433 rw.eu.un_640
+ 0x1c1b1aa9, 0x03642812, 0x02080e0b, 0x2a020eee, // tl.tr.id_544 sw.lg.nl_654 is.no.da_542 is.da.mt_422
+ 0x033f55a9, 0x3f215305, 0x1c1a21ad, 0x6412350c, // rw.af.nl_544 ht.jw.af_333 jw.tl.id_643 zu.hu.lg_543
+ // [3980]
+ 0x122d0aee, 0x082812a4, 0x081a2155, 0x3217520c, // pt.sk.hu_422 hu.sw.no_433 jw.tl.no_442 ha.sr.bs_543
+ 0x35211a04, 0x6811275a, 0x1f033faf, 0x13211aa7, // tl.jw.zu_332 gd.ro.ig_553 af.nl.cy_655 tl.jw.et_532
+ 0x6828030e, 0x1f00181b, 0x1c0e21ad, 0x0f190bec, // nl.sw.ig_555 ga.cy.un_770 jw.is.id_643 es.gl.lv_644
+ 0x092d11a4, 0x321711af, 0x641a2814, 0x3f031aa0, // ro.sk.pl_433 ro.sr.bs_655 sw.tl.lg_666 tl.nl.af_322
+ // [3990]
+ 0x12001c0d, 0x3b041e05, 0x2855110c, 0x0900190d, // id.hu.un_540 ms.fi.so_333 ro.rw.sw_543 gl.pl.un_540
+ 0x553268a7, 0x1a1e2104, 0x1e1c210c, 0x25001105, // ig.bs.rw_532 jw.ms.tl_332 jw.id.ms_543 ro.eu.un_330
+ 0x08021a05, 0x2d001019, 0x23033f60, 0x103f1307, // tl.da.no_333 lt.sk.un_750 af.nl.ca_664 et.af.lt_432
+ 0x090f2813, 0x121e5504, 0x32291ca0, 0x08041113, // sw.lv.pl_665 rw.ms.hu_332 id.sl.bs_322 ro.ru.uk_665
+ // [39a0]
+ 0x12002b22, 0x072001ee, 0x122b4a0c, 0x25000419, // vi.hu.un_870 en.sq.it_422 yo.vi.hu_543 fi.eu.un_750
+ 0x55642005, 0x04083f12, 0x355564ad, 0x2d0d6ba0, // sq.lg.rw_333 af.no.fi_654 lg.rw.zu_643 ceb.cs.sk_322
+ 0x64524aa4, 0x3f030655, 0x0e0d2b11, 0x0f2a2905, // yo.ha.lg_433 de.nl.af_442 vi.cs.is_653 sl.mt.lv_333
+ 0x072a010c, 0x3f001219, 0x0a6455ad, 0x353b5512, // en.mt.it_543 hu.af.un_750 rw.lg.pt_643 rw.so.zu_654
+ // [39b0]
+ 0x355206a4, 0x0a6410ee, 0x6e0b5307, 0x2d0d1ca0, // de.ha.zu_433 lt.lg.pt_422 ht.es.hmn_432 id.cs.sk_322
+ 0x553b28ec, 0x3b5255ad, 0x6b040f0c, 0x0e002714, // sw.so.rw_644 rw.ha.so_643 lv.fi.ceb_543 gd.is.un_660
+ 0x4a2855a9, 0x211b29a9, 0x290e1e08, 0x120f1a08, // rw.sw.yo_544 sl.tr.jw_544 ms.is.sl_443 tl.lv.hu_443
+ 0x120f040d, 0x01190b05, 0x0c0813a7, 0x121a0ca9, // fi.lv.hu_554 es.gl.en_333 et.no.sv_532 sv.tl.hu_544
+ // [39c0]
+ 0x52681b08, 0x04005323, 0x3f0635a4, 0x0800110e, // tr.ig.ha_443 ht.fi.un_880 zu.de.af_433 ro.uk.un_550
+ 0x12230f05, 0x525355a4, 0x05000e07, 0x55086407, // lv.ca.hu_333 rw.ht.ha_433 is.fr.un_420 lg.no.rw_432
+ 0x19530b5a, 0x3b554a05, 0x03001204, 0x07001f05, // es.ht.gl_553 yo.rw.so_333 hu.nl.un_320 cy.it.un_330
+ 0x5305520b, 0x6b05530c, 0x201b0fee, 0x12531a04, // ha.fr.ht_542 ht.fr.ceb_543 lv.tr.sq_422 tl.ht.hu_332
+ // [39d0]
+ 0x295305a4, 0x3b2864a0, 0x07530507, 0x0c520a04, // fr.ht.sl_433 lg.sw.so_322 fr.ht.it_432 pt.ha.sv_332
+ 0x2a001f14, 0x0f001209, 0x28002004, 0x06000e14, // cy.mt.un_660 hu.lv.un_440 sq.sw.un_320 is.de.un_660
+ 0x121a0fa4, 0x2800030c, 0x6412550c, 0x1a356b02, // lv.tl.hu_433 nl.sw.un_530 rw.hu.lg_543 ceb.zu.tl_222
+ 0x12000819, 0x64551b13, 0x06036408, 0x083f03ad, // no.hu.un_750 tr.rw.lg_665 lg.nl.de_443 nl.af.no_643
+ // [39e0]
+ 0x05530d12, 0x28556460, 0x53020508, 0x55641060, // cs.ht.fr_654 lg.rw.sw_664 fr.da.ht_443 lt.lg.rw_664
+ 0x04003502, 0x531b0e0d, 0x0f000e12, 0x0435110c, // zu.fi.un_220 is.tr.ht_554 is.lv.un_640 ro.zu.fi_543
+ 0x08000c19, 0x356455ec, 0x3b1b0e55, 0x32311b05, // sv.no.un_750 rw.lg.zu_644 is.tr.so_442 tr.az.bs_333
+ 0x10006422, 0x352852ad, 0x13292da4, 0x273b2aee, // lg.lt.un_870 ha.sw.zu_643 sk.sl.et_433 mt.so.gd_422
+ // [39f0]
+ 0x0b2319a4, 0x02002108, 0x212a200c, 0x1f000b1b, // gl.ca.es_433 jw.da.un_430 sq.mt.jw_543 es.cy.un_770
+ 0x09112da0, 0x041b0f0c, 0x09010d05, 0x10130b08, // sk.ro.pl_322 lv.tr.fi_543 cs.en.pl_333 es.et.lt_443
+ 0x35001f21, 0x05074aa0, 0x35552814, 0x530a110c, // cy.zu.un_860 yo.it.fr_322 sw.rw.zu_666 ro.pt.ht_543
+ 0x2300271b, 0x0a1e1c0c, 0x0a073507, 0x08000108, // gd.ca.un_770 id.ms.pt_543 zu.it.pt_432 en.no.un_430
+ // [3a00]
+ 0x281a11a4, 0x060e0ca4, 0x1c080205, 0x0b001b08, // ro.tl.sw_433 sv.is.de_433 da.no.id_333 tr.es.un_430
+ 0x105335a4, 0x2a0a04ee, 0x32170da4, 0x2835530e, // zu.ht.lt_433 fi.pt.mt_422 cs.sr.bs_433 ht.zu.sw_555
+ 0x2a0c3204, 0x2a013207, 0x311b35a0, 0x29530107, // bs.sv.mt_332 bs.en.mt_432 zu.tr.az_322 en.ht.sl_432
+ 0x2a3b1fa7, 0x311b21af, 0x03026e0c, 0x043f0eec, // cy.so.mt_532 jw.tr.az_655 hmn.da.nl_543 is.af.fi_644
+ // [3a10]
+ 0x0e0f0807, 0x29322d14, 0x530a1c0c, 0x291153ac, // no.lv.is_432 sk.bs.sl_666 id.pt.ht_543 ht.ro.sl_632
+ 0x35003b19, 0x08122aa0, 0x19003504, 0x1700101b, // so.zu.un_750 mt.hu.no_322 zu.gl.un_320 be.sr.un_770
+ 0x53002704, 0x3b1a6ba4, 0x2a0e06a4, 0x0d3b13ad, // gd.ht.un_320 ceb.tl.so_433 de.is.mt_433 et.so.cs_643
+ 0x11311b0d, 0x076435ec, 0x281e1cad, 0x1c1f1e55, // tr.az.ro_554 zu.lg.it_644 id.ms.sw_643 ms.cy.id_442
+ // [3a20]
+ 0x52001e04, 0x01036ba0, 0x1a2807af, 0x190a0709, // ms.ha.un_320 ceb.nl.en_322 it.sw.tl_655 it.pt.gl_444
+ 0x2d0d55af, 0x1c1a2508, 0x2d0d250d, 0x645525af, // rw.cs.sk_655 eu.tl.id_443 eu.cs.sk_554 eu.rw.lg_655
+ 0x0e2b120c, 0x31130808, 0x0b1029a9, 0x1b3529a7, // hu.vi.is_543 no.et.az_443 sl.lt.es_544 sl.zu.tr_532
+ 0x0a002d04, 0x170a0813, 0x3512280e, 0x55002809, // sk.pt.un_320 uk.mk.sr_665 sw.hu.zu_555 sw.rw.un_440
+ // [3a30]
+ 0x06000814, 0x0c1306a4, 0x01311ba4, 0x55352d0d, // no.de.un_660 de.et.sv_433 tr.az.en_433 sk.zu.rw_554
+ 0x21230512, 0x1b1152a7, 0x522855ee, 0x0e0464a0, // fr.ca.jw_654 ha.ro.tr_532 rw.sw.ha_422 lg.fi.is_322
+ 0x160f2d08, 0x061f0e04, 0x1364040c, 0x3b0a19a9, // sk.lv.hr_443 is.cy.de_332 fi.lg.et_543 gl.pt.so_544
+ 0x53000608, 0x1100520d, 0x4a35640e, 0x11210f0c, // de.ht.un_430 ha.ro.un_540 lg.zu.yo_555 lv.jw.ro_543
+ // [3a40]
+ 0x0b64350d, 0x06210913, 0x1c215205, 0x1f023f55, // zu.lg.es_554 pl.jw.de_665 ha.jw.id_333 af.da.cy_442
+ 0x12002302, 0x0c192aee, 0x3b001c02, 0x130f01a4, // ca.hu.un_220 mt.gl.sv_422 id.so.un_220 en.lv.et_433
+ 0x4a006812, 0x17202d07, 0x2d2318ad, 0x05000d1a, // ig.yo.un_640 sk.sq.sr_432 ga.ca.sk_643 cs.fr.un_760
+ 0x080c0207, 0x1e52250c, 0x03000121, 0x273f010c, // da.sv.no_432 eu.ha.ms_543 en.nl.un_860 en.af.gd_543
+ // [3a50]
+ 0x25114aa4, 0x212728a7, 0x1c090dee, 0x0c000614, // yo.ro.eu_433 sw.gd.jw_532 ne.hi.mr_422 de.sv.un_660
+ 0x1c2a1e07, 0x6b0a010b, 0x0c00030e, 0x6b03010c, // ms.mt.id_432 en.pt.ceb_542 nl.sv.un_550 en.nl.ceb_543
+ 0x0f062d08, 0x556b1a07, 0x2a063fad, 0x0e171355, // sk.de.lv_443 tl.ceb.rw_432 af.de.mt_643 et.sr.is_442
+ 0x0e00280c, 0x112a2308, 0x03066b02, 0x182b1c04, // sw.is.un_530 ca.mt.ro_443 ceb.de.nl_222 id.vi.ga_332
+ // [3a60]
+ 0x1f004a04, 0x01033fa9, 0x0f291f08, 0x100c6bad, // yo.cy.un_320 af.nl.en_544 cy.sl.lv_443 ceb.sv.lt_643
+ 0x073512a9, 0x01002a04, 0x1e1c23a4, 0x1c2a210c, // hu.zu.it_544 mt.en.un_320 ca.id.ms_433 jw.mt.id_543
+ 0x0700230e, 0x09000a07, 0x1a1f13a9, 0x321721a4, // ca.it.un_550 pt.pl.un_420 et.cy.tl_544 jw.sr.bs_433
+ 0x01032704, 0x1f033f09, 0x6b132da4, 0x18000a13, // gd.nl.en_332 af.nl.cy_444 sk.et.ceb_433 pt.ga.un_650
+ // [3a70]
+ 0x2100250d, 0x17292d0e, 0x1e1a13a4, 0x3f172913, // eu.jw.un_540 sk.sl.sr_555 et.tl.ms_433 sl.sr.af_665
+ 0x01002521, 0x1c1f21a4, 0x4a1b17a0, 0x1211070c, // eu.en.un_860 jw.cy.id_433 sr.tr.yo_322 it.ro.hu_543
+ 0x28311b08, 0x060c0304, 0x065308a7, 0x16060e12, // tr.az.sw_443 nl.sv.de_332 no.ht.de_532 is.de.hr_654
+ 0x0d192011, 0x03131fac, 0x29005214, 0x1a1b535a, // sq.gl.cs_653 cy.et.nl_632 ha.sl.un_660 ht.tr.tl_553
+ // [3a80]
+ 0x53003521, 0x1b1119ee, 0x052d0912, 0x2a000421, // zu.ht.un_860 gl.ro.tr_422 pl.sk.fr_654 fi.mt.un_860
+ 0x190b11a0, 0x11162da7, 0x311e3b0c, 0x06113ba0, // ro.es.gl_322 sk.hr.ro_532 so.ms.az_543 so.ro.de_322
+ 0x312a1c09, 0x641620ee, 0x3f1a1f11, 0x10000809, // id.mt.az_444 sq.hr.lg_422 cy.tl.af_653 uk.be.un_440
+ 0x5300200e, 0x271e130c, 0x32172daf, 0x21555204, // sq.ht.un_550 et.ms.gd_543 sk.sr.bs_655 ha.rw.jw_332
+ // [3a90]
+ 0x08350c07, 0x531b4aee, 0x04005307, 0x1c0f21a0, // sv.zu.no_432 yo.tr.ht_422 ht.fi.un_420 jw.lv.id_322
+ 0x190b4a0c, 0x083f6412, 0x1b125307, 0x531219a0, // yo.es.gl_543 lg.af.no_654 ht.hu.tr_432 gl.hu.ht_322
+ 0x2b003204, 0x016b020c, 0x3f005520, 0x551b5304, // bs.vi.un_320 da.ceb.en_543 rw.af.un_850 ht.tr.rw_332
+ 0x1b005314, 0x090d2013, 0x02036409, 0x1b006412, // ht.tr.un_660 sq.cs.pl_665 lg.nl.da_444 lg.tr.un_640
+ // [3aa0]
+ 0x21003521, 0x111b3113, 0x1100250e, 0x08041713, // zu.jw.un_860 az.tr.ro_665 eu.ro.un_550 sr.ru.uk_665
+ 0x55352813, 0x0c040614, 0x3f001208, 0x033f1f60, // sw.zu.rw_665 de.fi.sv_666 hu.af.un_430 cy.af.nl_664
+ 0x52002a05, 0x11170a05, 0x12002705, 0x0d001e04, // mt.ha.un_330 mk.sr.ro_333 gd.hu.un_330 ms.cs.un_320
+ 0x04036405, 0x201e270c, 0x1a21275a, 0x3f1b0607, // lg.nl.fi_333 gd.ms.sq_543 gd.jw.tl_553 de.tr.af_432
+ // [3ab0]
+ 0x0b002a04, 0x1b001e02, 0x0721270c, 0x05271811, // mt.es.un_320 ms.tr.un_220 gd.jw.it_543 ga.gd.fr_653
+ 0x2a1106ee, 0x1b2a03ee, 0x1e271f0d, 0x102718a4, // de.ro.mt_422 nl.mt.tr_422 cy.gd.ms_554 ga.gd.lt_433
+ 0x081c1ea9, 0x05001e08, 0x18082704, 0x03201b14, // ms.id.no_544 ms.fr.un_430 gd.no.ga_332 tr.sq.nl_666
+ 0x0600320c, 0x21005302, 0x1b121ea9, 0x28003f14, // bs.de.un_530 ht.jw.un_220 ms.hu.tr_544 af.sw.un_660
+ // [3ac0]
+ 0x1e283f08, 0x08070aad, 0x180504ee, 0x0d130955, // af.sw.ms_443 mk.bg.uk_643 fi.fr.ga_422 hi.bh.ne_442
+ 0x0125050c, 0x07000e11, 0x1b273114, 0x3f350311, // fr.eu.en_543 is.it.un_630 az.gd.tr_666 nl.zu.af_653
+ 0x0c002304, 0x3b2d29ee, 0x071008a4, 0x04170aa9, // ca.sv.un_320 sl.sk.so_422 uk.be.bg_433 mk.sr.ru_544
+ 0x1e1c280e, 0x04000c1b, 0x29091104, 0x21001022, // sw.id.ms_555 sv.fi.un_770 ro.pl.sl_332 lt.jw.un_870
+ // [3ad0]
+ 0x202813a4, 0x6b11130c, 0x3f000204, 0x041113af, // et.sw.sq_433 et.ro.ceb_543 da.af.un_320 et.ro.fi_655
+ 0x32170f14, 0x35030a04, 0x2511135a, 0x23022da0, // lv.sr.bs_666 pt.nl.zu_332 et.ro.eu_553 sk.da.ca_322
+ 0x0e2811ee, 0x1e291cec, 0x01000805, 0x052a3b04, // ro.sw.is_422 id.sl.ms_644 no.en.un_330 so.mt.fr_332
+ 0x21131160, 0x2300350c, 0x3b033f60, 0x112017ee, // ro.et.jw_664 zu.ca.un_530 af.nl.so_664 sr.sq.ro_422
+ // [3ae0]
+ 0x13042009, 0x190b05ad, 0x0e041308, 0x0f202855, // sq.fi.et_444 fr.es.gl_643 et.fi.is_443 sw.sq.lv_442
+ 0x16072a07, 0x070d4a05, 0x6428130c, 0x01112aee, // mt.it.hr_432 yo.cs.it_333 et.sw.lg_543 mt.ro.en_422
+ 0x1b120608, 0x05003119, 0x23003f07, 0x1c292860, // de.hu.tr_443 az.fr.un_750 af.ca.un_420 sw.sl.id_664
+ 0x061e1cec, 0x081213ad, 0x2301050c, 0x1e1c6b09, // id.ms.de_644 et.hu.no_643 fr.en.ca_543 ceb.id.ms_444
+ // [3af0]
+ 0x050701ec, 0x120e0cad, 0x03080cad, 0x216b13ee, // en.it.fr_644 sv.is.hu_643 sv.no.nl_643 et.ceb.jw_422
+ 0x05101f0c, 0x06001f09, 0x13004a07, 0x6b293507, // cy.lt.fr_543 cy.de.un_440 yo.et.un_420 zu.sl.ceb_432
+ 0x19201107, 0x0e005313, 0x28133f0d, 0x05005317, // ro.sq.gl_432 ht.is.un_650 af.et.sw_554 ht.fr.un_730
+ 0x1c061e04, 0x200911a9, 0x1e1c6ba4, 0x136b0e04, // ms.de.id_332 ro.pl.sq_544 ceb.id.ms_433 is.ceb.et_332
+ // [3b00]
+ 0x11002a13, 0x080e1fec, 0x1e1c06a9, 0x120964ad, // mt.ro.un_650 cy.is.no_644 de.id.ms_544 lg.pl.hu_643
+ 0x130d09ad, 0x1f001a13, 0x0f08120c, 0x6e6b68a0, // hi.ne.bh_643 tl.cy.un_650 hu.no.lv_543 ig.ceb.hmn_322
+ 0x643506ec, 0x03050108, 0x2808120c, 0x3b002819, // de.zu.lg_644 en.fr.nl_443 hu.no.sw_543 sw.so.un_750
+ 0x21082007, 0x255513a4, 0x132112ee, 0x0e1e1c0d, // sq.no.jw_432 et.rw.eu_433 hu.jw.et_422 id.ms.is_554
+ // [3b10]
+ 0x1b100cad, 0x13002308, 0x352768af, 0x040c0608, // sv.lt.tr_643 ca.et.un_430 ig.gd.zu_655 de.sv.fi_443
+ 0x0c00041a, 0x01000c04, 0x120523af, 0x040a1008, // fi.sv.un_760 sv.en.un_320 ca.fr.hu_655 be.mk.ru_443
+ 0x27352812, 0x1b002104, 0x080c0e13, 0x64043505, // sw.zu.gd_654 jw.tr.un_320 is.sv.no_665 zu.fi.lg_333
+ 0x203f0313, 0x12006b12, 0x130821a4, 0x0200551a, // nl.af.sq_665 ceb.hu.un_640 jw.no.et_433 rw.da.un_760
+ // [3b20]
+ 0x3b55350c, 0x0c0608ec, 0x0413125a, 0x0f3f0308, // zu.rw.so_543 no.de.sv_644 hu.et.fi_553 nl.af.lv_443
+ 0x64060e5a, 0x211c310c, 0x1b092da4, 0x18005507, // is.de.lg_553 az.id.jw_543 sk.pl.tr_433 rw.ga.un_420
+ 0x02001f1a, 0x5364120c, 0x071f0213, 0x01060ca4, // cy.da.un_760 hu.lg.ht_543 da.cy.it_665 sv.de.en_433
+ 0x230b05ac, 0x08040307, 0x10321708, 0x131a3b14, // fr.es.ca_632 nl.fi.no_432 sr.bs.lt_443 so.tl.et_666
+ // [3b30]
+ 0x2d0d3b0e, 0x2d0d0702, 0x28043ba9, 0x3b130311, // so.cs.sk_555 it.cs.sk_222 so.fi.sw_544 nl.et.so_653
+ 0x643f1aa4, 0x011353a0, 0x041e5205, 0x166b2008, // tl.af.lg_433 ht.et.en_322 ha.ms.fi_333 sq.ceb.hr_443
+ 0x321608ec, 0x12080209, 0x3b4a64ad, 0x03683b12, // no.hr.bs_644 da.no.hu_444 lg.yo.so_643 so.ig.nl_654
+ 0x033f3bad, 0x641a6b60, 0x0a070811, 0x1c033bee, // so.af.nl_643 ceb.tl.lg_664 uk.bg.mk_653 so.nl.id_422
+ // [3b40]
+ 0x190b23a0, 0x1e1b1a08, 0x531827af, 0x64133b05, // ca.es.gl_322 tl.tr.ms_443 gd.ga.ht_655 so.et.lg_333
+ 0x1300280c, 0x270401a9, 0x043f1aee, 0x2d090da0, // sw.et.un_530 en.fi.gd_544 tl.af.fi_422 cs.pl.sk_322
+ 0x1a6b1b0d, 0x133f0411, 0x0a0710ad, 0x032a2da0, // tr.ceb.tl_554 fi.af.et_653 be.bg.mk_643 sk.mt.nl_322
+ 0x07171155, 0x0d1f3102, 0x04311bad, 0x32001c04, // ro.sr.it_442 az.cy.cs_222 tr.az.fi_643 id.bs.un_320
+ // [3b50]
+ 0x190a0b0c, 0x0500230e, 0x06000f08, 0x2d0d0ea0, // es.pt.gl_543 ca.fr.un_550 lv.de.un_430 is.cs.sk_322
+ 0x283132a0, 0x290d3504, 0x230b19ad, 0x16000b07, // bs.az.sw_322 zu.cs.sl_332 gl.es.ca_643 es.hr.un_420
+ 0x07052107, 0x1a1f0111, 0x1a002702, 0x1c211e07, // jw.fr.it_432 en.cy.tl_653 gd.tl.un_220 ms.jw.id_432
+ 0x53041e0c, 0x0c0106a9, 0x071f1804, 0x170a1160, // ms.fi.ht_543 de.en.sv_544 ga.cy.it_332 ro.mk.sr_664
+ // [3b60]
+ 0x1a011f0d, 0x1a6b0407, 0x211228a4, 0x08020309, // cy.en.tl_554 fi.ceb.tl_432 sw.hu.jw_433 nl.da.no_444
+ 0x13201002, 0x1b003209, 0x054a6b0c, 0x04002312, // lt.sq.et_222 bs.tr.un_440 ceb.yo.fr_543 ca.fi.un_640
+ 0x5207210c, 0x0e00180e, 0x283b1ea4, 0x190b1105, // jw.it.ha_543 ga.is.un_550 ms.so.sw_433 ro.es.gl_333
+ 0x0d131c0d, 0x55283509, 0x1b000f21, 0x29181eee, // mr.bh.ne_554 zu.sw.rw_444 lv.tr.un_860 ms.ga.sl_422
+ // [3b70]
+ 0x53680707, 0x0c131b07, 0x310910af, 0x170f1ba7, // it.ig.ht_432 tr.et.sv_432 lt.pl.az_655 tr.lv.sr_532
+ 0x29283512, 0x20001a02, 0x21181208, 0x051c0e02, // zu.sw.sl_654 tl.sq.un_220 ur.ar.fa_443 is.id.fr_222
+ 0x130f1207, 0x190b120c, 0x12000b04, 0x3b1e280c, // hu.lv.et_432 hu.es.gl_543 es.hu.un_320 sw.ms.so_543
+ 0x23000507, 0x12230507, 0x32200faf, 0x13060707, // fr.ca.un_420 fr.ca.hu_432 lv.sq.bs_655 it.de.et_432
+ // [3b80]
+ 0x210f1b11, 0x190b2da9, 0x53000f08, 0x0e1f250c, // tr.lv.jw_653 sk.es.gl_544 lv.ht.un_430 eu.cy.is_543
+ 0x1a001c13, 0x23111855, 0x13040f08, 0x040d2055, // id.tl.un_650 ga.ro.ca_442 lv.fi.et_443 sq.cs.fi_442
+ 0x2a100f12, 0x680d01a4, 0x25100f13, 0x35006b12, // lv.lt.mt_654 en.cs.ig_433 lv.lt.eu_665 ceb.zu.un_640
+ 0x3f2a28a4, 0x1f000e1a, 0x230c1aa9, 0x0407175a, // sw.mt.af_433 is.cy.un_760 tl.sv.ca_544 sr.bg.ru_553
+ // [3b90]
+ 0x53080eec, 0x351a5302, 0x0f13170c, 0x25171b0c, // is.no.ht_644 ht.tl.zu_222 sr.et.lv_543 tr.sr.eu_543
+ 0x00002737, 0x116b0f07, 0x18005302, 0x6b071aaf, // gd.un.un_B00 lv.ceb.ro_432 ht.ga.un_220 tl.it.ceb_655
+ 0x08001b0e, 0x18005307, 0x3b040e04, 0x190b68a0, // tr.no.un_550 ht.ga.un_420 is.fi.so_332 ig.es.gl_322
+ 0x11200d0c, 0x53281b05, 0x100411a0, 0x200c2304, // cs.sq.ro_543 tr.sw.ht_333 ro.ru.be_322 ca.sv.sq_332
+ // [3ba0]
+ 0x64190bad, 0x1a5321a9, 0x05532012, 0x04250fee, // es.gl.lg_643 jw.ht.tl_544 sq.ht.fr_654 lv.eu.fi_422
+ 0x1120010c, 0x2d0d27af, 0x06521b0c, 0x191a0b0b, // en.sq.ro_543 gd.cs.sk_655 tr.ha.de_543 es.tl.gl_542
+ 0x10001602, 0x2a005314, 0x0e013b04, 0x1e281c0c, // hr.lt.un_220 ht.mt.un_660 so.en.is_332 id.sw.ms_543
+ 0x0f003207, 0x0b190aad, 0x170a1104, 0x0e022507, // bs.lv.un_420 pt.gl.es_643 ro.mk.sr_332 eu.da.is_432
+ // [3bb0]
+ 0x01001a04, 0x3152280d, 0x0b0a0714, 0x131a28ac, // tl.en.un_320 sw.ha.az_554 it.pt.es_666 sw.tl.et_632
+ 0x09190b0c, 0x25006813, 0x011b20a4, 0x2d001a05, // es.gl.pl_543 ig.eu.un_650 sq.tr.en_433 tl.sk.un_330
+ 0x3500641a, 0x013b6bad, 0x35201aa4, 0x09001334, // lg.zu.un_760 ceb.so.en_643 tl.sq.zu_433 bh.hi.un_A80
+ 0x0b290d0c, 0x0b0a0c11, 0x2d0d1014, 0x3b526408, // cs.sl.es_543 sv.pt.es_653 lt.cs.sk_666 lg.ha.so_443
+ // [3bc0]
+ 0x1a3b6b0c, 0x3f031c04, 0x211a6b0c, 0x3b001a19, // ceb.so.tl_543 id.nl.af_332 ceb.tl.jw_543 tl.so.un_750
+ 0x16005512, 0x0911200c, 0x0a0d2dec, 0x17292aa4, // rw.hr.un_640 sq.ro.pl_543 sk.cs.pt_644 mt.sl.sr_433
+ 0x06001707, 0x11081008, 0x1600100d, 0x02001213, // sr.de.un_420 be.uk.ro_443 lt.hr.un_540 hu.da.un_650
+ 0x07283513, 0x23111905, 0x35003204, 0x3f351c04, // zu.sw.it_665 gl.ro.ca_333 bs.zu.un_320 id.zu.af_332
+ // [3bd0]
+ 0x3f112514, 0x2b3f10ec, 0x1b07120c, 0x21033f0c, // eu.ro.af_666 lt.af.vi_644 hu.it.tr_543 af.nl.jw_543
+ 0x23271f0c, 0x6b3b1a0c, 0x31002d08, 0x060f1fa7, // cy.gd.ca_543 tl.so.ceb_543 sk.az.un_430 cy.lv.de_532
+ 0x3b521a0c, 0x03001004, 0x28000d04, 0x3b281a0c, // tl.ha.so_543 lt.nl.un_320 cs.sw.un_320 tl.sw.so_543
+ 0x120a4aaf, 0x28131aa0, 0x05230b08, 0x64003b13, // yo.pt.hu_655 tl.et.sw_322 es.ca.fr_443 so.lg.un_650
+ // [3be0]
+ 0x553b1aad, 0x3b312008, 0x2055310c, 0x04000214, // tl.so.rw_643 sq.az.so_443 az.rw.sq_543 da.fi.un_660
+ 0x29200f04, 0x1a043b05, 0x3b521b0c, 0x12000f13, // lv.sq.sl_332 so.fi.tl_333 tr.ha.so_543 lv.hu.un_650
+ 0x0a120ea0, 0x02011207, 0x11100460, 0x0e1321a7, // is.hu.pt_322 hu.en.da_432 ru.be.ro_664 jw.et.is_532
+ 0x232d0daf, 0x0e000d0d, 0x041028a4, 0x0a250b07, // cs.sk.ca_655 cs.is.un_540 sw.lt.fi_433 es.eu.pt_432
+ // [3bf0]
+ 0x0f0413af, 0x0d091cad, 0x6b683107, 0x351c2a0c, // et.fi.lv_655 mr.hi.ne_643 az.ig.ceb_432 mt.id.zu_543
+ 0x060a0308, 0x23060704, 0x29001122, 0x20253b07, // nl.pt.de_443 it.de.ca_332 ro.sl.un_870 so.eu.sq_432
+ 0x1e050807, 0x55001e13, 0x1c13090e, 0x03112d08, // no.fr.ms_432 ms.rw.un_650 hi.bh.mr_555 sk.ro.nl_443
+ 0x18003202, 0x122003ee, 0x0b002307, 0x283f0a12, // bs.ga.un_220 nl.sq.hu_422 ca.es.un_420 pt.af.sw_654
+
+ // [3c00]
+ 0x230b12ad, 0x353b28ad, 0x4a530708, 0x1b0c3ba0, // hu.es.ca_643 sw.so.zu_643 it.ht.yo_443 so.sv.tr_322
+ 0x042523af, 0x3b3510a4, 0x1f351bec, 0x552835ad, // ca.eu.fi_655 lt.zu.so_433 tr.zu.cy_644 zu.sw.rw_643
+ 0x0e521bec, 0x18000704, 0x181b27a4, 0x0e000a04, // tr.ha.is_644 it.ga.un_320 gd.tr.ga_433 pt.is.un_320
+ 0x35642809, 0x06553512, 0x1004170d, 0x282d3513, // sw.lg.zu_444 zu.rw.de_654 sr.ru.be_554 zu.sk.sw_665
+ // [3c10]
+ 0x0800020b, 0x1e313504, 0x020e550c, 0x32006807, // da.no.un_520 zu.az.ms_332 rw.is.da_543 ig.bs.un_420
+ 0x53112a04, 0x35311ba4, 0x050713a0, 0x120b23a4, // mt.ro.ht_332 tr.az.zu_433 et.it.fr_322 ca.es.hu_433
+ 0x0f00680c, 0x09000d33, 0x6e003b20, 0x1a6b4a0c, // ig.lv.un_530 ne.hi.un_A70 so.hmn.un_850 yo.ceb.tl_543
+ 0x2b0c0611, 0x040208ec, 0x186e64a4, 0x2d00092a, // de.sv.vi_653 no.da.fi_644 lg.hmn.ga_433 pl.sk.un_970
+ // [3c20]
+ 0x17071004, 0x1a013b07, 0x07006e08, 0x2d091fec, // be.bg.sr_332 so.en.tl_432 hmn.it.un_430 cy.pl.sk_644
+ 0x23214a05, 0x2b136ba0, 0x2d0d12b2, 0x682917a4, // yo.jw.ca_333 ceb.et.vi_322 hu.cs.sk_732 sr.sl.ig_433
+ 0x120c0f0c, 0x10070804, 0x25351905, 0x2d0d23af, // lv.sv.hu_543 uk.bg.be_332 gl.zu.eu_333 ca.cs.sk_655
+ 0x3113640d, 0x1b003202, 0x133b3fa4, 0x0235680c, // lg.et.az_554 bs.tr.un_220 af.so.et_433 ig.zu.da_543
+ // [3c30]
+ 0x6b2318a4, 0x3b003102, 0x01006b02, 0x28012aa4, // ga.ca.ceb_433 az.so.un_220 ceb.en.un_220 mt.en.sw_433
+ 0x121e1c07, 0x322d2905, 0x07005508, 0x16006e08, // id.ms.hu_432 sl.sk.bs_333 rw.it.un_430 hmn.hr.un_430
+ 0x191223a7, 0x1c2a2102, 0x31181b14, 0x030635a7, // ca.hu.gl_532 jw.mt.id_222 tr.ga.az_666 zu.de.nl_532
+ 0x03061812, 0x07100a05, 0x32101655, 0x1900230c, // ga.de.nl_654 mk.be.bg_333 hr.lt.bs_442 ca.gl.un_530
+ // [3c40]
+ 0x06040c12, 0x1b2531a4, 0x64551ea0, 0x090c0808, // sv.fi.de_654 az.eu.tr_433 ms.rw.lg_322 no.sv.pl_443
+ 0x082835ee, 0x53000509, 0x1b00230e, 0x1a356ba4, // zu.sw.no_422 fr.ht.un_440 ca.tr.un_550 ceb.zu.tl_433
+ 0x2b6b1c11, 0x0d00290e, 0x356864ec, 0x3b355555, // id.ceb.vi_653 sl.cs.un_550 lg.ig.zu_644 rw.zu.so_442
+ 0x64253513, 0x19121307, 0x0c001f2b, 0x131c0911, // zu.eu.lg_665 et.hu.gl_432 cy.sv.un_980 hi.mr.bh_653
+ // [3c50]
+ 0x1b1321a4, 0x3f001e04, 0x6b001c05, 0x110708a4, // jw.et.tr_433 ms.af.un_320 id.ceb.un_330 uk.bg.ro_433
+ 0x073f010c, 0x251819a0, 0x080204ee, 0x2d120513, // en.af.it_543 gl.ga.eu_322 fi.da.no_422 fr.hu.sk_665
+ 0x531907af, 0x191a5511, 0x1c211ea0, 0x23055312, // it.gl.ht_655 rw.tl.gl_653 ms.jw.id_322 ht.fr.ca_654
+ 0x52126b02, 0x190b070e, 0x200f5505, 0x10294aec, // ceb.hu.ha_222 it.es.gl_555 rw.lv.sq_333 yo.sl.lt_644
+ // [3c60]
+ 0x551f4a0c, 0x2d551908, 0x16000d0c, 0x1b27530b, // yo.cy.rw_543 gl.rw.sk_443 cs.hr.un_530 ht.gd.tr_542
+ 0x2a23060c, 0x4a051905, 0x531e0307, 0x19283555, // de.ca.mt_543 gl.fr.yo_333 nl.ms.ht_432 zu.sw.gl_442
+ 0x00005337, 0x0e182712, 0x1f071104, 0x1b000521, // ht.un.un_B00 gd.ga.is_654 ro.it.cy_332 fr.tr.un_860
+ 0x550a07a4, 0x171629a4, 0x19002004, 0x250602af, // it.pt.rw_433 sl.hr.sr_433 sq.gl.un_320 da.de.eu_655
+ // [3c70]
+ 0x20521aa0, 0x200b2704, 0x05000104, 0x0b004a02, // tl.ha.sq_322 gd.es.sq_332 en.fr.un_320 yo.es.un_220
+ 0x060313ee, 0x5552280c, 0x10003b14, 0x3f31050c, // et.nl.de_422 sw.ha.rw_543 so.lt.un_660 fr.az.af_543
+ 0x2a0c060c, 0x07191aa4, 0x0a081009, 0x0c0628a0, // de.sv.mt_543 tl.gl.it_433 be.uk.mk_444 sw.de.sv_322
+ 0x1a1011a4, 0x21251eee, 0x551364ec, 0x081c1e11, // ro.lt.tl_433 ms.eu.jw_422 lg.et.rw_644 ms.id.no_653
+ // [3c80]
+ 0x64211ca4, 0x1f3b0413, 0x52131b13, 0x131021a0, // id.jw.lg_433 fi.so.cy_665 tr.et.ha_665 jw.lt.et_322
+ 0x16006b0c, 0x27066ea0, 0x1a553513, 0x6e005314, // ceb.hr.un_530 hmn.de.gd_322 zu.rw.tl_665 ht.hmn.un_660
+ 0x64121fa0, 0x070168a0, 0x292d0d11, 0x6b1b53a4, // cy.hu.lg_322 ig.en.it_322 cs.sk.sl_653 ht.tr.ceb_433
+ 0x17066e07, 0x0718270c, 0x6b1b1a11, 0x291a6eee, // hmn.de.sr_432 gd.ga.it_543 tl.tr.ceb_653 hmn.tl.sl_422
+ // [3c90]
+ 0x3f6408a4, 0x12091812, 0x280855a4, 0x351b5505, // no.lg.af_433 ga.pl.hu_654 rw.no.sw_433 rw.tr.zu_333
+ 0x12182312, 0x09002008, 0x12006e04, 0x64520304, // ca.ga.hu_654 sq.pl.un_430 hmn.hu.un_320 nl.ha.lg_332
+ 0x111b13a4, 0x0d043f04, 0x0f1123ad, 0x53003519, // et.tr.ro_433 af.fi.cs_332 ca.ro.lv_643 zu.ht.un_750
+ 0x10251113, 0x13041a0c, 0x106b1a05, 0x043f52a4, // ro.eu.lt_665 tl.fi.et_543 tl.ceb.lt_333 ha.af.fi_433
+ // [3ca0]
+ 0x130825ad, 0x170a080c, 0x29000a08, 0x1f006e23, // eu.no.et_643 uk.mk.sr_543 pt.sl.un_430 hmn.cy.un_880
+ 0x3f0308af, 0x111e0707, 0x64033f12, 0x040208a4, // no.nl.af_655 it.ms.ro_432 af.nl.lg_654 no.da.fi_433
+ 0x23001216, 0x1c0d0911, 0x1b190aa4, 0x0d122d07, // hu.ca.un_720 hi.ne.mr_653 pt.gl.tr_433 sk.hu.cs_432
+ 0x3f1b2512, 0x1b1e1c08, 0x17042a07, 0x100b1912, // eu.tr.af_654 id.ms.tr_443 mt.fi.sr_432 gl.es.lt_654
+ // [3cb0]
+ 0x0e032daf, 0x6e000f08, 0x3b001a07, 0x0a001702, // sk.nl.is_655 lv.hmn.un_430 tl.so.un_420 sr.mk.un_220
+ 0x120655a4, 0x13160da4, 0x0c0d2912, 0x0b23110c, // rw.de.hu_433 cs.hr.et_433 sl.cs.sv_654 ro.ca.es_543
+ 0x1e13200c, 0x0d190aa9, 0x3f181f0d, 0x2d002302, // sq.et.ms_543 pt.gl.cs_544 cy.ga.af_554 ca.sk.un_220
+ 0x55200f0c, 0x3b551b60, 0x4a006802, 0x351a6b13, // lv.sq.rw_543 tr.rw.so_664 ig.yo.un_220 ceb.tl.zu_665
+ // [3cc0]
+ 0x01003b04, 0x0c0325ee, 0x1e1c55ee, 0x28556b12, // so.en.un_320 eu.nl.sv_422 rw.id.ms_422 ceb.rw.sw_654
+ 0x25213513, 0x1a6b5555, 0x350321af, 0x19100bec, // zu.jw.eu_665 rw.ceb.tl_442 jw.nl.zu_655 es.lt.gl_644
+ 0x0f190b0d, 0x32006b08, 0x130d2da0, 0x55212511, // es.gl.lv_554 ceb.bs.un_430 sk.cs.et_322 eu.jw.rw_653
+ 0x171325ee, 0x02033f0e, 0x4a5231af, 0x28213b12, // eu.et.sr_422 af.nl.da_555 az.ha.yo_655 so.jw.sw_654
+ // [3cd0]
+ 0x35003b07, 0x190a0f0e, 0x32162907, 0x276e2a07, // so.zu.un_420 lv.pt.gl_555 sl.hr.bs_432 mt.hmn.gd_432
+ 0x190a0ba0, 0x6b1e1c0d, 0x181923a4, 0x271801af, // es.pt.gl_322 id.ms.ceb_554 ca.gl.ga_433 en.ga.gd_655
+ 0x18010505, 0x4a135507, 0x1c3b1e0d, 0x030e3f04, // fr.en.ga_333 rw.et.yo_432 ms.so.id_554 af.is.nl_332
+ 0x250c3f08, 0x081a6bad, 0x06182713, 0x19122da7, // af.sv.eu_443 ceb.tl.no_643 gd.ga.de_665 sk.hu.gl_532
+ // [3ce0]
+ 0x0312250c, 0x1b251ea9, 0x060328a0, 0x1e1a1c11, // eu.hu.nl_543 ms.eu.tr_544 sw.nl.de_322 id.tl.ms_653
+ 0x0e082108, 0x04252108, 0x0c210812, 0x181b35ad, // jw.no.is_443 jw.eu.fi_443 no.jw.sv_654 zu.tr.ga_643
+ 0x271f01a9, 0x25092da0, 0x0c530e13, 0x211c6804, // en.cy.gd_544 sk.pl.eu_322 is.ht.sv_665 ig.id.jw_332
+ 0x272001a9, 0x271918ee, 0x06081355, 0x026b1a0d, // en.sq.gd_544 ga.gl.gd_422 et.no.de_442 tl.ceb.da_554
+ // [3cf0]
+ 0x02001a12, 0x1100181a, 0x1e211ca9, 0x20110fa4, // tl.da.un_640 ga.ro.un_760 id.jw.ms_544 lv.ro.sq_433
+ 0x6b1b28ac, 0x212a28a4, 0x132928a4, 0x08230611, // sw.tr.ceb_632 sw.mt.jw_433 sw.sl.et_433 de.ca.no_653
+ 0x3b1f5202, 0x03002513, 0x642718ec, 0x1900311b, // ha.cy.so_222 eu.nl.un_650 ga.gd.lg_644 az.gl.un_770
+ 0x6b002508, 0x0d1c130d, 0x190b11a4, 0x09135555, // eu.ceb.un_430 bh.mr.ne_554 ro.es.gl_433 rw.et.pl_442
+ // [3d00]
+ 0x52000f1a, 0x01072d05, 0x11050708, 0x124a1308, // lv.ha.un_760 sk.it.en_333 it.fr.ro_443 et.yo.hu_443
+ 0x230d2da9, 0x23011b0c, 0x19002d0d, 0x01000921, // sk.cs.ca_544 tr.en.ca_543 sk.gl.un_540 pl.en.un_860
+ 0x12046baf, 0x02180607, 0x040a0702, 0x25190aa4, // ceb.fi.hu_655 de.ga.da_432 bg.mk.ru_222 pt.gl.eu_433
+ 0x19521a0d, 0x06030f07, 0x080e03ad, 0x28004a13, // tl.ha.gl_554 lv.nl.de_432 nl.is.no_643 yo.sw.un_650
+ // [3d10]
+ 0x0d006814, 0x315528a4, 0x080218ee, 0x20285304, // ig.cs.un_660 sw.rw.az_433 ga.da.no_422 ht.sw.sq_332
+ 0x0205010c, 0x031c08ad, 0x3b3564af, 0x324a5508, // en.fr.da_543 no.id.nl_643 lg.zu.so_655 rw.yo.bs_443
+ 0x2d0d4aa6, 0x0c0a0809, 0x0a002108, 0x0a00010c, // yo.cs.sk_521 no.pt.sv_444 jw.pt.un_430 en.pt.un_530
+ 0x550128a4, 0x0c000621, 0x025508af, 0x100a04ee, // sw.en.rw_433 de.sv.un_860 no.rw.da_655 fi.pt.lt_422
+ // [3d20]
+ 0x060c010c, 0x350255af, 0x190b070c, 0x19002d08, // en.sv.de_543 rw.da.zu_655 it.es.gl_543 sk.gl.un_430
+ 0x2d0d0cee, 0x190b18ec, 0x020c08ee, 0x01001904, // sv.cs.sk_422 ga.es.gl_644 no.sv.da_422 gl.en.un_320
+ 0x050a3b04, 0x1a53010c, 0x0c06030c, 0x1055010c, // so.pt.fr_332 en.ht.tl_543 nl.de.sv_543 en.rw.lt_543
+ 0x0a005519, 0x6455050b, 0x234a050b, 0x64030607, // rw.pt.un_750 fr.rw.lg_542 fr.yo.ca_542 de.nl.lg_432
+ // [3d30]
+ 0x550c025a, 0x041110af, 0x1f354a05, 0x23004a12, // da.sv.rw_553 be.ro.ru_655 yo.zu.cy_333 yo.ca.un_640
+ 0x28001c04, 0x283b35ad, 0x4a000708, 0x1300030c, // id.sw.un_320 zu.so.sw_643 it.yo.un_430 nl.et.un_530
+ 0x0e0f1011, 0x2168200c, 0x08005304, 0x10000f2b, // lt.lv.is_653 sq.ig.jw_543 ht.no.un_320 lv.lt.un_980
+ 0x0e03100c, 0x35644aa4, 0x05006e04, 0x0b191812, // lt.nl.is_543 yo.lg.zu_433 hmn.fr.un_320 ga.gl.es_654
+ // [3d40]
+ 0x08132a0e, 0x643568af, 0x0e002d13, 0x3f036e08, // mt.et.no_555 ig.zu.lg_655 sk.is.un_650 hmn.nl.af_443
+ 0x04001a0e, 0x07002322, 0x136b1a07, 0x085528ee, // tl.fi.un_550 ca.it.un_870 tl.ceb.et_432 sw.rw.no_422
+ 0x01060c0c, 0x31041b21, 0x1e026407, 0x1f131aee, // sv.de.en_543 tr.fi.az_876 lg.da.ms_432 tl.et.cy_422
+ 0x3b000a12, 0x28011805, 0x3b0d52a0, 0x1a3b6ba0, // pt.so.un_640 ga.en.sw_333 ha.cs.so_322 ceb.so.tl_322
+ // [3d50]
+ 0x550664a0, 0x120635a4, 0x126b1a60, 0x06093f04, // lg.de.rw_322 zu.de.hu_433 tl.ceb.hu_664 af.pl.de_332
+ 0x291135a9, 0x3f060c14, 0x0a000616, 0x070511af, // zu.ro.sl_544 sv.de.af_666 de.pt.un_720 ro.fr.it_655
+ 0x1a036ba4, 0x0c251e0c, 0x171110a4, 0x190a2305, // ceb.nl.tl_433 ms.eu.sv_543 be.ro.sr_433 ca.pt.gl_333
+ 0x28352008, 0x04080305, 0x041a6b0c, 0x284a350c, // sq.zu.sw_443 nl.no.fi_333 ceb.tl.fi_543 zu.yo.sw_543
+ // [3d60]
+ 0x0e060ca9, 0x5300350d, 0x29552808, 0x6b102904, // sv.de.is_544 zu.ht.un_540 sw.rw.sl_443 sl.lt.ceb_332
+ 0x0d253b11, 0x17081105, 0x4a003522, 0x3f0112a9, // so.eu.cs_653 ro.uk.sr_333 zu.yo.un_870 hu.en.af_544
+ 0x0e2a080d, 0x2a070c07, 0x35115504, 0x1f283512, // no.mt.is_554 sv.it.mt_432 rw.ro.zu_332 zu.sw.cy_654
+ 0x32002911, 0x253f01a4, 0x19230aa9, 0x010c1f05, // sl.bs.un_630 en.af.eu_433 pt.ca.gl_544 cy.sv.en_333
+ // [3d70]
+ 0x08291607, 0x2821120c, 0x29001f14, 0x1004110c, // hr.sl.no_432 hu.jw.sw_543 cy.sl.un_660 ro.ru.be_543
+ 0x6b033f05, 0x20251b0c, 0x1c062107, 0x1c1b1ea0, // af.nl.ceb_333 tr.eu.sq_543 jw.de.id_432 ms.tr.id_322
+ 0x1e061ca4, 0x250c08a4, 0x35646813, 0x0e08010c, // id.de.ms_433 no.sv.eu_433 ig.lg.zu_665 en.no.is_543
+ 0x52311b60, 0x123f0305, 0x1100062b, 0x0c002908, // tr.az.ha_664 nl.af.hu_333 de.ro.un_980 sl.sv.un_430
+ // [3d80]
+ 0x18001e02, 0x2907090c, 0x531827ad, 0x0c0729a4, // ms.ga.un_220 pl.it.sl_543 gd.ga.ht_643 sl.it.sv_433
+ 0x1e1c27a4, 0x1b6b1a0e, 0x21521eee, 0x2d100d14, // gd.id.ms_433 tl.ceb.tr_555 ms.ha.jw_422 cs.lt.sk_666
+ 0x2a031a07, 0x083521a4, 0x35000a0d, 0x28006412, // tl.nl.mt_432 jw.zu.no_433 pt.zu.un_540 lg.sw.un_640
+ 0x2d121aa0, 0x090a18ec, 0x03643f12, 0x12003504, // tl.hu.sk_322 ga.pt.pl_644 af.lg.nl_654 zu.hu.un_320
+ // [3d90]
+ 0x04006407, 0x290c5307, 0x0a00121b, 0x17000a04, // lg.fi.un_420 ht.sv.sl_432 hu.pt.un_770 mk.sr.un_320
+ 0x11231e07, 0x0400280c, 0x0b230a07, 0x121301af, // ms.ca.ro_432 sw.fi.un_530 pt.ca.es_432 en.et.hu_655
+ 0x0264680c, 0x130e2007, 0x68526413, 0x12002108, // ig.lg.da_543 sq.is.et_432 lg.ha.ig_665 jw.hu.un_430
+ 0x0d001c04, 0x3b312aac, 0x02000122, 0x23001219, // id.cs.un_320 mt.az.so_632 en.da.un_870 hu.ca.un_750
+ // [3da0]
+ 0x130e0ca4, 0x1c002502, 0x08026eee, 0x13106e07, // sv.is.et_433 eu.id.un_220 hmn.da.no_422 hmn.lt.et_432
+ 0x0a002310, 0x230b01a4, 0x19251f07, 0x52352812, // ca.pt.un_620 en.es.ca_433 cy.eu.gl_432 sw.zu.ha_654
+ 0x6e080209, 0x3f005302, 0x190b25a6, 0x0200130d, // da.no.hmn_444 ht.af.un_220 eu.es.gl_521 et.da.un_540
+ 0x17100412, 0x21001302, 0x170f3204, 0x04000a33, // ru.be.sr_654 et.jw.un_220 bs.lv.sr_332 mk.ru.un_A70
+ // [3db0]
+ 0x09290f0c, 0x4a005322, 0x352555a9, 0x0600071a, // lv.sl.pl_543 ht.yo.un_870 rw.eu.zu_544 it.de.un_760
+ 0x3f0c0302, 0x18111eee, 0x3f016402, 0x16062508, // nl.sv.af_222 ms.ro.ga_422 lg.en.af_222 eu.de.hr_443
+ 0x0c006e0c, 0x19002302, 0x2b006e0e, 0x162d0d0d, // hmn.sv.un_530 ca.gl.un_220 hmn.vi.un_550 cs.sk.hr_554
+ 0x03000c08, 0x19060bee, 0x050b07ee, 0x16001702, // sv.nl.un_430 es.de.gl_422 it.es.fr_422 sr.hr.un_220
+ // [3dc0]
+ 0x1b082502, 0x1b002809, 0x53004a19, 0x07534a5a, // eu.no.tr_222 sw.tr.un_440 yo.ht.un_750 yo.ht.it_553
+ 0x0a100704, 0x180a1902, 0x120420ac, 0x3f0c0313, // bg.be.mk_332 gl.pt.ga_222 sq.fi.hu_632 nl.sv.af_665
+ 0x131b0e07, 0x192d0bec, 0x190e0a0c, 0x5306020e, // is.tr.et_432 es.sk.gl_644 pt.is.gl_543 da.de.ht_555
+ 0x19120b04, 0x18001902, 0x11283511, 0x31002919, // es.hu.gl_332 gl.ga.un_220 zu.sw.ro_653 sl.az.un_750
+ // [3dd0]
+ 0x1b003b12, 0x05003208, 0x21231ca9, 0x1b682513, // so.tr.un_640 bs.fr.un_430 id.ca.jw_544 eu.ig.tr_665
+ 0x524a68a9, 0x31003b0c, 0x4a000713, 0x01006e04, // ig.yo.ha_544 so.az.un_530 it.yo.un_650 hmn.en.un_320
+ 0x09282107, 0x35314a07, 0x6b121007, 0x203564ac, // jw.sw.pl_432 yo.az.zu_432 lt.hu.ceb_432 lg.zu.sq_632
+ 0x12000f18, 0x02005204, 0x3b020c0c, 0x3b352107, // lv.hu.un_740 ha.da.un_320 sv.da.so_543 jw.zu.so_432
+ // [3de0]
+ 0x040a07ec, 0x21190bad, 0x060d35a4, 0x20002513, // bg.mk.ru_644 es.gl.jw_643 zu.cs.de_433 eu.sq.un_650
+ 0x552804a4, 0x68005513, 0x21106ba4, 0x17080aa7, // fi.sw.rw_433 rw.ig.un_650 ceb.lt.jw_433 mk.uk.sr_532
+ 0x09253511, 0x0413080c, 0x5511280c, 0x25000f21, // zu.eu.pl_653 no.et.fi_543 sw.ro.rw_543 lv.eu.un_860
+ 0x2a6b13a9, 0x10531ca4, 0x642555a9, 0x11126807, // et.ceb.mt_544 id.ht.lt_433 rw.eu.lg_544 ig.hu.ro_432
+ // [3df0]
+ 0x1e1c3b04, 0x1a075307, 0x02000314, 0x07004a11, // so.id.ms_332 ht.it.tl_432 nl.da.un_660 yo.it.un_630
+ 0x033f2102, 0x1a6b1308, 0x52251a0d, 0x53255514, // jw.af.nl_222 et.ceb.tl_443 tl.eu.ha_554 rw.eu.ht_666
+ 0x07005305, 0x20002104, 0x1a00070d, 0x0325130c, // ht.it.un_330 jw.sq.un_320 it.tl.un_540 et.eu.nl_543
+ 0x52001907, 0x18315304, 0x0c082a0d, 0x4a0964a9, // gl.ha.un_420 ht.az.ga_332 mt.no.sv_554 lg.pl.yo_544
+ // [3e00]
+ 0x0f130408, 0x0955290c, 0x03095308, 0x1100031b, // fi.et.lv_443 sl.rw.pl_543 ht.pl.nl_443 nl.ro.un_770
+ 0x230511af, 0x68162da4, 0x6b002304, 0x031f0604, // ro.fr.ca_655 sk.hr.ig_433 ca.ceb.un_320 de.cy.nl_332
+ 0x093f2da4, 0x070a1004, 0x192a0714, 0x0f212aa4, // sk.af.pl_433 be.mk.bg_332 it.mt.gl_666 mt.jw.lv_433
+ 0x010435a0, 0x04101308, 0x08020cad, 0x2825135a, // zu.fi.en_322 et.lt.fi_443 sv.da.no_643 et.eu.sw_553
+ // [3e10]
+ 0x1125130c, 0x070a1113, 0x1e251a08, 0x10111fee, // et.eu.ro_543 ro.mk.bg_665 tl.eu.ms_443 cy.ro.lt_422
+ 0x2d002902, 0x322d29a4, 0x2535130c, 0x350f130c, // sl.sk.un_220 sl.sk.bs_433 et.zu.eu_543 et.lv.zu_543
+ 0x2800131b, 0x1b01130c, 0x35042805, 0x011f0b12, // et.sw.un_770 et.en.tr_543 sw.fi.zu_333 es.cy.en_654
+ 0x04133205, 0x3200090c, 0x23001112, 0x5364280c, // bs.et.fi_333 pl.bs.un_530 ro.ca.un_640 sw.lg.ht_543
+ // [3e20]
+ 0x1c0e10ad, 0x28001a07, 0x01002434, 0x13350ead, // lt.is.id_643 tl.sw.un_420 yi.iw.un_A80 is.zu.et_643
+ 0x09131c12, 0x12182dad, 0x03061ca4, 0x061803a0, // mr.bh.hi_654 sk.ga.hu_643 id.de.nl_433 nl.ga.de_322
+ 0x032b3f07, 0x070811a0, 0x162d2908, 0x0b0105af, // af.vi.nl_432 ro.uk.bg_322 sl.sk.hr_443 fr.en.es_655
+ 0x28351b0c, 0x0e043504, 0x23006408, 0x040721a9, // tr.zu.sw_543 zu.fi.is_332 lg.ca.un_430 jw.it.fi_544
+ // [3e30]
+ 0x04132105, 0x1e041307, 0x041a6b08, 0x043513a4, // jw.et.fi_333 et.fi.ms_432 ceb.tl.fi_443 et.zu.fi_433
+ 0x040d11ee, 0x2b001a02, 0x21006b02, 0x28213b07, // ro.cs.fi_422 tl.vi.un_220 ceb.jw.un_220 so.jw.sw_432
+ 0x03051105, 0x32126e0c, 0x0900180c, 0x166e2912, // ro.fr.nl_333 hmn.hu.bs_543 ga.pl.un_530 sl.hmn.hr_654
+ 0x256409a4, 0x0a000705, 0x53006e22, 0x0a1b530c, // pl.lg.eu_433 bg.mk.un_330 hmn.ht.un_870 ht.tr.pt_543
+ // [3e40]
+ 0x2d6e2912, 0x07081014, 0x28641f0c, 0x6e001e0d, // sl.hmn.sk_654 be.uk.bg_666 cy.lg.sw_543 ms.hmn.un_540
+ 0x0b000808, 0x11000b0c, 0x12206eec, 0x1c645512, // no.es.un_430 es.ro.un_530 hmn.sq.hu_644 rw.lg.id_654
+ 0x2d0f110c, 0x1b0604ad, 0x040f2aa6, 0x08060208, // ro.lv.sk_543 fi.de.tr_643 mt.lv.fi_521 da.de.no_443
+ 0x28555207, 0x0c18275a, 0x25000412, 0x1c641e0c, // ha.rw.sw_432 gd.ga.sv_553 fi.eu.un_640 ms.lg.id_543
+ // [3e50]
+ 0x0f002105, 0x070a1011, 0x29002023, 0x011311ad, // jw.lv.un_330 be.mk.bg_653 sq.sl.un_880 ro.et.en_643
+ 0x061c1e12, 0x29001e08, 0x6e00532c, 0x0f5511ad, // ms.id.de_654 ms.sl.un_430 ht.hmn.un_990 ro.rw.lv_643
+ 0x2d12110c, 0x4a11020b, 0x1e002a04, 0x01001804, // ro.hu.sk_543 da.ro.yo_542 mt.ms.un_320 ga.en.un_320
+ 0x02292da9, 0x2b521807, 0x0e321713, 0x3b6864a0, // sk.sl.da_544 ga.ha.vi_432 sr.bs.is_665 lg.ig.so_322
+ // [3e60]
+ 0x29551e0d, 0x550711af, 0x1e1c040e, 0x231120a4, // ms.rw.sl_554 ro.it.rw_655 fi.id.ms_555 sq.ro.ca_433
+ 0x4a121902, 0x060e1713, 0x25000414, 0x1217290c, // gl.hu.yo_222 sr.is.de_665 fi.eu.un_660 sl.sr.hu_543
+ 0x1104170c, 0x13040660, 0x1e001b05, 0x205329a9, // sr.ru.ro_543 de.fi.et_664 tr.ms.un_330 sl.ht.sq_544
+ 0x2a3120a7, 0x112805a7, 0x06521107, 0x18252804, // sq.az.mt_532 fr.sw.ro_532 ro.ha.de_432 sw.eu.ga_332
+ // [3e70]
+ 0x081a0c02, 0x09006e2c, 0x06046411, 0x2a2852ad, // sv.tl.no_222 hmn.pl.un_990 lg.fi.de_653 ha.sw.mt_643
+ 0x080204ec, 0x55281aa4, 0x1f001814, 0x0e4a120c, // fi.da.no_644 tl.sw.rw_433 ga.cy.un_660 hu.yo.is_543
+ 0x17002a13, 0x2a640412, 0x350f1fa7, 0x201232af, // mt.sr.un_650 fi.lg.mt_654 cy.lv.zu_532 bs.hu.sq_655
+ 0x6421200c, 0x23004a05, 0x19001f19, 0x281b64a4, // sq.jw.lg_543 yo.ca.un_330 cy.gl.un_750 lg.tr.sw_433
+ // [3e80]
+ 0x10000805, 0x2d006e08, 0x1f006421, 0x29002d05, // no.lt.un_330 hmn.sk.un_430 lg.cy.un_860 sk.sl.un_330
+ 0x2500030d, 0x4a000804, 0x530601ad, 0x0e004a13, // nl.eu.un_540 no.yo.un_320 en.de.ht_643 yo.is.un_650
+ 0x0c000104, 0x1c1e1a07, 0x2000131a, 0x255321ad, // en.sv.un_320 tl.ms.id_432 et.sq.un_760 jw.ht.eu_643
+ 0x08201ba4, 0x2b27180e, 0x522753ec, 0x52122ba9, // tr.sq.no_433 ga.gd.vi_555 ht.gd.ha_644 vi.hu.ha_544
+ // [3e90]
+ 0x07045205, 0x2a1e1c12, 0x0000161c, 0x13042aec, // ha.fi.it_333 id.ms.mt_654 hr.un.un_800 mt.fi.et_644
+ 0x2706250c, 0x1300520c, 0x1c0f1fa9, 0x640452ad, // eu.de.gd_543 ha.et.un_530 cy.lv.id_544 ha.fi.lg_643
+ 0x09130d0d, 0x6b073b07, 0x25002d13, 0x532552af, // ne.bh.hi_554 so.it.ceb_432 sk.eu.un_650 ha.eu.ht_655
+ 0x4a1a52ec, 0x0c000309, 0x022d17a4, 0x521f4aaf, // ha.tl.yo_644 nl.sv.un_440 sr.sk.da_433 yo.cy.ha_655
+ // [3ea0]
+ 0x2d04290b, 0x29205504, 0x0e060305, 0x1627010c, // sl.fi.sk_542 rw.sq.sl_332 nl.de.is_333 en.gd.hr_543
+ 0x123f0360, 0x52531e04, 0x042012a7, 0x0b1a6bee, // nl.af.hu_664 ms.ht.ha_332 hu.sq.fi_532 ceb.tl.es_422
+ 0x1b000619, 0x0e4a12b6, 0x3f1b1e14, 0x1b001a14, // de.tr.un_750 hu.yo.is_766 ms.tr.af_666 tl.tr.un_660
+ 0x130c0255, 0x31201b0c, 0x130f010c, 0x32292dee, // da.sv.et_442 tr.sq.az_543 en.lv.et_543 sk.sl.bs_422
+ // [3eb0]
+ 0x321707a4, 0x0a00170d, 0x6e3b060c, 0x6b0c0455, // it.sr.bs_433 sr.mk.un_540 de.so.hmn_543 fi.sv.ceb_442
+ 0x6e001c0d, 0x10171104, 0x1c002904, 0x55000a12, // id.hmn.un_540 ro.sr.be_332 sl.id.un_320 pt.rw.un_640
+ 0x320d2955, 0x130208a4, 0x02100609, 0x2a006b19, // sl.cs.bs_442 no.da.et_433 de.lt.da_444 ceb.mt.un_750
+ 0x520f06af, 0x1c002508, 0x6400350c, 0x130e0caf, // de.lv.ha_655 eu.id.un_430 zu.lg.un_530 sv.is.et_655
+ // [3ec0]
+ 0x20230509, 0x1e006e05, 0x203f10a0, 0x01006808, // fr.ca.sq_444 hmn.ms.un_330 lt.af.sq_322 ig.en.un_430
+ 0x0e271860, 0x2a091b07, 0x161e6ea7, 0x23130960, // ga.gd.is_664 tr.pl.mt_432 hmn.ms.hr_532 pl.et.ca_664
+ 0x0c136ea7, 0x27185305, 0x020c1fa4, 0x6e011107, // hmn.et.sv_532 ht.ga.gd_333 cy.sv.da_433 ro.en.hmn_432
+ 0x0c0e53ad, 0x060e180c, 0x23001021, 0x53050eaf, // ht.is.sv_643 ga.is.de_543 lt.ca.un_860 is.fr.ht_655
+ // [3ed0]
+ 0x351c1ead, 0x0f1f130c, 0x271853af, 0x01522802, // ms.id.zu_643 et.cy.lv_543 ht.ga.gd_655 sw.ha.en_222
+ 0x03643fa4, 0x08090e07, 0x271f0e0c, 0x18290111, // af.lg.nl_433 is.pl.no_432 is.cy.gd_543 en.sl.ga_653
+ 0x0d1c090d, 0x3f2903a4, 0x182708a4, 0x1e1c35a4, // hi.mr.ne_554 nl.sl.af_433 no.gd.ga_433 zu.id.ms_433
+ 0x18030605, 0x080e6b02, 0x0e27250c, 0x080c53af, // de.nl.ga_333 ceb.is.no_222 eu.gd.is_543 ht.sv.no_655
+ // [3ee0]
+ 0x122164a0, 0x53000612, 0x320e17a9, 0x180e53a4, // lg.jw.hu_322 de.ht.un_640 sr.is.bs_544 ht.is.ga_433
+ 0x4a086ba9, 0x182753ec, 0x0818530b, 0x29002121, // ceb.no.yo_544 ht.gd.ga_644 ht.ga.no_542 jw.sl.un_860
+ 0x024a6bee, 0x03003504, 0x083f0308, 0x6b213512, // ceb.yo.da_422 zu.nl.un_320 nl.af.no_443 zu.jw.ceb_654
+ 0x52030607, 0x1318060c, 0x074a2107, 0x09005502, // de.nl.ha_432 de.ga.et_543 jw.yo.it_432 rw.pl.un_220
+ // [3ef0]
+ 0x12060a13, 0x213555a4, 0x3b1e2104, 0x070b0a05, // pt.de.hu_665 rw.zu.jw_433 jw.ms.so_332 pt.es.it_333
+ 0x522d5508, 0x0420080c, 0x35070b07, 0x3b190bad, // rw.sk.ha_443 no.sq.fi_543 es.it.zu_432 es.gl.so_643
+ 0x55641b55, 0x10005307, 0x060e1ca0, 0x1b201312, // tr.lg.rw_442 ht.lt.un_420 id.is.de_322 et.sq.tr_654
+ 0x0f001907, 0x286452a4, 0x131a6b12, 0x0e001f18, // gl.lv.un_420 ha.lg.sw_433 ceb.tl.et_654 cy.is.un_740
+ // [3f00]
+ 0x11532aec, 0x29002105, 0x06291eee, 0x0b0a1ea0, // mt.ht.ro_644 jw.sl.un_330 ms.sl.de_422 ms.pt.es_322
+ 0x21291711, 0x083b2a55, 0x1a6b210c, 0x0100180e, // sr.sl.jw_653 mt.so.no_442 jw.ceb.tl_543 ga.en.un_550
+ 0x55062805, 0x64006b19, 0x2d0d21ec, 0x21281fa4, // sw.de.rw_333 ceb.lg.un_750 jw.cs.sk_644 cy.sw.jw_433
+ 0x64520909, 0x212d0d13, 0x1c005221, 0x5500170e, // pl.ha.lg_444 cs.sk.jw_665 ha.id.un_860 sr.rw.un_550
+ // [3f10]
+ 0x1f030e55, 0x20000e0d, 0x1e211ca4, 0x10211ca4, // is.nl.cy_442 is.sq.un_540 id.jw.ms_433 id.jw.lt_433
+ 0x2135520c, 0x32160eee, 0x0d2d2110, 0x1e1c2104, // ha.zu.jw_543 is.hr.bs_422 jw.sk.cs_642 jw.id.ms_332
+ 0x033f04ec, 0x32000702, 0x2d003f13, 0x550719a6, // fi.af.nl_644 it.bs.un_220 af.sk.un_650 gl.it.rw_521
+ 0x171304ec, 0x0a000304, 0x55005304, 0x2a0405ee, // fi.et.sr_644 nl.pt.un_320 ht.rw.un_320 fr.fi.mt_422
+ // [3f20]
+ 0x0d2d2111, 0x11001705, 0x2d002119, 0x071a0caf, // jw.sk.cs_653 sr.ro.un_330 jw.sk.un_750 sv.tl.it_655
+ 0x03000707, 0x211b0b12, 0x1e111b07, 0x25186808, // it.nl.un_420 es.tr.jw_654 tr.ro.ms_432 ig.ga.eu_443
+ 0x6e001e12, 0x083b0e04, 0x3b001323, 0x35000f07, // ms.hmn.un_640 is.so.no_332 et.so.un_880 lv.zu.un_420
+ 0x270e1808, 0x09006409, 0x283f35ad, 0x091b53ad, // ga.is.gd_443 lg.pl.un_440 zu.af.sw_643 ht.tr.pl_643
+ // [3f30]
+ 0x31001118, 0x211f135a, 0x0321080b, 0x53000a21, // ro.az.un_740 et.cy.jw_553 no.jw.nl_542 pt.ht.un_860
+ 0x18122d60, 0x1a284aa6, 0x311b0aac, 0x2a001f09, // sk.hu.ga_664 yo.sw.tl_521 pt.tr.az_632 cy.mt.un_440
+ 0x110c03ec, 0x1a281012, 0x64001f1b, 0x2d0d4aee, // nl.sv.ro_644 lt.sw.tl_654 cy.lg.un_770 yo.cs.sk_422
+ 0x2a000104, 0x013b2d0b, 0x0a062512, 0x52003202, // en.mt.un_320 sk.so.en_542 eu.de.pt_654 bs.ha.un_220
+ // [3f40]
+ 0x133f06a4, 0x31121b11, 0x231127a0, 0x094a6404, // de.af.et_433 tr.hu.az_653 gd.ro.ca_322 lg.yo.pl_332
+ 0x120a01af, 0x3b000223, 0x0d3f01a4, 0x68000104, // en.pt.hu_655 da.so.un_880 en.af.cs_433 en.ig.un_320
+ 0x061b13ec, 0x215352ee, 0x6b1a5505, 0x04000e20, // et.tr.de_644 ha.ht.jw_422 rw.tl.ceb_333 is.fi.un_850
+ 0x28680313, 0x0e0c1fad, 0x0b1123ee, 0x53022507, // nl.ig.sw_665 cy.sv.is_643 ca.ro.es_422 eu.da.ht_432
+ // [3f50]
+ 0x1f002911, 0x21121c11, 0x09003b12, 0x1029040b, // sl.cy.un_630 id.hu.jw_653 so.pl.un_640 fi.sl.lt_542
+ 0x2d3528a0, 0x0b00010c, 0x10000812, 0x311a3507, // sw.zu.sk_322 en.es.un_530 uk.be.un_640 zu.tl.az_432
+ 0x012911a4, 0x642a3512, 0x6435210d, 0x083b0caf, // ro.sl.en_433 zu.mt.lg_654 jw.zu.lg_554 sv.so.no_655
+ 0x081f310c, 0x1f000408, 0x20006407, 0x322d10a0, // az.cy.no_543 fi.cy.un_430 lg.sq.un_420 lt.sk.bs_322
+ // [3f60]
+ 0x28003521, 0x083f0313, 0x19200b5a, 0x2d001e0c, // zu.sw.un_860 nl.af.no_665 es.sq.gl_553 ms.sk.un_530
+ 0x31182712, 0x081c0e08, 0x4a18270c, 0x554a6408, // gd.ga.az_654 is.id.no_443 gd.ga.yo_543 lg.yo.rw_443
+ 0x0c1c530c, 0x3f1e64a9, 0x0802190c, 0x2d0d1614, // ht.id.sv_543 lg.ms.af_544 gl.da.no_543 hr.cs.sk_666
+ 0x1304280c, 0x2864350c, 0x1c0464a6, 0x20310fad, // sw.fi.et_543 zu.lg.sw_543 lg.fi.id_521 lv.az.sq_643
+ // [3f70]
+ 0x13000617, 0x552a530c, 0x1e001308, 0x52006e07, // de.et.un_730 ht.mt.rw_543 et.ms.un_430 hmn.ha.un_420
+ 0x160b1104, 0x351e64a9, 0x0c2916a4, 0x64001304, // ro.es.hr_332 lg.ms.zu_544 hr.sl.sv_433 et.lg.un_320
+ 0x521a250c, 0x0c232008, 0x040e0c12, 0x2d320aa0, // eu.tl.ha_543 sq.ca.sv_443 sv.is.fi_654 pt.bs.sk_322
+ 0x166e1707, 0x12001829, 0x29211c08, 0x0b0a2daf, // sr.hmn.hr_432 ar.ur.un_960 id.jw.sl_443 sk.pt.es_655
+ // [3f80]
+ 0x18271112, 0x0b2507a0, 0x020c08a0, 0x3f2125ac, // ro.gd.ga_654 it.eu.es_322 no.sv.da_322 eu.jw.af_632
+ 0x0b2d0a07, 0x316428a6, 0x07235205, 0x2d0d4aa4, // pt.sk.es_432 sw.lg.az_521 ha.ca.it_333 yo.cs.sk_433
+ 0x29000c04, 0x31272a08, 0x1f29010c, 0x05080205, // sv.sl.un_320 mt.gd.az_443 en.sl.cy_543 da.no.fr_333
+ 0x1e1c1aee, 0x3f0f0da4, 0x01072a11, 0x190a11a0, // tl.id.ms_422 cs.lv.af_433 mt.it.en_653 ro.pt.gl_322
+ // [3f90]
+ 0x2810110d, 0x3f00110c, 0x312029a0, 0x13000209, // ro.lt.sw_554 ro.af.un_530 sl.sq.az_322 da.et.un_440
+ 0x522a6808, 0x4a122d07, 0x32162a09, 0x273b06a4, // ig.mt.ha_443 sk.hu.yo_432 mt.hr.bs_444 de.so.gd_433
+ 0x4a523b5a, 0x6b006e0c, 0x23003b11, 0x250411af, // so.ha.yo_553 hmn.ceb.un_530 so.ca.un_630 ro.fi.eu_655
+ 0x556453a4, 0x12131bee, 0x4a6421a0, 0x3b252a0c, // ht.lg.rw_433 tr.et.hu_422 jw.lg.yo_322 mt.eu.so_543
+ // [3fa0]
+ 0x05000f36, 0x0910250c, 0x32162d0c, 0x19280ba7, // lv.fr.un_AA0 eu.lt.pl_543 sk.hr.bs_543 es.sw.gl_532
+ 0x0c0e0812, 0x0d0a0b60, 0x0e3f030c, 0x10252da4, // no.is.sv_654 es.pt.cs_664 nl.af.is_543 sk.eu.lt_433
+ 0x12090111, 0x68132514, 0x0425640c, 0x68006b13, // en.pl.hu_653 eu.et.ig_666 lg.eu.fi_543 ceb.ig.un_650
+ 0x536b1107, 0x2a1f01ec, 0x20002507, 0x2d0d0eee, // ro.ceb.ht_432 en.cy.mt_644 eu.sq.un_420 is.cs.sk_422
+ // [3fb0]
+ 0x1f006404, 0x1f000112, 0x20092509, 0x5513255a, // lg.cy.un_320 en.cy.un_640 eu.pl.sq_444 eu.et.rw_553
+ 0x532a6404, 0x6b00680d, 0x010511af, 0x194a0ba0, // lg.mt.ht_332 ig.ceb.un_540 ro.fr.en_655 es.yo.gl_322
+ 0x202910a9, 0x0c0625af, 0x1e1c0e0e, 0x251355a4, // lt.sl.sq_544 eu.de.sv_655 is.id.ms_555 rw.et.eu_433
+ 0x10252a0d, 0x0e002d18, 0x1c3b210c, 0x2a002509, // mt.eu.lt_554 sk.is.un_740 jw.so.id_543 eu.mt.un_440
+ // [3fc0]
+ 0x2a020812, 0x20040607, 0x31190a0c, 0x092a0711, // no.da.mt_654 de.fi.sq_432 pt.gl.az_543 it.mt.pl_653
+ 0x080c23a7, 0x02040804, 0x2b002313, 0x53002a02, // ca.sv.no_532 no.fi.da_332 ca.vi.un_650 mt.ht.un_220
+ 0x1b080214, 0x3f031304, 0x1300350e, 0x12060c02, // da.no.tr_666 et.nl.af_332 zu.et.un_550 sv.de.hu_222
+ 0x0c0801a4, 0x250c2aec, 0x270511ad, 0x010a1f0c, // en.no.sv_433 mt.sv.eu_644 ro.fr.gd_643 cy.pt.en_543
+ // [3fd0]
+ 0x2a00250d, 0x07534aa7, 0x05006b02, 0x10000c08, // eu.mt.un_540 yo.ht.it_532 ceb.fr.un_220 sv.lt.un_430
+ 0x030802ec, 0x5300060c, 0x03080213, 0x10002d08, // da.no.nl_644 de.ht.un_530 da.no.nl_665 sk.lt.un_430
+ 0x3b002322, 0x4a000e19, 0x08026e14, 0x64206805, // ca.so.un_870 is.yo.un_750 hmn.da.no_666 ig.sq.lg_333
+ 0x3b2a18a4, 0x02311bad, 0x05003f02, 0x136416a4, // ga.mt.so_433 tr.az.da_643 af.fr.un_220 hr.lg.et_433
+ // [3fe0]
+ 0x64001312, 0x21033b0c, 0x020408ee, 0x1e1a1ca0, // et.lg.un_640 so.nl.jw_543 no.fi.da_422 id.tl.ms_322
+ 0x08022a05, 0x18002d0d, 0x2a0d1e0b, 0x1008170c, // mt.da.no_333 sk.ga.un_540 ms.cs.mt_542 sr.uk.be_543
+ 0x3b52250c, 0x07001f1a, 0x120420a4, 0x01080260, // eu.ha.so_543 cy.it.un_760 sq.fi.hu_433 da.no.en_664
+ 0x18272313, 0x04102304, 0x02080c07, 0x326e10a4, // ca.gd.ga_665 ca.lt.fi_332 sv.no.da_432 lt.hmn.bs_433
+ // [3ff0]
+ 0x53106e08, 0x040a100c, 0x080203ec, 0x07256ea7, // hmn.lt.ht_443 be.mk.ru_543 nl.da.no_644 hmn.eu.it_532
+ 0x06000207, 0x2b006e04, 0x0d131c04, 0x3f0c080c, // da.de.un_420 hmn.vi.un_320 mr.bh.ne_332 no.sv.af_543
+ 0x100a11ee, 0x293f53ee, 0x1c090d07, 0x08000302, // ro.mk.be_422 ht.af.sl_422 ne.hi.mr_432 nl.no.un_220
+ 0x1b211ea4, 0x20001804, 0x6b1f0507, 0x0a2120a7, // ms.jw.tr_433 ga.sq.un_320 fr.cy.ceb_432 sq.jw.pt_532
+
+ // [4000]
+ 0x3b001a04, 0x21531c07, 0x0a291a09, 0x0e000f1b, // tl.so.un_320 id.ht.jw_432 tl.sl.pt_444 lv.is.un_770
+ 0x3b1a55a0, 0x641a55ad, 0x0b23180e, 0x321b2055, // rw.tl.so_322 rw.tl.lg_643 ga.ca.es_555 sq.tr.bs_442
+ 0x6b2a18a4, 0x0f091014, 0x0a190bad, 0x6b1935ee, // ga.mt.ceb_433 lt.pl.lv_666 es.gl.pt_643 zu.gl.ceb_422
+ 0x21230b09, 0x521e3b0c, 0x18002812, 0x352864af, // es.ca.jw_444 so.ms.ha_543 sw.ga.un_640 lg.sw.zu_655
+ // [4010]
+ 0x020608a0, 0x12000505, 0x19250b0c, 0x13000411, // no.de.da_322 fr.hu.un_330 es.eu.gl_543 fi.et.un_630
+ 0x32171ea4, 0x100a0408, 0x23180ba0, 0x290c090c, // ms.sr.bs_433 ru.mk.be_443 es.ga.ca_322 pl.sv.sl_543
+ 0x230b0ea4, 0x080a040c, 0x273b250c, 0x1000190e, // is.es.ca_433 ru.mk.uk_543 eu.so.gd_543 gl.lt.un_550
+ 0x3f030807, 0x55003108, 0x28072d08, 0x290207ee, // no.nl.af_432 az.rw.un_430 sk.it.sw_443 it.da.sl_422
+ // [4020]
+ 0x190b4a0e, 0x07063b07, 0x53001e04, 0x31523ba4, // yo.es.gl_555 so.de.it_432 ms.ht.un_320 so.ha.az_433
+ 0x3f3b01a9, 0x0c0803ec, 0x3b271f12, 0x31035512, // en.so.af_544 nl.no.sv_644 cy.gd.so_654 rw.nl.az_654
+ 0x192d0d5a, 0x2a3b2112, 0x03061f07, 0x190b0a13, // cs.sk.gl_553 jw.so.mt_654 cy.de.nl_432 pt.es.gl_665
+ 0x29002d18, 0x3b042813, 0x1300110e, 0x066407a4, // sk.sl.un_740 sw.fi.so_665 ro.et.un_550 it.lg.de_433
+ // [4030]
+ 0x64072aec, 0x23060407, 0x286b1a60, 0x05004a0d, // mt.it.lg_644 fi.de.ca_432 tl.ceb.sw_664 yo.fr.un_540
+ 0x19252aa4, 0x03122755, 0x2135640c, 0x1f3525a9, // mt.eu.gl_433 gd.hu.nl_442 lg.zu.jw_543 eu.zu.cy_544
+ 0x2012070c, 0x29161904, 0x230a0707, 0x01001907, // it.hu.sq_543 gl.hr.sl_332 it.pt.ca_432 gl.en.un_420
+ 0x03001c04, 0x23012aa4, 0x11001f0c, 0x13040555, // id.nl.un_320 mt.en.ca_433 cy.ro.un_530 fr.fi.et_442
+ // [4040]
+ 0x25001a07, 0x3f08040c, 0x0500120d, 0x05110708, // tl.eu.un_420 fi.no.af_543 hu.fr.un_540 it.ro.fr_443
+ 0x1f1a5212, 0x27002b13, 0x53004a34, 0x01060708, // ha.tl.cy_654 vi.gd.un_650 yo.ht.un_A80 it.de.en_443
+ 0x2b534aad, 0x21203bad, 0x1a685307, 0x1e1c3ba9, // yo.ht.vi_643 so.sq.jw_643 ht.ig.tl_432 so.id.ms_544
+ 0x23281fac, 0x2b4a5360, 0x1a356407, 0x051a6ba9, // cy.sw.ca_632 ht.yo.vi_664 lg.zu.tl_432 ceb.tl.fr_544
+ // [4050]
+ 0x2b002719, 0x53004a22, 0x1a2009a4, 0x175535a4, // gd.vi.un_750 yo.ht.un_870 pl.sq.tl_433 zu.rw.sr_433
+ 0x1013040c, 0x07110a55, 0x206b1c07, 0x12002309, // fi.et.lt_543 mk.ro.bg_442 id.ceb.sq_432 ca.hu.un_440
+ 0x2119120c, 0x53004a20, 0x07004a29, 0x121b0908, // hu.gl.jw_543 yo.ht.un_850 yo.it.un_960 pl.tr.hu_443
+ 0x64001b14, 0x3f03210c, 0x28001604, 0x21171aa4, // tr.lg.un_660 jw.nl.af_543 hr.sw.un_320 tl.sr.jw_433
+ // [4060]
+ 0x0b0a1a02, 0x03023f02, 0x2900281a, 0x210620a0, // tl.pt.es_222 af.da.nl_222 sw.sl.un_760 sq.de.jw_322
+ 0x10641aa4, 0x231b1108, 0x2d002819, 0x284a5212, // tl.lg.lt_433 ro.tr.ca_443 sw.sk.un_750 ha.yo.sw_654
+ 0x1c1e280c, 0x29352811, 0x17005204, 0x2953280c, // sw.ms.id_543 sw.zu.sl_653 ha.sr.un_320 sw.ht.sl_543
+ 0x190b64a0, 0x0b0a230e, 0x04005314, 0x0a230b0b, // lg.es.gl_322 ca.pt.es_555 ht.fi.un_660 es.ca.pt_542
+ // [4070]
+ 0x4a52280c, 0x35645304, 0x0e551b08, 0x091c13a0, // sw.ha.yo_543 ht.lg.zu_332 tr.rw.is_443 bh.mr.hi_322
+ 0x285568a0, 0x05520aee, 0x08002121, 0x1e2d6411, // ig.rw.sw_322 pt.ha.fr_422 jw.no.un_860 lg.sk.ms_653
+ 0x1b3b6412, 0x2800100d, 0x10086b55, 0x07022da0, // lg.so.tr_654 lt.sw.un_540 ceb.no.lt_442 sk.da.it_322
+ 0x28005213, 0x0a190b60, 0x0c001f0c, 0x03006418, // ha.sw.un_650 es.gl.pt_664 cy.sv.un_530 lg.nl.un_740
+ // [4080]
+ 0x0a173507, 0x1e1c5205, 0x1e006e0c, 0x101e1c0e, // zu.sr.pt_432 ha.id.ms_333 hmn.ms.un_530 id.ms.lt_555
+ 0x29000b02, 0x286e6812, 0x2a070aa4, 0x6400521b, // es.sl.un_220 ig.hmn.sw_654 pt.it.mt_433 ha.lg.un_770
+ 0x2d001307, 0x2100190c, 0x1c251e60, 0x041e1c14, // et.sk.un_420 gl.jw.un_530 ms.eu.id_664 id.ms.fi_666
+ 0x53002016, 0x136b0412, 0x203f040d, 0x2a3b29a9, // sq.ht.un_720 fi.ceb.et_654 fi.af.sq_554 sl.so.mt_544
+ // [4090]
+ 0x3f003b0c, 0x4a030c0b, 0x05002104, 0x090c13a4, // so.af.un_530 sv.nl.yo_542 jw.fr.un_320 et.sv.pl_433
+ 0x0f00092b, 0x283b2514, 0x1e1b0e08, 0x0f00520c, // pl.lv.un_980 eu.so.sw_666 is.tr.ms_443 ha.lv.un_530
+ 0x12200812, 0x1a6452af, 0x200812a9, 0x05201eaf, // no.sq.hu_654 ha.lg.tl_655 hu.no.sq_544 ms.sq.fr_655
+ 0x6455520b, 0x6b1b35a9, 0x201b05a4, 0x643555ad, // ha.rw.lg_542 zu.tr.ceb_544 fr.tr.sq_433 rw.zu.lg_643
+ // [40a0]
+ 0x115228a9, 0x1311040e, 0x522102a9, 0x19005204, // sw.ha.ro_544 fi.ro.et_555 da.jw.ha_544 ha.gl.un_320
+ 0x55213509, 0x64121aad, 0x355228a9, 0x190807a4, // zu.jw.rw_444 tl.hu.lg_643 sw.ha.zu_544 it.no.gl_433
+ 0x05110d05, 0x55042a0c, 0x2d0d02ee, 0x230b0a14, // cs.ro.fr_333 mt.fi.rw_543 da.cs.sk_422 pt.es.ca_666
+ 0x235564a9, 0x2003120c, 0x2b121b0c, 0x354a52ad, // lg.rw.ca_544 hu.nl.sq_543 tr.hu.vi_543 ha.yo.zu_643
+ // [40b0]
+ 0x12000e2a, 0x09041805, 0x21001119, 0x1a356413, // is.hu.un_970 ga.fi.pl_333 ro.jw.un_750 lg.zu.tl_665
+ 0x3f21090c, 0x1821270c, 0x030502ee, 0x200802a9, // pl.jw.af_543 gd.jw.ga_543 da.fr.nl_422 da.no.sq_544
+ 0x1017070c, 0x31001c02, 0x642a0712, 0x1c21520c, // bg.sr.be_543 id.az.un_220 it.mt.lg_654 ha.jw.id_543
+ 0x25072a0c, 0x01271802, 0x07321709, 0x2a2507af, // mt.it.eu_543 ga.gd.en_222 sr.bs.it_444 it.eu.mt_655
+ // [40c0]
+ 0x356b28a4, 0x28551209, 0x6b1f1a09, 0x0f1352a0, // sw.ceb.zu_433 hu.rw.sw_444 tl.cy.ceb_444 ha.et.lv_322
+ 0x211629a9, 0x06003b0d, 0x21001e22, 0x10001221, // sl.hr.jw_544 so.de.un_540 ms.jw.un_870 hu.lt.un_860
+ 0x0a1c1eec, 0x3f002722, 0x211c04a4, 0x1e3b10a4, // ms.id.pt_644 gd.af.un_870 fi.id.jw_433 lt.so.ms_433
+ 0x044a1ca4, 0x1b190b13, 0x101b3508, 0x0e004a21, // id.yo.fi_433 es.gl.tr_665 zu.tr.lt_443 yo.is.un_860
+ // [40d0]
+ 0x351b1ca4, 0x02000312, 0x4a000e29, 0x171e1c5a, // id.tr.zu_433 nl.da.un_640 is.yo.un_960 id.ms.sr_553
+ 0x12001702, 0x32161ca9, 0x171b290c, 0x0e2b4a11, // sr.hu.un_220 id.hr.bs_544 sl.tr.sr_543 yo.vi.is_653
+ 0x11030a07, 0x0a001105, 0x101303a9, 0x09033f0d, // pt.nl.ro_432 ro.mk.un_330 nl.et.lt_544 af.nl.pl_554
+ 0x28521aa9, 0x2d090dee, 0x3f080ea4, 0x0e03080c, // tl.ha.sw_544 cs.pl.sk_422 is.no.af_433 no.nl.is_543
+ // [40e0]
+ 0x1b52120c, 0x3b0f25a9, 0x35101ba4, 0x0f0b04ee, // hu.ha.tr_543 eu.lv.so_544 tr.lt.zu_433 fi.es.lv_422
+ 0x28351b07, 0x4a0e2dec, 0x31002904, 0x0b4a18ac, // tr.zu.sw_432 sk.is.yo_644 sl.az.un_320 ga.yo.es_632
+ 0x0e08135a, 0x3b13290c, 0x1a0f2a0d, 0x13351aad, // et.no.is_553 sl.et.so_543 mt.lv.tl_554 tl.zu.et_643
+ 0x13356b02, 0x1b281ea4, 0x1b002713, 0x55001f0c, // ceb.zu.et_222 ms.sw.tr_433 gd.tr.un_650 cy.rw.un_530
+ // [40f0]
+ 0x1807010c, 0x25033fa9, 0x090d1307, 0x350e2511, // en.it.ga_543 af.nl.eu_544 bh.ne.hi_432 eu.is.zu_653
+ 0x31003f0d, 0x042511a4, 0x686404a4, 0x19052305, // af.az.un_540 ro.eu.fi_433 fi.lg.ig_433 ca.fr.gl_333
+ 0x080a17a6, 0x27033f08, 0x01192102, 0x0b3568a9, // sr.mk.uk_521 af.nl.gd_443 jw.gl.en_222 ig.zu.es_544
+ 0x04006419, 0x04642108, 0x68216414, 0x080e25a4, // lg.fi.un_750 jw.lg.fi_443 lg.jw.ig_666 eu.is.no_433
+ // [4100]
+ 0x55000804, 0x10250707, 0x29000f1b, 0x053f015a, // no.rw.un_320 it.eu.lt_432 lv.sl.un_770 en.af.fr_553
+ 0x0f6b11af, 0x23000511, 0x3135200b, 0x556435ee, // ro.ceb.lv_655 fr.ca.un_630 sq.zu.az_542 zu.lg.rw_422
+ 0x0a110808, 0x190a21a0, 0x040d3b0c, 0x111a04ee, // uk.ro.mk_443 jw.pt.gl_322 so.cs.fi_543 fi.tl.ro_422
+ 0x3f033b0c, 0x2d006402, 0x0a015502, 0x0e122307, // so.nl.af_543 lg.sk.un_220 rw.en.pt_222 ca.hu.is_432
+ // [4110]
+ 0x03203505, 0x032a0412, 0x19050ba4, 0x6b0468a4, // zu.sq.nl_333 fi.mt.nl_654 es.fr.gl_433 ig.fi.ceb_433
+ 0x206b1aa4, 0x281e3207, 0x080220a9, 0x100f13ad, // tl.ceb.sq_433 bs.ms.sw_432 sq.da.no_544 et.lv.lt_643
+ 0x0c020e0c, 0x042d0da9, 0x1e1c0ca4, 0x02090808, // is.da.sv_543 cs.sk.fi_544 sv.id.ms_433 no.pl.da_443
+ 0x0c3f180c, 0x0e1f0c12, 0x3b181ba0, 0x0c6b2305, // ga.af.sv_543 sv.cy.is_654 tr.ga.so_322 ca.ceb.sv_333
+ // [4120]
+ 0x041a13a4, 0x043f1308, 0x13001a21, 0x120413af, // et.tl.fi_433 et.af.fi_443 tl.et.un_860 et.fi.hu_655
+ 0x04134aa0, 0x1a001e02, 0x1c212708, 0x3b0108a7, // yo.et.fi_322 ms.tl.un_220 gd.jw.id_443 no.en.so_532
+ 0x031f1b0c, 0x0e0803ec, 0x112a2313, 0x1b531f0c, // tr.cy.nl_543 nl.no.is_644 ca.mt.ro_665 cy.ht.tr_543
+ 0x642035a4, 0x0800030c, 0x252820af, 0x112706ee, // zu.sq.lg_433 nl.no.un_530 sq.sw.eu_655 de.gd.ro_422
+ // [4130]
+ 0x1c0913a6, 0x3f2a1fa4, 0x3f3b6404, 0x2555355a, // bh.hi.mr_521 cy.mt.af_433 lg.so.af_332 zu.rw.eu_553
+ 0x552868a4, 0x68281b09, 0x20356402, 0x0f64280d, // ig.sw.rw_433 tr.sw.ig_444 lg.zu.sq_222 sw.lg.lv_554
+ 0x1e646808, 0x1e07280c, 0x04131114, 0x1f170608, // ig.lg.ms_443 sw.it.ms_543 ro.et.fi_666 de.sr.cy_443
+ 0x072531a9, 0x0b1905a9, 0x35255508, 0x11313205, // az.eu.it_544 fr.gl.es_544 rw.eu.zu_443 bs.az.ro_333
+ // [4140]
+ 0x060a31a4, 0x286455a4, 0x280a5307, 0x20171ba4, // az.pt.de_433 rw.lg.sw_433 ht.pt.sw_432 tr.sr.sq_433
+ 0x10000e05, 0x090306a0, 0x1b002807, 0x11291e09, // is.lt.un_330 de.nl.pl_322 sw.tr.un_420 ms.sl.ro_444
+ 0x53003523, 0x53202aa9, 0x194a010c, 0x20552508, // zu.ht.un_880 mt.sq.ht_544 en.yo.gl_543 eu.rw.sq_443
+ 0x35310f0c, 0x10070f0c, 0x52061c0c, 0x250f0611, // lv.az.zu_543 lv.it.lt_543 id.de.ha_543 de.lv.eu_653
+ // [4150]
+ 0x64033fa4, 0x11073155, 0x0d0906a0, 0x0d120607, // af.nl.lg_433 az.it.ro_442 de.pl.cs_322 de.hu.cs_432
+ 0x1b005308, 0x1b02080c, 0x0a250705, 0x092d06a7, // ht.tr.un_430 no.da.tr_543 it.eu.pt_333 de.sk.pl_532
+ 0x1711080c, 0x06003209, 0x20062aac, 0x27000612, // uk.ro.sr_543 bs.de.un_440 mt.de.sq_632 de.gd.un_640
+ 0x2b001a12, 0x016b0207, 0x2b6b31a0, 0x010e64a4, // tl.vi.un_640 da.ceb.en_432 az.ceb.vi_322 lg.is.en_433
+ // [4160]
+ 0x31001704, 0x0d000612, 0x0e030613, 0x3b526404, // sr.az.un_320 de.cs.un_640 de.nl.is_665 lg.ha.so_332
+ 0x122d10a4, 0x06000e0e, 0x643f1ba0, 0x1b3b3113, // lt.sk.hu_433 is.de.un_550 tr.af.lg_322 az.so.tr_665
+ 0x11000807, 0x682135a9, 0x17000820, 0x01073104, // no.ro.un_420 zu.jw.ig_544 uk.sr.un_850 az.it.en_332
+ 0x27186408, 0x0100531b, 0x2a060460, 0x0f102daf, // lg.ga.gd_443 ht.en.un_770 fi.de.mt_664 sk.lt.lv_655
+ // [4170]
+ 0x35531105, 0x0900030c, 0x092a2504, 0x68006e21, // ro.ht.zu_333 nl.pl.un_530 eu.mt.pl_332 hmn.ig.un_860
+ 0x06003f1b, 0x162901a0, 0x5313045a, 0x070f1009, // af.de.un_770 en.sl.hr_322 fi.et.ht_553 lt.lv.it_444
+ 0x06030e0c, 0x013528a0, 0x060f0c13, 0x2a2307a4, // is.nl.de_543 sw.zu.en_322 sv.lv.de_665 it.ca.mt_433
+ 0x20000719, 0x3b251a05, 0x033f0fa4, 0x0b072d0c, // it.sq.un_750 tl.eu.so_333 lv.af.nl_433 sk.it.es_543
+ // [4180]
+ 0x102a0f0c, 0x2b100f0d, 0x04002d1a, 0x11006b04, // lv.mt.lt_543 lv.lt.vi_554 sk.fi.un_760 ceb.ro.un_320
+ 0x17000a07, 0x641e350c, 0x0500210d, 0x0e001313, // mk.sr.un_420 zu.ms.lg_543 jw.fr.un_540 et.is.un_650
+ 0x23555208, 0x07252aa9, 0x1c281ea4, 0x28002d04, // ha.rw.ca_443 mt.eu.it_544 ms.sw.id_433 sk.sw.un_320
+ 0x1e00070c, 0x21534a08, 0x1b1c210d, 0x21101a5a, // it.ms.un_530 yo.ht.jw_443 jw.id.tr_554 tl.lt.jw_553
+ // [4190]
+ 0x311035a0, 0x0f000312, 0x21351ca7, 0x1a004a12, // zu.lt.az_322 nl.lv.un_640 id.zu.jw_532 yo.tl.un_640
+ 0x191007a0, 0x3b25550c, 0x11522812, 0x0a1011a0, // it.lt.gl_322 rw.eu.so_543 sw.ha.ro_654 ro.be.mk_322
+ 0x5500110e, 0x21071e0c, 0x07001719, 0x21006805, // ro.rw.un_550 ms.it.jw_543 sr.bg.un_750 ig.jw.un_330
+ 0x3b520ea9, 0x0d292d0c, 0x23111c07, 0x13091c0c, // is.ha.so_544 sk.sl.cs_543 id.ro.ca_432 mr.hi.bh_543
+ // [41a0]
+ 0x18001019, 0x0700291a, 0x55522da7, 0x25521114, // lt.ga.un_750 sl.it.un_760 sk.ha.rw_532 ro.ha.eu_666
+ 0x01063fa0, 0x1c0927a0, 0x23201108, 0x25231109, // af.de.en_322 gd.pl.id_322 ro.sq.ca_443 ro.ca.eu_444
+ 0x1a001e04, 0x21251a11, 0x2b1f6bac, 0x312a3bee, // ms.tl.un_320 tl.eu.jw_653 ceb.cy.vi_632 so.mt.az_422
+ 0x1c1b1e07, 0x04100805, 0x311b1ea9, 0x1200100d, // ms.tr.id_432 uk.be.ru_333 ms.tr.az_544 lt.hu.un_540
+ // [41b0]
+ 0x200727ad, 0x25105504, 0x0e183f07, 0x07112daf, // gd.it.sq_643 rw.lt.eu_332 af.ga.is_432 sk.ro.it_655
+ 0x11002312, 0x1a536b13, 0x036e53ee, 0x190b230b, // ca.ro.un_640 ceb.ht.tl_665 ht.hmn.nl_422 ca.es.gl_542
+ 0x13112312, 0x0e6b3fee, 0x090d20ad, 0x2a002514, // ca.ro.et_654 af.ceb.is_422 sq.cs.pl_643 eu.mt.un_660
+ 0x28006b18, 0x0e04105a, 0x10000e07, 0x0b192355, // ceb.sw.un_740 lt.fi.is_553 is.lt.un_420 ca.gl.es_442
+ // [41c0]
+ 0x3b0506ad, 0x040652a9, 0x060e3fa9, 0x3f002b1b, // de.fr.so_643 ha.de.fi_544 af.is.de_544 vi.af.un_770
+ 0x64125204, 0x10040faf, 0x1c0d0902, 0x216e1ea7, // ha.hu.lg_332 lv.fi.lt_655 hi.ne.mr_222 ms.hmn.jw_532
+ 0x2b000a19, 0x11202904, 0x4a000c12, 0x08006404, // pt.vi.un_750 sl.sq.ro_332 sv.yo.un_640 lg.no.un_320
+ 0x230b180c, 0x110a10a4, 0x11000a1a, 0x64325202, // ga.es.ca_543 be.mk.ro_433 mk.ro.un_760 ha.bs.lg_222
+ // [41d0]
+ 0x132a2805, 0x0500180d, 0x2b000a12, 0x070823a4, // sw.mt.et_333 ga.fr.un_540 pt.vi.un_640 ca.no.it_433
+ 0x0f082aa4, 0x083f2aa7, 0x235201a4, 0x0a056413, // mt.no.lv_433 mt.af.no_532 en.ha.ca_433 lg.fr.pt_665
+ 0x2a0f08a4, 0x041f6b55, 0x100a11a0, 0x076b13ee, // no.lv.mt_433 ceb.cy.fi_442 ro.mk.be_322 et.ceb.it_422
+ 0x01191ea4, 0x1c231eee, 0x25001807, 0x06092a14, // ms.gl.en_433 ms.ca.id_422 ga.eu.un_420 mt.pl.de_666
+ // [41e0]
+ 0x1e041ca4, 0x20030f07, 0x2a3216a4, 0x19004a07, // id.fi.ms_433 lv.nl.sq_432 hr.bs.mt_433 yo.gl.un_420
+ 0x030c2bee, 0x0f080214, 0x0b003504, 0x050119a7, // vi.sv.nl_422 da.no.lv_666 zu.es.un_320 gl.en.fr_532
+ 0x2b0727a0, 0x0c1c03a4, 0x020c0fa7, 0x1e1c2107, // gd.it.vi_322 nl.id.sv_433 lv.sv.da_532 jw.id.ms_432
+ 0x3b04130c, 0x0c0704af, 0x2d1f2509, 0x25002702, // et.fi.so_543 fi.it.sv_655 eu.cy.sk_444 gd.eu.un_220
+ // [41f0]
+ 0x0e00110d, 0x1c1f2007, 0x10170a13, 0x16000f0c, // ro.is.un_540 sq.cy.id_432 mk.sr.be_665 lv.hr.un_530
+ 0x16003211, 0x09101b08, 0x1b001e09, 0x1c3b64a6, // bs.hr.un_630 tr.lt.pl_443 ms.tr.un_440 lg.so.id_521
+ 0x28211aa0, 0x062a07ac, 0x1b3b1311, 0x0e040ca4, // tl.jw.sw_322 it.mt.de_632 et.so.tr_653 sv.fi.is_433
+ 0x113b010c, 0x06253ba0, 0x1e6e1cad, 0x190b08a4, // en.so.ro_543 so.eu.de_322 id.hmn.ms_643 no.es.gl_433
+ // [4200]
+ 0x6e6b4a04, 0x6e1e2108, 0x0f252da4, 0x023f08a9, // yo.ceb.hmn_332 jw.ms.hmn_443 sk.eu.lv_433 no.af.da_544
+ 0x2123050c, 0x1b1c1ead, 0x3f003507, 0x2964550c, // fr.ca.jw_543 ms.id.tr_643 zu.af.un_420 rw.lg.sl_543
+ 0x4a00270c, 0x27005322, 0x29003f1a, 0x174a290c, // gd.yo.un_530 ht.gd.un_870 af.sl.un_760 sl.yo.sr_543
+ 0x02060c0d, 0x12202508, 0x180e050b, 0x03060f12, // sv.de.da_554 eu.sq.hu_443 fr.is.ga_542 lv.de.nl_654
+ // [4210]
+ 0x285520ec, 0x0c060eaf, 0x530221a4, 0x110a0da4, // sq.rw.sw_644 is.de.sv_655 jw.da.ht_433 cs.pt.ro_433
+ 0x3f082107, 0x351664ec, 0x351820af, 0x1c3f1aa7, // jw.no.af_432 lg.hr.zu_644 sq.ga.zu_655 tl.af.id_532
+ 0x642021a4, 0x23006405, 0x4a0f07a4, 0x28253108, // jw.sq.lg_433 lg.ca.un_330 it.lv.yo_433 az.eu.sw_443
+ 0x281b2905, 0x28001b18, 0x04201112, 0x18282707, // sl.tr.sw_333 tr.sw.un_740 ro.sq.fi_654 gd.sw.ga_432
+ // [4220]
+ 0x3125200c, 0x030f06a7, 0x060e0309, 0x0b0a2360, // sq.eu.az_543 de.lv.nl_532 nl.is.de_444 ca.pt.es_664
+ 0x180b270c, 0x0e001c04, 0x08131ea4, 0x2d0d4a09, // gd.es.ga_543 id.is.un_320 ms.et.no_433 yo.cs.sk_444
+ 0x251801a4, 0x2711180d, 0x00000203, 0x181227ee, // en.ga.eu_433 ga.ro.gd_554 da.un.un_300 gd.hu.ga_422
+ 0x1200351b, 0x066b0c08, 0x281227a0, 0x11072a0c, // zu.hu.un_770 sv.ceb.de_443 gd.hu.sw_322 mt.it.ro_543
+ // [4230]
+ 0x1e1c1aa9, 0x6b211ea9, 0x063f5507, 0x0b001114, // tl.id.ms_544 ms.jw.ceb_544 rw.af.de_432 ro.es.un_660
+ 0x29130908, 0x08021f0c, 0x07101fa7, 0x1021010c, // pl.et.sl_443 cy.da.no_543 cy.lt.it_532 en.jw.lt_543
+ 0x6b1a130e, 0x17352004, 0x2d120e12, 0x04002105, // et.tl.ceb_555 sq.zu.sr_332 is.hu.sk_654 jw.fi.un_330
+ 0x321609ec, 0x10001f05, 0x0d0855ec, 0x681b35a7, // pl.hr.bs_644 cy.lt.un_330 rw.no.cs_644 zu.tr.ig_532
+ // [4240]
+ 0x1c642107, 0x29121e07, 0x12070fa7, 0x250f1a02, // jw.lg.id_432 ms.hu.sl_432 lv.it.hu_532 tl.lv.eu_222
+ 0x3f04035a, 0x320c020c, 0x1e3f130d, 0x531901a9, // nl.fi.af_553 da.sv.bs_543 et.af.ms_554 en.gl.ht_544
+ 0x3f0c6ea4, 0x0a08170c, 0x02006e19, 0x6e002d1a, // hmn.sv.af_433 sr.uk.mk_543 hmn.da.un_750 sk.hmn.un_760
+ 0x0c310611, 0x32001008, 0x5500522c, 0x270208a4, // de.az.sv_653 lt.bs.un_430 ha.rw.un_990 no.da.gd_433
+ // [4250]
+ 0x27006b0d, 0x3f6b6eec, 0x1c130911, 0x6428520c, // ceb.gd.un_540 hmn.ceb.af_644 hi.bh.mr_653 ha.sw.lg_543
+ 0x0932310d, 0x0c000607, 0x64555202, 0x3f6e1fa9, // az.bs.pl_554 de.sv.un_420 ha.rw.lg_222 cy.hmn.af_544
+ 0x1f02030c, 0x1e1c1aa4, 0x04003513, 0x35186b0c, // nl.da.cy_543 tl.id.ms_433 zu.fi.un_650 ceb.ga.zu_543
+ 0x036e270e, 0x04081eee, 0x096e2d11, 0x04130fad, // gd.hmn.nl_555 ms.no.fi_422 sk.hmn.pl_653 lv.et.fi_643
+ // [4260]
+ 0x6b3513a4, 0x6b100aa4, 0x021a6bad, 0x126410a0, // et.zu.ceb_433 pt.lt.ceb_433 ceb.tl.da_643 lt.lg.hu_322
+ 0x53001904, 0x19051108, 0x520821a0, 0x060819a0, // gl.ht.un_320 ro.fr.gl_443 jw.no.ha_322 gl.no.de_322
+ 0x04110aad, 0x04002a07, 0x64001e02, 0x1e1c0414, // mk.ro.ru_643 mt.fi.un_420 ms.lg.un_220 fi.id.ms_666
+ 0x1c3b1ea4, 0x4a64280c, 0x10641305, 0x12211ea4, // ms.so.id_433 sw.lg.yo_543 et.lg.lt_333 ms.jw.hu_433
+ // [4270]
+ 0x0a35040d, 0x231228a0, 0x531f35a4, 0x080f1012, // fi.zu.pt_554 sw.hu.ca_322 zu.cy.ht_433 lt.lv.no_654
+ 0x08021a14, 0x1c201e11, 0x551a6bee, 0x0802270d, // tl.da.no_666 ms.sq.id_653 ceb.tl.rw_422 gd.da.no_554
+ 0x25001c02, 0x2a6407af, 0x1b000613, 0x13002502, // id.eu.un_220 it.lg.mt_655 de.tr.un_650 eu.et.un_220
+ 0x182821ad, 0x2d162804, 0x203f1ca4, 0x03021faf, // jw.sw.ga_643 sw.hr.sk_332 id.af.sq_433 cy.da.nl_655
+ // [4280]
+ 0x121e2a08, 0x6e033fa4, 0x201b3208, 0x2d050da6, // mt.ms.hu_443 af.nl.hmn_433 bs.tr.sq_443 cs.fr.sk_521
+ 0x0f041aee, 0x08001f0c, 0x0a0410ad, 0x25351207, // tl.fi.lv_422 cy.no.un_530 be.ru.mk_643 hu.zu.eu_432
+ 0x1012040c, 0x04231208, 0x352b1fa0, 0x0b000a02, // fi.hu.lt_543 hu.ca.fi_443 cy.vi.zu_322 pt.es.un_220
+ 0x0120270c, 0x3b131e12, 0x0f291705, 0x02002a14, // gd.sq.en_543 ms.et.so_654 sr.sl.lv_333 mt.da.un_660
+ // [4290]
+ 0x1a000104, 0x1b041c05, 0x12002012, 0x3b133f08, // en.tl.un_320 id.fi.tr_333 sq.hu.un_640 af.et.so_443
+ 0x040c3f07, 0x1a6b0bec, 0x0c19040d, 0x1f3b18ec, // af.sv.fi_432 es.ceb.tl_644 fi.gl.sv_554 ga.so.cy_644
+ 0x1a6b0f0c, 0x1f523fa4, 0x6b003b09, 0x11256b0c, // lv.ceb.tl_543 af.ha.cy_433 so.ceb.un_440 ceb.eu.ro_543
+ 0x68213b08, 0x0e355512, 0x0f101f02, 0x3b0b6b04, // so.jw.ig_443 rw.zu.is_654 cy.lt.lv_222 ceb.es.so_332
+ // [42a0]
+ 0x1c11210c, 0x0a291cee, 0x090d13a0, 0x351f0311, // jw.ro.id_543 id.sl.pt_422 bh.ne.hi_322 nl.cy.zu_653
+ 0x1f6b1a08, 0x13083f09, 0x1c1e6ea7, 0x3b1f0cec, // tl.ceb.cy_443 af.no.et_444 hmn.ms.id_532 sv.cy.so_644
+ 0x05550208, 0x13002d04, 0x19236eee, 0x160d175a, // da.rw.fr_443 sk.et.un_320 hmn.ca.gl_422 sr.cs.hr_553
+ 0x09002a13, 0x6e001e08, 0x10006e08, 0x19005308, // mt.pl.un_650 ms.hmn.un_430 hmn.lt.un_430 ht.gl.un_430
+ // [42b0]
+ 0x10190b0c, 0x6406550c, 0x1b3125a9, 0x183b27a4, // es.gl.lt_543 rw.de.lg_543 eu.az.tr_544 gd.so.ga_433
+ 0x08061805, 0x0f136ead, 0x061f0ea4, 0x52556bad, // ga.de.no_333 hmn.et.lv_643 is.cy.de_433 ceb.rw.ha_643
+ 0x522a285a, 0x124a530e, 0x080c1a09, 0x05001f13, // sw.mt.ha_553 ht.yo.hu_555 tl.sv.no_444 cy.fr.un_650
+ 0x0c21200c, 0x55112aec, 0x526b1805, 0x083f030c, // sq.jw.sv_543 mt.ro.rw_644 ga.ceb.ha_333 nl.af.no_543
+ // [42c0]
+ 0x080e0660, 0x52006b0e, 0x64063507, 0x1200201a, // de.is.no_664 ceb.ha.un_550 zu.de.lg_432 sq.hu.un_760
+ 0x17001122, 0x27000a02, 0x6b004a0d, 0x28065202, // ro.sr.un_870 pt.gd.un_220 yo.ceb.un_540 ha.de.sw_222
+ 0x20211205, 0x646b3509, 0x551718a4, 0x12080c60, // hu.jw.sq_333 zu.ceb.lg_444 ga.sr.rw_433 sv.no.hu_664
+ 0x0c181a08, 0x2106550c, 0x080c200c, 0x13033f0c, // tl.ga.sv_443 rw.de.jw_543 sq.sv.no_543 af.nl.et_543
+ // [42d0]
+ 0x02133f0c, 0x1f001014, 0x1f2d1807, 0x0b1008a0, // af.et.da_543 lt.cy.un_660 ga.sk.cy_432 no.lt.es_322
+ 0x1e1c3bec, 0x0b1b19a0, 0x06005519, 0x06002821, // so.id.ms_644 gl.tr.es_322 rw.de.un_750 sw.de.un_860
+ 0x1e6b1a0d, 0x201f03a0, 0x530e1f05, 0x05003113, // tl.ceb.ms_554 nl.cy.sq_322 cy.is.ht_333 az.fr.un_650
+ 0x2d013b07, 0x100f09ee, 0x2a041a0b, 0x0f005304, // so.en.sk_432 pl.lv.lt_422 tl.fi.mt_542 ht.lv.un_320
+ // [42e0]
+ 0x13534aa4, 0x311b5208, 0x231a210c, 0x0f00170d, // yo.ht.et_433 ha.tr.az_443 jw.tl.ca_543 sr.lv.un_540
+ 0x2d0d29a9, 0x102d01a4, 0x17111fec, 0x644a2808, // sl.cs.sk_544 en.sk.lt_433 cy.ro.sr_644 sw.yo.lg_443
+ 0x1f001602, 0x2500642b, 0x3b6b100d, 0x2a0c3bad, // hr.cy.un_220 lg.eu.un_980 lt.ceb.so_554 so.sv.mt_643
+ 0x0c001318, 0x0c2012a7, 0x07190b13, 0x29004a1a, // et.sv.un_740 hu.sq.sv_532 es.gl.it_665 yo.sl.un_760
+ // [42f0]
+ 0x2a001b19, 0x04641eee, 0x10001604, 0x1e1c1a0c, // tr.mt.un_750 ms.lg.fi_422 hr.lt.un_320 tl.id.ms_543
+ 0x28000a0d, 0x35004a19, 0x2500200e, 0x1729100c, // pt.sw.un_540 yo.zu.un_750 sq.eu.un_550 lt.sl.sr_543
+ 0x19002d04, 0x110305a0, 0x291055ad, 0x2b206eee, // sk.gl.un_320 fr.nl.ro_322 rw.lt.sl_643 hmn.sq.vi_422
+ 0x0f000707, 0x0c040dee, 0x210906a7, 0x0900311a, // it.lv.un_420 cs.fi.sv_422 de.pl.jw_532 az.pl.un_760
+ // [4300]
+ 0x1827280c, 0x250407ad, 0x1b002813, 0x0125200c, // sw.gd.ga_543 it.fi.eu_643 sw.tr.un_650 sq.eu.en_543
+ 0x1b002a11, 0x3f0e0214, 0x3f355505, 0x19520bee, // mt.tr.un_630 da.is.af_666 rw.zu.af_333 es.ha.gl_422
+ 0x53231807, 0x2b2718a4, 0x12645308, 0x0c000414, // ga.ca.ht_432 ga.gd.vi_433 ht.lg.hu_443 fi.sv.un_660
+ 0x32291711, 0x080219a0, 0x233b13ec, 0x3500031a, // sr.sl.bs_653 gl.da.no_322 et.so.ca_644 nl.zu.un_760
+ // [4310]
+ 0x64353104, 0x4a00521b, 0x09120da0, 0x52006420, // az.zu.lg_332 ha.yo.un_770 cs.hu.pl_322 lg.ha.un_850
+ 0x0000642d, 0x256b4aee, 0x35063f13, 0x12211c08, // lg.un.un_A00 yo.ceb.eu_422 af.de.zu_665 id.jw.hu_443
+ 0x1e061c04, 0x321629a4, 0x644a3504, 0x3f2d29a4, // id.de.ms_332 sl.hr.bs_433 zu.yo.lg_332 sl.sk.af_433
+ 0x10001114, 0x0d292d10, 0x290d2d0c, 0x35204aa0, // ro.be.un_660 sk.sl.cs_642 sk.cs.sl_543 yo.sq.zu_322
+ // [4320]
+ 0x35006420, 0x01310204, 0x3f000702, 0x06130c11, // lg.zu.un_850 da.az.en_332 it.af.un_220 sv.et.de_653
+ 0x250c1304, 0x272d0d05, 0x104a6808, 0x121c1ea4, // et.sv.eu_332 cs.sk.gd_333 ig.yo.lt_443 ms.id.hu_433
+ 0x12001a2a, 0x080205ac, 0x28005502, 0x11001721, // tl.hu.un_970 fr.da.no_632 rw.sw.un_220 sr.ro.un_860
+ 0x0e1a1205, 0x1c1a1e12, 0x536b1a08, 0x2a011308, // hu.tl.is_333 ms.tl.id_654 tl.ceb.ht_443 et.en.mt_443
+ // [4330]
+ 0x291320ad, 0x1a005509, 0x20531212, 0x1c3b6ba4, // sq.et.sl_643 rw.tl.un_440 hu.ht.sq_654 ceb.so.id_433
+ 0x0c190b0d, 0x212b3509, 0x2a07270c, 0x1e000619, // es.gl.sv_554 zu.vi.jw_444 gd.it.mt_543 de.ms.un_750
+ 0x52000707, 0x190b2360, 0x190b230e, 0x0e645204, // it.ha.un_420 ca.es.gl_664 ca.es.gl_555 ha.lg.is_332
+ 0x0b0a1811, 0x08070a14, 0x07100a14, 0x080201a9, // ga.pt.es_653 mk.bg.uk_666 mk.be.bg_666 en.da.no_544
+ // [4340]
+ 0x29001904, 0x096b1a12, 0x05070e04, 0x55293505, // gl.sl.un_320 tl.ceb.pl_654 is.it.fr_332 zu.sl.rw_333
+ 0x033f1fac, 0x0a006b22, 0x1f1811af, 0x080253a4, // cy.af.nl_632 ceb.pt.un_870 ro.ga.cy_655 ht.da.no_433
+ 0x3b3f0309, 0x1f0f1112, 0x02060807, 0x0e00232b, // nl.af.so_444 ro.lv.cy_654 no.de.da_432 ca.is.un_980
+ 0x1f000c22, 0x0a004a07, 0x04001904, 0x0f001604, // sv.cy.un_870 yo.pt.un_420 gl.fi.un_320 hr.lv.un_320
+ // [4350]
+ 0x3f311aad, 0x2528640c, 0x6853010b, 0x090627ec, // tl.az.af_643 lg.sw.eu_543 en.ht.ig_542 gd.de.pl_644
+ 0x11051b5a, 0x3100531b, 0x4a005304, 0x190b5205, // tr.fr.ro_553 ht.az.un_770 ht.yo.un_320 ha.es.gl_333
+ 0x1807110d, 0x23000804, 0x20072313, 0x1a252307, // ro.it.ga_554 no.ca.un_320 ca.it.sq_665 ca.eu.tl_432
+ 0x18270112, 0x190a29a9, 0x190b0a60, 0x28186ba7, // en.gd.ga_654 sl.pt.gl_544 pt.es.gl_664 ceb.ga.sw_532
+ // [4360]
+ 0x060c27a4, 0x53041012, 0x08020fa9, 0x1a6b2511, // gd.sv.de_433 lt.fi.ht_654 lv.da.no_544 eu.ceb.tl_653
+ 0x35641a0c, 0x100411ee, 0x08252855, 0x18270608, // tl.lg.zu_543 ro.ru.be_422 sw.eu.no_442 de.gd.ga_443
+ 0x1c4a210b, 0x1f0601ec, 0x1e1c1a0e, 0x1c121ea9, // jw.yo.id_542 en.de.cy_644 tl.id.ms_555 ms.hu.id_544
+ 0x0a110405, 0x201304a9, 0x033f0d12, 0x3b030f0d, // ru.ro.mk_333 fi.et.sq_544 cs.af.nl_654 lv.nl.so_554
+ // [4370]
+ 0x231a1109, 0x554a530b, 0x21091f07, 0x2a1f0111, // ro.tl.ca_444 ht.yo.rw_542 cy.pl.jw_432 en.cy.mt_653
+ 0x20001305, 0x2b6455a4, 0x091f2aa0, 0x23000c08, // et.sq.un_330 rw.lg.vi_433 mt.cy.pl_322 sv.ca.un_430
+ 0x55001a0d, 0x1b113113, 0x0e1220ee, 0x0a000f08, // tl.rw.un_540 az.ro.tr_665 sq.hu.is_422 lv.pt.un_430
+ 0x0c0d0412, 0x060b25ad, 0x112a23ec, 0x051123a0, // fi.cs.sv_654 eu.es.de_643 ca.mt.ro_644 ca.ro.fr_322
+ // [4380]
+ 0x641a01ad, 0x1812200c, 0x19002513, 0x1f00092a, // en.tl.lg_643 sq.hu.ga_543 eu.gl.un_650 pl.cy.un_970
+ 0x08002512, 0x1e2a310c, 0x2a091f0c, 0x08002534, // eu.no.un_640 az.mt.ms_543 cy.pl.mt_543 eu.no.un_A80
+ 0x1b1f0b0c, 0x6e003f0e, 0x07190a09, 0x053f0312, // es.cy.tr_543 af.hmn.un_550 pt.gl.it_444 nl.af.fr_654
+ 0x19110a0e, 0x5264060c, 0x2a002314, 0x6e0a3108, // pt.ro.gl_555 de.lg.ha_543 ca.mt.un_660 az.pt.hmn_443
+ // [4390]
+ 0x136b3b12, 0x06000523, 0x040c13a4, 0x20050b0c, // so.ceb.et_654 fr.de.un_880 et.sv.fi_433 es.fr.sq_543
+ 0x03001021, 0x0a002508, 0x12024aa6, 0x20000308, // lt.nl.un_860 eu.pt.un_430 yo.da.hu_521 nl.sq.un_430
+ 0x1713520c, 0x28643b08, 0x1b6e0fa7, 0x64080205, // ha.et.sr_543 so.lg.sw_443 lv.hmn.tr_532 da.no.lg_333
+ 0x1a106ba9, 0x6b1a1ea4, 0x55685209, 0x6e000f1a, // ceb.lt.tl_544 ms.tl.ceb_433 ha.ig.rw_444 lv.hmn.un_760
+ // [43a0]
+ 0x1f000b04, 0x0d002502, 0x3f072aa4, 0x101711a4, // es.cy.un_320 eu.cs.un_220 mt.it.af_433 ro.sr.be_433
+ 0x0c001804, 0x27004a07, 0x03000623, 0x5216280c, // ga.sv.un_320 yo.gd.un_420 de.nl.un_880 sw.hr.ha_543
+ 0x321f07ad, 0x3564530c, 0x0b0a11ec, 0x10002a02, // it.cy.bs_643 ht.lg.zu_543 ro.pt.es_644 mt.lt.un_220
+ 0x3b313511, 0x64553ba7, 0x04030f04, 0x3b28550c, // zu.az.so_653 so.rw.lg_532 lv.nl.fi_332 rw.sw.so_543
+ // [43b0]
+ 0x1e000a07, 0x11070111, 0x25643513, 0x64282514, // pt.ms.un_420 en.it.ro_653 zu.lg.eu_665 eu.sw.lg_666
+ 0x6b0d3ba9, 0x25003507, 0x01000302, 0x6452280d, // so.cs.ceb_544 zu.eu.un_420 nl.en.un_220 sw.ha.lg_554
+ 0x2a1806ee, 0x3b1c04ad, 0x1b001607, 0x35251212, // de.ga.mt_422 fi.id.so_643 hr.tr.un_420 hu.eu.zu_654
+ 0x3100060c, 0x051f0713, 0x3f00100e, 0x0413060c, // de.az.un_530 it.cy.fr_665 lt.af.un_550 de.et.fi_543
+ // [43c0]
+ 0x12090307, 0x08182712, 0x3b551a04, 0x11271808, // nl.pl.hu_432 gd.ga.no_654 tl.rw.so_332 ga.gd.ro_443
+ 0x0a081013, 0x132811a4, 0x0a35640d, 0x6b31550c, // be.uk.mk_665 ro.sw.et_433 lg.zu.pt_554 rw.az.ceb_543
+ 0x352a3b0c, 0x173216ad, 0x23000702, 0x13002707, // so.mt.zu_543 hr.bs.sr_643 it.ca.un_220 gd.et.un_420
+ 0x1b0306ad, 0x1c001a04, 0x0c001809, 0x251a3114, // de.nl.tr_643 tl.id.un_320 ga.sv.un_440 az.tl.eu_666
+ // [43d0]
+ 0x04292aee, 0x041a6bee, 0x232a20a4, 0x19230aa7, // mt.sl.fi_422 ceb.tl.fi_422 sq.mt.ca_433 pt.ca.gl_532
+ 0x110118a7, 0x05130808, 0x3f1e1c08, 0x18080cad, // ga.en.ro_532 no.et.fr_443 id.ms.af_443 sv.no.ga_643
+ 0x35002008, 0x050a0ba0, 0x35002121, 0x25001b0d, // sq.zu.un_430 es.pt.fr_322 jw.zu.un_860 tr.eu.un_540
+ 0x0e1009a0, 0x320f21a4, 0x1b210112, 0x043f2a05, // pl.lt.is_322 jw.lv.bs_433 en.jw.tr_654 mt.af.fi_333
+ // [43e0]
+ 0x1f003f05, 0x00000403, 0x214a3b0c, 0x13006b04, // af.cy.un_330 fi.un.un_300 so.yo.jw_543 ceb.et.un_320
+ 0x2500320c, 0x3f0e0205, 0x2a0c0408, 0x04191c07, // bs.eu.un_530 da.is.af_333 fi.sv.mt_443 id.gl.fi_432
+ 0x01000304, 0x1e1c2814, 0x3f523ba4, 0x071c1ead, // nl.en.un_320 sw.id.ms_666 so.ha.af_433 ms.id.it_643
+ 0x0a003b0e, 0x103b19a4, 0x070511a4, 0x123b280c, // so.pt.un_550 gl.so.lt_433 ro.fr.it_433 sw.so.hu_543
+ // [43f0]
+ 0x0a003b07, 0x3b003f12, 0x0600180c, 0x3f0108ee, // so.pt.un_420 af.so.un_640 ga.de.un_530 no.en.af_422
+ 0x08023baf, 0x080f0ea4, 0x351f4a09, 0x0700200c, // so.da.no_655 is.lv.no_433 yo.cy.zu_444 sq.it.un_530
+ 0x2b001102, 0x04100705, 0x2d000104, 0x2d0d310c, // ro.vi.un_220 bg.be.ru_333 en.sk.un_320 az.cs.sk_543
+ 0x1a000e13, 0x6b1e1c11, 0x17005507, 0x23001c08, // is.tl.un_650 id.ms.ceb_653 rw.sr.un_420 id.ca.un_430
+
+ // [4400]
+ 0x5200680e, 0x0c640808, 0x27002b0d, 0x04133f0c, // ig.ha.un_550 no.lg.sv_443 vi.gd.un_540 af.et.fi_543
+ 0x13640414, 0x04132511, 0x0719200c, 0x2d0d20ee, // fi.lg.et_666 eu.et.fi_653 sq.gl.it_543 sq.cs.sk_422
+ 0x230a520b, 0x1e1c4aec, 0x6e0b4aa4, 0x080420ee, // ha.pt.ca_542 yo.id.ms_644 yo.es.hmn_433 sq.fi.no_422
+ 0x35006434, 0x311b1211, 0x20320413, 0x1300200c, // lg.zu.un_A80 hu.tr.az_653 fi.bs.sq_665 sq.et.un_530
+ // [4410]
+ 0x212d2808, 0x20003202, 0x1b001a04, 0x0e2807a9, // sw.sk.jw_443 bs.sq.un_220 tl.tr.un_320 it.sw.is_544
+ 0x321a4aa7, 0x170a0405, 0x0c092da0, 0x2a051308, // yo.tl.bs_532 ru.mk.sr_333 sk.pl.sv_322 et.fr.mt_443
+ 0x0b1b5205, 0x53311e0c, 0x1c000b04, 0x311b0ba0, // ha.tr.es_333 ms.az.ht_543 es.id.un_320 es.tr.az_322
+ 0x0b002904, 0x321620a9, 0x643b6b13, 0x4a643b13, // sl.es.un_320 sq.hr.bs_544 ceb.so.lg_665 so.lg.yo_665
+ // [4420]
+ 0x290820ee, 0x1b042012, 0x08201608, 0x3b2864af, // sq.no.sl_422 sq.fi.tr_654 hr.sq.no_443 lg.sw.so_655
+ 0x0e1b3160, 0x272a1804, 0x311b0eee, 0x04170702, // az.tr.is_664 ga.mt.gd_332 is.tr.az_422 bg.sr.ru_222
+ 0x1e524a02, 0x35213fa0, 0x6e000619, 0x27070308, // yo.ha.ms_222 af.jw.zu_322 de.hmn.un_750 nl.it.gd_443
+ 0x532513ec, 0x0600270d, 0x0600291a, 0x1a643b12, // et.eu.ht_644 gd.de.un_540 sl.de.un_760 so.lg.tl_654
+ // [4430]
+ 0x0f1006a9, 0x2d0d06ec, 0x06000302, 0x0c1906a7, // de.lt.lv_544 de.cs.sk_644 nl.de.un_220 de.gl.sv_532
+ 0x52006e22, 0x1a002a08, 0x0a000404, 0x25000914, // hmn.ha.un_870 mt.tl.un_430 ru.mk.un_320 pl.eu.un_660
+ 0x354a1aa4, 0x555364ec, 0x082902a4, 0x13000420, // tl.yo.zu_433 lg.ht.rw_644 da.sl.no_433 fi.et.un_850
+ 0x235321a9, 0x1b002504, 0x04003b08, 0x28002304, // jw.ht.ca_544 eu.tr.un_320 so.fi.un_430 ca.sw.un_320
+ // [4440]
+ 0x03060e0c, 0x01103fa4, 0x0916290c, 0x0b23010c, // is.de.nl_543 af.lt.en_433 sl.hr.pl_543 en.ca.es_543
+ 0x123152ee, 0x08006402, 0x06250c05, 0x1e000c08, // ha.az.hu_422 lg.no.un_220 sv.eu.de_333 sv.ms.un_430
+ 0x19070aec, 0x0e202a0d, 0x07000a36, 0x18000808, // pt.it.gl_644 mt.sq.is_554 mk.bg.un_AA0 no.ga.un_430
+ 0x0d042d0c, 0x03001602, 0x0605010c, 0x1c000305, // sk.fi.cs_543 hr.nl.un_220 en.fr.de_543 nl.id.un_330
+ // [4450]
+ 0x1e1c35a9, 0x53050607, 0x18001708, 0x1100290d, // zu.id.ms_544 de.fr.ht_432 sr.ga.un_430 sl.ro.un_540
+ 0x0e0c2aad, 0x160d29ad, 0x53002707, 0x25001f1b, // mt.sv.is_643 sl.cs.hr_643 gd.ht.un_420 cy.eu.un_770
+ 0x190a07a4, 0x13002314, 0x2a00112a, 0x64551a0c, // it.pt.gl_433 ca.et.un_660 ro.mt.un_970 tl.rw.lg_543
+ 0x52553b08, 0x1e1c2112, 0x04292da4, 0x1a536b09, // so.rw.ha_443 jw.id.ms_654 sk.sl.fi_433 ceb.ht.tl_444
+ // [4460]
+ 0x25102807, 0x273f53a0, 0x2b004a09, 0x53001e0d, // sw.lt.eu_432 ht.af.gd_322 yo.vi.un_440 ms.ht.un_540
+ 0x3b640f08, 0x3f001904, 0x2700290e, 0x062a6e12, // lv.lg.so_443 gl.af.un_320 sl.gd.un_550 hmn.mt.de_654
+ 0x0e2b4a0c, 0x2a12080b, 0x2d1819a0, 0x271007a0, // yo.vi.is_543 no.hu.mt_542 gl.ga.sk_322 it.lt.gd_322
+ 0x06001607, 0x550531ad, 0x28121fec, 0x2b004a21, // hr.de.un_420 az.fr.rw_643 cy.hu.sw_644 yo.vi.un_860
+ // [4470]
+ 0x01032a02, 0x21011aee, 0x21251fad, 0x0f000702, // mt.nl.en_222 tl.en.jw_422 cy.eu.jw_643 it.lv.un_220
+ 0x07252904, 0x0b05010c, 0x13091c09, 0x64001a19, // sl.eu.it_332 en.fr.es_543 mr.hi.bh_444 tl.lg.un_750
+ 0x1b2855a9, 0x3b523108, 0x19001c04, 0x1a1c6b08, // rw.sw.tr_544 az.ha.so_443 id.gl.un_320 ceb.id.tl_443
+ 0x02040807, 0x02063112, 0x132025a4, 0x3b21640b, // no.fi.da_432 az.de.da_654 eu.sq.et_433 lg.jw.so_542
+ // [4480]
+ 0x01001104, 0x17200907, 0x31351b08, 0x13002a0e, // ro.en.un_320 pl.sq.sr_432 tr.zu.az_443 mt.et.un_550
+ 0x191b350c, 0x0c0821a0, 0x5531350c, 0x6431520c, // zu.tr.gl_543 jw.no.sv_322 zu.az.rw_543 ha.az.lg_543
+ 0x19003118, 0x1f001b13, 0x283f06a4, 0x3f010310, // az.gl.un_740 tr.cy.un_650 de.af.sw_433 nl.en.af_642
+ 0x203117ee, 0x55311b0d, 0x0d0553a7, 0x351b310d, // sr.az.sq_422 tr.az.rw_554 ht.fr.cs_532 az.tr.zu_554
+ // [4490]
+ 0x1121280c, 0x3217290c, 0x1e1c1a14, 0x1b0f1008, // sw.jw.ro_543 sl.sr.bs_543 tl.id.ms_666 lt.lv.tr_443
+ 0x04123fa9, 0x1e002504, 0x08022714, 0x0f1004a4, // af.hu.fi_544 eu.ms.un_320 gd.da.no_666 fi.lt.lv_433
+ 0x232153ec, 0x190b18a0, 0x131004a4, 0x4a1f2855, // ht.jw.ca_644 ga.es.gl_322 fi.lt.et_433 sw.cy.yo_442
+ 0x1b0f1012, 0x0655350c, 0x2b182702, 0x1835555a, // lt.lv.tr_654 zu.rw.de_543 gd.ga.vi_222 rw.zu.ga_553
+ // [44a0]
+ 0x0b232708, 0x28004a0e, 0x0a001e0c, 0x1710040c, // gd.ca.es_443 yo.sw.un_550 ms.pt.un_530 ru.be.sr_543
+ 0x6b121aaf, 0x01020fa0, 0x1253050e, 0x2010040c, // tl.hu.ceb_655 lv.da.en_322 fr.ht.hu_555 fi.lt.sq_543
+ 0x1935640c, 0x0f002d04, 0x0429200e, 0x071710a0, // lg.zu.gl_543 sk.lv.un_320 sq.sl.fi_555 be.sr.bg_322
+ 0x31063b07, 0x3b003204, 0x53180ba0, 0x21002b08, // so.de.az_432 bs.so.un_320 es.ga.ht_322 vi.jw.un_430
+ // [44b0]
+ 0x136e2512, 0x111e1912, 0x13000708, 0x05124aee, // eu.hmn.et_654 gl.ms.ro_654 it.et.un_430 yo.hu.fr_422
+ 0x0307350c, 0x2d002020, 0x100f35ad, 0x05640704, // zu.it.nl_543 sq.sk.un_850 zu.lv.lt_643 it.lg.fr_332
+ 0x06350314, 0x27001804, 0x09105305, 0x061b030c, // nl.zu.de_666 ga.gd.un_320 ht.lt.pl_333 nl.tr.de_543
+ 0x1f000705, 0x4a5504af, 0x191a2107, 0x122b19ad, // it.cy.un_330 fi.rw.yo_655 jw.tl.gl_432 gl.vi.hu_643
+ // [44c0]
+ 0x051103a9, 0x291055a4, 0x1a4a3504, 0x2011530c, // nl.ro.fr_544 rw.lt.sl_433 zu.yo.tl_332 ht.ro.sq_543
+ 0x07003522, 0x0a1c2dee, 0x05011111, 0x05110304, // zu.it.un_870 sk.id.pt_422 ro.en.fr_653 nl.ro.fr_332
+ 0x11000322, 0x31003512, 0x32006407, 0x32212d07, // nl.ro.un_870 zu.az.un_640 lg.bs.un_420 sk.jw.bs_432
+ 0x08170a0c, 0x021a3f0d, 0x2307110e, 0x21001a04, // mk.sr.uk_543 af.tl.da_554 ro.it.ca_555 tl.jw.un_320
+ // [44d0]
+ 0x190b230c, 0x52102907, 0x206b35ad, 0x35094aa7, // ca.es.gl_543 sl.lt.ha_432 zu.ceb.sq_643 yo.pl.zu_532
+ 0x20051305, 0x3b063f0c, 0x1c211360, 0x022b250c, // et.fr.sq_333 af.de.so_543 et.jw.id_664 eu.vi.da_543
+ 0x11052da4, 0x2a290fa4, 0x03001113, 0x03010505, // sk.fr.ro_433 lv.sl.mt_433 ro.nl.un_650 fr.en.nl_333
+ 0x16000108, 0x2d290d05, 0x64355513, 0x2505010d, // en.hr.un_430 cs.sl.sk_333 rw.zu.lg_665 en.fr.eu_554
+ // [44e0]
+ 0x10001105, 0x281a01af, 0x3b203108, 0x12061305, // ro.be.un_330 en.tl.sw_655 az.sq.so_443 et.de.hu_333
+ 0x5553350c, 0x0d002a0d, 0x042113af, 0x02003f12, // zu.ht.rw_543 mt.cs.un_540 et.jw.fi_655 af.da.un_640
+ 0x32006b02, 0x0d0f2d0c, 0x02003f1a, 0x0723095a, // ceb.bs.un_220 sk.lv.cs_543 af.da.un_760 pl.ca.it_553
+ 0x10001a04, 0x1c35250c, 0x04292809, 0x3f0208a4, // tl.lt.un_320 eu.zu.id_543 sw.sl.fi_444 no.da.af_433
+ // [44f0]
+ 0x35000912, 0x11005212, 0x080201ee, 0x08023f0c, // pl.zu.un_640 ha.ro.un_640 en.da.no_422 af.da.no_543
+ 0x17680cee, 0x132311ec, 0x252813a9, 0x020e08af, // sv.ig.sr_422 ro.ca.et_644 et.sw.eu_544 no.is.da_655
+ 0x3b6435ee, 0x072368a7, 0x1103010c, 0x13281a08, // zu.lg.so_422 ig.ca.it_532 en.nl.ro_543 tl.sw.et_443
+ 0x013f09ad, 0x101a1e0c, 0x122d6ba0, 0x17002312, // pl.af.en_643 ms.tl.lt_543 ceb.sk.hu_322 ca.sr.un_640
+ // [4500]
+ 0x35001a04, 0x212a1ca4, 0x2d0d20a4, 0x0c001302, // tl.zu.un_320 id.mt.jw_433 sq.cs.sk_433 et.sv.un_220
+ 0x2829175a, 0x12003514, 0x0a130413, 0x18002304, // sr.sl.sw_553 zu.hu.un_660 fi.et.pt_665 ca.ga.un_320
+ 0x2800521b, 0x200803ee, 0x1e131ca4, 0x21006407, // ha.sw.un_770 nl.no.sq_422 id.et.ms_433 lg.jw.un_420
+ 0x0700112a, 0x12060cac, 0x12004a18, 0x21003204, // ro.bg.un_970 sv.de.hu_632 yo.hu.un_740 bs.jw.un_320
+ // [4510]
+ 0x3b1b52af, 0x0f001214, 0x23051fa9, 0x5200531b, // ha.tr.so_655 hu.lv.un_660 cy.fr.ca_544 ht.ha.un_770
+ 0x0118270c, 0x0e00270d, 0x6b041a04, 0x321610a4, // gd.ga.en_543 gd.is.un_540 tl.fi.ceb_332 lt.hr.bs_433
+ 0x1e1c1a09, 0x180111ee, 0x1c031ea9, 0x0d005305, // tl.id.ms_444 ro.en.ga_422 ms.nl.id_544 ht.cs.un_330
+ 0x12001a04, 0x2a000802, 0x1e000c05, 0x3b112da4, // tl.hu.un_320 no.mt.un_220 sv.ms.un_330 sk.ro.so_433
+ // [4520]
+ 0x29006804, 0x1b00041b, 0x060e10a9, 0x352827ee, // ig.sl.un_320 fi.tr.un_770 lt.is.de_544 gd.sw.zu_422
+ 0x18551f07, 0x31202511, 0x1e00531a, 0x1c521eee, // cy.rw.ga_432 eu.sq.az_653 ht.ms.un_760 ms.ha.id_422
+ 0x1f0627a4, 0x0c042a0b, 0x18002822, 0x1200061a, // gd.de.cy_433 mt.fi.sv_542 sw.ga.un_870 de.hu.un_760
+ 0x2a000711, 0x090a0ba0, 0x3b110fa4, 0x01002a0d, // it.mt.un_630 es.pt.pl_322 lv.ro.so_433 mt.en.un_540
+ // [4530]
+ 0x0c000407, 0x0a112a08, 0x0a1004ee, 0x2d0d18ec, // fi.sv.un_420 mt.ro.pt_443 ru.be.mk_422 ga.cs.sk_644
+ 0x281e35a4, 0x1f0701a4, 0x09006e0e, 0x060301a0, // zu.ms.sw_433 en.it.cy_433 hmn.pl.un_550 en.nl.de_322
+ 0x03000604, 0x532b180b, 0x0e2d2a07, 0x063f2705, // de.nl.un_320 ga.vi.ht_542 mt.sk.is_432 gd.af.de_333
+ 0x0a070ba4, 0x17163207, 0x0c005504, 0x52002807, // es.it.pt_433 bs.hr.sr_432 rw.sv.un_320 sw.ha.un_420
+ // [4540]
+ 0x0100061a, 0x17041060, 0x1e3f035a, 0x3f00050d, // de.en.un_760 be.ru.sr_664 nl.af.ms_553 fr.af.un_540
+ 0x06121b1e, 0x35002919, 0x28002702, 0x210a3bad, // tr.hu.de_863 sl.zu.un_750 gd.sw.un_220 so.pt.jw_643
+ 0x19070b60, 0x11033f08, 0x16003507, 0x0805010c, // es.it.gl_664 af.nl.ro_443 zu.hr.un_420 en.fr.no_543
+ 0x0107050d, 0x0300280c, 0x0b18070b, 0x08130207, // fr.it.en_554 sw.nl.un_530 it.ga.es_542 da.et.no_432
+ // [4550]
+ 0x12001b14, 0x0200111b, 0x060c030c, 0x12231804, // tr.hu.un_660 ro.da.un_770 nl.sv.de_543 ga.ca.hu_332
+ 0x190b28a4, 0x1b000702, 0x3f001c02, 0x1800060c, // sw.es.gl_433 it.tr.un_220 id.af.un_220 de.ga.un_530
+ 0x01352107, 0x23006b08, 0x020428ee, 0x0e000212, // jw.zu.en_432 ceb.ca.un_430 sw.fi.da_422 da.is.un_640
+ 0x100f29ec, 0x3f082007, 0x190b1aa9, 0x21132909, // sl.lv.lt_644 sq.no.af_432 tl.es.gl_544 sl.et.jw_444
+ // [4560]
+ 0x13040c02, 0x10130f0c, 0x321713ee, 0x06040eee, // sv.fi.et_222 lv.et.lt_543 et.sr.bs_422 is.fi.de_422
+ 0x07002302, 0x31000b02, 0x0a041702, 0x21050aa9, // ca.it.un_220 es.az.un_220 sr.ru.mk_222 pt.fr.jw_544
+ 0x1b0f5302, 0x1652090c, 0x27120e07, 0x293b3202, // ht.lv.tr_222 pl.ha.hr_543 is.hu.gd_432 bs.so.sl_222
+ 0x08000619, 0x1c3231a4, 0x055325a0, 0x0e000c0b, // de.no.un_750 az.bs.id_433 eu.ht.fr_322 sv.is.un_520
+ // [4570]
+ 0x2a102505, 0x04171011, 0x1e1c280c, 0x29001607, // eu.lt.mt_333 be.sr.ru_653 sw.id.ms_543 hr.sl.un_420
+ 0x03643b0c, 0x2a000807, 0x08002308, 0x3b006412, // so.lg.nl_543 no.mt.un_420 ca.no.un_430 lg.so.un_640
+ 0x11003f13, 0x321716ac, 0x163528a6, 0x271206a4, // af.ro.un_650 hr.sr.bs_632 sw.zu.hr_521 de.hu.gd_433
+ 0x043264a0, 0x0900270c, 0x0435280d, 0x55046412, // lg.bs.fi_322 gd.pl.un_530 sw.zu.fi_554 lg.fi.rw_654
+ // [4580]
+ 0x1727110b, 0x19250b55, 0x3f06080c, 0x3f100c0b, // ro.gd.sr_542 es.eu.gl_442 no.de.af_543 sv.lt.af_542
+ 0x28003b13, 0x3f0320a4, 0x521c1eec, 0x32291612, // so.sw.un_650 sq.nl.af_433 ms.id.ha_644 hr.sl.bs_654
+ 0x203b520c, 0x100d2d12, 0x21255307, 0x0c031355, // ha.so.sq_543 sk.cs.lt_654 ht.eu.jw_432 et.nl.sv_442
+ 0x2d001218, 0x020e08a9, 0x2300070c, 0x0e003118, // hu.sk.un_740 no.is.da_544 it.ca.un_530 az.is.un_740
+ // [4590]
+ 0x120603a0, 0x233555af, 0x10081713, 0x31001014, // nl.de.hu_322 rw.zu.ca_655 sr.uk.be_665 lt.az.un_660
+ 0x18130411, 0x191755ac, 0x11006413, 0x19000721, // fi.et.ga_653 rw.sr.gl_632 lg.ro.un_650 it.gl.un_860
+ 0x211c6ea7, 0x320f1707, 0x181e1c0c, 0x0d00530d, // hmn.id.jw_532 sr.lv.bs_432 id.ms.ga_543 ht.cs.un_540
+ 0x645511ee, 0x1a046b13, 0x196e0aad, 0x3b0925ad, // ro.rw.lg_422 ceb.fi.tl_665 pt.hmn.gl_643 eu.pl.so_643
+ // [45a0]
+ 0x251155a4, 0x2a093b12, 0x020108ad, 0x12000318, // rw.ro.eu_433 so.pl.mt_654 no.en.da_643 nl.hu.un_740
+ 0x115320a9, 0x04080aa0, 0x23001911, 0x2810640c, // sq.ht.ro_544 mk.uk.ru_322 gl.ca.un_630 lg.lt.sw_543
+ 0x0300041b, 0x1c0a6e08, 0x09532aa4, 0x27180a13, // fi.nl.un_770 hmn.pt.id_443 mt.ht.pl_433 pt.ga.gd_665
+ 0x0a0401a4, 0x0c085507, 0x0a186ea0, 0x3b090eec, // en.fi.pt_433 rw.no.sv_432 hmn.ga.pt_322 is.pl.so_644
+ // [45b0]
+ 0x16002a08, 0x073f1111, 0x1c5521a4, 0x0e085511, // mt.hr.un_430 ro.af.it_653 jw.rw.id_433 rw.no.is_653
+ 0x4a5568a0, 0x123f030d, 0x06293fa4, 0x55002a0e, // ig.rw.yo_322 nl.af.hu_554 af.sl.de_433 mt.rw.un_550
+ 0x1f00231a, 0x29002507, 0x32000305, 0x281a6b12, // ca.cy.un_760 eu.sl.un_420 nl.bs.un_330 ceb.tl.sw_654
+ 0x1e230ba9, 0x28000d19, 0x6b190b09, 0x106b280c, // es.ca.ms_544 cs.sw.un_750 es.gl.ceb_444 sw.ceb.lt_543
+ // [45c0]
+ 0x3b001019, 0x0a2a0709, 0x131a280c, 0x0e191012, // lt.so.un_750 it.mt.pt_444 sw.tl.et_543 lt.gl.is_654
+ 0x160f29a9, 0x0f000421, 0x27002312, 0x181b31a7, // sl.lv.hr_544 fi.lv.un_860 ca.gd.un_640 az.tr.ga_532
+ 0x25072704, 0x17551304, 0x20033f5a, 0x25000613, // gd.it.eu_332 et.rw.sr_332 af.nl.sq_553 de.eu.un_650
+ 0x68003535, 0x27115509, 0x2a0427a0, 0x212a07a0, // zu.ig.un_A90 rw.ro.gd_444 gd.fi.mt_322 it.mt.jw_322
+ // [45d0]
+ 0x52211aa9, 0x19090aaf, 0x050d1905, 0x2d0d0aa9, // tl.jw.ha_544 pt.pl.gl_655 gl.cs.fr_333 pt.cs.sk_544
+ 0x0e2b2808, 0x1e181ca4, 0x031a2107, 0x2809550b, // sw.vi.is_443 id.ga.ms_433 jw.tl.nl_432 rw.pl.sw_542
+ 0x10002119, 0x310e0cee, 0x2d0d11a0, 0x32172009, // jw.lt.un_750 sv.is.az_422 ro.cs.sk_322 sq.sr.bs_444
+ 0x532305ad, 0x040a17ad, 0x644a52ee, 0x6412230c, // fr.ca.ht_643 sr.mk.ru_643 ha.yo.lg_422 ca.hu.lg_543
+ // [45e0]
+ 0x3b290f0c, 0x68004a07, 0x3b211a08, 0x01002707, // lv.sl.so_543 yo.ig.un_420 tl.jw.so_443 gd.en.un_420
+ 0x6b04010c, 0x0e002b19, 0x05002302, 0x13006e22, // en.fi.ceb_543 vi.is.un_750 ca.fr.un_220 hmn.et.un_870
+ 0x08002905, 0x1c532860, 0x1300120d, 0x0f071e05, // sl.no.un_330 sw.ht.id_664 hu.et.un_540 ms.it.lv_333
+ 0x21111707, 0x0f312907, 0x52000d0d, 0x2a0b0405, // sr.ro.jw_432 sl.az.lv_432 cs.ha.un_540 fi.es.mt_333
+ // [45f0]
+ 0x010e08a4, 0x0b64110c, 0x18000909, 0x2a1f1109, // no.is.en_433 ro.lg.es_543 pl.ga.un_440 ro.cy.mt_444
+ 0x2d170da4, 0x16073b07, 0x321c1e0c, 0x23002014, // cs.sr.sk_433 so.it.hr_432 ms.id.bs_543 sq.ca.un_660
+ 0x3f000207, 0x4a170b07, 0x21001f02, 0x0d004a19, // da.af.un_420 es.sr.yo_432 cy.jw.un_220 yo.cs.un_750
+ 0x03001f07, 0x2928040c, 0x12081fad, 0x1f002722, // cy.nl.un_420 fi.sw.sl_543 cy.no.hu_643 gd.cy.un_870
+ // [4600]
+ 0x032928a7, 0x550368a6, 0x051108af, 0x13000307, // sw.sl.nl_532 ig.nl.rw_521 no.ro.fr_655 nl.et.un_420
+ 0x06000c29, 0x311b1220, 0x23000610, 0x291302ee, // sv.de.un_960 hu.tr.az_875 de.ca.un_620 da.et.sl_422
+ 0x03043f5a, 0x1f003f14, 0x0c002004, 0x27003b04, // af.fi.nl_553 af.cy.un_660 sq.sv.un_320 so.gd.un_320
+ 0x4a00351b, 0x060108a4, 0x3f060f12, 0x03000614, // zu.yo.un_770 no.en.de_433 lv.de.af_654 de.nl.un_660
+ // [4610]
+ 0x31001702, 0x232a0514, 0x04001a04, 0x01003f09, // sr.az.un_220 fr.mt.ca_666 tl.fi.un_320 af.en.un_440
+ 0x52283fa9, 0x1a4a680c, 0x64312808, 0x271819a9, // af.sw.ha_544 ig.yo.tl_543 sw.az.lg_443 gl.ga.gd_544
+ 0x326428a6, 0x283b520c, 0x130e0b04, 0x1e1c28ee, // sw.lg.bs_521 ha.so.sw_543 es.is.et_332 sw.id.ms_422
+ 0x0764550c, 0x3100061b, 0x3f0f10ad, 0x1e003b0d, // rw.lg.it_543 de.az.un_770 lt.lv.af_643 so.ms.un_540
+ // [4620]
+ 0x0f2d10af, 0x112d64ad, 0x18190b5a, 0x20071aad, // lt.sk.lv_655 lg.sk.ro_643 es.gl.ga_553 tl.it.sq_643
+ 0x190b280c, 0x556b2508, 0x06030204, 0x13551e07, // sw.es.gl_543 eu.ceb.rw_443 da.nl.de_332 ms.rw.et_432
+ 0x111b0b02, 0x0e5220ee, 0x1c090d0b, 0x35080208, // es.tr.ro_222 sq.ha.is_422 ne.hi.mr_542 da.no.zu_443
+ 0x0d1c090b, 0x4a00281a, 0x180301a6, 0x190b07a4, // hi.mr.ne_542 sw.yo.un_760 en.nl.ga_521 it.es.gl_433
+ // [4630]
+ 0x20001e02, 0x35091f05, 0x211a6b14, 0x6b001109, // ms.sq.un_220 cy.pl.zu_333 ceb.tl.jw_666 ro.ceb.un_440
+ 0x0f3f035a, 0x0c25020c, 0x066b550d, 0x08251108, // nl.af.lv_553 da.eu.sv_543 rw.ceb.de_554 ro.eu.no_443
+ 0x130d09a4, 0x3b3f35ad, 0x2d00290b, 0x1b643b5a, // hi.ne.bh_433 zu.af.so_643 sl.sk.un_520 so.lg.tr_553
+ 0x021a1213, 0x5525520c, 0x0e2d19af, 0x3b126bad, // hu.tl.da_665 ha.eu.rw_543 gl.sk.is_655 ceb.hu.so_643
+ // [4640]
+ 0x09002304, 0x523b2a07, 0x09060107, 0x09001121, // ca.pl.un_320 mt.so.ha_432 en.de.pl_432 ro.pl.un_860
+ 0x3b3f64af, 0x285355ec, 0x353b1aa4, 0x251203a7, // lg.af.so_655 rw.ht.sw_644 tl.so.zu_433 nl.hu.eu_532
+ 0x2801180c, 0x2300531a, 0x1c163bee, 0x0000042d, // ga.en.sw_543 ht.ca.un_760 so.hr.id_422 ru.un.un_A00
+ 0x19230ba0, 0x0e003f14, 0x29006b21, 0x110417af, // es.ca.gl_322 af.is.un_660 ceb.sl.un_860 sr.ru.ro_655
+ // [4650]
+ 0x23001114, 0x1300031a, 0x250a280c, 0x17000702, // ro.ca.un_660 nl.et.un_760 sw.pt.eu_543 bg.sr.un_220
+ 0x0c003509, 0x04170802, 0x072305a4, 0x02000d08, // zu.sv.un_440 uk.sr.ru_222 fr.ca.it_433 cs.da.un_430
+ 0x2d091f11, 0x04190a13, 0x28011807, 0x11033f0c, // cy.pl.sk_653 pt.gl.fi_665 ga.en.sw_432 af.nl.ro_543
+ 0x6b68010c, 0x4a2b0ead, 0x040a08ad, 0x133f0aaf, // en.ig.ceb_543 is.vi.yo_643 uk.mk.ru_643 pt.af.et_655
+ // [4660]
+ 0x0e000214, 0x2d191008, 0x532d090c, 0x182704ad, // da.is.un_660 lt.gl.sk_443 pl.sk.ht_543 fi.gd.ga_643
+ 0x23072004, 0x120c28a4, 0x05000422, 0x0c521b0c, // sq.it.ca_332 sw.sv.hu_433 fi.fr.un_870 tr.ha.sv_543
+ 0x04130ca6, 0x3b281aad, 0x283b6412, 0x04170a02, // sv.et.fi_521 tl.sw.so_643 lg.so.sw_654 mk.sr.ru_222
+ 0x05033f0c, 0x2010530c, 0x55005212, 0x322852a7, // af.nl.fr_543 ht.lt.sq_543 ha.rw.un_640 ha.sw.bs_532
+ // [4670]
+ 0x16090dad, 0x68641a11, 0x52003b21, 0x09282d09, // cs.pl.hr_643 tl.lg.ig_653 so.ha.un_860 sk.sw.pl_444
+ 0x1a556bad, 0x06005212, 0x3f001819, 0x4a005202, // ceb.rw.tl_643 ha.de.un_640 ga.af.un_750 ha.yo.un_220
+ 0x212852ec, 0x35211e0c, 0x533528ad, 0x6b092107, // ha.sw.jw_644 ms.jw.zu_543 sw.zu.ht_643 jw.pl.ceb_432
+ 0x07271808, 0x55643513, 0x1a282105, 0x5200101a, // ga.gd.it_443 zu.lg.rw_665 jw.sw.tl_333 lt.ha.un_760
+ // [4680]
+ 0x25001309, 0x23001f05, 0x0f002808, 0x320e280c, // et.eu.un_440 cy.ca.un_330 sw.lv.un_430 sw.is.bs_543
+ 0x35645204, 0x28005533, 0x12001e11, 0x10001321, // ha.lg.zu_332 rw.sw.un_A70 ms.hu.un_630 et.lt.un_860
+ 0x10040805, 0x4a6825af, 0x18002307, 0x6e001022, // uk.ru.be_333 eu.ig.yo_655 ca.ga.un_420 lt.hmn.un_870
+ 0x21321608, 0x0c1f08a9, 0x190b5505, 0x0a1b2355, // hr.bs.jw_443 no.cy.sv_544 rw.es.gl_333 ca.tr.pt_442
+ // [4690]
+ 0x210e1fa0, 0x0c2a0208, 0x5528680c, 0x55645213, // cy.is.jw_322 da.mt.sv_443 ig.sw.rw_543 ha.lg.rw_665
+ 0x35642755, 0x05000309, 0x2a1e32a0, 0x13680fa4, // gd.lg.zu_442 nl.fr.un_440 bs.ms.mt_322 lv.ig.et_433
+ 0x52285508, 0x521e31a7, 0x13002514, 0x286855a4, // rw.sw.ha_443 az.ms.ha_532 eu.et.un_660 rw.ig.sw_433
+ 0x2012550c, 0x21003b0b, 0x25006821, 0x522755ee, // rw.hu.sq_543 so.jw.un_520 ig.eu.un_860 rw.gd.ha_422
+ // [46a0]
+ 0x133564ee, 0x10081705, 0x3b2a0712, 0x2d0d5205, // lg.zu.et_422 sr.uk.be_333 it.mt.so_654 ha.cs.sk_333
+ 0x641a550c, 0x11200e0c, 0x075564ad, 0x21000312, // rw.tl.lg_543 is.sq.ro_543 lg.rw.it_643 nl.jw.un_640
+ 0x311e55ee, 0x02080c13, 0x213f1355, 0x110805a4, // rw.ms.az_422 sv.no.da_665 et.af.jw_442 fr.no.ro_433
+ 0x2a001b13, 0x1a003b1b, 0x230e0c12, 0x2303010c, // tr.mt.un_650 so.tl.un_770 sv.is.ca_654 en.nl.ca_543
+ // [46b0]
+ 0x1c090d02, 0x1e00250e, 0x04002d04, 0x06020304, // ne.hi.mr_222 eu.ms.un_550 sk.fi.un_320 nl.da.de_332
+ 0x28002a0b, 0x17100704, 0x20000c0c, 0x032b0804, // mt.sw.un_520 bg.be.sr_332 sv.sq.un_530 no.vi.nl_332
+ 0x0e002304, 0x3b122a02, 0x1a1e0804, 0x321629a0, // ca.is.un_320 mt.hu.so_222 no.ms.tl_332 sl.hr.bs_322
+ 0x32170fa4, 0x132a12ad, 0x08000e07, 0x08112bee, // lv.sr.bs_433 hu.mt.et_643 is.no.un_420 vi.ro.no_422
+ // [46c0]
+ 0x0c104aa7, 0x163168a4, 0x11041005, 0x01001307, // yo.lt.sv_532 ig.az.hr_433 be.ru.ro_333 et.en.un_420
+ 0x212d05a7, 0x130f04ec, 0x32552907, 0x64001c02, // fr.sk.jw_532 fi.lv.et_644 sl.rw.bs_432 id.lg.un_220
+ 0x012123ee, 0x23311bad, 0x2b000302, 0x64080ca7, // ca.jw.en_422 tr.az.ca_643 nl.vi.un_220 sv.no.lg_532
+ 0x1b321608, 0x3b0117a0, 0x100f01a4, 0x082d1f07, // hr.bs.tr_443 sr.en.so_322 en.lv.lt_433 cy.sk.no_432
+ // [46d0]
+ 0x21251eaf, 0x13351ea4, 0x5500130e, 0x10190a09, // ms.eu.jw_655 ms.zu.et_433 et.rw.un_550 pt.gl.lt_444
+ 0x12351bee, 0x070553a4, 0x6400550b, 0x12172004, // tr.zu.hu_422 ht.fr.it_433 rw.lg.un_520 sq.sr.hu_332
+ 0x32350607, 0x12205508, 0x12645512, 0x2d172304, // de.zu.bs_432 rw.sq.hu_443 rw.lg.hu_654 ca.sr.sk_332
+ 0x211a250c, 0x18002b04, 0x553b2da0, 0x3f001608, // eu.tl.jw_543 vi.ga.un_320 sk.so.rw_322 hr.af.un_430
+ // [46e0]
+ 0x1213100c, 0x521b3112, 0x1664555a, 0x23120dee, // lt.et.hu_543 az.tr.ha_654 rw.lg.hr_553 cs.hu.ca_422
+ 0x01190a0c, 0x4a64550d, 0x1f521aa4, 0x1a525508, // pt.gl.en_543 rw.lg.yo_554 tl.ha.cy_433 rw.ha.tl_443
+ 0x523b1e12, 0x2a000919, 0x3264250c, 0x20002102, // ms.so.ha_654 pl.mt.un_750 eu.lg.bs_543 jw.sq.un_220
+ 0x64251ba4, 0x25645511, 0x09000c19, 0x29556412, // tr.eu.lg_433 rw.lg.eu_653 sv.pl.un_750 lg.rw.sl_654
+ // [46f0]
+ 0x08025302, 0x033f10a4, 0x1900130d, 0x0c4a5208, // ht.da.no_222 lt.af.nl_433 et.gl.un_540 ha.yo.sv_443
+ 0x2b120608, 0x16645508, 0x09000319, 0x03133f0d, // de.hu.vi_443 rw.lg.hr_443 nl.pl.un_750 af.et.nl_554
+ 0x01061f07, 0x5500642c, 0x4a686b07, 0x534a5512, // cy.de.en_432 lg.rw.un_990 ceb.ig.yo_432 rw.yo.ht_654
+ 0x1900050b, 0x112955a4, 0x120e2d12, 0x011905a4, // fr.gl.un_520 rw.sl.ro_433 sk.is.hu_654 fr.gl.en_433
+ // [4700]
+ 0x010a0455, 0x0a3b200c, 0x09001012, 0x25550407, // fi.pt.en_442 sq.so.pt_543 lt.pl.un_640 fi.rw.eu_432
+ 0x16041312, 0x13045505, 0x213b1aa7, 0x32002a07, // et.fi.hr_654 rw.fi.et_333 tl.so.jw_532 mt.bs.un_420
+ 0x20352107, 0x553513ee, 0x52201312, 0x1c00092c, // jw.zu.sq_432 et.zu.rw_422 et.sq.ha_654 hi.mr.un_990
+ 0x531a1255, 0x35125505, 0x103217a6, 0x19520ea4, // hu.tl.ht_442 rw.hu.zu_333 sr.bs.lt_521 is.ha.gl_433
+ // [4710]
+ 0x35251a05, 0x0a190b11, 0x293203ee, 0x534a52ee, // tl.eu.zu_333 es.gl.pt_653 nl.bs.sl_422 ha.yo.ht_422
+ 0x3b211c0d, 0x01040555, 0x07000c0e, 0x3500251a, // id.jw.so_554 fr.fi.en_442 sv.it.un_550 eu.zu.un_760
+ 0x17100a11, 0x68641aa4, 0x19230b0b, 0x193b0bee, // mk.be.sr_653 tl.lg.ig_433 es.ca.gl_542 es.so.gl_422
+ 0x13005504, 0x1b001902, 0x2004550c, 0x21006404, // rw.et.un_320 gl.tr.un_220 rw.fi.sq_543 lg.jw.un_320
+ // [4720]
+ 0x1e271813, 0x643529a4, 0x35001314, 0x64191aa0, // ga.gd.ms_665 sl.zu.lg_433 et.zu.un_660 tl.gl.lg_322
+ 0x17352904, 0x162d07a0, 0x086420a0, 0x1e0903a4, // sl.zu.sr_332 it.sk.hr_322 sq.lg.no_322 nl.pl.ms_433
+ 0x031b53ad, 0x31080205, 0x68001b04, 0x10005204, // ht.tr.nl_643 da.no.az_333 tr.ig.un_320 ha.lt.un_320
+ 0x2d0d1aee, 0x291613a7, 0x6b1a3bec, 0x052d0d0d, // tl.cs.sk_422 et.hr.sl_532 so.tl.ceb_644 cs.sk.fr_554
+ // [4730]
+ 0x123f0c0c, 0x321855a4, 0x35001e04, 0x55290713, // sv.af.hu_543 rw.ga.bs_433 ms.zu.un_320 it.sl.rw_665
+ 0x060b2004, 0x29160407, 0x28295507, 0x23190507, // sq.es.de_332 fi.hr.sl_432 rw.sl.sw_432 fr.gl.ca_432
+ 0x28642307, 0x0e6b1a02, 0x01000507, 0x2d250a05, // ca.lg.sw_432 tl.ceb.is_222 fr.en.un_420 pt.eu.sk_333
+ 0x190b0e05, 0x11001904, 0x08120c05, 0x080e2aec, // is.es.gl_333 gl.ro.un_320 sv.hu.no_333 mt.is.no_644
+ // [4740]
+ 0x04101713, 0x1b000b05, 0x35006402, 0x55093bee, // sr.be.ru_665 es.tr.un_330 lg.zu.un_220 so.pl.rw_422
+ 0x0d292dec, 0x28011fee, 0x2a042da9, 0x2b006b12, // sk.sl.cs_644 cy.en.sw_422 sk.fi.mt_544 ceb.vi.un_640
+ 0x321c2aee, 0x1c2b0aac, 0x3f27280c, 0x0b0928a4, // mt.id.bs_422 pt.vi.id_632 sw.gd.af_543 sw.pl.es_433
+ 0x2d292aec, 0x6b1a6807, 0x2a2b1a07, 0x27006b08, // mt.sl.sk_644 ig.tl.ceb_432 tl.vi.mt_432 ceb.gd.un_430
+ // [4750]
+ 0x6b2d07a0, 0x06000e21, 0x25000b04, 0x020c0855, // it.sk.ceb_322 is.de.un_860 es.eu.un_320 no.sv.da_442
+ 0x01000602, 0x013f03ad, 0x2d2029ad, 0x2a002b05, // de.en.un_220 nl.af.en_643 sl.sq.sk_643 vi.mt.un_330
+ 0x033f4aac, 0x29000f0e, 0x09002d0d, 0x64076802, // yo.af.nl_632 lv.sl.un_550 sk.pl.un_540 ig.it.lg_222
+ 0x0d2d290d, 0x20001f1a, 0x64000307, 0x022b0aec, // sl.sk.cs_554 cy.sq.un_760 nl.lg.un_420 pt.vi.da_644
+ // [4760]
+ 0x0e030ca7, 0x13080260, 0x0a1e0113, 0x012b3bee, // sv.nl.is_532 da.no.et_664 en.ms.pt_665 so.vi.en_422
+ 0x0c1009a4, 0x13000d19, 0x016b08a0, 0x3b033f05, // pl.lt.sv_433 ne.bh.un_750 no.ceb.en_322 af.nl.so_333
+ 0x290d17a9, 0x07093fa4, 0x3f000e14, 0x022b1207, // sr.cs.sl_544 af.pl.it_433 is.af.un_660 hu.vi.da_432
+ 0x27080e04, 0x233101a4, 0x21250e05, 0x181f280c, // is.no.gd_332 en.az.ca_433 is.eu.jw_333 sw.cy.ga_543
+ // [4770]
+ 0x2d0d28a9, 0x0f351304, 0x031827ec, 0x0f0706a9, // sw.cs.sk_544 et.zu.lv_332 gd.ga.nl_644 de.it.lv_544
+ 0x13001907, 0x05275307, 0x6811060c, 0x04191113, // gl.et.un_420 ht.gd.fr_432 de.ro.ig_543 ro.gl.fi_665
+ 0x190a05a4, 0x0a051912, 0x641f3504, 0x3f035309, // fr.pt.gl_433 gl.fr.pt_654 zu.cy.lg_332 ht.nl.af_444
+ 0x0f2913ee, 0x0c2927a7, 0x032b010c, 0x2d0d06ac, // et.sl.lv_422 gd.sl.sv_532 en.vi.nl_543 de.cs.sk_632
+ // [4780]
+ 0x060501a4, 0x07111755, 0x05010d09, 0x050d53ad, // en.fr.de_433 sr.ro.bg_442 cs.en.fr_444 ht.cs.fr_643
+ 0x6800060d, 0x030928a0, 0x526b1a60, 0x133525a0, // de.ig.un_540 sw.pl.nl_322 tl.ceb.ha_664 eu.zu.et_322
+ 0x0d2d1213, 0x0d555209, 0x6b4a350d, 0x25005304, // hu.sk.cs_665 ha.rw.cs_444 zu.yo.ceb_554 ht.eu.un_320
+ 0x256b1ca0, 0x0f003b12, 0x291e1c08, 0x1f0135a4, // id.ceb.eu_322 so.lv.un_640 id.ms.sl_443 zu.en.cy_433
+ // [4790]
+ 0x210f1c09, 0x21551f0c, 0x3b072a11, 0x0b0a01ee, // id.lv.jw_444 cy.rw.jw_543 mt.it.so_653 en.pt.es_422
+ 0x2d001211, 0x0a0c3b0b, 0x53006e07, 0x201a280c, // hu.sk.un_630 so.sv.pt_542 hmn.ht.un_420 sw.tl.sq_543
+ 0x080206a9, 0x35250ba4, 0x2800270e, 0x31002308, // de.da.no_544 es.eu.zu_433 gd.sw.un_550 ca.az.un_430
+ 0x02000513, 0x2a290f12, 0x09006404, 0x073125a9, // fr.da.un_650 lv.sl.mt_654 lg.pl.un_320 eu.az.it_544
+ // [47a0]
+ 0x281b2007, 0x2168280c, 0x25120107, 0x1b072a5a, // sq.tr.sw_432 sw.ig.jw_543 en.hu.eu_432 mt.it.tr_553
+ 0x19052308, 0x3f020312, 0x0a071ca4, 0x10000a02, // ca.fr.gl_443 nl.da.af_654 id.it.pt_433 mk.be.un_220
+ 0x17000822, 0x06070413, 0x17101112, 0x0500070e, // uk.sr.un_870 fi.it.de_665 ro.be.sr_654 it.fr.un_550
+ 0x17111012, 0x3516280c, 0x2a080e11, 0x033f01af, // be.ro.sr_654 sw.hr.zu_543 is.no.mt_653 en.af.nl_655
+ // [47b0]
+ 0x1c1353ee, 0x0a2d19a0, 0x1b093bad, 0x64000f19, // ht.et.id_422 gl.sk.pt_322 so.pl.tr_643 lv.lg.un_750
+ 0x12001e02, 0x1b2d090c, 0x64356b07, 0x0b0a2305, // ms.hu.un_220 pl.sk.tr_543 ceb.zu.lg_432 ca.pt.es_333
+ 0x6b003505, 0x046b130c, 0x0c1e3511, 0x4a5535ad, // zu.ceb.un_330 et.ceb.fi_543 zu.ms.sv_653 zu.rw.yo_643
+ 0x0d092da0, 0x1c0d1ea0, 0x211e1b07, 0x55352804, // sk.pl.cs_322 ms.cs.id_322 tr.ms.jw_432 sw.zu.rw_332
+ // [47c0]
+ 0x04000711, 0x55316407, 0x322b12ad, 0x0c235507, // bg.ru.un_630 lg.az.rw_432 hu.vi.bs_643 rw.ca.sv_432
+ 0x32290fa4, 0x1900120b, 0x093b5213, 0x046b1a0c, // lv.sl.bs_433 hu.gl.un_520 ha.so.pl_665 tl.ceb.fi_543
+ 0x1f00180d, 0x526b090c, 0x4a2135ec, 0x35002707, // ga.cy.un_540 pl.ceb.ha_543 zu.jw.yo_644 gd.zu.un_420
+ 0x211b53ee, 0x3b001c04, 0x0f2305a4, 0x05000c1a, // ht.tr.jw_422 id.so.un_320 fr.ca.lv_433 sv.fr.un_760
+ // [47d0]
+ 0x2b001c09, 0x171007af, 0x182729ad, 0x2a531c0c, // id.vi.un_440 bg.be.sr_655 sl.gd.ga_643 id.ht.mt_543
+ 0x171104a4, 0x1c006b0d, 0x08000f12, 0x2d100d05, // ru.ro.sr_433 ceb.id.un_540 lv.no.un_640 cs.lt.sk_333
+ 0x320f2308, 0x11002905, 0x023f08a6, 0x292d1607, // ca.lv.bs_443 sl.ro.un_330 no.af.da_521 hr.sk.sl_432
+ 0x290f1f12, 0x64000305, 0x35131a09, 0x1e1c27a0, // cy.lv.sl_654 nl.lg.un_330 tl.et.zu_444 gd.id.ms_322
+ // [47e0]
+ 0x233f2aee, 0x230f05a4, 0x20121fec, 0x230105a4, // mt.af.ca_422 fr.lv.ca_433 cy.hu.sq_644 fr.en.ca_433
+ 0x0f1035ac, 0x1b3f060c, 0x19211c0c, 0x25005514, // zu.lt.lv_632 de.af.tr_543 id.jw.gl_543 rw.eu.un_660
+ 0x23000b1b, 0x17001f0c, 0x1b000d04, 0x1b3113ad, // es.ca.un_770 cy.sr.un_530 cs.tr.un_320 et.az.tr_643
+ 0x13001e20, 0x0c6b08a4, 0x0e10250c, 0x2800350e, // ms.et.un_850 no.ceb.sv_433 eu.lt.is_543 zu.sw.un_550
+ // [47f0]
+ 0x5300281b, 0x5335090c, 0x0b23190d, 0x0e0b02ad, // sw.ht.un_770 pl.zu.ht_543 gl.ca.es_554 da.es.is_643
+ 0x11100da0, 0x0f002522, 0x2d091aaf, 0x52002504, // cs.lt.ro_322 eu.lv.un_870 tl.pl.sk_655 eu.ha.un_320
+ 0x55641b04, 0x07101705, 0x0f1008a0, 0x232d0704, // tr.lg.rw_332 sr.be.bg_333 no.lt.lv_322 it.sk.ca_332
+ 0x093f06a4, 0x2b00350c, 0x55004a0d, 0x11000707, // de.af.pl_433 zu.vi.un_530 yo.rw.un_540 it.ro.un_420
+
+ // [4800]
+ 0x1a1b1f0c, 0x3f2b2107, 0x2500131a, 0x020c0a0c, // cy.tr.tl_543 jw.vi.af_432 et.eu.un_760 pt.sv.da_543
+ 0x1f001e22, 0x042853af, 0x100f130c, 0x102518ad, // ms.cy.un_870 ht.sw.fi_655 et.lv.lt_543 ga.eu.lt_643
+ 0x096b3bee, 0x121a05ad, 0x080203ee, 0x6b181aee, // so.ceb.pl_422 fr.tl.hu_643 nl.da.no_422 tl.ga.ceb_422
+ 0x11270555, 0x521e25ad, 0x295310a4, 0x1f002522, // fr.gd.ro_442 eu.ms.ha_643 lt.ht.sl_433 eu.cy.un_870
+ // [4810]
+ 0x190f2512, 0x06041207, 0x080e2b02, 0x20251a13, // eu.lv.gl_654 hu.fi.de_432 vi.is.no_222 tl.eu.sq_665
+ 0x3b000411, 0x20352813, 0x270f04a0, 0x681f0faf, // fi.so.un_630 sw.zu.sq_665 fi.lv.gd_322 lv.cy.ig_655
+ 0x1c002a04, 0x122005ad, 0x1f04180d, 0x0a0804af, // mt.id.un_320 fr.sq.hu_643 ga.fi.cy_554 ru.uk.mk_655
+ 0x3b003202, 0x203b5555, 0x12310513, 0x6b1a2105, // bs.so.un_220 rw.so.sq_442 fr.az.hu_665 jw.tl.ceb_333
+ // [4820]
+ 0x52002021, 0x09002b14, 0x2000552a, 0x131c0960, // sq.ha.un_860 vi.pl.un_660 rw.sq.un_970 hi.mr.bh_664
+ 0x321729a0, 0x13000902, 0x033f0e55, 0x6b2128ee, // sl.sr.bs_322 hi.bh.un_220 is.af.nl_442 sw.jw.ceb_422
+ 0x202852ec, 0x1104085a, 0x23002b1a, 0x0c000b02, // ha.sw.sq_644 uk.ru.ro_553 vi.ca.un_760 es.sv.un_220
+ 0x2a001a0c, 0x20132311, 0x2500351a, 0x550f050b, // tl.mt.un_530 ca.et.sq_653 zu.eu.un_760 fr.lv.rw_542
+ // [4830]
+ 0x080207ee, 0x2d1029a4, 0x0107030c, 0x2a000e12, // it.da.no_422 sl.lt.sk_433 nl.it.en_543 is.mt.un_640
+ 0x3521280c, 0x08003b2c, 0x643b52af, 0x04351313, // sw.jw.zu_543 so.no.un_990 ha.so.lg_655 et.zu.fi_665
+ 0x05032308, 0x201f27af, 0x28002133, 0x17000b08, // ca.nl.fr_443 gd.cy.sq_655 jw.sw.un_A70 es.sr.un_430
+ 0x32001a07, 0x0604180c, 0x1a0507a4, 0x4a000a12, // tl.bs.un_420 ga.fi.de_543 it.fr.tl_433 pt.yo.un_640
+ // [4840]
+ 0x100f0c11, 0x0c0e0405, 0x03181c02, 0x316452ad, // sv.lv.lt_653 fi.is.sv_333 id.ga.nl_222 ha.lg.az_643
+ 0x1205010c, 0x0a08110c, 0x06200705, 0x070e1fa4, // en.fr.hu_543 ro.uk.mk_543 it.sq.de_333 cy.is.it_433
+ 0x20005313, 0x190a1fa9, 0x07001f0e, 0x3255520c, // ht.sq.un_650 cy.pt.gl_544 cy.it.un_550 ha.rw.bs_543
+ 0x01003204, 0x0a006b07, 0x16205507, 0x210a1ba0, // bs.en.un_320 ceb.pt.un_420 rw.sq.hr_432 tr.pt.jw_322
+ // [4850]
+ 0x13001f07, 0x2a001222, 0x13040aa0, 0x1b03060c, // cy.et.un_420 hu.mt.un_870 pt.fi.et_322 de.nl.tr_543
+ 0x06001c02, 0x0a190b0b, 0x052307ee, 0x17291e05, // id.de.un_220 es.gl.pt_542 it.ca.fr_422 ms.sl.sr_333
+ 0x5200352c, 0x06006b0e, 0x1000061b, 0x29001613, // zu.ha.un_990 ceb.de.un_550 de.lt.un_770 hr.sl.un_650
+ 0x641c2113, 0x1c091310, 0x2b6b01ee, 0x212027ee, // jw.id.lg_665 bh.hi.mr_642 en.ceb.vi_422 gd.sq.jw_422
+ // [4860]
+ 0x3f2301ad, 0x17001608, 0x082701ad, 0x271f1808, // en.ca.af_643 hr.sr.un_430 en.gd.no_643 ga.cy.gd_443
+ 0x01076b08, 0x0c522155, 0x253f3b11, 0x136804ee, // ceb.it.en_443 jw.ha.sv_442 so.af.eu_653 fi.ig.et_422
+ 0x03041ea4, 0x04000322, 0x045210a7, 0x1a1364ee, // ms.fi.nl_433 nl.fi.un_870 lt.ha.fi_532 lg.et.tl_422
+ 0x0e1806ad, 0x4a351308, 0x19170b55, 0x64003b0c, // de.ga.is_643 et.zu.yo_443 es.sr.gl_442 so.lg.un_530
+ // [4870]
+ 0x32291605, 0x0e6b1a5a, 0x10000a23, 0x1c3b1ea0, // hr.sl.bs_333 tl.ceb.is_553 mk.be.un_880 ms.so.id_322
+ 0x1a64115a, 0x08354aad, 0x1e1a110c, 0x0d001008, // ro.lg.tl_553 yo.zu.no_643 ro.tl.ms_543 lt.cs.un_430
+ 0x025208a0, 0x290d17a4, 0x6b1a110c, 0x3f1b21ad, // no.ha.da_322 sr.cs.sl_433 ro.tl.ceb_543 jw.tr.af_643
+ 0x1b000312, 0x6b1a350d, 0x1c2111a7, 0x211c35ee, // nl.tr.un_640 zu.tl.ceb_554 ro.jw.id_532 zu.id.jw_422
+ // [4880]
+ 0x6b112108, 0x25002802, 0x11006419, 0x111a640c, // jw.ro.ceb_443 sw.eu.un_220 lg.ro.un_750 lg.tl.ro_543
+ 0x13044aa4, 0x0e6421a9, 0x643b280e, 0x046864af, // yo.fi.et_433 jw.lg.is_544 sw.so.lg_555 lg.ig.fi_655
+ 0x3125110c, 0x1b3f030c, 0x1a0b6b04, 0x311135a4, // ro.eu.az_543 nl.af.tr_543 ceb.es.tl_332 zu.ro.az_433
+ 0x1c006e08, 0x03130604, 0x25005504, 0x08021ca0, // hmn.id.un_430 de.et.nl_332 rw.eu.un_320 id.da.no_322
+ // [4890]
+ 0x201b0e09, 0x311b1112, 0x111e520d, 0x11002504, // is.tr.sq_444 ro.tr.az_654 ha.ms.ro_554 eu.ro.un_320
+ 0x28070605, 0x03001705, 0x2506130c, 0x0a0208a4, // de.it.sw_333 sr.nl.un_330 et.de.eu_543 no.da.pt_433
+ 0x52002012, 0x64000104, 0x0f002a07, 0x06232511, // sq.ha.un_640 en.lg.un_320 mt.lv.un_420 eu.ca.de_653
+ 0x08320c07, 0x030a2560, 0x1c1b0ea4, 0x64550b08, // sv.bs.no_432 eu.pt.nl_664 is.tr.id_433 es.rw.lg_443
+ // [48a0]
+ 0x0000192d, 0x1a004a0c, 0x1b2a25a0, 0x200208a4, // gl.un.un_A00 yo.tl.un_530 eu.mt.tr_322 no.da.sq_433
+ 0x6b061fa7, 0x063f6413, 0x0b681805, 0x2a002807, // cy.de.ceb_532 lg.af.de_665 ga.ig.es_333 sw.mt.un_420
+ 0x201205af, 0x551a6b0c, 0x2d0d0b14, 0x6452210c, // fr.hu.sq_655 ceb.tl.rw_543 es.cs.sk_666 jw.ha.lg_543
+ 0x130520ad, 0x3f0123a7, 0x085507a0, 0x03160ea0, // sq.fr.et_643 ca.en.af_532 it.rw.no_322 is.hr.nl_322
+ // [48b0]
+ 0x16170dad, 0x0e270813, 0x0b2d0d14, 0x01006407, // cs.sr.hr_643 no.gd.is_665 cs.sk.es_666 lg.en.un_420
+ 0x13004a02, 0x030805a0, 0x1a201e07, 0x13002005, // yo.et.un_220 fr.no.nl_322 ms.sq.tl_432 sq.et.un_330
+ 0x3f2111a7, 0x35132811, 0x08250e04, 0x68005219, // ro.jw.af_532 sw.et.zu_653 is.eu.no_332 ha.ig.un_750
+ 0x0c552812, 0x1f353f12, 0x105525af, 0x1a316b09, // sw.rw.sv_654 af.zu.cy_654 eu.rw.lt_655 ceb.az.tl_444
+ // [48c0]
+ 0x68100f07, 0x2507130c, 0x256b290c, 0x0b000718, // lv.lt.ig_432 et.it.eu_543 sl.ceb.eu_543 it.es.un_740
+ 0x07286b08, 0x25106ba4, 0x06211e12, 0x0600210e, // ceb.sw.it_443 ceb.lt.eu_433 ms.jw.de_654 jw.de.un_550
+ 0x08006b04, 0x2b210612, 0x4a0b20af, 0x09532da4, // ceb.no.un_320 de.jw.vi_654 sq.es.yo_655 sk.ht.pl_433
+ 0x25002813, 0x32040f05, 0x2a04100c, 0x532a6ba0, // sw.eu.un_650 lv.fi.bs_333 lt.fi.mt_543 ceb.mt.ht_322
+ // [48d0]
+ 0x530f0405, 0x13256bac, 0x13042a0d, 0x0e00201b, // fi.lv.ht_333 ceb.eu.et_632 mt.fi.et_554 sq.is.un_770
+ 0x1e0425ad, 0x0f2853a4, 0x52002123, 0x1f000307, // eu.fi.ms_643 ht.sw.lv_433 jw.ha.un_880 nl.cy.un_420
+ 0x310c02ad, 0x321653ee, 0x1c121ea0, 0x080229ec, // da.sv.az_643 ht.hr.bs_422 ms.hu.id_322 sl.da.no_644
+ 0x061c21ad, 0x2100060c, 0x2100060e, 0x1c351e08, // jw.id.de_643 de.jw.un_530 de.jw.un_550 ms.zu.id_443
+ // [48e0]
+ 0x1e211cad, 0x55001a09, 0x3b3513ee, 0x530304ad, // id.jw.ms_643 tl.rw.un_440 et.zu.so_422 fi.nl.ht_643
+ 0x04523bad, 0x10032aaf, 0x250f4aa4, 0x1e002102, // so.ha.fi_643 mt.nl.lt_655 yo.lv.eu_433 jw.ms.un_220
+ 0x1f00090d, 0x0b6b4aee, 0x17162da4, 0x0d001614, // pl.cy.un_540 yo.ceb.es_422 sk.hr.sr_433 hr.cs.un_660
+ 0x2b182d07, 0x3b12350c, 0x02001e02, 0x11001907, // sk.ga.vi_432 zu.hu.so_543 ms.da.un_220 gl.ro.un_420
+ // [48f0]
+ 0x2d0d32a9, 0x21093ba0, 0x12002b0c, 0x6b6831ad, // bs.cs.sk_544 so.pl.jw_322 vi.hu.un_530 az.ig.ceb_643
+ 0x1104530c, 0x0e06035a, 0x31250fa9, 0x10040a0d, // ht.fi.ro_543 nl.de.is_553 lv.eu.az_544 mk.ru.be_554
+ 0x4a0b28a4, 0x190b11af, 0x2d0d0b0e, 0x08004a07, // sw.es.yo_433 ro.es.gl_655 es.cs.sk_555 yo.no.un_420
+ 0x0f00031a, 0x526b25ee, 0x12003f19, 0x02285514, // nl.lv.un_760 eu.ceb.ha_422 af.hu.un_750 rw.sw.da_666
+ // [4900]
+ 0x09291fa4, 0x25004a13, 0x080210a0, 0x026b08a9, // cy.sl.pl_433 yo.eu.un_650 lt.da.no_322 no.ceb.da_544
+ 0x07186e02, 0x1a6b0e07, 0x0223110c, 0x1108100c, // hmn.ga.it_222 is.ceb.tl_432 ro.ca.da_543 be.uk.ro_543
+ 0x0f102a13, 0x1800110c, 0x55041aee, 0x0f000621, // mt.lt.lv_665 ro.ga.un_530 tl.fi.rw_422 de.lv.un_860
+ 0x08071713, 0x041711ec, 0x4a042a0c, 0x231807ad, // sr.bg.uk_665 ro.sr.ru_644 mt.fi.yo_543 it.ga.ca_643
+ // [4910]
+ 0x0b000919, 0x180a2304, 0x19530b07, 0x0c0108a0, // pl.es.un_750 ca.pt.ga_332 es.ht.gl_432 no.en.sv_322
+ 0x55530708, 0x110c68ee, 0x6b2504ad, 0x5200251b, // it.ht.rw_443 ig.sv.ro_422 fi.eu.ceb_643 eu.ha.un_770
+ 0x051101a4, 0x062a08a4, 0x07000f1a, 0x23132513, // en.ro.fr_433 no.mt.de_433 lv.it.un_760 eu.et.ca_665
+ 0x25002009, 0x09001c2a, 0x08130e09, 0x03126e07, // sq.eu.un_440 mr.hi.un_970 is.et.no_444 hmn.hu.nl_432
+ // [4920]
+ 0x0405250d, 0x1218050c, 0x0f1a040c, 0x0c052305, // eu.fr.fi_554 fr.ga.hu_543 fi.tl.lv_543 ca.fr.sv_333
+ 0x04286b0d, 0x1a012aad, 0x04000608, 0x0d0b18a4, // ceb.sw.fi_554 mt.en.tl_643 de.fi.un_430 ga.es.cs_433
+ 0x1119520c, 0x02000b05, 0x3f002a0d, 0x0e002b13, // ha.gl.ro_543 es.da.un_330 mt.af.un_540 vi.is.un_650
+ 0x061152a4, 0x2a120ca4, 0x29182302, 0x00001924, // ha.ro.de_433 sv.hu.mt_433 ca.ga.sl_222 gl.un.un_900
+ // [4930]
+ 0x083f100b, 0x110f1305, 0x061308a4, 0x0f0603a0, // lt.af.no_542 et.lv.ro_333 no.et.de_433 nl.de.lv_322
+ 0x110e04ee, 0x0552280d, 0x120e180e, 0x521e28a4, // fi.is.ro_422 sw.ha.fr_554 ga.is.hu_555 sw.ms.ha_433
+ 0x0511230c, 0x4a12180c, 0x032852a0, 0x1c100f0c, // ca.ro.fr_543 ga.hu.yo_543 ha.sw.nl_322 lv.lt.id_543
+ 0x02110e0c, 0x05000e29, 0x0e121808, 0x0a680704, // is.ro.da_543 is.fr.un_960 ga.hu.is_443 it.ig.pt_332
+ // [4940]
+ 0x12041302, 0x20005208, 0x054a5312, 0x0a3553ad, // et.fi.hu_222 ha.sq.un_430 ht.yo.fr_654 ht.zu.pt_643
+ 0x07110413, 0x313555ad, 0x53054a0c, 0x190b1009, // ru.ro.bg_665 rw.zu.az_643 yo.fr.ht_543 lt.es.gl_444
+ 0x0a001308, 0x12002904, 0x292d0f0c, 0x1118010c, // et.pt.un_430 sl.hu.un_320 lv.sk.sl_543 en.ga.ro_543
+ 0x1e251cad, 0x0d00092a, 0x08040ca4, 0x0c0e13ac, // id.eu.ms_643 hi.ne.un_970 sv.fi.no_433 et.is.sv_632
+ // [4950]
+ 0x13006e07, 0x0f001702, 0x356407a4, 0x11201eee, // hmn.et.un_420 sr.lv.un_220 it.lg.zu_433 ms.sq.ro_422
+ 0x12002807, 0x1b00130c, 0x1100100c, 0x07350408, // sw.hu.un_420 et.tr.un_530 lt.ro.un_530 fi.zu.it_443
+ 0x2a190b05, 0x21002013, 0x0b1912ac, 0x190a0702, // es.gl.mt_333 sq.jw.un_650 hu.gl.es_632 it.pt.gl_222
+ 0x06001214, 0x131220a4, 0x12285508, 0x32160d14, // hu.de.un_660 sq.hu.et_433 rw.sw.hu_443 cs.hr.bs_666
+ // [4960]
+ 0x12001f0e, 0x53002913, 0x55311aa4, 0x3b002102, // cy.hu.un_550 sl.ht.un_650 tl.az.rw_433 jw.so.un_220
+ 0x0300060e, 0x0308010c, 0x29160ca6, 0x1b002302, // de.nl.un_550 en.no.nl_543 sv.hr.sl_521 ca.tr.un_220
+ 0x08020605, 0x063f03ad, 0x130c1012, 0x35002d08, // de.da.no_333 nl.af.de_643 lt.sv.et_654 sk.zu.un_430
+ 0x0c00132a, 0x042813ec, 0x0c3f1f5a, 0x06020508, // et.sv.un_970 et.sw.fi_644 cy.af.sv_553 fr.da.de_443
+ // [4970]
+ 0x13091c11, 0x12132aa0, 0x2b011b5a, 0x536431ad, // mr.hi.bh_653 mt.et.hu_322 tr.en.vi_553 az.lg.ht_643
+ 0x11100f05, 0x19003b02, 0x0217080c, 0x2a554a07, // lv.lt.ro_333 so.gl.un_220 no.sr.da_543 yo.rw.mt_432
+ 0x100e020c, 0x050601a7, 0x216468a4, 0x1b3106ec, // da.is.lt_543 en.de.fr_532 ig.lg.jw_433 de.az.tr_644
+ 0x64211c05, 0x2d0d1e02, 0x10000e21, 0x101c1ea6, // id.jw.lg_333 ms.cs.sk_222 is.lt.un_860 ms.id.lt_521
+ // [4980]
+ 0x040f21a4, 0x120f1304, 0x0802310c, 0x0c5204a9, // jw.lv.fi_433 et.lv.hu_332 az.da.no_543 fi.ha.sv_544
+ 0x050b070d, 0x07530413, 0x1f1108a4, 0x07170a02, // it.es.fr_554 fi.ht.it_665 no.ro.cy_433 mk.sr.bg_222
+ 0x1a002d1a, 0x201b130d, 0x530802a9, 0x0e063560, // sk.tl.un_760 et.tr.sq_554 da.no.ht_544 zu.de.is_664
+ 0x641b5208, 0x17001133, 0x273b180c, 0x0e1227a9, // ha.tr.lg_443 ro.sr.un_A70 ga.so.gd_543 gd.hu.is_544
+ // [4990]
+ 0x3b041112, 0x1f00110c, 0x3b1b2109, 0x52002802, // ro.fi.so_654 ro.cy.un_530 jw.tr.so_444 sw.ha.un_220
+ 0x1f002304, 0x53520413, 0x08352812, 0x28356460, // ca.cy.un_320 fi.ha.ht_665 sw.zu.no_654 lg.zu.sw_664
+ 0x19122dec, 0x212a1a04, 0x04181f08, 0x3b1b250c, // sk.hu.gl_644 tl.mt.jw_332 cy.ga.fi_443 eu.tr.so_543
+ 0x21003b2a, 0x53042108, 0x0c0e2aa7, 0x25000707, // so.jw.un_970 jw.fi.ht_443 mt.is.sv_532 it.eu.un_420
+ // [49a0]
+ 0x641a280c, 0x100a170c, 0x3f002113, 0x290e2d0c, // sw.tl.lg_543 sr.mk.be_543 jw.af.un_650 sk.is.sl_543
+ 0x08022905, 0x1e1f52a4, 0x131018a4, 0x072a550c, // sl.da.no_333 ha.cy.ms_433 ga.lt.et_433 rw.mt.it_543
+ 0x64552aaf, 0x27190a08, 0x3b046802, 0x2d29020e, // mt.rw.lg_655 pt.gl.gd_443 ig.fi.so_222 da.sl.sk_555
+ 0x2a00060c, 0x06250f13, 0x4a0455a6, 0x55641a08, // de.mt.un_530 lv.eu.de_665 rw.fi.yo_521 tl.lg.rw_443
+ // [49b0]
+ 0x190b0509, 0x16001a0c, 0x5528130b, 0x2d0d0eaf, // fr.es.gl_444 tl.hr.un_530 et.sw.rw_542 is.cs.sk_655
+ 0x2a3531ec, 0x1a311baf, 0x0e000311, 0x271835af, // az.zu.mt_644 tr.az.tl_655 nl.is.un_630 zu.ga.gd_655
+ 0x0e292d13, 0x320f17a4, 0x120e0aa4, 0x17041111, // sk.sl.is_665 sr.lv.bs_433 pt.is.hu_433 ro.ru.sr_653
+ 0x1200092b, 0x28063505, 0x3f0c1fee, 0x125364a0, // pl.hu.un_980 zu.de.sw_333 cy.sv.af_422 lg.ht.hu_322
+ // [49c0]
+ 0x131b1207, 0x25521a0c, 0x043f4aad, 0x3f2503af, // hu.tr.et_432 tl.ha.eu_543 yo.af.fi_643 nl.eu.af_655
+ 0x3b122011, 0x20000521, 0x111e1c09, 0x0f293ba4, // sq.hu.so_653 fr.sq.un_860 id.ms.ro_444 so.sl.lv_433
+ 0x160f55a4, 0x081113ad, 0x05005212, 0x53070560, // rw.lv.hr_433 et.ro.no_643 ha.fr.un_640 fr.it.ht_664
+ 0x350e130c, 0x0f00290c, 0x16293ba4, 0x0a001319, // et.is.zu_543 sl.lv.un_530 so.sl.hr_433 et.pt.un_750
+ // [49d0]
+ 0x0b1819a4, 0x2300110d, 0x5202310b, 0x11202313, // gl.ga.es_433 ro.ca.un_540 az.da.ha_542 ca.sq.ro_665
+ 0x0802040c, 0x321655a4, 0x102a1e0b, 0x07002304, // fi.da.no_543 rw.hr.bs_433 ms.mt.lt_542 ca.it.un_320
+ 0x23002a04, 0x101716ac, 0x11000919, 0x2900550c, // mt.ca.un_320 hr.sr.lt_632 pl.ro.un_750 rw.sl.un_530
+ 0x0f092905, 0x061a09a7, 0x282535a7, 0x2011010c, // sl.pl.lv_333 pl.tl.de_532 zu.eu.sw_532 en.ro.sq_543
+ // [49e0]
+ 0x131104ec, 0x0813680c, 0x2d0d04af, 0x17110460, // fi.ro.et_644 ig.et.no_543 fi.cs.sk_655 ru.ro.sr_664
+ 0x6425130c, 0x64092aad, 0x0e520813, 0x110f0404, // et.eu.lg_543 mt.pl.lg_643 no.ha.is_665 fi.lv.ro_332
+ 0x20112313, 0x1e005307, 0x321753ee, 0x64095511, // ca.ro.sq_665 ht.ms.un_420 ht.sr.bs_422 rw.pl.lg_653
+ 0x1e1c28a4, 0x25315307, 0x18000308, 0x19004a04, // sw.id.ms_433 ht.az.eu_432 nl.ga.un_430 yo.gl.un_320
+ // [49f0]
+ 0x1c201eec, 0x2d0d1c05, 0x68190a13, 0x1a2a53af, // ms.sq.id_644 id.cs.sk_333 pt.gl.ig_665 ht.mt.tl_655
+ 0x19110b08, 0x014a03a0, 0x1e52280c, 0x014a08a4, // es.ro.gl_443 nl.yo.en_322 sw.ha.ms_543 no.yo.en_433
+ 0x0e0825ee, 0x082b0ea4, 0x103228a4, 0x0327180c, // eu.no.is_422 is.vi.no_433 sw.bs.lt_433 ga.gd.nl_543
+ 0x08005204, 0x0b000507, 0x27033f05, 0x31201ba4, // ha.no.un_320 fr.es.un_420 af.nl.gd_333 tr.sq.az_433
+ // [4a00]
+ 0x27185214, 0x201e52ad, 0x20002302, 0x35003b14, // ha.ga.gd_666 ha.ms.sq_643 ca.sq.un_220 so.zu.un_660
+ 0x640c080c, 0x52001b09, 0x130401a9, 0x32001618, // no.sv.lg_543 tr.ha.un_440 en.fi.et_544 hr.bs.un_740
+ 0x55033f60, 0x4a001214, 0x53002802, 0x070f110e, // af.nl.rw_664 hu.yo.un_660 sw.ht.un_220 ro.lv.it_555
+ 0x2d0d4a0c, 0x12313f07, 0x07030fee, 0x04102104, // yo.cs.sk_543 af.az.hu_432 lv.nl.it_422 jw.lt.fi_332
+ // [4a10]
+ 0x21000707, 0x6b55280c, 0x122903a0, 0x131e1c0c, // it.jw.un_420 sw.rw.ceb_543 nl.sl.hu_322 id.ms.et_543
+ 0x080252a0, 0x09003f04, 0x1c5523a4, 0x08020da9, // ha.da.no_322 af.pl.un_320 ca.rw.id_433 cs.da.no_544
+ 0x2700071b, 0x251a01af, 0x5200070e, 0x272118a0, // it.gd.un_770 en.tl.eu_655 it.ha.un_550 ga.jw.gd_322
+ 0x18001223, 0x0b11235a, 0x12003b13, 0x52000712, // ur.ar.un_880 ca.ro.es_553 so.hu.un_650 it.ha.un_640
+ // [4a20]
+ 0x1a003b05, 0x060c20a4, 0x2025115a, 0x230c0e12, // so.tl.un_330 sq.sv.de_433 ro.eu.sq_553 is.sv.ca_654
+ 0x210b190c, 0x3b001919, 0x550921ec, 0x0425230c, // gl.es.jw_543 gl.so.un_750 jw.pl.rw_644 ca.eu.fi_543
+ 0x27185207, 0x23040aa0, 0x08064a08, 0x25000104, // ha.ga.gd_432 pt.fi.ca_322 yo.de.no_443 en.eu.un_320
+ 0x3f051312, 0x2a280707, 0x28040aa4, 0x041329ee, // et.fr.af_654 it.sw.mt_432 pt.fi.sw_433 sl.et.fi_422
+ // [4a30]
+ 0x18002702, 0x522a07a7, 0x2a0609ad, 0x2d001f08, // gd.ga.un_220 it.mt.ha_532 pl.de.mt_643 cy.sk.un_430
+ 0x0e004a08, 0x35686b12, 0x29000b09, 0x3f092a12, // yo.is.un_430 ceb.ig.zu_654 es.sl.un_440 mt.pl.af_654
+ 0x17110a0e, 0x640107ee, 0x29006429, 0x092a3f12, // mk.ro.sr_555 it.en.lg_422 lg.sl.un_960 af.mt.pl_654
+ 0x09006804, 0x3f31100c, 0x033f6412, 0x05033f08, // ig.pl.un_320 lt.az.af_543 lg.af.nl_654 af.nl.fr_443
+ // [4a40]
+ 0x4a3568ec, 0x102a07a0, 0x08040aa6, 0x12041aad, // ig.zu.yo_644 it.mt.lt_322 mk.ru.uk_521 tl.fi.hu_643
+ 0x53211ca4, 0x0f131bad, 0x136b11ad, 0x04281a0c, // id.jw.ht_433 tr.et.lv_643 ro.ceb.et_643 tl.sw.fi_543
+ 0x2b004a19, 0x18010ca7, 0x1e002813, 0x1c132107, // yo.vi.un_750 sv.en.ga_532 sw.ms.un_650 jw.et.id_432
+ 0x3500010d, 0x09001329, 0x3f003504, 0x12000422, // en.zu.un_540 bh.hi.un_960 zu.af.un_320 fi.hu.un_870
+ // [4a50]
+ 0x0a640702, 0x0f102a0e, 0x07001712, 0x161b1a08, // it.lg.pt_222 mt.lt.lv_555 sr.bg.un_640 tl.tr.hr_443
+ 0x08020302, 0x13282507, 0x11170a13, 0x041a1ea4, // nl.da.no_222 eu.sw.et_432 mk.sr.ro_665 ms.tl.fi_433
+ 0x0a00040e, 0x286825ee, 0x130425ee, 0x1f001912, // ru.mk.un_550 eu.ig.sw_422 eu.fi.et_422 gl.cy.un_640
+ 0x02133b05, 0x01082a07, 0x19230ba9, 0x04006b2a, // so.et.da_333 mt.no.en_432 es.ca.gl_544 ceb.fi.un_970
+ // [4a60]
+ 0x19001216, 0x3f10040c, 0x04000735, 0x170911a4, // hu.gl.un_720 fi.lt.af_543 bg.ru.un_A90 ro.pl.sr_433
+ 0x1f353f02, 0x0d092dee, 0x3b005302, 0x17250d0c, // af.zu.cy_222 sk.pl.cs_422 ht.so.un_220 cs.eu.sr_543
+ 0x315235a6, 0x190b2304, 0x0e000705, 0x270e08a7, // zu.ha.az_521 ca.es.gl_332 it.is.un_330 no.is.gd_532
+ 0x0700250d, 0x3b062bee, 0x19005304, 0x03643f05, // eu.it.un_540 vi.de.so_422 ht.gl.un_320 af.lg.nl_333
+ // [4a70]
+ 0x03023f5a, 0x28130460, 0x0a002a12, 0x1c00350d, // af.da.nl_553 fi.et.sw_664 mt.pt.un_640 zu.id.un_540
+ 0x2d0d3bee, 0x3b1a3513, 0x1a00350c, 0x0a002a0d, // so.cs.sk_422 zu.tl.so_665 zu.tl.un_530 mt.pt.un_540
+ 0x283b3560, 0x050235ee, 0x17080208, 0x18010502, // zu.so.sw_664 zu.da.fr_422 da.no.sr_443 fr.en.ga_222
+ 0x182007af, 0x0c131e07, 0x0b001819, 0x311711ec, // it.sq.ga_655 ms.et.sv_432 ga.es.un_750 ro.sr.az_644
+ // [4a80]
+ 0x11003b1e, 0x645235a4, 0x3b005520, 0x08003f0e, // so.ro.un_830 zu.ha.lg_433 rw.so.un_850 af.no.un_550
+ 0x06013505, 0x03002504, 0x321c2508, 0x0b2319a7, // zu.en.de_333 eu.nl.un_320 eu.id.bs_443 gl.ca.es_532
+ 0x3f3110ec, 0x2d0d120c, 0x522520a6, 0x2a190709, // lt.az.af_644 hu.cs.sk_543 sq.eu.ha_521 it.gl.mt_444
+ 0x1a6b3b07, 0x0f003209, 0x3b351ca0, 0x013f06a4, // so.ceb.tl_432 bs.lv.un_440 id.zu.so_322 de.af.en_433
+ // [4a90]
+ 0x0f04050c, 0x18001c02, 0x190b0a0d, 0x2809070b, // fr.fi.lv_543 id.ga.un_220 pt.es.gl_554 it.pl.sw_542
+ 0x0b000513, 0x190b055a, 0x0b005304, 0x08003f1b, // fr.es.un_650 fr.es.gl_553 ht.es.un_320 af.no.un_770
+ 0x3f000f19, 0x17160fee, 0x073b6404, 0x041a2107, // lv.af.un_750 lv.hr.sr_422 lg.so.it_332 jw.tl.fi_432
+ 0x0e002012, 0x311003a7, 0x011853a0, 0x09006818, // sq.is.un_640 nl.lt.az_532 ht.ga.en_322 ig.pl.un_740
+ // [4aa0]
+ 0x0e326405, 0x55000a08, 0x2b1f0755, 0x090f04a9, // lg.bs.is_333 pt.rw.un_430 it.cy.vi_442 fi.lv.pl_544
+ 0x06040f14, 0x03065504, 0x12001902, 0x07100f12, // lv.fi.de_666 rw.de.nl_332 gl.hu.un_220 lv.lt.it_654
+ 0x130e0f0c, 0x190b25a4, 0x3f2a0bad, 0x0f2d1007, // lv.is.et_543 eu.es.gl_433 es.mt.af_643 lt.sk.lv_432
+ 0x3f000f04, 0x033f25ec, 0x060e08a4, 0x310418a4, // lv.af.un_320 eu.af.nl_644 no.is.de_433 ga.fi.az_433
+ // [4ab0]
+ 0x6400030d, 0x133f3ba0, 0x0e04090c, 0x190d35ec, // nl.lg.un_540 so.af.et_322 pl.fi.is_543 zu.cs.gl_644
+ 0x040f52ad, 0x0c0f03a4, 0x101c0404, 0x27282008, // ha.lv.fi_643 nl.lv.sv_433 fi.id.lt_332 sq.sw.gd_443
+ 0x35053f07, 0x32002708, 0x3b061908, 0x193555ec, // af.fr.zu_432 gd.bs.un_430 gl.de.so_443 rw.zu.gl_644
+ 0x0e3b2708, 0x09182705, 0x2928550c, 0x113b10ec, // gd.so.is_443 gd.ga.pl_333 rw.sw.sl_543 lt.so.ro_644
+ // [4ac0]
+ 0x0f096ba6, 0x55086ba4, 0x18000720, 0x03000611, // ceb.pl.lv_521 ceb.no.rw_433 it.ga.un_850 de.nl.un_630
+ 0x0a180709, 0x18271c07, 0x162d32a0, 0x07182708, // it.ga.pt_444 id.gd.ga_432 bs.sk.hr_322 gd.ga.it_443
+ 0x4a176407, 0x10170413, 0x64190b08, 0x091e1cad, // lg.sr.yo_432 ru.sr.be_665 es.gl.lg_443 id.ms.pl_643
+ 0x213b0911, 0x0a271055, 0x190b2805, 0x162953a4, // pl.so.jw_653 lt.gd.pt_442 sw.es.gl_333 ht.sl.hr_433
+ // [4ad0]
+ 0x18192708, 0x6400071a, 0x2800271b, 0x1b002829, // gd.gl.ga_443 it.lg.un_760 gd.sw.un_770 sw.tr.un_960
+ 0x112855af, 0x070511ac, 0x1c521e0c, 0x053f5505, // rw.sw.ro_655 ro.fr.it_632 ms.ha.id_543 rw.af.fr_333
+ 0x230e0bee, 0x0f1a09a7, 0x2021520c, 0x07230608, // es.is.ca_422 pl.tl.lv_532 ha.jw.sq_543 de.ca.it_443
+ 0x07000120, 0x0a170814, 0x644a5312, 0x52001122, // en.it.un_850 uk.sr.mk_666 ht.yo.lg_654 ro.ha.un_870
+ // [4ae0]
+ 0x0c0a11a4, 0x32281bee, 0x524a1aa7, 0x1e010c02, // ro.pt.sv_433 tr.sw.bs_422 tl.yo.ha_532 sv.en.ms_222
+ 0x0700102b, 0x0e002d08, 0x03002102, 0x2a030605, // be.bg.un_980 sk.is.un_430 jw.nl.un_220 de.nl.mt_333
+ 0x0e005204, 0x23072a08, 0x0f080ea7, 0x1f0b1a07, // ha.is.un_320 mt.it.ca_443 is.no.lv_532 tl.es.cy_432
+ 0x100a07a6, 0x21231ca0, 0x25091b07, 0x19100ba0, // it.pt.lt_521 id.ca.jw_322 tr.pl.eu_432 es.lt.gl_322
+ // [4af0]
+ 0x3b0e0b04, 0x0d0e2da4, 0x0d0e0ba0, 0x170752ee, // es.is.so_332 sk.is.cs_433 es.is.cs_322 ha.it.sr_422
+ 0x271223ee, 0x52005314, 0x20190b08, 0x20001807, // ca.hu.gd_422 ht.ha.un_660 es.gl.sq_443 ga.sq.un_420
+ 0x070b3104, 0x2d0d2a02, 0x19120b12, 0x200c1a08, // az.es.it_332 mt.cs.sk_222 es.hu.gl_654 tl.sv.sq_443
+ 0x122152ee, 0x0b0a2da7, 0x080e1212, 0x080402a4, // ha.jw.hu_422 sk.pt.es_532 hu.is.no_654 da.fi.no_433
+ // [4b00]
+ 0x21035207, 0x0a086405, 0x25200b07, 0x105207ac, // ha.nl.jw_432 lg.no.pt_333 es.sq.eu_432 it.ha.lt_632
+ 0x0717100b, 0x3b202811, 0x55002102, 0x28553ba0, // be.sr.bg_542 sw.sq.so_653 jw.rw.un_220 so.rw.sw_322
+ 0x0e000405, 0x0b030e55, 0x190c0bad, 0x28000c07, // fi.is.un_330 is.nl.es_442 es.sv.gl_643 sv.sw.un_420
+ 0x17282d07, 0x310f2504, 0x093f0aa7, 0x2000290c, // sk.sw.sr_432 eu.lv.az_332 pt.af.pl_532 sl.sq.un_530
+ // [4b10]
+ 0x1b042a12, 0x062a35a4, 0x551b520c, 0x07000a20, // mt.fi.tr_654 zu.mt.de_433 ha.tr.rw_543 mk.bg.un_850
+ 0x6400250d, 0x0413680d, 0x25000b14, 0x01031fa7, // eu.lg.un_540 ig.et.fi_554 es.eu.un_660 cy.nl.en_532
+ 0x0725555a, 0x28000313, 0x55642513, 0x64555204, // rw.eu.it_553 nl.sw.un_650 eu.lg.rw_665 ha.rw.lg_332
+ 0x200c08ee, 0x07005214, 0x1c11310c, 0x0d002904, // no.sv.sq_422 ha.it.un_660 az.ro.id_543 sl.cs.un_320
+ // [4b20]
+ 0x0f004a07, 0x3f1352ee, 0x64255512, 0x1f052308, // yo.lv.un_420 ha.et.af_422 rw.eu.lg_654 ca.fr.cy_443
+ 0x3f076407, 0x16002a04, 0x55250da4, 0x532a07a4, // lg.it.af_432 mt.hr.un_320 cs.eu.rw_433 it.mt.ht_433
+ 0x283b68ac, 0x1b1029ec, 0x23192512, 0x1703110c, // ig.so.sw_632 sl.lt.tr_644 eu.gl.ca_654 ro.nl.sr_543
+ 0x280a6808, 0x554a64af, 0x525528ad, 0x0c64550d, // ig.pt.sw_443 lg.yo.rw_655 sw.rw.ha_643 rw.lg.sv_554
+ // [4b30]
+ 0x2a090ba0, 0x01051307, 0x0a180e04, 0x13090d08, // es.pl.mt_322 et.fr.en_432 is.ga.pt_332 ne.hi.bh_443
+ 0x2d3b0d0c, 0x6b3b5512, 0x160d32a7, 0x2868010c, // cs.so.sk_543 rw.so.ceb_654 bs.cs.hr_532 en.ig.sw_543
+ 0x27001e04, 0x55041308, 0x0e002804, 0x02355212, // ms.gd.un_320 et.fi.rw_443 sw.is.un_320 ha.zu.da_654
+ 0x09681fa0, 0x2100200b, 0x1b3525a0, 0x16001f04, // cy.ig.pl_322 sq.jw.un_520 eu.zu.tr_322 cy.hr.un_320
+ // [4b40]
+ 0x55006412, 0x01081fa0, 0x2a0712ec, 0x270907a0, // lg.rw.un_640 cy.no.en_322 hu.it.mt_644 it.pl.gd_322
+ 0x11053509, 0x27351807, 0x1b00280b, 0x10000a1a, // zu.fr.ro_444 ga.zu.gd_432 sw.tr.un_520 mk.be.un_760
+ 0x12080ea4, 0x23043504, 0x020b0a05, 0x3f3508a4, // is.no.hu_433 zu.fi.ca_332 pt.es.da_333 no.zu.af_433
+ 0x0b0813a9, 0x0a0c5211, 0x0f02290c, 0x0c1f0ea7, // et.no.es_544 ha.sv.pt_653 sl.da.lv_543 is.cy.sv_532
+ // [4b50]
+ 0x551a6412, 0x2d0d10a9, 0x293517ee, 0x6b551b08, // lg.tl.rw_654 lt.cs.sk_544 sr.zu.sl_422 tr.rw.ceb_443
+ 0x0b2d05a4, 0x2a052107, 0x55282aa4, 0x0713110c, // fr.sk.es_433 jw.fr.mt_432 mt.sw.rw_433 ro.et.it_543
+ 0x2013110c, 0x11122112, 0x6431550c, 0x110f29a4, // ro.et.sq_543 jw.hu.ro_654 rw.az.lg_543 sl.lv.ro_433
+ 0x130223a7, 0x55641ca0, 0x52213ba4, 0x0e03250c, // ca.da.et_532 id.lg.rw_322 so.jw.ha_433 eu.nl.is_543
+ // [4b60]
+ 0x17000707, 0x0f00130c, 0x1b1325a9, 0x120e08a4, // it.sr.un_420 et.lv.un_530 eu.et.tr_544 no.is.hu_433
+ 0x29081ba7, 0x10001b04, 0x231121a4, 0x3500070d, // tr.no.sl_532 tr.lt.un_320 jw.ro.ca_433 it.zu.un_540
+ 0x031b2512, 0x04100a5a, 0x101b20ec, 0x01251804, // eu.tr.nl_654 mk.be.ru_553 sq.tr.lt_644 ga.eu.en_332
+ 0x29071705, 0x05132a07, 0x1e061213, 0x25031b09, // sr.it.sl_333 mt.et.fr_432 hu.de.ms_665 tr.nl.eu_444
+ // [4b70]
+ 0x530602a0, 0x2a002d08, 0x3f0103ee, 0x1c0f3512, // da.de.ht_322 sk.mt.un_430 nl.en.af_422 zu.lv.id_654
+ 0x0d001114, 0x20001b21, 0x090e100c, 0x18002a04, // ro.cs.un_660 tr.sq.un_860 lt.is.pl_543 mt.ga.un_320
+ 0x04003504, 0x521f3baf, 0x11281360, 0x030e1b04, // zu.fi.un_320 so.cy.ha_655 et.sw.ro_664 tr.is.nl_332
+ 0x1900060b, 0x270b23a4, 0x2d0d180c, 0x25232aaf, // de.gl.un_520 ca.es.gd_433 ga.cs.sk_543 mt.ca.eu_655
+ // [4b80]
+ 0x1a122104, 0x18012aad, 0x1c0853ee, 0x1f180f5a, // jw.hu.tl_332 mt.en.ga_643 ht.no.id_422 lv.ga.cy_553
+ 0x21002019, 0x0e18270d, 0x02003202, 0x1c282107, // sq.jw.un_750 gd.ga.is_554 bs.da.un_220 jw.sw.id_432
+ 0x07006b21, 0x162935ee, 0x0b0a2314, 0x111b310d, // ceb.it.un_860 zu.sl.hr_422 ca.pt.es_666 az.tr.ro_554
+ 0x286b52a0, 0x04110805, 0x323b21ad, 0x18190a09, // ha.ceb.sw_322 uk.ro.ru_333 jw.so.bs_643 pt.gl.ga_444
+ // [4b90]
+ 0x35001605, 0x2a5220ac, 0x1e5528ee, 0x1a6b1b60, // hr.zu.un_330 sq.ha.mt_632 sw.rw.ms_422 tr.ceb.tl_664
+ 0x041306ad, 0x06001902, 0x131a2012, 0x033b3f0c, // de.et.fi_643 gl.de.un_220 sq.tl.et_654 af.so.nl_543
+ 0x116b64ee, 0x08000723, 0x23190da0, 0x0e001904, // lg.ceb.ro_422 bg.uk.un_880 cs.gl.ca_322 gl.is.un_320
+ 0x2d00120c, 0x0c006b04, 0x1225270d, 0x1800351a, // hu.sk.un_530 ceb.sv.un_320 gd.eu.hu_554 zu.ga.un_760
+ // [4ba0]
+ 0x0308350c, 0x2835185a, 0x32082aa0, 0x1a550455, // zu.no.nl_543 ga.zu.sw_553 mt.no.bs_322 fi.rw.tl_442
+ 0x35001819, 0x2b000808, 0x1e1c020e, 0x182827af, // ga.zu.un_750 no.vi.un_430 da.id.ms_555 gd.sw.ga_655
+ 0x291617a9, 0x0d006b07, 0x31646ba0, 0x321716ec, // sr.hr.sl_544 ceb.cs.un_420 ceb.lg.az_322 hr.sr.bs_644
+ 0x05004a12, 0x28001e13, 0x28642107, 0x21283b0c, // yo.fr.un_640 ms.sw.un_650 jw.lg.sw_432 so.sw.jw_543
+ // [4bb0]
+ 0x64040b02, 0x0e0c210d, 0x18352155, 0x233511ad, // es.fi.lg_222 jw.sv.is_554 jw.zu.ga_442 ro.zu.ca_643
+ 0x685564a9, 0x64215508, 0x2a006e08, 0x271e1cac, // lg.rw.ig_544 rw.jw.lg_443 hmn.mt.un_430 id.ms.gd_632
+ 0x033f3105, 0x0d1029a4, 0x29171614, 0x02001f21, // az.af.nl_333 sl.lt.cs_433 hr.sr.sl_666 cy.da.un_860
+ 0x18003521, 0x10001e0c, 0x2d004a07, 0x01000802, // zu.ga.un_860 ms.lt.un_530 yo.sk.un_420 no.en.un_220
+ // [4bc0]
+ 0x2b000122, 0x356b1f0b, 0x1c1e2112, 0x2a002705, // en.vi.un_870 cy.ceb.zu_542 jw.ms.id_654 gd.mt.un_330
+ 0x131228a4, 0x19230b0c, 0x1f3568a0, 0x4a271ba4, // sw.hu.et_433 es.ca.gl_543 ig.zu.cy_322 tr.gd.yo_433
+ 0x0c1f3b0e, 0x071c05a0, 0x190a2509, 0x1c27200c, // so.cy.sv_555 fr.id.it_322 eu.pt.gl_444 sq.gd.id_543
+ 0x11053f0c, 0x03006404, 0x0b122305, 0x3b232104, // af.fr.ro_543 lg.nl.un_320 ca.hu.es_333 jw.ca.so_332
+ // [4bd0]
+ 0x23001205, 0x0e081309, 0x190b20ee, 0x056b0c08, // hu.ca.un_330 et.no.is_444 sq.es.gl_422 sv.ceb.fr_443
+ 0x04136807, 0x646813a7, 0x6b2a070c, 0x18000305, // ig.et.fi_432 et.ig.lg_532 it.mt.ceb_543 nl.ga.un_330
+ 0x190a2307, 0x3f036411, 0x180135a7, 0x23132807, // ca.pt.gl_432 lg.nl.af_653 zu.en.ga_532 sw.et.ca_432
+ 0x05110309, 0x64134a5a, 0x28072a0c, 0x04212804, // nl.ro.fr_444 yo.et.lg_553 mt.it.sw_543 sw.jw.fi_332
+ // [4be0]
+ 0x1c003202, 0x113128a4, 0x0e6455ec, 0x06001104, // bs.id.un_220 sw.az.ro_433 rw.lg.is_644 ro.de.un_320
+ 0x171632ac, 0x06012007, 0x352a2012, 0x6b0105a0, // bs.hr.sr_632 sq.en.de_432 sq.mt.zu_654 fr.en.ceb_322
+ 0x6e1b53a9, 0x3f000913, 0x55001f02, 0x190b0505, // ht.tr.hmn_544 pl.af.un_650 cy.rw.un_220 fr.es.gl_333
+ 0x28686412, 0x645328ec, 0x21016ba0, 0x080c12a4, // lg.ig.sw_654 sw.ht.lg_644 ceb.en.jw_322 hu.sv.no_433
+ // [4bf0]
+ 0x27000104, 0x072028ee, 0x2a00351a, 0x35181c07, // en.gd.un_320 sw.sq.it_422 zu.mt.un_760 id.ga.zu_432
+ 0x09000413, 0x3f6b5507, 0x070a64ad, 0x28000b02, // fi.pl.un_650 rw.ceb.af_432 lg.pt.it_643 es.sw.un_220
+ 0x0f290707, 0x13001a04, 0x06190bec, 0x350968a9, // it.sl.lv_432 tl.et.un_320 es.gl.de_644 ig.pl.zu_544
+ 0x17000a02, 0x112123a4, 0x2a07180c, 0x022825a4, // mk.sr.un_220 ca.jw.ro_433 ga.it.mt_543 eu.sw.da_433
+
+ // [4c00]
+ 0x0c0e3fee, 0x02005305, 0x1a640914, 0x102313a9, // af.is.sv_422 ht.da.un_330 pl.lg.tl_666 et.ca.lt_544
+ 0x3f1053ec, 0x1b000305, 0x521e0f13, 0x03073fa9, // ht.lt.af_644 nl.tr.un_330 lv.ms.ha_665 af.it.nl_544
+ 0x1a5568a0, 0x0d1c09ac, 0x190b0aa6, 0x0a000507, // ig.rw.tl_322 hi.mr.ne_632 pt.es.gl_521 fr.pt.un_420
+ 0x0a0810ad, 0x53102509, 0x55005221, 0x311b55a9, // be.uk.mk_643 eu.lt.ht_444 ha.rw.un_860 rw.tr.az_544
+ // [4c10]
+ 0x082a25ad, 0x0e001f22, 0x29080ca4, 0x3f27100b, // eu.mt.no_643 cy.is.un_870 sv.no.sl_433 lt.gd.af_542
+ 0x4a001a12, 0x04532507, 0x0a004a05, 0x281225af, // tl.yo.un_640 eu.ht.fi_432 yo.pt.un_330 eu.hu.sw_655
+ 0x0c060404, 0x28006423, 0x25006422, 0x100f2508, // fi.de.sv_332 lg.sw.un_880 lg.eu.un_870 eu.lv.lt_443
+ 0x3b2a6812, 0x231b3ba4, 0x11310d07, 0x201b6e08, // ig.mt.so_654 so.tr.ca_433 cs.az.ro_432 hmn.tr.sq_443
+ // [4c20]
+ 0x08311804, 0x10286413, 0x1b293102, 0x08020a09, // ga.az.no_332 lg.sw.lt_665 az.sl.tr_222 pt.da.no_444
+ 0x32251f08, 0x52064a08, 0x08252707, 0x1f3105a4, // cy.eu.bs_443 yo.de.ha_443 gd.eu.no_432 fr.az.cy_433
+ 0x3f021bad, 0x1c642809, 0x2a1f05ec, 0x1f002104, // tr.da.af_643 sw.lg.id_444 fr.cy.mt_644 jw.cy.un_320
+ 0x01550507, 0x1b07350c, 0x111335ec, 0x1e251ba4, // fr.rw.en_432 zu.it.tr_543 zu.et.ro_644 tr.eu.ms_433
+ // [4c30]
+ 0x090d2da9, 0x521f645a, 0x13351108, 0x10000619, // sk.cs.pl_544 lg.cy.ha_553 ro.zu.et_443 de.lt.un_750
+ 0x3b080260, 0x1700082c, 0x28271808, 0x13000409, // da.no.so_664 uk.sr.un_990 ga.gd.sw_443 fi.et.un_440
+ 0x171608a0, 0x32133508, 0x12000212, 0x08000a09, // no.hr.sr_322 zu.et.bs_443 da.hu.un_640 mk.uk.un_440
+ 0x210b1ca4, 0x2818010c, 0x283564a4, 0x35070508, // id.es.jw_433 en.ga.sw_543 lg.zu.sw_433 fr.it.zu_443
+ // [4c40]
+ 0x13000314, 0x1f010611, 0x555364a7, 0x05002b0c, // nl.et.un_660 de.en.cy_653 lg.ht.rw_532 vi.fr.un_530
+ 0x531135a4, 0x1a643bee, 0x01002302, 0x13003507, // zu.ro.ht_433 so.lg.tl_422 ca.en.un_220 zu.et.un_420
+ 0x130711a4, 0x11005207, 0x101e250c, 0x130711ec, // ro.it.et_433 ha.ro.un_420 eu.ms.lt_543 ro.it.et_644
+ 0x114a3508, 0x0d003202, 0x28005302, 0x2500290d, // zu.yo.ro_443 bs.cs.un_220 ht.sw.un_220 sl.eu.un_540
+ // [4c50]
+ 0x55001c04, 0x13031a14, 0x1a133b60, 0x08060c09, // id.rw.un_320 tl.nl.et_666 so.et.tl_664 sv.de.no_444
+ 0x0a080460, 0x6b133b04, 0x130e08a9, 0x3b2a25a9, // ru.uk.mk_664 so.et.ceb_332 no.is.et_544 eu.mt.so_544
+ 0x03642502, 0x211c2a05, 0x1a6b35a4, 0x19206408, // eu.lg.nl_222 mt.id.jw_333 zu.ceb.tl_433 lg.sq.gl_443
+ 0x13091ca4, 0x063153a4, 0x2b250cad, 0x20321ba0, // mr.hi.bh_433 ht.az.de_433 sv.eu.vi_643 tr.bs.sq_322
+ // [4c60]
+ 0x1f101ea9, 0x190b06ba, 0x0b0a2302, 0x044a550c, // ms.lt.cy_544 de.es.gl_843 ca.pt.es_222 rw.yo.fi_543
+ 0x25000813, 0x68005322, 0x100a1108, 0x351355a7, // no.eu.un_650 ht.ig.un_870 ro.mk.be_443 rw.et.zu_532
+ 0x1f001018, 0x18272b5a, 0x3b2720ec, 0x0710080c, // lt.cy.un_740 vi.gd.ga_553 sq.gd.so_644 uk.be.bg_543
+ 0x31000e13, 0x13110ca0, 0x311b4aa7, 0x13086eec, // is.az.un_650 sv.ro.et_322 yo.tr.az_532 hmn.no.et_644
+ // [4c70]
+ 0x05001704, 0x20002a02, 0x10001f1b, 0x27185204, // sr.fr.un_320 mt.sq.un_220 cy.lt.un_770 ha.ga.gd_332
+ 0x3f201ca0, 0x10001f21, 0x050a1fa0, 0x234a0b02, // id.sq.af_322 cy.lt.un_860 cy.pt.fr_322 es.yo.ca_222
+ 0x0a005305, 0x6b000120, 0x1f00182c, 0x28002312, // ht.pt.un_330 en.ceb.un_850 ga.cy.un_990 ca.sw.un_640
+ 0x1812270c, 0x64251307, 0x3b110407, 0x0e522105, // gd.hu.ga_543 et.eu.lg_432 fi.ro.so_432 jw.ha.is_333
+ // [4c80]
+ 0x0c120207, 0x0f120eee, 0x25001f08, 0x251b520c, // da.hu.sv_432 is.hu.lv_422 cy.eu.un_430 ha.tr.eu_543
+ 0x0c000307, 0x1e231fa0, 0x28001f07, 0x05351308, // nl.sv.un_420 cy.ca.ms_322 cy.sw.un_420 et.zu.fr_443
+ 0x6b554a55, 0x04130d0c, 0x251b13ec, 0x3b640707, // yo.rw.ceb_442 cs.et.fi_543 et.tr.eu_644 it.lg.so_432
+ 0x324a3504, 0x13040a0c, 0x4a000520, 0x28006818, // zu.yo.bs_332 pt.fi.et_543 fr.yo.un_850 ig.sw.un_740
+ // [4c90]
+ 0x13114aa0, 0x284a55a4, 0x27011807, 0x18310812, // yo.ro.et_322 rw.yo.sw_433 ga.en.gd_432 no.az.ga_654
+ 0x2b030508, 0x31200f05, 0x2a00551a, 0x135568a0, // fr.nl.vi_443 lv.sq.az_333 rw.mt.un_760 ig.rw.et_322
+ 0x01003b09, 0x01131ba4, 0x25000508, 0x640f5512, // so.en.un_440 tr.et.en_433 fr.eu.un_430 rw.lv.lg_654
+ 0x2a180604, 0x6b520c07, 0x01000b05, 0x033555af, // de.ga.mt_332 sv.ha.ceb_432 es.en.un_330 rw.zu.nl_655
+ // [4ca0]
+ 0x352528ee, 0x18001f20, 0x061c0502, 0x32000612, // sw.eu.zu_422 cy.ga.un_850 fr.id.de_222 de.bs.un_640
+ 0x55002a1a, 0x3b211aec, 0x2a0764ec, 0x35000d13, // mt.rw.un_760 tl.jw.so_644 lg.it.mt_644 cs.zu.un_650
+ 0x3b205208, 0x211c3104, 0x1c0921ee, 0x1e120607, // ha.sq.so_443 az.id.jw_332 jw.pl.id_422 de.hu.ms_432
+ 0x2100251b, 0x205511a4, 0x2a00111b, 0x291c1a0c, // eu.jw.un_770 ro.rw.sq_433 ro.mt.un_770 tl.id.sl_543
+ // [4cb0]
+ 0x1a2a11ec, 0x35040805, 0x28551b0d, 0x20525508, // ro.mt.tl_644 no.fi.zu_333 tr.rw.sw_554 rw.ha.sq_443
+ 0x1c111ea4, 0x52112a08, 0x213b5508, 0x3b531808, // ms.ro.id_433 mt.ro.ha_443 rw.so.jw_443 ga.ht.so_443
+ 0x1a0b3b09, 0x55011fee, 0x28521108, 0x205211a4, // so.es.tl_444 cy.en.rw_422 ro.ha.sw_443 ro.ha.sq_433
+ 0x010b0a09, 0x01313bee, 0x55281108, 0x20213b0c, // pt.es.en_444 so.az.en_422 ro.sw.rw_443 so.jw.sq_543
+ // [4cc0]
+ 0x0e000702, 0x31211b0c, 0x16202912, 0x2a000307, // it.is.un_220 tr.jw.az_543 sl.sq.hr_654 nl.mt.un_420
+ 0x17005221, 0x106b1a0d, 0x551128ad, 0x250668ee, // ha.sr.un_860 tl.ceb.lt_554 sw.ro.rw_643 ig.de.eu_422
+ 0x25001108, 0x04112aa4, 0x1c6b1e0c, 0x102a1160, // ro.eu.un_430 mt.ro.fi_433 ms.ceb.id_543 ro.mt.lt_664
+ 0x640a3505, 0x03002307, 0x201a110c, 0x041a110c, // zu.pt.lg_333 ca.nl.un_420 ro.tl.sq_543 ro.tl.fi_543
+ // [4cd0]
+ 0x3b003122, 0x021008a4, 0x55682107, 0x0e1729ac, // az.so.un_870 no.lt.da_433 jw.ig.rw_432 sl.sr.is_632
+ 0x3f32290c, 0x1c231107, 0x10020f13, 0x16351aad, // sl.bs.af_543 ro.ca.id_432 lv.da.lt_665 tl.zu.hr_643
+ 0x172007a9, 0x026b3f07, 0x1e1c1309, 0x23530560, // it.sq.sr_544 af.ceb.da_432 et.id.ms_444 fr.ht.ca_664
+ 0x0e290f0c, 0x230e3f04, 0x29001e21, 0x0e006419, // lv.sl.is_543 af.is.ca_332 ms.sl.un_860 lg.is.un_750
+ // [4ce0]
+ 0x0a230c04, 0x23290bee, 0x182027ad, 0x28135213, // sv.ca.pt_332 es.sl.ca_422 gd.sq.ga_643 ha.et.sw_665
+ 0x23005305, 0x07002504, 0x3b001e13, 0x120364ee, // ht.ca.un_330 eu.it.un_320 ms.so.un_650 lg.nl.hu_422
+ 0x1b0a4a60, 0x523b0b55, 0x13001602, 0x32003105, // yo.pt.tr_664 es.so.ha_442 hr.et.un_220 az.bs.un_330
+ 0x3568640c, 0x64001f02, 0x0a00640c, 0x01002d04, // lg.ig.zu_543 cy.lg.un_220 lg.pt.un_530 sk.en.un_320
+ // [4cf0]
+ 0x08021b02, 0x3b041309, 0x171321ad, 0x2a0918ee, // tr.da.no_222 et.fi.so_444 jw.et.sr_643 ga.pl.mt_422
+ 0x07170405, 0x12005204, 0x0f001323, 0x18051108, // ru.sr.bg_333 ha.hu.un_320 et.lv.un_880 ro.fr.ga_443
+ 0x190b27a0, 0x1e04640c, 0x06001008, 0x211c4aad, // gd.es.gl_322 lg.fi.ms_543 lt.de.un_430 yo.id.jw_643
+ 0x100407ec, 0x322129a0, 0x5300680c, 0x121a27a7, // bg.ru.be_644 sl.jw.bs_322 ig.ht.un_530 gd.tl.hu_532
+ // [4d00]
+ 0x352168a9, 0x2b00180c, 0x07000114, 0x550a1bee, // ig.jw.zu_544 ga.vi.un_530 en.it.un_660 tr.pt.rw_422
+ 0x2d3b1ea6, 0x0d001a04, 0x3b1f1e05, 0x55000d08, // ms.so.sk_521 tl.cs.un_320 ms.cy.so_333 cs.rw.un_430
+ 0x0501110c, 0x64001f19, 0x181127af, 0x252023a0, // ro.en.fr_543 cy.lg.un_750 gd.ro.ga_655 ca.sq.eu_322
+ 0x1c1e3f0c, 0x6423350d, 0x25556808, 0x122952a6, // af.ms.id_543 zu.ca.lg_554 ig.rw.eu_443 ha.sl.hu_521
+ // [4d10]
+ 0x06002704, 0x0b1f5208, 0x285255a0, 0x55020812, // gd.de.un_320 ha.cy.es_443 rw.ha.sw_322 no.da.rw_654
+ 0x0455280d, 0x3f001e07, 0x1728290d, 0x0d003520, // sw.rw.fi_554 ms.af.un_420 sl.sw.sr_554 zu.cs.un_850
+ 0x3f000d04, 0x01681b0d, 0x042111a4, 0x10270408, // cs.af.un_320 tr.ig.en_554 ro.jw.fi_433 fi.gd.lt_443
+ 0x11282009, 0x13525513, 0x2a0955a7, 0x041c550c, // sq.sw.ro_444 rw.ha.et_665 rw.pl.mt_532 rw.id.fi_543
+ // [4d20]
+ 0x646b550c, 0x02045208, 0x3555280c, 0x080255ee, // rw.ceb.lg_543 ha.fi.da_443 sw.rw.zu_543 rw.da.no_422
+ 0x523b21a4, 0x31113bec, 0x190b35ad, 0x17551e0d, // jw.so.ha_433 so.ro.az_644 zu.es.gl_643 ms.rw.sr_554
+ 0x080205a4, 0x553f1f07, 0x070408ac, 0x13210c05, // fr.da.no_433 cy.af.rw_432 uk.ru.bg_632 sv.jw.et_333
+ 0x190a23a4, 0x100c08af, 0x0821350c, 0x29072aee, // ca.pt.gl_433 no.sv.lt_655 zu.jw.no_543 mt.it.sl_422
+ // [4d30]
+ 0x1113350c, 0x1812210b, 0x190b1aee, 0x25350308, // zu.et.ro_543 fa.ur.ar_542 tl.es.gl_422 nl.zu.eu_443
+ 0x2d521aee, 0x291705ad, 0x0c2925a9, 0x64001902, // tl.ha.sk_422 fr.sr.sl_643 eu.sl.sv_544 gl.lg.un_220
+ 0x551a2812, 0x1a641c0c, 0x11351fee, 0x28016405, // sw.tl.rw_654 id.lg.tl_543 cy.zu.ro_422 lg.en.sw_333
+ 0x6b1a2814, 0x35003b0d, 0x0f0c08af, 0x3b1052a9, // sw.tl.ceb_666 so.zu.un_540 no.sv.lv_655 ha.lt.so_544
+ // [4d40]
+ 0x09352513, 0x1a3b5212, 0x1e1c2155, 0x0a072d05, // eu.zu.pl_665 ha.so.tl_654 jw.id.ms_442 sk.it.pt_333
+ 0x293525a9, 0x190b55ec, 0x35645509, 0x170a2d07, // eu.zu.sl_544 rw.es.gl_644 rw.lg.zu_444 sk.pt.sr_432
+ 0x0c0f040c, 0x19003b19, 0x0a3b190c, 0x0e1f0c0d, // fi.lv.sv_543 so.gl.un_750 gl.so.pt_543 sv.cy.is_554
+ 0x082a07a7, 0x1c351ea4, 0x28321613, 0x21006e0d, // it.mt.no_532 ms.zu.id_433 hr.bs.sw_665 hmn.jw.un_540
+ // [4d50]
+ 0x1b64350d, 0x31000a19, 0x020c08a9, 0x3b281e0c, // zu.lg.tr_554 pt.az.un_750 no.sv.da_544 ms.sw.so_543
+ 0x17003512, 0x0a0b190b, 0x6b184a11, 0x321729af, // zu.sr.un_640 gl.es.pt_542 yo.ga.ceb_653 sl.sr.bs_655
+ 0x083f0c0c, 0x084a6b02, 0x20000807, 0x35000b04, // sv.af.no_543 ceb.yo.no_222 no.sq.un_420 es.zu.un_320
+ 0x050b0a04, 0x0d000221, 0x3f0d11ee, 0x012523ad, // pt.es.fr_332 da.cs.un_860 ro.cs.af_422 ca.eu.en_643
+ // [4d60]
+ 0x35002818, 0x130c010b, 0x28310b05, 0x3b012aee, // sw.zu.un_740 en.sv.et_542 es.az.sw_333 mt.en.so_422
+ 0x2a3b11ad, 0x010309a6, 0x11350502, 0x121311ec, // ro.so.mt_643 pl.nl.en_521 fr.zu.ro_222 ro.et.hu_644
+ 0x0500271b, 0x1f312308, 0x05200107, 0x52270c12, // gd.fr.un_770 ca.az.cy_443 en.sq.fr_432 sv.gd.ha_654
+ 0x10131108, 0x1e005202, 0x00001b06, 0x286b35a0, // ro.et.lt_443 ha.ms.un_220 tr.un.un_400 zu.ceb.sw_322
+ // [4d70]
+ 0x1053680d, 0x35520eee, 0x1e0413a0, 0x68086b0c, // ig.ht.lt_554 is.ha.zu_422 et.fi.ms_322 ceb.no.ig_543
+ 0x55116807, 0x251211ad, 0x3b001113, 0x3f001108, // ig.ro.rw_432 ro.hu.eu_643 ro.so.un_650 ro.af.un_430
+ 0x200c080c, 0x1e2a11ec, 0x0d0a0ea9, 0x21005307, // no.sv.sq_543 ro.mt.ms_644 is.pt.cs_544 ht.jw.un_420
+ 0x1327110c, 0x23101307, 0x0a251108, 0x062a3f55, // ro.gd.et_543 et.lt.ca_432 ro.eu.pt_443 af.mt.de_442
+ // [4d80]
+ 0x0c0a080b, 0x2109550d, 0x0e283508, 0x020c2305, // no.pt.sv_542 rw.pl.jw_554 zu.sw.is_443 ca.sv.da_333
+ 0x32002a19, 0x19110a05, 0x17001c04, 0x16520c07, // mt.bs.un_750 pt.ro.gl_333 id.sr.un_320 sv.ha.hr_432
+ 0x160b1fa7, 0x080e3f0c, 0x0a53030c, 0x212a1c12, // cy.es.hr_532 af.is.no_543 nl.ht.pt_543 id.mt.jw_654
+ 0x3b081ca7, 0x102017ee, 0x5500121b, 0x32001b0d, // id.no.so_532 sr.sq.lt_422 hu.rw.un_770 tr.bs.un_540
+ // [4d90]
+ 0x16553508, 0x280f520c, 0x1752290c, 0x072327a0, // zu.rw.hr_443 ha.lv.sw_543 sl.ha.sr_543 gd.ca.it_322
+ 0x202832a0, 0x190b11ec, 0x0f105208, 0x0800031b, // bs.sw.sq_322 ro.es.gl_644 ha.lt.lv_443 nl.no.un_770
+ 0x04000835, 0x0b292d12, 0x53551bec, 0x1b553504, // uk.ru.un_A90 sk.sl.es_654 tr.rw.ht_644 zu.rw.tr_332
+ 0x0d132d12, 0x21003108, 0x10280da4, 0x0b090709, // sk.et.cs_654 az.jw.un_430 cs.sw.lt_433 it.pl.es_444
+ // [4da0]
+ 0x09122707, 0x3b002104, 0x1725280c, 0x17001e07, // gd.hu.pl_432 jw.so.un_320 sw.eu.sr_543 ms.sr.un_420
+ 0x04282560, 0x28555312, 0x32171b0c, 0x28001905, // eu.sw.fi_664 ht.rw.sw_654 tr.sr.bs_543 gl.sw.un_330
+ 0x0c0f1005, 0x0d290912, 0x01190b13, 0x081218af, // lt.lv.sv_333 pl.sl.cs_654 es.gl.en_665 ga.hu.no_655
+ 0x25640c55, 0x25211ca9, 0x641a4aaf, 0x00000737, // sv.lg.eu_442 id.jw.eu_544 yo.tl.lg_655 it.un.un_B00
+ // [4db0]
+ 0x01000707, 0x2a1107a7, 0x1e1c29af, 0x0b070aee, // it.en.un_420 it.ro.mt_532 sl.id.ms_655 pt.it.es_422
+ 0x09006e04, 0x2b00181b, 0x050820af, 0x31000712, // hmn.pl.un_320 ga.vi.un_770 sq.no.fr_655 it.az.un_640
+ 0x31041ba9, 0x3b131f0c, 0x130b6bec, 0x202a13a4, // tr.fi.az_544 cy.et.so_543 ceb.es.et_644 et.mt.sq_433
+ 0x060c1205, 0x6b001334, 0x08006b13, 0x6b130d11, // hu.sv.de_333 et.ceb.un_A80 ceb.no.un_650 cs.et.ceb_653
+ // [4dc0]
+ 0x64352108, 0x1e3b1312, 0x352955a4, 0x0a07310c, // jw.zu.lg_443 et.so.ms_654 rw.sl.zu_433 az.it.pt_543
+ 0x0964550c, 0x1b23310c, 0x080213ee, 0x21283507, // rw.lg.pl_543 az.ca.tr_543 et.da.no_422 zu.sw.jw_432
+ 0x06000504, 0x6b112004, 0x106428a4, 0x1a6452a7, // fr.de.un_320 sq.ro.ceb_332 sw.lg.lt_433 ha.lg.tl_532
+ 0x55311ba4, 0x23181f12, 0x0c356b55, 0x1a006b02, // tr.az.rw_433 cy.ga.ca_654 ceb.zu.sv_442 ceb.tl.un_220
+ // [4dd0]
+ 0x12001c02, 0x10000104, 0x200f10af, 0x08120c02, // id.hu.un_220 en.lt.un_320 lt.lv.sq_655 sv.hu.no_222
+ 0x19001813, 0x2800680c, 0x6b000d13, 0x13006b13, // ga.gl.un_650 ig.sw.un_530 cs.ceb.un_650 ceb.et.un_650
+ 0x1a1255ee, 0x190b13a0, 0x53124a02, 0x08001304, // rw.hu.tl_422 et.es.gl_322 yo.hu.ht_222 et.no.un_320
+ 0x0a002a08, 0x1a002102, 0x35002307, 0x0a11175a, // mt.pt.un_430 jw.tl.un_220 ca.zu.un_420 sr.ro.mk_553
+ // [4de0]
+ 0x0e1308ec, 0x09000e09, 0x050c11a4, 0x136b2aa0, // no.et.is_644 is.pl.un_440 ro.sv.fr_433 mt.ceb.et_322
+ 0x211c01a9, 0x2a5204ad, 0x2100170c, 0x11521a0d, // en.id.jw_544 fi.ha.mt_643 sr.jw.un_530 tl.ha.ro_554
+ 0x0b004a1a, 0x0911230b, 0x082302ee, 0x6b521aa9, // yo.es.un_760 ca.ro.pl_542 da.ca.no_422 tl.ha.ceb_544
+ 0x21232aee, 0x520e2805, 0x4a005213, 0x21551ea9, // mt.ca.jw_422 sw.is.ha_333 ha.yo.un_650 ms.rw.jw_544
+ // [4df0]
+ 0x090b2508, 0x28684a0d, 0x550b1bee, 0x28104aec, // eu.es.pl_443 yo.ig.sw_554 tr.es.rw_422 yo.lt.sw_644
+ 0x08031208, 0x1b55310e, 0x4a55520c, 0x012305ad, // hu.nl.no_443 az.rw.tr_555 ha.rw.yo_543 fr.ca.en_643
+ 0x23070207, 0x1b312012, 0x21006812, 0x522555a4, // da.it.ca_432 sq.az.tr_654 ig.jw.un_640 rw.eu.ha_433
+ 0x2d1218af, 0x522168ec, 0x1e212a12, 0x31003b2b, // ga.hu.sk_655 ig.jw.ha_644 mt.jw.ms_654 so.az.un_980
+ // [4e00]
+ 0x130f0409, 0x21553bad, 0x521b0807, 0x20282755, // fi.lv.et_444 so.rw.jw_643 no.tr.ha_432 gd.sw.sq_442
+ 0x1b3504a4, 0x4a1b0f11, 0x3f001f19, 0x31001e07, // fi.zu.tr_433 lv.tr.yo_653 cy.af.un_750 ms.az.un_420
+ 0x32286b07, 0x00001337, 0x23072013, 0x2a230f04, // ceb.sw.bs_432 bh.un.un_B00 sq.it.ca_665 lv.ca.mt_332
+ 0x12061b04, 0x0617130e, 0x20003b0d, 0x551c1ba4, // tr.de.hu_332 et.sr.de_555 so.sq.un_540 tr.id.rw_433
+ // [4e10]
+ 0x250413af, 0x553552a0, 0x31002a07, 0x161335a4, // et.fi.eu_655 ha.zu.rw_322 mt.az.un_420 zu.et.hr_433
+ 0x01062705, 0x5300200c, 0x1c0e1204, 0x020301a4, // gd.de.en_333 sq.ht.un_530 hu.is.id_332 en.nl.da_433
+ 0x0f005202, 0x06005218, 0x282a18a4, 0x0d321708, // ha.lv.un_220 ha.de.un_740 ga.mt.sw_433 sr.bs.cs_443
+ 0x0d001e02, 0x25125555, 0x06200c04, 0x35555212, // ms.cs.un_220 rw.hu.eu_442 sv.sq.de_332 ha.rw.zu_654
+ // [4e20]
+ 0x250f13ee, 0x2300290c, 0x05002714, 0x0711050c, // et.lv.eu_422 sl.ca.un_530 gd.fr.un_660 fr.ro.it_543
+ 0x21550a04, 0x07050c05, 0x3b255208, 0x051a02ee, // pt.rw.jw_332 sv.fr.it_333 ha.eu.so_443 da.tl.fr_422
+ 0x23002107, 0x0f00061a, 0x0a002305, 0x68281e12, // jw.ca.un_420 de.lv.un_760 ca.pt.un_330 ms.sw.ig_654
+ 0x06020fad, 0x16000507, 0x2a000c07, 0x03001c02, // lv.da.de_643 fr.hr.un_420 sv.mt.un_420 id.nl.un_220
+ // [4e30]
+ 0x0e002a07, 0x25296808, 0x2000090e, 0x1e1c0cee, // mt.is.un_420 ig.sl.eu_443 pl.sq.un_550 sv.id.ms_422
+ 0x68002a0c, 0x080e10a4, 0x321703a0, 0x13000621, // mt.ig.un_530 lt.is.no_433 nl.sr.bs_322 de.et.un_860
+ 0x2d180ba9, 0x0c0308a9, 0x06310f02, 0x6b000608, // es.ga.sk_544 no.nl.sv_544 lv.az.de_222 de.ceb.un_430
+ 0x0603010c, 0x02060107, 0x271101a9, 0x250803a0, // en.nl.de_543 en.de.da_432 en.ro.gd_544 nl.no.eu_322
+ // [4e40]
+ 0x054a09a9, 0x05000213, 0x2b0121ee, 0x27181fad, // pl.yo.fr_544 da.fr.un_650 jw.en.vi_422 cy.ga.gd_643
+ 0x19113b05, 0x02042ba0, 0x25054aa7, 0x1f00030d, // so.ro.gl_333 vi.fi.da_322 yo.fr.eu_532 nl.cy.un_540
+ 0x2a08120e, 0x1b3b31ec, 0x050d01ec, 0x6b1f1a5a, // hu.no.mt_555 az.so.tr_644 en.cs.fr_644 tl.cy.ceb_553
+ 0x0e001119, 0x080235a4, 0x033f21af, 0x04111ba4, // ro.is.un_750 zu.da.no_433 jw.af.nl_655 tr.ro.fi_433
+ // [4e50]
+ 0x3f100a07, 0x522855ec, 0x031c1e0c, 0x0c031907, // pt.lt.af_432 rw.sw.ha_644 ms.id.nl_543 gl.nl.sv_432
+ 0x133b6405, 0x27201f0c, 0x030c19ee, 0x0c045212, // lg.so.et_333 cy.sq.gd_543 gl.sv.nl_422 ha.fi.sv_654
+ 0x64006821, 0x310453ec, 0x06210eee, 0x0464130c, // ig.lg.un_860 ht.fi.az_644 is.jw.de_422 et.lg.fi_543
+ 0x1a00532a, 0x01030faf, 0x0e000a19, 0x052923a0, // ht.tl.un_970 lv.nl.en_655 pt.is.un_750 ca.sl.fr_322
+ // [4e60]
+ 0x2d0d1ea4, 0x68536b0c, 0x03270613, 0x35251c12, // ms.cs.sk_433 ceb.ht.ig_543 de.gd.nl_665 id.eu.zu_654
+ 0x12000b14, 0x23013f0c, 0x043b2707, 0x18000a18, // es.hu.un_660 af.en.ca_543 gd.so.fi_432 pt.ga.un_740
+ 0x0e181912, 0x55350612, 0x2000270e, 0x08021b05, // gl.ga.is_654 de.zu.rw_654 gd.sq.un_550 tr.da.no_333
+ 0x524a20a4, 0x29535513, 0x25681309, 0x29005212, // sq.yo.ha_433 rw.ht.sl_665 et.ig.eu_444 ha.sl.un_640
+ // [4e70]
+ 0x68005312, 0x2d535505, 0x1312530b, 0x21250c08, // ht.ig.un_640 rw.ht.sk_333 ht.hu.et_542 sv.eu.jw_443
+ 0x11000b08, 0x1b006b19, 0x03000714, 0x2d0f0d0c, // es.ro.un_430 ceb.tr.un_750 it.nl.un_660 cs.lv.sk_543
+ 0x111707af, 0x0a5355a4, 0x092025a9, 0x211b020c, // bg.sr.ro_655 rw.ht.pt_433 eu.sq.pl_544 da.tr.jw_543
+ 0x07002a07, 0x286420af, 0x13324a02, 0x20000c1a, // mt.it.un_420 sq.lg.sw_655 yo.bs.et_222 sv.sq.un_760
+ // [4e80]
+ 0x0a16230c, 0x64682109, 0x182b28a0, 0x313f0fad, // ca.hr.pt_543 jw.ig.lg_444 sw.vi.ga_322 lv.af.az_643
+ 0x2021130c, 0x32000f19, 0x4a00120d, 0x3f080f04, // et.jw.sq_543 lv.bs.un_750 hu.yo.un_540 lv.no.af_332
+ 0x18003f04, 0x0a006b0b, 0x55001e08, 0x29001c02, // af.ga.un_320 ceb.pt.un_520 ms.rw.un_430 id.sl.un_220
+ 0x020608a9, 0x1c130d12, 0x080221ee, 0x27006b34, // no.de.da_544 ne.bh.mr_654 jw.da.no_422 ceb.gd.un_A80
+ // [4e90]
+ 0x123501a9, 0x0c270805, 0x283518a9, 0x2b35050c, // en.zu.hu_544 no.gd.sv_333 ga.zu.sw_544 fr.zu.vi_543
+ 0x3f1f0e60, 0x1c131eac, 0x1f002714, 0x21041eee, // is.cy.af_664 ms.et.id_632 gd.cy.un_660 ms.fi.jw_422
+ 0x6b030ca0, 0x05202b04, 0x13352aee, 0x2300040d, // sv.nl.ceb_322 vi.sq.fr_332 mt.zu.et_422 fi.ca.un_540
+ 0x0d180aaf, 0x20002122, 0x171b2304, 0x11001c09, // pt.ga.cs_655 jw.sq.un_870 ca.tr.sr_332 id.ro.un_440
+ // [4ea0]
+ 0x0d3119ee, 0x11002a0c, 0x6b20230c, 0x080212ee, // gl.az.cs_422 mt.ro.un_530 ca.sq.ceb_543 hu.da.no_422
+ 0x292a0dee, 0x1f0827ac, 0x1b003b0e, 0x1b2a04ad, // cs.mt.sl_422 gd.no.cy_632 so.tr.un_550 fi.mt.tr_643
+ 0x1b002a19, 0x07005504, 0x041711a6, 0x04641804, // mt.tr.un_750 rw.it.un_320 ro.sr.ru_521 ga.lg.fi_332
+ 0x35002b07, 0x550728af, 0x0d00550d, 0x20551702, // vi.zu.un_420 sw.it.rw_655 rw.cs.un_540 sr.rw.sq_222
+ // [4eb0]
+ 0x350364ec, 0x09001a04, 0x2d0d0aee, 0x18003507, // lg.nl.zu_644 tl.pl.un_320 pt.cs.sk_422 zu.ga.un_420
+ 0x55641a13, 0x35533105, 0x20006413, 0x350d64ad, // tl.lg.rw_665 az.ht.zu_333 lg.sq.un_650 lg.cs.zu_643
+ 0x09000513, 0x351a13af, 0x3b04200c, 0x352564ec, // fr.pl.un_650 et.tl.zu_655 sq.fi.so_543 lg.eu.zu_644
+ 0x180452a0, 0x1e1064ad, 0x041b35a9, 0x03110208, // ha.fi.ga_322 lg.lt.ms_643 zu.tr.fi_544 da.ro.nl_443
+ // [4ec0]
+ 0x2d060dad, 0x1100531b, 0x1c121eaf, 0x0e2a23a7, // cs.de.sk_643 ht.ro.un_770 ms.hu.id_655 ca.mt.is_532
+ 0x271813a4, 0x296416ee, 0x5300190d, 0x04120a04, // et.ga.gd_433 hr.lg.sl_422 gl.ht.un_540 pt.hu.fi_332
+ 0x2d2916a7, 0x352532ee, 0x11002908, 0x0a0813ee, // hr.sl.sk_532 bs.eu.zu_422 sl.ro.un_430 et.no.pt_422
+ 0x1e1a53a4, 0x03120aad, 0x1a352811, 0x183527a7, // ht.tl.ms_433 pt.hu.nl_643 sw.zu.tl_653 gd.zu.ga_532
+ // [4ed0]
+ 0x20001c08, 0x10003f13, 0x07170802, 0x0f286412, // id.sq.un_430 af.lt.un_650 uk.sr.bg_222 lg.sw.lv_654
+ 0x04643512, 0x08111711, 0x190a2da4, 0x0a111304, // zu.lg.fi_654 sr.ro.uk_653 sk.pt.gl_433 et.ro.pt_332
+ 0x05232b08, 0x292d08a0, 0x2d0d13a0, 0x6b3f0312, // vi.ca.fr_443 no.sk.sl_322 et.cs.sk_322 nl.af.ceb_654
+ 0x05101fec, 0x08030611, 0x0e1c1aa4, 0x68350d07, // cy.lt.fr_644 de.nl.no_653 tl.id.is_433 cs.zu.ig_432
+ // [4ee0]
+ 0x21521b09, 0x1f09350c, 0x02080e55, 0x032d20a4, // tr.ha.jw_444 zu.pl.cy_543 is.no.da_442 sq.sk.nl_433
+ 0x173129a4, 0x211820af, 0x0e3553a9, 0x12552107, // sl.az.sr_433 sq.ga.jw_655 ht.zu.is_544 jw.rw.hu_432
+ 0x32292da0, 0x53350a08, 0x072a3505, 0x06552805, // sk.sl.bs_322 pt.zu.ht_443 zu.mt.it_333 sw.rw.de_333
+ 0x2a001304, 0x1b060c12, 0x07003502, 0x2d0d21a4, // et.mt.un_320 sv.de.tr_654 zu.it.un_220 jw.cs.sk_433
+ // [4ef0]
+ 0x216b52a0, 0x2800210d, 0x0b1f23af, 0x291353a0, // ha.ceb.jw_322 jw.sw.un_540 ca.cy.es_655 ht.et.sl_322
+ 0x20001222, 0x64212305, 0x122a0408, 0x201f0107, // hu.sq.un_870 ca.jw.lg_333 fi.mt.hu_443 en.cy.sq_432
+ 0x031e010c, 0x250b01ee, 0x20131e0c, 0x530913a9, // en.ms.nl_543 en.es.eu_422 ms.et.sq_543 et.pl.ht_544
+ 0x0800210d, 0x2d003b22, 0x552d0314, 0x52000112, // jw.no.un_540 so.sk.un_870 nl.sk.rw_666 en.ha.un_640
+ // [4f00]
+ 0x174a52af, 0x061f18a4, 0x2d0d35ee, 0x201164a0, // ha.yo.sr_655 ga.cy.de_433 zu.cs.sk_422 lg.ro.sq_322
+ 0x20005521, 0x1000080d, 0x08002022, 0x21014a07, // rw.sq.un_860 uk.be.un_540 sq.no.un_870 yo.en.jw_432
+ 0x68355512, 0x165504ee, 0x55000b09, 0x160e04a4, // rw.zu.ig_654 fi.rw.hr_422 es.rw.un_440 fi.is.hr_433
+ 0x522a10ad, 0x2b002511, 0x13000f02, 0x280b55a4, // lt.mt.ha_643 eu.vi.un_630 lv.et.un_220 rw.es.sw_433
+ // [4f10]
+ 0x0c0809af, 0x1c3f2807, 0x5301280c, 0x321752a0, // pl.no.sv_655 sw.af.id_432 sw.en.ht_543 ha.sr.bs_322
+ 0x072d2907, 0x20002107, 0x07001321, 0x3125280c, // sl.sk.it_432 jw.sq.un_420 et.it.un_860 sw.eu.az_543
+ 0x165228a4, 0x021b17a0, 0x071f2904, 0x1b1a6b0d, // sw.ha.hr_433 sr.tr.da_322 sl.cy.it_332 ceb.tl.tr_554
+ 0x011311a0, 0x6e003f04, 0x050e1fee, 0x32031e07, // ro.et.en_322 af.hmn.un_320 cy.is.fr_422 ms.nl.bs_432
+ // [4f20]
+ 0x1c531f04, 0x21352812, 0x0b231fec, 0x131f31a9, // cy.ht.id_332 sw.zu.jw_654 cy.ca.es_644 az.cy.et_544
+ 0x2b006b07, 0x04002507, 0x522125a0, 0x1a1b6bad, // ceb.vi.un_420 eu.fi.un_420 eu.jw.ha_322 ceb.tr.tl_643
+ 0x1c3b27a4, 0x04251ca7, 0x19272d07, 0x35133113, // gd.so.id_433 id.eu.fi_532 sk.gd.gl_432 az.et.zu_665
+ 0x31005321, 0x132506a4, 0x1a215312, 0x12251b08, // ht.az.un_860 de.eu.et_433 ht.jw.tl_654 tr.eu.hu_443
+ // [4f30]
+ 0x032a130c, 0x1a3b0ba0, 0x18000804, 0x12200607, // et.mt.nl_543 es.so.tl_322 no.ga.un_320 de.sq.hu_432
+ 0x27130405, 0x1f1a1bee, 0x0c060ea0, 0x01004a02, // fi.et.gd_333 tr.tl.cy_422 is.de.sv_322 yo.en.un_220
+ 0x16000607, 0x251064a4, 0x09000c04, 0x0a000d12, // de.hr.un_420 lg.lt.eu_433 sv.pl.un_320 cs.pt.un_640
+ 0x080c020d, 0x182b01ad, 0x20003504, 0x27181ca0, // da.sv.no_554 en.vi.ga_643 zu.sq.un_320 id.ga.gd_322
+ // [4f40]
+ 0x1c3b1eaf, 0x07042a60, 0x23000504, 0x0b000e05, // ms.so.id_655 mt.fi.it_664 fr.ca.un_320 is.es.un_330
+ 0x2012130c, 0x2d0d0aa4, 0x53006b0b, 0x080e0413, // et.hu.sq_543 pt.cs.sk_433 ceb.ht.un_520 fi.is.no_665
+ 0x080253ec, 0x0d000a0e, 0x104a2008, 0x25070ea0, // ht.da.no_644 pt.cs.un_550 sq.yo.lt_443 is.it.eu_322
+ 0x1b001a02, 0x64553505, 0x081b040c, 0x020c52a7, // tl.tr.un_220 zu.rw.lg_333 fi.tr.no_543 ha.sv.da_532
+ // [4f50]
+ 0x13201f12, 0x190a18a6, 0x1000270e, 0x0d071304, // cy.sq.et_654 ga.pt.gl_521 gd.lt.un_550 et.it.cs_332
+ 0x0f030ea7, 0x02292d08, 0x280e13ad, 0x3b00682a, // is.nl.lv_532 sk.sl.da_443 et.is.sw_643 ig.so.un_970
+ 0x1f2701a4, 0x04050c11, 0x182327a4, 0x290d1f04, // en.gd.cy_433 sv.fr.fi_653 gd.ca.ga_433 cy.cs.sl_332
+ 0x120306ee, 0x21040204, 0x08060c02, 0x0c02035a, // de.nl.hu_422 da.fi.jw_332 sv.de.no_222 nl.da.sv_553
+ // [4f60]
+ 0x29090f0e, 0x0c021f07, 0x04030611, 0x02001f11, // lv.pl.sl_555 cy.da.sv_432 de.nl.fi_653 cy.da.un_630
+ 0x522d04ac, 0x35000e09, 0x3f080f07, 0x0a003508, // fi.sk.ha_632 is.zu.un_440 lv.no.af_432 zu.pt.un_430
+ 0x21531bad, 0x080e2a09, 0x0c231fa4, 0x1e0e1ca0, // tr.ht.jw_643 mt.is.no_444 cy.ca.sv_433 id.is.ms_322
+ 0x32021fee, 0x080e64a0, 0x1b531a0c, 0x17110aa7, // cy.da.bs_422 lg.is.no_322 tl.ht.tr_543 mk.ro.sr_532
+ // [4f70]
+ 0x2d0d4aec, 0x12001a12, 0x0e182dad, 0x0f002704, // yo.cs.sk_644 tl.hu.un_640 sk.ga.is_643 gd.lv.un_320
+ 0x1f001e04, 0x190a11ee, 0x0e003521, 0x1200210e, // ms.cy.un_320 ro.pt.gl_422 zu.is.un_860 fa.ur.un_550
+ 0x190b4aa0, 0x01001e02, 0x0c0e0308, 0x190b4a09, // yo.es.gl_322 ms.en.un_220 nl.is.sv_443 yo.es.gl_444
+ 0x12005202, 0x17001e04, 0x02031304, 0x200c11ee, // ha.hu.un_220 ms.sr.un_320 et.nl.da_332 ro.sv.sq_422
+ // [4f80]
+ 0x552028a4, 0x020f080c, 0x2902030c, 0x0a11040c, // sw.sq.rw_433 no.lv.da_543 nl.da.sl_543 ru.ro.mk_543
+ 0x314a1eee, 0x1e6b1ca4, 0x06030e11, 0x06106b08, // ms.yo.az_422 id.ceb.ms_433 is.nl.de_653 ceb.lt.de_443
+ 0x05232011, 0x1b003508, 0x32131607, 0x074a19a0, // sq.ca.fr_653 zu.tr.un_430 hr.et.bs_432 gl.yo.it_322
+ 0x20252a07, 0x31682105, 0x180501ec, 0x19005319, // mt.eu.sq_432 jw.ig.az_333 en.fr.ga_644 ht.gl.un_750
+ // [4f90]
+ 0x2d001f1a, 0x110a080c, 0x1f003f1a, 0x5300231d, // cy.sk.un_760 uk.mk.ro_543 af.cy.un_760 ca.ht.un_820
+ 0x13080ea7, 0x0e03010c, 0x112501ad, 0x0f006e08, // is.no.et_532 en.nl.is_543 en.eu.ro_643 hmn.lv.un_430
+ 0x52003b13, 0x021b6b12, 0x2b212512, 0x25006b1a, // so.ha.un_650 ceb.tr.da_654 eu.jw.vi_654 ceb.eu.un_760
+ 0x04250513, 0x2500680d, 0x6b2125a0, 0x25002122, // fr.eu.fi_665 ig.eu.un_540 eu.jw.ceb_322 jw.eu.un_870
+ // [4fa0]
+ 0x2a002904, 0x6b000e13, 0x1f003f08, 0x100c3f13, // sl.mt.un_320 is.ceb.un_650 af.cy.un_430 af.sv.lt_665
+ 0x53072305, 0x64315504, 0x3b045207, 0x28000713, // ca.it.ht_333 rw.az.lg_332 ha.fi.so_432 it.sw.un_650
+ 0x35521a07, 0x1c213108, 0x2b6b2512, 0x0c106b0c, // tl.ha.zu_432 az.jw.id_443 eu.ceb.vi_654 ceb.lt.sv_543
+ 0x05005312, 0x2d0d18ee, 0x030511ee, 0x1e521f07, // ht.fr.un_640 ga.cs.sk_422 ro.fr.nl_422 cy.ha.ms_432
+ // [4fb0]
+ 0x0b110560, 0x255535a4, 0x55351211, 0x2d001602, // fr.ro.es_664 zu.rw.eu_433 hu.zu.rw_653 hr.sk.un_220
+ 0x10002104, 0x0c1a0604, 0x1b0953ad, 0x29175307, // jw.lt.un_320 de.tl.sv_332 ht.pl.tr_643 ht.sr.sl_432
+ 0x31193507, 0x232719ee, 0x295255a4, 0x5511350c, // zu.gl.az_432 gl.gd.ca_422 rw.ha.sl_433 zu.ro.rw_543
+ 0x686b01a6, 0x0f100112, 0x27006807, 0x1b311aa9, // en.ceb.ig_521 en.lt.lv_654 ig.gd.un_420 tl.az.tr_544
+ // [4fc0]
+ 0x0a201b12, 0x063120ee, 0x311b1a14, 0x2300181a, // tr.sq.pt_654 sq.az.de_422 tl.tr.az_666 ga.ca.un_760
+ 0x32161309, 0x1c130d09, 0x0f101aa7, 0x010f13ec, // et.hr.bs_444 ne.bh.mr_444 tl.lt.lv_532 et.lv.en_644
+ 0x53322912, 0x233f01a9, 0x010e050c, 0x6e000104, // sl.bs.ht_654 en.af.ca_544 fr.is.en_543 en.hmn.un_320
+ 0x060f0107, 0x52001014, 0x180123a9, 0x04521313, // en.lv.de_432 lt.ha.un_660 ca.en.ga_544 et.ha.fi_665
+ // [4fd0]
+ 0x13005307, 0x05001e02, 0x3b01280c, 0x23001f21, // ht.et.un_420 ms.fr.un_220 sw.en.so_543 cy.ca.un_860
+ 0x35643112, 0x181027ec, 0x10281aad, 0x31525504, // az.lg.zu_654 gd.lt.ga_644 tl.sw.lt_643 rw.ha.az_332
+ 0x6e00012b, 0x35110aa4, 0x1b1a3108, 0x6e00100e, // en.hmn.un_980 pt.ro.zu_433 az.tl.tr_443 lt.hmn.un_550
+ 0x2d0d03a9, 0x1a001e1a, 0x082a010d, 0x172a070b, // nl.cs.sk_544 ms.tl.un_760 en.mt.no_554 it.mt.sr_542
+ // [4fe0]
+ 0x091308ad, 0x131a1ca4, 0x01002a07, 0x1a216bee, // no.et.pl_643 id.tl.et_433 mt.en.un_420 ceb.jw.tl_422
+ 0x356b6409, 0x283520a0, 0x01203b07, 0x1b106b60, // lg.ceb.zu_444 sq.zu.sw_322 so.sq.en_432 ceb.lt.tr_664
+ 0x1f1235ad, 0x0f556460, 0x6b013b07, 0x32132d0b, // zu.hu.cy_643 lg.rw.lv_664 so.en.ceb_432 sk.et.bs_542
+ 0x0c0110ee, 0x1b253113, 0x28090407, 0x08002713, // lt.en.sv_422 az.eu.tr_665 fi.pl.sw_432 gd.no.un_650
+ // [4ff0]
+ 0x641b31ec, 0x172d29a0, 0x0d004a12, 0x641a1e12, // az.tr.lg_644 sl.sk.sr_322 yo.cs.un_640 ms.tl.lg_654
+ 0x190a10a4, 0x181223a9, 0x3f002514, 0x045228ad, // lt.pt.gl_433 ca.hu.ga_544 eu.af.un_660 sw.ha.fi_643
+ 0x1f00010c, 0x0f095304, 0x6855200c, 0x1e31100c, // en.cy.un_530 ht.pl.lv_332 sq.rw.ig_543 lt.az.ms_543
+ 0x281b35ad, 0x55005208, 0x55000714, 0x550525a4, // zu.tr.sw_643 ha.rw.un_430 it.rw.un_660 eu.fr.rw_433
+
+ // [5000]
+ 0x3f002b0d, 0x0f005219, 0x190b12a4, 0x110129a4, // vi.af.un_540 ha.lv.un_750 hu.es.gl_433 sl.en.ro_433
+ 0x12190ea9, 0x1e1a1ca4, 0x0d071313, 0x206e1a0c, // is.gl.hu_544 id.tl.ms_433 et.it.cs_665 tl.hmn.sq_543
+ 0x1c002b21, 0x68004a0d, 0x1e3b1c0c, 0x521e280c, // vi.id.un_860 yo.ig.un_540 id.so.ms_543 sw.ms.ha_543
+ 0x1b5553ec, 0x3f0301ad, 0x01271812, 0x3b042007, // ht.rw.tr_644 en.nl.af_643 ga.gd.en_654 sq.fi.so_432
+ // [5010]
+ 0x25000629, 0x53045211, 0x1e131c07, 0x1f000621, // de.eu.un_960 ha.fi.ht_653 id.et.ms_432 de.cy.un_860
+ 0x016b35a0, 0x2b641c0c, 0x131104ee, 0x1a2821a4, // zu.ceb.en_322 id.lg.vi_543 fi.ro.et_422 jw.sw.tl_433
+ 0x1c1316a0, 0x641a350c, 0x1a2155a9, 0x6b1a1ca9, // hr.et.id_322 zu.tl.lg_543 rw.jw.tl_544 id.tl.ceb_544
+ 0x02283502, 0x21005504, 0x1e1c04a4, 0x23006e07, // zu.sw.da_222 rw.jw.un_320 fi.id.ms_433 hmn.ca.un_420
+ // [5020]
+ 0x01001b08, 0x1e1c5302, 0x0c532308, 0x200d3ba9, // tr.en.un_430 ht.id.ms_222 ca.ht.sv_443 so.cs.sq_544
+ 0x0c1b5305, 0x1f005204, 0x0e00522a, 0x283f5308, // ht.tr.sv_333 ha.cy.un_320 ha.is.un_970 ht.af.sw_443
+ 0x6b6853a7, 0x6e003204, 0x6b003507, 0x2b3f16a7, // ht.ig.ceb_532 bs.hmn.un_320 zu.ceb.un_420 hr.af.vi_532
+ 0x3f1e1c0c, 0x025208a4, 0x291b0fa4, 0x2a0d3b07, // id.ms.af_543 no.ha.da_433 lv.tr.sl_433 so.cs.mt_432
+ // [5030]
+ 0x01001604, 0x213f060c, 0x1b6e1604, 0x03001308, // hr.en.un_320 de.af.jw_543 hr.hmn.tr_332 et.nl.un_430
+ 0x526b1b0c, 0x0e0c5308, 0x28001314, 0x1e1c0ca9, // tr.ceb.ha_543 ht.sv.is_443 et.sw.un_660 sv.id.ms_544
+ 0x35002104, 0x3b004a08, 0x172916ad, 0x0b0a19a7, // jw.zu.un_320 yo.so.un_430 hr.sl.sr_643 gl.pt.es_532
+ 0x130110a4, 0x521f3b07, 0x19002308, 0x20102812, // lt.en.et_433 so.cy.ha_432 ca.gl.un_430 sw.lt.sq_654
+ // [5040]
+ 0x3f641307, 0x01231a08, 0x27001008, 0x020408a4, // et.lg.af_432 tl.ca.en_443 lt.gd.un_430 no.fi.da_433
+ 0x6b000d08, 0x643f3ba4, 0x522b010c, 0x192b0ba9, // cs.ceb.un_430 so.af.lg_433 en.vi.ha_543 es.vi.gl_544
+ 0x550603ad, 0x0f0b53a4, 0x0a0e52a7, 0x321752a9, // nl.de.rw_643 ht.es.lv_433 ha.is.pt_532 ha.sr.bs_544
+ 0x35033fee, 0x0204080c, 0x170b295a, 0x4a003b23, // af.nl.zu_422 no.fi.da_543 sl.es.sr_553 so.yo.un_880
+ // [5050]
+ 0x0e5227a9, 0x13116408, 0x4a001113, 0x255511a6, // gd.ha.is_544 lg.ro.et_443 ro.yo.un_650 ro.rw.eu_521
+ 0x135527ac, 0x1a3b0bee, 0x0a002704, 0x4a191e07, // gd.rw.et_632 es.so.tl_422 gd.pt.un_320 ms.gl.yo_432
+ 0x2b1c2707, 0x25186405, 0x6b3b2709, 0x641155a4, // gd.id.vi_432 lg.ga.eu_333 gd.so.ceb_444 rw.ro.lg_433
+ 0x046b13a4, 0x2d296460, 0x0d1b3b07, 0x64281a05, // et.ceb.fi_433 lg.sl.sk_664 so.tr.cs_432 tl.sw.lg_333
+ // [5060]
+ 0x011f55a0, 0x4a1b0107, 0x524a1e12, 0x0a100712, // rw.cy.en_322 en.tr.yo_432 ms.yo.ha_654 bg.be.mk_654
+ 0x684a3b08, 0x28113512, 0x1a68350c, 0x033513a9, // so.yo.ig_443 zu.ro.sw_654 zu.ig.tl_543 et.zu.nl_544
+ 0x3f03350c, 0x32161109, 0x29020c60, 0x1b11130c, // zu.nl.af_543 ro.hr.bs_444 sv.da.sl_664 et.ro.tr_543
+ 0x31001607, 0x351a0c07, 0x09006e34, 0x03113508, // hr.az.un_420 sv.tl.zu_432 hmn.pl.un_A80 zu.ro.nl_443
+ // [5070]
+ 0x1b113512, 0x031c1102, 0x03006e0e, 0x1c1b1eee, // zu.ro.tr_654 ro.id.nl_222 hmn.nl.un_550 ms.tr.id_422
+ 0x190b5209, 0x2d0d6e11, 0x1100351b, 0x02250811, // ha.es.gl_444 hmn.cs.sk_653 zu.ro.un_770 no.eu.da_653
+ 0x5311350c, 0x042752ad, 0x0d131113, 0x0c0620a4, // zu.ro.ht_543 ha.gd.fi_643 ro.et.cs_665 sq.de.sv_433
+ 0x07191f07, 0x1b01530c, 0x10003122, 0x3f13110c, // cy.gl.it_432 ht.en.tr_543 az.lt.un_870 ro.et.af_543
+ // [5080]
+ 0x113513af, 0x100711ec, 0x1f0e06a7, 0x052321ad, // et.zu.ro_655 ro.bg.be_644 de.is.cy_532 jw.ca.fr_643
+ 0x012135af, 0x31252a0c, 0x13310cac, 0x3f0305a6, // zu.jw.en_655 mt.eu.az_543 sv.az.et_632 fr.nl.af_521
+ 0x4a001a09, 0x1a3b040c, 0x1b3212a7, 0x6b041f04, // tl.yo.un_440 fi.so.tl_543 hu.bs.tr_532 cy.fi.ceb_332
+ 0x1e002104, 0x6e000a1b, 0x030c3f60, 0x21003b08, // jw.ms.un_320 pt.hmn.un_770 af.sv.nl_664 so.jw.un_430
+ // [5090]
+ 0x1e086e0c, 0x1200021a, 0x043b4aa0, 0x02006b02, // hmn.no.ms_543 da.hu.un_760 yo.so.fi_322 ceb.da.un_220
+ 0x6b1a2107, 0x110305ee, 0x056e1f0c, 0x02273f07, // jw.tl.ceb_432 fr.nl.ro_422 cy.hmn.fr_543 af.gd.da_432
+ 0x530a52a9, 0x3b001a13, 0x1e00211a, 0x030c3f0c, // ha.pt.ht_544 tl.so.un_650 jw.ms.un_760 af.sv.nl_543
+ 0x1b006b0c, 0x11030512, 0x0e003113, 0x04000621, // ceb.tr.un_530 fr.nl.ro_654 az.is.un_650 de.fi.un_860
+ // [50a0]
+ 0x0d1c13a7, 0x3b1a040c, 0x2b0a6eec, 0x03000c05, // bh.mr.ne_532 fi.tl.so_543 hmn.pt.vi_644 sv.nl.un_330
+ 0x0c0204ad, 0x01680ea4, 0x311b0707, 0x3500040c, // fi.da.sv_643 is.ig.en_433 it.tr.az_432 fi.zu.un_530
+ 0x2a002504, 0x3f1a210c, 0x020305a9, 0x0e0c0613, // eu.mt.un_320 jw.tl.af_543 fr.nl.da_544 de.sv.is_665
+ 0x2b00160e, 0x0311050c, 0x0501235a, 0x20521fad, // hr.vi.un_550 fr.ro.nl_543 ca.en.fr_553 cy.ha.sq_643
+ // [50b0]
+ 0x11000717, 0x01002802, 0x08072a0d, 0x1b0c31a4, // it.ro.un_730 sw.en.un_220 mt.it.no_554 az.sv.tr_433
+ 0x00001737, 0x0400172c, 0x210d32a0, 0x530308a9, // sr.un.un_B00 sr.ru.un_990 bs.cs.jw_322 no.nl.ht_544
+ 0x25002a05, 0x311b4aa4, 0x25005314, 0x060b03a4, // mt.eu.un_330 yo.tr.az_433 ht.eu.un_660 nl.es.de_433
+ 0x21006e04, 0x53190111, 0x555352a9, 0x4a213f11, // hmn.jw.un_320 en.gl.ht_653 ha.ht.rw_544 af.jw.yo_653
+ // [50c0]
+ 0x0f106e60, 0x204a2112, 0x131b680c, 0x02003f29, // hmn.lt.lv_664 jw.yo.sq_654 ig.tr.et_543 af.da.un_960
+ 0x13004a19, 0x081b0255, 0x021c2ba7, 0x2820070c, // yo.et.un_750 da.tr.no_442 vi.id.da_532 it.sq.sw_543
+ 0x3f640755, 0x086e4aa0, 0x0705520c, 0x29191c08, // it.lg.af_442 yo.hmn.no_322 ha.fr.it_543 id.gl.sl_443
+ 0x52132512, 0x4a1b02ee, 0x1a6e2855, 0x250603a9, // eu.et.ha_654 da.tr.yo_422 sw.hmn.tl_442 nl.de.eu_544
+ // [50d0]
+ 0x550d0504, 0x134a02a4, 0x2b681808, 0x04000922, // fr.cs.rw_332 da.yo.et_433 ga.ig.vi_443 pl.fi.un_870
+ 0x10006e2a, 0x3200520b, 0x3f1768a4, 0x11253b0c, // hmn.lt.un_970 ha.bs.un_520 ig.sr.af_433 so.eu.ro_543
+ 0x3f00090c, 0x1a005521, 0x07006e05, 0x2a030905, // pl.af.un_530 rw.tl.un_860 hmn.it.un_330 pl.nl.mt_333
+ 0x19090b0c, 0x06523fa4, 0x1b000c0e, 0x1100522a, // es.pl.gl_543 af.ha.de_433 sv.tr.un_550 ha.ro.un_970
+ // [50e0]
+ 0x6b311ba4, 0x0f6e0714, 0x680208af, 0x3f10230c, // tr.az.ceb_433 it.hmn.lv_666 no.da.ig_655 ca.lt.af_543
+ 0x02532908, 0x021b11a6, 0x31022507, 0x0d291ea0, // sl.ht.da_443 ro.tr.da_521 eu.da.az_432 ms.sl.cs_322
+ 0x0a6e2b05, 0x080203a4, 0x11081ea0, 0x0c100fa4, // vi.hmn.pt_333 nl.da.no_433 ms.no.ro_322 lv.lt.sv_433
+ 0x030f090c, 0x1f0309ad, 0x09201b0c, 0x0b000609, // pl.lv.nl_543 pl.nl.cy_643 tr.sq.pl_543 de.es.un_440
+ // [50f0]
+ 0x030f29a9, 0x68356ea4, 0x103f0405, 0x6b130f07, // sl.lv.nl_544 hmn.zu.ig_433 fi.af.lt_333 lv.et.ceb_432
+ 0x1f0364a9, 0x2a180a09, 0x040807ec, 0x10060e04, // lg.nl.cy_544 pt.ga.mt_444 bg.uk.ru_644 is.de.lt_332
+ 0x0f103fa7, 0x0e3b0408, 0x02081107, 0x0c041fa0, // af.lt.lv_532 fi.so.is_443 ro.no.da_432 cy.fi.sv_322
+ 0x06001f04, 0x1a211c08, 0x0200012a, 0x1e531ca4, // cy.de.un_320 id.jw.tl_443 en.da.un_970 id.ht.ms_433
+ // [5100]
+ 0x23003b04, 0x28001813, 0x170708ad, 0x0553020c, // so.ca.un_320 ga.sw.un_650 uk.bg.sr_643 da.ht.fr_543
+ 0x0e1b0f04, 0x25000908, 0x05006807, 0x190b0705, // lv.tr.is_332 pl.eu.un_430 ig.fr.un_420 it.es.gl_333
+ 0x011f28a6, 0x0b004a04, 0x1153050c, 0x683f27a9, // sw.cy.en_521 yo.es.un_320 fr.ht.ro_543 gd.af.ig_544
+ 0x321b5308, 0x352718ec, 0x251c1e0c, 0x08020114, // ht.tr.bs_443 ga.gd.zu_644 ms.id.eu_543 en.da.no_666
+ // [5110]
+ 0x25041ea7, 0x1e28110c, 0x10111ba7, 0x53112512, // ms.fi.eu_532 ro.sw.ms_543 tr.ro.lt_532 eu.ro.ht_654
+ 0x190a05ec, 0x17291eec, 0x0d091c0d, 0x066403a4, // fr.pt.gl_644 ms.sl.sr_644 mr.hi.ne_554 nl.lg.de_433
+ 0x29101f0c, 0x642d0d13, 0x21002a07, 0x6b1001a7, // cy.lt.sl_543 cs.sk.lg_665 mt.jw.un_420 en.lt.ceb_532
+ 0x191f2108, 0x68000321, 0x21001f0c, 0x060801a0, // jw.cy.gl_443 nl.ig.un_860 cy.jw.un_530 en.no.de_322
+ // [5120]
+ 0x07012a02, 0x101253ee, 0x0c004a02, 0x6b091aa9, // mt.en.it_222 ht.hu.lt_422 yo.sv.un_220 tl.pl.ceb_544
+ 0x6b6818ee, 0x080602ee, 0x680906ad, 0x28111aa9, // ga.ig.ceb_422 da.de.no_422 de.pl.ig_643 tl.ro.sw_544
+ 0x3b002008, 0x1e1c6ba9, 0x231e05a4, 0x1329090d, // sq.so.un_430 ceb.id.ms_544 fr.ms.ca_433 pl.sl.et_554
+ 0x2d04050c, 0x53006b19, 0x13004a04, 0x07230da4, // fr.fi.sk_543 ceb.ht.un_750 yo.et.un_320 cs.ca.it_433
+ // [5130]
+ 0x060e0860, 0x0e002d0c, 0x3b552807, 0x20011f11, // no.is.de_664 sk.is.un_530 sw.rw.so_432 cy.en.sq_653
+ 0x08020607, 0x131112a4, 0x3b311b08, 0x1c131a60, // de.da.no_432 hu.ro.et_433 tr.az.so_443 tl.et.id_664
+ 0x12001804, 0x100f20af, 0x350d2908, 0x052a2708, // ga.hu.un_320 sq.lv.lt_655 sl.cs.zu_443 gd.mt.fr_443
+ 0x68131e07, 0x20313b12, 0x07212a0c, 0x0c2808ee, // ms.et.ig_432 so.az.sq_654 mt.jw.it_543 no.sw.sv_422
+ // [5140]
+ 0x13252a0c, 0x0d002704, 0x135520a4, 0x03061fa9, // mt.eu.et_543 gd.cs.un_320 sq.rw.et_433 cy.de.nl_544
+ 0x051b530d, 0x28356ea4, 0x200d55a4, 0x0900310d, // ht.tr.fr_554 hmn.zu.sw_433 rw.cs.sq_433 az.pl.un_540
+ 0x2b06250c, 0x2a042560, 0x08020fa4, 0x03001e04, // eu.de.vi_543 eu.fi.mt_664 lv.da.no_433 ms.nl.un_320
+ 0x041e1a08, 0x21281a0c, 0x130b3bad, 0x0b1e6ea0, // tl.ms.fi_443 tl.sw.jw_543 so.es.et_643 hmn.ms.es_322
+ // [5150]
+ 0x02052aee, 0x072b1faf, 0x3b534aad, 0x1b102a04, // mt.fr.da_422 cy.vi.it_655 yo.ht.so_643 mt.lt.tr_332
+ 0x216e2707, 0x01002505, 0x1a3b6e0c, 0x18061f0c, // gd.hmn.jw_432 eu.en.un_330 hmn.so.tl_543 cy.de.ga_543
+ 0x282a6408, 0x0f2304af, 0x07002d1a, 0x1a556b0e, // lg.mt.sw_443 fi.ca.lv_655 sk.it.un_760 ceb.rw.tl_555
+ 0x23041112, 0x022508a4, 0x043f1ca4, 0x31001805, // ro.fi.ca_654 no.eu.da_433 id.af.fi_433 ga.az.un_330
+ // [5160]
+ 0x25060407, 0x3b1f10ec, 0x0d092daf, 0x25002708, // fi.de.eu_432 lt.cy.so_644 sk.pl.cs_655 gd.eu.un_430
+ 0x112825a9, 0x1a6b10ad, 0x1e1a1c0c, 0x060c1b0c, // eu.sw.ro_544 lt.ceb.tl_643 id.tl.ms_543 tr.sv.de_543
+ 0x6b1e1a11, 0x0f0401a4, 0x046b1caf, 0x08315308, // tl.ms.ceb_653 en.fi.lv_433 id.ceb.fi_655 ht.az.no_443
+ 0x1800252b, 0x1852100c, 0x190a05a7, 0x0b00030e, // eu.ga.un_980 lt.ha.ga_543 fr.pt.gl_532 nl.es.un_550
+ // [5170]
+ 0x13092d04, 0x07001807, 0x0f5528ee, 0x25091ea4, // sk.pl.et_332 ga.it.un_420 sw.rw.lv_422 ms.pl.eu_433
+ 0x0a07200c, 0x011f0fa7, 0x0b0306a4, 0x033f1705, // sq.it.pt_543 lv.cy.en_532 de.nl.es_433 sr.af.nl_333
+ 0x12000614, 0x1f0a0205, 0x190a2304, 0x270b18a4, // de.hu.un_660 da.pt.cy_333 ca.pt.gl_332 ga.es.gd_433
+ 0x20073ba4, 0x0a04010b, 0x05100fa4, 0x524a2804, // so.it.sq_433 en.fi.pt_542 lv.lt.fr_433 sw.yo.ha_332
+ // [5180]
+ 0x111a27a4, 0x53133fa4, 0x1a001812, 0x3b0a1aa7, // gd.tl.ro_433 af.et.ht_433 ga.tl.un_640 tl.pt.so_532
+ 0x1f041aad, 0x291713a9, 0x00000903, 0x2a110712, // tl.fi.cy_643 et.sr.sl_544 hi.un.un_300 it.ro.mt_654
+ 0x0a002d11, 0x6b000a22, 0x05091f07, 0x4a32160e, // sk.pt.un_630 pt.ceb.un_870 cy.pl.fr_432 hr.bs.yo_555
+ 0x19000a21, 0x6b130507, 0x530d2705, 0x13001a02, // pt.gl.un_860 fr.et.ceb_432 gd.cs.ht_333 tl.et.un_220
+ // [5190]
+ 0x02130807, 0x0a230eaf, 0x3b52010c, 0x06000a12, // no.et.da_432 is.ca.pt_655 en.ha.so_543 pt.de.un_640
+ 0x1f002302, 0x315206af, 0x2000522b, 0x2d0d6b02, // ca.cy.un_220 de.ha.az_655 ha.sq.un_980 ceb.cs.sk_222
+ 0x641f5508, 0x64000614, 0x4a000704, 0x311b12b4, // rw.cy.lg_443 de.lg.un_660 it.yo.un_320 hu.tr.az_754
+ 0x250b23a7, 0x0664550d, 0x23190a0e, 0x08002507, // ca.es.eu_532 rw.lg.de_554 pt.gl.ca_555 eu.no.un_420
+ // [51a0]
+ 0x1e1c0b0c, 0x05001f19, 0x06552112, 0x1f002822, // es.id.ms_543 cy.fr.un_750 jw.rw.de_654 sw.cy.un_870
+ 0x230d0b04, 0x681b3112, 0x063f09ad, 0x0a2d11af, // es.cs.ca_332 az.tr.ig_654 pl.af.de_643 ro.sk.pt_655
+ 0x2a00310d, 0x08100ea9, 0x02000e09, 0x1c28290c, // az.mt.un_540 is.lt.no_544 is.da.un_440 sl.sw.id_543
+ 0x4a555202, 0x1f0c25a7, 0x0a2913a4, 0x1b4a6ba4, // ha.rw.yo_222 eu.sv.cy_532 et.sl.pt_433 ceb.yo.tr_433
+ // [51b0]
+ 0x1c2521ad, 0x1a041fa7, 0x115313a0, 0x3f002813, // jw.eu.id_643 cy.fi.tl_532 et.ht.ro_322 sw.af.un_650
+ 0x1a1c1fa0, 0x06132807, 0x20113f07, 0x3b5568a9, // cy.id.tl_322 sw.et.de_432 af.ro.sq_432 ig.rw.so_544
+ 0x2b272860, 0x20002721, 0x0b0c250c, 0x2a0827a0, // sw.gd.vi_664 gd.sq.un_860 eu.sv.es_543 gd.no.mt_322
+ 0x1f006823, 0x5368055a, 0x190b05af, 0x0a003505, // ig.cy.un_880 fr.ig.ht_553 fr.es.gl_655 zu.pt.un_330
+ // [51c0]
+ 0x35000a21, 0x1003520c, 0x2d001107, 0x1000251a, // pt.zu.un_860 ha.nl.lt_543 ro.sk.un_420 eu.lt.un_760
+ 0x6e1f2bec, 0x28521c0c, 0x28002704, 0x280c1a07, // vi.cy.hmn_644 id.ha.sw_543 gd.sw.un_320 tl.sv.sw_432
+ 0x01202811, 0x3b18280c, 0x13283ba4, 0x6b00682a, // sw.sq.en_653 sw.ga.so_543 so.sw.et_433 ig.ceb.un_970
+ 0x172855a9, 0x0f071308, 0x0f321005, 0x033b6411, // rw.sw.sr_544 et.it.lv_443 lt.bs.lv_333 lg.so.nl_653
+ // [51d0]
+ 0x011e2ba0, 0x033b1a0b, 0x3f002504, 0x052b01a6, // vi.ms.en_322 tl.so.nl_542 eu.af.un_320 en.vi.fr_521
+ 0x32003107, 0x3f020fa0, 0x21002808, 0x11190baf, // az.bs.un_420 lv.da.af_322 sw.jw.un_430 es.gl.ro_655
+ 0x041321af, 0x28523b07, 0x072a0405, 0x5300231a, // jw.et.fi_655 so.ha.sw_432 fi.mt.it_333 ca.ht.un_760
+ 0x01002a02, 0x012805ee, 0x2b6b040c, 0x212b1c09, // mt.en.un_220 fr.sw.en_422 fi.ceb.vi_543 id.vi.jw_444
+ // [51e0]
+ 0x532b28ee, 0x0c000d04, 0x10171111, 0x1828680c, // sw.vi.ht_422 cs.sv.un_320 ro.sr.be_653 ig.sw.ga_543
+ 0x0000072d, 0x1f5327a9, 0x324a17a0, 0x5200042c, // it.un.un_A00 gd.ht.cy_544 sr.yo.bs_322 fi.ha.un_990
+ 0x102b680d, 0x2b000712, 0x0e0425ad, 0x201923a9, // ig.vi.lt_554 it.vi.un_640 eu.fi.is_643 ca.gl.sq_544
+ 0x523b01af, 0x0e0452ad, 0x1b0e31af, 0x13090d5a, // en.so.ha_655 ha.fi.is_643 az.is.tr_655 ne.hi.bh_553
+ // [51f0]
+ 0x11040da4, 0x552528a4, 0x202723a4, 0x52183ba4, // cs.fi.ro_433 sw.eu.rw_433 ca.gd.sq_433 so.ga.ha_433
+ 0x2800291a, 0x1a002113, 0x00000142, 0x6b1a10a7, // sl.sw.un_760 jw.tl.un_650 en.un.un_C00 lt.tl.ceb_532
+ 0x061e1f07, 0x310b0eee, 0x1b0e3108, 0x1f00230c, // cy.ms.de_432 is.es.az_422 az.is.tr_443 ca.cy.un_530
+ 0x0e04520c, 0x19040b0c, 0x16130404, 0x08000702, // ha.fi.is_543 es.fi.gl_543 fi.et.hr_332 it.no.un_220
+ // [5200]
+ 0x6b2d1a07, 0x251364a4, 0x0e231f12, 0x07001812, // tl.sk.ceb_432 lg.et.eu_433 cy.ca.is_654 ga.it.un_640
+ 0x08231fa4, 0x18551b07, 0x352028af, 0x1b005304, // cy.ca.no_433 tr.rw.ga_432 sw.sq.zu_655 ht.tr.un_320
+ 0x23002a22, 0x0f100e12, 0x06082308, 0x6b1a1308, // mt.ca.un_870 is.lt.lv_654 ca.no.de_443 et.tl.ceb_443
+ 0x060b1fa0, 0x1712080b, 0x20001c0c, 0x551021ec, // cy.es.de_322 no.hu.sr_542 id.sq.un_530 jw.lt.rw_644
+ // [5210]
+ 0x4a0d52ee, 0x3b212012, 0x094a1307, 0x55006e04, // ha.cs.yo_422 sq.jw.so_654 et.yo.pl_432 hmn.rw.un_320
+ 0x0a051960, 0x191f0a04, 0x1c0429a0, 0x2d0d35a9, // gl.fr.pt_664 pt.cy.gl_332 sl.fi.id_322 zu.cs.sk_544
+ 0x2d0d6b14, 0x1e1c10ee, 0x190b3b05, 0x03011007, // ceb.cs.sk_666 lt.id.ms_422 so.es.gl_333 lt.en.nl_432
+ 0x531b0ba4, 0x10006b12, 0x060120a0, 0x1b1c6ba4, // es.tr.ht_433 ceb.lt.un_640 sq.en.de_322 ceb.id.tr_433
+ // [5220]
+ 0x01353fa0, 0x4a2368a4, 0x02001a0d, 0x3b1f6baf, // af.zu.en_322 ig.ca.yo_433 tl.da.un_540 ceb.cy.so_655
+ 0x3b001a20, 0x3b2a29a9, 0x1a0931a0, 0x193b0b55, // tl.so.un_850 sl.mt.so_544 az.pl.tl_322 es.so.gl_442
+ 0x2302010c, 0x3f0f0112, 0x6400091b, 0x110d2911, // en.da.ca_543 en.lv.af_654 pl.lg.un_770 sl.cs.ro_653
+ 0x13010f11, 0x55071c04, 0x08071160, 0x0a0817ee, // lv.en.et_653 id.it.rw_332 ro.bg.uk_664 sr.uk.mk_422
+ // [5230]
+ 0x0f00011a, 0x1b030aee, 0x6b001212, 0x080229a4, // en.lv.un_760 pt.nl.tr_422 hu.ceb.un_640 sl.da.no_433
+ 0x0e002908, 0x55003112, 0x16001a07, 0x0d1e1c13, // sl.is.un_430 az.rw.un_640 tl.hr.un_420 id.ms.cs_665
+ 0x68013ba7, 0x2d29010c, 0x04132304, 0x2d0d35a4, // so.en.ig_532 en.sl.sk_543 ca.et.fi_332 zu.cs.sk_433
+ 0x06000713, 0x35006e07, 0x126468ad, 0x29000c14, // it.de.un_650 hmn.zu.un_420 ig.lg.hu_643 sv.sl.un_660
+ // [5240]
+ 0x55356808, 0x04002322, 0x0a2119a4, 0x0b121907, // ig.zu.rw_443 ca.fi.un_870 gl.jw.pt_433 gl.hu.es_432
+ 0x1e001304, 0x06000e0c, 0x203564a9, 0x192d0b07, // et.ms.un_320 is.de.un_530 lg.zu.sq_544 es.sk.gl_432
+ 0x08120eee, 0x09292008, 0x641a6ba7, 0x3b6855a7, // is.hu.no_422 sq.sl.pl_443 ceb.tl.lg_532 rw.ig.so_532
+ 0x10000802, 0x080c0e05, 0x1c521eec, 0x2d0d230c, // uk.be.un_220 is.sv.no_333 ms.ha.id_644 ca.cs.sk_543
+ // [5250]
+ 0x1e1c3b14, 0x6b2d12a9, 0x4a1a5511, 0x1a2d1208, // so.id.ms_666 hu.sk.ceb_544 rw.tl.yo_653 hu.sk.tl_443
+ 0x1b2920ad, 0x201029a4, 0x1b072014, 0x10000704, // sq.sl.tr_643 sl.lt.sq_433 sq.it.tr_666 it.lt.un_320
+ 0x25040308, 0x551b200c, 0x212d1209, 0x4a005221, // nl.fi.eu_443 sq.tr.rw_543 hu.sk.jw_444 ha.yo.un_860
+ 0x2125120c, 0x1c0d090b, 0x0c081a08, 0x3b13030c, // hu.eu.jw_543 hi.ne.mr_542 tl.no.sv_443 nl.et.so_543
+ // [5260]
+ 0x4a00682c, 0x1000072b, 0x121f1104, 0x0a190513, // ig.yo.un_990 bg.be.un_980 ro.cy.hu_332 fr.gl.pt_665
+ 0x3f2512a4, 0x28250da0, 0x1a122d11, 0x0b0a05a4, // hu.eu.af_433 cs.eu.sw_322 sk.hu.tl_653 fr.pt.es_433
+ 0x2d29120d, 0x1c1a1212, 0x520e01af, 0x12001c1a, // hu.sl.sk_554 hu.tl.id_654 en.is.ha_655 id.hu.un_760
+ 0x08001707, 0x640313ee, 0x1a001105, 0x19002304, // sr.uk.un_420 et.nl.lg_422 ro.tl.un_330 ca.gl.un_320
+ // [5270]
+ 0x01023fa0, 0x23190704, 0x0106280d, 0x111b1605, // af.da.en_322 it.gl.ca_332 sw.de.en_554 hr.tr.ro_333
+ 0x1f0105a0, 0x0d092d0c, 0x55006823, 0x13040ca0, // fr.en.cy_322 sk.pl.cs_543 ig.rw.un_880 sv.fi.et_322
+ 0x6b526813, 0x04644a08, 0x4a002513, 0x0c030609, // ig.ha.ceb_665 yo.lg.fi_443 eu.yo.un_650 de.nl.sv_444
+ 0x03005304, 0x53312009, 0x08000321, 0x6800550e, // ht.nl.un_320 sq.az.ht_444 nl.no.un_860 rw.ig.un_550
+ // [5280]
+ 0x020308a9, 0x273b2011, 0x09642504, 0x0a000735, // no.nl.da_544 sq.so.gd_653 eu.lg.pl_332 bg.mk.un_A90
+ 0x23200413, 0x271c010c, 0x3b1b1fec, 0x68003b1b, // fi.sq.ca_665 en.id.gd_543 cy.tr.so_644 so.ig.un_770
+ 0x04002019, 0x0b251908, 0x684a0408, 0x061a3bad, // sq.fi.un_750 gl.eu.es_443 fi.yo.ig_443 so.tl.de_643
+ 0x100e3f13, 0x0320060c, 0x6b683b05, 0x35551a09, // af.is.lt_665 de.sq.nl_543 so.ig.ceb_333 tl.rw.zu_444
+ // [5290]
+ 0x6e352108, 0x3b196ea9, 0x0d000504, 0x2a2023a9, // jw.zu.hmn_443 hmn.gl.so_544 fr.cs.un_320 ca.sq.mt_544
+ 0x3b3f1308, 0x35281205, 0x6b1b09a4, 0x06000204, // et.af.so_443 hu.sw.zu_333 pl.tr.ceb_433 da.de.un_320
+ 0x21521c11, 0x2700351a, 0x35000108, 0x68255508, // id.ha.jw_653 zu.gd.un_760 en.zu.un_430 rw.eu.ig_443
+ 0x352804ad, 0x2523520b, 0x35000613, 0x320f6804, // fi.sw.zu_643 ha.ca.eu_542 de.zu.un_650 ig.lv.bs_332
+ // [52a0]
+ 0x64521a04, 0x35006829, 0x350a07a9, 0x1268550d, // tl.ha.lg_332 ig.zu.un_960 it.pt.zu_544 rw.ig.hu_554
+ 0x1b000308, 0x68002818, 0x1c063502, 0x28533b14, // nl.tr.un_430 sw.ig.un_740 zu.de.id_222 so.ht.sw_666
+ 0x09213507, 0x3500522a, 0x350d0909, 0x100953ec, // zu.jw.pl_432 ha.zu.un_970 pl.cs.zu_444 ht.pl.lt_644
+ 0x06021113, 0x21686404, 0x190a200c, 0x171013a4, // ro.da.de_665 lg.ig.jw_332 sq.pt.gl_543 et.lt.sr_433
+ // [52b0]
+ 0x0f190d0b, 0x3525090c, 0x06003508, 0x55282108, // cs.gl.lv_542 pl.eu.zu_543 zu.de.un_430 jw.sw.rw_443
+ 0x2b131ca9, 0x35551aa9, 0x200c0208, 0x1e100708, // id.et.vi_544 tl.rw.zu_544 da.sv.sq_443 it.lt.ms_443
+ 0x552021a4, 0x3f3b6b08, 0x2b271e0c, 0x0c1b080c, // jw.sq.rw_433 ceb.so.af_443 ms.gd.vi_543 no.tr.sv_543
+ 0x35002702, 0x061b310d, 0x251a350c, 0x090d13a4, // gd.zu.un_220 az.tr.de_554 zu.tl.eu_543 bh.ne.hi_433
+ // [52c0]
+ 0x254a68ac, 0x1e001a04, 0x033f1b0c, 0x0f5510ec, // ig.yo.eu_632 tl.ms.un_320 tr.af.nl_543 lt.rw.lv_644
+ 0x11551e08, 0x11000514, 0x0b000511, 0x6b1b53ec, // ms.rw.ro_443 fr.ro.un_660 fr.es.un_630 ht.tr.ceb_644
+ 0x35526baf, 0x051104a4, 0x102001ad, 0x20005509, // ceb.ha.zu_655 fi.ro.fr_433 en.sq.lt_643 rw.sq.un_440
+ 0x356b13ac, 0x03002021, 0x11003b04, 0x1f1118ac, // et.ceb.zu_632 sq.nl.un_860 so.ro.un_320 ga.ro.cy_632
+ // [52d0]
+ 0x21523b0c, 0x5528130e, 0x293b130c, 0x311c55a0, // so.ha.jw_543 et.sw.rw_555 et.so.sl_543 rw.id.az_322
+ 0x3b2519a9, 0x09130d13, 0x1b101ea0, 0x1a001707, // gl.eu.so_544 ne.bh.hi_665 ms.lt.tr_322 sr.tl.un_420
+ 0x04290304, 0x10006814, 0x68002012, 0x521b4a0c, // nl.sl.fi_332 ig.lt.un_660 sq.ig.un_640 yo.tr.ha_543
+ 0x5221530c, 0x3b556412, 0x1800280e, 0x311b5312, // ht.jw.ha_543 lg.rw.so_654 sw.ga.un_550 ht.tr.az_654
+ // [52e0]
+ 0x0c4a6e02, 0x1a004a1b, 0x060105ec, 0x64556814, // hmn.yo.sv_222 yo.tl.un_770 fr.en.de_644 ig.rw.lg_666
+ 0x03002a12, 0x2a23110d, 0x68003b19, 0x645335af, // mt.nl.un_640 ro.ca.mt_554 so.ig.un_750 zu.ht.lg_655
+ 0x551e1f04, 0x04200ca0, 0x64005302, 0x110603ec, // cy.ms.rw_332 sv.sq.fi_322 ht.lg.un_220 nl.de.ro_644
+ 0x311b4aac, 0x1f645511, 0x100a07a0, 0x17080405, // yo.tr.az_632 rw.lg.cy_653 bg.mk.be_322 ru.uk.sr_333
+ // [52f0]
+ 0x0a0817ec, 0x05000b0c, 0x0d00531a, 0x6e1a6baf, // sr.uk.mk_644 es.fr.un_530 ht.cs.un_760 ceb.tl.hmn_655
+ 0x0c002a1b, 0x16286408, 0x033f0aaf, 0x2a112308, // mt.sv.un_770 lg.sw.hr_443 pt.af.nl_655 ca.ro.mt_443
+ 0x1a5564a4, 0x23110d12, 0x35002a07, 0x1f1a6bec, // lg.rw.tl_433 cs.ro.ca_654 mt.zu.un_420 ceb.tl.cy_644
+ 0x523f230c, 0x1800191a, 0x1a003b14, 0x271f0208, // ca.af.ha_543 gl.ga.un_760 so.tl.un_660 da.cy.gd_443
+ // [5300]
+ 0x1355520e, 0x1f0727a9, 0x32003505, 0x13003519, // ha.rw.et_555 gd.it.cy_544 zu.bs.un_330 zu.et.un_750
+ 0x0f64550d, 0x3b211b0c, 0x351b13a4, 0x1a101e07, // rw.lg.lv_554 tr.jw.so_543 et.tr.zu_433 ms.lt.tl_432
+ 0x1107130c, 0x64006819, 0x284a645a, 0x170925a7, // et.it.ro_543 ig.lg.un_750 lg.yo.sw_553 eu.pl.sr_532
+ 0x2d29100c, 0x35280713, 0x13046408, 0x05110713, // lt.sl.sk_543 it.sw.zu_665 lg.fi.et_443 it.ro.fr_665
+ // [5310]
+ 0x1e101c0c, 0x2d170d07, 0x12082907, 0x120721ad, // id.lt.ms_543 cs.sr.sk_432 sl.no.hu_432 jw.it.hu_643
+ 0x291625ee, 0x3b355304, 0x21005202, 0x35646808, // eu.hr.sl_422 ht.zu.so_332 ha.jw.un_220 ig.lg.zu_443
+ 0x3f3b1f12, 0x322a09a4, 0x1e004a19, 0x0500101b, // cy.so.af_654 pl.mt.bs_433 yo.ms.un_750 lt.fr.un_770
+ 0x29204a13, 0x0200201a, 0x53001602, 0x202a2104, // yo.sq.sl_665 sq.da.un_760 hr.ht.un_220 jw.mt.sq_332
+ // [5320]
+ 0x022008ad, 0x172952a7, 0x0a182708, 0x0c20080c, // no.sq.da_643 ha.sl.sr_532 gd.ga.pt_443 no.sq.sv_543
+ 0x180e2709, 0x11033fec, 0x120e0cee, 0x2d1729af, // gd.is.ga_444 af.nl.ro_644 sv.is.hu_422 sl.sr.sk_655
+ 0x033f2311, 0x2d0807ac, 0x0e250504, 0x0206080c, // ca.af.nl_653 it.no.sk_632 fr.eu.is_332 no.de.da_543
+ 0x0d091c07, 0x11000522, 0x1f2327a9, 0x11033f60, // mr.hi.ne_432 fr.ro.un_870 gd.ca.cy_544 af.nl.ro_664
+ // [5330]
+ 0x13091cac, 0x1c6e1e07, 0x231918a4, 0x03002804, // mr.hi.bh_632 ms.hmn.id_432 ga.gl.ca_433 sw.nl.un_320
+ 0x0e020604, 0x206432a4, 0x1a6407ad, 0x2032160d, // de.da.is_332 bs.lg.sq_433 it.lg.tl_643 hr.bs.sq_554
+ 0x21002029, 0x0613250c, 0x032555a9, 0x1e2021af, // sq.jw.un_960 eu.et.de_543 rw.eu.nl_544 jw.sq.ms_655
+ 0x09550e08, 0x082002af, 0x11061fa4, 0x4a0c6804, // is.rw.pl_443 da.sq.no_655 cy.de.ro_433 ig.sv.yo_332
+ // [5340]
+ 0x1f002321, 0x3f55070c, 0x100907a4, 0x1f3b07ad, // ca.cy.un_860 it.rw.af_543 it.pl.lt_433 it.so.cy_643
+ 0x16042904, 0x011803a0, 0x103b29a0, 0x190113a4, // sl.fi.hr_332 nl.ga.en_322 sl.so.lt_322 et.en.gl_433
+ 0x6e2b4aa0, 0x0c002d0d, 0x35006b02, 0x133f0c08, // yo.vi.hmn_322 sk.sv.un_540 ceb.zu.un_220 sv.af.et_443
+ 0x030764a7, 0x100b2da0, 0x3b000729, 0x64002108, // lg.it.nl_532 sk.es.lt_322 it.so.un_960 jw.lg.un_430
+ // [5350]
+ 0x3f0c350c, 0x6e0a1f0c, 0x13232508, 0x285521a9, // zu.sv.af_543 cy.pt.hmn_543 eu.ca.et_443 jw.rw.sw_544
+ 0x1906050c, 0x042013ad, 0x2118280c, 0x10002d04, // fr.de.gl_543 et.sq.fi_643 sw.ga.jw_543 sk.lt.un_320
+ 0x101f3b13, 0x3b00040c, 0x0a0b19ad, 0x6b001a11, // so.cy.lt_665 fi.so.un_530 gl.es.pt_643 tl.ceb.un_630
+ 0x190a0c0c, 0x550c2da0, 0x130c2d07, 0x08171014, // sv.pt.gl_543 sk.sv.rw_322 sk.sv.et_432 be.sr.uk_666
+ // [5360]
+ 0x52003504, 0x68190b13, 0x55282907, 0x3f036455, // zu.ha.un_320 es.gl.ig_665 sl.sw.rw_432 lg.nl.af_442
+ 0x32060fa0, 0x4a00212a, 0x554a6812, 0x520b5504, // lv.de.bs_322 jw.yo.un_970 ig.yo.rw_654 rw.es.ha_332
+ 0x02005513, 0x1b646808, 0x101b4aee, 0x551e4aa4, // rw.da.un_650 ig.lg.tr_443 yo.tr.lt_422 yo.ms.rw_433
+ 0x11000307, 0x532105a6, 0x32002018, 0x0b4a6808, // nl.ro.un_420 fr.jw.ht_521 sq.bs.un_740 ig.yo.es_443
+ // [5370]
+ 0x35684aec, 0x190b0207, 0x06110a08, 0x122911a0, // yo.ig.zu_644 da.es.gl_432 pt.ro.de_443 ro.sl.hu_322
+ 0x28092913, 0x0e180d12, 0x02003513, 0x18001f0e, // sl.pl.sw_665 cs.ga.is_654 zu.da.un_650 cy.ga.un_550
+ 0x3b00011b, 0x11000735, 0x3b180913, 0x190306a4, // en.so.un_770 bg.ro.un_A90 pl.ga.so_665 de.nl.gl_433
+ 0x52646b04, 0x1e132107, 0x52001a1b, 0x211b1207, // ceb.lg.ha_332 jw.et.ms_432 tl.ha.un_770 hu.tr.jw_432
+ // [5380]
+ 0x0700350d, 0x681b5308, 0x2500120d, 0x3500022b, // zu.it.un_540 ht.tr.ig_443 hu.eu.un_540 da.zu.un_980
+ 0x1e022505, 0x01062ba0, 0x1e3b1a12, 0x03080e08, // eu.da.ms_333 vi.de.en_322 tl.so.ms_654 is.no.nl_443
+ 0x1b1e21a4, 0x093b6e0c, 0x52190bec, 0x0b0223ee, // jw.ms.tr_433 hmn.so.pl_543 es.gl.ha_644 ca.da.es_422
+ 0x02001807, 0x09003b0e, 0x08060205, 0x5331120c, // ga.da.un_420 so.pl.un_550 da.de.no_333 hu.az.ht_543
+ // [5390]
+ 0x0709110c, 0x0a1925ad, 0x25190e0c, 0x066b1aad, // ro.pl.it_543 eu.gl.pt_643 is.gl.eu_543 tl.ceb.de_643
+ 0x12092512, 0x201f11ad, 0x1c6b53a0, 0x06002307, // eu.pl.hu_654 ro.cy.sq_643 ht.ceb.id_322 ca.de.un_420
+ 0x230621a4, 0x21000607, 0x251710af, 0x110925a0, // jw.de.ca_433 de.jw.un_420 lt.sr.eu_655 eu.pl.ro_322
+ 0x552a25a4, 0x3f081208, 0x4a0512a9, 0x040513a0, // eu.mt.rw_433 hu.no.af_443 hu.fr.yo_544 et.fr.fi_322
+ // [53a0]
+ 0x02185504, 0x68004a21, 0x230a0e05, 0x55640611, // rw.ga.da_332 yo.ig.un_860 is.pt.ca_333 de.lg.rw_653
+ 0x0c030502, 0x4a07230c, 0x040a17a6, 0x1c1a2b11, // fr.nl.sv_222 ca.it.yo_543 sr.mk.ru_521 vi.tl.id_653
+ 0x1b0d55a0, 0x10001313, 0x086b270d, 0x101b0f60, // rw.cs.tr_322 et.lt.un_650 gd.ceb.no_554 lv.tr.lt_664
+ 0x13041eee, 0x6e033f0d, 0x1e1c35a0, 0x1b06210c, // ms.fi.et_422 af.nl.hmn_554 zu.id.ms_322 jw.de.tr_543
+ // [53b0]
+ 0x1c1a1e07, 0x29004a04, 0x683506af, 0x6b2b010c, // ms.tl.id_432 yo.sl.un_320 de.zu.ig_655 en.vi.ceb_543
+ 0x0b1f100d, 0x0c121fad, 0x08023502, 0x07001f23, // lt.cy.es_554 cy.hu.sv_643 zu.da.no_222 cy.it.un_880
+ 0x06202aa9, 0x03350612, 0x1c1225ee, 0x0c061fac, // mt.sq.de_544 de.zu.nl_654 eu.hu.id_422 cy.de.sv_632
+ 0x0a001f20, 0x0c002018, 0x52071fa4, 0x08000402, // cy.pt.un_850 sq.sv.un_740 cy.it.ha_433 ru.uk.un_220
+ // [53c0]
+ 0x016b1007, 0x68642508, 0x10080405, 0x0700252c, // lt.ceb.en_432 eu.lg.ig_443 ru.uk.be_333 eu.it.un_990
+ 0x29311ba9, 0x68185512, 0x20001e13, 0x190b01ee, // tr.az.sl_544 rw.ga.ig_654 ms.sq.un_650 en.es.gl_422
+ 0x130601a6, 0x110704a9, 0x20000d20, 0x0e2a1609, // en.de.et_521 ru.bg.ro_544 cs.sq.un_850 hr.mt.is_444
+ 0x101f1ca0, 0x171606a4, 0x21066404, 0x1e12520b, // id.cy.lt_322 de.hr.sr_433 lg.de.jw_332 ha.hu.ms_542
+ // [53d0]
+ 0x0d002304, 0x04202308, 0x1c061e12, 0x23082007, // ca.cs.un_320 ca.sq.fi_443 ms.de.id_654 sq.no.ca_432
+ 0x2300530d, 0x311f0e0c, 0x1255680d, 0x551a3b08, // ht.ca.un_540 is.cy.az_543 ig.rw.hu_554 so.tl.rw_443
+ 0x04202105, 0x31011802, 0x28042007, 0x1b250e08, // jw.sq.fi_333 ga.en.az_222 sq.fi.sw_432 is.eu.tr_443
+ 0x2325280c, 0x041052ad, 0x1a0e2009, 0x0f10530b, // sw.eu.ca_543 ha.lt.fi_643 sq.is.tl_444 ht.lt.lv_542
+ // [53e0]
+ 0x355225a4, 0x051e5302, 0x3b2717ee, 0x521e1fa4, // eu.ha.zu_433 ht.ms.fr_222 sr.gd.so_422 cy.ms.ha_433
+ 0x290917a0, 0x12135213, 0x231101af, 0x012927a4, // sr.pl.sl_322 ha.et.hu_665 en.ro.ca_655 gd.sl.en_433
+ 0x1f002a04, 0x180327af, 0x012311a4, 0x020b6b0c, // mt.cy.un_320 gd.nl.ga_655 ro.ca.en_433 ceb.es.da_543
+ 0x2100071a, 0x52002908, 0x094a5212, 0x05000c13, // it.jw.un_760 sl.ha.un_430 ha.yo.pl_654 sv.fr.un_650
+ // [53f0]
+ 0x28073508, 0x52051ea0, 0x1353250b, 0x531725ee, // zu.it.sw_443 ms.fr.ha_322 eu.ht.et_542 eu.sr.ht_422
+ 0x1b250408, 0x25073ba0, 0x070e0aec, 0x1b4a6809, // fi.eu.tr_443 so.it.eu_322 pt.is.it_644 ig.yo.tr_444
+ 0x6800022c, 0x04201b13, 0x2a000804, 0x1b00531a, // da.ig.un_990 tr.sq.fi_665 no.mt.un_320 ht.tr.un_760
+ 0x2b2301ad, 0x070c25af, 0x55521f07, 0x2b1c01a4, // en.ca.vi_643 eu.sv.it_655 cy.ha.rw_432 en.id.vi_433
+
+ // [5400]
+ 0x11005302, 0x120d350c, 0x68000822, 0x280d03a4, // ht.ro.un_220 zu.cs.hu_543 no.ig.un_870 nl.cs.sw_433
+ 0x35164a0c, 0x07000622, 0x12003522, 0x681b3b0e, // yo.hr.zu_543 de.it.un_870 zu.hu.un_870 so.tr.ig_555
+ 0x0607050c, 0x525328ee, 0x18080c0d, 0x130852a9, // fr.it.de_543 sw.ht.ha_422 sv.no.ga_554 ha.no.et_544
+ 0x04171819, 0x324a13ad, 0x23684aec, 0x52005512, // ar.sr.ru_765 et.yo.bs_643 yo.ig.ca_644 rw.ha.un_640
+ // [5410]
+ 0x4a685355, 0x68132309, 0x0635290c, 0x55004a07, // ht.ig.yo_442 ca.et.ig_444 sl.zu.de_543 yo.rw.un_420
+ 0x3f201112, 0x0c0504ee, 0x0100682b, 0x042011ee, // ro.sq.af_654 fi.fr.sv_422 ig.en.un_980 ro.sq.fi_422
+ 0x02170cec, 0x20681313, 0x190b010c, 0x0768010b, // sv.sr.da_644 et.ig.sq_665 en.es.gl_543 en.ig.it_542
+ 0x2d004a13, 0x202552a0, 0x0e001704, 0x19313b0d, // yo.sk.un_650 ha.eu.sq_322 sr.is.un_320 so.az.gl_554
+ // [5420]
+ 0x092564a4, 0x35000a35, 0x0d0655ad, 0x03010ca4, // lg.eu.pl_433 pt.zu.un_A90 rw.de.cs_643 sv.en.nl_433
+ 0x0c000f18, 0x0e000c2b, 0x050e01af, 0x1029130c, // lv.sv.un_740 sv.is.un_980 en.is.fr_655 et.sl.lt_543
+ 0x522313a9, 0x02270812, 0x1255110b, 0x32272d0c, // et.ca.ha_544 no.gd.da_654 ro.rw.hu_542 sk.gd.bs_543
+ 0x321601ee, 0x070111a9, 0x20556860, 0x123f1fa9, // en.hr.bs_422 ro.en.it_544 ig.rw.sq_664 cy.af.hu_544
+ // [5430]
+ 0x0600181a, 0x061203a9, 0x252d1112, 0x02061208, // ga.de.un_760 nl.hu.de_544 ro.sk.eu_654 hu.de.da_443
+ 0x2d0d0fa6, 0x0713010c, 0x20190bec, 0x250711a4, // lv.cs.sk_521 en.et.it_543 es.gl.sq_644 ro.it.eu_433
+ 0x29006807, 0x12006e0d, 0x6e000c0e, 0x11095505, // ig.sl.un_420 hmn.hu.un_540 sv.hmn.un_550 rw.pl.ro_333
+ 0x6e000c12, 0x1a003f1a, 0x0c001012, 0x0a00530c, // sv.hmn.un_640 af.tl.un_760 lt.sv.un_640 ht.pt.un_530
+ // [5440]
+ 0x28682a0d, 0x28682907, 0x280152a0, 0x291668ac, // mt.ig.sw_554 sl.ig.sw_432 ha.en.sw_322 ig.hr.sl_632
+ 0x190a0ea0, 0x685521a9, 0x11050760, 0x53002504, // is.pt.gl_322 jw.rw.ig_544 it.fr.ro_664 eu.ht.un_320
+ 0x680555a4, 0x16001019, 0x030f68ee, 0x25070607, // rw.fr.ig_433 lt.hr.un_750 ig.lv.nl_422 de.it.eu_432
+ 0x25000f13, 0x283553ee, 0x12002805, 0x3500060c, // lv.eu.un_650 ht.zu.sw_422 sw.hu.un_330 de.zu.un_530
+ // [5450]
+ 0x0a0523ec, 0x0f006813, 0x291603ee, 0x1a6b18ee, // ca.fr.pt_644 ig.lv.un_650 nl.hr.sl_422 ga.ceb.tl_422
+ 0x08001c02, 0x0c353baf, 0x4a00210c, 0x08273fa0, // id.no.un_220 so.zu.sv_655 jw.yo.un_530 af.gd.no_322
+ 0x531628ee, 0x04063512, 0x1b3552af, 0x4a002807, // sw.hr.ht_422 zu.de.fi_654 ha.zu.tr_655 sw.yo.un_420
+ 0x52534aaf, 0x32550b0c, 0x1e251702, 0x6b311b13, // yo.ht.ha_655 es.rw.bs_543 sr.eu.ms_222 tr.az.ceb_665
+ // [5460]
+ 0x172568ad, 0x2d320d07, 0x1f003b08, 0x28001a0e, // ig.eu.sr_643 cs.bs.sk_432 so.cy.un_430 tl.sw.un_550
+ 0x12001602, 0x31000a04, 0x13090dac, 0x2506550c, // hr.hu.un_220 pt.az.un_320 ne.hi.bh_632 rw.de.eu_543
+ 0x020608a4, 0x533504a0, 0x1200550d, 0x32160ba4, // no.de.da_433 fi.zu.ht_322 rw.hu.un_540 es.hr.bs_433
+ 0x311b1fa4, 0x04351aa4, 0x11104a02, 0x042564a4, // cy.tr.az_433 tl.zu.fi_433 yo.lt.ro_222 lg.eu.fi_433
+ // [5470]
+ 0x28093505, 0x55640da4, 0x311b08a9, 0x2302110c, // zu.pl.sw_333 cs.lg.rw_433 no.tr.az_544 ro.da.ca_543
+ 0x31006b19, 0x184a0daf, 0x31001a04, 0x0811060d, // ceb.az.un_750 cs.yo.ga_655 tl.az.un_320 de.ro.no_554
+ 0x09022305, 0x31001f13, 0x5500120d, 0x681b5508, // ca.da.pl_333 cy.az.un_650 hu.rw.un_540 rw.tr.ig_443
+ 0x03000108, 0x190a05af, 0x2d0d0ea9, 0x13000212, // en.nl.un_430 fr.pt.gl_655 is.cs.sk_544 da.et.un_640
+ // [5480]
+ 0x270e1812, 0x11232508, 0x29001c04, 0x1c32210d, // ga.is.gd_654 eu.ca.ro_443 id.sl.un_320 jw.bs.id_554
+ 0x09002519, 0x0601230c, 0x191b0aec, 0x135304ad, // eu.pl.un_750 ca.en.de_543 pt.tr.gl_644 fi.ht.et_643
+ 0x10170713, 0x28001f19, 0x0c2a230c, 0x211205a6, // bg.sr.be_665 cy.sw.un_750 ca.mt.sv_543 fr.hu.jw_521
+ 0x020508a4, 0x062308a7, 0x2d120cac, 0x021f0807, // no.fr.da_433 no.ca.de_532 sv.hu.sk_632 no.cy.da_432
+ // [5490]
+ 0x17000c13, 0x18201313, 0x0600051a, 0x03060412, // sv.sr.un_650 et.sq.ga_665 fr.de.un_760 fi.de.nl_654
+ 0x0e0f13ad, 0x1e1c06ac, 0x0f110e08, 0x0c000305, // et.lv.is_643 de.id.ms_632 is.ro.lv_443 nl.sv.un_330
+ 0x270e010c, 0x3f00041a, 0x090c0812, 0x1f06230c, // en.is.gd_543 fi.af.un_760 no.sv.pl_654 ca.de.cy_543
+ 0x53002514, 0x111310ec, 0x21003b1b, 0x1b000408, // eu.ht.un_660 lt.et.ro_644 so.jw.un_770 fi.tr.un_430
+ // [54a0]
+ 0x21101c0c, 0x6b1a68ac, 0x116b6e08, 0x05181311, // id.lt.jw_543 ig.tl.ceb_632 hmn.ceb.ro_443 et.ga.fr_653
+ 0x28000b0e, 0x0f001e13, 0x1b06030d, 0x050a12ad, // es.sw.un_550 ms.lv.un_650 nl.de.tr_554 hu.pt.fr_643
+ 0x230a0e55, 0x12001302, 0x06031f13, 0x4a0e1805, // is.pt.ca_442 et.hu.un_220 cy.nl.de_665 ga.is.yo_333
+ 0x322820a6, 0x1f2b35ac, 0x52046e12, 0x1a0405a0, // sq.sw.bs_521 zu.vi.cy_632 hmn.fi.ha_654 fr.fi.tl_322
+ // [54b0]
+ 0x120f6808, 0x35001221, 0x1a1f2111, 0x6e1001a9, // ig.lv.hu_443 hu.zu.un_860 jw.cy.tl_653 en.lt.hmn_544
+ 0x11101713, 0x21082011, 0x06000c02, 0x06041b07, // sr.be.ro_665 sq.no.jw_653 sv.de.un_220 tr.fi.de_432
+ 0x31041ba4, 0x03041ba7, 0x1b02030c, 0x21002004, // tr.fi.az_433 tr.fi.nl_532 nl.da.tr_543 sq.jw.un_320
+ 0x16002702, 0x056b0709, 0x05000607, 0x0e3217a6, // gd.hr.un_220 it.ceb.fr_444 de.fr.un_420 sr.bs.is_521
+ // [54c0]
+ 0x11006408, 0x2500640d, 0x08020eee, 0x103b35a4, // lg.ro.un_430 lg.eu.un_540 is.da.no_422 zu.so.lt_433
+ 0x1a5505ad, 0x31031b07, 0x130425ec, 0x020e0c09, // fr.rw.tl_643 tr.nl.az_432 eu.fi.et_644 sv.is.da_444
+ 0x09013f12, 0x091f1a12, 0x3b250b0e, 0x03123bee, // af.en.pl_654 tl.cy.pl_654 es.eu.so_555 so.hu.nl_422
+ 0x103f0d11, 0x4a283505, 0x29003505, 0x2d1b25ec, // cs.af.lt_653 zu.sw.yo_333 zu.sl.un_330 eu.tr.sk_644
+ // [54d0]
+ 0x64063ba4, 0x551f13a7, 0x1e211c55, 0x1a073b0c, // so.de.lg_433 et.cy.rw_532 id.jw.ms_442 so.it.tl_543
+ 0x641368a9, 0x0b0a07a4, 0x043f1305, 0x096b4a04, // ig.et.lg_544 it.pt.es_433 et.af.fi_333 yo.ceb.pl_332
+ 0x283b10ec, 0x35526402, 0x0a23195a, 0x082a1205, // lt.so.sw_644 lg.ha.zu_222 gl.ca.pt_553 hu.mt.no_333
+ 0x6b005212, 0x6b285209, 0x35000722, 0x061809ad, // ha.ceb.un_640 ha.sw.ceb_444 it.zu.un_870 pl.ga.de_643
+ // [54e0]
+ 0x0d1c1312, 0x2718310c, 0x04554aaf, 0x4a001b1b, // bh.mr.ne_654 az.ga.gd_543 yo.rw.fi_655 tr.yo.un_770
+ 0x1200040e, 0x212568ad, 0x08022a0c, 0x20533f08, // fi.hu.un_550 ig.eu.jw_643 mt.da.no_543 af.ht.sq_443
+ 0x211b640d, 0x191325ee, 0x1c090dac, 0x3f002018, // lg.tr.jw_554 eu.et.gl_422 ne.hi.mr_632 sq.af.un_740
+ 0x0a000519, 0x08020c0d, 0x533f230c, 0x0c003f02, // fr.pt.un_750 sv.da.no_554 ca.af.ht_543 af.sv.un_220
+ // [54f0]
+ 0x031a3faf, 0x1e1c10a4, 0x020f11ad, 0x3200550c, // af.tl.nl_655 lt.id.ms_433 ro.lv.da_643 rw.bs.un_530
+ 0x3f002021, 0x2500272b, 0x0d00521a, 0x35004a12, // sq.af.un_860 gd.eu.un_980 ha.cs.un_760 yo.zu.un_640
+ 0x35256ba4, 0x06032709, 0x04110a05, 0x08000e05, // ceb.eu.zu_433 gd.nl.de_444 mk.ro.ru_333 is.no.un_330
+ 0x126b1a0d, 0x2d0d07a6, 0x212518a4, 0x183f1105, // tl.ceb.hu_554 it.cs.sk_521 ga.eu.jw_433 ro.af.ga_333
+ // [5500]
+ 0x252307a0, 0x0d006b04, 0x28000a05, 0x08020614, // it.ca.eu_322 ceb.cs.un_320 pt.sw.un_330 de.da.no_666
+ 0x116b27a0, 0x0e6b5307, 0x0429090c, 0x04642508, // gd.ceb.ro_322 ht.ceb.is_432 pl.sl.fi_543 eu.lg.fi_443
+ 0x350153a7, 0x681a2507, 0x2d3b6405, 0x04642512, // ht.en.zu_532 eu.tl.ig_432 lg.so.sk_333 eu.lg.fi_654
+ 0x0d356eac, 0x0c081b09, 0x0c310808, 0x04191f11, // hmn.zu.cs_632 tr.no.sv_444 no.az.sv_443 cy.gl.fi_653
+ // [5510]
+ 0x68130414, 0x08004a0d, 0x4a6468a0, 0x04162da9, // fi.et.ig_666 yo.no.un_540 ig.lg.yo_322 sk.hr.fi_544
+ 0x080b310c, 0x100464a4, 0x16002708, 0x3b1b045a, // az.es.no_543 lg.fi.lt_433 gd.hr.un_430 fi.tr.so_553
+ 0x0d092d07, 0x130c04a6, 0x31000c0d, 0x12532812, // sk.pl.cs_432 fi.sv.et_521 sv.az.un_540 sw.ht.hu_654
+ 0x21283bad, 0x0a0f0d11, 0x23311b0d, 0x353b6807, // so.sw.jw_643 cs.lv.pt_653 tr.az.ca_554 ig.so.zu_432
+ // [5520]
+ 0x31081b12, 0x530568ec, 0x16002504, 0x2a006404, // tr.no.az_654 ig.fr.ht_644 eu.hr.un_320 lg.mt.un_320
+ 0x68002108, 0x683521a4, 0x53006e12, 0x3b006821, // jw.ig.un_430 jw.zu.ig_433 hmn.ht.un_640 ig.so.un_860
+ 0x6e1e1c13, 0x043b03a9, 0x253b04ad, 0x040925ee, // id.ms.hmn_665 nl.so.fi_544 fi.so.eu_643 eu.pl.fi_422
+ 0x281e1c0c, 0x03043b12, 0x043f2509, 0x25003b22, // id.ms.sw_543 so.fi.nl_654 eu.af.fi_444 so.eu.un_870
+ // [5530]
+ 0x31003207, 0x55281a12, 0x1f180911, 0x090a2da4, // bs.az.un_420 tl.sw.rw_654 pl.ga.cy_653 sk.pt.pl_433
+ 0x130f1ba4, 0x080c1eec, 0x35315505, 0x3b004a13, // tr.lv.et_433 ms.sv.no_644 rw.az.zu_333 yo.so.un_650
+ 0x25003b18, 0x10526412, 0x033b040d, 0x645355ad, // so.eu.un_740 lg.ha.lt_654 fi.so.nl_554 rw.ht.lg_643
+ 0x08200ead, 0x3b00042a, 0x2853100c, 0x0f022508, // is.sq.no_643 fi.so.un_970 lt.ht.sw_543 eu.da.lv_443
+ // [5540]
+ 0x2d0d0fa9, 0x08021bec, 0x6400280d, 0x6b003204, // lv.cs.sk_544 tr.da.no_644 sw.lg.un_540 bs.ceb.un_320
+ 0x28032b12, 0x08021baf, 0x06102304, 0x2b190513, // vi.nl.sw_654 tr.da.no_655 ca.lt.de_332 fr.gl.vi_665
+ 0x2b00051a, 0x081b0208, 0x2b071112, 0x23072bad, // fr.vi.un_760 da.tr.no_443 ro.it.vi_654 vi.it.ca_643
+ 0x19000e0c, 0x2a130708, 0x08021b12, 0x1b00080d, // is.gl.un_530 it.et.mt_443 tr.da.no_654 no.tr.un_540
+ // [5550]
+ 0x17000802, 0x3f000504, 0x2b051f0c, 0x31051bec, // uk.sr.un_220 fr.af.un_320 cy.fr.vi_543 tr.fr.az_644
+ 0x021b0808, 0x04052b08, 0x1c130da4, 0x2b1104a9, // no.tr.da_443 vi.fr.fi_443 ne.bh.mr_433 fi.ro.vi_544
+ 0x042b05af, 0x04005208, 0x23002b07, 0x3b000e12, // fr.vi.fi_655 ha.fi.un_430 vi.ca.un_420 is.so.un_640
+ 0x0c000802, 0x1c130dad, 0x531f05ad, 0x01005302, // no.sv.un_220 ne.bh.mr_643 fr.cy.ht_643 ht.en.un_220
+ // [5560]
+ 0x23110404, 0x3b101aec, 0x323b1aa4, 0x012321a4, // fi.ro.ca_332 tl.lt.so_644 tl.so.bs_433 jw.ca.en_433
+ 0x3f1f0308, 0x256b550c, 0x6b255311, 0x55080cad, // nl.cy.af_443 rw.ceb.eu_543 ht.eu.ceb_653 sv.no.rw_643
+ 0x0a000511, 0x531b0507, 0x531c05ee, 0x05000a1a, // fr.pt.un_630 fr.tr.ht_432 fr.id.ht_422 pt.fr.un_760
+ 0x3b1a5512, 0x35006407, 0x0c002707, 0x6e003f2b, // rw.tl.so_654 lg.zu.un_420 gd.sv.un_420 af.hmn.un_980
+ // [5570]
+ 0x3f6e0811, 0x0d03110c, 0x20001e11, 0x19182760, // no.hmn.af_653 ro.nl.cs_543 ms.sq.un_630 gd.ga.gl_664
+ 0x04120faf, 0x27086ead, 0x052718ec, 0x031c3fad, // lv.hu.fi_655 hmn.no.gd_643 ga.gd.fr_644 af.id.nl_643
+ 0x6e052314, 0x2a001a04, 0x040d6b0c, 0x6b3528a6, // ca.fr.hmn_666 tl.mt.un_320 ceb.cs.fi_543 sw.zu.ceb_521
+ 0x161c290c, 0x213b20ad, 0x2d090c09, 0x10556405, // sl.id.hr_543 sq.so.jw_643 sv.pl.sk_444 lg.rw.lt_333
+ // [5580]
+ 0x2b086813, 0x101f0411, 0x2a070407, 0x11000622, // ig.no.vi_665 fi.cy.lt_653 fi.it.mt_432 de.ro.un_870
+ 0x0f1210a9, 0x6b1e1cec, 0x2b006e22, 0x211e050c, // lt.hu.lv_544 id.ms.ceb_644 hmn.vi.un_870 fr.ms.jw_543
+ 0x1a2052a0, 0x0e003504, 0x29005202, 0x25521baf, // ha.sq.tl_322 zu.is.un_320 ha.sl.un_220 tr.ha.eu_655
+ 0x1b3108a4, 0x1c1e520b, 0x18283ba0, 0x070911ad, // no.az.tr_433 ha.ms.id_542 so.sw.ga_322 ro.pl.it_643
+ // [5590]
+ 0x1c000f02, 0x6e002b2a, 0x4a5352a4, 0x190a0daf, // lv.id.un_220 vi.hmn.un_970 ha.ht.yo_433 cs.pt.gl_655
+ 0x35006e1a, 0x1304525a, 0x35080e02, 0x290e090c, // hmn.zu.un_760 ha.fi.et_553 is.no.zu_222 pl.is.sl_543
+ 0x3f25100c, 0x030507a4, 0x351b5555, 0x1c0d09a9, // lt.eu.af_543 it.fr.nl_433 rw.tr.zu_442 hi.ne.mr_544
+ 0x2a105505, 0x090d3b07, 0x080c1c02, 0x19235302, // rw.lt.mt_333 so.cs.pl_432 id.sv.no_222 ht.ca.gl_222
+ // [55a0]
+ 0x170c21a4, 0x2b1c3fa7, 0x06070e0c, 0x211c08a4, // jw.sv.sr_433 af.id.vi_532 is.it.de_543 no.id.jw_433
+ 0x061368a0, 0x1e000c02, 0x13002b19, 0x531001a4, // ig.et.de_322 sv.ms.un_220 vi.et.un_750 en.lt.ht_433
+ 0x25046812, 0x13046b14, 0x132d0408, 0x03085311, // ig.fi.eu_654 ceb.fi.et_666 fi.sk.et_443 ht.no.nl_653
+ 0x2b000104, 0x1c004a04, 0x6e272355, 0x110a07a0, // en.vi.un_320 yo.id.un_320 ca.gd.hmn_442 it.pt.ro_322
+ // [55b0]
+ 0x120c1bec, 0x132b2807, 0x2a211905, 0x27530ea4, // tr.sv.hu_644 sw.vi.et_432 gl.jw.mt_333 is.ht.gd_433
+ 0x0b111908, 0x130c0413, 0x6e6b3502, 0x20000b09, // gl.ro.es_443 fi.sv.et_665 zu.ceb.hmn_222 es.sq.un_440
+ 0x355521ee, 0x02203f12, 0x0400642a, 0x292a0eee, // jw.rw.zu_422 af.sq.da_654 lg.fi.un_970 is.mt.sl_422
+ 0x1c0d09ee, 0x181b0e04, 0x0802030c, 0x1b000e08, // hi.ne.mr_422 is.tr.ga_332 nl.da.no_543 is.tr.un_430
+ // [55c0]
+ 0x25001705, 0x21132d08, 0x06003205, 0x1f35090c, // sr.eu.un_330 sk.et.jw_443 bs.de.un_330 pl.zu.cy_543
+ 0x11550b05, 0x280518a4, 0x682a4aad, 0x520306ad, // es.rw.ro_333 ga.fr.sw_433 yo.mt.ig_643 de.nl.ha_643
+ 0x3f030faf, 0x0304210c, 0x092d3f04, 0x133b030c, // lv.nl.af_655 jw.fi.nl_543 af.sk.pl_332 nl.so.et_543
+ 0x1c1712ad, 0x080204a0, 0x0f4a68ec, 0x211268ec, // hu.sr.id_643 fi.da.no_322 ig.yo.lv_644 ig.hu.jw_644
+ // [55d0]
+ 0x2a18010c, 0x5200010d, 0x0f101b08, 0x0a001012, // en.ga.mt_543 en.ha.un_540 tr.lt.lv_443 lt.pt.un_640
+ 0x1300682b, 0x120f10a4, 0x52006808, 0x64001c1a, // ig.et.un_980 lt.lv.hu_433 ig.ha.un_430 id.lg.un_760
+ 0x25556b55, 0x1b033f0d, 0x2a02010d, 0x28002a12, // ceb.rw.eu_442 af.nl.tr_554 en.da.mt_554 mt.sw.un_640
+ 0x0d112da0, 0x08520207, 0x6e0a13ee, 0x09281ca4, // sk.ro.cs_322 da.ha.no_432 et.pt.hmn_422 id.sw.pl_433
+ // [55e0]
+ 0x0c003107, 0x21031fa4, 0x5200201b, 0x68283505, // az.sv.un_420 cy.nl.jw_433 sq.ha.un_770 zu.sw.ig_333
+ 0x17095208, 0x13556ba4, 0x0a11100c, 0x160752ee, // ha.pl.sr_443 ceb.rw.et_433 be.ro.mk_543 ha.it.hr_422
+ 0x20001107, 0x201152a0, 0x0f2a070c, 0x21045308, // ro.sq.un_420 ha.ro.sq_322 it.mt.lv_543 ht.fi.jw_443
+ 0x120c2aa0, 0x4a2a070c, 0x0c002904, 0x0a521107, // mt.sv.hu_322 it.mt.yo_543 sl.sv.un_320 ro.ha.pt_432
+ // [55f0]
+ 0x6b2a2dec, 0x0b00111a, 0x21003f07, 0x52001102, // sk.mt.ceb_644 ro.es.un_760 af.jw.un_420 ro.ha.un_220
+ 0x0a002302, 0x12131f08, 0x29001012, 0x13251260, // ca.pt.un_220 cy.et.hu_443 lt.sl.un_640 hu.eu.et_664
+ 0x10000919, 0x11120aa0, 0x0d1c09a7, 0x356812a4, // pl.lt.un_750 pt.hu.ro_322 hi.mr.ne_532 hu.ig.zu_433
+ 0x3f003b13, 0x0a100702, 0x1300680d, 0x2d110d0c, // so.af.un_650 bg.be.mk_222 ig.et.un_540 cs.ro.sk_543
+ // [5600]
+ 0x0e001e02, 0x2a0628af, 0x2d290913, 0x1f294aad, // ms.is.un_220 sw.de.mt_655 pl.sl.sk_665 yo.sl.cy_643
+ 0x12003105, 0x09530407, 0x521312a7, 0x3f004a1a, // az.hu.un_330 fi.ht.pl_432 hu.et.ha_532 yo.af.un_760
+ 0x196b0bad, 0x23280714, 0x0e1b2008, 0x0455530c, // es.ceb.gl_643 it.sw.ca_666 sq.tr.is_443 ht.rw.fi_543
+ 0x31641b0d, 0x130e23ee, 0x211b3114, 0x080a05a9, // tr.lg.az_554 ca.is.et_422 az.tr.jw_666 fr.pt.no_544
+ // [5610]
+ 0x25100ead, 0x5500080d, 0x282555a9, 0x2b000722, // is.lt.eu_643 no.rw.un_540 rw.eu.sw_544 it.vi.un_870
+ 0x03681f0c, 0x0e0635ec, 0x0f000320, 0x10530408, // cy.ig.nl_543 zu.de.is_644 nl.lv.un_850 fi.ht.lt_443
+ 0x04001b08, 0x09001704, 0x1e021ca4, 0x201b6411, // tr.fi.un_430 sr.pl.un_320 id.da.ms_433 lg.tr.sq_653
+ 0x0e051ba4, 0x0c080da0, 0x063f1ca4, 0x55131a0c, // tr.fr.is_433 cs.no.sv_322 id.af.de_433 tl.et.rw_543
+ // [5620]
+ 0x0c352a04, 0x1a64550c, 0x350264a4, 0x060b55a4, // mt.zu.sv_332 rw.lg.tl_543 lg.da.zu_433 rw.es.de_433
+ 0x063564a4, 0x0e003b07, 0x55061a05, 0x206e0112, // lg.zu.de_433 so.is.un_420 tl.de.rw_333 en.hmn.sq_654
+ 0x16002307, 0x08040b02, 0x0c000502, 0x5531210c, // ca.hr.un_420 es.fi.no_222 fr.sv.un_220 jw.az.rw_543
+ 0x0e2909a4, 0x1b033205, 0x4a000d04, 0x1e1c2102, // pl.sl.is_433 bs.nl.tr_333 cs.yo.un_320 jw.id.ms_222
+ // [5630]
+ 0x1b35310c, 0x07112d0c, 0x120720af, 0x20280ead, // az.zu.tr_543 sk.ro.it_543 sq.it.hu_655 is.sw.sq_643
+ 0x6b6423ad, 0x3f0453a9, 0x531e3fad, 0x0f1b0512, // ca.lg.ceb_643 ht.fi.af_544 af.ms.ht_643 fr.tr.lv_654
+ 0x08020c08, 0x35250608, 0x68553505, 0x2d000912, // sv.da.no_443 de.eu.zu_443 zu.rw.ig_333 pl.sk.un_640
+ 0x6b002713, 0x0b5511a4, 0x03002009, 0x25015505, // gd.ceb.un_650 ro.rw.es_433 sq.nl.un_440 rw.en.eu_333
+ // [5640]
+ 0x0a080e12, 0x2d080d07, 0x642835ee, 0x350a550c, // is.no.pt_654 cs.no.sk_432 zu.sw.lg_422 rw.pt.zu_543
+ 0x0a251b08, 0x1f0419ee, 0x0c083555, 0x0e1f06a9, // tr.eu.pt_443 gl.fi.cy_422 zu.no.sv_442 de.cy.is_544
+ 0x271f060c, 0x0a230ea9, 0x081f0508, 0x08000c11, // de.cy.gd_543 is.ca.pt_544 fr.cy.no_443 sv.no.un_630
+ 0x1e1b28ad, 0x0c025509, 0x01000a04, 0x2a000f08, // sw.tr.ms_643 rw.da.sv_444 pt.en.un_320 lv.mt.un_430
+ // [5650]
+ 0x1b00250c, 0x0329640c, 0x28646808, 0x0b1f070c, // eu.tr.un_530 lg.sl.nl_543 ig.lg.sw_443 it.cy.es_543
+ 0x080c05ec, 0x05002702, 0x0c000a05, 0x2a000204, // fr.sv.no_644 gd.fr.un_220 pt.sv.un_330 da.mt.un_320
+ 0x2d0d13a4, 0x01091f04, 0x285218a9, 0x52006b02, // et.cs.sk_433 cy.pl.en_332 ga.ha.sw_544 ceb.ha.un_220
+ 0x0d282d07, 0x08040e12, 0x35550255, 0x0800060e, // sk.sw.cs_432 is.fi.no_654 da.rw.zu_442 de.no.un_550
+ // [5660]
+ 0x10000f07, 0x03001b02, 0x09016ba4, 0x0c00291a, // lv.lt.un_420 tr.nl.un_220 ceb.en.pl_433 sl.sv.un_760
+ 0x3f1306a0, 0x1810270e, 0x080206af, 0x0c080708, // de.et.af_322 gd.lt.ga_555 de.da.no_655 it.no.sv_443
+ 0x0a00072c, 0x0b0507a4, 0x320916ee, 0x06293f02, // bg.mk.un_990 it.fr.es_433 hr.pl.bs_422 af.sl.de_222
+ 0x061225a7, 0x070b2314, 0x0d052113, 0x2d210d12, // eu.hu.de_532 ca.es.it_666 jw.fr.cs_665 cs.jw.sk_654
+ // [5670]
+ 0x6b011fa9, 0x1a023f04, 0x28001a22, 0x03000520, // cy.en.ceb_544 af.da.tl_332 tl.sw.un_870 fr.nl.un_850
+ 0x6b352112, 0x08110308, 0x354a5514, 0x3b00290e, // jw.zu.ceb_654 nl.ro.no_443 rw.yo.zu_666 sl.so.un_550
+ 0x05002311, 0x25000a04, 0x21001012, 0x2d0d4a05, // ca.fr.un_630 pt.eu.un_320 lt.jw.un_640 yo.cs.sk_333
+ 0x64351902, 0x25271204, 0x6800211a, 0x1008110c, // gl.zu.lg_222 hu.gd.eu_332 jw.ig.un_760 ro.uk.be_543
+ // [5680]
+ 0x06001122, 0x12001704, 0x16001107, 0x4a21050e, // ro.de.un_870 sr.hu.un_320 ro.hr.un_420 fr.jw.yo_555
+ 0x64014aa7, 0x0d131c0c, 0x2d0d05a9, 0x25202112, // yo.en.lg_532 mr.bh.ne_543 fr.cs.sk_544 jw.sq.eu_654
+ 0x17072907, 0x0600550d, 0x28006e13, 0x4a006b04, // sl.it.sr_432 rw.de.un_540 hmn.sw.un_650 ceb.yo.un_320
+ 0x1b002520, 0x272d5507, 0x030b01a9, 0x211e1804, // eu.tr.un_850 rw.sk.gd_432 en.es.nl_544 ga.ms.jw_332
+ // [5690]
+ 0x53003f35, 0x1c041360, 0x320901ac, 0x233f53ec, // af.ht.un_A90 et.fi.id_664 en.pl.bs_632 ht.af.ca_644
+ 0x0c53210c, 0x53001c2b, 0x53233fad, 0x19530a04, // jw.ht.sv_543 id.ht.un_980 af.ca.ht_643 pt.ht.gl_332
+ 0x05532308, 0x04005221, 0x2d180d12, 0x53003f1a, // ca.ht.fr_443 ha.fi.un_860 cs.ga.sk_654 af.ht.un_760
+ 0x1004520c, 0x182d0d0c, 0x27070b08, 0x550e53a9, // ha.fi.lt_543 cs.sk.ga_543 es.it.gd_443 ht.is.rw_544
+ // [56a0]
+ 0x29020ea4, 0x16044aa0, 0x2d0d1809, 0x35016ba0, // is.da.sl_433 yo.fi.hr_322 ga.cs.sk_444 ceb.en.zu_322
+ 0x53113f55, 0x3f005334, 0x0d001709, 0x53006419, // af.ro.ht_442 ht.af.un_A80 sr.cs.un_440 lg.ht.un_750
+ 0x1b00522b, 0x03533f12, 0x18000d21, 0x20000534, // ha.tr.un_980 af.ht.nl_654 cs.ga.un_860 fr.sq.un_A80
+ 0x10001f19, 0x644a35ad, 0x190b35ee, 0x3b0f520c, // cy.lt.un_750 zu.yo.lg_643 zu.es.gl_422 ha.lv.so_543
+ // [56b0]
+ 0x230f0608, 0x09523bad, 0x101304ad, 0x18000d13, // de.lv.ca_443 so.ha.pl_643 fi.et.lt_643 cs.ga.un_650
+ 0x521b4aad, 0x10011f12, 0x09001220, 0x131c095a, // yo.tr.ha_643 cy.en.lt_654 hu.pl.un_850 hi.mr.bh_553
+ 0x251c21a9, 0x2a000512, 0x19000609, 0x09215509, // jw.id.eu_544 fr.mt.un_640 de.gl.un_440 rw.jw.pl_444
+ 0x03023fa9, 0x09000f08, 0x053f2312, 0x231925a4, // af.da.nl_544 lv.pl.un_430 ca.af.fr_654 eu.gl.ca_433
+ // [56c0]
+ 0x351b1208, 0x07192b13, 0x1000522c, 0x3f030e05, // hu.tr.zu_443 vi.gl.it_665 ha.lt.un_990 is.nl.af_333
+ 0x05251012, 0x521108a4, 0x4a2b0fad, 0x031125ad, // lt.eu.fr_654 no.ro.ha_433 lv.vi.yo_643 eu.ro.nl_643
+ 0x2d0d68a0, 0x0d001812, 0x551228af, 0x310c2aa4, // ig.cs.sk_322 ga.cs.un_640 sw.hu.rw_655 mt.sv.az_433
+ 0x0c250808, 0x6e0752ad, 0x1f07190c, 0x0d0f2dec, // no.eu.sv_443 ha.it.hmn_643 gl.it.cy_543 sk.lv.cs_644
+ // [56d0]
+ 0x2b00251b, 0x0a1b110c, 0x28526e0e, 0x2b236ea9, // eu.vi.un_770 ro.tr.pt_543 hmn.ha.sw_555 hmn.ca.vi_544
+ 0x0f11250d, 0x07521f08, 0x55001b20, 0x6e1352ec, // eu.ro.lv_554 cy.ha.it_443 tr.rw.un_850 ha.et.hmn_644
+ 0x2d0d3fa4, 0x03130111, 0x033f20ad, 0x061f0308, // af.cs.sk_433 en.et.nl_653 sq.af.nl_643 nl.cy.de_443
+ 0x040a0f12, 0x130329ee, 0x2b041012, 0x521125af, // lv.pt.fi_654 sl.nl.et_422 lt.fi.vi_654 eu.ro.ha_655
+ // [56e0]
+ 0x1e1c68a4, 0x0c3b0ea6, 0x012d05a0, 0x03001e02, // ig.id.ms_433 is.so.sv_521 fr.sk.en_322 ms.nl.un_220
+ 0x6b01050c, 0x06192507, 0x010d050c, 0x010305ad, // fr.en.ceb_543 eu.gl.de_432 fr.cs.en_543 fr.nl.en_643
+ 0x2a001112, 0x23001008, 0x23053f09, 0x060305a0, // ro.mt.un_640 lt.ca.un_430 af.fr.ca_444 fr.nl.de_322
+ 0x35000304, 0x010607ec, 0x2d0d06ee, 0x1053020c, // nl.zu.un_320 it.de.en_644 de.cs.sk_422 da.ht.lt_543
+ // [56f0]
+ 0x1e271ca9, 0x274a2107, 0x532b52a4, 0x0f6e20a9, // id.gd.ms_544 jw.yo.gd_432 ha.vi.ht_433 sq.hmn.lv_544
+ 0x02253bad, 0x0c251a0c, 0x10290dad, 0x643b1f0d, // so.eu.da_643 tl.eu.sv_543 cs.sl.lt_643 cy.so.lg_554
+ 0x2b522dee, 0x1e2855a0, 0x3b1752a4, 0x1f00530e, // sk.ha.vi_422 rw.sw.ms_322 ha.sr.so_433 ht.cy.un_550
+ 0x68100504, 0x2b1f27a4, 0x1900520b, 0x1e536e0c, // fr.lt.ig_332 gd.cy.vi_433 ha.gl.un_520 hmn.ht.ms_543
+ // [5700]
+ 0x0a00021a, 0x1c521a0c, 0x1f001a1b, 0x251f2708, // da.pt.un_760 tl.ha.id_543 tl.cy.un_770 gd.cy.eu_443
+ 0x2d0d3fee, 0x16000104, 0x21271c08, 0x536e1f0c, // af.cs.sk_422 en.hr.un_320 id.gd.jw_443 cy.hmn.ht_543
+ 0x051c1a0c, 0x321a16ee, 0x07056407, 0x6e000d0d, // tl.id.fr_543 hr.tl.bs_422 lg.fr.it_432 cs.hmn.un_540
+ 0x21006818, 0x10002009, 0x3255210c, 0x040912a4, // ig.jw.un_740 sq.lt.un_440 jw.rw.bs_543 hu.pl.fi_433
+ // [5710]
+ 0x3f0305a0, 0x03023f14, 0x05001f0e, 0x320f0da0, // fr.nl.af_322 af.da.nl_666 cy.fr.un_550 cs.lv.bs_322
+ 0x64002502, 0x171329ee, 0x0c2a290c, 0x160d2905, // eu.lg.un_220 sl.et.sr_422 sl.mt.sv_543 sl.cs.hr_333
+ 0x23000614, 0x3f211ea4, 0x0e311b08, 0x1f1e270c, // de.ca.un_660 ms.jw.af_433 tr.az.is_443 gd.ms.cy_543
+ 0x64004a19, 0x08000704, 0x35211aad, 0x283b5208, // yo.lg.un_750 bg.uk.un_320 tl.jw.zu_643 ha.so.sw_443
+ // [5720]
+ 0x1b0e310c, 0x1b3113a0, 0x08171b07, 0x32001220, // az.is.tr_543 et.az.tr_322 tr.sr.no_432 hu.bs.un_850
+ 0x190a01ee, 0x182d120c, 0x4a003b08, 0x680f3b0c, // en.pt.gl_422 hu.sk.ga_543 so.yo.un_430 so.lv.ig_543
+ 0x04023107, 0x3b1e68a4, 0x20131004, 0x6b004a2b, // az.da.fi_432 ig.ms.so_433 lt.et.sq_332 yo.ceb.un_980
+ 0x283b0fad, 0x1c1b210c, 0x1c1f18a0, 0x0a0c070c, // lv.so.sw_643 jw.tr.id_543 ga.cy.id_322 it.sv.pt_543
+ // [5730]
+ 0x6b13010c, 0x684a3b0c, 0x122a130d, 0x20043b0c, // en.et.ceb_543 so.yo.ig_543 et.mt.hu_554 so.fi.sq_543
+ 0x1c0312a7, 0x4a003b18, 0x0d1c09ee, 0x55003534, // hu.nl.id_532 so.yo.un_740 hi.mr.ne_422 zu.rw.un_A80
+ 0x52006813, 0x1b1c3b0c, 0x03066402, 0x3b044a07, // ig.ha.un_650 so.id.tr_543 lg.de.nl_222 yo.fi.so_432
+ 0x08021aa4, 0x05006b04, 0x1e6b01ee, 0x256835ad, // tl.da.no_433 ceb.fr.un_320 en.ceb.ms_422 zu.ig.eu_643
+ // [5740]
+ 0x02062a11, 0x0b002314, 0x16000e05, 0x08002304, // mt.de.da_653 ca.es.un_660 is.hr.un_330 ca.no.un_320
+ 0x13000113, 0x100e16a0, 0x23001307, 0x0500130d, // en.et.un_650 hr.is.lt_322 et.ca.un_420 et.fr.un_540
+ 0x0e211caf, 0x024a1cad, 0x1a002807, 0x0b000a12, // id.jw.is_655 id.yo.da_643 sw.tl.un_420 pt.es.un_640
+ 0x16000d19, 0x55000302, 0x0200290b, 0x1b003207, // cs.hr.un_750 nl.rw.un_220 sl.da.un_520 bs.tr.un_420
+ // [5750]
+ 0x050c070c, 0x313f2008, 0x190b09a4, 0x3b211e0d, // it.sv.fr_543 sq.af.az_443 pl.es.gl_433 ms.jw.so_554
+ 0x043b0305, 0x216b1c09, 0x1c041e11, 0x04683b0c, // nl.so.fi_333 id.ceb.jw_444 ms.fi.id_653 so.ig.fi_543
+ 0x190b1a08, 0x21041e0c, 0x17322955, 0x133f1812, // tl.es.gl_443 ms.fi.jw_543 sl.bs.sr_442 ga.af.et_654
+ 0x1b31680d, 0x033f3ba9, 0x4a683bad, 0x2a2103ac, // ig.az.tr_554 so.af.nl_544 so.ig.yo_643 nl.jw.mt_632
+ // [5760]
+ 0x121b0d0b, 0x32681a08, 0x0d2512a9, 0x1c1e3b12, // cs.tr.hu_542 tl.ig.bs_443 hu.eu.cs_544 so.ms.id_654
+ 0x190b1faf, 0x68354a12, 0x121b1c12, 0x32161ea9, // cy.es.gl_655 yo.zu.ig_654 id.tr.hu_654 ms.hr.bs_544
+ 0x190b03a4, 0x06100c04, 0x1a003b12, 0x031a1309, // nl.es.gl_433 sv.lt.de_332 so.tl.un_640 et.tl.nl_444
+ 0x4a3b285a, 0x0802180c, 0x19002119, 0x350206a4, // sw.so.yo_553 ga.da.no_543 jw.gl.un_750 de.da.zu_433
+ // [5770]
+ 0x3f030907, 0x250d1208, 0x1a2b18a0, 0x042029ad, // pl.nl.af_432 hu.cs.eu_443 ga.vi.tl_322 sl.sq.fi_643
+ 0x2a000a1b, 0x06120305, 0x3500252a, 0x0400281a, // pt.mt.un_770 nl.hu.de_333 eu.zu.un_970 sw.fi.un_760
+ 0x03000607, 0x1b121ca4, 0x033f1e0d, 0x0c001f11, // de.nl.un_420 id.hu.tr_433 ms.af.nl_554 cy.sv.un_630
+ 0x29001208, 0x640752a4, 0x182a270c, 0x0b2955ee, // hu.sl.un_430 ha.it.lg_433 gd.mt.ga_543 rw.sl.es_422
+ // [5780]
+ 0x251118ec, 0x641235ec, 0x17200655, 0x05001811, // ga.ro.eu_644 zu.hu.lg_644 de.sq.sr_442 ga.fr.un_630
+ 0x231907a4, 0x06001221, 0x190b2311, 0x2d002307, // it.gl.ca_433 hu.de.un_860 ca.es.gl_653 ca.sk.un_420
+ 0x1000250e, 0x550d3555, 0x211c1f0c, 0x1c0d13a9, // eu.lt.un_550 zu.cs.rw_442 cy.id.jw_543 bh.ne.mr_544
+ 0x3b0b2b12, 0x550105a0, 0x2a20520c, 0x08020daf, // vi.es.so_654 fr.en.rw_322 ha.sq.mt_543 cs.da.no_655
+ // [5790]
+ 0x1c6412ad, 0x1700280d, 0x2d0d3202, 0x35001a02, // hu.lg.id_643 sw.sr.un_540 bs.cs.sk_222 tl.zu.un_220
+ 0x07280b08, 0x1200642a, 0x1b2753a0, 0x32002807, // es.sw.it_443 lg.hu.un_970 ht.gd.tr_322 sw.bs.un_420
+ 0x2d0d0c05, 0x190b07a9, 0x6e002b12, 0x072b19a0, // sv.cs.sk_333 it.es.gl_544 vi.hmn.un_640 gl.vi.it_322
+ 0x1c1127a0, 0x2700210e, 0x08021f0e, 0x2b000119, // gd.ro.id_322 jw.gd.un_550 cy.da.no_555 en.vi.un_750
+ // [57a0]
+ 0x3b001602, 0x28315504, 0x55201105, 0x1b312bec, // hr.so.un_220 rw.az.sw_332 ro.sq.rw_333 vi.az.tr_644
+ 0x050129ec, 0x29006821, 0x11071a13, 0x0e21160c, // sl.en.fr_644 ig.sl.un_860 tl.it.ro_665 hr.jw.is_543
+ 0x253b5208, 0x190b18a9, 0x552d1f05, 0x354a6413, // ha.so.eu_443 ga.es.gl_544 cy.sk.rw_333 lg.yo.zu_665
+ 0x28125507, 0x02121fa0, 0x551220a0, 0x120c17ee, // rw.hu.sw_432 cy.hu.da_322 sq.hu.rw_322 sr.sv.hu_422
+ // [57b0]
+ 0x130608ee, 0x55682d07, 0x0c0e1fad, 0x311b25ad, // no.de.et_422 sk.ig.rw_432 cy.is.sv_643 eu.tr.az_643
+ 0x2a006813, 0x16282909, 0x282a64af, 0x19180ba4, // ig.mt.un_650 sl.sw.hr_444 lg.mt.sw_655 es.ga.gl_433
+ 0x68554a12, 0x2812290c, 0x55002021, 0x0e004a04, // yo.rw.ig_654 sl.hu.sw_543 sq.rw.un_860 yo.is.un_320
+ 0x281809ad, 0x531b13a4, 0x16643204, 0x19250ba4, // pl.ga.sw_643 et.tr.ht_433 bs.lg.hr_332 es.eu.gl_433
+ // [57c0]
+ 0x644a6812, 0x0c000e21, 0x3f095311, 0x2d290d12, // ig.yo.lg_654 is.sv.un_860 ht.pl.af_653 cs.sl.sk_654
+ 0x31003b04, 0x3f010f05, 0x29000508, 0x64002a14, // so.az.un_320 lv.en.af_333 fr.sl.un_430 mt.lg.un_660
+ 0x190b18a6, 0x01003102, 0x0b190a60, 0x311a1bec, // ga.es.gl_521 az.en.un_220 pt.gl.es_664 tr.tl.az_644
+ 0x1f3b030c, 0x19080307, 0x0f1064ad, 0x16000b04, // nl.so.cy_543 nl.no.gl_432 lg.lt.lv_643 es.hr.un_320
+ // [57d0]
+ 0x10640407, 0x29642109, 0x1c002807, 0x28003b07, // fi.lg.lt_432 jw.lg.sl_444 sw.id.un_420 so.sw.un_420
+ 0x2d0d1fa4, 0x1a6e64a7, 0x32162105, 0x190b03a0, // cy.cs.sk_433 lg.hmn.tl_532 jw.hr.bs_333 nl.es.gl_322
+ 0x0f000412, 0x07005302, 0x050f010c, 0x3216070c, // fi.lv.un_640 ht.it.un_220 en.lv.fr_543 it.hr.bs_543
+ 0x02010807, 0x031b0e04, 0x282a18a0, 0x2904550c, // no.en.da_432 is.tr.nl_332 ga.mt.sw_322 rw.fi.sl_543
+ // [57e0]
+ 0x190b1a0c, 0x080212a4, 0x25002305, 0x31000c04, // tl.es.gl_543 hu.da.no_433 ca.eu.un_330 sv.az.un_320
+ 0x64350b09, 0x07230b08, 0x113507a7, 0x29644a0c, // es.zu.lg_444 es.ca.it_443 it.zu.ro_532 yo.lg.sl_543
+ 0x1a000707, 0x64001214, 0x06250304, 0x4a005513, // it.tl.un_420 hu.lg.un_660 nl.eu.de_332 rw.yo.un_650
+ 0x182811ec, 0x1a004a07, 0x0e520ca4, 0x3f001c12, // ro.sw.ga_644 yo.tl.un_420 sv.ha.is_433 id.af.un_640
+ // [57f0]
+ 0x042910a9, 0x2152120c, 0x23201308, 0x52252312, // lt.sl.fi_544 hu.ha.jw_543 et.sq.ca_443 ca.eu.ha_654
+ 0x0f5204ac, 0x2a101708, 0x12005223, 0x23000f1a, // fi.ha.lv_632 sr.lt.mt_443 ha.hu.un_880 lv.ca.un_760
+ 0x23005219, 0x1a162102, 0x03001f12, 0x11001813, // ha.ca.un_750 jw.hr.tl_222 cy.nl.un_640 ga.ro.un_650
+ 0x23053fee, 0x0e11250c, 0x04000c11, 0x0821520c, // af.fr.ca_422 eu.ro.is_543 sv.fi.un_630 ha.jw.no_543
+
+ // [5800]
+ 0x18102704, 0x2a1710ad, 0x172a10ac, 0x3b056ba9, // gd.lt.ga_332 lt.sr.mt_643 lt.mt.sr_632 ceb.fr.so_544
+ 0x3b4a6ba9, 0x072a1fad, 0x180d0ba0, 0x09062dad, // ceb.yo.so_544 cy.mt.it_643 es.cs.ga_322 sk.de.pl_643
+ 0x1f296ea4, 0x210302ee, 0x55001e04, 0x523555ac, // hmn.sl.cy_433 da.nl.jw_422 ms.rw.un_320 rw.zu.ha_632
+ 0x554a0508, 0x190a35a4, 0x6b2511af, 0x130c0fec, // fr.yo.rw_443 zu.pt.gl_433 ro.eu.ceb_655 lv.sv.et_644
+ // [5810]
+ 0x2a52130c, 0x68004a1a, 0x0a1110a4, 0x1a00531a, // et.ha.mt_543 yo.ig.un_760 lt.ro.pt_433 ht.tl.un_760
+ 0x1b0802a0, 0x3f3b1aad, 0x283f5314, 0x0c030e04, // da.no.tr_322 tl.so.af_643 ht.af.sw_666 is.nl.sv_332
+ 0x03002104, 0x13000704, 0x272b3bad, 0x4a00642b, // jw.nl.un_320 it.et.un_320 so.vi.gd_643 lg.yo.un_980
+ 0x254a6808, 0x526e2512, 0x0200082a, 0x282764ad, // ig.yo.eu_443 eu.hmn.ha_654 no.da.un_970 lg.gd.sw_643
+ // [5820]
+ 0x2029120c, 0x2b6e25a9, 0x11061213, 0x230668af, // hu.sl.sq_543 eu.hmn.vi_544 hu.de.ro_665 ig.de.ca_655
+ 0x2500532b, 0x232b1f13, 0x1f001c04, 0x0b310ea4, // ht.eu.un_980 cy.vi.ca_665 id.cy.un_320 is.az.es_433
+ 0x313f1f55, 0x25001f14, 0x23005202, 0x0e000b04, // cy.af.az_442 cy.eu.un_660 ha.ca.un_220 es.is.un_320
+ 0x12030811, 0x6e005222, 0x0b0e3108, 0x3b000913, // no.nl.hu_653 ha.hmn.un_870 az.is.es_443 pl.so.un_650
+ // [5830]
+ 0x181f01a4, 0x081110a4, 0x0b003105, 0x6e2b1114, // en.cy.ga_433 be.ro.uk_433 az.es.un_330 ro.vi.hmn_666
+ 0x3b1a060c, 0x04236e04, 0x1e002118, 0x101a6b13, // de.tl.so_543 hmn.ca.fi_332 jw.ms.un_740 ceb.tl.lt_665
+ 0x0b002822, 0x0b311b12, 0x6b000612, 0x0212060c, // sw.es.un_870 tr.az.es_654 de.ceb.un_640 de.hu.da_543
+ 0x52001f18, 0x0a006805, 0x5553290c, 0x08020609, // cy.ha.un_740 ig.pt.un_330 sl.ht.rw_543 de.da.no_444
+ // [5840]
+ 0x3555680b, 0x18000702, 0x1e1c20ee, 0x09001a09, // ig.rw.zu_542 it.ga.un_220 sq.id.ms_422 tl.pl.un_440
+ 0x2a100f09, 0x1a011b04, 0x04001133, 0x53003504, // lv.lt.mt_444 tr.en.tl_332 ro.ru.un_A70 zu.ht.un_320
+ 0x0b001a20, 0x3200031a, 0x21003f04, 0x1c1e1307, // tl.es.un_850 nl.bs.un_760 af.jw.un_320 et.ms.id_432
+ 0x6b1f3f11, 0x03000113, 0x554a68ee, 0x03000c02, // af.cy.ceb_653 en.nl.un_650 ig.yo.rw_422 sv.nl.un_220
+ // [5850]
+ 0x171a250c, 0x17682da0, 0x1e521c12, 0x04522aad, // eu.tl.sr_543 sk.ig.sr_322 id.ha.ms_654 mt.ha.fi_643
+ 0x1a25350c, 0x55351a12, 0x554a35a9, 0x531f520d, // zu.eu.tl_543 tl.zu.rw_654 zu.yo.rw_544 ha.cy.ht_554
+ 0x3f1b1f07, 0x1e681ca9, 0x2352090c, 0x11005507, // cy.tr.af_432 id.ig.ms_544 pl.ha.ca_543 rw.ro.un_420
+ 0x52001a08, 0x6b686eee, 0x0e0d21a0, 0x253b10a4, // tl.ha.un_430 hmn.ig.ceb_422 jw.cs.is_322 lt.so.eu_433
+ // [5860]
+ 0x1a5255ac, 0x32000605, 0x2d000704, 0x040b5502, // rw.ha.tl_632 de.bs.un_330 it.sk.un_320 rw.es.fi_222
+ 0x03200b14, 0x2d0d12b3, 0x08005312, 0x13001c02, // es.sq.nl_666 hu.cs.sk_743 ht.no.un_640 id.et.un_220
+ 0x12001b12, 0x16173207, 0x12004a2b, 0x2d1a21ee, // tr.hu.un_640 bs.sr.hr_432 yo.hu.un_980 jw.tl.sk_422
+ 0x320a0f5a, 0x2b00182c, 0x530b120c, 0x171329a0, // lv.pt.bs_553 ga.vi.un_990 hu.es.ht_543 sl.et.sr_322
+ // [5870]
+ 0x1c003b07, 0x00002b37, 0x18004a1b, 0x131c090d, // so.id.un_420 vi.un.un_B00 yo.ga.un_770 hi.mr.bh_554
+ 0x1c253bec, 0x1b2129ee, 0x080c13ec, 0x2a00130d, // so.eu.id_644 sl.jw.tr_422 et.sv.no_644 et.mt.un_540
+ 0x351325af, 0x041029a4, 0x4a190a13, 0x05191bee, // eu.et.zu_655 sl.lt.fi_433 pt.gl.yo_665 tr.gl.fr_422
+ 0x0a004a14, 0x3f00010c, 0x11551ba4, 0x17291bad, // yo.pt.un_660 en.af.un_530 tr.rw.ro_433 tr.sl.sr_643
+ // [5880]
+ 0x253521a6, 0x080525a4, 0x105223a7, 0x2a002309, // jw.zu.eu_521 eu.fr.no_433 ca.ha.lt_532 ca.mt.un_440
+ 0x03001218, 0x04006b07, 0x0801070d, 0x203528ec, // hu.nl.un_740 ceb.fi.un_420 it.en.no_554 sw.zu.sq_644
+ 0x18001e18, 0x04132dee, 0x2b003f08, 0x13003502, // ms.ga.un_740 sk.et.fi_422 af.vi.un_430 zu.et.un_220
+ 0x551e1c14, 0x0f291009, 0x28000d0d, 0x05640ca0, // id.ms.rw_666 lt.sl.lv_444 cs.sw.un_540 sv.lg.fr_322
+ // [5890]
+ 0x01000702, 0x1100020c, 0x1b2752a4, 0x08022502, // it.en.un_220 da.ro.un_530 ha.gd.tr_433 eu.da.no_222
+ 0x2d2904af, 0x04000d12, 0x0f001308, 0x534a21ee, // fi.sl.sk_655 cs.fi.un_640 et.lv.un_430 jw.yo.ht_422
+ 0x20212a04, 0x531f2aad, 0x0f093107, 0x351a53ec, // mt.jw.sq_332 mt.cy.ht_643 az.pl.lv_432 ht.tl.zu_644
+ 0x016b52ee, 0x100f1ba6, 0x2d0d09a4, 0x28214a0c, // ha.ceb.en_422 tr.lv.lt_521 pl.cs.sk_433 yo.jw.sw_543
+ // [58a0]
+ 0x09001a0d, 0x190b2310, 0x2d0d17a9, 0x1e1c31a4, // tl.pl.un_540 ca.es.gl_642 sr.cs.sk_544 az.id.ms_433
+ 0x532a09ec, 0x2d1b310d, 0x0e001604, 0x080c21a0, // pl.mt.ht_644 az.tr.sk_554 hr.is.un_320 jw.sv.no_322
+ 0x2a083b13, 0x100f285a, 0x110b0408, 0x04000e0e, // so.no.mt_665 sw.lv.lt_553 fi.es.ro_443 is.fi.un_550
+ 0x351a31ad, 0x28000412, 0x52002a13, 0x5500211a, // az.tl.zu_643 fi.sw.un_640 mt.ha.un_650 jw.rw.un_760
+ // [58b0]
+ 0x016b04ee, 0x351008a9, 0x04000a2a, 0x642035ad, // fi.ceb.en_422 no.lt.zu_544 pt.fi.un_970 zu.sq.lg_643
+ 0x1b001707, 0x07060360, 0x2b002a0d, 0x321710b3, // sr.tr.un_420 nl.de.it_664 mt.vi.un_540 lt.sr.bs_743
+ 0x27230509, 0x2a0823af, 0x13093b02, 0x321735ee, // fr.ca.gd_444 ca.no.mt_655 so.pl.et_222 zu.sr.bs_422
+ 0x1a093b07, 0x160c320c, 0x3217530c, 0x10041baf, // so.pl.tl_432 bs.sv.hr_543 ht.sr.bs_543 tr.fi.lt_655
+ // [58c0]
+ 0x551b6ea0, 0x0e1a0b0c, 0x11001c07, 0x10530dad, // hmn.tr.rw_322 es.tl.is_543 id.ro.un_420 cs.ht.lt_643
+ 0x051801a4, 0x02522109, 0x13091c07, 0x1f2a2360, // en.ga.fr_433 jw.ha.da_444 mr.hi.bh_432 ca.mt.cy_664
+ 0x1e1c2108, 0x1a00121a, 0x1e003202, 0x16002a12, // jw.id.ms_443 hu.tl.un_760 bs.ms.un_220 mt.hr.un_640
+ 0x32000607, 0x31103f04, 0x12001307, 0x190b0aaf, // de.bs.un_420 af.lt.az_332 et.hu.un_420 pt.es.gl_655
+ // [58d0]
+ 0x055568a0, 0x10252908, 0x02103fec, 0x3f00101a, // ig.rw.fr_322 sl.eu.lt_443 af.lt.da_644 lt.af.un_760
+ 0x0f00292b, 0x130111a4, 0x2900090c, 0x0b006b18, // sl.lv.un_980 ro.en.et_433 pl.sl.un_530 ceb.es.un_740
+ 0x1a3b090c, 0x09270607, 0x0a6b2a07, 0x0b25190d, // pl.so.tl_543 de.gd.pl_432 mt.ceb.pt_432 gl.eu.es_554
+ 0x09002a22, 0x1004120e, 0x11000a17, 0x2d0d29a7, // mt.pl.un_870 hu.fi.lt_555 pt.ro.un_730 sl.cs.sk_532
+ // [58e0]
+ 0x29000813, 0x110464a0, 0x6b006405, 0x0b001f09, // no.sl.un_650 lg.fi.ro_322 lg.ceb.un_330 cy.es.un_440
+ 0x0c3f64ec, 0x13000c07, 0x52643b12, 0x0a315555, // lg.af.sv_644 sv.et.un_420 so.lg.ha_654 rw.az.pt_442
+ 0x29000e08, 0x0a3b1907, 0x011325ee, 0x0900550c, // is.sl.un_430 gl.so.pt_432 eu.et.en_422 rw.pl.un_530
+ 0x100453ad, 0x25191f04, 0x55202112, 0x096468ac, // ht.fi.lt_643 cy.gl.eu_332 jw.sq.rw_654 ig.lg.pl_632
+ // [58f0]
+ 0x52001904, 0x352813a7, 0x04003511, 0x1c090d04, // gl.ha.un_320 et.sw.zu_532 zu.fi.un_630 ne.hi.mr_332
+ 0x320c25a4, 0x2a1320a9, 0x1b1a6bad, 0x102a3b11, // eu.sv.bs_433 sq.et.mt_544 ceb.tl.tr_643 so.mt.lt_653
+ 0x09000e0d, 0x0e0c2511, 0x2a272504, 0x25003f02, // is.pl.un_540 eu.sv.is_653 eu.gd.mt_332 af.eu.un_220
+ 0x070a25ad, 0x53093508, 0x681c27ac, 0x0d090aa0, // eu.pt.it_643 zu.pl.ht_443 gd.id.ig_632 pt.pl.cs_322
+ // [5900]
+ 0x0d091f07, 0x32175305, 0x1e1c2a0c, 0x52310507, // cy.pl.cs_432 ht.sr.bs_333 mt.id.ms_543 fr.az.ha_432
+ 0x28171112, 0x11002a1b, 0x073b0b09, 0x0c000421, // ro.sr.sw_654 mt.ro.un_770 es.so.it_444 fi.sv.un_860
+ 0x18006b07, 0x0d016ba0, 0x09006e11, 0x1b1a53a4, // ceb.ga.un_420 ceb.en.cs_322 hmn.pl.un_630 ht.tl.tr_433
+ 0x02091f13, 0x1e1c2805, 0x11055208, 0x1e1c68a9, // cy.pl.da_665 sw.id.ms_333 ha.fr.ro_443 ig.id.ms_544
+ // [5910]
+ 0x080c3fee, 0x640968ec, 0x1e1c52a9, 0x29002304, // af.sv.no_422 ig.pl.lg_644 ha.id.ms_544 ca.sl.un_320
+ 0x20326812, 0x0464685a, 0x170f03ac, 0x3f032da4, // ig.bs.sq_654 ig.lg.fi_553 nl.lv.sr_632 sk.nl.af_433
+ 0x05642308, 0x03001109, 0x1b3555ec, 0x0c003504, // ca.lg.fr_443 ro.nl.un_440 rw.zu.tr_644 zu.sv.un_320
+ 0x060e27a9, 0x64355212, 0x64552014, 0x2a00121b, // gd.is.de_544 ha.zu.lg_654 sq.rw.lg_666 hu.mt.un_770
+ // [5920]
+ 0x640e13ec, 0x232b0555, 0x52533bad, 0x060103ad, // et.is.lg_644 fr.vi.ca_442 so.ht.ha_643 nl.en.de_643
+ 0x551f0708, 0x0b0a230c, 0x1b5264a4, 0x08001b18, // it.cy.rw_443 ca.pt.es_543 lg.ha.tr_433 tr.no.un_740
+ 0x20000e14, 0x112123af, 0x133f1b0c, 0x0c311307, // is.sq.un_660 ca.jw.ro_655 tr.af.et_543 et.az.sv_432
+ 0x321603a4, 0x2500210e, 0x29173fa4, 0x2b000a09, // nl.hr.bs_433 jw.eu.un_550 af.sr.sl_433 pt.vi.un_440
+ // [5930]
+ 0x2a2d09a6, 0x0a002b0d, 0x64000512, 0x12000c18, // pl.sk.mt_521 vi.pt.un_540 fr.lg.un_640 sv.hu.un_740
+ 0x09011fac, 0x3f0c12a6, 0x0810020c, 0x08021702, // cy.en.pl_632 hu.sv.af_521 da.lt.no_543 sr.da.no_222
+ 0x230b25a9, 0x03070ca0, 0x28002d12, 0x2b000a07, // eu.es.ca_544 sv.it.nl_322 sk.sw.un_640 pt.vi.un_420
+ 0x1b005502, 0x21005321, 0x684a315a, 0x0d2d09a4, // rw.tr.un_220 ht.jw.un_860 az.yo.ig_553 pl.sk.cs_433
+ // [5940]
+ 0x011f0c08, 0x53002114, 0x35000204, 0x0700231d, // sv.cy.en_443 jw.ht.un_660 da.zu.un_320 ca.it.un_820
+ 0x04123fa0, 0x3b2b01ee, 0x556468ec, 0x08023faf, // af.hu.fi_322 en.vi.so_422 ig.lg.rw_644 af.da.no_655
+ 0x08020705, 0x681a20ee, 0x3f130e0c, 0x35311b12, // it.da.no_333 sq.tl.ig_422 is.et.af_543 tr.az.zu_654
+ 0x07000507, 0x31211b07, 0x5321230e, 0x23001c04, // fr.it.un_420 tr.jw.az_432 ca.jw.ht_555 id.ca.un_320
+ // [5950]
+ 0x352d530e, 0x08001311, 0x100923a7, 0x2d002a05, // ht.sk.zu_555 et.no.un_630 ca.pl.lt_532 mt.sk.un_330
+ 0x230b3107, 0x6e6b01ad, 0x10041702, 0x52002109, // az.es.ca_432 en.ceb.hmn_643 sr.ru.be_222 jw.ha.un_440
+ 0x20001104, 0x290a1b07, 0x27000807, 0x1300060b, // ro.sq.un_320 tr.pt.sl_432 no.gd.un_420 de.et.un_520
+ 0x095510ad, 0x0a192107, 0x321609af, 0x190b1f0c, // lt.rw.pl_643 jw.gl.pt_432 pl.hr.bs_655 cy.es.gl_543
+ // [5960]
+ 0x0d002521, 0x55003b13, 0x2d291104, 0x0100280d, // eu.cs.un_860 so.rw.un_650 ro.sl.sk_332 sw.en.un_540
+ 0x166b0909, 0x072d010c, 0x0700190e, 0x2a001b09, // pl.ceb.hr_444 en.sk.it_543 gl.it.un_550 tr.mt.un_440
+ 0x2d0d12a4, 0x01276ba0, 0x211c2812, 0x093f10a4, // hu.cs.sk_433 ceb.gd.en_322 sw.id.jw_654 lt.af.pl_433
+ 0x291805ec, 0x310e1b13, 0x12002909, 0x050c010c, // fr.ga.sl_644 tr.is.az_665 sl.hu.un_440 en.sv.fr_543
+ // [5970]
+ 0x07004a02, 0x52684a13, 0x310e1f12, 0x253568a0, // yo.it.un_220 yo.ig.ha_665 cy.is.az_654 ig.zu.eu_322
+ 0x1e1c2909, 0x0900120c, 0x041e12ad, 0x2100080e, // sl.id.ms_444 hu.pl.un_530 hu.ms.fi_643 no.jw.un_550
+ 0x1609295a, 0x1e003104, 0x1e1c2509, 0x19350711, // sl.pl.hr_553 az.ms.un_320 eu.id.ms_444 it.zu.gl_653
+ 0x1e044aa0, 0x03002d14, 0x29210705, 0x03013fa9, // yo.fi.ms_322 sk.nl.un_660 it.jw.sl_333 af.en.nl_544
+ // [5980]
+ 0x1f311b13, 0x0528190d, 0x0c0831ec, 0x05002007, // tr.az.cy_665 gl.sw.fr_554 az.no.sv_644 sq.fr.un_420
+ 0x04001209, 0x1e001208, 0x08000218, 0x12000d08, // hu.fi.un_440 hu.ms.un_430 da.no.un_740 cs.hu.un_430
+ 0x00002042, 0x063f0111, 0x122d210e, 0x200d04af, // sq.un.un_C00 en.af.de_653 jw.sk.hu_555 fi.cs.sq_655
+ 0x04001e13, 0x1e1c0d09, 0x32001e02, 0x175307a7, // ms.fi.un_650 cs.id.ms_444 ms.bs.un_220 it.ht.sr_532
+ // [5990]
+ 0x311b1e09, 0x536e070b, 0x18311b09, 0x08311309, // ms.tr.az_444 it.hmn.ht_542 tr.az.ga_444 et.az.no_444
+ 0x08020305, 0x23310511, 0x201b1ea4, 0x23041302, // nl.da.no_333 fr.az.ca_653 ms.tr.sq_433 et.fi.ca_222
+ 0x64005202, 0x19182b0c, 0x060727a0, 0x1e001c34, // ha.lg.un_220 vi.ga.gl_543 gd.it.de_322 id.ms.un_A80
+ 0x18270aa9, 0x2b210605, 0x250703a0, 0x64001f22, // pt.gd.ga_544 de.jw.vi_333 nl.it.eu_322 cy.lg.un_870
+ // [59a0]
+ 0x201b4a07, 0x53002523, 0x08001104, 0x251b68af, // yo.tr.sq_432 eu.ht.un_880 ro.uk.un_320 ig.tr.eu_655
+ 0x18006e0e, 0x07003504, 0x522807a4, 0x0b005505, // hmn.ga.un_550 zu.it.un_320 it.sw.ha_433 rw.es.un_330
+ 0x27091808, 0x2900202a, 0x1b001004, 0x53256bad, // ga.pl.gd_443 sq.sl.un_970 lt.tr.un_320 ceb.eu.ht_643
+ 0x0c684aa0, 0x3f1f0913, 0x03083fad, 0x0c001b0b, // yo.ig.sv_322 pl.cy.af_665 af.no.nl_643 tr.sv.un_520
+ // [59b0]
+ 0x3b2319a7, 0x04132a0c, 0x28001902, 0x1e122512, // gl.ca.so_532 mt.et.fi_543 gl.sw.un_220 eu.hu.ms_654
+ 0x643f29a4, 0x2503090b, 0x20212509, 0x1e251c0c, // sl.af.lg_433 pl.nl.eu_542 eu.jw.sq_444 id.eu.ms_543
+ 0x28001702, 0x25001812, 0x28000e04, 0x21001b2b, // sr.sw.un_220 ga.eu.un_640 is.sw.un_320 tr.jw.un_980
+ 0x2d1f0d07, 0x284a31a9, 0x0400090e, 0x0c3f0fee, // cs.cy.sk_432 az.yo.sw_544 pl.fi.un_550 lv.af.sv_422
+ // [59c0]
+ 0x0d000807, 0x100c1bee, 0x1e291ca4, 0x55212012, // no.cs.un_420 tr.sv.lt_422 id.sl.ms_433 sq.jw.rw_654
+ 0x211e1aa9, 0x17251c0c, 0x00000201, 0x1b000c1b, // tl.ms.jw_544 id.eu.sr_543 da.un.un_200 sv.tr.un_770
+ 0x1e1c25a9, 0x551b68af, 0x19310b55, 0x1c4a1ea4, // eu.id.ms_544 ig.tr.rw_655 es.az.gl_442 ms.yo.id_433
+ 0x64001f12, 0x190a11a9, 0x68534a60, 0x270631a7, // cy.lg.un_640 ro.pt.gl_544 yo.ht.ig_664 az.de.gd_532
+ // [59d0]
+ 0x28100612, 0x1b0b08a0, 0x08101b08, 0x35211ca9, // de.lt.sw_654 no.es.tr_322 tr.lt.no_443 id.jw.zu_544
+ 0x0c523b08, 0x13211ba4, 0x68000219, 0x32162005, // so.ha.sv_443 tr.jw.et_433 da.ig.un_750 sq.hr.bs_333
+ 0x0a231913, 0x3b190a0d, 0x3b1b1012, 0x08000734, // gl.ca.pt_665 pt.gl.so_554 lt.tr.so_654 bg.uk.un_A80
+ 0x04081113, 0x23002a1a, 0x232013a7, 0x181011a0, // ro.no.fi_665 mt.ca.un_760 et.sq.ca_532 ro.lt.ga_322
+ // [59e0]
+ 0x1e171c04, 0x35201b08, 0x166b0eee, 0x04201b0b, // id.sr.ms_332 tr.sq.zu_443 is.ceb.hr_422 tr.sq.fi_542
+ 0x0406130d, 0x3f1b06ad, 0x014a0c04, 0x32000a08, // et.de.fi_554 de.tr.af_643 sv.yo.en_332 pt.bs.un_430
+ 0x0b0a0eec, 0x2d290da4, 0x3b281a02, 0x081f02ee, // is.pt.es_644 cs.sl.sk_433 tl.sw.so_222 da.cy.no_422
+ 0x080c0412, 0x32000704, 0x2a0c0812, 0x1a27180e, // fi.sv.no_654 it.bs.un_320 no.sv.mt_654 ga.gd.tl_555
+ // [59f0]
+ 0x18191f08, 0x6e532ba4, 0x53206e05, 0x1f3f0213, // cy.gl.ga_443 vi.ht.hmn_433 hmn.sq.ht_333 da.af.cy_665
+ 0x2a3b0a08, 0x0a1b6e12, 0x09002804, 0x10005313, // pt.so.mt_443 hmn.tr.pt_654 sw.pl.un_320 ht.lt.un_650
+ 0x6e0d2dee, 0x190b08a0, 0x311b6e04, 0x19181f0c, // sk.cs.hmn_422 no.es.gl_322 hmn.tr.az_332 cy.ga.gl_543
+ 0x29272b13, 0x1a00521a, 0x211a1e12, 0x0c0a2913, // vi.gd.sl_665 ha.tl.un_760 ms.tl.jw_654 sl.pt.sv_665
+ // [5a00]
+ 0x351a64a9, 0x1a0a68ee, 0x29000a2c, 0x53183ba4, // lg.tl.zu_544 ig.pt.tl_422 pt.sl.un_990 so.ga.ht_433
+ 0x08001e08, 0x52000c04, 0x3b1135ee, 0x080232a4, // ms.no.un_430 sv.ha.un_320 zu.ro.so_422 bs.da.no_433
+ 0x16293bad, 0x29001313, 0x101709a4, 0x12000618, // so.sl.hr_643 et.sl.un_650 pl.sr.lt_433 de.hu.un_740
+ 0x6b183bee, 0x0f105313, 0x3b000c19, 0x3b002913, // so.ga.ceb_422 ht.lt.lv_665 sv.so.un_750 sl.so.un_650
+ // [5a10]
+ 0x250631a4, 0x04280f04, 0x29063ba4, 0x01006b14, // az.de.eu_433 lv.sw.fi_332 so.de.sl_433 ceb.en.un_660
+ 0x17003107, 0x28311baf, 0x16005307, 0x35002804, // az.sr.un_420 tr.az.sw_655 ht.hr.un_420 sw.zu.un_320
+ 0x281a555a, 0x181b27ac, 0x3f0313ad, 0x1911180c, // rw.tl.sw_553 gd.tr.ga_632 et.nl.af_643 ga.ro.gl_543
+ 0x0a1a6bad, 0x4a001902, 0x21551aa4, 0x0e256ba9, // ceb.tl.pt_643 gl.yo.un_220 tl.rw.jw_433 ceb.eu.is_544
+ // [5a20]
+ 0x0c1117a4, 0x27003202, 0x19130ba4, 0x52000422, // sr.ro.sv_433 bs.gd.un_220 es.et.gl_433 fi.ha.un_870
+ 0x27000b02, 0x133f6405, 0x55006b0e, 0x0c130a08, // es.gd.un_220 lg.af.et_333 ceb.rw.un_550 pt.et.sv_443
+ 0x04201f0d, 0x13643f05, 0x3f1a1208, 0x0e041fee, // cy.sq.fi_554 af.lg.et_333 hu.tl.af_443 cy.fi.is_422
+ 0x276b1a12, 0x5300212a, 0x210c52a7, 0x29132008, // tl.ceb.gd_654 jw.ht.un_970 ha.sv.jw_532 sq.et.sl_443
+ // [5a30]
+ 0x0305020c, 0x531f0bee, 0x0c0e0407, 0x172905ee, // da.fr.nl_543 es.cy.ht_422 fi.is.sv_432 fr.sl.sr_422
+ 0x1e005221, 0x1b3111a4, 0x310827a0, 0x68002b0d, // ha.ms.un_860 ro.az.tr_433 gd.no.az_322 vi.ig.un_540
+ 0x103f68a6, 0x230b64a4, 0x060c6bec, 0x1f1b0e0c, // ig.af.lt_521 lg.es.ca_433 ceb.sv.de_644 is.tr.cy_543
+ 0x52350e5a, 0x3f1a0213, 0x0c002108, 0x210b1805, // is.zu.ha_553 da.tl.af_665 jw.sv.un_430 ga.es.jw_333
+ // [5a40]
+ 0x111201a7, 0x053f0309, 0x043b13af, 0x2a00681b, // en.hu.ro_532 nl.af.fr_444 et.so.fi_655 ig.mt.un_770
+ 0x061801a4, 0x08180cac, 0x0300682b, 0x020f0c0c, // en.ga.de_433 sv.ga.no_632 ig.nl.un_980 sv.lv.da_543
+ 0x0c1304a4, 0x07181907, 0x0b002707, 0x0e000308, // fi.et.sv_433 gl.ga.it_432 gd.es.un_420 nl.is.un_430
+ 0x1e1c64af, 0x29256804, 0x0c04060e, 0x68003f21, // lg.id.ms_655 ig.eu.sl_332 de.fi.sv_555 af.ig.un_860
+ // [5a50]
+ 0x0c0504ac, 0x112701ad, 0x190a0e09, 0x1b311a08, // fi.fr.sv_632 en.gd.ro_643 is.pt.gl_444 tl.az.tr_443
+ 0x6e006404, 0x09006e07, 0x230e2da0, 0x05313f04, // lg.hmn.un_320 hmn.pl.un_420 sk.is.ca_322 af.az.fr_332
+ 0x21002804, 0x01002714, 0x233b05ec, 0x2d520dec, // sw.jw.un_320 gd.en.un_660 fr.so.ca_644 cs.ha.sk_644
+ 0x644a52ad, 0x0f1725a0, 0x07190bec, 0x68005508, // ha.yo.lg_643 eu.sr.lv_322 es.gl.it_644 rw.ig.un_430
+ // [5a60]
+ 0x040c0660, 0x281f0aee, 0x68002a05, 0x6b1a2907, // de.sv.fi_664 pt.cy.sw_422 mt.ig.un_330 sl.tl.ceb_432
+ 0x55005302, 0x1e1c13ec, 0x03023fee, 0x193b25a4, // ht.rw.un_220 et.id.ms_644 af.da.nl_422 eu.so.gl_433
+ 0x02201f12, 0x04283bad, 0x101b09a0, 0x0e312b55, // cy.sq.da_654 so.sw.fi_643 pl.tr.lt_322 vi.az.is_442
+ 0x193f0b07, 0x3b002919, 0x08131807, 0x1f001b19, // es.af.gl_432 sl.so.un_750 ga.et.no_432 tr.cy.un_750
+ // [5a70]
+ 0x68004a35, 0x1b3f13af, 0x1200020d, 0x0406250b, // yo.ig.un_A90 et.af.tr_655 da.hu.un_540 eu.de.fi_542
+ 0x10092daf, 0x2d000920, 0x1000641a, 0x3f000634, // sk.pl.lt_655 pl.sk.un_850 lg.lt.un_760 de.af.un_A80
+ 0x3b003f0c, 0x043b29ee, 0x6b0610a0, 0x0f001c02, // af.so.un_530 sl.so.fi_422 lt.de.ceb_322 id.lv.un_220
+ 0x133b5504, 0x4a005207, 0x21001a02, 0x1c110a04, // rw.so.et_332 ha.yo.un_420 tl.jw.un_220 pt.ro.id_332
+ // [5a80]
+ 0x2d290dad, 0x0b2364ee, 0x110f2daf, 0x0f042d0c, // cs.sl.sk_643 lg.ca.es_422 sk.lv.ro_655 sk.fi.lv_543
+ 0x062a0914, 0x3b271812, 0x21136807, 0x2b005502, // pl.mt.de_666 ga.gd.so_654 ig.et.jw_432 rw.vi.un_220
+ 0x07112312, 0x02031fec, 0x0a0318a4, 0x02093ba7, // ca.ro.it_654 cy.nl.da_644 ga.nl.pt_433 so.pl.da_532
+ 0x6b2035a9, 0x0d0f2da4, 0x16070aad, 0x233b27a9, // zu.sq.ceb_544 sk.lv.cs_433 pt.it.hr_643 gd.so.ca_544
+ // [5a90]
+ 0x2d000a19, 0x35003f0c, 0x29041809, 0x0a212960, // pt.sk.un_750 af.zu.un_530 ga.fi.sl_444 sl.jw.pt_664
+ 0x010e25ad, 0x190b04ee, 0x25000407, 0x646b13ee, // eu.is.en_643 fi.es.gl_422 fi.eu.un_420 et.ceb.lg_422
+ 0x0b001012, 0x1b0231a4, 0x1b23020c, 0x190a1a14, // lt.es.un_640 az.da.tr_433 da.ca.tr_543 tl.pt.gl_666
+ 0x2021350c, 0x282552a0, 0x19001208, 0x100f1c0c, // zu.jw.sq_543 ha.eu.sw_322 hu.gl.un_430 id.lv.lt_543
+ // [5aa0]
+ 0x6e070912, 0x11292da0, 0x1a0f1e12, 0x21200e12, // pl.it.hmn_654 sk.sl.ro_322 ms.lv.tl_654 is.sq.jw_654
+ 0x321703ee, 0x09190b09, 0x08020e0e, 0x21066b12, // nl.sr.bs_422 es.gl.pl_444 is.da.no_555 ceb.de.jw_654
+ 0x190f0b0c, 0x53053faf, 0x25000212, 0x2d180d0d, // es.lv.gl_543 af.fr.ht_655 da.eu.un_640 cs.ga.sk_554
+ 0x0f001219, 0x290f0da0, 0x052852ad, 0x032d1007, // hu.lv.un_750 cs.lv.sl_322 ha.sw.fr_643 lt.sk.nl_432
+ // [5ab0]
+ 0x3f071060, 0x093f0fee, 0x1a6b55ad, 0x1f23190d, // lt.it.af_664 lv.af.pl_422 rw.ceb.tl_643 gl.ca.cy_554
+ 0x1b2555ac, 0x0b251955, 0x3b55640d, 0x2d1219a9, // rw.eu.tr_632 gl.eu.es_442 lg.rw.so_554 gl.hu.sk_544
+ 0x070f52ad, 0x09085507, 0x6b1a2a12, 0x352a53a4, // ha.lv.it_643 rw.no.pl_432 mt.tl.ceb_654 ht.mt.zu_433
+ 0x122d190c, 0x13006e0d, 0x3b6e6b08, 0x2d2307a9, // gl.sk.hu_543 hmn.et.un_540 ceb.hmn.so_443 it.ca.sk_544
+ // [5ac0]
+ 0x18001307, 0x35001a19, 0x55014aee, 0x1b1a010c, // et.ga.un_420 tl.zu.un_750 yo.en.rw_422 en.tl.tr_543
+ 0x133b1aad, 0x130f1008, 0x1f3f030e, 0x192d0aa4, // tl.so.et_643 lt.lv.et_443 nl.af.cy_555 pt.sk.gl_433
+ 0x25002302, 0x2d280707, 0x641e28a0, 0x186810a4, // ca.eu.un_220 it.sw.sk_432 sw.ms.lg_322 lt.ig.ga_433
+ 0x6b1a2a09, 0x17003f1a, 0x120b04a4, 0x0e23180d, // mt.tl.ceb_444 af.sr.un_760 fi.es.hu_433 ga.ca.is_554
+ // [5ad0]
+ 0x1a6b2a12, 0x27050712, 0x2d0d05af, 0x043f0312, // mt.ceb.tl_654 it.fr.gd_654 fr.cs.sk_655 nl.af.fi_654
+ 0x03052102, 0x0100250d, 0x016e6ba0, 0x2a001912, // jw.fr.nl_222 eu.en.un_540 ceb.hmn.en_322 gl.mt.un_640
+ 0x11002702, 0x082a06ee, 0x0700191b, 0x19000707, // gd.ro.un_220 de.mt.no_422 gl.it.un_770 it.gl.un_420
+ 0x01051fac, 0x1c211e0b, 0x6b2521a4, 0x3f0c0813, // cy.fr.en_632 ms.jw.id_542 jw.eu.ceb_433 no.sv.af_665
+ // [5ae0]
+ 0x0c180e05, 0x2b005319, 0x05270408, 0x52283b13, // is.ga.sv_333 ht.vi.un_750 fi.gd.fr_443 so.sw.ha_665
+ 0x052312a4, 0x090b6b02, 0x122d0aac, 0x523b5508, // hu.ca.fr_433 ceb.es.pl_222 pt.sk.hu_632 rw.so.ha_443
+ 0x0e000513, 0x353b5507, 0x050e0fa4, 0x64531aa0, // fr.is.un_650 rw.so.zu_432 lv.is.fr_433 tl.ht.lg_322
+ 0x1a206b0d, 0x0500211a, 0x053111ad, 0x2d120aa0, // ceb.sq.tl_554 jw.fr.un_760 ro.az.fr_643 pt.hu.sk_322
+ // [5af0]
+ 0x19070aee, 0x092d3fee, 0x033b0d07, 0x07111805, // pt.it.gl_422 af.sk.pl_422 cs.so.nl_432 ga.ro.it_333
+ 0x041828a6, 0x230731a0, 0x07116807, 0x31354aaf, // sw.ga.fi_521 az.it.ca_322 ig.ro.it_432 yo.zu.az_655
+ 0x190c0aee, 0x06080ca6, 0x0b3b07a0, 0x12531013, // pt.sv.gl_422 sv.no.de_521 it.so.es_322 lt.ht.hu_665
+ 0x190e0aad, 0x013f070c, 0x55001309, 0x1e201a05, // pt.is.gl_643 it.af.en_543 et.rw.un_440 tl.sq.ms_333
+ // [5b00]
+ 0x0e6864a4, 0x181f2807, 0x23110aa9, 0x64350e05, // lg.ig.is_433 sw.cy.ga_432 pt.ro.ca_544 is.zu.lg_333
+ 0x6b1f1804, 0x2b1e3f02, 0x2100110d, 0x6455110c, // ga.cy.ceb_332 af.ms.vi_222 ro.jw.un_540 ro.rw.lg_543
+ 0x04002121, 0x10352a13, 0x09052a0c, 0x680735af, // jw.fi.un_860 mt.zu.lt_665 mt.fr.pl_543 zu.it.ig_655
+ 0x0200350c, 0x03001707, 0x10080212, 0x23000521, // zu.da.un_530 sr.nl.un_420 da.no.lt_654 fr.ca.un_860
+ // [5b10]
+ 0x2353250c, 0x0a1b1904, 0x3b00111b, 0x0a002123, // eu.ht.ca_543 gl.tr.pt_332 ro.so.un_770 jw.pt.un_880
+ 0x23001923, 0x5264530c, 0x10000b08, 0x20061f0c, // gl.ca.un_880 ht.lg.ha_543 es.lt.un_430 cy.de.sq_543
+ 0x1b2d35af, 0x4a000b02, 0x31001902, 0x05230ead, // zu.sk.tr_655 es.yo.un_220 gl.az.un_220 is.ca.fr_643
+ 0x281a0408, 0x55002519, 0x530f215a, 0x1000090c, // fi.tl.sw_443 eu.rw.un_750 jw.lv.ht_553 pl.lt.un_530
+ // [5b20]
+ 0x55285213, 0x6b521a09, 0x10311bec, 0x080613ee, // ha.sw.rw_665 tl.ha.ceb_444 tr.az.lt_644 et.de.no_422
+ 0x0400230c, 0x1b1a25a4, 0x1c000d02, 0x0f100cad, // ca.fi.un_530 eu.tl.tr_433 ne.mr.un_220 sv.lt.lv_643
+ 0x292d0f07, 0x0b121902, 0x0c001c02, 0x6b002813, // lv.sk.sl_432 gl.hu.es_222 id.sv.un_220 sw.ceb.un_650
+ 0x203132a0, 0x55253112, 0x4a5528a4, 0x080e0fad, // bs.az.sq_322 az.eu.rw_654 sw.rw.yo_433 lv.is.no_643
+ // [5b30]
+ 0x045228a9, 0x533f05a7, 0x101a1ea4, 0x094a6860, // sw.ha.fi_544 fr.af.ht_532 ms.tl.lt_433 ig.yo.pl_664
+ 0x256b1a60, 0x20183508, 0x016b0507, 0x3b2808a0, // tl.ceb.eu_664 zu.ga.sq_443 fr.ceb.en_432 no.sw.so_322
+ 0x282055af, 0x4a355304, 0x2a5325a6, 0x351f680c, // rw.sq.sw_655 ht.zu.yo_332 eu.ht.mt_521 ig.cy.zu_543
+ 0x53023f08, 0x1100180e, 0x0b000613, 0x190b050c, // af.da.ht_443 ga.ro.un_550 de.es.un_650 fr.es.gl_543
+ // [5b40]
+ 0x2d001112, 0x53006808, 0x10010fad, 0x081f0cec, // ro.sk.un_640 ig.ht.un_430 lv.en.lt_643 sv.cy.no_644
+ 0x0605190c, 0x6b3b19ad, 0x4a000d13, 0x0a0702a4, // gl.fr.de_543 gl.so.ceb_643 cs.yo.un_650 da.it.pt_433
+ 0x2d000508, 0x124a0509, 0x3b060b05, 0x3f000519, // fr.sk.un_430 fr.yo.hu_444 es.de.so_333 fr.af.un_750
+ 0x4a2a13ad, 0x070a100e, 0x4a002b0d, 0x2b001809, // et.mt.yo_643 be.mk.bg_555 vi.yo.un_540 ga.vi.un_440
+ // [5b50]
+ 0x0a2d05a9, 0x3f3507a7, 0x6b681aec, 0x551304ee, // fr.sk.pt_544 it.zu.af_532 tl.ig.ceb_644 fi.et.rw_422
+ 0x20002502, 0x64002a0d, 0x110b23ad, 0x0e184a14, // eu.sq.un_220 mt.lg.un_540 ca.es.ro_643 yo.ga.is_666
+ 0x07001a2b, 0x1a0e64af, 0x182a27ee, 0x0c000a08, // tl.it.un_980 lg.is.tl_655 gd.mt.ga_422 pt.sv.un_430
+ 0x06002d04, 0x17001104, 0x6b1a55a4, 0x0d1c1fa0, // sk.de.un_320 ro.sr.un_320 rw.tl.ceb_433 cy.id.cs_322
+ // [5b60]
+ 0x07000e0c, 0x01063ba7, 0x0b4a280c, 0x64551c11, // is.it.un_530 so.de.en_532 sw.yo.es_543 id.rw.lg_653
+ 0x1200060d, 0x2d350da4, 0x21001202, 0x2100030e, // de.hu.un_540 cs.zu.sk_433 ur.fa.un_220 nl.jw.un_550
+ 0x27002013, 0x0d2910a4, 0x036b68a0, 0x552d2855, // sq.gd.un_650 lt.sl.cs_433 ig.ceb.nl_322 sw.sk.rw_442
+ 0x04132912, 0x0300350e, 0x2d004a11, 0x100b0fa7, // sl.et.fi_654 zu.nl.un_550 yo.sk.un_630 lv.es.lt_532
+ // [5b70]
+ 0x101a1305, 0x0a2728a0, 0x321a0307, 0x2d1f0ca4, // et.tl.lt_333 sw.gd.pt_322 nl.tl.bs_432 sv.cy.sk_433
+ 0x12101313, 0x06001304, 0x1b553bec, 0x4a6b350c, // et.lt.hu_665 et.de.un_320 so.rw.tr_644 zu.ceb.yo_543
+ 0x2d001813, 0x253507ee, 0x13554a02, 0x060c3fa4, // ga.sk.un_650 it.zu.eu_422 yo.rw.et_222 af.sv.de_433
+ 0x32000104, 0x28181ba4, 0x070e1f02, 0x120a2105, // en.bs.un_320 tr.ga.sw_433 cy.is.it_222 jw.pt.hu_333
+ // [5b80]
+ 0x0f000b04, 0x0c351807, 0x0a000b04, 0x1b000a07, // es.lv.un_320 ga.zu.sv_432 es.pt.un_320 pt.tr.un_420
+ 0x10076b08, 0x551764ee, 0x0968285a, 0x35311fa7, // ceb.it.lt_443 lg.sr.rw_422 sw.ig.pl_553 cy.az.zu_532
+ 0x09130404, 0x13006407, 0x1b3152ee, 0x1335100c, // fi.et.pl_332 lg.et.un_420 ha.az.tr_422 lt.zu.et_543
+ 0x2a20550d, 0x25001b14, 0x200709ad, 0x07001018, // rw.sq.mt_554 tr.eu.un_660 pl.it.sq_643 be.bg.un_740
+ // [5b90]
+ 0x683b130c, 0x12251fad, 0x11002309, 0x21283b04, // et.so.ig_543 cy.eu.hu_643 ca.ro.un_440 so.sw.jw_332
+ 0x20646bec, 0x6800132c, 0x181f55ee, 0x53250f08, // ceb.lg.sq_644 et.ig.un_990 rw.cy.ga_422 lv.eu.ht_443
+ 0x1c64210d, 0x13201014, 0x0c55250c, 0x29000304, // jw.lg.id_554 lt.sq.et_666 eu.rw.sv_543 nl.sl.un_320
+ 0x0e002705, 0x0d091c13, 0x01001807, 0x3f0203a0, // gd.is.un_330 mr.hi.ne_665 ga.en.un_420 nl.da.af_322
+ // [5ba0]
+ 0x0f046408, 0x1f002b13, 0x13183b0c, 0x0c072aad, // lg.fi.lv_443 vi.cy.un_650 so.ga.et_543 mt.it.sv_643
+ 0x1f002b19, 0x3b1f6b0b, 0x04103ba9, 0x53004a0d, // vi.cy.un_750 ceb.cy.so_542 so.lt.fi_544 yo.ht.un_540
+ 0x182120a9, 0x35041a09, 0x0c032aee, 0x3110120c, // sq.jw.ga_544 tl.fi.zu_444 mt.nl.sv_422 hu.lt.az_543
+ 0x0d292da0, 0x523b10af, 0x183b27ec, 0x3b11100d, // sk.sl.cs_322 lt.so.ha_655 gd.so.ga_644 lt.ro.so_554
+ // [5bb0]
+ 0x0653030c, 0x3b0c52af, 0x11251011, 0x0f3b13ec, // nl.ht.de_543 ha.sv.so_655 lt.eu.ro_653 et.so.lv_644
+ 0x0d001707, 0x031a6bee, 0x2d0d1f14, 0x311b0c0c, // sr.cs.un_420 ceb.tl.nl_422 cy.cs.sk_666 sv.tr.az_543
+ 0x12113512, 0x0d3b27ee, 0x0431100c, 0x0f002304, // zu.ro.hu_654 gd.so.cs_422 lt.az.fi_543 ca.lv.un_320
+ 0x20003f04, 0x205521a9, 0x0804020e, 0x68195508, // af.sq.un_320 jw.rw.sq_544 da.fi.no_555 rw.gl.ig_443
+ // [5bc0]
+ 0x0b0a0702, 0x2d1128ad, 0x3f080ca6, 0x016b0eee, // it.pt.es_222 sw.ro.sk_643 sv.no.af_521 is.ceb.en_422
+ 0x681c3507, 0x64213507, 0x0e52550c, 0x08026405, // zu.id.ig_432 zu.jw.lg_432 rw.ha.is_543 lg.da.no_333
+ 0x03250f60, 0x1610200c, 0x1b4a68af, 0x0a531cee, // lv.eu.nl_664 sq.lt.hr_543 ig.yo.tr_655 id.ht.pt_422
+ 0x190b55ee, 0x29136402, 0x071f27ec, 0x55253b0c, // rw.es.gl_422 lg.et.sl_222 gd.cy.it_644 so.eu.rw_543
+ // [5bd0]
+ 0x281a1204, 0x211a64ad, 0x105528a9, 0x325525a7, // hu.tl.sw_332 lg.tl.jw_643 sw.rw.lt_544 eu.rw.bs_532
+ 0x09006b0d, 0x250f0baf, 0x0c034a0c, 0x2d000308, // ceb.pl.un_540 es.lv.eu_655 yo.nl.sv_543 nl.sk.un_430
+ 0x0e001b07, 0x2d033bee, 0x0b002102, 0x3f100b0c, // tr.is.un_420 so.nl.sk_422 jw.es.un_220 es.lt.af_543
+ 0x280e52a4, 0x53033faf, 0x2d0d0909, 0x132718ec, // ha.is.sw_433 af.nl.ht_655 pl.cs.sk_444 ga.gd.et_644
+ // [5be0]
+ 0x18120307, 0x2b000621, 0x11002519, 0x071a10a4, // nl.hu.ga_432 de.vi.un_860 eu.ro.un_750 lt.tl.it_433
+ 0x1f3b090d, 0x06001704, 0x04131005, 0x1200040c, // pl.so.cy_554 sr.de.un_320 lt.et.fi_333 fi.hu.un_530
+ 0x06530112, 0x55106408, 0x2d0d1fad, 0x102928a4, // en.ht.de_654 lg.lt.rw_443 cy.cs.sk_643 sw.sl.lt_433
+ 0x0a2a05ee, 0x0335100c, 0x20006807, 0x13031ea7, // fr.mt.pt_422 lt.zu.nl_543 ig.sq.un_420 ms.nl.et_532
+ // [5bf0]
+ 0x6b1a1f0c, 0x0f1f060c, 0x1a09210c, 0x53001b12, // cy.tl.ceb_543 de.cy.lv_543 jw.pl.tl_543 tr.ht.un_640
+ 0x28001a19, 0x16291007, 0x5535290c, 0x0d092d04, // tl.sw.un_750 lt.sl.hr_432 sl.zu.rw_543 sk.pl.cs_332
+ 0x033f0fec, 0x10000922, 0x046e1304, 0x3b1b31ec, // lv.af.nl_644 pl.lt.un_870 et.hmn.fi_332 az.tr.so_644
+ 0x53091b07, 0x2100092a, 0x182d0dec, 0x6e683b04, // tr.pl.ht_432 pl.jw.un_970 cs.sk.ga_644 so.ig.hmn_332
+
+ // [5c00]
+ 0x212007ee, 0x120964a7, 0x311b6408, 0x20000a07, // it.sq.jw_422 lg.pl.hu_532 lg.tr.az_443 pt.sq.un_420
+ 0x1055130c, 0x1b006e08, 0x13003b34, 0x030e0208, // et.rw.lt_543 hmn.tr.un_430 so.et.un_A80 da.is.nl_443
+ 0x0b0a01a4, 0x01000e04, 0x0503190c, 0x282519ec, // en.pt.es_433 is.en.un_320 gl.nl.fr_543 gl.eu.sw_644
+ 0x3f28310c, 0x35002904, 0x27001f23, 0x1f006b23, // az.sw.af_543 sl.zu.un_320 cy.gd.un_880 ceb.cy.un_880
+ // [5c10]
+ 0x20281007, 0x182112ee, 0x2b13010c, 0x64554a08, // lt.sw.sq_432 ur.fa.ar_422 en.et.vi_543 yo.rw.lg_443
+ 0x010753ee, 0x06033555, 0x2d0a0dec, 0x10001212, // ht.it.en_422 zu.nl.de_442 cs.pt.sk_644 hu.lt.un_640
+ 0x5500310c, 0x64353114, 0x2a3f0aa0, 0x0a2d2504, // az.rw.un_530 az.zu.lg_666 pt.af.mt_322 eu.sk.pt_332
+ 0x203f130c, 0x35182813, 0x0a1f2d11, 0x09001907, // et.af.sq_543 sw.ga.zu_665 sk.cy.pt_653 gl.pl.un_420
+ // [5c20]
+ 0x55314a5a, 0x06000f04, 0x03290608, 0x1b042aa0, // yo.az.rw_553 lv.de.un_320 de.sl.nl_443 mt.fi.tr_322
+ 0x4a0725ad, 0x235305a9, 0x04252309, 0x20000302, // eu.it.yo_643 fr.ht.ca_544 ca.eu.fi_444 nl.sq.un_220
+ 0x04001f04, 0x2d0d0609, 0x190b0702, 0x12001f12, // cy.fi.un_320 de.cs.sk_444 it.es.gl_222 cy.hu.un_640
+ 0x53012bec, 0x320c1708, 0x3b001f14, 0x1b00100e, // vi.en.ht_644 sr.sv.bs_443 cy.so.un_660 lt.tr.un_550
+ // [5c30]
+ 0x08000a04, 0x03001311, 0x07000519, 0x6b531a14, // mk.uk.un_320 et.nl.un_630 fr.it.un_750 tl.ht.ceb_666
+ 0x0a2327a4, 0x53001f1b, 0x4a001909, 0x061e0804, // gd.ca.pt_433 cy.ht.un_770 gl.yo.un_440 no.ms.de_332
+ 0x3f2a0714, 0x55006814, 0x35551108, 0x01532702, // it.mt.af_666 ig.rw.un_660 ro.rw.zu_443 gd.ht.en_222
+ 0x64006829, 0x27001c02, 0x2500101a, 0x11000e2a, // ig.lg.un_960 id.gd.un_220 lt.eu.un_760 is.ro.un_970
+ // [5c40]
+ 0x202821a4, 0x0c0203a0, 0x321602ee, 0x21000e0c, // jw.sw.sq_433 nl.da.sv_322 da.hr.bs_422 is.jw.un_530
+ 0x09201f08, 0x05001f05, 0x133b0308, 0x1f53230c, // cy.sq.pl_443 cy.fr.un_330 nl.so.et_443 ca.ht.cy_543
+ 0x68181fa9, 0x3f640955, 0x2a0523a9, 0x252009a4, // cy.ga.ig_544 pl.lg.af_442 ca.fr.mt_544 pl.sq.eu_433
+ 0x32160f0e, 0x6b01200d, 0x3f280a12, 0x53006813, // lv.hr.bs_555 sq.en.ceb_554 pt.sw.af_654 ig.ht.un_650
+ // [5c50]
+ 0x6b3f130c, 0x282153ec, 0x3b001e02, 0x0103060d, // et.af.ceb_543 ht.jw.sw_644 ms.so.un_220 de.nl.en_554
+ 0x20005302, 0x532103a4, 0x18352712, 0x29000d17, // ht.sq.un_220 nl.jw.ht_433 gd.zu.ga_654 cs.sl.un_730
+ 0x1b3132a0, 0x11170aec, 0x0f68640e, 0x642b35af, // bs.az.tr_322 mk.sr.ro_644 lg.ig.lv_555 zu.vi.lg_655
+ 0x281335a9, 0x080e0c13, 0x03645307, 0x3f030912, // zu.et.sw_544 sv.is.no_665 ht.lg.nl_432 pl.nl.af_654
+ // [5c60]
+ 0x21000302, 0x0b135204, 0x091c13a6, 0x2d0d0414, // nl.jw.un_220 ha.et.es_332 bh.mr.hi_521 fi.cs.sk_666
+ 0x18002302, 0x08200207, 0x071310ad, 0x1b312109, // ca.ga.un_220 da.sq.no_432 lt.et.it_643 jw.az.tr_444
+ 0x53001c07, 0x1e1c0faf, 0x0a231908, 0x6b001818, // id.ht.un_420 lv.id.ms_655 gl.ca.pt_443 ga.ceb.un_740
+ 0x521a28a9, 0x3b001a0d, 0x07001f13, 0x3b133fa0, // sw.tl.ha_544 tl.so.un_540 cy.it.un_650 af.et.so_322
+ // [5c70]
+ 0x55062108, 0x10001723, 0x68003508, 0x35092812, // jw.de.rw_443 sr.be.un_880 zu.ig.un_430 sw.pl.zu_654
+ 0x5525680d, 0x685553af, 0x133b520d, 0x280535ee, // ig.eu.rw_554 ht.rw.ig_655 ha.so.et_554 zu.fr.sw_422
+ 0x0300021a, 0x68551b12, 0x6425350c, 0x130410ec, // da.nl.un_760 tr.rw.ig_654 zu.eu.lg_543 lt.fi.et_644
+ 0x12000608, 0x25120fa4, 0x1f000c0e, 0x192003a0, // de.hu.un_430 lv.hu.eu_433 sv.cy.un_550 nl.sq.gl_322
+ // [5c80]
+ 0x643552a7, 0x2a001f07, 0x1b640f11, 0x1c211607, // ha.zu.lg_532 cy.mt.un_420 lv.lg.tr_653 hr.jw.id_432
+ 0x321620af, 0x1a212308, 0x640f280c, 0x06002534, // sq.hr.bs_655 ca.jw.tl_443 sw.lv.lg_543 eu.de.un_A80
+ 0x64280f13, 0x28535508, 0x092d0f04, 0x35102513, // lv.sw.lg_665 rw.ht.sw_443 lv.sk.pl_332 eu.lt.zu_665
+ 0x102d070c, 0x64533b05, 0x08111005, 0x121b53ee, // it.sk.lt_543 so.ht.lg_333 be.ro.uk_333 ht.tr.hu_422
+ // [5c90]
+ 0x13350612, 0x194a64a0, 0x282d0f08, 0x05230a0d, // de.zu.et_654 lg.yo.gl_322 lv.sk.sw_443 pt.ca.fr_554
+ 0x291b17a9, 0x03061312, 0x191323a7, 0x210452ad, // sr.tr.sl_544 et.de.nl_654 ca.et.gl_532 ha.fi.jw_643
+ 0x2300190b, 0x555325af, 0x03000907, 0x192a0aad, // gl.ca.un_520 eu.ht.rw_655 pl.nl.un_420 pt.mt.gl_643
+ 0x1f000a1a, 0x270a4a13, 0x211b55a6, 0x5531250c, // pt.cy.un_760 yo.pt.gd_665 rw.tr.jw_521 eu.az.rw_543
+ // [5ca0]
+ 0x3b2d5512, 0x2d0953a0, 0x2d00060c, 0x1c0d13ee, // rw.sk.so_654 ht.pl.sk_322 de.sk.un_530 bh.ne.mr_422
+ 0x553135a4, 0x682752a4, 0x21000723, 0x1029680d, // zu.az.rw_433 ha.gd.ig_433 it.jw.un_880 ig.sl.lt_554
+ 0x35003b2b, 0x190721a0, 0x01003502, 0x35002014, // so.zu.un_980 jw.it.gl_322 zu.en.un_220 sq.zu.un_660
+ 0x29065202, 0x0a122d0d, 0x044a21ee, 0x31251311, // ha.de.sl_222 sk.hu.pt_554 jw.yo.fi_422 et.eu.az_653
+ // [5cb0]
+ 0x254a1a12, 0x08002b04, 0x0b102807, 0x0d292d11, // tl.yo.eu_654 vi.no.un_320 sw.lt.es_432 sk.sl.cs_653
+ 0x04000905, 0x6400130d, 0x3f0c1f14, 0x07003108, // pl.fi.un_330 et.lg.un_540 cy.sv.af_666 az.it.un_430
+ 0x55002d04, 0x1b0513ee, 0x0d1652ee, 0x29006e08, // sk.rw.un_320 et.fr.tr_422 ha.hr.cs_422 hmn.sl.un_430
+ 0x07000418, 0x0b0929ee, 0x251b07ad, 0x050755a7, // ru.bg.un_740 sl.pl.es_422 it.tr.eu_643 rw.it.fr_532
+ // [5cc0]
+ 0x6e036b04, 0x022d290e, 0x016b64a0, 0x283210a9, // ceb.nl.hmn_332 sl.sk.da_555 lg.ceb.en_322 lt.bs.sw_544
+ 0x08000304, 0x014a1307, 0x6b521c0b, 0x133255ee, // nl.no.un_320 et.yo.en_432 id.ha.ceb_542 rw.bs.et_422
+ 0x063552a0, 0x29190a0d, 0x13002108, 0x01281307, // ha.zu.de_322 pt.gl.sl_554 jw.et.un_430 et.sw.en_432
+ 0x1a1c0c0d, 0x02200602, 0x52554a08, 0x080212ec, // sv.id.tl_554 de.sq.da_222 yo.rw.ha_443 hu.da.no_644
+ // [5cd0]
+ 0x03001e0d, 0x211268a9, 0x320816a4, 0x0b0a11ac, // ms.nl.un_540 ig.hu.jw_544 hr.no.bs_433 ro.pt.es_632
+ 0x0c1c53ad, 0x1e1c1aec, 0x1a206ea0, 0x13002017, // ht.id.sv_643 tl.id.ms_644 hmn.sq.tl_322 sq.et.un_730
+ 0x68130204, 0x3f00550c, 0x1b002013, 0x0c270607, // da.et.ig_332 rw.af.un_530 sq.tr.un_650 de.gd.sv_432
+ 0x32162909, 0x0c02120c, 0x27061ead, 0x23061f12, // sl.hr.bs_444 hu.da.sv_543 ms.de.gd_643 cy.de.ca_654
+ // [5ce0]
+ 0x082a02a0, 0x0c051304, 0x03020c0d, 0x35002302, // da.mt.no_322 et.fr.sv_332 sv.da.nl_554 ca.zu.un_220
+ 0x230b3ba4, 0x0c6b08a0, 0x01030602, 0x2a001804, // so.es.ca_433 no.ceb.sv_322 de.nl.en_222 ga.mt.un_320
+ 0x0c3f2907, 0x0c520608, 0x18002d18, 0x251b0ba0, // sl.af.sv_432 de.ha.sv_443 sk.ga.un_740 es.tr.eu_322
+ 0x531a520c, 0x55521bad, 0x5264550c, 0x5300211a, // ha.tl.ht_543 tr.ha.rw_643 rw.lg.ha_543 jw.ht.un_760
+ // [5cf0]
+ 0x18230713, 0x53006422, 0x6b1a1213, 0x2505190c, // it.ca.ga_665 lg.ht.un_870 hu.tl.ceb_665 gl.fr.eu_543
+ 0x6b0e125a, 0x09102504, 0x1b2864a0, 0x161304a4, // hu.is.ceb_553 eu.lt.pl_332 lg.sw.tr_322 fi.et.hr_433
+ 0x0f0310ec, 0x31002102, 0x256b3b09, 0x3f0c25a4, // lt.nl.lv_644 jw.az.un_220 so.ceb.eu_444 eu.sv.af_433
+ 0x6b3153af, 0x3f531e08, 0x120a07a9, 0x033b3fac, // ht.az.ceb_655 ms.ht.af_443 it.pt.hu_544 af.so.nl_632
+ // [5d00]
+ 0x280a2104, 0x20000609, 0x0a033f14, 0x35002102, // jw.pt.sw_332 de.sq.un_440 af.nl.pt_666 jw.zu.un_220
+ 0x080a10ec, 0x1c002a13, 0x190b04a9, 0x052723a4, // be.mk.uk_644 mt.id.un_650 fi.es.gl_544 ca.gd.fr_433
+ 0x031f27ad, 0x27000621, 0x272311af, 0x6b201a0b, // gd.cy.nl_643 de.gd.un_860 ro.ca.gd_655 tl.sq.ceb_542
+ 0x5517040c, 0x02001a07, 0x641b55ee, 0x05072004, // fi.sr.rw_543 tl.da.un_420 rw.tr.lg_422 sq.it.fr_332
+ // [5d10]
+ 0x53091113, 0x280c1060, 0x271f2308, 0x0600230d, // ro.pl.ht_665 lt.sv.sw_664 ca.cy.gd_443 ca.de.un_540
+ 0x10006414, 0x17531baf, 0x16000c0b, 0x53001222, // lg.lt.un_660 tr.ht.sr_655 sv.hr.un_520 hu.ht.un_870
+ 0x25005204, 0x19050a07, 0x3b002107, 0x2307680c, // ha.eu.un_320 pt.fr.gl_432 jw.so.un_420 ig.it.ca_543
+ 0x1b0e1307, 0x100a20a9, 0x051a20ee, 0x2a100504, // et.is.tr_432 sq.pt.lt_544 sq.tl.fr_422 fr.lt.mt_332
+ // [5d20]
+ 0x641b1307, 0x1e4a1ca4, 0x07001707, 0x3b005221, // et.tr.lg_432 id.yo.ms_433 sr.it.un_420 ha.so.un_860
+ 0x03000702, 0x3528170c, 0x08525312, 0x53001e1a, // it.nl.un_220 sr.sw.zu_543 ht.ha.no_654 ms.ht.un_760
+ 0x252d29a0, 0x06100f0d, 0x09002302, 0x53001113, // sl.sk.eu_322 lv.lt.de_554 ca.pl.un_220 ro.ht.un_650
+ 0x3f030fa4, 0x1c002709, 0x521e050c, 0x0727110c, // lv.nl.af_433 gd.id.un_440 fr.ms.ha_543 ro.gd.it_543
+ // [5d30]
+ 0x250210ac, 0x0c130fa4, 0x27002b04, 0x523b2508, // lt.da.eu_632 lv.et.sv_433 vi.gd.un_320 eu.so.ha_443
+ 0x1200190d, 0x35130412, 0x1c005302, 0x68001e0d, // gl.hu.un_540 fi.et.zu_654 ht.id.un_220 ms.ig.un_540
+ 0x016b21a0, 0x31006812, 0x3b311ea9, 0x20001902, // jw.ceb.en_322 ig.az.un_640 ms.az.so_544 gl.sq.un_220
+ 0x55523bec, 0x552752a9, 0x311a52ec, 0x3b004a02, // so.ha.rw_644 ha.gd.rw_544 ha.tl.az_644 yo.so.un_220
+ // [5d40]
+ 0x32001b08, 0x213b520c, 0x35645212, 0x07001e04, // tr.bs.un_430 ha.so.jw_543 ha.lg.zu_654 ms.it.un_320
+ 0x1b1a3ba9, 0x28002114, 0x272118af, 0x356431a7, // so.tl.tr_544 jw.sw.un_660 ga.jw.gd_655 az.lg.zu_532
+ 0x0f10270e, 0x351a28af, 0x0b0f1008, 0x060c020e, // gd.lt.lv_555 sw.tl.zu_655 lt.lv.es_443 da.sv.de_555
+ 0x112b28a4, 0x21313ba4, 0x523b31a9, 0x182927ee, // sw.vi.ro_433 so.az.jw_433 az.so.ha_544 gd.sl.ga_422
+ // [5d50]
+ 0x31645509, 0x2d0d1aa4, 0x55002113, 0x6b5552ec, // rw.lg.az_444 tl.cs.sk_433 jw.rw.un_650 ha.rw.ceb_644
+ 0x1a3531ec, 0x64682812, 0x0d020c12, 0x0b0710af, // az.zu.tl_644 sw.ig.lg_654 sv.da.cs_654 lt.it.es_655
+ 0x0b1f6b11, 0x32172da4, 0x08060207, 0x211b6ea0, // ceb.cy.es_653 sk.sr.bs_433 da.de.no_432 hmn.tr.jw_322
+ 0x063f0aee, 0x19000704, 0x2d0d3205, 0x25193107, // pt.af.de_422 it.gl.un_320 bs.cs.sk_333 az.gl.eu_432
+ // [5d60]
+ 0x0c001a07, 0x55043b0c, 0x1b0a1a07, 0x0c000a02, // tl.sv.un_420 so.fi.rw_543 tl.pt.tr_432 pt.sv.un_220
+ 0x2700050d, 0x1c002d04, 0x13090d55, 0x1e2125a0, // fr.gd.un_540 sk.id.un_320 ne.hi.bh_442 eu.jw.ms_322
+ 0x12553504, 0x1f55520c, 0x18003518, 0x11172911, // zu.rw.hu_332 ha.rw.cy_543 zu.ga.un_740 sl.sr.ro_653
+ 0x35001822, 0x013527a0, 0x103b5507, 0x0f3b3511, // ga.zu.un_870 gd.zu.en_322 rw.so.lt_432 zu.so.lv_653
+ // [5d70]
+ 0x130f6b0c, 0x1c130d04, 0x080612a4, 0x3b004a19, // ceb.lv.et_543 ne.bh.mr_332 hu.de.no_433 yo.so.un_750
+ 0x101113a0, 0x1101070c, 0x3553050c, 0x04120f55, // et.ro.lt_322 it.en.ro_543 fr.ht.zu_543 lv.hu.fi_442
+ 0x0e0713a7, 0x20231304, 0x10072512, 0x200523a9, // et.it.is_532 et.ca.sq_332 eu.it.lt_654 ca.fr.sq_544
+ 0x0c082aad, 0x041b270b, 0x0e0429ac, 0x20002807, // mt.no.sv_643 gd.tr.fi_542 sl.fi.is_632 sw.sq.un_420
+ // [5d80]
+ 0x1a001c02, 0x3b526460, 0x29355212, 0x1c036b02, // id.tl.un_220 lg.ha.so_664 ha.zu.sl_654 ceb.nl.id_222
+ 0x3f6b1a12, 0x1f016804, 0x21002a02, 0x2d0f29a4, // tl.ceb.af_654 ig.en.cy_332 mt.jw.un_220 sl.lv.sk_433
+ 0x0a001920, 0x0b000a1e, 0x0e12080c, 0x29200fee, // gl.pt.un_850 pt.es.un_830 no.hu.is_543 lv.sq.sl_422
+ 0x11006818, 0x13090d0d, 0x10042307, 0x25006e04, // ig.ro.un_740 ne.hi.bh_554 ca.fi.lt_432 hmn.eu.un_320
+ // [5d90]
+ 0x102852a0, 0x0c080412, 0x0e041bad, 0x08040212, // ha.sw.lt_322 fi.no.sv_654 tr.fi.is_643 da.fi.no_654
+ 0x68000d13, 0x2d002514, 0x6b2d550c, 0x033f080c, // cs.ig.un_650 eu.sk.un_660 rw.sk.ceb_543 no.af.nl_543
+ 0x0502010c, 0x16001104, 0x536b2807, 0x111923ee, // en.da.fr_543 ro.hr.un_320 sw.ceb.ht_432 ca.gl.ro_422
+ 0x016e02ee, 0x0e124aa9, 0x2a00041b, 0x522d3fa0, // da.hmn.en_422 yo.hu.is_544 fi.mt.un_770 af.sk.ha_322
+ // [5da0]
+ 0x1208350c, 0x080220af, 0x552d0d0d, 0x2a686b07, // zu.no.hu_543 sq.da.no_655 cs.sk.rw_554 ceb.ig.mt_432
+ 0x102528a4, 0x2d00041a, 0x042d5504, 0x32006e07, // sw.eu.lt_433 fi.sk.un_760 rw.sk.fi_332 hmn.bs.un_420
+ 0x2b00250e, 0x1a6b55a9, 0x1000070e, 0x3b353112, // eu.vi.un_550 rw.ceb.tl_544 it.lt.un_550 az.zu.so_654
+ 0x20071aa0, 0x5500680e, 0x25531f05, 0x3b001902, // tl.it.sq_322 ig.rw.un_550 cy.ht.eu_333 gl.so.un_220
+ // [5db0]
+ 0x052868ee, 0x0f181ea9, 0x356820a4, 0x32003f08, // ig.sw.fr_422 ms.ga.lv_544 sq.ig.zu_433 af.bs.un_430
+ 0x190a050c, 0x10282507, 0x0b0a25a4, 0x01646b02, // fr.pt.gl_543 eu.sw.lt_432 eu.pt.es_433 ceb.lg.en_222
+ 0x16120dee, 0x1e0c1c0c, 0x052a0408, 0x322a160c, // cs.hu.hr_422 id.sv.ms_543 fi.mt.fr_443 hr.mt.bs_543
+ 0x3f0704a4, 0x04003112, 0x080b060c, 0x12061b07, // fi.it.af_433 az.fi.un_640 de.es.no_543 tr.de.hu_432
+ // [5dc0]
+ 0x072a0a0c, 0x3f250b0d, 0x285535a4, 0x1008250c, // pt.mt.it_543 es.eu.af_554 zu.rw.sw_433 eu.no.lt_543
+ 0x132704a7, 0x1a3525a4, 0x64551fa4, 0x08280207, // fi.gd.et_532 eu.zu.tl_433 cy.rw.lg_433 da.sw.no_432
+ 0x03062a08, 0x050704a4, 0x290a350c, 0x13120409, // mt.de.nl_443 fi.it.fr_433 zu.pt.sl_543 fi.hu.et_444
+ 0x0305040c, 0x2d19350b, 0x012027ee, 0x082a0455, // fi.fr.nl_543 zu.gl.sk_542 gd.sq.en_422 fi.mt.no_442
+ // [5dd0]
+ 0x52006e2a, 0x0c002102, 0x04554a0d, 0x1c291e04, // hmn.ha.un_970 jw.sv.un_220 yo.rw.fi_554 ms.sl.id_332
+ 0x1c001a07, 0x20231f0d, 0x55121ea4, 0x081220a6, // tl.id.un_420 cy.ca.sq_554 ms.hu.rw_433 sq.hu.no_521
+ 0x19230b13, 0x35124aee, 0x1e001704, 0x271128af, // es.ca.gl_665 yo.hu.zu_422 sr.ms.un_320 sw.ro.gd_655
+ 0x12000c0b, 0x1c1e3b04, 0x011f09a7, 0x1f00060e, // sv.hu.un_520 so.ms.id_332 pl.cy.en_532 de.cy.un_550
+ // [5de0]
+ 0x29000108, 0x3f002102, 0x6b000709, 0x313519ad, // en.sl.un_430 jw.af.un_220 it.ceb.un_440 gl.zu.az_643
+ 0x280e35a4, 0x04133ba7, 0x0c2a200d, 0x10001f0e, // zu.is.sw_433 so.et.fi_532 sq.mt.sv_554 cy.lt.un_550
+ 0x01130a05, 0x25102805, 0x09001213, 0x35001a12, // pt.et.en_333 sw.lt.eu_333 hu.pl.un_650 tl.zu.un_640
+ 0x190b01a0, 0x10002704, 0x1e1c0dee, 0x190a0faf, // en.es.gl_322 gd.lt.un_320 cs.id.ms_422 lv.pt.gl_655
+ // [5df0]
+ 0x02006b07, 0x130f0ba4, 0x6b68350c, 0x2a0703a4, // ceb.da.un_420 es.lv.et_433 zu.ig.ceb_543 nl.it.mt_433
+ 0x0f210bee, 0x2a000504, 0x28002505, 0x523b21ec, // es.jw.lv_422 fr.mt.un_320 eu.sw.un_330 jw.so.ha_644
+ 0x2d3b0d07, 0x2d0d080c, 0x191c0ba0, 0x0e006b12, // cs.so.sk_432 no.cs.sk_543 es.id.gl_322 ceb.is.un_640
+ 0x356864a4, 0x07193b05, 0x0b076b07, 0x4a3b52ec, // lg.ig.zu_433 so.gl.it_333 ceb.it.es_432 ha.so.yo_644
+ // [5e00]
+ 0x1f2012af, 0x1e000808, 0x071a4aa0, 0x0b003204, // hu.sq.cy_655 no.ms.un_430 yo.tl.it_322 bs.es.un_320
+ 0x0a063fad, 0x1f00521a, 0x35010407, 0x20182905, // af.de.pt_643 ha.cy.un_760 fi.en.zu_432 sl.ga.sq_333
+ 0x202d55a0, 0x080d02ee, 0x0d00350e, 0x06002702, // rw.sk.sq_322 da.cs.no_422 zu.cs.un_550 gd.de.un_220
+ 0x1b0f1304, 0x11000c04, 0x1f000d18, 0x2305080c, // et.lv.tr_332 sv.ro.un_320 cs.cy.un_740 no.fr.ca_543
+ // [5e10]
+ 0x1b131007, 0x081710ec, 0x19050b12, 0x19530a08, // lt.et.tr_432 be.sr.uk_644 es.fr.gl_654 pt.ht.gl_443
+ 0x13003f13, 0x3f031055, 0x23001013, 0x3f032760, // af.et.un_650 lt.nl.af_442 lt.ca.un_650 gd.nl.af_664
+ 0x10003f19, 0x10000312, 0x1b6e10a4, 0x1c4a1ea0, // af.lt.un_750 nl.lt.un_640 lt.hmn.tr_433 ms.yo.id_322
+ 0x1a2d21a0, 0x290f18ad, 0x32311705, 0x11531ba0, // jw.sk.tl_322 ga.lv.sl_643 sr.az.bs_333 tr.ht.ro_322
+ // [5e20]
+ 0x55211009, 0x122a3505, 0x2d0d0f0c, 0x55046bad, // lt.jw.rw_444 zu.mt.hu_333 lv.cs.sk_543 ceb.fi.rw_643
+ 0x52200fa4, 0x12210408, 0x10030ead, 0x29040ea4, // lv.sq.ha_433 fi.jw.hu_443 is.nl.lt_643 is.fi.sl_433
+ 0x18001b0e, 0x0c2b27ad, 0x6b0f21a9, 0x3f000223, // tr.ga.un_550 gd.vi.sv_643 jw.lv.ceb_544 da.af.un_880
+ 0x52202707, 0x033f1812, 0x1c1f1e12, 0x6e271809, // gd.sq.ha_432 ga.af.nl_654 ms.cy.id_654 ga.gd.hmn_444
+ // [5e30]
+ 0x1106070c, 0x0412280c, 0x3f110304, 0x162528a6, // it.de.ro_543 sw.hu.fi_543 nl.ro.af_332 sw.eu.hr_521
+ 0x122821a4, 0x4a0e6805, 0x3f020608, 0x1c201e12, // jw.sw.hu_433 ig.is.yo_333 de.da.af_443 ms.sq.id_654
+ 0x27040512, 0x05010408, 0x0a003502, 0x170f1a08, // fr.fi.gd_654 fi.en.fr_443 zu.pt.un_220 tl.lv.sr_443
+ 0x18090555, 0x19040508, 0x0b0a19a6, 0x2d12290c, // fr.pl.ga_442 fr.fi.gl_443 gl.pt.es_521 sl.hu.sk_543
+ // [5e40]
+ 0x0c0406a7, 0x0d093f0e, 0x25285512, 0x0f1001a4, // de.fi.sv_532 af.pl.cs_555 rw.sw.eu_654 en.lt.lv_433
+ 0x0a1129a0, 0x25000714, 0x081023af, 0x08230f08, // sl.ro.pt_322 it.eu.un_660 ca.lt.no_655 lv.ca.no_443
+ 0x290821a6, 0x1f2a3ba4, 0x21002802, 0x29002d02, // jw.no.sl_521 so.mt.cy_433 sw.jw.un_220 sk.sl.un_220
+ 0x11103107, 0x190a52ee, 0x100f040c, 0x20273fa0, // az.lt.ro_432 ha.pt.gl_422 fi.lv.lt_543 af.gd.sq_322
+ // [5e50]
+ 0x286b5304, 0x2d0d2505, 0x180b1fa0, 0x52201fa4, // ht.ceb.sw_332 eu.cs.sk_333 cy.es.ga_322 cy.sq.ha_433
+ 0x29001723, 0x1c13090b, 0x1712200c, 0x535204ad, // sr.sl.un_880 hi.bh.mr_542 sq.hu.sr_543 fi.ha.ht_643
+ 0x2d0d1209, 0x12000307, 0x556401a4, 0x25030f55, // hu.cs.sk_444 nl.hu.un_420 en.lg.rw_433 lv.nl.eu_442
+ 0x131f03ad, 0x0a003f04, 0x35030a02, 0x02003f07, // nl.cy.et_643 af.pt.un_320 pt.nl.zu_222 af.da.un_420
+ // [5e60]
+ 0x202503ad, 0x2d1e2304, 0x551a68ec, 0x2b322dad, // nl.eu.sq_643 ca.ms.sk_332 ig.tl.rw_644 sk.bs.vi_643
+ 0x05351102, 0x21000204, 0x35295512, 0x27060211, // ro.zu.fr_222 da.jw.un_320 rw.sl.zu_654 da.de.gd_653
+ 0x682855ad, 0x0b2318a4, 0x1e071c07, 0x07000502, // rw.sw.ig_643 ga.ca.es_433 id.it.ms_432 fr.it.un_220
+ 0x2a005302, 0x1e1b1aaf, 0x2d1c0804, 0x3220170c, // ht.mt.un_220 tl.tr.ms_655 no.id.sk_332 sr.sq.bs_543
+ // [5e70]
+ 0x021b1fa7, 0x2a004a0d, 0x32172905, 0x0728350d, // cy.tr.da_532 yo.mt.un_540 sl.sr.bs_333 zu.sw.it_554
+ 0x0f090304, 0x321631a0, 0x09681107, 0x210c1a04, // nl.pl.lv_332 az.hr.bs_322 ro.ig.pl_432 tl.sv.jw_332
+ 0x276b06ee, 0x0c006805, 0x12111304, 0x01523f04, // de.ceb.gd_422 ig.sv.un_330 et.ro.hu_332 af.ha.en_332
+ 0x320512ee, 0x091a64ec, 0x1b00640e, 0x08270cad, // hu.fr.bs_422 lg.tl.pl_644 lg.tr.un_550 sv.gd.no_643
+ // [5e80]
+ 0x0e003f21, 0x0b000a1b, 0x03272a04, 0x1c006e04, // af.is.un_860 pt.es.un_770 mt.gd.nl_332 hmn.id.un_320
+ 0x0c002a05, 0x0b070a07, 0x3500210c, 0x16000c07, // mt.sv.un_330 pt.it.es_432 jw.zu.un_530 sv.hr.un_420
+ 0x0f000604, 0x0a1a6eee, 0x29071308, 0x52353fa0, // de.lv.un_320 hmn.tl.pt_422 et.it.sl_443 af.zu.ha_322
+ 0x0a291008, 0x12201ba7, 0x08076b04, 0x03273f12, // lt.sl.pt_443 tr.sq.hu_532 ceb.it.no_332 af.gd.nl_654
+ // [5e90]
+ 0x273f230c, 0x02126bee, 0x190b23a6, 0x036b3faf, // ca.af.gd_543 ceb.hu.da_422 ca.es.gl_521 af.ceb.nl_655
+ 0x100220a4, 0x2d0d25ee, 0x6b003f07, 0x01006402, // sq.da.lt_433 eu.cs.sk_422 af.ceb.un_420 lg.en.un_220
+ 0x53352508, 0x05011fee, 0x190b2509, 0x070a12a9, // eu.zu.ht_443 cy.en.fr_422 eu.es.gl_444 hu.pt.it_544
+ 0x132935a0, 0x093516ee, 0x040a1f05, 0x132325af, // zu.sl.et_322 hr.zu.pl_422 cy.pt.fi_333 eu.ca.et_655
+ // [5ea0]
+ 0x52281eaf, 0x05092309, 0x04003f13, 0x08352312, // ms.sw.ha_655 ca.pl.fr_444 af.fi.un_650 ca.zu.no_654
+ 0x03002321, 0x20001e0d, 0x0a0b1960, 0x100701a9, // ca.nl.un_860 ms.sq.un_540 gl.es.pt_664 en.it.lt_544
+ 0x1121010c, 0x05010807, 0x1100201b, 0x080717ec, // en.jw.ro_543 no.en.fr_432 sq.ro.un_770 sr.bg.uk_644
+ 0x04641a09, 0x355209ad, 0x27016ba0, 0x35092802, // tl.lg.fi_444 pl.ha.zu_643 ceb.en.gd_322 sw.pl.zu_222
+ // [5eb0]
+ 0x033f06a6, 0x4a0f28ee, 0x21533b04, 0x02000c22, // de.af.nl_521 sw.lv.yo_422 so.ht.jw_332 sv.da.un_870
+ 0x28000920, 0x32203509, 0x290d12af, 0x1c000c05, // pl.sw.un_850 zu.sq.bs_444 hu.cs.sl_655 sv.id.un_330
+ 0x020e08ec, 0x1e211c60, 0x1c000c18, 0x6b2864ac, // no.is.da_644 id.jw.ms_664 sv.id.un_740 lg.sw.ceb_632
+ 0x0b1912a9, 0x1c0f6eee, 0x07282a13, 0x11000f12, // hu.gl.es_544 hmn.lv.id_422 mt.sw.it_665 lv.ro.un_640
+ // [5ec0]
+ 0x32000f04, 0x0c0603a0, 0x120d2012, 0x1b001604, // lv.bs.un_320 nl.de.sv_322 sq.cs.hu_654 hr.tr.un_320
+ 0x3f230512, 0x126425af, 0x290353af, 0x3b000c05, // fr.ca.af_654 eu.lg.hu_655 ht.nl.sl_655 sv.so.un_330
+ 0x0a3b13af, 0x556b680d, 0x2d120daf, 0x18120bec, // et.so.pt_655 ig.ceb.rw_554 cs.hu.sk_655 es.hu.ga_644
+ 0x68133b12, 0x253f03ad, 0x032d0513, 0x03000502, // so.et.ig_654 nl.af.eu_643 fr.sk.nl_665 fr.nl.un_220
+ // [5ed0]
+ 0x55005204, 0x68003f1a, 0x25002002, 0x533f6b02, // ha.rw.un_320 af.ig.un_760 sq.eu.un_220 ceb.af.ht_222
+ 0x03270c07, 0x08171208, 0x212864a0, 0x033f0dee, // sv.gd.nl_432 hu.sr.no_443 lg.sw.jw_322 cs.af.nl_422
+ 0x10000907, 0x130823ad, 0x010a19ee, 0x10023f0c, // pl.lt.un_420 ca.no.et_643 gl.pt.en_422 af.da.lt_543
+ 0x0c05080c, 0x0f060512, 0x1b525508, 0x091953ec, // no.fr.sv_543 fr.de.lv_654 rw.ha.tr_443 ht.gl.pl_644
+ // [5ee0]
+ 0x281125ad, 0x1f0c020c, 0x1c006402, 0x21013bee, // eu.ro.sw_643 da.sv.cy_543 lg.id.un_220 so.en.jw_422
+ 0x19110a0c, 0x1800190d, 0x17290fad, 0x291e1c13, // pt.ro.gl_543 gl.ga.un_540 lv.sl.sr_643 id.ms.sl_665
+ 0x3f055312, 0x04005302, 0x32002104, 0x190a0b07, // ht.fr.af_654 ht.fi.un_220 jw.bs.un_320 es.pt.gl_432
+ 0x1f52640c, 0x07160a07, 0x05000d04, 0x3b521e12, // lg.ha.cy_543 pt.hr.it_432 cs.fr.un_320 ms.ha.so_654
+ // [5ef0]
+ 0x08010bee, 0x033f10ad, 0x06000d04, 0x27001308, // es.en.no_422 lt.af.nl_643 cs.de.un_320 et.gd.un_430
+ 0x6b531a09, 0x6b641a05, 0x25066b02, 0x1f3b2aee, // tl.ht.ceb_444 tl.lg.ceb_333 ceb.de.eu_222 mt.so.cy_422
+ 0x025208af, 0x0c2a07a9, 0x083f0508, 0x321e1c14, // no.ha.da_655 it.mt.sv_544 fr.af.no_443 id.ms.bs_666
+ 0x0e216ba0, 0x6b1a55af, 0x64183fa7, 0x35006e0e, // ceb.jw.is_322 rw.tl.ceb_655 af.ga.lg_532 hmn.zu.un_550
+ // [5f00]
+ 0x316e1307, 0x321606ec, 0x31001111, 0x06001805, // et.hmn.az_432 de.hr.bs_644 ro.az.un_630 ga.de.un_330
+ 0x2d004a05, 0x11000408, 0x051a6860, 0x06000b05, // yo.sk.un_330 ru.ro.un_430 ig.tl.fr_664 es.de.un_330
+ 0x25036ba0, 0x21111a0c, 0x190a6eec, 0x52113107, // ceb.nl.eu_322 tl.ro.jw_543 hmn.pt.gl_644 az.ro.ha_432
+ 0x0b0a10a6, 0x1b002709, 0x0e280caf, 0x08006e0d, // lt.pt.es_521 gd.tr.un_440 sv.sw.is_655 hmn.no.un_540
+ // [5f10]
+ 0x55253b08, 0x1632170c, 0x01282bad, 0x6b1f0c09, // so.eu.rw_443 sr.bs.hr_543 vi.sw.en_643 sv.cy.ceb_444
+ 0x32000804, 0x25042008, 0x53000521, 0x0b0a2309, // no.bs.un_320 sq.fi.eu_443 fr.ht.un_860 ca.pt.es_444
+ 0x11051fa0, 0x203f1304, 0x2d0d0b02, 0x0f082804, // cy.fr.ro_322 et.af.sq_332 es.cs.sk_222 sw.no.lv_332
+ 0x08005219, 0x190b0ea4, 0x3f00280c, 0x1f6b2707, // ha.no.un_750 is.es.gl_433 sw.af.un_530 gd.ceb.cy_432
+ // [5f20]
+ 0x11050304, 0x1b001702, 0x3f000e21, 0x556b2812, // nl.fr.ro_332 sr.tr.un_220 is.af.un_860 sw.ceb.rw_654
+ 0x3f001f14, 0x05040f0c, 0x05001b14, 0x0d1c13a0, // cy.af.un_660 lv.fi.fr_543 tr.fr.un_660 bh.mr.ne_322
+ 0x05000829, 0x3f000f23, 0x10006e1a, 0x013f23af, // no.fr.un_960 lv.af.un_880 hmn.lt.un_760 ca.af.en_655
+ 0x68001c22, 0x520e1a5a, 0x23075308, 0x0164180c, // id.ig.un_870 tl.is.ha_553 ht.it.ca_443 ga.lg.en_543
+ // [5f30]
+ 0x532852ad, 0x030f0111, 0x282568af, 0x0f003f23, // ha.sw.ht_643 en.lv.nl_653 ig.eu.sw_655 af.lv.un_880
+ 0x28133bec, 0x060c0860, 0x103528a9, 0x1e1c52af, // so.et.sw_644 no.sv.de_664 sw.zu.lt_544 ha.id.ms_655
+ 0x2d210da4, 0x1b000e04, 0x08020f07, 0x09520ea9, // cs.jw.sk_433 is.tr.un_320 lv.da.no_432 is.ha.pl_544
+ 0x1005280d, 0x13042508, 0x1f0802ee, 0x180413a9, // sw.fr.lt_554 eu.fi.et_443 da.no.cy_422 et.fi.ga_544
+ // [5f40]
+ 0x092155af, 0x28100508, 0x080c2aec, 0x0b070112, // rw.jw.pl_655 fr.lt.sw_443 mt.sv.no_644 en.it.es_654
+ 0x08520e05, 0x64683bec, 0x0b0a0eee, 0x17072055, // is.ha.no_333 so.ig.lg_644 is.pt.es_422 sq.it.sr_442
+ 0x180727a0, 0x110e0f04, 0x1e001a0c, 0x2a0807a9, // gd.it.ga_322 lv.is.ro_332 tl.ms.un_530 it.no.mt_544
+ 0x04050fa9, 0x683b6412, 0x35215212, 0x3b071e02, // lv.fr.fi_544 lg.so.ig_654 ha.jw.zu_654 ms.it.so_222
+ // [5f50]
+ 0x2d0d23ec, 0x190510ad, 0x3b006402, 0x271018a9, // ca.cs.sk_644 lt.fr.gl_643 lg.so.un_220 ga.lt.gd_544
+ 0x0b000208, 0x0f080e08, 0x0c065213, 0x55640b07, // da.es.un_430 is.no.lv_443 ha.de.sv_665 es.lg.rw_432
+ 0x6b4a550c, 0x02183b55, 0x350b0304, 0x280903a4, // rw.yo.ceb_543 so.ga.da_442 nl.es.zu_332 nl.pl.sw_433
+ 0x3b090407, 0x1c001e20, 0x6453550d, 0x521a1b0c, // fi.pl.so_432 ms.id.un_850 rw.ht.lg_554 tr.tl.ha_543
+ // [5f60]
+ 0x321608af, 0x19003102, 0x186e27a7, 0x083b1b0d, // no.hr.bs_655 az.gl.un_220 gd.hmn.ga_532 tr.so.no_554
+ 0x190b27a4, 0x13005204, 0x216468a0, 0x062a070c, // gd.es.gl_433 ha.et.un_320 ig.lg.jw_322 it.mt.de_543
+ 0x27180605, 0x1101230c, 0x2d0d68a4, 0x0f000a08, // de.ga.gd_333 ca.en.ro_543 ig.cs.sk_433 pt.lv.un_430
+ 0x183b5213, 0x3b27250c, 0x21111aa4, 0x2a1b6ba4, // ha.so.ga_665 eu.gd.so_543 tl.ro.jw_433 ceb.tr.mt_433
+ // [5f70]
+ 0x281a1b0c, 0x100b2804, 0x18000313, 0x1331110c, // tr.tl.sw_543 sw.es.lt_332 nl.ga.un_650 ro.az.et_543
+ 0x1c641ea0, 0x082501a4, 0x030501a9, 0x05005504, // ms.lg.id_322 en.eu.no_433 en.fr.nl_544 rw.fr.un_320
+ 0x1f05040c, 0x641135a4, 0x18210fa4, 0x0d1613ad, // fi.fr.cy_543 zu.ro.lg_433 lv.jw.ga_433 et.hr.cs_643
+ 0x1029350c, 0x322935a4, 0x17286b08, 0x04111a02, // zu.sl.lt_543 zu.sl.bs_433 ceb.sw.sr_443 tl.ro.fi_222
+ // [5f80]
+ 0x1b311208, 0x212a290c, 0x16002107, 0x10130f08, // hu.az.tr_443 sl.mt.jw_543 jw.hr.un_420 lv.et.lt_443
+ 0x16006408, 0x1c2a1ea0, 0x1017040c, 0x190f0bad, // lg.hr.un_430 ms.mt.id_322 ru.sr.be_543 es.lv.gl_643
+ 0x201b060c, 0x2800041b, 0x190b23ac, 0x030c2013, // de.tr.sq_543 fi.sw.un_770 ca.es.gl_632 sq.sv.nl_665
+ 0x131827a9, 0x04032704, 0x030806ee, 0x041c25a9, // gd.ga.et_544 gd.nl.fi_332 de.no.nl_422 eu.id.fi_544
+ // [5f90]
+ 0x103f04a7, 0x0a002104, 0x0e001812, 0x3f5310a4, // fi.af.lt_532 jw.pt.un_320 ga.is.un_640 lt.ht.af_433
+ 0x0d000f12, 0x273f03af, 0x0a2311af, 0x64001602, // lv.cs.un_640 nl.af.gd_655 ro.ca.pt_655 hr.lg.un_220
+ 0x3f032704, 0x3f001f2a, 0x1f3f100c, 0x011f10ee, // gd.nl.af_332 cy.af.un_970 lt.af.cy_543 lt.cy.en_422
+ 0x072320a4, 0x211e1a08, 0x1a4a2813, 0x2d18270d, // sq.ca.it_433 tl.ms.jw_443 sw.yo.tl_665 gd.ga.sk_554
+ // [5fa0]
+ 0x10116ea6, 0x0e2a0b08, 0x4a003113, 0x10072d12, // hmn.ro.lt_521 es.mt.is_443 az.yo.un_650 sk.it.lt_654
+ 0x270b18af, 0x1900281a, 0x0f1310ec, 0x08001913, // ga.es.gd_655 sw.gl.un_760 lt.et.lv_644 gl.no.un_650
+ 0x05002a02, 0x27003b0c, 0x64133508, 0x0b0a5202, // mt.fr.un_220 so.gd.un_530 zu.et.lg_443 ha.pt.es_222
+ 0x21001604, 0x1a006e0d, 0x5200050d, 0x3229080b, // hr.jw.un_320 hmn.tl.un_540 fr.ha.un_540 no.sl.bs_542
+ // [5fb0]
+ 0x100f0c09, 0x4a0b0a14, 0x17523b08, 0x3b0403a9, // sv.lv.lt_444 pt.es.yo_666 so.ha.sr_443 nl.fi.so_544
+ 0x2b006b02, 0x0e101307, 0x6b1c0408, 0x0405310c, // ceb.vi.un_220 et.lt.is_432 fi.id.ceb_443 az.fr.fi_543
+ 0x312a6b04, 0x31201f07, 0x2909320d, 0x3b6413a4, // ceb.mt.az_332 cy.sq.az_432 bs.pl.sl_554 et.lg.so_433
+ 0x315220a7, 0x3f061fa4, 0x07021007, 0x2021040c, // sq.ha.az_532 cy.de.af_433 lt.da.it_432 fi.jw.sq_543
+ // [5fc0]
+ 0x1a216b60, 0x640a4aa0, 0x5200200c, 0x64011fee, // ceb.jw.tl_664 yo.pt.lg_322 sq.ha.un_530 cy.en.lg_422
+ 0x0a5210a4, 0x28203bad, 0x18000121, 0x11163211, // lt.ha.pt_433 so.sq.sw_643 en.ga.un_860 bs.hr.ro_653
+ 0x1a002722, 0x1e251c07, 0x182b2709, 0x1c271e07, // gd.tl.un_870 id.eu.ms_432 gd.vi.ga_444 ms.gd.id_432
+ 0x52005304, 0x52556812, 0x644a115a, 0x2d000f11, // ht.ha.un_320 ig.rw.ha_654 ro.yo.lg_553 lv.sk.un_630
+ // [5fd0]
+ 0x11004a23, 0x1a255512, 0x686b280c, 0x1b1821ee, // yo.ro.un_880 rw.eu.tl_654 sw.ceb.ig_543 jw.ga.tr_422
+ 0x6b1e1aa4, 0x282055a7, 0x1e1c1fee, 0x1e251ca4, // tl.ms.ceb_433 rw.sq.sw_532 cy.id.ms_422 id.eu.ms_433
+ 0x0f091212, 0x201b1104, 0x1a00250d, 0x0f02270c, // hu.pl.lv_654 ro.tr.sq_332 eu.tl.un_540 gd.da.lv_543
+ 0x1a3b4a14, 0x55111a09, 0x10132014, 0x03000e08, // yo.so.tl_666 tl.ro.rw_444 sq.et.lt_666 is.nl.un_430
+ // [5fe0]
+ 0x53003202, 0x17005302, 0x0d530aee, 0x53001e02, // bs.ht.un_220 ht.sr.un_220 pt.ht.cs_422 ms.ht.un_220
+ 0x270618ac, 0x1e1c520e, 0x681a11a4, 0x230225ec, // ga.de.gd_632 ha.id.ms_555 ro.tl.ig_433 eu.da.ca_644
+ 0x351a3f0c, 0x0e103f07, 0x10000c1a, 0x321752a4, // af.tl.zu_543 af.lt.is_432 sv.lt.un_760 ha.sr.bs_433
+ 0x4a00180d, 0x2d022955, 0x1716550c, 0x6e0f1014, // ga.yo.un_540 sl.da.sk_442 rw.hr.sr_543 lt.lv.hmn_666
+ // [5ff0]
+ 0x271f4a60, 0x041a0fa4, 0x0f2510ad, 0x013f290c, // yo.cy.gd_664 lv.tl.fi_433 lt.eu.lv_643 sl.af.en_543
+ 0x190b23ec, 0x6b0604a4, 0x29164a0c, 0x16231f08, // ca.es.gl_644 fi.de.ceb_433 yo.hr.sl_543 cy.ca.hr_443
+ 0x1c351f04, 0x05321fee, 0x3b002721, 0x1f1b1213, // cy.zu.id_332 cy.bs.fr_422 gd.so.un_860 hu.tr.cy_665
+ 0x3200530c, 0x270f04a6, 0x52350f08, 0x00000406, // ht.bs.un_530 fi.lv.gd_521 lv.zu.ha_443 ru.un.un_400
+
+ // [6000]
+ 0x2327040c, 0x112327a9, 0x681c6408, 0x23071fa4, // fi.gd.ca_543 gd.ca.ro_544 lg.id.ig_443 cy.it.ca_433
+ 0x0600040e, 0x0c64080b, 0x0b0952ac, 0x070b2808, // fi.de.un_550 no.lg.sv_542 ha.pl.es_632 sw.es.it_443
+ 0x0955520c, 0x21524aa9, 0x28523bad, 0x20002d12, // ha.rw.pl_543 yo.ha.jw_544 so.ha.sw_643 sk.sq.un_640
+ 0x356b01ee, 0x0f311bec, 0x13113b11, 0x6b0a28a0, // en.ceb.zu_422 tr.az.lv_644 so.ro.et_653 sw.pt.ceb_322
+ // [6010]
+ 0x21681aad, 0x32162a14, 0x2d043fad, 0x193f0b11, // tl.ig.jw_643 mt.hr.bs_666 af.fi.sk_643 es.af.gl_653
+ 0x182735ec, 0x21000a04, 0x556b2802, 0x07112112, // zu.gd.ga_644 pt.jw.un_320 sw.ceb.rw_222 jw.ro.it_654
+ 0x1b0e520c, 0x021a5507, 0x21205512, 0x080c3faf, // ha.is.tr_543 rw.tl.da_432 rw.sq.jw_654 af.sv.no_655
+ 0x53200908, 0x09113b0c, 0x05232aad, 0x093b1112, // pl.sq.ht_443 so.ro.pl_543 mt.ca.fr_643 ro.so.pl_654
+ // [6020]
+ 0x1b2a5212, 0x083f02a0, 0x1f060905, 0x1c0c1a0c, // ha.mt.tr_654 da.af.no_322 pl.de.cy_333 tl.sv.id_543
+ 0x23530cee, 0x20000a2b, 0x0300681a, 0x1f2b07af, // sv.ht.ca_422 pt.sq.un_980 ig.nl.un_760 it.vi.cy_655
+ 0x2d1310ee, 0x1f0135ad, 0x200835ad, 0x04002a04, // lt.et.sk_422 zu.en.cy_643 zu.no.sq_643 mt.fi.un_320
+ 0x2d006808, 0x08023505, 0x1a526ba0, 0x2b005314, // ig.sk.un_430 zu.da.no_333 ceb.ha.tl_322 ht.vi.un_660
+ // [6030]
+ 0x041352a9, 0x1f6b2102, 0x190b1a14, 0x1c521ea0, // ha.et.fi_544 jw.ceb.cy_222 tl.es.gl_666 ms.ha.id_322
+ 0x0a0905a0, 0x18001a07, 0x3f6e1312, 0x3b19070c, // fr.pl.pt_322 tl.ga.un_420 et.hmn.af_654 it.gl.so_543
+ 0x35280755, 0x280306ec, 0x23001604, 0x19100b02, // it.sw.zu_442 de.nl.sw_644 hr.ca.un_320 es.lt.gl_222
+ 0x1107270c, 0x031853a0, 0x645520a4, 0x120b0aa9, // gd.it.ro_543 ht.ga.nl_322 sq.rw.lg_433 pt.es.hu_544
+ // [6040]
+ 0x68092812, 0x112007ad, 0x53006e04, 0x3f6e0307, // sw.pl.ig_654 it.sq.ro_643 hmn.ht.un_320 nl.hmn.af_432
+ 0x3b2907a7, 0x280705af, 0x276b07ee, 0x4a006e04, // it.sl.so_532 fr.it.sw_655 it.ceb.gd_422 hmn.yo.un_320
+ 0x115568ec, 0x3f036e05, 0x3512030c, 0x684a25ec, // ig.rw.ro_644 hmn.nl.af_333 nl.hu.zu_543 eu.yo.ig_644
+ 0x53684a08, 0x321721a9, 0x0a6b070c, 0x052707ad, // yo.ig.ht_443 jw.sr.bs_544 it.ceb.pt_543 it.gd.fr_643
+ // [6050]
+ 0x2a3b0712, 0x232511ee, 0x27060312, 0x0a07280c, // it.so.mt_654 ro.eu.ca_422 nl.de.gd_654 sw.it.pt_543
+ 0x272806a9, 0x311b6b13, 0x4a0f68ad, 0x4a2568a9, // de.sw.gd_544 ceb.tr.az_665 ig.lv.yo_643 ig.eu.yo_544
+ 0x644a5512, 0x20024a02, 0x17002d35, 0x68293f5a, // rw.yo.lg_654 yo.da.sq_222 sk.sr.un_A90 af.sl.ig_553
+ 0x32163f14, 0x1c2764a4, 0x0a014aa0, 0x1c00170c, // af.hr.bs_666 lg.gd.id_433 yo.en.pt_322 sr.id.un_530
+ // [6060]
+ 0x080221a4, 0x2a000d08, 0x6e6b1fa0, 0x211b1caf, // jw.da.no_433 cs.mt.un_430 cy.ceb.hmn_322 id.tr.jw_655
+ 0x21003504, 0x64132a07, 0x1e1c52a0, 0x043b1ea7, // zu.jw.un_320 mt.et.lg_432 ha.id.ms_322 ms.so.fi_532
+ 0x19050ba0, 0x2a006421, 0x682a640c, 0x0f27100d, // es.fr.gl_322 lg.mt.un_860 lg.mt.ig_543 lt.gd.lv_554
+ 0x061b31a0, 0x646b35ad, 0x0e002507, 0x1a52530c, // az.tr.de_322 zu.ceb.lg_643 eu.is.un_420 ht.ha.tl_543
+ // [6070]
+ 0x07310e04, 0x1120530c, 0x1f270f09, 0x060704a9, // is.az.it_332 ht.sq.ro_543 lv.gd.cy_444 fi.it.de_544
+ 0x5355210c, 0x1c101ea9, 0x281f27a6, 0x64002a19, // jw.rw.ht_543 ms.lt.id_544 gd.cy.sw_521 mt.lg.un_750
+ 0x072b110c, 0x040812a4, 0x030152a0, 0x21041a0c, // ro.vi.it_543 hu.no.fi_433 ha.en.nl_322 tl.fi.jw_543
+ 0x2a005213, 0x27002823, 0x03683f0d, 0x1e6425a4, // ha.mt.un_650 sw.gd.un_880 af.ig.nl_554 eu.lg.ms_433
+ // [6080]
+ 0x0a120612, 0x3b001105, 0x64211aa9, 0x0d173bad, // de.hu.pt_654 ro.so.un_330 tl.jw.lg_544 so.sr.cs_643
+ 0x2b0155a0, 0x102506a4, 0x2a643f04, 0x35041f0c, // rw.en.vi_322 de.eu.lt_433 af.lg.mt_332 cy.fi.zu_543
+ 0x0e1809ec, 0x0e06130c, 0x68000608, 0x2d2917ee, // pl.ga.is_644 et.de.is_543 de.ig.un_430 sr.sl.sk_422
+ 0x073164a7, 0x6b6806ec, 0x52190b13, 0x0b2753a0, // lg.az.it_532 de.ig.ceb_644 es.gl.ha_665 ht.gd.es_322
+ // [6090]
+ 0x0a002307, 0x2b53270c, 0x1f062bee, 0x25002321, // ca.pt.un_420 gd.ht.vi_543 vi.de.cy_422 ca.eu.un_860
+ 0x21521aec, 0x06531f11, 0x0c530604, 0x23552aa0, // tl.ha.jw_644 cy.ht.de_653 de.ht.sv_332 mt.rw.ca_322
+ 0x11130605, 0x4a1a11a9, 0x08002d08, 0x122855ee, // de.et.ro_333 ro.tl.yo_544 sk.no.un_430 rw.sw.hu_422
+ 0x0a001134, 0x20291711, 0x1b0768a9, 0x19001f02, // ro.pt.un_A80 sr.sl.sq_653 ig.it.tr_544 cy.gl.un_220
+ // [60a0]
+ 0x0d1016a4, 0x130c0614, 0x3f00291a, 0x020553af, // hr.lt.cs_433 de.sv.et_666 sl.af.un_760 ht.fr.da_655
+ 0x6b010713, 0x29130460, 0x1c1e2104, 0x25000302, // it.en.ceb_665 fi.et.sl_664 jw.ms.id_332 nl.eu.un_220
+ 0x1e2d0d14, 0x1e101c04, 0x213b5512, 0x27001105, // cs.sk.ms_666 id.lt.ms_332 rw.so.jw_654 ro.gd.un_330
+ 0x0c0229ad, 0x55132a0c, 0x205528ec, 0x071035a4, // sl.da.sv_643 mt.et.rw_543 sw.rw.sq_644 zu.lt.it_433
+ // [60b0]
+ 0x063f3508, 0x0f006423, 0x100f1a12, 0x683f06ad, // zu.af.de_443 lg.lv.un_880 tl.lv.lt_654 de.af.ig_643
+ 0x216b28ad, 0x2735110c, 0x4a0f52a4, 0x133b6b14, // sw.ceb.jw_643 ro.zu.gd_543 ha.lv.yo_433 ceb.so.et_666
+ 0x080253ee, 0x2000211b, 0x64683ba7, 0x354a530c, // ht.da.no_422 jw.sq.un_770 so.ig.lg_532 ht.yo.zu_543
+ 0x27100f09, 0x19050ba7, 0x68003505, 0x055331ec, // lv.lt.gd_444 es.fr.gl_532 zu.ig.un_330 az.ht.fr_644
+ // [60c0]
+ 0x0f1320a9, 0x10006b0d, 0x292d3f05, 0x01002905, // sq.et.lv_544 ceb.lt.un_540 af.sk.sl_333 sl.en.un_330
+ 0x352820af, 0x28003b18, 0x010910a0, 0x0b3507a4, // sq.sw.zu_655 so.sw.un_740 lt.pl.en_322 it.zu.es_433
+ 0x32191704, 0x3b281308, 0x643b13a0, 0x01002b07, // sr.gl.bs_332 et.sw.so_443 et.so.lg_322 vi.en.un_420
+ 0x53003f09, 0x3b281f02, 0x08120cee, 0x23001f12, // af.ht.un_440 cy.sw.so_222 sv.hu.no_422 cy.ca.un_640
+ // [60d0]
+ 0x12042a12, 0x680112a0, 0x6b2052a9, 0x23001235, // mt.fi.hu_654 hu.en.ig_322 ha.sq.ceb_544 hu.ca.un_A90
+ 0x283b1fee, 0x0c081905, 0x644a0911, 0x20006822, // cy.so.sw_422 gl.no.sv_333 pl.yo.lg_653 ig.sq.un_870
+ 0x53163fa9, 0x6b005523, 0x52136860, 0x3f68640c, // af.hr.ht_544 rw.ceb.un_880 ig.et.ha_664 lg.ig.af_543
+ 0x3f2a2512, 0x25180aaf, 0x0201080c, 0x320f20a4, // eu.mt.af_654 pt.ga.eu_655 no.en.da_543 sq.lv.bs_433
+ // [60e0]
+ 0x3b2820ad, 0x202b08ee, 0x64521fa9, 0x1b001220, // sq.sw.so_643 no.vi.sq_422 cy.ha.lg_544 hu.tr.un_850
+ 0x1e551ca9, 0x642810a4, 0x06350ea0, 0x3f035509, // id.rw.ms_544 lt.sw.lg_433 is.zu.de_322 rw.nl.af_444
+ 0x21131e07, 0x21103507, 0x28002120, 0x283b55ee, // ms.et.jw_432 zu.lt.jw_432 jw.sw.un_850 rw.so.sw_422
+ 0x230308a4, 0x64013f0c, 0x2a1955ee, 0x214a1ca4, // no.nl.ca_433 af.en.lg_543 rw.gl.mt_422 id.yo.jw_433
+ // [60f0]
+ 0x6b080213, 0x1121520c, 0x353255a4, 0x0a080405, // da.no.ceb_665 ha.jw.ro_543 rw.bs.zu_433 ru.uk.mk_333
+ 0x3b002805, 0x2d520d08, 0x3b536b08, 0x1c0e4aac, // sw.so.un_330 cs.ha.sk_443 ceb.ht.so_443 yo.is.id_632
+ 0x28000707, 0x0f001e07, 0x3f2864ac, 0x291310af, // it.sw.un_420 ms.lv.un_420 lg.sw.af_632 lt.et.sl_655
+ 0x1e1c6414, 0x19230a11, 0x64041eee, 0x050806ee, // lg.id.ms_666 pt.ca.gl_653 ms.fi.lg_422 de.no.fr_422
+ // [6100]
+ 0x0b18020c, 0x4a0453a0, 0x530504a4, 0x284a5208, // da.ga.es_543 ht.fi.yo_322 fi.fr.ht_433 ha.yo.sw_443
+ 0x3f3b02a0, 0x060308a4, 0x052d27a0, 0x32162911, // da.so.af_322 no.nl.de_433 gd.sk.fr_322 sl.hr.bs_653
+ 0x0f100713, 0x5305640c, 0x0a1205af, 0x04056407, // it.lt.lv_665 lg.fr.ht_543 fr.hu.pt_655 lg.fr.fi_432
+ 0x070255af, 0x1b1304ec, 0x1809050c, 0x316b1ca0, // rw.da.it_655 fi.et.tr_644 fr.pl.ga_543 id.ceb.az_322
+ // [6110]
+ 0x08021caf, 0x23011e0c, 0x072823a4, 0x132a0ca9, // id.da.no_655 ms.en.ca_543 ca.sw.it_433 sv.mt.et_544
+ 0x010c0404, 0x5300230e, 0x3b0b0411, 0x0b0a235a, // fi.sv.en_332 ca.ht.un_550 fi.es.so_653 ca.pt.es_553
+ 0x01050407, 0x551b640c, 0x0f0e13af, 0x0d271860, // fi.fr.en_432 lg.tr.rw_543 et.is.lv_655 ga.gd.cs_664
+ 0x0d122d0d, 0x3b3f1313, 0x12254a13, 0x3b005213, // sk.hu.cs_554 et.af.so_665 yo.eu.hu_665 ha.so.un_650
+ // [6120]
+ 0x29165207, 0x27092513, 0x292d2008, 0x282a68ad, // ha.hr.sl_432 eu.pl.gd_665 sq.sk.sl_443 ig.mt.sw_643
+ 0x0b1923a6, 0x27643b13, 0x531e4aa4, 0x521b2308, // ca.gl.es_521 so.lg.gd_665 yo.ms.ht_433 ca.tr.ha_443
+ 0x080e180c, 0x231827a9, 0x3f2a0605, 0x11000e21, // ga.is.no_543 gd.ga.ca_544 de.mt.af_333 is.ro.un_860
+ 0x2a040f0c, 0x29110da7, 0x035325ee, 0x4a250655, // lv.fi.mt_543 cs.ro.sl_532 eu.ht.nl_422 de.eu.yo_442
+ // [6130]
+ 0x21251c12, 0x125225a9, 0x18000919, 0x1a1c52af, // id.eu.jw_654 eu.ha.hu_544 pl.ga.un_750 ha.id.tl_655
+ 0x190b25ee, 0x6b002504, 0x521a1e11, 0x1f0618a4, // eu.es.gl_422 eu.ceb.un_320 ms.tl.ha_653 ga.de.cy_433
+ 0x6b351a12, 0x3b06270e, 0x291f06af, 0x13002817, // tl.zu.ceb_654 gd.de.so_555 de.cy.sl_655 sw.et.un_730
+ 0x231f6ea9, 0x52682504, 0x080e6b5a, 0x521b25ec, // hmn.cy.ca_544 eu.ig.ha_332 ceb.is.no_553 eu.tr.ha_644
+ // [6140]
+ 0x130452a0, 0x0452070c, 0x21073fad, 0x10251213, // ha.fi.et_322 it.ha.fi_543 af.it.jw_643 hu.eu.lt_665
+ 0x09002a02, 0x0c1e2305, 0x0f2d04a4, 0x0f6b1a0d, // mt.pl.un_220 ca.ms.sv_333 fi.sk.lv_433 tl.ceb.lv_554
+ 0x0b0a1b09, 0x16000c04, 0x3b2a5208, 0x052a5202, // tr.pt.es_444 sv.hr.un_320 ha.mt.so_443 ha.mt.fr_222
+ 0x640428af, 0x0700050e, 0x160710ee, 0x1009680e, // sw.fi.lg_655 fr.it.un_550 lt.it.hr_422 ig.pl.lt_555
+ // [6150]
+ 0x17291607, 0x271825ad, 0x25001f2b, 0x282120ec, // hr.sl.sr_432 eu.ga.gd_643 cy.eu.un_980 sq.jw.sw_644
+ 0x68042107, 0x351e5209, 0x642510a7, 0x08130405, // jw.fi.ig_432 ha.ms.zu_444 lt.eu.lg_532 fi.et.no_333
+ 0x32111e0b, 0x4a001018, 0x110725af, 0x0b350507, // ms.ro.bs_542 lt.yo.un_740 eu.it.ro_655 fr.zu.es_432
+ 0x07081213, 0x68122114, 0x102718ec, 0x231f0a02, // hu.no.it_665 jw.hu.ig_666 ga.gd.lt_644 pt.cy.ca_222
+ // [6160]
+ 0x0a072da9, 0x1a6b2d0c, 0x2d1925a4, 0x643b07ad, // sk.it.pt_544 sk.ceb.tl_543 eu.gl.sk_433 it.so.lg_643
+ 0x31001007, 0x02001705, 0x1a04680c, 0x23160a07, // lt.az.un_420 sr.da.un_330 ig.fi.tl_543 pt.hr.ca_432
+ 0x0a003114, 0x11006b02, 0x2d2928a7, 0x1e000505, // az.pt.un_660 ceb.ro.un_220 sw.sl.sk_532 fr.ms.un_330
+ 0x23071fa0, 0x120421a0, 0x081025a0, 0x041328ee, // cy.it.ca_322 jw.fi.hu_322 eu.lt.no_322 sw.et.fi_422
+ // [6170]
+ 0x0a111712, 0x10271f12, 0x10000b19, 0x23281909, // sr.ro.mk_654 cy.gd.lt_654 es.lt.un_750 gl.sw.ca_444
+ 0x01003b02, 0x643b6804, 0x11002d0d, 0x1a001902, // so.en.un_220 ig.so.lg_332 sk.ro.un_540 gl.tl.un_220
+ 0x0e23080c, 0x1a00020c, 0x281b1c0c, 0x522835a4, // no.ca.is_543 da.tl.un_530 id.tr.sw_543 zu.sw.ha_433
+ 0x556e2804, 0x10000e13, 0x2a521bad, 0x19041309, // sw.hmn.rw_332 is.lt.un_650 tr.ha.mt_643 et.fi.gl_444
+ // [6180]
+ 0x16283508, 0x23181ea0, 0x55281f0c, 0x07000b02, // zu.sw.hr_443 ms.ga.ca_322 cy.sw.rw_543 es.it.un_220
+ 0x2b004a0c, 0x21001f22, 0x202821ad, 0x21001b1a, // yo.vi.un_530 cy.jw.un_870 jw.sw.sq_643 tr.jw.un_760
+ 0x2800091a, 0x3f032012, 0x211c2008, 0x0e551e0c, // pl.sw.un_760 sq.nl.af_654 sq.id.jw_443 ms.rw.is_543
+ 0x0f3f0ca9, 0x020f0807, 0x101721a7, 0x08050f07, // sv.af.lv_544 no.lv.da_432 jw.sr.lt_532 lv.fr.no_432
+ // [6190]
+ 0x291a21a4, 0x2d0220ec, 0x040e28af, 0x0c003f0e, // jw.tl.sl_433 sq.da.sk_644 sw.is.fi_655 af.sv.un_550
+ 0x16321712, 0x02002304, 0x35001b12, 0x35002a1a, // sr.bs.hr_654 ca.da.un_320 tr.zu.un_640 mt.zu.un_760
+ 0x010603ee, 0x18055502, 0x1b2023a6, 0x1c051e07, // nl.de.en_422 rw.fr.ga_222 ca.sq.tr_521 ms.fr.id_432
+ 0x0a050fa7, 0x011b13ee, 0x3f27130c, 0x04100e08, // lv.fr.pt_532 et.tr.en_422 et.gd.af_543 is.lt.fi_443
+ // [61a0]
+ 0x1300102c, 0x64201109, 0x35551b07, 0x01072005, // lt.et.un_990 ro.sq.lg_444 tr.rw.zu_432 sq.it.en_333
+ 0x18273bec, 0x2300270b, 0x21121ea9, 0x01002119, // so.gd.ga_644 gd.ca.un_520 ms.hu.jw_544 jw.en.un_750
+ 0x28042107, 0x2d006b04, 0x200513ee, 0x17290d05, // jw.fi.sw_432 ceb.sk.un_320 et.fr.sq_422 cs.sl.sr_333
+ 0x1c2a070c, 0x12020812, 0x1e211baf, 0x082d250c, // it.mt.id_543 no.da.hu_654 tr.jw.ms_655 eu.sk.no_543
+ // [61b0]
+ 0x6b002522, 0x03001a08, 0x6b080eaf, 0x21001602, // eu.ceb.un_870 tl.nl.un_430 is.no.ceb_655 hr.jw.un_220
+ 0x321729a9, 0x6b4a1a07, 0x536b05ee, 0x2d0d17a4, // sl.sr.bs_544 tl.yo.ceb_432 fr.ceb.ht_422 sr.cs.sk_433
+ 0x11202508, 0x35131ea0, 0x13356808, 0x4a3f3b08, // eu.sq.ro_443 ms.et.zu_322 ig.zu.et_443 so.af.yo_443
+ 0x27112304, 0x272d2a07, 0x09003520, 0x25092aee, // ca.ro.gd_332 mt.sk.gd_432 zu.pl.un_850 mt.pl.eu_422
+ // [61c0]
+ 0x6e185212, 0x12003b0d, 0x3f4a13a4, 0x07280b13, // ha.ga.hmn_654 so.hu.un_540 et.yo.af_433 es.sw.it_665
+ 0x25211fa4, 0x122b3b0d, 0x6b6817a6, 0x062d0f07, // cy.jw.eu_433 so.vi.hu_554 sr.ig.ceb_521 lv.sk.de_432
+ 0x0768280c, 0x2d0d0ca4, 0x106825ad, 0x281c010b, // sw.ig.it_543 sv.cs.sk_433 eu.ig.lt_643 en.id.sw_542
+ 0x0b251a08, 0x0700210c, 0x01271112, 0x27191812, // tl.eu.es_443 jw.it.un_530 ro.gd.en_654 ga.gl.gd_654
+ // [61d0]
+ 0x1c6b0cad, 0x2d5511ad, 0x68016b0d, 0x1a00520d, // sv.ceb.id_643 ro.rw.sk_643 ceb.en.ig_554 ha.tl.un_540
+ 0x3b681b0c, 0x3b1a28a9, 0x5300230c, 0x320216a4, // tr.ig.so_543 sw.tl.so_544 ca.ht.un_530 hr.da.bs_433
+ 0x3b1a5209, 0x180b2708, 0x17211c0e, 0x21281c08, // ha.tl.so_444 gd.es.ga_443 id.jw.sr_555 id.sw.jw_443
+ 0x121b55a4, 0x01000614, 0x1b3168af, 0x05551104, // rw.tr.hu_433 de.en.un_660 ig.az.tr_655 ro.rw.fr_332
+ // [61e0]
+ 0x1a52310c, 0x04641309, 0x1e1f1ca4, 0x0c6b2aec, // az.ha.tl_543 et.lg.fi_444 id.cy.ms_433 mt.ceb.sv_644
+ 0x0a2119ad, 0x04134a13, 0x08212a0c, 0x3f3b2504, // gl.jw.pt_643 yo.et.fi_665 mt.jw.no_543 eu.so.af_332
+ 0x640e5508, 0x1b681a0c, 0x2d0d4aa0, 0x0e002a33, // rw.is.lg_443 tl.ig.tr_543 yo.cs.sk_322 mt.is.un_A70
+ 0x2d0d08a4, 0x13271860, 0x231729ec, 0x03233fa0, // no.cs.sk_433 ga.gd.et_664 sl.sr.ca_644 af.ca.nl_322
+ // [61f0]
+ 0x21641705, 0x0f2301a9, 0x180f01ad, 0x126403a9, // sr.lg.jw_333 en.ca.lv_544 en.lv.ga_643 nl.lg.hu_544
+ 0x19014aa0, 0x29071f0d, 0x080f2908, 0x0e00272a, // yo.en.gl_322 cy.it.sl_554 sl.lv.no_443 gd.is.un_970
+ 0x0818270d, 0x3b001007, 0x0b2d1904, 0x551b35ee, // gd.ga.no_554 lt.so.un_420 gl.sk.es_332 zu.tr.rw_422
+ 0x0a073b05, 0x53130eee, 0x00001203, 0x09001f02, // so.it.pt_333 is.et.ht_422 ur.un.un_300 cy.pl.un_220
+ // [6200]
+ 0x04281b55, 0x211304ec, 0x1b1110a4, 0x25130460, // tr.sw.fi_442 fi.et.jw_644 lt.ro.tr_433 fi.et.eu_664
+ 0x113104ee, 0x08006b20, 0x2d120dad, 0x0f000720, // fi.az.ro_422 ceb.no.un_850 cs.hu.sk_643 it.lv.un_850
+ 0x5200250b, 0x07001a1a, 0x0a1219a9, 0x64005308, // eu.ha.un_520 tl.it.un_760 gl.hu.pt_544 ht.lg.un_430
+ 0x0b001708, 0x08090304, 0x3f2103ac, 0x1b1011af, // sr.es.un_430 nl.pl.no_332 nl.jw.af_632 ro.lt.tr_655
+ // [6210]
+ 0x1a6b2814, 0x23053ba0, 0x06180812, 0x216e0e04, // sw.ceb.tl_666 so.fr.ca_322 no.ga.de_654 is.hmn.jw_332
+ 0x234a1812, 0x0e0b1a08, 0x0f200907, 0x076b11a0, // ga.yo.ca_654 tl.es.is_443 pl.sq.lv_432 ro.ceb.it_322
+ 0x29001222, 0x3b1f6e0d, 0x2a072513, 0x23180d0c, // hu.sl.un_870 hmn.cy.so_554 eu.it.mt_665 cs.ga.ca_543
+ 0x27010cee, 0x12005302, 0x5200210c, 0x280f2504, // sv.en.gd_422 ht.hu.un_220 jw.ha.un_530 eu.lv.sw_332
+ // [6220]
+ 0x0a251ca7, 0x1e3b3508, 0x104a0f11, 0x08023f02, // id.eu.pt_532 zu.so.ms_443 lv.yo.lt_653 af.da.no_222
+ 0x123104af, 0x1e531c04, 0x536b4a04, 0x3b352a12, // fi.az.hu_655 id.ht.ms_332 yo.ceb.ht_332 mt.zu.so_654
+ 0x1e0d3155, 0x07190a14, 0x280f0d04, 0x16003f12, // az.cs.ms_442 pt.gl.it_666 cs.lv.sw_332 af.hr.un_640
+ 0x12520407, 0x55000513, 0x2a000618, 0x1f195214, // fi.ha.hu_432 fr.rw.un_650 de.mt.un_740 ha.gl.cy_666
+ // [6230]
+ 0x64001209, 0x16131e08, 0x0b1720ee, 0x0c271809, // hu.lg.un_440 ms.et.hr_443 sq.sr.es_422 ga.gd.sv_444
+ 0x12101f09, 0x13050455, 0x535228a9, 0x2b006808, // cy.lt.hu_444 fi.fr.et_442 sw.ha.ht_544 ig.vi.un_430
+ 0x552811af, 0x0a1229ee, 0x02001012, 0x1e1c1bac, // ro.sw.rw_655 sl.hu.pt_422 lt.da.un_640 tr.id.ms_632
+ 0x31000904, 0x12000702, 0x1200680c, 0x0a000d08, // pl.az.un_320 it.hu.un_220 ig.hu.un_530 cs.pt.un_430
+ // [6240]
+ 0x100f090c, 0x21124aee, 0x071117ad, 0x042852ad, // pl.lv.lt_543 yo.hu.jw_422 sr.ro.bg_643 ha.sw.fi_643
+ 0x190a25ec, 0x3f061ea4, 0x0a000707, 0x211c35a0, // eu.pt.gl_644 ms.de.af_433 it.pt.un_420 zu.id.jw_322
+ 0x05001912, 0x10131ea4, 0x03120660, 0x043525ad, // gl.fr.un_640 ms.et.lt_433 de.hu.nl_664 eu.zu.fi_643
+ 0x535229af, 0x0f11130d, 0x21273fa0, 0x01005505, // sl.ha.ht_655 et.ro.lv_554 af.gd.jw_322 rw.en.un_330
+ // [6250]
+ 0x2a003f0d, 0x081b02ad, 0x3f13060c, 0x081b1305, // af.mt.un_540 da.tr.no_643 de.et.af_543 et.tr.no_333
+ 0x3b2d35a9, 0x321709a4, 0x1e1c18a4, 0x271f18ad, // zu.sk.so_544 pl.sr.bs_433 ga.id.ms_433 ga.cy.gd_643
+ 0x3f3552a0, 0x21523fad, 0x211f3507, 0x2d0a23af, // ha.zu.af_322 af.ha.jw_643 zu.cy.jw_432 ca.pt.sk_655
+ 0x210f1c0c, 0x1a001008, 0x52004a02, 0x1c031eee, // id.lv.jw_543 lt.tl.un_430 yo.ha.un_220 ms.nl.id_422
+ // [6260]
+ 0x03131ba7, 0x1f122b07, 0x021008a0, 0x020e0812, // tr.et.nl_532 vi.hu.cy_432 no.lt.da_322 no.is.da_654
+ 0x08020314, 0x6b55045a, 0x2035530c, 0x35201708, // nl.da.no_666 fi.rw.ceb_553 ht.zu.sq_543 sr.sq.zu_443
+ 0x04311b08, 0x642804ee, 0x06311b08, 0x09071813, // tr.az.fi_443 fi.sw.lg_422 tr.az.de_443 ga.it.pl_665
+ 0x2b002a02, 0x3f000f13, 0x23534a08, 0x190b1114, // mt.vi.un_220 lv.af.un_650 yo.ht.ca_443 ro.es.gl_666
+ // [6270]
+ 0x181013a4, 0x09172dee, 0x2400010c, 0x4a1e550c, // et.lt.ga_433 sk.sr.pl_422 iw.yi.un_530 rw.ms.yo_543
+ 0x190b0da4, 0x0a2d23ec, 0x311b09a7, 0x19050a0b, // cs.es.gl_433 ca.sk.pt_644 pl.tr.az_532 pt.fr.gl_542
+ 0x520e0412, 0x170e16a4, 0x181123a4, 0x080c02a0, // fi.is.ha_654 hr.is.sr_433 ca.ro.ga_433 da.sv.no_322
+ 0x0d00160c, 0x01000205, 0x190a55af, 0x060c0fee, // hr.cs.un_530 da.en.un_330 rw.pt.gl_655 lv.sv.de_422
+ // [6280]
+ 0x0b2312ec, 0x1f0e5355, 0x6e005522, 0x1e00030d, // hu.ca.es_644 ht.is.cy_442 rw.hmn.un_870 nl.ms.un_540
+ 0x190a3205, 0x190b2312, 0x0f32250d, 0x0b005207, // bs.pt.gl_333 ca.es.gl_654 eu.bs.lv_554 ha.es.un_420
+ 0x230a0bec, 0x52002d0d, 0x100c0ea9, 0x03054a02, // es.pt.ca_644 sk.ha.un_540 is.sv.lt_544 yo.fr.nl_222
+ 0x05520655, 0x19120a0d, 0x0d102d12, 0x2d0d23a4, // de.ha.fr_442 pt.hu.gl_554 sk.lt.cs_654 ca.cs.sk_433
+ // [6290]
+ 0x01251fa0, 0x230a19a9, 0x12003202, 0x1e043b12, // cy.eu.en_322 gl.pt.ca_544 bs.hu.un_220 so.fi.ms_654
+ 0x29126ea4, 0x2d0d1fec, 0x06030fad, 0x524a10ad, // hmn.hu.sl_433 cy.cs.sk_644 lv.nl.de_643 lt.yo.ha_643
+ 0x130f08a4, 0x3b006e02, 0x04002308, 0x23000e19, // no.lv.et_433 hmn.so.un_220 ca.fi.un_430 is.ca.un_750
+ 0x09100faf, 0x310264ec, 0x6b000307, 0x04283b09, // lv.lt.pl_655 lg.da.az_644 nl.ceb.un_420 so.sw.fi_444
+ // [62a0]
+ 0x21001210, 0x092a0f13, 0x06006804, 0x0900020e, // hu.jw.un_620 lv.mt.pl_665 ig.de.un_320 da.pl.un_550
+ 0x1b316b0c, 0x05093f08, 0x0d062da9, 0x07002b07, // ceb.az.tr_543 af.pl.fr_443 sk.de.cs_544 vi.it.un_420
+ 0x10006405, 0x090f05a9, 0x07000e07, 0x04001702, // lg.lt.un_330 fr.lv.pl_544 is.it.un_420 sr.ru.un_220
+ 0x291c0e04, 0x645505a0, 0x082d0d13, 0x1c001f12, // is.id.sl_332 fr.rw.lg_322 cs.sk.no_665 cy.id.un_640
+ // [62b0]
+ 0x1300100c, 0x3b290eee, 0x3f270c07, 0x286b550c, // lt.et.un_530 is.sl.so_422 sv.gd.af_432 rw.ceb.sw_543
+ 0x1b1304a4, 0x042031ad, 0x35135508, 0x1b313505, // fi.et.tr_433 az.sq.fi_643 rw.et.zu_443 zu.az.tr_333
+ 0x1a553508, 0x13001702, 0x03130407, 0x312d1b07, // zu.rw.tl_443 sr.et.un_220 fi.et.nl_432 tr.sk.az_432
+ 0x3f1210af, 0x033f0404, 0x4a002b19, 0x1819110c, // lt.hu.af_655 fi.af.nl_332 vi.yo.un_750 ro.gl.ga_543
+ // [62c0]
+ 0x250120a4, 0x00002837, 0x2d0d11af, 0x03006e04, // sq.en.eu_433 sw.un.un_B00 ro.cs.sk_655 hmn.nl.un_320
+ 0x68001f1a, 0x0a552008, 0x1f5564af, 0x23112004, // cy.ig.un_760 sq.rw.pt_443 lg.rw.cy_655 sq.ro.ca_332
+ 0x0a000e02, 0x190a4a09, 0x556852af, 0x200102a0, // is.pt.un_220 yo.pt.gl_444 ha.ig.rw_655 da.en.sq_322
+ 0x3f6b1a0e, 0x356e2b05, 0x3f001805, 0x1000230d, // tl.ceb.af_555 vi.hmn.zu_333 ga.af.un_330 ca.lt.un_540
+ // [62d0]
+ 0x01050808, 0x556b1ca0, 0x0e6b1305, 0x6b001e22, // no.fr.en_443 id.ceb.rw_322 et.ceb.is_333 ms.ceb.un_870
+ 0x286852a7, 0x0b0764a4, 0x2a1f25af, 0x1e104aee, // ha.ig.sw_532 lg.it.es_433 eu.cy.mt_655 yo.lt.ms_422
+ 0x2b005202, 0x31281ca4, 0x3f0a1804, 0x050753ec, // ha.vi.un_220 id.sw.az_433 ga.pt.af_332 ht.it.fr_644
+ 0x1a5325a4, 0x521c6808, 0x2b530f05, 0x55001319, // eu.ht.tl_433 ig.id.ha_443 lv.ht.vi_333 et.rw.un_750
+ // [62e0]
+ 0x64006b05, 0x350e0c0e, 0x3f00520c, 0x530f1305, // ceb.lg.un_330 sv.is.zu_555 ha.af.un_530 et.lv.ht_333
+ 0x11002102, 0x072053a7, 0x1964550c, 0x070e53ad, // jw.ro.un_220 ht.sq.it_532 rw.lg.gl_543 ht.is.it_643
+ 0x042a10ec, 0x355364ec, 0x09102d12, 0x091f6809, // lt.mt.fi_644 lg.ht.zu_644 sk.lt.pl_654 ig.cy.pl_444
+ 0x6855110c, 0x2564230b, 0x3f4a6808, 0x18684a04, // ro.rw.ig_543 ca.lg.eu_542 ig.yo.af_443 yo.ig.ga_332
+ // [62f0]
+ 0x55004a34, 0x13000f1a, 0x1b4a2808, 0x010628ee, // yo.rw.un_A80 lv.et.un_760 sw.yo.tr_443 sw.de.en_422
+ 0x09525512, 0x042513ad, 0x1028160c, 0x0e02200c, // rw.ha.pl_654 et.eu.fi_643 hr.sw.lt_543 sq.da.is_543
+ 0x6e25130d, 0x281b35ee, 0x32101707, 0x2a0f10af, // et.eu.hmn_554 zu.tr.sw_422 sr.lt.bs_432 lt.lv.mt_655
+ 0x1f066809, 0x1e1c04af, 0x04002511, 0x12001a23, // ig.de.cy_444 fi.id.ms_655 eu.fi.un_630 tl.hu.un_880
+ // [6300]
+ 0x354a53ec, 0x19030ba4, 0x31002702, 0x681f06af, // ht.yo.zu_644 es.nl.gl_433 gd.az.un_220 de.cy.ig_655
+ 0x030b060e, 0x4a2125a7, 0x130c25a9, 0x19350b07, // de.es.nl_555 eu.jw.yo_532 eu.sv.et_544 es.zu.gl_432
+ 0x28124aec, 0x03042802, 0x03353fa4, 0x110405af, // yo.hu.sw_644 sw.fi.nl_222 af.zu.nl_433 fr.fi.ro_655
+ 0x1b0a25ac, 0x3b2a19ec, 0x06032a0c, 0x032012ec, // eu.pt.tr_632 gl.mt.so_644 mt.nl.de_543 hu.sq.nl_644
+ // [6310]
+ 0x52201ba7, 0x3f1303a9, 0x09000305, 0x1b533104, // tr.sq.ha_532 nl.et.af_544 nl.pl.un_330 az.ht.tr_332
+ 0x352d0ca4, 0x1e1c0b05, 0x0a230ea4, 0x321b0e13, // sv.sk.zu_433 es.id.ms_333 is.ca.pt_433 is.tr.bs_665
+ 0x28126eee, 0x11550e07, 0x121023a0, 0x05002513, // hmn.hu.sw_422 is.rw.ro_432 ca.lt.hu_322 eu.fr.un_650
+ 0x2d0d2108, 0x55000908, 0x05235309, 0x53050aa7, // jw.cs.sk_443 pl.rw.un_430 ht.ca.fr_444 pt.fr.ht_532
+ // [6320]
+ 0x21000704, 0x31283b02, 0x0213080c, 0x644a0411, // it.jw.un_320 so.sw.az_222 no.et.da_543 fi.yo.lg_653
+ 0x6e6855ad, 0x6819200d, 0x25286bee, 0x00002537, // rw.ig.hmn_643 sq.gl.ig_554 ceb.sw.eu_422 eu.un.un_B00
+ 0x6b2a5507, 0x0455250c, 0x13520455, 0x20045502, // rw.mt.ceb_432 eu.rw.fi_543 fi.ha.et_442 rw.fi.sq_222
+ 0x201a5207, 0x200429ee, 0x0621010c, 0x2d290d0d, // ha.tl.sq_432 sl.fi.sq_422 en.jw.de_543 cs.sl.sk_554
+ // [6330]
+ 0x28116802, 0x53003b12, 0x29210aee, 0x52002913, // ig.ro.sw_222 so.ht.un_640 pt.jw.sl_422 sl.ha.un_650
+ 0x3b1a2aa0, 0x0f1810a9, 0x2a1e1c09, 0x0e2909ac, // mt.tl.so_322 lt.ga.lv_544 id.ms.mt_444 pl.sl.is_632
+ 0x033f290c, 0x04103b11, 0x28685208, 0x13006804, // sl.af.nl_543 so.lt.fi_653 ha.ig.sw_443 ig.et.un_320
+ 0x02536804, 0x0e003f05, 0x0d090f14, 0x0d2d0208, // ig.ht.da_332 af.is.un_330 lv.pl.cs_666 da.sk.cs_443
+ // [6340]
+ 0x52553509, 0x681c1eac, 0x6455680d, 0x2d0d2355, // zu.rw.ha_444 ms.id.ig_632 ig.rw.lg_554 ca.cs.sk_442
+ 0x0c205260, 0x64355508, 0x043b0912, 0x0500250b, // ha.sq.sv_664 rw.zu.lg_443 pl.so.fi_654 eu.fr.un_520
+ 0x35002114, 0x0c1808ac, 0x2d002512, 0x1300101b, // jw.zu.un_660 no.ga.sv_632 eu.sk.un_640 lt.et.un_770
+ 0x552120a4, 0x25000f08, 0x235205ad, 0x1e28520c, // sq.jw.rw_433 lv.eu.un_430 fr.ha.ca_643 ha.sw.ms_543
+ // [6350]
+ 0x35005305, 0x0c002302, 0x060105a0, 0x0f033f13, // ht.zu.un_330 ca.sv.un_220 fr.en.de_322 af.nl.lv_665
+ 0x6b681c05, 0x291e060c, 0x1e1c2aa4, 0x68642008, // id.ig.ceb_333 de.ms.sl_543 mt.id.ms_433 sq.lg.ig_443
+ 0x0b100a13, 0x641b3109, 0x0b000804, 0x080e100d, // pt.lt.es_665 az.tr.lg_444 no.es.un_320 lt.is.no_554
+ 0x0c0a550c, 0x2321200c, 0x2b001905, 0x230705ec, // rw.pt.sv_543 sq.jw.ca_543 gl.vi.un_330 fr.it.ca_644
+ // [6360]
+ 0x101325ad, 0x19002b19, 0x07683ba7, 0x311b3bec, // eu.et.lt_643 vi.gl.un_750 so.ig.it_532 so.tr.az_644
+ 0x1c091ea4, 0x2b002013, 0x55203513, 0x28203512, // ms.pl.id_433 sq.vi.un_650 zu.sq.rw_665 zu.sq.sw_654
+ 0x1a103108, 0x121e3513, 0x20111bad, 0x3b1b1ea7, // az.lt.tl_443 zu.ms.hu_665 tr.ro.sq_643 ms.tr.so_532
+ 0x060c08a9, 0x1e1c2aee, 0x553b1b09, 0x0508060c, // no.sv.de_544 mt.id.ms_422 tr.so.rw_444 de.no.fr_543
+ // [6370]
+ 0x3b1b5507, 0x0b1221ee, 0x08021ca4, 0x20282907, // rw.tr.so_432 jw.hu.es_422 id.da.no_433 sl.sw.sq_432
+ 0x0a006e1b, 0x321710ec, 0x64000708, 0x06010cee, // hmn.pt.un_770 lt.sr.bs_644 it.lg.un_430 sv.en.de_422
+ 0x352520ad, 0x31082702, 0x2b316e07, 0x11000a2a, // sq.eu.zu_643 gd.no.az_222 hmn.az.vi_432 mk.ro.un_970
+ 0x12050a0e, 0x2900350c, 0x190b0dec, 0x3b3f1305, // pt.fr.hu_555 zu.sl.un_530 cs.es.gl_644 et.af.so_333
+ // [6380]
+ 0x1335530c, 0x554a35a0, 0x13041608, 0x1f040ea4, // ht.zu.et_543 zu.yo.rw_322 hr.fi.et_443 is.fi.cy_433
+ 0x312913ad, 0x020a07a0, 0x2d212b12, 0x1a002912, // et.sl.az_643 it.pt.da_322 vi.jw.sk_654 sl.tl.un_640
+ 0x08003b12, 0x3f2821ad, 0x322925ad, 0x0e001a12, // so.no.un_640 jw.sw.af_643 eu.sl.bs_643 tl.is.un_640
+ 0x64251a0c, 0x21003b18, 0x352521ad, 0x2128080d, // tl.eu.lg_543 so.jw.un_740 jw.eu.zu_643 no.sw.jw_554
+ // [6390]
+ 0x0e080b04, 0x04001b13, 0x284a10a6, 0x55273bec, // es.no.is_332 tr.fi.un_650 lt.yo.sw_521 so.gd.rw_644
+ 0x216435a9, 0x32002507, 0x1c2a28ad, 0x211e2807, // zu.lg.jw_544 eu.bs.un_420 sw.mt.id_643 sw.ms.jw_432
+ 0x01280802, 0x64255508, 0x2d0d17ee, 0x0e0806a7, // no.sw.en_222 rw.eu.lg_443 sr.cs.sk_422 de.no.is_532
+ 0x20643512, 0x1b0903ec, 0x1a526412, 0x1c071e0c, // zu.lg.sq_654 nl.pl.tr_644 lg.ha.tl_654 ms.it.id_543
+ // [63a0]
+ 0x25551c11, 0x20281e12, 0x20002b2a, 0x21526ba0, // id.rw.eu_653 ms.sw.sq_654 vi.sq.un_970 ceb.ha.jw_322
+ 0x082821a4, 0x3f6b3b0c, 0x06002808, 0x082b18ad, // jw.sw.no_433 so.ceb.af_543 sw.de.un_430 ga.vi.no_643
+ 0x0a001602, 0x1c6e1e11, 0x2900270d, 0x19250ba0, // hr.pt.un_220 ms.hmn.id_653 gd.sl.un_540 es.eu.gl_322
+ 0x283564ec, 0x186e0a0c, 0x320d1f04, 0x190b53ee, // lg.zu.sw_644 pt.hmn.ga_543 cy.cs.bs_332 ht.es.gl_422
+ // [63b0]
+ 0x1f076e0d, 0x19250b12, 0x211b310d, 0x6b680b0c, // hmn.it.cy_554 es.eu.gl_654 az.tr.jw_554 es.ig.ceb_543
+ 0x030e2513, 0x13003f0e, 0x180a3107, 0x020e0860, // eu.is.nl_665 af.et.un_550 az.pt.ga_432 no.is.da_664
+ 0x1e005212, 0x3f020605, 0x0d5304ad, 0x102509ee, // ha.ms.un_640 de.da.af_333 fi.ht.cs_643 pl.eu.lt_422
+ 0x3f090409, 0x55122105, 0x3500532a, 0x21311b08, // fi.pl.af_444 jw.hu.rw_333 ht.zu.un_970 tr.az.jw_443
+ // [63c0]
+ 0x25062b04, 0x19000505, 0x21003f02, 0x1a003107, // vi.de.eu_332 fr.gl.un_330 af.jw.un_220 az.tl.un_420
+ 0x1006090b, 0x1e1c2bee, 0x1c201eaf, 0x1c111e04, // pl.de.lt_542 vi.id.ms_422 ms.sq.id_655 ms.ro.id_332
+ 0x3f1105a7, 0x1a55250c, 0x281a01a7, 0x53280504, // fr.ro.af_532 eu.rw.tl_543 en.tl.sw_532 fr.sw.ht_332
+ 0x6b6e68a0, 0x29002a0d, 0x0100071b, 0x191b0a07, // ig.hmn.ceb_322 mt.sl.un_540 it.en.un_770 pt.tr.gl_432
+ // [63d0]
+ 0x0500681b, 0x06003520, 0x130504a9, 0x030727a0, // ig.fr.un_770 zu.de.un_850 fi.fr.et_544 gd.it.nl_322
+ 0x6b003b1a, 0x2d00350d, 0x130411ee, 0x64281208, // so.ceb.un_760 zu.sk.un_540 ro.fi.et_422 hu.sw.lg_443
+ 0x1c0405a0, 0x091f31ad, 0x03000412, 0x11130460, // fr.fi.id_322 az.cy.pl_643 fi.nl.un_640 fi.et.ro_664
+ 0x05003b07, 0x35005212, 0x180b6ba0, 0x21002a19, // so.fr.un_420 ha.zu.un_640 ceb.es.ga_322 mt.jw.un_750
+ // [63e0]
+ 0x130452ad, 0x0500020b, 0x01536402, 0x2a005202, // ha.fi.et_643 da.fr.un_520 lg.ht.en_222 ha.mt.un_220
+ 0x1c000729, 0x17291008, 0x0f3f10ec, 0x530609af, // it.id.un_960 lt.sl.sr_443 lt.af.lv_644 pl.de.ht_655
+ 0x11074a12, 0x1a1053a0, 0x55002807, 0x05006b19, // yo.it.ro_654 ht.lt.tl_322 sw.rw.un_420 ceb.fr.un_750
+ 0x29020dee, 0x28010404, 0x041f09ec, 0x181f3b02, // cs.da.sl_422 fi.en.sw_332 pl.cy.fi_644 so.cy.ga_222
+ // [63f0]
+ 0x2a003b2a, 0x072b01ee, 0x2800160e, 0x0c0e0b0c, // so.mt.un_970 en.vi.it_422 hr.sw.un_550 es.is.sv_543
+ 0x02005319, 0x0b0a55a4, 0x0e0a23a0, 0x525525a7, // ht.da.un_750 rw.pt.es_433 ca.pt.is_322 eu.rw.ha_532
+ 0x132864a0, 0x08002807, 0x28000807, 0x52641a05, // lg.sw.et_322 sw.no.un_420 no.sw.un_420 tl.lg.ha_333
+ 0x1a2a53a7, 0x3f280f0c, 0x16006405, 0x1c281e0c, // ht.mt.tl_532 lv.sw.af_543 lg.hr.un_330 ms.sw.id_543
+
+ // [6400]
+ 0x09006b23, 0x29071fee, 0x6b2a0607, 0x092804a4, // ceb.pl.un_880 cy.it.sl_422 de.mt.ceb_432 fi.sw.pl_433
+ 0x6e2a53af, 0x1e6b1c07, 0x530f0cad, 0x2a072704, // ht.mt.hmn_655 id.ceb.ms_432 sv.lv.ht_643 gd.it.mt_332
+ 0x2d000a05, 0x3f6803a4, 0x06002a02, 0x050c0909, // pt.sk.un_330 nl.ig.af_433 mt.de.un_220 pl.sv.fr_444
+ 0x641b3113, 0x121604ad, 0x0a311112, 0x032a09ec, // az.tr.lg_665 fi.hr.hu_643 ro.az.pt_654 pl.mt.nl_644
+ // [6410]
+ 0x292a0713, 0x01640ba0, 0x2d00081a, 0x09550fa4, // it.mt.sl_665 es.lg.en_322 no.sk.un_760 lv.rw.pl_433
+ 0x090f06af, 0x13002807, 0x1e1c6bee, 0x1c1921ee, // de.lv.pl_655 sw.et.un_420 ceb.id.ms_422 jw.gl.id_422
+ 0x181f1ca9, 0x1e1f2711, 0x2d002907, 0x21275505, // id.cy.ga_544 gd.cy.ms_653 sl.sk.un_420 rw.gd.jw_333
+ 0x6e0407a0, 0x251b2da4, 0x230f1807, 0x2a050eaf, // it.fi.hmn_322 sk.tr.eu_433 ga.lv.ca_432 is.fr.mt_655
+ // [6420]
+ 0x1b522113, 0x5300040c, 0x07062512, 0x27000b13, // jw.ha.tr_665 fi.ht.un_530 eu.de.it_654 es.gd.un_650
+ 0x52641a0c, 0x2a0a23a9, 0x4a131b12, 0x183f08a9, // tl.lg.ha_543 ca.pt.mt_544 tr.et.yo_654 no.af.ga_544
+ 0x2a002b0d, 0x0e002913, 0x043b1305, 0x040610ec, // vi.mt.un_540 sl.is.un_650 et.so.fi_333 lt.de.fi_644
+ 0x100604a4, 0x2b001a22, 0x322010a7, 0x291f1e0b, // fi.de.lt_433 tl.vi.un_870 lt.sq.bs_532 ms.cy.sl_542
+ // [6430]
+ 0x680501a7, 0x09002902, 0x1e002302, 0x0f103ba7, // en.fr.ig_532 sl.pl.un_220 ca.ms.un_220 so.lt.lv_532
+ 0x3f3b3508, 0x132b210b, 0x1b185212, 0x062031ad, // zu.so.af_443 jw.vi.et_542 ha.ga.tr_654 az.sq.de_643
+ 0x073f19a7, 0x071e1aee, 0x02000607, 0x322d1609, // gl.af.it_532 tl.ms.it_422 de.da.un_420 hr.sk.bs_444
+ 0x64553f07, 0x190a2005, 0x19680d07, 0x131135a4, // af.rw.lg_432 sq.pt.gl_333 cs.ig.gl_432 zu.ro.et_433
+ // [6440]
+ 0x323504ee, 0x3f4a0313, 0x21204aa6, 0x1f2d0d0d, // fi.zu.bs_422 nl.yo.af_665 yo.sq.jw_521 cs.sk.cy_554
+ 0x183b6804, 0x4a553b12, 0x133b2813, 0x042d0fa4, // ig.so.ga_332 so.rw.yo_654 sw.so.et_665 lv.sk.fi_433
+ 0x030213ad, 0x2a003f21, 0x68552804, 0x02080c0d, // et.da.nl_643 af.mt.un_860 sw.rw.ig_332 sv.no.da_554
+ 0x0b3f10a4, 0x3f02180c, 0x55110ba0, 0x061225ee, // lt.af.es_433 ga.da.af_543 es.ro.rw_322 eu.hu.de_422
+ // [6450]
+ 0x23051aa4, 0x6b1a2a0c, 0x09100ca0, 0x275564ac, // tl.fr.ca_433 mt.tl.ceb_543 sv.lt.pl_322 lg.rw.gd_632
+ 0x021f080c, 0x3b0413a0, 0x6804130e, 0x3f133b05, // no.cy.da_543 et.fi.so_322 et.fi.ig_555 so.et.af_333
+ 0x08063f13, 0x0b0519ee, 0x06032013, 0x353f2813, // af.de.no_665 gl.fr.es_422 sq.nl.de_665 sw.af.zu_665
+ 0x03533bee, 0x10000605, 0x16320304, 0x0c060813, // so.ht.nl_422 de.lt.un_330 nl.bs.hr_332 no.de.sv_665
+ // [6460]
+ 0x03533fa0, 0x06133fa9, 0x11000b1b, 0x1a276b09, // af.ht.nl_322 af.et.de_544 es.ro.un_770 ceb.gd.tl_444
+ 0x1a076b07, 0x52080c13, 0x080e520b, 0x1b230cee, // ceb.it.tl_432 sv.no.ha_665 ha.is.no_542 sv.ca.tr_422
+ 0x0d000607, 0x211a2b07, 0x6b003b04, 0x19000d08, // de.cs.un_420 vi.tl.jw_432 so.ceb.un_320 cs.gl.un_430
+ 0x3b35200c, 0x120b05af, 0x06251f60, 0x07006b0e, // sq.zu.so_543 fr.es.hu_655 cy.eu.de_664 ceb.it.un_550
+ // [6470]
+ 0x011b0507, 0x13001609, 0x2d0d050e, 0x356468a9, // fr.tr.en_432 hr.et.un_440 fr.cs.sk_555 ig.lg.zu_544
+ 0x53162913, 0x126b52a0, 0x0d2d120b, 0x00001615, // sl.hr.ht_665 ha.ceb.hu_322 hu.sk.cs_542 hr.un.un_700
+ 0x0b001804, 0x0b0a13ee, 0x081f0208, 0x310f1207, // ga.es.un_320 et.pt.es_422 da.cy.no_443 hu.lv.az_432
+ 0x120a0d14, 0x010b0f0c, 0x02003f02, 0x3216230c, // cs.pt.hu_666 lv.es.en_543 af.da.un_220 ca.hr.bs_543
+ // [6480]
+ 0x283b1ead, 0x27051b0c, 0x2a2007ec, 0x64011aa4, // ms.so.sw_643 tr.fr.gd_543 it.sq.mt_644 tl.en.lg_433
+ 0x2a121f0c, 0x1b041f08, 0x06121fa7, 0x20006b12, // cy.hu.mt_543 cy.fi.tr_443 cy.hu.de_532 ceb.sq.un_640
+ 0x040908af, 0x12003519, 0x1e1c68a0, 0x0c532008, // no.pl.fi_655 zu.hu.un_750 ig.id.ms_322 sq.ht.sv_443
+ 0x19250bec, 0x52283b0c, 0x2b64050b, 0x0c000702, // es.eu.gl_644 so.sw.ha_543 fr.lg.vi_542 it.sv.un_220
+ // [6490]
+ 0x190b31a0, 0x3f641f07, 0x681828af, 0x2d041f0c, // az.es.gl_322 cy.lg.af_432 sw.ga.ig_655 cy.fi.sk_543
+ 0x0d6b1aec, 0x03061207, 0x281235ad, 0x06283fa4, // tl.ceb.cs_644 hu.de.nl_432 zu.hu.sw_643 af.sw.de_433
+ 0x186b27ec, 0x10181f05, 0x535201a4, 0x6b683b0c, // gd.ceb.ga_644 cy.ga.lt_333 en.ha.ht_433 so.ig.ceb_543
+ 0x68270608, 0x05032aee, 0x290209ad, 0x31002004, // de.gd.ig_443 mt.nl.fr_422 pl.da.sl_643 sq.az.un_320
+ // [64a0]
+ 0x0d201212, 0x6b533bee, 0x060327ad, 0x212a53af, // hu.sq.cs_654 so.ht.ceb_422 gd.nl.de_643 ht.mt.jw_655
+ 0x68062809, 0x1a1227a9, 0x4a3b1109, 0x2a1f23af, // sw.de.ig_444 gd.hu.tl_544 ro.so.yo_444 ca.cy.mt_655
+ 0x105268a9, 0x536e2a14, 0x03280608, 0x1e1c07af, // ig.ha.lt_544 mt.hmn.ht_666 de.sw.nl_443 it.id.ms_655
+ 0x180968a4, 0x061a35a9, 0x041f130e, 0x2a114a12, // ig.pl.ga_433 zu.tl.de_544 et.cy.fi_555 yo.ro.mt_654
+ // [64b0]
+ 0x3f2a0e05, 0x05003b12, 0x086b68af, 0x3508060c, // is.mt.af_333 so.fr.un_640 ig.ceb.no_655 de.no.zu_543
+ 0x01083555, 0x0b004a0d, 0x211a28a6, 0x32534aad, // zu.no.en_442 yo.es.un_540 sw.tl.jw_521 yo.ht.bs_643
+ 0x040e1e0c, 0x3f212b0e, 0x3f011f0d, 0x0b20050d, // ms.is.fi_543 vi.jw.af_555 cy.en.af_554 fr.sq.es_554
+ 0x01001802, 0x10006805, 0x32003b13, 0x0a0f0e08, // ga.en.un_220 ig.lt.un_330 so.bs.un_650 is.lv.pt_443
+ // [64c0]
+ 0x52001b22, 0x04000520, 0x16101704, 0x286b100c, // tr.ha.un_870 fr.fi.un_850 sr.lt.hr_332 lt.ceb.sw_543
+ 0x3f6b1aa0, 0x3f2301ee, 0x02080c12, 0x211c6411, // tl.ceb.af_322 en.ca.af_422 sv.no.da_654 lg.id.jw_653
+ 0x080e020b, 0x3b682008, 0x020c085a, 0x68003b0e, // da.is.no_542 sq.ig.so_443 no.sv.da_553 so.ig.un_550
+ 0x28350605, 0x643208ee, 0x282523a4, 0x64003f13, // de.zu.sw_333 no.bs.lg_422 ca.eu.sw_433 af.lg.un_650
+ // [64d0]
+ 0x03183fa4, 0x4a6b1ba4, 0x090802ee, 0x53081907, // af.ga.nl_433 tr.ceb.yo_433 da.no.pl_422 gl.no.ht_432
+ 0x2d060daf, 0x35171aa4, 0x0f00251b, 0x040b1313, // cs.de.sk_655 tl.sr.zu_433 eu.lv.un_770 et.es.fi_665
+ 0x172910a6, 0x043f64ee, 0x03006b04, 0x190b4aa4, // lt.sl.sr_521 lg.af.fi_422 ceb.nl.un_320 yo.es.gl_433
+ 0x6400080d, 0x080227ee, 0x04002a1b, 0x1a092811, // no.lg.un_540 gd.da.no_422 mt.fi.un_770 sw.pl.tl_653
+ // [64e0]
+ 0x200c6eec, 0x32281a0c, 0x02080460, 0x04001808, // hmn.sv.sq_644 tl.sw.bs_543 fi.no.da_664 ga.fi.un_430
+ 0x180631a0, 0x6e00200e, 0x10002d11, 0x0d202da9, // az.de.ga_322 sq.hmn.un_550 sk.lt.un_630 sk.sq.cs_544
+ 0x1b04520c, 0x53212312, 0x0e0f045a, 0x0d006807, // ha.fi.tr_543 ca.jw.ht_654 fi.lv.is_553 ig.cs.un_420
+ 0x641b0409, 0x0d082da0, 0x13006402, 0x13001b02, // fi.tr.lg_444 sk.no.cs_322 lg.et.un_220 tr.et.un_220
+ // [64f0]
+ 0x645535af, 0x2a040a0c, 0x0a2105af, 0x03131fa6, // zu.rw.lg_655 pt.fi.mt_543 fr.jw.pt_655 cy.et.nl_521
+ 0x35016412, 0x00001242, 0x231b0aad, 0x2331250c, // lg.en.zu_654 ur.un.un_C00 pt.tr.ca_643 eu.az.ca_543
+ 0x29250d12, 0x1e002a0b, 0x20536ba9, 0x066b1ca4, // cs.eu.sl_654 mt.ms.un_520 ceb.ht.sq_544 id.ceb.de_433
+ 0x04201307, 0x0b001a08, 0x06001a0d, 0x555235a4, // et.sq.fi_432 tl.es.un_430 tl.de.un_540 zu.ha.rw_433
+ // [6500]
+ 0x0e53010c, 0x0b0a19ec, 0x1b233109, 0x136828ad, // en.ht.is_543 gl.pt.es_644 az.ca.tr_444 sw.ig.et_643
+ 0x27000704, 0x4a6828ec, 0x3b5255a9, 0x0a006e14, // it.gd.un_320 sw.ig.yo_644 rw.ha.so_544 hmn.pt.un_660
+ 0x1a2952ee, 0x1b00681a, 0x6b002822, 0x2d280713, // ha.sl.tl_422 ig.tr.un_760 sw.ceb.un_870 it.sw.sk_665
+ 0x2a1628ee, 0x12001608, 0x07006809, 0x190b0109, // sw.hr.mt_422 hr.hu.un_430 ig.it.un_440 en.es.gl_444
+ // [6510]
+ 0x320f2907, 0x641a1313, 0x11000d16, 0x2a1a53a4, // sl.lv.bs_432 et.tl.lg_665 cs.ro.un_720 ht.tl.mt_433
+ 0x130419a7, 0x030f08a4, 0x190b2302, 0x211219a0, // gl.fi.et_532 no.lv.nl_433 ca.es.gl_222 gl.hu.jw_322
+ 0x0a0b19ee, 0x04080aa7, 0x0d2908a7, 0x3229160e, // gl.es.pt_422 mk.uk.ru_532 no.sl.cs_532 hr.sl.bs_555
+ 0x2a35190b, 0x25121b12, 0x29130405, 0x5232200c, // gl.zu.mt_542 tr.hu.eu_654 fi.et.sl_333 sq.bs.ha_543
+ // [6520]
+ 0x13352512, 0x0b181907, 0x2d0d55a0, 0x551b040c, // eu.zu.et_654 gl.ga.es_432 rw.cs.sk_322 fi.tr.rw_543
+ 0x041c4aa4, 0x00003b37, 0x2b322711, 0x4a550408, // yo.id.fi_433 so.un.un_B00 gd.bs.vi_653 fi.rw.yo_443
+ 0x1c0a1a0c, 0x013552a0, 0x2b206eec, 0x200325ec, // tl.pt.id_543 ha.zu.en_322 hmn.sq.vi_644 eu.nl.sq_644
+ 0x13060da9, 0x3f0703a4, 0x280121a7, 0x09006822, // cs.de.et_544 nl.it.af_433 jw.en.sw_532 ig.pl.un_870
+ // [6530]
+ 0x050f0714, 0x133f21a4, 0x10001e04, 0x1c2921af, // it.lv.fr_666 jw.af.et_433 ms.lt.un_320 jw.sl.id_655
+ 0x3f642509, 0x033f6812, 0x04172911, 0x2b211aec, // eu.lg.af_444 ig.af.nl_654 sl.sr.fi_653 tl.jw.vi_644
+ 0x1b000f1b, 0x10312008, 0x55211f07, 0x3b005508, // lv.tr.un_770 sq.az.lt_443 cy.jw.rw_432 rw.so.un_430
+ 0x52136b09, 0x1c00190e, 0x25191caf, 0x2700070e, // ceb.et.ha_444 gl.id.un_550 id.gl.eu_655 it.gd.un_550
+ // [6540]
+ 0x292025a6, 0x28641e0c, 0x215213a4, 0x532a2007, // eu.sq.sl_521 ms.lg.sw_543 et.ha.jw_433 sq.mt.ht_432
+ 0x201b250d, 0x1c212b0d, 0x13072004, 0x1a522ba7, // eu.tr.sq_554 vi.jw.id_554 sq.it.et_332 vi.ha.tl_532
+ 0x4a1819ee, 0x080c0213, 0x20071205, 0x05201f07, // gl.ga.yo_422 da.sv.no_665 hu.it.sq_333 cy.sq.fr_432
+ 0x520f210c, 0x051b0313, 0x280f64a4, 0x101764ee, // jw.lv.ha_543 nl.tr.fr_665 lg.lv.sw_433 lg.sr.lt_422
+ // [6550]
+ 0x251b2a09, 0x05003f0b, 0x52284aee, 0x07002812, // mt.tr.eu_444 af.fr.un_520 yo.sw.ha_422 sw.it.un_640
+ 0x1b2025ac, 0x120a180b, 0x642831af, 0x190b28af, // eu.sq.tr_632 ga.pt.hu_542 az.sw.lg_655 sw.es.gl_655
+ 0x0a042704, 0x21002320, 0x102304a7, 0x28522055, // gd.fi.pt_332 ca.jw.un_850 fi.ca.lt_532 sq.ha.sw_442
+ 0x55000612, 0x32160405, 0x0e2820ac, 0x200812ad, // de.rw.un_640 fi.hr.bs_333 sq.sw.is_632 hu.no.sq_643
+ // [6560]
+ 0x251e64a0, 0x190564a0, 0x531b0512, 0x641b050d, // lg.ms.eu_322 lg.fr.gl_322 fr.tr.ht_654 fr.tr.lg_554
+ 0x5500030e, 0x313b280d, 0x020621ec, 0x0500190b, // nl.rw.un_550 sw.so.az_554 jw.de.da_644 gl.fr.un_520
+ 0x321620a4, 0x3216130c, 0x10002304, 0x043f0809, // sq.hr.bs_433 et.hr.bs_543 ca.lt.un_320 no.af.fi_444
+ 0x3b0602a9, 0x321731a0, 0x112005af, 0x120d0f05, // da.de.so_544 az.sr.bs_322 fr.sq.ro_655 lv.cs.hu_333
+ // [6570]
+ 0x121b3f55, 0x311b3f08, 0x1c1a21a4, 0x3100130e, // af.tr.hu_442 af.tr.az_443 jw.tl.id_433 et.az.un_550
+ 0x04253512, 0x1b313faf, 0x190b2d0c, 0x1b003f12, // zu.eu.fi_654 af.az.tr_655 sk.es.gl_543 af.tr.un_640
+ 0x551f6bac, 0x0b0d01a4, 0x20531c0c, 0x1e1c3bee, // ceb.cy.rw_632 en.cs.es_433 id.ht.sq_543 so.id.ms_422
+ 0x52000d21, 0x1e00350d, 0x1e041cee, 0x06111307, // cs.ha.un_860 zu.ms.un_540 id.fi.ms_422 et.ro.de_432
+ // [6580]
+ 0x55290dad, 0x092a3507, 0x0b0a1f05, 0x1100011a, // cs.sl.rw_643 zu.mt.pl_432 cy.pt.es_333 en.ro.un_760
+ 0x06551307, 0x1c1a1e04, 0x6b2104ee, 0x21000f0c, // et.rw.de_432 ms.tl.id_332 fi.jw.ceb_422 lv.jw.un_530
+ 0x172007ee, 0x210f6807, 0x13000f09, 0x3f02100c, // it.sq.sr_422 ig.lv.jw_432 lv.et.un_440 lt.da.af_543
+ 0x100817ec, 0x1b00010d, 0x01310cee, 0x20000c18, // sr.uk.be_644 en.tr.un_540 sv.az.en_422 sv.sq.un_740
+ // [6590]
+ 0x0900210d, 0x0b190aee, 0x3f0806ee, 0x250f2908, // jw.pl.un_540 pt.gl.es_422 de.no.af_422 sl.lv.eu_443
+ 0x2d000808, 0x250f08a4, 0x640452a7, 0x0f3202a9, // no.sk.un_430 no.lv.eu_433 ha.fi.lg_532 da.bs.lv_544
+ 0x686428af, 0x121906ad, 0x09291713, 0x050255af, // sw.lg.ig_655 de.gl.hu_643 sr.sl.pl_665 rw.da.fr_655
+ 0x2a13255a, 0x122523a4, 0x0b190a5a, 0x31292d09, // eu.et.mt_553 ca.eu.hu_433 pt.gl.es_553 sk.sl.az_444
+ // [65a0]
+ 0x1e003102, 0x080212a0, 0x312d2913, 0x2d0d1902, // az.ms.un_220 hu.da.no_322 sl.sk.az_665 gl.cs.sk_222
+ 0x200511a0, 0x100f020b, 0x02051305, 0x5500011a, // ro.fr.sq_322 da.lv.lt_542 et.fr.da_333 en.rw.un_760
+ 0x28182a13, 0x01130507, 0x29312d13, 0x316e2da6, // mt.ga.sw_665 fr.et.en_432 sk.az.sl_665 sk.hmn.az_521
+ 0x1306250c, 0x31292dac, 0x190a07ee, 0x31292d13, // eu.de.et_543 sk.sl.az_632 it.pt.gl_422 sk.sl.az_665
+ // [65b0]
+ 0x012a1fa0, 0x09645502, 0x0f6e290b, 0x124a5209, // cy.mt.en_322 rw.lg.pl_222 sl.hmn.lv_542 ha.yo.hu_444
+ 0x292a11a7, 0x061b3fa0, 0x0d0804a9, 0x130c28a4, // ro.mt.sl_532 af.tr.de_322 fi.no.cs_544 sw.sv.et_433
+ 0x0a192bb2, 0x1e005512, 0x1c0d0904, 0x2d005334, // vi.gl.pt_732 rw.ms.un_640 hi.ne.mr_332 ht.sk.un_A80
+ 0x0f2920ec, 0x2800022a, 0x02121bee, 0x1f00210b, // sq.sl.lv_644 da.sw.un_970 tr.hu.da_422 jw.cy.un_520
+ // [65c0]
+ 0x3100292b, 0x072b2aa0, 0x0f0c0904, 0x06100305, // sl.az.un_980 mt.vi.it_322 pl.sv.lv_332 nl.lt.de_333
+ 0x3509640c, 0x350655ad, 0x55131ea0, 0x551b28ad, // lg.pl.zu_543 rw.de.zu_643 ms.et.rw_322 sw.tr.rw_643
+ 0x090c1e0c, 0x234a1907, 0x35286402, 0x2a1a28a9, // ms.sv.pl_543 gl.yo.ca_432 lg.sw.zu_222 sw.tl.mt_544
+ 0x11040faf, 0x09110f0c, 0x2a001302, 0x200a1ba4, // lv.fi.ro_655 lv.ro.pl_543 et.mt.un_220 tr.pt.sq_433
+ // [65d0]
+ 0x1a20520d, 0x23060e11, 0x1b523113, 0x2520520c, // ha.sq.tl_554 is.de.ca_653 az.ha.tr_665 ha.sq.eu_543
+ 0x194a2d04, 0x211f35a0, 0x042552af, 0x52000d04, // sk.yo.gl_332 zu.cy.jw_322 ha.eu.fi_655 cs.ha.un_320
+ 0x1f4a52ec, 0x531f35ad, 0x0925210c, 0x182d0a07, // ha.yo.cy_644 zu.cy.ht_643 jw.eu.pl_543 pt.sk.ga_432
+ 0x4a3152ec, 0x19111308, 0x31233b0c, 0x1c3b4aad, // ha.az.yo_644 et.ro.gl_443 so.ca.az_543 yo.so.id_643
+ // [65e0]
+ 0x0d31045a, 0x03095305, 0x0613010c, 0x27000505, // fi.az.cs_553 ht.pl.nl_333 en.et.de_543 fr.gd.un_330
+ 0x286421ad, 0x04003121, 0x21045508, 0x29251bec, // jw.lg.sw_643 az.fi.un_860 rw.fi.jw_443 tr.eu.sl_644
+ 0x350e645a, 0x08021305, 0x3b2a31ac, 0x0c5511a7, // lg.is.zu_553 et.da.no_333 az.mt.so_632 ro.rw.sv_532
+ 0x131f200c, 0x53311a14, 0x0d35295a, 0x6e133113, // sq.cy.et_543 tl.az.ht_666 sl.zu.cs_553 az.et.hmn_665
+ // [65f0]
+ 0x31131e07, 0x5301190c, 0x05001107, 0x1e132aaf, // ms.et.az_432 gl.en.ht_543 ro.fr.un_420 mt.et.ms_655
+ 0x31521e0c, 0x53281b13, 0x0f001005, 0x3b316e08, // ms.ha.az_543 tr.sw.ht_665 lt.lv.un_330 hmn.az.so_443
+ 0x181135ad, 0x13213112, 0x0d001322, 0x0f1132a0, // zu.ro.ga_643 az.jw.et_654 bh.ne.un_870 bs.ro.lv_322
+ 0x1c041e07, 0x28160f04, 0x2d0d0709, 0x08003519, // ms.fi.id_432 lv.hr.sw_332 it.cs.sk_444 zu.no.un_750
+ // [6600]
+ 0x0a00190b, 0x0e003107, 0x080213a9, 0x320728a0, // gl.pt.un_520 az.is.un_420 et.da.no_544 sw.it.bs_322
+ 0x1b133113, 0x531b5555, 0x321708a0, 0x5200030c, // az.et.tr_665 rw.tr.ht_442 no.sr.bs_322 nl.ha.un_530
+ 0x312a1fad, 0x20551104, 0x0d1c13ec, 0x3f002312, // cy.mt.az_643 ro.rw.sq_332 bh.mr.ne_644 ca.af.un_640
+ 0x1f002807, 0x021a6bee, 0x091725a6, 0x4a1153a0, // sw.cy.un_420 ceb.tl.da_422 eu.sr.pl_521 ht.ro.yo_322
+ // [6610]
+ 0x2b184a02, 0x0d000813, 0x526425a4, 0x1c110a02, // yo.ga.vi_222 no.cs.un_650 eu.lg.ha_433 pt.ro.id_222
+ 0x3b535512, 0x0b0220a7, 0x061f23a4, 0x1a6807a7, // rw.ht.so_654 sq.da.es_532 ca.cy.de_433 it.ig.tl_532
+ 0x64286802, 0x55282514, 0x5200252a, 0x3f256412, // ig.sw.lg_222 eu.sw.rw_666 eu.ha.un_970 lg.eu.af_654
+ 0x03001320, 0x531b250e, 0x190b25ec, 0x134a6409, // et.nl.un_850 eu.tr.ht_555 eu.es.gl_644 lg.yo.et_444
+ // [6620]
+ 0x18232dac, 0x04200fad, 0x521b25ee, 0x3f001e21, // sk.ca.ga_632 lv.sq.fi_643 eu.tr.ha_422 ms.af.un_860
+ 0x1b0b53ee, 0x171f32ee, 0x211c3f12, 0x35004a02, // ht.es.tr_422 bs.cy.sr_422 af.id.jw_654 yo.zu.un_220
+ 0x3f25030d, 0x05000c04, 0x25003f1a, 0x350208ac, // nl.eu.af_554 sv.fr.un_320 af.eu.un_760 no.da.zu_632
+ 0x08211b08, 0x2d0d13af, 0x040e52a4, 0x080252a9, // tr.jw.no_443 et.cs.sk_655 ha.is.fi_433 ha.da.no_544
+ // [6630]
+ 0x1b020307, 0x1c064aa6, 0x0e181fad, 0x040655ec, // nl.da.tr_432 yo.de.id_521 cy.ga.is_643 rw.de.fi_644
+ 0x182752a9, 0x270e53ad, 0x6b6827ee, 0x013f64a6, // ha.gd.ga_544 ht.is.gd_643 gd.ig.ceb_422 lg.af.en_521
+ 0x1002040c, 0x18005209, 0x0e275307, 0x3f030a11, // fi.da.lt_543 ha.ga.un_440 ht.gd.is_432 pt.nl.af_653
+ 0x28686e0b, 0x55061e0c, 0x18130612, 0x080255a0, // hmn.ig.sw_542 ms.de.rw_543 de.et.ga_654 rw.da.no_322
+ // [6640]
+ 0x5500040d, 0x010f0302, 0x16092912, 0x682852a0, // fi.rw.un_540 nl.lv.en_222 sl.pl.hr_654 ha.sw.ig_322
+ 0x131011a0, 0x180c3fa4, 0x1a000423, 0x1e091005, // ro.lt.et_322 af.sv.ga_433 fi.tl.un_880 lt.pl.ms_333
+ 0x23002104, 0x3b1f640c, 0x076b1aa9, 0x17162da0, // jw.ca.un_320 lg.cy.so_543 tl.ceb.it_544 sk.hr.sr_322
+ 0x23050e0e, 0x1f1b0205, 0x1e0e1ca4, 0x08070209, // is.fr.ca_555 da.tr.cy_333 id.is.ms_433 da.it.no_444
+ // [6650]
+ 0x1e002907, 0x311b18af, 0x0f2a3fa4, 0x190b1eaf, // sl.ms.un_420 ga.tr.az_655 af.mt.lv_433 ms.es.gl_655
+ 0x310e080c, 0x251607a4, 0x32041cee, 0x04005212, // no.is.az_543 it.hr.eu_433 id.fi.bs_422 ha.fi.un_640
+ 0x320a3108, 0x2804550c, 0x3b3119ee, 0x213b1fec, // az.pt.bs_443 rw.fi.sw_543 gl.az.so_422 cy.so.jw_644
+ 0x06120207, 0x352507af, 0x23310ead, 0x4a002317, // da.hu.de_432 it.eu.zu_655 is.az.ca_643 ca.yo.un_730
+ // [6660]
+ 0x211b1eaf, 0x3f286804, 0x1b002007, 0x1f52130c, // ms.tr.jw_655 ig.sw.af_332 sq.tr.un_420 et.ha.cy_543
+ 0x0f002d0d, 0x0d2d290b, 0x1a536404, 0x1e001b13, // sk.lv.un_540 sl.sk.cs_542 lg.ht.tl_332 tr.ms.un_650
+ 0x040c13a0, 0x35250604, 0x1f0e0408, 0x172868a0, // et.sv.fi_322 de.eu.zu_332 fi.is.cy_443 ig.sw.sr_322
+ 0x6b1a04a4, 0x17001b07, 0x3b00271a, 0x04002304, // fi.tl.ceb_433 tr.sr.un_420 gd.so.un_760 ca.fi.un_320
+ // [6670]
+ 0x04271160, 0x3f0e040c, 0x041f0e11, 0x28354aa0, // ro.gd.fi_664 fi.is.af_543 is.cy.fi_653 yo.zu.sw_322
+ 0x6e53290c, 0x190a3b05, 0x2d290da0, 0x2d090d13, // sl.ht.hmn_543 so.pt.gl_333 cs.sl.sk_322 cs.pl.sk_665
+ 0x040503a4, 0x05040612, 0x3b4a355a, 0x2505040b, // nl.fr.fi_433 de.fi.fr_654 zu.yo.so_553 fi.fr.eu_542
+ 0x1113530c, 0x090d17ad, 0x1b354a07, 0x11001221, // ht.et.ro_543 sr.cs.pl_643 yo.zu.tr_432 hu.ro.un_860
+ // [6680]
+ 0x1c531eee, 0x1b023104, 0x12354a12, 0x641035ad, // ms.ht.id_422 az.da.tr_332 yo.zu.hu_654 zu.lt.lg_643
+ 0x0d131c13, 0x0b5255ee, 0x2d120d0c, 0x1e1c05ee, // mr.bh.ne_665 rw.ha.es_422 cs.hu.sk_543 fr.id.ms_422
+ 0x07000411, 0x0b0a12ee, 0x20002d0d, 0x2a002502, // ru.bg.un_630 hu.pt.es_422 sk.sq.un_540 eu.mt.un_220
+ 0x1125100c, 0x17004a05, 0x4a2011ad, 0x0400530e, // lt.eu.ro_543 yo.sr.un_330 ro.sq.yo_643 ht.fi.un_550
+ // [6690]
+ 0x0e006404, 0x2b004a07, 0x3b002504, 0x6b352aa9, // lg.is.un_320 yo.vi.un_420 eu.so.un_320 mt.zu.ceb_544
+ 0x1f090ea4, 0x0b091109, 0x041f4aa7, 0x09000408, // is.pl.cy_433 ro.pl.es_444 yo.cy.fi_532 fi.pl.un_430
+ 0x1a4a6bee, 0x6412280c, 0x213120ec, 0x1c0f52ee, // ceb.yo.tl_422 sw.hu.lg_543 sq.az.jw_644 ha.lv.id_422
+ 0x0f082007, 0x213b640e, 0x0c2a4a08, 0x6b005521, // sq.no.lv_432 lg.so.jw_555 yo.mt.sv_443 rw.ceb.un_860
+ // [66a0]
+ 0x3f3b6807, 0x4a641b5a, 0x133b0407, 0x3b3f13ad, // ig.so.af_432 tr.lg.yo_553 fi.so.et_432 et.af.so_643
+ 0x53040d14, 0x1a3110a9, 0x351a100d, 0x356b53a4, // cs.fi.ht_666 lt.az.tl_544 lt.tl.zu_554 ht.ceb.zu_433
+ 0x20212dee, 0x1a5510a9, 0x182a2007, 0x3252100c, // sk.jw.sq_422 lt.rw.tl_544 sq.mt.ga_432 lt.ha.bs_543
+ 0x531a2a05, 0x2b003202, 0x07000b0e, 0x2d530d12, // mt.tl.ht_333 bs.vi.un_220 es.it.un_550 cs.ht.sk_654
+ // [66b0]
+ 0x181f23ad, 0x311b100c, 0x2d0d53a9, 0x1f2109ad, // ca.cy.ga_643 lt.tr.az_543 ht.cs.sk_544 pl.jw.cy_643
+ 0x1b3110ec, 0x083525a0, 0x531b10a4, 0x130f1112, // lt.az.tr_644 eu.zu.no_322 lt.tr.ht_433 ro.lv.et_654
+ 0x64000804, 0x0800050b, 0x2a641f55, 0x1c004a19, // no.lg.un_320 fr.no.un_520 cy.lg.mt_442 yo.id.un_750
+ 0x2a003b19, 0x19002807, 0x1a0228a4, 0x0c3f080c, // so.mt.un_750 sw.gl.un_420 sw.da.tl_433 no.af.sv_543
+ // [66c0]
+ 0x28252aaf, 0x10011102, 0x1e101ca4, 0x0c060a0c, // mt.eu.sw_655 ro.en.lt_222 id.lt.ms_433 pt.de.sv_543
+ 0x2d000e07, 0x3500290b, 0x04280fa4, 0x211b52ee, // is.sk.un_420 sl.zu.un_520 lv.sw.fi_433 ha.tr.jw_422
+ 0x1a2b55a7, 0x21001f09, 0x351b640c, 0x050b1ea4, // rw.vi.tl_532 cy.jw.un_440 lg.tr.zu_543 ms.es.fr_433
+ 0x350f0e05, 0x131c09a9, 0x04001f29, 0x1c120fa4, // is.lv.zu_333 hi.mr.bh_544 cy.fi.un_960 lv.hu.id_433
+ // [66d0]
+ 0x162917a4, 0x53291ea0, 0x060a0e13, 0x21031e08, // sr.sl.hr_433 ms.sl.ht_322 is.pt.de_665 ms.nl.jw_443
+ 0x190a28ec, 0x55282102, 0x27002102, 0x2b641207, // sw.pt.gl_644 jw.sw.rw_222 jw.gd.un_220 hu.lg.vi_432
+ 0x283208ee, 0x0b002323, 0x212d1c12, 0x2a005508, // no.bs.sw_422 ca.es.un_880 id.sk.jw_654 rw.mt.un_430
+ 0x0a0d010c, 0x16001b08, 0x1c210714, 0x192d0aad, // en.cs.pt_543 tr.hr.un_430 it.jw.id_666 pt.sk.gl_643
+ // [66e0]
+ 0x1e281ca0, 0x122528a4, 0x2d0d1ca4, 0x55002912, // id.sw.ms_322 sw.eu.hu_433 id.cs.sk_433 sl.rw.un_640
+ 0x1b0d100c, 0x3f2d0fa7, 0x3217350c, 0x290e0caf, // lt.cs.tr_543 lv.sk.af_532 zu.sr.bs_543 sv.is.sl_655
+ 0x131c0908, 0x1b032dad, 0x29002721, 0x231b01a4, // hi.mr.bh_443 sk.nl.tr_643 gd.sl.un_860 en.tr.ca_433
+ 0x55641aa7, 0x3f041909, 0x251055ec, 0x1f643baf, // tl.lg.rw_532 gl.fi.af_444 rw.lt.eu_644 so.lg.cy_655
+ // [66f0]
+ 0x1b641108, 0x060c20b4, 0x6b00111b, 0x64551105, // ro.lg.tr_443 sq.sv.de_754 ro.ceb.un_770 ro.rw.lg_333
+ 0x521e21af, 0x1a00551a, 0x0b28535a, 0x640c1314, // jw.ms.ha_655 rw.tl.un_760 ht.sw.es_553 et.sv.lg_666
+ 0x2a313502, 0x6b681aa4, 0x21060e05, 0x02002a07, // zu.az.mt_222 tl.ig.ceb_433 is.de.jw_333 mt.da.un_420
+ 0x2d324aee, 0x28203ba4, 0x2300072c, 0x0b1219a0, // yo.bs.sk_422 so.sq.sw_433 it.ca.un_990 gl.hu.es_322
+ // [6700]
+ 0x12103f08, 0x28001212, 0x074a1608, 0x03001702, // af.lt.hu_443 hu.sw.un_640 hr.yo.it_443 sr.nl.un_220
+ 0x25112313, 0x64192a04, 0x18004a04, 0x351a5204, // ca.ro.eu_665 mt.gl.lg_332 yo.ga.un_320 ha.tl.zu_332
+ 0x1c2106a0, 0x23191113, 0x3b0e06a0, 0x2500180e, // de.jw.id_322 ro.gl.ca_665 de.is.so_322 ga.eu.un_550
+ 0x530e55a4, 0x0e000d04, 0x08021f02, 0x136b55ec, // rw.is.ht_433 cs.is.un_320 cy.da.no_222 rw.ceb.et_644
+ // [6710]
+ 0x52201ba4, 0x21190205, 0x52070f0c, 0x13001821, // tr.sq.ha_433 da.gl.jw_333 lv.it.ha_543 ga.et.un_860
+ 0x13110e05, 0x3b1e110c, 0x11200707, 0x686b3ba4, // is.ro.et_333 ro.ms.so_543 it.sq.ro_432 so.ceb.ig_433
+ 0x11022a0d, 0x0700351b, 0x53026eaf, 0x00000c37, // mt.da.ro_554 zu.it.un_770 hmn.da.ht_655 sv.un.un_B00
+ 0x10070aad, 0x110e55a4, 0x081353a0, 0x03020755, // mk.bg.be_643 rw.is.ro_433 ht.et.no_322 it.da.nl_442
+ // [6720]
+ 0x062507af, 0x3b4a200c, 0x2025060c, 0x31006e07, // it.eu.de_655 sq.yo.so_543 de.eu.sq_543 hmn.az.un_420
+ 0x4a002d21, 0x6455250c, 0x35006804, 0x2d055312, // sk.yo.un_860 eu.rw.lg_543 ig.zu.un_320 ht.fr.sk_654
+ 0x20292aee, 0x03000d08, 0x120e1112, 0x0f006b33, // mt.sl.sq_422 cs.nl.un_430 ro.is.hu_654 ceb.lv.un_A70
+ 0x323525ee, 0x2064070c, 0x1c551ea9, 0x1a6b070c, // eu.zu.bs_422 it.lg.sq_543 ms.rw.id_544 it.ceb.tl_543
+ // [6730]
+ 0x1e1c3514, 0x2b4a1cad, 0x55041313, 0x043b2010, // zu.id.ms_666 id.yo.vi_643 et.fi.rw_665 sq.so.fi_642
+ 0x2a00050c, 0x18001313, 0x190e0ba4, 0x0e001807, // fr.mt.un_530 et.ga.un_650 es.is.gl_433 ga.is.un_420
+ 0x11130404, 0x55526413, 0x0e1b1f55, 0x0c003b12, // fi.et.ro_332 lg.ha.rw_665 cy.tr.is_442 so.sv.un_640
+ 0x53001f02, 0x1a1b4aad, 0x04003202, 0x0d1c1313, // cy.ht.un_220 yo.tr.tl_643 bs.fi.un_220 bh.mr.ne_665
+ // [6740]
+ 0x2913170c, 0x190b18af, 0x070c2a13, 0x1a4a640c, // sr.et.sl_543 ga.es.gl_655 mt.sv.it_665 lg.yo.tl_543
+ 0x04130312, 0x29000405, 0x21001e18, 0x16003504, // nl.et.fi_654 fi.sl.un_330 ms.jw.un_740 zu.hr.un_320
+ 0x091f3faf, 0x27044a02, 0x03096412, 0x05072a13, // af.cy.pl_655 yo.fi.gd_222 lg.pl.nl_654 mt.it.fr_665
+ 0x190b1f09, 0x190b35ec, 0x0f001c0b, 0x1e1c4aa9, // cy.es.gl_444 zu.es.gl_644 id.lv.un_520 yo.id.ms_544
+ // [6750]
+ 0x105535a4, 0x1f311b09, 0x2a00032a, 0x52002829, // zu.rw.lt_433 tr.az.cy_444 nl.mt.un_970 sw.ha.un_960
+ 0x2910120c, 0x2a5507ad, 0x533135a7, 0x033f1ca4, // hu.lt.sl_543 it.rw.mt_643 zu.az.ht_532 id.af.nl_433
+ 0x19230b60, 0x55000e23, 0x031c3b12, 0x1c0219a7, // es.ca.gl_664 is.rw.un_880 so.id.nl_654 gl.da.id_532
+ 0x0c0e1bec, 0x0e00520c, 0x121809ec, 0x1e3f1c0c, // tr.is.sv_644 ha.is.un_530 pl.ga.hu_644 id.af.ms_543
+ // [6760]
+ 0x1e1c11a4, 0x041a1b0c, 0x21202ba0, 0x28684aac, // ro.id.ms_433 tr.tl.fi_543 vi.sq.jw_322 yo.ig.sw_632
+ 0x351a52af, 0x2a0731a9, 0x1c521fa0, 0x1c0e1eee, // ha.tl.zu_655 az.it.mt_544 cy.ha.id_322 ms.is.id_422
+ 0x1a0453ec, 0x10353fa0, 0x525504a4, 0x25063ba4, // ht.fi.tl_644 af.zu.lt_322 fi.rw.ha_433 so.de.eu_433
+ 0x3f2a230c, 0x033f1c12, 0x20022a5a, 0x530f5507, // ca.mt.af_543 id.af.nl_654 mt.da.sq_553 rw.lv.ht_432
+ // [6770]
+ 0x172935ac, 0x68001a0c, 0x070a1aec, 0x13552813, // zu.sl.sr_632 tl.ig.un_530 tl.pt.it_644 sw.rw.et_665
+ 0x2a0721a4, 0x646b1e0c, 0x05001217, 0x1f0a4aad, // jw.it.mt_433 ms.ceb.lg_543 hu.fr.un_730 yo.pt.cy_643
+ 0x3f000612, 0x20000605, 0x23000d19, 0x321b2dee, // de.af.un_640 de.sq.un_330 cs.ca.un_750 sk.tr.bs_422
+ 0x1f031ea4, 0x182a10a0, 0x1c1f1e0c, 0x52310ea7, // ms.nl.cy_433 lt.mt.ga_322 ms.cy.id_543 is.az.ha_532
+ // [6780]
+ 0x113b0309, 0x1655030c, 0x172d0d14, 0x315368a9, // nl.so.ro_444 nl.rw.hr_543 cs.sk.sr_666 ig.ht.az_544
+ 0x1b3b1305, 0x08022dee, 0x21031313, 0x02003b13, // et.so.tr_333 sk.da.no_422 et.nl.jw_665 so.da.un_650
+ 0x11530612, 0x0f001112, 0x10062aec, 0x6b092da0, // de.ht.ro_654 ro.lv.un_640 mt.de.lt_644 sk.pl.ceb_322
+ 0x3b001808, 0x1c641e07, 0x092d0fee, 0x55283bad, // ga.so.un_430 ms.lg.id_432 lv.sk.pl_422 so.sw.rw_643
+ // [6790]
+ 0x1f276ba7, 0x3b12100d, 0x0a1027a7, 0x29000522, // ceb.gd.cy_532 lt.hu.so_554 gd.lt.pt_532 fr.sl.un_870
+ 0x64004a13, 0x64284aad, 0x082927a0, 0x29053fa0, // yo.lg.un_650 yo.sw.lg_643 gd.sl.no_322 af.fr.sl_322
+ 0x13001804, 0x525531ee, 0x2a050fa4, 0x2a252104, // ga.et.un_320 az.rw.ha_422 lv.fr.mt_433 jw.eu.mt_332
+ 0x03001b19, 0x1e1c53af, 0x29086ea0, 0x18000510, // tr.nl.un_750 ht.id.ms_655 hmn.no.sl_322 fr.ga.un_620
+ // [67a0]
+ 0x19160bac, 0x2d0d2907, 0x35002507, 0x291f07ad, // es.hr.gl_632 sl.cs.sk_432 eu.zu.un_420 it.cy.sl_643
+ 0x3b1e2aad, 0x2d0d160e, 0x2d0d0ab6, 0x2d001e02, // mt.ms.so_643 hr.cs.sk_555 pt.cs.sk_766 ms.sk.un_220
+ 0x3b281bad, 0x6800290c, 0x0c020e04, 0x3b3155a4, // tr.sw.so_643 sl.ig.un_530 is.da.sv_332 rw.az.so_433
+ 0x00000137, 0x08000e18, 0x070a2360, 0x13090dee, // en.un.un_B00 is.no.un_740 ca.pt.it_664 ne.hi.bh_422
+ // [67b0]
+ 0x11005504, 0x08095305, 0x4a681f0c, 0x28040e08, // rw.ro.un_320 ht.pl.no_333 cy.ig.yo_543 is.fi.sw_443
+ 0x19001e05, 0x20004a12, 0x2a1207ad, 0x3229170c, // ms.gl.un_330 yo.sq.un_640 it.hu.mt_643 sr.sl.bs_543
+ 0x321709af, 0x23181aac, 0x52350555, 0x036806ee, // pl.sr.bs_655 tl.ga.ca_632 fr.zu.ha_442 de.ig.nl_422
+ 0x31532011, 0x02080e07, 0x20000208, 0x080264ee, // sq.ht.az_653 is.no.da_432 da.sq.un_430 lg.da.no_422
+ // [67c0]
+ 0x2d0d0cec, 0x00000637, 0x350455a0, 0x04005508, // sv.cs.sk_644 de.un.un_B00 rw.fi.zu_322 rw.fi.un_430
+ 0x073f08a9, 0x53201f0c, 0x08021005, 0x20350fad, // no.af.it_544 cy.sq.ht_543 lt.da.no_333 lv.zu.sq_643
+ 0x190b23ee, 0x0c030802, 0x10005508, 0x07110a05, // ca.es.gl_422 no.nl.sv_222 rw.lt.un_430 mk.ro.bg_333
+ 0x35210cad, 0x12110fa0, 0x08022b0c, 0x08002d02, // sv.jw.zu_643 lv.ro.hu_322 vi.da.no_543 sk.no.un_220
+ // [67d0]
+ 0x280813ee, 0x06271fa0, 0x16100d07, 0x1a001608, // et.no.sw_422 cy.gd.de_322 cs.lt.hr_432 hr.tl.un_430
+ 0x0c003512, 0x530b0aa6, 0x190b1fee, 0x20082da0, // zu.sv.un_640 pt.es.ht_521 cy.es.gl_422 sk.no.sq_322
+ 0x55000707, 0x181a4a12, 0x2a0753a0, 0x070b4a13, // it.rw.un_420 yo.tl.ga_654 ht.it.mt_322 yo.es.it_665
+ 0x6b55520c, 0x0f002505, 0x4a5529a9, 0x1a5228ec, // ha.rw.ceb_543 eu.lv.un_330 sl.rw.yo_544 sw.ha.tl_644
+ // [67e0]
+ 0x52005521, 0x022808ad, 0x52071c07, 0x522d03a9, // rw.ha.un_860 no.sw.da_643 id.it.ha_432 nl.sk.ha_544
+ 0x55005333, 0x0f523bad, 0x096b2007, 0x35286812, // ht.rw.un_A70 so.ha.lv_643 sq.ceb.pl_432 ig.sw.zu_654
+ 0x18000504, 0x274a110d, 0x1300050c, 0x1e051ca0, // fr.ga.un_320 ro.yo.gd_554 fr.et.un_530 id.fr.ms_322
+ 0x532b2dec, 0x64554aec, 0x4a1601ec, 0x19020ba4, // sk.vi.ht_644 yo.rw.lg_644 en.hr.yo_644 es.da.gl_433
+ // [67f0]
+ 0x1c101e0c, 0x2000230e, 0x2968100c, 0x1e1c2160, // ms.lt.id_543 ca.sq.un_550 lt.ig.sl_543 jw.id.ms_664
+ 0x17000e05, 0x0a2a2104, 0x1e001312, 0x1c0e1e13, // is.sr.un_330 jw.mt.pt_332 et.ms.un_640 ms.is.id_665
+ 0x110b68ad, 0x2d2b0d13, 0x532052a6, 0x06002d0e, // ig.es.ro_643 cs.vi.sk_665 ha.sq.ht_521 sk.de.un_550
+ 0x033f0e11, 0x551b25ec, 0x131b520d, 0x29312504, // is.af.nl_653 eu.tr.rw_644 ha.tr.et_554 eu.az.sl_332
+
+ // [6800]
+ 0x192a070c, 0x6b3518a6, 0x04200e12, 0x01532a09, // it.mt.gl_543 ga.zu.ceb_521 is.sq.fi_654 mt.ht.en_444
+ 0x131b2907, 0x0f006419, 0x190a0da4, 0x0a0b1907, // sl.tr.et_432 lg.lv.un_750 cs.pt.gl_433 gl.es.pt_432
+ 0x081a02ee, 0x07130404, 0x351f5308, 0x3b1e1cec, // da.tl.no_422 fi.et.it_332 ht.cy.zu_443 id.ms.so_644
+ 0x12533ba4, 0x19180b12, 0x4a641b08, 0x1b00070e, // so.ht.hu_433 es.ga.gl_654 tr.lg.yo_443 it.tr.un_550
+ // [6810]
+ 0x0f050208, 0x1c004a02, 0x1e002112, 0x551c20ad, // da.fr.lv_443 yo.id.un_220 jw.ms.un_640 sq.id.rw_643
+ 0x3b1f0712, 0x2a1220ee, 0x353b100c, 0x06002804, // it.cy.so_654 sq.hu.mt_422 lt.so.zu_543 sw.de.un_320
+ 0x0a53010c, 0x201105a9, 0x16290f0c, 0x532a3fad, // en.ht.pt_543 fr.ro.sq_544 lv.sl.hr_543 af.mt.ht_643
+ 0x270608af, 0x321711a0, 0x551701a4, 0x20001b0e, // no.de.gd_655 ro.sr.bs_322 en.sr.rw_433 tr.sq.un_550
+ // [6820]
+ 0x04006b02, 0x202129a9, 0x53206404, 0x051c04a4, // ceb.fi.un_220 sl.jw.sq_544 lg.sq.ht_332 fi.id.fr_433
+ 0x20102514, 0x25041a0c, 0x10000e0e, 0x0e000604, // eu.lt.sq_666 tl.fi.eu_543 is.lt.un_550 de.is.un_320
+ 0x03213faf, 0x1110530d, 0x09021f12, 0x19051113, // af.jw.nl_655 ht.lt.ro_554 cy.da.pl_654 ro.fr.gl_665
+ 0x27002d05, 0x01040512, 0x201f3fec, 0x253104ac, // sk.gd.un_330 fr.fi.en_654 af.cy.sq_644 fi.az.eu_632
+ // [6830]
+ 0x10252a07, 0x0e001f02, 0x080225af, 0x0c17120c, // mt.eu.lt_432 cy.is.un_220 eu.da.no_655 hu.sr.sv_543
+ 0x0729120c, 0x1000280c, 0x171168a4, 0x0d000c04, // hu.sl.it_543 sw.lt.un_530 ig.ro.sr_433 sv.cs.un_320
+ 0x133512ad, 0x11001b11, 0x020c13ac, 0x13204a04, // hu.zu.et_643 tr.ro.un_630 et.sv.da_632 yo.sq.et_332
+ 0x320d1107, 0x080235ee, 0x12312813, 0x1e1c18a9, // ro.cs.bs_432 zu.da.no_422 sw.az.hu_665 ga.id.ms_544
+ // [6840]
+ 0x3211200c, 0x111b17a0, 0x172a2bee, 0x0c000521, // sq.ro.bs_543 sr.tr.ro_322 vi.mt.sr_422 fr.sv.un_860
+ 0x170810ec, 0x0b003b08, 0x1f275307, 0x28200b02, // be.uk.sr_644 so.es.un_430 ht.gd.cy_432 es.sq.sw_222
+ 0x286409a4, 0x020c0811, 0x291611ad, 0x16003f0c, // pl.lg.sw_433 no.sv.da_653 ro.hr.sl_643 af.hr.un_530
+ 0x16023207, 0x11000c29, 0x100f2a60, 0x0209080c, // bs.da.hr_432 sv.ro.un_960 mt.lv.lt_664 no.pl.da_543
+ // [6850]
+ 0x29205309, 0x0c230aa9, 0x25202814, 0x0a2319ad, // ht.sq.sl_444 pt.ca.sv_544 sw.sq.eu_666 gl.ca.pt_643
+ 0x09101fad, 0x1b102aad, 0x211c640c, 0x29251b07, // cy.lt.pl_643 mt.lt.tr_643 lg.id.jw_543 tr.eu.sl_432
+ 0x13001014, 0x190a5202, 0x02002d21, 0x35520f0c, // lt.et.un_660 ha.pt.gl_222 sk.da.un_860 lv.ha.zu_543
+ 0x130f280c, 0x270b2807, 0x230619a0, 0x2d0d18ba, // sw.lv.et_543 sw.es.gd_432 gl.de.ca_322 ga.cs.sk_843
+ // [6860]
+ 0x0468030c, 0x323564ac, 0x684a3505, 0x060701ad, // nl.ig.fi_543 lg.zu.bs_632 zu.yo.ig_333 en.it.de_643
+ 0x6e2d2b0c, 0x64000705, 0x19130aec, 0x3f081c0c, // vi.sk.hmn_543 it.lg.un_330 pt.et.gl_644 id.no.af_543
+ 0x0100062c, 0x08021a0e, 0x026e27a0, 0x00001f42, // de.en.un_990 tl.da.no_555 gd.hmn.da_322 cy.un.un_C00
+ 0x044a350c, 0x09001702, 0x06000908, 0x25200612, // zu.yo.fi_543 sr.pl.un_220 pl.de.un_430 de.sq.eu_654
+ // [6870]
+ 0x070c2d07, 0x080418ad, 0x18000607, 0x17161ba0, // sk.sv.it_432 ga.fi.no_643 de.ga.un_420 tr.hr.sr_322
+ 0x070c3bee, 0x1a1935ee, 0x4a002305, 0x0f203209, // so.sv.it_422 zu.gl.tl_422 ca.yo.un_330 bs.sq.lv_444
+ 0x1b5253a9, 0x190c230c, 0x31006b29, 0x25230611, // ht.ha.tr_544 ca.sv.gl_543 ceb.az.un_960 de.ca.eu_653
+ 0x0c0823ec, 0x4a000702, 0x3b1b4aa7, 0x081e0c11, // ca.no.sv_644 it.yo.un_220 yo.tr.so_532 sv.ms.no_653
+ // [6880]
+ 0x070435ee, 0x1a003f08, 0x28351e11, 0x0d311fa4, // zu.fi.it_422 af.tl.un_430 ms.zu.sw_653 cy.az.cs_433
+ 0x3b2004a0, 0x1b071ca0, 0x110529ee, 0x070f01a4, // fi.sq.so_322 id.it.tr_322 sl.fr.ro_422 en.lv.it_433
+ 0x0a122dee, 0x204a2308, 0x0806020d, 0x6e2a1faf, // sk.hu.pt_422 ca.yo.sq_443 da.de.no_554 cy.mt.hmn_655
+ 0x1f132a0c, 0x3f00071a, 0x07000208, 0x13001121, // mt.et.cy_543 it.af.un_760 da.it.un_430 ro.et.un_860
+ // [6890]
+ 0x0e001e0d, 0x05005202, 0x230b0a05, 0x1a6e5204, // ms.is.un_540 ha.fr.un_220 pt.es.ca_333 ha.hmn.tl_332
+ 0x050607ec, 0x0a252102, 0x2b1f200b, 0x2a12530c, // it.de.fr_644 jw.eu.pt_222 sq.cy.vi_542 ht.hu.mt_543
+ 0x6b1e20ee, 0x3b001b13, 0x033f29a4, 0x31531208, // sq.ms.ceb_422 tr.so.un_650 sl.af.nl_433 hu.ht.az_443
+ 0x3f4a530c, 0x25001f18, 0x08002313, 0x08024aa0, // ht.yo.af_543 cy.eu.un_740 ca.no.un_650 yo.da.no_322
+ // [68a0]
+ 0x1b3b6ba0, 0x3f001702, 0x11685307, 0x08050cee, // ceb.so.tr_322 sr.af.un_220 ht.ig.ro_432 sv.fr.no_422
+ 0x3b1b6bad, 0x641f1baf, 0x1f19115a, 0x0c1f520c, // ceb.tr.so_643 tr.cy.lg_655 ro.gl.cy_553 ha.cy.sv_543
+ 0x1f2d53a7, 0x1e351c0c, 0x0d532d0c, 0x1e000205, // ht.sk.cy_532 id.zu.ms_543 sk.ht.cs_543 da.ms.un_330
+ 0x10180c07, 0x2b31530c, 0x1c00010d, 0x0d1b3f0c, // sv.ga.lt_432 ht.az.vi_543 en.id.un_540 af.tr.cs_543
+ // [68b0]
+ 0x03124aee, 0x12051ba4, 0x2a070612, 0x0b2d1955, // yo.hu.nl_422 tr.fr.hu_433 de.it.mt_654 gl.sk.es_442
+ 0x190b3502, 0x290525ad, 0x1c061eee, 0x1900250d, // zu.es.gl_222 eu.fr.sl_643 ms.de.id_422 eu.gl.un_540
+ 0x231105a7, 0x12250560, 0x0b191811, 0x210535ee, // fr.ro.ca_532 fr.eu.hu_664 ga.gl.es_653 zu.fr.jw_422
+ 0x20051b55, 0x6400200d, 0x1105255a, 0x25001707, // tr.fr.sq_442 sq.lg.un_540 eu.fr.ro_553 sr.eu.un_420
+ // [68c0]
+ 0x6b000304, 0x162909a9, 0x3f030daf, 0x21070fec, // nl.ceb.un_320 pl.sl.hr_544 cs.nl.af_655 lv.it.jw_644
+ 0x1620100c, 0x523b0bee, 0x1a4a6bec, 0x0d091c09, // lt.sq.hr_543 es.so.ha_422 ceb.yo.tl_644 mr.hi.ne_444
+ 0x0f531014, 0x3f031a0c, 0x1a104aad, 0x051127a0, // lt.ht.lv_666 tl.nl.af_543 yo.lt.tl_643 gd.ro.fr_322
+ 0x3229160c, 0x08000604, 0x07181108, 0x072d1fa0, // hr.sl.bs_543 de.no.un_320 ro.ga.it_443 cy.sk.it_322
+ // [68d0]
+ 0x126403a0, 0x3b1f1aa7, 0x1a0a6b09, 0x081302ec, // nl.lg.hu_322 tl.cy.so_532 ceb.pt.tl_444 da.et.no_644
+ 0x1e1c0ba0, 0x27000408, 0x1a533512, 0x13001019, // es.id.ms_322 fi.gd.un_430 zu.ht.tl_654 lt.et.un_750
+ 0x09030fa0, 0x10521a0d, 0x1f52100d, 0x06056e02, // lv.nl.pl_322 tl.ha.lt_554 lt.ha.cy_554 hmn.fr.de_222
+ 0x136e1aad, 0x0c1e0211, 0x12111007, 0x21351fa0, // tl.hmn.et_643 da.ms.sv_653 lt.ro.hu_432 cy.zu.jw_322
+ // [68e0]
+ 0x190a1eee, 0x13013ba7, 0x3b3f6405, 0x070d0e07, // ms.pt.gl_422 so.en.et_532 lg.af.so_333 is.cs.it_432
+ 0x29170f0c, 0x274a6808, 0x0a001809, 0x3b006e20, // lv.sr.sl_543 ig.yo.gd_443 ga.pt.un_440 hmn.so.un_850
+ 0x2b1a52a0, 0x0f130460, 0x0802060e, 0x522a68af, // ha.tl.vi_322 fi.et.lv_664 de.da.no_555 ig.mt.ha_655
+ 0x3f080b0c, 0x1a0328a9, 0x203b31ad, 0x0b0208ac, // es.no.af_543 sw.nl.tl_544 az.so.sq_643 no.da.es_632
+ // [68f0]
+ 0x062b11ec, 0x090d1fad, 0x251811af, 0x32201b08, // ro.vi.de_644 cy.cs.pl_643 ro.ga.eu_655 tr.sq.bs_443
+ 0x071011ee, 0x6e00190d, 0x010c230c, 0x52256b12, // ro.be.bg_422 gl.hmn.un_540 ca.sv.en_543 ceb.eu.ha_654
+ 0x4a6b2b0d, 0x023f060c, 0x311b0505, 0x554a2812, // vi.ceb.yo_554 de.af.da_543 fr.tr.az_333 sw.yo.rw_654
+ 0x52002a36, 0x012706a0, 0x0c1f1c02, 0x6b312155, // mt.ha.un_AA0 de.gd.en_322 id.cy.sv_222 jw.az.ceb_442
+ // [6900]
+ 0x1304310c, 0x00000e37, 0x03251bee, 0x0a23210d, // az.fi.et_543 is.un.un_B00 tr.eu.nl_422 jw.ca.pt_554
+ 0x211b5507, 0x1a3521ee, 0x0d002107, 0x04000113, // rw.tr.jw_432 jw.zu.tl_422 jw.cs.un_420 en.fi.un_650
+ 0x02061fee, 0x072501ad, 0x1f016ba0, 0x552125a4, // cy.de.da_422 en.eu.it_643 ceb.en.cy_322 eu.jw.rw_433
+ 0x6806250c, 0x290d6e0c, 0x3b00250d, 0x21255213, // eu.de.ig_543 hmn.cs.sl_543 eu.so.un_540 ha.eu.jw_665
+ // [6910]
+ 0x1e1c0f14, 0x10003f0c, 0x18000c07, 0x126413ee, // lv.id.ms_666 af.lt.un_530 sv.ga.un_420 et.lg.hu_422
+ 0x083b0204, 0x353b2d07, 0x1b00190d, 0x52040360, // da.so.no_332 sk.so.zu_432 gl.tr.un_540 nl.fi.ha_664
+ 0x072a1e11, 0x523118a4, 0x20551008, 0x55281b13, // ms.mt.it_653 ga.az.ha_433 lt.rw.sq_443 tr.sw.rw_665
+ 0x256b2da4, 0x52001b1a, 0x1c171ea0, 0x102a550d, // sk.ceb.eu_433 tr.ha.un_760 ms.sr.id_322 rw.mt.lt_554
+ // [6920]
+ 0x321628a4, 0x01000b02, 0x04526405, 0x53212bad, // sw.hr.bs_433 es.en.un_220 lg.ha.fi_333 vi.jw.ht_643
+ 0x2932350c, 0x2a0964af, 0x6e2b2807, 0x0b2307a0, // zu.bs.sl_543 lg.pl.mt_655 sw.vi.hmn_432 it.ca.es_322
+ 0x1e1c2aec, 0x213b1b0c, 0x35281ba4, 0x6b01180e, // mt.id.ms_644 tr.so.jw_543 tr.sw.zu_433 ga.en.ceb_555
+ 0x130e0407, 0x1b1025a0, 0x31111b07, 0x6b0a55a0, // fi.is.et_432 eu.lt.tr_322 tr.ro.az_432 rw.pt.ceb_322
+ // [6930]
+ 0x311b5212, 0x023f20ac, 0x1213170c, 0x522b11a0, // ha.tr.az_654 sq.af.da_632 sr.et.hu_543 ro.vi.ha_322
+ 0x070527a4, 0x201216ee, 0x1b002d19, 0x291b2d0c, // gd.fr.it_433 hr.hu.sq_422 sk.tr.un_750 sk.tr.sl_543
+ 0x35552a60, 0x2d200d12, 0x0a0807ee, 0x1100102b, // mt.rw.zu_664 cs.sq.sk_654 bg.uk.mk_422 be.ro.un_980
+ 0x2d291fa4, 0x53201b0c, 0x2900171a, 0x1b003f04, // cy.sl.sk_433 tr.sq.ht_543 sr.sl.un_760 af.tr.un_320
+ // [6940]
+ 0x3100210c, 0x162953a7, 0x1a3b6413, 0x190b05ec, // jw.az.un_530 ht.sl.hr_532 lg.so.tl_665 fr.es.gl_644
+ 0x102901a0, 0x180a32a0, 0x08022d0c, 0x03006818, // en.sl.lt_322 bs.pt.ga_322 sk.da.no_543 ig.nl.un_740
+ 0x13684a04, 0x181912a9, 0x1e1a1c07, 0x100364ad, // yo.ig.et_332 hu.gl.ga_544 id.tl.ms_432 lg.nl.lt_643
+ 0x0d1918a4, 0x2823530d, 0x271820af, 0x206e6ba0, // ga.gl.cs_433 ht.ca.sw_554 sq.ga.gd_655 ceb.hmn.sq_322
+ // [6950]
+ 0x0d122d04, 0x0d1912ec, 0x3f02010c, 0x190b1209, // sk.hu.cs_332 hu.gl.cs_644 en.da.af_543 hu.es.gl_444
+ 0x190b2da4, 0x194a0b07, 0x12184a13, 0x13001a34, // sk.es.gl_433 es.yo.gl_432 yo.ga.hu_665 tl.et.un_A80
+ 0x4a2a0d12, 0x1f006e07, 0x255521af, 0x19270aad, // cs.mt.yo_654 hmn.cy.un_420 jw.rw.eu_655 pt.gd.gl_643
+ 0x0a0c27ad, 0x020601af, 0x234a2bad, 0x31553555, // gd.sv.pt_643 en.de.da_655 vi.yo.ca_643 zu.rw.az_442
+ // [6960]
+ 0x2d002a02, 0x20001c02, 0x04001f18, 0x6b2818ad, // mt.sk.un_220 id.sq.un_220 cy.fi.un_740 ga.sw.ceb_643
+ 0x074a2712, 0x0500520c, 0x03000104, 0x02086ea7, // gd.yo.it_654 ha.fr.un_530 en.nl.un_320 hmn.no.da_532
+ 0x1b005211, 0x0c00270e, 0x2b274aec, 0x2a002d12, // ha.tr.un_630 gd.sv.un_550 yo.gd.vi_644 sk.mt.un_640
+ 0x130e0f04, 0x10005202, 0x6b251a13, 0x01000807, // lv.is.et_332 ha.lt.un_220 tl.eu.ceb_665 no.en.un_420
+ // [6970]
+ 0x3b250ca4, 0x06020eee, 0x64000e14, 0x1b000302, // sv.eu.so_433 is.da.de_422 is.lg.un_660 nl.tr.un_220
+ 0x53000d22, 0x20080caf, 0x1b0c08af, 0x535520a0, // cs.ht.un_870 sv.no.sq_655 no.sv.tr_655 sq.rw.ht_322
+ 0x64066ba7, 0x322d6ea0, 0x53000d20, 0x061304a7, // ceb.de.lg_532 hmn.sk.bs_322 cs.ht.un_850 fi.et.de_532
+ 0x351b04af, 0x550635a0, 0x25554a09, 0x201f6807, // fi.tr.zu_655 zu.de.rw_322 yo.rw.eu_444 ig.cy.sq_432
+ // [6980]
+ 0x25104aee, 0x06296e13, 0x28001b1b, 0x170a08ec, // yo.lt.eu_422 hmn.sl.de_665 tr.sw.un_770 uk.mk.sr_644
+ 0x5306640d, 0x1e1c28a9, 0x12184aec, 0x231128a4, // lg.de.ht_554 sw.id.ms_544 yo.ga.hu_644 sw.ro.ca_433
+ 0x531a0111, 0x19350a60, 0x3200110c, 0x0a191155, // en.tl.ht_653 pt.zu.gl_664 ro.bs.un_530 ro.gl.pt_442
+ 0x1c1e21a7, 0x13253fa4, 0x1b000913, 0x106828ad, // jw.ms.id_532 af.eu.et_433 pl.tr.un_650 sw.ig.lt_643
+ // [6990]
+ 0x32292dad, 0x32252da4, 0x0e0302a4, 0x3216110c, // sk.sl.bs_643 sk.eu.bs_433 da.nl.is_433 ro.hr.bs_543
+ 0x23006e21, 0x1a003f14, 0x050420ee, 0x28552a05, // hmn.ca.un_860 af.tl.un_660 sq.fi.fr_422 mt.rw.sw_333
+ 0x3b023faf, 0x1e0d1cad, 0x2b001219, 0x08113f60, // af.da.so_655 id.cs.ms_643 hu.vi.un_750 af.ro.no_664
+ 0x3f000221, 0x3f021107, 0x17001612, 0x096b28a4, // da.af.un_860 ro.da.af_432 hr.sr.un_640 sw.ceb.pl_433
+ // [69a0]
+ 0x0f271005, 0x1a001011, 0x1c1309ac, 0x4a001c0e, // lt.gd.lv_333 lt.tl.un_630 hi.bh.mr_632 id.yo.un_550
+ 0x35005312, 0x0e0c130c, 0x0f05100c, 0x105313af, // ht.zu.un_640 et.sv.is_543 lt.fr.lv_543 et.ht.lt_655
+ 0x06530e0c, 0x130406a0, 0x1c1e2108, 0x08021e09, // is.ht.de_543 de.fi.et_322 jw.ms.id_443 ms.da.no_444
+ 0x08021cee, 0x2b074a12, 0x2a350707, 0x072b4a12, // id.da.no_422 yo.it.vi_654 it.zu.mt_432 yo.vi.it_654
+ // [69b0]
+ 0x033564af, 0x04000604, 0x6b09350d, 0x4a1c2512, // lg.zu.nl_655 de.fi.un_320 zu.pl.ceb_554 eu.id.yo_654
+ 0x13020ea4, 0x28136b09, 0x162d21a9, 0x07640507, // is.da.et_433 ceb.et.sw_444 jw.sk.hr_544 fr.lg.it_432
+ 0x050b01af, 0x0b0a2109, 0x110607ec, 0x0e3f2aee, // en.es.fr_655 jw.pt.es_444 it.de.ro_644 mt.af.is_422
+ 0x202a32a4, 0x072155a9, 0x1c1e5508, 0x2d0d1805, // bs.mt.sq_433 rw.jw.it_544 rw.ms.id_443 ga.cs.sk_333
+ // [69c0]
+ 0x2b68280d, 0x32171ca4, 0x0c031304, 0x0f4a1013, // sw.ig.vi_554 id.sr.bs_433 et.nl.sv_332 lt.yo.lv_665
+ 0x0d131c11, 0x1b001308, 0x35552804, 0x2a071e12, // mr.bh.ne_653 et.tr.un_430 sw.rw.zu_332 ms.it.mt_654
+ 0x68002512, 0x0f4a2707, 0x1c001804, 0x1e2104a0, // eu.ig.un_640 gd.yo.lv_432 ga.id.un_320 fi.jw.ms_322
+ 0x1f001307, 0x03002907, 0x35010fa4, 0x53000f19, // et.cy.un_420 sl.nl.un_420 lv.en.zu_433 lv.ht.un_750
+ // [69d0]
+ 0x32173fa0, 0x31001302, 0x100b1ca4, 0x0a07680c, // af.sr.bs_322 et.az.un_220 id.es.lt_433 ig.it.pt_543
+ 0x285268ad, 0x23090ba0, 0x29100f04, 0x1b000d0d, // ig.ha.sw_643 es.pl.ca_322 lv.lt.sl_332 cs.tr.un_540
+ 0x170b1c07, 0x3f111307, 0x6e3153a7, 0x0b000c05, // id.es.sr_432 et.ro.af_432 ht.az.hmn_532 sv.es.un_330
+ 0x4a001205, 0x020c11a4, 0x20523b07, 0x1f00211a, // hu.yo.un_330 ro.sv.da_433 so.ha.sq_432 jw.cy.un_760
+ // [69e0]
+ 0x08021ea0, 0x211a68a4, 0x25190c07, 0x01643f0c, // ms.da.no_322 ig.tl.jw_433 sv.gl.eu_432 af.lg.en_543
+ 0x3f131e0c, 0x28006805, 0x01060509, 0x23071ea4, // ms.et.af_543 ig.sw.un_330 fr.de.en_444 ms.it.ca_433
+ 0x041307af, 0x12190508, 0x1e1c21ac, 0x04523514, // it.et.fi_655 fr.gl.hu_443 jw.id.ms_632 zu.ha.fi_666
+ 0x0c280f07, 0x1c1e525a, 0x030611a9, 0x096b53a7, // lv.sw.sv_432 ha.ms.id_553 ro.de.nl_544 ht.ceb.pl_532
+ // [69f0]
+ 0x1e1c29a4, 0x11061fa7, 0x11000304, 0x4a00350c, // sl.id.ms_433 cy.de.ro_532 nl.ro.un_320 zu.yo.un_530
+ 0x033f2004, 0x3b2028a0, 0x200613ad, 0x1e1c11a9, // sq.af.nl_332 sw.sq.so_322 et.de.sq_643 ro.id.ms_544
+ 0x1f002d08, 0x3f00040c, 0x19210ba4, 0x2a002702, // sk.cy.un_430 fi.af.un_530 es.jw.gl_433 gd.mt.un_220
+ 0x1c1b27ee, 0x0b272307, 0x25313b08, 0x0e0c4a5a, // gd.tr.id_422 ca.gd.es_432 so.az.eu_443 yo.sv.is_553
+ // [6a00]
+ 0x1c251e12, 0x4a130e11, 0x0205080c, 0x29111e0b, // ms.eu.id_654 is.et.yo_653 no.fr.da_543 ms.ro.sl_542
+ 0x0d292da4, 0x130523a9, 0x0c0f230c, 0x12135304, // sk.sl.cs_433 ca.fr.et_544 ca.lv.sv_543 ht.et.hu_332
+ 0x27183f14, 0x103b0f12, 0x12532304, 0x0807175a, // af.ga.gd_666 lv.so.lt_654 ca.ht.hu_332 sr.bg.uk_553
+ 0x0e033f13, 0x2b0125ee, 0x292311ac, 0x5331130c, // af.nl.is_665 eu.en.vi_422 ro.ca.sl_632 et.az.ht_543
+ // [6a10]
+ 0x2d0d0a14, 0x5313020c, 0x533b130c, 0x080209ad, // pt.cs.sk_666 da.et.ht_543 et.so.ht_543 pl.da.no_643
+ 0x3f0613ad, 0x05533fee, 0x07000e04, 0x016b1305, // et.de.af_643 af.ht.fr_422 is.it.un_320 et.ceb.en_333
+ 0x29002a2b, 0x0a2027a0, 0x4a006b0c, 0x3b133108, // mt.sl.un_980 gd.sq.pt_322 ceb.yo.un_530 az.et.so_443
+ 0x0e0c4aa0, 0x1e6e3ba0, 0x02002907, 0x13043b02, // yo.sv.is_322 so.hmn.ms_322 sl.da.un_420 so.fi.et_222
+ // [6a20]
+ 0x32191f08, 0x19231108, 0x6b113bec, 0x0f161713, // cy.gl.bs_443 ro.ca.gl_443 so.ro.ceb_644 sr.hr.lv_665
+ 0x29006404, 0x55286402, 0x0b001a04, 0x1c2d1ea0, // lg.sl.un_320 lg.sw.rw_222 tl.es.un_320 ms.sk.id_322
+ 0x1a1e10a4, 0x0e1c06a9, 0x103f0fac, 0x1e1c1fa4, // lt.ms.tl_433 de.id.is_544 lv.af.lt_632 cy.id.ms_433
+ 0x6e003b19, 0x13551205, 0x0f003114, 0x0b003b05, // so.hmn.un_750 hu.rw.et_333 az.lv.un_660 so.es.un_330
+ // [6a30]
+ 0x21641a0c, 0x061353a6, 0x2a0619a0, 0x13002319, // tl.lg.jw_543 ht.et.de_521 gl.de.mt_322 ca.et.un_750
+ 0x1c3564a9, 0x21291855, 0x6b0110a0, 0x032806ad, // lg.zu.id_544 ga.sl.jw_442 lt.en.ceb_322 de.sw.nl_643
+ 0x192a0b07, 0x0b0a32a0, 0x321e17ee, 0x16001b04, // es.mt.gl_432 bs.pt.es_322 sr.ms.bs_422 tr.hr.un_320
+ 0x02530807, 0x310f10a0, 0x28680e05, 0x531225a7, // no.ht.da_432 lt.lv.az_322 is.ig.sw_333 eu.hu.ht_532
+ // [6a40]
+ 0x523f3b07, 0x133b0b02, 0x35000920, 0x4a2825ad, // so.af.ha_432 es.so.et_222 pl.zu.un_850 eu.sw.yo_643
+ 0x09524aee, 0x07001b21, 0x09252da9, 0x16354aa0, // yo.ha.pl_422 tr.it.un_860 sk.eu.pl_544 yo.zu.hr_322
+ 0x216b1c5a, 0x28005312, 0x31531b0c, 0x183b095a, // id.ceb.jw_553 ht.sw.un_640 tr.ht.az_543 pl.so.ga_553
+ 0x2d0d250c, 0x163b35a0, 0x1a6e28ec, 0x311b0405, // eu.cs.sk_543 zu.so.hr_322 sw.hmn.tl_644 fi.tr.az_333
+ // [6a50]
+ 0x552a3512, 0x1e160ea4, 0x02280807, 0x32006b20, // zu.mt.rw_654 is.hr.ms_433 no.sw.da_432 ceb.bs.un_850
+ 0x12040f0d, 0x0e10190c, 0x0a001817, 0x21000c23, // lv.fi.hu_554 gl.lt.is_543 ga.pt.un_730 sv.jw.un_880
+ 0x10230eaf, 0x2d0d5502, 0x23520ca4, 0x1b121f0c, // is.ca.lt_655 rw.cs.sk_222 sv.ha.ca_433 cy.hu.tr_543
+ 0x03291313, 0x071306ee, 0x646855a9, 0x020d08ee, // et.sl.nl_665 de.et.it_422 rw.ig.lg_544 no.cs.da_422
+ // [6a60]
+ 0x35556bec, 0x100f1fac, 0x20006818, 0x07002d04, // ceb.rw.zu_644 cy.lv.lt_632 ig.sq.un_740 sk.it.un_320
+ 0x2112230c, 0x311202ee, 0x645553ec, 0x050207a0, // ca.hu.jw_543 da.hu.az_422 ht.rw.lg_644 it.da.fr_322
+ 0x090f01a4, 0x0720080c, 0x31182aa7, 0x18121baf, // en.lv.pl_433 no.sq.it_543 mt.ga.az_532 tr.hu.ga_655
+ 0x28000619, 0x192311a7, 0x53001012, 0x230f0b13, // de.sw.un_750 ro.ca.gl_532 lt.ht.un_640 es.lv.ca_665
+ // [6a70]
+ 0x27000a08, 0x0a002102, 0x3b1c5212, 0x110723ad, // pt.gd.un_430 jw.pt.un_220 ha.id.so_654 ca.it.ro_643
+ 0x19000412, 0x522a1ea9, 0x120e520e, 0x52001e22, // fi.gl.un_640 ms.mt.ha_544 ha.is.hu_555 ms.ha.un_870
+ 0x051104ad, 0x191c11ee, 0x0d212aec, 0x6b12070d, // fi.ro.fr_643 ro.id.gl_422 mt.jw.cs_644 it.hu.ceb_554
+ 0x25003b12, 0x013b07ac, 0x0a29070c, 0x3b27280c, // so.eu.un_640 it.so.en_632 it.sl.pt_543 sw.gd.so_543
+ // [6a80]
+ 0x3f086eee, 0x32170f05, 0x032b0c08, 0x2700022a, // hmn.no.af_422 lv.sr.bs_333 sv.vi.nl_443 da.gd.un_970
+ 0x05202509, 0x0b002302, 0x13014aee, 0x10000d04, // eu.sq.fr_444 ca.es.un_220 yo.en.et_422 cs.lt.un_320
+ 0x25040ea4, 0x1929070c, 0x17131ba4, 0x13005314, // is.fi.eu_433 it.sl.gl_543 tr.et.sr_433 ht.et.un_660
+ 0x0e4a0b08, 0x172855ee, 0x2852640c, 0x531129a0, // es.yo.is_443 rw.sw.sr_422 lg.ha.sw_543 sl.ro.ht_322
+ // [6a90]
+ 0x1e1c0a02, 0x12000314, 0x6e002d08, 0x16641bec, // pt.id.ms_222 nl.hu.un_660 sk.hmn.un_430 tr.lg.hr_644
+ 0x04002012, 0x6b042007, 0x2a1b3113, 0x0a001b1a, // sq.fi.un_640 sq.fi.ceb_432 az.tr.mt_665 tr.pt.un_760
+ 0x073525a0, 0x291f2da0, 0x253f1307, 0x0b0a1ca0, // eu.zu.it_322 sk.cy.sl_322 et.af.eu_432 id.pt.es_322
+ 0x13043fac, 0x25353fa4, 0x64043b04, 0x190a2312, // af.fi.et_632 af.zu.eu_433 so.fi.lg_332 ca.pt.gl_654
+ // [6aa0]
+ 0x1b170907, 0x05000d0d, 0x32091004, 0x52641ca0, // pl.sr.tr_432 cs.fr.un_540 lt.pl.bs_332 id.lg.ha_322
+ 0x030e0f07, 0x190b05a0, 0x2b002323, 0x1e1c2111, // lv.is.nl_432 fr.es.gl_322 ca.vi.un_880 jw.id.ms_653
+ 0x1752550c, 0x04293b08, 0x2564290c, 0x322916ee, // rw.ha.sr_543 so.sl.fi_443 sl.lg.eu_543 hr.sl.bs_422
+ 0x08023fec, 0x290d01a9, 0x124a2aa0, 0x18272a04, // af.da.no_644 en.cs.sl_544 mt.yo.hu_322 mt.gd.ga_332
+ // [6ab0]
+ 0x092d25a0, 0x310e0111, 0x4a3b1ba7, 0x252d0909, // eu.sk.pl_322 en.is.az_653 tr.so.yo_532 pl.sk.eu_444
+ 0x0f0c3fa9, 0x09522da4, 0x080227a4, 0x093552a9, // af.sv.lv_544 sk.ha.pl_433 gd.da.no_433 ha.zu.pl_544
+ 0x275302ad, 0x042053a7, 0x21201cee, 0x3b2531ad, // da.ht.gd_643 ht.sq.fi_532 id.sq.jw_422 az.eu.so_643
+ 0x10003102, 0x520c23ec, 0x0b236b05, 0x130f0c02, // az.lt.un_220 ca.sv.ha_644 ceb.ca.es_333 sv.lv.et_222
+ // [6ac0]
+ 0x0f100a11, 0x3b1b10a9, 0x1a5513a4, 0x07002702, // pt.lt.lv_653 lt.tr.so_544 et.rw.tl_433 gd.it.un_220
+ 0x0f276bee, 0x35000e19, 0x086b2aee, 0x1c1b1e0d, // ceb.gd.lv_422 is.zu.un_750 mt.ceb.no_422 ms.tr.id_554
+ 0x17293b0c, 0x25000718, 0x033b1ba9, 0x02000702, // so.sl.sr_543 it.eu.un_740 tr.so.nl_544 it.da.un_220
+ 0x3f5213af, 0x090d3105, 0x012806a0, 0x316b13ee, // et.ha.af_655 az.cs.pl_333 de.sw.en_322 et.ceb.az_422
+ // [6ad0]
+ 0x040c1312, 0x1a005204, 0x29520f55, 0x011b55a0, // et.sv.fi_654 ha.tl.un_320 lv.ha.sl_442 rw.tr.en_322
+ 0x1f683ba7, 0x0a002a02, 0x11002811, 0x041b640b, // so.ig.cy_532 mt.pt.un_220 sw.ro.un_630 lg.tr.fi_542
+ 0x1a00102a, 0x641f0607, 0x6b1f55ee, 0x23552107, // lt.tl.un_970 de.cy.lg_432 rw.cy.ceb_422 jw.rw.ca_432
+ 0x681e550c, 0x35001c13, 0x03520ead, 0x0353010c, // rw.ms.ig_543 id.zu.un_650 is.ha.nl_643 en.ht.nl_543
+ // [6ae0]
+ 0x08020c07, 0x0b6e2012, 0x4a1805af, 0x0b530fee, // sv.da.no_432 sq.hmn.es_654 fr.ga.yo_655 lv.ht.es_422
+ 0x21182b13, 0x280f2aa9, 0x53000a08, 0x11055214, // vi.ga.jw_665 mt.lv.sw_544 pt.ht.un_430 ha.fr.ro_666
+ 0x1e1c28a6, 0x4a531f14, 0x4a0c64a9, 0x080264a0, // sw.id.ms_521 cy.ht.yo_666 lg.sv.yo_544 lg.da.no_322
+ 0x534a640e, 0x6b681f07, 0x314a250c, 0x12171609, // lg.yo.ht_555 cy.ig.ceb_432 eu.yo.az_543 hr.sr.hu_444
+ // [6af0]
+ 0x4a001135, 0x1c171eec, 0x231302a4, 0x3f532013, // ro.yo.un_A90 ms.sr.id_644 da.et.ca_433 sq.ht.af_665
+ 0x1a016ba0, 0x324a2aa0, 0x23112aac, 0x06291105, // ceb.en.tl_322 mt.yo.bs_322 mt.ro.ca_632 ro.sl.de_333
+ 0x1a6b20ee, 0x04553ba0, 0x0d162dac, 0x6b180ca0, // sq.ceb.tl_422 so.rw.fi_322 sk.hr.cs_632 sv.ga.ceb_322
+ 0x016813a0, 0x550e3508, 0x1f002b11, 0x010605ee, // et.ig.en_322 zu.is.rw_443 vi.cy.un_630 fr.de.en_422
+ // [6b00]
+ 0x123b1aac, 0x052853a9, 0x131a1f02, 0x27003b2b, // tl.so.hu_632 ht.sw.fr_544 cy.tl.et_222 so.gd.un_980
+ 0x1c0b180c, 0x1800680c, 0x020507ec, 0x522328a4, // ga.es.id_543 ig.ga.un_530 it.fr.da_644 sw.ca.ha_433
+ 0x645312ec, 0x1f2b18ee, 0x31521bec, 0x030d3f0e, // hu.ht.lg_644 ga.vi.cy_422 tr.ha.az_644 af.cs.nl_555
+ 0x10001b1b, 0x12000e07, 0x00001837, 0x293564af, // tr.lt.un_770 is.hu.un_420 ar.un.un_B00 lg.zu.sl_655
+ // [6b10]
+ 0x021708a9, 0x1a350ea4, 0x1e1221a0, 0x551a1b0c, // no.sr.da_544 is.zu.tl_433 jw.hu.ms_322 tr.tl.rw_543
+ 0x02005302, 0x255528a9, 0x1b552908, 0x13001004, // ht.da.un_220 sw.rw.eu_544 sl.rw.tr_443 lt.et.un_320
+ 0x06082aa0, 0x01111b0c, 0x351b4aa0, 0x04030d05, // mt.no.de_322 tr.ro.en_543 yo.tr.zu_322 cs.nl.fi_333
+ 0x0b000420, 0x2d0d53a0, 0x531905ee, 0x55000913, // fi.es.un_850 ht.cs.sk_322 fr.gl.ht_422 pl.rw.un_650
+ // [6b20]
+ 0x3f0503a0, 0x0c230409, 0x21003104, 0x28530111, // nl.fr.af_322 fi.ca.sv_444 az.jw.un_320 en.ht.sw_653
+ 0x01535502, 0x55351baf, 0x20002312, 0x060e29a6, // rw.ht.en_222 tr.zu.rw_655 ca.sq.un_640 sl.is.de_521
+ 0x0b000e07, 0x211c3513, 0x100d2dee, 0x64001a13, // is.es.un_420 zu.id.jw_665 sk.cs.lt_422 tl.lg.un_650
+ 0x55001e02, 0x1c062007, 0x190d20a0, 0x200805a0, // ms.rw.un_220 sq.de.id_432 sq.cs.gl_322 fr.no.sq_322
+ // [6b30]
+ 0x07053ba0, 0x081004ac, 0x2d0d07ee, 0x090f2955, // so.fr.it_322 ru.be.uk_632 it.cs.sk_422 sl.lv.pl_442
+ 0x311b0fa0, 0x01000404, 0x0d2d2107, 0x1b2a35a7, // lv.tr.az_322 fi.en.un_320 jw.sk.cs_432 zu.mt.tr_532
+ 0x18192705, 0x130464a0, 0x281223ee, 0x06006402, // gd.gl.ga_333 lg.fi.et_322 ca.hu.sw_422 lg.de.un_220
+ 0x31091b04, 0x2b000307, 0x07000811, 0x07012a14, // tr.pl.az_332 nl.vi.un_420 uk.bg.un_630 mt.en.it_666
+ // [6b40]
+ 0x1c001704, 0x2b001019, 0x10170709, 0x21536405, // sr.id.un_320 lt.vi.un_750 bg.sr.be_444 lg.ht.jw_333
+ 0x643135af, 0x03532702, 0x532507a9, 0x0e270c55, // zu.az.lg_655 gd.ht.nl_222 it.eu.ht_544 sv.gd.is_442
+ 0x2d005504, 0x6e003512, 0x35283f09, 0x2a006414, // rw.sk.un_320 zu.hmn.un_640 af.sw.zu_444 lg.mt.un_660
+ 0x31000302, 0x13002a09, 0x1c162bee, 0x0b2868ee, // nl.az.un_220 mt.et.un_440 vi.hr.id_422 ig.sw.es_422
+ // [6b50]
+ 0x1c003f02, 0x2d0d2502, 0x53000912, 0x21000504, // af.id.un_220 eu.cs.sk_222 pl.ht.un_640 fr.jw.un_320
+ 0x3568060d, 0x060b2804, 0x080602a0, 0x1e1c0308, // de.ig.zu_554 sw.es.de_332 da.de.no_322 nl.id.ms_443
+ 0x0d6e1ca0, 0x3b006420, 0x20001608, 0x0f000407, // id.hmn.cs_322 lg.so.un_850 hr.sq.un_430 fi.lv.un_420
+ 0x04000519, 0x0f102907, 0x05005335, 0x2d0d050c, // fr.fi.un_750 sl.lt.lv_432 ht.fr.un_A90 fr.cs.sk_543
+ // [6b60]
+ 0x100f0313, 0x35316402, 0x0700030e, 0x25091013, // nl.lv.lt_665 lg.az.zu_222 nl.it.un_550 lt.pl.eu_665
+ 0x091153ad, 0x0a0512bb, 0x290f350c, 0x2a2564ec, // ht.ro.pl_643 hu.fr.pt_854 zu.lv.sl_543 lg.eu.mt_644
+ 0x64002d13, 0x06040504, 0x64520409, 0x21006402, // sk.lg.un_650 fr.fi.de_332 fi.ha.lg_444 lg.jw.un_220
+ 0x31003507, 0x1e000404, 0x042010af, 0x532a4aa0, // zu.az.un_420 fi.ms.un_320 lt.sq.fi_655 yo.mt.ht_322
+ // [6b70]
+ 0x170a2111, 0x321768ac, 0x11522508, 0x1b003102, // jw.pt.sr_653 ig.sr.bs_632 eu.ha.ro_443 az.tr.un_220
+ 0x18030612, 0x132d29a0, 0x1e211ca7, 0x31251bec, // de.nl.ga_654 sl.sk.et_322 id.jw.ms_532 tr.eu.az_644
+ 0x321f0ead, 0x1e1c2da4, 0x53004a0b, 0x35315502, // is.cy.bs_643 sk.id.ms_433 yo.ht.un_520 rw.az.zu_222
+ 0x0e211b0c, 0x530f0514, 0x0d13090b, 0x130420a0, // tr.jw.is_543 fr.lv.ht_666 hi.bh.ne_542 sq.fi.et_322
+ // [6b80]
+ 0x13005202, 0x16005304, 0x13002304, 0x0a002804, // ha.et.un_220 ht.hr.un_320 ca.et.un_320 sw.pt.un_320
+ 0x64094a09, 0x23003505, 0x0f5529a0, 0x190b2907, // yo.pl.lg_444 zu.ca.un_330 sl.rw.lv_322 sl.es.gl_432
+ 0x55006812, 0x040b530c, 0x53002521, 0x3f6403a0, // ig.rw.un_640 ht.es.fi_543 eu.ht.un_860 nl.lg.af_322
+ 0x0b0528a7, 0x52003f04, 0x101b1112, 0x1c003504, // sw.fr.es_532 af.ha.un_320 ro.tr.lt_654 zu.id.un_320
+ // [6b90]
+ 0x182721a9, 0x213b1aaf, 0x53002702, 0x2d1235af, // jw.gd.ga_544 tl.so.jw_655 gd.ht.un_220 zu.hu.sk_655
+ 0x520625ec, 0x0d000b04, 0x20553513, 0x07001e0d, // eu.de.ha_644 es.cs.un_320 zu.rw.sq_665 ms.it.un_540
+ 0x1b121aa7, 0x210e1a09, 0x311b1cec, 0x07001e02, // tl.hu.tr_532 tl.is.jw_444 id.tr.az_644 ms.it.un_220
+ 0x21203b0d, 0x25115309, 0x2d0d25af, 0x18000b02, // so.sq.jw_554 ht.ro.eu_444 eu.cs.sk_655 es.ga.un_220
+ // [6ba0]
+ 0x1e1f1c0c, 0x6b52090c, 0x2a0703a0, 0x08022302, // id.cy.ms_543 pl.ha.ceb_543 nl.it.mt_322 ca.da.no_222
+ 0x08020e14, 0x16296b07, 0x3b000b02, 0x52002119, // is.da.no_666 ceb.sl.hr_432 es.so.un_220 jw.ha.un_750
+ 0x0f000e09, 0x13252d08, 0x551005a0, 0x0c0802ee, // is.lv.un_440 sk.eu.et_443 fr.lt.rw_322 da.no.sv_422
+ 0x3509040d, 0x06083fa0, 0x0a0f6b07, 0x2d0d3ba4, // fi.pl.zu_554 af.no.de_322 ceb.lv.pt_432 so.cs.sk_433
+ // [6bb0]
+ 0x0c030612, 0x09066413, 0x12202114, 0x061e21a9, // de.nl.sv_654 lg.de.pl_665 jw.sq.hu_666 jw.ms.de_544
+ 0x64001a34, 0x1e1c2308, 0x320d17a0, 0x03002318, // tl.lg.un_A80 ca.id.ms_443 sr.cs.bs_322 ca.nl.un_740
+ 0x2a276ba7, 0x32002013, 0x122135af, 0x040710a6, // ceb.gd.mt_532 sq.bs.un_650 zu.jw.hu_655 be.bg.ru_521
+ 0x110427a9, 0x06000919, 0x21353b04, 0x00006b37, // gd.fi.ro_544 pl.de.un_750 so.zu.jw_332 ceb.un.un_B00
+ // [6bc0]
+ 0x1e1a1c12, 0x044a1f07, 0x0f2d0d13, 0x685264ad, // id.tl.ms_654 cy.yo.fi_432 cs.sk.lv_665 lg.ha.ig_643
+ 0x256835a0, 0x190a0bee, 0x2d0d31ee, 0x3f001313, // zu.ig.eu_322 es.pt.gl_422 az.cs.sk_422 et.af.un_650
+ 0x4a0e2ba9, 0x2a4a07af, 0x191206a6, 0x215204ec, // vi.is.yo_544 it.yo.mt_655 de.hu.gl_521 fi.ha.jw_644
+ 0x1b6b6802, 0x3564250e, 0x1300061a, 0x0c002b04, // ig.ceb.tr_222 eu.lg.zu_555 de.et.un_760 vi.sv.un_320
+ // [6bd0]
+ 0x20123512, 0x121e1c14, 0x12003529, 0x1320030d, // zu.hu.sq_654 id.ms.hu_666 zu.hu.un_960 nl.sq.et_554
+ 0x046b4a0e, 0x04100aec, 0x11100f13, 0x04132a07, // yo.ceb.fi_555 mk.be.ru_644 lv.lt.ro_665 mt.et.fi_432
+ 0x55001b08, 0x32255514, 0x1b552a08, 0x32002902, // tr.rw.un_430 rw.eu.bs_666 mt.rw.tr_443 sl.bs.un_220
+ 0x32172911, 0x35001e1a, 0x20000508, 0x32172d0c, // sl.sr.bs_653 ms.zu.un_760 fr.sq.un_430 sk.sr.bs_543
+ // [6be0]
+ 0x2a0f55a4, 0x2d681a08, 0x104a0f07, 0x130103a6, // rw.lv.mt_433 tl.ig.sk_443 lv.yo.lt_432 nl.en.et_521
+ 0x0d005214, 0x1b185204, 0x05010c08, 0x270a18af, // ha.cs.un_660 ha.ga.tr_332 sv.en.fr_443 ga.pt.gd_655
+ 0x0e3b12ad, 0x1c533bee, 0x0300350c, 0x1b101307, // hu.so.is_643 so.ht.id_422 zu.nl.un_530 et.lt.tr_432
+ 0x10016ba0, 0x27171fa4, 0x35001a2b, 0x256b5212, // ceb.en.lt_322 cy.sr.gd_433 tl.zu.un_980 ha.ceb.eu_654
+ // [6bf0]
+ 0x0f230560, 0x02250812, 0x2d0d2855, 0x052353ad, // fr.ca.lv_664 no.eu.da_654 sw.cs.sk_442 ht.ca.fr_643
+ 0x041710a0, 0x251b0413, 0x53232112, 0x166b1a0c, // be.sr.ru_322 fi.tr.eu_665 jw.ca.ht_654 tl.ceb.hr_543
+ 0x2b006412, 0x31002a05, 0x2d0d1017, 0x21532312, // lg.vi.un_640 mt.az.un_330 lt.cs.sk_753 ca.ht.jw_654
+ 0x3f1f0208, 0x21532307, 0x17111e0c, 0x0000172d, // da.cy.af_443 ca.ht.jw_432 ms.ro.sr_543 sr.un.un_A00
+
+ // [6c00]
+ 0x3217030c, 0x033f04ee, 0x21235312, 0x1c25210c, // nl.sr.bs_543 fi.af.nl_422 ht.ca.jw_654 jw.eu.id_543
+ 0x1f013fa9, 0x11191fa0, 0x0c6808ee, 0x646b35a0, // af.en.cy_544 cy.gl.ro_322 no.ig.sv_422 zu.ceb.lg_322
+ 0x114a16a4, 0x120425af, 0x556407ee, 0x2a003509, // hr.yo.ro_433 eu.fi.hu_655 it.lg.rw_422 zu.mt.un_440
+ 0x04521ba4, 0x1a00530c, 0x022d0305, 0x041c1313, // tr.ha.fi_433 ht.tl.un_530 nl.sk.da_333 et.id.fi_665
+ // [6c10]
+ 0x0302185a, 0x1c251eec, 0x21056ea7, 0x0e250fac, // ga.da.nl_553 ms.eu.id_644 hmn.fr.jw_532 lv.eu.is_632
+ 0x2731250c, 0x060405ec, 0x1c110605, 0x20003102, // eu.az.gd_543 fr.fi.de_644 de.ro.id_333 az.sq.un_220
+ 0x3f030ba9, 0x64006812, 0x16173212, 0x231907ad, // es.nl.af_544 ig.lg.un_640 bs.sr.hr_654 it.gl.ca_643
+ 0x0e030808, 0x130c080e, 0x07050b08, 0x270752ac, // no.nl.is_443 no.sv.et_555 es.fr.it_443 ha.it.gd_632
+ // [6c20]
+ 0x1b536b09, 0x0600310e, 0x2d0d2714, 0x080e02ac, // ceb.ht.tr_444 az.de.un_550 gd.cs.sk_666 da.is.no_632
+ 0x53192811, 0x21002302, 0x6b00101a, 0x6b103513, // sw.gl.ht_653 ca.jw.un_220 lt.ceb.un_760 zu.lt.ceb_665
+ 0x0a0b10a7, 0x02005312, 0x13550208, 0x551b180c, // lt.es.pt_532 ht.da.un_640 da.rw.et_443 ga.tr.rw_543
+ 0x0b011904, 0x4a23530e, 0x0b6e19ee, 0x19050a55, // gl.en.es_332 ht.ca.yo_555 gl.hmn.es_422 pt.fr.gl_442
+ // [6c30]
+ 0x3f2823ee, 0x0c001813, 0x023b3f0d, 0x2d0e0dad, // ca.sw.af_422 ga.sv.un_650 af.so.da_554 cs.is.sk_643
+ 0x3b00200b, 0x1b160fa4, 0x35002b29, 0x06180eee, // sq.so.un_520 lv.hr.tr_433 vi.zu.un_960 is.ga.de_422
+ 0x282d0d0e, 0x11200fee, 0x3f001005, 0x2d060d0c, // cs.sk.sw_555 lv.sq.ro_422 lt.af.un_330 cs.de.sk_543
+ 0x0b0a4aa4, 0x12003113, 0x18061fee, 0x100502ee, // yo.pt.es_433 az.hu.un_650 cy.de.ga_422 da.fr.lt_422
+ // [6c40]
+ 0x27061807, 0x31001234, 0x0628530c, 0x0a000d04, // ga.de.gd_432 hu.az.un_A80 ht.sw.de_543 cs.pt.un_320
+ 0x556b2da9, 0x28550613, 0x32162014, 0x060d12a4, // sk.ceb.rw_544 de.rw.sw_665 sq.hr.bs_666 hu.cs.de_433
+ 0x0c1604a4, 0x0d0c2da4, 0x553228ad, 0x086431a4, // fi.hr.sv_433 sk.sv.cs_433 sw.bs.rw_643 az.lg.no_433
+ 0x64010ba0, 0x552109ec, 0x2d0d12bb, 0x1325280c, // es.en.lg_322 pl.jw.rw_644 hu.cs.sk_854 sw.eu.et_543
+ // [6c50]
+ 0x68001704, 0x0811175a, 0x321620ec, 0x10041b04, // sr.ig.un_320 sr.ro.uk_553 sq.hr.bs_644 tr.fi.lt_332
+ 0x32162a0e, 0x0c002914, 0x1a001605, 0x176428ad, // mt.hr.bs_555 sl.sv.un_660 hr.tl.un_330 sw.lg.sr_643
+ 0x64531713, 0x060304a9, 0x170c0612, 0x030619ee, // sr.ht.lg_665 fi.nl.de_544 de.sv.sr_654 gl.de.nl_422
+ 0x320e2802, 0x1e1c0f07, 0x0200042b, 0x1e001a02, // sw.is.bs_222 lv.id.ms_432 fi.da.un_980 tl.ms.un_220
+ // [6c60]
+ 0x105525ac, 0x2d131212, 0x64040e08, 0x202928ec, // eu.rw.lt_632 hu.et.sk_654 is.fi.lg_443 sw.sl.sq_644
+ 0x3f0429a4, 0x084a0e07, 0x52640e07, 0x31000e09, // sl.fi.af_433 is.yo.no_432 is.lg.ha_432 is.az.un_440
+ 0x551135ec, 0x23001313, 0x13042909, 0x070b06a4, // zu.ro.rw_644 et.ca.un_650 sl.fi.et_444 de.es.it_433
+ 0x1c2d25a0, 0x07030612, 0x080c6412, 0x07060309, // eu.sk.id_322 de.nl.it_654 lg.sv.no_654 nl.de.it_444
+ // [6c70]
+ 0x352129a6, 0x040e2102, 0x351f06a4, 0x080221a0, // sl.jw.zu_521 jw.is.fi_222 de.cy.zu_433 jw.da.no_322
+ 0x1b1a0a07, 0x28355509, 0x27005319, 0x03060714, // pt.tl.tr_432 rw.zu.sw_444 ht.gd.un_750 it.de.nl_666
+ 0x182d0e0c, 0x112155af, 0x35531aaf, 0x1b2006ac, // is.sk.ga_543 rw.jw.ro_655 tl.ht.zu_655 de.sq.tr_632
+ 0x2000642b, 0x321609ee, 0x3b001a36, 0x0b2a19af, // lg.sq.un_980 pl.hr.bs_422 tl.so.un_AA0 gl.mt.es_655
+ // [6c80]
+ 0x1607280c, 0x640d01ec, 0x1f00202a, 0x07062a12, // sw.it.hr_543 en.cs.lg_644 sq.cy.un_970 mt.de.it_654
+ 0x1e1c0fee, 0x1f003f36, 0x230f09ee, 0x20002b0d, // lv.id.ms_422 af.cy.un_AA0 pl.lv.ca_422 vi.sq.un_540
+ 0x1a6b2ba7, 0x35135304, 0x20001604, 0x35121e08, // vi.ceb.tl_532 ht.et.zu_332 hr.sq.un_320 ms.hu.zu_443
+ 0x1e1c2802, 0x2b000a02, 0x0c000318, 0x0b110604, // sw.id.ms_222 pt.vi.un_220 nl.sv.un_740 de.ro.es_332
+ // [6c90]
+ 0x270518af, 0x19251004, 0x3b12080c, 0x08022309, // ga.fr.gd_655 lt.eu.gl_332 no.hu.so_543 ca.da.no_444
+ 0x53002305, 0x0d2d270d, 0x10003f05, 0x2a3b2811, // ca.ht.un_330 gd.sk.cs_554 af.lt.un_330 sw.so.mt_653
+ 0x0c000714, 0x27182860, 0x3f001f12, 0x110d55a4, // it.sv.un_660 sw.ga.gd_664 cy.af.un_640 rw.cs.ro_433
+ 0x1c1b28a0, 0x052707a7, 0x0e006e08, 0x190b55a0, // sw.tr.id_322 it.gd.fr_532 hmn.is.un_430 rw.es.gl_322
+ // [6ca0]
+ 0x1e2135ac, 0x2718070e, 0x13071bee, 0x2a111814, // zu.jw.ms_632 it.ga.gd_555 tr.it.et_422 ga.ro.mt_666
+ 0x3b354a13, 0x16290aa9, 0x130d090c, 0x0b0a0da6, // yo.zu.so_665 pt.sl.hr_544 hi.ne.bh_543 cs.pt.es_521
+ 0x020c05ee, 0x521a6bad, 0x6b553507, 0x193b0ba0, // fr.sv.da_422 ceb.tl.ha_643 zu.rw.ceb_432 es.so.gl_322
+ 0x64551ba0, 0x1b6b0ba4, 0x0900551b, 0x35001902, // tr.rw.lg_322 es.ceb.tr_433 rw.pl.un_770 gl.zu.un_220
+ // [6cb0]
+ 0x185509ad, 0x64550960, 0x0621090c, 0x080206ec, // pl.rw.ga_643 pl.rw.lg_664 pl.jw.de_543 de.da.no_644
+ 0x1b001a12, 0x536421a0, 0x09536404, 0x21121005, // tl.tr.un_640 jw.lg.ht_322 lg.ht.pl_332 lt.hu.jw_333
+ 0x55002902, 0x0a000823, 0x1a0510a4, 0x1b2731ee, // sl.rw.un_220 uk.mk.un_880 lt.fr.tl_433 az.gd.tr_422
+ 0x071b0513, 0x052918a0, 0x12001f0c, 0x01005502, // fr.tr.it_665 ga.sl.fr_322 cy.hu.un_530 rw.en.un_220
+ // [6cc0]
+ 0x0e12040e, 0x04132a14, 0x120f0e07, 0x0c061b04, // fi.hu.is_555 mt.et.fi_666 is.lv.hu_432 tr.de.sv_332
+ 0x1001230c, 0x08022305, 0x3f000904, 0x120805a4, // ca.en.lt_543 ca.da.no_333 pl.af.un_320 fr.no.hu_433
+ 0x10200fa4, 0x191123a0, 0x21091ca0, 0x320f165a, // lv.sq.lt_433 ca.ro.gl_322 id.pl.jw_322 hr.lv.bs_553
+ 0x20230baf, 0x176852a6, 0x1300250b, 0x0f0510ee, // es.ca.sq_655 ha.ig.sr_521 eu.et.un_520 lt.fr.lv_422
+ // [6cd0]
+ 0x1b000412, 0x0c001e02, 0x171029a0, 0x211355ec, // fi.tr.un_640 ms.sv.un_220 sl.lt.sr_322 rw.et.jw_644
+ 0x52350ea4, 0x063f0b0c, 0x25290f12, 0x641a2108, // is.zu.ha_433 es.af.de_543 lv.sl.eu_654 jw.tl.lg_443
+ 0x6435230c, 0x120e090c, 0x4a0a52a9, 0x2718230c, // ca.zu.lg_543 pl.is.hu_543 ha.pt.yo_544 ca.ga.gd_543
+ 0x0d000208, 0x311b55a4, 0x190a2aee, 0x642d35ad, // da.cs.un_430 rw.tr.az_433 mt.pt.gl_422 zu.sk.lg_643
+ // [6ce0]
+ 0x11290f07, 0x19230b0d, 0x17002008, 0x20063f09, // lv.sl.ro_432 es.ca.gl_554 sq.sr.un_430 af.de.sq_444
+ 0x290d17a6, 0x01093b0b, 0x0a1108ec, 0x55093b0b, // sr.cs.sl_521 so.pl.en_542 uk.ro.mk_644 so.pl.rw_542
+ 0x32100ba0, 0x55000819, 0x1a0b530b, 0x0d022d60, // es.lt.bs_322 no.rw.un_750 ht.es.tl_542 sk.da.cs_664
+ 0x23251914, 0x0f121008, 0x050607a0, 0x1206250c, // gl.eu.ca_666 lt.hu.lv_443 it.de.fr_322 eu.de.hu_543
+ // [6cf0]
+ 0x32000207, 0x022008a4, 0x0d6b2d0c, 0x043f1313, // da.bs.un_420 no.sq.da_433 sk.ceb.cs_543 et.af.fi_665
+ 0x2a071baf, 0x1c1e3b07, 0x1e000e13, 0x0e2d1212, // tr.it.mt_655 so.ms.id_432 is.ms.un_650 hu.sk.is_654
+ 0x231f0e07, 0x011c0ea7, 0x32166ea0, 0x0900640c, // is.cy.ca_432 is.id.en_532 hmn.hr.bs_322 lg.pl.un_530
+ 0x64521e02, 0x11000a02, 0x02001804, 0x08210eee, // ms.ha.lg_222 pt.ro.un_220 ga.da.un_320 is.jw.no_422
+ // [6d00]
+ 0x06200c13, 0x351827ec, 0x252a5312, 0x1a6b0b04, // sv.sq.de_665 gd.ga.zu_644 ht.mt.eu_654 es.ceb.tl_332
+ 0x060805a4, 0x200111ec, 0x071711ec, 0x19072313, // fr.no.de_433 ro.en.sq_644 ro.sr.bg_644 ca.it.gl_665
+ 0x06351f0c, 0x2505350c, 0x131911ad, 0x071711a6, // cy.zu.de_543 zu.fr.eu_543 ro.gl.et_643 ro.sr.bg_521
+ 0x17162da6, 0x130e35ee, 0x17102aa4, 0x130407a0, // sk.hr.sr_521 zu.is.et_422 mt.lt.sr_433 it.fi.et_322
+ // [6d10]
+ 0x0c100f05, 0x015318a0, 0x1b210e08, 0x0e0704a7, // lv.lt.sv_333 ga.ht.en_322 is.jw.tr_443 fi.it.is_532
+ 0x04100614, 0x0e100c04, 0x3f2d0d13, 0x2d0b19af, // de.lt.fi_666 sv.lt.is_332 cs.sk.af_665 gl.es.sk_655
+ 0x190a0e14, 0x063b35ec, 0x06113fad, 0x190a0609, // is.pt.gl_666 zu.so.de_644 af.ro.de_643 de.pt.gl_444
+ 0x041f06ad, 0x110768a7, 0x0d202d0c, 0x04000f13, // de.cy.fi_643 ig.it.ro_532 sk.sq.cs_543 lv.fi.un_650
+ // [6d20]
+ 0x1c281e55, 0x28351313, 0x061f35a4, 0x2a002007, // ms.sw.id_442 et.zu.sw_665 zu.cy.de_433 sq.mt.un_420
+ 0x18021f07, 0x0c6402af, 0x2d180dec, 0x0c1f640c, // cy.da.ga_432 da.lg.sv_655 cs.ga.sk_644 lg.cy.sv_543
+ 0x174a28ac, 0x17022a0c, 0x09004a1b, 0x523b64af, // sw.yo.sr_632 mt.da.sr_543 yo.pl.un_770 lg.so.ha_655
+ 0x5300280b, 0x25551007, 0x4a002814, 0x0e00230c, // sw.ht.un_520 lt.rw.eu_432 sw.yo.un_660 ca.is.un_530
+ // [6d30]
+ 0x31051ba7, 0x3b1e250b, 0x0c0b64ee, 0x2d000620, // tr.fr.az_532 eu.ms.so_542 lg.es.sv_422 de.sk.un_850
+ 0x532811ec, 0x64021f0c, 0x17002804, 0x35003f02, // ro.sw.ht_644 cy.da.lg_543 sw.sr.un_320 af.zu.un_220
+ 0x11001a02, 0x093b060c, 0x270364ee, 0x17161e02, // tl.ro.un_220 de.so.pl_543 lg.nl.gd_422 ms.hr.sr_222
+ 0x272a1fad, 0x1b006e13, 0x0628640d, 0x05230fa7, // cy.mt.gd_643 hmn.tr.un_650 lg.sw.de_554 lv.ca.fr_532
+ // [6d40]
+ 0x550628a7, 0x3b28530c, 0x1e121a12, 0x032b3fa0, // sw.de.rw_532 ht.sw.so_543 tl.hu.ms_654 af.vi.nl_322
+ 0x3f1702a0, 0x530d05ad, 0x1c213b0e, 0x2331100c, // da.sr.af_322 fr.cs.ht_643 so.jw.id_555 lt.az.ca_543
+ 0x12002702, 0x0e040cec, 0x55003504, 0x53001007, // gd.hu.un_220 sv.fi.is_644 zu.rw.un_320 lt.ht.un_420
+ 0x13200f05, 0x4a006405, 0x19070b07, 0x311e1a12, // lv.sq.et_333 lg.yo.un_330 es.it.gl_432 tl.ms.az_654
+ // [6d50]
+ 0x204a6412, 0x05000f13, 0x1f114a07, 0x0f00230e, // lg.yo.sq_654 lv.fr.un_650 yo.ro.cy_432 ca.lv.un_550
+ 0x080220a4, 0x351c1a0c, 0x32160fa4, 0x53232807, // sq.da.no_433 tl.id.zu_543 lv.hr.bs_433 sw.ca.ht_432
+ 0x3f101fa7, 0x3f2520ee, 0x04120da0, 0x64000219, // cy.lt.af_532 sq.eu.af_422 cs.hu.fi_322 da.lg.un_750
+ 0x171c21a9, 0x285368ec, 0x190b1ba0, 0x1f20090c, // jw.id.sr_544 ig.ht.sw_644 tr.es.gl_322 pl.sq.cy_543
+ // [6d60]
+ 0x1b552aa7, 0x0e0c1314, 0x133b080b, 0x056b0807, // mt.rw.tr_532 et.sv.is_666 no.so.et_542 no.ceb.fr_432
+ 0x0c1b1009, 0x552b0508, 0x55281ca7, 0x0800130d, // lt.tr.sv_444 fr.vi.rw_443 id.sw.rw_532 et.no.un_540
+ 0x28532a0c, 0x052d11a0, 0x21002a23, 0x20000f07, // mt.ht.sw_543 ro.sk.fr_322 mt.jw.un_880 lv.sq.un_420
+ 0x03050604, 0x254a550d, 0x32200ea7, 0x13091c5a, // de.fr.nl_332 rw.yo.eu_554 is.sq.bs_532 mr.hi.bh_553
+ // [6d70]
+ 0x2a000e0c, 0x212a6813, 0x28003502, 0x312a2804, // is.mt.un_530 ig.mt.jw_665 zu.sw.un_220 sw.mt.az_332
+ 0x23002008, 0x4a2568ad, 0x183b2012, 0x11312012, // sq.ca.un_430 ig.eu.yo_643 sq.so.ga_654 sq.az.ro_654
+ 0x53232005, 0x32044a02, 0x1a00030d, 0x100f3b14, // sq.ca.ht_333 yo.fi.bs_222 nl.tl.un_540 so.lv.lt_666
+ 0x08000908, 0x170429a0, 0x556425ee, 0x252855a0, // pl.no.un_430 sl.fi.sr_322 eu.lg.rw_422 rw.sw.eu_322
+ // [6d80]
+ 0x180e090c, 0x110510a0, 0x211f55a0, 0x25231e0c, // pl.is.ga_543 lt.fr.ro_322 rw.cy.jw_322 ms.ca.eu_543
+ 0x13002522, 0x18001107, 0x05001b04, 0x0f043f14, // eu.et.un_870 ro.ga.un_420 tr.fr.un_320 af.fi.lv_666
+ 0x2d0d08a0, 0x1e211c0b, 0x04005513, 0x281e1ba9, // no.cs.sk_322 id.jw.ms_542 rw.fi.un_650 tr.ms.sw_544
+ 0x12000202, 0x0c1b53ee, 0x166425ad, 0x19110b09, // da.hu.un_220 ht.tr.sv_422 eu.lg.hr_643 es.ro.gl_444
+ // [6d90]
+ 0x28001805, 0x64105502, 0x64042dee, 0x64256b11, // ga.sw.un_330 rw.lt.lg_222 sk.fi.lg_422 ceb.eu.lg_653
+ 0x2a0f120c, 0x04001602, 0x00000901, 0x202a1b07, // hu.lv.mt_543 hr.fi.un_220 pl.un.un_200 tr.mt.sq_432
+ 0x556425ec, 0x1e120da0, 0x6400252b, 0x64251008, // eu.lg.rw_644 cs.hu.ms_322 eu.lg.un_980 lt.eu.lg_443
+ 0x2900251a, 0x28520e0d, 0x52111ba4, 0x3b526ba9, // eu.sl.un_760 is.ha.sw_554 tr.ro.ha_433 ceb.ha.so_544
+ // [6da0]
+ 0x11005205, 0x1300352b, 0x2d0d280c, 0x231f06ad, // ha.ro.un_330 zu.et.un_980 sw.cs.sk_543 de.cy.ca_643
+ 0x113f1308, 0x52551ba9, 0x023f1213, 0x551a35a4, // et.af.ro_443 tr.rw.ha_544 hu.af.da_665 zu.tl.rw_433
+ 0x6b552512, 0x6800522c, 0x551e250c, 0x05011a0d, // eu.rw.ceb_654 ha.ig.un_990 eu.ms.rw_543 tl.en.fr_554
+ 0x12002520, 0x13251012, 0x130c05ec, 0x0a131204, // eu.hu.un_850 lt.eu.et_654 fr.sv.et_644 hu.et.pt_332
+ // [6db0]
+ 0x280911ac, 0x03040da4, 0x0d0c13a7, 0x12113f08, // ro.pl.sw_632 cs.fi.nl_433 et.sv.cs_532 af.ro.hu_443
+ 0x1b55280d, 0x0802280c, 0x0d062d0c, 0x353b640d, // sw.rw.tr_554 sw.da.no_543 sk.de.cs_543 lg.so.zu_554
+ 0x20000702, 0x350f3ba4, 0x35525512, 0x043f3ba4, // it.sq.un_220 so.lv.zu_433 rw.ha.zu_654 so.af.fi_433
+ 0x12000208, 0x17000402, 0x31021b55, 0x1f0e0cee, // da.hu.un_430 ru.sr.un_220 tr.da.az_442 sv.is.cy_422
+ // [6dc0]
+ 0x1f080213, 0x2a005223, 0x3b0b68ee, 0x0f003b0c, // da.no.cy_665 ha.mt.un_880 ig.es.so_422 so.lv.un_530
+ 0x53001f0e, 0x021f08ec, 0x12002b1a, 0x121f2da0, // cy.ht.un_550 no.cy.da_644 vi.hu.un_760 sk.cy.hu_322
+ 0x08022007, 0x1a063fa0, 0x19001f07, 0x0e002512, // sq.da.no_432 af.de.tl_322 cy.gl.un_420 eu.is.un_640
+ 0x20291a09, 0x050720ec, 0x0a5206a4, 0x322012ad, // tl.sl.sq_444 sq.it.fr_644 de.ha.pt_433 hu.sq.bs_643
+ // [6dd0]
+ 0x0200190d, 0x011868ee, 0x1c1e1a0c, 0x1e551c0c, // gl.da.un_540 ig.ga.en_422 tl.ms.id_543 id.rw.ms_543
+ 0x05001a0b, 0x190a5505, 0x0d09290d, 0x0d292d0d, // tl.fr.un_520 rw.pt.gl_333 sl.pl.cs_554 sk.sl.cs_554
+ 0x1c181e04, 0x100f5211, 0x642916a9, 0x0b0e19a4, // ms.ga.id_332 ha.lv.lt_653 hr.sl.lg_544 gl.is.es_433
+ 0x27281813, 0x25001008, 0x171b35ac, 0x18282bee, // ga.sw.gd_665 lt.eu.un_430 zu.tr.sr_632 vi.sw.ga_422
+ // [6de0]
+ 0x16000207, 0x190a0b11, 0x532d0604, 0x643b070e, // da.hr.un_420 es.pt.gl_653 de.sk.ht_332 it.so.lg_555
+ 0x4a0c28a0, 0x04000b02, 0x0402280d, 0x132d0904, // sw.sv.yo_322 es.fi.un_220 sw.da.fi_554 pl.sk.et_332
+ 0x043f64a0, 0x25002107, 0x53001e11, 0x6b201aa0, // lg.af.fi_322 jw.eu.un_420 ms.ht.un_630 tl.sq.ceb_322
+ 0x20000c02, 0x060c0513, 0x092568a0, 0x1f1a0aa6, // sv.sq.un_220 fr.sv.de_665 ig.eu.pl_322 pt.tl.cy_521
+ // [6df0]
+ 0x2a092d60, 0x3b6852a9, 0x320d2aa7, 0x6e092d12, // sk.pl.mt_664 ha.ig.so_544 mt.cs.bs_532 sk.pl.hmn_654
+ 0x68214a0c, 0x35000302, 0x2a080eac, 0x68184aa9, // yo.jw.ig_543 nl.zu.un_220 is.no.mt_632 yo.ga.ig_544
+ 0x11520e0e, 0x091f1012, 0x2b520aa7, 0x3b4a520c, // is.ha.ro_555 lt.cy.pl_654 pt.ha.vi_532 ha.yo.so_543
+ 0x3b1917a4, 0x0a001c0d, 0x2a2035a9, 0x0b3f04a4, // sr.gl.so_433 id.pt.un_540 zu.sq.mt_544 fi.af.es_433
+ // [6e00]
+ 0x1900290c, 0x2a000f05, 0x120852a7, 0x521304ec, // sl.gl.un_530 lv.mt.un_330 ha.no.hu_532 fi.et.ha_644
+ 0x3b002502, 0x02000f19, 0x4a002812, 0x2a640e07, // eu.so.un_220 lv.da.un_750 sw.yo.un_640 is.lg.mt_432
+ 0x210952ee, 0x0802290c, 0x35525507, 0x20291055, // ha.pl.jw_422 sl.da.no_543 rw.ha.zu_432 lt.sl.sq_442
+ 0x02050812, 0x1a52060b, 0x1b00070c, 0x10000d0d, // no.fr.da_654 de.ha.tl_542 it.tr.un_530 cs.lt.un_540
+ // [6e10]
+ 0x050253a0, 0x1e641cee, 0x05010fa4, 0x0a001305, // ht.da.fr_322 id.lg.ms_422 lv.en.fr_433 et.pt.un_330
+ 0x18110713, 0x072a1208, 0x112d2104, 0x23000c04, // it.ro.ga_665 hu.mt.it_443 jw.sk.ro_332 sv.ca.un_320
+ 0x352568a0, 0x0b0e19a7, 0x25000d04, 0x190a0ca4, // ig.eu.zu_322 gl.is.es_532 cs.eu.un_320 sv.pt.gl_433
+ 0x022b0e0b, 0x1a006b0b, 0x23050209, 0x1b181fee, // is.vi.da_542 ceb.tl.un_520 da.fr.ca_444 cy.ga.tr_422
+ // [6e20]
+ 0x10072014, 0x07005514, 0x210753a7, 0x0e000913, // sq.it.lt_666 rw.it.un_660 ht.it.jw_532 pl.is.un_650
+ 0x121e10a7, 0x0f10200c, 0x28185202, 0x120d04a4, // lt.ms.hu_532 sq.lt.lv_543 ha.ga.sw_222 fi.cs.hu_433
+ 0x17000908, 0x1008070e, 0x16112da4, 0x01005207, // pl.sr.un_430 bg.uk.be_555 sk.ro.hr_433 ha.en.un_420
+ 0x190b0708, 0x1c006419, 0x06102aa4, 0x190a0502, // it.es.gl_443 lg.id.un_750 mt.lt.de_433 fr.pt.gl_222
+ // [6e30]
+ 0x20041205, 0x0e070da9, 0x29682d11, 0x051227a0, // hu.fi.sq_333 cs.it.is_544 sk.ig.sl_653 gd.hu.fr_322
+ 0x113155a0, 0x0c2a10af, 0x13236b14, 0x2d0d0b0c, // rw.az.ro_322 lt.mt.sv_655 ceb.ca.et_666 es.cs.sk_543
+ 0x1f000202, 0x110468a0, 0x1c1929a0, 0x132904ec, // da.cy.un_220 ig.fi.ro_322 sl.gl.id_322 fi.sl.et_644
+ 0x52131013, 0x083132a0, 0x080710ac, 0x02080c55, // lt.et.ha_665 bs.az.no_322 be.bg.uk_632 sv.no.da_442
+ // [6e40]
+ 0x28216ba4, 0x1f350f12, 0x1107050c, 0x1f183b07, // ceb.jw.sw_433 lv.zu.cy_654 fr.it.ro_543 so.ga.cy_432
+ 0x2753180c, 0x1e201c0c, 0x6b030612, 0x52531807, // ga.ht.gd_543 id.sq.ms_543 de.nl.ceb_654 ga.ht.ha_432
+ 0x250405ec, 0x3127250c, 0x100e21af, 0x3217040c, // fr.fi.eu_644 eu.gd.az_543 jw.is.lt_655 fi.sr.bs_543
+ 0x27051f07, 0x321217a0, 0x2d0d31a0, 0x020e250c, // cy.fr.gd_432 sr.hu.bs_322 az.cs.sk_322 eu.is.da_543
+ // [6e50]
+ 0x6b6401ec, 0x190b2511, 0x3b64280d, 0x1c001602, // en.lg.ceb_644 eu.es.gl_653 sw.lg.so_554 hr.id.un_220
+ 0x2d0d05ee, 0x52181008, 0x52131008, 0x5200181b, // fr.cs.sk_422 lt.ga.ha_443 lt.et.ha_443 ga.ha.un_770
+ 0x6b0a0604, 0x07003520, 0x251a1f13, 0x0728110c, // de.pt.ceb_332 zu.it.un_850 cy.tl.eu_665 ro.sw.it_543
+ 0x1c130d05, 0x1b0e21a7, 0x013f1314, 0x28521f12, // ne.bh.mr_333 jw.is.tr_532 et.af.en_666 cy.ha.sw_654
+ // [6e60]
+ 0x1e1c6409, 0x2531180c, 0x31681808, 0x551021a9, // lg.id.ms_444 ga.az.eu_543 ga.ig.az_443 jw.lt.rw_544
+ 0x0f000907, 0x531b0707, 0x6b641b12, 0x0c133108, // pl.lv.un_420 it.tr.ht_432 tr.lg.ceb_654 az.et.sv_443
+ 0x1b000f04, 0x1b3109a7, 0x11001308, 0x352b28ee, // lv.tr.un_320 pl.az.tr_532 et.ro.un_430 sw.vi.zu_422
+ 0x0e0c1ba4, 0x322a68a0, 0x0e3f1b02, 0x131a2113, // tr.sv.is_433 ig.mt.bs_322 tr.af.is_222 jw.tl.et_665
+ // [6e70]
+ 0x060823ee, 0x530628a7, 0x0c1f3bad, 0x25350313, // ca.no.de_422 sw.de.ht_532 so.cy.sv_643 nl.zu.eu_665
+ 0x1e2a3b07, 0x3b5264ec, 0x0a1a0408, 0x080228af, // so.mt.ms_432 lg.ha.so_644 fi.tl.pt_443 sw.da.no_655
+ 0x11525507, 0x03203f08, 0x3b5520a4, 0x1e006405, // rw.ha.ro_432 af.sq.nl_443 sq.rw.so_433 lg.ms.un_330
+ 0x18122dec, 0x2b000e1a, 0x3b0e64ec, 0x55312805, // sk.hu.ga_644 is.vi.un_760 lg.is.so_644 sw.az.rw_333
+ // [6e80]
+ 0x2b001007, 0x0d1208ad, 0x532055af, 0x1e6b1c0c, // lt.vi.un_420 no.hu.cs_643 rw.sq.ht_655 id.ceb.ms_543
+ 0x012005ad, 0x2a6435ec, 0x0400030d, 0x0d202812, // fr.sq.en_643 zu.lg.mt_644 nl.fi.un_540 sw.sq.cs_654
+ 0x0800060b, 0x10201f0c, 0x0b1205ec, 0x07002021, // de.no.un_520 cy.sq.lt_543 fr.hu.es_644 sq.it.un_860
+ 0x0755200c, 0x210464a6, 0x3b0b52a0, 0x033b1ba0, // sq.rw.it_543 lg.fi.jw_521 ha.es.so_322 tr.so.nl_322
+ // [6e90]
+ 0x095505a4, 0x0a551107, 0x16091b08, 0x6b1a0eaf, // fr.rw.pl_433 ro.rw.pt_432 tr.pl.hr_443 is.tl.ceb_655
+ 0x062725ec, 0x0f312512, 0x16203207, 0x0b310cee, // eu.gd.de_644 eu.az.lv_654 bs.sq.hr_432 sv.az.es_422
+ 0x1c080cec, 0x0500061a, 0x64001321, 0x05016ba0, // sv.no.id_644 de.fr.un_760 et.lg.un_860 ceb.en.fr_322
+ 0x551b11a7, 0x1b20310d, 0x022a080b, 0x1a000307, // ro.tr.rw_532 az.sq.tr_554 no.mt.da_542 nl.tl.un_420
+ // [6ea0]
+ 0x11123f0d, 0x180c0609, 0x0f2a07ec, 0x29000914, // af.hu.ro_554 de.sv.ga_444 it.mt.lv_644 pl.sl.un_660
+ 0x32101712, 0x05122aec, 0x20132a0c, 0x25033f13, // sr.lt.bs_654 mt.hu.fr_644 mt.et.sq_543 af.nl.eu_665
+ 0x2d0d2a05, 0x0a1307ee, 0x106b0f0c, 0x0c001114, // mt.cs.sk_333 it.et.pt_422 lv.ceb.lt_543 ro.sv.un_660
+ 0x0d0f2d07, 0x09204aee, 0x6b071ca0, 0x64051309, // sk.lv.cs_432 yo.sq.pl_422 id.it.ceb_322 et.fr.lg_444
+ // [6eb0]
+ 0x32175505, 0x20135508, 0x162955a4, 0x35282008, // rw.sr.bs_333 rw.et.sq_443 rw.sl.hr_433 sq.sw.zu_443
+ 0x6b051208, 0x28000e1a, 0x32161eaf, 0x29001b09, // hu.fr.ceb_443 is.sw.un_760 ms.hr.bs_655 tr.sl.un_440
+ 0x0c2304ee, 0x10001e02, 0x2d0d2a0c, 0x081a0e04, // fi.ca.sv_422 ms.lt.un_220 mt.cs.sk_543 is.tl.no_332
+ 0x1c0e1e0c, 0x06081aa4, 0x160d1707, 0x1e1c2905, // ms.is.id_543 tl.no.de_433 sr.cs.hr_432 sl.id.ms_333
+ // [6ec0]
+ 0x12003118, 0x320d1bee, 0x091b1208, 0x1c031b0c, // az.hu.un_740 tr.cs.bs_422 hu.tr.pl_443 tr.nl.id_543
+ 0x3f230ea7, 0x052927a0, 0x120905a4, 0x020c08a7, // is.ca.af_532 gd.sl.fr_322 fr.pl.hu_433 no.sv.da_532
+ 0x29002319, 0x0f1f1813, 0x023f03af, 0x18006405, // ca.sl.un_750 ga.cy.lv_665 nl.af.da_655 lg.ga.un_330
+ 0x190a2dec, 0x0b191212, 0x111e20a4, 0x21070608, // sk.pt.gl_644 hu.gl.es_654 sq.ms.ro_433 de.it.jw_443
+ // [6ed0]
+ 0x0a1b2b0c, 0x17212d07, 0x08020f09, 0x251b32af, // vi.tr.pt_543 sk.jw.sr_432 lv.da.no_444 bs.tr.eu_655
+ 0x311f1ba4, 0x12184a0c, 0x2a2868a9, 0x031f09a7, // tr.cy.az_433 yo.ga.hu_543 ig.sw.mt_544 pl.cy.nl_532
+ 0x010464a0, 0x28000a02, 0x20051b0c, 0x0b3f06ee, // lg.fi.en_322 pt.sw.un_220 tr.fr.sq_543 de.af.es_422
+ 0x05000408, 0x2d123502, 0x282a3f02, 0x013b2aee, // fi.fr.un_430 zu.hu.sk_222 af.mt.sw_222 mt.so.en_422
+ // [6ee0]
+ 0x21125507, 0x062a09ad, 0x12050fee, 0x0f003112, // rw.hu.jw_432 pl.mt.de_643 lv.fr.hu_422 az.lv.un_640
+ 0x2a001802, 0x55001112, 0x0f002a04, 0x0802350c, // ga.mt.un_220 ro.rw.un_640 mt.lv.un_320 zu.da.no_543
+ 0x05190a14, 0x19180bec, 0x123510ac, 0x521a0612, // pt.gl.fr_666 es.ga.gl_644 lt.zu.hu_632 de.tl.ha_654
+ 0x190b25ac, 0x521c110c, 0x030620ad, 0x126407a9, // eu.es.gl_632 ro.id.ha_543 sq.de.nl_643 it.lg.hu_544
+ // [6ef0]
+ 0x6b005305, 0x1c051ea4, 0x013b0b07, 0x12060eee, // ht.ceb.un_330 ms.fr.id_433 es.so.en_432 is.de.hu_422
+ 0x281a64ec, 0x0d002312, 0x25002907, 0x111b3b0c, // lg.tl.sw_644 ca.cs.un_640 sl.eu.un_420 so.tr.ro_543
+ 0x03002112, 0x311b64a9, 0x25032812, 0x250f0ca7, // jw.nl.un_640 lg.tr.az_544 sw.nl.eu_654 sv.lv.eu_532
+ 0x4a0b6408, 0x17003b04, 0x2128640d, 0x120e2d04, // lg.es.yo_443 so.sr.un_320 lg.sw.jw_554 sk.is.hu_332
+ // [6f00]
+ 0x03193fa4, 0x090f25a0, 0x25001b07, 0x20002118, // af.gl.nl_433 eu.lv.pl_322 tr.eu.un_420 jw.sq.un_740
+ 0x19530b55, 0x23001f0c, 0x640a1ba4, 0x100b0408, // es.ht.gl_442 cy.ca.un_530 tr.pt.lg_433 fi.es.lt_443
+ 0x1b001f0c, 0x53001b1b, 0x3b001a2c, 0x32283bad, // cy.tr.un_530 tr.ht.un_770 tl.so.un_990 so.sw.bs_643
+ 0x3f35640e, 0x190a08a9, 0x032508a9, 0x2d060d11, // lg.zu.af_555 no.pt.gl_544 no.eu.nl_544 cs.de.sk_653
+ // [6f10]
+ 0x190a01a4, 0x55003b2a, 0x53000d19, 0x3b1c1eac, // en.pt.gl_433 so.rw.un_970 cs.ht.un_750 ms.id.so_632
+ 0x55003b23, 0x550153ee, 0x1f006807, 0x21132a07, // so.rw.un_880 ht.en.rw_422 ig.cy.un_420 mt.et.jw_432
+ 0x280411a0, 0x13283bad, 0x11001702, 0x1e211c0d, // ro.fi.sw_322 so.sw.et_643 sr.ro.un_220 id.jw.ms_554
+ 0x202d07ac, 0x1f52535a, 0x52531f0c, 0x06000913, // it.sk.sq_632 ht.ha.cy_553 cy.ht.ha_543 pl.de.un_650
+ // [6f20]
+ 0x32060707, 0x6e190a0e, 0x2d0d2955, 0x1e04530c, // it.de.bs_432 pt.gl.hmn_555 sl.cs.sk_442 ht.fi.ms_543
+ 0x06230baf, 0x680952af, 0x1f3b0a5a, 0x5300320c, // es.ca.de_655 ha.pl.ig_655 pt.so.cy_553 bs.ht.un_530
+ 0x271a6bad, 0x2a27520c, 0x3b0f1fa9, 0x3f4a35ee, // ceb.tl.gd_643 ha.gd.mt_543 cy.lv.so_544 zu.yo.af_422
+ 0x25001318, 0x0800091a, 0x07082b13, 0x0c010212, // et.eu.un_740 pl.no.un_760 vi.no.it_665 da.en.sv_654
+ // [6f30]
+ 0x05010614, 0x112521ad, 0x0c06350e, 0x1e3b3112, // de.en.fr_666 jw.eu.ro_643 zu.de.sv_555 az.so.ms_654
+ 0x31121005, 0x1b0e120c, 0x1e1c64ec, 0x0d3121a0, // lt.hu.az_333 hu.is.tr_543 lg.id.ms_644 jw.az.cs_322
+ 0x1c041eee, 0x011b0eee, 0x202855a0, 0x07281a0c, // ms.fi.id_422 is.tr.en_422 rw.sw.sq_322 tl.sw.it_543
+ 0x0b1153af, 0x2d002319, 0x0718645a, 0x0f002012, // ht.ro.es_655 ca.sk.un_750 lg.ga.it_553 sq.lv.un_640
+ // [6f40]
+ 0x110755af, 0x1800061b, 0x03230cee, 0x071328a9, // rw.it.ro_655 de.ga.un_770 sv.ca.nl_422 sw.et.it_544
+ 0x1e290fad, 0x27351fee, 0x3f000a14, 0x1f001819, // lv.sl.ms_643 cy.zu.gd_422 pt.af.un_660 ga.cy.un_750
+ 0x11003b14, 0x646b1b0c, 0x1e1c07a4, 0x0b230aa0, // so.ro.un_660 tr.ceb.lg_543 it.id.ms_433 pt.ca.es_322
+ 0x081f0e04, 0x0a070f12, 0x3100231b, 0x530b6ba0, // is.cy.no_332 lv.it.pt_654 ca.az.un_770 ceb.es.ht_322
+ // [6f50]
+ 0x55070509, 0x210c2bee, 0x071321a7, 0x6b005307, // fr.it.rw_444 vi.sv.jw_422 jw.et.it_532 ht.ceb.un_420
+ 0x100e0cad, 0x190a1809, 0x08005302, 0x080e010c, // sv.is.lt_643 ga.pt.gl_444 ht.no.un_220 en.is.no_543
+ 0x11071c07, 0x55211c09, 0x0b120a5a, 0x2d0d02ec, // id.it.ro_432 id.jw.rw_444 pt.hu.es_553 da.cs.sk_644
+ 0x5300250c, 0x2000091b, 0x08020c5a, 0x1c211e08, // eu.ht.un_530 pl.sq.un_770 sv.da.no_553 ms.jw.id_443
+ // [6f60]
+ 0x0c3518ec, 0x23002005, 0x0a003b11, 0x53002908, // ga.zu.sv_644 sq.ca.un_330 so.pt.un_630 sl.ht.un_430
+ 0x0a002514, 0x29096e0c, 0x190b18a4, 0x0a283b0c, // eu.pt.un_660 hmn.pl.sl_543 ga.es.gl_433 so.sw.pt_543
+ 0x35000212, 0x55002a29, 0x520a3b12, 0x06012b04, // da.zu.un_640 mt.rw.un_960 so.pt.ha_654 vi.en.de_332
+ 0x062301a7, 0x3f03230e, 0x18174a02, 0x6e00230d, // en.ca.de_532 ca.nl.af_555 yo.sr.ga_222 ca.hmn.un_540
+ // [6f70]
+ 0x173132ee, 0x171e1107, 0x1e521c11, 0x19000702, // bs.az.sr_422 ro.ms.sr_432 id.ha.ms_653 it.gl.un_220
+ 0x030f3fad, 0x03053f13, 0x29132dec, 0x04280dee, // af.lv.nl_643 af.fr.nl_665 sk.et.sl_644 cs.sw.fi_422
+ 0x35104aa7, 0x3b200f12, 0x1e00100c, 0x1b1f31ee, // yo.lt.zu_532 lv.sq.so_654 lt.ms.un_530 az.cy.tr_422
+ 0x05682813, 0x1f4a0e08, 0x110c07a0, 0x5200071b, // sw.ig.fr_665 is.yo.cy_443 it.sv.ro_322 it.ha.un_770
+ // [6f80]
+ 0x10000f05, 0x03000819, 0x120855a7, 0x022d080b, // lv.lt.un_330 no.nl.un_750 rw.no.hu_532 no.sk.da_542
+ 0x0e4a52a9, 0x32030eee, 0x10160707, 0x1b524a0d, // ha.yo.is_544 is.nl.bs_422 it.hr.lt_432 yo.ha.tr_554
+ 0x0c006b02, 0x061103ec, 0x13253b5a, 0x1c351eee, // ceb.sv.un_220 nl.ro.de_644 so.eu.et_553 ms.zu.id_422
+ 0x020c03a0, 0x0b0f25a0, 0x131c08ee, 0x4a3f68ec, // nl.sv.da_322 eu.lv.es_322 no.id.et_422 ig.af.yo_644
+ // [6f90]
+ 0x213b0213, 0x13002919, 0x230b080e, 0x2d2a4a07, // da.so.jw_665 sl.et.un_750 no.es.ca_555 yo.mt.sk_432
+ 0x0b006402, 0x28001f04, 0x0e136409, 0x21003119, // lg.es.un_220 cy.sw.un_320 lg.et.is_444 az.jw.un_750
+ 0x21313b13, 0x060d1fad, 0x6e002b09, 0x061b12af, // so.az.jw_665 cy.cs.de_643 vi.hmn.un_440 hu.tr.de_655
+ 0x3f1f35ee, 0x350231a0, 0x10043509, 0x0e081aee, // zu.cy.af_422 az.da.zu_322 zu.fi.lt_444 tl.no.is_422
+ // [6fa0]
+ 0x101235ee, 0x193b0a08, 0x0c042107, 0x52002102, // zu.hu.lt_422 pt.so.gl_443 jw.fi.sv_432 jw.ha.un_220
+ 0x3f0f050c, 0x050621ee, 0x32051707, 0x11071760, // fr.lv.af_543 jw.de.fr_422 sr.fr.bs_432 sr.bg.ro_664
+ 0x3b002d04, 0x04133b05, 0x0c1f08a0, 0x1c681e55, // sk.so.un_320 so.et.fi_333 no.cy.sv_322 ms.ig.id_442
+ 0x056421a7, 0x1b286b08, 0x0b231109, 0x1a6b520e, // jw.lg.fr_532 ceb.sw.tr_443 ro.ca.es_444 ha.ceb.tl_555
+ // [6fb0]
+ 0x20001911, 0x4a002108, 0x1e1c0da4, 0x04060e04, // gl.sq.un_630 jw.yo.un_430 cs.id.ms_433 is.de.fi_332
+ 0x0c00280c, 0x250f550d, 0x080229ee, 0x35642507, // sw.sv.un_530 rw.lv.eu_554 sl.da.no_422 eu.lg.zu_432
+ 0x3f020308, 0x1e08550c, 0x12052aa7, 0x351123a0, // nl.da.af_443 rw.no.ms_543 mt.fr.hu_532 ca.ro.zu_322
+ 0x112923ee, 0x00000101, 0x19090b12, 0x553220ad, // ca.sl.ro_422 en.un.un_200 es.pl.gl_654 sq.bs.rw_643
+ // [6fc0]
+ 0x0b00052b, 0x280f10ad, 0x531a1c0c, 0x1a6b1ca0, // fr.es.un_980 lt.lv.sw_643 id.tl.ht_543 id.ceb.tl_322
+ 0x1c0f1ea4, 0x17291aa9, 0x6b251aa9, 0x320929ee, // ms.lv.id_433 tl.sl.sr_544 tl.eu.ceb_544 sl.pl.bs_422
+ 0x0b050fec, 0x2b100faf, 0x3b6b64a6, 0x0e29100c, // lv.fr.es_644 lv.lt.vi_655 lg.ceb.so_521 lt.sl.is_543
+ 0x1a1204a9, 0x0b00210c, 0x0e0811ec, 0x033f68a0, // fi.hu.tl_544 jw.es.un_530 ro.no.is_644 ig.af.nl_322
+ // [6fd0]
+ 0x27522904, 0x1e1c53a4, 0x0452550c, 0x2d0d0ea4, // sl.ha.gd_332 ht.id.ms_433 rw.ha.fi_543 is.cs.sk_433
+ 0x32002504, 0x280e520d, 0x180a07a0, 0x19000908, // eu.bs.un_320 ha.is.sw_554 it.pt.ga_322 pl.gl.un_430
+ 0x311b52a7, 0x076b1fa4, 0x093507ee, 0x092152ad, // ha.tr.az_532 cy.ceb.it_433 it.zu.pl_422 ha.jw.pl_643
+ 0x08123b09, 0x2d0d53a4, 0x2d0603ee, 0x072805af, // so.hu.no_444 ht.cs.sk_433 nl.de.sk_422 fr.sw.it_655
+ // [6fe0]
+ 0x32171aa6, 0x190b35a4, 0x02002a02, 0x230b530c, // tl.sr.bs_521 zu.es.gl_433 mt.da.un_220 ht.es.ca_543
+ 0x681b0708, 0x0d092da7, 0x28000b09, 0x050108a0, // it.tr.ig_443 sk.pl.cs_532 es.sw.un_440 no.en.fr_322
+ 0x161118a7, 0x01530ca0, 0x0c09020e, 0x550b4aee, // ga.ro.hr_532 sv.ht.en_322 da.pl.sv_555 yo.es.rw_422
+ 0x0e000c20, 0x13002d22, 0x521155ad, 0x23002112, // sv.is.un_850 sk.et.un_870 rw.ro.ha_643 jw.ca.un_640
+ // [6ff0]
+ 0x166429a0, 0x08025502, 0x202b31ee, 0x1b310e60, // sl.lg.hr_322 rw.da.no_222 az.vi.sq_422 is.az.tr_664
+ 0x0c0d3f04, 0x21526407, 0x211020a4, 0x1f000e2a, // af.cs.sv_332 lg.ha.jw_432 sq.lt.jw_433 is.cy.un_970
+ 0x23110ead, 0x20006e04, 0x0c190a14, 0x1b0e2aad, // is.ro.ca_643 hmn.sq.un_320 pt.gl.sv_666 mt.is.tr_643
+ 0x192d0eb4, 0x0b230aad, 0x211f200c, 0x0e1f2009, // is.sk.gl_754 pt.ca.es_643 sq.cy.jw_543 sq.cy.is_444
+
+ // [7000]
+ 0x291317a0, 0x0b006b04, 0x53112012, 0x533f1c0b, // sr.et.sl_322 ceb.es.un_320 sq.ro.ht_654 id.af.ht_542
+ 0x06072aa9, 0x281a350d, 0x190b07ec, 0x321707ee, // mt.it.de_544 zu.tl.sw_554 it.es.gl_644 it.sr.bs_422
+ 0x110f25ad, 0x2835210e, 0x080e3fa0, 0x530325ad, // eu.lv.ro_643 jw.zu.sw_555 af.is.no_322 eu.nl.ht_643
+ 0x3b002121, 0x28183b5a, 0x6e006813, 0x0c192504, // jw.so.un_860 so.ga.sw_553 ig.hmn.un_650 eu.gl.sv_332
+ // [7010]
+ 0x0a00232b, 0x64550c0d, 0x05002102, 0x08250c08, // ca.pt.un_980 sv.rw.lg_554 jw.fr.un_220 sv.eu.no_443
+ 0x190a250d, 0x28182008, 0x2a0a6b12, 0x0c0806ee, // eu.pt.gl_554 sq.ga.sw_443 ceb.pt.mt_654 de.no.sv_422
+ 0x041a1fad, 0x181a27ad, 0x1a0835ee, 0x321016a4, // cy.tl.fi_643 gd.tl.ga_643 zu.no.tl_422 hr.lt.bs_433
+ 0x351f2812, 0x230f02a4, 0x1e000b04, 0x273518af, // sw.cy.zu_654 da.lv.ca_433 es.ms.un_320 ga.zu.gd_655
+ // [7020]
+ 0x6400290b, 0x2855010c, 0x09122a0e, 0x16292da6, // sl.lg.un_520 en.rw.sw_543 mt.hu.pl_555 sk.sl.hr_521
+ 0x20001214, 0x063f08ac, 0x196401a7, 0x2b1c01a6, // hu.sq.un_660 no.af.de_632 en.lg.gl_532 en.id.vi_521
+ 0x0812290c, 0x2d090205, 0x03000405, 0x041e1902, // sl.hu.no_543 da.pl.sk_333 fi.nl.un_330 gl.ms.fi_222
+ 0x25091208, 0x250d1ba4, 0x2a00201b, 0x521331ee, // hu.pl.eu_443 tr.cs.eu_433 sq.mt.un_770 az.et.ha_422
+ // [7030] --- double_langprob_start=7037 ---
+ 0x2a00640e, 0x5368350e, 0x030d200c, 0x20000e07, // lg.mt.un_550 zu.ig.ht_555 sq.cs.nl_543 is.sq.un_420
+ 0x0a112d04, 0x080635a0, 0x286b11a0, // sk.ro.pt_332 zu.de.no_322 ro.ceb.sw_322
+ //
+ };
+
+// COMPILE_ASSERT(28727 <= 32768, k_indirectbits_too_small);
+
+extern const CLD2TableSummary kQuad_obj = {
+ kQuadChrome0122_16,
+ kQuadChrome0122_16Ind,
+ kQuadChrome0122_16SizeOne,
+ kQuadChrome0122_16Size,
+ kQuadChrome0122_16KeyMask,
+ kQuadChrome0122_16BuildDate,
+ kQuadChrome0122_16RecognizedLangScripts,
+};
+
+static const uint32 kQuadChrome0122_16_2Size = 8192; // Bucket count
+static const uint32 kQuadChrome0122_16_2KeyMask = 0xffffe000; // Mask hash key
+
+static const IndirectProbBucket4 kQuadChrome0122_16_2[kQuadChrome0122_16_2Size] = {
+ // hash_indirect[4], tokens[4] in UTF-8
+ {{0x6563a002,0x6443a003,0x6fdfa004,0x2d932005}}, // [000] kenh, huni, pêch, _foxe_,
+ {{0x63bbc006,0x69db8007,0x109b0008,0x764f2009}}, // hiun, _akue, _סביב, önyö,
+ {{0x7afce00a,0xf536e00b,0x25a9400c,0x6d59c00d}}, // žrtv, שטער_, bhal_, _obwa,
+ {{0x7ddec00e,0x6e24000f,0xeae66010,0x3207e011}}, // pësi, _kwib, _करीत_, _muny_,
+ {{0xddc42012,0x99984013,0x63bbc014,0xa91d8015}}, // _mriž, strų_, diun, _požr,
+ {{0x0877000b,0x2451a016,0x77644017,0x98a32018}}, // יענט_, _bơm_, leix, lajā_,
+ {{0x26cd8019,0xddc2e01a,0x7c85601b,0x2ca0800a}}, // _exeo_, _prož, _луѓе, šid_,
+ {{0x7c66801c,0x7d04001d,0x7416801e,0xdb0d401f}}, // _پارل, _ngis, _نورا, riaç,
+ {{0xe8d9e016,0x2007e020,0xa3d74021,0x44236022}}, // _đỡ_, _auni_, ाकत_, _zwj_,
+ {{0x27ed8023,0x321a6024,0x44248025,0xbbd0e026}}, // _zhen_, oppy_, _iwm_, सकेक,
+ {{0x63bae027,0x6ed62026,0x1ddfa028,0x59dfa029}}, // ritn, _मुलु, _पीटत, _पीटर,
+ {{0x2007e02a,0x7c2f002b,0xac19002c,0x2419002d}}, // _duni_, tscr, лому_, ломы_,
+ {{0x7528a02e,0xdb03a02f,0x98a24030,0x229cc031}}, // _kedz, cinó, _bekč_, _dákú_,
+ {{0xd83f8032,0x53d6a033,0x7bd8a034,0x7d040035}}, // _účet_, धविश, novu, _egis,
+ {{0x25f34010,0x212b0036,0x63a64037,0x2007e038}}, // ंगली_, pach_, _blkn, _guni_,
+ {{0x6563a039,0x7afdc03a,0x291ee03b,0xdcfc603c}}, // zenh, ľstv, lbta_, _sorğ,
+ {{0x6b8d403d,0x69d7603e,0x6443a03f,0xfe09e016}}, // [010] rjag, toxe, zuni, _tắc_,
+ {{0xa3e6e040,0x67c1c041,0x69db8042,0x0a51e043}}, // पति_, rēji, _skue, _بھیج,
+ {{0x7ddde044,0x7bd8a045,0xa3bc2046,0x7ff42047}}, // mèst, dovu, _आगत_, _بسيا,
+ {{0x44236048,0xdfc6c049,0x67c8c04a,0xb0be604b}}, // _vwj_, _ري_, dějo, ्थजग,
+ {{0x6727604c,0xed59c04d,0x3ce00022,0xd6ab8049}}, // _tejj, niže_, _xyiv_, خدام_,
+ {{0x672d404e,0xa349a04f,0x20090037,0x3e176050}}, // laaj, узка_, _muai_, _نظرس,
+ {{0x212ce051,0x3f9fc052,0x31582053,0x6d5aa030}}, // dadh_, tkuu_, ייגן_, _dbta,
+ {{0x69c98054,0x69cae055,0xdfd48056,0x62804057}}, // _ujee, _ajfe, _досы, _ámoc,
+ {{0x7bdd4058,0x3f9fc054,0x64456059,0x69d8a05a}}, // _mksu, rkuu_, luhi, bove,
+ {{0x4431205b,0xee3f005c,0x2475a041,0x3f9fc04e}}, // ksz_, drý_, _tām_, skuu_,
+ {{0x2ee0005d,0x59c7405e,0x4424805f,0xddc42060}}, // _syif_, रचार, _zwm_, _priž,
+ {{0x6d4bc057,0xdcfd4061,0x13a7e062,0x69d98063}}, // _ucga, _ansč, انگی_, nowe,
+ {{0x44312064,0x68e1a065,0x9c7ca066,0xda08c067}}, // esz_, _hyld, _vyče, _tỏa_,
+ {{0x2007e068,0x3171605d,0xa01b4069,0xb276c053}}, // _tuni_, _nazz_, _kröf, _לענג_,
+ {{0x443ee06a,0x98a32041,0x2365e06b,0xe73a406c}}, // crt_, tajā_, melj_, лев_,
+ {{0x6445606d,0x2a6d806e,0xdced406f,0x63bc2070}}, // duhi, _oseb_, adač, sirn,
+ {{0x46ea404d,0x32090071,0x69d8a072,0x65644073}}, // [020] удан_, _guay_, yove, reih,
+ {{0x65660074,0xe8f74075,0x44770076,0xe80ac077}}, // lekh, слу_, _פעיל_, _हंसा_,
+ {{0x75298078,0x386d8071,0x7bde2079,0xee3a607a}}, // _beez, _aser_, _ikpu, уне_,
+ {{0x29dc807b,0x06d1807c,0x6d984057,0x3a3fc07d}}, // vías_, ়েছি, _vías, krup_,
+ {{0xdb0e6057,0x7e56807e,0x6609807d,0x98a4c041}}, // _embá, стец, _cuek, kamā_,
+ {{0xa97a200b,0xe9df2049,0xdb1ae00e,0x25b8607f}}, // ראַכ, ltúr_, ritë, _umrl_,
+ {{0x8c436080,0x443e6081,0x6b840082,0xdb060064}}, // _нере, _ít_, _onig, nikó,
+ {{0xb800207c,0xdb1ae083,0x20024068,0xa814c084}}, // ্ঠিত_, mité, _jiki_, одиш,
+ {{0xfd0fc085,0x5ebb400b,0x6ab2e086,0x8f9b4087}}, // رجي_, ָזיק, ुप्र, טיקי,
+ {{0x67c1c041,0x69de2088,0xddcd2089,0x200a608a}}, // dēju, _nkpe, _krať, _kubi_,
+ {{0x2258e052,0x3860008b,0xcb04008c,0x4df8408d}}, // шины_, _apir_, रखंड_, ंगाई_,
+ {{0x6446008e,0x443ee08f,0xd2510049,0xa5bb6031}}, // fuki, rrt_, ونج_, _awól,
+ {{0xddc2e041,0xb5fb603e,0xeabf4090,0x27fc802e}}, // _esoš, _tráx, _ceùl_, ívny_,
+ {{0xa2c5c010,0x67c54013,0x61142091,0xfe37c00b}}, // ापर्, mėji, здру, _פרוי_,
+ {{0x200a6092,0x3d950093,0x5ba74094,0x71bbe095}}, // _nubi_, _микр, браз, _תצוג,
+ {{0x69de2096,0x2d8c2097,0x8aa7003b,0x21204098}}, // _ekpe, öder_, _грод, gbih_,
+ {{0x85b96099,0x63a2809a,0xe450409b,0xc1b9609c}}, // [030] алас_, dkon, فضل_, алах_,
+ {{0x200a609d,0x5a34409e,0xfe09e067,0x6440c049}}, // _bubi_, янут, _mắn_, irmi,
+ {{0x660ae09f,0x67298096,0x98afe04d,0x98b860a0}}, // _jufk, _peej, ćaće_, _barı_,
+ {{0x8f474052,0x443fc0a1,0x6b84e0a2,0x8afdc0a3}}, // _уход, yru_, _ɗiga, deřn,
+ {{0xd246c0a4,0x7ae2c0a5,0x317160a6,0x4425a0a7}}, // _تن_, _nyot, _tazz_, _swl_,
+ {{0xa2b860a8,0x37cf007c,0x63a760a9,0x65aa4066}}, // ्पश्, রতির, _uljn, _výhe,
+ {{0xa2c140aa,0x80da807c,0x443fc0ab,0x2903e0a9}}, // लपक्, _বৃদ্, wru_, žjak_,
+ {{0xa3e9404a,0xdb0bc0ac,0x2fc940ad,0x2a6d8048}}, // यति_, _omgå, nnag_, _tseb_,
+ {{0x320a6066,0xf1bf006f,0xfe09e067,0x6d4b80ae}}, // _zuby_, diá_, _cắn_, mgga,
+ {{0x200360af,0x61e440b0,0x6d5c605d,0x644600b1}}, // _jiji_, zlil, _xbra, vuki,
+ {{0x64472098,0x386000b2,0x6d4f40b3,0x4426c0b4}}, // juji, _spir_, _occa, _ewo_,
+ {{0x212b406f,0x2451e0b5,0x9f5860b6,0x8b67c0b7}}, // _nech_, rám_, _mirá_, _دائم,
+ {{0x69dbc0b8,0xa2e660b9,0x399c404a,0x25bfc0ba}}, // moue, _कर्ज_, _míst_, miul_,
+ {{0x656600bb,0x008620bc,0x63a280bd,0xb4fa80be}}, // sekh, олго, zkon, יפרי,
+ {{0x63bb80bf,0xa06a20c0,0x6603e0c1,0x2fcd8037}}, // _omun, шава_, _iink, _ojeg_,
+ {{0xdb01e049,0xc052e076,0x798400c2,0xe3bf0057}}, // chlá, _חזה_, _uniw, ciñ_,
+ {{0x69c9c0c3,0xb886c0c4,0xa3e60028,0x25e300c5}}, // [040] inee, mtíð, _बीए_, _टीटी_,
+ {{0x63a980c6,0x4427e0c7,0x660bc058,0x644720c8}}, // _alen, _kwn_, _mugk, buji,
+ {{0x7bdc20c9,0x7dd60009,0x200360ca,0x7e7d40cb}}, // moru, dász, _diji_, _ersp,
+ {{0x9f5860cc,0xec7a80cd,0x7bdae0ce,0x2d85a061}}, // _dirá_, ипе_, zotu, _anle_,
+ {{0x938aa0cf,0xeafa20d0,0xdb1c20d1,0x200b40d2}}, // иска_, _حرکت_, tirè, _guci_,
+ {{0x7a1520c2,0x200360d3,0xe3bf00d4,0x2b4fc0d5}}, // _wąte, _giji_, ziñ_, _acgc_,
+ {{0x6da680d6,0x9939a0d7,0x7a11a0d8,0x8fa680d9}}, // _дива, сячу_, _pāta, _даве,
+ {{0x68e400da,0xe29a80db,0x2002c0dc,0x7e9b200b}}, // _oyid, _каб_, öki_, יסצו,
+ {{0x672bc0dd,0x0b8ae0de,0x3ea040df,0x54ee80c2}}, // _degj, асни_, nyit_, _जरिए_,
+ {{0x81f6a0e0,0x672f00c2,0x7d1b604d,0xdb1d00e1}}, // _بہتر_, wacj, _đusk, misé,
+ {{0x63bb80e2,0x6ac640e3,0x6441e0e4,0x7bdae0e5}}, // _ymun, लपुर, grli, rotu,
+ {{0x75eda0e6,0xc486c0e7,0x27e000e8,0x7bdae0e9}}, // púzc, _млек, _ckin_, sotu,
+ {{0x7ae4000f,0x212b40ea,0xa2d6c0eb,0xf1bf002e}}, // _cyit, _rech_, _بيشت, riá_,
+ {{0x1dbca0ec,0x27e000ed,0x7bdc20ee,0x67efe065}}, // ्चित, _ekin_, goru, tøje,
+ {{0x644280ef,0x212b40f0,0x8438e049,0xa3bc20f1}}, // hroi, _pech_, _اكبر_, _आगर_,
+ {{0x02a30079,0x7e62c037,0x63a440f2,0x764280f3}}, // _apụọ, _kpop, gkin, kroy,
+ {{0x7aed00f4,0x98a32064,0x3f86c066,0x200ca0f5}}, // [050] _uzat, dają_, _mnou_, _ludi_,
+ {{0x200480f6,0x81c2c07c,0x601360f7,0x6568e0f8}}, // _aimi_, ৃতি_, _aħma, nedh,
+ {{0x212b40b3,0x200480f9,0x2367a07f,0x200b40a2}}, // _tech_, _bimi_, venj_, _wuci_,
+ {{0x644280fa,0x45d540fb,0x9f5860fc,0x27e680fd}}, // froi, _хомс, _virá_, glon_,
+ {{0x6448e0fe,0x0c2480ff,0x399c40c4,0xab5dc0c2}}, // hudi, дмін, _víst_, niże,
+ {{0x69dd0100,0x7b646101,0x6d4d4102,0xeb96a103}}, // hose, _отсе, lgaa, зиш_,
+ {{0xe739c104,0x3167a105,0x25bfc0ba,0x98ba2106}}, // бей_, renz_, tiul_, _yapı_,
+ {{0xfe6e4043,0x69dd0107,0x64576048,0x3f994108}}, // _جگہ_, jose, mtxi, _josu_,
+ {{0x69c9c109,0x6604010a,0x6442810b,0xa3db2033}}, // rnee, _riik, croi, ़वा_,
+ {{0xd6c6810c,0x27e00076,0x7ae3e10d,0x6e29810e}}, // _عملی, _skin_, _pynt, _iweb,
+ {{0x320d810f,0x6441e110,0xe9da0111,0x6448e112}}, // _huey_, urli, йко_, gudi,
+ {{0x7bcb8113,0x660bc114,0x6441e115,0x69cb8116}}, // nngu, _tugk, rrli, nnge,
+ {{0x68e3e117,0x920be118,0x2611a119,0xa3db611a}}, // _wynd, _सूरज_, _háo_, डका_,
+ {{0xdb15211b,0x660d0037,0x7c29811c,0x6604011d}}, // tizá, _duak, _mwer, _wiik,
+ {{0x386dc11e,0x8e83211f,0xcb12e095,0x69dd0120}}, // lwer_, нгре, _אלו_, bose,
+ {{0x3f986121,0x394dc122,0x7643a058,0x7bdc2123}}, // _soru_, nges_, irny, soru,
+ {{0x44290124,0xfe09e119,0x9f428125,0xdce3a126}}, // [060] _gwa_, _mắm_, _óké_, denč,
+ {{0x7bc0c127,0x69c1e128,0x64412129,0x98a5e12a}}, // zimu, nile, álic, cală_,
+ {{0x2ca0412b,0x44290030,0x7c29812c,0xddc9a12d}}, // syid_, _zwa_, _awer, _brež,
+ {{0x21024013,0x5f4620e0,0x6e29811d,0x9f404071}}, // _ačiū_, _بنگل, _bweb, rmió_,
+ {{0x212d8037,0xa3cbe010,0xff03812e,0x20048012}}, // _deeh_, रचा_, няшн, _vimi_,
+ {{0x46db212f,0x2d99400f,0x27e68130,0x201ee131}}, // _मुँह, _zose_, slon_, ypti_,
+ {{0x2d994132,0xdce3a133,0xdbc76134,0x7c364135}}, // _yose_, benč, töös, lsyr,
+ {{0x69c380e0,0x6d9cc049,0x6b99c136,0x75244108}}, // énet, _réas, _cowg, nbiz,
+ {{0x7643a137,0x66064138,0xdcebc041,0x7bc1e12a}}, // arny, _mikk, _pagā, filu,
+ {{0xa1c3a139,0xaf12c125,0xb21b0069,0x6c33813a}}, // _збуд, _aụtụ, lvæg, _أفلا,
+ {{0x442a613b,0x7bdd013c,0x69c0c13d,0x7988a00f}}, // _jwb_, tosu, pime, _indw,
+ {{0x65b0204e,0x6568e0eb,0x7524413e,0x6444413f}}, // _lähd, sedh, jbiz, krii,
+ {{0x98a5a041,0x67244140,0x76444141,0x63aae057}}, // _ielā_, dbij, jriy, _tlfn,
+ {{0x65b0204e,0x44446142,0x77ade057,0x24800143}}, // _nähd, kr_, _púxe, _drim_,
+ {{0x66064144,0x69cb8145,0x69dd0146,0x31694108}}, // _bikk, ynge, pose, teaz_,
+ {{0x44446147,0x2ba9a148,0x63a60149,0x1994414a}}, // dr_, _कत्थ, ckkn, нася,
+ {{0xa2bea14b,0x113c0076,0xdb1d014c,0x9c194049}}, // [070] _वेस्, _התחל, rnsä, زياء_,
+ {{0x6443a14d,0x6d4d414e,0x7ae6414f,0x09c5804b}}, // yrni, sgaa, _dykt, लच्य,
+ {{0x7bcb8150,0x44446151,0x67244152,0x44386065}}, // tngu, gr_, bbij, _cvr_,
+ {{0x65608153,0x9f5ea0f9,0x69c28154,0x386dc155}}, // _cbmh, _dití_, gioe, ywer_,
+ {{0x78a12156,0xf8aea0e0,0x9f964076,0xd838a157}}, // älvf, یکم_, _חדרה_, _avč_,
+ {{0xdcfc603b,0x442a6048,0xfa780095,0xedf7c158}}, // _norė, _fwb_, פעות_, ुद्ध_,
+ {{0xd4986159,0xdb03e089,0x2006c15a,0x6280815b}}, // _ерт_, _plní, _bioi_, _ermo,
+ {{0xd8382066,0xcda98050,0x7c29815c,0xbc566066}}, // áči_, _بهره_, _twer, žšíc,
+ {{0xe1f9c013,0x2005a15d,0x6280815e,0xa3da815f}}, // rmų_, _tili_, _grmo, ठकर_,
+ {{0xdb1e2004,0x442a6022,0x644b8160,0x2d890161}}, // _empê, _ywb_, mugi, _anae_,
+ {{0xdb082162,0xfe09e016,0x799b8163,0x386dc095}}, // _élém, _tắm_, _kouw, swer_,
+ {{0x2006c067,0x76444164,0x69de6165,0x21e420cb}}, // _gioi_, yriy, zope, höht_,
+ {{0x644b8166,0xfd4ee088,0x69de6167,0x8db5e0ff}}, // nugi, _jenụ, yope, _оскі,
+ {{0xb5fb6168,0x44446169,0x2ca9e04a,0x3f89016a}}, // _spán, yr_, _řadu_, _enau_,
+ {{0x65b0216b,0x91e6416c,0x68e640ff,0x764b80ae}}, // _nähe, дове, _sykd, hugy,
+ {{0x2360016d,0x27e9416e,0x2fcdc16f,0xdb1c2019}}, // _ubij_, glan_, nneg_, mirí,
+ {{0x7bc28170,0x6e36403b,0x76444054,0x66076171}}, // [080] viou, usyb, uriy, _bijk,
+ {{0x76444172,0x200fc173,0x61e9c174,0x6aa28167}}, // rriy, _mugi_, hlel, vyof,
+ {{0x7bde6175,0x1d0a4176,0xb4db40f9,0x6444410a}}, // ropu, _мени_, _adàd, srii,
+ {{0x6b9b8177,0x2007e178,0x660f4179,0x29d7417a}}, // _coug, _lini_, _fuck, għad_,
+ {{0x2613e016,0x4422017b,0x69c3a17c,0x660f40cb}}, // _lão_, npk_, fine, _guck,
+ {{0x69c3817d,0x61fbc17e,0x443860ba,0x69c2817f}}, // éner, emul, _tvr_, sioe,
+ {{0xb1134125,0xa2bea180,0xe3b90181,0xa967a182}}, // _tụta, _वेश्, rsız_, фија_,
+ {{0xdb1c2183,0x200ee184,0x2240a06f,0x644b8185}}, // dirí, _rufi_, čik_, bugi,
+ {{0x212fc037,0x6281a186,0xdb1c6187,0x7bc3a188}}, // _degh_, _erlo, _amré, binu,
+ {{0x2613e016,0x26070028,0x7bc3a189,0x61fbc18a}}, // _bão_, _सूजी_, cinu, amul,
+ {{0x3178618b,0xdb1e218c,0x799c616f,0x61e8e18d}}, // _marz_, _impè, _morw, rldl,
+ {{0x61e9c18e,0x7ac6c18f,0x2d9e2190,0x200fc191}}, // clel, _осле, öten_, _fugi_,
+ {{0xd378a143,0x69cb6009,0x6ce4014f,0x79fbc076}}, // _kać_, égek, віре, _ולאח,
+ {{0x0cd4e192,0xe4c7c0eb,0xddc9e04a,0xa5c6c193}}, // _полю, _تصمی, kteř, bjóð,
+ {{0x224b0037,0x7bc08194,0x27e04037,0x63ad0194}}, // suck_, _ommu, foin_, _tlan,
+ {{0x60d5605d,0x22468195,0x6608a196,0xdb072052}}, // _rxzm, krok_, _lidk, kijö,
+ {{0x7777217a,0x79898197,0x24584052,0x645ae198}}, // [090] _taxx, _rnew, мать_, ltti,
+ {{0x644b8132,0xd378a199,0xf993619a,0x5c75219b}}, // vugi, _nać_, خبر_, _злот,
+ {{0x2458e026,0x5335419c,0x6b9b8012,0xc808e067}}, // ném_, _пент, _poug, _cởi_,
+ {{0xa295419d,0x68e76066,0x4421619e,0xe3c2419f}}, // _замі, _vyjd, rph_, tmış_,
+ {{0x61e0c1a0,0x9f5880d5,0x251a41a1,0xa3b62026}}, // doml, _auró_, _מורא, चोक_,
+ {{0xdb18a1a2,0x6562c1a3,0x6281a1a4,0xe3c24181}}, // tivá, _aboh, _prlo, rmış_,
+ {{0x6e2d01a5,0xc2e7a1a6,0x644b81a7,0x7d0d01a8}}, // _lwab, গেনি_, sugi, _ogas,
+ {{0xf41f4156,0xceb261a9,0x644601aa,0xdb1e21ab}}, // _träd_, _עין_, yrki, _empè,
+ {{0xf771e1ac,0x3ce90048,0x7bc3a1ad,0xcc178050}}, // هاد_, _nyav_, pinu, _آذرب,
+ {{0x6f0d000f,0x753520f7,0x2613e1ae,0xdb1c21af}}, // _agac, jazz, _pão_, tirí,
+ {{0x6ac801b0,0xddc601b1,0x7c2d01b2,0x65c60101}}, // रप्र, ебни, _awar, ебна,
+ {{0x9984c13a,0x8d7601b3,0x31794064,0xdb1c21af}}, // _للمو, راجا, _nasz_, rirí,
+ {{0xe9f9e1b4,0x80d9607c,0x443a21b5,0x23ae4069}}, // _енді_, ভেচ্, _svp_, _nýja_,
+ {{0x7d0d01b6,0x27e161b7,0x3cf54077,0x7bc561b8}}, // _egas, john_, ्धने_, jihu,
+ {{0x799d41b9,0x638321ba,0x4422005d,0x27e041bb}}, // _bosw, ыгра, spk_, toin_,
+ {{0xeb9741bc,0xed640066,0xe61a41bd,0x6283e0e6}}, // нис_, _drží_, зда_, _irno,
+ {{0x6d560132,0x765bc1be,0x2b400098,0x178660fb}}, // [0a0] _icya, ntuy, _idic_, нгам,
+ {{0x290d81bf,0xe1f2219a,0xa5bb60f9,0x7bc5600f}}, // _agea_, _حسب_, _atób, gihu,
+ {{0xb14681c0,0x75d36049,0xe1f241c1,0x65ab20cb}}, // енел, ديلا, _مسح_, _mühl,
+ {{0x80daa026,0xa2daa026,0x501b2076,0x765bc0ae}}, // _पुगे, _पुग्, _מוטו, ktuy,
+ {{0x7c2281c2,0x61e6417a,0x3ce90048,0xdb1e61c3}}, // upor, _ikkl, _xyav_, lipí,
+ {{0x798bc1c4,0x7bc601c5,0x442001c6,0x765ae052}}, // _angw, hiku, _mti_, ytty,
+ {{0x387240e2,0x412a81c7,0x765c21c8,0x442000f9}}, // lwyr_, _ново_, ntry, _lti_,
+ {{0xedc8e1c9,0x7bc081ca,0x7bc60167,0x2c26c0ff}}, // िच्छ, _ummu, jiku, ньог,
+ {{0xdb1e21cb,0xc7c6e1cc,0x387241cd,0xb88681ce}}, // _empé, есни, nwyr_, _blíž,
+ {{0x6282c1cf,0x9f588017,0x7abb4076,0x61e0c1d0}}, // _vroo, _turó_, פציו, soml,
+ {{0x61fe61d1,0xddcd21d2,0xf363e1d3,0x6e2d01d4}}, // impl, _praž, утын, _swab,
+ {{0xd469a13a,0x645c21d5,0xb4cca1d6,0x38cba050}}, // _تحكم_, dtri, रपी_, _عالی_,
+ {{0x753521d7,0xddcd21d8,0x65ab2105,0x21ef6105}}, // tazz, _vraž, _fühl, fühl_,
+ {{0x645c2105,0xb894e049,0x61e281d9,0x7c94e049}}, // ftri, _للتع, hool, _للتص,
+ {{0x645c21da,0x7bc2c05d,0x777aa1db,0x2d9ea14f}}, // gtri, _kmou, _matx, _mote_,
+ {{0x6e3ae1dc,0xdb18a1dd,0xdb1c21de,0x6dab01df}}, // mstb, livä, tirã, rƙas,
+ {{0x7c2441e0,0x3df521e1,0x656d41e2,0x717961e3}}, // [0b0] gpir, _изис, reah, мбар_,
+ {{0x201161e4,0x442d81e5,0x75f60026,0xa91d81e6}}, // _puzi_, _rwe_, ráze, _inži,
+ {{0x765d01e7,0x6e3ae1e8,0x22a741e9,0x6e208194}}, // ltsy, nstb, _bíkú_, _atmb,
+ {{0x27e161ea,0x660ae143,0xa5bb60f9,0xa03a200b}}, // sohn_, _lifk, _awót, _קערפ,
+ {{0x8a1841eb,0xaf12c088,0x539a4076,0x3f9ea1ec}}, // _пояс_, _rụpụ, _חירו, _botu_,
+ {{0x7ddd00e0,0x69c2c1ed,0x66098064,0xcb66a16c}}, // gész, _amoe, _wiek, ваше_,
+ {{0x69d56171,0xdb1c21ee,0x9e3561ef,0xdb0f0057}}, // _ijze, nirá, _шевч, nicó,
+ {{0xceb8e064,0x442d800f,0x68e36105,0x656f01f0}}, // dzę_, _twe_, ündu, jech,
+ {{0x443ce1f1,0x6448e1f2,0x212941f3,0x61ed41f4}}, // _avv_, erdi, ibah_, ilal,
+ {{0x7c3ae1f5,0x387fc1f6,0x4c85e1f7,0x2132005d}}, // fstr, rvur_, влов, _seyh_,
+ {{0xa91d8098,0x3869012a,0x657b81f8,0x7bc721f9}}, // _anži, _apar_, _kauh, diju,
+ {{0x2ca7a0c4,0xdb0f0057,0x799e21fa,0x656e21fb}}, // mynd_, dicó, _sopw, webh,
+ {{0x7c3ae1fc,0x6729c1fd,0xe56ee12a,0x657b81e7}}, // astr, mbej, _кз_, _mauh,
+ {{0x7c3ae1fe,0x7bc601ff,0x6f0f4057,0x6f1d4200}}, // bstr, siku, _agcc, _afsc,
+ {{0xed5a4201,0x7bc3e202,0x6449c203,0xcf92a087}}, // мов_, _imnu, lrei, סטל_,
+ {{0x765c209a,0xd00f0043,0x273c6026,0xb602c069}}, // rtry, صلہ_, kání_, _þátt,
+ {{0x7bc72204,0x6e3c6205,0x7e698206,0x6449c207}}, // [0c0] biju, _svrb, _npep, nrei,
+ {{0x61e3a208,0xa2b78209,0x7c24420a,0x7aebc013}}, // jonl, ्छन्, rpir, _mygt,
+ {{0xf1c6020b,0x442fc048,0x2d9e4004,0x27e7e05d}}, // _लगान, _lwg_, êtes_, _mknn_,
+ {{0x657aa20c,0x09d5e07c,0x248d420d,0x18a6820e}}, // _sath, _সীমা, _šeme_, каем,
+ {{0x61e3a20f,0xaca44088,0x777aa108,0x7c3bc143}}, // fonl, _kpọt, _patx, jsur,
+ {{0xdb0b8069,0x201aa1ae,0x35c84028,0x7649c210}}, // rigð, ípio_, _रगड़, drey,
+ {{0x2b586211,0x69c40212,0x6449c213,0xd04d00e8}}, // _icrc_, _omie, erei, _fyaɗ,
+ {{0xadf84064,0x2489c00a,0x44224214,0x61e44052}}, // ंगटन_, _šama_, _ktk_, noil,
+ {{0x7c3bc215,0x4422405d,0xa3b660a8,0xa806a0ba}}, // gsur, _jtk_, _जता_, _азал,
+ {{0xa2bb8216,0x21382217,0x27edc218,0x7aebc065}}, // _शेट्, jarh_, alen_, _dygt,
+ {{0x2bafa219,0x61e4404e,0x4256a21a,0x61ed421b}}, // जघरा, koil, _штат, ylal,
+ {{0x645d00ff,0x7649c069,0x67f8804a,0x75f8804a}}, // ttsi, brey, bíje, bíze,
+ {{0x6449c21c,0x1074021d,0x6282821e,0x200ca0e8}}, // crei, аляю, nvoo, _kidi_,
+ {{0x8f34e21f,0xfe6fe062,0x261761f6,0x65664220}}, // _секц, حدی_, _bħol_, _abkh,
+ {{0x2fcd20e4,0x53342221,0x645d0222,0x9f43200e}}, // đeg_, рест, stsi, lojë_,
+ {{0xa01b4156,0x442fc223,0x62816057,0x77b02156}}, // _fröl, _ywg_, _áloi, _växa,
+ {{0x657c61cd,0xfe70413a,0xdb0080e0,0x200b4224}}, // [0d0] _barh, يدك_, _elmú, _vici_,
+ {{0x660d0187,0x200ca225,0x39586153,0x4c952226}}, // _hiak, _nidi_, _ccrs_, _биос,
+ {{0x3958402e,0xdce40227,0x443ea228,0x7bc52229}}, // _šesť_, _iniġ, _nvt_, _imhu,
+ {{0xdb1ae22a,0x2fc7a22b,0x81458050,0x9f43200e}}, // litä, wing_, انشن, kojë_,
+ {{0x777c6054,0x657b8052,0x7c22c22c,0xdb072156}}, // _farx, _vauh, _ntor, shjä,
+ {{0xdb0760e0,0xdb1ae22d,0x7aebc22e,0x2fc94048}}, // _eljá, nitä, _rygt, liag_,
+ {{0x6e22c22f,0x6e95a230,0x2486c231,0xfc3f4232}}, // _atob, العا, _arom_, _hví_,
+ {{0x7ff5c233,0xfe738050,0x63a080e8,0x27edc234}}, // دستا, _پدر_, _gomn, ulen_,
+ {{0x7c3bc235,0x68ed0236,0x6f1bc237,0x2486c238}}, // usur, _nyad, rcuc, _crom_,
+ {{0x7d1bc239,0x200ca082,0xdddba23a,0x6d42c024}}, // scus, _gidi_, _fruš, _edoa,
+ {{0x27e90058,0xbb55c050,0x7c3bc23b,0x660bc0ae}}, // _bkan_, _مناب, ssur, _tigk,
+ {{0xdea3a043,0xaca3c096,0x6281e23c,0x973ca23d}}, // _میڈی, _atụm, rvlo, _voće,
+ {{0x61e5623e,0x2bc641b0,0x63ad423f,0x245c604a}}, // kohl, रोफा, dkan, zím_,
+ {{0x69c8e240,0x26198067,0x2d98e241,0x2bc60028}}, // cide, _mèo_, öret_, _लगवा,
+ {{0x61e4404e,0x61464242,0xb7dae0be,0x75d2e243}}, // toil, лева, _אקסי, _ديوا,
+ {{0xa6960244,0x777c6005,0x3a224037,0xaca3c125}}, // _број, _sarx, _ptkp_, _njịk,
+ {{0x6aa9c245,0x7aed0246,0x05666247,0xfe0a0119}}, // [0e0] nyef, _gyat, лван, _dắt_,
+ {{0x7c3ea248,0x4427a037,0xb17b0249,0xdb1e6057}}, // špri, kpn_, blås, fipá,
+ {{0x5336e00b,0x98a6224a,0xf539c06f,0xf536e00b}}, // רנען_, _сине, kať_, רטער_,
+ {{0xe29a424b,0x200ca24c,0xaf36824d,0x7bc8e24e}}, // _зам_, _sidi_, ارات, zidu,
+ {{0x61fb824f,0x07a6a250,0x8fa6a251,0xa2cb4026}}, // _dhul, ладн, ладе, _सेप्,
+ {{0x61fb8079,0x63a1a252,0x7bc8e253,0x44f3e254}}, // _ehul, _coln, xidu, _ọ_,
+ {{0xc9532095,0xdca36255,0x61fb8256,0xa5bb60c2}}, // צמה_, _лари, _fhul, _twór,
+ {{0x2d804257,0x9f43200e,0x200ca258,0x6738a259}}, // ndie_, tojë_, _widi_, savj,
+ {{0x27e6825a,0x8556a25b,0x6602825c,0x35b4e25d}}, // moon_, _مخاط, mmok, ибир,
+ {{0x63ad425e,0x104ae1ef,0x237fc143,0x27e9025f}}, // zkan, нями_, aduj_, _skan_,
+ {{0xa2cb4260,0xb603a261,0x2139000e,0xe81a80c2}}, // _सेन्, íšen, tash_, _फंडा_,
+ {{0x7a152064,0x69c9c171,0xe45f004e,0xa01b4156}}, // _wątk, ciee, ttö_, _dröj,
+ {{0xb21b422e,0x395a2030,0x62868012,0x44f3e031}}, // _præm, _bcps_, _škoj, _ẹ_,
+ {{0x6d564025,0xa01b4262,0x21390263,0x69c64264}}, // egya, _fröj, sash_, _amke,
+ {{0x753ae265,0x31712266,0x6e24003c,0x66028267}}, // matz, rezz_, _etib, kmok,
+ {{0x753560f7,0x65ab2268,0xb5a44013,0x29120194}}, // _sezz, _mühi, бруй, _ggya_,
+ {{0xec0a0067,0x443ee269,0x4423626a,0x7c3e626b}}, // [0f0] _vết_, lst_, _stj_, espr,
+ {{0x60d3e041,0x7e6d026c,0x2489026d,0x673ae26e}}, // ņemš, _ipap, _iram_, natj,
+ {{0x5695226f,0xb87b4270,0x76b3e03c,0x63a1a064}}, // _тант, _oxíg, təyə, _roln,
+ {{0x68ee6271,0x628441b4,0x61e56272,0xa857e076}}, // _dybd, bvio, sohl, ריאה_,
+ {{0x443ee105,0x2369000e,0xfc3f0273,0x3cf300c2}}, // hst_, _mbaj_, nsí_, _घरों_,
+ {{0x61e72274,0x3a248057,0x2fc6c0ae,0x69d52105}}, // nojl, _dtmp_, _amog_, nnze,
+ {{0xa91dc098,0x9f6b80db,0x7bc9c275,0x3f91600f}}, // hdže, _праз_, tieu, _inzu_,
+ {{0x13d220ec,0xe045c276,0x629a2052,0x31b8c009}}, // _सद्भ, ании, ätoi, _nézd_,
+ {{0x984881e1,0x6446c03b,0xdb18e277,0x7bcb8278}}, // _бяха_, škin, _omvä, migu,
+ {{0x4465e279,0x237f8223,0x753ae27a,0x62898037}}, // авов, _hauj_, gatz, _ireo,
+ {{0x2489027b,0x232a027c,0x28c7427d,0x69c9c27e}}, // _aram_, кони_, _रेसि, piee,
+ {{0x888aa00b,0xdb0d027f,0xb4e5e031,0xd7060280}}, // _שרײַ, _amañ, _alàà, изни,
+ {{0x67d20041,0x753ae108,0x38bbe0f9,0x777f0227}}, // nāji, batz, _báré_, _daqx,
+ {{0x69cb8281,0x753bc282,0xdfd22049,0x443ee283}}, // hige, lauz, زيز_, bst_,
+ {{0x69cb8284,0x6da64052,0xac16e1e3,0x7bcb8285}}, // kige, аива, _коју_, kigu,
+ {{0xa92641d3,0x6d5b8286,0xa5264056,0x7bcaa287}}, // рдал, _ncua, рмад, zifu,
+ {{0x69cb8288,0x660f4289,0x7c29c28a,0xb425428b}}, // [100] dige, _dick, nper, معلو,
+ {{0x6441a04a,0x7c29c28c,0xcb12e00b,0x1e3b0076}}, // _ovli, iper, ַלד_, _שגיא,
+ {{0x386d828d,0x63a3e28e,0x644d428f,0xb607a041}}, // _aper_, _monn, arai, _mašī,
+ {{0x1a2a80ff,0xb4e66290,0x317ea105,0xaca46291}}, // вжди_, _पडी_, _satz_, _nwụr,
+ {{0xdce1a03b,0x7e7b8009,0xa2ca2292,0x2e3c60f9}}, // _kalė, _csup, _हेत्, _báfá_,
+ {{0x98a4804a,0x66028293,0x973ca294,0x2fc7e1e7}}, // _země_, smok, _boća, _nmng_,
+ {{0x7880a295,0x69d64296,0x03968297,0xdca66298}}, // dává, onye, _края, _тави,
+ {{0xef18a041,0x6288a299,0x77b84005,0x2fc7e0ae}}, // _daļa_, _vrdo, _fíxe, _amng_,
+ {{0x7c29c29a,0xb997c052,0x443fc1e7,0xbcfb029b}}, // gper, рвых_, gsu_, gfél,
+ {{0x200fc069,0xed59c29c,0x237f805d,0x6b82829d}}, // _eigi_, dižu_, _zauj_, ldog,
+ {{0x443fc29e,0xd378e29f,0x63b602a0,0x63a402a1}}, // asu_, noći_, _blyn, _boin,
+ {{0x224dc2a2,0x3f80001f,0xaca46079,0x31c702a3}}, // brek_, _caiu_, _gwụr, рсов,
+ {{0xdcfc62a4,0xddcd6089,0x6d5c62a5,0x23b542a6}}, // _barċ, braň, _ocra, _nåja_,
+ {{0x394d2105,0x6442c2a7,0x261c22a8,0x7bcb82a9}}, // ßes_, _ivoi, _bío_, zigu,
+ {{0x03c742aa,0x94a822ab,0xb87b4009,0x660f42ac}}, // асам, атта_, _nyír, _pick,
+ {{0x798082ad,0x4255e043,0xd5b842ae,0x5b1542af}}, // _namw, _انٹر, асу_, смат,
+ {{0x752d42b0,0x753d02b1,0x6d412069,0x7bcb80fa}}, // [110] rbaz, masz, ólas, vigu,
+ {{0xa91d803b,0x261c22b2,0x69cb82b3,0xa2cb42b4}}, // _maže, _fío_, wige, _सेण्,
+ {{0x644d42b5,0x7c29c2b6,0x660f42b7,0x2d81600e}}, // srai, zper, _tick, rdhe_,
+ {{0x67d202b8,0x798282b9,0x237f8022,0x48fe0026}}, // tāji, gdow, _qauj_, रेको_,
+ {{0xe6cba069,0x201862ba,0x27e942bb,0xb21b00c4}}, // _íbúð, _euri_, moan_, kvæm,
+ {{0x2d8122bc,0x6722c1e9,0x69cd41cd,0x85e8a0ff}}, // _jahe_, _afoj, liae, адів_,
+ {{0x38754106,0x0bb82076,0x69cb82bd,0x7c29c2be}}, // şarı_, שלום_, pige, wper,
+ {{0x27e7a0d1,0x673c22bf,0x2139400e,0x656982c0}}, // sonn_, zarj, _jesh_, _ubeh,
+ {{0x6440c2c1,0x628ae1cd,0x7c29c0d5,0x753bc2c2}}, // gsmi, _arfo, uper, rauz,
+ {{0xaca34088,0x4426c2c3,0x67f0604e,0x61e8e2c4}}, // _adịb, _dto_, täji, godl,
+ {{0x200fc2c5,0x2bc480a8,0x2d92005d,0x2cace1cd}}, // _tigi_, लोवा, _pnye_, gydd_,
+ {{0x673d00ff,0x2139400e,0xed5702c6,0x753d0009}}, // gasj, _nesh_, бою_, gasz,
+ {{0x7bcd42c7,0x644fc2c8,0xa91d82c9,0x98a041e4}}, // diau, ácid, _gaže, mbić_,
+ {{0xa2c242ca,0x64a622cb,0xf77f01de,0xdca2c2cc}}, // _रेग्, бага, boço_, _хаши,
+ {{0xe7e442cd,0xa91d82ce,0x290c22cf,0x6813e0ba}}, // ककला_, _zaže, äda_, _pădu,
+ {{0x224042d0,0x69d76057,0x2d8322be,0x3d2840eb}}, // tsik_, enxe, adje_, شتری_,
+ {{0x67f60066,0x644e22d1,0x27f242d2,0x07a342d3}}, // [120] nájm, srbi, rlyn_, чатн,
+ {{0x27ff80f9,0x25a5a2d4,0x044622d5,0x6ee12079}}, // _ahun_, _noll_, _леон, _ịbel,
+ {{0x2bb96010,0x7bcd403b,0x0a4a6052,0xb5fb60f9}}, // _इतरा, biau, узей_, _apás,
+ {{0x628bc2d6,0x7e6282d7,0xb73a813a,0xa3bc4086}}, // _orgo, ttop, اثاء_, _अति_,
+ {{0x6f156012,0x95caa2d8,0x27ff82d9,0x25a5a2da}}, // _fgzc, лука_, _dhun_, _boll_,
+ {{0x3f8242db,0x69c982dc,0x6d5c62dd,0x38cba050}}, // _haku_, _emee, _ucra, گاهی_,
+ {{0xdfd2813a,0x9c87006f,0x37cde07c,0x6b81a2de}}, // _فيس_, _kočí, রকার, _dalg,
+ {{0xdbf10026,0xb8ce807c,0xdb1e62df,0x77b5c057}}, // _příb, _ওই_, sipä, _páxa,
+ {{0x1a9a400b,0x7bce22e0,0x201a22e1,0x29cd22e2}}, // ריִע, kibu, _lupi_, džan_,
+ {{0x1543a2e3,0xc07b40be,0x25a5a0fd,0xe7d6e07c}}, // _неум, נטיש, _goll_, হত্য,
+ {{0x644402e4,0x96f8e1fc,0xc986c12a,0x5186c2e5}}, // _avii, рент_, _гули, _гула,
+ {{0x224082d1,0x6b844171,0x25a5a105,0x78a12156}}, // šiku_, jdig, _zoll_, älvm,
+ {{0x644282e6,0x8b03a04a,0xa3e5c077,0xddc4206f}}, // nsoi, ěřen, पकर_, _spiš,
+ {{0x6b8442e7,0xf1bf6057,0x7bce22e8,0x673d02e9}}, // edig, _atán_, gibu, sasj,
+ {{0xdb01a2ea,0xa91dc00a,0x75f8806f,0x956200a2}}, // _polé, ldža, vízn, _imمn,
+ {{0x5c0742eb,0x2fc902ec,0x2011620f,0xb5fb21af}}, // сяга, _umag_, _sizi_, ctán,
+ {{0x656d02ed,0x644002ee,0x69cf02ef,0x953962f0}}, // [130] _ibah, ámit, mice, изат_,
+ {{0x27e942f1,0xb5fb60f9,0x4427e05d,0x27374067}}, // soan_, _apár, _ytn_, ần_,
+ {{0xa63561b4,0x201162f2,0x78ad42f3,0x7a3f42f4}}, // інді, _vizi_, syav, _bàtá,
+ {{0x442902f5,0x69d8a2f6,0xb17b42f7,0x628d02f8}}, // _kta_, enve, _sjåf, _krao,
+ {{0xa91dc2f9,0x7ae4a121,0x6d5982fa,0x8c45807a}}, // jdža, şitl, rgwa, целе,
+ {{0x6e2d42fb,0x7dc9200a,0x3e61a0e0,0x661aa2fc}}, // mpab, džsk, tót_, _butk,
+ {{0x27ff82fd,0x79956105,0x6605c2fe,0x6b82c2ff}}, // _thun_, _inzw, опка, _baog,
+ {{0xaba9c14a,0x21f90089,0x63a76300,0x69cf0301}}, // рвиз_, dého_, _hojn, jice,
+ {{0x61e9c302,0x6b81a303,0x2d836304,0x0d99e099}}, // roel, _talg, _kaje_, атны_,
+ {{0x69ce2305,0xfc45e05c,0xdcf8e041,0x75240024}}, // xibe, žíme_, _savā, _sfiz,
+ {{0x2fcdc306,0x63bbc1fb,0xdca5e307,0xa5bb6031}}, // pieg_, phun, цани, _atók,
+ {{0x4e1d8308,0x2a7dc05f,0x628d0309,0x65bfc0f9}}, // _पढाई_, _tswb_, _brao, _dàhù,
+ {{0x333fc162,0x6443a30a,0x661b8046,0x3e45a018}}, // maux_, lsni, _juuk, lēt_,
+ {{0x25b940ae,0xab29e30b,0x7c2d430c,0x3f82430d}}, // _alsl_, _тока_, dpar, _saku_,
+ {{0x03a6030e,0x6b83e30f,0x213b017b,0x7c2d4310}}, // _мико, _iang, _feqh_, epar,
+ {{0x6b83e311,0x452a4312,0xbebb000e,0x628d0256}}, // _hang, ржан_, rbër, _frao,
+ {{0x7c2d4313,0x61eb8314,0x0dca6056,0xdb068315}}, // [140] gpar, dogl, алай_, ümün,
+ {{0x79840316,0x7c24c13a,0xe81d8033,0xdb0e6317}}, // _haiw, _éire, _पढ़ा_, _albá,
+ {{0x3f6a4318,0x65b5c261,0x3f824319,0xe5a6431a}}, // _кино_, _náho, _taku_, _дини,
+ {{0x673aa31b,0x2d824286,0x3e45a018,0xb5fb2049}}, // _setj, _uake_, dēt_, htál,
+ {{0x7c2d4090,0x661aa31c,0x6d5bc1e2,0x7a4061e9}}, // cpar, _sutk, dgua, _bátá,
+ {{0x7bcbc1cd,0x26c20108,0x7983e0a2,0x1c3982cb}}, // _amgu, izko_, _nanw, сяць_,
+ {{0x442dc31d,0xdb03e31e,0x69d8a31f,0xc7b8c320}}, // epe_, _foné, rnve, _luđe_,
+ {{0x938ac084,0x442a6321,0xdb008322,0x466ac323}}, // рска_, _itb_, _homí, аром_,
+ {{0x2d83600f,0x63a28324,0x3f82c06f,0x753c6325}}, // _yaje_, mjon, ľku_, _kerz,
+ {{0x21f90326,0x628e6066,0x65b020cb,0x270ee03c}}, // tého_, _krbo, _fähi, tən_,
+ {{0x7983e327,0x3e45a041,0xa2ca2328,0x656d00ae}}, // _danw, cēt_, _हेल्, _pbah,
+ {{0x661c6329,0x3f86832a,0x2d84832b,0x6458a32c}}, // _murk, ndou_, _jame_, muvi,
+ {{0x4444632d,0x28c50260,0x6015a03b,0x395f0018}}, // hs_, _लेखि, _išma, ļus_,
+ {{0x9f5a20d1,0x7bcae0bb,0x65c0e031,0x3866832e}}, // _sipò_, _umfu, _dáhù, itor_,
+ {{0x7bcf032f,0x442a614f,0x6e2dc17a,0x661c6330}}, // picu, _ntb_, _ħabi, _nurk,
+ {{0x5f944331,0x2d9e2156,0x65b5c1ce,0x44204332}}, // мист, öter_, _náhl, fqi_,
+ {{0x31694333,0x1af1a07c,0x545541e1,0xf1bf0049}}, // [150] rfaz_, _চলছে_, яват, mhá_,
+ {{0x6b840334,0x6602c07d,0x7bdae069,0x249d805f}}, // _zaig, _lhok, nntu, vxwm_,
+ {{0x2d83606e,0x64444335,0xe7198049,0xdb03e2a8}}, // _vaje_, asii, ميات_, _poné,
+ {{0x661c6336,0x248d81ab,0x127a200b,0x05b94043}}, // _durk, _prem_, _פארע, _اگست_,
+ {{0x628e6337,0xcb9a4095,0xa5f9a338,0x7643a037}}, // _erbo, _הסרט, беду_, wsny,
+ {{0xd6580076,0x79852339,0xd378e33a,0x6602c33b}}, // קיות_, _jahw, jeć_, _ahok,
+ {{0x6280833c,0x0577833d,0x629820fa,0x8d77833e}}, // _esmo, _وارد, _àvot, _وارا,
+ {{0x44394223,0xee39c33f,0x7bdae340,0x02a30079}}, // _kws_, бни_, entu, _awọọ,
+ {{0x7643a341,0x6e298342,0xd2518343,0x673d4344}}, // ssny, _uteb, _سنا_, _mesj,
+ {{0x79852058,0xd131a13a,0x77bcc057,0x69cd00fd}}, // _nahw, _أما_, _réxe, _amae,
+ {{0x9ee9a28b,0x44204345,0xa7fd0181,0x1815e046}}, // _افضل_, zqi_, rtın, णदेव_,
+ {{0x64444054,0x2c640346,0x628f4019,0x39404048}}, // ysii, töd_, _orco, xais_,
+ {{0x4279800b,0x661d4347,0x7bdae048,0x3a2f805d}}, // זאָג, _nusk, bntu, npgp_,
+ {{0x66156348,0x44446349,0x6b83e34a,0x7bcd034b}}, // _nizk, ys_, _uang, _emau,
+ {{0x661d434c,0xf1b9a2d1,0x656f4020,0xb5fb207b}}, // _ausk, _guše_, _bbch, ctám,
+ {{0x9c26434d,0xc7b8c0e4,0x661d409a,0x442a604d}}, // здад, _tuđe_, _busk, _rtb_,
+ {{0x753c634e,0xdee3434f,0x673d4350,0x2b404351}}, // [160] _perz, еоти, _desj, raic_,
+ {{0x39420352,0x661c6353,0xb4b80354,0x6445600e}}, // laks_, _purk, चनी_, fshi,
+ {{0xdb0d0355,0x63a98356,0x27edc357,0x39404358}}, // _plaç, _boen, koen_, pais_,
+ {{0xe1ff2359,0x2009412a,0x7644435a,0x80cd835b}}, // ntón_, cmai_, psiy, _देहे,
+ {{0x7bc3800e,0x628f8057,0x69dae35c,0x29cd2143}}, // ënua, _ácol, ynte, džaj_,
+ {{0x661e235d,0x6d40c35e,0x68f60052,0x60c2835f}}, // _kupk, wama, _myyd, rzom,
+ {{0x66040265,0x3f85a360,0x38c8a0eb,0x629a6361}}, // _ohik, _dalu_, غاتی_, _štof,
+ {{0x3942010a,0xdef9a362,0x4439405f,0xdcfc6018}}, // jaks_, цыі_, _yws_, _garā,
+ {{0x44394223,0x6f02a0f9,0x442b4363,0x39420046}}, // _xws_, _bíbí, _xtc_, daks_,
+ {{0x6ce6e013,0x63a98171,0xe1ff2019,0x273ac119}}, // зіме, _zoen, etón_, ẫn_,
+ {{0x61ee2364,0x2009403b,0x27edc365,0xd378e320}}, // dobl, ymai_, boen_, reć_,
+ {{0x7bce603e,0xba9ba076,0x629a60fa,0x4efba0be}}, // _lmbu, _לסבי, _àtou, _פלאג,
+ {{0xbcfb4366,0x753d4367,0x5c072368,0x201ea229}}, // _océa, _resz, зява, _huti_,
+ {{0x673d4369,0xa91ca02e,0x9f5dc1e9,0x9178c031}}, // _sesj, _maľo, _fiwó_, _fẹ́_,
+ {{0x657ae36a,0x6445636b,0x6842e031,0x2cb3004a}}, // meth, xshi, _dídá, _řadě_,
+ {{0x60c440cb,0x2246836c,0x3209436d,0x2d87a36e}}, // fzim, ksok_, umay_, zdne_,
+ {{0x2009436f,0x44394370,0x765ae371,0x201ea372}}, // [170] rmai_, _qws_, luty, _luti_,
+ {{0x27edc373,0x63bb8026,0x63a98374,0x673f000e}}, // zoen_, _slun, _soen, _heqj,
+ {{0xb4d76077,0x752560cb,0x7bdd0375,0x27e4202e}}, // ापे_, rchz, onsu, émne_,
+ {{0x31358376,0x442b4004,0x64456377,0x44394048}}, // федр, _ttc_, rshi, _tws_,
+ {{0x645ae378,0x657ae379,0x6445637a,0x3f99437b}}, // huti, keth, sshi, _insu_,
+ {{0x9f47e06f,0x321ea064,0x628f8005,0x201ea37c}}, // _okná_, _buty_, _ácom, _buti_,
+ {{0xe739c04e,0x213ea37d,0x3f86c0ca,0xb7b5c119}}, // оей_, _deth_, _baou_, _dụn,
+ {{0x2fdf82f8,0x98b8212a,0x3f89437e,0x9605e37f}}, // _ujug_, lară_, ndau_, रष्ट_,
+ {{0x3f85a380,0x3942010a,0x6b876381,0x4439c382}}, // _talu_, vaks_, _kajg, ès_,
+ {{0x69c0c00a,0x321ea0a2,0x98b820ba,0x0c25e12a}}, // ahme, _futy_, nară_, ммон,
+ {{0x3cf0a05c,0x38694383,0x256fa0b0,0x656d4384}}, // čové_, htar_, rılı_, kfah,
+ {{0x7c3b8385,0xb1134125,0xdb1aa157,0x7a1c2361}}, // _kwur, _mụba, _altè, _učti,
+ {{0x201ea2e2,0x657ae386,0xdb008387,0x2b4d805d}}, // _zuti_, beth, _komá, _mdec_,
+ {{0x6b89c388,0x63bd4171,0x657ae2f8,0x6e2d0049}}, // ldeg, _alsn, ceth, _dtab,
+ {{0x2c87204a,0x6604000e,0xe2f820e0,0x98ba6389}}, // vídá_, _thik, سورڈ_, kapā_,
+ {{0x60db438a,0x7989c38b,0x442d838c,0x3f87e38d}}, // _žuma, ndew, _ote_, _hanu_,
+ {{0xa3af438e,0x41aa638f,0x442d8390,0x3a2d6391}}, // [180] _कवर_, ован_, _nte_, _čepa_,
+ {{0xdb008392,0x2d87e393,0x3f8641ce,0x7bc1e394}}, // _romà, _jane_, žou_, nhlu,
+ {{0xe1f9c03b,0x64498395,0x7c3b82f4,0xf8bf0396}}, // klų_, _avei, _awur, ncén_,
+ {{0x442ca397,0x38694398,0xe29a4399,0x2a694022}}, // _rtd_, btar_, _дам_, btab_,
+ {{0xe1ff2009,0x442ca39a,0xdfcf8049,0x201ea39b}}, // któl_, _std_, جين_, _suti_,
+ {{0x6b876098,0x7bce60bb,0x4432439c,0xdce0c018}}, // _gajg, _umbu, mpy_, zemē,
+ {{0xe297a39d,0x2e4aa14a,0xf623639e,0x752981b6}}, // дај_, цяло_, _адсо, _ffez,
+ {{0xbbbb239f,0xd130c13a,0x628403a0,0x20186108}}, // _उत्क, دمة_, _osio, _hiri_,
+ {{0x7c2de3a1,0xdb08a162,0x657ae3a2,0x7e69c23c}}, // _čara, _modé, teth, gtep,
+ {{0x765ae3a3,0x7d1b80a9,0xa09b4053,0xa91d809f}}, // tuty, _zgus, קייט, _lažl,
+ {{0x6d4e605d,0x777ae265,0x799aa00f,0xa3c220c5}}, // _mdba, retx, _intw, ंसन_,
+ {{0xadc38067,0xa194012e,0x7c2083a4,0xbb940052}}, // _miến, ваюч, _kumr, вающ,
+ {{0x7bc1e048,0x66172108,0x777ae108,0x4fc6e3a5}}, // bhlu, _pixk, petx, _оска,
+ {{0x442003a6,0x6e2083a7,0x201863a8,0x64552098}}, // _dui_, _mumb, _niri_, trzi,
+ {{0x61e2c042,0xdb01a05c,0x7c2083a9,0x29024037}}, // _kjol, _kolá, _lumr, _azka_,
+ {{0x391543aa,0x6627e3ab,0x6d4e6229,0x3eae03ac}}, // ембр, žské, _adba, ëjtë_,
+ {{0xdbd6a04e,0x28f8404e,0x386943ad,0x7c2e6041}}, // [190] _kään, деть_, utar_, _atbr,
+ {{0x657d03ae,0xa2ca23af,0x69c283b0,0x63a56022}}, // lesh, _हेक्, ehoe, ujhn,
+ {{0x3944c3b1,0x645c23b2,0x81e4a07c,0xdb0403b3}}, // kams_, curi, নতা_, _goiâ,
+ {{0x657d03b4,0xdbd6a10a,0x29d7417a,0x6d4083b5}}, // nesh, _lään, għat_, _cema,
+ {{0x798763b6,0xee3883b7,0x443ce048,0x6d4f43b8}}, // _tajw, дні_, _lwv_, _idca,
+ {{0x6d40810a,0x201863b9,0x7988a1cd,0xf789e1de}}, // _eema, _giri_, _cadw, _maçã_,
+ {{0xdb03e3ba,0x6d408076,0x7bdde3bb,0x1fb5c12a}}, // _poní, _fema, ésum, еспр,
+ {{0xc91823bc,0x2d87e3bd,0xceb26095,0x6448e3be}}, // וחות_, _sane_, _גיל_, dsdi,
+ {{0x395fc3bf,0x645d03c0,0x321943c1,0xc175a1e1}}, // rgus_, jusi, _misy_, _плащ,
+ {{0x6b88a3c2,0x69d52055,0x09dc43c3,0x6560c13a}}, // _gadg, jize, _यद्य, agmh,
+ {{0x7c208098,0x6d4083c4,0x25ad83c5,0x63ad03c6}}, // _zumr, _yema, _koel_, _boan,
+ {{0x645c23c7,0xcb1263c8,0xdb00822e,0x8cb060c5}}, // vuri, טלי_, _domæ, ंहतो,
+ {{0x6285200f,0x61fbc121,0x291dc3c9,0xef19817a}}, // _asho, mlul, _igwa_, _bażi_,
+ {{0x437523ca,0xdb09c04e,0xa3ab2029,0xf1bf43cb}}, // куст, rkeä, कॉल_, _alá_,
+ {{0x201863cc,0x6373a3cd,0xdce403ce,0x657d03cf}}, // _riri_, mını, _obić, besh,
+ {{0x657d03d0,0x3eb8e3d1,0x25ad83d2,0xd8d6e1a1}}, // cesh, ørte_, _noel_, _רוקט_,
+ {{0x3956601b,0x442123d3,0x3cfe602e,0xfbd14026}}, // [1a0] ењет, _euh_, _štv_, _सगरम,
+ {{0xac86201b,0xe1f763d4,0xb4e0404b,0xaca44079}}, // _згол, егу_, _तशी_, _nrụt,
+ {{0xd94643d5,0x25ad82be,0x7c2083d6,0x7988a3d7}}, // _пени, _boel_, _sumr, _radw,
+ {{0x6846834f,0x3940813a,0xaca44079,0x186a83d8}}, // енда, úis_, _arụt, зади_,
+ {{0xb21b0042,0x61fbc174,0x69d643d9,0x628523da}}, // lvær, dlul, miye, _zsho,
+ {{0xdb01a3db,0x7bc3a3dc,0xead4a3dd,0xb345a01b}}, // _solá, ghnu, коль, vaçã,
+ {{0x394243de,0xbcfb0004,0x6373a3df,0x7c2e63e0}}, // _keks_, nfér, dını, _utbr,
+ {{0x61fbc3e1,0x69d643e2,0x442243e3,0x442fc05d}}, // glul, niye, _kuk_, _btg_,
+ {{0xdb01a3e4,0x442ee3e5,0x394680eb,0xe9df006f}}, // _volá, _stf_, maos_, vnú_,
+ {{0x660d4041,0xafdb20ff,0xbbad43e6,0xef1f03df}}, // zmak, rsøk, _टक्क, rdüm_,
+ {{0x63ad03e7,0x6448e03b,0x7bd523e8,0x645e63e9}}, // _poan, usdi, vizu, nupi,
+ {{0x6d4563ea,0x69d64054,0xdddba23d,0x3f8a63eb}}, // zaha, jiye, _isuš, _labu_,
+ {{0x63ad03ec,0xecd04077,0x98b8014a,0x2e46e0f9}}, // _voan, _सेलफ, елят_, _bífá_,
+ {{0x6373a3ed,0xdced03ee,0x5ea6607c,0x443ce286}}, // cını, _ubač, _গেছে, _twv_,
+ {{0x7bd523ef,0x661aa3f0,0xd90ee3f1,0x69d643f2}}, // rizu, _mitk, _دید_, fiye,
+ {{0x3cfbe095,0x628283f3,0x645d03f4,0x98ad429c}}, // _מלונ, kwoo, pusi, _žeđ_,
+ {{0x672d0011,0x63ae6364,0xa01b03f5,0x6d4fc3f6}}, // [1b0] _ifaj, _bobn, rvös, ócai,
+ {{0x660d43f7,0x61fd03f8,0x62864037,0x224943f9}}, // smak, llsl, _dsko, rsak_,
+ {{0x3b2e8079,0x32090119,0xa3c0c3fa,0x25ad81cf}}, // _ịyị_, _khay_, ँसा_, _poel_,
+ {{0x3fcf807c,0x661aa3fb,0x9e3583fc,0x248d420d}}, // িক্ষ, _aitk, ведч, _šemu_,
+ {{0x44224265,0x6b8d43fd,0x316f8057,0x6b898292}}, // _guk_, mdag, rfgz_, _vaeg,
+ {{0x79898187,0x25a943fe,0x6e21a3ff,0xab5b40cb}}, // _waew, ljal_, _pulb, _flüc,
+ {{0xf991c049,0x443ea400,0x44268401,0x7e6d4402}}, // لبة_, _cwt_, aqo_, ltap,
+ {{0x44224403,0xc6942087,0x63ae606e,0xe65d8121}}, // _yuk_, ראק_, _zobn, ştığ,
+ {{0x7529c024,0x2485a05d,0xba748047,0x69c3a0ca}}, // rcez, _wslm_, _مالت, phne,
+ {{0x7c22c194,0x443f8385,0x32090054,0x6e21a404}}, // _duor, _iwu_, _ahay_, _tulb,
+ {{0xeadc2405,0xd709e04e,0xdee5e406,0x66e5e407}}, // _ধর্ম, ьное_, тони, тона,
+ {{0x32090408,0x645f40bb,0x3ebaa14f,0x2b47a409}}, // _chay_, kuqi, øpte_, lanc_,
+ {{0x850c040a,0x4c83040b,0xa91dc40c,0x69d64106}}, // सेंट_, аляв, jdži, viye,
+ {{0x2b47a40d,0x23bd21e9,0xeab0c049,0xfce6240e}}, // nanc_, _dìjo_, _دعم_, вого,
+ {{0x386dc40f,0xdb01a410,0x69d64411,0xbb432412}}, // lter_, _dolç, tiye, реск,
+ {{0x16218026,0xc2c6013a,0x27e04413,0x60d6c013}}, // मदार_, _ليبي, unin_, _žymi,
+ {{0x7e6d4414,0x69c1a415,0x44236416,0x798bc417}}, // [1c0] gtap, _olle, _auj_, _magw,
+ {{0x27ed4418,0x645e6419,0x7c23e082,0xdb04041a}}, // čena_, tupi, _kunr, _goiá,
+ {{0x224d80d1,0x2002a03b,0x0b4680ff,0xc1bb21a9}}, // _avek_, _ūkio_, внен, _חמיש,
+ {{0x7c2de41b,0x63a9c41c,0x0696e00b,0x2487e41d}}, // _čarl, jjen, כדעם_, _msnm_,
+ {{0xb5fb201f,0x765980e2,0x63bbc41e,0x6562800e}}, // rtáv, grwy, dkun, rgoh,
+ {{0x6d44041f,0x7c240420,0x66098166,0x44320071}}, // _leia, _muir, _ehek, _mty_,
+ {{0xdca36421,0x83fd0422,0x29d203de,0xe1268423}}, // _кари, luđe, ršaj_, _амби,
+ {{0x68ed400e,0x6d472424,0x6b8bc425,0xdb07214f}}, // _ëndë, zaja, _cagg, rkjø,
+ {{0x798bc426,0x6d472427,0x661aa428,0x44236286}}, // _dagw, yaja, _titk, _zuj_,
+ {{0x3f9dc125,0x4423605f,0x7c23e3f6,0x6457a03e}}, // _enwu_, _yuj_, _bunr, áxic,
+ {{0x20090067,0x44236022,0x6b8d4155,0x2d8ca106}}, // _phai_, _xuj_, ydag, _iade_,
+ {{0x6d472429,0x63a9c42a,0x39448088,0x68fc642b}}, // waja, cjen, _jems_, _myrd,
+ {{0x1958e20e,0x44320067,0xe29800db,0xdb18a046}}, // _залы_, _cty_, ваў_, rivõ,
+ {{0x7bc2c04a,0x6fdec018,0x61e642a4,0xbcfb0071}}, // _hlou, dīci, _ajkl, bgén,
+ {{0x2d8ca42c,0xc7c7042d,0xf1b9842e,0x7640805f}}, // _made_, _аспи, _hiše_, _lwmy,
+ {{0x6d43e42f,0x7e6d4430,0x3ce90286,0x661c6090}}, // _zena, ttap, _txav_, _airk,
+ {{0x2fc680eb,0x9f4ce431,0x3495003b,0xdb052009}}, // [1d0] dhog_, hodí_, _сапр, _dohá,
+ {{0x44236048,0x69d8a07f,0x26c9416d,0xa28320d0}}, // _puj_, jive, rzao_, _پیرو,
+ {{0x63af4432,0x661c6433,0x7e60c434,0x67d541b1}}, // _tocn, _dirk, hump, _бону,
+ {{0x68fb808b,0xdb03a0cb,0xbddb6163,0x27e6c012}}, // _pyud, rknü, _avèt, _njon_,
+ {{0x38794435,0x39494436,0x6adba437,0x6288a08b}}, // _upsr_, maas_, यप्र, _asdo,
+ {{0x2d8ca438,0x1d09a439,0x661c643a,0x2d9ea0b6}}, // _cade_, нели_, _girk, _cnte_,
+ {{0x2d8ca43b,0x63bbc43c,0xddc4612a,0x3f8ca43d}}, // _dade_, tkun, stiţ, _dadu_,
+ {{0x3949443e,0x2d81643f,0x443f8440,0x3f816098}}, // naas_, lehe_, _twu_, lehu_,
+ {{0x69d8a441,0x3947a442,0x7e60c443,0x7bd8a444}}, // bive, pans_, gump, bivu,
+ {{0x6b8bc00d,0x1db98010,0x6d452445,0x39494054}}, // _wagg, _आवडत, _neha, haas_,
+ {{0x216a0446,0x2bb8a447,0x201ce018,0x39494448}}, // нини_, _अवता, _divi_, kaas_,
+ {{0xf7720449,0x60002156,0xbf0fa046,0xe1ff20c2}}, // حاد_, römm, ाधीन_, otów_,
+ {{0x7e60c359,0xa01b60e0,0x6b8d023c,0x2d816187}}, // cump, _csök, _daag, kehe_,
+ {{0x6e23e44a,0x2d8ca057,0x6145e01b,0xddc46013}}, // _tunb, _xade_, _сека, ktiš,
+ {{0x69d9844b,0xe2a8e13a,0xe299e2a3,0x4376444c}}, // kiwe, _لاين_, _чай_, луат,
+ {{0xa766044d,0xb5fb206f,0x7dd4203c,0xf2d2a00b}}, // _скид, tuác, _müsə, צען_,
+ {{0x7bc4044e,0xdce1e013,0x27ffc052,0xe81bc118}}, // [1e0] _iliu, telė, ilun_, _पूछा_,
+ {{0x65698037,0x728a21e3,0x3860421c,0x69d8a44f}}, // _aceh, _због_, ruir_, xive,
+ {{0x69d8a450,0x7afd4451,0x83fd023d,0x645bc049}}, // vive, _eyst, suđe, hrui,
+ {{0x929d8064,0x3f8d803e,0xf1bf6452,0x645bc23c}}, // _czło, _caeu_, _stát_, krui,
+ {{0x61e28453,0x386040b8,0xdb08a454,0xe2978455}}, // rnol, quir_, _vodí, лац_,
+ {{0x645bc456,0x98bee12a,0xbe8aa457,0x7434a458}}, // drui, nată_, есие_, анкф,
+ {{0xdb1e2459,0x7bd8a45a,0x61e4445b,0x6684601c}}, // _impó, rivu, onil, _خیال,
+ {{0x9054c45c,0x8b958104,0x6d46445d,0xdcfc245e}}, // авиц, _круч, _jeka, jerč,
+ {{0x7980c0b1,0x798e645f,0x6441a0f7,0x386ea460}}, // remw, _kabw, _ewli, stfr_,
+ {{0x4274e461,0x6b81e462,0xfd65a291,0x3f9ea463}}, // агос, gelg, _metụ, _untu_,
+ {{0x61e44464,0x6d4aa465,0xb5fb2009,0xd917e052}}, // knil, mafa, ktár, лью_,
+ {{0x0ecf2466,0x31668467,0x7bc2c468,0x764e2469}}, // _हेगड, ngoz_, _ulou, dsby,
+ {{0xdb076071,0x7c26446a,0x6fb8a04b,0x2906c125}}, // _mojá, _nukr, _अवां, _uzoa_,
+ {{0xb5fb201f,0x798d0054,0x7afd446b,0x5334e46c}}, // etár, _waaw, _ryst, _тект,
+ {{0xa91d803b,0x201ea46d,0x7bc40256,0x69c7246e}}, // _važi, _miti_, _fliu, rhje,
+ {{0xb0c4846f,0x8aa70470,0xa8a70471,0x2bbb013a}}, // वनाग, _брод, _брок, _ساعة_,
+ {{0xdb1d013a,0xbebb40dd,0x752d4472,0x03d56473}}, // [1f0] bhsá, _afër, rcaz, ажаю,
+ {{0xd5672474,0x661e220d,0x6d4aa475,0xa7fb2005}}, // _стип, _gipk, jafa, buña,
+ {{0x3f83204d,0xe3bf607b,0x4426c476,0x7afde04a}}, // meju_, _cuña_, _luo_, řstv,
+ {{0x8937813a,0x09e1e477,0xb5fb2478,0x6d464479}}, // اعضا, _पद्य, ctár, _geka,
+ {{0x4429447a,0x69d9847b,0xb7b5a125,0xddc4647c}}, // qqa_, riwe, _dịn, stiš,
+ {{0x7bdae47d,0xd246e0b7,0x69d9847e,0x7bd5647f}}, // jitu, _ون_, siwe, _umzu,
+ {{0x79828005,0x6b828480,0x4425a481,0x6b81e482}}, // beow, beog, _qul_, velg,
+ {{0x69c3e06f,0xdb1d4069,0x2d832483,0x7981e484}}, // _slne, _umsó, heje_, welw,
+ {{0xdb098069,0xae152485,0x6e948486,0x290c613a}}, // _gleð, णगान_, _литу, _údar_,
+ {{0x645bc487,0x61e98488,0x249940c7,0x2b46c157}}, // rrui, _hjel, _mrsm_, _eeoc_,
+ {{0x2d8ee489,0x3946c48a,0xd5b9848b,0x645bc48c}}, // _dafe_, _feos_, _आवाज, srui,
+ {{0xaca36079,0x6e27605d,0x224dc48d,0xa91dc07f}}, // _gwục, _mujb, psek_, režb,
+ {{0x03a5e48e,0xdb08a049,0x61fb8167,0xaca3c079}}, // _вило, _iodá, _mkul, _mmịk,
+ {{0x61e98143,0x09de404b,0x2d8fc48f,0x69c52153}}, // _ljel, नच्य, _hage_, _clhe,
+ {{0x6723e490,0x28d9c2b4,0x753d0009,0x78a9c322}}, // _ignj, _बेति, bbsz, lxev,
+ {{0x09e30491,0x69c40492,0x3946c057,0x798e6493}}, // _мощн, _ulie, _xeos_, _sabw,
+ {{0x09c161a6,0x0b466494,0x3f8fc495,0xe1fa6496}}, // [200] োচনা, инан, _magu_, вга_,
+ {{0x8af00497,0xe29a6498,0x7c276499,0xaca3c079}}, // _edəc, каз_, _bujr, _amịk,
+ {{0x98a6249a,0x4427e49b,0x6e220024,0x05772076}}, // _тине, _jun_, _èobb, _דגים_,
+ {{0xea76e49c,0x7bdae49d,0x7bc8e00e,0x628bc0fd}}, // יגער_, zitu, zhdu, _osgo,
+ {{0x2a7dc022,0x69dae49e,0x249940ae,0x7bdae284}}, // _npwb_, yite, _grsm_, yitu,
+ {{0x3ce0c49f,0xf50a64a0,0xdb0ae1af,0xdfcfa4a1}}, // _živo_, _англ_, _sofí, وين_,
+ {{0x201ea4a2,0xd378e4a3,0x61e984a4,0x80b6207c}}, // _viti_, niće_, _fjel, ৃপক্,
+ {{0x6b8444a5,0xe7c1007c,0xa3d3e033,0xf1bf6057}}, // neig, _উদ্য, सोस_, _juán_,
+ {{0x29000022,0x27f824a6,0x2d99807b,0x9df8e4a7}}, // _nyia_, korn_, _ósea_, лнит_,
+ {{0x2259c3e4,0x3947e017,0x6fc4e153,0x660d04a8}}, // áska_, _cens_, _bòca, _shak,
+ {{0x7786c4a9,0x660d0174,0x3086a4aa,0x60cd44ab}}, // _влез, _phak, _خلاف, zzam,
+ {{0xdebba4ac,0xf1b984ad,0xd378e3a1,0x6442c064}}, // _שמאל, _kiša_, diće_, _twoi,
+ {{0x5f9404ae,0x2d8324af,0x91e6e4b0,0x6b8444b1}}, // цият, teje_, _коде, deig,
+ {{0x6d56018c,0x69dae00e,0x27e6805d,0x7bdae00e}}, // _edya, qite, hnon_, qitu,
+ {{0xe0e8c07c,0x0ce1607c,0x6d4764b2,0xdb1d01ae}}, // _পরিভ, _ভর্ত, _reja, visõ,
+ {{0x6d4b84b3,0x660d0296,0x27e904b4,0x394ce065}}, // xaga, _uhak, _ujan_, lads_,
+ {{0x91fd0041,0x26e2a0c2,0x2418420e,0x27e68114}}, // [210] stās, गपुर_, роты_, dnon_,
+ {{0x61e984b5,0x6671e050,0x69c644b6,0xa3b76010}}, // _sjel, _بگیر, _elke, _जवळ_,
+ {{0x3ed964b7,0x4095213a,0x66028069,0x7c29605c}}, // دواج_, _الجر, flok, _čers,
+ {{0x69c9c048,0x78a24089,0x660284b8,0x225e202e}}, // xhee, ťova, glok, átke_,
+ {{0x394904b9,0x7f476022,0x182184ba,0x2bba0028}}, // _meas_, _tejq, मदेव_, _उकसा,
+ {{0x629aa012,0xb602e066,0x68336227,0xdcfe204a}}, // _brto, _žánr, _aħde, _napě,
+ {{0x61e984bb,0x442904bc,0xdb08a05c,0x69c9c200}}, // _tjel, _lua_, _podá, thee,
+ {{0x4998c04e,0x442900ba,0x2d84c4bd,0x61e9807f}}, // атья_, _oua_, geme_, _ujel,
+ {{0x657b84be,0x4427e4bf,0x5fa9c0c2,0xdb08a066}}, // _ibuh, _sun_, काबल, _vodá,
+ {{0x78bd0052,0xd6dba4c0,0x7e63a050,0x68e48049}}, // dysv, _ите_, punp, úide,
+ {{0x6e28a24f,0xb1134088,0x3f9164c1,0xe61a04c2}}, // _gudb, _bụka, _mazu_, лдо_,
+ {{0x5184c02d,0x3949012a,0x3a2902a8,0x3f84c4c3}}, // _муча, _ceas_, _buap_, cemu_,
+ {{0x34abc049,0x7bdc24c4,0xe3bf6019,0x69a4c11a}}, // _جداً_, tiru, _nuño_, कारी,
+ {{0x6e3bc4c5,0x6723e2e2,0x2901205d,0xfe70c0eb}}, // mpub, _ugnj, _byha_, _زده_,
+ {{0xd435e25b,0x8fa644c6,0x07a641bc,0x4427e054}}, // _اعتب, баве, бавн, _uun_,
+ {{0x7ae124c7,0x44200037,0x645ce00a,0x3e71e009}}, // últa, _uii_, šric, mát_,
+ {{0x2a7f8022,0x6e3bc4c8,0x4421217a,0xbea344c9}}, // [220] _npub_, npub, _fih_, патк,
+ {{0x5d86213a,0xb5fb24ca,0xd8d7000b,0x645e64cb}}, // _الدل, nuál, _לופט_, trpi,
+ {{0x7e6560c7,0x69d9c4cc,0x6fc604cd,0x248d84ce}}, // guhp, _omwe, _zóca, _asem_,
+ {{0xf0d28291,0x798600c1,0x6e28a4cf,0x443a64d0}}, // bịlọ, nekw, _rudb, rpp_,
+ {{0x6ba52069,0x3e71e0e0,0x2d9204d1,0xdcf604d2}}, // _útgá, hát_, _maye_, _obyč,
+ {{0xf1b980e4,0x3e71e4d3,0x232764d4,0x69dd00bb}}, // _viša_, kát_, _гори_, zise,
+ {{0xc3332095,0x63a404d5,0xf8c824d6,0xa91dc4d7}}, // לוג_, _onin, रनिय, veža,
+ {{0xa8576095,0x69c76098,0x442244d8,0x6569c4d9}}, // _לילה_, _zlje, _jik_, ggeh,
+ {{0x25ef64da,0x442244db,0xe29784dc,0x629b803b}}, // _आदमी_, _mik_, _лат_, _gruo,
+ {{0xa4b74095,0x2d84c4dd,0x660444de,0xd62aa4df}}, // צליה_, seme_, hlik, _боже_,
+ {{0x02b304e0,0x7c298105,0xc7afc04e,0x69daa4e1}}, // ुन्न, _zuer, _её_, _imte,
+ {{0x442a64e2,0x2d92005d,0x63b60090,0x69dde0fa}}, // _mub_, _caye_, _coyn, éser,
+ {{0x7bdd04e3,0x107404e4,0x2bfa64e5,0x2bbf44e6}}, // risu, пляю, ्तां_, _एकबा,
+ {{0x63a404e7,0x4438607d,0x7f4d4054,0x6ca6e4e8}}, // _enin, _otr_, waaq, _гриж,
+ {{0x660444e9,0x63b564ea,0x7de54013,0x6377e095}}, // flik, _rozn, lėsi, _הגוף_,
+ {{0x6fc06105,0x7c2d44eb,0x4421207d,0xa3abc466}}, // _höch, tqar, _wih_, खाय_,
+ {{0xbbeb24b7,0xa3e8a010,0x78c6216d,0x3dc904ec}}, // [230] _مردم_, मचा_, _čuvš, _ilaw_,
+ {{0x442244ed,0x7f4f0197,0x2bbb6049,0x2d9204ee}}, // _eik_, lacq, دارة_, _zaye_,
+ {{0xa3be24ef,0x2bbea0e3,0x3ebee4f0,0x7d7be1a1}}, // _आवत_, ्सना, nytt_, רנבו,
+ {{0x225c605c,0x6d5ea4f1,0xfd10613a,0xfbcee07c}}, // ávka_, ópan, رجل_, িচিত,
+ {{0x6d58e4f2,0x7ae4c4f3,0x80da607c,0x6f02c096}}, // _odva, _žitn, _বুদ্, _nyoc,
+ {{0x2d85e4f4,0xdb1b8017,0x7bc284f5,0x6b87200e}}, // xele_, _lluç, akou, nejg,
+ {{0xa2db04f6,0xd5ae80e0,0x7c2984f7,0x442a64f8}}, // _पेस्, رہے_, _wuer, _gub_,
+ {{0x7c22c4f9,0x2d9324fa,0xe3b9c4fb,0x7bcd44fc}}, // _cior, _laxe_, рби_, nhau,
+ {{0x6603a03a,0x6d4e24fd,0x27ff84fe,0xb7b5c079}}, // plnk, yaba, _ikun_, _bụr,
+ {{0xd7fba091,0x394f8069,0x2fc900ae,0x6d4e24ff}}, // _суб_, lags_, _alag_, xaba,
+ {{0xb5fde500,0xf98604b0,0x1d09e501,0x69c98502}}, // bušn, огно, рени_, _klee,
+ {{0x27ff81d7,0x216a0503,0x78a4c504,0x656b805d}}, // _jkun_, шими_, _šive, nggh,
+ {{0x00e5c043,0x6e2de098,0xe919c0ff,0x8af0003c}}, // _بتای, _čabu, _тоді_, _edən,
+ {{0x61b68505,0x657d4506,0x7bc98507,0x27e9c0e4}}, // _अक्ष, _absh, _lleu, čanu_,
+ {{0x80db007c,0x7f4f0024,0xcfaa0508,0x3f920509}}, // _যুদ্, bacq, _ناظم_, _tayu_,
+ {{0x290422b8,0x6d4ae50a,0xd378e06a,0x6604450b}}, // āma_, _zefa, mića_, ulik,
+ {{0x394f850c,0xe1f7412e,0x546a42fe,0x386040fa}}, // [240] dags_, ігу_, разм_, urir_,
+ {{0x69db81bf,0x644040dd,0x7d03e50d,0xb7b5a079}}, // _amue, _çmim, _kyns, _dịk,
+ {{0xd378e50e,0x7c23e07d,0x3086650f,0xdb0e6510}}, // nića_, _minr, تلاف, _tobí,
+ {{0x7bcd4511,0x443a20c2,0x7ae36049,0x2903600e}}, // chau, _itp_, únta, _dyja_,
+ {{0x91fd0041,0xf38d000b,0x2486606f,0x7bde6050}}, // duāl, טראָ, ňom_, pipu,
+ {{0x27ed8435,0x27e04512,0x69c28513,0x7ae4c04d}}, // _ejen_, liin_, skoe, _žito,
+ {{0xa3bfe046,0x3a2a60ae,0x2167a1e3,0x3160a106}}, // ीसन_, _uubp_, цији_, ğiz_,
+ {{0x3e55e041,0x2005e514,0x7c23e515,0x61fbc516}}, // dāt_, elli_, _ainr, goul,
+ {{0x3f98c04a,0xa5bb61e9,0xa96aa517,0x7c22c0ba}}, // ěru_, _atóy, _виза_, _vior,
+ {{0xe80ac518,0x69c3a519,0x27e9451a,0x44248095}}, // _हीरा_, ckne, znan_, _him_,
+ {{0x443a251b,0xa91dc51c,0x61fae0d5,0xf1d9e51d}}, // _ntp_, bežo, rotl, योधन,
+ {{0x61b7651e,0x442ca51f,0x7c3aa17a,0xc6a70520}}, // _आक्ष, _iud_, _ittr, орги,
+ {{0x3e74404e,0x27e04521,0x6b9641cd,0xddcd612a}}, // jät_, diin_, ddyg, ntaţ,
+ {{0x69ce210f,0x7d03e522,0x442b4523,0x6e23e524}}, // ghbe, _fyns, _ruc_, _ginb,
+ {{0x7c240054,0x27e94525,0xdb0080c2,0xc1052049}}, // _fiir, tnan_, _komó, سوري,
+ {{0x7bcd4526,0x3a2b4071,0xd6db006c,0x27e04527}}, // uhau, _pucp_, _вто_, giin_,
+ {{0x6fc60057,0x6d412106,0x2d87a528,0xb4c6a026}}, // [250] _zóco, ılab, yene_, उनु_,
+ {{0x443ee200,0x6455617b,0x6833613e,0x6b88e529}}, // opt_, _pvzi, _aħda, nedg,
+ {{0x3f87a52a,0xaa95613a,0x7bdf452b,0x1b1ce07c}}, // venu_, ثلاث, piqu, পুরে_,
+ {{0x2249052c,0x61fbc057,0x443ee52d,0xab5b00cb}}, // _kwak_, xoul, ipt_, chüt,
+ {{0xa13640be,0xd6db852e,0x6282c022,0xe5a5a52f}}, // _מארק_, јте_, _npoo, зили,
+ {{0x61fbc163,0x61fd0530,0x200560c4,0xb5fde012}}, // woul, hosl, _óli_, gušl,
+ {{0x7c2bc531,0x6fc2206f,0x21290532,0xdd0ea3df}}, // _sugr, _dôch, _ogah_, rışm,
+ {{0x78a68533,0x2d894011,0x6e23e534,0x27ff8535}}, // _škve, neae_, _sinb, _ukun_,
+ {{0x7d03e536,0xddcd612a,0x6d41e537,0xf1b9a20d}}, // _syns, ctaţ, tbla, _sušu_,
+ {{0x25a6c057,0x49b8a1b3,0xa3ae6538,0x6f03e1cd}}, // _enol_, _والد_, काय_, _pync,
+ {{0x61fc2042,0xab2a02a3,0x27e04054,0x6e23e135}}, // vorl, йона_, yiin_, _vinb,
+ {{0x9f5ea163,0x7c2d0539,0xdb0760f9,0x2cb8e156}}, // _aktè_, _buar, _bojú, ärda_,
+ {{0x80b1a53a,0xd9bd807c,0xe8eec53b,0xf1b9853c}}, // _अपडे, _আগাম, _чл_, _moš_,
+ {{0xdd9240d0,0x78a68069,0x6e2d0037,0x63b8e53d}}, // کور_, _ákve, _duab, _govn,
+ {{0x7c24053e,0x18a3253f,0x69c44540,0xd00ee049}}, // _tiir, нарм, tkie, _حلو_,
+ {{0x656d4541,0x7c2d0542,0x5bb8a029,0xb7b5a079}}, // ggah, _fuar, _अव्व, _dịi,
+ {{0x4425a543,0x442d80fa,0x629bc052,0x7d040544}}, // [260] _oil_, _nue_, nvuo, _uyis,
+ {{0xdc9b0545,0x7d1a6069,0x69dd40bb,0x317f812a}}, // _פסיכ, _útsk, _emse, _abuz_,
+ {{0x249f8546,0x7bde2547,0x7bcbc548,0x2ca00320}}, // _arum_, _impu, _olgu, _hrid_,
+ {{0x7649811d,0x394d8549,0xd7af004b,0x5faf004b}}, // _bwey, _cees_, टायच, टायल,
+ {{0x7bc56022,0xf194a54a,0x6005c019,0xd00aa54b}}, // bkhu, диль, cómi, беде_,
+ {{0x4424854c,0x2b98c067,0xf2d3400b,0x7f5b81ab}}, // _wim_, _ức_, _טער_, _eduq,
+ {{0x59cc0010,0xd7cc0010,0xb5fb2069,0x2bc1604b}}, // ासार, ासाच, krán, _शकता,
+ {{0x249f854d,0x5838c12a,0x3253a14a,0x673640c2}}, // _frum_, озия_, хвър, ncyj,
+ {{0xa91dc54e,0x61e0c3df,0x6d4d0134,0x7bde2194}}, // ležj, riml, _reaa, _ompu,
+ {{0xe3a4a54f,0xdb01a183,0x6e2e6550,0x60c0c277}}, // _تشری, _coló, _kubb, rymm,
+ {{0xdcf52041,0x7c3aa551,0x645a2552,0x394d8223}}, // rdzī, _uttr, štij, _yees_,
+ {{0x394d0019,0x6b88e012,0xdd8fc553,0x386940a2}}, // úes_, sedg, _موم_, yuar_,
+ {{0x64a3c554,0x386940dd,0x6602c031,0x1ee70555}}, // _паца, xuar_, _ikok, سوسی_,
+ {{0x6d5c6556,0x3869400e,0x64b5604a,0xfbcfe049}}, // _ndra, vuar_, _hřiš, _متى_,
+ {{0xe97be095,0xa97be076,0x4b7be557,0xb93a4088}}, // _הנוש, _האוכ, _האוו, _ọmụr,
+ {{0x2ca00558,0xddeb20eb,0x62840090,0x6005c559}}, // _frid_, _گربه_, _fpio, tómi,
+ {{0x645520e0,0xe664455a,0xa3ca035b,0x66f4419c}}, // [270] sszi, етхо, लघर_, епту,
+ {{0x7bcd055b,0x2b4d8005,0xddcd6143,0xf99f40ca}}, // _hlau, _seec_, ftaš, _chèr_,
+ {{0x2484805d,0x4425a55c,0x26c7604a,0x853001df}}, // _apmm_, _ril_, áno_, _haɗa,
+ {{0x28de455d,0x4425a55e,0xf09f4016,0x3e75655f}}, // _नेवि, _sil_, _trà_, råt_,
+ {{0x61e1e560,0x7dcfa271,0x920ce028,0x32094561}}, // yill, _løse, हताज_, mlay_,
+ {{0x27ed40e4,0x69c72488,0x7bcd0562,0x2adea10a}}, // čenu_, nkje, _llau, _केहु_,
+ {{0x8464614a,0xf1a9c0c2,0xfc64614a,0x293821a1}}, // _пъте, कालन, _пътн, לאגן_,
+ {{0x442d8563,0x32094090,0x2d8b053d,0xdb01a564}}, // _tue_, nlay_, jece_, _soló,
+ {{0x442d810a,0xdb01a565,0x9327a566,0xc3344095}}, // _uue_, _colò, _فران, עוץ_,
+ {{0x6d45628f,0x7bcd0013,0xde16e049,0xa2d68567}}, // nbha, _alau, وقيت_, _मेट्,
+ {{0x9d0be07c,0xaf37c54f,0x442ee296,0x2009403b}}, // রশ্ন_, _فرشت, _cuf_, klai_,
+ {{0x6b8b8568,0x6458e098,0x6d456569,0x67d4c04e}}, // negg, _dvvi, hbha, _поху,
+ {{0xb5fde56a,0x27edc56b,0x7bc6004e,0x68e9423d}}, // nušk, mnen_, rkku, _žedn,
+ {{0x4426c56c,0x61fe60d5,0x61e3a56d,0xe3bf42a8}}, // _zio_, xopl, ninl, _riña_,
+ {{0x7d06404e,0x69cd01cd,0xdb18e56e,0xabf6009e}}, // _syks, _flae, _novè, _очищ,
+ {{0x6441e56f,0x7e644570,0x6d5c6197,0x629c2110}}, // mpli, trip, _sdra, rvro,
+ {{0x6e276200,0x6d4e607d,0x29116571,0xd12f2043}}, // [280] _bijb, _peba, _azza_, امہ_,
+ {{0x63a98572,0x04466052,0x2bcc0573,0x6609c0bb}}, // _anen, дежн, ासवा, hlek,
+ {{0x7e644574,0x225e00c2,0x6eb5239f,0xdb0f40fc}}, // srip, ątku_, _उपयु, _vocá,
+ {{0xddcd6575,0x83838052,0x6d456420,0x6b8b8576}}, // rtaš, выше, abha, gegg,
+ {{0x442fc223,0x661bc0a9,0x4427e577,0xa3cde578}}, // _nug_, dmuk, _oin_, रसा_,
+ {{0xb5fde579,0x63a9857a,0x3a26c1cd,0x7d78a049}}, // sušj, _enen, _siop_, سمبر_,
+ {{0x443dc07d,0x64b56026,0xc246612e,0x27edc105}}, // _atw_, _přiš, _інак, fnen_,
+ {{0x63bb8030,0xcb132095,0x2d98657b,0x26040005}}, // _goun, כלו_, _hare_, iños_,
+ {{0x443ce57c,0x2485a229,0x3f8b0012,0xdb19c0ca}}, // _stv_, _eplm_, vecu_, _jowè,
+ {{0x7bcd057d,0xa2a20026,0x27edc57e,0x63a9806f}}, // _slau, खिन्, anen_, _znen,
+ {{0xd12fa57f,0x63bb8580,0x63bc6076,0x200940fd}}, // _ضمن_, _youn, _morn, ylai_,
+ {{0x7dc3410a,0xceb3a053,0xdb0d40c4,0x394683b0}}, // _tõsi, _שיף_, kkað, nbos_,
+ {{0x5c0721e1,0x621bc053,0xb5fb2581,0xf09f40f9}}, // дява, _וואק, brál, _ayàn_,
+ {{0x3f824020,0x8c14807c,0x7dc06582,0x51842583}}, // _abku_, িদিন_, _möss, куса,
+ {{0x85300068,0xbcfb00fa,0x7dd14584,0x27e32585}}, // _waɗa, ggér, _låse, rijn_,
+ {{0x7bc72586,0x442fc223,0xb17b4587,0x2a66805d}}, // skju, _yug_, _småb, krob_,
+ {{0xdb1aa588,0x61ed4589,0x9f586069,0x4815658a}}, // [290] _botë, rnal, _skrá_, емас,
+ {{0x2909058b,0x63bb849b,0x6d45658c,0x6609c0e0}}, // _iyaa_, _soun, ubha, zlek,
+ {{0x6b8b814a,0xb5fde06e,0xa3c600a8,0x2009458d}}, // tegg, kršn, _एवम_, plai_,
+ {{0x7c28a58e,0x7f552071,0x386681cd,0xb4db458f}}, // _aidr, lazq, fror_, _deàr,
+ {{0x61e3a590,0xddc9a04a,0x6b8d4591,0x2d8ce592}}, // tinl, _zveř, meag, kede_,
+ {{0x9f4200dd,0x26c4c03b,0x443ea593,0x3e78e0e0}}, // tikë_, dymo_, _btt_, két_,
+ {{0x61e3a594,0x6b8b8197,0x6b686009,0x7e6d4595}}, // rinl, pegg, ségü, luap,
+ {{0x61e3a3ed,0x9f58203a,0xfc3f413a,0x2486c07d}}, // sinl, vorí_, _tríd_, _bpom_,
+ {{0x60160326,0xba25e159,0xe3bf403e,0x644ea17a}}, // náme, ндик, _liño_, _ħbie,
+ {{0x44290596,0xc6924095,0x6609c597,0x66040598}}, // _aia_, נאי_, slek, _skik,
+ {{0x5faca599,0x443ea0e8,0x4431659a,0x2c78e59b}}, // चारल, _gtt_, _juz_, géd_,
+ {{0xb21b659c,0x4427e59d,0xdcebc17a,0x7643a037}}, // _spæl, _win_, _nagħ, mpny,
+ {{0x7c29859e,0x2d8364bb,0x6459802b,0x61e4459f}}, // _mier, _obje_, nswi, viil,
+ {{0xa91dc5a0,0x764d00ca,0x69c8e5a1,0x7641e5a2}}, // ježi, _dway, ckde, pply,
+ {{0xfc3f41e9,0x3eb5c12a,0x2d9945a3,0xdcebc13e}}, // _atí_, ţetă_, _base_, _bagħ,
+ {{0xee3a45a4,0x98ac003c,0xaca381e9,0x7bc9c5a5}}, // зна_, şdı_, _ayọn, nkeu,
+ {{0xe0da41e1,0x386dc5a6,0x2d994057,0x8fa625a7}}, // [2a0] чва_, nuer_, _dase_, _запе,
+ {{0x317fc0c7,0x9f5ca066,0xdb1ae049,0x447b00be}}, // hfuz_, pové_, chtó, _צניע,
+ {{0x44320016,0x3f986018,0x056685a8,0x6fcde057}}, // _huy_, _varu_, евен, _xúca,
+ {{0x3e79c0d1,0xa6db40c4,0x2d8ce5a9,0x3d1525aa}}, // kèt_, _veðr, zede_, _धरते_,
+ {{0x3d06e10a,0xdd99805c,0x645985ab,0x224d8037}}, // _सुने_, _daň_, gswi, _cwek_,
+ {{0x7c2985ac,0x245aa018,0x60160019,0x63bd4089}}, // _eier, tīm_, námb, _zosn,
+ {{0x78a2c5ad,0x443205ae,0x6829603b,0xd90d80e0}}, // _srov, _luy_, _užda, میہ_,
+ {{0x3f9a2041,0x249f40e4,0xd84f0079,0x39498069}}, // _lapu_, _šumi_, _bọlb_, ðast_,
+ {{0x444465af,0x3ea2c0c4,0x7999c5b0,0x798e200d}}, // lp_, íkt_, _daww, kebw,
+ {{0x798405b1,0x3f9a2012,0x444465b2,0x7c2985b3}}, // _abiw, _napu_, op_, _zier,
+ {{0x44446157,0x443ea041,0x6d5525b4,0xe0be61a6}}, // np_, _utt_, vaza, _আধুন,
+ {{0x6aa405b5,0x44290048,0x386dc5b6,0x7c29817a}}, // _brif, _qia_, buer_, _xier,
+ {{0xdd8fc13a,0x2ca04504,0x799aa5b7,0xc458004e}}, // _دون_, jvid_, _katw, ниях_,
+ {{0x2ca04277,0x6fcb203c,0x2005206f,0x2d9945b8}}, // dvid_, _gücl, ôli_, _sase_,
+ {{0xdb1c60dd,0x27e685b9,0xdb1565ba,0x98a90098}}, // _dorë, kion_, _pozí, _igač_,
+ {{0x3ebe2277,0x752d05bb,0x659445bc,0xdb1aa3bb}}, // ätta_, _ugaz, ласу, _coté,
+ {{0x7c298355,0x3f9945bd,0x24f845be,0x32016066}}, // [2b0] _rier, _vasu_, енты_, tohy_,
+ {{0xb21b2065,0xd056a03c,0x20186153,0x65608049}}, // dtæg, şbəx, _chri_, _admh,
+ {{0x765ae5bf,0x644085c0,0x659585c1,0xb4bd65c2}}, // nsty, _atmi, кажу, ेने_,
+ {{0x7d0985c3,0x645985c4,0xa2a4a5c5,0x765ae5c6}}, // _pyes, tswi, किन्, isty,
+ {{0xa4a5e0f9,0x444465c7,0x9f494049,0x799aa2ad}}, // _asọ̀, ap_, nnaí_, _batw,
+ {{0x3206c5c8,0x645985c9,0xb5fb207b,0x7999c5ca}}, // _akoy_, rswi, cuát, _saww,
+ {{0xa3e925cb,0x2ec9c5cc,0xc3342076,0xe296a14a}}, // _मगन_, िन्त, נוק_, ващ_,
+ {{0x3e79c5cd,0xddc2a03b,0x7dd26065,0x6b9d05ce}}, // tèt_, ruoš, _væse, jdsg,
+ {{0x200325cf,0x7e6f0012,0x7fd5e5d0,0xa283c5d1}}, // koji_, jucp, віні, ایتو,
+ {{0x799aa5d2,0x442a6223,0x28d00029,0xe530603c}}, // _gatw, _xib_, हनसि, _müğə,
+ {{0x216a05d3,0x78a405d4,0x9991e0e0,0xbebdc03b}}, // мини_, _sriv, öző_, ncūz,
+ {{0x2d9a25d5,0x3f8f80e2,0x59a6e12f,0x2d9d85d6}}, // _sape_, negu_, _कोठर, ndwe_,
+ {{0x3f9a25d7,0x799b85d8,0x32020066,0x2d998057}}, // _papu_, _mauw, roky_, _óseu_,
+ {{0x6a85e19b,0x444465d9,0xdce1a315,0xf1bf4071}}, // _алка, yp_, _halı, _diám_,
+ {{0x07a325da,0x3f8f8108,0x660d45db,0x7e7d05dc}}, // јасн, kegu_, flak, atsp,
+ {{0xf1b9823d,0x443325dd,0xb222c0c4,0x673bc431}}, // _kišu_, _dux_, _þætt, ncuj,
+ {{0xf1b5e578,0x501b0095,0x7e7c212d,0xdb1c2049}}, // [2c0] _अचान, _קופו, strp, thró,
+ {{0x3ea045de,0x98a0000a,0x7c2ae5df,0x9f4a25e0}}, // rvit_, _agić_, _xifr, robó_,
+ {{0x7c36400e,0x66028035,0x8e3a05e1,0xdb0640c2}}, // sqyr, took, _عسکر_, _pokó,
+ {{0x6b9aa48d,0x9c82606e,0xaa4682d7,0x83fd00a9}}, // _satg, ščob, ведл, buđu,
+ {{0x27e7a5e2,0x799b85e3,0x61e405e4,0x7641a013}}, // finn_, _dauw, _mmil, _atly,
+ {{0x9d4365e5,0x6602800d,0xda022021,0xe4e3a026}}, // _верд, sook, लकित_, _केहि_,
+ {{0x7dc3410a,0xfc3f407b,0xab646106,0x539b6076}}, // _tõst, _iría_, rdüğ, _קילו,
+ {{0xbeb78026,0xd8768043,0x3f8ea167,0xad9b40f9}}, // _šíře, _لائب, sefu_, _akúj,
+ {{0xdb172005,0xdb0ae0fa,0x3e7c65e6,0x60c720c2}}, // _roxí, _infé, lít_, zyjm,
+ {{0x71a3a5e7,0xd9bf45e8,0x798285e9,0x799b80c2}}, // _кауз, _एक्ट, lfow, _zauw,
+ {{0x2ba6e010,0x6d58a5ea,0x6b9c65eb,0x78a28066}}, // _कोणा, mava, _marg, lvov,
+ {{0x7c2bc1e2,0x386945ec,0xa91ce06f,0xbb46c5ed}}, // _gigr, yrar_, deľt, _редк,
+ {{0x8bd7c076,0x6b9d05ee,0x41e725d0,0x200325ef}}, // _בואו_, rdsg, віва, toji_,
+ {{0x2d852066,0x7dcfa5f0,0x60c00277,0x799c65f1}}, // ýle_, _løsn, ämma, _narw,
+ {{0x200325f2,0xa3b3a010,0x257d85f3,0xd378e2e2}}, // roji_, टात_, náló_, jići_,
+ {{0x7bc081a5,0x2d9ee5f4,0x938b01e1,0x6603a5f5}}, // _nomu, ldte_, _цска_, zonk,
+ {{0x442ca5f6,0x2489006e,0x394641af,0x395905f7}}, // [2d0] _oid_, _upam_, ñosa_, mass_,
+ {{0x68ee213a,0xb8db453a,0x7d0d05f8,0x2004c5f9}}, // ándá, _अप_, _iyas, homi_,
+ {{0x320dc5fa,0x48dea10a,0x3204c5fb,0xa3d02077}}, // yley_, _कइगो_, komy_, वघर_,
+ {{0xb5fb21de,0xe819e077,0x8af785fc,0xe4a4214f}}, // cuár, _नीना_, _çəkm, _трьо,
+ {{0x6b9c65fd,0x26c7a0e2,0x07f820d0,0x69c085fe}}, // _farg, wyno_, _سریع_, _dome,
+ {{0x7c2bc5ff,0x2d912600,0x799c6601,0xdce1a602}}, // _sigr, neze_, _garw, _salı,
+ {{0xc2936050,0x7c2bc603,0xb4bfa604,0x61e8e066}}, // _خیاب, _pigr, ुने_, didl,
+ {{0x6b9c6605,0x3ea6c606,0xd3a48607,0x09ca807c}}, // _zarg, _brot_, _круп, লোবা,
+ {{0xe4e784a4,0x7dc60608,0x799d4296,0x7d048609}}, // _рівн, _móst, _masw, üist,
+ {{0xb5fde3ee,0x7dc60005,0x6d55660a,0x2bcc03af}}, // jušt, _lóst, _jeza, ास्थ,
+ {{0xa3ea00de,0x442d860b,0x645bc60c,0x1bea01c7}}, // една_, _kie_, ssui, едни_,
+ {{0xa3b6860d,0x2004c60e,0xa06a060f,0x7d0d0610}}, // जाय_, comi_, нама_, _byas,
+ {{0xd37a800b,0xed59c611,0x3ea6c612,0x9f47a3ac}}, // ערשט, maž_, _grot_, jinë_,
+ {{0xdee62613,0x799d4614,0xfce62075,0xa3aca4e6}}, // гоги, _aasw, гого, गाए_,
+ {{0x3f87e615,0x7bc1a616,0x29c3c333,0x09cc407c}}, // _ibnu_, _molu, _uñas_, রোফা,
+ {{0x2005e617,0x6fc605e0,0x799c6258,0xd6d9c064}}, // loli_, _cóct, _sarw, wały_,
+ {{0x64a60618,0x18674619,0x3958261a,0x3b86661b}}, // [2e0] _сапа, лати_, pars_, ллаг,
+ {{0x69c1a61c,0x7bc0861d,0x27e940c7,0x7643e07d}}, // _nole, _somu, aian_, _ktny,
+ {{0x8b56e00b,0xc332e095,0x7dca40c4,0x442d861e}}, // ריעס_, צוא_, _lýsi, _bie_,
+ {{0x2a7fc61f,0xd378e0e4,0xf1a3c064,0x9f5825df}}, // ktub_, rići_, _खोलन, forç_,
+ {{0x2005e620,0xa3b68621,0x66056622,0x799e24ec}}, // koli_, जाब_, bohk, _kapw,
+ {{0x66060623,0xd378e0a9,0x60dbc018,0x25a04046}}, // hokk, pići_, dzum, ldil_,
+ {{0xb5fb20e0,0xa3a94010,0x3f6a8624,0x74136555}}, // sság, _गोड_, _пиво_, _طوفا,
+ {{0x6d598625,0x64440194,0xa2a2e0a8,0x2ba4a626}}, // cawa, _otii, गिस्, _गोरा,
+ {{0xd130e13a,0x9fb8805c,0x46a38627,0xf487854f}}, // يمة_, nčí_, _латв, _مالی,
+ {{0x27e94628,0x6e2e6629,0xc6934095,0x5d54e62a}}, // zian_, _jibb, צאת_, шкот,
+ {{0x96f8e62b,0x7d0d0440,0xe8d90096,0x249a204d}}, // тент_, _syas, _mbụ_, _nspm_,
+ {{0x7c22862c,0x3d05c5cb,0x2e16c13a,0x61eaa62d}}, // nmor, _हुवे_, _صباح, lifl,
+ {{0xbcfb462e,0x6e2e6022,0x6b9d462f,0x2d9ce630}}, // _adén, _oibb, _rasg, _tave_,
+ {{0x6aa9429f,0x26dbc076,0x6f0d00b1,0x7d0d0631}}, // _šefo, _אקדמ, _vyac, _vyas,
+ {{0x7dc60632,0x37ab4633,0x6d4b8054,0xa157e095}}, // _póst, нтан_, ybga, _גבוה_,
+ {{0x9f47a0dd,0x442ee634,0xb886806f,0x2499405d}}, // tinë_, _hif_, _zníž, _pssm_,
+ {{0x6d5564fa,0x6e2e6635,0x29186636,0xfc3fa05c}}, // [2f0] _peza, _bibb, _azra_, _čím_,
+ {{0x6d598637,0x61e52174,0x6aa8a638,0xb33ca17a}}, // wawa, _umhl, _ordf, _jaħd,
+ {{0x2ca90066,0x02a760fb,0xdb1aa639,0x25a0417a}}, // _hrad_, ырам, _botí, bdil_,
+ {{0x78874041,0xa3df80c2,0xe45f4069,0x7c2280f0}}, // tīvā, धों_, _tvö_, gmor,
+ {{0x49b8263a,0xe50be046,0x2d9dc011,0x1287863b}}, // _شاید_, _सुनि_, _rawe_, امتی_,
+ {{0xada5a03b,0x6d4d463c,0x442ee63d,0xfe45a63e}}, // ракл, lbaa, _nif_, анко,
+ {{0xdb0640e0,0xabd5c52e,0x2249436c,0x6d5725fc}}, // _inká, ициј, lpak_, _mexa,
+ {{0x6609863f,0x60dbc640,0x6e2e6012,0x9e9580eb}}, // _ukek, tzum, _zibb, _پارچ,
+ {{0x629aa641,0x6d5605cd,0x2ca90048,0x3f87e07d}}, // _esto, _reya, _nrad_, _pbnu_,
+ {{0xed59806e,0xf1f945cb,0x7c2e6642,0x7dd5c0e6}}, // _kožo_, ्कड़_, _xibr, _básc,
+ {{0x64440643,0x2ca9012a,0x60c9c644,0x6ce4a0ff}}, // _stii, _arad_, syem, _ліце,
+ {{0x442ee645,0x9f5ca066,0x6d4d4171,0x6299c0a2}}, // _eif_, mová_, jbaa, _tswo,
+ {{0x8934c646,0xc2ec607c,0x394dc647,0x76452069}}, // _اعما, _করছি_, mbes_, _athy,
+ {{0x7649c4b7,0xe0462197,0xfaa5e648,0x69c2c030}}, // mpey, инги, _вако, _zooe,
+ {{0xa3ccc55d,0x2d9e206f,0xdbcf606f,0x442fc649}}, // _रकम_, ľte_, bľúb, _kig_,
+ {{0x6d4d464a,0x0b46434f,0x27e7e1e7,0xfc3f4071}}, // gbaa, рнан, _hmnn_, _crío_,
+ {{0xe61a264b,0x9f5ca64c,0x8af0003c,0xee37464d}}, // [300] _иди_, hová_, _edər, инт_,
+ {{0xb4c20010,0xed59864e,0x2480464f,0x79898650}}, // ंही_, _božo_, rtim_, _abew,
+ {{0x2007a651,0xfc3f4359,0x6d5bc652,0x78a98653}}, // doni_, _frío_, kaua, _arev,
+ {{0x442fc031,0xdb1ae654,0x7afd4037,0x69c40655}}, // _nig_, rktø, _fxst, _joie,
+ {{0x81cd807c,0x913b000b,0x2d852017,0x200683c5}}, // ষোভ_, דענק, òleg_, rooi_,
+ {{0x7c24403c,0x0466a656,0x7dcb210a,0x9f90a031}}, // lmir, атим, _püsi, _bàá_,
+ {{0xae0604e6,0x853001df,0xdb0f4011,0x9f91c1e9}}, // रवान_, _daɗi, _ancè, _báà_,
+ {{0x7dd8413a,0x298ac657,0xdb18e1ce,0x442ee658}}, // _físe, тско_, _nová, _sif_,
+ {{0x6aa98659,0xab5b03df,0xab27865a,0xdfdae1e1}}, // _gref, kkür, _вота_, вън_,
+ {{0x66e6a656,0x14cae052,0x69d6065b,0x127b8053}}, // _коза, выми_, _alye, _באמע,
+ {{0x3958665c,0xd5b7e65d,0xe737a04e,0x6280c65e}}, // _mers_, рсы_, _тех_, stmo,
+ {{0xb4c22026,0xe6950052,0x7bc40090,0x7de7014f}}, // ्नै_, бимы, _coiu, ріод,
+ {{0x7c2f465f,0x6d572005,0x58870056,0x926b014a}}, // _ricr, _pexa, шына, ърна_,
+ {{0x60160005,0x7dd5c049,0xc88485fc,0x69d60660}}, // bámo, _tásc, mağı_, _elye,
+ {{0xb4d121d6,0xb5fb2661,0xd7f82662,0x64464663}}, // वने_, drát, руя_, _atki,
+ {{0x3207a664,0x27ff4098,0x9f98c0f9,0x62828665}}, // zony_, čuno_, _béé_, ktoo,
+ {{0x61eb814a,0x7f5c2666,0xd9048050,0x9df9662a}}, // [310] vigl, barq, _آی_, гнат_,
+ {{0xcb9920ff,0xdb0ae1ae,0x2007a00e,0x6d58e667}}, // _свої_, _infâ, xoni_, _jeva,
+ {{0x76452156,0x61eb8576,0x79952668,0x78a98057}}, // _uthy, tigl, mezw, _srev,
+ {{0x69c52669,0x63a2866a,0x7995266b,0x6608e06e}}, // _hohe, gdon, lezw, hodk,
+ {{0x660d0167,0xdca5804d,0x9b45a049,0x6fd5c019}}, // _mkak, сали, منشو, _náca,
+ {{0x0cb6a07c,0xaca32088,0x25a120a2,0x6fd8404a}}, // জনৈত, _ndịd, _jahl_, _více,
+ {{0x60cd466c,0x24954315,0x6282866d,0x61eb866e}}, // lyam, şamı_, atoo, pigl,
+ {{0xa3b5666f,0xe3b38343,0xd0f821a9,0x9f5ca326}}, // झाव_, _برس_, ימות_, rová_,
+ {{0x2007a670,0x9f5ca05c,0xcdb84095,0x6fcb203c}}, // poni_, sová_, שפחה_, _hücu,
+ {{0x61fb8536,0x3cff4064,0x69c40004,0x6d5bc671}}, // _hjul, _रखने_, _soie, saua,
+ {{0x7bc3e672,0xa5bb0673,0x81d6c07c,0x11d6013a}}, // _vonu, rmón, _সদর_, متحد,
+ {{0x27e9007d,0xb5fb2674,0x629d4675,0xdb0f4676}}, // _cman_, krás, _osso, _encé,
+ {{0xa069c677,0x6283a0a9,0x60cd400f,0x9045c678}}, // _сала_, ltno, jyam, _انته,
+ {{0x799523cf,0x395d8223,0xd91a8679,0x6608e025}}, // gezw, haws_, טורל, codk,
+ {{0xf99f0355,0x61fb867a,0x216a467b,0x6fd0267c}}, // glès_, _ojul, ливи_, _häck,
+ {{0x3860467d,0x1a2a467e,0xa3cac077,0x2a60467f}}, // tsir_, ужби_, रॉप_, tsib_,
+ {{0xe3b2225b,0x7f5c202f,0x62828054,0x3a200561}}, // [320] _فرد_, parq, xtoo, _ihip_,
+ {{0x320d80ca,0x6609c680,0x291201df,0x25bfc681}}, // _akey_, hoek, _iyya_, hjul_,
+ {{0x2a604682,0x61ee2683,0x319b2095,0x6609c684}}, // ssib_, mibl, _גבינ, koek,
+ {{0x53e5814f,0xab5d80c2,0xb21b222e,0x9f4b03ac}}, // оціа, _poża, ntæn, vicë_,
+ {{0x44394685,0xdce64686,0x44316687,0x61fb80a6}}, // _gus_, _takı, _fiz_, _djul,
+ {{0xddc4649f,0xd838a04d,0x44320163,0x39594057}}, // briš, _puč_, _liy_, _zess_,
+ {{0xa3ca0688,0x44200689,0x63a084f2,0x395a268a}}, // ोसा_, _ohi_, _tamn, _meps_,
+ {{0x7995268b,0x6d43e3b0,0x4f65868c,0xe50be0c5}}, // zezw, _afna, _واقف, _सुधि_,
+ {{0x69dae68d,0x63b38326,0x9603e0c2,0x853000a2}}, // thte, šení, रकूट_, _kaɗu,
+ {{0x7f44005d,0x389b4095,0x6aa94098,0x78a9400a}}, // _afiq, ניינ, _šefk, _ševk,
+ {{0x44200569,0x69c52046,0x4432068e,0x7e62868f}}, // _bhi_, _rohe, _biy_, lsop,
+ {{0x9f4d80dd,0x69c52058,0x63a44690,0x0c26c691}}, // _vjeç_, _sohe, jdin, _умен,
+ {{0x7995249e,0xdb1d4692,0x2246c114,0x6892e0e0}}, // tezw, _posí, _utok_, _کیجئ,
+ {{0x2fc6c693,0x867bc694,0x6d5e6695,0x83fd00e4}}, // _hoog_, _תרגו, japa, vrđu,
+ {{0x7bc640bb,0x27e90037,0x7a296013,0x44268696}}, // _boku, _tman_, _užti, kmo_,
+ {{0x25a12105,0x443a2697,0x60188005,0xb5fb2698}}, // _wahl_, _dup_, dímo, grár,
+ {{0x200a235f,0x798d0699,0x2281a0e0,0x394160fa}}, // [330] bobi_, _ibaw, zók_, tchs_,
+ {{0x2fc6c05f,0x61ee269a,0x200b069b,0xb5fb20e0}}, // _loog_, cibl, loci_, rrás,
+ {{0xddcd603b,0x69c5a052,0xb5fb2005,0xe61f4119}}, // ptaž, öhem, brár, _thô_,
+ {{0x63a2c167,0x27edc69c,0x395a269d,0x7dd84057}}, // _naon, xien_, _zeps_, _vísc,
+ {{0x69d8e510,0x2ed1a026,0xb5fda066,0x7dd5c066}}, // _olve, तन्त, _avša, _básn,
+ {{0x395ee017,0xa3c3469e,0xdcfc2018,0xdb1ae69f}}, // jats_, ्सक_, cerē, nktó,
+ {{0x59a6e026,0xf1c9a0f9,0x201f8067,0x6025c03b}}, // _कोइर, _aláì_, _thui_, одка,
+ {{0x2d8ac0e0,0x6d5f4401,0x78ad06a0,0x7f5f46a1}}, // őben_, naqa, _orav, naqq,
+ {{0x6d5aa6a2,0x85b9a6a3,0x7bdbc6a4,0x63a2c6a5}}, // _feta, _клас_, rhuu, _daon,
+ {{0x6fd02277,0xb801407c,0x6d59c4b7,0x649ee29f}}, // _väck, ্ষিত_, _wewa, kšić,
+ {{0x27e0a068,0x3a20005d,0x649ee6a6,0x62844108}}, // ɗin_, _phip_, jšić, ztio,
+ {{0xe819e029,0xdb040049,0x29c9e019,0x2465a018}}, // _नीरा_, _iniú, _púas_, mēm_,
+ {{0x2b4d04cd,0xdb00800e,0x443a26a7,0x15fb66a8}}, // ñeca_, _ramë, _sup_, ्वार_,
+ {{0x224dc6a9,0xab29e1bc,0xdb0d0057,0xa3ab8046}}, // mpek_, _кола_, _coañ, _कोस_,
+ {{0x61ee26aa,0xe73a46ab,0x78ad06ac,0x412a23d5}}, // ribl, иев_, _erav, рово_,
+ {{0x657bc6ad,0x660f46ae,0x7f5b84cd,0x6d5f46af}}, // nguh, _ckck, _neuq, gaqa,
+ {{0x61ebc05d,0x973ca143,0x443fc227,0x26174057}}, // [340] _mmgl, _laća, nqu_, cçom_,
+ {{0xe69306b0,0x95f486b1,0x7dcb2105,0x628446b2}}, // _کلید, ेक्ट_, _düss, rtio,
+ {{0x5faf0010,0x78ad006e,0x601d06b3,0x6d5aa6b4}}, // _जोडल, _zrav, léma, _reta,
+ {{0x7e6286b5,0x8c35a0f9,0xb33b41de,0x60188005}}, // tsop, _as̟o, _taça, rímo,
+ {{0x3ea9412a,0x25a3617a,0xdb008396,0x2fc6c6b6}}, // cvat_, _fajl_, _kamé, _roog_,
+ {{0x2bd2c010,0x2d8d8079,0x69d9c6b7,0xc2914050}}, // तस्थ, _ebee_, _alwe, _سیمب,
+ {{0x2fc6c6b8,0x30a3a20e,0x2d9826b9,0x63a2c425}}, // _poog_, дрыв, iere_, _paon,
+ {{0x6aa940e4,0x6d5aa6ba,0xaa66c6bb,0xd90da050}}, // _šefi, _weta, оток, دیم_,
+ {{0x614140e0,0xa37b01ae,0xc5f8e041,0x2fc7e6bc}}, // _háló, nhõe, tnē_, _bong_,
+ {{0xd7f7e4e4,0x186aa6bd,0x23604022,0x388e4018}}, // жую_, _важи_, haij_, mērā_,
+ {{0x200b0068,0x443b01d7,0x7f5c6005,0x6aad06be}}, // toci_, _suq_, _merq, _sraf,
+ {{0x628606bf,0x6aad00ba,0xd0ebc04b,0x764d41e7}}, // ftko, _praf, _जेवण_, ypay,
+ {{0x370706c0,0x3949e6c1,0x2ba3c026,0x6835a013}}, // ючов, _şase_, _खोटा, _išda,
+ {{0x6d4646c2,0xdb0ae02f,0xdde24143,0x7c3ee6c3}}, // _afka, _enfá, _ćušk, _épro,
+ {{0x31604037,0x320ce6c4,0x600fe271,0xa802820f}}, // faiz_, mody_, rømt, şıya,
+ {{0xad9b42f4,0x6560c6c5,0xd9ff06c6,0x2b49e3ee}}, // _ojúe, namh, _उदित_, _đaci_,
+ {{0x0459413a,0x69daa12a,0x7f5c6227,0xe9df0049}}, // [350] صلاة_, _olte, _berq, mhú_,
+ {{0x7658e022,0x7f5c66c7,0x200ce0fd,0x2901205f}}, // _kwvy, _cerq, nodi_, _txha_,
+ {{0x7a35a20d,0x6e3c66c8,0x7c22c534,0x9f490187}}, // _ošta, _curb, _ohor, _amaé_,
+ {{0x443ce4e2,0x7c3b86c9,0x248686ca,0x2d9906cb}}, // _muv_, _puur, ftom_, nese_,
+ {{0x6560c6cc,0x2d990197,0x26f6e0c2,0xd945a6cd}}, // damh, iese_, ँपुर_, пели,
+ {{0x2d9906ce,0x6e3c6337,0x2ba7c067,0xf1a3c0c2}}, // hese_, _furb, _ốc_, _खोजन,
+ {{0xdb01a6cf,0x2d9906d0,0x9f4ce004,0x61ed06d1}}, // _kalé, kese_, cidé_, _omal,
+ {{0x6e3566d2,0x3ead86d3,0x6d5c66d4,0x9599c4a0}}, // _hizb, _tret_, _zera, стку_,
+ {{0x395fc6d5,0x249f818c,0xdb01a6d6,0x3866c20d}}, // raus_, _usum_, _malé, _cvor_,
+ {{0x7f5d4004,0x63a406d7,0xa3c90021,0x6e3d46d8}}, // _lesq, _sain, लॉस_, _musb,
+ {{0x601606d9,0xb33b4057,0x1d09e6da,0x68296013}}, // lámi, _naço, сени_, _uždu,
+ {{0x2440c0d1,0xdb0bc03e,0x628603e0,0x37bda07c}}, // _fòm_, _engá, ttko, _অতির,
+ {{0x98abe121,0x3ea1200e,0x2fda207d,0xe449c049}}, // ılık_, _asht_, _plpg_, _التى_,
+ {{0x853ce013,0x600fe271,0x22840009,0xdd99c431}}, // ndėl, røms, rök_, leň_,
+ {{0x6d4566db,0x6d5c66dc,0x98a5e3ed,0x291e26dd}}, // dcha, _rera, palı_, åta_,
+ {{0x2d812096,0xe1ff20c2,0xdb0f46de,0x27ed81e7}}, // _eche_, wrót_, _encí, _nmen_,
+ {{0x3ea1200e,0x75d34049,0x6e3d4025,0xdb1d4066}}, // [360] _esht_, ريكا, _cusb, _posá,
+ {{0xa7fd06df,0x7bdb8194,0x3ceca10a,0xe1ff41e9}}, // nsın, _aluu, _अइले_, _ejó_,
+ {{0xd910254f,0x7f5c66e0,0xb4d666e1,0x7bc286e2}}, // _شیر_, _verq, िने_, tjou,
+ {{0x6d5c66e3,0x934386e4,0x6014e5df,0x24868076}}, // _wera, енце, ràmi, ttom_,
+ {{0xa1164050,0x74138050,0x644162f8,0x27ff813e}}, // _روست, روها, _élin, _djun_,
+ {{0x7c29c06f,0x7c3e26e5,0x3f990108,0x9f5ca02e}}, // emer, _kupr, zesu_, lovú_,
+ {{0x6d5d46e6,0x61ee66e7,0x7c29c0cb,0x20000291}}, // _yesa, _imbl, fmer, _ojii_,
+ {{0x7d040057,0x69c446e8,0x8fa38013,0xada380db}}, // _oxis, njie, _пасе, _пасл,
+ {{0x7416a343,0xb4ac6028,0x26c04057,0xfc3f004a}}, // _فورا, _कछु_, nxio_, tvím_,
+ {{0x6d5e26e9,0x6e9384aa,0x63a646ea,0x9f9426eb}}, // _nepa, _سلما, _lakn, _jää_,
+ {{0x2b4d02a8,0x38c8e6ec,0x53990052,0x7ddda187}}, // ñeco_, زادی_, овня_, _fèsb,
+ {{0x9c8701ce,0x3ddfc171,0x656280ce,0x671f63fa}}, // _načí, chuw_, naoh, _भरसक_,
+ {{0x61ee66ed,0x395ea037,0x7f5d46ee,0xdb00807b}}, // _ombl, _kets_, _resq, _inmó,
+ {{0xaa66e6ef,0x7d1dc6f0,0x3ea12037,0x3866805d}}, // _етик, üsse, _psht_, isor_,
+ {{0x6e3d46f1,0xd48fe07a,0xdb05600e,0xb17b022e}}, // _susb, _яр_, rdhë, rhåb,
+ {{0x69dc66f2,0xd117e053,0x6d5e21e7,0x69cae6f3}}, // _alre, _מקוה_, _eepa, _hofe,
+ {{0xdb01a6f4,0xb5fb2019,0x6d5d46f5,0xe0df40f9}}, // [370] _valé, lsám, _vesa, _efòn_,
+ {{0x799ae6f6,0x69cae057,0x67e06156,0x7bc986f7}}, // netw, _jofe, _nöjd, _soeu,
+ {{0x98a7a315,0xdb160157,0x27e046f8,0x75484009}}, // yanı_, _anyè, bhin_, észí,
+ {{0x7e9be00b,0x7dd5c066,0x3a248071,0x25e88028}}, // מסבו, _pásm, _chmp_, चोली_,
+ {{0x7bdd46f9,0x98a7a6fa,0x25a5a6fb,0xe5a5a6bb}}, // _ilsu, vanı_, _pall_, дили,
+ {{0x76ba4076,0x69cae0fd,0xf9c7a6fc,0x6d4720c2}}, // _ומשפ, _nofe, ящен, ncja,
+ {{0x78a2c04d,0x27ed807d,0x6aa2c2f4,0x78ad4046}}, // _asov, _tmen_, _asof, lvav,
+ {{0x56b82095,0x63b8e030,0x394686fd,0x9999604a}}, // לפון_, _anvn, ccos_, _kusů_,
+ {{0xdb1c61ae,0xdd99c06f,0xa2828050,0x98a7a315}}, // _porç, reň_, _لینو, ranı_,
+ {{0x7ddcc057,0x27e1017a,0x7dd84071,0x6b82c2dd}}, // _pésc, _ċina_, _aísl, _ecog,
+ {{0x66e5e6fe,0x6d5f06ff,0x7dd02277,0x38cac050}}, // фона, _beqa, _mäst, مایی_,
+ {{0x7e698013,0x200f8700,0x764d0614,0x24894701}}, // _kvep, nogi_, _btay, ktam_,
+ {{0x644bc3e0,0xdb0ae105,0x398fa0b0,0x09e30702}}, // _utgi, _anfä, pısı_, тоян,
+ {{0x7bcbc071,0x62956171,0x6b9bc703,0x601d0057}}, // _hogu, _opzo, leug, bémo,
+ {{0x644d0004,0x6d5f017b,0x753b8704,0x25a94705}}, // _etai, _feqa, _oguz, fdal_,
+ {{0x395f8706,0xe1f200eb,0x9f942052,0x38668707}}, // _neus_, _جست_, _sää_, ysor_,
+ {{0x63a64708,0x6563a709,0xfc3f4049,0xb4d7870a}}, // [380] _vakn, kanh, _cuí_, ानो_,
+ {{0x6fdcc004,0x6aa400bb,0xc58ee067,0x00da6049}}, // _méca, _isif, _lồng_, يبات_,
+ {{0x7a01c018,0x3f9a604d,0xb33ca0f7,0x27e0470b}}, // zēta, repu_, _daħl, phin_,
+ {{0x7bcbc70c,0x3eb20066,0x799ae0c2,0x601de425}}, // _nogu, _kryt_, zetw, kèmo,
+ {{0x4425a70d,0x0443870e,0xac0aa70f,0x24894710}}, // _bhl_, вечн, онда_, ctam_,
+ {{0xafdb6042,0x4425a153,0xf1bf4067,0x7763a017}}, // _støv, _chl_, _thác_, ganx,
+ {{0x316d8019,0x9f5822a8,0xf7728711,0x63ade0a2}}, // _fdez_, loró_, _لاء_, _ɗant,
+ {{0x2d804712,0x7ddcc071,0x69d88018,0x2613c41a}}, // ngie_, _bésa, īves, dãos_,
+ {{0x5a34e14a,0x6563a713,0x442b003e,0xfcaa86ec}}, // ннит, banh, vmc_, _بازو_,
+ {{0x44386054,0xddc9e714,0x6f0da20f,0x63a9c715}}, // _jir_, treš, şaca, aden,
+ {{0xdb1c6042,0x2613c1ae,0x44446716,0x69cbc717}}, // _forå, gãos_, nq_, _foge,
+ {{0x3f9ca718,0xafdb614f,0x2b51e106,0x63a8a719}}, // levu_, _utøv, ızca_, _hadn,
+ {{0x7bcae71a,0x61fd06b5,0xb8f3c1a6,0x3eadc143}}, // _tofu, rnsl, _হে_, cvet_,
+ {{0x799c22ad,0x4fc6e71b,0x69d8a71c,0x224003c5}}, // gerw, _ісла, nkve, _duik_,
+ {{0x4034271d,0x78a4071e,0x3b4c4125,0x1d4c4079}}, // терс, _esiv, _ọdịk, _ọdịm,
+ {{0x63a760e0,0x386901d8,0x656e6153,0x2905217a}}, // _sajn, _tvar_, _adbh, ħla_,
+ {{0xd7e720ff,0x63a76022,0x25a9471f,0x799d0720}}, // [390] _жіно, _pajn, rdal_, mesw,
+ {{0x6fd4a355,0x24894721,0x78ad4722,0x63a9c723}}, // _fàci, rtam_, rvav, zden,
+ {{0x656444ec,0xbcfb42f4,0x78ad459f,0xaca32079}}, // baih, _adét, svav, _mpịg,
+ {{0x9f5820e6,0x24894724,0x61e28725,0x63baa726}}, // boró_, ptam_, ghol, _antn,
+ {{0xf99241a9,0x6e2d4727,0x24444728,0x44386729}}, // _ערך_, mmab, _göm_, _fir_,
+ {{0x61f52181,0x628aa271,0x6602c052,0x6a85a06c}}, // nizl, ftfo, _ajok, елка,
+ {{0xd5fba125,0xfc46206f,0x61e2872a,0x291860a2}}, // _asụ, žíva_, bhol, _gyra_,
+ {{0x6fdda5df,0x6e92a049,0x6563a72b,0x7e7bc320}}, // _dèca, اليا, sanh, tuup,
+ {{0x2d9d872c,0x4426c72d,0x2918600e,0x2b4b4012}}, // mewe_, _dho_, _zyra_, _mfcc_,
+ {{0x7bcbc72e,0x2d9ca72f,0x7c2d4730,0x60e9e731}}, // _wogu, ceve_, hmar, змом_,
+ {{0xef19e54a,0x69cbc732,0x9f4b0733,0x7ac4a734}}, // ямо_, _toge, ficà_, _исце,
+ {{0x799c2735,0xdb0bc736,0x64408737,0x2cb201cd}}, // werw, _angå, _yumi, _pryd_,
+ {{0x7ccae5fc,0xe8034738,0xe6a8404a,0x9f47a106}}, // lərl, रचना_, _příš, ginç_,
+ {{0x270e8067,0x64a62013,0x69c12046,0x644821de}}, // ộng_, нага, ölep, ídio,
+ {{0xfce62739,0x809f60a8,0xc0e6273a,0x6441a73b}}, // хово, _गैले, ховк, _juli,
+ {{0x63a9873c,0x3ea0473d,0x61e2873e,0x6e2d4114}}, // _naen, twit_, xhol, gmab,
+ {{0x27e00497,0xe9da673f,0x6441a740,0x442dc741}}, // [3a0] _ilin_, пка_, _luli, ime_,
+ {{0xbcfb41e9,0x2fdf80ae,0x3d112010,0x26e5a07c}}, // _adés, _alug_, _दुवे_, _কখনো_,
+ {{0x2003e742,0x38c5000e,0xb17b4065,0x39494022}}, // čjih_, _tërë_, _slåe, bcas_,
+ {{0x2d820743,0x44394744,0x65660745,0x2a694022}}, // ngke_, _gis_, nakh, bsab_,
+ {{0x7e69c065,0x76444746,0x2fcd81e7,0x6561a747}}, // jsep, qqiy, _doeg_, _belh,
+ {{0x2d8ea0fa,0xe4a6a4c2,0xa520804b,0xd25aa159}}, // uffe_, ерио, _बरीच_, пци_,
+ {{0xdb156748,0xe2976470,0x27ffc069,0xdcfc2041}}, // _dozó, _пар_, fnun_, derī,
+ {{0x2d9ca749,0x69c0c437,0x66040385,0x8026863b}}, // seve_, वाधी, _njik, _سرزم,
+ {{0x75240610,0x60c44005,0x32078009,0x98ab0181}}, // _nziz, nxim, énye_, yacı_,
+ {{0x6441a054,0x8e978087,0xf1bae067,0xa5bb074a}}, // _fuli, _אדמו_, _thơm_, ológ,
+ {{0x91a6e125,0x4426c74b,0xd6d7e74c,0x61e4474d}}, // _atọ_, _tho_, етя_, khil,
+ {{0x04dba095,0x201125fb,0x1ae6c74e,0x656600bb}}, // _מקבל, wozi_, _поем, gakh,
+ {{0x6e3aa17a,0x9f4b05df,0x6d4aa049,0x442ea74f}}, // _kitb, ricà_, ocfa, omf_,
+ {{0x7afb40e4,0xf1bf4067,0x7c3aa750,0x7c2d4751}}, // _žuto, _chán_, _jitr, wmar,
+ {{0x38694068,0xe4e420ff,0x29dc4005,0x656600bb}}, // wsar_, гітн, _vían_, bakh,
+ {{0x7a25c2c8,0xb6060066,0x7bdf0071,0x7ddcc752}}, // póte, _pláš, _ulqu, _méso,
+ {{0x63a98753,0xd838c030,0x9f9c40f9,0x24986631}}, // [3b0] _raen, _asčk_, _díà_, _aprm_,
+ {{0xdb01a5e0,0x443a2754,0x44290755,0x7ccae03c}}, // _salí, _gip_, _hha_, yərl,
+ {{0x6fdcc005,0xe1f1a050,0x25aa6756,0x34d98026}}, // _néco, یست_, _dabl_, भन्द,
+ {{0xdb1aa757,0xdfd5419c,0x98ace19f,0x2ca6c0fd}}, // _intè, _ромы, nadı_, _osod_,
+ {{0x6fd6e0fa,0x98a58758,0x10a580de,0x443b0759}}, // _tâch, тиле, тилн, _miq_,
+ {{0x25a9875a,0xf994200b,0x443a2017,0xeb96a6bd}}, // žal_, ָרק_, _xip_, виш_,
+ {{0xed59807f,0x2fc76069,0xd2620018,0x656601fb}}, // _anže_, öngu_, _ziņā_, yakh,
+ {{0xe739c75b,0x4427e75c,0x77672022,0x3a290067}}, // мей_, _shn_, hajx, _nhap_,
+ {{0x6d49c75d,0xf8bf8066,0x2499417b,0x6442c75e}}, // rcea, _šéf_, _jpsm_, _cuoi,
+ {{0x7641a035,0xdb01a1ae,0x442ea057,0x7d09875f}}, // _tuly, _calã, cmf_, _ixes,
+ {{0x27e00760,0xd6dba04e,0x6441a761,0xdb03e030}}, // _plin_, _эта_, _uuli, _annò,
+ {{0xa06a0762,0xdb1c6763,0x3167a764,0x3940e0ba}}, // мама_, _corù, lanz_, _şist_,
+ {{0xdb06406f,0x20076098,0x3e416013,0xdb1aa157}}, // _také, čnij_, _dėti_, _antè,
+ {{0x3f832765,0x7dd840c4,0x63abc0c4,0xd24ec711}}, // ggju_, _tísk, _hagn, _بنو_,
+ {{0x703a403b,0x442f8025,0xdb08a0d5,0x96632766}}, // дчас_, kmg_, _cadè, икре,
+ {{0xeb97412a,0x2d832171,0xd00f0049,0xddcd607f}}, // кис_, agje_, _ولو_, vraš,
+ {{0x7643e767,0x2367a768,0xe0434769,0x61f64121}}, // [3c0] _huny, kanj_, инти, riyl,
+ {{0x69cf4057,0x61f643ed,0x7e7e676a,0x6805403b}}, // _eoce, siyl, tupp, rėda,
+ {{0x63ad476b,0x69dbc04e,0x7d1b84ec,0x7dd4a5df}}, // bdan, kkue, _ayus, _màst,
+ {{0x7c3ee024,0x27e08049,0x6443e76c,0x69dc276d}}, // _èpre, óin_, _muni, lkre,
+ {{0x6b82876e,0x69c9c098,0x25aa676f,0x7c298770}}, // rgog, djee, _tabl_, _dher,
+ {{0x25a04771,0xb4de40c2,0xdb01a031,0x9cd70076}}, // leil_, तनी_, _balà, כונה_,
+ {{0xdb08a187,0xdb03e089,0x9f5ca089,0x3263a772}}, // _kadé, _baní, cový_, атыв,
+ {{0x225e205c,0xdb01a01f,0x63aae069,0xa01b2009}}, // átky_, _salã, _safn, ltöt,
+ {{0x2367a23d,0x6d408773,0x8c468774,0x7763e775}}, // banj_, _igma, _резе, _benx,
+ {{0x27e68776,0xbbd12026,0xf1bae119,0x7dd4a0b8}}, // lhon_, _सकेक, _khơi_, _càst,
+ {{0x63ad4777,0x25bea778,0xb97b6095,0xddc2a064}}, // ydan, _intl_, וניי, zpoś,
+ {{0x27e68426,0xa785c1ac,0x44290779,0x6fd5c071}}, // nhon_, _مشهو, _qha_, _bácu,
+ {{0x051f207c,0xf8bf60a8,0xfc3f40f9,0x69cf477a}}, // _দলের_, _एप्प, _asín_, _roce,
+ {{0xf1bf4067,0x69dc277b,0x5c042099,0x6ab6077c}}, // _pháo_, gkre, аяса, _dryf,
+ {{0x6280c77d,0x63ad414f,0xed5f8066,0x2d83277e}}, // lumo, tdan, ážka_, rgje_,
+ {{0x442f077f,0x5fc5c04b,0x2018618c,0x7c2de197}}, // _òg_, वायल, _akri_, _èarr,
+ {{0x6fdcc0fa,0x442a66f8,0x24804017,0x63ad008b}}, // [3d0] _récl, _bhb_, guim_, _iaan,
+ {{0x2d876780,0x6e298781,0xf1bf4067,0x3b546782}}, // üne_, _sheb, _tháo_, икур,
+ {{0x7ae9c783,0x7c298784,0x601ec00e,0x61e2c785}}, // zzet, _pher, rëmj, _olol,
+ {{0x7ddda5cd,0x9f5ca05c,0x1a654043,0xdd01c23d}}, // _pèso, rový_, _میری_, _žučn,
+ {{0x3b098786,0xbc19a0db,0x6568e787,0xf1bf4788}}, // çaq_, нікі_, kadh, _iná_,
+ {{0x6e3c6789,0xe4f602b4,0xfc3f0005,0x27e68561}}, // _girb, ीपति_, lvís_, ahon_,
+ {{0xc984a78a,0x7c3b878b,0x6563e78c,0xb17b0262}}, // ручи, _tiur, _renh, thål,
+ {{0x443ce022,0x764520c7,0x25adc78d,0xa3ab80c2}}, // _aiv_, _muhy, udel_, _कोच_,
+ {{0x25adc78c,0x65640105,0x601de5df,0x7c2ae049}}, // rdel_, _reih, lèmi, _bhfr,
+ {{0x47c5e78e,0x69dbc78f,0x7aa5e1b1,0x2d83a065}}, // убов, skue, гиоз, _øje_,
+ {{0x77640790,0x69c46010,0x68e9c791,0x3255a792}}, // _peix, राती, szed, _свер,
+ {{0x9ac4c17a,0x23694793,0x6016a1de,0x7bcb8133}}, // _biċċ, jaaj_, lâmp, njgu,
+ {{0xdb1aa066,0xe8dfa119,0x69dc2794,0x6443e795}}, // _motý, _diễm_, tkre, _wuni,
+ {{0x69dd0052,0xaae5e566,0xdb08a18c,0x7643e796}}, // akse, _مسعو, _sadé, _tuny,
+ {{0xe81ba028,0x442a6797,0xf3f0e13a,0x3ea90798}}, // _पीछा_, _rhb_, _بأن_, _esat_,
+ {{0xdca64799,0xbea6479a,0x29050181,0x8c43620f}}, // лаби, лабк, ğlar_, ışla,
+ {{0x7c3c679b,0x443ce022,0xdb064157,0x6b84479c}}, // [3e0] _pirr, _yiv_, _ankò, rgig,
+ {{0x79898020,0xef17479d,0x2904203c,0x20032361}}, // _acew, уму_, şmaq_, cnji_,
+ {{0x69c2a79e,0xdceae5f9,0xf99f4187,0xcb6a45d0}}, // लासी, ćiće, _ijèn_, _пане_,
+ {{0x5f462062,0x7986079f,0x7a3ee018,0x3ebf47a0}}, // _جنگل, ngkw, _jūta, _šute_,
+ {{0x248047a1,0x6281e7a2,0x2449c7a3,0xdce0c018}}, // quim_, hulo, _núm_, kamā,
+ {{0x601d07a4,0x27e687a5,0x656641e7,0xaca48088}}, // rémi, shon_, _hekh, _ntụp,
+ {{0xa5bb4049,0x63ae61e7,0x1ae66052,0x998d806f}}, // _shói, _iabn, _совм, _sieť_,
+ {{0x644647a6,0x200323ce,0xa01b2277,0x9874c080}}, // _kuki, znji_, ntör, алиц,
+ {{0x80a407a7,0x63ae6022,0x090687a8,0xd91086b0}}, // _चैले, _kabn, _спан, _بیش_,
+ {{0x27134016,0x7dd5c13a,0x97a6e07a,0x443ce286}}, // ệng_, _sást, урил, _qiv_,
+ {{0xa01b27a9,0x1959002d,0xb97b600b,0x764647aa}}, // ktör, каны_, ענטי, _luky,
+ {{0x9a86c7ab,0x6280c019,0x443247ac,0x7528a018}}, // _букл, pumo, jmy_, _izdz,
+ {{0x7dd5c4cd,0x69dd00ff,0xdb18e019,0xb4de4046}}, // _vást, rkse, _inví, तनो_,
+ {{0xe7bf607c,0xd7c7a010,0x3a2b4071,0x61e400bb}}, // _ইত্য, लायच, _shcp_, _elil,
+ {{0x539be095,0x7d1d4249,0x7dd5c13a,0x65652098}}, // _ניהו, _syss, _tást, _vehh,
+ {{0x6d5c27ad,0xdb0bc56e,0x3b55401f,0xa3e9004a}}, // abra, _bagè, ркар, _यति_,
+ {{0x5fc5c010,0xa2c60466,0xb33b01ae,0x8c4360b0}}, // [3f0] वातल, ामण्, beça, ışma,
+ {{0xdcfc2018,0x212906fa,0x443247ae,0xad9b40c4}}, // sgrā, _izah_, amy_, _kjúk,
+ {{0x9f59000e,0xa3e907af,0x5f186466,0x4395403b}}, // jisë_, _यता_, _बुद्_, _канс,
+ {{0x2bc321d6,0xa5678062,0x63a1e7b0,0x644647b1}}, // शावा, ادان, zeln, _fuki,
+ {{0x98b12315,0x6281e7b2,0xdcebc17a,0x6d41a1e7}}, // mazı_, zulo, _ibgħ, _pgla,
+ {{0x7987205d,0xd47a4053,0x2be207b3,0x867a4076}}, // ngjw, _באשל, _पत्थ, _ברשו,
+ {{0xd24ea7b4,0x68ed47b5,0x228f47b6,0x7a01c041}}, // ونو_, lzad, lük_, lēti,
+ {{0x2a6dc10a,0x7df460c2,0x244aa067,0x7bdf40da}}, // tseb_, yńsk, _dùm_, nkqu,
+ {{0x394dc146,0x660982e2,0x386dc7b7,0x661b8096}}, // rces_, _ijek, user_, _ikuk,
+ {{0x61fae1e7,0x8467a1e1,0xe8dfa016,0x443ea121}}, // gitl, _бъде, _niệm_, _git_,
+ {{0x9f5900dd,0x656767b8,0x1d09e1c7,0x7843a0e0}}, // cisë_, _mejh, тени_, _bőve,
+ {{0x1959c7b9,0x656b87ba,0x7c3e27bb,0x3a2d805d}}, // _рады_, nagh, _sipr, _jhep_,
+ {{0xaa4604a4,0x61e9c7bc,0xfc49e067,0xb6060031}}, // регл, mhel, _bậc_, _aláá,
+ {{0x7c2d07bd,0x61fbc7be,0x3f8b47bf,0x0eb32046}}, // _ehar, liul, _accu_, ुमंड,
+ {{0x6283a7c0,0x443f8057,0x67e3410a,0xa96727c1}}, // nuno, _oiu_, _sõja, риса_,
+ {{0xd848c016,0x442047c2,0x764647c3,0x78ba606f}}, // _họa_, uli_, _puky, _štvo,
+
+ {{0x656b87c4,0x1f6667c5,0x6fdcc004,0x645567c6}}, // [400] dagh, икам, _déch, _atzi,
+ {{0x60c9c5df,0x661b87c7,0xa3ea27c8,0x660980f9}}, // ixem, _akuk, _मति_, _ajek,
+ {{0x68ed403e,0x27e947c9,0x7b6687ca,0x61fc27cb}}, // azad, bhan_, атле, mirl,
+ {{0x69c1a7cc,0x443f8355,0x61fbc167,0x2367e7cd}}, // _anle, _ciu_, jiul, _lenj_,
+ {{0x61fc213a,0x2fcdc2e2,0xe9f92031,0xf3c920eb}}, // oirl, djeg_, _asẹ_, _شبیه_,
+ {{0x442d87ce,0x61fc27cf,0x0ca68290,0x6283a7d0}}, // _ehe_, nirl, _गनीम, funo,
+ {{0x68fb403a,0x98b24121,0x9f45e04a,0x6d5d0025}}, // _ľudi, mayı_, chlé_, ybsa,
+ {{0xdb00803a,0x7e62c0d1,0x69c1a7d1,0x61fc27d2}}, // _pamä, _pwop, _enle, hirl,
+ {{0x63a3a0e2,0x973ce504,0x27f867d3,0xb376a050}}, // benn, deća, _kmrn_, _نداش,
+ {{0x6283a7d4,0x9f5822a8,0x644087d5,0x2d8ae106}}, // buno, biré_, _himi, übe_,
+ {{0x91e6c7d6,0x764087d7,0x270247d8,0x0bb76095}}, // _коже, _kimy, लपुर_, בלים_,
+ {{0x7aed42b1,0xdb1aa017,0x7e62c157,0xe4f967d9}}, // yzat, _fotò, _twop, ्पति_,
+ {{0x9f5907da,0x201827db,0x6568a050,0x2bc964f6}}, // nisé_, fori_, _kedh, रामा,
+ {{0x67fec041,0x321827dc,0x7e64048d,0x6fdcc0fa}}, // rīju, gory_, _dwip, _séch,
+ {{0x7c2287dd,0x6568a7de,0x67fec041,0x27e04020}}, // klor, _medh, sīju, fkin_,
+ {{0x3edf0125,0xe3b041b3,0x600803df,0x6618a066}}, // _apụọ_, ورق_, nıml, hovk,
+ {{0x2d8ca7df,0x809f6064,0xe695013a,0xdcf5603c}}, // [410] _ocde_, _गैजे, _الخد, _razı,
+ {{0x6447612b,0xd6db67e0,0x660980ff,0x6283a7e1}}, // _puji, кте_, _sjek, yuno,
+ {{0xb4e16010,0xfa34213a,0x6808c04a,0x60080106}}, // धने_, _بريد, vědn, kıml,
+ {{0x644087e2,0x27e047e3,0x7c2287e4,0x6568a614}}, // _cimi, ckin_, glor, _aedh,
+ {{0x224900c7,0x7cfc6271,0x644760a2,0x6abaa7e5}}, // _juak_, _pårø, _wuji, _artf,
+ {{0x61fd07e6,0x78baa199,0x6aad0167,0x764d403c}}, // hisl, _brtv, _msaf, mqay,
+ {{0x3ea947e7,0x3f87a098,0x64556108,0xbab5a20e}}, // lwat_, rgnu_, _utzi, сёлы,
+ {{0x27e6c071,0x1a6880d0,0x2367e64e,0x6f04c7e8}}, // _clon_, _چینی_, _senj_, _žica,
+ {{0x5ef9607c,0xd7f887e9,0x61fbc12a,0x6448a187}}, // _অডিও_, _тур_, riul, _eudi,
+ {{0x63a447ea,0x6448a7eb,0x7bc2c095,0x27fd81cd}}, // zein, _fudi, _enou, liwn_,
+ {{0x60dbc7ec,0x6aad07ed,0x61fbc12a,0x7a23810a}}, // syum, _asaf, piul, võtj,
+ {{0x3ead8012,0x61e9c1fb,0x201907ee,0xef1f00b0}}, // _kset_, qhel, gosi_, rgün_,
+ {{0x6b9b87ef,0x7ddf000e,0xe730c7f0,0xd848c119}}, // _mbug, _mësh, _قصه_, _tọa_,
+ {{0x973ce6be,0x7ddf000e,0x2bc5c7f1,0xe8df8067}}, // seća, _lësh, वावा, _ngọn_,
+ {{0xdb01e0e0,0x320687f2,0x66e327f3,0xc255e50f}}, // kelé, ynoy_, пора, _اختت,
+ {{0xa3b827f4,0x2d8d8022,0x2291e009,0x395fc7f5}}, // _घोर_, _ncee_, mák_, nbus_,
+ {{0xf4130095,0x644087f6,0x61fc27f7,0x321826c4}}, // [420] _מפה_, _rimi, pirl, pory_,
+ {{0x00ca2399,0xdce3e2f9,0x20182746,0xc9aa27f8}}, // _блок_, _lenč, qori_, _свое_,
+ {{0xdb03e01f,0x656e20bb,0xb86567f9,0xa1364050}}, // _danç, mabh, قانو, _گردش,
+ {{0x765c20ae,0xed5a414a,0x3d18c7fa,0x9f5903bb}}, // mpry, _щом_, _पडले_, tisé_,
+ {{0xeafa8085,0x7989c7fb,0xad9b4031,0x25f6c029}}, // درات_, dgew, _ajúw, एफसी_,
+ {{0x656e26f1,0x2d8a27fc,0x41c967fd,0x80d1407c}}, // nabh, ngbe_, राणस, _সেক্,
+ {{0xa3cc4026,0x6618a05c,0x7641a0e2,0xd467a1e3}}, // लाप_, rovk, _dily, бије_,
+ {{0x6e2444ec,0x6b89c1cf,0x69d607fe,0x7989c7ff}}, // nlib, ggeg, _noye, ggew,
+ {{0x7bd56786,0x6d46408b,0x765bc114,0x78bb84f0}}, // _yozu, _mgka, gpuy, _gruv,
+ {{0xd910e555,0x7648a1e7,0x9f44c2a8,0x31c6e052}}, // ویش_, _tudy, timó_, ссив,
+ {{0x69c401d7,0xfbc40010,0x61e28800,0xf54f4125}}, // _bnie, षांम, okol, _kụta_,
+ {{0xf400607c,0x5693e09e,0x7c29417a,0x24804801}}, // ্তির_, чают, ċerk, krim_,
+ {{0xa3cc4802,0xead4c09c,0x64486197,0xdb076803}}, // लान_, _фоль, _èdif, _majá,
+ {{0x2291e804,0x6d464805,0xfc49a089,0x61fd0806}}, // bák_, _agka, slíš_, sisl,
+ {{0x9b58212a,0x69d60807,0x43950808,0x9f5a60fa}}, // сист_, _foye, _дайс, cipé_,
+ {{0xa5bb4071,0x6aad035f,0xdb03e809,0x61e2880a}}, // _ahór, _tsaf, _sanç, jkol,
+ {{0xe1ff080b,0x4424c10a,0x61e28300,0x7642c0ce}}, // [430] mió_, ilm_, dkol, _mioy,
+ {{0xdd310064,0x9f45620f,0x61e2880c,0x6e244035}}, // _mężc, _ölü_, ekol, alib,
+ {{0x6441a80d,0x2bc7a04b,0x61e340ba,0x660d01b4}}, // _rili, लासा, _înlo, _kjak,
+ {{0x3ebe2065,0x19ab480e,0x60da2046,0xf8bf4031}}, // ætte_, _стоп_, ätme, _ayée_,
+ {{0xdb052121,0xa5bb4049,0x6287280f,0x62952108}}, // _bahç, _fhór, lujo, ltzo,
+ {{0x7a05403b,0x660d000a,0x32094810,0x973ce23d}}, // dėti, _ljak, lnay_, jećo,
+ {{0x656f0811,0x6b89c812,0x02b3a021,0xdb08a017}}, // kach, tgeg, ंटेन, _cadà,
+ {{0xb068a049,0x656f0813,0xe1ff0071,0x395fc006}}, // حصول_, jach, jió_, ubus_,
+ {{0xd848c814,0x60cd4815,0x6441a816,0x7658e03b}}, // _wọn_, nxam, _tili, _atvy,
+ {{0xcf39e0ff,0x2251e066,0x61ed4817,0xdb1e2106}}, // ичні_, ízko_, hhal, _popü,
+ {{0x060a009e,0x6280c58f,0x69c40306,0x8e0a01b1}}, // инок_, armo, _pnie, инов_,
+ {{0x2291e723,0xeaba0818,0x644ae00f,0x3634c049}}, // rák_, айм_, _dufi, _فرنس,
+ {{0xdb03a066,0x81aca07c,0xf770c819,0xe56ee81a}}, // dené, গার_, _سان_, _оз_,
+ {{0x69d72057,0xed59e81b,0x7bc3e765,0x9f45e019}}, // _coxe, _ток_, _unnu, riló_,
+ {{0x6609c296,0x644bc81c,0x656bc0c7,0x752d081d}}, // onek, _hugi, _kegh, _ezaz,
+ {{0x6443e81e,0x543606b0,0xceb2a0be,0xa6ca001e}}, // _hini, _برتر, ויל_, _نوبل_,
+ {{0x3a20048d,0x4425e81f,0xf34ec079,0x2fc0c049}}, // [440] _ikip_, oll_, _kụrụ_, _óige_,
+ {{0x201f8820,0xdb1c6069,0x6443e821,0x6281e23c}}, // _akui_, _borð, _jini, orlo,
+ {{0xd9464822,0xeb9a412a,0x5ea66047,0xf8c9e0aa}}, // _мени, _кин_, تمال, िमिय,
+ {{0x64a68823,0x61e98425,0x27edc07f,0x2bdc6026}}, // жада, _clel, jhen_, _मकवा,
+ {{0xf1bf4016,0xa6dee067,0x779140e0,0x93fe204b}}, // _cháu_, _ngưn, _سیلا, _उगाच_,
+ {{0xdb1aa824,0x7524000f,0x2716e04a,0xf34ec088}}, // _antá, _myiz, něné_, _nụrụ_,
+ {{0xafe6812a,0x9f5ca089,0x5333a825,0x6d46c089}}, // _довл, nivé_, мешт, žkam,
+ {{0xd49ac826,0xe1ff05df,0x6d47600e,0x6458e098}}, // аро_, xió_, _zgja, _rtvi,
+ {{0x661ae827,0x3eafc119,0x9f47a2a8,0x973ce20d}}, // totk, _csgt_, ginó_, tećo,
+ {{0x33d5a139,0x6fdcc004,0x764bc1e7,0x7643e1df}}, // _ніхт, _récu, _dugy, _ciny,
+ {{0x2475e041,0x69c64828,0x32094025,0xdce72018}}, // gām_, _onke, ynay_, kajā,
+ {{0x2b408552,0x7a202009,0x29dc40e6,0xdb03a005}}, // žicu_, zött, _mías_, xené,
+ {{0x6281e829,0x2fc6c08b,0x47ada1a6,0x9f47a82a}}, // arlo, _inog_, চারী, cinó_,
+ {{0xd250e13a,0xbddb4355,0x36d5282b,0x7643e82c}}, // _كنت_, _cièn, погр, _giny,
+ {{0x63a7282d,0x63b522b8,0xf96b41e1,0x3a20007d}}, // rejn, rdzn, ирай_, _fkip_,
+ {{0x61e44644,0x6288e82e,0x7bd8e488,0x5334282f}}, // gkil, mudo, _hovu, фест,
+ {{0x68fb406f,0x69d72057,0x752d0830,0x321ca066}}, // [450] _ľuds, _toxe, _uzaz, kovy_,
+ {{0x2d8ce831,0x27edc0e8,0x68120018,0x3f8ce832}}, // ngde_, zhen_, rāde, ngdu_,
+ {{0x644521e7,0xd848c125,0x2d8ce833,0x6d5520c2}}, // _iihi, _sọl_, igde_, mcza,
+ {{0xdb01a049,0x61ff4786,0x6dc781c1,0xd848c291}}, // _talú, qiql, غزال, _pọl_,
+ {{0x3ebea65f,0xb2ab8834,0xed59a835,0x24f883fc}}, // _artt_, ртеж_, бол_, онцы_,
+ {{0x6288e836,0x656bc197,0x644520a2,0xb4e74292}}, // kudo, _segh, _jihi, बनी_,
+ {{0x6443e837,0xb4e3c026,0x9f5ee017,0x7643e838}}, // _rini, नन्_, mitè_, _riny,
+ {{0xf1b9c3ce,0xf771c57f,0x64452839,0x2475e041}}, // koše_, راب_, _lihi, vām_,
+ {{0xdb0f407b,0xc7b283c8,0x212d8361,0xeab0868c}}, // _incó, _אבן_, _uzeh_, _لعل_,
+ {{0x4432083a,0x28bda83b,0x2001683c,0xe8f6c03b}}, // _shy_, ्मदि, nihi_, ялы_,
+ {{0xc617a79e,0x69c7683d,0x2716e04a,0x24894223}}, // दकीय_, _mnje, věné_, huam_,
+ {{0x224d805d,0xeb9a0792,0x2d8ce54c,0xade9e0eb}}, // _kuek_, щим_, agde_, _گفتم_,
+ {{0xdb03e83e,0x7bc76171,0xddcd683f,0x6459c840}}, // _janú, _onju, draž, _utwi,
+ {{0xed572841,0x44442842,0xd11c4843,0x2ca9a0c2}}, // зор_, _qi_, _भुषण_, ładu_,
+ {{0x412a2844,0x75e060e0,0x200c41ae,0x9f5ee00e}}, // сово_, _közv, édio_, zitë_,
+ {{0x60dce845,0x2fd86846,0x798d4847,0x67efa065}}, // ärme, _sorg_, ggaw, _højd,
+ {{0x64452848,0x63b56041,0xf1b9c2e2,0x32020066}}, // [460] _fihi, _bazn, coše_, miky_,
+ {{0x657b8849,0x69d9c095,0x4abda3af,0x6296484a}}, // _aduh, _lowe, ्माव, rtyo,
+ {{0x4426884b,0x25a9484c,0xdb060009,0x02a8c84d}}, // tlo_, beal_, deké, _कन्न,
+ {{0x628fe064,0x0446404e,0x7bc7611d,0x6249484e}}, // ńcow, _немн, _enju, _džob,
+ {{0xf1c7a010,0x656445c8,0x25a00037,0xdb18e156}}, // लांन, mbih, _mbil_, _invå,
+ {{0xe3b8e121,0xa533884f,0x69c7609f,0x9f590066}}, // rlık_, хніч, _gnje, visí_,
+ {{0x9f5ee00e,0x18a36850,0xa01b2851,0x8afdc04a}}, // sitë_, _парм, krön, kařs,
+ {{0x64464068,0x6d552064,0x2fc7e187,0x26c00852}}, // _jiki, zcza, _anng_, _orio_,
+ {{0x6d552064,0x3ebf8853,0x776d0057,0x6d48a143}}, // ycza, _grut_, _reax, _tgda,
+ {{0x764e6610,0x2d8ce854,0x25a00046,0x236d805f}}, // _kuby, ugde_, _abil_, _zeej_,
+ {{0x661d0855,0x9f5ee856,0x2d8ce110,0x776d003e}}, // tosk, nité_, rgde_, _peax,
+ {{0x76452037,0xb904e857,0x63a8e858,0xaca441e9}}, // _sihy, _नथ_, sedn, _awọt,
+ {{0x64452859,0x69daa85a,0x63b60025,0xdce7604d}}, // _pihi, _kote, _dayn, _rejč,
+ {{0x19582056,0x307bc0be,0x2d9c4009,0x765c6262}}, // зары_, _פאונ, ővel_, _otry,
+ {{0x7bc8a05d,0x69d9c85b,0x69daa85c,0x6298a4d9}}, // _mndu, _yowe, _mote, ntvo,
+ {{0x26c0085d,0x78ad4614,0xdb0bc2a8,0xdb1aa156}}, // _frio_, twav, _hagá, _intä,
+ {{0x7646408b,0x73d9685e,0x386901df,0x63b5685f}}, // [470] _diky, одар_, _uwar_, _pazn,
+ {{0x4ab0e260,0x44212037,0x9f582024,0xceb403c8}}, // _जनाव, _ukh_, cirà_, _טיפ_,
+ {{0x20032860,0x2452c016,0x321d8064,0x236d8048}}, // miji_, _lâm_, towy_, _peej_,
+ {{0xa2c0a861,0x98b90315,0x2a604022,0x2cadc1cd}}, // विन्, lası_, bpib_, ywed_,
+ {{0x7bdaa862,0x201ee863,0xb9224088,0xb17b45ee}}, // _botu, koti_, _mkpị_, _blåv,
+ {{0x6abbc864,0x64464865,0x27e682c7,0x3ebf812a}}, // vvuf, _ziki, akon_, _vrut_,
+ {{0xe299a866,0x236d805f,0xdb0f4019,0x6d47e0a9}}, // цай_, _teej_, _mací, žjak,
+ {{0xb9c5813a,0x44236037,0x764f4867,0xb4db4090}}, // _تقري, _hkj_, _kucy, _agài,
+ {{0x69c98868,0x656f4869,0x765b800f,0x52df407c}}, // _inee, _mech, _utuy, _নেতৃ,
+ {{0x69c98048,0x2452c016,0x2451a016,0x68e1610a}}, // _hnee, _dâm_, _tám_, _ülds,
+ {{0x644e686a,0xb21b286b,0x61ed086c,0x3eadc3b0}}, // _xubi, træn, _blal, swet_,
+ {{0x2734483e,0x9f58286d,0xddc2e86e,0xd6d8a04e}}, // _júní_, dirá_, _gvož, _эту_,
+ {{0xddcd286f,0x64464605,0xa3cc4870,0x8af04497}}, // _svaš, _riki, लास_, hbər,
+ {{0xc332c095,0x9f4b0333,0x61ed0871,0x660dc06e}}, // _סוג_, ficó_, _elal, čaku,
+ {{0x81bce041,0x69b2e010,0x44294872,0x50432607}}, // klēj, _असती, fla_, терб,
+ {{0x6603a873,0xbbdaa021,0x290ec00a,0xdb03a066}}, // hink, _बक्क, šmaš_, jení,
+ {{0xe2974874,0x78a96875,0xf1c96010,0xcf92c49c}}, // [480] мат_, _ćeva, रांन, כטן_,
+ {{0x7c29c876,0xa3cc4877,0x5ef38295,0xee376878}}, // hler, लाह_, ्छन्_, дну_,
+ {{0x645d4879,0x2fc90058,0x9f5821af,0x644f4057}}, // _etsi, _ynag_, cirá_, _euci,
+ {{0xe45f4156,0xda0e0010,0x090685a7,0x27eca05d}}, // _bröd_, ाचित_, мпен, _sldn_,
+ {{0xc1734095,0xe3b8e3df,0xb05b0277,0x4420487a}}, // _בחר_, ydı_, rväg, moi_,
+ {{0x6d59c87b,0x2365e098,0xa06a687c,0x27ed887d}}, // _afwa, jblj_, _мага_, _elen_,
+ {{0xdb0720e0,0x2d96887e,0xc6e8e07c,0x7ae3a14f}}, // tejé, _орас, _পশ্চ, gynt,
+ {{0x69db887f,0x7ddcc1af,0xdb00c5df,0x6e29c880}}, // _goue, _césp, temà, gleb,
+ {{0x5186e881,0x3ea9e361,0xdb0520f9,0x973ce143}}, // муна, _ćata_, _dahù, meći,
+ {{0x752fe064,0x7e628882,0x660081e7,0x200dc0ba}}, // ęczn, mpop, _hmmk, anei_,
+ {{0x44204883,0xca48c016,0x61ed0884,0x660d4121}}, // koi_, _mời_, _plal, ynak,
+ {{0x4420400e,0xd378a320,0xd257e0db,0xdb1c6262}}, // joi_, _doće_, ьць_, _inrä,
+ {{0x2247e885,0xfbdf4016,0xdb0bc886,0x69dc6887}}, // _fink_, _biên_, _tagá, _oore,
+ {{0xdb03a04a,0x6448a888,0x7fb8e0eb,0x395a2733}}, // zení, _jidi, _رهبر_, _cfps_,
+ {{0xdb08e0e0,0x61ed0889,0x3e7aa041,0x24868153}}, // kedé, _tlal, nīt_, hrom_,
+ {{0x62856066,0x63b8e3d1,0x660d488a,0x26c24320}}, // vrho, _havn, unak, _brko_,
+ {{0x6d5aa88b,0x2bd144ef,0x6447607d,0x7e62888c}}, // [490] _ofta, हाबा, _piji, jpop,
+ {{0x7a120041,0x69c9888d,0x2129088e,0x69db8030}}, // nātn, _snee, _iyah_, _soue,
+ {{0x7c29c121,0x625361d7,0x6249488f,0x660d4037}}, // yler, _għol, _džoa, pnak,
+ {{0x44204890,0x3f158891,0x7c24005d,0xd1318049}}, // coi_, ндес, _xkir, هما_,
+ {{0xf77080a4,0xed59a892,0xa3cc4290,0x7af64064}}, // شان_, пол_, लार_, czyt,
+ {{0x9f98c069,0x28bda893,0xdcf8e030,0x1c008894}}, // _séð_, ्मलि, _advč, लोबल_,
+ {{0x6e20c1af,0x2bd2e04b,0x2ca6c895,0xdd946099}}, // fomb, ताना, _apod_, _шары,
+ {{0xfe438896,0x69cae897,0x9e48006f,0x69dc6898}}, // _شکای, _anfe, _keďž, _zore,
+ {{0x7bdd4426,0xc245e503,0x7648a899,0x442161e2}}, // _mosu, нник, _fidy, ooh_,
+ {{0x0e65e4c9,0xf1d2637f,0x3c32006f,0x4420489a}}, // нкон, सायन, dáva_, zoi_,
+ {{0xdb0e689b,0xe9ff8016,0xf99f014a,0x6729807d}}, // _kabá, _giản_, ffè_, _hyej,
+ {{0x68e4489c,0x02bda290,0x60c2c89d,0x98a0c03b}}, // yyid, ्मीन, _from, _šią_,
+ {{0x4420489e,0x25adc89f,0x65752037,0x64a5c0db}}, // voi_, meel_, fazh, _чала,
+ {{0xba39e00b,0x63b9c17a,0xe80200c5,0xdce720c2}}, // _צײַט, _hawn, _लगवा_, zają,
+ {{0x69dd48a0,0xe1ff6064,0xa3e3c010,0x3d1a2843}}, // _bose, _twój_, _नका_, _मुखे_,
+ {{0x25b860fa,0xa2c0a8a1,0x7bcae143,0x6724817a}}, // _sarl_, वित्, _znfu, żiji,
+ {{0xf1d0e081,0x63bbc669,0xc4bda077,0x2ecb08a2}}, // [4a0] _mạnh_, idun, ्मुख, ामृत,
+ {{0x2bd2e55d,0x442168a3,0x660560c7,0x1f66219d}}, // ताया, goh_, gihk, _яком,
+ {{0x68e448a4,0xdcf4829f,0x973ce29f,0x7bdc6069}}, // syid, đači, teći, _voru,
+ {{0x6d5980bb,0x7c2aa1cd,0x69dd48a5,0xd36f8049}}, // gcwa, wlfr, _gose, مهم_,
+ {{0x63a408a6,0x660568a7,0x973ce0e4,0xbcfb41e9}}, // _mbin, bihk, reći, _aféf,
+ {{0x660608a8,0xb05b467c,0x248688a9,0xdb1aa057}}, // hikk, _krän, rrom_, _intú,
+ {{0x644988aa,0x67224041,0x22442066,0xe4da60e0}}, // _fiei, ļoju, ímky_, _عورت_,
+ {{0x3266c8ab,0x6448a8ac,0x69dd48ad,0x63a40291}}, // нтов, _widi, _xose, _nbin,
+ {{0xa3d0a8ae,0x69cbc037,0xbc6a8050,0x26c048af}}, // षाद_, _dnge, _رمان_, nvio_,
+ {{0x9f4ce8b0,0xa2c0a8b1,0x2d800031,0x6e21e8b2}}, // vidó_, विद्, _adie_, golb,
+ {{0x63ae2026,0xf1b9c8b3,0x7dd840e0,0x2129043d}}, // debn, soša_, _dísz, _syah_,
+ {{0x660608b4,0x2298e8b5,0xad9b0049,0x3c32002e}}, // gikk, rék_, ciúl, táva_,
+ {{0x3205e05c,0x629bc8b6,0x7bdd44b4,0x629c21e2}}, // bily_, ctuo, _rosu, etro,
+ {{0x63a4000d,0x442b00d5,0x6d4e68b7,0xdb01e057}}, // _ebin, slc_, _ngba, selá,
+ {{0xdb1aa071,0xf1c5c026,0x386d200a,0x68306156}}, // _antú, वाचन, ćero_, tädn,
+ {{0x69de205d,0x6d4d0054,0xf77348b8,0x99cf407c}}, // _eope, _ugaa, طار_, রস্ক,
+ {{0x7c2288b9,0x69de21e2,0xbbe140a8,0x2cb241cd}}, // [4b0] door, _fope, _नवीक, gwyd_,
+ {{0x64498057,0x69de2119,0xfd50e125,0x6fb868ba}}, // _siei, _gope, _rudị, нгур_,
+ {{0x628f08bb,0x629c28bc,0xb4e984e5,0x7bdd48bd}}, // luco, ctro, मन्_, _tosu,
+ {{0x987a2087,0x63af0066,0x27e9405d,0x6b6740fa}}, // _קארט, necn, qkan_, _dégâ,
+ {{0x44386133,0x69c168be,0xe8dfa067,0x7bdf08bf}}, // _fhr_, _óleo, _phỏm_, _moqu,
+ {{0xeb9f44a4,0xdee288c0,0x2005e03c,0xe5a5a45c}}, // _gjør_, роши, xili_, тики,
+ {{0x25a48143,0x80bda029,0x69cbc07d,0xad9b0049}}, // _fbml_, ्में, _pnge, tiúl,
+ {{0x644ae8c1,0x248dc8c2,0x63a4a098,0x442ce8c3}}, // _aifi, tuem_, đina, eld_,
+ {{0x25adc110,0x75e060e0,0xdcee6026,0x6606059f}}, // reel_, _közp, _odbě, vikk,
+ {{0x2292c4b7,0x629bc03b,0xe9df413a,0x6577600e}}, // گلیس, rtuo, _siúl_, naxh,
+ {{0x3ea908c4,0x442328c5,0x35c688c6,0x63b9c227}}, // _apat_, doj_, _रोड़, _tawn,
+ {{0x69de28c7,0x1ad8807c,0x63a52090,0x224b48c8}}, // _sope, _দেয়া, _abhn, _nick_,
+ {{0x9f4f807b,0xe3aee7e9,0x63baa037,0x644bc1e7}}, // ligó_, _кб_, _yatn, _iigi,
+ {{0x6e2d48c9,0x6825c019,0x7c2d48ca,0x6d5d48cb}}, // elab, códr, elar, _afsa,
+ {{0x63ae203a,0x7a13c12a,0x683888cc,0xed572331}}, // rebn, năto, líde, тос_,
+ {{0xb7bc8405,0x60c408cd,0x644bc8ce,0x6d5bc8cf}}, // _অক্ট, _trim, _jigi, ncua,
+ {{0xe3ba607a,0x7a3888d0,0x1d074501,0x248948d1}}, // [4c0] мба_, níte, вети_, gram_,
+ {{0xf1d26029,0xa25b48d2,0x64c5600a,0x6e2d48d3}}, // साधन, _trôn, kčić, alab,
+ {{0x3ddf8200,0xf98648d4,0x443a205d,0x248941ae}}, // _bouw_, _игно, _ihp_, aram_,
+ {{0x644bc5f1,0x186768d5,0xa3c8e8d6,0x7aeb4009}}, // _nigi, таци_, _लोन_, ölté,
+ {{0x442ce8d7,0xf9930095,0x6d5c2090,0x26c5a108}}, // yld_, שרד_, ocra, _arlo_,
+ {{0x27e00004,0x6289c6bf,0x2fcd8037,0x644bc8d8}}, // _loin_, dreo, _eneg_, _aigi,
+ {{0xe7a42437,0x764bc8d9,0x7bce68da,0x4426c8db}}, // ख्यप, _bigy, _inbu, _pko_,
+ {{0x201248dc,0xe4e3a0ff,0x0b8ac762,0xd848e8dd}}, // nnyi_, рішн, фски_, _lọt_,
+ {{0x7a3880e0,0x644ae2a8,0xfbd6004a,0x317820c2}}, // gíte, _pifi, धानम, karz_,
+ {{0x443fc8de,0xee37a8df,0x27e007ad,0x09d0e1a6}}, // amu_, _ану_, _aoin_, িস্থ,
+ {{0x442ce8e0,0xd5afc8e1,0xd00fa50f,0x61e088e2}}, // rld_, _кс_, _ظلم_, _koml,
+ {{0x6289c8e3,0x6a86c8e4,0xb4fba00b,0x3ea90037}}, // breo, _алда, _קלאר, _ppat_,
+ {{0xea00e067,0x7c2d48e5,0x68388057,0xd848e8e6}}, // _chảy_, vlar, cíde, _bọt_,
+ {{0x27e00256,0xfbdf4016,0x2458a016,0xe918e71b}}, // _eoin_, _viêm_, _kém_, _полі_,
+ {{0x2d5828e7,0x7c24413a,0x624968e8,0x973ce0a9}}, // тись_, foir, _džol, teću,
+ {{0xf8b6c0e3,0x61f641cd,0x996448e9,0x69cd0167}}, // _अनाय, ghyl, итул, _unae,
+ {{0x195921ba,0x3ea90426,0x98bee6fa,0x3869a29c}}, // [4d0] _папы_, _upat_, matı_, ćari_,
+ {{0x35b528ea,0x44276067,0xa3d5e260,0x98bee315}}, // _сбор, ôn_, हाय_, latı_,
+ {{0x7c2448eb,0x68e8e0e2,0x443fc8ec,0x63a288ed}}, // boir, nydd, ymu_, ffon,
+ {{0xfd1f4031,0x69ce68ee,0x98bee315,0x2007a0bb}}, // _adìe_, _enbe, natı_, wini_,
+ {{0x2ba50437,0xc178e013,0x3b8488ef,0x1da50861}}, // ग्या, nkės_, рліг, ग्यत,
+ {{0xdb06404a,0x1a684050,0x442dc8f0,0x9f4002d9}}, // _jaký, _شیمی_, wle_, _cliù_,
+ {{0x3a2908f1,0x31790367,0xa3cfc8f2,0x58d468f3}}, // _okap_, kasz_, शाल_, _корт,
+ {{0x64c5623d,0x442ea110,0x85048010,0xa3d70028}}, // rčić, alf_, _शेवट_, साफ_,
+ {{0x4a5488f4,0x0a49e4a7,0x75f5c026,0x6575607d}}, // _вкус, нзий_, _náze, _kezh,
+ {{0x644bc8f5,0x27e008f6,0x6b82c8f7,0x200948f8}}, // _wigi, _soin_, _edog, hiai_,
+ {{0x2009403b,0x7a3888f9,0xa91d850e,0x7c3b88fa}}, // kiai_, síte, _udžb, _khur,
+ {{0x394fc62c,0x752d0284,0x28c782ca,0x7bdae3ac}}, // _uggs_, _byaz, लिपि, ajtu,
+ {{0x645568fb,0x2d83616d,0x7c3b88fc,0x35a5e056}}, // _luzi, _odje_, _mhur, _балг,
+ {{0xec35a053,0x65756530,0xab5d82a4,0xd378e8fd}}, // טאָר_, _nezh, _inżu, cać_,
+ {{0x539ae095,0x201908fe,0x7c3b8291,0x9324e0eb}}, // _ניקו, éric_, _ohur, _پرون,
+ {{0x4425e8ff,0x63bc610a,0xdb01e277,0x9c138125}}, // lol_, _sarn, relä, _sọnl,
+ {{0x25f7e7d9,0x7a360057,0x8f75471b,0xdb0e20c4}}, // [4e0] एसपी_, cáta, русі, ndbú,
+ {{0x61ed4900,0x7c298901,0x27edc037,0xb14347ab}}, // akal, _aker, hken_, снул,
+ {{0x179b200b,0x66e62850,0x6455601f,0x61e0849f}}, // _אייב, _сона, _cuzi, _poml,
+ {{0x249fc902,0x63bc6903,0xe3b2433e,0x7c3b8904}}, // ktum_, _warn, _ارد_, _chur,
+ {{0xead48905,0x91e6a54a,0x5c06a12e,0x42d54013}}, // роль, лоде, ляка, _кіру,
+ {{0x2fc04906,0x61ee21e2,0xdcbaa1e1,0x2fcfc907}}, // ldig_, nkbl, ещи_, _angg_,
+ {{0x61e08908,0x6609c23c,0x645dc0c4,0x224d805d}}, // _toml, fiek, ísin, _fiek_,
+ {{0x2bd26909,0x7984090a,0x38668156,0x5d54c62a}}, // सावा, _ndiw, ppor_, шкит,
+ {{0x2ca0490b,0x25d7400b,0xc796a0a4,0xd378e064}}, // ntid_, רופן_, _مشاب, tać_,
+ {{0xad9b013a,0x21f1c066,0x60cdc1e4,0xa96aa13a}}, // miúi, _váha_, _šamc, _أمام_,
+ {{0x752d00c7,0xdb03a90c,0xad9b013a,0xf9936095}}, // _syaz, venç, liúi, זרת_,
+ {{0x4426890d,0x2ca0410a,0x75f84026,0x6441e90e}}, // noo_, ktid_, _jízd, amli,
+ {{0x5187290f,0x6b8400e2,0xad9b0049,0x644d0057}}, // лува, _ddig, niúi, _viai,
+ {{0xf41f6277,0x682da910,0x69c0c911,0x3083c049}}, // _iväg_, túdi, ldme, _الوف,
+ {{0x59b8c0c2,0x3f8760c4,0xad9b0049,0x9f5ee912}}, // _आसार, ónum_, hiúi, citá_,
+ {{0xb4c04913,0xf1d2e914,0x2fc048c4,0x3a2a607d}}, // ंटू_, तावन, gdig_, _akbp_,
+ {{0x557568f4,0xdbd26046,0xa295471b,0x3a268155}}, // [4f0] игат, _füüs, разі, doop_,
+ {{0x224d8915,0x9f5120b6,0x6e3c606f,0x27edc916}}, // _siek_, rizó_, _chrb, zken_,
+ {{0x7c3b8090,0x44b561fc,0xdb0f0057,0x41558917}}, // _phur, рбес, decé, авес,
+ {{0x628b883d,0x569540fb,0xce95414a,0xa3ce0918}}, // trgo, _камт, _камъ, राए_,
+ {{0xdb172919,0xed59c00a,0xeb99a91a,0x7a3601ce}}, // _maxí, jdža_, вил_, kátn,
+ {{0x88c5a13a,0x6609c064,0x25bdc1cd,0xac776555}}, // لتعل, wiek, _sawl_, _مارش,
+ {{0x4425e91b,0x7777217a,0x28c5a91c,0x14b0e37f}}, // vol_, _mexx, विधि, _जनगण,
+ {{0x270ea03c,0x644e691d,0xddc9a055,0x6a85c14a}}, // _dən_, _zibi, _uvež, алиа,
+ {{0xde5980ff,0xfa3480eb,0x443ce91e,0x45d5a0ba}}, // _разі_, _طرفد, _bhv_, _коас,
+ {{0x7a120041,0x656d4058,0x78bb8361,0x764f40c2}}, // māti, hbah, _isuv, _licy,
+ {{0x3ea5e423,0x4427a91f,0x7cd9a33e,0x25bf8920}}, // шинг, mon_, _جواز_, _kaul_,
+ {{0xd9460921,0x63be2922,0x3f85a133,0xdb076009}}, // иени, _sapn, _hdlu_, _hajó,
+ {{0x200a2923,0xed4ec924,0xdb03a925,0xdb1c600e}}, // tibi_, _фо_, senä, _parë,
+ {{0x6145e926,0x7e64005c,0xdce3a927,0x64572928}}, // _кела, _vtip, lanđ, _buxi,
+ {{0x6443a929,0x63a9892a,0x4426892b,0x4427a92c}}, // imni, _oben, yoo_, ion_,
+ {{0x656d492d,0x69c1e92e,0x7a360066,0xdb1e20dd}}, // gbah, ldle, máto, _hapë,
+ {{0x61e3e92f,0x7a2b60e0,0x442b4143,0xa206603b}}, // [500] _konl, yütt, _dkc_, апад,
+ {{0x307b0095,0x2722a06f,0x657bc1ae,0x645ee930}}, // _שאינ, nčné_, hauh, _épic,
+ {{0x78bb82df,0x60c98931,0x23776050,0xdb1c6396}}, // _asuv, _arem, شمند_, _karé,
+ {{0x705300d0,0x3ead8098,0x8fa68103,0x2d9820d1}}, // _دنیا, _bpet_, раде, lgre_,
+ {{0x660aa820,0x3ead8932,0x649a8933,0xa3d70934}}, // tifk, _cpet_, ттер_, सात_,
+ {{0x657c2935,0x6444404e,0x3ead807d,0x7a3d00e0}}, // narh, lmii, _dpet_, méte,
+ {{0x645bc071,0xb6068361,0xd33560db,0xd00760ba}}, // equi, _pašć, _гэты, _тере_,
+ {{0x6ca6c936,0x66164052,0x443ce937,0x786680ff}}, // арож, nnyk, _shv_, _вказ,
+ {{0x94a98938,0x69c0c14f,0x387fc939,0x2617417a}}, // _مطلق_, rdme, gsur_, dħol_,
+ {{0x53e6e0ba,0xdb156009,0x245bc18b,0x2258693a}}, // рциа, _hazá, _dêm_, _kurk_,
+ {{0x3fc8e0d0,0x869aa93b,0xec376095,0x7a36093c}}, // ندگی_, _штат_, שאיר_, gáto,
+ {{0xa3d2610a,0x69c2893d,0x2480493e,0x69c0893f}}, // वाँ_, ndoe, ksim_, _jame,
+ {{0x644f4940,0x98b80866,0x61e40941,0xdb1c6057}}, // _rici, алят_, _doil, _daré,
+ {{0x683d0005,0x68360942,0x6282c943,0x6280c944}}, // déde, ládl, _ivoo, msmo,
+ {{0x7c28e945,0x02b6c893,0x6e272946,0x245c20e0}}, // modr, _अनुन, rojb, _cím_,
+ {{0x78a28089,0x80ad8405,0x69c08947,0xdb18e0e0}}, // ktov, চিত্, _name, _javí,
+ {{0x67ea4069,0xdb238315,0xa3b6a0c2,0x6284803b}}, // [510] _nýju, ürül, ज़ा_, šioj,
+ {{0xf3676948,0xb8ff4026,0xeb936949,0x44446194}}, // ртан, _थप_, تظر_, gm_,
+ {{0xa3d0a10a,0x752f40c2,0x25bf894a,0xe94540eb}}, // षाई_, _wycz, _paul_, _گردی,
+ {{0x3869494b,0x69c0894c,0x2d99094d,0x66eae03c}}, // ppar_, _came, ngse_, məkd,
+ {{0x7a3d01af,0xfd108678,0x660d494e,0xf1bf0009}}, // céte, وجه_, miak, llás_,
+ {{0xa01b2262,0x289a0053,0x61e52227,0x6812094f}}, // mrös, ריװא, _johl, rādi,
+ {{0x3866c01f,0x6e2d080c,0x6443a950,0x62952098}}, // _ator_, _okab, rmni, nuzo,
+ {{0x81c3e07c,0xc9182694,0xef1fe106,0x2d982199}}, // _একই_, יחות_, şük_, zgre_,
+ {{0xf1d2e010,0x61e3e951,0x63b520e0,0x316dc071}}, // तांन, _ponl, kezn, rbez_,
+ {{0xdb08a057,0x200ce952,0x64456420,0xdb1c6050}}, // _cadó, gidi_, nmhi, _saré,
+ {{0x7a158064,0x7bc1e046,0x660d4953,0xdb056954}}, // jątk, rdlu, kiak, behå,
+ {{0xc692649c,0xf3e92694,0x186a0955,0xa159c956}}, // האט_, _אף_, лами_, _саду_,
+ {{0x290be064,0x7a30604e,0x6458e005,0x7056013a}}, // ńca_, säti, _euvi, إنسا,
+ {{0xd999e0d0,0xe36326c0,0xdb0f4005,0x2007e098}}, // _صنعت_, зкри, _facú, _umni_,
+ {{0xa3a9a578,0xe695e13a,0x660980e4,0x6295200a}}, // ग्य_, _العد, _omek, guzo,
+ {{0x443f8016,0x7ddda5cd,0x51f6010c,0xf662c088}}, // _nhu_, _vèsy, _دستر, _họọr,
+ {{0xf662c125,0xe7e94028,0x5baa603b,0x81ccc07c}}, // [520] _kọọr, _टकरा_, лкам_, _রকম_,
+ {{0xab8c8121,0x200e0064,0xa7fcc20f,0x9947412a}}, // _çözü, ślić_, _atıl, спуб_,
+ {{0x44446957,0xcf572095,0x6459c958,0x74adc07c}}, // rm_, _לבית_, _luwi, কটিউ,
+ {{0x26ca6959,0x7bc1a1e7,0x2aa080d1,0x78a2895a}}, // _vrbo_, _aalu, tòb_, ttov,
+ {{0x661b85f9,0xd378a23d,0x63b64025,0x6838804a}}, // _djuk, _peć_, meyn, bídn,
+ {{0x3ea0c04e,0xe2a700c4,0x92df207c,0x7bc0895b}}, // _äiti_, óðir_, _দেয়_, _wamu,
+ {{0x7bc1a95c,0x657e6174,0xf1b32095,0x60cbc090}}, // _dalu, maph, פסה_, _argm,
+ {{0x67f5c333,0x7bc08296,0x5f94c67b,0x9f4240d1}}, // _pája, _uamu, циит, _pokè_,
+ {{0x4062c95d,0x7bc1a95e,0x443ea007,0x657d095f}}, // _bọọl, _falu, _tht_, vash,
+ {{0xf2e78050,0x6c36a062,0x28c94960,0x65640961}}, // _دکمه_, _افطا, रिति, _afih,
+ {{0x78a442df,0xdd910085,0x442a2962,0x6ca3e052}}, // htiv, وود_, dob_, пряж,
+ {{0x68ed435f,0x7c2aa2a8,0x62964963,0xdb0f403e}}, // yyad, lofr, kuyo, _vacú,
+ {{0x200ce964,0x69c1a965,0x200a6167,0x6e28e06e}}, // pidi_, _yale, _ombi_, podb,
+ {{0xdb1aa966,0x67d4e967,0x4062c088,0x69c44018}}, // _latí, _фоку, _gọọl, edie,
+ {{0x7bc2c968,0xa3d26969,0x657d096a,0xb4d1496b}}, // _kaou, वाई_, pash, वम्_,
+ {{0x69c4496c,0xdb0b803e,0xdb0f0919,0x7bcb6018}}, // gdie, hegá, lecí, ēgum,
+ {{0x44c86315,0x1a5b413a,0x61e5204a,0x7988a0ae}}, // [530] _ağ_, اشرة_, _tohl, _oddw,
+ {{0x442b096d,0xf1b9c96e,0xa3b0296f,0x6009603c}}, // loc_, roši_, _टॉप_, _kömə,
+ {{0x2bbdc04a,0x6aa4407d,0xfc3f4016,0x645aa970}}, // ्यमा, atif, _thí_, _nuti,
+ {{0xe7f5604b,0x43748971,0xdb0f01de,0x38a1a972}}, // _इतका_, пушт, mecâ, vór_,
+ {{0x201c801f,0xe70b8050,0x3f8900ce,0x6286c013}}, // évia_, دتان_, _mdau_, škoj,
+ {{0x237d8022,0x657aa0eb,0x2ba6c0ec,0xf771a049}}, // sawj_, _ceth, क्सा, ذات_,
+ {{0x91bfa125,0x6142a3d8,0x16d1c064,0x4429c016}}, // _atụ_, меша, तम्ब, _ưa_,
+ {{0x7aea6097,0x60cd0973,0x291f805f,0x4fe9c974}}, // äfte, _oram, _txua_, рмин_,
+ {{0x200dc975,0x2bcf87f1,0x2bdd011a,0x2a690048}}, // riei_, _सोया, याना, _ntab_,
+ {{0x69c4406f,0xc9a6e04e,0x5285e976,0x2cbf80ae}}, // zdie, овье_, огоб, _isud_,
+ {{0x3ebea05d,0xe9ff8067,0x38cac050,0x442b0017}}, // _gstt_, _nhẫn_, هایی_, foc_,
+ {{0xddcd6098,0x81bce041,0x1b4a0977,0x69dce156}}, // jsaž, slēp, изни_, örer,
+ {{0x657aa174,0xddc2a018,0x6459c1e7,0x645aa978}}, // _yeth, lpoš, _uuwi, _zuti,
+ {{0xf3f14067,0x2a6dc022,0xae024077,0xd7f14119}}, // ện_, mpeb_, रोईन_, ễn_,
+ {{0xd347e050,0x7a360089,0xb147e0eb,0x271583c3}}, // _دیده_, nátk, _دیدم_, तपुर_,
+ {{0x6b9bc979,0x25ad238a,0x7a30697a,0x6aa4497b}}, // ngug, đela_, hätt, ttif,
+ {{0x7bd60132,0xc7b2c1a9,0x6d4ee23d,0x2a690037}}, // [540] _inyu, ובן_, žbat, _gtab_,
+ {{0x61e76143,0x602620ff,0x63b6403c,0xdb18e97c}}, // _cojl, _єдна, seyn, _havá,
+ {{0xf74621bc,0x78ad204a,0x569360ff,0xa01b2156}}, // _фено, čová, даєт, prör,
+ {{0x5fdd0010,0x6296497d,0x200f897e,0x67f842a8}}, // यायल, puyo, gigi_, _fíja,
+ {{0x254ee03c,0x657aa360,0x6013417a,0x6366e04a}}, // zəl_, _peth, għmu, nční,
+ {{0x200a697f,0x79898980,0x69c40981,0xa3b02029}}, // _umbi_, _edew, _laie, _टॉम_,
+ {{0x59ca0982,0x200ea983,0x2bdd0984,0xdb1c2017}}, // ियार, rifi_, यामा, ndrà,
+ {{0x442fc05d,0x63a9c1cd,0x62844013,0x660f0066}}, // _ckg_, gfen, osio, xick,
+ {{0x59a6c437,0xfba6c11a,0x75fcc0e0,0x26cd80ae}}, // क्षर, क्षम, _néze, _greo_,
+ {{0x660f0985,0xc3334095,0x80bb207c,0x27f8e069}}, // wick, ווח_, _উপস্, órn_,
+ {{0x442b0986,0x96c28029,0x657c6121,0x41de2026}}, // toc_, लिकॉ, _merh, मानस,
+ {{0xc5f420e8,0x69c40987,0x69c3e194,0x320b0926}}, // _ruɓa_, _caie, _eane, рхон_,
+ {{0x7a13c12a,0x38cbc043,0x63a0817b,0x61e8a988}}, // gătu, _پانی_, _mcmn, _kodl,
+ {{0xc6938989,0xdb152009,0x628e8018,0x3ea68018}}, // ואש_, lezé, _ābol, itot_,
+ {{0xf77f05df,0x6286468f,0xadeec98a,0x61e8a7ac}}, // paç_, _avko, _जवान_, _modl,
+ {{0x69c40108,0x2c60c0d1,0x6d586013,0xf54f4125}}, // _gaie, _kòd_, žval, _rụba_,
+ {{0x60cd098b,0x8ccdc064,0x7bc3e6d2,0x78b3e0e4}}, // [550] _uram, तियो, _yanu, čevš,
+ {{0xf770625b,0xdb18e066,0xdb17203c,0x6281204a}}, // نام_, _zavá, _naxç, álos,
+ {{0xf402a07c,0x35b5898c,0x61e12069,0xdb18a241}}, // ৎকার_, збер, öllu, ndvä,
+ {{0x2bb7e870,0x799d0812,0xaca34125,0x6aa561cd}}, // _अस्थ, ngsw, _ahục, rthf,
+ {{0x645c698d,0xa969a080,0x3e60c0d1,0x61e8a0fd}}, // _euri, била_, _nòt_, _bodl,
+ {{0x645d40ae,0x2366c012,0x7bc72009,0x29076049}}, // _iusi, _afoj_, ndju, únas_,
+ {{0x6e95a13a,0x38a400e0,0x442ce98e,0x765c6054}}, // ألعا, zör_, dod_, _gury,
+ {{0x2249498f,0x7c2d4265,0x251c200b,0x26cd8990}}, // nmak_, noar, יווא, _treo_,
+ {{0x61fb8991,0x2011246d,0x7e698992,0x7642c18c}}, // _ilul, kizi_, _utep, _dhoy,
+ {{0xaf05e1e1,0xa91dc993,0x69c52994,0x60c9c995}}, // зпол, jaže, _nahe, rvem,
+ {{0x61e98996,0x2fc48058,0x20112997,0x22494106}}, // _koel, _yamg_, dizi_, kmak_,
+ {{0x442fc998,0xfe70a0eb,0x7a322999,0x7bc3e99a}}, // _ukg_, _شدم_, hætt, _wanu,
+ {{0x7a36099b,0x455ac1a9,0x2011299c,0x6d59c99d}}, // náti, _הכנס, fizi_, _igwa,
+ {{0x9f47e00e,0xf1b9c99e,0xcb6721fc,0x644fe017}}, // _jonë_, leš_, заре_, ïcid,
+ {{0xdb1c699f,0x69c409a0,0x2d8040ba,0x63af458f}}, // _parí, _taie, taie_, _bbcn,
+ {{0xa5bb49a1,0xe1ff09a2,0x6b81e0f9,0x7649c121}}, // _skór, chó_, lalg, nmey,
+ {{0xd347e050,0xd5af413a,0xdb9b0008,0x69c721e4}}, // [560] _دیگه_, طفل_, _הספר, bdje,
+ {{0x6146203b,0xb21b2069,0xdcfc29a3,0x7980c9a4}}, // _непа, nsæl, larč, zamw,
+ {{0xe7bdc180,0x628609a5,0x6456011d,0x2ec6613a}}, // ्याप, lsko, _kiyi, ثقاف,
+ {{0x443160ca,0x628449a6,0x7981e9a7,0x28c949a8}}, // _ekz_, psio, halw, रिशि,
+ {{0x645c6605,0x60c1a058,0x3e61e067,0x645609a9}}, // _wuri, _aslm, _lót_, _miyi,
+ {{0x3ea689aa,0x765c6064,0x6736004e,0x6f0289ab}}, // rtot_, _tury, _myyj, rzoc,
+ {{0xb05b49ac,0xdb1aa9ad,0x63b8a026,0x7980c9ae}}, // _fräs, _batá, revn, tamw,
+ {{0xb05b4277,0xfaa68498,0x3e60c163,0xdca689af}}, // _gräs, _набо, _pòt_, _наби,
+ {{0x2d8209b0,0x6142a1e9,0xdb1c61de,0xa967c01b}}, // dake_, _bílí, _farã, чиња_,
+ {{0xf1aaa050,0xe9df4016,0xa2f4e0ff,0x2ef4e9b1}}, // _داره_, _khúc_, дпоч, дзор,
+ {{0x765600e8,0xf1b9c9b2,0x644882f3,0xa91dc0a9}}, // _biyy, beš_, _èdis, važe,
+ {{0xf1b9c261,0xaadba095,0x76440030,0x442ea9b3}}, // ceš_, _לחבר, _chiy, lof_,
+ {{0x2d8169b4,0x69c529b5,0x6abd0171,0x26cfc012}}, // yahe_, _pahe, uwsf, _grgo_,
+ {{0x171bc095,0x9980403b,0xf1dac9b6,0x442ce9b7}}, // _פוגע, klių_, भावन, qod_,
+ {{0x201129b8,0x28f8204e,0xd90f2050,0x7bd8e9b9}}, // rizi_, перь_, ایج_, _invu,
+ {{0x660d09ba,0xbbb9a0e3,0x442ea9bb,0x7bc529bc}}, // _umak, _इस्क, hof_, _wahu,
+ {{0xd378a50e,0x224949bd,0x7c2e29be,0x2d9ee110}}, // [570] _hoću_, rmak_, bobr, ogte_,
+ {{0x61eae00a,0x7bc6417b,0x7e6d09bf,0x26c12286}}, // _jofl, _eaku, _itap, _tsho_,
+ {{0x6d4089c0,0xdb1c6355,0x22494121,0xa7fb69c1}}, // _azma, _carà, pmak_, _nuñe,
+ {{0x63bae0e0,0x7bc649c2,0x09aaa07c,0x7aea6156}}, // hetn, _gaku, _গোলা, äfta,
+ {{0x25e0c9c3,0x64556024,0xdb1ae277,0xf3f3613a}}, // कानी_, _tizi, ldtä, _كأس_,
+ {{0x2fc949c4,0x645f09c5,0x614720fa,0xfe7020eb}}, // ldag_, _muqi, célé, _جدی_,
+ {{0x60c2c9c6,0x628729c7,0x9f47e00e,0x2d8209c8}}, // _asom, nsjo, _vonë_, yake_,
+ {{0xb4e96604,0xdcf8e143,0x628609c9,0x798d00bb}}, // यही_, _cevč, ysko, _ndaw,
+ {{0xf1b9c42e,0x60c2c0e0,0x7bc769ca,0x9f5a64cd}}, // reš_, _csom, _kaju, cipó_,
+ {{0xdb1c69cb,0x386d89cc,0xdb0e6057,0x7963c1e9}}, // _hará, _iter_, _gabó, _bíwó,
+ {{0x2eac28c6,0x7c2e2552,0x6b81e9cd,0xea004016}}, // _चहेत, vobr, ralg, _loạt_,
+ {{0xe7d8407c,0x2ef5c955,0x6b81e9ce,0x443909cf}}, // _সত্য, _озер, salg, gls_,
+ {{0x522ac49c,0x6457224f,0xddcd0064,0xfc3f2183}}, // _ווײַ, _bixi, _biał, rtín_,
+ {{0xfc3f29d0,0x69c9c684,0x44212037,0x63bbc9d1}}, // stín_, ldee, _cjh_, leun,
+ {{0x2fc7e0ae,0x2d8d8088,0xdca32826,0xa0a329d2}}, // _iang_, _odee_, кари, кард,
+ {{0x24442277,0xc8c94029,0x6f044200,0x61ebc9d3}}, // döme_, रिंट, tzic, _jogl,
+ {{0x4444242c,0xdb03a05c,0xf412c095,0x69c649d4}}, // [580] _th_, vený, דפן_, _vake,
+ {{0x69c649d5,0x61f649d6,0x98b06381,0x683889d7}}, // _wake, skyl, žači_, mídi,
+ {{0x63bc29d8,0x9980403b,0xdb03a9d9,0x386d80eb}}, // mern, ulių_, genü, _ater_,
+ {{0x629c29da,0x78a9c0dd,0x3f8760c4,0x225f80ae}}, // muro, ktev, ónur_, _buuk_,
+ {{0x645729db,0x69c9c9dc,0xdbc6a10a,0x6aa9c00a}}, // _yixi, ddee, _mööb, jtef,
+ {{0x64572939,0xe9df4016,0x6d5b89dd,0x0a6aa7f8}}, // _xixi, _thúc_, _egua, ории_,
+ {{0x3a22400a,0x661d006f,0xe81fa9de,0xa3df204b}}, // _kjkp_, ynsk, _बदरा_, णात_,
+ {{0x63bae037,0x69d5c37f,0x828ac052,0xef18a018}}, // wetn, भागी, особ_, _ceļa_,
+ {{0x80c449df,0x2fc7e9e0,0x629c29e1,0x78a9c9e2}}, // रिटे, _bang_, huro, gtev,
+ {{0x69c9c054,0x4422407d,0x68388057,0x3e644262}}, // adee, _ljk_, dídi, _möt_,
+ {{0x2fc7e9e3,0xa3bda9e4,0x645729e5,0xbcfb00fa}}, // _dang_, ेयर_, _rixi, rcém,
+ {{0x83fcc0e4,0x998940c2,0x63bae31b,0x2d8329e6}}, // _buđe, ział_, setn, waje_,
+ {{0x6b808058,0xaac78295,0xd5a50029,0x60c2c9e7}}, // _memg, लिएक, ग्गज, _usom,
+ {{0x442240c7,0x442f89e8,0x69caa9e9,0xdb03a057}}, // _ajk_, zog_, ndfe, cenó,
+ {{0x395ce005,0x6b844049,0x533429ea,0x660280bb}}, // _igvs_, faig, верт, khok,
+ {{0x628720ff,0xa008013a,0x7bc769eb,0xdcc8c067}}, // rsjo, _يقول_, _saju, _kỉ_,
+ {{0xe9df4016,0x660289ec,0xdb1d42a8,0x7e6d09ed}}, // [590] _chúa_, dhok, _basá, _utap,
+ {{0x2fc949ee,0x3f84c9ef,0xcec8c016,0xeddbc37f}}, // sdag_, hamu_, _mộ_, _मच्छ,
+ {{0xcec8c081,0x2d84c9f0,0xed59c2f9,0x6288e9f1}}, // _lộ_, kame_, leže_, nsdo,
+ {{0x6d5c69f2,0xceb2200b,0xa91d89f3,0x63bd0013}}, // _egra, _גיט_, _leže, nesn,
+ {{0x2fc909f4,0x69c769f5,0x6d41a0c2,0xef1981f6}}, // _maag_, _taje, _szla, _beża_,
+ {{0x6cd280e0,0xf745a07a,0xd945a14a,0xb05b09f6}}, // اقوا, нело, нели, lväs,
+ {{0x442f83a1,0x7bc8a9f7,0x6fa50010,0x644d49f8}}, // pog_, _dadu, _काढू, omai,
+ {{0x69c8a090,0x78a9c9f9,0x6b85612b,0x660289fa}}, // _eade, ttev, lahg, chok,
+ {{0xf8c8c081,0x63bc29fb,0xddc98041,0x443129fc}}, // _cứ_, yern, _pieņ, koz_,
+ {{0x89a9e9fd,0x644d4049,0x2d812079,0x60006009}}, // чков_, hmai, _nehe_, _töme,
+ {{0x66152265,0x7c22c488,0x26d1e9fe,0x2fc7e9ff}}, // dizk, _fjor, ízo_, _vang_,
+ {{0x60c400e4,0x201eea00,0x63bd0a01,0x63bc2a02}}, // _psim, anti_, gesn, wern,
+ {{0x24894022,0xed350099,0xbddb4061,0x61ed0a03}}, // jsam_, вэрэ, _chèm, _doal,
+ {{0x6b81a041,0x44312a04,0x20020a05,0x11d9e049}}, // _jelg, goz_, shki_, _روعة_,
+ {{0xc5f2a00b,0x6459c236,0x27ff83df,0x44224058}}, // נדל_, _kiwi, _olun_, _pjk_,
+ {{0x7bc9807d,0x2bcf8010,0x63bd0a06,0x629c2a07}}, // _naeu, _सोला, cesn, ruro,
+ {{0xa3cb2a08,0xdb172057,0x6b85617b,0x224dc06e}}, // [5a0] _रोक_, _xaxú, fahg, imek_,
+ {{0x2d812669,0x59d1c10a,0xfd47065d,0x6289c273}}, // _gehe_, _दोसर, _язык_, iseo,
+ {{0x8f9b200b,0x248945c8,0x9f45a066,0x69cb82be}}, // _ציטי, asam_, _bolí_, fdge,
+ {{0x7bcf801f,0xa9268a09,0x7a360912,0xb05b0262}}, // _ócul, ндел, pátu, mvär,
+ {{0xb05b0241,0x7985600f,0xc95540db,0x248941cd}}, // lvär, bahw, _атры, csam_,
+ {{0x3f84ca0a,0x09e3a04f,0x63bd0a0b,0x22594066}}, // wamu_, лочн, zesn, _zisk_,
+ {{0x8cd3220b,0x9d436a0c,0xeac8c016,0xa3ea87ca}}, // डियो, _берд, _sỹ_, _едва_,
+ {{0x629e6a0d,0xb4b0a10a,0xb14880e0,0xcb0ac14a}}, // mupo, _ओही_, _قیام_, зход_,
+ {{0x3a26aa0e,0x6d43ea0f,0x2d85e042,0x7659c0e2}}, // _змаг, _azna, fale_, _diwy,
+ {{0xaca44291,0x63a28a10,0x7c240296,0xeac9e2f4}}, // _ahịr, lgon, _ajir, _bẹ_,
+ {{0x2bdd0a11,0x98b3638a,0x7c3bca12,0x69ddaa11}}, // याला, čeće_, blur, पारी,
+ {{0x6288ea13,0xdcc8c016,0xb05b0156,0x628aa271}}, // rsdo, _tỉ_, dvär, msfo,
+ {{0x7bdc6110,0x6f06e0fa,0xddc420a9,0xe61f01ae}}, // _onru, _bébé, _stiš, rnô_,
+ {{0xb5fdaa14,0x9f5dc0ca,0xdb1ae0fa,0x2fc903c5}}, // _tuše, _alwè_, heté, _waag_,
+ {{0xe9df4049,0x7f4401fb,0x5fce60aa,0x399ac19f}}, // _dhún_, _eziq, _होइल, cüsü_,
+ {{0xd7cf8a15,0x765aaa16,0x61ee6090,0xbedb20f9}}, // _सोंच, _lity, _aobl, _aṣòw,
+ {{0x7bcaea17,0x442361f3,0x4c944a18,0x2d824a19}}, // [5b0] _kafu, _pjj_, литс, _deke_,
+ {{0x764d4a1a,0x24894a1b,0x64584425,0x628aaa1c}}, // smay, rsam_, _èvin, ksfo,
+ {{0x25de22b4,0x3f824143,0x23694098,0xfbdfa119}}, // खारी_, _feku_, pcaj_, _đê_,
+ {{0xe719813a,0x645aaa1d,0x628aa78c,0x6d452a1e}}, // ويات_, _aiti, dsfo, _izha,
+ {{0x7bdd4a1f,0x765aaa20,0x69a1e010,0x27e94a21}}, // _insu, _bity, _खाली, mjan_,
+ {{0x61ee6a22,0x6448a153,0x6aad40ce,0x527420eb}}, // _gobl, _chdi, mtaf, _جایز,
+ {{0x9f4de3df,0x224dc121,0x63a32069,0x628aaa23}}, // ünün_, tmek_, _önnu, gsfo,
+ {{0xe8d90031,0xb606c09f,0x7e61aa24,0x660dc06f}}, // _agọ_, ješć, _vulp, ďako,
+ {{0x3f836a25,0x27e94a26,0xa3e50148,0x9f5ca024}}, // _keju_, ijan_, नान_, rivò_,
+ {{0x5b1484c9,0x765aaa27,0xc1cb2010,0x7bdd40ae}}, // _смут, _gity, ायोग, _lnsu,
+ {{0xcd2bca28,0x7bcaea29,0xd5dd0046,0xfdf58a2a}}, // _کسان_, _dafu, यांज, _आवास_,
+ {{0x3f85e046,0xc332a087,0x1515ca2b,0x24442156}}, // salu_, _דוב_, _идея, döma_,
+ {{0x628b8a2c,0x7bcaea2d,0x6b86017b,0xf652800b}}, // nsgo, _fafu, rakg, יצט_,
+ {{0x2d87a8ef,0x7bdd4a2e,0xe787e251,0x7d09c00a}}, // nane_, _ansu, _чудо, mzes,
+ {{0x2aabca2f,0x15f4aa30,0x764e200e,0xa3e2a04b}}, // løb_, _अवसर_, rmby, धात_,
+ {{0x6e240a31,0x3f87aa32,0x61ee6a33,0x66044037}}, // _ujib, hanu_, _sobl, uhik,
+ {{0xb05b2277,0x25b2c121,0xe45a6a34,0x9f5ee4cd}}, // [5c0] rvär, _öyle_, джа_, citó_,
+ {{0xb05b2a35,0x6b83ea36,0x2d82431d,0x76498054}}, // svär, _jeng, _weke_, _ahey,
+ {{0x38abc042,0x645aaa37,0x61ef413a,0x61e9c0ff}}, // hør_, _riti, _focl, kjel,
+ {{0xe29a8143,0xddc3e0e0,0x645b82d9,0x7e6dea38}}, // дад_, _minő, _ciui, _čapl,
+ {{0x2d87aa39,0x78ad4017,0xc178e03b,0x442200e0}}, // fane_, ctav, ybė_, ink_,
+ {{0x6b83ea3a,0x6d464a3b,0x79840197,0x7bce25c4}}, // _neng, _izka, _leiw, ndbu,
+ {{0x3940c3c0,0xdb1c2639,0x27e0e098,0x60c44a3c}}, // _šis_, feré, _đin_, lwim,
+ {{0x645aaa3d,0x765b80ae,0xddc980c2,0x38abc271}}, // _witi, _giuy, _mieś, før_,
+ {{0x3f98c3df,0x63a44a3e,0x3eadc065,0xb606c490}}, // ğru_, igin, gtet_, vešć,
+ {{0x6ebda029,0x645aa54c,0xa91dca3f,0xaca44096}}, // _वैकु, _uiti, pažn, _nhọr,
+ {{0xa5c6e0f9,0x2fdea03e,0x2cadca40,0x60c44105}}, // _abóò, _intg_, ated_, hwim,
+ {{0xaca44088,0x69de20d1,0x26c6c286,0xdb156066}}, // _ahọr, _anpe, _tsoo_, _nazý,
+ {{0xa3df2a41,0x7d09c064,0x3f848a42,0x645c613a}}, // णाल_, czes, _memu_, _oiri,
+ {{0xe4d4e938,0x6602ca43,0x443361f6,0x645c6a44}}, // _مقتد, _hlok, xox_, _niri,
+ {{0x19954866,0xdb18a1de,0xdb1e206f,0xf1a74936}}, // лавя, levâ, _napä, кран,
+ {{0x60d600dd,0x2d848a45,0x6b83ea46,0x31354a47}}, // _frym, _neme_, _zeng, гебр,
+ {{0x60d60a48,0x7e640a49,0x3869e0a9,0x765c6a4a}}, // [5d0] _grym, _guip, _čari_, _biry,
+ {{0x2d87aa4b,0x7bc0ca4c,0x645b8090,0x7c2281cd}}, // xane_, hemu, _siui, enor,
+ {{0x69c0ca4d,0x8934001c,0xfb156553,0xf1aa0982}}, // keme, _معیا, دواج, _कामन,
+ {{0x7bc0ca4e,0x6e352274,0x60d5616d,0x4426ca4f}}, // jemu, lozb, _vrzm, _njo_,
+ {{0x645d48c4,0x588443fc,0x61e9ca50,0x12e44099}}, // _iisi, _выса, vjel, _кірг,
+ {{0x7bcbca51,0xb5fda16d,0x7988e403,0x645d4a52}}, // _sagu, _buša, jadw, _hisi,
+ {{0xa3a9aa53,0xad9b0049,0x61fbca54,0xed59ca55}}, // ग्ग_, thúl, tkul, deža_,
+ {{0x38abc042,0x645c6a56,0x2cadca57,0x7d09c186}}, // tør_, _ziri, tted_, tzes,
+ {{0x644ae13a,0x7a3d0a58,0x645c6a59,0x3f87aa5a}}, // _bhfi, céti, _yiri, panu_,
+ {{0x3fce207c,0x61e9ca5b,0x765d4a5c,0x38abc271}}, // _রক্ষ, sjel, _lisy, rør_,
+ {{0x7a388a5d,0x38720584,0x3ea04a5e,0x7c29406e}}, // mítr, _styr_, zuit_, čerj,
+ {{0x7d1ce049,0x15e4004b,0x7524ca5f,0x6724c066}}, // úrsa, गाधर_, _žize, _žije,
+ {{0x629ae62c,0x3eba2037,0xab5d817a,0xdb01ea60}}, // arto, _sppt_, _bażi, nflú,
+ {{0x3f85a2e2,0xab29ea61,0x6fde2064,0x645d4a62}}, // _jelu_, _пола_, माइं, _aisi,
+ {{0x69ce2a63,0x2fc04a64,0x645c6a65,0x4427e05d}}, // rdbe, teig_, _riri, _ijn_,
+ {{0x443fc181,0x20190a66,0x61fd0a67,0x60d60a68}}, // nlu_, cisi_, cksl, _trym,
+ {{0x44386435,0x645c6a69,0x63a44a6a,0x6e228a6b}}, // [5e0] _pkr_, _piri, rgin, vnob,
+ {{0x2d85aa6c,0x443fca6d,0xa50a6a6e,0xd8398030}}, // _nele_, hlu_, неза_, _akō_,
+ {{0x5a16e00b,0x3ea20a6f,0xb7d682f4,0xa8064315}}, // נקען_, lukt_, _aṣay, tlığ,
+ {{0x831b200b,0x78a0c72c,0x5336e00b,0xdc9b23c8}}, // _טויז, yumv, אנען_, _איטל,
+ {{0x2d85aa70,0x443ee5ec,0x81ce007c,0x877b000b}}, // _bele_, ylt_, রাপ_, גאני,
+ {{0xdb01a005,0x645d4a71,0xbe86668c,0x7a306105}}, // _eclí, _zisi, _مجرو, hätz,
+ {{0x20190a72,0x5faa0010,0x295561e1,0x443fca73}}, // yisi_, _काढल, _вътр, flu_,
+ {{0x7c244a74,0x3a3a2037,0xa3ab8046,0x57a6ca75}}, // nnir, _lkpp_, _खाय_, ушка,
+ {{0x3204805d,0xb21b6065,0x2dd7c049,0x60cdc143}}, // _ilmy_, _ivær, طبية_, _šamr,
+ {{0x78a28a2b,0x32b7c13a,0xdcfaa12a,0x2d85aa76}}, // muov, ادية_, _cetă, _gele_,
+ {{0x7988e1cd,0x2d8a2a77,0xaca32079,0xb5fda320}}, // radw, dabe_, _kwụg, _kušn,
+ {{0x443fca78,0x25e0c04b,0x443a2a79,0x69ce6a7a}}, // clu_, काळी_, _akp_, _labe,
+ {{0x60c9805d,0x765e2194,0xdb1ae07b,0x61fd0a7b}}, // _xsem, _bipy, metí, rksl,
+ {{0xd250e28b,0x2fcd050e,0x6440c1ea,0x645e2a7c}}, // _بنت_, žeg_, llmi, _cipi,
+ {{0x628e25ee,0xfc3f2a7d,0x731b20f9,0xafdb022e}}, // gsbo, ktív_, _bìjẹ, rløb,
+ {{0xddc2aa7e,0x6282ca7f,0x645d46a1,0x44f502a3}}, // jpož, _kwoo, _qisi, _впис,
+ {{0xbcfb4a80,0x656d4a81,0xa3c264f6,0x26c904a3}}, // [5f0] _afér, rcah, ्यं_, _usao_,
+ {{0x2bdd0a82,0x44290a83,0xd99961b3,0x7a3de5df}}, // याका, _hja_, ئنات_, tèti,
+ {{0x645f0004,0x656f0024,0x9f47ea84,0x765d4a85}}, // _épis, occh, _koná_, _tisy,
+ {{0xe8d7e076,0xdb156019,0x60060071,0x248dca86}}, // טובר_, _sazó, _tóme, ysem_,
+ {{0x66f701b0,0x765f0786,0x61ed4a87,0x443fca88}}, // ुनिक_, _miqy, mjal, vlu_,
+ {{0x683de355,0x32094a89,0x248dc06e,0xa3b0a40a}}, // pèdi, lhay_, vsem_, ट्ल_,
+ {{0xd8390098,0x3dd7207c,0x2aaf403c,0x38af420f}}, // jučo_, _সকাল, lüb_, lür_,
+ {{0x32094a8a,0x67ff000e,0xaca32125,0xa5bb0005}}, // nhay_, _bëjm, _gwụg, lióf,
+ {{0x3f85aa8b,0x60c98a8c,0x628f0064,0xfde6ca8d}}, // _telu_, _usem, jsco, рдык_,
+ {{0x7bcf4a8e,0x66160042,0x27e001d7,0x41a7c029}}, // _lacu, _smyk, _snin_, _गावस,
+ {{0x22404a8f,0x645e2a90,0x764d0a91,0xd5e60a92}}, // ylik_, _ripi, _bhay, ажни,
+ {{0xc106013a,0x22160a93,0x26d94024,0x2c6b8065}}, // لوجي, рфор, _orso_, _kød_,
+ {{0x20094a94,0x27edc00e,0x044622d3,0xef18e018}}, // dhai_, mjen_, иемн, daļu_,
+ {{0x6609c1d7,0x69cf40ae,0x46f5e0ff,0x225f8a95}}, // lhek, _aace, _вчит, _liuk_,
+ {{0xdb0aaa96,0x69cf4a97,0x7afbc5fc,0x78bd4a98}}, // lefò, _bace, lyut, _apsv,
+ {{0x27edca99,0x66040a9a,0xa3d30010,0x2d8b0a9b}}, // njen_, _ulik, _होऊ_, cace_,
+ {{0x443a2a9c,0x0d866a9d,0xa3d169e4,0x18a64a9e}}, // [600] _tkp_, илан, वयं_, разм,
+ {{0x6aa3a105,0x7bc3aa9f,0x6e3b8aa0,0x27edc00e}}, // kunf, jenu, _akub, hjen_,
+ {{0x27edc0dd,0x6705eaa1,0x201a6aa2,0xdce8e041}}, // kjen_, शनिक_, ripi_, vadī,
+ {{0x3f87eaa3,0xe8fa8aa4,0xdca64aa5,0x661aeaa6}}, // _lenu_, кле_, _ками, zitk,
+ {{0x661ae07d,0xdb0b8510,0x7c298aa7,0x224d82f8}}, // yitk, degó, _djer, _dhek_,
+ {{0x44382aa8,0x25a681cd,0x25aa60ae,0x69cf4015}}, // nor_, sgol_, _ucbl_, _zace,
+ {{0x6b8b8035,0x082ac8f4,0xa3c80aa9,0x2cb24aaa}}, // bagg, кции_, ैया_, ltyd_,
+ {{0x27edc00e,0xe5a38aab,0x69c44aac,0xa2c360a8}}, // gjen_, _миси, neie, रबर्,
+ {{0x644d0aad,0x65696121,0x6fa7c033,0x3f8b029c}}, // _rhai, _şehi, _गारं, vacu_,
+ {{0x6abe20ff,0x2d8b0068,0x44382aae,0xceb34095}}, // _oppf, wace_, jor_, קית_,
+ {{0x7d0de605,0x2d87eaaf,0x661aeab0,0x3a38223c}}, // _ƙasa, _dene_, ritk, dorp_,
+ {{0xb4c6e295,0x661aeab1,0x7988aab2,0x644d0048}}, // _उनी_, sitk, _hedw, _qhai,
+ {{0x3f87eab3,0x44382488,0xe9470691,0x628f0024}}, // _fenu_, for_, рхно, usco,
+ {{0x3cee8064,0x60db423d,0x2d8b0ab4,0x1866e1e3}}, // _आपने_, _šumo, sace_, _књиг,
+ {{0x6e94eab5,0xdb1c22ee,0xe7fb4010,0x44268ab6}}, // _البا, berí, _एकदा_, kno_,
+ {{0xa3bb04aa,0xdb172005,0x2d87eab7,0x1473e0e0}}, // _شاعر_, _saxó, _zene_, _باوج,
+ {{0x478b6491,0x6b8b8ab8,0x2167626f,0xc953c00b}}, // [610] всем_, vagg, атег, ַמע_,
+ {{0x44268ab9,0x2d876aba,0x201b403c,0xc7b8e16d}}, // eno_, úne_, siqi_, biđe_,
+ {{0xe719613a,0x69c44abb,0x6d4085dd,0x7e608abc}}, // ديات_, beie, _ayma, _bimp,
+ {{0x2a690048,0x2d98e064,0xe29f0232,0xb05b6156}}, // _kuab_, órej_, orð_, _späd,
+ {{0x6b8b8abd,0x60daaabe,0x2a690022,0x26cca057}}, // ragg, _artm, _juab_, _asdo_,
+ {{0x6b8b8abf,0x2006cac0,0xf991a13a,0xd838e07f}}, // sagg, _aloi_, سبت_, joč_,
+ {{0xdb0b8ac1,0x27edc588,0xac946ac2,0x2a690048}}, // tegó, tjen_, _марш, _luab_,
+ {{0x16348503,0xdb0e2ac3,0x6ebb4295,0x2d87eac4}}, // _деся, ékér, _उहाँ, _sene_,
+ {{0x69c3aac5,0x2d87eac6,0x2d8ceac7,0xdb0b8ac8}}, // pene, _pene_, fade_, regó,
+ {{0x3206c019,0x61e2cac9,0x6006e3fc,0xdb18aaca}}, // _eloy_, _enol, йным_, devæ,
+ {{0x63ad0037,0xddc66013,0x6609c0bb,0x7afbc05d}}, // _bcan, _sukū, phek, syut,
+ {{0x3255cacb,0x3f87eacc,0xb5fd8361,0x2cbf807d}}, // _увер, _wenu_, _fiše, _kpud_,
+ {{0x60060057,0x3a382156,0x6b8d4acd,0xdb1c21af}}, // _cóma, torp_, daag, rerí,
+ {{0xdb0600eb,0x6d41aace,0x59aa0acf,0x7e96c1b3}}, // ngké, _kyla, _कावर, _انور_,
+ {{0x69c44ad0,0xf8bf40f9,0xddcd212a,0x7643a07d}}, // teie, _apé_, _staţ, ilny,
+ {{0x1f66439e,0x6607616d,0x249fcad1,0x60dce271}}, // скам, _aljk, orum_, ærme,
+ {{0xdb18e071,0xc044e067,0xa01b2156,0x249fcad2}}, // [620] _favó, _đoạt_, rsör, nrum_,
+ {{0xe8194ad3,0xe0584050,0x20004025,0x49ca2ad4}}, // नोबा_, _چیست_, skii_, _слон_,
+ {{0x6b898ad5,0x69ad239f,0x09f72095,0x7c2dc06e}}, // _beeg, _जाती, _המים_, čarj,
+ {{0x2a690022,0x81cc407c,0x3f8ce066,0x6443aad6}}, // _yuab_, লাহ_, zadu_, elni,
+ {{0xb4e0a4e5,0x6d41a20f,0x38b1e3e4,0x44d82098}}, // दम्_, _ayla, hár_, _hč_,
+ {{0x41e2a064,0xaca40125,0xdee66ad7,0x63a9cad8}}, // पाइस, _adịw, _логи, egen,
+ {{0x23698ad9,0x64428ada,0xdca362a3,0x2d8ceadb}}, // žaja_, ploi, _нари, vade_,
+ {{0x9874cadc,0x3a3a6277,0x201caadd,0x7bf8c331}}, // олиц, lopp_, sivi_, рнир_,
+ {{0x44446ade,0x2902cadf,0x201a2071,0xf76fc7f0}}, // ol_, áka_, _ompi_, راي_,
+ {{0xf09f014a,0x7cde812a,0x2ca04ae0,0xea012067}}, // vrà_, _cărţ, irid_, _đắt_,
+ {{0x7bc60ae1,0xd7efcae2,0x89dba095,0x2d8ceae3}}, // feku, _еу_, _תחבי, rade_,
+ {{0x3f8ea296,0x38612ae4,0x2a690286,0x44d7e12a}}, // lafu_, _sihr_, _puab_, _dă_,
+ {{0x2ca0405d,0xdb18e019,0x44facae5,0xade5c10a}}, // jrid_, _pavó, _حراج_, कारन_,
+ {{0x7bd82ae6,0x25ad017a,0x2a690022,0xdb0f80fa}}, // _óvul, żel_, _vuab_, édér,
+ {{0x661d0ae7,0xfc3f2359,0x443eaae8,0x44446ae9}}, // pisk, guía_, _mkt_, dl_,
+ {{0x3f8eaaea,0xf09f0024,0x61f5619f,0x9f4ca425}}, // hafu_, prà_, _sozl, _modà_,
+ {{0x26dce098,0xa3ba410a,0x6443a944,0xdb18a1dd}}, // [630] _hrvo_, _आउर_, ylni, levä,
+ {{0x6b8d4aeb,0x7e7d0aec,0xbddb4aed,0x21676aee}}, // saag, mpsp, _chèr, стаг,
+ {{0x3ea0400e,0x7c28eaef,0x61e52174,0xdb1aa019}}, // arit_, indr, _inhl, _mató,
+ {{0x8d87804e,0x201eeaf0,0xa91dcaf1,0x6e3aeaf2}}, // сужд, hiti_, saži, hotb,
+ {{0x7e61a07d,0x35de2028,0xda78a06f,0x443b475f}}, // _pilp, _फोड़, _atď_, moq_,
+ {{0x69c724bd,0x44446af3,0x2d8a6af4,0xd006aaf5}}, // neje, cl_, _gebe_, жеше_,
+ {{0x7bc60af6,0x92e9807c,0x2baa0af7,0x6d42caf8}}, // yeku, মনে_, _काला, _ayoa,
+ {{0x2bd906e1,0x7c2d0af9,0xb05b2105,0x6e2d0050}}, // _बोला, _njar, swäh, _njab,
+ {{0x6443a05c,0xa3e9aafa,0x7cde812a,0x443ea7a0}}, // plni, याद_, _părţ, _fkt_,
+ {{0x3ea5e10a,0x115c400b,0x6e2d0afb,0x201eeafc}}, // tult_, נדזע, _ajab, giti_,
+ {{0x25dde010,0xa3e50afd,0xa3ea0afe,0xb4e3e046}}, // _कोणी_, नां_, адна_, नमे_,
+ {{0xcad66095,0x201a20c7,0x290be064,0x38b1eaff}}, // קורת_, _smpi_, ąca_, rár_,
+ {{0x2903e06e,0x3a294b00,0xe2dac0e0,0xc483093b}}, // šja_, dnap_, _پانچ_, мляк,
+ {{0x201fc03b,0xee3f406f,0xa7fb4071,0x3a2d8320}}, // liui_, _aký_, _riña, _ljep_,
+ {{0x661b8b01,0x3f8a6b02,0x2d8f8b03,0xdd8ee049}}, // _omuk, _rebu_, kage_, _شوي_,
+ {{0x3f8b4480,0xa3e50861,0x200dc1ae,0x201fc03b}}, // _cecu_, नाः_, nhei_, niui_,
+ {{0x661e6b04,0xbddb4004,0x64444665,0x3ea04aaa}}, // [640] tipk, _chèq, rlii, urit_,
+ {{0x7c2d00dd,0x3ea04b05,0xbb1b0004,0x2bd1c010}}, // _zjar, rrit_, raît, _दोघा,
+ {{0x2d8f8004,0xfc3f21af,0x660d41fb,0xf1bf0009}}, // fage_, quía_, bhak, knál_,
+ {{0x661e68a4,0x3a3f8345,0x3ea7a292,0x2d8b43ed}}, // sipk, _ckup_, dunt_, _gece_,
+ {{0x201eeb06,0x8e570087,0x661b805d,0x443eab07}}, // yiti_, קינג_, _dmuk, _pkt_,
+ {{0xdd92601c,0x2d8b412a,0x9f4fc071,0xddd56018}}, // _زور_, _zece_, _cogí_, _aizņ,
+ {{0x66098b08,0xdb1c60dd,0x321eeb09,0x3ea7ab0a}}, // _flek, _mbrë, vity_, gunt_,
+ {{0xf1bf0803,0x69d64b0b,0x3f8f8b0c,0xdcbac14a}}, // gnál_, ndye, cagu_, ащо_,
+ {{0x2fc7ab0d,0x29004b0e,0x225e4066,0x7e63eb0f}}, // beng_, nyia_, ítky_, _binp,
+ {{0x2fc7ab10,0x7c3c2071,0xd9c74046,0x7c2d0b11}}, // ceng_, jorr, _लस्ट, _sjar,
+ {{0xd83904ad,0x7c3bcb12,0x321ee42c,0x104b004e}}, // luči_, bour, rity_, иями_,
+ {{0x320dc10f,0x6b8226be,0xdb1d204a,0x2d8cab13}}, // chey_, _đoga, ázít, _hede_,
+ {{0x2d8cab14,0x3f8ca096,0xed5acb15,0xf9938557}}, // _kede_, _kedu_, _тод_, ורר_,
+ {{0x38b44156,0x63bb2422,0x60c2cb16,0xa91dcb17}}, // där_, đuno, _ipom, gažu,
+ {{0x2d8cab18,0x3f820058,0xdb1c2b19,0xf485063b}}, // _mede_, bbku_, terá, _کابی,
+ {{0x7a3fa004,0x386ca066,0x7e7d0b1a,0x83fcc143}}, // nêtr, _mudr_, ppsp, _buđi,
+ {{0x200a6037,0x3a2940e0,0x38b44156,0x2fc7ab1b}}, // [650] _blbi_, rnap_, gär_, yeng_,
+ {{0x35b108c6,0x2d8cab1c,0x78a28b1d,0xd37b6b1e}}, // _झाड़, _nede_, drov, рча_,
+ {{0x81ce007c,0x2d83200e,0xc7b221a9,0x798d0037}}, // রার_, mbje_, _אבי_, _keaw,
+ {{0xb87b4b1f,0x61f8e271,0x44294b20,0xdb1c62a8}}, // _príd, _lovl, qna_, _aaró,
+ {{0x443cab21,0x3f8cab22,0xeb76400b,0x2bb2235b}}, // fov_, _bedu_, _מערץ_, _जामा,
+ {{0xf1b9c29c,0x28b9a13a,0xa91d8b23,0x6d4f42a8}}, // diša_, مطبخ_, _ježi, _izca,
+ {{0xeb99cb24,0x6b8bc14a,0x7c29c042,0xc7b9c0e0}}, // бий_, _pegg, tner, tfő_,
+ {{0xa91d8b25,0x7c29cb26,0x95f5eb27,0xddc1c143}}, // _leži, uner, ेस्ट_, _bulš,
+ {{0x7c3bc12a,0x6b9b8114,0x7c29cb28,0xf1bf0387}}, // rour, _idug, rner, rnál_,
+ {{0x7e7b8b29,0x7c3bc095,0x69d56b2a,0x11d5e049}}, // _itup, sour, _jaze, ستخد,
+ {{0x6d440b2b,0xee3a02d7,0xa91dc326,0xe1fb4010}}, // _syia, йно_, važu, _एकूण_,
+ {{0xd12ec555,0x78b6404e,0xf1b9c04d,0xb2baa095}}, // ئمی_, ytyv, biša_, _למער,
+ {{0xa91d8500,0xa3ac6a41,0xdee5c103,0x66e5c423}}, // _beži, _गाल_, _холи, _хола,
+ {{0x61f9c054,0x78a9c333,0x63ad4b2c,0xf1bf41e9}}, // _howl, muev, egan, _abá_,
+ {{0xdb1c2602,0x66e32b2d,0x6d4f42a8,0xed59cb2e}}, // _üzün, нора, _azca, deži_,
+ {{0x80b1e07c,0x68fceb2f,0x661c6b30,0x83fcc012}}, // _জনপ্, ärde, _smrk, _puđi,
+ {{0xb95b40f9,0x24890125,0x2d9127ef,0x6441a015}}, // [660] _abìb, _gwam_, baze_, _okli,
+ {{0x4a436b31,0x63ad4b32,0x04466251,0x25bf8b33}}, // енув, agan, чезн, _abul_,
+ {{0x26df8125,0xdb1d4b34,0xb5fb40e0,0xf1bf0066}}, // _aruo_, _basó, _kiál, znám_,
+ {{0x38c88062,0x68e32005,0xdb0524d2,0x443d8b35}}, // قاتی_, _ánda, _uchá, gow_,
+ {{0xdb1c6317,0x2d924b36,0x200ea6ae,0x6b844b37}}, // _paró, maye_, thfi_, mbig,
+ {{0x6b9b8b38,0xec168011,0x6e21eb02,0x6aa28733}}, // _edug, _کورد, jilb, rrof,
+ {{0x2a64805d,0x22468b39,0x5886a013,0xdb1d4b3a}}, // _timb_, plok_, чыла, _absè,
+ {{0xa3dca046,0xc181c0e0,0x386d80cb,0x2d924b3b}}, // _तोर_, پیوٹ, _fuer_, naye_,
+ {{0x9f4eeb3c,0xdca38b3d,0x64a38afe,0xe8e0c119}}, // _sofà_, _пати, _пата, _đồi_,
+ {{0xd776a13a,0x69d60b3e,0x6e228b3f,0x6b8e6320}}, // _رائع, _aaye, liob, _jebg,
+ {{0x26de6098,0x7bcaa167,0xdcf6003c,0x660f000e}}, // _štoj_, mefu, _peyğ, thck,
+ {{0xa3d7cb40,0x27e6c0ae,0x2ee001cd,0xab5b0b41}}, // ायः_, _unon_, _brif_, nfüg,
+ {{0x23ac6b42,0x7c21eb43,0x9f45a0d1,0x69d60b44}}, // _चांद, bilr, _anlè_, _daye,
+ {{0x69caab45,0x9c83206f,0x2005e106,0x9605437f}}, // nefe, účov, ckli_, रस्ट_,
+ {{0x49bbe008,0xc1bbe095,0x3f912b46,0x3bbbe557}}, // _המוס, _המוש, razu_, _המוד,
+ {{0xa7fb42b2,0x69caab47,0x38b60b48,0xa5bb4031}}, // _viño, hefe, bær_, _ajór,
+ {{0x8f9c0095,0x69d56b49,0x2fc94256,0x27e90b4a}}, // [670] _היחי, _paze, reag_, _inan_,
+ {{0xdb0780fa,0x39474018,0x7bc08194,0x2bf981a6}}, // _élég, āns_, _abmu, _আগেই_,
+ {{0xb5fd82c9,0x7fd5814f,0x63ad4b4b,0xda0cc0f1}}, // _dišn, ділі, pgan, िघात_,
+ {{0x7bd56b4c,0x7641a013,0x38b56584,0x6b844025}}, // _wazu, _skly, rår_, cbig,
+ {{0x3e71a081,0xa3ace010,0xa5bb4069,0x92ad607c}}, // _sát_, गला_, _fjór, কবে_,
+ {{0x7c3e6024,0x466b6b4d,0x3e71a04a,0xdb1c2156}}, // copr, _грам_, _pát_, berä,
+ {{0x6263c0e0,0x1dd7e021,0x69d72b4e,0x7d0280ae}}, // _főol, _भोगत, _maxe, ayos,
+ {{0x1309c09e,0x386d80fa,0xc1740020,0x63bd0171}}, // жний_, _tuer_, _kaɗe_, jfsn,
+ {{0xdb1c60c4,0x61e98b4f,0x25adcb50,0x7bcb8b51}}, // _harð, _inel, rgel_, megu,
+ {{0xad9b0232,0x61faa156,0x99ddc05c,0x9f4eeb52}}, // skúl, _gotl, plňo, _sofá_,
+ {{0x44232b53,0x22494b54,0x2d924068,0x61e98b55}}, // dij_, klak_, yaye_, _knel,
+ {{0x6448e265,0x442ceb56,0x38b60042,0x7ae36052}}, // aldi, and_, tær_, änty,
+ {{0x6449c31b,0x81d0a07c,0x69d72025,0x61e8ab57}}, // mlei, ষার_, _baxe, _yndl,
+ {{0x2d924b58,0xb5fb4359,0x200c2b59,0x35fbe050}}, // waye_, _diám, ódi_, _درصد_,
+ {{0xde6d0016,0x614325e5,0x7c2d4069,0x7bd60b5a}}, // _vươn, вера, fnar, _tayu,
+ {{0xa3b1cb5b,0xa5bb003a,0x6e23ab5c,0x6aa12632}}, // ञ्च_, lión, hinb, álfa,
+ {{0xa3d927f4,0x76440b5d,0x60c9600a,0x7c294066}}, // [680] _ठोक_, _ikiy, _ćema, čerp,
+ {{0x62964b5e,0x6449c105,0x7c2da049,0x7d06810a}}, // rsyo, hlei, éarl, _üksi,
+ {{0x62964b5f,0x7643e037,0x0403407c,0x200d8488}}, // ssyo, _mkny, রোধী_, _blei_,
+ {{0x629640d1,0xc314c07c,0x2d8fcb60,0x27e9e0e8}}, // psyo, াপতি_, _nege_, _ɗana_,
+ {{0xe7e32010,0xdb1e2b61,0x2fc1217b,0x6d4760c4}}, // खाचा_, _tapó, _ybhg_, _eyja,
+ {{0xdb1c204e,0x7e6e6022,0x753600ae,0xa3aea04b}}, // perä, _tubp, _mxyz, _काळ_,
+ {{0x7d044b62,0x7c228b63,0x07a3639d,0x9f4361e9}}, // lyis, pior, _јасн, _dojó_,
+ {{0x61fc6b64,0xe1ff4057,0xc6f8cb65,0x7bcaa1b7}}, // _horl, _lión_, зних_, sefu,
+ {{0x7bcaab66,0x61faa2f7,0x3e7b8b67,0x6f0445f1}}, // pefu, _totl, _câtă_, nyic,
+ {{0xa5bb006f,0x660d0b68,0x44f4e73f,0x31574053}}, // gión, _slak, епос, ויפן_,
+ {{0x6449cb69,0x61fc6b6a,0xa3b6c033,0xb4bc604b}}, // blei, _morl, _जाप_, _अहो_,
+ {{0x6dc4800a,0xceb8ea0b,0x0906c1c0,0x7c2e21b9}}, // _učač, rtę_, _опен, enbr,
+ {{0x1169c555,0xceb8eb6b,0x63b40089,0x62828b6c}}, // _علوی_, stę_, _žená, npoo,
+ {{0x25756ac3,0x69c1a05d,0xdb1e20d1,0xe1ff407b}}, // _múló_, _xble, _rapò, _ción_,
+ {{0x660d0b6d,0x2019eb6e,0x394d400a,0x7c2d4b6f}}, // _tlak, ësie_, _šes_, tnar,
+ {{0x44dee17a,0x600b2046,0x44200b70,0x7bdaeb71}}, // _iċ_, _gümn, _gmi_, ldtu,
+ {{0xa3e88b72,0xcd04204a,0x2904cb73,0xdb098b74}}, // [690] बाई_, vněž_, nyma_, _oceá,
+ {{0xf3ff4016,0x7c2d4b75,0xa3eac960,0x9f8920c4}}, // _giãn_, snar, माँ_, _góðu_,
+ {{0x7649c121,0x7c2da049,0x08f80062,0xe7e4004b}}, // yley, éarm, _فریب_, गाचा_,
+ {{0x7bc1a13b,0x6440cb76,0x6298ab77,0x44442b78}}, // _sblu, jomi, dsvo, _zk_,
+ {{0x3d17a7a7,0x3f8fc939,0x26c6c05f,0x6d476064}}, // _भेजे_, _regu_, _npoo_, _wyja,
+ {{0xc3324095,0x443fc157,0xb4cb435b,0xdb1c2156}}, // _תוך_, wou_, लबी_, rdrö,
+ {{0xa3eac029,0xe3b1c13a,0x6449cb79,0xeb99c1fc}}, // मां_, هرة_, tlei, пий_,
+ {{0x6440cb7a,0x248ca057,0xeb99c04e,0x44212b7b}}, // gomi, _dwdm_, чии_, _lmh_,
+ {{0x68e2c2d9,0x3947e3e0,0x0e35a955,0x63ada35f}}, // _crod, _syns_, _знаю, ɗann,
+ {{0x7c244b7c,0x7ae2cb7d,0x69c1ab7e,0x4424c256}}, // ziir, _drot, _uble, aim_,
+ {{0xb4be24ef,0x38b8eb7f,0x6449cb80,0x6d48a1cd}}, // _इहे_, gér_, plei, _gyda,
+ {{0x4424cb81,0x64a60b82,0x44212256,0xd875c13a}}, // cim_, нама, _amh_, _كاتب,
+ {{0xb3b1008c,0x62998b83,0xea9fe1e9,0x38690b84}}, // _झारख, nswo, _baṣi_, _diar_,
+ {{0x4444213e,0x69dbc108,0x9c7ca381,0x3e7523e0}}, // _qk_, ldue, _hrča, _båt_,
+ {{0x78bbcb85,0x7c2e2b86,0x44212aaa,0x68e2c6c4}}, // ltuv, rnbr, _dmh_, _zrod,
+ {{0xfc3f2359,0x7c36200a,0x7bdbcb87,0x61fd4b88}}, // rría_, _šarč, nduu, _cosl,
+ {{0xa3b6e295,0x7bd9cb89,0xdcf460a9,0x9c7ca143}}, // [6a0] ङ्क_, _mawu, đačk, _mrča,
+ {{0x25bfcb8a,0xb5fb6b8b,0x41c10026,0x68e3e391}}, // hful_, _suár, ष्ठस, _krnd,
+ {{0x78bbc2df,0x2fd86b8c,0x61ebcb8d,0x2a7f80ae}}, // htuv, _varg_, _ongl, _atub_,
+ {{0x7e698064,0xdd8f833e,0x78bbcb8e,0x2a69005f}}, // _ciep, موم_, ktuv, _xiab_,
+ {{0x3f868b8f,0x7bdbcb90,0xc8b6402d,0xa5bb0071}}, // rbou_, dduu, _ясны, diól,
+ {{0xb3cf607c,0x998940c2,0x62812019,0x7ae40b91}}, // রাউজ, chał_, ílog, _mrit,
+ {{0x7f498a43,0x5334a04e,0xb4be24ef,0x39e764c9}}, // _eyeq, ъект, _इहो_, _ядро_,
+ {{0xd9436b92,0xb6bb61a9,0x69d8eb93,0x69c3eb94}}, // _сеси, _מצלי, _save, _abne,
+ {{0x6d48ab95,0x442241e7,0x7bd9cb96,0x3eadc1ab}}, // _wyda, _mmk_, _dawu, guet_,
+ {{0xb906a07c,0x9f45a071,0xe3bf003e,0x625341de}}, // _পথ_, _coló_, diña_, içoa,
+ {{0x6440cb97,0x69dbcb98,0x62588049,0x644642e4}}, // pomi, bdue, ríof, _okki,
+ {{0xb87b004a,0x68e40b99,0x69dc2b9a,0xa3e76466}}, // jvíc, _brid, edre, _मोन_,
+ {{0x2d920b9b,0x7bdc2b9c,0xf8cf40c5,0x7bd8eb9d}}, // _deye_, fdru, _सैंय, _tavu,
+ {{0xdb1c627f,0x24520b9e,0xd7f82093,0x78adc066}}, // _abrí, ráma_, нус_, ňový,
+ {{0x9980403a,0x61ebc1cd,0xa3aea148,0x07e74b9f}}, // zniť_, _yngl, _काई_, нцам,
+ {{0xe3bf0005,0x80c22ba0,0x69dc2ba1,0x6ed3a5fc}}, // biña_, _लहरे, adre, _qəbə,
+ {{0x4034404e,0x7b74413a,0xb5fda6a6,0x69cf0ba2}}, // [6b0] уетс, اطفا, _mušr, mece,
+ {{0x2fd941cd,0xb4bf0ba3,0x2fcdc022,0xb4cd0046}}, // _wasg_, ीबी_, zeeg_, रबी_,
+ {{0x3f916ba4,0x02b10960,0xd4e467ca,0xaefb41e9}}, // _vezu_, _जमुन, люци, _agùt,
+ {{0x38baa00e,0x2d216010,0x76428025,0x7c256098}}, // dër_, _मधील_, gooy, sihr,
+ {{0xfd964694,0x69dd0ba5,0x38baaaaa,0xfbb8607c}}, // _בדרך_, idse, eër_, ীয়ত,
+ {{0x764d4ba6,0x6e22cba7,0x7c260163,0x2fcdc31d}}, // llay, _amob, zikr, weeg_,
+ {{0x41b100c2,0x7bcf00b1,0x9c7ca143,0x8c3d0181}}, // _झांस, kecu, _prča, luğu,
+ {{0x69cf0ba8,0x2d8947d3,0x6441eba9,0x2ca9428f}}, // jece, nbae_, woli, orad_,
+ {{0x4ec3a1a6,0x25bfcbaa,0xa3ace04b,0xe3bf003e}}, // ্মেল, rful_, गलं_, xiña_,
+ {{0x2b9c6026,0x764d4bab,0x1309e04e,0x68e40bac}}, // mácí_, hlay, дной_, _rrid,
+ {{0x2d9205cd,0x7ae4003b,0x24800bad,0x2ca94066}}, // _peye_, _srit, _stim_, hrad_,
+ {{0x6281abae,0x62856baf,0x7bdaabb0,0x7bdb807d}}, // _itlo, ipho, _zatu, _jauu,
+ {{0x7bdaabb1,0x7bdc2bb2,0xb5fd803b,0x7bce2bb3}}, // _yatu, tdru, _aišk, tebu,
+ {{0xf1c4a37f,0x33d5284f,0x7d060bb4,0x4425ebb5}}, // ल्पन, ліст, ryks, qil_,
+ {{0x6d4ae0e2,0x3e76486b,0x78bd0bb6,0x3ea94bb7}}, // _gyfa, _sæt_, atsv, erat_,
+ {{0x3f894bb8,0x7bdb88c4,0x69cf0bb9,0x7bdc2bba}}, // gbau_, _nauu, cece, sdru,
+ {{0xe9da6bbb,0x6e240bbc,0x3ea94bbd,0xba3b25df}}, // [6c0] мка_, _imib, grat_, tuït,
+ {{0x81ce01a6,0x764d4bbe,0xaf062bbf,0x20000187}}, // রাও_, alay, _апол, _hoii_,
+ {{0x764d4bc0,0x44224bc1,0xa3b36ba0,0xa3d5a861}}, // blay, _tmk_, _टार_, िजन_,
+ {{0x7989c171,0x4427a3f6,0xb87b027f,0xbea361ba}}, // jbew, ein_, lvía, рацк,
+ {{0x3ea94bc2,0x8c3d0106,0x644283c5,0x6d556035}}, // crat_, cuğu, rooi, _ezza,
+ {{0x7c22cbc3,0x629aebc4,0x6aa9c0fd,0x6383616c}}, // _smor, ysto, dref, _вгра,
+ {{0xf7732076,0x41dd8bc5,0x62856bc6,0x69dd022e}}, // סקה_, _नोकस, cpho, ydse,
+ {{0x34c2a38e,0xa3b64acf,0x7659cbc7,0x6b89cbc8}}, // _शहीद, च्च_, _chwy, gbeg,
+ {{0x62588bc9,0x44324bca,0xe3baabcb,0x20d3e12a}}, // ríod, iny_, _оба_, _aţi_,
+ {{0x61ed006a,0x7afce156,0x3a2481e7,0x7bc28bcc}}, // _snal, ärto, _hmmp_, lfou,
+ {{0x26c9016d,0xb95b40f9,0x65950bcd,0x6448a041}}, // _spao_, _abìl, лану, _ikdi,
+ {{0x0326c312,0x27ff0069,0x7ac6cbce,0xa5bb40f9}}, // _иден, ðun_, _аске, _amón,
+ {{0xdcf52041,0x25a127d3,0x629aebcf,0x403408e9}}, // pazī, _pdhl_, ssto, реяс,
+ {{0xc0aaebd0,0x7bc64020,0xf1bf0057,0xb4bec3fa}}, // _قابل_, _abku, cián_, ँझी_,
+ {{0xd378a0e4,0x333a2057,0x9f47e071,0x07a3e39d}}, // _gaće_, _cxpx_, _donó_, _тајн,
+ {{0x228426bb,0xae1f05cb,0x2ca9487d,0xdcfc604d}}, // рург, मोशन_, trad_, _nerđ,
+ {{0x3f982bd1,0x9f5ea0dd,0x26c9006a,0x5d554bd2}}, // [6d0] baru_, _botë_, _upao_, укат,
+ {{0x25e2a295,0x61ee6037,0x6e28e0e4,0x644d4bd3}}, // _टोली_, _cnbl, nidb, slai,
+ {{0x8c3de106,0x44324b46,0x2d9900ba,0x6b682009}}, // muşt, bny_, oase_, _végü,
+ {{0x7643abd4,0x2ca9406f,0xb8cb6626,0x69dc6bd5}}, // wony, prad_, _खि_, _eare,
+ {{0xceeb8555,0x6282c286,0x7659c1cd,0x8fa5abd6}}, // بران_, _ntoo, _rhwy, гале,
+ {{0xdce8a03c,0x201e4018,0x629d0bd7,0xf1bf0005}}, // _addı, ītie_, isso, xián_,
+ {{0x5e58200b,0x3f990bd8,0x6ecf8026,0xafdb046b}}, // דיגע_, kasu_, _धनकु, nløs,
+ {{0xa3eac38e,0xa3c16bd9,0x8af7c497,0x27ed8bda}}, // माग_, ंभव_, _çəki, _unen_,
+ {{0xb5fd8361,0xa5358052,0xfc3f203e,0xfe45e01b}}, // _niši, _снач, luíu_, лнио,
+ {{0xe8f9ebdb,0x443249a1,0x63a2cbdc,0x2d982bdd}}, // ело_, zny_, _edon, xare_,
+ {{0xbef380aa,0xabb5c1b3,0xaad8cbde,0x224dcbdf}}, // _आपुन_, _مماث, _बैठक, plek_,
+ {{0x2d990174,0x6601a04a,0x3f99035d,0xb8ebe960}}, // gase_, _holk, gasu_, _रङ_,
+ {{0xb05b604e,0x18a5cbe0,0x645aa052,0x386d8be1}}, // _epäi, _салм, _yhti, _mier_,
+ {{0xddcd2be2,0x290940ae,0x60060be3,0xee3f2066}}, // _etaž, dyaa_, _rómu, brým_,
+ {{0x8fa640db,0x03a64be4,0x8c1ae095,0x44324be5}}, // лаве, ливо, _רוצי, tny_,
+ {{0x6e29cbe6,0xa3e5a077,0xb5fd86a6,0x69dd494c}}, // nieb, _बोस_, _fiši, _case,
+ {{0x2cbfcbe7,0x6b956be8,0x7bdd4be9,0x7d1bcbea}}, // [6e0] ntud_, _bezg, _dasu, nzus,
+ {{0xb8cc4a53,0xb05b2052,0x28a6c046,0x9d1b01a1}}, // _गि_, ttäe, _खिसि, _נויט,
+ {{0x6e29c200,0x09e68beb,0x7bc28bec,0x41aa816c}}, // kieb, ложн, tfou, евен_,
+ {{0x2bc02bed,0xf2d32087,0x4425a1cd,0x7e7ee0e0}}, // श्रा, _פער_, _aml_, _éppe,
+ {{0x8cd3c077,0xc7a38471,0x69c44363,0x3e78a119}}, // _बैलो, сичк, mfie, _sét_,
+ {{0x644601fb,0x6b960bee,0x644980a2,0xfc3f203e}}, // hoki, _leyg, _ekei, buíu_,
+ {{0x2fddc048,0x2d990bef,0x7ae44bf0,0x6e28e3de}}, // _bawg_, yase_, lvit, vidb,
+ {{0x244e805c,0xe3bf027f,0x7bdd4054,0x6b98a249}}, // kými_, viño_, _xasu, savg,
+ {{0x7e6d065f,0x2245e042,0x69c16984,0xb87b40f9}}, // _riap, folk_, र्वी, _aríj,
+ {{0x32bb400b,0x290a2013,0x4086ebf1,0x27e04052}}, // עזיד, kyba_, лумб, hdin_,
+ {{0x6d4d00c7,0x7f4d0058,0x44294bf2,0xe9ff8067}}, // _syaa, _syaq, yia_, _chặn_,
+ {{0xe8d7a095,0xb4d4e077,0x63a2cbf3,0x6ee6c243}}, // _תואר_, _हनी_, _udon, _مستل,
+ {{0xdddbe38a,0x63a401cd,0x69c8a0ae,0x3f9a6bf4}}, // spuš, _ddin, _kbde, kapu_,
+ {{0x69dd4bf5,0xd576e052,0x3f9900b1,0x44e3a106}}, // _sase, _судь, sasu_, _yı_,
+ {{0x6016e01f,0x3ea00bf6,0x7ccaa497,0x799aebf7}}, // _câme, _evit_, _hərb, matw,
+ {{0x6b9aebf8,0x9f5eabf9,0x6288e197,0x7abbe0be}}, // latg, _coté_, mpdo, _נצחו,
+ {{0x9477401e,0x69c8a06e,0x37ab662a,0x20012046}}, // [6f0] ادرا, _obde, етен_, _tohi_,
+ {{0x4438607d,0x6fb22064,0x60cd0bfa,0x442a2bfb}}, // _cjr_, _जाएं, _ipam, cib_,
+ {{0x3e7980d1,0x6013820f,0x6113e03c,0x7ae44bfc}}, // _sèt_, _açma, tələ, avit,
+ {{0xd6d861ba,0x7bde249f,0x4429400e,0x6601abfd}}, // ыту_, _zapu, qia_, _solk,
+ {{0x2d94203a,0x6601abfe,0x6113e03c,0x3ec4404a}}, // ždeň_, _polk, rələ, ěstí_,
+ {{0x386d8363,0x6258813a,0x3dddc058,0x867c000b}}, // _wier_, tíoc, _saww_, ערוו,
+ {{0x80d58029,0x3d0ee0c2,0x9f424052,0x8015cbff}}, // _मैरे, ानों_, _enkä_, ифиц,
+ {{0x6b8d4c00,0x673b8048,0x7999805d,0x4425a1f6}}, // nbag, _txuj, qaww, _wml_,
+ {{0x6d4e61e2,0x64460c01,0xee39ebd2,0x7af68052}}, // _zyba, voki, кни_, äytt,
+ {{0x27e04381,0xb5fb4049,0x6f1bc0c2,0x7d1bcc02}}, // zdin_, _fhág, rzuc, rzus,
+ {{0x2fddc286,0xd131a13a,0x69c44c03,0x2247a1f6}}, // _tawg_, _بما_, yfie, monk_,
+ {{0x244e8066,0xe366060f,0x6d5d803e,0xed598012}}, // tými_, икни, _úsas, _kzž_,
+ {{0x68e44004,0x661d000e,0x3eaceb83,0x7d012156}}, // xvid, ërka, ardt_, älsn,
+ {{0x2fdf8286,0x6b9bcc04,0x1dd9e010,0xa136c00b}}, // _laug_, laug, _बघित, _דאנק_,
+ {{0x699641e1,0x9f5ea04a,0x249fc765,0xe9df41de}}, // ирах, _poté_, lsum_, _baú_,
+ {{0xe8e06016,0xb38640ba,0x3b864099,0xcb12a00b}}, // _hiệp_, рлал, рлаг, קלן_,
+ {{0x7bde2c05,0x62852c06,0x27e00569,0x14266c07}}, // [700] _tapu, _etho, _iain_, адам,
+ {{0x442a2c08,0xa3e5a46f,0xc7b8e098,0x6016e2e5}}, // qib_, _बोल_, biđu_, _zâmb,
+ {{0x2247a0d1,0x799c2c09,0xf1bf40e6,0x3eadcc0a}}, // donk_, marw, _imán_, hret_,
+ {{0xdce1e6df,0x6603e0bb,0x3eadc4b5,0xb5fb4c0b}}, // nalı, _lonk, kret_, _diás,
+ {{0x80c4007c,0x245c8066,0xb5fb4019,0xdce0c19f}}, // শিক্, jíme_, _viát, yamı,
+ {{0x799c2068,0x442b0c0c,0x30a6abcb,0x02a6a2d8}}, // narw, zic_, арив, арим,
+ {{0x9999003a,0x82a3612a,0xdce0c3ed,0xdce1e0b0}}, // losť_, _марж, vamı, kalı,
+ {{0x57f4cc0d,0x6015c326,0x3f804c0e,0x28a7a260}}, // спит, _rámc, nciu_, _गिरि,
+ {{0x799c2c0f,0x6d59c0c2,0xdefacc10,0x3e7c2c11}}, // karw, _czwa, тый_, _lít_,
+ {{0x4427ec12,0x46c72c13,0x9e06ec14,0xbea6a0ff}}, // _cmn_, _लहलह, рчил, _важк,
+ {{0x397ba087,0x799c2068,0xbb46a1e1,0x78ad4c15}}, // _סטאנ, darw, _тежк, zrav,
+ {{0x2ca044ef,0x6b9ae5df,0xb8cee7f4,0x2d8040c2}}, // ksid_, satg, _ओट_, jcie_,
+ {{0x61e281d1,0x60102262,0x787ee018,0x628aa363}}, // idol, _täml, _dīva, lpfo,
+ {{0x9999006f,0x3f9cac16,0x2486c006,0xbb84e13a}}, // dosť_, navu_, _itom_, _الغي,
+ {{0x64472c17,0x443a2037,0xe45300b7,0x66160c18}}, // roji, _djp_, _وضع_, _flyk,
+ {{0xdce1e19f,0x9dab014a,0x64472c19,0xf1b9c143}}, // calı, _ръка_, soji, bišu_,
+ {{0x2d9cac1a,0x7875a03b,0xcf9b601b,0x799d00b1}}, // [710] kave_, _išve, вје_, masw,
+ {{0x80dae20b,0x27e00c1b,0x44290048,0x2ca76333}}, // _बनाइ, _zain_, _hma_, ándo_,
+ {{0xdc3c2076,0x22524c1c,0x7bd52c1d,0x78ad459f}}, // _בעזר, blyk_, mezu, srav,
+ {{0x1b05807c,0x3f804197,0x2485a63f,0x20048c1e}}, // _শেষে_, cciu_, _rtlm_, _comi_,
+ {{0x7648ec1f,0x2ca04c20,0x3204839c,0xb9c4813a}}, // kody, csid_, _domy_, تقني,
+ {{0x7c2b8c21,0x600fa271,0x7ae98c22,0x1404004b}}, // sigr, _tømm, _vret, _लव्ह_,
+ {{0x68e98c23,0x60c28af8,0x2a786c24,0x2d816090}}, // _wred, atom, _gurb_, mche_,
+ {{0x2d986088,0x6603ec25,0x29382095,0x442cec26}}, // _zere_, _sonk, יאון_, eid_,
+ {{0x799c2068,0x6f0d4c27,0x2d986121,0x6603ec28}}, // yarw, nyac, _yere_, _ponk,
+ {{0x7c2d4c29,0xf8ae8050,0x2d986057,0x245c806f}}, // hiar, _سکه_, _xere_, ríme_,
+ {{0x2cadc23d,0x26c12022,0x59a74010,0x245c8326}}, // sred_, _nqho_, गणार, síme_,
+ {{0x799c2068,0xe9df4067,0x2d9d8c2a,0x2ca04025}}, // warw, _thúy_, nawe_, ysid_,
+ {{0xdce3ac2b,0x7bdac1a9,0x3954e03a,0x5285c13a}}, // manı, _תקנו, _časť_, _التك,
+ {{0x27e00c2c,0x61e1ac2d,0xb7bdc12a,0x2005ac2e}}, // _wain_, _jall, diţi, _joli_,
+ {{0x81cd007c,0x639ae053,0xdcebc1f6,0x61e28c2f}}, // রয়_, _עסקנ, _begħ, ydol,
+ {{0x38a3e03c,0x661603d1,0xf2d2e00b,0x7c3b8c30}}, // mır_, _ulyk, _זעה_, _njur,
+ {{0x7d0d40e0,0x6e364a0e,0x2bbde029,0x7ccaa03c}}, // [720] gyas, rnyb, ्भपा, _məra,
+ {{0xc301c07c,0x3ea20c31,0x68fb0076,0x80ca41a6}}, // এনপি_, mskt_, _עליה, রিল্,
+ {{0xdce3a3cd,0x7c2d4c32,0x386000c7,0xad5a64e4}}, // kanı, biar, _khir_, урах_,
+ {{0x2d9d84b7,0x44382c33,0x6f0d4071,0x3f99413e}}, // gawe_, lnr_, byac, _gesu_,
+ {{0x9f498069,0xdcfc2041,0x7aedc066,0x7e2aa71b}}, // ðað_, karī, _šatn, ліка_,
+ {{0x60c4400e,0x6e298096,0xb95b41e9,0xb87b41e9}}, // mtim, _emeb, _abìw, _aríw,
+ {{0xdcfae041,0x859b6095,0x7ccaa5fc,0x2486cc34}}, // ratī, _תשלו, _bəra, _stom_,
+ {{0x2d9a2c35,0xf1bf0009,0x5b14c052,0x661bc426}}, // _lepe_, riák_, жмит, ghuk,
+ {{0xc224a0eb,0xdcfae018,0xb5fd8013,0x99804013}}, // _وکتو, patī, _višt, lnių_,
+ {{0x44290c36,0x61e1ac37,0x1dd86033,0x3e41403b}}, // _sma_, _gall, ड़ित, mėte_,
+ {{0x4bfb6087,0xa3c941b0,0x6d5aac38,0x0dcac0ff}}, // אליס, ल्ड_, _uzta, _були_,
+ {{0xaaa9ec39,0x9e66cc3a,0x786b21d7,0x78602156}}, // _किरक, _увед, _iżvi, hövd,
+ {{0x2d994197,0x2a600025,0x7ae20143,0xdb00804a}}, // _rese_, _dhib_, _šotr, _odmí,
+ {{0x442eac3b,0x61e44bfc,0x3959c105,0x65760009}}, // nif_, edil, üsse_, _egyh,
+ {{0x799d0296,0x5333ec3c,0x9f41600e,0x6e2d4c3d}}, // pasw, _нешт, rdhë_, tiab,
+ {{0x2d9eec3e,0x0855412e,0x506744e4,0xb7bdc12a}}, // late_, іваю, стга, tiţi,
+ {{0xb87b0066,0x6e2e2c3f,0x53996052,0x66064c40}}, // [730] zvíj, bibb, ивая_, _dokk,
+ {{0xdce3a6df,0x68ed0c41,0x2d816c42,0x6b9aa013}}, // zanı, _irad, uche_, _netg,
+ {{0x61e44c43,0x66064c44,0x3f8161cd,0xb5fb4c45}}, // bdil, _fokk, rchu_, _chác,
+ {{0x4cbbe00b,0x63a8a0e8,0x224b0030,0x3206c1e7}}, // רמאג, _addn, nock_, _nooy_,
+ {{0xbddb40f9,0x2bc66077,0x7c2ae8ef,0x2d9d8c46}}, // _ajèg, र्हा, _omfr, rawe_,
+ {{0xa3b6cc47,0x853ca03b,0x628d4c48,0x2d8320c2}}, // _जाउ_, _idėj, mpao, ncje_,
+ {{0x28dd437f,0xf991c13a,0x600a406f,0x661d000a}}, // _नैति, وبة_, _týmt, khsk,
+ {{0x99800c49,0x225f807d,0x0609cc4a,0x61e1ac4b}}, // _omiš_, _uhuk_, рник_, _wall,
+ {{0x1dc66c4c,0x27ffcc4d,0x66076022,0xe619ec4e}}, // र्वत, rjun_, _kojk, иди_,
+ {{0xdce3a3df,0x61e1a6f1,0x6b840ab8,0xf625e0ff}}, // sanı, _uall, _sfig, ідно,
+ {{0x6b9b817b,0x78602c4f,0x26dea1e7,0xc17400e8}}, // _keug, növe, _gsto_, _haɗi_,
+ {{0x2006c3be,0x7e7b8c50,0x644b8c51,0x799aa0d1}}, // _gooi_, _kuup, nogi, _zetw,
+ {{0x44382c52,0x41c2c077,0x60c44746,0xa9a5ebbf}}, // tnr_, _वॉटस, xtim, _дилд,
+ {{0x2d81e10a,0x442f8c53,0x66076143,0x78602009}}, // _ühe_, hig_, _nojk, köve,
+ {{0x7d036277,0x6b82810f,0x7aed0c54,0xe0df4031}}, // änsl, ycog, _erat, _asò_,
+ {{0x64470552,0x28dca292,0x44390090,0xdfd24049}}, // čkic, _मनसि, cns_, بير_,
+ {{0x644aac55,0x3207ec56,0x68e9c009,0x628d4c57}}, // [740] yofi, _kony_, nved, gpao,
+ {{0xb05b667c,0xa3e220c5,0xb5fb4119,0x80d700c2}}, // _spär, _नोच_, _phác, यबरे,
+ {{0x62898a49,0xe7d9c028,0x6015c3ab,0xb4c7e10a}}, // _ateo, ढ़ाप, _námo, _उहो_,
+ {{0x6b9aac58,0x46ea8c59,0x6d5e2c5a,0x76a061e9}}, // _setg, иден_, _izpa, _aáyá,
+ {{0x442a6c5b,0x764140e0,0x92e6a07c,0x91a6c0f9}}, // _tmb_, élye, _পথে_, _ajo̟_,
+ {{0x2cb240e2,0xee37a056,0x78a28c5c,0x63a98c5d}}, // mryd_, онч_, rsov, _eden,
+ {{0x6d5c6c5e,0x644b8733,0x44390c5f,0x6aa28c60}}, // _uzra, bogi, yns_, ssof,
+ {{0x2bc66c61,0xddc9812a,0x799c6c62,0x6b844c63}}, // र्रा, _vieţ, _herw, ncig,
+ {{0xc17401df,0xb4c9010a,0x81cfa07c,0xdb076030}}, // _faɗi_, _ईहे_, ষয়_, _adjè,
+ {{0x7e7c6c64,0x2007ec65,0x136aec66,0x68ed000e}}, // _kurp, _coni_, ишни_, _rrad,
+ {{0xb97b61a9,0x25a90057,0x61e2c466,0x7ae9c024}}, // יניי, _pdal_, _taol, avet,
+ {{0xa2cd6c67,0x7c3e2b0c,0x2007e030,0x81c261a6}}, // _सहस्, _ajpr, _eoni_, ্যি_,
+ {{0x7336c0e0,0x2ee00057,0x7ae9c993,0x2eed80ae}}, // _برائ, _csif_, cvet, _xref_,
+ {{0x60c56c68,0x7c2f0c69,0xdfcfe049,0x27e68052}}, // ythm, sicr, _ليه_, hdon_,
+ {{0xdce5603c,0x644b8c6a,0x3a2ca071,0x443ea197}}, // yahı, yogi, _mmdp_, _mjt_,
+ {{0x7afce10a,0xbddb40f9,0x09acc07c,0x7bda20fa}}, // ärtu, _akèr, ক্রা, ôtur,
+ {{0xd37b6c6b,0x61e3e3df,0xb4e54290,0x40352a18}}, // [750] аче_, _yanl, पटे_, _неис,
+ {{0xd25160b7,0x3eb820cb,0x628d4c6c,0x51f865d0}}, // رنا_, burt_, rpao, оную_,
+ {{0x4cd3c07c,0x799c6c6d,0x644b8c6e,0x59c42029}}, // তিযু, _derw, togi, _लॉटर,
+ {{0x69caa09a,0x245c80c4,0x6015c803,0x7bce6035}}, // effe, tíma_, _rámo, _ebbu,
+ {{0x25aa6098,0x7ccc6041,0x387ce143,0x3eb90046}}, // _fdbl_, vērš, _luvr_, nust_,
+ {{0x38694c6f,0x799d4561,0xfc04a0ff,0xab5b0105}}, // lmar_, _hesw, мпіо, rfüh,
+ {{0xab5b0669,0x439589fd,0x5ba78af5,0x442cac70}}, // sfüh, _навс, _мраз, _dmd_,
+ {{0x13e4207c,0x7ae9c00e,0x68e9c018,0x2eed8c71}}, // মায়, tvet, tved, _tref_,
+ {{0x2be0804b,0x6d46e106,0x7bc9cc72,0x3a2d80d5}}, // _नसता, şkal, rfeu, _imep_,
+ {{0x80d22405,0x6015c0e0,0x7bd8a1e1,0x68e9cc73}}, // হিত্, _támo, cevu, rved,
+ {{0x68e9cc74,0x59cb44ba,0xe945c8b5,0x81bd61a6}}, // sved, ाभार, _کرای, _আসা_,
+ {{0xe61f602e,0xb33b45fc,0x7ae9c009,0x799d4c75}}, // _stôl_, _keçd, pvet, _nesw,
+ {{0x3eb90c76,0x644d4108,0xdb1c23f5,0x8d5601ba}}, // gust_, doai, nerö, яточ,
+ {{0xfc3f42a8,0xa06a20fb,0x25a04561,0x2d9dc229}}, // _ají_, сага_, wail_, _hewe_,
+ {{0xe3b2013a,0x799d4c77,0x99fae076,0x64436089}}, // _كرة_, _besw, _לפספ, čnil,
+ {{0xbc764049,0xe73720ba,0xac864c78,0x2618cc79}}, // كهرب, чес_, згал, _léon_,
+ {{0x934341fc,0x6b844054,0x55bb0076,0x2bdca026}}, // [760] енте, rcig, _המפו, _यसला,
+ {{0x224dcc7a,0x2d85ec7b,0xb904455d,0x2d9ce005}}, // hoek_, ncle_, _भई_, _xeve_,
+ {{0x248b405d,0x64414c7c,0xa3c9410a,0xa5bb41e9}}, // _etcm_, élic, ल्ह_, _amóu,
+ {{0x7c2d0c7d,0x69c1a090,0x38694057,0x7bc1ac7e}}, // _xmar, _acle, cmar_, _aclu,
+ {{0x1e83412e,0x66098b83,0x6e3bc143,0x7ded0053}}, // _плям, _doek, dnub, יקאַ,
+ {{0xf8b020e3,0x3fe6e07c,0x69caac7f,0x78a244d2}}, // _जटाय, _পক্ষ, uffe, šovs,
+ {{0x69caa0e2,0x2bc2e8ae,0x877b200b,0x27e68c80}}, // rffe, _शाबा, זאצי, pdon_,
+ {{0xb2268c81,0xbb3b61a9,0xdb1c2c82,0xb5fb0c83}}, // _эмбл, _העלי, berö, rmák,
+ {{0x0ef4e20b,0x8c4381e1,0x0c26ac84,0x764e206f}}, // इम्स_, _чете, _еман, koby,
+ {{0x63a0c069,0x9f516071,0x7c42c143,0x3f868005}}, // samn, _gozó_, _štrč, lcou_,
+ {{0x44312c85,0x61e64c86,0x61e5207f,0x6fc16c87}}, // viz_, _oakl, _rahl, र्टू,
+ {{0x201ee00e,0x2d868030,0xa5bb4071,0x60126069}}, // shti_, ncoe_, _ilóg, _dæmi,
+ {{0x78a60708,0x0cd7407c,0x5e25c0e0,0xfc3f21af}}, // gskv, _সপ্ত, _تعلق, esía_,
+ {{0xb5fb4c88,0x67244c89,0x644e2c8a,0xe8d90125}}, // _chán, dzij, gobi, _uzọ_,
+ {{0xe1ff4119,0x644d4c8b,0x69d980ce,0x764d4c8c}}, // _nhóc_, toai, yewe, toay,
+ {{0x6443606e,0x2d9eac8d,0xc299439e,0x9c7ca066}}, // čnim, _lete_, жках_, _urču,
+ {{0x644e21df,0x61e52c8e,0x25a16c8f,0x660983c5}}, // [770] bobi, _tahl, tahl_, _roek,
+ {{0x661b809a,0x69daec90,0x2ca6808b,0xfc3f203e}}, // _sluk, nete, dsod_, rrís_,
+ {{0x6e3d01dc,0x69d981cf,0xf4852555,0x44204c91}}, // onsb, tewe, _تاخی, bhi_,
+ {{0x7e7d4098,0xf1bf0c92,0x7ae2cc93,0xa3dd0046}}, // _tusp, ngá_, _osot, ड़ि_,
+ {{0x601f00dd,0x3f9ea012,0x661d4156,0x69cb8c94}}, // _këmb, _betu_, _ilsk, rfge,
+ {{0xe3b8a03c,0x69cd41cd,0x7bdaec95,0x6ecaa5fc}}, // _alıb_, lfae, jetu, _təbl,
+ {{0x63a28c96,0x27e6c5c8,0xa4d460ff,0x7ae2cc97}}, // baon, _baon_, _порі, _asot,
+ {{0x63a282d9,0x61f8e061,0x3e642156,0x443f8c98}}, // caon, _anvl, möte_, _uju_,
+ {{0x27e94577,0xf99f4b16,0xc17400e8,0x8ca9aa53}}, // idan_, _blèz_, _yaɗu_, _टिको,
+ {{0xc87f4105,0x248d8c99,0xbebb000e,0x7e640095}}, // _muß_, _item_, tzën, _ship,
+ {{0x6d452022,0xa3c8c029,0x186a00de,0x4d65e0ff}}, // _nxha, _लॉक_, жани_, ьков,
+ {{0xe4d4a0e0,0x2d9ea361,0x443245dd,0xf0926053}}, // _ستمب, _zete_, xiy_, כנט_,
+ {{0x7bdaec9a,0x3ea0ac9b,0xe44eec9c,0xe29705be}}, // betu, ćite_, _аж_, мая_,
+ {{0x7bdaec9d,0xe45f2052,0x60c9cc9e,0x0478e0eb}}, // cetu, stön_, mtem, _کليک_,
+ {{0x4252e0e0,0x200a6c9f,0x764e2ca0,0x248d80ae}}, // _جنور, _robi_, roby, _ltem_,
+ {{0x6d440ca1,0xdb1c2ca2,0xdb1b827f,0x63ad0ca3}}, // _txia, ngré, _acuí, _gdan,
+ {{0xa5bb403e,0xed57401b,0x037a2062,0x6b6720fa}}, // [780] _amós, дот_, _وحدت_, tégé,
+ {{0x63ad0ca4,0x27e7e030,0x8d634052,0x2ca6806e}}, // _zdan, _jann_, твуе, vsod_,
+ {{0xa2a40046,0x9f42c03a,0xd9462874,0x661e217b}}, // _कबड्, ľké_, _цени, _ilpk,
+ {{0x2d9eaca5,0x7bdc2ca6,0x6441a143,0xf77f41ae}}, // _sete_, leru, _ajli, _taça_,
+ {{0x25a0010a,0xd6d08ca7,0x3f8689fe,0x69daeca8}}, // _meil_, اقت_, rcou_, yete,
+ {{0xb21b6065,0x3eaf022e,0xdce8a06f,0x69dae0b8}}, // _stæv, _ægte_, _vedľ, xete,
+ {{0xa6e1e069,0x7bdc20ba,0x291d20e8,0xa3be0ca9}}, // óðle, ieru, _ƙwai_, आला_,
+ {{0xd257c7b9,0x68206156,0x9f45a163,0x27e7e153}}, // мцы_, _döde, _malè_, _aann_,
+ {{0x442eecaa,0x3f9ea012,0x7c228cab,0xd6dac0ff}}, // _pmf_, _tetu_, mhor, іти_,
+ {{0x27e94cac,0xb463aa34,0x6015c13a,0x3958c041}}, // zdan_, _акул, _lámh, ārs_,
+ {{0x7a206cad,0x7aed486f,0x7ccaa497,0x260228c6}}, // _göte, zvat, _mərk, शानी_,
+ {{0x200cacae,0x442fc425,0x27e94caf,0x320ca089}}, // _hodi_, _gmg_, xdan_, _hody_,
+ {{0x63a08106,0x27e94cb0,0xd90fc0e0,0xc8c8c0eb}}, // _memn, vdan_, _نیٹ_, _روغن_,
+ {{0xdb1c6912,0xdce2ccb1,0xb8d70466,0x9f45a2f4}}, // _acrí, _ugođ, _चट_, _balè_,
+ {{0x25a000ff,0x3866c05d,0x200b4011,0x69cbccb2}}, // _feil_, _khor_, _poci_, थ्वी,
+ {{0xa5bb40f9,0xa3cb0180,0x60102105,0x9f45a0f9}}, // _amór, र्श_, _sämt, _dalè_,
+ {{0x80a02010,0x61faa23c,0x046727f3,0x7c228cb3}}, // [790] गीबे, _ontl, _отом, dhor,
+ {{0x27e94cb4,0xa3cb0cb5,0x7865c01f,0x7bdd0cb6}}, // sdan_, र्र_, móve, lesu,
+ {{0x7a3605f9,0x645aecb7,0xdb1bc019,0x61e76cb8}}, // lšta, ilti, rgué, _vajl,
+ {{0xa29460ff,0xdb1c6049,0x387f8cb9,0x7c228147}}, // таці, _gcrí, _puur_, ghor,
+ {{0x7e6d4cba,0x61e9ccbb,0x7ae400ae,0x3e61c0c4}}, // mmap, vdel, _xsit, jóta_,
+ {{0x200cacbc,0xd366e047,0x7e6d4cbd,0x81d3007c}}, // _codi_, _مه_, lmap, _হোম_,
+ {{0xdb03a00e,0x38794143,0x7bdd0469,0x69dbc105}}, // manë, _kisr_, kesu, teue,
+ {{0xee3f0326,0x7a36003b,0x3ebe6277,0xaca38088}}, // jný_, kšta, _åtta_, _arụn,
+ {{0x3ea94cbe,0x973cecbf,0x3dcdc1e2,0x7bc52066}}, // nsat_, vaća, rfew_, _ochu,
+ {{0x63a3acc0,0x63a08cc1,0x200ca1cd,0x7bc40cc2}}, // qann, _zemn, _godi_, _sciu,
+ {{0x62808071,0x69c4a909,0xe9ffc067,0x81bca041}}, // _zumo, _राती, _ngầm_, _slēg,
+ {{0xdb040005,0x660d0037,0x7bc52cc3,0x69dc2cc4}}, // _adiá, _coak, _achu, were,
+ {{0xb5fb42b2,0xd378e29f,0x6281acc5,0x9f9d0069}}, // _chám, veće_, _hulo, ræði_,
+ {{0x201e2cc6,0x7d04804e,0xa3cf6029,0x20190052}}, // óti_, äisi, _वॉल_, aksi_,
+ {{0x9f45acc7,0x7e5601b1,0x63a1a041,0x60102156}}, // _balé_, _птиц, _meln, _säms,
+ {{0xee3f0066,0x09e34cc8,0xd00740de,0x3c3ac041}}, // cný_, лосн, несе_, tīvi_,
+ {{0x5275412a,0xd54740ba,0xb907253a,0x671fc37f}}, // [7a0] туру, епте_, _मै_, यनिक_,
+ {{0x44200223,0x25a5e00c,0x386dc105,0xaf9a204e}}, // _hli_, nall_, hmer_, _этих_,
+ {{0x62808cc9,0x6b964193,0x7c228095,0x320ca153}}, // _sumo, pbyg, thor, _rody_,
+ {{0x5ab7200b,0x78a9ccca,0x1a9ae00b,0x6fc2604b}}, // _אלטע_, ksev, פינע, _लावू,
+ {{0x61e8accb,0x386dcccc,0x25a5eccd,0x69c64944}}, // _padl, dmer_, kall_, _icke,
+ {{0xfc3f0026,0x7d0ee065,0x386dc06f,0x25a5ecce}}, // zní_, øbsk, emer_, jall_,
+ {{0xf1d22893,0x63a324ef,0x63a08ccf,0x50436cd0}}, // द्यन, _õnne, _temn, _серб,
+ {{0xaa0d2026,0x2a66c022,0x61e8a10e,0x81d4207c}}, // _सक्छ_, _qhob_, _wadl, _সোম_,
+ {{0x6281acd1,0x63a1acd2,0x69c0ccd3,0x1c46a242}}, // _eulo, _feln, rgme, _знам,
+ {{0xf3ff4067,0x69c283c5,0xb05b29f6,0x3a200cd4}}, // _nhãn_, lgoe, ltäv, _alip_,
+ {{0x6281acd5,0xa3dd04ef,0x7bdd0cd6,0x69c64944}}, // _gulo, ड़ल_, tesu, _ocke,
+ {{0x78bd02df,0x63a56cd7,0x69c28cd8,0x7bc1e022}}, // tusv, zahn, ngoe, aglu,
+ {{0xee3f6026,0x6281a534,0xf1d00119,0x44200153}}, // _svým_, _zulo, _tạng_, _dli_,
+ {{0x9325c009,0x7e7d8cd9,0xdb02c1ae,0x7a360013}}, // _جرمن, _èspe, _adoç, ršta,
+ {{0x6281a0d5,0x7bdd02df,0x22aca04a,0x656400bb}}, // _xulo, pesu, něk_, _ezih,
+ {{0x3a200cda,0x443160ce,0xb4e12738,0x2ca94cdb}}, // _glip_, _smz_, _धनी_, tsad_,
+ {{0xe803a026,0xe8df8125,0x645560ae,0xdb01e3ab}}, // [7b0] लामा_, _kwụa_, _pkzi, valé,
+ {{0x765d0cdc,0x4ea76cdd,0xda7b2cde,0x3ea94cdf}}, // llsy, ерда, _ням_, rsat_,
+ {{0x2a69005f,0x44212098,0xdd91813a,0x6ad98010}}, // _khab_, _ilh_, لوا_, णब्र,
+ {{0x9f5fc069,0x6015c005,0xa3bb43e6,0x386900a2}}, // kkuð_, _támi, _घाट_, _jhar_,
+ {{0x1dd305c5,0x66e58407,0x61ed4ce0,0x7dd3a121}}, // ध्यत, тола, mdal, yısı,
+ {{0x21294ce1,0xa136400b,0xa3d2e010,0x61ed4ce2}}, // mzah_, _יארק_, ह्म_, ldal,
+ {{0x92e8a049,0x24590066,0x63a1ab8e,0x38690534}}, // فريق_, lému_, _veln, _ohar_,
+ {{0x7bc1e022,0x7ccaa5fc,0xdb03a530,0xcec188fd}}, // wglu, _xəri, nané, _pięć_,
+ {{0x7989cce3,0x9f47e00e,0x63a7200a,0x21294037}}, // rcew, _tanë_, kajn, nzah_,
+ {{0x61ed4052,0x2129405d,0x69c1ece4,0x6b89c024}}, // hdal, izah_, ugle, sceg,
+ {{0xf745e5a8,0x7dd3a20f,0x60cd41dd,0x26388098}}, // тено, rısı, htam, mčor_,
+ {{0x44332ce5,0x9a84a1fc,0x22404746,0x2374a0a4}}, // _amx_, _суфл, xnik_, _صالح,
+ {{0xdb064089,0x9f47ec1e,0x60cd4ce6,0x38690ce7}}, // _odká, _mané_, jtam, _dhar_,
+ {{0x61fd44f0,0x7bc3ace8,0x3ebee13e,0x3cfe6057}}, // _ansl, ngnu, butt_, _átvg_,
+ {{0xf3ff401f,0x60102097,0x660f4149,0xf09f41e9}}, // _chão_, _dämp, _bock, _awà_,
+ {{0x26cdc504,0x200fc0e0,0x44320037,0x7e7b8007}}, // oteo_, _jogi_, _umy_, _niup,
+ {{0x6e244ce9,0x63a3ecea,0xa0a64013,0x25a3604d}}, // [7c0] rhib, _kenn, тазд, _bejl_,
+ {{0xdb1d4057,0x61eae054,0x61ed4ceb,0x61ebc68f}}, // _absó, _xafl, adal, _lagl,
+ {{0x7bde6cec,0x6ecaa03c,0x62840ced,0x61fe2cee}}, // repu, _təbi, _huio, _inpl,
+ {{0xdb1c2966,0x6441e105,0x22aca026,0x7e7b812a}}, // ngrí, hnli, věk_, _ciup,
+ {{0x25a36a2f,0x27e04cef,0x6272e064,0x6283ecf0}}, // _fejl_, mein_, _głow, _luno,
+ {{0xdb04001b,0x27edccf1,0x63a2ccf2,0x7ae4449e}}, // _ediç, eden_, _seon, mwit,
+ {{0x29182cf3,0x64a360fb,0x7a3ec041,0x2d800cf4}}, // nyra_, _васа, līti, _ogie_,
+ {{0x3ce6ccf5,0x7e7b8114,0x6cedc79e,0x200fc071}}, // _psov_, _giup, जिंग_, _cogi_,
+ {{0x63a3ecf6,0xdb20ecf7,0x6283e509,0x62836066}}, // _benn, _àtún, _auno, ínov,
+ {{0xdb008cf8,0xae056893,0x49baa01c,0x27e04cf9}}, // _remè, रायन_, _زائد_, hein_,
+ {{0x33294227,0x2a69005f,0x21bb6095,0x539ba3c8}}, // zzax_, _phab_, וזיא, _חיבו,
+ {{0xd6d2c4aa,0xe7b48046,0x3ebfccfa,0x3ce6c286}}, // _نقص_, ुलिप, buut_, _tsov_,
+ {{0x26da207d,0xe8056026,0x2246c0c7,0x23c9a4ba}}, // _appo_, रामा_, _ijok_, _रामद,
+ {{0x68f600fd,0x3ebee0cb,0x7a3ec018,0x4425ecfb}}, // _dryd, putt_, dīti, chl_,
+ {{0x55582691,0x03c74362,0xc27be087,0x9f412cfc}}, // варя_, ксам, _קרוי, _bahá_,
+ {{0x6b8081e7,0xd838ecfd,0x27e04cfe,0xe3c9c057}}, // _ngmg, lič_, gein_, mañá_,
+ {{0x44212345,0x32094037,0x245c8066,0x394980c2}}, // [7d0] _tlh_, rjay_, cími_, żasz_,
+ {{0x9f448355,0x63a40108,0x2ee90cff,0x61ed488b}}, // _camí_, _zein, _isaf_, sdal,
+ {{0x9f436d00,0x7e7b8098,0x2d8123c9,0x6d5b817b}}, // _tají_, _piup, _ighe_, _syua,
+ {{0xbde5e0f9,0x644280fa,0x28ac4d01,0x3a2681e7}}, // _abẹ̀, gnoi, _चिचि, ghop_,
+ {{0x6d4ec105,0x61ebc1cd,0x7e7d4d02,0xd838e07f}}, // ßbal, _ragl, _iisp, kič_,
+ {{0xe8d90125,0x25a94054,0x64552d03,0x251c0087}}, // _ayị_, laal_, nozi, וודא,
+ {{0x200fc561,0x78ad4cde,0x4993c050,0x442681fb}}, // _pogi_, lsav, لیار, bho_,
+ {{0x63ad8069,0x26cdcd04,0x317c2095,0x32cbc0ff}}, // ðand, tteo_, ורומ, tøy_,
+ {{0xd378e29f,0x34b2c13a,0x7d04804e,0xa7fce121}}, // veća_, مميز, äist, klıd,
+ {{0xf993a25b,0x661bcd05,0x27edcd06,0x63a8ed07}}, // _ضبط_, skuk, sden_, gadn,
+ {{0xe807238e,0x7c22c8cd,0x6d5d4d08,0x25a9421e}}, // शाना_, _flor, _lysa, kaal_,
+ {{0x6e22cd09,0x7e7d4d0a,0xaca34125,0x2604cd0b}}, // _glob, _nisp, _akọc, वाणी_,
+ {{0x6b9bcd0c,0xdcee2315,0x7643ad0d,0x63a3ed0e}}, // mbug, vabı, onny, _tenn,
+ {{0x395e0041,0x34b2c050,0x59c60010,0x60db8d0f}}, // āts_, _اموز, _वावर, _mpum,
+ {{0x6d5d424f,0x70d26033,0x2ee90079,0x28c6a8a2}}, // _aysa, _सहेल, _esaf_, रूपि,
+ {{0x61ed0ad5,0x69c98286,0x69c56d10,0x6e92e13a}}, // _gaal, _ncee, eghe, _اليا,
+ {{0xdb06059b,0x7ae98079,0x2604cd11,0xaca44125}}, // [7e0] kaké, _nset, वाती_, _otụt,
+ {{0x26c20166,0x7ae44d12,0x7e7d4d13,0xaca44079}}, // muko_, rwit, _eisp, _ntụt,
+ {{0x68e98d14,0xe2976d15,0x60c1ed16,0x7d09000e}}, // _ased, гах_, nulm, _tërë,
+ {{0xc178203b,0x24976050,0x4426804a,0x225fcd17}}, // žės_, انند_, uho_, jluk_,
+ {{0xdb00cd18,0x3e4cc04a,0x26c20d19,0x69c600cb}}, // tamí, něte_, nuko_, igke,
+ {{0x25a5ad1a,0x6459c0b1,0x60c1ed1b,0x628641e7}}, // _cell_, _akwi, kulm, _iuko,
+ {{0x26c2049e,0x7c22cd1c,0x83fdc0e0,0x21676197}}, // huko_, _plor, klőd, _бири_,
+ {{0x6d5e2d1d,0x3a3a6851,0xb5fb413a,0x4422c0e0}}, // _mypa, lipp_, _phái, ók_,
+ {{0x57468423,0x81e7607c,0x610ae03c,0x64552c55}}, // _снаб, ভার_, məli, yozi,
+ {{0x6459cd1e,0xf1c6e026,0x26c20d1f,0x2000012a}}, // _ekwi, _část_, duko_, _anii_,
+ {{0xe457600b,0x32576095,0xb05b2156,0x7c240090}}, // רייט_, רסים_, ktär, _blir,
+ {{0x64a6ccde,0x443a6d20,0x44446d21,0x61e28d22}}, // _бада, hip_, hn_, neol,
+ {{0x68e9805d,0x25a94d23,0xddc9c106,0x64444401}}, // _xsed, vaal_, lleş, dnii,
+ {{0x8c43ed24,0x61ed0d25,0x7e7d40d1,0x3e414013}}, // _лече, _waal, _sisp, mėti_,
+ {{0xdddbc602,0x443a6d26,0xddc9c0b0,0x3e414013}}, // nmuş, dip_, nleş, lėti_,
+ {{0x201201df,0x43954099,0x60c28d27,0x26c20d28}}, // _goyi_, раас, kuom, buko_,
+ {{0x44446d29,0x3e41403b,0x76552064,0x6b82c167}}, // [7f0] fn_, nėti_, pozy, _mgog,
+ {{0x25a94d2a,0x8574471b,0x47352d2b,0x6aad4d2c}}, // saal_, слух, _внос, rsaf,
+ {{0x7e7d4d2d,0xddc8a143,0x60db865a,0xe803a567}}, // _tisp, _bidž, _spum, लासा_,
+ {{0x5fc76010,0x7982cd2e,0x61e28d2f,0xceb3e0be}}, // _लावल, _ngow, geol, מיע_,
+ {{0x44294d30,0x25a5a507,0xb33b403c,0x60c1ed31}}, // mha_, _pell_, _keçm, zulm,
+ {{0x4424803c,0x443b4d32,0x6b82cd33,0x48b5a2a3}}, // _elm_, liq_, _agog, ащит,
+ {{0x25a5ad34,0xe8080026,0x442940a2,0x63a9cd35}}, // _vell_, षामा_, oha_, taen,
+ {{0xc888a13a,0x26c20d36,0xa802c315,0x0d85c662}}, // دخول_, yuko_, şınm, шлин,
+ {{0xd347c0eb,0xf1bf0049,0x7ae98d37,0x672d4d38}}, // دیشه_, thán_, _uset, nzaj,
+ {{0x225fcd39,0x391480ba,0x7c360232,0x8539e0a2}}, // sluk_, _умур, _smyr, _haɗɗ,
+ {{0x2cadc095,0x4425ad3a,0x9f5e6067,0x9f42c06f}}, // ssed_, _ill_, _ôtô_, ľká_,
+ {{0x44294d3b,0x6459c00f,0x63a76d3c,0x25a0e143}}, // jha_, _ukwi, _lejn, _đile_,
+ {{0xd90fe043,0xde05e0ff,0x7afb4cb1,0x59daa029}}, // تیں_, _впли, _šutn, _मॉडर,
+ {{0x387f8d3d,0x752d4098,0x6da32d3e,0x99832013}}, // _liur_, dzaz, жира, dijų_,
+ {{0x6286c05c,0x63a64d3f,0x443b4d40,0x61ee6d41}}, // íkov, _pekn, fiq_, _rabl,
+ {{0x6d5e2032,0x2249000e,0x26c20167,0x6abce0c4}}, // _vypa, _gjak_, puko_, árfe,
+
+ {{0x66e60d42,0x63a76d43,0x81c1607c,0xb0690343}}, // [800] _топа, _bejn, ্জন_, _حصول_,
+ {{0x61ee6d44,0x386d8037,0x3c3f4052,0x3ead017a}}, // _qabl, _aher_, tävä_, ġett_,
+ {{0x38604017,0x443b45fc,0x78a26098,0xe6d4a3fa}}, // plir_, biq_, ćovo, _दहेज,
+ {{0xfbd2237f,0xdb03ed45,0xeaa7c049,0x44446d46}}, // द्रम, _venè, _وع_, sn_,
+ {{0x64460d47,0x8539e0a2,0xe809c870,0x44248012}}, // inki, _daɗɗ, वाना_, _vlm_,
+ {{0x61e28d48,0xdca34d49,0x60c2803b,0x4425a071}}, // seol, _фари, ruom, _cll_,
+ {{0x387ead4a,0xb87b61e9,0x68eda0c2,0xb7db63c8}}, // _vitr_, _asíw, ładn, _שקלי,
+ {{0xd90dc050,0x63aaa069,0xa3de65cb,0xb05b2d4b}}, // کیه_, safn, ढ़क_, präg,
+ {{0x44386d4c,0x390ac0fb,0xa3d6291c,0xd846ed4d}}, // _kmr_, тэйн_, सभा_, ихоп,
+ {{0x60de20ff,0x3eb8c06e,0x3e414013,0x24800090}}, // _oppm, črt_, rėti_, _aiim_,
+ {{0x201ee03b,0xb87b606f,0x7e6d0d4e,0x443cad4f}}, // rkti_, _príz, _phap, miv_,
+ {{0xd25800ff,0x7c3bc12a,0x2bc2e437,0xdb5ac052}}, // иця_, ciur, _शाका, _июл_,
+ {{0x3ea9a4ad,0x81ac607c,0x4aa6e35b,0x5e94c13a}}, // ćate_, _গান_, _कौरव, _التط,
+ {{0x20c96010,0x7c264614,0x7c3c2108,0x28b740c2}}, // ांमध, _alkr, girr, ेंसि,
+ {{0x44204d50,0x2ba74244,0x5fd78914,0x443b4d51}}, // fki_, ијав, ण्डल, tiq_,
+ {{0x60dd40c7,0x27e32d52,0x2249029c,0xe1ff4067}}, // _ppsm, sejn_, _ujak_, _khói_,
+ {{0xdb03a03a,0xa3cc06c6,0xf773c00b,0x6e3d0d53}}, // [810] vaní, _लात_, ַקע_, misb,
+ {{0xdb03e005,0x7afaa194,0x3ec3e04a,0x6015cd54}}, // _sené, _ortt, _ústí_, _lámp,
+ {{0x6288a0e6,0xdb01a065,0x672d40c2,0xfb8741ba}}, // _nudo, _udlø, szaj, _выгн,
+ {{0x05962050,0x1dc9a010,0x442940f7,0x7e628d55}}, // _پایگ, _राहत, qha_, glop,
+ {{0xc29386b0,0x7c3bc017,0x68faad56,0x6eaa008d}}, // زیاب, viur, _artd, जीपु,
+ {{0x1ee760e0,0xe3b1a050,0x99824157,0x7c3d00ae}}, // _پوری_, سرت_, _ankň_, hisr,
+ {{0x81e9a07c,0x9f47e04a,0x4439470b,0x3d20e028}}, // যাল_, _paní_, _kms_, मनों_,
+ {{0x13aa854f,0x26c4cd57,0x1dc9a893,0x61e56d58}}, // _منفی_, fumo_, _रावत, lehl,
+ {{0x645d4037,0x6447212d,0x645980c2,0x34948d59}}, // _mksi, knji, mowi, _матр,
+ {{0x9f4c2326,0x6602cd5a,0x69cd0019,0x69c8e342}}, // ždé_, _enok, _acae, ggde,
+ {{0x44204d5b,0x7ccaa03c,0xa5bb40f9,0x68fb8066}}, // yki_, _fərq, _alóm, _hrud,
+ {{0x69d9c00f,0x24890153,0x765980c2,0x7aed0227}}, // _ubwe, _buam_, nowy, _bsat,
+ {{0xf8bf0004,0x3075e1c7,0xd6cee050,0x7c3c2d5c}}, // gré_, _лукс, وقی_, tirr,
+ {{0x69dde009,0xe3b8ed5d,0x656bc227,0x7afb8d5e}}, // őseb, lkın_, _izgh, _mrut,
+ {{0xab272d5f,0x68e9c0e2,0x25a90057,0x765980c2}}, // бора_, lwed, _feal_, kowy,
+ {{0x60c4404e,0x24890022,0x68fb8d60,0x25a9028f}}, // tuim, _fuam_, _orud, _geal_,
+ {{0x973ce29f,0x61e56105,0x76598064,0xf8bf0004}}, // [820] vaći, fehl, dowy, cré_,
+ {{0xb4e40028,0x3b0a660c,0x3dc9408b,0x3a204d61}}, // नबी_, лезо_, agaw_, skip_,
+ {{0x9f4eed62,0x68e9c105,0x60c6048d,0xe804804b}}, // _cafè_, hwed, lukm, ळाला_,
+ {{0x443a208b,0xb346e1ae,0x98a0400a,0x24890223}}, // _hmp_, leçõ, jzić_, _yuam_,
+ {{0x7a23410a,0xdb01ad63,0xdb008d64,0x24890022}}, // _mõtl, _delí, _temí, _xuam_,
+ {{0x765c6d65,0x7e628d66,0x6288ad67,0xe4a6ad68}}, // _ukry, slop, _pudo, брио,
+ {{0x6f1d00c2,0x645e2088,0x7e6288da,0xd25ac0de}}, // zysc, _mkpi, plop, лци_,
+ {{0x3ebfc2a4,0xf2e88050,0x644722e2,0x26c4cd69}}, // frut_, _نکته_, znji, tumo_,
+ {{0x25a90d6a,0xf98fc13a,0x270ee03c,0x9f4ee0d1}}, // _real_, ربي_, məni_, _zafè_,
+ {{0x443a2037,0x6016e12a,0x63ad4035,0xba23aae2}}, // _nmp_, _câmp, zaan, _едук,
+ {{0x629c2d6b,0x25a9010a,0xeb97a076,0x64472098}}, // jpro, _peal_, _הדבר_, vnji,
+ {{0x2d912064,0xf8bf0d6c,0xc0cb0d6d,0x4427e071}}, // wcze_, tré_, луге_, _eln_,
+ {{0x629c2d53,0xe043cd6e,0xa805c019,0xdcfc613e}}, // epro, _энци, señá, _ferħ,
+ {{0x7a260d6f,0x629c29cd,0x69caa59c,0xdb03ad70}}, // _vóta, fpro, ngfe, laná,
+ {{0x644fe004,0xdc3be00b,0x3a22005d,0x61e60d71}}, // écie, _געגר, bkkp_, bekl,
+ {{0x7e644d72,0x68ed035f,0x9f5161de,0x44f8a119}}, // glip, _tsad, _fazê_, _mĩ_,
+ {{0x52156d73,0x4815687c,0xe8056010,0x764d0296}}, // [830] одат, омас, राला_, _ijay,
+ {{0x7afc6037,0x6448ed74,0xb4e94d75,0xb5fb4d76}}, // _brrt, nndi, _मनी_, _chár,
+ {{0x44290aaa,0x7c2b8227,0xdb160030,0x6281a208}}, // _kla_, thgr, _adyè, _rilo,
+ {{0xa5f948c0,0xa2c0e37f,0x60c56058,0x7afc6037}}, // _тему_, _विन्, ruhm, _drrt,
+ {{0x63a98d77,0x6e954093,0xdcf5219f,0x9f47e2a8}}, // _veen, _фигу, mazı, _maná_,
+ {{0xe894454a,0x765980c2,0xd5bf4018,0x2c4cc04a}}, // _харь, sowy, žādu_, věda_,
+ {{0xec6b8471,0x442200ff,0x69c9c171,0xe299a03b}}, // _брак_, ykk_, tgee, шай_,
+ {{0x6e2d4d78,0xbb48613a,0x68e9c105,0x7ae9cd79}}, // nhab, _الفن_, twed, twet,
+ {{0x27e5e097,0x98a04927,0x786fe042,0x6e298d7a}}, // teln_, rzić_, høve, _ileb,
+ {{0xfe42e0ff,0x765aed7b,0x2ca9c17a,0x5ba9e63e}}, // дньо, goty, _ħadd_, шком_,
+ {{0x67d5c4a4,0xfc3f005c,0xf988a049,0x6e2d4d7c}}, // _можу, fií_, _انمي_, khab,
+ {{0x26c7ad7d,0x443eed7e,0x27e5ed7f,0xfc3f0066}}, // muno_, ait_, seln_, gií_,
+ {{0xb5fb00b5,0x9f51600e,0x26c5e11d,0x764d0d80}}, // plán, _bazë_, sulo_, _djay,
+ {{0x7bcf4d81,0x2249c0e4,0x4adda578,0x27f20054}}, // _accu, čaku_, _महाव, _wayn_,
+ {{0x186a0d82,0x6495a1f6,0xb346e1de,0x25eee04b}}, // _каки_, _fġie, reçõ, _असती_,
+ {{0x6283ed83,0xb4664a0e,0x628bcd84,0xccf2a095}}, // _hino, ікал, _kugo, וכל_,
+ {{0x25a04d85,0x27e7a0d1,0x94056029,0xd7c9a578}}, // [840] rbil_, kenn_, रांच_, _रांच,
+ {{0x26c04d86,0xa3dc8437,0x55e364c2,0x6283e04a}}, // rrio_, त्न_, моуб, _jino,
+ {{0x765c2d87,0xae03ad88,0x78a76022,0xd46a44a7}}, // mory, लाईन_, _cwjv, _вине_,
+ {{0x7c2d4d89,0x2005ad8a,0x26c7a65f,0x7c3e6d8b}}, // char, _anli_, duno_, sipr,
+ {{0xdd94a0fb,0x27e68d8c,0x7649cd8d,0x6282c3c5}}, // палы, reon_, dney, _rioo,
+ {{0x27e7a090,0x236d8143,0x27e68d8e,0xdb044057}}, // genn_, _dzej_, seon_, caiá,
+ {{0x9f4cad8f,0x69caa149,0xe5c6cb2d,0xddcd47a0}}, // _andò_, rgfe, оспо, lmaš,
+ {{0x2900e0db,0x765c2d90,0x6283e04e,0x628bcd91}}, // _čia_, hory, _aino, _bugo,
+ {{0x27e7a157,0x6faa204b,0x212040c7,0x629e6052}}, // benn_, करां, nyih_, lppo,
+ {{0x6448e39a,0x68ed8d92,0x443ee153,0x6e2d41df}}, // undi, çade, uit_, zhab,
+ {{0x9354c043,0x442dcd93,0x59cf6447,0x1958e3fc}}, // _کراچ, bhe_, स्टर, саны_,
+ {{0x0dc7c73f,0x95c7c52f,0x261c42a8,0xe3b2c062}}, // _души_, _душа_, _míos_, _آرا_,
+ {{0x249a207d,0x81b6e07c,0x51840d94,0x4424c037}}, // _atpm_, ঙ্গ_, дура, mkm_,
+ {{0x442a65f2,0xc05b214f,0xddcd4d95,0x09b4e1a6}}, // _nlb_, рім_, dmaš, জ্জা,
+ {{0x6e2d4d96,0x973ca40c,0x539be1a9,0xb5fb0009}}, // thab, _beći, _דיוו, llám,
+ {{0x442a6d97,0xe8d90291,0xb5fb4066,0x682b203c}}, // _alb_, _agụ_, _cháp, _büdc,
+ {{0xfbd32d98,0x26022a53,0x8d872d99,0x6b8de5fc}}, // [850] _तापम, शाखी_, _мунд, _şagi,
+ {{0x442dc00e,0x442a6016,0xf3676d9a,0xad9b41ae}}, // zhe_, _clb_, отен, _inúm,
+ {{0x60184005,0x161f079e,0xdb03a5fc,0x83988125}}, // _tímp, _पवार_, manç, _ịhụg,
+ {{0x155a2095,0x7afd44f3,0x68eda0c2,0x61e8e0ae}}, // _לכתב, _trst, ładk, hedl,
+ {{0x06fb08c6,0x62852d9b,0xdb008057,0x1b1d007c}}, // एटिव_, _kiho, _pemá, _পেতে_,
+ {{0xa3e20216,0x7e79600b,0x26c7a1cd,0x6618e20d}}, // _नॉन_, _לאָז, tuno_, _novk,
+ {{0x7aed4d9c,0x20194d9d,0x6283ed9e,0x44ed4c49}}, // lwat, _kosi_, _rino, _hž_,
+ {{0x63ad0d9f,0x7c298265,0xe6c4c0a9,0x7bcd4da0}}, // _nean, _uler, _žešć, ngau,
+ {{0xd706c04e,0x443ce071,0xf77f01ae,0x442ea098}}, // зные_, _bmv_, deço_, bhf_,
+ {{0xdb008065,0x80e1207c,0x63abc1b9,0x629b8561}}, // _bemæ, _পছন্, _wegn, _ituo,
+ {{0x3ea90da1,0x2ca90da2,0x443fcda3,0xab65e936}}, // _awat_, _awad_, piu_, явол,
+ {{0x7c2dc05c,0x3ea900d1,0xfaff40dd,0xdee60da4}}, // ďars, _bwat_, _orë_, поми,
+ {{0x62852da5,0x61e9cda6,0x644b8da7,0x2806e04a}}, // _biho, meel, ingi, ávní_,
+ {{0xeb972a92,0x6a65cda8,0x317ac095,0x5edae07c}}, // зир_, dófi, _פרסמ, _বছরে,
+ {{0x0e6a413a,0xa3bc8da9,0x20194daa,0x09b001a6}}, // مصري_, _आयल_, _bosi_, _কাঠা,
+ {{0x61e9cdab,0x3ebf064e,0x60d6404e,0x69dbc0ca}}, // neel, šuta_, ttym, nfue,
+ {{0x6e244dac,0x6adbedad,0xa2c34029,0x9f4ca066}}, // [860] rkib, _बहुर, िंग्, _radí_,
+ {{0x261c4dae,0xddcd46a6,0x4efb0053,0x20186025}}, // _píos_, rmaš, _פליג, _qori_,
+ {{0x141b20be,0xa3aecdaf,0xdcf6420f,0x1ee860eb}}, // _הויב, करम_, rayı, توای_,
+ {{0x98a08db0,0x644b8cc5,0x26ca2d87,0x2242014f}}, // šič_, gngi, mubo_, nikk_,
+ {{0x61f60db1,0x261c413a,0x442a608b,0xa5bb41e9}}, // _mayl, _fíor_, _tlb_, _alów,
+ {{0xa8a6aa9d,0x7ae40d84,0xfec3c07c,0x30a6a60f}}, // прик, _mpit, ্বাধ, прив,
+ {{0x6440cdb2,0xb8668db3,0xe6a90982,0x3f8a6291}}, // timi, _فارو, _कब्ज, _igbu_,
+ {{0xa3d9edb4,0x61f60db5,0x289b20be,0x81e9a07c}}, // ड्स_, _nayl, זיקא, যাগ_,
+ {{0x44268db6,0xb8d3a960,0x6aa980e2,0x6618e022}}, // mko_, _जौ_, _gwef, _povk,
+ {{0x765e6db7,0x76b8e662,0x248ca17b,0x64428db8}}, // nopy, олор_, _tudm_, lioi,
+ {{0x29000db9,0x69dc2dba,0x69dae9cd,0xdb0f0359}}, // _aria_, dfre, rfte, macé,
+ {{0xdb03e1ae,0x62852dbb,0xae0284f6,0x29000030}}, // _senã, _siho, _रोमन_, _bria_,
+ {{0x62852dbc,0xe1ff0dbd,0x81c8207c,0x7641edbe}}, // _piho, blón_, োজন_, bily,
+ {{0x442ca0c7,0x27e942f8,0xa2c48a53,0xcf2ec20f}}, // _jld_, wean_, _रिन्, ığım_,
+ {{0x27e94dbf,0x63ad0dc0,0x78a924ad,0x5fc60010}}, // tean_, _tean, ćevo, _वाचल,
+ {{0x7a2a406f,0x3e720066,0x34dc8914,0x6a65cdc1}}, // _pýta, táte_, _यहूद, tófi,
+ {{0x20090dc2,0xd7670043,0xc1bc0095,0x68302277}}, // [870] _inai_, _ڈاکٹ, _המחש, _bädd,
+ {{0x26cb0dc3,0x7ed40049,0x9c13c079,0x6d482049}}, // muco_, _أزيا, _tọgh, údac,
+ {{0x7afd0042,0x2019404e,0x60c446ac,0x68ed4dc4}}, // lvst, _tosi_, arim, swad,
+ {{0x7a2fa0ff,0xc95221a9,0x8d74633e,0x05746938}}, // _møte, _ימי_, رالا, رالد,
+ {{0x60c44dc5,0x443ea5fc,0xdb0ae057,0x6441edc6}}, // crim, _bmt_, _defé, yili,
+ {{0x7e62cdc7,0x628642a9,0x628f841a,0x443eadc8}}, // _akop, _ziko, _écom, _cmt_,
+ {{0x44268dc9,0x9f45e0fa,0xfbbf0567,0xf0f9a5cb}}, // bko_, pelé_, ्लाम, ्मेद_,
+ {{0x64428dca,0x9f47a06f,0xdb076026,0x63ae616d}}, // cioi, nené_, _její, _zebn,
+ {{0x7641edcb,0x61f606fa,0x9f5ea0d5,0x69dc20fd}}, // tily, _rayl, _entà_, yfre,
+ {{0x61e9cdcc,0x6619c037,0x4b25e0ff,0x68302052}}, // seel, _wowk, дмов, _käde,
+ {{0x442d8dcd,0x7641edce,0xa25b201b,0x443f8dcf}}, // _kle_, rily, trôn, _kmu_,
+ {{0x2bdd6260,0x9f47a05c,0xdb03edd0,0xa96a0dd1}}, // न्ता, jené_, _cená, жима_,
+ {{0x682b2dd2,0x2101623d,0x68302097,0xdb06407b}}, // _süda, lčić_, _läde, _pekí,
+ {{0x682fa042,0x26ca2644,0x3b0a204e,0x4427a17b}}, // _føde, tubo_, оего_, nkn_,
+ {{0x69dc2dd3,0x44268013,0x38694a88,0x1ddfa969}}, // rfre, yko_, flar_, प्यत,
+ {{0x6443add4,0x4427a13b,0x09e34a00,0x6e2d0018}}, // hini, hkn_, косн, _glab,
+ {{0x21674878,0x4427a17b,0x7e64072c,0x62864dd5}}, // [880] дити_, kkn_, _ikip, _viko,
+ {{0x3ea048cc,0xb0dda918,0x38694dd6,0xf1bf4dd7}}, // rpit_, _महंग, alar_, _lláh_,
+ {{0x661b8163,0x26ca21fb,0x3ea04106,0x7e69cdd8}}, // _bouk, qubo_, spit_, klep,
+ {{0x442d8dd9,0x38694dda,0xf987c25b,0xddc1e06f}}, // _cle_, clar_, _لب_, silň,
+ {{0x9f404ddb,0xa3cc0ddc,0x63ae6022,0x661b80ca}}, // rdió_, _लाई_, _tebn, _douk,
+ {{0x2cb860a9,0x68ed81ae,0x66098ddd,0x798bc1b6}}, // _tvrd_, çada, _enek, _aggw,
+ {{0xc0e3a55a,0x4426849f,0x628e608b,0xdb83612a}}, // војк, pko_, _uubo, _агри,
+ {{0x661c6dde,0x29024ddf,0x38604de0,0xb6a3c119}}, // _hork, _krka_, noir_, _thấ,
+ {{0x6b63893b,0x67244132,0x01638de1,0x26c5ec48}}, // _акта, nyij, _акто, erlo_,
+ {{0x7e640167,0xdb03e066,0x443ea05d,0xdb052066}}, // _akip, _sená, _umt_, _lehá,
+ {{0x6e2060ba,0x661c6cde,0x9726a0eb,0xdb1aa0b8}}, // _îmbu, _mork, _تفاو, _ectò,
+ {{0x7874e017,0x64a6c03b,0x27e121e7,0x60e7c156}}, // dàve, _жада, _sbhn_, örmå,
+ {{0x09d7c1a6,0x63be20d5,0xd838c0ca,0xe667214f}}, // _দোকা, _bdpn, _dwčt_, дтво,
+ {{0x7d048052,0x3e6e802e,0xf99f4030,0x38694c8a}}, // äisy, kýto_, _enèl_, wlar_,
+ {{0xe8004033,0x9f47ade2,0xb5fb0049,0x3e45c018}}, // _लोहा_, tené_, hlái, cēts_,
+ {{0x4ea74244,0xc6a7490f,0xd6f2cafa,0xf1b98066}}, // држа, држи, _अनाथ_, _koše_,
+ {{0x9f47a03a,0x58844099,0x7a302de3,0x443f800a}}, // [890] rené_, тыра, _säte, _rmu_,
+ {{0xdcfae3df,0x3869420f,0x356b6103,0x44272067}}, // latı, slar_, орен_, _ôn_,
+ {{0x26ccede4,0xd251454f,0xb5fd86a6,0x27f860fd}}, // nudo_, _مند_, _akša, _darn_,
+ {{0x60cb8d72,0x7870697a,0x682de057,0xdfa7413a}}, // tugm, räva, _múda, _تحدي,
+ {{0x442d8de5,0x4427a8a7,0x628761ae,0x6b8d0c57}}, // _vle_, tkn_, _tijo, _mgag,
+ {{0x6288ade6,0x661ce156,0xeb99ade7,0x9f42404a}}, // _cido, örkl, чик_, _jaký_,
+ {{0x69d724ef,0x694521e4,0x2489005d,0xdcfae315}}, // _बानी, _učeć, _oiam_, katı,
+ {{0xae09c077,0x442d8de8,0x3e746105,0x248fc05d}}, // वाइन_, _ule_, räte_, _pugm_,
+ {{0x7fd5e407,0x7d02c8e0,0xa3e58029,0x224dcde9}}, // ніні, _cros, _बॉय_, rnek_,
+ {{0x6f02c00c,0x6ec0e4e6,0x699fc1b0,0xb5fdc64e}}, // _droc, _विशु, _ग्री, doše,
+ {{0xe0460702,0xd24ec01c,0x60cd4dea,0xf99f4011}}, // енни, شنی_, huam, _anèm_,
+ {{0xc7b8adeb,0x661d47c6,0x3495c39d,0xc8692095}}, // _vođe_, _nosk, _задр, _הן_,
+ {{0xd12eedec,0x44294ded,0xad27c01e,0x7d02cdee}}, // لمی_, dka_, _تراو, _gros,
+ {{0x68260057,0x26cce4f8,0xb5fb0057,0x61e2c07f}}, // _módi, budo_, toác, _zbol,
+ {{0x64456054,0xb4ada4ef,0x2604c010,0x5884e1ba}}, // dihi, _कबो_, वाची_, _рыца,
+ {{0xd7dfa046,0x628980c2,0x61e2c17b,0x201ce0a9}}, // प्तच, _nieo, _xbol, _zovi_,
+ {{0x442fcdef,0x787601ae,0x6f03e066,0x69d6000f}}, // [8a0] _mlg_, gáve, _hrnc, _icye,
+ {{0xd12f20e0,0xa3e58077,0x6b8d0df0,0x5efe8292}}, // جمہ_, _बॉब_, _zgag, लिस्_,
+ {{0xa68640d6,0xe1c9adf1,0x6288adf2,0xfeba2ca7}}, // _плод, _राजघ, _rido, _عادت_,
+ {{0x2bd2a010,0x4a9b200b,0x1e1ceaf7,0x7e61edf3}}, // _साहा, _מיטג, _पक्ष_, holp,
+ {{0x44f1a0db,0xa06aadf4,0x63b64054,0x67d4a677}}, // _aš_, пада_, mayn, волу,
+ {{0xf1b98098,0xdb03e121,0x7c29c3f3,0x78ada361}}, // _roše_, _genç, eker, ćavn,
+ {{0x8858cdf5,0x7e7e61e7,0x6abd00c2,0xed576df6}}, // микс_, mmpp, _ऍट्र, _зор_,
+ {{0xa3cc010a,0x64460df7,0xddcbe098,0xd90da050}}, // _लाग_, jiki, _čiši, تیم_,
+ {{0xd7faec84,0xe8056010,0x26cdc019,0x682ec153}}, // пун_, राचा_, gueo_, _cùda,
+ {{0xd010c043,0x61ed4df8,0x26c7adf9,0x2c86a031}}, // الک_, zeal, brno_, _dídì_,
+ {{0x44f1a00a,0x3a750dfa,0x24890286,0x3a376095}}, // _gš_, клор, _piam_, גרים_,
+ {{0x24890dfb,0xf1b980a9,0x64456dfc,0xadf4c5cb}}, // _qiam_, _toše_, zihi, _आसिन_,
+ {{0xdca3c0ff,0x201eadfd,0xdb0e607b,0x6fa6c6a8}}, // _бачи, _koti_, _debé, _ट्यू,
+ {{0xf9f1007c,0x63adedfe,0x442ee94b,0xa49b42d9}}, // টাইল_, _đang, _ulf_, _bròn,
+ {{0x64460dff,0xb5fdc86e,0x69c08e00,0xa49b4392}}, // biki, soše, _odme, _cròn,
+ {{0x7876001f,0xddcf8e01,0xbbcb4026,0x63b64e02}}, // táve, _žeže, िलेक, gayn,
+ {{0xf9c765ed,0xdb064066,0x9f42406f,0x6495a227}}, // [8b0] ещен, _peká, _taký_, _aġil,
+ {{0x61ee2e03,0xddde2143,0xc058684f,0x61fd0e04}}, // cebl, _tipš, ніх_, ldsl,
+ {{0x628aee05,0x68260e06,0x533526bb,0xdcfc219f}}, // _mifo, _sódi, _рент, barı,
+ {{0x68ed8e07,0x64456e08,0xb6ba2076,0x68260530}}, // çado, sihi, _קצרי, _pódi,
+ {{0xdb0764cd,0xfd664088,0x68302046,0x61fd01b9}}, // _dejá, _mkpị, _häda, idsl,
+ {{0x69d56064,0x7642ce09,0xa5bb4e0a,0x290480a9}}, // _ucze, _amoy, _glór, _drma_,
+ {{0xa3d2a077,0xb3d2a010,0x9f42c03a,0xed156043}}, // _वार_, _सारख, ľký_, _جہاں_,
+ {{0x7e6d40e0,0x60dd0e0b,0x7e91a018,0x61fd0065}}, // nlap, ktsm, _jāpi, jdsl,
+ {{0x09e0c07c,0x3ea0e0e4,0x60dbc04e,0x3ce90286}}, // _মোহা, _čita_, utum, _npav_,
+ {{0xdb0f0e0c,0x7c2b8277,0x601ee04a,0x6441ae0d}}, // gací, lkgr, _námě, _umli,
+ {{0xf9926095,0x26cf8e0e,0x6515a711,0xe6c7a13a}}, // ירי_, lugo_, _جوائ, _ستاي,
+ {{0x60dd0e0f,0x9f47a06f,0x26c9420d,0x25a94089}}, // ftsm, není_, krao_, jbal_,
+ {{0xdb0f0e10,0x64460547,0xdcfc2181,0x628eee11}}, // bací, riki, varı, íbor,
+ {{0xb5fdc098,0xa0a30e12,0x628aee13,0xdb0f0019}}, // bošc, рард, _gifo, cací,
+ {{0x7e628194,0x60dd05dc,0xeb9725a8,0x1f592e14}}, // yoop, atsm, тис_, ешть_,
+ {{0x61ee231d,0xcfcaa07c,0xdb03e04e,0x61fb8e15}}, // sebl, ল্পন, _venä, _naul,
+ {{0x27e043ad,0x26c944ad,0x7e63ae16,0x27fd80e2}}, // [8c0] rfin_, grao_, konp, ddwn_,
+ {{0xb69b401f,0x60c9c004,0xddc283de,0x69c1a0bb}}, // _trân, irem, dloš, _ndle,
+ {{0x2247a31d,0xdb098057,0xf767ca28,0x09b821a6}}, // dink_, _odeó, _چا_, _জাপা,
+ {{0xfbdf0016,0x69c1ae17,0x386dce18,0x61e92480}}, // hiên_, _adle, jler_, đele,
+ {{0xe8e228c6,0x61faae19,0x7ae98057,0xe2978e1a}}, // _पहुच, _patl, _cpet, вач_,
+ {{0xf99f42f8,0x6da3614f,0xfc3f207b,0x61faae1b}}, // _anèh_, _вира, opía_, _qatl,
+ {{0xa2c48c87,0x7a23410a,0xc6c3c07c,0x61e32106}}, // _रिव्, _tõtt, ্বোচ, _önle,
+ {{0xdcfd03cd,0x5186c5be,0xd7068967,0xaca42125}}, // zası, купа, _изби, _akịt,
+ {{0x9f4b04cd,0x7c2086f1,0x9f4fce1c,0xa686ae1d}}, // pecé_, _iomr, _pagá_, _алад,
+ {{0x386dc76d,0xe9df0049,0x7e6d4e1e,0x61e28e1f}}, // aler_, giún_, zlap, lfol,
+ {{0xa3d6210a,0x68314156,0x2c758156,0xdb0f0089}}, // हलन_, _låda, låda_, rací,
+ {{0x644724b7,0x8143c050,0xd36fa7f0,0x9f5200f9}}, // wiji, اپیم, _بهم_, _bayà_,
+ {{0x3ea68163,0x9f5862a8,0xecc7014a,0xdb152e20}}, // npot_, _daré_, ъщно, mazé,
+ {{0xf1b98143,0x61ef0e21,0x63b8a271,0x68302e22}}, // _moša_, secl, navn, _päda,
+ {{0x9f586017,0x8ccc8077,0xf487e0eb,0x35d88046}}, // _faré_, _समझो, _شاهی, _डाढ़,
+ {{0x660d0e23,0x64472e24,0x27fce271,0x63b8a271}}, // _unak, siji, _havn_, havn,
+ {{0x3d0d4064,0xd7d2a046,0xb5fdc041,0x44316e25}}, // [8d0] समें_, _सांच, noša, _plz_,
+ {{0xdb00ce26,0x26deee27,0x26cf800f,0xb5fd8012}}, // lamó, otto_, vugo_, _ekšl,
+ {{0x4420007d,0x387fc5fc,0x6835c0c4,0xe0df0197}}, // _yoi_, ymur_, _háde, ltò_,
+ {{0x26d120c1,0x628d0e28,0x61e28271,0xb5fdc018}}, // muzo_, _jiao, gfol, koša,
+ {{0xb5fb00e0,0x27ef8584,0x22494a03,0x9f47a06f}}, // llás, regn_, miak_, rení_,
+ {{0x44332037,0x628bc106,0xdb08a64c,0x9f47a1ce}}, // _jlx_, _sigo, _sedá, sení_,
+ {{0x9f47a032,0x81b9007c,0x3cef808d,0xb7d5c1e9}}, // pení_, _চান_, _इनके_, _aṣat,
+ {{0x6e208e29,0x998fc03a,0xa3e324da,0xc1b9c13a}}, // _gomb, ťaže_, फ्त_, رابط_,
+ {{0x26dee65f,0x2906c005,0x7e698e2a,0x387fce2b}}, // etto_, _croa_, _ikep, rmur_,
+ {{0x6fd8e010,0x44212e2c,0x96d54c87,0x35b980c5}}, // _माणू, _noh_, यूयॉ, _उजड़,
+ {{0x9f586e2d,0x78a2ce2e,0x628bc0ca,0x06860926}}, // _paré_, _etov, _tigo, лгон,
+ {{0xfbdfa010,0x629a205c,0x23dfad0b,0x212948a7}}, // प्रम, ítom, प्रद, kyah_,
+ {{0x6e21ae2f,0x7c2d4e30,0x656bc05d,0x44f52026}}, // _kolb, dkar, _uygh, _ať_,
+ {{0x656d01e7,0xd040e03c,0x212948a7,0x27ffc265}}, // _dyah, kumə, dyah_, ldun_,
+ {{0xed572e31,0x69c3e026,0x69d8e010,0xa7fb0057}}, // гор_, _idne, _माती, coño,
+ {{0x44442e32,0xf1b9c320,0x44332733,0x7a314584}}, // _wm_, naš_, _elx_, _påta,
+ {{0xe09ec079,0xb5fb0009,0xfc3f2510,0x7e644071}}, // [8e0] _kọọ_, rlát, mpín_, roip,
+ {{0x2907e88e,0x442dce33,0x629600ae,0xb5fb0e34}}, // _krna_, hke_, _iuyo, slát,
+ {{0xa56508b5,0xa3d2a046,0xa3baae35,0xdb07206f}}, // _مدین, वलस_, _अजब_, zajú,
+ {{0x68fce065,0xdbd740cb,0xb5fb0a7d,0xdb08a009}}, // ærdi, mäßi, llár, _ülés,
+ {{0x78520041,0x627c00c2,0x6d5fa5fc,0x2ca68114}}, // tāvj, _głoś, şqal, upod_,
+ {{0x61e28e36,0xf1b9804d,0xe09ec079,0x7c2e2b83}}, // sfol, _voša_, _nọọ_, nkbr,
+ {{0xe8070295,0xaa66ce37,0xaae6a343,0x787603e4}}, // षयमा_, лтик, استو, záva,
+ {{0x69c3e0fd,0x68260009,0x27f24e38,0x3b06ce39}}, // _adne, _móds, neyn_, _proq_,
+ {{0x2907ee3a,0x8fa6a7f3,0x049ec125,0x7c9ec079}}, // _arna_, _базе, _bọọd_, _bọọl_,
+ {{0xe29fc83e,0x7c2d4e3b,0x44224e3c,0xdb1645cd}}, // íða_, zkar, _lok_, rayè,
+ {{0x26c00e3d,0x656d0b2b,0x7d93a13a,0xf1b9c7a0}}, // _avio_, _syah, _المؤ, baš_,
+ {{0x6449c8aa,0x7d044944,0x3866831d,0xe29ace3e}}, // ciei, kvis, noor_, _зад_,
+ {{0xc5f3c095,0xd90f06b0,0x74c0e3af,0x2907ee3f}}, // _לדף_, سید_, _विकृ, _erna_,
+ {{0x7c2d4e40,0x961d8041,0xe0df0024,0x2a668286}}, // wkar, _viņi, rtò_, hoob_,
+ {{0x62982e41,0xa3e6a010,0x5334207a,0xdb0e289b}}, // _évol, य्य_, берт, tabá,
+ {{0x9f47a06f,0x62828022,0x682b25fc,0x26d12e42}}, // nená_, jmoo, _müdi, ruzo_,
+ {{0x644aae43,0xdb03a05c,0x61fd4e44,0x69d7c8c6}}, // [8f0] kifi, daný, _vasl, _यासी,
+ {{0x69c4003a,0x63baee45,0x64464098,0x29090098}}, // _zdie, natn, _emki, _iraa_,
+ {{0xdb0bc0e0,0x7a36e0fa,0x1c0048b1,0xbcfb42f4}}, // _megá, _gâte, _लोकल_, _ayét,
+ {{0x68ed0058,0x7d044071,0x9f47a066,0x62828054}}, // _kpad, bvis, jená_, gmoo,
+ {{0x61ff0c43,0xb3ba4095,0xf8bf0e46,0xb87b62b2}}, // _maql, _במרכ, nsé_, _osíx,
+ {{0x1aebe07c,0xa3e7c914,0xdb0bc322,0xb5fdc098}}, // টিতে_, म्न_, _negá, gošn,
+ {{0x7c22ce47,0x6449c057,0x7876005c,0x6e21a026}}, // _boor, tiei, dávn, _volb,
+ {{0x6009ce48,0x2d9ee12a,0x9f45a017,0x248ee227}}, // тним_, ecte_, _saló_, _aifm_,
+ {{0x61e98e49,0xe739c926,0x2003c026,0x27ffce4a}}, // _ibel, теп_, ěji_, rdun_,
+ {{0x661bc342,0x1c45eadc,0x1309e04e,0xaae7a13a}}, // sjuk, рном, вной_, _مسؤو,
+ {{0x63ad4e4b,0x787880b6,0x78a409ce,0x9f47a05c}}, // kban, líva, _stiv, bená_,
+ {{0xb87b6005,0x62960e4c,0x764b84ec,0x6455265f}}, // _esíx, _puyo, nigy, enzi,
+ {{0xdb01ee4d,0xae004e4e,0xe8f70e4f,0x25adce50}}, // taló, _लोगन_, аля_, mbel_,
+ {{0x645dee51,0x63baee52,0x68384005,0xdb0f4e53}}, // ésid, catn, _míde, _cecí,
+ {{0x60cd45fb,0x7ae32277,0x2a7dc05f,0x6297203e}}, // eram, _äntl, _khwb_, _cuxo,
+ {{0x20116e54,0xaca38079,0x644b8b87,0x62960e55}}, // _anzi_, _arọn, jigi, _tuyo,
+ {{0x6aa4ce56,0x20120e57,0x87ba6052,0x3e7c8066}}, // [900] _çift, _inyi_, вует_, níte_,
+ {{0x656f4e58,0x6283a16d,0x3ce04e59,0x7c23e2a8}}, // _dych, jmno, rtiv_, _jonr,
+ {{0x63bbce5a,0x2619a20b,0x44224e5b,0x1ddde46f}}, // kaun, पानी_, _tok_, _मानत,
+ {{0x63bbc041,0x09068474,0x91e68e5c,0x7d044e5d}}, // jaun, ипен, роде, pvis,
+ {{0x46f68892,0x38668e5e,0x63bbce5f,0xb5fdce60}}, // рчет, roor_, daun, tošn,
+ {{0xf2c6a312,0xd90d8555,0x7d098e61,0x69db8019}}, // асин, عیل_, _dres, _ecue,
+ {{0x6e22ce62,0xdb0bc066,0x26df8e63,0x27fea31b}}, // _poob, _regá, _equo_, _vatn_,
+ {{0xddcd46ca,0x4814ce64,0x644b8e65,0x4423605f}}, // zmaž, смис, cigi, _zoj_,
+ {{0x6e23ee66,0x26c040fd,0x2498617b,0xdca382d8}}, // _bonb, nsio_, _hurm_, _мати,
+ {{0x66028e67,0x91a6c0c2,0x212b0066,0xa3e7c1c9}}, // ldok, _ट्रै, vych_, म्ब_,
+ {{0x7bc64e68,0xde88e067,0xc914e07c,0x4ac2c010}}, // _odku, _dịp_, ান্ত_, _शिकव,
+ {{0x629800e4,0x26cdce69,0xe786c07a,0x3e720049}}, // _čvor, breo_, _куло, náth_,
+ {{0x63bc27e4,0xddc28064,0xdce08041,0xe1ff45df}}, // farn, wnoś, _izmē, _raó_,
+ {{0x644b8e6a,0x2bdde7b3,0x63bae227,0xf8bf0e6b}}, // zigi, _माया, qatn, rsé_,
+ {{0x87042691,0x645dee6c,0xc7b8a29c,0x61ff0e6d}}, // ояте, ésie, _međ_, _taql,
+ {{0x44f8a067,0x44c12013,0xdb0080cb,0x66008e6e}}, // _mũ_, rė_, _bemü, _namk,
+ {{0x7e6d0e6f,0xdb1ae00e,0x628f4e70,0x60cd45df}}, // [910] _ikap, katë, _vico, uram,
+ {{0xa81460bc,0x44248e71,0x644b8e72,0x25e30870}}, // одуш, _aom_, wigi, ट्री_,
+ {{0x9f5ea00e,0xf09f4067,0x7cec2009,0xa7fce5fc}}, // _datë_, _trào_, _vörö, nlıq,
+ {{0x442360c2,0x66028e73,0x67282156,0xdb01a066}}, // _woj_, gdok, ädje, _celý,
+ {{0x7cec20e0,0x7e6d0167,0xee87665d,0xe8004e74}}, // _törö, _mkap, _выво, _लोटा_,
+ {{0x3e7c8032,0x44e28067,0x2ca5a057,0xe8df4067}}, // víte_, _dư_, _ptld_, _chục_,
+ {{0x7afc605d,0x2499417b,0x6283a16d,0x62834024}}, // _gsrt, _husm_, rmno, _ènos,
+ {{0x20012e75,0x5f0827a7,0xcf582694,0x764d48a4}}, // _lahi_, हित्_, יבות_, niay,
+ {{0x25adce76,0x9f5ea0d1,0x672d4e77,0x64498e78}}, // rbel_, _latè_, nyaj, _imei,
+ {{0x30e7a4a4,0x4424805f,0x644d4e79,0x645b8361}}, // _відб, _zom_, hiai, _hjui,
+ {{0x69d8e046,0x63bd0e7a,0x2c7200e0,0xa3bb87f4}}, // _मारी, fasn, ládi_, _आजा_,
+ {{0x6285658f,0xb995c13a,0x2bddc466,0x6e252090}}, // hmho, _الإب, क्खू_, _aohb,
+ {{0x644d4e7b,0x2bdcc55d,0x539ac095,0x53dc2870}}, // diai, _याता, _עיסו, _बादश,
+ {{0x07a32e7c,0x6495a361,0xdb0ae851,0xdb1980eb}}, // зарн, _išib, _befä, gawé,
+ {{0x9f95e10a,0x20012a88,0xb05b2156,0x7a2ec58f}}, // _müük_, _dahi_, vrät, _bùth,
+ {{0x7bc7673b,0x7aef4e7d,0x20120079,0x44e1a071}}, // _adju, _bpct, _unyi_, ió_,
+ {{0x25a04653,0x87b941d3,0x69c76320,0x14c840eb}}, // [920] rcil_, руст_, _bdje, شهای_,
+ {{0x4425a9d1,0x44e08d8f,0xdb1600d1,0x60c92018}}, // _nol_, vò_, _reyè, ņema,
+ {{0x5f0625be,0x764d413b,0x657b8e7e,0x3a248e7f}}, // _узна, biay, _azuh, _pomp_,
+ {{0x44f8a067,0xaa7b6e80,0xb900646f,0xf1b98320}}, // _rũ_, _spýt, _दम_, _ceš_,
+ {{0xa2c2410a,0x27e68e81,0xc7b8a143,0x44e1a057}}, // _लिट्, rfon_, _peđ_, eó_,
+ {{0x3e36654f,0x6c36654f,0x44e08e82,0xdb01a0e0}}, // _افسر, _افسا, rò_, _felü,
+ {{0x66008e83,0x63b8ee84,0x28d80ba0,0x81c2807c}}, // _tamk, _revn, भूति, ্ভব_,
+ {{0x224dce85,0x3ea76e86,0x6e3bce87,0x4425ae88}}, // giek_, ínte_, ghub, _eol_,
+ {{0x4425a8c5,0x9f4940c4,0x9f4ce06f,0x7a2b2046}}, // _fol_, ndað_, redí_, _kütt,
+ {{0x3a25a1e7,0x26cac07f,0xd491a119,0xa3ca6a53}}, // _golp_, čbo_, _gì_, ोलि_,
+ {{0x290ae1ae,0xe3bf4057,0xdd90e25b,0x7a2b2e89}}, // íba_, _loña_, بود_, _mütt,
+ {{0x60c28e8a,0x3cf94295,0x63be6058,0x7ae44e8b}}, // osom, _उनले_, hapn, ktit,
+ {{0x6265c13a,0x629aae8c,0x66044e8d,0x648960e4}}, // _بالق, _huto, ddik, _iživ,
+ {{0x2348e050,0x2bdb0e8e,0x6458a451,0x68fce22e}}, // _پستی_, _भावा, nnvi, ærds,
+ {{0x63a2865f,0x4877413a,0x68e44e8f,0x25e102b4}}, // kcon, مدرس, etid, _कापी_,
+ {{0x6299c037,0x44386090,0x3179c0c2,0x3a26c363}}, // _yuwo, _alr_, ższe_, _joop_,
+ {{0x7878882d,0x7ae4492d,0x91b962f0,0xe3bf407b}}, // [930] tívn, gtit, игат_, _coña_,
+ {{0x44386e90,0xaca4c079,0x63be6d3d,0x44e1ae91}}, // _clr_, _adịz, gapn, yó_,
+ {{0xdce8a026,0xdb0f407b,0xa195427c,0x44204e92}}, // _vzdě, _decá, _манч, bji_,
+ {{0x4426ce93,0x6601ae94,0x33758e95,0x644f0e96}}, // _noo_, _salk, огер, nici,
+ {{0x7d032e97,0xcee88553,0x290b40ba,0x25bd8c8a}}, // bírá, مرون_, _urca_, rawl_,
+ {{0xbcfb4004,0xdb1ae4b7,0x224dc0c2,0x2486806f}}, // _exéc, patè, wiek_, amom_,
+ {{0x7c3bce98,0x44394e99,0x02a2e088,0x6299ce9a}}, // thur, _kls_, _blọọ, _suwo,
+ {{0x69c8a11d,0x2617620b,0xddd4e23d,0xed1580e0}}, // _edde, नावी_, _čaši, _وہاں_,
+ {{0xc0528087,0x7c3bce9b,0x6f0d0125,0x7c43c274}}, // _חזן_, rhur, _nrac, _ćurč,
+ {{0x44e1ae9c,0x26c20015,0x752f0064,0xb95b41e9}}, // pó_, vsko_, dycz, _adìk,
+ {{0x6e276e9d,0x26c200c2,0x35d320c2,0xbb55a0eb}}, // _mojb, wsko_, _ताज़, _بناب,
+ {{0x02a2e125,0x7d0dc057,0x7bdae05f,0x7b206274}}, // _glọọ, _áaso, agtu, _ušuš,
+ {{0x63be613b,0xcd2bc711,0x3e560041,0xdb1c2050}}, // yapn, _حسان_, nāti_, maré,
+ {{0xb05b6277,0xdb08a03e,0xdb0d2066,0x80ca61a6}}, // _svän, _redú, _škál, _রহস্,
+ {{0xa2c24295,0x3ea90e9e,0x764e2e9f,0x20024e5a}}, // _लिङ्, _etat_, riby, _paki_,
+ {{0x6d054077,0xee374ea0,0x41aa427c,0x60c48ea1}}, // रिंग_, онс_, рван_, šimi,
+ {{0x7ae442a8,0x26c2cea2,0xec4a604e,0x9c26667b}}, // [940] utit, škov_, азал_, одад,
+ {{0xf77226ec,0xb4ea4a53,0xb5fb40f9,0x7ae44ea3}}, // _ياد_, _मही_, _ajád, rtit,
+ {{0x442046e8,0x6f0d029f,0x9f586057,0xe8168010}}, // pji_, _zrac, _bará_, णाला_,
+ {{0x44e3e15a,0x2aaa80de,0x6d41e09f,0x629aaea4}}, // iñ_, атко_, izla, _ruto,
+ {{0x4427eea5,0x63bb8ea6,0x63ad84cd,0x60c2803b}}, // _oon_, _deun, ñana, usom,
+ {{0x20036ea7,0xbddb0162,0x63a44ea8,0x3af9400b}}, // _gaji_, nièr, lcin, _פּרע,
+ {{0x9f586ea9,0xd838a299,0x34b70d0b,0xd6d0a049}}, // _fará_, _vlč_, _अब्द, رقة_,
+ {{0x69dbceaa,0x3ae40eab,0x6d41e05d,0x69d60d0b}}, // ggue, köp_, dzla, _भागी,
+ {{0x25ebce4e,0x80c48029,0x7d09ceac,0x290d80ba}}, // _अउरी_, _रिटे, gves, _grea_,
+ {{0x63bc6654,0xdb1c2ead,0x3942000a,0x27f82eae}}, // _jern, parè, dzks_, jern_,
+ {{0xd25104b7,0x63bc6eaf,0x68e40eb0,0x6fdd6010}}, // فند_, _mern, _aqid, _पासू,
+ {{0x20068eb1,0x09b4c07c,0x29000037,0x32068c57}}, // ndoi_, _টাকা, _bsia_, ndoy_,
+ {{0xa5bb4057,0x5fba6026,0xb606c2e2,0x78adc066}}, // _ilóx, इराल, rašć, čový,
+ {{0x26c7603a,0x752f0064,0x7bdc2eb2,0x44fae1d3}}, // ánok_, rycz, ggru, _tū_,
+ {{0x43942eb3,0x29000eb4,0x2bdc2eb5,0x0dcb0eb6}}, // дарс, _esia_, _बारा, _купи_,
+ {{0x200368a4,0x63a44174,0x6fdd6010,0xafdb0042}}, // _saji_, gcin, _पाहू, riøs,
+ {{0xe04b2050,0x7d0d0eb7,0x629c6eb8,0x7aedeeb9}}, // [950] _نشده_, _uras, _auro, _ćati,
+ {{0x9f4b44cd,0x26d82eba,0x69dc2ebb,0xf1b9800a}}, // _sacó_, buro_, cgre, _tošk_,
+ {{0xdb08e886,0x629c6133,0x5695604e,0x7c2284a4}}, // madó, _curo, чает, fjor,
+ {{0x6281a515,0x49082bc5,0xfad62095,0x8ccc6738}}, // _shlo, हिरो_, _צורך_, _हिरो,
+ {{0x24894ebc,0x200488c4,0x8c1a4095,0xf99081ac}}, // mmam_, _dami_, _הורי, نبه_,
+ {{0x35e145cb,0xdb08eebd,0x2be2604b,0xb5fdcebe}}, // _फाड़, nadó, _पाया, pošk,
+ {{0x61e9cebf,0x23984079,0xa49b40b8,0x25eca046}}, // tfel, _ọji_, _eròt, _आउरी_,
+ {{0x60c5600e,0x3b0d8037,0x787604d2,0x200481df}}, // mshm, _treq_, rávk, _gami_,
+ {{0xe8e0c016,0xed5986be,0x290ee0e8,0x98b24064}}, // _đợi_, _beže_, _arfa_, rzyć_,
+ {{0x6281aec0,0x7a302105,0x69dbc07b,0x7c298b9c}}, // _uhlo, _täti, sgue, _hoer,
+ {{0xbddb0004,0xdb03e057,0x200480bb,0x629c6928}}, // vièr, _renó, _yami_, _xuro,
+ {{0x6aaae07c,0xc0492095,0x2cadcec1,0x63bd4c8e}}, // _কিশো, _גז_, pped_, _nesn,
+ {{0x7c2987ff,0x02dbab27,0x69cbc0ae,0x443ee256}}, // _moer, _यमुन, _idge, bht_,
+ {{0x60c560dd,0xd945eec2,0x386dc110,0xfbd3e0d0}}, // kshm, _нели, loer_, _آتش_,
+ {{0x290fc058,0x6e3b8ec3,0x638327a8,0x63bd4ec4}}, // _hrga_, _olub, егра, _besn,
+ {{0x26d90ec5,0xba234331,0x9f4b2009,0x27f82ec6}}, // buso_, ндск, áját_, sern_,
+ {{0x60c560dd,0xe29f4069,0x9989400a,0xb146462a}}, // [960] eshm, _yrði_, tkaš_, чнал,
+ {{0xaf9a621a,0x320485cf,0x6296063f,0xdb1aa00e}}, // стах_, _samy_, _hiyo, _qetë,
+ {{0x6e3b8885,0x99894274,0x69c0cec7,0x0404a07c}}, // _blub, rkaš_, yame, লামী_,
+ {{0x63bd4ec8,0x2611a290,0x7c3b8907,0x6e28a06e}}, // _gesn, _धोबी_, _clur, _sodb,
+ {{0x62960ec9,0x6284000c,0x225f8143,0x26c5206f}}, // _miyo, _mhio, _djuk_, álov_,
+ {{0xdd90a043,0x7ae4c052,0x645c2090,0x7bc0ceca}}, // نڈا_, _äiti, nnri, wamu,
+ {{0x69c0cea3,0x09c7007c,0xf1be0026,0xed598ecb}}, // tame, ষ্টা, ्णान, _reže_,
+ {{0x443fc17a,0x4394ce12,0x645ae5fa,0x62956ecc}}, // ghu_, майс, wnti, _zizo,
+ {{0xfe458555,0x98a4c013,0x23dc28d6,0xdb08e315}}, // _تکلی, kymą_, _बांद, sadü,
+ {{0x81b9007c,0x09c50010,0x2a60088e,0x442a6ecd}}, // _চাই_, वण्य, _ajib_, _job_,
+ {{0x2d800ece,0xe41ae06f,0x44290ecf,0x645d0ed0}}, // _azie_, _kľúč, _poa_, ério,
+ {{0x0e9ba095,0x6d444ed1,0xed59ced2,0x39576095}}, // _השאל, kzia, saža_, לשים_,
+ {{0x644e6ed3,0x2005ab01,0xb87b2168,0x442a6048}}, // _ombi, _yali_, stím, _oob_,
+ {{0xc05b2a0e,0x7d02ced4,0xb7b5a079,0xb5fb40f9}}, // сім_, _isos, _họr, _akáp,
+ {{0x9f5a21af,0x629d408b,0x63bd4ed5,0xb17b0065}}, // _papá_, _suso, _pesn, dgåe,
+ {{0x66064ed6,0x62840ed7,0x80cd8c87,0x61faeed8}}, // _cakk, _ghio, _सिले, letl,
+ {{0x8d77413a,0x66064bec,0x63bd4ed9,0xa3dfc010}}, // [970] سارا, _dakk, _vesn, _तास_,
+ {{0xaad4ceda,0x17580076,0xe3bf407b,0x7c2ae733}}, // _दिनक, _מסחר_, _toño_, _jofr,
+ {{0x02a426fc,0xa3c2a180,0xf773633e,0xbddb00d1}}, // _прям, ्णन_, راط_, chèc,
+ {{0x7bc1eedb,0xddcd4edc,0x66064edd,0xe0d0624d}}, // zalu, blaš, _gakk, ازل_,
+ {{0x6440c13a,0xfaff00b6,0x645d0ede,0x2bdde3af}}, // dhmi, nwë_, insi, _माला,
+ {{0x7c298b64,0xddc4416d,0x7c2aeedf,0x69d8e04b}}, // _woer, moiš, _nofr, _मागी,
+ {{0x6f0d407b,0x2007aee0,0x7d0d4ee1,0x2089c7ca}}, // lvac, rdni_, lvas, ойки_,
+ {{0x7bc1eee2,0x32094ded,0x443ce052,0x9f59000e}}, // walu, nday_, _alv_, gesë_,
+ {{0x66076ee3,0x6d43a0e0,0x1306c04e,0x200940a2}}, // _kajk, szna, дный_, idai_,
+ {{0x05dc2295,0x645bc12a,0x68e16156,0x64838046}}, // _बाइब, snui, _älds, sõid,
+ {{0x60c4803b,0x7d0d42df,0x6d456ee4,0x62960ee5}}, // šimt, hvas, nzha, _piyo,
+ {{0x7ec2eac3,0x78a96ee6,0x29116098,0x2611aee7}}, // _sípá, _čevl, _mrza_, _धोती_,
+ {{0x66064ee8,0x69cd01cd,0xd01220e0,0xec6ee331}}, // _rakk, _ddae, پلز_, _ап_,
+ {{0xddcd4ee9,0x7c2ae1cd,0x7bc7e069,0x68384057}}, // vlaš, _gofr, ðjud, _pído,
+ {{0x22404050,0x81e6407c,0x224fc037,0xdb1c6eea}}, // thik_, _যোগ_, _kmgk_, _verë,
+ {{0x799b8eeb,0xb5fb0057,0xd838c320,0x2fdfc0ae}}, // _nguw, gnád, _kuča_, ngug_,
+ {{0x27edc105,0xdb01a105,0x2fc0c10a,0x2baf24ba}}, // [980] ifen_, _gelö, _õige_, _ज्वा,
+ {{0x7c36403b,0x7a3fe004,0x7d040eec,0x261c0010}}, // skyr, _fête, _isis, यावी_,
+ {{0x3207e42c,0x7ae9ceed,0xae1b200b,0x141b200b}}, // _many_, htet, _וויכ, _וויב,
+ {{0x3209424f,0xb8e4407c,0x26da6eee,0x2007eeef}}, // cday_, _এম_, rupo_, _lani_,
+ {{0x290246ca,0x7c3d000e,0xe7e60573,0xbea38ef0}}, // _uska_, ërri, _काना_, вачк,
+ {{0x63a98ef1,0x69c44ef2,0x7d0400ce,0x2006c8c1}}, // _efen, laie, _msis, _saoi_,
+ {{0x38a449ac,0x3e560041,0x16db0308,0x8c3b6105}}, // _hör_, nāts_, _बम्ब, _fußb,
+ {{0x26068ef3,0xe81c6021,0x2007e436,0x683ccef4}}, // _सोची_, नावा_, _aani_, _béda,
+ {{0x61faeef5,0x3207e017,0xab5ce041,0xa3adc028}}, // tetl, _bany_, daļa, कड़_,
+ {{0x645d0ef6,0xdfd0e4a1,0xddc8e064,0xdb0400c4}}, // wnsi, ديد_, widł, _reið,
+ {{0xbbd76087,0x9986c066,0xda6fc20e,0x61ed4a88}}, // וויץ_, _ulož_, _ая_, yfal,
+ {{0x6e3e2361,0x201a2cd9,0x60dc2ef7,0x290481e7}}, // _alpb, _anpi_, durm, _ksma_,
+ {{0x0936cef8,0x2007eef9,0x443ea08b,0x442681ae}}, // _تراج, _fani_, _klt_, ijo_,
+ {{0x6aa08efa,0x2056e0ff,0x260f0d01,0xed598012}}, // _mumf, _ютер, _तोरी_, _ježa_,
+ {{0xdb1c2a5d,0x443eaefb,0x25a901d7,0x62972005}}, // barí, _mlt_, _tfal_, _vixo,
+ {{0x2498628f,0xbddb4efc,0x2007eefd,0xea9f2031}}, // _airm_, _amèr, _zani_, _aiṣe_,
+ {{0xe80f2029,0x682fa271,0xb5fdc133,0x62972efe}}, // [990] ायदा_, _mødr, mošt, _tixo,
+ {{0xb3a983df,0x38a44156,0x69c3a0d5,0x32094eff}}, // ğımı, _dör_, xane, sday_,
+ {{0x64896f00,0x7d0d4f01,0xc108c067,0x7c2d015a}}, // _džip, svas, _lỗ_, _koar,
+ {{0x6298ef02,0x442b4f03,0x3157e00b,0x76552157}}, // _livo, _uoc_, ויבן_, lizy,
+ {{0x660762f9,0x7c2d01fc,0x443ead4c,0xfb358056}}, // _tajk, _moar, _blt_, эндэ,
+ {{0xdb1c21af,0x25b283ed,0x6fdde077,0x6e2d0019}}, // zarí, ıyla_, _माइं, _loab,
+ {{0x61fbcf04,0x7a3cc59b,0x45d5809e,0xb5fdc133}}, // teul, _séta, _повс, košt,
+ {{0x2c640497,0x2bdb0010,0x27edc669,0x3e746277}}, // kıda_, _भागा, ufen_, sätt_,
+ {{0x2007ef05,0xdb18a195,0x61e52066,0x24994071}}, // _pani_, ravá, _ochl, _mism_,
+ {{0x51848818,0x26dca03b,0xed59c47c,0xe3b1e13a}}, // _руса, buvo_, sažo_, درة_,
+ {{0x25e1038e,0x2ca00f06,0x6443a6a5,0xdb1d4005}}, // _काशी_, _suid_, mhni, _desé,
+ {{0x78a0800f,0x7bc56f07,0xcfe9c050,0x3958e049}}, // _yumv, hahu, _رفته_, úrsa_,
+ {{0xdb1e20dd,0xdb1c2966,0x98e5c13a,0xc108c016}}, // _tepë, rarí, _أكتو, _gỗ_,
+ {{0x7bc1af08,0x3ea005df,0x69cf4f09,0x443e2009}}, // _jelu, _vuit_, _adce, ót_,
+ {{0x6299cf0a,0x5e964049,0x99804013,0xdb1c2f0b}}, // _kiwo, فلسط, ykių_, parí,
+ {{0x61fc2f0c,0xc32321ba,0xafdb4042,0x60d64f0d}}, // serl, ымск, _smør, trym,
+ {{0xd9434f0e,0x9c82c05c,0xed5a0b92,0xab664f0f}}, // [9a0] гети, íčin, _хоп_, хвал,
+ {{0x9f404f10,0x6aa08f11,0x6b840f12,0x7a302156}}, // lgië_, _rumf, _izig, _väts,
+ {{0x1636e00b,0x6608a530,0x44268f13,0x2d9dc088}}, // ענער_, _radk, tjo_, _ogwe_,
+ {{0x667686b0,0xdb1aee20,0xdfd28050,0x3a3ea07d}}, // ردار, natá, ايش_, _sltp_,
+ {{0x26dd848d,0x2905a106,0xe61f4067,0xdb18ef14}}, // guwo_, _asla_, _khôn_, _reví,
+ {{0x7bc56f15,0x442d8057,0x644282df,0x7bce6065}}, // cahu, _eoe_, shoi, _udbu,
+ {{0x316c0064,0x443ea149,0x645d0050,0x9663663e}}, // ędzy_, _vlt_, érik, _скре,
+ {{0x2c6400b0,0x3f7b6053,0x442d8f16,0x6443af17}}, // yıda_, _זאלס, _goe_, ahni,
+ {{0xc6f7c7b9,0xed59849f,0x442caf18,0x9c7d0098}}, // еных_, _teža_, _tod_, stčk,
+ {{0xb806a043,0x7656403c,0x3f9dc385,0x78a1a197}}, // _سکتے_, hiyy, _egwu_, _fulv,
+ {{0xe816a0c5,0xb7bd812a,0x3ea24f19,0x44446f1a}}, // _धोया_, _soţi, _lukt_, ih_,
+ {{0xdb1aef1b,0xe508c081,0x60dd02df,0x69c60f1c}}, // gatá, _tỷ_, tusm, gake,
+ {{0xdb056f1d,0x7bc56f1e,0x3ea122df,0x7e640030}}, // rché, yahu, _suht_, _djip,
+ {{0x9606e13a,0x2c640181,0xfc3f4049,0xdb056f1f}}, // _سبتم, rıda_, _slí_, sché,
+ {{0x44cca026,0xbd682f20,0x6146e0fa,0x44390098}}, // ně_, ерте_, _délé, pks_,
+ {{0xb21b0065,0xb8f7007c,0x6b9e2057,0x9f47a066}}, // dlæg, _সহ_, _egpg, jený_,
+ {{0xb87b29fe,0x68302156,0x61e28f21,0x3a2d8854}}, // [9b0] tuíd, _vädr, dgol, _roep_,
+ {{0x99676f22,0x7a36e004,0x442d8f23,0x78a2c691}}, // нтел, _bâti, _soe_, _muov,
+ {{0x200a6f24,0x7bc1af25,0x78a2c052,0x443f8f26}}, // _dabi_, _relu, _luov, _plu_,
+ {{0x2906c265,0x3ea2468f,0x7a35c1de,0xb9096c67}}, // _osoa_, _fukt_, _táti, _मम_,
+ {{0x5184487c,0xd009af27,0xbddb45df,0xdb1aa057}}, // _бура, деле_, _elèc, _detí,
+ {{0xbddb4f28,0x8d748ca7,0x660d4f29,0x7bc72f2a}}, // _flèc, داما, ldak, naju,
+ {{0x2fc5e3d1,0x48bdc07c,0x443f8f2b,0x442d85b2}}, // valg_, _আমের, _tlu_, _toe_,
+ {{0x443f8f2c,0x200a635f,0x29020064,0xb803607c}}, // _ulu_, _zabi_, ywka_, _উচিত_,
+ {{0x200b4a45,0x98f484aa,0x9e65e9b1,0x60c9c004}}, // _maci_, _مثلا, квид, usem,
+ {{0x5d54864d,0xc3344694,0x64564bd4,0x7c2f4197}}, // _скут, רוץ_, ziyi, _locr,
+ {{0x5589e6fe,0xd46a0f2d,0x6b8400e0,0xb21b086b}}, // ьбом_, дине_, _szig, klæd,
+ {{0x2fc5e042,0x6289805d,0xbbd1af2e,0x60c9c046}}, // salg_, _jheo, _सयुक, psem,
+ {{0x09cc607c,0x7e6940a9,0x6724417a,0x3f9f80ce}}, // র্টা, čepl, xxij, _mguu_,
+ {{0x2d9c603a,0x44294424,0x7a3dab3a,0x9f47a326}}, // ľvek_, eja_, _mèto, zený_,
+ {{0x2175e00b,0x6145e143,0xdca62f2f,0xa3e8cf30}}, // _בײַם_, _шека, таби, _बाम_,
+ {{0x6e3bcf31,0xf2d2a00b,0x6e29cf32,0x244d200a}}, // nkub, יעל_, njeb, džmu_,
+ {{0x9f47a05c,0x6495c098,0x03a6673f,0x64564f33}}, // [9c0] vený_, _ašik, визо, riyi,
+ {{0x76444f34,0x44446f35,0x3f9f8079,0x14d4c010}}, // shiy, uh_, _aguu_, _दिसण,
+ {{0xa5d9200b,0x77698057,0x69c4062c,0x4dd9200b}}, // _אַרי, _axex, _keie, _אַרױ,
+ {{0xdb1c2359,0x7d160f36,0xed5a804e,0x78a406eb}}, // cará, _krys, мое_, _kuiv,
+ {{0x6aa3e4cd,0xd838a29c,0x626581b3,0x7d094057}}, // _lunf, _liče_, دالق, _áesp,
+ {{0xddcd4064,0xa3e96028,0x2d80c1d7,0x9f5ca0fa}}, // ciał, _याय_, _ġie_, levé_,
+ {{0x9f47af37,0x661d4488,0x7d044f38,0x71766047}}, // denó_, _ynsk, lwis, _شهرا,
+ {{0x2619ca11,0xdb18e05c,0x7c3bcf39,0xfe6fc4a1}}, // भागी_, _nevá, gkur, يدي_,
+ {{0x2ca64ab5,0xf653442c,0x57eaa01f,0x660bcf3a}}, // íodh_, יצה_, _одам_, _dagk,
+ {{0x660d4f3b,0x6a788f3c,0x78a3ef3d,0x8af6e5fc}}, // zdak, rífu, _bunv, _çəkd,
+ {{0x7c29ca2f,0x787d0e41,0xd48fcf3e,0x3949e068}}, // bjer, lévi, _вр_, _ƙasa_,
+ {{0x6f160f3f,0x25a0065f,0x7bc72f40,0x629c600e}}, // _bryc, _cgil_, waju, _liro,
+ {{0x69c40f41,0xa2a245cb,0x442eef42,0x78a40f43}}, // _deie, _कूल्, _tof_, _cuiv,
+ {{0xd7f82967,0x7d160f44,0xcf938076,0x7bc3ef45}}, // кус_, _drys, יטר_, _genu,
+ {{0xf1bf8066,0x9f532005,0xa3d54143,0x6738a009}}, // áška_, _raxó_, ловч, nyvj,
+ {{0x61e44f46,0x69c3ef47,0x46d28148,0x53342f48}}, // ggil, _zene, _सिंह, шест,
+ {{0x22590536,0x645761cb,0x660d4a88,0xc6a44312}}, // [9d0] misk_, tixi, rdak, архи,
+ {{0x69c3e4fa,0xe1f06ab5,0x7bc8ef49,0x201ea13b}}, // _xene, قسم_, nadu, _nnti_,
+ {{0xda786f4a,0x645a45df,0x26cd816d,0xb87b6057}}, // тят_, ètic, _sveo_, _mpíx,
+ {{0x645a4004,0xdb1d4057,0x7bc8ef4b,0x64576025}}, // étit, _hesí, hadu, sixi,
+ {{0xb87b241d,0xdb1c6f4c,0x7a35c0c4,0x9479af4d}}, // btít, _cerâ, _mátt, нску_,
+ {{0x69dd6010,0x200cab33,0x69c8ef4e,0x442fcf4f}}, // _पाटी, _cadi_, jade, _sog_,
+ {{0x320caf50,0xb87b2017,0xdb1d4019,0x7bc8ef51}}, // _dady_, ltís, _mesí, dadu,
+ {{0x6442cf52,0x6edb8290,0x2246817b,0x2909004e}}, // _cloi, _निपु, chok_, _osaa_,
+ {{0x97ea8686,0x6e3bcf53,0x26cd829f,0x44316163}}, // ıştı, rkub, _uveo_, _koz_,
+ {{0x629c6f54,0x27e90f55,0x6e29cf56,0x201ea037}}, // _xiro, _acan_, sjeb, _gnti_,
+ {{0x660bccd1,0x29090096,0x7d044f57,0xb5fb41e9}}, // _tagk, _asaa_, zwis, _ajám,
+ {{0xd838a0a9,0x2fc5a3e0,0x7d0440c2,0x765980fd}}, // _viče_, _helg_, ywis, niwy,
+ {{0x515600fb,0x69c40f58,0xeaaec07a,0x7bc9c187}}, // утну, _veie, _ый_, maeu,
+ {{0x7bc8ef59,0x25adc0b6,0x2a79405d,0x60cd4f5a}}, // cadu, lcel_, _aksb_, dsam,
+ {{0x629d4ea3,0xbb4326bb,0x15432143,0xa2ca87d9}}, // _biso, берк, берм, _सौन्,
+ {{0xdb18eaca,0x7d044f5b,0x60da6277,0x2a786742}}, // _bevæ, twis, _åtmi, _skrb_,
+ {{0x69c52f5c,0x27f24f5d,0x765981cd,0x644400ce}}, // [9e0] _fehe, rfyn_, diwy, _ilii,
+ {{0x1a062aab,0xc0062d2b,0x629ee5df,0x1615c4ef}}, // _спом, _спок, _èpoc, _दोसर_,
+ {{0xdb1c6f5e,0x91a90f5f,0x28c68216,0xb0c68064}}, // _terí, _hoá_, रीनि, रीनग,
+ {{0xdd8f61ac,0xf7464407,0x7bc8ef60,0x50db200b}}, // سون_, _бемо, zadu, _אקטא,
+ {{0x4420048d,0xda7aa4c9,0x629e2f61,0x69c52f62}}, // _mni_, няк_, _kipo, _yehe,
+ {{0x7bc6400e,0x69c52108,0x9985e013,0xf41f0052}}, // _heku, _xehe, nklų_, lmät_,
+ {{0x6442c12a,0x9c14c125,0x7658a163,0x7bc8ef63}}, // _ploi, _dọny, rivy, vadu,
+ {{0xdb1aa510,0x60dbcf64,0xb5fb41e9,0x44cee013}}, // _cetá, frum, _ajáj, vę_,
+ {{0xdb0446c7,0x216781e3,0x0524a07c,0x8966804f}}, // mbió, _бити_, বনের_, _скаж,
+ {{0xe45f2277,0x64440f65,0x64a6a0ff,0x7ae0cf66}}, // rsök_, _alii, _бажа, rumt,
+ {{0x9c7d006a,0x2d98ef67,0x68e1ef68,0x64440153}}, // jučc, äre_, guld, _blii,
+ {{0x629e2f69,0x44200f6a,0x71f7e043,0x44ceeb6b}}, // _aipo, _cni_, پریس_, rę_,
+ {{0xb4c10010,0x44ee6261,0xade60046,0x7ae2803b}}, // ुळे_, hý_, _कारन_, nuot,
+ {{0x629e2f6b,0x25e1040a,0x2a79405d,0x629d4f6c}}, // _cipo, _काकी_, _sksb_, _siso,
+ {{0x3317e1b3,0xb87b21af,0x2139000e,0x44316f6d}}, // _مزيد_, guía, sysh_, _roz_,
+ {{0x44ee6066,0x36d5404e,0x69c52f6e,0x7e644c57}}, // dý_, иобр, _wehe, gnip,
+ {{0x7ae2803b,0x69caa97e,0xb9994f6f,0x200eef70}}, // [9f0] juot, kafe, твах_, _kafi_,
+ {{0x629e2f71,0xb05b2277,0x3dc6c05d,0x7c208030}}, // _gipo, gsät, _leow_, _anmr,
+ {{0xdb0e6105,0x61e98cb1,0x2be8a5cb,0x629f041a}}, // _gebü, _pcel, च्छू_, _épos,
+ {{0x39158acb,0x27e68f72,0x44316f73,0xf42a0052}}, // имер, ggon_, _woz_, mpää_,
+ {{0xf21a212f,0x765aef74,0x998ca04a,0x7ae28013}}, // _फोड़_, kity, _bodů_, guot,
+ {{0xa2c18295,0xdb18e04e,0xe80d4026,0x645560bb}}, // रीष्, _kevä, ाएका_, _umzi,
+ {{0xaa9580ff,0xf625c09e,0x63bbc4d9,0x6a788322}}, // _вивч, идко, tbun, rífr,
+ {{0xd1bb8555,0xa2a2639f,0x61e98f75,0x7bc7600f}}, // _سارا_, गदर्, _ucel, _heju,
+ {{0x44d16f76,0x87e7a4aa,0xdb1c601f,0x53478f77}}, // lą_, _مثال, _terã, _схва,
+ {{0x28dc0bc5,0xdb0d0057,0xa2dd40aa,0xe29f40c4}}, // _बिबि, _neañ, _पिन्, _lið_,
+ {{0x60260f78,0xafdb0065,0x8bd5c1ac,0xd8260470}}, // адна, dkøb, _نقاش, адни,
+ {{0x443ee200,0xd756c050,0x9f58265f,0x5c560f79}}, // akt_, الیت_, verà_, ртоф,
+ {{0x442dcf7a,0x765ae5fb,0x657e2f7b,0x64896361}}, // mje_, bity, _syph, _džiz,
+ {{0xafe2e0c0,0x6603af7c,0x44200403,0xa2d70d88}}, // _дошл, nenk, _wni_, _बिर्,
+ {{0xb779e49c,0x5d79e49c,0x7ae2803b,0x2aab8065}}, // _באַש, _באַק, zuot, _løb_,
+ {{0xb33ca17a,0x200fc187,0x68434f7d,0xfe434f7e}}, // _jgħa, _jagi_, онса, онсо,
+ {{0xd007433f,0x63a40d72,0x237f8286,0x63a9006e}}, // [a00] шете_, _igin, _nyuj_, ženc,
+ {{0x28ddc8a1,0x41b66aa5,0x248d81e7,0x2fc7e43d}}, // _मिनि, рсат, _ahem_, _jeng_,
+ {{0x2fc7ef7f,0xdb052065,0x09066f80,0x4fc6803b}}, // _meng_, _afhæ, рпен, асла,
+ {{0x7ae28f81,0x68e3af82,0xdb1c6359,0x249f8820}}, // tuot, dund, _cerá, _cium_,
+ {{0x69c76f83,0xc5fee07c,0xddc1e06e,0x7e6dc098}}, // _feje, ্ঞতা_, dolž, čapl,
+ {{0x442dc064,0x2c1c6064,0x38ab8042,0xaa466f84}}, // eje_, नाओं_, _dør_, _тегл,
+ {{0x7bcaaf85,0xdb0bc765,0x200ee0e8,0xdb1d401f}}, // rafu, _segð, _rafi_, _tesã,
+ {{0x10a36bff,0x60c2c0d1,0xb5fb03e4,0x8cd70021}}, // _фитн, _pwom, gnál, _बिलो,
+ {{0x5186c139,0x3ead6098,0x7a35c97c,0x2338c04e}}, // шука, _četi_, _pátr, уппы_,
+ {{0x2ca7ef86,0x7afe20ff,0xa2db807d,0x69da2be3}}, // _bund_, _oppt, _नित्, ótel,
+ {{0xe8df4067,0xd838c06e,0x09b4a466,0x273ccf87}}, // _tiệc_, _luči_, ुरीय, _bánú_,
+ {{0x63a2c296,0xdb19c0cb,0x6251803a,0x6448ef88}}, // _ugon, _bewä, sťov, shdi,
+ {{0x200ee068,0x6445205f,0x9989004d,0x2246cf89}}, // _tafi_, _plhi, _boaš_, _ilok_,
+ {{0x442241f3,0x5fe26960,0x68e44f8a,0x291a207d}}, // _ank_, _पागल, duid, _brpa_,
+ {{0x629f0037,0x7bc76f8b,0x660f4f8c,0xdb1e6134}}, // _tiqo, _reju, _pack, napä,
+ {{0xb87b2017,0x92b3e711,0x29194f8d,0x9f5ca04a}}, // dríe, _بحوا, _prsa_, jeví_,
+ {{0xf1bf0f8e,0x20032f8f,0xf1a76c4e,0x765c2f90}}, // [a10] mbá_, peji_, арен, biry,
+ {{0x2004cf91,0x7bcb8d0d,0x683cce41,0x74bdef92}}, // hemi_, wagu, _dédi, वीकृ,
+ {{0x753d00e0,0xd041c03c,0x245944a7,0x80f56f93}}, // lysz, _evlə, _бань_, спех,
+ {{0xf39101e9,0x44224f94,0x2004cf95,0xa2db8bed}}, // _aṣia_, _gnk_, jemi_, _निद्,
+ {{0x93fa4095,0xa28264b7,0x3b6441e1,0x2ee4c422}}, // _גלרי, _کیلو, _дърв, jumf_,
+ {{0x645d0f96,0x44d16f76,0xa969a8df,0xe81b45cb}}, // hisi, pą_, лила_, _पोता_,
+ {{0xd49b831a,0x7bcb8f97,0xd5aea085,0x38668f98}}, // _ара_, pagu, رفي_, bnor_,
+ {{0x7d0d0f99,0xccf26095,0x69c8a054,0x645d0f9a}}, // _osas, _מכל_, _eede, jisi,
+ {{0x645c2f9b,0x765d0f9c,0x7bc8a0c7,0x98a3c03b}}, // yiri, disy, _fedu, ąjį_,
+ {{0x3e7ba004,0x4a45ef9d,0x79e7e0e0,0x60cd8098}}, // rêts_, снов, جودہ_, šams,
+ {{0x44262067,0x3f860f9e,0x320fc066,0x7d1b8f9f}}, // _đo_, _šou_, _tagy_, _hrus,
+ {{0x645c2fa0,0x2004c04a,0x68384066,0xdb1522a8}}, // wiri, cemi_, _lídr, hazó,
+ {{0x8e74c13a,0xaad4c295,0x1474c13a,0xed5982f9}}, // _بالض, _दिएक, _بالج, _beži_,
+ {{0x291a216d,0x711ac095,0xdb0281ae,0x261808ae}}, // _prpa_, _תוספ, scoç, _बोरी_,
+ {{0x61fb8fa1,0x62828fa2,0x9c7d029f,0x6efd0156}}, // _obul, yloo, mučn, _påbö,
+ {{0xf8a58050,0xb87b21de,0x442360ca,0x6f1b8fa3}}, // _يک_, quín, _anj_, _oruc,
+ {{0x20124006,0x9ce821a6,0x66060fa4,0x61f64fa5}}, // [a20] rdyi_, খবেন_, mekk, rfyl,
+ {{0x09d08010,0x232a645c,0x32120038,0xa3be27f4}}, // हण्य, лози_, _hayy_, आरओ_,
+ {{0x2004cfa6,0x69ce2fa7,0x1fb562eb,0x7bce2fa8}}, // yemi_, mabe, юстр, mabu,
+ {{0x2005efa9,0x9f59407b,0x2ca9005f,0x03268691}}, // heli_, _casó_, _yuad_, йден,
+ {{0x21d866b0,0x66e3827c,0x20120faa,0x7ff7410c}}, // _نژاد_, зоча, _mayi_, _اسپا,
+ {{0x20116fab,0x7d1b8fac,0x62844732,0x62828fad}}, // _gazi_, _drus, llio, sloo,
+ {{0x645d0fae,0xfce361fc,0x64b76098,0xb3a98602}}, // yisi, _норо, _aćif, ğırı,
+ {{0x09e1207c,0x6283a88c,0x69ce2faf,0xbb3b600b}}, // ম্পা, alno, habe, _געלי,
+ {{0x80d8c010,0x2ee041cd,0x66060b80,0x62844fb0}}, // _मिळे, nrif_, dekk, ilio,
+ {{0x27e94018,0x645d0fb1,0x9411603c,0x9c7d053d}}, // zgan_, wisi, _bizə_, gučn,
+ {{0x2717e081,0xbb86a13a,0x6e360403,0x645aafb2}}, // _căn_, _الاي, _boyb, _imti,
+ {{0x78a1a200,0x6441e31d,0x3ea24064,0xe0df065f}}, // _zilv, akli, _nikt_, trò_,
+ {{0x2ca903d2,0xd85161e9,0x9f5ee2a8,0x7bce2f3d}}, // _quad_, _bọ̀_, metí_, fabu,
+ {{0xdb1e2510,0x4d7bc00b,0x24446156,0x769f01e9}}, // _sepá, _ערגע, _sömn_, _bàyì,
+ {{0xce6b4c14,0x2bf7e1a9,0x644b600e,0x64428052}}, // град_, _המון_, ëgim, kkoi,
+ {{0x8c3d812a,0x6e240187,0x38ae20c4,0xbddb4187}}, // _mişc, _gnib, _dýr_, _ilèk,
+ {{0xdd944056,0x645aa194,0xb87b2057,0x2ee040fd}}, // [a30] заты, _omti, quío, grif_,
+ {{0x9f5a2fb3,0x9f5940f9,0xbfed4079,0x6aad43ac}}, // _capó_, _basò_, _ṅụọ_, rqaf,
+ {{0x61e9cfb4,0x6aaae854,0xb5fb0066,0x1635414a}}, // ygel, _juff, piád, _деня,
+ {{0xb9016180,0xa5f98fb5,0x9f5ee04a,0xdd7be1a1}}, // _दि_, реду_, jetí_, נטאל,
+ {{0xa3ea8d0b,0xbcfb49fe,0xab298fb6,0x7ae566eb}}, // _मार_, _exér, рола_, suht,
+ {{0x7bcae089,0xdd314064,0x69caefb7,0x6f1b8fb8}}, // _nefu, zęśc, _nefe, _vruc,
+ {{0xd1bb8049,0xc2126557,0x1a29cfb9,0x7ae72089}}, // _ماذا_, _קהל_, ижки_, nujt,
+ {{0x7bcf0fba,0x26dfc2a8,0x81c1207c,0xeaf886b0}}, // jacu, truo_, ুলি_, _گرفت_,
+ {{0x6aaae105,0xe733825b,0x69ce2fbb,0xaca38067}}, // _auff, _عصر_, yabe, _trốn,
+ {{0x66060fbc,0x7982c08b,0x99488049,0x25a6c187}}, // wekk, _dyow, _الكل_, _egol_,
+ {{0x291ce40c,0x2461403b,0x764d4174,0x6aaaefbd}}, // _crva_, nėms_, khay, _cuff,
+ {{0x28f9c04e,0x61eb8fbe,0x645e6fbf,0x28e0839f}}, // _ведь_, nggl, zipi, _निमि,
+ {{0x764d4fc0,0xfe42c0ff,0xdb01a065,0x644021ae}}, // dhay, _іншо, _aflø, êmio,
+ {{0x80b04029,0x7e7bcfc1,0x44f1efc2,0x69cbcfc3}}, // _अंबे, loup, pá_, _hege,
+ {{0x7643a88e,0xf21ec033,0x2bfba029,0xf3f9c12a}}, // ikny, _मोड़_, ल्यू_, enţi_,
+ {{0x7c240fc4,0x1ae600ba,0x69cf028e,0xb81a2a08}}, // _unir, _доим, cace, _फोरम_,
+ {{0x75d3413a,0x44c7e0e0,0xd838c143,0x6442800a}}, // [a40] بيقا, lő_, _guču_, vkoi,
+ {{0x386d8fc5,0x387f8fc6,0x69cae03e,0x628441e4}}, // _ajer_, _akur_, _xefe, slio,
+ {{0x25eb64d6,0x2beb6219,0xe7eda010,0x69c1efc7}}, // _चारी_, _चारू_, च्छा_, ible,
+ {{0x2bb4aa82,0x4425afc8,0xdb1ae3d1,0x7983e11e}}, // ुर्थ, _anl_, ratø, _mynw,
+ {{0x6b83eb48,0x491d00a8,0x7e7bcfc9,0x2007a1fb}}, // _lyng, मियो_, doup, feni_,
+ {{0x76444fca,0x69cf0fcb,0x69d9cfcc,0xdfd260eb}}, // lkiy, zace, _adwe, _زير_,
+ {{0xf84ace14,0x78a2cfcd,0x7c06813a,0x60cd804d}}, // ичий_, _piov, _وبرك, šamp,
+ {{0x27e0a3df,0x3ea2c277,0x386d8488,0x66164fce}}, // şin_, ökt_, _gjer_, ndyk,
+ {{0x44386fcf,0x3218c026,0x645c6037,0x444465b2}}, // _kor_, ěry_, _kmri, ok_,
+ {{0x3207a6c4,0xdb1c6052,0x69c28fd0,0xb87b2057}}, // ceny_, _terä, lboe, tuím,
+ {{0x7644435f,0x64444052,0x7e7c2d53,0x6b840fd1}}, // kkiy, kkii, dorp, _ayig,
+ {{0x69c28f10,0x64a6c0db,0x7e7bcfd2,0x2fcca098}}, // nboe, _дада, coup, _hedg_,
+ {{0x22490fd3,0x1a9b849c,0x2beb6010,0x62560066}}, // _vlak_, ייטע, _चालू_, yšov,
+ {{0xa5bb4e1c,0x9f5a20d1,0x290b00c2,0xb0660052}}, // _enól, _rapò_, rwca_, ttää,
+ {{0x644ae037,0x6d40c02d,0x69c281cf,0x2ca36171}}, // _ilfi, lyma, kboe, _rijd_,
+ {{0x68e28fd4,0x7c38e156,0x7ae28fd5,0x3207a1ce}}, // krod, _hovr, krot, zeny_,
+ {{0x7c38e0e4,0x4426cfd6,0xfbab609e,0xe8df6016}}, // [a50] _kovr, _mno_, атей_, _thừa_,
+ {{0xf8bf07a4,0xb87b21af,0x3255284f,0x644d4fd7}}, // mpé_, bría, _хвор, shai,
+ {{0x7bcd0037,0xdb078057,0x29024012,0x3207afd8}}, // _keau, _ámús, _epka_, veny_,
+ {{0x6d40c03b,0x645c6fd9,0x69c2823c,0x5a3587ca}}, // kyma, _emri, gboe, янет,
+ {{0xb5fdc29f,0x444461e2,0x069b6050,0x25a9812d}}, // niše, bk_, _نخست_, žalo_,
+ {{0xd126e13a,0x26c90167,0x7d0d4fda,0xfaa5afdb}}, // _كم_, _mwao_, mwas, пако,
+ {{0x3ffc249c,0x28ddc4ef,0x78a3efdc,0xdb18a6df}}, // יפגע, _मिहि, _rinv, cavü,
+ {{0x7d02c049,0xe68e403c,0x6449812a,0x27e960ba}}, // _bpos, _məşğ, _ulei, şină_,
+ {{0x6615603b,0xddc3e04d,0x6b83efdd,0x7bcbcfde}}, // _kazk, _ajnš, _syng, _vegu,
+ {{0x1dcb6292,0x1bf2a0c2,0xb5fdc16d,0x6abb8079}}, // िरित, _घायल_, diše, _ituf,
+ {{0x44394fdf,0xa5bb407b,0x68e8efe0,0xfbcf8466}}, // _oos_, _anóm, gudd, _हजाम,
+ {{0x80b3007c,0xdb1260e0,0xdb07600e,0x6d40cfe1}}, // _জিজ্, égév, _agjë, byma,
+ {{0x09e3607c,0x3ea5a0ca,0x6aa3e0f7,0xa2dd4077}}, // ন্যা, _kilt_, _tinf, _पिष्,
+ {{0x44d5e018,0x25fd6914,0xe73a220e,0x3af56156}}, // pā_, र्मी_, бег_, kåp_,
+ {{0x8c4600ba,0x3ebfc07d,0x38b1a009,0x291e2fe2}}, // _неке, lput_, _jár_, íta_,
+ {{0x6aad0fe3,0x2167201f,0x09e3607c,0x673ce1dd}}, // _fuaf, чиси_, ন্মা, ärje,
+ {{0xdcf4a422,0x62872fe4,0x38b1a13a,0x38604054}}, // [a60] _đačk, bljo, _lár_, riir_,
+ {{0x5ed3607c,0xd91b000b,0x8b96645c,0x3af44262}}, // _সহজে, _פויל, преч, räp_,
+ {{0x44394fe5,0xaca44088,0x7a3cc004,0xc43b2095}}, // _fos_, _ahụt, _métr, _התיי,
+ {{0x3a3a2551,0xb7eac028,0x3ea5a049,0x44d7a12a}}, // _hopp_, _टाइम_, _ailt_, dă_,
+ {{0x66160fe6,0x69c28b83,0x1a9b400b,0xb05b210a}}, // _mayk, rboe, _ליטע, späe,
+ {{0x7bc28fe7,0x24868fe8,0x82d7000b,0x80d0a07c}}, // sbou, slom_, לונג_, সংস্,
+ {{0x7e60cfe9,0x7ae28fea,0xa7fb0057,0x6608e271}}, // timp, srot, hiña, vedk,
+ {{0x54b8cf4a,0x7ae8e249,0x200a2feb,0x68e8e11d}}, // огия_, vudt, hebi_, vudd,
+ {{0x291f800e,0xdee38312,0x443a2fec,0xdd90c062}}, // _grua_, _поти, _oop_, کوک_,
+ {{0x68e8e035,0x2eb5871b,0x7bce60eb,0x2fcd805f}}, // tudd, _ісус, _jebu, _zeeg_,
+ {{0x7d03e037,0x2fcd8022,0x44d86f50,0x55bba076}}, // _cpns, _yeeg_, nč_, _המבו,
+ {{0x60d64fed,0x765e2fee,0x61ed4025,0x7c39c401}}, // ksym, _ampy, xgal, _gowr,
+ {{0x6e3aa0f7,0xfaf7c8b5,0x3ce94286,0x81cc01a6}}, // _kotb, _فروغ_, xuav_, _লাশ_,
+ {{0xbeed0fef,0x76892069,0xdca3eff0,0xe81d0ff1}}, // _जमीन_, mþyk, _зачи, _बोरा_,
+ {{0x7c3aaff2,0x443a2846,0x68e4410a,0xd7cc8010}}, // _motr, _dop_, erid, ाराच,
+ {{0xf1bfa016,0x6aa6405d,0x672162c9,0xaa584fb5}}, // _đám_, _cikf, _šljo, пису_,
+ {{0xa7fb04fa,0x6fc30e35,0xa2d708ae,0x7a3daff3}}, // [a70] ciña, _व्यं, _बिट्, _mètr,
+ {{0x929da064,0x9f404069,0x768b6121,0xddc8e098}}, // _opła, rfið_, rüyo, hodž,
+ {{0x9f5eaff4,0xddc8e098,0x60c98aaa,0xdb1aeff5}}, // _mató_, kodž, _swem, mató,
+ {{0x3cddc033,0x9cd62076,0x2cad8071,0x81b000a2}}, // _खिले_, _קורה_, _pued_, _kaɓa,
+ {{0x3eaeeff6,0x6609cff7,0x69cf4018,0xdb156071}}, // _luft_, veek, _iece, _pezó,
+ {{0x6fbf2077,0xfaf8e018,0x81b000e8,0x2fcd83c5}}, // ्रिं, ktī_, _maɓa, _weeg_,
+ {{0x3ea5accd,0x7a3cc004,0x200b006f,0x61fbcff8}}, // _vilt_, _rétr, keci_, tful,
+ {{0x3cdea12f,0x27ff813b,0x7ae9c108,0xdb02cff9}}, // _गिरे_, _ubun_, tuet, _egoí,
+ {{0x14ddc010,0xd5b1a067,0x7d1c249f,0x2ca6c0fd}}, // _मिळण, _các_, zvrs, _diod_,
+ {{0x6e3b8ffa,0x69cf4ffb,0x7c3b8ffc,0x7ae9c108}}, // _houb, _lece, _hour, ruet,
+ {{0x491447a7,0x764d0157,0xc7b2a095,0x7d1bc59f}}, // _नैनो_, _blay, _סבא_, svus,
+ {{0x7ae48b53,0xc1c628b1,0x9c7d00e4,0x644d0ffd}}, // šite, वर्ग, trča, _clai,
+ {{0x386dc8ef,0xd1b8c8b5,0x64bae13e,0xa2c1804b}}, // mner_, _رانا_, _aċid, रीच्,
+ {{0x7c3aa057,0x68e44ffe,0xe8df4119,0x7ae44fff}}, // _xotr, vrid, _chọc_, vrit,
+ {{0xb5fb0803,0x69c45000,0x69cf5001,0x63833002}}, // vnáv, tbie, _bece, вгра,
+ {{0x7c3b9003,0xb5fb4057,0xb87b213a,0xbf2820f9}}, // _nour, _amáb, brío, _aṣòr,
+ {{0xd91060d0,0xd5af2043,0x69ce6065,0x6d43a1cd}}, // [a80] گیز_, افہ_, _pebe, hyna,
+ {{0xe29761d3,0x6aa76200,0x7aeb9004,0xff5f0004}}, // дау_, _cijf, dugt, raît_,
+ {{0xf8d22ad3,0x3ea7ec90,0x7c298037,0x68e44090}}, // _तबिय, _mint_, _bner, srid,
+ {{0x2a6dd005,0x5ee0e5cb,0x3ea7ed53,0xdb03e049}}, // jneb_, _किस्_, _lint_, _ngní,
+ {{0x17c7414f,0x2d87e155,0x2ac441e9,0x2d9161b6}}, // _ігри_, _lyne_, _débò_, _ezze_,
+ {{0x38b41006,0x22469007,0x3ea6cc2c,0x6721a0a9}}, // _mär_, skok_, _siot_, _brlj,
+ {{0x212240c7,0xdb052067,0xb5fb0803,0x2bc47008}}, // _hrkh_, _nghì, dián, _ल्या,
+ {{0x9c7d00a9,0x69c5a069,0x3914cf27,0x6721a3ee}}, // ručk, ðher, емир, _drlj,
+ {{0x2ca7e725,0xd7e6e71b,0x5fb260c2,0xd90ea0e0}}, // _bind_, міно, _जलाल, _چیت_,
+ {{0x7c3c6108,0x2bcc83af,0x7e6081e7,0xb5fdc098}}, // _jorr, ारवा, _hmmp, tišc,
+ {{0x386dc2bd,0x7c3b8095,0x81b001df,0xdb1af009}}, // bner_, _your, _saɓa, vató,
+ {{0x6e3b8005,0xb33ca17a,0xb87b2049,0x63adf00a}}, // _xoub, _ngħi, trío, _şant,
+ {{0x6e3c6854,0x629601e7,0x58872052,0x2bbfe180}}, // _oorb, _dhyo, дыва, _श्वा,
+ {{0x36d4e0ff,0xe167c0eb,0x7d1d022e,0x69a02321}}, // _зокр, _تضمی, rvss, _गृही,
+ {{0xdb1af00b,0x7e6d436c,0x442d700c,0x78a8b00d}}, // rató, tnap, _đe_, _midv,
+ {{0x200cf00e,0xd5b800ff,0x3ce6900f,0xdb172071}}, // ledi_, _ось_, krov_, _sexó,
+ {{0x67294098,0xdef8465d,0x61fe6885,0x81ca607c}}, // [a90] _šejb, дых_, ffpl, োলন_,
+ {{0x2a64c1fc,0x200cf010,0x6280cdd8,0x3ce6875a}}, // himb_, nedi_, homo, drov_,
+ {{0xe3b04043,0x6aa76200,0x69c7200e,0x78a76171}}, // ارہ_, _vijf, mbje, _vijv,
+ {{0xddc2823d,0x22842156,0x7bd53011,0x62560098}}, // tnoš, sökt_, lazu, nšop,
+ {{0x386dc042,0x442b5012,0xdee586bd,0xfbd08050}}, // vner_, _inc_, холи, شتن_,
+ {{0x02e08857,0x6e3b8157,0x64906052,0x442a7013}}, // _निरभ, _woub, läin, _gnb_,
+ {{0x7c3b9014,0x63ad1015,0x3ea7e090,0x6722c1e9}}, // _tour, _ogan, _rint_, _aroj,
+ {{0x44f8ee11,0x2ca90022,0x7c2ae105,0x65946734}}, // dé_, _niad_, _anfr, _расу,
+ {{0x64aaa04a,0x25ad9016,0x628521fb,0xd706c052}}, // _přic, _igel_, _okho, еные_,
+ {{0x2ca7e0dd,0x6721a0e4,0x628b9017,0xd9ee210a}}, // _qind_, _trlj, llgo, _जाइत_,
+ {{0x44f8ee41,0x65694066,0x79898939,0x63adf018}}, // gé_, _žehl, _hyew, _şans,
+ {{0x60cd02e0,0x7659819f,0xd838a6ca,0x38b40262}}, // _bwam, _çayı, _riči_, _pär_,
+ {{0xddc45019,0x660d4265,0xa49b61e9,0xc56bc50f}}, // jniš, deak, _apòm, _بحال_,
+ {{0x44f9c163,0x661bd01a,0x6449c052,0x2451c066}}, // mè_, lduk, lkei, _dáme_,
+ {{0x63bb8ee2,0x38b400cb,0x68fbd01b,0x6e3d43c5}}, // _ofun, _wär_, ltud, _bosb,
+ {{0x6449d01c,0x6485c057,0x7e76204d,0x6e3d501d}}, // nkei, cóit, _šapč, _cosb,
+ {{0x7aed501e,0x68fbc9fe,0xadee2010,0x44f9c0d1}}, // [aa0] guat, ntud, _जाऊन_, nè_,
+ {{0x4ad3edf1,0x628b8996,0x69d5301f,0x32194095}}, // _दबाव, elgo, caze, _easy_,
+ {{0x20195020,0x2484805d,0x09d8a07c,0x68fbc046}}, // _fasi_, _pkmm_, সলমা, htud,
+ {{0xdb01f021,0x442b42a8,0x60c28552,0x2ee69022}}, // lcló, _gnc_, tpom, trof_,
+ {{0x69d65023,0x20187024,0x23722041,0x200ce0e0}}, // maye, _tari_, šajā_, yedi_,
+ {{0x2fc69025,0x67240320,0xb87b2057,0x661bc2c7}}, // sbog_, _mrij, buíu, eduk,
+ {{0x201a3019,0x60c287c1,0x1bee204b,0x27e01026}}, // _mapi_, spom, _जाईल_, _odin_,
+ {{0x75241027,0x6618e274,0x64a39028,0xd497c1ba}}, // _oriz, _savk, _баса, нры_,
+ {{0x7bdf0153,0x57cc8026,0x6618e6ca,0x7bd527ed}}, // _rdqu, ारीह, _pavk, yazu,
+ {{0x63a48edc,0x69d65029,0x07a6a19d,0x443ce048}}, // žins, haye, _зазн, _qov_,
+ {{0x6723e875,0xe8026eb5,0x7bd5302a,0x38b64271}}, // _crnj, र्या_, vazu, _lær_,
+ {{0x442cb02b,0xa3c3c260,0x386688cd,0x201a2561}}, // _hnd_, ्रण_, nior_, _aapi_,
+ {{0x661ab02c,0x7524012a,0x4ab54033,0xc43bc095}}, // _katk, _criz, _अंधव, _מתאי,
+ {{0x66040b01,0x7c3d4939,0xe80260ec,0x386685f1}}, // _ebik, _sosr, र्मा_, hior_,
+ {{0x7bc72cad,0x7c2bd02d,0x69d6502e,0xddc9902f}}, // rbju, _yngr, faye, _lješ,
+ {{0x69d53030,0x7bd65031,0xdb0aa156,0x1fcd61a6}}, // saze, gayu, bbfö, _লাইস,
+ {{0xc4c48043,0x6e2d1032,0x747c00be,0x067c01a9}}, // [ab0] _دے_, _inab, _מנהג, _מנהל,
+ {{0x2bee2064,0x998dc23d,0x83fdc0e0,0x661ab033}}, // _जाएं_, nješ_, lnőt, _natk,
+ {{0x5b2461ba,0x7c2d068f,0xf12461ba,0xc885e105}}, // льца, _knar, льцо, äßig_,
+ {{0xddc9816d,0x22b82299,0xdb1aa019,0x3cea404b}}, // _bješ, _učka_, _letó, _चमचे_,
+ {{0xcc342896,0x64482277,0xa2cd4ca9,0x661d0109}}, // _شریع, ödig, दीश्, idsk,
+ {{0xa509a90f,0x78a98105,0x68e323d1,0x98be2013}}, // дека_, _wiev, _ånds, štą_,
+ {{0x44faa0dd,0xa1946197,0x93c6412a,0xa3d540c2}}, // gë_, _барч, sfăş, _सजा_,
+ {{0xa295803b,0x6e2d1034,0xaca3a1e9,0xdb03e3f6}}, // _рабі, _nnab, _ipọn, _ngná,
+ {{0xdb1aa06f,0xa3b94969,0x661bd035,0x32cf40c2}}, // _betó, _चलन_, rduk, nży_,
+ {{0x6723e3ce,0x6485c13a,0x644ab036,0xdb1aa057}}, // _prnj, góir, ckfi, _cetó,
+ {{0x29090037,0xc7b261a9,0x44f9d037,0x69d65038}}, // _apaa_, יבי_, sè_, yaye,
+ {{0x6281f039,0x3f6a103a,0x443eb03b,0x6485c13a}}, // solo, димо_, _zot_, róis,
+ {{0x7bc9d03c,0x249fd03d,0x3776087c,0x60c4e07f}}, // mbeu, mmum_, тынс, _čime,
+ {{0xc0e2c618,0x2d8c0041,0x6723e6ca,0xa2e62052}}, // _тошк, āde_, _trnj, когд,
+ {{0x6b63303e,0x6715e12f,0x7c3e206f,0x69d64a97}}, // акра, _तनिक_, _vopr, taye,
+ {{0x6604103f,0x6283b040,0x3dc94114,0x3eaa617a}}, // _ubik, hono, gbaw_, _sibt_,
+ {{0x93434b92,0x7bd65041,0x23d1a064,0x6e3e3042}}, // [ac0] анте, rayu, हराद, _topb,
+ {{0x64a63043,0x443f8653,0x0715e66f,0x68e9cc1e}}, // _шапа, _aou_, _तनाव_, ired,
+ {{0x7bd64a85,0xd8770043,0x6493403c,0x69cee0c4}}, // payu, _سمیت_, mçin, ðbei,
+ {{0x076a833e,0x91e69044,0x5ee90148,0xdee37045}}, // وماً_, тоде, _जिम्_, роци,
+ {{0xd5b1a119,0x3ea05046,0x69c9d047,0x2ca04046}}, // _cáo_, mmit_, dbee, mmid_,
+ {{0xb7d8a049,0x76ac6106,0xdcf5c04a,0x975b0076}}, // روبا_, _eşya, ůběh, ודנט,
+ {{0xa3c1a5cb,0x200ea1cd,0x81d901a6,0xe8042e8e}}, // ंड़_, refi_, ালত_, श्या_,
+ {{0x442d858f,0x8c43804d,0xe4e3a0a8,0x38c8a0f9}}, // _gne_, _кесе, _गिरि_, _aìrò_,
+ {{0x66029048,0xb505801e,0x6aaae944,0x7afd0156}}, // mfok, _مظلو, _piff, xtst,
+ {{0x22f78095,0xad259049,0x44faa00e,0x63bc21e4}}, // _בזמן_, _حرفو, pë_, jcrn,
+ {{0x3958a536,0xd345a050,0x661b904a,0x2ac8a1e9}}, // _års_, _میوه_, _yauk, _dìbò_,
+ {{0x2ee9504b,0x98aa403b,0x3ce9904c,0x60daf04d}}, // yraf_, mybė_, šave_, rstm,
+ {{0x661d104e,0x764081e7,0x60dbc143,0x5fb26046}}, // rdsk, _momy, csum, जशाल,
+ {{0x6486e069,0xdb1c704f,0x2ca04046,0xe2982362}}, // rðis, _berü, emid_, каў_,
+ {{0x26c25050,0xadf7e13a,0xc7c7039d,0xdb0440e0}}, // _atko_, _شروط_, _испи, kció,
+ {{0x3ce940ca,0xbbcc8026,0x64409051,0x3ea04114}}, // trav_, ारोक, _nomi, gmit_,
+ {{0xdddc616d,0x6283b052,0x6724c00a,0x7d18e04d}}, // [ad0] _skrš, yono, _šijj, _hsvs,
+ {{0xa2b0027d,0x2ee95053,0xfaff000e,0x1d6b633e}}, // ंदर्, rraf_, mtë_, وصاً_,
+ {{0x6ad1a046,0x201cf054,0xd3a7684f,0xd25147f0}}, // _सबेर, _mavi_, трап, _هند_,
+ {{0x62806057,0x67220143,0x7c2e615a,0x6d1e9055}}, // _ímon, _šoji, _enbr, पिंग_,
+ {{0x81d6207c,0x91e5a01b,0x69d8b056,0x629aa012}}, // _হাত_, голе, fave, _bhto,
+ {{0x69d8ae04,0x661c7057,0x2006c057,0x7ae9d058}}, // gave, _gark, _aboi_, vret,
+ {{0x39494cc5,0x64409059,0xa01b0851,0xdddc62d1}}, // lyas_, _fomi, nlös, _ukrš,
+ {{0x80d2807c,0x644d505a,0x3a2d8d5e,0xae1be98a}}, // _সমর্, nkai, _unep_, _भोजन_,
+ {{0xe800b05b,0x8cbb8050,0x2717a03c,0x201123cf}}, // ल्हा_, _آدرس_, _mənə_, kezi_,
+ {{0xa3c3c39f,0x62856066,0x7bc9c0f0,0x248dd05c}}, // ्रह_, noho, sbeu, slem_,
+ {{0xa06a0cdd,0x798d0650,0xe1ff007b,0x248dd05d}}, // еана_, _ayaw, rión_, plem_,
+ {{0x661d505e,0xddd6405c,0x6285632c,0xdb0565e0}}, // _nask, chyň, hoho, lchó,
+ {{0x3869505f,0xdb1d4019,0x6b8d00b1,0x6f074018}}, // diar_, _mesó, _cyag, nībā,
+ {{0x9f582197,0x39494dce,0x7bd99060,0x2cad9061}}, // derò_, dyas_, hawu, _lied_,
+ {{0x7c2f013a,0x610aa03c,0x201dd062,0xd00a412a}}, // áirí, _gəld, _kawi_, неве_,
+ {{0x3dcd607c,0x8c3d812a,0x70d1a026,0x62898088}}, // _লাগল, _fişi, _सबैल, _nkeo,
+ {{0x69d980bb,0x6d5bc3e8,0xb87b3063,0xed57401f}}, // [ae0] dawe, nzua, ruír, гот_,
+ {{0x661c7064,0xed5a621a,0xc1cc9065,0xdb1af066}}, // _qark, ноз_, ार्ग, ratö,
+ {{0x3ead8067,0x9d462e1a,0xd9462ae2,0x7bd57067}}, // _biet_, _сенд, _сени, _dezu,
+ {{0x201dc058,0x69d8a1b7,0xa2cb0077,0x629b9068}}, // _nawi_, wave, तीक्, _chuo,
+ {{0xa06aa520,0x6e3bc098,0x7e69c14e,0xa3b9410a}}, // нада_, djub, diep, _चलत_,
+ {{0x764e3069,0xd252633e,0x26dca00a,0x9873aa0e}}, // nkby, _انس_, tsvo_, бліц,
+ {{0x6722880f,0x4420506a,0x3eaca042,0x60c40da8}}, // svoj, odi_, _vidt_, _ltim,
+ {{0x69d5706b,0x61e4106c,0x8fa6906d,0x2bc060ec}}, // _zeze, _ndil, _бабе, शुरा,
+ {{0x3254e1fc,0x78a28425,0x6aad06f9,0xa2dd4028}}, // ивир, mmov, _riaf, _पिछ्,
+ {{0x2d8cb06e,0xdb04403e,0x6d48f06f,0x291a3070}}, // _tyde_, nciñ, tyda, _ospa_,
+ {{0x9b94e1fc,0x69d600a0,0x78ad0024,0xa194ec78}}, // ринц, _beye, _piav, ранч,
+ {{0xc3336095,0x6ac3c07c,0x442fc1e7,0x24869071}}, // צור_, _শিরো, _gng_, noom_,
+ {{0x629c605c,0xdd252041,0xf3ff01ae,0x290da0a9}}, // _ohro, dīša, sião_, čkaš_,
+ {{0x9f582024,0x3cf1c066,0xa546e009,0x2717a03c}}, // verò_, ňový_, _مضبو, _sənə_,
+ {{0x6604417a,0xfe7f25df,0xa4d520ff,0xa91d8261}}, // ffik, duït_, робі, _vyži,
+ {{0x7bd57072,0x9f582197,0x24184772,0x2449e765}}, // _sezu, terò_, лосы_, _rúmi_,
+ {{0x629c600c,0xdb1d4057,0x69d56108,0xfaff000e}}, // [af0] _bhro, _resó, _peze, qtë_,
+ {{0x69d98296,0x36d52b1e,0x7d1dc065,0xd37b64c2}}, // wawe, _коор, æssi, нче_,
+ {{0x53352307,0x629c628f,0x98518064,0x57b583d5}}, // _тент, _dhro, nąć_, абет,
+ {{0x6f0d0088,0x9f5c604a,0xa3b9410a,0xa3d65073}}, // _kpac, ývá_, _चलि_, हरण_,
+ {{0x6d5d1074,0xb814e033,0x2919418b,0x67244143}}, // nzsa, तजाम_, _ussa_, cvij,
+ {{0xcb69b075,0x6aaf4143,0xd009abcb,0x201eb044}}, // кале_, _hicf, теке_, _cati_,
+ {{0x7e69cb83,0xe454620e,0x6e3bc6be,0x7a41604a}}, // tiep, _укры, tjub, _vítě,
+ {{0x6d5bc265,0x7aed5076,0x21290381,0x3eb980b6}}, // tzua, orat, _orah_, _ésto_,
+ {{0x6ec1e041,0x44216569,0x7e69c09a,0xddd4017a}}, // _mēbe, odh_, riep, ħaża,
+ {{0x3209036d,0x6aae6048,0xdd252018,0x9a87a12a}}, // _abay_, _xibf, zīša, _субл,
+ {{0x69d60b02,0x7f75e280,0xe29a01e3,0x29004265}}, // _peye, рупц, као_, ztia_,
+ {{0x6442c171,0x38cae050,0xa06a0504,0x7e6b808b}}, // _gooi, کایی_, тама_, nigp,
+ {{0x201ea058,0x62872c48,0xe8e02067,0x624600f9}}, // _yati_, fojo, _kiệt_, _bóor,
+ {{0xb5fdc03b,0x7e66405d,0x69d60194,0x6da5e4c2}}, // kišk, _smkp, _weye, _кила,
+ {{0x9f582069,0xdd252041,0x7aed403a,0xdddc206a}}, // gerð_, tīša, erat, gorš,
+ {{0x6724c391,0x69dbc105,0xdb1c3077,0x629c7078}}, // _šiji, naue, ncré, _shro,
+ {{0x7aed5079,0x248a6037,0xdd252041,0x2d85307a}}, // [b00] grat, _pkbm_, rīša, üler_,
+ {{0x7d16431d,0x6443f07b,0x4420507c,0x9adb01a1}}, // rwys, _joni, sdi_, _שחיט,
+ {{0x7aed507d,0x6f1b8057,0xb5fdc03b,0xe28680ba}}, // arat, _asuc, gišk, алжи,
+ {{0x7bdaf07e,0x44316200,0x6287102f,0x3a20005d}}, // zatu, _enz_, čkog, _jaip_,
+ {{0x2901600c,0x7529907f,0x6442c3be,0x60c97019}}, // atha_, _crez, _rooi, _čeme,
+ {{0xb5fdd080,0x7642d081,0x290ca05d,0x7bd72057}}, // bišk, _sooy, _ppda_, _xexu,
+ {{0xe3e9200b,0xdee365a8,0xfbcfa07c,0x29016049}}, // אַפֿ, _мори, _রাখত, ctha_,
+ {{0x5a34c3d5,0x6d4b9082,0xe81468c6,0xd90da6ec}}, // снит, byga, ड़वा_, عین_,
+ {{0x25fd6914,0x6247c071,0x610aa03c,0xb4e50292}}, // र्की_, _años, _qəlb, _पटे_,
+ {{0xa2e6a63e,0x64440090,0x3eb87083,0x6272a0c2}}, // _вожд, _aoii, _kurt_, dłog,
+ {{0xa3d768ae,0x7643e4b7,0x2fd8612a,0x62873084}}, // सरत_, _dony, _merg_, vojo,
+ {{0x7c209085,0xe704c050,0xe4db8026,0xd186c4e4}}, // _jamr, _هستی, zšíř, _клей,
+ {{0x44320026,0x27e6c35f,0x7c209086,0x64960049}}, // _dny_, _idon_, _mamr, páin,
+ {{0xea01c016,0x248ca17b,0x7ae2d087,0x68ed5088}}, // _đẩy_, _jkdm_, _ivot, vrad,
+ {{0x7e7de2a8,0x6e3ce277,0xa3e6804b,0xb05b2052}}, // éspe, örbu, पणा_, npäi,
+ {{0x200a6c65,0xd378e04d,0x877ba053,0x6e228143}}, // _abbi_, gaće_, דאלי, jdob,
+ {{0x451ca07c,0x291e629f,0x7aed5089,0x26c52013}}, // [b10] থমিক_, _štap_, urat, ūlo_,
+ {{0xb5fdc03b,0x6443f08a,0x270ea03c,0x78b8e106}}, // tišk, _xoni, _kənd_, _kuvv,
+ {{0x3eb86017,0xfbd22095,0x62584049,0x6459908b}}, // _curt_, _מתי_, _díob, thwi,
+ {{0xd378e143,0x7ae2c133,0x24542262,0x23bd4031}}, // caće_, _ovot, _jämn_, _dájó_,
+ {{0xa2cb0010,0x6445259f,0x64960049,0x386900a2}}, // तीच्, _kohi, dáil, _mmar_,
+ {{0x6288e037,0x7bc2c005,0xdd282041,0x66152265}}, // jodo, _afou, lēša, nezk,
+ {{0x999a2041,0xc2126095,0x2903308c,0x3669d08d}}, // _kopš_, _מהם_, ntja_, лако_,
+ {{0x7bdc308e,0x7529908f,0x3eb95090,0x260f6028}}, // zaru, _trez, _kust_, ड़की_,
+ {{0x7bdd1091,0x4bd9e823,0x7d1b9092,0x69dd1093}}, // dasu, льня_, _usus, dase,
+ {{0x98c622e2,0x27e6c163,0x6b9b9094,0x2ab761f6}}, // _žuči_, _edon_, _izug, _aħbi_,
+ {{0x4432007d,0x4c8602a3,0x249f9095,0x29012022}}, // _pny_, йлов, _khum_, _nqha_,
+ {{0x7a3f00dd,0x229903e4,0x237dc05f,0xb9208096}}, // _këty, téka_, _txwj_, _narị_,
+ {{0x645bc256,0x8b68c0ff,0x68e9029c,0x7e7ec009}}, // mhui, _київ_, šedn, éppe,
+ {{0xb5fdc35d,0x65c33096,0x2a6dc02e,0xfbc33097}}, // diši, збра, lieb_, збро,
+ {{0x69d9d098,0x394dc0e0,0x2002e497,0x64453099}}, // _kewe, lyes_, əki_, _dohi,
+ {{0x7bdd109a,0xe8e04016,0x69d9d09b,0x44320258}}, // casu, _suốt_, _jewe, _uny_,
+ {{0xbbb84010,0xe29745d3,0xe8146033,0x69d9c07d}}, // [b20] _अलीक, сат_, ड़ला_, _mewe,
+ {{0x68f52e01,0x69dc309c,0xb69b28d4,0xd9f9e077}}, // buzd, qare, ltân, ्भित_,
+ {{0x2ee04004,0x7ae400ff,0x290fc1e2,0x3ea00561}}, // ssif_, _hvit, _lpga_, _khit_,
+ {{0x7ae40488,0x443fcd79,0xfd50e1e9,0xb69b22e5}}, // _kvit, jju_, _aidọ, ntân,
+ {{0x9f4044cd,0xf772413a,0xb9216088,0x09cfa07c}}, // rgió_, _راح_, _kasị_, _রাজা,
+ {{0x9416803c,0x6e228b0a,0x6e20909d,0x6f02909e}}, // _əgər_, sdob, _wamb, rtoc,
+ {{0x6288f09f,0x3d01a064,0x6e21a025,0x7ae2c0e4}}, // vodo, gów_, _dalb, _svot,
+ {{0x6288e064,0x2480c12d,0x68e40987,0xdd1c2066}}, // wodo, čimo_, _ovid, káže,
+ {{0x645c2625,0x236050a0,0xb6a38ae2,0xb05b2052}}, // khri, nzij_, _хитл, späi,
+ {{0xddc3a1fc,0x443fc2f4,0x634ae497,0x7d0450a1}}, // rinţ, aju_, yənd, itis,
+ {{0x994aa50f,0x4614f0a2,0x7c2450a3,0x9f452181}}, // _حلال_, موکر, kdir, ülü_,
+ {{0x628ab0a4,0x877b60be,0x7645207d,0x6d4d50a5}}, // lofo, ראטי, _sohy, yyaa,
+ {{0x7c2450a6,0x644530a7,0x66152265,0x68e290a8}}, // ddir, _pohi, tezk, nsod,
+ {{0xa3cb4010,0x44e3e181,0x7bdaa00e,0x7ae90098}}, // रुन_, bı_, _jetu, šeto,
+ {{0x2600a5e8,0x7e6d50a9,0xd12ff0aa,0x7ea96013}}, // _रानी_, tiap, _эх_, _užpi,
+ {{0x3ea00057,0x2240400e,0x6abaa121,0x628ab0ab}}, // _ghit_, gjik_, _mutf, hofo,
+ {{0xdd282041,0x7d1e20ae,0x66032009,0xeabf4067}}, // [b30] rēša, _esps, _önko, _trùm_,
+ {{0x386dc200,0x5c7530ac,0x7c22ca53,0x44d680c2}}, // zier_, _флот, _laor, dł_,
+ {{0x4422477c,0x104b874c,0xdd9b6bd2,0x984b8297}}, // _fak_, ляди_, уша_, ляда_,
+ {{0x644646ae,0x19a870ad,0x6e22c552,0xb5fdc015}}, // _goki, ступ_, _naob, piši,
+ {{0x70554050,0x68e410ae,0x9239a739,0x2a6dc02e}}, // _هنگا, _xvid, ичку_, vieb_,
+ {{0x442250af,0x7bdf4da8,0xe3bf4071,0x2fdd8022}}, // _zak_, laqu, _dañe_, sawg_,
+ {{0x249f90b0,0x7bd9c17b,0x7c22c030,0x8459c12a}}, // _thum_, _pewu, _baor, ирит_,
+ {{0x09e1e010,0x25e1e021,0x237f8223,0x27e910b1}}, // पण्य, पण्ण, _txuj_, _ndan_,
+ {{0x69daa987,0xe3bf4005,0x09d8a07c,0x7c22c515}}, // _fete, _gañe_, _সারা, _daor,
+ {{0x645bd0b2,0x3d01a064,0xdd1c2066,0x7c2450b3}}, // shui, pów_, váže, zdir,
+ {{0x69d9d0b4,0x2c0b6292,0xbb45e0d7,0x64a60936}}, // _tewe, स्यं_, ценк, жана,
+ {{0x7de640e0,0x7d0440c4,0x60c9807d,0xdb1c6106}}, // _késő, ytis, _ktem, _terö,
+ {{0x7bdaaaf8,0x28b550b5,0xafe5e052,0x60cd423d}}, // _yetu, _अंगि, _долл, jpam,
+ {{0xda0b6033,0x27e910b6,0xe939e0d0,0x69db8aaa}}, // स्मत_, _edan_, _نسبت_, _leue,
+ {{0x764650b7,0x96ba30b8,0x1faa2fb9,0x7bdf400e}}, // _poky, ругу_, ркви_, faqu,
+ {{0x31afc19f,0x31604108,0x7d0450b9,0xe1ff00c2}}, // _gözü_, tziz_, ttis, diów_,
+ {{0xc5d9207c,0x6446403b,0xd378e0e4,0x68e40454}}, // [b40] _তারপ, _voki, maća_, _uvid,
+ {{0x212004b7,0x2d9670ba,0x7c2410bb,0x8b9662d3}}, // _isih_, орес, _hair, ореч,
+ {{0xdd0f2121,0x2905e2a8,0x7bdb8037,0x09d8a07c}}, // mışt, ntla_, _beuu, _সালা,
+ {{0x7c2610bc,0x634ae03c,0x87b9412e,0x6e23f0bd}}, // ndkr, lənc, _гурт_, _lanb,
+ {{0xcaa5813a,0xdca670be,0x69db90bf,0x69dab0c0}}, // أصلي, _мами, _deue, _pete,
+ {{0xab2a70c1,0xe9f90016,0x7529c0e0,0x2246c058}}, // _кога_, _khả_, dvez, _sook_,
+ {{0x7c22c04d,0x7659c1cd,0x81cfe07c,0x261ba026}}, // _paor, _blwy, ষণা_, _यसरी_,
+ {{0x7c2410c2,0x27e050c3,0x260b67f4,0x7c23e996}}, // _nair, nain_, ठ्ठी_, _aanr,
+ {{0x44268e12,0x4974d0c4,0x212d90c5,0x998dc106}}, // mdo_, ялис, _greh_, rdeş_,
+ {{0xb50e6028,0x6e23f0c6,0xa7fce0b0,0x61e9807f}}, // _सहाय_, _canb, lnız, _zdel,
+ {{0x6802c026,0x21200b39,0x7659c0e2,0xb5fb0910}}, // _půjč, _asih_, _flwy, diár,
+ {{0x644290c7,0x69c440c2,0xd7f7c9af,0x6618b0c8}}, // njoi, jcie, _душ_, levk,
+ {{0x8af8003c,0xe3afe0e0,0xddc2c0ba,0xd91000e0}}, // _şərt, ٹری_, _emoţ, نیٹ_,
+ {{0x68e450c9,0x270ea03c,0x09d34077,0x61e0c181}}, // dsid, _gənc_, तर्य, laml,
+ {{0x6e240004,0xcddb45da,0x9f5ef0ca,0x628b85d2}}, // _faib, иња_, retó_, yogo,
+ {{0xa8a44139,0x3ced3019,0xa3d92ef3,0x27e910cb}}, // друк, čev_, ारह_, _udan_,
+ {{0x64476026,0xb5fdc2c9,0xdb0e6031,0xb606a06f}}, // [b50] _poji, mišt, _agbà, _kráľ,
+ {{0x69db8017,0x3f81205f,0xfbd06049,0x7bdf4c1e}}, // _seue, _txhu_, نتم_, saqu,
+ {{0x64476490,0x200120fd,0x69db90cc,0x30158607}}, // _voji, _ichi_, _peue, здер,
+ {{0xb17be00b,0x9d1a2095,0x69dc70cd,0x81d9007c}}, // סטאר, _פורט, _eere, ালক_,
+ {{0x69db8017,0xcfab2062,0xf77088b5,0x80dc61a6}}, // _veue, _حاکم_, پان_, মূল্,
+ {{0x94ba4095,0x44236022,0xbbb92026,0x628b8284}}, // _למשת, _uaj_, _आलोक, sogo,
+ {{0x6e252227,0x201910ce,0x78bd4249,0x6284d0cf}}, // _mahb, kesi_, _husv, čion,
+ {{0x78a2c032,0x787ec041,0x44268054,0x7c23f0d0}}, // _chov, tīva, cdo_, _sanr,
+ {{0x41b5d0d1,0x201910d2,0x61fbd0d3,0x201830d4}}, // _ہمار, desi_, rgul, yeri_,
+ {{0x98a92133,0x2ca950d5,0x7bdc603e,0x57b845cb}}, // _brač_, hmad_, _xeru, _अल्ह,
+ {{0x69c10026,0xdce7c121,0x7ae5600e,0xb5fdc00a}}, // ělen, ğlık, nsht, fišt,
+ {{0xfbcfa518,0x7bc5628f,0xdce7c121,0x6aa2c037}}, // _स्तम, hchu, şlık, _ghof,
+ {{0x6e23e60a,0xdbd9083e,0x321830d6,0x6258413a}}, // _tanb, ræði, tery_, _bíon,
+ {{0x645b8355,0xaadd80a8,0xd019c0c2,0x8b08204a}}, // _llui, नीयक, leń_, jdří,
+ {{0xceb2a3bc,0xdca64080,0x69dc70d7,0x6abd4669}}, // תים_, зави, _rere, _ausf,
+ {{0xb5fdc16d,0xf1bf0019,0xa3b825cb,0x2451c1e9}}, // cišt, ldán_, _छलक_, _bámi_,
+ {{0x29120167,0x6abc70d8,0x78bc70d9,0xcaa50678}}, // [b60] _upya_, _surf, _surv, _تصوي,
+ {{0xc079e067,0xe9f90067,0xada630da,0xb87b29d7}}, // _điệp_, _thả_, _напл, nsíl,
+ {{0x69de2b9a,0xe8026028,0x7bde20ba,0x1c0260c2}}, // _iepe, र्गा_, _iepu, र्गल_,
+ {{0xdd8f601e,0x746980ff,0x7bdc70db,0x7520805d}}, // دوم_, орів_, _weru, _psmz,
+ {{0x2007403c,0xe29aa0fb,0x20076017,0x7ae610dc}}, // əni_, бад_, ònic_, nskt,
+ {{0x6490604e,0x2907a29c,0x1994a368,0x7c5a6049}}, // räis, ftna_, маля, _السر_,
+ {{0xfaa370dd,0x69dd50de,0x6493403c,0x80dbe07c}}, // _чаро, _yese, rçiv, _মৎস্,
+ {{0x628e2132,0x442490df,0x44268054,0x78a2c048}}, // hobo, _uam_, qdo_, _qhov,
+ {{0x61e0d0e0,0x851b0028,0x645da013,0xe803691c}}, // raml, _पैंट_, _įsiv, _लामा_,
+ {{0x7e6d00ae,0x20c1e119,0xd3e4a0eb,0x20c0c119}}, // _smap, _bói_, _آقای, _vòi_,
+ {{0xcfe1007c,0xcf8da0eb,0xfe45a63b,0x4f66aae5}}, // _ভাবন, _پژو_, _تکنی, _عاطف,
+ {{0x6b82c022,0xb606a066,0xf8aac60f,0x76bba076}}, // _txog, _dráž, _увек_, _המאפ,
+ {{0x7ac70d2b,0x321907ac,0xa3be008d,0x7eb5c012}}, // ясне, resy_, ेड़_, _ušpa,
+ {{0x98a9235d,0x8ca240c2,0x7522cf60,0x7bde2018}}, // _trač_, _कीबो, _isoz, _cepu,
+ {{0x3ebea046,0x4c9550e1,0x7c38f0e2,0x6aa40090}}, // _jutt_, дивс, _invr, _fhif,
+ {{0x5eb44841,0x61e28046,0x5597e076,0xeb97e076}}, // ейст, jaol, _מדוע_, _מדור_,
+ {{0x2ca950e3,0xad9b41e9,0x28b70029,0xa2b560ff}}, // [b70] rmad_, _abúr, _इंटि, дбач,
+ {{0x645f400e,0x44386425,0xd019c0c2,0xe3bf42a8}}, // shqi, _cnr_, zeń_, _daña_,
+ {{0x7f1961ac,0xa1340555,0x2c0ee04b,0xb5fb03ab}}, // تياز_, _قریش, त्यू_, chác,
+ {{0x7ae560dd,0x2fdea005,0x645c70e4,0x7bc8a054}}, // rsht, _aetg_, _elri, _afdu,
+ {{0xe3bf42b2,0x645d40f7,0x68e5605d,0xf1b98012}}, // _gaña_, _ilsi, sshd, _hašk_,
+ {{0xef188a0e,0x7ae5600e,0x60cd0167,0x660da16d}}, // ямі_, psht, _mtam, đako,
+ {{0x752d50e5,0x7afaf0e6,0x61ed10e7,0x3a25a68f}}, // lvaz, jutt, _odal, _valp_,
+ {{0x60cd10e8,0xfbd390e9,0xa29480ff,0xcac9c04e}}, // _otam, _ستر_, _засі, огие_,
+ {{0xa3a96026,0x2d98eaf4,0x63bb90ea,0x764ae0ce}}, // खेर_, üren_, _igun, _bofy,
+ {{0x6fb1604b,0x4425b0eb,0x4426cb26,0x2bffc1a6}}, // _जणां, _ual_, _eao_, ্যেই_,
+ {{0x3ebea14f,0x27e20058,0x3555a1b3,0x64aac04a}}, // _gutt_, takn_, _تناز, _přit,
+ {{0x9ce7e043,0x628e30ec,0xecd560c2,0xdb1c20cb}}, // _ہوتے_, wobo, डीएफ, rbrü,
+ {{0x61fe6277,0xb5fb4326,0x9c7cd0ed,0x6e2650ee}}, // ygpl, _mlád, _ovčj, _rakb,
+ {{0x3ebf804e,0x60cd10ef,0xe3bf4005,0xda0ee067}}, // _muut_, _dtam, _raña_, _hỏng_,
+ {{0x92e8e13a,0xe1ff10f0,0x644bc35f,0x44386090}}, // _فريق_, giós_, _kogi, _rnr_,
+ {{0x61e3b0f1,0x98a3424a,0x10a3439d,0x69d52022}}, // hanl, висе, висн, abze,
+ {{0x672d484e,0x3cf54064,0x6e26423c,0x1d074474}}, // [b80] gvaj, एंगे_, _vakb, мети_,
+ {{0x61ed10f2,0x672410f3,0x752400bb,0x27ed8ce6}}, // _zdal, _isij, _isiz, _aden_,
+ {{0xa057200b,0x7c29d0f4,0xe9ff8016,0xdb03e057}}, // _חסיד_, kder, _soạn_, _ignó,
+ {{0xf8bf45e0,0x64922065,0x644bd0f5,0x29094025}}, // _fué_, pæis, _nogi, btaa_,
+ {{0x60c9403c,0x7d09c00e,0x3e77417a,0x94aab0f6}}, // _çemp, jtes, għtu_, отка_,
+ {{0xdd1d21ce,0x3a27e0ae,0x3e720013,0x644bc090}}, // lášt, _nanp_, kšto_, _aogi,
+ {{0x69c98035,0xeb97654a,0x90c3653f,0x09e2c1a6}}, // _ffee, _жир_, _абсе, _যাবা,
+ {{0x7766005d,0x8fa370f7,0x6e29d0f8,0x7d09d0f9}}, // kzkx, _расе, gdeb, ftes,
+ {{0xf50381e1,0xd7cfa180,0x7afc30fa,0x20048037}}, // _изто, _स्वच, hurt, _icmi_,
+ {{0x2745000e,0x661c2749,0x6579a049,0x0c26a19d}}, // _lënë_, jerk, ábhá, _імен,
+ {{0x27e010fb,0xddc400e4,0xc7b8e53d,0x2129c579}}, // _bein_, _smiš, jeđe_, _šaht_,
+ {{0xd9ad007c,0x201cb0fc,0x61e0807f,0x2246848d}}, // _ক্ষম, levi_, _jeml, njok_,
+ {{0x7d09d0fd,0xfc67c01c,0xed5800db,0x644af0fe}}, // ctes, _تخلی, доў_, _tofi,
+ {{0x7afaf0ff,0x3e7ac018,0x752d4361,0xddc9d100}}, // putt, dīti_, vvaz, mieš,
+ {{0xdd9420db,0xa3b15101,0x2745000e,0x752400bb}}, // вары, टेन_, _bënë_, _esiz,
+ {{0x7c276050,0x2cb86420,0xe1ff0017,0x3ce986ca}}, // _sajr, _aird_, riós_, šavi_,
+ {{0xd7d02216,0x1fabaaa1,0xaca32125,0xddc9c041}}, // [b90] _त्वच, _चण्ड, _ajụg, nieš,
+ {{0x3b0941f6,0xcf9b61e3,0x443a3102,0x2cbf9103}}, // rtaq_, оје_, _gnp_, _ruud_,
+ {{0x03d62095,0x61e4428f,0x3de3e07c,0x3a29031d}}, // _אותם_, bail, _মামল, _kaap_,
+ {{0x7c28a10a,0x603e812a,0xfe100067,0x2c6d200a}}, // _aadr, _rămâ, _lắng_, dždi_,
+ {{0x3a291104,0x6e2761d7,0x61e3b105,0x09e2207c}}, // _maap_, _tajb, tanl, _বাতা,
+ {{0x6282d106,0x38a1d107,0x68fbc052,0x7afbc052}}, // _ajoo, dóra_, vuud, vuut,
+ {{0x3ea6c153,0x629af108,0x2bae6914,0x2055d109}}, // _bhot_, dlto, झेदा, етпр,
+ {{0x7c29d10a,0xfbb82095,0xdd9585be,0x6f09d10b}}, // uder, ופות_, _забы, ttec,
+ {{0x2b908050,0xfe0ee067,0x38a1d10c,0x4427f10d}}, // _پیوس, _vững_, góra_, _pan_,
+ {{0x6009ebcb,0x6e3b8022,0x6e29807d,0x68fbd10e}}, // чном_, _hnub, _haeb, ruud,
+ {{0xd6cee896,0xd70b407c,0x20004025,0x2d994271}}, // یقی_, রিয়া_, ygii_, _lyse_,
+ {{0x68fc2271,0xf1bfa119,0x4429110f,0x26050b27}}, // vurd, _đáy_, _caa_, _वाणी_,
+ {{0x44291110,0x200ac03c,0x2745000e,0xdbc68046}}, // _daa_, əbi_, _rënë_, _tööd,
+ {{0x7c298aaa,0x7860603c,0x61e440a2,0x7c28b111}}, // _laer, _mövc, wail, _xadr,
+ {{0x96960da4,0x7c3b8916,0xf0fe64d6,0xb5fb0049}}, // _приш, _onur, _उमेद_, mhán,
+ {{0xa3cb410a,0xfbda007c,0x9f4204b7,0xf41f4156}}, // रुल_, _থাকত, haké_, _skär_,
+ {{0xd9f3f112,0x65b68602,0x5f4660e0,0x752400bb}}, // [ba0] _आयात_, _mühü, کنال, _usiz,
+ {{0x6013a3df,0xdbd64052,0xf1b989a3,0xcfb70076}}, // nımı, _sääd, _gaši_, _אלפי_,
+ {{0xb346e1ae,0x779420d0,0x26c1208b,0x20c60067}}, // maçõ, _شیرا, _guho_, _hôi_,
+ {{0x62957113,0x68fce5df,0x7ae9d114,0x61e61115}}, // _ekzo, àrdi, kset, nakl,
+ {{0x224d854c,0x4525e1a6,0x6e3b813e,0xb4dec046}}, // _doek_, যমিক_, _dnub, _तबे_,
+ {{0xdceb00e4,0xd467b116,0xb346e1ae,0xe805a7af}}, // šićn, ниче_, naçõ, _राणा_,
+ {{0x35a37117,0x7afd1118,0x20c60067,0xfaff000e}}, // _барг, zust, _lôi_, jzën_,
+ {{0x60c1a025,0x64599083,0x2005a2a4,0xf99f40f9}}, // _dulm, ckwi, _ecli_, _gbès_,
+ {{0x60c0808b,0x7afe7119,0x201cb11a,0xb5fca13e}}, // _uumm, lupt, pevi_, _alġe,
+ {{0xb602e295,0x7afd06eb,0x60c1a197,0x3eadd11b}}, // _část, vust, _fulm, amet_,
+ {{0x7afd023c,0x60c1b11c,0x63a4111d,0xa5f9111e}}, // wust, _gulm, _azin, мену_,
+ {{0x2cadc037,0x5186c0ba,0x69c0111f,0x26c12552}}, // cmed_, _пула, žmen, _ruho_,
+ {{0x442a61ae,0xf1b98018,0x75264012,0x61e57120}}, // _oab_, _paši_, _nskz, yahl,
+ {{0x61e1a17a,0x24804037,0x20052009,0x7afe7121}}, // _xell, enim_, üli_, kupt,
+ {{0x29882822,0x1995214a,0x26c25122,0xdff4c10a}}, // есто_, тавя, _auko_, _इयाद_,
+ {{0x442a7123,0x7c2af124,0x4b37e076,0xa3cb44e5}}, // _aab_, _hafr, _טרול_, रुं_,
+ {{0x59d4013a,0x6702a04b,0xb4dec10a,0x3f9e20c4}}, // [bb0] _صغير, रंडक_, _तबो_, ýtur_,
+ {{0xaaa50ff1,0x224d8f06,0x3eb94200,0x3eadc121}}, // _गीतक, _soek_, _wist_, zmet_,
+ {{0x7afe61f6,0x764e66c4,0x7c2ae04a,0x290ce746}}, // gupt, _doby, _mafr, ntda_,
+ {{0x43759125,0x64960049,0x61e56745,0xf993e053}}, // култ, láir, sahl, ארע_,
+ {{0x85b84841,0x20c60119,0x61e561fb,0x09d8a1a6}}, // _плюс_, _xôi_, pahl, _সাজা,
+ {{0x80d3807c,0xc7b8a098,0x443ce098,0x6e2d5126}}, // _সিদ্, _lađu_, _nnv_, ldab,
+ {{0xc984673f,0x442b5127,0x27e68d87,0x9f420050}}, // _бути, _kac_, baon_, raké_,
+ {{0x443cf128,0xc7b8a29f,0x7aed1129,0xcd4280e0}}, // _anv_, _nađu_, _ovat, _چھوٹ,
+ {{0x51878084,0xef19c0ff,0xd706c04e,0x22848498}}, // _чува, ємо_, вные_, _сург,
+ {{0x201ef12a,0x64aac020,0x442b512b,0x3e7ac018}}, // geti_, _aƙid, _lac_, dītu_,
+ {{0x7d0d4419,0x20c60067,0xb346e1ae,0xf1d2c4ba}}, // htas, _sôi_, vaçõ, _ध्वन,
+ {{0x443cf12b,0x6e2d4022,0x442ce1cd,0x7c2ae227}}, // _env_, jdab, add_, _eafr,
+ {{0x60c2c0e8,0x443cf12c,0xb8138077,0x7ebef12d}}, // _guom, _fnv_, ण्यम_, _rūpe,
+ {{0x31568053,0x10a6312e,0x61e6112f,0x443cec1e}}, // דישן_, тигн, sakl, _gnv_,
+ {{0xdee3245c,0xafe32607,0x2ca9028f,0x38a42156}}, // иори, росл, _fhad_, göra_,
+ {{0xdcf4400a,0x3e646b2f,0x6d444265,0x61e3f130}}, // žačk, _möte_, txia, _henl,
+ {{0xa80666fe,0x7d0d4d72,0x80c8407c,0xf7460a09}}, // [bc0] кзал, gtas, _লিঙ্, _репо,
+ {{0xe3b8e602,0x3f9a27ac,0x4463690f,0x037a67f9}}, // mdır_, _typu_, авув, وحات_,
+ {{0x09e69131,0xf1b9816d,0x61e41132,0x661e659b}}, // воен, _mašu_, _keil, repk,
+ {{0x68fe607d,0x90e68050,0xd25980ff,0x644f4143}}, // rupd, نستن, нці_, _goci,
+ {{0xf11ef133,0x645ae342,0xa06ab0ac,0x2ee000d1}}, // _बन्द_, ykti, мада_, _jwif_,
+ {{0x61e41134,0xdce1a20f,0xc7b821e4,0x66045135}}, // _leil, _aylı, žđu_, lgik,
+ {{0xc0e37136,0x60c403f6,0xe297612a,0x78bab137}}, // _воск, _luim, _рар_, _vitv,
+ {{0xbbc100a8,0x645c200e,0x765bc114,0x6abb8197}}, // _एलेक, hkri, gkuy, _giuf,
+ {{0x9f494069,0x7eb8f138,0x2ca901cd,0x0906839d}}, // ngað_, нгис_, _rhad_, _шпан,
+ {{0x443dc0e2,0x61e3f139,0x61e2d13a,0x60c3ef87}}, // _enw_, _cenl, _weol, _bunm,
+ {{0x2c146029,0x442dc271,0x6e2bd13b,0xb87b24d2}}, // न्यू_, bde_, _fagb, príz,
+ {{0xb87b21ae,0x752440b1,0x201eeed9,0x442ae06e}}, // nsív, kwiz, seti_, žb_,
+ {{0x290dc07b,0xd877213a,0xda65c049,0x39468609}}, // ctea_, ناسب, _حالي, nxos_,
+ {{0x60c3e0f9,0x442b512b,0x26c48cc5,0x394686c7}}, // _funm, _sac_, _mumo_, ixos_,
+ {{0xac19513c,0x645d000e,0x200765df,0x25a52156}}, // нову_, ëris, ònim_, åll_,
+ {{0x2004c939,0x80d3807c,0x9c7d0013,0x4e0ee026}}, // ngmi_, _সিস্, ksči, त्रै_,
+ {{0x78bc600a,0x60c4113d,0x2900513e,0x7524513f}}, // [bd0] _birv, _guim, guia_, gwiz,
+ {{0x6aa99140,0xd37b667b,0x648b6018,0x21291141}}, // _shef, мче_, kļie, _isah_,
+ {{0x442dc654,0x9f4e00e0,0x27e37142,0x9ed96b15}}, // yde_, önöm_, _vejn_, хмет_,
+ {{0x442b5143,0x41b4c7d9,0xe0df40d1,0x5fb4c077}}, // _uac_, ुशास, _twò_, ुशाल,
+ {{0xe9d880db,0xee3f403a,0xed46c0e0,0x3959005d}}, // ткі_, _iný_, _اپ_, nyss_,
+ {{0xc1b84139,0x6e2d0054,0x68ed4bf2,0xf1b9c579}}, // _шлях_, _laab, msad, ješa_,
+ {{0xe3b8e181,0xd7e3004b,0x62829144,0x9f47a00e}}, // zdır_, पराच, bnoo, manë_,
+ {{0x3e7ac041,0x0c25d145,0x2499417b,0x7af6803b}}, // dīts_, умин, _jksm_, šyta,
+ {{0x645bc2bb,0x387c80fa,0xf77f01ae,0x625cc7da}}, // rkui, èvre_, daço_, _géom,
+ {{0x68ed401f,0x645bd146,0x1dc7b101,0x61e52f75}}, // isad, skui, _ललित, _nehl,
+ {{0x442d80eb,0x6d599147,0xf1b98fe8,0x3ce000d1}}, // _kae_, lywa, _vašu_, _swiv_,
+ {{0xe43c404a,0x6e2d0025,0x8335c233,0x9f45e066}}, // _příč, _caab, _مرتض, nalé_,
+ {{0xc879c3df,0x27e08232,0x81e9607c,0x61e8f148}}, // miş_, ðini_, _যান_, badl,
+ {{0x7aed5149,0x3f9e0041,0xfc3f40f9,0x7e62c2c9}}, // dsat, ātu_, _aní_, _zlop,
+ {{0xfe70e0d0,0xa96a239d,0x672440f7,0x95e961a6}}, // _بدن_, хива_, wwij, খলাম_,
+ {{0x442d914a,0xc006114b,0x3eaa605d,0x1df3e029}}, // _nae_, _спик, _shbt_, _आयुष_,
+ {{0x61e5314c,0x2d850121,0x7ae9006f,0x42562ca7}}, // [be0] _fehl, şler_, šetr, _منظر,
+ {{0x291a217b,0x2bd0279e,0x68fbc05d,0x6e2d000d}}, // _tppa_, तुशा, irud, _zaab,
+ {{0x6724514d,0xf1a82295,0x6e2d0ad5,0xfba8314e}}, // swij, _गरिन, _yaab, _गरिम,
+ {{0x443f8037,0xb4fb24ac,0x7aed4025,0x6b7b2679}}, // _cnu_, _אפטי, bsat, _טרינ,
+ {{0xf8c8c016,0x20c9c016,0x7e760057,0x6296514f}}, // _mức_, _núi_, _mmyp, moyo,
+ {{0xb4c04518,0xf772a043,0x6e20cc75,0x4421643d}}, // ँदी_, یاں_, wemb, ceh_,
+ {{0xb8eb8738,0xd378e381,0x443ea013,0x60c64264}}, // _रू_, paći_, _vnt_, _hukm,
+ {{0x6e2e7150,0x6d41635d,0x5454d151,0x62953152}}, // _habb, _šlag, рвит, zozo,
+ {{0x127b849c,0x7c2d1153,0xa8574095,0x270ea03c}}, // _נאמע, _raar, טיקה_, _məni_,
+ {{0x60c64ce1,0xb9094291,0x5c14007c,0x3f14e331}}, // _mukm, _achị_, িযোগ_, адос,
+ {{0x44221154,0x3606c0a4,0x61e5205d,0x249fc8a3}}, // fek_, _مواف, _rehl, blum_,
+ {{0x6e2e7155,0x60c53074,0x21b500ff,0xdee6d156}}, // _labb, _ruhm, ийня, _боди,
+ {{0x7c93c13a,0xd9e46029,0x7d028013,0x290160a9}}, // _النص, गरात_, nuos, zuha_,
+ {{0x7c229157,0x25eee04b,0xae03698a,0x69ba40c2}}, // heor, _आयटी_, _लाइन_, ्शनी,
+ {{0x62953158,0x634ae03c,0x6d5980c2,0x4421615e}}, // rozo, kəni, zywa, veh_,
+ {{0xddcd5159,0x2bdd6260,0x7ae2d15a,0x291ce1de}}, // snaž, यर्थ, _kwot, _ipva_,
+ {{0x634ae497,0x442ee797,0xf3ff001f,0x69dc315b}}, // [bf0] dəni, _kaf_, lhão_, bbre,
+ {{0x2901715c,0x443f915d,0xe70400e0,0x3ea0515e}}, // tuha_, _snu_, _اسٹی, alit_,
+ {{0x2614655d,0x442d8292,0xc879c602,0xfbd681a6}}, // न्धी_, _pae_, yiş_, _হয়ত,
+ {{0x6d598064,0x44e1a03b,0x473583a5,0x6f028197}}, // tywa, vų_, инес, fuoc,
+ {{0x2903204e,0x29112265,0x2901715f,0x61458677}}, // luja_, ltza_, suha_, рела,
+ {{0x2c146a08,0x5fcfaaa1,0x9f47a050,0x2bd58077}}, // न्दू_, _स्खल, lané_, _ड्रा,
+ {{0x32582095,0x83fd80e0,0x6845d156,0x270ea03c}}, // רסום_, _előa, инка, _yəni_,
+ {{0x2baa7160,0x29021161,0x764d5162,0x3ebea31b}}, // _करता, yuka_, njay, _eitt_,
+ {{0x44e1a013,0x27e6c155,0xb225e7ab,0xe664840e}}, // sų_, _deon_, ампл, _утро,
+ {{0x69c2ce3b,0xaa7b6d3f,0x61eb9163,0x9f47b164}}, // _egoe, _zvýr, lagl, hané_,
+ {{0xfe46004e,0xc4f8a4a1,0xe80a0033,0xa9a5e2f0}}, // анно, _معنا_, _वादा_, ринд,
+ {{0x7c2f450e,0x54a7cdb3,0x61e77165,0xb5fb413a}}, // _nacr, _صحاف, _lejl, _slán,
+ {{0x76b2e064,0xdee5c60f,0x8c45eef0,0x673b4098}}, // _płyt, _толи, _веле, _šujn,
+ {{0x05a8c0ff,0x3ebf805d,0x270ea03c,0xe7f20026}}, // _свій_, _liut_, _səni_, _आएका_,
+ {{0xddcd0686,0x60c65166,0xcec8c016,0x7afd1167}}, // _ulaş, _sukm, _tộc_, arst,
+ {{0x7e7bc13b,0x6e23b168,0x6aad1169,0x3f8c217a}}, // niup, henb, _ghaf, ħdu_,
+
+ {{0x61eb916a,0xe29a601f,0xb89b0095,0x29112265}}, // [c00] dagl, ваа_, _בבקש, atza_,
+ {{0x6aa4c1d7,0x7c2e6025,0x60c6420d,0xe45f6156}}, // ċifi, _qabr, _vukm, _stör_,
+ {{0x6e23b16b,0x6e2e665f,0x7e7c200a,0xd946316c}}, // denb, _vabb, mirp, _тени,
+ {{0xeabf465f,0x7d02916d,0x61eb865f,0x68e4116e}}, // _giù_, tuos, gagl, _kwid,
+ {{0xf9924938,0x2fd16012,0x634ae5fc,0x61eaa765}}, // _ذبح_, _ffzg_, rəni, tafl,
+ {{0x5c73a0ff,0x81b5607c,0x7ae4116f,0xe283a471}}, // оліт, ছুই_, _mwit, олчи,
+ {{0x7c22807b,0x3266c52f,0x721b6557,0x2bd340c5}}, // peor, итив, _שולח, धुवा,
+ {{0xd576893b,0x8af0403c,0xddcd020d,0x245c41e9}}, // _кузь, yyəl, _omaš, _bími_,
+ {{0xbebb600e,0x32cb814f,0x7d028425,0x3ebea4f0}}, // _rrëz, _gøy_, quos, _titt_,
+ {{0x6738e098,0x6e244105,0xb5fb4031,0x44325170}}, // _prvj, heib, _alál, hdy_,
+ {{0xdd94e056,0x7e7ae156,0x7ae41171,0x7d03b172}}, // саны, ritp, _awit, cuns,
+ {{0x7d0365df,0xd498007a,0x6d5bc022,0x7ae4000f}}, // ànsi, иря_, byua, _bwit,
+ {{0x236dc064,0x78a2805c,0xac18e04d,0xd378e29c}}, // czej_, ilov, _болу_, vaću_,
+ {{0x7ae40058,0x3d0760c2,0x38694025,0x60c09173}}, // _dwit, _हमले_, whar_, _limm,
+ {{0xee87004e,0x2c1624e5,0x4df88295,0xf0b420ff}}, // _выпо, द्धं_, ुलाई_, ійсь,
+ {{0x4e196c87,0x60c09174,0x61e76065,0x6e23b175}}, // न्नई_, _nimm, _sejl, zenb,
+ {{0xa2caf176,0x61eb9177,0x611a8041,0xe9df1178}}, // [c10] _सूत्, vagl, nālā, mbú_,
+ {{0x6f03a3ed,0x7e7d1179,0x7e6060ba,0x38a9a3e4}}, // yunc, lisp, _împl, kúra_,
+ {{0x61136026,0x7bc5317a,0x6e23b04d,0x7ae4117b}}, // dělá, _ighu, venb, _zwit,
+ {{0x83fd8009,0x3d074077,0x200ca194,0xbd446049}}, // _előn, िंदे_, _acdi_, تنمي,
+ {{0x5334404e,0xa5da000b,0x7c23a1e8,0x26088033}}, // _деят, אַצי, tenr, _हारी_,
+ {{0x95cb69b1,0x7eada06f,0x98a8004a,0xbcfb0009}}, // _шума_, kúpe, _brně_, lzés,
+ {{0x245c517c,0x6e23a5c4,0xb4c1b17d,0x649d0049}}, // _sími_, renb, ंदे_, néis,
+ {{0xc245d17e,0x3209492d,0x628ae055,0x442fc048}}, // сник, ngay_, _ajfo, _qag_,
+ {{0x225fd17f,0x6f056098,0xe939c63b,0x7c23a9cd}}, // rkuk_, luhc, اسبت_, penr,
+ {{0x224dc579,0x7c24401f,0x6d5d1180,0x2a66c098}}, // sjek_, zeir, dysa, _elob_,
+ {{0x44317181,0xe81968a1,0x27e7e0ff,0x24983182}}, // _maz_, न्या_, _venn_, vorm_,
+ {{0x201a205d,0x62860108,0x76880106,0x61ed5183}}, // _sbpi_, unko, rıyo, jaal,
+ {{0x60c1a013,0x2904d184,0x7c2560cb,0x628609ee}}, // _kilm, cuma_, kehr, rnko,
+ {{0xc0b2e016,0x76880121,0x7529d185,0x44317186}}, // _mười_, pıyo, mwez, _naz_,
+ {{0xcaa6413a,0x61ed5187,0x26088046,0xa967216c}}, // مصري, faal, _हाली_, бира_,
+ {{0x68e40227,0x61e99188,0x2c13e321,0xe9f90119}}, // _twid, _neel, त्रं_, _khẽ_,
+ {{0x291f8286,0x62872098,0xceb4403c,0xa3e7b008}}, // [c20] _npua_, bnjo, rmə_, परा_,
+ {{0x6298b189,0x2a60422e,0x4432518a,0x7c2560cb}}, // xovo, skib_, sdy_, gehr,
+ {{0xb113c291,0x2912518b,0x27edca0c,0x629e2088}}, // _bụkw, stya_, kaen_, _ikpo,
+ {{0x61e8a089,0xe3bf01af,0x2effca94,0xa3af08c6}}, // _sedl, leña_, kruf_, _करम_,
+ {{0x753b918c,0x6443e04e,0x645600d1,0x86374095}}, // _cruz, _onni, _moyi, _הרכב_,
+ {{0x4424c064,0x60c1a054,0xa4d8a39e,0x200a318d}}, // wem_, _cilm, адку_, ngbi_,
+ {{0xb5fb40f9,0x27edd18e,0xb7db20be,0xf487a062}}, // _amáy, faen_, אקצי, حانی,
+ {{0x78a4518f,0xb881c06f,0x27e0517f,0x61e8b190}}, // nliv, _šíre, nbin_, _wedl,
+ {{0x753b9191,0x6d469192,0xe80d9193,0xdee3845c}}, // _gruz, _škaf, _हाता_, _ноти,
+ {{0x76441194,0x7e629195,0x26ca60bb,0x61ed400d}}, // _aniy, lkop, _kubo_, zaal,
+ {{0x2904cdc9,0x2129405d,0xd943b196,0xfe9b60be}}, // suma_, zwah_, _нефи, ריימ,
+ {{0x44321197,0xbddb4011,0xaa67095a,0x8c67031a}}, // _cay_, _anèn, сток, стод,
+ {{0x61ed5198,0x28be4292,0xd544e031,0x2e37c076}}, // vaal, ्दनि, _bẹ̀r, _לראש_,
+ {{0x29005199,0x76440814,0x61ed519a,0x656f0064}}, // dria_, _eniy, waal, rzch,
+ {{0x7c26119b,0x447be076,0x9f45ac1e,0xdd3a20ba}}, // bekr, _תנוע, _pelé_, văţa,
+ {{0x2900519c,0x38c94043,0x985d00c2,0x63a2c00d}}, // fria_, لائی_, _błąd_, _kyon,
+ {{0xbcfb00e0,0x61ee319d,0x90556423,0x463bc00b}}, // [c30] rzés, babl, овац, נעמע,
+ {{0x6299919e,0x6dc76052,0x67ff4031,0x20094108}}, // wowo, ссаж, _bàjà, sgai_,
+ {{0x0ec4a626,0x99d7813a,0xfbab6056,0x645604ee}}, // _लंगड, متشا, утай_, _yoyi,
+ {{0x7e628c57,0x2c6ba065,0xf8c8c437,0x3e6ba0ff}}, // gkop, _møde_, रदाय, _møte_,
+ {{0x61e9919f,0x4425f1a0,0x212b01cd,0x62584049}}, // _veel, yel_, nwch_, _tíor,
+ {{0xb4db40f9,0x26c251a1,0xd378a012,0x351c0076}}, // _aràg, _ziko_, _leću_, טודנ,
+ {{0x6609c54c,0x63a2d1a2,0x61e991a3,0xb7fb083b}}, // tgek, _ayon, _teel, ्लाम_,
+ {{0x60dbd1a4,0x64572005,0x661c66ca,0x7e6291a5}}, // tpum, _loxi, _zbrk, ckop,
+ {{0xb5fb4082,0x2bdaf17d,0x6272a0c2,0x7d036057}}, // _alák, _म्हा, głos, ánsf,
+ {{0x0696e049,0x78a44454,0x60d8e0d5,0x63a1a0c2}}, // ينية_, zliv, _btvm, _tyln,
+ {{0xdb1fa04e,0x6e260009,0x44320095,0xf3f980ba}}, // äväs, tekb, _pay_, _alţi_,
+ {{0x6288f1a6,0x2c6ba3d1,0x764520fd,0xe81971a7}}, // ando, _døde_, _anhy, न्ता_,
+ {{0x26d94048,0x09c5207c,0x1c1ea0c2,0x7afb0274}}, // _ntso_, ্রনা, _मॉडल_, šutn,
+ {{0x95cbf1a8,0x7fb6c7f0,0x11da2049,0x81c141a6}}, // _суда_, _ظهور_, شورة_, ুরা_,
+ {{0x0595e050,0x645611a9,0x629e2098,0x63ad11aa}}, // _باشگ, _toyi, _vkpo, _ezan,
+ {{0x70b440e3,0x60c2c071,0x7bd562be,0x7e6d51ab}}, // ंगेल, _xiom, _afzu, ghap,
+ {{0x27e051ac,0x07a34455,0x26cb407b,0x2ca0008b}}, // [c40] rbin_, датн, _cuco_, _ikid_,
+ {{0x249f805d,0x60c3e0a2,0x6d5e71ad,0xc4862b24}}, // _akum_, _jinm, typa, _флок,
+ {{0x26c251ae,0xb5fae053,0xd6cf64c7,0x7e6d40bb}}, // _tiko_, שלעכ, نقل_, bhap,
+ {{0xa5da6d0b,0x6289c0a9,0xd9464974,0x1b20c1a6}}, // _प्रौ, jneo, _деми, বিতে_,
+ {{0x28c36216,0x81e9607c,0xc5f3a07c,0xc1a6e029}}, // _वंचि, _যার_, চ্ছা_, _ऑर्ग,
+ {{0x63a400b1,0x248d8071,0x60c2d1af,0x395fc08b}}, // _myin, _ejem_, _siom, dyus_,
+ {{0x35a36e1d,0xe5a36974,0x20e820ba,0xed5a614f}}, // _жарг, _жири, nşi_, _тов_,
+ {{0x27eea069,0x2000a12a,0x65636065,0x7bc7600e}}, // safn_, şii_, ønhe, _zgju,
+ {{0xfd62f1b0,0x368ae26f,0xc7b8e098,0xf8af60e0}}, // _fitọ, исон_, međi_, _سکے_,
+ {{0x3ea691b1,0x61e286bf,0x60c3e38d,0x628391b2}}, // llot_, lbol, _cinm, énov,
+ {{0x3ea00561,0x443331b3,0xddc8e00a,0xe3b8e106}}, // _bkit_, _rax_, pidž, ldız_,
+ {{0x61ebd1b4,0x3ea0017b,0x248d01de,0x63a3e0fd}}, // _gegl, _ckit_, õem_, _dynn,
+ {{0x5d8504c7,0x2246c15a,0x6ecae466,0xa25b4926}}, // سلسل, _inok_, _सूरु, _biôn,
+ {{0xf8ca6292,0x20d08119,0xcfe741a6,0x7afb11b5}}, // िदाय, _mài_, _ফাইন, šuto,
+ {{0x7c28e0fd,0x26cb441a,0x7e6d51b6,0x6458e425}}, // medr, _suco_, thap, _iovi,
+ {{0x44f511b7,0x44332ded,0x63a7a22e,0xc7b8e012}}, // _епис, _wax_, _øjne, jeđi_,
+ {{0xa3af0046,0xe3bf05e0,0x63ad11b8,0xa3de8485}}, // [c50] _करि_, meño_, _uzan, _द्ध_,
+ {{0x2a6dc6a6,0xa3a761e3,0x0b8840ff,0x2451c066}}, // zheb_, ојек, істи_, _dámy_,
+ {{0x61ed013b,0x26ccb1b9,0x60cd11ba,0xf429e052}}, // _keal, _nudo_, _huam, lmää_,
+ {{0x60cd0296,0x32d08016,0xf1af00a8,0x6fda4077}}, // _kuam, _bày_, _घरान, युदं,
+ {{0x6d4b4277,0x71278085,0x60cd11bb,0x3d07610a}}, // ågad, ترال, _juam, _हमके_,
+ {{0xd5e2c0f9,0x2bdae0aa,0x61e291bc,0xe8e02119}}, // _alò, _म्ला, abol, _nhớt_,
+ {{0x9a8780fb,0x25a9c49f,0xad9b4049,0x7c28e09a}}, // _дуал, _šal_, _gcúr, dedr,
+ {{0x98afe0e4,0xdb1c60e0,0x7e7b91bd,0x28af60c2}}, // đače_, _ugrá, _umup, _जीति,
+ {{0xb5fb11be,0x61ebd1bf,0xaca3c079,0x9f586030}}, // tkán, _vegl, _nhụj, _adrè_,
+ {{0x394dd1c0,0x9f4a6b3c,0x3dc90cc5,0x61ebcd53}}, // rxes_, _bebè_, _agaw_, _wegl,
+ {{0xe9d0a0a4,0xb5fb4071,0xbddb4733,0xdced402e}}, // _شغل_, _tláh, _anèm, dzač,
+ {{0x628b91c1,0xaca3c079,0xa3bd437f,0x2489c071}}, // nngo, _kwọl, ेखा_, _ñam_,
+ {{0x61ed11c2,0x29020054,0x628b91c3,0x6458e071}}, // _deal, urka_, ingo, _fovi,
+ {{0x63a3f1c4,0xad25e33e,0x200ce05d,0x09e2207c}}, // _tynn, _فرعو, cgdi_, _বাঙা,
+ {{0xbbaa61d6,0x7d09d1c5,0x5fcb8f92,0x61e288e5}}, // _करेक, lues, िशाल, ybol,
+ {{0x6281f1c6,0x6446e041,0xfd64a1e9,0x69c991c7}}, // lilo, ēkie, _airọ, _ngee,
+ {{0x290331c8,0x7ae98385,0x63a401fb,0xb4db4733}}, // [c60] arja_, _nwet, _uyin, _aràb,
+ {{0x6a606262,0xd3e4263b,0x1dddb1c9,0xeb9a6926}}, // _löfv, _تقدی, नुमत, биз_,
+ {{0x2f160a2f,0x442951ca,0x20d08016,0x7d1651cb}}, // læg_, bea_, _sài_, stys,
+ {{0x213f8098,0x7d09c0dd,0x2ab200e0,0xdfcf813a}}, // _bruh_, kues, lább_, هيم_,
+ {{0x290a297e,0x1634a491,0xa3e24021,0x27f24054}}, // luba_, деля, _न्य_, mayn_,
+ {{0x213f805c,0x61fb91cc,0x6447629c,0x628b8ab8}}, // _druh_, _edul, _gnji, ango,
+ {{0x290a31cd,0x629d030f,0xd9b90308,0xd6d7a772}}, // nuba_, yoso, _इण्ट, пты_,
+ {{0xae0eabd9,0x3f6a804e,0x4420036c,0x78702156}}, // _सावन_, _либо_, _nbi_, _hävd,
+ {{0x9f45e066,0xa01b4069,0xb5fdc018,0x628281dd}}, // valá_, _kjör, ukša, mioo,
+ {{0xe678e0ff,0x29032cce,0x61e451ce,0x20d1a0c4}}, // ійні_, yrja_, hbil, _fái_,
+ {{0x7d044054,0x2918206f,0x290a31cf,0x67240143}}, // hris, jtra_, juba_, _apij,
+ {{0xb69b21ae,0xad9b4005,0x44294108,0x386691d0}}, // grân, _ocúp, xea_, nkor_,
+ {{0xe29ae93b,0x6f09c071,0x5334c16c,0xe534c54a}}, // _лад_, cuec, _целт, _цель,
+ {{0x6447c018,0x44294264,0xccf8a04a,0x7c38ab83}}, // ējie, wea_, _kvě_, ndvr,
+ {{0x69d5269f,0x8bec01a6,0x6e942926,0x386691d1}}, // rcze, _কারন_, хиру, kkor_,
+ {{0x39400110,0x7af520c2,0x6448a006,0xdb03e066}}, // _fris_, rszt, _lndi, _uzná,
+ {{0x5a35644c,0xb066004e,0xfbd0413a,0x7e6d11d2}}, // [c70] мнат, ssää, اتف_, _ilap,
+ {{0x201f8143,0x27ed8095,0x7c29d1d3,0x290a31d4}}, // _sbui_, _seen_, zeer, buba_,
+ {{0xaca4e079,0x51f46049,0x7c29d1d5,0x7d09c00e}}, // _ngọz, عسكر, yeer, zues,
+ {{0x37e6a07c,0x628b866e,0xb5fdc018,0xaacfe7f4}}, // _নাগর, ungo, ekšn, _संसक,
+ {{0x20d30148,0x3ea94156,0xcc564008,0x9f4b004a}}, // _धूमध, mlat_, _חברי_, kací_,
+ {{0x61ee71d6,0xddc8a00a,0x9f4485df,0x6281e265}}, // _gebl, _emdž, _demà_, xilo,
+ {{0x6281f1d7,0x9f4b0326,0x224911d8,0x1dda40a8}}, // vilo, dací_, _onak_, युवत,
+ {{0x32d1a016,0x61ef51d9,0x7648b1da,0x69c3e0c2}}, // _váy_, _jecl, _endy, _रणबी,
+ {{0x26c5a0e8,0x61e4807b,0x2ca941cd,0xb5fb41de}}, // _tilo_, ñile, ilad_, _fláv,
+ {{0x61ef51db,0xddc3e0ba,0x4d65eca6,0xb5fb11dc}}, // _lecl, _conş, дков, skál,
+ {{0x382a08f4,0x672411dd,0x356bb1de,0x44200827}}, // ожно_, _spij, _уран_, _pbi_,
+ {{0x2a6d8037,0x290b0098,0x6279e066,0x1fc5207c}}, // _jleb_, fuca_, jňov, ্রাস,
+ {{0x6448b1df,0x7bcbd1e0,0x61aa71c9,0xddd8a2e2}}, // _yndi, _iggu, _कर्ष, livš,
+ {{0x60c4e41a,0x3f69e51b,0x386691e1,0x9f4f80fa}}, // _éimp, _мило_, zkor_, gagé_,
+ {{0x845a50da,0x4f9531e2,0x8fa33156,0x2ca94025}}, // орат_, ерту, варе, flad_,
+ {{0x290a31e3,0x2b4006f1,0x6f0b8174,0x2ca951e4}}, // ruba_, _tric_, kugc, glad_,
+ {{0x32d2c081,0xdca351e5,0x672400e4,0x60ce71e6}}, // [c80] _xây_, тати, _upij, _subm,
+ {{0x66f30028,0x656280ce,0x26cee0e8,0x7ebf0013}}, // _अबतक_, vyoh, _yufo_, _rūpi,
+ {{0xee3a8fb5,0x69d9c171,0x68e4c17a,0xddd8a20d}}, // жне_, _ofwe, _ħidm, jivš,
+ {{0x6448a8a4,0x26dc6041,0x06e3c07c,0x2249017b}}, // _sndi, īvo_, _মিনি, _xnak_,
+ {{0x20d3e067,0xa51e26c6,0xddc2c143,0xaac580eb}}, // _lãi_, _पहुच_, _ološ, رتمن,
+ {{0xa3c1d1e7,0x6d41a058,0x200f8024,0x442251e8}}, // ंधन_, _brla, aggi_, _hbk_,
+ {{0x628e29cd,0x2ba50982,0x645b83d3,0x7d0b8013}}, // inbo, _गुना, _foui, augs,
+ {{0xa534c45c,0x9f45a004,0x786faaca,0x26cfc1df}}, // енич, _delà_, _røve, _bugo_,
+ {{0x62808dee,0x765ab1e9,0x65644054,0x41c9ea30}}, // _immo, _toty, nyih, रशंस,
+ {{0x5186e1eb,0x69cbcb11,0x224911ea,0x6d41a012}}, // дума, _egge, _snak_, _frla,
+ {{0xda0a08ae,0x6d41b1eb,0x25b20300,0x765c71ec}}, // वलंत_, _grla, _azyl_, _mory,
+ {{0x628e21b9,0x32062020,0xcec8c119,0x60c8a13e}}, // enbo, _ɓoye_, _lội_, _hidm,
+ {{0xa6db40c4,0x7ae291ed,0x44faa018,0x2c750031}}, // _orða, npot, lī_, _aúdù_,
+ {{0x291911ee,0x9f43206f,0xf7732050,0x3135303e}}, // rtsa_, majú_, ناز_, негр,
+ {{0xf2c740fb,0x62808227,0x7e6d11ef,0x290b0133}}, // дсан, _ommo, _tlap, suca_,
+ {{0x26cfc071,0x38a42156,0x628e31f0,0x443871f1}}, // _yugo_, förs_, anbo, _aar_,
+ {{0x9c130079,0x26cfc005,0x44224012,0x60c76361}}, // [c90] _fọre, _xugo_, _dbk_, _pijm,
+ {{0x44386d81,0xe616406e,0xcec8e119,0xb5fdc018}}, // _car_, čišč, _bội_, ekšl,
+ {{0x2013a017,0x9f4325ba,0xb5fb51f2,0x6e22d1f3}}, // òxim_, hajú_, _blát, _obob,
+ {{0x7aed11f4,0x9f43206f,0x1bd58de7,0x22990066}}, // _kwat, kajú_, ходя, téky_,
+ {{0x442cf1f5,0xf1a500a8,0x443871f6,0x290cea36}}, // ked_, _गुमन, _far_, huda_,
+ {{0x7c38f1f7,0x8cb94158,0x9f43206f,0x201131f8}}, // _navr, ्दको, dajú_, ngzi_,
+ {{0x290ce71e,0x64ca71f9,0x442b91fa,0x6da2ab65}}, // juda_, िदृश, _ºc_, лиша,
+ {{0x68ed11fb,0x644991fc,0x443951fd,0xfc31c050}}, // _owad, _unei, _jas_, احث_,
+ {{0x442cf1fe,0x2bc36626,0xb5fb4066,0x79a7839d}}, // fed_, _वृता, _hlás, _црве,
+ {{0x628571ff,0xc3324694,0x7c22d200,0x60c9804e}}, // niho, נוי_, _ebor, _hiem,
+ {{0xe286033f,0x27efc3d1,0x3869431b,0x4423605d}}, // елни, _tegn_, kkar_, _mbj_,
+ {{0x38694156,0xac19ca0c,0x6e22c031,0x7c22c2f4}}, // jkar_, _ходу_, _gbob, _gbor,
+ {{0x270d8010,0x442cf201,0x7e69c0c4,0x3ea05202}}, // _समोर_, bed_, mkep, voit_,
+ {{0x7c22d203,0xa06a203b,0x7e69c271,0x7d0d405d}}, // _zbor, чага_, lkep, duas,
+ {{0x2617b204,0xaca3c125,0x8af0403c,0xe8180026}}, // _नानी_, _ebụm, yyəs, _थापा_,
+ {{0x38b20082,0xda0a0010,0x645d5205,0x645c7206}}, // bára_, _वाटत_, _cosi, _sori,
+ {{0x3ea05207,0x765d5208,0x7d0d5209,0x2d8761af}}, // [ca0] roit_, _dosy, guas, únen_,
+ {{0x60c9804e,0x4439520a,0x6285720b,0xa3cc4028}}, // _aiem, _eas_, giho, _ललक_,
+ {{0x4422407d,0x644bd06b,0x9f43206f,0x961dc041}}, // _tbk_, _ongi, zajú_, ziņa,
+ {{0x4438720c,0x6e39c022,0x61e366df,0x6e3bc23d}}, // _war_, _nawb, ınla, jdub,
+ {{0xd838a42e,0xaca46088,0x60c9920d,0xb5fb462f}}, // _pač_, _arụs, _diem, _elás,
+ {{0x290cf20e,0x6e2e2576,0x2000007d,0xe45fc052}}, // yuda_, nebb, _ldii_, öön_,
+ {{0x4420520f,0x26d2008b,0x6e240291,0x69dc7210}}, // ofi_, _luyo_, _obib, _ifre,
+ {{0xdbc7210a,0x61f65211,0x6280835f,0x20124a45}}, // _töök, nayl, _ummo, ngyi_,
+ {{0x645e3212,0x91fca090,0xdb1d0009,0x8506850f}}, // _oopi, _blāt, lcsö, _سوان,
+ {{0xd250c555,0x7c2d4265,0x6e241213,0x7c3bd214}}, // انک_, zear, _abib, adur,
+ {{0x6d43e5a0,0xdb1c6005,0x6594f215,0x25a9007d}}, // _crna, _agrú, тану, _syal_,
+ {{0x7860603c,0x443a21cf,0x26d201e7,0x6458b005}}, // _mövq, _aap_, _buyo_, ljvi,
+ {{0x63a4a20f,0xbfab020e,0x2bdda04a,0x7ea5c057}}, // şind, ятое_, नुवा, rópt,
+ {{0x69dcc064,0x6e240fa1,0x7c3aabf2,0x64892098}}, // śred, _ebib, _jatr, džic,
+ {{0xe80a1160,0x38b20559,0x6e3ab216,0x3ced229f}}, // _वाचा_, sára_, _matb, ćevo_,
+ {{0xe8e02119,0x7bdc7217,0x7f4400d5,0x62861218}}, // _chốt_, _afru, _friq, ciko,
+ {{0x3eb866a6,0x9e077219,0x7689203b,0xf8bf00fa}}, // [cb0] _bhrt_, ечел, ržyb, mmé_,
+ {{0x69daf21a,0x21290037,0x645d4d5e,0x80b34029}}, // ncte, _ipah_, _wosi, _आठवे,
+ {{0xf77f4004,0x6f0f121b,0x63a98114,0x7c2d521c}}, // _reçu_, lucc, _syen, pear,
+ {{0x3ea5a05d,0x6f1d0171,0xb87b60f9,0x61f64025}}, // _sklt_, otsc, _awít, cayl,
+ {{0x442dc374,0x60c9921d,0x6f0f121e,0x6e3ab21f}}, // vee_, _viem, nucc, _batb,
+ {{0x7cc900c2,0x80076627,0x442484cf,0x7d1d1220}}, // _ośro, _ячме, _ebm_, itss,
+ {{0xba74a68c,0x69c8204d,0x6f1d104d,0x3869c0a2}}, // دانت, ždev, htsc, _ƴar_,
+ {{0x7bdae0ba,0xf1650031,0x26d3248a,0x6f048a14}}, // ectu, _ayọ̀_, _muxo_, šica,
+ {{0x6e3bd221,0x26cb4737,0x9f4b1222,0x58d580ff}}, // rdub, _mico_, yacá_, _розт,
+ {{0x6f1bd223,0x78ad5224,0x6d43e4b4,0x20001225}}, // rtuc, hlav, _prna, _sdii_,
+ {{0x628723b1,0x63aae0e2,0x6d452048,0x21290037}}, // dijo, _cyfn, _nrha, _apah_,
+ {{0x9f5240fa,0x7d1bd226,0x644d0049,0xf772e062}}, // sayé_, ptus, _cnai, شاپ_,
+ {{0x7c3aa011,0x6e3b9227,0x5121a4ef,0x207ac00b}}, // _yatr, _maub, _महुआ_, _קאנא,
+ {{0x412a2792,0x7c3b9228,0x248680c2,0x3e646009}}, // мого_, _laur, ziom_, _jött_,
+ {{0xada62792,0x44205229,0x2c742277,0x6f1d122a}}, // тавл, tfi_, _rädd_, atsc,
+ {{0x61e9c1e8,0x7c2e322b,0x442ea3c1,0x6e2e322c}}, // nbel, sebr, zef_, sebb,
+ {{0x7d1c242e,0xbc1b0095,0x6b63487c,0x2ee0ce56}}, // [cc0] strs, _חופש, акта, _çift_,
+ {{0x20042013,0x1f6630ba,0x09c5e1a6,0x9f4ce02e}}, // ėmis_, _аком, _শ্যা, ladá_,
+ {{0x68e4522d,0x7c3aa4d8,0x3860017b,0xa3a8d22e}}, // spid, _ratr, _koir_, _खुन_,
+ {{0xfce6922f,0x78ad4ed9,0x442f80f3,0x443b0054}}, // водо, clav, geg_, _xaq_,
+ {{0x6aa3a944,0x6f09c205,0x7d09c22e,0x6e3b9230}}, // fonf, jrec, jres, _daub,
+ {{0xdc08e041,0xa7fca20f,0x6728a05d,0x442f8046}}, // _mēģi, _alır, _ppdj, aeg_,
+ {{0x442f9231,0x764e6156,0x6d4640a9,0x229c8066}}, // beg_, _inby, _hrka, zíky_,
+ {{0x8c3ce3df,0x291ca0e0,0x6d41e6ca,0x7c3c7232}}, // doğa, rtva_, dvla, _harr,
+ {{0x4425a1e2,0x6f09d233,0x442ea024,0x290eb234}}, // _fbl_, grec, pef_, sufa_,
+ {{0x25e2c029,0x290a206e,0x4734e8ba,0x6e3b9235}}, // _ट्री_, krba_, гнос, _zaub,
+ {{0xa5bb40f9,0xe7bd207c,0x27f820fd,0x64a3e01b}}, // _aból, _অভ্য, darn_, раќа,
+ {{0x6b7ba087,0xf76feef8,0x59d52026,0x26c043de}}, // _קראנ, تاً_, _दलहर, jmio_,
+ {{0x7f4281ab,0x38b900e0,0x9b5801e1,0xdca3c1c0}}, // nvoq, jére_, вият_, _таци,
+ {{0x3e7540ff,0x26f3040a,0xb5fb42f4,0x7f429236}}, // _måte_, _अबीर_, _aláp, ivoq,
+ {{0x644d08c4,0x6d465237,0x9f4b406f,0x2ba505cb}}, // _tnai, _arka, _vecí_, _गँवा,
+ {{0x2a600037,0x6d4640a9,0x644d1238,0x6d416049}}, // _goib_, _brka, _unai, _álai,
+ {{0x6d46429f,0x961dc041,0x7c3c6729,0xa5bb5239}}, // [cd0] _crka, ziņo, _barr, _gból,
+ {{0x442f8b9c,0x160ea6a8,0x2ba7523a,0xd56761b4}}, // weg_, _सागर_, _कुणा, ктеп,
+ {{0xb5fd84ad,0x442f923b,0x5bcb9008,0x7afd123c}}, // _loše, teg_, िश्व, lsst,
+ {{0x69dd00e0,0x63ad123d,0x644e723e,0xb4bf8033}}, // ncse, _kyan, _enbi, ीदे_,
+ {{0x38b9e004,0xef1f0181,0x93c920e0,0x7875c071}}, // lère_, ntü_, _تاکہ_, _jáve,
+ {{0xa3acc010,0x6288e4cd,0x7e6d523f,0x9f420326}}, // _गरज_, jido, lkap, jaký_,
+ {{0x68e2c1cd,0x3ec780db,0x291d9240,0x59a74f30}}, // _atod, _асаб, stwa_, _कुतर,
+ {{0x7afbd11b,0x6e3c62a4,0x7c3d405d,0x6d477241}}, // tsut, _zarb, _jasr, _irja,
+ {{0x38b9e0fa,0xaca36125,0x7c84809e,0x2d99c00e}}, // hère_, _nrịb, _туре, çse_,
+ {{0xd2598041,0x6602c088,0x68fbcc00,0x628407ad}}, // _viņu_, _edok, rsud, _smio,
+ {{0xb7d601e9,0x644f5242,0x24894013,0xf770a233}}, // _aṣap, _onci, kiam_, _وام_,
+ {{0xa3df0f30,0xf2c9200b,0x6f09d243,0x6d035244}}, // दुल_, _זע_, prec, _लिंग_,
+ {{0x69dac00b,0x7cd3e12a,0xc8cfe8b1,0x2b46c017}}, // _אַוו, _mări, _संकट, _groc_,
+ {{0x63ad1245,0xa80646df,0xd29520ff,0x7c229246}}, // _dyan, zdığ, арсь, zfor,
+ {{0x05a42028,0x7c965247,0x291fc04e,0x38b9e004}}, // _खुशब, грац, ltua_, gère_,
+ {{0x26c0416d,0xa3dff248,0x63ad0030,0x6a79e0f9}}, // umio_, धुर_, _fyan, _fùfù,
+ {{0x44313249,0x6e3d4037,0x261bd24a,0x6146124b}}, // [ce0] bez_, _dasb, _यानी_, _сепа,
+ {{0x9f4ca066,0x66c96013,0xfaaa60eb,0xb4636407}}, // _sedí_, _užkl, رهاي_, бкул,
+ {{0x5d86613a,0x8c4681c0,0x645ce046,0x85f6e00b}}, // دلال, леже, örii, רמעס_,
+ {{0xfc3247f9,0x2489524c,0x7ae40143,0x6288f24d}}, // _احد_, ciam_, _ktit, zido,
+ {{0x2ca4c00a,0xf41f124e,0xab62803c,0x628521cd}}, // vomd_, onär_, _şübh, _ymho,
+ {{0x291ee48f,0x4432524f,0x2c71c04a,0xda7260e0}}, // ytta_, ley_, _ráda_, _اُس_,
+ {{0x68e2d250,0x25ad8187,0xbddb4733,0xfbb86ff1}}, // _stod, _eyel_, _inèr, _अरिम,
+ {{0xfaa69251,0x6f93613a,0x386dc939,0x68e4000a}}, // _сабо, _الفض, gker_, _otid,
+ {{0xb274e01b,0x60cd06a5,0x3a74f06d,0x2e16a6b0}}, // илиш, _riam, илир, _مباح,
+ {{0x27e00162,0x44324054,0x60c29252,0x2bc0d253}}, // _afin_, hey_, lmom, शेषा,
+ {{0x6286410d,0x2ba424ba,0xa2e6b254,0x291fc534}}, // _omko, _खुला, _соед, atua_,
+ {{0x3ea68110,0xb5fd8480,0x539ba0be,0x2912461d}}, // noot_, _toše, _איבו, kuya_,
+ {{0xa06af255,0x291fc987,0x9f512066,0x786b2046}}, // _пада_, ctua_, razí_, _hüvi,
+ {{0x60d600b0,0xe0df42d9,0x38b9eaed,0x6440d256}}, // _duym, _bròn_, tère_, ldmi,
+ {{0x7c3d5257,0x443dc0e8,0xdcbb41e1,0xceb3a087}}, // _pasr, _yaw_, еща_, מיש_,
+ {{0x60d5629c,0x443dd258,0x7640d259,0x6e3d525a}}, // _ruzm, _xaw_, ndmy, _qasb,
+ {{0x2ca68acd,0x6e9520ff,0x3b554d6d,0x9c140079}}, // [cf0] dood_, _випу, шкар, _mọlt,
+ {{0x6d48b25b,0x2489525c,0xda78604f,0x9ed9216c}}, // _orda, siam_, лях_, _имот_,
+ {{0x386dd25d,0x660120c4,0x628fe0fa,0x998d00c2}}, // yker_, ólku, écoi, żeń_,
+ {{0x60c2925e,0x2485a17b,0x2912525f,0x261b2466}}, // gmom, _smlm_, buya_, _बाडी_,
+ {{0x64db2010,0x7e61b260,0x07a5839d,0x61f566fa}}, // _मंडळ, _polp, јалн, _tezl,
+ {{0x6d48a320,0xcb6680de,0x20048157,0x443dc076}}, // _brda, раше_, _edmi_, _saw_,
+ {{0x3ea5f261,0xe509e918,0x6289d262,0xe6dec088}}, // volt_, _विधि_, tieo, _rịọ_,
+ {{0x60ddc019,0x34958143,0x9b05c143,0xfaff03ac}}, // ísmo, _табр, азид, ksë_,
+ {{0xe456c099,0x6d48b263,0x2ba5046f,0x645bc333}}, // ржы_, _erda, _गुला, rjui,
+ {{0x6d499264,0x753b9265,0x161ca029,0x660404b4}}, // _irea, _isuz, _नायर_, _sdik,
+ {{0xe1ff0064,0x672d1266,0x3ea5e0e0,0xb035c04e}}, // nków_, _apaj, rolt_, _внеш,
+ {{0x614603fc,0xa6dee016,0xa96a01e3,0x6739c05f}}, // аема, _trưn, вима_, _tswj,
+ {{0x7ae53267,0x61f73268,0xe3b2050f,0xe9cec51b}}, // _atht, _bexl, ترد_, _фк_,
+ {{0x2b4905df,0x61fbd11b,0x4427e17b,0x6d444024}}, // _drac_, laul, _ubn_, vvia,
+ {{0xe81723af,0x26cfc610,0x44290c8a,0x6f0d5269}}, // _तारा_, _kigo_, _fba_, erac,
+ {{0xcec8e016,0x6d44c03b,0xa7fb403e,0x5ed7a07c}}, // _hộp_, _šiai, _poña, _সবাই,
+ {{0x06866de1,0xfc3f41ae,0x26cfc071,0x2240526a}}, // [d00] аган, _daí_, _migo_, rdik_,
+ {{0xdb01a052,0x61fbd26b,0x38b907da,0x290dc0ba}}, // _kylä, haul, méra_, irea_,
+ {{0x6441f26c,0x60c2926d,0x61fc326e,0x1dabf26f}}, // idli, tmom, marl, _घुमत,
+ {{0x3205a0e8,0xe9a641b1,0x9f5822a8,0x7f8ce053}}, // _adly_, _вамп, jaré_, ענאַ,
+ {{0x61fbca81,0x3ea69270,0x6cc6a0d7,0x9f4324cd}}, // daul, root_, айда, bajó_,
+ {{0xcec8e067,0x60c45271,0x443eb272,0x2ca7a061}}, // _nộp_, lmim, _qat_, gond_,
+ {{0xd467a1e3,0x6d410018,0x290dd273,0xb5fb0009}}, // рије_, ālai, erea_, rház,
+ {{0xec1686b0,0xd838e06e,0x83fca04d,0x27edc009}}, // _خورد, moči_, _dođa, gben_,
+ {{0x29005274,0x1da50010,0xb273c4a4,0x61faeef5}}, // nsia_, _गुंत, ільш, tatl,
+ {{0xc7c6f275,0x69b94010,0x443ea153,0x2bc36046}}, // исни, ्धती, _uat_, वेवा,
+ {{0x752d1276,0x29005277,0x290dc12a,0xf8bf2004}}, // _spaz, hsia_, area_, ntée_,
+ {{0x7ebd00e0,0x76428f3d,0x4fa6c54a,0xe818ed0b}}, // képe, ndoy, _киев, _दावा_,
+ {{0x26cef278,0x7ae9c017,0xf27bc557,0xdb03e066}}, // _tifo_, cpet, _בראש, _vyná,
+ {{0x4efbc095,0x628b9279,0x248b000a,0x7d0d527a}}, // _יהדו, zigo, sicm_, vras,
+ {{0xe937e049,0xfc3f4071,0x3abbe557,0x6722927b}}, // _نسيت_, _paí_, _שמונ, htoj,
+ {{0x38a1c0c2,0x7cd3e12a,0x63af51ef,0x6f02c133}}, // wórz_, _căru, _pycn, _kvoc,
+ {{0xe2ab2938,0xb4b74028,0x66d5a013,0xcc341049}}, // [d10] _لادن_, _छठी_, _iške, _ذريع,
+ {{0x6440927c,0x27edc0e0,0xe6136233,0x7e54614f}}, // _aami, zben_, تشر_, овує,
+ {{0x4426927d,0x27edc0e0,0x05a7527e,0x67228c64}}, // ffo_, yben_, _कुरब, etoj,
+ {{0xdb1c607b,0x7e554062,0x6288a090,0xb5fdc013}}, // _agró, _انحص, _amdo, nkšt,
+ {{0x6441f27f,0x28c80f92,0x60db6012,0xd91b8052}}, // ydli, रगति, _čumu, тье_,
+ {{0x61e2d280,0xc484a71b,0x26d87281,0x78a8e320}}, // _afol, олік, _furo_, jodv,
+ {{0x26d95282,0x96f86677,0x49170292,0x61fbd283}}, // _huso_, _вест_, भूतो_, taul,
+ {{0x83fca16d,0xf8db42b4,0x7875c02f,0x2ca7aaaa}}, // _vođa, _बढाय, _báva, sond_,
+ {{0x9f583284,0x61fc3285,0x98a92133,0xb4db42d9}}, // raré_, yarl, _apač_, _brài,
+ {{0x6f02c03a,0x27edc0e0,0x26d95286,0x61fbd287}}, // _dvoc, sben_, _muso_, saul,
+ {{0x81a9607c,0x07a5e376,0x141aa0be,0x26d86005}}, // খের_, санн, _תועב, _xuro_,
+ {{0xb87b7288,0xfc320049,0x6441b289,0x3e7660c4}}, // _stíl, أحد_, _hali, _mæta_,
+ {{0x38b2128a,0x889a8087,0xa059e711,0xe7a7528b}}, // nári_, רברי, _مؤثر_, _कुलप,
+ {{0x225fc0e0,0x7cd3f044,0xfd490125,0x27fd80fd}}, // ljuk_, _săru, lomọ, hawn_,
+ {{0x7643a037,0x6f02c361,0x7cd3f044,0x6d4bc012}}, // idny, _zvoc, _păru, _hrga,
+ {{0x7ebd08cc,0x28d004f6,0xb5fb11be,0xeabf528c}}, // tépe, _सूचि, skár, _chù_,
+ {{0x6e35204d,0x27e0528d,0x69d60009,0x63a9113e}}, // [d20] bezb, rcin_, _igye, üent,
+ {{0xbddb128e,0x6aa9c3be,0x2f566792,0x2900413c}}, // ndèn, hoef, йтес, rsia_,
+ {{0xa96a328f,0x628e3290,0xdb1c6733,0x130a612a}}, // _сина_, mibo, _agrò, уней_,
+ {{0x2902c0db,0x6441b291,0xd838e42e,0x2900515e}}, // škai_, _aali, roči_, psia_,
+ {{0x225fc0e0,0x3ebf8569,0xf8bf20fa,0x44269292}}, // djuk_, _dhut_, stée_, rfo_,
+ {{0x628e3293,0x8b060064,0xbb3b0087,0x6440866b}}, // nibo, częś, טעני, _wami,
+ {{0x649601d7,0x4426800f,0x5f43a049,0x61e41294}}, // rġij, pfo_, كنول, _ofil,
+ {{0x261a0308,0x26d21295,0x6f040098,0xb176e016}}, // _भाषी_, _niyo_, _ovic, _ngượ,
+ {{0x7e665296,0xb5fdc041,0xe819c66f,0xb8ffaf30}}, // _mokp, ekšs, _धारा_, _धू_,
+ {{0x61e2814a,0x6aad088b,0x6d4bd297,0x527b600b}}, // lcol, _skaf, _drga, ָנטא,
+ {{0xa96aa085,0x499aa14a,0xd5b2c050,0x29021298}}, // _حمام_, _стая_, _کفش_, eska_,
+ {{0x6a6b20cb,0x75245299,0x7af601cd,0x06e721a6}}, // _lüft, ktiz, _bwyt, _ফিলি,
+ {{0x7d02929a,0x9f47e65f,0x05a74029,0x6d4bc012}}, // nsos, _menù_, _कुंब, _grga,
+ {{0x8c43f29b,0x6298a105,0x3f804013,0x26da20f9}}, // _нече, nnvo, dziu_, _bupo_,
+ {{0x26da329c,0x61fd129d,0x61e401cd,0x26d16019}}, // _cupo_, pasl, _ffil, _rizo_,
+ {{0xd6d82376,0x2902129e,0x76444f09,0x248900e8}}, // стр_, bska_, gdiy, _umam_,
+ {{0x68f601cd,0x7e7d0049,0x195841ba,0x6443a46b}}, // [d30] _gwyd, mhsp, баты_, ydni,
+ {{0x51f861ba,0x6442c28f,0xf21f00c5,0x76444025}}, // йную_, _laoi, _भाड़_, adiy,
+ {{0xd9996049,0x20cd6012,0x629c6187,0x2c6ba22e}}, // جنات_, _užim_, _djro, _mødt_,
+ {{0x2901729f,0x6442c28f,0x7d1640ae,0xdb1f6193}}, // rsha_, _naoi, buys, _ávís,
+ {{0x4ad90010,0x6e2d01a7,0x29016227,0x61ff403c}}, // ढदिव, _mbab, ssha_, maql,
+ {{0x83fca2e2,0xc05b60ff,0x2c6ba271,0x63bd4018}}, // _dođo, _сім_, _nødt_, _izsn,
+ {{0x442ca07d,0x394952a0,0x225fc0e0,0x6cc5d2a1}}, // _dbd_, lvas_, tjuk_, ойка,
+ {{0x7b084069,0x61e28ea3,0x628e20bb,0x7643a037}}, // _ástæ, ccol, zibo, sdny,
+ {{0x61e3e435,0xb7bd812a,0x3ce90048,0x7641b2a2}}, // _pfnl, _obţi, _ntav_, _taly,
+ {{0x26c00b39,0x68ed421c,0x4489e307,0x656f12a3}}, // _shio_, ipad, рбон_, dych,
+ {{0xe7efa010,0x64456569,0x29020064,0x7f4d12a4}}, // _घ्या_, idhi, wska_, _araq,
+ {{0x2bbe4077,0x26da3022,0x22436320,0x442952a5}}, // ्धमा, _rupo_, _lajk_, jfa_,
+ {{0xdea3c043,0x645572a6,0x61fb8b56,0x27ff00c4}}, // _ویڈی, _onzi, _meul, ðuna_,
+ {{0x9f47a06f,0x6ff9a00b,0x479ac053,0x8f9ac1a1}}, // zaný_, יפֿג, _דינס, _דיני,
+ {{0xe2972ea0,0x7e66408b,0xf74332a7,0xe73a3156}}, // цар_, _pokp, меро, шев_,
+ {{0x7ae9804e,0x7c2d08b7,0x2a66c048,0x7e7d12a8}}, // _otet, _gbar, _xoob_, chsp,
+ {{0x7fd6284f,0xb466664d,0xceb44497,0x64556098}}, // [d40] _міні, окал, rlə_, _bnzi,
+ {{0x3f8052a9,0x7c2d12aa,0xaec360ff,0x2d8052ab}}, // rziu_, _zbar, дбул, rzie_,
+ {{0x7c2d008b,0x9f4172ac,0xe9da92ad,0xddc08026}}, // _ybar, nchè_, аке_, _pomů,
+ {{0x136a8dd1,0xfbae004b,0xf2202046,0x9c87604a}}, // ишли_, _घडाम, _बाड़_, _počá,
+ {{0x9f5ca05c,0xddc2d2ae,0x2b4d8355,0x644404ec}}, // mavé_, _slož, _crec_, _maii,
+ {{0x3a2d8005,0xc1730095,0x3d120010,0x2a66c048}}, // _ebep_, וחד_, _तिने_, _soob_,
+ {{0x66dee041,0x6aaaa87d,0x61fab2af,0xd6d7a5be}}, // _rīko, roff, _vetl, оты_,
+ {{0x6443f2b0,0x2a66c286,0xf484a6b0,0x6aaab2b1}}, // _aani, _qoob_, _کاری, soff,
+ {{0x6f044283,0x2b4d81ab,0x2c1b2960,0x93bca12a}}, // nsic, _grec_, _बालू_, _scăd,
+ {{0x7c2d12b2,0x0f574095,0x6d4e72b3,0xf76fc049}}, // _sbar, קיים_, _krba, ضاً_,
+ {{0x656f0bca,0x2a66d2b4,0x98ba4061,0x3866caaa}}, // wych, _toob_, _aspč_, _toor_,
+ {{0xe292c6b0,0x764400a2,0x656f00c2,0x443823c5}}, // _غذا_, _caiy, tych, eer_,
+ {{0x27e6c163,0x6b8292b5,0x2b4012b6,0xe1ff0e53}}, // _ifon_, nzog, _csic_, raó_,
+ {{0x6a7cc004,0x95f54960,0x3ea9c49f,0xe821e7b3}}, // _défe, ेण्ट_, čate_, _माया_,
+ {{0x644361ae,0x39400019,0x7d0452b7,0x224681e7}}, // ônim, _esis_, esis, kdok_,
+ {{0x1dca8046,0x6e2d12b8,0x2b4947a0,0x9b894049}}, // िधात, _ubab, tvac_, منزل_,
+ {{0x142b2139,0x3ce906dd,0x6f044090,0xa3e00021}}, // [d50] _кіно_, _utav_, gsic, _तलत_,
+ {{0x7af8e05f,0x291901a5,0xfc336049,0x6443e36b}}, // _kwvt, musa_, بحر_, _xani,
+ {{0x6600d2b9,0x291912ba,0xc6a78b1e,0x25a24097}}, // kamk, lusa_, орди, _exkl_,
+ {{0x644532bb,0xb5fd8143,0x2904d2bc,0x61e44425}}, // _hahi, _vošo, ksma_, ccil,
+ {{0xd126cab5,0x98176050,0x61fd4018,0xa85a4076}}, // _ثم_, _وبسا, _iesl, _הדרכ,
+ {{0x64472aaa,0x7c38a3c5,0x2c79a011,0xb5fd8133}}, // ndji, gevr, _bèda_, _tošo,
+ {{0x68e98064,0xb902810a,0x6d468e9d,0x61e5613a}}, // _wted, _नू_, _škat, mchl,
+ {{0x60db82df,0x442d8f12,0x6d4f40a9,0x2b4d92bd}}, // _tuum, _ube_, _krca, _trec_,
+ {{0x61fd4121,0x22648066,0x6d4f40ae,0x61e56256}}, // _mesl, žská_, _jrca, ochl,
+ {{0x6a7cd2be,0x291912bf,0x66dee041,0x200172c0}}, // _réfe, dusa_, _tīkl, nahi_,
+ {{0x66e8e295,0x9f5ee5cd,0xe739e7ca,0x644412c1}}, // _někd, natè_, щен_, _paii,
+ {{0x60dd52c2,0x6f044c37,0x3a382854,0xb87b6071}}, // _ousm, ysic, werp_, _huíd,
+ {{0x5ed2c07c,0x291912c3,0x7645205d,0x69d9cc98}}, // _সবচে, gusa_, _bahy, _igwe,
+ {{0x2cadc3c5,0x60c4e197,0x9f05e243,0x38b46052}}, // loed_, _èimp, _فوتو, päri_,
+ {{0x644532c4,0x7e7b92c5,0x200160eb,0x4439119d}}, // _dahi, _olup, dahi_, bes_,
+ {{0x6601f2c6,0x53a6084f,0x26ddd2c7,0x6d4f5192}}, // lalk, _найб, _kuwo_, _brca,
+ {{0xe0d74139,0x3aeb40e0,0x60c412c8,0x6d41b15f}}, // [d60] зву_, مبلی_, _ihim, _lsla,
+ {{0x61e452c9,0x60dd52ca,0x60d572cb,0x200172cc}}, // scil, _dusm, _cizm, gahi_,
+ {{0x78b6406f,0x7bd9c079,0xf99220eb,0x9f47a089}}, // plyv, _ogwu, _سبد_, ybná_,
+ {{0x7e7b8057,0x6d5ca569,0x7eb4e5df,0xdca65097}}, // _clup, _àrai, ràpi, _хами,
+ {{0x60c2c07d,0x3897c12a,0x2245b2cd,0x41b94914}}, // _rhom, cări_, _balk_, _आरएस,
+ {{0x6601f2ce,0xd6d980ff,0x81d5407c,0x2905f2cf}}, // jalk, іті_, _সড়ক_, ksla_,
+ {{0x9f5ee0dd,0xa967a5da,0x443a72d0,0x6600d2d1}}, // ratë_, зија_, mep_, tamk,
+ {{0x387fd2d2,0xc19b6095,0x443912d3,0x0d7760eb}}, // ghur_, _השלי, xes_, _عینک_,
+ {{0x98a3812a,0x76aae052,0x442252d4,0x48aaf2d5}}, // _чите, йтов_, _mck_, йтом_,
+ {{0x4439011e,0x7bd9c385,0x3ae1e067,0x6600c9e9}}, // wes_, _egwu, _bóp_, samk,
+ {{0x3897c12a,0x2cace1cd,0x2cadc037,0xddc9c00a}}, // zări_, rodd_, boed_, rkeš,
+ {{0x66028167,0xb5fb0009,0xf1be41c9,0x387fc2a1}}, // naok, aján, ्धान, chur_,
+ {{0x661d12d6,0x9f5ee0d1,0x61e572d7,0x386900fc}}, // rgsk, zatè_, ychl, _voar_,
+ {{0x61fd4121,0x6d41b259,0x98bfa0a9,0x9f5ee089}}, // _sesl, _ysla, _vruć_, haté_,
+ {{0x68e9604d,0x44e1e031,0xf1a42bbb,0x291912d8}}, // _čedo, _gó_, ертн, susa_,
+ {{0x3897c12a,0x2db7e095,0x179be053,0xfd51c125}}, // tări_, _מלון_, _ליוב, _nkeọ,
+ {{0x7e698996,0x200ca1cd,0xa5bb52d9,0x443a600a}}, // [d70] _roep, _oddi_, _abór, fep_,
+ {{0x60c9c1fc,0xf77048b8,0x7e6992da,0x3897c12a}}, // zmem, داف_, _soep, rări_,
+ {{0xb87b69fe,0x61e572db,0x6f1ae0a9,0x7e698d53}}, // _ruíd, rchl, nutc, _poep,
+ {{0x6d42c265,0xb87b6005,0x6a7360b0,0x6280d2dc}}, // _osoa, _suíd, _sıfı, jhmo,
+ {{0x63b560c2,0x443a6106,0xd469a8ba,0xa3b96077}}, // _wyzn, bep_, жиле_, _चरक_,
+ {{0x660bc058,0x6e22c0d5,0x764652dd,0x940165fc}}, // _sdgk, _acob, _zaky, yihə_,
+ {{0x6d346874,0x6d4d41cf,0x61e60156,0x787f000e}}, // _перф, lvaa, yckl, _zëve,
+ {{0x6a70267c,0x248d804d,0xa3c2f2de,0x7658e0ca}}, // _häft, _umem_, ्धन_, _anvy,
+ {{0xe821e8c6,0x200ca057,0x2245a095,0x9f582005}}, // _मादा_, _fddi_, _talk_, iará_,
+ {{0x3e89e0ff,0x4df4e09e,0x7d0992df,0xfe71e049}}, // ійно_, няют, _ives, ئدة_,
+ {{0x38960041,0x64a612e0,0x6601f2e1,0x7d1af2e2}}, // kārt_, дана, ralk, guts,
+ {{0x6448f2e3,0x2905e31b,0x68fb8125,0x39436071}}, // addi, rsla_, _kwud, _msjs_,
+ {{0x5d69caab,0x2905f2e4,0x7c3bd2e5,0xaca36088}}, // _филм_, ssla_, meur, _anọc,
+ {{0x200212e6,0x7d1bd2e7,0xaec4e0ff,0x6e2d4171}}, // saki_, muus, _збіл, efab,
+ {{0x60c412e8,0x2c7c4019,0x644652e9,0x7d1af2ea}}, // _thim, _aída_, _paki, cuts,
+ {{0x3994610a,0x9f47f2eb,0x3ea0000e,0x6b9b4012}}, // _तs_, _menü_, _ujit_, _žuga,
+ {{0x7d1bc2e4,0x4432000f,0x9f5825df,0x7647600f}}, // [d80] nuus, _iby_, sarà_, _bajy,
+ {{0x200d805d,0x291a72ec,0x61e992ed,0xe73a260f}}, // _adei_, tupa_, _afel, _део_,
+ {{0xaca46125,0x2247e163,0x248052ee,0x3a3a6c31}}, // _haịr, _mank_, phim_, repp_,
+ {{0xed5a50aa,0x7d1bd2ef,0x6f1c2f55,0xa5bb407b}}, // _дом_, kuus, murc, _icón,
+ {{0xaca46096,0xc6f78139,0x44e3a071,0xc0e3804e}}, // _asụs, дніх_, _añ_, вочк,
+ {{0x3ead4364,0x69c0807f,0x7d1bd2f0,0x69db8108}}, // čete_, _vzme, duus, _egue,
+ {{0x442040fd,0xb05b4156,0x9c7cd09f,0x38960018}}, // ogi_, _ohäl, _luče, nārs_,
+ {{0x64476026,0x941e6982,0x44e452f1,0x2a7dc1cd}}, // _zaji, _पाँच_, _kö_, _clwb_,
+ {{0x6f1641cd,0x2907a025,0x68e445fc,0x764772f2}}, // nryc, asna_, nqid, _yajy,
+ {{0x1620c010,0x66044534,0x7c2412f3,0x6448f2f4}}, // _यावर_, haik, _acir, uddi,
+ {{0x6d5ca6be,0x7c2d41cd,0x018ae052,0x44e452f5}}, // _šraf, yfar, ющий_, _lö_,
+ {{0xae1d43fa,0x9f5820fc,0xdefac03b,0x3eb9a018}}, // _फाइन_, xará_, _дык_, _īsti_,
+ {{0x660452f6,0x6d42c12a,0x2247e143,0x6d43f2f7}}, // daik, _usoa, _fank_, _esna,
+ {{0x69dc72f8,0x442052f9,0xeb918053,0x443af2fa}}, // _ngre, egi_, אָל_, _úp_,
+ {{0xf548c096,0x6f1c20a2,0xb4cae04b,0xf413e557}}, // _hụ_, gurc, लगी_, _ספק_,
+ {{0x6d4d52fb,0x395212fc,0x200332fd,0x6e3c2197}}, // tvaa, _frys_, saji_, aerb,
+ {{0xf548c079,0x44316133,0x200329e6,0x9c7cc143}}, // [d90] _jụ_, _pbz_, paji_, _guče,
+ {{0x3e71c069,0x44e44156,0x78a2c04d,0x645a0018}}, // _hátt_, _dö_, _ljov, ētie,
+ {{0x443cb2fe,0xb5fd8098,0x38b46277,0x6f0992ff}}, // dev_, _hoši, värt_, _svec,
+ {{0xb87b60f9,0x442040ae,0x442ea1b9,0x60d8e78c}}, // _atít, cgi_, eff_, _livm,
+ {{0x64477300,0x3a248057,0xd90f801c,0xf548c8dd}}, // _taji, _dcmp_, شیا_, _nụ_,
+ {{0x28bf4ba0,0x660080d4,0x638583bb,0x388cc0a3}}, // ्षवि, _eemk, _aîné, měry_,
+ {{0xd404a14f,0x7648a401,0xddc1a5f2,0x657987d2}}, // тячи, _dady, _bolš, _şahı,
+ {{0x62829301,0x05c34033,0x645aae96,0x6d4529fa}}, // choo, _शराब, _enti, _msha,
+ {{0x765b810f,0x7d0980a9,0x81d7c07c,0xa3cac010}}, // _inuy, _uves, ়ুন_, ळेल_,
+ {{0xdb0ec13a,0x7c3bd302,0xa3d011f9,0xda0f7303}}, // ádál, seur, _वृष_, ाणित_,
+ {{0x291d9304,0x27298049,0xaca3e079,0x386d8046}}, // luwa_, lún_, _nzụk, _koer_,
+ {{0x6e2520ca,0x6601b305,0x6e3d1306,0x6a72622e}}, // _achb, _helk, gesb, _hæft,
+ {{0x78bbc04a,0x61e9d307,0x4e03ac13,0x386d9308}}, // mluv, mcel, _रजाई_, _moer_,
+ {{0x62953309,0x7d09d30a,0x38a9a02e,0x249fc0c4}}, // gizo, mses, túry_, lnum_,
+ {{0x2a7f8497,0x660443fc,0x291d930b,0x3205e02e}}, // _olub_, taik, huwa_, maly_,
+ {{0x2005ea71,0x6601a0e0,0x4420530c,0x7f440058}}, // lali_, _lelk, ugi_, _tsiq,
+ {{0x7875c005,0xa7a74df4,0xfd65e067,0x7d1d130d}}, // [da0] _cávi, екта_, _nhuậ, cuss,
+ {{0xb21b0065,0x291d8a2d,0x27e9530e,0x69c4130f}}, // nhæn, duwa_, acan_, _izie,
+ {{0x249fc069,0x15f404f6,0xee3a928f,0x386ca057}}, // knum_, _आभार_, зне_, _rodr_,
+ {{0x2a7f8d81,0x7d165310,0x3944807d,0x44fe204a}}, // _club_, prys, _psms_, mů_,
+ {{0x62964121,0x6284428f,0xdb01a121,0x645aa058}}, // miyo, mhio, _eylü, _pnti,
+ {{0x78a2ca2f,0x6448b311,0x6f09d312,0x3ea05313}}, // _sjov, _qadi, dsec, lnit_,
+ {{0x66061314,0x7ae1b315,0x442ea0e2,0xc60b007c}}, // kakk, _cult, rff_, ষ্টা_,
+ {{0x7bde205d,0x6448b316,0x6601a046,0x7d1d0037}}, // _lgpu, _wadi, _eelk, yuss,
+ {{0x7d09d317,0xfe202028,0xb21b0065,0x8fa3833f}}, // gses, _बांस_, fhæn, _бате,
+ {{0x2004d318,0x645ab319,0x6448a0ce,0x2002531a}}, // sami_, _unti, _uadi, _leki_,
+ {{0x7d1d131b,0x7ae1af68,0xe9df407b,0x26c6d31c}}, // wuss, _gult, _baúl_, _thoo_,
+ {{0x66060622,0xb5fd849f,0x62964535,0x877ba053}}, // gakk, _poši, jiyo, _מאבי,
+ {{0x69c2c167,0x7ae4e490,0x6489200a,0xcfaac711}}, // _uzoe, _čitu, džir, _عاصم_,
+ {{0x9f5ee431,0x7c3e678c,0xb05b0262,0xdcdfa3fa}}, // latí_, jepr, kläg, _पूँछ,
+ {{0x9f34214f,0x2249023c,0x77932050,0x6280931d}}, // лері, _taak_, ایجا, _olmo,
+ {{0xba7707f0,0x3ea0531e,0xae1e6026,0xfe1e604b}}, // _ساعت, gnit_, _पाउन_, _पाउस_,
+ {{0x61ed0277,0xad9b4f87,0x20024106,0x48140052}}, // [db0] _ifal, _adúr, _deki_, _смыс,
+ {{0x78bd131f,0x261b21d6,0x6f0d1320,0xdb9c0095}}, // llsv, _बाटी_, _ivac, _מסחר,
+ {{0x7ae2c04e,0x4426c03e,0x443d9321,0x4ea46a00}}, // _luot, _oco_, tew_, урта,
+ {{0xd999813a,0x81ac007c,0x3ea05322,0xb0660052}}, // ونات_, কেও_, cnit_, mpää,
+ {{0x386d823c,0x645d4ea3,0x6483410a,0x291ef323}}, // _voer_, _insi, _võid, huta_,
+ {{0x628080e0,0x2c1e60c2,0xe739a8ef,0x1d164076}}, // _elmo, _पाएं_, дек_, _בקשר_,
+ {{0x386d8aaa,0x6602c143,0x9424e03c,0x61ed01cd}}, // _toer_, _beok, _ötən_, _ofal,
+ {{0x291d84ec,0xd9de2028,0x291900a9,0xee39cb92}}, // puwa_, _मल्ट, krsa_, фни_,
+ {{0x4102c04e,0xdfe5612f,0xddc2c2d1,0x7d09d196}}, // азыв, _औलाद_, _pooš, uses,
+ {{0xb4ab2026,0x752d5020,0x20037324,0x6abbd325}}, // गको_, itaz, _jeji_, sluf,
+ {{0x66061326,0x2018f327,0x7d0d1328,0x672d46eb}}, // takk, ória_, _avas, htaj,
+ {{0x672d5329,0x6833a20f,0x6386e0fa,0x26f0207c}}, // ktaj, sıdı, _séné, _ছিলো_,
+ {{0x672d4012,0x9f5ca066,0x2002532a,0x6484f03c}}, // jtaj, mavá_, _reki_, _bòid,
+ {{0x765d40d1,0x61ed00e2,0x644af32b,0x7eb88019}}, // _ansy, _efal, _gafi, lípt,
+ {{0x2919081f,0xe5170a2a,0x2b15254a,0x9103260f}}, // arsa_, _तिथि_, льтр, ипре,
+ {{0x63a98156,0x2bde2354,0x6a714b57,0x644d4493}}, // _oxen, _मलका, _såfr, gdai,
+ {{0x4cc8007c,0x6603e20e,0xa5074822,0x6d55732c}}, // [dc0] _রংপু, _kenk, кета_, _arza,
+ {{0x2bc66861,0x44fe204a,0x764bc5c8,0x9c7cc012}}, // _वरदा, pů_, _lagy, _muča,
+ {{0x6d464064,0x7e7bd32d,0x2002532e,0x403360ff}}, // _wska, kkup, _teki_, реєс,
+ {{0x7d1e732f,0x38bac0dd,0xd7064501,0x6281a239}}, // rups, përi_, _изми, _allo,
+ {{0x2006813a,0x27e09330,0xb606206e,0x60c99331}}, // raoi_, žino_, pušč, _dhem,
+ {{0xca7661ba,0xbebb200e,0x60c99332,0x290ca171}}, // _румы, stëv, _ehem, _pvda_,
+ {{0x4a9b200b,0x1a9b200b,0x443fc253,0x37d5a07c}}, // ויסג, ויסע, feu_, _স্বর,
+ {{0x4427f333,0x68e2c013,0x7d1cb334,0x249820cb}}, // _acn_, _puod, _årsm, hirm_,
+ {{0xc6934095,0x4427f335,0x64960026,0x78785336}}, // יאה_, _bcn_, tšin, _cívi,
+ {{0x2486817a,0x4427e733,0x64960013,0xd8f8e0ff}}, // lhom_, _ccn_, ušin, нної_,
+ {{0x441b600b,0x261bc010,0x621b600b,0x6298a06e}}, // וויס, _याची_, וויק, mivo,
+ {{0x78a29337,0x44e72081,0x0446cf2d,0x60db8c12}}, // nnov, _rõ_, _једн, _xium,
+ {{0x3e754156,0xddc41338,0xe0df41e9,0xddd61339}}, // _mått_, _poiš, _asòn_, _plyš,
+ {{0x68e3edb2,0x68e40693,0x7ae3e265,0x8438e13a}}, // _fund, _duid, _funt, _أكثر_,
+ {{0x6282d33a,0xdee3e1e3,0x35c700c2,0x4423200e}}, // _kloo, _који, रेज़, rgj_,
+ {{0x44e72016,0x6abd6f92,0x3e754277,0x61ed0167}}, // _võ_, ्ग्र, _nått_, _ufal,
+ {{0xb05b0845,0xead4409e,0x645f133b,0x2120405d}}, // [dd0] smäs, роть, _inqi, guih_,
+ {{0x644d46f1,0x60db933c,0xdce720c2,0x68e3f33d}}, // sdai, _sium, zyję, _yund,
+ {{0x60dc68f8,0x20cd6055,0x6b89c576,0xe1f9c013}}, // _dirm, _džip_, zzeg, ybų_,
+ {{0x224ca17b,0xb21b0065,0x78a281ce,0xb4bf8b5b}}, // _aadk_, skæf, gnov, ुगे_,
+ {{0x86256896,0xa82560d0,0x721a4008,0x3e742156}}, // _مکال, _مکان, _אורח, _tätt_,
+ {{0x32094524,0x61ed533e,0x03a5b33f,0xdb1aa009}}, // laay_, lcal, лило, _sztá,
+ {{0x6605206e,0xd5b87340,0xaca3a079,0xeb35a062}}, // _mehk, _аст_, _ntụn, ارتخ,
+ {{0xfd5e2088,0x7e7bc0e4,0x3949010a,0x9f5128cc}}, // _anyị, ukup, _osas_, lazó_,
+ {{0x2007b341,0xdd1243df,0x9555c13a,0xaca3a088}}, // pani_, _müşt, اخبا, _atụn,
+ {{0x6b89c0c2,0x660401dd,0x0dc89342,0x212dc17b}}, // rzeg, _seik, _руси_, rteh_,
+ {{0x443fd343,0x20049344,0x7ce7a121,0x99c0007c}}, // peu_, _yemi_, _kırm, _ইলেক,
+ {{0xdb0d1345,0x7ae4111b,0x44e8e004,0x3205a0e0}}, // _azañ, _puit, _dû_, _hely_,
+ {{0x764d0054,0x61fdc0c4,0x27edd2c2,0x3205a0fb}}, // _daay, ðslu, mcen_, _kely_,
+ {{0xdee3106d,0x9407a03c,0x7c298133,0x8f9ac1a1}}, // сори, tinə_, _lcer, _איני,
+ {{0x644d0025,0xa3abe354,0x9c130088,0x24991346}}, // _faai, _कुच_, _kọle, bism_,
+ {{0x61fbd347,0x6d499348,0x61464de1,0x753b8133}}, // nbul, _osea, леба, _opuz,
+ {{0xa81a6043,0x6e244ffe,0x81b5c07c,0xd2a744a7}}, // [de0] فتار_, rgib, ছের_, укте_,
+ {{0x2005a046,0x4425f349,0x4a462da4,0x644d02be}}, // _neli_, ngl_, _снов, _zaai,
+ {{0x61e90c35,0x6441e12a,0xed45b34a,0x8d766049}}, // žele, ieli, _بھ_, داءا,
+ {{0xaca46125,0xfce68624,0xaaba80d0,0x127a4049}}, // _krịs, годо, ردار_, _أحدث_,
+ {{0x7c2981ce,0x628400ce,0x7641ea47,0x6496007f}}, // _dcer, _mlio, kely, jšim,
+ {{0x26c0534b,0x2242017a,0x2005b34c,0x248680f7}}, // llio_, hekk_, _celi_, shom_,
+ {{0x7640d34d,0x26ddc0d1,0xd8d7200b,0x6284134e}}, // temy, _biwo_, נוצט_, _olio,
+ {{0x26dce12d,0x75d68243,0x5b14caf5,0x6441f34f}}, // _sivo_, _ميزا, амит, eeli,
+ {{0x200a3350,0xf8b42029,0x44e9d351,0xb5fd835d}}, // kabi_, ंकाय, _cú_, _vošt,
+ {{0xdee6a080,0x44e9d352,0x9b58e90f,0x644e7353}}, // _божи, _dú_, виот_, _jabi,
+ {{0x09d7e07c,0x7ae6494b,0x44e9c06f,0x5275b075}}, // _দ্বা, _lukt, _eú_, _туту,
+ {{0x7d1bd354,0x39491355,0xf127012e,0x6442813a}}, // brus, _psas_, льго, neoi,
+ {{0x60dd5356,0xe6c4e069,0x46f6c691,0xf8bf00fa}}, // _rism, þjóð, _счет, rlé_,
+ {{0x2005229b,0x62841357,0x64428049,0xf8bf0066}}, // úli_, _elio, heoi, slé_,
+ {{0xc87961fc,0x29024125,0x6d5a709f,0x867be1a9}}, // _îşi_, _awka_, _štan, _ארגו,
+ {{0x56945358,0x57f540ff,0x6d58e037,0x66052171}}, // баст, рпат, _irva, _wehk,
+ {{0xe7f7c893,0x660aa05d,0x20094854,0x60cd1359}}, // [df0] ीरता_, kafk, raai_, _iham,
+ {{0x200b135a,0xbebb200e,0x51872052,0x291ca0a9}}, // laci_, rtës, _бума, krva_,
+ {{0x7c2ae2a8,0xa3e90028,0x3ceae466,0x2243204d}}, // _mcfr, _बला_, _घूमे_, mejk_,
+ {{0x200b0ed9,0xa3c9f35b,0xd5b8735c,0x61e48b2e}}, // naci_, लेट_, усу_, žiln,
+ {{0x7ae9607f,0x3ce6c425,0x67228013,0x6f0f40ce}}, // _četu, _nuov_, guoj, _uvcc,
+ {{0x8704804e,0x6b8d4098,0xd3364679,0x764e735d}}, // сяце, mzag, _דרשה_, _gaby,
+ {{0x442b535e,0xa01b135f,0x9c7cc381,0xb5fb4049}}, // _kcc_, rhöj, _vučn, _gnác,
+ {{0x200b0fe8,0x7ae64187,0x26dea057,0x78adc16d}}, // jaci_, _yukt, _eito_, čavo,
+ {{0x0685f0f6,0x6283e05c,0x68e76098,0xfd4ca016}}, // игин, _plno, _hujd, _khiể,
+ {{0x26deb360,0x44eab361,0x644e7362,0x98a060a9}}, // _gito_, _fù_, _xabi, rtić_,
+ {{0xf771e0d0,0x442b4733,0x3da7a60c,0xa3cba010}}, // زاد_, _occ_, _сраб, रेख_,
+ {{0x200a3363,0x6d4f8005,0x7d1bd364,0x7521e05d}}, // wabi_, _ácas, prus, rulz,
+ {{0x98484497,0xa3c9e621,0x660641ef,0x9c130125}}, // xşı_, लेज_, _rekk, _gọme,
+ {{0x61e2c06e,0xdb052057,0xceb90064,0x7ccac0a2}}, // _zgol, _exhí, stęp_, _iƙra,
+ {{0x6abb80bf,0x78bb8b01,0x7ae76048,0xb8ce2028}}, // _okuf, _okuv, _nujt, _कद_,
+ {{0x7643b365,0x3207e037,0x200b1366,0x7d1cf367}}, // heny, _heny_, caci_, árse,
+ {{0x4e1a204e,0x60d70087,0xa20662d8,0xbebb200e}}, // [e00] _июня_, _דוקא_, ипад, rtër,
+ {{0x66065368,0x9c7ca098,0x757b0008,0xc5d7c07c}}, // _wekk, _kiče, _סטיפ, _সভাপ,
+ {{0x764f5369,0x7643b36a,0x3207f36b,0xb146706d}}, // _facy, deny, _meny_, рнел,
+ {{0x3136e00b,0x6f04000f,0x7ae6536c,0x644e6781}}, // ינעם_, _kwic, _tukt, _wabi,
+ {{0x6497c12a,0x87038766,0x1154809c,0x201aa1de}}, // rţil, ояще, склю, ópio_,
+ {{0xed59936d,0xaca46125,0x7d02c00f,0x57f3a5da}}, // _drž_, _krọs, _rwos, општ,
+ {{0x2731a016,0xf8bf2004,0xa7b8a5be,0x6723b36e}}, // _cơn_, quée_, ылку_, gunj,
+ {{0x395a2057,0x6f02c0d1,0x629ae867,0x98a6880e}}, // _mrps_, _pwoc, wito, _тибе,
+ {{0x635ca1fc,0xc866e881,0xd90dc555,0x25bf8037}}, // _sănă, атни, فین_, _gyul_,
+ {{0x7643a037,0x3eb82536,0x60c28eac,0x200b136f}}, // ceny, jort_, llom, waci_,
+ {{0x6b8d5370,0x75244996,0xe457600b,0x200b1371}}, // zzag, huiz, בייט_, taci_,
+ {{0xa17ba00b,0x657af09b,0x3cfd8ca9,0x22820031}}, // _שטאט, ryth, रीके_, _dóko_,
+ {{0x24805297,0xfbd2c11a,0x2d9e226a,0x320b1372}}, // dkim_, देशम, úten_, racy_,
+ {{0x38624061,0x7ae09373,0x61e3e194,0x8c43ef0e}}, // _ankr_, _limt, _ggnl, _мече,
+ {{0xbc07430e,0x60c29374,0x98b88157,0x6d4722be}}, // ичай, klom, _aprč_, uwja,
+ {{0xa3cbae8e,0x394b4037,0x6b8d4108,0xf00b6067}}, // रेट_, _pscs_, tzag, _đằng_,
+ {{0x6443b375,0x60c29376,0x80c9e07c,0x7d0400fd}}, // [e10] yeni, dlom, রদর্, _gwis,
+ {{0x6443b377,0xd838e05c,0x442ca05d,0x2cb83378}}, // xeni, biče_, _ncd_, cord_,
+ {{0x44eb8271,0x99677379,0xb767679a,0x26c32364}}, // _sø_, штал, штай, mljo_,
+ {{0x6443a9cd,0x7ebd007b,0x4fd9e00b,0x4ea4803b}}, // weni, cépt, אַנד, орча,
+ {{0x69c081cd,0x27ec206f,0x660763ac,0x6298eb83}}, // _cyme, ždne_, _tejk, _omvo,
+ {{0x20856711,0x291ee037,0xa5f8737a,0x2486d37b}}, // _السّ, krta_, _веру_, _alom_,
+ {{0xa3d73133,0x660b82ed,0xd6db8aa5,0xe299cafe}}, // िधि_, pagk, _ата_, лак_,
+ {{0xc3326095,0xe518c292,0x6abb8174,0x7ae0803b}}, // _מול_, _दिशि_, _ukuf, _gimt,
+ {{0x2007f37c,0x7643b37d,0x69c081cd,0xa3cba567}}, // _peni_, peny, _gyme, रेज_,
+ {{0x387fc0c4,0x628ab37e,0x61e400ad,0x200cf37f}}, // skur_, chfo, _sgil, gadi_,
+ {{0xe820c010,0xbb45f380,0x908aa043,0x5f13c0c5}}, // _याचा_, щенк, _حملے_, _दिक्_,
+ {{0xff50a062,0x660d5106,0xcb6a0f84,0x1bef8026}}, // _زخم_, jaak, гаме_, _छलफल_,
+ {{0x0d9a01d3,0x0d9901ba,0x442d9381,0x6e3b88f7}}, // утны_, атры_, _mce_, _mbub,
+ {{0x3ce9005f,0x69cbc098,0x44fbc057,0x200cf382}}, // _duav_, _izge, _iª_, cadi_,
+ {{0xa3b52028,0xbcf5e4c7,0x6da2f0da,0x7e7ce0cb}}, // _छुआ_, _استغ, _ниша, örpe,
+ {{0x7c3b2381,0x3eb90c8a,0xe297441b,0x69c1b383}}, // đura, cost_, бас_, _myle,
+ {{0x7c365384,0x7ae1a00c,0x7c29d385,0x21674b65}}, // [e20] rfyr, _oilt, iger, бити_,
+ {{0x42263386,0x7e214029,0xdfcf428b,0xc619e07c}}, // _удов, यलॉग_, ريل_, ত্রা_,
+ {{0x6299c00d,0x442cb387,0x54772076,0x6d5b8046}}, // _omwo, _rcd_, _הגיע_, _arua,
+ {{0x200ce055,0x6b812042,0x6d5b9388,0x5fbae00b}}, // zadi_, ølge, _brua, אצענ,
+ {{0x64457062,0x7ae08022,0xbcfb40f9,0x391380ff}}, // behi, _vimt, _aréf, змір,
+ {{0x64461389,0x6d49c011,0xbae580ff,0x68e8affe}}, // heki, dwea, іцій, _pudd,
+ {{0xe7fb2028,0x66098079,0x2731f38a,0x39400037}}, // ्रता_, _feek, ján_, _lpis_,
+ {{0x994c806f,0x7c3b8291,0x2b40138b,0xc3332095}}, // môže_, _gbur, _opic_, פוח_,
+ {{0xe1efc1ac,0x6d4f8057,0x200a60a2,0x7afbc0f2}}, // رسي_, _ácap, _kebi_, gput,
+ {{0xb4b23303,0x442cb38c,0x9c7cc29c,0x7bcf8057}}, // टको_, _ucd_, _kučk, _ºcur,
+ {{0x3eb90418,0x320ce7ac,0x3abb6087,0x7e76138d}}, // tost_, rady_, רמינ, _boyp,
+ {{0x660d4025,0x8503d38e,0x6446138f,0x7afc207f}}, // xaak, _کوون, geki, dprt,
+ {{0x44295390,0xeb97c052,0x937ac68c,0xc9841391}}, // vga_, _лиц_, _حصار_, пури,
+ {{0xfd4d4016,0x660d5392,0x68fb400e,0x7c2ab393}}, // _thoả, waak, _çudi, ngfr,
+ {{0x39401394,0x660d5395,0xa2b96c67,0x66d5c013}}, // _epis_, taak, ्तस्, _iškr,
+ {{0xd0110043,0x7f4d0058,0x18a405da,0x6d408b83}}, // _کلک_, _tsaq, _најм, _opma,
+ {{0x7ae2ce88,0x9fe7613a,0x200ea35f,0x6287620d}}, // [e30] _miot, مساه, kafi_, _pljo,
+ {{0xf8bf3396,0xf09f05df,0x7aed45dd,0xf807260f}}, // rtés_, lià_, rqat, _учин,
+ {{0xf8bf3397,0x64472167,0x68e99398,0x7bda6066}}, // stés_, meji, _sued, _útul,
+ {{0x66f1a2b8,0x4ac90dad,0xe29f0069,0x68e1b399}}, // _nāka, रतिव, nið_, _sild,
+ {{0x21698f77,0x3ea9539a,0xa9698f77,0x2019407d}}, // рили_, mnat_, рила_, _idsi_,
+ {{0x7bc6e0c2,0x26c4c197,0x6447339b,0x6d4f539c}}, // ękuj, elmo_, neji, _isca,
+ {{0xe8d92079,0xf771a19a,0x644612d8,0xb7b3e1a6}}, // _arị_, ظات_, yeki, _ঘণ্ট,
+ {{0x64552058,0xc7b26095,0x44ef010a,0x6e3b939d}}, // hdzi, _חבל_, _oü_, _ubub,
+ {{0x6447339e,0x628980f9,0x7ae2c265,0x68e1b39f}}, // keji, _ileo, _diot, _tild,
+ {{0x291fd3a0,0x27ffc17b,0x2cbf8133,0x7e760071}}, // rrua_, sbun_, _hkud_, _soyp,
+ {{0xc668a90f,0x68fbc133,0xddaba052,0x2ca953a1}}, // _уште_, spud, _стал_, knad_,
+ {{0xb05b13a2,0x7aeae76d,0x645533a3,0x2ee36171}}, // slän, _duft, edzi, _lijf_,
+ {{0x78ad02d1,0x78a9d3a4,0x78bbc28e,0xe7f900c5}}, // _djav, mnev, mouv, ंड़ा_,
+ {{0x24890bf2,0x78a9d3a5,0x248dd3a6,0xefb3c0eb}}, // _elam_, lnev, lhem_, _ویژگ,
+ {{0x660e2962,0xe7fc8026,0x7d1ca831,0x7aebc0c4}}, // rabk, उँमा_, _årst, _hugt,
+ {{0x7ae3f3a7,0x9f5200d1,0x07a600ff,0xf09f0017}}, // _hint, _deyò_, _майн, cià_,
+ {{0xdca350da,0x8af7403c,0x9f49206f,0x0ffa6049}}, // [e40] фати, _şəra, _škôl_, لعاب_,
+ {{0x03a66691,0x67272245,0xb5fb407b,0xa7fca0b0}}, // оизо, bujj, _unán, _anın,
+ {{0x4fea81bc,0xf8bf2195,0x249fc05d,0x200ea098}}, // амен_, ptér_, kium_, vafi_,
+ {{0x320a605c,0x9c7cc29f,0x68e40025,0x200f93a8}}, // _weby_, _vučk, _jiid, gagi_,
+ {{0x6497c0ba,0x69c3e725,0xa01b11cc,0x7d0021e9}}, // cţii, _lyne, kkön, _fàrò,
+ {{0x68e413a9,0x442fc153,0x6b57e125,0x6d5e212d}}, // _liid, _acg_, _ụgw, _krpa,
+ {{0xa3d4a029,0x629c7356,0xc04681c1,0x80b973aa}}, // सें_, _imro, _وخرو, ्तरे,
+ {{0x7ae3f3ab,0x442fd3ac,0x64552a0b,0x3ea053ad}}, // _aint, _ccg_, zdzi, niit_,
+ {{0x3ea6a691,0x78ad006e,0xd7ef8049,0xb6a6b3ae}}, // _диаг, _rjav, _شكل_, _диал,
+ {{0x7aebd3af,0x6146b3b0,0xa3ab40c2,0x26c693b1}}, // _dugt, _меза, गपत_, lloo_,
+ {{0x6abc33b2,0xd00fcf20,0xa3cfe37f,0x249ef3b3}}, // dorf, _оф_, _शरण_, ritm_,
+ {{0x27e0850e,0x57f401e1,0x6e3e205d,0x69c3e42b}}, // žinu_, мпют, _abpb, _dyne,
+ {{0xc87fc669,0x320ca03a,0x20194057,0x2ca94342}}, // öße_, _kedy_, _rdsi_, vnad_,
+ {{0x38786156,0xee3f0066,0x69ce60a2,0x6721e2e2}}, // _norr_, mným_, _ozbe, brlj,
+ {{0x4fc42250,0xb05b097a,0x628d53b4,0xee3f005c}}, // дста, lläm, thao, lným_,
+ {{0xd9e30857,0x82f7e6b0,0xa3cfe046,0x6d5e2012}}, // _गणपत_, _بروز_, _शरत_, _drpa,
+ {{0x68e3eecc,0xee3f0066,0x27e901e7,0x7ae3f3b5}}, // [e50] _yind, nným_, _igan_, _yint,
+ {{0x29090125,0x6ef3617a,0x9c7cc5f9,0x78bd0530}}, // _iwaa_, _aħba, _duči, losv,
+ {{0x64a402e2,0x9bf88079,0xf8bf20fa,0x200f93b6}}, // rđiv, _ịsụg, grée_, tagi_,
+ {{0x2ca04054,0x29090079,0x6448f3b7,0x63adc0c4}}, // ciid_, _kwaa_, hedi, _þang,
+ {{0x248c203b,0x161f212f,0xee3f0066,0x22494108}}, // šimų_, _बयार_, jným_, meak_,
+ {{0xee3f0d3f,0x69cf53b8,0x6d5d8069,0x6728e098}}, // dným_, _izce, _ásam, kudj,
+ {{0x68ed0256,0x7f4d417a,0x7f42c071,0xe8d92088}}, // _luad, lwaq, _apoq, _nrọ_,
+ {{0xd838f3b9,0xddc7202e,0x3a3ea1f6,0x6d41a07f}}, // viča_, rejň, _ebtp_, _vpla,
+ {{0x66d0697a,0x6448f3ba,0x78bbd3bb,0x6d4d4e47}}, // säke, fedi, rouv, nwaa,
+ {{0xa3cfee8e,0x200cb3bc,0x660bc7ff,0xc8798106}}, // _शरद_, _gedi_, _wegk, _hoş_,
+ {{0x660d13bd,0xb5fb40f9,0x317fc17b,0x6d4d4cc0}}, // _beak, _anám, syuz_, hwaa,
+ {{0x6abd13be,0x8517004b,0x6448e057,0x69c3e066}}, // gosf, _तिखट_, aedi, _vyne,
+ {{0xb4b7c010,0xaca3e291,0x6f152064,0x27e90058}}, // चकी_, _ahụh, eszc, _dgan_,
+ {{0x7c2d469b,0xfc3f41e9,0x7649c054,0x33294037}}, // egar, _abí_, leey, duax_,
+ {{0x7ae5220d,0x01632734,0x8704e049,0x7d0980bb}}, // _diht, нкро, _قبيل, _lwes,
+ {{0x443f93bf,0x273453c0,0x88d1a07c,0x6d4d53c1}}, // _nbu_, tän_, াদিক, fwaa,
+ {{0x18a633c2,0x9f5825df,0x395f8022,0x29090079}}, // [e60] _наим, taró_, _nrus_, _gwaa_,
+ {{0xf421a07c,0x66f1a041,0x823640e0,0x90c3604e}}, // প্তর_, _nāko, _کردا, ебуе,
+ {{0x200ca4d8,0xa87b2095,0xf67b2095,0x645573c3}}, // _redi_, _האיר, _האימ, _dazi,
+ {{0x200cac6c,0xe7b36050,0x7bce6cfd,0x765613c4}}, // _sedi_, رمند, _vzbu, _jayy,
+ {{0x9f5833c5,0x26c24020,0x6d5c33c6,0x442ce3e0}}, // paró_, _ikko_, lvra, ygd_,
+ {{0x75228ab8,0x653b000b,0x6d4413c7,0xee3f04d2}}, // rroz, מענד, _mpia, vným_,
+ {{0x6d5c2693,0x6aa1e435,0xbb436c84,0x6b6661ba}}, // nvra, dilf, _перк, _эква,
+ {{0x660453c8,0x752453c9,0x6448e1cd,0x321240ae}}, // nbik, oriz, wedi, nayy_,
+ {{0x201133ca,0x7d0981cd,0x68e640a2,0xaed4e143}}, // yazi_, _gwes, _mikd, моиш,
+ {{0xf8be69e4,0x628bd3cb,0x7ae653cc,0xee3f0066}}, // ्तिय, _elgo, _likt, rným_,
+ {{0x2fc74016,0x7413a01e,0x644ab3cd,0x3ea210dc}}, // ́ng_, _سونا, mefi, fikt_,
+ {{0x66cda06f,0x2bd987f1,0x7c2d4025,0xd24fc0eb}}, // núkn, बेवा, xgar, _منم_,
+ {{0x645613ce,0xfc3f005c,0x60c2d3cf,0x3211204a}}, // _dayi, lník_, _ikom, tazy_,
+ {{0x7d164944,0x176ae27c,0xe3afe6ec,0x39a34046}}, // dsys, _грип_, ئری_, _कs_,
+ {{0x765601df,0x75280064,0x9412403c,0x543be00b}}, // _fayy, ądza, liyə_, _לעגא,
+ {{0x201253d0,0x27356156,0xb4db61e9,0x98c440ba}}, // gayi_, vån_, _asàp, есул,
+ {{0x3160035d,0x248693d1,0x443f807d,0xbc79693b}}, // [e70] _griz_, dkom_, _rbu_, обах_,
+ {{0xed600013,0x6e3d04f7,0xddc9c361,0x765601df}}, // įžti_, lfsb, nješ, _zayy,
+ {{0x1a65854f,0x92598052,0x7d098a43,0x76560020}}, // ویسی_, жает_, _swes, _yayy,
+ {{0x22590536,0xfbd0613a,0x66f1a041,0x60c2d3d2}}, // ndsk_, اتك_, _sāko, _nkom,
+ {{0x3dc5a0e2,0x2d4d423d,0x69a2a077,0x81df007c}}, // _sylw_, _uže_, _केबी, দুর_,
+ {{0x6496016d,0xbcfb47df,0x660f4bea,0x3ead416d}}, // nšir, _eréc, _heck, četu_,
+ {{0x248693d3,0x20c1c049,0x3f8213d4,0xd9fea046}}, // bkom_, dóir_, zyku_, _उभरत_,
+ {{0xa3ad055d,0xb4b7ca53,0x61e98425,0x7c3d02be}}, // कपा_, चके_, _ugel, jfsr,
+ {{0xdd9483fc,0x3944817b,0x644ab3d5,0x6ba6a03c}}, // _заты, _gpms_, befi, _əsgə,
+ {{0x442dc6f1,0xdeff217a,0xfc3f0066,0x7d1a206f}}, // sge_, ċċju_, bník_, átsk,
+ {{0x765601e7,0x201253d6,0x3ea20018,0xc5f400a2}}, // _payy, yayi_, tikt_, _laɓe_,
+ {{0x7e7b93d7,0x20c1d3d8,0x68e76171,0xe73a0772}}, // _joup, róis_, _lijd, оем_,
+ {{0x3ebfd3d9,0xddc9c04d,0x60c9d3da,0x3ebef3db}}, // mout_, bješ, mlem, bott_,
+ {{0x645721ae,0x7e7b93dc,0x69a2a4e6,0x3ea203e0}}, // _caxi, _loup, _केडी, sikt_,
+ {{0xa01b09ac,0xed5724c2,0x2005f3dd,0x765600b0}}, // rhör, мор_, mbli_, _tayy,
+ {{0x628d0420,0x186753de,0xad9b4066,0x3f89c018}}, // _glao, маси_, _neús, _ļauj_,
+ {{0x644b93df,0xfc3f005c,0xb8d648c6,0x07a3522f}}, // [e80] degi, zník_, _जद_, ватн,
+ {{0x7aee6022,0x79828167,0xd838a361,0x7d1653e0}}, // _pubt, vyow, _omča_, rsys,
+ {{0x3eadd3e1,0x443ca05d,0x64902046,0x644ab3e2}}, // knet_, vfv_, _väid, vefi,
+ {{0x0dca853f,0xd9104555,0xd1ca8772,0xe1f8b3e3}}, // олей_, _میر_, олее_, ुर्ण_,
+ {{0x1fbae3af,0x7bc64106,0x20c2e765,0xcce58049}}, // _उड्ड, _uyku, iðir_, رسمي,
+ {{0x61ebd3e4,0x6b83a1cd,0xb87b6019,0x6b8285d2}}, // _aggl, fyng, _quít, ryog,
+ {{0x2242405d,0x764b93e5,0x64902046,0xc5f4035f}}, // _kbkk_, begy, _käib, _zaɓe_,
+ {{0xfc3f0066,0x60dbc8c4,0x30a3c5be,0xd90da0eb}}, // rník_, gmum, крыв, صیه_,
+ {{0x321dc1cd,0xb97b4053,0x20c1c049,0x6d5e63d1}}, // _ddwy_, ַנטי, tóir_, lvpa,
+ {{0x7e698089,0x4974e399,0xd7f7e1eb,0x61ebcbcf}}, // _znep, елос, мую_, _eggl,
+ {{0x60f8e2cb,0x7e7b93e6,0xb05b0d6e,0xd2510243}}, // яння_, _youp, lläi, هند_,
+ {{0xc0aacca7,0x44f3e067,0x68e8aded,0x671e4077}}, // _باطل_, _dã_, _hidd, _पिंक_,
+ {{0x656fc105,0x26c04024,0xb4e913e7,0xeabf13e8}}, // ächt, doio_, यदे_, roù_,
+ {{0x68e8b3e9,0x3b53c0ff,0x032000c5,0xf77325d1}}, // _jidd, _шкір, _बिरह_, پاس_,
+ {{0x44f3e067,0x7ae76171,0x78ad4363,0x1833e049}}, // _gã_, _rijt, tnav, _شروح,
+ {{0x2bdbd133,0x2a690071,0x645af3ea,0x850541c1}}, // मेवा, _unab_, ldti, فورن,
+ {{0x2242405d,0x7bca6105,0xb83360f9,0xbcfb4049}}, // [e90] _dbkk_, üfun, _ajúṣ, _bréa,
+ {{0x443ee669,0x68e8b3eb,0x2bc4a033,0x7d0d125e}}, // nft_, _nidd, _लुभा, _iwas,
+ {{0x3ce90048,0x69c8b3ec,0x60c9c121,0x78a3a052}}, // _kiav_, _nyde, ylem, vinv,
+ {{0x80d1007c,0x29016227,0x68e76171,0x6aa3a62c}}, // _সূত্, rpha_, _wijd, winf,
+ {{0x60c0d3ed,0x3eadc5f0,0x909841e1,0x290ca05d}}, // domm, vnet_, _цвят_, _awda_,
+ {{0x7ff5abd0,0x3ebfd3ee,0x7642c62e,0xf53641a9}}, // رستا, wout_, _aboy, _אנשי_,
+ {{0xdbd6e6eb,0x7ae8a426,0x3946c1ae,0x60dbd3ef}}, // _jääk, _didt, _apos_, tmum,
+ {{0xea9f81e9,0x101780e0,0x672613f0,0x7d026143}}, // _daṣa_, _زبرد, yrkj, ćost,
+ {{0x752d4024,0x64806052,0x661533f1,0x6f0d00a2}}, // nuaz, _töit, kazk, _nwac,
+ {{0x60c9d3f2,0x60dbc614,0x6562c0cb,0xa3b88466}}, // slem, smum, _droh, _घुट_,
+ {{0x2169e4c2,0x2738e508,0x2007b1c8,0x3d1640a8}}, // чини_, gén_, mbni_, _पौने_,
+ {{0xb05b4241,0xd7f8e0ba,0x63bb220f,0x7d0d13f3}}, // _skän, acă_, ğunl, _bwas,
+ {{0x6d5ce6dd,0x68fe27d3,0x2bd1e35b,0x7e6bc011}}, // årar, _stpd, _हरवा, _ingp,
+ {{0x2006812a,0x6459c054,0x6288f083,0x24894018}}, // zboi_, _hawi, ckdo, ekam_,
+ {{0x224e6497,0x27ed8200,0x6f028064,0x7bcb4105}}, // _ölkə_, _ogen_, zpoc, ügun,
+ {{0x2bfbe029,0x765bc00f,0xf772a053,0xfa0961a6}}, // लुरू_, nduy, נקן_, _লাগল_,
+ {{0xbcfb53f4,0x7bc98167,0x443fc167,0x22b8807f}}, // [ea0] _préa, _nyeu, ifu_, nčke_,
+ {{0x224dc171,0x201f8167,0xb21b4065,0x799640c2}}, // heek_, _adui_, _blæs, rzyw,
+ {{0xdb1d4066,0x3947e17b,0xd7fa81fc,0x764e277b}}, // _vysá, _jpns_, пул_, meby,
+ {{0x83fca6be,0x26c213f5,0x645c33f6,0xe6128062}}, // _inđi, noko_, ldri, اشا_,
+ {{0xa3e2ac4c,0x9f4045df,0x98a7c04a,0x764404b7}}, // नेन_, rció_, stně_, _mbiy,
+ {{0x644e33f7,0x6459c5c8,0x20116106,0x628e60ae}}, // nebi, _aawi, _gezi_, _ulbo,
+ {{0x7d044e27,0x443fd3f8,0xce94c14a,0x9c7cb3f9}}, // lpis, ffu_, какъ, _hičk,
+ {{0x09e4407c,0xdee3845c,0x7d04404e,0x224dd3fa}}, // _ফ্যা, _боси, opis, geek_,
+ {{0x7d0453fb,0x68e8a17a,0xe572c4a1,0xe4a6f3fc}}, // npis, _tidd, اطع_, ерно,
+ {{0x9f4a61e9,0x444423d2,0x443fd3fd,0x443ee105}}, // _agbé_, _nb_, afu_, uft_,
+ {{0x6e950255,0x229507ca,0x26c20194,0x7d092098}}, // вину, виня, foko_, ćesk,
+ {{0x645c33fe,0x9bb7a095,0xf3ffa067,0x44f52584}}, // edri, _אהבה_, _đãi_, _rå_,
+ {{0xa887c7ca,0x3ce90048,0x69c9805f,0xbcfb53ff}}, // _яйца_, _viav_, _xyee, _arén,
+ {{0x644e3400,0xdfcfc13a,0x81c5007c,0x6f045401}}, // gebi, _ايه_, ৌশল_, dpic,
+ {{0x7e7d4130,0x765ab402,0x77641403,0xd32426fc}}, // _posp, _maty, _erix, льти,
+ {{0x672126ca,0x20116163,0x7af5205d,0x8aa444a0}}, // šljo, _sezi_, rqzt, груд,
+ {{0x648de13a,0x7d0448b2,0xf1a401ba,0xfaf340e0}}, // [eb0] _múin, gpis, _брэн, اثر_,
+ {{0x645bc0ba,0x44443404,0x20d20049,0x201200a2}}, // zdui, _gb_, náin_, _zeyi_,
+ {{0x6f1c6098,0x6d5a6cf5,0x26c3203b,0x442122d9}}, // _cvrc, _štar, mojo_, _idh_,
+ {{0x9f40465f,0x25a606be,0x44443405,0x6aa72171}}, // rciò_, _žoli_, _zb_, lijf,
+ {{0x6d48ab8e,0x673d1406,0x20d90049,0x6fd54028}}, // _apda, ntsj, méid_, _दरिं,
+ {{0x6abae38e,0xaca36125,0x6aa2c1e9,0x26c21407}}, // ईकोर, _kpọb, _amof, zoko_,
+ {{0x648de069,0x7e6d1408,0xb882204a,0x60c28cfa}}, // _búin, _onap, _říka, boom,
+ {{0x32094bee,0x2739d409,0xa4d5884f,0x7e6d0079}}, // nbay_, tèn_, _робі, _nnap,
+ {{0x6d5a6057,0x26c3340a,0x6494a58f,0x9e15a0db}}, // _átar, kojo_, _bàid, _адзі,
+ {{0x644e2265,0x395fd40b,0x3947e037,0x645bd40c}}, // xebi, rvus_, _ppns_, sdui,
+ {{0x69cae0e2,0x26c2140d,0x2129540e,0x765b8068}}, // _cyfe, toko_, hrah_, _kauy,
+ {{0x7e7e27f7,0x4421228f,0x2ca7b40f,0x644f02a8}}, // _sopp, _adh_, lind_, geci,
+ {{0x644e3410,0x645ab411,0x32094401,0x61fbc61d}}, // tebi, _yati, dbay_, mcul,
+ {{0x645aa03c,0x765b835f,0x6aa60ad2,0x60c3a0ca}}, // _xati, _lauy, sikf, nonm,
+ {{0x673d02be,0x6d5ca0c4,0xe3b83412,0x6b8de0a2}}, // atsj, _árat, çı_, _ƙagu,
+ {{0x9c7ca64e,0x44213413,0x765b8068,0x6569400a}}, // _pičk, _edh_, _nauy, _šeha,
+ {{0x95834955,0xa3df7414,0x60c3b415,0x273aa0dd}}, // [ec0] алте, देस_, konm, zën_,
+ {{0x225e0041,0x42c4407c,0x2a6d804a,0x6aa72171}}, // ētku_, ্দেশ, _aneb_, cijf,
+ {{0x212948a7,0x50b5612a,0xd826401b,0x98ad4106}}, // arah_, усту, _идни, _çoğu_,
+ {{0x645ab416,0x21295417,0x0d86831a,0x6b7ae053}}, // _sati, brah_, клен, ורענ,
+ {{0xb8f96021,0x929de0c2,0x60c3a4cf,0x645820c4}}, // _ठी_, zpła, fonm, _óvis,
+ {{0xfc036a0c,0x399b00be,0xb1466a93,0xe45f0262}}, // _впро, וינד, _ингл, tlök_,
+ {{0x9f586069,0x387f9418,0x6d498108,0x8fa6673f}}, // _gerð_, _four_, _epea, _раве,
+ {{0x3eb2404e,0x9c7cb3f9,0xab83b254,0x44387419}}, // nnyt_, _miči, рушк, _hcr_,
+ {{0xdd90c4aa,0xd94681e3,0x9c7ca015,0x69caeaaa}}, // صود_, _јези, _liči, _syfe,
+ {{0x9f98a326,0x7afdc530,0x273aa00e,0x98e6a049}}, // lším_, ísta, pën_, _شكاو,
+ {{0x20d20049,0x2d98a135,0x60cd541a,0x7aeaf41b}}, // ráin_, _åre_, ylam, _vift,
+ {{0xa9c3e4a4,0x2ebd253a,0x8c3dc6df,0x2013341c}}, // рськ, ोत्त, nişl, _sexi_,
+ {{0xe0df0024,0x20d203f6,0xa2b8541d,0x273c741e}}, // rmò_, máil_, ्कन्, hín_,
+ {{0x26c4d41f,0xf366e799,0x672f016d,0x673d1420}}, // lomo_, _атин, rucj, rtsj,
+ {{0x6aa722be,0x26c33421,0xd4983422,0x78ada3bb}}, // rijf, rojo_, тря_, éavi,
+ {{0xe802b423,0x60c3a061,0xd838e126,0x38ad200a}}, // रुता_, zonm, diči_, džre_,
+ {{0x9ed9616c,0x645e62be,0x61ed802e,0x32095424}}, // [ed0] емат_, fdpi, žalk, rbay_,
+ {{0x273c7425,0x9418203c,0x634aa03c,0xa49b61e9}}, // fín_, kirə_, _kənd, _awòr,
+ {{0x7690204e,0x20d9013a,0x69c863d1,0x2bd5448b}}, // _käyn, péid_, _ødel, _दरवा,
+ {{0xbebd0018,0x634aa5fc,0x7c22d426,0xb4ccc04b}}, // drīk, _zəng, _ndor, ळतो_,
+ {{0x96b82050,0x3ea95427,0x2366c05f,0xf9908049}}, // _سایز_, miat_, _nroj_, يبه_,
+ {{0x20191428,0x2bc6c38e,0xaec4aa0e,0x7aed0e88}}, // hasi_, _लड़ा, абіл, _liat,
+ {{0x7aebc1e7,0x4439404d,0xfaa5d429,0x41e463b7}}, // _pigt, _kcs_, лако, _віта,
+ {{0x7aed08a4,0x799ae064,0x201907b9,0x4439405d}}, // _niat, dztw, jasi_, _jcs_,
+ {{0x645c6c0f,0x6609d42a,0x7d02c05d,0x5c15e65d}}, // _yari, rbek, _ctos, льну,
+ {{0x6609d42b,0x2fcd8187,0x7d02c3f6,0xaca3e088}}, // sbek, _iyeg_, _dtos, mpịn,
+ {{0x8c3dc121,0x69cd0037,0x273d542c,0x60db942d}}, // lişm, _ayae, hìn_, _khum,
+ {{0xe45f4156,0x27e9820d,0x68ed058f,0xbcfb40fa}}, // _skön_, žanu_, _ciad, _prél,
+ {{0x6d5ee026,0xf1a7a4ef,0xaa45ea8d,0x22b8806e}}, // _špat, _केतन, _белл, nčka_,
+ {{0x6e22c7ac,0x44f8a082,0x645d542e,0x9c7ca04d}}, // _zdob, _yé_, _aasi, _piči,
+ {{0xb8dc27c8,0x3ea9542f,0x442243ac,0x2b4b40d5}}, // _अद_, fiat_, _pdk_, _apcc_,
+ {{0x48c4407c,0x78a4049f,0xa3ea40de,0x7d1c21ae}}, // ্দ্র, _umiv, едба_, rsrs,
+ {{0x7d048041,0x799bd430,0x44f99431,0x9f98a026}}, // [ee0] ģisk, nzuw, _nè_, vším_,
+ {{0x66161432,0x9419003c,0x0717f2de,0x645d4095}}, // _heyk, disə_, _दबाव_, _easi,
+ {{0x248dd433,0x26c5e197,0x98a6412a,0x1bba6049}}, // kkem_, iolo_, _бине, رائع_,
+ {{0xdb1ea162,0xa5bb41af,0x7b206066,0x628e3434}}, // _évén, _idón, _náuš, lkbo,
+ {{0x5fe04cb5,0xa3e04688,0xa3aa04ba,0x7bcd0286}}, // पेशल, धेश_, _गेम_, _xyau,
+ {{0x628e29e2,0x2a60410a,0x28d840aa,0x386fc030}}, // nkbo, ldib_, _भीति, _angr_,
+ {{0x9f98a066,0x2ba7a8f2,0x661980c2,0x6e240125}}, // pším_, _केदा, bawk, _odib,
+ {{0x6f036017,0x27f201e7,0x645d4caf,0x6b81214f}}, // ínce, _ngyn_, _xasi, ølgi,
+ {{0x68ed003a,0xb05b0669,0x44f8b239,0x0caae501}}, // _riad, klär, _wé_, етни_,
+ {{0x26c5f435,0xfb1b81a9,0x68ed1436,0x68ee617a}}, // golo_, _נובמ, _siad, _jibd,
+ {{0x29120187,0x61ea05fc,0x54776053,0xaa63e056}}, // _awya_, əllə, אגיע_, стык,
+ {{0x6f040425,0x81b961a6,0xeb9acaf5,0x20c9a3f6}}, // _btic, _চরম_, _пие_, búis_,
+ {{0x44395437,0x862724e4,0x984b214a,0x2366c00e}}, // _rcs_, льве, нява_, _uroj_,
+ {{0x2ca95438,0x6281b439,0x859bc095,0x656941e4}}, // wiad_, _xolo, _אשדו, _šeho,
+ {{0x6444471a,0xdd9420fb,0x7aed00f9,0x3219143a}}, // ffii, бары, _tiat, pasy_,
+ {{0x291ef43b,0x4444743c,0x6d5aa167,0xb05b13a2}}, // msta_, ef_, _msta, rläs,
+ {{0x4439401f,0x7bcd01e7,0x25f640c5,0x7c3ab43d}}, // [ef0] _vcs_, _uyau, ौड़ी_, _octr,
+ {{0x69ce6f06,0x2ca941cd,0x443a217b,0x6d4d143e}}, // _bybe, siad_, _gcp_, _ipaa,
+ {{0x2a6044ec,0x20d4604e,0x60c56b39,0x3ea6c552}}, // bdib_, päin_, rohm, _omot_,
+ {{0x297a2087,0xa7fb41af,0xed5986be,0x80d3407c}}, // _סטרא, _bañe, _psž_, _দূর্,
+ {{0xdc36449c,0x645e2dd4,0x44fae00e,0x225ea187}}, // _דארט_, _yapi, _lë_, _batk_,
+ {{0x44f98163,0x656d806f,0x64902052,0xd019c0c2}}, // _vè_, ťaho, _näil, wań_,
+ {{0x4095c486,0x26c5e14a,0x44248065,0x7af7343f}}, // ирит, volo_, _fdm_, _juxt,
+ {{0x7af725fc,0x0685c5a8,0xa3dee0a3,0x4c85d440}}, // _muxt, ргин, _běží_, рлив,
+ {{0x661af441,0x23690048,0x6c84813a,0xe7e0c10a}}, // fatk, _nraj_, _القم, खेला_,
+ {{0x672d43ee,0x44fae00e,0x78a9cb83,0x248dcdd8}}, // iraj, _bë_, siev, skem_,
+ {{0x291ef442,0xade1a55d,0x39417443,0xbb45f444}}, // gsta_, गेशन_, nths_, шенк,
+ {{0x7c252256,0x64bc9445,0x39405446,0x9c7ca030}}, // _adhr, mčic, ytis_, _chčf,
+ {{0x64861447,0xa3e9c308,0x7ff4c449,0x645f0025}}, // _sóis, _मणि_, _اسما, _daqi,
+ {{0xa3da9112,0xa5bb40f9,0x672d5448,0x99582066}}, // डेट_, _adóo, draj, ráže_,
+ {{0x99582066,0x2b4ca17b,0x2abac018,0xd175220e}}, // sáže_, _xpdc_, lība_, быты,
+ {{0x2eeef449,0x660d544a,0xcf92a00b,0x752d4603}}, // _ziff_, gbak, קטן_, fraz,
+ {{0x6444544b,0x4fea53b0,0x68e28954,0x6d5ee005}}, // [f00] rfii, тман_, vmod, _ápar,
+ {{0x6e3641cd,0x64902046,0x76498c75,0x38600076}}, // sgyb, _käim, _abey, _hair_,
+ {{0x867b2095,0x26c7a14a,0x645f144c,0x752d544d}}, // _סרטו, dono_, _yaqi, araz,
+ {{0x7bce6066,0x6b836277,0xab66863e,0xeb068a92}}, // _vybu, änga, ивел, ачко,
+ {{0x6283e037,0x7694a0f9,0x44fbd44e,0xdce2e7a0}}, // _oono, _aàya, _nê_, _sroč,
+ {{0x291ef44f,0x38600050,0x69ce6bca,0xafdb0271}}, // ysta_, _lair_, _tybe, dføl,
+ {{0xbbd31450,0x4425acc2,0x92d8407c,0xfdd30077}}, // _सर्क, _ddl_, াদী_, _सर्फ,
+ {{0x661ae820,0x20005451,0x6d5aacf5,0x2fcfc551}}, // watk, ncii_, _vsta, _bygg_,
+ {{0x44fc28e6,0x7d06405d,0x26c7a0bb,0x6d41e0c2}}, // _lí_, _jtks, bono_, etla,
+ {{0xd838a143,0x68e45452,0xdce2e29c,0x6296011d}}, // _omči_, hmid, _troč, _alyo,
+ {{0xd48fd453,0x99f7642c,0x6568a0dd,0x291ef454}}, // _др_, יזיה_, _urdh, usta_,
+ {{0x2aad2199,0xa2daa292,0x9c83c098,0x321ca066}}, // džba_, _पीत्, _ščvr, lavy_,
+ {{0x6283f455,0xe3b2cca7,0xaae5c33e,0x6d429456}}, // _fono, _برا_, _اسلو, ntoa,
+ {{0x68e44046,0x6d4e622e,0x752d44f3,0x645f1457}}, // emid, _opba, vraz, _waqi,
+ {{0x82a741ba,0x8aa75458,0x443ce5df,0xd4c5e13a}}, // ашае, арад, _icv_, _اغني,
+ {{0x26c7b459,0x80d1007c,0x60c7200e,0x442d345a}}, // zono_, _সংক্, sojm, şe_,
+ {{0x6b5c2342,0x7aef48cd,0x9f4a6031,0x7e628024}}, // [f10] _pågå, _vict, _agbà_, ddop,
+ {{0xa3d7aa2a,0x2906c167,0x449861ba,0x2018745b}}, // _सरल_, _mtoa_, авую_, _ceri_,
+ {{0x44fc2031,0x6455345c,0x7e60945d,0xd5b826fc}}, // _gí_, mezi, _bamp, _вся_,
+ {{0xb87b641a,0xb4c0c026,0x2ef86110,0x29094052}}, // _juíz, ंको_, _durf_, ppaa_,
+ {{0x63bb23df,0xbebd0041,0x2ca90079,0x291e2199}}, // ğunu, brīv, _mmad_, štat_,
+ {{0x6d41e5fc,0x2fcfc249,0xbebd0041,0xaf3760d0}}, // ytla, _rygg_, nsīb, _پرست,
+ {{0x661bd45e,0x1a6864b7,0x60cd005c,0x26c7b45f}}, // tauk, _خیلی_, _okam, rono_,
+ {{0xb5fd9192,0x13b82095,0x291fd460,0x45b82095}}, // _baše, יהול_, tsua_, יפול_,
+ {{0x798d5461,0x6d5b8e96,0x64553462,0x200dd463}}, // nyaw, _usua, kezi, rbei_,
+ {{0x7ae4445e,0x64553464,0x291fc058,0xafdb0042}}, // zmit, jezi, rsua_, rføl,
+ {{0x7e609465,0x66dd10a2,0xe643c315,0xa2ba6437}}, // _yamp, zéke, şmış, ्वत्,
+ {{0x160bb466,0x661c3467,0x753521d7,0xbebd0041}}, // _स्तर_, wark, duzz, esīb,
+ {{0x80d1007c,0x7e61b468,0x443dc037,0xe3b1e10c}}, // _সংখ্, _kalp, _icw_, ترک_,
+ {{0x66dd00e0,0xf1b9c40c,0x78bb8262,0x2cadd469}}, // véke, gaše_, _ljuv, lied_,
+ {{0x753ca105,0x69a76aa9,0x64b746a6,0xdddc60ba}}, // _ärzt, _टेली, pćil, _morţ,
+ {{0xa924606f,0x64a34407,0x86994a75,0xb21b0065}}, // _úžit, паса, атут_, skær,
+ {{0x6455346a,0x3219448d,0x09e5607c,0x634aa03c}}, // [f20] bezi, _desy_, _প্লা, _məna,
+ {{0x6455214a,0xaa7b406f,0x7e1088b1,0x3eadd46b}}, // cezi, _chýb, ार्ग_, hiet_,
+ {{0x44fc2049,0x69db8071,0x3eadd46c,0x41cec026}}, // _uí_, _azue, kiet_, _हुनस,
+ {{0x7e628187,0xdcee4013,0xb4db2017,0xe81a80a4}}, // udop, vybė, ntàn, ازات_,
+ {{0x2723e602,0x44fd0016,0x763b000b,0x98a066a6}}, // hın_, _xì_, רענג, jrić_,
+ {{0x6ab2c8b1,0x98a0650e,0xb4c2c046,0x6456546d}}, // ुक्र, drić_, ृत्_, leyi,
+ {{0x6d5c746e,0x66040434,0x3e856026,0xb8ff87c8}}, // _usra, _ofik, _dítě_, _धी_,
+ {{0x8c438093,0xb4d2004b,0x6d429273,0x68f9c194}}, // _несе, वतो_, ptoa, _buwd,
+ {{0x649cc13a,0xdee38052,0xb5fd8cf5,0x66d88066}}, // _léig, _хоти, _inšp, líko,
+ {{0x661d012b,0x0907c07c,0x3ea91234,0x415b400b}}, // wask, _শিশু_, _smat_, נדיג,
+ {{0x7bdaa041,0x7524146f,0x26ca3470,0x7d0aaa93}}, // _uztu, _aviz, dobo_, _снег_,
+ {{0x8c3dc03c,0x24804d52,0x98a062c9,0x7f44405d}}, // yişi, jjim_, brić_, ktiq,
+ {{0x26ca282a,0x27ed0052,0xb4c31471,0x66dd0009}}, // fobo_, äen_, ्तौ_, tékb,
+ {{0x661d1472,0x1994e4a0,0x2723e315,0x20020065}}, // sask, _маля, bın_, acki_,
+ {{0x66028037,0xd2d74049,0xf8bfe0fa,0xdce2a04d}}, // kcok, مغرب, éée_, jvoč,
+ {{0x645520bb,0x7d7900e0,0x67229473,0x661ab474}}, // sezi, _نمبر_, ksoj, _letk,
+ {{0x78ad5475,0xe1f04049,0x6f1600fd,0xbd022079}}, // [f30] riav, فسك_, _gwyc, _ọktọ,
+ {{0x8c3dc3ed,0x26ca3476,0x75353477,0x6b8d4b0c}}, // rişi, cobo_, puzz, ryag,
+ {{0x628640e0,0x752400a9,0x2907600e,0x9df99478}}, // _foko, _zviz, ëna_, анет_,
+ {{0x7c28a095,0x29076018,0x443ea076,0xd05c80e8}}, // _addr, īna_, _act_, _murɗ,
+ {{0x764d106c,0x7afaa04e,0xa2c7c37f,0x98b8412a}}, // _mbay, _autt, ावस्, stră_,
+ {{0x0f1b60e0,0x1869b479,0x3eadd47a,0x2bd96062}}, // _مغرب_, раки_, wiet_, _نازک_,
+ {{0x28abe295,0xa5c38064,0x543c200b,0x64bc90b7}}, // टोफि, _łódz, טעגא, sčia,
+ {{0x4429147b,0x22494058,0x6490210a,0x6d45747c}}, // _nda_, nfak_, _päik, mtha,
+ {{0x2ee001cd,0x6495c049,0x3f42c0c4,0xc985c049}}, // _rhif_, _fáin, gðu_, تشغي,
+ {{0x2ed205c5,0x649024ef,0x7d098434,0x764d02a9}}, // सत्त, _väik, _ites, _abay,
+ {{0x6d5f0625,0x7afaa488,0x4429147d,0x3ce00022}}, // _asqa, _gutt, _bda_, _phiv_,
+ {{0x395dc022,0x2caf8171,0x25e1608d,0x644d0090}}, // _tsws_, nigd_, _करनी_, _cbai,
+ {{0xb4db25df,0x7c3e2143,0xa01b0149,0x78bd003e}}, // rtàn, _pcpr, rköp, érva,
+ {{0xb4db25df,0xa7fb4005,0x3ce68714,0x2bdc947e}}, // stàn, _raña, zmov_, _बरसा,
+ {{0x3ce0005f,0x6724817a,0xed5a22d7,0x290dd47f}}, // _thiv_, ġiji, шов_, lpea_,
+ {{0xa3a92010,0x66040c55,0x26cb1480,0x7d098066}}, // _खेळ_, _ufik, coco_, _otes,
+ {{0x629c3481,0x6d4560e2,0x7afb831d,0x2caf8200}}, // [f40] phro, etha, _nuut, digd_,
+ {{0x44291482,0x7f445483,0x62865484,0x6ee96012}}, // _zda_, rtiq, _woko, _ožbu,
+ {{0xed5a68ea,0x7ce032f1,0x6d457485,0x201fc17b}}, // рое_, göre, gtha, kaui_,
+ {{0xf8bf20fa,0x7f445486,0x7c2de020,0x443eb487}}, // rsée_, ptiq, _ɓara, _sct_,
+ {{0x443f9488,0x7ae1a090,0x2499000e,0xc9838052}}, // _dcu_, _bhlt, ërmi_, дущи,
+ {{0x6724486e,0x752fe064,0x6723a6a6,0xe5a66aab}}, // msij, ńczy, fsnj, _диги,
+ {{0x321ee089,0xdddc7489,0xd130a049,0x41276143}}, // vaty_, _forš, رمة_, _моро_,
+ {{0x6d460469,0x2240005d,0x6496e0ba,0x6e3bd48a}}, // ktka, _ncik_, _câin, ggub,
+ {{0x66d0604e,0x4429148b,0x2258223c,0x236d8b47}}, // säku, _rda_, kerk_, _grej_,
+ {{0xa49b20d1,0xb87b6057,0x7524548c,0x656d000e}}, // npòt, _quíx, isiz, _rrah,
+ {{0x7f441441,0x321ee8db,0x7e640125,0xe3e8a050}}, // _aqiq, raty_, _baip, _اکشن_,
+ {{0x7e64148d,0x92cae07c,0x7658b48e,0x6d5c20fd}}, // _caip, লগে_, levy, dwra,
+ {{0x7cf5a013,0x69de2108,0x28dfb48f,0x64409490}}, // _išra, _azpe, _पीडि, _mcmi,
+ {{0xf8caa028,0xe7e1a026,0x769cc0f9,0x96a6e04b}}, // ाकिय, गेका_, _déye, टोशॉ,
+ {{0x7ce02277,0x349500d6,0x6e9500bc,0x22494106}}, // förb, _накр, _нику, tfak_,
+ {{0x90c4039d,0x6d456048,0x539be076,0x212d207f}}, // _обје, vtha, _כיוו, čeh_,
+ {{0xe719413a,0x656e6256,0x228720fb,0x249d8022}}, // [f50] صيات_, _arbh, _дунг, vhwm_,
+ {{0x661c7491,0xa2b4204e,0x7afb82df,0x6d457492}}, // _cerk, _обыч, _ruut, ttha,
+ {{0xa907663b,0x661c7493,0xddc1a018,0x2caf82be}}, // زبان, _derk, _valū, tigd_,
+ {{0x660de00a,0x6738a579,0x7ce02728,0x7ae2c125}}, // _đakm, duvj, röre, _nhot,
+ {{0x7d022098,0x534450f6,0xc6175133,0x6e20d494}}, // _čosi, _охра, तरीय_, damb,
+ {{0xb5fd8552,0x22b60018,0x80a8a14f,0x2606a35b}}, // _daša, rāki_, овів_, सुली_,
+ {{0x78ad010a,0x7bc2c005,0x7afd5495,0xa2ba6086}}, // _omav, _axou, _hust, ्वर्,
+ {{0xa5bb4dc1,0xba778678,0x395f8022,0x224d807d}}, // _adói, _باست, _tsus_, _ubek_,
+ {{0x442b504a,0x68fc64cd,0xf1b98490,0xaca3a067}}, // _mdc_, _zurd, _beše_, _quỳn,
+ {{0x201cec35,0x68fc619f,0xa5bb4e1c,0xfd648088}}, // _cevi_, _yurd, _teóf, _larị,
+ {{0x6490210a,0x5f05e0db,0x186a1097,0xb5fd8012}}, // _käiv, язна, бани_, _zaša,
+ {{0x645980fd,0x2a65b006,0xa5bb4071,0xa7fce6fa}}, // newi, _halb_, _geóg, ldıl,
+ {{0x2be2c7c8,0x3f8f017a,0xbf9b01de,0xc7b8e29c}}, // _परया, _żgur_, scên, rađa_,
+ {{0xf8bf42f4,0x64bc8013,0x2bc6e028,0x7d097496}}, // _ajé_, yčio, _रुका, _česn,
+ {{0x96965497,0x09b8804b,0x66dec00e,0xd5b22049}}, // праш, _अश्य, lëka, رفر_,
+ {{0x68e9d498,0x2360005f,0x7af56041,0xf09f0090}}, // nmed, _tsij_, _aizt, chà_,
+ {{0xf539c06f,0x649cd2d4,0x6738a579,0xd00f20e0}}, // [f60] nuť_, _héic, zuvj, ئلہ_,
+ {{0x66d0604e,0xc17300be,0xe1ff06d9,0x244fa3ed}}, // väks, _נחת_, ndón_, nımı_,
+ {{0x7afd4108,0x44220265,0x7ce5c057,0x3ebf8227}}, // _eust, oak_, mórf, _bjut_,
+ {{0x63a29499,0x6ba10121,0x629de3ac,0x76598155}}, // tzon, _özgü, ësoj, gewy,
+ {{0x44220265,0x3866864f,0x6724417a,0x7bcb417a}}, // iak_, rdor_, qsij, żgur,
+ {{0x644e749a,0x3ead80b8,0x68f56012,0x68e9c165}}, // _ubbi, _emet_, _gizd, emed,
+ {{0x81d4a07c,0x64598854,0xda65a13a,0x7afc6171}}, // সেন_, bewi, باني, _uurt,
+ {{0xb902807c,0xbddb549b,0xdb1c60c2,0x5ba6c5d0}}, // _নং_, _ibèr, _wyró, зроз,
+ {{0x3f804057,0xa87b4053,0x7c22846b,0x8c3dd49c}}, // nxiu_, מאקר, maor, vişt,
+ {{0x27f8e042,0xf8cc6064,0x7ae9d49d,0x5f94e3d5}}, // ørn_, ़किय, amet, диот,
+ {{0xb5fd835d,0x7ae9d49e,0x68e406f1,0x59be749f}}, // _mašn, bmet, _bhid, ्थिर,
+ {{0xa7fb4057,0xfaa3c03b,0xba17c049,0xddc1e009}}, // _raño, _пахо, _فيما_, gelő,
+ {{0x661d40d1,0x225914a0,0x7ae414a1,0x61fb02c9}}, // _resk, resk_, _dhit, žulo,
+ {{0x7cc1c2b8,0xf1be6180,0x1adca029,0x6562d4a2}}, // mēra, ्थान, यतिथ, _isoh,
+ {{0x27e00e3b,0x661e34a3,0xa4d5471b,0x7de720e0}}, // _ezin_, _depk, дові, désé,
+ {{0xf8bf207b,0x768fa14f,0x3264414f,0x7ae414a4}}, // trés_, _høyt, нтув, _ghit,
+ {{0xc311807c,0x200902a8,0x7af56108,0x443ee171}}, // [f70] _হয়নি_, _ifai_, _pizt, ogt_,
+ {{0xa2c50914,0x3ea040ae,0x661d54a5,0xf8bf34a6}}, // रवक्, ahit_, _wesk, rrés_,
+ {{0x7ae9d4a7,0xb5fd8098,0x7afe2136,0x6e2d005d}}, // ymet, _dašn, _gupt, _kdab,
+ {{0x02a749b1,0xddcd006e,0x35b58799,0x20d20049}}, // _хром, _znaš, мбер, láir_,
+ {{0xd126c0a4,0x6702a088,0xbddb4061,0xf194814f}}, // _جم_, _ọkpa, _ebèr, ниць,
+ {{0x2a65a17a,0x66dd004a,0x7af7217a,0x6283a022}}, // _qalb_, kéko, _jixt, ujno,
+ {{0x66dd00e0,0x6e21f4a8,0xa7fce6fa,0x2298c187}}, // jéko, valb, rdıl, _céko_,
+ {{0xb3aa207c,0x3949403b,0x6e2d04fd,0xe7d0e0a8}}, // _খুঁজ, otas_, _ndab, _हड़प,
+ {{0x6efa6026,0x6490210a,0x9f586061,0x7ce02156}}, // _vůbe, _käit, _agrè_, höra,
+ {{0xb0bf6857,0x290bc041,0x648de049,0x3ea0400e}}, // ्विग, īca_, _rúis, zhit_,
+ {{0x68e9d4a9,0x6da5e423,0xe5a5f24b,0x6e21e48f}}, // pmed, фина, фини, ralb,
+ {{0x7d1b8088,0x2a694022,0x49148026,0x656d802e}}, // _kwus, jdab_, नीको_, ťahu,
+ {{0xe0cef4aa,0xc058c5d0,0x442334ab,0xa3ac54ac}}, // _ав_, _хіх_, faj_, _कें_,
+ {{0x672600ff,0x7af734ad,0xa3e2c4d6,0x20d205e0}}, // rskj, _cixt, _धरत_, táis_,
+ {{0x7ce02277,0x394954ae,0xcb6721fc,0x9f43e04e}}, // göra, etas_, маре_, äjä_,
+ {{0x2ca04058,0x6e23b4af,0x7f49c5df,0x39494af1}}, // uhid_, hanb, oteq, ftas_,
+ {{0x9f44004e,0xaec634b0,0x661e2106,0x4b260362}}, // [f80] ämä_, _обил, _tepk, _ўмов,
+ {{0x6d49c00c,0x3ea040dd,0xb21b0065,0xb937000b}}, // itea, shit_, hjæl, _קריג_,
+ {{0xf9920050,0x04436192,0x290d8365,0xb4da20c2}}, // _سبک_, техн, _atea_, ़ती_,
+ {{0xa3c954b1,0x290ca229,0x648ec58f,0x7e69d4b2}}, // _लुक_, _rtda_, _lùir, jdep,
+ {{0xfe798026,0x7e69c0f7,0xb5fd85f9,0x6e2454b3}}, // _dnů_, ddep, _tašn, maib,
+ {{0xc3334087,0x6e24503c,0x61fb94b4,0x645af4b5}}, // _בוש_, laib, _egul, xeti,
+ {{0x645bc3d3,0x26cf94b6,0x66d184f0,0x660994b7}}, // feui, bogo_, råkr, _ffek,
+ {{0xafe360ff,0x443fd4b8,0x64902046,0x2ec983af}}, // _росл, ggu_, _käis, िकृत,
+ {{0x442ca0ca,0xdd8f86b0,0x2bbe6026,0x6d49c049}}, // _tdd_, _پول_, ्थवा, gtea,
+ {{0xb34782a4,0x443fd4b9,0xb17b4053,0x7c208944}}, // _duħħ, agu_, קטיר, _hemr,
+ {{0xf8376095,0x7d0094ba,0x628bd4bb,0xdeb5004e}}, // ונית_, _hums, _fogo, ебны,
+ {{0x3949403b,0x7d0094bc,0x6d49d4bd,0x6b836f67}}, // ytas_, _kums, btea, ängi,
+ {{0x645c2ea3,0x6490204e,0x7d09629f,0x2a7a2037}}, // feri, _näis, _česm, _bnpb_,
+ {{0x7d0094be,0x7ce5c005,0x765c277c,0x6298b4bf}}, // _mums, tórg, gery, nkvo,
+ {{0x1bd42702,0x67d42bcd,0x2a79405d,0xdfd8214a}}, // воря, вору, _pnsb_, дът_,
+ {{0x6e2094c0,0x2eca0bde,0xdced229f,0xf1b9816d}}, // _nemb, रवृत, _irač, _leša_,
+ {{0x7e7aa23c,0x394954c1,0x7d0d14c2,0xb5fd8143}}, // [f90] _ontp, utas_, _utas, _sašo,
+ {{0x7e68a300,0x644d53f4,0x5a346e48,0x2b4954c3}}, // _nadp, sfai, кнут, rtac_,
+ {{0xb4bd2d88,0x649026eb,0x6e23b4c4,0x38358bd6}}, // ेकी_, _väit, vanb, енер,
+ {{0x765d01cd,0xa3e2a026,0x61e2d4c5,0xe80a94c6}}, // nesy, नेछ_, _ozol, हुला_,
+ {{0x7e68b4c7,0x61ad407c,0x6abd4ba0,0xdd32003c}}, // _badp, _কুরআ, ्कीर, _məşq,
+ {{0x6e20808b,0xb5fd8326,0x320945dd,0x765d01e7}}, // _eemb, _zašl, lcay_, hesy,
+ {{0xc7b8a274,0xff51a566,0x6e23b4c8,0xf1b98012}}, // _peđa_, اخت_, ranb, _deša_,
+ {{0x6e23b4c9,0xfdf82095,0x7c23af87,0x2b15d219}}, // sanb, וצות_, sanr, ньор,
+ {{0xd84ee016,0x645c34ca,0x21294050,0x752994cb}}, // _vọng_, yeri, nsah_, _uvez,
+ {{0x93bca12a,0x0d85e27c,0x316dc0e1,0x68ed54cc}}, // _adău, елон, rvez_, hmad,
+ {{0x628bd2cc,0x7e698493,0x248d8143,0xd49c41e9}}, // _togo, _kaep, _koem_, _bọ̀_,
+ {{0x776405fc,0x753bc167,0x442ef4cd,0xed5a14ce}}, // _psix, puuz, _edf_, пом_,
+ {{0x7d01b308,0x69cee105,0xdb0a2026,0x2d498951}}, // _huls, über, čníh, núe_,
+ {{0xcb36c1a1,0x62860012,0xddc40227,0x2a6900d4}}, // _נאצי_, sjko, _iniż, _eaab_,
+ {{0xdced24ad,0x649cd2d4,0x649a4143,0xd874e25b}}, // _grač, _méin, птар_, _جانب,
+ {{0xdce2e50e,0x649cc13a,0xa01b00e0,0x6441ea13}}, // _proć, _léin, zköz, lgli,
+ {{0x6441f4cf,0x00e665ef,0xee374013,0x6da34aab}}, // [fa0] ogli, ежан, энт_, гита,
+ {{0x69d9d4d0,0x7d0094d1,0x60dbc72a,0x7641f23c}}, // _mywe, _rums, hlum, ngly,
+ {{0x7c209104,0x321820c7,0x20074064,0x290120b1}}, // _pemr, mbry_, śnia_, _guha_,
+ {{0x64a654d2,0x2a690022,0xa683454a,0x629d0013}}, // _чама, _xaab_, _блюд, ūrov,
+ {{0x69c76071,0xafdb03d1,0x3ebfd4d3,0x26c054d4}}, // _exje, lfør, dnut_, mnio_,
+ {{0x6e2094d5,0x3ea00017,0x78a294d6,0xb87b0057}}, // _wemb, _llit_, shov, lmít,
+ {{0x2ca0010a,0x442fd4d7,0x3ce6c022,0x672bc0ff}}, // _olid_, _bdg_, _phov_, _avgj,
+ {{0x442254d8,0x26c0465f,0x4425f4d9,0x6441f4da}}, // _jek_, nnio_, eal_, egli,
+ {{0x7af8e022,0x38690171,0x64a388df,0x22b6a0c2}}, // _pivt, _raar_, _сата, ałka_,
+ {{0x649cd447,0x1828a050,0x610c6121,0x6f01a197}}, // _géin, _آقای_, _eğle, _fulc,
+ {{0xd5baeb2d,0xa3d48292,0x2be2cb72,0xb637600b}}, // яси_, _सुत_, _परवा, ערטס_,
+ {{0x4424d4db,0xafdb05f0,0x673d14dc,0x7792c050}}, // qam_, dfør, tusj, _میبا,
+ {{0x38690aaa,0x6aa454dd,0x20094048,0x3cfa2030}}, // _vaar_, dhif, vcai_, _bipv_,
+ {{0x765d14de,0x60c40468,0x7eb6136d,0x69d8e271}}, // sesy, _ejim, ršpa, _tyve,
+ {{0xbcfb4009,0x41d065e8,0x2902431b,0x26d13099}}, // _esél, _तुलस, _auka_, rozo_,
+ {{0x68ed54a1,0x645d000e,0x768fa14f,0x69daa831}}, // umad, qesi, _løyp, _myte,
+ {{0x195847b9,0x29024345,0x634aa03c,0x25b70043}}, // [fb0] наты_, _cuka_, _dəni, _سندھ_,
+ {{0x644534df,0x6eba6626,0x7e6994e0,0x60dd06bf}}, // _uchi, ्वगु, _saep, llsm,
+ {{0x442694e1,0x81d821a6,0x68ed40b0,0xb40e8125}}, // fao_, ়েব_, pmad, _ịpịa_,
+ {{0x4c954470,0x442254e2,0x7afaa121,0xa2c24026}}, // _римс, _gek_, _aitt, रचण्,
+ {{0x649cc049,0x4425f4e3,0x442fd4e4,0xb4bf54e5}}, // _péin, yal_, _rdg_, ेवी_,
+ {{0x248954e6,0x6e2727a0,0x212b0105,0x6e21b4e7}}, // ljam_, najb, isch_, _qelb,
+ {{0x9f47604e,0x6e22c105,0x753c8041,0x6fc9c029}}, // änä_, _beob, _ārze, _हुजू,
+ {{0x628f54e8,0x6f0961ce,0x397c20be,0xb17c2087}}, // _joco, _čech, לטונ, לטור,
+ {{0x81d7207c,0x44236d9d,0xa7fb407b,0x0682c0ba}}, // িধি_, _kej_, _bañi, ргын,
+ {{0x26c041e4,0x2ebc055d,0x97a7a406,0x7529d4e9}}, // znio_, ोक्त, _прел, rsez,
+ {{0x6d4d404e,0x6441f132,0x7c2614ea,0x60dbc3ed}}, // htaa, rgli, takr, plum,
+ {{0x6f02c14a,0x66096121,0x7bdb87aa,0xf3ff4067}}, // _fuoc, _şekl, _kyuu, _ngã_,
+ {{0x4427a108,0xb5fb0066,0x291dc125,0x645e73c7}}, // oan_, zdáv, _uwwa_, yepi,
+ {{0x26d2400d,0xed5a34eb,0x68fb8013,0x672d14ec}}, // woyo_, мог_, _liud, _dvaj,
+ {{0x6443b4ed,0x628f48a4,0xe3bf027f,0x7c22c037}}, // igni, _boco, raña_, _yeor,
+ {{0x38ad200a,0x2a6dc1cd,0xf65f0163,0x7e6d4114}}, // džru_, ndeb_, ceæ_, gdap,
+ {{0x6f03f4ee,0x4431600a,0x442374ef,0x386dd4f0}}, // [fc0] _hunc, _bdz_, _bej_, ider_,
+ {{0x7e6bc561,0x7c3b60e8,0x7ae98f60,0x442254f1}}, // _lagp, _ɓurg, _ahet, _wek_,
+ {{0x7c23e058,0x26c30e9d,0x386dc17b,0xc7a620ff}}, // _menr, čkov_, kder_, _ринк,
+ {{0x78a1ac31,0x6d4d4054,0x7d0de098,0x6aa1b4f2}}, // _allv, btaa, _časl, _allf,
+ {{0xef17404e,0x7d03f4f3,0x69dab4f4,0xb05b4156}}, // _имя_, _luns, _syte, _mjäl,
+ {{0x61e64c5a,0xdd938013,0x66d88069,0x65938450}}, // _izkl, рашы, víku, рашу,
+ {{0x2d9834f5,0x394054f6,0x3cfde028,0x3f8080ba}}, // nyre_, luis_, _रंगे_, ţiu_,
+ {{0x3eb823f6,0x628f45df,0xdce2a199,0x83fca29c}}, // hirt_, _xoco, kvoć, _mađi,
+ {{0xa5bb4005,0x7e6bd4f7,0xe9d787f7,0x44236022}}, // _león, _dagp, _акт_, _yej_,
+ {{0xc7b8e29f,0x7ce029ac,0x2ee90058,0xd9e66021}}, // lađi_, förm, _shaf_, _करात_,
+ {{0x290494f8,0x83fce4bb,0x394054f9,0x291214fa}}, // _huma_, jeđe, huis_, _atya_,
+ {{0x394dd4fb,0xb5fd8055,0x7d0414fc,0x83fca2e2}}, // btes_, _rašk, _buis, _nađi,
+ {{0x0eb6a026,0xaca474fd,0x6d4e2b83,0x212b00cb}}, // _अगाड, _apọs, etba, usch_,
+ {{0x6abd43af,0x2abac018,0xf2c42052,0x645600b1}}, // ्क्र, rību_, ассн, _ebyi,
+ {{0xff042491,0x6448a098,0xb6074104,0x28f824a7}}, // иятн, _mcdi, вязк, терь_,
+ {{0x6e23ee3b,0xa3caa10a,0x442374fe,0x7f4d54ff}}, // _zenb, _लखन_, _sej_, ttaq,
+ {{0x2904801b,0x7afb9500,0x6443a78c,0x68e981cd}}, // [fd0] _numa_, _riut, ygni, _rhed,
+ {{0x64bc803b,0xceb8e064,0x7afb9501,0x7e6d02df}}, // ičiu, nię_, _siut, _haap,
+ {{0x7e60d502,0x7afb9503,0x5f9526bb,0x4395241b}}, // kemp, _piut, _синт, _санс,
+ {{0x1de2c27d,0x7c28f504,0xc953e00b,0x83fce143}}, // _परंत, hadr, ימע_, ceđe,
+ {{0x394dc162,0x44446143,0xab8454aa,0x29845505}}, // xtes_, bg_, _курк, _кырг,
+ {{0x69dc6042,0x38a21506,0xb5fb00e0,0xc1056678}}, // _fyre, _móra_, ldás, _موسي,
+ {{0x3a248057,0x6443b507,0xddc1a04d,0xddc8a143}}, // _femp_, rgni, _balš, _endž,
+ {{0x69dd5508,0x6f03f509,0x7afd550a,0x7c252669}}, // _kyse, _runc, _jist, _lehr,
+ {{0x6e24150b,0x7c23e0fd,0x673ae54c,0xf1b79450}}, // _reib, _penr, ertj, _अधिन,
+ {{0x20012016,0x7c2520bd,0x6e24150c,0x27e6c143}}, // _nghi_, _nehr, _seib, _dzon_,
+ {{0x7d04150d,0x7ce02277,0x2913205f,0x2d82c156}}, // _suis, börj, _ntxa_, äker_,
+ {{0x6d4f150e,0x2375c711,0x634aa03c,0x4433350f}}, // ftca, _ماتح, _cənu, _adx_,
+ {{0x673ae3c5,0x6e28f510,0x7ce5c057,0x386d811e}}, // artj, badb, córa, _maer_,
+ {{0x55bac095,0x6576031d,0x661bc0a0,0xa5bb403e}}, // _אמנו, _vryh, lbuk, _peón,
+ {{0x64a640db,0x2d9e3511,0xed572e14,0x610c6602}}, // гава, áte_, лор_, _oğla,
+ {{0x661b64a3,0x18675512,0xc61e007c,0x7c29d513}}, // _đuka, ласи_, _থাকা_, naer,
+ {{0x78a40296,0xb87b0026,0x764440a0,0xee37020e}}, // [fe0] _iliv, blíb, rgiy, _сны_,
+ {{0x5a662818,0xb4662792,0x68fd5514,0x610c620f}}, // _скоб, _скол, _eisd, _ağla,
+ {{0x672340e4,0xef1a61e1,0x7afc7515,0x26da2088}}, // ćnja, хме_, _virt, _ikpo_,
+ {{0xb4db2017,0x673b006e,0xfbd32095,0x20d90049}}, // gràf, šuje, _לתת_, léir_,
+ {{0x61e64018,0x3ce0414f,0x82d70053,0x26138028}}, // _uzkl, mliv_, נונג_, दड़ी_,
+ {{0x6ffbc2b8,0x60c45516,0xf96aa07a,0x6acccf30}}, // _mācī, lnim, ерий_, ाव्र,
+ {{0x249d8022,0x6d41ea43,0x6494b03c,0x6e28f517}}, // bkwm_, jula, _bàir, vadb,
+ {{0xa4f8801c,0x2905b518,0x69de217b,0x290480a5}}, // _شکار_, _dula_, _mype, _tuma_,
+ {{0x5e93813a,0x8626e1eb,0x6e26404d,0x38bac0f9}}, // _القط, льне, _mekb, _aàrè_,
+ {{0x44295519,0xfc46e04a,0x7e62810d,0x3b54e1d3}}, // zaa_, _číst_, leop, акор,
+ {{0xceb8e064,0x443321e2,0xda6fd51a,0xe3bf01af}}, // rię_, _rdx_, _ля_, taño_,
+ {{0x629de00e,0x7c29d51b,0xb5fd8552,0x60c4551c}}, // ësor, caer, _saši, jnim,
+ {{0x3eb9151d,0xa2d3c0eb,0x6602d51e,0xceb8e0c2}}, // rist_, _اينت, _igok, pię_,
+ {{0x3eba600e,0x4ea7239e,0x78a401fb,0x6d41f51f}}, // jipt_, урма, _eliv, bula,
+ {{0x81dd207c,0x69de239b,0xe78422d7,0x81b881a6}}, // তেন_, _cype, буто, _ঘুম_,
+ {{0x26c33520,0x7c2520cb,0xc9875521,0x2906c0ce}}, // snjo_, _wehr, лузи, _kuoa_,
+ {{0x7c253522,0xbbe1a026,0x2d8c2065,0x26c3207f}}, // [ff0] _tehr, _फर्क, øder_, pnjo_,
+ {{0x20d913d8,0xb6032026,0x7afd5523,0x26155450}}, // péis_, část, _wist, _फ्री_,
+ {{0xdce46361,0x04c86049,0x78a40052,0x7ceda62f}}, // zvić, دولي_, _yliv, lúrg,
+ {{0xddde2026,0x6d5b805d,0xb4bed303,0xb4db2017}}, // _např, _ppua, ँचो_, ctàr,
+ {{0x68ff05fc,0x070be08d,0x7658e1ce,0x7aff000e}}, // _miqd, _संभव_, _obvy, _miqt,
+ {{0x6602c1b6,0xd126e243,0xd491a119,0x6495c3f6}}, // _agok, _وم_, _tìm_, _náir,
+ {{0xa2d63524,0xddc40013,0x6722c1e9,0x7e61e62c}}, // णवत्, _maiš, _awoj, velp,
+ {{0x64498057,0x673bd525,0x2905b526,0x6e356320}}, // _ucei, truj, _vula_, _hdzb,
+ {{0x2d98e042,0xd05d003c,0x77672048,0x200dd527}}, // øren_, _üzər, hwjx, rcei_,
+ {{0xddc4006a,0x4095e052,0x6495d447,0x26c901e9}}, // _naiš, ррит, _páis, _ajao_,
+ {{0x68ed1528,0x6a86034d,0x60c980ff,0x332dd529}}, // _bhad, ална, _kjem, ssex_,
+ {{0x3ced805d,0x7d0ea066,0x6495c057,0xe45f4156}}, // _jhev_, žisé, _váis, _sjön_,
+ {{0x6fdb2021,0x3ea9552a,0xa7fb0005,0x68ed152b}}, // _मुबं, dhat_, teña, _dhad,
+ {{0x6495c049,0x249fd52c,0x5c07ea00,0x7e63b52d}}, // _gáir, lkum_, _сяда, nenp,
+ {{0xb825007c,0x4cbae095,0xbcfb6066,0x7aed0569}}, // _ভারত_, _במסג, _fréz, _fhat,
+ {{0x6d5d46a6,0x64bac1d7,0x7c35605d,0x2ca9552e}}, // _bpsa, nċip, _adzr, ghad_,
+
+ {{0xb22673fc,0x7afe352f,0xb4c41530,0x3b8647ab}}, // [1000] имал, _tipt, ्वी_, шлаг,
+ {{0x26da2125,0x249fc6fa,0xbcfb6030,0x221660fb}}, // _ukpo_, hkum_, _kréy, рфар,
+ {{0x7c264121,0x5be2d244,0x14c84050,0x26d83531}}, // _tekr, _पर्व, _مهدی_, moro_,
+ {{0x6d43b532,0x6d5e217a,0x67241533,0x2ca94569}}, // duna, _ippa, _kwij, chad_,
+ {{0x91fce041,0x7e63a5dc,0xe45f5137,0x3d1c20f9}}, // ndār, fenp, _mjöl_, _fàwò_,
+ {{0x35db212f,0x60db9534,0x7e63a0f0,0xfaa66501}}, // _मुड़, _ekum, genp, _таго,
+ {{0xfaa6c8df,0xe297a5da,0x5066c099,0x442b0bfb}}, // ишин, рај_, атпа, yac_,
+ {{0x2d686106,0x78a9c024,0xea002067,0x6281b535}}, // _işe_, ghev, _giấu_, _enlo,
+ {{0x6b9bd536,0x2ca041e7,0xa686b537,0x7ac6f538}}, // gyug, ikid_, _клад, исое,
+ {{0x68e29539,0x26d8353a,0x660402e0,0x722461a6}}, // llod, joro_, _agik, _পাঁচ_,
+ {{0x64a6d53b,0x2be2c021,0x672401e9,0x5886c20e}}, // _кажа, _परका, _awij, _выла,
+ {{0x08fac50f,0x7524000f,0x27e00030,0x2299a0f9}}, // _شراب_, _bwiz, _byin_, _dèku_,
+ {{0x26d82618,0x6f08a6bf,0x91e3c8ab,0x65764089}}, // foro_, _hudc, _доце, jvyh,
+ {{0x3944c041,0x7cedb53c,0xfa23407c,0x2907eee2}}, // mums_, rúrg, _ফাইল_, _funa_,
+ {{0xd6db4d9a,0x787c2277,0x6282c54c,0xfc3f4071}}, // вта_, _såvä, _knoo, _alía_,
+ {{0x38a46156,0x26c90167,0x6e3600c2,0x27e3a065}}, // _röra_, _ujao_, _gdyb, _øjne_,
+ {{0x5a35614a,0x90a700e0,0xb603a066,0xb4db2017}}, // [1010] йнат, _محکم, áško, tràd,
+ {{0x63ad553d,0x6e28a78c,0x7d08a23c,0xda78604e}}, // rzan, _nedb, _ouds, иях_,
+ {{0x63ad553e,0x29090037,0x6724153f,0xfc3f4071}}, // szan, _huaa_, _zwij, _elía_,
+ {{0xdb03a066,0x7ea600c4,0x7ae29540,0x7f43a197}}, // jzná, _kópa, glot, vunq,
+ {{0x6e2b8bb8,0xee38884f,0x2bdb2064,0x66c80121}}, // ragb, рні_, _मुता, dıkl,
+ {{0x2bcfe66f,0x81e9007c,0xb4d0e026,0xd83ba099}}, // _सुझा, _বলা_, वको_, тэд_,
+ {{0x6aa9d541,0x3d0cc033,0x6e28a0e8,0xdf7380f9}}, // thef, _डूबे_, _dedb, _bẹ̀é,
+ {{0x644d0286,0x60c99542,0x1e85c39d,0xa3e8010a}}, // _ncai, _ujem, слим, _भरल_,
+ {{0xdd91a043,0x62957543,0x657aa0e2,0x249fc0c4}}, // توں_, _kozo, _erth, rkum_,
+ {{0xb7bdc12a,0xe3bf4019,0x6b9d01cd,0x3ea04534}}, // naţi, _leño_, dysg, zkit_,
+ {{0x62957544,0x6e2d5545,0x7ceda0e6,0x7d08a584}}, // _mozo, kaab, gúre, _guds,
+ {{0x6b9d01cd,0x26d91546,0x644d0090,0x39524052}}, // fysg, goso_, _ccai, ytys_,
+ {{0x6e298b83,0xf1bf4049,0x6abc20c4,0x7d01b547}}, // _meeb, _seán_, tirf, _kils,
+ {{0x7e656037,0x62957548,0x6f1bd549,0xd8398030}}, // jehp, _nozo, mpuc, _blōk_,
+ {{0x6f01a985,0x09e623ca,0xe5a33045,0xada32407}}, // _milc, совн, пири, парл,
+ {{0x442dc0ce,0x644d013a,0x67240200,0xe3bf4071}}, // nae_, _gcai, _twij, _ceño_,
+ {{0x7e9a250f,0x26d8354a,0x6f0980b6,0xf1b9c098}}, // [1020] _منظر_, poro_, _nuec, pašu_,
+ {{0x6d44506d,0x29012f3d,0x673e67a0,0x3872446b}}, // ruia, _giha_, trpj, sdyr_,
+ {{0xe61a8aa4,0x7ce02156,0x3a29005d,0xc81560ff}}, // где_, förv, _yeap_, юєть,
+ {{0x7ce5c1af,0x6b836156,0xcf256049,0x7d01b54b}}, // fórm, ånga, تركي, _ails,
+ {{0x6283e110,0xdee6a423,0x2f568101,0x7c2d8765}}, // _onno, боди, стес, ðara,
+ {{0x224d848d,0xa01b0156,0x307a200b,0x442ce025}}, // _ecek_, njör, ראַנ, xad_,
+ {{0x7d01b54c,0xb9966049,0x4420554d,0x7c29954e}}, // _dils, _الزب, obi_, _feer,
+ {{0x7d098333,0x5066c297,0x7ae4554f,0x5fc80026}}, // _fues, стка, nlit, रपाल,
+ {{0x7c2e3550,0x95ca84c0,0x7d099551,0x657b85f9}}, // kabr, _слаб_, _gues, _gruh,
+ {{0x3944d552,0x4734e11f,0x44205553,0xa2c08046}}, // rums_, онос, hbi_, लचक्,
+ {{0x867ba095,0x62960258,0x6e2e20f7,0xdbe66026}}, // _תרבו, _boyo, dabb, _करोड_,
+ {{0xb5fdc35d,0x39405554,0x7cf064cf,0xb5fb0581}}, // mešn, kris_, lärd, beál,
+ {{0x44386320,0x6abd1555,0x4394cae2,0x6e2e2ab8}}, // _odr_, risf, _фалс, fabb,
+ {{0xa7fb0359,0x6e2e3241,0x60c4a13a,0x46f6e052}}, // seño, gabb, éimh, _учет,
+ {{0xb05b004e,0x6295606f,0x7ae45556,0x27e241e7}}, // nnäk, _rozo, flit, _aykn_,
+ {{0x29024068,0x5ba71156,0x3da70844,0x7ae45557}}, // _cika_, _гроз, _гроб, glit,
+ {{0x6d460efb,0x26c78004,0x60cd0132,0x6e2d4ad5}}, // [1030] cuka, énom_, _ijam, raab,
+ {{0x2fc74016,0xaca38016,0xf99f00d1,0x3ebef558}}, // ăng_, _khủn, myè_, nitt_,
+ {{0x7d1bd43a,0xde198db3,0xce9541e1,0x78a29559}}, // zpus, لقات_, _данъ, fkov,
+ {{0x51874905,0x2006c119,0x7cefe46b,0xdb086009}}, // _луга, _ngoi_, pørg, álás,
+ {{0x6f098bda,0x7d0d46a1,0xb4db25df,0x7cf06156}}, // _quec, qqas, cràc, färd,
+ {{0x3206c0ae,0x657d555a,0x26da711a,0x8339a052}}, // _agoy_, _irsh, copo_, ачит_,
+ {{0x78a28066,0x6f01a018,0x3135755b,0x59a4a0c2}}, // bkov, _vilc, _фебр, गैलर,
+ {{0xf99f05df,0xdce9a480,0x3f77e12a,0xe299ccdd}}, // ncès_, _iseč, _rău_, раи_,
+ {{0x7c2af55c,0x3f77e12a,0x5fdb2d0b,0xf993813a}}, // _befr, _său_, _मुसल, _عبر_,
+ {{0x442ea0fd,0xa3e92010,0x6449d55d,0x4420555e}}, // caf_, _बरं_, sgei, zbi_,
+ {{0xf86604b0,0xe9a8c896,0x61ebc064,0x6e2e355f}}, // овно, لدین_, _wzgl, vabb,
+ {{0x3947b560,0x62852065,0x6b84c098,0x7d02d561}}, // muns_, _anho, _šige, _gios,
+ {{0x7c2e3562,0x7ae4400a,0x26190028,0x3ebfd563}}, // tabr, vlit, पड़ी_, miut_,
+ {{0xe8e04016,0x5fdb212f,0xdce423ee,0x61ed0037}}, // _đốc_, _मुहल, _priđ, _dzal,
+ {{0x7cefea2f,0x657d5564,0x6603230e,0x442b5565}}, // gøre, _arsh, дпра, _bec_,
+ {{0x4438613b,0x442f9566,0x6ab6a1b0,0x62853567}}, // _sdr_, jag_, _अग्र, _enho,
+ {{0x7eab2121,0x6d552265,0x7d04c13a,0x290b5568}}, // [1040] _süpe, atza, _éist, _cuca_,
+ {{0x25de4010,0x78a99569,0x60c9c126,0x290b42dd}}, // _कुणी_, _alev, hnem, _duca_,
+ {{0x6561a048,0x442a756a,0xe96a6486,0xa3e646e1}}, // _nplh, _veb_, раел_, _फरक_,
+ {{0x7d7700d0,0xb5fdc06e,0x4b4ce00b,0x443a23ba}}, // _امیر_, ješo, רגאַ, _hdp_,
+ {{0xe8faa331,0x6f02c00c,0x290b40b1,0xbe8aada4}}, // алд_, _rioc, _guca_, иске_,
+ {{0x649cc13a,0x25f18028,0xccb7a0ff,0x6f04154e}}, // _léit, ेशजी_, огії_, _liic,
+ {{0x6e2af56b,0xf99f05cd,0x7d02c064,0xda65a13a}}, // _refb, zyè_, _pios, ثاني,
+ {{0x442f8054,0x9634cd73,0x2240a20f,0x6d41e786}}, // cag_, чниц, şik_, drla,
+ {{0x7c2bc91f,0xdddd00c2,0xa3d6a026,0x6130a1e9}}, // _degr, zesł, _सडक_, _bólá,
+ {{0x7d0bc054,0x6d40c851,0x3a2f0098,0x442ea0f7}}, // _dugs, rrma, _žgp_, qaf_,
+ {{0xf8bf0004,0x3947aa12,0x60dc356c,0x7d040052}}, // tié_, cuns_, dorm, _biis,
+ {{0x7d040054,0x442cb4b2,0x60dbc057,0x3206c08b}}, // _ciis, _hed_, coum, _ugoy_,
+ {{0xf8bf05a6,0x9c134088,0x44394b67,0x78fb81a1}}, // rié_, _kọva, _rds_, יפטו,
+ {{0x60dc20e8,0xf99f00d1,0x6ab78984,0x442f956d}}, // gorm, ryè_, _आग्र, zag_,
+ {{0x3ce68e9d,0x78ad556e,0x290b556f,0x7c229570}}, // klov_, thav, _suca_, kbor,
+ {{0x291ef571,0x7d040006,0x6f03f572,0x60cd0df7}}, // mpta_, _giis, _zinc, _ujam,
+ {{0x290ca0a9,0x29037573,0x255e8041,0x78ad414e}}, // [1050] _luda_, _pija_, nālā_, rhav,
+ {{0x51873574,0xc9872769,0x7c2d1575,0xab8723b7}}, // _дума, _думи, _hear, _думк,
+ {{0x8027428b,0x290cb576,0xbc19812e,0x78ad40ff}}, // _ورحم, _nuda_, білі_, phav,
+ {{0x6f0d0296,0xf993e0be,0x26c4206f,0x6298f0cf}}, // _kuac, ורע_, émov_, _lovo,
+ {{0x7c2d1577,0x443a217b,0x0dc8401b,0x4421605d}}, // _mear, _ydp_, _дури_, pbh_,
+ {{0x7c2d1578,0xccf8e026,0x644d5579,0x25f0427d}}, // _lear, jmě_, lgai, _आरती_,
+ {{0x3d06e4ef,0x7762c005,0x60c9d57a,0x290480e8}}, // _संगे_, _apox, tnem, _fima_,
+ {{0x6d576022,0x26c16931,0x7c2d1575,0x78a98c1d}}, // mtxa, liho_, _near, _ulev,
+ {{0x3949407b,0xf77081b3,0x5f778050,0x7cf22271}}, // nuas_, _لال_, _بازر, lære,
+ {{0x7fd5e013,0xa3b6abde,0x2903eb2f,0xdddb816d}}, // зімі, _चेक_, öja_, _gnuš,
+ {{0xbb48a13a,0x6562c3ee,0x66e60d3e,0x4431357b}}, // _ولكن_, _epoh, зона, daz_,
+ {{0x9980c06f,0x216a014a,0x2905b57c,0x644120c4}}, // _žiť_, бими_, _hila_, ólin,
+ {{0x6f03e189,0x7c3b8098,0x38694cfc,0x09b87073}}, // _tinc, _mdur, dear_, _आध्य,
+ {{0x7ce4a355,0x5239e00b,0x442c28cc,0x3ae1d57d}}, // tòri, _הײַנ, úd_, rópu_,
+ {{0xea002081,0x7c3b957e,0x3869413a,0xdca600bc}}, // _khẩu_, _odur, fear_, _наки,
+ {{0x7c3b957f,0x7c2d80c4,0xb4c78026,0x6d3ae076}}, // _ndur, ðarl, ैको_, _התקנ,
+ {{0x443a207d,0x4431323e,0x7d0d1580,0xf709e067}}, // [1060] _tdp_, baz_, _guas, _mầm_,
+ {{0x4d7b000b,0x66098af8,0xf709e067,0x7e69c54c}}, // _פריע, _agek, _lầm_, heep,
+ {{0x442ca095,0x7c2294a8,0x7529844b,0x35fa4050}}, // _red_, tbor, _awez, _گردد_,
+ {{0x26dd9581,0x6909400a,0x69c4c03b,0x645c29ee}}, // gowo_, _džeb, _žiem, lfri,
+ {{0x60c1f582,0x442cb583,0x2ee69584,0x9a12c125}}, // kilm, _ped_, rlof_, _mọtụ,
+ {{0x7c2441e2,0x7cf22271,0x7e757585,0x291ee300}}, // lbir, bære, _gazp, ypta_,
+ {{0x60c1e6df,0x75298088,0x5694c2a3,0x7e68e271}}, // dilm, _ewez, дайт, vedp,
+ {{0x44313586,0x661b6480,0x60de7587,0x7e756265}}, // zaz_, _đuki, lopm, _zazp,
+ {{0x443123ed,0x6e2d1588,0x6299c064,0xe646ad94}}, // yaz_, _reab, _dowo, _неап,
+ {{0x7cf18042,0x7b0b0041,0x9c134125,0x1c020077}}, // råre, _kļuv, _họta, ोशनल_,
+ {{0x38695589,0x9c134125,0xf98fc0d0,0x765c20fd}}, // year_, _kọta, نبی_, dfry,
+ {{0x629ab58a,0xb5fb013a,0x26c20265,0x7e68e4d8}}, // _hoto, leái, giko_, sedp,
+ {{0x4431358b,0x201860d1,0x3ea6958c,0x68e2d408}}, // taz_, _ofri_, ikot_, _ikod,
+ {{0xde8fc016,0xbcfb607b,0x7ceb6106,0xb9250125}}, // _tịch_, _apén, gürl, _zipụ_,
+ {{0x63a2955a,0x997e20ba,0x24c461a6,0x7d06494f}}, // hyon, văţa_, ্তাহ, _biks,
+ {{0xe8d92016,0x2d800042,0x4431358d,0x4de6610a}}, // _trữ_, _frie_, saz_, _करजई_,
+ {{0x3869558e,0xe719413a,0x6e2e758f,0x78ad1590}}, // [1070] rear_, زيات_, _bebb, _ilav,
+ {{0x9bf4020e,0x4432436d,0x7c244035,0x7cd209cf}}, // _языч, aay_, abir, māra,
+ {{0x26cce01f,0x26c16610,0x6e244035,0x98bf012a}}, // indo_, riho_, bbib, cută_,
+ {{0x644f01fb,0x6d576265,0x6e2e6025,0xddd4c0a3}}, // ngci, rtxa, _eebb, žařs,
+ {{0x442d9591,0x69d823d1,0x78ad004d,0x60cd5592}}, // _vee_, _øver, _mlav, mnam,
+ {{0x6447c03b,0x68e941d7,0x6299c105,0x60cd5593}}, // ėjim, ċedu, _sowo, lnam,
+ {{0x24890030,0x7d0e6037,0x63a2948a,0x78ad0a0c}}, // _onam_, _gubs, ayon, _olav,
+ {{0xb4ac0026,0x7c256049,0xef19c496,0x92cda07c}}, // _गते_, lbhr, оми_, রতে_,
+ {{0x60c1f594,0x7e69cb83,0xdb08e066,0x6738e1f6}}, // tilm, reep, vzdá, _avvj,
+ {{0x7ae2d595,0x26def596,0x7e7601e7,0x7c222143}}, // _ekot, goto_, _sayp, _đoro,
+ {{0xa3e447af,0xeb99e677,0x3ce94163,0x765bc00f}}, // _पुन_, цин_, klav_, pfuy,
+ {{0x7ceb6315,0x6d445597,0x442ee363,0xddcd404d}}, // dürm, yria, _eef_, zdaš,
+ {{0x6f0640a2,0xb4db25df,0x290ee0a2,0xe72ed138}}, // _rikc, rràn, _eufa_, _ше_,
+ {{0xda0daaa1,0x26c32edc,0xcc12c125,0x44337598}}, // िखित_, gijo_, _fọsị, jax_,
+ {{0x78ad1599,0x2cad810a,0x6e2e60f7,0x61e640ff}}, // _flav, _oled_, _rebb, _sykl,
+ {{0x6e2e759a,0xf212c125,0x2d8002d9,0x7f44559b}}, // _sebb, _họrọ, _trie_, triq,
+ {{0x26c3359c,0x6616559d,0x7ae4159e,0x7d076200}}, // [1080] bijo_, rcyk, _ikit, _bijs,
+ {{0x60cd46f1,0x81dd207c,0xbddb05df,0x7e60807d}}, // anam, তেই_, rgèt, _sbmp,
+ {{0x68e9d59f,0x2cad8a14,0x9a12c125,0x21204636}}, // kled, _bled_, _mọrụ, spih_,
+ {{0x63a28157,0xddcd15a0,0x6aaaf3aa,0x3ce955a1}}, // tyon, _haaš, _छत्र, clav_,
+ {{0x4ad22ff1,0x38aac58f,0x7c2e6a88,0x2d8f4262}}, // _सदाव, _bùrn_, _tebr, äget_,
+ {{0x6d4ab5a2,0x2be1c028,0xcc12c079,0xf212c079}}, // tufa, _पड़ा, _nọrị, _nọrọ,
+ {{0x6d4560e2,0x63a3a0ff,0x6f1c6579,0x387864b9}}, // crha, gynn, _utrc, _iarr_,
+ {{0x6d4b95a3,0x3f8241d8,0x387875a4,0x6d58b5a5}}, // buga, _krku_, _harr_, rtva,
+ {{0x81e0407c,0x2002c052,0xb4da8a53,0x290ef5a6}}, // ধের_, äki_, ठकी_, _sufa_,
+ {{0x26ccf5a7,0x38a9e0f9,0x24890425,0x6d46035d}}, // undo_, _dúro_, _snam_, drka,
+ {{0x387864b5,0xa7fce106,0x4424d5a8,0x6d4615a9}}, // _marr_, ldız, pbm_, erka,
+ {{0x4fc6c974,0x7ea06156,0xf8bf20fa,0x9c7cf5aa}}, // _осла, _köps, ipée_, doče,
+ {{0x27e6d5ab,0x26cea024,0x60c449c5,0x6aa340ba}}, // _uyon_, onfo_, diim, _înfi,
+ {{0x7c2f55ac,0x7ceb6315,0x68e415ad,0xdfd4e1ba}}, // _secr, türm, _ekid, _полы,
+ {{0xafdb0065,0x9d182762,0x7c2d80c4,0x290fc020}}, // ggør, дост_, ðark, _zuga_,
+ {{0x3f5735ae,0x394695af,0x816b062a,0xbebcc018}}, // rçu_, kros_, _дроб_, _avīz,
+ {{0xb5fdc06e,0x6d5af5b0,0x2bde949f,0x7afd15b1}}, // [1090] vešk, ltta, _फुला, mmst,
+ {{0xa7752878,0x26cdd5b2,0x9c7cf5b3,0x6d4b8610}}, // _злоч, zneo_, ločb, vuga,
+ {{0x60cd41cf,0x92c3007c,0x6566405d,0x7ea4e017}}, // pnam, ্তী_, _epkh, _còpi,
+ {{0x7d076171,0x19954866,0x628ae0ae,0x7d08b5b4}}, // _wijs, _замя, _lnfo, _aids,
+ {{0x2366c05f,0x6b82c022,0xe76b825b,0x628af5b5}}, // _npoj_, _nrog, تحان_, _onfo,
+ {{0xa3c0e8a2,0xfaa7a09e,0x68e9d5b6,0x66c80106}}, // ूपा_, ьшен, wled, lıkt,
+ {{0x290fd5b7,0x7982d5b8,0x25a9806e,0x7d08a2d9}}, // _suga_, _arow, šal_, _dids,
+ {{0x63b160e4,0xb5fdc7a0,0x62899273,0x26c4c013}}, // đenč, leši, _uneo, gimo_,
+ {{0xc05ba84f,0xf1dfa10a,0x2f15294b,0x291ea582}}, // _між_, _पुरन, _låg_, _etta_,
+ {{0x26cdc504,0x0325e51b,0x3a3f8058,0xd838a2e2}}, // sneo_, ндон, _hdup_, _poče_,
+ {{0x61e98037,0x2f140156,0x7ea075b9,0x7cf06156}}, // _hyel, _säg_, _köpr, kärn,
+ {{0x6e3e2098,0x443175ba,0xe7e32033,0xfb24e050}}, // _sdpb, _lez_, _गड़ा_, کریپ,
+ {{0xf1c3a029,0x850be7b3,0x661b8167,0xe3aec04f}}, // _वेतन, _संकट_, _mfuk, _сб_,
+ {{0x7afd1308,0x7af60153,0x6d5bd331,0x6ef6003e}}, // amst, _whyt, mtua, dábe,
+ {{0xb4cbe010,0x2d98e271,0x44268025,0x6d5bc13c}}, // रची_, øret_, ybo_, ltua,
+ {{0x9c7cf5bb,0x81da807c,0x291e2041,0x7ae415bc}}, // roče, ডেট_, īta_, _ukit,
+ {{0xff244043,0x60c615bd,0x6ef60005,0x216755be}}, // [10a0] ئبری, mikm, gábe, нити_,
+ {{0x394685da,0xb9db0095,0x6d5bd5bf,0xfc3f0049}}, // vros_, _מחפש, itua, bhís_,
+ {{0x63b64064,0x2367e05d,0x2d8f53a2,0x03a36517}}, // szyn, _jpnj_, äger_, лифо,
+ {{0x26c4d18c,0x3ea955c0,0xc878a6df,0x2d8244a3}}, // ximo_, ckat_, _sağ_, _trke_,
+ {{0x6d5bc0dd,0x7ea6001f,0xada34099,0x29120068}}, // jtua, _cópi, _баял, _juya_,
+ {{0x2b468017,0x2d58e0fa,0xd00fa243,0x80bca077}}, // rroc_, yée_, مله_, ्चें,
+ {{0x6205c03c,0x290a6561,0x629e2052,0x232a6af5}}, // ərlə, _iiba_, _mopo, _хоби_,
+ {{0xea002119,0xe60a80eb,0x6ee38046,0x2b468b67}}, // _nhậu_, _رزرو_, sõbr, proc_,
+ {{0x443ea683,0x60c60037,0x291175c1,0x6b8415c2}}, // _tdt_, dikm, _zuza_, _nrig,
+ {{0x6b83e037,0xb87b40f9,0x26c5f242,0x44321209}}, // _brng, _akíl, gilo_, _aey_,
+ {{0xddc3a12a,0x443215c3,0x6d5ae052,0xd2b76095}}, // renţ, _bey_, utta, גלית_,
+ {{0x6d5c35c4,0x7cd3c0ba,0x7982d5c5,0xea002119}}, // dtra, păra, _trow, _chậu_,
+ {{0x2f15294b,0x7eaa4066,0xb5fb4031,0x2a64805d}}, // _såg_, _výpl, _abán, _mbmb_,
+ {{0x2bd97303,0x44320025,0x92c3007c,0x6b840784}}, // _बुझा, _eey_, ্তে_, _drig,
+ {{0xd5afed94,0x443215c6,0x65696098,0x386053eb}}, // _сс_, _fey_, _ćeho, ffir_,
+ {{0x27e90561,0x67208320,0x6ef6003e,0x68e295c7}}, // _tyan_, _otmj, tábe, jood,
+ {{0x5455667b,0xa923a03b,0x9984013a,0x3d1127c8}}, // [10b0] кват, ąžin, _مليو, _दूरे_,
+ {{0x59b0a292,0x6d4f15c8,0x6f0721e9,0x386dd5c9}}, // जनार, muca, _débì, zeer_,
+ {{0x6d5bc265,0x6d5d07ce,0x629d55ca,0x4427a6a6}}, // ztua, ltsa, _toso, vbn_,
+ {{0x7ec7c187,0x60c72363,0x2ea9abed,0x629f013e}}, // _mèpè, lijm, कस्त, _joqo,
+ {{0xa0364076,0xc88640cb,0x7f4d4926,0x09c9b1c9}}, // _בארה_, ößen_, quaq, िप्य,
+ {{0xe5a295cb,0x2a7ca098,0xf09f4067,0x628bc0ae}}, // риши, gdvb_, _hoà_, _sngo,
+ {{0x673ae200,0x68e1f5cc,0x7aed55cd,0x08d5c9fd}}, // jstj, vold, olat, кция,
+ {{0x6d4f15ce,0x394dd5cf,0x673ae584,0x290a62a9}}, // kuca, tues_, dstj, _ziba_,
+ {{0x6d48eac8,0x23690223,0x78a9d5d0,0x248d8019}}, // erda, _npaj_, rkev, _inem_,
+ {{0x394dd5d1,0x25e66029,0x78a9d5d2,0x6c74a33d}}, // rues_, _जुडी_, skev, _مطمئ,
+ {{0x68e1f5d3,0x79840b39,0x6d48e098,0x394dd5d4}}, // rold, _sriw, grda, sues_,
+ {{0x7cf6a0ba,0x443322be,0x6d5d15d5,0x2b4954c3}}, // râre, _aex_, ftsa, krac_,
+ {{0x26c5f5d6,0x68e1f5d7,0x68ed55d8,0x6d4f016d}}, // pilo_, pold, dlad, guca,
+ {{0x2b4955d9,0x6d4e35da,0x6d49d5db,0x68e3adf7}}, // drac_, tuba, mrea, nond,
+ {{0x42564b7a,0x09e62fb9,0xceb2e0be,0xc7b9c0e0}}, // ктат, товн, _דיג_, lző_,
+ {{0x79840403,0xf74a013a,0xe7e56033,0x6d5c35dc}}, // _triw, _قلبي_, _कड़ा_, rtra,
+ {{0x2120005d,0x6d4375dd,0xada34099,0x6b8415de}}, // [10c0] _utih_, ánan, аатл, _urig,
+ {{0x96fa213a,0x26c7b5df,0xd0f44010,0xe610603c}}, // _تعبر_, jino_, _आठवण_, _düşə,
+ {{0x98c6020b,0xe0d560ff,0x68e295e0,0xe45a82f0}}, // _लगाए, ують, tood, дже_,
+ {{0x7e7b8071,0xa2c144e5,0x6441b5e1,0xd308e119}}, // _caup, रोद्, _adli, _hệt_,
+ {{0xdce2e431,0xd3484050,0xaa7b0431,0x7e7aa561}}, // _spoč, _سیاه_, plýv, _patp,
+ {{0x290b55e2,0x2d85a012,0x2ab0a1e9,0x7524c106}}, // _zica_, _brle_, _bàba_, _çizi,
+ {{0xd308e067,0x7c29ccf9,0x7ae295e3,0x443835e4}}, // _mệt_, fber, poot, iar_,
+ {{0x6e29c4ec,0x2918e41a,0x7d1b403e,0xb6a39537}}, // gbeb, íram_, _éusc, _кисл,
+ {{0xdc54a63b,0x2d8480ba,0x6d40004d,0xa067c60f}}, // _مراک, _urme_, šmaj, тања_,
+ {{0x673ae069,0x7cf06156,0xf70a0119,0x6d5d0022}}, // tstj, kärm, _mầu_, vtsa,
+ {{0x623500d7,0xee3f0066,0x26d24037,0x9e3515e5}}, // лену, ským_, knyo_, ленч,
+ {{0x6d5d15e6,0x443820fd,0x60c4000e,0xf8d2b1c9}}, // ttsa, ear_, _cmim, _तदुप,
+ {{0x7e7d009a,0xc649c049,0x6d5d15e7,0x6ef8807b}}, // rdsp, _أجمل_, utsa, cíbe,
+ {{0x6d5d0831,0x7d1d85df,0x7ac72bbb,0x3866c133}}, // rtsa, _ésse, тсме, _hbor_,
+ {{0x6d5d0ffe,0x26c48037,0x57a435e8,0x68ed55e9}}, // stsa, _ommo_, ашта, tlad,
+ {{0x32550501,0x497515ea,0x7ae455eb,0x3f5c6a60}}, // _свир, _блис, goit, díu_,
+ {{0x7ae3b45d,0x7e7b95ec,0x290b55ed,0x63a720c2}}, // [10d0] yont, _raup, _vica_, syjn,
+ {{0x3a3835ee,0x6ef603cb,0xd838f5ef,0x717935f0}}, // carp_, mába, nač_, _збор_,
+ {{0x38b1d239,0xceb3e00b,0x7bceb5f1,0x25a52277}}, // _lára_, סיע_, _žbun, älld_,
+ {{0x9295439e,0x7ae3b5f2,0xac198250,0x7c222143}}, // _самц, wont, ходу_, _đori,
+ {{0x628f55f3,0x38b54277,0xe504020b,0x7e7c703c}}, // _inco, _vård_, रगति_, _farp,
+ {{0xeb9081b3,0x6f0bc1fb,0x6d5d42a4,0x63a00b57}}, // تظم_, _sigc, _iqsa, ømni,
+ {{0x289c200b,0x2eedd5f4,0x3f86c057,0x7eadeddb}}, // ויגא, tlef_, _arou_, _dúpl,
+ {{0x78ad55f5,0x3f86d5f6,0x48d1c07c,0x9c7ce07f}}, // nkav, _brou_, াত্র, voča,
+ {{0xa3b5f414,0x64686069,0x7e7d55f7,0xdce54041}}, // चैन_, _þrið, _masp, _arhī,
+ {{0x7cf60057,0xe453a68c,0x2d84420d,0xd838e12d}}, // dára, _خضر_, _šmek_, gač_,
+ {{0xa6ca033f,0x3a3820c4,0x200fc05d,0xbea60e12}}, // елна_, varp_, _tggi_, ганк,
+ {{0x78ad423d,0x6e3575f8,0x7cf06156,0x61ed011d}}, // jkav, _lezb, värm, _byal,
+ {{0x4a9ac0be,0x20cd26a6,0xd838e35d,0xd945d5f9}}, // _רינג, džib_, bač_, _сели,
+ {{0x290c35fa,0x38b66065,0xe1f0c19a,0x6e398048}}, // öda_, _kære_, _بسم_, hawb,
+ {{0xdb0620e0,0x7643e037,0x6d5e75fb,0xa3c9a5cb}}, // nyké, _idny, xtpa, _लेम_,
+ {{0x60c9d5fc,0x2a7fc10a,0x26182466,0x3eadcc90}}, // niem, ndub_, बुजी_, nket_,
+ {{0x7e7c75fd,0x395fd593,0x7ae455fe,0x38cb4050}}, // [10e0] _parp, ntus_, roit, رافی_,
+ {{0x395fc04e,0x443835ff,0x7ae44052,0xb4dda026}}, // itus_, qar_, soit, णको_,
+ {{0x7e7c7600,0x7c3600c4,0xa91d8012,0x07a6287c}}, // _varp, _keyr, _bržo, _санн,
+ {{0x7e7e3601,0x27ed9602,0xbf0f8064,0x26ca3603}}, // _happ, _byen_, ादून_, libo_,
+ {{0x04438501,0x7c361604,0x8c4383d5,0x629bc098}}, // сечн, _meyr, сече, djuo,
+ {{0xdee3a6fc,0x24804181,0x23604022,0xbcfb20fa}}, // бочи, ldim_, mtij_, ptée,
+ {{0x6aa1b605,0x35a3612a,0x76440079,0x430ac04d}}, // _dolf, _ларг, _odiy, нхим_,
+ {{0x442002f4,0xa3dd0029,0xacea813a,0x60c9d606}}, // _nfi_, _तुझ_, _أرسل_, giem,
+ {{0xd838e530,0x6f029607,0x68fc7608,0x395fd609}}, // tač_, mmoc, _khrd, gtus_,
+ {{0x63a8e042,0x64440b3e,0x7ea3410a,0x7cf605ba}}, // tydn, _adii, _lõpp, vára,
+ {{0x645ab60a,0x2a7dc286,0x7524160b,0x7d0d160c}}, // _icti, _fawb_, _atiz, _pias,
+ {{0x6f02807b,0x6ef6160d,0x6aa1a197,0x6360a291}}, // nmoc, tába, _zolf, _ịnwe,
+ {{0xdb0465ba,0x9a86e1d3,0xc3878031,0x1c1928a2}}, // nzió, _буйл, _bẹ́ẹ, _दलदल_,
+ {{0x6ef6160e,0xb5fdc35d,0x4444249b,0xaca36125}}, // rába, rešt, _cd_, _kwụb,
+ {{0xa3cbe0c2,0x442000fd,0x2d856017,0x3135360f}}, // _रेप_, _ffi_, _àlex_, ребр,
+ {{0x290ef610,0x6d437611,0x30a4401f,0xd468060f}}, // _hifa_, ánam, брув, _биће_,
+ {{0x9259601b,0xdd944052,0x7afc6090,0x344b6af5}}, // [10f0] каат_, баты, _bhrt, ечен_,
+ {{0x68e99612,0x9c7ce299,0x09d7e07c,0x656d0bae}}, // _sked, zočn, _হরতা, _ipah,
+ {{0xe7873613,0xef1f4105,0xd01144c7,0x78ad4018}}, // _супо, _grün_, _ولد_, pkav,
+ {{0xa3c3e04a,0xf8bf6187,0x2f0b4031,0x6d4b849f}}, // náší_, _awét_, _dògì_, trga,
+ {{0xaca36125,0x2244805d,0x7bdc605d,0x763a400b}}, // _awụb, _ddmk_, _exru, _בערג,
+ {{0x6b836277,0x64dee010,0x38b42241,0x26c6c0f9}}, // ångs, नकोश, _kära_, _amoo_,
+ {{0x7ae608b2,0x7c2d80c4,0x7cf60066,0x9ed87512}}, // yokt, ðars, dárn, _смрт_,
+ {{0x290ef614,0x6aa2d615,0x395fd616,0x58d580ff}}, // _aifa_, _coof, ttus_, _тобт,
+ {{0x7c3aec8a,0x2295a63b,0x6d46cf9e,0x290ef617}}, // fatr, _پلاس, škan, _bifa_,
+ {{0xa2a18c87,0xe765f28f,0x7c2d4af4,0x88bd0026}}, // कॉर्, авоп, hbar, spěl,
+ {{0xbd4ba13a,0xe8fa01cc,0x3f890108,0x443b400e}}, // _سؤال_, кло_, _arau_, kaq_,
+ {{0x7c2d4064,0xb4c140a8,0x395fd618,0x443b405d}}, // jbar, ्चो_, ptus_, jaq_,
+ {{0xb8de08ae,0x29020055,0x443b4716,0xa3c9a046}}, // _इत_, rmka_, daq_, _लेत_,
+ {{0x38b42156,0xa3e664d6,0x644b4193,0x6eefe22e}}, // _bära_, _पडल_, ógin, købi,
+ {{0xe73725d3,0xddc72cf5,0x7cefe14f,0x7c2d5619}}, // иер_, tejš, jøri, fbar,
+ {{0x14264a8d,0xb4d0404b,0x26ca361a,0xb4e040aa}}, // рдам, वचे_, sibo_, दके_,
+ {{0x7e7e361b,0xddc7242e,0x7a40629b,0x2c014010}}, // [1100] _tapp, rejš, játí, लेलं_,
+ {{0x6aa40049,0xdb18a009,0x3d0fa0c2,0x394d0057}}, // _hoif, szvé, ादों_, áese_,
+ {{0x2360561c,0x2d85304e,0x394dc105,0x7d0f4256}}, // stij_, ålet_, hres_, _fics,
+ {{0x95ca803b,0x9c144096,0x7d06a009,0x1b1d807c}}, // кула_, _kọmp, _éksz, _নিজে_,
+ {{0x387f8403,0x4422561d,0x5f06ae12,0xdddc20e0}}, // _daur_, _ifk_, азда, merő,
+ {{0x2ab1d61e,0x7d02961f,0x6b899620,0x442dd621}}, // _bábo_, smos, _dreg, ebe_,
+ {{0x7989818b,0xdfcfa13a,0x645c6024,0x39405622}}, // _erew, ويه_, _icri, lsis_,
+ {{0x394dd623,0x8c3ca686,0x3cfdc048,0x290ee296}}, // fres_, _doğa, _chwv_, _sifa_,
+ {{0xf50ab624,0x649cc057,0x44205625,0x7cfd1626}}, // _анал_, _méix, ici_, nére,
+ {{0xd406e691,0x442dd4b7,0xfaa6f0c4,0xf992a6ec}}, // ияни, abe_, ршин, _وبا_,
+ {{0x44225627,0x7b096098,0x394dd628,0xdddd4041}}, // _ofk_, _džuh, ares_, _pasū,
+ {{0x6aa41629,0x6f0443ee,0x2b4042d1,0x6281eb56}}, // _coif, jmic, ksic_, adlo,
+ {{0x4420562a,0x6562962b,0x6280962c,0x78a40004}}, // dci_, ntoh, _mamo, _doiv,
+ {{0x6d40d62d,0xd838a0ca,0x6ce721b4,0x3f8903ac}}, // msma, _alčz_, ріме, _vrau_,
+ {{0x443b45fc,0xd6d8219c,0x7cf6003e,0x4a31e06f}}, // taq_, ртя_, cáro, äčší_,
+ {{0x5a3501fc,0xdced2098,0x6562800e,0x6f044361}}, // _унит, _osać, ktoh, gmic,
+ {{0x2a7f8dfb,0x2918762e,0x656280dd,0x92014a41}}, // [1110] _raub_, _aura_, jtoh, लेंज_,
+ {{0x2a7f8286,0x443cb62f,0x44205630,0x68e8e1cd}}, // _saub_, jav_, aci_, nodd,
+ {{0x443b5631,0x7c38e54c,0x442dcc90,0x62808c75}}, // paq_, _mevr, ybe_, _bamo,
+ {{0x38b9a004,0x6d40d632,0x4438623c,0x2a7f805f}}, // _mère_, ksma, _eer_, _qaub_,
+ {{0x7aed0296,0x7bd84121,0x7cf18249,0x4abb2021}}, // _mkat, şvur, nåri, _उताव,
+ {{0x63ad5633,0x443cb634,0x7d07c0e0,0x9e3ca06f}}, // myan, gav_, _éjsz, _poďa,
+ {{0x27fa8064,0x3cfdc022,0x6a226043,0x7aed1635}}, // ępna_, _phwv_, _رہنم, _okat,
+ {{0x7cf600e0,0x2b49acb1,0x6d4f0552,0x62828054}}, // váro, čac_, krca, cdoo,
+ {{0x7c3bd21e,0x442164b9,0xdcb1c067,0x29187636}}, // raur, och_, _của_, _zura_,
+ {{0x26ccf535,0x6729c267,0x2918607d,0x7c22c12a}}, // gido_, rpej, _yura_, _efor,
+ {{0x7d045637,0xea002067,0x3cfdc022,0x20d2006f}}, // ymis, _khấu_, _thwv_, hšie_,
+ {{0xe50a0016,0x442f804d,0x24800022,0xc0c8c5d3}}, // _cặp_, nbg_, _qaim_, _куче_,
+ {{0x60c98096,0xe8076290,0x63ad4132,0x6aa3f15b}}, // _mmem, वेता_, jyan, _tonf,
+ {{0x6281b638,0x6abaf639,0x64464037,0xed59e013}}, // _jalo, chtf, _sdki, _бок_,
+ {{0x4420562f,0xa0a60407,0x7cfde004,0x4438763a}}, // tci_, _гайд, gère, _rer_,
+ {{0x60cd446a,0x672dc1d7,0x6e3c27cc,0xb920a031}}, // giam, _ħajj, sarb, _aarọ_,
+ {{0x63ad436c,0x29186a36,0x6a16628b,0x6b9bc2b2}}, // [1120] gyan, _sura_, مبار, nxug,
+ {{0x60db80f9,0xee376376,0x60c98af6,0x4422163b}}, // _ajum, йну_, _amem, lck_,
+ {{0x61fb963c,0xb5fb4057,0x5d86213a,0x443a20ca}}, // _azul, _acád, _الجل, _iep_,
+ {{0x29120a2d,0xba16807c,0x7d1b606e,0xb87b40f9}}, // _jiya_, ার্ড_, _čust, _ajíf,
+ {{0x29120b33,0x2fd72555,0x6bd723f1,0xd838a07f}}, // _miya_, _نوید_, _نویس_, _peč_,
+ {{0x48e3a1e1,0x4439563d,0x61fb843d,0xdee3b63e}}, // почв, _zes_, _dzul, почи,
+ {{0x6d56563f,0x7c3e7640,0x44395641,0x9186a13a}}, // luya, mapr, _yes_, إجتم,
+ {{0x60db800e,0xd00a8501,0x6cd6833e,0x7cf60019}}, // _gjum, _бебе_, _اقرا, fárm,
+ {{0xf09f4016,0x7c3d012a,0x2fc0516b,0x6e3d0747}}, // _giàu_, vasr, nzig_, vasb,
+ {{0x29121642,0x757b40be,0x291a2aaa,0xbda6e1c1}}, // _aiya_, נטיפ, _oupa_, محجو,
+ {{0x7eade005,0x2d8ae9d7,0x63ad5643,0x2cb87644}}, // _xúpi, íbe_, zyan, _mlrd_,
+ {{0x6d43613a,0x2ca04feb,0x6d4f02e2,0xdcfba143}}, // ánai, jjid_, trca, _pruć,
+ {{0x6281b645,0x7c3d017b,0x6111c041,0x7cf06105}}, // _yalo, rasr, _tālr, läru,
+ {{0x9985e013,0xf8bf7646,0x66160009,0x9f34e14f}}, // galų_, _atén_, _egyk, _декі,
+ {{0x2ca6d5c7,0xdddd0009,0xa49b4733,0x5726e33d}}, // _kood_, reső, _biòp, _ارتق,
+ {{0x7cd3c12a,0x26ceb647,0x8cf5484f,0x3ea0400e}}, // tări, hifo_, ізац, gjit_,
+ {{0x4034404e,0x6fb8c028,0xdd9441d3,0x3ea6c136}}, // [1130] яетс, इनिं, паты, _moot_,
+ {{0x6b8d1648,0x7cd3c12a,0x9d1bc095,0xb87b40f9}}, // _irag, rări, טומט, _akít,
+ {{0x257ae041,0x6281b649,0x09ada07c,0x7cd3c0ba}}, // _jūl_, _ralo, _কেনা, sări,
+ {{0x2ca6d071,0x6d5655e3,0x7cd3c0ba,0x63ad5086}}, // _nood_, buya, pări, pyan,
+ {{0x291a364a,0xfaa5ada4,0xdca5b097,0xb87b40f9}}, // _zupa_, жало, жали, _ajíg,
+ {{0x1fa9b64b,0x7cf88dc1,0x69c0c480,0x2eb1c026}}, // икли_, bíro, dzme, जस्त,
+ {{0x7cf8964c,0xea002119,0x5c5c2053,0x6d41e09a}}, // círo, _thấu_, אדוק, xsla,
+ {{0x60c989fa,0x7d1ab64d,0x2fdf8048,0x2d87b019}}, // _umem, _duts, _txug_, rvne_,
+ {{0xdbf20026,0x68e9c1cd,0x78bbc97a,0x386d80d5}}, // _přír, roed, rhuv, _iber_,
+ {{0x6e3b8885,0x78bbd64e,0x7e6d0e6a,0x672d4c8c}}, // _heub, shuv, _abap, ipaj,
+ {{0x2169ed73,0x2ca6c095,0x2912164f,0xa2af2026}}, // шини_, _food_, _piya_, ुसन्,
+ {{0x2901205d,0x2483600e,0x7c3e6b78,0x91a00088}}, // _ahha_, _lajm_, zapr, _ndị_,
+ {{0x7d1b82df,0x6e2482b2,0x7c3e61e7,0xe29fc069}}, // _juus, ñibi, yapr, öðu_,
+ {{0x998361f6,0xf8b580c2,0x69dcc106,0x6ab58028}}, // _bejż_, ंसुप, ğret, ंसुर,
+ {{0x644981ae,0x29120a85,0x7cf60803,0x798d1650}}, // _odei, _tiya_, nárk, _eraw,
+ {{0x3ea7e030,0x6e3e6037,0xfc3f0049,0x64498049}}, // _hont_, wapb, caí_, _ndei,
+ {{0xb5fb4005,0x3ea0405d,0x67299651,0x7ce5c005}}, // [1140] _acáb, rjit_, _ntej, fórz,
+ {{0xb4c7e0c5,0x62841652,0x3eb24530,0x93bce0ba}}, // _ईगो_, _haio, skyt_, mbăr,
+ {{0xb05b0277,0x3e4cc03b,0xf772628b,0xa3c9a10a}}, // miär, lėtų_, باء_, _लेल_,
+ {{0x2ca7f653,0x6283e530,0xed5a54b0,0xd838a06e}}, // _lond_, _lano, _ком_, _točk_,
+ {{0x2ca6c200,0x44325654,0xf48786b0,0xdb0ee066}}, // _rood_, mby_, تالی, ždém,
+ {{0x2d800157,0x7af6404e,0x7ae9406e,0x7f429631}}, // _msie_, llyt, čete, rsoq,
+ {{0x26cf9655,0x3ea6d656,0x2d8d8125,0xa09b20be}}, // bigo_, _poot_, _eree_, ייסט,
+ {{0x7c3b8684,0x2005003c,0xd838b657,0xbe8ad2ad}}, // _geur, əlif_, _moči_, рске_,
+ {{0xd7fac3b7,0x3ea7f658,0xeeb7c0ff,0x7c244012}}, // шук_, _bont_, ільш_, icir,
+ {{0x6e3c6133,0x39583659,0x7c3b965a,0x7aeaa00e}}, // _jerb, kurs_, _zeur, qoft,
+ {{0xdd90e24d,0x6b8d0b39,0x3ea6c08b,0xac1900ff}}, // روح_, _srag, _toot_, йону_,
+ {{0x29132057,0xe297c143,0x212901e7,0x7e776119}}, // _sixa_, _мах_, _ptah_, zexp,
+ {{0x6283eed9,0x236683ac,0xfbc50599,0xd7fb0fdb}}, // _fano, ntoj_, _लेखम, рун_,
+ {{0x6296165b,0x69c28171,0x2484965c,0x672d4022}}, // _enyo, izoe, _lamm_, vpaj,
+ {{0xcd33e049,0x02a7409c,0x3958365d,0x2720c119}}, // _قريب, орам, gurs_, _hòn_,
+ {{0x4c944052,0x7640c5d2,0x628400fc,0xd838a320}}, // мисс, namy, _gaio, _doči_,
+ {{0x236683ac,0x4adba077,0x6d4452c1,0x651541c1}}, // [1150] jtoj_, _बदलव, gsia, سوائ,
+ {{0x672d565e,0xd838b65f,0x7640c132,0xe8014064}}, // rpaj, _foči_, hamy, लेगा_,
+ {{0xddc402d1,0xa3c9a4da,0x61406ac3,0x7d1b82df}}, // _ibiš, _लें_, _rálá, _suus,
+ {{0xa879400b,0x317a200b,0x3946c9fe,0x7d1b9660}}, // _פאָר, _פארד, _ovos_, _puus,
+ {{0x24849661,0x27fea012,0x29095662,0x7640d663}}, // _damm_, _aztn_, mmaa_, damy,
+ {{0xcf580095,0x3d16a077,0xc17240be,0x6440c071}}, // קבות_, _पंखे_, _רחם_, eami,
+ {{0x6f02c28f,0xdddbc143,0x6915a1f6,0x201e40ba}}, // _bhoc, dduš, _aġer, _ştii_,
+ {{0x644ae133,0x237f8286,0x6f02d664,0x3eb9c069}}, // _adfi, _tsuj_, _choc, óst_,
+ {{0x6f1d540e,0x20d2006f,0x26d13665,0x2d8ee0e8}}, // _jusc, jšia_, kizo_, _arfe_,
+ {{0x62840265,0x7ebcc004,0xdce2e17a,0x44324431}}, // _saio, _répe, _proġ, zby_,
+ {{0x6440c97e,0x212dc993,0xe8e00067,0xf1a8c050}}, // bami, speh_, _ngợi_, مایه_,
+ {{0x6285208b,0x6d457666,0x2a782105,0x01faa076}}, // _aaho, isha, werb_, _הפעל,
+ {{0x2d813667,0x7c244199,0xa295e0ff,0x628401e2}}, // _ashe_, vcir, _наді, _vaio,
+ {{0x752960a9,0x80b981a6,0x1e84e14f,0x6f09d668}}, // _čezn, েকট্, _олім, lmec,
+ {{0x6abb9669,0x78bb800d,0xe29a4080,0xa09ae053}}, // _oluf, _oluv, бав_, _ליסט,
+ {{0x6441f66a,0x645c21cd,0xaa7b4069,0x6f1eb66b}}, // lali, sgri, _skýr, ípci,
+ {{0xcb130095,0xef1742ae,0xd00743d5,0x443cf66c}}, // [1160] _אלה_, зму_, зете_, _yev_,
+ {{0x443dc659,0x26d1207b,0x7d09d66d,0x6f09c105}}, // _lew_, cizo_, hmes, hmec,
+ {{0x6aa8b66e,0x2721e069,0x7d04166f,0x24848037}}, // _rodf, _jón_, _khis, _pamm_,
+ {{0x7cf4e355,0xa5078dd1,0x7c3c60f7,0x7d1d5670}}, // ràri, пеха_, _werr, _fuss,
+ {{0xfe7060e0,0x24848030,0x6f1d4197,0x33f68049}}, // _لگا_, _vamm_, _gusc, أساس,
+ {{0x81be007c,0x7c3e2403,0x2d825671,0x7640c614}}, // _আশা_, _jepr, _iske_, wamy,
+ {{0x7640cedd,0x6f02d672,0x7c3b0069,0x291dd04a}}, // tamy, _phoc, ðurk, _auwa_,
+ {{0x1c1ea0c2,0x3afac00e,0x6266c13a,0x6f040088}}, // _पलवल_, rëpo_, سابق, _nhic,
+ {{0xaca3a067,0x291dd141,0xdee380ff,0x7c86ed82}}, // _cuồn, _cuwa_, _чоти, пуне,
+ {{0xdb1ae0e0,0x16014029,0x443ce05f,0xc1bb8095}}, // sztí, लेटर_, _qev_, _הממש,
+ {{0x7cefe042,0x7640c5f1,0x2d8fc3c5,0x4427e0d4}}, // tørr, pamy, _erge_, _dfn_,
+ {{0x2eb96148,0x443ce022,0x291dc0a2,0xbec36018}}, // _इत्त, _wev_, _fuwa_, _ķīmi,
+ {{0x291eb673,0x6f040054,0x3f8fc098,0x2721e057}}, // _huta_, _dhic, _grgu_, _eón_,
+ {{0xd041e03c,0x7c3e215e,0x7d041674,0x20d21675}}, // kilə, _cepr, _ehis, ršia_,
+ {{0xe1ff40e0,0x6d598ee4,0xb4bd2064,0x3b09403c}}, // _szó_, zuwa, _आती_, tmaq_,
+ {{0x62853676,0xfdf7e0be,0x7d040e70,0x2903605d}}, // _waho, _מצות_, _ghis, _shja_,
+ {{0x29095677,0x6d457678,0x64428351,0x20c0f03c}}, // [1170] rmaa_, wsha, daoi, _còig_,
+ {{0x6288f679,0xddc40098,0x2d81205f,0x752d063f}}, // nddo, _ubiš, _tshe_, _itaz,
+ {{0xf579619a,0x26d24025,0x6f1e2012,0x7c25767a}}, // تماع_, biyo_, _gupc, rchr,
+ {{0xc333e095,0x6286567b,0x6e2560cb,0xf8aea062}}, // תוף_, _gako, schb, یکل_,
+ {{0x6b82c385,0xfaf8e018,0x2bc8567c,0x4e94a0eb}}, // _nsog, klī_, _еуро_, کشنر,
+ {{0xeb99a0d6,0x69096013,0x20cd204d,0x7cf18b57}}, // жик_, _ežer, džik_, tårs,
+ {{0x644d0153,0x6f172071,0x7d09d67d,0x0d8461ba}}, // _odai, _mixc, tmes, _плён,
+ {{0x7cf18042,0xdbd7a2df,0xa2c14077,0x752d0066}}, // rårs, väär, रोच्, _otaz,
+ {{0x27ff83df,0x6aaaf67e,0x60dbd67f,0x62877680}}, // _uzun_, _boff, rnum, _kajo,
+ {{0xfc3f5681,0xe9d9f0e1,0x6d5af682,0x7d1600a2}}, // _leí_, жко_, futa, _siys,
+ {{0xdddc63b1,0x291eb4e4,0x69c45683,0x98a06299}}, // _karš, _guta_, zzie, spić_,
+ {{0x644bca2f,0x2b52c6df,0x8d74a0d0,0x7641f684}}, // _udgi, üncü_, _ساما, saly,
+ {{0x6d4603fc,0xd041e03c,0x443ea095,0x7c3e3685}}, // uska, zilə, _yet_, _pepr,
+ {{0x6289d686,0xe739e2d3,0xfd5f0016,0x443f9687}}, // ldeo, _дек_, _huyệ, _leu_,
+ {{0x7769c3f5,0x26d25688,0xe45f1689,0x6443a425}}, // ltex, tiyo_, giös_, iani,
+ {{0x225fc97f,0x7c2d84cd,0xdddc603b,0x6e2d807b}}, // nguk_, ñarl, _narš, ñabl,
+ {{0xe9da4afe,0xda65413a,0x6143568a,0x7c3e368b}}, // [1180] цка_, عافي, кета, _tepr,
+ {{0x224d88a7,0x3f91629f,0x26c0568c,0xb605e013}}, // _adek_, _brzu_, shio_, _tušč,
+ {{0x73065247,0xf1c3a013,0x6d4d8098,0x6299d1b0}}, // _опоз, nešė_, šaal, _onwo,
+ {{0x44290071,0xdb1ae009,0x753b85f1,0x917a4119}}, // _xfa_, nztá, _bwuz, _cập_,
+ {{0xe3b248b8,0x4e222295,0x2eeeb68d,0x7644568e}}, // _شرح_, _मलाई_, toff_, maiy,
+ {{0x7769c011,0x7bc6006e,0x6d5aeb0d,0x386dd68f}}, // dtex, izku, yuta, efer_,
+ {{0x386dcb28,0x7c248071,0x6aabc22e,0xff2360ba}}, // ffer_, ñirs, _bogf, _амсо,
+ {{0xb05b4277,0x38c8a0d0,0x6d5ae10a,0xf526d0ba}}, // _gläd, _کاری_, vuta, мфон,
+ {{0x6aabc1cd,0x6443adb2,0x3f14e8f3,0x9a12c079}}, // _dogf, bani, ндис, _sọbụ,
+ {{0xb4bd2064,0x7eade06f,0xeb91400b,0xbcfb20e0}}, // _आते_, _súpr, אָט_, rték,
+ {{0x2bd285c2,0x32576095,0x20d207a0,0x867b63c8}}, // _देता, וסים_, ršin_, חריו,
+ {{0x76444058,0x2293c049,0x20d3c0ba,0x7982c0a2}}, // jaiy, _للمس, pţia_, _tsow,
+ {{0x63a28733,0x21200187,0x20d20143,0xdfa6c049}}, // nxon, _cuih_, pšin_, _تحضي,
+ {{0x63a2841a,0x20d3c0ba,0x7cfd0666,0x6d473406}}, // ixon, nţin_, péro, tsja,
+ {{0x48ab403b,0x6127a106,0x2fc9e0a2,0x2d920614}}, // отам_, _kıla, _ƙage_, _erye_,
+ {{0x6a155690,0x44291691,0x260c004b,0x6d48e488}}, // ембу, _ufa_, ठेही_, msda,
+ {{0x6d473692,0x98b970ba,0x60c282a4,0x3d16c08d}}, // [1190] ssja, плат_, dhom, _पूछे_,
+ {{0x2244c6a6,0x29187693,0x6289c098,0x690b20f7}}, // hamk_, _cira_, zdeo, _eżer,
+ {{0x628aa0e2,0x7c298105,0x24890bee,0x31694108}}, // ddfo, _pfer, _kaam_, staz_,
+ {{0x29187694,0xa3c9a077,0x6fd0404b,0x66c06156}}, // _eira_, _लेग_, _ठेवू, _röke,
+ {{0x225820d0,0x24891695,0x3949813a,0x291944ec}}, // _عجیب_, _maam_, éas_, _iisa_,
+ {{0x68e2c031,0xc104a049,0x7cfd0e1c,0x629aa187}}, // _ajod, نوني, dérm, _cnto,
+ {{0x6d4ef696,0x76408030,0x20cd26ca,0x917a4119}}, // ábal, _femy, ržih_, _tập_,
+ {{0x6915c05c,0x76456435,0x7769d697,0xd788c067}}, // _všeo, lahy, ttex, _bể_,
+ {{0x2d98f698,0x6d4ae3e0,0xf70a0119,0x752080ce}}, // åren_, _avfa, _gầy_, _gumz,
+ {{0x6288b699,0x6d5d169a,0xfaa7a056,0x80b9e0c2}}, // _gado, dusa, _ўшан, इसें,
+ {{0x7eb5d69b,0x6f18ea5f,0xc6a7c07a,0x7d0d4106}}, // _lápi, _divc, _орди, kmas,
+ {{0x2ec825e8,0x18a6026f,0x628b9308,0x7ebcc057}}, // रफ्त, хамм, ndgo, _tépa,
+ {{0xf1bf0049,0xd0a8e555,0xeb99c14a,0x20d5e71b}}, // scán_, سطین_, _хил_, _пікс,
+ {{0x6721b69c,0x3075204e,0x6d49c4b9,0x7c29d147}}, // _kulj, кусс, msea, lcer,
+ {{0x91e321bc,0x21200939,0x11d520ff,0x67222361}}, // горе, _wuih_, вітр, _čojs,
+ {{0x6b8401df,0x8fa641d3,0xadd6e0be,0x7641b69d}}, // _tsig, набе, _רוקח_, _lely,
+ {{0x9c7ce05c,0xf389e067,0x442a617b,0x6440969e}}, // [11a0] počt, _hả_, _pfb_, _remi,
+ {{0x76460b5e,0x6f0d569f,0xb4636c84,0xe8f774d2}}, // laky, amac, лкул, елу_,
+ {{0x4429406f,0x395d8171,0x6d5c36a0,0x24890048}}, // bca_, euws_, qura, _yaam_,
+ {{0x2cad80fd,0x53a641c0,0x5c0684e4,0x44f68050}}, // _coed_, _замб, няла, رساز,
+ {{0x7ae396a1,0xb0d7240a,0x291a36a2,0xa2b196a3}}, // énta, _डगमग, _kipa_, _अकर्,
+ {{0x644560eb,0x644e6a7e,0x291956a4,0x7ebf000e}}, // cahi, _udbi, _zisa_, _mëpa,
+ {{0x7ae416a5,0xfce376a6,0x7cf604d2,0x69d82013}}, // _ojit, _соро, nárs, _žvej,
+ {{0x2907ec43,0x6441a10a,0x68e3e04d,0x6f18e143}}, // _ahna_, _eeli, _ajnd, _sivc,
+ {{0x6288b6a7,0x248916a8,0x6f18f1b5,0xceb8e0c2}}, // _tado, _raam_, _pivc, tkę_,
+ {{0x8c3d812a,0xa49b0017,0x7cf6006f,0x6b82808e}}, // _roşi, ològ, kárs, mwog,
+ {{0x6eb4e0c2,0xceb8ea0b,0x49baa0e0,0x7d0d56a9}}, // ेसबु, rkę_, _عائد_, ymas,
+ {{0x8289c0ff,0xdd12020f,0x6d48e732,0x644616aa}}, // _осіб_, rüşl, rsda, gaki,
+ {{0x27260067,0x629c62be,0xe0df0024,0xf09f42d9}}, // _côn_, _onro, rnò_, _bhàn_,
+ {{0x271ea088,0x645cec1e,0x13f4e14a,0x6442c7ad}}, // _ọnya_, órid, _изля, _heoi,
+ {{0x66040d28,0xddc8f6ab,0x27f3e066,0x6d5e6582}}, // _ezik, ledž, čané_, jupa,
+ {{0xd5fbe3c8,0x644616ac,0x68e94012,0x26c00090}}, // _מפור, caki, čedo, _glio_,
+ {{0x6d58b6ad,0x481540d6,0x09d5e07c,0x26d90066}}, // [11b0] hrva, тмас, _সুপা, érou_,
+ {{0x65956fb5,0x26dfc0a9,0x3f9bc03e,0xa3bd666f}}, // казу, znuo_, _áque_, इनल_,
+ {{0xa2b261b0,0x200496ae,0x442956af,0x2d932005}}, // _आकर्, _azmi_, pca_, _urxe_,
+ {{0x6d58a098,0x254ca04a,0x48cf807c,0x6d4aa831}}, // drva, děl_, িক্র, dsfa,
+ {{0xa2a4c5c5,0x7982808b,0x09b5c1a6,0x63bb635f}}, // कान्, gwow, _জেনা, _ƙuns,
+ {{0xf77401a9,0x628ae066,0xb4be04e5,0x2ca94046}}, // רקס_, _nafo, _इत्_, ljad_,
+ {{0xf0942679,0xb8d9a04b,0x6d49c62c,0x09d5e1a6}}, // טנס_, _चव_, wsea, _সুনা,
+ {{0x6d49d6b0,0x59d28e35,0xf1b30029,0x6923a0a9}}, // tsea, _देवर, ुनान, _uđem,
+ {{0x644736b1,0xc692800b,0xb0c94029,0xaa7b0026}}, // kaji, _דאן_, _रतनग, znýc,
+ {{0x7d1b96b2,0x22460274,0x61fb85fc,0x60c4400e}}, // _hius, _žok_, _iyul, zhim,
+ {{0x7afbd6b3,0x6009e052,0x644736b4,0x628d40ae}}, // plut, ьном_, daji, kdao,
+ {{0x656d4644,0xe3b0c0d0,0xe61f4067,0x20d200a9}}, // ktah, _گرم_, _ngô_, pšim_,
+ {{0x6d4b86bf,0x628bc057,0x958303fc,0x41d056b5}}, // nsga, _iago, аляе, _सेंस,
+ {{0x6d5e6d87,0x7cf601a2,0x644736b6,0x6d4b94f2}}, // yupa, társ, gaji, isga,
+ {{0xf09f4016,0x764616b7,0x99804013,0xa06a553b}}, // _chào_, paky, ybių_, мава_,
+ {{0x7643f6b8,0xc952a0be,0x98bf00ba,0x628d4426}}, // _heny, ימל_, rstă_, gdao,
+ {{0x7c24e315,0xceb2c694,0x7523f556,0x61464f22}}, // [11c0] _şiri, בין_, _hunz, теза,
+ {{0xed590326,0x8c3ca121,0x888ce00b,0x1fb663a5}}, // áže_, _yoğu, ּראַ, ксер,
+ {{0x546a83fc,0x387fc854,0x2247aaaa,0x60c456b9}}, // даем_, keur_, dank_, phim,
+ {{0x387fc0fa,0x628e36ba,0x290f80ae,0x248dd6bb}}, // jeur_, ldbo, gmga_, jdem_,
+ {{0x387fd6bc,0xa2b3a567,0x7e7e76bd,0x20cd216d}}, // deur_, _अवश्, pepp, rživ_,
+ {{0x254ca04a,0x248054ee,0x6263a1e3,0xc882c03c}}, // těl_, leim_, авља, mişə_,
+ {{0x6d5996be,0x6df6613a,0x8cb3a39f,0x75240c9d}}, // arwa, _شكرا, _अवरो, _luiz,
+ {{0x78a9c488,0x6b960064,0x4420008a,0x672401f6}}, // gjev, _oryg, _ngi_, _ouij,
+ {{0x6d4ab6bf,0x66d6003b,0x7d1ab6c0,0x8c3ca121}}, // ssfa, eško, _tits, _soğu,
+ {{0xcc12c125,0xddc8f6c1,0x26d8310f,0x7523ebef}}, // _fọnị, redž, jiro_, _bunz,
+ {{0x387fc31d,0x6f1c76c2,0x44200058,0x64440090}}, // beur_, _mirc, _bgi_, _beii,
+ {{0x69c9c022,0x387fc0fa,0x629e2061,0x98bf0013}}, // bzee, ceur_, _anpo, kstą_,
+ {{0x6723ec12,0x59d9c573,0xd130c8b5,0x7643f6c3}}, // _eunj, _बेपर, _امت_, _feny,
+ {{0x9f586057,0x442008fb,0x6360a088,0x67240363}}, // _eyré_, _egi_, _ịnye, _duij,
+ {{0x3ea956c4,0x7648f6c5,0x97252ef8,0xbddb40f9}}, // tjat_, mady, افرو, _adèp,
+ {{0x7648f147,0x442ce011,0x387241cd,0x656d4c04}}, // lady, lcd_, gfyr_, ttah,
+ {{0x7ebccf3c,0x628d428f,0xb7d90938,0x66c06156}}, // [11d0] _répl, rdao, _سودا_, _söka,
+ {{0x539a0095,0xa5bb4698,0x7d0996c6,0x387fc52c}}, // _ניתו, _agóc, _shes, zeur_,
+ {{0x68f536c7,0x7d1c710f,0x442ee944,0xaca36125}}, // mozd, _dirs, _mff_, _ntụb,
+ {{0x7ceda005,0x62a54089,0xf5488125,0xa5c700c4}}, // lúrx, ároč, fụ_, _gróð,
+ {{0x628bd6c8,0xd5a6c0a4,0xee38884f,0x75240005}}, // _rago, _اف_, тні_, _xuiz,
+ {{0x6d4b809a,0xb4b1e021,0x6560c1e7,0xe057a0eb}}, // rsga, _टके_, dumh, ئیات_,
+ {{0x7643e4b4,0xbcfb20e0,0x78a9c00e,0x80ba4010}}, // _reny, ltés, tjev, _एकमे,
+ {{0x61fd4066,0x6723e133,0x6a85ce27,0x395fc052}}, // _kysl, _runj, улиа, tuus_,
+ {{0x7af520e0,0x7cfd0c0b,0x316dc004,0x8c3d803c}}, // kozt, céri, utez_, _qoşu,
+ {{0x60c2d6c9,0xceb4603c,0x7523ef59,0x5274a63b}}, // _elom, qqət_, _punz, _نامز,
+ {{0x395fd6ca,0x752400e6,0x316dd6cb,0xd048e03c}}, // suus_, _suiz, stez_, nidə,
+ {{0x442ef6cc,0x6d4f43ee,0x66d603de,0x291ce057}}, // _eff_, _ovca, rško, _eiva_,
+ {{0xfc470359,0xe918c0ff,0x7e9b6041,0x6724048a}}, // ñías_, _році_, _kāpē, _quij,
+ {{0x291dc0f2,0x6b960aaa,0x7db5e938,0x290b4030}}, // _hiwa_, _vryg, _مصبا, _ahca_,
+ {{0x291dd6cd,0xd5e40079,0x7ae3b6ce,0x3f8916cf}}, // _kiwa_, _baị, innt, _esau_,
+ {{0x44200958,0x290a60dd,0xa7d600a4,0x26d836d0}}, // _ugi_, _shba_, _متخص, siro_,
+ {{0x20d2029c,0x03a3464d,0x2bd9d6d1,0x7e6456d2}}, // [11e0] vših_, бито, _बेमा, rgip,
+ {{0x6280d6d3,0xdd044315,0x7f3b0095,0x291ce0d5}}, // zemo, ırır, _רעיו, _xiva_,
+ {{0x68e28b83,0x3abae00b,0x79898434,0x7cd20018}}, // tnod, עמענ, _asew, dārz,
+ {{0x68468677,0x442dd6d4,0x2bd54010,0x136a8084}}, // унда, jce_, _ठेवा, ешки_,
+ {{0x99dd806f,0x60c40167,0x6e3bc20d,0x60d8a157}}, // _peňa, _mlim, dbub, tivm,
+ {{0x44224299,0x64465375,0x2b4dc098,0x3ce6c065}}, // _hgk_, _heki, dsec_, _sjov_,
+ {{0x291cf6d5,0x3cffc05d,0xf1a7e049,0x68e455c7}}, // _riva_, fluv_, _اء_, onid,
+ {{0x26da6296,0x442240c7,0x37e8c07c,0x6f1e2064}}, // lipo_, _jgk_, _পরবর, _lipc,
+ {{0x6280d6d6,0x6d5c2105,0x7cfec00e,0x8af065fc}}, // remo, hrra, nëri, hrəm,
+ {{0x628e76d7,0x386681cd,0x6d4e268f,0xb05b0156}}, // _jabo, lgor_, ksba, rhäm,
+ {{0x26d90733,0x2018e1dd,0xb9098088,0x6127a0b0}}, // tiso_, äri_, _nchọ_, _yılm,
+ {{0x628e6e96,0x7c3bc315,0x2bef407c,0x291dd6d8}}, // _labo, cbur, চেয়ে_, _giwa_,
+ {{0x7526405d,0x20cd2098,0x442ef6d9,0xcc12c125}}, // _nukz, džir_, _tff_, _kọmị,
+ {{0x6234f386,0x26d900bb,0xf1c2007f,0x59d620f1}}, // _теку, siso_, dišč_, _डेहर,
+ {{0xbcfb207b,0x26d901b6,0x644528cd,0x7ceda03e}}, // guéa, piso_, _vehi, rúrx,
+ {{0x644aaf2c,0x628e76da,0x7ae456db,0x59c488a2}}, // hafi, _aabo, gnit, वनार,
+ {{0x60f96052,0x3f856057,0x1958465d,0xa15842d5}}, // [11f0] вная_, _áluz_, латы_, лату_,
+ {{0x25a52277,0x21294058,0x7c2d56dc,0x7c3bd6dd}}, // älls_, rqah_, scar, zbur,
+ {{0x711a2557,0x6d4f02a5,0x60c40052,0x6f0d0119}}, // _שותפ, lsca, _ylim, _khac,
+ {{0x644656de,0x69c1610a,0x4fd62095,0x224b0156}}, // _geki, _ülem, _כותב_, nack_,
+ {{0x291eb6df,0xd04aa03c,0x6f0d0256,0x644aa1df}}, // _bita_, lifə, _mhac, fafi,
+ {{0xf8bf11b2,0x6b89806e,0x0555a14a,0xd6d9c0c2}}, // hké_, _vseg, атия, wił_,
+ {{0x645560c2,0xd498614a,0xa3d4e077,0x30c9a009}}, // _idzi, _сря_, _सेल_, _یورپ_,
+ {{0x52d12e74,0x248d8071,0x26c94c1e,0x272ab6e0}}, // सफुस, _uaem_, nhao_, _dùn_,
+ {{0x7e9b6041,0x20d3c1fc,0x2019c04e,0x256f4ed8}}, // _tāpē, nţii_, äsi_, nül_,
+ {{0x27ff96e1,0x681bc041,0x7f5bc676,0x6561f6e2}}, // _iyun_, _rādī, rruq, tulh,
+ {{0x60c416e3,0x68e9814f,0x7c3bc77c,0x38bb000e}}, // _plim, _kjed, pbur, _zëri_,
+ {{0x95d9c052,0x6d5bc00e,0xcebae1df,0xef1701fc}}, // _идет_, prua, _riƙe_, имэ_,
+ {{0x645576e4,0x3eadc00e,0x7646477b,0x3ceb0066}}, // _odzi, mjet_, _reky, _živé_,
+ {{0xed5a2f2d,0x6d4e2305,0x59b565aa,0xaca46291}}, // ког_, tsba, ंहार, _kwụs,
+ {{0x272b8042,0x6d5c36e5,0x3f9941df,0xe1ff4031}}, // _løn_, urra, _arsu_, _ayó_,
+ {{0x60c409fa,0x7d0d16e6,0x2247eb83,0xc952e095}}, // _ulim, _ghas, _henk_, _ומה_,
+ {{0xc2466f27,0x9c134088,0x6d4e36e7,0x644ab6e8}}, // [1200] инак, _fọma, ssba, yafi,
+ {{0x63a9d6e9,0x68e456ea,0x68fb96eb,0x628e6025}}, // ixen, snid, _akud, _qabo,
+ {{0x644aa03c,0x35a62517,0x7d01e569,0x7e26816c}}, // vafi, _танг, ills, адеж,
+ {{0xe73a96ec,0x27ff8c12,0x63bbd6ed,0x7bcd405f}}, // вед_, _byun_, kyun, bzau,
+ {{0xddd8a026,0x38669598,0xf194809e,0x290d8f1f}}, // devš, rgor_, силь, _dhea_,
+ {{0xd90da062,0x60f7a84f,0xd8436066,0x3f9a20a9}}, // فیق_, анія_, ručí_, _krpu_,
+ {{0x27ff81e9,0x27e0c0ba,0x290d8090,0x68e9822e}}, // _eyun_, _ţin_, _fhea_, _fjed,
+ {{0x3ebfd6ee,0x7eb840e0,0xb5fb4273,0x290d8037}}, // gkut_, _típu, _abáy, _ghea_,
+ {{0x25a0c6be,0x3cf822cf,0xa8054071,0x7d028a13}}, // _šilb_, korv_, muñé, mlos,
+ {{0x6233a1e3,0x5976a20e,0x9873e4e4,0x6e240c3f}}, // _међу, _выду, ольц, _agib,
+ {{0xfd532125,0x2ef82105,0x2163e07e,0x6d60a4d2}}, // _dofọ, dorf_, отяг, ývač,
+ {{0xbcfb2057,0xfaa3d138,0xe8d96088,0xd7fac0ba}}, // guén, _махо, trị_, _иул_,
+ {{0xf8bf16ef,0xa3dd76f0,0x7648b675,0x20d2006f}}, // rké_, _तेन_, _kedy, jšiu_,
+ {{0x2484d6f1,0x7b1060cb,0x7d0d02a8,0x4b7bc053}}, // lemm_, läuf, _whas, _סאוו,
+ {{0xd437e00b,0x78ad56f2,0x76476163,0x6f0296f3}}, // _דרוק_, tjav, _rejy, kloc,
+ {{0x30a4401b,0x7cfd0009,0x6283a7b3,0xbcfb22a8}}, // орув, gért, zeno, cuén,
+ {{0x2f0be22e,0x6728a320,0xaa7b0066,0x644b96f4}}, // [1210] søge_, _ludj, nným, vagi,
+ {{0x2d9941e4,0x20d3c12a,0x7bce2361,0xa7fce106}}, // _vrse_, rţii_, bzbu, ygıl,
+ {{0xb995213a,0xa49b42d9,0xe3b00555,0x21276119}}, // _الجب, _bhòt, _فری_, ênh_,
+ {{0x78a2c071,0x8f9a2076,0x20d3c0ba,0x7cfd0009}}, // _nnov, _עירי, pţii_, mérs,
+ {{0x68e980dd,0xa2836050,0xcc12c088,0x3a248057}}, // _vjed, _ویرو, _dọkị, _dgmp_,
+ {{0x6728b6f5,0xb4d5e028,0x7528b6f6,0x141a43c8}}, // _budj, _सगे_, _budz, _תושב,
+ {{0xdddc36f7,0xd6582095,0x7c2d81af,0x2247e106}}, // zdrž, ביות_, ñars, _renk_,
+ {{0x68fb8174,0xb05b4156,0x15b8a1d3,0xe576a02d}}, // _ukud, _oläm, рышы_, ёзы_,
+ {{0x28a9c4e6,0x395eeb6c,0x7ebda187,0x3f8940fd}}, // काबि, erts_, _lèpi, nwau_,
+ {{0x26c5a0e8,0x21200037,0x7528a04d,0x2cbfd6f8}}, // _ullo_, _siih_, _fudz, skud_,
+ {{0x2cb8e156,0xdced604d,0x5f95a009,0x6728b6f9}}, // örda_, jtač, _دلائ, _gudj,
+ {{0x60dd16fa,0x7c84c739,0x20cac090,0x6448b6fb}}, // gism, _муче, _cùid_, _zedi,
+ {{0xfe70c25b,0x395ee3c5,0x628576fc,0xd36ee050}}, // _آدم_, arts_, keho, وهی_,
+ {{0x232702dd,0xa2d3d6fd,0x3a3fc037,0x69ce20cb}}, // сори_, बोर्, lbup_, tzbe,
+ {{0x0135e01e,0x68fd4037,0x7196e050,0x64a64fdb}}, // _اعتد, _bksd, _هنوز_, бава,
+ {{0xf8bf603e,0x65656037,0x7e7b8079,0x60c76012}}, // _quén_, duhh, _nbup, _aljm,
+ {{0x6aa416fe,0x1d0740de,0x3fe2807c,0x93bce0ba}}, // [1220] _inif, сети_, _বর্ষ, scăt,
+ {{0xbcfb2004,0xb466260f,0x8e862049,0x386943f6}}, // trée, _укол, _الده, agar_,
+ {{0x64498105,0xfaff200e,0x0eba8099,0x7ce8020f}}, // _beei, rtët_, луды_, yırl,
+ {{0x91e6439e,0x6efd16ff,0xcebae1df,0x232a9700}}, // _гоме, lébr, _miƙa_, годи_,
+ {{0x7ebcc0e0,0xa3d9e960,0x9dba420e,0x2365e00a}}, // _képv, _ठेस_, _сыну_, hulj_,
+ {{0x954a6ca7,0x60dd1701,0x8fa67702,0x386d8058}}, // _مشرق_, zism, _лаге, _ecer_,
+ {{0x60dd1703,0xed3561d3,0x80a4c914,0xb05b1704}}, // yism, _мэтэ, काले, shäi,
+ {{0xa5bb4064,0xccf9c0c2,0xaa7b04d2,0x7e69ce36}}, // _ogól, goś_, tným, ggep,
+ {{0x7d045705,0x39aee03c,0xc6934095,0xf7438471}}, // ilis, məsi_, לאה_, _нето,
+ {{0x60dd0037,0xbcfb2057,0x443ee0cb,0xaa7b0089}}, // wism, guél, ubt_, rným,
+ {{0xaa7b0089,0x57b31706,0x8af065fc,0x60c29707}}, // sným, ुन्ह, msəd, okom,
+ {{0x635c60ba,0x48ab0731,0xbc1b6053,0xb87b0431}}, // mănă, утом_, טויש, mník,
+ {{0x9e4b21fc,0x6d5a203a,0x68e381af,0x02fa7708}}, // ţămâ, átan, éndi, ्ताह_,
+ {{0x7cfd0066,0x2904d709,0x7d04570a,0x38bc404a}}, // térs, llma_, elis, _míru_,
+ {{0xd6d836a6,0x7d04570b,0x91fce018,0x6285770c}}, // стя_, flis, zgāj, veho,
+ {{0x21291441,0x6602c00d,0x6b8d170d,0x6286170e}}, // _tuah_, _kyok, _tsag, ceko,
+ {{0x9ed9768a,0xb8d14029,0x63a9104e,0x7e7c6037}}, // [1230] амат_, _टच_, åend, _bbrp,
+ {{0x59a8b244,0xa49b42d9,0xfaff23ac,0x80b881a6}}, // कमार, _chòr, rtës_, _আগষ্,
+ {{0x65656037,0x6d34202d,0x986540e0,0x39aee5fc}}, // uuhh, _неэф, _ویسے_, fəsi_,
+ {{0x7afaf70f,0x46a72292,0x6721a59c,0x2d9ce012}}, // hott, खावह, _silj, _mrve_,
+ {{0x68ed0167,0xdbe34057,0x62857710,0x68f98d8d}}, // _mjad, _téño, peho, rowd,
+ {{0x26def711,0x90c5a09e,0x644f1712,0xdce46143}}, // jito_, юбле, haci, vtić,
+ {{0x39aee03c,0xa3df2466,0x3ec48009,0x5fe00077}}, // bəsi_, _देम_, ártó_, _पेनल,
+ {{0x212b52dd,0xf8c94ba0,0x66c4e030,0x6b9b9713}}, // _kuch_, रसिय, _bòko, _trug,
+ {{0x752ae105,0x2d98e271,0xb05b00cb,0x7522c534}}, // _aufz, æren_, nhäu, _dioz,
+ {{0x4425a579,0xdb1c400e,0xb80de8c6,0x26c91280}}, // _ugl_, tyrë, _हरदम_, _alao_,
+ {{0xbef70010,0x2487a0d1,0x3f9ce012,0xe9df4019}}, // ीतून_, menm_, _crvu_, _seúl_,
+ {{0x224e2143,0x673bc052,0x6915c089,0x6458f714}}, // _žfk_, ppuj, _všes, _edvi,
+ {{0x2365e04d,0x232a0a0c,0xb05b404e,0x3495e45c}}, // sulj_, роги_, _eläk, _мадр,
+ {{0xa3158010,0x7e7d4105,0x7ebcc0fa,0x272f0106}}, // तगुज_, _absp, _dépu, _dün_,
+ {{0x7cfec0dd,0x24921715,0xdd1c81ce,0x60db9716}}, // përt, _taym_, vážn, _omum,
+ {{0xb6a343ca,0x764bc0e0,0x78a523d1,0x6458e066}}, // мисл, _jegy, _enhv, _zdvi,
+ {{0x644bd717,0x29120899,0x8b2640ff,0xcb6a4323}}, // [1240] _megi, _ihya_, ідже, разе_,
+ {{0x6723f718,0x2905f719,0x7c656049,0x7ae9c5cf}}, // _jinj, nlla_, كامل, hnet,
+ {{0x39aee03c,0x60c9971a,0x25bfc17b,0xdd1c8066}}, // təsi_, _blem, hyul_, rážn,
+ {{0x7afbd629,0x3160571b,0x4427e037,0x764bd71c}}, // jout, priz_, _lgn_, _negy,
+ {{0x0caaa3a5,0x3f86602e,0x2d8f422e,0xddc880c2}}, // итки_, ňou_, æget_, _żołn,
+ {{0x60db8291,0x179b0087,0xe0df0024,0x25bfcbae}}, // _emum, יינב, ziò_, dyul_,
+ {{0x2912007d,0x60c292d6,0xb87b004a,0x68eda049}}, // _ohya_, pkom, bníh, éada,
+ {{0x68e9caca,0x68fbc968,0xa0678c4e,0x320ca4d2}}, // gned, goud, _маса_, _vzdy_,
+ {{0x2002c069,0x2cb8610a,0x2db7400b,0x3076a052}}, // æki_, _kord_, ַלטן_, _мужс,
+ {{0x68fc371d,0x60c4571e,0x7c3b62d1,0x6457c020}}, // jord, hkim, _đurk, _ɗaiɗ,
+ {{0xa2a7a6a8,0xa2b9a37f,0x6d5600ca,0x764bc009}}, // चार्, ्सर्, _avya, _fegy,
+ {{0x644bd71f,0x8afd00c2,0x7afaf720,0x4427f33c}}, // _gegi, dręc, sott, _egn_,
+ {{0x7c290012,0xae16c982,0x68fc2b28,0x99982013}}, // žero, देशन_, ford, karų_,
+ {{0x70dac0c5,0xd5b800db,0x6b82c425,0x67240194}}, // _बगुल, іся_, _ipog, _eiij,
+ {{0x4394252f,0x28bf4738,0xbcfb3721,0x6d586143}}, // марс, _एकदि, quém, švaj,
+ {{0x6568f722,0x629641e7,0x68e0ca88,0x6723f09d}}, // mudh, gdyo, nimd, _zinj,
+ {{0x62873723,0x6b82c1f6,0x3ce95724,0x6723f725}}, // [1250] pejo, _jpog, rnav_, _yinj,
+ {{0x7afbc52c,0x394905c8,0x9c7cf5b3,0x6562815b}}, // zout, _iwas_, biče, droh,
+ {{0x3f824012,0xf09f4153,0x3ced8320,0x602de009}}, // _epku_, _bhàt_, _pjev_, _hőmé,
+ {{0x397be00b,0x7bc0c013,0x672d1726,0x6ab8e271}}, // שטאנ, kymu, _kuaj, _lovf,
+ {{0xa3cb010a,0xdc3f4326,0x644d0049,0x28a9c0a8}}, // लनि_, níčk, _meai, कासि,
+ {{0x6b8d5665,0x23694022,0x3ea6c013,0x3eb9491f}}, // mwag, muaj_, _anot_, _host_,
+ {{0xf2d2600b,0x387c2156,0x672bc00e,0x23694022}}, // _װען_, _övre_, _sugj, luaj_,
+ {{0x6603f727,0x644d0a98,0x65776022,0x24895728}}, // _rynk, _neai, mtxh, neam_,
+ {{0x6603f729,0x4427e07d,0x66e5e491,0x764bc0e0}}, // _synk, _pgn_, зопа, _vegy,
+ {{0x68e9d5cc,0x69c5e10a,0x644bd72a,0x68fbd3dc}}, // sned, _ühek, _wegi, soud,
+ {{0x39490a81,0x01bd807c,0x3eb94200,0x7641f72b}}, // _awas_, _অধিদ, _oost_, rbly,
+ {{0x644d1728,0xef0ec0ff,0x81e921a6,0x6723e0b1}}, // _ceai, _їм_, _বরং_, _winj,
+ {{0x672d00e6,0x59bd85c5,0x20040069,0x7643a022}}, // _cuaj, ्निर, æmi_, obny,
+ {{0x9c7ce49f,0x3a29172c,0x224d8171,0x2012a03c}}, // tiče, _egap_, _leek_, əyib_,
+ {{0x505a41ba,0x6d5520e0,0x8fa32471,0x65776048}}, // йшая_, gsza, наре, jtxh,
+ {{0x55bae095,0x2731a0c4,0xa3ab8026,0x3eb94e96}}, // _למצו, _mán_, गमा_, _cost_,
+ {{0x2ee04054,0x62960c57,0xab664866,0xdce3c299}}, // [1260] riif_, _iayo, явал, junč,
+ {{0xdce3c490,0x7afc2025,0x26c5e013,0x80aa0acf}}, // dunč, qort, nklo_, टावे,
+ {{0xdebb2095,0xe9f9e067,0x224d82be,0x673b8796}}, // _המיל, _đẻ_, _beek_, _atuj,
+ {{0x6562972d,0x24895728,0xeb9f4271,0x186a972e}}, // troh, ceam_, _frø_, сади_,
+ {{0x200e209f,0x3eb8670b,0x765b8614,0x6e3b6106}}, // šlić_, _tort_, _dduy, _şube,
+ {{0x3eab617a,0x6b841682,0xcebae0a2,0x80d84077}}, // ċità_, _mpig, _riƙo_, नोरे,
+ {{0x7afd172f,0xf65f022e,0xe29a8474,0x753b8614}}, // yost, mhæv_, _лав_, _etuz,
+ {{0x62960abc,0x2731b730,0x3f9eb731,0x66cb203c}}, // _nayo, _dán_, _vrtu_, _müka,
+ {{0x7ae29732,0xb5fb4057,0xa3d4f733,0x68e0c181}}, // miot, _edáf, _सेज_, rimd,
+ {{0x672d00dd,0x68e1f734,0x4734f138,0x02c9439f}}, // _ruaj, gild, мнос, _रत्न,
+ {{0x644e6291,0x6aa8aa2f,0x78a8a86b,0x660645f0}}, // _mebi, _indf, _indv, _lykk,
+ {{0x7ae29735,0x44386194,0x6726407f,0x2347c0eb}}, // niot, _lfr_, _nikj, _کلوپ_,
+ {{0x98a780e4,0x2b40a23d,0x28a9c53a,0x8c43cff0}}, // šići_, ćicu_, कारि, _чече,
+ {{0x442901e2,0x6298ad53,0x645c600e,0x628aa09a}}, // _wga_, ndvo, _ndri, nefo,
+ {{0x6c7be095,0xe7842956,0x200520c4,0x4ddc6466}}, // _האופ, нуто, æli_, _गेलै_,
+ {{0x672d00dd,0x6606405d,0x96d840c2,0x2bde4857}}, // _tuaj, _bykk, नोलॉ, _नेहा,
+ {{0xaca34125,0x24895736,0x645c7737,0x60cd1738}}, // [1270] _nsụd, ream_, _bdri, _ilam,
+ {{0x644e7739,0x290254b7,0xfaff0155,0xb05b00cb}}, // _cebi, _ekka_, loë_, chär,
+ {{0x2907a066,0x6295773a,0xdb0e8089,0x645c773b}}, // vlna_, _vazo, ždýc, _ddri,
+ {{0x2ee32200,0x7ae1e0d1,0xec6b45a7,0x6d56417b}}, // lijf_, zilt, _урок_, bsya,
+ {{0xc984573c,0x68e1e5fc,0x7e878013,0x644e71ef}}, // _пури, yild, _įspė, _febi,
+ {{0xc332400b,0x4444658f,0x2eba0158,0x2557a0ba}}, // _בוך_, cb_, _उक्त, tăl_,
+ {{0x386dc703,0xb05b0052,0x7ae1f73d,0xd164614a}}, // tger_, tkäl, vilt, _пъти,
+ {{0x7c29973e,0x7f59c90f,0x386dd73f,0x68e1eaaa}}, // _uger, ојот_, uger_, wild,
+ {{0x7aed4852,0xcd0280c2,0xa227a050,0x2f188052}}, // inat, _dość_, _فراه, _хоть_,
+ {{0x78bab740,0xf65f4065,0x5c74a583,0x67276b33}}, // _gotv, _blæk_, _плут, _jijj,
+ {{0x7aed552c,0x693c20a9,0x644562a1,0x208a1741}}, // knat, _ičeg, ibhi, ойни_,
+ {{0x2f146292,0xd00a001f,0xabfaa076,0x672774b7}}, // mäge_, жеме_, _והער, _lijj,
+ {{0x69c3b742,0x3ea7e098,0x2f1473a2,0xdce20018}}, // lyne, _tnnt_, läge_, gulā,
+ {{0x69080005,0x62961743,0x38732187,0x645d4e39}}, // iñei, _wayo, _ccxr_, _adsi,
+ {{0x1d07264d,0x46f601c7,0xe7372255,0x60cd0db2}}, // фери_, _очит, дес_, _flam,
+ {{0x6d564187,0x44387744,0x65645745,0x69d9410a}}, // tsya, _sfr_, trih, _नइखी,
+ {{0x628b9746,0x7d040a88,0x7bc3a261,0x443861f6}}, // [1280] dego, _ikis, hynu, _pfr_,
+ {{0x3cfa6064,0x6d920927,0xa3bfa48b,0x39405747}}, // ्तें_, ržać, ुना_, rpis_,
+ {{0x644e6105,0xb4fae00b,0xb87b1284,0x628aa46b}}, // _vebi, קלער, rnív, vefo,
+ {{0x6e2d806e,0x656b8024,0x644f4a88,0x443a2012}}, // žabn, fugh, _geci, _hfp_,
+ {{0xe8faa87e,0x6d564058,0x6298a16d,0x764e7748}}, // олд_, qsya, tdvo, _teby,
+ {{0xdca3a471,0x29004108,0x64a3a1b1,0x6281a090}}, // качи, loia_, кача, _cblo,
+ {{0x6abaa143,0xf8d244e6,0x6d59c030,0x28b1eba0}}, // _votf, _सताय, _avwa, जानि,
+ {{0x78a98480,0x44383749,0x23c9c04b,0x2360a07f}}, // _gnev, hcr_, िनंद, čij_,
+ {{0xadba8049,0x225d40c2,0x2007ed5d,0x69d68026}}, // _بهذا_, ówki_, _ayni_, _मेची,
+ {{0x673e2579,0x2246974a,0x2900415a,0xc17ae14f}}, // _otpj, lbok_, hoia_, оїм_,
+ {{0x7ae4574b,0x68ed574c,0x29004534,0xda6fd741}}, // kiit, ynad, koia_, _ия_,
+ {{0xbfc6c1e1,0x6297374d,0x6d58ab48,0x7d02c167}}, // _обек, _saxo, msva, _ukos,
+ {{0xb33b401f,0x351bc00b,0x443941e2,0xc178e03b}}, // _orça, _וואנ, _rfs_, stė_,
+ {{0x7c84249a,0xbcfb2057,0x3cfa60c2,0x3166974e}}, // кусе, ntéx, ्तों_, iroz_,
+ {{0x442b574f,0x45d5211f,0x2ee322be,0x67d5214f}}, // _pgc_, ховс, rijf_, хову,
+ {{0x23668143,0x66d02262,0x68ed5750,0x6608a187}}, // kroj_, _räke, unad, _mydk,
+ {{0x6282c1b6,0x6298f751,0x02df40c5,0xb5fb0009}}, // [1290] _mboo, _kavo, _नगीन, rgás,
+ {{0x6728a00a,0x2904958c,0x6d58b752,0x442b4071}}, // _nidj, _akma_, ksva, _wgc_,
+ {{0x644f5753,0x7d09d754,0x68e45755,0x78a9806e}}, // _teci, yles, biid, _pnev,
+ {{0x20090b39,0x21290037,0x645d4065,0x7d04005d}}, // _kyai_, _jiah_, _udsi, _xkis,
+ {{0x7d09d756,0x2129005d,0x7d00d757,0x2d4d01ce}}, // vles, _miah_, doms, nže_,
+ {{0x656b9758,0xe0576555,0x6d41e2f7,0xe802c148}}, // rugh, _آیات_, ypla, _लड़ा_,
+ {{0x6d58b02d,0x98a4b044,0x20c9e3f6,0x20116614}}, // gsva, _sumă_, _dúil_, _uzzi_,
+ {{0x236680e4,0x1fb5c470,0x64553759,0x26c94012}}, // broj_, есор, hazi, nkao_,
+ {{0xf770833e,0x320901e7,0x7bcea0cb,0x6d4d0291}}, // _نال_, _nyay_, _übun, _nwaa,
+ {{0x6298e65f,0xb881e066,0x6f09d75a,0x68e3b75b}}, // _cavo, _číse, slec, qind,
+ {{0x656d575c,0x6d598d53,0xa3df2046,0xdcfba0a9}}, // huah, lswa, _देल_, _usuđ,
+ {{0xdb00a733,0x6d4d0035,0xcdda6053,0x5fd8a04b}}, // _armè, _bwaa, פֿיר, _भेटल,
+ {{0x61fb2121,0x2be007af,0x2bc827d9,0x29016046}}, // şull, _पेशा, रन्थ, koha_,
+ {{0x2bb6aba0,0x765520c2,0x7529975d,0x6d422143}}, // _अपमा, gazy, _liez, _čoab,
+ {{0x6f01e06e,0x7d16175e,0x2d94e056,0x38c8463b}}, // molc, _thys, _арыс, واری_,
+ {{0x6d59975f,0xba23202d,0x3f87e037,0xe814e029}}, // kswa, удск, _ipnu_, _डरता_,
+ {{0x66099760,0xe046682b,0x7ae444c4,0x68e452e7}}, // [12a0] _nyek, ензи, riit, riid,
+ {{0x442d9761,0x60c9d762,0x26d240b1,0x6306668c}}, // _age_, hkem, shyo_, روال,
+ {{0x22520561,0xdcffc04a,0x7bc6087c,0xb4cc208d}}, // _keyk_, ívěs, lyku, लसी_,
+ {{0x66c600c4,0xddc0804a,0x660994b7,0x20cac2d9}}, // _bóki, _zemř, _byek, _cùil_,
+ {{0x19948936,0x64565763,0x6da381c7,0x78bc6ffe}}, // таля, mayi, лица, _torv,
+ {{0x765641df,0x7ae61764,0x657bc0ae,0x2360807f}}, // layy, hikt, dtuh, šijo_,
+ {{0x9c839520,0x442d8057,0x7529805d,0x6f01f21e}}, // ščar, _fge_, _fiez, dolc,
+ {{0x42538ca7,0x2b4007a0,0xe297841b,0x7ae1a1f6}}, // _منفر, _otic_, _пас_, _emlt,
+ {{0x6d4451e1,0xe3b2c243,0x68e605fc,0x6f00d765}}, // npia, ارع_, dikd, romc,
+ {{0x442ca07d,0x6e2d1766,0xddc986be,0xdbd6a052}}, // _ugd_, _sgab, _uceš, _pääo,
+ {{0x28f908e9,0xa803203c,0x62840090,0x7b05c057}}, // день_, _çıxm, _bbio, góus,
+ {{0x20c0f03c,0xbcfb23bb,0x212a63f6,0x93f48321}}, // _còir_, rrém, _libh_, _अशाच_,
+ {{0xdb00a004,0x68e2c00f,0x64565767,0x212b8171}}, // _armé, _imod, dayi, _écht_,
+ {{0x2f97c095,0xab64a669,0x629aa265,0x212a6c8a}}, // _נכון_, _grüß, _jato, _nibh_,
+ {{0x7ae60d87,0x7d02859f,0x2f120009,0x25a04012}}, // bikt, koos, sága_, evil_,
+ {{0x6d4d1308,0x7c25e066,0x442ee187,0x212a658f}}, // _twaa, _úhra, _hgf_, _aibh_,
+ {{0x6d5c7768,0x7c84004e,0xc2996905,0x2d9e2249}}, // [12b0] _avra, _суще, мках_, åter_,
+ {{0x3a2d8057,0x628400c2,0x656d405d,0x765641e7}}, // _sgep_, _zbio, ruah, aayy,
+ {{0x2d877769,0xf653e053,0x2901776a,0x6910659f}}, // ïne_, רצע_, roha_, mäel,
+ {{0x2905a066,0x394d808b,0x59b0c5cc,0x68e722be}}, // _skla_, _pwes_, जमार, lijd,
+ {{0x2249576b,0x6d59976c,0x2cbea17b,0x6d40805d}}, // mbak_, uswa, _botd_, _ctma,
+ {{0x7529976d,0xda62a54a,0x68e7376e,0x2249462c}}, // _wiez, авши, nijd, lbak_,
+ {{0x2bd9ccb5,0x2902176f,0x8aa4614a,0x973ca012}}, // _बेजा, yoka_, _сряд, _prća,
+ {{0x6564800a,0x22495770,0xddcd017a,0xd05525fc}}, // šiha, nbak_, _bbaż, rizə,
+ {{0x629ab771,0xa283a050,0x629d01cd,0x248dd772}}, // _fato, _میشو, ddso, reem_,
+ {{0x2369429f,0x2579c0d1,0xc3c8c13a,0x63a41773}}, // iraj_, rèl_, عظيم_, _rrin,
+ {{0xdd91a5d1,0x186a10da,0x3a75c8f3,0x8602a050}}, // _خوا_, мани_, _алер, _پژوه,
+ {{0x6d41a1f6,0x7ae6e187,0x628f048c,0x628520cb}}, // _itla, éktr, geco, _abho,
+ {{0x7d0d5774,0x2d8c217a,0x628e2aa8,0xda0df775}}, // dlas, ħdem_, tebo, _हरकत_,
+ {{0xa3dac7af,0x2369405d,0x200c0018,0xdce3c0ba}}, // _डेट_, draj_, ādi_, bună,
+ {{0x29194187,0xb4bfe98a,0xfaff23ac,0x2fc7a1e7}}, // _bhsa_, ीसी_, prën_, nyng_,
+ {{0x753ca0e0,0x7649c9a9,0x7d03b776,0xdb02e187}}, // _érze, nbey, hons, _aroè,
+ {{0x7ae72143,0x68e41777,0xb4d2527e,0x1d0742ab}}, // [12c0] bijt, _imid, _वती_, тети_,
+ {{0x29033778,0x7d0d4046,0x6d445779,0x2fc68022}}, // coja_, alas, rpia, vyog_,
+ {{0x6d444556,0x2cbf8155,0xc00fe119,0x6d456119}}, // spia, _boud_, _đuối_, gpha,
+ {{0x25ad406e,0xd94688d4,0x0ea949df,0x0b8a8084}}, // _šele_, веди, _चोपड, дски_,
+ {{0x6bd6801e,0x9393813a,0xa3c20046,0x6d45728c}}, // ختار, لجوا, ंहि_, apha,
+ {{0x25a480ae,0xf8bf6067,0x7d076187,0xe283a926}}, // _vrml_, _quét_, _gkjs, илчи,
+ {{0x2fc6805f,0xdb1d2009,0xd91ac052,0xfce3695a}}, // syog_, nysá, мьи_, _торо,
+ {{0xa3c21706,0x6d5af77a,0x69c36052,0x23768711}}, // ंहा_, vsta, änee, _لائح,
+ {{0x57cae843,0x09cae437,0xbcfb2066,0x6282977b}}, // ान्ह, ान्य, bréh, mfoo,
+ {{0x9346a834,0x3ebf8171,0x26174010,0x7d0d577c}}, // _анже, _zout_, पेटी_, zlas,
+ {{0xddc8a064,0x7d0d55c3,0x290dca03,0x6f0d577d}}, // _wedł, ylas, blea_, ylac,
+ {{0x290dd77e,0x7769c03e,0x61faa071,0x77cacfb9}}, // clea_, brex, _ixtl, _олег_,
+ {{0x629d09ee,0x291a258f,0xc69381a1,0x656f0071}}, // rdso, _bhpa_, באר_, uuch,
+ {{0x9abca227,0x59b6b77f,0xd6d0e3f1,0xaa642423}}, // _biċi, _अपार, _دقت_, итск,
+ {{0x3cff5780,0xa9c4265d,0xe80f0292,0x03c422a3}}, // लवले_, астк, _सरका_, астм,
+ {{0x656aa049,0x48bd807c,0xbcfb3781,0x6f03b782}}, // irfh, _আগ্র, guér, zonc,
+ {{0xe7bd807c,0x20c2013a,0x5c0741e1,0x7658b2ec}}, // [12d0] _অধ্য, _tóir_, тява, kavy,
+ {{0x316955bf,0x68e8e11d,0x22494b83,0x6e456049}}, // rraz_, nidd, sbak_, انضم,
+ {{0x69dae186,0x60c09783,0x6e946450,0x44276052}}, // nzte, _bomm, риху, än_,
+ {{0x26c00057,0xdea401df,0x63a64012,0xf8bf6071}}, // _xoio_, _bunƙ, _frkn, _pués_,
+ {{0xa3ad49de,0x6281f784,0xdea400a2,0x6d41ab6d}}, // _कपट_, yflo, _cunƙ, _stla,
+ {{0xf7736043,0x3ebf80d1,0x660d02ad,0x3ead869b}}, // _پاس_, _wout_, _myak, _wnet_,
+ {{0x6d42ca03,0xf52787ab,0x81e6a1a6,0x443eb785}}, // _atoa, _афан, _যুব_, _dft_,
+ {{0xe3a6a050,0x6d5f1631,0x2cad81cd,0x39468022}}, // اشوی, _ovqa, _uned_, bpos_,
+ {{0x629c733d,0x7658a167,0xdd94853f,0x60cd5786}}, // _yaro, bavy, _каты, ikam,
+ {{0xb4bfe046,0x6acc0077,0x25a6c030,0xbcfb3787}}, // ीसे_, ास्र, _drol_, bréi,
+ {{0x6d42cb8a,0x64557788,0x4b2600e7,0x03260143}}, // _etoa, _mezi, лмов, лдон,
+ {{0x6146168a,0x60c1b71d,0x25a6c057,0x673563ee}}, // лема, _holm, _frol_, _muzj,
+ {{0x20f89789,0x7d04578a,0x273980d1,0x958301ba}}, // nčia_, vois, _jèn_, бляе,
+ {{0x320c2064,0x6459978b,0xe689e049,0x5a47e362}}, // żdym_, hawi, _أنثى_, _рэда,
+ {{0x63a64098,0x6b6320ff,0x629c73c9,0x660d0bd4}}, // _srkn, йкра, _raro, _eyak,
+ {{0x7b106105,0x6d5c378c,0x6abbc1fd,0x629c65d1}}, // räum, rsra, njuf, _saro,
+ {{0xb928c291,0x21674878,0x69dbc0e6,0x69c9d78d}}, // [12e0] _akwụ_, лити_, nzue, nyee,
+ {{0x629c778e,0x60cd480c,0x3cff429f,0x93b70095}}, // _qaro, akam, čuva_, _עליו_,
+ {{0x320d8054,0x44316079,0x6458b78f,0x160ac028}}, // _ayey_, _dgz_, vavi, _हुनर_,
+ {{0x3a3ea1e2,0x7d061790,0x2258a16d,0xdd126041}}, // _sftp_, noks, _žrk_, _kļūs,
+ {{0x7658b791,0x212d8187,0x7d1b9792,0x2904d793}}, // tavy, _dieh_, _dhus, voma_,
+ {{0x64560082,0x443f8096,0x75357794,0x629c7795}}, // _leyi, _efu_, _fuzz, _uaro,
+ {{0x75357796,0xf72ac1fc,0x7ae9cd53,0x60c1aa88}}, // _guzz, нций_, fiet, _dolm,
+ {{0x5a34cd9a,0x26c25797,0x28a4a857,0x9634c1bc}}, // шнит, _joko_, _गोरि, шниц,
+ {{0x6676a0a4,0x249dc048,0x8fa382dd,0x5046a39d}}, // _مدار, _dawm_, _лате, _безб,
+ {{0xc3334087,0x7afce06e,0x2738a005,0xf9934076}}, // זוז_, črto, _tén_, צרת_,
+ {{0x69c9c054,0x660d08a4,0x33f6a04e,0x2ef50f2f}}, // ayee, _syak, _счас, изор,
+ {{0x273984b7,0x81e6007c,0x672e613e,0x2ca0417b}}, // _yèn_, _বুধ_, _libj, jdid_,
+ {{0xd7f80834,0x2d9a2030,0x9f3502f7,0x629e3798}}, // гур_, _aspe_, регі, _bapo,
+ {{0x645980c2,0x249dc022,0x2d51e06e,0x53340b2d}}, // zawi, _zawm_, bše_, серт,
+ {{0xb2ab473a,0x672d0022,0xe4e4271b,0x6d46e3de}}, // нтаж_, _tiaj, сіян, ćkas,
+ {{0x60cd4013,0x75356197,0x2fc94d87,0x44320194}}, // ukam, _ruzz, tyag_, _ggy_,
+ {{0xa855633f,0x660d00bb,0x7d1c6090,0x75356425}}, // [12f0] икач, _uyak, _bhrs, _suzz,
+ {{0xdc3ce03b,0x7d1b9799,0x68e9c018,0x25a905c8}}, // kšči, _shus, zied, _iral_,
+ {{0xdd9160a4,0xe7ebcbc5,0x25e6c021,0xa29fa10a}}, // توا_, _जेना_, _जेसी_, _गोग्,
+ {{0xdced2227,0x25a90181,0x3dc9408b,0x994806ec}}, // _bsaħ, _kral_, pyaw_, _جلیل_,
+ {{0x629e379a,0x60c1ac65,0xeb9987f8,0x69c6c0ff}}, // _yapo, _polm, тил_, økel,
+ {{0xa969a103,0x2169b64b,0x656d579b,0x6b8d0096}}, // вика_, вики_, mrah, _mpag,
+ {{0x2905e30b,0x2fc76277,0x6f1b928c,0x08d5c9fd}}, // vola_, ängd_, _thuc, иция,
+ {{0x443f979c,0x7d1b8296,0x443327d3,0x2905f79d}}, // _ufu_, _uhus, _lgx_, wola_,
+ {{0x26c36361,0x60c1b79e,0x39495308,0xdddae066}}, // _kojo_, _tolm, npas_, latň,
+ {{0x645af79f,0x2ed2448b,0x656d40c7,0xfaf3a0d0}}, // gati, _सत्त, irah, _اثر_,
+ {{0x6b8d0f3c,0x2d816d7e,0x2d51e480,0x645600d1}}, // _apag, nthe_, uše_, _peyi,
+ {{0xb605e082,0x2023a17a,0xd5b1cca7,0x656d57a0}}, // _aláà, ħriġ_, _وفا_, krah,
+ {{0x2d98e271,0x63a98a95,0x629e37a1,0x60dd000e}}, // ærer_, _jren, _papo, ërma,
+ {{0x26c2506c,0x249f84e2,0x6d5e77a2,0xa069f0da}}, // _soko_, _laum_, yspa, _рака_,
+ {{0x6d49d7a3,0x672e607f,0x68eb8006,0x7aeb8114}}, // lpea, _ribj, kigd, kigt,
+ {{0xd7fae095,0x629e20ce,0x765bc114,0x44332227}}, // _בהצל, _wapo, nauy, _egx_,
+ {{0x628440fd,0xa01b0e34,0xee374fb5,0x4127462a}}, // [1300] rfio, nföl, рну_, рото_,
+ {{0x62899280,0x2ed2c39f,0xe3e72043,0x3865b7a4}}, // _abeo, _तत्त, اکین_, _ndlr_,
+ {{0xad9b403e,0xb4db65df,0x799b97a5,0x656d57a6}}, // _axúd, _itàl, _asuw, arah,
+ {{0xb3584043,0xa96762af,0x200c003c,0xdca649b1}}, // _میرا_, рица_, ədir_, _тами,
+ {{0xac0781fc,0x24078844,0x660fc105,0xe2974368}}, // анца_, анци_, ückl, _тая_,
+ {{0x4ed5639e,0x316dd1fa,0x6d5e77a7,0x3f82017b}}, // _люст, drez_, pspa, itku_,
+ {{0x63a997a8,0xd5b26009,0x249f8090,0x8c3d9412}}, // _eren, _ظفر_, _faum_, _başd,
+ {{0x7af6404e,0xdb1c4249,0x2489a29c,0x291dd1da}}, // nnyt, byrå, đama_, _chwa_,
+ {{0x24869019,0x9998207f,0x68f641cd,0x6441a1cd}}, // mfom_, jbrž_, inyd, _ffli,
+ {{0xdbdca066,0x25a697a9,0x2bc9204b,0x63a8a06e}}, // lším, mvol_, रहमा, _trdn,
+ {{0x60c40748,0x6aa08605,0xa3d67706,0x20f8803b}}, // _boim, _kamf, िनि_, nčio_,
+ {{0x7afb407f,0xdb00a0ba,0x236dcbe5,0x91b7c0eb}}, // čutk, _urmâ, brej_, _چطور_,
+ {{0x6d4521f6,0x6aa08037,0xf1022914,0x98bf0018}}, // _stha, _mamf, लकूद_, lstī_,
+ {{0x629f17aa,0xa3d78918,0x66d1468f,0x6d5ced70}}, // _waqo, ानत_, _påko, áras,
+ {{0x629f0037,0x9c7ce274,0x3945231d,0x0dcae90f}}, // _taqo, jiči, ëls_, _јули_,
+ {{0x9c7ce06f,0x273c37ab,0x764d4561,0x3946c0c2}}, // diči, _dín_, ubay, _ktos_,
+ {{0x249f97ac,0x6135a03b,0x7658e052,0x94194df5}}, // [1310] _raum_, _išle, _kevy, ужат_,
+ {{0xd05c203c,0xf64777ad,0x2e3d40f9,0xba99214f}}, // kirə, рхан, _báfó_, _своє_,
+ {{0x7aeb8c8f,0x8c09007c,0x66d5ca84,0x200740c2}}, // tigt, রেশন_, _láka, łni_,
+ {{0x3f8161cd,0x249f8286,0xd05c203c,0x25a6006e}}, // rthu_, _qaum_, dirə, _šoli_,
+ {{0x26c48167,0x3946c022,0x6441a105,0xe5a5895a}}, // _domo_, _ntos_, _pfli, сили,
+ {{0x61fb37ae,0x270fc8b1,0x5887a0fb,0xe9d88013}}, // şulu, ात्र_, рыда, шкі_,
+ {{0x249f805f,0x7013c07c,0xf994200b,0x7158a0fb}}, // _taum_, _হলেও_, ַרץ_, арыс_,
+ {{0x765d005d,0x316dc004,0x04e7a07c,0xc3342095}}, // jasy, trez_, খকের_, חוק_,
+ {{0x81e4e07c,0x765d17af,0x3946c17b,0xdce74064}}, // _ভুল_, dasy, _ctos_, cują,
+ {{0x71d84095,0x3ea017b0,0x8387a04e,0x6d49d7b1}}, // יוחד_, _sait_, _выде, rpea,
+ {{0x2d8337b2,0x212fc28f,0x7658f7b3,0x765c37b4}}, // jtje_, _tigh_, _devy, vary,
+ {{0xf8dbc10a,0x2ca0005d,0x32d9a030,0xf09f57b5}}, // _बतिय, _qaid_, _dèye_, _thày_,
+ {{0x273c317c,0xdca5f7b6,0x7d0d0037,0x2bba2eda}}, // _sín_, _гали, _dkas, _उपहा,
+ {{0x2cbfd7b7,0x6459d7b8,0x7d09c1cd,0xdddd40c2}}, // ljud_, _hewi, loes, _odsł,
+ {{0x68ed40e2,0x98dbc20b,0x7ae997b9,0x63abc037}}, // fiad, _बताए, _omet, _hrgn,
+ {{0x3d09a010,0xcd2a51a8,0x273c37ba,0x798280c2}}, // ाकडे_, ужбе_, _vín_, ytow,
+ {{0x661b8291,0x693c2320,0x645c37bb,0x2bba2180}}, // [1320] _nzuk, _ičem, pari, _उपवा,
+ {{0x273c2016,0x6441f7bc,0xd05d003c,0x2eedd04d}}, // _tín_, ncli, disə, hief_,
+ {{0x68ed57bd,0x1a9b200b,0x225877be,0x20f88391}}, // biad, _גייע, _verk_, učio_,
+ {{0x291ea480,0xdd8f619a,0x693ae227,0x63bce262}}, // _shta_, عون_, _bċej, ärni,
+ {{0x20f88013,0x787b000b,0x60dbd7bf,0x68fb8011}}, // sčio_, אָוו, dhum, _djud,
+ {{0x290a37c0,0x6cd6619a,0x398085df,0xd05c203c}}, // noba_, _اقسا, ròs_, tirə,
+ {{0x2722a016,0x7659c31d,0xddc1a009,0x60c12069}}, // _lưng_, _bewy, _belő, ölmi,
+ {{0x3ea243e0,0xe81ea0c2,0x7afb8156,0x60dbc114}}, // _jakt_, येगा_, _gjut, ghum,
+ {{0x798457c1,0xb5fb4031,0x7bce2066,0x3ea243e0}}, // ntiw, _adáj, hybu, _makt_,
+ {{0x7afb806e,0xe1ff4019,0x644414b7,0x764282a8}}, // _zjut, _odón_, _afii, lcoy,
+ {{0xac18e0d7,0x290a2b46,0xc0e6c04f,0x20d42052}}, // рону_, doba_, _ложк, _päin_,
+ {{0xdb08c057,0x2ed56ff1,0x693c2066,0xf79ac711}}, // _ordé, _धत्त, _včel, _منصب_,
+ {{0x3946c4ec,0x7643e037,0x629657c2,0x6aa441cd}}, // _utos_, _ffny, deyo, ddif,
+ {{0x6e360009,0x673ab7c3,0x2eeea05d,0x1bd4e808}}, // _egyb, _kutj, niff_, _моля,
+ {{0x6ce7084f,0x60c64488,0x67d43391,0x7d0d17c4}}, // _гіпе, _bokm, пору, _tkas,
+ {{0x6d5601cd,0x78a2c067,0x2ba9a077,0x2f44e088}}, // _fwya, _jaov, किता, _ịgwa_,
+ {{0x290a37c5,0xdcdce026,0x753ab7c6,0x799e2163}}, // [1330] boba_, यसपछ, _lutz, _espw,
+ {{0xadebc4ef,0x2738a067,0xccf8e04a,0xa49b40f9}}, // _जइसन_, _tĩnh_, nně_, _akòt,
+ {{0x291f8048,0x26c5b7c7,0xa2bf6c87,0x999969af}}, // _phua_, _rolo_, लॉग्, ркет_,
+ {{0xd126c68c,0x78a2c6ca,0x48158e31,0x60dbc0bb}}, // _زم_, _naov, омес, xhum,
+ {{0x6c7a400b,0x91b6a077,0x2f0f60cb,0x26c5b7c8}}, // _דארפ, _अपॉइ, fügt_, _polo_,
+ {{0x7f4d4071,0x3ea257c9,0x9fc9a338,0xe8f8a2cb}}, // mpaq, _zakt_, игла_, блі_,
+ {{0xddd520c2,0x38690020,0x3cedc02e,0x9db9a099}}, // lazł, _odar_, tiev_, рылу_,
+ {{0x2ca366a6,0x63b46066,0x20d1c049,0x78a1a3c5}}, // _hajd_, šený, _táim_, _walv,
+ {{0xdce9e42e,0x39490022,0x3f9ea2bb,0x6d4d4b6c}}, // sreč, _ntas_, _estu_, npaa,
+ {{0xdce9f7ca,0x98f7a54f,0xab65e841,0x2d9f8037}}, // preč, _اثرا, овол, _isue_,
+ {{0xb4db60f9,0x6441f7cb,0xed59e60f,0xeaba095a}}, // _atàk, rcli, јом_, айн_,
+ {{0x6b8561cd,0xe80ac077,0x290a200f,0x7b1060cb}}, // ithg, _हुवा_, woba_, läut,
+ {{0x64586069,0xdd94c0db,0x628d0090,0x2b4900d5}}, // ðvik, _нацы, _dbao, _ctac_,
+ {{0x201dc0bb,0x60c657cc,0x657761fb,0x69ce2056}}, // _izwi_, _sokm, kuxh, tybe,
+ {{0xe5a32486,0xd62a2df6,0x2738a067,0x07a32407}}, // мири, рове_, _vũng_, марн,
+ {{0x25ad97cd,0x645b97ce,0x7e698291,0xddc760c2}}, // _orel_, _neui, _ndep, _wejś,
+ {{0x27060626,0xa3e70010,0x63a4ca60,0x78a457cf}}, // [1340] रवीर_, _येऊ_, _áint, rdiv,
+ {{0x798455c5,0x66f20018,0xe8e0e119,0x186a6471}}, // rtiw, rāku, _thụy_, ражи_,
+ {{0xfc3f0359,0x645ab7d0,0x6d4997d1,0xdb0421de}}, // ldía_, _reti, _atea, _triâ,
+ {{0x41aa8967,0xccf8e026,0xb7c060f9,0x753b81b6}}, // ивен_, zně_, _aṣá, _buuz,
+ {{0xb4ca8029,0x7cf3417a,0x386057d2,0xa2c12077}}, // _लकी_, għru, mair_, रॉक्,
+ {{0x2d8525df,0x765dc069,0xdb6aa691,0x7d1657d3}}, // àleg_, ðsyn, арил_, llys,
+ {{0x63a9d7d4,0x8c3b4105,0x628e61f6,0x645c694f}}, // even, _maßn, _ibbo, _ieri,
+ {{0xeeb8c6bd,0x8bfc21a6,0x32d8c18c,0x398417d5}}, // слиш_, _ইরান_, _béya_, kös_,
+ {{0x656457d6,0xdd90c25b,0x2918c018,0x66fc8364}}, // nsih, رود_, ēra_, lčko,
+ {{0xd90de050,0x60c297d7,0x8574e54a,0x2498214f}}, // هین_, ljom, плох, jerm_,
+ {{0x26c7f7d8,0x2d9ea265,0xf8bf41bf,0x6d48a4a4}}, // _dono_, _uste_, _miér_, _utda,
+ {{0x60c297d9,0x645c77da,0x39490019,0x6298b7db}}, // njom, _leri, _ptas_, levo,
+ {{0xccf8e026,0x290b08d4,0x6aa401f6,0xa3ae4148}}, // pně_, roca_, _daif, किफ_,
+ {{0xe297e247,0xe7f28148,0x644657dc,0x6aa3e0fd}}, // _мач_, _आइना_, _afki, _ganf,
+ {{0x753c605d,0x518437dd,0x764440a2,0x24800733}}, // _nurz, мута, fciy, _fcim_,
+ {{0x444477de,0x63a4c057,0x6d46a143,0xdcee4018}}, // ec_, _áins, _čkal, tubā,
+ {{0x26c7f7df,0x3949c3ac,0xb33ca13e,0x62976089}}, // [1350] _yono_, _çast_, _irħu, texo,
+ {{0x6d4d57e0,0x63ae6090,0x645b80fa,0x66d920f9}}, // rpaa, _brbn, _seui, _fìka,
+ {{0xd9eaa5aa,0x66ca4089,0x27e62020,0x3ce2e046}}, // _झेलत_, _výkr, _ƙone_, _ओतने_,
+ {{0x6721a098,0x63a9d593,0x8f3544d7,0x24990098}}, // _shlj, yven, _немц, nesm_,
+ {{0xa4f94010,0x443940ce,0xdce7619f,0x317917e1}}, // ्कीच_, _igs_, şlıc, nusz_,
+ {{0xe739b7e2,0x8c3d8746,0x6722c261,0x95cb6850}}, // бек_, _daşa, _ahoj, _куба_,
+ {{0xd378e0a9,0x39840009,0x395861cd,0x7b6477e3}}, // juć_, zös_, _gwrs_, _отте,
+ {{0xadc44031,0xb4d5c466,0x645d57e4,0x3a394143}}, // _afẹs, हसे_, _jesi, _jgsp_,
+ {{0x78a417e5,0x29030cfd,0xf3f9c0ba,0xc8ff61a6}}, // _raiv, čkal_, faţa_, ্গীত_,
+ {{0x2d85e42c,0x7aed0547,0x6efec041,0x753d553e}}, // ttle_, _amat, rība, _musz,
+ {{0x753d57e6,0x753577e7,0xa0a60143,0x66dda187}}, // _lusz, _mizz, панд, _cèke,
+ {{0x2b4b40ae,0xbddb4061,0x644572d4,0x2a604022}}, // _otcc_, _afèm, hchi, xaib_,
+ {{0x38604733,0x7aed0e01,0x6aa3e009,0x7de26009}}, // vair_, _dmat, _tanf, _kísé,
+ {{0x765d417b,0xb5fdc00a,0x64456cb8,0x398417e8}}, // _aesy, gaše, jchi, rös_,
+ {{0x44386187,0x2467e009,0x60232312,0x444477e9}}, // _rgr_, _című_, едра, vc_,
+ {{0xa3e668a2,0x934645a4,0x27f8e02e,0x2b594227}}, // _बेग_, знае, úrne_, _bwsc_,
+ {{0xb4ca00a8,0x7ce077ea,0x6d41e5fc,0x67357099}}, // [1360] ोसी_, _jörg, lqla, _bizj,
+ {{0xb5fd8098,0x83fce29c,0x6fb34049,0x7ce06069}}, // _kešf, sađe, عملا, _mörg,
+ {{0x765d4435,0x7aed00e2,0x2baeea2a,0xbe3b2076}}, // _fesy, _ymat, टिया, _לעית,
+ {{0x629987ff,0x6445772f,0x7e60c5df,0x645c77eb}}, // gewo, achi, xamp, _weri,
+ {{0x673d46ca,0x673c60e8,0x7ae38b83,0x753c60cb}}, // _gusj, _wurj, ënta, _wurz,
+ {{0x59cbe4f6,0x2b5861de,0x9879216d,0xdb0be017}}, // ाहार, _twrc_, ršću_, _urgè,
+ {{0x60db97ec,0x6d59d7ed,0x8af0603c,0x62998d53}}, // _flum, _awwa, ssəs, bewo,
+ {{0xa3cc010a,0x60d647b9,0x29120800,0x681380c2}}, // लहा_, nkym, _nkya_, _będą,
+ {{0xed3581d3,0x2002c03c,0xe166c049,0x6f038057}}, // _цэрэ, çki_, أدبي, énce,
+ {{0xaa7b406f,0x07a6b0f6,0x03a6a474,0xa0c9a13a}}, // _umýv, _назн, _низо, _وذلك_,
+ {{0xf1f7c050,0x6722d7ee,0x7afbc3ab,0x1bf28ba0}}, // قعیت_, _thoj, bnut, _आइडल_,
+ {{0xda2080c5,0xdce466ca,0xdee3c192,0x66e3d7ef}}, // _बरात_, brić, _похи, _поха,
+ {{0xbcfb604a,0x6f0d40ba,0xdb0e83ac,0x765d416f}}, // _svém, voac, _arbë, _resy,
+ {{0x3a200361,0x753d469f,0x2012a03c,0x63bce156}}, // _dzip_, _rusz, əyir_, ärns,
+ {{0x7535711a,0x0f1b413a,0x22468037,0x9c7ce066}}, // _rizz, مغرب_, kcok_, sičs,
+ {{0x290ea0f9,0x753d4791,0x80c12077,0x2a7ee057}}, // hofa_, _pusz, रॉजे, lgtb_,
+ {{0x60c9854c,0xfc3f4d64,0x4735603b,0x68fd0156}}, // [1370] _roem, _caía_, днас, mnsd,
+ {{0x60c997f0,0x692ac0a3,0x2d87a066,0x291917f1}}, // _soem, _křes, ytne_, llsa_,
+ {{0x6ce4614f,0xaa7b0089,0xddcde013,0x7afe0431}}, // хіте, chýc, _įkūr, čstv,
+ {{0x6463629c,0x60c44098,0xdb040271,0x3863217a}}, // _štić, cjim, _ernæ, lajr_,
+ {{0x6d4d0037,0x0b44814f,0x290ea912,0x6ec0d7f2}}, // _jtaa, енін, fofa_, वानु,
+ {{0x213ea58f,0x7c39c1cd,0xe8224077,0x7ff420eb}}, // _buth_, _sgwr, _मरता_, _آسیا,
+ {{0xf8b26557,0x6282c1f6,0x90d5c119,0x60c98155}}, // _בשם_, _bcoo, _dùn, _toem,
+ {{0x2ca957f3,0xdce2e143,0x9f5f80e0,0xdce463de}}, // ndad_, _dvoč, áról_, trić,
+ {{0xb05b0cad,0x2ca944cd,0x26cb42f8,0x3ea5a271}}, // tjän, idad_, _moco_, _talt_,
+ {{0x7ce063df,0xbddb4030,0xab5dc0c2,0x658aa5fc}}, // _görd, _afèk, wyżs, _zəhm,
+ {{0xf771c0d0,0x7e61f7f4,0x5f0600ff,0x443a2013}}, // راک_, ralp, дзна, _rgp_,
+ {{0xe3e4807c,0x66d02156,0x7afd102d,0xb4f94148}}, // _ফুটব, _läkt, gnst, ्कोप_,
+ {{0xb91580f9,0x823603f1,0xa7fb0019,0x61e220e8}}, // _bibẹ_, بردا, taña, _ƙolo,
+ {{0xed59e1e3,0x66d26069,0xe8e0c067,0x78a9d7f5}}, // _док_, _tæki, ượt_, ldev,
+ {{0x044647ca,0xaac4e6ec,0x63ad57f6,0x672417f7}}, // дебн, _ستون, evan, _thij,
+ {{0x6ee073a2,0x225f965a,0x6e3b97f8,0x26ca77f9}}, // _möbe, _neuk_, _ngub, _sobo_,
+ {{0x59ba37fa,0x442000b1,0x60d64013,0x2d8941e7}}, // [1380] _उपकर, _uzi_, rkym, gtae_,
+ {{0x645f000e,0x2366816d,0x7d09407f,0x6b89c108}}, // _zeqi, vsoj_, česa, iteg,
+ {{0x26cb57fb,0xe9df02a8,0x290f8241,0x6283e071}}, // _foco_, ncún_, foga_, _mcno,
+ {{0x3ea7e987,0x398641de,0x3f9fc088,0xeaafa0e0}}, // _lant_, pôs_, kwuu_, یٹن_,
+ {{0x290eb7fc,0xe894a6fc,0xd9438924,0x777c2005}}, // tofa_, еаль, вечи, lurx,
+ {{0x7c3b8108,0xdb09a2a8,0x3ea7e0fd,0x657ae1fb}}, // _egur, _freí, _nant_, yuth,
+ {{0x69d86271,0xfa8ee119,0x778aa5fc,0x7c3b813e}}, // øved, _bừng_, _təxm, _fgur,
+ {{0x66cfa2f7,0x6b89cffe,0xa6db60c4,0x3ea7e363}}, // _søkt, fteg, _guðb, _aant_,
+ {{0xe7ee0da9,0x63a94057,0x7e63a227,0x6b89d7fd}}, // _चेला_, _áent, banp, gteg,
+ {{0xe0df25cd,0x7ae297fe,0x6284041a,0xdb0d2057}}, // npòt_, lhot, _acio, _traé,
+ {{0xa0a3e0fb,0x6f0f0024,0x38600256,0x6b960013}}, // тшыл, tocc, _beir_, _apyg,
+ {{0x25a057ff,0x83fca04d,0x7ae29800,0x386d817b}}, // kwil_, _ceđe, nhot, _xder_,
+ {{0x8c43c2d8,0x26cca5f2,0x3eba2037,0x0443d16c}}, // _рече, _kodo_, _bnpt_, _речн,
+ {{0xfc3f4019,0x2cb95801,0x290f80b1,0xd5b0e009}}, // _caín_, _snsd_, zoga_, _مفت_,
+ {{0x20d8c13a,0xb8e5007c,0xfd13457f,0x5ba3e0fb}}, // _léim_, _এত_, نجر_, _арыз,
+ {{0x38601384,0xe7ab806f,0x22490106,0x66dda187}}, // _geir_, _ďaľš, _ufak_, _mèka,
+ {{0x44224066,0x2bb1c0c2,0xb87b0057,0x3e880009}}, // [1390] _czk_, जिया, chía, sító_,
+ {{0x2126c48d,0x333f8054,0xbf15613a,0xfc3f004a}}, // _lhoh_, _ruux_, رواب, zdíl_,
+ {{0x644980cb,0x8c9588ef,0x84e80555,0xaaa82bc5}}, // _pfei, ерлі, _رفیق_, _छोटक,
+ {{0xdbdc6765,0x501be076,0x394d80d5,0x3f895044}}, // ráða, חובו, _ptes_, ptau_,
+ {{0x60cd1693,0xbdf887f0,0x7e609802,0x260f74c6}}, // _moam, ورها_, _demp, _तुरी_,
+ {{0x64586069,0x443ce797,0x672d5803,0xf771a049}}, // ðvit, _ngv_, lmaj, هاب_,
+ {{0x6d5c61cd,0x2139400e,0x7ae29804,0x6e22d805}}, // _gwra, _kish_, bhot, _azob,
+ {{0x7b1600cc,0xbcfb6026,0x6d5d405d,0x213f8037}}, // náut, _svéh, _kwsa, _tuuh_,
+ {{0x7d034005,0x58848099,0xa7fb1806,0x83fca133}}, // _ínsu, _шыра, daño, _ređe,
+ {{0x39401807,0x8c3ca181,0xa3df2180,0x46dbc5cb}}, // _ruis_, _yağm, थना_, _बतकह,
+ {{0x3ea7f808,0xc6926087,0x2ca90025,0x3ea903c5}}, // _vant_, נאט_, _baad_, _baat_,
+ {{0xa7fb04cd,0x6d41b809,0xfc3f4005,0xf1b260be}}, // gaño, _iula, _saín_, אסט_,
+ {{0x60cd01fc,0x291e0041,0xb05b0052,0x83fce16d}}, // _doam, ēta_, nkäy, rađa,
+ {{0xbebb400e,0x64a2f3de,0x6f02c012,0x777c203e}}, // _dhëm, _баша, _zjoc, turx,
+ {{0xf8d3c028,0x60cd180a,0x628e2c1e,0xf2d2a053}}, // _तवाय, _foam, rfbo, נעל_,
+ {{0x2903600d,0x63a260c2,0x672d4143,0xa7fb0019}}, // _ajja_, łonk, gmaj, caño,
+ {{0x68368121,0x032661bc,0x09e343fc,0xfc3f4057}}, // [13a0] _müdü, едан, готн, _taín_,
+ {{0x6e2400bb,0xdb0be2a8,0x629c23ac,0x7c2405f1}}, // _izib, _orgí, qero, _izir,
+ {{0x68e28b93,0x26ccb80b,0x443dc00d,0x7f40803c}}, // thod, _rodo_, _ogw_, _sumq,
+ {{0x68e2804a,0x44224012,0x891684c7,0x27e6980c}}, // uhod, _tzk_, ربائ, tzon_,
+ {{0xfd48c0f9,0xc1c8a0a3,0x7bdd85fc,0x78a8b75d}}, // _damọ, िङ्ग, _üsul, _padv,
+ {{0x63a64098,0x6105403b,0xd00f81ac,0x29004367}}, // _iskn, mėla, حله_, lnia_,
+ {{0x6e22c0e0,0x6d41b80d,0xdb0be01f,0x6adbc064}}, // _szob, _cula, _orgâ, बसूर,
+ {{0x61e16046,0x71f7a243,0x7d1bcc04,0xe876c5d1}}, // _ülla, عروس_, glus, راعظ,
+ {{0xb09b8095,0xa09b41a1,0x657e60bb,0x1a9b8053}}, // _צימר, ליקט, luph, _צימע,
+ {{0x629e61b9,0x026ae04e,0x6108c026,0xc66ae052}}, // nepo, чший_, děle, чшие_,
+ {{0x68f5380e,0x7c240284,0x2018e0c4,0xdbdee057}}, // vizd, _azir, æri_, tíña,
+ {{0xfbe04028,0x6449c0b8,0x1db2404b,0x7ae4407f}}, // फनाम, ccei, ीमात, jhit,
+ {{0x4dd3c949,0x693c206e,0x7f41a05d,0xba3b0017}}, // _متوس, _očes, _zulq, veïd,
+ {{0x7c240041,0x2ca90054,0x2139580f,0x44248037}}, // _dzir, _waad_, _sish_, _mzm_,
+ {{0x673ab28e,0x60cd01fc,0x62852066,0xf527482f}}, // _mitj, _toam, _vcho, ефан,
+ {{0x7cd8220f,0x68e44420,0x7ae45810,0x628520c2}}, // _ağrı, ghid, ghit, _wcho,
+ {{0xb87b5811,0xb2740052,0x95cb5109,0x7c2402a4}}, // [13b0] _alíc, _слыш, чува_, _gzir,
+ {{0xd56401e1,0x39425812,0x443ea156,0x63a2854c}}, // _стъп, _duks_, _ngt_, dwon,
+ {{0x26cee926,0x66d260c4,0x7ce4e58f,0xa49b00d5}}, // _mofo_, _rækt, _iòrd, cnòc,
+ {{0x51874772,0x62829813,0xf65390e9,0xe8d12290}}, // _пуга, ggoo, ائض_, _सकुच,
+ {{0xf77f0106,0x78a98046,0x673aa07f,0xc7a5814a}}, // nuç_, _vaev, _bitj, тилк,
+ {{0x798d5814,0x6b8d4954,0x629e691e,0x6f1d00cb}}, // mtaw, mtag, cepo, hlsc,
+ {{0x6d41abfb,0x673ab815,0xceb421a9,0x2486d816}}, // _qula, _ditj, טיס_, _bcom_,
+ {{0x7e61ac8f,0xdb1be057,0x81d4e1a6,0x6d41b817}}, // _welp, rxuí, _সেভ_, _vula,
+ {{0x6d41b818,0x479480d0,0x67299819,0x26dc8018}}, // _wula, _مجلس, _ihej, īvot_,
+ {{0xccf8c04a,0xda67b81a,0x2900581b,0x59b32bde}}, // _svět_, _سائي, znia_, ुमार,
+ {{0x2295c049,0x27e941f3,0x25a6c0ca,0x2900581c}}, // _للاس, hzan_, _esol_, ynia_,
+ {{0x9d21e07c,0xcb6a062a,0x693c204d,0xe45f0156}}, // নদেন_, маме_, _očer, sjön_,
+ {{0x06b0e07c,0x798d4022,0x60dbc0ce,0x3327e363}}, // _চকরি, jtaw, mkum, _thnx_,
+ {{0xa01b0277,0x66d26065,0x249fd783,0x29004064}}, // tför, _lækr, leum_, wnia_,
+ {{0x2912581d,0xa7fb4071,0x45d2e39d,0x644d01cd}}, // toya_, _reña, _војс, _ffai,
+ {{0xfd62c088,0x6d43e08b,0xb90341ba,0x68e4417b}}, // _netị, _iuna, азск, uhid,
+ {{0x2912581e,0x3f8dc5df,0xaa466627,0xa7fb49c1}}, // [13c0] roya_, nteu_, везл, _peña,
+ {{0x321a343a,0x39462052,0x98a0400a,0xdfcf413a}}, // _typy_, _иног, kmić_, غيل_,
+ {{0xa7fb403e,0x753aa2bd,0x661b81df,0x7e63e108}}, // _veña, _ritz, _ayuk, _menp,
+ {{0x443f981f,0x385a40e0,0xdb0400d5,0x63b56364}}, // _cgu_, _تشدد_, _arnú, _drzn,
+ {{0xa06785d0,0x29021820,0xf8b300be,0x3eab6052}}, // тача_, inka_, ישא_, öitä_,
+ {{0x3f9a2037,0xdce2e06f,0x98b88187,0x6d42c052}}, // _kppu_, _spoľ, _bură_, _ruoa,
+ {{0x4420c03b,0xa3c3e032,0xb0e20029,0x673aa488}}, // _ši_, yšší_, _पतंग, _vitj,
+ {{0x2bb48046,0xb5fb4049,0x60dbc0f2,0x20d42046}}, // ूमदा, _meán, gkum, _käis_,
+ {{0x6f029821,0x673aa17a,0x7ce60049,0x78abd822}}, // mnoc, _titj, _mórd, _dagv,
+ {{0x80d120c2,0x2ee685dc,0xb87b0049,0x29021823}}, // _सकें, lhof_, thío, enka_,
+ {{0x6aabd824,0x656d1825,0x61e9c0d4,0x287b61a9}}, // _fagf, _svah, azel, ונימ,
+ {{0x6f028105,0x2900c013,0x2ee685c4,0xeabf503c}}, // nnoc, čias_, nhof_, _chùl_,
+ {{0x2a648037,0x3f8dc017,0x7d029826,0x6f028322}}, // _memb_, cteu_, inos, inoc,
+ {{0x66d5c031,0x78abc012,0x2d805827,0x7d029828}}, // _dáku, _zagv, duie_, hnos,
+ {{0x60cf4012,0xd7f82052,0xdb0e8926,0x9c7ce530}}, // _pocm, вут_, _arbí, lhčo,
+ {{0xf837e076,0x69d6c106,0x27242262,0x2caca16f}}, // _בנות_, _üyes, röna_, _ladd_,
+ {{0x673c6dac,0x63b61829,0x3d0f0028,0xc1056049}}, // [13d0] _birj, _gryn, सवीं_, هوري,
+ {{0x249fd0f2,0x78ad01dd,0x60dd09cd,0x6729800e}}, // zeum_, _haav, lksm, _shej,
+ {{0x78ad582a,0x7f43e03e,0x3eadd82b,0x316d8143}}, // pdav, _xunq, ydet_, _svez_,
+ {{0x98ad8064,0x63a44200,0x60dd003b,0xc0ab80e0}}, // _mieć_, bwin, nksm, بائل_,
+ {{0x2489017b,0x673c782c,0x26cfd82d,0xeab08711}}, // _mcam_, _firj, _rogo_, تعل_,
+ {{0x78ad0660,0x6135c013,0xd6dba54a,0xf4140076}}, // _laav, _išli, ьте_, ספס_,
+ {{0xfc3fc05c,0xef1f06df,0x443f817b,0xc33423c8}}, // žím_, klü_, _tgu_, בוס_,
+ {{0x6d43f82e,0x3ea164ef,0x7e63f82f,0x3ce6805c}}, // _runa, leht_, _senp, chov_,
+ {{0x7e63f830,0xeb1f804b,0x316d87a0,0x661d5831}}, // _penp, बतीत_, _uvez_, _jysk,
+ {{0x60dbd832,0xed59e926,0xfff68555,0x26d16a38}}, // skum, ноо_, _حکمت_, _jozo_,
+ {{0x4155a39d,0x644d4037,0x656d5833,0xe8df8079}}, // _свес, kcai, hsah, _ndịa_,
+ {{0x455aa076,0xdce7c018,0x28c400c2,0xf487c39d}}, // _נכנס, ēlēj, षाधि, _јужн,
+ {{0x2d816105,0x7e63e108,0xeabf5834,0x90e041e9}}, // huhe_, _tenp, _chùm_, _dọ̀,
+ {{0x877ac053,0x68e2c089,0x7ce60057,0x776d4b47}}, // _נאצי, _zlod, _sórd, dsax,
+ {{0xba208216,0x7ce06069,0xafe33835,0x6aad0025}}, // _बर्फ_, _börn, борл, _faaf,
+ {{0x63a99836,0x6da64423,0x25d6e0be,0x3ce68286}}, // _osen, либа, _תוקן_, xhov_,
+ {{0x2d8d8022,0x6b9b212a,0x6441a2f0,0x656d5837}}, // [13e0] _nqee_, ăuga, _ogli, gsah,
+ {{0x644d400c,0xf3f9c0ba,0x3ebf8827,0xbd5b0076}}, // acai, gaţi_, _anut_, _רכיש,
+ {{0x2ca05838,0x63a45839,0x68e4004a,0x291a2079}}, // peid_, swin, _klid, _ikpa_,
+ {{0x657bd83a,0x298a898c,0x20d42046,0xf8bf4049}}, // kruh, еско_, _täis_, _gné_,
+ {{0x6aa1e954,0x80c466a8,0x645ae025,0x7981e1fb}}, // kelf, रादे, ybti, hulw,
+ {{0x2ee685dc,0x3320413e,0x7bd8a013,0x2918783b}}, // shof_, llix_, tyvu, _ukra_,
+ {{0x291a2385,0x23674098,0x661d583c,0x2d82183d}}, // _mkpa_, ćnje_, _zysk, huke_,
+ {{0xa6db4765,0x26d1783e,0xd90dc6b0,0x7ae40011}}, // _viðg, _zozo_, ویل_, _nlit,
+ {{0x386a383f,0x78a29840,0x7ae2cbb6,0x638aa1de}}, // kabr_, meov, _vlot, _bênç,
+ {{0x78a28320,0x6b80cc52,0xb05b4156,0x44f4e0ff}}, // leov, rumg, _knäc, рпос,
+ {{0x6f044320,0x656d405c,0x925902dd,0x4438e277}}, // knic, zsah, кант_, är_,
+ {{0x3f821791,0x28af0982,0x0446c084,0x7d094e60}}, // fuku_, _जोखि, _седн, česk,
+ {{0x6442c426,0x26d2018c,0x2d821841,0xd7c7e0eb}}, // _igoi, _doyo_, guke_, نویه_,
+ {{0x2a694025,0x6b81f842,0x7ae41843,0x3ead004a}}, // waab_, bulg, _elit, žet_,
+ {{0x6d46431b,0x63a240fa,0x26d17844,0x7d045845}}, // _auka, çonn, _rozo_, fnis,
+ {{0xd904863b,0x26d21846,0xf548c119,0xdce0e018}}, // _ری_, _goyo_, _gục_, kumī,
+ {{0x656d5847,0x70552043,0x612b6105,0x6d453819}}, // [13f0] usah, _پنجا, hüle, _wuha,
+ {{0xeb0d81b0,0xe8162010,0x6f044197,0xa3b3898a}}, // िक्त_, _दुवा_, anic, टिस_,
+ {{0x63a9800d,0x7ce06c82,0x62857848,0xf3f9c12a}}, // _ssen, _törn, rgho, raţi_,
+ {{0x3f8320eb,0xbd05e0f9,0x6442cc88,0x213ea7ad}}, // muju_, _amẹ́, _ngoi, _aith_,
+ {{0x3949417a,0x2b46c067,0xddd0a04a,0x0878000b}}, // qqas_, _nuoc_, _češt, רעדט_,
+ {{0x69d98f06,0xe9b88043,0x81b9a0e8,0x612020cb}}, // rywe, انوں_, _haɓɓ, völk,
+ {{0x672d041b,0x612b6315,0x645bc1b9,0x3f820bc1}}, // _ohaj, rüld, tbui, zuku_,
+ {{0x752d0291,0x6d556108,0x63a98466,0x6d44e06f}}, // _nhaz, _itza, _tsen, _čiap,
+ {{0xa7fb01af,0x656f0171,0x2b46c067,0x7981ed28}}, // bañi, jsch, _cuoc_, wulw,
+ {{0x2b46c016,0x7ae4088b,0x7e6b9849,0x752d184a}}, // _duoc_, _slit, lagp, _ahaz,
+ {{0xe4c60d2b,0xa2c2abde,0x657bd84b,0x6d5980ae}}, // айни, लार्, sruh, lpwa,
+ {{0x656f1385,0x6126e069,0xf530c049,0xf1b98552}}, // fsch, rðla, _فإن_, _krš_,
+ {{0x61ed4098,0x656f116b,0x95830052,0xa158c71b}}, // jzal, gsch, оляе, _бачу_,
+ {{0x271e48b1,0x644f065f,0x62872536,0x6aa3b84c}}, // पत्र_, acci, ggjo, nenf,
+ {{0x25e8412f,0x7e66584d,0xb5fdc06e,0x3ea2184e}}, // चैनी_, _pekp, kašk, pekt_,
+ {{0x7ae4184f,0x66d5c066,0x3eafc065,0x26cdc00a}}, // _ulit, _zákr, _jagt_, njeo_,
+
+ {{0x5d6a533f,0x5fab60c2,0x656f00ca,0x64440561}}, // [1400] тизм_, _टैबल, csch, _igii,
+ {{0x27edd770,0x63a90121,0x7e566d94,0x9e3a613a}}, // izen_, çene, ртац, نساء_,
+ {{0x6f045850,0x7a7b2087,0x68e9caaa,0x7d04a7a4}}, // pnic, _בריס, khed, éism,
+ {{0xe9da9826,0x41b306b0,0x969645da,0x2ee94751}}, // кке_, _امیر, _трош, chaf_,
+ {{0x6aa29851,0xa3e60026,0x61ed40c2,0x7d09800e}}, // reof, पनि_, czal, _djes,
+ {{0x249a205d,0x212d8425,0x6299c194,0x7c298018}}, // _jbpm_, _eheh_, _abwo, _dzer,
+ {{0x2fc769ac,0x27edc200,0x5f092029,0x6b844090}}, // ängt_, ezen_, _सदस्_, luig,
+ {{0xbcfb3852,0x7d099853,0xa3b384ba,0x61fb400a}}, // nsér, _gjes, टिश_, _žuli,
+ {{0x644041de,0x6d408025,0x6563e1f6,0x256ca1e9}}, // _úmid, _iima, _awnh, _bólá_,
+ {{0x672291c5,0x6b9e24a4,0x83fce0e4,0x612b6106}}, // lloj, _oppg, lađi, püle,
+ {{0x68e9c0bb,0x3867e1e7,0x3866c41a,0x7ce06156}}, // bhed, _denr_, _teor_, _rörl,
+ {{0x6f18ab81,0x69daf854,0x3946c013,0x69c9c022}}, // lovc, ryte, _tuos_, bxee,
+ {{0xf8c96292,0x799e2037,0x6d409855,0x656f0105}}, // रामय, _appw, _mima, usch,
+ {{0xa3da410a,0x2d84d856,0xd7f80772,0x3d1060c2}}, // ़हन_, mume_, рую_, ़कों_,
+ {{0xaca48088,0x644fe064,0x8f35135c,0x3ceea10a}}, // _azọp, ścic, _лекц, _अतने_,
+ {{0x9553e050,0x7afc228f,0x7ce22057,0x799e205d}}, // _بخوا, airt, _fôro, _dppw,
+ {{0xb5fdc42e,0xb995013a,0x692ac026,0x629aa06e}}, // [1410] vašk, _الخب, _břez, _obto,
+ {{0x7afc3857,0x7d1b9858,0x7d098579,0x4545613a}}, // cirt, _skus, _sjes, منطق,
+ {{0xa6db4069,0x9259804e,0xf77367f9,0x2ee6d137}}, // _viðb, гает_, تاذ_, _olof_,
+ {{0xdb0d3859,0x6d4765f9,0x78a44095,0x60c2d85a}}, // _braç, _vuja, ceiv, _nnom,
+ {{0x8c3d812a,0x7d1b84d2,0x6aa3b85b,0x7e6773bd}}, // _naşt, _vkus, tenf, _tejp,
+ {{0x63ad0167,0x6125c07b,0x98adc6a6,0xfaa5b85c}}, // _msan, tólo, rmeč_, сако,
+ {{0xb5fdc0a9,0x78a3b85d,0x68fbd85e,0xc4466062}}, // maši, renv, tiud, _ایمن_,
+ {{0x7d09985f,0xfc4a6049,0x8c3d8106,0x63ad1860}}, // _ujes, _tríú_, _başt, _osan,
+ {{0x68fbc0dd,0xdb0d201f,0x63ad02e0,0xef19e14a}}, // riud, _graç, _nsan, лми_,
+ {{0x68e9d861,0x06c9e5be,0x6d409862,0x6125c019}}, // shed, угой_, _zima, pólo,
+ {{0x7a6a024a,0x27edc0e0,0x6d4097aa,0xdc6a05a8}}, // линг_, szen_, _yima, ланд_,
+ {{0x7ce2206f,0x4735d1b7,0xe5a6014a,0x6da6030b}}, // _pôro, _унес, бими, бима,
+ {{0x3f84d863,0x386dc042,0x291dd864,0x64452143}}, // cumu_, maer_, _ikwa_, _bghi,
+ {{0xaa7b003a,0x2b47e0a0,0x7d76c0d0,0x7afd1865}}, // tkýc, _tunc_, _امور_, aist,
+ {{0x41b524ae,0xa355e1ac,0x1fb52acb,0x248d80e4}}, // остт, _اختص, остр, _ocem_,
+ {{0xa2da6077,0xe3b2050f,0x628d0049,0x6b9cae22}}, // पॉन्, _فرخ_, _gcao, _ärge,
+ {{0xa3ab4ba0,0xf1aa4050,0x6aa45866,0x3ce04584}}, // [1420] _कैद_, دازه_, reif, rkiv_,
+ {{0xf4846050,0x5e57010c,0x68fc22a4,0x7ce06052}}, // تاری, _پلیس_, qird, _törm,
+ {{0x41a6a0c2,0xf2df6119,0xdb154066,0x60d60106}}, // _कैंस, _tuân_, žbác, _koym,
+ {{0xe89480ff,0x4fea85a8,0x6d408bee,0x39490048}}, // іаль, лмен_, _qima, _yuas_,
+ {{0x7ceb20e0,0x290680e9,0x39490286,0xceb4403c}}, // _fürd, rnoa_, _xuas_, mdə_,
+ {{0x83fce29c,0x3d11e064,0xef1f2121,0x6d5af03d}}, // sađi, ठकों_, ltür_, ypta,
+ {{0x6d408fcf,0x2167a1e3,0x752291d7,0x629c7867}}, // _tima, бији_, ploz, _ibro,
+ {{0x67244f10,0x973cc4bb,0xa3c9a4da,0x63baa742}}, // nlij, _opće, _ऊपर_, _vrtn,
+ {{0x3ea4d868,0x63a9d869,0x6f18b86a,0x2d804071}}, // remt_, gwen, povc, nrie_,
+ {{0xa6db4069,0x628d013a,0x2ca681a4,0x2d80586b}}, // _miða, _scao, leod_, irie_,
+ {{0x25a00426,0x5335065a,0x3991e3cb,0x3cf060c2}}, // _apil_, жент, gás_, _इतने_,
+ {{0x63a9c4fd,0x68e2986c,0xa193e71b,0x612da069}}, // bwen, nkod, чаюч, rúle,
+ {{0x644fe064,0x2ca5e171,0x254ce04a,0x6d41a0bb}}, // ścia, beld_, _tělo_, _yila,
+ {{0x7afe600e,0x3ea5e018,0x3f85f86d,0x61054013}}, // jipt, celt_, bulu_, mėli,
+ {{0xb5fdd86e,0x25a9517f,0xf3f980ba,0x67244585}}, // vaši, wwal_, _veţi_, flij,
+ {{0x25a9586f,0x25ecc028,0x67245870,0x7d1ae0bb}}, // twal_, आईपी_, glij, lots,
+ {{0xa49b0017,0x2ca68046,0x6bda00e0,0x09e1604b}}, // [1430] riòd, deod_, _نوٹس_, पन्य,
+ {{0x6e22cf7b,0x7d1af871,0xdb09f872,0xb33cc1f6}}, // _myob, nots, rveç, _isħu,
+ {{0x69de738e,0xb5fb4049,0xfb84603b,0x2fc76067}}, // gype, _meái, зычн, ãng_,
+ {{0x7d1af873,0x0cab467b,0x7ae28037,0xddc3a06e}}, // hots, _стои_, gkot, janš,
+ {{0x6f198064,0xb5fdc6a6,0x7ae8000e,0x28c96077}}, // rowc, paši, ëjti, रादि,
+ {{0x83fce490,0x61054013,0xb579b0c4,0xb87b0a7d}}, // lađu, dėli, ащих_, chív,
+ {{0x23608018,0x28daa2b4,0x3ce900ff,0x29095662}}, // ģija_, _बकरि, _olav_, onaa_,
+ {{0x83fce0e4,0x63a9d874,0x3ea5e105,0xfbdf01ae}}, // nađu, twen, welt_, ncês_,
+ {{0x63ad81ae,0xe5c78312,0x6e2d049e,0x3f85e174}}, // çand, _усво, _nzab, wulu_,
+ {{0x63a9d875,0x237fc058,0x5187b3f9,0x7c22cc57}}, // rwen, rruj_, _гуда, _dyor,
+ {{0xed59850e,0x628f414a,0xeaaec043,0x3255d876}}, // _drže_, _occo, رٹی_, _двер,
+ {{0x8234c050,0x291dc00f,0x2d85f877,0x6d42d878}}, // _کرما, _ukwa_, rule_, _gioa,
+ {{0x6d4ae057,0xfc3f4a60,0x7d09d406,0x7afb82dc}}, // _eufa, _maís_, mnes, _mmut,
+ {{0x3943662c,0xdb1ac1ab,0x6f0d1879,0x2d85f87a}}, // _nijs_, _artè, _ejac, pule_,
+ {{0xceb4403c,0xfaff031d,0x7afb8b01,0x3f87b0a3}}, // tdə_, bië_, _omut, nunu_,
+ {{0x7c3eed3f,0x7c2bed6d,0x2d80587b,0x3f80587c}}, // _úpra, đorđ, trie_, triu_,
+ {{0x1636c00b,0x2d87b87d,0x2e4ec088,0x3f87b87e}}, // [1440] ַנער_, hune_, _kọrọ_, hunu_,
+ {{0x2ea6c8ae,0x3f87b87f,0x2d87b880,0x7524462f}}, // _खस्त, kunu_, kune_, sliz,
+ {{0x2ca7a1cf,0x68fe605d,0x389b200b,0x7d1bd881}}, // dend_, ripd, _דיינ, kous,
+ {{0x2d805882,0x644760dd,0x6d59d883,0xdce4629f}}, // prie_, _egji, _otwa, ksić,
+ {{0xa5bb40e0,0x5e58a052,0x6f09c530,0x3ea7b884}}, // _szóv, биля_, dnec, fent_,
+ {{0x6f1c28b6,0x394a7607,0x1c1dc4ef,0xa3bfc4e6}}, // norc, _tubs_, _पड़ल_, ुमन_,
+ {{0x2d87b885,0xe80100c2,0x7c22c0c7,0xab5b4046}}, // gune_, _लेना_, _syor, _prüg,
+ {{0xdce9e06a,0x27e04054,0x6aa72098,0x7c240088}}, // dređ, oyin_, zejf, _oyir,
+ {{0xb87b4069,0x68e45886,0x7ae44108,0x6d4af819}}, // _slík, ikid, ikit, _sufa,
+ {{0x6b829104,0x63ad9887,0xaefb61e9,0x6d440090}}, // mrog, çane, _awùj, _aiia,
+ {{0xf6536095,0xd6dac050,0x7c241888,0x7c23e1cd}}, // וצת_, _پوشش_, _ayir, _cynr,
+ {{0x70daa69e,0x7d1af889,0x39944052,0xfc46604a}}, // _बकुल, sots, käs_, šího_,
+ {{0x26c6c054,0x7b1fa1de,0x7f43e0b8,0x80ada1a6}}, // _inoo_, pêut, _finq, চার্,
+ {{0xdb00a105,0x7ce6188a,0x050b007c,0x6b828005}}, // _ermö, _fóro, রতের_, irog,
+ {{0x628f4071,0x2732a0ba,0xdb0d61de,0xdb1ad88b}}, // _pcco, mând_, lvaç, _arté,
+ {{0x2ca7b04d,0x3f816066,0x6d5d0469,0x290a2025}}, // zend_, vrhu_, ppsa, anba_,
+ {{0x44248187,0x29094054,0x3d94516c,0xdb1c800e}}, // [1450] _nym_, rnaa_, митр, _errë,
+ {{0x7d1bc2b2,0xd25b6d94,0x7d00d518,0x7d0dc06f}}, // zous, рца_, hims, časi,
+ {{0x6d4d0939,0x6d452c57,0xb4e720c2,0xa5bb4009}}, // _huaa, _iiha, पसी_, _azót,
+ {{0x28c964e6,0xd126c8b8,0x394362be,0xaac974e5}}, // राहि, _سم_, _wijs_, राहक,
+ {{0x4993454f,0xa3b92914,0x6d45295f,0xa3a78949}}, // _زیار, चिव_, _kiha, تحان,
+ {{0x05962050,0xe8f8a19d,0xdcfbe0a9,0xf548c067}}, // _بایگ, олі_, truč, _cụm_,
+ {{0x69dce042,0x68e9848d,0x7ae980f7,0x612da005}}, // ørel, _tled, _tlet, gúla,
+ {{0x2d820042,0x6d4bcc05,0x3f87a983,0x53348d24}}, // yrke_, _puga, runu_, _жест,
+ {{0x3f87a3df,0xe81bb88c,0x26d940d1,0xb5fdc00a}}, // sunu_, _पुरा_, _moso_, jašt,
+ {{0x2d87b5ed,0x386d8e47,0x290b188d,0x61318156}}, // pune_, _heer_, enca_, såld,
+ {{0xaca34079,0x7d084066,0xdce9ecb1,0x66df000e}}, // _awọd, édsk, uređ, _lëku,
+ {{0x7aeae305,0x387a2143,0x7cf14584,0x291d8063}}, // _elft, _sdpr_, _hårf, nowa_,
+ {{0x6d4bd88e,0x2e4ec254,0x7aebc018,0x21332119}}, // _uuga, _tọrọ_, _ilgt, _bhxh_,
+ {{0x7cf0210a,0xeab24043,0xa879e00b,0x6aa9d88f}}, // _märg, کٹر_, _האַר, leef,
+ {{0x7e7b829f,0x20132057,0xb8e42bde,0x7e6d0005}}, // _odup, _cxxi_, _एफ_, _feap,
+ {{0x2732a12a,0x2bb0e026,0xa3bd8026,0xf1b2a00b}}, // mâne_, _जनता, ेमा_, עסן_,
+ {{0x3b864056,0xe5a355ea,0x394d9890,0xbebb03ac}}, // [1460] ялаг, нити, _nues_, ymëz,
+ {{0x8b236052,0x1dbdb3aa,0x2fc00143,0x7cea40c4}}, // едуе, ्मात, _irig_, _dýra,
+ {{0x69cb4d4b,0x63ad54b7,0x7c25a0cb,0x70a9e026}}, // äger, awan, ührl, _कसैल,
+ {{0x6125d3c9,0xd9464406,0x6d453891,0x395fc07d}}, // bóli, _неми, _ziha, kpus_,
+ {{0x26da34d7,0x2d8a2c75,0x21269074,0xa6db40c4}}, // _kopo_, lube_, rloh_, _miðl,
+ {{0x7cfec03b,0x9966b892,0xe45f0156,0x63a4046d}}, // tūro, птил, njör_, _mpin,
+ {{0x2d8a3893,0x61e1f831,0x2ca00012,0x03a3695a}}, // nube_, jyll, _obid_, _пиро,
+ {{0x3f508016,0x29021894,0x74c47895,0x2d983896}}, // _tàu_, jika_, राकृ, itre_,
+ {{0x3f6781e1,0x2fc00286,0xddc1a35d,0x8c53c049}}, // _нито_, _nrig_, _velš, رئيا,
+ {{0xdb0d601f,0x61318042,0x7c26405d,0x7648a013}}, // rvaç, råle, _mykr, _ugdy,
+ {{0x7cefa3d1,0x6d4e7897,0x10a6c6fc,0xbc6ac050}}, // _sørg, _juba, _нижн, _همان_,
+ {{0x9f4e0066,0x386d8025,0x6e9f2026,0xdb0f6017}}, // žným_, _xeer_, ख्नु, _escè,
+ {{0x254ce026,0x2d78606e,0x61334106,0x37aea07c}}, // _měli_, hče_, nçle, _কপির,
+ {{0x96d82216,0x26da21ae,0x7d1e6098,0x7d029898}}, // नॉलॉ, _copo_, jops, hios,
+ {{0x4426cc12,0x06fda026,0x6b844cf9,0x765520c2}}, // _hyo_, _říká_, erig, rczy,
+ {{0x29021899,0x63ad509d,0x2fc0189a,0x79976048}}, // cika_, twan, _frig_, vtxw,
+ {{0x57c68010,0x2ca946d7,0x9ed969af,0x2b46c012}}, // [1470] लमोह, read_, омат_, _mioc_,
+ {{0x399560ac,0xf1b0eca9,0x386d8aaa,0xa2c465c5}}, // tås_, _जनान, _seer_, राग्,
+ {{0x7522d89b,0x6125c07b,0x7d02989c,0x5faec0c2}}, // _okoz, sóli, fios, _जैसल,
+ {{0x4426d89d,0x6d4647c4,0x7afd4105,0x29016da2}}, // _oyo_, _gika, _umst, siha_,
+ {{0x3f8b0121,0x399566dd,0xab5b4105,0x6b844017}}, // nucu_, sås_, _drüc, crig,
+ {{0x6d5d4265,0xd246e13a,0x68ed0cbc,0x7aed0507}}, // _itsa, _لن_, _llad, _llat,
+ {{0x6721a42e,0x7522c098,0x2902189e,0xbcfb61e9}}, // _vklj, _bkoz, yika_, _atég,
+ {{0x6f02832a,0x6d4f589f,0xdce9a320,0x7e7b98a0}}, // cioc, _kuca, _oveć, _udup,
+ {{0x7bc08098,0x78a9c035,0x442b8067,0x629c2bca}}, // _grmu, reev, _ác_, yfro,
+ {{0xceb44497,0x290218a1,0x3f52c016,0x6f0d58a2}}, // nlər_, wika_, _dâu_, hnac,
+ {{0xf549e016,0x3dc12022,0x9eaa0c6c,0xb905e861}}, // _tấm_, _nrhw_, овна_, _नव_,
+ {{0xb5fb08cc,0x141aa00b,0xdced6315,0x6f0d58a3}}, // rbán, _וועב, yrağ, jnac,
+ {{0x26da3062,0xaa7b005c,0x7aed0064,0x6d5d4286}}, // _popo_, ckým, _dlat, _ntsa,
+ {{0x6d4658a4,0x2d8a259d,0x69c1a0e4,0xc172c095}}, // _sika, tube_, _krle, _יחד_,
+ {{0x68ed18a5,0xceb4403c,0x2d9916a8,0xbb432d68}}, // _flad, dlər_, atse_, терк,
+ {{0x3947e069,0x3ea007a0,0x2d8a20b1,0x290dc582}}, // _hins_, _ubit_, rube_, nnea_,
+ {{0x6d476d3d,0xe57a58a6,0x63a40296,0x752418a7}}, // [1480] _bija, юза_, _upin, _ikiz,
+ {{0x6e2b4669,0x4427f8a8,0x6d476e99,0x443216f0}}, // ügba, _kyn_, _cija, _izy_,
+ {{0x21294820,0x6d5d4534,0x7c2640c2,0xeb9770e1}}, // blah_, _etsa, _wykr, дих_,
+ {{0xfce38501,0x644bc050,0x2b47e1cd,0x1867860f}}, // кохо, _nggi, _linc_, даци_,
+ {{0x7d044e38,0x2458acde,0x25ad6361,0x6729d8a9}}, // miis, паль_, _ćele_, dlej,
+ {{0x6459d2fb,0x4426c00f,0x3946d8aa,0x7d0290aa}}, // _afwi, _ryo_, _sios_, sios,
+ {{0x27362065,0x29000052,0x69ca2057,0x3ce6875a}}, // mænd_, _omia_, _áfed, pkov_,
+ {{0xf9932095,0x2cb878ab,0x60db80d1,0x7cf158ac}}, // דרה_, _hard_, _goum, _gård,
+ {{0x9486e0ba,0x2cb871e0,0x69c081fc,0xb7bec07c}}, // мынд, _kard_, _urme, েন্ট,
+ {{0x65c6a1e1,0x5436a566,0xd4c6a13a,0x6436a13a}}, // _обза, _قرار, _تغري, _قراء,
+ {{0xddc40013,0xe8df8067,0x7529d8ad,0x27358271}}, // _reiš, _hiền_, blez, bånd_,
+ {{0x2d8b18ae,0x7ce06156,0x9663c7f7,0x2129405d}}, // tuce_, _körs, _акце, xlah_,
+ {{0x7ce3410a,0x4426c631,0x7ae340ba,0x60dc6155}}, // _kõrv, _uyo_, _înti, _oorm,
+ {{0x3f8b00b0,0x2d8b18af,0x61120018,0x7bc2c229}}, // rucu_, ruce_, rāla, _irou,
+ {{0x7d044054,0xdd9420db,0xf20424a0,0x19942bcd}}, // fiis, кары, вято, каря,
+ {{0x7bc2c04a,0xe8d92125,0x7aed0046,0x403502f0}}, // _krou, _itọ_, _ulat, _пейс,
+ {{0x3f8ce17a,0xd838e35d,0x6d5863ab,0xde8f8067}}, // [1490] ludu_, reču_, ívan, hịch_,
+ {{0x39490939,0x60db8bf9,0x0dcb21e1,0x798b800f}}, // _hias_, _soum, _думи_, vugw,
+ {{0xceb2200b,0x60db8653,0x305c8088,0xfbd080eb}}, // _גיי_, _poum, _ịlụ_, یته_,
+ {{0x80cd604b,0xb87b4019,0xceb4403c,0x7e7d422e}}, // साहे, _clít, slər_, _udsp,
+ {{0x6d5d43e0,0x60db8005,0xdbd6c2df,0x6130697a}}, // _utsa, _voum, _jääm, mäln,
+ {{0x7c28a31d,0x6aa2c057,0x98c4604e,0x6aad5488}}, // _bydr, _abof, _иссл, leaf,
+ {{0x8c3ce106,0xa3b38914,0x25a6c733,0x7c28a0fd}}, // beği, टिङ_, _apol_, _cydr,
+ {{0x6f03a4f2,0xb5fd8205,0x3f8698b0,0x6d48b8b1}}, // sinc, _bešt, brou_, _eida,
+ {{0x673bc7ac,0x6729c065,0xb5fd8320,0x78b8e614}}, // rmuj, rlej, _pešu, _bavv,
+ {{0xddc418b2,0x1f65ef20,0x69c2d658,0x4427eaaa}}, // _afiş, нком, _droe, _pyn_,
+ {{0x26dce1e2,0xddc640a9,0x2d8ce10a,0xead4a14a}}, // _dovo_, _mekš, gude_, _шофь,
+ {{0xd756c10c,0xa107c050,0x23cb0021,0x394fd8b3}}, // بلیت_, _غذاه, ामंद, _tugs_,
+ {{0x13a7c050,0x6909200a,0x9986e16d,0x6b8d42a8}}, // _آنتی_, džeb, čiše_, juag,
+ {{0x6134e5df,0x2b5f805d,0xddc98143,0x60dd458f}}, // tàle, _mtuc_, _odež, _aosm,
+ {{0x6da321c7,0x3eb86271,0x44291669,0x13c84914}}, // лира, _rart_, _eya_, रम्भ,
+ {{0x395e2041,0x69c0a98a,0x69dca5df,0x78bbc046}}, // īts_, विती, _àree, nduv,
+ {{0x656d00e2,0x27188e9d,0xd5a404ef,0x799bc068}}, // [14a0] _gwah, nčne_, _ओहिज, ntuw,
+ {{0x3c6623b7,0x8c1b0095,0x3eadd8b4,0x7aef4071}}, // _яког, _מופי, heet_, _elct,
+ {{0xd00f613a,0x06868926,0x2904d8b5,0xc486851b}}, // ملك_, еген, yima_, елек,
+ {{0xdb1ba057,0x2905f8b6,0x39490048,0x80ca407c}}, // _druí, hila_, _xias_, রসঙ্,
+ {{0xaca46088,0xda638450,0x3ea04143,0x4429005f}}, // _azụr, увчи, mfit_, _xya_,
+ {{0xa8248050,0x3f86832a,0xf548e119,0xdd90a33e}}, // _یکشن, rrou_, _tụi_, لوت_,
+ {{0x3eadc3b0,0x6720c00e,0x78b8e024,0x57c9e290}}, // feet_, tomj, _ravv, िमोह,
+ {{0x2cace0e2,0xbb43882b,0x81af807c,0x78b8f8b7}}, // wedd_, _беск, _ওপর_, _savv,
+ {{0x3eace097,0x3f8dc5df,0x2d87b8b8,0xff06e03b}}, // tedt_, gueu_, arne_, еянн,
+ {{0x27320026,0x3f87a29c,0x13f3c052,0x644de315}}, // vána_, brnu_, узья, _şair,
+ {{0x69c3e143,0x61360066,0x23674098,0x3eace654}}, // _crne, zále, ćnji_, redt_,
+ {{0x26dea35f,0x39490048,0x3eaeb8b9,0x290420d1}}, // _hoto_, _qias_, left_, èman_,
+ {{0x48e6eacb,0x69c3f8ba,0xe8df8016,0xceb3800b}}, // _подв, _erne, _hiển_, ניש_,
+ {{0x24800786,0x2fc048ef,0x6aa4011d,0x78a40035}}, // _edim_, dvig_, _ebif, _ebiv,
+ {{0x3eb94095,0x8ccdea2a,0x69c4098d,0xab68e1f6}}, // _past_, तारो, _erie, _beżż,
+ {{0x78bab8bb,0xb063a06f,0x644d0400,0x61e6052d}}, // _latv, _zväč, _ugai, bykl,
+ {{0x27e680d1,0x7bc6804d,0x612b78bc,0x656d0668}}, // [14b0] jyon_, _škun, lüli, _uwah,
+ {{0x6abd0b48,0x799d0022,0x3eaea143,0x2ee94b13}}, // ldsf, mtsw, jeft_, skaf_,
+ {{0x657af8bd,0x5f95614a,0x65608153,0x7b0b6018}}, // nsth, тиет, _atmh, kļuv,
+ {{0xc01c2016,0x6d4af8be,0x629560c2,0x7bcdc057}}, // _lược_, _lifa, _wczo, _áaug,
+ {{0x69d863d1,0x752d58bf,0xe9ffe119,0x6b8d58c0}}, // øver, mlaz, _khải_, quag,
+ {{0x3999c0d1,0xfaa5acd0,0x2126c1e7,0x8698a14a}}, // vès_, тако, _akoh_, етът_,
+ {{0x60cd01fb,0x5b14639d,0x3f8ea00f,0x27e685c8}}, // _onam, _смрт, gufu_, ayon_,
+ {{0x2a7f8054,0xb4ac60c5,0x9f52c066,0x7ce078c1}}, // _udub_, कड़ी_, ždým_, _hörp,
+ {{0x3eadd8c2,0x7bdce105,0xa6db40c4,0x3f87a098}}, // reet_, ärun, _viðh, srnu_,
+ {{0x2caf8200,0x61e618c3,0x3eadc831,0x6f07c56e}}, // legd_, vykl, seet_, _bèbè,
+ {{0x4ac68216,0xc01c2016,0x8ae7c0ff,0x2d82894b}}, // _रोडव, _dược_, _підл, _åker_,
+ {{0x2a720054,0xd7d14026,0xf1b26053,0x7d07c0eb}}, // _qeyb_, समाच, בסט_, _dèrè,
+ {{0x7d072768,0x7ceb2121,0x3f894022,0xc8f5e14a}}, // fijs, _türl, jrau_, _издъ,
+ {{0x7d0618c4,0x63a98167,0x6b89d8bd,0x6f0da049}}, // siks, _mpen, mreg, éach,
+ {{0x7c9642f0,0x7cf5c057,0xc692a0be,0xeb972bd2}}, // крац, _cárg, יאם_, кир_,
+ {{0x3f84229c,0x6d4af862,0x799d0022,0xa2c18026}}, // čmu_, _zifa, btsw, _रोल्,
+ {{0x4127467b,0x7d04139d,0x3f8958c5,0x399aa00e}}, // [14c0] тото_, _imis, grau_, zës_,
+ {{0x98bea03b,0xb4636799,0xa3d5e0c2,0xee374fb5}}, // _kitą_, икул, _सपा_, тну_,
+ {{0x212dc0c7,0xceb2e095,0x127b200b,0xaefb78c6}}, // kleh_, שיב_, _קאטע, _awùs,
+ {{0x13e6007c,0x2739018c,0xe8034077,0x2167678e}}, // _পেয়, réng_, _रेवा_, тици_,
+ {{0x29068265,0x6f040143,0x3f8f835e,0xd9978009}}, // rioa_, _mmic, gugu_, نخوا_,
+ {{0x29068265,0x27e698c7,0x7c22417a,0x7d02c425}}, // sioa_, ryon_, żors, _smos,
+ {{0x39404013,0x66e3a01b,0x29068108,0x6b89cd53}}, // omis_, роја, pioa_, ereg,
+ {{0x8c1b61a9,0x64a6841b,0xb9e3a0ff,0x2d8ea0cb}}, // _חולי, _шаба, ріши, rufe_,
+ {{0x290481d7,0x0b46ae31,0x399aa0dd,0x78a28274}}, // _imma_, _анан, sës_, mfov,
+ {{0x6723a0ba,0x752d50e5,0x6ef4a090,0x6d4bd3f0}}, // conj, zlaz, _bàbe, _eiga,
+ {{0x6d4bd8c8,0x6d5a20c4,0xdddaa009,0x75244534}}, // _figa, ítal, _tető, koiz,
+ {{0x3ea9a29c,0x645bd8c9,0xcf936076,0x4bfba557}}, // đate_, ccui, סטר_, _קלאס,
+ {{0x6d586066,0x63a9805d,0x394b4017,0x213a2090}}, // ívaj, _xpen, _rics_, _bhph_,
+ {{0x48bd607c,0x6018ed15,0x7bc52133,0xd00fc0eb}}, // _আক্র, _поля_, _prhu, _بله_,
+ {{0x35e4203b,0x6d4bd8ca,0x2c5851b0,0x6f0730c5}}, // ацтв, _yiga, _ọda_, rijc,
+ {{0x7d0738cb,0x8c6441fc,0x3f57617a,0x6d4bd8cc}}, // sijs, итуд, _dħul_, _xiga,
+ {{0xf54a0016,0xab5d8227,0xdddc6106,0x291c6143}}, // [14d0] _cấu_, _arżn, _perş, čvan_,
+ {{0x752d58cd,0x7cf5c0e0,0x6abc78ce,0x399c6049}}, // slaz, _tárg, _carf, fís_,
+ {{0x613600e0,0x78bb8004,0x8c3dd45a,0x2d58a0fa}}, // lála, _pauv, leşt, _née_,
+ {{0x249860ae,0x506440ff,0x3ebce3d1,0x6ef5ca7d}}, // _ecrm_, _втра, _lavt_, _kábe,
+ {{0x613600e0,0x78bb98cf,0x07a58501,0x6723b8d0}}, // nála, _vauv, уалн, tonj,
+ {{0x3f8f98d1,0xa3c04c87,0x8c3dc12a,0x2d58ae2d}}, // rugu_, ंटर_, ieşt, _bée_,
+ {{0x442ca1cd,0x27e958d2,0x10a5c14a,0xc878e181}}, // _cyd_, lyan_, гийн, dağ_,
+ {{0x27320026,0xe1ff0009,0x26cd804d,0xe3b0a047}}, // váno_, tból_, _uneo_, تره_,
+ {{0x2d9ef8d3,0xf771c19a,0x6abc60a2,0x442ca827}}, // ette_, حاب_, _yarf, _eyd_,
+ {{0x6d4bc050,0xe9d9e2d8,0x291248a7,0x2d912160}}, // _wiga, еко_, znya_, kuze_,
+ {{0x6e2d18d4,0x6605f497,0x442ca1cd,0x6d55749a}}, // _ayab, упна, _gyd_, _juza,
+ {{0x442d8b01,0x291244b4,0x9407603c,0x68ed58d5}}, // _kye_, xnya_, ünə_, jkad,
+ {{0x61360db3,0x6e2d000f,0x7ae1a106,0x7cf260ff}}, // gála, _cyab, _kolt, _læra,
+ {{0xe8d0898a,0x2cbfc10a,0x68ed4025,0x7c2d18d6}}, // _सच्च, ldud_, ekad, _dyar,
+ {{0x75298939,0x68ed4143,0x7d0402a4,0x2739e0fa}}, // _okez, fkad, _tmis, gène_,
+ {{0xa3b7c2b4,0xf992e087,0x3ebfd8d7,0xa91de12d}}, // _चना_, _הרה_, ndut_, ružu,
+ {{0x7c2d18d8,0x3f9fc04e,0x2b40542f,0x69c778d9}}, // [14e0] _gyar, ntuu_, rmic_, _arje,
+ {{0x2d91295b,0x7cf14277,0x7bcb01ae,0x7d09d8da}}, // buze_, _våra, _água, hies,
+ {{0x3f9fc04e,0xdb0d227f,0xea76e00b,0x409b200b}}, // htuu_, _grañ, רגער_, _אביס,
+ {{0x6d5562e2,0x29124058,0x27e95122,0x61fbc037}}, // _duza, qnya_, byan_, kzul,
+ {{0x66e6a2fe,0x3f58a069,0x7bc65016,0x442cb8db}}, // лода, _séu_, _urku, _syd_,
+ {{0x6d5605d2,0x69c458dc,0xa2a1c567,0x672982f4}}, // _muya, mvie, क्स्, _ekej,
+ {{0x2d9244cd,0x68e1b8dd,0xa6db4765,0x69c28110}}, // luye_, _dold, _viðv, svoe,
+ {{0xc448c050,0x28ab8029,0x3cedc022,0x717680eb}}, // نیان_, _टॉपि, gkev_, _مهرا,
+ {{0x7cf5d8de,0x2d92400f,0x6130610a,0x3f924106}}, // _bárb, nuye_, välj, nuyu_,
+ {{0x2d8b00e4,0x7aed4265,0x8c3dc12a,0x2126807d}}, // vrce_, zkat, veşt, looh_,
+ {{0x4034e312,0x6ef5c03e,0x59b6c0a8,0x23608018}}, // ренс, _sábe, _अनवर, ģiju_,
+ {{0x28d2e437,0x2ca6d8df,0x61e5a105,0x21268098}}, // ताहि, _ubod_, ähle, nooh_,
+ {{0x588723fc,0x628401cd,0x7c2e78e0,0x6abe200f}}, // лыва, _ddio, _lybr, _bapf,
+ {{0xac97e13a,0x6d4e64fd,0x290a38e1,0x3f912108}}, // _منها_, _niba, giba_, tuzu_,
+ {{0x5186e0fb,0xd13b4df6,0x7e57a0be,0xe801010a}}, // _сула, еха_, רסלב_, _लइका_,
+ {{0x5577a00b,0x61360009,0xa3c24870,0x8c3dd49c}}, // טעלן_, láln, ्मं_, peşt,
+ {{0x7ae2c6eb,0x290a38e2,0x65954967,0x3ea905c8}}, // [14f0] _moot, biba_, разу, _ibat_,
+ {{0x63ad18e3,0x68e2c4ef,0x27e958e4,0x442d98e5}}, // _ipan, _lood, ryan_, _rye_,
+ {{0x2bc30010,0x53354d59,0x62852011,0x66282089}}, // शिवा, _вент, _idho, čské,
+ {{0xd5b860fb,0x43954056,0xdd9544a7,0x2d8ce049}}, // ысу_, _камс, _камы, irde_,
+ {{0xc879c3ed,0x960da0a8,0x67256052,0x3cedc022}}, // vaş_, िप्ट_, pohj, vkev_,
+ {{0xc05ba0ff,0x69c3a037,0x6b8d4271,0xdd01e06f}}, // хід_, tvne, mrag, _ľuďo,
+ {{0x3ebea31b,0x25aca0ae,0xa2d80026,0xd2296049}}, // _datt_, _cpdl_, याम्, _شكره_,
+ {{0x2d936057,0x7ae1b8e6,0xa91de143,0xb385cf0e}}, // muxe_, _wolt, mrži, алил,
+ {{0x98a043ce,0x62852ea2,0x7ae1a576,0xe0d6c1ba}}, // slić_, _odho, _tolt, рвы_,
+ {{0x613603e4,0xe8df8067,0x3ebea2a4,0x2cbf9406}}, // gáln, _biện_, _gatt_, _haud_,
+ {{0x6da5e471,0xe80c4a30,0xe5a5eb92,0x7d09d8e7}}, // шина, _सेना_, шини, pies,
+ {{0x6b8d58e8,0x3325e17b,0xdb09e03e,0x7cf02262}}, // krag, solx_, rveñ, _päro,
+ {{0x9967cb2d,0x3ebea17b,0x7ceb20cb,0xe8eee26f}}, // _стел, _yatt_, _züri, _дл_,
+ {{0x80a1d073,0x2d92400f,0x7a4020e0,0x394fc04d}}, // क्रे, vuye_, játé, _higs_,
+ {{0xd62a38e9,0x6d57203e,0x6b8d57e7,0x7cefa14f}}, // тове_, _cuxa, erag, _rørl,
+ {{0x290a335e,0x225fccd4,0x6d4e62e8,0x2d92400f}}, // siba_, ncuk_, _siba, tuye_,
+ {{0xa2b8c0ec,0x8be9a07c,0x7cf4b8ea,0x798d4aaa}}, // [1500] ्यत्, _কেমন_, _làra, graw,
+ {{0x577b000b,0x629b807b,0x7bc440ba,0x7e7bd8eb}}, // _שטיצ, _acuo, rviu, haup,
+ {{0x4ada051d,0x6d4e6296,0x63ad0143,0x7ae40052}}, // _बचाव, _viba, _zpan, _koit,
+ {{0xa3ba07f1,0x7b668056,0x26d17477,0x2bb6c295}}, // _घना_, атке, _enzo_, _अनला,
+ {{0xb146a0fb,0x2a6981af,0xa6db4069,0xe3b280a4}}, // андл, ñaba_, _viðt, ترا_,
+ {{0x7e7c2193,0x6d4458ec,0x68e406f1,0x6d428265}}, // narp, lmia, _loid, smoa,
+ {{0x7d0aa069,0x26d218ed,0x3af8c04a,0x612b78ee}}, // rifs, _onyo_, _lépe_, rült,
+ {{0x1fa6ca47,0x68e3f8ef,0x26d20b01,0x3f8dc41a}}, // арог, _aond, _nnyo_, freu_,
+ {{0x7ae3f8f0,0xdfd0c13a,0x7e7ae0b8,0xfbc940a3}}, // _bont, سيد_, tatp, रियम,
+ {{0x387878f1,0xb4d52292,0x5f94e471,0xa2cdf1c9}}, // _jerr_, _सचे_, биот, ताग्,
+ {{0x387860dd,0x2718842e,0x26c0003e,0x80b8cff1}}, // _merr_, nčno_, _baio_, ्यदे,
+ {{0xab5b4669,0xe8df8081,0x6f164064,0x442ee31d}}, // _früh, _viện_, jnyc, _vyf_,
+ {{0x7e7ae133,0x442ee1cd,0x81c7a018,0x4bd7c0eb}}, // patp, _wyf_, rtēš, _نبود_,
+ {{0x7e7c28a5,0x2fc918f2,0xd943eae2,0x7cf02241}}, // garp, _vrag_, _дечи, _härj,
+ {{0xbbb6c578,0x60c298f3,0x39586153,0x68f618f4}}, // _अनुक, kdom, _nurs_, _flyd,
+ {{0x629c78f5,0x248d20b0,0x8c1ba3c8,0x2fcd6012}}, // _acro, şem_, דולי, _čeg_,
+ {{0x7e7d18f6,0x63a298f7,0x38786ccc,0x6b55a013}}, // [1510] masp, jton, _berr_, _išge,
+ {{0xf54a0016,0x63a29518,0xf8bf4050,0x7e7d18f8}}, // _hấp_, dton, _waé_, lasp,
+ {{0x2455824d,0x8af9a03c,0x6519463b,0xddc2c6ca}}, // مناس, nləş, _مقدس_, _neož,
+ {{0x63bb8037,0x9c7ca07f,0x6728e361,0x2b586197}}, // _psun, _roča, hodj, _durc_,
+ {{0x63a28b5e,0x7f3a4095,0x6d58f8f9,0x752d0167}}, // gton, _תערו, _luva, _mkaz,
+ {{0x61eb81cd,0x290cf8fa,0x7d0b8a13,0x6d5dc07b}}, // rygl, jida_, sigs, ísam,
+ {{0x7db4613a,0x6728e1e4,0x60c08024,0x60c298fb}}, // _وصفا, dodj, _famm, bdom,
+ {{0x613066eb,0x38786098,0x2bb0e7af,0xce948425}}, // väli, _zerr_, _जनजा, _масъ,
+ {{0xc6928053,0x26c000fc,0x3878605d,0x5ac6c20e}}, // _מאן_, _raio_, _yerr_, слым_,
+ {{0x5ea5e711,0x7cefa042,0x9c7ca38a,0x6d444064}}, // حمدل, _mørk, _toča, zmia,
+ {{0x6d4441cd,0x60c1a057,0x2721c066,0x7cf02156}}, // ymia, _ialm, zóny_, _färj,
+ {{0x81ae407c,0x61ed400d,0x8a1480e0,0x61322069}}, // কিত_, kyal, _اظہا, mæli,
+ {{0x78a92199,0x6ef5c057,0x26c138fc,0x6f098088}}, // đevn, _gába, _baho_, _mmec,
+ {{0x2a7d8ca1,0x612da005,0x6113c0ba,0x6f1b8320}}, // hawb_, cúlt, dăli, _ljuc,
+ {{0x7ae418fd,0xd94617ef,0x68e4010a,0xdbdc405c}}, // _toit, _леки, _toid, ršíc,
+ {{0x7d0d58fe,0x2a787773,0x4bda4052,0xfb86e555}}, // gias, _serb_, льзя_, _ادوی,
+ {{0x78b64098,0x0686679a,0x82970095,0x66eee857}}, // [1520] _čavč, йган, _הדפס_, _चकमक_,
+ {{0x23b7a010,0xdfcf413a,0xdb1ac5df,0xc8d260a8}}, // _आनंद, عيل_, _estè, साइट,
+ {{0xf536e053,0x0b62c125,0xcc7ae00b,0x2a572076}}, // צטער_, _mụọr, רװער, _לבין_,
+ {{0x6d45628f,0xe9f902f4,0x2fc680e4,0x53a6464d}}, // amha, _abẹ_, tvog_, _ламб,
+ {{0x09e0607c,0x613618ff,0x213f818c,0x60c09900}}, // _যেখা, váll, _dhuh_, _vamm,
+ {{0x60c0811d,0x6d456420,0xf8bf40eb,0xe762d1b0}}, // _wamm, cmha, _akéh_, _nọọs,
+ {{0x81e6607c,0xceb32095,0xdbf1c04a,0x63a45901}}, // _বের_, פיה_, _dřív, ltin,
+ {{0x5f068099,0x7528e0c2,0x7c3aa06e,0x92d8a07c}}, // _узба, wodz, _vztr, াসে_,
+ {{0x69c72579,0x2000412a,0x60c44e88,0xac18c79d}}, // zvje, nzii_, idim, ропу_,
+ {{0x2bc945c5,0x610f6013,0x3ae74046,0x7d09802e}}, // रिता, _gėlė, _lõpp_, _zmes,
+ {{0xda6fc0db,0x290ce4a8,0xd8db600b,0x7e7d1902}}, // _ня_, rida_, אקיר, tasp,
+ {{0xdc3ba00b,0x7e629903,0xa91df904,0x39400153}}, // _געבר, ncop, držu, _chis_,
+ {{0x6f0d5905,0x290a6264,0xa1590917,0x5162c031}}, // viac, _omba_, рагу_, _fọọm,
+ {{0x6d4601f3,0xd6d7207c,0x55580d82,0xa99bc076}}, // amka, হস্প, саря_, _עבור,
+ {{0x26c32258,0x7d0d5906,0x6f0d5907,0xff294049}}, // rdjo_, tias, tiac, لكسي_,
+ {{0x798280c2,0x443ce012,0x7cf5c049,0x8c0061a6}}, // ksow, _izv_, _lárn, ্থান_,
+ {{0x7ae5203a,0x60c2c296,0x2d83600a,0x6eede0cc}}, // [1530] _toht, _maom, _svje_, _súbi,
+ {{0x7529c167,0x212b1908,0x4e78668c,0x3b0dc037}}, // zoez, loch_, محمد_, zieq_,
+ {{0x80ca88ae,0x25a4d909,0x7d0d453f,0x96eb84e4}}, // _सोहे, html_, pias, льда_,
+ {{0x248900ae,0x41a9c10a,0xfc3f007b,0x61ed4106}}, // _jdam_, _कहलस, nfío_, pyal,
+ {{0xdbf1c04a,0x2919190a,0x3e4ee03c,0x3f8f990b}}, // _přív, insa_, hət_, urgu_,
+ {{0x2a7d8022,0x3e4ee5fc,0x70b3a35b,0x4b7c0053}}, // sawb_, kət_, ंजेल, נאוו,
+ {{0x6d58603a,0x6a9c2095,0x7bc7c041,0x0579a13a}}, // ívat, אשונ, ājum, جمعة_,
+ {{0xab65c7c5,0x6f65cc9d,0x6d59c0f2,0xfd4c6088}}, // овил, овиз, _puwa, _obiọ,
+ {{0x7ae76022,0x0edd0295,0x3ce5b90c,0x7c3b990d}}, // _kojt, यानड, _tolv_, _uzur,
+ {{0xd709e04e,0xe450813a,0x1309e04e,0x1fe4e07c}}, // рное_, _أضف_, рной_, _ফেইস,
+ {{0x60c5613a,0x39400286,0x6b840b48,0x273c990e}}, // idhm, _phis_, _svig, lína_,
+ {{0x2b83e03c,0x7ae7600e,0x7afd190f,0x2bba4026}}, // rıc_, _lojt, ghst, _उनला,
+ {{0x63a4490b,0x386d1910,0x6d5aa186,0x613600e0}}, // xtin, ñera_, _zuta, lálk,
+ {{0x6d5b948e,0x63be2037,0x1869e6fc,0x29191911}}, // _muua, _pspn, _таки_, ansa_,
+ {{0xddc3207a,0x3ce6c048,0x6916006e,0x6f0f0197}}, // мбри, _yoov_, jšeg, bicc,
+ {{0x61306105,0x672b8939,0x463ae00b,0x6f0f177e}}, // hält, jogj, _דעצע, cicc,
+ {{0x82fa00d0,0x656d4e87,0xe5a34bcd,0x63bbd912}}, // [1540] _مرکز_, gpah, мити, nwun,
+ {{0x80d80010,0xeb91000b,0x60c41913,0x200052ab}}, // यावे, _אָט_, _haim, rzii_,
+ {{0x656980dd,0x25bf8bae,0x3eb83914,0x68e77915}}, // _ateh, _asul_, mert_, _dojd,
+ {{0x2d983916,0x7cfcc0e0,0x290f9917,0x4c3b2095}}, // mure_, _kére, giga_, _כתיב,
+ {{0x44209918,0x39408041,0x387fc753,0x87ba8052}}, // çi_, ķis_, daur_, рует_,
+ {{0x7cfcc0e0,0x6b845919,0x3eb8391a,0x141b4095}}, // _mére, msig, nert_, _כוכב,
+ {{0x7769991b,0xc8ca127e,0x20dd0010,0xbbba410a}}, // _etex, ियाट, यामध, _उनुक,
+ {{0xa3cdf91c,0x3e4ee5fc,0xddaac0ba,0x7e7b904d}}, // रिन_, vət_, атол_, _geup,
+ {{0xd012850f,0x69cc2010,0x6d5aa068,0x629c23b0}}, // _غلط_, हिडी, _wuta, kgro,
+ {{0x26c4991d,0x9980c03b,0x97a6e6fc,0xd8f8e0ff}}, // _hamo_, _šių_, ормл, сної_,
+ {{0x60c4191e,0xb4b6a0c2,0x6ef5ca7d,0x6f0f0197}}, // _baim, ज़ी_, _kábl, ticc,
+ {{0x3f982e05,0xdb1d6017,0x46d1c0c2,0xa3ca4f30}}, // duru_, _essè, _दोपह, लटा_,
+ {{0x3eb8391f,0x6f0f121e,0xdcfb0041,0x212b1920}}, // fert_, ricc, gstā, roch_,
+ {{0x7c22c17b,0x212b1921,0xfbc9437f,0x6ef920f9}}, // _ixor, soch_, रिसम, _bìba,
+ {{0x41e6ea0e,0x2d853922,0xa8eec026,0x67f10031}}, // _філа, éle_, _जवाफ_, _bójú,
+ {{0xae1ec2b4,0x25a6836c,0x443ea04d,0x7ae8a22e}}, // _बखान_, ktol_, _mzt_, _lodt,
+ {{0x7d0d1923,0x31b7a2b4,0x6b845924,0x2bcdc118}}, // [1550] _imas, _आन्ध, gsig, ़िया,
+ {{0xd131601e,0x68e8a0fd,0x629c258f,0xfe706047}}, // رما_, _nodd, cgro, ندم_,
+ {{0x7f5b8054,0x7e7b8004,0x6cd54ef8,0x3f991925}}, // _suuq, _peup, _فقدا, lusu_,
+ {{0x6d41b926,0x60c4003e,0x7bce7927,0x6367c06f}}, // _shla, _xaim, _drbu, nčný,
+ {{0x6d41b928,0x2d99010a,0x6f0d0079,0x9f35a0ff}}, // _phla, nuse_, _mmac, педі,
+ {{0x1fd0c07c,0x7d0d1929,0x61e00156,0x6d5d5806}}, // িহাস, _lmas, ämli, _iusa,
+ {{0x3f84c018,0xd49b801b,0x0ec68857,0x7cf8404a}}, // esmu_, _врз_, _रोकड, _mírn,
+ {{0x66b4804e,0x7cf026eb,0x85bb8050,0xb065e052}}, // _обсу, _väri, _پارس_, kkää,
+ {{0xdd9181b3,0xc3344095,0xf991c13a,0x656985f9}}, // _سوا_, ווק_, ربة_, _uteh,
+ {{0x69c9d92a,0x4805e0ff,0x3d948702,0xb87b4057}}, // rvee, зпов, _цифр, _elíx,
+ {{0x2cb8392b,0x60c3e54c,0x2d99192c,0x80d80026}}, // verd_, _vanm, euse_, याले,
+ {{0x69ace877,0x443dc0c2,0x2d98392d,0xdce4207f}}, // _जहरी, _tzw_, vure_, _vtič,
+ {{0x60c406eb,0x6136192e,0x2cb82b83,0x7cf5c009}}, // _vaim, náli, terd_, _járm,
+ {{0x186726bd,0x7ae9810a,0x2d92592f,0xa0672df6}}, // зари_, _loet, vrye_, зара_,
+ {{0x2732a1fc,0x68fb9930,0x3eb820ff,0x7bcf4189}}, // mânt_, _olud, rert_, _arcu,
+ {{0x26c5a2ed,0x613d08aa,0x2d983931,0xc0e35932}}, // _lalo_, péla, rure_, носк,
+ {{0xae0c4029,0x6d5d5933,0x2fc00cc5,0x3f983934}}, // [1560] _सेशन_, _cusa, _usig_, suru_,
+ {{0x69cc210a,0x6eedf935,0x39494013,0xfd1f41e9}}, // हिदी, _túbu, amas_, _ejì_,
+ {{0x6d440223,0x6d561936,0xf8bdc39f,0xa2d921b0}}, // _khia, _kiya, ्याय, मार्,
+ {{0x7ae982d2,0x4fc683fc,0xdb0587da,0x68e99937}}, // _coet, зска, nthè, _coed,
+ {{0x2718806e,0x6280caba,0x92e926b0,0x443ea0c2}}, // nčni_, vamo, _طریق_, _szt_,
+ {{0x7afb9938,0x26c5b939,0x7e7e33e2,0x66044167}}, // _elut, _calo_, _jepp, mzik,
+ {{0x25a6993a,0xf76fa050,0x9c7ca143,0x2560d93b}}, // stol_, هاي_, _hočk, _sòl_,
+ {{0x361b6095,0x61e1393c,0x6d5e2173,0x9c7ca04a}}, // _הולד, älli, _mupa, _kočk,
+ {{0x29112bb1,0x2056ac84,0xbcfb793d,0x7cf36181}}, // yiza_, _етер, _stéi, _fırı,
+ {{0x8626e936,0x98b8803b,0xfee9a13a,0x6d561280}}, // зьме, _kurį_, _وعلى_, _aiya,
+ {{0x645aac57,0xb5caa68c,0x6d42c016,0xdceb438a}}, // _igti, _دوام_, _thoa, _žiča,
+ {{0x6d49d93e,0x63a4c0ba,0x3866993f,0x7c22c108}}, // cmea, _ţinu, ncor_, _txor,
+ {{0xa5bb4005,0x703ac233,0xc8c82046,0x68e4400a}}, // _axón, _اساس_, िएंट, djid,
+ {{0x3f82c013,0x6d55c03e,0x7e7e2c65,0x2d9a7940}}, // škus_, _ézar, _cepp, kupe_,
+ {{0x368b48e4,0x7f556939,0x2561e3e4,0x387eb941}}, // йсан_, _rizq, _gól_, _metr_,
+ {{0x539be095,0x6d557942,0xf4082049,0x7cefa271}}, // _היהו, _siza, سيقى_, _hørt,
+ {{0xa5bb4057,0x6eefa065,0x690921e4,0x7ae98aaa}}, // [1570] _exón, _købt, džev, _roet,
+ {{0xd7fc0076,0x27320057,0x7e7e2425,0xd25b60db}}, // _להחל, báns_, _gepp, сца_,
+ {{0x7ae98db2,0x3f8760b5,0xfe7040e0,0x656d01f6}}, // _poet, énu_, ئدہ_, _htah,
+ {{0x79856011,0xe8d93943,0x29190050,0xd2518886}}, // rshw, _muọ_, érah_, ننا_,
+ {{0x9989803a,0x6f00c049,0x6ea51303,0x6d5e3944}}, // ťaž_, dhmc, _कानु, _zupa,
+ {{0x2d51c04a,0x61361945,0x799af665,0xa49b458f}}, // _všem_, ráli, kutw, _blòg,
+ {{0x6d49d946,0x61361947,0x26c5b0aa,0x7ae37948}}, // tmea, sáli, _valo_, öntu,
+ {{0xa2a2d17d,0x6d573949,0x61360803,0x6d4a4197}}, // _गाण्, _lixa, páli, _èfat,
+ {{0x752f00c2,0x6d44194a,0x7f5f017a,0x25a7a171}}, // docz, _rhia, _nuqq, stnl_,
+ {{0x799ae167,0xca48a050,0xa49b4030,0x5589e8c0}}, // futw, _کلمه_, _flòg, обом_,
+ {{0x112bc6fc,0xdb01c065,0xb5fb0049,0x27320057}}, // _люди_, _oplø, scál, xáns_,
+ {{0x6d45394b,0x6d440048,0x3f85e069,0x6d572733}}, // _ahha, _qhia, rslu_, _aixa,
+ {{0x060702fe,0xe9dbe0eb,0xe8df8119,0x6d57215a}}, // дняк_, _گذشت_, _nhọn_, _bixa,
+ {{0xf1bcc982,0x60c640eb,0x61306262,0x6f1d1157}}, // ्टरन, _sakm, jälp, ansc,
+ {{0x78ad594c,0x2912594d,0x6da326bd,0xe9a320ff}}, // ffav, tiya_, кира, карп,
+ {{0x7c240265,0x9c7ca431,0x7cefa22e,0x29137615}}, // _txir, _počk, _kørs, eixa_,
+ {{0xdb0b0057,0x3eba612a,0xaec66297,0x46ea74b0}}, // [1580] _esfó, tept_, мбал, одан_,
+ {{0xddc8b94e,0x6fb1e029,0x6d5e394f,0x68e45950}}, // _redž, _जहां, _tupa, sjid,
+ {{0x80a64aa1,0x6b898931,0x7cf5c0e0,0xddc8a6a6}}, // _छापे, _aveg, _várj, _sedž,
+ {{0x31be604a,0x6b838088,0x2d9a6282,0x3eba7951}}, // _vůz_, _ọgar, rupe_, sept_,
+ {{0x656d005d,0x094ab3fc,0x2ca040ae,0x914ab952}}, // _xtah, очки_, mgid_, очка_,
+ {{0x248d9953,0x78a2d796,0xdcfc4013,0x229481e9}}, // _edem_, _scov, turė, _búkà_,
+ {{0x3f869954,0x31601955,0x28cf8029,0xa2db6260}}, // ssou_, _luiz_, _सोवि, नार्,
+ {{0x63a9c828,0xddc8a098,0x6efcc050,0x78bc2747}}, // ften, _tedž, _béba, herv,
+ {{0x2004c38a,0x61fda5fc,0x5d54e16c,0x395f9956}}, // uzmi_, _əsli, нкит, _guus_,
+ {{0x7cf1478c,0x3374e450,0x2732b044,0x395eb957}}, // _hårt, нгор, râns_, _tuts_,
+ {{0x65609958,0x8e976076,0x611500bc,0xa7151593}}, // _humh, ודיו_, едну, емні,
+ {{0x6f029959,0x6f1d00cb,0x799c395a,0x6d57395b}}, // nhoc, unsc, durw, _sixa,
+ {{0x613883b8,0xb95b40f9,0xa294e84f,0x7cf14156}}, // gíli, _asìn, _залі, _mårt,
+ {{0x6abc395c,0xe60fe0d0,0x78bc395d,0xdb05995e}}, // gerf, اشی_, gerv, rthé,
+ {{0x799c200f,0x5757e076,0x672f07a0,0xf2c741d3}}, // gurw, _קבוע_, pocj, мсан,
+ {{0xa2d6e028,0x66252018,0xd25825d0,0x7cf02262}}, // बाक्, _ārkā, ьця_, _bärs,
+ {{0x44386090,0x6d58f95f,0x69dca057,0x3f8e6098}}, // [1590] _ayr_, _kiva, _áref, šful_,
+ {{0x3eb200c2,0x80c9610a,0x3ea48012,0x291ef960}}, // _zbyt_, _होखे, _acmt_, nnta_,
+ {{0x65608090,0xf1c8c067,0x6b9d1961,0x6b9c3044}}, // _aumh, _lỡ_, lusg, curg,
+ {{0x62808753,0x7aed0265,0x2b5860ae,0x6f152106}}, // _cemo, _joat, _eirc_, lizc,
+ {{0x38c4a013,0x2f1ac018,0x387f862c,0xd838a030}}, // kūrė_, cīgi_, _veur_, _ayč_,
+ {{0x39595962,0x68ebd963,0x98c622e2,0xdb1ba049}}, // _hiss_, _sogd, ćući_, _tsuí,
+ {{0x291166df,0x2b46c153,0x78bd05ee,0xa295803b}}, // _imza_, _bhoc_, kesv, _паві,
+ {{0x6b899964,0x6b9d1965,0x61f86042,0x673ae54c}}, // _tveg, kusg, øvle, eltj,
+ {{0x78bbc004,0xc8aba14a,0x6138841a,0x39595966}}, // reuv, _мъже_, xíli, _miss_,
+ {{0x78a4065f,0x8af0603c,0xddd8a16d,0x6d4d4025}}, // _sciv, qqət, savš, hmaa,
+ {{0x68ed0593,0xb4db58c6,0x24800057,0x260fc621}}, // _boad, _ahàm, _peim_, _तेरी_,
+ {{0x3ce0c010,0x6abd0b45,0x6722c3ac,0x6abc3404}}, // काणे_, gesf, _gjoj, werf,
+ {{0x6d4d423c,0x6abc2732,0xb4db4153,0x799d13cd}}, // dmaa, terf, _chàm, gusw,
+ {{0x61388912,0xb4bc8029,0x62857967,0x6d4d4d53}}, // ríli, _अस्_, jaho, emaa,
+ {{0xe8168064,0x612803ed,0x7529416d,0x62857968}}, // _देना_, tıld, čezn, daho,
+ {{0x07a60974,0x645c7969,0x6abc2271,0xa3bef3e3}}, // _заин, _sgri, serf, ुटर_,
+ {{0x28dd0aa1,0x2ca04d22,0x2bd2c918,0x6d59d96a}}, // [15a0] यारि, rgid_, तिया, _miwa,
+ {{0x6d4d41dd,0xaaac037f,0x628449fe,0x6285748b}}, // amaa, ट्रक, saio, gaho,
+ {{0x6284596b,0x499a639e,0x39586a98,0x2b59596c}}, // paio, ятая_, _virs_, _fisc_,
+ {{0x60c8b96d,0x4427e2a8,0x6ef5ce20,0x2bd2d96e}}, // _sadm, _mxn_, _fábi, तिमा,
+ {{0xddcd412a,0x753bd152,0x69c3622e,0xc95301a1}}, // rbaţ, dluz, æner, אמא_,
+ {{0x6561a066,0x2d9cb96f,0x66e3a39e,0x7d045970}}, // _bulh, tuve_, лоча, lhis,
+ {{0x044373b0,0x7cf15971,0xfc3f4057,0xb87b006f}}, // _берн, _tårt, _icía_, rkíz,
+ {{0x44443972,0x3dc20939,0x7d152098,0xcf932076}}, // _mz_, kwkw_, zizs, אטה_,
+ {{0xd11780be,0x0326a052,0x69dca143,0x925aa0eb}}, // _נקמה_, _здан, _šred, _فشار_,
+ {{0x67241973,0x291ef974,0xdbdbe0f9,0x753ae108}}, // _ajij, unta_, _bààr, ultz,
+ {{0x7aee65f9,0xed4fa050,0x61360e34,0x2900058f}}, // _mobt, _اپل_, jáls, _blia_,
+ {{0x62857975,0xf1c9e067,0x6d4d4054,0x7bc65976}}, // zaho, _dạ_, xmaa, _osku,
+ {{0x040b4016,0x6561b977,0x7cfcd978,0x78bd1979}}, // _dưỡn, _zulh, _héro, resv,
+ {{0x443f417a,0x2bc5b17d,0x656fd97a,0xe8b58106}}, // _ġu_, विचा, íche, kışı,
+ {{0x764401a5,0x4427e067,0x7a490041,0x69c657bb}}, // _eziy, _gxn_, _sūtī, _aske,
+ {{0x69dd8057,0x98a50018,0x6d4d597b,0x9f6038ff}}, // _áseg, ēlē_, umaa, árás_,
+ {{0x3959597c,0x10174050,0x7e6d0f8f,0x7cf02277}}, // [15b0] _viss_, ربرد, _ifap, _värr,
+ {{0x3ea16095,0xbebce041,0x3ebef3e9,0x8e2776f4}}, // ught_, lnīb, nett_, нфед,
+ {{0xa2d06010,0x7bc5266b,0x60db8057,0xd4986013}}, // _डोळ्, _ushu, _pnum, эрт_,
+ {{0xe4e4812e,0xdbe402f4,0x395a202e,0x29012187}}, // лічн, _béèr, _zips_, _hlha_,
+ {{0x26118216,0xdca5a607,0xbea5a58a,0xa2a1e984}}, // _देशी_, вали, валк, _खाल्,
+ {{0x26ca797d,0x2d990049,0x2d8b197e,0x2d9d997f}}, // _gabo_, irse_, isce_, ruwe_,
+ {{0xa92804d2,0x7c2920cb,0x6d48a58f,0xf2d42053}}, // nažé, ßerh, _dhda, טעס_,
+ {{0x78a28098,0x6efcd980,0x26cb5981,0x62872530}}, // cgov, _débo, _jaco_, hajo,
+ {{0x20095982,0x26ca7983,0x13b48ef8,0x62873984}}, // nzai_, _yabo_, _مصلح, kajo,
+ {{0x779080e0,0x394905c8,0x9e07a822,0xdcfc4018}}, // _ایوا, _ahas_, _пчел, turī,
+ {{0x2d9ef985,0x8cd6e0c2,0x2b490256,0x60cd4013}}, // gute_, बाजो, _bhac_, kdam,
+ {{0xdb1ba108,0x23dd0077,0xb815c39d,0x6282c3b0}}, // _iruñ, ममंद, _идеј, _geoo,
+ {{0x7afbcfd6,0x6562c0a2,0x2b490025,0x78b56066}}, // mkut, _guoh, _dhac_, _obzv,
+ {{0x81e7407c,0x2eeee227,0x6d5ab986,0xed573702}}, // বনা_, _goff_, _xita, коя_,
+ {{0x2905e569,0x6b8d01f6,0x395a3987,0x98ad81f6}}, // mhla_, _evag, _vips_, _gieħ_,
+ {{0x2eeef988,0xa3e18026,0x7fd641b4,0x752401df}}, // _zoff_, धमा_, кізі, _ujiz,
+ {{0x6563f989,0xccf30095,0xb4db05df,0xdceac29c}}, // [15c0] _hunh, _ככה_, rmàt, žiće,
+ {{0x60cd598a,0x26ca6054,0xc7d70076,0x799e60ce}}, // adam, _qabo_, _יופי_, tupw,
+ {{0xa50a4421,0x1d0a467b,0xdb0f6057,0x68fbc10a}}, // _жена_, _жени_, _ascó, kkud,
+ {{0x6563f98b,0x7afbc098,0x61e12156,0x65640167}}, // _munh, jkut, ålle, _kuih,
+ {{0x64a3998c,0x6d5b813b,0x26ca798d,0x2d9ef98e}}, // рача, _diua, _tabo_, zute_,
+ {{0x7cf26042,0x09c8e07c,0x26ddc0d1,0x62840108}}, // _værs, ষমতা, _anwo_, _leio,
+ {{0xcec36026,0x26cb4071,0x73e36792,0xdb1ba057}}, // ářů_, _yaco_, _корз, _cruñ,
+ {{0x4cc8a07c,0xab27c90f,0x7c3aa066,0x3ea280ff}}, // লাধু, која_, _vytr, _økt_,
+ {{0x7d03412a,0x7c292106,0x973d004d,0xff06e052}}, // _înse, ğeri, nuće, вянн,
+ {{0xc796a099,0x6722803b,0x3f9ef98f,0x60cae05d}}, // _арды, lnoj, tutu_, _qafm,
+ {{0x6b8d0954,0x5d5501e1,0x248481d7,0x4207a312}}, // _svag, лкот, _kemm_, _инфо_,
+ {{0x26cae42e,0x2d9ef990,0x60cbc426,0x68e9c579}}, // žbo_, rute_, _gagm, cjed,
+ {{0x6284015a,0x69d7e057,0x7bd7e005,0x6f02d8a3}}, // _deio, _áxen, _áxun, _iloc,
+ {{0x2905e4d2,0xd5b0e050,0x6f18a066,0xdb1e4153}}, // chla_, _هفت_, nivc, _aspà,
+ {{0x3949005f,0xa1582b7a,0x7aef4066,0xa438e0ff}}, // _thas_, тару_, _poct, _язку_,
+ {{0xbcfb64cd,0x21670052,0x78ada098,0x37045991}}, // _cuén, _итог, đavi, ачув,
+ {{0x7c8456f4,0x2b4a6569,0x7bcdc057,0x62873992}}, // [15d0] русе, _bhbc_, _áaut, pajo,
+ {{0x26cca4fa,0x613d0004,0x6443403c,0x60cd5993}}, // _nado_, déli, ənil, sdam,
+ {{0x612806df,0x60cd1994,0xaaaa0c4c,0xf2b1807c}}, // lıla, _kaam, _कामक, _ঘোষণ,
+ {{0xf1b3e00b,0x28a7c046,0x7cf5c009,0x6916035d}}, // יסע_, _गाधि, _járt, jšel,
+ {{0x61280c2b,0x6d5d5995,0x69d56064,0x25f36010}}, // nıla, _iisa, _wrze, ूनही_,
+ {{0x6d5d5996,0xdb00e277,0x7cfcc057,0x7d02c157}}, // _hisa, ntmä, _dérm, _alos,
+ {{0x68e9c579,0x60c0d997,0x4fc46052,0xbebce041}}, // tjed, femm, _кста, znīc,
+ {{0x27e9b998,0x6d5c7999,0x7afd0200,0x6283f99a}}, // ćan_, _zira, jkst, _seno,
+ {{0x26ccb99b,0x26cdc019,0x3f42e069,0x7cff199c}}, // _fado_, rdeo_, rðun_, _përn,
+ {{0x6d5c686a,0x7e6d40c7,0x63bb8c96,0x6284199d}}, // _xira, hcap, _ipun, _seio,
+ {{0xd7c8a0eb,0xa2a50af7,0x7cfda011,0x6e3d4654}}, // _لوله_, _काश्, _jèrm, _lysb,
+ {{0x5a0ca00b,0x60c0c0fa,0x63a0c1fb,0x38612229}}, // _קלאַ, cemm, bumn, _aghr_,
+ {{0x26c1603a,0x333fc2a4,0x78a44042,0x48e0c0c2}}, // keho_, mlux_, vgiv, कारो_,
+ {{0x6da6207a,0x673521d7,0x3392c13a,0x2569c13a}}, // лига, gozj, _الوز, _iúl_,
+ {{0xd627234d,0x6285399e,0xb176e067,0x81e981a6}}, // горе_, _deho, _trượ, মনি_,
+ {{0xf2a340ba,0x60cd0b90,0x69d603ac,0xbebce018}}, // йисп, _gaam, _trye, pnīc,
+ {{0xa06a399f,0x26c219a0,0xd7f8a016,0x6b8d4644}}, // [15e0] _папа_, meko_, _ngăn_, gsag,
+ {{0xcc36233e,0x6b9bd9a1,0x91e661c0,0x81e981a6}}, // _مرجع, irug, лозе, মনা_,
+ {{0x9f47805c,0xb8cc791c,0x2739003e,0xdb1e53c9}}, // šný_, _गा_, céns_, _espá,
+ {{0xbc1b200b,0xfce643d5,0x26ccb9a2,0x798d4022}}, // _טויש, _сомо, _sado_, bsaw,
+ {{0xd12f8049,0xaff540e0,0x752299a3,0xbcfb478d}}, // أمن_, _پہلا_, rnoz, _diéd,
+ {{0x63a1f86c,0x2907a0f7,0x613d19a4,0x6d5d59a5}}, // kuln, ghna_, réli, _zisa,
+ {{0x3ce0d133,0x26cca368,0x69c98af8,0xdfd0a13a}}, // काले_, _vado_, _esee, زية_,
+ {{0x443dc0e2,0x6b9bd9a6,0xaa7b403a,0x6f1981cd}}, // _byw_, frug, _inýc, ciwc,
+ {{0x8af0403c,0x171b800b,0x28e199a7,0x6b9bc8b2}}, // mməd, _נומע, फारि, grug,
+ {{0xd90de0d0,0x8af0403c,0x63a28167,0x3f14e471}}, // وین_, lməd, muon, рдос,
+ {{0x60cd01da,0x386983ee,0x6ef4a5df,0x59d2c026}}, // _paam, žare_, _fàbr, तिहर,
+ {{0x26c1600f,0x7d040ce5,0x443dc1cd,0x69dd8057}}, // zeho_, _clis, _fyw_, _ásec,
+ {{0x33f6c7b4,0xadf98148,0x8ff7d9a8,0x6b4b64f7}}, // _حساس, ्नयन_, _مرور_, lüge,
+ {{0xc6bf607c,0xf53f0584,0x27ff017a,0x59b8a984}}, // _উচ্চ, slå_, ġun_, _आहार,
+ {{0x6b4b6105,0x6f0419a9,0x29048181,0x1efbe053}}, // nüge, _flic, _olma_, _אלגע,
+ {{0x4275465f,0xf2df4067,0xb27541de,0x7f5d4746}}, // ргас, _thâm_, рлаш, _qisq,
+ {{0x248951f1,0x443dc022,0xd37147f0,0x351c0557}}, // [15f0] raam_, _xyw_, زها_, _רוחנ,
+ {{0x6f0419aa,0x752459ab,0x798d4827,0x1da8e028}}, // _zlic, aniz, rsaw, _कमात,
+ {{0xfe1ae029,0xf647612e,0x6d4d0025,0x7deae03c}}, // _फेमस_, ухан, _khaa, fəsi,
+ {{0x6d5e39ac,0x60c1f8ee,0xba7420eb,0x0c7420eb}}, // _zipa, zelm, _پایت, _پدید,
+ {{0x91e599ad,0x7d1af9ae,0x6b656013,0x2b5ea090}}, // роле, kits, _mėgi, _bitc_,
+ {{0xe3b0254f,0x291a79af,0xeb99a0bc,0x6d4bc0a2}}, // _بری_, cipa_, вик_, _shga,
+ {{0x7cff000e,0xf075c0e0,0x3b06a14a,0x256ab03c}}, // _përl, لیاں_, рещо_, _cùl_,
+ {{0x2fd94098,0x1c1699b0,0x6ef84057,0x395ea07d}}, // _krsg_, _देवल_, _fíbu, _eits_,
+ {{0x2907a098,0x628aa1fb,0x63bb8167,0xab5b6046}}, // phna_, bafo, _upun, _psüh,
+ {{0x63a1e260,0x9f58400e,0x28a7c026,0xe787a12e}}, // tuln, tyrë_, _गावि, _будо,
+ {{0x60c3b3d7,0x8c45e192,0x28e2a3af,0x6f0419b1}}, // menm, рене, पारि, _plic,
+ {{0x33d5c139,0xcb6a1145,0x28d9ac87,0x68ed59b2}}, // _вікт, каме_, _योति, jjad,
+ {{0x7aed59b3,0x63a1f9b4,0x7f5f003c,0x26cfd719}}, // djat, suln, _diqq, _iago_,
+ {{0xa8568095,0x394d841a,0x8467e14a,0xd618c050}}, // יירה_, _lhes_, _съде, _حتما_,
+ {{0x673b800d,0x61fbc114,0xdb020851,0x26c20108}}, // _okuj, lyul, ttlä, peko_,
+ {{0x67244041,0x753b8096,0x056619b5,0xb22640d7}}, // unij, _nkuz, _квин, амбл,
+ {{0x60c3a1cf,0x7c3e2064,0x21a648ab,0x7b160066}}, // [1600] kenm, _wypr, ризм, yšuj,
+ {{0xe0466406,0xef8639b6,0x67299973,0x673b87ed}}, // анзи, _клоп, _ajej, _akuj,
+ {{0x8ca7679e,0x6f1c39b7,0xcd29224d,0xc05960ff}}, // _चारो, mirc, _حسين_, гії_,
+ {{0x291a60a9,0x69d9d9b8,0xa5bb403e,0x26cfd9b9}}, // sipa_, _orwe, _axóu, _nago_,
+ {{0x60ce6022,0x88bce04a,0xe3b24949,0xfb37400b}}, // _tabm, dněn, _برج_, _כאטש_,
+ {{0x60c3b9ba,0xc7d70076,0x9ea7a60f,0x9713a71b}}, // genm, יוני_, авља_, оміц,
+ {{0x2120a06a,0xfbc7e33e,0x7cfccaed,0x7d1af9bb}}, // đih_, _بت_, _héri, vits,
+ {{0x6448a06f,0x8f9b2087,0x2900402d,0xdddc6143}}, // _vzdi, דיקי, nkia_, _gerš,
+ {{0x7b160066,0xa2db604b,0x7d1c2806,0xdb0219bc}}, // pšuj, नाच्, kirs, pulê,
+ {{0x443ea05d,0x7aed417a,0x273204d3,0x7f42956c}}, // _tyt_, zjat, mány_, lloq,
+ {{0x30d9600b,0xda6fc1fc,0x60d9600b,0x3cffc022}}, // אַרע, _мя_, אַרג, bkuv_,
+ {{0x6738b90b,0xe61ac844,0x2367e012,0x6f1bcd82}}, // lovj, _иде_, _dunj_, ciuc,
+ {{0x80a76984,0xa3c920c2,0x96470013,0x5067214a}}, // _चाले, लौन_, рэнд, итва,
+ {{0x644099bd,0x656099be,0x26c4d543,0x628b99bf}}, // _mymi, _limh, lemo_, zago,
+ {{0xfa77e1a9,0x68ed59c0,0x7cfcc004,0x06cf607c}}, // _דעות_, tjad, _aéri, রামি,
+ {{0x6287628e,0x5f73e25b,0x60c3a0f0,0x2cba2030}}, // _sejo, _باور, zenm, _ebpd_,
+ {{0x395f8017,0x6568a153,0x76b979c1,0x6738b9c2}}, // [1610] _rius_, _ludh, ллар_, kovj,
+ {{0xd657c095,0x65608256,0xa857c076,0x5fc6a026}}, // שימת_, _aimh, שימה_, लबाल,
+ {{0x3135493b,0xdddc79c3,0x705520eb,0x68e2c291}}, // _генр, _perš, _دندا, _onod,
+ {{0xb6068098,0xf8c9e1e9,0xdb1ace06,0xa857e095}}, // _opšć, _abẹ́_, _estú, ביבה_,
+ {{0xee87604e,0x752999c4,0x7cff00dd,0x24890733}}, // _выбо, _vjez, _përj, _meam_,
+ {{0x6f1d1385,0xd006e711,0x248906f1,0x6288a197}}, // hisc, _فل_, _leam_, _cedo,
+ {{0xb3a9e497,0x6ef36026,0x43946323,0x60c3b9c5}}, // _çıxı, _výbě, _марс, renm,
+ {{0xdb03c00e,0x656fc3ab,0xaacf80a8,0x945d81e9}}, // munë, ícho, _सोचक, _bańt,
+ {{0x6d41e562,0x63a56058,0x6abb8079,0x26d179c6}}, // tlla, luhn, _ibuf, _kazo_,
+ {{0x7d09d9c7,0x6568a058,0x628d40ae,0x3cffc6b6}}, // shes, _fudh, haao, skuv_,
+ {{0x63a44171,0x7af579c8,0x7d1c39c9,0xdab9c0db}}, // zuin, _mozt, virs, _былі_,
+ {{0x7c3823d1,0x7ae2c6f1,0x6ef84019,0x7980c125}}, // _øvri, _gnot, _líbr, _ịwec,
+ {{0x7d1c39ca,0x61e12277,0x8fc7c343,0x38c840d0}}, // tirs, ålln, _خزان, یاری_,
+ {{0x78a9d9cb,0xa2ad2010,0x84e5c028,0x777bc03e}}, // lgev, _जाण्, कावट_, mpux,
+ {{0x78bb99cc,0x7d064041,0x236dc0dd,0x3f9ee29f}}, // _obuv, _plks, lqej_, crtu_,
+ {{0x0e664618,0x333919cd,0x63a459ce,0x3c6640db}}, // скан, cosx_, tuin, скаг,
+ {{0x9c82a03b,0x69c9d9cf,0x26d16ecc,0x6d4f59d0}}, // [1620] ščia, nwee, _bazo_, _dhca,
+ {{0x2bd50010,0x26d162a8,0x6aa9c105,0x261963fa}}, // दिरा, _cazo_, hgef, _येही_,
+ {{0x23690048,0x387fc17b,0xa2ad28b1,0x69db8187}}, // _zuaj_, kbur_, _जात्, _arue,
+ {{0x23690048,0x2fc94006,0x78a9c171,0x2bef01a6}}, // _yuaj_, bwag_, jgev, _চেয়ে_,
+ {{0x6d4459d1,0x78a9c3b0,0xe5a388e9,0x6b45c057}}, // mlia, dgev, зичи, rógl,
+ {{0x31624037,0x2fcca005,0x76408066,0x68e2d9d2}}, // _hikz_, _psdg_, _vymy, _snod,
+ {{0x68e402f4,0x6aa9d9d3,0xa49b4031,0xfb1b6076}}, // _onid, fgef, _amòy, _עולמ,
+ {{0x656086a5,0x7bdaa066,0x6288a0eb,0x7986448d}}, // _uimh, _vrtu, _wedo, _kwkw,
+ {{0x6d4459d4,0xe8df8067,0xc006854a,0xb606007f}}, // ilia, _nhộn_, _упак, nišč,
+ {{0x26c5f9d5,0x25a5e94b,0xd8e6e8ab,0x7d0299d6}}, // gelo_, full_, сцип, lkos,
+ {{0x628e39d7,0x69da606e,0x26d219d8,0xfd4d8291}}, // dabo, _štej, _bayo_, _nchọ,
+ {{0x07a6ceb6,0x7f83ca28,0x2d9ef9d9,0x7ae2ca43}}, // _гадн, _کلون, rrte_, _unot,
+ {{0x14aa039f,0xfd4d8096,0xd0e5ca11,0xf09f4090}}, // _कारण, _achọ, कारण_, _blàn_,
+ {{0x7d028089,0xde58e362,0x8ca50077,0xf1c86021}}, // hkos, _калі_, _काको, रबान,
+ {{0x26c68098,0x0eb109a8,0x7d1d19da,0x7afa2262}}, // keoo_, _झापड, piss, ötta,
+ {{0x63a619db,0x628e2c57,0xa2d92567,0x7af60c1e}}, // bukn, aabo, _फोर्, _goyt,
+ {{0xc1b5a04a,0x63a6005d,0x7bcd1795,0xb4db42d9}}, // [1630] _अङ्ग, cukn, _usau, _bhàr,
+ {{0x69dc79dc,0x831940eb,0x25a699dd,0x2f0ba22e}}, // _brre, _چقدر_, duol_, _køge_,
+ {{0x25bf99de,0x6f0340ba,0xaca36079,0x2c19611a}}, // _spul_, _înco, _nyục, _येशू_,
+ {{0x25a054e0,0xe8122b27,0x26d179df,0x3ce48019}}, // bril_, _डेटा_, _wazo_, _cnmv_,
+ {{0x2bd12626,0x656982a8,0x7d0d59e0,0xff51a8b5}}, // हिका, _queh, mhas, وخت_,
+ {{0xdb0d6049,0x13ba4053,0x4fb426ec,0x26c5f9e1}}, // otaí, _דזשע, _بصیر, xelo_,
+ {{0x1986a14a,0x442fc03e,0xf53f30dc,0x2d49a03e}}, // общи_, _sxg_, mråd_, búen_,
+ {{0xdb02078d,0x14d90010,0x6561b9e2,0x6f0d59e3}}, // pulé, _बोलण, _wilh, nhac,
+ {{0x26d200e6,0x2d9e0064,0xae168010,0x9c7ca35d}}, // _rayo_, ątek_, _देऊन_, _inčn,
+ {{0x161f80a8,0x25a5e00e,0x3494a8e4,0xdb074156}}, // _मेयर_, tull_, _наур, ltjä,
+ {{0x3ce60aa1,0x628e35f7,0x26c5e32a,0x6cc618a6}}, // टाले_, vabo, relo_, ойна,
+ {{0x320be064,0x7bdd4012,0xc7d66076,0x8afee0a2}}, // ący_, _orsu, קורי_, _riƙe,
+ {{0x7ae40026,0x60c60106,0x752d0163,0x7af72363}}, // _vnit, rekm, _djaz, _boxt,
+ {{0x753bd9e4,0xb4c9c180,0x6d4441cd,0x7ae3f9e5}}, // louz, _लघु_, wlia, _unnt,
+ {{0x27ffd9e6,0xab19271b,0x614325a7,0x39aea03c}}, // lyun_, сцях_, фера, _həsr_,
+ {{0x26c7b2c1,0x6d4459e7,0x212d800e,0x69dd4012}}, // heno_, ulia, _njeh_, _brse,
+ {{0x88bce04a,0x13a6b133,0xd7ef0049,0x6f02962f}}, // [1640] dněj, _गम्भ, _لكي_, vkoc,
+ {{0xb606042e,0x7643e07d,0xb87b4005,0x752d08db}}, // rišč, _kyny, _unín, _zjaz,
+ {{0x6f0d46a5,0x0b5a861b,0x69c1a05f,0x7cff000e}}, // bhac, арды_, _nple, _sëri,
+ {{0x290cf9e8,0xb6038066,0x7643e1cd,0x69de2024}}, // zhda_, _omáč, _myny, _irpe,
+ {{0xdce0a1fc,0x291ef9e9,0x7cfcc009,0x7643f9ea}}, // _numă, yita_, _mért, _lyny,
+ {{0x6ea7c026,0x6563f955,0x628bd9eb,0x9c876089}}, // _गाउँ, _ninh, _aego, ručí,
+ {{0xf98fa4a1,0x7bdc6265,0x6f0999ec,0x656bc090}}, // نبي_, _urru, _flec, _augh,
+ {{0x90c399ed,0x673c207f,0x7d098944,0x764419ee}}, // _обсе, horj, _gles, _oyiy,
+ {{0x80d3e1a6,0x27ffc1e7,0x628f19ef,0x26c7a4f2}}, // তান্, gyun_, vaco, beno_,
+ {{0x62828d53,0xe8d92088,0x6563e0a2,0x7d1aa0c1}}, // lboo, _kwụ_, _cinh, _umts,
+ {{0xbcfb6005,0x3959e017,0x753ae108,0x644419f0}}, // _quéi, ïssa_, rotz, _ayii,
+ {{0x628299f1,0x61e2006e,0xa2ad2077,0x656bc197}}, // nboo, _šola, _जाह्, _fugh,
+ {{0x644419f2,0x3eb91385,0x6800c031,0xdcf8c018}}, // _cyii, rfst_, _dájú, ssvē,
+ {{0xbcfb4019,0x6281f9f3,0x7ae6400e,0xdb046aed}}, // _cién, cblo, _ankt, quiè,
+ {{0x2bd2c7af,0x7643f73b,0x69de2143,0x368b4df4}}, // तिका, _gyny, _crpe, исан_,
+ {{0x33754a00,0x3eaf40cb,0x2b4680ba,0x7bdd46ca}}, // згар, ügt_, jloc_, _prsu,
+ {{0x62828025,0x3f874098,0xbcfb6071,0x629ab9f4}}, // [1650] dboo, ćnuo_, _muév, _odto,
+ {{0x69dd4361,0x7945c0c2,0x7d044194,0x26c7204a}}, // _vrse, tówk, akis, _úno_,
+ {{0xf53f30dc,0x656d19f5,0xf0788125,0x9c1311b0}}, // rråd_, _huah, _ịgụn, kọka,
+ {{0x27ffd9f6,0x753d0367,0xcdda200b,0x657a21b3}}, // yyun_, nosz, _פֿיר, íthe,
+ {{0x69cf59f7,0x6f1d4049,0x6281f9f8,0x2570858f}}, // _usce, _imsc, yblo, _càl_,
+ {{0xddc9c361,0xff7c0053,0xeb96911a,0xdb02046b}}, // jceš, סטומ, чиш_, stlø,
+ {{0x628bc1ae,0x7b03410a,0x656bd77e,0x07a5a905}}, // _pego, _nõuk, _sugh, чайн,
+ {{0x25deeba0,0x9943c106,0x21216098,0x161680c2}}, // गिनी_, _dış_, lihh_, _देकर_,
+ {{0x25a94095,0xdb09e1de,0x753bc057,0xa49b458f}}, // nual_, nteç, rouz, _clòi,
+ {{0x2904c08b,0x290dc0ce,0xa3b8a0eb,0x61e6c5ee}}, // akma_, shea_, _ژانر_, åkla,
+ {{0x68f8e143,0x7cfcc019,0xbcfb4071,0x7c2dc143}}, // _dovd, _pért, _rién, _žard,
+ {{0xdb23c050,0x27f8e156,0x672b93f0,0x7bcd40a2}}, // _توصی, ärna_, nngj, kwau,
+ {{0x316d8333,0x69de204a,0x60c9c11d,0xbcfb4071}}, // _juez_, _srpe, meem, _pién,
+ {{0xe7f38688,0xbb78c009,0x63a9c264,0xe689e049}}, // _आपना_, _بلوچ_, muen, _انثى_,
+ {{0xdee642d8,0xbcfb42a8,0x8c42e2d3,0x9f5e404a}}, // пови, _vién, _пеше, ští_,
+ {{0x141a4049,0xe9df59f9,0x2cadd9fa,0x7d06eb8e}}, // ميزة_, _brú_, nged_, ūkst,
+ {{0x80cee07c,0x444439fb,0x2fcdd16b,0x6d408012}}, // [1660] ়ার্, _wy_, nweg_, _rkma,
+ {{0x63b6402d,0x7af8e286,0xe81680c2,0x7bde2012}}, // rtyn, _xovt, _देगा_, _trpu,
+ {{0xe9df4031,0x09b7c1a6,0x63a9c125,0x929dc0c2}}, // _erú_, জিটা, huen, zkła,
+ {{0xc8dacef3,0x656ee049,0x8afee0e8,0x6b8999fc}}, // _मोंट, íbhi, _miƙa, _bweg,
+ {{0xda168046,0x2b468425,0x49738cde,0xdcf740ba}}, // _देखत_, rloc_, еліс, şcăr,
+ {{0x656659fd,0x63a44e36,0x628291f1,0x61360009}}, // _hikh, mrin, sboo, rály,
+ {{0x8afee0e8,0x60c8e654,0x68f8f9fe,0xe814c077}}, // _niƙa, vedm, _rovd, _तेजा_,
+ {{0x2cadc5c7,0xe01dc0c2,0x290ea167,0xf8bf1922}}, // gged_, _बेहद_, shfa_, tném_,
+ {{0x8fa38d3e,0x656659ff,0x7af8e048,0x2a6a6037}}, // _пате, _mikh, _povt, _kgbb_,
+ {{0x69060057,0x65665a00,0x656e61fb,0xe297a12a}}, // _dóem, _likh, _kubh, _нау_,
+ {{0xb87b4032,0x127ba00b,0x7bdc200a,0x8afee0a2}}, // _sním, _האבע, jvru, _ciƙa,
+ {{0x628e65d1,0x656640c1,0x973d0012,0x6d41a0a3}}, // _lebo, _nikh, mućk, _zkla,
+ {{0xb9098079,0x60c0805d,0x60d60d8d,0x6916006f}}, // _aghọ_, _mbmm, _daym, všet,
+ {{0xa2bfa4e5,0x291ce00f,0x75228197,0xd378a133}}, // ल्प्, _umva_, hioz, _zrća_,
+ {{0x76bbe095,0x8c41e105,0x78ad5a01,0xa3d2404b}}, // _המופ, ößte, tgav, विझ_,
+ {{0x0f09aba0,0x26d82425,0x7cff03ac,0x76a2e0f9}}, // _वत्स_, adro_, _përu, _fíyà,
+ {{0x6566413b,0x6d42c296,0x75229a02,0x88bdc0c2}}, // [1670] _dikh, _mkoa, dioz, liśc,
+ {{0x04f9807c,0x63aaa00a,0x4275306d,0x78ad5a03}}, // _আগের_, kufn, _югос, sgav,
+ {{0xd5b8e041,0xb9554691,0x3f8080ba,0x656e658f}}, // ntā_, _овощ, ţiul_, _cubh,
+ {{0xd491a119,0x7bc520bb,0xa3d3fa04,0x200f68fd}}, // _còn_, _iphu, _सैफ_, ągi_,
+ {{0x6d41a098,0x49744f93,0x29cd46ca,0x60d56d5d}}, // _pkla, _пляс, _uža_, _tazm,
+ {{0x7b160098,0x69dd0046,0x4dc3a07c,0x657d5526}}, // nšur, ivse, ্মসং, _itsh,
+ {{0x60c9c2df,0x7d0d0d16,0x32020066,0x2246c827}}, // teem, _olas, zyky_, _ayok_,
+ {{0x48e8007c,0x752d548c,0xb4d7c4da,0x6b8994b6}}, // _পত্র, nnaz, ाये_, _tweg,
+ {{0x3cf08066,0xb0aec029,0x2d817a05,0x29078013}}, // ťové_, _टारग, lphe_, ūnas_,
+ {{0xa2bdc216,0xdb03c333,0xe3ca84cd,0x6673e050}}, // _वॉर्, muní, señó_, رگتر,
+ {{0xbcfb64cd,0xa6ca0762,0xe81dc046,0x7bc4000e}}, // _hués, олна_, _बेरा_, _spiu,
+ {{0x68fb9a06,0xeab0c049,0x63a9c071,0xdb020071}}, // _joud, _معه_, puen, pulí,
+ {{0xe299e967,0xb8f60021,0x26ca3a07,0x672d5a08}}, // _дал_, _हस_, tebo_, dnaj,
+ {{0x63a44407,0xdb23e052,0x6d5bda09,0x752d5a0a}}, // vrin, äräi, lmua, enaz,
+ {{0x98ace04a,0xdbcbe005,0x2492408b,0x68e99a0b}}, // hodě_, póñe, taym_, _oned,
+ {{0x628f5a0c,0x26ca32c8,0xdb0d6105,0x68fb9a0d}}, // _ceco, sebo_, traß, _noud,
+ {{0xc7b9c0e0,0x65677a0e,0x224c604d,0x14aea04b}}, // [1680] ntő_, _bijh, _šdk_, _घालण,
+ {{0x68e99a0f,0x6723a035,0x6d5bc00e,0x68fb81f6}}, // _aned, jinj, hmua, _aoud,
+ {{0x823620e0,0xd259c018,0x7523a425,0x6d5c2944}}, // _زردا, meņu_, dinz, mmra,
+ {{0x333fda10,0x4fc690aa,0x6603a064,0xa2bdc029}}, // joux_, дска, dynk, _वॉल्,
+ {{0x2fc5a037,0xe81dd303,0x63ab9a11,0x6d48e0c4}}, // _aplg_, _बेला_, gugn, ylda,
+ {{0xdee3a18f,0x26c240d4,0xdddc28fd,0x6d46e0a3}}, // кочи, _hbko_, karż, ůkaz,
+ {{0x656f5875,0x66044009,0x3b0a80ba,0x66029a12}}, // _zuch, lyik, _дево_, syok,
+ {{0x399b2095,0x63ab8c57,0x6d5bc022,0xe8e00119}}, // קיפד, bugn, gmua, _trội_,
+ {{0x8fa3c55a,0x6e94e331,0x88bdc064,0xd1b3863b}}, // вање, нипу, wiśc, _لینک,
+ {{0x7bc64125,0x7523b9f7,0x2907a156,0xbebb054c}}, // _mpku, cinz, ckna_, giën,
+ {{0x26cb0e06,0xdb0ac156,0x6d48e262,0x2b494025}}, // teco_, ttfä, rlda, ylac_,
+ {{0xa3df7a13,0xdce2e02e,0x3f42e0c4,0x2572c0fd}}, // तित_, _uvoľ, gður_, _tâl_,
+ {{0x2768e63b,0x8af0403c,0x33200011,0x2911204d}}, // _کشتی_, rməl, _dmix_, thza_,
+ {{0xdb0ac851,0xd703e14a,0x2124c049,0x23c2a0c2}}, // stfä, _изчи, limh_, _शहाद,
+ {{0xb065e2df,0x27390066,0x25a68c2c,0x9c7cba14}}, // rjää, zény_, hrol_, _ančk,
+ {{0x80bf2077,0xe6671512,0x657a2009,0x2124c351}}, // _लॉरे, _отпо, ítha, nimh_,
+ {{0x443f2064,0x7523ab01,0x6b82806e,0x26d87a15}}, // [1690] łu_, yinz, jpog, _baro_,
+ {{0x6b8d01e7,0x224905c8,0x25a6816f,0x6d49c108}}, // _iwag, _iyak_, drol_, zlea,
+ {{0x6d4520bb,0xa295471b,0x2b4940d5,0xa3ab435b}}, // _ikha, _пані, plac_, _गमक_,
+ {{0x60c1a07d,0x80bb32de,0x798d0fae,0x25f46485}}, // _sblm, श्रे, _kwaw, ंहजी_,
+ {{0x7523b4e4,0xc3cb81b3,0x60cd5a16,0x31b8e009}}, // tinz, تظام_, meam, héz_,
+ {{0x26d9423d,0x2c0ee037,0x7afb8f73,0x6b8d02e0}}, // _haso_, ानां_, _wout, _mwag,
+ {{0x200985c0,0x2367e00e,0x68fb8157,0x8c42b0da}}, // šai_, _rinj_, _toud, леше,
+ {{0x61e2da17,0x6448a03b,0x68e981cd,0xfd64c079}}, // _brol, _dydi, _uned, _ikwụ,
+ {{0x34948808,0x6b8d1a18,0x333fda19,0x1d35a14a}}, // _шатр, _nwag, roux_, _зная,
+ {{0xfbd24095,0x2d8d8079,0x2120005d,0xb4ce2292}}, // נתי_, _iwee_, _smih_, _रसे_,
+ {{0xe1260279,0x69261a1a,0x7c294143,0x64dac04b}}, // емни, емна, _žerl, _मोकळ,
+ {{0x63ad5a1b,0x2d8d8079,0x70aa07fa,0xbebb0aaa}}, // kuan, _kwee_, कलेल, siën,
+ {{0x60cd40ef,0x26cce019,0x60dbd603,0xe3b204aa}}, // deam, bedo_, mdum, درج_,
+ {{0xfd64c125,0x63bbda1c,0x6ec161c9,0x6b9b80b1}}, // _okwụ, mtun, र्मु, _mvug,
+ {{0xf2d2a00b,0x1be730ba,0x75d3a03c,0x2bde0290}}, // װען_, ндри_, _bəzə, मिहा,
+ {{0x64410105,0x60cd5a1d,0x6d4400ce,0xdb021a1e}}, // ßlic, geam, _ukia, bulá,
+ {{0xb4cc0010,0x4fa34a92,0x6b8d1a1f,0xa6e6653f}}, // [16a0] रजी_, литв, _gwag, нжал,
+ {{0xdbc6c10a,0x2cbfc939,0x386d9a20,0x249f81f6}}, // _tööa, hfud_, _ager_, _adum_,
+ {{0x68fc7a21,0xcd022064,0x2929206f,0x24868098}}, // _vord, ęść_, ýtať_, tbom_,
+ {{0xa96a4024,0x6b561a22,0x25adc4b4,0x6568aa36}}, // _нима_, lágb, kuel_, _sidh,
+ {{0x60c41a23,0x26da3a24,0xd12f84c7,0xe8df8119}}, // _mbim, _kapo_, حمن_, _chốn_,
+ {{0x6146604d,0x7aebda25,0xb17b4156,0xb4bca046}}, // _чега, _angt, _krån, ेजे_,
+ {{0x25a69a26,0x3ea000ca,0x6568ba27,0x2fc6c155}}, // srol_, _odit_, _vidh, _spog_,
+ {{0x60dbd518,0x91bb6076,0x61e2da28,0x3ebfc143}}, // gdum, _ממלי, _prol, gfut_,
+ {{0xef1aa42d,0x5ce3c052,0x27e0448f,0x764994b7}}, // _ема_, ующа, nvin_, _gyey,
+ {{0xb4db40f9,0x6d5d0022,0x6b8d00b1,0x0bb74076}}, // _akàl, wmsa, _rwag, עלים_,
+ {{0x62953a29,0x63bbda2a,0x2418e772,0x69d61a2b}}, // vazo, atun, фоны_, _asye,
+ {{0x26ceb953,0x6d4641e7,0x7c29404d,0x386d0271}}, // lefo_, _nkka, _žerm, øer_,
+ {{0xaca461e9,0x26d94e12,0x60daba2c,0xdb02046b}}, // _ayọr, _raso_, _katm, kulæ,
+ {{0xbebdc013,0xdb03c066,0xe66727f7,0x60c4011d}}, // niūn, muná, етбо, _ebim,
+ {{0xe9ff8016,0xb4bee626,0x35a6c033,0x26c05a2d}}, // _khảo_, ुजी_, _खिड़, ffio_,
+ {{0x798d1465,0xa3df6292,0x2249008b,0x2d8322be}}, // _twaw, तिस_, _tyak_, rpje_,
+ {{0x6ca768e7,0xa2b68026,0x31720187,0xa0a760ba}}, // [16b0] ереж, ैभन्, _guyz_, ешел,
+ {{0x63af01e4,0x29095662,0x6b522065,0xdddc6012}}, // mucn, skaa_, lægn, _derž,
+ {{0xa3c5210a,0xe7e3e046,0x2d8d817b,0x27e48187}}, // _एहि_, गिया_, _swee_, _armn_,
+ {{0x237f9a2e,0x63bbda2f,0xc0ab4049,0x644aea53}}, // _ptuj_, ytun, _عاجل_, _myfi,
+ {{0x60dd0109,0x24895a30,0x26da20ce,0x68ed1a31}}, // idsm, mbam_, _yapo_, _mnad,
+ {{0x6722c1e9,0x7d09d4d9,0xf7740087,0x2f142156}}, // _amoj, wkes, אקס_, _vägg_,
+ {{0xe5346052,0x6b44a017,0x8f3460ff,0x7aed1a32}}, // _серь, tògr, _серц, _onat,
+ {{0x67272098,0x68ed1a18,0x7aed02dc,0x6d4d5a33}}, // hijj, _nnad, _nnat, nlaa,
+ {{0x61e4c049,0x26cf80c2,0x1fbdca2a,0x6f0340ba}}, // _áill, mego_, ्बेड, _încu,
+ {{0x7522da34,0xf8aa039f,0x2d9e204a,0x6d4d5a35}}, // _emoz, कल्प, átek_, hlaa,
+ {{0x61edc013,0x62964b87,0x25adda36,0xa3da60c2}}, // _šald, yayo, suel_, ़िए_,
+ {{0x66061729,0xeb9a048e,0x644afa37,0x8af045fc}}, // tykk, мим_, _dyfi, rmək,
+ {{0x7d06c04e,0x67272098,0x60daa19f,0x6d4d4025}}, // ökse, fijj, _yatm, dlaa,
+ {{0x656bda38,0x66060b48,0x63a9ce36,0x60db9a39}}, // _high, rykk, mren, _laum,
+ {{0x6b9bd126,0x644ae0fd,0xfaff477c,0xad9b0009}}, // lsug, _gyfi, _boë_, nyúj,
+ {{0x394dc2b2,0x26cf8064,0x63ae2089,0x44394733}}, // nles_, jego_, rubn, _bxs_,
+ {{0x16664e64,0x645561b6,0x26c041cd,0x68fe3a3a}}, // [16c0] твам, _azzi, rfio_, _topd,
+ {{0xb17b4156,0x6b845a3b,0x63a9cfdb,0xdb00e5fc}}, // _trån, rpig, iren, rumç,
+ {{0x656bc00c,0xdbcc6005,0x6d4d53c1,0x69c98031}}, // _oigh, _póña, blaa, _apee,
+ {{0x6b8455cc,0xdcfec041,0xc6a3848e,0x2d4781de}}, // ppig, īvīb, арши, dões_,
+ {{0x31bdd88c,0x60db9a3c,0x645561b6,0x6d4e3a3d}}, // ्बोध, _daum, _ezzi, olba,
+ {{0xde58a12e,0xa2c1691c,0x62976057,0x6d59cf3d}}, // налі_, र्द्, caxo, _ahwa,
+ {{0xdb0740a3,0x68ee6227,0x26c5a0d4,0x273c8089}}, // zují, _inbd, _dblo_, zíny_,
+ {{0x31604030,0x3ced804d,0xa2a221c9,0x95d8d537}}, // nmiz_, _gnev_, कृत्, едит_,
+ {{0xdb046057,0x799bcc96,0x31bc6071,0x504690c4}}, // ltiñ, gsuw, líz_, _реаб,
+ {{0x86eaa049,0xed598133,0x9bb781a1,0x14aec04b}}, // _تعرف_, _zhž_, _בהמה_, _टाकण,
+ {{0x4c95067e,0x395fc022,0x656bc076,0x60cf0057}}, // линс, bmus_, _figh, tecm,
+ {{0x29000609,0xd026c14a,0x75240012,0xdb046057}}, // _coia_, _имей, _cmiz, itiñ,
+ {{0x7ff3c711,0x3944c013,0x83340a47,0x290000d4}}, // _نسوا, moms_, рнях, _doia_,
+ {{0xaca4e125,0x63bd1a3e,0xfbcfe0eb,0x629a2da8}}, // _kpọz, rtsn, حتی_, ótop,
+ {{0x6e94306d,0x9f44a00e,0x24784385,0x61e64013}}, // рису, _armë_, _ọma_, _arkl,
+ {{0x4734487c,0x0dc7e799,0x6298ba3f,0x2bf3804b}}, // ансс, _руши_, havo, _आपलं_,
+ {{0xe8d90016,0x2d782098,0x69da7a40,0x2571a0a9}}, // [16d0] _nhỉ_, _učen_, _štep, _išle_,
+ {{0x395fca7d,0x59ce28c6,0x6d43a15a,0x61e12156}}, // zmus_, _हैकर, xona, ålls,
+ {{0x6d43a0e0,0x63a4813a,0x2d812174,0x78a2c06f}}, // vona, áinn, _uthe_, _odov,
+ {{0x2900003e,0x26dcfa41,0x7d164066,0x224b4156}}, // _xoia_, _lavo_, chys, _tyck_,
+ {{0x7d009a42,0x29012eed,0xfd5644ac,0x443f4016}}, // _doms, _koha_, _תשרי_, _âu_,
+ {{0xf1c72010,0x69cae0cb,0x78a2c0d5,0x7c2dc15e}}, // _लहान, _opfe, _adov, _žaro,
+ {{0xb4c16d0b,0x2d94a03b,0x656bc1da,0x6b82da43}}, // ्जी_, арыс, _pigh, _atog,
+ {{0x693c8490,0x3ea9b00a,0x394dda44,0xdb02a0c4}}, // včeg, şat_, ules_, stoð,
+ {{0x6298a089,0x3947e0c7,0x395fda45,0xb87b4057}}, // bavo, _pkns_, rmus_, _uníu,
+ {{0x6b9bda46,0x6d445a47,0x394dda48,0x29001a49}}, // rsug, zoia, sles_, _soia_,
+ {{0x2129417b,0x2bd8c028,0xaaab60a8,0x31604e39}}, // kiah_, _बैठा, _चयनक, ymiz_,
+ {{0x62957a4a,0x6f01ba4b,0x249829cd,0x4a74c12a}}, // _lezo, _holc, warm_, _кышт,
+ {{0xe5a5c967,0x6da5cc14,0x29012066,0xef286315}}, // _сили, _сила, _boha_, lmüş_,
+ {{0xf487e312,0x9d56225b,0x26dce013,0xdce3e018}}, // _рудн, انست, _gavo_, _finā,
+ {{0x6609c0e0,0xfaa64407,0x3ea24939,0x3945e9cf}}, // lyek, лаво, _pdkt_, mols_,
+ {{0x25bfc0ba,0x60dd4ce6,0x9f4020d5,0x53b7cba0}}, // otul_, _casm, _triï_, _आमाश,
+ {{0x2d8360dd,0xecea6d82,0x2f166065,0x7d008488}}, // [16e0] _atje_, ндал_, _læge_, _roms,
+ {{0x98a7a013,0x7d0086af,0xf06340d9,0x26ddc0bb}}, // monė_, _soms, скуп, _lawo_,
+ {{0x94bb2095,0xa3b68010,0x2fcb4361,0x290dc052}}, // _אמית, चून_, _epcg_, kkea_,
+ {{0xd6d9814f,0x257985cd,0xd5ba807a,0x60dc7a4c}}, // кті_, _bèl_, еск_, _warm,
+ {{0x2578a011,0xdcfc820d,0xda7aa013,0xac978ae5}}, // _sél_, _utrč, няй_, انها_,
+ {{0x26ddcaf8,0xdb1c8156,0x661bc098,0x6f03412a}}, // _aawo_, _sprä, dzuk, _încr,
+ {{0x6d5b8569,0x2fc053a2,0x2b07e026,0xdb1be0fa}}, // _fhua, ltig_, ाउनु_, ctué,
+ {{0x6d461a4d,0x60c44157,0xf1bf0089,0x629560c1}}, // joka, nfim, ytá_, _zezo,
+ {{0x46b6a0a4,0x29025a4e,0x2578a171,0xa2c165c5}}, // _محاس, _moka_, _wél_, र्व्,
+ {{0x59dca026,0xfc4ac0f9,0x7d01a1f6,0xf77fc1ae}}, // _मनपर, _akíó_, _gols, íça_,
+ {{0x6b8405f1,0x290dc6be,0xdb1e800e,0x7528fa4f}}, // _atig, bkea_, jtpë, ridz,
+ {{0x78a2da50,0xa84ac13a,0x41e6c33e,0x2d49a019}}, // _udov, _كلام_, _مستف, búes_,
+ {{0x6d44c03b,0x31d1604a,0x644e60e8,0x26d251da}}, // čiad, _सन्ध, _lybi, deyo_,
+ {{0x59b8a79e,0x3dc660c2,0xdb1ee156,0x272b00ca}}, // _इमार, łowy_, övän, _bònè_,
+ {{0x7d0d5a51,0x29024551,0x627be1a9,0xe47be095}}, // tkas, _boka_, _אנונ, _ארוכ,
+ {{0x2571a2e2,0xfd4cc031,0x7d02da52,0xa7750192}}, // _ušle_, _ailẹ, _joos, _клич,
+ {{0x29012058,0x2d9eeb9c,0x6d5b8286,0x2bdce2ca}}, // [16f0] _toha_, lste_, _rhua, यिका,
+ {{0xf09f4569,0x7bcd0d87,0xeb9f03d1,0xd257c076}}, // _clàr_, _ipau, msø_, רשמה_,
+ {{0x69daa1f6,0x6d5b805f,0xdc43804a,0x656e62d9}}, // _nste, _phua, _véčk, _dibh,
+ {{0x6d5b8223,0xd0118049,0x19b7e00b,0x2129417b}}, // _qhua, يلا_, רפאר_, qiah_,
+ {{0xbcfb4a7d,0x39a78474,0x25bfc0ba,0x7529c05a}}, // _diét, ушев, xtul_, viez,
+ {{0x212b128c,0xde05a423,0x945dc064,0xfce5b9c1}}, // hich_, ипли, zeńs, роко,
+ {{0xe8d90081,0x629afa53,0x41e46a0e,0xbcfb403e}}, // _chị_, dato, _літа, _chég,
+ {{0x26d369fe,0x2be28485,0x656f40ce,0x7c3b8534}}, // lexo_, पिशा, _jich, _txur,
+ {{0x6d473a54,0x7bcd0223,0x3945fa55,0x80de007c}}, // koja, _npau, tols_, যাম্,
+ {{0xd709e052,0x2d9ee7ff,0xdb046395,0x26c90194}}, // тное_, fste_, ruiç, _abao_,
+ {{0xe8d90125,0xb17b4342,0xf99f00d1,0x6aa7c07c}}, // _ghị_, _bråk, syèl_, ক্ষো,
+ {{0xf8660607,0x7d02d1f1,0xd946048e,0x61460470}}, // ивно, _goos, реми, рема,
+ {{0xd45601e1,0x9cd68095,0x2fdb01f6,0x4ac67a56}}, // ртнь, בורה_, _csqg_, र्डव,
+ {{0x6577200e,0x629ae8cd,0x26dea0d5,0xb05b1393}}, // _buxh, cato, _xato_, pfän,
+ {{0xc896455a,0x395dc05f,0x6f03e13a,0x78bb84a3}}, // ирањ, _khws_, _ionc, _ocuv,
+ {{0xac864494,0x3947a0fa,0x644e743a,0x656e6c91}}, // ргал, hons_, _rybi, _sibh,
+ {{0x8af04497,0x8b2661ba,0x21675a57,0x290374d3}}, // [1700] dmət, адае, рити_, _boja_,
+ {{0xdb062121,0x7d03e94b,0x6d472019,0x6f03e930}}, // dukç, _jons, coja, _jonc,
+ {{0x2bde0eda,0xbcfb43bb,0x60c32009,0x26d25a58}}, // मिका, _piét, _önma, peyo_,
+ {{0xbcfb4019,0x4973603b,0x25ad006f,0x3fe6839d}}, // _diés, іліс, šele_, ижев,
+ {{0x98a32041,0x69c1e046,0x7bc1e10a,0x629afa59}}, // lijā_, htle, htlu, yato,
+ {{0x2d9ee969,0x3ce0005f,0xdb1d20e0,0x0cd31a5a}}, // yste_, _laiv_, ttsé, _तस्म,
+ {{0x98a32041,0x6da36c9d,0x6028c018,0xeb97706d}}, // nijā_, _мира, zīmī, _вис_,
+ {{0x7bdaa032,0x25a98130,0x61edda5b,0x2ee00011}}, // _vstu, šala_, _šala, _naif_,
+ {{0x6d472df7,0xaac66567,0x02c66292,0xe0df40ca}}, // yoja, र्थक, र्थभ, _chòy_,
+ {{0x7bc0da5c,0xada6a0ff,0x69c1fa5d,0x68e29a5e}}, // rtmu, _важл, ftle, ldod,
+ {{0xbca6c050,0x0f576095,0x68e08de6,0x212b1a5f}}, // _نماي, שיים_, _kamd, tich_,
+ {{0x645aba60,0xf8bf5a61,0xd7e6c14f,0x6b82a125}}, // _izti, _amén_, _віко, _ọgwa,
+ {{0x3ce00022,0xdc54c043,0x62972005,0x9c7ca04d}}, // _daiv_, _اراک, _pexo, _tačd,
+ {{0x2d9ee480,0xa3ae8295,0x28df40a8,0x8afee0a2}}, // pste_, _किन_, _पोजि, _riƙw,
+ {{0xb17b46dd,0x7ae29a62,0x6b560057,0x68fce046}}, // _tråk, kdot, cágo, ördu,
+ {{0x79690064,0x257c204a,0x212cf03c,0x6298e052}}, // _oświ, _díl_, lidh_, _hevo,
+ {{0xd3714678,0xaefb458f,0x69dc73c8,0x645cc5fc}}, // [1710] سها_, _biùr, _asre, əric,
+ {{0x67ca4041,0x1ddae0c2,0x8aa728df,0xceb8e013}}, // dējā, _बनात, _трид, ngę_,
+ {{0x290489c8,0x3ce00022,0x2571a012,0x26d3603e}}, // _boma_, _yaiv_, _ošla_, rexo_,
+ {{0x7ae0817b,0x6b5d0ac3,0x7de0a0e0,0xdb1660e0}}, // _camt, légb, zásá, rtyá,
+ {{0x26cb4022,0x6d48f406,0x6b5600e0,0x4aa88407}}, // _ibco_, koda, zágo, ркін_,
+ {{0x672d5a63,0x29c28067,0x1c06e028,0xe164a049}}, // liaj, _dưa_, शहाल_, يدوي,
+ {{0x21290050,0x51846c6c,0xdb046052,0x835280e0}}, // _omah_, _мута, ttiö, _کھلا,
+ {{0x7ae0803b,0x28382095,0x3947b0d8,0x657a2049}}, // _gamt, ינוך_, sons_, íthi,
+ {{0xa3d2c033,0x692b0026,0x7528a098,0xe8d90088}}, // हौल_, třen, _fmdz, _chọ_,
+ {{0xe9d9e6fc,0x6298e2f9,0x63a2c9a3,0x249d8048}}, // гко_, _cevo, _dvon, lawm_,
+ {{0x3946009e,0x672d40ce,0x60d53a64,0x7d160ffe}}, // йног, kiaj, dezm, _plys,
+ {{0x68e1a59c,0xfaff000e,0x69cf4579,0x7ae0807d}}, // _hald, gjë_, _opce, _xamt,
+ {{0x2905aed9,0x33291a65,0x5d85c13a,0x6b5600e0}}, // _kola_, _dmax_, _التل, ságo,
+ {{0x2f18c009,0x6d48fa66,0x70b2204b,0x2bfa804b}}, // _vége_, coda, जलेल, ्हतं_,
+ {{0x75298026,0xb4c5e077,0x249d8022,0x31d48290}}, // _omez, ॉजी_, kawm_, _धन्ध,
+ {{0x394949fe,0xe6d060a8,0x753b9a67,0xdb046005}}, // goas_, सज्ज, _njuz, driá,
+ {{0xf8bf4187,0x63a3e194,0x98a32018,0x3f56222e}}, // [1720] _imél_, _hvnn, sijā_, læum_,
+ {{0x2f1546dd,0xaa7b0431,0x68e09a68,0xaefb458f}}, // _våga_, ndýn, _samd, _chùb,
+ {{0x7d1bda69,0x25a05a6a,0x23600022,0x9d46493b}}, // khus, ssil_, _khij_, _генд,
+ {{0xf4872050,0x6299c7b1,0x8af0403c,0x9b25819d}}, // _پایی, _newo, rməs, офіл,
+ {{0xadc34067,0x7ae08022,0x69c1a227,0x24986095}}, // _thạc, _vamt, _aqle, _term_,
+ {{0x753b8125,0xdd8f4049,0x6143a8f4,0x5f067043}}, // _ejuz, _سوف_, печа, _узга,
+ {{0x3ce241e4,0xf4876043,0x6d4e60f7,0x2be04180}}, // _kakv_, _چاہی, _ikba, निका,
+ {{0x44205a6b,0xa06a8520,0x7644a106,0x69c44213}}, // ozi_, _баба_, şiyo, otie,
+ {{0x80df207c,0x629d1a6c,0x2905ba6d,0xdca6a423}}, // মাত্, vaso, _fola_, _гази,
+ {{0x7d065a6e,0xfc3f4019,0x7ae1ba6f,0x6f0640e8}}, // _loks, _odín_, _galt, _lokc,
+ {{0x69c45a70,0x443ea005,0xd6d50772,0x7d1bda71}}, // htie, _uxt_, ожны, bhus,
+ {{0x6d48fa72,0x6d4aba73,0x6f1bcf3c,0xa3e72148}}, // soda, lofa, chuc, पिस_,
+ {{0x672d5a74,0xdcef612a,0x321ee0c2,0x752d5a75}}, // viaj, _jucă, szty_, viaz,
+ {{0xdd90e50f,0x443f01d7,0x29052069,0xf7732049}}, // _قوت_, ħu_, ðla_, كار_,
+ {{0xd7f82a47,0x60d52157,0x6f065a76,0x33290cc2}}, // бут_, rezm, _bokc, _tmax_,
+ {{0x7ae2c046,0x249d8286,0xe29f4765,0x6e2dd099}}, // _jaot, xawm_, _boða_, _žabi,
+ {{0xa1584407,0xba99614f,0xc299654a,0x6f1d03f6}}, // [1730] сату_, иває_, иках_, mhsc,
+ {{0xa3ce010a,0x69c3a089,0x657aa4cf,0x29186090}}, // _रहत_, ytne, _outh, _clra_,
+ {{0x80cae04b,0x2b5f8067,0x2d9e3520,0x2d87756e}}, // त्ये, _phuc_, šter_, ëne_,
+ {{0xa3aa6021,0xdcef60ba,0x6b589a77,0xa3b983f1}}, // _गटर_, _bucă, sígn, لاتر_,
+ {{0x68e1b8f8,0x26cca071,0x5ee1607c,0x6281fa78}}, // _pald, _bbdo_, নাবে, yclo,
+ {{0x777aba79,0x249d8022,0x27eca090,0xdb238009}}, // _butx, sawm_, _brdn_, _کوری,
+ {{0x657ab98a,0x6f1bd28c,0x2b5f8067,0x6ebfa3af}}, // _cuth, thuc, _thuc_, ल्गु,
+ {{0x75299a7a,0x67299a7b,0xc9a9c14a,0x61fbc108}}, // _umez, _umej, авие_, txul,
+ {{0x63a280e0,0x4ac67a13,0x69c57a7c,0x5bc9610a}}, // cson, र्वव, lthe, _रहुव,
+ {{0x69c5704d,0x6b8d476e,0xf1c3e026,0x3160000a}}, // othe, ipag, _naší_, _shiz_,
+ {{0x7d0762ce,0x23600022,0xe29f0069,0x68e2c569}}, // _mojs, _phij_, fað_, _faod,
+ {{0x212f813a,0xdb09e1af,0x27ed8f08,0x671de1c9}}, // nigh_, rteñ, _kren_, योजक_,
+ {{0x317a2012,0x505a0627,0xdb09e07b,0x629e7a7d}}, // _supz_, ршня_, steñ, yapo,
+ {{0x9f44a1ae,0x693c8261,0x3f8dda7e,0x61e9c0bb}}, // _irmã_, jčen, mpeu_, mvel,
+ {{0x869a4c59,0x23600022,0x6f06405d,0x6d4b9a7f}}, // итат_, _thij_, _pokc, koga,
+ {{0x27ed9a80,0x6abbda81,0x6d4aaf40,0x448a4517}}, // _oren_, nguf, zofa, рбан_,
+ {{0x6d4aa9fa,0x6b8d4b5e,0x60d65a82,0xd838e06e}}, // [1740] yofa, gpag, reym, mača_,
+ {{0x27e01062,0x6b84c049,0x3ebf8253,0x7ae4400e}}, // _isin_, _éige, _acut_, sdit,
+ {{0x6b8989ca,0x7ae3fa83,0xed5a44c4,0x8c3b0105}}, // _ateg, _mant, _вон_, raße,
+ {{0x92b62896,0x938a8439,0x260ae077,0xa2ca60c2}}, // _احکا, аска_, ाहती_, _सॉर्,
+ {{0x2d4f65df,0xa3bbe0c2,0x3f8900c7,0x6b5d00fa}}, // gües_, इंड_, _xtau_, néga,
+ {{0x68e41a84,0x940b4497,0x212f86a5,0x63a45a85}}, // _laid, _necə_, aigh_, msin,
+ {{0x657aa058,0x66dfe013,0xdb09e071,0x29190b8e}}, // _quth, iškė, greí, ūrai_,
+ {{0x26d83a86,0x4ac66064,0x2ca04e88,0x2907e2d9}}, // hero_, र्रव, naid_, _aona_,
+ {{0x26d828fb,0x27ed9a87,0x80c16eb5,0xa2c161c9}}, // kero_, _gren_, र्के, र्क्,
+ {{0xcff74076,0x38cba555,0xad26e049,0x3ea05a88}}, // קציה_, _نامی_, تربو, hait_,
+ {{0xdb09e333,0x3f89012a,0x27e952cb,0x68e41540}}, // creí, _stau_, zvan_, _baid,
+ {{0x74ca6969,0x25b82187,0x8cca7244,0xdce40012}}, // स्तृ, durl_, स्तो, _bunđ,
+ {{0x68e40605,0x2fc69a89,0x63b8a05c,0x60c0805d}}, // _daid, ntog_, luvn, _mcmm,
+ {{0x25fc0028,0x940b403c,0xe8d90119,0x3949c0a3}}, // _ऊपरी_, _gecə_, _thỏ_, čase_,
+ {{0xc1c96064,0x63a45a8a,0x6d4b92cc,0x69c5658f}}, // _रहेग, esin, yoga, xthe,
+ {{0x692b0026,0xbcfb413a,0x2fc69a8b,0x98a20013}}, // zřej, _bhéa, ktog_, tiką_,
+ {{0x7c3b40a9,0x61ee62a4,0x0ca8414a,0x673aeb83}}, // [1750] _žurb, _arbl, ютри_, ontj,
+ {{0x26d83a8c,0x61ee63ee,0x81cde07c,0x249fda8d}}, // cero_, _brbl, রির_, zaum_,
+ {{0x13d3c07c,0x6b8d5a8e,0x753ae108,0xdb1bfa8f}}, // ়িয়, spag, intz, ntuá,
+ {{0x6f1aa12a,0x5ead007c,0x6441a05d,0x27ed975e}}, // _altc, ট্রে, _rxli, _pren_,
+ {{0x82348050,0xdb09e1ae,0x7d076048,0xbbd9c026}}, // ورها, queç, _tojs, _भनेक,
+ {{0x2db80095,0x9ccba0fb,0x2d95a11f,0xdb1be803}}, // קלון_, рыда_, прис, ktuá,
+ {{0xdb07403a,0x7bc73a90,0xd9a81a91,0x69374098}}, // dujú, ntju, _कट्ट, tćeh,
+ {{0x26d83a92,0x212bc0d1,0x6562c090,0x2fdf8022}}, // zero_, òch_, _choh, _tsug_,
+ {{0xb17b4249,0x249fd3e0,0xe3b1c049,0x6f1b8071}}, // _gråt, raum_, ررة_, _iluc,
+ {{0x672d19a5,0x44b4add1,0xe576c1eb,0x291efa93}}, // _amaj, _обус, язь_, ghta_,
+ {{0x2907ecce,0x26d90631,0x7bc52048,0xdee600bc}}, // _vona_, feso_, _nqhu, пони,
+ {{0x3980c0d1,0x629d5a94,0xdb1c4066,0xdbd02066}}, // _fòs_, _neso, ktrá, užív,
+ {{0xee3700ff,0x21a30052,0x657d4003,0x291ee420}}, // зня_, ниям, _nush, bhta_,
+ {{0x5fbfa026,0x3ea05a95,0x63a446bf,0x6562c05d}}, // _एमाल, wait_, vsin, _zhoh,
+ {{0x551fc55d,0x629c6011,0xdb1be057,0xa96a01c0}}, // _बताए_, _rero, ctuá, _лика_,
+ {{0x63864926,0xec7a4e12,0xd83f0c49,0x628568db}}, // згаа, спа_, ščak_, echo,
+ {{0x3b07433f,0x69c72200,0x65641a11,0xb2bb0095}}, // [1760] чето_, atje, _ihih, _המפר,
+ {{0x213f8187,0x212d818c,0x7c3b4a5f,0xe29a674c}}, // _ajuh_, _ameh_, _žurc, саж_,
+ {{0xc0472119,0x6d5641e7,0x41b668e4,0x777c6401}}, // _mượn_, plya, ясат, _qurx,
+ {{0x7d0987db,0xdb0ac156,0x7d08a07f,0xdddb8012}}, // _coes, ltfö, _sods, _sfuš,
+ {{0x2d98e359,0xdb046aaa,0x6f08ba96,0xaac10026}}, // árez_, nsië, _podc, ष्टक,
+ {{0xa9a66a18,0x39400143,0xb87b0009,0xdbc74046}}, // _нигд, _ljis_, gdíj, tööt,
+ {{0xf41f03f5,0x6e228009,0x52d86046,0x672e61f6}}, // nbär_, szob, ड़ीस, _imbj,
+ {{0xa6b5804e,0x98a4c03b,0x7ae643e0,0x3983a03c}}, // _осущ, kimą_, _jakt, _qısa_,
+ {{0xba3b43c5,0xa2c4a026,0x24869a97,0xdb07402e}}, // _geïd, _राप्, mcom_, tujú,
+ {{0xf55aa13a,0x6aa0cb48,0x98a5e0ba,0x3981f2d9}}, // _الطب_, samf, vilă_, _bós_,
+ {{0x3f591a98,0x3981e32a,0xbcfb1a99,0x8cc10148}}, // séum_, _cós_, lméi, _वादो,
+ {{0x290a7a9a,0xc7b36095,0x6d58b31f,0xe0cfe050}}, // _loba_, עבר_, llva, پزی_,
+ {{0x78bd1a9b,0x6d58a747,0x61207a9c,0x78a1fa9d}}, // rgsv, olva, _köld, balv,
+ {{0xdddbc12a,0x70770555,0xe6c888ae,0x61e2d5f3}}, // scuţ, _کاغذ, रभुज, _isol,
+ {{0xb87b4057,0x7ae522df,0x24868939,0x3d0d28c6}}, // _faíl, _vaht, kcom_, _सकीं_,
+ {{0x657d4393,0xd7bbe076,0x765560c2,0x776401f6}}, // _push, _הצהר, _ryzy, _ghix,
+ {{0x224d20c2,0x7b3c9a9e,0xdb0d61de,0x2486802e}}, // [1770] łek_, rčul, nuaç, dcom_,
+ {{0x6b8d1a9f,0x61edd3f9,0x7d0980f0,0x673bdaa0}}, // _itag, _šalk, _soes, znuj,
+ {{0xdb046004,0x2ee5baa1,0x99d961c1,0x657e2229}}, // isiè, _ralf_, تواء_, _guph,
+ {{0xa3acc04b,0xdb0d63f6,0x32cfad5d,0x311485d0}}, // गळा_, mraí, zıyı_, нфіс,
+ {{0x2a612012,0xed5880ff,0xaf3760eb,0xdee5a517}}, // _hzhb_, чої_, _فرست, доли,
+ {{0x798d00ce,0xdb1c8049,0x6b560071,0x3ce94046}}, // _mtaw, _bpró, rági, ldav_,
+ {{0x78a1e2df,0x2d862320,0xa3d2a182,0x399ab12d}}, // valv, _čoek_, војч, gūs_,
+ {{0x27f86066,0x248694b3,0x6d58a005,0x6b560071}}, // šený_, ccom_, alva, pági,
+ {{0x61ed4dab,0xd0a8c13a,0x7e7b9aa2,0x2d99cbf9}}, // nval, سطين_, _igup, èse_,
+ {{0x2fc94105,0x98a4c03b,0x7c248569,0x399aaa98}}, // itag_, vimą_, ùire, būs_,
+ {{0xa96a0971,0x6aa1e78c,0x9d461aa3,0x6b8d07ef}}, // жина_, ralf, денд, _atag,
+ {{0x8fa6027c,0xdbd78052,0x3015c1ef,0x61e2c227}}, // маме, päät, _одер, _fsol,
+ {{0x78a1e5be,0x7ae9c061,0x6d5980d5,0x2cbfc954}}, // palv, mdet, ilwa, mgud_,
+ {{0x28cf7244,0x2fc94065,0x6b8d0049,0xf8cee067}}, // स्मि, dtag_, _dtag, _hứng_,
+ {{0xdb0adaa4,0x3ea20200,0x5894e13a,0x69c9d11b}}, // rtfö, pakt_, _أجهز, ltee,
+ {{0x6d4f00b8,0x7e7b9aa5,0x680ae03c,0xdb0acc31}}, // coca, _ngup, qəda, stfö,
+ {{0x68e7613e,0x7ae9daa6,0x31648143,0x6365c005}}, // [1780] _bajd, idet, _rhmz_, mónd,
+ {{0x7ae9c04e,0xfe7268b8,0x6d4e3aa7,0xc05a7096}}, // hdet, ندا_, qoba, орец_,
+ {{0x68e7600a,0x6e462423,0xf8bf1aa8,0x04462691}}, // _dajd, _ценз, ciés_, _ценн,
+ {{0xaca3a096,0x6d41a143,0x61ed4018,0x7ae65aa9}}, // _faịl, _ajla, bval, _uakt,
+ {{0x60dbc2b2,0xdb04613a,0x97da8566,0x2ca00d77}}, // deum, iriú, تظار_, _meid_,
+ {{0x7ae9d683,0x317ea526,0x6d4f1aaa,0x28cfe4e5}}, // edet, _putz_, zoca, त्मि,
+ {{0x7b102105,0xda0e2604,0x1dd26dad,0x693c802e}}, // _häus, सहित_, _सहित, nček,
+ {{0x7ae9daab,0x8cca63af,0x68e9daac,0x27edc262}}, // gdet, स्रो, gded, fven_,
+ {{0x6b8de012,0x63bbdaad,0x81d6c1a6,0x61e40194}}, // _čaga, guun, ায়ন_, _nsil,
+ {{0x93fb40be,0x98a00064,0x60dc214f,0x78a08156}}, // קליי, _imię_, jerm, _hemv,
+ {{0x3d022216,0x34b76095,0x69c9c335,0x290ca0ae}}, // शाने_, ופים_, atee, _ioda_,
+ {{0x7bc8e23c,0xdbd961ae,0x92bee07c,0x63bbcb90}}, // rtdu, cçõe, ঁজে_, buun,
+ {{0x6aa08622,0x290cbaae,0x61e400e0,0x7ae8a493}}, // _memf, _koda_, _csil, _kadt,
+ {{0x68e8baaf,0x60dc38bd,0x6aa080da,0x7e62dab0}}, // _jadd, germ, _lemf, _izop,
+ {{0x5bdca969,0xead54662,0xa4d5412e,0x8c3dc5fc}}, // _मनोव, новь, нові, maşa,
+ {{0xdb0580dd,0x290cb673,0x78ad00bb,0xadc3c1e9}}, // rshë, _loda_, _udav, _apẹj,
+ {{0xd56727ca,0x58844056,0xe7bcc07c,0x6b8d0631}}, // [1790] _отоп, тыса, _অনুয, _utag,
+ {{0x2fc95ab1,0x60dc3ab2,0x69cfa148,0xa91daebe}}, // rtag_, cerm, _दहली, _kuže,
+ {{0x7cd98491,0x2fc95ab3,0x7ae9dab4,0x63bc2071}}, // змер_, stag_, ydet, curn,
+ {{0x26dc000b,0x81d5c1a6,0x69c9c052,0x38b561e9}}, // ַקומ, হিত_, ytee, _bùrà_,
+ {{0x3ce90037,0x1959a20e,0xeb998318,0x61e4005d}}, // _maav_, пады_, чил_, _xsil,
+ {{0x25a947d2,0x02dc0026,0x41bc0076,0xdb1d222e}}, // msal_, _बस्न, קצוע, stsæ,
+ {{0xd378a012,0x3f812b23,0x2d813ab5,0x6aa09ab6}}, // _dić_, _juhu_, _juhe_, _femf,
+ {{0x75229ab7,0x26164010,0xfba35244,0x63a993df}}, // choz, पैकी_, _ओबाम, _tven,
+ {{0x60dbc7ad,0x3219c066,0x3ea01ab8,0xee39c2d7}}, // reum, ásy_, _reit_, яни_,
+ {{0x60c41ab9,0xa205f386,0x4425f972,0x68e9816f}}, // _scim, епод, tzl_, _haed,
+ {{0x7778faba,0xed59e098,0x7afb894b,0x63bbc6d1}}, // _divx, juže_, _knut, suun,
+ {{0xe29703dd,0xa91da0e4,0x8c1a6095,0xed59e29c}}, // наю_, _duže, פורי, duže_,
+ {{0xaca36125,0xdb1d2156,0x7981a08b,0x741607f0}}, // _ayọc, ktsä, _hulw, روسا,
+ {{0x7981aee2,0x2d5c8017,0xfd1f4425,0x5cd52823}}, // _kulw, víem_, _alì_, кісх,
+ {{0x7658e013,0x5239e053,0xf484f9ad,0xb4abe04b}}, // _gyvy, _מײַנ, _ручн, गणी_,
+ {{0x656d4d72,0x8af0403c,0x66e359ad,0x6b81babb}}, // gmah, lməz, лоса, _mulg,
+ {{0x05667342,0x78a440bc,0x377541ba,0xf772c053}}, // [17a0] еван, raiv, тырс, רקן_,
+ {{0x3ea5fabc,0x2d5c85df,0x63b64c8a,0x7a0ae5fc}}, // nalt_, ríem_, rryn, kətl,
+ {{0x69caa097,0xdb062066,0x429b21a9,0x2571c143}}, // xtfe, jské, _מסיב, _ešli_,
+ {{0x80de007c,0x291ea939,0x6f0d0005,0x98ab00ba}}, // যাক্, _slta_, _xoac, mică_,
+ {{0x78a1babd,0x3ce0ea11,0x69374055,0x2d55814f}}, // _belv, _कसले_, rćet, vået_,
+ {{0x2b405abe,0x67229abf,0x3ea5e10a,0x628bdac0}}, // lnic_, shoj, jalt_, _afgo,
+ {{0x26dca490,0x68e99ac1,0x98ab01fc,0x9d437ac2}}, // revo_, _faed, nică_, _серд,
+ {{0x2d825ac3,0x290ca0c2,0x79808284,0xdb046057}}, // _kuke_, _woda_, _tumw, muiñ,
+ {{0x93c88043,0xb146a739,0xa3e87ac4,0x68e8bac5}}, // _تازہ_, _знал, बिक_, _tadd,
+ {{0x39404105,0xf1b34694,0x6d561304,0x6f0e62bd}}, // hnis_, נסת_, _akya, _jobc,
+ {{0x6e95009e,0x3f825ac6,0x7d0d03ac,0x07376076}}, // кину, _luku_, _poas, תאים_,
+ {{0x8c1ba095,0x2d813ac7,0x25a94025,0x39a6e0f9}}, // _מובי, _ruhe_, ysal_, _aísá_,
+ {{0x27e0c65f,0x7d0e61e7,0x6d440030,0x0b8b014a}}, // _èin_, _oobs, _djia, ясни_,
+ {{0x6456a5fc,0x613b4d6e,0x27e6c7e0,0xa3c00290}}, // əyir, kälä, _ison_, ँढ़_,
+ {{0x8af0403c,0x657abac8,0x3957e095,0xcf57e095}}, // lməy, _mith, _רשום_, _רבות_,
+ {{0xbcfb4004,0x39405ac9,0x6b82c167,0x611826fa}}, // _thém, gnis_, _kuog, _ağlı,
+ {{0x3ea68426,0x645aa013,0xdcfba012,0x8af045fc}}, // [17b0] daot_, _lyti, _suuč, nməy,
+ {{0xce6b6657,0x3f8f4041,0xf9900049,0x7a0ae03c}}, // пред_, īgu_, _ابي_, yətl,
+ {{0x291f8048,0x290d8561,0xdb047377,0xe8df6119}}, // _plua_, _poea_, guiñ, _ngục_,
+ {{0x2b4050bf,0xcda80050,0x26c6c048,0x2c0e2295}}, // cnic_, _تهیه_, _ncoo_, ठहरू_,
+ {{0x777aa265,0x7aed40ba,0x6b82c013,0x6d5bc022}}, // _bitx, mdat, _nuog, vlua,
+ {{0x7c87a04e,0x81d6c07c,0xbc3820eb,0x29094855}}, // худе, াটা_, _آسیا_, ljaa_,
+ {{0x9412003c,0x78a2daca,0x9f4bfacb,0x29c98510}}, // _deyə_, _beov, ící_, súa_,
+ {{0x2ca5facc,0x78a1a3c5,0x63b8606f,0x6b886071}}, // wald_, _welv, ávne, _édga,
+ {{0x3ea37142,0xb4dd8046,0x26defacd,0x316ddace}}, // _kejt_, तजी_, feto_, rmez_,
+ {{0xbcfb004a,0x7aed5acf,0xdb0620a3,0x2ca361f6}}, // hléd, hdat, pské, _jejd_,
+ {{0x3ea5effe,0xd629e53f,0x765b8037,0xb4dd08c6}}, // ralt_, фоне_, _hyuy, ड़ू_,
+ {{0x4087c14a,0x33332ce5,0xe8044026,0x6aca20c2}}, // _чужб, _amxx_, रममा_, िभ्र,
+ {{0x61e1e1cd,0x290fdad0,0xfd5982f4,0x6b54e017}}, // swll, _ioga_, _bibẹ, ràgr,
+ {{0x2bd26028,0xaca3e088,0x27262067,0x3ea25ad1}}, // _सहला, _kpọk, _mông_, _sekt_,
+ {{0x3f8360eb,0x394059d3,0x27262016,0x693c8299}}, // _nuju_, tnis_, _lông_, mčev,
+ {{0x93434a47,0x3a3f4133,0x7aebc522,0xf99f40d1}}, // ансе, _župa_, _jagt, _bwè_,
+ {{0x2b405ad2,0x6d41fad3,0x7d1bd407,0x3ea240ff}}, // [17c0] rnic_, onla, ikus, _vekt_,
+ {{0x6d41fad4,0x7d1bc134,0x656987d4,0x68ed5ad5}}, // nnla, hkus, _aheh, adad,
+ {{0x18a68e4f,0x765b80e8,0x321c6066,0x9f4da2a8}}, // ваем, _ayuy, ávy_, _creí_,
+ {{0xe9ffa016,0x6123410a,0xee3a95ea,0x2d836133}}, // _nhằm_, _põle, мне_, _duje_,
+ {{0x7c29dad6,0x98a7af76,0x201c600a,0xdce3e133}}, // jzer, linę_, švi_, _linđ,
+ {{0x78a41114,0x7c29d100,0x9c7cbad7,0xdb1b0071}}, // _leiv, dzer, _kači, ruté,
+ {{0xb8dabad8,0xec776399,0x6363810a,0x97a3a013}}, // _आए_, _апр_, sõna, арыл,
+ {{0x9c7caf9e,0xdb1b00fa,0xa06a8808,0x7aebc025}}, // _mači, puté, _жаба_, _cagt,
+ {{0xb87b0009,0xc8f2c148,0xf366e474,0x98ab08fd}}, // ndít, _अफीम_, ктон, nicą_,
+ {{0x657aa23c,0x2ca7a12a,0xf8eb0028,0x7bce3ad9}}, // _uith, cand_, _जोड़ा_, ktbu,
+ {{0x2369005f,0x3f848167,0x91820067,0x33750255}}, // _phaj_, _humu_, yện_, лгор,
+ {{0x9c13bada,0x61e45adb,0x6124e0d1,0x7aed4171}}, // _kọnk, kwil, _sòld, xdat,
+ {{0xaed4e1fc,0x27262119,0x6b8407ad,0xfb1bc076}}, // _болш, _xông_, _cuig, _קודמ,
+ {{0x69c0c0b1,0x7aed4187,0x8233e555,0x6aa3fadc}}, // mume, wdat, اریا, _genf,
+ {{0xb4bee518,0x6b5609fe,0x78a72d52,0x443ae046}}, // इली_, rágr, rajv, _äp_,
+ {{0x63ad84f3,0x69cd404e,0x63ad0042,0x2ca7badd}}, // šani, ttae, _tvan, zand_,
+ {{0x3f836c12,0x26c91ade,0x2139193d,0x195845be}}, // [17d0] _suju_, _icao_, mish_, таты_,
+ {{0x3ea7aca2,0xdd114041,0x69cd4108,0x31c445da}}, // xant_, _kļūd, rtae, јств,
+ {{0x7c29dadf,0x7bcd53a7,0x6d55212d,0x27262016}}, // zzer, stau, moza, _sông_,
+ {{0x3cfea258,0x2ca7a5c4,0x3f848035,0x9f4920b8}}, // _antv_, wand_, _bumu_, _braç_,
+ {{0x14d640be,0x22498106,0x6d58e06e,0x4ac604e5}}, // _גורל_, çak_, _okva, _वासव,
+ {{0x6722c07b,0x3ea943c5,0x1869aa09,0xa069a520}}, // _aloj, laat_, нали_, нала_,
+ {{0xa493c54f,0x6aa3f890,0x39aee03b,0x4429c06f}}, // لیات, _renf, jęs_, _ťa_,
+ {{0x3ea94e9b,0xa6e5c9b1,0x6e29dae0,0x2ca7bae1}}, // naat_, ужил, tzeb, sand_,
+ {{0x3ea7a1ab,0x7983e082,0x78a406c7,0xa2ca68b1}}, // pant_, _sunw, _reiv, स्क्,
+ {{0x2fcdc258,0x7d09dae2,0x3ea95ae3,0x213901e7}}, // rteg_, sjes, haat_, eish_,
+ {{0x7d09c7a0,0x645d5ae4,0x693c849f,0x7aebdae5}}, // pjes, _lysi, rčev, _tagt,
+ {{0xbcfb413a,0x68ed0054,0xa3e444ef,0xa2d3e029}}, // _fhéi, _caad, _भईल_, _बॉर्,
+ {{0x2ca94054,0xb4dfcf30,0x6aa3e227,0xb05b0851}}, // daad_, दजी_, _tenf, sgän,
+ {{0x8afce064,0x2f18c0e0,0x6722c098,0xfaff400e}}, // ględ, _régi_, _zloj, _anë_,
+ {{0x7aed1ae6,0x9c7ce12d,0x2f142156,0x60c98143}}, // _faat, teče, _sägs_, _ocem,
+ {{0x63ad5598,0x61e441ea,0x248dc858,0x69db84cd}}, // fsan, twil, ncem_, _opue,
+ {{0x7bce37a7,0x777d4057,0xa2cae4e5,0x61260d64}}, // [17e0] stbu, _disx, त्क्, _cóle,
+ {{0x9c7ce3de,0x68ed153f,0x3f85a052,0x61e449cd}}, // seče, _zaad, _oulu_, rwil,
+ {{0x2912035f,0x2ca94054,0x6d59cbd4,0xeb9a64a7}}, // _koya_, baad_, _okwa, хие_,
+ {{0x63ad4025,0xa91da0a9,0xf539c06f,0x0cbec1a6}}, // bsan, _ruža, jiť_, _আস্ত,
+ {{0x6d445ae7,0x35b688ea,0xb8dc8af7,0x29121ae8}}, // mnia, ущес, _आय_, _moya_,
+ {{0x2912017b,0xfc3f4005,0x657d539d,0x63bbd132}}, // _loya_, _edís_, _zish, drun,
+ {{0x7ae1fae9,0x69c1faea,0x6ed62029,0x186770ba}}, // delt, jule, म्यु, _бари_,
+ {{0xdc082041,0x29120322,0x2d849aeb,0xdb0d61ae}}, // dēļa, _noya_, _tume_, traç,
+ {{0x7aee6524,0x7a0ae03c,0x77928050,0x2ca5a0cb}}, // _kabt, məti, _ایتا, _feld_,
+ {{0x7c3b4e3d,0x6d445ac1,0x7ae29aec,0x680ae03c}}, // _žurk, hnia, leot, lədi,
+ {{0xf539c03a,0x6d565aed,0x6d5520e4,0x2d982927}}, // biť_, koya, voza, jpre_,
+ {{0xac190503,0xa91da0f5,0x92e0607c,0x6da6c90f}}, // вому_, _južn, থায়_, _бида,
+ {{0x21390db2,0x6d445aee,0xddcbfaef,0xfc3f0057}}, // rish_, dnia, čišt, raío_,
+ {{0x7aee6048,0xce94e1e1,0x657d41fb,0x2ca94054}}, // _nabt, _такъ, _sish, waad_,
+ {{0x645d53fb,0xceb4403c,0xc7b821ba,0x657d5af0}}, // _sysi, rkən_, лёт_, _pish,
+ {{0x63a2d3f3,0x5c07414a,0xf2075af1,0x67240171}}, // _kwon, ляза, лязо, _glij,
+ {{0x798640a2,0xdb1b0066,0x629c291e,0x6d43a0fd}}, // [17f0] _cukw, nutí, bbro, ynna,
+ {{0x2ca9436d,0x645d406f,0x6d445af2,0x60c98024}}, // saad_, _vysi, ania, _scem,
+ {{0x657d5af3,0x6d565af4,0x60c5605d,0x7aee4052}}, // _tish, boya, rghm, öntä,
+ {{0x61207af5,0x25addaf6,0x8f9a2095,0xd9f08021}}, // _höll, ysel_, _שירי, चिंत_,
+ {{0xa3e5612f,0x636b620f,0x3f85baf7,0x6b864187}}, // _बैर_, tünd, _sulu_, _gukg,
+ {{0x6d59c306,0x9f4da049,0x4ea7a0ba,0x7bc1e6fa}}, // _skwa, _breá_, урда, yulu,
+ {{0xdb02a9c1,0xab5b2105,0x636b7af8,0x9f4da071}}, // groñ, prüf, ründ, _creá_,
+ {{0xdc2121d7,0x63a1203a,0x2d85baf9,0x6d5aacc5}}, // _aċċe, álny, _vule_, _ekta,
+ {{0x629bc03b,0xf576c13a,0x3ea5b9eb,0xfba52960}}, // rbuo, جميع_, _telt_, गीतम,
+ {{0x6d445afa,0xdb1c4042,0x69dd417b,0xdbdea765}}, // znia, strø, _mpse, _tíða,
+ {{0x2a690ce1,0x225f8c12,0x67240579,0x236d805f}}, // _azab_, _hyuk_, _plij, _khej_,
+ {{0x2498e3df,0x8503a046,0x6d59c544,0x657e20bb}}, // ırma_, लावट_, _ukwa, _siph,
+ {{0x68e1fafb,0x7404007c,0xe3c7a07c,0x8fa5cfdb}}, // peld, উন্ট_, _শনিব, _тале,
+ {{0x26c7b5ab,0xa96a2c6c,0x98ab0041,0x8235e050}}, // ngno_, тига_, ējās_, _پرتا,
+ {{0x29121afc,0x7a0ae03c,0x68e3ab55,0x398b8654}}, // _toya_, yəti, iend, _løs_,
+ {{0x67241afd,0x96ba10ac,0x21295afe,0x3ea7eb48}}, // _ulij, _руку_, ghah_, _hent_,
+
+ {{0x442dc547,0xdb09faff,0xe2ca6de1,0xb5fb40f9}}, // [1800] nze_, queó, клад_, _ayáb,
+ {{0x69dc7b00,0x21294022,0x7f44400e,0x130360ff}}, // _ppre, ahah_, sniq, озум,
+ {{0x7aef48cd,0x068684c9,0xc6a687c1,0x2d85204a}}, // _fact, аген, арли, ílet_,
+ {{0x7c446098,0xeb9a8c4e,0xaad228b1,0xe3a7c8b8}}, // _čurć, вид_, द्रक, _ور_,
+ {{0x6aaaa11d,0x44208067,0x26e5e290,0xeb06bb01}}, // taff, ái_, _कसूर_, ичко,
+ {{0x69c3bb02,0x6d5b9b03,0x60cbc090,0xa91da6ca}}, // fune, _ekua, _acgm, _dužo,
+ {{0xdcc60029,0x8884c062,0x2d87e860,0x693c8098}}, // _वांछ, میان, _nune_, učes,
+ {{0x753af3d5,0x7d008071,0xa96a898c,0x21204364}}, // witz, _unms, _риба_, nkih_,
+ {{0x5506efdb,0x272ac067,0xeb06e279,0xa534e12a}}, // ачна, _lùng_, ачно, йнич,
+ {{0x69de223c,0x6d461b04,0x656280dd,0xdb1c4333}}, // _oppe, enka, lloh, durí,
+ {{0x25ec8033,0x9813c050,0xb8cee3fa,0x6d220c87}}, // _आईडी_, مبیا, _ओज_, मसंग_,
+ {{0x612b2046,0xdb046005,0x69c44a13,0x3f87ef8f}}, // _külg, iriñ, kuie, _dunu_,
+ {{0x8d94c13a,0x6d4561cd,0x3eab1b05,0xfd57a0f9}}, // _التش, ynha, ract_, _abaṣ,
+ {{0x3f87e133,0x673ae00e,0x80aa6077,0x3d04c7fa}}, // _funu_, qitj, टरफे, वावे_,
+ {{0x93882731,0x92a7455a,0x612079d3,0x0b882822}}, // иста_, ијал, _völl, исти_,
+ {{0xc27be00b,0x78a8ab30,0xfaff000e,0x8a7be095}}, // _ברוי, _ledv, rmës_, _באות,
+ {{0x26db0022,0xbcfb1b06,0x7988bb07,0x2129405d}}, // [1810] _ibqo_, cléa, _ludw, rhah_,
+ {{0x2ca91b08,0x3949c06e,0x64a70028,0xf8ef80c5}}, // _head_, časi_, _ख़ुश, _घोड़ा_,
+ {{0x62852079,0x2d8ae06f,0x7d02c614,0x63a41b07}}, // _igho, íbeh_, _onos, _zwin,
+ {{0x5c759156,0xe50563fa,0x85056028,0xbd689116}}, // блет, राहि_, राहट_, арче_,
+ {{0x8af045fc,0x798080ca,0x67f06009,0xeef80989}}, // tləv, _dimw, tójá, ימאר_,
+ {{0x6aad4e47,0x6d473b09,0x6d5d40ae,0xdb04603e}}, // laaf, nnja, _hksa, briñ,
+ {{0xd37b80ba,0x3ea7e076,0x6aa8a16f,0x60da2466}}, // _ача_, _rent_, _dedf, õtmi,
+ {{0x0ae9c662,0x850565e8,0xd1056437,0x6d5c604a}}, // удий_, रावट_, रावण_, _zkra,
+ {{0xa1948905,0x661d0065,0x673d00ff,0x2bda8555}}, // _матч, jysk, disj, _مالک_,
+ {{0x6729c132,0x6f02db0a,0x3ea90019,0x8af0403c}}, // shej, _enoc, _aeat_, llət,
+ {{0x6aa99b0b,0x61fb800a,0x7bc56b5a,0x9c7ce742}}, // _keef, _hrul, nuhu, teča,
+ {{0x62853b0c,0xa3bb8028,0x27e94795,0x673c2037}}, // _agho, _आटा_, kwan_, wirj,
+ {{0x753de043,0x395838bb,0xc588c016,0x27e95b0d}}, // észs, tors_, _hồn_, jwan_,
+ {{0x661c3b0e,0x6d472143,0x6370610a,0x61e9db0f}}, // tyrk, gnja, mäng, mwel,
+ {{0x61ed09dd,0x7d1ce097,0xd838a06e,0x60db8ee2}}, // _esal, örse, _peči_, _obum,
+ {{0x39583b10,0x6362601f,0x86995b11,0x6aa99b12}}, // sors_, môni, итут_, _neef,
+ {{0x3946800a,0x3f890fb3,0xf388c067,0x39582b70}}, // [1820] vnos_, _guau_, _lợn_, pors_,
+ {{0x6d58a942,0x2fd24156,0xdb04641a,0xed59c04a}}, // xova, rtyg_, isiç, hož_,
+ {{0xc333200b,0x27e94258,0xe73a5138,0x7e608dee}}, // _מוז_, awan_, _бен_, _symp,
+ {{0x984a8627,0xb4c28295,0x2d5c80b6,0xf38a8082}}, // ляла_, ंले_, víes_, _aṣa_,
+ {{0x69c61135,0x61ed005d,0xad9b41e9,0x7981b598}}, // nuke, _xsal, _arúf, _bilw,
+ {{0x60db8291,0x26d821cd,0x96f8a1fc,0xc588c119}}, // _ebum, nfro_, рект_, _bồn_,
+ {{0x3eaa6057,0xdb1d21ae,0x7988a17b,0x25bfc0ba}}, // _iebt_, lusã, _qudw, erul_,
+ {{0xa4918896,0x6d598064,0xbc1b6095,0x1ae4207c}}, // _قیمت, cowa, _גולש, মায়া,
+ {{0x6b81a0f7,0x3eb86037,0xe9a385da,0x673d014f}}, // _filg, _kdrt_, _натп, visj,
+ {{0x6d5e2088,0x8c46a4a7,0x95d8e01b,0x8afca0c2}}, // _nkpa, _лезе, адот_, _chęt,
+ {{0x2ca9010a,0x3eace0e0,0xe8d90067,0x27e000f9}}, // _pead_, radt_, _chở_, _apin_,
+ {{0x81bce2b8,0xc27b6087,0x6c86c13a,0x1959003b}}, // dzēt, טריי, _الإم, рамы_,
+ {{0x7d1dc0cb,0x5eab01a6,0x3f8a7b13,0xd8d78053}}, // össe, _কাদে, _lubu_, זוכט_,
+ {{0x87b7c3c8,0x2d6840cb,0x661d00c2,0x81df61a6}}, // _פלוס_, ußen_, rysk, তিত_,
+ {{0x7ae61b14,0x653be095,0x61ed005d,0x2fc05b15}}, // bekt, _תעוד, _wsal, erig_,
+ {{0xbcfb40f9,0x26c94480,0xa91da579,0x69c607c7}}, // _ahér, ugao_, _ružm, buke,
+ {{0x6aa994d0,0x7bc61b16,0xdb2400e0,0x69d82277}}, // [1830] _reef, cuku, _írás, _även,
+ {{0xd13120d0,0x3f8a73c7,0x612b27d2,0xaefb503c}}, // _آمد_, _bubu_, _büle, _chùr,
+ {{0x2ba94033,0x65645b17,0xa2ce2437,0x27e940a2}}, // _चौरा, blih, _तात्, swan_,
+ {{0x01c94043,0x9df9826f,0xcf93e00b,0x2486c0a2}}, // _پوسٹ_, инет_, לטע_, _ngom_,
+ {{0xa2c4a295,0xdfc6c243,0xbddb45df,0x6b89865f}}, // _राख्, _تي_, _exèr, _queg,
+ {{0x3f824098,0xd5dd2028,0xad9b4031,0xdd86fb18}}, // _ziku_, _पहाड़, _arúg, _يو_,
+ {{0x7bd52e3b,0x8af04497,0x6d599b19,0x3d0ce0c2}}, // ntzu, mlər, powa, ़ाने_,
+ {{0x672d4605,0x61fb9b1a,0x25bfc12a,0x254cc04a}}, // nhaj, _trul, trul_, těla_,
+ {{0x27ed92b6,0x2d837b1b,0x3eadc271,0xe0df4030}}, // _usen_, _kije_, raet_, _ajòk_,
+ {{0x3eaf8654,0x6f1600b6,0x2d837037,0x69c61b1c}}, // lagt_, _soyc, _jije_, vuke,
+ {{0xd838a3a1,0x3669e0de,0x7ae72026,0x64416579}}, // _beču_, рано_, dejt, _žlij,
+ {{0x27e0c12a,0x3f836143,0x2ee5fb1d,0xdb1b0089}}, // _ţine_, _liju_, self_, tutá,
+ {{0xa2cc8578,0x1d09e01b,0x6d89406f,0x68e61b1e}}, // _हास्, _вели_, _uľah, rekd,
+ {{0x6d4d1b1f,0x6d5af39c,0x39494b8e,0x21290037}}, // _ejaa, cota, enas_, _elah_,
+ {{0x3ea05b20,0x69c600bb,0x2905b2c2,0x2d8b5b21}}, // tbit_, suke, _mnla_, _auce_,
+ {{0x2b495b22,0x6f040ea3,0x4d66412e,0x752d5241}}, // gnac_, _unic, ікав, ghaz,
+ {{0xe8c61160,0x3f824069,0x25a0a06a,0x27e01b23}}, // [1840] वलीच, _viku_, ćili_, _upin_,
+ {{0x3f83616d,0xfe4665a8,0x753b9b24,0x5ec1207c}}, // _ciju_, онзо, _amuz, শ্বে,
+ {{0xe29767b9,0x80b10064,0x3f6a4c4e,0x79840079}}, // цах_, _अजमे, _вино_, _kiiw,
+ {{0x57cce00b,0x6d5c36dc,0x27f8a0c4,0x6d4140fa}}, // רמאַ, lora, _árni_, élat,
+ {{0x6d5ae167,0xc33300be,0x7d17203c,0x7b39e066}}, // yota, ווא_, _yoxs, rňuj,
+ {{0x6d5c2db2,0x3f8a6227,0x67298009,0x27e98017}}, // nora, _tubu_, _elej, çana_,
+ {{0x272f2105,0x76a9a0ff,0x6d48eb57,0x7f5c2057}}, // _fünf_, стів_, vnda, iorq,
+ {{0x3a3f4133,0x7af65b25,0x6287600e,0x75244108}}, // _župi_, ndyt, _zgjo, nkiz,
+ {{0x0ed2a010,0x78a2908f,0x7e641b26,0x6b8bc425}}, // _सापड, mbov, _nyip, _cugg,
+ {{0x01d001a6,0x7af64052,0x37e6aeb3,0xa3ce6077}}, // িবাদ, hdyt, _мозг, _शमा_,
+ {{0xe61f4016,0xab2ad096,0x0e8ec291,0x2fc7ab39}}, // _ngôn_, _кожа_, _dịrị_, cung_,
+ {{0x6d49c28f,0xf41f0052,0x69c3607f,0x78a22143}}, // cnea, tyä_, šneg, _đova,
+ {{0x2caca0e0,0x6d4abb27,0x201ee013,0x6d40d0aa}}, // _kedd_, onfa, syti_, mima,
+ {{0x7e6401e7,0x7983e1e7,0x3f848035,0x3eab5b28}}, // _dyip, _ginw, _limu_, _sect_,
+ {{0x636da049,0xdb062156,0x3f83604d,0x69d52108}}, // núna, dskä, _riju_, utze,
+ {{0x28dd605e,0xbdfb6050,0x2d8cb242,0x29187b29}}, // न्नि, یرضا_, _mude_, _aora_,
+ {{0x8af04497,0x5a354d9a,0x6d5c3a83,0x6f18e098}}, // [1850] tlər, чнат, bora, _kovc,
+ {{0x61e2c08e,0x9967612e,0x6e676799,0x752d5b2a}}, // _mpol, італ, отеж, shaz,
+ {{0x39495b2b,0x6b8d1819,0xf77088b5,0x8c3dc106}}, // pnas_, _huag, چان_, laşt,
+ {{0x80cda7fa,0x636da049,0x68e8e1cd,0x2d8b4274}}, // _साहे, dúna, hedd, _tuce_,
+ {{0xfa964087,0x68e8eb87,0x80d87b2c,0x60cd4167}}, // _מדרש_, kedd, न्हे, mgam,
+ {{0x61ed4800,0x7bc8fb2d,0x2bdbe688,0xe7e000c5}}, // mwal, kudu, _बहला, खौटा_,
+ {{0xf62860ff,0x2d8ca0e8,0x3869c361,0x6d5bd4cd}}, // _діти_, _cude_, _šar_, toua,
+ {{0x6d40d747,0x69c28d53,0xa3bbe9e4,0xdb1be5df}}, // gima, broe, ेंस_, truï,
+ {{0x7e63e277,0x6b8d0004,0x4255a555,0x2729e019}}, // _synp, _nuag, _کنار, _aúna_,
+ {{0xfbd241a9,0xe80f1b2e,0x78abc3b0,0x75244108}}, // עתי_, ामना_, _wegv, zkiz,
+ {{0x2d85a088,0x09e1c07c,0x69c8fb2f,0x3f8580fa}}, // _iile_, বিধা, gude, _élus_,
+ {{0xd343c043,0x7b1cc057,0x3f85a6be,0x442055a6}}, // _تفصی, _déus, _hilu_, yyi_,
+ {{0x6130210a,0x7ed4c0d0,0xa159e71b,0x3ead88c3}}, // _jälg, _آزما, _ладу_, _meet_,
+ {{0xe7e38518,0x3b0a204e,0xb113c096,0xdb09e5e0}}, // _गहना_, щего_, _bụgh, rreó,
+ {{0x7f5d0019,0xfaa64974,0x2d9fc071,0xd00443c3}}, // bosq, забо, lpue_, रम्भ_,
+ {{0x6364b28e,0x2cad9b08,0x63a99b30,0x5fc2c4da}}, // ròni, _need_, _owen, _शिमल,
+ {{0x6365c019,0x63a98194,0x1aeee07c,0x68f57b31}}, // [1860] góni, _nwen, জারে_, _bazd,
+ {{0x44205b32,0x68f61b33,0x75244108,0x39405b34}}, // ryi_, _hayd, skiz, siis_,
+ {{0x27ff82f4,0x44264f68,0x6b9b9b35,0x6d40c631}}, // _arun_, šo_, _atug, yima,
+ {{0x6d4b8e87,0x29195ab6,0x3f8cbb36,0x69d64c57}}, // gnga, _gosa_, _rudu_, ptye,
+ {{0x29187b37,0x68e8e035,0x27ff858f,0x61ed41fb}}, // _tora_, yedd, _crun_, cwal,
+ {{0x3991a13a,0xf767e0e0,0x3f8cbb38,0x672bc0c4}}, // _bás_, _سا_, _pudu_, _algj,
+ {{0x79864f65,0x63b64271,0x6d5d0a36,0x2730a016}}, // _kikw, lsyn, yosa, _làng_,
+ {{0x68e8f748,0x7b1cc162,0x973d0579,0x93fb2095}}, // wedd, _réus, opće, מלצי,
+ {{0x7986465b,0x5f76a0e0,0x636da049,0xb4b8804b}}, // _mikw, _فائر, súna, चणी_,
+ {{0x81df6405,0x9666a5e7,0x2cb24054,0x3991a049}}, // তির_, _екзе, hayd_, _fás_,
+ {{0x61ed4064,0xbc6aaca7,0xb09ba0be,0x6365c019}}, // zwal, _امان_, _היבר, zóni,
+ {{0x25a90163,0x798e62ad,0x39420046,0x63b65b39}}, // _pwal_, _mubw, giks_, ksyn,
+ {{0xbcfb00e0,0x6b8d1b3a,0x68f60025,0xd05c635f}}, // llék, _quag, _dayd, _karɓ,
+ {{0x58d3e90f,0x2d85210a,0x6365d320,0x61234046}}, // _зошт, öle_, vóni, _tõlk,
+ {{0x7af5606e,0x6d5d1b3b,0x798e600f,0xd8dbe076}}, // _razt, posa, _nubw, _הקור,
+ {{0xae9b013a,0x7d1aad85,0x6568e156,0xe1ff4395}}, // _اضغط_, _lots, lldh, _pró_,
+ {{0x70550050,0x68f5729e,0xdebc0076,0x8ccdbb3c}}, // [1870] _زندا, _pazd, _המחל, _सालो,
+ {{0x7d1ab640,0x2a66c037,0x635200d8,0xfc4664d2}}, // _nots, _myob_, māna, čína_,
+ {{0x6d4b8c57,0x9f407b3d,0x78ae6071,0xd498220e}}, // tnga, rvið_, _debv, _эры_,
+ {{0x7d08bb3e,0x63722065,0x6d4b9b3f,0xb9156291}}, // _ands, ræne, unga, _yabụ_,
+ {{0xb8d3607c,0x61ed5b40,0x752d00e4,0x672d1b41}}, // _টা_, qwal, _mlaz, _mlaj,
+ {{0x7bcaaa71,0x68e9d0a5,0xf1b12f30,0x9c7ce07f}}, // fufu, weed, _जबान, rečj,
+ {{0x2a66c05d,0x46bf0026,0x2cad8046,0x672d1b42}}, // _ayob_, ्लाह, _teed_, _olaj,
+ {{0x2d86cab8,0x7f4d4227,0x29091a18,0x63bdc066}}, // _cioe_, nnaq, _nnaa_, ásno,
+ {{0x7bc3a0e4,0xe4e7a12e,0xfd4ea088,0x27ff8cd4}}, // srnu, _ніжн, _jekọ, _urun_,
+ {{0x61fbc1cf,0xa3d71008,0x66021b43,0x69cabb44}}, // rvul, _सहज_, _šoko, bufe,
+ {{0x69cb9b45,0x69d98105,0x68e9c046,0xfd4ea079}}, // luge, ltwe, peed, _lekọ,
+ {{0xd5d19008,0x28dd6026,0xe2971574,0x63b8605c}}, // _समाज, न्ति, маю_, ávni,
+ {{0x68f72caf,0x6d5e6167,0x7bc56105,0x291a3b46}}, // _baxd, yopa, hrhu, _popa_,
+ {{0x09e62501,0x1dc5e4e6,0x7af72057,0x59c5f895}}, // могн, _विपत, _caxt, _विपर,
+ {{0x3f8fc304,0xd62a214a,0x257460cb,0xd84f0125}}, // _hugu_, цове_, näle_, wọta_,
+ {{0xbebb000e,0xb4bc924a,0x69cb909d,0x7bcb8eca}}, // shër, _आये_, kuge, kugu,
+ {{0x18674b65,0xb928c096,0x8c435b47,0x61e9623d}}, // [1880] дати_, _anwụ_, вете, _ćela,
+ {{0x8c430105,0x25a0540d,0x9f4b6197,0xe9ffa119}}, // ößer, rpil_, _uscì_, _nhắm_,
+ {{0xdb0ac0dd,0x7d1aa109,0x25a0486b,0x69cb86be}}, // ënës, _rots, spil_, euge,
+ {{0x628bc050,0x2d9c6018,0x3f9c6018,0x1dc5e148}}, // _nggo, īve_, īvu_, _विनत,
+ {{0x394d5b48,0xb0d2a028,0xa3ab85cb,0xf8b343c8}}, // čest_, _सादग, _कौआ_, _בשר_,
+ {{0x69d8a14f,0x6d4380fa,0x28dd7b49,0x6299c061}}, // ttve, énar, न्दि, _afwo,
+ {{0x3866c0c7,0xa91da013,0x6d429a47,0x3f8fdb4a}}, // _syor_, _nužu, pioa, _augu_,
+ {{0x92aea07c,0x2d9ce41d,0x3f8fc35f,0xa2be2180}}, // কলে_, _rtve_, _bugu_, वृत्,
+ {{0x7d1abb4b,0x63ada1e4,0x6365c069,0x2d8fc012}}, // _tots, ćana, jónu, _cuge_,
+ {{0x656409e6,0x692c4106,0xb7d4a1e9,0x7769db4c}}, // _akih, rşey, _aṣed, alex,
+ {{0x7c24400f,0xbcfb1b4d,0x6d4d4025,0xc588c067}}, // hyir, lléi, ynaa, _mồi_,
+ {{0x7e26c04e,0x39432200,0x6b88a0ae,0xaefb4031}}, // _одеж, wijs_, _hidg, _akùn,
+ {{0x752d03ee,0x63a28265,0x637601af,0x3f87e0a9}}, // _vlaz, npon, dánd, _finu_,
+ {{0x638805cd,0x27e6db4e,0x3f87e0e8,0x61fd0065}}, // tènè, _ipon_, _ginu_, rvsl,
+ {{0x200d012a,0x6d87c009,0x63760057,0x386d404d}}, // ţei_, _műan, fánd, _šer_,
+ {{0xa3cfa79e,0x636b66df,0xd30fc067,0x3d0ce046}}, // वंत_, rünm, _lệnh_, ़ावे_,
+ {{0x93780ca7,0xb345a01f,0x7bdae04e,0x7d1b8666}}, // [1890] _تصور_, niçã, lttu, _rous,
+ {{0x7d1b9b4f,0x61314277,0x63ad1b50,0x25721b51}}, // _sous, _såld, _iwan, mála_,
+ {{0x2ef86669,0x25720886,0x6d4f19d2,0x7ae2db52}}, // _darf_, lála_, onca, _obot,
+ {{0x636fe0ff,0x2b0800c2,0x69cb8afc,0x4424c4d2}}, // jønn, षाएँ_, tuge, kym_,
+ {{0xbbeb850f,0x61314277,0x63a29b53,0x7d09842e}}, // ترام_, _våld, gpon, _vnes,
+ {{0x7bcb9b54,0xb345a01f,0x6f1c61cd,0x394dc10f}}, // rugu, diçã, _gorc, wnes_,
+ {{0xa2d734c6,0x7bcb810a,0x63ad1b55,0x3f8fdb56}}, // _बान्, sugu, _lwan, _sugu_,
+ {{0xa2d7607c,0x2bd3c029,0x636da4cd,0x6d4f02a8}}, // _সফটও, _दमदा, múnm, ébal,
+ {{0x657bc17b,0x64572057,0xa3c1c12f,0x3f87fb57}}, // rmuh, _lxxi, ौंध_, _pinu_,
+ {{0x21294622,0xf389e081,0xee39f1e5,0x809fc180}}, // hkah_, _hải_, зно_, _ग्रे,
+ {{0x216a025d,0x25a2017b,0x27342851,0xd009f255}}, // зини_, wpkl_, _häng_, чене_,
+ {{0xb345a1ae,0x7bc60098,0xef1981f6,0xe732050f}}, // biçã, trku, _biżi_, اصد_,
+ {{0xdb0be342,0x69dafb58,0x394f9b59,0x25720049}}, // _avgå, atte, ings_, gála_,
+ {{0x3d069b5a,0x6d4f11de,0x290b46c1,0x68f9c1cd}}, // _सोने_, anca, _anca_, _hawd,
+ {{0xc0e64792,0x2ca680e2,0x63a280e0,0xb113c088}}, // новк, ybod_, zpon, _gụch,
+ {{0x4425fb5b,0x63788639,0x63bb9b5c,0x3f9e2041}}, // myl_, míng, _ovun, ītu_,
+ {{0x7e698236,0x612b3b5d,0x61302156,0xf388e067}}, // [18a0] _nyep, _küll, _välb, _sợi_,
+ {{0x3945fb5e,0x63718156,0x7afbc052,0xb14364a0}}, // nils_, våna, hdut, гнул,
+ {{0x61314536,0x63bb9b5f,0x7c24413b,0x6b899b60}}, // _såle, _avun, syir, _bieg,
+ {{0x809fc029,0x6135c069,0x0b8a8244,0xe9ffa067}}, // _ग्ले, _hálf, пски_, _chậm_,
+ {{0x9c7ce917,0x28f8a54a,0x98a7a03b,0x4af8a04e}}, // reči, дель_, ninė_, делю_,
+ {{0x7bdae0c4,0xc588e119,0xdfd0a243,0x7b2b01df}}, // yttu, _tồi_, ليت_, nƙur,
+ {{0x69ce2011,0x291dc0f9,0x27f25b61,0xd47a2053}}, // nube, _bowa_, lwyn_, האַל,
+ {{0xb345a01f,0x68fbcb5e,0x6d41a4de,0x31baa041}}, // tiçã, gdud, _emla, rīz_,
+ {{0xf8dd6afa,0xdef8a17a,0x65628037,0x612de06f}}, // न्वय, _taċ_, mooh, _súla,
+ {{0xa91d842e,0x3944c78b,0x69ce311d,0x2eaa2010}}, // _nižj, sims_, kube, करोत,
+ {{0x38690d3d,0x6603f6ad,0x2d4400e0,0x041ec07c}}, // _syar_, _crnk, rűen_, _বেশী_,
+ {{0x68e2c0a9,0x68ed5aff,0x63ad0167,0x69c72579}}, // _ubod, xead, _pwan, vrje,
+ {{0x660405d7,0x6d41a0e2,0x16038064,0x6f1d5b62}}, // _crik, _ymla, लियर_, _rosc,
+ {{0xe1ff08cc,0x612b2315,0x4425e04a,0x539bc087}}, // rvó_, _güll, byl_, _ציוו,
+ {{0x0697e095,0x7afaaea3,0x4425e0da,0x272e4069}}, // _אדום_, _matt, cyl_, _sýna_,
+ {{0x2d916274,0x63ad1b63,0x3f8a6214,0x3f9200e8}}, // _ruze_, _twan, _aibu_, _fuyu_,
+ {{0x6d897b64,0x6d9361d7,0x7d0d1b3f,0x68ed5575}}, // [18b0] _džan, _għaj, _inas, read,
+ {{0x69dc2016,0xf38a0016,0x612b210a,0x69c7217a}}, // btre, _rải_, _külm, prje,
+ {{0x6f1d5b65,0x32049b66,0x7d0d06b5,0x66040098}}, // _tosc, _army_, _knas, _zrik,
+ {{0x629c6659,0x27e9007d,0x612600c4,0x3f921b67}}, // _efro, _kpan_, _póli, _yuyu_,
+ {{0x290ca17b,0x7bcf1b68,0x25560041,0x7afd0109}}, // _bnda_, nucu, nāla_, idst,
+ {{0x1869a251,0x69dd1b69,0xf9940087,0x02c2a12e}}, // мали_, itse, ארס_, айшо,
+ {{0xbe656085,0xd5e2c1e9,0x7bdd0046,0xb0d2a7f4}}, // _شهري, _ayò, htsu, _सारग,
+ {{0x3ea95b6a,0x7aef11e6,0x7afd1b6b,0x25746156}}, // nbat_, ject, jdst, mäla_,
+ {{0x6ea20077,0x6d473732,0x7afbd32c,0xb606805c}}, // _क्षु, kija, rdut, _pláž,
+ {{0x69cf08cd,0x4425fb6c,0x69c0006f,0x273543d1}}, // duce, tyl_, ámem, _hånd_,
+ {{0xbea60ad4,0x6f1e29be,0x66040463,0x7bdc315b}}, // канк, _ropc, _srik, xtru,
+ {{0x26c121e7,0x3f92017b,0x68fb8a98,0x236cfb6d}}, // _adho_, _puyu_, _jaud, aldj_,
+ {{0x291f8022,0x2d9339a1,0x3945fb6e,0xf8bf004a}}, // _moua_, _auxe_, pils_, ckém_,
+ {{0x6d473b6f,0x98a7a03b,0x6f0d1b28,0x629d5b70}}, // gija, rinė_, _enac, _afso,
+ {{0x290c220f,0x442c2049,0x6376a1ae,0x612b3b71}}, // ıda_, ád_, lânc, _gülm,
+ {{0x57d18738,0x442680a2,0x656d5655,0x2d8b4552}}, // _समूह, yyo_, glah, _cice_,
+ {{0xac864099,0x4427a1cd,0x6f1e3b72,0x25a01b73}}, // [18c0] тгал, hyn_, _topc, _util_,
+ {{0xb5b700be,0x6ab64121,0x2d932057,0x63bbdb74}}, // _שליח_, sayf, _fuxe_, isun,
+ {{0xf6514043,0x80d72064,0x8fa62aee,0x20563358}}, // لئے_, _बाते, _шане, _штир,
+ {{0x25ad012a,0x2cb83b75,0x1e572076,0x6d897b76}}, // ţele_, lard_, _ישיר_, _užan,
+ {{0xb4c04216,0xb4ce43e6,0xa3ba433e,0x81e8607c}}, // ीली_, रली_, _تاجر_, বিধ_,
+ {{0xb87b007b,0x290d9b77,0x6d42db78,0x2366c262}}, // nfía, _enea_, _smoa, _skoj_,
+ {{0x8c4661fc,0x63bc62e2,0xe45f0728,0x798bd4b6}}, // _реве, _uvrn, ngör_, _bigw,
+ {{0xed57c55a,0x63bbcd3d,0x7bc9c105,0xf486c11f}}, // кој_, fsun, freu, купн,
+ {{0x28d327af,0xc6f7c65d,0x9f4da5e0,0xd186a425}}, // _तालि, тных_, _creó_, _алай,
+ {{0x6563a01f,0xa3cfa046,0xbcfb00fa,0x7bdd0048}}, // conh, वंश_, hlét, vtsu,
+ {{0xdb046359,0x656d5b17,0x69de7890,0x68fb936f}}, // lsió, ylah, ntpe, _yaud,
+ {{0x645ab60a,0x7bc9cb56,0x3f8ca05d,0x2d932005}}, // _ixti, breu, _hidu_, _ruxe_,
+ {{0x7bc9ce6c,0xb5fd8013,0x3eb8209a,0x4fc7014a}}, // creu, _ryšk, fart_, ъсна,
+ {{0x45d4ea09,0x2d8b4026,0x656d4054,0x5693e03b}}, // _бокс, _sice_, wlah, _кашт,
+ {{0x33200676,0xa2d38485,0x7ae298e8,0x27354271}}, // _foix_, _डाल्, kfot, _låne_,
+ {{0x7f5600dd,0x850d4029,0x6f0d1b79,0x69caa049}}, // _gjyq, हाइट_, _unac, irfe,
+ {{0x25a9a4ad,0x2ca95b7a,0x68fb9593,0xd2d2a3fa}}, // [18d0] ćali_, rbad_, _raud, _सांझ,
+ {{0x656d5b7b,0x2ca9503c,0x612b2315,0x7e4728f3}}, // slah, sbad_, _mülk, _ахме,
+ {{0x61260459,0x68e280fd,0x69c9c05d,0x67208187}}, // _vólv, ffod, zree, _bomj,
+ {{0xdbd1210a,0x5a35862a,0x60db2098,0x92599254}}, // _müüg, лнет, đumu, нает_,
+ {{0x212121e7,0x6b8bc9ee,0xa3d4e077,0x68fb9b7c}}, // _kohh_, _rigg, िंड_, _vaud,
+ {{0x3eb90046,0xd491a067,0x69c9c854,0x6d48fb7d}}, // hast_, _vào_, vree, jida,
+ {{0x3eb9031b,0xdb062066,0x62868066,0x7e6d0194}}, // kast_, jský, _úkon, _lyap,
+ {{0x23690048,0x672d45ba,0xde8f4079,0x98bca12a}}, // _nkaj_, nkaj, _aịza_, tivă_,
+ {{0x54558eb6,0x4427a8ef,0x25bea105,0xaca321e9}}, // _сват, syn_, _evtl_, _akọd,
+ {{0x236dc75a,0x6376af4c,0xa3d44029,0x98ad8143}}, // slej_, rânc, संत_, _mleč_,
+ {{0x798d013b,0x394a1b7e,0x5cd5c4a1,0xe8df8067}}, // _biaw, езно_, _مقاط, _ngừa_,
+ {{0x8fa605e5,0x3495c1e3,0x28a3e04b,0xe5a60844}}, // ламе, _садр, _ख्रि, лими,
+ {{0x798d0753,0x33200004,0x6135c057,0x5184c0ba}}, // _diaw, _voix_, _cále, _туфа,
+ {{0x2d8c22cf,0x2d91fb7f,0x7e6d00ca,0xdc3a8053}}, // öde_, ízes_, _dyap, געשר,
+ {{0xe8df8125,0x62816261,0xe4f9db80,0x44294025}}, // _amọn_, _úloh, ्यपि_, eya_,
+ {{0x5c16404e,0x44295b81,0xfc3f40b6,0x798d00ae}}, // льзу, fya_, _reír_, _giaw,
+ {{0x6b8deab5,0x386d9b82,0x635c8e9d,0xe57a64fb}}, // [18e0] _éags, _nyer_, nčne, еза_,
+ {{0x29dc6359,0xe8d0c295,0xbf9b41ae,0x81eaa07c}}, // tía_, _सञ्च, _grêm, মিত_,
+ {{0x6720994e,0x63706277,0x44294b87,0xaca3c291}}, // _pomj, länk, aya_, _asọm,
+ {{0xdcf4fb83,0x61fce5f0,0x27e654a6,0x386d8271}}, // _čači, ærli, çons_, _byer_,
+ {{0x9cd70095,0x7d1bc055,0xbef3e010,0x657b88a4}}, // רונה_, djus, _असून_, _dhuh,
+ {{0x29004e3b,0x3998a9fe,0x68e45b84,0x81e7207c}}, // ldia_, _pés_, lfid, পির_,
+ {{0x63a9640c,0x69cb854c,0x8b9570c4,0x656f01cd}}, // _čeng, arge, _вруч, ylch,
+ {{0x3998a5df,0x4253650f,0x63a40171,0x70536343}}, // _vés_, _انفر, _otin, _انفا,
+ {{0xdce2c29c,0x50d6a33e,0x442a2066,0x6b828ff8}}, // _ukoč, _مزار, hyb_, mmog,
+ {{0x5983a052,0xe80544ba,0xad9b42a8,0x39495a1e}}, // _глуб, रिता_, _brúj, zias_,
+ {{0xb893bb85,0x6b8d1b86,0x7e56bb87,0x798d1b88}}, // _المع, _piag, _стац, _piaw,
+ {{0x60c2c09f,0x40a8c050,0x2006db89,0xa2d7239f}}, // _udom, _شخصی_, _troi_, _बाह्,
+ {{0xa19501d3,0x3b0041f3,0x6d896490,0x637d0011}}, // рагч, ddiq_, _džaj, héng,
+ {{0xe9df0c24,0x6609445e,0xfd9ec096,0x2579e0fa}}, // rtú_, _šeko, _gụọ_, tèle_,
+ {{0xe3d18295,0x39495b8a,0x7afe2011,0xe9df06db}}, // _सम्झ, tias_, _dapt, stú_,
+ {{0xd6d85043,0xfd9ec088,0x672d40ce,0x63706851}}, // лту_, _zụọ_, ukaj, bänk,
+ {{0x612b2046,0x30a760cf,0x7c2aa0fd,0x3f858425}}, // [18f0] _lüli, грев, hyfr, _èlui_,
+ {{0x61ed1b8b,0x68ff003c,0x39998163,0x6feaa03c}}, // _ipal, _haqd, _rès_, _məcb,
+ {{0x386d817b,0xdce9c16d,0x62882066,0xdb1b0005}}, // _syer_, zmeđ, ždor, esté,
+ {{0xdce9c5f2,0x80dc4290,0x68ff013e,0x7aff013e}}, // tleč, _फाये, _jaqd, _jaqt,
+ {{0xa29560ff,0xe8d92067,0x69cb9308,0x66044066}}, // _вагі, _quỷ_, urge, cvik,
+ {{0x2d8efb8c,0xfbc5eba0,0x56946cde,0x9f5ec0b8}}, // _nife_, _विलम, _гарт, _artà_,
+ {{0xf204614a,0x389e8291,0x2a7f002e,0xd8b8aae5}}, // _лято, _ịre_, ľuby_, ادها_,
+ {{0x29042018,0x6e3bd016,0x27274466,0x53349b8d}}, // ēmai_, tzub, _tõnu_, _лест,
+ {{0xa3b0ca82,0xe3b6c1ba,0x60db800f,0x7d126009}}, // टील_, абы_, _icum, _újsá,
+ {{0x66099b8e,0x27ed9b8f,0x6ab61b90,0xdb0203f5}}, // _irek, _ipen_, _seyf, nslö,
+ {{0x9984a13a,0x4384a13a,0x6135caba,0x610ba041}}, // _اللو, _اللق, _cálc, _vēlē,
+ {{0x7afe26c1,0x399aa0be,0x6933c050,0x0086078e}}, // _sapt, _סינד, _دکتر, илно,
+ {{0x3da7c78e,0x2738c18c,0x3eaddb91,0x768bc03c}}, // _среб, _séng_, mbet_, _döyü,
+ {{0x56b5a00b,0x66099b92,0x3eaddb93,0x46a5e39d}}, // יפֿן_, _mrek, lbet_, _такв,
+ {{0x63a5217b,0x60db8071,0x80daa026,0x6722c286}}, // _cthn, _ocum, प्टे, _yooj,
+ {{0x66098628,0x6abbc0cb,0x29117b94,0x66020431}}, // _orek, nauf, _anza_, _šoku,
+ {{0x7ae440dd,0x473600d0,0x3ead007b,0x0b4661d3}}, // [1900] rfit, _مرکز, ñete_, анан,
+ {{0x63a9db95,0x3b00403c,0x09e664c2,0xddcd0012}}, // ipen, sdiq_, иозн, _uzaš,
+ {{0x4adde437,0xaca44079,0x7523fb96,0x61fb897e}}, // _मानव, _agụt, _monz, _asul,
+ {{0x84e6884f,0x27e05b97,0x539b21a9,0x69cd5b98}}, // родж, ptin_, _שיכו, brae,
+ {{0x6d5640bb,0x69c1a736,0x63a9c171,0x9ad40125}}, // mnya, _avle, jpen, _rịlt,
+ {{0x6abc2069,0x6b829b99,0x6723fb9a,0xed5a61fc}}, // narf, smog, _nonj, _рог_,
+ {{0x29121b9b,0xb3478227,0x63a9db9c,0x60db8153}}, // _onya_, _baħħ, epen, _fcum,
+ {{0x2d98ab0a,0xa91d8530,0x60db8049,0x6ed2bb9d}}, // _érem_, _bižu, _gcum, _ठाकु,
+ {{0xdb1c4005,0xdb1540e0,0x6abc3b9e,0x8cd7c029}}, // luró, nszá, karf, _यारो,
+ {{0xf1bf203a,0x4734e40e,0x3cff8048,0xdcfc8018}}, // žšie_, снос, _zauv_, _turē,
+ {{0x25746277,0x27354342,0x2d8fc64e,0x394b1b9f}}, // väll_, _låna_, _fige_, tics_,
+ {{0x35471593,0x3cff805f,0x853020e8,0x3f987ba0}}, // рхов, _xauv_, _ruɗa, _muru_,
+ {{0x394b1ba1,0x78bc2999,0x15a7c14a,0x25a6204d}}, // rics_, farv, _къщи_, _čola_,
+ {{0x29021ba2,0xf1bf0049,0xf669c0eb,0x78bc302d}}, // adka_, rsá_, _صحنه_, garv,
+ {{0x394b1ba3,0xa3d445e8,0x937be0be,0x757be1a1}}, // pics_, सूस_, _שטות, _שטופ,
+ {{0x6d56459b,0xef19a0c2,0x6d4b9ba4,0x490688a2}}, // gnya, _duży_, xiga, _सोलो_,
+ {{0x6f029ba5,0xf993c00b,0xe9ffa067,0x2eff8e89}}, // [1910] ddoc, ַרף_, _thầm_, _rauf_,
+ {{0x637ec00e,0x66099ba6,0x9f4060d1,0x394cfba7}}, // lënd, _rrek, ntiè_, nids_,
+ {{0xceb22087,0x636b6105,0x04958049,0x938868ba}}, // _מיי_, wüns, ملاح, рста_,
+ {{0x26d90050,0xe807e4e5,0x3f987ba8,0x7bd53ba9}}, // ngso_, षिता_, _duru_, muzu,
+ {{0xfc058312,0x2d987baa,0xf0924095,0x61e288b2}}, // спло, _eure_, _מנכ_, gtol,
+ {{0xa2d38040,0x75356037,0x637ec00e,0x69c0c09a}}, // _डाक्, _ulzz, hënd, dsme,
+ {{0x7c2d4602,0x7bc2c0fa,0xdb090069,0x59c98028}}, // lyar, _avou, nsdó, _रिवर,
+ {{0x91cc6046,0x29013bab,0x78bd006f,0x442ce1cd}}, // _हितै, _laha_, jasv, dyd_,
+ {{0x29000296,0x68f520e0,0x929d8064,0xd706c04e}}, // _raia_, kezd, _całe, сные_,
+ {{0xd7e7a4a4,0xdce3e1d7,0x7bc2c026,0x3eaddbac}}, // _відо, _minħ, _dvou, sbet_,
+ {{0x29000302,0x2d8fdbad,0x3eb94017,0xc6890557}}, // _paia_, _tige_, _oest_, _נא_,
+ {{0x6135c459,0x200b5bae,0x2fc040e2,0xe9ffa016}}, // _fála, _orci_, ysig_, _chấm_,
+ {{0x7523e194,0x2d99400e,0x3085c049,0x637d003e}}, // _tonz, _nuse_, _الشف, xéne,
+ {{0x64a334d2,0x6136e0ba,0x2d917baf,0xa009e4c7}}, // зара, _vâlc, _nize_, _مقتل_,
+ {{0x251ae095,0x6d564058,0x637df4a6,0x27354156}}, // _תוצא, tnya, rènd, _såna_,
+ {{0x7999c0e8,0x3f4f6018,0xdb154009,0x6abc3bb0}}, // _kuww, kļus_, tszá, sarf,
+ {{0x6d565bb1,0x69c1e4a6,0x28d18308,0x2cb87bb2}}, // [1920] rnya, lsle, _हाजि, _perd_,
+ {{0x2d9200a2,0x2fc05bb3,0xf7726049,0x2018e584}}, // _hiye_, rsig_, ساء_, årig_,
+ {{0x7bc1eb83,0x2cb87bb4,0xf3ec807c,0x2fc05bb5}}, // nslu, _verd_, ওয়ার_, ssig_,
+ {{0x3eb86283,0xecba81ac,0xfe05526f,0x93aa8049}}, // _wert_, اطات_, रिशस_, هاتف_,
+ {{0x6d5b807d,0x28db0bc5,0x601881eb,0x3eb86136}}, // _djua, _भासि, роля_, _tert_,
+ {{0x6f01a94c,0xe29773f9,0x69c1fbb6,0xdce0c041}}, // _calc, _гар_, ksle, domā,
+ {{0x7bd65bb7,0xdce4416d,0x442dc025,0x2d9a3bb8}}, // luyu, glić, eye_, _jupe_,
+ {{0x69c0dbb9,0x2360c06e,0xae180029,0x69c1e831}}, // tsme, čijo_, दमान_, dsle,
+ {{0x6f04503c,0x3f9a3bba,0x63788089,0x2d916544}}, // idic, _lupu_, líno, _yize_,
+ {{0x7bc280ca,0x602381ba,0xfe6f60e0,0xc8d880c5}}, // msou, _эдуа, _آگے_, _डाँट,
+ {{0x2d98eaaa,0xab96a13a,0x6568f7bf,0x3fcba062}}, // êre_, _الصغ, rodh, _مدنی_,
+ {{0xdced4261,0x69d52631,0x63a4812a,0x394ddb9f}}, // tlač, vuze, ţinu, cies_,
+ {{0x78baa03b,0x13ada07c,0x69c2831d,0x5118e4a0}}, // _ketv, _গিয়, nsoe, _волю_,
+ {{0x68e92098,0x2d9200f9,0xdce90106,0x3f994d94}}, // đedo, _eiye_, ılıy, _rusu_,
+ {{0x61fe2057,0x3f9a2827,0x6eaec0c2,0x6135c29b}}, // _cspl, _cupu_, ीरपु, _máln,
+ {{0x78baa12d,0xdb090069,0x3ebefbbb,0x16054077}}, // _letv, rsdó, latt_, रिलर_,
+ {{0x29025bbc,0x7bd64258,0x7f4d5bbd,0x29074026}}, // [1930] _daka_, guyu, riaq, ěna_,
+ {{0x2126c48d,0x6440dbbe,0x200cb890,0x1cf10096}}, // _looh_, izmi, _ordi_, _ọjọọ_,
+ {{0x273b000e,0x2cbd8022,0x6d9361f6,0x7c2f1bbf}}, // _tënd_, tawd_, _aħar, lycr,
+ {{0xcebc203c,0x2d9b000e,0x443fc089,0xa6db00c4}}, // _şəxs_, _kuqe_, yzu_, miðs,
+ {{0x35e780ff,0x637de017,0xab5da2a4,0xb9018148}}, // сцев, nènc, _avża, _दा_,
+ {{0xc7d80095,0x443fc03c,0x69c1fbc0,0x4adde3af}}, // מודי_, vzu_, ysle, _माधव,
+ {{0xa3b3a857,0x7d02d819,0x26c902d1,0xb4db4090}}, // जीं_, _baos, _odao_, _amài,
+ {{0x80dba8ae,0x2007b019,0x612b20e0,0x6f02d03c}}, // _नावे, rvni_, _küls, _caoc,
+ {{0x442dc161,0x7deaa03c,0x69c1edee,0xdce442d1}}, // uye_, _vəsa, wsle, slić,
+ {{0x3bc3e497,0x7bc1e23c,0x637de355,0x6f045731}}, // tıq_, tslu, dènc, zdic,
+ {{0xe784b380,0x3d1d2b42,0xf2d26557,0x2b4dc0c2}}, // _хуто, माने_, ועי_, piec_,
+ {{0xcb6a05a4,0xa96a1255,0x442dc0d1,0x273540ff}}, // раме_, рима_, pye_, _sånn_,
+ {{0x69c52488,0x28db0857,0xbebb4aaa,0x69c3b41b}}, // _avhe, _भारि, _voël, lsne,
+ {{0x69c1e5af,0x656f4030,0x68ed15ca,0x78bb9a78}}, // psle, _akch, _ebad, _leuv,
+ {{0x7af6404e,0x7bc3acdc,0x67264037,0x5f064e64}}, // teyt, nsnu, _pokj, озва,
+ {{0x7ae9dbc1,0x26cc003b,0x2d932815,0xdb1c4069}}, // nfet, ėdos_, _eixe_, gurð,
+ {{0x15ba40fb,0xdb1b01ae,0x03266075,0x25adc07d}}, // [1940] рыбы_, bstâ, одан, npel_,
+ {{0x61e44c99,0x7d03f4a8,0x7aed00dd,0x69d65bc2}}, // rtil, _jans, _zbat, ruye,
+ {{0x6f041819,0x6abe60b1,0xbd06c005,0x071e00b9}}, // _kaic, rapf, ntéñ, _बचाव_,
+ {{0xdcb6e8b1,0x63ad40ae,0x6ed7a026,0x7d040534}}, // _आज्ञ, bpan, _ठाउँ, _jais,
+ {{0x3ebfc0ae,0x6aa2977e,0x26c059e1,0xada38089}}, // daut_, rcof, maio_, _neúč,
+ {{0x63a98037,0x2574604e,0x320d9bc3,0xf1a6af0e}}, // _dten, käli_, _drey_, орин,
+ {{0x2d9fc2a8,0x394ea004,0x78baa651,0x3ebfc004}}, // eque_, rifs_, _vetv, faut_,
+ {{0x67029bc4,0x26c04024,0x63a9012a,0x60dbdbc5}}, // _रोचक_, naio_, ţeni, ggum,
+ {{0x6abc60c4,0x3ebee14f,0x5454cde7,0x200d9bc6}}, // _kerf, tatt_, явит, _grei_,
+ {{0x752286a6,0x637d01cb,0xf72ae19c,0x68ed0754}}, // ljoz, déna, рций_, _sbad,
+ {{0xeb9aa90f,0x28abc1b0,0x9f52e814,0x78bc7bc7}}, // _тие_, _ट्वि, _èyí_, _merv,
+ {{0x637de355,0x2730a067,0x799c60b1,0xad9b5bc8}}, // tènc, _sành_, _murw, _krút,
+ {{0x2bd299a7,0x69d8ac04,0x3ebeed2d,0xe133c362}}, // _सिफा, luve, patt_, _іншы,
+ {{0x3ebf46ca,0xdea280a2,0x3cf660c2,0x6d95a1f6}}, // _žut_, rkoƙ, ीयों_, _iġar,
+ {{0x637fa01f,0x69d8b65e,0x9e66e0d0,0xda6fe0ff}}, // lênc, nuve, _باشن, _ця_,
+ {{0x7d03e0d1,0x213a2cd1,0x63706156,0x6e282156}}, // _zans, _elph_, käns, ädba,
+ {{0x27e6800e,0x6edc4021,0x3d176026,0xdb1b0017}}, // [1950] jton_, _फारु, नाले_, mstà,
+ {{0x2fd82cf9,0x3d056e4e,0x33291bc9,0x6abc7bca}}, // burg_, _होखे_, _hoax_, _cerf,
+ {{0x6374e5df,0x7bc44046,0x7af77555,0xdb18c22e}}, // gàni, asiu, rext, msvæ,
+ {{0x69d77bcb,0x27e69bcc,0x673aaa53,0x63760803}}, // ruxe, fton_, _altj, vánk,
+ {{0x6abd405d,0x2d895bcd,0xe1ff42a8,0x3d1c0a53}}, // _iesf, mmae_, _usó_, याते_,
+ {{0x442f9bce,0x69c3bbcf,0xa8580076,0x25720009}}, // ryg_, tsne, חידה_, lált_,
+ {{0x439464d7,0xe8d90291,0x6b9d4c57,0x6374e017}}, // _царс, _ajọ_, _husg, càni,
+ {{0x9623c07c,0x6441403c,0xf4126076,0x27e680ae}}, // _বেগম_, _əlin, _אפל_, bton_,
+ {{0xe80e4290,0xaadc2260,0x7c38b94e,0x637fa01f}}, // सिया_, _बालक, _čarš, gênc,
+ {{0xb4bb84ef,0x28abd450,0xfe45e7ca,0x78bd4071}}, // _आजु_, _ट्रि, зноо, _lesv,
+ {{0x1d0a0657,0x2c74a049,0xab5b20e0,0x3f89405d}}, // бени_, rúdú_, tsün, kmau_,
+ {{0x7d03e00d,0xc104c13a,0xf53f6584,0x6feaa03c}}, // _wans, _يولي, _stål_, _məcl,
+ {{0x6d5ae048,0x69d99bd0,0x26c04197,0x637fa1de}}, // bnta, nuwe, vaio_, cênc,
+ {{0x2d9c2009,0x69c57bd1,0x2fd83bd2,0xa3b0c077}}, // _évek_, kshe, turg_, टीओ_,
+ {{0xb4d7e04b,0xdb1b1bd3,0x6138404a,0x61e610bc}}, // िली_, bstà, _díln, stkl,
+ {{0x030b8028,0x2d9ddbd4,0xf1c7408d,0x6e964049}}, // _सोलह_, _kuwe_, _लिखन, سلطا,
+ {{0xda6348f3,0x79957bd5,0x6b957099,0x79840561}}, // [1960] евти, _bizw, _bizg, _ihiw,
+ {{0x3d1d2010,0x2d9ddbd6,0x2905bbd7,0x673b8434}}, // माणे_, _muwe_, _nala_, _aluj,
+ {{0x27342cad,0x69c77bd8,0xe29760d7,0x60cbc098}}, // _länk_, _cvje, чах_, _odgm,
+ {{0xdce3a026,0x673b812a,0xaadc2026,0x78bc6171}}, // plně, _cluj, _बाँक, _werv,
+ {{0x2d4d3bd9,0x6b9c7bda,0x6b9d5bdb,0x67228052}}, // džej_, _wurg, _gusg, rjoj,
+ {{0x637fa1ae,0xf4878555,0x26c21bdc,0x60c1e0f7}}, // vênc, سانی, hako_, jalm,
+ {{0x7d7a8050,0xdb074156,0x6d5ae022,0x395f8227}}, // _همسر_, rsjö, vnta, _fjus_,
+ {{0xe80d2567,0xd00f6009,0x69c61bdd,0x27342262}}, // हिता_, _حلف_, kske, _bänk_,
+ {{0x69c60e9d,0x6f064435,0x76444315,0x44446105}}, // jske, _makc, nziy, lz_,
+ {{0xb8d3cc87,0x200d4927,0x637de0d1,0x75298167}}, // _ज्_, _šeik_, tèna, _zoez,
+ {{0x26c2080c,0x28dde969,0xfbd2b34a,0x67298037}}, // fako_, _मारि, _ستا_, _yoej,
+ {{0x6d4e766f,0x26c16610,0xe45760be,0x987b6076}}, // _lmba, zaho_, לייט_, מריק,
+ {{0x2905bbde,0x51873bdf,0x3f9eac0f,0x67245be0}}, // _yala_, зува, _hutu_, djij,
+ {{0x78bd4533,0x6ffbc095,0xf617c076,0x3d0c6466}}, // _sesv, _להדפ, _מחדש_, _डोले_,
+ {{0xde57e0ff,0x961d8018,0x20025be1,0x1897e049}}, // _наші_, _izņe, _aski_, _عضوة_,
+ {{0xaf044a00,0xda5be095,0x6b840425,0x3f9dc0e8}}, // нчук, _לכול, _ghig, _yuwu_,
+ {{0x2d9ebbe2,0x7d065448,0xa3d44026,0x25aca05d}}, // [1970] _lute_, _daks, संग_, _ltdl_,
+ {{0x3ebebbe3,0x629aa0ae,0x6abe20cb,0x23d4c984}}, // _nett_, _ngto, _gepf, _दिनद,
+ {{0x6376a01f,0x65154043,0xdce9817a,0xd25100d0}}, // câni, _ہوگئ, _jieħ, _سنگ_,
+ {{0x63ad0037,0x6135c29b,0x7bc57be4,0x7bd99be5}}, // _ktan, _pálm, rshu, tuwu,
+ {{0xa3cf6a30,0x3ebead4b,0x2d9ebbe6,0x2207204a}}, // _शिव_, _bett_, _aute_, bíhá_,
+ {{0x27e95be7,0x69c73be8,0x656f1be9,0xe29ae067}}, // mtan_, osje, hoch, _ngư_,
+ {{0xba776896,0x7d065b14,0xb4d74028,0x60cd1bea}}, // _راست, _yaks, _साफ़_, _odam,
+ {{0x273c429b,0x2b4d8057,0xeab38049,0x20d5c71b}}, // _kína_, _umec_, _سعر_, фікс,
+ {{0x3ebebbeb,0x6f65a050,0x26c3206e,0x3d1c0148}}, // _fett_, _قهرم, kajo_, यासे_,
+ {{0x3ebea277,0x2bd28064,0x69dae372,0x68e2d284}}, // _gett_, _सिता, fute, _ecod,
+ {{0xd6e3207c,0xb4d90f30,0x61e8e0ae,0x6ed72984}}, // য়ায, ालू_, gtdl, _बाजु,
+ {{0x69c61bec,0x656f1bed,0x69d520c2,0x2d9dc0ce}}, // tske, goch, drze, _tuwe_,
+ {{0x2d9ea274,0x60cd1bee,0x29094025,0x69cea6dd}}, // _zute_, _ddam, ddaa_, _åber,
+ {{0x6376a1ae,0x63ad0037,0x9f84e0ff,0x799e21e7}}, // tâni, _dtan, _згід, _pupw,
+ {{0x27e94e3b,0xfd648016,0x10a32619,0xd0072338}}, // etan_, _chuộ, тисн, мере_,
+ {{0xc7d6e095,0x7e76004e,0x25746105,0xbb860049}}, // _חוקי_, _tyyp, hält_, _تلبي,
+ {{0xdb1b0069,0xaa4603fc,0x7d0773bd,0x5ed3207c}}, // [1980] fstæ, _цепл, _bajs, দ্ধে,
+ {{0xe9d74503,0x2011650e,0x7afbc04e,0x6aa98031}}, // ьку_, _brzi_, heut, _afef,
+ {{0x61e9c6eb,0x61464103,0xad1b2087,0x831b200b}}, // htel, _жена, _וויר, _וויז,
+ {{0x973cc274,0x3f9ea552,0x70d8804b,0x395d8022}}, // _kuće, _rutu_, डलेल, gnws_,
+ {{0xb0dde08c,0x7d08e277,0x80c3e07c,0x6d4e6058}}, // _मांग, ydds, _শান্, _tmba,
+ {{0x68fc21e1,0x7d077bef,0x60c3bbf0,0x2d85a030}}, // nerd, _gajs, ganm, _chle_,
+ {{0x3157200b,0x2fc7a037,0x69da8028,0x395d8048}}, // ויסן_, gsng_, _नमकी, bnws_,
+ {{0x644040c4,0x85a6c01b,0x6f07615e,0x26c3207f}}, // _ýmis, мјод, _zajc, zajo_,
+ {{0x929d8064,0xd379029c,0x63ade04d,0xe2caa6bd}}, // _zało, nuće_, _čana, _млад_,
+ {{0x2d9ebbf1,0xb383a0ba,0x20048106,0x7afae70b}}, // _tute_, _алул, _ismi_, uett,
+ {{0x61e9dbf2,0xdb0ac277,0xd36fc1fc,0x3f52004d}}, // atel, dsfö, _ач_, ršun_,
+ {{0x6602dbf3,0xd37900e4,0x2cb1e20f,0x27e9419f}}, // _tsok, kuće_, ızda_, ytan_,
+ {{0xdce981d7,0x212b4653,0x60c447ad,0x61314536}}, // _wieħ, _roch_, daim, _målt,
+ {{0x2d986e3b,0x68fc3bf4,0x69c7214e,0xc4c48043}}, // _nire_, gerd, tsje, _ھے_,
+ {{0x5c06e1e1,0x7d08aaca,0xf773419a,0x212ca4b9}}, // _цяла, _mads, نار_, _modh_,
+ {{0x26c33bf5,0x8afea020,0x2c0b404b,0x26c000d4}}, // sajo_, moƙu, हटलं_, _geio_,
+ {{0xf99f528e,0x7bc73bf6,0x2d986704,0x637881ae}}, // [1990] _què_, ssju, _bire_, cíni,
+ {{0x9f9ad8cf,0xfd648081,0x7bdd1bf7,0x69dc2310}}, // mään_, _thuộ, musu, bure,
+ {{0x7bdc2132,0x7bc8fa78,0x69dc2c3e,0x3f986265}}, // curu, nsdu, cure, _diru_,
+ {{0x60c3b141,0x273c4193,0x853020a2,0x98bf6181}}, // wanm, _tína_, _ruɗi, ırın_,
+ {{0xdb1b09ac,0x63760359,0xfad64095,0x7afd01fc}}, // mstä, mánt, _אורך_, iest,
+ {{0x321160c2,0x395d8022,0x5de5a297,0xdb1c8262}}, // _trzy_, snws_, ежка, _avrä,
+ {{0xdce44121,0x26c12320,0x7d09c15e,0x2ca0800a}}, // nliğ, _meho_, tdes, žida_,
+ {{0xda08c081,0x60c3bbf8,0x26c4dbf9,0x2d8ce23c}}, // _bỏ_, sanm, gamo_, emde_,
+ {{0x6562c07d,0x6d41f93d,0x6f09dbfa,0x4439dbfb}}, // _djoh, thla, rdec, ás_,
+ {{0x7d098187,0x7c8482f0,0x798d4939,0x25a003c5}}, // _haes, _русе, hmaw, _ruil_,
+ {{0xdbd722df,0x66e60a47,0x2d9940fa,0x6368020f}}, // _jääv, нона, _oise_, yınd,
+ {{0x9f47c06f,0x68e53bfc,0x9f480065,0x25560018}}, // ktné_, _achd, _opnå_, vāls_,
+ {{0xdb0ac277,0xdb1b0277,0x6d553bfd,0xb606c069}}, // tsfö, dstä, fiza, rráð,
+ {{0x25d6c00b,0x9c82e07f,0xdb18c68f,0xfce5e182}}, // _נוצן_, ščam, rsvå, _чоко,
+ {{0x26c120ca,0xdb0ac262,0xd8d6e1a1,0x2cab03f6}}, // _deho_, rsfö, _טוסט_, éidí_,
+ {{0x69dc2ffe,0x14e26010,0x7afc3a83,0xdb0ac156}}, // rure, _पाहण, sert, ssfö,
+ {{0x6d553bfe,0x3171355f,0xe3634977,0x291a2058}}, // [19a0] biza, bozz_, укти, _tnpa_,
+ {{0x2d994155,0x63a449a6,0x2d8dc05d,0x2d9e21ae}}, // _eise_, rqin, hmee_, ítes_,
+ {{0xeb9a73ae,0x59d80010,0xaaddf423,0x59d280c5}}, // чие_, भंकर, _माईक, _सिहर,
+ {{0x2bd17bff,0xe9f901e9,0x2731c057,0x0ee2717d}}, // _हिरा, _afẹ_, _máns_, _पावड,
+ {{0x3ce10028,0x6281206f,0x2d52007f,0x26c121e7}}, // कलते_, ýlov, jšem_, _yeho_,
+ {{0x23a6003a,0xae1fe37f,0x59d307fa,0xa5a4a0c2}}, // _môj_, यमान_, _तिसर, _चंदौ,
+ {{0x7a46e004,0x6b63641b,0x39400d67,0x7deaa03c}}, // _mété, _скра, _olis_, _kəsi,
+ {{0x213f8320,0x657bccc5,0x59768013,0xff26814a}}, // _gluh_, gluh, _чыгу, _ямбо,
+ {{0x26c24381,0x6ce6e056,0x60c1ae5d,0xe81340f1}}, // _meko_, ніне, _felm, ठिया_,
+ {{0x26c25c00,0x69dd1c01,0xd90da062,0x61fb01de}}, // _leko_, vuse, _وین_, çulm,
+ {{0x69dd00c1,0x2d8cf2cd,0x26c4dc02,0x2013e005}}, // wuse, rmde_, pamo_, _áxil_,
+ {{0xbcfb4031,0x2a66c286,0x7deaa03c,0xdced42e2}}, // _ajén, _txob_, _nəsi, mlać,
+ {{0x6d44439b,0x60c1a071,0x68e28125,0xb4db40d5}}, // dhia, _yelm, igod, _llàc,
+ {{0xf53f4536,0x69de7c03,0xde8f4125,0x3f99410a}}, // _slå_, kupe, _dịla_, _sisu_,
+ {{0x7bdd0a85,0x799ab4b6,0x798d47d3,0x92682618}}, // susu, _mitw, tmaw, ерта_,
+ {{0x28db0028,0x6b8d42c7,0x613ccde0,0x4a7be076}}, // _भाटि, umag, _mélo, _מרוב,
+ {{0x63760886,0x752e61f6,0x290ce025,0x26c25c04}}, // [19b0] ránt, _bobz, odda_, _deko_,
+ {{0xade4fc05,0x65c760e0,0x78a2403a,0x7d098360}}, // _कमान_, _néhá, ľova, _saes,
+ {{0x28e26086,0xb4dd227d,0x2d52006f,0x69caa143}}, // _पारि, डली_, dšej_, jsfe,
+ {{0x3ce6c048,0xacbb4004,0xd324854a,0x60c73c06}}, // _ncov_, _coût, льчи, lajm,
+ {{0x5b1441e1,0x290a6005,0x6d43a049,0x9a844627}}, // _смят, _faba_, thna, _бурл,
+ {{0x5ff56844,0xd184a84f,0xe0df42d9,0x7ae28108}}, // _изгу, глій, _blòg_, agot,
+ {{0x61ed5c07,0x3206c114,0x6132622e,0x69caa59c}}, // ltal, _asoy_, _vælt, gsfe,
+ {{0x9f5d003a,0x69c9d11b,0x290a7c08,0x5f94860f}}, // _prvý_, tsee, _zaba_, _бист,
+ {{0x63b48066,0x60c2c153,0x5f76c13a,0x78a9dc09}}, // čený, _deom, خميس_, rcev,
+ {{0xc6bc207c,0xe7a9ef7e,0x88bd80c2,0x7d0d4052}}, // _অঞ্চ, мвол_, _pośw, hdas,
+ {{0x39400de5,0x3f890090,0x6146016c,0x26c37c0a}}, // _plis_, _bhau_, вена, _mejo_,
+ {{0x26c37c0b,0xaca3c088,0x3f9a205d,0xd14bc049}}, // _lejo_, _kpọm, _sipu_, _عشان_,
+ {{0x60c60106,0x6d565929,0x69dee14f,0xaae2604b}}, // rakm, viya, _åpen, _पालक,
+ {{0x69cb926c,0x60c73c0c,0x27edd393,0x7d0bc069}}, // isge, gajm, mten_, _hags,
+ {{0x7d0bc0ae,0x7f4440fa,0xe5a62517,0x50b60aa4}}, // _kags, thiq, тиби, _испу,
+ {{0x273c405c,0x66f9cafa,0x7529dc0d,0x6e244108}}, // _víno_, ्यटक_, njez, txib,
+ {{0x6d444820,0x7a46e7fc,0x9e6600ff,0x61ed5c0e}}, // [19c0] rhia, _tété, _швид, gtal,
+ {{0x69de6530,0x26c7bc0f,0x98a00098,0x7d0d5c10}}, // tupe, jano_, _boić_, adas,
+ {{0x82360896,0x200d4041,0x15fe4e4e,0x673bc2df}}, // _سرکا, _šeit_, _उनकर_, kkuj,
+ {{0x7bde7c11,0xe8d90016,0x63a3fc12,0x69de7c13}}, // rupu, _thụ_, _munn, rupe,
+ {{0x63a3fc14,0x69cb89cd,0x6b898054,0x6729c098}}, // _lunn, gsge, _dheg, djej,
+ {{0x27edc042,0xd12fa13a,0x14dde04b,0x6135c0c4}}, // dten_, يمن_, _मागण, _málu,
+ {{0x5fd4c4ef,0x63a41c15,0x212fc090,0x27e041d1}}, // _दिहल, _luin, _bogh_, luin_,
+ {{0x26c2d239,0xe3afe07c,0x35a74028,0x61385c16}}, // úko_, _কিংব, _कूड़, _bíli,
+ {{0xed5782fe,0x27e041d5,0x6135c0e0,0x25a68187}}, // _рот_, nuin_, _nálu, qqol_,
+ {{0x6b9c6068,0xbcfb40f9,0x87b78076,0x81abe07c}}, // _jirg, _ajél, _סלבס_, গঠন_,
+ {{0x6d58bc17,0x91ae407c,0x61ed4009,0x6aad00f0}}, // miva, টওয়্, ztal, _pfaf,
+ {{0x3f890022,0x2924c066,0x291dc1e7,0x61ed5c18}}, // _qhau_, ávač_, _gnwa_, ytal,
+ {{0x60c41c19,0x1bd4cb24,0x29004395,0x67d4c8ba}}, // _deim, _соля, deia_, _солу,
+ {{0xf99f00d1,0x44383c1a,0x26c486d8,0x799c6931}}, // twèb_, fyr_, _lemo_, _nirw,
+ {{0x60c73c1b,0x291ea058,0x78a2406f,0x4ea7401b}}, // rajm, _mnta_, ľovn, врза,
+ {{0x6d58b34f,0x15e30029,0x05d28029,0x7ae45286}}, // hiva, कंदर_, _सिंब, ggit,
+ {{0x2bda4626,0x395901d5,0x26c7ad72,0xc179003b}}, // [19d0] यंगा, miss_, yano_, stės_,
+ {{0x4fc72471,0x44382042,0x7bdae1b9,0x50b72d98}}, // _исма, byr_, ortu, _अभिष,
+ {{0x44268928,0xb05b4105,0x26c37c1c,0x63a40171}}, // exo_, _geän, _vejo_, _zuin,
+ {{0xbd05e1e9,0x69cb8cef,0x6d41a00e,0x6f1d5c1d}}, // _abẹ́, tsge, _slla, _unsc,
+ {{0x7deaa03c,0x61ef1c1e,0xc6a76439,0xd2506043}}, // _məsu, ntcl, _арби, منے_,
+ {{0xf77f26fa,0x39468012,0x799c6194,0xdce0c0a3}}, // ftçi_, ahos_, _girw, pomě,
+ {{0xb0628052,0x6b899c1f,0x7ff5a678,0x6d41a00e}}, // _ääni, _theg, استا, _vlla,
+ {{0x2409d8cf,0x26c7bc20,0x6b9c7c21,0x60c5311b}}, // ении_, sano_, _zirg, _lehm,
+ {{0x89d98ca7,0xf8ae80e0,0x673bdc22,0x64a5f043}}, // _سوار_, _رکن_, rkuj, лапа,
+ {{0x7f42dc23,0x63789c24,0x2b1c60c2,0x66099c25}}, // _eloq, sínt, नाएँ_, _isek,
+ {{0x7ae562a1,0x27e05c26,0x60e8c156,0x28dccc87}}, // ight, zuin_, ärmö, _याचि,
+ {{0x7d0d0054,0x6e2165df,0x60c3e0e8,0xdb154009}}, // _caas, _àlbu, _wenm, gszü,
+ {{0x60c5200a,0x9f47c026,0xde8f4385,0x63a3e194}}, // _behm, ntní_, _dịka_, _wunn,
+ {{0x6b9d4351,0x3eadc0d5,0xe5a300ba,0x3ce9204b}}, // _aisg, lcet_, сири, _जाणे_,
+ {{0x2007e16d,0x7d0d1c27,0x09cc404b,0x39590e76}}, // _usni_, _faas, ाळ्य, biss_,
+ {{0x2d9ce2e2,0x3eaddc28,0x39582162,0x661b5c29}}, // _zive_, ncet_, sirs_, _šuka,
+ {{0x61ee3c2a,0x21201c2b,0xf2da6711,0x6d599c2c}}, // [19e0] stbl, _inih_, فظات_, diwa,
+ {{0x7bdbc05d,0x7e7d5c2d,0x61e1e057,0x7d01effe}}, // iruu, _dysp, oull, nels,
+ {{0x637deb3a,0x6b9d4c1e,0x3a252156,0x2120004a}}, // gèni, _fisg, älpt_, _knih_,
+ {{0x2bd4c98a,0x4426827a,0x2d9dc49e,0x106a81fc}}, // _दिला, txo_, _niwe_, нией_,
+ {{0x65628089,0x65644088,0xa2ba2eda,0x539b4076}}, // rnoh, mnih, ्रप्, _ריכו,
+ {{0xfaa37c2e,0x6b9c6153,0x290212b8,0x2d805c2f}}, // _гаро, _uirg, heka_, llie_,
+ {{0x6d42c12a,0xb8cca292,0x29020fae,0x61fb9087}}, // _ploa, _गल_, keka_, _epul,
+ {{0x9f40604e,0x26ca2054,0xaea16315,0x237a2037}}, // stiä_, habo_, ğışl, _lkpj_,
+ {{0x7d0d0e38,0xbc6681e3,0x69c1a1d7,0x6b46a069}}, // _raas, _свак, _ewle, _aðge,
+ {{0x3706eaa4,0x7d0d19ae,0x8ff7c13a,0x6135c02f}}, // учив, _saas, ارير_, _báls,
+ {{0x7d0d0213,0x3f82e2b8,0xa2ba2aa1,0x60c9c194}}, // _paas, ēku_, ्रन्, baem,
+ {{0x64a6d11e,0x63706105,0x98a6c691,0x3f9ce0ce}}, // _сада, gänz, _сиде, _wivu_,
+ {{0xfc43e050,0x3636c50f,0x2b591c30,0xb9540052}}, // لپیپ, _حراس, risc_, свящ,
+ {{0x6d56000d,0x6f0d00ce,0x2d805308,0x63a527a0}}, // _emya, _waac, elie_, _puhn,
+ {{0x69ce22bd,0x657523e4,0x645ca00a,0x533541de}}, // gsbe, rozh, _žrij, аевт,
+ {{0x99d3e0e0,0x6d473c31,0xb33b6733,0x7bc2c0ca}}, // _اتوا, shja, _puço, _kwou,
+ {{0x69dc365a,0x2bc68026,0x9099414a,0xa3e4a2ca}}, // [19f0] arre, रीमा, тват_, भूत_,
+ {{0x7e7d4066,0x6d44002e,0xb4db4733,0x63a65c32}}, // _vysp, _zlia, _glàn, _dukn,
+ {{0x05794049,0x26c5a0f2,0xa3e14077,0xdb09e1de}}, // _لمدة_, _relo_, _धमक_, speç,
+ {{0x78a48ebe,0x681ca0ba,0xdef8a2a4,0x491d2026}}, // živn, _mădă, _abċ_, माको_,
+ {{0x3949428f,0x02c5a457,0xf62581d3,0x752d5241}}, // mhas_, айло, рдло, mjaz,
+ {{0x29033a63,0x672d5019,0x69cf0049,0x7bda4076}}, // neja_, ljaj, isce, _בקשו,
+ {{0xddc2a491,0x7d0f5510,0xb0b50029,0x65c2a691}}, // общи, _kacs, ंडाग, обща,
+ {{0x25a5a355,0x8cc881ac,0x6f01e0cb,0x2fcddc33}}, // _vull_, _موفق_, welc, tseg_,
+ {{0x44294174,0x2d53c1fc,0x613f00dd,0x29033c34}}, // nxa_, nţei_, _vëll, keja_,
+ {{0x60cb8426,0xefb28050,0x7bdbc054,0x5a349c35}}, // lagm, _بیوگ, rruu, _унут,
+ {{0x24891c36,0x2d8d8022,0xa3e81c37,0x7bdd0a13}}, // _azam_, _khee_, मून_, ersu,
+ {{0xa3e5c029,0xad9b6057,0x6f03ae96,0x2bddc010}}, // बंद_, _crúz, lenc, _मिपा,
+ {{0x752d40ab,0x7bc2c5cd,0xc60e2046,0x80d0607c}}, // djaz, _gwou, ाट्य_, স্ট্,
+ {{0x6d49dbbf,0xa06a20db,0x2d9e3689,0x290efc38}}, // lhea, вага_, öte_, _gafa_,
+ {{0xdea20050,0xa879e00b,0x4279e00b,0x248909c0}}, // _ویدی, _באַר, _באַג, _ezam_,
+ {{0x6d440167,0x65699c39,0x26cb0c1e,0x26c7e0fd}}, // _ulia, _njeh, caco_, _heno_,
+ {{0x09e64cde,0x6f0f4cc2,0xe4f8c7c8,0x0c264312}}, // [1a00] розн, _dacc, ्जति_, шман,
+ {{0xe6c56049,0x613da061,0x200b4090,0xb17b3c3a}}, // متمي, _dèlk, _dsci_, står,
+ {{0xaadb2095,0x32db21a9,0x35dc0028,0x60c77c3b}}, // _בחיר, _בחינ, _बिड़, _dejm,
+ {{0x1bf6e00b,0x09e6404e,0x69c41c3c,0x443a677c}}, // יצער_, _комн, _kwie, ryp_,
+ {{0x3f9eb610,0xfaa66bbb,0x5f06b342,0x212dc579}}, // _pitu_, _ваго, изда, djeh_,
+ {{0x2ae3c026,0x4153a0ff,0xf8c6208d,0x9f53a823}}, // _गाउँ_, овіс, वरिय, овіч,
+ {{0xe607e50f,0xcc24a07c,0x6ce60028,0x2bd28026}}, // _بش_, বন্ধ_, _कांग_, _सिका,
+ {{0x290332ff,0x2d9ebb67,0x29124614,0x320ac0c2}}, // zeja_, _wite_, ndya_, łbym_,
+ {{0xa2e6b254,0xb8fec9b6,0xdcf181d7,0x27f241cd}}, // _возд, _तय_, _żgħa, ntyn_,
+ {{0x752d4306,0x98a3a07a,0x25a6c65f,0x6370604e}}, // zjaz, _дифе, _vuol_, täny,
+ {{0x63b61c3d,0x39404134,0xd6c4c050,0xa3c97c3e}}, // _atyn, kkis_, _همای, लीप_,
+ {{0x60c8a48f,0x7f49dc3f,0x69cf1c40,0x48e920aa}}, // _hedm, cheq, tsce, _जाहो_,
+ {{0x6b829267,0x6f044fc7,0x661aa143,0x6445e046}}, // ilog, deic, _hrtk, _ühin,
+ {{0x63a09c41,0x6722c066,0x79a6e276,0x6aa401b6}}, // _limn, _hnoj, _крие, _egif,
+ {{0x7aefe066,0xf99f4cd9,0x290fcb33,0x201a26ca}}, // žitý, _piè_, _yaga_, _crpi_,
+ {{0xb09be0be,0x7e6d0265,0xa3c96033,0xf8b4a026}}, // _ביור, _txap, लीफ_, ुरुप,
+ {{0xa3c97c42,0x2d81675f,0xeabf0197,0x63a4c197}}, // [1a10] लीन_, tlhe_, mbù_, _èind,
+ {{0xec71c49c,0x2904d791,0xf7706449,0x3946c057}}, // פֿן_, hema_, وال_, _llos_,
+ {{0x55594a2b,0x6d40c726,0xc678010c,0x3940405f}}, // _баня_, kkma, _محیط_, bkis_,
+ {{0x2bd2808d,0x63a7635d,0xfcab46ec,0x59d58049}}, // _सिखा, _vujn, _جادو_, مغتر,
+ {{0x60cd5c43,0x395940b6,0xed5880ff,0xf8b4a0c2}}, // maam, _imss_, шої_, ुरूप,
+ {{0x7db7633e,0x3946dc44,0xa3e80a82,0xbcfb0066}}, // _نصرا, _alos_, मंत_, dném,
+ {{0xdd9b81fc,0x7aed00d5,0x98f4a0a4,0x6eba24e5}}, // _аша_, _ocat, تثنا, ्रतु,
+ {{0x657b8174,0x6d5d1bab,0x2904dc45,0x6f0560c7}}, // _ukuh, jisa, gema_, lehc,
+ {{0x26ccf292,0x26c7fc46,0x613cc057,0xe579e62a}}, // fado_, _peno_, _xéli, лзи_,
+ {{0x61e686dd,0xa3c8e0a8,0x9985a049,0x2738c017}}, // _åkla, _लौह_, _الطو, _béns_,
+ {{0x61e56058,0x7c3bd15f,0x660d00da,0xb4db41e9}}, // nuhl, syur, _asak, _alàm,
+ {{0x59c4c026,0x200bc065,0x26c7efb3,0x6feaa03c}}, // लीहर, æcis_, _weno_, _təcr,
+ {{0x3a29c03c,0x4174c13a,0x5f74c13a,0x2374c13a}}, // _çap_, _بالس, _بالر, _بالح,
+ {{0x61e56174,0x59a76021,0x3959404d,0x9f406049}}, // kuhl, गदार, _amss_, stiú_,
+ {{0x63bbd570,0x6ade607c,0xbebb00dd,0xe64320ff}}, // lpun, ন্ডো, gjës, перп,
+ {{0xccc607ca,0xfd120ef8,0x661ce065,0x4205b1c9}}, // _убий, _رجب_, ærke, _शनैः_,
+ {{0x637d0050,0x63680602,0x99d6033e,0x75357c47}}, // [1a20] rént, nınm, _اتبا, _bozz,
+ {{0x6f04503c,0x39405c48,0x61e45c49,0xc7b8a320}}, // seic, skis_, ruil, _hrđe_,
+ {{0x61e56058,0x7d04494b,0x6d59cb01,0xe9da6bcb}}, // guhl, peis, _omwa, уке_,
+ {{0x2b2100c2,0xd37980ff,0x0c2682cb,0x2904c057}}, // याएँ_, ачі_, ймен, xema_,
+ {{0x991664a4,0xb347001b,0xa2aa8029,0x61e61c4a}}, // _льві, luçõ, टशब्, nukl,
+ {{0x6d5d11c7,0x7d060134,0x6281a013,0x257204d3}}, // zisa, heks, _bylo, rály_,
+ {{0x236680e4,0x395ca156,0xd7faa425,0xdb078271}}, // pnoj_, rivs_, уул_, _stjå,
+ {{0x26cce096,0x463b649c,0x26ca600a,0x57b4dc4b}}, // wado_, _געלע, _hebo_, обит,
+ {{0x26ca6b39,0x29117412,0x63a1a90b,0xdb01ac1e}}, // _kebo_, _yaza_, _filn, _milê,
+ {{0x6d5d1b67,0xeb9ab7ad,0x67241c22,0x752416fb}}, // wisa, _сиз_, _anij, _aniz,
+ {{0xdd9507b9,0x5286a13a,0x2904c296,0xe97b600b}}, // жаны, _الاك, pema_, רניש,
+ {{0x63a1a1fc,0x2b5a2057,0xb34701ae,0x2fdfc5ce}}, // _ziln, _ampc_, duçõ, brug_,
+ {{0xa3cb2064,0x2d83220d,0x69da206f,0x79844063}}, // रीन_, tlje_, áten, dliw,
+ {{0x3f84c17a,0x7c220055,0xd90f2062,0x273c40c4}}, // llmu_, _šorc, ریج_, _mínu_,
+ {{0x6fb4e343,0x67357c4c,0x644f069f,0x20000030}}, // _بمبا, _rozj, szci, _epii_,
+ {{0x672407a0,0xbfa824dc,0x25a90037,0x322b471b}}, // _gnij, стре_, _tual_, лізм_,
+ {{0x61e60121,0x75357c4d,0x7d05603c,0x6d5e62f4}}, // [1a30] cukl, _pozz, tehs, fipa,
+ {{0x973cc143,0x66df40f9,0x21248030,0x200955a6}}, // _bućk, _bàkà, _anmh_, rwai_,
+ {{0xe535404e,0x2b49017b,0x3f1589fd,0x60cd4052}}, // _день, _klac_, ждес, paam,
+ {{0x25aa7c4e,0xa301a843,0x501be076,0x6282c079}}, // _dubl_, लज्ज_, טובו, _oyoo,
+ {{0xbcfb00fa,0x26ca6f8f,0x777ae534,0x6d4d41b9}}, // ciée, _febo_, kotx, mhaa,
+ {{0x6e20427f,0x527a4076,0x2b4910bf,0x973cc133}}, // _ámba, _כנרא, _llac_, _fućk,
+ {{0x29133c4f,0x6282c51f,0x3ebfc0ae,0xa5bb43f6}}, // _maxa_, _ayoo, tbut_, _bróg,
+ {{0x6d48bc50,0x2905fc51,0xc245d3fc,0xf1d4c0c2}}, // _elda, wela_, жник, _दिखन,
+ {{0xdb1c8069,0x7d061729,0x05a6c04e,0x98a9403a}}, // _evró, veks, овый_, kiaľ_,
+ {{0x39490939,0x6d5ec162,0x3d19dc52,0x628bc42e}}, // _alas_, épar, _मोरे_, _vzgo,
+ {{0x63a2c6a5,0x6b8445d4,0x7d0729be,0xdced438a}}, // _fion, ylig, dejs, hlađ,
+ {{0xdced5bd8,0x78a2404d,0x69dce359,0x6d5d0004}}, // klađ, žovi, área, érap,
+ {{0xdb009c53,0x5f74c553,0x1539e0eb,0x6282c614}}, // _dimè, _عامر, _پزشک_, _gyoo,
+ {{0x6d428e3b,0xa3e6e077,0x63abc08b,0xdb01a1ae}}, // zkoa, यूह_, _iugn, _silê,
+ {{0x4176054f,0x6d5f4efb,0x26ca6546,0x3392e050}}, // _وابس, fiqa, _rebo_, _تلوز,
+ {{0xbcfb03ab,0x236d806e,0x973ca143,0x395fdc54}}, // jnéh, _njej_, _mića, nius_,
+ {{0xfd1f4067,0x27e053b1,0x3ce600c5,0x6283e0fd}}, // [1a40] _nhìu_, urin_, कलीं_, _hyno,
+ {{0x6283fc55,0xf1e08914,0x23e087c8,0xdee3652f}}, // _kyno, _निबन, _निबद, дофи,
+ {{0x316d8098,0x48eb612f,0x9a6a613a,0xdce455aa}}, // _bjez_, _चारो_, جمال_, lnič,
+ {{0x98a681bc,0x06868bd6,0x6b844025,0xa5bb510c}}, // циде, оген, qlig, _bród,
+ {{0x6d428534,0xe5a38494,0x6569c02e,0x34a7414f}}, // rkoa, фици, dneh, _євро_,
+ {{0xadc322f4,0x6d445c56,0x6d5b8194,0x27e6808b}}, // _afẹd, lkia, _emua, ruon_,
+ {{0xb53a00f9,0x7f498fb3,0x6569c0cb,0x5693bc57}}, // _akọ́, _fleq, fneh, машт,
+ {{0xceb44497,0x236d80dd,0x387f017a,0x63a3e420}}, // ncə_, _gjej_, żuri_, _ainn,
+ {{0xfd4f4016,0xa3e5c077,0x22404396,0x61e1e057}}, // _phiế, बूल_, nyik_, erll,
+ {{0x6d5abc58,0xb603c02e,0x88bca04a,0x60c29c59}}, // _umta, úšaj, _zněn, lbom,
+ {{0x7d073c5a,0x777bc005,0x2731c009,0xceb4403c}}, // vejs, boux, _lány_, kcə_,
+ {{0x2d92005d,0x8fa6c34d,0x2d85e052,0xe9b7a043}}, // _chye_, _даде, alle_, _بچوں_,
+ {{0x657aef42,0xdb9b8087,0x26cb5c5b,0x4f65e0e0}}, // poth, יסטר, _reco_, _کانف,
+ {{0x63aae143,0x61e29063,0x657ae0bb,0x26cca4f8}}, // _tufn, irol, qoth, _jedo_,
+ {{0x63760009,0x6283e0fd,0x28c80077,0x78b02089}}, // hány, _gyno, लरशि, _živý,
+ {{0xd37900e4,0x63a41518,0x6602c088,0x6d5c7c5c}}, // kući_, _giin, _kpok, _amra,
+ {{0x7640cccc,0x25a362be,0x32555043,0x61e8fc5d}}, // [1a50] nymy, _pijl_, чвар, ludl,
+ {{0x6d5b903c,0x7f4d5bd7,0xbef8cba0,0x63a40800}}, // _smua, shaq, ्जुन_, _ziin,
+ {{0xab98213a,0x26d120ca,0x20024012,0x61e8e426}}, // _بخير_, mazo_, _epki_, nudl,
+ {{0x661d429c,0x2907b1b6,0x6602dc5e,0xdb0b0156}}, // _trsk, wena_, _opok, _utfä,
+ {{0xe8d90088,0x6fc684ba,0x6569c06f,0x2bc68026}}, // _amị_, रीलं, vneh, रीला,
+ {{0x2126c08b,0x657d1c5f,0x63ad1c60,0x660d4284}}, // _anoh_, hosh, _muan, mwak,
+ {{0xeaf8a13a,0xe57388b8,0x61e8e1f3,0x201ea320}}, // ترنت_, _قطر_, judl, _crti_,
+ {{0x657d1c61,0xb4db458f,0x63a3fc62,0x2bff410a}}, // josh, _blài, _sinn, _उहां_,
+ {{0x442dc1ae,0x26d13c63,0x69dce195,0x613dabf2}}, // uxe_, kazo_, áren, _bèlt,
+ {{0x6d444265,0x657bc04a,0x26cca025,0x394dc393}}, // zkia, souh, _gedo_, shes_,
+ {{0x26d125e0,0x443fdc64,0x6284005d,0x63a40134}}, // dazo_, syu_, _syio, _piin,
+ {{0x657d1c65,0x63a3fa4c,0xa96a1255,0x6d4afc66}}, // gosh, _winn, сима_, _elfa,
+ {{0x63a53c67,0xdb01ae2d,0x81ada07c,0x4acb635b}}, // _bihn, _ailé, _কবি_, िराव,
+ {{0x63a3f03c,0x26d12019,0xdb1c40fa,0x7bc980ce}}, // _uinn, gazo_, mpré, _mweu,
+ {{0x69d520e0,0x21395c68,0x63ad0090,0x63a4111b}}, // gsze, _bosh_, _euan, _tiin,
+ {{0x20024058,0xf412a00b,0x63bb95ca,0xb33b61de}}, // _ppki_, עפן_, _otun, _muçu,
+ {{0xa3d60ba3,0x945f80e8,0x248ee143,0x69c98088}}, // [1a60] _हिट_, _fāɗ, _zzfm_, _nwee,
+ {{0x6d46004e,0x629a6066,0x6d5d5c69,0x26d12889}}, // lkka, _útok, _emsa, cazo_,
+ {{0x913b2076,0x69c98035,0x26140485,0xdb01a011}}, // _העיק, _awee, दिकी_, _gilé,
+ {{0x63ad0020,0x44201c6a,0x68fbc098,0x2be08180}}, // _yuan, _kri_, jfud, _निदा,
+ {{0x25bf61e4,0xb4db4031,0x7d161c6b,0x26086148}}, // _čule_, _alàw, _mays, _सनकी_,
+ {{0xd37902e2,0x7d160054,0x69c981e2,0x657d0c55}}, // rući_, _lays, _dwee, zosh,
+ {{0x9f98c955,0x06e3c07c,0xdb0f6009,0x69c98035}}, // овку_, _মসজি, _utcá, _ewee,
+ {{0x7d08e54c,0x998920ba,0xf1be05c5,0x23dc1b2c}}, // weds, _oraş_, ्ठान, _बिंद,
+ {{0x69d65c6c,0xebe38244,0x69d52367,0x63ae7c6d}}, // nsye, _потп, zsze, _hubn,
+ {{0x68ed45e0,0xd7ddc010,0x65629c6e,0x753a2105}}, // zgad, _मिरच, lioh, ötzl,
+ {{0x7d09dc6f,0x6728a17a,0x201eb278,0x3a200011}}, // bees, _indj, _urti_, _arip_,
+ {{0x753aa108,0x98a7e04a,0xe0df4030,0x9c870089}}, // _hotz, _koně_, _elòj_, _ničí,
+ {{0x69d52064,0xdcf8a098,0x6b82c7a0,0x753aaa13}}, // wsze, hovč, _ikog, _kotz,
+ {{0x657d0617,0x8fa3e841,0x25a520c4,0x63a22425}}, // sosh, _заче, öll_, _èonl,
+ {{0x6fd90010,0xb604212e,0x753aaa03,0x317ee108}}, // _भटकं, нятк, _motz, motz_,
+ {{0x7d160054,0x657d074d,0x753aa108,0x53345c70}}, // _gays, qosh, _lotz, нест,
+ {{0xdb040005,0x290a31a1,0xc434e043,0x21290071}}, // [1a70] _etnó, beba_, _رکھت, _inah_,
+ {{0x69d522b1,0x7afd1c71,0xfbd20095,0x9d146825}}, // psze, lfst, _שתי_, ндуч,
+ {{0x21394c8a,0x661bc530,0x69c98957,0x7d09cbbe}}, // _tosh_, zvuk, _swee, yees,
+ {{0x290b08d4,0x2bddc07d,0xdb0be1ab,0xf41f4156}}, // neca_, _मिला, _eugè, _trä_,
+ {{0x68434031,0x7ae48dc1,0x63bc6030,0x6dac60ba}}, // _dídú, óita, _etrn, _aşad,
+ {{0x290b1c72,0x2be24861,0xd040c03c,0xa49b40d1}}, // heca_, _पिता, qamə, _ebòc,
+ {{0xe8d901e9,0x2486c187,0xe51ecc13,0xf8ae80e0}}, // _amọ_, _ayom_, _मोहि_, اکہ_,
+ {{0x6e24d192,0xf0942087,0x290b05a0,0x2f54c8e7}}, // _šiba, ענק_, jeca_, етьс,
+ {{0x6561fc73,0x63ae61d8,0x395f98b3,0x6f09dbd7}}, // tilh, _zubn, _imus_, reec,
+ {{0xb4e601b0,0x7d161c74,0x673aa361,0x63af429c}}, // कल्प_, _says, _gotj, _mucn,
+ {{0x7d160004,0x6d4bc08b,0x753b9c75,0x69d76057}}, // _pays, _tlga, _kouz, nsxe,
+ {{0xa5b20064,0x34c622b4,0x63a76200,0xe9ff4067}}, // _इंदौ, वर्द, _lijn, _nhắc_,
+ {{0x7d172caf,0x5155e242,0x6b89c31b,0xd7f8e12a}}, // _baxs, _отку, mleg, ată_,
+ {{0x2480a106,0x2f18e052,0x26cfc108,0xd379029c}}, // şime_, _ночь_, _hego_, duću_,
+ {{0x35a321d3,0x6b503006,0xe1f9c013,0x316dc0ba}}, // тарг, _näge, ltų_, onez_,
+ {{0x378701e1,0x61e45c76,0xdb0782a8,0x290a3c77}}, // _общо_, tril, _bují, seba_,
+ {{0xd37900a9,0xed5a27f8,0x24f669fd,0x61e45c78}}, // [1a80] guću_, _мои_, дчер, uril,
+ {{0xa3cb22b4,0x49ba2553,0x644280ce,0x6443b259}}, // रीह_, _واجد_, vyoi, kyni,
+ {{0xdfcf613a,0x673b8163,0x25ea4021,0x973ca143}}, // ليل_, _bouj, _चमकी_, _sićo,
+ {{0x236ddaee,0xdb02c057,0xb95b60f9,0x0b468376}}, // jnej_, _tioé, _atìd, ьнен,
+ {{0x6563a41a,0xdca380cf,0xf2d34053,0xb4db4017}}, // finh, ваци, _בער_, _clàu,
+ {{0x290b0055,0x25a6dc79,0x5694a425,0xe4570053}}, // zeca_, _siol_, налт, וינט_,
+ {{0x63af420d,0x7bcbc2a4,0xfbb041a6,0x6b640009}}, // _zucn, _awgu, _ছবিত, zőga,
+ {{0x61eab9d3,0x6b9bc0ae,0xb814c39d,0x61f8ab83}}, // rufl, gmug, едиј, rtvl,
+ {{0xe4e6e14f,0x3cfbbc7a,0x201e664e,0xd007c01b}}, // мінн, ल्हे_, _štih_, дење_,
+ {{0x753b8653,0x26cfc025,0x799ae022,0x93bca0ba}}, // _zouz, _eego_, umtw, _crăc,
+ {{0x25a6c057,0xc057e0ff,0x753c669f,0x61e61c7b}}, // _tiol_, дія_, _morz, erkl,
+ {{0x3a29c16d,0xa01b4052,0xb7d4c1e9,0xdcfaa2a4}}, // _šape_, _syöm, _aṣeb, _bitħ,
+ {{0x25afc6be,0x24920064,0x442240ff,0x753c6200}}, // _gugl_, _czym_, _nrk_, _oorz,
+ {{0x70ab8148,0x7984000d,0x9f4340a3,0xb4db4733}}, // _छल्ल, _ekiw, rují_, _llàt,
+ {{0x63a76171,0xf1a7487e,0x672ae2a4,0x6b51563b}}, // _rijn, дран, _infj, _låge,
+ {{0x7d18f971,0x291860f9,0x14bf21c9,0x6e22c187}}, // _havs, _aara_, ्रहण, _krob,
+ {{0x1eab2049,0xa01b4052,0xddc9c0c2,0x78ad0046}}, // [1a90] _وادي_, _työm, czeń, _igav,
+ {{0xddc28064,0x67299c7c,0xadf2e69e,0x661d8069}}, // szoś, _snej, _अमान_, _áski,
+ {{0x69d8a200,0x58d54f2f,0x63a7600e,0x27e0813a}}, // jsve, _понт, _vijn, áinn_,
+ {{0xa83541e3,0x69d8a9ee,0x6e22c852,0x7bcd104a}}, // _земљ, dsve, _orob, _kwau,
+ {{0x9f5a40c4,0x6f0d50a1,0x9105b9c1,0x6443a1cd}}, // _uppá_, meac, епле, wyni,
+ {{0x673c7c7d,0x39495540,0x7643a04e,0xfbd026b0}}, // _gorj, lkas_, tyny, _حتی_,
+ {{0xd88364b7,0x290ce03b,0x57b4687c,0x3206c4ec}}, // _شهری, deda_, _абст, _apoy_,
+ {{0x6e946702,0x6443bc7e,0x3cff20c2,0x3f816066}}, // _рису, ryni, श्ते_, lohu_,
+ {{0x7d01bc7f,0x2d9d8b01,0x7989dc80,0x6006c3fc}}, // _tbls, mmwe_, rlew, еным_,
+ {{0x63a9804e,0x6aad1c81,0x2bdaf204,0x26cfc17b}}, // _hien, _agaf, _भिखा, _wego_,
+ {{0xbcfb00e6,0x39494046,0x25a91c82,0x1da8a028}}, // rién, kkas_, _bial_, _छूटत,
+ {{0x6f02c291,0x62898037,0x7c3ce156,0x48dbe026}}, // _gboc, _kyeo, ärra, _गएको_,
+ {{0xfce5e6fc,0x7d18faba,0x7bc1fc83,0x7d0d4ce5}}, // _золо, _favs, splu, deas,
+ {{0x79460064,0x21f8e18c,0x7d19c054,0x3eac2271}}, // _mówi, béh_, _haws, ødt_,
+ {{0xb8d8210a,0x279641e1,0xeb9a40c0,0xab2a5138}}, // _छल_, ешар, зив_, пова_,
+ {{0xe7374276,0x6fe12046,0x6f1bc8cd,0x41e12857}}, // нес_, _फिरं, nduc, _फिरस,
+ {{0x7d040132,0x6b502277,0x29195c84,0x2d821c85}}, // [1aa0] _ibis, _vägb, _dasa_, moke_,
+ {{0x7c240082,0xa01b404e,0x06e3607c,0xb95b61e9}}, // _irir, _myöh, ন্সি, _atìb,
+ {{0x130a87b9,0xd910a050,0x9743429f,0x76444058}}, // зней_, ییر_, šćav, syiy,
+ {{0xdb1b1c86,0xfd4d8125,0x6f0d44b9,0x61fbdb14}}, // sstö, _ighọ, ceac, ktul,
+ {{0xd04085fc,0x6288a2a6,0xb4db4733,0x44225c87}}, // _yemə, _sydo, _elàs, _urk_,
+ {{0x99440602,0x6e24c098,0x44204167,0x27e08edc}}, // yış_, _šibo, mvi_, šino_,
+ {{0x7bc29c88,0x6ed6fc89,0x27e68058,0x3facc07c}}, // spou, _मजबु, sron_, _গবেষ,
+ {{0x2900566b,0x7bd8bc8a,0x8fa39c8b,0x9f4de052}}, // nfia_, rsvu, _басе, hteä_,
+ {{0xb603a041,0x88818050,0x213dc05d,0x69d8b492}}, // āšan, _پیون, _dowh_, ssve,
+ {{0x25a90a81,0x61ee25d9,0x7980c72c,0x26dea0ae}}, // _sial_, kubl, somw, _udto_,
+ {{0x61fbd617,0xdb1c8019,0x39576076,0x3157600b}}, // atul, _atré, גשים_, גיין_,
+ {{0x6d49dc8c,0x29028277,0x21f9c050,0x7d02c99d}}, // ckea, _ökad_, bèh_, _ubos,
+ {{0x61fbc12a,0x6e240cc2,0xf20403fc,0x60d52098}}, // ctul, _crib, ляро, tazm,
+ {{0xf99f6061,0x6b81e197,0x291cbc8d,0x7966e011}}, // _apèn_, colg, ndva_, _déwè,
+ {{0xf7734085,0x442046e8,0x26d16163,0x2d9ee105}}, // هار_, evi_, _rezo_, mmte_,
+ {{0x69da2004,0x26d16098,0x3949403b,0x69c08055}}, // âtea, _sezo_, ukas_, _otme,
+ {{0x7bdc6121,0x23cf827d,0x4423604d,0xb4bf01b0}}, // [1ab0] _avru, _सौंद, _srj_, ेरी_,
+ {{0x2aee2064,0xa8a4453f,0xb4db5c8e,0x7985313a}}, // _जाएँ_, урск, _plàs, _ukhw,
+ {{0x2fd9213a,0x16bf2d98,0x69c09b43,0xceb4403c}}, // _توجد_, ्रीब, _atme, mdən_,
+ {{0x6b829994,0xf42a002e,0x6f1d02b7,0x9f4060c4}}, // goog, späť_, ndsc, stið_,
+ {{0x291a3344,0x237484aa,0x62868042,0x08f8201c}}, // _yapa_, دالح, _økol, _غریب_,
+ {{0xa3cc6033,0xdb040105,0x69d98d53,0x63a99c8f}}, // _शौक_, _zunä, rswe, _wien,
+ {{0xf770a28b,0x7bdafc90,0x3a24805d,0x6b829c91}}, // حان_, jstu, _ermp_, boog,
+ {{0x6f0f05df,0x3eb983ed,0x2d832003,0x236681d3}}, // jecc, _üst_, hoje_, cioj_,
+ {{0x7d1bc290,0x3f833c92,0x5724c049,0xc115a176}}, // rdus, koju_, طريق, _имај,
+ {{0x6b8d4669,0x7cd9a01c,0x672d1c93,0xd6848656}}, // hlag, _آواز_, _anaj, _сусп,
+ {{0x2d821b67,0x6b8d4831,0x2d833c94,0x798d5963}}, // toke_, klag, doje_, klaw,
+ {{0x8afce064,0x7d0e3c95,0x628ae1cd,0x6b81effe}}, // wnęt, webs, _dyfo, solg,
+ {{0x6566041e,0xee3701ef,0x3b1b013e,0xc4bf20c2}}, // rikh, вня_, _daqq_, ्रूख,
+ {{0x6566185f,0xd138e064,0x4425bc96,0x9f4de277}}, // sikh, cją_, _krl_, fteå_,
+ {{0x764de0a2,0x9479313c,0x64461c97,0x2d788133}}, // _ƙayu, есту_, syki, nčec_,
+ {{0x6e241c98,0x7d1b9c99,0xdb0e8019,0x6f0f0197}}, // _trib, _naus, _tubé, cecc,
+ {{0x629ca032,0x7bdbd11b,0xe5a34b2d,0xef1a604e}}, // [1ac0] _úrov, nsuu, рити, мма_,
+ {{0xe29741fc,0xf2134405,0xf8e1e064,0x08c364d4}}, // тау_, সমূহ_, पणिय, ибун,
+ {{0xd5b9614f,0x66099655,0x9fa30057,0x7c22004d}}, // есі_, _apek, cíón_, _šorh,
+ {{0x6f1b9c9a,0x66e68b2d,0x6b8d5c9b,0x80cc8029}}, // _cauc, тода, clag, ारें,
+ {{0x6fb38230,0x7983a1cd,0x79828025,0x628ec0c2}}, // لمنا, fonw, roow, ębok,
+ {{0x69d86032,0xe8d90125,0x69c56048,0x6f0f0024}}, // áves, _ikụ_, bphe, zecc,
+ {{0x2dd7a13a,0x290f936e,0x399900e0,0x69c5703c}}, // نبية_, bega_, lése_, cphe,
+ {{0x39401bd3,0xdce6003b,0x1514c12a,0x26d82a03}}, // _nois_, mokė, удия, haro_,
+ {{0x3d178010,0x5d678b24,0xdb0f6005,0x26d83c9c}}, // _नसते_, _ритм_, _sucé, karo_,
+ {{0x6f1c620d,0xaca40079,0x2bb54028,0x6d409c9d}}, // _jarc, _chọw, _अंसा, _homa,
+ {{0x28f908e9,0x2bd00010,0x26d32057,0x7d1b835f}}, // нень_, तीसा, _rexo_, _yaus,
+ {{0x61fd0551,0x27e99c9e,0xfd1f40f9,0x7bdb6095}}, // ttsl, šane_, _akìn_, וקטו,
+ {{0xdb03e1ae,0x6d565c9f,0x26d83ca0,0x2d84c320}}, // _dinâ, dhya, faro_, mome_,
+ {{0x7bc2cf1f,0x929d80c2,0xa2c501a6,0x3f84dca1}}, // _itou, _cały, ্লেখ, lomu_,
+ {{0x61ef0098,0x1df8220e,0x1b0801a6,0x61fd0584}}, // sucl, керы_, ষয়ে_, stsl,
+ {{0x2bdc1ca2,0x3a26c065,0xdb01a530,0xdef8e17a}}, // _बिचा, _krop_, _milá, diċi_,
+ {{0x2d833ca3,0xa91dc3a1,0x27e95ca4,0x7d1b9a3c}}, // [1ad0] poje_, noža, uran_, _raus,
+ {{0x2d876162,0x6568fca5,0x2d83e38a,0x63ad1ca6}}, // ône_, nidh, čjeg_, _hian,
+ {{0x99520066,0x6f029ca7,0x6f1c7ca8,0x60d76caf}}, // láš_, ffoc, _darc, raxm,
+ {{0x7c2a2069,0x27e95ca9,0xfd1f0031,0x7ae2ca05}}, // _áfra, pran_, ndì_, _ndot,
+ {{0x2d84caf1,0x6b83bc12,0x69da212d,0x0f36400b}}, // dome_, tong, štet, _ערשט_,
+ {{0x6568e0dd,0x69dd1caa,0x2b8e805c,0x75d28049}}, // jidh, isse, hých_, حيوا,
+ {{0x45d4604e,0x6f1b8105,0x2d8780fa,0xb5a781d3}}, // _восс, _tauc, înes_, _арай,
+ {{0x2ee9a03c,0x93bca067,0x99520066,0x69dbc265}}, // şaf_, _trăn, káš_, tsue,
+ {{0xfc31c13a,0x79898079,0x38608049,0x7d1b4057}}, // صحة_, _ikew, úirt_, _óusu,
+ {{0x29112320,0x4426dcab,0x8ccaba13,0x25ad8363}}, // jeza_, _dro_, _स्पो, _hiel_,
+ {{0x25ad9ac9,0x628bc064,0x6d41a0ef,0x399900e0}}, // _kiel_, _wygo, _iola, zése_,
+ {{0xa5bb5cac,0xf0926095,0xaca3e291,0xdb08c009}}, // _pról, מני_, _ntụk, _tudá,
+ {{0x2bddc033,0x6d4d4054,0x6569c04e,0x79898167}}, // _मिटा, dkaa, mieh, _mkew,
+ {{0xf8c6a738,0x645bc0ce,0x291cf4b7,0x394ce425}}, // रुपय, mzui, _gava_, ckds_,
+ {{0x1fb536a6,0x6d935cad,0x63b5609f,0x69dd0ffe}}, // истр, rçan, _nuzn, asse,
+ {{0x6f1c7cae,0x2508054f,0x26d83997,0x69dc3c7b}}, // _sarc, _شرعی_, saro_, rsre,
+ {{0xe1ff0333,0xdd92625b,0x6e93413a,0x7649dcaf}}, // [1ae0] ctó_, مور_, ملفا, nyey,
+ {{0x79899280,0x799b81df,0xb8eb5065,0x79861cb0}}, // _akew, _ahuw, _र्_, lokw,
+ {{0x3cfb2095,0x6f1c777e,0x8afb20be,0x63b60067}}, // _אלינ, _varc, _אפיק, _huyn,
+ {{0x291ddcb1,0xed579cb2,0x61fca156,0xdb09e1ae}}, // _nawa_, лох_, _ärli, mpeõ,
+ {{0xa96aa04d,0x23cf8029,0x7f46b9b6,0xd6d9c064}}, // нида_, सीलद, ледж, dał_,
+ {{0x7d1d5cb3,0x79860bef,0x7f41a090,0x2d8780fa}}, // _zass, hokw, _colq, îner_,
+ {{0x68e414fd,0x7c229cb4,0x26d91cb5,0x7f40805f}}, // _odid, svor, yaso_, _tomq,
+ {{0x3a26dcb6,0x291cfcb7,0xbea684e4,0x645bc171}}, // _prop_, _sava_, _бабк, fzui,
+ {{0xed5786bb,0x06f6aca7,0x63bb6098,0x6448fb61}}, // _сот_, _مساج, _čuni, wydi,
+ {{0xdd04c106,0x845aa1c0,0x6448f5a5,0xa01b4052}}, // ırıy, _прет_, tydi, _työv,
+ {{0x33750d49,0x69c41cb8,0x4c94f0da,0x49750792}}, // игор, _atie, ринс, илос,
+ {{0xa3c96029,0x7649c00f,0x6569c06f,0x6568fcb9}}, // लीट_, byey, cieh, sidh,
+ {{0x2d85fcba,0x63ad1cbb,0xef1f06df,0x5ce720ff}}, // bole_, _vian, rdü_, люва,
+ {{0x7c244b48,0xe693c13a,0x63a29cbc,0x7c2f8057}}, // dvir, _الهد, hmon, _ácre,
+ {{0x6f044239,0x4427e08b,0x63a28011,0x6db2c0c2}}, // ffic, _grn_, kmon, _wład,
+ {{0x63ae61f3,0xa3b9fb5a,0x69c8e017,0x9d184844}}, // _aibn, _अंत_, mpde, лост_,
+ {{0x7d1d53e9,0x69de6684,0x5ed4207c,0xd7e08292}}, // [1af0] _vass, espe, _তারে, _निगच,
+ {{0x2b99e0fa,0x69c73241,0x3a290197,0x799b81df}}, // pèce_, ppje, _irap_, _shuw,
+ {{0x44290326,0x6aa6407c,0x39425c4e,0x48156143}}, // _hra_, গুলো, _foks_, рмес,
+ {{0x7bc520bb,0x27ec2066,0xe0e701a6,0xdce440c2}}, // _ithu, ádne_, গ্রহ_, knię,
+ {{0x9fdfc07c,0x60cd4df7,0x2000412a,0xa3cf84da}}, // _বঙ্গ, mbam, ctii_, वीं_,
+ {{0xed516043,0x25ad8110,0x1869a7f7,0x60cd5cbd}}, // _بھر_, _wiel_, кали_, lbam,
+ {{0x200ca005,0x7f42c054,0x79860287,0x8835a1d3}}, // _cpdi_, _booq, yokw, рэлц,
+ {{0x3fe5d2ad,0x7d1f0b20,0xa3ea60aa,0x25aee153}}, // ажив, _naqs, _मित_, _aifl_,
+ {{0xdb162067,0x44291cbe,0x6702637f,0x947786ec}}, // _duyê, _nra_, र्वक_, _صدرا,
+ {{0x20d3c067,0x661b9cbf,0x60d72037,0x613cc0e0}}, // ải_, _isuk, _nexm, _mély,
+ {{0x291dc1fa,0xed5a053f,0x764b80fd,0x394dc037}}, // _tawa_, вон_, lygy, pkes_,
+ {{0xa96a1cc0,0x399ac00e,0x6d5a8013,0x6d598037}}, // тима_, tëse_, _įtak, nhwa,
+ {{0xbb84c13a,0x06f76573,0x60dbc0ce,0xa01b4052}}, // _الني, ुभाव_, maum, _myös,
+ {{0x39424012,0xb3d7807c,0x442c59e4,0x18a5f196}}, // _soks_, _সমাজ, _čd_, _калм,
+ {{0x6d9d013a,0x3a2907f2,0xdb1c80c4,0x63ae6364}}, // féad, _erap_, _strí, _ribn,
+ {{0x60dbc296,0x6d557cc1,0xdb162016,0x26da6296}}, // naum, _alza, _xuyê, wapo_,
+ {{0x3a291cc2,0x2baf88ae,0x1f6664a0,0x4aaa5cc3}}, // [1b00] _grap_, _जंजा, акам, ткан_,
+ {{0x6d44010a,0xd00f063b,0x9e66254a,0x0e66204f}}, // _hoia, _کلی_, _ввод, _вкон,
+ {{0x3942403b,0xa01b404e,0xa2d9a033,0x8afce064}}, // _toks_, _syöt, _नज़्, więk,
+ {{0x1426852f,0x6d4410cd,0x7c298197,0x65664174}}, // идем, _joia, _brer, _umkh,
+ {{0xa91dcebe,0x27eddcc4,0x6d58bcc5,0x6d440733}}, // tožn, jren_, thva, _moia,
+ {{0xe5e58049,0x92ce41a6,0x63a4551d,0x07e6a825}}, // إثني, _রায়_, lmin, ацим,
+ {{0xa91ddcc6,0x12e9200b,0xdbcfc089,0x656b9cc7}}, // rožn, אַפּ, _džín, bigh,
+ {{0x6e2dc2d1,0xab748050,0x24536049,0xdb0780c4}}, // _šaba, _انرژ, _بنفس, _stjö,
+ {{0x6e298a03,0xdb03e005,0x6db2c0c2,0x0e668d15}}, // _greb, _siná, _słab, _ткан,
+ {{0x9abca227,0xda26e14f,0x442a6089,0x2d87bcc8}}, // _baċe, аємо, _krb_, cone_,
+ {{0xb87b41e9,0x6d43ee96,0x660e605d,0x6b502156}}, // _adíl, _dona, _jpbk, _vägl,
+ {{0x26d87973,0x7a0aa03c,0xb4d548c6,0x8c48420f}}, // _lero_, _tətb, _सजे_, _kağı,
+ {{0x75c1e018,0x7f43e1f6,0x7bcaa076,0xdcfbc6fa}}, // _rēze, _fonq, lpfu, lluğ,
+ {{0x26d87cc9,0x6d561703,0x442a6282,0x27e05cca}}, // _nero_, _elya, _orb_, dsin_,
+ {{0x613cc03e,0xf9c4e555,0xe8194028,0xdcfbc20f}}, // _bélx, _احتی, नौना_, nluğ,
+ {{0xe73801e3,0xc2e8c07c,0x23bc2009,0x61e2c02e}}, // _већ_, ক্রি_, _díj_, _kvol,
+ {{0x60dd01e7,0x6d43f650,0x96272041,0x2b920066}}, // [1b10] masm, _yona, _saņē, dách_,
+ {{0x2ef5337a,0xdb0f738b,0xdb0bc2a8,0x629aa108}}, // _узор, _tucí, _vigé, _ozto,
+ {{0x2d87a197,0x7d152106,0x60dbd12b,0x644b81cd}}, // vone_, mezs, yaum, wygi,
+ {{0x291362b2,0xaca34079,0x20017ccb,0x63a446fa}}, // sexa_, _anụb, rthi_, cmin,
+ {{0x7afd40ae,0x67208a14,0x20016050,0xf1a4402d}}, // _icst, _damj, sthi_, _фрэн,
+ {{0x6b74a926,0x60c2c0f9,0x6b88e3ce,0x27edd40d}}, // олчу, _afom, jodg, vren_,
+ {{0x3dc6c037,0x2b920066,0x6d5afccc,0x9f43402e}}, // _atow_, bách_, dhta, dujú_,
+ {{0xe6782095,0xf8bf4639,0x7415c13a,0x387e00ba}}, // יתוח_, _inés_, موعا, ştri_,
+ {{0x3f87bb43,0xb345e1ae,0xaa49c052,0x7f43e071}}, // ponu_, _noçã, уппа_, _ponq,
+ {{0x21212037,0xaca321e9,0x26dd9ccd,0x63a44a88}}, // _nahh_, _amọd, lawo_, zmin,
+ {{0x5e95a13a,0x2b9c8026,0x7c95a13a,0x63a566a5}}, // _الاط, síce_, _الاص, imhn,
+ {{0xe8f71574,0x2d78804d,0x04c9cae5,0x69c9dcce}}, // блю_, jčel_, _صوتي_, ppee,
+ {{0x9f43406f,0x4279800b,0xdb1c80c2,0xee2eca8d}}, // bujú_, ראָג, _zwró, _эн_,
+ {{0x644d4f62,0x798d0194,0x41cf8077,0xa06a253f}}, // dyai, _ekaw, सीएस, гага_,
+ {{0x6d453ccf,0x2cac2156,0x6d988a60,0xe81fc077}}, // _doha, ädd_, dían, बिका_,
+ {{0x65698125,0xdb0d40cb,0x63a45cd0,0x213fc7a0}}, // _nmeh, rmaß, umin, njuh_,
+ {{0x98a3482f,0x78c6e0e0,0x7d018009,0xcebae0a2}}, // [1b20] зите, _érvé, záró, _haƙa_,
+ {{0xef1a21e3,0x442b51a1,0x39920009,0x6d5bd833}}, // _смо_, _drc_, dási_, hhua,
+ {{0x26c04639,0xa2aa8982,0x232a833f,0xe3b260e0}}, // pcio_, जेन्, ложи_, سرا_,
+ {{0x2d8a3543,0x27fae14f,0x10a69479,0x2bb7e0ba}}, // lobe_, _åpne_, биен, _căci_,
+ {{0xfecfa07c,0xb87b0d64,0x26da3cd1,0x9f406052}}, // _রাজধ, caíd, _kepo_, triä_,
+ {{0xdce3a041,0x2d8a3cd2,0x225fc098,0x6d452005}}, // tinā, nobe_, dzuk_, _xoha,
+ {{0x9f43406f,0x60d8eb30,0x26d95cd3,0x2b920066}}, // vujú_, _revm, _yeso_, pách_,
+ {{0x26da2c35,0x7d165cd4,0x6b55c057,0x629c7944}}, // _lepo_, neys, _pága, _izro,
+ {{0x2d8ca05d,0x60ddc0c4,0xec7a8967,0x37aaf045}}, // _tkde_, ðsma, _спа_, лтон_,
+ {{0x6b8d1cd5,0x25a00143,0x501b4095,0x66045cd6}}, // _skag, _ahil_, נויו, itik,
+ {{0xdb1c804a,0x71790a18,0x6d453cd7,0x6d465cd8}}, // _ztrá, ибор_, _roha, _ooka,
+ {{0xb4c7404b,0x236dc0c2,0xdb0e62f8,0xd10a4077}}, // _उभे_, ciej_, _dibé, ह्मण_,
+ {{0xd467cc4e,0x7d1649a9,0x75245cd9,0x286b0423}}, // _лице_, deys, ddiz, урно_,
+ {{0x61f52098,0x63baa01a,0x96bafcda,0xd85160f9}}, // tuzl, _kutn, _суду_, _bọ́_,
+ {{0x6d9d0004,0xa3e50026,0x2c04807c,0x66045cdb}}, // réab, _पटक_, লিয়ে_, etik,
+ {{0x2121248d,0x3d066c87,0x201eaefb,0x6d4640e8}}, // _wahh_, स्से_, _msti_, _coka,
+ {{0x629c605d,0x661e205d,0x8b04404a,0x7ff70049}}, // [1b30] _azro, _espk, ířat, _اسعا,
+ {{0xdd0e203c,0xdb0f4057,0x60de6037,0x26dd8035}}, // mışd, _micé, gapm, wawo_,
+ {{0xdb0e8698,0x61e41cdc,0x26d95796,0x50b58192}}, // _subá, _zvil, _teso_, ослу,
+ {{0x26c17cdd,0x26defcde,0x6d46c052,0xdb0be105}}, // scho_, hato_, ökal, _zugä,
+ {{0x201ea766,0x3945b5f6,0x6280805d,0x26dd811d}}, // _asti_, _pols_, _cxmo, rawo_,
+ {{0xdb076071,0x69cd4187,0x27e9812d,0x629d4018}}, // _fijá, mpae, šano_, _izso,
+ {{0x6d46411d,0x7c29400a,0xdb08a017,0x2d9f8067}}, // _yoka, _šerh, _didà, _thue_,
+ {{0x9b588f0e,0x22aee03c,0x5894a049,0x47c5c98c}}, // _лист_, dək_, أجهز, обив,
+ {{0x4429447b,0x2fc90286,0x316ddcdf,0x25e5a028}}, // nva_, _ntag_, riez_, _टिकी_,
+ {{0x0609e624,0xf1bf00e0,0x3945a17b,0x6a1480e0}}, // анок_, tván_, _uols_, _عبقر,
+ {{0x7c2d1ce0,0xf8bfc608,0x3946c05f,0xdce9cf9e}}, // _arar, ñés_, _foos_, lieč,
+ {{0x6d5d0049,0x4ca8007c,0x2d8b1ce1,0xdce9c143}}, // ghsa, _ক্ষু, goce_, dneć,
+ {{0xdee5e51b,0x7c2d0005,0x1b23207c,0x2d8a3ce2}}, // _доли, _crar, মানে_, tobe_,
+ {{0xa91dc05c,0x7a0aa03c,0x60db82d9,0x2b946156}}, // kožk, _mətn, _leum, räck_,
+ {{0x44295ce3,0x7c2d1ce4,0xa3aee04b,0x8b26401f}}, // eva_, _erar, _कळत_, одве,
+ {{0x3947ef8f,0x6e2d01e2,0x6f0d0088,0x7d0d0385}}, // _hons_, _frab, _gbac, _gbas,
+ {{0x6ab26077,0x5ea8007c,0x60de6037,0xcb12c557}}, // [1b40] जश्र, _ক্রে, tapm, ולן_,
+ {{0x21a3601b,0x6d477ce5,0x23814013,0x6b584183}}, // диум, _coja, vėje_, _díga,
+ {{0x3205e42c,0x6d465ce6,0x46d02295,0x442001e2}}, // ntly_, _toka, _त्यह, _hsi_,
+ {{0x9f496069,0xa50a8fb5,0x1d0a878e,0x26dee024}}, // stað_, реда_, реди_, zato_,
+ {{0x44200026,0x88846555,0x629c6064,0x6dbc206f}}, // _jsi_, _کیان, _wzro, _očak,
+ {{0x6723f793,0x7c29c31b,0x6d59dce7,0x7d09dce8}}, // _nanj, dver, _alwa, efes,
+ {{0x7d09cf44,0x7c29dce9,0x66036822,0xd7f0a13a}}, // ffes, ever, _опра, ركة_,
+ {{0x49066026,0x3f8fc05d,0x75241cea,0x3946d258}}, // स्रो_, _ckgu_, _naiz, _qoos_,
+ {{0x44200b01,0x6d9885e0,0x2d8b177e,0x3946dceb}}, // _nsi_, níam, voce_, _voos_,
+ {{0x59d7a026,0x3a3aa156,0x8c1b8095,0xc7b98009}}, // णीहर, äpps_, _דומי, _erős_,
+ {{0x2004d657,0xb4ab8069,0x2d788cfd,0x75240265}}, // stmi_, _íþró, jček_, _baiz,
+ {{0xa9c3e0ff,0xc178e03b,0x6dbc2133,0x6b60e1e9}}, // ьськ, snė_, _ečak, _bágù,
+ {{0x6b5cc009,0x98b34041,0x2603e005,0x637ec018}}, // _cége, ādīt_, año_, cīna,
+ {{0x60c441ae,0xdb01a057,0x7053c0eb,0x2d8b004a}}, // ecim, _silú, _تنها, soce_,
+ {{0x80ddc07c,0xcebae0e8,0x9f406049,0x777ae048}}, // _যাত্, _haƙo_, briú_, mntx,
+ {{0x6d48b03c,0x6723fbf9,0x26160010,0x9992a06f}}, // _loda, _zanj, _पैकी_, ždňa_,
+ {{0x3eaf4669,0x6d48bcec,0xdcbb60ff,0x6e2d00b1}}, // [1b50] ägt_, _ooda, аще_, _urab,
+ {{0x3f8ce0f7,0xceb40076,0x7d18b5aa,0x44f5253f}}, // nodu_, _קיץ_, jevs, _эпос,
+ {{0x6d4774ec,0x69c985b2,0x4974271b,0x5595401b}}, // _voja, _stee, _ілюс, _наоѓ,
+ {{0xed59a04a,0x6d48a6f1,0x2006800e,0x7c29c76d}}, // _muže_, _aoda, ftoi_, yver,
+ {{0xeb3a400b,0x656d00ae,0x7c2e60cb,0x33248227}}, // _הערש, _mmah, _erbr, _damx_,
+ {{0x399200e0,0x6e2041de,0x67253ced,0x21248153}}, // lást_, _âmba, _jahj, _eamh_,
+ {{0xa3cf8077,0x7523fcee,0x63bd5cef,0x60dd4106}}, // वीज_, _ranz, _husn, _kesm,
+ {{0x85b98844,0x63bd4827,0xceb26076,0x63bb99ae}}, // _глас_, _kusn, _טים_, _tuun,
+ {{0x61e90274,0xee36c3fc,0x442ee0ea,0x6f09c8c8}}, // šelj, зны_, _arf_, rfec,
+ {{0x29000024,0x6b5cc009,0x3f8cfab0,0x0cb84a2a}}, // _scia_, _rége, godu_, _अल्म,
+ {{0x2277a896,0x6b8d4534,0xc7b46076,0x63b56714}}, // _بلاگ_, koag, ובץ_, _mizn,
+ {{0xe456600b,0x7523fcf0,0x889aa095,0x6376006e}}, // פירט_, _wanz, _הבעי, kšne,
+ {{0x6d5b8022,0x395fdcf1,0x659ac0be,0x39490057}}, // _mlua, mhus_, _פינק, _doas_,
+ {{0xc104e13a,0x88bce04a,0xdb1ac017,0x9f4304d2}}, // _يوني, nděl, _autè, čká_,
+ {{0x7ae1fcf2,0xd24ee1ac,0x29191cf3,0x63bd4105}}, // malt, _منو_, besa_, _ausn,
+ {{0x27e6dcf4,0x442fc4b4,0xf8b98067,0x317f8490}}, // _yvon_, _krg_, _ngũ_, _njuz_,
+ {{0x5577000b,0x63a41cf5,0x26d240ae,0x6b965cf6}}, // [1b60] _מעקן_, _ihin, rbyo_, rlyg,
+ {{0x6d989cf7,0x60dd410a,0x629600a2,0x6d5b9cf8}}, // ríam, _eesm, _iyyo, _alua,
+ {{0x98a24013,0xf53f0488,0x14ca8052,0x636060c4}}, // _tokį_, kmål_, были_, _löng,
+ {{0x68e1fcf9,0x60c56515,0x37bde07c,0x9f4221ce}}, // hald, achm, _আবার, dské_,
+ {{0xf3f12067,0xba764233,0x0cdbe046,0x2007bcfa}}, // ợc_, _راحت, _मजूम, etni_,
+ {{0x68e1fcfb,0x7ae1e052,0x61e2407f,0xe6d90077}}, // jald, jalt, šols, _भज्ज,
+ {{0x68e1fcfc,0x8af005fc,0x03a37cc3,0x7c2e6012}}, // dald, _aləm, _чиро, _urbr,
+ {{0x44224227,0x7ae1e090,0x63bd4058,0xceb321a9}}, // _ksk_, ealt, _yusn, עיה_,
+ {{0x0566bcfd,0x26ddc037,0x67264920,0x93bca12a}}, // _еван, _dewo_, _makj, _arăt,
+ {{0x394a62b7,0x291a7cfe,0x68e280ef,0xdb1d61ae}}, // _jobs_, nepa_, laod, _ausê,
+ {{0xf2dbc07c,0xdeefc12a,0x63a2cdb2,0x27e68547}}, // _ধারণ, _пы_, _thon, lson_,
+ {{0x7afd0018,0x8ccce447,0x2d8cedee,0x636060c4}}, // ugst, _द्रो, sode_, _hönd,
+ {{0x6283e2a8,0xc7c70de7,0x46a3ce95,0x7d02dcff}}, // _exno, ясни, _зацв, _icos,
+ {{0xed59a3ee,0x4a53c0ff,0x1874e0ff,0x4421200e}}, // _tuže_, _якіс, _огля, _psh_,
+ {{0x27e680a2,0x3171217a,0x80dc007c,0x44224095}}, // hson_, rizz_, _ভার্, _ask_,
+ {{0xa91dd62f,0x6d4ae77c,0x656e79ff,0x6c7be076}}, // toži, _hofa, _ambh, _לאופ,
+ {{0x25a9491f,0x3f8eb665,0x6725205d,0x7ae28114}}, // [1b70] rmal_, kofu_, _tahj, daot,
+ {{0xa91ddd00,0x2d9906f1,0x69cd00ce,0x7bcd1b95}}, // roži, llse_, _itae, _itau,
+ {{0x29c1e016,0x63b56055,0x63a9dd01,0x60c57d02}}, // _xóa_, _vizn, ymen, rchm,
+ {{0x7ae1f035,0xdb066b41,0x0dba20e0,0x27e69bb0}}, // zalt, _zukü, _گئیں_, fson_,
+ {{0x443fc17a,0x63607137,0x27e69d03,0x7b026d6e}}, // xxu_, _bönd, gson_, äytö,
+ {{0xe736ad3e,0x7526435f,0xa3d42843,0x61faf1c7}}, // деш_, _zakz, सीक_, kutl,
+ {{0xb8e5a1b0,0x6e22cbb1,0x6726417b,0x63607d04}}, // _एल_, _asob, _yakj, _dönd,
+ {{0xe7ee04da,0xd4d9c139,0x0dc9c662,0x7aed183d}}, // _जिला_, цькі_, олий_, _ndat,
+ {{0x67277d05,0x7527635d,0x2b4b417b,0x80e160c2}}, // _kajj, _kajz, _mocc_, _फ़ाइ,
+ {{0x61e98a39,0x67277d06,0x21294058,0xbebce041}}, // _ivel, _jajj, hdah_, ldīj,
+ {{0x61fafd07,0xdbf2204a,0x7c2d5d08,0x6e22cee2}}, // gutl, třít, hvar, _esob,
+ {{0x6560c420,0x3255d342,0xed57014f,0x3a29c012}}, // chmh, _овер, дою_, _šapu_,
+ {{0xb8d6004b,0xdb1c8011,0x6d4ae7c1,0x3cfe6290}}, // _जण_, _durè, _fofa, _लाले_,
+ {{0x68ed1d09,0x6d5d5d0a,0x7d1bdd0b,0x7aed1a79}}, // _edad, _alsa, leus, _edat,
+ {{0x2bdd8b27,0x6b4fa488,0x3d0e9d0c,0x9f4de071}}, // नीता, _høgs, ण्डे_, nreí_,
+ {{0x7d1bdd0d,0x63a41d0e,0x9f584017,0x2127e067}}, // neus, _uhin, ltrú_, _hanh_,
+ {{0x91e344c4,0xe3a70555,0x0d862425,0x13d6a07c}}, // [1b80] коте, _تشوی, _олин, সওয়,
+ {{0xef176052,0x995c8066,0xda666049,0x6609d0a9}}, // мму_, díš_, واضي, itek,
+ {{0x2127e067,0x442366de,0x63be2143,0x61ed800a}}, // _manh_, _csj_, _uupn, šall,
+ {{0x6d46049f,0xdcfb8143,0xa3c1b3e7,0x2127e067}}, // njka, _fjuč, ंदन_, _lanh_,
+ {{0x8b666555,0x3f8ea475,0x5edee07c,0x9abca17a}}, // _قاسم, tofu_, _মাসে, _faċl,
+ {{0x61fbd2f0,0x25bf8524,0xf1bf44d2,0x2ee00584}}, // duul, _duul_, _práv_, _leif_,
+ {{0x60c99d0f,0x44204d14,0x55772053,0x6609dd10}}, // _ffem, lwi_, ועסן_, etek,
+ {{0x6b54a5df,0xf70ee119,0xbcfb4187,0x64a682af}}, // _màgi, _củng_, _gléd, _жаба,
+ {{0xb0cfa010,0x6d4ae320,0x9f4542f4,0x28cfa1b0}}, // _स्वग, _pofa, élé_, _स्वि,
+ {{0x2900405d,0x9986e133,0x4cd8a07c,0xbcfb0049}}, // hgia_, _broš_, _ঠাকু, dhéa,
+ {{0x68e08c12,0x9c7ca504,0x6e2413d0,0x6b502156}}, // _kemd, _beče, _asib, _lägs,
+ {{0x9c7ce274,0x6d4bca77,0x6d5e3d11,0x3946913e}}, // gača, _goga, _alpa, njos_,
+ {{0x7d1ae98d,0x21295d12,0xed5f806f,0xa36760f9}}, // pets, vdah_, úžia_, _bọ̀r,
+ {{0x92bf007c,0xd04f45fc,0x6b4fa488,0xe1f1a0e0}}, // ইরে_, _gecə, _høgr, _جسے_,
+ {{0x442487d3,0x394b45df,0xa2cb2969,0xad9b2049}}, // _lsm_, _pocs_, तुष्, ntúi,
+ {{0xed59ab23,0x67277d13,0x6728bd14,0x25b86090}}, // _muža_, _sajj, _ladj, _airl_,
+ {{0x672774f8,0x7bc09d15,0x5ee3607c,0x21294a94}}, // [1b90] _pajj, _numu, _পাবে, rdah_,
+ {{0x7d1c21af,0x2127e081,0x25094896,0x7d0d5d16}}, // cers, _xanh_, _کردی_, sfas,
+ {{0x68e45d17,0x61e99d18,0x394b40b8,0x6d58a584}}, // baid, _svel, _tocs_, dkva,
+ {{0xdb1d6e41,0x2b0ee0aa,0xbcfb4e1c,0x442ddd19}}, // _musé, त्तु_, _emér, yve_,
+ {{0x06e2207c,0x27e99b06,0x395ea0ca,0x77a5c005}}, // _বাতি, éan_, _blts_, nóxe,
+ {{0x69c09d1a,0x6b82caf8,0x7982c0f9,0x44237d1b}}, // _dume, _ajog, _ajow, _tsj_,
+ {{0xe611a6b0,0x69dc61cd,0x61fd1d1c,0x3eb3a156}}, // اشت_, _gwre, husl, äxt_,
+ {{0x69348307,0xe9d9d0f6,0x56b82095,0x68e57d1d}}, // _инсу, пки_, ופון_, lahd,
+ {{0x7529c0e0,0x96caa216,0xdb0e60e0,0xb87b013a}}, // rdez, _स्कॉ, _hibá, daío,
+ {{0x6b55cf3c,0x6f04149a,0x6728bd1e,0x1309e5be}}, // _mági, _scic, _gadj, чной_,
+ {{0xa3e6420b,0xf1b982e2,0xb5fb00e0,0x99520066}}, // _यौन_, _kiš_, szál, ráž_,
+ {{0x7bc087d2,0x7d1c3d1f,0x6d4d0005,0x6f052090}}, // _yumu, wers, _coaa, _achc,
+ {{0x7c252920,0x7bc1bd20,0x65752037,0x27e94156}}, // _ashr, _hulu, fizh, jsan_,
+ {{0x27e95d21,0x69c1bd22,0x44320aaa,0xd138e0c2}}, // dsan_, _kule, _vry_, gląd_,
+ {{0x6f1d1385,0x69cf4041,0xa879e00b,0x399900e0}}, // besc, _atce, _מאַר, zési_,
+ {{0x44205d23,0x7c241d24,0x2fc01518,0x44320095}}, // twi_, _tsir, _tuig_, _try_,
+ {{0xe1fa5c2e,0x291ca03c,0x24f668e9,0x6b8403c9}}, // [1ba0] яга_, yeva_, ечер, _ijig,
+ {{0x673b8011,0x23814013,0x78a401fb,0x31720089}}, // _anuj, bėjo_, _iziv, _hmyz_,
+ {{0x68e086ae,0x6b840098,0xfce642d8,0x2129058b}}, // _pemd, _kjig, _поно, _yaah_,
+ {{0x5de681e1,0xed59a381,0x63ad5d25,0x06868407}}, // ежда, _ruža_, cman, нген,
+ {{0x7ae1bd26,0x69c1ba79,0x65628ee4,0x27e94025}}, // _belt, _aule, rhoh, csan_,
+ {{0xd943a0bc,0x680aa03c,0x9c7ce530,0x0877400b}}, // лечи, _hədi, dačn, _װעלט_,
+ {{0x7bce6277,0xb4dc6ef3,0x2d805d27,0x7ae09d28}}, // _utbu, ठरी_, onie_, _temt,
+ {{0x6b9bc069,0x4425ae25,0x68e1a046,0x5694cd2b}}, // glug, _dsl_, _eeld, кайт,
+ {{0xd90dc6b0,0x69c1bd29,0x3207c1d3,0xcebae0a2}}, // تین_, _eule, нхэн_, _haƙi_,
+ {{0x44447d2a,0x3f85220d,0x61fd0466,0x69c1bd2b}}, // ox_, čluk_, vusl, _fule,
+ {{0x98a4a046,0x63b8e048,0x2498e065,0xdbdbe071}}, // _ओरिए, _vivn, ærm_, _báñe,
+ {{0xdb01a86d,0x7ae1bd2c,0x6298e326,0x2d804064}}, // _filó, _zelt, _vyvo, jnie_,
+ {{0x2bb2e12f,0x9f478050,0x656457bf,0x69de20f9}}, // ीददा, éné_, dhih, _awpe,
+ {{0x6363410a,0x48ed0295,0x21290939,0xd5b8204e}}, // _mõne, _आएको_, _waah_, есс_,
+ {{0x2258204a,0x3da74af5,0xb90701a6,0x61eabd2d}}, // _čeká_, ераб, _পা_, nsfl,
+ {{0x2d87606e,0x61fd0493,0x2905217a,0x2d9242e0}}, // čnem_, pusl, żla_, goye_,
+ {{0x7f98807b,0x27e95d2e,0x2418402d,0x9f5a83bb}}, // [1bb0] níqu, usan_, ноты_, cupé_,
+ {{0x68e2dd2f,0x7c2f8057,0x63bab7d7,0x9c7ce066}}, // _leod, _ácri, _nitn, načo,
+ {{0xdbd74052,0x2d8040c2,0x26c94098,0x78a40143}}, // _säät, bnie_, pcao_, _zziv,
+ {{0x45d5895a,0x69c1bd30,0x63a8a320,0x7ae73d31}}, // водс, _rule, _ahdn, lajt,
+ {{0x291d8068,0x9c7ce066,0x7ae1a052,0xdd91668c}}, // rewa_, xačn, _pelt, _نوح_,
+ {{0x660d5d32,0x6d5985e3,0x39494cdb,0x672d410a}}, // mtak, skwa, ljas_, ldaj,
+ {{0x6498a1e1,0x7ae2dd33,0xd7f861e3,0x3a26c0f9}}, // нтър_, _beot, _пут_, _asop_,
+ {{0x9c7cfd34,0x2019407d,0x7ae73d35,0x7777617a}}, // tačn, _kpsi_, hajt, lixx,
+ {{0x7ed5e0ff,0xe7848520,0x44232223,0x9f4d006f}}, // кінч, _суро, hwj_, budú_,
+ {{0xdb1c85f0,0xa2d98219,0x23600098,0x6b96141b}}, // _strø, मरक्, _slij_, _skyg,
+ {{0x660d5d36,0x316007a0,0x61e9dd37,0xed59a04d}}, // htak, _pliz_, ssel, _hužo_,
+ {{0x25646009,0x6aba6d70,0x63bb80ce,0x27ed8488}}, // _zöld_, _útfe, _jiun, _kven_,
+ {{0x2b4942d1,0x64444057,0x8477c031,0x4426c15e}}, // djac_, xxii, _fáwẹ, _gso_,
+ {{0x2a695d38,0x2d805d39,0x29050009,0x61ed162d}}, // dzab_, wnie_, őlap_, _dval,
+ {{0x660d4e3b,0x3ebf807d,0x4909a026,0xf796e949}}, // etak, _ogut_, _सानो_, _جنوب_,
+ {{0xc5f2a00b,0x6619c17b,0x93fae1a9,0xa2c31ac4}}, // ידל_, _kpwk, _חלקי, रखण्,
+ {{0x212b5118,0xcc8a0050,0xaa461d3a,0x27ffce38}}, // [1bc0] _dach_, _زنده_, _репл, nuun_,
+ {{0xd36f213a,0x2ee3600a,0x399ac00e,0x4427e05d}}, // _وهي_, _cejf_, rësi_, _ksn_,
+ {{0x99802432,0x62898057,0x6faa41e3,0x63bb8090}}, // _križ_, _axeo, _овог_, _biun,
+ {{0x7ae410cd,0x69c41d3b,0xd910401c,0x20274052}}, // _jeit, _kuie, _دیر_, äviä_,
+ {{0x68e405a5,0x44446227,0x2b9c8066,0x63baaebe}}, // _meid, sx_, níci_, _pitn,
+ {{0x399900e0,0xae1cc5cb,0x6b55c581,0x6d4f5d3c}}, // lést_, _बैगन_, _mágu, _zoca,
+ {{0x63bb94ee,0xab646121,0x5f0681c7,0x317b2095}}, // _fiun, rlüğ, _изва, ורסמ,
+ {{0x63a99d3d,0x6da68977,0x2d542046,0xc05ac14f}}, // _ghen, _риба, _käes_, діо_,
+ {{0x2492a064,0x68e3fd3e,0x6d5c20c4,0x15ee0028}}, // łym_, _bend, kkra, _जिगर_,
+ {{0xdbd9a01f,0x6d5c34ec,0x2385c041,0x6f16004a}}, // _açõe, jkra, rēja_, _abyc,
+ {{0x2bbbc38e,0x3a26c0ca,0x217901ba,0xdd8fa343}}, // _ऊंचा, _wsop_, ейны_, _بول_,
+ {{0xe1ff04cd,0xbf13c0e0,0x63a9800e,0x9257e052}}, // tuó_, _نومب, _xhen, тают_,
+ {{0x69c3e143,0x7e69c0c2,0x2287012a,0x27ed046b}}, // _eune, czep, тунг, éen_,
+ {{0x040da067,0xceb38557,0x3f821d3f,0x48fe635b}}, // _nướn, ייר_, anku_, _लागो_,
+ {{0xf1a71342,0x69c3fd40,0xa18420db,0xdb1c9d41}}, // _ирин, _gune, рыял, _aurí,
+ {{0x2ee37d42,0x8ab7e095,0x24890048,0xe0b7e00b}}, // _sejf_, _בלוג_, _txam_, _בלוט_,
+ {{0x6600c058,0x63a980fd,0x69c416e9,0x7bc3e35f}}, // [1bd0] numk, _rhen, _guie, _zunu,
+ {{0x3a291441,0x636600b6,0x212cbd43,0x80dd207c}}, // _isap_, _póng, _nadh_, _বাক্,
+ {{0x35f5634f,0x777760f7,0x9abca17a,0x4424c223}}, // упер, rixx, _faċi, hwm_,
+ {{0x4ae30afa,0xfe7040e0,0x6600dd44,0xdb18e187}}, // परिव, حدہ_, kumk, _divé,
+ {{0xa3e5bd45,0x2489801f,0x201940c7,0x672d1d46}}, // बीन_, çam_, _upsi_, _maaj,
+ {{0x68e8eb33,0x2409a7f8,0x9c7ca361,0x212ca153}}, // jadd, енки_, _seča, _cadh_,
+ {{0x68e8e336,0x63bd4e1e,0x6d5bdd47,0xdb0fe62e}}, // dadd, _kisn, tkua, _àdép,
+ {{0x2d833d48,0xe1f9c013,0xa925c9b1,0x2249a09f}}, // hnje_, trų_, удил, ćak_,
+ {{0xd9e35d49,0x68e8fd4a,0x6600dd4b,0x7c28a0ca}}, // गठित_, fadd, gumk, _esdr,
+ {{0x69c4e03a,0x6d5bd3a0,0x399900e0,0x320dc054}}, // _čier, skua, zést_, rtey_,
+ {{0x7bc53d4c,0xc4d2a095,0x7dc7a121,0x9f4224d2}}, // _nuhu, _הגב_, _kısm, jská_,
+ {{0xf99f00d1,0x7ae3fd4d,0xd435c33d,0xdb03fd4e}}, // glè_, _went, _معتب, _pinó,
+ {{0xe8e02067,0x7d04419f,0x3999004a,0x4424c0fd}}, // _liễu_, vgis, vést_, cwm_,
+ {{0x98a04098,0x6e20441a,0x2d820098,0x2001648d}}, // ldić_, _âmbi, snke_, kuhi_,
+ {{0xd00ee68c,0xa3dfdd4f,0xdb01e262,0xdb1aa011}}, // _فلو_, धीर_, rmlä, _ditè,
+ {{0x68e9c88e,0xa09ae053,0x040da067,0xc879c12a}}, // naed, _איצט, _sướn, eaşi_,
+ {{0x7a0aa03c,0x7c2999d7,0xd46748f4,0x39401d50}}, // [1be0] _hətt, _nser, гите_, _inis_,
+ {{0xdd998061,0x7ae53d51,0x20021d52,0xdb1d6395}}, // _akň_, _geht, muki_, _fusí,
+ {{0x29c9dd53,0x672d0022,0x9bb72095,0x69c5200f}}, // _lúa_, _yaaj, _תהיה_, _guhe,
+ {{0x614653b0,0x60cd5d54,0x7c298133,0x2fd7663b}}, // _сема, ccam, _bser, اوند_,
+ {{0x040da016,0x7bc53d55,0x6721fd56,0x06e8607c}}, // _tướn, _zuhu, kelj, _পাঠি,
+ {{0x6458a03b,0xe8d90125,0xed598579,0xaca40096}}, // tyvi, _ijụ_, _riže_, _nkọw,
+ {{0xb90a8405,0xd5bacd3e,0xa3b5279e,0x6562c320}}, // _মা_, нси_, जगत_, _ploh,
+ {{0x7ae9c0e2,0x3d09ae4e,0xad9b203a,0x4154c242}}, // gaet, _साथे_, ntúr, рвис,
+ {{0x6721e35d,0x21678471,0x7ae65d57,0xa3dfc010}}, // felj, _сити_, _mekt, धील_,
+ {{0x6fdd8877,0x69c65d58,0x25a04f2c,0x60cd4e10}}, // _नौटं, _muke, hlil_, zcam,
+ {{0x7764055f,0x0d976095,0x44291d59,0x2d8327a0}}, // _blix, רכים_, _ssa_, vnje_,
+ {{0x403501ba,0x29068265,0x2b14666f,0x48a7e3fc}}, // аемс, ngoa_, न्तु_, ытым_,
+ {{0xd378e88c,0xf593c13a,0x9c57c050,0x63bd59dc}}, // pić_, _النج, _مجوز_, _risn,
+ {{0x6601e5cf,0x6721e143,0x6b584005,0xc69380be}}, // bulk, celj, _mígu, מאר_,
+ {{0x7bc65d5a,0x6b584057,0x63bd5d5b,0x6601fd5c}}, // _auku, _lígu, _pisn, culk,
+ {{0xc7c8c016,0xaca48088,0x2d9a2291,0x2918662f}}, // _gốc_, _asọp, _ekpe_, _abra_,
+ {{0xb606629f,0x3a2908c4,0x60c2c9fa,0x44387406}}, // [1bf0] _lišć, _usap_, _mgom, _arr_,
+ {{0x20016eb0,0x9c7ca055,0x81e8007c,0x7bc53d5d}}, // tuhi_, _rečn, মূহ_, _tuhu,
+ {{0x7a0aa5fc,0xab5da227,0x2b146a2a,0xc879c0ba}}, // _xətt, _bużn, न्धु_, raşi_,
+ {{0x657afd5e,0x799aad87,0x9635834f,0x09dac1a6}}, // hith, _aktw, инец, _দিনা,
+ {{0x3f891d5f,0x05db40c2,0xa509aa92,0x69c6400f}}, // _mjau_, मीखब, вела_, _guke,
+ {{0xa3c1a029,0x23694037,0xee3880e0,0x752e60a2}}, // ंदल_, lhaj_, اقوں_, _gabz,
+ {{0x7ae9c1cd,0xf8b52028,0x20112265,0xcf582095}}, // taet, ंधिय, ntzi_, רבות_,
+ {{0x986a6315,0x27fc606f,0x672e6361,0x29c9c019}}, // lığa_, ávne_, _zabj, _púa_,
+ {{0x7aeb8b71,0x6fb9a425,0x3eb9c3f5,0x68eb8644}}, // magt, _агар_, äst_, magd,
+ {{0x7aeb9d60,0x3f84cd3d,0xb8fcd7f2,0x20032026}}, // lagt, anmu_, _त्_, kuji_,
+ {{0x63ad00ef,0x44395d61,0x7ae77d62,0x8d76001e}}, // _bhan, _lrs_, _mejt, لاحا,
+ {{0x7aeb9d63,0x68eb88c4,0x7c3b417a,0x27e0812a}}, // nagt, nagd, _ġurn, ţine_,
+ {{0x7db4c0d0,0x8d5ac0be,0x26d16200,0xb4d1c010}}, // _اصلا, _שכני, _ofzo_, वडी_,
+ {{0xdee321fc,0x60c2c0ba,0xbdfd41a6,0x98a240c2}}, // жори, _zgom, ুবাদ_, _jaką_,
+ {{0x0f7ae087,0xe29a2a92,0xe7e12021,0x63be3d64}}, // _ברסל, тав_, गीला_, _vipn,
+ {{0xbdfaa1a6,0x75d1a018,0x69c6527b,0xdb1d6733}}, // _আহমদ_, _gāze, _puke, _ausà,
+
+ {{0x7523bd65,0x8d76233e,0x212fd93d,0x442a6fcf}}, // [1c00] kenz, _نادا, _magh_, _ssb_,
+ {{0x7bc6583b,0x798980f9,0xdb1d44b7,0x69c77d66}}, // _vuku, _ajew, _lisè, _buje,
+ {{0x6b5842ee,0xd02660fb,0x6da6425d,0xaec696f4}}, // _sígu, рмей, _вина, абел,
+ {{0x2d982ea3,0xd91086b0,0xd7fa803b,0x26076046}}, // lore_, ثیر_, вук_, _हमरी_,
+ {{0x765ae064,0xead4a772,0x75228381,0x44395d67}}, // zyty, боль, reoz, _grs_,
+ {{0x4438606e,0xb4dff1c9,0x3f983d68,0x99892089}}, // _trr_, तरो_, noru_, _trať_,
+ {{0xb4d1c04b,0x394ddd69,0x22476d4b,0xca48a050}}, // वडू_, fjes_, änkt_, العه_,
+ {{0x75244019,0x7414c13a,0xb4c2dd6a,0xe81e2028}}, // neiz, موبا, ्डे_, _पहना_,
+ {{0x69c76098,0x661e217b,0x8afee35f,0xa5bb61e9}}, // _zuje, _lppk, _alƙa, _aróy,
+ {{0xab5ca041,0x60c41d6b,0xdb156009,0xaaaa6028}}, // _biļe, _agim, _kizá, _कराक,
+ {{0x3f8689fe,0x656aa351,0x7c2bc683,0x61e400e8}}, // onou_, mhfh, _esgr, _awil,
+ {{0x645ae407,0x66044155,0x6db2e0c2,0x61e40030}}, // ryti, kuik, _płas, _bwil,
+ {{0x9f47603a,0x69c09d6c,0x752f4064,0x26086021}}, // čnú_, _mime, _racz, ांडी_,
+ {{0x6d4d4200,0x7f8805fc,0xdcf8a013,0x61e4018c}}, // wjaa, lıql, rovė, _dwil,
+ {{0x9c7ce2e2,0xd7c92050,0x26d166be,0x442ca05d}}, // kači, اوره_, _rfzo_, _jsd_,
+ {{0x7c2bc0e2,0x661e2058,0xd6d84739,0x63ad0445}}, // _ysgr, _dppk, иту_, _uhan,
+ {{0xed59829f,0x68e76361,0x6723a035,0x6603b1e1}}, // [1c10] _niža_, _pejd, yenj, zunk,
+ {{0x2366c0dd,0x2418420e,0xdb09c049,0x443a214f}}, // _lloj_, рофы_, imeá, _frp_,
+ {{0x7bc8a050,0x2fc7e016,0x439541d3,0x2d76a0c2}}, // _nudu, _xung_, _ганс, ałek_,
+ {{0x6d9d0057,0x9f584197,0x27fe202e,0x3384814a}}, // méas, strò_, átne_, оучв,
+ {{0x68eb88c4,0x68e8b7eb,0x7aeb8b13,0x661d5d6d}}, // ragd, _bedd, ragt, _upsk,
+ {{0x69c77d6e,0xed59a299,0x7dd3613e,0x68eb80ae}}, // _tuje, _kuži_, _aħse, sagd,
+ {{0xdb1e3d6f,0x61fe60e4,0x68e8a1cd,0x25646105}}, // _sipë, crpl, _dedd, _köln_,
+ {{0xed59a05c,0x2d983d70,0x2004c65f,0x248d0064}}, // _muži_, zore_, fumi_, żemy_,
+ {{0x2004caf1,0x660567d3,0x7d1b8961,0x6603bd71}}, // gumi_, luhk, _ibus, sunk,
+ {{0x386dc105,0x68ed4054,0x7bc8a054,0x7aed57dc}}, // rzer_, haad, _fudu, haat,
+ {{0x657d0bc1,0x66056345,0xe6a568c6,0x69d72286}}, // fish, nuhk, _करोड़, _ntxe,
+ {{0x442d808b,0x394dc00e,0xd05c203c,0x3a3a2071}}, // _kse_, qjes_, carə, _rrpp_,
+ {{0x44295d72,0x7d09dd73,0x2d9dc088,0xe3c8c067}}, // jwa_, mges, _ikwe_, _tựa_,
+ {{0x7d09d59f,0x98a0886f,0x69dbc333,0x7c29dd74}}, // lges, žić_, mpue, mwer,
+ {{0x7aed5d75,0x7bc8a025,0xf1b984e5,0x26e701a6}}, // faat, _xudu, ेदान, _কারো_,
+ {{0xd113fd76,0xed59a29f,0xad9b203a,0x63760edc}}, // त्रण_, _duži_, stúp, kšni,
+ {{0x7bc09997,0x26c5a098,0x63660057,0x9f406049}}, // [1c20] _rimu, _oglo_, _góna, msiú_,
+ {{0x7d1b8ea3,0x44201d77,0x6b5cc009,0x75260108}}, // _abus, _ipi_, _végi, lekz,
+ {{0x69c099ae,0x7c29c669,0xb9c106df,0x3f86805c}}, // _pime, hwer, _üçün, tnou_,
+ {{0x6e3b9d78,0xdb1e20d1,0x7bdc3d79,0x8fc5210c}}, // _brub, _sipè, mpru, _گزین,
+ {{0x3f869d7a,0x68f8654f,0x7c29c200,0x2005fd7b}}, // rnou_, _نکاح_, jwer, huli_,
+ {{0x290a2082,0x63a2849f,0x3f9dc079,0x9c7ce552}}, // ngba_, slon, _akwu_, sači,
+ {{0xdcf52041,0x2366c223,0x38c8e01c,0x1b7b600b}}, // nizā, _ploj_, یادی_, _עטלע,
+ {{0x442d9d7c,0xdd90c25b,0x4fd4cfb9,0x7d09dd7d}}, // _fse_, بوب_, ожит, gges,
+ {{0x27f2504e,0x7bc980fa,0x9c7ca143,0x442d8425}}, // nsyn_, _gueu, _kečk, _gse_,
+ {{0x3d1a235b,0x657d116f,0x69c8bd7e,0x2d9dc291}}, // म्मे_, wish, _tude, _ekwe_,
+ {{0xeb9f2042,0x69dc2e9d,0xa3be220b,0x2d991d7f}}, // ktør_, jpre, ेदन_, tose_,
+ {{0x68ed4054,0x65629d80,0xe572c50f,0x99670094}}, // xaad, nkoh, _عطا_, стол,
+ {{0xdbe2e057,0x225ac566,0x68ed4046,0x29183d81}}, // _véñe, _حجاب_, vaad, ffra_,
+ {{0x2d991d82,0xb87b03f6,0x995c80a3,0x25efa04b}}, // sose_, daít, jíž_, ींची_,
+ {{0xdce98e01,0x200040ba,0x68ed5d83,0x2cd64079}}, // _umeć, erii_, taad, _ọhụ_,
+ {{0x9f49613a,0x6e2d035f,0x6d9d0049,0x2b1a2026}}, // rsaí_, _tsab, réas, म्बु_,
+ {{0x442ee037,0x31352471,0x9f5f0326,0x5d557156}}, // [1c30] _hsf_, _депр, nutí_, окат,
+ {{0xb8826326,0x317ef016,0x7bdd111b,0x6f1b8024}}, // čína, nitz_, mpsu, _sbuc,
+ {{0x2f1941ba,0x59d400e0,0x442efd84,0x61e641f6}}, // _конь_, _وغیر, _jsf_, _ewkl,
+ {{0x66056d3d,0x6f1c6143,0x02b52077,0x68ed4025}}, // ruhk, _ebrc, ंधीन, qaad,
+ {{0x06e8607c,0x31690552,0xb4e6c118,0xe1ff00c2}}, // _পারি, _mlaz_, _बड़े_, rzów_,
+ {{0x7645e0c4,0xabd5b512,0xe1ff00c2,0x44213d85}}, // _áhyg, оциј, szów_, _kph_,
+ {{0x68e995c7,0x7aef02be,0x657aa090,0x1b49d741}}, // _teed, kact, _dmth, изки_,
+ {{0x7bc1a0eb,0xf1cfe026,0x160ba0c2,0x7c3b800e}}, // _wilu, _संबन, संबर_, _trur,
+ {{0x69c1b562,0x443ce7c8,0x69cafbaa,0x9f605d86}}, // _tile, _arv_, _aufe, érés_,
+ {{0x27ffdd87,0xe619e45c,0x69c1a256,0x75d3e12a}}, // rrun_, адо_, _uile, _răzb,
+ {{0x69261d88,0xe12602ab,0x7c29c547,0x291ce143}}, // омна, омни, swer, _dbva_,
+ {{0xeb9a0758,0x657e6174,0x7bdbdd89,0x2bd0608d}}, // рим_, ziph, ppuu, _तूफा,
+ {{0x317ee10f,0x2005fd8a,0xa3e5ac87,0x752726a6}}, // bitz_, suli_, बीर_, fejz,
+ {{0x6b5cc004,0x7aebcaaa,0x256ba271,0x2005fcce}}, // _dégu, _hegt, _føle_, puli_,
+ {{0x7aee2025,0x7bc3e08b,0x3e4ee03c,0x6b84a049}}, // rabt, _iinu, yəti_, éigi,
+ {{0x7989dd8b,0xb4e88028,0x64432381,0xdce40df9}}, // nnew, _मजे_, _šnic, _klič,
+ {{0x44213d8c,0xe57a6d24,0x25646277,0x64436277}}, // [1c40] _eph_, аза_, _föll_, ånin,
+ {{0xbcfb00e0,0x85062050,0x27f24654,0x63b6502d}}, // nkén, _بودن, rsyn_, smyn,
+ {{0x4f9680ba,0x656e2049,0xf1c9e067,0x2d9a7d8d}}, // орду, mhbh, _mạo_, rope_,
+ {{0x2007bd8e,0x657e6174,0x6b89dd8f,0x9f5cc04a}}, // duni_, siph, jneg, prvé_,
+ {{0xbddb4355,0x6e296361,0x7f4640c1,0x2d9a6022}}, // _crèd, _ćebe, _inkq, pope_,
+ {{0x6234aa6e,0xc0aa650f,0x636b38ee,0x291ddd90}}, // зеку, _نازل_, _tüne, _abwa_,
+ {{0xdbd1c1ae,0xa0a37cc3,0x69d9d4b6,0xd90da0eb}}, // qüên, _жард, _atwe, ایم_,
+ {{0x7aeaea95,0x3f9ea88e,0xdb064052,0x6b9bc143}}, // _seft, _wktu_, _eikö, goug,
+ {{0x6616403b,0x69cafd91,0x7bcae9c8,0x274ae192}}, // ntyk, _sufe, _sufu, ачно_,
+ {{0xdc3ea066,0x2918a569,0x02e30026,0x2613a1ae}}, // líče, _ùra_, पर्न, bão_,
+ {{0x39587d92,0x6d573d93,0x69d9c614,0x29d7617a}}, // _mors_, _roxa, _etwe, _għad_,
+ {{0xed59a2e2,0x443eb4b3,0x44212058,0x236ddd94}}, // _dužu_, _irt_, _rph_, chej_,
+ {{0x6d572005,0x799afc7f,0x69c3eea3,0x660281e7}}, // _poxa, potw, _fine, nrok,
+ {{0xf1c9e067,0x5334eae2,0x443ebd95,0x59ca6292}}, // _gạo_, _хект, _krt_, िदार,
+ {{0x95cb30da,0x163429fd,0x9e3421fc,0x63bb6425}}, // руга_, меря, мерч, _èuni,
+ {{0xdb1c6359,0x3201605c,0x7bcbd33d,0x2002000a}}, // _dirí, vrhy_, _yugu, brki_,
+ {{0x20040017,0x2d51c0e6,0x6b9d0057,0x799c20b1}}, // [1c50] àmit_, _páez_, mosg, borw,
+ {{0x68ed1d96,0x4422405d,0x628be29c,0x8aa760e7}}, // _head, _dpk_, šloš, оред,
+ {{0x2d5c403e,0x69cee06f,0x9ad88125,0xb4d88291}}, // _cíes_, íbeh, _ịrụg, _ịzụt,
+ {{0x29d08031,0x2613a1de,0xab5d82a4,0x2d5c4733}}, // _bàa_, vão_, _biża, _díes_,
+ {{0x8af04497,0x2d9ca2e2,0xa2a24893,0x8d742050}}, // rkət, fove_, _कुप्, _رایا,
+ {{0x68ed1d97,0x21295d98,0x7bcbd743,0x20094037}}, // _lead, leah_, _rugu, muai_,
+ {{0x7989dd99,0x7bcbc046,0x6e3b4098,0x7ae28024}}, // tnew, _sugu, _šubh, bbot,
+ {{0x2b46c28f,0x7bc3fd9a,0xab5da17a,0x3f8b0143}}, // _cnoc_, _sinu, _mużi, jncu_,
+ {{0x2d9ca29c,0x6b9bc42c,0x6ed8a295,0x656f000c}}, // bove_, roug, नुहु, dhch,
+ {{0x2d8b1d9b,0x2d9d9d9c,0x7bdb8037,0x7c22c299}}, // ence_, lowe_, _ituu, _dpor,
+ {{0x6aa4013b,0x7c22ca43,0x6b89d542,0x31ae8066}}, // _syif, _epor, pneg, lýza_,
+ {{0x50cb827d,0x75357c47,0x7bcbdd9d,0x27e91d9e}}, // िशिष, _lazz, _tugu, _bwan_,
+ {{0x7bcd00b6,0x6728fd9f,0x656e228f,0xed599da0}}, // _cuau, bedj, thbh, _mož_,
+ {{0xd00ec13a,0x3ced8022,0x7bc52932,0x7529dd90}}, // _صلى_, _leev_, _cihu, leez,
+ {{0xf2d2e00b,0x27e904ec,0xdb1d413a,0x32433d85}}, // _טעג_, _ewan_, _eisí, нерг,
+ {{0x6b9c33e0,0x290ddda1,0x88ca44d4,0x3ced8022}}, // sorg, ngea_, слав_, _neev_,
+ {{0x7bcd0071,0x9b4640e0,0x61e98125,0x39587da2}}, // [1c60] _guau, _ہندو, _nwel, _pors_,
+ {{0x6d59cf40,0x60c99650,0x636de019,0x7afb9da3}}, // _lowa, _agem, _fúne, _adut,
+ {{0x2b594e6c,0xc7268691,0xdb09858f,0x6d05a028}}, // _fosc_, здей, _bheà, _राजग_,
+ {{0x44317da4,0x6448617a,0xc7c6839d,0x31b20326}}, // _dsz_, _ġdid, пски, báze_,
+ {{0x27ee4026,0xdbd1c5df,0x7536005d,0xe5196046}}, // álně_, qüèn, _mayz, न्हि_,
+ {{0x7c240167,0x656bdda5,0x29d1a1e9,0x6b8b81e7}}, // _mpir, _algh, _báa_, angg,
+ {{0x442c60d1,0x6ba7401f,0x7bdb8f74,0x32182095}}, // _èd_, _órgã, _etuu, ntry_,
+ {{0x2eed8f10,0x8fa38967,0x3947f8ea,0x61d8c6e4}}, // _geef_, _засе, _anns_, омия_,
+ {{0x6d58f62a,0xe8e02081,0x27e05da6,0x66045da7}}, // _pova, _chịu_, npin_, nrik,
+ {{0x21201da8,0x79829da9,0x2fc5a098,0xb4bcc010}}, // _abih_, liow, _gilg_, _आणी_,
+ {{0xdd950013,0x656f000c,0x4432012b,0x7aed1daa}}, // даны, thch, _asy_, _peat,
+ {{0x69c65dab,0x44248435,0xa91dc0e4,0x7bc65791}}, // _nike, _kpm_, tiže, _niku,
+ {{0x65ab6105,0x6609c037,0xdce3a013,0x6234d096}}, // fühl, cuek, ginė, _целу,
+ {{0xe085c049,0xa91dc07f,0x2582e031,0x2d9d80c2}}, // _تجمي, riže, _délè_, zowe_,
+ {{0x200a30c5,0x443afdac,0x69c53dad,0x356b4471}}, // gubi_, _ép_, _vihe, бран_,
+ {{0x7ae44c96,0xf2dfa067,0xe81e22b4,0xdce3a013}}, // gbit, _đâm_, _पहरा_, binė,
+ {{0x89d9413a,0x6d5aa10a,0xdce3a03b,0x7c2d4171}}, // [1c70] زوار_, _oota, cinė, uwar,
+ {{0x65a5c009,0x20191dae,0x656d1daf,0x23c320f9}}, // róhi, mtsi_, _hlah, _déjì_,
+ {{0x636b23cd,0x7c2d5db0,0x2d9d9db1,0x80a24010}}, // _güna, swar, towe_, _कुठे,
+ {{0x442dddb2,0x69dc6049,0x23668022,0x63a2cbef}}, // ywe_, _dtre, gkoj_, _nkon,
+ {{0x3a3f803c,0x45d44bbb,0xac0980ff,0x69ca6019}}, // _qrup_, _пояс, інка_, ífer,
+ {{0xb925613a,0x69dd5db3,0x7bc642a9,0x961820e0}}, // _تفسي, _itse, _ziku, _بغیر_,
+ {{0x26173db4,0x6d59c0c2,0x5c0461ba,0x69dc6071}}, // nço_, _powa, _пята, _gtre,
+ {{0xeafb80d0,0x61c8d450,0x5f75c1ac,0x5f77a0e0}}, // _درست_, रदूष, باتر, _کارر,
+ {{0x21201c67,0x69ce7db5,0xf1c70cb5,0xdb0d0057}}, // _rbih_, _zube, _रंजन, _chaí,
+ {{0x21200eb9,0x0667a0e0,0x7aef5db6,0xceb28076}}, // _sbih_, _ٹائپ, _lect, _יין_,
+ {{0x6b5cc0e0,0x6d59d372,0xdb076156,0x6d5b9db7}}, // _végr, _towa, _ihjä, _koua,
+ {{0x69dd410a,0x6b81edc9,0x92ec01a6,0x2240091e}}, // _otse, silg, কল্প_, _prik_,
+ {{0xbf9b201f,0x63702277,0xbcfb1db8,0x7bcf475c}}, // rrên, _läne, ckém, _nucu,
+ {{0x4425a04e,0x2242c065,0x1869f5ea,0xa069f0da}}, // _kpl_, ække_, _мали_, _мала_,
+ {{0x6d893db9,0xc7b2e095,0x7bc65dba,0xdce3a013}}, // džad, _שבו_, _piku, sinė,
+ {{0xb4c99c37,0x200a34b4,0x0410207c,0x20183dbb}}, // ोडो_, rubi_, াবলী_, rtri_,
+ {{0x1da96033,0x03a350ba,0x9ad34079,0x7aee6022}}, // [1c80] कतंत, тито, _aịsa, _pebt,
+ {{0x7ae45dbc,0x3aec4067,0x4425a157,0x7e628167}}, // sbit, ếp_, _opl_, vyop,
+ {{0x6371446b,0x6d5b9dbd,0x7afa20c4,0x63a4015a}}, // _bånd, _boua, óttu, _kkin,
+ {{0x290f0156,0xbc6a8243,0xab5d813e,0xdaaa9690}}, // _ögat_, يمان_, _diżo, овед_,
+ {{0x63a9dca1,0x63714277,0x6faf6029,0x2fddc022}}, // dlen, _sång, _जीवं, _ntwg_,
+ {{0x63a400ae,0xf767e33e,0x26ca695d,0x2d9efdbe}}, // _lkin, _شا_, _ugbo_, xote_,
+ {{0x8b036026,0xa2a00621,0x7bce608b,0xdb040049}}, // _úřad, _गुस्, _uubu, _dhiú,
+ {{0x63a9dcab,0x4425a827,0xa3b52028,0x9343862b}}, // glen, _dpl_, _छीन_, _инсе,
+ {{0x3f9ef30f,0xf202a0c5,0x8706ea00,0x3ce68364}}, // totu_, रूज़_, мяне, mbov_,
+ {{0x2d8cfdbf,0x7f5c7dc0,0x2d8000fa,0x2fcfcff8}}, // unde_, _jorq, _amie_, _dugg_,
+ {{0x6441a29f,0xc7c8c016,0xcad2c079,0x69c88009}}, // _grli, _mối_, _dịrị, _édes,
+ {{0x95cb001b,0x63a9ddc1,0xe81fe046,0x4ab280f1}}, // жува_, clen, _बहरा_, _जराव,
+ {{0x9cc800fb,0xe7edc0c5,0x636b20cb,0x29050181}}, // дыра_, चीदा_, _dünn, ğla_,
+ {{0x2ca7e0e2,0xdb0d0057,0x61e28425,0x212b1dc2}}, // _fynd_, _viañ, ipol, sech_,
+ {{0x394d600a,0x776d00b6,0x3ead4098,0x4425a0ae}}, // _đes_, _tlax, _šet_, _xpl_,
+ {{0x656d1603,0x7bc76296,0x4426c088,0x3f84ddc3}}, // _ulah, _siju, _kpo_, nimu_,
+ {{0x200ce17a,0x7bc77dc4,0x8afee0e8,0x8f0ea0a8}}, // [1c90] ludi_, _piju, _alƙi, _सांझ_,
+ {{0x2ca91dc5,0xdb0f4019,0xb87b5dc6,0x9f5f0049}}, // _iyad_, _picó, _seís, irtí_,
+ {{0x63a9ddc7,0x63bbddc8,0xd7f8e12a,0x63a0d02d}}, // ylen, ymun, nsă_, komn,
+ {{0x7afd4a2f,0x2fc90022,0x63a9c926,0x69c76939}}, // _udst, _jiag_, xlen, _wije,
+ {{0xba7460d0,0x2d84ddc9,0x7bc77dca,0x7c2227a0}}, // _یافت, dime_, _tiju, _ćori,
+ {{0x6d5ce156,0x660d4546,0x236945dd,0x4425b8af}}, // öral, muak, lkaj_, _ppl_,
+ {{0x9f422066,0x60cd0ee2,0xdca2a423,0x63bbddcb}}, // jský_, _ogam, лаши, tmun,
+ {{0x7bc8bdcc,0x7af52265,0x3ddea0ae,0x69d5e066}}, // _eidu, hazt, _dttw_, ízem,
+ {{0x7e99a050,0x63a3e05d,0x2d9fcaaa,0x61ed0291}}, // _کنار_, _pknn, roue_, _nwal,
+ {{0xdce99019,0xdbdcc069,0x3ea7e271,0x6366007b}}, // _mleč, ráðh, _pynt_, _ióni,
+ {{0x660d41f3,0x1ae7007c,0xc625c07c,0x6442c2dd}}, // huak, _কাজে_, বিধা_, _eroi,
+ {{0xfaff000e,0x25a0405d,0x61ed011d,0x7bc8bdcd}}, // dhës_, xoil_, _bwal, _zidu,
+ {{0x60db8296,0x6d8926a6,0x27ed80d1,0x752d40ba}}, // _mfum, džab, _jwen_, deaz,
+ {{0x2571c026,0x60cd0ee2,0x7539c0e8,0x997180c2}}, // _dále_, _egam, _hawz, nął_,
+ {{0xdfd2213a,0x27ed80d1,0xd7be4010,0x1fb64f6f}}, // اير_, _lwen_, ्दलच, есар,
+ {{0x6d4bc07d,0x7dc7c0e0,0x7f5d42a8,0x6569ddce}}, // _jnga, _műso, _cosq, nkeh,
+ {{0xb910a079,0x6aa98291,0xf1ca0119,0x8c1401a6}}, // [1ca0] _celụ_, _nyef, _nại_, িবহন_,
+ {{0x3f84d1c7,0xd29b0095,0x4427eabb,0xba9b0008}}, // zimu_, _משפט, _kpn_, _מספי,
+ {{0x61fb9dcf,0x2619c067,0xdce44098,0x69c98071}}, // _avul, hèo_, lkič, _biee,
+ {{0x76441dd0,0x69c98037,0xd6c48050,0xf1ca0067}}, // _kriy, _ciee, نمای, _bại_,
+ {{0xdce98143,0x7bc8a046,0xb17b6831,0x6366007b}}, // _gleč, _pidu, _ståe, _cóni,
+ {{0xf8af40e0,0x5bbe4291,0x81be4125,0x29be4125}}, // _چکے_, _ọchị_, _ọchọ_, _ọchụ_,
+ {{0x5334c14a,0x29001dd1,0x4426ddd2,0x6d5d5dd3}}, // вейт, _odia_, _spo_, _yosa,
+ {{0x63661935,0x69c8bdd4,0x5bc8eafa,0x6da3c5da}}, // _fóni, _wide, रद्व, лија,
+ {{0x200dc01b,0x63a29dd5,0xadc36016,0x443241cd}}, // guei_, moon, _hoạc, nwy_,
+ {{0x9f42205c,0x6d5e39ff,0x02e0ddd6,0xbf9b2004}}, // tský_, _nopa, _ग्रह_, trêm,
+ {{0x661c207f,0x661bc108,0x69d6400e,0x66e3a0fb}}, // jtrk, atuk, hqye, _соха,
+ {{0xe0dfa016,0x395ea108,0x76440157,0x6b86008b}}, // _đòi_, _hots_, _briy, gikg,
+ {{0xb51060aa,0x22404143,0x7644018c,0x38c906ec}}, // ालाय_, jvik_, _criy, بادی_,
+ {{0x65b06105,0x4427e361,0x6033a5fc,0x76440827}}, // wähn, _fpn_, _səmə, _driy,
+ {{0x1fa71537,0x2fc90ca1,0x76441dd7,0xf20426fc}}, // _приг, _tiag_, _eriy, лято,
+ {{0x7af6408b,0x76440489,0x753aa534,0x5ba96260}}, // gayt, _friy, _latz, कत्व,
+ {{0x6aaae0e2,0x4ea701ba,0x2a668022,0xa2be679e}}, // [1cb0] _hyff, _ярка, jyob_, वेस्,
+ {{0xdce98cfd,0xf1bf0057,0x752f00c2,0xe1cfe7f4}}, // _vleč, lmá_, lecz, _सूँघ,
+ {{0x443fc326,0x7bcae296,0x27ed80d1,0x61fc6361}}, // zvu_, _mifu, _swen_, _cvrl,
+ {{0x752f00c2,0x3eaa622e,0x6d5e2530,0x69cafd37}}, // necz, _dybt_, _zopa, _life,
+ {{0x22404066,0x6d5e2071,0x72ec0053,0x7ae482a8}}, // cvik_, _yopa, פֿאַ, ñita,
+ {{0xdb1ac005,0x6b82ce9a,0xdb00caed,0x7bcae9e7}}, // _outó, _amog, lomè, _nifu,
+ {{0x753aba02,0x98a7e04a,0xa5bb2057,0x63a29dd8}}, // _datz, _daně_, ptóf, boon,
+ {{0x60db8167,0x6ed54077,0x6724a055,0x672f0320}}, // _ufum, युचु, đija, jecj,
+ {{0xe89480ff,0xa535a81b,0x7bcae2ad,0x67d582cb}}, // _бать, _инач, _bifu, _розу,
+ {{0x4427fdd9,0xdcfae12a,0x290120ae,0x0b42e0db}}, // _ppn_, vită, _ndha_, аньн,
+ {{0x1d0a04c2,0x3f85fdda,0x753b8066,0xa50a0242}}, // мени_, rilu_, _kauz, мена_,
+ {{0x2d91214a,0x6e298291,0x7c298088,0x661d0a7b}}, // enze_, _kpeb, _kper, ftsk,
+ {{0x61e4e29c,0x9f5f004e,0x2d82405d,0x8afee35f}}, // _čile, yttö_, _rmke_, _alƙu,
+ {{0x320953e8,0x61e44041,0x6b82d330,0x61fbdddb}}, // dray_, vpil, _zmog, msul,
+ {{0x7bcbc9ae,0xfaa62470,0x25ee0046,0x661d0aaa}}, // _kigu, таво, _अबकी_, atsk,
+ {{0x63ad5ddc,0x98a76026,0x76441dd9,0x7ae9dddd}}, // glan, žně_, _triy, nbet,
+ {{0x3f8689fe,0x0e6353fc,0x60db6143,0x764400b1}}, // [1cc0] viou_, актн, _đumr, _uriy,
+ {{0xbef20010,0xbc663116,0x9c12f1b0,0x22405dde}}, // _अजुन_, _авок, _mọnd, rvik_,
+ {{0xa2a2448b,0x1c09eaa1,0x5a9622a3,0x60cf4057}}, // _कुर्, _विफल_, _шриф, _fgcm,
+ {{0x395f87f7,0x32095ddf,0x61fbdde0,0xafe680db}}, // _cous_, bray_, ksul, водл,
+ {{0x673abde1,0x69cb0026,0xdbdf7de2,0x394ca0a2}}, // _patj, _संघी, _bíót, _pnds_,
+ {{0xa3b654ac,0x69cbc81f,0x7c299de3,0x77b8803e}}, // _चीत_, _aige, _dper, díxe,
+ {{0x7bcbc5d2,0x3c3ca04a,0xf78381f6,0x2fda60eb}}, // _bigu, dává_, _baħħ_, _آورد_,
+ {{0x4734ca18,0x60cde106,0x2ef820cb,0x200ea1b8}}, // унис, _şamp, harf_, rufi_,
+ {{0x442a6299,0x2d848098,0x1b0a207c,0xaca48088}}, // _hpb_, _imme_, শ্যই_, _kpọp,
+ {{0x7bcbc0c4,0x6d4d0aaa,0x2002c03b,0xed5986ca}}, // _eigu, _snaa, škio_, _dižu_,
+ {{0x2ef82105,0x6376e12a,0x637146dd,0x7bcae167}}, // darf_, _lâng, _låna, _vifu,
+ {{0x66e6c02d,0xf4136095,0xdce1e041,0x661d088b}}, // _рода, ספר_, rklā, ttsk,
+ {{0x63ad48ef,0x76429de4,0x69cae0f7,0x261c6057}}, // vlan, nvoy, _tife, hío_,
+ {{0x61edc6df,0xdb00cc7c,0x20094cc2,0x660f1393}}, // _çala, domé, vrai_, ruck,
+ {{0x7987200f,0xa2c043af,0x08f1607c,0x2246c012}}, // rijw, लेस्, _চালু_, _hrok_,
+ {{0x261c61af,0x81bcc041,0x6d4d0167,0x2fc04c57}}, // dío_, _spēj, _unaa, gmig_,
+ {{0xc05b014f,0x201efde5,0x32094025,0xdb00d890}}, // [1cd0] _цій_, ltti_, uray_, romè,
+ {{0x261c61af,0x6603606f,0xf77364a1,0x201efde6}}, // fío_, ánko, جار_, otti_,
+ {{0x0ccb8aa1,0x395f9de7,0x02cb8028,0xe8cb81c9}}, // िश्म, _pous_, िश्न, िश्च,
+ {{0xd6db80ff,0xc477e076,0xe9f98488,0x20094425}}, // єте_, כתבו_, енді_, prai_,
+ {{0xf41f18cf,0xdb00c03e,0xdce2c23d,0x64464143}}, // ntä_, comé, _zloć, _grki,
+ {{0x7d02dde8,0x4639a1fc,0x7bcd0013,0x2126c18c}}, // _ados, ечия_, _liau, _aboh_,
+ {{0x2bda0aa1,0xb5958657,0x7bcbdde9,0x673d4424}}, // _यूना, _бивш, _pigu, _kasj,
+ {{0x229460ff,0x64464232,0xdced004d,0xe9a9c050}}, // _тися, _yrki, _blač, رگان_,
+ {{0x05848013,0x63a3a653,0x7bcbddea,0x753d4367}}, // _турм, ponn, _vigu, _masz,
+ {{0x6b840306,0x7ae6c121,0x2d87a6ff,0x27fe206e}}, // _smig, ıkta, qine_, štni_,
+ {{0xb4bf27f4,0xcb68a050,0x1869eb15,0xfb15c7f0}}, // ेशो_, _جمله_, хани_, _مواج,
+ {{0x6aad0bb1,0x7bcd0618,0x2a6940ae,0xceb26679}}, // _byaf, _ciau, kyab_, מיי_,
+ {{0x291e00c2,0xe44ec691,0xa2aa604b,0x2ef820c4}}, // ęta_, _уж_, _जुन्, tarf_,
+ {{0xdcef0041,0x69dcbdeb,0x7989d308,0xdee2e762}}, // ficē, _érec, liew, _лоши,
+ {{0x2d925dec,0xd011e13a,0x7bc1e105,0x46a42033}}, // tnye_, _الع_, mmlu, _खुशह,
+ {{0xb8864026,0x7989c064,0x6366003e,0x442a605d}}, // hlíž, niew, _cónt, _rpb_,
+ {{0xc7a901ac,0xdce40098,0x637849fe,0xc72660fb}}, // [1ce0] _جدول_, _alić, _líng, лдай,
+ {{0x7bd57ded,0x2011211d,0x69d57dee,0x63bb8f60}}, // _buzu, buzi_, _buze, _ahun,
+ {{0xbb468a2b,0x201fc052,0x68f60025,0x4f0a8a2b}}, // лежк, htui_, _keyd, енен_,
+ {{0x14c88050,0x753c60c2,0x7c2bd9cf,0x9990806f}}, // نهای_, _warz, _opgr, ťaži_,
+ {{0xe918a12e,0x69d600e8,0x7bd600a2,0xef218018}}, // волі_, _juye, _juyu, daļā_,
+ {{0x6d4e7def,0xd838a066,0xdb0d000e,0x3326c0ae}}, // _unba, _psč_, _thaç, _sbox_,
+ {{0x63660069,0x6fba4626,0x8026a050,0x2a3561fc}}, // _jóns, ्षपू, ارتم, _кэтр,
+ {{0x7d0409ca,0x75245df0,0x6b88f2d4,0x5e9364c7}}, // _ndis, nfiz, widg, _الفط,
+ {{0xb716a13a,0x3494c866,0x3a204b10,0x6d89229f}}, // _مباش, рапр, ntip_, džan,
+ {{0x7bd57df1,0x90d5a067,0x61e2206e,0x37e3c07c}}, // _yuzu, _hàn, _čoln, _মিশর,
+ {{0xdb55004e,0x69d600a2,0x7bcd01ae,0x63784071}}, // авны, _auye, _piau, _kínd,
+ {{0x38694605,0x998963ed,0x7d040004,0x442ca06e}}, // yyar_, ktaş_, _cdis, _ipd_,
+ {{0x6448a066,0x442cbdf2,0x7afdc0c4,0x644ea057}}, // _hrdi, _hpd_, ðste, _ábib,
+ {{0x68e9600a,0xf487c062,0xdb03a017,0xa666e25b}}, // _đedo, _حامی, gonè, _مطبو,
+ {{0x63a61697,0x3f8941cd,0x6366007b,0x6376eb67}}, // bokn, tiau_, _cóns, _vând,
+ {{0x3d0f4077,0xb4db40d5,0x673d43ac,0xd90ee6ec}}, // िलें_, _anàr, _qasj, _بیگ_,
+ {{0x3f8b1df3,0xa3da6033,0x3d9520ff,0x32555342}}, // [1cf0] micu_, _डूब_, _випр, швар,
+ {{0x3dcd8efb,0x70d1d008,0x6448aea3,0x66cbc786}}, // _siew_, _हल्ल, _ordi, _hökü,
+ {{0x63bb9df4,0xdcfbc143,0x64445df5,0x6561a1de}}, // _phun, znuđ, avii, _rolh,
+ {{0x7c2d0088,0x973ce0a9,0x636ec2f4,0xdb044057}}, // _kpar, koćo, _bùnm, pliá,
+ {{0x442b5646,0x21291104,0xf8078d24,0x7afafdf6}}, // _upc_, _mbah_, учен, katt,
+ {{0x69cf420d,0x443a20ae,0x68f981cd,0x68ed5df7}}, // _hice, _ysp_, sawd, lbad,
+ {{0x27e9590a,0x69cf4361,0xf99f6157,0x539a6557}}, // lpan_, _kice, _avèg_, _יישו,
+ {{0x68ed5df8,0x290ae0e0,0x201fc052,0x7bd72025}}, // nbad, óban_, ttui_, _muxu,
+ {{0x27e9523e,0x316dd2f7,0x7989d6cc,0x3075c20e}}, // npan_, rkez_, riew, руйс,
+ {{0x0c25e0fb,0x671ca861,0xb225e056,0x2bd84028}}, // амон, _नायक_, амол, _भंसा,
+ {{0x8af04497,0x3f8b02e2,0x44216095,0x69d6048d}}, // rkəz, ficu_, nth_, _suye,
+ {{0x186a0955,0x64434041,0x2d8b1df9,0x68460be0}}, // вами_, ānij, gice_, анма,
+ {{0xdd95e02d,0x66cbc106,0x63a61dfa,0x7afbd952}}, // _кады, _dökü, rokn, maut,
+ {{0x213f8058,0xcf92a00b,0x63a61dfb,0x290c05fc}}, // _lauh_, ַטן_, sokn, şda_,
+ {{0x673f00dd,0x3f8b0552,0x6b6641e1,0x7c3aa1e2}}, // _faqj, bicu_, аква, _xstr,
+ {{0x3f8b029f,0x68ed5dfc,0x64499dfd,0x7a40a0e0}}, // cicu_, gbad, _orei, látá,
+ {{0x39405747,0xe0434662,0x433b0053,0x69d72057}}, // [1d00] rdis_, онти, _יעקב, _euxe,
+ {{0x01fb0053,0x2fcfc488,0x7abb0095,0x888627f8}}, // _שפיל, _ligg_, _בצפו, _влож,
+ {{0x27e94820,0x68ed5dfe,0x68fbc6eb,0x69cf5dff}}, // apan_, bbad, kaud, _fice,
+ {{0x7afc28c1,0x3940405d,0x61e9c05d,0x7bcf400f}}, // lart, qdis_, kpel, _gicu,
+ {{0x68fae5fc,0x44204025,0x97c6afb9,0x9ea78338}}, // yatd, qti_, айде, авца_,
+ {{0xc5ebe07c,0x69cf45b8,0x6563e1b7,0x645ce59f}}, // _কিনা_, _zice, _nonh, ärik,
+ {{0x9cd72095,0x20136037,0x61e9ce9b,0x76498bbe}}, // בוצה_, cuxi_, epel, _erey,
+ {{0x63a7200e,0x2904868e,0x2cafc488,0x28c70328}}, // zojn, _udma_, _bygd_, रेडि,
+ {{0x6563fe00,0x7763e057,0x4973c0ff,0x44200223}}, // _bonh, _bonx, ільс, _nqi_,
+ {{0x2509054f,0x20004054,0x69c45e01,0x65b060cb}}, // گردی_, isii_, hmie, fähi,
+ {{0x3eb8e94b,0x7c2d1e02,0x68fbcaf1,0x9f5d6050}}, // årt_, _spar, baud, éwé_,
+ {{0x200dde03,0x7c2d18d0,0x29182071,0x65b6004a}}, // brei_, _ppar, egra_, sáhn,
+ {{0x69d73e04,0x7afc3e05,0x63a720dd,0x442d817b}}, // _suxe, fart, tojn, _xpe_,
+ {{0x69d728aa,0x7c229e06,0x7c2d9e07,0xd6d0e7f0}}, // _puxe, itor, çari, _عقب_,
+ {{0xe1ff1e08,0x7c229e09,0x2b4006f1,0x29dc2019}}, // ssó_, htor, _faic_, _cía_,
+ {{0x3f8cec43,0x224d66ca,0x394007bc,0xb4b727fa}}, // lidu_, _ček_, _gais_, _जरी_,
+ {{0x76b9487e,0x3eaf51df,0x224911e1,0x98b948ba}}, // [1d10] улар_, ægt_, _urak_, улат_,
+ {{0xdb03a359,0x68fd1e0a,0x201e25df,0x7af8e5f9}}, // poné, lasd, àtic_, _jevt,
+ {{0x533548f4,0x637844c7,0x39400022,0x2ef86071}}, // _лент, _síne, _yais_, _derf_,
+ {{0x7bd8efda,0x2d8cf49c,0x39400286,0x3d19610a}}, // _muvu, hide_, _xais_, _पावे_,
+ {{0x443cfe0b,0xb6044866,0x7c229e0c,0x3f8ce6ca}}, // _lsv_, _мярк, gtor, kidu_,
+ {{0x63ad00ae,0x2a3a4053,0x6b8b9e0d,0x77b88057}}, // _lkan, _געשמ, sigg, ríxa,
+ {{0xa91dc20d,0x6563e00a,0x65ab6105,0x44390048}}, // liži, _ronh, bühr, hws_,
+ {{0x200dc6ab,0x7c3b91f8,0x9ad34088,0x76499e0e}}, // trei_, _tsur, _aịza, _trey,
+ {{0x68fc3e0f,0x7e6d5e10,0x443ce041,0x644afe11}}, // yard, nyap, _asv_, _arfi,
+ {{0x02a7a3a5,0x27ffc0b0,0x6374a090,0x442ee05d}}, // _крем, rsun_, _tàna, _bpf_,
+ {{0x6d8922e2,0x69c57838,0x798d4820,0x442ee1ae}}, // ržal, imhe, kiaw, _cpf_,
+ {{0x320dc090,0xf1bf4067,0x2902c009,0x6375d9f9}}, // prey_, _nhá_, ókat_, _fána,
+ {{0x657ae256,0x6375c071,0x9cdac00b,0x63a9c155}}, // bhth, _gána, _עקספ, moen,
+ {{0x1ddb4064,0xdee32a61,0x7e6d5b14,0x395fc241}}, // _बढ़त, зори, dyap, ljus_,
+ {{0x4807265a,0x5f74e7f0,0x3eaae066,0x6e2f4030}}, // йерн_, _ظاهر, čité_, _apcb,
+ {{0x4422007d,0x659305da,0x6b8d5e12,0x7bd9cfae}}, // ptk_, _нају, giag, _kuwu,
+ {{0x753534b7,0x9f5f0066,0xf53f0262,0x7c2284cf}}, // [1d20] bezz, nutý_, rlåt_, xtor,
+ {{0x799b9e13,0x65bd00e1,0x8afee0a2,0x75353c47}}, // _ajuw, réhe, _koƙa, cezz,
+ {{0x2ef86110,0x32020089,0xfbd260a4,0x07a644a0}}, // _verf_, lsky_, ستا_, _ганн,
+ {{0xdb00c1af,0x61fca271,0x3f89005d,0x2ef86854}}, // tomí, _ærli, _xmau_, _werf_,
+ {{0x25bf9e14,0xdb01a1ce,0x0b8aa1c7,0xdce28098}}, // _dhul_, _sklá, лски_, hkoć,
+ {{0x3940803b,0x7c245e15,0x64586052,0x68e2c197}}, // žis_, mtir, ävis, _sfod,
+ {{0x63ae605d,0x6146968a,0x63ab2057,0x7afe7356}}, // _ikbn, _лева, _ígne, mapt,
+ {{0x637cc18c,0x63a9c265,0x656640a2,0x8d9684c7}}, // _méng, goen, _jokh, _الرش,
+ {{0x26182033,0x6d445e16,0xb4d8c573,0x6719c148}}, // बूती_, idia, _ाली_, _धारक_,
+ {{0x7ae40132,0x291a6da1,0x32021e17,0x7c3e23c1}}, // _afit, ngpa_, dsky_, _ospr,
+ {{0xdb1c7b06,0x649aa4c2,0x644bde18,0x9999001b}}, // _chré, _стар_, _ergi, икот_,
+ {{0x65664174,0x63a8f6c7,0xf651a0e0,0x248041f3}}, // _nokh, sodn, _کئے_, kzim_,
+ {{0x6b8d4aa5,0x7bdabe19,0x644bc2c9,0x7a4320e0}}, // viag, _hutu, _grgi, kítá,
+ {{0xdcebacb1,0xd943e87e,0x6143e87c,0x637cc187}}, // čići, _нечи, _неча, _béng,
+ {{0x2366c048,0x443ebe1a,0xf773e076,0xc058219d}}, // _kooj_, _jst_, _מקס_, біт_,
+ {{0x2002129e,0x7c245e1b,0x636dea60,0x349500fb}}, // bski_, ftir, _rúni, _майр,
+ {{0x657d013a,0x2fc6829c,0x7d08accd,0xf53f10dc}}, // [1d30] mhsh, dmog_, _odds, slås_,
+ {{0x68fab4b2,0xa91dc07f,0x29f680c2,0x6b8d5e1c}}, // _netd, riži, dła_, siag,
+ {{0x3a3ea0c7,0xdb0d4049,0x63a9c167,0x93e0204b}}, // _nstp_, llaí, yoen, _खूपच_,
+ {{0x69c08089,0xdce08030,0xdb01f945,0x637da011}}, // _chme, _fomč, volí, _kèng,
+ {{0x3f8dc017,0x2d991e1d,0x29f680c2,0x6374a0b8}}, // vieu_, inse_, gła_, _càno,
+ {{0x1be9a702,0x63aaa1cd,0xd5afa0e0,0x3cfd8022}}, // адки_, gofn, رہا_, sawv_,
+ {{0x69d9de1e,0x39424037,0x6b9b86dd,0x2e48614a}}, // _suwe, _yaks_, _tjug, _лято_,
+ {{0xdb03a1af,0x6009de1f,0x636b2105,0x249f8022}}, // moní, аним_, _küns, _txum_,
+ {{0x2ca9a064,0x7afab5d5,0x44399e20,0x644d1e21}}, // ład_, _fett, _ès_, _nrai,
+ {{0x9397a25b,0x68faa03c,0x98ba2041,0x7afaa095}}, // _اجرا, _getd, _lapā_, _gett,
+ {{0x6444c00a,0x7c244108,0x6fe1807c,0x7dc02009}}, // _šiit, ztir, _নিয়ে, nöse,
+ {{0x3a98e04e,0x443f81f6,0x3cca01c7,0x69d820e1}}, // стью_, _ksu_, илно_, _évei,
+ {{0x6566448d,0xdce4400a,0x7afe7e22,0x637cc011}}, // _rokh, lkić, yapt, _dénd,
+ {{0xc17420e8,0x7c3bc229,0x65b06134,0xdb1c6049}}, // _buɗa_, mwur, näht, _thré,
+ {{0x21294058,0x7f43f615,0x7a4320e0,0x09e329b1}}, // ffah_, _ianq, vítá, досн,
+ {{0x22460381,0xaca38079,0x6d557e23,0x61e98a88}}, // _šoku_, _adọn, _anza, _otel,
+ {{0xa76665a8,0x61e980ee,0x63ab8425,0xdb0d4049}}, // [1d40] окад, _ntel, dogn, claí,
+ {{0x7f4445fc,0x201126a6,0x27edde24,0xaade2292}}, // sdiq, brzi_, ipen_, _मलिक,
+ {{0xee3a6461,0x3942592f,0x95869479,0x2d8f9e25}}, // рне_, _taks_, олже, fige_,
+ {{0x6d4574a1,0x224d9113,0x7afb8019,0x2d8f9e26}}, // adha, _brek_, _ceut, gige_,
+ {{0x27edc200,0x4fc6af6f,0xa3d48148,0x29005273}}, // jpen_, оска, _सूख_, maia_,
+ {{0x2d99e07b,0x443ea222,0x7d098052,0x2ca0817a}}, // ésel_, _pst_, _edes, ħid_,
+ {{0xdb00c5df,0xd37ac64b,0x2366c048,0xdce1a381}}, // tomà, ичи_, _pooj_, _golč,
+ {{0x93bcc1fc,0x3f8f86ca,0x644e7e27,0x69c1a1fb}}, // _apăr, cigu_, _irbi, _ehle,
+ {{0x6d43fe28,0x68e44057,0x69daa088,0x2e2400c4}}, // _bana, icid, _wute, jöf_,
+ {{0x64408c0a,0x543b849c,0xdb1aa01f,0xd6dafe29}}, // _ismi, _דעמא, _vitó, шти_,
+ {{0x6d44026f,0xb87b4019,0x973ce0a9,0x637cc07b}}, // _baia, _afíl, noći, _pénd,
+ {{0x9e66c4aa,0x44cee03c,0xd62ad2ad,0x9f5140e0}}, // _واشن, rə_, _воде_, sszú_,
+ {{0x7afc74bf,0x798f1e2a,0x637f03ac,0x4346f876}}, // _oert, ricw, _lëng, _медв,
+ {{0x98b9412a,0x7afc7e2b,0x291ca013,0xd378c012}}, // _lasă_, _nert, ngva_, _duće_,
+ {{0x44cee03c,0x984821e1,0x938b4c1e,0x39437af9}}, // qə_, оята_, асаа_, _rajs_,
+ {{0x6d56008b,0xdb0d4765,0xab5da0c2,0x63b6411e}}, // _gnya, gmað, _duży, glyn,
+ {{0x644d14d7,0x64410041,0xe87c43cd,0x439521e1}}, // [1d50] _urai, ālis, rüşü, _наис,
+ {{0x344b64a9,0x3946801f,0x4431605d,0x3a3f8037}}, // ичен_, edos_, _ppz_, _rsup_,
+ {{0x77b06156,0x27edc265,0x64408b81,0xbb3a2076}}, // växt, zpen_, _asmi, _העתי,
+ {{0x7bc8e058,0x883a2095,0xe9d860db,0xf538000b}}, // hmdu, _פתרו, ікі_, ַטור_,
+ {{0x68fc7e2c,0xdee5a399,0xdb0982d9,0x316900a2}}, // _ferd, поли, _bheò, _moaz_,
+ {{0xdb01e4d3,0xa3dede2d,0xe739b342,0x63ad5652}}, // kolá, _दूध_, сек_, loan,
+ {{0xe4e4a139,0x6b8d1366,0xddc9e064,0xdb1c6005}}, // фічн, _omag, rzeż, _eiró,
+ {{0x7bc2c653,0x69dd49fa,0xb4bba2cd,0x03a8c13a}}, // _chou, _kuse, _घरी_, يديو_,
+ {{0x7529cfdc,0x98bf8a7e,0x7bd56bf7,0x6fab279e}}, // rfez, _kauč_, _kizu, चकां,
+ {{0x8d77a25b,0x637f000e,0x0577a1ac,0x7bdc6054}}, // _بارا, _lënd, _بارد, _yuru,
+ {{0xa91d803b,0x29d761d7,0xcea90053,0x6aa2c037}}, // _amži, _għat_, _עי_, _exof,
+ {{0x20191e2e,0xb92381e9,0x65b06105,0xcf8920be}}, // fusi_, _aarẹ_, nähr, _גט_,
+ {{0xee2eccde,0x64486057,0x2138205d,0x46abe10a}}, // _ян_, _ádiv, terh_, _घुरह,
+ {{0x32182a2a,0x7c2618e8,0xd378c012,0x65698090}}, // tury_, rtkr, _vuće_, _loeh,
+ {{0x3079e00b,0x63ad41fc,0x3ebe2551,0x69dd5e2f}}, // _לאַנ, foan, ått_, _ause,
+ {{0xa0a60de7,0x644f4098,0x18a602cb,0x06ab207c}}, // _найд, _brci, _найм, _গৃহি,
+ {{0xdb0d4069,0xdca350da,0x69d56f60,0x29005b95}}, // [1d60] rmað, еати, _bize, raia_,
+ {{0x645ce0ac,0xdb0d40c4,0x6d453e30,0x69dd5e31}}, // årig, smað, _gaha, _duse,
+ {{0x7bd56265,0xdce2c07f,0x7d00de32,0x11d660eb}}, // _dizu, _sooč, yams, ستاد,
+ {{0x7bdd5e33,0xdb1c6b34,0xa3e44029,0xdb1d4057}}, // _fusu, _piró, _पंप_, _disó,
+ {{0x6568bd5c,0x29017e34,0x69d60d72,0x7bdd400f}}, // _podh, baha_, _miye, _gusu,
+ {{0xa3c2e295,0x2d8d8125,0x636de03e,0x6d45217a}}, // ्दछ_, _emee_, _xúnt, _xaha,
+ {{0x3945a4cf,0x644e7e35,0x62828048,0xb8f4a48b}}, // _dals_, _urbi, szoo, _सः_,
+ {{0x69c40016,0xdb1c6019,0x7e9b6095,0xdb01e326}}, // _nhie, _tiró, _הסלו, volá,
+ {{0x2007fe36,0x69dd4054,0x321a6066,0x6568a351}}, // _avni_, _xuse, lupy_, _todh,
+ {{0x29d761d7,0x69d602f4,0x7d00d02d,0x63ad51c6}}, // _għas_, _aiye, sams, zoan,
+ {{0xdb18e066,0x69c40090,0x636ec090,0x25add2da}}, // _chvá, _bhie, _bùnt, boel_,
+ {{0xdc2ac050,0xba2ac0eb,0xe6950052,0x7bd60827}}, // _هسته_, _هستم_, димы, _ciyu,
+ {{0x6fd6446f,0x2be4a07c,0xe9ff8067,0x7bd6036d}}, // _मंजू, _গিয়ে_, _trấn_, _diyu,
+ {{0x3d172021,0x645ce1dd,0x2139000e,0x62844b6b}}, // तलें_, äris, sesh_, dzio,
+ {{0x20191e37,0x4ea42adc,0x213900dd,0xae1d238e}}, // susi_, ерта, pesh_, बंधन_,
+ {{0xe3b8e6df,0x798d1e38,0x201a6341,0xdbd6e04e}}, // llı_, _umaw, dupi_, sään,
+ {{0x29017419,0x6f029e39,0x7f45205d,0x7ae571c7}}, // [1d70] taha_, daoc, _tahq, tcht,
+ {{0x63ad5e3a,0x201a6167,0x673afe3b,0x261c4028}}, // soan, fupi_, netj, मढ़ी_,
+ {{0x2d925e3c,0x7ae57385,0x69d57e3d,0x63ad5da1}}, // biye_, rcht, _vize, poan,
+ {{0x6442c04e,0x7bdd5e3e,0x2d925df1,0xe3b8e602}}, // _osoi, _tusu, ciye_, hlı_,
+ {{0x3945b607,0x644f4261,0xb9058028,0x272fa19f}}, // _pals_, _urci, _नल_, yını_,
+ {{0x6722c2a8,0xdb1e2017,0xb8834066,0x673af7e9}}, // _acoj, _hipò, rníč, jetj,
+ {{0x25adc54c,0x7642c0f9,0x7ae60156,0x644d5e3f}}, // toel_, _asoy, yckt, lvai,
+ {{0x61ed1e40,0xe1ff0b0a,0xe7bf4c4c,0xa3dec077}}, // _otal, nyós_, ्षिप, _दंश_,
+ {{0x69c401cd,0x39495e41,0x799bd4a5,0x290d9e42}}, // _rhie, idas_, rnuw, _idea_,
+ {{0x69d73e43,0x8e09e95a,0xeaf8a050,0x291ee05d}}, // _nixe, онов_, _فرمت_, ggta_,
+ {{0x6f01e06e,0xaca34096,0x7d01e654,0xa3ad42cd}}, // ralc, _baịb, rals, गवा_,
+ {{0x3946c057,0xe8dfc079,0x6f01fe44,0x32090030}}, // _gaos_, _kpọm_, salc, _bvay_,
+ {{0xe3aee362,0xd469c3d5,0x60db80ce,0x8c45de45}}, // _аб_, _филе_, _mgum, _целе,
+ {{0xdb1c613a,0x2d92597d,0x657d4025,0x81e5207c}}, // _phrí, wiye_, _alsh, _নিজ_,
+ {{0x60db8035,0xb9063e46,0xd6f5407c,0x2d805b7c}}, // _ogum, _फल_, _ছাড়া_, thie_,
+ {{0x7d03aa0c,0x69c52009,0x78a4005f,0x660982f9}}, // hans, _ehhe, _txiv, _ovek,
+ {{0x65644d3d,0x656bce90,0x61e4e64e,0xb8d52026}}, // [1d80] rjih, _mogh, _čili, _छु_,
+ {{0x753bc0c7,0x7644066b,0x637cce41,0x656bde47}}, // heuz, _isiy, _ména, _logh,
+ {{0x2d805b7c,0xa6ca20d0,0x637ffe48,0x443fc058}}, // phie_, _گوگل_, _gêne, hwu_,
+ {{0x661c29c0,0xe3b8e3ed,0x673c3e49,0x442a22a8}}, // murk, zlı_, lerj, mtb_,
+ {{0xd90d8896,0x661c2820,0x2900001f,0xb8f68010}}, // صیل_, lurk, _meia_, _सण_,
+ {{0x6f0458ea,0x29001134,0x2738c016,0xc1a8a010}}, // laic, _leia_, _ẩn_, गवेग,
+ {{0xa3c1f775,0x20068197,0x3ce6802e,0x69d84009}}, // ंतन_, ssoi_, pcov_, _éves,
+ {{0x64440f74,0x071e6033,0xdd99a030,0x44443404}}, // _osii, _पाँव_, _awňd_, _ms_,
+ {{0x6f03be4a,0xe3b8e3df,0xb4db40f9,0xa2d0e6e1}}, // banc, tlı_, _baàj, डेन्,
+ {{0x661c2dc9,0x61ed0187,0x64486057,0x290ca156}}, // kurk, _rtal, _ádis, _udda_,
+ {{0x660982e2,0xd5afd06d,0xe3b8e3df,0x44295e4b}}, // _zvek, _ас_, rlı_, zta_,
+ {{0x61e09e4c,0xe3b8e315,0x27e01e4d,0x2d9e4009}}, // _kuml, slı_, _buin_, étel_,
+ {{0x6f045e4e,0x63af04f3,0x6b9d0156,0xe4e720ff}}, // daic, rocn, rnsg, дібн,
+ {{0xd250e09b,0xe8e00119,0xc05805d0,0x60c2de4f}}, // _منت_, _muội_, фію_, _izom,
+ {{0x29001bd3,0x8d77413a,0x356b5e50,0x6d892098}}, // _feia_, وارا, оран_, džat,
+ {{0x69d8f890,0x6f008559,0x657e2090,0x63a2c037}}, // _hive, _nemc, _elph, _jjon,
+ {{0xfbdf5e51,0x644d40fa,0x8cc4c0c2,0x27e009f2}}, // [1d90] _quê_, uvai, ाइटो, _guin_,
+ {{0x7dc02156,0x644d5e52,0xdb1c62d9,0x60c01e53}}, // lösn, rvai, _chrà, ümme,
+ {{0xdb1c7e54,0xa3e44cb5,0x39491813,0x6d49c1e2}}, // _virð, _पंथ_, _kaas_, ydea,
+ {{0x6d48a24f,0xf9d9e00b,0xc7b9000a,0x63a2de55}}, // _aada, פֿעל, buđe_, _njon,
+ {{0x7c29dd81,0x31a403df,0x69cb8c94,0x201b403c}}, // xter, nıza_, rmge, quqi_,
+ {{0x39491e56,0x63a2c052,0x637da0ca,0x7bd8ebef}}, // _laas_, _ajon, _bèna, _nivu,
+ {{0x3ce08066,0xbbbd68b1,0xe057a050,0x2fc6c6f1}}, // živa_, ्गीक, بیات_, _bhog_,
+ {{0x1309c4a4,0xc245c312,0x64452296,0x6d48b8ea}}, // чний_, еник, _mshi, _eada,
+ {{0x69d8e68f,0x461580a4,0x7bd8e00f,0x661c3e57}}, // _bive, _سوار, _bivu, zurk,
+ {{0xf1b9e143,0x67240012,0x63a2cf8f,0x3eb94926}}, // kuše_, _scij, _ejon, _myst_,
+ {{0x7ae48b6d,0x236d8098,0x661bdcce,0x656bc049}}, // žite, _koej_, suuk, _togh,
+ {{0x200b4133,0x70dc72de,0xf1b9f5aa,0x753d1e58}}, // _ovci_, _बल्ल, duše_, gesz,
+ {{0x7d04578a,0x6d48b04a,0x29001e59,0xab18c0ff}}, // vais, _yada, _veia_, _цієї_,
+ {{0x7d01be5a,0x3f8200dd,0x32095e5b,0x66d68121}}, // _jels, shku_, dsay_, _hükü,
+ {{0x6d89203b,0xe4e4e0ff,0xfd692088,0x6f0446f1}}, // ežas, _річн, _hapụ, taic,
+ {{0x6375c7fc,0x63bbcffe,0x661d16f6,0x69d8e107}}, // _báni, nlun, busk, _zive,
+ {{0x66e62841,0xd2a74825,0x6375c057,0x661d1e5c}}, // [1da0] _попа, екте_, _cáni, cusk,
+ {{0x69d9de3e,0xfd692079,0x7d056efb,0x66164213}}, // _liwe, _mapụ, gahs, rryk,
+ {{0x63bbd137,0x7e612156,0x7d045df5,0x2905e0ba}}, // klun, älpt, pais, iala_,
+ {{0x6366003e,0x6609de5d,0x09e7207c,0xee49e067}}, // _cónx, ksek, _ফিচা, _mẽ_,
+ {{0x6609de5e,0x6133a181,0xa2d64047,0x26c7204a}}, // jsek, kılı, _سيست, _únor_,
+ {{0x29fdc06f,0x2904de5f,0x7bd9c0f9,0xa294a12e}}, // dňa_, wama_, _aiwu, калі,
+ {{0x3ebe6069,0x7d008106,0x69d9c035,0xa3e44466}}, // _átt_, _tems, _biwe, _पूत_,
+ {{0x7c2aad4c,0x63bbde60,0xa3e32028,0x7bd9c0a2}}, // rtfr, glun, _फंस_, _ciwu,
+ {{0xada6a099,0x6d5aa058,0x2905fe61,0xdb0d41ae}}, // _заал, _tnta, fala_, slaç,
+ {{0xf8abe067,0x8233c049,0x764294b7,0xc178e013}}, // ười_, اريا, mwoy, ukė_,
+ {{0x798440bb,0x2d800030,0x6721e077,0x661e61e7}}, // khiw, _blie_, _मारक_, nupk,
+ {{0x389b6087,0x7ae9cfdc,0xca48c016,0xaaa9be62}}, // ליינ, ccet, _tờ_, _चुटक,
+ {{0x59b06010,0x6d893e63,0x799531a1,0x7a36e5fc}}, // जकार, džar, tizw, _bütö,
+ {{0x2246c2f8,0x764280a2,0xadc441e9,0x63a411c7}}, // _isok_, iwoy, _ikẹt, _ejin,
+ {{0x27fea05d,0xf1bf20e0,0xf9c74a2b,0x5f95401b}}, // _jwtn_, lták_, ещан, вивт,
+ {{0x213eed90,0x201efe64,0xf1b9e9b2,0x32094a5c}}, // leth_, muti_, ruše_, tsay_,
+ {{0x69dabe65,0x02a30079,0xaac9c026,0x7bcd5e66}}, // [1db0] _oite, _kpọọ, ाइएक, rmau,
+ {{0x200bc06f,0xa3092043,0x7bdabd0e,0x491b2026}}, // ácii_, _کرکے_, _nitu, _बाटो_,
+ {{0x6f05605d,0x2121605d,0x656e62a5,0xe717e3c8}}, // rahc, rghh_, _dobh, תחבר_,
+ {{0x3eb95e67,0x69daa0ef,0x645a6012,0x656e6090}}, // _tyst_, _aite, _štic, _eobh,
+ {{0x6d9602b8,0x6d4b81cf,0xb954409e,0x69dac0c2}}, // kšan, rdga, _свящ, _पंजी,
+ {{0x3b05e497,0x6d4d4acd,0x24894625,0x44386132}}, // xalq_, ldaa, mzam_, _fpr_,
+ {{0x69d9de68,0xadc441e9,0xcf582095,0xdcfb8066}}, // _piwe, _akẹt, תבות_, _hluč,
+ {{0x2246c037,0x9f5f0066,0x25742105,0x637cc004}}, // _bsok_, ystá_, _hält_, _déno,
+ {{0x4439417b,0x6d4afe69,0x290375a8,0xa3dd0033}}, // _jps_, _bafa, _keja_, _तंग_,
+ {{0x69daa00f,0x645a7e6a,0x673f400e,0x69da9e6b}}, // _gite, _átic, heqj, _éter,
+ {{0xa0a5fd85,0x2246cce1,0x7886f397,0x6f07206f}}, // танд, _esok_, _révé, dajc,
+ {{0x6455650e,0xed5700ff,0x6e2d5a12,0x1eeac043}}, // _mrzi, вою_, ktab, _قومی_,
+ {{0x2907b1c6,0xfebbe062,0x333fc0fa,0x3dc90025}}, // oana_, _کاشت_, meux_, _dhaw_,
+ {{0x66e2e5da,0xe0df40f9,0x64a62cde,0x29037e6c}}, // _коша, _agò_, вага, _neja_,
+ {{0xe1ff4031,0xa2d36026,0x66032731,0x394b5e6d}}, // _awó_, भेम्, ипра, _bacs_,
+ {{0x6569c0dd,0x6d4d4bae,0x69db8187,0x6f02c005}}, // njeh, gdaa, _niue, _xeoc,
+ {{0x6d4af2bb,0x6e2d4426,0xc9aa290f,0xdb0b807b}}, // [1dc0] _yafa, gtab, _овие_, gogí,
+ {{0x6d4bcfcf,0x656f413a,0x25a05e6e,0x8ad6613a}}, // _laga, _eoch, snil_, نتائ,
+ {{0x61e403d3,0xe6bec1b0,0xdce44320,0x92bdc07c}}, // _huil, _ऊर्ज, ljič, _আলো_,
+ {{0x7c2d5e6f,0xa11381ac,0x61e41e70,0x333fc0fa}}, // btar, لومت, _kuil, jeux_,
+ {{0x7bc9868d,0x3ce09e71,0x2907be72,0x661e6037}}, // _dheu, živo_, fana_, supk,
+ {{0x1ea98085,0xd378c00a,0x656f55c9,0xdb08e009}}, // راني_, _pućo_, _zoch, kodá,
+ {{0x61e2de73,0x2d84c004,0x443a3e74,0x4394c4c2}}, // _suol, thme_, _jpp_, лакс,
+ {{0x6288fe75,0x3f983e76,0x656f5e77,0x7981ab45}}, // vzdo, hiru_, _xoch, _ellw,
+ {{0x69daa9bb,0xcf934694,0x49baadb3,0x75e7a121}}, // _tite, יטה_, _قائد_, _hızl,
+ {{0x6d4bc6a5,0x3f869e78,0xd130e19a,0x75e7a121}}, // _eaga, lhou_, حمد_, _kızl,
+ {{0x7bdc7e79,0x7d02ccbd,0xd91b6087,0x3f9ba004}}, // _miru, _teos, וויל, èque_,
+ {{0xaca46088,0x69dc7e7a,0x6d4e2d53,0x7644400f}}, // _asịr, _lire, edba, kwiy,
+ {{0x7c2d578e,0xb5fb00e0,0x7d07206e,0x3f812048}}, // xtar, lyáz, tajs, _plhu_,
+ {{0x290486c7,0x2d85e032,0xfd554088,0x6e20c631}}, // _lema_, chle_, _fraị, mumb,
+ {{0xe8d90125,0xdb1bc017,0x7d073e7b,0xd5b90018}}, // _inọ_, fluè, rajs, utās_,
+ {{0x660d986e,0x443a2007,0x5b144a2b,0x444460fd}}, // šaki, _dpp_, имст, dw_,
+ {{0xd1b800d0,0x656f5e7c,0x69c99e7d,0x44446547}}, // [1dd0] _جاوا_, _voch, _rhee, ew_,
+ {{0x1b14e405,0xd9cb8328,0x2bc34029,0x7bdc662f}}, // ত্রে_, िष्ट, _वीरा, _ciru,
+ {{0x656f4115,0x248940c2,0x645a0041,0x48157882}}, // _toch, szam_, ātie, умес,
+ {{0x671d4cb5,0x27e48321,0x442dde7e,0x63a298c0}}, // _फाटक_, _bumn_, yte_, gnon,
+ {{0x637cc057,0xec35400b,0x44204d82,0x61e24049}}, // _vénl, _גאָר_, cui_, íolt,
+ {{0x3ea9817a,0x76556064,0x7aed42dd,0x7c3aa187}}, // ħat_, _trzy, lcat, _bptr,
+ {{0xdd946099,0x2d991e7f,0xe800c033,0x80ada07c}}, // _тары, kise_, लीसा_, য়েন্,
+ {{0x3f868530,0x6b82de80,0x7bdd4037,0xf1b9fc67}}, // chou_, _clog, _jisu, duša_,
+ {{0x7793a043,0x6d4bde81,0x69caeab5,0x6f041e82}}, // _پیغا, _vaga, _bhfe, _reic,
+ {{0xe1f9e80e,0x657bc5d9,0xc0c89882,0x61e3ea89}}, // нго_, skuh, _русе_, _punl,
+ {{0x2d87a28f,0x7f4d0054,0xadf5a7ca,0x7dc7c0e0}}, // mhne_, _baaq, _спеш, _műsz,
+ {{0x6e20c2a9,0x637f000e,0xdcfae013,0x888ca053}}, // bumb, _dëno, kitė, _קראַ,
+ {{0x44217e83,0x26068029,0xed4ee825,0x877a800b}}, // huh_, सीपी_, _ло_, גארי,
+ {{0x63b53e5c,0x200bc06f,0x6f040105,0xd378a579}}, // gozn, áciu_, _weic, _tiće_,
+ {{0x69c40064,0x2249017b,0xdbd1e10a,0x69dc7672}}, // रतदी, _esak_, _müüj, _rire,
+ {{0x2258e065,0x3f983e84,0x64560076,0x7f9425fc}}, // ærke_, siru_, _tryi, _müqə,
+ {{0xa7a74f4a,0x442d8223,0x733b000b,0x79840167}}, // [1de0] укта_, _nqe_, _טעקס, _iliw,
+ {{0x6d40de85,0x25754156,0x64444401,0xed4621c0}}, // zema, _sålt_, rwii, _сноп,
+ {{0x661bde86,0xb9252125,0x7bdc7e87,0x7c21e05d}}, // hruk, _lepụ_, _viru, nulr,
+ {{0x6e20d9fc,0xa09b200b,0x20557043,0x7bdd5518}}, // yumb, _טייט, штур, _gisu,
+ {{0xdca3882b,0x7f41e3ac,0x6375c9d0,0x7dc7c009}}, // баци, kelq, _láns, _fűsz,
+ {{0x6b840355,0x6d40de88,0x6e20c160,0x63a28d1c}}, // _llig, wema, vumb, snon,
+ {{0xee3a6052,0x2d9901a5,0xa2d6d91c,0x7bdd40b1}}, // _янв_, yise_, मेन्, _yisu,
+ {{0x7afbcb5e,0xa5bb2183,0xe5c6c3fc,0x5e57200b}}, // gbut, cróf, ысло, ריקע_,
+ {{0x6b82c06e,0x63a45c62,0xcad2c125,0xbb3b400b}}, // _vlog, inin, _gịlị, געפי,
+ {{0x6b96010f,0x290a3e89,0x3d94ee37,0x1637c13a}}, // _amyg, jaba_, риор, اسية_,
+ {{0xa5bb2064,0xa3b4451d,0x7c2d000e,0x61e64561}}, // któr, टवा_, _sqar, _lukl,
+ {{0x2905a265,0x7dc02156,0x660d52c1,0x22490037}}, // _zela_, lösh, ysak, _psak_,
+ {{0x6eb04688,0x44216098,0x5f94014a,0x51873e8a}}, // _अँगु, zuh_, сият, гува,
+ {{0x290a3350,0x7d0abe8b,0x6d41e506,0xb865c896}}, // gaba_, nafs, cela, _خامو,
+ {{0x61e65b43,0x3da750ba,0x7aed40d5,0x6b8408e0}}, // _aukl, граб, tcat, _flig,
+ {{0x644ae0f7,0x4c13e68c,0x6602de8c,0x6d4e7e8d}}, // _isfi, _ابوس, _kwok, _aaba,
+ {{0xb87b4031,0x443b005d,0x3b094025,0x68e2c0ce}}, // [1df0] _afír, _upq_, raaq_, _mgod,
+ {{0x7afd0669,0x8d77657f,0xf52726e4,0x25e66028}}, // lbst, دارا, _сфин, _जूही_,
+ {{0x660d59c1,0x44216037,0xf8b92029,0xa2d91073}}, // ssak, uuh_, _इडिय, _नृत्,
+ {{0x7ae48066,0x68e2c1fb,0x7c228197,0x320dc025}}, // žitn, _ngod, fuor, ysey_,
+ {{0xaf0480ff,0x44232ca4,0x6d4e6659,0x6602c096}}, // спіл, luj_, _faba, _nwok,
+ {{0x6edbc041,0x3f9a6098,0x6458e7f7,0x661d1e8e}}, // _jābū, cipu_, _orvi, irsk,
+ {{0x27f8806e,0x98b820ba,0x6498a14a,0x442ee0c1}}, // _črne_, meră_, лтър_, _nqf_,
+ {{0x64498cab,0x68fc20a9,0x7d076950,0x3f8940fd}}, // _tsei, zbrd, _hejs, nhau_,
+ {{0x64a5c3fc,0xdca5c471,0x27f8805c,0x443ce07d}}, // рапа, рапи, čený_, _apv_,
+ {{0x27e6de8f,0x661bc220,0x78a4cbe2,0x3869566b}}, // _duon_, rruk, _živc, ixar_,
+ {{0x442320e8,0x290a3e90,0x63bb817a,0xfd10a049}}, // juj_, vaba_, _kkun, _رجل_,
+ {{0x63bb81d7,0x7bdf0057,0x69de3e91,0x6b8400f7}}, // _jkun, _ciqu, _sipe, _qlig,
+ {{0xe8eeed59,0xc8d00077,0x799ae284,0x69cd00eb}}, // _мл_, सेंट, bitw, _dhae,
+ {{0x63bb80ae,0xbddb6030,0x637843f6,0x8cb12118}}, // _lkun, _erèz, _línt, _आँखो,
+ {{0x1219220e,0x63784005,0x3ebe2069,0x61e650aa}}, // люты_, _oínt, ætt_, _sukl,
+ {{0x2bbf5ad8,0x79840167,0x59b42026,0x6b89c747}}, // ्षगा, _uliw, ंकहर, nheg,
+ {{0x8c4606fc,0xdced194e,0x3f9a613b,0xa3c8c010}}, // [1e00] _тепе, _glađ, tipu_, ोगत_,
+ {{0x69c1f0bf,0xda18c010,0x2fc0494b,0x69df1e92}}, // nlle, _दिसत_, slig_, _éper,
+ {{0x799c2bb1,0x3f894048,0x68e3e1e7,0x81e7e07c}}, // mirw, bhau_, _mgnd, মীর_,
+ {{0x3f895e93,0xa5bb207b,0x2fdf8005,0xcad2c088}}, // chau_, tróg, _ciug_, _dịjị,
+ {{0x98b820ba,0x799ae0b1,0xdca6882b,0x33d6614f}}, // beră_, yitw, шади, _вівт,
+ {{0xab0961b3,0x6d428108,0x6d445e94,0x7c228534}}, // _متفق_, seoa, leia, ruor,
+ {{0x6f0aa098,0xdca36399,0x44325e95,0x46a37e96}}, // rafc, _дари, lty_, _дарв,
+ {{0xea004081,0x69c1fe97,0x637840f9,0x8fa68799}}, // _luật_, elle, _tínu, _табе,
+ {{0x68e9612a,0x7d0aab83,0x44233e98,0x637cc011}}, // _şedi, pafs, zuj_, _jéni,
+ {{0x69c281cd,0xd378c012,0x6e3e214f,0x201ca9a3}}, // lloe, _mući_, _oppb, prvi_,
+ {{0x61e083df,0x7ae404b4,0x7bcd0286,0x2d9cb1c8}}, // _kiml, _bgit, _phau, mive_,
+ {{0x25a680e2,0x63a4d019,0x645abe99,0x77930050}}, // nnol_, čins, _irti, تیبا,
+ {{0x602aa03c,0x637da187,0x395254f2,0x7afd0325}}, // _təms, _bènh, ddys_, rbst,
+ {{0xdb056277,0x4ea73429,0x8cb0410a,0xe4a725d0}}, // llhö, арба, _अँजो, арбо,
+ {{0x7414e63b,0x443cfe9a,0x37075002,0xf206e14a}}, // _اوبا, _upv_, ичав, _тяло,
+ {{0x7d08b971,0x44313e9b,0x2d9cbe9c,0x201ee0a9}}, // _leds, rtz_, hive_, mrti_,
+ {{0xdb00c07b,0xa7870555,0x6e24521e,0x752d003c}}, // [1e10] lomó, _مشکو, guib, _icaz,
+ {{0x6d43b9a1,0x2907fe9d,0x764d0427,0x290ceded}}, // xena, _xena_, _isay, nada_,
+ {{0x61e96504,0xd8b80050,0x656d46a6,0xa91d806f}}, // _čels, _مدیا_, pjah, _dlžn,
+ {{0x27e912f4,0x3f85a07d,0xac1940ff,0x60d9e00b}}, // _kuan_, _sllu_, _йому_, אַנג,
+ {{0x637cc004,0x6f08a037,0xb345a1ae,0xbcfb4049}}, // _géni, _bedc, jeçã, _gnéa,
+ {{0x248dc42e,0x3a3ea037,0x3dcd81e7,0xf745adf5}}, // vzem_, _bptp_, _whew_, секо,
+ {{0x672d03ee,0x69c28d53,0x6d43be9e,0x7f43a0b8}}, // _ocaj, bloe, rena, renq,
+ {{0x3f86c1ab,0x61e8be9f,0x6b9c3929,0x6e23a265}}, // _clou_, _dudl, zirg, runb,
+ {{0x645ca0c4,0xd24ec13a,0xe7eb612f,0x3a24c098}}, // _árin, فني_, _जूता_, gump_,
+ {{0x443f9ea0,0x9b74a13a,0x237fdea1,0x7d74a13a}}, // _ipu_, _والص, skuj_, _والط,
+ {{0x644d1ea2,0x2ca9c098,0xdce9c7a0,0x44337ea3}}, // _asai, _žadd_, lječ, ntx_,
+ {{0x63b990d6,0x4424dea4,0x6d457ea5,0x2d9d9ea6}}, // nown, bum_, heha, niwe_,
+ {{0x27e900ef,0x443f807d,0x3bd5ead4,0x6e3e205d}}, // _cuan_, _jpu_, _люкс, _sppb,
+ {{0x60c1a052,0x3f9940a2,0x7649c7b1,0xa8030057}}, // _kylm, _amsu_, mwey, íñas,
+ {{0x7dc6e83e,0x6563fea7,0x799c2a20,0x753b811d}}, // iðsl, _innh, rirw, _obuz,
+ {{0x44325ea8,0x29d781d7,0xb6a32702,0xfaa332a1}}, // tty_, _għax_, числ, часо,
+ {{0x27e9017b,0x61e09ea9,0xe8f700fb,0x3b866093}}, // [1e20] _guan_, _riml, _улы_, благ,
+ {{0x7c245eaa,0x66186066,0x60c08156,0xdfcf6049}}, // ruir, ávko, _rymm, فيق_,
+ {{0x6e3e3eab,0xdb01e057,0x539ae0be,0x645b8090}}, // _uppb, loló, ניעו, _arui,
+ {{0x543b200b,0x4425e12a,0xd378c2e2,0x7df3e12a}}, // _געטא, iul_, _tući_, _găse,
+ {{0x9412a497,0xa3e7d6fd,0xb87b1eac,0x69c441ea}}, // əcək_, _मूल_, ncíp, mlie,
+ {{0x7c256435,0x6d457ead,0x26056010,0x7bc2804a}}, // buhr, ceha, हीही_, slou,
+ {{0x44337eae,0xdfd0c1ac,0x645b8200,0x8e86613a}}, // ctx_, ويت_, _erui, _الأه,
+ {{0xa91b4069,0xadfa00ec,0x2d9d80b1,0x645b8e96}}, // _alþj, ्ठान_, ciwe_, _frui,
+ {{0x290a605d,0x6e96813a,0x7d0e3eaf,0x2002e16d}}, // _keba_, _الذا, kabs, ćkim_,
+ {{0x39468510,0x443ea058,0xdb00caba,0xd90ee896}}, // leos_, _tpt_, tomó, ریب_,
+ {{0x91bb60be,0x7bc44013,0x6d460614,0x7aed8a38}}, // רמיי, kliu, feka, žate,
+ {{0x6562c04e,0x7d0d4054,0x3a24c76a,0x398ac0f9}}, // _unoh, xaas, pump_, _bùse_,
+ {{0x657dc262,0x321128fd,0xfa33c0eb,0x3945feb0}}, // öshe, wszy_, _فرود, bels_,
+ {{0xe81d2029,0xe29f0069,0x998963de,0x3ce52488}}, // _फिदा_, naði_, ktaš_, ølv_,
+ {{0x877ba0be,0x63792031,0x442680ce,0x1b7ba053}}, // נאלי, _bìnr, huo_, נטלע,
+ {{0x60f9465d,0x35a405da,0xb8cf007c,0xe9a405da}}, // рная_, _најг, _কর_, _најп,
+ {{0x7d0d4e38,0x798280c2,0x63ad1eb1,0xf1ab2050}}, // [1e30] raas, dkow, _ijan, _ماده_,
+ {{0x3f877eb2,0x61ed4bb5,0x60cd1ea6,0xb4e90046}}, // ónu_, rqal, _izam, _बलु_,
+ {{0xdb176057,0xe3bf22a8,0x64408018,0x290eb819}}, // noxé, cuña_, _apmi, dafa_,
+ {{0x63bae089,0xe7c90eda,0x61eafda4,0xa1595eb3}}, // hotn, रतिप, _mufl, _таму_,
+ {{0x61e995f3,0x290ebeb4,0x645c6640,0x63b98064}}, // _quel, fafa_, _erri, rown,
+ {{0x9f4800dd,0xe7eda8c6,0x6fe8e026,0x644e6098}}, // _punë_, _चंदा_, _těch, _fsbi,
+ {{0x63bae037,0x94756050,0x29c9a03e,0x7d0999ae}}, // dotn, _بگذا, búan_, _tees,
+ {{0x61e1bd37,0x201fdcfd,0x09e2c9fd,0x290b4361}}, // _will, trui_, мощн, _jeca_,
+ {{0x61eae105,0xb4fb204b,0x6729deb5,0x3945edee}}, // _aufl, ्राय_, rgej, tels_,
+ {{0x61e1beb6,0xbca484a1,0xae1e40aa,0x2d8ce13a}}, // _uill, _عملي, यूटन_, ghde_,
+ {{0x44205eb7,0x753d4064,0x61e2deb8,0x76aba656}}, // zri_, _obsz, _fiol, _став_,
+ {{0x6d55364a,0x2d9a27d3,0x2d832171,0x672b8440}}, // edza, _smpe_, ekje_, nggj,
+ {{0x63a9deb9,0x6d9600e4,0x798d5eba,0x69c44110}}, // mnen, kšav, dhaw, vlie,
+ {{0x3f8dc1ae,0xc31f607c,0x50da8557,0xf1bf4788}}, // lheu_, ন্তি_, יקרא, _aká_,
+ {{0x644f5ebb,0xe732013a,0x25bf8121,0xdee3262b}}, // _asci, _قصة_, _okul_, дори,
+ {{0x290a7ebc,0x61e3febd,0x2918605d,0xdb01e017}}, // _seba_, _hinl, _sdra_, colò,
+ {{0xd62a43fc,0x6f0bc009,0xf50a6494,0xada34926}}, // [1e40] розе_, _megc, инал_, фатл,
+ {{0x3f9a2037,0x61e41677,0x7d0bc009,0x637f000e}}, // _umpu_, _hiil, _legs, _rëni,
+ {{0x6441a05f,0x63bbc0ca,0x7bc4403b,0xf21e4064}}, // _npli, koun, pliu, यूज़_,
+ {{0x290f9ebe,0xaca3c079,0x7dcda5e0,0x63a9c05a}}, // gaga_, _atọm, yúsc, jnen,
+ {{0xade36028,0xe29f00c4,0x88cfe07c,0x645e2012}}, // _कंचन_, taði_, রশিক, _hrpi,
+ {{0xceb4a03c,0xdb1d456e,0x39528013,0x29c9a005}}, // şəm_, _aksè, žys_, súan_,
+ {{0x2d9efc98,0x7d0afebf,0x44216037,0x66d061e9}}, // vite_, _refs, crh_, _dókí,
+ {{0x2242417b,0x1c1d2028,0xdb03a69f,0x6b845ec0}}, // _kpkk_, _फिसल_, fonó, nkig,
+ {{0xb6a380ff,0x316ae0de,0x7dc033c0,0xdb1c6011}}, // _житл, ашно_, nöss, _akré,
+ {{0xaac03133,0x973ce29f,0xdb18a1ce,0x63bc3ec1}}, // _शुभक, kiće, kové, jorn,
+ {{0x61e3e3df,0x6b845182,0x3f8905df,0x6d472fdb}}, // _dinl, kkig, _plau_, veja,
+ {{0x6f029ec2,0x637cc7df,0xa3c94046,0x69cae0c2}}, // nboc, _cént, _लीं_, _सीरी,
+ {{0xb7db8095,0x39532057,0x290cbec3,0xe3b9003c}}, // יקטי, _paxs_, _keda_, brın_,
+ {{0x26c48425,0x644020ba,0x7d0287a0,0x637cc0e1}}, // _mymo_, ămin, hbos, _vénu,
+ {{0x6f0f065f,0x7dcda049,0xe8d9e067,0xdb09c105}}, // pacc, núsa, _đệ_, nieß,
+ {{0x61e40006,0x645d5ec4,0x765d40ae,0x63ad0df7}}, // _giil, _prsi, _prsy, _ujan,
+ {{0xe1f040d0,0x290f93a8,0x61e3ee39,0x68e76071}}, // [1e50] نسل_, vaga_, _yinl, _pgjd,
+ {{0x67f361d7,0x63adc0f5,0x24840042,0x25bf808b}}, // _aħja, čani, ømme_, _skul_,
+ {{0x6da404ad,0x7d0d0d3d,0x63a9dec5,0xed59c29c}}, // rđav, _keas, ynen, lože_,
+ {{0x3947a31d,0xb5fb4326,0xbd05e1e9,0x637cc057}}, // wens_, _prád, _afẹ́, _méns,
+ {{0x394940ef,0x7d0d1ec6,0x2b47bec7,0x645d4c9d}}, // meas_, _meas, tenc_, _ursi,
+ {{0x752d5ec8,0x35cfe028,0x764d5ec9,0x7df3e12a}}, // lgaz, _सीढ़, mway, _lăsa,
+ {{0x6d48feca,0x3a2948a4,0x63a9c03a,0x6010a4c7}}, // deda, luap_, tnen, _rómá,
+ {{0xb5fb413a,0x0595a050,0x62359ecb,0x69c72098}}, // _trád, _کارگ, _небу, hlje,
+ {{0x61e3e271,0x764d5ca0,0x60b5a0e0,0x799c60fd}}, // _pinl, nway, _کمائ, _ymrw,
+ {{0x7bc72ee6,0x291138d1,0x6b844108,0xd83ba4a7}}, // jlju, jaza_, zkig, _вэд_,
+ {{0x6f0d1a38,0x63a9cb23,0xad9b60f9,0x644d5ecc}}, // _beac, pnen, _awúk, hwai,
+ {{0x661520c2,0x28dfc70a,0xdb18a066,0xaadfceda}}, // dszk, _पृथि, vové, _पृथक,
+ {{0xa2bfa028,0x61e401dd,0x673bc143,0x29113ecd}}, // _लुत्, _viil, mfuj, faza_,
+ {{0x290c3320,0x6d48fece,0x69c7264e,0x27fa207d}}, // úda_, ceda, glje, _ptpn_,
+ {{0x27ffc827,0x660994b4,0x91fca018,0xa96a40de}}, // lpun_, _lwek, _grād, бива_,
+ {{0x63a1fecf,0xe81d2021,0x2bcfe4ba,0x33f1ac7c}}, // liln, _फिरा_, _सीता, _máx_,
+ {{0x973ce0e4,0x2903355f,0x764d5ed0,0x0d8667e9}}, // [1e60] siće, bbja_, gway, план,
+ {{0x6b844241,0x7778e022,0x63ab807d,0xe617484f}}, // skig, _xovx, engn, яду_,
+ {{0x6ac6613a,0x24805ed1,0xb77b2095,0x877b2095}}, // أقام, syim_, _האיש, _האיי,
+ {{0x7e612a2f,0x7c29c09a,0x7dc5c057,0x7d0160ba}}, // ælpe, kuer, bósi, _pârâ,
+ {{0x7dc5c005,0x4444240f,0xb6e38627,0xad9b2071}}, // cósi, _hp_, нюшк, spúe,
+ {{0x63ab8058,0x69c1a037,0xdb0d4069,0x69d99ed2}}, // angn, _ckle, llað, amwe,
+ {{0x290cbed3,0x61fb8f74,0x7f49c05d,0x04436f84}}, // _veda_, _etul, feeq, _перн,
+ {{0xe9448062,0x69c08013,0x68e981f6,0x6d48fed4}}, // _ترسی, _ukme, _gged, weda,
+ {{0xa5bb2459,0x63a0ded5,0xb5fb4019,0xaa58c075}}, // drón, rimn, _tráe, ципу_,
+ {{0x3f8681ce,0x35f4e2f0,0x41be67af,0x53be6bde}}, // lkou_, мпор, ्तिस, ्तिश,
+ {{0x6d4aa167,0xa3be498a,0x6e2446fa,0x6ff3e0ba}}, // mefa, ेता_, hrib, _păca,
+ {{0xa3d367f4,0x60c5204e,0x6e24557c,0x9f44a5df}}, // _हीन_, _ryhm, krib, _humà_,
+ {{0x764d474d,0x29112631,0x61ee781c,0x6e244007}}, // xway, taza_, _lubl, jrib,
+ {{0xdb153ed6,0xed59c35d,0x629ca0c4,0x27e6c090}}, // pozí, rože_, _þros, _hion_,
+ {{0xb5fb4ae6,0x6d561ed7,0x29033709,0x637f000e}}, // _orác, _faya, rbja_, _bënt,
+ {{0x63a28167,0x291122ad,0x61ed1ed8,0x83fca012}}, // jion, saza_, _tual, _grđe,
+ {{0x7bc7250e,0x92bf207c,0xb606a3ce,0x6b8d1ed9}}, // [1e70] plju, েশে_, lošć, _ilag,
+ {{0x61ee6066,0x6fc5c0c2,0x60c52052,0x04fe81a6}}, // _bubl, róci, _tyhm, ুলোর_,
+ {{0x6d556c9d,0x7f556019,0x6d408037,0x63a29eda}}, // _vaza, _vazq, _bbma, fion,
+ {{0x6d97c12a,0xa49b458f,0x27e6c534,0x49052026}}, // nţat, _beòt, _nion_, वरको_,
+ {{0xdd95a0fb,0xa3c0c1b0,0x44442126,0xddeb41ac}}, // мады, ंकन_, _zp_, _درجه_,
+ {{0x7bc2dedb,0x6d4abedc,0x61e64108,0x25a203de}}, // _akou, gefa, _zikl, zikl_,
+ {{0xfd10a085,0x26c6c939,0x4424c1f6,0x657aabc6}}, // اجه_, _ayoo_, erm_, _doth,
+ {{0x2d8ca121,0x27fc606f,0x9f4360d1,0x7d1b9edd}}, // _elde_, ívne_, _sijè_, _udus,
+ {{0x6d561ede,0x26d16533,0x443900f7,0x66098167}}, // _raya, _hzzo_, jts_, _uwek,
+ {{0x443906bf,0x645bdedf,0x6281fd94,0xac85e627}}, // dts_, rvui, tylo, дгол,
+ {{0x3ce6406f,0x290ee121,0x3dc944ec,0x394a098c}}, // ľovi_, _defa_, klaw_, озно_,
+ {{0x6d599ee0,0x798d1ee1,0xa96a01e3,0xe8f70376}}, // ndwa, _claw, цима_, млю_,
+ {{0xa5bb2dc1,0x63ad5ee2,0x44391ee3,0x644521fb}}, // rrón, dnan, gts_, _aphi,
+ {{0x61e65dd9,0x6d560ed1,0x63a3a451,0x291249c8}}, // _sikl, _waya, ninn, waya_,
+ {{0x04b522a3,0x63ad4069,0x29136054,0x7643e037}}, // естя, fnan, daxa_, _upny,
+ {{0x2fc95655,0x27e7fee4,0x6f1d40a2,0x7bdbc052}}, // glag_, _hinn_, _cdsc, nmuu,
+ {{0x98a3473f,0x61e65ee5,0x61ef404d,0x7c2b8361}}, // [1e80] вите, _vikl, _cucl, jugr,
+ {{0x63ad5ee6,0x290fdee7,0x776980fc,0xef1a65d0}}, // anan, _lega_, _anex, іме_,
+ {{0x6c4a650f,0x51f6001c,0x92650050,0x7bc9dee8}}, // تلاف_, _عسکر, _قدیم, kleu,
+ {{0xed59c143,0xd83e607f,0x5aca801f,0x7e695ee9}}, // jdž_, ščob_, олем_, _šepe,
+ {{0x657abeea,0x7bc9c1cd,0xeb9740ba,0x69c3f48f}}, // _poth, dleu, _ция_, _okne,
+ {{0x98beef76,0x20186939,0xaa58a878,0x6d408037}}, // letą_, _tvri_, зику_, _wbma,
+ {{0x63a28064,0x6da68e64,0xa5bb2005,0xcf576557}}, // pion, _жива, dról, _קבלת_,
+ {{0xb50a2260,0x7c372106,0xdb18a1ae,0xd378a20d}}, // वरुप_, _çarş, moví, _mići_,
+ {{0x7c38a171,0x6da3862b,0xa5bb2009,0xb906c037}}, // stvr, _рита, rtóz, _यः_,
+ {{0xf698e39d,0x80db407c,0x6568a050,0x44269eeb}}, // звој_, যুক্, _undh, mro_,
+ {{0x2b58689d,0x25bfc653,0x39587eec,0x9f448bf9}}, // _marc_, boul_, _mars_, _aimé_,
+ {{0x69d60157,0x6e95103a,0x63a45aad,0x8354c0eb}}, // _chye, еиму, jiin, _سپتا,
+ {{0x6d40206a,0x2905feed,0xb50a21b0,0x2918e13a}}, // đman, bbla_, वरूप_, óras_,
+ {{0x63ad40c2,0x5f0101a6,0xb606a6ca,0xe8d94079}}, // wnan, ্লেখ_, pošć, nkụ_,
+ {{0x04c92049,0xdb00c22e,0xd37b40c0,0x7c260155}}, // سوري_, rnmæ, оча_, arkr,
+ {{0xb4af2010,0x9df940de,0xea002067,0x69c0deee}}, // _कशी_, знат_, _giật_, nome,
+ {{0x6f0f41e1,0x7aed1eef,0x4fd56aa5,0x7bdd0262}}, // [1e90] _vecc, _igat, ежат, mmsu,
+ {{0x6493203b,0x660d1ef0,0x2246c07d,0x6569806f}}, // ežiū, _iwak, _mpok_, _sneh,
+ {{0x660d0187,0xc7b9029c,0x7d1523ed,0x644521fb}}, // _hwak, suđu_, mazs, _uphi,
+ {{0x6e2b8114,0xa49b458f,0x7c2b9ef1,0x63a4535d}}, // tugb, _deòr, tugr, ciin,
+ {{0x77698067,0xa969b512,0x39587ef2,0x7aed01e2}}, // _vnex, зила_, _fars_, _mgat,
+ {{0x777c68ad,0x7c2b8c44,0x2055a6bd,0xac1970ac}}, // _gorx, rugr, етир, _ногу_,
+ {{0x199587b9,0x24899ef3,0x27e7e569,0xdb0e3ef4}}, // _завя, šam_, _rinn_, tobú,
+ {{0x656ae153,0x7dc5c069,0x9f45a089,0xc867814a}}, // _anfh, jóst, _milé_, _отзи,
+ {{0x98a0416d,0xdb01e78d,0x25bfc0ba,0x6fd60e1c}}, // rgić_, lilé, roul_, náce,
+ {{0x96b9ecde,0x25addef5,0xb176e067,0x25bfc3ac}}, // зуму_, snel_, _phượ, soul_,
+ {{0xf8b1e01c,0x2246def6,0x28a56077,0x2d894037}}, // دکش_, _epok_, _गेमि, kkae_,
+ {{0x660d0bb1,0x27e9081f,0x3f8ee167,0x27e7fef7}}, // _bwak, _cian_, _elfu_, _winn_,
+ {{0x7c2d4cb3,0x8c45d391,0x4439400e,0x80c8007c}}, // juar, _челе, _nqs_, রেন্,
+ {{0xc6928087,0xb176e016,0xdce4006e,0x60c98426}}, // ראך_, _thượ, _unič, _myem,
+ {{0x69d6000e,0x63a45ef8,0xf3f1a125,0x2b594017}}, // _thye, tiin, _jụ_, _basc_,
+ {{0x2b594017,0x69c41ef9,0x7bc520bb,0x9f45a1e9}}, // _casc_, _ukie, _ekhu, _dilé_,
+ {{0x809f8aa1,0x657d48c1,0x442ddefa,0x656bc058}}, // [1ea0] _खेले, _dosh, nue_, _mngh,
+ {{0x63a56058,0x777c603c,0x3860c6a6,0x6fb6613a}}, // gihn, _qorx, _širk_, رمضا,
+ {{0x661b9efb,0x29117efc,0x657d400e,0x660d0171}}, // _avuk, _deza_, _fosh, _zwak,
+ {{0xaca3c088,0x442ddefd,0xc05a8056,0x5efda028}}, // _chịk, kue_, зік_, रुस्_,
+ {{0x6493203b,0x6376e1fc,0x63a56622,0x3d052010}}, // ržiū, _vânz, bihn, वरचे_,
+ {{0x2906832a,0x7e64c00a,0xa49b5efe,0x2d8fdeff}}, // sboa_, _šipo, _teòr, _alge_,
+ {{0xd04e803c,0x6d5c3f00,0x83fca20d,0x3cf0a046}}, // _ədəb, idra, _brđa, _चलीं_,
+ {{0x442ce9ee,0x7c3bdf01,0x09e381cc,0x63a081e7}}, // vud_, ftur, _босн, _ummn,
+ {{0x25a5ff02,0x63a60058,0x442cea25,0x27e90e88}}, // fill_, dikn, wud_, _rian_,
+ {{0x656bc1cd,0x25f40029,0x7d164054,0x38c8a555}}, // _engh, ंदनी_, hays, _قاضی_,
+ {{0x661b81d8,0xbcfb00e0,0xdd95003b,0x27e91f03}}, // _zvuk, ndég, ваны, _pian_,
+ {{0x27e90546,0x9f97400e,0x67108ba0,0x6d58f4d5}}, // _qian_, rçë_, ारिक_, _wava,
+ {{0x2fc6c1e2,0x62856037,0xb5fd8012,0x5fd2210a}}, // _ikog_, zyho, _krša, _दीहल,
+ {{0xb5fb4261,0xca57c087,0x26c90167,0xdcf8f019}}, // _brán, _ניוז_, _vyao_, _kovč,
+ {{0xf36743d5,0x8624209e,0x656bc0e2,0x6f153f04}}, // нтан, льте, _yngh, razc,
+ {{0x660d02ad,0x7e641f05,0x6d59c025,0xf215c118}}, // _twak, _frip, _xawa, _दौड़_,
+ {{0xe89441ef,0x161d2485,0x932760eb,0x9f47e061}}, // [1eb0] гать, _फिकर_, فران, _binè_,
+ {{0xb7bde1fc,0x39594037,0x7bc520bb,0xd6db61fc}}, // tuţi, _wass_, _ukhu, пте_,
+ {{0x6d452174,0x60c98644,0xa3c0cba0,0xb5fb4317}}, // _ibha, _syem, ंका_, _grán,
+ {{0x2d8b0026,0xdb03bf06,0x236ca143,0xa91d806e}}, // nkce_, miné, _andj_, _možg,
+ {{0xdce9c579,0xd1164095,0xe10c000b,0x29094167}}, // mjeć, _בקרה_, פּאָ, mbaa_,
+ {{0x60c98167,0xbea5ab92,0xa49b40f9,0xa3cfe0c2}}, // _vyem, калк, _afòg, _वीं_,
+ {{0x94d465a8,0xead46052,0x1fb5c318,0xf4ffe0f9}}, // _борц, _борь, ксир, _dẹlé_,
+ {{0x60c981e7,0x7e7ea262,0x224906d6,0x6d5aa2a5}}, // _tyem, äppa, _opak_, _eata,
+ {{0x6d5aa57b,0x2489a041,0x6d4e3f07,0x7989df08}}, // _fata, ņam_, yeba, rkew,
+ {{0x7bcd59d3,0x6d440024,0x98bee0c2,0x3d27c0f9}}, // hlau, _sbia, letę_, _bìwà_,
+ {{0x7c3bdc62,0x2000012a,0x442dc095,0x394ddf09}}, // stur, _stii_, sue_, pees_,
+ {{0x8fa602a3,0x7d164054,0x7dcda06f,0x7c3bc8cd}}, // таме, xays, kúsk, ptur,
+ {{0xdb18ae97,0x5576800b,0x442f98eb,0xdce9c40c}}, // lová, דערן_, nug_, djeć,
+ {{0x63a2dee9,0x6d5aa025,0x61eaf412,0x62861f0a}}, // _zmon, _xata, _gifl, ryko,
+ {{0x711ae0be,0x9f4a82a8,0xf992e076,0x628606b5}}, // _חוצפ, _subí_, _דרג_, syko,
+ {{0x69d8e00e,0xdd924938,0xf8660844,0x7e640e96}}, // _zhve, جور_, _авио, _trip,
+ {{0x6d5c2a6f,0x2bf64c10,0xd70621e1,0x39405f0b}}, // [1ec0] pdra, тябр, _изпи, rfis_,
+ {{0xdb008004,0x442f9f0c,0x63a72171,0xdfcf6243}}, // _immé, dug_, cijn, قيق_,
+ {{0x9f47e0fa,0xa3c2f706,0x317f8030,0x9f4de2a8}}, // _ciné_, ंवत_, _bouz_, lpeó_,
+ {{0xa3d20738,0xdcf8e361,0xf1bf204a,0x66044d6e}}, // वति_, _rovč, hrál_, ppik,
+ {{0xbad541d3,0x63a2c1f6,0x63a41f0d,0x29183f0e}}, // _біры, _rmon, _mmin, oara_,
+ {{0x4a9b0053,0xce6aa103,0x6448a012,0x661c6320}}, // וינג, дрид_, _ppdi, _tvrk,
+ {{0xe5a37196,0x63a41f0f,0xd7f8a016,0xa2ce8033}}, // _тири, _omin, _trăm_, _तरक्,
+ {{0x3869c1e4,0x6d4f0017,0x63a72171,0x3d0a6010}}, // _šara_, xeca, zijn, िरके_,
+ {{0x6d41ff10,0x661bcbae,0xd378a320,0x69d9df11}}, // ffla, gsuk, _siću_, _dhwe,
+ {{0xceb4003c,0xb5fd8012,0x27e05f12,0x69d9d72a}}, // _edək_, _kršn, hmin_, _ehwe,
+ {{0xf8bf4162,0xa2e6a491,0xdb18a1ce,0x63a4005d}}, // _idée_, _рожд, cová, _bmin,
+ {{0x6448012a,0x7c3d094b,0x78a4c552,0xa183c052}}, // ădin, ttsr, _živu, _вышл,
+ {{0x2bb380c5,0x81d8407c,0x6e29c005,0x6aa20143}}, // ंचता, াদা_, creb, _žofr,
+ {{0x7c3e69ee,0xc4d30148,0x601883f6,0x79808614}}, // ktpr, _सरीख, _zúmá, _lomw,
+ {{0x28e04077,0xdb1d027f,0x2d801890,0xc31381a6}}, // पेसि, losí, _foie_, _হাসি_,
+ {{0x7d18a042,0xa8064106,0x7bce3f13,0x33f8a071}}, // havs, ktığ, albu, _méx_,
+ {{0x7f5c6bee,0xdb03a13a,0xb87b40f9,0x17ed4046}}, // [1ed0] _barq, siné, _agír, जदेव_,
+ {{0x1755214a,0x63a8e098,0xad9b61e9,0x6d453f14}}, // _своя, nidn, _awúr, _ubha,
+ {{0xa91d816d,0x80c9c026,0x2619c573,0x3cde6843}}, // _lože, _हुने, _मौनी_, केले_,
+ {{0x9f81c069,0xb8eb6028,0x443ee31b,0x7522c088}}, // jóð_, _लड_, itt_, _ndoz,
+ {{0xef18e041,0x4429417a,0x656f4baa,0xf4d1807c}}, // kaļ_, qra_, _inch, িশ্ব,
+ {{0x442b0420,0x28c6c10a,0x764ae065,0x237f80d1}}, // irc_, _लड़ि, _opfy, _wouj_,
+ {{0x290b0037,0x24894009,0x798080a2,0x27eca095}}, // kbca_, lyam_, _fomw, _didn_,
+ {{0xf8d30292,0x236d8037,0x1db7917d,0x60cd01b6}}, // _सरूप, _unej_, _अदित, _oyam,
+ {{0x291906dc,0xab5b0105,0x69db8088,0x69c981e7}}, // dasa_, hlüs, _ihue, _ikee,
+ {{0x7522d094,0x2fcddf15,0x6009e04e,0xdb0d00c4}}, // _edoz, sleg_, нном_, _skað,
+ {{0x26cd8037,0x44313f16,0xc984a04d,0x5b25c13a}}, // _hyeo_, juz_, _тути, _مفضل,
+ {{0xc105c049,0x24894194,0x7c29df17,0x6f1980c2}}, // _صوتي, kyam_, prer, nawc,
+ {{0x661d04a6,0xd945e501,0x27ed9f18,0x1545efdb}}, // gssk, _бели, _mien_, _белм,
+ {{0x6d5d4761,0x2005e1f6,0x291901e7,0x2d8dd662}}, // _aasa, ppli_, aasa_, lkee_,
+ {{0x69c98af8,0x2258e065,0xb184204a,0x6288e0c2}}, // _okee, ærkt_, šťov, cydo,
+ {{0x7f5d4004,0xf1bb0219,0x6f18a29c,0x5de64e12}}, // _casq, _उदयन, zavc, ужба,
+ {{0xb5fb00e0,0x629bc618,0x7db6268c,0x3a3fc907}}, // [1ee0] lván, nzuo, _مصدا, ntup_,
+ {{0x69c5600e,0xdcfaa2d1,0x69df0197,0xe29a62fe}}, // gohe, _potč, _èper, наз_,
+ {{0x6da6426f,0x6289c07d,0x0c266df6,0x69db8049}}, // _бина, hyeo, умен, _bhue,
+ {{0xeb978878,0x66040058,0x27ed9f19,0x2f5640ff}}, // лих_, _ktik, _cien_, _стос,
+ {{0x69c56483,0x6f160037,0x6d43a17b,0x80cae969}}, // bohe, _meyc, ffna, _सुने,
+ {{0x59d84ef3,0x7d161e1b,0xea002067,0xdcfc6b23}}, // _भीतर, _leys, _ngẫu_, _korč,
+ {{0x44324c96,0x5694a2a3,0x443fdf1a,0x4c94a2a3}}, // luy_, райт, etu_, рийс,
+ {{0x7d0d8069,0x3ced0552,0x7df3e12a,0x224053c8}}, // ðast, ževi_, _găsi, otik_,
+ {{0x2fc5e105,0x7bdc74f4,0xf8b440c2,0x69c609e6}}, // folg_, _khru, ंपिय, doke,
+ {{0x2fc68374,0x7e6dc480,0xaa93813a,0x63a9cd7e}}, // loog_, _šapc, _الكث, aien,
+ {{0xf363e1ba,0x63a8ff1b,0x58d4f3f9,0x973ce274}}, // стын, ridn, ронт, lićk,
+ {{0x7dd600e0,0x6d5e3f1c,0x16200028,0x3946c0f2}}, // tása, _aapa, _बिखर_, _ubos_,
+ {{0x1439c050,0x7bc56167,0x443fdf1d,0x69dc7f1e}}, // _لينک_, yohu, ctu_, _ohre,
+ {{0xf99f65cd,0xf237c1a9,0x61ee6057,0x1a9bc00b}}, // _jwèt_, _ערוץ_, _nibl, _אידע,
+ {{0xfbd341ac,0xee3f01ce,0x395dc022,0xe91940ff}}, // متر_, stý_, _yaws_, кові_,
+ {{0x395dc05f,0x69c61f1f,0x67240320,0xa1585043}}, // _xaws_, coke, _gdij, ласу_,
+ {{0x248d2041,0xdb08e1ae,0x2d82435f,0x6560c049}}, // [1ef0] ņem_, vidê, _doke_, idmh,
+ {{0x69dc7f20,0x63a9df21,0x6d5e3f22,0x69c9977b}}, // _chre, zien, _gapa, _skee,
+ {{0x248948a7,0x2d82407d,0x2cae403c,0x7bdc6256}}, // syam_, _foke_, _əldə_, _dhru,
+ {{0x7a474041,0x201eeaf1,0x2eb0810a,0x2837e00b}}, // tītā, isti_, जपूत, רנאך_,
+ {{0xe6d30029,0x69dc66a5,0xb5fb005c,0xfe35400b}}, // _सरोज, _fhre, zván, _דאָך_,
+ {{0x69dc628f,0x27ed9f23,0x61e28e27,0x7bdc628f}}, // _ghre, _wien_, amol, _ghru,
+ {{0x32095f24,0x6f1aecb1,0xf53820be,0xa8064121}}, // lpay_, datc, אטור_, rtış,
+ {{0x0c25cb67,0x637cc0e0,0xf625c520,0xd3468050}}, // амин, _kény, адио, _نیمه_,
+ {{0x69c73f25,0xd347a050,0xbb84813a,0x7bc72041}}, // koje, _میشه_, _القي, koju,
+ {{0xb4db40f9,0x2007a069,0xd49ba1e3,0x7bc60ee2}}, // _abàk, ppni_, _пре_, woku,
+ {{0x7bc61f26,0x6595df27,0xa3c307d3,0x395dc223}}, // toku, _кажу, ्तं_, _taws_,
+ {{0x6b82df28,0xfc30a50f,0x6fd06156,0x800ae0e0}}, // _goog, _رحم_, täck, کریہ_,
+ {{0x6f0905e0,0x201ee04e,0x395f9f29,0x6d5e3f2a}}, // ñeca, asti_, _maus_, _papa,
+ {{0x2d825f2b,0x765aa0e2,0x6e2d4af1,0xdcfc6098}}, // _soke_, _ysty, drab, _porč,
+ {{0xa3d5846f,0xa3c2ff2c,0x443e2004,0x6b83e8fc}}, // हता_, ंवर_, ît_, _iong,
+ {{0x7bdc7f2d,0x395f9f2e,0x7d1bda3c,0x3869017a}}, // _shru, _naus_, naus, _frar_,
+ {{0x637cc043,0x61ee65cd,0x443f8022,0x2360576e}}, // [1f00] _pénz, _pibl, _nqu_, rdij_,
+ {{0x2d825f2f,0xdb08ff30,0x31601f31,0x645a20c4}}, // _woke_, vidë, _haiz_, ætis,
+ {{0x7d1bdf32,0xf3c961ac,0x7d0d5f33,0xed576b7a}}, // kaus, ابقه_, bbas, рох_,
+ {{0xdb1c6277,0x614692d5,0x395f9f34,0x6b83edee}}, // _skrä, реда, _caus_, _long,
+ {{0x06e4407c,0x7d1aee05,0x20568197,0x31601f35}}, // _ফ্রি, yats, ртер, _maiz_,
+ {{0x6b83f09d,0xdb08e5df,0x6b9601cd,0x0eba2028}}, // _nong, cidè, _llyg, _उखाड,
+ {{0x6609dbc3,0x6b9600fd,0x2eaed9a7,0xd017a772}}, // epek, _olyg, ञप्त, афы_,
+ {{0x31600265,0xdb08ff36,0x9f45c057,0x4422512c}}, // _naiz_, midé, _zulú_, _kvk_,
+ {{0x9f4ca026,0xcb67c55a,0x179b8087,0x4feaa103}}, // _lidé_, рање_, _סימב, _аман_,
+ {{0x3a2048c4,0x23c66a30,0x80f5298a,0xfc4ac031}}, // isip_, वविद, _आलेख_, _abíó_,
+ {{0x244080d1,0x6f0d5b66,0x201ef446,0x395f8286}}, // lòm_, ybac, usti_, _yaus_,
+ {{0x80bfaaa1,0xa91d816d,0x0446c4a9,0x44225f37}}, // _लुटे, _koža, _ведн, _ovk_,
+ {{0xda198064,0x37cc407c,0xfaff000e,0x23c66077}}, // नीपत_, লগার, rcë_, ववाद,
+ {{0x65608515,0x29024054,0xdb1c23e4,0x44205f38}}, // _lamh, _afka_, torá, dsi_,
+ {{0x69c042d1,0x44205f39,0x7bc72f68,0x6441e62c}}, // čmen, esi_, roju, ctli,
+ {{0x3f837f3a,0x637da5cd,0x65609480,0x8cddc5aa}}, // _soju_, _dèny, _namh, _परमो,
+ {{0x7c2d5f3b,0xdce6403b,0x395f9f3c,0x6f172071}}, // [1f10] urar, _mokė, _raus_, _texc,
+ {{0x973ce29c,0x3866864e,0x7d1d1f3d,0x753b812a}}, // fići, dvor_, lass, _scuz,
+ {{0x3f8480ce,0xfbd040e0,0x7d0d462c,0x7d18e106}}, // _bomu_, ستہ_, pbas, _mevs,
+ {{0xdb09c200,0xf6502043,0x4422417b,0x395f8022}}, // rieë, _کئی_, _fvk_, _qaus_,
+ {{0x645a21d7,0x69c8f84e,0x29187629,0xe8200028}}, // ħtie, kode, _fera_, _बिछा_,
+ {{0xdb09c3c5,0x973ce20d,0x69c8e071,0x395f80a2}}, // pieë, bići, jode, _waus_,
+ {{0x7dcda7a1,0xe8df6081,0x395f8286,0x69c8ff3e}}, // dúst, _trực_, _taus_, dode,
+ {{0xa3d7466f,0x6f1d069d,0xdb00c3e4,0x23bac041}}, // िति_, jasc, timá, tīja_,
+ {{0x7d1bd3e0,0x291d9f3f,0x6f1c235f,0x31600e06}}, // raus, mawa_, yarc, _raiz_,
+ {{0x7d1c28ad,0x6b9ae163,0x69c8ff40,0x614187fc}}, // xars, ghtg, gode, báló,
+ {{0xe6b3a555,0x6b83f126,0xd3e6a062,0x645d4071}}, // _بلاگ, _vong, _اقلی, _lssi,
+ {{0x68ed805c,0x645d4e54,0x568ca00b,0x6b83e173}}, // žadu, _ossi, _שטאַ, _wong,
+ {{0xe8d9e016,0x6561a04a,0x7643a07d,0x69c12066}}, // _đổ_, _kalh, ltny, člen,
+ {{0xdcfe2055,0x7dcda019,0x237fc07f,0x9103214a}}, // _topč, cúst, ljuj_, зпре,
+ {{0x6561b629,0xe0d1eca7,0xdcfaa12a,0x6f1d0c48}}, // _malh, _عزت_, _hotă, basc,
+ {{0x29194510,0xa3c9c05e,0x969603b7,0x2d85a0f9}}, // _cesa_, ोति_, _грош, _lole_,
+ {{0xa9674f4a,0x61e44fc3,0xa3ba2050,0xcaf6613a}}, // [1f20] щита_, rmil, _مادر_, مساب,
+ {{0x6fdd02b2,0x2d85a6be,0x3a204939,0x8af0403c}}, // réce, _nole_, rsip_, ddət,
+ {{0x291d8068,0x799bc05d,0x644f4071,0x291940e8}}, // fawa_, khuw, _epci, _fesa_,
+ {{0x7bc8ff41,0x7d19c40f,0x38cb8062,0xbddb3f42}}, // zodu, _news, گامی_, ntèn,
+ {{0x29187f43,0x260c610a,0x653b000b,0xf3f1a119}}, // _tera_, डीजी_, רענד, _bị_,
+ {{0xdb1e6057,0x6d4bd047,0x6d565f44,0x9b966049}}, // bopá, _abga, leya, _الست,
+ {{0x60db8096,0x7d1d15a6,0x442f8256,0x6443a065}}, // _ezum, yass, arg_, gtni,
+ {{0xb5fb413a,0x77645f45,0x7c24006e,0xd90dc6b0}}, // _srái, ndix, _ovir, نیم_,
+ {{0x3f85a098,0xd6d0c13a,0xf8dc8046,0x55774053}}, // _folu_, بقة_, _बरिय, מעקן_,
+ {{0x2d85bf46,0x25a906bf,0x628e203b,0xf3f1a079}}, // _gole_, _smal_, kybo, _gị_,
+ {{0x26c00037,0x6f0900b6,0x6fd886c7,0xcb136008}}, // _axio_, ñeco, níco, הלת_,
+ {{0xc6936053,0xdb1aa3d1,0x69dac049,0x798641a5}}, // פאר_, _aktø, _صباح_, _nokw,
+ {{0x7c22c098,0xb901037f,0x6d565f47,0x6e2282e0}}, // _uvor, _थर_, deya, nsob,
+ {{0x61fabf48,0x5694f521,0xd010e4c7,0xa91d9f49}}, // _kutl, _малт, _قلت_, _nožn,
+ {{0x6f029f4a,0x7afdc066,0xdb0aa1ab,0x6f1d1f4b}}, // kcoc, žste, rifè, pasc,
+ {{0x291a20f9,0x6d48ff4c,0x0615454a,0x27e007ad}}, // _eepa_, lfda, ждаю, _ghin_,
+ {{0x7d0280ae,0xdced01f6,0xdb00c052,0x2919515e}}, // [1f30] dcos, _flaġ, mimä, _vesa_,
+ {{0x63bb8277,0x628f0011,0x753aff4d,0x44236037}}, // _sjun, myco, ngtz, _pvj_,
+ {{0x9f842046,0x64445f4e,0xdb01e22e,0x1bf24148}}, // töö_, atii, bilæ, _आंचल_,
+ {{0x398ba654,0x69d520cb,0x555520e0,0x333ea229}}, // _løst_, llze, _اپگر, _actx_,
+ {{0x6561a1ae,0xb5fd849f,0x6444412a,0xaacfe914}}, // _palh, _krši, ctii, _सुनक,
+ {{0x7761a5fc,0x2409ad59,0xdb1560ca,0xbddb60d5}}, // _qalx, анки_, _ekzò, _epèr,
+ {{0xeae5c07c,0x4999c8ea,0xb5fd8299,0xcfe9813a}}, // _নভেম, ития_, _mrši, _نفسه_,
+ {{0x91f90029,0xdb1c21ae,0x644578bf,0xc005c4a7}}, // ंदाज_, porç, mthi, опик,
+ {{0x6561ac9d,0xb17b0584,0x7643bf4f,0x6b9bdf50}}, // _talh, rmål, stny, rhug,
+ {{0x7d1abf51,0x6f1b926c,0x6d8b203c,0x7643a037}}, // _gets, _heuc, _müas, ptny,
+ {{0x7d1b87e7,0x764561cd,0xb5fb41ae,0x7dd069f6}}, // _keus, nthy, _gráv, mäst,
+ {{0xa2aac010,0x660d5f52,0x6d576025,0x98b36013}}, // _जेव्, kpak, hexa, ždės_,
+ {{0xa2d0c077,0x6f1b832a,0x44220013,0x44447f53}}, // _डुप्, _meuc, usk_, zt_,
+ {{0x6f029f54,0x200dd77e,0x7d1b81b9,0x6d4d097e}}, // zcoc, mpei_, _leus, _ebaa,
+ {{0x6d9cc13a,0x2b4d8326,0xbc19e488,0x394d805d}}, // _féad, _obec_, _тілі_, _obes_,
+ {{0x7c24003b,0x6d4d11b0,0x7c228707,0xdcfbc0a9}}, // _tvir, _gbaa, ysor, kjuč,
+ {{0x2d87ff55,0x6563e1e2,0xdb03a04a,0x05aa7f56}}, // [1f40] _kone_, _kanh, ziná, авай_,
+ {{0x394d805d,0x38601f57,0x5bd28c4c,0x6d5657c2}}, // _abes_, _isir_, तत्व, seya,
+ {{0x7d1b8153,0x7bcb8912,0x66098f60,0x77640108}}, // _beus, fogu, _atek, _kaix,
+ {{0x6563f7bd,0x7d1b858f,0x6f1b8733,0xa96a42af}}, // _lanh, _ceus, _ceuc, _тима_,
+ {{0xb51e2033,0x6f1b903c,0x7bcaa475,0x7c838052}}, // _बजाय_, _deuc, tofu, дуще,
+ {{0x64a66056,0xf806abdb,0x291aa6db,0x61fb8054}}, // _дага, очин, úpa_, _duul,
+ {{0x66098916,0x39583308,0x7f49ce39,0xe9df207b}}, // _etek, iers_, ffeq, ntúa_,
+ {{0xdb03a942,0x776405df,0x207b6053,0x65641655}}, // riná, _naix, _דאלא, _naih,
+ {{0xef1f03df,0x395828c3,0xe2978d94,0x66094106}}, // kkür_, kers_, _дат_, _çekt,
+ {{0x7dd069f6,0x212904b4,0x6563f955,0x6729948f}}, // mäss, _sdah_, _canh, _zdej,
+ {{0x7dd07f58,0xb7db600b,0x22468eac,0xe4fb600b}}, // läss, שקיי, ltok_, יפיש,
+ {{0x6b8294b6,0xf1bf2026,0x9f5200d1,0x37ab045c}}, // njog, hrát_, _jiyè_, ртон_,
+ {{0x7c38a4ad,0x80cae180,0xdce9c579,0x39583b1d}}, // luvr, _सुरे, bjeđ, fers_,
+ {{0x291eff59,0x7c245f5a,0x4424c071,0x645c21cd}}, // qata_, dsir, msm_, fwri,
+ {{0x2b91c067,0x5f950052,0x769101e9,0x291ceaf1}}, // _bách_, _никт, _bóyú, _ieva_,
+ {{0x77640265,0x6d58a134,0x7dcfe271,0x25db20c2}}, // _gaix, heva, søst, _खीरी_,
+ {{0x6d577f5b,0x69cb9f5c,0x518724d4,0xd50b6396}}, // [1f50] texa, voge, _дуна, لغان_,
+ {{0x78a2806f,0x26c12022,0x6564005d,0x80ddce74}}, // dzov, _txho_, _zaih, _परसे,
+ {{0xdb060de2,0xc8d23f5d,0x7dd0604e,0xe9df207b}}, // liká, _दुपट, väst, ctúa_,
+ {{0x443247ac,0x6f044024,0x4424c05d,0x6d4e600d}}, // bry_, ccic, ksm_, _ebba,
+ {{0xea008016,0xdb061f5e,0x224688b2,0xee38819d}}, // _đảm_, niká, gtok_, жні_,
+ {{0x4e164095,0x6b9d000e,0x57f46052,0x29dc8057}}, // _מחשב_, ërgj, _опят, ríaa_,
+ {{0x4426d9ae,0x2d9eff5f,0xdb0d41de,0x98bf8012}}, // _avo_, dhte_, gnaç, _obuć_,
+ {{0x6563e4ec,0xe619c7c1,0x7d1c6105,0x63ad008b}}, // _sanh, бди_, _zers, _nman,
+ {{0x6f1d521e,0x3964a04e,0xa2cd2b5b,0x753bc1df}}, // _mesc, össä_, _दुस्, rguz,
+ {{0xdb0603e4,0xdb1640e0,0x10a5f77e,0xdbd702df}}, // diká, rnyé, зион, hääl,
+ {{0x39582dee,0x5fbd591c,0x2ef5cd9a,0x9ad36079}}, // vers_, ्काल, _езер, _dịgo,
+ {{0x9f589f60,0x6ee10088,0x8c4960b0,0x8c4886fa}}, // _puré_, _ịban, maşı, bağı,
+ {{0xe72eee14,0x63ad1f61,0x8c4963df,0x644baa14}}, // _пе_, _dman, laşı, žnič,
+ {{0xdca5e491,0xaca3e096,0x39583f62,0x656401b4}}, // _нали, _ahụi, uers_, _waih,
+ {{0x6d59835f,0xe29739b5,0x09e642d8,0x251ae1a9}}, // kewa, пар_, зовн, _הוצא,
+ {{0x63ad0af2,0x44325f63,0x3a260e37,0xb883804a}}, // _gman, try_, _емиг, _klíč,
+ {{0x291dc031,0x88d1a07c,0x395838ab,0xa3c18d01}}, // [1f60] _mewa_, াধিক, pers_, ुवन_,
+ {{0x7521e576,0x44443f64,0x7dd06052,0x6fd6006f}}, // nalz, _tq_, väss, dáci,
+ {{0xc332e095,0x27e94c8a,0x7d1d5f65,0x6f044098}}, // תוב_, bman_, _fess, pcic,
+ {{0x69ce22e8,0x7d1e2121,0xe73a968a,0x200763b8}}, // lobe, _heps, сед_, ínia_,
+ {{0x63bbdab3,0x65652939,0x2907ff66,0x9f5fe1de}}, // dnun, _yahh, _ofna_, rquê_,
+ {{0xae00a7fd,0x61fc7f67,0x39591ab3,0x78a28530}}, // _लंदन_, _turl, zess_, rzov,
+ {{0x9ea7655a,0x9f483239,0x9f44a0e6,0xb5fb49d0}}, // _евра_, óhùn_, _sumó_, _frás,
+ {{0x7d1e3f68,0xa91ca06f,0xb886e057,0x9f5840b6}}, // _leps, _koľk, _laíñ, mpró_,
+ {{0x98b1a041,0x4427faba,0xdca395cb,0xdcfc2041}}, // ībām_, _avn_, _зати, nkrē,
+ {{0xdce08432,0x2b942156,0x6721f241,0x61e2c095}}, // _jamč, _däck_, galj, _whol,
+ {{0xdb0f0057,0xa9350cde,0x6720c00e,0x7bce3f69}}, // licé, менш, pamj, dobu,
+ {{0xdb061f6a,0x27e94d8d,0x61e2d842,0xdb03a1de}}, // tiká, xman_, _uhol, tinç,
+ {{0x25a05f6b,0x6d42c0ba,0x61e41f6c,0x4427ff6d}}, // dhil_, _icoa, _dhil, _evn_,
+ {{0x69ce200d,0x7521e12a,0xa3dd0033,0xdce08098}}, // gobe, calz, _तीर_, _namč,
+ {{0xa2cd3733,0x660966be,0x61fe3f6e,0xbef7e13a}}, // _दुश्, _čeko, _cupl, _عروض_,
+ {{0xf1bf22ee,0x1cbbe095,0xb5fd838a,0xddc420e4}}, // trás_, _למוע, _kršt, _vriš,
+ {{0xfbdca0a3,0xb5fb41e9,0x7d057f6f,0xddc2e04a}}, // [1f70] यताम, _arár, tchs, _spoř,
+ {{0xe7e106a8,0x213ee095,0xfd56e1e9,0xf1bf23cb}}, // _गीता_, ngth_, _angẹ, rrás_,
+ {{0x6f1d5f70,0x2d8a7f71,0x799b8c57,0x660d0167}}, // _tesc, _dobe_, _pluw, _htak,
+ {{0x9cb581ac,0x291ea1e7,0x6d41bf72,0x69d8ad53}}, // ومات, _aeta_, _scla, elve,
+ {{0x6b898200,0x957ce013,0x6b9b8854,0xb5fb4942}}, // _voeg, rmąj, _vlug, _vrás,
+ {{0x6448f2cf,0xb90b20f9,0x2d8af006,0x4425e04a}}, // ktdi, _adiẹ_, öbel_, ysl_,
+ {{0x7d060156,0x246ce04a,0x7e62c082,0x798983c5}}, // ycks, _něm_, _asop, _toew,
+ {{0x96272018,0x8c496315,0x672d1abf,0xaad0e148}}, // _ieņē, raşı, _ndaj, _हड़क,
+ {{0xb5fd864e,0x69ce23cf,0x7bce2bd4,0xf771e25b}}, // _pršu, yobe, yobu, ناد_,
+ {{0x6d5ae8cd,0x6d4d4025,0x9f5320fa,0xbebb4155}}, // geta, hfaa, _fixé_, _geëe,
+ {{0xdb18a05c,0xdb03a845,0x2369407f,0xee36e1fc}}, // nový, rinä, kdaj_, мнэ_,
+ {{0x2905f66b,0xb605d019,0x23694022,0x7bcf0057}}, // scla_, _pešč, jdaj_, gocu,
+ {{0xe9cee1c0,0x69c36e9d,0xa3cacc87,0xdb18a066}}, // _ак_, čnej, लकर_, hový,
+ {{0x6b8bdf73,0x69d98e0d,0x291e3de2,0x22494e1f}}, // _hogg, hlwe, úta_, dtak_,
+ {{0xd5b24049,0x29090071,0x6448e2a4,0x7d098088}}, // نفس_, _ffaa_, ctdi, _ofes,
+ {{0x213fcd3d,0x7c2613f0,0xaca380f9,0xdb18a326}}, // nguh_, pskr, _afọn, dový,
+ {{0xdb08ff74,0x656641e2,0xb9b30049,0x70dc80c2}}, // [1f80] lidá, _vakh, _جميع, _बरेल,
+ {{0x7523bf75,0x60c4a0b0,0x7afb9f76,0x386ddf77}}, // janz, şimi, _agut, iver_,
+ {{0x28afc0a8,0x7c3bd7dc,0x59adc028,0xdb1aa0d1}}, // _जेसि, huur, टफार, _oktò,
+ {{0x69c1a143,0x3f8b4361,0xf1bf3f78,0xa91ca02e}}, // _ajle, _gocu_, nsán_, _toľk,
+ {{0x925a80a4,0x386dc200,0x7769c054,0xdb0e6069}}, // تشار_, jver_, ddex, þjón,
+ {{0x2906841a,0xdce44066,0x2d982009,0x7c3bdf79}}, // scoa_, ndič, nkre_, duur,
+ {{0xeb9a9f7a,0x443a600e,0x291f818c,0x61eab8da}}, // _див_, qup_, _feua_, rmfl,
+ {{0x6f1641cd,0x88bca0a3,0x7afb8614,0x3942c046}}, // nbyc, _oděv, _ggut, üks_,
+ {{0x2616e0c5,0x7dd88071,0xfd652119,0x6e3c27d4}}, // _भौजी_, jísi, _chuồ, hurb,
+ {{0xb33b4121,0x6fd8806f,0x6e3c20c4,0x67244363}}, // _ilçe, díci, kurb, haij,
+ {{0x80d6e07c,0x6b8bcd8f,0x657aa1fb,0x01376076}}, // ডেন্, _fogg, _inth, ורית_,
+ {{0x6600866b,0x20000194,0x6e3640c2,0x14afc04b}}, // _kumk, _buii_, kryb, _जेवण,
+ {{0x16dc82cd,0xc324607c,0x02e2cba0,0x67208058}}, // _बरोब, _পানি_, _परिभ, _memj,
+ {{0xa5bb2069,0xe8dfe067,0xdb08e057,0x2d8cbf7b}}, // msók, _giỏi_, bidá, _jode_,
+ {{0x2d8b4361,0x4bdb0049,0x8afee0e8,0x0463e0fb}}, // _poce_, _وبعد_, _haƙo, _атым,
+ {{0x2b49a3ce,0xdb18a066,0x6f0de490,0x5edd607c}}, // đaci_, vový, _đaci, _বললে,
+ {{0xdb02c1ae,0x27f87f7c,0x443ca048,0x7523a2ad}}, // [1f90] _emoç, _birn_, huv_, yanz,
+ {{0x6568bf7d,0x7f5d07df,0x7c3c2265,0xdce1a098}}, // _nadh, lesq, burr, _salč,
+ {{0x473588ba,0x6e3d1f7e,0xdce1a12d,0xc178e013}}, // енес, lusb, _palč, lmės_,
+ {{0x75208071,0x4427a098,0x386dc46b,0x63b53f7f}}, // _cemz, vsn_, yver_, lizn,
+ {{0x92bba07c,0x6e3d1f80,0xdb03b88a,0xa509b342}}, // _ঘরে_, nusb, minú, пела_,
+ {{0x3edf0088,0xa3ab4e8e,0xfce5a7f7,0x6fd6007b}}, // _arịọ_, कोर_, ноло, láct,
+ {{0x37d6007c,0xa5c5a1e9,0x6568a011,0x2d8cad53}}, // _সংঘর, _akóì, _dadh, _dode_,
+ {{0xa2d624da,0x6568a28f,0x36d580ff,0x8afee0e8}}, // _मुफ्, _eadh, _зобр, _baƙo,
+ {{0xa3df3008,0x2d8ca1ae,0x69c2df81,0x7bc2c653}}, // तता_, _fode_, _djoe, _djou,
+ {{0xdaa9ebd2,0x7dd880c4,0x61ed5f82,0x3f8ca018}}, // звод_, vísi, hmal, _godu_,
+ {{0x61460520,0x917be067,0x752f40c2,0x672f4320}}, // нена, _về_, _odcz, _odcj,
+ {{0x6721bf83,0xa96a060f,0x6dbc8133,0x386040fd}}, // _helj, чима_, včak, ywir_,
+ {{0x7524403b,0x02cfe8ae,0x7f5c21ea,0x6601bf84}}, // vaiz, _सुरभ, terq, _hulk,
+ {{0x6721a143,0x81bd0018,0xa2caf065,0xd945ee64}}, // _jelj, nsēj, _सुक्, _чеки,
+ {{0xe299e139,0x7dd8807b,0x959a40ba,0x34dc9133}}, // _хай_, sísi, птау_, _बर्द,
+ {{0x69d56105,0x6fd61f85,0x7c3c235f,0x6d41f420}}, // _akze, tácu, rurr, lgla,
+ {{0xb4ea4010,0x7d16575d,0x97c3409e,0xea002067}}, // [1fa0] मधे_, rbys, уйте, _thắt_,
+ {{0x65a0603c,0x7d1640e2,0x7ae40174,0x6c33413a}}, // _söhb, sbys, _izit, افلا,
+ {{0xfc3f0066,0x9f47e0f9,0x38605a31,0x644abf6f}}, // nzín_, _binú_, swir_, xtfi,
+ {{0x6568a047,0x6db2a064,0xd7078a75,0x63a28098}}, // _sadh, ałal, енце_, uhon,
+ {{0xdb00c0dd,0x3f9ea07d,0x63a45f86,0xa3bf404b}}, // shmë, _pltu_, mhin, ेवर_,
+ {{0xf1bf0326,0xa2cb6021,0x764aa156,0xcebae0a2}}, // zná_, _तुक्, ttfy, _leƙa_,
+ {{0x3afb200b,0x6601a0ca,0x6d5d0c55,0xa2e361b1}}, // פּגע, _culk, yesa, _борд,
+ {{0x7c3e6098,0x49086026,0xff5f03bb,0xf1bf002e}}, // mupr, ाँको_, noît_, xná_,
+ {{0x67260037,0x61f8e04d,0xe60fc63b,0xe73abf87}}, // dakj, _pivl, یشی_, _неа_,
+ {{0x2124c6a5,0xc6a6e1fc,0x29d74017,0x6d40c057}}, // samh_, ерми, lçat_, sgma,
+ {{0x3636a6b0,0x68e41f88,0x7ae406e6,0x44294ed8}}, // _سراس, _azid, _azit, zsa_,
+ {{0x23a46277,0x7c3d02df,0x2d83e156,0x9f4ee0f9}}, // _höjd_, tusr, öjer_, _difá_,
+ {{0x4429403c,0x7c29c009,0x61fabf89,0x12c441a6}}, // xsa_, cser, _kitl, ্ধুদ,
+ {{0x6c54e052,0x81dee1a6,0x5334e8ab,0x660fc02e}}, // _акку, _দূর_, _рект, ícke,
+ {{0x7ae41f8a,0x6d5e7602,0xdbd70052,0x443d81de}}, // _ezit, depa, _pääh, zuw_,
+ {{0x6602dc6e,0x2121205d,0x61faa31b,0xb2751f8b}}, // _kuok, _wehh_, _litl, _алиш,
+ {{0x9989606f,0xfc45e066,0x2d87a0c2,0x29183f8c}}, // [1fb0] hrať_, žíte_, yjne_, bbra_,
+ {{0x63a44054,0xb6bc0076,0x28d22ba0,0x98c76a8d}}, // ahin, _מצחי, _दुवि, нсел,
+ {{0xbd0560f9,0x55ac200b,0xc7c468c0,0xe5c46399}}, // _abéè, _זייַ, усти, усто,
+ {{0x442b45df,0x6d9ccf1d,0x7f94a5df,0x98b9003b}}, // _tvc_, _séan, _màqu, nesį_,
+ {{0xa3c40969,0x6721ab53,0xd945b6a6,0x273ac6df}}, // ्वत_, _pelj, вели, zünü_,
+ {{0x20d520aa,0x61faa05c,0xdb0f0066,0xdce3e667}}, // _धडाध, _citl, dicí, _sanč,
+ {{0xa066a1e1,0x1866a1e1,0xada5c1d3,0xaca36079}}, // ваща_, ващи_, какл, _kpọc,
+ {{0x442cadee,0x34e1a0c2,0xe7e10028,0x61faa049}}, // _dvd_, _फरीद, _गीला_, _eitl,
+ {{0x25bfc12a,0x20037f8d,0x57f5a52f,0xdd8ec049}}, // rnul_, _huji_, _апат, توي_,
+ {{0x9989603a,0x3946c019,0xdced023d,0x09ca1008}}, // brať_, _ecos_, _znać, रव्य,
+ {{0xa3c0a010,0x7527229f,0xb4e024ef,0xe5a60822}}, // ीवर_, dajz, _तरे_, вини,
+ {{0x764d4106,0x2005e5fc,0x28d22843,0x6722c011}}, // ktay, rqli_, _दुषि, _geoj,
+ {{0x7c24a00a,0xdb052156,0xfe7901ba,0x273ac19f}}, // ćire, _omhä, нёры_, sünü_,
+ {{0x7dd600e0,0x2ca9400a,0x316ddf8e,0x290b0024}}, // táss, dzad_, ldez_, acca_,
+ {{0x224ddf8f,0x442b0049,0x799bc167,0x61ee200e}}, // ltek_, asc_, okuw, rmbl,
+ {{0x7d1b0069,0x25a004b4,0x656bc0f7,0x236dc00e}}, // ðust, _ulil_, _jagh, ndej_,
+ {{0x2fc0483e,0xa7fca602,0x764d4839,0x6603e154}}, // [1fc0] rnig_, _asıl, gtay, _hunk,
+ {{0x69c1e31b,0x67240c26,0x75240105,0x70f6613a}}, // nnle, _heij, _heiz, نسائ,
+ {{0xd59b2076,0x179b2076,0x61e99f90,0xe286614a}}, // _חביל, _חייב, _bhel, ължи,
+ {{0x644d50b0,0x99d3a050,0x290d8005,0x61caa39f}}, // btai, یتوا, _cfea_, ाक्ष,
+ {{0x644d5f91,0x91fce041,0x70548ef8,0x61e989ec}}, // ctai, nvār, انتا, _dhel,
+ {{0xa3e28960,0x39942277,0x61e98025,0x35b6a14a}}, // _नीर_, _gäst_, _ehel, ъщес,
+ {{0x7d576989,0x1c396052,0x33f6807a,0xa3bec290}}, // _שילד_, _пять_, _ачас, ँचल_,
+ {{0x91e68974,0x443fc167,0xa91d820d,0xa2a0e010}}, // _робе, fuu_, _božu, खनस्,
+ {{0x6603e2a8,0x6f0d174f,0x6d44573b,0x61fc60b0}}, // _aunk, _rfac, igia, _kirl,
+ {{0xa3e7c2cd,0xa3e44029,0x6f0d0ab8,0x7c24a098}}, // _मीठ_, _पीस_, _sfac, ćirb,
+ {{0x6fd88005,0x61fc60ca,0x7b66c423,0x27e91f92}}, // sícu, _mirl, _атле, _phan_,
+ {{0x9f587284,0xf2d36008,0x2d8fdf93,0x63b8add8}}, // _diré_, יעת_, _goge_, livn,
+ {{0x442d0162,0x9f586005,0x7dd600e0,0x7dd8913e}}, // ée_, _eiré_, tásr, líss,
+ {{0xc7c42052,0x29000265,0x61bd45c5,0x6723ff94}}, // асси, _egia_, ्क्ष, _genj,
+ {{0x93fbe087,0xe1ff40f9,0x752400a2,0x75e5c009}}, // _קלוי, _aróp_, _feiz, lóza,
+ {{0x67272b01,0xad9b61e9,0x44391f95,0xddd9e030}}, // sajj, _atúp, prs_, _apwň,
+ {{0xd7f84bd6,0x3b55612a,0x7523e0bb,0x2d894057}}, // [1fd0] тур_, лкар, _yenz, rjae_,
+ {{0x38691f96,0x765aa0c2,0x9c140125,0x63a2df97}}, // _isar_, _opty, _kọns, _llon,
+ {{0x656d0e38,0xa3c8a026,0x61fb9f98,0xf99223c8}}, // _kaah, ोकन_, _piul, _הרי_,
+ {{0x29d74395,0x2d8b009f,0x657d5f99,0x6d93819f}}, // rças_, njce_, _unsh, _açar,
+ {{0x2004800f,0x69c1e451,0x7ddec00e,0x236ca030}}, // _cumi_, ynle, mësh, _badj_,
+ {{0xe6136949,0x656d0054,0x7c2b89ee,0x261800c2}}, // _نشر_, _laah, rsgr, बीटी_,
+ {{0x7d0d4197,0x6d9cc049,0x6f04849f,0x29d903f6}}, // ocas, _téal, žica, néas_,
+ {{0x61e9846b,0x6440dcd9,0x442d8593,0xdfd5804e}}, // _uhel, fumi, _tve_, _собы,
+ {{0x44399f9a,0x7528e058,0x3f9160e4,0x644d0223}}, // _ís_, fadz, _kozu_, _nqai,
+ {{0x6dbc850e,0x60dd405d,0xea002067,0x66041edf}}, // učav, _mysm, _giặt_, _ruik,
+ {{0x20049e68,0x660405ac,0x7ddec00e,0x6aa9c0e0}}, // _zumi_, _suik, kësh, szef,
+ {{0x2d916274,0xf770c230,0xcd2bc63b,0x3f91620d}}, // _loze_, _وان_, _آسان_, _lozu_,
+ {{0x7523fad6,0x6728e361,0x6723ea19,0xff04c14a}}, // _tenz, badj, _tenj, _тяхн,
+ {{0xfc0307e0,0x290dc12a,0xa06a2362,0xa3e7c046}}, // спро, lcea_, нага_, _मीत_,
+ {{0x7e96e711,0x61fd5f9b,0x21294625,0x5a952841}}, // _منور_, _bisl, faah_, артф,
+ {{0xdb1c20c2,0x77a60071,0x61fd4089,0x69c76194}}, // toró, _bóxe, _cisl, _ajje,
+ {{0x3f91605d,0x4e1d0064,0x442004ec,0xd9e3a010}}, // [1fe0] _bozu_, बीआई_, _uwi_, गतात_,
+ {{0x7f498019,0x7d0d4005,0xdb1d0057,0xaca3c125}}, // _aceq, acas, bosó, _nkọk,
+ {{0x527628e9,0x3f917cea,0x656d0167,0x6f0d40ae}}, // _супу, _dozu_, _yaah, bcac,
+ {{0x6f0d5f9c,0x42ca8618,0xafdb2271,0xd24f0555}}, // ccac, нген_, støt, _بنی_,
+ {{0xb4e2c028,0x70e2cd01,0xdb0f0019,0x84e66028}}, // _धरे_, _परेल, nicá, _करवट_,
+ {{0xa3e60518,0xb4266566,0x7bd9c096,0x63a41f97}}, // _बीस_, _معرو, _akwu, _llin,
+ {{0x6561e1dc,0x7ddec00e,0x201820d1,0x52e10afa}}, // delh, lësi, npri_, _नर्स,
+ {{0x63a40187,0x69d5264e,0x5f94db47,0x2005b225}}, // _nlin, zoze, риит, _duli_,
+ {{0x7ddec00e,0x48e3816c,0x28db2969,0x69c45f9d}}, // nësi, _готв, _मुनि, inie,
+ {{0x66064035,0x317ea00a,0x2609c1af,0x645c605d}}, // _mukk, _untz_, _dúo_, _kpri,
+ {{0x7641ff9e,0x69dabea3,0xdce3e041,0x7d1bdb3d}}, // guly, _ikte, _panā, bbus,
+ {{0x63a408cd,0xdc9b6095,0x645c7f9f,0x7528faee}}, // _clin, גיטל, _mpri, sadz,
+ {{0x27e05fa0,0x25b90098,0x6b9d1259,0xdce77fa1}}, // dlin_, risl_, rksg, _rajč,
+ {{0x244e6d3f,0x9f49413a,0x2005a068,0x656e79d0}}, // kým_, rmaí_, _yuli_, _nabh,
+ {{0x2d91776e,0xd084204e,0x9f47e04a,0xddc2e06e}}, // _roze_, быти, _jiný_, _krož,
+ {{0xc27be087,0x69d65fa2,0x2d9200a2,0xdb03a066}}, // _טרוי, goye, _goye_, diný,
+ {{0x7bdaa92d,0xa9c72343,0x61fd5fa3,0x656e7fa4}}, // [1ff0] _oktu, _مزدو, _visl, _babh,
+ {{0x6606457b,0x61fe2006,0xbe8f41e9,0x7c2f0ce5}}, // _dukk, _gipl, _aṣáj, mscr,
+ {{0x2d9ef2fb,0x27e05fa5,0xddc2e06e,0x6d4affa6}}, // ikte_, blin_, _orož, _mcfa,
+ {{0xd5674421,0x9327801e,0x656e6420,0x7127868c}}, // _стоп, اران, _eabh, ارال,
+ {{0x63baed3d,0x15f6e029,0xc324607c,0x8afee020}}, // kitn, ीदार_, _পারি_, _daƙi,
+ {{0x629ae03b,0x69c3a232,0x3946803b,0x645d53c7}}, // kyto, unne, agos_, _ipsi,
+ {{0x61ed0098,0x442dc543,0x39544057,0x6e22c434}}, // _ohal, wse_, _éasí_, _awob,
+ {{0x2d9ee3c5,0xe3b380d0,0x6441ffa7,0x69daa14f}}, // ekte_, _طرز_, vuli, _ekte,
+ {{0x2006c067,0x23a46156,0xa3d90033,0x7a08e04a}}, // _cuoi_, _höja_, ़कन_, _dětm,
+ {{0x27e049c0,0x63bae5c8,0x6e3bc32a,0xe0d08ef8}}, // zlin_, gitn, rrub, _رزق_,
+ {{0x9f9745df,0x6e3bc156,0xb17aa00b,0x28cfef30}}, // nçó_, srub, _שטער, _सुखि,
+ {{0x645d5fa8,0x442fc4fa,0x7643aeca,0xa2b86738}}, // _opsi, _tvg_, muny, ्पत्,
+ {{0xfe09e016,0xdd14806f,0x8f9ac095,0xdb01a004}}, // _bắc_, núši, _שיני, _allé,
+ {{0x6d4733f0,0x3f8dc274,0x27e05ade,0x66065fa9}}, // ggja, ljeu_, wlin_, _rukk,
+ {{0x7643bfaa,0x7ae98098,0xf5064624,0x44236022}}, // nuny, _lzet, изво, _nwj_,
+ {{0xd1ba0043,0x4ad97d6a,0x212b1fab,0x66064e9b}}, // _جاتا_, _बुधव, cach_, _pukk,
+
+ };
+ // table_hash = 5447-cf0f, unused_entries = 0 (0.00%)
+
+static const uint32 kQuadChrome0122_16_2SizeOne = 8108; // One-langprob count
+static const uint32 kQuadChrome0122_16_2IndSize = 8108; // Largest subscript
+static const uint32 kQuadChrome0122_16_2Ind[kQuadChrome0122_16_2IndSize] = {
+ // [0000]
+ 0x00000000, 0x00000000, 0x080c03a7, 0x20005504, // -- -- nl.sv.no_532 rw.sq.un_320
+ 0x0000050f, 0x00001903, 0x00006b0a, 0x00002801, // fr.un.un_600 gl.un.un_300 ceb.un.un_500 sw.un.un_200
+ 0x24000118, 0x0000120f, 0x00003203, 0x00002415, // iw.yi.un_740 hu.un.un_600 bs.un.un_300 yi.un.un_700
+ 0x18002719, 0x0000641c, 0x0000200f, 0x00005515, // gd.ga.un_750 lg.un.un_800 sq.un.un_600 rw.un.un_700
+ // [0010]
+ 0x00001c15, 0x00002103, 0x00001601, 0x0000100f, // mr.un.un_700 jw.un.un_300 hr.un.un_200 lt.un.un_600
+ 0x21190a02, 0x32002905, 0x00002b15, 0x0000230a, // pt.gl.jw_222 sl.bs.un_330 vi.un.un_700 ca.un.un_500
+ 0x00000f0f, 0x19000b02, 0x16000d05, 0x00000a15, // lv.un.un_600 es.gl.un_220 cs.hr.un_330 mk.un.un_700
+ 0x21001219, 0x21003523, 0x1821120d, 0x00000a0f, // ur.fa.un_750 zu.jw.un_880 ur.fa.ar_554 pt.un.un_600
+ // [0020]
+ 0x00005206, 0x1c091305, 0x00006e06, 0x281852a0, // ha.un.un_400 bh.hi.mr_333 hmn.un.un_400 ha.ga.sw_322
+ 0x00000706, 0x00003b0f, 0x00000d15, 0x1c2d0d04, // it.un.un_400 so.un.un_600 ne.un.un_700 cs.sk.id_332
+ 0x13000909, 0x1c000909, 0x3b2028a4, 0x010603a0, // hi.bh.un_440 hi.mr.un_440 sw.sq.so_433 nl.de.en_322
+ 0x041008ec, 0x04001008, 0x00002d0a, 0x190a0b02, // uk.be.ru_644 be.ru.un_430 sk.un.un_500 es.pt.gl_222
+ // [0030]
+ 0x00005301, 0x00004a0a, 0x2d000d1a, 0x1300090e, // ht.un.un_200 yo.un.un_500 cs.sk.un_760 hi.bh.un_550
+ 0x2d0d31a0, 0x0000640f, 0x182709a7, 0x00001c01, // az.cs.sk_322 lg.un.un_600 pl.gd.ga_532 id.un.un_200
+ 0x1e001a04, 0x0312355a, 0x00002d15, 0x00001015, // tl.ms.un_320 zu.hu.nl_553 sk.un.un_700 lt.un.un_700
+ 0x00003115, 0x1c131e0c, 0x00001906, 0x1b2825ee, // az.un.un_700 ms.et.id_543 gl.un.un_400 eu.sw.tr_422
+ // [0040]
+ 0x13090d14, 0x00000f15, 0x08000214, 0x0000121c, // ne.hi.bh_666 lv.un.un_700 da.no.un_660 ur.un.un_800
+ 0x2153230c, 0x1000310e, 0x0000130f, 0x18002112, // ca.ht.jw_543 az.lt.un_550 bh.un.un_600 fa.ar.un_640
+ 0x00006e0a, 0x0000180f, 0x00000d0f, 0x00001c0f, // hmn.un.un_500 ar.un.un_600 cs.un.un_600 mr.un.un_600
+ 0x2a126408, 0x00001703, 0x00000415, 0x10080411, // lg.hu.mt_443 sr.un.un_300 fi.un.un_700 ru.uk.be_653
+ // [0050]
+ 0x00002115, 0x18002729, 0x0000040f, 0x0000240f, // fa.un.un_700 gd.ga.un_960 fi.un.un_600 yi.un.un_600
+ 0x00003b15, 0x32001705, 0x0000100a, 0x00001901, // so.un.un_700 sr.bs.un_330 be.un.un_500 gl.un.un_200
+ 0x1e001c02, 0x041a2807, 0x03002d08, 0x12003513, // id.ms.un_220 sw.tl.fi_432 sk.nl.un_430 zu.hu.un_650
+ 0x2d000d14, 0x00001e01, 0x1c000d04, 0x00006e0f, // cs.sk.un_660 ms.un.un_200 ne.mr.un_320 hmn.un.un_600
+ // [0060]
+ 0x32291007, 0x00005306, 0x2100120e, 0x35000912, // lt.sl.bs_432 ht.un.un_400 ur.fa.un_550 pl.zu.un_640
+ 0x00000915, 0x0000020f, 0x2d000d0e, 0x00002b0f, // pl.un.un_700 da.un.un_600 cs.sk.un_550 vi.un.un_600
+ 0x00005215, 0x00000e15, 0x321716a9, 0x321629a4, // ha.un.un_700 is.un.un_700 hr.sr.bs_544 sl.hr.bs_433
+ 0x040a07ec, 0x13551a07, 0x0000290f, 0x00002d0f, // bg.mk.ru_644 tl.rw.et_432 sl.un.un_600 sk.un.un_600
+ // [0070]
+ 0x0e1c21a0, 0x00000b01, 0x2d1c35a0, 0x0600040e, // jw.id.is_322 es.un.un_200 zu.id.sk_322 fi.de.un_550
+ 0x1200350d, 0x0804170c, 0x0000010f, 0x1c000905, // zu.hu.un_540 sr.ru.uk_543 iw.un.un_600 hi.mr.un_330
+ 0x1e006402, 0x0000680a, 0x0000111c, 0x19000b05, // lg.ms.un_220 ig.un.un_500 ro.un.un_800 es.gl.un_330
+ 0x00000b15, 0x00001c03, 0x1100081a, 0x0000290a, // bn.un.un_700 id.un.un_300 uk.ro.un_760 sl.un.un_500
+ // [0080]
+ 0x0a0717a4, 0x00002b1c, 0x00004a15, 0x19080507, // sr.bg.mk_433 vi.un.un_800 yo.un.un_700 fr.no.gl_432
+ 0x070a1714, 0x18002114, 0x1c0d0907, 0x01002412, // sr.mk.bg_666 fa.ar.un_660 hi.ne.mr_432 yi.iw.un_640
+ 0x0000680f, 0x2d000d09, 0x556435a0, 0x00001a03, // ig.un.un_600 cs.sk.un_440 zu.lg.rw_322 tl.un.un_300
+ 0x1c130911, 0x13000913, 0x28006413, 0x0e0c0607, // hi.bh.mr_653 hi.bh.un_650 lg.sw.un_650 de.sv.is_432
+ // [0090]
+ 0x00002701, 0x10081112, 0x64071c07, 0x0a0704a4, // gd.un.un_200 ro.uk.be_654 id.it.lg_432 ru.bg.mk_433
+ 0x10070408, 0x00000115, 0x0000681c, 0x06000c0d, // ru.bg.be_443 iw.un.un_700 ig.un.un_800 sv.de.un_540
+ 0x00003201, 0x00001006, 0x0c080205, 0x211218ad, // bs.un.un_200 be.un.un_400 da.no.sv_333 ar.ur.fa_643
+ 0x1004080d, 0x3555640b, 0x04000813, 0x17321604, // uk.ru.be_554 lg.rw.zu_542 uk.ru.un_650 hr.bs.sr_332
+ // [00a0]
+ 0x1b003105, 0x101f2504, 0x00005201, 0x00000d0a, // az.tr.un_330 eu.cy.lt_332 ha.un.un_200 cs.un.un_500
+ 0x121821af, 0x21002812, 0x3f003202, 0x18531902, // fa.ar.ur_655 sw.jw.un_640 bs.af.un_220 gl.ht.ga_222
+ 0x0d000909, 0x32171605, 0x1c001304, 0x09002a0c, // hi.ne.un_440 hr.sr.bs_333 bh.mr.un_320 mt.pl.un_530
+ 0x08020cec, 0x27001f19, 0x00006b06, 0x4a002813, // sv.da.no_644 cy.gd.un_750 ceb.un.un_400 sw.yo.un_650
+ // [00b0]
+ 0x31001b12, 0x0000550f, 0x08022a02, 0x07050112, // tr.az.un_640 rw.un.un_600 mt.da.no_222 en.fr.it_654
+ 0x53004a0e, 0x0e2d0d08, 0x00000b06, 0x211218af, // yo.ht.un_550 cs.sk.is_443 es.un.un_400 ar.ur.fa_655
+ 0x00002306, 0x131c090d, 0x0000110a, 0x00003515, // ca.un.un_400 hi.mr.bh_554 ro.un.un_500 zu.un.un_700
+ 0x04000a08, 0x1b2d0d04, 0x0100240d, 0x3500642a, // mk.ru.un_430 cs.sk.tr_332 yi.iw.un_540 lg.zu.un_970
+ // [00c0]
+ 0x0a07170c, 0x0000350a, 0x0000090f, 0x3f0804ee, // sr.bg.mk_543 zu.un.un_500 pl.un.un_600 fi.no.af_422
+ 0x00000e0f, 0x09001308, 0x040208a4, 0x00001e06, // is.un.un_600 bh.hi.un_430 no.da.fi_433 ms.un.un_400
+ 0x2a2855ac, 0x55121104, 0x00005303, 0x0000060a, // rw.sw.mt_632 ro.hu.rw_332 ht.un.un_300 de.un.un_500
+ 0x190a0b05, 0x04171112, 0x0000280a, 0x070a0408, // es.pt.gl_333 ro.sr.ru_654 sw.un.un_500 ru.mk.bg_443
+ // [00d0]
+ 0x1200211a, 0x00005315, 0x201c2105, 0x4a005205, // fa.ur.un_760 ht.un.un_700 jw.id.sq_333 ha.yo.un_330
+ 0x00002501, 0x00002301, 0x08040707, 0x04081009, // eu.un.un_200 ca.un.un_200 bg.ru.uk_432 be.uk.ru_444
+ 0x00000f03, 0x0a1710ad, 0x00003506, 0x0000101c, // lv.un.un_300 be.sr.mk_643 zu.un.un_400 be.un.un_800
+ 0x1200130e, 0x00002015, 0x17070a12, 0x1c1e12ad, // et.hu.un_550 sq.un.un_700 mk.bg.sr_654 hu.ms.id_643
+ // [00e0]
+ 0x00001215, 0x12000509, 0x00001f1c, 0x0d001302, // ur.un.un_700 fr.hu.un_440 cy.un.un_800 bh.ne.un_220
+ 0x32171609, 0x092504a4, 0x19000b04, 0x07170a11, // hr.sr.bs_444 fi.eu.pl_433 es.gl.un_320 mk.sr.bg_653
+ 0x00005203, 0x04002508, 0x05001f05, 0x0000210f, // ha.un.un_300 eu.fi.un_430 cy.fr.un_330 fa.un.un_600
+ 0x1c090da9, 0x1b1e25ad, 0x4a002a04, 0x18002707, // ne.hi.mr_544 eu.ms.tr_643 mt.yo.un_320 gd.ga.un_420
+ // [00f0]
+ 0x06000305, 0x091c13ee, 0x6b001a0e, 0x18002102, // nl.de.un_330 bh.mr.hi_422 tl.ceb.un_550 jw.ga.un_220
+ 0x552d1ba4, 0x17002d05, 0x1e004a08, 0x00002a0f, // tr.sk.rw_433 sk.sr.un_330 yo.ms.un_430 mt.un.un_600
+ 0x6b1820ee, 0x00004a03, 0x0000050a, 0x00001001, // sq.ga.ceb_422 yo.un.un_300 fr.un.un_500 be.un.un_200
+ 0x19000a09, 0x00001f0f, 0x315228a4, 0x00000815, // pt.gl.un_440 cy.un.un_600 sw.ha.az_433 uk.un.un_700
+ // [0100]
+ 0x060435a0, 0x04070a0e, 0x02133b05, 0x17070a09, // zu.fi.de_322 mk.bg.ru_555 so.et.da_333 mk.bg.sr_444
+ 0x10041107, 0x0000060f, 0x00001b0f, 0x17003507, // ro.ru.be_432 de.un.un_600 tr.un.un_600 zu.sr.un_420
+ 0x0000250f, 0x08033f07, 0x00001315, 0x071105ee, // eu.un.un_600 af.nl.no_432 et.un.un_700 fr.ro.it_422
+ 0x12002118, 0x02000804, 0x0900350c, 0x01006b04, // fa.ur.un_740 no.da.un_320 zu.pl.un_530 ceb.en.un_320
+ // [0110]
+ 0x03003f0e, 0x040807ad, 0x11522507, 0x00002b03, // af.nl.un_550 bg.uk.ru_643 eu.ha.ro_432 vi.un.un_300
+ 0x00006b0f, 0x18000607, 0x210e0604, 0x013f1f07, // ceb.un.un_600 de.ga.un_420 de.is.jw_332 cy.af.en_432
+ 0x13000912, 0x00002b0a, 0x1c000d12, 0x0d122d60, // hi.bh.un_640 vi.un.un_500 ne.mr.un_640 sk.hu.cs_664
+ 0x285564a9, 0x00006415, 0x1f003f0d, 0x080a1704, // lg.rw.sw_544 lg.un.un_700 af.cy.un_540 sr.mk.uk_332
+ // [0120]
+ 0x1a1c6402, 0x00001b15, 0x230501a7, 0x1b4a1107, // lg.id.tl_222 tr.un.un_700 en.fr.ca_532 ro.yo.tr_432
+ 0x68006422, 0x00006806, 0x292d0d04, 0x285535a4, // lg.ig.un_870 ig.un.un_400 cs.sk.sl_332 zu.rw.sw_433
+ 0x4a0e1b0c, 0x2d0d1902, 0x0000110f, 0x211e1c5a, // tr.is.yo_543 gl.cs.sk_222 ro.un.un_600 id.ms.jw_553
+ 0x4a642104, 0x16002904, 0x08001013, 0x0900130d, // jw.lg.yo_332 sl.hr.un_320 be.uk.un_650 bh.hi.un_540
+ // [0130]
+ 0x16002908, 0x044a0ea7, 0x0000551c, 0x00001603, // sl.hr.un_430 is.yo.fi_532 rw.un.un_800 hr.un.un_300
+ 0x04001312, 0x08020c0c, 0x01003f04, 0x091e1c02, // et.fi.un_640 sv.da.no_543 af.en.un_320 id.ms.pl_222
+ 0x080204a4, 0x08001014, 0x00001815, 0x1e001c05, // fi.da.no_433 be.uk.un_660 ar.un.un_700 id.ms.un_330
+ 0x07042055, 0x042007a0, 0x00002a0a, 0x0f041355, // sq.fi.it_442 it.sq.fi_322 mt.un.un_500 et.fi.lv_442
+ // [0140]
+ 0x32092907, 0x1c3b2102, 0x0d003504, 0x00001701, // sl.pl.bs_432 jw.so.id_222 zu.cs.un_320 sr.un.un_200
+ 0x03086407, 0x08021fa4, 0x2305010b, 0x18001f07, // lg.no.nl_432 cy.da.no_433 en.fr.ca_542 cy.ga.un_420
+ 0x13000905, 0x06000c05, 0x0000070f, 0x0d091c0b, // hi.bh.un_330 sv.de.un_330 bg.un.un_600 mr.hi.ne_542
+ 0x060c1f10, 0x1f0e0ca9, 0x03080204, 0x0000080f, // cy.sv.de_642 sv.is.cy_544 da.no.nl_332 no.un.un_600
+ // [0150]
+ 0x2b006b08, 0x0d6b1fee, 0x03122aa7, 0x00002703, // ceb.vi.un_430 cy.ceb.cs_422 mt.hu.nl_532 gd.un.un_300
+ 0x3f002502, 0x00003f0a, 0x00000c0a, 0x0000530a, // eu.af.un_220 af.un.un_500 sv.un.un_500 ht.un.un_500
+ 0x090d1c08, 0x0a17110c, 0x00002503, 0x20000604, // mr.ne.hi_443 ro.sr.mk_543 eu.un.un_300 de.sq.un_320
+ 0x2a645504, 0x1a1604ee, 0x00002903, 0x09001c0e, // rw.lg.mt_332 fi.hr.tl_422 sl.un.un_300 mr.hi.un_550
+ // [0160]
+ 0x286455ee, 0x28005305, 0x00000515, 0x0000530f, // rw.lg.sw_422 ht.sw.un_330 fr.un.un_700 ht.un.un_600
+ 0x3b00310e, 0x0300280c, 0x25001a04, 0x0000280f, // az.so.un_550 sw.nl.un_530 tl.eu.un_320 sw.un.un_600
+ 0x2d0d0e0e, 0x3f0e3ba4, 0x1e1c1fee, 0x13000612, // is.cs.sk_555 so.is.af_433 cy.id.ms_422 de.et.un_640
+ 0x0a000714, 0x17163202, 0x1f6b0eec, 0x00001f0a, // bg.mk.un_660 bs.hr.sr_222 is.ceb.cy_644 cy.un.un_500
+ // [0170]
+ 0x3b000119, 0x0000030a, 0x521b3ba4, 0x55002114, // en.so.un_750 nl.un.un_500 so.tr.ha_433 jw.rw.un_660
+ 0x0000351c, 0x19110bee, 0x0a001719, 0x64010507, // zu.un.un_800 es.ro.gl_422 sr.mk.un_750 fr.en.lg_432
+ 0x1c282008, 0x02180107, 0x00002a15, 0x00001e03, // sq.sw.id_443 en.ga.da_432 mt.un.un_700 ms.un.un_300
+ 0x080201ee, 0x19122dad, 0x2b006407, 0x1f3f2504, // en.da.no_422 sk.hu.gl_643 lg.vi.un_420 eu.af.cy_332
+ // [0180]
+ 0x0d091caf, 0x31001b13, 0x17000a14, 0x18190b04, // mr.hi.ne_655 tr.az.un_650 mk.sr.un_660 es.gl.ga_332
+ 0x25205207, 0x1a645507, 0x0600250d, 0x00002101, // ha.sq.eu_432 rw.lg.tl_432 eu.de.un_540 jw.un.un_200
+ 0x111013ee, 0x12001104, 0x3b2864a4, 0x00003f01, // et.lt.ro_422 ro.hu.un_320 lg.sw.so_433 af.un.un_200
+ 0x00002106, 0x01000802, 0x012711a0, 0x07041702, // jw.un.un_400 no.en.un_220 ro.gd.en_322 sr.ru.bg_222
+ // [0190]
+ 0x0c060404, 0x0a091107, 0x07040807, 0x00000e06, // fi.de.sv_332 ro.pl.pt_432 uk.ru.bg_432 is.un.un_400
+ 0x00006401, 0x122d0d04, 0x3b000c0d, 0x00000703, // lg.un.un_200 cs.sk.hu_332 sv.so.un_540 it.un.un_300
+ 0x081b04ad, 0x32001607, 0x21121812, 0x070a1004, // fi.tr.no_643 hr.bs.un_420 ar.ur.fa_654 be.mk.bg_332
+ 0x00001124, 0x10000819, 0x01000204, 0x1b00310e, // ro.un.un_900 uk.be.un_750 da.en.un_320 az.tr.un_550
+ // [01a0]
+ 0x29000c05, 0x0100240c, 0x2d001219, 0x35556b07, // sv.sl.un_330 yi.iw.un_530 hu.sk.un_750 ceb.rw.zu_432
+ 0x01002704, 0x6400351a, 0x00000b0f, 0x216455a0, // gd.en.un_320 zu.lg.un_760 bn.un.un_600 rw.lg.jw_322
+ 0x6423250c, 0x24000119, 0x3b080e07, 0x05002304, // eu.ca.lg_543 iw.yi.un_750 is.no.so_432 ca.fr.un_320
+ 0x18002119, 0x1004130e, 0x00000a0a, 0x19000b09, // fa.ar.un_750 et.fi.lt_555 pt.un.un_500 es.gl.un_440
+ // [01b0]
+ 0x1c0d0914, 0x0a040705, 0x2100010b, 0x18001213, // hi.ne.mr_666 bg.ru.mk_333 en.jw.un_520 ur.ar.un_650
+ 0x00000801, 0x0c0305ee, 0x00006406, 0x01003504, // uk.un.un_200 fr.nl.sv_422 lg.un.un_400 zu.en.un_320
+ 0x5200280e, 0x03003f04, 0x0400100d, 0x272504ec, // sw.ha.un_550 af.nl.un_320 be.ru.un_540 fi.eu.gd_644
+ 0x170a07a4, 0x170a0405, 0x2b001e04, 0x00000b0a, // bg.mk.sr_433 ru.mk.sr_333 ms.vi.un_320 es.un.un_500
+ // [01c0]
+ 0x17001108, 0x18001209, 0x05001604, 0x2d0d1809, // ro.sr.un_430 ur.ar.un_440 hr.fr.un_320 ga.cs.sk_444
+ 0x4a5352a0, 0x6b251304, 0x28001214, 0x170a0713, // ha.ht.yo_322 et.eu.ceb_332 hu.sw.un_660 bg.mk.sr_665
+ 0x01100807, 0x1c000d05, 0x3b1e5204, 0x05001902, // no.lt.en_432 ne.mr.un_330 ha.ms.so_332 gl.fr.un_220
+ 0x070a170c, 0x00001f15, 0x2d000d12, 0x03003f05, // sr.mk.bg_543 cy.un.un_700 cs.sk.un_640 af.nl.un_330
+ // [01d0]
+ 0x3f1235a0, 0x04001a05, 0x2d100dad, 0x00001003, // zu.hu.af_322 tl.fi.un_330 cs.lt.sk_643 be.un.un_300
+ 0x1a002a07, 0x0c001a04, 0x091c130c, 0x00002a1c, // mt.tl.un_420 tl.sv.un_320 bh.mr.hi_543 mt.un.un_800
+ 0x162d0d08, 0x13030109, 0x02001a04, 0x232b25ad, // cs.sk.hr_443 en.nl.et_444 tl.da.un_320 eu.vi.ca_643
+ 0x03000c04, 0x13000412, 0x00000a06, 0x0000520a, // sv.nl.un_320 fi.et.un_640 pt.un.un_400 ha.un.un_500
+ // [01e0]
+ 0x126b1a11, 0x00000715, 0x00006b03, 0x0000171c, // tl.ceb.hu_653 bg.un.un_700 ceb.un.un_300 sr.un.un_800
+ 0x171632a0, 0x065355ad, 0x322d1055, 0x00001a01, // bs.hr.sr_322 rw.ht.de_643 lt.sk.bs_442 tl.un.un_200
+ 0x0c0603a0, 0x00004a06, 0x06002a02, 0x08100413, // nl.de.sv_322 yo.un.un_400 mt.de.un_220 ru.be.uk_665
+ 0x1b002502, 0x19002105, 0x12190b04, 0x04000812, // eu.tr.un_220 jw.gl.un_330 es.gl.hu_332 uk.ru.un_640
+ // [01f0]
+ 0x03000912, 0x3b000707, 0x3f0625ad, 0x1c001e04, // pl.nl.un_640 it.so.un_420 eu.de.af_643 ms.id.un_320
+ 0x281a6bee, 0x060e3fa4, 0x00002a01, 0x080717ee, // ceb.tl.sw_422 af.is.de_433 mt.un.un_200 sr.bg.uk_422
+ 0x6b000418, 0x170f1007, 0x1c001a07, 0x0000350f, // fi.ceb.un_740 lt.lv.sr_432 tl.id.un_420 zu.un.un_600
+ 0x00001115, 0x20286407, 0x1c012d07, 0x041013a4, // ro.un.un_700 lg.sw.sq_432 sk.en.id_432 et.lt.fi_433
+ // [0200]
+ 0x0000030f, 0x070a040c, 0x11002a0d, 0x08060fa4, // nl.un.un_600 ru.mk.bg_543 mt.ro.un_540 lv.de.no_433
+ 0x550f4a55, 0x2d001604, 0x4a006e08, 0x080f0608, // yo.lv.rw_442 hr.sk.un_320 hmn.yo.un_430 de.lv.no_443
+ 0x11002a02, 0x1c090dee, 0x10040f04, 0x09000d1b, // mt.ro.un_220 ne.hi.mr_422 lv.fi.lt_332 ne.hi.un_770
+ 0x27183508, 0x32161702, 0x04001009, 0x31001b14, // zu.ga.gd_443 sr.hr.bs_222 be.ru.un_440 tr.az.un_660
+ // [0210]
+ 0x0e355504, 0x283b5304, 0x04002d0e, 0x04003f04, // rw.zu.is_332 ht.so.sw_332 sk.fi.un_550 af.fi.un_320
+ 0x52002811, 0x211a1c0c, 0x1c00090e, 0x321617a6, // sw.ha.un_630 id.tl.jw_543 hi.mr.un_550 sr.hr.bs_521
+ 0x06292504, 0x1c001312, 0x10040814, 0x311b1a0b, // eu.sl.de_332 bh.mr.un_640 uk.ru.be_666 tl.tr.az_542
+ 0x230b0a05, 0x0804100b, 0x3f1303a9, 0x08170705, // pt.es.ca_333 be.ru.uk_542 nl.et.af_544 bg.sr.uk_333
+ // [0220]
+ 0x52002502, 0x0a08040b, 0x13000804, 0x00006e1c, // eu.ha.un_220 ru.uk.mk_542 no.et.un_320 hmn.un.un_800
+ 0x23002907, 0x4a000705, 0x070417a0, 0x00002a03, // sl.ca.un_420 it.yo.un_330 sr.ru.bg_322 mt.un.un_300
+ 0x3f2b03a0, 0x00005501, 0x0c00061a, 0x1a3f0112, // nl.vi.af_322 rw.un.un_200 de.sv.un_760 en.af.tl_654
+ 0x554a680d, 0x040c0607, 0x0000020a, 0x64001902, // ig.yo.rw_554 de.sv.fi_432 da.un.un_500 gl.lg.un_220
+ // [0230]
+ 0x12211811, 0x0c296bee, 0x00000e03, 0x121821ec, // ar.fa.ur_653 ceb.sl.sv_422 is.un.un_300 fa.ar.ur_644
+ 0x063f0302, 0x190b07a4, 0x1c28210d, 0x071e1c02, // nl.af.de_222 it.es.gl_433 jw.sw.id_554 id.ms.it_222
+ 0x2b1127a0, 0x0507010c, 0x32161707, 0x05080107, // gd.ro.vi_322 en.it.fr_543 sr.hr.bs_432 en.no.fr_432
+ 0x03003f13, 0x321716a4, 0x060d35a9, 0x03293bee, // af.nl.un_650 hr.sr.bs_433 zu.cs.de_544 so.sl.nl_422
+ // [0240]
+ 0x230701a4, 0x13000c0c, 0x0a1704a4, 0x21001813, // en.it.ca_433 sv.et.un_530 ru.sr.mk_433 ar.fa.un_650
+ 0x0a001722, 0x1200640c, 0x531264a6, 0x041707ad, // sr.mk.un_870 lg.hu.un_530 lg.hu.ht_521 bg.sr.ru_643
+ 0x0f291605, 0x08000c0e, 0x07040a04, 0x100407ee, // hr.sl.lv_333 sv.no.un_550 mk.ru.bg_332 bg.ru.be_422
+ 0x52053ba0, 0x122118ad, 0x113525a9, 0x00003b1c, // so.fr.ha_322 ar.fa.ur_643 eu.zu.ro_544 so.un.un_800
+ // [0250]
+ 0x0410080c, 0x070a0807, 0x0b0a2da0, 0x11002304, // uk.be.ru_543 uk.mk.bg_432 sk.pt.es_322 ca.ro.un_320
+ 0x4a006813, 0x08040aa9, 0x1800270e, 0x083f05ee, // ig.yo.un_650 mk.ru.uk_544 gd.ga.un_550 fr.af.no_422
+ 0x1c002108, 0x321629a0, 0x6b043f05, 0x181221af, // jw.id.un_430 sl.hr.bs_322 af.fi.ceb_333 fa.ur.ar_655
+ 0x04005205, 0x0a08070d, 0x290c2504, 0x09200f07, // ha.fi.un_330 bg.uk.mk_554 eu.sv.sl_332 lv.sq.pl_432
+ // [0260]
+ 0x1c000d08, 0x0d002d13, 0x00000c06, 0x1600200b, // ne.mr.un_430 sk.cs.un_650 sv.un.un_400 sq.hr.un_520
+ 0x00002806, 0x00002515, 0x122a21a4, 0x10001c02, // sw.un.un_400 eu.un.un_700 jw.mt.hu_433 id.lt.un_220
+ 0x131b31ad, 0x0c0e01a0, 0x0e000a07, 0x0f033fa4, // az.tr.et_643 en.is.sv_322 pt.is.un_420 af.nl.lv_433
+ 0x6b4a1a12, 0x0a005307, 0x3f00230e, 0x0a001107, // tl.yo.ceb_654 ht.pt.un_420 ca.af.un_550 ro.mk.un_420
+ // [0270]
+ 0x23190bee, 0x0800020e, 0x0d003507, 0x4a001812, // es.gl.ca_422 da.no.un_550 zu.cs.un_420 ga.yo.un_640
+ 0x321617a0, 0x2b030504, 0x07000a12, 0x00000c0f, // sr.hr.bs_322 fr.nl.vi_332 mk.bg.un_640 sv.un.un_600
+ 0x290c35a0, 0x170408a4, 0x2300251a, 0x233153a0, // zu.sv.sl_322 uk.ru.sr_433 eu.ca.un_760 ht.az.ca_322
+ 0x07000a05, 0x09001c1a, 0x0f3f03ec, 0x0b001908, // mk.bg.un_330 mr.hi.un_760 nl.af.lv_644 gl.es.un_430
+ // [0280]
+ 0x0a0817a0, 0x3b000614, 0x11000f04, 0x23000607, // sr.uk.mk_322 de.so.un_660 lv.ro.un_320 de.ca.un_420
+ 0x6400550e, 0x64552504, 0x00006e15, 0x286435ad, // rw.lg.un_550 eu.rw.lg_332 hmn.un.un_700 zu.lg.sw_643
+ 0x3f0c08a4, 0x01062008, 0x530425a4, 0x1200181a, // no.sv.af_433 sq.de.en_443 eu.fi.ht_433 ar.ur.un_760
+ 0x0f103f04, 0x08001e04, 0x53000505, 0x18002713, // af.lt.lv_332 ms.no.un_320 fr.ht.un_330 gd.ga.un_650
+ // [0290]
+ 0x09001307, 0x00006815, 0x00001303, 0x033f2507, // bh.hi.un_420 ig.un.un_700 bh.un.un_300 eu.af.nl_432
+ 0x17001607, 0x00000d1c, 0x00002815, 0x08071007, // hr.sr.un_420 cs.un.un_800 sw.un.un_700 be.bg.uk_432
+ 0x170a07a0, 0x00001606, 0x203f08ee, 0x12000e0d, // bg.mk.sr_322 hr.un.un_400 no.af.sq_422 is.hu.un_540
+ 0x32161705, 0x103b12ec, 0x1f3b4a5a, 0x32161709, // sr.hr.bs_333 hu.so.lt_644 yo.so.cy_553 sr.hr.bs_444
+ // [02a0]
+ 0x10001f19, 0x2700180e, 0x1b033faf, 0x080407af, // cy.lt.un_750 ga.gd.un_550 af.nl.tr_655 bg.ru.uk_655
+ 0x00002a06, 0x2700180c, 0x08020cee, 0x28550507, // mt.un.un_400 ga.gd.un_530 sv.da.no_422 fr.rw.sw_432
+ 0x00000b03, 0x356455af, 0x17081004, 0x07080aa0, // es.un.un_300 rw.lg.zu_655 be.uk.sr_332 mk.uk.bg_322
+ 0x0c0601ee, 0x64005514, 0x1710080c, 0x0a071707, // en.de.sv_422 rw.lg.un_660 uk.be.sr_543 sr.bg.mk_432
+ // [02b0]
+ 0x25000305, 0x09001213, 0x0000190a, 0x55003f14, // nl.eu.un_330 hu.pl.un_650 gl.un.un_500 af.rw.un_660
+ 0x0d0913a4, 0x10182707, 0x121b25ee, 0x0306010c, // bh.hi.ne_433 gd.ga.lt_432 eu.tr.hu_422 en.de.nl_543
+ 0x00000f1c, 0x091a6b55, 0x07132504, 0x21002504, // lv.un.un_800 ceb.tl.pl_442 eu.et.it_332 eu.jw.un_320
+ 0x131c2109, 0x06000207, 0x00000306, 0x2a322912, // jw.id.et_444 da.de.un_420 nl.un.un_400 sl.bs.mt_654
+ // [02c0]
+ 0x35080208, 0x080e0204, 0x0f002511, 0x0b001a09, // da.no.zu_443 da.is.no_332 eu.lv.un_630 tl.es.un_440
+ 0x16293507, 0x4a002a0d, 0x04100811, 0x13006b04, // zu.sl.hr_432 mt.yo.un_540 uk.be.ru_653 ceb.et.un_320
+ 0x0b190a0d, 0x321716ee, 0x091c0dad, 0x10000813, // pt.gl.es_554 hr.sr.bs_422 ne.mr.hi_643 uk.be.un_650
+ 0x0a0717ee, 0x13001c1a, 0x2d002907, 0x0c001313, // sr.bg.mk_422 mr.bh.un_760 sl.sk.un_420 et.sv.un_650
+ // [02d0]
+ 0x6b1a25a4, 0x16321704, 0x1f003f08, 0x0a04070d, // eu.tl.ceb_433 sr.bs.hr_332 af.cy.un_430 bg.ru.mk_554
+ 0x182a0c0c, 0x17040804, 0x11120704, 0x040708af, // sv.mt.ga_543 uk.ru.sr_332 it.hu.ro_332 no.it.fi_655
+ 0x080a1712, 0x0000270a, 0x0c002005, 0x3b000419, // sr.mk.uk_654 gd.un.un_500 sq.sv.un_330 fi.so.un_750
+ 0x64006812, 0x07001104, 0x1f191b55, 0x04001313, // ig.lg.un_640 ro.it.un_320 tr.gl.cy_442 et.fi.un_650
+ // [02e0]
+ 0x5500640e, 0x116b07a0, 0x17163205, 0x041707a7, // lg.rw.un_550 it.ceb.ro_322 bs.hr.sr_333 bg.sr.ru_532
+ 0x04006407, 0x0a00110e, 0x04000513, 0x03063f12, // lg.fi.un_420 ro.mk.un_550 fr.fi.un_650 af.de.nl_654
+ 0x64003509, 0x13000819, 0x190b0d05, 0x0708100e, // zu.lg.un_440 no.et.un_750 cs.es.gl_333 be.uk.bg_555
+ 0x29001609, 0x00001a1c, 0x19000b14, 0x07201804, // hr.sl.un_440 tl.un.un_800 es.gl.un_660 ga.sq.it_332
+ // [02f0]
+ 0x07001113, 0x2b00250e, 0x120d0705, 0x21000707, // ro.bg.un_650 eu.vi.un_550 it.cs.hu_333 it.jw.un_420
+ 0x00004a0f, 0x52002005, 0x16003f02, 0x0000080a, // yo.un.un_600 sq.ha.un_330 af.hr.un_220 no.un.un_500
+ 0x0000210a, 0x29001704, 0x061f5207, 0x551a07ee, // jw.un.un_500 sr.sl.un_320 ha.cy.de_432 it.tl.rw_422
+ 0x201610ad, 0x07272ba7, 0x08041007, 0x182b27a0, // lt.hr.sq_643 vi.gd.it_532 be.ru.uk_432 gd.vi.ga_322
+ // [0300]
+ 0x092d0d04, 0x32170c02, 0x07003f04, 0x1f1a13a0, // cs.sk.pl_332 sv.sr.bs_222 af.it.un_320 et.tl.cy_322
+ 0x52005502, 0x060c0305, 0x09002a08, 0x040711ee, // rw.ha.un_220 nl.sv.de_333 mt.pl.un_430 ro.bg.ru_422
+ 0x0d0913a9, 0x271718a9, 0x3f080cee, 0x1704070e, // bh.hi.ne_544 ga.sr.gd_544 sv.no.af_422 bg.ru.sr_555
+ 0x090c02a0, 0x1c0f1305, 0x07000819, 0x28001a08, // da.sv.pl_322 et.lv.id_333 uk.bg.un_750 tl.sw.un_430
+ // [0310]
+ 0x02000304, 0x1a6b01a0, 0x070a1707, 0x3f6b1a07, // nl.da.un_320 en.ceb.tl_322 sr.mk.bg_432 tl.ceb.af_432
+ 0x160729a0, 0x1b003113, 0x28001e13, 0x12001804, // sl.it.hr_322 az.tr.un_650 ms.sw.un_650 ga.hu.un_320
+ 0x0a0704af, 0x0f522da0, 0x07001702, 0x0e000808, // ru.bg.mk_655 sk.ha.lv_322 sr.bg.un_220 no.is.un_430
+ 0x321631a0, 0x00003f15, 0x19002108, 0x06080ea0, // az.hr.bs_322 af.un.un_700 jw.gl.un_430 is.no.de_322
+ // [0320]
+ 0x32001602, 0x00001c0a, 0x23001904, 0x041710a4, // hr.bs.un_220 id.un.un_500 gl.ca.un_320 be.sr.ru_433
+ 0x200f35a0, 0x1000060c, 0x2d000d13, 0x4a1f5304, // zu.lv.sq_322 de.lt.un_530 cs.sk.un_650 ht.cy.yo_332
+ 0x0d1c09af, 0x04522107, 0x19000a05, 0x202910ee, // hi.mr.ne_655 jw.ha.fi_432 pt.gl.un_330 lt.sl.sq_422
+ 0x04645509, 0x010f3f07, 0x190a11a0, 0x010a110c, // rw.lg.fi_444 af.lv.en_432 ro.pt.gl_322 ro.pt.en_543
+ // [0330]
+ 0x1309040e, 0x0a071705, 0x23001c02, 0x19000b0c, // fi.pl.et_555 sr.bg.mk_333 id.ca.un_220 es.gl.un_530
+ 0x68162507, 0x3b006407, 0x3b005208, 0x03000704, // eu.hr.ig_432 lg.so.un_420 ha.so.un_430 it.nl.un_320
+ 0x0400170e, 0x213b3f07, 0x09321602, 0x045525ee, // sr.ru.un_550 af.so.jw_432 hr.bs.pl_222 eu.rw.fi_422
+ 0x0a192307, 0x18122111, 0x2118120c, 0x170a0712, // ca.gl.pt_432 fa.ur.ar_653 ur.ar.fa_543 bg.mk.sr_654
+ // [0340]
+ 0x0f101104, 0x3f001c04, 0x08000c0d, 0x211812ad, // ro.lt.lv_332 id.af.un_320 sv.no.un_540 ur.ar.fa_643
+ 0x211c2007, 0x211e1c02, 0x12000c0c, 0x3b001012, // sq.id.jw_432 id.ms.jw_222 sv.hu.un_530 lt.so.un_640
+ 0x252d29ad, 0x3b04010c, 0x1c00280d, 0x25002102, // sl.sk.eu_643 en.fi.so_543 sw.id.un_540 jw.eu.un_220
+ 0x0f100607, 0x0a00071a, 0x321620ee, 0x0a0811a0, // de.lt.lv_432 bg.mk.un_760 sq.hr.bs_422 ro.uk.mk_322
+ // [0350]
+ 0x050e250c, 0x27001812, 0x091a13ee, 0x131004a7, // eu.is.fr_543 ga.gd.un_640 et.tl.pl_422 fi.lt.et_532
+ 0x091c13a7, 0x00002315, 0x3f1f0ca7, 0x080225ec, // bh.mr.hi_532 ca.un.un_700 sv.cy.af_532 eu.da.no_644
+ 0x191023ad, 0x19000b0e, 0x1b1a31a7, 0x1c001307, // ca.lt.gl_643 es.gl.un_550 az.tl.tr_532 bh.mr.un_420
+ 0x040c3ba0, 0x17001602, 0x3b6435a4, 0x0000520f, // so.sv.fi_322 hr.sr.un_220 zu.lg.so_433 ha.un.un_600
+ // [0360]
+ 0x21001f19, 0x32001702, 0x00001024, 0x00000303, // cy.jw.un_750 sr.bs.un_220 be.un.un_900 nl.un.un_300
+ 0x00002906, 0x080225a9, 0x190b050e, 0x12000913, // sl.un.un_400 eu.da.no_544 fr.es.gl_555 pl.hu.un_650
+ 0x10000713, 0x3f0809ad, 0x352001ac, 0x31003b05, // bg.be.un_650 pl.no.af_643 en.sq.zu_632 so.az.un_330
+ 0x12001a04, 0x3b001a04, 0x09292da0, 0x1c000f05, // tl.hu.un_320 tl.so.un_320 sk.sl.pl_322 lv.id.un_330
+ // [0370]
+ 0x21006e07, 0x64041007, 0x20006404, 0x25000313, // hmn.jw.un_420 lt.fi.lg_432 lg.sq.un_320 nl.eu.un_650
+ 0x033f1307, 0x020c3fa4, 0x04100812, 0x206b01a6, // et.af.nl_432 af.sv.da_433 uk.be.ru_654 en.ceb.sq_521
+ 0x13005519, 0x21003519, 0x0800520b, 0x05005208, // rw.et.un_750 zu.jw.un_750 ha.no.un_520 ha.fr.un_430
+ 0x281a1055, 0x1f2327a7, 0x1f061004, 0x1c090d0e, // lt.tl.sw_442 gd.ca.cy_532 lt.de.cy_332 ne.hi.mr_555
+ // [0380]
+ 0x13001f1a, 0x17001605, 0x052123a0, 0x18201b12, // cy.et.un_760 hr.sr.un_330 ca.jw.fr_322 tr.sq.ga_654
+ 0x06001e02, 0x00006824, 0x1f003519, 0x0d122d0d, // ms.de.un_220 ig.un.un_900 zu.cy.un_750 sk.hu.cs_554
+ 0x3b002507, 0x00000f01, 0x32171608, 0x281f3f04, // eu.so.un_420 lv.un.un_200 hr.sr.bs_443 af.cy.sw_332
+ 0x684a250c, 0x1f005205, 0x13000914, 0x041117a4, // eu.yo.ig_543 ha.cy.un_330 hi.bh.un_660 sr.ro.ru_433
+ // [0390]
+ 0x556e6404, 0x16003204, 0x27002312, 0x2000010d, // lg.hmn.rw_332 bs.hr.un_320 ca.gd.un_640 en.sq.un_540
+ 0x2b0e35ec, 0x19000a07, 0x12002104, 0x28002a04, // zu.is.vi_644 pt.gl.un_420 jw.hu.un_320 mt.sw.un_320
+ 0x18235207, 0x17040704, 0x01000609, 0x21002804, // ha.ca.ga_432 bg.ru.sr_332 de.en.un_440 sw.jw.un_320
+ 0x2d0d0914, 0x0a00171a, 0x0804100c, 0x090d1c13, // pl.cs.sk_666 sr.mk.un_760 be.ru.uk_543 mr.ne.hi_665
+ // [03a0]
+ 0x09041012, 0x321617a9, 0x0c123504, 0x04536408, // lt.fi.pl_654 sr.hr.bs_544 zu.hu.sv_332 lg.ht.fi_443
+ 0x161b2807, 0x0a0408a0, 0x01233f07, 0x556408a0, // sw.tr.hr_432 uk.ru.mk_322 af.ca.en_432 no.lg.rw_322
+ 0x08002513, 0x1e1c21ec, 0x17001122, 0x2d000d0d, // eu.no.un_650 jw.id.ms_644 ro.sr.un_870 cs.sk.un_540
+ 0x0000200a, 0x0e005218, 0x016820a4, 0x091c0d0d, // sq.un.un_500 ha.is.un_740 sq.ig.en_433 ne.mr.hi_554
+ // [03b0]
+ 0x03003f09, 0x0f001019, 0x110501af, 0x0a000e19, // af.nl.un_440 lt.lv.un_750 en.fr.ro_655 is.pt.un_750
+ 0x6855280c, 0x21001b05, 0x281e2a07, 0x1000081b, // sw.rw.ig_543 tr.jw.un_330 mt.ms.sw_432 uk.be.un_770
+ 0x0a002304, 0x644a07a4, 0x0b2d0d05, 0x00000506, // ca.pt.un_320 it.yo.lg_433 cs.sk.es_333 fr.un.un_400
+ 0x24000120, 0x010752ee, 0x03003f19, 0x10130f12, // iw.yi.un_850 ha.it.en_422 af.nl.un_750 lv.et.lt_654
+ // [03c0]
+ 0x0f00101b, 0x09002d08, 0x186b08a0, 0x13090d11, // lt.lv.un_770 sk.pl.un_430 no.ceb.ga_322 ne.hi.bh_653
+ 0x643b3507, 0x03003f0d, 0x11006404, 0x130455ee, // zu.so.lg_432 af.nl.un_540 lg.ro.un_320 rw.fi.et_422
+ 0x24000112, 0x55006808, 0x170a0714, 0x19001213, // iw.yi.un_640 ig.rw.un_430 bg.mk.sr_666 hu.gl.un_650
+ 0x4a556813, 0x1b00311a, 0x171632a4, 0x283555ad, // ig.rw.yo_665 az.tr.un_760 bs.hr.sr_433 rw.zu.sw_643
+ // [03d0]
+ 0x016b3b07, 0x02000813, 0x01000505, 0x0300050c, // so.ceb.en_432 no.da.un_650 fr.en.un_330 fr.nl.un_530
+ 0x17041004, 0x04070aa4, 0x16210e04, 0x2d090604, // be.ru.sr_332 mk.bg.ru_433 is.jw.hr_332 de.pl.sk_332
+ 0x17040a0c, 0x6b3b1b55, 0x2b006b07, 0x2d0d18af, // mk.ru.sr_543 tr.so.ceb_442 ceb.vi.un_420 ga.cs.sk_655
+ 0x18006e04, 0x08041011, 0x32171604, 0x31001b1a, // hmn.ga.un_320 be.ru.uk_653 hr.sr.bs_332 tr.az.un_760
+ // [03e0]
+ 0x0c000813, 0x0e001a14, 0x35311b14, 0x082d0c07, // no.sv.un_650 tl.is.un_660 tr.az.zu_666 sv.sk.no_432
+ 0x122d0d0d, 0x0c000a0e, 0x13091caf, 0x112916a4, // cs.sk.hu_554 pt.sv.un_550 mr.hi.bh_655 hr.sl.ro_433
+ 0x20001105, 0x1a004a08, 0x255255a7, 0x1e1c0fee, // ro.sq.un_330 yo.tl.un_430 rw.ha.eu_532 lv.id.ms_422
+ 0x190a53a0, 0x31001b19, 0x321617a4, 0x10002008, // ht.pt.gl_322 tr.az.un_750 sr.hr.bs_433 sq.lt.un_430
+ // [03f0]
+ 0x062a0411, 0x12002112, 0x311b3b07, 0x643f035a, // fi.mt.de_653 fa.ur.un_640 so.tr.az_432 nl.af.lg_553
+ 0x6b1a1007, 0x06000c07, 0x0000180a, 0x030408a4, // lt.tl.ceb_432 sv.de.un_420 ga.un.un_500 no.fi.nl_433
+ 0x080e0c07, 0x1c121b07, 0x0d0913ee, 0x0f0127a0, // sv.is.no_432 tr.hu.id_432 bh.hi.ne_422 gd.en.lv_322
+ 0x04001012, 0x6b000c05, 0x13002913, 0x1a1113a0, // be.ru.un_640 sv.ceb.un_330 sl.et.un_650 et.ro.tl_322
+
+ // [0400]
+ 0x1f002808, 0x00003b0a, 0x0e000404, 0x21001c08, // sw.cy.un_430 so.un.un_500 fi.is.un_320 id.jw.un_430
+ 0x3f1311ec, 0x00000b1c, 0x0a111705, 0x08001004, // ro.et.af_644 bn.un.un_800 sr.ro.mk_333 be.uk.un_320
+ 0x53002b14, 0x0d2018a0, 0x1c09130c, 0x08071011, // vi.ht.un_660 ga.sq.cs_322 bh.hi.mr_543 be.bg.uk_653
+ 0x171632ee, 0x2d291107, 0x170a0404, 0x050601a4, // bs.hr.sr_422 ro.sl.sk_432 ru.mk.sr_332 en.de.fr_433
+ // [0410]
+ 0x3100230d, 0x3b1b5504, 0x040a1755, 0x0e005207, // ca.az.un_540 rw.tr.so_332 sr.mk.ru_442 ha.is.un_420
+ 0x121a6b12, 0x231304ad, 0x05006e07, 0x351f1a05, // ceb.tl.hu_654 fi.et.ca_643 hmn.fr.un_420 tl.cy.zu_333
+ 0x16290d07, 0x28001308, 0x19000a0c, 0x10001704, // cs.sl.hr_432 et.sw.un_430 pt.gl.un_530 sr.lt.un_320
+ 0x52122aa4, 0x23190b05, 0x3b003f05, 0x1f130807, // mt.hu.ha_433 es.gl.ca_333 af.so.un_330 no.et.cy_432
+ // [0420]
+ 0x27001813, 0x170a0709, 0x3216170c, 0x070a1705, // ga.gd.un_650 bg.mk.sr_444 sr.hr.bs_543 sr.mk.bg_333
+ 0x09000f08, 0x00000701, 0x00006b15, 0x28003513, // lv.pl.un_430 it.un.un_200 ceb.un.un_700 zu.sw.un_650
+ 0x2a00121a, 0x351e28a4, 0x32160914, 0x08021fa9, // hu.mt.un_760 sw.ms.zu_433 pl.hr.bs_666 cy.da.no_544
+ 0x0000011c, 0x070a11ee, 0x00002915, 0x68253ba4, // en.un.un_800 ro.mk.bg_422 sl.un.un_700 so.eu.ig_433
+ // [0430]
+ 0x13040e04, 0x0d002d0d, 0x3229160c, 0x3f3b29ee, // is.fi.et_332 sk.cs.un_540 hr.sl.bs_543 sl.so.af_422
+ 0x35004a04, 0x00001e0a, 0x3b001a0e, 0x090d1cec, // yo.zu.un_320 ms.un.un_500 tl.so.un_550 mr.ne.hi_644
+ 0x11073ba4, 0x07041705, 0x08005218, 0x3f535213, // so.it.ro_433 sr.ru.bg_333 ha.no.un_740 ha.ht.af_665
+ 0x041b0e07, 0x1e1c21a4, 0x3b0f1a04, 0x212813ad, // is.tr.fi_432 jw.id.ms_433 tl.lv.so_332 et.sw.jw_643
+ // [0440]
+ 0x08001e02, 0x13321607, 0x0c0353ee, 0x6b351aad, // ms.no.un_220 hr.bs.et_432 ht.nl.sv_422 tl.zu.ceb_643
+ 0x64135511, 0x35002905, 0x07080a04, 0x0d1c09ec, // rw.et.lg_653 sl.zu.un_330 mk.uk.bg_332 hi.mr.ne_644
+ 0x043f3bee, 0x21121860, 0x4a005202, 0x356428a7, // so.af.fi_422 ar.ur.fa_664 ha.yo.un_220 sw.lg.zu_532
+ 0x11080408, 0x1711040c, 0x28001013, 0x0a002302, // ru.uk.ro_443 ru.ro.sr_543 lt.sw.un_650 ca.pt.un_220
+ // [0450]
+ 0x07000a04, 0x08000e0c, 0x18000d1a, 0x232d1ba0, // pt.it.un_320 is.no.un_530 cs.ga.un_760 tr.sk.ca_322
+ 0x0d002d08, 0x08101712, 0x033f2704, 0x0407110c, // sk.cs.un_430 sr.be.uk_654 gd.af.nl_332 ro.bg.ru_543
+ 0x070a1002, 0x18001907, 0x0400550d, 0x04111312, // be.mk.bg_222 gl.ga.un_420 rw.fi.un_540 et.ro.fi_654
+ 0x17070a05, 0x0f005207, 0x2d003202, 0x5355280d, // mk.bg.sr_333 ha.lv.un_420 bs.sk.un_220 sw.rw.ht_554
+ // [0460]
+ 0x011805ee, 0x081117a4, 0x0c06030c, 0x1e1c210c, // fr.ga.en_422 sr.ro.uk_433 nl.de.sv_543 jw.id.ms_543
+ 0x04290e0d, 0x55353baf, 0x0000130a, 0x281b25ee, // is.sl.fi_554 so.zu.rw_655 bh.un.un_500 eu.tr.sw_422
+ 0x35006809, 0x08000c05, 0x13001012, 0x08000208, // ig.zu.un_440 sv.no.un_330 lt.et.un_640 da.no.un_430
+ 0x0a081702, 0x645528a4, 0x200802ad, 0x1c130914, // sr.uk.mk_222 sw.rw.lg_433 da.no.sq_643 hi.bh.mr_666
+ // [0470]
+ 0x080a1707, 0x04070a04, 0x0b00070c, 0x041008ad, // sr.mk.uk_432 mk.bg.ru_332 it.es.un_530 uk.be.ru_643
+ 0x08170a05, 0x28000e13, 0x522104ad, 0x0d1c13ac, // mk.sr.uk_333 is.sw.un_650 fi.jw.ha_643 bh.mr.ne_632
+ 0x0a190b5a, 0x643f0655, 0x313b2aad, 0x64005512, // es.gl.pt_553 de.af.lg_442 mt.so.az_643 rw.lg.un_640
+ 0x1000290e, 0x28201108, 0x4a2835a9, 0x35000613, // sl.lt.un_550 ro.sq.sw_443 zu.sw.yo_544 de.zu.un_650
+ // [0480]
+ 0x32001708, 0x643b31a0, 0x3f0810a0, 0x2000550d, // sr.bs.un_430 az.so.lg_322 lt.no.af_322 rw.sq.un_540
+ 0x35001f19, 0x1c0913a0, 0x0a171108, 0x033f27a4, // cy.zu.un_750 bh.hi.mr_322 ro.sr.mk_443 gd.af.nl_433
+ 0x00000806, 0x53005202, 0x2a000b04, 0x09001c05, // no.un.un_400 ha.ht.un_220 es.mt.un_320 mr.hi.un_330
+ 0x18000304, 0x21001c02, 0x041708a4, 0x0c000807, // nl.ga.un_320 id.jw.un_220 uk.sr.ru_433 no.sv.un_420
+ // [0490]
+ 0x321617ee, 0x0700041a, 0x35282aec, 0x1a006b0b, // sr.hr.bs_422 ru.bg.un_760 mt.sw.zu_644 ceb.tl.un_520
+ 0x0a000405, 0x13281fee, 0x070a0804, 0x0000311c, // ru.mk.un_330 cy.sw.et_422 uk.mk.bg_332 az.un.un_800
+ 0x08100408, 0x162032ee, 0x070a1155, 0x06000119, // ru.be.uk_443 bs.sq.hr_422 ro.mk.bg_442 en.de.un_750
+ 0x0000241c, 0x25112005, 0x28005513, 0x16002907, // yi.un.un_800 sq.ro.eu_333 rw.sw.un_650 sl.hr.un_420
+ // [04a0]
+ 0x04081012, 0x21001819, 0x280e200c, 0x16321708, // be.uk.ru_654 ar.fa.un_750 sq.is.sw_543 sr.bs.hr_443
+ 0x0000081c, 0x06100e04, 0x08020ea0, 0x0400110d, // no.un.un_800 is.lt.de_332 is.da.no_322 ro.ru.un_540
+ 0x52000108, 0x0a00071b, 0x21181212, 0x642a0705, // en.ha.un_430 bg.mk.un_770 ur.ar.fa_654 it.mt.lg_333
+ 0x01002419, 0x3216170e, 0x0000071c, 0x125553a7, // yi.iw.un_750 sr.hr.bs_555 bg.un.un_800 ht.rw.hu_532
+ // [04b0]
+ 0x040708a4, 0x08002719, 0x211e2005, 0x3b251908, // uk.bg.ru_433 gd.no.un_750 sq.ms.jw_333 gl.eu.so_443
+ 0x1e001c04, 0x20000808, 0x3f00121b, 0x0000211c, // id.ms.un_320 no.sq.un_430 hu.af.un_770 fa.un.un_800
+ 0x02001a02, 0x27001814, 0x1c0d09af, 0x32001614, // tl.da.un_220 ga.gd.un_660 hi.ne.mr_655 hr.bs.un_660
+ 0x180a11a4, 0x080255a0, 0x351a1c04, 0x52000113, // ro.pt.ga_433 rw.da.no_322 id.tl.zu_332 en.ha.un_650
+ // [04c0]
+ 0x0a0711a9, 0x0f005513, 0x17070a07, 0x32092d04, // ro.bg.mk_544 rw.lv.un_650 mk.bg.sr_432 sk.pl.bs_332
+ 0x04001105, 0x6b1c1aec, 0x100a07ec, 0x12001812, // ro.fi.un_330 tl.id.ceb_644 bg.mk.be_644 ga.hu.un_640
+ 0x28536ba0, 0x080410a0, 0x192d0deb, 0x2d321602, // ceb.ht.sw_322 be.ru.uk_322 cs.sk.gl_662 hr.bs.sk_222
+ 0x53006421, 0x19000b07, 0x1c213f12, 0x00000c01, // lg.ht.un_860 es.gl.un_420 af.jw.id_654 sv.un.un_200
+ // [04d0]
+ 0x20002802, 0x31355207, 0x0d002d12, 0x2d0d12af, // sw.sq.un_220 ha.zu.az_432 sk.cs.un_640 hu.cs.sk_655
+ 0x080717a4, 0x253f4aee, 0x1c00130d, 0x10001709, // sr.bg.uk_433 yo.af.eu_422 bh.mr.un_540 sr.lt.un_440
+ 0x53002104, 0x06033f04, 0x1300091a, 0x3f1220a4, // jw.ht.un_320 af.nl.de_332 hi.bh.un_760 sq.hu.af_433
+ 0x04171104, 0x0d132807, 0x35001b12, 0x080a17a0, // ro.sr.ru_332 sw.et.cs_432 tr.zu.un_640 sr.mk.uk_322
+ // [04e0]
+ 0x1c0d1311, 0x03322aee, 0x6b006e0d, 0x11640408, // bh.ne.mr_653 mt.bs.nl_422 hmn.ceb.un_540 fi.lg.ro_443
+ 0x08041008, 0x1c000902, 0x0d091305, 0x1b0435ad, // be.ru.uk_443 hi.mr.un_220 bh.hi.ne_333 zu.fi.tr_643
+ 0x110a0714, 0x52533fa6, 0x0d092d0d, 0x31202aa7, // bg.mk.ro_666 af.ht.ha_521 sk.pl.cs_554 mt.sq.az_532
+ 0x00001a0f, 0x0e1013a0, 0x35005202, 0x0000131c, // tl.un.un_600 et.lt.is_322 ha.zu.un_220 bh.un.un_800
+ // [04f0]
+ 0x0c00080e, 0x0e00190c, 0x16000d04, 0x2d002904, // no.sv.un_550 gl.is.un_530 cs.hr.un_320 sl.sk.un_320
+ 0x3500110d, 0x016b04ad, 0x09001c07, 0x00000606, // ro.zu.un_540 fi.ceb.en_643 mr.hi.un_420 de.un.un_400
+ 0x3b002904, 0x271118af, 0x0000190f, 0x071117a4, // sl.so.un_320 ga.ro.gd_655 gl.un.un_600 sr.ro.bg_433
+ 0x0f0806a6, 0x35645508, 0x4a002a21, 0x3b1935ee, // de.no.lv_521 rw.lg.zu_443 mt.yo.un_860 zu.gl.so_422
+ // [0500]
+ 0x29171605, 0x17070a13, 0x3f0313af, 0x0400081a, // hr.sr.sl_333 mk.bg.sr_665 et.nl.af_655 uk.ru.un_760
+ 0x0000170a, 0x0d091ca9, 0x3b001c08, 0x1f002313, // sr.un.un_500 mr.hi.ne_544 id.so.un_430 ca.cy.un_650
+ 0x21001218, 0x52001a04, 0x64356804, 0x25123fee, // ur.fa.un_740 tl.ha.un_320 ig.zu.lg_332 af.hu.eu_422
+ 0x020e0c05, 0x04000e13, 0x3217160e, 0x182112ec, // sv.is.da_333 is.fi.un_650 hr.sr.bs_555 ur.fa.ar_644
+ // [0510]
+ 0x0b001904, 0x68050607, 0x1a00041b, 0x25003f08, // gl.es.un_320 de.fr.ig_432 fi.tl.un_770 af.eu.un_430
+ 0x1f3b0702, 0x18002705, 0x53190507, 0x04070aa0, // it.so.cy_222 gd.ga.un_330 fr.gl.ht_432 mk.bg.ru_322
+ 0x0d09130e, 0x0c0106a4, 0x09291807, 0x08001705, // bh.hi.ne_555 de.en.sv_433 ga.sl.pl_432 sr.no.un_330
+ 0x16000f16, 0x1c001311, 0x0d1c0913, 0x1c006402, // lv.hr.un_720 bh.mr.un_630 hi.mr.ne_665 lg.id.un_220
+ // [0520]
+ 0x170a0704, 0x1c043bee, 0x3f000212, 0x18022302, // bg.mk.sr_332 so.fi.id_422 da.af.un_640 ca.da.ga_222
+ 0x3b006b0c, 0x534a1aa0, 0x06000a08, 0x13043b04, // ceb.so.un_530 tl.yo.ht_322 pt.de.un_430 so.fi.et_332
+ 0x53081b07, 0x086b0c08, 0x101705a4, 0x552305ad, // tr.no.ht_432 sv.ceb.no_443 fr.sr.lt_433 fr.ca.rw_643
+ 0x03005304, 0x10000304, 0x0a001723, 0x040a17a0, // ht.nl.un_320 nl.lt.un_320 sr.mk.un_880 sr.mk.ru_322
+ // [0530]
+ 0x2d000d05, 0x213210a4, 0x28211c07, 0x0000160a, // cs.sk.un_330 lt.bs.jw_433 id.jw.sw_432 hr.un.un_500
+ 0x0000250a, 0x3b00520c, 0x0c080260, 0x0c3f03a0, // eu.un.un_500 ha.so.un_530 da.no.sv_664 nl.af.sv_322
+ 0x1c090d07, 0x1b1f18ee, 0x1c0d0913, 0x17000718, // ne.hi.mr_432 ga.cy.tr_422 hi.ne.mr_665 bg.sr.un_740
+ 0x0f001609, 0x17321655, 0x04133ba7, 0x04001004, // hr.lv.un_440 hr.bs.sr_442 so.et.fi_532 be.ru.un_320
+ // [0540]
+ 0x04000908, 0x210c6b04, 0x271b18af, 0x6b000119, // pl.fi.un_430 ceb.sv.jw_332 ga.tr.gd_655 en.ceb.un_750
+ 0x55003518, 0x2400010e, 0x1c002107, 0x55000104, // zu.rw.un_740 iw.yi.un_550 jw.id.un_420 en.rw.un_320
+ 0x11131bad, 0x6e000309, 0x08000413, 0x04001711, // tr.et.ro_643 nl.hmn.un_440 ru.uk.un_650 sr.ru.un_630
+ 0x3f00030e, 0x0e001702, 0x2932170c, 0x21001214, // nl.af.un_550 sr.is.un_220 sr.bs.sl_543 ur.fa.un_660
+ // [0550]
+ 0x0e3b6404, 0x0c00081a, 0x321716a0, 0x18211208, // lg.so.is_332 no.sv.un_760 hr.sr.bs_322 ur.fa.ar_443
+ 0x10041105, 0x21001213, 0x183107a0, 0x24000113, // ro.ru.be_333 ur.fa.un_650 it.az.ga_322 iw.yi.un_650
+ 0x31000c04, 0x12002d04, 0x17000a22, 0x6e000e1a, // sv.az.un_320 sk.hu.un_320 mk.sr.un_870 is.hmn.un_760
+ 0x19002104, 0x09000d19, 0x191b0da0, 0x02000c0c, // jw.gl.un_320 ne.hi.un_750 cs.tr.gl_322 sv.da.un_530
+ // [0560]
+ 0x1b04315a, 0x00001a06, 0x2000230b, 0x07011ea0, // az.fi.tr_553 tl.un.un_400 ca.sq.un_520 ms.en.it_322
+ 0x23004a02, 0x4a1823b3, 0x18122160, 0x1c0d090e, // yo.ca.un_220 ca.ga.yo_743 fa.ur.ar_664 hi.ne.mr_555
+ 0x6e6b0707, 0x0000271c, 0x4a6b6804, 0x06080cad, // it.ceb.hmn_432 gd.un.un_800 ig.ceb.yo_332 sv.no.de_643
+ 0x2500070d, 0x6b1b3112, 0x53002107, 0x072a01a4, // it.eu.un_540 az.tr.ceb_654 jw.ht.un_420 en.mt.it_433
+ // [0570]
+ 0x03132507, 0x1e1c6409, 0x55356404, 0x1c091302, // eu.et.nl_432 lg.id.ms_444 lg.zu.rw_332 bh.hi.mr_222
+ 0x21001112, 0x3210160d, 0x12000704, 0x5200250d, // ro.jw.un_640 hr.lt.bs_554 it.hu.un_320 eu.ha.un_540
+ 0x09001c08, 0x32001609, 0x64213507, 0x52000808, // mr.hi.un_430 hr.bs.un_440 zu.jw.lg_432 no.ha.un_430
+ 0x08002d04, 0x0f0e10a0, 0x08032502, 0x12182113, // sk.no.un_320 lt.is.lv_322 eu.nl.no_222 fa.ar.ur_665
+ // [0580]
+ 0x531c01a6, 0x12001809, 0x04000c04, 0x081710a0, // en.id.ht_521 ga.hu.un_440 sv.fi.un_320 be.sr.uk_322
+ 0x0c08020d, 0x2a200307, 0x2a0c0e07, 0x020c08af, // da.no.sv_554 nl.sq.mt_432 is.sv.mt_432 no.sv.da_655
+ 0x0000201c, 0x073101a4, 0x040a10a0, 0x3b001c05, // sq.un.un_800 en.az.it_433 be.mk.ru_322 id.so.un_330
+ 0x180627ac, 0x211c23a9, 0x18001e05, 0x00002706, // gd.de.ga_632 ca.id.jw_544 ms.ga.un_330 gd.un.un_400
+ // [0590]
+ 0x041b31ad, 0x28182702, 0x08021baf, 0x190b23a0, // az.tr.fi_643 gd.ga.sw_222 tr.da.no_655 ca.es.gl_322
+ 0x4a311b08, 0x04001c02, 0x2513110c, 0x29081ba4, // tr.az.yo_443 id.fi.un_220 ro.et.eu_543 tr.no.sl_433
+ 0x023f08a6, 0x0d001c19, 0x5200090c, 0x12002108, // no.af.da_521 mr.ne.un_750 pl.ha.un_530 jw.hu.un_430
+ 0x0e000804, 0x032b0107, 0x0f112d0c, 0x0400130e, // no.is.un_320 en.vi.nl_432 sk.ro.lv_543 et.fi.un_550
+ // [05a0]
+ 0x1732165a, 0x01060c0c, 0x010c0812, 0x070501af, // hr.bs.sr_553 sv.de.en_543 no.sv.en_654 en.fr.it_655
+ 0x10070aa4, 0x0300040d, 0x02000512, 0x070408a4, // mk.bg.be_433 fi.nl.un_540 fr.da.un_640 uk.ru.bg_433
+ 0x07170a05, 0x121b09ec, 0x1c001308, 0x1a066ba0, // mk.sr.bg_333 pl.tr.hu_644 bh.mr.un_430 ceb.de.tl_322
+ 0x033f0808, 0x10000d19, 0x2b006b04, 0x3f02080c, // no.af.nl_443 cs.lt.un_750 ceb.vi.un_320 no.da.af_543
+ // [05b0]
+ 0x2a005209, 0x355564a6, 0x033f0107, 0x252006a0, // ha.mt.un_440 lg.rw.zu_521 en.af.nl_432 de.sq.eu_322
+ 0x110d1211, 0x312a1fad, 0x0802050c, 0x031a64a4, // hu.cs.ro_653 cy.mt.az_643 fr.da.no_543 lg.tl.nl_433
+ 0x35001113, 0x6b042511, 0x12002d13, 0x3516250c, // ro.zu.un_650 eu.fi.ceb_653 sk.hu.un_650 eu.hr.zu_543
+ 0x10000a07, 0x2d321702, 0x10000419, 0x093f0c0e, // mk.be.un_420 sr.bs.sk_222 ru.be.un_750 sv.af.pl_555
+ // [05c0]
+ 0x0f00101a, 0x08040aad, 0x13001c18, 0x316b20ee, // lt.lv.un_760 mk.ru.uk_643 mr.bh.un_740 sq.ceb.az_422
+ 0x3f030607, 0x091c0da9, 0x0910045a, 0x183f0107, // de.nl.af_432 ne.mr.hi_544 fi.lt.pl_553 en.af.ga_432
+ 0x00001a0a, 0x3f0603a0, 0x2a005208, 0x0900130c, // tl.un.un_500 nl.de.af_322 ha.mt.un_430 bh.hi.un_530
+ 0x13090da4, 0x0000531c, 0x03000212, 0x2d000d04, // ne.hi.bh_433 ht.un.un_800 da.nl.un_640 cs.sk.un_320
+ // [05d0]
+ 0x0800100d, 0x21001208, 0x64005505, 0x11070aaf, // be.uk.un_540 ur.fa.un_430 rw.lg.un_330 mk.bg.ro_655
+ 0x01001e02, 0x1c111ea7, 0x642835a4, 0x6b001604, // ms.en.un_220 ms.ro.id_532 zu.sw.lg_433 hr.ceb.un_320
+ 0x03281a07, 0x0e001a04, 0x17000a1a, 0x53000e04, // tl.sw.nl_432 tl.is.un_320 mk.sr.un_760 is.ht.un_320
+ 0x03063fa4, 0x00003b01, 0x0d112304, 0x0000230f, // af.de.nl_433 so.un.un_200 ca.ro.cs_332 ca.un.un_600
+ // [05e0]
+ 0x19000b08, 0x21001202, 0x18000e18, 0x03005207, // es.gl.un_430 ur.fa.un_220 is.ga.un_740 ha.nl.un_420
+ 0x136428ac, 0x1000111a, 0x18122d04, 0x07001119, // sw.lg.et_632 ro.be.un_760 sk.hu.ga_332 ro.bg.un_750
+ 0x0d130911, 0x52095502, 0x04131104, 0x180e01a0, // hi.bh.ne_653 rw.pl.ha_222 ro.et.fi_332 en.is.ga_322
+ 0x0c080e0d, 0x080704ad, 0x08020ca4, 0x17001005, // is.no.sv_554 ru.bg.uk_643 sv.da.no_433 lt.sr.un_330
+ // [05f0]
+ 0x0200081a, 0x0000550a, 0x10002908, 0x4a001218, // no.da.un_760 rw.un.un_500 sl.lt.un_430 hu.yo.un_740
+ 0x1202080c, 0x093235ee, 0x01000307, 0x0f0113a0, // no.da.hu_543 zu.bs.pl_422 nl.en.un_420 et.en.lv_322
+ 0x55354a11, 0x32001704, 0x01001f04, 0x09006405, // yo.zu.rw_653 sr.bs.un_320 cy.en.un_320 lg.pl.un_330
+ 0x0000310f, 0x52000807, 0x11000104, 0x082a0e0c, // az.un.un_600 no.ha.un_420 en.ro.un_320 is.mt.no_543
+ // [0600]
+ 0x68201113, 0x3b002119, 0x1b003119, 0x53000704, // ro.sq.ig_665 jw.so.un_750 az.tr.un_750 it.ht.un_320
+ 0x1c000d18, 0x0000521c, 0x08060ea4, 0x170804a4, // ne.mr.un_740 ha.un.un_800 is.de.no_433 ru.uk.sr_433
+ 0x0b00190c, 0x23001908, 0x25005307, 0x3f552104, // gl.es.un_530 gl.ca.un_430 ht.eu.un_420 jw.rw.af_332
+ 0x04000a0e, 0x0d0913a0, 0x23110707, 0x00001715, // pt.fi.un_550 bh.hi.ne_322 it.ro.ca_432 sr.un.un_700
+ // [0610]
+ 0x64005519, 0x1000290c, 0x27033fa0, 0x07170405, // rw.lg.un_750 sl.lt.un_530 af.nl.gd_322 ru.sr.bg_333
+ 0x00006403, 0x3b1e1c08, 0x6b1964a4, 0x3500521a, // lg.un.un_300 id.ms.so_443 lg.gl.ceb_433 ha.zu.un_760
+ 0x10000704, 0x0a0708a4, 0x0f002305, 0x00000403, // bg.be.un_320 uk.bg.mk_433 ca.lv.un_330 ru.un.un_300
+ 0x170f0707, 0x1b003513, 0x3f682011, 0x1e312a12, // it.lv.sr_432 zu.tr.un_650 sq.ig.af_653 mt.az.ms_654
+ // [0620]
+ 0x2a290daf, 0x1c1309ec, 0x1e001c0d, 0x3f0304a7, // cs.sl.mt_655 hi.bh.mr_644 id.ms.un_540 fi.nl.af_532
+ 0x04080aa4, 0x3b001e04, 0x1c001302, 0x08041004, // mk.uk.ru_433 ms.so.un_320 bh.mr.un_220 be.ru.uk_332
+ 0x2000250c, 0x3b0e2aad, 0x07000a13, 0x170a11a0, // eu.sq.un_530 mt.is.so_643 mk.bg.un_650 ro.mk.sr_322
+ 0x00000301, 0x231b3107, 0x05004a07, 0x190b0a04, // nl.un.un_200 az.tr.ca_432 yo.fr.un_420 pt.es.gl_332
+ // [0630]
+ 0x20001013, 0x28005504, 0x18000e1a, 0x08171104, // lt.sq.un_650 rw.sw.un_320 is.ga.un_760 ro.sr.uk_332
+ 0x01530c0b, 0x52072a07, 0x1b003204, 0x64521a08, // sv.ht.en_542 mt.it.ha_432 bs.tr.un_320 tl.ha.lg_443
+ 0x020c08ec, 0x23190b04, 0x2100121b, 0x12002114, // no.sv.da_644 es.gl.ca_332 ur.fa.un_770 fa.ur.un_660
+ 0x3b3f0313, 0x08002304, 0x08041707, 0x2800550e, // nl.af.so_665 ca.no.un_320 sr.ru.uk_432 rw.sw.un_550
+ // [0640]
+ 0x0600250c, 0x0b0a01a4, 0x20001904, 0x11001314, // eu.de.un_530 en.pt.es_433 gl.sq.un_320 et.ro.un_660
+ 0x6b001a14, 0x01002a02, 0x12182114, 0x20230555, // tl.ceb.un_660 mt.en.un_220 fa.ar.ur_666 fr.ca.sq_442
+ 0x0a001014, 0x6b000213, 0x4a033fad, 0x0a071755, // be.mk.un_660 da.ceb.un_650 af.nl.yo_643 sr.bg.mk_442
+ 0x0b2d0d55, 0x070a1104, 0x29001604, 0x2300200e, // cs.sk.es_442 ro.mk.bg_332 hr.sl.un_320 sq.ca.un_550
+ // [0650]
+ 0x354a6404, 0x20002d04, 0x1e211c04, 0x05005304, // lg.yo.zu_332 sk.sq.un_320 id.jw.ms_332 ht.fr.un_320
+ 0x02000808, 0x5305230c, 0x0a170812, 0x0a071713, // no.da.un_430 ca.fr.ht_543 uk.sr.mk_654 sr.bg.mk_665
+ 0x113f0e04, 0x53001f04, 0x0700110e, 0x53006413, // is.af.ro_332 cy.ht.un_320 ro.bg.un_550 lg.ht.un_650
+ 0x1f0511ad, 0x10000414, 0x06130807, 0x0000070a, // ro.fr.cy_643 ru.be.un_660 no.et.de_432 it.un.un_500
+ // [0660]
+ 0x64005304, 0x12190e05, 0x04001112, 0x01001004, // ht.lg.un_320 is.gl.hu_333 ro.ru.un_640 lt.en.un_320
+ 0x0d120960, 0x3b0413a9, 0x0a000508, 0x17002a04, // pl.hu.cs_664 et.fi.so_544 fr.pt.un_430 mt.sr.un_320
+ 0x352855ad, 0x00000615, 0x10006b02, 0x35002814, // rw.sw.zu_643 de.un.un_700 ceb.lt.un_220 sw.zu.un_660
+ 0x64001220, 0x133b64a0, 0x1a000704, 0x130d0912, // hu.lg.un_850 lg.so.et_322 it.tl.un_320 hi.ne.bh_654
+ // [0670]
+ 0x282a2905, 0x1c251e04, 0x20120e0e, 0x2d120e05, // sl.mt.sw_333 ms.eu.id_332 is.hu.sq_555 is.hu.sk_333
+ 0x2d120d0e, 0x07230a04, 0x190523a4, 0x170a11af, // cs.hu.sk_555 pt.ca.it_332 ca.fr.gl_433 ro.mk.sr_655
+ 0x21001814, 0x01002411, 0x35134a07, 0x07000a14, // ar.fa.un_660 yi.iw.un_630 yo.et.zu_432 mk.bg.un_660
+ 0x06000c0c, 0x271e1c07, 0x0a1708a4, 0x13006e09, // sv.de.un_530 id.ms.gd_432 uk.sr.mk_433 hmn.et.un_440
+ // [0680]
+ 0x64033f0c, 0x52080204, 0x53132a07, 0x190b2309, // af.nl.lg_543 da.no.ha_332 mt.et.ht_432 ca.es.gl_444
+ 0x033f25ec, 0x27001e0d, 0x00001b1c, 0x192a0aad, // eu.af.nl_644 ms.gd.un_540 tr.un.un_800 pt.mt.gl_643
+ 0x0d0913a7, 0x04682513, 0x01352a07, 0x35095508, // bh.hi.ne_532 eu.ig.fi_665 mt.zu.en_432 rw.pl.zu_443
+ 0x21181208, 0x2000270d, 0x31006b07, 0x08000c08, // ur.ar.fa_443 gd.sq.un_540 ceb.az.un_420 sv.no.un_430
+ // [0690]
+ 0x201703ee, 0x07000414, 0x19000d13, 0x3f00031a, // nl.sr.sq_422 ru.bg.un_660 cs.gl.un_650 nl.af.un_760
+ 0x2400011a, 0x042128a4, 0x211e29ee, 0x3f1118a0, // iw.yi.un_760 sw.jw.fi_433 sl.ms.jw_422 ga.ro.af_322
+ 0x12001902, 0x1c4a6b04, 0x190b31a0, 0x09000707, // gl.hu.un_220 ceb.yo.id_332 az.es.gl_322 it.pl.un_420
+ 0x25232a04, 0x0f003202, 0x0d091302, 0x12000912, // mt.ca.eu_332 bs.lv.un_220 bh.hi.ne_222 pl.hu.un_640
+ // [06a0]
+ 0x13042da4, 0x31002a0c, 0x2a2920a7, 0x10070812, // sk.fi.et_433 mt.az.un_530 sq.sl.mt_532 uk.bg.be_654
+ 0x3f040312, 0x2700181a, 0x00003206, 0x01000509, // nl.fi.af_654 ga.gd.un_760 bs.un.un_400 fr.en.un_440
+ 0x0d1c09a4, 0x091c2104, 0x080231ee, 0x110a07a4, // hi.mr.ne_433 jw.id.pl_332 az.da.no_422 bg.mk.ro_433
+ 0x1300070c, 0x551a6b12, 0x21001c07, 0x3b00350c, // it.et.un_530 ceb.tl.rw_654 id.jw.un_420 zu.so.un_530
+ // [06b0]
+ 0x12002119, 0x0d1c0955, 0x0c040107, 0x19052107, // fa.ur.un_750 hi.mr.ne_442 en.fi.sv_432 jw.fr.gl_432
+ 0x052301a4, 0x08020c02, 0x13006e0c, 0x353f0304, // en.ca.fr_433 sv.da.no_222 hmn.et.un_530 nl.af.zu_332
+ 0x3f006e0d, 0x0311085a, 0x64682160, 0x07040805, // hmn.af.un_540 no.ro.nl_553 jw.ig.lg_664 uk.ru.bg_333
+ 0x1a002b12, 0x17070a0e, 0x00001706, 0x08020ca0, // vi.tl.un_640 mk.bg.sr_555 sr.un.un_400 sv.da.no_322
+ // [06c0]
+ 0x08000714, 0x1b00110c, 0x52023ba4, 0x190a050c, // bg.uk.un_660 ro.tr.un_530 so.da.ha_433 fr.pt.gl_543
+ 0x092d0d09, 0x35001819, 0x0d09130c, 0x19002305, // cs.sk.pl_444 ga.zu.un_750 bh.hi.ne_543 ca.gl.un_330
+ 0x016b110c, 0x03041312, 0x32171602, 0x320701a4, // ro.ceb.en_543 et.fi.nl_654 hr.sr.bs_222 en.it.bs_433
+ 0x27186bee, 0x17070aa0, 0x13070305, 0x05214a04, // ceb.ga.gd_422 mk.bg.sr_322 nl.it.et_333 yo.jw.fr_332
+ // [06d0]
+ 0x214a13a6, 0x64130408, 0x283152a0, 0x532023ad, // et.yo.jw_521 fi.et.lg_443 ha.az.sw_322 ca.sq.ht_643
+ 0x2a5325ee, 0x10040612, 0x212d0d04, 0x18000104, // eu.ht.mt_422 de.fi.lt_654 cs.sk.jw_332 en.ga.un_320
+ 0x4a005204, 0x12190b08, 0x11070a05, 0x2d001812, // ha.yo.un_320 es.gl.hu_443 mk.bg.ro_333 ga.sk.un_640
+ 0x52002104, 0x08000c0c, 0x23000b02, 0x1b003114, // jw.ha.un_320 sv.no.un_530 es.ca.un_220 az.tr.un_660
+ // [06e0]
+ 0x06001908, 0x130d1c12, 0x0c005304, 0x643b2a07, // gl.de.un_430 mr.ne.bh_654 ht.sv.un_320 mt.so.lg_432
+ 0x070411ad, 0x281610ee, 0x64553507, 0x11002a0c, // ro.ru.bg_643 lt.hr.sw_422 zu.rw.lg_432 mt.ro.un_530
+ 0x0f002a12, 0x0f100807, 0x1c121e04, 0x1300041a, // mt.lv.un_640 no.lt.lv_432 ms.hu.id_332 fi.et.un_760
+ 0x2100120d, 0x16000b02, 0x0b0a2aa0, 0x081107a4, // ur.fa.un_540 es.hr.un_220 mt.pt.es_322 bg.ro.uk_433
+ // [06f0]
+ 0x31130612, 0x00002715, 0x3f0b01ad, 0x06002909, // de.et.az_654 gd.un.un_700 en.es.af_643 sl.de.un_440
+ 0x190a05a0, 0x041b0802, 0x3f6b28a0, 0x531c05ad, // fr.pt.gl_322 no.tr.fi_222 sw.ceb.af_322 fr.id.ht_643
+ 0x27001a04, 0x20000702, 0x1b003112, 0x0e0c1304, // tl.gd.un_320 it.sq.un_220 az.tr.un_640 et.sv.is_332
+ 0x08000414, 0x120d2807, 0x0804100d, 0x2000350e, // ru.uk.un_660 sw.cs.hu_432 be.ru.uk_554 zu.sq.un_550
+ // [0700]
+ 0x29001f07, 0x0f001213, 0x08070413, 0x03002302, // cy.sl.un_420 hu.lv.un_650 ru.bg.uk_665 ca.nl.un_220
+ 0x1b006408, 0x3b082a02, 0x233f030d, 0x12001f0c, // lg.tr.un_430 mt.no.so_222 nl.af.ca_554 cy.hu.un_530
+ 0x080c0e13, 0x35046b04, 0x09000d04, 0x01000504, // is.sv.no_665 ceb.fi.zu_332 ne.hi.un_320 fr.en.un_320
+ 0x23000f08, 0x2b002702, 0x170410a0, 0x17000402, // lv.ca.un_430 gd.vi.un_220 be.ru.sr_322 ru.sr.un_220
+ // [0710]
+ 0x0a0f1102, 0x1800120d, 0x201c28ee, 0x182b0aa0, // ro.lv.pt_222 ur.ar.un_540 sw.id.sq_422 pt.vi.ga_322
+ 0x2d002908, 0x013b2d07, 0x2a003112, 0x08023bee, // sl.sk.un_430 sk.so.en_432 az.mt.un_640 so.da.no_422
+ 0x640d28ee, 0x122a1fee, 0x04006404, 0x0800100e, // sw.cs.lg_422 cy.mt.hu_422 lg.fi.un_320 be.uk.un_550
+ 0x3f0610ee, 0x040a08a4, 0x13003519, 0x1f0e0807, // lt.de.af_422 uk.mk.ru_433 zu.et.un_750 no.is.cy_432
+ // [0720]
+ 0x1e213f04, 0x0a0f1208, 0x10041305, 0x2d0d12a4, // af.jw.ms_332 hu.lv.pt_443 et.fi.lt_333 hu.cs.sk_433
+ 0x11001214, 0x3f080204, 0x08002a07, 0x010c0504, // hu.ro.un_660 da.no.af_332 mt.no.un_420 fr.sv.en_332
+ 0x1b000c0b, 0x2a000104, 0x0600350d, 0x1a0a350c, // sv.tr.un_520 en.mt.un_320 zu.de.un_540 zu.pt.tl_543
+ 0x2800551a, 0x27182107, 0x55004a02, 0x203207a0, // rw.sw.un_760 jw.ga.gd_432 yo.rw.un_220 it.bs.sq_322
+ // [0730]
+ 0x016b0604, 0x0408170e, 0x080201a4, 0x00002303, // de.ceb.en_332 sr.uk.ru_555 en.da.no_433 ca.un.un_300
+ 0x040a170d, 0x1f3f64a0, 0x020c08a9, 0x0a005504, // sr.mk.ru_554 lg.af.cy_322 no.sv.da_544 rw.pt.un_320
+ 0x13000d05, 0x040817af, 0x0804070d, 0x0b230107, // ne.bh.un_330 sr.uk.ru_655 bg.ru.uk_554 en.ca.es_432
+ 0x2800290e, 0x53002a07, 0x20020ca4, 0x0a0407a4, // sl.sw.un_550 mt.ht.un_420 sv.da.sq_433 bg.ru.mk_433
+ // [0740]
+ 0x2000640c, 0x11271807, 0x16002912, 0x6b1a2112, // lg.sq.un_530 ga.gd.ro_432 sl.hr.un_640 jw.tl.ceb_654
+ 0x6b3f08ee, 0x1e003512, 0x00003103, 0x12003f04, // no.af.ceb_422 zu.ms.un_640 az.un.un_300 af.hu.un_320
+ 0x09001904, 0x16002007, 0x0b122da0, 0x6e212b05, // gl.pl.un_320 sq.hr.un_420 sk.hu.es_322 vi.jw.hmn_333
+ 0x08000713, 0x3b003512, 0x170807ee, 0x033f23a7, // bg.uk.un_650 zu.so.un_640 bg.uk.sr_422 ca.af.nl_532
+ // [0750]
+ 0x1e532aee, 0x1f000309, 0x23052104, 0x1f001c02, // mt.ht.ms_422 nl.cy.un_440 jw.fr.ca_332 id.cy.un_220
+ 0x1f000704, 0x3f1c2702, 0x1f001704, 0x53230504, // it.cy.un_320 gd.id.af_222 sr.cy.un_320 fr.ca.ht_332
+ 0x11040705, 0x31002011, 0x2d00290e, 0x100408a9, // bg.ru.ro_333 sq.az.un_630 sl.sk.un_550 uk.ru.be_544
+ 0x2b002102, 0x27180555, 0x2b000708, 0x00003501, // jw.vi.un_220 fr.ga.gd_442 it.vi.un_430 zu.un.un_200
+ // [0760]
+ 0x29001105, 0x1a003b19, 0x0a07170e, 0x27002d08, // ro.sl.un_330 so.tl.un_750 sr.bg.mk_555 sk.gd.un_430
+ 0x1e000607, 0x00000e0a, 0x07000412, 0x126b1a0c, // de.ms.un_420 is.un.un_500 ru.bg.un_640 tl.ceb.hu_543
+ 0x29003207, 0x07001114, 0x13000c08, 0x21101ea0, // bs.sl.un_420 ro.bg.un_660 sv.et.un_430 ms.lt.jw_322
+ 0x2a2301a0, 0x06080204, 0x0500070e, 0x04091fa7, // en.ca.mt_322 da.no.de_332 it.fr.un_550 cy.pl.fi_532
+ // [0770]
+ 0x21203bec, 0x532705a7, 0x10000412, 0x6b3b320d, // so.sq.jw_644 fr.gd.ht_532 ru.be.un_640 bs.so.ceb_554
+ 0x171107a4, 0x203519ee, 0x04006b05, 0x1f311b08, // bg.ro.sr_433 gl.zu.sq_422 ceb.fi.un_330 tr.az.cy_443
+ 0x28011802, 0x35006e18, 0x0b0a1fa0, 0x08002104, // ga.en.sw_222 hmn.zu.un_740 cy.pt.es_322 jw.no.un_320
+ 0x00003f03, 0x6b6410ee, 0x0800200e, 0x27000822, // af.un.un_300 lt.lg.ceb_422 sq.no.un_550 no.gd.un_870
+ // [0780]
+ 0x06001b19, 0x3b3555a0, 0x0804110b, 0x1b2a0713, // tr.de.un_750 rw.zu.so_322 ro.ru.uk_542 it.mt.tr_665
+ 0x6b001f12, 0x4a003b14, 0x0000310a, 0x3b2128a0, // cy.ceb.un_640 so.yo.un_660 az.un.un_500 sw.jw.so_322
+ 0x4a002d0d, 0x1c2352a6, 0x08041712, 0x10002302, // sk.yo.un_540 ha.ca.id_521 sr.ru.uk_654 ca.lt.un_220
+ 0x0c080209, 0x05002102, 0x0a0717af, 0x080225a0, // da.no.sv_444 jw.fr.un_220 sr.bg.mk_655 eu.da.no_322
+ // [0790]
+ 0x23190a13, 0x09001214, 0x08000419, 0x3b000b02, // pt.gl.ca_665 hu.pl.un_660 ru.uk.un_750 es.so.un_220
+ 0x17060804, 0x21005204, 0x28556407, 0x3f001e02, // no.de.sr_332 ha.jw.un_320 lg.rw.sw_432 ms.af.un_220
+ 0x20100fa6, 0x0a171104, 0x08001002, 0x25200e07, // lv.lt.sq_521 ro.sr.mk_332 be.uk.un_220 is.sq.eu_432
+ 0x3b1e1304, 0x10081705, 0x09001c14, 0x3f1a6ba0, // et.ms.so_332 sr.uk.be_333 mr.hi.un_660 ceb.tl.af_322
+ // [07a0]
+ 0x32001605, 0x0a00230e, 0x131a6b13, 0x2b192307, // hr.bs.un_330 ca.pt.un_550 ceb.tl.et_665 ca.gl.vi_432
+ 0x05002107, 0x5268200b, 0x32642808, 0x09001313, // jw.fr.un_420 sq.ig.ha_542 sw.lg.bs_443 bh.hi.un_650
+ 0x070a11a4, 0x120c1bad, 0x1c006407, 0x08041002, // ro.mk.bg_433 tr.sv.hu_643 lg.id.un_420 be.ru.uk_222
+ 0x2d0d09af, 0x18002704, 0x012d090d, 0x1c090d12, // pl.cs.sk_655 gd.ga.un_320 pl.sk.en_554 ne.hi.mr_654
+ // [07b0]
+ 0x120d09a4, 0x53006402, 0x2d6425a0, 0x0d000908, // pl.cs.hu_433 lg.ht.un_220 eu.lg.sk_322 hi.ne.un_430
+ 0x1218210c, 0x190b31a4, 0x1b311213, 0x06051304, // fa.ar.ur_543 az.es.gl_433 hu.az.tr_665 et.fr.de_332
+ 0x29203202, 0x04001013, 0x276b1a55, 0x21355355, // bs.sq.sl_222 be.ru.un_650 tl.ceb.gd_442 ht.zu.jw_442
+ 0x0c001f12, 0x01003507, 0x28101112, 0x051c03ac, // cy.sv.un_640 zu.en.un_420 ro.lt.sw_654 nl.id.fr_632
+ // [07c0]
+ 0x206b1a0c, 0x17000a05, 0x132507a0, 0x23001a07, // tl.ceb.sq_543 mk.sr.un_330 it.eu.et_322 tl.ca.un_420
+ 0x00006b24, 0x17040813, 0x25000f19, 0x6468355a, // ceb.un.un_900 uk.ru.sr_665 lv.eu.un_750 zu.ig.lg_553
+ 0x00001306, 0x6b1a27ad, 0x04000714, 0x291b3107, // bh.un.un_400 gd.tl.ceb_643 bg.ru.un_660 az.tr.sl_432
+ 0x060c0809, 0x53001704, 0x132506ee, 0x07311b55, // no.sv.de_444 sr.ht.un_320 de.eu.et_422 tr.az.it_442
+ // [07d0]
+ 0x17001f04, 0x1b2b05ec, 0x31001b0b, 0x00001c06, // cy.sr.un_320 fr.vi.tr_644 tr.az.un_520 id.un.un_400
+ 0x35001a04, 0x1a0e3b05, 0x0407080c, 0x31281b14, // tl.zu.un_320 so.is.tl_333 uk.bg.ru_543 tr.sw.az_666
+ 0x130d09ac, 0x09000d0e, 0x2100050e, 0x07001f04, // hi.ne.bh_632 ne.hi.un_550 fr.jw.un_550 cy.it.un_320
+ 0x5300011a, 0x0c2521ee, 0x08212008, 0x05190b02, // en.ht.un_760 jw.eu.sv_422 sq.jw.no_443 es.gl.fr_222
+ // [07e0]
+ 0x171104a4, 0x0b006b08, 0x3b07110e, 0x6b010404, // ru.ro.sr_433 ceb.es.un_430 ro.it.so_555 fi.en.ceb_332
+ 0x0c001f07, 0x010a2da0, 0x131b1aa0, 0x1e003f12, // cy.sv.un_420 sk.pt.en_322 tl.tr.et_322 af.ms.un_640
+ 0x172916a0, 0x10040805, 0x0912250c, 0x520a1fa9, // hr.sl.sr_322 uk.ru.be_333 eu.hu.pl_543 cy.pt.ha_544
+ 0x1b1e1c02, 0x28356404, 0x28352004, 0x686455a4, // id.ms.tr_222 lg.zu.sw_332 sq.zu.sw_332 rw.lg.ig_433
+ // [07f0]
+ 0x18002113, 0x0d1c1307, 0x01001a08, 0x170a1108, // fa.ar.un_650 bh.mr.ne_432 tl.en.un_430 ro.mk.sr_443
+ 0x09001304, 0x531903a0, 0x171307ee, 0x08000705, // bh.hi.un_320 nl.gl.ht_322 it.et.sr_422 it.no.un_330
+ 0x070a0412, 0x12001818, 0x13001c12, 0x016b3fee, // ru.mk.bg_654 ar.ur.un_740 mr.bh.un_640 af.ceb.en_422
+ 0x12004a04, 0x13000918, 0x3105350c, 0x06033f0c, // yo.hu.un_320 hi.bh.un_740 zu.fr.az_543 af.nl.de_543
+
+ // [0800]
+ 0x28006407, 0x311b2007, 0x0d091c07, 0x122d0d08, // lg.sw.un_420 sq.tr.az_432 mr.hi.ne_432 cs.sk.hu_443
+ 0x2d0d12ac, 0x1a006402, 0x31101b55, 0x31000504, // hu.cs.sk_632 lg.tl.un_220 tr.lt.az_442 fr.az.un_320
+ 0x07001002, 0x31000a04, 0x2d091205, 0x0b00230d, // be.bg.un_220 pt.az.un_320 hu.pl.sk_333 ca.es.un_540
+ 0x09006404, 0x1c0720a4, 0x07040aa0, 0x04002908, // lg.pl.un_320 sq.it.id_433 mk.ru.bg_322 sl.fi.un_430
+ // [0810]
+ 0x12053bac, 0x532b2807, 0x033f0613, 0x031c28ee, // so.fr.hu_632 sw.vi.ht_432 de.af.nl_665 sw.id.nl_422
+ 0x00004a24, 0x23001909, 0x53000418, 0x520635ad, // yo.un.un_900 gl.ca.un_440 fi.ht.un_740 zu.de.ha_643
+ 0x040710a0, 0x122118a9, 0x04080aa0, 0x04170708, // be.bg.ru_322 ar.fa.ur_544 mk.uk.ru_322 bg.sr.ru_443
+ 0x6b0e1a12, 0x2500351a, 0x6b0e1aec, 0x18002709, // tl.is.ceb_654 zu.eu.un_760 tl.is.ceb_644 gd.ga.un_440
+ // [0820]
+ 0x211e1c55, 0x52002121, 0x0a1707a4, 0x08001012, // id.ms.jw_442 jw.ha.un_860 bg.sr.mk_433 be.uk.un_640
+ 0x0c190b08, 0x17001113, 0x0a17070e, 0x1c002104, // es.gl.sv_443 ro.sr.un_650 bg.sr.mk_555 jw.id.un_320
+ 0x060208a0, 0x0b033f04, 0x07000b02, 0x0a0417a0, // no.da.de_322 af.nl.es_332 es.it.un_220 sr.ru.mk_322
+ 0x6423530c, 0x00002d1c, 0x29001f18, 0x110a1708, // ht.ca.lg_543 sk.un.un_800 cy.sl.un_740 sr.mk.ro_443
+ // [0830]
+ 0x35552812, 0x0c0208a4, 0x52000e08, 0x08033f08, // sw.rw.zu_654 no.da.sv_433 is.ha.un_430 af.nl.no_443
+ 0x080411ee, 0x08000707, 0x04123509, 0x55112007, // ro.ru.uk_422 bg.uk.un_420 zu.hu.fi_444 sq.ro.rw_432
+ 0x12556b0b, 0x6b3b1a12, 0x280131a0, 0x1c0d090c, // ceb.rw.hu_542 tl.so.ceb_654 az.en.sw_322 hi.ne.mr_543
+ 0x293b52a0, 0x29321704, 0x00000e1c, 0x2d290dee, // ha.so.sl_322 sr.bs.sl_332 is.un.un_800 cs.sl.sk_422
+ // [0840]
+ 0x350955a7, 0x08070408, 0x056e2007, 0x1c0d1305, // rw.pl.zu_532 ru.bg.uk_443 sq.hmn.fr_432 bh.ne.mr_333
+ 0x070a17af, 0x06040c07, 0x0c003f07, 0x6b521a14, // sr.mk.bg_655 sv.fi.de_432 af.sv.un_420 tl.ha.ceb_666
+ 0x04005209, 0x1e1c55a0, 0x2d1b3104, 0x161a2d07, // ha.fi.un_440 rw.id.ms_322 az.tr.sk_332 sk.tl.hr_432
+ 0x53002708, 0x13091ca9, 0x0f1017a0, 0x1000081a, // gd.ht.un_430 mr.hi.bh_544 sr.lt.lv_322 uk.be.un_760
+ // [0850]
+ 0x070a11a0, 0x06000c08, 0x07002507, 0x52001f02, // ro.mk.bg_322 sv.de.un_430 eu.it.un_420 cy.ha.un_220
+ 0x03003f12, 0x0f00040c, 0x2d0d05ec, 0x091c1304, // af.nl.un_640 fi.lv.un_530 fr.cs.sk_644 bh.mr.hi_332
+ 0x17290d07, 0x13041aa0, 0x530d0405, 0x3b356404, // cs.sl.sr_432 tl.fi.et_322 fi.cs.ht_333 lg.zu.so_332
+ 0x05100107, 0x0b000a12, 0x17000804, 0x17000905, // en.lt.fr_432 pt.es.un_640 uk.sr.un_320 pl.sr.un_330
+ // [0860]
+ 0x3200520c, 0x09000d05, 0x112520ad, 0x35041004, // ha.bs.un_530 ne.hi.un_330 sq.eu.ro_643 lt.fi.zu_332
+ 0x201b3204, 0x551e28a4, 0x07001013, 0x0900550c, // bs.tr.sq_332 sw.ms.rw_433 be.bg.un_650 rw.pl.un_530
+ 0x3f3b03af, 0x53680107, 0x19003b19, 0x0e000212, // nl.so.af_655 en.ig.ht_432 so.gl.un_750 da.is.un_640
+ 0x161f2aa4, 0x0a190b0d, 0x3200170c, 0x321716af, // mt.cy.hr_433 es.gl.pt_554 sr.bs.un_530 hr.sr.bs_655
+ // [0870]
+ 0x1c000908, 0x12003512, 0x2a0e2808, 0x250604ee, // hi.mr.un_430 zu.hu.un_640 sw.is.mt_443 fi.de.eu_422
+ 0x0a1107a4, 0x17163209, 0x1b000614, 0x09001318, // bg.ro.mk_433 bs.hr.sr_444 de.tr.un_660 bh.hi.un_740
+ 0x0800171a, 0x1b31040c, 0x04202bad, 0x3b3f0307, // sr.uk.un_760 fi.az.tr_543 vi.sq.fi_643 nl.af.so_432
+ 0x04001007, 0x0c001f04, 0x0a001704, 0x05003f07, // be.ru.un_420 cy.sv.un_320 sr.mk.un_320 af.fr.un_420
+ // [0880]
+ 0x08021105, 0x0a170808, 0x35003f08, 0x09200408, // ro.da.no_333 uk.sr.mk_443 af.zu.un_430 fi.sq.pl_443
+ 0x091f18af, 0x06006b04, 0x1200180e, 0x4a003f19, // ga.cy.pl_655 ceb.de.un_320 ga.hu.un_550 af.yo.un_750
+ 0x3b4a53ac, 0x35000b04, 0x04006b04, 0x0e0c08a4, // ht.yo.so_632 es.zu.un_320 ceb.fi.un_320 no.sv.is_433
+ 0x16000908, 0x023f0355, 0x1e001c07, 0x0f321702, // pl.hr.un_430 nl.af.da_442 id.ms.un_420 sr.bs.lv_222
+ // [0890]
+ 0x2b002304, 0x170711ee, 0x0a071113, 0x09001302, // ca.vi.un_320 ro.bg.sr_422 ro.bg.mk_665 bh.hi.un_220
+ 0x0d091c60, 0x6b2d0dad, 0x2100121a, 0x06531f0d, // mr.hi.ne_664 cs.sk.ceb_643 ur.fa.un_760 cy.ht.de_554
+ 0x29005308, 0x1b001e04, 0x25002014, 0x122d0d13, // ht.sl.un_430 ms.tr.un_320 sq.eu.un_660 cs.sk.hu_665
+ 0x1c521e04, 0x06010507, 0x110405a4, 0x133b3f04, // ms.ha.id_332 fr.en.de_432 fr.fi.ro_433 af.so.et_332
+ // [08a0]
+ 0x1c6b1a04, 0x0d091cec, 0x13000902, 0x1e1c2102, // tl.ceb.id_332 mr.hi.ne_644 hi.bh.un_220 jw.id.ms_222
+ 0x211e1c05, 0x0c002504, 0x2168280c, 0x1c001e08, // id.ms.jw_333 eu.sv.un_320 sw.ig.jw_543 ms.id.un_430
+ 0x04000312, 0x12080aa7, 0x0a001908, 0x07170805, // nl.fi.un_640 pt.no.hu_532 gl.pt.un_430 uk.sr.bg_333
+ 0x211c2aa0, 0x31001908, 0x1c0913a4, 0x230a0704, // mt.id.jw_322 gl.az.un_430 bh.hi.mr_433 it.pt.ca_332
+ // [08b0]
+ 0x19000b0b, 0x091c0d12, 0x12006b08, 0x0f321708, // es.gl.un_520 ne.mr.hi_654 ceb.hu.un_430 sr.bs.lv_443
+ 0x64080202, 0x21001212, 0x190b23ee, 0x4a006819, // da.no.lg_222 hu.jw.un_640 ca.es.gl_422 ig.yo.un_750
+ 0x122118af, 0x01033b0d, 0x170a115a, 0x0a1c21a0, // ar.fa.ur_655 so.nl.en_554 ro.mk.sr_553 jw.id.pt_322
+ 0x1105010c, 0x1b006409, 0x190b0aa6, 0x0b0705ee, // en.fr.ro_543 lg.tr.un_440 pt.es.gl_521 fr.it.es_422
+ // [08c0]
+ 0x17080412, 0x18005204, 0x20230aa7, 0x033f01af, // ru.uk.sr_654 ha.ga.un_320 pt.ca.sq_532 en.af.nl_655
+ 0x00001a15, 0x32002004, 0x09001312, 0x23042504, // tl.un.un_700 sq.bs.un_320 bh.hi.un_640 eu.fi.ca_332
+ 0x0b000108, 0x130603ee, 0x252313ac, 0x3f020e0c, // en.es.un_430 nl.de.et_422 et.ca.eu_632 is.da.af_543
+ 0x12000b04, 0x11000108, 0x682a52a9, 0x1c002012, // es.hu.un_320 en.ro.un_430 ha.mt.ig_544 sq.id.un_640
+ // [08d0]
+ 0x0b121812, 0x0c53010b, 0x05002b12, 0x64001113, // ga.hu.es_654 en.ht.sv_542 vi.fr.un_640 ro.lg.un_650
+ 0x11000a08, 0x07171155, 0x131c09ad, 0x31020811, // mk.ro.un_430 ro.sr.bg_442 hi.mr.bh_643 no.da.az_653
+ 0x21002704, 0x64001a12, 0x03000c05, 0x09002d0d, // gd.jw.un_320 tl.lg.un_640 sv.nl.un_330 sk.pl.un_540
+ 0x64551213, 0x68002b0e, 0x3b4a6411, 0x08001704, // hu.rw.lg_665 vi.ig.un_550 lg.yo.so_653 sr.uk.un_320
+ // [08e0]
+ 0x6b00010c, 0x07111055, 0x6e1235a0, 0x271a1807, // en.ceb.un_530 be.ro.bg_442 zu.hu.hmn_322 ga.tl.gd_432
+ 0x00000401, 0x0c311b07, 0x4a002b0c, 0x04000819, // ru.un.un_200 tr.az.sv_432 vi.yo.un_530 uk.ru.un_750
+ 0x320f1707, 0x08001113, 0x07000419, 0x27051902, // sr.lv.bs_432 ro.uk.un_650 ru.bg.un_750 gl.fr.gd_222
+ 0x09101fad, 0x051f07ee, 0x25005312, 0x00000803, // cy.lt.pl_643 it.cy.fr_422 ht.eu.un_640 uk.un.un_300
+ // [08f0]
+ 0x091f3b04, 0x09005311, 0x0d091c14, 0x08070aa0, // so.cy.pl_332 ht.pl.un_630 mr.hi.ne_666 mk.bg.uk_322
+ 0x0a070414, 0x21095507, 0x251805ad, 0x21006807, // ru.bg.mk_666 rw.pl.jw_432 fr.ga.eu_643 ig.jw.un_420
+ 0x1f000708, 0x18120d0b, 0x1c3b1eee, 0x25006404, // it.cy.un_430 cs.hu.ga_542 ms.so.id_422 lg.eu.un_320
+ 0x2818270c, 0x0000090a, 0x190a05a6, 0x6b2a3b07, // gd.ga.sw_543 pl.un.un_500 fr.pt.gl_521 so.mt.ceb_432
+ // [0900]
+ 0x64101a5a, 0x554a0807, 0x0c0e08a0, 0x1e1c01ee, // tl.lt.lg_553 no.yo.rw_432 no.is.sv_322 en.id.ms_422
+ 0x27000108, 0x10040812, 0x033f080c, 0x21006b07, // en.gd.un_430 uk.ru.be_654 no.af.nl_543 ceb.jw.un_420
+ 0x16006b02, 0x0d1c130c, 0x552864ad, 0x0c003b04, // ceb.hr.un_220 bh.mr.ne_543 lg.sw.rw_643 so.sv.un_320
+ 0x06230a0c, 0x6b3b0455, 0x023f13ec, 0x00000a1c, // pt.ca.de_543 fi.so.ceb_442 et.af.da_644 mk.un.un_800
+ // [0910]
+ 0x12000a0c, 0x020c13a0, 0x0b000a08, 0x091c13a9, // pt.hu.un_530 et.sv.da_322 pt.es.un_430 bh.mr.hi_544
+ 0x09000d14, 0x10003f13, 0x1b00250d, 0x17001014, // ne.hi.un_660 af.lt.un_650 eu.tr.un_540 be.sr.un_660
+ 0x130d09ec, 0x0d001904, 0x0a110408, 0x23002d08, // hi.ne.bh_644 gl.cs.un_320 ru.ro.mk_443 sk.ca.un_430
+ 0x09000d12, 0x31552508, 0x02000302, 0x6b000104, // ne.hi.un_640 eu.rw.az_443 nl.da.un_220 en.ceb.un_320
+ // [0920]
+ 0x21001e02, 0x040a1107, 0x320f1012, 0x4a003112, // ms.jw.un_220 ro.mk.ru_432 lt.lv.bs_654 az.yo.un_640
+ 0x041711ad, 0x130c0411, 0x00000a01, 0x163217a7, // ro.sr.ru_643 fi.sv.et_653 mk.un.un_200 sr.bs.hr_532
+ 0x3b001907, 0x18000f19, 0x06293504, 0x286e3bad, // gl.so.un_420 lv.ga.un_750 zu.sl.de_332 so.hmn.sw_643
+ 0x111f2512, 0x6b001a1a, 0x3f020ead, 0x203153ee, // eu.cy.ro_654 tl.ceb.un_760 is.da.af_643 ht.az.sq_422
+ // [0930]
+ 0x00000503, 0x1c005505, 0x27001c02, 0x17080405, // fr.un.un_300 rw.id.un_330 id.gd.un_220 ru.uk.sr_333
+ 0x13091ca4, 0x16290e07, 0x080410ad, 0x2b086bee, // mr.hi.bh_433 is.sl.hr_432 be.ru.uk_643 ceb.no.vi_422
+ 0x182112a9, 0x21001c05, 0x100313a0, 0x040810a9, // ur.fa.ar_544 id.jw.un_330 et.nl.lt_322 be.uk.ru_544
+ 0x0d2d12a9, 0x253f03a4, 0x200f1b07, 0x556801ee, // hu.sk.cs_544 nl.af.eu_433 tr.lv.sq_432 en.ig.rw_422
+ // [0940]
+ 0x102007ee, 0x271811a4, 0x0a2d0d0b, 0x0b033fa4, // it.sq.lt_422 ro.ga.gd_433 cs.sk.pt_542 af.nl.es_433
+ 0x00000c03, 0x0d2d2104, 0x32162aa9, 0x172901a4, // sv.un.un_300 jw.sk.cs_332 mt.hr.bs_544 en.sl.sr_433
+ 0x0810110b, 0x12211813, 0x55000112, 0x0c000808, // ro.be.uk_542 ar.fa.ur_665 en.rw.un_640 no.sv.un_430
+ 0x110701a4, 0x03023fa7, 0x04252dec, 0x00000f0a, // en.it.ro_433 af.da.nl_532 sk.eu.fi_644 lv.un.un_500
+ // [0950]
+ 0x0c00020e, 0x4a190b08, 0x4a00351a, 0x4a2520ee, // da.sv.un_550 es.gl.yo_443 zu.yo.un_760 sq.eu.yo_422
+ 0x02000c08, 0x04000814, 0x0804170e, 0x3f000108, // sv.da.un_430 uk.ru.un_660 sr.ru.uk_555 en.af.un_430
+ 0x00002124, 0x01003202, 0x07000405, 0x643555a4, // jw.un.un_900 bs.en.un_220 fi.it.un_330 rw.zu.lg_433
+ 0x206b1aa4, 0x4a00681a, 0x200c12af, 0x28001207, // tl.ceb.sq_433 ig.yo.un_760 hu.sv.sq_655 hu.sw.un_420
+ // [0960]
+ 0x0d091309, 0x35004a11, 0x2d0d3bee, 0x6b1b35a4, // bh.hi.ne_444 yo.zu.un_630 so.cs.sk_422 zu.tr.ceb_433
+ 0x2a071312, 0x28643560, 0x0e190b55, 0x040a07a4, // et.it.mt_654 zu.lg.sw_664 es.gl.is_442 bg.mk.ru_433
+ 0x05005302, 0x09000d08, 0x0f2028ad, 0x0d091ca0, // ht.fr.un_220 ne.hi.un_430 sw.sq.lv_643 mr.hi.ne_322
+ 0x23033f0c, 0x23551105, 0x0f321704, 0x131c0911, // af.nl.ca_543 ro.rw.ca_333 sr.bs.lv_332 hi.mr.bh_653
+ // [0970]
+ 0x021310a4, 0x11170a12, 0x12000b02, 0x3b100704, // lt.et.da_433 mk.sr.ro_654 es.hu.un_220 it.lt.so_332
+ 0x04170a04, 0x2500111a, 0x0708170e, 0x0a080702, // mk.sr.ru_332 ro.eu.un_760 sr.uk.bg_555 bg.uk.mk_222
+ 0x06002507, 0x281355af, 0x04000c0e, 0x312a0704, // eu.de.un_420 rw.et.sw_655 sv.fi.un_550 it.mt.az_332
+ 0x182d0d05, 0x1a1b6ba4, 0x13006407, 0x1e1c21a9, // cs.sk.ga_333 ceb.tr.tl_433 lg.et.un_420 jw.id.ms_544
+ // [0980]
+ 0x06006807, 0x062313ec, 0x13090daf, 0x310f1b07, // ig.de.un_420 et.ca.de_644 ne.hi.bh_655 tr.lv.az_432
+ 0x0d001c12, 0x09000613, 0x112723a0, 0x23001107, // mr.ne.un_640 de.pl.un_650 ca.gd.ro_322 ro.ca.un_420
+ 0x35311bad, 0x01002408, 0x0d130908, 0x295528af, // tr.az.zu_643 yi.iw.un_430 hi.bh.ne_443 sw.rw.sl_655
+ 0x080a07a4, 0x13002507, 0x2a0f1208, 0x53001b19, // bg.mk.uk_433 eu.et.un_420 hu.lv.mt_443 tr.ht.un_750
+ // [0990]
+ 0x68182ba4, 0x131a35a0, 0x28080c08, 0x29001707, // vi.ga.ig_433 zu.tl.et_322 sv.no.sw_443 sr.sl.un_420
+ 0x553b06ee, 0x02041355, 0x3f000313, 0x2800070e, // de.so.rw_422 et.fi.da_442 nl.af.un_650 it.sw.un_550
+ 0x35001c04, 0x02000e0d, 0x2164280c, 0x190a0b09, // id.zu.un_320 is.da.un_540 sw.lg.jw_543 es.pt.gl_444
+ 0x321e55a0, 0x35006802, 0x16000f0b, 0x23002d04, // rw.ms.bs_322 ig.zu.un_220 lv.hr.un_520 sk.ca.un_320
+ // [09a0]
+ 0x0513110c, 0x2d0d09ec, 0x190a0b0c, 0x29001702, // ro.et.fr_543 pl.cs.sk_644 es.pt.gl_543 sr.sl.un_220
+ 0x552835a9, 0x17080d07, 0x31002013, 0x35003f14, // zu.sw.rw_544 cs.no.sr_432 sq.az.un_650 af.zu.un_660
+ 0x1c0d1302, 0x1b003b13, 0x12000f13, 0x2d000307, // bh.ne.mr_222 so.tr.un_650 lv.hu.un_650 nl.sk.un_420
+ 0x06000c12, 0x0d1219a0, 0x2855640d, 0x08040705, // sv.de.un_640 gl.hu.cs_322 lg.rw.sw_554 bg.ru.uk_333
+ // [09b0]
+ 0x13002113, 0x04001705, 0x0d001604, 0x0e643fa0, // jw.et.un_650 sr.ru.un_330 hr.cs.un_320 af.lg.is_322
+ 0x211a6bad, 0x133f0411, 0x0d130913, 0x1c3b2aa0, // ceb.tl.jw_643 fi.af.et_653 hi.bh.ne_665 mt.so.id_322
+ 0x20556407, 0x553f03ad, 0x281a3505, 0x033f0608, // lg.rw.sq_432 nl.af.rw_643 zu.tl.sw_333 de.af.nl_443
+ 0x55002819, 0x25001b19, 0x09001702, 0x1a0a280d, // sw.rw.un_750 tr.eu.un_750 sr.pl.un_220 sw.pt.tl_554
+ // [09c0]
+ 0x31001e05, 0x25190b04, 0x6b55640d, 0x0d001c1b, // ms.az.un_330 es.gl.eu_332 lg.rw.ceb_554 mr.ne.un_770
+ 0x03083fa0, 0x3b006409, 0x4a006405, 0x092a0860, // af.no.nl_322 lg.so.un_440 lg.yo.un_330 no.mt.pl_664
+ 0x28005207, 0x090408a0, 0x55216407, 0x19120b0c, // ha.sw.un_420 no.fi.pl_322 lg.jw.rw_432 es.hu.gl_543
+ 0x1b2a07a0, 0x06003f04, 0x07080204, 0x00000f06, // it.mt.tr_322 af.de.un_320 da.no.it_332 lv.un.un_400
+ // [09d0]
+ 0x18000e08, 0x1c001f05, 0x041007a0, 0x231c210c, // is.ga.un_430 cy.id.un_330 bg.be.ru_322 jw.id.ca_543
+ 0x080c0307, 0x01522807, 0x040e080b, 0x4a000a0d, // nl.sv.no_432 sw.ha.en_432 no.is.fi_542 pt.yo.un_540
+ 0x02292d07, 0x0d2d0611, 0x16005507, 0x31006b04, // sk.sl.da_432 de.sk.cs_653 rw.hr.un_420 ceb.az.un_320
+ 0x52643bec, 0x35072555, 0x1c0913a7, 0x0d00090c, // so.lg.ha_644 eu.it.zu_442 bh.hi.mr_532 hi.ne.un_530
+ // [09e0]
+ 0x2b1a01a0, 0x1f6b200d, 0x02033fa0, 0x6b6e2bad, // en.tl.vi_322 sq.ceb.cy_554 af.nl.da_322 vi.hmn.ceb_643
+ 0x0d000919, 0x3b001905, 0x55002811, 0x2835550e, // hi.ne.un_750 gl.so.un_330 sw.rw.un_630 rw.zu.sw_555
+ 0x3f122da0, 0x0e000607, 0x110408a4, 0x20210fa0, // sk.hu.af_322 de.is.un_420 uk.ru.ro_433 lv.jw.sq_322
+ 0x2000210d, 0x3200280c, 0x0c000804, 0x4a002819, // jw.sq.un_540 sw.bs.un_530 no.sv.un_320 sw.yo.un_750
+ // [09f0]
+ 0x10552d0c, 0x0c0f3fee, 0x27005307, 0x160d2904, // sk.rw.lt_543 af.lv.sv_422 ht.gd.un_420 sl.cs.hr_332
+ 0x1c033f07, 0x2a0d0908, 0x0c000412, 0x4a000613, // af.nl.id_432 pl.cs.mt_443 fi.sv.un_640 de.yo.un_650
+ 0x18042709, 0x070c1307, 0x35002819, 0x026b1a55, // gd.fi.ga_444 et.sv.it_432 sw.zu.un_750 tl.ceb.da_442
+ 0x1b5325ad, 0x0400070e, 0x19000a0e, 0x6e3f2b08, // eu.ht.tr_643 bg.ru.un_550 pt.gl.un_550 vi.af.hmn_443
+ // [0a00]
+ 0x07001008, 0x123f1007, 0x1c213fa0, 0x00002506, // lt.it.un_430 lt.af.hu_432 af.jw.id_322 eu.un.un_400
+ 0x11002512, 0x20002812, 0x2d0d29a0, 0x6b2025a0, // eu.ro.un_640 sw.sq.un_640 sl.cs.sk_322 eu.sq.ceb_322
+ 0x130d0913, 0x040807a0, 0x21356407, 0x10000912, // hi.ne.bh_665 bg.uk.ru_322 lg.zu.jw_432 pl.lt.un_640
+ 0x04000808, 0x13041a07, 0x0800101a, 0x25122304, // uk.ru.un_430 tl.fi.et_432 be.uk.un_760 ca.hu.eu_332
+ // [0a10]
+ 0x0c0807ad, 0x1c000d14, 0x0500110d, 0x06003f08, // it.no.sv_643 ne.mr.un_660 ro.fr.un_540 af.de.un_430
+ 0x171629a0, 0x090d13ad, 0x1b536408, 0x356452a4, // sl.hr.sr_322 bh.ne.hi_643 lg.ht.tr_443 ha.lg.zu_433
+ 0x0a04170d, 0x21001602, 0x1a1b3b07, 0x060f1b07, // sr.ru.mk_554 hr.jw.un_220 so.tr.tl_432 tr.lv.de_432
+ 0x023f08a0, 0x25182702, 0x311e29ac, 0x112301a4, // no.af.da_322 gd.ga.eu_222 sl.ms.az_632 en.ca.ro_433
+ // [0a20]
+ 0x64005521, 0x0c002904, 0x3b211f04, 0x080c0204, // rw.lg.un_860 sl.sv.un_320 cy.jw.so_332 da.sv.no_332
+ 0x032911a0, 0x1e1c21a0, 0x6b0431ee, 0x55646ba4, // ro.sl.nl_322 jw.id.ms_322 az.fi.ceb_422 ceb.lg.rw_433
+ 0x2100120b, 0x280652a0, 0x0d000912, 0x04000712, // ur.fa.un_520 ha.de.sw_322 hi.ne.un_640 bg.ru.un_640
+ 0x0c00270d, 0x4a005212, 0x53641ea0, 0x00000215, // gd.sv.un_540 ha.yo.un_640 ms.lg.ht_322 da.un.un_700
+ // [0a30]
+ 0x130d0914, 0x28001e05, 0x4a1f5555, 0x162d6bee, // hi.ne.bh_666 ms.sw.un_330 rw.cy.yo_442 ceb.sk.hr_422
+ 0x04071004, 0x13040c11, 0x21002808, 0x1b1007ee, // be.bg.ru_332 sv.fi.et_653 sw.jw.un_430 it.lt.tr_422
+ 0x2d321604, 0x08003504, 0x252135a4, 0x0f002913, // hr.bs.sk_332 zu.no.un_320 zu.jw.eu_433 sl.lv.un_650
+ 0x35002a08, 0x641e2104, 0x10033f07, 0x32160d0c, // mt.zu.un_430 jw.ms.lg_332 af.nl.lt_432 cs.hr.bs_543
+ // [0a40]
+ 0x3100010c, 0x1c0913ec, 0x4a003202, 0x00003503, // en.az.un_530 bh.hi.mr_644 bs.yo.un_220 zu.un.un_300
+ 0x6b2a5504, 0x12005205, 0x35531ba4, 0x04081004, // rw.mt.ceb_332 ha.hu.un_330 tr.ht.zu_433 be.uk.ru_332
+ 0x1f000c13, 0x25190b02, 0x31645504, 0x05013bee, // sv.cy.un_650 es.gl.eu_222 rw.lg.az_332 so.en.fr_422
+ 0x351355ec, 0x0c6408a4, 0x081c2104, 0x20682907, // rw.et.zu_644 no.lg.sv_433 jw.id.no_332 sl.ig.sq_432
+ // [0a50]
+ 0x32161214, 0x25211aa4, 0x0c002812, 0x00001301, // hu.hr.bs_666 tl.jw.eu_433 sw.sv.un_640 bh.un.un_200
+ 0x120408a0, 0x1716290c, 0x52645508, 0x12130112, // no.fi.hu_322 sl.hr.sr_543 rw.lg.ha_443 en.et.hu_654
+ 0x050b19ee, 0x643b550c, 0x1716130c, 0x08122007, // gl.es.fr_422 rw.so.lg_543 et.hr.sr_543 sq.hu.no_432
+ 0x53001a02, 0x230b19ee, 0x201153a4, 0x0f001702, // tl.ht.un_220 gl.es.ca_422 ht.ro.sq_433 sr.lv.un_220
+ // [0a60]
+ 0x0e001904, 0x040a17a4, 0x2710250c, 0x0c033f07, // gl.is.un_320 sr.mk.ru_433 eu.lt.gd_543 af.nl.sv_432
+ 0x08062304, 0x1c4a5507, 0x31071baf, 0x01060ca0, // ca.de.no_332 rw.yo.id_432 tr.it.az_655 sv.de.en_322
+ 0x08091f04, 0x23000804, 0x03100107, 0x2d0d16a0, // cy.pl.no_332 no.ca.un_320 en.lt.nl_432 hr.cs.sk_322
+ 0x13000a07, 0x311f3507, 0x0a0711ee, 0x0c08030b, // pt.et.un_420 zu.cy.az_432 ro.bg.mk_422 nl.no.sv_542
+ // [0a70]
+ 0x173112a4, 0x64352808, 0x350453a4, 0x1b0e1f07, // hu.az.sr_433 sw.zu.lg_443 ht.fi.zu_433 cy.is.tr_432
+ 0x0e001814, 0x170710a0, 0x4a3f03ee, 0x25005202, // ga.is.un_660 be.bg.sr_322 nl.af.yo_422 ha.eu.un_220
+ 0x31051104, 0x682a1bee, 0x251f0107, 0x0c0608a0, // ro.fr.az_332 tr.mt.ig_422 en.cy.eu_432 no.de.sv_322
+ 0x181c1fee, 0x2d001213, 0x32171655, 0x6400680d, // cy.id.ga_422 hu.sk.un_650 hr.sr.bs_442 ig.lg.un_540
+ // [0a80]
+ 0x2d0d4a09, 0x211e1c09, 0x09001c18, 0x29000e0d, // yo.cs.sk_444 id.ms.jw_444 mr.hi.un_740 is.sl.un_540
+ 0x4a2d0d0d, 0x21001a04, 0x121b2004, 0x2a002804, // cs.sk.yo_554 tl.jw.un_320 sq.tr.hu_332 sw.mt.un_320
+ 0x31001b07, 0x3b006b07, 0x3b2b6b07, 0x2d1721ad, // tr.az.un_420 ceb.so.un_420 ceb.vi.so_432 jw.sr.sk_643
+ 0x0828350d, 0x04001002, 0x050711ee, 0x1b313fa0, // zu.sw.no_554 be.ru.un_220 ro.it.fr_422 af.az.tr_322
+ // [0a90]
+ 0x0f000707, 0x1c3521a4, 0x0a0817a4, 0x04000a05, // it.lv.un_420 jw.zu.id_433 sr.uk.mk_433 mk.ru.un_330
+ 0x211c1e07, 0x2a001c02, 0x23005313, 0x525535a0, // ms.id.jw_432 id.mt.un_220 ht.ca.un_650 zu.rw.ha_322
+ 0x10000f13, 0x321708ee, 0x28000807, 0x350705af, // lv.lt.un_650 no.sr.bs_422 no.sw.un_420 fr.it.zu_655
+ 0x121b1ca7, 0x041708ec, 0x07041007, 0x321c16a0, // id.tr.hu_532 uk.sr.ru_644 be.ru.bg_432 hr.id.bs_322
+ // [0aa0]
+ 0x55643508, 0x0d00090e, 0x212964a0, 0x28354a12, // zu.lg.rw_443 hi.ne.un_550 lg.sl.jw_322 yo.zu.sw_654
+ 0x040a1711, 0x0a000708, 0x2d290d0e, 0x050820ee, // sr.mk.ru_653 bg.mk.un_430 cs.sl.sk_555 sq.no.fr_422
+ 0x0a0c0107, 0x130d09af, 0x00003f0f, 0x07170a13, // en.sv.pt_432 hi.ne.bh_655 af.un.un_600 mk.sr.bg_665
+ 0x083f2302, 0x00001f24, 0x0b00011a, 0x1b002119, // ca.af.no_222 cy.un.un_900 en.es.un_760 jw.tr.un_750
+ // [0ab0]
+ 0x04120cee, 0x0c0204af, 0x06032702, 0x0d4a3202, // sv.hu.fi_422 fi.da.sv_655 gd.nl.de_222 bs.yo.cs_222
+ 0x522105ee, 0x0000181c, 0x291c21ee, 0x32001208, // fr.jw.ha_422 ar.un.un_800 jw.id.sl_422 hu.bs.un_430
+ 0x2a000708, 0x3b10290c, 0x190b0a0e, 0x1c000304, // it.mt.un_430 sl.lt.so_543 pt.es.gl_555 nl.id.un_320
+ 0x55641aa4, 0x3b0607ad, 0x2d1b310c, 0x64000713, // tl.lg.rw_433 it.de.so_643 az.tr.sk_543 it.lg.un_650
+ // [0ac0]
+ 0x28211e02, 0x2d001223, 0x04100805, 0x12004a09, // ms.jw.sw_222 hu.sk.un_880 uk.be.ru_333 yo.hu.un_440
+ 0x29311ba7, 0x0823250c, 0x08070ba0, 0x1b310c12, // tr.az.sl_532 eu.ca.no_543 es.it.no_322 sv.az.tr_654
+ 0x09000b05, 0x230729a0, 0x08000212, 0x071704a4, // es.pl.un_330 sl.it.ca_322 da.no.un_640 ru.sr.bg_433
+ 0x53352811, 0x033f3b12, 0x13100c0d, 0x0d1c1304, // sw.zu.ht_653 so.af.nl_654 sv.lt.et_554 bh.mr.ne_332
+ // [0ad0]
+ 0x253f06ec, 0x2d1b13ac, 0x06020cee, 0x091c13ad, // de.af.eu_644 et.tr.sk_632 sv.da.de_422 bh.mr.hi_643
+ 0x070410a0, 0x64003b1a, 0x111709ac, 0x170a04a4, // be.ru.bg_322 so.lg.un_760 pl.sr.ro_632 ru.mk.sr_433
+ 0x640306a4, 0x17163214, 0x210105a7, 0x290c1308, // de.nl.lg_433 bs.hr.sr_666 fr.en.jw_532 et.sv.sl_443
+ 0x04070808, 0x2a1107ee, 0x3f1f0107, 0x0e2d0d5a, // uk.bg.ru_443 it.ro.mt_422 en.cy.af_432 cs.sk.is_553
+ // [0ae0]
+ 0x131827ec, 0x20103205, 0x0a1711af, 0x060c0107, // gd.ga.et_644 bs.lt.sq_333 ro.sr.mk_655 en.sv.de_432
+ 0x03003204, 0x1800210e, 0x0b0a1905, 0x08020da0, // bs.nl.un_320 fa.ar.un_550 gl.pt.es_333 cs.da.no_322
+ 0x31000c12, 0x1f000d13, 0x1f3528ee, 0x041364a0, // sv.az.un_640 cs.cy.un_650 sw.zu.cy_422 lg.et.fi_322
+ 0x3f130c02, 0x5300050d, 0x040a1004, 0x3f050607, // sv.et.af_222 fr.ht.un_540 be.mk.ru_332 de.fr.af_432
+ // [0af0]
+ 0x532931a0, 0x10000f08, 0x25001c02, 0x27001f04, // az.sl.ht_322 lv.lt.un_430 id.eu.un_220 cy.gd.un_320
+ 0x1b00060d, 0x07000a0e, 0x643528ad, 0x13001c08, // de.tr.un_540 mk.bg.un_550 sw.zu.lg_643 mr.bh.un_430
+ 0x64004a04, 0x080e2107, 0x1c090dec, 0x523528ec, // yo.lg.un_320 jw.is.no_432 ne.hi.mr_644 sw.zu.ha_644
+ 0x136455a0, 0x0d091cee, 0x070a10a4, 0x122d0eec, // rw.lg.et_322 mr.hi.ne_422 be.mk.bg_433 is.sk.hu_644
+ // [0b00]
+ 0x12001a02, 0x00006424, 0x53001c05, 0x641c02a0, // tl.hu.un_220 lg.un.un_900 id.ht.un_330 da.id.lg_322
+ 0x291e1c04, 0x030e2007, 0x353153ad, 0x080209a4, // id.ms.sl_332 sq.is.nl_432 ht.az.zu_643 pl.da.no_433
+ 0x16020807, 0x2d0d01ec, 0x12002304, 0x4a6b64a0, // no.da.hr_432 en.cs.sk_644 ca.hu.un_320 lg.ceb.yo_322
+ 0x00005506, 0x1a6b2107, 0x23002814, 0x2b001c02, // rw.un.un_400 jw.ceb.tl_432 sw.ca.un_660 id.vi.un_220
+ // [0b10]
+ 0x1e1c2109, 0x3f0e0807, 0x0d010504, 0x02003f08, // jw.id.ms_444 no.is.af_432 fr.en.cs_332 af.da.un_430
+ 0x21024aad, 0x07000a02, 0x53004a04, 0x10321704, // yo.da.jw_643 mk.bg.un_220 yo.ht.un_320 sr.bs.lt_332
+ 0x0a033f60, 0x2d0d1214, 0x13060c02, 0x1c6b2107, // af.nl.pt_664 hu.cs.sk_666 sv.de.et_222 jw.ceb.id_432
+ 0x1b080260, 0x29172d05, 0x0a1708a0, 0x0a002d10, // da.no.tr_664 sk.sr.sl_333 uk.sr.mk_322 sk.pt.un_620
+ // [0b20]
+ 0x3b002a0e, 0x31001105, 0x10002104, 0x16002d08, // mt.so.un_550 ro.az.un_330 jw.lt.un_320 sk.hr.un_430
+ 0x080411a9, 0x162d2907, 0x64002502, 0x0d1c09ad, // ro.ru.uk_544 sl.sk.hr_432 eu.lg.un_220 hi.mr.ne_643
+ 0x080201a0, 0x681e1c0d, 0x323b2d07, 0x211e1c60, // en.da.no_322 id.ms.ig_554 sk.so.bs_432 id.ms.jw_664
+ 0x07033bee, 0x07000805, 0x1600290d, 0x13000c12, // so.nl.it_422 uk.bg.un_330 sl.hr.un_540 sv.et.un_640
+ // [0b30]
+ 0x0d002905, 0x07080a12, 0x643b27ee, 0x3b005204, // sl.cs.un_330 mk.uk.bg_654 gd.so.lg_422 ha.so.un_320
+ 0x09001902, 0x27093b08, 0x5352280c, 0x6b213b04, // gl.pl.un_220 so.pl.gd_443 sw.ha.ht_543 so.jw.ceb_332
+ 0x191368a7, 0x1c00210c, 0x2100230c, 0x52353105, // ig.et.gl_532 jw.id.un_530 ca.jw.un_530 az.zu.ha_333
+ 0x07002308, 0x17070aa7, 0x3b004a05, 0x1c2128a9, // ca.it.un_430 mk.bg.sr_532 yo.so.un_330 sw.jw.id_544
+ // [0b40]
+ 0x1c000d02, 0x1200060b, 0x13091c60, 0x121b1fee, // ne.mr.un_220 de.hu.un_520 mr.hi.bh_664 cy.tr.hu_422
+ 0x53003b0d, 0x06001f02, 0x2d0d09a4, 0x02000c0e, // so.ht.un_540 cy.de.un_220 pl.cs.sk_433 sv.da.un_550
+ 0x0e08020e, 0x11100fee, 0x311b3b0d, 0x08020302, // da.no.is_555 lv.lt.ro_422 so.tr.az_554 nl.da.no_222
+ 0x355528ec, 0x080a1104, 0x05193bee, 0x073511ee, // sw.rw.zu_644 ro.mk.uk_332 so.gl.fr_422 ro.zu.it_422
+ // [0b50]
+ 0x061f1305, 0x110a28ee, 0x0b000a0e, 0x321629af, // et.cy.de_333 sw.pt.ro_422 pt.es.un_550 sl.hr.bs_655
+ 0x2b001a12, 0x3f0803a0, 0x053f0307, 0x0800020c, // tl.vi.un_640 nl.no.af_322 nl.af.fr_432 da.no.un_530
+ 0x55535213, 0x0e124aa4, 0x6b211a09, 0x0d001308, // ha.ht.rw_665 yo.hu.is_433 tl.jw.ceb_444 bh.ne.un_430
+ 0x251b6ba7, 0x311b5511, 0x1a006b1b, 0x2b005304, // ceb.tr.eu_532 rw.tr.az_653 ceb.tl.un_770 ht.vi.un_320
+ // [0b60]
+ 0x17293f12, 0x4a190b05, 0x04001213, 0x18002502, // af.sl.sr_654 es.gl.yo_333 hu.fi.un_650 eu.ga.un_220
+ 0x3f050305, 0x1700081a, 0x082801ee, 0x00001103, // nl.fr.af_333 uk.sr.un_760 en.sw.no_422 ro.un.un_300
+ 0x0c0e0804, 0x28272307, 0x1b051fa0, 0x10000914, // no.is.sv_332 ca.gd.sw_432 cy.fr.tr_322 pl.lt.un_660
+ 0x06000302, 0x290d2da4, 0x20003f13, 0x0e290855, // nl.de.un_220 sk.cs.sl_433 af.sq.un_650 no.sl.is_442
+ // [0b70]
+ 0x0f002302, 0x02123f0c, 0x13091c0c, 0x0d000c08, // ca.lv.un_220 af.hu.da_543 mr.hi.bh_543 sv.cs.un_430
+ 0x2d190d09, 0x1c0e25a6, 0x091710ad, 0x03083f04, // cs.gl.sk_444 eu.is.id_521 lt.sr.pl_643 af.no.nl_332
+ 0x29002504, 0x130e0607, 0x17111007, 0x27001e02, // eu.sl.un_320 de.is.et_432 lt.ro.sr_432 ms.gd.un_220
+ 0x683b11ee, 0x1f0c0e07, 0x35003207, 0x12000212, // ro.so.ig_422 is.sv.cy_432 bs.zu.un_420 da.hu.un_640
+ // [0b80]
+ 0x3f030808, 0x32002d08, 0x070a10a0, 0x3f000308, // no.nl.af_443 sk.bs.un_430 be.mk.bg_322 nl.af.un_430
+ 0x1f002707, 0x10041307, 0x060e0ca0, 0x3b006413, // gd.cy.un_420 et.fi.lt_432 sv.is.de_322 lg.so.un_650
+ 0x0b00270c, 0x35645208, 0x01004a02, 0x12190b5a, // gd.es.un_530 ha.lg.zu_443 yo.en.un_220 es.gl.hu_553
+ 0x20000c05, 0x531f05ad, 0x0f001013, 0x0d2d3f0d, // sv.sq.un_330 fr.cy.ht_643 lt.lv.un_650 af.sk.cs_554
+ // [0b90]
+ 0x3b00640e, 0x2100280c, 0x0a071105, 0x17000108, // lg.so.un_550 sw.jw.un_530 ro.bg.mk_333 en.sr.un_430
+ 0x5300060d, 0x0000091c, 0x522164ad, 0x162d09a4, // de.ht.un_540 pl.un.un_800 lg.jw.ha_643 pl.sk.hr_433
+ 0x0100230b, 0x6b1f01ee, 0x0f000305, 0x31005313, // ca.en.un_520 en.cy.ceb_422 nl.lv.un_330 ht.az.un_650
+ 0x063f0309, 0x040f1bad, 0x4a121804, 0x170410a9, // nl.af.de_444 tr.lv.fi_643 ga.hu.yo_332 be.ru.sr_544
+ // [0ba0]
+ 0x0d000905, 0x201308ec, 0x19230a07, 0x1c130955, // hi.ne.un_330 no.et.sq_644 pt.ca.gl_432 hi.bh.mr_442
+ 0x32171614, 0x3f082707, 0x3b5231a4, 0x1a080b04, // hr.sr.bs_666 gd.no.af_432 az.ha.so_433 es.no.tl_332
+ 0x3b321604, 0x351f5304, 0x181101a7, 0x1b3b350b, // hr.bs.so_332 ht.cy.zu_332 en.ro.ga_532 zu.so.tr_542
+ 0x20002a12, 0x0c001107, 0x1a006b14, 0x182b53a0, // mt.sq.un_640 ro.sv.un_420 ceb.tl.un_660 ht.vi.ga_322
+ // [0bb0]
+ 0x643255ee, 0x5500641b, 0x063f0312, 0x68552507, // rw.bs.lg_422 lg.rw.un_770 nl.af.de_654 eu.rw.ig_432
+ 0x04023f0c, 0x31003b0c, 0x063f030c, 0x200c2d07, // af.da.fi_543 so.az.un_530 nl.af.de_543 sk.sv.sq_432
+ 0x06004a08, 0x026b55ad, 0x033f06ad, 0x100408a4, // yo.de.un_430 rw.ceb.da_643 de.af.nl_643 uk.ru.be_433
+ 0x55003523, 0x112a2305, 0x64003b12, 0x0a071702, // zu.rw.un_880 ca.mt.ro_333 so.lg.un_640 sr.bg.mk_222
+ // [0bc0]
+ 0x1a313ba9, 0x20002807, 0x01521107, 0x071028a0, // so.az.tl_544 sw.sq.un_420 ro.ha.en_432 sw.lt.it_322
+ 0x01040907, 0x0d001312, 0x2b001805, 0x09001f19, // pl.fi.en_432 bh.ne.un_640 ga.vi.un_330 cy.pl.un_750
+ 0x06034aee, 0x23190a09, 0x09001f08, 0x04001713, // yo.nl.de_422 pt.gl.ca_444 cy.pl.un_430 sr.ru.un_650
+ 0x27033f04, 0x0800110c, 0x0a171002, 0x0e00080c, // af.nl.gd_332 ro.uk.un_530 be.sr.mk_222 no.is.un_530
+ // [0bd0]
+ 0x12002122, 0x6800520b, 0x07170aa4, 0x0f1804a4, // fa.ur.un_870 ha.ig.un_520 mk.sr.bg_433 fi.ga.lv_433
+ 0x3500641b, 0x01002507, 0x0a000805, 0x3f040507, // lg.zu.un_770 eu.en.un_420 uk.mk.un_330 fr.fi.af_432
+ 0x13005204, 0x1c091311, 0x190b21a0, 0x17000808, // ha.et.un_320 bh.hi.mr_653 jw.es.gl_322 uk.sr.un_430
+ 0x203525ad, 0x0c001112, 0x1c090daf, 0x21033f07, // eu.zu.sq_643 ro.sv.un_640 ne.hi.mr_655 af.nl.jw_432
+ // [0be0]
+ 0x17070a02, 0x3f112dee, 0x32162905, 0x0e190b02, // mk.bg.sr_222 sk.ro.af_422 sl.hr.bs_333 es.gl.is_222
+ 0x040a08a7, 0x0d2d0912, 0x03090ba0, 0x190b1312, // uk.mk.ru_532 pl.sk.cs_654 es.pl.nl_322 et.es.gl_654
+ 0x09290fa0, 0x3b2152a4, 0x32000607, 0x0a040708, // lv.sl.pl_322 ha.jw.so_433 de.bs.un_420 bg.ru.mk_443
+ 0x03002104, 0x091c0d08, 0x00003b03, 0x35005502, // jw.nl.un_320 ne.mr.hi_443 so.un.un_300 rw.zu.un_220
+ // [0bf0]
+ 0x130e04a7, 0x10080a02, 0x21005304, 0x6b35280d, // fi.is.et_532 mk.uk.be_222 ht.jw.un_320 sw.zu.ceb_554
+ 0x55122807, 0x211a3511, 0x53001104, 0x645528ad, // sw.hu.rw_432 zu.tl.jw_653 ro.ht.un_320 sw.rw.lg_643
+ 0x06122355, 0x53000512, 0x6b4a1a60, 0x3b003112, // ca.hu.de_442 fr.ht.un_640 tl.yo.ceb_664 az.so.un_640
+ 0x13000704, 0x0c082904, 0x310904a4, 0x0a0407af, // it.et.un_320 sl.no.sv_332 fi.pl.az_433 bg.ru.mk_655
+
+ // [0c00]
+ 0x02002104, 0x042010a4, 0x0912060c, 0x6b1f3f04, // jw.da.un_320 lt.sq.fi_433 de.hu.pl_543 af.cy.ceb_332
+ 0x13000e08, 0x100f1a05, 0x3b351f12, 0x17081012, // is.et.un_430 tl.lv.lt_333 cy.zu.so_654 be.uk.sr_654
+ 0x1e3b31a9, 0x211f5207, 0x1b002a08, 0x0b0a19ee, // az.so.ms_544 ha.cy.jw_432 mt.tr.un_430 gl.pt.es_422
+ 0x090d1104, 0x0a041713, 0x11102daf, 0x5500520e, // ro.cs.pl_332 sr.ru.mk_665 sk.lt.ro_655 ha.rw.un_550
+ // [0c10]
+ 0x11000413, 0x0e002b12, 0x2b001c04, 0x0d091307, // ru.ro.un_650 vi.is.un_640 id.vi.un_320 bh.hi.ne_432
+ 0x04170702, 0x20321604, 0x171613a4, 0x16100f05, // bg.sr.ru_222 hr.bs.sq_332 et.hr.sr_433 lv.lt.hr_333
+ 0x0e0c0811, 0x0f002907, 0x16292007, 0x28251e07, // no.sv.is_653 sl.lv.un_420 sq.sl.hr_432 ms.eu.sw_432
+ 0x00003f06, 0x0d3528a0, 0x00000a03, 0x09006412, // af.un.un_400 sw.zu.cs_322 pt.un.un_300 lg.pl.un_640
+ // [0c20]
+ 0x12001f07, 0x1f103555, 0x2d2911ee, 0x093f2104, // cy.hu.un_420 zu.lt.cy_442 ro.sl.sk_422 jw.af.pl_332
+ 0x23001812, 0x3b3f35a7, 0x0400030e, 0x231b5507, // ga.ca.un_640 zu.af.so_532 nl.fi.un_550 rw.tr.ca_432
+ 0x04005304, 0x13250707, 0x35642804, 0x31001b22, // ht.fi.un_320 it.eu.et_432 sw.lg.zu_332 tr.az.un_870
+ 0x1f001e05, 0x522a28a4, 0x1e53050c, 0x1f001b14, // ms.cy.un_330 sw.mt.ha_433 fr.ht.ms_543 tr.cy.un_660
+ // [0c30]
+ 0x21000c0d, 0x0e000c0c, 0x1e1c2505, 0x06030ca0, // sv.jw.un_540 sv.is.un_530 eu.id.ms_333 sv.nl.de_322
+ 0x163f0307, 0x17002913, 0x68211cad, 0x1f00011a, // nl.af.hr_432 sl.sr.un_650 id.jw.ig_643 en.cy.un_760
+ 0x0f00251a, 0x091c130d, 0x17040713, 0x051b31ee, // eu.lv.un_760 bh.mr.hi_554 bg.ru.sr_665 az.tr.fr_422
+ 0x10170a12, 0x011c13a0, 0x071101a4, 0x07006404, // mk.sr.be_654 et.id.en_322 en.ro.it_433 lg.it.un_320
+ // [0c40]
+ 0x031208ee, 0x1b253108, 0x53050613, 0x2a003b19, // no.hu.nl_422 az.eu.tr_443 de.fr.ht_665 so.mt.un_750
+ 0x0e3f0305, 0x0a00180d, 0x094a28a0, 0x0d1c1312, // nl.af.is_333 ga.pt.un_540 sw.yo.pl_322 bh.mr.ne_654
+ 0x0b000704, 0x29001607, 0x17110705, 0x3b52010b, // it.es.un_320 hr.sl.un_420 bg.ro.sr_333 en.ha.so_542
+ 0x091c0da4, 0x1e1c0ea4, 0x170a0755, 0x12060c09, // ne.mr.hi_433 is.id.ms_433 bg.mk.sr_442 sv.de.hu_444
+ // [0c50]
+ 0x682813ec, 0x1c211f12, 0x03060c02, 0x1a3b0614, // et.sw.ig_644 cy.jw.id_654 sv.de.nl_222 de.so.tl_666
+ 0x3b1325a7, 0x2800350e, 0x285564a0, 0x1a006b07, // eu.et.so_532 zu.sw.un_550 lg.rw.sw_322 ceb.tl.un_420
+ 0x23002a19, 0x0a07110d, 0x29000f13, 0x0b0a23a0, // mt.ca.un_750 ro.bg.mk_554 lv.sl.un_650 ca.pt.es_322
+ 0x081103ee, 0x68131b05, 0x17000f0b, 0x0c0e3f04, // nl.ro.no_422 tr.et.ig_333 lv.sr.un_520 af.is.sv_332
+ // [0c60]
+ 0x060713a0, 0x09130d12, 0x033f1f55, 0x12000702, // et.it.de_322 ne.bh.hi_654 cy.af.nl_442 it.hu.un_220
+ 0x04000f08, 0x29000704, 0x0a00070e, 0x0d091ca4, // lv.fi.un_430 it.sl.un_320 bg.mk.un_550 mr.hi.ne_433
+ 0x1f010604, 0x35001f04, 0x553521a0, 0x0a17070c, // de.en.cy_332 cy.zu.un_320 jw.zu.rw_322 bg.sr.mk_543
+ 0x17000704, 0x06001f0d, 0x033f100b, 0x520c08a0, // it.sr.un_320 cy.de.un_540 lt.af.nl_542 no.sv.ha_322
+ // [0c70]
+ 0x01000e04, 0x3f001f14, 0x17060505, 0x100802a4, // is.en.un_320 cy.af.un_660 fr.de.sr_333 da.no.lt_433
+ 0x0f082dee, 0x35006404, 0x131701a4, 0x64533f11, // sk.no.lv_422 lg.zu.un_320 en.sr.et_433 af.ht.lg_653
+ 0x0a0810a0, 0x53000509, 0x21033f11, 0x21002304, // be.uk.mk_322 fr.ht.un_440 af.nl.jw_653 ca.jw.un_320
+ 0x190b0aa9, 0x186e2aa9, 0x23005302, 0x6b0305ee, // pt.es.gl_544 mt.hmn.ga_544 ht.ca.un_220 fr.nl.ceb_422
+ // [0c80]
+ 0x0c006b07, 0x100411ec, 0x12000c09, 0x0d120e0c, // ceb.sv.un_420 ro.ru.be_644 sv.hu.un_440 is.hu.cs_543
+ 0x0a071107, 0x53201ba4, 0x01000804, 0x1c000912, // ro.bg.mk_432 tr.sq.ht_433 no.en.un_320 hi.mr.un_640
+ 0x18002b13, 0x321703a0, 0x00001f03, 0x11000411, // vi.ga.un_650 nl.sr.bs_322 cy.un.un_300 fi.ro.un_630
+ 0x28000b04, 0x32082d07, 0x0d001b05, 0x3f00060c, // es.sw.un_320 sk.no.bs_432 tr.cs.un_330 de.af.un_530
+ // [0c90]
+ 0x12000104, 0x182735ee, 0x0e2d0aa9, 0x04682511, // en.hu.un_320 zu.gd.ga_422 pt.sk.is_544 eu.ig.fi_653
+ 0x3f0603a4, 0x100820ee, 0x6b001a05, 0x1a134aa7, // nl.de.af_433 sq.no.lt_422 tl.ceb.un_330 yo.et.tl_532
+ 0x35006812, 0x0a000108, 0x251e1c08, 0x1632175a, // ig.zu.un_640 en.pt.un_430 id.ms.eu_443 sr.bs.hr_553
+ 0x111008a4, 0x11000a04, 0x060828a0, 0x202d0907, // uk.be.ro_433 pt.ro.un_320 sw.no.de_322 pl.sk.sq_432
+ // [0ca0]
+ 0x2d0d55a9, 0x00006e24, 0x05002312, 0x021018a0, // rw.cs.sk_544 hmn.un.un_900 ca.fr.un_640 ga.lt.da_322
+ 0x2d0d090c, 0x53190a08, 0x11100804, 0x18211211, // pl.cs.sk_543 pt.gl.ht_443 no.lt.ro_332 ur.fa.ar_653
+ 0x2864120c, 0x091c13a0, 0x321623a0, 0x271f1812, // hu.lg.sw_543 bh.mr.hi_322 ca.hr.bs_322 ga.cy.gd_654
+ 0x32311b0b, 0x00000c15, 0x25282904, 0x31003b13, // tr.az.bs_542 sv.un.un_700 sl.sw.eu_332 so.az.un_650
+ // [0cb0]
+ 0x17003104, 0x17321608, 0x130d1c14, 0x21002007, // az.sr.un_320 hr.bs.sr_443 mr.ne.bh_666 sq.jw.un_420
+ 0x311a2712, 0x1c091309, 0x08111304, 0x041827ad, // gd.tl.az_654 bh.hi.mr_444 et.ro.no_332 gd.ga.fi_643
+ 0x17006e08, 0x1300030d, 0x4a000c07, 0x210c08af, // hmn.sr.un_430 nl.et.un_540 sv.yo.un_420 no.sv.jw_655
+ 0x23001f1b, 0x04001319, 0x20231108, 0x1732160b, // cy.ca.un_770 et.fi.un_750 ro.ca.sq_443 hr.bs.sr_542
+ // [0cc0]
+ 0x00003b06, 0x2d0d0faf, 0x05000704, 0x28001f13, // so.un.un_400 lv.cs.sk_655 it.fr.un_320 cy.sw.un_650
+ 0x1f6401a4, 0x1a006b05, 0x4a120e0c, 0x210a4aa4, // en.lg.cy_433 ceb.tl.un_330 is.hu.yo_543 yo.pt.jw_433
+ 0x08100aa4, 0x2d2110a6, 0x041b13a0, 0x08291204, // mk.be.uk_433 lt.jw.sk_521 et.tr.fi_322 hu.sl.no_332
+ 0x00001f01, 0x0c080ea0, 0x20000e12, 0x2d0d29a4, // cy.un.un_200 is.no.sv_322 is.sq.un_640 sl.cs.sk_433
+ // [0cd0]
+ 0x10171105, 0x6e006b05, 0x17001219, 0x235502ee, // ro.sr.be_333 ceb.hmn.un_330 hu.sr.un_750 da.rw.ca_422
+ 0x1b002108, 0x0a1a0208, 0x182820a4, 0x061e1c05, // jw.tr.un_430 da.tl.pt_443 sq.sw.ga_433 id.ms.de_333
+ 0x1f032507, 0x07005308, 0x1f3f020b, 0x0c00130d, // eu.nl.cy_432 ht.it.un_430 da.af.cy_542 et.sv.un_540
+ 0x06080c0c, 0x07040a02, 0x08001007, 0x12111ba0, // sv.no.de_543 mk.ru.bg_222 be.uk.un_420 tr.ro.hu_322
+ // [0ce0]
+ 0x0e083507, 0x211c1e0c, 0x3b0812a4, 0x01005207, // zu.no.is_432 ms.id.jw_543 hu.no.so_433 ha.en.un_420
+ 0x10051f0c, 0x11001807, 0x28003b07, 0x27003b0d, // cy.fr.lt_543 ga.ro.un_420 so.sw.un_420 so.gd.un_540
+ 0x6b030ead, 0x121e1c09, 0x060e01a0, 0x31002704, // is.nl.ceb_643 id.ms.hu_444 en.is.de_322 gd.az.un_320
+ 0x1e0d25ee, 0x28006b07, 0x033f25a0, 0x0e0306a4, // eu.cs.ms_422 ceb.sw.un_420 eu.af.nl_322 de.nl.is_433
+ // [0cf0]
+ 0x07351a07, 0x2a030613, 0x1c132755, 0x12002013, // tl.zu.it_432 de.nl.mt_665 gd.et.id_442 sq.hu.un_650
+ 0x013f1a07, 0x29002d13, 0x641f120c, 0x00004a2d, // tl.af.en_432 sk.sl.un_650 hu.cy.lg_543 yo.un.un_A00
+ 0x05005312, 0x3f000607, 0x6b033fee, 0x06000d05, // ht.fr.un_640 de.af.un_420 af.nl.ceb_422 cs.de.un_330
+ 0x19002707, 0x2d0d29a9, 0x271203ee, 0x52001f11, // gd.gl.un_420 sl.cs.sk_544 nl.hu.gd_422 cy.ha.un_630
+ // [0d00]
+ 0x2d0d4aa4, 0x0d00130c, 0x23001a04, 0x351125a0, // yo.cs.sk_433 bh.ne.un_530 tl.ca.un_320 eu.ro.zu_322
+ 0x035207ee, 0x0e1304ac, 0x08030607, 0x0f091255, // it.ha.nl_422 fi.et.is_632 de.nl.no_432 hu.pl.lv_442
+ 0x0c080a05, 0x290a01a4, 0x111b2aad, 0x1c130908, // pt.no.sv_333 en.pt.sl_433 mt.tr.ro_643 hi.bh.mr_443
+ 0x556421ec, 0x64001c05, 0x08120107, 0x55353fa0, // jw.lg.rw_644 id.lg.un_330 en.hu.no_432 af.zu.rw_322
+ // [0d10]
+ 0x3f072aa0, 0x0d091cad, 0x55063f02, 0x03061808, // mt.it.af_322 mr.hi.ne_643 af.de.rw_222 ga.de.nl_443
+ 0x35001f07, 0x10080413, 0x311b1208, 0x1c003202, // cy.zu.un_420 ru.uk.be_665 hu.tr.az_443 bs.id.un_220
+ 0x0e2d0d13, 0x251035a0, 0x1f6b010c, 0x1b3b04ad, // cs.sk.is_665 zu.lt.eu_322 en.ceb.cy_543 fi.so.tr_643
+ 0x1a002307, 0x0c086ba0, 0x35682a07, 0x10552511, // ca.tl.un_420 ceb.no.sv_322 mt.ig.zu_432 eu.rw.lt_653
+ // [0d20]
+ 0x6b1b015a, 0x023506a4, 0x132718ad, 0x130e3fa0, // en.tr.ceb_553 de.zu.da_433 ga.gd.et_643 af.is.et_322
+ 0x17070408, 0x28643b08, 0x211c1b07, 0x13102807, // ru.bg.sr_443 so.lg.sw_443 tr.id.jw_432 sw.lt.et_432
+ 0x64003513, 0x0e001f1a, 0x06133fa9, 0x040807af, // zu.lg.un_650 cy.is.un_760 af.et.de_544 bg.uk.ru_655
+ 0x020e1f04, 0x08002a12, 0x28352112, 0x07183fa0, // cy.is.da_332 mt.no.un_640 jw.zu.sw_654 af.ga.it_322
+ // [0d30]
+ 0x2a271808, 0x121b3112, 0x201e31ee, 0x4a005214, // ga.gd.mt_443 az.tr.hu_654 az.ms.sq_422 ha.yo.un_660
+ 0x0800230e, 0x086828a4, 0x55281a08, 0x2d0d35ee, // ca.no.un_550 sw.ig.no_433 tl.sw.rw_443 zu.cs.sk_422
+ 0x092a28a7, 0x171b3f07, 0x27011207, 0x32002a12, // sw.mt.pl_532 af.tr.sr_432 hu.en.gd_432 mt.bs.un_640
+ 0x200f2a02, 0x1e001c09, 0x08070a07, 0x0d002d19, // mt.lv.sq_222 id.ms.un_440 mk.bg.uk_432 sk.cs.un_750
+ // [0d40]
+ 0x2a00310c, 0x121629a4, 0x040a1002, 0x2a002014, // az.mt.un_530 sl.hr.hu_433 be.mk.ru_222 sq.mt.un_660
+ 0x3b312a0d, 0x185323a7, 0x0e00011a, 0x063f0407, // mt.az.so_554 ca.ht.ga_532 en.is.un_760 fi.af.de_432
+ 0x041918ad, 0x081711ee, 0x31003204, 0x0c00060c, // ga.gl.fi_643 ro.sr.uk_422 bs.az.un_320 de.sv.un_530
+ 0x0c001c04, 0x04170a02, 0x2b1f3507, 0x0d13290c, // id.sv.un_320 mk.sr.ru_222 zu.cy.vi_432 sl.et.cs_543
+ // [0d50]
+ 0x52091804, 0x27003112, 0x20003204, 0x3f000304, // ga.pl.ha_332 az.gd.un_640 bs.sq.un_320 nl.af.un_320
+ 0x190b12af, 0x03053fa0, 0x202931ee, 0x64072811, // hu.es.gl_655 af.fr.nl_322 az.sl.sq_422 sw.it.lg_653
+ 0x351e0d12, 0x08070405, 0x2964350c, 0x0e3109ad, // cs.ms.zu_654 ru.bg.uk_333 zu.lg.sl_543 pl.az.is_643
+ 0x2a0b0ea0, 0x00001b0a, 0x28002104, 0x040717a4, // is.es.mt_322 tr.un.un_500 jw.sw.un_320 sr.bg.ru_433
+ // [0d60]
+ 0x55003202, 0x3f0e085a, 0x0700230d, 0x2d230a0c, // bs.rw.un_220 no.is.af_553 ca.it.un_540 pt.ca.sk_543
+ 0x0a190b04, 0x2d0d09a6, 0x0c032902, 0x23000412, // es.gl.pt_332 pl.cs.sk_521 sl.nl.sv_222 fi.ca.un_640
+ 0x07040aee, 0x554a1007, 0x0b00010c, 0x17090dee, // mk.ru.bg_422 lt.yo.rw_432 en.es.un_530 cs.pl.sr_422
+ 0x0e0205a0, 0x0000170f, 0x0000040a, 0x0b001812, // fr.da.is_322 sr.un.un_600 ru.un.un_500 ga.es.un_640
+ // [0d70]
+ 0x12000e09, 0x02061bee, 0x6b001a12, 0x0a0807a4, // is.hu.un_440 tr.de.da_422 tl.ceb.un_640 bg.uk.mk_433
+ 0x0e00530e, 0x1c13090e, 0x2d00180d, 0x03001313, // ht.is.un_550 hi.bh.mr_555 ga.sk.un_540 et.nl.un_650
+ 0x1b062a07, 0x64002a13, 0x3500680c, 0x09000c04, // mt.de.tr_432 mt.lg.un_650 ig.zu.un_530 sv.pl.un_320
+ 0x1b1e3507, 0x6b1a1fa4, 0x3f050307, 0x290c06ad, // zu.ms.tr_432 cy.tl.ceb_433 nl.fr.af_432 de.sv.sl_643
+ // [0d80]
+ 0x53001c02, 0x0500011a, 0x0700110c, 0x4a1a6ba0, // id.ht.un_220 en.fr.un_760 ro.bg.un_530 ceb.tl.yo_322
+ 0x556428af, 0x0c002508, 0x25190b13, 0x1a006b0e, // sw.lg.rw_655 eu.sv.un_430 es.gl.eu_665 ceb.tl.un_550
+ 0x1c000d1a, 0x05680107, 0x1b006b07, 0x35001014, // ne.mr.un_760 en.ig.fr_432 ceb.tr.un_420 lt.zu.un_660
+ 0x010c18ee, 0x01003b07, 0x21041809, 0x53000707, // ga.sv.en_422 so.en.un_420 ga.fi.jw_444 it.ht.un_420
+ // [0d90]
+ 0x01001f13, 0x64325508, 0x050a23a9, 0x183527af, // cy.en.un_650 rw.bs.lg_443 ca.pt.fr_544 gd.zu.ga_655
+ 0x10001107, 0x16000f07, 0x06351807, 0x0d2011ee, // ro.be.un_420 lv.hr.un_420 ga.zu.de_432 ro.sq.cs_422
+ 0x0d1c0960, 0x08170a02, 0x07000a19, 0x04122807, // hi.mr.ne_664 mk.sr.uk_222 mk.bg.un_750 sw.hu.fi_432
+ 0x2a525304, 0x4a172905, 0x2a072007, 0x19000707, // ht.ha.mt_332 sl.sr.yo_333 sq.it.mt_432 it.gl.un_420
+ // [0da0]
+ 0x351e10a0, 0x1a001e04, 0x52006b04, 0x102d110e, // lt.ms.zu_322 ms.tl.un_320 ceb.ha.un_320 ro.sk.lt_555
+ 0x17000413, 0x2a1125a4, 0x280413a4, 0x27002819, // ru.sr.un_650 eu.ro.mt_433 et.fi.sw_433 sw.gd.un_750
+ 0x0b0a19a0, 0x090d130c, 0x292028a7, 0x3f000408, // gl.pt.es_322 bh.ne.hi_543 sw.sq.sl_532 fi.af.un_430
+ 0x25003113, 0x13000d0c, 0x19001804, 0x090d1307, // az.eu.un_650 ne.bh.un_530 ga.gl.un_320 bh.ne.hi_432
+ // [0db0]
+ 0x0d2d290b, 0x3b311aee, 0x20000104, 0x18001219, // sl.sk.cs_542 tl.az.so_422 en.sq.un_320 ur.ar.un_750
+ 0x0d091c11, 0x3b1b1a07, 0x2d2928a4, 0x01001a04, // mr.hi.ne_653 tl.tr.so_432 sw.sl.sk_433 tl.en.un_320
+ 0x0425280d, 0x116807ad, 0x281f6407, 0x21193502, // sw.eu.fi_554 it.ig.ro_643 lg.cy.sw_432 zu.gl.jw_222
+ 0x04006b08, 0x192d0b07, 0x10531ba4, 0x2700252b, // ceb.fi.un_430 es.sk.gl_432 tr.ht.lt_433 eu.gd.un_980
+ // [0dc0]
+ 0x27001822, 0x12001904, 0x52001e07, 0x190b550b, // ga.gd.un_870 gl.hu.un_320 ms.ha.un_420 rw.es.gl_542
+ 0x3f6428ee, 0x18071107, 0x351b31af, 0x31004a0c, // sw.lg.af_422 ro.it.ga_432 az.tr.zu_655 yo.az.un_530
+ 0x012b1b04, 0x0f002504, 0x11001902, 0x531a6b5a, // tr.vi.en_332 eu.lv.un_320 gl.ro.un_220 ceb.tl.ht_553
+ 0x060413a0, 0x130853ec, 0x536b1a55, 0x06001c02, // et.fi.de_322 ht.no.et_644 tl.ceb.ht_442 id.de.un_220
+ // [0dd0]
+ 0x2d0d0aec, 0x0704170d, 0x06001319, 0x080e06af, // pt.cs.sk_644 sr.ru.bg_554 et.de.un_750 de.is.no_655
+ 0x28001b13, 0x320428ad, 0x0b2327a0, 0x27001902, // tr.sw.un_650 sw.fi.bs_643 gd.ca.es_322 gl.gd.un_220
+ 0x29000d08, 0x530527a0, 0x1b2331ad, 0x23000b07, // cs.sl.un_430 gd.fr.ht_322 az.ca.tr_643 es.ca.un_420
+ 0x09000d20, 0x356425ee, 0x2d0d3b08, 0x17162907, // ne.hi.un_850 eu.lg.zu_422 so.cs.sk_443 sl.hr.sr_432
+ // [0de0]
+ 0x21000504, 0x07100804, 0x120d2dec, 0x0c0413ec, // fr.jw.un_320 uk.be.bg_332 sk.cs.hu_644 et.fi.sv_644
+ 0x6b000b07, 0x00005324, 0x0f006b04, 0x07080413, // es.ceb.un_420 ht.un.un_900 ceb.lv.un_320 ru.uk.bg_665
+ 0x6800281a, 0x091b12a7, 0x04006e0b, 0x171632a9, // sw.ig.un_760 hu.tr.pl_532 hmn.fi.un_520 bs.hr.sr_544
+ 0x21001220, 0x3b000108, 0x05000104, 0x21005302, // ur.fa.un_850 en.so.un_430 en.fr.un_320 ht.jw.un_220
+ // [0df0]
+ 0x32092904, 0x0d1c13ee, 0x3b2a07ad, 0x0c3f03ee, // sl.pl.bs_332 bh.mr.ne_422 it.mt.so_643 nl.af.sv_422
+ 0x04071711, 0x07040aa9, 0x17000707, 0x35002808, // sr.bg.ru_653 mk.ru.bg_544 bg.sr.un_420 sw.zu.un_430
+ 0x09112504, 0x170d29a0, 0x0408100d, 0x1e006e08, // eu.ro.pl_332 sl.cs.sr_322 be.uk.ru_554 hmn.ms.un_430
+ 0x283555a6, 0x201304ee, 0x2b001702, 0x554a6408, // rw.zu.sw_521 fi.et.sq_422 sr.vi.un_220 lg.yo.rw_443
+ // [0e00]
+ 0x17292d55, 0x16001704, 0x211a3bee, 0x3f002102, // sk.sl.sr_442 sr.hr.un_320 so.tl.jw_422 jw.af.un_220
+ 0x0e0208ec, 0x31005508, 0x190b0aee, 0x23000a0e, // no.da.is_644 rw.az.un_430 pt.es.gl_422 pt.ca.un_550
+ 0x211a3b07, 0x1a004a04, 0x0e180a04, 0x0c080faf, // so.tl.jw_432 yo.tl.un_320 pt.ga.is_332 lv.no.sv_655
+ 0x0b0d1907, 0x06003512, 0x64006b04, 0x01060804, // gl.cs.es_432 zu.de.un_640 ceb.lg.un_320 no.de.en_332
+ // [0e10]
+ 0x2d000b02, 0x4a2d0d08, 0x07001004, 0x55001602, // es.sk.un_220 cs.sk.yo_443 be.bg.un_320 hr.rw.un_220
+ 0x0000112d, 0x046b2804, 0x25045307, 0x06001f12, // ro.un.un_A00 sw.ceb.fi_332 ht.fi.eu_432 cy.de.un_640
+ 0x17000207, 0x1a0f1ba4, 0x17080aa4, 0x2a003109, // da.sr.un_420 tr.lv.tl_433 mk.uk.sr_433 az.mt.un_440
+ 0x0b0a1902, 0x10000702, 0x12003202, 0x12000808, // gl.pt.es_222 bg.be.un_220 bs.hu.un_220 no.hu.un_430
+ // [0e20]
+ 0x12000a08, 0x05003502, 0x1300060c, 0x163528a7, // pt.hu.un_430 zu.fr.un_220 de.et.un_530 sw.zu.hr_532
+ 0x284a2a12, 0x0100060d, 0x180e23a7, 0x04000704, // mt.yo.sw_654 de.en.un_540 ca.is.ga_532 it.fi.un_320
+ 0x282a52ee, 0x2d5212a4, 0x35006807, 0x09310e04, // ha.mt.sw_422 hu.ha.sk_433 ig.zu.un_420 is.az.pl_332
+ 0x041a13a4, 0x05004a04, 0x04003202, 0x083b0e07, // et.tl.fi_433 yo.fr.un_320 bs.fi.un_220 is.so.no_432
+ // [0e30]
+ 0x080c09a7, 0x0a171007, 0x01000612, 0x29211313, // pl.sv.no_532 be.sr.mk_432 de.en.un_640 et.jw.sl_665
+ 0x12000e12, 0x1c1309af, 0x21080204, 0x17071104, // is.hu.un_640 hi.bh.mr_655 da.no.jw_332 ro.bg.sr_332
+ 0x04003b12, 0x00003101, 0x1f0e18a6, 0x0000251c, // so.fi.un_640 az.un.un_200 ga.is.cy_521 eu.un.un_800
+ 0x2a3f0ea4, 0x0f001707, 0x100a0708, 0x030e08a4, // is.af.mt_433 sr.lv.un_420 bg.mk.be_443 no.is.nl_433
+ // [0e40]
+ 0x09006b08, 0x21000513, 0x28295508, 0x0e2821a6, // ceb.pl.un_430 fr.jw.un_650 rw.sl.sw_443 jw.sw.is_521
+ 0x3f001111, 0x121a0f07, 0x0b4a05af, 0x3f033baf, // ro.af.un_630 lv.tl.hu_432 fr.yo.es_655 so.nl.af_655
+ 0x04081760, 0x103568ad, 0x1b522508, 0x0e031aee, // sr.uk.ru_664 ig.zu.lt_643 eu.ha.tr_443 tl.nl.is_422
+ 0x2b0b23a0, 0x12182daf, 0x00001324, 0x100704af, // ca.es.vi_322 sk.ga.hu_655 bh.un.un_900 ru.bg.be_655
+ // [0e50]
+ 0x1c211304, 0x21000519, 0x12001112, 0x120a2304, // et.jw.id_332 fr.jw.un_750 ro.hu.un_640 ca.pt.hu_332
+ 0x2a000713, 0x556b1aa0, 0x20001b14, 0x526b6808, // it.mt.un_650 tl.ceb.rw_322 tr.sq.un_660 ig.ceb.ha_443
+ 0x2d001f19, 0x202a11a9, 0x13001a08, 0x291e080c, // cy.sk.un_750 ro.mt.sq_544 tl.et.un_430 no.ms.sl_543
+ 0x04070a55, 0x0c001219, 0x3b003f19, 0x1c0752a9, // mk.bg.ru_442 hu.sv.un_750 af.so.un_750 ha.it.id_544
+ // [0e60]
+ 0x29000d14, 0x2d060107, 0x2d6e29af, 0x23190704, // cs.sl.un_660 en.de.sk_432 sl.hmn.sk_655 it.gl.ca_332
+ 0x04071704, 0x3b1b1604, 0x055325a4, 0x13122507, // sr.bg.ru_332 hr.tr.so_332 eu.ht.fr_433 eu.hu.et_432
+ 0x29090da9, 0x190b1aee, 0x64553512, 0x030b050c, // cs.pl.sl_544 tl.es.gl_422 zu.rw.lg_654 fr.es.nl_543
+ 0x05002309, 0x1e1c2aee, 0x2b28350b, 0x286b1aee, // ca.fr.un_440 mt.id.ms_422 zu.sw.vi_542 tl.ceb.sw_422
+ // [0e70]
+ 0x051107a0, 0x2a002704, 0x091a55a0, 0x12136ba6, // it.ro.fr_322 gd.mt.un_320 rw.tl.pl_322 ceb.et.hu_521
+ 0x1c0913a6, 0x3b1a6bad, 0x06002905, 0x1e1c2855, // bh.hi.mr_521 ceb.tl.so_643 sl.de.un_330 sw.id.ms_442
+ 0x1300280d, 0x1f040707, 0x1e1f1ca0, 0x251c1fee, // sw.et.un_540 it.fi.cy_432 id.cy.ms_322 cy.id.eu_422
+ 0x070a100e, 0x18005304, 0x52006808, 0x093f03a9, // be.mk.bg_555 ht.ga.un_320 ig.ha.un_430 nl.af.pl_544
+ // [0e80]
+ 0x0e002d12, 0x17311fad, 0x4a000714, 0x29285255, // sk.is.un_640 cy.az.sr_643 it.yo.un_660 ha.sw.sl_442
+ 0x29025307, 0x0312250c, 0x19001813, 0x1a006b18, // ht.da.sl_432 eu.hu.nl_543 ga.gl.un_650 ceb.tl.un_740
+ 0x27001807, 0x1b063107, 0x64281902, 0x6b081302, // ga.gd.un_420 az.de.tr_432 gl.sw.lg_222 et.no.ceb_222
+ 0x32002813, 0x08022a0c, 0x0d091c12, 0x13002d12, // sw.bs.un_650 mt.da.no_543 mr.hi.ne_654 sk.et.un_640
+ // [0e90]
+ 0x27002a02, 0x194a0b0c, 0x0f292aa0, 0x1a683b0c, // mt.gd.un_220 es.yo.gl_543 mt.sl.lv_322 so.ig.tl_543
+ 0x32043baf, 0x080a1007, 0x23000108, 0x12000d13, // so.fi.bs_655 be.mk.uk_432 en.ca.un_430 cs.hu.un_650
+ 0x27201f04, 0x17001c02, 0x4a002102, 0x3f040304, // cy.sq.gd_332 id.sr.un_220 jw.yo.un_220 nl.fi.af_332
+ 0x4a120b09, 0x2d002912, 0x095505a7, 0x55005313, // es.hu.yo_444 sl.sk.un_640 fr.rw.pl_532 ht.rw.un_650
+ // [0ea0]
+ 0x10081105, 0x321016a4, 0x2d0d29ec, 0x07000104, // ro.uk.be_333 hr.lt.bs_433 sl.cs.sk_644 en.it.un_320
+ 0x0c005512, 0x3b000414, 0x25001f19, 0x521e1c13, // rw.sv.un_640 fi.so.un_660 cy.eu.un_750 id.ms.ha_665
+ 0x070f0a08, 0x4a190a5a, 0x05001a04, 0x0e000c0e, // pt.lv.it_443 pt.gl.yo_553 tl.fr.un_320 sv.is.un_550
+ 0x12000813, 0x53002312, 0x1708020d, 0x201217a0, // no.hu.un_650 ca.ht.un_640 da.no.sr_554 sr.hu.sq_322
+ // [0eb0]
+ 0x1c001e13, 0x11002013, 0x080702a6, 0x101704a7, // ms.id.un_650 sq.ro.un_650 da.it.no_521 ru.sr.be_532
+ 0x1c004a05, 0x0d1c0908, 0x170a070b, 0x281155ee, // yo.id.un_330 hi.mr.ne_443 bg.mk.sr_542 rw.ro.sw_422
+ 0x070105ee, 0x16003207, 0x3b1055a4, 0x55002702, // fr.en.it_422 bs.hr.un_420 rw.lt.so_433 gd.rw.un_220
+ 0x050f5209, 0x120e180c, 0x292d0d09, 0x06001208, // ha.lv.fr_444 ga.is.hu_543 cs.sk.sl_444 hu.de.un_430
+ // [0ec0]
+ 0x0d2d35ad, 0x181301a7, 0x070411ee, 0x4a35640c, // zu.sk.cs_643 en.et.ga_532 ro.ru.bg_422 lg.zu.yo_543
+ 0x3f1720a4, 0x101a350c, 0x1f3f0607, 0x1b6b35a4, // sq.sr.af_433 zu.tl.lt_543 de.af.cy_432 zu.ceb.tr_433
+ 0x53033f07, 0x1b552155, 0x556435a7, 0x29171608, // af.nl.ht_432 jw.rw.tr_442 zu.lg.rw_532 hr.sr.sl_443
+ 0x55003512, 0x0602010c, 0x03001e05, 0x0a002807, // zu.rw.un_640 en.da.de_543 ms.nl.un_330 sw.pt.un_420
+ // [0ed0]
+ 0x2d050aee, 0x21001e07, 0x171629af, 0x0c111602, // pt.fr.sk_422 ms.jw.un_420 sl.hr.sr_655 hr.ro.sv_222
+ 0x2a045505, 0x17292da4, 0x32121ca0, 0x1127180e, // rw.fi.mt_333 sk.sl.sr_433 id.hu.bs_322 ga.gd.ro_555
+ 0x311b1212, 0x17000d07, 0x1c090da4, 0x32645207, // hu.tr.az_654 cs.sr.un_420 ne.hi.mr_433 ha.lg.bs_432
+ 0x10002911, 0x0e006404, 0x0e001808, 0x53003504, // sl.lt.un_630 lg.is.un_320 ga.is.un_430 zu.ht.un_320
+ // [0ee0]
+ 0x2a291605, 0x130f12af, 0x35006413, 0x2d3216a0, // hr.sl.mt_333 hu.lv.et_655 lg.zu.un_650 hr.bs.sk_322
+ 0x06005204, 0x1a1b5304, 0x32002912, 0x0d0913ac, // ha.de.un_320 ht.tr.tl_332 sl.bs.un_640 bh.hi.ne_632
+ 0x132a0455, 0x3217160d, 0x212320ac, 0x553521af, // fi.mt.et_442 hr.sr.bs_554 sq.ca.jw_632 jw.zu.rw_655
+ 0x4a68350c, 0x1300201a, 0x10001904, 0x20162955, // zu.ig.yo_543 sq.et.un_760 gl.lt.un_320 sl.hr.sq_442
+ // [0ef0]
+ 0x08170a5a, 0x321f1baf, 0x13250504, 0x0d09130b, // mk.sr.uk_553 tr.cy.bs_655 fr.eu.et_332 bh.hi.ne_542
+ 0x0500210b, 0x3100121a, 0x18011fad, 0x1b310e0d, // jw.fr.un_520 hu.az.un_760 cy.en.ga_643 is.az.tr_554
+ 0x2118125a, 0x09112804, 0x4a0155a0, 0x21001e04, // ur.ar.fa_553 sw.ro.pl_332 rw.en.yo_322 ms.jw.un_320
+ 0x2105230c, 0x20005205, 0x3b2a19a0, 0x283b01a4, // ca.fr.jw_543 ha.sq.un_330 gl.mt.so_322 en.so.sw_433
+ // [0f00]
+ 0x100f1707, 0x08041307, 0x250f0707, 0x2b00230d, // sr.lv.lt_432 et.fi.no_432 it.lv.eu_432 ca.vi.un_540
+ 0x110128ad, 0x2d1309af, 0x00003f1c, 0x046b55a0, // sw.en.ro_643 pl.et.sk_655 af.un.un_800 rw.ceb.fi_322
+ 0x29001e02, 0x53006b07, 0x6400521a, 0x0a0b19a4, // ms.sl.un_220 ceb.ht.un_420 ha.lg.un_760 gl.es.pt_433
+ 0x07061f07, 0x090c1f11, 0x04170805, 0x10071707, // cy.de.it_432 cy.sv.pl_653 uk.sr.ru_333 sr.bg.be_432
+ // [0f10]
+ 0x00000315, 0x0602520c, 0x55683512, 0x3f232904, // nl.un.un_700 ha.da.de_543 zu.ig.rw_654 sl.ca.af_332
+ 0x0b0d2da9, 0x19050ba0, 0x11000302, 0x0d3b2da7, // sk.cs.es_544 es.fr.gl_322 nl.ro.un_220 sk.so.cs_532
+ 0x6e000605, 0x0c030807, 0x1e1c0605, 0x23001217, // de.hmn.un_330 no.nl.sv_432 de.id.ms_333 hu.ca.un_730
+ 0x550e2105, 0x18000514, 0x525528af, 0x00001803, // jw.is.rw_333 fr.ga.un_660 sw.rw.ha_655 ga.un.un_300
+ // [0f20]
+ 0x040811a0, 0x1f000b05, 0x170711a6, 0x193513ee, // ro.uk.ru_322 es.cy.un_330 ro.bg.sr_521 et.zu.gl_422
+ 0x52004a1a, 0x0b0111ee, 0x2d1f05a0, 0x17040aaf, // yo.ha.un_760 ro.en.es_422 fr.cy.sk_322 mk.ru.sr_655
+ 0x0500530e, 0x1b132508, 0x111728a4, 0x32003f04, // ht.fr.un_550 eu.et.tr_443 sw.sr.ro_433 af.bs.un_320
+ 0x1e003108, 0x00001724, 0x1c0d13a4, 0x041711a0, // az.ms.un_430 sr.un.un_900 bh.ne.mr_433 ro.sr.ru_322
+ // [0f30]
+ 0x1c0913ee, 0x3b5564ad, 0x1c6421a0, 0x1b006405, // bh.hi.mr_422 lg.rw.so_643 jw.lg.id_322 lg.tr.un_330
+ 0x523b35af, 0x1e051ca0, 0x0c0908a4, 0x0b2d0d0d, // zu.so.ha_655 id.fr.ms_322 no.pl.sv_433 cs.sk.es_554
+ 0x06643509, 0x023f6b02, 0x03006b05, 0x310f1baf, // zu.lg.de_444 ceb.af.da_222 ceb.nl.un_330 tr.lv.az_655
+ 0x0b000a05, 0x00005503, 0x170811a0, 0x01091fee, // pt.es.un_330 rw.un.un_300 ro.uk.sr_322 cy.pl.en_422
+ // [0f40]
+ 0x35002812, 0x25231fa9, 0x05000304, 0x1100050e, // sw.zu.un_640 cy.ca.eu_544 nl.fr.un_320 fr.ro.un_550
+ 0x08021fec, 0x06250107, 0x1a0e2155, 0x20251260, // cy.da.no_644 en.eu.de_432 jw.is.tl_442 hu.eu.sq_664
+ 0x1707040c, 0x4a132507, 0x0400071a, 0x6b6808a4, // ru.bg.sr_543 eu.et.yo_432 bg.ru.un_760 no.ig.ceb_433
+ 0x11000a0c, 0x1700101a, 0x3b231355, 0x02000604, // pt.ro.un_530 be.sr.un_760 et.ca.so_442 de.da.un_320
+ // [0f50]
+ 0x2d005304, 0x104a25a0, 0x05182711, 0x0e311ea0, // ht.sk.un_320 eu.yo.lt_322 gd.ga.fr_653 ms.az.is_322
+ 0x31001904, 0x1b005204, 0x0e0803ac, 0x35060913, // gl.az.un_320 ha.tr.un_320 nl.no.is_632 pl.de.zu_665
+ 0x23000819, 0x190b07a0, 0x3b130207, 0x043f0304, // no.ca.un_750 it.es.gl_322 da.et.so_432 nl.af.fi_332
+ 0x122a1fa9, 0x3f021fec, 0x0a12195a, 0x2b006e08, // cy.mt.hu_544 cy.da.af_644 gl.hu.pt_553 hmn.vi.un_430
+ // [0f60]
+ 0x25005507, 0x32002807, 0x1c001a04, 0x190b10ee, // rw.eu.un_420 sw.bs.un_420 tl.id.un_320 lt.es.gl_422
+ 0x11000e13, 0x64002819, 0x121b0f04, 0x0c1306ad, // is.ro.un_650 sw.lg.un_750 lv.tr.hu_332 de.et.sv_643
+ 0x10000f19, 0x18284aa0, 0x1e002302, 0x1c1f0704, // lv.lt.un_750 yo.sw.ga_322 ca.ms.un_220 it.cy.id_332
+ 0x644a35af, 0x09531107, 0x06003b12, 0x07041004, // zu.yo.lg_655 ro.ht.pl_432 so.de.un_640 be.ru.bg_332
+ // [0f70]
+ 0x0e3152a0, 0x02556ba0, 0x065221a6, 0x5300030d, // ha.az.is_322 ceb.rw.da_322 jw.ha.de_521 nl.ht.un_540
+ 0x0400640d, 0x2d0d35a9, 0x09001013, 0x040817a4, // lg.fi.un_540 zu.cs.sk_544 lt.pl.un_650 sr.uk.ru_433
+ 0x0a0710af, 0x04110713, 0x0800201a, 0x01005502, // be.bg.mk_655 bg.ro.ru_665 sq.no.un_760 rw.en.un_220
+ 0x0d350408, 0x041011ad, 0x0408070e, 0x1e1c3fa4, // fi.zu.cs_443 ro.be.ru_643 bg.uk.ru_555 af.id.ms_433
+ // [0f80]
+ 0x100708a4, 0x0400101b, 0x1b3125ee, 0x102012a7, // uk.bg.be_433 lt.fi.un_770 eu.az.tr_422 hu.sq.lt_532
+ 0x0a000712, 0x0e550a05, 0x1c06020d, 0x18004a04, // bg.mk.un_640 pt.rw.is_333 da.de.id_554 yo.ga.un_320
+ 0x1c003105, 0x21171605, 0x27033f0d, 0x110b0a02, // az.id.un_330 hr.sr.jw_333 af.nl.gd_554 pt.es.ro_222
+ 0x0c0601a6, 0x32160da4, 0x2d0a4aa0, 0x00004a01, // en.de.sv_521 cs.hr.bs_433 yo.pt.sk_322 yo.un.un_200
+ // [0f90]
+ 0x09556404, 0x280720ad, 0x1c090d11, 0x070410a7, // lg.rw.pl_332 sq.it.sw_643 ne.hi.mr_653 be.ru.bg_532
+ 0x20001602, 0x29002808, 0x21352804, 0x201a2507, // hr.sq.un_220 sw.sl.un_430 sw.zu.jw_332 eu.tl.sq_432
+ 0x08001702, 0x1304250b, 0x3b1b2807, 0x31556412, // sr.no.un_220 eu.fi.et_542 sw.tr.so_432 lg.rw.az_654
+ 0x1a6b5308, 0x17100808, 0x2d001008, 0x32202904, // ht.ceb.tl_443 uk.be.sr_443 lt.sk.un_430 sl.sq.bs_332
+ // [0fa0]
+ 0x55001f14, 0x6800642a, 0x3f3b6b02, 0x1b1f31af, // cy.rw.un_660 lg.ig.un_970 ceb.so.af_222 az.cy.tr_655
+ 0x12640404, 0x02080e04, 0x351b4a07, 0x06356404, // fi.lg.hu_332 is.no.da_332 yo.tr.zu_432 lg.zu.de_332
+ 0x210e3504, 0x55351f0e, 0x55005311, 0x321b55a4, // zu.is.jw_332 cy.zu.rw_555 ht.rw.un_630 rw.tr.bs_433
+ 0x32001008, 0x3f1303a4, 0x556435ec, 0x1b200605, // lt.bs.un_430 nl.et.af_433 zu.lg.rw_644 de.sq.tr_333
+ // [0fb0]
+ 0x1f10270c, 0x21001f1b, 0x103b31a4, 0x23000b04, // gd.lt.cy_543 cy.jw.un_770 az.so.lt_433 es.ca.un_320
+ 0x0c103f04, 0x08001713, 0x110a0708, 0x31111b0c, // af.lt.sv_332 sr.uk.un_650 bg.mk.ro_443 tr.ro.az_543
+ 0x32000304, 0x07040812, 0x033b3204, 0x351b55a0, // nl.bs.un_320 uk.ru.bg_654 bs.so.nl_332 rw.tr.zu_322
+ 0x3f036405, 0x4a6b0704, 0x6b1a21a0, 0x35282507, // lg.nl.af_333 it.ceb.yo_332 jw.tl.ceb_322 eu.sw.zu_432
+ // [0fc0]
+ 0x21003b19, 0x25050d07, 0x4a000a05, 0x08001207, // so.jw.un_750 cs.fr.eu_432 pt.yo.un_330 hu.no.un_420
+ 0x190b1105, 0x21201e09, 0x1c211e04, 0x06000b04, // ro.es.gl_333 ms.sq.jw_444 ms.jw.id_332 es.de.un_320
+ 0x11532512, 0x16001902, 0x3b1b31a4, 0x110d55a0, // eu.ht.ro_654 gl.hr.un_220 az.tr.so_433 rw.cs.ro_322
+ 0x35091fa7, 0x110a070c, 0x18091004, 0x3b000804, // cy.pl.zu_532 it.pt.ro_543 lt.pl.ga_332 no.so.un_320
+ // [0fd0]
+ 0x1f033fee, 0x5535640d, 0x010905ee, 0x29033fa4, // af.nl.cy_422 lg.zu.rw_554 fr.pl.en_422 af.nl.sl_433
+ 0x1b0910a6, 0x171809ee, 0x1200280b, 0x136e28a4, // lt.pl.tr_521 pl.ga.sr_422 sw.hu.un_520 sw.hmn.et_433
+ 0x122d0dad, 0x1b002019, 0x6428550c, 0x0a001007, // cs.sk.hu_643 sq.tr.un_750 rw.sw.lg_543 be.mk.un_420
+ 0x2a00070c, 0x020e08a4, 0x08230ead, 0x6e003f13, // it.mt.un_530 no.is.da_433 is.ca.no_643 af.hmn.un_650
+ // [0fe0]
+ 0x02006412, 0x08021fa0, 0x194a0eaf, 0x18002702, // lg.da.un_640 cy.da.no_322 is.yo.gl_655 gd.ga.un_220
+ 0x2a001704, 0x27532355, 0x31001a0e, 0x032105ee, // sr.mt.un_320 ca.ht.gd_442 tl.az.un_550 fr.jw.nl_422
+ 0x32002d04, 0x11002005, 0x0800180e, 0x20003202, // sk.bs.un_320 sq.ro.un_330 ga.no.un_550 bs.sq.un_220
+ 0x3f000d08, 0x04000913, 0x1c0621ee, 0x13091c14, // cs.af.un_430 pl.fi.un_650 jw.de.id_422 mr.hi.bh_666
+ // [0ff0]
+ 0x08040a05, 0x0d001304, 0x112320ee, 0x23530511, // mk.ru.uk_333 bh.ne.un_320 sq.ca.ro_422 fr.ht.ca_653
+ 0x19230b07, 0x2d0a09ad, 0x08020605, 0x3f136404, // es.ca.gl_432 pl.pt.sk_643 de.da.no_333 lg.et.af_332
+ 0x01000c04, 0x190a0ba4, 0x0d001914, 0x17001907, // sv.en.un_320 es.pt.gl_433 gl.cs.un_660 gl.sr.un_420
+ 0x0825010c, 0x27050108, 0x08000202, 0x0f132004, // en.eu.no_543 en.fr.gd_443 da.no.un_220 sq.et.lv_332
+
+ // [1000]
+ 0x0d3f2a11, 0x12001b07, 0x17000712, 0x6b530507, // mt.af.cs_653 tr.hu.un_420 bg.sr.un_640 fr.ht.ceb_432
+ 0x1a3f6bee, 0x0c003202, 0x06001313, 0x2d0d3f05, // ceb.af.tl_422 bs.sv.un_220 et.de.un_650 af.cs.sk_333
+ 0x0d001c08, 0x12000a05, 0x311b110d, 0x122d0aa4, // mr.ne.un_430 pt.hu.un_330 ro.tr.az_554 pt.sk.hu_433
+ 0x32002b08, 0x3f00290d, 0x291b0da0, 0x2d3129a0, // vi.bs.un_430 sl.af.un_540 cs.tr.sl_322 sl.az.sk_322
+ // [1010]
+ 0x171b0704, 0x251235a0, 0x1819010b, 0x0b005304, // it.tr.sr_332 zu.hu.eu_322 en.gl.ga_542 ht.es.un_320
+ 0x0605010b, 0x4a2753a6, 0x06002505, 0x080c1f0d, // en.fr.de_542 ht.gd.yo_521 eu.de.un_330 cy.sv.no_554
+ 0x31111baf, 0x17002907, 0x1b002519, 0x1b131f02, // tr.ro.az_655 sl.sr.un_420 eu.tr.un_750 cy.et.tr_222
+ 0x060a04ad, 0x11001f07, 0x010a07ee, 0x0a003512, // fi.pt.de_643 cy.ro.un_420 it.pt.en_422 zu.pt.un_640
+ // [1020]
+ 0x28000707, 0x0b0a19a4, 0x03002107, 0x3b003521, // it.sw.un_420 gl.pt.es_433 jw.nl.un_420 zu.so.un_860
+ 0x1c211108, 0x02006b08, 0x21000807, 0x112a0705, // ro.jw.id_443 ceb.da.un_430 no.jw.un_420 it.mt.ro_333
+ 0x171011ad, 0x1b353b12, 0x11000d0d, 0x16001a04, // ro.be.sr_643 so.zu.tr_654 cs.ro.un_540 tl.hr.un_320
+ 0x1b130414, 0x0c000e05, 0x52313508, 0x17321607, // fi.et.tr_666 is.sv.un_330 zu.az.ha_443 hr.bs.sr_432
+ // [1030]
+ 0x64000d12, 0x1a6b21ec, 0x551a2807, 0x09000204, // cs.lg.un_640 jw.ceb.tl_644 sw.tl.rw_432 da.pl.un_320
+ 0x6800641a, 0x25001b05, 0x06010c02, 0x4a005313, // lg.ig.un_760 tr.eu.un_330 sv.en.de_222 ht.yo.un_650
+ 0x3b3552a0, 0x12356408, 0x071704af, 0x035320ad, // ha.zu.so_322 lg.zu.hu_443 ru.sr.bg_655 sq.ht.nl_643
+ 0x0000270f, 0x05000e04, 0x0410110d, 0x09355507, // gd.un.un_600 is.fr.un_320 ro.be.ru_554 rw.zu.pl_432
+ // [1040]
+ 0x356b2da0, 0x1a5552ee, 0x021b03a0, 0x17001008, // sk.ceb.zu_322 ha.rw.tl_422 nl.tr.da_322 be.sr.un_430
+ 0x00001106, 0x17040a02, 0x120c0107, 0x3b00060e, // ro.un.un_400 mk.ru.sr_222 en.sv.hu_432 de.so.un_550
+ 0x351e1c09, 0x18001204, 0x28005204, 0x1f001e07, // id.ms.zu_444 ur.ar.un_320 ha.sw.un_320 ms.cy.un_420
+ 0x171629a9, 0x06000307, 0x0c0208ec, 0x190b06ed, // sl.hr.sr_544 nl.de.un_420 no.da.sv_644 de.es.gl_622
+ // [1050]
+ 0x13003207, 0x070f0107, 0x6b006418, 0x522a230e, // bs.et.un_420 en.lv.it_432 lg.ceb.un_740 ca.mt.ha_555
+ 0x20311b12, 0x0d1c0911, 0x0e002304, 0x3b0f52ad, // tr.az.sq_654 hi.mr.ne_653 ca.is.un_320 ha.lv.so_643
+ 0x53052d05, 0x0f1f5304, 0x121004af, 0x09131cad, // sk.fr.ht_333 ht.cy.lv_332 fi.lt.hu_655 mr.bh.hi_643
+ 0x2d3f0d0d, 0x0d002902, 0x1c211ea0, 0x0b2308a4, // cs.af.sk_554 sl.cs.un_220 ms.jw.id_322 no.ca.es_433
+ // [1060]
+ 0x6b2135ad, 0x3f0106a7, 0x4a002107, 0x0a001904, // zu.jw.ceb_643 de.en.af_532 jw.yo.un_420 gl.pt.un_320
+ 0x3b002019, 0x1c000d0e, 0x0c001b14, 0x111025a0, // sq.so.un_750 ne.mr.un_550 tr.sv.un_660 eu.lt.ro_322
+ 0x18282b11, 0x0c080b02, 0x1f133b08, 0x35002513, // vi.sw.ga_653 es.no.sv_222 so.et.cy_443 eu.zu.un_650
+ 0x5521285a, 0x07001107, 0x08023f09, 0x0c00100d, // sw.jw.rw_553 ro.bg.un_420 af.da.no_444 lt.sv.un_540
+ // [1070]
+ 0x080925a0, 0x13033f08, 0x11003512, 0x1c0d0905, // eu.pl.no_322 af.nl.et_443 zu.ro.un_640 hi.ne.mr_333
+ 0x06001e04, 0x0a00111a, 0x07000513, 0x19051855, // ms.de.un_320 ro.mk.un_760 fr.it.un_650 ga.fr.gl_442
+ 0x18000d05, 0x070501a4, 0x06001b12, 0x20001008, // cs.ga.un_330 en.fr.it_433 tr.de.un_640 lt.sq.un_430
+ 0x12003107, 0x3b000712, 0x1255250c, 0x0b001112, // az.hu.un_420 it.so.un_640 eu.rw.hu_543 ro.es.un_640
+ // [1080]
+ 0x29001019, 0x1c003b0e, 0x02006b04, 0x01000605, // lt.sl.un_750 so.id.un_550 ceb.da.un_320 de.en.un_330
+ 0x2d0d0fa0, 0x21291e04, 0x1a002102, 0x12003504, // lv.cs.sk_322 ms.sl.jw_332 jw.tl.un_220 zu.hu.un_320
+ 0x071b0a02, 0x100705a0, 0x19002a02, 0x011f35a0, // pt.tr.it_222 fr.it.lt_322 mt.gl.un_220 zu.cy.en_322
+ 0x2a2012ec, 0x1004170d, 0x111c55ee, 0x2d0d11ee, // hu.sq.mt_644 sr.ru.be_554 rw.id.ro_422 ro.cs.sk_422
+ // [1090]
+ 0x0c031311, 0x55002513, 0x161335ee, 0x0e5513ee, // et.nl.sv_653 eu.rw.un_650 zu.et.hr_422 et.rw.is_422
+ 0x68002513, 0x52006e05, 0x040a0711, 0x07170a02, // eu.ig.un_650 hmn.ha.un_330 bg.mk.ru_653 mk.sr.bg_222
+ 0x3f211c0d, 0x00002901, 0x6b1135a7, 0x18060111, // id.jw.af_554 sl.un.un_200 zu.ro.ceb_532 en.de.ga_653
+ 0x2a2031ad, 0x55003505, 0x070311ee, 0x17002d08, // az.sq.mt_643 zu.rw.un_330 ro.nl.it_422 sk.sr.un_430
+ // [10a0]
+ 0x29000312, 0x282718ad, 0x0000120a, 0x31001b09, // nl.sl.un_640 ga.gd.sw_643 ur.un.un_500 tr.az.un_440
+ 0x3f2903a0, 0x3b006402, 0x1f00641b, 0x20002919, // nl.sl.af_322 lg.so.un_220 lg.cy.un_770 sl.sq.un_750
+ 0x04001f18, 0x04002504, 0x10000408, 0x012d35a0, // cy.fi.un_740 eu.fi.un_320 ru.be.un_430 zu.sk.en_322
+ 0x080417a0, 0x04100808, 0x010a2b04, 0x350353ec, // sr.ru.uk_322 uk.be.ru_443 vi.pt.en_332 ht.nl.zu_644
+ // [10b0]
+ 0x27006e07, 0x4a201b07, 0x18033f12, 0x291b315a, // hmn.gd.un_420 tr.sq.yo_432 af.nl.ga_654 az.tr.sl_553
+ 0x033f64ec, 0x0d1c13ec, 0x1c252112, 0x2d0d10af, // lg.af.nl_644 bh.mr.ne_644 jw.eu.id_654 lt.cs.sk_655
+ 0x0817110e, 0x0e0401a0, 0x08001108, 0x1e180107, // ro.sr.uk_555 en.fi.is_322 ro.uk.un_430 en.ga.ms_432
+ 0x0c000605, 0x1f5325ad, 0x080411a0, 0x1f002307, // de.sv.un_330 eu.ht.cy_643 ro.ru.uk_322 ca.cy.un_420
+ // [10c0]
+ 0x552901a4, 0x07170aec, 0x524a3ba4, 0x6b1f270c, // en.sl.rw_433 mk.sr.bg_644 so.yo.ha_433 gd.cy.ceb_543
+ 0x0400080e, 0x17002905, 0x3b002b09, 0x0400050e, // uk.ru.un_550 sl.sr.un_330 vi.so.un_440 fr.fi.un_550
+ 0x0d171307, 0x010208ee, 0x19230ba0, 0x25002113, // et.sr.cs_432 no.da.en_422 es.ca.gl_322 jw.eu.un_650
+ 0x23001707, 0x25000a0c, 0x211b1307, 0x16001007, // sr.ca.un_420 pt.eu.un_530 et.tr.jw_432 lt.hr.un_420
+ // [10d0]
+ 0x3f4a0707, 0x00001224, 0x681b35a4, 0x190a1ba4, // it.yo.af_432 ur.un.un_900 zu.tr.ig_433 tr.pt.gl_433
+ 0x1b645513, 0x13001805, 0x0100090c, 0x6b2321a9, // rw.lg.tr_665 ga.et.un_330 pl.en.un_530 jw.ca.ceb_544
+ 0x0c05010c, 0x13050108, 0x17000a08, 0x5500211a, // en.fr.sv_543 en.fr.et_443 mk.sr.un_430 jw.rw.un_760
+ 0x08020c09, 0x071710ec, 0x641b35ee, 0x6e002705, // sv.da.no_444 be.sr.bg_644 zu.tr.lg_422 gd.hmn.un_330
+ // [10e0]
+ 0x080e3104, 0x07000813, 0x051103a0, 0x3b181308, // az.is.no_332 uk.bg.un_650 nl.ro.fr_322 et.ga.so_443
+ 0x12002302, 0x16002a05, 0x131204ec, 0x35171bee, // ca.hu.un_220 mt.hr.un_330 fi.hu.et_644 tr.sr.zu_422
+ 0x046425ad, 0x18001211, 0x284a350b, 0x20001f02, // eu.lg.fi_643 ur.ar.un_630 zu.yo.sw_542 cy.sq.un_220
+ 0x644a53ee, 0x17291607, 0x2d0c53a7, 0x18006b07, // ht.yo.lg_422 hr.sl.sr_432 ht.sv.sk_532 ceb.ga.un_420
+ // [10f0]
+ 0x1200230d, 0x061b31ee, 0x2d090da0, 0x10283507, // ca.hu.un_540 az.tr.de_422 cs.pl.sk_322 zu.sw.lt_432
+ 0x3f202107, 0x32162902, 0x0a070455, 0x17111011, // jw.sq.af_432 sl.hr.bs_222 ru.bg.mk_442 be.ro.sr_653
+ 0x6b060807, 0x080220a0, 0x18002519, 0x06080e08, // no.de.ceb_432 sq.da.no_322 eu.ga.un_750 is.no.de_443
+ 0x1b133507, 0x0b0123a0, 0x095231a7, 0x030604ad, // zu.et.tr_432 ca.en.es_322 az.ha.pl_532 fi.de.nl_643
+ // [1100]
+ 0x0f002d0e, 0x1c0d0911, 0x18005302, 0x08000308, // sk.lv.un_550 hi.ne.mr_653 ht.ga.un_220 nl.no.un_430
+ 0x21001c0e, 0x0e311baf, 0x04006409, 0x120e1809, // id.jw.un_550 tr.az.is_655 lg.fi.un_440 ga.is.hu_444
+ 0x0f001704, 0x17000a12, 0x0607030e, 0x06020702, // sr.lv.un_320 mk.sr.un_640 nl.it.de_555 it.da.de_222
+ 0x09121808, 0x6b1f01a0, 0x13043b60, 0x52003b04, // ga.hu.pl_443 en.cy.ceb_322 so.fi.et_664 so.ha.un_320
+ // [1110]
+ 0x683b08a4, 0x3b190a08, 0x1c0d0960, 0x53201ea0, // no.so.ig_433 pt.gl.so_443 hi.ne.mr_664 ms.sq.ht_322
+ 0x08130412, 0x1e1c1ba4, 0x070a11a7, 0x04000a02, // fi.et.no_654 tr.id.ms_433 ro.mk.bg_532 mk.ru.un_220
+ 0x0900060e, 0x05111002, 0x0a000704, 0x04001308, // de.pl.un_550 lt.ro.fr_222 it.pt.un_320 et.fi.un_430
+ 0x523121a0, 0x556435ee, 0x041017a4, 0x10002d0e, // jw.az.ha_322 zu.lg.rw_422 sr.be.ru_433 sk.lt.un_550
+ // [1120]
+ 0x1e1b350c, 0x6b0d2012, 0x1a005204, 0x2513020c, // zu.tr.ms_543 sq.cs.ceb_654 ha.tl.un_320 da.et.eu_543
+ 0x1f0e180e, 0x0a111714, 0x07000e07, 0x1b003b0e, // ga.is.cy_555 sr.ro.mk_666 is.it.un_420 so.tr.un_550
+ 0x0c0225a7, 0x076b04ad, 0x21121107, 0x0100050c, // eu.da.sv_532 fi.ceb.it_643 ro.hu.jw_432 fr.en.un_530
+ 0x0e000308, 0x0f00100e, 0x08070a12, 0x1e1a1b55, // nl.is.un_430 lt.lv.un_550 mk.bg.uk_654 tr.tl.ms_442
+ // [1130]
+ 0x08021f02, 0x11040a05, 0x0e001305, 0x13090dec, // cy.da.no_222 mk.ru.ro_333 et.is.un_330 ne.hi.bh_644
+ 0x130a0855, 0x13002504, 0x0a0804a4, 0x0e000c07, // no.pt.et_442 eu.et.un_320 ru.uk.mk_433 sv.is.un_420
+ 0x07001704, 0x1f001905, 0x35001e04, 0x4a08020d, // sr.bg.un_320 gl.cy.un_330 ms.zu.un_320 da.no.yo_554
+ 0x1008170c, 0x23190a02, 0x23000a05, 0x0928550c, // sr.uk.be_543 pt.gl.ca_222 pt.ca.un_330 rw.sw.pl_543
+ // [1140]
+ 0x550120ee, 0x21005207, 0x20001704, 0x64006b02, // sq.en.rw_422 ha.jw.un_420 sr.sq.un_320 ceb.lg.un_220
+ 0x6e003b12, 0x11070aa4, 0x21043f07, 0x09001f04, // so.hmn.un_640 mk.bg.ro_433 af.fi.jw_432 cy.pl.un_320
+ 0x3b3135a4, 0x3b0802a4, 0x1c1128ac, 0x0a0411ee, // zu.az.so_433 da.no.so_433 sw.ro.id_632 ro.ru.mk_422
+ 0x0000061c, 0x03002a0e, 0x090d13ec, 0x28003502, // de.un.un_800 mt.nl.un_550 bh.ne.hi_644 zu.sw.un_220
+ // [1150]
+ 0x2a523bee, 0x0a000718, 0x09003508, 0x033b680c, // so.ha.mt_422 bg.mk.un_740 zu.pl.un_430 ig.so.nl_543
+ 0x1c1b3f07, 0x07640ea4, 0x040a0705, 0x03061804, // af.tr.id_432 is.lg.it_433 bg.mk.ru_333 ga.de.nl_332
+ 0x200f12a4, 0x321729a0, 0x3f2a0908, 0x0c002a04, // hu.lv.sq_433 sl.sr.bs_322 pl.mt.af_443 mt.sv.un_320
+ 0x2a006b0d, 0x160908a0, 0x20000407, 0x1e006b04, // ceb.mt.un_540 no.pl.hr_322 fi.sq.un_420 ceb.ms.un_320
+ // [1160]
+ 0x00001c1c, 0x556452a7, 0x211e5255, 0x3f271a05, // mr.un.un_800 ha.lg.rw_532 ha.ms.jw_442 tl.gd.af_333
+ 0x2d0d21ec, 0x2a32020c, 0x1c211e07, 0x093f13a4, // jw.cs.sk_644 da.bs.mt_543 ms.jw.id_432 et.af.pl_433
+ 0x25000614, 0x2a1e28ac, 0x1a0e07a7, 0x03000608, // de.eu.un_660 sw.ms.mt_632 it.is.tl_532 de.nl.un_430
+ 0x0a170713, 0x0b001007, 0x09553509, 0x55002814, // bg.sr.mk_665 lt.es.un_420 zu.rw.pl_444 sw.rw.un_660
+ // [1170]
+ 0x041f0dad, 0x214a1aee, 0x55001112, 0x64000c05, // cs.cy.fi_643 tl.yo.jw_422 ro.rw.un_640 sv.lg.un_330
+ 0x3f2a0607, 0x6b0325ee, 0x1c0d09ec, 0x1200070e, // de.mt.af_432 eu.nl.ceb_422 hi.ne.mr_644 it.hu.un_550
+ 0x23180b02, 0x3f0713ee, 0x68006b0d, 0x0621030c, // es.ga.ca_222 et.it.af_422 ceb.ig.un_540 nl.jw.de_543
+ 0x4a000e19, 0x09001c12, 0x0a08170c, 0x52002a04, // is.yo.un_750 mr.hi.un_640 sr.uk.mk_543 mt.ha.un_320
+ // [1180]
+ 0x3f001f07, 0x092a0fec, 0x13033f12, 0x13283b04, // cy.af.un_420 lv.mt.pl_644 af.nl.et_654 so.sw.et_332
+ 0x31550fa4, 0x21286455, 0x311b2505, 0x64003b0e, // lv.rw.az_433 lg.sw.jw_442 eu.tr.az_333 so.lg.un_550
+ 0x033f1304, 0x2d0d19a0, 0x12001f04, 0x1c1221ee, // et.af.nl_332 gl.cs.sk_322 cy.hu.un_320 jw.hu.id_422
+ 0x190b0a14, 0x2a2b4aa0, 0x021f0812, 0x082d0d08, // pt.es.gl_666 yo.vi.mt_322 no.cy.da_654 cs.sk.no_443
+ // [1190]
+ 0x35093f0c, 0x090f1004, 0x29003202, 0x0d1c13a7, // af.pl.zu_543 lt.lv.pl_332 bs.sl.un_220 bh.mr.ne_532
+ 0x1c1a5207, 0x040d09a6, 0x04001107, 0x6b3b2b07, // ha.tl.id_432 pl.cs.fi_521 ro.ru.un_420 vi.so.ceb_432
+ 0x131b0407, 0x070b2312, 0x283b640c, 0x08020309, // fi.tr.et_432 ca.es.it_654 lg.so.sw_543 nl.da.no_444
+ 0x0a000c0c, 0x190b01a4, 0x6e356407, 0x3f130313, // sv.pt.un_530 en.es.gl_433 lg.zu.hmn_432 nl.et.af_665
+ // [11a0]
+ 0x1b5312a4, 0x5500280d, 0x4a3564ad, 0x683f1307, // hu.ht.tr_433 sw.rw.un_540 lg.zu.yo_643 et.af.ig_432
+ 0x0c001c02, 0x0d120c02, 0x103b07a0, 0x09130d08, // id.sv.un_220 sv.hu.cs_222 it.so.lt_322 ne.bh.hi_443
+ 0x100417af, 0x214a64ee, 0x1b2035ee, 0x686b1a0d, // sr.ru.be_655 lg.yo.jw_422 zu.sq.tr_422 tl.ceb.ig_554
+ 0x2a315207, 0x12050807, 0x4a0f10a4, 0x27181f55, // ha.az.mt_432 no.fr.hu_432 lt.lv.yo_433 cy.ga.gd_442
+ // [11b0]
+ 0x4a00680c, 0x050408a0, 0x212d0d5a, 0x3b002a02, // ig.yo.un_530 no.fi.fr_322 cs.sk.jw_553 mt.so.un_220
+ 0x06003f12, 0x29001602, 0x1a003504, 0x0a11170c, // af.de.un_640 hr.sl.un_220 zu.tl.un_320 sr.ro.mk_543
+ 0x55311ba4, 0x0b07205a, 0x0b6e2804, 0x28000b02, // tr.az.rw_433 sq.it.es_553 sw.hmn.es_332 es.sw.un_220
+ 0x3b000702, 0x1a005519, 0x12000d11, 0x080e2004, // it.so.un_220 rw.tl.un_750 cs.hu.un_630 sq.is.no_332
+ // [11c0]
+ 0x19002318, 0x21002b04, 0x2700011a, 0x3f6b25ec, // ca.gl.un_740 vi.jw.un_320 en.gd.un_760 eu.ceb.af_644
+ 0x04081f11, 0x04002013, 0x11002809, 0x64003504, // cy.no.fi_653 sq.fi.un_650 sw.ro.un_440 zu.lg.un_320
+ 0x2000290c, 0x0d000902, 0x112825ad, 0x1f00041a, // sl.sq.un_530 hi.ne.un_220 eu.sw.ro_643 fi.cy.un_760
+ 0x131204a7, 0x3b554aa0, 0x06000c02, 0x3f3564ad, // fi.hu.et_532 yo.rw.so_322 sv.de.un_220 lg.zu.af_643
+ // [11d0]
+ 0x0c2512a7, 0x6b2a1255, 0x6b4a1a04, 0x32643ba4, // hu.eu.sv_532 hu.mt.ceb_442 tl.yo.ceb_332 so.lg.bs_433
+ 0x55296404, 0x4a3b640c, 0x06033f13, 0x0d291107, // lg.sl.rw_332 lg.so.yo_543 af.nl.de_665 ro.sl.cs_432
+ 0x16002513, 0x11003b17, 0x64005312, 0x53320504, // eu.hr.un_650 so.ro.un_730 ht.lg.un_640 fr.bs.ht_332
+ 0x12000e07, 0x17290307, 0x0a000702, 0x02000e12, // is.hu.un_420 nl.sl.sr_432 bg.mk.un_220 is.da.un_640
+ // [11e0]
+ 0x2a006b07, 0x12002508, 0x080a11a6, 0x4a000d13, // ceb.mt.un_420 eu.hu.un_430 ro.mk.uk_521 cs.yo.un_650
+ 0x130c02a0, 0x0a070805, 0x2300010c, 0x1c00090c, // da.sv.et_322 uk.bg.mk_333 en.ca.un_530 hi.mr.un_530
+ 0x16000c04, 0x2d12640c, 0x3f00020c, 0x32002702, // sv.hr.un_320 lg.hu.sk_543 da.af.un_530 gd.bs.un_220
+ 0x093b1fee, 0x5300250e, 0x1a1225a0, 0x0d002105, // cy.so.pl_422 eu.ht.un_550 eu.hu.tl_322 jw.cs.un_330
+ // [11f0]
+ 0x3b3f03a7, 0x3f003b04, 0x0d0e18ad, 0x20006804, // nl.af.so_532 so.af.un_320 ga.is.cs_643 ig.sq.un_320
+ 0x52003509, 0x1312010c, 0x07000119, 0x3f2d0da4, // zu.ha.un_440 en.hu.et_543 en.it.un_750 cs.sk.af_433
+ 0x2b005204, 0x0d0913ad, 0x190b0a05, 0x6409355a, // ha.vi.un_320 bh.hi.ne_643 pt.es.gl_333 zu.pl.lg_553
+ 0x110623ec, 0x1c0310a7, 0x28181fac, 0x040e6b07, // ca.de.ro_644 lt.nl.id_532 cy.ga.sw_632 ceb.is.fi_432
+ // [1200]
+ 0x4a00320d, 0x1f3f0114, 0x040511a7, 0x11292d07, // bs.yo.un_540 en.af.cy_666 ro.fr.fi_532 sk.sl.ro_432
+ 0x0d0913ec, 0x230b0705, 0x2a1a11a4, 0x05202704, // bh.hi.ne_644 it.es.ca_333 ro.tl.mt_433 gd.sq.fr_332
+ 0x53001b1b, 0x20002108, 0x2700130e, 0x35556ba0, // tr.ht.un_770 jw.sq.un_430 et.gd.un_550 ceb.rw.zu_322
+ 0x3b060112, 0x03210fee, 0x1a552104, 0x3b000704, // en.de.so_654 lv.jw.nl_422 jw.rw.tl_332 it.so.un_320
+ // [1210]
+ 0x25681804, 0x311b6b0c, 0x04131aa0, 0x644a55ec, // ga.ig.eu_332 ceb.tr.az_543 tl.et.fi_322 rw.yo.lg_644
+ 0x1f002514, 0x04000a18, 0x1b080c0d, 0x520220ee, // eu.cy.un_660 mk.ru.un_740 sv.no.tr_554 sq.da.ha_422
+ 0x2a092da0, 0x1100071a, 0x1f051107, 0x112107a0, // sk.pl.mt_322 bg.ro.un_760 ro.fr.cy_432 it.jw.ro_322
+ 0x2527010b, 0x04002d12, 0x23000704, 0x0c251a02, // en.gd.eu_542 sk.fi.un_640 it.ca.un_320 tl.eu.sv_222
+ // [1220]
+ 0x0800060c, 0x0c3f0dee, 0x27000b08, 0x070b0a05, // de.no.un_530 cs.af.sv_422 es.gd.un_430 pt.es.it_333
+ 0x042d0dad, 0x3b001e02, 0x0e111307, 0x056b1a0c, // cs.sk.fi_643 ms.so.un_220 et.ro.is_432 tl.ceb.fr_543
+ 0x07080107, 0x20312aee, 0x060103a7, 0x080235ee, // en.no.it_432 mt.az.sq_422 nl.en.de_532 zu.da.no_422
+ 0x64001218, 0x190b13a4, 0x0d1c1308, 0x0a041708, // hu.lg.un_740 et.es.gl_433 bh.mr.ne_443 sr.ru.mk_443
+ // [1230]
+ 0x10000504, 0x29211f04, 0x20250107, 0x036b0aee, // fr.lt.un_320 cy.jw.sl_332 en.eu.sq_432 pt.ceb.nl_422
+ 0x64003202, 0x0f6e060b, 0x050b23a0, 0x3b1b0107, // bs.lg.un_220 de.hmn.lv_542 ca.es.fr_322 en.tr.so_432
+ 0x251c28ee, 0x00004a1c, 0x09131c11, 0x0c1f12a4, // sw.id.eu_422 yo.un.un_800 mr.bh.hi_653 hu.cy.sv_433
+ 0x08020ea4, 0x52006419, 0x25005304, 0x1225040c, // is.da.no_433 lg.ha.un_750 ht.eu.un_320 fi.eu.hu_543
+ // [1240]
+ 0x53000914, 0x12002a04, 0x35000a04, 0x0f060111, // pl.ht.un_660 mt.hu.un_320 pt.zu.un_320 en.de.lv_653
+ 0x1c0d0912, 0x236b5302, 0x2d122bad, 0x170a0805, // hi.ne.mr_654 ht.ceb.ca_222 vi.hu.sk_643 uk.mk.sr_333
+ 0x1c00130b, 0x2a002504, 0x1c130960, 0x17110a12, // bh.mr.un_520 eu.mt.un_320 hi.bh.mr_664 mk.ro.sr_654
+ 0x100a2d05, 0x0a000d04, 0x06130c08, 0x533b010b, // sk.pt.lt_333 cs.pt.un_320 sv.et.de_443 en.so.ht_542
+ // [1250]
+ 0x09080da0, 0x0a171013, 0x08005212, 0x131c0908, // cs.no.pl_322 be.sr.mk_665 ha.no.un_640 hi.mr.bh_443
+ 0x0a000413, 0x07001713, 0x06000e07, 0x1e1c2107, // ru.mk.un_650 sr.bg.un_650 is.de.un_420 jw.id.ms_432
+ 0x3b006e07, 0x0e003f04, 0x2a003b0d, 0x1831250c, // hmn.so.un_420 af.is.un_320 so.mt.un_540 eu.az.ga_543
+ 0x18001012, 0x0c083f04, 0x2a001a07, 0x6b6435a7, // lt.ga.un_640 af.no.sv_332 tl.mt.un_420 zu.lg.ceb_532
+ // [1260]
+ 0x07292da4, 0x12530aa0, 0x11000304, 0x081b2507, // sk.sl.it_433 pt.ht.hu_322 nl.ro.un_320 eu.tr.no_432
+ 0x1118270c, 0x313f5507, 0x1e044aa0, 0x27181ea0, // gd.ga.ro_543 rw.af.az_432 yo.fi.ms_322 ms.ga.gd_322
+ 0x01006b07, 0x18092da0, 0x1e311b08, 0x04062804, // ceb.en.un_420 sk.pl.ga_322 tr.az.ms_443 sw.de.fi_332
+ 0x06002704, 0x03062702, 0x0e311b05, 0x091c1307, // gd.de.un_320 gd.de.nl_222 tr.az.is_333 bh.mr.hi_432
+ // [1270]
+ 0x1c033f11, 0x042013ad, 0x1e2031a7, 0x2500110c, // af.nl.id_653 et.sq.fi_643 az.sq.ms_532 ro.eu.un_530
+ 0x11042808, 0x0a081711, 0x062a0711, 0x00001e15, // sw.fi.ro_443 sr.uk.mk_653 it.mt.de_653 ms.un.un_700
+ 0x20000704, 0x64683504, 0x07002008, 0x20000412, // it.sq.un_320 zu.ig.lg_332 sq.it.un_430 fi.sq.un_640
+ 0x1a043bac, 0x0c1f07ee, 0x091c13a4, 0x090c1004, // so.fi.tl_632 it.cy.sv_422 bh.mr.hi_433 lt.sv.pl_332
+ // [1280]
+ 0x52004a07, 0x0a003b0d, 0x32160bee, 0x25230407, // yo.ha.un_420 so.pt.un_540 es.hr.bs_422 fi.ca.eu_432
+ 0x0b002305, 0x1e311b12, 0x21000704, 0x0f1001a9, // ca.es.un_330 tr.az.ms_654 it.jw.un_320 en.lt.lv_544
+ 0x0e1812af, 0x1a5201a0, 0x12002d0e, 0x1c0d090b, // hu.ga.is_655 en.ha.tl_322 sk.hu.un_550 hi.ne.mr_542
+ 0x2b002707, 0x1b5209a4, 0x0000231c, 0x070817a4, // gd.vi.un_420 pl.ha.tr_433 ca.un.un_800 sr.uk.bg_433
+ // [1290]
+ 0x0c1a35ee, 0x3b281aaf, 0x3b000a04, 0x3f1b35a0, // zu.tl.sv_422 tl.sw.so_655 pt.so.un_320 zu.tr.af_322
+ 0x11003508, 0x1a00551b, 0x0f001c02, 0x09002909, // zu.ro.un_430 rw.tl.un_770 id.lv.un_220 sl.pl.un_440
+ 0x2d003f04, 0x25060fa0, 0x1c6b4aee, 0x1704110c, // af.sk.un_320 lv.de.eu_322 yo.ceb.id_422 ro.ru.sr_543
+ 0x07000b04, 0x2d0d3fa0, 0x09002904, 0x3b202805, // es.it.un_320 af.cs.sk_322 sl.pl.un_320 sw.sq.so_333
+ // [12a0]
+ 0x13100f0c, 0x04071012, 0x1f643b12, 0x1f092d04, // lv.lt.et_543 be.bg.ru_654 so.lg.cy_654 sk.pl.cy_332
+ 0x31001902, 0x322d2aee, 0x64350312, 0x081104a4, // gl.az.un_220 mt.sk.bs_422 nl.zu.lg_654 ru.ro.uk_433
+ 0x0c2106ee, 0x2d001113, 0x0d002012, 0x11002d0c, // de.jw.sv_422 ro.sk.un_650 sq.cs.un_640 sk.ro.un_530
+ 0x5300070e, 0x04001719, 0x16000d0c, 0x0c08230d, // it.ht.un_550 sr.ru.un_750 cs.hr.un_530 ca.no.sv_554
+ // [12b0]
+ 0x3b641aa0, 0x060c0204, 0x1f00070e, 0x0d001602, // tl.lg.so_322 da.sv.de_332 it.cy.un_550 hr.cs.un_220
+ 0x6e001313, 0x0700550d, 0x190b23a4, 0x07200fee, // et.hmn.un_650 rw.it.un_540 ca.es.gl_433 lv.sq.it_422
+ 0x285535ec, 0x28521ea0, 0x64073504, 0x2800550c, // zu.rw.sw_644 ms.ha.sw_322 zu.it.lg_332 rw.sw.un_530
+ 0x21001312, 0x0923110c, 0x0500210d, 0x52071105, // et.jw.un_640 ro.ca.pl_543 jw.fr.un_540 ro.it.ha_333
+ // [12c0]
+ 0x1a1c2507, 0x10001a04, 0x05005204, 0x12072807, // eu.id.tl_432 tl.lt.un_320 ha.fr.un_320 sw.it.hu_432
+ 0x00001a24, 0x35644a0c, 0x121e3bad, 0x35003b1b, // tl.un.un_900 yo.lg.zu_543 so.ms.hu_643 so.zu.un_770
+ 0x55006b05, 0x53071b04, 0x31110f07, 0x32001104, // ceb.rw.un_330 tr.it.ht_332 lv.ro.az_432 ro.bs.un_320
+ 0x6b002104, 0x0c3f030c, 0x0400290e, 0x08000f12, // jw.ceb.un_320 nl.af.sv_543 sl.fi.un_550 lv.no.un_640
+ // [12d0]
+ 0x0a005302, 0x1e1c28ac, 0x1e215202, 0x2319010b, // ht.pt.un_220 sw.id.ms_632 ha.jw.ms_222 en.gl.ca_542
+ 0x00001801, 0x0800040e, 0x3f000c04, 0x2d1f0dad, // ga.un.un_200 ru.uk.un_550 sv.af.un_320 cs.cy.sk_643
+ 0x286435a7, 0x19004a04, 0x213f03ee, 0x0d180608, // zu.lg.sw_532 yo.gl.un_320 nl.af.jw_422 de.ga.cs_443
+ 0x2d006e07, 0x2d000d02, 0x13000907, 0x100835ee, // hmn.sk.un_420 cs.sk.un_220 hi.bh.un_420 zu.no.lt_422
+ // [12e0]
+ 0x04171012, 0x25293b07, 0x065513a7, 0x231b3ba0, // be.sr.ru_654 so.sl.eu_432 et.rw.de_532 so.tr.ca_322
+ 0x0c002a0e, 0x11000512, 0x01640f07, 0x043b1312, // mt.sv.un_550 fr.ro.un_640 lv.lg.en_432 et.so.fi_654
+ 0x18273512, 0x6b1a01a9, 0x015535a0, 0x1b061213, // zu.gd.ga_654 en.tl.ceb_544 zu.rw.en_322 hu.de.tr_665
+ 0x530428ad, 0x121735a0, 0x2b003505, 0x13640412, // sw.fi.ht_643 zu.sr.hu_322 zu.vi.un_330 fi.lg.et_654
+ // [12f0]
+ 0x043b6404, 0x121b0cee, 0x6e005512, 0x2b5511a0, // lg.so.fi_332 sv.tr.hu_422 rw.hmn.un_640 ro.rw.vi_322
+ 0x1e002102, 0x0e000c04, 0x1e1c5209, 0x25001b13, // jw.ms.un_220 sv.is.un_320 ha.id.ms_444 tr.eu.un_650
+ 0x2000211a, 0x253b2a0c, 0x2d0d2b0c, 0x3f000311, // jw.sq.un_760 mt.so.eu_543 vi.cs.sk_543 nl.af.un_630
+ 0x0c081f05, 0x211b28ee, 0x293113a4, 0x17000f04, // cy.no.sv_333 sw.tr.jw_422 et.az.sl_433 lv.sr.un_320
+ // [1300]
+ 0x211628ee, 0x53280307, 0x110504a4, 0x13000d1a, // sw.hr.jw_422 nl.sw.ht_432 fi.fr.ro_433 ne.bh.un_760
+ 0x1a00641a, 0x0c0413a0, 0x060c05a4, 0x023531a4, // lg.tl.un_760 et.fi.sv_322 fr.sv.de_433 az.zu.da_433
+ 0x03003f07, 0x52351107, 0x1b001304, 0x1c005219, // af.nl.un_420 ro.zu.ha_432 et.tr.un_320 ha.id.un_750
+ 0x11130f08, 0x070103a0, 0x0b002704, 0x2d000f0e, // lv.et.ro_443 nl.en.it_322 gd.es.un_320 lv.sk.un_550
+ // [1310]
+ 0x09003f0d, 0x2a3b3108, 0x21001704, 0x0d001113, // af.pl.un_540 az.so.mt_443 sr.jw.un_320 ro.cs.un_650
+ 0x6404520c, 0x231901a4, 0x354a3ba0, 0x08131a0c, // ha.fi.lg_543 en.gl.ca_433 so.yo.zu_322 tl.et.no_543
+ 0x2d070907, 0x1a000119, 0x310e09ad, 0x3f000613, // pl.it.sk_432 en.tl.un_750 pl.is.az_643 de.af.un_650
+ 0x01002104, 0x190b2502, 0x1a2911a0, 0x0e080ca9, // jw.en.un_320 eu.es.gl_222 ro.sl.tl_322 sv.no.is_544
+ // [1320]
+ 0x2d001902, 0x1f2a5304, 0x0d00110d, 0x041355ee, // gl.sk.un_220 ht.mt.cy_332 ro.cs.un_540 rw.et.fi_422
+ 0x0d005208, 0x2a200e07, 0x1c641ea4, 0x0a002d13, // ha.cs.un_430 is.sq.mt_432 ms.lg.id_433 sk.pt.un_650
+ 0x200413a7, 0x29090fad, 0x090e29a4, 0x2b005507, // et.fi.sq_532 lv.pl.sl_643 sl.is.pl_433 rw.vi.un_420
+ 0x07112507, 0x2a0413af, 0x211b04a4, 0x21001c12, // eu.ro.it_432 et.fi.mt_655 fi.tr.jw_433 id.jw.un_640
+ // [1330]
+ 0x29001014, 0x21002013, 0x311a06a6, 0x52002312, // lt.sl.un_660 sq.jw.un_650 de.tl.az_521 ca.ha.un_640
+ 0x020c0811, 0x0b002312, 0x190b23ec, 0x2a060708, // no.sv.da_653 ca.es.un_640 ca.es.gl_644 it.de.mt_443
+ 0x0f002911, 0x102d0d0d, 0x3f031305, 0x2a3b3112, // sl.lv.un_630 cs.sk.lt_554 et.nl.af_333 az.so.mt_654
+ 0x00000203, 0x55005204, 0x1b002304, 0x040811ad, // da.un.un_300 ha.rw.un_320 ca.tr.un_320 ro.uk.ru_643
+ // [1340]
+ 0x07000402, 0x13042805, 0x04001708, 0x110a23a4, // ru.bg.un_220 sw.fi.et_333 sr.ru.un_430 ca.pt.ro_433
+ 0x1b004a04, 0x25001902, 0x13005302, 0x31001b05, // yo.tr.un_320 gl.eu.un_220 ht.et.un_220 tr.az.un_330
+ 0x21083fee, 0x1f000d04, 0x00001203, 0x07041f12, // af.no.jw_422 cs.cy.un_320 ur.un.un_300 cy.fi.it_654
+ 0x17293b04, 0x01556407, 0x04251f5a, 0x13001107, // so.sl.sr_332 lg.rw.en_432 cy.eu.fi_553 ro.et.un_420
+ // [1350]
+ 0x3500520e, 0x180a2bec, 0x182d4a0c, 0x251e3b07, // ha.zu.un_550 vi.pt.ga_644 yo.sk.ga_543 so.ms.eu_432
+ 0x290813ee, 0x53001e04, 0x1c002102, 0x53001307, // et.no.sl_422 ms.ht.un_320 jw.id.un_220 et.ht.un_420
+ 0x080411a6, 0x351a5505, 0x52160dee, 0x1c0d09a4, // ro.ru.uk_521 rw.tl.zu_333 cs.hr.ha_422 hi.ne.mr_433
+ 0x17100805, 0x11003b0c, 0x285564a6, 0x04000c07, // uk.be.sr_333 so.ro.un_530 lg.rw.sw_521 sv.fi.un_420
+ // [1360]
+ 0x55001c08, 0x07004a07, 0x193b25ec, 0x4a35520c, // id.rw.un_430 yo.it.un_420 eu.so.gl_644 ha.zu.yo_543
+ 0x0c0813ee, 0x6b12350d, 0x11350704, 0x0e000b02, // et.no.sv_422 zu.hu.ceb_554 it.zu.ro_332 es.is.un_220
+ 0x3f036407, 0x3b001902, 0x101c3bad, 0x2d0c0812, // lg.nl.af_432 gl.so.un_220 so.id.lt_643 no.sv.sk_654
+ 0x10081a0c, 0x2d0d1602, 0x294a6407, 0x1c00520d, // tl.no.lt_543 hr.cs.sk_222 lg.yo.sl_432 ha.id.un_540
+ // [1370]
+ 0x020752a4, 0x17070dee, 0x09000114, 0x0c083502, // ha.it.da_433 cs.it.sr_422 en.pl.un_660 zu.no.sv_222
+ 0x2110350e, 0x31281b07, 0x19006b04, 0x250b19ee, // zu.lt.jw_555 tr.sw.az_432 ceb.gl.un_320 gl.es.eu_422
+ 0x0523010c, 0x0a10170c, 0x0410170d, 0x3f120305, // en.ca.fr_543 sr.be.mk_543 sr.be.ru_554 nl.hu.af_333
+ 0x4a005305, 0x102328a0, 0x18000613, 0x643f35a0, // ht.yo.un_330 sw.ca.lt_322 de.ga.un_650 zu.af.lg_322
+ // [1380]
+ 0x040811a9, 0x28003f04, 0x073b2304, 0x03001004, // ro.uk.ru_544 af.sw.un_320 ca.so.it_332 lt.nl.un_320
+ 0x080e1f11, 0x03000613, 0x17070413, 0x23005504, // cy.is.no_653 de.nl.un_650 ru.bg.sr_665 rw.ca.un_320
+ 0x081827ad, 0x130435ee, 0x0e00121a, 0x0d001902, // gd.ga.no_643 zu.fi.et_422 hu.is.un_760 gl.cs.un_220
+ 0x190118a0, 0x182b1aa0, 0x00001201, 0x13552805, // ga.en.gl_322 tl.vi.ga_322 ur.un.un_200 sw.rw.et_333
+ // [1390]
+ 0x29001b0c, 0x17001111, 0x3f0364ec, 0x0c000608, // tr.sl.un_530 ro.sr.un_630 lg.nl.af_644 de.sv.un_430
+ 0x0a00530b, 0x64210412, 0x23120507, 0x12000513, // ht.pt.un_520 fi.jw.lg_654 fr.hu.ca_432 fr.hu.un_650
+ 0x251120a4, 0x080f13a4, 0x0c2311af, 0x0b10280c, // sq.ro.eu_433 et.lv.no_433 ro.ca.sv_655 sw.lt.es_543
+ 0x0a113b07, 0x3500551a, 0x2d0d0fa4, 0x0b02080b, // so.ro.pt_432 rw.zu.un_760 lv.cs.sk_433 no.da.es_542
+ // [13a0]
+ 0x0420250c, 0x0c1308ec, 0x0c000613, 0x0f000914, // eu.sq.fi_543 no.et.sv_644 de.sv.un_650 pl.lv.un_660
+ 0x08132907, 0x12001312, 0x0c030aa0, 0x060401a4, // sl.et.no_432 et.hu.un_640 pt.nl.sv_322 en.fi.de_433
+ 0x55001313, 0x0f3b13ec, 0x0d091c0c, 0x182725a6, // et.rw.un_650 et.so.lv_644 mr.hi.ne_543 eu.gd.ga_521
+ 0x0f002704, 0x131a04a0, 0x071104af, 0x126b1a08, // gd.lv.un_320 fi.tl.et_322 ru.ro.bg_655 tl.ceb.hu_443
+ // [13b0]
+ 0x0a001004, 0x04003b04, 0x29061fa0, 0x53110ca0, // be.mk.un_320 so.fi.un_320 cy.de.sl_322 sv.ro.ht_322
+ 0x2b001818, 0x645235ee, 0x126b13ee, 0x1f001105, // ga.vi.un_740 zu.ha.lg_422 et.ceb.hu_422 ro.cy.un_330
+ 0x29000f18, 0x292d0f07, 0x201b1f07, 0x045305ee, // lv.sl.un_740 lv.sk.sl_432 cy.tr.sq_432 fr.ht.fi_422
+ 0x523b2109, 0x12000c04, 0x531925a0, 0x0800680c, // jw.so.ha_444 sv.hu.un_320 eu.gl.ht_322 ig.no.un_530
+ // [13c0]
+ 0x06000412, 0x033b3fa9, 0x070417af, 0x07005304, // fi.de.un_640 af.so.nl_544 sr.ru.bg_655 ht.it.un_320
+ 0x52001e02, 0x4a000b04, 0x03083fee, 0x00002803, // ms.ha.un_220 es.yo.un_320 af.no.nl_422 sw.un.un_300
+ 0x25002104, 0x190b0a09, 0x31003518, 0x28122511, // jw.eu.un_320 pt.es.gl_444 zu.az.un_740 eu.hu.sw_653
+ 0x10080faf, 0x6b002807, 0x3b6453ec, 0x352a55af, // lv.no.lt_655 sw.ceb.un_420 ht.lg.so_644 rw.mt.zu_655
+ // [13d0]
+ 0x643552a7, 0x1e292d07, 0x2a55640c, 0x2d002a08, // ha.zu.lg_532 sk.sl.ms_432 lg.rw.mt_543 mt.sk.un_430
+ 0x0d2d090d, 0x06003505, 0x21525513, 0x04005309, // pl.sk.cs_554 zu.de.un_330 rw.ha.jw_665 ht.fi.un_440
+ 0x18000a0d, 0x0d005302, 0x08131b04, 0x2a0112ee, // pt.ga.un_540 ht.cs.un_220 tr.et.no_332 hu.en.mt_422
+ 0x0d00050e, 0x23132aa0, 0x0a001702, 0x10000e14, // fr.cs.un_550 mt.et.ca_322 sr.mk.un_220 is.lt.un_660
+ // [13e0]
+ 0x06000804, 0x023f0807, 0x02000e04, 0x1c130dec, // no.de.un_320 no.af.da_432 is.da.un_320 ne.bh.mr_644
+ 0x011205ee, 0x1008020d, 0x010705a0, 0x13091cec, // fr.hu.en_422 da.no.lt_554 fr.it.en_322 mr.hi.bh_644
+ 0x00000501, 0x2a000808, 0x060302a0, 0x1f002a07, // fr.un.un_200 no.mt.un_430 da.nl.de_322 mt.cy.un_420
+ 0x10080260, 0x0508020e, 0x0100530c, 0x01002a04, // da.no.lt_664 da.no.fr_555 ht.en.un_530 mt.en.un_320
+ // [13f0]
+ 0x0e000814, 0x0d252dad, 0x133f1b07, 0x535564af, // no.is.un_660 sk.eu.cs_643 tr.af.et_432 lg.rw.ht_655
+ 0x18000511, 0x1000250d, 0x132308a4, 0x3b1f1ba7, // fr.ga.un_630 eu.lt.un_540 no.ca.et_433 tr.cy.so_532
+ 0x2a641fad, 0x10001702, 0x05002504, 0x04002104, // cy.lg.mt_643 sr.lt.un_220 eu.fr.un_320 jw.fi.un_320
+ 0x070804a4, 0x1f6428ad, 0x3f060f07, 0x2d120d09, // ru.uk.bg_433 sw.lg.cy_643 lv.de.af_432 cs.hu.sk_444
+
+ // [1400]
+ 0x03250605, 0x2d006b07, 0x5300101a, 0x0c001908, // de.eu.nl_333 ceb.sk.un_420 lt.ht.un_760 gl.sv.un_430
+ 0x06000113, 0x2d00060e, 0x08001304, 0x28002512, // en.de.un_650 de.sk.un_550 et.no.un_320 eu.sw.un_640
+ 0x55001704, 0x53002120, 0x4a001012, 0x100613a0, // sr.rw.un_320 jw.ht.un_850 lt.yo.un_640 et.de.lt_322
+ 0x3f0327a4, 0x1b002504, 0x0a001e04, 0x133f0e07, // gd.nl.af_433 eu.tr.un_320 ms.pt.un_320 is.af.et_432
+ // [1410]
+ 0x081b1faf, 0x1c1e2807, 0x1b00310c, 0x18205307, // cy.tr.no_655 sw.ms.id_432 az.tr.un_530 ht.sq.ga_432
+ 0x1c0913ad, 0x522d5309, 0x530f0107, 0x0a002108, // bh.hi.mr_643 ht.sk.ha_444 en.lv.ht_432 jw.pt.un_430
+ 0x05000118, 0x6b005512, 0x01311ba0, 0x0e080204, // en.fr.un_740 rw.ceb.un_640 tr.az.en_322 da.no.is_332
+ 0x31112dee, 0x00000d06, 0x4a00180d, 0x103f35a7, // sk.ro.az_422 ne.un.un_400 ga.yo.un_540 zu.af.lt_532
+ // [1420]
+ 0x0c001305, 0x0b28100c, 0x11000813, 0x1c0d13a0, // et.sv.un_330 lt.sw.es_543 uk.ro.un_650 bh.ne.mr_322
+ 0x283b1b08, 0x18194aee, 0x21685507, 0x131123ee, // tr.so.sw_443 yo.gl.ga_422 rw.ig.jw_432 ca.ro.et_422
+ 0x520435ee, 0x041710a0, 0x1e1c13ee, 0x0c060202, // zu.fi.ha_422 be.sr.ru_322 et.id.ms_422 da.de.sv_222
+ 0x4a002b14, 0x52003513, 0x3b041304, 0x23001104, // vi.yo.un_660 zu.ha.un_650 et.fi.so_332 ro.ca.un_320
+ // [1430]
+ 0x5506520c, 0x4a072ba4, 0x0e1b3112, 0x12032a04, // ha.de.rw_543 vi.it.yo_433 az.tr.is_654 mt.nl.hu_332
+ 0x0c003f0d, 0x4a6407ee, 0x27091809, 0x1105070d, // af.sv.un_540 it.lg.yo_422 ga.pl.gd_444 it.fr.ro_554
+ 0x09001f13, 0x3b310b04, 0x092d0d0d, 0x0e100c04, // cy.pl.un_650 es.az.so_332 cs.sk.pl_554 sv.lt.is_332
+ 0x3f030112, 0x03000509, 0x4a6b1a11, 0x01002504, // en.nl.af_654 fr.nl.un_440 tl.ceb.yo_653 eu.en.un_320
+ // [1440]
+ 0x07080a0c, 0x1c001e0c, 0x0f100c08, 0x6418010c, // mk.uk.bg_543 ms.id.un_530 sv.lt.lv_443 en.ga.lg_543
+ 0x170408a7, 0x0d001702, 0x0e001013, 0x00001806, // uk.ru.sr_532 sr.cs.un_220 lt.is.un_650 ga.un.un_400
+ 0x0f002104, 0x06286ba4, 0x1a6b4a13, 0x13043b07, // jw.lv.un_320 ceb.sw.de_433 yo.ceb.tl_665 so.fi.et_432
+ 0x35003b04, 0x25000714, 0x2b003f08, 0x0e3b095a, // so.zu.un_320 it.eu.un_660 af.vi.un_430 pl.so.is_553
+ // [1450]
+ 0x0d1c0912, 0x2d110d0c, 0x020106ee, 0x11071709, // hi.mr.ne_654 cs.ro.sk_543 de.en.da_422 sr.bg.ro_444
+ 0x100d0412, 0x230825a0, 0x251104ec, 0x1e3b350c, // fi.cs.lt_654 eu.no.ca_322 fi.ro.eu_644 zu.so.ms_543
+ 0x17001018, 0x293510a4, 0x11001b0e, 0x11001f05, // be.sr.un_740 lt.zu.sl_433 tr.ro.un_550 cy.ro.un_330
+ 0x0d072807, 0x35556404, 0x25046805, 0x192107a6, // sw.it.cs_432 lg.rw.zu_332 ig.fi.eu_333 it.jw.gl_521
+ // [1460]
+ 0x04002519, 0x52213508, 0x35001219, 0x102106a6, // eu.fi.un_750 zu.jw.ha_443 hu.zu.un_750 de.jw.lt_521
+ 0x16003507, 0x552864a9, 0x0d13095a, 0x1e1c0909, // zu.hr.un_420 lg.sw.rw_544 hi.bh.ne_553 pl.id.ms_444
+ 0x041b0faf, 0x2a06010c, 0x0625350c, 0x082b03a7, // lv.tr.fi_655 en.de.mt_543 zu.eu.de_543 nl.vi.no_532
+ 0x3f0f0907, 0x4a311b12, 0x16001e04, 0x1b5311ee, // pl.lv.af_432 tr.az.yo_654 ms.hr.un_320 ro.ht.tr_422
+ // [1470]
+ 0x292d3b0e, 0x00000d01, 0x3b0f1ea4, 0x041320a9, // so.sk.sl_555 ne.un.un_200 ms.lv.so_433 sq.et.fi_544
+ 0x16000407, 0x0a102d55, 0x190b35ee, 0x07000304, // fi.hr.un_420 sk.lt.pt_442 zu.es.gl_422 nl.it.un_320
+ 0x100a0407, 0x04000a12, 0x3f002a0c, 0x64551ba4, // ru.mk.be_432 mk.ru.un_640 mt.af.un_530 tr.rw.lg_433
+ 0x6e003519, 0x06006404, 0x1c13090d, 0x110b25a0, // zu.hmn.un_750 lg.de.un_320 hi.bh.mr_554 eu.es.ro_322
+ // [1480]
+ 0x18003507, 0x010535a0, 0x2d290d12, 0x05003b0d, // zu.ga.un_420 zu.fr.en_322 cs.sl.sk_654 so.fr.un_540
+ 0x350928ee, 0x026e1804, 0x552305a7, 0x29230202, // sw.pl.zu_422 ga.hmn.da_332 fr.ca.rw_532 da.ca.sl_222
+ 0x0200180d, 0x16000f12, 0x64006b0d, 0x3b005505, // ga.da.un_540 lv.hr.un_640 ceb.lg.un_540 rw.so.un_330
+ 0x07006409, 0x0a2718ec, 0x28000414, 0x09000d0c, // lg.it.un_440 ga.gd.pt_644 fi.sw.un_660 ne.hi.un_530
+ // [1490]
+ 0x01001802, 0x172129a7, 0x060e08ee, 0x03001b12, // ga.en.un_220 sl.jw.sr_532 no.is.de_422 tr.nl.un_640
+ 0x3b6835a9, 0x1a6b0807, 0x290d100e, 0x040a10ad, // zu.ig.so_544 no.ceb.tl_432 lt.cs.sl_555 be.mk.ru_643
+ 0x106b1b07, 0x25230307, 0x07002a04, 0x4a00230e, // tr.ceb.lt_432 nl.ca.eu_432 mt.it.un_320 ca.yo.un_550
+ 0x1b001108, 0x1c2113a6, 0x31000a0e, 0x0d131c11, // ro.tr.un_430 et.jw.id_521 pt.az.un_550 mr.bh.ne_653
+ // [14a0]
+ 0x20002a02, 0x3b002712, 0x01045507, 0x2d0d1ca0, // mt.sq.un_220 gd.so.un_640 rw.fi.en_432 id.cs.sk_322
+ 0x2b001104, 0x21003f0d, 0x2300050d, 0x091b31a4, // ro.vi.un_320 af.jw.un_540 fr.ca.un_540 az.tr.pl_433
+ 0x0c000304, 0x28000c0e, 0x08001104, 0x1253200d, // nl.sv.un_320 sv.sw.un_550 ro.uk.un_320 sq.ht.hu_554
+ 0x1c091307, 0x1e001902, 0x0b0a0fec, 0x2b2d3b04, // bh.hi.mr_432 gl.ms.un_220 lv.pt.es_644 so.sk.vi_332
+ // [14b0]
+ 0x070417ee, 0x1c1309a9, 0x00000206, 0x12002704, // sr.ru.bg_422 hi.bh.mr_544 da.un.un_400 gd.hu.un_320
+ 0x35006409, 0x6b192507, 0x55006405, 0x0000640a, // lg.zu.un_440 eu.gl.ceb_432 lg.rw.un_330 lg.un.un_500
+ 0x643b0e05, 0x10003b07, 0x322817a4, 0x0a00120b, // is.so.lg_333 so.lt.un_420 sr.sw.bs_433 hu.pt.un_520
+ 0x3552280c, 0x2b031ea0, 0x1b000c04, 0x06003f02, // sw.ha.zu_543 ms.nl.vi_322 sv.tr.un_320 af.de.un_220
+ // [14c0]
+ 0x643521a6, 0x0a0f1004, 0x55281212, 0x23001605, // jw.zu.lg_521 lt.lv.pt_332 hu.sw.rw_654 hr.ca.un_330
+ 0x032b16a0, 0x35000f12, 0x1c0d1307, 0x3f030c08, // hr.vi.nl_322 lv.zu.un_640 bh.ne.mr_432 sv.nl.af_443
+ 0x253b31ee, 0x3b0653ec, 0x20061b07, 0x32003505, // az.so.eu_422 ht.de.so_644 tr.de.sq_432 zu.bs.un_330
+ 0x18131f55, 0x53000507, 0x0417080e, 0x3f00070b, // cy.et.ga_442 fr.ht.un_420 uk.sr.ru_555 it.af.un_520
+ // [14d0]
+ 0x01002707, 0x02060ca0, 0x10001707, 0x16002d04, // gd.en.un_420 sv.de.da_322 sr.be.un_420 sk.hr.un_320
+ 0x190b1fa4, 0x642835a0, 0x12352004, 0x28001c04, // cy.es.gl_433 zu.sw.lg_322 sq.zu.hu_332 id.sw.un_320
+ 0x2a1e2107, 0x01002702, 0x03133fa9, 0x2a311e08, // jw.ms.mt_432 gd.en.un_220 af.et.nl_544 ms.az.mt_443
+ 0x130408af, 0x213b28ee, 0x1f3553a0, 0x53352804, // no.fi.et_655 sw.so.jw_422 ht.zu.cy_322 sw.zu.ht_332
+ // [14e0]
+ 0x1e1c13a0, 0x180128a6, 0x3f210360, 0x6b5301a4, // et.id.ms_322 sw.en.ga_521 nl.jw.af_664 en.ht.ceb_433
+ 0x11005507, 0x0d091c0e, 0x32122904, 0x2000310e, // rw.ro.un_420 mr.hi.ne_555 sl.hu.bs_332 az.sq.un_550
+ 0x0b005302, 0x11072aa4, 0x211e0eee, 0x10170808, // ht.es.un_220 mt.it.ro_433 is.ms.jw_422 uk.sr.be_443
+ 0x29002d05, 0x06000e13, 0x12002702, 0x1b1120ad, // sk.sl.un_330 is.de.un_650 gd.hu.un_220 sq.ro.tr_643
+ // [14f0]
+ 0x030608a4, 0x1a003f07, 0x06001f07, 0x3b081a12, // no.de.nl_433 af.tl.un_420 cy.de.un_420 tl.no.so_654
+ 0x08006b05, 0x12002019, 0x18270304, 0x080e02ee, // ceb.no.un_330 sq.hu.un_750 nl.gd.ga_332 da.is.no_422
+ 0x00002a24, 0x273f0360, 0x551264ec, 0x06002312, // mt.un.un_900 nl.af.gd_664 lg.hu.rw_644 ca.de.un_640
+ 0x050318a0, 0x68004a09, 0x020c2912, 0x1e1c2aa0, // ga.nl.fr_322 yo.ig.un_440 sl.sv.da_654 mt.id.ms_322
+ // [1500]
+ 0x07002718, 0x101127a0, 0x210468ad, 0x1c270712, // gd.it.un_740 gd.ro.lt_322 ig.fi.jw_643 it.gd.id_654
+ 0x55001e02, 0x1000040b, 0x234a18ee, 0x290c1807, // ms.rw.un_220 ru.be.un_520 ga.yo.ca_422 ga.sv.sl_432
+ 0x2d0d04ec, 0x0f001e07, 0x2a000d1a, 0x13060f07, // fi.cs.sk_644 ms.lv.un_420 cs.mt.un_760 lv.de.et_432
+ 0x4a101f04, 0x1b130507, 0x031c23a0, 0x2a00180d, // cy.lt.yo_332 fr.et.tr_432 ca.id.nl_322 ga.mt.un_540
+ // [1510]
+ 0x12003b0d, 0x4a0d2d12, 0x0a001714, 0x0b000304, // so.hu.un_540 sk.cs.yo_654 sr.mk.un_660 nl.es.un_320
+ 0x0600270c, 0x0f1001a4, 0x3b002914, 0x31002907, // gd.de.un_530 en.lt.lv_433 sl.so.un_660 sl.az.un_420
+ 0x00006b1c, 0x530f28ac, 0x10001121, 0x1f001902, // ceb.un.un_800 sw.lv.ht_632 ro.be.un_860 gl.cy.un_220
+ 0x2900200d, 0x0e1301a4, 0x17002505, 0x643501a0, // sq.sl.un_540 en.et.is_433 eu.sr.un_330 en.zu.lg_322
+ // [1520]
+ 0x29003204, 0x07170a0e, 0x285231af, 0x2a000304, // bs.sl.un_320 mk.sr.bg_555 az.ha.sw_655 nl.mt.un_320
+ 0x131c0955, 0x0d092d07, 0x20003508, 0x120f11a7, // hi.mr.bh_442 sk.pl.cs_432 zu.sq.un_430 ro.lv.hu_532
+ 0x6b002705, 0x03010aa0, 0x20001213, 0x21003b13, // gd.ceb.un_330 pt.en.nl_322 hu.sq.un_650 so.jw.un_650
+ 0x1e000e04, 0x530406a0, 0x2a001819, 0x031b31a0, // is.ms.un_320 de.fi.ht_322 ga.mt.un_750 az.tr.nl_322
+ // [1530]
+ 0x090d1c0c, 0x28100aa4, 0x6b0e25a4, 0x645503ec, // mr.ne.hi_543 pt.lt.sw_433 eu.is.ceb_433 nl.rw.lg_644
+ 0x3f6435ee, 0x190b0a55, 0x25001a02, 0x07080404, // zu.lg.af_422 pt.es.gl_442 tl.eu.un_220 ru.uk.bg_332
+ 0x0a000411, 0x6b191fa7, 0x4a6855ec, 0x10070aec, // ru.mk.un_630 cy.gl.ceb_532 rw.ig.yo_644 mk.bg.be_644
+ 0x230b0a0d, 0x17000908, 0x12000918, 0x0900030d, // pt.es.ca_554 pl.sr.un_430 pl.hu.un_740 nl.pl.un_540
+ // [1540]
+ 0x10000f04, 0x35031f0c, 0x0900290d, 0x29003504, // lv.lt.un_320 cy.nl.zu_543 sl.pl.un_540 zu.sl.un_320
+ 0x12192da0, 0x1a6b640c, 0x0b190a04, 0x04003107, // sk.gl.hu_322 lg.ceb.tl_543 pt.gl.es_332 az.fi.un_420
+ 0x0f003505, 0x10352ba7, 0x6800551b, 0x1f271812, // zu.lv.un_330 vi.zu.lt_532 rw.ig.un_770 ga.gd.cy_654
+ 0x271b0fee, 0x13523ba9, 0x11003b04, 0x0e002104, // lv.tr.gd_422 so.ha.et_544 so.ro.un_320 jw.is.un_320
+ // [1550]
+ 0x200c3107, 0x282a01a4, 0x0f1801a4, 0x1b202aa0, // az.sv.sq_432 en.mt.sw_433 en.ga.lv_433 mt.sq.tr_322
+ 0x10530c04, 0x18000c04, 0x03000702, 0x1a6b130e, // sv.ht.lt_332 sv.ga.un_320 it.nl.un_220 et.ceb.tl_555
+ 0x0c0806af, 0x2d0d1702, 0x1e1c52a0, 0x11001721, // de.no.sv_655 sr.cs.sk_222 ha.id.ms_322 sr.ro.un_860
+ 0x080206ee, 0x0e1f27ad, 0x16003104, 0x12002a0c, // de.da.no_422 gd.cy.is_643 az.hr.un_320 mt.hu.un_530
+ // [1560]
+ 0x23000a13, 0x16180704, 0x21000e07, 0x0200200c, // pt.ca.un_650 it.ga.hr_332 is.jw.un_420 sq.da.un_530
+ 0x322052a0, 0x230511a0, 0x3f000f19, 0x190b230c, // ha.sq.bs_322 ro.fr.ca_322 lv.af.un_750 ca.es.gl_543
+ 0x190a2302, 0x19131b04, 0x32173107, 0x282a18a4, // ca.pt.gl_222 tr.et.gl_332 az.sr.bs_432 ga.mt.sw_433
+ 0x07000505, 0x12000309, 0x3f2003ee, 0x321816ee, // fr.it.un_330 nl.hu.un_440 nl.sq.af_422 hr.ga.bs_422
+ // [1570]
+ 0x0e000305, 0x100523ee, 0x0a351b07, 0x20000b09, // nl.is.un_330 ca.fr.lt_422 tr.zu.pt_432 es.sq.un_440
+ 0x081004a9, 0x182701ad, 0x2d0d07a0, 0x2718110e, // ru.be.uk_544 en.gd.ga_643 it.cs.sk_322 ro.ga.gd_555
+ 0x27000120, 0x182725a4, 0x29083f04, 0x0f0925ee, // en.gd.un_850 eu.gd.ga_433 af.no.sl_332 eu.pl.lv_422
+ 0x1a2528a0, 0x2d000e12, 0x16094a02, 0x08554aa0, // sw.eu.tl_322 is.sk.un_640 yo.pl.hr_222 yo.rw.no_322
+ // [1580]
+ 0x230b07ee, 0x1c0921ad, 0x0e1b31a4, 0x086e1a07, // it.es.ca_422 jw.pl.id_643 az.tr.is_433 tl.hmn.no_432
+ 0x0e033f0e, 0x111310a0, 0x09002512, 0x6b6401ec, // af.nl.is_555 lt.et.ro_322 eu.pl.un_640 en.lg.ceb_644
+ 0x0a101104, 0x01005302, 0x522d0daf, 0x1e250d14, // ro.lt.pt_332 ht.en.un_220 cs.sk.ha_655 cs.eu.ms_666
+ 0x1e001a07, 0x2d0d25a0, 0x18270a07, 0x2a0c64a0, // tl.ms.un_420 eu.cs.sk_322 pt.gd.ga_432 lg.sv.mt_322
+ // [1590]
+ 0x2d001b13, 0x3f001314, 0x1a003107, 0x10000804, // tr.sk.un_650 et.af.un_660 az.tl.un_420 no.lt.un_320
+ 0x0f001b07, 0x042a0ca0, 0x19550a04, 0x1f041012, // tr.lv.un_420 sv.mt.fi_322 pt.rw.gl_332 lt.fi.cy_654
+ 0x3b002a04, 0x07012807, 0x0c000704, 0x31230555, // mt.so.un_320 sw.en.it_432 it.sv.un_320 fr.ca.az_442
+ 0x101729ec, 0x0c000213, 0x31102805, 0x03001b08, // sl.sr.lt_644 da.sv.un_650 sw.lt.az_333 tr.nl.un_430
+ // [15a0]
+ 0x322916ee, 0x282d0dac, 0x0a642807, 0x556b6405, // hr.sl.bs_422 cs.sk.sw_632 sw.lg.pt_432 lg.ceb.rw_333
+ 0x080c27a0, 0x080210a0, 0x64005202, 0x0b0a6402, // gd.sv.no_322 lt.da.no_322 ha.lg.un_220 lg.pt.es_222
+ 0x17001e02, 0x032d3ba0, 0x32000d05, 0x21006b11, // ms.sr.un_220 so.sk.nl_322 cs.bs.un_330 ceb.jw.un_630
+ 0x051901a4, 0x25006413, 0x31000513, 0x06121007, // en.gl.fr_433 lg.eu.un_650 fr.az.un_650 lt.hu.de_432
+ // [15b0]
+ 0x061204ad, 0x680e0604, 0x17001e08, 0x0d002908, // fi.hu.de_643 de.is.ig_332 ms.sr.un_430 sl.cs.un_430
+ 0x293b1304, 0x00000201, 0x1f27010c, 0x0c686b07, // et.so.sl_332 da.un.un_200 en.gd.cy_543 ceb.ig.sv_432
+ 0x2b4a1e07, 0x0c001b12, 0x05102504, 0x0d002918, // ms.yo.vi_432 tr.sv.un_640 eu.lt.fr_332 sl.cs.un_740
+ 0x25002814, 0x09005304, 0x17000814, 0x0a00250d, // sw.eu.un_660 ht.pl.un_320 uk.sr.un_660 eu.pt.un_540
+ // [15c0]
+ 0x20000c12, 0x09003504, 0x21276807, 0x313b1ba4, // sv.sq.un_640 zu.pl.un_320 ig.gd.jw_432 tr.so.az_433
+ 0x08060204, 0x21001f07, 0x0e1f53a0, 0x3b001313, // da.de.no_332 cy.jw.un_420 ht.cy.is_322 et.so.un_650
+ 0x213b55a7, 0x35000307, 0x4a006407, 0x08001707, // rw.so.jw_532 nl.zu.un_420 lg.yo.un_420 sr.uk.un_420
+ 0x080203a0, 0x3b0907a0, 0x1700350b, 0x23002012, // nl.da.no_322 it.pl.so_322 zu.sr.un_520 sq.ca.un_640
+ // [15d0]
+ 0x2d040da0, 0x020520ad, 0x13080407, 0x1f1b25ee, // cs.fi.sk_322 sq.fr.da_643 fi.no.et_432 eu.tr.cy_422
+ 0x20000119, 0x0c080604, 0x162d35a4, 0x29001c08, // en.sq.un_750 de.no.sv_332 zu.sk.hr_433 id.sl.un_430
+ 0x020c1805, 0x17163210, 0x556b1c0b, 0x2d0d27a0, // ga.sv.da_333 bs.hr.sr_642 id.ceb.rw_542 gd.cs.sk_322
+ 0x0f060107, 0x0e12180b, 0x02005502, 0x3b1029ee, // en.de.lv_432 ga.hu.is_542 rw.da.un_220 sl.lt.so_422
+ // [15e0]
+ 0x29133b12, 0x1e0c1b04, 0x17115504, 0x1a006408, // so.et.sl_654 tr.sv.ms_332 rw.ro.sr_332 lg.tl.un_430
+ 0x190a08af, 0x100a0713, 0x0c5208af, 0x530852a0, // no.pt.gl_655 bg.mk.be_665 no.ha.sv_655 ha.no.ht_322
+ 0x100a170c, 0x03061bee, 0x08170a14, 0x0411250c, // sr.mk.be_543 tr.de.nl_422 mk.sr.uk_666 eu.ro.fi_543
+ 0x066e0fee, 0x16001104, 0x6e006405, 0x172d0d09, // lv.hmn.de_422 ro.hr.un_320 lg.hmn.un_330 cs.sk.sr_444
+ // [15f0]
+ 0x11100a0c, 0x16321707, 0x52532112, 0x050701a4, // mk.be.ro_543 sr.bs.hr_432 jw.ht.ha_654 en.it.fr_433
+ 0x06002a12, 0x0e101208, 0x3f002305, 0x23005304, // mt.de.un_640 hu.lt.is_443 ca.af.un_330 ht.ca.un_320
+ 0x31201b05, 0x170708af, 0x0c001318, 0x06002702, // tr.sq.az_333 uk.bg.sr_655 et.sv.un_740 gd.de.un_220
+ 0x100304a4, 0x6b001c07, 0x0527040c, 0x202a3160, // fi.nl.lt_433 id.ceb.un_420 fi.gd.fr_543 az.mt.sq_664
+ // [1600]
+ 0x040e1005, 0x1a3f01a7, 0x53000808, 0x35006b12, // lt.is.fi_333 en.af.tl_532 no.ht.un_430 ceb.zu.un_640
+ 0x0e002102, 0x3f0329a0, 0x0d103f07, 0x01002304, // jw.is.un_220 sl.nl.af_322 af.lt.cs_432 ca.en.un_320
+ 0x2a001a02, 0x0f106ea7, 0x3b00311a, 0x0b55530c, // tl.mt.un_220 hmn.lt.lv_532 az.so.un_760 ht.rw.es_543
+ 0x07000913, 0x190b12ec, 0x19122d55, 0x0a0804ad, // pl.it.un_650 hu.es.gl_644 sk.hu.gl_442 ru.uk.mk_643
+ // [1610]
+ 0x32002107, 0x2d001802, 0x0c2a20a4, 0x1100101b, // jw.bs.un_420 ga.sk.un_220 sq.mt.sv_433 be.ro.un_770
+ 0x07284a04, 0x23001902, 0x052a1307, 0x11006407, // yo.sw.it_332 gl.ca.un_220 et.mt.fr_432 lg.ro.un_420
+ 0x0f131004, 0x03023f02, 0x1a6b6408, 0x0c2a0408, // lt.et.lv_332 af.da.nl_222 lg.ceb.tl_443 fi.mt.sv_443
+ 0x1e1b5302, 0x25040c0b, 0x2d004a09, 0x0f1310a4, // ht.tr.ms_222 sv.fi.eu_542 yo.sk.un_440 lt.et.lv_433
+ // [1620]
+ 0x10080ead, 0x0b680611, 0x0e100f0d, 0x231f0560, // is.no.lt_643 de.ig.es_653 lv.lt.is_554 fr.cy.ca_664
+ 0x041110af, 0x111807a0, 0x0d00050c, 0x1b001707, // be.ro.ru_655 it.ga.ro_322 fr.cs.un_530 sr.tr.un_420
+ 0x190113a0, 0x0a000512, 0x292d0d60, 0x1e1c2014, // et.en.gl_322 fr.pt.un_640 cs.sk.sl_664 sq.id.ms_666
+ 0x551f1012, 0x08020eee, 0x52000507, 0x29002d0d, // lt.cy.rw_654 is.da.no_422 fr.ha.un_420 sk.sl.un_540
+ // [1630]
+ 0x0d202da0, 0x00003106, 0x3f211007, 0x315564ee, // sk.sq.cs_322 az.un.un_400 lt.jw.af_432 lg.rw.az_422
+ 0x0c0213ec, 0x64000c07, 0x25005207, 0x3b0e04ad, // et.da.sv_644 sv.lg.un_420 ha.eu.un_420 fi.is.so_643
+ 0x3f530405, 0x180306ec, 0x21053ba0, 0x0c0802ee, // fi.ht.af_333 de.nl.ga_644 so.fr.jw_322 da.no.sv_422
+ 0x190a0b0e, 0x6e000313, 0x170407a4, 0x356b1aec, // es.pt.gl_555 nl.hmn.un_650 bg.ru.sr_433 tl.ceb.zu_644
+ // [1640]
+ 0x13003504, 0x6e00011a, 0x52004a04, 0x3100530d, // zu.et.un_320 en.hmn.un_760 yo.ha.un_320 ht.az.un_540
+ 0x1731100e, 0x1b643512, 0x2d0d23ee, 0x07001f13, // lt.az.sr_555 zu.lg.tr_654 ca.cs.sk_422 cy.it.un_650
+ 0x55002519, 0x0a005304, 0x09000f0e, 0x04080a0d, // eu.rw.un_750 ht.pt.un_320 lv.pl.un_550 mk.uk.ru_554
+ 0x2d0d19ee, 0x4a5552ad, 0x040c1307, 0x28001e02, // gl.cs.sk_422 ha.rw.yo_643 et.sv.fi_432 ms.sw.un_220
+ // [1650]
+ 0x64002104, 0x2a006813, 0x2800110d, 0x4a1f53ee, // jw.lg.un_320 ig.mt.un_650 ro.sw.un_540 ht.cy.yo_422
+ 0x09070da0, 0x6b001a07, 0x3f031a0c, 0x0d002912, // cs.it.pl_322 tl.ceb.un_420 tl.nl.af_543 sl.cs.un_640
+ 0x3f031fac, 0x0c0f0804, 0x03002512, 0x352368ee, // cy.nl.af_632 no.lv.sv_332 eu.nl.un_640 ig.ca.zu_422
+ 0x066b0c08, 0x0f0e23a4, 0x0e001c04, 0x17003208, // sv.ceb.de_443 ca.is.lv_433 id.is.un_320 bs.sr.un_430
+ // [1660]
+ 0x6804130d, 0x08230ca0, 0x3b000412, 0x55001a02, // et.fi.ig_554 sv.ca.no_322 fi.so.un_640 tl.rw.un_220
+ 0x09280107, 0x64002814, 0x523b28a0, 0x0e4a52ac, // en.sw.pl_432 sw.lg.un_660 sw.so.ha_322 ha.yo.is_632
+ 0x070d1ba0, 0x354a6413, 0x351a1309, 0x0a002309, // tr.cs.it_322 lg.yo.zu_665 et.tl.zu_444 ca.pt.un_440
+ 0x31006e07, 0x132006ad, 0x02005309, 0x35001e02, // hmn.az.un_420 de.sq.et_643 ht.da.un_440 ms.zu.un_220
+ // [1670]
+ 0x311206ee, 0x13045207, 0x052755a0, 0x09005207, // de.hu.az_422 ha.fi.et_432 rw.gd.fr_322 ha.pl.un_420
+ 0x13002702, 0x10002d0c, 0x281f5507, 0x133b04ec, // gd.et.un_220 sk.lt.un_530 rw.cy.sw_432 fi.so.et_644
+ 0x686b3ba4, 0x3f031f0e, 0x1f1806a0, 0x642555af, // so.ceb.ig_433 cy.nl.af_555 de.ga.cy_322 rw.eu.lg_655
+ 0x0a001112, 0x23131b13, 0x3f072aee, 0x060e1307, // ro.mk.un_640 tr.et.ca_665 mt.it.af_422 et.is.de_432
+ // [1680]
+ 0x6b6835a0, 0x18000b08, 0x6400280e, 0x06000704, // zu.ig.ceb_322 es.ga.un_430 sw.lg.un_550 it.de.un_320
+ 0x100a21a0, 0x0d2a21a0, 0x082519ee, 0x0a5311af, // jw.pt.lt_322 jw.mt.cs_322 gl.eu.no_422 ro.ht.pt_655
+ 0x551b3bee, 0x060c040c, 0x10000a05, 0x0e200dad, // so.tr.rw_422 fi.sv.de_543 mk.be.un_330 cs.sq.is_643
+ 0x1f6828ad, 0x0600081a, 0x52003113, 0x06002a0c, // sw.ig.cy_643 no.de.un_760 az.ha.un_650 mt.de.un_530
+ // [1690]
+ 0x071710a0, 0x0a0e28a0, 0x08062aa0, 0x17005202, // be.sr.bg_322 sw.is.pt_322 mt.de.no_322 ha.sr.un_220
+ 0x08191f0c, 0x1a006e07, 0x18001904, 0x0c002104, // cy.gl.no_543 hmn.tl.un_420 gl.ga.un_320 jw.sv.un_320
+ 0x08020ca6, 0x3b520f12, 0x52551307, 0x190b0a5a, // sv.da.no_521 lv.ha.so_654 et.rw.ha_432 pt.es.gl_553
+ 0x2a00041b, 0x106b030b, 0x100501a4, 0x071827ac, // fi.mt.un_770 nl.ceb.lt_542 en.fr.lt_433 gd.ga.it_632
+ // [16a0]
+ 0x1e1c3b05, 0x190b21a9, 0x53002804, 0x13000d02, // so.id.ms_333 jw.es.gl_544 sw.ht.un_320 ne.bh.un_220
+ 0x11005505, 0x04000b05, 0x041108a4, 0x1f682a04, // rw.ro.un_330 es.fi.un_330 uk.ro.ru_433 mt.ig.cy_332
+ 0x133f03a9, 0x101b0907, 0x13255504, 0x10170fa4, // nl.af.et_544 pl.tr.lt_432 rw.eu.et_332 lv.sr.lt_433
+ 0x0f2155a7, 0x3217160c, 0x1c1b1ea7, 0x292d09a4, // rw.jw.lv_532 hr.sr.bs_543 ms.tr.id_532 pl.sk.sl_433
+ // [16b0]
+ 0x27251311, 0x21280fa7, 0x6b000413, 0x09001107, // et.eu.gd_653 lv.sw.jw_532 fi.ceb.un_650 ro.pl.un_420
+ 0x32003b07, 0x1c1309ad, 0x17520f04, 0x1a046b0c, // so.bs.un_420 hi.bh.mr_643 lv.ha.sr_332 ceb.fi.tl_543
+ 0x106b1a09, 0x012b35a7, 0x3f030202, 0x0d292d55, // tl.ceb.lt_444 zu.vi.en_532 da.nl.af_222 sk.sl.cs_442
+ 0x553f0504, 0x35000c04, 0x01006404, 0x06080c09, // fr.af.rw_332 sv.zu.un_320 lg.en.un_320 sv.no.de_444
+ // [16c0]
+ 0x533f1aee, 0x100f32a0, 0x3132110c, 0x1f001214, // tl.af.ht_422 bs.lv.lt_322 ro.bs.az_543 hu.cy.un_660
+ 0x132a23a4, 0x6b002112, 0x35002012, 0x12002908, // ca.mt.et_433 jw.ceb.un_640 sq.zu.un_640 sl.hu.un_430
+ 0x3b521f07, 0x04354a0b, 0x3b000420, 0x11250512, // cy.ha.so_432 yo.zu.fi_542 fi.so.un_850 fr.eu.ro_654
+ 0x08003f07, 0x64002112, 0x180827ad, 0x283f1aa4, // af.no.un_420 jw.lg.un_640 gd.no.ga_643 tl.af.sw_433
+ // [16d0]
+ 0x4a5564ad, 0x1c0913ac, 0x08020aa0, 0x090a2daf, // lg.rw.yo_643 bh.hi.mr_632 pt.da.no_322 sk.pt.pl_655
+ 0x32290da6, 0x0c550704, 0x0b000104, 0x1c002908, // cs.sl.bs_521 it.rw.sv_332 en.es.un_320 sl.id.un_430
+ 0x4a00520c, 0x1b002812, 0x4a3b1a0c, 0x07230105, // ha.yo.un_530 sw.tr.un_640 tl.so.yo_543 en.ca.it_333
+ 0x190b01a0, 0x32172909, 0x64063fa6, 0x0e2155a4, // en.es.gl_322 sl.sr.bs_444 af.de.lg_521 rw.jw.is_433
+ // [16e0]
+ 0x27004a1a, 0x535231a7, 0x0a002a07, 0x175311ee, // yo.gd.un_760 az.ha.ht_532 mt.pt.un_420 ro.ht.sr_422
+ 0x2900091a, 0x3b252702, 0x182728a6, 0x0c080607, // pl.sl.un_760 gd.eu.so_222 sw.gd.ga_521 de.no.sv_432
+ 0x28356402, 0x1900230c, 0x0f1e2707, 0x281335ee, // lg.zu.sw_222 ca.gl.un_530 gd.ms.lv_432 zu.et.sw_422
+ 0x10070a0e, 0x1c216404, 0x1e1c6bee, 0x0d00210d, // mk.bg.be_555 lg.jw.id_332 ceb.id.ms_422 jw.cs.un_540
+ // [16f0]
+ 0x00000901, 0x06132aa6, 0x131223a7, 0x0d2d0c04, // hi.un.un_200 mt.et.de_521 ca.hu.et_532 sv.sk.cs_332
+ 0x10001104, 0x172104ee, 0x321709ec, 0x163211a9, // ro.lt.un_320 fi.jw.sr_422 pl.sr.bs_644 ro.bs.hr_544
+ 0x13023f0c, 0x080e2aa6, 0x0a0e1255, 0x35002507, // af.da.et_543 mt.is.no_521 hu.is.pt_442 eu.zu.un_420
+ 0x040108ad, 0x1c0d09a7, 0x0c4a53a9, 0x12190512, // no.en.fi_643 hi.ne.mr_532 ht.yo.sv_544 fr.gl.hu_654
+ // [1700]
+ 0x170a0812, 0x12532504, 0x100407af, 0x1b005304, // uk.mk.sr_654 eu.ht.hu_332 bg.ru.be_655 ht.tr.un_320
+ 0x0c0413a9, 0x041327a0, 0x0d1c13a0, 0x32170fa0, // et.fi.sv_544 gd.et.fi_322 bh.mr.ne_322 lv.sr.bs_322
+ 0x1300090b, 0x20002a0c, 0x09130407, 0x210631a0, // hi.bh.un_520 mt.sq.un_530 fi.et.pl_432 az.de.jw_322
+ 0x13000c04, 0x18005212, 0x1b080305, 0x062704af, // sv.et.un_320 ha.ga.un_640 nl.no.tr_333 fi.gd.de_655
+ // [1710]
+ 0x13172902, 0x0b002805, 0x0b2d3107, 0x031f08a0, // sl.sr.et_222 sw.es.un_330 az.sk.es_432 no.cy.nl_322
+ 0x08102004, 0x31001a02, 0x6400682a, 0x0e1f1209, // sq.lt.no_332 tl.az.un_220 ig.lg.un_970 hu.cy.is_444
+ 0x1c6452a0, 0x1f001907, 0x1f0818ee, 0x1b005312, // ha.lg.id_322 gl.cy.un_420 ga.no.cy_422 ht.tr.un_640
+ 0x1f1012af, 0x020c08a4, 0x04201305, 0x3f3b1fa4, // hu.lt.cy_655 no.sv.da_433 et.sq.fi_333 cy.so.af_433
+ // [1720]
+ 0x2a041205, 0x0b1905ee, 0x3b1e21a0, 0x0f000b07, // hu.fi.mt_333 fr.gl.es_422 jw.ms.so_322 es.lv.un_420
+ 0x02001302, 0x4a3555ee, 0x2000280d, 0x080209ec, // et.da.un_220 rw.zu.yo_422 sw.sq.un_540 pl.da.no_644
+ 0x2700110c, 0x04080205, 0x35006402, 0x20103fec, // ro.gd.un_530 da.no.fi_333 lg.zu.un_220 af.lt.sq_644
+ 0x0d001907, 0x2d0d20a0, 0x170a0807, 0x01002807, // gl.cs.un_420 sq.cs.sk_322 uk.mk.sr_432 sw.en.un_420
+ // [1730]
+ 0x184a2b08, 0x171629a4, 0x28090455, 0x1c09130b, // vi.yo.ga_443 sl.hr.sr_433 fi.pl.sw_442 bh.hi.mr_542
+ 0x021b0ea4, 0x091710a0, 0x0e1801ee, 0x01001804, // is.tr.da_433 lt.sr.pl_322 en.ga.is_422 ga.en.un_320
+ 0x351b3b0c, 0x061e1bee, 0x201b10a0, 0x10001f0c, // so.tr.zu_543 tr.ms.de_422 lt.tr.sq_322 cy.lt.un_530
+ 0x071711ee, 0x07100404, 0x35025511, 0x06002305, // ro.sr.bg_422 fi.lt.it_332 rw.da.zu_653 ca.de.un_330
+ // [1740]
+ 0x6e6b6804, 0x0400070c, 0x1f00120b, 0x64525505, // ig.ceb.hmn_332 bg.ru.un_530 hu.cy.un_520 rw.ha.lg_333
+ 0x060e05ee, 0x2d2920ee, 0x101f25ad, 0x13001007, // fr.is.de_422 sq.sl.sk_422 eu.cy.lt_643 lt.et.un_420
+ 0x64001f1a, 0x55283b04, 0x0c252a04, 0x13642805, // cy.lg.un_760 so.sw.rw_332 mt.eu.sv_332 sw.lg.et_333
+ 0x1f0d31ee, 0x01002d04, 0x21000a07, 0x28001804, // az.cs.cy_422 sk.en.un_320 pt.jw.un_420 ga.sw.un_320
+ // [1750]
+ 0x3b270e02, 0x166b10ad, 0x080c130e, 0x31190a12, // is.gd.so_222 lt.ceb.hr_643 et.sv.no_555 pt.gl.az_654
+ 0x0c011bee, 0x13314a04, 0x08025302, 0x020908ee, // tr.en.sv_422 yo.az.et_332 ht.da.no_222 no.pl.da_422
+ 0x033f0709, 0x1e683504, 0x031f0dac, 0x352031a4, // it.af.nl_444 zu.ig.ms_332 cs.cy.nl_632 az.sq.zu_433
+ 0x076b0a04, 0x10003f04, 0x23001f04, 0x3f6b1a0d, // pt.ceb.it_332 af.lt.un_320 cy.ca.un_320 tl.ceb.af_554
+ // [1760]
+ 0x28216813, 0x19050108, 0x13001b19, 0x3553310c, // ig.jw.sw_665 en.fr.gl_443 tr.et.un_750 az.ht.zu_543
+ 0x2100030c, 0x122d18ee, 0x27000708, 0x53523507, // nl.jw.un_530 ga.sk.hu_422 it.gd.un_430 zu.ha.ht_432
+ 0x11310704, 0x05033f0d, 0x6b00550d, 0x1c211e11, // it.az.ro_332 af.nl.fr_554 rw.ceb.un_540 ms.jw.id_653
+ 0x06006b12, 0x09000308, 0x0f000308, 0x553564ad, // ceb.de.un_640 nl.pl.un_430 nl.lv.un_430 lg.zu.rw_643
+ // [1770]
+ 0x25000308, 0x52200a13, 0x16133fa0, 0x2a002012, // nl.eu.un_430 pt.sq.ha_665 af.et.hr_322 sq.mt.un_640
+ 0x081309af, 0x13091c12, 0x042835ee, 0x553508a4, // pl.et.no_655 mr.hi.bh_654 zu.sw.fi_422 no.zu.rw_433
+ 0x20000b04, 0x190910a0, 0x080f2d07, 0x13003f0d, // es.sq.un_320 lt.pl.gl_322 sk.lv.no_432 af.et.un_540
+ 0x0f001b12, 0x3b000907, 0x11000702, 0x1c090d05, // tr.lv.un_640 pl.so.un_420 it.ro.un_220 ne.hi.mr_333
+ // [1780]
+ 0x1c0d13ec, 0x2300120d, 0x16093502, 0x0c030804, // bh.ne.mr_644 hu.ca.un_540 zu.pl.hr_222 no.nl.sv_332
+ 0x03001f12, 0x1f0103a0, 0x061064a0, 0x18000b04, // cy.nl.un_640 nl.en.cy_322 lg.lt.de_322 es.ga.un_320
+ 0x53680dec, 0x2d001019, 0x05000412, 0x3f1a2807, // cs.ig.ht_644 lt.sk.un_750 fi.fr.un_640 sw.tl.af_432
+ 0x3f0c0fa0, 0x283b64ec, 0x3b003105, 0x17291007, // lv.sv.af_322 lg.so.sw_644 az.so.un_330 lt.sl.sr_432
+ // [1790]
+ 0x100904a6, 0x55002808, 0x3b212708, 0x2900550c, // fi.pl.lt_521 sw.rw.un_430 gd.jw.so_443 rw.sl.un_530
+ 0x01212d07, 0x35006b08, 0x64000705, 0x4a250408, // sk.jw.en_432 ceb.zu.un_430 it.lg.un_330 fi.eu.yo_443
+ 0x1a645502, 0x205528ee, 0x28003112, 0x0e002111, // rw.lg.tl_222 sw.rw.sq_422 az.sw.un_640 jw.is.un_630
+ 0x0a286855, 0x096864ee, 0x12291307, 0x1125010c, // ig.sw.pt_442 lg.ig.pl_422 et.sl.hu_432 en.eu.ro_543
+ // [17a0]
+ 0x20000e02, 0x05120da0, 0x04003f0e, 0x190b2505, // is.sq.un_220 cs.hu.fr_322 af.fi.un_550 eu.es.gl_333
+ 0x53055507, 0x21005202, 0x1e003b0e, 0x08020305, // rw.fr.ht_432 ha.jw.un_220 so.ms.un_550 nl.da.no_333
+ 0x4a3f1b04, 0x3100030d, 0x35003b19, 0x18004a12, // tr.af.yo_332 nl.az.un_540 so.zu.un_750 yo.ga.un_640
+ 0x6e000613, 0x04001702, 0x1b3111af, 0x1b1a530c, // de.hmn.un_650 sr.ru.un_220 ro.az.tr_655 ht.tl.tr_543
+ // [17b0]
+ 0x1b0405a7, 0x25002705, 0x03002019, 0x53001012, // fr.fi.tr_532 gd.eu.un_330 sq.nl.un_750 lt.ht.un_640
+ 0x53101bad, 0x00002b06, 0x0a1108a4, 0x160c13ad, // tr.lt.ht_643 vi.un.un_400 uk.ro.mk_433 et.sv.hr_643
+ 0x1f533fa0, 0x176813ee, 0x2d0d0e05, 0x1b000104, // af.ht.cy_322 et.ig.sr_422 is.cs.sk_333 en.tr.un_320
+ 0x1b0131ee, 0x2b001f07, 0x0c0e0813, 0x3b00280c, // az.en.tr_422 cy.vi.un_420 no.is.sv_665 sw.so.un_530
+ // [17c0]
+ 0x0e6452a0, 0x3b1f53a0, 0x35005308, 0x1635030c, // ha.lg.is_322 ht.cy.so_322 ht.zu.un_430 nl.zu.hr_543
+ 0x2a001e02, 0x1e005504, 0x3f0625a0, 0x17160aa0, // ms.mt.un_220 rw.ms.un_320 eu.de.af_322 pt.hr.sr_322
+ 0x19000108, 0x53000309, 0x172d29a6, 0x03310fa0, // en.gl.un_430 nl.ht.un_440 sl.sk.sr_521 lv.az.nl_322
+ 0x1e121b0c, 0x290d53a0, 0x051106a9, 0x05131b04, // tr.hu.ms_543 ht.cs.sl_322 de.ro.fr_544 tr.et.fr_332
+ // [17d0]
+ 0x0a530107, 0x18002512, 0x03271805, 0x080204ee, // en.ht.pt_432 eu.ga.un_640 ga.gd.nl_333 fi.da.no_422
+ 0x080203ee, 0x12000412, 0x28041aa0, 0x2a001702, // nl.da.no_422 fi.hu.un_640 tl.fi.sw_322 sr.mt.un_220
+ 0x19070a07, 0x1c2a21a0, 0x314a1b05, 0x071908a0, // pt.it.gl_432 jw.mt.id_322 tr.yo.az_333 no.gl.it_322
+ 0x03003b0c, 0x070811ee, 0x2d2b0907, 0x28001c02, // so.nl.un_530 ro.uk.bg_422 pl.vi.sk_432 id.sw.un_220
+ // [17e0]
+ 0x031304a0, 0x0900120e, 0x0a0804a0, 0x0700040e, // fi.et.nl_322 hu.pl.un_550 ru.uk.mk_322 ru.bg.un_550
+ 0x324a09a6, 0x0a1304a4, 0x09006e07, 0x64002a07, // pl.yo.bs_521 fi.et.pt_433 hmn.pl.un_420 mt.lg.un_420
+ 0x0c001212, 0x20002904, 0x0e130cee, 0x1f003b08, // hu.sv.un_640 sl.sq.un_320 sv.et.is_422 so.cy.un_430
+ 0x0c000a08, 0x1e005208, 0x6e002013, 0x0700170e, // pt.sv.un_430 ha.ms.un_430 sq.hmn.un_650 sr.bg.un_550
+ // [17f0]
+ 0x1c1321a0, 0x27162a0c, 0x0d1c095a, 0x0b0c13af, // jw.et.id_322 mt.hr.gd_543 hi.mr.ne_553 et.sv.es_655
+ 0x06080302, 0x083f02a0, 0x016b2dad, 0x2a5303a7, // nl.no.de_222 da.af.no_322 sk.ceb.en_643 nl.ht.mt_532
+ 0x55213512, 0x5321290c, 0x1c0d09ad, 0x190b0aec, // zu.jw.rw_654 sl.jw.ht_543 hi.ne.mr_643 pt.es.gl_644
+ 0x64090eee, 0x02123fee, 0x0a000d13, 0x1e1c1a0c, // is.pl.lg_422 af.hu.da_422 cs.pt.un_650 tl.id.ms_543
+
+ // [1800]
+ 0x03060aa0, 0x1c002b05, 0x0e030807, 0x122d1304, // pt.de.nl_322 vi.id.un_330 no.nl.is_432 et.sk.hu_332
+ 0x080235a4, 0x5500350c, 0x25000b02, 0x0403270b, // zu.da.no_433 zu.rw.un_530 es.eu.un_220 gd.nl.fi_542
+ 0x5300081a, 0x64001a09, 0x0b01110c, 0x4a21100c, // no.ht.un_760 tl.lg.un_440 ro.en.es_543 lt.jw.yo_543
+ 0x0c1925a7, 0x270b3bee, 0x10003202, 0x20002105, // eu.gl.sv_532 so.es.gd_422 bs.lt.un_220 jw.sq.un_330
+ // [1810]
+ 0x1200110e, 0x0b0a23ee, 0x02001704, 0x033f3b0c, // ro.hu.un_550 ca.pt.es_422 sr.da.un_320 so.af.nl_543
+ 0x1f28520c, 0x3f001c02, 0x3f006b07, 0x2b002004, // ha.sw.cy_543 id.af.un_220 ceb.af.un_420 sq.vi.un_320
+ 0x5200211a, 0x2a005202, 0x18001202, 0x201209ec, // jw.ha.un_760 ha.mt.un_220 ur.ar.un_220 pl.hu.sq_644
+ 0x18000912, 0x531b55ee, 0x641b3102, 0x1e0a05a9, // pl.ga.un_640 rw.tr.ht_422 az.tr.lg_222 fr.pt.ms_544
+ // [1820]
+ 0x09083bac, 0x09002702, 0x033f0ea0, 0x093b2d0c, // so.no.pl_632 gd.pl.un_220 is.af.nl_322 sk.so.pl_543
+ 0x0e080260, 0x2d0d180e, 0x04000702, 0x3f00110d, // da.no.is_664 ga.cs.sk_555 it.fi.un_220 ro.af.un_540
+ 0x29131fa0, 0x0c1f1055, 0x1000230d, 0x04001b13, // cy.et.sl_322 lt.cy.sv_442 ca.lt.un_540 tr.fi.un_650
+ 0x0a005202, 0x524a3ba0, 0x270f5507, 0x251c53ec, // ha.pt.un_220 so.yo.ha_322 rw.lv.gd_432 ht.id.eu_644
+ // [1830]
+ 0x4a005302, 0x0400020c, 0x130f2d07, 0x0a001e02, // ht.yo.un_220 da.fi.un_530 sk.lv.et_432 ms.pt.un_220
+ 0x27002b0e, 0x0a001002, 0x296435ee, 0x06001a0b, // vi.gd.un_550 be.mk.un_220 zu.lg.sl_422 tl.de.un_520
+ 0x12002711, 0x03353f05, 0x122d2007, 0x1b001602, // gd.hu.un_630 af.zu.nl_333 sq.sk.hu_432 hr.tr.un_220
+ 0x04000911, 0x35135504, 0x35005304, 0x53003118, // pl.fi.un_630 rw.et.zu_332 ht.zu.un_320 az.ht.un_740
+ // [1840]
+ 0x2b001604, 0x25005512, 0x35252807, 0x35000108, // hr.vi.un_320 rw.eu.un_640 sw.eu.zu_432 en.zu.un_430
+ 0x53001602, 0x3f000e13, 0x0b6452a0, 0x6b001e0d, // hr.ht.un_220 is.af.un_650 ha.lg.es_322 ms.ceb.un_540
+ 0x06130c02, 0x123f1aa0, 0x5568250c, 0x2a1206ee, // sv.et.de_222 tl.af.hu_322 eu.ig.rw_543 de.hu.mt_422
+ 0x06000818, 0x0c001a0c, 0x20000804, 0x6b1a28a0, // no.de.un_740 tl.sv.un_530 no.sq.un_320 sw.tl.ceb_322
+ // [1850]
+ 0x29000d04, 0x08001807, 0x05001907, 0x2000081a, // cs.sl.un_320 ga.no.un_420 gl.fr.un_420 no.sq.un_760
+ 0x02090807, 0x192b1b07, 0x6800280e, 0x0f000b04, // no.pl.da_432 tr.vi.gl_432 sw.ig.un_550 es.lv.un_320
+ 0x0d102d07, 0x23200aaf, 0x2a00680d, 0x0c0306a0, // sk.lt.cs_432 pt.sq.ca_655 ig.mt.un_540 de.nl.sv_322
+ 0x0407100c, 0x06040307, 0x02042704, 0x00003524, // be.bg.ru_543 nl.fi.de_432 gd.fi.da_332 zu.un.un_900
+ // [1860]
+ 0x13640405, 0x20355205, 0x28355507, 0x310f55af, // fi.lg.et_333 ha.zu.sq_333 rw.zu.sw_432 rw.lv.az_655
+ 0x28006807, 0x04001812, 0x1f000614, 0x35273b02, // ig.sw.un_420 ga.fi.un_640 de.cy.un_660 so.gd.zu_222
+ 0x1208020d, 0x64352aaf, 0x2d001602, 0x016b05a9, // da.no.hu_554 mt.zu.lg_655 hr.sk.un_220 fr.ceb.en_544
+ 0x12001007, 0x68003108, 0x10001605, 0x1a6b53af, // lt.hu.un_420 az.ig.un_430 hr.lt.un_330 ht.ceb.tl_655
+ // [1870]
+ 0x530f1005, 0x25001304, 0x3100200d, 0x033555ac, // lt.lv.ht_333 et.eu.un_320 sq.az.un_540 rw.zu.nl_632
+ 0x2a350655, 0x090306a7, 0x08170414, 0x0f1113ad, // de.zu.mt_442 de.nl.pl_532 ru.sr.uk_666 et.ro.lv_643
+ 0x112b0704, 0x050a1104, 0x160513a0, 0x0d110505, // it.vi.ro_332 ro.pt.fr_332 et.fr.hr_322 fr.ro.cs_333
+ 0x11102307, 0x16002104, 0x311b35ee, 0x1b312a12, // ca.lt.ro_432 jw.hr.un_320 zu.tr.az_422 mt.az.tr_654
+ // [1880]
+ 0x25132107, 0x04000d12, 0x11000713, 0x64000919, // jw.et.eu_432 cs.fi.un_640 it.ro.un_650 pl.lg.un_750
+ 0x1f120302, 0x212513af, 0x13006414, 0x200a230c, // nl.hu.cy_222 et.eu.jw_655 lg.et.un_660 ca.pt.sq_543
+ 0x1b315502, 0x350413ee, 0x2d001907, 0x122305a0, // rw.az.tr_222 et.fi.zu_422 gl.sk.un_420 fr.ca.hu_322
+ 0x13000d14, 0x20002305, 0x3b001a0c, 0x3f033b0d, // ne.bh.un_660 ca.sq.un_330 tl.so.un_530 so.nl.af_554
+ // [1890]
+ 0x23000507, 0x25355512, 0x0a0411a0, 0x6b000b04, // fr.ca.un_420 rw.zu.eu_654 ro.ru.mk_322 es.ceb.un_320
+ 0x253528a7, 0x1c0d0908, 0x2b00050d, 0x211e3b04, // sw.zu.eu_532 hi.ne.mr_443 fr.vi.un_540 so.ms.jw_332
+ 0x071318a9, 0x111255a6, 0x11001f0e, 0x53641211, // ga.et.it_544 rw.hu.ro_521 cy.ro.un_550 hu.lg.ht_653
+ 0x231807a0, 0x4a006421, 0x4a5564a7, 0x32351b04, // it.ga.ca_322 lg.yo.un_860 lg.rw.yo_532 tr.zu.bs_332
+ // [18a0]
+ 0x321355a0, 0x55281a04, 0x18270660, 0x09000305, // rw.et.bs_322 tl.sw.rw_332 de.gd.ga_664 nl.pl.un_330
+ 0x1e351c07, 0x3f0c02ee, 0x070410af, 0x281b55ec, // id.zu.ms_432 da.sv.af_422 be.ru.bg_655 rw.tr.sw_644
+ 0x55000e07, 0x13020d0c, 0x10002712, 0x3f03010c, // is.rw.un_420 cs.da.et_543 gd.lt.un_640 en.nl.af_543
+ 0x020c0812, 0x190b25a9, 0x090d050d, 0x01000d04, // no.sv.da_654 eu.es.gl_544 fr.cs.pl_554 cs.en.un_320
+ // [18b0]
+ 0x190a0d05, 0x08101fad, 0x311b11ac, 0x01001a02, // cs.pt.gl_333 cy.lt.no_643 ro.tr.az_632 tl.en.un_220
+ 0x016b04ee, 0x352864ad, 0x131a3512, 0x4a1e0fa0, // fi.ceb.en_422 lg.sw.zu_643 zu.tl.et_654 lv.ms.yo_322
+ 0x170713a0, 0x232520a0, 0x0f000607, 0x0f000108, // et.it.sr_322 sq.eu.ca_322 de.lv.un_420 en.lv.un_430
+ 0x121b13a9, 0x080206a0, 0x3b350e07, 0x35002805, // et.tr.hu_544 de.da.no_322 is.zu.so_432 sw.zu.un_330
+ // [18c0]
+ 0x07000507, 0x06040e0c, 0x6b0401ee, 0x2d100d12, // fr.it.un_420 is.fi.de_543 en.fi.ceb_422 cs.lt.sk_654
+ 0x0c1b0407, 0x061f2304, 0x27004a05, 0x08005302, // fi.tr.sv_432 ca.cy.de_332 yo.gd.un_330 ht.no.un_220
+ 0x4a000504, 0x07002708, 0x353b55a9, 0x29321609, // fr.yo.un_320 gd.it.un_430 rw.so.zu_544 hr.bs.sl_444
+ 0x19003b13, 0x07002a13, 0x073b1f04, 0x0000041c, // so.gl.un_650 mt.it.un_650 cy.so.it_332 fi.un.un_800
+ // [18d0]
+ 0x16002a07, 0x16002807, 0x313b1208, 0x071305a4, // mt.hr.un_420 sw.hr.un_420 hu.so.az_443 fr.et.it_433
+ 0x35554aec, 0x0c001702, 0x53001a09, 0x21001108, // yo.rw.zu_644 sr.sv.un_220 tl.ht.un_440 ro.jw.un_430
+ 0x12005219, 0x0c0304ee, 0x06000e05, 0x080c02a9, // ha.hu.un_750 fi.nl.sv_422 is.de.un_330 da.sv.no_544
+ 0x0f002b0e, 0x0c311b12, 0x190b0aa4, 0x17001a04, // vi.lv.un_550 tr.az.sv_654 pt.es.gl_433 tl.sr.un_320
+ // [18e0]
+ 0x09000702, 0x29001a04, 0x641b5505, 0x4a1a6b08, // it.pl.un_220 tl.sl.un_320 rw.tr.lg_333 ceb.tl.yo_443
+ 0x313b21a4, 0x3f005513, 0x03010602, 0x093f0f08, // jw.so.az_433 rw.af.un_650 de.en.nl_222 lv.af.pl_443
+ 0x0c003f04, 0x08170712, 0x00002724, 0x6b000612, // af.sv.un_320 bg.sr.uk_654 gd.un.un_900 de.ceb.un_640
+ 0x6b041fa7, 0x556828ad, 0x1b001213, 0x18280a0c, // cy.fi.ceb_532 sw.ig.rw_643 hu.tr.un_650 pt.sw.ga_543
+ // [18f0]
+ 0x1e1f1207, 0x53001e02, 0x32163f07, 0x100c0814, // hu.cy.ms_432 ms.ht.un_220 af.hr.bs_432 no.sv.lt_666
+ 0x080c020c, 0x686b01a4, 0x0e001318, 0x17322055, // da.sv.no_543 en.ceb.ig_433 et.is.un_740 sq.bs.sr_442
+ 0x1a130f07, 0x0a64040c, 0x0b643ba0, 0x10550504, // lv.et.tl_432 fi.lg.pt_543 so.lg.es_322 fr.rw.lt_332
+ 0x6b1a5507, 0x051813a7, 0x04131012, 0x0e001214, // rw.tl.ceb_432 et.ga.fr_532 lt.et.fi_654 hu.is.un_660
+ // [1900]
+ 0x6e000413, 0x0e1001a4, 0x080e1002, 0x551c05ee, // fi.hmn.un_650 en.lt.is_433 lt.is.no_222 fr.id.rw_422
+ 0x2d0d290c, 0x2d000704, 0x53130407, 0x25002d08, // sl.cs.sk_543 it.sk.un_320 fi.et.ht_432 sk.eu.un_430
+ 0x06182d07, 0x21272807, 0x04000e0e, 0x322916a0, // sk.ga.de_432 sw.gd.jw_432 is.fi.un_550 hr.sl.bs_322
+ 0x02080c5a, 0x11522807, 0x2d0d0ea9, 0x0200270c, // sv.no.da_553 sw.ha.ro_432 is.cs.sk_544 gd.da.un_530
+ // [1910]
+ 0x19250ba9, 0x1c000412, 0x21064a04, 0x04182804, // es.eu.gl_544 fi.id.un_640 yo.de.jw_332 sw.ga.fi_332
+ 0x11081208, 0x2d090d07, 0x112055ee, 0x554a13ee, // hu.no.ro_443 cs.pl.sk_432 rw.sq.ro_422 et.yo.rw_422
+ 0x311b2012, 0x02001819, 0x020608a6, 0x013527a0, // sq.tr.az_654 ga.da.un_750 no.de.da_521 gd.zu.en_322
+ 0x13000d07, 0x1a003202, 0x1000251a, 0x0c050608, // ne.bh.un_420 bs.tl.un_220 eu.lt.un_760 de.fr.sv_443
+ // [1920]
+ 0x18272dad, 0x1f002d12, 0x0d001212, 0x351055ee, // sk.gd.ga_643 sk.cy.un_640 hu.cs.un_640 rw.lt.zu_422
+ 0x1a6b3b60, 0x311b640c, 0x321827a9, 0x0c001a02, // so.ceb.tl_664 lg.tr.az_543 gd.ga.bs_544 tl.sv.un_220
+ 0x181f2760, 0x1b003102, 0x130304a4, 0x08000319, // gd.cy.ga_664 az.tr.un_220 fi.nl.et_433 nl.no.un_750
+ 0x3f00050e, 0x3f5505a0, 0x0a001213, 0x3f005312, // fr.af.un_550 fr.rw.af_322 hu.pt.un_650 ht.af.un_640
+ // [1930]
+ 0x64354aa4, 0x0705550c, 0x0a001120, 0x07111f02, // yo.zu.lg_433 rw.fr.it_543 ro.mk.un_850 cy.ro.it_222
+ 0x4a1b2807, 0x0e001902, 0x6428520c, 0x23191fac, // sw.tr.yo_432 gl.is.un_220 ha.sw.lg_543 cy.gl.ca_632
+ 0x35121307, 0x192b07a0, 0x1a002d08, 0x5300230d, // et.hu.zu_432 it.vi.gl_322 sk.tl.un_430 ca.ht.un_540
+ 0x0c060412, 0x27001804, 0x18112707, 0x2d0d20a4, // fi.de.sv_654 ga.gd.un_320 gd.ro.ga_432 sq.cs.sk_433
+ // [1940]
+ 0x28292d07, 0x0d1f31a4, 0x53003513, 0x2b006809, // sk.sl.sw_432 az.cy.cs_433 zu.ht.un_650 ig.vi.un_440
+ 0x160f2904, 0x2d0d12ee, 0x01001302, 0x0e000a04, // sl.lv.hr_332 hu.cs.sk_422 et.en.un_220 pt.is.un_320
+ 0x04000e0c, 0x190a3ba0, 0x1a6e1f12, 0x20132aad, // is.fi.un_530 so.pt.gl_322 cy.hmn.tl_654 mt.et.sq_643
+ 0x0c082a04, 0x21643108, 0x1716320c, 0x285304a4, // mt.no.sv_332 az.lg.jw_443 bs.hr.sr_543 fi.ht.sw_433
+ // [1950]
+ 0x1c1e3b07, 0x31000807, 0x100804a4, 0x11005302, // so.ms.id_432 no.az.un_420 ru.uk.be_433 ht.ro.un_220
+ 0x05520aec, 0x2b000a07, 0x2a3b03a0, 0x211c0602, // pt.ha.fr_644 pt.vi.un_420 nl.so.mt_322 de.id.jw_222
+ 0x28182a05, 0x062b0aa0, 0x551f520b, 0x3b6b3508, // mt.ga.sw_333 pt.vi.de_322 ha.cy.rw_542 zu.ceb.so_443
+ 0x080c0207, 0x0e000207, 0x0500180c, 0x12000407, // da.sv.no_432 da.is.un_420 ga.fr.un_530 fi.hu.un_420
+ // [1960]
+ 0x270e1812, 0x131f6ba4, 0x0c003118, 0x21001a02, // ga.is.gd_654 ceb.cy.et_433 az.sv.un_740 tl.jw.un_220
+ 0x29000e1a, 0x25006b0d, 0x1a280107, 0x3b132aa0, // is.sl.un_760 ceb.eu.un_540 en.sw.tl_432 mt.et.so_322
+ 0x556b3b07, 0x1f00271a, 0x6428210c, 0x232507a7, // so.ceb.rw_432 gd.cy.un_760 jw.sw.lg_543 it.eu.ca_532
+ 0x070511a4, 0x1b003202, 0x1c090d0c, 0x100b0f55, // ro.fr.it_433 bs.tr.un_220 ne.hi.mr_543 lv.es.lt_442
+ // [1970]
+ 0x020a0807, 0x08020ca9, 0x00000603, 0x55004a0c, // no.pt.da_432 sv.da.no_544 de.un.un_300 yo.rw.un_530
+ 0x07003b05, 0x29125504, 0x320c13ee, 0x1c521eee, // so.it.un_330 rw.hu.sl_332 et.sv.bs_422 ms.ha.id_422
+ 0x210b0507, 0x3f080ca0, 0x0a0d180c, 0x04133b11, // fr.es.jw_432 sv.no.af_322 ga.cs.pt_543 so.et.fi_653
+ 0x0f000804, 0x3b00550d, 0x0700180d, 0x2155280d, // no.lv.un_320 rw.so.un_540 ga.it.un_540 sw.rw.jw_554
+ // [1980]
+ 0x4a0a050c, 0x18003f07, 0x07002108, 0x52355513, // fr.pt.yo_543 af.ga.un_420 jw.it.un_430 rw.zu.ha_665
+ 0x0f2935af, 0x11552507, 0x2a313ba4, 0x0c000802, // zu.sl.lv_655 eu.rw.ro_432 so.az.mt_433 no.sv.un_220
+ 0x53006b04, 0x1a006b02, 0x3b002707, 0x0c000a05, // ceb.ht.un_320 ceb.tl.un_220 gd.so.un_420 pt.sv.un_330
+ 0x0a0810af, 0x4a295207, 0x10251111, 0x4a253104, // be.uk.mk_655 ha.sl.yo_432 ro.eu.lt_653 az.eu.yo_332
+ // [1990]
+ 0x21110807, 0x10080a12, 0x050429a0, 0x3f0510ee, // no.ro.jw_432 mk.uk.be_654 sl.fi.fr_322 lt.fr.af_422
+ 0x3b6413a4, 0x043b1a08, 0x6b281e07, 0x55000704, // et.lg.so_433 tl.so.fi_443 ms.sw.ceb_432 it.rw.un_320
+ 0x321617af, 0x256455a4, 0x2100100b, 0x19005204, // sr.hr.bs_655 rw.lg.eu_433 lt.jw.un_520 ha.gl.un_320
+ 0x033f20ac, 0x1f000a0d, 0x121f050d, 0x1710110c, // sq.af.nl_632 pt.cy.un_540 fr.cy.hu_554 ro.be.sr_543
+ // [19a0]
+ 0x25283507, 0x25001907, 0x05002007, 0x290a0f04, // zu.sw.eu_432 gl.eu.un_420 sq.fr.un_420 lv.pt.sl_332
+ 0x120a05a0, 0x643555af, 0x0200230e, 0x13090dad, // fr.pt.hu_322 rw.zu.lg_655 ca.da.un_550 ne.hi.bh_643
+ 0x2100180e, 0x190c010b, 0x290916a4, 0x192025a0, // ar.fa.un_550 en.sv.gl_542 hr.pl.sl_433 eu.sq.gl_322
+ 0x552528a0, 0x10080404, 0x04001304, 0x112307ee, // sw.eu.rw_322 ru.uk.be_332 et.fi.un_320 it.ca.ro_422
+ // [19b0]
+ 0x091c1308, 0x0d0311a4, 0x2d005204, 0x21132aa4, // bh.mr.hi_443 ro.nl.cs_433 ha.sk.un_320 mt.et.jw_433
+ 0x1c121ea9, 0x0a001104, 0x0811100e, 0x1b00180e, // ms.hu.id_544 ro.mk.un_320 be.ro.uk_555 ga.tr.un_550
+ 0x271f6402, 0x09642507, 0x031b06a0, 0x0813040e, // lg.cy.gd_222 eu.lg.pl_432 de.tr.nl_322 fi.et.no_555
+ 0x21003f08, 0x010a1eee, 0x350c6ea7, 0x551225a9, // af.jw.un_430 ms.pt.en_422 hmn.sv.zu_532 eu.hu.rw_544
+ // [19c0]
+ 0x0c13230c, 0x08000407, 0x32162aa4, 0x171610a4, // ca.et.sv_543 ru.uk.un_420 mt.hr.bs_433 lt.hr.sr_433
+ 0x0d32160d, 0x00001b06, 0x535564a0, 0x552035ac, // hr.bs.cs_554 tr.un.un_400 lg.rw.ht_322 zu.sq.rw_632
+ 0x2d00250c, 0x0f103104, 0x310e3b55, 0x3f031307, // eu.sk.un_530 az.lt.lv_332 so.is.az_442 et.nl.af_432
+ 0x2d0d640c, 0x2500180d, 0x3f270407, 0x033f020d, // lg.cs.sk_543 ga.eu.un_540 fi.gd.af_432 da.af.nl_554
+ // [19d0]
+ 0x3b002705, 0x10002704, 0x18000702, 0x0e000612, // gd.so.un_330 gd.lt.un_320 it.ga.un_220 de.is.un_640
+ 0x25101fec, 0x280a070c, 0x2d041205, 0x6b006407, // cy.lt.eu_644 it.pt.sw_543 hu.fi.sk_333 lg.ceb.un_420
+ 0x53355555, 0x06002012, 0x230504ee, 0x12001c02, // rw.zu.ht_442 sq.de.un_640 fi.fr.ca_422 id.hu.un_220
+ 0x08002702, 0x1f001a02, 0x1c3f030c, 0x55352812, // gd.no.un_220 tl.cy.un_220 nl.af.id_543 sw.zu.rw_654
+ // [19e0]
+ 0x1218270c, 0x19002508, 0x03080604, 0x0a2b0ca0, // gd.ga.hu_543 eu.gl.un_430 de.no.nl_332 sv.vi.pt_322
+ 0x0d005308, 0x27000812, 0x6b1a1c07, 0x1e1f1012, // ht.cs.un_430 no.gd.un_640 id.tl.ceb_432 lt.cy.ms_654
+ 0x20006b04, 0x55006421, 0x1f002702, 0x12000304, // ceb.sq.un_320 lg.rw.un_860 gd.cy.un_220 nl.hu.un_320
+ 0x0b0a0605, 0x080711ad, 0x35646807, 0x19002d07, // de.pt.es_333 ro.bg.uk_643 ig.lg.zu_432 sk.gl.un_420
+ // [19f0]
+ 0x1a5564a0, 0x3f3b0307, 0x55001c02, 0x1c002304, // lg.rw.tl_322 nl.so.af_432 id.rw.un_220 ca.id.un_320
+ 0x2d0d2909, 0x211c0aa0, 0x1e1c520c, 0x35000707, // sl.cs.sk_444 pt.id.jw_322 ha.id.ms_543 it.zu.un_420
+ 0x080c05a0, 0x0e001812, 0x131f01ad, 0x3f1f0955, // fr.sv.no_322 ga.is.un_640 en.cy.et_643 pl.cy.af_442
+ 0x55006412, 0x25001e02, 0x020c080c, 0x21003505, // lg.rw.un_640 ms.eu.un_220 no.sv.da_543 zu.jw.un_330
+ // [1a00]
+ 0x1a350807, 0x03000813, 0x03002504, 0x08022305, // no.zu.tl_432 no.nl.un_650 eu.nl.un_320 ca.da.no_333
+ 0x0d1c09ac, 0x210555a0, 0x53000414, 0x0f3f5509, // hi.mr.ne_632 rw.fr.jw_322 fi.ht.un_660 rw.af.lv_444
+ 0x0d292da4, 0x1e1c20a9, 0x25000705, 0x01352da0, // sk.sl.cs_433 sq.id.ms_544 it.eu.un_330 sk.zu.en_322
+ 0x21231702, 0x3f000413, 0x2a00030b, 0x02071fee, // sr.ca.jw_222 fi.af.un_650 nl.mt.un_520 cy.it.da_422
+ // [1a10]
+ 0x070a05ee, 0x07001a04, 0x3b002804, 0x1c090d0d, // fr.pt.it_422 tl.it.un_320 sw.so.un_320 ne.hi.mr_554
+ 0x29005302, 0x52103b04, 0x19002813, 0x0c1810a6, // ht.sl.un_220 so.lt.ha_332 sw.gl.un_650 lt.ga.sv_521
+ 0x64006807, 0x3f000505, 0x04111007, 0x28001305, // ig.lg.un_420 fr.af.un_330 be.ro.ru_432 et.sw.un_330
+ 0x0c2128ee, 0x182827a7, 0x12000a02, 0x1f645212, // sw.jw.sv_422 gd.sw.ga_532 pt.hu.un_220 ha.lg.cy_654
+ // [1a20]
+ 0x1f212509, 0x033f0605, 0x4a001213, 0x645568ee, // eu.jw.cy_444 de.af.nl_333 hu.yo.un_650 ig.rw.lg_422
+ 0x20291304, 0x2b001a02, 0x3f125302, 0x280c200c, // et.sl.sq_332 tl.vi.un_220 ht.hu.af_222 sq.sv.sw_543
+ 0x16000104, 0x12320d07, 0x04276404, 0x1a005304, // en.hr.un_320 cs.bs.hu_432 lg.gd.fi_332 ht.tl.un_320
+ 0x13001b0e, 0x07001f0e, 0x3200290c, 0x08001f07, // tr.et.un_550 cy.it.un_550 sl.bs.un_530 cy.no.un_420
+ // [1a30]
+ 0x521e11a0, 0x2a00280c, 0x230c55a0, 0x3b030407, // ro.ms.ha_322 sw.mt.un_530 rw.sv.ca_322 fi.nl.so_432
+ 0x2a2507af, 0x04003b0d, 0x0200050d, 0x1f002012, // it.eu.mt_655 so.fi.un_540 fr.da.un_540 sq.cy.un_640
+ 0x062701ad, 0x0e040fa0, 0x03310208, 0x180205a0, // en.gd.de_643 lv.fi.is_322 da.az.nl_443 fr.da.ga_322
+ 0x10000607, 0x25003f04, 0x3f0802ee, 0x2d0d1302, // de.lt.un_420 af.eu.un_320 da.no.af_422 et.cs.sk_222
+ // [1a40]
+ 0x292d1702, 0x0b0a0702, 0x08020c05, 0x10004a04, // sr.sk.sl_222 it.pt.es_222 sv.da.no_333 yo.lt.un_320
+ 0x0f10050b, 0x101b13ee, 0x3f023b02, 0x1100250d, // fr.lt.lv_542 et.tr.lt_422 so.da.af_222 eu.ro.un_540
+ 0x050110ee, 0x071911a0, 0x53253504, 0x0d003b04, // lt.en.fr_422 ro.gl.it_322 zu.eu.ht_332 so.cs.un_320
+ 0x09030107, 0x12211008, 0x0e2910ee, 0x1e000f07, // en.nl.pl_432 lt.jw.hu_443 lt.sl.is_422 lv.ms.un_420
+ // [1a50]
+ 0x17000205, 0x0c1304a4, 0x3f03130e, 0x12000804, // da.sr.un_330 fi.et.sv_433 et.nl.af_555 no.hu.un_320
+ 0x130f10a4, 0x0f002312, 0x1c090d09, 0x07170812, // lt.lv.et_433 ca.lv.un_640 ne.hi.mr_444 uk.sr.bg_654
+ 0x64001a02, 0x55642804, 0x090d1ca0, 0x29101602, // tl.lg.un_220 sw.lg.rw_332 mr.ne.hi_322 hr.lt.sl_222
+ 0x522a0607, 0x061b08af, 0x31005204, 0x27002d13, // de.mt.ha_432 no.tr.de_655 ha.az.un_320 sk.gd.un_650
+ // [1a60]
+ 0x31290fec, 0x190b2305, 0x250f1007, 0x10042807, // lv.sl.az_644 ca.es.gl_333 lt.lv.eu_432 sw.fi.lt_432
+ 0x11000b02, 0x2b001e02, 0x110509ee, 0x64001704, // es.ro.un_220 ms.vi.un_220 pl.fr.ro_422 sr.lg.un_320
+ 0x0e5308a0, 0x1e1c0ca0, 0x232a1355, 0x68526407, // no.ht.is_322 sv.id.ms_322 et.mt.ca_442 lg.ha.ig_432
+ 0x192312a7, 0x272018ad, 0x0f130e12, 0x25000808, // hu.ca.gl_532 ga.sq.gd_643 is.et.lv_654 no.eu.un_430
+ // [1a70]
+ 0x2a000419, 0x02003512, 0x120429a0, 0x5229280e, // fi.mt.un_750 zu.da.un_640 sl.fi.hu_322 sw.sl.ha_555
+ 0x13000b02, 0x0b072d5a, 0x0c001605, 0x190a23a0, // es.et.un_220 sk.it.es_553 hr.sv.un_330 ca.pt.gl_322
+ 0x01000302, 0x2500230b, 0x11552804, 0x3217280c, // nl.en.un_220 ca.eu.un_520 sw.rw.ro_332 sw.sr.bs_543
+ 0x02001209, 0x121a28a0, 0x190a230c, 0x250929ee, // hu.da.un_440 sw.tl.hu_322 ca.pt.gl_543 sl.pl.eu_422
+ // [1a80]
+ 0x201f0304, 0x130e28a4, 0x53000e0b, 0x00000103, // nl.cy.sq_332 sw.is.et_433 is.ht.un_520 en.un.un_300
+ 0x2700100d, 0x350e2814, 0x1a280705, 0x170c5302, // lt.gd.un_540 sw.is.zu_666 it.sw.tl_333 ht.sv.sr_222
+ 0x6b000508, 0x1f1a6bad, 0x10133fec, 0x6b003f0d, // fr.ceb.un_430 ceb.tl.cy_643 af.et.lt_644 af.ceb.un_540
+ 0x190b2dee, 0x0a006e07, 0x03001105, 0x2d0d0a09, // sk.es.gl_422 hmn.pt.un_420 ro.nl.un_330 pt.cs.sk_444
+ // [1a90]
+ 0x12292307, 0x0d131c08, 0x32170da0, 0x0c002704, // ca.sl.hu_432 mr.bh.ne_443 cs.sr.bs_322 gd.sv.un_320
+ 0x0f350d55, 0x016b3ba0, 0x27090107, 0x09002d04, // cs.zu.lv_442 so.ceb.en_322 en.pl.gd_432 sk.pl.un_320
+ 0x0508210c, 0x18000a02, 0x134a35a0, 0x0e080ca0, // jw.no.fr_543 pt.ga.un_220 zu.yo.et_322 sv.no.is_322
+ 0x0e121ba9, 0x03280f0b, 0x29162d5a, 0x4a1a5505, // tr.hu.is_544 lv.sw.nl_542 sk.hr.sl_553 rw.tl.yo_333
+ // [1aa0]
+ 0x2d0d29ee, 0x13000604, 0x6b006809, 0x08000a12, // sl.cs.sk_422 de.et.un_320 ig.ceb.un_440 mk.uk.un_640
+ 0x1b000c13, 0x1c3521ad, 0x040625ac, 0x3b003508, // sv.tr.un_650 jw.zu.id_643 eu.de.fi_632 zu.so.un_430
+ 0x2d0d050e, 0x09000809, 0x17003504, 0x6b0820ee, // fr.cs.sk_555 no.pl.un_440 zu.sr.un_320 sq.no.ceb_422
+ 0x08001a0d, 0x64002804, 0x4a522908, 0x2a005212, // tl.no.un_540 sw.lg.un_320 sl.ha.yo_443 ha.mt.un_640
+ // [1ab0]
+ 0x16003502, 0x023106a0, 0x190b1ba0, 0x12000613, // zu.hr.un_220 de.az.da_322 tr.es.gl_322 de.hu.un_650
+ 0x1b000414, 0x29161309, 0x0c002302, 0x100d2812, // fi.tr.un_660 et.hr.sl_444 ca.sv.un_220 sw.cs.lt_654
+ 0x100e1fee, 0x18000709, 0x01000b02, 0x1100130c, // cy.is.lt_422 it.ga.un_440 es.en.un_220 et.ro.un_530
+ 0x0c1308a7, 0x07001207, 0x0d2911af, 0x55002013, // no.et.sv_532 hu.it.un_420 ro.sl.cs_655 sq.rw.un_650
+ // [1ac0]
+ 0x3f003b13, 0x00001e0f, 0x070804ad, 0x133552ad, // so.af.un_650 ms.un.un_600 ru.uk.bg_643 ha.zu.et_643
+ 0x0d130960, 0x2a006404, 0x16000404, 0x55000611, // hi.bh.ne_664 lg.mt.un_320 fi.hr.un_320 de.rw.un_630
+ 0x35280604, 0x0e000604, 0x080217a0, 0x18000d13, // de.sw.zu_332 de.is.un_320 sr.da.no_322 cs.ga.un_650
+ 0x553f0604, 0x0a005207, 0x11051b11, 0x1e1c04ee, // de.af.rw_332 ha.pt.un_420 tr.fr.ro_653 fi.id.ms_422
+ // [1ad0]
+ 0x1f002304, 0x20060ea0, 0x05291111, 0x3100180e, // ca.cy.un_320 is.de.sq_322 ro.sl.fr_653 ga.az.un_550
+ 0x0e2708af, 0x313b1807, 0x17000304, 0x321710ee, // no.gd.is_655 ga.so.az_432 nl.sr.un_320 lt.sr.bs_422
+ 0x130d0960, 0x20082aa0, 0x4a006809, 0x1e552aa0, // hi.ne.bh_664 mt.no.sq_322 ig.yo.un_440 mt.rw.ms_322
+ 0x1f1302a0, 0x03001112, 0x2a006b04, 0x292a0713, // da.et.cy_322 ro.nl.un_640 ceb.mt.un_320 it.mt.sl_665
+ // [1ae0]
+ 0x25210604, 0x131108a4, 0x20030807, 0x04033fa6, // de.jw.eu_332 no.ro.et_433 no.nl.sq_432 af.nl.fi_521
+ 0x011f3fa0, 0x6b183b05, 0x28003b04, 0x1f000919, // af.cy.en_322 so.ga.ceb_333 so.sw.un_320 pl.cy.un_750
+ 0x550e3504, 0x070c0407, 0x35000e05, 0x13002818, // zu.is.rw_332 fi.sv.it_432 is.zu.un_330 sw.et.un_740
+ 0x074a53a0, 0x21006412, 0x2d000912, 0x17000d04, // ht.yo.it_322 lg.jw.un_640 pl.sk.un_640 cs.sr.un_320
+ // [1af0]
+ 0x281f20ee, 0x1000070e, 0x6e6b13ec, 0x2a6b280c, // sq.cy.sw_422 bg.be.un_550 et.ceb.hmn_644 sw.ceb.mt_543
+ 0x524a6b04, 0x0c060e0e, 0x1b002d14, 0x1e1b0fa0, // ceb.yo.ha_332 is.de.sv_555 sk.tr.un_660 lv.tr.ms_322
+ 0x31061b0e, 0x20001702, 0x09100207, 0x130e2508, // tr.de.az_555 sr.sq.un_220 da.lt.pl_432 eu.is.et_443
+ 0x31002118, 0x32281609, 0x281c1eee, 0x0a190b55, // jw.az.un_740 hr.sw.bs_444 ms.id.sw_422 es.gl.pt_442
+ // [1b00]
+ 0x6b252aad, 0x070a17ac, 0x0e6435ad, 0x21202507, // mt.eu.ceb_643 sr.mk.bg_632 zu.lg.is_643 eu.sq.jw_432
+ 0x0f2d3f13, 0x031f01ec, 0x1800050e, 0x09000607, // af.sk.lv_665 en.cy.nl_644 fr.ga.un_550 de.pl.un_420
+ 0x1300011a, 0x08002a02, 0x0c005504, 0x181364a0, // en.et.un_760 mt.no.un_220 rw.sv.un_320 lg.et.ga_322
+ 0x182768a0, 0x1c002a0d, 0x08020e05, 0x191f28ee, // ig.gd.ga_322 mt.id.un_540 is.da.no_333 sw.cy.gl_422
+ // [1b10]
+ 0x01230f0d, 0x171108a4, 0x03003b04, 0x10006407, // lv.ca.en_554 uk.ro.sr_433 so.nl.un_320 lg.lt.un_420
+ 0x1b002105, 0x3f03060e, 0x1e1c5505, 0x3b006b18, // jw.tr.un_330 de.nl.af_555 rw.id.ms_333 ceb.so.un_740
+ 0x2112180c, 0x5328090c, 0x1701080b, 0x20095504, // ar.ur.fa_543 pl.sw.ht_543 no.en.sr_542 rw.pl.sq_332
+ 0x04003512, 0x3f00011a, 0x12000302, 0x1c1e2112, // zu.fi.un_640 en.af.un_760 nl.hu.un_220 jw.ms.id_654
+ // [1b20]
+ 0x1b001a07, 0x0f005204, 0x080205ee, 0x041c1eee, // tl.tr.un_420 ha.lv.un_320 fr.da.no_422 ms.id.fi_422
+ 0x55641105, 0x060910ad, 0x4a002112, 0x3f1807a0, // ro.lg.rw_333 lt.pl.de_643 jw.yo.un_640 it.ga.af_322
+ 0x01001104, 0x1c0b27ee, 0x285535a0, 0x0f100c0b, // ro.en.un_320 gd.es.id_422 zu.rw.sw_322 sv.lt.lv_542
+ 0x131c0912, 0x282535a7, 0x13090dac, 0x23001314, // hi.mr.bh_654 zu.eu.sw_532 ne.hi.bh_632 et.ca.un_660
+ // [1b30]
+ 0x016435ad, 0x1b005302, 0x3b0955ee, 0x3b1a1ba0, // zu.lg.en_643 ht.tr.un_220 rw.pl.so_422 tr.tl.so_322
+ 0x13003b0e, 0x5568640d, 0x10005204, 0x55210802, // so.et.un_550 lg.ig.rw_554 ha.lt.un_320 no.jw.rw_222
+ 0x0d001e04, 0x0809040c, 0x6b000705, 0x23250107, // ms.cs.un_320 fi.pl.no_543 it.ceb.un_330 en.eu.ca_432
+ 0x131c09ac, 0x00000e01, 0x08130e11, 0x1a002807, // hi.mr.bh_632 is.un.un_200 is.et.no_653 sw.tl.un_420
+ // [1b40]
+ 0x1c351e07, 0x282129ec, 0x4a291212, 0x0f001008, // ms.zu.id_432 sl.jw.sw_644 hu.sl.yo_654 lt.lv.un_430
+ 0x0a23350c, 0x1300640e, 0x191711ee, 0x070a11af, // zu.ca.pt_543 lg.et.un_550 ro.sr.gl_422 ro.mk.bg_655
+ 0x170d1607, 0x1c130d08, 0x130e0f12, 0x03233f04, // hr.cs.sr_432 ne.bh.mr_443 lv.is.et_654 af.ca.nl_332
+ 0x0b000702, 0x05001807, 0x1c121aa6, 0x05530d12, // it.es.un_220 ga.fr.un_420 tl.hu.id_521 cs.ht.fr_654
+ // [1b50]
+ 0x1a00550e, 0x18000e13, 0x0c5564ac, 0x121a6ba9, // rw.tl.un_550 is.ga.un_650 lg.rw.sv_632 ceb.tl.hu_544
+ 0x11005519, 0x35645313, 0x0f28130d, 0x254a2a08, // rw.ro.un_750 ht.lg.zu_665 et.sw.lv_554 mt.yo.eu_443
+ 0x020804ad, 0x053f23a4, 0x1c130913, 0x1f006b04, // fi.no.da_643 ca.af.fr_433 hi.bh.mr_665 ceb.cy.un_320
+ 0x643507a0, 0x1b311307, 0x2a00230c, 0x0c3564ee, // it.zu.lg_322 et.az.tr_432 ca.mt.un_530 lg.zu.sv_422
+ // [1b60]
+ 0x2a0609ac, 0x3f001f12, 0x0b0a11a9, 0x28645512, // pl.de.mt_632 cy.af.un_640 ro.pt.es_544 rw.lg.sw_654
+ 0x0f1032a0, 0x0a180707, 0x6b012ba4, 0x55002104, // bs.lt.lv_322 it.ga.pt_432 vi.en.ceb_433 jw.rw.un_320
+ 0x05001b12, 0x180413ee, 0x53002521, 0x0300020e, // tr.fr.un_640 et.fi.ga_422 eu.ht.un_860 da.nl.un_550
+ 0x09013fee, 0x4a686b07, 0x01000f05, 0x32101307, // af.en.pl_422 ceb.ig.yo_432 lv.en.un_330 et.lt.bs_432
+ // [1b70]
+ 0x023b3f5a, 0x1b133113, 0x03022d07, 0x0b0a11ee, // af.so.da_553 az.et.tr_665 sk.da.nl_432 ro.pt.es_422
+ 0x133b0607, 0x536e05a0, 0x10001602, 0x200725a0, // de.so.et_432 fr.hmn.ht_322 hr.lt.un_220 eu.it.sq_322
+ 0x11002704, 0x01002812, 0x133127ee, 0x13522807, // gd.ro.un_320 sw.en.un_640 gd.az.et_422 sw.ha.et_432
+ 0x06000504, 0x3b0b28a9, 0x0a0417ec, 0x19120aa6, // fr.de.un_320 sw.es.so_544 sr.ru.mk_644 pt.hu.gl_521
+ // [1b80]
+ 0x13090d5a, 0x531b28af, 0x12001e0d, 0x0000160f, // ne.hi.bh_553 sw.tr.ht_655 ms.hu.un_540 hr.un.un_600
+ 0x016b18a0, 0x00001824, 0x07001c04, 0x081110a4, // ga.ceb.en_322 ar.un.un_900 id.it.un_320 be.ro.uk_433
+ 0x1c211e0c, 0x2b001f19, 0x00000406, 0x4a001a14, // ms.jw.id_543 cy.vi.un_750 fi.un.un_400 tl.yo.un_660
+ 0x28352702, 0x17000407, 0x1a4a25a7, 0x20001a02, // gd.zu.sw_222 ru.sr.un_420 eu.yo.tl_532 tl.sq.un_220
+ // [1b90]
+ 0x0c1b31a0, 0x211120a7, 0x1c2120ad, 0x20102aee, // az.tr.sv_322 sq.ro.jw_532 sq.jw.id_643 mt.lt.sq_422
+ 0x2500280d, 0x28002507, 0x230a07ee, 0x0e202b05, // sw.eu.un_540 eu.sw.un_420 it.pt.ca_422 vi.sq.is_333
+ 0x0a001f04, 0x082103a0, 0x20002102, 0x00006803, // cy.pt.un_320 nl.jw.no_322 jw.sq.un_220 ig.un.un_300
+ 0x2d000302, 0x0d130912, 0x0e085202, 0x2300011a, // nl.sk.un_220 hi.bh.ne_654 ha.no.is_222 en.ca.un_760
+ // [1ba0]
+ 0x096813a0, 0x232701ad, 0x092d3b12, 0x23030108, // et.ig.pl_322 en.gd.ca_643 so.sk.pl_654 en.nl.ca_443
+ 0x19000c04, 0x23271fee, 0x20002a08, 0x01212302, // sv.gl.un_320 cy.gd.ca_422 mt.sq.un_430 ca.jw.en_222
+ 0x684a28a0, 0x1b64310e, 0x05000613, 0x28003b13, // sw.yo.ig_322 az.lg.tr_555 de.fr.un_650 so.sw.un_650
+ 0x35312aa0, 0x13000508, 0x28251f02, 0x32553502, // mt.az.zu_322 fr.et.un_430 cy.eu.sw_222 zu.rw.bs_222
+ // [1bb0]
+ 0x0e000c02, 0x211e1cad, 0x05233faf, 0x29003f08, // sv.is.un_220 id.ms.jw_643 af.ca.fr_655 af.sl.un_430
+ 0x130823af, 0x0c060808, 0x080210a4, 0x1b002102, // ca.no.et_655 no.de.sv_443 lt.da.no_433 jw.tr.un_220
+ 0x1c2105a9, 0x080213ee, 0x0d001104, 0x08120607, // fr.jw.id_544 et.da.no_422 ro.cs.un_320 de.hu.no_432
+ 0x3b2052ee, 0x55000502, 0x20000f12, 0x01000a04, // ha.sq.so_422 fr.rw.un_220 lv.sq.un_640 pt.en.un_320
+ // [1bc0]
+ 0x2d0d1f07, 0x072a0aa4, 0x1b2b0b09, 0x13001a02, // cy.cs.sk_432 pt.mt.it_433 es.vi.tr_444 tl.et.un_220
+ 0x1c0d09ee, 0x0c6b64a0, 0x1011080c, 0x081b0507, // hi.ne.mr_422 lg.ceb.sv_322 no.ro.lt_543 fr.tr.no_432
+ 0x2d000e0d, 0x01001c04, 0x64051fee, 0x3b0519a0, // is.sk.un_540 id.en.un_320 cy.fr.lg_422 gl.fr.so_322
+ 0x010c20a7, 0x1c002a02, 0x3f021fa9, 0x03000e04, // sq.sv.en_532 mt.id.un_220 cy.da.af_544 is.nl.un_320
+ // [1bd0]
+ 0x031a3f04, 0x3f002005, 0x233f13ee, 0x0a00230c, // af.tl.nl_332 sq.af.un_330 et.af.ca_422 ca.pt.un_530
+ 0x282135ec, 0x2b535507, 0x2800640e, 0x35003b12, // zu.jw.sw_644 rw.ht.vi_432 lg.sw.un_550 so.zu.un_640
+ 0x1732160d, 0x2d003207, 0x3f035205, 0x0e001a02, // hr.bs.sr_554 bs.sk.un_420 ha.nl.af_333 tl.is.un_220
+ 0x21002502, 0x0c133fa0, 0x354a1ca0, 0x070a0813, // eu.jw.un_220 af.et.sv_322 id.yo.zu_322 uk.mk.bg_665
+ // [1be0]
+ 0x322a17a0, 0x1b5225ad, 0x20005302, 0x062a08ec, // sr.mt.bs_322 eu.ha.tr_643 ht.sq.un_220 no.mt.de_644
+ 0x3b2008ee, 0x6400210e, 0x0d162d0c, 0x521a6bad, // no.sq.so_422 jw.lg.un_550 sk.hr.cs_543 ceb.tl.ha_643
+ 0x03001604, 0x06002808, 0x12001b05, 0x060c0813, // hr.nl.un_320 sw.de.un_430 tr.hu.un_330 no.sv.de_665
+ 0x293f2d05, 0x21000307, 0x1f006414, 0x16296eee, // sk.af.sl_333 nl.jw.un_420 lg.cy.un_660 hmn.sl.hr_422
+ // [1bf0]
+ 0x210b02a0, 0x53000702, 0x13072da7, 0x1a6b52ec, // da.es.jw_322 it.ht.un_220 sk.it.et_532 ha.ceb.tl_644
+ 0x03021f04, 0x04002904, 0x080c2aa9, 0x64551a0c, // cy.da.nl_332 sl.fi.un_320 mt.sv.no_544 tl.rw.lg_543
+ 0x4a520c07, 0x55002914, 0x062009a4, 0x190a1214, // sv.ha.yo_432 sl.rw.un_660 pl.sq.de_433 hu.pt.gl_666
+ 0x2100270b, 0x321e5309, 0x351255a0, 0x1c090d08, // gd.jw.un_520 ht.ms.bs_444 rw.hu.zu_322 ne.hi.mr_443
+
+ // [1c00]
+ 0x52004a02, 0x3500131a, 0x554a2904, 0x28000408, // yo.ha.un_220 et.zu.un_760 sl.yo.rw_332 fi.sw.un_430
+ 0x171606a0, 0x1c091312, 0x125320ad, 0x08021205, // de.hr.sr_322 bh.hi.mr_654 sq.ht.hu_643 hu.da.no_333
+ 0x55355213, 0x201605a0, 0x4a00290e, 0x20004a0d, // ha.zu.rw_665 fr.hr.sq_322 sl.yo.un_550 yo.sq.un_540
+ 0x29005313, 0x20113502, 0x0c1a120c, 0x170b29a0, // ht.sl.un_650 zu.ro.sq_222 hu.tl.sv_543 sl.es.sr_322
+ // [1c10]
+ 0x3b31270d, 0x1e1c28a0, 0x64000808, 0x290f1104, // gd.az.so_554 sw.id.ms_322 no.lg.un_430 ro.lv.sl_332
+ 0x08642711, 0x0718270c, 0x0d000e11, 0x1329040c, // gd.lg.no_653 gd.ga.it_543 is.cs.un_630 fi.sl.et_543
+ 0x12090804, 0x101f180c, 0x08021fee, 0x321653ee, // no.pl.hu_332 ga.cy.lt_543 cy.da.no_422 ht.hr.bs_422
+ 0x29000a12, 0x01000607, 0x01002302, 0x1f012bee, // pt.sl.un_640 de.en.un_420 ca.en.un_220 vi.en.cy_422
+ // [1c20]
+ 0x16290908, 0x0f005214, 0x13002007, 0x01072302, // pl.sl.hr_443 ha.lv.un_660 sq.et.un_420 ca.it.en_222
+ 0x19001818, 0x135535ee, 0x20001e02, 0x3b001309, // ga.gl.un_740 zu.rw.et_422 ms.sq.un_220 et.so.un_440
+ 0x12211ca0, 0x2d0d3202, 0x03080ca0, 0x1c3216a0, // id.jw.hu_322 bs.cs.sk_222 sv.no.nl_322 hr.bs.id_322
+ 0x1a3b28ee, 0x016b09ee, 0x11001013, 0x3f030107, // sw.so.tl_422 pl.ceb.en_422 be.ro.un_650 en.nl.af_432
+ // [1c30]
+ 0x18002309, 0x200e18ee, 0x10000c07, 0x12006e08, // ca.ga.un_440 ga.is.sq_422 sv.lt.un_420 hmn.hu.un_430
+ 0x042d0faf, 0x1000171a, 0x28001e0d, 0x091c1355, // lv.sk.fi_655 sr.be.un_760 ms.sw.un_540 bh.mr.hi_442
+ 0x11201804, 0x68002013, 0x02080c04, 0x290d2aa0, // ga.sq.ro_332 sq.ig.un_650 sv.no.da_332 mt.cs.sl_322
+ 0x2a000919, 0x20001f12, 0x0d091c55, 0x0a000304, // pl.mt.un_750 cy.sq.un_640 mr.hi.ne_442 nl.pt.un_320
+ // [1c40]
+ 0x030c0202, 0x210c35a0, 0x13001c04, 0x64043baf, // da.sv.nl_222 zu.sv.jw_322 mr.bh.un_320 so.fi.lg_655
+ 0x0b1853ee, 0x556413af, 0x132921a0, 0x2a000704, // ht.ga.es_422 et.lg.rw_655 jw.sl.et_322 it.mt.un_320
+ 0x1a101304, 0x180403a0, 0x1b6b0f04, 0x1707080c, // et.lt.tl_332 nl.fi.ga_322 lv.ceb.tr_332 uk.bg.sr_543
+ 0x2d090d0d, 0x32000707, 0x31001704, 0x0c093ba0, // cs.pl.sk_554 it.bs.un_420 sr.az.un_320 so.pl.sv_322
+ // [1c50]
+ 0x0c0e3155, 0x1a683512, 0x091c1311, 0x23002104, // az.is.sv_442 zu.ig.tl_654 bh.mr.hi_653 jw.ca.un_320
+ 0x1210010c, 0x2d0d3fa9, 0x25001013, 0x0a00170e, // en.lt.hu_543 af.cs.sk_544 lt.eu.un_650 sr.mk.un_550
+ 0x06350e07, 0x080210ee, 0x0d022dec, 0x0b000c07, // is.zu.de_432 lt.da.no_422 sk.da.cs_644 sv.es.un_420
+ 0x1e683b07, 0x023531a0, 0x09002107, 0x1a5535a4, // so.ig.ms_432 az.zu.da_322 jw.pl.un_420 zu.rw.tl_433
+ // [1c60]
+ 0x4a6e2804, 0x200f350c, 0x0e000104, 0x102835ad, // sw.hmn.yo_332 zu.lv.sq_543 en.is.un_320 zu.sw.lt_643
+ 0x1e1c1a05, 0x55233b05, 0x120b0e0c, 0x0000320a, // tl.id.ms_333 so.ca.rw_333 is.es.hu_543 bs.un.un_500
+ 0x31002007, 0x1b003509, 0x4a532907, 0x1a003b08, // sq.az.un_420 zu.tr.un_440 sl.ht.yo_432 so.tl.un_430
+ 0x1e6b5305, 0x3b000d11, 0x04002812, 0x12643f04, // ht.ceb.ms_333 cs.so.un_630 sw.fi.un_640 af.lg.hu_332
+ // [1c70]
+ 0x11080405, 0x3f0e03af, 0x0a006b04, 0x082a0aa4, // ru.uk.ro_333 nl.is.af_655 ceb.pt.un_320 pt.mt.no_433
+ 0x311a6b12, 0x53000d13, 0x04132dee, 0x0f6425ad, // ceb.tl.az_654 cs.ht.un_650 sk.et.fi_422 eu.lg.lv_643
+ 0x1300110e, 0x102729ad, 0x13091cad, 0x08000302, // ro.et.un_550 sl.gd.lt_643 mr.hi.bh_643 nl.no.un_220
+ 0x02001702, 0x29111604, 0x09101f08, 0x28005302, // sr.da.un_220 hr.ro.sl_332 cy.lt.pl_443 ht.sw.un_220
+ // [1c80]
+ 0x1e1c3fec, 0x646823a4, 0x0a002704, 0x13020fee, // af.id.ms_644 ca.ig.lg_433 gd.pt.un_320 lv.da.et_422
+ 0x2100520e, 0x4a006402, 0x060c0e12, 0x08100302, // ha.jw.un_550 lg.yo.un_220 is.sv.de_654 nl.lt.no_222
+ 0x050d53a0, 0x1c0d130c, 0x080c020d, 0x100807af, // ht.cs.fr_322 bh.ne.mr_543 da.sv.no_554 bg.uk.be_655
+ 0x0a000b04, 0x1312110c, 0x23002721, 0x09030604, // es.pt.un_320 ro.hu.et_543 gd.ca.un_860 de.nl.pl_332
+ // [1c90]
+ 0x032d17a4, 0x033f6407, 0x0f1009a7, 0x686428ad, // sr.sk.nl_433 lg.af.nl_432 pl.lt.lv_532 sw.lg.ig_643
+ 0x20001018, 0x066b03ee, 0x21081ca0, 0x1e100405, // lt.sq.un_740 nl.ceb.de_422 id.no.jw_322 fi.lt.ms_333
+ 0x53000104, 0x016b0807, 0x050b1107, 0x07050aa9, // en.ht.un_320 no.ceb.en_432 ro.es.fr_432 pt.fr.it_544
+ 0x3b001008, 0x01182004, 0x32002907, 0x01213ba0, // lt.so.un_430 sq.ga.en_332 sl.bs.un_420 so.jw.en_322
+ // [1ca0]
+ 0x3b003504, 0x35000d08, 0x1c130daf, 0x2d0d1005, // zu.so.un_320 cs.zu.un_430 ne.bh.mr_655 lt.cs.sk_333
+ 0x52532705, 0x21282704, 0x27122802, 0x01000704, // gd.ht.ha_333 gd.sw.jw_332 sw.hu.gd_222 it.en.un_320
+ 0x6b072da0, 0x08005307, 0x040605ac, 0x1f000813, // sk.it.ceb_322 ht.no.un_420 fr.de.fi_632 no.cy.un_650
+ 0x0b192da4, 0x0a1b05ee, 0x012311ad, 0x6855530e, // sk.gl.es_433 fr.tr.pt_422 ro.ca.en_643 ht.rw.ig_555
+ // [1cb0]
+ 0x283f3504, 0x1a005214, 0x08000404, 0x09006402, // zu.af.sw_332 ha.tl.un_660 ru.uk.un_320 lg.pl.un_220
+ 0x3f030605, 0x3b1a0ba0, 0x5308230c, 0x68000f09, // de.nl.af_333 es.tl.so_322 ca.no.ht_543 lv.ig.un_440
+ 0x190f0b0c, 0x1a002113, 0x07006419, 0x23052da4, // es.lv.gl_543 jw.tl.un_650 lg.it.un_750 sk.fr.ca_433
+ 0x042b200c, 0x0d2510ec, 0x686e0f55, 0x6b3555ad, // sq.vi.fi_543 lt.eu.cs_644 lv.hmn.ig_442 rw.zu.ceb_643
+ // [1cc0]
+ 0x1100171b, 0x190b07af, 0x033f530d, 0x17000a02, // sr.ro.un_770 it.es.gl_655 ht.af.nl_554 mk.sr.un_220
+ 0x2500020e, 0x32160e04, 0x321729ad, 0x1a006812, // da.eu.un_550 is.hr.bs_332 sl.sr.bs_643 ig.tl.un_640
+ 0x01070907, 0x060107ee, 0x275313af, 0x1f001812, // pl.it.en_432 it.en.de_422 et.ht.gd_655 ga.cy.un_640
+ 0x27203b5a, 0x35006421, 0x64000407, 0x2d0d25ec, // so.sq.gd_553 lg.zu.un_860 fi.lg.un_420 eu.cs.sk_644
+ // [1cd0]
+ 0x130e10a7, 0x29001c04, 0x291664a0, 0x350b64a0, // lt.is.et_532 id.sl.un_320 lg.hr.sl_322 lg.es.zu_322
+ 0x0e003b19, 0x08020eaf, 0x10001305, 0x1e1c12a4, // so.is.un_750 is.da.no_655 et.lt.un_330 hu.id.ms_433
+ 0x3b4a3fee, 0x07006412, 0x10170812, 0x64001312, // af.yo.so_422 lg.it.un_640 uk.sr.be_654 et.lg.un_640
+ 0x10292a04, 0x27003204, 0x3b6b5507, 0x2511050e, // mt.sl.lt_332 bs.gd.un_320 rw.ceb.so_432 fr.ro.eu_555
+ // [1ce0]
+ 0x1b4a550b, 0x550529a7, 0x2b0e55a0, 0x0d2a2d07, // rw.yo.tr_542 sl.fr.rw_532 rw.is.vi_322 sk.mt.cs_432
+ 0x07250604, 0x11550b02, 0x201b2da0, 0x526401a4, // de.eu.it_332 es.rw.ro_222 sk.tr.sq_322 en.lg.ha_433
+ 0x0c00640d, 0x3f0603ec, 0x16002507, 0x196e0a07, // lg.sv.un_540 nl.de.af_644 eu.hr.un_420 pt.hmn.gl_432
+ 0x3b001318, 0x3217200c, 0x061b0302, 0x1e1c0805, // et.so.un_740 sq.sr.bs_543 nl.tr.de_222 no.id.ms_333
+ // [1cf0]
+ 0x285552ad, 0x08020cac, 0x1b0a0407, 0x321625ee, // ha.rw.sw_643 sv.da.no_632 fi.pt.tr_432 eu.hr.bs_422
+ 0x0305530c, 0x4a1a5507, 0x100e02a6, 0x190b0aaf, // ht.fr.nl_543 rw.tl.yo_432 da.is.lt_521 pt.es.gl_655
+ 0x1e001108, 0x130e0814, 0x2d0d0fa9, 0x32000e0b, // ro.ms.un_430 no.is.et_666 lv.cs.sk_544 is.bs.un_520
+ 0x021a25a0, 0x11001005, 0x28532102, 0x11003504, // eu.tl.da_322 be.ro.un_330 jw.ht.sw_222 zu.ro.un_320
+ // [1d00]
+ 0x2d290d05, 0x6b0901ec, 0x1f00060e, 0x3f271f02, // cs.sl.sk_333 en.pl.ceb_644 de.cy.un_550 cy.gd.af_222
+ 0x310e1b13, 0x321c64a0, 0x64000b02, 0x08006b07, // tr.is.az_665 lg.id.bs_322 es.lg.un_220 ceb.no.un_420
+ 0x040e13a4, 0x19250b0c, 0x311b0505, 0x091f05a4, // et.is.fi_433 es.eu.gl_543 fr.tr.az_333 fr.cy.pl_433
+ 0x09130d60, 0x09040555, 0x28552504, 0x1f006407, // ne.bh.hi_664 fr.fi.pl_442 eu.rw.sw_332 lg.cy.un_420
+ // [1d10]
+ 0x0f00130d, 0x6b000805, 0x171632a6, 0x64522aa9, // et.lv.un_540 no.ceb.un_330 bs.hr.sr_521 mt.ha.lg_544
+ 0x215329ee, 0x1b550f11, 0x080211a6, 0x0f272512, // sl.ht.jw_422 lv.rw.tr_653 ro.da.no_521 eu.gd.lv_654
+ 0x0e070807, 0x021b0808, 0x1a213b04, 0x29000b02, // no.it.is_432 no.tr.da_443 so.jw.tl_332 es.sl.un_220
+ 0x0d0c1307, 0x04003113, 0x321753a0, 0x3f1a09a4, // et.sv.cs_432 az.fi.un_650 ht.sr.bs_322 pl.tl.af_433
+ // [1d20]
+ 0x351e2007, 0x183b2760, 0x35000804, 0x2a55530c, // sq.ms.zu_432 gd.so.ga_664 no.zu.un_320 ht.rw.mt_543
+ 0x13005219, 0x18170ba0, 0x122508a0, 0x09005302, // ha.et.un_750 es.sr.ga_322 no.eu.hu_322 ht.pl.un_220
+ 0x1f00210c, 0x06172007, 0x0b183102, 0x0a003b04, // jw.cy.un_530 sq.sr.de_432 az.ga.es_222 so.pt.un_320
+ 0x25060fad, 0x080c0e02, 0x270e3bee, 0x2800270c, // lv.de.eu_643 is.sv.no_222 so.is.gd_422 gd.sw.un_530
+ // [1d30]
+ 0x1b2801a7, 0x29122d0c, 0x52280e08, 0x2517270c, // en.sw.tr_532 sk.hu.sl_543 is.sw.ha_443 gd.sr.eu_543
+ 0x2d170d09, 0x6e2d1255, 0x01132855, 0x06030107, // cs.sr.sk_444 hu.sk.hmn_442 sw.et.en_442 en.nl.de_432
+ 0x1e1c32a0, 0x09000108, 0x04070aa7, 0x28003f13, // bs.id.ms_322 en.pl.un_430 mk.bg.ru_532 af.sw.un_650
+ 0x0b003505, 0x27001108, 0x10000104, 0x1e1c3ba9, // zu.es.un_330 ro.gd.un_430 en.lt.un_320 so.id.ms_544
+ // [1d40]
+ 0x312125ad, 0x0b2319a0, 0x09002a02, 0x011927a0, // eu.jw.az_643 gl.ca.es_322 mt.pl.un_220 gd.gl.en_322
+ 0x0f1e35a0, 0x091c13ec, 0x3b280404, 0x04001e07, // zu.ms.lv_322 bh.mr.hi_644 fi.sw.so_332 ms.fi.un_420
+ 0x293216a6, 0x090d1312, 0x1f003b1a, 0x1e1c35ee, // hr.bs.sl_521 bh.ne.hi_654 so.cy.un_760 zu.id.ms_422
+ 0x20311c02, 0x3f3509a4, 0x09002304, 0x1c13090c, // id.az.sq_222 pl.zu.af_433 ca.pl.un_320 hi.bh.mr_543
+ // [1d50]
+ 0x271a1811, 0x2100060c, 0x130435a0, 0x19002b13, // ga.tl.gd_653 de.jw.un_530 zu.fi.et_322 vi.gl.un_650
+ 0x190407ee, 0x64251ea0, 0x16122055, 0x31081ba4, // it.fi.gl_422 ms.eu.lg_322 sq.hu.hr_442 tr.no.az_433
+ 0x1c645507, 0x64070407, 0x101352ad, 0x1a002913, // rw.lg.id_432 fi.it.lg_432 ha.et.lt_643 sl.tl.un_650
+ 0x2d0d21a4, 0x3b522860, 0x35001814, 0x0c001602, // jw.cs.sk_433 sw.ha.so_664 ga.zu.un_660 hr.sv.un_220
+ // [1d60]
+ 0x08023fa4, 0x06106ba0, 0x20322aee, 0x3f001a12, // af.da.no_433 ceb.lt.de_322 mt.bs.sq_422 tl.af.un_640
+ 0x322b16a0, 0x010335a0, 0x552164a0, 0x080b0a02, // hr.vi.bs_322 zu.nl.en_322 lg.jw.rw_322 pt.es.no_222
+ 0x09320f04, 0x0820030c, 0x0d1c090c, 0x64532007, // lv.bs.pl_332 nl.sq.no_543 hi.mr.ne_543 sq.ht.lg_432
+ 0x550728ee, 0x013f1aa0, 0x286429ee, 0x21002018, // sw.it.rw_422 tl.af.en_322 sl.lg.sw_422 sq.jw.un_740
+ // [1d70]
+ 0x17002007, 0x121009a4, 0x35532808, 0x061b3f12, // sq.sr.un_420 pl.lt.hu_433 sw.ht.zu_443 af.tr.de_654
+ 0x643f55ec, 0x1e006412, 0x1c130909, 0x1b0a28a0, // rw.af.lg_644 lg.ms.un_640 hi.bh.mr_444 sw.pt.tr_322
+ 0x08002102, 0x210b2055, 0x0a2d0d13, 0x35551aa4, // jw.no.un_220 sq.es.jw_442 cs.sk.pt_665 tl.rw.zu_433
+ 0x19002a07, 0x0300010b, 0x02001308, 0x086410a7, // mt.gl.un_420 en.nl.un_520 et.da.un_430 lt.lg.no_532
+ // [1d80]
+ 0x354a0408, 0x2a000c04, 0x046410ee, 0x28133b07, // fi.yo.zu_443 sv.mt.un_320 lt.lg.fi_422 so.et.sw_432
+ 0x21000302, 0x04000804, 0x05001214, 0x25005214, // nl.jw.un_220 no.fi.un_320 hu.fr.un_660 ha.eu.un_660
+ 0x10040a0e, 0x18000412, 0x282164a4, 0x032a3ba0, // mk.ru.be_555 fi.ga.un_640 lg.jw.sw_433 so.mt.nl_322
+ 0x3f271607, 0x321701a4, 0x2a0728ac, 0x29000914, // hr.gd.af_432 en.sr.bs_433 sw.it.mt_632 pl.sl.un_660
+ // [1d90]
+ 0x52006402, 0x52001118, 0x08023f07, 0x011911ee, // lg.ha.un_220 ro.ha.un_740 af.da.no_432 ro.gl.en_422
+ 0x0d092d0c, 0x041213a0, 0x6b13010c, 0x050701a6, // sk.pl.cs_543 et.hu.fi_322 en.et.ceb_543 en.it.fr_521
+ 0x35005302, 0x2b001112, 0x1a0401a0, 0x6b0501ad, // ht.zu.un_220 ro.vi.un_640 en.fi.tl_322 en.fr.ceb_643
+ 0x3f00091a, 0x6b005505, 0x28001a02, 0x03001702, // pl.af.un_760 rw.ceb.un_330 tl.sw.un_220 sr.nl.un_220
+ // [1da0]
+ 0x17002911, 0x11002813, 0x081323ee, 0x16005504, // sl.sr.un_630 sw.ro.un_650 ca.et.no_422 rw.hr.un_320
+ 0x32001e02, 0x0700130d, 0x256b4aad, 0x020c08a7, // ms.bs.un_220 et.it.un_540 yo.ceb.eu_643 no.sv.da_532
+ 0x161e32a0, 0x0900280e, 0x1827130c, 0x132a3504, // bs.ms.hr_322 sw.pl.un_550 et.gd.ga_543 zu.mt.et_332
+ 0x12002b18, 0x120420af, 0x52006e07, 0x2d0d3502, // vi.hu.un_740 sq.fi.hu_655 hmn.ha.un_420 zu.cs.sk_222
+ // [1db0]
+ 0x063f21ee, 0x5500091a, 0x55353f13, 0x55040112, // jw.af.de_422 pl.rw.un_760 af.zu.rw_665 en.fi.rw_654
+ 0x1b230aec, 0x52250608, 0x23000104, 0x055316a0, // pt.ca.tr_644 de.eu.ha_443 en.ca.un_320 hr.ht.fr_322
+ 0x0d2d05af, 0x17003204, 0x25211007, 0x0e002a09, // fr.sk.cs_655 bs.sr.un_320 lt.jw.eu_432 mt.is.un_440
+ 0x0e2a3bee, 0x522a0502, 0x190a2502, 0x11000614, // so.mt.is_422 fr.mt.ha_222 eu.pt.gl_222 de.ro.un_660
+ // [1dc0]
+ 0x2a001909, 0x112705ee, 0x1f000d11, 0x642a10af, // gl.mt.un_440 fr.gd.ro_422 cs.cy.un_630 lt.mt.lg_655
+ 0x32212aa0, 0x3b005202, 0x0b00120b, 0x01091b07, // mt.jw.bs_322 ha.so.un_220 hu.es.un_520 tr.pl.en_432
+ 0x1b311fec, 0x102d20ee, 0x0b010aee, 0x03083f02, // cy.az.tr_644 sq.sk.lt_422 pt.en.es_422 af.no.nl_222
+ 0x0f101e04, 0x55351104, 0x06043f02, 0x0a3504a6, // ms.lt.lv_332 ro.zu.rw_332 af.fi.de_222 fi.zu.pt_521
+ // [1dd0]
+ 0x21005313, 0x190b0702, 0x321720ee, 0x352164a6, // ht.jw.un_650 it.es.gl_222 sq.sr.bs_422 lg.jw.zu_521
+ 0x06090112, 0x133f6408, 0x1c1309a4, 0x1b006418, // en.pl.de_654 lg.af.et_443 hi.bh.mr_433 lg.tr.un_740
+ 0x3f643ba9, 0x1c005307, 0x29005204, 0x1e2835ac, // so.lg.af_544 ht.id.un_420 ha.sl.un_320 zu.sw.ms_632
+ 0x6b1a08a0, 0x3f210604, 0x130c08ec, 0x05003b02, // no.tl.ceb_322 de.jw.af_332 no.sv.et_644 so.fr.un_220
+ // [1de0]
+ 0x131b04ec, 0x130420ad, 0x0e004a07, 0x1e1c1602, // fi.tr.et_644 sq.fi.et_643 yo.is.un_420 hr.id.ms_222
+ 0x1b000508, 0x1b00040d, 0x2a0407ee, 0x233f530d, // fr.tr.un_430 fi.tr.un_540 it.fi.mt_422 ht.af.ca_554
+ 0x230b25a4, 0x6b211005, 0x190528a4, 0x19050a55, // eu.es.ca_433 lt.jw.ceb_333 sw.fr.gl_433 pt.fr.gl_442
+ 0x53021ea0, 0x20551107, 0x16112007, 0x283b18a0, // ms.da.ht_322 ro.rw.sq_432 sq.ro.hr_432 ga.so.sw_322
+ // [1df0]
+ 0x0600530c, 0x1b005512, 0x0d165307, 0x16001c07, // ht.de.un_530 rw.tr.un_640 ht.hr.cs_432 id.hr.un_420
+ 0x27182b04, 0x13000404, 0x2a0c0408, 0x25133bee, // vi.ga.gd_332 fi.et.un_320 fi.sv.mt_443 so.et.eu_422
+ 0x3b005304, 0x162911a4, 0x12000d08, 0x1c001212, // ht.so.un_320 ro.sl.hr_433 cs.hu.un_430 hu.id.un_640
+ 0x3b684aee, 0x082505ad, 0x0c003b14, 0x051f52a9, // yo.ig.so_422 fr.eu.no_643 so.sv.un_660 ha.cy.fr_544
+ // [1e00]
+ 0x1f000512, 0x2a0406a9, 0x0c000108, 0x11003f07, // fr.cy.un_640 de.fi.mt_544 en.sv.un_430 af.ro.un_420
+ 0x00001915, 0x0c521fa4, 0x19110a0d, 0x0a002018, // gl.un.un_700 cy.ha.sv_433 pt.ro.gl_554 sq.pt.un_740
+ 0x0e1923ee, 0x2d2004af, 0x27315211, 0x06001707, // ca.gl.is_422 fi.sq.sk_655 ha.az.gd_653 sr.de.un_420
+ 0x12203f09, 0x3b003f07, 0x316b0ead, 0x311b520e, // af.sq.hu_444 af.so.un_420 is.ceb.az_643 ha.tr.az_555
+ // [1e10]
+ 0x1e1c55a4, 0x0e005204, 0x10132509, 0x28004a0c, // rw.id.ms_433 ha.is.un_320 eu.et.lt_444 yo.sw.un_530
+ 0x2a183b0b, 0x11000f12, 0x07181f12, 0x2d0d21a0, // so.ga.mt_542 lv.ro.un_640 cy.ga.it_654 jw.cs.sk_322
+ 0x1b1606a4, 0x20522812, 0x014a29a7, 0x3b000e13, // de.hr.tr_433 sw.ha.sq_654 sl.yo.en_532 is.so.un_650
+ 0x1c002504, 0x3f001812, 0x2a1a21a4, 0x08111714, // eu.id.un_320 ga.af.un_640 jw.tl.mt_433 sr.ro.uk_666
+ // [1e20]
+ 0x53214a55, 0x27006e18, 0x00001b01, 0x642506ad, // yo.jw.ht_442 hmn.gd.un_740 tr.un.un_200 de.eu.lg_643
+ 0x0603250c, 0x06001813, 0x1306020d, 0x1e002a04, // eu.nl.de_543 ga.de.un_650 da.de.et_554 mt.ms.un_320
+ 0x3b2501a0, 0x17080a13, 0x55001f12, 0x0c101f07, // en.eu.so_322 mk.uk.sr_665 cy.rw.un_640 cy.lt.sv_432
+ 0x3f1b08a7, 0x13091c09, 0x0e282102, 0x190b0605, // no.tr.af_532 mr.hi.bh_444 jw.sw.is_222 de.es.gl_333
+ // [1e30]
+ 0x1a6b5507, 0x2d005504, 0x35006b04, 0x11003202, // rw.ceb.tl_432 rw.sk.un_320 ceb.zu.un_320 bs.ro.un_220
+ 0x28553b5a, 0x0607250d, 0x1b2053a9, 0x286b310c, // so.rw.sw_553 eu.it.de_554 ht.sq.tr_544 az.ceb.sw_543
+ 0x35091a07, 0x1632170c, 0x28001102, 0x12003f02, // tl.pl.zu_432 sr.bs.hr_543 ro.sw.un_220 af.hu.un_220
+ 0x534a5507, 0x1b1112a4, 0x28006404, 0x530f05a0, // rw.yo.ht_432 hu.ro.tr_433 lg.sw.un_320 fr.lv.ht_322
+ // [1e40]
+ 0x25640c02, 0x190b1005, 0x07000112, 0x2a001904, // sv.lg.eu_222 lt.es.gl_333 en.it.un_640 gl.mt.un_320
+ 0x0f1b2907, 0x071104a4, 0x130d0908, 0x072a1105, // sl.tr.lv_432 ru.ro.bg_433 hi.ne.bh_443 ro.mt.it_333
+ 0x05000a0d, 0x201b29ec, 0x52001b09, 0x0f2512ad, // pt.fr.un_540 sl.tr.sq_644 tr.ha.un_440 hu.eu.lv_643
+ 0x0c1b28ee, 0x0e00270d, 0x52002721, 0x2a351207, // sw.tr.sv_422 gd.is.un_540 gd.ha.un_860 hu.zu.mt_432
+ // [1e50]
+ 0x17080a05, 0x0a002b19, 0x100504a0, 0x0600130d, // mk.uk.sr_333 vi.pt.un_750 fi.fr.lt_322 et.de.un_540
+ 0x27000e18, 0x322035a0, 0x3b003f13, 0x1209250b, // is.gd.un_740 zu.sq.bs_322 af.so.un_650 eu.pl.hu_542
+ 0x520612ec, 0x0b0a23a9, 0x012b12a6, 0x3b001802, // hu.de.ha_644 ca.pt.es_544 hu.vi.en_521 ga.so.un_220
+ 0x12001604, 0x13041ba4, 0x17160205, 0x52283508, // hr.hu.un_320 tr.fi.et_433 da.hr.sr_333 zu.sw.ha_443
+ // [1e60]
+ 0x1a0c0e07, 0x52000704, 0x0d1c09a7, 0x321629a9, // is.sv.tl_432 it.ha.un_320 hi.mr.ne_532 sl.hr.bs_544
+ 0x1a001319, 0x190a2709, 0x05061005, 0x1f000c0d, // et.tl.un_750 gd.pt.gl_444 lt.de.fr_333 sv.cy.un_540
+ 0x09002105, 0x5235550b, 0x0a190b07, 0x2d0d05a0, // jw.pl.un_330 rw.zu.ha_542 es.gl.pt_432 fr.cs.sk_322
+ 0x2900520c, 0x1f000507, 0x05002904, 0x3b1252ee, // ha.sl.un_530 fr.cy.un_420 sl.fr.un_320 ha.hu.so_422
+ // [1e70]
+ 0x033f2804, 0x2d0d1609, 0x35002d08, 0x071004af, // sw.af.nl_332 hr.cs.sk_444 sk.zu.un_430 fi.lt.it_655
+ 0x1e005304, 0x2d0d290e, 0x2500520e, 0x27000b04, // ht.ms.un_320 sl.cs.sk_555 ha.eu.un_550 es.gd.un_320
+ 0x3f000a12, 0x10640f13, 0x19006405, 0x2d0920a9, // pt.af.un_640 lv.lg.lt_665 lg.gl.un_330 sq.pl.sk_544
+ 0x0d280307, 0x3f006e04, 0x093f08a4, 0x20003518, // nl.sw.cs_432 hmn.af.un_320 no.af.pl_433 zu.sq.un_740
+ // [1e80]
+ 0x18001f04, 0x532a0a12, 0x27000619, 0x06002119, // cy.ga.un_320 pt.mt.ht_654 de.gd.un_750 jw.de.un_750
+ 0x2a5264ad, 0x03092507, 0x21006b04, 0x28130107, // lg.ha.mt_643 eu.pl.nl_432 ceb.jw.un_320 en.et.sw_432
+ 0x3f00640b, 0x0b522112, 0x0a000814, 0x0000281c, // lg.af.un_520 jw.ha.es_654 uk.mk.un_660 sw.un.un_800
+ 0x5300640d, 0x641a3ba7, 0x0e003202, 0x0c1f1a0d, // lg.ht.un_540 so.tl.lg_532 bs.is.un_220 tl.cy.sv_554
+ // [1e90]
+ 0x190b13af, 0x28532055, 0x19050a07, 0x2b1f0611, // et.es.gl_655 sq.ht.sw_442 pt.fr.gl_432 de.cy.vi_653
+ 0x112308ad, 0x0400010c, 0x08000a02, 0x230605af, // no.ca.ro_643 en.fi.un_530 mk.uk.un_220 fr.de.ca_655
+ 0x172d0907, 0x042a1baf, 0x0b002512, 0x01250605, // pl.sk.sr_432 tr.mt.fi_655 eu.es.un_640 de.eu.en_333
+ 0x321711a0, 0x6b3f2a07, 0x1c2501a0, 0x010d1f04, // ro.sr.bs_322 mt.af.ceb_432 en.eu.id_322 cy.cs.en_332
+ // [1ea0]
+ 0x1e006804, 0x20006e05, 0x4a2827a0, 0x25002a12, // ig.ms.un_320 hmn.sq.un_330 gd.sw.yo_322 mt.eu.un_640
+ 0x082b0107, 0x13352108, 0x35005513, 0x0e000820, // en.vi.no_432 jw.zu.et_443 rw.zu.un_650 no.is.un_850
+ 0x0400011a, 0x29320ca0, 0x0b110707, 0x0c000e13, // en.fi.un_760 sv.bs.sl_322 it.ro.es_432 is.sv.un_650
+ 0x2d000a13, 0x211c0c07, 0x6e686b04, 0x0f6b02ec, // pt.sk.un_650 sv.id.jw_432 ceb.ig.hmn_332 da.ceb.lv_644
+ // [1eb0]
+ 0x3f0301a0, 0x4a535511, 0x4a0e2d07, 0x0a001022, // en.nl.af_322 rw.ht.yo_653 sk.is.yo_432 be.mk.un_870
+ 0x28524a09, 0x100f1304, 0x6b1827ad, 0x311e2dad, // yo.ha.sw_444 et.lv.lt_332 gd.ga.ceb_643 sk.ms.az_643
+ 0x27090804, 0x13003514, 0x1f3b2107, 0x6b6807a0, // no.pl.gd_332 zu.et.un_660 jw.so.cy_432 it.ig.ceb_322
+ 0x182a2d11, 0x06001a08, 0x55003b1b, 0x08020eec, // sk.mt.ga_653 tl.de.un_430 so.rw.un_770 is.da.no_644
+ // [1ec0]
+ 0x0c253b04, 0x0e0a0ca4, 0x530c6b02, 0x535213ad, // so.eu.sv_332 sv.pt.is_433 ceb.sv.ht_222 et.ha.ht_643
+ 0x29002d07, 0x27093b04, 0x271801af, 0x0a29230b, // sk.sl.un_420 so.pl.gd_332 en.ga.gd_655 ca.sl.pt_542
+ 0x0b002505, 0x6400530e, 0x1e53100c, 0x11081013, // eu.es.un_330 ht.lg.un_550 lt.ht.ms_543 be.uk.ro_665
+ 0x06021f07, 0x35001b0d, 0x190b11a4, 0x2a002908, // cy.da.de_432 tr.zu.un_540 ro.es.gl_433 sl.mt.un_430
+ // [1ed0]
+ 0x183552a0, 0x1e211c07, 0x013f2804, 0x101b2d07, // ha.zu.ga_322 id.jw.ms_432 sw.af.en_332 sk.tr.lt_432
+ 0x1c211f04, 0x1c002d08, 0x12002d0c, 0x1b685204, // cy.jw.id_332 sk.id.un_430 sk.hu.un_530 ha.ig.tr_332
+ 0x130f1004, 0x6b681aad, 0x091f1804, 0x2d00530c, // lt.lv.et_332 tl.ig.ceb_643 ga.cy.pl_332 ht.sk.un_530
+ 0x0e280607, 0x091355ee, 0x0b523b0c, 0x3f00030b, // de.sw.is_432 rw.et.pl_422 so.ha.es_543 nl.af.un_520
+ // [1ee0]
+ 0x553506a4, 0x01001f0c, 0x1f2d0d0e, 0x0c0f050e, // de.zu.rw_433 cy.en.un_530 cs.sk.cy_555 fr.lv.sv_555
+ 0x2a000e1a, 0x08021702, 0x18006414, 0x1e1c07ee, // is.mt.un_760 sr.da.no_222 lg.ga.un_660 it.id.ms_422
+ 0x213f0313, 0x2900100c, 0x321620ec, 0x3b001f12, // nl.af.jw_665 lt.sl.un_530 sq.hr.bs_644 cy.so.un_640
+ 0x08000104, 0x0e2a0c11, 0x20030107, 0x355513af, // en.no.un_320 sv.mt.is_653 en.nl.sq_432 et.rw.zu_655
+ // [1ef0]
+ 0x1a214a04, 0x0e003504, 0x08023107, 0x32290f07, // yo.jw.tl_332 zu.is.un_320 az.da.no_432 lv.sl.bs_432
+ 0x190b230e, 0x03002904, 0x090c530c, 0x011a5302, // ca.es.gl_555 sl.nl.un_320 ht.sv.pl_543 ht.tl.en_222
+ 0x113b0405, 0x250928a7, 0x0a05010c, 0x552a1b08, // fi.so.ro_333 sw.pl.eu_532 en.fr.pt_543 tr.mt.rw_443
+ 0x19005309, 0x02252805, 0x2700230c, 0x3f1129a0, // ht.gl.un_440 sw.eu.da_333 ca.gd.un_530 sl.ro.af_322
+ // [1f00]
+ 0x070f0a04, 0x2a0e110c, 0x23010e09, 0x27180407, // pt.lv.it_332 ro.is.mt_543 is.en.ca_444 fi.ga.gd_432
+ 0x1b002912, 0x080211a0, 0x20000512, 0x64203504, // sl.tr.un_640 ro.da.no_322 fr.sq.un_640 zu.sq.lg_332
+ 0x1e1c52ec, 0x2300030e, 0x092d0dee, 0x0a000e13, // ha.id.ms_644 nl.ca.un_550 cs.sk.pl_422 is.pt.un_650
+ 0x211c3bec, 0x2a006402, 0x25001107, 0x4a000414, // so.id.jw_644 lg.mt.un_220 ro.eu.un_420 fi.yo.un_660
+ // [1f10]
+ 0x1f050ca0, 0x3b002102, 0x643b1b07, 0x132d0855, // sv.fr.cy_322 jw.so.un_220 tr.so.lg_432 no.sk.et_442
+ 0x27003513, 0x12080eaf, 0x32642a04, 0x172d20ee, // zu.gd.un_650 is.no.hu_655 mt.lg.bs_332 sq.sk.sr_422
+ 0x052d2b12, 0x190b2da7, 0x2a210fee, 0x1e1c0c05, // vi.sk.fr_654 sk.es.gl_532 lv.jw.mt_422 sv.id.ms_333
+ 0x1c001a02, 0x2d0d0502, 0x06002d0e, 0x35001c02, // tl.id.un_220 fr.cs.sk_222 sk.de.un_550 id.zu.un_220
+ // [1f20]
+ 0x1f1827af, 0x25070908, 0x081c6ba0, 0x020806ad, // gd.ga.cy_655 pl.it.eu_443 ceb.id.no_322 de.no.da_643
+ 0x11002702, 0x29040d08, 0x130428a7, 0x08100a14, // gd.ro.un_220 cs.fi.sl_443 sw.fi.et_532 mk.be.uk_666
+ 0x06000104, 0x0a000604, 0x21000104, 0x52004a14, // en.de.un_320 de.pt.un_320 en.jw.un_320 yo.ha.un_660
+ 0x09131c12, 0x180127ec, 0x23006e19, 0x4a013502, // mr.bh.hi_654 gd.en.ga_644 hmn.ca.un_750 zu.en.yo_222
+ // [1f30]
+ 0x21002010, 0x1b2b250c, 0x101a040c, 0x07520ca0, // sq.jw.un_620 eu.vi.tr_543 fi.tl.lt_543 sv.ha.it_322
+ 0x23006e0c, 0x0b00250c, 0x02004a04, 0x3f001705, // hmn.ca.un_530 eu.es.un_530 yo.da.un_320 sr.af.un_330
+ 0x09133bee, 0x1f042014, 0x17164aa0, 0x07235207, // so.et.pl_422 sq.fi.cy_666 yo.hr.sr_322 ha.ca.it_432
+ 0x086e06ad, 0x04000104, 0x293f3504, 0x52001a23, // de.hmn.no_643 en.fi.un_320 zu.af.sl_332 tl.ha.un_880
+ // [1f40]
+ 0x231929ee, 0x11001704, 0x53212312, 0x13172004, // sl.gl.ca_422 sr.ro.un_320 ca.jw.ht_654 sq.sr.et_332
+ 0x3b353105, 0x02252a07, 0x17003b07, 0x3b002112, // az.zu.so_333 mt.eu.da_432 so.sr.un_420 jw.so.un_640
+ 0x6b001b12, 0x0d292d07, 0x52001c02, 0x07102d02, // tr.ceb.un_640 sk.sl.cs_432 id.ha.un_220 sk.lt.it_222
+ 0x08023fa0, 0x0c005202, 0x11133b07, 0x0c002d04, // af.da.no_322 ha.sv.un_220 so.et.ro_432 sk.sv.un_320
+ // [1f50]
+ 0x03000e12, 0x081031ee, 0x08001a04, 0x0f06120d, // is.nl.un_640 az.lt.no_422 tl.no.un_320 hu.de.lv_554
+ 0x2d000b07, 0x08020405, 0x07001018, 0x3b002a19, // es.sk.un_420 fi.da.no_333 be.bg.un_740 mt.so.un_750
+ 0x04000614, 0x20003b13, 0x08003205, 0x0c001905, // de.fi.un_660 so.sq.un_650 bs.no.un_330 gl.sv.un_330
+ 0x04035504, 0x1c0913a9, 0x0d2d1260, 0x182720ad, // rw.nl.fi_332 bh.hi.mr_544 hu.sk.cs_664 sq.gd.ga_643
+ // [1f60]
+ 0x0b002302, 0x31001c02, 0x06233fad, 0x090d010c, // ca.es.un_220 id.az.un_220 af.ca.de_643 en.cs.pl_543
+ 0x1c1e2b12, 0x062305a0, 0x0e1f6bee, 0x311b0807, // vi.ms.id_654 fr.ca.de_322 ceb.cy.is_422 no.tr.az_432
+ 0x0d2d09a7, 0x68002d07, 0x2d0d12ec, 0x1e2721a0, // pl.sk.cs_532 sk.ig.un_420 hu.cs.sk_644 jw.gd.ms_322
+ 0x21273bac, 0x20002b0c, 0x21181107, 0x01000c02, // so.gd.jw_632 vi.sq.un_530 ro.ga.jw_432 sv.en.un_220
+ // [1f70]
+ 0x2d0d1ba0, 0x29682dec, 0x3f001107, 0x08003b19, // tr.cs.sk_322 sk.ig.sl_644 ro.af.un_420 so.no.un_750
+ 0x122d0a04, 0x0b002a05, 0x23556407, 0x13000304, // pt.sk.hu_332 mt.es.un_330 lg.rw.ca_432 nl.et.un_320
+ 0x4a001908, 0x1103130c, 0x0a00080c, 0x0b003f11, // gl.yo.un_430 et.nl.ro_543 uk.mk.un_530 af.es.un_630
+ 0x32201702, 0x2d0d28a6, 0x12006b04, 0x171209ee, // sr.sq.bs_222 sw.cs.sk_521 ceb.hu.un_320 pl.hu.sr_422
+ // [1f80]
+ 0x0c6b25a0, 0x02002102, 0x1e1306ee, 0x13000e04, // eu.ceb.sv_322 jw.da.un_220 de.et.ms_422 is.et.un_320
+ 0x322813ad, 0x190b0a0c, 0x0c271802, 0x11000a1a, // et.sw.bs_643 pt.es.gl_543 ga.gd.sv_222 mk.ro.un_760
+ 0x283525a9, 0x0e081bad, 0x6864350c, 0x04000a0b, // eu.zu.sw_544 tr.no.is_643 zu.lg.ig_543 mk.ru.un_520
+ 0x072a1212, 0x52001704, 0x190b25a0, 0x2a0812ad, // hu.mt.it_654 sr.ha.un_320 eu.es.gl_322 hu.no.mt_643
+ // [1f90]
+ 0x27002105, 0x190527ad, 0x1f002b19, 0x35005207, // jw.gd.un_330 gd.fr.gl_643 vi.cy.un_750 ha.zu.un_420
+ 0x1e1c20ec, 0x0d0f5508, 0x55005212, 0x23001f18, // sq.id.ms_644 rw.lv.cs_443 ha.rw.un_640 cy.ca.un_740
+ 0x1100230c, 0x015255a0, 0x0e004a1a, 0x203b0607, // ca.ro.un_530 rw.ha.en_322 yo.is.un_760 de.so.sq_432
+ 0x1b000704, 0x25040604, 0x025364ee, 0x35296ba0, // it.tr.un_320 de.fi.eu_332 lg.ht.da_422 ceb.sl.zu_322
+ // [1fa0] --- double_langprob_start=1fac ---
+ 0x04093bee, 0x2d160d0d, 0x25006402, 0x31000f18, // so.pl.fi_422 cs.hr.sk_554 lg.eu.un_220 lv.az.un_740
+ 0x18003514, 0x011809a4, 0x01003f02, 0x042810a0, // zu.ga.un_660 pl.ga.en_433 af.en.un_220 lt.sw.fi_322
+ 0x1f1b3fa4, 0x0e133f04, 0x1c006b04, 0x1f1809a4, // af.tr.cy_433 af.et.is_332 ceb.id.un_320 pl.ga.cy_433
+ //
+ };
+
+// COMPILE_ASSERT(8108 <= 8192, k_indirectbits_too_small);
+
+extern const CLD2TableSummary kQuad_obj2 = {
+ kQuadChrome0122_16_2,
+ kQuadChrome0122_16_2Ind,
+ kQuadChrome0122_16_2SizeOne,
+ kQuadChrome0122_16_2Size,
+ kQuadChrome0122_16_2KeyMask,
+ kQuadChrome0122_16BuildDate,
+ kQuadChrome0122_16RecognizedLangScripts,
+};
+
+} // End namespace CLD2
+
+// End of generated tables
diff --git a/browser/components/translation/cld2/internal/cld2tablesummary.h b/browser/components/translation/cld2/internal/cld2tablesummary.h
new file mode 100644
index 000000000..a30bbb56f
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld2tablesummary.h
@@ -0,0 +1,55 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_CLD2TABLESUMMARY_H_
+#define I18N_ENCODINGS_CLD2_INTERNAL_CLD2TABLESUMMARY_H_
+
+#include "integral_types.h"
+
+namespace CLD2 {
+
+// Hash bucket for four-way associative lookup, indirect probabilities
+// 16 bytes per bucket, 4-byte entries
+typedef struct {
+ uint32 keyvalue[4]; // Upper part of word is hash, lower is indirect prob
+} IndirectProbBucket4;
+
+
+// Expanded version December 2012.
+// Moves cutoff for 6-language vs. 3-language indirects
+// Has list of recognized lang-script combinations
+typedef struct {
+ const IndirectProbBucket4* kCLDTable;
+ // Each bucket has four entries, part
+ // key and part indirect subscript
+ const uint32* kCLDTableInd; // Each entry is three packed lang/prob
+ uint32 kCLDTableSizeOne; // Indirect subscripts >= this: 2 entries
+ uint32 kCLDTableSize; // Bucket count
+ uint32 kCLDTableKeyMask; // Mask hash key
+ uint32 kCLDTableBuildDate; // yyyymmdd
+ const char* kRecognizedLangScripts; // Character string of lang-Scripts
+ // recognized: "en-Latn az-Arab ..."
+ // Single space delimiter, Random order
+} CLD2TableSummary;
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_CLD2TABLESUMMARY_H_
+
+
diff --git a/browser/components/translation/cld2/internal/cld_generated_cjk_delta_bi_4.cc b/browser/components/translation/cld2/internal/cld_generated_cjk_delta_bi_4.cc
new file mode 100644
index 000000000..6f6d732b2
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld_generated_cjk_delta_bi_4.cc
@@ -0,0 +1,1136 @@
+//
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// Created by postproc-cld2 4.0 on 2013-06-14 17:07:50
+// From command line:
+// --cld2 --cc --just_read_raw --delta_bi
+// --wrt=cld2_generated_unicjkchrome0614.bin --cjk --minchars=2
+// --mincount=2 --max_items_per_langscript=2000 --flatmap --rr_alloc
+// --freq_alloc --boostcloseweakerpercent=00 --indirectbits=12 --thresh=224
+// --v25 --kentries=4 --tablename=CjkDeltaBi --remap=xxx-Latn=>ut-Latn
+// sh-Latn=>hr-Latn sh-Cyrl=>sr-Cyrl nn-Latn=>no-Latn mo-Cyrl=>ro-Cyrl
+// --include=af-Latn ar-Arab be-Cyrl bg-Cyrl ca-Latn cs-Latn cy-Latn
+// da-Latn de-Latn el-Grek en-Latn es-Latn et-Latn fa-Arab fi-Latn fr-Latn
+// ga-Latn gd-Latn hi-Deva hr-Latn hu-Latn id-Latn is-Latn it-Latn
+// iw-Hebr ja-Hani ko-Hani lg-Latn lt-Latn lv-Latn mk-Cyrl ms-Latn
+// nl-Latn no-Latn pl-Latn pt-Latn ro-Latn ro-Cyrl ru-Cyrl rw-Latn
+// sh-Cyrl sh-Latn sk-Latn sl-Latn sr-Cyrl sv-Latn sw-Latn th-Thai
+// tl-Latn tr-Latn uk-Cyrl vi-Latn yi-Hebr zh-Hani zh-TW zh-Hant
+// sq-Latn az-Latn eu-Latn bn-Beng gl-Latn ht-Latn mt-Latn sr-Latn ur-Arab
+// bh-Deva mr-Deva ne-Deva lg-Latn rw-Latn gd-Latn ut-Latn ut-Deva
+// tlh-Latn ceb-Latn blu-Latn jw-Latn --ko_english --force_to_lang_soft
+// --nosoft_cram2 --nomsidlevel --shapeflatprob --langpriorpercent=10
+// --skipnuc --noshapeforcetop --noshapeeventop --noshapesteep2 --spread=15
+// --nodoubleclose --langcounts --writebin --list_items=120
+// i18n/encodings/cld2/prob_data/vetted_bigram_prob_20130614_sort.utf8
+//
+
+#include "cld2tablesummary.h"
+namespace CLD2 {
+
+static const uint32 kCjkDeltaBiBuildDate = 20130614; // yyyymmdd
+
+
+// Of 2674 offered items into 4096 table entries:
+// 2466 filled (92%), 0 merged (0%), 208 dropped (7%)
+
+// Nil-grams: 19 languages
+// GREEK MALAYALAM TELUGU TAMIL GUJARATI THAI KANNADA PUNJABI
+// GEORGIAN SINHALESE ARMENIAN LAOTHIAN KHMER DHIVEHI CHEROKEE
+// SYRIAC LIMBU ORIYA INUKTITUT
+
+// Uni-grams: 4 languages
+// Japanese Korean Chinese ChineseT
+
+// Words/Quads: 4 languages in range Japanese..ChineseT:
+//
+
+// Japanese 524
+// Korean 10
+// Chinese 748
+// ChineseT 1184
+
+
+
+// Recognized language-script combinations [4]:
+static const char* const kCjkDeltaBiRecognizedLangScripts =
+ "ja-Hani ko-Hani zh-Hani zh-Hant ";
+
+static const uint32 kCjkDeltaBiSize = 1024; // Bucket count
+static const uint32 kCjkDeltaBiKeyMask = 0xfffff000; // Mask hash key
+
+static const IndirectProbBucket4 kCjkDeltaBi[kCjkDeltaBiSize] = {
+ // hash_indirect[4], tokens[4] in UTF-8
+ {{0xf945f002,0xf93b1003,0x00000000,0x00000000}}, // [000] 手工, も同, --, --,
+ {{0xf829b004,0x00000000,0x00000000,0x00000000}}, // 制作, --, --, --,
+ {{0xfb731005,0x00000000,0x00000000,0x00000000}}, // 瑕疵, --, --, --,
+ {{0xfb83e006,0xf4afd002,0xf8354003,0xf913a004}}, // 佈置, 店面, 能人, 导小,
+ {{0xf9268007,0x00000000,0x00000000,0x00000000}}, // 部分, --, --, --,
+ {{0xfb5c8006,0xf813f003,0x00000000,0x00000000}}, // 入社, 単位, --, --,
+ {{0xfa4c1007,0xfb4d4006,0xf5a6c002,0x00000000}}, // 科技, 寵物, 對象, --,
+ {{0xfa77d006,0xf8256008,0xfa7c0003,0xf8404004}}, // 主治, 國中, 値段, 中介,
+ {{0xfa4b8009,0xf91df006,0xf923b00a,0xf944c00b}}, // 材料, 員名, 中共, 之外,
+ {{0xf597c003,0x00000000,0x00000000,0x00000000}}, // 機能, --, --, --,
+ {{0xfb72800c,0xf937c009,0xf90ff00a,0xf9414006}}, // 投稿, 办公, 共和, 指引,
+ {{0xf92ca00d,0x00000000,0x00000000,0x00000000}}, // 姑娘, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf93d2005,0xf5b7d003,0xf928b002,0x00000000}}, // 仁哲, へ行, 未分, --,
+ {{0xf4bd300a,0xf8227006,0xfb7e700a,0xf93c800a}}, // 的通, 有何, 的第, 一家,
+ {{0xfa589004,0xf933c004,0x00000000,0x00000000}}, // 农村, 力度, --, --,
+ {{0xf917d008,0x00000000,0x00000000,0x00000000}}, // [010] 覺得, --, --, --,
+ {{0xf4a85006,0x00000000,0x00000000,0x00000000}}, // 質量, --, --, --,
+ {{0xf92db007,0xf929d00d,0x00000000,0x00000000}}, // 打印, 个字, --, --,
+ {{0xf940f006,0xfa4f7006,0x00000000,0x00000000}}, // 則回, 引擎, --, --,
+ {{0xf5c5e003,0xfb7f5006,0x00000000,0x00000000}}, // の解, 項目, --, --,
+ {{0xfa6e3006,0xf927a00e,0xfa85500a,0xf4ae9003}}, // 右手, 師傅, 物流, 外部,
+ {{0xf9127004,0xf93bc008,0x00000000,0x00000000}}, // 深入, 時候, --, --,
+ {{0xf9378003,0xfb66200a,0xf5b4b003,0xfb6f700b}}, // 九州, 全省, 落解, 策略,
+ {{0xfa7eb002,0xf9382006,0xf9390006,0x00000000}}, // 內政, 舟山, 一名, --,
+ {{0xf91be00f,0xf9456010,0xf82a6004,0x00000000}}, // 产品, 今回, 华人, --,
+ {{0xfa586006,0xf9410010,0xf80e800a,0xfa4c4006}}, // 依本, 有名, 民主, 我最,
+ {{0xf93cd002,0x00000000,0x00000000,0x00000000}}, // 億元, --, --, --,
+ {{0xf938d003,0xfb6ed006,0xfa76e00b,0xf81f4006}}, // 輝度, 斗神, 人文, 的使,
+ {{0xfb526004,0x00000000,0x00000000,0x00000000}}, // 政策, --, --, --,
+ {{0xf80ca006,0xfa5cf011,0x00000000,0x00000000}}, // 殖事, 省政, --, --,
+ {{0xf4bfb003,0xf9187004,0xf939d012,0xfa5da006}}, // 病院, 出口, 最初, 堅持,
+ {{0xf920500e,0xf9474010,0x00000000,0x00000000}}, // [020] 廢墟, 富士, --, --,
+ {{0xf9339006,0xf92dd006,0xfa49d003,0xfb714013}}, // 務必, 取回, を教, 純粹,
+ {{0xf5a77007,0xf90fe002,0x00000000,0x00000000}}, // 不能, 史建, --, --,
+ {{0xf93fb00a,0xf9432006,0xf82f200b,0x00000000}}, // 的影, 之前, 到一, --,
+ {{0xf4aca00a,0xfa66c00d,0x00000000,0x00000000}}, // 打造, 律援, --, --,
+ {{0xfa4cf004,0xfa5ed006,0x00000000,0x00000000}}, // 成本, 的武, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf917f006,0xf592900b,0x00000000,0x00000000}}, // 司名, 定要, --, --,
+ {{0xfb864009,0xfa78c00a,0xfa62e00b,0x00000000}}, // 位置, 交流, 按摩, --,
+ {{0xf5c5e014,0x00000000,0x00000000,0x00000000}}, // 可能, --, --, --,
+ {{0xf83a5015,0xfa5ee004,0xfa844003,0xfa663006}}, // 工作, 规模, 目指, 看板,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf9138004,0xf92d2011,0xf819c00b,0xf80eb002}}, // 国土, 郑州, 十三, 著作,
+ {{0xf8237003,0x00000000,0x00000000,0x00000000}}, // る一, --, --, --,
+ {{0xfa61f00a,0x00000000,0x00000000,0x00000000}}, // 三星, --, --, --,
+ {{0xf4a23002,0x00000000,0x00000000,0x00000000}}, // [030] 業部, --, --, --,
+ {{0xf4bee003,0xf9480003,0xfb5a3004,0x00000000}}, // 掃除, 崎市, 油田, --,
+ {{0xffef1010,0xfa79600a,0xf81b0005,0x00000000}}, // パソ, 司法, 十九, --,
+ {{0xf946c008,0xf4c3200a,0x00000000,0x00000000}}, // 國家, 下降, --, --,
+ {{0xf5ca2006,0xf815d003,0x00000000,0x00000000}}, // 優良, マ一, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa4be00a,0xfb6a3003,0xf839e00a,0x00000000}}, // 成果, を生, 方便, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf4b2c011,0x00000000,0x00000000,0x00000000}}, // 这里, --, --, --,
+ {{0xf942b007,0xf5ba9003,0x00000000,0x00000000}}, // 安全, て行, --, --,
+ {{0xfa5c7004,0x00000000,0x00000000,0x00000000}}, // 权所, --, --, --,
+ {{0xf925a00d,0x00000000,0x00000000,0x00000000}}, // 周刊, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb6b2004,0x00000000,0x00000000,0x00000000}}, // 隐私, --, --, --,
+ {{0xfa7e0004,0x00000000,0x00000000,0x00000000}}, // 满意, --, --, --,
+ {{0xf947e009,0xfb608002,0xf926b003,0x00000000}}, // 今年, 灣省, の好, --,
+ {{0xf48ef011,0x00000000,0x00000000,0x00000000}}, // [040] 数量, --, --, --,
+ {{0xf947e003,0xf4bdc003,0xf59f600d,0x00000000}}, // が大, 鉄道, 的落, --,
+ {{0xf90f4003,0x00000000,0x00000000,0x00000000}}, // 地元, --, --, --,
+ {{0xf842c003,0xf82d0002,0xfb7ca006,0x00000000}}, // は不, 網上, 裁示, --,
+ {{0xf6d22016,0xf9474006,0xf91c3003,0xf59ca002}}, // 티벳, 前出, 大分, 還要,
+ {{0xfa870003,0xf8256006,0xf823500b,0x00000000}}, // の新, 行事, 之一, --,
+ {{0xf945f003,0xfb64800b,0x00000000,0x00000000}}, // が分, 聯繫, --, --,
+ {{0xf5c0d00a,0xfb4d1006,0xf5c0a00d,0x00000000}}, // 患者, 署立, 语言, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf93bf002,0xf90b6004,0xf92cc004,0x00000000}}, // 時尚, 获得, 青年, --,
+ {{0xfa5db009,0xfb76b006,0x00000000,0x00000000}}, // 资源, 衝突, --, --,
+ {{0xf5bbb003,0xfa67000e,0xfa764013,0xfb67d006}}, // で行, 後悔, 活潑, 貨物,
+ {{0xfb6e6006,0xf90fd00d,0xfa79200e,0x00000000}}, // 聖火, 讲座, 遺憾, --,
+ {{0xf58d3009,0xf80ba009,0x00000000,0x00000000}}, // 或者, 网上, --, --,
+ {{0xfa579010,0xf9386003,0xfa5ce00b,0x00000000}}, // た方, 検定, 的故, --,
+ {{0xf942f017,0xf913c002,0xf5b95004,0x00000000}}, // 奈川, 發布, 对象, --,
+ {{0xfa661006,0xf93fb00a,0xf8441002,0x00000000}}, // [050] 個方, 各地, 萬人, --,
+ {{0xf5a7d013,0xf843a003,0xf9276002,0x00000000}}, // 和諧, の不, 推展, --,
+ {{0xf58c000a,0xfb7f4006,0xfb581006,0xf93db00b}}, // 金融, 會社, 人物, 心得,
+ {{0xf915200a,0xf9084006,0xf928a006,0x00000000}}, // 施工, 搶先, 未必, --,
+ {{0xf498d007,0xfa8ad007,0xf93a4006,0xf9262006}}, // 改革, 自治, 府出, 輩子,
+ {{0xf91a1009,0xf48a5002,0xf945d006,0xf492800a}}, // 许可, 嚴重, 國古, 政部,
+ {{0xf4af000a,0x00000000,0x00000000,0x00000000}}, // 街道, --, --, --,
+ {{0xf9382007,0xf9214003,0xf91e100a,0xf93fb006}}, // 土地, 設定, 模式, 的山,
+ {{0xf4bad003,0xfb4a500e,0xf90d5006,0xf9455002}}, // 一部, 收穫, 歷年, 認定,
+ {{0xf9447018,0x00000000,0x00000000,0x00000000}}, // 名前, --, --, --,
+ {{0xf80ca003,0x00000000,0x00000000,0x00000000}}, // 佐世, --, --, --,
+ {{0xfa80a006,0x00000000,0x00000000,0x00000000}}, // 絡方, --, --, --,
+ {{0xf4c62003,0xfb82f008,0xfa6ac002,0x00000000}}, // が高, 先生, 長期, --,
+ {{0xfb5b4003,0xfa54c011,0x00000000,0x00000000}}, // 処理, 业执, --, --,
+ {{0xf9182004,0x00000000,0x00000000,0x00000000}}, // 顺利, --, --, --,
+ {{0xf9436008,0xfb712014,0x00000000,0x00000000}}, // 國小, 引用, --, --,
+ {{0xf844e004,0x00000000,0x00000000,0x00000000}}, // [060] 息中, --, --, --,
+ {{0xf93fe006,0xf913900a,0xf5c66006,0x00000000}}, // 來回, 精彩, 用者, --,
+ {{0xf8115009,0xfa721003,0xf493d003,0x00000000}}, // 进一, 社概, 都道, --,
+ {{0xf8393007,0xf91b500a,0xfa539006,0x00000000}}, // 以下, 健全, 香料, --,
+ {{0xf90ff006,0xfb838006,0xf947700b,0x00000000}}, // 新回, 轉移, 不好, --,
+ {{0xf4a1500a,0xf82c2006,0x00000000,0x00000000}}, // 公里, 長信, --, --,
+ {{0xfb7d700a,0xf5c6e003,0xf815e006,0x00000000}}, // 的生, の色, 站使, --,
+ {{0xf5c4e013,0xf93b6006,0x00000000,0x00000000}}, // 天蠍, 要引, --, --,
+ {{0xf5c61010,0xf919800a,0xf9336006,0xfa650006}}, // と言, 常委, 肚子, 請注,
+ {{0xfa77c010,0xf5bd9003,0xfa6e7006,0xf49da019}}, // 選手, で表, 農村, 纠错,
+ {{0xf9497004,0x00000000,0x00000000,0x00000000}}, // 经常, --, --, --,
+ {{0xf94af012,0xf91b200b,0x00000000,0x00000000}}, // 美味, 接受, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa62000d,0x00000000,0x00000000,0x00000000}}, // 过渡, --, --, --,
+ {{0xf9240003,0xf9482006,0x00000000,0x00000000}}, // の心, 而出, --, --,
+ {{0xfb86d006,0xfb51a005,0xf824e00d,0xfb7a4006}}, // 刊登, 新竹, 不久, 送私,
+ {{0xfa647002,0xf9118007,0xfa5c700b,0x00000000}}, // [070] 國民, 平台, 市民, --,
+ {{0xf8178003,0xf80fe004,0xf90f0006,0xfb66f004}}, // た人, 南京, 河川, 邮箱,
+ {{0xf93f0007,0xfa5e900a,0x00000000,0x00000000}}, // 的工, 居民, --, --,
+ {{0xf845c003,0xf924a004,0xf92e200a,0xf5ae6006}}, // の中, 确定, 南市, 腳踏,
+ {{0xf9214002,0x00000000,0x00000000,0x00000000}}, // 廣州, --, --, --,
+ {{0xf844b004,0xfa67b006,0x00000000,0x00000000}}, // 确保, 意思, --, --,
+ {{0xf938c002,0xfa63f006,0x00000000,0x00000000}}, // 觀光, 版本, --, --,
+ {{0xf8449003,0xfb6f4006,0xf93ee00b,0x00000000}}, // の保, 金石, 概念, --,
+ {{0xfb7d200a,0x00000000,0x00000000,0x00000000}}, // 组织, --, --, --,
+ {{0xf91f9011,0xf916f006,0xf9168006,0xf939200b}}, // 加工, 改名, 電子, 回家,
+ {{0xf9122010,0xf948f006,0x00000000,0x00000000}}, // 山口, 美少, --, --,
+ {{0xf9382006,0xf9364003,0xf9172002,0xf845701a}}, // 攝取, 仙台, 確定, 可以,
+ {{0xf48f0009,0xf8164006,0xf9327003,0xf941e00b}}, // 干部, 條例, 婚式, 有多,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf8131004,0xf93db003,0xfb666010,0xf90e9002}}, // 这一, も大, の空, 進展,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb623010,0xfa89701b,0xf935c006,0xf5cb400b}}, // [080] 面白, 木柵, 點名, 只能,
+ {{0xf9143006,0x00000000,0x00000000,0x00000000}}, // 覽器, --, --, --,
+ {{0xfa65f013,0xf9193003,0xf942d002,0x00000000}}, // 對抗, 日光, 下午, --,
+ {{0xf8470006,0xf947500b,0x00000000,0x00000000}}, // 師事, 完全, --, --,
+ {{0xfa62f008,0xf939d002,0x00000000,0x00000000}}, // 個月, 攝影, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb798009,0xf808f003,0xf9189004,0xf843a003}}, // 搜索, を上, 对外, の位,
+ {{0xf9164006,0xf9126012,0xf4a7b00a,0xf9304002}}, // 主回, 印刷, 全部, 協助,
+ {{0xfa7fd004,0xf9146002,0xfb6cb014,0x00000000}}, // 档案, 社工, 世界, --,
+ {{0xf93f400b,0xf825800b,0x00000000,0x00000000}}, // 垃圾, 同一, --, --,
+ {{0xf9189008,0x00000000,0x00000000,0x00000000}}, // 電影, --, --, --,
+ {{0xf814800a,0xf90d1005,0xfa7cd006,0xf9128006}}, // 通信, 網友, 性感, 新年,
+ {{0xf9358004,0xfa6a6005,0x00000000,0x00000000}}, // 蒙古, 長沙, --, --,
+ {{0xf945f010,0x00000000,0x00000000,0x00000000}}, // が必, --, --, --,
+ {{0xf499400a,0x00000000,0x00000000,0x00000000}}, // 改造, --, --, --,
+ {{0xfb6e600a,0xfb55d014,0xfa604006,0xfb515006}}, // 受理, 出版, 資料, 平米,
+ {{0xf837e003,0xfb528006,0x00000000,0x00000000}}, // [090] 主人, 就知, --, --,
+ {{0xfb87f002,0x00000000,0x00000000,0x00000000}}, // 美秀, --, --, --,
+ {{0xfb6d700a,0xfa4da00d,0xfa5f0002,0x00000000}}, // 科研, 字母, 的日, --,
+ {{0xf822d007,0xf9405010,0xf93c9002,0xfb5bb00d}}, // 合作, 理士, 紀念, 欧美,
+ {{0xfb65301b,0xfb5d5003,0xf92b801a,0xfb6bb014}}, // 修繕, 戦略, 自己, 自然,
+ {{0xf8321018,0xf92bb00d,0xfb871006,0x00000000}}, // 東京, 网友, 認知, --,
+ {{0xf940e006,0xfa76d00b,0xfa70b00b,0x00000000}}, // 三名, 特殊, 英文, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa785003,0xf90b7004,0xfa872006,0x00000000}}, // 空港, 年初, 器材, --,
+ {{0xf914300a,0xfb7e6006,0xf943c006,0x00000000}}, // 房地, 棄物, 證券, --,
+ {{0xf4a4c004,0xf90a8006,0xf9429006,0x00000000}}, // 粮食, 年前, 指出, --,
+ {{0xfb6b1010,0x00000000,0x00000000,0x00000000}}, // を目, --, --, --,
+ {{0xf92ec006,0xfb6ce00b,0x00000000,0x00000000}}, // 傑出, 民生, --, --,
+ {{0xf9327006,0xfb7d0003,0xfa68b00b,0x00000000}}, // 君子, 専用, 明星, --,
+ {{0xfa707010,0xfb873004,0xf81b9014,0x00000000}}, // 音波, 经理, 所以, --,
+ {{0xfa58e006,0xf9272003,0xf911900b,0x00000000}}, // 一手, の家, 便宜, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [0a0] --, --, --, --,
+ {{0xfa496018,0x00000000,0x00000000,0x00000000}}, // を持, --, --, --,
+ {{0xf9299003,0xf837a006,0x00000000,0x00000000}}, // に加, 為何, --, --,
+ {{0xfa5f2004,0xf81af006,0xf90e0006,0xf93d500a}}, // 的方, 時事, 更年, 市公,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf941c007,0xf90f2003,0x00000000,0x00000000}}, // 合同, 田市, --, --,
+ {{0xf913000a,0x00000000,0x00000000,0x00000000}}, // 深圳, --, --, --,
+ {{0xf4c2401c,0xf91f2002,0x00000000,0x00000000}}, // 有限, 內地, --, --,
+ {{0xf82a0002,0xfb749005,0x00000000,0x00000000}}, // 康促, 烘焙, --, --,
+ {{0xf923a010,0xf9462003,0x00000000,0x00000000}}, // の名, 不快, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf944e002,0xfa86e006,0xf9376006,0x00000000}}, // 國外, 溫泉, 高山, --,
+ {{0xf5a94006,0xf5a6f006,0xf929e010,0xfa6ba019}}, // 件者, 式脂, を忘, 晶显,
+ {{0xf8418007,0xfa63e002,0xf5b3d006,0xf5bd7003}}, // 第二, 國文, 滿足, で自,
+ {{0xf91e0006,0xfb889004,0xfa78a00b,0xfb8ab006}}, // 婦女, 栏目, 人次, 明白,
+ {{0xfb74c009,0xfa68f003,0xf91b800a,0xf942d006}}, // 通知, 家族, 大型, 月初,
+ {{0xfa5e3007,0xfa663002,0xf942e00a,0xf9125006}}, // [0b0] 市政, 國政, 有利, 簽名,
+ {{0x0006e013,0xf8483003,0xf82c7004,0x00000000}}, // ㄏㄏ, に保, 维修, --,
+ {{0xf91a7004,0xf9323004,0xf4bc6006,0xfb740006}}, // 符合, 投入, 考量, 博物,
+ {{0xfb5e801c,0xfa562006,0xf5c8f01a,0x00000000}}, // 管理, 記本, 部落, --,
+ {{0xf5c54018,0xfb86c006,0x00000000,0x00000000}}, // の著, 告知, --, --,
+ {{0xf945f007,0xf910c00b,0x00000000,0x00000000}}, // 不得, 避免, --, --,
+ {{0xf8401006,0xfb6e1011,0xf492a00e,0x00000000}}, // 監事, 北省, 能量, --,
+ {{0xf5b6d006,0xf91c500d,0xfa63a00b,0x00000000}}, // 曼谷, 书店, 事故, --,
+ {{0xfa781003,0xfa869014,0xfb57d00b,0x00000000}}, // 効果, 全文, 書籍, --,
+ {{0xf9136006,0x00000000,0x00000000,0x00000000}}, // 放器, --, --, --,
+ {{0xfa88d002,0xfa5e9006,0xfa665014,0x00000000}}, // 全政, 謄本, 希望, --,
+ {{0xf935b002,0xf81d6002,0x00000000,0x00000000}}, // 務工, 時代, --, --,
+ {{0xf8149006,0xf4a21006,0xf9439002,0xf83cb00b}}, // 啟事, 班途, 開心, 加上,
+ {{0xfb57e003,0xfa579004,0x00000000,0x00000000}}, // 攻略, 变更, --, --,
+ {{0xfb578007,0xf496b005,0xfb591008,0xf5949006}}, // 主管, 基隆, 學生, 記者,
+ {{0xfa6c4002,0xfa745002,0x00000000,0x00000000}}, // 男性, 調整, --, --,
+ {{0xf9470006,0xf812d00b,0x00000000,0x00000000}}, // [0c0] 納入, 留下, --, --,
+ {{0xfb5fa006,0xfb864006,0xf5b35002,0x00000000}}, // 你知, 國社, 發行, --,
+ {{0xf90cf014,0xf91c500b,0xfa821006,0x00000000}}, // 年度, 另外, 面板, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf9129002,0xf8213014,0x00000000,0x00000000}}, // 總局, 有一, --, --,
+ {{0xf4a04009,0xfb832006,0xfb7b2006,0xf843100a}}, // 务院, 國立, 市立, 第五,
+ {{0xf4b75007,0xf929c009,0xf90c500a,0xfa734011}}, // 保障, 结合, 水利, 政执,
+ {{0xfa5f3010,0xf93f2010,0xf9151003,0xf490800d}}, // い方, い出, 現地, 英雄,
+ {{0xf93b7003,0xf9288002,0xf5c39003,0xf9415005}}, // 一度, 木工, の行, 轉寄,
+ {{0xf91f500a,0xf92d0004,0xf917f005,0x00000000}}, // 形式, 监察, 人士, --,
+ {{0xfa526011,0xf923c003,0xfb537004,0xfa88d003}}, // 坚持, の小, 儿童, に比,
+ {{0xf9162010,0xfa71c004,0xf5992014,0xf90f6006}}, // 購入, 长期, 需要, 女子,
+ {{0xf4a61004,0xf9323011,0x00000000,0x00000000}}, // 质量, 业和, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa53a004,0xf841c014,0xf83e9006,0x00000000}}, // 进性, 第一, 大使, --,
+ {{0xf9195009,0xfb6f0006,0xf497f006,0xfa869006}}, // [0d0] 报告, 取締, 數量, 風情,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf5ca5010,0x00000000,0x00000000,0x00000000}}, // に追, --, --, --,
+ {{0xf818e004,0xf923b006,0x00000000,0x00000000}}, // 简介, 中山, --, --,
+ {{0xfa554004,0x00000000,0x00000000,0x00000000}}, // 这次, --, --, --,
+ {{0xf83bf003,0xf8442002,0x00000000,0x00000000}}, // で一, 本人, --, --,
+ {{0xf820b00a,0xfb870006,0xf9461005,0xf93a9005}}, // 集中, 不知, 名字, 老婆,
+ {{0xfa57600b,0x00000000,0x00000000,0x00000000}}, // 生日, --, --, --,
+ {{0xfa6d0007,0x00000000,0x00000000,0x00000000}}, // 直接, --, --, --,
+ {{0xf945b003,0xf938d006,0xfa52d006,0x00000000}}, // 立大, 門前, 雙手, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa8a5006,0xfb6ae00b,0x00000000,0x00000000}}, // 繪本, 航空, --, --,
+ {{0xf938d009,0xf8128009,0xf910500b,0xfa581006}}, // 开始, 给予, 即可, 機械,
+ {{0xfa66d003,0xf5b52008,0xf937b002,0xfa7e200b}}, // 住所, 發表, 關心, 造成,
+ {{0xf845b00d,0xf91ae00b,0x00000000,0x00000000}}, // 在今, 常常, --, --,
+ {{0xf93a800b,0xfa5c9006,0x00000000,0x00000000}}, // [0e0] 所得, 的最, --, --,
+ {{0xfa5a200d,0x00000000,0x00000000,0x00000000}}, // 作日, --, --, --,
+ {{0xf92ae002,0x00000000,0x00000000,0x00000000}}, // 豐富, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa8b4004,0xf93e000a,0xf9400006,0x00000000}}, // 措施, 市建, 的年, --,
+ {{0xfa713006,0xf9385002,0xf4bd7006,0xf921b002}}, // 傳承, 勞工, 宅配, 萬元,
+ {{0xf923e003,0xfa75100d,0xf59f000d,0x00000000}}, // 宮城, 导演, 的若, --,
+ {{0xf5c57003,0xf490e003,0xfb7d7006,0x00000000}}, // の表, 共通, 還真, --,
+ {{0xf90de003,0xf9465006,0xf82f1003,0x00000000}}, // 観光, 服器, グ一, --,
+ {{0xf4a57003,0xf90fe006,0x00000000,0x00000000}}, // の部, 進出, --, --,
+ {{0xfb5bd009,0xf92ac003,0xf9234012,0x00000000}}, // 学生, を受, 趣味, --,
+ {{0xf92f3007,0xfa5be002,0xf81f2003,0xf930300a}}, // 法律, 區政, い人, 法定,
+ {{0xfa756006,0xf944b00b,0x00000000,0x00000000}}, // 於本, 案工, --, --,
+ {{0xf941600a,0xf80bc003,0xf5b61003,0xf4c04011}}, // 指南, 向上, 発行, 过错,
+ {{0xf9379006,0xfa759008,0x00000000,0x00000000}}, // 凝土, 落格, --, --,
+ {{0xfa4c1003,0x00000000,0x00000000,0x00000000}}, // 近所, --, --, --,
+ {{0xf4ca0011,0x00000000,0x00000000,0x00000000}}, // [0f0] 透露, --, --, --,
+ {{0xfa775006,0xf5b15004,0x00000000,0x00000000}}, // 人感, 现象, --, --,
+ {{0xf90b4003,0xf93a3004,0xfa52a00b,0x00000000}}, // 海外, 作出, 教授, --,
+ {{0xf80f0003,0x00000000,0x00000000,0x00000000}}, // 世代, --, --, --,
+ {{0xf80c3002,0xf926300a,0x00000000,0x00000000}}, // 們一, 全市, --, --,
+ {{0xf921d007,0xf814f006,0xfb7ec00a,0x00000000}}, // 第十, 原住, 的管, --,
+ {{0xf59e8006,0xf9287003,0xfb7b0006,0x00000000}}, // 來越, に向, 節目, --,
+ {{0xfb7f5012,0xfa497003,0xf90ee006,0x00000000}}, // 装置, を求, 地名, --,
+ {{0xfa783009,0xf83a9006,0xf936d006,0xfb56f005}}, // 基本, 辦事, 高出, 麻煩,
+ {{0xf93bf003,0xf49ae004,0xfa696002,0xfa5e2006}}, // 広告, 报道, 澎湖, 的木,
+ {{0xfa86a003,0xf5b2c00a,0xf9356003,0xf49f8004}}, // 注文, 山西, た商, 删除,
+ {{0xf9326002,0xf9098006,0x00000000,0x00000000}}, // 招募, 當初, --, --,
+ {{0xf91ac006,0xfa716011,0x00000000,0x00000000}}, // 學出, 长沙, --, --,
+ {{0xf707801d,0xf9249003,0xf4ae700a,0xf9089006}}, // 훌륭, は全, 抗震, 當前,
+ {{0xf814e004,0xf9392002,0xfa7bf00b,0x00000000}}, // 会主, 門市, 大概, --,
+ {{0xf9454003,0xf83ae00b,0x00000000,0x00000000}}, // 同和, 另一, --, --,
+ {{0xf93d7004,0xf9207003,0xf921c004,0xf499500b}}, // [100] 的原, ち度, 代化, 人都,
+ {{0xfb819004,0xf90fb00d,0x00000000,0x00000000}}, // 采用, 河北, --, --,
+ {{0xfa83e003,0xf8442008,0xf93fb006,0x00000000}}, // の流, 責任, 情形, --,
+ {{0xfa5ef006,0xf5c73003,0xf90b4009,0x00000000}}, // 了最, に行, 收入, --,
+ {{0xf93f4006,0xf5bb3004,0x00000000,0x00000000}}, // 寄出, 来自, --, --,
+ {{0xf58c400d,0xfa840002,0xf59ca00b,0xf9368006}}, // 发言, 華民, 的老, 功夫,
+ {{0xf8353004,0xf49a4002,0xf5a7900a,0xfb7c6006}}, // 国人, 學院, 才能, 會福,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb8a5006,0xfa66d006,0xf934e00b,0x00000000}}, // 李登, 緊急, 察局, --,
+ {{0xfb83c002,0xf81c6010,0xf82a501a,0x00000000}}, // 專用, 送信, 其他, --,
+ {{0xf5b7e003,0x00000000,0x00000000,0x00000000}}, // 発表, --, --, --,
+ {{0xf8232010,0xfa840006,0xf9257003,0xf9248003}}, // る事, 喬治, の全, の多,
+ {{0xf93ae013,0xf4c58003,0xfb6ca002,0x00000000}}, // 最夯, お酒, 預算, --,
+ {{0xfb895002,0xf921d00a,0xfa5c0004,0xfa85a003}}, // 族群, 京市, 开放, の支,
+ {{0xf5a43005,0xfb656006,0xf931800b,0x00000000}}, // 興趣, 顯示, 四十, --,
+ {{0xf815c002,0xf925b003,0xfa5d4004,0xfa5c900b}}, // 務人, の公, 的思, 一段,
+ {{0xf93b0006,0xfb628008,0x00000000,0x00000000}}, // [110] 要回, 監督, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf5bc1005,0xf90fb00a,0xfa860004,0x00000000}}, // 實踐, 河南, 注明, --,
+ {{0xf91f5006,0xf81a000b,0x00000000,0x00000000}}, // 產品, 十二, --, --,
+ {{0xfa65700a,0xf5c1a00a,0xf9388014,0xf93b600b}}, // 同意, 形象, 非常, 最大,
+ {{0xf4a1c006,0xf81a300a,0xf9136006,0x00000000}}, // 公頃, 作人, 爾夫, --,
+ {{0xfa76f004,0x00000000,0x00000000,0x00000000}}, // 养殖, --, --, --,
+ {{0xf9254003,0xf5a02006,0xf9361006,0xf9146006}}, // の外, 資者, 原子, 找出,
+ {{0xf92c7009,0xf5bb0003,0xfb50f006,0xf83cc003}}, // 发布, 芸能, 想知, で作,
+ {{0xf4992007,0xf4ad4004,0xf910600b,0x00000000}}, // 方面, 频道, 到底, --,
+ {{0xf5901006,0xf4b56010,0xfb867003,0xfa845003}}, // 動者, 募集, が生, の文,
+ {{0xf93f100a,0xfb82d002,0xf93c600b,0x00000000}}, // 的基, 應用, 七十, --,
+ {{0xf92e500a,0xfb7f6006,0xfa643004,0x00000000}}, // 湖南, 稅目, 克思, --,
+ {{0xfb4ff010,0xf9481011,0xf5c47003,0xf4c51011}}, // 掲示, 职工, は自, 不错,
+ {{0xfb60b006,0xfb545002,0xf9240006,0x00000000}}, // 模特, 社群, 禮品, --,
+ {{0xfa7a8006,0xfa5f5004,0x00000000,0x00000000}}, // 核武, 备案, --, --,
+ {{0xfa7ac009,0xf814d004,0xf93fd004,0x00000000}}, // [120] 来源, 统一, 领域, --,
+ {{0xf822f006,0xf935300a,0xf5c91003,0x00000000}}, // 之事, 做好, に表, --,
+ {{0xf9118003,0x00000000,0x00000000,0x00000000}}, // 放同, --, --, --,
+ {{0xf936b006,0xfa4c9002,0x00000000,0x00000000}}, // 藝品, 恐怖, --, --,
+ {{0xfa4de018,0xfb605003,0xf4c54003,0xfa7d9006}}, // 気持, 業界, 予防, 警方,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf4bda009,0xf933c004,0x00000000,0x00000000}}, // 资金, 进入, --, --,
+ {{0xf5a4d007,0xf82b6004,0x00000000,0x00000000}}, // 重要, 况下, --, --,
+ {{0xf918c00a,0x00000000,0x00000000,0x00000000}}, // 人大, --, --, --,
+ {{0xf940800a,0x00000000,0x00000000,0x00000000}}, // 分娩, --, --, --,
+ {{0xf9454017,0xfa5e4006,0xfa4d3006,0xfb7fb006}}, // 開催, 市松, 汐止, 羅素,
+ {{0xf8158009,0xf5c55003,0xfa4f6002,0x00000000}}, // 办事, の自, 苗栗, --,
+ {{0xf81b900b,0xf9354006,0x00000000,0x00000000}}, // 十五, 期待, --, --,
+ {{0xfb7e1006,0xf826a00b,0x00000000,0x00000000}}, // 的火, 事件, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf825d00a,0xf4b9a00b,0x00000000,0x00000000}}, // 事人, 最高, --, --,
+ {{0xfa838010,0xf909e004,0xf9198004,0xf811e00b}}, // [130] の手, 海市, 出台, 晚上,
+ {{0xf94a8002,0x00000000,0x00000000,0x00000000}}, // 美容, --, --, --,
+ {{0xf9465006,0xf49d0004,0xf923a010,0xfa631003}}, // 之女, 学院, の前, 合成,
+ {{0xf922e002,0xf8361004,0x00000000,0x00000000}}, // 代役, 人事, --, --,
+ {{0xfb7f7004,0xf91dd006,0xf9219002,0xfb645006}}, // 过程, 怡半, 本局, 緬甸,
+ {{0xf81a400b,0xf5bac006,0x00000000,0x00000000}}, // 十一, 接近, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf59e5006,0xf9279004,0xfb7ea006,0x00000000}}, // 善良, 扩大, 元素, --,
+ {{0xfa57d007,0xfa7d8006,0xfb49d00d,0x00000000}}, // 依法, 大早, 户籍, --,
+ {{0xfa53e011,0xf9263006,0xf939000b,0x00000000}}, // 浙江, 孩子, 保健, --,
+ {{0xfa54b009,0x00000000,0x00000000,0x00000000}}, // 创新, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf91bf002,0xfa5ce004,0xf927700d,0x00000000}}, // 哥大, 的情, 在北, --,
+ {{0xf91bb006,0xf9372006,0x00000000,0x00000000}}, // 學年, 綜合, --, --,
+ {{0xf825900a,0xf91e4011,0xf91cc00b,0x00000000}}, // 案件, 务局, 造型, --,
+ {{0xf9258003,0xf58f400a,0xfb6e7011,0xfb51b006}}, // の利, 法行, 民群, 誰知,
+ {{0xf492e006,0x00000000,0x00000000,0x00000000}}, // [140] 現金, --, --, --,
+ {{0xfb734003,0xf92f900b,0x00000000,0x00000000}}, // 厚生, 我家, --, --,
+ {{0xf9278002,0xf91ab006,0x00000000,0x00000000}}, // 復健, 日子, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf5c0f010,0xfa597006,0xf837d002,0x00000000}}, // 読者, 戀情, 數位, --,
+ {{0xfb68c018,0xfb534006,0x00000000,0x00000000}}, // 表示, 鋼筋, --, --,
+ {{0xf842f003,0xfb7c1004,0x00000000,0x00000000}}, // は一, 权益, --, --,
+ {{0xf9174002,0xfa5e9006,0x00000000,0x00000000}}, // 竹市, 情感, --, --,
+ {{0xfb4c0006,0xf911d006,0xf9257006,0x00000000}}, // 寶石, 田尾, 是出, --,
+ {{0xfa527011,0xf81d5006,0x00000000,0x00000000}}, // 业技, 故事, --, --,
+ {{0xf9367010,0x00000000,0x00000000,0x00000000}}, // 機器, --, --, --,
+ {{0xf9242005,0xf912b002,0xf91db006,0xf844600b}}, // 公尺, 共工, 無奈, 是他,
+ {{0xf919a003,0xf58b6003,0xf9117006,0xf5be700e}}, // 津市, を解, 鳳山, 葡萄,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa5fc00a,0xf926b004,0xf81a6006,0x00000000}}, // 文明, 动力, 简体, --,
+ {{0xf93cf00a,0xfa6e7002,0x00000000,0x00000000}}, // 的商, 郵政, --, --,
+ {{0xfa62f006,0x00000000,0x00000000,0x00000000}}, // [150] 葉振, --, --, --,
+ {{0xf9237002,0xfa7ba006,0xf5a8d003,0xfa708006}}, // 售屋, 日止, が起, 西洋,
+ {{0xf936e006,0xfb4a0003,0xfb52601a,0x00000000}}, // 增回, 究科, 使用, --,
+ {{0xf8241004,0xf81ef00b,0x00000000,0x00000000}}, // 不予, 元以, --, --,
+ {{0xf843d003,0xfa86800a,0xfb6eb006,0x00000000}}, // の一, 修改, 我知, --,
+ {{0xf8283003,0xf93fe003,0xf4903006,0x00000000}}, // 二人, 縄地, 罰金, --,
+ {{0xfa763003,0xf9299006,0xf92e0004,0xf928100a}}, // 発明, 毒品, 帖子, 革命,
+ {{0xfb55f00b,0x00000000,0x00000000,0x00000000}}, // 建築, --, --, --,
+ {{0xf5980009,0xf9482010,0xf8365002,0xf947900d}}, // 作者, が出, 為一, 后再,
+ {{0xf49ad014,0x00000000,0x00000000,0x00000000}}, // 知道, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf92e400a,0xfa767006,0xf4c5a00b,0x00000000}}, // 取得, 繼承, 不限, --,
+ {{0xfa7eb018,0xf9174004,0xfb7ca006,0x00000000}}, // 無料, 区域, 的私, --,
+ {{0xf8464003,0xf92b1006,0xfa809006,0x00000000}}, // の交, 說出, 中最, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa621006,0xf93ab006,0xf8133002,0xf9448006}}, // 有感, 讀取, 這一, 養品,
+ {{0xfa5e9006,0x00000000,0x00000000,0x00000000}}, // [160] 範本, --, --, --,
+ {{0xfb59c006,0xf494c002,0x00000000,0x00000000}}, // 書目, 製造, --, --,
+ {{0xf8392007,0xf5b89006,0xfa5ad004,0x00000000}}, // 以上, 學者, 要思, --,
+ {{0xf912b010,0xf90c7002,0xfa6db006,0xf847d006}}, // 製品, 職工, 色情, 天使,
+ {{0xf59a7005,0xf819f002,0x00000000,0x00000000}}, // 威脅, 條件, --, --,
+ {{0xf5a9b002,0xf5c8f003,0xf8091006,0xfb7fe006}}, // 執行, に自, 沒事, 爆笑,
+ {{0xfb859003,0xf947b006,0xf9128003,0xfb701008}}, // 再生, 獎名, 東大, 處理,
+ {{0xfb77f006,0xf941200b,0x00000000,0x00000000}}, // 土石, 三十, --, --,
+ {{0xfa646003,0xf845700a,0x00000000,0x00000000}}, // 構成, 副主, --, --,
+ {{0xfb711009,0xf9313002,0xfa888010,0xf947100a}}, // 联系, 列印, 昨日, 重大,
+ {{0xfa71d008,0xf91c700a,0xf8253003,0xfb51a004}}, // 地政, 大力, が上, 设立,
+ {{0xfa822004,0xf8103010,0x00000000,0x00000000}}, // 财政, 受信, --, --,
+ {{0xfa6dc003,0xfb672010,0xfb819004,0x00000000}}, // 更新, に立, 文物, --,
+ {{0xf93f8003,0xf942300a,0x00000000,0x00000000}}, // 心地, 理念, --, --,
+ {{0xf9334002,0xfa61d006,0xfa57600d,0x00000000}}, // 務局, 與本, 生死, --,
+ {{0xfb7da004,0xf90c6006,0x00000000,0x00000000}}, // 调研, 陷入, --, --,
+ {{0xf4a88003,0xfa845003,0xf49e5004,0x00000000}}, // [170] に限, の指, 渠道, --,
+ {{0xfa4e1003,0xf4a76006,0x00000000,0x00000000}}, // ご案, 用途, --, --,
+ {{0xf928f00a,0xf92af002,0xfb668003,0x00000000}}, // 努力, 壓力, の相, --,
+ {{0xfa6eb006,0xf9492006,0x00000000,0x00000000}}, // 詳情, 二年, --, --,
+ {{0xf80bd003,0xfa77a003,0x00000000,0x00000000}}, // 子供, 活性, --, --,
+ {{0xf59d8011,0xf5b40006,0xf90fb008,0x00000000}}, // 雅虎, 貼者, 報告, --,
+ {{0xfa5ff007,0xf90ba006,0xfa768002,0xfb877006}}, // 分析, 念品, 診所, 題目,
+ {{0xf9443006,0xf90c0002,0xf5a66004,0xf8247014}}, // 之土, 當地, 不良, 上一,
+ {{0xf8461003,0xfb63b010,0x00000000,0x00000000}}, // と一, の美, --, --,
+ {{0xfa5a3007,0xf83e6003,0xf944900a,0xf924b00b}}, // 要求, 大人, 上市, 公共,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf4ab0006,0xfa5c4014,0x00000000,0x00000000}}, // 限量, 如果, --, --,
+ {{0xf9134003,0xfa60f002,0xfa4cb006,0x00000000}}, // 格安, 證明, 首歌, --,
+ {{0xf931e00a,0xf91d0010,0x00000000,0x00000000}}, // 招商, 映像, --, --,
+ {{0xf82ae003,0xfb5a8003,0x00000000,0x00000000}}, // 件中, 学等, --, --,
+ {{0xfb831006,0xfa82f006,0xf9114006,0xf9261006}}, // 指甲, 正方, 百合, 用品,
+ {{0xf497a010,0xfb831006,0x00000000,0x00000000}}, // [180] 特集, 開立, --, --,
+ {{0xf830e00a,0xf937300a,0x00000000,0x00000000}}, // 女人, 速度, --, --,
+ {{0xf933e004,0xf9438006,0xfa737006,0x00000000}}, // 业化, 載入, 西方, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa675006,0xf9313006,0x00000000,0x00000000}}, // 反方, 演出, --, --,
+ {{0xf4a3e003,0xfa82f006,0x00000000,0x00000000}}, // 是非, 是最, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb839007,0xf5bdf008,0xfb626006,0xf93ee00b}}, // 按照, 歡迎, 設置, 分局,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf914b003,0xf81d6006,0xfb866003,0xf923a00a}}, // 追加, 情事, 同盟, 第六,
+ {{0xfa819002,0xf949d006,0xf592d00a,0x00000000}}, // 公所, 小女, 引起, --,
+ {{0xf9405009,0xfb82a006,0xf9227006,0x00000000}}, // 内容, 文稿, 聯合, --,
+ {{0xfa666006,0xfa66c006,0x00000000,0x00000000}}, // 請河, 題材, --, --,
+ {{0xf9298006,0xf8477003,0xf824800a,0xfa5a6006}}, // 太子, に一, 前位, 要情,
+ {{0xf946000a,0xf90cb00a,0xf5b5a006,0x00000000}}, // 完善, 更加, 費者, --,
+ {{0xf9127006,0xfa809006,0x00000000,0x00000000}}, // [190] 命名, 維持, --, --,
+ {{0xf80fe009,0xfb708003,0xfa73b006,0x00000000}}, // 北京, 誕生, 格最, --,
+ {{0xf948200a,0xfb743004,0xfa683004,0x00000000}}, // 美元, 优秀, 双方, --,
+ {{0xf92e2008,0xf8260003,0xf8300002,0xf9144006}}, // 北市, 友人, 統一, 放入,
+ {{0xfa798011,0xf93b800b,0x00000000,0x00000000}}, // 机械, 一大, --, --,
+ {{0xf926e003,0xf91d2003,0x00000000,0x00000000}}, // 撮影, て大, --, --,
+ {{0xf844a003,0xfa659002,0xfa695002,0xfa5b7006}}, // の作, 變成, 每日, 一旦,
+ {{0xf93cb002,0x00000000,0x00000000,0x00000000}}, // 雄市, --, --, --,
+ {{0xf923a003,0xfa4bf008,0xf9121006,0x00000000}}, // の反, 結果, 房子, --,
+ {{0xf8212006,0xf9171002,0x00000000,0x00000000}}, // 來信, 決定, --, --,
+ {{0xfa76f004,0x00000000,0x00000000,0x00000000}}, // 论文, --, --, --,
+ {{0xf4c22011,0x00000000,0x00000000,0x00000000}}, // 合适, --, --, --,
+ {{0xfa56d006,0x00000000,0x00000000,0x00000000}}, // 官方, --, --, --,
+ {{0xf9126004,0xfb80e006,0xf8309006,0x00000000}}, // 丰台, 鄉立, 政事, --,
+ {{0xf4914002,0xf9440010,0x00000000,0x00000000}}, // 報道, お友, --, --,
+ {{0xfa82a002,0xf5a1000a,0xf5c40003,0xfa66e009}}, // 中正, 了解, の考, 反映,
+ {{0xf9292003,0xf92b7003,0x00000000,0x00000000}}, // [1a0] 阪府, を加, --, --,
+ {{0xf59d6003,0xf93a0006,0xf9423006,0x00000000}}, // 旅行, 九年, 版品, --,
+ {{0xfa6f1004,0x00000000,0x00000000,0x00000000}}, // 声明, --, --, --,
+ {{0xf58f900b,0xfa84d006,0x00000000,0x00000000}}, // 列表, 是本, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf80f3010,0xfb55b00a,0xfa63a006,0xf90ec002}}, // 仕事, 治理, 事情, 獲得,
+ {{0xf5b2300d,0x00000000,0x00000000,0x00000000}}, // 兰花, --, --, --,
+ {{0xfb6cc004,0xf91e4003,0xf918e004,0xf596401b}}, // 体系, で大, 许多, 秘訣,
+ {{0xf58de009,0xfa6a7010,0xf81c100b,0xf90b0006}}, // 发表, 毎日, 最佳, 男子,
+ {{0xfa64c003,0xf910600a,0x00000000,0x00000000}}, // 手法, 西安, --, --,
+ {{0xf9417004,0xfa63d00a,0x00000000,0x00000000}}, // 采取, 廉政, --, --,
+ {{0xfa57c018,0xf9270003,0xf80b4003,0x00000000}}, // 実施, の地, を中, --,
+ {{0xfb55e004,0xfb60a006,0x00000000,0x00000000}}, // 追究, 員登, --, --,
+ {{0xfa580003,0xfa5fc00a,0xf935b006,0xf935a002}}, // 感想, 的新, 從前, 記得,
+ {{0xfa66d007,0xf92b2003,0xfb65a006,0xfa5d6006}}, // 完成, 結局, 禮物, 終止,
+ {{0xf910a002,0xf80a1003,0xf924b006,0x00000000}}, // 彰化, を保, 適合, --,
+ {{0xfb4d3008,0xf82cc00b,0x00000000,0x00000000}}, // [1b0] 課程, 年代, --, --,
+ {{0xf9360007,0xf4a85017,0xf59b1006,0x00000000}}, // 州市, 太郎, 一般, --,
+ {{0xf934e011,0x00000000,0x00000000,0x00000000}}, // 党建, --, --, --,
+ {{0xfb686009,0xf4b3a004,0x00000000,0x00000000}}, // 卫生, 创造, --, --,
+ {{0xf4a49003,0x00000000,0x00000000,0x00000000}}, // 京都, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf8386004,0xfa556002,0x00000000,0x00000000}}, // 为中, 這次, --, --,
+ {{0xf936f003,0xf9257003,0x00000000,0x00000000}}, // 初心, の周, --, --,
+ {{0xfb548006,0x00000000,0x00000000,0x00000000}}, // 眼神, --, --, --,
+ {{0xf59da003,0xf916b010,0xfa857010,0xf498b013}}, // 必要, 彼女, の注, 烹飪,
+ {{0xfa610003,0xf9297002,0xf5b56006,0xfb7f6006}}, // 清水, 編制, 追追, 的白,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf911c011,0xfb56e003,0xfb873006,0xf9181002}}, // 广州, 活用, 才知, 書局,
+ {{0xfa58e004,0xfa528006,0xfb5ef003,0xf944b002}}, // 实施, 定最, 校生, 立委,
+ {{0xfa6ef002,0xf818e014,0x00000000,0x00000000}}, // 創新, 一下, --, --,
+ {{0xf83a8003,0xfa861014,0x00000000,0x00000000}}, // て下, 注意, --, --,
+ {{0xfb6fb002,0x00000000,0x00000000,0x00000000}}, // [1c0] 字第, --, --, --,
+ {{0xf93a7004,0xf4966010,0xfb518006,0xf5a7a00a}}, // 开展, 発送, 新知, 上述,
+ {{0xf84b0009,0xf923000a,0x00000000,0x00000000}}, // 个人, 正常, --, --,
+ {{0xf5c64003,0x00000000,0x00000000,0x00000000}}, // と考, --, --, --,
+ {{0xf90cc009,0xf811f002,0xf5b1b004,0xf5b01006}}, // 电子, 法令, 举行, 鄰近,
+ {{0xf912400d,0x00000000,0x00000000,0x00000000}}, // 西北, --, --, --,
+ {{0xf9360006,0xf843c009,0xf9144002,0x00000000}}, // 保守, 环保, 政制, --,
+ {{0xfb72c013,0xf847f006,0xf924e011,0xfb7fd013}}, // 忙碌, 物使, 港口, 約翰,
+ {{0xf5bda010,0xf92ab003,0x00000000,0x00000000}}, // 信越, を得, --, --,
+ {{0xfb7db003,0xf4bfd004,0xfa656006,0x00000000}}, // 参照, 预防, 歌手, --,
+ {{0xfb614004,0xfa879005,0x00000000,0x00000000}}, // 独立, 表演, --, --,
+ {{0xf922500a,0xfa702006,0x00000000,0x00000000}}, // 第四, 色派, --, --,
+ {{0xfb6e4004,0xf925d003,0xfa7c4019,0xf9415014}}, // 监督, と同, 警惕, 文字,
+ {{0xf9445014,0x00000000,0x00000000,0x00000000}}, // 不同, --, --, --,
+ {{0xf5bcf00a,0xf4996014,0x00000000,0x00000000}}, // 日起, 交通, --, --,
+ {{0xf9325006,0xfb4fa004,0x00000000,0x00000000}}, // 減少, 佳答, --, --,
+ {{0xf947f007,0xf58d6009,0xf92c7004,0xf8120004}}, // [1d0] 城市, 体育, 首先, 会上,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf940a003,0xf91e500b,0xfb76c006,0x00000000}}, // 指定, 股市, 搞笑, --,
+ {{0xfa87f00d,0xfb58a004,0xf92fd006,0x00000000}}, // 个月, 费用, 孔子, --,
+ {{0xf9476003,0xf91c3006,0x00000000,0x00000000}}, // が可, 日出, --, --,
+ {{0xf9467006,0xf90ed004,0xfb57e006,0x00000000}}, // 國女, 电影, 決策, --,
+ {{0xf941400a,0xf92cc004,0xf90b4004,0xf9401004}}, // 理工, 输入, 鉴定, 商引,
+ {{0xfa6f6003,0xf941f00f,0xfa4ff013,0xfb4d4019}}, // 平成, 历史, 洗澡, 爷爷,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf946a013,0xf927c002,0x00000000,0x00000000}}, // 伊凡, 在天, --, --,
+ {{0xfa517002,0xf81f3006,0xf81e700b,0x00000000}}, // 四技, 會使, 的中, --,
+ {{0xfa7a2008,0xfa665002,0x00000000,0x00000000}}, // 價格, 每月, --, --,
+ {{0xf9240003,0x00000000,0x00000000,0x00000000}}, // 京大, --, --, --,
+ {{0xf92bb003,0xf9253014,0x00000000,0x00000000}}, // 限定, 是否, --, --,
+ {{0xf910700d,0xfb53c004,0x00000000,0x00000000}}, // 数字, 设置, --, --,
+ {{0xf9436002,0xfa83c003,0x00000000,0x00000000}}, // 個小, の意, --, --,
+ {{0xfb5d6006,0xf945f002,0xfa5cf006,0x00000000}}, // [1e0] 大火, 變化, 燃料, --,
+ {{0xf4956006,0x00000000,0x00000000,0x00000000}}, // 電量, --, --, --,
+ {{0xfa4b5017,0xf938d006,0x00000000,0x00000000}}, // を探, 一半, --, --,
+ {{0xf9199004,0xfb652004,0xfb7d4006,0x00000000}}, // 报名, 问答, 頂端, --,
+ {{0xf9304008,0xf91b4003,0x00000000,0x00000000}}, // 志工, 陸地, --, --,
+ {{0xf4b57013,0xfa78f004,0xf9395002,0xfb7f100d}}, // 回饋, 根本, 紀元, 的空,
+ {{0xfa892002,0x00000000,0x00000000,0x00000000}}, // 風格, --, --, --,
+ {{0xf918e006,0xf9240007,0xf917700b,0xf94a5005}}, // 人履, 公室, 改善, 過去,
+ {{0xf9313003,0xf8336004,0xf94a5006,0x00000000}}, // 北地, 政主, 每年, --,
+ {{0xf49fb009,0xfa661004,0xf93cc004,0xf5a26013}}, // 管部, 行情, 证券, 草莓,
+ {{0xf9236003,0x00000000,0x00000000,0x00000000}}, // の光, --, --, --,
+ {{0xf9304003,0xfa73c00a,0xfa544006,0x00000000}}, // ご利, 能源, 教材, --,
+ {{0xf143901e,0xffe6b017,0x00000000,0x00000000}}, // 역삼, のソ, --, --,
+ {{0xfb645003,0xfb85101b,0xf4968002,0xf837900b}}, // 修理, 珊瑚, 頻道, 死亡,
+ {{0xf81c600a,0xf9218003,0xf49e0006,0x00000000}}, // 的位, 薬局, 素食, --,
+ {{0xf9132004,0xfa85b006,0x00000000,0x00000000}}, // 广大, 用手, --, --,
+ {{0xfb8ac006,0xf5927004,0x00000000,0x00000000}}, // [1f0] 美白, 陕西, --, --,
+ {{0xf836b018,0xf9321006,0xfb763006,0xf93c300b}}, // 紹介, 展出, 沙田, 十六,
+ {{0xf91ea00a,0xf9157011,0xf5b3e004,0xfa742019}}, // 加快, 总局, 绿色, 百慧,
+ {{0xfa891010,0x00000000,0x00000000,0x00000000}}, // に注, --, --, --,
+ {{0xf8403004,0x00000000,0x00000000,0x00000000}}, // 务中, --, --, --,
+ {{0xf946c003,0xfb5ea003,0xf82c6004,0xfa860002}}, // 残念, 無理, 请人, 修正,
+ {{0xf81ec011,0xf80c400b,0xfa4f0006,0x00000000}}, // 的信, 我一, 拖欠, --,
+ {{0xf9321006,0xf83c3002,0xfa847006,0xfb864006}}, // 多半, 日人, 魯木, 植物,
+ {{0xf92c6004,0x00000000,0x00000000,0x00000000}}, // 我市, --, --, --,
+ {{0xf9142003,0xf4bed00d,0xf912d00b,0xfa63a00b}}, // 畿地, 视野, 都市, 之旅,
+ {{0xf4978004,0xf93d5006,0x00000000,0x00000000}}, // 医院, 較少, --, --,
+ {{0xf4b55010,0x00000000,0x00000000,0x00000000}}, // 真集, --, --, --,
+ {{0xf8287006,0xf9124002,0x00000000,0x00000000}}, // 當事, 好友, --, --,
+ {{0xf4c0e003,0xfa6eb002,0xfa5c5006,0x00000000}}, // 三重, 雲林, 的歌, --,
+ {{0xfb539006,0x00000000,0x00000000,0x00000000}}, // 好笑, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [200] --, --, --, --,
+ {{0xf5891003,0xfb735004,0xf81ca00a,0xfa577004}}, // を行, 业生, 的企, 高新,
+ {{0xf90af014,0xfa5c0006,0x00000000,0x00000000}}, // 家庭, 言板, --, --,
+ {{0xfb761006,0xfa56e006,0x00000000,0x00000000}}, // 務白, 原本, --, --,
+ {{0xfa6cc009,0xfa875003,0x00000000,0x00000000}}, // 价格, の水, --, --,
+ {{0xfa6c3004,0xf9284002,0xfa649004,0x00000000}}, // 阶段, 溫州, 律法, --,
+ {{0xfa72b010,0xf82fc002,0xf912300b,0x00000000}}, // 箱根, 環保, 百分, --,
+ {{0xf9497004,0xf93fe00b,0x00000000,0x00000000}}, // 经典, 的家, --, --,
+ {{0xf9368011,0xfb4b500d,0xf93e4006,0xf5a4a014}}, // 员工, 时空, 集合, 不要,
+ {{0xfb79b018,0xfb659013,0xfa63d004,0x00000000}}, // 検索, 周秉, 友情, --,
+ {{0xfb4f5006,0xf9479003,0x00000000,0x00000000}}, // 傳真, 越地, --, --,
+ {{0xf4a1a006,0xf93aa00b,0x00000000,0x00000000}}, // 測量, 十分, --, --,
+ {{0xfb795002,0xf80d800a,0xf48e5006,0x00000000}}, // 節省, 提交, 飲食, --,
+ {{0xf84a9018,0xf915d003,0xf493b006,0xf824a00a}}, // 自信, 発光, 白金, 予以,
+ {{0xf90b8006,0xf916b014,0xf90fc005,0x00000000}}, // 水土, 系列, 花卉, --,
+ {{0xf9115002,0xf5a87004,0xfa77e004,0x00000000}}, // 影展, 职能, 系方, --,
+ {{0xfa77b010,0xfb76b006,0xf93d2003,0xfa69a004}}, // [210] 出演, 期目, も可, 时期,
+ {{0xf826e010,0x00000000,0x00000000,0x00000000}}, // 配信, --, --, --,
+ {{0xf90ec00b,0x00000000,0x00000000,0x00000000}}, // 平均, --, --, --,
+ {{0xf92a1009,0xfb62a002,0x00000000,0x00000000}}, // 当前, 監管, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf90e9004,0x00000000,0x00000000,0x00000000}}, // 阳市, --, --, --,
+ {{0xfa661010,0xfa68c005,0x00000000,0x00000000}}, // る方, 疏濬, --, --,
+ {{0xfa735010,0xf91c5003,0xf9254003,0xf90aa004}}, // 稿日, 日常, は大, 伴奏,
+ {{0xf9472012,0x00000000,0x00000000,0x00000000}}, // 住宅, --, --, --,
+ {{0xf91ff008,0xf9450003,0x00000000,0x00000000}}, // 廣告, 反射, --, --,
+ {{0xf9445006,0xf9378006,0xf93c9006,0xfa66000e}}, // 國土, 團年, 流出, 遊憩,
+ {{0xf93d8009,0xf4a8200a,0xfa5dd00a,0xf83a6006}}, // 参加, 快速, 的法, 給予,
+ {{0xf9166006,0x00000000,0x00000000,0x00000000}}, // 妻子, --, --, --,
+ {{0xfa5d2009,0xf5a8a003,0xf946b003,0xf83b7006}}, // 资料, 野菜, 今度, 給付,
+ {{0xf5b94004,0x00000000,0x00000000,0x00000000}}, // 履行, --, --, --,
+ {{0xf58af003,0x00000000,0x00000000,0x00000000}}, // を表, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [220] --, --, --, --,
+ {{0xf838e004,0xfb582006,0x00000000,0x00000000}}, // 区人, 幸福, --, --,
+ {{0xfb885004,0xfb860006,0x00000000,0x00000000}}, // 和社, 癌症, --, --,
+ {{0xfb545004,0x00000000,0x00000000,0x00000000}}, // 显示, --, --, --,
+ {{0xf815a002,0x00000000,0x00000000,0x00000000}}, // 生保, --, --, --,
+ {{0xf9262003,0xfb881003,0xfa58f013,0x00000000}}, // の大, 野球, 神拳, --,
+ {{0xf5b1600d,0x00000000,0x00000000,0x00000000}}, // 艰苦, --, --, --,
+ {{0xf9115006,0xf9171014,0x00000000,0x00000000}}, // 英子, 方式, --, --,
+ {{0xf91a5003,0xfb769004,0xfa6f8002,0xf922f006}}, // 入力, 战略, 新文, 華夏,
+ {{0xfa7de010,0xf93d500a,0xfa592006,0x00000000}}, // て欲, 考察, 關注, --,
+ {{0xf821500a,0xf9243003,0xf917e002,0xf819a00a}}, // 理人, の分, 永和, 作中,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf49bc011,0x00000000,0x00000000,0x00000000}}, // 大量, --, --, --,
+ {{0xf93ea008,0x00000000,0x00000000,0x00000000}}, // 參加, --, --, --,
+ {{0xfa7b8018,0xf4927002,0xf917e006,0xf8120006}}, // 日本, 沿革, 登入, 通事,
+ {{0xfb86300b,0xf816300b,0x00000000,0x00000000}}, // 不用, 高中, --, --,
+ {{0xf589b003,0x00000000,0x00000000,0x00000000}}, // [230] を超, --, --, --,
+ {{0xf90b3007,0xf8438010,0xf59c500a,0xf848000a}}, // 更多, の事, 的行, 在中,
+ {{0xf81bd003,0xf93c7014,0x00000000,0x00000000}}, // 終了, 的小, --, --,
+ {{0xf942d003,0xfb60b00a,0xfa4bc006,0x00000000}}, // 予定, 代理, 民歌, --,
+ {{0xfa849006,0xfa4d4003,0xfb7d900a,0xf9199011}}, // 公河, 外旅, 突破, 项工,
+ {{0xf5a06002,0xf935f006,0xfa84e003,0x00000000}}, // 來自, 孟子, の成, --,
+ {{0xf823f003,0x00000000,0x00000000,0x00000000}}, // り上, --, --, --,
+ {{0xf48de002,0xf91d9002,0xf4996003,0xfa88c006}}, // 創造, 廠商, 以降, 貨方,
+ {{0xf81a700a,0xfb75a006,0xf8336002,0xfb4c1005}}, // 十七, 標示, 製作, 陶瓷,
+ {{0xf8260003,0xf92ff011,0xf92f9004,0xfa78a005}}, // る人, 窗口, 返回, 智慧,
+ {{0xfb66d003,0x00000000,0x00000000,0x00000000}}, // 利用, --, --, --,
+ {{0xfb51d006,0xf921a00a,0x00000000,0x00000000}}, // 新社, 杭州, --, --,
+ {{0xfa7f3011,0xf9431008,0x00000000,0x00000000}}, // 污染, 開始, --, --,
+ {{0xf82ac007,0xfa744003,0xfb7e7006,0x00000000}}, // 其中, 弾性, 清真, --,
+ {{0xfa569006,0xf93c6013,0x00000000,0x00000000}}, // 土木, 寂寞, --, --,
+ {{0xf8157006,0xf9190006,0x00000000,0x00000000}}, // 務信, 人出, --, --,
+ {{0xf4a46003,0x00000000,0x00000000,0x00000000}}, // [240] の高, --, --, --,
+ {{0xf948c006,0x00000000,0x00000000,0x00000000}}, // 里山, --, --, --,
+ {{0xfa736011,0xf92cf002,0x00000000,0x00000000}}, // 政治, 經常, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa787003,0xf9340011,0xf925300d,0xfb512013}}, // 方法, 创建, 键字, 隱瞞,
+ {{0xfa743004,0xfa4b2003,0xf9195006,0x00000000}}, // 链接, を支, 為女, --,
+ {{0xf92e1004,0xfa7fb00b,0x00000000,0x00000000}}, // 联合, 加油, --, --,
+ {{0xf916000f,0xf9293003,0xf9473003,0xf5c8100b}}, // 国家, と共, お好, 自行,
+ {{0xfa59b007,0xf845e003,0xfa60f00a,0xf5ae800b}}, // 思想, の代, 震救, 想要,
+ {{0xf5c20003,0xf9480004,0xf9286003,0x00000000}}, // 関西, 减少, と大, --,
+ {{0xf914f006,0xf59cb006,0xf942d014,0x00000000}}, // 松山, 的脂, 很多, --,
+ {{0xf919700b,0xf92a2006,0x00000000,0x00000000}}, // 書店, 姓名, --, --,
+ {{0xf918200a,0x00000000,0x00000000,0x00000000}}, // 方向, --, --, --,
+ {{0xfb664014,0x00000000,0x00000000,0x00000000}}, // 全球, --, --, --,
+ {{0xf4c6c003,0xf5999006,0xf59e200b,0x00000000}}, // 削除, 讀者, 的表, --,
+ {{0xf8438003,0x00000000,0x00000000,0x00000000}}, // の下, --, --, --,
+ {{0xfa79b006,0xf92eb011,0xf4be200b,0x00000000}}, // [250] 入最, 村建, 的部, --,
+ {{0xf801a016,0xf92bd00b,0x00000000,0x00000000}}, // 달걀, 結婚, --, --,
+ {{0xf929e018,0xfa600002,0xfa4d5004,0x00000000}}, // に入, 來源, 民法, --,
+ {{0xf920c01c,0xf90d2002,0xf591d008,0x00000000}}, // 中心, 強制, 體育, --,
+ {{0xf945b010,0xfb870006,0x00000000,0x00000000}}, // お店, 籍登, --, --,
+ {{0xfa513010,0xfa76a00a,0xf830d003,0x00000000}}, // 答日, 方案, 報保, --,
+ {{0xf6dac016,0xfa507006,0xf5c3600a,0xf916f00a}}, // 그룹, 字方, 公路, 固定,
+ {{0xfb5cb004,0x00000000,0x00000000,0x00000000}}, // 产生, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf9176006,0xf8215003,0xf91eb003,0xf8313004}}, // 人回, 円以, 大好, 现代,
+ {{0xfb884006,0xfb702008,0x00000000,0x00000000}}, // 問答, 雖然, --, --,
+ {{0xfa793010,0xf9487003,0xfa807019,0x00000000}}, // 示板, が好, 管执, --,
+ {{0xf82c700b,0x00000000,0x00000000,0x00000000}}, // 男人, --, --, --,
+ {{0xf837a006,0x00000000,0x00000000,0x00000000}}, // 電信, --, --, --,
+ {{0xf836b006,0xf946600a,0xf4ac500d,0x00000000}}, // 幹事, 立即, 发送, --,
+ {{0xf929c003,0x00000000,0x00000000,0x00000000}}, // に大, --, --, --,
+ {{0xf4a33003,0xf91b9002,0xfa7c8004,0xf4c2a00e}}, // [260] 本部, 學家, 严格, 晉霖,
+ {{0xf9181006,0xfa672006,0xf93f0006,0xf81cc004}}, // 出差, 對方, 會出, 省人,
+ {{0xfa870006,0xfa848003,0xfa4d7002,0x00000000}}, // 幫手, 本格, 論文, --,
+ {{0xfb846013,0xfb632010,0xf58ad003,0x00000000}}, // 光焰, 中空, を自, --,
+ {{0xf9381007,0xfa58e006,0xf82dc006,0xfa756008}}, // 增加, 孝武, 徵信, 施政,
+ {{0xfa503018,0xf5c30006,0xf9383002,0x00000000}}, // ご注, 揭諦, 機制, --,
+ {{0xfa751003,0xfb4b9003,0xf5c8700b,0x00000000}}, // 能性, 年生, 只要, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf93a0006,0xfa57000a,0xfa4c0002,0x00000000}}, // 區域, 真正, 結束, --,
+ {{0xfa80800a,0xf49fd004,0xfb858006,0xfa562004}}, // 形成, 务部, 認真, 勘探,
+ {{0xf920a008,0xf929300a,0x00000000,0x00000000}}, // 員工, 用地, --, --,
+ {{0xf5b8200d,0xfa663002,0xf92bf00b,0x00000000}}, // 彻落, 開放, 我和, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf946c006,0xf9108008,0xf8256003,0xf9247004}}, // 之年, 台北, が不, 息化,
+ {{0xfa638003,0xf9226003,0xfb642004,0xf8369006}}, // 請求, 測定, 转移, 任何,
+ {{0xf59a1013,0xf8489003,0xfb63e003,0x00000000}}, // 一趟, 物件, 歯科, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [270] --, --, --, --,
+ {{0xf9335006,0xfa836003,0x00000000,0x00000000}}, // 務品, の有, --, --,
+ {{0xf81cc006,0xf824200b,0x00000000,0x00000000}}, // 如何, 上下, --, --,
+ {{0xfa58a009,0xf92f9002,0xf81a2003,0xf5955005}}, // 农民, 展局, 品一, 舞蹈,
+ {{0xfa5ee003,0xf93cb004,0xf49e2006,0xfb89c006}}, // 終更, 杂志, 盡量, 透社,
+ {{0xfb6aa002,0x00000000,0x00000000,0x00000000}}, // 經理, --, --, --,
+ {{0xfa737003,0xf92f3003,0xf91de004,0xf9442003}}, // 東海, 協力, 左右, 豊富,
+ {{0xf92ce007,0xfa4f4006,0xf92b0003,0xf937c006}}, // 限公, 南投, を利, 作品,
+ {{0xf937c00b,0x00000000,0x00000000,0x00000000}}, // 生命, --, --, --,
+ {{0xf8360006,0xfa5dd00d,0x00000000,0x00000000}}, // 為例, 节日, --, --,
+ {{0xfb596006,0xf9278008,0xfb5b5008,0x00000000}}, // 空白, 義工, 辦理, --,
+ {{0xfa767006,0xfb5f8004,0x00000000,0x00000000}}, // 人最, 计算, --, --,
+ {{0xf92a6006,0xf8465003,0xfa72c006,0x00000000}}, // 兒子, の主, 東方, --,
+ {{0xf919f007,0xf93cb004,0xfb5a2004,0xf91da002}}, // 工商, 突出, 方米, 章分,
+ {{0xf9108008,0xfa5c2004,0x00000000,0x00000000}}, // 台南, 宁波, --, --,
+ {{0xfa5dc004,0x00000000,0x00000000,0x00000000}}, // 组成, --, --, --,
+ {{0xfb818003,0xfb5f3004,0xf838d002,0xf844e00a}}, // [280] 雇用, 因素, 為主, 是中,
+ {{0xfa5d000b,0x00000000,0x00000000,0x00000000}}, // 的文, --, --, --,
+ {{0xf932200f,0xf9144003,0x00000000,0x00000000}}, // 四川, 楽天, --, --,
+ {{0xf5b73004,0xf4abb003,0x00000000,0x00000000}}, // 导致, を除, --, --,
+ {{0xfa73b004,0xf92cc003,0xfa5f500a,0x00000000}}, // 国民, 道大, 的政, --,
+ {{0xf92d6004,0xf58c5014,0x00000000,0x00000000}}, // 抓好, 我要, --, --,
+ {{0xfb789006,0xf91f5006,0xf948800b,0xfb828006}}, // 聞稿, 鏡子, 每天, 指示,
+ {{0xf90fe006,0xf9209003,0xfb5ab002,0xfb7d7006}}, // 報名, 拡大, 學系, 的神,
+ {{0xfb77200c,0xf93ec00a,0xf825500a,0x00000000}}, // 回答, 理局, 程中, --,
+ {{0xf9443006,0xfb768006,0x00000000,0x00000000}}, // 之原, 祝福, --, --,
+ {{0xfa707006,0xf9420006,0xf824900a,0x00000000}}, // 彰投, 案名, 降低, --,
+ {{0xfa4e8003,0x00000000,0x00000000,0x00000000}}, // ご意, --, --, --,
+ {{0xfb58d00f,0xf9294005,0xf4adf00a,0xf8095003}}, // 项目, 在台, 成都, を一,
+ {{0xfb52d00b,0xf92d8004,0x00000000,0x00000000}}, // 朱熹, 当地, --, --,
+ {{0xf9217006,0xf83a4006,0x00000000,0x00000000}}, // 樣子, 相信, --, --,
+ {{0xf931d003,0xfb504006,0x00000000,0x00000000}}, // 何度, 隱私, --, --,
+ {{0xf941e006,0xf91e9008,0xf9202004,0xfa4d2006}}, // [290] 先前, 內容, 务公, 我感,
+ {{0xf925c010,0xfb704004,0x00000000,0x00000000}}, // 部屋, 应用, --, --,
+ {{0xf9465003,0xf59e000a,0x00000000,0x00000000}}, // お客, 的自, --, --,
+ {{0xf9290002,0xf911f002,0x00000000,0x00000000}}, // 幫助, 許可, --, --,
+ {{0xfb7f5004,0x00000000,0x00000000,0x00000000}}, // 的社, --, --, --,
+ {{0xfb5e1006,0xf91df006,0xf83dd004,0xfb560003}}, // 你真, 占卜, 条例, 人等,
+ {{0xfa66e003,0xf9266006,0xf4ab6006,0xf8172006}}, // 手段, 置入, 蒐集, 號信,
+ {{0xfa865006,0xf8367004,0xfb7cf004,0xfa5c3006}}, // 揮春, 为一, 适用, 的手,
+ {{0xf945b003,0x00000000,0x00000000,0x00000000}}, // お得, --, --, --,
+ {{0xf5ba500a,0xfb7c3006,0xfa5e8004,0x00000000}}, // 特色, 的立, 资本, --,
+ {{0xfa84c004,0xf939a006,0x00000000,0x00000000}}, // 术支, 一切, --, --,
+ {{0xfa873003,0x00000000,0x00000000,0x00000000}}, // 普段, --, --, --,
+ {{0xf93cc00a,0xfb66f00a,0xf941601a,0x00000000}}, // 市委, 全生, 文化, --,
+ {{0xf9110004,0xf9223006,0xf598000a,0xf90e2002}}, // 亿元, 爭取, 生育, 鐵工,
+ {{0xfa85f003,0x00000000,0x00000000,0x00000000}}, // 用意, --, --, --,
+ {{0xfb81a00a,0x00000000,0x00000000,0x00000000}}, // 效益, --, --, --,
+ {{0xf9458006,0xfa4bf00a,0xfa5f2006,0xfb6b7002}}, // [2a0] 二名, 民族, 理情, 自由,
+ {{0xfa6ac006,0xf8173006,0xf932b00e,0xfa4f400b}}, // 長江, 關事, 驕傲, 我想,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf9399006,0xf90d2005,0x00000000,0x00000000}}, // 要先, 鍵字, --, --,
+ {{0xf82fd003,0xfa857003,0xf5a4100a,0xf59c7014}}, // 新作, の推, 诉讼, 一起,
+ {{0xfb57d003,0xfa878003,0xf9442004,0xf915700b}}, // 決算, 復活, 压力, 好好,
+ {{0xf922f010,0xf5ab6002,0xfb65b003,0xfa6fd00a}}, // 中古, 導致, の第, 育活,
+ {{0xf917e003,0xf4a48003,0xf917d006,0x00000000}}, // 特定, の通, 書名, --,
+ {{0xfa4f6006,0xfa49d003,0xf49d300d,0xfa59300b}}, // 動手, を指, 赠送, 需求,
+ {{0xf90ff002,0x00000000,0x00000000,0x00000000}}, // 詳全, --, --, --,
+ {{0xf924e010,0xfb4b6006,0xf4c28013,0xf5a00002}}, // の子, 創立, 按鈕, 豆腐,
+ {{0xf93a9003,0xfa5bd004,0x00000000,0x00000000}}, // 障害, 一方, --, --,
+ {{0xf842c009,0xf4c08010,0xfa6df006,0xf8159006}}, // 责任, 超音, 平洋, 從事,
+ {{0xfb764009,0xf943c006,0xf8248006,0x00000000}}, // 办理, 有出, 不住, --,
+ {{0xf848e006,0xf93d200d,0x00000000,0x00000000}}, // 自付, 七夜, --, --,
+ {{0xf911b01c,0xf4a69010,0x00000000,0x00000000}}, // 政府, 編集, --, --,
+ {{0xf920800a,0xfb82700a,0x00000000,0x00000000}}, // [2b0] 中小, 合理, --, --,
+ {{0xfa82e006,0xf5b7e003,0x00000000,0x00000000}}, // 公投, 出身, --, --,
+ {{0xf492f002,0x00000000,0x00000000,0x00000000}}, // 政院, --, --, --,
+ {{0xf598a00d,0x00000000,0x00000000,0x00000000}}, // 回落, --, --, --,
+ {{0xf922900b,0xfb697003,0xf9351006,0x00000000}}, // 中壢, 全然, 領域, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa5fc004,0xf5a10002,0xf5933008,0x00000000}}, // 调整, 舉行, 留言, --,
+ {{0xf93f100a,0xf90ba006,0x00000000,0x00000000}}, // 的建, 極品, --, --,
+ {{0xfb531003,0xf5b2a006,0x00000000,0x00000000}}, // 埼玉, 總裁, --, --,
+ {{0xf942c006,0xf9212007,0xf922d006,0xfa82e006}}, // 事先, 公安, 賣出, 賣方,
+ {{0xfa4ec007,0xfb86c010,0xf92b8003,0xf917600a}}, // 民政, お知, 成分, 添加,
+ {{0xfa666006,0xf948e003,0xf926f003,0xf91cd00b}}, // 對本, が家, の影, 工具,
+ {{0xf4b99006,0xf81c9007,0xfa6d0011,0xfa554006}}, // 一集, 的一, 整治, 誘惑,
+ {{0xfa785006,0xfa60e008,0xf90f8006,0x00000000}}, // 為止, 資源, 至少, --,
+ {{0xf5bf5004,0x00000000,0x00000000,0x00000000}}, // 欢迎, --, --, --,
+ {{0xf911900c,0xf9188011,0xfb50e011,0xf9142004}}, // 東北, 人口, 西省, 国外,
+ {{0xf90ca006,0xf9327011,0xf4a2b005,0x00000000}}, // [2c0] 張子, 业局, 路透, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf5963005,0xfa674002,0xfa5ee011,0x00000000}}, // 回覆, 權政, 的执, --,
+ {{0xf80be006,0xf4c4b011,0xfa89800b,0x00000000}}, // 我介, 不适, 用水, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa63a006,0xf93ed006,0xf9243006,0x00000000}}, // 之情, 獅子, 六年, --,
+ {{0xfb7f2004,0x00000000,0x00000000,0x00000000}}, // 的特, --, --, --,
+ {{0xfa84a012,0xf4b17011,0xf93d6006,0x00000000}}, // 温泉, 力量, 餃子, --,
+ {{0xfb716006,0x00000000,0x00000000,0x00000000}}, // 動物, --, --, --,
+ {{0xf927b00a,0xf914d00e,0xf9251006,0x00000000}}, // 在全, 發展, 種子, --,
+ {{0xf9336011,0x00000000,0x00000000,0x00000000}}, // 进口, --, --, --,
+ {{0xfa4b2004,0xfa53600b,0x00000000,0x00000000}}, // 结果, 定期, --, --,
+ {{0xf825b002,0xfa5ec00d,0xf4bec019,0x00000000}}, // 後一, 布日, 清醒, --,
+ {{0xf48f8007,0xf5a88009,0xfa5a0004,0x00000000}}, // 地震, 银行, 证明, --,
+ {{0xf81bc003,0xf80a2003,0xfa56a011,0xf9171006}}, // 一人, を作, 保持, 油品,
+ {{0xfb6e6011,0xf918600a,0xf5c97005,0x00000000}}, // 监管, 核心, 鴨脷, --,
+ {{0xf945c002,0xf932e00a,0xf823c00b,0xf9417006}}, // [2d0] 態度, 法制, 有人, 等待,
+ {{0xf81e8004,0xf92bf006,0x00000000,0x00000000}}, // 担保, 向前, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa71e006,0xf81df003,0x00000000,0x00000000}}, // 影本, 求人, --, --,
+ {{0xf9391006,0xf93d1006,0xfb60e006,0x00000000}}, // 保固, 樂器, 螢火, --,
+ {{0xf49c9003,0xfa63b006,0xf939100d,0x00000000}}, // 学部, 應急, 东北, --,
+ {{0xf918e00b,0xf4a07006,0x00000000,0x00000000}}, // 核定, 搭配, --, --,
+ {{0xf83c4002,0xfa76c010,0xf5898003,0xf499d00d}}, // 別人, 人情, を考, 报送,
+ {{0xf929a006,0x00000000,0x00000000,0x00000000}}, // 跨年, --, --, --,
+ {{0xf81d100a,0xf82e7003,0xf81f5004,0xfb7f300b}}, // 境保, 郵便, 装修, 的相,
+ {{0xf9336006,0xf920400a,0xf838300d,0xfa542002}}, // 多回, 正式, 类似, 願意,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb7e4010,0x00000000,0x00000000,0x00000000}}, // い物, --, --, --,
+ {{0xfb52d009,0xfb7cb006,0xfa70e004,0x00000000}}, // 精神, 威特, 平方, --,
+ {{0xf9108010,0x00000000,0x00000000,0x00000000}}, // 印字, --, --, --,
+ {{0xf8439008,0xf909800a,0xf5a0d004,0xfa67f002}}, // 單位, 制定, 理解, 變更,
+ {{0xfb7bd010,0xfa6ab006,0xf4c8e006,0x00000000}}, // [2e0] 丁目, 年最, 獎金, --,
+ {{0xf810f006,0xf4bbd006,0xf4bf5004,0xfb4c2002}}, // 些事, 市集, 内部, 當然,
+ {{0xf845c006,0xf93f1004,0x00000000,0x00000000}}, // 可使, 庆市, --, --,
+ {{0xfa674006,0xf815000b,0x00000000,0x00000000}}, // 式料, 多人, --, --,
+ {{0xf9462002,0xfa623006,0x00000000,0x00000000}}, // 權利, 鄉村, --, --,
+ {{0xf5a55003,0xf91bc00b,0x00000000,0x00000000}}, // が行, 大小, --, --,
+ {{0xfa55c006,0x00000000,0x00000000,0x00000000}}, // 雙方, --, --, --,
+ {{0xf90ff00c,0xfa81a003,0xf83c2003,0xfb666003}}, // 地域, 説明, 日以, の為,
+ {{0xf5c0e006,0x00000000,0x00000000,0x00000000}}, // 業者, --, --, --,
+ {{0xfa845003,0x00000000,0x00000000,0x00000000}}, // の教, --, --, --,
+ {{0xf597e014,0xf9462006,0x00000000,0x00000000}}, // 功能, 不含, --, --,
+ {{0xf9396006,0xfb50f009,0xf91e0006,0xf93ca006}}, // 一千, 关系, 無名, 一年,
+ {{0xfa7dd006,0xfa61b010,0xf941200a,0xfa53f006}}, // 實施, 了承, 安局, 高手,
+ {{0xf9428003,0xf9213014,0x00000000,0x00000000}}, // 変化, 公告, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa5f9005,0x00000000,0x00000000,0x00000000}}, // 的拳, --, --, --,
+ {{0xf9233009,0xf4abb007,0xfb82800a,0xf8241006}}, // [2f0] 环境, 提高, 效率, 車事,
+ {{0xf9421006,0xf4ae5002,0x00000000,0x00000000}}, // 鄉土, 預防, --, --,
+ {{0xf8243006,0x00000000,0x00000000,0x00000000}}, // 軍事, --, --, --,
+ {{0xf8260002,0xf81e000a,0xf5a2400d,0x00000000}}, // 國人, 市人, 三角, --,
+ {{0xfb59f003,0xf91aa006,0xfb64f006,0x00000000}}, // 知的, 輸入, 微笑, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf6e15016,0xf5964013,0xfa7f8008,0xfa6b4002}}, // 재밌, 啟詔, 無法, 當日,
+ {{0xfa709006,0xfa720006,0xfb5ac005,0xfa765004}}, // 熱情, 由本, 相簿, 场所,
+ {{0xf00c4016,0xfa6e8002,0x00000000,0x00000000}}, // 컴퓨, 新消, --, --,
+ {{0xfa5c3003,0xfb684006,0xfa71c00e,0xfb560004}}, // 時期, 寫真, 尾椎, 虽然,
+ {{0xfa5e8006,0xfb532011,0xf8133004,0xf936b006}}, // 分手, 政管, 会保, 付出,
+ {{0xf9478008,0x00000000,0x00000000,0x00000000}}, // 規定, --, --, --,
+ {{0xf59d9006,0x00000000,0x00000000,0x00000000}}, // 附近, --, --, --,
+ {{0xfb769006,0xfb4f4002,0x00000000,0x00000000}}, // 生物, 地球, --, --,
+ {{0xfb64b003,0xf81d600a,0xfa81000b,0xf826a006}}, // の生, 的作, 中文, 意事,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf935e004,0xfa831008,0xf934a00d,0x00000000}}, // [300] 多年, 縣政, 写字, --,
+ {{0xf9225007,0xfa4dd00a,0xf82d5002,0xf9459006}}, // 中央, 科普, 進一, 不少,
+ {{0xf80dc006,0xf920b006,0x00000000,0x00000000}}, // 店住, 索引, --, --,
+ {{0xfb739003,0xf5ad8006,0xfa50e002,0xf831d004}}, // 料理, 仲裁, 四月, 地使,
+ {{0xf81ed003,0xf9209002,0xf9348008,0xfa7db002}}, // う一, 業局, 租屋, 警政,
+ {{0xfa62d00a,0xf9189006,0xfb82c006,0xfa5b1006}}, // 效果, 登山, 與社, 一本,
+ {{0xf92e9006,0x00000000,0x00000000,0x00000000}}, // 逐年, --, --, --,
+ {{0xf4b48002,0xf9442006,0x00000000,0x00000000}}, // 高雄, 融合, --, --,
+ {{0xfa842003,0xf920e006,0xf5a4400d,0x00000000}}, // の旅, 索取, 棉花, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf6eb201d,0xf9383003,0x00000000,0x00000000}}, // 느낀, 実家, --, --,
+ {{0xf9430006,0xf9114006,0xf93c0004,0x00000000}}, // 文山, 朱子, 的合, --,
+ {{0xfb5fe003,0xfa637006,0x00000000,0x00000000}}, // 採用, 國最, --, --,
+ {{0xf928b006,0xf919800a,0xf4901004,0xf80ce003}}, // 兩年, 基地, 关部, 間以,
+ {{0xf6eb6016,0x00000000,0x00000000,0x00000000}}, // 느낄, --, --, --,
+ {{0xfb7b9002,0xf9464010,0xf82c9002,0xf8109002}}, // 一群, 総合, 創作, 動作,
+ {{0xf93f3004,0xf934e006,0x00000000,0x00000000}}, // [310] 内外, 四年, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf846100a,0xf809f004,0x00000000,0x00000000}}, // 在一, 当事, --, --,
+ {{0xf8120006,0xfa53e011,0xf93fc014,0x00000000}}, // 做事, 龙江, 的地, --,
+ {{0xf5b56007,0xf908a006,0x00000000,0x00000000}}, // 主要, 戶名, --, --,
+ {{0xfb561009,0xfa662003,0xfa728006,0x00000000}}, // 建立, お支, 統治, --,
+ {{0xf936800a,0xfa60500a,0xfb600006,0x00000000}}, // 高度, 篇文, 縣立, --,
+ {{0xf91c9002,0xfa85600d,0x00000000,0x00000000}}, // 入好, 息日, --, --,
+ {{0xf93cd00a,0x00000000,0x00000000,0x00000000}}, // 的各, --, --, --,
+ {{0xf5c2500a,0x00000000,0x00000000,0x00000000}}, // 蔬菜, --, --, --,
+ {{0xfa867003,0x00000000,0x00000000,0x00000000}}, // の改, --, --, --,
+ {{0xfa83200a,0x00000000,0x00000000,0x00000000}}, // 解放, --, --, --,
+ {{0xf91a1006,0xf92f3006,0xf919a011,0x00000000}}, // 別墅, 動合, 机制, --,
+ {{0xf93fc006,0xfa8a6006,0x00000000,0x00000000}}, // 七年, 截止, --, --,
+ {{0xfa7a8014,0x00000000,0x00000000,0x00000000}}, // 日期, --, --, --,
+ {{0xfb52800d,0x00000000,0x00000000,0x00000000}}, // 国美, --, --, --,
+ {{0xf4c4c010,0xf92da009,0xfb877002,0xf944b002}}, // [320] 配送, 专家, 美玉, 開展,
+ {{0xf946300e,0xfa5a0010,0x00000000,0x00000000}}, // 不妨, 曜日, --, --,
+ {{0xfb573003,0xf5949006,0xf80db003,0xf924c00a}}, // 発生, 存者, 世保, 本地,
+ {{0xfa838006,0x00000000,0x00000000,0x00000000}}, // 設施, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf8235003,0xfb672010,0xfb5ac00d,0x00000000}}, // 事一, の男, 书籍, --,
+ {{0xf9290003,0xf909c004,0x00000000,0x00000000}}, // を始, 户名, --, --,
+ {{0xf9464003,0xf9443006,0xfa656006,0x00000000}}, // が多, 配合, 二手, --,
+ {{0xf4a82007,0xf925e00a,0xfb6d2006,0x00000000}}, // 全面, 良好, 我真, --,
+ {{0xf9231004,0xf48b0004,0xfa64d006,0x00000000}}, // 药品, 制造, 楊梅, --,
+ {{0xfa864018,0xf910f00a,0x00000000,0x00000000}}, // の日, 政局, --, --,
+ {{0xfb764002,0xfb59a009,0xf8279004,0xf908f004}}, // 儘管, 认真, 从事, 制品,
+ {{0xf90a3018,0xf91b0003,0xf4c5a006,0xf492400d}}, // 場合, 以外, 積金, 声音,
+ {{0xf9445006,0xf59c000a,0x00000000,0x00000000}}, // 位名, 技能, --, --,
+ {{0xf9094009,0xf929c011,0xf9356004,0xf93bf00b}}, // 时候, 纪委, 党史, 最好,
+ {{0xfb6df003,0x00000000,0x00000000,0x00000000}}, // 外科, --, --, --,
+ {{0xfa68701c,0xf93c1010,0xfa6a4010,0xf90f3008}}, // [330] 行政, い合, 明治, 環境,
+ {{0xfb6bd002,0xfa7d4009,0xf92af006,0x00000000}}, // 兒童, 执法, 八年, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf11ce016,0xfb581006,0xf4b42006,0xf9161006}}, // 도쿄, 建物, 標金, 發出,
+ {{0xfa5c4014,0x00000000,0x00000000,0x00000000}}, // 最新, --, --, --,
+ {{0xf92f500a,0xf9125011,0xf9305006,0x00000000}}, // 化工, 县委, 得出, --,
+ {{0xfa67d00a,0x00000000,0x00000000,0x00000000}}, // 掌握, --, --, --,
+ {{0xf91f500a,0xfa71b00d,0x00000000,0x00000000}}, // 控制, 记日, --, --,
+ {{0xfb685003,0xfa865003,0xf8226004,0x00000000}}, // に生, の活, 万人, --,
+ {{0xf93d600f,0xf90f0004,0x00000000,0x00000000}}, // 规定, 稳定, --, --,
+ {{0xf92da011,0xf9207002,0x00000000,0x00000000}}, // 体制, 縣市, --, --,
+ {{0xf920b002,0xf927b002,0xf909a009,0xf9423006}}, // 中市, 利工, 汶川, 贈品,
+ {{0xfa867018,0xf9266010,0x00000000,0x00000000}}, // の方, の出, --, --,
+ {{0xf90a1006,0xf5960002,0x00000000,0x00000000}}, // 刷品, 語言, --, --,
+ {{0xf8454003,0xfa568006,0xfa6ef004,0x00000000}}, // の他, 期末, 关文, --,
+ {{0xf4977003,0xfb839003,0xf82eb006,0x00000000}}, // 画面, 予算, 新事, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // [340] --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb6af003,0xfb7f500a,0x00000000,0x00000000}}, // を用, 的精, --, --,
+ {{0xf92f8002,0xf93e200a,0xfb822006,0xf917e004}}, // 動和, 充分, 祈福, 强化,
+ {{0xf9260003,0x00000000,0x00000000,0x00000000}}, // 茨城, --, --, --,
+ {{0xfa618006,0x00000000,0x00000000,0x00000000}}, // 有情, --, --, --,
+ {{0xf5bbb009,0xf490600a,0xf595c004,0xf90ef004}}, // 执行, 关闭, 实行, 丰富,
+ {{0xfa5c800a,0xf5a71003,0xf5978004,0xf9196006}}, // 的意, が自, 江西, 以前,
+ {{0xf9297002,0xf90c4006,0xfb55b005,0x00000000}}, // 園市, 當年, 玻璃, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa51d006,0xf9298006,0x00000000,0x00000000}}, // 愛情, 親子, --, --,
+ {{0xf59ca00a,0xf8281003,0xf944b00a,0x00000000}}, // 的要, れ以, 下列, --,
+ {{0xf59e4004,0x00000000,0x00000000,0x00000000}}, // 节能, --, --, --,
+ {{0xf813e018,0xfa65f006,0x00000000,0x00000000}}, // 記事, 下方, --, --,
+ {{0xf8437003,0xf919e003,0xfb67c004,0x00000000}}, // の上, 特典, 动物, --,
+ {{0xfa4e4008,0x00000000,0x00000000,0x00000000}}, // 檔案, --, --, --,
+ {{0xfa59b002,0xf819d006,0x00000000,0x00000000}}, // [350] 十月, 品事, --, --,
+ {{0xf92bd010,0xf8107003,0xfb602002,0xfb7e7006}}, // を入, 研修, 產生, 砂石,
+ {{0xf59b9003,0x00000000,0x00000000,0x00000000}}, // 参考, --, --, --,
+ {{0xfb6f6006,0xf915e003,0xf919b006,0xfa886008}}, // 圖示, 国地, 藥品, 說明,
+ {{0xfa6f0011,0xf93c700d,0xfa62e005,0x00000000}}, // 新技, 摄像, 震撼, --,
+ {{0xf9467006,0xf5a0d006,0xf4c4a010,0xf82ef004}}, // 個女, 將近, 重量, 到位,
+ {{0xf924c010,0xf5911003,0xfa83c006,0xfa5d7006}}, // の回, 飛行, 本校, 的感,
+ {{0xf9234004,0xf919500d,0x00000000,0x00000000}}, // 积分, 报刊, --, --,
+ {{0xfa67b007,0xfa6bb002,0xf9156006,0xf9381003}}, // 上海, 創意, 幾年, 生地,
+ {{0xf91d800b,0x00000000,0x00000000,0x00000000}}, // 大利, --, --, --,
+ {{0xfb59c006,0xfa869006,0xf9217002,0xf4c8c006}}, // 批示, 種方, 管制, 美食,
+ {{0xfb5ab002,0xf594700a,0x00000000,0x00000000}}, // 健科, 高考, --, --,
+ {{0xfb7b5002,0xfa557003,0xf936500a,0x00000000}}, // 區管, 務次, 站地, --,
+ {{0xfa609002,0xf9132009,0xfa80d00a,0xfb60701f}}, // 三月, 综合, 正文, 中美,
+ {{0xf8257004,0xf92bb003,0xf80e6004,0x00000000}}, // 操作, を大, 比例, --,
+ {{0xfa671006,0xf8466003,0xf58c400a,0xfa86600b}}, // 上方, の人, 道路, 支援,
+ {{0xf596401c,0xfb82c006,0x00000000,0x00000000}}, // [360] 教育, 築物, --, --,
+ {{0xf9107009,0xf9090003,0xf9097004,0xf842f014}}, // 广告, 島市, 时尚, 是一,
+ {{0xf8311008,0xfb871011,0x00000000,0x00000000}}, // 台中, 苏省, --, --,
+ {{0xf93c0003,0xf9236003,0x00000000,0x00000000}}, // も多, の安, --, --,
+ {{0xf59cb002,0xf4a20006,0xf93b1006,0xf825400b}}, // 參考, 召集, 門口, 之中,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfa6f500a,0xfb595006,0x00000000,0x00000000}}, // 防止, 春秋, --, --,
+ {{0xf5c1e007,0xf9127003,0xfa5ed00a,0x00000000}}, // 代表, 便利, 病毒, --,
+ {{0xfb7ab020,0xfa64a00b,0x00000000,0x00000000}}, // 一番, 立法, --, --,
+ {{0xf9414006,0xf927e005,0xf9218006,0xfa6df006}}, // 三千, 兩岸, 陣子, 飲料,
+ {{0xf59a1018,0xf4c05006,0xf5b75004,0xf9458006}}, // 最近, 文集, 费者, 車子,
+ {{0xf8454003,0xf933b011,0x00000000,0x00000000}}, // の世, 党委, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf946e002,0x00000000,0x00000000,0x00000000}}, // 舊建, --, --, --,
+ {{0xf945a004,0xfb64700a,0x00000000,0x00000000}}, // 程度, 犯罪, --, --,
+ {{0xfb873006,0xf8259003,0x00000000,0x00000000}}, // [370] 及社, が一, --, --,
+ {{0xf5bc9006,0xfa605006,0xfb64d004,0xf945c014}}, // 入追, 劇情, 利益, 二十,
+ {{0xfa514003,0xf810e010,0xfb860006,0x00000000}}, // 北海, 返信, 刊物, --,
+ {{0xf8120008,0xfa6ec006,0x00000000,0x00000000}}, // 線上, 陳情, --, --,
+ {{0xf82d1006,0xfb7cb00a,0xf5b53004,0xf58ae004}}, // 仲介, 心理, 导航, 运行,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf92a0010,0xf8386003,0x00000000,0x00000000}}, // に出, 人中, --, --,
+ {{0xf92e2010,0xf4bc5007,0x00000000,0x00000000}}, // 取引, 的重, --, --,
+ {{0xf5c6f010,0xf5aa4006,0xfa509006,0x00000000}}, // の花, 長者, 北方, --,
+ {{0xf8234007,0xf90f2009,0xfa74e011,0x00000000}}, // 文件, 决定, 设施, --,
+ {{0xfa7de006,0x00000000,0x00000000,0x00000000}}, // 寧波, --, --, --,
+ {{0xfa7c3002,0x00000000,0x00000000,0x00000000}}, // 辦法, --, --, --,
+ {{0xf9350003,0xf9228002,0xfa765006,0xfa83600a}}, // 通常, 召募, 為最, 是指,
+ {{0xf8418009,0xf925a009,0xfb83a002,0x00000000}}, // 条件, 帮助, 計算, --,
+ {{0xfa874003,0xf5b2f002,0xfa6e9013,0x00000000}}, // の更, 東西, 極拳, --,
+ {{0xf80d700e,0x00000000,0x00000000,0x00000000}}, // 子信, --, --, --,
+ {{0xfa685010,0xf837d00a,0xfb58f00d,0x00000000}}, // [380] ヶ月, 主任, 人空, --,
+ {{0xf8415002,0x00000000,0x00000000,0x00000000}}, // 馬上, --, --, --,
+ {{0xfa6fc011,0xfa7e5004,0xf93ad006,0xfa555006}}, // 防治, 无法, 龜山, 感情,
+ {{0xf9155021,0xf92af00d,0x00000000,0x00000000}}, // 画像, 结婚, --, --,
+ {{0xfb74e00a,0xfa690002,0x00000000,0x00000000}}, // 江省, 究所, --, --,
+ {{0xfa6a4010,0xf4996004,0xf93b7010,0xf4975006}}, // 明日, 严重, 銀座, 基金,
+ {{0xf934f009,0x00000000,0x00000000,0x00000000}}, // 食品, --, --, --,
+ {{0xfa75a006,0xf8458002,0xf91f9006,0x00000000}}, // 活情, 華人, 加入, --,
+ {{0xf9442003,0xfa6ba008,0xf8471003,0xfa886006}}, // 不安, 戶政, に上, 身材,
+ {{0xfb583006,0xf908d002,0xf81c3006,0xf827d002}}, // 建置, 當局, 的介, 過一,
+ {{0xfa71b009,0xf59ac00d,0xf93ae00b,0x00000000}}, // 地方, 鲜花, 一天, --,
+ {{0xf4bd9006,0x00000000,0x00000000,0x00000000}}, // 的金, --, --, --,
+ {{0xf8339004,0xfa63b004,0xfa66a006,0xfb59d013}}, // 县人, 等方, 筋混, 肥皂,
+ {{0xf921f00d,0xfb685002,0xf93ee014,0x00000000}}, // 饭店, 醫生, 的大, --,
+ {{0xf90c8013,0x00000000,0x00000000,0x00000000}}, // 彷彿, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf83fb003,0x00000000,0x00000000,0x00000000}}, // [390] 素人, --, --, --,
+ {{0xf814a011,0xf818d003,0xfa69300b,0x00000000}}, // 业信, 初代, 完整, --,
+ {{0xf92d8009,0xfa81b003,0x00000000,0x00000000}}, // 发展, 連法, --, --,
+ {{0xfa885013,0xfb692007,0x00000000,0x00000000}}, // 煩惱, 部署, --, --,
+ {{0xf91f700a,0xf59a7004,0x00000000,0x00000000}}, // 加大, 东西, --, --,
+ {{0xfa5ab010,0xf92aa006,0xf92e4006,0xfa80c008}}, // 送料, 結合, 視引, 財政,
+ {{0xfa85b010,0xfb6e100a,0xf90a4004,0xfa5ca006}}, // の本, 南省, 胶南, 心情,
+ {{0xfa4f4006,0xf942d006,0x00000000,0x00000000}}, // 北投, 請先, --, --,
+ {{0xf4c0c006,0xf81c3006,0x00000000,0x00000000}}, // 資金, 的事, --, --,
+ {{0xf48ae003,0x00000000,0x00000000,0x00000000}}, // 海道, --, --, --,
+ {{0xf9432006,0xf949f010,0xf48a9006,0xfa783006}}, // 之名, 意味, 水量, 為本,
+ {{0xfa567012,0xfa4b3004,0xf5c7e00d,0xfa647006}}, // 高校, 结束, 猪肉, 上最,
+ {{0xfb89a006,0xf9173006,0xf9382004,0x00000000}}, // 小白, 為原, 烟台, --,
+ {{0xf9415003,0xfa5da00a,0xfa800005,0xfa5c900a}}, // 安心, 的成, 模擬, 的所,
+ {{0xf4bc8013,0x00000000,0x00000000,0x00000000}}, // 流鼻, --, --, --,
+ {{0xf936a002,0xf9458002,0xfa54d019,0x00000000}}, // 九十, 專家, 定执, --,
+ {{0xfa7a7006,0xfa629004,0x00000000,0x00000000}}, // [3a0] 學校, 吉林, --, --,
+ {{0xf4a4700a,0xf489f003,0xfa551006,0xf845c006}}, // 普通, 制限, 這本, 推介,
+ {{0xf92b4003,0xf9117006,0x00000000,0x00000000}}, // 道府, 鳳凰, --, --,
+ {{0xf941c003,0x00000000,0x00000000,0x00000000}}, // 安定, --, --, --,
+ {{0xfa88b006,0xf917400a,0xf93b1006,0x00000000}}, // 在根, 主席, 時前, --,
+ {{0xf4c5300a,0xf8143006,0xf910a008,0x00000000}}, // 消防, 記住, 房屋, --,
+ {{0xf5ba901b,0x00000000,0x00000000,0x00000000}}, // 船舶, --, --, --,
+ {{0xf90e6006,0xf84ae003,0xf9357020,0xf4bd100a}}, // 債券, な人, 神奈, 的高,
+ {{0xf914f004,0xf90cf002,0xf82de011,0x00000000}}, // 类型, 歷史, 电信, --,
+ {{0xf928c003,0xfa48e003,0xf93f9006,0xf92a5006}}, // 自分, を有, 的女, 青少,
+ {{0xf83e4004,0x00000000,0x00000000,0x00000000}}, // 级以, --, --, --,
+ {{0xfb658006,0x00000000,0x00000000,0x00000000}}, // 斯特, --, --, --,
+ {{0xf81b5003,0xf935a002,0xf9164006,0x00000000}}, // も一, 類型, 人名, --,
+ {{0xf921c00a,0xfb61b006,0x00000000,0x00000000}}, // 公布, 解答, --, --,
+ {{0xfa572009,0xf5af9009,0xf929e006,0xfa5c900a}}, // 办法, 记者, 寫出, 的技,
+ {{0xf9205002,0xf8249006,0xf5ae6011,0x00000000}}, // 中和, 個体, 防腐, --,
+ {{0xfb754018,0xf4b40006,0x00000000,0x00000000}}, // [3b0] 会社, 儘量, --, --,
+ {{0xfb75a002,0xf9267003,0xfa4e0013,0xfa63500a}}, // 候群, 注射, 恐懼, 合法,
+ {{0xf9254003,0xf916f003,0xf4bf3006,0xfb86c00b}}, // の受, 出力, 集集, 不管,
+ {{0xf9207003,0xfb644003,0xfa55d00b,0x00000000}}, // 昭和, の理, 星期, --,
+ {{0xf9266003,0xf91fb006,0xf4c06004,0x00000000}}, // の基, 採取, 理部, --,
+ {{0xfb58e006,0xfb5e1004,0xfa6b3002,0x00000000}}, // 人知, 传真, 階段, --,
+ {{0xf90dd003,0xf946e004,0xf82d200a,0xfa526006}}, // 歴史, 同志, 年以, 法施,
+ {{0xfb489005,0xf93cc006,0x00000000,0x00000000}}, // 耶穌, 送出, --, --,
+ {{0xf59b8003,0x00000000,0x00000000,0x00000000}}, // 概要, --, --, --,
+ {{0xf9300006,0x00000000,0x00000000,0x00000000}}, // 林口, --, --, --,
+ {{0xfa54e014,0xf5a4300b,0x00000000,0x00000000}}, // 香港, 三菱, --, --,
+ {{0xf928a006,0x00000000,0x00000000,0x00000000}}, // 被引, --, --, --,
+ {{0xf4b1900a,0xf925e010,0xf842d004,0x00000000}}, // 法院, の口, 责人, --,
+ {{0xfa6ab003,0xf9321002,0x00000000,0x00000000}}, // 場所, 體制, --, --,
+ {{0xf937a003,0xfa653003,0xf9359004,0xf5c5a006}}, // 記念, 予想, 变化, 路追,
+ {{0xf80f900f,0xfb6d2009,0xf838f004,0xf4a4f003}}, // 单位, 发生, 为主, の道,
+ {{0xfb794007,0xf4c8f006,0xfa6d3002,0xf9382004}}, // [3c0] 作用, 澎金, 聲明, 损失,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf8462009,0xf82f8018,0xf909e006,0xf92f300a}}, // 软件, 甲信, 帶回, 民共,
+ {{0xf945d00b,0x00000000,0x00000000,0x00000000}}, // 下去, --, --, --,
+ {{0xfa61a002,0x00000000,0x00000000,0x00000000}}, // 專案, --, --, --,
+ {{0xf90b2007,0xf5b95004,0xf90fc006,0xf91f000b}}, // 制度, 来越, 進入, 大地,
+ {{0xfb618006,0xfa66e004,0x00000000,0x00000000}}, // 檢索, 同比, --, --,
+ {{0xf8414007,0xfa4d9006,0xf91f000b,0x00000000}}, // 第三, 著手, 你可, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xfb66c006,0x00000000,0x00000000,0x00000000}}, // 央社, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf927d006,0xf91e3013,0xf948800b,0x00000000}}, // 優先, 大埤, 福利, --,
+ {{0xfa86d018,0xfa7d3007,0xfb723008,0xf928f006}}, // と思, 学校, 衛生, 親切,
+ {{0xf9400009,0xfb537018,0xf5c9e004,0x00000000}}, // 万元, 対策, 自身, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf9138003,0xfb77c011,0xf59f5006,0x00000000}}, // [3d0] 寿司, 东省, 筆者, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf90c400a,0xf82ad004,0xfb65c006,0xfb79c006}}, // 家具, 时代, 獨立, 私立,
+ {{0xfb604006,0xfb62a00a,0xf9255006,0x00000000}}, // 中秋, 督管, 物品, --,
+ {{0xfb59301c,0xfa842010,0xf4984006,0xfa7bc006}}, // 工程, の情, 卹金, 若波,
+ {{0xfa5c1006,0xf4a98008,0xf8121002,0xf946500b}}, // 球手, 醫院, 法人, 看好,
+ {{0xfb74100c,0xf591000f,0xfb860006,0xf91cc002}}, // 写真, 进行, 對策, 辦公,
+ {{0xfa6aa004,0x00000000,0x00000000,0x00000000}}, // 说明, --, --, --,
+ {{0x00000000,0x00000000,0x00000000,0x00000000}}, // --, --, --, --,
+ {{0xf9297004,0x00000000,0x00000000,0x00000000}}, // 表彰, --, --, --,
+ {{0xf8314006,0x00000000,0x00000000,0x00000000}}, // 即使, --, --, --,
+ {{0xfb7d2009,0xf9194003,0xfa648002,0x00000000}}, // 处理, 判定, 權所, --,
+ {{0xf949600a,0x00000000,0x00000000,0x00000000}}, // 福建, --, --, --,
+ {{0xfa701011,0xf917c002,0xf9466014,0x00000000}}, // 关注, 臺北, 不可, --,
+ {{0xfb721004,0x00000000,0x00000000,0x00000000}}, // 展示, --, --, --,
+ {{0xfa77701c,0xf4aa0003,0xfb6e4006,0xf94af004}}, // 人民, を通, 歐米, 美女,
+ {{0xf924e006,0xf58e5003,0x00000000,0x00000000}}, // [3e0] 天前, 子育, --, --,
+ {{0xf831f006,0x00000000,0x00000000,0x00000000}}, // 議使, --, --, --,
+ {{0xfa55f003,0xfb80a00b,0x00000000,0x00000000}}, // 生成, 等等, --, --,
+ {{0xf81f000a,0xf826e00a,0xfb5b5006,0x00000000}}, // 的主, 小企, 藥物, --,
+ {{0xfa70b003,0xf4994006,0xfa4a6003,0xf91b9003}}, // 女性, 學金, を提, 大和,
+ {{0xfa878004,0xf92e3003,0xf910c002,0xfa68b005}}, // 天津, 気分, 許多, 父母,
+ {{0xf9138003,0xfb5fa00a,0xfa534002,0x00000000}}, // 都府, 信用, 務所, --,
+ {{0xfa80f003,0xf90ed004,0x00000000,0x00000000}}, // 価格, 电台, --, --,
+ {{0xf9244003,0xf80ef003,0xfa64f00a,0xfa777006}}, // の商, ご了, 合格, 阻止,
+ {{0xf93ed021,0x00000000,0x00000000,0x00000000}}, // 商品, --, --, --,
+ {{0xf90fe00d,0x00000000,0x00000000,0x00000000}}, // 女士, --, --, --,
+ {{0xf92ad010,0xf93bd00b,0x00000000,0x00000000}}, // を取, 十八, --, --,
+ {{0xfa58c003,0xf92db009,0xfa648003,0xfb586002}}, // 作成, 提出, 変更, 基督,
+ {{0xfa66e00c,0xf80c1010,0xfb81e003,0xf9346007}}, // 今日, を使, 理由, 博客,
+ {{0xf92a0003,0xfa5f9004,0xfb57f004,0xf80da00b}}, // に基, 资格, 机票, 成人,
+ {{0xfa4e300a,0xfa599006,0xf81c7006,0x00000000}}, // 答案, 停止, 的住, --,
+ {{0xf80d9003,0xf8401004,0xfa517004,0xf90bb008}}, // [3f0] し上, 董事, 供求, 郵局,
+ {{0xf4c57003,0xf918f004,0xf5960004,0xfa869006}}, // 構造, 去年, 优良, 表情,
+ {{0xf59e8004,0x00000000,0x00000000,0x00000000}}, // 商行, --, --, --,
+ {{0xfa845006,0xf598e008,0x00000000,0x00000000}}, // 種情, 銀行, --, --,
+ {{0xf9135007,0xfa6fe006,0xfa799003,0xf9192002}}, // 能力, 唱歌, 特性, 此分,
+ {{0xfa82f004,0xf5af3008,0xf819000b,0x00000000}}, // 支持, 網路, 一位, --,
+ {{0xfa4e2002,0x00000000,0x00000000,0x00000000}}, // 五月, --, --, --,
+ {{0xfb6da004,0xfb56e002,0x00000000,0x00000000}}, // 当然, 費用, --, --,
+ {{0xf9308006,0xfa71800d,0x00000000,0x00000000}}, // 列出, 艾滋, --, --,
+ {{0xfb851002,0x00000000,0x00000000,0x00000000}}, // 運用, --, --, --,
+ {{0xf8386006,0xfa683003,0xf84ad00a,0xfa5e6004}}, // 此事, 小林, 自主, 雅思,
+ {{0xfa88a006,0x00000000,0x00000000,0x00000000}}, // 用方, --, --, --,
+ {{0xf9286003,0xfb7d2019,0x00000000,0x00000000}}, // 株式, 的矛, --, --,
+ {{0xfb75b006,0xf90da007,0xf5b43003,0xf92ae010}}, // 務登, 水平, 対象, を含,
+ {{0xf93f5006,0x00000000,0x00000000,0x00000000}}, // 包含, --, --, --,
+ {{0xf80d9004,0xfb68b006,0xf947d004,0x00000000}}, // 岗位, 獨特, 半年, --,
+
+ };
+ // table_hash = 860b-1885, unused_entries = 1630 (39.79%)
+
+static const uint32 kCjkDeltaBiSizeOne = 34; // One-langprob count
+static const uint32 kCjkDeltaBiIndSize = 34; // Largest subscript
+static const uint32 kCjkDeltaBiInd[kCjkDeltaBiIndSize] = {
+ // [0000]
+ 0x00000000, 0x00000000, 0x00001d1c, 0x00000242, // -- -- zh-Hant.un.un_800 ja.un.un_C00
+ 0x0000051c, 0x00001d15, 0x00001d42, 0x00000503, // zh.un.un_800 zh-Hant.un.un_700 zh-Hant.un.un_C00 zh.un.un_300
+ 0x00001d24, 0x00000524, 0x00000501, 0x00001d01, // zh-Hant.un.un_900 zh.un.un_900 zh.un.un_200 zh-Hant.un.un_200
+ 0x00000203, 0x00000542, 0x00001d37, 0x0000052d, // ja.un.un_300 zh.un.un_C00 zh-Hant.un.un_B00 zh.un.un_A00
+ // [0010]
+ 0x0000021c, 0x00000515, 0x00000201, 0x00001d0f, // ja.un.un_800 zh.un.un_700 ja.un.un_200 zh-Hant.un.un_600
+ 0x00001d03, 0x0000050a, 0x00000301, 0x00000215, // zh-Hant.un.un_300 zh.un.un_500 ko.un.un_200 ja.un.un_700
+ 0x00000224, 0x0000050f, 0x00001d06, 0x00001d2d, // ja.un.un_900 zh.un.un_600 zh-Hant.un.un_400 zh-Hant.un.un_A00
+ 0x00000506, 0x0000030f, 0x00000315, 0x00000537, // zh.un.un_400 ko.un.un_600 ko.un.un_700 zh.un.un_B00
+ // [0020] --- double_langprob_start=0022 ---
+ 0x0000020f, 0x0000022d, // ja.un.un_600 ja.un.un_A00
+ //
+ };
+
+// COMPILE_ASSERT(34 < (1 << 12), k_indirectbits_too_small);
+
+extern const CLD2TableSummary kCjkDeltaBi_obj = {
+ kCjkDeltaBi,
+ kCjkDeltaBiInd,
+ kCjkDeltaBiSizeOne,
+ kCjkDeltaBiSize,
+ kCjkDeltaBiKeyMask,
+ kCjkDeltaBiBuildDate,
+ kCjkDeltaBiRecognizedLangScripts,
+};
+
+} // End namespace CLD2
+
+// End of generated tables
diff --git a/browser/components/translation/cld2/internal/cld_generated_cjk_uni_prop_80.cc b/browser/components/translation/cld2/internal/cld_generated_cjk_uni_prop_80.cc
new file mode 100644
index 000000000..06ad80643
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld_generated_cjk_uni_prop_80.cc
@@ -0,0 +1,7133 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Created by utf8tablebuilder version 2.8
+//
+// Maps properties of all codes from file:
+// cld_generated_ctjkvz.txt
+// Accepts all other UTF-8 codes 0000..10FFFF
+// Space optimized
+//
+// ** ASSUMES INPUT IS STRUCTURALLY VALID UTF-8 **
+//
+// Table offsets for byte 2-of-3 and byte 3-of-4 are
+// multiplied by 16; offsets for 3-of-3 and 4-of-4 are
+// relative +/-127 from previous state.
+
+#include "utf8statetable.h"
+
+namespace CLD2 {
+
+#define X__ (kExitIllegalStructure)
+#define RJ_ (kExitReject)
+#define S1_ (kExitReplace1)
+#define S2_ (kExitReplace2)
+#define S3_ (kExitReplace3)
+#define S21 (kExitReplace21)
+#define S31 (kExitReplace31)
+#define S32 (kExitReplace32)
+#define T1_ (kExitReplaceOffset1)
+#define T2_ (kExitReplaceOffset2)
+#define S11 (kExitReplace1S0)
+#define SP_ (kExitSpecial)
+#define D__ (kExitDoAgain)
+#define RJA (kExitRejectAlt)
+
+// Entire table has 1172 state blocks of 64 entries each
+
+static const unsigned int cld_generated_CjkUni_STATE0 = 0; // state[0]
+static const unsigned int cld_generated_CjkUni_STATE0_SIZE = 64; // =[1]
+static const unsigned int cld_generated_CjkUni_TOTAL_SIZE = 75008;
+static const unsigned int cld_generated_CjkUni_MAX_EXPAND_X4 = 0;
+static const unsigned int cld_generated_CjkUni_SHIFT = 6;
+static const unsigned int cld_generated_CjkUni_BYTES = 1;
+static const unsigned int cld_generated_CjkUni_LOSUB = 0x80808080;
+static const unsigned int cld_generated_CjkUni_HIADD = 0x00000000;
+
+static const uint8 cld_generated_CjkUni[] = {
+// state[0] 0x000000 Byte 1 (row Ex offsets 16x small)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+X__,X__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 3, 2, 4, 5, 9, 13, 17, 21, 25, 29, 30, 30, 31, 2, 32,
+ 5, 3, 3, 3, 4,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+// state[2 + 2] 0x000080 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[3 + 2] 0x040000 Byte 2 of 4 (offsets 16x small)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[4 + 2] 0x100000 Byte 2 of 4 (offsets 16x small)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+// state[5 + 2] 0x000000 Byte 2 of 4 (offsets 16x small)
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 33, 35, 39, 43, 47, 51, 55, 59, 63, 67, 71, 2, 2, 2, 2, 73,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[6 + 2] 0x001100 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[7 + 2] 0x001140 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[8 + 2] 0x001180 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[9 + 2] 0x0011c0 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0,
+
+// state[10 + 2] 0x003000 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,231, 0, 0, 0, 0,
+
+// state[11 + 2] 0x003040 Byte 3 of 3 (property)
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+
+// state[12 + 2] 0x003080 Byte 3 of 3 (property)
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 4, 4, 4,
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+
+// state[13 + 2] 0x0030c0 Byte 3 of 3 (property)
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 4, 4,
+
+// state[14 + 2] 0x003100 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0,
+ 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[15 + 2] 0x003180 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[16 + 2] 0x000000 Byte 2 of 3 (relative offsets)
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+-14,-14,-14,-14,-14,-14,-14,-14, -14,-14,-14,-14,-14,-14,-14,-14,
+-14,-14,-14,-14,-14,-14,-14,-14, -14,-14,-14,-14,-14,-14,-14,-14,
+
+// state[17 + 2] 0x0031c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+
+// state[18 + 2] 0x003400 Byte 3 of 3 (property)
+ 0,229,231, 3,233,233,233, 3, 3, 3, 3, 3, 0, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3,208, 3, 3, 3, 3, 3,229, 3, 3, 3,
+ 3,208, 3, 3,208, 0, 3, 4, 208,208, 4,208,233,228,233,172,
+233, 0,229, 0, 0,208, 0, 6, 208,208, 0,227,229, 6,208, 3,
+
+// state[19 + 2] 0x003440 Byte 3 of 3 (property)
+217,228,233, 0,229,228, 6, 0, 6,208,229,229,229,227,208,227,
+ 0, 0, 4,194, 6, 6, 3, 0, 233,229, 0,229,229,229,233,229,
+ 6, 3, 6,229,233, 0,228,228, 4, 0, 4,218, 3, 3,208,208,
+208,229, 3, 0,229,229,229,229, 172,238,208,229,208,208,229,229,
+
+// state[20 + 2] 0x003480 Byte 3 of 3 (property)
+229,229,208,229,229,229,208,229, 208,208, 0,208,208,229,208, 3,
+ 3,229,228,229,229,229,208, 5, 6, 0,229,229,229,229,208,229,
+229,208, 3, 3,229,208,229,229, 208,229,208,233,208,208, 6,217,
+208, 3,229,229,233,233,233,227, 208,229, 5,208,228,208,208,229,
+
+// state[21 + 2] 0x0034c0 Byte 3 of 3 (property)
+ 3,194,208,228,208,208, 6,194, 208,229,208,208,208, 6,208,229,
+208,229, 3,208,208, 5,229,233, 208, 3,229,194,233,233,208,227,
+ 0, 0,229,208, 0, 6, 6,208, 229,208,226, 6,233,227,208,208,
+ 3,208,228,208,208,229, 0,208, 229,208,229,229,229,208,208, 3,
+
+// state[22 + 2] 0x003500 Byte 3 of 3 (property)
+229,233,208,208,229,208,229,208, 6, 6,208,229,229,208,229,229,
+229,208,229,233, 3,233, 3,233, 5,208,233, 3,233,229,208, 0,
+229,208,208,208,208, 0,233, 0, 208,229,208, 4,233,208,208,229,
+ 0,233, 5,233,233,233,229,208, 229, 0, 0,208,233,208,228,229,
+
+// state[23 + 2] 0x003540 Byte 3 of 3 (property)
+217,229,208,233,208,208,208,229, 208,208, 0,208,229,208,229,229,
+229,229,229,238,217,208,208,229, 231, 0,228,228,233, 4,233, 0,
+208,208,229,231,208,208,228,228, 6,208,229,208, 0, 0,228,233,
+227,229,208,229, 6, 0,208,208, 227,228,208,229,229,172, 3, 3,
+
+// state[24 + 2] 0x003580 Byte 3 of 3 (property)
+208,229,208,229,229,227,208,227, 217, 6, 6, 3, 3, 3, 3,208,
+208,227,208,208,227,208,233,227, 208, 3, 3, 3, 3, 3,231,229,
+227, 0,233,227,208,208,233, 0, 233,229, 0, 0, 6,208,219, 3,
+ 3, 3, 3, 3, 5,208,233,208, 227, 0, 0, 0,227,227,208, 0,
+
+// state[25 + 2] 0x0035c0 Byte 3 of 3 (property)
+ 0, 0,227,229,208, 0,233,233, 227,229,208,229,229,208,208, 6,
+ 5,218,208,208,233,227, 0,229, 229,227, 0, 0, 0,208,218, 3,
+ 3, 3, 0,208,228,229, 0,229, 208, 0,208,228,229, 0, 3, 3,
+208, 0,229,227, 0,208, 0, 6, 3, 3, 6,229,208,208,208,229,
+
+// state[26 + 2] 0x003600 Byte 3 of 3 (property)
+208,208,208,218,219,228,208,227, 0, 0,229,227,208,208, 0, 3,
+208,229, 3, 0,229,208, 0,219, 217, 0, 0,208,233,229,229, 5,
+229,208,228,208,229,208,208,208, 227,208,229,233,208, 0, 6, 0,
+208, 0,233,208,228, 0,208, 0, 208,208, 0,208, 6,219,217,229,
+
+// state[27 + 2] 0x003640 Byte 3 of 3 (property)
+208,227,208,208,228,208, 3,229, 208,229,228,208, 0,208,208,229,
+229,208,229,208, 0,208, 3,219, 218,208,208,208,208,229,208,208,
+229,229,229, 0, 0,208, 6,229, 208,229,229,229,208,217, 0,208,
+208, 0,208,208,227,208,208, 0, 208,208,229,233,208, 3,229, 3,
+
+// state[28 + 2] 0x003680 Byte 3 of 3 (property)
+229, 0,229,208, 5,208,172,229, 4,233,208,208,208,208, 5,208,
+208, 4,208,229,208,228,233,208, 229,172,208, 6,208,208,208,208,
+228,208,208,208,229,208,208,229, 208,229,208,208,208,208,208, 6,
+229,229,229,229,208,208,208,229, 229,208,208,228,208,208,229,208,
+
+// state[29 + 2] 0x0036c0 Byte 3 of 3 (property)
+ 6,208,229,208,229,229,219,208, 208,208,208, 3, 3,208,227,233,
+229,229,233,208,208,208, 0,208, 229,208,208,208,208,208,208, 6,
+ 6,217,208, 6, 6,208,208,208, 208,208, 0,208,208,208,229,229,
+229,208,208,208,229,208,229,233, 208,229,229,208,208,208,208, 6,
+
+// state[30 + 2] 0x003700 Byte 3 of 3 (property)
+ 6,208,208,208,208,208,208,208, 229,208,208,208,208,208,229,229,
+208,229,208,208, 6,208,229,229, 0,208,208,208,208,229,229, 6,
+208,208,208,229,208, 0,208,208, 208,208,229,208,208,217, 0,229,
+ 0,208, 0, 0,208,229, 0,229, 0, 0, 0,208,208,208,233,208,
+
+// state[31 + 2] 0x003740 Byte 3 of 3 (property)
+208,229,229, 0,208,229, 3,233, 229, 0,208,172,208,217,229,229,
+229,233,208,229,229, 0,208,229, 233,233,219,229,229,208,208,233,
+148,226,228,208,229,208,208,208, 219,208,229,228,233,233,208,229,
+229,229,229, 3,208, 0,229, 5, 233, 0,229,229,229,208,229,229,
+
+// state[32 + 2] 0x002000 Byte 2 of 3 (relative offsets)
+-30,-30,-30,-30,-30,-30,-30,-30, -30,-30,-30,-30,-30,-30,-30,-30,
+-30,-30,-30,-30,-30,-30,-30,-30, -30,-30,-30,-30,-30,-30,-30,-30,
+-30,-30,-30,-30,-30,-30,-30,-30, -30,-30,-30,-30,-30,-30,-30,-30,
+-30,-30,-30,-30,-30,-30,-30,-30, -30,-30,-30,-30,-30,-30,-30,-30,
+
+// state[33 + 2] 0x003780 Byte 3 of 3 (property)
+229,208,229,229,208,229,229,229, 208,208,208,208,208, 4, 6,208,
+229,208,208,208,208,229,208,229, 208,208,208,208,229, 3, 6,229,
+208,208,208,229,208,208,208,208, 208,208,233,208,233,208, 4,217,
+208,229,208,229,233,208,208,233, 219,229,233,233,233,208,208,233,
+
+// state[34 + 2] 0x0037c0 Byte 3 of 3 (property)
+208,233, 6,217,208,229, 6,208, 208,208,208,208,208,208,208,208,
+208,208,233,229,208, 3,208,208, 208,208,208,233,233,208,208,233,
+208, 4,233, 3, 6, 5,208,233, 233,208,229,208,229,208,219,208,
+229,229, 0,229, 0,233,208, 0, 208,229, 0,228,229, 0,208,208,
+
+// state[35 + 2] 0x003800 Byte 3 of 3 (property)
+ 0,229, 0, 3,208,208,229,208, 208,228,229,229, 0,229,228,208,
+ 0,229,208,208,208,233,228,233, 5,229,208, 0,208,233, 3,219,
+208,208, 0, 6, 6,208,208,229, 229,208,233, 3, 3,208,208,228,
+ 3,229,229, 3,208,208,233, 0, 0,229, 0,208,233,208,229,208,
+
+// state[36 + 2] 0x003840 Byte 3 of 3 (property)
+ 0,229,229,229,229,208,208,229, 229,208,229, 0,228, 6, 5,229,
+ 0,208,208,229, 0,229,208,208, 229,229,229,229,228,229,228,229,
+229, 0,233,233, 3, 0,229,229, 229,208,208,233, 0,208,208,208,
+208,229,229,233,227,208,208,208, 229,208,208,208,208,233,208,229,
+
+// state[37 + 2] 0x003880 Byte 3 of 3 (property)
+208,229,208,208,229,208,229, 3, 228,208,208,228, 5,229,208,229,
+ 5,233,229,208,208,233,229,208, 208,229,208,208,233,229,229,208,
+208,233, 4,233, 3,229,229,233, 208,208,208, 6,208, 6,229,208,
+229, 3,208,208,233,229,208, 6, 229, 3,208,229,229,208,208, 3,
+
+// state[38 + 2] 0x0038c0 Byte 3 of 3 (property)
+233,229,208,233,208,229,208,208, 208,233, 3,208,229,208,208,208,
+208,229,208,208,229, 3, 3, 3, 233,208,229,208,208,208,233,208,
+ 0, 3,228,229,229,228,208,219, 3, 3,226,229,208,229, 3,229,
+229,233,228, 3, 3,208,208,229, 3, 3,194,229,228,208,229,229,
+
+// state[39 + 2] 0x003900 Byte 3 of 3 (property)
+ 3,208,208,229,208,233,229,208, 227,229,229,208,208,229,208,229,
+229,208,229, 0,229, 0,229,194, 0, 0,233,229,229, 0,229,208,
+233, 0,233,229,208, 0,208,229, 208, 0,229,172,208, 6,229,233,
+208,208,229,227,229,233,208,208, 229,208, 0,229, 3, 6,229,229,
+
+// state[40 + 2] 0x003940 Byte 3 of 3 (property)
+233,228,208,229,229, 0,208,208, 208,208,229,227,233,229,208,229,
+233,208,208,208,208,208,229, 6, 3, 3,229,229, 0,229,208,229,
+228,208,229,233,233, 0,228,229, 208,228, 6, 5, 0,229, 0,228,
+229,228,228,228,208,229,208,229, 208,208,208, 6,229,229,229,229,
+
+// state[41 + 2] 0x003980 Byte 3 of 3 (property)
+229,229,208, 0,227,229, 0,208, 6,227,229,208,208,208, 0,229,
+208,208,229, 0,208,229,227, 0, 233,208,229,228,229,229,228,229,
+208,227,229,228,228,233,208,229, 0,229,229,229,229,229,238,229,
+229,172,228, 3,229,229,233,208, 229,229,208,229,208,229,228,229,
+
+// state[42 + 2] 0x0039c0 Byte 3 of 3 (property)
+229,229,208,229,229,227,229,208, 233,229,229,208,208,227,229, 0,
+ 6, 6,229,229,208,208,229,229, 229,229,208, 6,208, 6,238, 0,
+ 3, 0,229,229,208,229,229,229, 233,229,229,208,229,208,208,227,
+ 6,208,208,208,208, 0, 0,208, 208,229, 0,229, 0,233, 0,229,
+
+// state[43 + 2] 0x003a00 Byte 3 of 3 (property)
+ 0,229,227,227, 0,208,229,208, 5,229,229, 0,208,229,229,208,
+ 0,229,229,208, 0,227,229,228, 227,229,229,229,229,208,229,208,
+229,229,227,229,208,229,229,208, 208,208,208, 6, 6,229,208,233,
+208,208,227, 0,227,229, 0,229, 208,229,208,228,208,229,217,228,
+
+// state[44 + 2] 0x003a40 Byte 3 of 3 (property)
+ 5, 3, 3, 0,229,229, 0, 0, 208, 0,229, 0,229,228, 0, 6,
+229, 6, 0,208, 0,229,229, 0, 208,208,229,208, 0,229,227,219,
+229, 0, 0,229,229,229, 0, 0, 208, 0,208,227,229,229, 0,229,
+ 0, 0, 0,231,229,229, 0,208, 219, 0,208,229,229,228,208,229,
+
+// state[45 + 2] 0x003a80 Byte 3 of 3 (property)
+208,229,229, 6,229,228, 6,208, 229,233, 6,229,229,229,229,229,
+219,229,229,229,219,229,229,208, 4, 6,208,219,229,229,229,233,
+229,229,208,208,229,229,229,219, 229,229, 6,208,229,229,208,229,
+233,229, 3, 3,208,229,229,229, 208,229,229,229,208,233,229,233,
+
+// state[46 + 2] 0x003ac0 Byte 3 of 3 (property)
+233,229,229,208,228,229, 3, 3, 3, 3,229,229, 3,208,229,229,
+233,228,229,229,208,233, 4,233, 208,208,229,228,229,229,228,229,
+233,229,219, 6, 0,208,233,208, 172,229,238,229,208, 3, 5,233,
+ 0,229, 0,228,233,227,233,233, 229,208,208,227, 0,229,213, 3,
+
+// state[47 + 2] 0x003b00 Byte 3 of 3 (property)
+ 3,233,229,208,208,229,208,208, 229,229,228,208,208,229, 0,229,
+208, 6,229,229,229,208, 3,229, 229, 0,228,229,233,233,229, 6,
+229,229,228,229,219,228,228,194, 3,208,208,208,208,208,208,229,
+228,229,229,229,229,233,233, 6, 208,229, 3,229,233, 6, 6, 0,
+
+// state[48 + 2] 0x001000 Byte 2 of 3 (relative offsets)
+-46,-46,-46,-46,-42,-41,-40,-39, -46,-46,-46,-46,-46,-46,-46,-46,
+-46,-46,-46,-46,-46,-46,-46,-46, -46,-46,-46,-46,-46,-46,-46,-46,
+-46,-46,-46,-46,-46,-46,-46,-46, -46,-46,-46,-46,-46,-46,-46,-46,
+-46,-46,-46,-46,-46,-46,-46,-46, -46,-46,-46,-46,-46,-46,-46,-46,
+
+// state[49 + 2] 0x003b40 Byte 3 of 3 (property)
+ 6,227,208,233,208, 3, 3,208, 208,229, 0,229,233,219, 0, 6,
+ 3,229,208,229,208,228, 6, 3, 229,228,208,208,208,233,229,229,
+ 0,229,229, 6, 6, 6, 3,219, 0,229,229,229, 0,228,229,229,
+229,229, 0,218, 4, 6, 6,172, 228,208,229,233,227,227,229,229,
+
+// state[50 + 2] 0x003b80 Byte 3 of 3 (property)
+ 0,229,229,229,229,208,208,233, 223, 6, 4,228,208,233,229,229,
+229,229,208,208,227,229,208,229, 229,208,229, 0,229,229,229,208,
+218,208, 6,208,228,229,229, 0, 229,227,229,229,208,229,231,229,
+229,229, 0,208,229,233,233,229, 208,229,208,172,208, 3, 0,227,
+
+// state[51 + 2] 0x003bc0 Byte 3 of 3 (property)
+229,229,208,228, 0,229,229,208, 208,229, 0,229,208,228,208,208,
+208, 3, 3,229,229,229, 0,229, 229,229,229,229,208, 0,229,208,
+208,229,233,208,208,208,208, 6, 6, 6,208,229,233,229,229,229,
+233,229, 0,228,208, 5,208, 0, 228,208,229,229, 6, 3,208, 0,
+
+// state[52 + 2] 0x003c00 Byte 3 of 3 (property)
+ 0, 0,208,208,208,208,208,208, 208,208,208,229, 0,229,208,233,
+229,208, 0, 4,229,229,229,229, 0,227,229,208, 0,229,233,229,
+229,229,229,229,229,229,228,229, 229,229,229,229,229,228,228, 0,
+229,228,229,229,229,229,229,229, 4,229,229,229, 0,229,229,229,
+
+// state[53 + 2] 0x003c40 Byte 3 of 3 (property)
+229,208,229,229,229,233,229,229, 208,208,229,233,229,229,229,228,
+ 3, 3,208, 3,208,231,229,229, 208,229,229,229,208, 4,229,229,
+229,229,208,229,208,208, 0,229, 208, 6,229,229,229, 0, 6,229,
+229,208,229,229,229,229,208, 3, 229,208,208,229,229,229, 0,229,
+
+// state[54 + 2] 0x003c80 Byte 3 of 3 (property)
+229,229,208,229,208,229,229,208, 229,229,233,229,229,229,208,229,
+ 6,208,229,229,229,229,229,229, 208,229,229,228,208,228, 0,208,
+208,229,229,208,229,229,229,229, 229, 3,229,229,229,208, 3,229,
+229,229,229,229,229,208,208,208, 0,208,229,229,233,229,208, 6,
+
+// state[55 + 2] 0x003cc0 Byte 3 of 3 (property)
+208,229,229,228, 0,229,208,208, 229,218,208,208, 0,208,229,208,
+229,233,233,217, 6, 6,233,229, 229,233,228,229,228,227,208,238,
+ 0, 6, 6, 3,229,227,208,229, 229,208,227,208,229,208, 0, 0,
+ 6, 6, 3, 5,208,233, 0,229, 208, 0,228, 0,229,229,208,229,
+
+// state[56 + 2] 0x003d00 Byte 3 of 3 (property)
+233,229,208,229,229,208,208,208, 229,229,208, 6, 3, 3,208,229,
+ 0,228,229,229,229,208,208,229, 229,229,208,208,227,208,228, 6,
+ 0,208, 3, 3, 3, 0,208,229, 229,208,208,208,228,229,208,208,
+ 0, 0,229,208,208,233,229, 0, 229, 3, 3, 5,229,208,229,229,
+
+// state[57 + 2] 0x003d40 Byte 3 of 3 (property)
+208, 0,208, 0,208,229,229, 6, 3, 3, 0,208,229,229,233,208,
+208,208, 0,233,229,229,229,229, 208,208, 6, 3, 3,228,229,229,
+229,229,227, 0,228,229,229, 0, 229,229, 0,229,208,229,233,229,
+ 0,229,208, 0,229, 0, 0,229, 229,208,208,208, 4,229,229,229,
+
+// state[58 + 2] 0x003d80 Byte 3 of 3 (property)
+229,229, 3, 3,229,229,229,229, 229, 6,217, 3,229,208,208,229,
+ 3,208,229,228,229,208,229,208, 208, 3,194,229,208, 3,229,229,
+208,229, 3,229,227,227,228,229, 208,227, 5, 0,229,229,208,208,
+208, 3,208,229,229,233, 6, 3, 3,208,229,208,229, 6,208,229,
+
+// state[59 + 2] 0x003dc0 Byte 3 of 3 (property)
+228,217,229,229,208,229,208,229, 229,228,229,229,229,208,229,229,
+208,229,229,229,228,229,233,229, 208,227,227,208, 3,213, 3,229,
+229,229,229,229,229,229,208,208, 208, 3, 6, 0,229,229,229,229,
+229, 4, 3,208,208,208,229,229, 208,229, 6,208, 5,217,229,229,
+
+// state[60 + 2] 0x003e00 Byte 3 of 3 (property)
+208,229,229, 5,208,228,208,219, 229,208,229,229,208,229,229,233,
+208,229,229,208,217,208,208,229, 208,229,229,229,229,229,208,229,
+208,229,229,229,229,229, 0,208, 229,229,229,229,229,229,229,208,
+229,208,229,233, 3,229,229,229, 229,229,229,229, 0,229, 0, 4,
+
+// state[61 + 2] 0x003e40 Byte 3 of 3 (property)
+233,229,229,229,208,228,229,229, 208,229,229,229,229,229,229,229,
+229,229,229,229,229,229,229,229, 229,229,229,229,228,208,229, 0,
+ 0,229,229,233, 0, 0, 0,208, 226, 6,219, 0,208, 0,229, 0,
+ 3,229,228,208,208,229, 0,233, 229,233,229,229,208,229,229, 0,
+
+// state[62 + 2] 0x003e80 Byte 3 of 3 (property)
+229,208,229, 0,229,229, 6,208, 229,208,228,229,229, 6,208,229,
+229,229, 0, 5, 0,229,229, 0, 208, 6,229,208,229, 0, 0,208,
+229,208, 6,208,208,229,229, 0, 229,229,229,208,208,208,229, 3,
+208,229,233,208,208,208, 3,208, 5,229,208, 6, 6,217, 3,229,
+
+// state[63 + 2] 0x003ec0 Byte 3 of 3 (property)
+229,229,229,208,208, 6, 3, 0, 5,229,208, 0,229,229,208, 6,
+217,208,229,229,208,208,208,208, 6,208,194,217, 3,208,229,208,
+229,233,229,217, 3, 3, 3,208, 194,217, 6,229,229,208,208,208,
+229,208,208,208,217,208,229,229, 229,229,208,217,208,229,229,229,
+
+// state[64 + 2] 0x003000 Byte 2 of 3 (relative offsets)
+-54,-53,-52,-51,-50,-58,-49,-47, -62,-62,-62,-62,-62,-62,-62,-62,
+-46,-45,-44,-43,-42,-41,-40,-39, -38,-37,-36,-35,-34,-33,-31,-30,
+-29,-28,-27,-26,-25,-24,-23,-22, -21,-20,-19,-18,-17,-15,-14,-13,
+-12,-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4,
+
+// state[65 + 2] 0x003f00 Byte 3 of 3 (property)
+217,217,208, 3,208,217,208,208, 6,229,208,228,229,229,208,229,
+229, 0,229,229,208,229,229,229, 233,208,229,229,229,229,229,229,
+229,229,229,229,208,229,208,229, 229,229,229,229,229,229,229,208,
+229,229,229,229,208, 0,229,208, 229,208,229,229,233,229, 0,229,
+
+// state[66 + 2] 0x003f40 Byte 3 of 3 (property)
+208,208,229,208,229,219,229,229, 208,229,229,229,208,208,229, 6,
+229,229,208,208, 3,229,233,194, 229,208,208,229,208,229,217,229,
+229,229,208,208,208,229,229,208, 229,229,228,229,208,229,208,229,
+208,229,228,224,229,228, 3,228, 208,229,229,229,208,229,229, 0,
+
+// state[67 + 2] 0x003f80 Byte 3 of 3 (property)
+229,229,208,229,227,208,208,208, 229,229,208,208,229,229,229,229,
+229, 0,208,229,208,229,229,228, 208,229,229,229,229,208, 0,229,
+229,208,229,229,229,229,229,229, 229,229,229,229,229,229,228, 0,
+229, 3, 6, 3,219, 6,208,208, 208,208,229, 0,229,229, 0, 0,
+
+// state[68 + 2] 0x003fc0 Byte 3 of 3 (property)
+208,208, 0,229,229,229,229,229, 229,228, 0,233,229, 0,229,229,
+ 3, 0,229,229,219,229, 0,228, 208,229,208,229,208,208,208,233,
+ 3,208,208,229,233,229,229,229, 0,229,229, 0,229,229,229,229,
+ 0,229,229,229,229,208,229,229, 0,229,229,229,229,229,229,208,
+
+// state[69 + 2] 0x004000 Byte 3 of 3 (property)
+229,229,229,208,229,229,229,229, 229,229,229,208, 3,229,208,229,
+229,208,229,229,229,229,229,229, 229,229,229,229,229, 0,208,208,
+229, 0,229,229,229, 6, 6, 0, 0, 0,208,229,229,229,229,229,
+229,208, 0,208,228,228,229,229, 229,228,229,229,229,229,229,229,
+
+// state[70 + 2] 0x004040 Byte 3 of 3 (property)
+ 0,229, 0,229,229, 0,229,229, 228,229,229,229,208,229,229,229,
+ 0,228,229,229,229,229, 0,229, 0,229,229,227,229,229,208,233,
+227, 3,229,229,229,229,229,229, 229, 0,229,229,229,229,229,229,
+229,228, 0,229,229,229,229,229, 0,208,229,229,229,229, 0,208,
+
+// state[71 + 2] 0x004080 Byte 3 of 3 (property)
+ 0,229,208,229,229,229,208,229, 208,208,233, 6,229,229,229,229,
+208,229,229,228,229,229,228,229, 229,208,229,208,229,208,229,208,
+229,208,228,229,229,208,229,229, 208,229,229,229,229,208,229,208,
+208,229,233,229,208, 6,208,229, 229,229,229,229,208,208,229,229,
+
+// state[72 + 2] 0x0040c0 Byte 3 of 3 (property)
+208,229,229, 3,218, 6, 3,208, 229,229,229,208,229,228,229,229,
+ 3,229,229,229,229,229,229,208, 229,229,229,229,229,228,229, 0,
+229,229, 0, 0,229,229,229,229, 3,208,229,229,229,229,208, 4,
+208,229,208,229,229,229,229,229, 229,227,229,208, 0,229,228, 0,
+
+// state[73 + 2] 0x004100 Byte 3 of 3 (property)
+228,208,233,228,229,233,229, 0, 229,229, 6,229,229,229,229,208,
+229,208,233,229,208,208,219, 3, 229, 0,229,208,208,229, 3,229,
+229,208,208, 3,229,208,228,229, 229,229,228,228,208,208,229,229,
+229,229,208,208, 6, 3,229,229, 228, 6,229,229,208,208,229,229,
+
+// state[74 + 2] 0x004140 Byte 3 of 3 (property)
+229,229,229,229,229, 0,228,208, 233, 6,208,233,229,229,229,233,
+208,208,229,208,229,229,229,208, 0,229,229,229,229,229,228, 0,
+208,208,228,228,229,228,229,229, 229,229, 6, 3,208,229,229,229,
+208,229, 0,208, 0,229,208,229, 229,229,229,229,208,231,229,208,
+
+// state[75 + 2] 0x004180 Byte 3 of 3 (property)
+229,233,208,229,229,229, 3, 3, 208,229,229,217, 6,229,229,229,
+208,229,233,229,229,229,229,229, 229,229,229,229,208,229,229,229,
+229,229,229,208,229,208,229,229, 229,229,229,229,229,229,219,208,
+ 0,229,229,229,215,229,229,229, 229,229,229,208,229,208,229,228,
+
+// state[76 + 2] 0x0041c0 Byte 3 of 3 (property)
+229,229,208,208,227,229,208,208, 208,208,194,233,229,229,229,229,
+229,229,233,229,229,229,229,229, 229,208,229,227,229,208,229,229,
+ 0,228,229,229,208,208,233,208, 229,229,227,229,229,229, 0,229,
+229,233,208, 4,219,208,229,229, 229,229,229, 0,229,229,229, 4,
+
+// state[77 + 2] 0x004200 Byte 3 of 3 (property)
+208,229,229,208,208,229,208,228, 208,229,227,208,208,208,233,229,
+229,208, 0,208,229,228,229,229, 229,208,229,229, 0,208,233,208,
+229,208,208,229,208,208,229, 0, 6, 6,208, 0,208,229,208,229,
+229,229,229,229,228, 0,229,208, 229, 0,229,229,229,229,229,208,
+
+// state[78 + 2] 0x004240 Byte 3 of 3 (property)
+208,229,229,229,208,208,233, 6, 208,229,208,208,208,208,229,208,
+208,229, 0,208, 0,229,228,229, 208,208,208,229,228,208,208,229,
+208,229,208,208,215,208,208,208, 208,229,229,208,229,227,208,229,
+229,208,208,229,229,208,229,229, 208,229,228,208,208,228,229,229,
+
+// state[79 + 2] 0x004280 Byte 3 of 3 (property)
+233,229,229,229,229,228,229,229, 229,229,208,208,208,208,229,208,
+208,233,208,228,229,208,229,228, 229,229,229,229,229,208,229, 0,
+208,229,194,233,208,208,229,229, 229,208,229,229,208,229,208,233,
+229,208,229,229,208,229,233, 0, 208,208,229,208,227,229,229,229,
+
+// state[80 + 2] 0x004000 Byte 2 of 3 (relative offsets)
+-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44,-78, 45, 46, 47, 48, 49, 50, 51, 52,
+
+// state[81 + 2] 0x0042c0 Byte 3 of 3 (property)
+229, 0,229,229,229, 3, 4, 4, 229,229,229,229,208,229, 0,208,
+228,208,229,208,208,208, 0,208, 227,229,208,228,229, 4, 5,208,
+208,229,229,229,208, 0, 0,208, 228,229,229,229,208,229,208,208,
+229,229,229,229,229,208,229,229, 229,229,229,229,229,229,229,229,
+
+// state[82 + 2] 0x004300 Byte 3 of 3 (property)
+229,229,228,208,229,229,233,233, 229,208,208,219, 0,229,229,208,
+229, 0,229,228,208,229, 0,208, 229,229,208,208,229,229,228,229,
+229,229,229,229,229, 0,229,208, 229,208,208,228,233,229,229,208,
+229, 0,208,229,229,229, 6,231, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[83 + 2] 0x004340 Byte 3 of 3 (property)
+ 6, 6, 0,228,229,208,229,229, 208,208,229,208,229,229,229,194,
+ 0,208,208,229,229,208,229,229, 229,229, 0,229,229,208, 5,208,
+ 6,229,229,229,229,229,229,229, 0,208,228,208,229,229,229, 0,
+208,208,229,229,229,229,229,229, 229,229,229,229,229,229,229,229,
+
+// state[84 + 2] 0x004380 Byte 3 of 3 (property)
+229,229,229,229,229,208,208,229, 229,229,229,208,229,229,208,229,
+229,229,229,208,229,229,229,229, 229,229,229, 3,208,229, 3,229,
+208,229,229, 0,229, 6,229,229, 229,228,229,229, 0,208,229,229,
+229, 0,229,229,208,208,229,229, 229,229,229,229,229,229,208,229,
+
+// state[85 + 2] 0x0043c0 Byte 3 of 3 (property)
+208,229,229,229,229,208,229, 0, 208,229,233,233,229,229,229,208,
+208,229,229,229,229,228,229,208, 208,229,229,208,208,231,233, 0,
+229,208,208,229,229,208,229, 0, 208,229,229,229,233,217, 4,228,
+228,229,208,208, 0,229,229,228, 233,208,208,208,229,229, 0,229,
+
+// state[86 + 2] 0x004400 Byte 3 of 3 (property)
+229,229,229,229,208,208,219,229, 228,229,229,229,233,229,208, 0,
+229,229,229,208,229,229,229, 0, 229,229,208,208,228,208,229,229,
+ 0,208,228,208,228, 0,229,229, 229,229, 6,229,229, 0,229,229,
+229,208,229,229,229,229, 0,229, 229,229,229,208,208,229,229,229,
+
+// state[87 + 2] 0x004440 Byte 3 of 3 (property)
+229,229,208, 0,229,227,229,229, 0,229,229,229,229,229,208,229,
+ 0,233, 0,228,229,229, 6, 0, 229,229,208,228,229,208,208,229,
+229,229,229,229,229,229,229,228, 233, 0,229, 0,229,233, 6,229,
+229,208,229,229,208,208,228, 6, 219,229,228,229,229,229, 6,229,
+
+// state[88 + 2] 0x004480 Byte 3 of 3 (property)
+229,229,208,208,229,229,208, 6, 208,229,229,229,229,208,208,229,
+229,194,229,208,233,229,208,208, 208, 6,208,208, 0,208,229,208,
+208,229,228,208,229,229,229,229, 208,233,208,233,208,229, 6, 6,
+229,228,208,233,208,229,233,229, 229, 0,208,229,229,208,172, 6,
+
+// state[89 + 2] 0x0044c0 Byte 3 of 3 (property)
+217,194,208,229,229,208,229,229, 229,208,227,229,228,229,208,229,
+233,229,208,194,172, 6,231,229, 229,229,229, 0,229,229,229,208,
+229, 0,208,208,208,208,233,208, 6,208,208,208, 0,208,208,208,
+208,229, 0,208,229,233,233,208, 229,208,233, 6,229,208,229,229,
+
+// state[90 + 2] 0x004500 Byte 3 of 3 (property)
+208,208,229,229, 0,227,233,229, 228,229,233,229,208,233,208,229,
+229,229,229,229,208,229,229,208, 208,208,208,229,208,208,229,208,
+229,229,208,208,228, 0,208,208, 208, 5,229, 5,208,229,229,229,
+229,208, 0,229,208,233,229,229, 208,208,229,233,228,229,208,229,
+
+// state[91 + 2] 0x004540 Byte 3 of 3 (property)
+229,208, 6,194,229,208,229, 0, 229,229,229,229,228,208,208,208,
+ 6,208, 0,229,208,208,208,208, 208,208,229,208,208, 0,233,208,
+208, 0,208,219,208,229, 0,229, 229,229, 0,233, 0,229, 0, 0,
+229,227,229,208, 0,229, 0, 0, 229,208, 0,208, 0,229,228,208,
+
+// state[92 + 2] 0x004580 Byte 3 of 3 (property)
+208,208,229,229,208,208,229,233, 208,208,229,208,229,194,233,229,
+229,229,229,228,229,208,208,229, 229,229,229,233,229, 0,208,229,
+208, 0,229,233, 0,229,229,228, 229,229,229,229, 6,208,208,208,
+208,229,208, 0, 0,229,229, 0, 228, 0,208,229,208,208, 6,229,
+
+// state[93 + 2] 0x0045c0 Byte 3 of 3 (property)
+227,229,229,229,208, 0,229,208, 229,208, 0,208,208,229,208,229,
+229,208,229,229,227,208, 6,229, 229,228,229,208,208,228,208,233,
+208,229,208,229,229,228,208,229, 229,229,228,229,229, 6,208,208,
+229,229, 0, 4,208,229, 0,229, 229,208,229, 6,208,229,208,229,
+
+// state[94 + 2] 0x004600 Byte 3 of 3 (property)
+228,208,208, 0,229,229, 0,208, 208,229,229,208,231,228,208,228,
+228, 0,229,229,229,229,238,208, 228,208,229, 6,227,229, 6,229,
+229,228,208,208,229,208,208,208, 229,229,229,229,208,208, 0,229,
+ 0,233, 6,229,229,229,229,229, 229, 0,228,229,229,229,229,229,
+
+// state[95 + 2] 0x004640 Byte 3 of 3 (property)
+229,228, 0,229,229,229,229,229, 229,208, 6, 6,231, 3,208,229,
+229,229,229, 6,229,229,208,229, 229,208,229,208,229,217,228,229,
+229, 0,208,208,229,228,208,229, 208,208,229, 6,229,229,208, 0,
+208,229,228,208,228,229, 6,208, 0,228,233, 6,229,233,229,208,
+
+// state[96 + 2] 0x004680 Byte 3 of 3 (property)
+208,208,229,229,229,229,229,229, 228,229,229,208,208,229,208,208,
+229,208,208,229,229,219,229,229, 229,229,208,229,229,229,229,229,
+229,228,229,229, 3,229,229,229, 208,229,229,229,172,229,228,233,
+208,228,208,229,229,229,229,229, 228,229,208,229,229,229,229,208,
+
+// state[97 + 2] 0x0046c0 Byte 3 of 3 (property)
+229,148,229,229,229,229,229,229, 229,229,229,229,229,229,229,229,
+208,233,208, 6,208,228,229,229, 229,229,229,208,208,229,229,229,
+229,208,229,229,229, 0,233,208, 208,229,229,229,229,228,228,229,
+229,228,229,229,229,229,229,229, 208,229,229,229,229,208,229,229,
+
+// state[98 + 2] 0x004700 Byte 3 of 3 (property)
+208,208,208,229,208,229,229,229, 229,208,229,228,228,229,229,228,
+229,229,229,229,229,229,208,229, 229,229,229,229,229,229,229,228,
+229,229,228,231, 6, 6, 6, 6, 6, 0,229,229,228,229,229,208,
+229,208, 0, 3,208,208,229,229, 229,208,229,229,229,229,208, 5,
+
+// state[99 + 2] 0x004740 Byte 3 of 3 (property)
+229,229,208,208,229,229,208,229, 229, 0,208,229,208,229,229,229,
+229,229,229,229,229,208,229,229, 208, 6,229, 6,229,229,208,229,
+229,229,229,208,228,208,229,229, 228,228,229,229,208,229,229,228,
+229,208, 5,229,229,229,229,233, 229,229,233,229, 0,229,208,228,
+
+// state[100 + 2] 0x004780 Byte 3 of 3 (property)
+229,229,229,229,229,229,208,217, 208,208, 5,229, 6, 0, 6, 6,
+ 6,229,229,229,229,229,229,229, 229,229,229,208,208,229,229,229,
+229,229,229,208,229,229,229,229, 208,229,229,229,229,229,229,228,
+208,229, 6,229,229,229,229,208, 229,229,229,229,208,229,229,229,
+
+// state[101 + 2] 0x0047c0 Byte 3 of 3 (property)
+229,229,229, 0,229,229,229,208, 208,229,229,229,208,229,208,229,
+229,229,229,229,229,229,208,229, 0,229,208,229,229,229,208,229,
+229,229,227,229,229,229,228,229, 229,229,229,229, 0,208, 0,229,
+229,208, 6, 5,229,229,229,208, 229,229,229, 0,229,228,229,229,
+
+// state[102 + 2] 0x004800 Byte 3 of 3 (property)
+229,233,208,229,229,229,208,208, 0,229,229,229, 6,229,229, 0,
+229,229,229,229,208,229,172,208, 208,229,229,229,229,208,229,229,
+229,208,229, 0,229,229,229,229, 229,229,229,229,229,208,229,229,
+229,229,208,229,228,229,229,229, 229,208, 6,229,229,229,208,208,
+
+// state[103 + 2] 0x004840 Byte 3 of 3 (property)
+229,229,229,229,238,229,229,229, 229,229,229,208,229,229,228,229,
+229,208,229,229,229,208,229, 0, 229,229, 0,208,229,229,229,208,
+229,229,229,229,229,229,229,229, 229,208,229,229,229,208,229,229,
+229,233,229,229,229,208,229,229, 208,229,208, 6,229,208,229,208,
+
+// state[104 + 2] 0x004880 Byte 3 of 3 (property)
+ 6, 6, 6,229,208,229,208,229, 208,233,208,229,229,229,229, 3,
+229,208,208,208,229,208, 0,172, 3,229,229,208,229,229,233,233,
+208,229,229,172,208,208, 0,229, 229,208,208,208,208,208,229,229,
+208,229,229,208,208,228,208, 5, 208,208, 6,208,208,229,208,208,
+
+// state[105 + 2] 0x0048c0 Byte 3 of 3 (property)
+208,208,208,229,208,208,208,208, 208, 3,229,208,208,229,208,208,
+233,208, 6,208,208,208,208,208, 208,229,208,228,229,208,208,208,
+208,229,208,208,229,208,172,229, 208,228,229,229,233, 5,208,229,
+208,229,208,233,228,229,229, 0, 0,229,229,229,229,229,229,229,
+
+// state[106 + 2] 0x004900 Byte 3 of 3 (property)
+ 6,229,229,229,229,229,229,229, 229,229,229,229,208,229,228,229,
+229,229,229,229,229,229,229,229, 229,229,208,233,208,228,233,208,
+227,229,233,172,233,229,208,208, 229, 0,208,233,229,208,229,229,
+228,233,218,229,229, 0,229,228, 208,229,229,229,208,229,208,229,
+
+// state[107 + 2] 0x004940 Byte 3 of 3 (property)
+229,229,229,228,229,208,208, 0, 229,229,208,229,208,233,233,228,
+229, 0,208,229,208,229, 0, 0, 208,229,208,208, 3,229,208,208,
+229,208,233,229,229, 5, 0,208, 229,229,229,229,228,208,233,229,
+208,229,208,208, 5,229,228, 0, 229, 3, 0, 6, 6, 0, 6, 6,
+
+// state[108 + 2] 0x004980 Byte 3 of 3 (property)
+ 6, 6, 6, 0, 6, 0, 0,233, 208,208,229,229,229,229,208,229,
+229,229,229,228,229,229,229,208, 229,229,228, 6,233,229,233,208,
+208,229,229,229,208,208,208,229, 229, 6,229,229,229,228,229,229,
+ 4,229,229,229,228,229, 0, 0, 6,229,208,208, 0,233,172, 6,
+
+// state[109 + 2] 0x0049c0 Byte 3 of 3 (property)
+ 6,229,229,208,233,229,229,229, 6,233,229,229,208,208,208, 0,
+208,208,229,229,228,229,229,229, 229,208,208,229,229,208,228,229,
+208,208,228,208,208,208,229,233, 229, 6,229,219,208, 6,208,208,
+229,229,229,208,229,229,228,208, 229,227,228,229,229,208,208,208,
+
+// state[110 + 2] 0x004a00 Byte 3 of 3 (property)
+208,229,229, 5,228,208,208,229, 229,208,229,208,208,229,208,229,
+229,229,229,229,227,229,233,229, 229, 0, 6,229,229,208,229,208,
+208,208,229,208,229,229,229,208, 229,233,229,208,233,208,208,208,
+229,229,229,229, 0,229, 5,229, 229,229,229,231,229,229,229,229,
+
+// state[111 + 2] 0x004a40 Byte 3 of 3 (property)
+208,208,208,229, 6,208,229,229, 208,229,229,229,229,228, 0,229,
+229,208,229,229,229,229,229,233, 229,229,229,229,229,208,229,229,
+229,208,229,208, 0,229,208,208, 0,208,229,233,229,229,208,229,
+229,229,229,229,229,229,229,229, 229, 0,229,208,229,208,229,229,
+
+// state[112 + 2] 0x004a80 Byte 3 of 3 (property)
+229,229,229,229,233,229,229,229, 229,229,229,208,229,208,229,229,
+229,229, 0,229,229,229,229,229, 208,229,229,229,229,229,208, 3,
+229,229,208, 5,229,229,229,208, 208,228, 3,229,229,208,229,229,
+229,208,229,229,229,233,229,228, 228,229,229,229,228,229,229,229,
+
+// state[113 + 2] 0x004ac0 Byte 3 of 3 (property)
+229,229,229,229,229,229,229,208, 229,229,229,229,229,229,229,229,
+229,208,229,233,229,229,229,208, 229,229,229,229,229,233,229,229,
+229,229,229,229,229,229,229,208, 229,229,229,228,229,229,229,229,
+229,229,229,229,229,208,229,229, 229,229,229,229,229,229,229,229,
+
+// state[114 + 2] 0x004b00 Byte 3 of 3 (property)
+ 0,229,229,208, 0,229,229,229, 229,229,229,229,229,229,229,229,
+229,229,229,229,229,229,229,229, 229, 0,208,229,208,229,229, 0,
+229,208,228,229,229,208,229,229, 229,208,229,229,229,229,208,229,
+ 0,229,229, 0,229,229,208, 0, 229,229,208,228,229,229,229,208,
+
+// state[115 + 2] 0x004b40 Byte 3 of 3 (property)
+229,229,229,229,208,229,229,229, 229,229, 0,208,208,229,208, 3,
+229,229,229,229,229,228,229, 0, 229,229,229,208, 3,229,229,208,
+229,229,229,208,229, 0,229,229, 229,229, 6,208,208,229,229,229,
+228,229,229,208,229,228,229,208, 229,229,208,229,208,208,208,228,
+
+// state[116 + 2] 0x004b80 Byte 3 of 3 (property)
+208,208,229,208,229,208,208,208, 208,229,208,208,229,217,229,208,
+208,208,208,229,229,229,229,229, 229,229,229,208,229,229,228,229,
+229,229,229,229,229,208,208,229, 229,229,229,229,208,229,229,208,
+229,229,228,229, 0,229,208,229, 229,229,229,229,229,229,229,229,
+
+// state[117 + 2] 0x004bc0 Byte 3 of 3 (property)
+ 0,208,228, 6, 6, 6,229,229, 229,229,228,229,229,229,208,229,
+208,229,228,229,229,229,229,229, 229,229,229,228,229,208,229,229,
+229,229,229,229,229,229,229,229, 228, 3,229,233,233,229,229,229,
+229,229,229,208,229,229,208,229, 229,229,208, 3,208,229,229,229,
+
+// state[118 + 2] 0x004c00 Byte 3 of 3 (property)
+229,229,229,229,229,208, 0,233, 229,229,229,229,228,229,229,229,
+229,208,229,229,229,229,208,233, 229,208,208,208,208,229,229, 0,
+233,229,229,208,229,229,229,229, 208,229,208,229,229,229, 3,229,
+208,208,229, 3,229,233, 3,229, 0,208,233,229, 0,229,208,229,
+
+// state[119 + 2] 0x004c40 Byte 3 of 3 (property)
+229, 0,229,208,208,229,208,229, 208,229, 0,229,229, 0,229,229,
+229, 0, 0,229,208, 0,208,229, 229,229,227, 0, 0,229,208,229,
+208,229,229, 0,229, 0,208,208, 5,229, 3,208,229,229,229,208,
+228,229,208,208,219,229,233,229, 227,229,229,229,229,208, 0,208,
+
+// state[120 + 2] 0x004c80 Byte 3 of 3 (property)
+229,208,208,229,229,229,208, 3, 217,229,229,229,229,229,208,208,
+229,229,229,229,229,229,229,208, 6,229,229, 0,229, 6, 6, 6,
+ 0,231, 0, 0,208,208,229,229, 229,229,208,208,228,229,208,208,
+ 0,229,229,228,229,229, 3, 5, 229,229,229,229,227,228,228,208,
+
+// state[121 + 2] 0x004cc0 Byte 3 of 3 (property)
+229,229, 0,208,228,229,229,229, 208,229,229,229,229,229,229,229,
+229,228,229,208,229,229,229,208, 229,229,229,229,229,229,229, 0,
+229, 0,208,229,208,208,229, 0, 229,229,208,229,208,229,229,229,
+229, 0,229,229,229,229,208,229, 229,219,229,229,229,229,229,229,
+
+// state[122 + 2] 0x004d00 Byte 3 of 3 (property)
+229,229,229,229,208,229,229,228, 227,229,229,229,227,229,228,229,
+208,229,229, 6, 0, 0, 0, 0, 0, 0,229,229,229,229,229,228,
+229,228, 0,208,229, 0,229,229, 3,208,229,229,229,229,229,229,
+208,229,229,208,229, 0,229,229, 0,229,229,208,229,208,229,229,
+
+// state[123 + 2] 0x004d40 Byte 3 of 3 (property)
+229,229,229,229,229,229,208,229, 229,208,229,229,229, 0,229,229,
+229, 0,229,229,208,229,229,229, 229,229,229,208,229,229,229,229,
+229,229, 0, 0,229,229,229, 0, 208,229,229,229,229,229,229, 0,
+ 0,229, 0,229,229,229,229,228, 208,229,229,208,229,229,229,229,
+
+// state[124 + 2] 0x004d80 Byte 3 of 3 (property)
+229,208,229,229,208,229,229,208, 208,229,229,229,208,229,229,229,
+229,228,229,229,229,229,229,229, 229,229,229,229,229,229, 0,229,
+229,229,229,229,229,229,229,208, 229,229,229,229,233, 6, 0,233,
+228,208, 6,208,229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[125 + 2] 0x004e00 Byte 3 of 3 (property)
+140, 88,231,138,190,190,231,145, 103,136,136,140,231,138,114,232,
+130,134,231, 28,118, 68,136,190, 137, 68, 26, 72, 11, 16, 68, 25,
+231,117, 72, 0, 28, 28,143, 72, 72,231, 10,118,231, 68,231, 0,
+ 10,231,138,231, 28, 0, 47,231, 53,140, 28,134,116,128, 28, 47,
+
+// state[126 + 2] 0x004e40 Byte 3 of 3 (property)
+237,237,194,136,220, 88,231,231, 72, 28, 0,111,133,115, 85,140,
+ 16,231,118,120, 72,203,138, 15, 85,136,231,231,233,138, 68,126,
+ 16, 28,231,231, 0,231, 16,231, 0,204, 0,231,231,231,231,231,
+ 28, 65,231,136,231,231,231, 0, 231,231,231,231, 0,231, 97,231,
+
+// state[127 + 2] 0x004e80 Byte 3 of 3 (property)
+ 44,231, 75,231,231,231,134, 0, 103, 65,203,137,138,233,128, 98,
+ 0, 99,140,191,140, 53,191,191, 53,111, 10,118, 92, 72, 78,115,
+231,140, 68,207,138, 68,130, 28, 68,133,191,126, 35,137,126,231,
+191,191, 16, 72,233,207,191,229, 191,191,140,233,191,191, 72, 26,
+
+// state[128 + 2] 0x004ec0 Byte 3 of 3 (property)
+118, 68,231,230,201, 28,120, 68, 220, 0,137,137,231,144, 16,116,
+231, 63,220, 72,138,106,136,126, 88, 88,191,191,191,189,199,121,
+231,191,191,137,140,138,191,191, 207,191, 16,233, 10,172, 15,191,
+136,191, 88,221,191,220,140, 28, 191, 0,191,140,191,144,191,118,
+
+// state[129 + 2] 0x004f00 Byte 3 of 3 (property)
+233,135,232,206,233,191,212,207, 230,206,137,231,220,115, 53,137,
+ 68, 88, 0, 0, 0,204, 0, 26, 28,141,145, 0,231,117, 72, 16,
+ 28,231,220,231, 16, 0, 63, 0, 231, 0, 98,191, 0,221, 0, 68,
+144,207,231, 0,137, 0,111, 0, 88,231, 97, 0,137, 68,231, 0,
+
+// state[130 + 2] 0x004f40 Byte 3 of 3 (property)
+231, 0,231, 88, 0, 0,126, 87, 27,232, 0, 0,211,140,137, 88,
+105, 68, 0, 65, 25,136,231,201, 209, 35,120,126,135,199,197, 72,
+118, 0,204,118,199,220, 0, 0, 231, 85,232, 0,130, 0,211,121,
+118,231,191,111,221, 87,164,207, 133,128, 72,233,111, 72,204,136,
+
+// state[131 + 2] 0x004f80 Byte 3 of 3 (property)
+128, 0,233,115,199, 0,121,207, 115, 0,233,136, 0, 88,220,209,
+221, 87,231,231,232,231,121,118, 198, 0,231,140, 0,134, 0, 0,
+108, 15, 0, 72, 0, 72, 98, 16, 133,235, 0,231,191,203,135, 68,
+ 0, 0, 0, 0, 0,140, 87,127, 0,231, 0, 0, 0,231,231,136,
+
+// state[132 + 2] 0x004fc0 Byte 3 of 3 (property)
+231,232,146,134,115,233,231,204, 231,231, 68,204, 0,231,189,118,
+118,201, 0,232,231, 0, 0,136, 115,232,201,232,232,140,118,111,
+ 75,140,232, 44,232,232,231,231, 191, 72, 72,231,210,128,136,138,
+ 0,144,231,116,204, 47,197, 0, 68,231, 92,231,231,231,121,231,
+
+// state[133 + 2] 0x005000 Byte 3 of 3 (property)
+232, 0,232, 0, 0,198,121, 0, 0,146, 0,143,206,137,231,199,
+191, 27,136,230,199,207, 88,207, 130,138,126,206,233,191,191,137,
+231,124,204, 47, 15,197,138,233, 232, 85,118, 87,206, 47,233,220,
+204,204,231, 0,231,191,101,128, 133,172,133,167,125,220, 16,212,
+
+// state[134 + 2] 0x005040 Byte 3 of 3 (property)
+233,199,191,201,172, 72,191,118, 119,143,191,191,208,190,206,134,
+172,172,191, 72,231, 97,231,233, 191,231,125, 72,136,231, 72,172,
+191,172,233,191,191,136,231,235, 172,191,190,231,231,198,231,231,
+198,198,105,231,146, 91,138,144, 231,231,198,191,198,146, 0, 16,
+
+// state[135 + 2] 0x005080 Byte 3 of 3 (property)
+ 51,231,233,231,231, 68,231,231, 191,231,231,231,221, 88,231,231,
+231,143,231,231,231,190,204,191, 87, 87, 72, 72, 72,172, 72,231,
+231,231, 25, 72,207,235,207,231, 16,191,198,203,100,143, 0,221,
+ 0, 0,126, 75,232,107,231, 87, 0, 0, 0,118, 0,231,146,231,
+
+// state[136 + 2] 0x0050c0 Byte 3 of 3 (property)
+231, 0,204,231,231,131,231,231, 231,232,198,231,231, 15,231,136,
+231, 76,231,231,231,116,170,232, 231,231, 88, 0,231,231,238,231,
+231, 0,231,231, 0,129,231, 68, 232,231,231,231, 0,197,206,231,
+233,127,231,191,231,118,231,231, 231, 23,231, 68,231,231,232, 0,
+
+// state[137 + 2] 0x005100 Byte 3 of 3 (property)
+ 87,198,119,231,146,231,206,231, 232, 50,233,199,233,191,233,191,
+199,191, 68,191,206,121,231,191, 129,231,101,220,220, 0,231, 87,
+ 0, 51,231, 0,207,233,231, 0, 0, 0,107,233, 0, 0,191,212,
+207,128,119, 72,207,199,231,121, 174,232,189,199, 50, 0,231, 72,
+
+// state[138 + 2] 0x005140 Byte 3 of 3 (property)
+201, 68,231,138,137,140,136,119, 137,136,231,134, 78,138,116, 0,
+ 15, 98,127,231,118,232,128,204, 207, 0,114,233,136, 0, 0,231,
+235, 0,118, 0,231,137, 0, 21, 140, 75,215,136,138,138,126,220,
+ 16,140,220, 28, 16,136,118,140, 88, 63, 0, 28,140, 98,212, 0,
+
+// state[139 + 2] 0x005180 Byte 3 of 3 (property)
+ 67, 0,231,231,231,145, 15,206, 72, 67,143, 0, 16,136,231,204,
+231,109,136,231, 0,130,231, 88, 231,145,231, 10, 26,231,221, 0,
+134,231,199,212,138,136,235,212, 92, 72,118,198,137,207, 72, 98,
+118,231, 72, 28, 44, 28, 74,136, 0, 0, 0, 16,207,201,199,220,
+
+// state[140 + 2] 0x0051c0 Byte 3 of 3 (property)
+133, 0, 0,211,101,232, 99,207, 211, 72,230,111,140,146, 0, 28,
+231, 72, 0, 0, 0, 0,231,231, 0, 0, 0,100,194,138,231, 0,
+ 72,134,231, 0, 16,231, 15,116, 0,231,116,179,231, 72,231, 16,
+134, 76, 0,120, 0,198,140,231, 137, 88,137, 10,191, 97,203, 72,
+
+// state[141 + 2] 0x005200 Byte 3 of 3 (property)
+136,118,235, 53,231,231,137,137, 105, 0, 88,204, 0,191,197, 0,
+ 0,140, 10,211,231,231,231,138, 16, 10, 16, 28, 0,103, 0, 0,
+ 16, 0, 0, 0,103,146, 0,231, 67,136, 75, 28, 0,231,118, 0,
+115, 0,211,190, 0, 0,139,136, 88, 53, 88,137, 0,220,231, 0,
+
+// state[142 + 2] 0x005240 Byte 3 of 3 (property)
+ 0,118, 16, 88,198, 0, 0,143, 0,211,100,119,111,136,121,231,
+235, 16,211, 0,126, 0,138,231, 0,133,207, 68,206,130,231,207,
+231,233, 0,117, 15,145,191,128, 207,132,126, 0,220, 0,233,134,
+101,172,100,233,204,143, 0,211, 0,220, 0,220, 0,201, 0,120,
+
+// state[143 + 2] 0x005280 Byte 3 of 3 (property)
+220, 0,231, 27,204,191, 0, 87, 111, 76,206,191, 72, 77,212,212,
+ 0,122,233,221,198,191,191,191, 191,191,191,135,191, 72, 26,138,
+140, 26,231,135,212,220,199,212, 28,134,140,134,232,206, 0,235,
+ 0,142, 98, 28,117,206,212,212, 220, 15, 0,231,199,231,111, 28,
+
+// state[144 + 2] 0x005000 Byte 2 of 3 (relative offsets)
+-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+
+// state[145 + 2] 0x0052c0 Byte 3 of 3 (property)
+232,119, 0,140, 0,116, 0,140, 0,103, 0, 72, 0,231,220, 0,
+ 72,207,138, 0, 0, 87,233,204, 137, 87, 0,129, 0,109,122,106,
+231, 0, 87,161,137, 0,232, 15, 212,191,207,128,128,133,207,128,
+206,221, 44, 76, 0, 20,220,221, 75,231,201,127, 0, 0, 68, 97,
+
+// state[146 + 2] 0x005300 Byte 3 of 3 (property)
+ 72,203,117, 0,148,138,118,231, 67, 0, 0,204, 0,201, 0,204,
+201,231, 0, 0, 0,189,140,136, 0,126,231, 0, 0,111, 0, 0,
+103,137, 0,111,231, 0,231,231, 0, 0,126, 0, 0,204, 72, 25,
+ 0,129, 0,231, 0,231,231, 0, 231, 35, 71, 65, 0,231,118,103,
+
+// state[147 + 2] 0x005340 Byte 3 of 3 (property)
+ 21,138,231,137,221, 78,231,118, 97,118,137, 0,231, 87, 28, 26,
+231, 51,105,136,107, 16, 16,140, 15, 0,140, 0, 67, 0,167,235,
+135,144, 98,231, 63, 0,126, 16, 148,231,231, 26,231, 0,233, 68,
+140,137,232,126, 65, 88, 0,144, 88,129,235, 21,211,232, 0, 68,
+
+// state[148 + 2] 0x005380 Byte 3 of 3 (property)
+ 0, 0, 16,231, 68, 28, 10, 0, 0, 16, 0, 16, 72,235, 0, 0,
+220,220,204,191, 0, 63,190,231, 115,210,137,220,207,121, 0,137,
+197,207, 72, 0,231, 68, 72,207, 65,103,191,220,220,119,191,220,
+232,191,119, 15,207,191,198,220, 212,233,220,138,212,133,212, 28,
+
+// state[149 + 2] 0x0053c0 Byte 3 of 3 (property)
+212,124,145,122,191,207,191, 0, 140,134,138,140, 65,137, 15, 72,
+ 0, 28, 0,191, 68,207,137,137, 28, 65,231, 68, 0,231, 0,201,
+ 72, 87,143,140, 68, 68,144,207, 209,103,126,134,115,130,118,140,
+138,103,136,136, 0,233,142,145, 115, 63,231,233,207,191, 0, 0,
+
+// state[150 + 2] 0x005400 Byte 3 of 3 (property)
+191,120,207,118,140,220,199,220, 137,136,138,121,136, 88, 67, 68,
+140,137,170, 72,210, 16,199, 72, 0, 0, 0, 88, 0,111, 85, 88,
+103,199, 0,231,220,220,126,144, 16,209, 0, 88,128,199,209, 16,
+ 0,132,207, 75, 63,118,119, 0, 136,103, 0,134,138,202, 88,231,
+
+// state[151 + 2] 0x005440 Byte 3 of 3 (property)
+118, 0,146,206, 0,207,136,221, 138, 44,138,199, 0,231,121, 0,
+ 72,117,231,233,231, 72, 0,207, 26, 0, 0,191,191, 0, 0,117,
+ 0, 0,144,221,207, 0,122, 0, 137, 0,101,233,207,128,128, 72,
+191,130,233, 88,128,118,231,121, 210, 0, 0,201,137,137,221,204,
+
+// state[152 + 2] 0x005480 Byte 3 of 3 (property)
+115,199,233,207,201, 0,103, 0, 231, 0,231, 53,139, 0, 88, 72,
+209,231,130, 0,207,130,132,231, 0,191,130,191,231,220,231,231,
+ 0,232,231,235,172,116,206,130, 128,119,130,201,126,232, 0,120,
+ 0,118, 44,136, 0, 0, 0,231, 67, 0,231,206,231,137,204,199,
+
+// state[153 + 2] 0x0054c0 Byte 3 of 3 (property)
+ 68,136,233,231,115, 0,130,130, 144, 88, 0, 0,191, 28,118,221,
+220, 72,207,235,235, 0,211,128, 231,235,207, 0, 0,220,210, 72,
+ 0,107,231, 0, 0,118,144,207, 68,121,118, 0, 0,126,115, 0,
+ 0,221, 68, 0, 0, 0, 0, 0, 0,231,138, 0,118,206,231,232,
+
+// state[154 + 2] 0x005500 Byte 3 of 3 (property)
+231,208,231, 0,109, 0, 35,136, 0,118, 0, 0, 0, 0,204,199,
+ 68, 72, 0, 0,130, 0,116, 0, 0, 0, 0,191, 0, 0, 0, 0,
+212, 0,207, 0, 72, 0, 0,206, 220, 0, 0,231,121,211,144,136,
+207,136,220,121,212,199,207, 25, 131,233,220,220, 0,231, 88,231,
+
+// state[155 + 2] 0x005540 Byte 3 of 3 (property)
+231,210, 0,130, 88,191,137,233, 0,191,118,233,231,231,207,107,
+232,231, 0, 44, 0,210,202,204, 220, 0, 0, 0,201,231, 96, 27,
+220,132, 0,129,118,118,132,207, 220,220,130,207,191, 0,179,212,
+191, 0,207,220,212,206,199,206, 72, 0,220,206,111, 0,121,207,
+
+// state[156 + 2] 0x005580 Byte 3 of 3 (property)
+115,232,120,115,140,231,167,121, 0,136,118,105,207, 0,199,233,
+212,221,207,207, 20,191,133,128, 138,206, 87,212,136,126,231,206,
+ 0, 0, 0, 0, 0, 0,221,103, 204,116, 87,116, 91, 0, 27, 0,
+117,199,129,208, 0,121, 15, 16, 0,191, 0,118, 0,199,148,231,
+
+// state[157 + 2] 0x0055c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0,194, 88,129,204, 0,199,221, 0,232, 0, 27,207,
+207,208,199,130,206,207,207,128, 128, 72, 97, 72, 68,208, 0,201,
+ 0,121,231, 51,189,210,209, 0, 130,209,207,220,233,207, 0,121,
+ 0, 0,199,207, 0, 0,127,199, 0,232, 0, 0, 0,126,231, 0,
+
+// state[158 + 2] 0x005600 Byte 3 of 3 (property)
+199, 0, 0, 0, 0,220, 87,231, 206,126, 0, 0,199,129,118,232,
+ 0,231, 0,221,107, 0,204, 76, 92, 0,220,144,211, 0,233,118,
+ 0, 0, 0,220,233, 0, 0,199, 0,146, 0, 0, 0,220,204,119,
+204,145,137,231,118,231,209, 0, 129,206, 0,118,231, 0, 0,118,
+
+// state[159 + 2] 0x005640 Byte 3 of 3 (property)
+231,121,117, 0, 0, 0, 0,231, 0,232, 0, 0,105, 0,206, 0,
+231, 0,231,129,220,231, 0,121, 221,230, 0, 44, 72, 0, 0, 0,
+221, 0,130,207,206,210,232,220, 97,209,126,206,130, 0,207,199,
+207,206,232, 0, 87, 0,130, 0, 121,121, 92,207,191,207,220, 0,
+
+// state[160 + 2] 0x005680 Byte 3 of 3 (property)
+121, 0,220,220,207,230,198,119, 220, 0,231,212,220,207,209,209,
+127,220,220,207,231,130, 0,210, 0,121,204, 0,210,231, 0,207,
+231, 0,101,128,220,143,206,221, 129, 0, 0, 0,220,233,121,220,
+ 0, 0, 0,238, 75, 0,204,209, 0, 0, 0, 0, 68, 0, 0, 0,
+
+// state[161 + 2] 0x0056c0 Byte 3 of 3 (property)
+194,105,121,101, 0, 0,231, 0, 204, 19,118, 0,221,121,231,231,
+ 0,121, 0,194,233, 0,231,206, 198, 0,137,140, 0,204,136,233,
+134,199, 26,117,120,191,212,120, 128,133,208,230,128, 26,198,232,
+135,128, 15, 24, 28,220,207,207, 0,189,136,231, 0, 71, 16,201,
+
+// state[162 + 2] 0x005700 Byte 3 of 3 (property)
+203, 0, 0,140,206, 0, 16,211, 118,197,231, 75,220,122, 0, 24,
+ 0,231,107,129, 0,231, 19, 0, 127, 0, 0, 0,206, 0, 0,137,
+ 0,231, 0,128, 0, 0,231, 15, 138, 72,207,231,231, 68,210,232,
+137, 0, 0,141,232, 0, 0,231, 231,199, 16,206, 0, 0,144, 0,
+
+// state[163 + 2] 0x005740 Byte 3 of 3 (property)
+144,211, 44, 0, 0, 0, 0,140, 0, 0,136,211,204,141,118, 72,
+126,111,221, 0,221, 0, 0, 16, 0,231, 28,133, 0, 98, 72, 72,
+ 72, 85, 0, 0,126,231,134,231, 124,189,136,232, 0,207,231,120,
+174, 0, 0,207,221,204, 0,209, 0, 0,231,199,232,211, 0,231,
+
+// state[164 + 2] 0x005780 Byte 3 of 3 (property)
+210, 0,140,144, 72,212, 0,231, 237,231, 0,140,207, 0,212, 0,
+231,231, 72,233,233,231, 0,231, 231,231,199, 72,231,191,231,207,
+206,212, 88, 53,232, 0,133,220, 0,191,235, 98,231,212,118,212,
+191,212, 0,191,220,208,128,231, 207,128,207,207,191,191,191,207,
+
+// state[165 + 2] 0x0057c0 Byte 3 of 3 (property)
+ 98, 0,199,126,207,220,232,191, 233, 0,235, 88,233, 0,135,204,
+ 0, 0,197,231, 89,121,191,191, 0,179,212,212,105,191,207,137,
+115,207,191,191,127,212, 0,210, 231,231, 0, 0,231,199,231, 0,
+ 0, 0,231,231,105,204,232,143, 206,115,137,232, 24,221,231,231,
+
+// state[166 + 2] 0x005800 Byte 3 of 3 (property)
+109,221,136,206,231,143,140,121, 231,204,204,231, 0,233,206,231,
+231,207, 0, 0, 0, 65, 0, 0, 231,232,207,191,191, 72,229, 72,
+220,118,191,220,140,128,231,231, 0, 0,136,207,230,231, 0, 51,
+ 74,146, 0,231,146,134, 0,231, 231,231,117, 0, 0,231,231,204,
+
+// state[167 + 2] 0x005840 Byte 3 of 3 (property)
+116,117, 0,231,231,231, 0,231, 221,231,143,232,118,207, 0,221,
+ 0,115,231, 0,140,231, 0,109, 115,116,116, 0, 0, 0,138, 0,
+ 0,231,121,231,232, 0, 0, 0, 231,117, 0,126,212,129,231,211,
+231,204,198, 0, 0,143, 0, 0, 231,129, 0, 0,231,232,105, 0,
+
+// state[168 + 2] 0x005880 Byte 3 of 3 (property)
+206,231,231,140, 0,118, 0,231, 231,206, 19, 0,231,235,207, 0,
+231, 0, 98,137, 0,212,220,117, 129, 63, 0,191, 87,233,125, 68,
+220, 0,233,204, 0, 0,220, 0, 138,118,220,232,122,231, 96,191,
+207,233,191,109,191,212,231,231, 231,231,198,206, 0,128,129,199,
+
+// state[169 + 2] 0x0058c0 Byte 3 of 3 (property)
+231, 88,231, 0, 0,121,191, 91, 207, 0,101,191,117, 0,174,231,
+231,209,199,127,231, 35,231,231, 130,197,232,231,203,233, 25,121,
+232, 0, 19, 0, 85,231, 0,231, 231,121, 0,136, 68,231, 65, 78,
+145, 44, 15, 63,231, 0, 72,116, 0,121,143,231,129, 51, 0,231,
+
+// state[170 + 2] 0x005900 Byte 3 of 3 (property)
+234,231,231, 0, 28,231,199, 28, 212, 15, 72, 72,207, 28,207,137,
+ 72,231, 0, 0,170,103,136, 0, 191,103,136,172, 88,231,231, 16,
+ 27, 0,146, 0,232, 25,231,136, 231,138,136, 88,232, 68,140,141,
+231,140,232, 0, 16,231, 0, 68, 120, 16, 16, 0,233, 0, 17, 0,
+
+// state[171 + 2] 0x005940 Byte 3 of 3 (property)
+206,231,191, 0,103,231,231,138, 106, 68,231, 16,231,231, 67, 88,
+204, 88,231,231,134, 68, 26,118, 111, 0,167,231,231,231, 0, 0,
+118, 0,134,231, 0, 71, 0, 78, 15,232,146, 0,204,161,146, 0,
+ 0, 0, 0,137, 53, 0,125, 0, 115,125,231,210,232,138, 0,231,
+
+// state[172 + 2] 0x005980 Byte 3 of 3 (property)
+204,232,126, 68, 68, 0, 16, 16, 72, 0,100,231, 0,118,231,129,
+ 0, 0,130,138, 0, 0,137,199, 204,136,231,231, 0, 23,118,231,
+ 0,232, 0,232,118,138,221,207, 136,207,220, 63,116,231,144, 72,
+ 0,231, 99,122,207,191,191,212, 212,136,233,103,207,191, 68,212,
+
+// state[173 + 2] 0x0059c0 Byte 3 of 3 (property)
+191,231,212,232, 0,231,118,207, 199, 44,144,136,233,122,207,220,
+126, 68,199,126,139,220,221,128, 72,233, 67,220, 68,130,233,212,
+ 0,231,212,199,128, 88, 87,235, 126,191,106, 44, 85,207,204, 0,
+191,232, 0,191, 72,141,101, 72, 220,207,233,134,231, 0,220,103,
+
+// state[174 + 2] 0x005a00 Byte 3 of 3 (property)
+207, 68, 0,118, 72,128,128, 72, 128, 72,191,231,207,231,231, 0,
+ 0, 67,233,118,231,231, 0,231, 137,231,231, 25,118, 0,231,144,
+100,231,220,120,231,118, 0,191, 231,137,231,231, 0,231, 0,117,
+231, 16, 63,221,128,231,126,231, 232, 0,231, 0, 35, 0,191,191,
+
+// state[175 + 2] 0x005a40 Byte 3 of 3 (property)
+199, 51,128, 72, 72,191,134, 72, 0,126, 72, 0,232,232,199,207,
+232,231, 0,204, 0,130,221, 0, 0,231,136, 0,231, 0,221, 0,
+ 0,231, 67,231, 0,232,107,199, 0, 0,209,231,204,204, 0,231,
+231,231,231,231, 98, 98,191,144, 0, 0, 72, 0, 0, 0, 0, 88,
+
+// state[176 + 2] 0x005a80 Byte 3 of 3 (property)
+ 0, 0, 0, 0,204,231,231, 0, 0,231, 0, 0, 0,231, 0,231,
+ 0,231,134, 0, 0, 0,221,232, 0, 0,140,103,211, 0,204, 0,
+ 0, 0, 0,231,231,231,231,204, 231,231,235,231,232,220,232,232,
+233, 0,130,118,191,231,231, 0, 0, 0,204, 0,199, 25,206,231,
+
+// state[177 + 2] 0x005ac0 Byte 3 of 3 (property)
+231,137,118,231,232,212,232, 0, 210, 88,231,232, 88, 0, 0,220,
+231, 0,220, 0,207, 0,120,204, 208,231,208, 0,231, 0,231,231,
+ 0,164,211,209,231,232, 99,220, 233,118,233, 0,191,207,191,220,
+ 72, 0,231,233,231,204,231,191, 231,191,204,121,191,233, 0, 0,
+
+// state[178 + 2] 0x005b00 Byte 3 of 3 (property)
+231,231, 0,231,231,208, 0, 0, 204,106,231,204,131,231, 0, 0,
+ 0,231,231, 0, 0, 0,231,207, 0,232,231,204, 0,204, 0, 0,
+ 0,211, 44, 0,129,231, 0,191, 191,191, 67, 72, 65,191,128,231,
+ 76, 0,190, 0,206,207,233,233, 204,231, 0,231, 0, 0,231,210,
+
+// state[179 + 2] 0x005b40 Byte 3 of 3 (property)
+199,231, 0,175, 0,204, 0, 0, 231, 0, 0,221,204, 0,231, 0,
+136,199, 0,206,138,126,233,140, 136, 16, 67,206, 67, 68, 0, 68,
+ 0, 0,141,140, 68,232,145, 0, 0,144,191, 51,210, 0, 0,231,
+130,233,232,130,231,134, 0,231, 127, 0,171, 0,231,130, 0,211,
+
+// state[180 + 2] 0x005b80 Byte 3 of 3 (property)
+ 72, 11,231,125,207, 88,231, 88, 137,140, 0, 68,140,116,221,140,
+231,231,211,199,231,139,231, 68, 140, 88,137, 88,138, 65, 28, 15,
+ 16, 26,137,134,140,137,201, 0, 0, 0, 16, 67,232, 0,146,231,
+135,231,231,137, 88,140,140, 0, 115,136, 0, 0,231, 16, 16, 88,
+
+// state[181 + 2] 0x005bc0 Byte 3 of 3 (property)
+232,231,136,231, 87, 68,138, 68, 231,231, 0, 0,137,221,231, 0,
+209,231,136,115,231, 0,233,231, 231,231, 0,116, 0,101,126,140,
+233,138, 19,231,199,115, 76, 51, 115, 87,231, 75, 75,231, 97,232,
+118,231,207,197, 0, 76, 76, 0, 136, 28, 88, 16, 28,231, 15,145,
+
+// state[182 + 2] 0x005c00 Byte 3 of 3 (property)
+ 0,138,117, 0,136,231,142,127, 27, 68,140,129, 0,127,143,136,
+220,136,231,162,128,191,126,231, 16,167,138,233, 0, 72,231,231,
+231,231,232, 0,126, 0,235, 72, 233,231,208,231,118,172,231,191,
+191,126,231, 0, 72,191, 0,129, 67, 68,111,116,126, 65,137,136,
+
+// state[183 + 2] 0x005c40 Byte 3 of 3 (property)
+134,138, 28, 0,232,137, 20,231, 103,191,145, 88,121, 51,111,118,
+206,136, 0,117,231,139, 0,231, 204,220, 0,220,127, 0,145, 0,
+115, 72, 50,232,107,137, 0, 0, 204, 0, 0, 0, 21,231,198, 68,
+ 0,137,231, 0, 0, 0,231, 0, 207,115,230,191,191, 72,128, 81,
+
+// state[184 + 2] 0x005c80 Byte 3 of 3 (property)
+124, 63, 72,207, 72,231,211,231, 210, 0,207,207,206,221,231, 0,
+105, 67,231,231,118, 0,212, 26, 212,207, 72, 81,220,231,231,231,
+232,109,191, 0,231,231,232, 0, 198, 88,231,209, 47,128, 0, 0,
+ 0,115,231,137, 0,232,231,209, 136,231,231,231,203,234,231,207,
+
+// state[185 + 2] 0x005cc0 Byte 3 of 3 (property)
+231,220, 0,235,191,203, 0,121, 204,231, 0,207,231, 0,221,231,
+ 0,231,209, 0,221, 0, 0, 0, 231, 68, 0,231,231,231,231, 0,
+117, 53, 0,231,191,212,191,231, 136,235, 99,231, 0,130, 0,106,
+140, 0, 0, 0,161,231,109, 0, 231, 0,231,134,231, 75,231, 0,
+
+// state[186 + 2] 0x005d00 Byte 3 of 3 (property)
+233,129, 72,133,235,231,199,140, 0,220, 0,198,191,232,105, 0,
+ 72,115,128,231, 68, 98,140, 68, 72, 76,199,118,212,128, 0,199,
+210,207,209,207,191,207,231,121, 128,103,220,212,191, 72,207,212,
+ 98,191,207,207,130,207, 72,207, 207,133,207,231,128,207,220,231,
+
+// state[187 + 2] 0x005d40 Byte 3 of 3 (property)
+ 0,207,233,231, 0,231,191,207, 0, 0, 72,206,126,231,232,231,
+ 47, 0,231,231, 0,231, 0,231, 191,204, 0,220,203, 0, 0,231,
+231, 0,231, 0,231, 0, 0, 0, 231,137,231, 0,233,231,220, 47,
+ 0,231, 0,231, 0,231,231,231, 0,231,231, 0,231,231,231, 0,
+
+// state[188 + 2] 0x005d80 Byte 3 of 3 (property)
+ 0, 0,199,191,121,128,207,209, 191,128, 0,117,116,231,207,220,
+231,231,231, 0,211,231, 0,232, 0,207, 0, 0, 0,233, 0, 0,
+204, 0,238,231, 0,231,231,232, 231, 0, 0, 0,231, 0,198, 0,
+231,221,232,231,231, 0,231,233, 204,231, 51,231, 76, 51, 0, 0,
+
+// state[189 + 2] 0x005dc0 Byte 3 of 3 (property)
+ 0,231,231,211,231,207,231,231, 231,232,231,231,116,115,231, 0,
+231,231,129,231,122,231, 51,231, 231,231,231,198,231, 53,135,231,
+232,136,118, 15,231,139,136,138, 140, 98, 0, 68, 0,231,136,191,
+231,140,118, 53,126,231, 0,111, 0,232, 0, 15, 0,103,136,122,
+
+// state[190 + 2] 0x005e00 Byte 3 of 3 (property)
+231, 72,140,134,231, 16,136, 0, 28,231,191,231,136,231, 0,220,
+ 16,204, 98,231,221,144, 99,207, 72,194,201, 68,128, 51,207,220,
+231,207,204,231,231,119, 16, 72, 220,233,231, 91, 0,135, 10, 15,
+ 15, 0, 0,143,231,231, 75,115, 140,231,231, 0,133,136,220,231,
+
+// state[191 + 2] 0x005e40 Byte 3 of 3 (property)
+119,191,235, 72,199,137,191,108, 191,212,212,128,105,191,191,207,
+191,207,191,207,206,138, 98,199, 72,233,231,233,231, 0,233,119,
+ 0,105,115,129,231, 0, 0, 0, 231,231, 0, 20,231,231,231,231,
+231,231, 99,137,136,231,128,161, 88, 87,167,136,136, 68, 91, 11,
+
+// state[192 + 2] 0x005e80 Byte 3 of 3 (property)
+ 0, 15,231, 15,135, 0, 28,138, 210,231,136,221,231,231, 0,134,
+ 98,220,232, 28, 28,140,201, 88, 191, 16, 68,233,139,191, 72, 16,
+199, 72, 72, 72,199,210,140,137, 128, 0, 0,109,231,140,207,191,
+231,231, 0, 0,231, 53, 53,136, 68,206, 0,231,231,231,170, 0,
+
+// state[193 + 2] 0x005ec0 Byte 3 of 3 (property)
+ 0,127,121,117,211, 0,161,231, 91,115,138,204,210,204, 0,231,
+231,231, 0,126,204,221,130,231, 0,231, 17,233, 0,204, 0, 76,
+ 17,204, 75, 76,231, 0, 0,231, 204,204,220,231, 68, 0,231, 0,
+204,231, 0, 77,231, 0,136, 68, 198, 0,134, 44,198,231,231, 68,
+
+// state[194 + 2] 0x005f00 Byte 3 of 3 (property)
+ 28, 44, 16, 16,138,232, 0,204, 118,203,103,171,198,231,231,136,
+116,191,204, 68, 51,137,203, 68, 68, 0, 0, 88,231,211, 0,136,
+ 10,231,220,231, 0, 92, 88, 68, 191, 68, 0, 98,212,206,191,108,
+233,137,212,207, 98, 87, 72, 87, 191, 72, 11, 98, 68,191, 44,191,
+
+// state[195 + 2] 0x005f40 Byte 3 of 3 (property)
+232,191,191,207,220,161,206,235, 127,220,162, 0, 51,233,121, 0,
+231,231, 16,145,231, 26,204, 88, 232,129,221,233,231, 99,220, 0,
+ 0, 44,140,211,118,121, 53,206, 0,140,115,109, 68, 85,231,231,
+138,138,220,231,220, 0,210, 87, 232, 88,231, 26, 53,231,220, 87,
+
+// state[196 + 2] 0x005f80 Byte 3 of 3 (property)
+134, 67,197,191, 71,137,231, 72, 144,206,137,139, 87,231, 0,231,
+ 68, 25, 88, 15,231,207, 0,138, 137, 96,231, 0,208, 0,122, 0,
+194,100, 0, 0,231, 0, 0,232, 103, 87,140,231,204,233,136,231,
+231, 0, 0, 44, 13, 21, 0, 82, 0,146,231,231,233,120,231,231,
+
+// state[197 + 2] 0x005fc0 Byte 3 of 3 (property)
+ 0,231, 0,140,233, 88, 16, 0, 0,232,212,191,136, 88,220,191,
+206,206,206,128, 72,128,206,140, 103,136,212,128, 15,204,232, 0,
+ 68,209,231,191,199,191,231, 16, 0,231,210,134,231, 0,221,231,
+231,118,231,232,231,137, 0,231, 233, 0,231,120,231, 68,220,164,
+
+// state[198 + 2] 0x006000 Byte 3 of 3 (property)
+128, 10,191,220, 0,207,212, 0, 0, 0, 0, 0, 0,221,118,233,
+231,231, 88,232,209,144, 88,231, 231,204, 0,204, 35,103,231, 0,
+ 88,130,204,231,231,137,199,136, 68,232,136,231,231, 0,232, 88,
+231,231, 0,231,231,207, 0,231, 0,221,231, 28, 0,231, 0,220,
+
+// state[199 + 2] 0x006040 Byte 3 of 3 (property)
+231,202,197,201,231,204, 76,231, 0, 0,231, 53, 0,111,231,231,
+136,231,135,221, 0,121,231, 0, 0,201,204, 0, 0,231, 0,231,
+231, 0, 85,111, 68,109, 0, 0, 138, 68, 99,197,130, 68, 0,115,
+126, 0,231, 72, 0, 44,128,231, 220,220,191,207, 72,220,231,206,
+
+// state[200 + 2] 0x006080 Byte 3 of 3 (property)
+ 0,233,191,231,118, 75, 0,231, 231,115,191,231,202,126,191, 0,
+ 0,191,232,231,136,232,209, 72, 191,133,130,204,207,204,191, 88,
+136,133,191,137,235,212,145, 72, 144,101, 15,234, 98, 0,220,207,
+199,233, 88,190,201,206,136,207, 88,207,207,206,137,209,191,207,
+
+// state[201 + 2] 0x0060c0 Byte 3 of 3 (property)
+191,233,233,212,191,103,206,103, 191,235, 72,206,207,231,231,231,
+ 0,137, 0,231,191,118, 0,231, 206, 0,105,231,140, 0,191, 68,
+ 85, 76,231,117,231,231,209, 65, 145, 98,231,191,207,191,191, 72,
+137,129,204,140,199,231,130,231, 0, 88,164,204, 0, 0, 0, 0,
+
+// state[202 + 2] 0x006100 Byte 3 of 3 (property)
+232, 68, 0,231, 0,231,204,231, 130,136, 0,231,231,170,233,136,
+231,231,231,232,232,103,231,231, 0,231, 68, 87,129,231, 0, 88,
+235,231,231,209, 72,231, 0,115, 199,191,212,206, 72, 72,191,191,
+ 72,231, 0,231,109,128,199,129, 0, 0, 0,231,215,231,232,124,
+
+// state[203 + 2] 0x006140 Byte 3 of 3 (property)
+231, 0,231, 0, 91, 0, 0,198, 68, 0,232, 87,140,204,140, 0,
+231,128, 0,231, 0, 68,231, 0, 25,231,204,231,231,231, 0, 91,
+231,231,136,107,148,198, 0,111, 140, 0,221,204,232, 0, 91,231,
+140,231, 0,204,231,206, 91,130, 0, 0,231,231,204, 0,129,231,
+
+// state[204 + 2] 0x006180 Byte 3 of 3 (property)
+ 0,231, 87,231, 0,231, 0,231, 231, 0,131,209,211,204,103, 0,
+146,131,211,204,201, 0,231,231, 233,231,194,230, 0, 0,231,204,
+231, 72,231, 0, 87, 0, 0,100, 130, 87,232,131,201,204,198,221,
+233,231, 51,207,207,221,143, 0, 199,231,198,231,233,231,134,210,
+
+// state[205 + 2] 0x0061c0 Byte 3 of 3 (property)
+ 0,231,118,189,235,191,233, 87, 115,129,201,130,199,228,212,191,
+101,220, 72, 72, 0,235,191,212, 191,235,235, 0,191,191,191,210,
+191,191,231,197,221, 0,206, 0, 230, 0, 0, 0, 0, 0,232,231,
+ 0,231, 88,191,231,209,126, 75, 146,221, 91, 0, 78,231,204, 51,
+
+// state[206 + 2] 0x006200 Byte 3 of 3 (property)
+ 91, 0,231,231,231,231, 0,232, 85,231, 68, 0, 68,171, 88, 11,
+140,115,138,231,189,206,144,220, 10,199,140,199,191, 72, 72,201,
+128,206,233, 72, 0,231,101, 0, 231,238,118, 0,220,191, 88, 44,
+ 77,161, 27,121,138, 0, 21, 16, 44, 0, 0, 15, 0,210,210,134,
+
+// state[207 + 2] 0x006240 Byte 3 of 3 (property)
+140, 68,210,232, 0,231,232,136, 201, 53, 0,137,231,115,124, 0,
+231, 72,118,139,120, 15, 0,231, 141, 0, 0,118, 0, 0,204,231,
+232, 0,211,125,231,204,209, 26, 231, 16,191, 98,133,144,126,118,
+ 16, 15,211,118, 0,231, 68,231, 0, 99,161, 0,115,221,144,140,
+
+// state[208 + 2] 0x006000 Byte 2 of 3 (relative offsets)
+-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+
+// state[209 + 2] 0x006280 Byte 3 of 3 (property)
+139,231,231,231,135, 0,210, 0, 0,111,134,231, 0,231,221,231,
+231,137,126,124,231,137,118,136, 136, 0, 16, 67, 15,231, 15,235,
+191,212, 16,231, 28, 28,231,231, 130, 0,231,137,118,232,231,231,
+ 0,136,231,231, 0,138,231, 0, 221,136,211,233,103,140,231,207,
+
+// state[210 + 2] 0x0062c0 Byte 3 of 3 (property)
+ 0, 0,115,220,199,145,125,115, 206,118,232,121,126,111,209,194,
+135,191,138,135, 85,231,118,103, 136, 88,121,115,144, 44,231, 16,
+101, 15, 72,128, 0, 16, 72,191, 16, 28, 0,231,134, 88,103,118,
+230,118,231,136,199,198, 15,115, 232,230,199,207,144,199,136,118,
+
+// state[211 + 2] 0x006300 Byte 3 of 3 (property)
+231,137,128,220, 0, 0,220,137, 206,118,207,231,121,231,128, 0,
+231,136, 0, 0,231,231,118, 0, 231, 15,128,191,231,191,191, 92,
+ 72, 72,231, 72, 98, 26, 0,235, 100, 0,118,140,231,231, 0,103,
+231, 0,210,191,220,231,233,233, 207, 50,126,191,231,140,111, 15,
+
+// state[212 + 2] 0x006340 Byte 3 of 3 (property)
+231,233,207,231,199,209,118,128, 231,136, 0,206, 88,130,207,138,
+118,231, 0,231, 0,137, 0,101, 231, 0, 0,231, 13, 0, 72, 16,
+231, 72, 28, 72,231,231, 0,140, 146,194, 0,170, 0,220, 72, 0,
+ 0,206,119, 0, 0, 0,209,130, 0, 0,103, 53, 0,231,231, 0,
+
+// state[213 + 2] 0x006380 Byte 3 of 3 (property)
+118,231,199,107,204,231,191,199, 140,144, 0,235,126,191,231,118,
+209,128,126, 72,207,191, 72,128, 137,129,231,107,231,207,220,116,
+115,107, 88,206,221,140, 0,134, 134,118,139,231,201,231,210, 0,
+130,231, 15,191,101,231, 0,191, 229, 0, 72,116, 0, 0,197, 0,
+
+// state[214 + 2] 0x0063c0 Byte 3 of 3 (property)
+204, 0, 0, 24,197, 0, 93, 0, 0,136, 0,231, 0,206, 0, 88,
+138,232,144, 0,231, 0,103, 0, 231,231, 87,107, 0, 0, 0,231,
+221,140, 0,130,232,231,231,231, 0,199,120, 0,231,118, 87, 0,
+231,231, 0,231, 88,231,197,231, 0,129, 44,231, 0, 72,231, 0,
+
+// state[215 + 2] 0x006400 Byte 3 of 3 (property)
+207, 98,191, 0, 0, 72,204, 0, 0,231,231,231, 0,107,231,118,
+209,231,231,118,121, 0, 25,131, 0,231,231,231,144,231,125,231,
+ 0,233,232, 0, 0,211,197,199, 232,207,209,128,140, 88, 0,231,
+231, 0, 0,231, 72,232, 23,231, 0,191, 92, 0, 0,199,109, 0,
+
+// state[216 + 2] 0x006440 Byte 3 of 3 (property)
+211,207,101,210, 16, 0, 63, 16, 212,231, 98, 0, 0,231,232,231,
+ 0,204,208,231,118,212, 0,231, 140, 0, 0,232,207,231,207,129,
+199,231,231, 0,231,231,231,118, 204,138,231,191, 0,221,211, 91,
+211,231, 0,129,231,233,206, 0, 118,130,143,127,231,204, 0,231,
+
+// state[217 + 2] 0x006480 Byte 3 of 3 (property)
+233,220,207,117, 0,208,231,130, 76,231, 0, 0,211,231, 0,231,
+127, 16,138, 91,231,118,211,199, 204,204,109, 0, 0,231,126,211,
+211, 0,204,127,140,131, 0,231, 231,130,231, 91,199, 68,105,191,
+ 68,206, 51,128,128,212, 98,207, 220, 92,231, 99,126,221,210, 19,
+
+// state[218 + 2] 0x0064c0 Byte 3 of 3 (property)
+191,143,115,207,121,118, 0, 25, 0,231, 21,127, 0,136,144,231,
+211, 0,118, 0, 25,231,231,211, 121, 0, 77,221,231,231,212,231,
+127,232,103,232,210,231,136,161, 0,231, 0, 0,131,198,231,204,
+204,129,119,211, 25, 0,231, 19, 0, 0, 25,204, 0,198, 76,231,
+
+// state[219 + 2] 0x006500 Byte 3 of 3 (property)
+115, 0, 0, 0, 0,231,221,231, 231,231, 0,231,231, 0, 0,118,
+231,231,128, 0,121,231,232, 0, 68,204, 0,231, 25, 75, 0, 0,
+ 0, 0,204,146,127,207,233,231, 0, 0,143,201,127, 0,231,140,
+212,191, 0,220,231,231,125,175, 111,140, 0,137, 0,128,140,115,
+
+// state[220 + 2] 0x006540 Byte 3 of 3 (property)
+231, 0,212, 72,212,140,207,212, 125, 0,207,191, 16,199, 50,136,
+133,134,231,207,232,206,115, 87, 25,140, 0,128,231,121,126, 0,
+ 0,231,140,136,191, 0, 68,231, 231,231,233,231, 68, 0,207,231,
+145,212,134,231,140,109,211,105, 21,210,211,207,220,231,212,231,
+
+// state[221 + 2] 0x006580 Byte 3 of 3 (property)
+207,199,111,119, 0,233, 0, 68, 231, 44, 0, 72, 67, 0, 44, 0,
+ 88,140,231,207, 0,204, 0,134, 0, 88, 0,209,137,231, 0,111,
+204,103,231,231, 85,134,231,137, 0, 72,231,233, 51,145, 0, 85,
+136, 0,204, 0, 0,231,231, 75, 231,137,191,207,121,140,220, 72,
+
+// state[222 + 2] 0x0065c0 Byte 3 of 3 (property)
+ 72,144,120,206,233,140,232,207, 72, 0, 0,138,209, 0,199,140,
+231, 0,232,191, 0,231,209,134, 231,231, 0,116, 0, 0, 0, 0,
+ 16,204,137,221,231,136,136,145, 136, 88, 0, 0,103, 88,207,207,
+ 0,115,231,210, 0, 0, 28, 72, 212, 0,134,111,204, 0, 0,237,
+
+// state[223 + 2] 0x006600 Byte 3 of 3 (property)
+121,231,134,231, 0, 0,139,146, 0,199, 67, 0, 68,221,136,138,
+ 0, 0, 0,138, 53, 67, 0,231, 231,191, 0, 0,204,207,237,136,
+103,232,210, 0,231,137,231, 88, 137,231,231,232,231, 53,231,126,
+204, 96, 0,198, 47, 72,111, 0, 231,232,232,237,101,231, 10,231,
+
+// state[224 + 2] 0x006640 Byte 3 of 3 (property)
+ 0,189,107, 88,198,231, 0,231, 231, 50,221, 53,206,231, 0, 68,
+231,211,135, 16,212, 72, 72,191, 0,233,144,191,191,111,199, 68,
+212,211,189,212,126,233,103,198, 118, 44,207,191,207, 0,140,138,
+118,233,191,226,103, 0, 97,206, 207,231, 68, 0,231,231,118, 0,
+
+// state[225 + 2] 0x006680 Byte 3 of 3 (property)
+220, 44, 16,231,206,231,167,103, 111, 51,231,231,211, 0,198, 0,
+121, 88,231,231,231,220,140, 88, 78,191, 0,211,207,206,212,191,
+226,220,129,191,220,235, 53,191, 132,207,221,143,191,233,105,235,
+220, 25,231, 0,140,231,231,231, 121,201,211,161,231,221,232,231,
+
+// state[226 + 2] 0x0066c0 Byte 3 of 3 (property)
+ 0,204, 0, 0, 50,231,129,109, 204, 76,231,231,204, 0,231, 0,
+231,231, 0, 0, 0,231,146,212, 191,140,191,221, 13,134, 72,128,
+126,207,133,231, 0,231,118, 0, 128,204,207, 0,127, 0,231, 0,
+ 68, 0, 88, 88,136,231, 0,161, 146, 68,148,231,126, 44,126, 88,
+
+// state[227 + 2] 0x006700 Byte 3 of 3 (property)
+136,231, 0,127,231,231, 0, 0, 137,134,230,126, 0,115,231,231,
+191,231,221,232, 68,201,231,137, 0,231,231,137, 0, 53,231,136,
+ 0, 0, 0, 0, 0,231,201,109, 103, 0,136,137,136,109,119, 16,
+231, 68,231,231, 51,130,198,231, 232,231, 72,231, 0,137,231,232,
+
+// state[228 + 2] 0x006740 Byte 3 of 3 (property)
+ 16,203, 16, 28, 0,211,120, 0, 128, 88,231, 0,232, 0, 68,136,
+136,135,231, 47,231, 0,137, 0, 0,232,231, 0, 68, 0,115,138,
+ 72,142,117,203,231, 65,231, 0, 128,191,232, 0,231, 99,231,136,
+ 67,146,206,206, 0,103,221,137, 199,220,233,204,197,220, 88,136,
+
+// state[229 + 2] 0x006780 Byte 3 of 3 (property)
+ 0, 10,233,191, 16,233, 0,137, 220,118,231,121,233,191,207,231,
+138,207,221,206,207, 88,207,140, 231,207,100,191,137,137,212,207,
+117,101, 65, 98,207,220,190,212, 0,231, 63, 72,231,207,231,137,
+206,232, 0,189,204,221,126,201, 115,231,231,191,220, 0,231,231,
+
+// state[230 + 2] 0x0067c0 Byte 3 of 3 (property)
+231,231, 0,221,105,207,191,191, 230,231,100,233,231, 72,231,136,
+134,140, 96,140,136,133,231,231, 35,221, 68,128, 72,204,189,128,
+ 72, 72,204,128,191,125,207,191, 133,201, 0,231,118, 0,231, 85,
+233,137,231, 68,137, 51,231, 0, 232,231,204, 15, 0,220,198,137,
+
+// state[231 + 2] 0x006800 Byte 3 of 3 (property)
+226,198,116, 24,117, 72, 0, 26, 128,212, 0, 72,220, 0,212, 28,
+ 0, 26, 0, 88, 0, 0, 35, 88, 232,231,231,231, 0,206,116, 0,
+232,140,197,231,235, 0,220,191, 128,111,105,191,235, 72, 0, 0,
+231,204,233,221,198,230,231,128, 126,137,231,232,137,137, 72,231,
+
+// state[232 + 2] 0x006840 Byte 3 of 3 (property)
+206,105,139, 97,232,199,118,231, 138,199, 0, 0,132,231,199, 0,
+140,137,231, 68,134,207,232, 0, 0,231, 0, 0, 44,116,231,116,
+212,220,191, 9,220,133,128,145, 191, 72,231,208, 0,231, 0,204,
+ 0, 0,211,231,206, 0,138,191, 221, 0, 0, 0,204,211,231,119,
+
+// state[233 + 2] 0x006880 Byte 3 of 3 (property)
+231, 68, 0,197, 0,137,206,231, 231,231,231, 0, 0,231, 0,199,
+ 0,221,221,136,194,231, 0,136, 221, 0, 0,198, 0,119,231, 91,
+194,231,137,221, 0, 0, 16, 68, 103,231, 0,231,231,118,231,126,
+137, 13,231,115,231, 68,117, 0, 231,191, 72, 98,190,220,220,220,
+
+// state[234 + 2] 0x0068c0 Byte 3 of 3 (property)
+ 28,212,179, 98, 68,220,231, 0, 231,118,231,136,191,126,233,231,
+231,230, 88,221,233,118,232,126, 137,191, 35,207,191,191,207,146,
+126,233, 0,120,233, 0,235,121, 204,191,232,231,191,191,103,191,
+191, 67,143,231,220,118, 0,233, 0,197,137,204,232,221, 0,207,
+
+// state[235 + 2] 0x006900 Byte 3 of 3 (property)
+116,233,233,231,232, 97,204, 0, 72,231,231, 35,231,140, 88,194,
+220, 0,139, 0,231,233, 0,231, 234,203,203,203, 15, 0, 0,220,
+ 0,231,231,231, 98,198,231, 0, 231, 0,209,231, 0,191, 0, 0,
+138, 0, 0,231,172, 0,231, 0, 0,201,231,191,233, 67,207, 53,
+
+// state[236 + 2] 0x006940 Byte 3 of 3 (property)
+128,128,120, 72,207, 72, 0, 0, 0,231, 76,230,231,231,231,231,
+ 0,231,231,143,111,116, 0,233, 0,204,126,231,231,206,118, 0,
+138, 44,116,118,231,221,208, 0, 76, 0,231,197,232,146,197,105,
+ 0, 0,231,190,231, 87, 0,134, 189,130,231, 0, 98, 15,231, 0,
+
+// state[237 + 2] 0x006980 Byte 3 of 3 (property)
+231,231,137, 0, 72, 0,120, 0, 72,191,101,231, 0, 0,116, 0,
+232,198, 0,231,111,118,129, 0, 233, 0, 0,103,118, 0, 0, 0,
+231, 0, 0,221,221,221,221,189, 118, 0, 0,206, 0,118, 76, 0,
+ 0,231,231, 0, 68, 0, 0,209, 0, 0,231,130,231, 0,231,232,
+
+// state[238 + 2] 0x0069c0 Byte 3 of 3 (property)
+231,206,231, 88,231,191,231,101, 207,231,232, 87, 88, 91,189,231,
+ 99,233, 0,129, 0,220, 0,235, 15,116, 0, 98,231,231,231,191,
+ 0, 0,231, 0,211, 0,220,231, 232, 0, 0,231,191,202,207,231,
+220,220,233,129,221, 0,220,220, 212,191,220,101,207, 88,220,164,
+
+// state[239 + 2] 0x006a00 Byte 3 of 3 (property)
+207,121, 75, 0, 0,198, 0,232, 220, 0,115, 44,231, 0, 0,233,
+ 0, 77,198,127,231, 0, 0,197, 233,143,231,231,220, 0,121,138,
+220,138,231, 25, 0, 0, 0, 0, 207, 15, 92,116, 0, 0,231,220,
+231, 72, 0, 0,232,111,231,191, 121, 87, 97,128,128,106,206,207,
+
+// state[240 + 2] 0x006a40 Byte 3 of 3 (property)
+128, 0, 0, 0,118,231, 72,189, 189, 0, 0,146, 0, 0, 0, 0,
+232, 0, 0, 0, 0, 0,231, 0, 136,140, 0,229, 0, 0, 0, 87,
+ 0,118,129, 0, 0, 0,231, 0, 0, 0,231, 75, 0, 0, 0, 0,
+ 0, 72,231,231, 0, 0, 0, 0, 231,226, 0,231,220, 0,232,116,
+
+// state[241 + 2] 0x006a80 Byte 3 of 3 (property)
+ 51,221, 0, 0,197, 0, 0, 0, 0,204, 0, 0, 0,232,105, 0,
+120, 0,231, 0, 27, 0, 0,189, 0, 0, 0, 0, 91, 0,232, 0,
+232,127, 23,232, 0, 0, 0,220, 210,179,231,220,126, 0,189, 25,
+ 0, 0, 0, 76, 0, 0, 0,220, 119, 0,220,143,231,231, 0, 0,
+
+// state[242 + 2] 0x006ac0 Byte 3 of 3 (property)
+ 0,231,198, 17,231,207,191,207, 0, 0,212, 0,220,207, 0,220,
+ 0,231,220,100,220,207, 0, 0, 220,220,111,106,233,228,232,194,
+207, 0,233,207,235,121, 0,230, 190, 0,204,211,221, 0, 0, 0,
+ 0, 0, 0,230, 0, 0,231, 0, 211, 0,204, 87, 0,231, 0, 0,
+
+// state[243 + 2] 0x006b00 Byte 3 of 3 (property)
+ 0, 0, 0,231,143,203, 0, 0, 231,211, 21, 0,220,235,220,124,
+207, 72, 68,128, 0,231,121, 0, 207,233, 0,231, 0,203,231,231,
+103,136, 10,126,220, 0, 0, 65, 0, 0, 0, 0,232, 0, 0, 0,
+ 0, 0, 88, 0,221, 0,231,232, 204,232,136, 0, 0, 91,126, 0,
+
+// state[244 + 2] 0x006b40 Byte 3 of 3 (property)
+ 0, 0, 0,231, 0, 0, 78,118, 0,130,231, 0, 88,231,119, 0,
+122, 0, 0, 15,232, 0, 0, 0, 0, 72, 0,204, 0, 0,231,194,
+ 0, 25,136,137,118,141, 68,118, 0, 24, 88, 0,231, 0, 0, 15,
+ 0, 0, 21, 15, 15, 0, 0,129, 76,130, 0, 88,128, 0, 0,194,
+
+// state[245 + 2] 0x006b80 Byte 3 of 3 (property)
+231,220,231,130,232, 0,103,207, 0, 88,138, 92,220,231,231,221,
+ 0,207,179,220,212,191,140,220, 75, 0,191,232,212,220,204, 0,
+ 0, 72, 0,231,170,221, 0, 0, 212, 0,231,206,220,121,194,119,
+233,231,106,233, 92,136,212, 68, 220, 0,146, 44, 25,191,212, 88,
+
+// state[246 + 2] 0x006bc0 Byte 3 of 3 (property)
+129, 63,207, 0,207,136,121,220, 233, 0, 0, 96,226,136, 24,144,
+206, 0,134, 85,140, 16,191, 67, 51,199, 0,136, 0, 0,210,203,
+ 0,191, 0, 0, 0, 0,231, 0, 0, 0,231,118,143,220, 0,134,
+ 0, 0, 0,231, 0, 0, 0, 0, 0, 0, 0, 0, 0,207, 0, 0,
+
+// state[247 + 2] 0x006c00 Byte 3 of 3 (property)
+231, 0,231, 0, 0,231, 0, 0, 129, 0, 0, 0, 0, 0, 0, 53,
+167,134, 0,118, 10, 0,233, 15, 220,206,230,144, 0, 0, 0,118,
+ 0,207, 72,121,206,233,209,144, 118,191,210, 20,124,128,118,144,
+118,133,233,199,140,233,128, 44, 68,232, 0, 0,207,235, 87,231,
+
+// state[248 + 2] 0x006c40 Byte 3 of 3 (property)
+ 68, 88,140, 0, 0, 0,206, 26, 0, 16,191,231,231, 0,109, 0,
+ 87, 0,221, 0,210, 72, 0, 88, 0,121, 24,120,206, 68,118,139,
+136,141,231, 0, 16, 0,231, 0, 199,207,115, 0, 0,220, 0,232,
+137, 0,137,231,130, 0, 85, 0, 0,191,107, 0, 0,118, 67, 0,
+
+// state[249 + 2] 0x006c80 Byte 3 of 3 (property)
+ 0,115,120, 68,210,118,233, 0, 135,118, 0, 0, 88,231, 0,207,
+111, 0, 25,100,233, 0,146, 0, 161, 68,233, 68, 0, 0, 0, 16,
+ 0, 72,117,226,191, 72, 72, 98, 0,179, 98,134,211, 72, 50, 0,
+ 0,120, 0,139,231,231, 0, 0, 68,140,232,140, 53, 99, 85,137,
+
+// state[250 + 2] 0x006cc0 Byte 3 of 3 (property)
+ 0,107, 0, 0, 74,233, 0, 0, 0, 88,100, 0,136, 0, 0, 0,
+230, 0, 0,130,207,140,233, 67, 0,198,231,141,231,232,207,220,
+206,138,140,103, 0, 68,220, 0, 35, 0,108,221,221,221,206,206,
+ 68,199,231, 88, 0,120, 0,191, 128, 0,212, 63, 72, 16, 98,220,
+
+// state[251 + 2] 0x006d00 Byte 3 of 3 (property)
+ 0,128, 0,220,206, 0,221,220, 0,231,231, 88,197,207,221,207,
+ 0,220, 53, 0, 0, 0,133,136, 124, 47, 0, 68,212,207, 68,172,
+220, 0,207,212,207, 35,133,204, 207, 87, 68,231, 0,207, 72,220,
+191,118,138,204,207,201,121,221, 189,199,210,136, 72,132,136,233,
+
+// state[252 + 2] 0x006d40 Byte 3 of 3 (property)
+231,140,220,212, 22,145, 16, 72, 191,207, 72, 16, 0,220, 26, 26,
+191, 72, 72, 16, 98,231,220,212, 0, 72, 68, 0,101, 0,231, 0,
+ 72, 0, 0, 74,204,210, 35, 0, 220,137, 68, 0,194, 0,136,121,
+ 0, 0,231, 0, 88,231,207,137, 88,175, 0, 0, 72, 0, 0,161,
+
+// state[253 + 2] 0x006d80 Byte 3 of 3 (property)
+ 0, 0,141, 0, 0, 97, 0,175, 140, 82, 0, 0, 99, 0, 88, 0,
+ 0,233,191, 67,199,130,191,207, 128, 53,128, 98,172,133,191, 98,
+220, 72,128,207, 72, 0, 16, 72, 16, 72, 72, 0,231, 0,130, 88,
+ 0,128,136,230,211,118, 0,221, 103, 0, 0, 0, 91, 0, 0, 63,
+
+// state[254 + 2] 0x006dc0 Byte 3 of 3 (property)
+ 65, 0,211, 0, 72,199,111,118, 0,210,231,138,209, 0, 0,232,
+231, 68,129, 0, 0,204, 72, 0, 115,206, 75,221, 0,199,201, 0,
+207,136, 0, 0,118,232,206, 0, 127,121,121,135,209,231,120,204,
+ 0,139,231, 68, 0, 51,232,137, 161,126,119,137,209, 0, 0, 0,
+
+// state[255 + 2] 0x006e00 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0,138, 0,116, 15,117, 72, 44,191, 72,128, 0,
+ 16,212, 0,116, 16,117, 0, 72, 0,204, 35, 87, 0,120, 0,198,
+ 67, 88,231,115, 67, 88, 87,231, 0,145, 0,194,143,115,231,140,
+221, 0,132, 0,118, 0,231, 0, 120,231,111,207,204, 0, 51, 0,
+
+// state[256 + 2] 0x006e40 Byte 3 of 3 (property)
+220,207, 0,209,118, 0, 0,220, 0,233, 51, 0,231,171,233, 0,
+ 0, 0, 0, 0,204, 0,140,231, 35,231, 0,134,228, 0,232,167,
+ 0,220, 0,231, 0, 0, 0,146, 231, 0,220,189, 0, 0,206,146,
+207,207,204,122,207, 0,231, 0, 0,220, 0, 0, 0,220, 65, 65,
+
+// state[257 + 2] 0x006e80 Byte 3 of 3 (property)
+117, 0,231, 72, 0,207,212, 0, 221,118, 0, 0,203, 0,221,199,
+134, 0,231,220,231, 0,107, 0, 197, 0, 0, 0, 88,143, 0,164,
+231, 0,136, 0, 0,164, 0,128, 0, 0, 85, 75, 0, 0,231,118,
+ 0,208,232, 0,120, 0,137,198, 0, 0,136, 0, 19,231,220,233,
+
+// state[258 + 2] 0x006ec0 Byte 3 of 3 (property)
+207, 72,189,128, 68, 68,207, 67, 128,198,231,136,129, 0,199,207,
+230,136, 0,189,115,120, 0,231, 207,204, 16, 0, 0, 44,145,212,
+ 0, 10,191, 0, 16, 98, 72, 0, 133, 98, 0, 0,121, 0, 0,127,
+ 0, 0,143, 0,140, 0,231,129, 143,220, 0,231, 0, 0,121, 75,
+
+// state[259 + 2] 0x006f00 Byte 3 of 3 (property)
+ 0, 91,140, 0, 0, 0,136, 0, 220,100,231, 0, 0, 0, 0,136,
+ 0,116, 0, 67,136, 53, 0, 0, 0,231,204, 0, 0, 0, 0, 0,
+137,231, 51,143, 0,211, 0,232, 0,209,130,138,109, 0, 0,120,
+ 0, 68,121, 67, 0,232,232,220, 76, 0, 0, 0,231, 0,118,119,
+
+// state[260 + 2] 0x006f40 Byte 3 of 3 (property)
+ 0,204, 0, 0, 0,117,220, 72, 220, 0,220,220, 0,133, 0,191,
+212, 25, 0,233,131, 0,207,220, 85,191, 0, 75,145, 0, 72,116,
+206,207,144, 0, 87, 0,206,128, 207,207,191, 0, 0, 85,140,161,
+146,220,233, 0,172,220, 0, 0, 206, 0,130,207,120,232,169, 0,
+
+// state[261 + 2] 0x006f80 Byte 3 of 3 (property)
+121,101,206, 0,136, 0, 76,211, 118, 0, 0, 0,231,206, 25, 0,
+211,235, 0, 0,210, 0,231,164, 212, 0, 0,233,128,231, 0, 0,
+232,118, 0,232, 51,233,231, 72, 231,232,105, 0, 0,231,161, 0,
+ 0,143, 0, 85, 0,231, 72, 0, 0, 99, 0, 0,231, 0, 0, 0,
+
+// state[262 + 2] 0x006fc0 Byte 3 of 3 (property)
+137, 91,209, 87,207,220, 63,128, 128, 72,167,128, 0, 0, 0,231,
+207,191, 72,221,231,127, 0, 0, 194, 0,231,121, 0, 0, 72, 49,
+ 99, 35, 0, 0, 51, 0, 0, 0, 231,231, 0,143, 50, 0, 63,105,
+204, 87, 0,231, 0,198,198, 0, 220,231,129, 0,221, 0,131, 0,
+
+// state[263 + 2] 0x007000 Byte 3 of 3 (property)
+ 0,204, 0,231, 0,204, 50,231, 0,119, 0, 51, 0,231, 0, 23,
+ 0,111, 0,231,221,143, 0, 0, 204, 0,126, 85, 0,111,109, 93,
+221, 0, 0,128, 0, 0,232, 47, 78, 0, 0, 0,117,212, 0,207,
+121,231,204,221, 0,128,233,220, 0,128,233,207, 0,220, 93, 0,
+
+// state[264 + 2] 0x007040 Byte 3 of 3 (property)
+ 0,207, 0,199,233, 0,220, 0, 207,220,191,233, 67,220, 0,128,
+220,121,220,212, 0,211,211, 0, 87,212,207, 0, 0,209, 72,220,
+122,220, 0, 21,161,191,207, 0, 0,207,191,137,191, 16,212, 65,
+ 68,207,212,220,207, 63,118, 0, 136, 0, 0,212,137, 87, 26, 72,
+
+// state[265 + 2] 0x007080 Byte 3 of 3 (property)
+191,210, 0, 0, 0,199,206,231, 0, 65,103, 0, 0,212,140, 0,
+ 0, 0,140, 0,209,199, 72, 0, 204, 88, 0,231, 72,220, 0, 0,
+ 0,231, 0, 0, 50, 0,231, 0, 0,191,212, 82, 99,137,118, 68,
+199, 0, 72, 68,128,124,207,206, 134, 71,119,231, 16,191, 0, 0,
+
+// state[266 + 2] 0x0070c0 Byte 3 of 3 (property)
+ 0, 72, 16,191, 0, 0,207,207, 68, 0,121,231, 0, 0, 0, 51,
+ 0, 0, 0, 0,232, 0, 0, 0, 144,111, 0, 72,204,204, 0, 16,
+ 0, 0, 0, 0,144, 0, 72, 16, 191,207, 0, 72,191, 28, 0,118,
+ 0,232, 0, 0,204, 0,231,118, 0, 97,232, 0, 0, 67, 0, 0,
+
+// state[267 + 2] 0x007100 Byte 3 of 3 (property)
+ 0, 0,231, 0,204, 0, 0,231, 0,136,118, 0,221, 0, 0,231,
+ 0, 0, 0,233,116, 72,191,121, 207, 97, 97, 0,121,231, 0, 0,
+204,107,211, 0, 0, 0,134, 0, 0, 0, 0, 0,231, 0, 0,199,
+118,199, 0, 0, 0,212,140, 0, 207,220,207,220,117,212,207,230,
+
+// state[268 + 2] 0x007140 Byte 3 of 3 (property)
+212,220,220,191,207,207,232,204, 212,119,199,220,134, 0,140,212,
+199,207,129,220, 0,142,204, 0, 207, 51, 0,133, 68,220,130,128,
+207,207,233,220,120, 51, 96,134, 199, 91,231,212,170, 0, 88, 0,
+ 0, 0,124, 0, 0, 0,220, 0, 207, 0, 0, 0,212,137, 0, 0,
+
+// state[269 + 2] 0x007180 Byte 3 of 3 (property)
+231, 0, 0, 0,118, 0,207, 0, 198, 0, 88, 0, 0, 0, 0,120,
+ 0, 0,204, 0,126,231, 0, 0, 220, 68, 0, 0,231, 0, 0,140,
+209, 0,231, 0, 0,231, 0, 0, 111, 0,231, 0,118, 0, 0,231,
+ 0,143,207, 0,191,207,212,128, 128, 68, 72,207,128, 0, 91, 0,
+
+// state[270 + 2] 0x0071c0 Byte 3 of 3 (property)
+ 0, 50,207,137, 75, 0, 0, 0, 129, 76,208, 0, 0, 0,201,231,
+105,231, 75,231,232, 68, 0,117, 0, 19,233, 0,204, 0, 0, 20,
+204, 0, 0, 0,221, 88, 91,162, 0, 0, 0, 0,129,143, 50, 0,
+ 0, 0, 0, 0,121,203, 0, 0, 0,204, 0,143,194, 0,175,131,
+
+// state[271 + 2] 0x007200 Byte 3 of 3 (property)
+237, 0, 0, 0, 0, 0,137, 0, 0, 0, 0, 0,204,121, 0, 0,
+ 25, 0, 0, 0,231,231, 0, 0, 0, 0, 0, 91, 0, 0,231, 0,
+ 0,231, 0,220, 0, 0, 0, 0, 233, 0, 88,231,138, 75,231, 0,
+119, 16, 51, 0, 0, 68,136, 72, 144,118, 87,201,191,136, 78,233,
+
+// state[272 + 2] 0x007000 Byte 2 of 3 (relative offsets)
+ -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
+
+// state[273 + 2] 0x007240 Byte 3 of 3 (property)
+198, 0,231,220,220, 0,121,134, 136, 0,207,199,118,212,220,207,
+220,231,201,199,221,191,204,212, 206, 68, 0,136,191, 47,207, 68,
+ 20,137,139,231, 0,231,207,140, 231,136, 0, 0, 0,231, 0,208,
+ 0, 0,137,233,121, 98, 0,231, 191,136, 72, 0, 0,143,231,231,
+
+// state[274 + 2] 0x007280 Byte 3 of 3 (property)
+ 88,118,198, 0,230, 0, 0,204, 0, 0,128, 0, 0,199, 0, 0,
+231,231,206, 0, 0, 0,206, 0, 0,212, 0,204, 0, 0, 0,212,
+ 24, 0,204, 0, 0, 0, 0,121, 231, 0, 0,191,103,231,207,137,
+221,191, 72,128,232,128, 92,207, 220, 72,212,133,231,221,231, 0,
+
+// state[275 + 2] 0x0072c0 Byte 3 of 3 (property)
+ 27,231,136,231, 68,207,198, 0, 207, 0, 0, 0, 0,220,170, 0,
+139, 0,206, 0, 0, 0,231, 68, 0,105, 0,116, 0,221,212,211,
+141,111,198, 0, 0,221, 0, 0, 0,105, 0, 0,145, 92, 72, 0,
+212, 28, 0, 0,231, 0,221,204, 68, 76,221,233,136,194, 0, 0,
+
+// state[276 + 2] 0x007300 Byte 3 of 3 (property)
+ 0,210,221, 0, 0, 0, 0,191, 0, 0,197,221, 0,231, 72, 0,
+ 0, 0, 0, 0, 0,179,130,199, 0,204, 0,140,118,209, 0,116,
+ 0,231,221, 0,231, 88, 0, 0, 0,111,142, 92,191,211,142,231,
+ 0,231, 0, 0,118,221, 93,111, 0, 0, 0,221, 0, 0,201, 53,
+
+// state[277 + 2] 0x007340 Byte 3 of 3 (property)
+232, 0, 0,221, 87,143, 0, 0, 0,231, 0,231, 0,221, 27,198,
+199, 0,199,220,231,231, 0,209, 231,233, 0, 0,235,231,231,231,
+206, 0,210, 15,231,191,231, 0, 76,231,198, 0,231,191, 63, 0,
+194, 0,143, 0, 0, 76, 0,204, 77, 0,194, 75,204, 0,206, 0,
+
+// state[278 + 2] 0x007380 Byte 3 of 3 (property)
+211, 0,211, 0, 68, 0,204,136, 0,137, 0, 68, 0, 0,206, 0,
+ 0,191,232,221, 0,232, 88,221, 233,231, 0, 16,231, 0, 0,121,
+206,211,206, 0, 0,121,204, 0, 204,111, 0,144,211,232,128, 28,
+ 28, 0,138,199,231,231,221,199, 0,204,191,118, 0,231, 0,232,
+
+// state[279 + 2] 0x0073c0 Byte 3 of 3 (property)
+138, 0, 88, 0, 0,207,221,211, 68,167,126,221, 0,136,203,199,
+207,207,231, 0,207,220,233, 0, 220,199,233,210,230,220,201, 0,
+134, 0, 0,170, 0,206, 0,221, 210,206, 47, 0, 0,126,121,220,
+220,231,212, 0, 0,232,207,220, 231, 0,206, 0, 0,204,107, 0,
+
+// state[280 + 2] 0x007400 Byte 3 of 3 (property)
+221,204, 0,136,211, 67,140,121, 0, 68,199, 0,211,210, 0,207,
+ 72, 0, 0, 0, 0, 0, 0, 0, 0, 0,199,118, 0, 0, 0, 0,
+ 0,211,137,221,204, 88, 85, 0, 209,204,144,161,121,232,121,210,
+206,231,109, 68, 68, 68, 68, 0, 237,221,131, 0, 63, 0, 0,175,
+
+// state[281 + 2] 0x007440 Byte 3 of 3 (property)
+121,199, 0, 0, 96, 0,221,231, 232, 0,232,127, 0, 0, 0, 0,
+231, 0,232, 0, 0,111, 0,199, 0,115,136, 68,126,231,126, 85,
+ 47, 0,170, 76, 93,212,191,191, 169,121,121,232, 0,206,221,119,
+118,206,233,190, 0, 0,108,233, 0,207, 0, 0,211,221, 67, 0,
+
+// state[282 + 2] 0x007480 Byte 3 of 3 (property)
+118,208, 0, 68,231,211,204,118, 204, 76,211, 68, 0, 0,230, 0,
+118, 0, 0, 0,231, 0, 0,231, 206, 0, 0, 0,118,231,130, 50,
+206,231,231,175, 0,221,204, 35, 96,206,231,232, 0, 0, 0, 0,
+107,231,231, 0, 0,232,211,231, 232,231, 72, 0, 0,119, 0,129,
+
+// state[283 + 2] 0x0074c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0,221, 0, 0, 232,237,129, 0,231,221,231,129,
+ 0,231,207, 0,201,220, 0,231, 233,235,161,231,115, 0,221,204,
+199, 0, 88,118,191,207,138,231, 207,204, 0,231, 0,220,199, 98,
+231,231,231, 0,207, 0,138,118, 231, 0, 0, 0, 0, 0, 0,233,
+
+// state[284 + 2] 0x007500 Byte 3 of 3 (property)
+ 0,161, 0,231, 77,231, 0,211, 221, 0,229, 0,175,190,231, 0,
+ 0,197,231,231, 0, 91, 0, 0, 135,191,115, 0,126,231,231,137,
+ 0,211, 27, 24, 0,103, 93,211, 136,118,233, 68, 72,199,221,118,
+ 53,140,136,140, 0, 28,211,136, 118, 0, 44, 92,231, 0,199,231,
+
+// state[285 + 2] 0x007540 Byte 3 of 3 (property)
+232, 0,231,231,231, 16,231,221, 207,231,233,232,136,232,221, 68,
+232, 44,231,231,136, 0, 0, 0, 0,138,204,204,140, 68,220,231,
+101,220,127, 0,231,140, 35,237, 191,231,105, 25,210,231, 0,232,
+146,191,199,117, 72,219,121,142, 115,233,191,220,220, 0,207,105,
+
+// state[286 + 2] 0x007580 Byte 3 of 3 (property)
+207,220,231,207,207,220, 67, 97, 0,231, 49,105, 0, 0,117, 85,
+ 0,136,231, 0,232,231,220, 10, 0,208,209, 0, 0,111, 0,212,
+ 0,128, 0, 99,118,201, 0, 0, 0, 0,211,115, 0,221, 72, 16,
+ 0, 35, 88,197,220,111, 0, 0, 88,137, 0, 0,126, 99,140, 0,
+
+// state[287 + 2] 0x0075c0 Byte 3 of 3 (property)
+232, 0,201,231,221,136, 0,136, 179, 72,130, 0,221,197, 0, 0,
+211, 0, 65, 0,137,138, 0, 0, 126,146,231, 88,221, 0,121, 0,
+129, 0,137,111,199, 0,207,206, 220,117, 72,212,231, 0,231, 0,
+138,207,194,232,137, 0, 0,232, 231,124,107, 0,199, 0,204,209,
+
+// state[288 + 2] 0x007600 Byte 3 of 3 (property)
+130,199,211, 0, 0, 0, 0, 0, 207,233, 0,121, 0,146,220, 0,
+221,207, 0,121,230,232,211, 0, 179,199,211,232,208,211,204,118,
+209,143,189,204,134,221,118,129, 210,209,229, 72, 0, 0, 0,220,
+232, 0, 0, 0,197, 0, 0, 0, 199, 0,221,198,232,220,128,212,
+
+// state[289 + 2] 0x007640 Byte 3 of 3 (property)
+233, 0,107,231, 0,231,204,131, 204,232,231,231,136, 0,234, 0,
+ 0, 0,109, 0,220,231,100, 0, 204, 0, 0, 0,199, 0,191,210,
+231, 96,129,128,232,204, 0,231, 231,194,198,212,143,204,121, 0,
+204,129,129, 0, 0, 0,198, 0, 111,211, 15,137, 25,136, 68, 0,
+
+// state[290 + 2] 0x007680 Byte 3 of 3 (property)
+231,232,130,231,134, 0, 87, 68, 121, 0, 0, 67, 0, 0,206, 0,
+116,199,231,138, 0, 0, 72, 0, 211,130,204,232,204,204,204, 0,
+ 0, 0, 0, 0,231,231,231,231, 0,231,231, 0,220,231,136,191,
+121, 72,220,231,206,233,231,235, 232,231,119, 0, 0, 0, 0,103,
+
+// state[291 + 2] 0x0076c0 Byte 3 of 3 (property)
+ 0, 0,201,119,221,206,140,231, 85,231,140, 0,231,232,118, 72,
+133, 26,132,221,118, 0, 16,145, 28, 0,221,137, 75, 0,129,138,
+ 0, 76,221, 87,146,111,232, 51, 0, 0, 76, 0,232, 0,140,118,
+ 0,199,140, 0,140, 0, 0, 0, 138,199, 0,231,144,221,139, 0,
+
+// state[292 + 2] 0x007700 Byte 3 of 3 (property)
+231, 74, 0, 0,231,204, 0,232, 201,134,232,134,117,235, 0, 0,
+ 0, 0,221, 0,231,211, 0, 0, 0,199,231,232, 0, 0, 47,137,
+137, 0,232, 0,204,204,233, 0, 209, 88, 0,231,212,206,231,199,
+191, 0,221,210, 0,221,209,111, 115,207,106,210,136,220, 27, 0,
+
+// state[293 + 2] 0x007740 Byte 3 of 3 (property)
+ 65, 72, 0, 0,233, 0,231,199, 0, 0, 0,231, 0,233, 0,206,
+128,191,231, 0, 0,221, 0, 0, 220,220,231,118,129, 0,129,231,
+ 0,138, 72, 74,231,204,137, 0, 105, 0,129,126,207,211, 0,221,
+ 0, 0,221, 0,231,231, 0,231, 0,118,231, 0,231,206, 65, 68,
+
+// state[294 + 2] 0x007780 Byte 3 of 3 (property)
+231, 0, 0, 0,118,207, 0,127, 0,211, 0,119,122,211,118,211,
+ 0,106, 72, 0, 0,231, 0, 0, 0, 0, 0, 0,231, 0,119,206,
+189, 0,231, 0, 0,201, 0,118, 0, 72,130, 0, 88,111, 0, 0,
+ 88, 0, 0, 53,221, 0,204,221, 0,231,231, 85, 88,197,231,209,
+
+// state[295 + 2] 0x0077c0 Byte 3 of 3 (property)
+231,220,207, 0, 0, 0, 98,204, 212,207,212,220,230,233, 0, 0,
+ 0,231,191,168, 0,231,231,130, 0,207,121,139,201,220,232,231,
+ 0, 0, 53,126, 0,137,231,198, 0,134,221, 72, 0,140,126,146,
+ 0, 0, 0,137, 0, 0, 72, 0, 72, 0,231,221,231,121,167, 26,
+
+// state[296 + 2] 0x007800 Byte 3 of 3 (property)
+212, 16,103,221, 0, 0, 0, 0, 0,232, 0, 0,115,118, 0, 0,
+221,210,197, 0,137,117, 98, 0, 0, 0, 72, 0,212,207,211, 0,
+231,204,231,207, 0,103,100,115, 0,210, 0, 0,191,206,231, 0,
+208, 0, 87, 0,140,221, 0,118, 118, 0, 92,212,212, 0,191,203,
+
+// state[297 + 2] 0x007840 Byte 3 of 3 (property)
+ 26, 0, 0,121, 0, 99, 0, 0, 221,221,232, 0,221, 0,231,161,
+199, 0,120, 0, 0, 16,212, 0, 0, 0,133, 0,231,140,231, 0,
+231, 0, 0, 0,232,231,221, 0, 204, 0, 0,135,138, 0, 28,143,
+ 0,231,198, 0,231, 0,212,179, 0,231,231, 0,115, 0,207,221,
+
+// state[298 + 2] 0x007880 Byte 3 of 3 (property)
+ 0, 88, 0, 0, 0, 0,231, 87, 0,130, 0, 0,126, 72,118, 0,
+ 0, 68, 0, 87, 0,198, 0,138, 118,231, 72,212,220, 0, 0, 20,
+ 0, 0, 0,199,231,221, 0, 68, 0,127,231,221, 0,204, 0,231,
+144, 63,233,144,208,231,128, 0, 0,231,146,237, 27, 0,115, 0,
+
+// state[299 + 2] 0x0078c0 Byte 3 of 3 (property)
+ 0,136, 0, 0, 0,118,231, 0, 231,221,115,115,231, 0,231, 0,
+103,232, 0, 0,197,199, 0, 0, 221,220,127, 0, 0, 0,221, 0,
+ 0,233, 0, 0,231,231,231,204, 136, 0, 0, 0,201,207, 0,146,
+ 0,231,204,220,199,233, 0,141, 235, 72,118, 72,128,191,232,207,
+
+// state[300 + 2] 0x007900 Byte 3 of 3 (property)
+191,138,191,212,233,221,220,231, 212,207,212,212,220, 0,146, 0,
+221,204,198, 0, 0, 0, 0, 0, 0, 19,221, 0, 0, 0, 0,231,
+ 0,211, 0, 0,220, 0, 25, 0, 0, 0,194,143,194, 0,232, 0,
+231,211, 0, 0,191, 0, 0, 0, 0, 0,137, 0,145,210,137, 0,
+
+// state[301 + 2] 0x007940 Byte 3 of 3 (property)
+ 68, 67,121,231, 0,232,204, 51, 88,105,231,231, 0, 0,207,231,
+106, 0,211,116,221,127, 68, 51, 232,221, 50,120,199,137, 88,103,
+ 68,221,105, 0,221,136,191,221, 138,233, 0,207, 0, 88,220,191,
+207, 0, 0, 0,207,231, 0, 92, 72,191,111,220,230, 0,231, 50,
+
+// state[302 + 2] 0x007980 Byte 3 of 3 (property)
+191,137, 0, 0, 53,145, 0, 0, 0,231,197,233, 0, 68, 51,136,
+231, 0,207, 0,232,204,231, 0, 204, 0, 0,206, 0,231, 0, 0,
+ 0, 0, 0, 0,230, 0,119, 68, 231, 0, 50, 0, 0, 0, 76, 0,
+109,121, 0,232, 0, 0, 0, 0, 231, 68, 72, 10, 0,115, 68, 91,
+
+// state[303 + 2] 0x0079c0 Byte 3 of 3 (property)
+136, 88, 0, 72, 0, 0,128, 0, 204, 85,117,137, 0, 10, 0,211,
+ 0,140, 88, 0,231,191,221, 0, 136, 0, 0, 0,231,221,232,118,
+ 0,231,231,189,138,231, 68,118, 211, 74,221,233,233, 72,191, 26,
+ 65,231, 0, 0,212,212,207,207, 72,212,191,137,231,128,231,235,
+
+// state[304 + 2] 0x007a00 Byte 3 of 3 (property)
+140,191, 0,232, 0, 27, 0,220, 197,207,220,138, 0,118,145,212,
+220,128,207,212,103, 0,220,189, 232,161, 88,211, 87, 0,207,119,
+115, 0, 0,207, 0, 0, 0,231, 0,231, 0, 0, 0, 0,146, 0,
+231,127, 44, 26, 0,231, 0,171, 0,233,231, 67,100,138,231,103,
+
+// state[305 + 2] 0x007a40 Byte 3 of 3 (property)
+ 51, 0, 44,231, 0, 0, 68, 0, 0,231, 0, 0, 76,146, 76, 24,
+203,220, 0, 0, 0, 0, 0, 68, 0, 0,232, 0, 0,204, 0, 0,
+204,204, 51,116, 0, 0,211, 0, 211,122,231, 87, 0, 0, 0, 0,
+161,207, 0, 0, 88,231,140, 72, 221,111, 88, 0,231,198, 0,115,
+
+// state[306 + 2] 0x007a80 Byte 3 of 3 (property)
+ 0,135, 0, 74,134, 0, 0, 0, 111, 0, 0,231, 0,128, 0, 0,
+221, 72,103, 15,207,121,120,125, 130, 0,231,191, 72, 72,207, 88,
+206, 0, 0,221, 0, 72, 72,231, 207, 91,109, 0, 0, 0, 91,146,
+231, 0, 0,211, 0, 0,198, 0, 221, 0, 91,231, 0, 0, 0,199,
+
+// state[307 + 2] 0x007ac0 Byte 3 of 3 (property)
+ 0, 0, 0,203, 91,129, 0, 76, 116,221,129,136, 0,231, 0,231,
+ 0,206,232,231, 0,231, 63, 0, 232,125,232, 0, 44,161, 26, 85,
+134,232,231,139,231, 97,197,231, 0, 0, 44,232, 0,144, 0,136,
+231, 0, 0, 0,233,231, 87,220, 231, 68,137,231,231,210, 0, 97,
+
+// state[308 + 2] 0x007b00 Byte 3 of 3 (property)
+231,231,235,191,194,220,206, 0, 111,207,190, 63,233,231,204,105,
+ 0, 88,199,220, 16,212, 0, 0, 190,111,191, 88, 0, 0,233,133,
+ 47,212,210,210,220,105,115,220, 118,220,233,206,140,211,233,231,
+ 0,210,221,206, 0,232,231, 0, 0,117,191, 0, 72, 0, 0, 0,
+
+// state[309 + 2] 0x007b40 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0,198,143,220, 116,140,204,103,194,143,231, 88,
+ 88, 74, 88, 0,136, 0,140, 0, 0, 0, 0, 72, 0,108, 0, 0,
+130, 0, 0, 0, 0,198, 0,194, 0,231, 0, 0,203,231,206,231,
+198,118, 0,221,232,201, 0,118, 0,133,203, 0, 0, 0, 10,211,
+
+// state[310 + 2] 0x007b80 Byte 3 of 3 (property)
+ 63, 0, 0, 0,204,220,203, 13, 0,231, 0,146, 0,209,232,143,
+199,207,172, 98,136, 47,128,136, 191,231,191,128,232,204,191,191,
+199,134, 0,212,231,212, 0, 0, 231,212,101,191,207,126,207,191,
+212,138,207,212,111,207, 0, 0, 106,207,191,212, 0,231,207,231,
+
+// state[311 + 2] 0x007bc0 Byte 3 of 3 (property)
+143,189,231,221, 87, 0, 68,115, 211,146, 0,194,232, 0, 0,231,
+ 0,220, 0,191,221,221,231, 0, 0,210,231,231, 0,189, 0, 0,
+116,118, 0, 0,109,198,233, 0, 0, 17,199, 0, 0,116, 98, 0,
+ 0,191, 0,194, 0, 0,231,118, 0, 0, 0, 0, 0, 0,199, 0,
+
+// state[312 + 2] 0x007c00 Byte 3 of 3 (property)
+198, 0, 0, 0, 0, 0, 0,118, 0, 0, 0,233,206,204, 0, 0,
+ 0,194,237,231,231, 0, 0,198, 0, 0, 0,231,231, 0,204,232,
+231,143, 0,204, 0,230, 0,118, 207, 0,201,204,233, 0,220,231,
+231,191,220,231,231,220,220,121, 201, 0,220,207,207, 25, 87, 97,
+
+// state[313 + 2] 0x007c40 Byte 3 of 3 (property)
+199,191,210,121, 0, 0, 0, 0, 0, 0, 0, 0, 75, 88, 0,203,
+143, 0, 0,210,194, 0,231, 0, 231,204, 0, 0,211, 0, 0,129,
+107, 0, 0,221, 17,232, 0, 0, 0,232, 0, 0, 76, 0,129, 0,
+ 0, 0,127,137, 0,204, 0, 0, 0, 0, 0, 28,133,118,109, 0,
+
+// state[314 + 2] 0x007c80 Byte 3 of 3 (property)
+ 0,231,194,231,211, 0, 0, 0, 72,136,220,101,207,199,212,220,
+235,199,136,207,207,135, 0,140, 135, 72,220,101, 0, 0, 0, 68,
+220,235,232,207, 98,140, 0,109, 233,220,128,231,235,191, 26,128,
+231,118,206, 72, 0, 50,212,231, 0,118,220,128,206,111,134,122,
+
+// state[315 + 2] 0x007cc0 Byte 3 of 3 (property)
+116,220,231, 0, 0,233, 0, 0, 0, 0, 68, 0,220,207,198, 0,
+ 0, 0,231,231,231,144,140,206, 231,118, 0, 0,201,231,146,138,
+ 35, 0,204,231,231,231, 0,143, 0, 0, 0,231,127, 0, 0,115,
+127, 0,232, 0,232, 0,232,221, 116,231,203,134, 0, 0,119, 0,
+
+// state[316 + 2] 0x007d00 Byte 3 of 3 (property)
+143,231,164, 0,146,143,194,204, 204,129, 99,107, 0, 87, 0, 0,
+ 91,207, 72,118,107,199,207, 87, 51,146, 91,143,204,199,207, 0,
+137, 87,137,233, 0, 0,212, 28, 231,220,231,140,101, 0,119,134,
+146,233,199, 87, 0,233,207, 0, 220,109,116,231,231, 0, 0,232,
+
+// state[317 + 2] 0x007d40 Byte 3 of 3 (property)
+127,231,146, 91, 87,198,146,231, 231, 0, 0,198, 15, 0,231,232,
+107, 0, 0,231,231, 27,231, 0, 0, 0, 0,204,204,231,116, 0,
+ 0,143, 51,190, 0, 0,107, 0, 131, 0,211,231,221, 0,130, 0,
+ 0,131,121,164, 0, 13, 44, 0, 0, 87,233,207,220,172,191,199,
+
+// state[318 + 2] 0x007d80 Byte 3 of 3 (property)
+207,121, 0,232,231,191,191,191, 233,206, 98,191,231,207,199, 78,
+ 0,206, 0,127,207, 0,191,220, 0, 44, 15,231,131,231,221,231,
+ 21, 0,129,204,231, 0, 72,231, 0, 0,231,206,164,143,232,198,
+175, 51, 17,231,146, 76, 0,232, 119,232,109,107, 0,119,109,146,
+
+// state[319 + 2] 0x007dc0 Byte 3 of 3 (property)
+ 0, 0, 0,231,232,231, 0,204, 0, 0,143,146,231,232, 0,117,
+ 0, 44,146, 0, 0,231,161,232, 194,232,107,231,231,127,111, 0,
+109,199, 0,122,191,191,221, 0, 87, 87, 0,231,119, 0, 0, 87,
+ 0,206,199, 0,107,220, 0,231, 231,121, 0,131,231,231, 0, 0,
+
+// state[320 + 2] 0x007e00 Byte 3 of 3 (property)
+ 0, 15, 0, 0, 15,231, 0, 0, 129,204,194,198, 0, 0, 0, 0,
+211,204,198, 0, 0,221, 0, 0, 0, 0, 0,109, 0,204,109,232,
+232,231,232, 75, 0, 0, 15, 0, 0, 0, 0,107, 0,232,146,231,
+ 0, 25,198,234,204,204,231,131, 0,194,116,232,232,121, 87,233,
+
+// state[321 + 2] 0x007e40 Byte 3 of 3 (property)
+ 0,140, 72,130,191,221,130,206, 191, 0, 15,117, 0, 92, 0, 0,
+207,207,171, 0, 87,107,206,207, 207,232,201,133, 0,145,121,231,
+231, 75, 0,231, 0, 0,231,116, 0, 68, 76,122, 0, 87,231, 0,
+ 24,235,191, 20,128,220,191, 0, 230,111,220, 72,122,121,210, 98,
+
+// state[322 + 2] 0x007e80 Byte 3 of 3 (property)
+128,233, 68,191,220, 0,220,206, 108,233, 72,128, 25,124,231, 87,
+203, 0,198,204,204, 0, 25, 0, 204, 0, 0,233,127,231, 0, 0,
+133, 0, 16,207, 63,220, 28, 28, 220, 0, 16,128, 72,207, 0, 16,
+212, 72, 98, 28,231, 16, 72, 16, 16, 72,133,231, 0, 16,128, 28,
+
+// state[323 + 2] 0x007ec0 Byte 3 of 3 (property)
+ 72,191,207, 16, 28, 72, 10, 26, 28, 72,207,128,207, 10, 72, 28,
+207, 72, 72, 10,207, 16, 0,191, 16, 16, 72, 72, 10,128, 72, 28,
+128,207,128, 63, 0, 72,226, 28, 207, 16, 98,191, 0, 28,128, 72,
+207,179,207, 72, 16, 98, 72,167, 72,231,207,128, 28, 72,133, 10,
+
+// state[324 + 2] 0x007f00 Byte 3 of 3 (property)
+ 72, 98,128,207,191, 72, 72,207, 207, 72, 0, 0, 0,128,207,231,
+ 0,220, 0, 16, 98,128, 28, 0, 72, 98, 63,220,207, 16, 0, 0,
+ 72, 0,212, 0,133,207,220, 0, 191, 16,128,191,220,207, 72,220,
+179,235, 0,220,133, 0, 44,221, 144, 0,144, 0, 0,129,232, 0,
+
+// state[325 + 2] 0x007f40 Byte 3 of 3 (property)
+231, 0,207,231,206,233, 0,231, 204, 0, 0, 0,204,232,231, 0,
+126, 28,231,231,206,118, 0, 63, 72, 0, 26,231,233,211, 0,204,
+116,199, 72,206, 0, 0, 0,233, 231,144,140,101, 0, 0,140, 0,
+143, 0,138, 0,221,143,233, 93, 233, 97, 0,221, 0,231,233, 0,
+
+// state[326 + 2] 0x007f80 Byte 3 of 3 (property)
+231,128,233,129, 0, 51,189,233, 119,207,134,231, 99,191,136,220,
+ 0,232,220, 0,120,220,231,220, 0,220,111,221, 0,231, 88,191,
+ 0, 72,221,204,140,211, 0,199, 87, 87, 0,212, 0,231,231,111,
+233, 0, 50, 0,231,231,206, 0, 206, 88, 0, 0, 0,103, 0,121,
+
+// state[327 + 2] 0x007fc0 Byte 3 of 3 (property)
+233, 68,231,232, 0,126,198,231, 0, 0, 50,204,106,231,130, 0,
+ 0,221,107, 0,140,206, 0, 0, 72, 0, 0, 0, 0, 0, 0,120,
+134,138, 0, 0, 0,233,121, 0, 231,130, 0,198,204,220,232,220,
+ 68,209, 0,189,210, 0, 0, 0, 0,119,231,140, 68,233,207,220,
+
+// state[328 + 2] 0x008000 Byte 3 of 3 (property)
+ 68,138,231,137,233,137, 51,231, 0, 0, 0,206,118,118, 0,231,
+ 88,206, 72, 0, 0,139, 0,134, 115,209, 0, 0,233, 0, 0,220,
+ 0,191, 0, 0,220, 0,130,191, 197, 0, 0, 0, 0, 0, 0, 0,
+231,212, 0,136, 0, 0, 68,233, 128, 0, 0, 72,231, 68,231, 68,
+
+// state[329 + 2] 0x008040 Byte 3 of 3 (property)
+220,220, 72,233,220,211,118, 0, 0, 0,144,128, 26, 0, 0, 0,
+ 0, 0,206, 0, 28, 0, 51, 0, 118, 0,118,231,231, 0,107,231,
+ 0,117,231, 0,231, 0, 0, 0, 231,220, 72,231, 0, 0, 0,121,
+ 91, 0, 75,131, 15,232,121,107, 0,231, 0, 0, 0,121, 87, 78,
+
+// state[330 + 2] 0x008080 Byte 3 of 3 (property)
+ 0, 0, 0,133,121, 49,111,138, 232,136, 0,137,136,231, 0,221,
+ 0,220, 0,206, 0, 0, 68, 0, 88, 0,118,136,232,136,231,191,
+ 98,126,136, 0, 72,139, 0,220, 0,137,136,230,231,232,199,115,
+ 0,164,137, 0, 35, 0, 0, 0, 231,231,140, 0,191,141, 72, 16,
+
+// state[331 + 2] 0x0080c0 Byte 3 of 3 (property)
+ 98, 98,233,137,199, 0, 65, 0, 231, 0, 0, 0,136,207,138, 0,
+ 0,207, 0, 0, 0, 0,118,221, 0,231,136,197,124,202,136, 0,
+231,134,231,231, 53,201, 0,167, 0, 0,220,207, 0,209, 0,209,
+144, 88, 0,207,103, 0, 16, 0, 136, 0,118, 0,204,140, 0, 0,
+
+// state[332 + 2] 0x008100 Byte 3 of 3 (property)
+ 0, 0,136, 0, 0, 87,136,117, 87, 72,136, 0,212,191,231, 98,
+ 72, 16,207,128, 0,199,118,191, 206,207,145,164,212,207, 0,220,
+212,207,208,170,210,207,212,191, 212,164,212, 75, 0,207,212,209,
+199,145,199,117,220,231, 0,235, 72, 87, 0,220, 0,220,134,220,
+
+// state[333 + 2] 0x008140 Byte 3 of 3 (property)
+220,231, 0, 0, 0,231,206, 0, 199, 0,118, 88, 72, 0,107, 0,
+135, 88, 0,201,140, 88,231, 0, 0,231,235, 0, 0,231, 0,198,
+232,231,221,231, 0,118, 23,232, 0,207, 0,146, 0,207,130,231,
+137, 88, 0, 27,206,212, 0,207, 87,103,136, 72,207, 0, 98,134,
+
+// state[334 + 2] 0x008180 Byte 3 of 3 (property)
+140, 0,231,232, 0,231, 0, 0, 206, 0,199, 0, 0, 0, 0,138,
+ 0,220, 0,231, 0,232, 0, 0, 207, 0, 87,209,136, 88, 0, 0,
+119, 0, 0,116,231,231,199, 0, 137,127, 0,231, 0, 0, 0, 0,
+232,232,232, 88,231,117,231,231, 231,231, 96,199,232, 76,194,146,
+
+// state[335 + 2] 0x0081c0 Byte 3 of 3 (property)
+126, 0,126,207,231, 0,164,231, 203, 25,233, 0,220,143, 0,204,
+ 0,231, 0, 15,232, 0, 0,191, 78,190,204,231, 0,231, 0,127,
+232, 0,221, 51, 0, 68,233,171, 87,220,140,207,210, 88,220, 0,
+212,212, 0,126,140,220,220, 0, 0, 0,127,118,106, 0,199, 0,
+
+// state[336 + 2] 0x008000 Byte 2 of 3 (relative offsets)
+ -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+
+// state[337 + 2] 0x008200 Byte 3 of 3 (property)
+206,190,206,220, 0,115, 16,122, 143, 27, 75,210,136, 82, 22,231,
+105,231, 85, 0,132, 0, 19, 15, 103,220,212,105, 68, 0,136,140,
+220,231,210, 0, 0, 0,210, 0, 204,203,137,201, 88, 0,231, 0,
+ 72, 72,221,198, 0,137,135,103, 191,137,122, 0, 0, 0,233, 0,
+
+// state[338 + 2] 0x008240 Byte 3 of 3 (property)
+231,231,231, 0, 0, 0, 0,136, 0,232, 0,122,220, 0,231,221,
+ 0,231,231,231,231,231, 0,231, 115, 75,231,232, 0,231,231,231,
+ 0, 0,231, 0,198,231, 87,231, 231, 0,203,198, 0, 0,171,103,
+ 98,119,140, 63,231, 0, 24, 25, 109,231, 10,231, 0,230,118,207,
+
+// state[339 + 2] 0x008280 Byte 3 of 3 (property)
+231, 0, 26,206,231, 0, 0, 0, 0, 0,132,136, 0,201,121,231,
+ 0, 0,138, 0, 0, 0,221, 98, 233,134,231,211, 98,137,210,231,
+210,206,211,210, 0,135, 71,232, 233,206,207,206,130, 97, 96,140,
+232,136, 0,136,232, 0,221,130, 106,140, 0,194,231,137,199,231,
+
+// state[340 + 2] 0x0082c0 Byte 3 of 3 (property)
+221,212, 0,231,207,117, 0,133, 0, 0,231,212,220, 98,179, 11,
+ 0,140,199,111, 88,199,232,134, 235,232, 0, 88,209, 0,111,118,
+ 0,206, 0,130,210, 97,137,162, 0, 0, 0,100, 0,231, 0,118,
+ 0,136, 0,129,199, 0, 0,209, 0, 63,109,233,231,231,210, 0,
+
+// state[341 + 2] 0x008300 Byte 3 of 3 (property)
+232,130, 68,124,115,137,199, 0, 220, 68, 0, 0,191,211,145,191,
+ 0, 0,231, 0,220, 0,231,138, 231,231, 0,204,136,231,232, 0,
+204, 0, 0,231,231,231, 0, 72, 100,221,221, 68, 72,208, 0,201,
+231,111,119, 0,130,118,136,231, 137,136, 0, 0,208, 0, 0,231,
+
+// state[342 + 2] 0x008340 Byte 3 of 3 (property)
+201, 0,211,130, 0,204, 98,232, 231,140,119, 0,211, 0,211,100,
+ 10,232,137, 0,118, 0,129, 0, 24, 0, 98, 0, 0, 0,212, 72,
+212, 16,231, 28, 98,191, 0, 72, 72, 0,191, 72,191, 0, 0, 28,
+ 0, 0, 0,119, 0,231, 0, 88, 207, 0,221,106, 88,199, 0,231,
+
+// state[343 + 2] 0x008380 Byte 3 of 3 (property)
+ 0, 0,231, 0, 0, 72, 67,231, 0,111, 75, 0, 0, 0,118, 0,
+ 0, 0,130,144, 0,221, 50, 0, 118,232,231,232,207, 0, 99,231,
+201, 0,131, 0,221, 0, 0,204, 197,231,199, 68,231, 0, 0, 0,
+ 0,108, 63,220,133,203,231, 28, 231, 72,191,231,226,171, 0,129,
+
+// state[344 + 2] 0x0083c0 Byte 3 of 3 (property)
+206,130, 0, 0, 0,105, 0, 82, 211,232, 88, 0,140,231,231, 72,
+212,221, 0, 13,232,231,103, 0, 206, 0, 0, 0,140,204, 0, 47,
+118,206, 0,231, 0, 0,231, 0, 0, 68,233,194,231, 0, 0,143,
+105, 88,118,231,164, 0,231,231, 19,231, 0,232,231,206,231, 0,
+
+// state[345 + 2] 0x008400 Byte 3 of 3 (property)
+ 0,204,231,126,134, 0,221,204, 0, 0, 75,206,103,118,140,232,
+ 0,231, 0,232, 0, 0, 0, 0, 199, 0, 0, 0,233, 98, 0, 0,
+198,221,231,129,167, 26,212, 98, 16, 47,231,231, 76, 0, 0, 0,
+ 0, 68, 0, 0, 0,204, 0, 0, 201, 0, 0, 0,111,137, 0, 0,
+
+// state[346 + 2] 0x008440 Byte 3 of 3 (property)
+231, 0, 0, 0, 0, 0,118,221, 231,146, 0, 0,231,231,198,231,
+ 0,199,221,231,231,231,211, 88, 0,221,233, 88, 0,231,231,231,
+231,134,237,115, 0,231, 91,231, 231,209, 0,118,137,162,231,194,
+ 0, 65, 0,121, 0, 88,210,129, 0,231,100,231, 0, 0, 0, 0,
+
+// state[347 + 2] 0x008480 Byte 3 of 3 (property)
+231,231,144, 0,231,231, 0,231, 231, 0, 0, 72,212, 0, 0, 0,
+ 17, 0, 0,231, 47, 0, 0,191, 0, 68, 0,231,134,221, 19,119,
+ 0,201,231, 0, 0, 0,231, 0, 121, 0,231, 0, 0,231, 0,206,
+ 0,231, 68, 0,206, 0, 0, 0, 137,233,210,119,146,233,231, 99,
+
+// state[348 + 2] 0x0084c0 Byte 3 of 3 (property)
+129,121,231, 0,137, 0,129,232, 0,126,204, 51, 0,232, 0, 0,
+231,100,148,130, 0, 0,199, 0, 0,231,231,231,231, 16, 0,128,
+ 0, 0, 0,235, 0,133,191,231, 231,231,232, 0,115,231,131,204,
+ 0,191, 0,204,204,231,231,231, 0, 0, 0, 0,103,211,231,209,
+
+// state[349 + 2] 0x008500 Byte 3 of 3 (property)
+198, 0, 0, 0, 0,231,232, 0, 0, 0, 0, 0,231, 0, 0, 0,
+ 0,103, 0,134,121,231, 0,118, 76, 0, 68,231, 0,231,211,233,
+ 0, 85, 0, 75, 0,127, 91, 0, 0, 0, 0,207,141, 87, 0,231,
+231,231, 0, 0,211, 44, 0,207, 212, 0,212,132,199,140,231,231,
+
+// state[350 + 2] 0x008540 Byte 3 of 3 (property)
+232,194, 0, 68, 0,204, 0, 0, 206,140,126,231, 0, 0, 13, 0,
+ 0, 0, 0,204, 0,232,221,198, 232,111,231, 0, 0, 0,232, 0,
+ 0,231,221,231,206,221, 0,231, 88, 93,143,231, 0, 76, 0,231,
+ 0, 0,191, 0, 63, 0, 0,204, 0,199,199,233, 0, 0,138, 0,
+
+// state[351 + 2] 0x008580 Byte 3 of 3 (property)
+198, 0, 0, 0,136,233, 0,136, 129, 0,164, 0,175, 0, 0,121,
+231,121, 0, 0,146, 0, 0,116, 127,109, 0, 85,206, 0, 0, 0,
+231,221, 0, 0,232, 0,131, 0, 162, 91,126, 44, 15,231,101,115,
+ 77, 0, 0, 0, 0, 0,231,232, 0,189,164,231, 0, 0,232, 0,
+
+// state[352 + 2] 0x0085c0 Byte 3 of 3 (property)
+ 0, 53, 0, 0, 0, 0, 0, 0, 0,130, 0, 0, 0, 91,221,144,
+201, 0, 0,191, 0,120,231, 0, 0, 0, 0, 0,206,121, 0,233,
+220, 0,231, 0,105,122,211, 0, 232, 53, 53, 0, 0, 0, 0, 0,
+ 0, 0, 0,231, 0, 0,221,111, 0,194,129, 88,231, 0,231,206,
+
+// state[353 + 2] 0x008600 Byte 3 of 3 (property)
+ 0, 0,198, 0,204,206, 76, 51, 0, 0,119, 25,231, 0, 0, 0,
+231,120,231,231, 0, 0, 63,204, 221, 0,194, 0, 0, 0,221,231,
+ 0, 0,238, 0,231, 0, 0,232, 0,232, 0, 0, 0,129, 0,231,
+231, 0,231, 0, 0, 0,221, 0, 199, 0,232, 0,221, 0, 0,129,
+
+// state[354 + 2] 0x008640 Byte 3 of 3 (property)
+231,231,231,231, 0, 0, 0, 0, 231, 0,231,231,232,231,134,191,
+ 88, 16,232,231, 68, 25,231,231, 231,231,142, 75, 47,231, 68,127,
+231, 0,199, 0, 0, 0, 0,127, 0, 0, 0, 35,191, 0, 0,204,
+231,115, 0,221,211,231, 0,221, 0,140,232,105, 0, 16, 98, 0,
+
+// state[355 + 2] 0x008680 Byte 3 of 3 (property)
+ 72,128,128, 0,231, 0, 0, 0, 0, 0, 97,204,120, 0, 0, 0,
+221, 0, 0,209,211,142,231, 0, 221, 0,210, 0,124,191, 0, 0,
+ 0,231, 0,206,111,221, 0,207, 231,199,206,231,212, 0, 0,209,
+231,207, 0,204,220,121,199, 0, 221, 0,221, 0, 0,231,211, 0,
+
+// state[356 + 2] 0x0086c0 Byte 3 of 3 (property)
+132,211,221, 0,199,231,201,136, 0,202,191,115,221, 44,172,212,
+207, 0, 0,231,199, 0, 0, 0, 0,126, 0,136, 0, 0,204, 99,
+231,231, 0,231,111, 0, 0, 0, 0,232, 0, 0,231,100, 65,116,
+207, 0,231,212,212, 0, 0, 0, 105, 88,211,129, 0,231,138, 0,
+
+// state[357 + 2] 0x008700 Byte 3 of 3 (property)
+ 68, 0,136, 53, 0, 0,129,199, 201,194,201,231, 0,201, 0, 0,
+ 0,232,130,130, 0,207, 0, 72, 136, 0,199, 0,138,231,206,231,
+ 0, 63,206, 0, 0,111,221, 0, 0,190, 0, 0, 0, 0, 0, 0,
+ 0,199, 0, 0,201, 0, 0,100, 0, 0,231, 88, 0, 0, 0,111,
+
+// state[358 + 2] 0x008740 Byte 3 of 3 (property)
+231, 0, 0, 0, 0,228, 0, 98, 212,142, 0,117,206, 0, 72, 0,
+232,221,221,194,211,131,221,201, 211,111,211,211,211,204,211,204,
+111,211,211,194, 0,233,131,221, 76, 0,204, 0,211,221,202,211,
+ 0, 0, 0,211,130, 0, 97,211, 131, 0,211, 0,191,207,212,117,
+
+// state[359 + 2] 0x008780 Byte 3 of 3 (property)
+ 0, 0,111,132,221,230, 0, 0, 210, 0,221, 0, 0,140, 0, 0,
+ 0, 0,231,231, 0, 0,207,231, 0, 0, 0, 0, 0, 0,121, 99,
+ 0, 0, 17,199, 0, 0, 0, 0, 124, 0, 0,204,207,206, 0,209,
+ 0, 0, 0,201, 0,232, 0, 0, 0, 0,138,194, 0,204, 0,232,
+
+// state[360 + 2] 0x0087c0 Byte 3 of 3 (property)
+130, 0, 0, 0,194, 0,201,198, 232, 0,220,121,211, 0,211, 0,
+231,130,209, 0, 0, 0, 0, 0, 0, 0, 0,221,231, 0, 0, 0,
+201,211, 0,232, 0,220, 0, 0, 0, 0, 0, 0, 50, 0,220,194,
+ 0, 0,129,210,221, 0,198,198, 0,138, 0, 87,231, 0,111, 0,
+
+// state[361 + 2] 0x008800 Byte 3 of 3 (property)
+ 0, 0,221,231,231,119,232,231, 221,231,233,204, 0,119,203,231,
+ 0,204,231,221,204,115,189, 0, 0, 0, 0,231, 0, 0, 0,127,
+ 0,209,115,146, 0, 0, 0,231, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,194,232, 0, 0,127, 51, 0, 0,199, 0, 75, 0, 0, 0, 0,
+
+// state[362 + 2] 0x008840 Byte 3 of 3 (property)
+136, 0,231, 0,206,191, 44, 0, 0, 0,204, 0,137, 68,231, 0,
+ 0,231,198, 87, 98,231,211,140, 0, 68,231,143,231, 87,194, 0,
+ 0,134, 72,136,231, 28, 0, 0, 140,233,211,144, 72,211,191, 0,
+136,221,206, 0,231,231, 0,134, 0,211, 0, 0,231,233,197, 47,
+
+// state[363 + 2] 0x008880 Byte 3 of 3 (property)
+231, 67,201, 0,191,179, 0, 0, 105, 0, 0, 88, 0,126,231, 0,
+ 0, 0,209,211, 0, 0, 88,232, 220,233,233,232, 72,220,204, 0,
+ 0, 0,101, 0,199, 0, 0, 0, 0, 0,204,138, 0, 98,231, 0,
+231, 99,231, 0,116,231, 0,190, 0, 0, 0, 0, 0, 0, 0,231,
+
+// state[364 + 2] 0x0088c0 Byte 3 of 3 (property)
+ 0,137,137,203,203, 65,207, 0, 0, 0,204, 0, 0, 0,221,109,
+ 0, 0,231, 0, 68,137,231, 0, 118,144, 0, 0,107, 27, 0,106,
+ 0,121,231, 0, 72,231, 0, 0, 206, 0, 0, 0, 0, 0, 0, 0,
+ 0,130,231, 88,115,161, 0, 0, 137,118, 0, 0,231,107,105, 0,
+
+// state[365 + 2] 0x008900 Byte 3 of 3 (property)
+ 0,231,199, 0,203, 0, 0,143, 0,220,232,231,197,191,207,191,
+140,233, 88,121,132, 0, 0,231, 232,211,209,235,191,231,231,232,
+ 0,233,231, 0, 0,103,231,231, 231, 0, 88,204, 0, 0, 0, 0,
+128,231, 19, 0,220,221,111, 0, 204,231, 0,143, 0, 0,231, 0,
+
+// state[366 + 2] 0x008940 Byte 3 of 3 (property)
+ 0,233, 0,231, 68, 0, 0, 0, 0,221, 0, 0,232,231, 0, 0,
+ 0, 0, 0, 0, 0, 0,109, 0, 0, 0,231,231,221,221,197, 88,
+204, 0, 0, 0,204, 0,197, 0, 0, 0,127, 0,174,231, 0, 19,
+ 0, 0,146, 0,198,231, 0,203, 0, 0, 0, 0,231, 0,231,140,
+
+// state[367 + 2] 0x008980 Byte 3 of 3 (property)
+231,140,221,120, 0, 0,111, 44, 129, 0,231,109, 0,231, 0,107,
+ 0,231,231,121,234, 0,143,117, 198, 0, 15, 0,221, 0, 0, 0,
+ 0,232, 0, 0, 0, 0,204, 15, 0,231,107, 0,204, 0, 0,198,
+ 0, 0,204, 24, 0, 0, 0,204, 0, 0,127, 0, 0, 23, 0,198,
+
+// state[368 + 2] 0x0089c0 Byte 3 of 3 (property)
+ 75, 28, 10, 0, 26, 72, 28,212, 10,128,191,231, 0, 0,212, 0,
+207,207, 68,231,204, 0,231, 0, 0, 0,232, 0,162,231,220, 0,
+ 0, 0, 0,140, 0,233, 92,231, 0, 0, 0, 0, 0,231, 0, 0,
+ 0, 0, 0,221,175, 0, 0, 0, 127, 0, 0, 0, 0,231, 0, 0,
+
+// state[369 + 2] 0x008a00 Byte 3 of 3 (property)
+ 88, 0,131,109,231, 0, 0,233, 107, 0, 17, 0,232, 0,143,231,
+204, 0,231,143, 0,204, 76,146, 146, 0, 0, 47, 0,131,231, 87,
+ 0, 0,170,143, 0,161, 0,206, 0, 0,107,220, 0,107, 0, 0,
+221,143, 0,117,143,231,143,231, 0, 0,107, 75, 13,231,228, 0,
+
+// state[370 + 2] 0x008a40 Byte 3 of 3 (property)
+221,204, 0, 0,231, 0,204, 0, 197, 0, 0, 0, 0, 0,204, 0,
+143,231,204, 0, 51,107, 0, 0, 0, 0, 0,131, 0,231, 87, 0,
+ 87,175, 25, 47, 0, 0,107,231, 211, 87, 0, 13,204,119,143, 0,
+ 13,107,143,146,231,232,231, 0, 0,130,231, 0,204,231, 0,231,
+
+// state[371 + 2] 0x008a80 Byte 3 of 3 (property)
+ 0, 0,198, 0,232,202,204,109, 0,142,235, 0,146,146, 0,221,
+ 0,194,221,137,211,146, 0, 0, 146, 0,204, 0, 0, 0, 91, 0,
+ 87, 76, 0,170, 91,204, 51,204, 129, 0, 27, 0, 15, 15, 0, 0,
+146, 0,107, 0, 0, 0,221, 0, 0,146, 0, 0, 76, 0,232, 87,
+
+// state[372 + 2] 0x008ac0 Byte 3 of 3 (property)
+ 0,231,204, 0,194,231, 0,146, 0,204, 0,143,203,194, 0, 44,
+ 0, 0, 91, 0, 0, 0,143,232, 0, 0,231,204, 51,231,232,231,
+194,161,232, 0,238, 0,107, 91, 0, 0, 0,106,231,146, 17, 0,
+231, 51,231, 76,211, 0,175, 91, 146, 0, 91, 0,211, 0, 87, 0,
+
+// state[373 + 2] 0x008b00 Byte 3 of 3 (property)
+ 87,164, 93, 0,143,211,221,199, 0, 0,121,221,203,211,146,221,
+109,211,211,221,194,127,194,107, 211,146,194,146,204,143,221, 0,
+ 76, 44,211, 0, 0,221,232,231, 51, 0, 0,204, 51,231, 0, 0,
+ 0,231, 0,109, 0, 0, 0,231, 0, 91,221,231,231, 0,129, 0,
+
+// state[374 + 2] 0x008b40 Byte 3 of 3 (property)
+ 0,194, 0, 0, 0,231,231,211, 0, 75, 0, 0,198, 0,194,175,
+ 0, 0, 0,204,221, 0,232, 0, 107,175, 51,203, 91, 0, 0,232,
+211, 0, 0, 0, 0, 0,138, 0, 0,231, 0,194,111,211, 0, 75,
+ 87,231, 15, 0,129, 0, 0,107, 0, 0, 0, 0, 0,121, 0,231,
+
+// state[375 + 2] 0x008b80 Byte 3 of 3 (property)
+127,231, 0,117, 0, 0, 0, 0, 231,231,127,221,232, 0,204, 0,
+116, 0,194,127,231,236,194, 0, 0,231, 76, 0,232, 0,204, 0,
+231, 26, 26,191, 28,191, 0,233, 16, 16,207,207, 0, 28, 28, 28,
+ 28, 0, 28, 72,212, 0, 72,179, 10,207, 28, 0, 98, 72, 26, 28,
+
+// state[376 + 2] 0x008bc0 Byte 3 of 3 (property)
+124, 28,235,207, 26,207, 28,231, 128, 28, 16,207,220,128,231, 72,
+ 0, 72,191,233, 0, 16, 0, 16, 191,191, 16,128,231, 10, 98,191,
+ 72, 72, 28,207,191, 10, 28,207, 235,191, 0, 72,191, 63,231, 16,
+191, 16, 72,220, 16, 72,191, 16, 16, 0, 16, 28,231, 72, 28, 72,
+
+// state[377 + 2] 0x008c00 Byte 3 of 3 (property)
+220, 16,231, 10,235, 72,191, 0, 28, 0, 98,133,207, 72, 72, 72,
+133,207,191, 16,235,212,231,212, 233,207,212,191, 72, 0, 0,191,
+ 0,220, 72, 63,128,212, 72,191, 98,191,235,237,207, 72,231,191,
+233, 16,235,231, 72, 0,220, 53, 0, 0,231, 0, 0, 0, 0,164,
+
+// state[378 + 2] 0x008c40 Byte 3 of 3 (property)
+ 0,126,221, 0, 0, 0,137,207, 119,199, 44,127, 99, 0,121, 0,
+127,221, 0, 0, 75,197, 0, 0, 0, 0,103, 0,221, 0, 0, 0,
+ 0,137,202, 0, 0, 0, 0, 0, 232, 0,136, 85, 25,231, 0,211,
+232,211,211,232, 0, 0, 0, 0, 233, 68,206, 0,231,231, 0, 0,
+
+// state[379 + 2] 0x008c80 Byte 3 of 3 (property)
+ 0, 0,111, 0, 0,206, 0,231, 0,209,161, 0,134,194,232, 0,
+231,231, 0, 25,121,232, 0, 0, 204,231,231,231, 0,143, 51, 0,
+ 87,143,146, 0, 0, 0, 0, 87, 143,109,129,146, 87,231,232,109,
+ 44, 0,204, 76, 87, 0,119,146, 146, 0,232,143,143,129, 0,119,
+
+// state[380 + 2] 0x008cc0 Byte 3 of 3 (property)
+146,204,119,109,143,211, 0,131, 51, 0, 51,231, 0,231,203, 0,
+ 0,109,211, 91, 0,231,231, 0, 0,221,232, 15, 87,221,146, 0,
+143,129, 51,127, 51, 0, 91, 0, 231, 0,146, 0,121,143,231,231,
+231, 0, 0, 0, 75,231,231,231, 129,204, 19,232,143,121,238, 0,
+
+// state[381 + 2] 0x008d00 Byte 3 of 3 (property)
+231,235,191, 0,116,109, 0,232, 143, 0, 76,116,220,204, 0, 25,
+231, 0,231,129,116,231, 51,211, 0,231, 0,170, 0, 63, 72, 28,
+207,133, 26, 26, 16, 16, 16, 16, 28, 72, 16, 16, 72, 26, 98, 26,
+ 98, 72,207, 0, 28, 16, 0,133, 28, 28, 63,128, 72,220, 98, 98,
+
+// state[382 + 2] 0x008d40 Byte 3 of 3 (property)
+ 0,133, 98, 63, 28,207, 0, 0, 133,212,212, 98, 98,234, 72, 16,
+ 72,231, 0,191,133, 0, 16, 0, 191, 0,133, 16, 0,226, 63,212,
+ 16, 98, 98, 98,100, 0, 68,204, 0,231, 0, 68,221, 72, 0,211,
+140,231, 0,189,134, 16, 72,140, 0, 0, 0,210, 0,211, 0, 0,
+
+// state[383 + 2] 0x008d80 Byte 3 of 3 (property)
+ 0,118, 0,231,220,136, 0, 0, 0, 0,137,133, 0, 0, 0, 0,
+231,231, 0, 0, 0, 25,211, 0, 0, 51, 0, 0, 0, 0,231,118,
+ 0,231, 0, 88, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0,231,
+ 0,231,204,137,121,199, 0, 0, 212,231,204, 0,221, 0, 97,231,
+
+// state[384 + 2] 0x008dc0 Byte 3 of 3 (property)
+ 0,231,231, 98,235, 0,130, 0, 0,231, 0, 68,118, 0,206,197,
+ 0,144, 0, 0, 0,231,206,233, 0, 0,199,201, 0,140, 0,144,
+ 0,146, 0,231,209, 0, 0, 0, 118,211,115,232, 0, 0, 0,138,
+ 0,231, 0,138, 0, 71, 0,207, 220,220,124,128,231, 0, 0,231,
+
+// state[385 + 2] 0x008e00 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0,221, 0, 0, 231,206, 92,231,212, 0, 0,136,
+ 75, 0, 0, 0, 0, 0,231, 0, 0, 0, 0, 0, 0,111,201,232,
+231,231,126, 0, 0, 0, 0, 0, 221,118,108,211, 0, 0,230, 0,
+170,207, 0, 0, 19,103,231, 0, 0,206, 0, 0,221,210, 0, 0,
+
+// state[386 + 2] 0x008e40 Byte 3 of 3 (property)
+ 0, 0,201, 0,134, 0, 0, 72, 118,202,103,206,204,232,211, 0,
+232,220,212,211,231,232,231, 0, 231,197,231, 0, 0, 0, 0, 76,
+194,231, 0,129, 23, 0,118,221, 0,230, 0, 0,209,199, 0, 0,
+231, 0,115, 0,100, 0,199, 0, 0,231,129,232,206, 0, 0,191,
+
+// state[387 + 2] 0x008e80 Byte 3 of 3 (property)
+ 0,138,211, 0,231,197, 0,100, 0,211,105,204, 0,107, 0,207,
+221,198,231,194,231,232, 0, 0, 0,203,231, 0, 0, 0, 0, 0,
+ 0,204, 0, 0, 0,221, 0, 0, 0, 0,204,136, 68, 0, 0,142,
+198,231,118,231, 0, 0, 0, 0, 0, 0,118, 0, 0, 0,117, 0,
+
+// state[388 + 2] 0x008ec0 Byte 3 of 3 (property)
+129, 0, 0, 0, 0,231,233, 0, 231,231,107, 91, 87, 51, 0,231,
+ 0, 0,146, 0,210, 0, 0, 0, 220, 0, 0,204, 0,231,221,131,
+ 0, 0, 15,231, 0, 0,220, 0, 0, 0, 0,161,220, 0, 0, 0,
+231, 0, 0,220, 0, 0,220,220, 146,233,221,164,204,117, 76, 0,
+
+// state[389 + 2] 0x008f00 Byte 3 of 3 (property)
+ 0,220,207,143,207,199, 0, 0, 0,146,204, 0,101, 0, 0, 0,
+ 0, 0, 50,204,143, 75, 0, 0, 0,231,204,121,197, 87,232,121,
+ 0, 0, 0,220, 0,199,189, 0, 220,146,107,231,235,231,231,121,
+231, 0,231,198,231,235,231,231, 143,191, 0,143, 0, 0,129, 87,
+
+// state[390 + 2] 0x008f40 Byte 3 of 3 (property)
+220,220,199, 0,143, 76,194, 0, 0,122, 0, 0,231, 51,121,231,
+ 0, 0, 0,231,231, 0, 0,231, 220, 0, 0, 0,231, 0, 0, 87,
+231,194,116,231,197, 0, 16, 72, 98, 72, 0,220, 28,220, 16, 10,
+ 72,220,207, 0, 16,220,191, 0, 220, 0, 0, 16,191, 28, 0, 72,
+
+// state[391 + 2] 0x008f80 Byte 3 of 3 (property)
+ 0, 0, 0, 28,191, 16,133,220, 72, 16, 72,235, 0, 72,220, 0,
+ 98, 28, 0, 28,231,133,133,207, 191,191, 0,136,126, 0, 92, 67,
+ 0, 0, 0,115, 0, 0, 27,231, 67, 16, 0,191, 0, 75,204, 76,
+ 51,135,107, 0, 0,231,231,198, 0, 16, 15,116, 15,133, 28,116,
+
+// state[392 + 2] 0x008fc0 Byte 3 of 3 (property)
+ 0,133,103, 0, 88,139,231, 10, 16, 0,231,231, 0, 0,138, 0,
+ 28, 88,231,204,100, 0, 0,231, 10, 16,203, 28, 10, 26,128, 98,
+231,232,111,231,170,130, 51,231, 204,172,118,137,231,115,221,231,
+140,231, 0,207, 25,221,231, 88, 201, 72,121, 0,232, 88,231, 0,
+
+// state[393 + 2] 0x009000 Byte 3 of 3 (property)
+138,103, 10,137,206,111,103, 0, 231, 28, 72,206, 0, 68,231,138,
+115,206, 16,116,137,129,204, 97, 0, 25,140,130, 0,137,137,136,
+140,197,136,146,221,231,220,231, 0, 0, 0, 0, 0, 0, 88,230,
+ 0,107,143, 0,231,201,233,231, 68,194, 0, 72, 68, 0,144, 0,
+
+// state[394 + 2] 0x009040 Byte 3 of 3 (property)
+ 0,111, 88, 0,204, 15, 0,140, 0,231,143,143, 0,115,143,118,
+111,206,233,137, 87,146,203, 16, 232, 91, 0,208, 51,221, 75, 0,
+ 87,116,210, 53, 0, 35, 0,231, 130,107, 0, 0,231,138,136,232,
+ 0,231, 76, 0,132,115,129, 91, 107,231,146, 0, 51, 87,231,140,
+
+// state[395 + 2] 0x009080 Byte 3 of 3 (property)
+118, 91,111,118,143,231,231, 91, 199,116, 76,206, 0, 0,231,129,
+204, 68, 0, 98, 0, 67, 0,128, 231,191,231, 72, 0,212, 0, 0,
+232,191, 67, 68, 0,231,136, 0, 189, 0, 68, 0,207, 0, 10, 67,
+208, 96,231, 72,233, 67,231, 0, 88, 72,133, 16, 0,231,233, 0,
+
+// state[396 + 2] 0x0090c0 Byte 3 of 3 (property)
+ 0, 68, 0,204,233,199, 0,233, 204, 0,135, 0, 0, 0, 53,212,
+ 0, 16, 0,212,211,232, 0,233, 0,231, 0,231,199,118,161,231,
+ 0, 47,189, 0,204, 0,212,133, 140, 0,233, 72,231, 68, 0,199,
+ 0, 0,231, 0, 72,143, 0,117, 72, 0, 0, 0, 0,137,191,232,
+
+// state[397 + 2] 0x009100 Byte 3 of 3 (property)
+231, 0, 72,231,191, 0,232, 0, 0, 27, 0,221, 0, 0, 0, 0,
+221, 0, 50, 0,204,161,232,231, 231, 68, 0, 0,231, 0, 72,231,
+231, 0, 72,233,231, 0, 0, 51, 0,231,231, 0,231, 50, 0,199,
+ 25,199,175, 0,170, 0,231,231, 0,231,204, 0, 0,231, 0, 0,
+
+// state[398 + 2] 0x009140 Byte 3 of 3 (property)
+ 0, 0, 0,220, 0, 0,206,231, 204, 68,189, 68, 97, 88, 24, 0,
+208, 0,140, 0, 15,221,231,130, 232, 0,118,231,231, 63,191, 0,
+ 0, 0,105,209,231,126, 0, 0, 0,197,136, 0,140, 0,118,118,
+ 72, 72,231,231,221, 88,118,140, 137,221,237, 0, 0, 0, 0, 72,
+
+// state[399 + 2] 0x009180 Byte 3 of 3 (property)
+ 0, 0,198,129, 0,231, 0,115, 0, 85, 0,118,220,106,231,231,
+106, 0,126,220,220,220,220,101, 0, 0,120,118, 87, 0, 75, 0,
+ 0,221,231,122,117, 0, 0, 0, 0, 0,199, 25, 75, 0,121,121,
+ 0,122, 0,231,100,232, 0, 0, 117, 0,206,231, 0, 0,231, 0,
+
+// state[400 + 2] 0x009000 Byte 2 of 3 (relative offsets)
+ -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+
+// state[401 + 2] 0x0091c0 Byte 3 of 3 (property)
+ 50,170, 0,231, 0, 0,194,120, 15, 88, 16,127,134,139,103,140,
+119,136,231,231,211,221,231,129, 143,221,231,231,103,107,231,231,
+ 0,231, 0,109,232,231,131, 13, 0,211, 0, 0,211,204,231, 0,
+ 0,221, 0, 0, 0,121,231,211, 0,210, 0,231,231, 0, 0,231,
+
+// state[402 + 2] 0x009200 Byte 3 of 3 (property)
+211,232, 0, 0,221,233,231, 0, 221,127,204, 0, 0,146,194, 0,
+204,143, 0, 0,119, 19, 0, 0, 0, 0, 0, 0,232, 0, 76, 0,
+ 0,231, 0,127, 0,128,127, 72, 0,191,220, 0,231,204,211,230,
+221, 0,231,221,107, 0,191, 99, 211, 72, 85,212,235,207,210,194,
+
+// state[403 + 2] 0x009240 Byte 3 of 3 (property)
+ 76, 0, 0,231, 44,127,204, 0, 194, 51,211,162, 0,211,232,231,
+231,204, 0, 0, 0, 0, 0,131, 0,231,204,107, 0, 0,204,231,
+ 0, 0, 44, 0,119, 0,194,231, 0, 0, 0, 0,211,211, 0, 0,
+ 0, 15, 0, 0, 16, 0, 0,231, 204, 0,221,129, 0, 0,116,232,
+
+// state[404 + 2] 0x009280 Byte 3 of 3 (property)
+107, 0, 0,109, 0, 87, 72, 72, 72, 98,128,211, 0,221, 0,133,
+ 0,131, 0, 75, 0,198,129, 0, 107,231,116,198,121,129, 0, 0,
+211, 0, 0,211, 0,211,204,232, 204, 0,221,221,211,117,212, 0,
+ 0, 0,129,127, 0,211, 0, 27, 0,121, 0,211,211,231, 0, 0,
+
+// state[405 + 2] 0x0092c0 Byte 3 of 3 (property)
+ 0,127,221,211, 0,122,207,211, 0,211, 0,232,204, 0, 0,109,
+204, 0, 91,231, 0,204, 0,231, 0,231, 0, 0, 0, 0, 0, 0,
+231,231, 0, 0,131, 0, 0,231, 221,231,119,231,231,117,232,211,
+127, 0,116,116, 0, 0, 0, 0, 143,231,198,231, 87, 0, 0,231,
+
+// state[406 + 2] 0x009300 Byte 3 of 3 (property)
+221, 0,232, 0,127, 0,116, 0, 0, 0,231, 0, 0,231, 0,204,
+107, 0,221, 0, 0,204, 0, 0, 143,204,129,221, 0,231,204,221,
+146,121,127,231,211,231, 91,231, 131,211,221, 51, 44, 0,194,131,
+ 0, 0, 15,127, 0,231, 23, 0, 127, 0,203,231,221, 0, 0, 0,
+
+// state[407 + 2] 0x009340 Byte 3 of 3 (property)
+ 0, 0, 0, 0,231, 0, 0,204, 231,231,118, 88, 72,119,128, 72,
+128,207,207, 72, 99, 72,191, 72, 72,128,120,137,191,128,207, 72,
+ 72,128,207,207,120,124, 72,207, 133, 72,231, 72,109, 0,116, 0,
+ 25,221, 0, 0, 0,131,211, 0, 0, 0,127, 0,109, 0, 51,231,
+
+// state[408 + 2] 0x009380 Byte 3 of 3 (property)
+ 0, 0,122, 0, 0, 0, 0, 0, 0, 0,122, 0,116, 0, 0,206,
+ 0, 0, 0, 0, 50, 0,107,194, 129, 0,131, 0, 0,221, 0, 0,
+ 0,221,127,232,231,221,221,146, 0,221,221, 0, 47,167,111,128,
+ 67,207,128,144, 72,208, 72,128, 128, 98,128,207, 98,199,124,233,
+
+// state[409 + 2] 0x0093c0 Byte 3 of 3 (property)
+128, 72, 72, 72,207,133, 72, 72, 130, 72,221,128, 16,124, 0, 0,
+232,116, 0, 0, 0, 0,199,129, 129, 0, 0, 0,204,194,204,129,
+ 0,143,129, 0, 76,231, 0,231, 198, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,204, 0, 0, 231,221, 0, 0, 0,127, 0, 0,
+
+// state[410 + 2] 0x009400 Byte 3 of 3 (property)
+ 0, 0, 0,232, 0, 0, 0,231, 0,231, 0, 0, 0, 0,128, 72,
+ 72,128,128, 72, 72,207,207, 72, 68,189,190,191,191,191,207,128,
+ 72,189, 72,128,212,207,207,199, 207,191,207,199,207,128,208, 0,
+ 0,231,204,129, 0, 76,204, 0, 119, 0,194, 0, 0,210, 0,211,
+
+// state[411 + 2] 0x009440 Byte 3 of 3 (property)
+ 0,231, 0, 0,121,231, 0, 0, 231, 0,232, 0,221, 0, 0, 0,
+ 0,107, 76,198, 0,231, 0, 0, 0, 0,194,198, 0, 0,231, 0,
+204, 0,231,204, 0, 0, 0, 0, 0, 0,232,141, 0,232, 0, 0,
+121,191,130,207,207,233,231,206, 191,191,191,207,111,119,121,119,
+
+// state[412 + 2] 0x009480 Byte 3 of 3 (property)
+220,231, 0,207,233,220,235,235, 16, 72,191, 0,220,191,226,220,
+231,204,191, 72, 0,191, 0,191, 212, 72,220, 72,207,191, 72,128,
+ 72,191, 16,191,220, 72, 72, 72, 63, 63,235, 0,231, 0, 72,191,
+ 72, 16,231, 72,191,179, 0, 0, 0,220,226, 98, 72,226, 72,212,
+
+// state[413 + 2] 0x0094c0 Byte 3 of 3 (property)
+191, 16, 72, 16,212, 72,191, 0, 235,212,191,191,191,191,191,207,
+191,235,235, 0, 0,233,212,235, 0,220, 0,191, 16, 16, 0,191,
+191,212,212, 63,191,231,204,212, 212,220,235, 0, 72, 72,191,220,
+191,233, 63,220, 0, 98, 11,220, 63,231, 98,212,233,231, 28,207,
+
+// state[414 + 2] 0x009500 Byte 3 of 3 (property)
+ 26, 72, 72,220,207, 98,191, 0, 67,191, 0,133, 72, 0, 0,212,
+ 98,191,220, 0, 98,128, 0,191, 0, 16,128, 72,220, 72,231,124,
+ 0, 16,207,128, 63, 72, 16, 0, 226, 0, 0, 0, 0, 72,133, 72,
+ 72, 0,212, 0,212,191,191,212, 0,191,220, 98, 0, 0, 0, 0,
+
+// state[415 + 2] 0x009540 Byte 3 of 3 (property)
+ 72, 72,207, 0, 0,231, 0, 28, 0,191,191, 0,179, 72,231,235,
+191, 72,220,191, 0,212,207,212, 0, 0, 0,212, 16,235,220,231,
+ 0, 0,234,191, 0,231,220,235, 235, 0, 0, 0,235,191, 0,207,
+199,191,231,191, 0, 0, 63,107, 231,220, 0, 0,211, 0, 0, 28,
+
+// state[416 + 2] 0x009580 Byte 3 of 3 (property)
+ 87, 0,204, 91, 0, 0,232,231, 231,146,231,107, 0, 0,204,164,
+ 0, 51, 19,146, 51, 0,203, 0, 119,232, 0, 0, 0, 0,221,231,
+231,211, 15,146, 91, 87,231,231, 76, 75, 0,199,209,171,206, 0,
+207, 23,105,128,231,128,232, 0, 98, 72, 98, 99, 67,199,189, 72,
+
+// state[417 + 2] 0x0095c0 Byte 3 of 3 (property)
+207, 98,133,199,191,233,121,105, 72,210,111,204,194,194, 0, 0,
+204, 0, 0,204,129,119,129,221, 117, 0,221, 0,122, 0,204, 0,
+ 0,119,121, 0, 0,238, 0, 0, 26,235, 16, 72, 0, 26, 28, 72,
+207,191,128,220, 10, 72,231, 72, 98, 16,207, 26, 0, 63,212, 0,
+
+// state[418 + 2] 0x009600 Byte 3 of 3 (property)
+ 72, 98,191, 0,220, 26,133, 0, 191,191,212, 0, 0, 0, 72, 0,
+ 72,191, 0, 0, 98,235,207,212, 0,191,212, 0, 53,231,231, 28,
+ 0,206, 0, 0, 0, 0, 0, 0, 204, 0,116, 0, 0,231, 50,232,
+ 0,118,140, 26, 16, 72, 16, 0, 0,221, 0,138,231, 0, 0, 68,
+
+// state[419 + 2] 0x009640 Byte 3 of 3 (property)
+ 68,231, 67, 0,126, 26,128, 72, 28,212, 0,115,118,140,221,231,
+138,210, 0,210,206,133, 0, 0, 232, 0,231,103, 91,164,119,167,
+ 0,118,138, 87,136,117,203, 0, 212, 26,115, 0,204, 0, 0, 0,
+ 87, 0,209, 76,221, 68,136, 85, 87, 0, 15,231, 0, 51,231, 0,
+
+// state[420 + 2] 0x009680 Byte 3 of 3 (property)
+ 0, 0,231,231,204, 53, 68, 0, 105,221, 87, 68, 0,130,107,142,
+128, 0, 0, 0, 97,109, 0,197, 115, 88, 0,107,140,231,211,231,
+116,211,221, 44,204,231, 0,126, 121, 0, 75, 0, 0, 0, 0,231,
+233, 25,203, 0,164, 0, 63, 44, 121,204, 0,131,103,191, 10, 0,
+
+// state[421 + 2] 0x0096c0 Byte 3 of 3 (property)
+136,140, 0,221, 97,138,137, 88, 0,111, 0, 76,136, 68,233, 98,
+ 0, 15,199,127,231,144, 25, 0, 0,121,231,109,127,231, 25, 0,
+ 0, 0, 87, 87, 0, 0, 0,221, 137,206,137,109, 0, 0,231, 89,
+ 13, 0, 91,207, 0, 0,126,140, 231, 99, 0,143, 0, 0, 98,211,
+
+// state[422 + 2] 0x009700 Byte 3 of 3 (property)
+115,207, 0, 0,126,174,115,137, 121,141, 44, 0, 0,118,201,130,
+ 0,197, 0,115, 0, 0, 68, 0, 231,194, 0, 0,138, 0,140, 0,
+ 0, 0, 0,231,231, 0, 0,143, 0, 0,232, 0, 0,191, 0, 0,
+197, 0, 97,231, 0, 0, 0, 0, 144,111, 0,231, 0,204,209, 0,
+
+// state[423 + 2] 0x009740 Byte 3 of 3 (property)
+ 0, 0,119,231,194, 0,232, 0, 75,232,231, 0, 0,231,231,231,
+ 0,161,137, 72, 0,231, 68, 0, 232, 53,121,120,127, 0,136, 0,
+118,137,140, 0,231,233,175, 0, 204,140, 0,203, 0,116, 0, 0,
+ 0,116, 0,120,103, 0,118,231, 0,231,161, 0,197,211, 0, 0,
+
+// state[424 + 2] 0x009780 Byte 3 of 3 (property)
+ 0,231, 0, 0,116,206,203,231, 127, 0,211,130,231, 74,211, 50,
+231,235, 0, 0, 0,231, 0, 0, 88, 0,231, 0,231, 0,232, 0,
+ 68, 0, 0,189, 0,231,204, 0, 161, 0, 0,231,231,140,231,220,
+ 0,231, 0,231,231, 0, 0, 0, 0, 0, 0, 0, 0, 0,231, 0,
+
+// state[425 + 2] 0x0097c0 Byte 3 of 3 (property)
+ 0,204, 0,194, 0, 0,204, 0, 231, 0, 0, 76,127, 0, 0,221,
+ 0, 0,231, 91, 0, 0, 0,231, 0,232, 0, 0,194, 0,211,231,
+ 0,204, 0, 0, 0, 0, 72, 72, 231, 16,210,207,191,118,101, 0,
+ 0, 0,231, 88, 0, 72,118, 0, 0, 0,231, 51, 0,231,221,143,
+
+// state[426 + 2] 0x009800 Byte 3 of 3 (property)
+231,119,146,109, 0, 87,146,221, 143, 0,161, 0, 91,231,161,232,
+131,109,119, 91, 0, 0, 0,119, 87, 0,117,211,211,204,232, 0,
+211,121, 0,231,119,231,127, 0, 221,211, 0,206,116,107, 0, 0,
+121, 0,221,231,198,231, 0,109, 87,121,231,143, 44,198, 0,231,
+
+// state[427 + 2] 0x009840 Byte 3 of 3 (property)
+231, 0, 0, 0, 0,231,131, 0, 0, 0, 0,231,143,146, 88,121,
+ 72,128, 72, 67, 53, 92, 98,204, 146,232,237,119, 0,221,143, 0,
+ 0,221,128,191,220,120,128,143, 128,207,207,115,199,128, 0,127,
+194,121, 0,204,204, 26, 16, 98, 0, 26, 28, 10,226, 72, 16, 16,
+
+// state[428 + 2] 0x009880 Byte 3 of 3 (property)
+220,133, 72, 0, 26,128, 28, 72, 16,207,207, 0,207, 98, 0,235,
+ 72, 28, 0,179,220, 0, 72, 16, 28,231,207,179,128, 26,220, 0,
+ 72,212,212, 0, 72, 0,220,207, 87, 0,203, 0, 0,231,221,109,
+ 0,121,211,211, 0, 0,204, 0, 0, 0,174,221,204, 0, 0, 0,
+
+// state[429 + 2] 0x0098c0 Byte 3 of 3 (property)
+ 0, 0, 0,231, 76, 0, 19,231, 231, 0,231, 0, 0, 0, 10, 0,
+ 0, 0,212,212, 0,220, 0, 0, 98,128,207, 87,198, 0, 16,137,
+231,231, 51, 0,231,232, 0,221, 207,194,129,198, 0,194,231,107,
+ 0, 0,107, 0,109,232,232, 0, 231,221,221, 0,146,146,143, 0,
+
+// state[430 + 2] 0x009900 Byte 3 of 3 (property)
+231, 0, 0,146, 0,143, 0, 0, 211,194,143, 0,109, 0, 0, 0,
+118,221,204, 87,232,231, 0, 0, 76, 0,121,204, 0,231,119, 0,
+161,143, 0, 0,231, 0, 0,231, 143, 0, 0, 0,232, 0,199, 0,
+ 0, 0, 0, 0, 0,129, 0, 0, 0, 0, 0,231,232,129,129,204,
+
+// state[431 + 2] 0x009940 Byte 3 of 3 (property)
+ 0,231,203,204, 0,146, 0, 0, 211,198, 0, 25,129,231, 0, 0,
+231, 76, 93, 0, 0,121,231,131, 231,231,231,231,204,231,204,231,
+231, 0,231,231,231, 63, 0, 0, 191, 0, 98,231,207, 16, 16,191,
+ 16, 72,133, 0,220,128, 98,191, 0, 0, 72, 0, 98,212, 0, 72,
+
+// state[432 + 2] 0x009980 Byte 3 of 3 (property)
+226,191, 0, 0,191, 98, 16, 0, 133, 0,220,191, 0,179, 0,191,
+235, 0, 72, 0,220,235,126,201, 232,136,231, 0, 0, 0,231, 0,
+ 0, 0, 0, 0, 0, 68, 0, 0, 68, 0, 0, 0, 87,129, 51, 0,
+ 0,206, 0, 87,109,231, 0, 0, 0, 0, 0, 0,191, 0, 0, 0,
+
+// state[433 + 2] 0x0099c0 Byte 3 of 3 (property)
+ 0, 76, 0, 0,117, 44, 15, 0, 203,231, 0, 0, 0, 0, 0, 0,
+146,232,109, 0,232, 76, 0, 0, 232,204,231, 25, 0,119, 0,161,
+ 0,221,175, 0, 0,231,231,231, 0, 0, 0, 0, 0, 78,231, 0,
+231,119,231, 0, 0, 0, 0, 0, 198,231, 0,231, 0, 0, 0, 91,
+
+// state[434 + 2] 0x009a00 Byte 3 of 3 (property)
+ 0,204,221,231, 0,232, 0, 0, 0, 0, 0,231, 0, 0, 87,204,
+ 0,231, 44, 15, 0, 0,204, 0, 0,143, 0, 0, 0, 0, 0, 0,
+231, 0, 0,231, 0, 0, 0, 0, 116, 0, 0,204, 0,232,232, 0,
+ 87, 0, 0,231, 0,221,161,127, 231, 0, 0,231, 0, 0,194, 0,
+
+// state[435 + 2] 0x009a40 Byte 3 of 3 (property)
+194,238,232,204,204, 75, 0, 0, 0, 0,129,231,221,194,232, 0,
+ 0, 0,204, 0, 0, 91, 0, 25, 0, 0, 87, 51, 0, 0, 0, 17,
+ 0, 0,129, 0,194,129, 0, 0, 0,232, 47,231, 10,128,212, 72,
+ 98, 63, 0, 72, 72, 0,133,212, 212,207, 0,133, 98,220, 16, 72,
+
+// state[436 + 2] 0x009a80 Byte 3 of 3 (property)
+231,191, 63, 0, 72,133, 72,191, 235, 0,212,207, 16, 0, 0, 72,
+212, 72, 0, 0, 0, 0, 0, 16, 0, 0, 72,212,220,220, 63,231,
+212,191,220, 0, 98,191, 0,212, 137, 0,228, 0, 0,231, 0,211,
+206,231, 0, 0, 0, 0,191,130, 137, 0,220, 0,118, 0, 0, 0,
+
+// state[437 + 2] 0x009ac0 Byte 3 of 3 (property)
+232,230,230,231, 44,212,231, 0, 0, 0, 0,207,220, 0,231,143,
+ 0,198,127,130,122,204,129, 0, 140,116, 0, 0,231, 0,231,231,
+231,206,203,198, 0, 0,111, 0, 0,231, 44,197, 0, 47,127,189,
+ 0,231, 0, 0,198, 0, 0,198, 0,204, 0,201, 0, 0, 0, 0,
+
+// state[438 + 2] 0x009b00 Byte 3 of 3 (property)
+ 0, 0,231,206, 0, 0,129, 0, 221, 0, 0, 0, 0,129, 0, 0,
+237, 0, 0,207, 0, 0,231, 0, 194,231,131, 0, 0, 0, 0,199,
+ 0, 0,194,197,231, 25,231, 25, 194,198,204, 0, 0,231,194,190,
+ 0, 91,233, 0, 0, 0, 0,231, 231, 0, 0,206,136, 0, 0, 0,
+
+// state[439 + 2] 0x009b40 Byte 3 of 3 (property)
+ 0,136, 88,197,126,103, 0,212, 221,235, 0,231, 0,197,194, 68,
+ 0,197, 0, 0, 88, 0, 0, 0, 204, 0,107,221, 0,231,231,211,
+211, 0, 0, 0, 0, 0, 0, 0, 211,221, 0, 0, 0, 0,231, 76,
+ 0, 0,231,231,232,231, 0,129, 0,231, 0, 0, 0,221, 0, 0,
+
+// state[440 + 2] 0x009b80 Byte 3 of 3 (property)
+231, 0, 0,231, 0, 0,231, 0, 0, 0, 0,221, 0, 0,116,231,
+232,119,198,198,221, 0,231,231, 0,231, 0, 0, 0,231, 0,198,
+231, 0, 0, 0,231, 0,231,231, 116,231,143,109, 0,146, 91, 0,
+191,231, 0, 0,231, 0, 0, 0, 232,231, 0,231, 0,231, 0, 0,
+
+// state[441 + 2] 0x009bc0 Byte 3 of 3 (property)
+232,232, 0, 0,231, 0,231,232, 231, 87,121, 0, 0, 0,231,231,
+231,231,231,211,232, 0, 13,232, 0,221, 0,146,231,221, 0, 0,
+ 0,232,232,231, 76, 0, 0,211, 87, 0,221,231,231, 0, 0, 0,
+162,198,231, 0, 0,203, 0,204, 231, 0, 0, 0, 0,204, 0, 0,
+
+// state[442 + 2] 0x009c00 Byte 3 of 3 (property)
+231, 0,231, 0,231, 0,198, 0, 194,231,203,231,203,194, 0, 0,
+116,231,231,194,231,204, 0, 0, 0, 0, 0,231, 0, 0, 0, 0,
+ 0,232, 0, 0,198,232, 0,231, 221,232, 0, 0,231,143,232,203,
+231,204,129, 0, 0, 0,231, 0, 0,109,194,143, 0, 0,204, 0,
+
+// state[443 + 2] 0x009c40 Byte 3 of 3 (property)
+ 0, 0,211, 0, 0, 0,231,198, 109,204,231, 0, 0, 0,231,231,
+ 0, 0,109,231,204, 0,221, 51, 204, 0,198, 0, 0, 0, 0,211,
+232, 0, 0,231, 0, 0, 0,194, 231,231, 0,231, 0, 0, 0, 0,
+ 0,231,232, 0,231, 0,231,121, 129,231,211,204,128, 0, 0,207,
+
+// state[444 + 2] 0x009c80 Byte 3 of 3 (property)
+235,128,212,220, 0,212,212,226, 191, 0, 0, 0, 0, 72,220, 0,
+231,212, 0, 0, 0, 0, 0, 0, 0, 0,220,220, 16, 0,179,212,
+231,220,133, 0, 72,235, 0, 0, 72,220, 0, 72, 0,220,220, 0,
+ 0,235,207,207,231,191,207,212, 128, 0, 0,231,231,220, 0, 0,
+
+// state[445 + 2] 0x009cc0 Byte 3 of 3 (property)
+ 0,231, 0,191, 63,207, 0, 0, 0, 0,220, 0, 63,207,235,220,
+220, 0, 0, 0,220,191,207, 72, 0,212, 0, 0,212,133,128,191,
+ 0,231,220, 0, 0,146, 0,204, 0,109,221,231,231, 0, 0,211,
+231, 0, 0, 51,146, 0,143, 0, 0, 0,221, 0,231, 0, 0, 0,
+
+// state[446 + 2] 0x009d00 Byte 3 of 3 (property)
+ 0, 0, 0,232, 0, 0,238,194, 198, 91, 0,231, 0, 0, 44, 0,
+ 0, 0,194, 0, 0,206, 0,231, 0, 0, 0,119,231,211,211,204,
+ 0, 0,231,206,231, 0,119,231, 51,231,231,203,203, 0, 0,232,
+ 0, 0, 0, 0,211, 0, 0, 0, 0, 0, 0, 91, 0,231,231,121,
+
+// state[447 + 2] 0x009d40 Byte 3 of 3 (property)
+ 0,231,232, 0,203, 0,231, 0, 231,231, 0, 0, 0, 0, 0, 0,
+231,111,221, 0, 0, 0,231, 0, 0,231, 0, 0,109, 25,198,204,
+106,131, 0, 0,231, 0, 0, 0, 0, 0,204,231, 91, 0, 0,194,
+121, 0, 76, 0, 0, 0,231,232, 231, 0,203, 0,231, 0,231, 0,
+
+// state[448 + 2] 0x009d80 Byte 3 of 3 (property)
+231,231,231,231,231, 0, 0,204, 0,194,231,231,231,231, 0, 15,
+231,231,231,232,231,231,211,231, 232, 0,204, 0,231,231, 0, 0,
+ 0,232,231, 0,231, 0, 0, 0, 0,204, 0,231, 0, 0, 0, 91,
+ 0,231,204, 0,109, 0, 0, 0, 231,221,204,204,211, 0, 0,204,
+
+// state[449 + 2] 0x009dc0 Byte 3 of 3 (property)
+221,231,204, 0,161, 0,231, 0, 0,231, 0, 0, 0, 0, 0,231,
+ 0, 0, 0,204, 0, 0,232, 76, 0,232,211, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,204,204, 0, 0, 0, 0, 0,231,231, 0,232,
+ 0,231,146,221,211, 0, 0, 0, 129, 87,107, 0, 0,231, 0,211,
+
+// state[450 + 2] 0x009e00 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0,231, 0, 0, 0, 0,231, 0, 0,231, 0,
+ 0, 0, 0, 0, 0,204,231, 0, 0,231,129,204, 0,204, 51, 72,
+191,133,207, 98, 0,191, 72,231, 235,220,212, 0,220, 98, 0,191,
+220, 0, 0,191, 0,212, 0, 0, 220, 0, 0, 0, 0, 72,191, 16,
+
+// state[451 + 2] 0x009e40 Byte 3 of 3 (property)
+ 0, 0,212, 72,191, 63,231,234, 0,207,207,220,191, 0, 0, 98,
+ 0,207, 0,231, 0, 0,231,235, 220, 0,179, 0,212, 0,220, 0,
+ 0, 0, 0, 0, 98, 0,207,212, 0, 0, 0,191,220, 72,220, 0,
+ 72, 0, 0,207, 0, 76, 0, 0, 117,121, 0, 0, 76, 75, 0, 88,
+
+// state[452 + 2] 0x009e80 Byte 3 of 3 (property)
+231,203,121,231,231, 0, 0,232, 204, 0, 0,206,231, 0, 0, 0,
+ 0,231,138,137, 0, 0,231, 51, 231, 0, 0,231, 0,111, 0, 68,
+ 0, 0, 0, 0,232, 75, 71,221, 231,109,231,231,231,231, 0, 0,
+ 0, 0, 0, 0,121,127, 0, 0, 98,117,117,137, 25, 96,189,116,
+
+// state[453 + 2] 0x009ec0 Byte 3 of 3 (property)
+ 0, 0, 0, 75, 65, 0, 0, 0, 0,220, 0, 0,198,201,126, 89,
+231, 82,117, 0, 72, 0, 0, 0, 85,117, 0,111,206,206, 27, 72,
+206, 0, 0, 0, 0,204, 0, 0, 77, 0, 0, 0, 0, 0, 0,130,
+ 0, 0, 0, 0,129, 0,231,204, 231,231,221,232,232,238,220,232,
+
+// state[454 + 2] 0x009f00 Byte 3 of 3 (property)
+ 0, 0,231,231, 0, 0, 0,232, 198,232, 0,220, 0, 0, 68,221,
+206, 0, 0,134, 0,232,231,231, 0,231, 0, 0,231, 0, 0, 0,
+126,231,231, 0, 0,231,231, 0, 0,221, 0,232,189, 0, 0,204,
+ 0, 0, 0, 0,211, 0, 0,232, 0,220, 0, 88, 0, 0,206, 0,
+
+// state[455 + 2] 0x009f40 Byte 3 of 3 (property)
+ 0,211, 0, 0, 0, 0, 0, 0, 0, 0, 76, 51, 0, 0,198,231,
+133, 0, 75, 0,231,232, 0, 0, 0, 0, 0, 0,221, 0, 0,194,
+231,122, 15,121, 0, 0,129,198, 0, 0,204, 0,194, 0,231, 0,
+ 0, 0,204, 0, 0, 0,232,232, 0, 0, 0, 0, 0, 0, 0, 72,
+
+// state[456 + 2] 0x009f80 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 16,233, 0,220, 207, 0,212,207,212, 51, 0, 0,
+ 75,232,231, 0,121,204,231,231, 231, 11, 72,212, 50,231,231,120,
+237, 0,204, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0,
+
+// state[457 + 2] 0x009fc0 Byte 3 of 3 (property)
+ 0, 0, 0,229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[458 + 2] 0x000080 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[459 + 2] 0x001100 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[460 + 2] 0x001100 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[461 + 2] 0x001100 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[462 + 2] 0x00d780 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[463 + 2] 0x000080 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[464 + 2] 0x00a000 Byte 2 of 3 (relative offsets)
+ -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6,
+ -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6,
+ -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6,
+ -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5,
+
+// state[465 + 2] 0x000080 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[466 + 2] 0x00f900 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3,217, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,215, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 215,215, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3,215, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[467 + 2] 0x00f940 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+215, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[468 + 2] 0x001100 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[469 + 2] 0x00f9c0 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+215, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,215, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[470 + 2] 0x00fa00 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,238,194,231,231,
+215,116,215,231,231,215,215,215, 215,215,215,215,215,215,215,231,
+ 4,231,215,231,231,215,215,231, 231,231,215,215,215,215, 0, 0,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+
+// state[471 + 2] 0x00fa40 Byte 3 of 3 (property)
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[472 + 2] 0x00fac0 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[473 + 2] 0x00ff40 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+
+// state[474 + 2] 0x00ff80 Byte 3 of 3 (property)
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0,
+
+// state[475 + 2] 0x00ffc0 Byte 3 of 3 (property)
+ 0, 0, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3,
+ 0, 0, 3, 3, 3, 3, 3, 3, 0, 0, 3, 3, 3, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[476 + 2] 0x020000 Byte 4 of 4 (property)
+208, 6, 5,208, 5,208, 3,208, 208,217, 6,228, 6, 6, 6, 5,
+ 5, 6,208, 6,208, 6, 2, 2, 208,208,208, 6, 6, 6, 6, 6,
+208,208,208, 5, 6,208, 6, 2, 2,208, 2, 2,208,208,208, 6,
+ 6, 6, 2, 2, 2, 5, 5, 6, 6, 6, 6,208, 6, 6, 5, 2,
+
+// state[477 + 2] 0x020040 Byte 4 of 4 (property)
+ 2,229, 2,208, 2, 6, 5, 5, 208, 6, 6, 5, 5, 5, 5, 6,
+ 6, 2, 5, 5, 2,229, 5,208, 6, 2, 2, 5, 6, 2, 6, 2,
+ 6,208, 5, 6,208,217,208,208, 5,208, 2, 5, 6,229,208, 5,
+208,208,208, 6,208, 2, 5,208, 2, 2, 2, 2, 6,208,208,208,
+
+// state[478 + 2] 0x020080 Byte 4 of 4 (property)
+ 6, 6,208, 5,208, 5,208,208, 208,172,217, 6, 5,208, 2,208,
+ 6, 6, 5, 5,208,208, 6, 5, 5, 5, 5, 5,208, 6,208, 6,
+208, 6,228,229,228,208, 6, 6, 208,208, 2, 2,208, 6, 5,229,
+ 5,208,208,208, 5,208,208,208, 6,229,208, 6,208,208,208,208,
+
+// state[479 + 2] 0x0200c0 Byte 4 of 4 (property)
+ 6, 6,208, 5,208, 2, 5, 6, 6,229,208,208,208,217, 3,208,
+208, 5, 5, 6, 5,208,208, 3, 5, 5, 6, 6, 6, 2,208,208,
+208,208, 6, 2,208,229, 5,208, 6, 2, 5, 6, 6,208, 5, 2,
+208,208,208,208, 5, 6,208, 2, 0, 6, 6, 5,208,208,208, 6,
+
+// state[480 + 2] 0x00b000 Byte 2 of 3 (relative offsets)
+-20,-20,-20,-20,-20,-20,-20,-20, -20,-20,-20,-20,-20,-20,-20,-20,
+-20,-20,-20,-20,-20,-20,-20,-20, -20,-20,-20,-20,-20,-20,-20,-20,
+-20,-20,-20,-20,-20,-20,-20,-20, -20,-20,-20,-20,-20,-20,-20,-20,
+-20,-20,-20,-20,-20,-20,-20,-20, -20,-20,-20,-20,-20,-20,-20,-20,
+
+// state[481 + 2] 0x020100 Byte 4 of 4 (property)
+ 2, 6, 5, 6, 5,229, 5,208, 208,208,208,208,208,208,208,229,
+ 3,208,208,208,208,208,208,208, 5,208,208,208, 2,208,208,208,
+ 5,208,208,208,208, 6, 2, 2, 208, 2, 5, 3,217, 5,208, 2,
+208,208, 5, 2,208,208, 2, 5, 5,208, 2, 2, 2, 2, 6, 6,
+
+// state[482 + 2] 0x020140 Byte 4 of 4 (property)
+ 5, 6, 6,208, 5, 5, 6, 5, 208, 5, 5, 5,208, 2, 2, 5,
+ 2, 5, 2,208,208, 5,208,208, 229,229, 6, 6, 2, 5,208,208,
+208,208, 5, 5, 5, 5, 6, 5, 6,208,208,208,208, 5, 5, 5,
+208, 6, 6, 2, 5, 5, 5, 6, 6, 6, 6, 2, 2, 6, 5, 6,
+
+// state[483 + 2] 0x020180 Byte 4 of 4 (property)
+ 5,208,208, 5, 5, 5,208,208, 6,208,208, 6,208, 2,208, 5,
+ 6, 6,208, 6, 6, 6, 6, 6, 6, 6, 6, 6,208,208, 6, 6,
+ 6, 6, 4,208,208, 5,208, 5, 208,229, 6, 5, 5,208, 6,208,
+208, 6,208, 2,208,208,229,208, 208,208,208,208,208, 6, 6, 6,
+
+// state[484 + 2] 0x0201c0 Byte 4 of 4 (property)
+ 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 6,218, 6,208,
+208, 6, 6, 6, 5, 2, 2,219, 229,208,208,208,208,208, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6,
+ 6,229,208, 6,208, 6, 6,208, 6, 6, 6, 3, 2, 2,208,208,
+
+// state[485 + 2] 0x020200 Byte 4 of 4 (property)
+208,229, 3,229,208,208,208,208, 208,229,229,227,208,208,208,208,
+208,208, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,208, 6, 6, 6, 6,208, 208,208,208, 6,229,229,229,208,
+229,229,208,208,208,229,229,229, 208,208,229,208,208,208,208,208,
+
+// state[486 + 2] 0x020240 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,208, 6,208,229,208, 6, 208, 6, 6, 0, 2, 2, 2, 2,
+ 2,208,208,229,229,229,208,229, 208,229,229,208,229,208,208, 6,
+ 6,216, 5,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[487 + 2] 0x020280 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208,208, 6, 6, 6,
+ 6,208,208, 6,208, 6,208, 6, 6,216, 2, 2, 2, 2, 5, 2,
+ 5, 2,208,208,208,229,229,229, 208,229,229, 0,229,229,208, 6,
+208,208,229,229,208,229,229,208, 208,208,208,208, 6, 6,208, 5,
+
+// state[488 + 2] 0x0202c0 Byte 4 of 4 (property)
+ 5, 5,217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5,208,208,208,208,208,208, 6, 6, 6, 6,208, 6,208, 6, 6,
+ 6,208,229,208, 6,216, 2,208, 229,229,208,208,208,208,208,208,
+208,208,229,229,208,208,229,229, 229,208, 0,229,208,208,208,208,
+
+// state[489 + 2] 0x020300 Byte 4 of 4 (property)
+208, 6, 6, 6, 6, 6, 6, 5, 208, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 6, 6,208, 208, 6, 6, 6,208, 6, 6, 6,
+208,208, 6, 6, 6, 5, 2, 2, 2,208,229,233,208,208,229,208,
+229,208,229,208,208,208,208,208, 219,208,208,208,229, 6,208,208,
+
+// state[490 + 2] 0x020340 Byte 4 of 4 (property)
+208, 5, 6, 6,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 229, 6, 6, 6, 6, 6,208,208,
+ 6, 6,208, 2, 2, 2, 2,208, 208,229,208,208,208, 2,208,208,
+208,233,208,208,229, 0,208,208, 208,208,208,208, 6, 6, 5, 5,
+
+// state[491 + 2] 0x020380 Byte 4 of 4 (property)
+ 5, 4, 5, 5, 5, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 208,208,208,208, 6,219,208, 6,
+208,208,208,208, 6, 6, 6, 5, 2, 2, 2, 2,216, 6,229,208,
+208,229,208,208, 2, 5,208,229, 229,208,208,208,208,208,229,229,
+
+// state[492 + 2] 0x0203c0 Byte 4 of 4 (property)
+229,208,208,208,208, 6, 6,208, 208, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 6,208, 6,208,208, 6, 6,208, 6, 6, 3, 6, 2,
+ 2,229,208,208,208, 2, 2,208, 229,208,229,208,208,227,229,208,
+229, 6, 2, 2, 5, 5, 5, 5, 5, 4, 5, 5, 5, 6, 6, 6,
+
+// state[493 + 2] 0x020400 Byte 4 of 4 (property)
+208, 6, 6, 6, 6, 2,208, 6, 229,208,229,229,229,229,208, 6,
+ 6, 6, 5, 5, 5, 5, 5, 5, 5, 6,229,208, 6, 6, 2, 5,
+ 6,208,208,208,229,208,208,229, 208, 6, 5, 5, 2, 3, 5, 5,
+ 5,208,208,208, 6,208,208, 6, 2, 6,208,229,208,208,208,208,
+
+// state[494 + 2] 0x020440 Byte 4 of 4 (property)
+ 5, 5,208, 6,229,208, 6, 6, 2, 2, 4, 2, 5,208,208, 5,
+ 6,208,208,208, 6, 2, 2,208, 229,208,208,208, 5, 5, 6,229,
+ 6, 6, 2, 6,208, 5, 5, 5, 5,229,229,208, 2, 5, 5,208,
+ 6, 6,229,208, 6, 6,208,208, 6,229,208,208, 6,208,208,208,
+
+// state[495 + 2] 0x020480 Byte 4 of 4 (property)
+208,208, 6,208,208,208,208, 5, 5, 6,208,208,208,208,208, 6,
+ 5, 5, 5, 5, 5, 5,208, 5, 5, 5, 5, 5, 6, 6, 5, 5,
+ 5, 5,208, 5, 5, 2, 2, 6, 208, 5, 5, 5, 2, 5, 6, 2,
+208,208, 5, 5, 5,208,208, 2, 229, 5, 5, 5, 5, 5, 6,208,
+
+// state[496 + 2] 0x00d000 Byte 2 of 3 (relative offsets)
+-35,-35,-35,-35,-35,-35,-35,-35, -35,-35,-35,-35,-35,-35,-35,-35,
+-35,-35,-35,-35,-35,-35,-35,-35, -35,-35,-35,-35,-35,-35,-34,-33,
+-33,-33,-33,-33,-33,-33,-33,-33, -33,-33,-33,-33,-33,-33,-33,-33,
+-33,-33,-33,-33,-33,-33,-33,-33, -33,-33,-33,-33,-33,-33,-33,-33,
+
+// state[497 + 2] 0x0204c0 Byte 4 of 4 (property)
+ 2, 2, 5, 5, 5, 2,208, 2, 5, 5, 6,208,208, 5, 5, 5,
+ 2, 2, 5, 5, 2, 5, 6,208, 5, 6, 6,208,208,208, 6,208,
+208,208, 6, 6,208,208, 6,208, 2, 2,229,208, 2,208,208, 5,
+ 5,208, 6, 5,208, 6,208,208, 208, 2, 2, 2, 5,208, 5, 6,
+
+// state[498 + 2] 0x020500 Byte 4 of 4 (property)
+208,208,208,208, 5, 6, 5,208, 208,194,208,208,208, 6,208,208,
+208, 6,208,208, 6,208,208,208, 208,208,208,208,208, 5, 6,208,
+208, 5, 5, 6, 6,229, 2,208, 208, 5, 5, 5, 6, 2, 2,208,
+ 6,208, 6, 5, 5, 5,208, 5, 208,229, 6,231,208,208,208, 6,
+
+// state[499 + 2] 0x020540 Byte 4 of 4 (property)
+ 6,208,229,208,208, 6, 5, 5, 5, 6,208,229, 6,208,208, 6,
+208,208,208, 5,208, 6,208, 6, 208, 6, 5,208, 6,208,208,208,
+208, 6, 5,208,208,208,208,208, 208, 5, 6, 6, 5,208,208, 5,
+208, 5,208,208, 6,208, 5, 5, 6,208,208,208, 6, 5, 6, 6,
+
+// state[500 + 2] 0x020580 Byte 4 of 4 (property)
+ 5, 5, 6, 6,208, 5, 6, 6, 2, 6,208, 6,208, 5, 5, 5,
+ 5, 5, 5,208,208, 5, 5,208, 2, 5,208, 6,208, 2, 5,208,
+208, 5, 5,208, 2, 5,208,208, 2,208, 6,208, 6, 2, 5, 2,
+ 2,208, 5, 5, 5, 6,208,208, 208,208, 5, 5, 5, 5, 2, 2,
+
+// state[501 + 2] 0x0205c0 Byte 4 of 4 (property)
+ 5, 5,229,208,208,208,208, 5, 229,229,208,208,208,208,208, 6,
+ 5, 5, 5, 5, 5, 5, 4,208, 208,208,208,208,208,208, 5, 5,
+ 5, 5,208, 2, 2,208,208,208, 229, 6, 5, 5, 5, 5, 5, 5,
+ 2,208,229,229,208,208,229, 5, 2, 2,208,208,208,229,208,208,
+
+// state[502 + 2] 0x020600 Byte 4 of 4 (property)
+208, 6, 6, 2,208,208,208,208, 5, 5,208,208,208,208, 5, 6,
+ 6, 5,208, 5, 5,208,208,229, 208, 5, 5, 5, 5,208,208,208,
+ 5,208,208,208,208,208, 6,208, 4,208, 6, 5, 5, 5, 5,208,
+ 5,229,208,208, 6,208,208,208, 208, 6,208, 6,208,208,208, 5,
+
+// state[503 + 2] 0x020640 Byte 4 of 4 (property)
+ 5, 6, 6, 6, 6, 2,229,208, 208,208,229,208, 5, 5, 6,208,
+208,208,208, 5, 6, 6,208,208, 208,208,208, 5, 5, 5, 6,208,
+208, 5,208, 2, 6, 6, 6, 5, 5, 5, 5, 5, 6,208, 5, 5,
+ 5, 5, 5, 5, 6,208,208,208, 208,208,208,208, 6,208,208,208,
+
+// state[504 + 2] 0x020680 Byte 4 of 4 (property)
+208,208, 5, 6, 6, 6,208,208, 5, 6, 6, 5, 6,208,208,229,
+ 2,208,208, 6, 6, 6,208,208, 229,208, 5,208, 6,208, 5,208,
+ 6, 5, 2, 5, 6,208,208,208, 208,208,208, 5,208,208,208, 0,
+208,229,208,208,208, 6, 6,208, 208,229,208,208,208,208,208,208,
+
+// state[505 + 2] 0x0206c0 Byte 4 of 4 (property)
+208,208,208,208,208, 6, 6, 5, 5,208, 6, 6, 2,208,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208, 6, 6, 6, 6,
+208, 6, 6, 2, 2,208,208,208, 208,208,208,208,229,208,208,208,
+208,208,208, 6,208,208,208,208, 208,208, 6, 5, 5, 5, 5, 5,
+
+// state[506 + 2] 0x020700 Byte 4 of 4 (property)
+ 6, 6,208, 6, 6, 6,208,208, 6,208, 6, 6,208, 6, 5, 2,
+208,208,208,208,208,208,208,208, 208,229,208,208,208,208, 6,208,
+208, 5, 5, 5, 6,208,208, 6, 0, 6, 6, 6, 2, 2, 2, 2,
+ 5,229,208,208,229,208,229,208, 208,208, 0,208,208,208,229,208,
+
+// state[507 + 2] 0x020740 Byte 4 of 4 (property)
+208,208,208,208,208,208, 6, 2, 5, 5, 5, 5, 5, 5, 6, 4,
+ 6,208,208, 2, 2, 2, 2, 2, 208,208,208,208, 6,208,208,208,
+208,229,208,229,208,208, 6,208, 208,208,208, 2, 6, 5, 5, 5,
+ 6,208, 6, 6, 6, 6, 6, 6, 6, 5, 2, 2, 2,208, 5,208,
+
+// state[508 + 2] 0x020780 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+ 5, 5, 5, 5, 5, 6, 6, 6, 208, 6, 6,218,208,208, 6, 2,
+ 2, 2, 2, 6,208,208,229,208, 208,208,208,208,208,208,229,208,
+229,208, 5, 5, 6, 6, 6,208, 2, 2, 2,208,208,208,208,208,
+
+// state[509 + 2] 0x0207c0 Byte 4 of 4 (property)
+ 2, 5,208,229,208,229,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 208, 5, 5, 5,208, 6, 6,208,
+ 6, 6, 6, 6, 2, 2,208,208, 208,208,229,208,208,208,208,208,
+208,208, 2,208, 6,208, 6, 6, 2, 2,229,208,208,208,208,208,
+
+// state[510 + 2] 0x020800 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,233, 5, 2, 2, 5, 6,227,208,208,
+208, 2, 5, 5, 6, 6, 6,208, 6, 2, 2,208,229,208,208, 5,
+208,208,208,208, 6, 6,208,208, 6, 2,208,208, 5, 2,208,208,
+208,208,208,208,208, 6,208,208, 5,208, 4, 5, 5, 5, 5, 5,
+
+// state[511 + 2] 0x020840 Byte 4 of 4 (property)
+ 6, 5,208,208,208, 6, 5, 5, 5, 6, 6, 6,208,229, 2, 2,
+ 3,208,208,208,208, 5, 5, 5, 5, 5, 2, 2, 6, 6,208,208,
+208,208, 5, 6, 6, 6,208, 2, 5, 5, 5,208,208,208,208,208,
+208,208, 5, 5, 5, 5, 6, 6, 208,208, 2,208,208,208,208,208,
+
+// state[512 + 2] 0x00f000 Byte 2 of 3 (relative offsets)
+-47,-47,-47,-47,-47,-47,-47,-47, -47,-47,-47,-47,-47,-47,-47,-47,
+-47,-47,-47,-47,-47,-47,-47,-47, -47,-47,-47,-47,-47,-47,-47,-47,
+-47,-47,-47,-47,-46,-45,-44,-43, -42,-41,-44,-40,-47,-47,-47,-47,
+-47,-47,-47,-47,-47,-47,-47,-47, -47,-47,-47,-47,-47,-39,-38,-37,
+
+// state[513 + 2] 0x020880 Byte 4 of 4 (property)
+ 5, 5, 5, 6,208,208,208,208, 208,208, 5, 5, 6, 6,208,208,
+ 6, 2,208,208,208,229,208,229, 5,208, 6, 6, 6, 2, 2, 2,
+208,208,208,208,208,208,208,208, 5, 6, 6, 6, 2,208, 6,208,
+208,208,208,208,208, 6,208,208, 5,233,208,208,208, 6, 6,208,
+
+// state[514 + 2] 0x0208c0 Byte 4 of 4 (property)
+208,208, 2,208,208, 5,208,208, 208,208,208,208, 5,208,208,208,
+ 6,208,208, 5,208,208,208,208, 208,208,208, 2, 5, 5,208,208,
+208,208, 5,208,208,208,208,208, 208,208,208,208, 6, 6,229,208,
+229,208, 6,208,208,208, 2, 5, 208,208,208, 5, 5,208,208,208,
+
+// state[515 + 2] 0x020900 Byte 4 of 4 (property)
+208, 5, 6, 5,208,208, 2,208, 208,208,208,208, 2, 6,229,208,
+208,208,208,208, 5,208, 5,208, 208,208,208, 5, 5, 6,208,208,
+ 5, 5,208,208, 5, 5, 6, 2, 5, 2,208,208,208, 5,208,208,
+208,208, 6,208,208,208, 5,219, 208,208,208, 6,208, 6,208,208,
+
+// state[516 + 2] 0x020940 Byte 4 of 4 (property)
+208,208,208,208, 5, 5, 6, 6, 2,208,208,208,208, 6,208, 5,
+208,208,208,208, 5, 6,208, 0, 6,208,208,208,229,208,208, 6,
+ 3, 5,208,208, 2, 5,208, 6, 6,208,208,208,208, 5,208,208,
+208,208, 5, 5,208, 5,208, 5, 229, 5,208,208, 4,208,208, 6,
+
+// state[517 + 2] 0x020980 Byte 4 of 4 (property)
+208, 6,208,229,219,208,208,208, 208, 6, 6, 6,229,208, 6, 6,
+ 6,208,208,208, 5, 5, 5, 6, 208,208,208,208,208,194, 5, 5,
+ 5, 5, 5,208, 5, 5, 5, 6, 6, 2,208,208,208, 5, 6, 2,
+ 5, 5, 6, 2,208,208,208,208, 6, 2, 2, 2, 5, 5, 5, 2,
+
+// state[518 + 2] 0x0209c0 Byte 4 of 4 (property)
+208, 5, 5, 6,208, 6, 2, 6, 5, 6, 5,231, 2, 2,208,208,
+ 5,208, 6,208,208, 6, 6,208, 208,229,208,208,208, 6,208,208,
+208,208,208,208,208,208, 5,229, 208,208,229,208, 5, 5, 5, 5,
+ 6, 6,208,208,208,208,208,208, 208, 5,208,208,208,208, 6, 6,
+
+// state[519 + 2] 0x020a00 Byte 4 of 4 (property)
+208,208,208,208, 6,208,208,208, 208, 5, 6,208, 6,208,208, 5,
+ 5, 5,208,208,208, 6, 5, 6, 208,208,229,208, 0, 5, 5,208,
+208, 2,229,208, 6,208, 6,208, 6, 6, 2, 6,208,208,208,208,
+208,208,208, 6,208,208,208,208, 208,208,208,208, 6, 5, 6, 6,
+
+// state[520 + 2] 0x020a40 Byte 4 of 4 (property)
+208,208,229,208, 5,219, 6, 5, 6, 6,208,208,208,208,208,208,
+ 5, 5, 5, 6, 6, 2,208,208, 208,208,208,208,208,208,208,208,
+208, 6, 5, 5,208, 6,208,208, 208,208,208,208,208,208,208,208,
+208,208, 5, 5, 5, 6, 6, 6, 208,208,208,208,208,208, 5,208,
+
+// state[521 + 2] 0x020a80 Byte 4 of 4 (property)
+ 6, 6, 6, 6,208, 6,208,208, 208,208,208,208,208,208, 5,208,
+ 6,208,208,208,208, 5, 5, 6, 208,208,208,208,208,208,208,208,
+ 5, 6, 5, 6,208,208,208, 6, 6, 6,208,208, 6,208,208,208,
+208, 6, 6, 5, 5, 5, 6, 6, 2,208,208,208,208, 5, 5,208,
+
+// state[522 + 2] 0x020ac0 Byte 4 of 4 (property)
+ 5, 5, 5, 6, 6, 2, 2, 5, 208,208, 6, 6,208,208,208, 2,
+208, 6, 5,233,208,208, 6,208, 6,208, 5, 6, 6,219,208,208,
+208, 3, 5, 6,208,208,208, 5, 208,208,208,208,208, 6, 6, 6,
+208,208, 6,208, 5, 6, 6, 6, 6,208,208,208,208, 6, 2,208,
+
+// state[523 + 2] 0x020b00 Byte 4 of 4 (property)
+208,208,208, 2, 5, 5,208,208, 208, 2, 5, 6, 3,208,208, 5,
+208,208, 2,208, 6, 2, 2, 5, 208, 6,208,208,208,233,208,208,
+ 2,208,208,208, 6,208,208,208, 208,208,208,208,208,208,208, 5,
+ 6,208,208,208, 5, 5, 5, 5, 208,208,208,208,208, 5, 6,208,
+
+// state[524 + 2] 0x020b40 Byte 4 of 4 (property)
+208,208,208, 5, 5, 5, 6,208, 6,208,208,208,208,208,208,208,
+ 5, 5, 5, 5,208,208,208,208, 208,208,208,208,208,208, 5, 5,
+ 5,208, 6,208, 2,208,208,208, 208, 5, 5, 5, 5, 5, 5,208,
+208,208,208,208,208,208,208, 6, 208, 6, 6,208, 5, 6,208,208,
+
+// state[525 + 2] 0x020b80 Byte 4 of 4 (property)
+ 6, 5, 5,208,208, 5,208,208, 5,208,208,208, 6,208,208, 5,
+229,208, 5,217, 5,208,208,208, 208,208,208,208,208, 5,208,233,
+208,208,208,208,208, 5,208,208, 208,216,208,208,208,229,208,208,
+208,208,208,229,229,229, 6, 5, 5, 5, 5, 5, 6, 6,218,227,
+
+// state[526 + 2] 0x020bc0 Byte 4 of 4 (property)
+ 6,208,208, 6, 6, 2, 5, 2, 208,208,208,227,208,208,208,208,
+208,208,208,208,208,208,208,229, 208,208,208,208,208,208,208, 6,
+ 6, 5, 5, 5, 5, 5,216, 5, 5, 6,218,208,208, 6,233, 6,
+ 6, 6, 6,208, 6,208,208, 6, 6,218, 6,216, 2, 2, 2,216,
+
+// state[527 + 2] 0x020c00 Byte 4 of 4 (property)
+ 2, 2, 2, 2,208,208, 6, 6, 208,227,208,208,227,227,208,227,
+208,208,208,229,208,208,208,208, 208,227,208,208,208,208,208, 6,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,216, 5, 5,
+ 5, 6,208,218,208,208,208,218, 208, 6,227,229, 6, 6, 6,208,
+
+// state[528 + 2] 0x020000 Byte 3 of 4 (relative offsets)
+-52,-51,-50,-49,-47,-46,-45,-44, -43,-42,-41,-40,-39,-38,-37,-36,
+-35,-34,-33,-31,-30,-29,-28,-27, -26,-25,-24,-23,-22,-21,-20,-19,
+-18,-17,-15,-14,-13,-12,-11,-10, -9, -8, -7, -6, -5, -4, -3, -2,
+ -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+
+// state[529 + 2] 0x020c40 Byte 4 of 4 (property)
+227, 5, 5, 5, 2, 2, 2, 2, 213, 2, 2, 2, 2, 2,208, 6,
+208,208,208,208,229,208,208,208, 208,208,208,208,227,208,208,208,
+208,208,208,208, 0,208,208,208, 208,208, 6,208,208,208,208,208,
+208,208,208,208, 6, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[530 + 2] 0x020c80 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208,208, 0,227,
+208, 6, 6,227,218, 6,227,208, 6, 6, 6, 6,227, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2,208,208, 5, 6,208,227,208,
+208,208,208,208,208,227,208,208, 208,227,227,208,208,208,208,227,
+
+// state[531 + 2] 0x020cc0 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208, 5,
+218, 2, 2, 5, 5, 5, 5, 2, 2, 2, 2, 5, 5,216, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 216, 5, 5,208, 6,208,208,208,
+208,208, 6, 6,208, 6, 6,208, 208,227,227,208, 6, 6, 6,227,
+
+// state[532 + 2] 0x020d00 Byte 4 of 4 (property)
+ 6, 6, 6,208, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2,216, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6,
+ 2,208,208,208, 2, 2,208,208, 229,208,208,227,208,208,208,208,
+208,208,229,208,208,208,208,208, 229,208,208,208,227,208,208,208,
+
+// state[533 + 2] 0x020d40 Byte 4 of 4 (property)
+208,208,208,208, 6, 4, 5,231, 5, 5,208, 2, 5, 5, 5, 2,
+ 2, 5, 5, 5, 5, 5, 5, 5, 216, 5,216, 5, 5, 5, 5, 5,
+208,208,208, 6, 6,208, 6, 6, 218,218,208, 6, 6, 6, 6,208,
+208,227,208, 6,208,208, 6,208, 6,208, 6, 6,208,213, 5, 5,
+
+// state[534 + 2] 0x020d80 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2,216, 2, 2, 2, 2, 2,216, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2,208, 208,208,208,208,208,208, 5, 2,
+208,208,208,208,208,208, 6,208, 229, 6,229,208,208,208,208,208,
+
+// state[535 + 2] 0x020dc0 Byte 4 of 4 (property)
+229,208,208,208,208,208,208,227, 208,208,229,208,208,208,208,208,
+ 6,208,208,208,229,208,208,208, 208,208,208,208,208,208,208,208,
+ 6, 4, 6, 6, 2, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 208, 6, 6,208,208,208,208, 6,
+
+// state[536 + 2] 0x020e00 Byte 4 of 4 (property)
+208, 6,218, 6,227, 6, 6, 6, 3, 5, 5, 3,217,216, 5, 5,
+ 5, 5, 2, 2, 2, 2, 5, 2, 2, 2, 2, 2, 2, 5, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,208, 6,
+208,208,208,208, 6, 5,208,208, 208,208,227,208, 2, 2, 2,208,
+
+// state[537 + 2] 0x020e40 Byte 4 of 4 (property)
+229,208,208,208,208,208,208,208, 208,229,208,208,208,208,208,208,
+208,208,208,208,229,208,208, 0, 229,208,208, 0,208, 6,208,208,
+208,208,208,208,233,208,208,208, 208,208,208, 5, 6,194, 5, 2,
+ 2, 2, 2,216, 2, 5, 5, 5, 5, 5, 5, 5, 2, 2, 2, 5,
+
+// state[538 + 2] 0x020e80 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,
+ 6, 6, 6,208, 6,225,208, 6, 208,218,218, 6, 6,208, 6,218,
+218, 6,208, 6,208, 2, 2, 2, 2, 2, 0, 5, 5, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2,216, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[539 + 2] 0x020ec0 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,208,208,208,
+208, 5, 2, 2,229,208,208,208, 208,208,208,208,229,208,229,208,
+208,208,208,208, 0,227,208,208, 208,229, 0,208,208,208,208,208,
+208,208, 6, 6, 6, 2,208, 2, 5, 5, 5, 5, 2, 2, 5, 5,
+
+// state[540 + 2] 0x020f00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5,227,227, 6, 208, 6,208, 6,208,208,208, 6,
+ 6, 6,208,208, 6, 6,227, 6, 6,208,218, 6, 6, 5, 5, 2,
+ 5, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 2, 2, 2, 2,
+
+// state[541 + 2] 0x020f40 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,216, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,208, 2, 4,
+ 5, 6,208,208,208,208,208,208, 208,208,229,208,208,208,227,208,
+208,208,229,208,208,229,208, 6, 208,208,208,208,229,208,208,208,
+
+// state[542 + 2] 0x020f80 Byte 4 of 4 (property)
+227,208,229,208,208,208,208, 6, 208,208,208,208,208, 5, 6, 6,
+208, 6, 2, 2, 2, 2,217, 5, 5, 5, 5,216, 5, 5, 5, 5,
+ 5, 5, 6,218, 6,208,227,208, 6, 6,208, 6, 6, 5, 6,208,
+208, 6, 6, 6, 5, 5, 5, 2, 2, 2, 2, 2,216, 2, 2, 2,
+
+// state[543 + 2] 0x020fc0 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2,208,208,229, 0,208,208,208,208,208,208,229,
+208,208,227,208,208,229,208,208, 208,208,227,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 6, 6, 5, 5, 5, 5,216, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5,216,
+
+// state[544 + 2] 0x021000 Byte 4 of 4 (property)
+ 5, 5,216, 5, 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5,
+208, 6,208,208,227, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 6,208,208, 5, 5,208, 6,227,
+
+// state[545 + 2] 0x021040 Byte 4 of 4 (property)
+ 6,208,208,208,208,208,208,208, 208,208, 6,208,208,208,227,208,
+208,208,208,208, 6,229,208, 5, 6, 2, 2, 2,216, 2, 2, 5,
+ 5, 5, 5, 5,216, 5, 6, 6, 208,208, 6,208,208,208, 6, 5,
+ 6, 6, 6, 6, 6, 5, 0, 5, 5, 2, 2, 5, 2, 2, 2, 2,
+
+// state[546 + 2] 0x021080 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 216, 2,208,208, 2, 2, 2,229,
+208,227,227,227,208,208,208,208, 208,227,208,208,208,208,208,229,
+208,208,208,208,208,208,208,208, 208,208, 6, 6, 5, 6, 2, 2,
+ 2, 2, 2, 2, 5, 2, 5,216, 5,216, 5, 5, 5, 5, 5,208,
+
+// state[547 + 2] 0x0210c0 Byte 4 of 4 (property)
+ 5, 5, 6, 6, 6, 6, 6, 5, 5, 5, 2, 2, 2, 2, 2,216,
+ 2, 2, 2,216, 2, 2, 2, 2, 2, 2, 2, 2, 2,208, 6, 5,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+ 6, 6, 5, 6, 5, 5, 5,208, 6, 2, 2, 2, 2, 2, 2, 2,
+
+// state[548 + 2] 0x021100 Byte 4 of 4 (property)
+ 2, 2, 5, 5, 5, 5, 5, 6, 208,208,208, 6, 6, 2, 2, 2,
+ 2,208,229,208,208,208,208,208, 208,208,208,208, 5, 5, 5, 6,
+208,218, 6,208, 6,218, 2, 2, 2, 2, 2, 2, 2, 2, 2,216,
+ 2,208,208,208,229,229,229,208, 208,208,208,208, 6, 5, 2, 5,
+
+// state[549 + 2] 0x021140 Byte 4 of 4 (property)
+ 5, 5, 6, 6, 6,208, 6, 6, 5, 2, 2, 2, 2, 2, 2,216,
+ 2, 2, 2, 6, 5, 6, 6,208, 208,208,208, 6,208,208,208,208,
+ 2,208, 2, 5, 6, 6, 6, 2, 2, 2, 2, 2, 2,208,208, 2,
+208,208, 5, 2, 6, 6,208, 2, 5,208,208,208,208,208,208,208,
+
+// state[550 + 2] 0x021180 Byte 4 of 4 (property)
+ 5, 2, 2, 5, 2,208,208, 5, 2, 5, 5, 5, 2, 2,208,208,
+208,227,208,208, 6, 6, 2, 2, 2, 2, 2, 5, 5, 6, 2, 6,
+208,208, 5,208, 6, 6, 6,208, 208,208, 6, 6, 3, 5, 6, 6,
+208, 6,208,208,208,208,208,208, 208, 5, 5,208,208, 6,208,208,
+
+// state[551 + 2] 0x0211c0 Byte 4 of 4 (property)
+ 6,208, 6,208,208, 6,208,208, 229,208,229,208,208,208,208, 6,
+ 5,208,208,208,208,208,208,208, 208, 5, 6,208, 6, 6, 6, 6,
+208,208,208,208,208, 6, 6, 6, 6, 6,208, 6, 6, 6, 6, 6,
+208,229,208,208,208,208,208,208, 218,208, 6, 2,229,208,208,208,
+
+// state[552 + 2] 0x021200 Byte 4 of 4 (property)
+208, 4, 5, 5, 5, 6, 6,208, 2,208,208,208,208,208,208,208,
+ 6,208, 6, 6, 5,208,208,208, 208,208,208, 5, 5, 5, 6,208,
+ 6, 2,208,208,208, 5, 6, 6, 6, 2,208, 6, 5,229,208, 6,
+ 6, 5, 6,208, 6, 6, 6, 5, 208,208, 2, 6,208, 4,208,208,
+
+// state[553 + 2] 0x021240 Byte 4 of 4 (property)
+ 6, 5, 5, 6,208, 6, 6, 6, 6, 6,208, 6, 5, 5,218, 5,
+ 6, 6,208, 6, 6,214, 2, 6, 208,208,208,208, 6,208, 6,217,
+ 5, 5, 5, 5, 5, 5,216, 5, 6, 6, 6, 6,208, 6, 6, 6,
+ 6,208,208,208,172,208,208,208, 6, 6, 6, 4, 5, 2, 2, 2,
+
+// state[554 + 2] 0x021280 Byte 4 of 4 (property)
+208,208,208,208,229,208,208,208, 208,208,208,208,208,208,208,208,
+ 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5,208,229, 6,208, 6,
+227,208, 6,208,208,208,208,208, 208, 5, 6, 6,208, 6, 6, 6,
+216, 2, 2, 2,208,208,208,208, 227,208,208,208,208,208,208, 6,
+
+// state[555 + 2] 0x0212c0 Byte 4 of 4 (property)
+ 2, 2, 2, 2,217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 4, 208, 6, 6,208,208,208,208, 6,
+208, 6, 6, 5, 4, 2, 2,208, 208,208,208,208,208,208,208,229,
+208,208,208,208,208,208,208,208, 208, 6, 6, 6, 6, 4, 0, 2,
+
+// state[556 + 2] 0x021300 Byte 4 of 4 (property)
+ 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6,208,208, 6,208,208,208, 208,208,208, 4,208, 6, 6,208,
+ 2, 2, 2, 2,208,208,208,208, 6,208,208,208,208,208,208,208,
+208,208,208,229,208, 6,194, 5, 5, 5, 5, 5, 5,216, 5,216,
+
+// state[557 + 2] 0x021340 Byte 4 of 4 (property)
+ 5, 5, 5, 5,194, 5, 5, 5, 5, 5,208, 6, 6, 6, 6,208,
+ 6,208, 6,208,208,208, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2,
+208,208, 2, 2,208,208,208,208, 208,229, 0,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 6, 6, 5,208, 5, 5, 5, 5,
+
+// state[558 + 2] 0x021380 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,208, 6,
+208, 6, 6, 6, 6,208,208,208, 208, 6,208, 2,216, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2,208, 208,208,208,208,208, 2,229,208,
+208,208, 6,208,229,208,208,208, 208,208,208,208,208,208,208,208,
+
+// state[559 + 2] 0x0213c0 Byte 4 of 4 (property)
+208,208,208,208, 4, 5, 5,208, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6,208, 218, 6, 6,218, 6, 6, 6,218,
+ 2, 2, 2, 2, 2, 2, 2, 2, 3,208,208,208, 6,208,208,227,
+208,208,208,208,208,208,208,208, 208, 6, 6, 6, 6, 2, 5, 5,
+
+// state[560 + 2] 0x021000 Byte 3 of 4 (relative offsets)
+-16,-15,-14,-13,-12,-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1,
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+
+// state[561 + 2] 0x021400 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208, 6, 6, 6,208, 6,
+208,208, 6,208,208,208,208, 2, 2, 2, 2,229, 6,208,208,208,
+208,208,208,229,208,208,229, 6, 208,208,208,208, 6, 5, 2, 2,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208,
+
+// state[562 + 2] 0x021440 Byte 4 of 4 (property)
+ 6, 6,208, 6, 6, 6, 6,208, 208,208,208, 6, 6, 6, 6,208,
+ 6,208, 0, 2, 5, 5, 2, 2, 208,208,208,208,208, 2,229,208,
+208,229,208,208,208, 6,208, 6, 5, 2, 2, 5, 5,223, 4, 6,
+ 2, 2, 2, 2, 2, 2,208,208, 208,208,208, 6, 6, 6, 6, 6,
+
+// state[563 + 2] 0x021480 Byte 4 of 4 (property)
+ 6, 6,208, 6, 6, 6,208, 5, 5,208,208,208,208,208,208, 6,
+208, 6,208,208, 6, 6,208, 5, 2, 5, 5, 5, 5, 5, 5, 5,
+ 5,208, 6,208,208, 6, 6, 6, 208, 6, 2, 2, 2, 2, 2,227,
+208,208,208,208, 5, 5, 0, 5, 5, 5, 5,216,208,208, 6, 6,
+
+// state[564 + 2] 0x0214c0 Byte 4 of 4 (property)
+ 6, 2, 2, 2, 2, 2, 2, 2, 2,208,208, 2, 5,208, 2, 2,
+208,208,208,208,227, 5, 6, 5, 6,208,208,208, 6, 6, 2, 2,
+ 2, 2, 2,208,208, 2,208, 5, 5, 5, 5, 5,208, 6, 2, 2,
+208,208,229,208,208,208,208, 6, 5, 5, 5, 5,208, 5, 2,208,
+
+// state[565 + 2] 0x021500 Byte 4 of 4 (property)
+ 6,208,208, 2, 5, 6, 6,208, 208,208,208,208, 6, 2, 6,229,
+ 6, 2, 2, 6, 2,208, 2,208, 5,208,208,208, 6,208,208, 5,
+ 5, 5, 5,219,208,208,208, 5, 208, 6, 6, 6,208, 5, 6,208,
+208, 6, 6, 6, 6,208,208,208, 6, 5, 5,208,208, 5,208, 2,
+
+// state[566 + 2] 0x021540 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6,208,208, 6, 6, 6, 5,208,208,208,208, 6,
+ 6,208,208,208, 6,229,208, 6, 208, 5,208,208,208,208,208,208,
+208,208,208,208, 5, 6, 5,208, 208,208,208,208,208, 6,208,208,
+ 6, 5,208, 5,208, 2,229, 5, 5,208,208,208,208,208,208,208,
+
+// state[567 + 2] 0x021580 Byte 4 of 4 (property)
+ 5, 5,229, 6,208, 6,208,219, 208, 6,208,208, 6,208, 6, 5,
+ 6,208,208,208,208,208, 5, 5, 5, 5,208, 6,208,208,208,208,
+ 6, 2,208,208,208, 5, 6, 6, 6,208,208,208,208,208, 5,208,
+208, 2,208,208,208, 2,208,208, 5,208, 6, 6, 2,208,208,208,
+
+// state[568 + 2] 0x0215c0 Byte 4 of 4 (property)
+ 0,208, 2, 5, 2, 2,208, 6, 208, 2, 2, 2,208, 6, 2, 6,
+ 2, 6,208,208, 5,208, 6,172, 5, 5, 6, 6,208,208,208,208,
+208,208,208, 5, 6,208,208, 6, 208, 5, 5, 5, 5, 5, 5, 5,
+208, 6, 6, 6,208, 6, 2,208, 208,208,208,208,208,208,208,208,
+
+// state[569 + 2] 0x021600 Byte 4 of 4 (property)
+ 5, 5,208, 6,208,208,208, 6, 6,208, 5, 5,208,208,208,208,
+208, 6, 5, 5, 5, 5, 5, 5, 5,208,208, 6, 6,208, 6,208,
+208, 6, 5, 5, 5, 5, 5, 6, 6,208, 6,208, 6, 6, 2, 2,
+208, 6,208, 5,208,208,208,208, 208,208,208,208,208, 5, 5, 5,
+
+// state[570 + 2] 0x021640 Byte 4 of 4 (property)
+ 6,208, 6, 6,208, 6, 6,233, 208,208,208, 6, 5, 5,208,208,
+208, 6, 6, 6, 6,208,208,208, 208, 6,208, 6,208,208, 6, 6,
+ 5, 5, 5, 6, 6, 6, 2, 2, 208,208,208, 5,208,208,208, 5,
+ 5, 5,208, 6, 2, 2,208,208, 208,208,208,208, 5, 5, 5, 5,
+
+// state[571 + 2] 0x021680 Byte 4 of 4 (property)
+208,208, 6, 2, 2,208,208,208, 208,208,208, 5,208, 6, 2,208,
+208,208, 5, 6, 6, 6,229,208, 5, 6, 2,208,208, 2, 5, 6,
+208, 2, 2, 5,208, 5, 2,219, 208, 6, 5, 5, 6, 6, 6,208,
+217,217, 5, 5,194, 5, 6, 6, 208,208,208,208,208,229,208, 5,
+
+// state[572 + 2] 0x0216c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208, 6,
+208, 6,208, 5, 2, 5,208,208, 208,208,208,208,208,229,229,208,
+208,208,208, 6, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 6, 6,208, 6,208, 5, 5, 5,208,208, 6,
+
+// state[573 + 2] 0x021700 Byte 4 of 4 (property)
+208,208,208,208,208,208,233,208, 208,208,208,208,208,208,208, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208,208, 6,208,
+208,208, 6,208, 2,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208, 6, 208, 6, 5, 5, 5, 5, 5, 5,
+
+// state[574 + 2] 0x021740 Byte 4 of 4 (property)
+ 5, 5,194,216, 5, 5, 5, 5, 6, 6,208, 6,229,208, 6,208,
+ 6,208, 6, 6, 2, 2, 2, 0, 5,208,208,208,208,208,208,208,
+208,208,208,208,208,208,227,208, 208,229,208,229, 5, 5, 5, 5,
+ 5, 5,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[575 + 2] 0x021780 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 6,
+208,208, 6,208,208, 2, 2, 2, 208,208,208,208,208,208,208,208,
+208,208,208,208,208, 6,229,208, 208,208,208,208,208,208,208,208,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[576 + 2] 0x0217c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6,208, 6,
+ 6,208, 6, 6,208,208,208, 6, 6, 5, 5, 5, 5,208,208,208,
+208,208,208,208,208,208,208, 5, 208,208,229,208,208,208,229,208,
+208,208,208,208,208, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[577 + 2] 0x021800 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 6, 6, 208, 6, 6,229, 6, 6,208, 6,
+208,208,208,208,208, 5, 6,208, 6,208,208,208,208,208,208,208,
+208, 6,208, 2,208,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5,229, 208,208, 0, 5, 6,208, 6, 6,
+
+// state[578 + 2] 0x021840 Byte 4 of 4 (property)
+208, 6,227,208, 6, 0, 2, 2, 208,208,208,208,208,208, 6,208,
+208,208,208,208,208,208,208,208, 6,229,208,208,208, 5, 0,208,
+ 5, 5, 5, 5, 5,208, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6, 5,208,208,208,208,
+
+// state[579 + 2] 0x021880 Byte 4 of 4 (property)
+ 6, 6, 5, 5, 5, 5,208,208, 208,208,208,208, 6, 5, 6, 6,
+ 2,208,208,208,229,208,208,208, 229,208,208,208,208, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+ 6,208,208, 6,208,208, 6,208, 208, 6, 3, 2, 2,214, 5, 5,
+
+// state[580 + 2] 0x0218c0 Byte 4 of 4 (property)
+208,208,208,208, 2,208,229,208, 208,208, 6,208,208,208,208,208,
+208,208,208, 6,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6,208, 6, 6,208, 6, 6, 2, 2,208,229,208,219,229,208,208,
+208,208, 5, 5, 5, 5, 6,208, 208, 6, 5, 2,208,208,208,208,
+
+// state[581 + 2] 0x021900 Byte 4 of 4 (property)
+208,208, 5, 5, 5, 5, 5, 5, 229, 6, 2,208,208, 5, 6,208,
+ 5, 5, 5, 2, 2, 5,208,208, 208,208,208,208, 5, 5, 6, 2,
+ 2,208,208,229,208,208, 5, 5, 208, 5, 5,208,229, 6,208,208,
+ 5,208,208, 6, 5, 6,208, 5, 6,208,208, 5, 5, 6,208,208,
+
+// state[582 + 2] 0x021940 Byte 4 of 4 (property)
+208,208, 5,208, 5, 5,208,208, 208,208, 5, 6, 2,208,208,208,
+208,208, 5, 5, 5, 5, 5,208, 208, 2, 2,208,208,208,208, 6,
+ 6, 5, 5, 5, 2, 2,208, 6, 208,208,208, 5, 5,208, 5, 5,
+ 5, 5, 5, 6, 2, 2,208,208, 219,208,208, 5, 5,208,208, 6,
+
+// state[583 + 2] 0x021980 Byte 4 of 4 (property)
+208, 6, 2,208,208,227,208, 5, 5, 5, 6,208,208, 6,208,208,
+ 6,208, 5, 5, 6,208, 5,208, 208, 6, 5, 6, 6,208,208,208,
+208,208,208,208, 5, 5, 6, 2, 6,208, 6, 2, 6, 5, 2, 5,
+ 5, 6, 6, 6, 6, 5, 2, 6, 5, 5, 6, 6,208,208,208,208,
+
+// state[584 + 2] 0x0219c0 Byte 4 of 4 (property)
+ 6, 6, 5,194, 5, 5, 5, 5, 208, 6, 6,208,208,208,208,208,
+ 6, 5, 6, 6, 6, 6,208,208, 208,208,208, 5, 5, 5, 5, 5,
+ 5, 6,208,208, 6, 6, 6, 6, 6,208,208,208,208,208,208,229,
+208,208, 2, 5, 5, 5, 5, 5, 5, 6,208,208, 6, 6, 6, 6,
+
+// state[585 + 2] 0x021a00 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208, 6, 2, 5, 5, 5,
+ 5, 5, 6,208,208, 6, 6, 6, 6, 6, 6, 6,208, 6,208,208,
+208, 6,229,208,208,229,208,227, 208,208, 5, 5, 5, 5, 5, 5,
+ 5, 5, 6, 6,229, 6,208, 6, 2, 2, 2,208,208,208,208,208,
+
+// state[586 + 2] 0x021a40 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208, 6,229, 6, 6, 5, 5,
+ 5, 5, 5, 5, 5, 5,219, 6, 6,208, 6,208, 6, 6, 5,229,
+208,208,208,208,208,208,208,208, 208,208,208, 6, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,208,208,208,229,208,208,
+
+// state[587 + 2] 0x021a80 Byte 4 of 4 (property)
+208,208,208,208,229,208,208, 2, 5, 5, 5, 5, 6, 6,208,208,
+208, 6, 6,208,208, 6,208,208, 208, 6,208,208, 2, 6, 6,208,
+208,208,208,208,208,208, 2, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6, 6,208, 6,208,208, 208, 6,208, 2,208,208,208,208,
+
+// state[588 + 2] 0x021ac0 Byte 4 of 4 (property)
+208,208,208,208,208,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+ 2, 6,208, 5, 5, 5, 5, 5, 5, 6,208,208,208,208,208,208,
+ 5, 2, 5, 5, 6, 6, 6, 6, 2,208, 5, 2,208,208,208,208,
+208,208, 5,208, 6, 6, 2,208, 6, 6, 6, 5,208,229,208, 5,
+
+// state[589 + 2] 0x021b00 Byte 4 of 4 (property)
+ 5, 6, 6,208,229,229,208,208, 2, 6,208, 6,208,208, 6, 5,
+ 6,208,208,208, 5, 6,208, 6, 208,208,208,208, 6,208, 6,208,
+208, 6, 5, 5, 6, 6, 6,208, 208, 5,208, 6,208, 6,208,208,
+208,208, 6,208,208, 6,219, 2, 6,208,208,208, 2,208,229, 5,
+
+// state[590 + 2] 0x021b40 Byte 4 of 4 (property)
+ 6,208,208, 6, 5, 5,208, 6, 208, 2,208,208,208,208,208, 6,
+208,208,208,208, 6,227, 5, 6, 6,208, 0, 6, 2,208,208,229,
+208, 5, 6, 6, 6,208, 2, 2, 5,208,208,208, 2, 6, 6,208,
+ 2, 2, 2,208,229, 5, 6, 6, 6,208, 2, 2, 6,229,208,208,
+
+// state[591 + 2] 0x021b80 Byte 4 of 4 (property)
+208,208, 5, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208, 5,
+208, 6, 2, 2, 2,208, 6, 2, 2,208, 6, 2, 2, 5, 0,208,
+ 2,208, 6, 2, 2, 2,208,208, 2, 2, 2, 2, 2, 2, 2, 2,
+208,208, 2, 2, 5, 2, 2, 2, 2, 2, 2,208, 6, 2, 2,208,
+
+// state[592 + 2] 0x021bc0 Byte 4 of 4 (property)
+ 2,208,208, 5,208, 5, 5, 6, 6,208,208, 6, 6,208, 5,208,
+208, 6,208, 6, 5, 6,208, 6, 208,208, 6,208,208,208, 5, 5,
+208, 5,208,208, 6,208,208,208, 208,208,208, 6, 5, 5, 5, 5,
+ 6, 6, 5,208,208,208,208,208, 5, 6,208,208, 5, 6, 6, 6,
+
+// state[593 + 2] 0x021c00 Byte 4 of 4 (property)
+ 6, 6, 5,208,208, 6, 6,229, 6,208, 5,208,208, 5,208, 2,
+208,208,208, 6,208, 5,208,208, 208, 5, 6, 6, 5,229, 6,219,
+208, 6,208, 5,208,229,208, 6, 6,208,208,208,208,208,208,208,
+ 5, 6, 6, 6, 6, 6,208, 5, 208, 6,208, 6,208,208,208,208,
+
+// state[594 + 2] 0x021c40 Byte 4 of 4 (property)
+208,229,208,208, 5, 6, 6, 6, 6, 6, 6,208,208,208,208,208,
+208,208,208,208,208,208, 4, 5, 5, 5, 5, 5, 6,208, 6, 6,
+208,208,208,208,208,208,208,208, 208, 2, 5, 5, 5, 5, 5, 6,
+208,208,208, 6,208, 6, 2,208, 2, 5,229,208,208,208,208,208,
+
+// state[595 + 2] 0x021c80 Byte 4 of 4 (property)
+208,208,208,208, 5, 5, 5, 5, 216, 5, 5, 5,208, 6,208, 6,
+ 6,208,208, 5,208,229,208,229, 208,208,208,208,208,208,208,208,
+208,208, 5,208, 2, 5, 5, 5, 5,208,208, 2,208,208,208,208,
+229,208,208,208,208,208,208, 6, 5, 5, 5,208, 6, 6,208, 6,
+
+// state[596 + 2] 0x021cc0 Byte 4 of 4 (property)
+ 2, 2,208, 2,208,208,208,208, 208,208, 5, 6, 6,208, 2,208,
+229,208, 2, 5, 5, 5, 5, 5, 208, 6, 6, 2, 2, 2,208,208,
+208,208, 2, 2, 2, 2, 5,208, 208, 2, 2,208,208,208, 6, 6,
+ 2, 5, 5, 2,208, 2, 2, 5, 208, 2, 2, 6, 6, 2,208, 5,
+
+// state[597 + 2] 0x021d00 Byte 4 of 4 (property)
+208,208,208, 6,208,208,208, 6, 208, 2, 6,208,208,208,208,208,
+ 6,208, 5,208, 6,208,208,208, 208, 5, 6,208,208,208,208,208,
+ 6,208, 2, 5,208,208,208,208, 208,208, 6,208,208,233, 6,227,
+208,208,208,208, 6, 5, 5, 5, 208, 6, 6,208,208, 6, 6,208,
+
+// state[598 + 2] 0x021d40 Byte 4 of 4 (property)
+208,208, 6, 6, 6, 4, 5, 5, 5,218,208,208, 6, 6,208,208,
+ 6,208, 6,229,208,208,208,208, 208,208,208,208,208,208,208, 6,
+ 6, 6, 4, 5, 5, 6, 5, 5, 5, 5, 5,217, 6, 6,208, 6,
+208,208, 6,208, 6, 6, 6, 6, 4,208,227, 6,208, 6,208, 6,
+
+// state[599 + 2] 0x021d80 Byte 4 of 4 (property)
+ 2, 2,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,233,208, 5, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5,
+ 5, 4,208, 6,208, 6, 6, 6, 2,208,208,229,208,208,208,208,
+208,208, 6,208, 6, 6,208, 4, 208, 5, 5, 5, 5, 5, 5, 5,
+
+// state[600 + 2] 0x021dc0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5,208,208,208, 6,208, 5, 0,208, 6, 6,208,
+208, 5,208,208, 5,208,208,208, 229,208,208,208,208,208,229,208,
+233,208,208,229,208,208,208,208, 208,208,208,229, 6,208,208, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 208, 5, 6, 6, 6, 6, 6,208,
+
+// state[601 + 2] 0x021e00 Byte 4 of 4 (property)
+ 6, 6,208, 6, 6, 6, 6, 2, 208,208,208, 6,208, 6,208, 2,
+ 5,208,208,208,208,208,208,208, 208,208,208,208,208, 6, 0,208,
+208,208,208,208,208,208,208,208, 208,208,208, 6,208, 6, 6, 6,
+ 6, 6, 6, 4, 4,229, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[602 + 2] 0x021e40 Byte 4 of 4 (property)
+ 5, 5, 5,229, 6, 6,208, 6, 6, 6,208,208, 6, 5, 6,208,
+ 6, 6,208, 6,229, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2,
+ 2, 2,208,208,208,208, 6,208, 208,208,208,208,208,208,229,208,
+208, 6,208,208,219,208,208,208, 208,208,208,208,208,208,208, 6,
+
+// state[603 + 2] 0x021e80 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 6,208, 6, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6,208, 6,208, 6, 6, 6, 6,208,229, 6, 6,208, 6, 6,
+ 6,208, 6, 6, 5, 2, 2, 6, 5, 6, 6,208,208,229,208,208,
+208,208,208,208,208,208,208,208, 6,208,208,208,229,208,208,208,
+
+// state[604 + 2] 0x021ec0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 208, 6, 6,208, 6,208, 6, 6,
+ 6,208, 6, 2, 2, 5, 2,227, 208,208,208,208,208,229,208,208,
+208,208,208,208,208,208,208,208, 208,208,229, 6,208,208,229,229,
+208,208,208, 6,208, 5, 5, 5, 5, 5, 6, 6, 6, 6,208, 6,
+
+// state[605 + 2] 0x021f00 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 6, 6, 2, 2, 6,208,208,208,208,208,229,
+208,229,208,208,208,229,208,208, 208,208,208,208,208,208, 4, 6,
+ 6, 6, 6, 6, 6,217, 5, 5, 5, 5, 5, 6, 6, 6, 6,208,
+208, 6, 6, 6, 6, 6,208, 6, 2, 2,208, 6,208,208,208,229,
+
+// state[606 + 2] 0x021f40 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208, 6, 5, 5, 5, 6,
+208, 6, 6,208,208,208, 6, 6, 208, 6,208,208,208,208,208,208,
+208,229, 6,208,208,208,208,208, 6, 6, 5, 2,229, 5, 5, 5,
+ 5, 6, 6, 6, 6,208,172, 6, 229, 6,229, 6, 6,208, 2, 6,
+
+// state[607 + 2] 0x021f80 Byte 4 of 4 (property)
+208, 6,208, 6, 6,208,208,208, 208,208,229,229,208,208, 5, 6,
+208,208, 6, 6,208,208, 6, 6, 208,208,208,208,229,208,208,229,
+ 6, 5, 5, 5, 5, 6,229,208, 6,208, 6, 2, 2, 2,229,229,
+229,208,208, 6, 6, 2, 5,208, 6, 6, 6,208,208,208,208, 6,
+
+// state[608 + 2] 0x021fc0 Byte 4 of 4 (property)
+ 6,208, 6, 6, 6, 2,208,208, 208,208,208,208,208, 6, 6,208,
+ 6,208, 6, 6,208,229,229, 6, 208, 6, 6,208,208,229, 6, 5,
+208,229,208,208, 6,229, 6,208, 208,208,208, 5, 5,208,208,208,
+208,208, 5, 5, 6, 6, 6,208, 208,208, 4,208, 6,208,208,208,
+
+// state[609 + 2] 0x022000 Byte 4 of 4 (property)
+208,208,208,208,208, 5, 5,208, 208,208,208, 6, 6, 6,208, 6,
+208,208,208, 5, 5, 6, 6, 6, 208,208, 6, 6,208,208, 6, 5,
+208,208, 5,208, 5, 2, 2, 2, 2,208, 6, 2, 2, 2, 2,208,
+ 6, 2, 2, 3,208, 6,208,208, 6,208, 6, 5, 6, 6, 6,208,
+
+// state[610 + 2] 0x022040 Byte 4 of 4 (property)
+208,229,229,208, 5,208, 5, 5, 5, 5, 2, 2,208, 2, 6, 6,
+208, 2,208,208,208,229,208,208, 229,208,229,208, 5, 6, 6,208,
+ 6, 6,208,208,208,208,208,229, 208,208,208, 6,208,208,208,208,
+208,208, 5,208,208,208, 6, 6, 2,229,229,208,229,208,208,229,
+
+// state[611 + 2] 0x022080 Byte 4 of 4 (property)
+208,229,208,208, 6, 6,208,208, 5, 6,208,208, 2,208, 2,208,
+229,208,208,208,208,208,208,208, 208, 6,208, 6, 2,208, 5, 5,
+ 5, 5, 5, 5, 6,229,208, 6, 6,208,208,208,208, 6,208,208,
+218,208,208, 6,208,208,208,208, 229,208,208,208,208,229,208,208,
+
+// state[612 + 2] 0x0220c0 Byte 4 of 4 (property)
+208,208,208, 5,229, 6,229, 5, 2, 2, 2,208, 5,208,229,208,
+229,229,208,208,208,208,208,208, 229,208,208,208,208, 5, 5, 5,
+ 5, 5, 6,229,229, 6, 6,208, 208, 6,208,208,208,208,208,208,
+208,227,208,208,208,208,208,208, 229,208,208, 5, 5, 5, 5, 6,
+
+// state[613 + 2] 0x022100 Byte 4 of 4 (property)
+ 6,208, 2, 2,208,208,208,208, 5, 6,229,208,208,208,229,208,
+229, 6, 6,229,208,208,208,208, 208, 6, 5, 5,208, 2,208,208,
+229,208,208,229,208,208,208,208, 6, 2, 5, 5, 6, 6, 6, 2,
+208,208,208,208,208,208,229,208, 208, 0,208,219, 0,208,208,208,
+
+// state[614 + 2] 0x022140 Byte 4 of 4 (property)
+ 6, 6,208, 6, 2, 2, 2, 6, 6,208,208,208, 3,208,229,229,
+208,208,208,229,208,229, 5,208, 6,208,208,208, 6, 6,208,208,
+208,229,208, 5, 6,172,208, 2, 229,208,208,208, 2,208,208,229,
+208,208, 5, 5, 6,208, 5, 6, 208,208,208,228,208,208,208, 5,
+
+// state[615 + 2] 0x022180 Byte 4 of 4 (property)
+ 6, 2, 6,208,208,208, 6,208, 5,229,229,208, 6,208,208, 5,
+ 5,208, 5, 5, 6, 2, 6, 6, 2, 5, 5,208,229, 6,219,208,
+ 2, 5, 5, 6, 5, 2, 6, 2, 2, 6, 2, 2,208, 2, 5,208,
+ 6,208,229,208,208, 5,208,229, 208, 5, 6,229,208,208,208,208,
+
+// state[616 + 2] 0x0221c0 Byte 4 of 4 (property)
+208, 5, 6, 5, 6, 6, 5,229, 6, 2, 5, 5,208,208, 5,208,
+ 5,208,208, 6,208, 6,208,208, 208,208,208,208,208,208,208, 6,
+208, 2, 2,208,208,229,208,208, 208,208,208,208, 0, 5, 6,208,
+208, 2,208,208,229,229,208,229, 208,208,208,208,208, 5, 5, 5,
+
+// state[617 + 2] 0x022200 Byte 4 of 4 (property)
+ 5, 5,208, 6,208,208, 6,229, 229,208,208,208,208, 5, 5, 5,
+ 5, 5,208, 6, 6, 6, 2,208, 4,208,208,208,208,208,208,208,
+229,208,208,208,208,208, 6, 6, 5, 6, 6, 6,208,208, 6, 6,
+ 6, 2,208,208,229,208,208,229, 208,229,229,208,208,229,208,208,
+
+// state[618 + 2] 0x022240 Byte 4 of 4 (property)
+208,208,208,229,208,208,208, 6, 229, 6, 5, 5, 5, 5, 5, 5,
+ 5, 6, 6, 6, 6,208,208, 6, 208,208,208,208,208,208,208,208,
+208,208,208,208,229,208,208,208, 229,229,208,208,208,208, 5, 5,
+ 5, 5, 5, 6,208, 6,208, 6, 6, 6, 6, 6,217, 2, 2,208,
+
+// state[619 + 2] 0x022280 Byte 4 of 4 (property)
+208,229,208,229,208,229,208,208, 6, 5, 5, 5, 5,172,208, 6,
+208, 2,208,208,208,208,229,208, 208,208, 6,208,229,208, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 208, 6,208, 2, 5,208,208,208,
+208,208,208,208,208,229, 5, 5, 5, 5,229,208,208, 6, 6, 6,
+
+// state[620 + 2] 0x0222c0 Byte 4 of 4 (property)
+ 6, 5,208,208,208, 0,208,208, 6, 6, 6, 3, 5, 5, 5, 5,
+ 5,208,208,208,208, 5, 5, 5, 5,219,208, 6,208,208, 6, 6,
+ 2,208,229,208, 5, 5,208,208, 219, 6,208,208, 2,208,219,208,
+208,208,208, 3, 2, 5, 5, 5, 208, 6, 6,208,208,208,208,229,
+
+// state[621 + 2] 0x022300 Byte 4 of 4 (property)
+208, 6,208,208,229, 6, 5,208, 208,208, 2,208, 2,208, 2, 5,
+ 6,208,208,208,208,208, 5, 5, 5, 5, 6,229,217,208,228, 6,
+ 5, 5, 5, 5, 3, 5,208, 5, 208, 5, 5,208, 5,208, 6,208,
+ 6,208, 6,208,208,208, 5, 6, 208, 6,208,229,229, 5,208,208,
+
+// state[622 + 2] 0x022340 Byte 4 of 4 (property)
+ 6,229, 5,208,208,208,208, 6, 6, 6,208, 6, 6,208,208,229,
+208, 6,208, 6,208,208,208,208, 208,208,208,208,208,208,208, 5,
+208, 6,208,208, 5, 6,208,208, 208, 6, 6,208,208, 6,208,208,
+229,229,208,208, 6, 6,229,208, 208,208,208,208, 6, 6, 5,229,
+
+// state[623 + 2] 0x022380 Byte 4 of 4 (property)
+208,208, 6,208,229, 6,208,208, 6, 6,208, 6,208,208,208, 6,
+208,208,229,208, 6, 6, 6, 6, 208,208,208, 6,208,208, 6,208,
+208, 6,208,208,208,229,208,208, 208,208,208,208, 6,233, 6, 6,
+ 5, 5, 5, 6, 6, 6, 5,208, 208,229,208,208, 5, 5, 5, 5,
+
+// state[624 + 2] 0x022000 Byte 3 of 4 (relative offsets)
+-15,-14,-13,-12,-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1,
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
+ 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
+
+// state[625 + 2] 0x0223c0 Byte 4 of 4 (property)
+ 5,208, 6, 6, 6,208,229,208, 208,208, 6, 6, 6, 6, 6, 6,
+ 5, 2,208,208,208,208,208,208, 208,229,208, 6, 6, 6, 6, 6,
+ 2, 2, 2, 2,208, 6,208,208, 208,208,208, 5,208, 6,229,208,
+208,208,208,208,208,208,208,208, 208, 5, 5, 5,208,208,208, 2,
+
+// state[626 + 2] 0x022400 Byte 4 of 4 (property)
+208,208,208,229,229,208,208,208, 208, 6,229,208,208, 5,218,208,
+ 6, 6,208,208,208,208,208, 6, 5,208, 6, 6, 6,208,229,229,
+208,208, 5, 5,208,208,208,229, 208,208, 6, 6,208,208, 6, 5,
+ 5, 6,208,208, 5, 5, 6,208, 208,208,208,208,208, 6,208,208,
+
+// state[627 + 2] 0x022440 Byte 4 of 4 (property)
+208,229, 5,208,208, 6,208,208, 208,208,208,208,208, 5, 6, 6,
+ 5,208,208,208,208,208,208,208, 208,208,208, 5,208, 6,208, 6,
+ 6, 5,208,208,208, 5,208, 6, 208, 5,208,208, 6,208, 6,208,
+208, 5, 5,208, 6,208,208,208, 208,208, 6, 6, 6,208,208,208,
+
+// state[628 + 2] 0x022480 Byte 4 of 4 (property)
+229,229, 5,208, 5, 6, 6, 5, 208,208,208, 5,208, 6, 2,208,
+208,229, 6, 6,229,208,208,208, 5, 5, 5,208,208,208,227,208,
+208,208, 5, 6,208,208,208,208, 5,208,208,208,229,208,208,208,
+208, 6,208,208,208,208, 5,208, 208,208, 3, 5, 6,208,208,208,
+
+// state[629 + 2] 0x0224c0 Byte 4 of 4 (property)
+208, 6,208,208,208,208,229,208, 229,208,208,208, 6, 6, 6,208,
+208, 6,208,208,208,208,229,208, 208, 6, 6, 3,229,208,208,208,
+208,219,208,208, 6, 5, 6, 6, 6,208,208,208,208,229,208,208,
+208,208,229,208,208,229,208,208, 208, 5, 5, 6,208,208,208, 6,
+
+// state[630 + 2] 0x022500 Byte 4 of 4 (property)
+208,208,208,208,208, 6,229,208, 229,208,208,208,229,208,208,208,
+208,208, 6, 5, 6, 6, 6, 6, 208, 6,208,217,208,208, 5,208,
+208,229,208,208,208,208,208,208, 208,208,229, 5, 6,208, 6, 6,
+ 5,208, 6,208,208,208,208,208, 208, 6, 6, 5, 5, 5, 5, 5,
+
+// state[631 + 2] 0x022540 Byte 4 of 4 (property)
+ 5, 6, 6,208, 6, 6, 6,208, 208, 6, 6, 6, 6,229,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+ 5, 5, 5, 5, 5, 6, 6,208, 6, 6,208,229,208,208,208,208,
+208,208, 6, 5, 5, 6,208,208, 2,208,229,208,208, 6,208, 6,
+
+// state[632 + 2] 0x022580 Byte 4 of 4 (property)
+ 2, 5, 5,208,208, 6, 6, 6, 208,208,208, 6, 6,229,208,229,
+208,229,208, 5, 6, 2, 2,208, 208,208,208,208,208,208,208,208,
+ 5, 6,208,208,208,208,208,208, 6, 5,208,208,208,208, 2, 5,
+ 5, 2,208,208,208,208,208,229, 208,208,227,208, 6, 5, 5, 6,
+
+// state[633 + 2] 0x0225c0 Byte 4 of 4 (property)
+ 6,208, 6,208, 6, 6, 2,208, 208,227,229,208,208,208,229, 6,
+208,229,208,208,208,208,224,229, 229, 6, 2, 5, 5, 5, 5, 5,
+216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208, 6,208, 6,
+ 6,208,208, 6, 6,208, 6, 2, 2,208,208, 5, 2, 2, 2,208,
+
+// state[634 + 2] 0x022600 Byte 4 of 4 (property)
+208,208,208,208,229,208,208,229, 208,229,208,208,208,208,208,208,
+208,208,229,208,208,208,208, 6, 6, 6, 5, 5, 5,216, 5, 6,
+208, 6, 6,208, 6, 6,208,208, 208, 6,208, 5, 2, 2, 2, 5,
+ 5, 5, 5, 5,208,208,208,208, 208, 6,208,208,208,208,208,229,
+
+// state[635 + 2] 0x022640 Byte 4 of 4 (property)
+208,208,208,229,229,229,229,208, 208,208,229,208,208,208,208, 6,
+ 6, 6, 6, 6, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,229,229, 6, 6, 5, 6, 5, 2, 2, 2, 2, 2,208, 6,
+208,229,227,208,208, 0,208,208, 208,208,208,208,208,227,208,208,
+
+// state[636 + 2] 0x022680 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,208, 6, 6, 6, 6, 6, 6, 6, 6,208,
+229, 6, 2, 2, 2, 2, 2, 2, 2, 2,208,208, 2, 2, 6, 6,
+
+// state[637 + 2] 0x0226c0 Byte 4 of 4 (property)
+ 6,229,229,208,208,208,208,208, 208,208,208,208,229,208,208,229,
+208,208,208,208,208,208,229,208, 208,208,229,208,208,208,208,208,
+208,208,208,208,208,208,208,229, 208,208,208,208,208,208,208, 6,
+ 5, 6, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[638 + 2] 0x022700 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,216,
+ 5, 5, 5, 5,208, 6,208,227, 6, 6,208, 5, 2, 2, 2, 5,
+ 2, 2, 2,208,208,208, 6, 5, 208,208,208,208,229,208,229,208,
+229,208,208,208,208,208,208,208, 208,208,229,208,208,208,208,208,
+
+// state[639 + 2] 0x022740 Byte 4 of 4 (property)
+208,229,208,208,208,208,208,229, 208,208,229,208,208,208,208,208,
+208,208,208,208,229,208, 6,208, 2, 2, 2, 2, 2, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,208,208, 6,
+ 6, 6,229,218, 6, 5, 6, 6, 218, 6, 6, 6, 6,208, 6, 3,
+
+// state[640 + 2] 0x022780 Byte 4 of 4 (property)
+ 3, 5, 2, 2, 2, 2, 2,208, 6,208,208, 6, 5, 5,229,208,
+208,208,208,229,229,208,229,208, 208,208,229, 6,208,208,208,208,
+229,229,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,229,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[641 + 2] 0x0227c0 Byte 4 of 4 (property)
+ 5, 5, 5,208,208, 6, 6, 6, 6, 6,208,208, 6,217, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,229, 2, 2,
+208,208,208,208,229, 6,208,208, 208,208,229,208,229, 6,229,229,
+208,208,208,208,208,208,208,208, 208,227,229,208,208,208,208,208,
+
+// state[642 + 2] 0x022800 Byte 4 of 4 (property)
+208, 6, 6, 5, 2, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5, 5,
+216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6,208,208,
+208,208, 6, 2, 2, 2, 2, 2, 2, 2, 2, 6,208, 6,208, 2,
+ 6,229,208,229,229,229,208,208, 229,229,208,208,229,208,229,208,
+
+// state[643 + 2] 0x022840 Byte 4 of 4 (property)
+208,208,208,208,229,208,229,208, 208,208,229,208,219, 6,208,208,
+208,208,208,208,208,208,229,208, 208,208,219, 4, 6, 6, 5, 5,
+ 5, 5,208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6,229, 6,208,208, 6, 6, 6, 208, 6, 6,219, 6,208,208, 0,
+
+// state[644 + 2] 0x022880 Byte 4 of 4 (property)
+ 6,218,218, 6, 2, 2, 2, 2, 2, 2, 2, 2,208,208, 6, 5,
+ 6, 5,208,208,229,208,208,208, 208,208,208,208,229,208,229,208,
+208,208,229,229,208,229,208,208, 6, 6, 6, 4, 2, 5, 5, 2,
+ 5, 5,216, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,218, 6, 6,
+
+// state[645 + 2] 0x0228c0 Byte 4 of 4 (property)
+ 2, 5, 2, 2, 2, 2, 2, 2, 2,208,208, 5, 5,208,208,208,
+229,229,208,208,208,229,208, 6, 208,208,208,208,208,208,208,229,
+208,208,208, 6, 6, 6, 6, 2, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6,208, 6, 6, 6,208, 5, 2, 2, 6,229,208,208,208,229,
+
+// state[646 + 2] 0x022900 Byte 4 of 4 (property)
+208,229,208,208,229,208,208,208, 208,208,208,208,208, 6,208,208,
+229,208,208,208, 6, 5, 5, 5, 208,208, 6, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2,208, 5,229,229, 208,229,208,208,208,208,208,208,
+208,229,208,208,229,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 6,
+
+// state[647 + 2] 0x022940 Byte 4 of 4 (property)
+ 6,208, 6,208,208, 2, 2, 2, 2, 2, 5,208,208,208,208,208,
+208,229,208,229, 5, 6, 2,208, 208, 6,229,208,208,208,229,208,
+219,208, 2, 2, 5,208,208,229, 229,229, 2, 5, 5, 5, 5,208,
+229,208,208, 6, 6, 6, 2, 2, 5, 5, 5,229,229,208,208, 5,
+
+// state[648 + 2] 0x022980 Byte 4 of 4 (property)
+ 5, 5, 2, 6, 2,229, 5, 5, 229,208, 6, 5, 6, 6,208,233,
+208, 6, 6,208, 5,208,208, 6, 6,208,208,208,208,208,208,208,
+208,208,208, 5,208, 6,208,229, 208,208,229,229,208, 6, 5, 5,
+ 6, 6, 6, 6, 6, 6,208,208, 208,208,208, 5, 6, 6, 6,208,
+
+// state[649 + 2] 0x0229c0 Byte 4 of 4 (property)
+229,208, 5, 5, 6, 6,208,208, 208,229,229,208,208, 5, 5, 6,
+ 6,208,208, 5,208, 0,208, 6, 208, 6, 2, 5,208, 6, 6,208,
+208, 6,208, 6,208,208,208,208, 208, 5, 5, 5,208, 6, 6, 6,
+ 6, 6, 2,229,208,229,208,208, 208,208, 5, 6, 6,208,208,208,
+
+// state[650 + 2] 0x022a00 Byte 4 of 4 (property)
+ 6, 6, 6, 2, 5, 5, 5,208, 208,229,208,208,208,208,208, 6,
+208,208,208,208,208, 5, 6, 6, 6, 5,208,229, 6, 6,208,208,
+ 5,208, 6,208, 5,208,229, 6, 6,208,229, 6, 5, 5, 6,229,
+ 6, 6,208, 6,208, 2, 6,208, 208,208,208, 5, 5, 5,208, 6,
+
+// state[651 + 2] 0x022a40 Byte 4 of 4 (property)
+ 6,208, 6, 6,208,229,208,208, 208, 2, 2, 5, 6,208, 5, 6,
+ 6,208,208,208, 6,208,208, 6, 229,208, 6, 2, 2,208, 6, 6,
+229, 6,229, 2, 2,208,208, 2, 208,208, 2,208,208,227,227, 6,
+208,208,208, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 2, 2, 2,
+
+// state[652 + 2] 0x022a80 Byte 4 of 4 (property)
+ 2, 6,208,208,229,208,208,208, 208,208,208,208,208,208,229,208,
+208,208,208,208,208,208,208,227, 6, 6, 6, 6, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5,218, 6,208, 6, 5, 6, 6, 6,213, 2, 2,
+ 2, 2, 2, 2,208,208,208,227, 228, 6,208,208,208,208,208,208,
+
+// state[653 + 2] 0x022ac0 Byte 4 of 4 (property)
+208, 6, 5, 2, 2, 2,216, 5, 216, 5, 5, 5,216, 5, 5, 5,
+ 6,218, 6, 6,208,216, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2,208, 2,208,208,233,229, 229,229,208,208,208,208,229,208,
+229,208,208,208,208,229,208,208, 208,208,208,208,208,208,208,208,
+
+// state[654 + 2] 0x022b00 Byte 4 of 4 (property)
+208, 6, 6,208, 2, 2, 5,216, 5, 5, 5, 5, 5, 5, 5, 5,
+216, 5, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 6, 6,208, 6,
+ 6, 6,216, 2, 2, 2, 2, 2, 2, 2,208,208,229, 2, 2,208,
+208, 0,208,208,229,229,208,229, 208,227,208,208,208,229,208,227,
+
+// state[655 + 2] 0x022b40 Byte 4 of 4 (property)
+208,208,208,208,208,208,228,229, 208,208,208,208,208,208,208, 4,
+ 4, 2, 5, 5, 5, 5, 5,216, 5, 5, 5, 5,216,216, 5, 5,
+ 5, 5, 5, 5,208, 6,208, 6, 208, 6, 5, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[656 + 2] 0x022b80 Byte 4 of 4 (property)
+ 2,208,208,208,208, 6,208,208, 208,208,208,229,208,208,208,208,
+208,227,208,229,208,208,208,208, 229,208,208, 6,208,208,208,208,
+208,208,208,208,208, 6, 4, 5, 5, 2, 2, 2, 2, 2, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,216,
+
+// state[657 + 2] 0x022bc0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 6,208, 6, 208, 6,208, 6,208, 6, 5, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 5, 2, 2, 2, 2, 2,208,208,208,229,208,227,208,
+229,229,208,208,208,208,208,227, 208,229,208,229,229,208,208,208,
+
+// state[658 + 2] 0x022c00 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208,208, 4, 6, 6,
+ 6, 6, 6, 6, 4, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,216, 5, 5, 5, 5, 5, 5,216, 5, 5,216, 5, 6,
+
+// state[659 + 2] 0x022c40 Byte 4 of 4 (property)
+208, 6,208,208, 6,208, 6,208, 6, 6, 6, 6,208,217, 2, 2,
+ 2, 5, 2, 2, 2, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,
+208,208,229,208,208,208,208, 0, 227,208,208,229,208, 6,208,208,
+208,208,208,208,208, 6,208,208, 208,208,208,208,208, 6,208,208,
+
+// state[660 + 2] 0x022c80 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 6,218,208,208, 6, 208, 6, 6,216, 6, 2, 6, 2,
+ 2, 5, 2, 2, 2, 2, 2, 2, 2,216, 2, 2, 2, 2, 2, 2,
+208, 2,216,208,229, 0,208,208, 208,208,208,229,208,208,208, 6,
+
+// state[661 + 2] 0x022cc0 Byte 4 of 4 (property)
+229, 0,208,208,229,208,227,208, 208,208, 6,208, 6,229,208,208,
+208,208,208,208,208,208,208, 6, 208,208,229, 6, 6, 6, 2, 6,
+ 2, 5, 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6,
+
+// state[662 + 2] 0x022d00 Byte 4 of 4 (property)
+208,208, 6, 6, 6, 6, 6,216, 5, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2,216, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6,
+229,229,208,208,229,208,229,208, 208,208,208,208,208,208,208, 6,
+208,208,229,229,208,208,208,208, 208,208,229,208,229,208,208,208,
+
+// state[663 + 2] 0x022d40 Byte 4 of 4 (property)
+208,208, 6,208,208, 6, 6, 6, 208, 2, 5, 2,216, 5, 5, 5,
+ 5, 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6, 6,208, 6, 6, 6,216, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6,208,
+
+// state[664 + 2] 0x022d80 Byte 4 of 4 (property)
+208,208,227,229,208,229,229,208, 208,208,229,208,208,229,208,229,
+ 0,227, 0,208,208,229,208,208, 208,208,208,208,229,208, 6, 2,
+ 5, 6, 2, 5, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 6, 6, 6,208,218, 0, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[665 + 2] 0x022dc0 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 2, 2,208, 5, 5, 6,208,208,229,229,229,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208, 6, 6,
+ 6, 4, 6, 2, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,216, 2,
+ 2, 2, 2, 2, 2, 2,208,208, 208,227,208,208,208,208,208,208,
+
+// state[666 + 2] 0x022e00 Byte 4 of 4 (property)
+208,208,208,208,208,229,208,229, 208,208, 6, 6, 6,231, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 208,208, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2,208,208,208,208,208, 208,208,208,208,208,229,229,208,
+208,208,208,208,229, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[667 + 2] 0x022e40 Byte 4 of 4 (property)
+ 5, 5, 5, 6,208, 2, 2, 2, 2, 2, 2, 5, 2,229, 6,208,
+229,208, 6,208,208,208,208,208, 208,229,229, 6, 6, 6, 6, 5,
+ 5, 5, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2,229,229,229,229,
+208,208,208,208,208,208, 6, 5, 5, 5, 5, 5, 6, 2, 2, 2,
+
+// state[668 + 2] 0x022e80 Byte 4 of 4 (property)
+ 2, 2,208,208,229,229,208,208, 229,208,208,229, 5, 2, 2, 5,
+ 5,208, 2, 2, 2,208,229, 6, 5, 5, 5, 2, 2, 2,229,208,
+208,229,229, 6, 2, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 6,
+208,208, 5,227,208,208,208,208, 5,216,216, 6, 6, 6,208, 6,
+
+// state[669 + 2] 0x022ec0 Byte 4 of 4 (property)
+ 6,208,208,229,208, 6, 6, 6, 208, 6,208, 6, 6,208,208,208,
+ 5, 5, 6, 6,229,208, 5,208, 6, 6,208,208, 6,208,208, 6,
+208, 5, 6, 6, 6,208,208,208, 6,208,229, 6,208,208,208,208,
+ 6, 5,208,208,208, 6,208,208, 6,229,208,208, 6,208,208, 6,
+
+// state[670 + 2] 0x022f00 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6,208, 5, 5, 5,208,208,208,208,208,208,208,
+208,229, 6, 6, 5, 5, 6, 6, 208, 6, 6,219,208,208,208,208,
+208,208,208,208,219,208, 6,208, 208, 6,208, 6, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6,208, 6, 6,219,208,208, 6,208, 6,208,
+
+// state[671 + 2] 0x022f40 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208, 5, 5, 6, 6, 6, 5,
+ 5, 5, 5, 5, 5,208, 6,208, 208, 2, 6,208, 6,208,208,229,
+208,208, 6,219,208,208,208,208, 208,208,208, 6,208,208,208,208,
+208, 6, 5, 5, 5, 5, 5, 5, 6, 6,208, 6, 2, 6, 6,208,
+
+// state[672 + 2] 0x022f80 Byte 4 of 4 (property)
+208, 6,208,208,208,229,208,208, 208,208,219,208,229,208, 6,208,
+208, 6,208,208,208, 6, 6, 6, 6,217, 5, 5, 5, 5, 5, 5,
+208, 6, 6, 5, 5,208, 6,208, 208,229,208,208,229,208,229,208,
+ 6,229, 6,208,208,208,208,208, 208,208,208, 6, 5, 5, 5, 6,
+
+// state[673 + 2] 0x022fc0 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 3, 6,229,229, 208,229,208,229,208,208,208, 6,
+208,208,208,208,208, 5, 5, 5, 229, 5, 6,229, 6, 0,208,208,
+208,208,219,208,208,208, 6, 6, 208,208,208,229,208,208,208, 6,
+ 6,219, 5, 5, 5, 5, 5,208, 208,208, 6, 6, 2, 5,208,208,
+
+// state[674 + 2] 0x023000 Byte 4 of 4 (property)
+229,229,208,219, 6, 6, 5, 6, 6, 5, 5, 5, 5, 6,208,208,
+208,208,229, 6,229,208,208, 6, 229, 6,208,219, 6,208,208, 2,
+ 5, 5, 5,208,208,208,208,208, 208, 6,208,208,208,208,229,208,
+ 6, 5, 6,229, 5,208,208,208, 6, 6, 5, 6, 5, 5, 6, 6,
+
+// state[675 + 2] 0x023040 Byte 4 of 4 (property)
+ 6, 5, 5, 5,208, 3, 5, 5, 5, 6,208,208, 5, 6,229,208,
+ 5, 5,208, 6, 2, 5, 6,208, 5,208, 5, 6,208,208,208,229,
+ 5, 5,208,208, 5, 5,208, 5, 6, 5, 6,208,208, 6, 5, 6,
+ 6,208, 2,229,208,208,208,208, 5, 6,208,208,229, 5, 5,208,
+
+// state[676 + 2] 0x023080 Byte 4 of 4 (property)
+208,229,208, 2,208, 5,229, 6, 6,229,229, 2,229, 6, 5,208,
+ 6,208, 6, 5,208,208, 6, 6, 6,208, 6, 5, 6, 6,208,208,
+208,208,208,208,208,208,229,229, 208, 6,229,208, 6,208,208,208,
+ 6,208,208, 6, 6,208,208, 0, 5, 5,229,208, 0,208, 6,208,
+
+// state[677 + 2] 0x0230c0 Byte 4 of 4 (property)
+208,229,229,208, 6, 6,208, 6, 208, 6,208,208,208,208, 6,208,
+208,208, 6, 5,229, 6,208, 6, 208,229,208, 5, 5,208,208,229,
+219, 5,208, 2, 2, 3,208,208, 208,208,208, 5, 5, 5, 5,208,
+ 6, 2, 2,208, 6,208, 6, 5, 5, 5, 2,208,229,229,208,208,
+
+// state[678 + 2] 0x023100 Byte 4 of 4 (property)
+229,208, 5, 5, 5, 6, 6, 2, 208,208,208, 5, 5, 6,208,229,
+229,208, 2, 6,208, 6, 2, 5, 208,229,229, 6, 6,208,229,208,
+229,229,229, 6,208, 2,208,208, 208, 6,208, 5, 2, 6,208, 6,
+208,208,208, 6,208,208,208,208, 208, 5, 6,208,229, 5,229,208,
+
+// state[679 + 2] 0x023140 Byte 4 of 4 (property)
+229, 5, 5, 6,208, 2,229,229, 229,208,208,229,208, 6,208, 6,
+ 5, 5, 5, 5, 6, 6,208, 2, 2, 2,229,208,229,208,208,208,
+229,229,229,229,208,208,208, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6,208,208, 0,208, 2, 2,208, 229,229,208,208,208,229,208, 6,
+
+// state[680 + 2] 0x023180 Byte 4 of 4 (property)
+ 6, 5, 5, 5, 5, 5, 5, 6, 6,208,208,208, 6, 6, 6, 2,
+ 2,219,208,208,208,208,208,208, 208,229,208,229,208,208,208, 6,
+208, 6, 6, 5, 5, 5, 5,208, 208,208, 6,208, 6, 6, 2, 2,
+ 2, 2,229,229,208,208,228,208, 208,229,208,208,208,208,208,208,
+
+// state[681 + 2] 0x0231c0 Byte 4 of 4 (property)
+ 5, 6, 6, 4, 4, 6, 5, 6, 5, 5, 2, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6, 6, 6, 6,208, 3, 2, 2, 2, 2,
+ 2, 2,208,208,208,208,208,208, 208,208,208,227,229, 6,208,208,
+208,208,229,208, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[682 + 2] 0x023200 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 6, 0,208, 5, 6, 6, 6, 6,208, 6,208,
+ 6, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,
+208,208, 2, 6,208,208,208,208, 208,208,208, 6, 6, 6, 5, 0,
+ 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+
+// state[683 + 2] 0x023240 Byte 4 of 4 (property)
+ 5, 5, 5, 5,208, 6, 6, 6, 208, 6,229, 6, 6, 2, 2, 2,
+ 2,208,229,208,229,208,208,208, 229,208,208,208,208, 6, 6, 6,
+ 6, 6, 5, 5, 5, 5, 5, 5, 5,208,229,208,208,208, 6, 6,
+229, 6, 2, 2, 2, 2, 2, 2, 2, 2,208,229,229,208,229,229,
+
+// state[684 + 2] 0x023280 Byte 4 of 4 (property)
+ 6,229,208,208,208, 6, 5, 6, 6, 5, 5, 2, 2, 2, 2, 5,
+ 5, 5, 6,219,208,208,208, 3, 2,229,208,208, 6,208,208,229,
+208,208, 6,208,208,208,208,208, 208, 6, 6, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5,208, 6,208, 6, 208, 6,208, 6, 6,208, 2, 2,
+
+// state[685 + 2] 0x0232c0 Byte 4 of 4 (property)
+ 2, 2, 2, 2,208,208, 2, 2, 5,229,229,229,208,208, 6,208,
+208, 2, 5, 5, 5, 5, 5,208, 208, 6, 2, 2, 2, 2,229,208,
+ 5, 5, 6,208, 6,208, 6, 6, 2, 2, 5, 5, 5, 5, 5,208,
+ 2, 2,229,208,208,208,208,208, 208, 6, 5, 2, 2, 2, 2,229,
+
+// state[686 + 2] 0x023300 Byte 4 of 4 (property)
+ 5, 2, 2, 5, 5,208, 2,208, 229, 2, 5, 2, 2,229, 5,208,
+ 6, 6, 2,208, 2, 6,208,229, 208, 5, 6, 2,229, 2, 6,229,
+ 6, 6, 5,208, 5, 5,208,208, 208, 5, 5, 5,208,208, 6, 5,
+ 5, 5, 5, 5, 2, 2, 2, 5, 5, 2, 2, 5,208,208,229, 6,
+
+// state[687 + 2] 0x023340 Byte 4 of 4 (property)
+ 2, 2, 2,229,208, 2,229,208, 208, 6, 2, 2, 5, 2, 6,208,
+ 6,208, 5, 5, 5, 2,229, 2, 208,208, 6,208,208,208,208,229,
+208,208,208,208,208,208,208,208, 6, 6, 6, 5,208, 6,229, 6,
+ 6, 6, 4, 5,208,208,208,208, 208,208,229, 6, 5, 6, 6, 6,
+
+// state[688 + 2] 0x023000 Byte 3 of 4 (relative offsets)
+-14,-13,-12,-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2,
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+
+// state[689 + 2] 0x023380 Byte 4 of 4 (property)
+ 2, 5, 5, 2,229,229, 6, 6, 6, 5,216, 5,208,208,219, 2,
+ 5, 6,229,229,229, 6, 2, 5, 5, 5,208,229,229, 6, 2, 5,
+ 5, 5, 2,227,208, 5, 2, 6, 6,208, 6, 6, 5,208,229, 6,
+ 6,208,229,229,229,229, 5, 2, 229,208, 5, 5,217, 5,219,208,
+
+// state[690 + 2] 0x0233c0 Byte 4 of 4 (property)
+208,208,229,208, 6, 5, 5, 5, 5, 5, 5, 5,208, 6, 6, 3,
+233,208, 0, 4,229,228,229,208, 208,229,228,208,229,229,208,233,
+229,208, 6, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,208,208, 5, 5,208, 6, 208,208,208,208, 6,208,223, 2,
+
+// state[691 + 2] 0x023400 Byte 4 of 4 (property)
+ 5,208,219,208, 2, 2, 2, 5, 208,208,208,208,208,229,208,208,
+208,229,208,208, 6, 6, 6, 5, 5, 5, 5, 5, 5,216, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
+208, 6, 2, 2, 2,208,208,208, 208,229,208,208,208,208,208,229,
+
+// state[692 + 2] 0x023440 Byte 4 of 4 (property)
+208,208,208,229,208, 6, 6, 6, 6, 6, 4, 4, 2, 6, 2, 5,
+ 5,194, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5,228, 6, 6, 208, 6,208,208, 6,208,208,208,
+ 6,229, 5, 2, 2, 2, 2, 2, 229,208, 2, 2, 6, 5, 5,229,
+
+// state[693 + 2] 0x023480 Byte 4 of 4 (property)
+229,208,208,208,208,208,229,229, 208,208, 6, 6, 6, 6, 3, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+216, 5, 5,216, 5, 5, 5, 5, 208,208,208,229,208,208, 6,208,
+ 6, 2, 2, 2, 2, 2,208,208, 208,208,208,208,229, 6, 2, 5,
+
+// state[694 + 2] 0x0234c0 Byte 4 of 4 (property)
+208,208,229,229,208,208, 0,208, 229,229,208, 0,229,208,208,229,
+208,208,229,208,208,208,208,208, 208,229,208,208,208,208,208,208,
+208,208, 6, 6, 4, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,216,
+
+// state[695 + 2] 0x023500 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,
+ 6,208,208,227,208,208,208, 6, 6,208, 6, 6, 6, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6,208,229,229,208,208,
+ 5,229,227,229,208,208,208,208, 208,229,229,229,208,208,208,229,
+
+// state[696 + 2] 0x023540 Byte 4 of 4 (property)
+208,208,229,229,208,229,208,229, 208,208,229,229,229,208,208, 6,
+208,208,229,208, 6, 6, 6, 6, 2, 5,194, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,216, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,208,
+
+// state[697 + 2] 0x023580 Byte 4 of 4 (property)
+229,208,208,208, 6, 6, 6,208, 6, 6,208, 6, 6, 6, 6, 6,
+208, 6, 6, 5,215, 5, 2, 2, 2,216, 2, 2,216, 2, 2, 2,
+ 2, 2, 2,208,208,208, 5, 2, 208, 5,208,229,208,229,208,208,
+208,229,208, 6,208,208,208,229, 229,208,229,229,208,208,208,229,
+
+// state[698 + 2] 0x0235c0 Byte 4 of 4 (property)
+ 6,208,208,208, 4, 6, 6, 6, 6, 6, 6, 6, 2, 5, 5, 5,
+229, 2, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5,
+ 5, 2, 6,229,208,208, 6,208, 208, 6,208,208,218, 6, 2, 2,
+
+// state[699 + 2] 0x023600 Byte 4 of 4 (property)
+ 5, 2, 2, 2, 2, 5, 2, 5, 2, 2, 2, 5, 2, 5, 5, 5,
+208,208,208, 2, 5,208,208,229, 229,208,208,229,208,229,208,208,
+208,208,229,208,229,208,229,229, 229,208,208,208,229,208,208,208,
+208,208,229,229, 6, 6, 6, 6, 4, 4, 4,229, 5, 2, 2, 5,
+
+// state[700 + 2] 0x023640 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5,194, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 0, 6,227, 6,208, 6, 6, 6,208,
+ 6, 6, 6, 6,229, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+229,208,208,208,229,208,208,227, 2, 2, 2,229,208,208,208,208,
+
+// state[701 + 2] 0x023680 Byte 4 of 4 (property)
+208,208,208,229,229,208,208,229, 208,208,208,229,208,208,229,208,
+229,208,208, 6,229,229,208,208, 229,229,229,208,229,229,208,229,
+229,208,208,229,208,208,208, 6, 208,229,208, 6, 6, 5, 2, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[702 + 2] 0x0236c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 6, 6, 6, 6,208, 6, 208, 6,208,219, 6, 6, 3,216,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208, 5, 2, 5, 2,
+ 5,227,208,208,229,229,208,208, 208,208,208,229,208,208, 6,208,
+
+// state[703 + 2] 0x023700 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208, 6, 6, 2, 4, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,194, 5, 5, 5,
+ 5, 6, 6,208,208,208,208,208, 208, 6, 6, 6,208,208, 3, 5,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2,208,208, 2, 5, 6, 2,223,
+
+// state[704 + 2] 0x023740 Byte 4 of 4 (property)
+ 2,229,208,208,208, 0,208,208, 229, 0,229,208,229,229,208,229,
+208,229,208,229,229,229,208,208, 208,208,229,208,229,208,208, 6,
+ 6, 6, 6, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5,208, 6,208, 6,208,208, 6,208, 6,
+
+// state[705 + 2] 0x023780 Byte 4 of 4 (property)
+ 6, 5, 2, 2, 2, 2,208,208, 6, 5, 2,217, 5, 2, 2, 6,
+208,229,208,208,208,208,208,229, 208,229,208,208,208,208,208,208,
+ 6, 6, 5, 5, 5, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5,
+208,219,208, 6, 6,208,227,208, 6, 6, 6,227, 5, 2, 2, 5,
+
+// state[706 + 2] 0x0237c0 Byte 4 of 4 (property)
+219, 6,216, 2,229,208,208,208, 208,229,229,208,229,208,208,208,
+208,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 6, 6, 6,172, 208, 2, 2, 2,238, 2, 2,208,
+229,172,208,229,229,229,229, 6, 208,208,208,208,208,229,208, 4,
+
+// state[707 + 2] 0x023800 Byte 4 of 4 (property)
+ 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 6,208,208,
+208, 2, 2, 2,208, 2, 2, 2, 2,208,208,229,229,229,208,229,
+229,208, 6, 6, 4, 5, 5, 5, 5,216, 5, 5, 5,208,208, 6,
+ 2, 2, 2, 2, 6, 5, 2,229, 229,229, 0,208,208,233,208,208,
+
+// state[708 + 2] 0x023840 Byte 4 of 4 (property)
+208, 6, 6, 5, 5, 5,208, 6, 6,208,208, 6,208,208,208, 5,
+ 5, 5, 5,216, 5, 6, 6, 6, 208, 2, 2,229, 5, 2,208,219,
+208, 6, 5,208, 2,208, 6,208, 5,229,208,208, 6, 5, 5, 2,
+ 6, 5, 5,208,208, 6,229,208, 208, 2,208, 5,208, 6, 6, 6,
+
+// state[709 + 2] 0x023880 Byte 4 of 4 (property)
+208,229,208,208,208,229,208,229, 6,208,229,229,208,229,208,208,
+229,229,229,208, 5, 6, 6,229, 229,208,208,229,229,229,229,229,
+229,208,208, 5, 6, 6,208,208, 208,208,229,219,229,208,208,229,
+229,229,229,208,208, 5, 6, 6, 6, 2,229,208, 6,208, 6, 5,
+
+// state[710 + 2] 0x0238c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 6, 229,229,229,208,229,229,229,229,
+208,229,229,208, 6,208, 5, 5, 5,208, 6, 6, 6,208,229,229,
+208,208,208,208,208,208,208,208, 229,208, 6, 5,208, 6, 6,208,
+208, 5,229,229,208,208,208,229, 208,229,208,208,208,208,208,208,
+
+// state[711 + 2] 0x023900 Byte 4 of 4 (property)
+208,229,208,208, 5, 5, 6,208, 229,208,229,229,229,208,208,208,
+208,208,208, 6, 5, 5, 6,208, 229,208,208, 6,208, 5, 5, 6,
+208,208,229,208, 5,208, 6, 6, 219,208, 6, 6,208, 5, 5, 6,
+ 6,229,208, 6,208,229,208,208, 6,229,208,208,208, 6, 5,208,
+
+// state[712 + 2] 0x023940 Byte 4 of 4 (property)
+208,208,208,229,208,208, 6,229, 208,229,229,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 208,208, 6, 5, 5, 5, 6, 6,
+208, 6,208,208, 6,208,229,229, 208,208,208,208, 6,208, 6, 6,
+ 6, 2,208,229,208, 5, 6, 6, 208,229,229,208,229,208,229,208,
+
+// state[713 + 2] 0x023980 Byte 4 of 4 (property)
+ 6,208,208,229,208,208, 2,229, 208,208, 5,208, 6, 2, 2, 6,
+ 6, 6, 6, 2,208,208,208,229, 5, 5, 5, 2,208,208,208,208,
+ 6,208,229, 6, 5, 5,229,208, 208, 6, 6, 6, 6, 6, 2,208,
+ 2,208, 5, 6, 6,229,229,229, 208,229,208,208,208,208, 6,208,
+
+// state[714 + 2] 0x0239c0 Byte 4 of 4 (property)
+208,208,229,208,229,208,208,208, 6,208,208,229,208,229,208,208,
+208,229,208,208,208,208,208, 6, 6, 6, 6, 6, 6,229,229,229,
+229,229,208,229,229,208,229,208, 208,229,208,208,208,208,208, 5,
+208, 5,229,229,208,208,208,217, 229,229, 6,229,208,229,229,208,
+
+// state[715 + 2] 0x023a00 Byte 4 of 4 (property)
+229,208, 6,208, 6,229,208,208, 208,208,229,208,208,229,208,208,
+208,208, 5, 6, 6, 2,208,208, 229,229,208,229,208,208,208,229,
+229,229,208,208,229,208,208, 2, 5, 5, 6, 6, 6, 2, 2, 5,
+ 2,208,208,229,208,208,229,208, 208,208, 6,208, 6, 5, 6, 6,
+
+// state[716 + 2] 0x023a40 Byte 4 of 4 (property)
+ 6, 2, 2, 5,208,229,229,208, 229,208, 6,208, 5,229,208,208,
+208,208,208,208,208,229,208,208, 5,208, 6, 6, 6,208,208,208,
+208,229,208,208, 5, 5,218, 6, 208,208,229,208,208,229, 6,208,
+229,229,208,208,229, 6,208,229, 2, 6,208,229,229, 6,208,229,
+
+// state[717 + 2] 0x023a80 Byte 4 of 4 (property)
+229, 6,229, 5, 6,208,208, 6, 208, 6, 6,208,208,208,229,208,
+208, 5, 5, 5, 5,208,208,208, 172,208, 6,217, 5, 6, 6, 5,
+229,208, 6,208,208, 5, 5, 5, 6,208,208, 6,229,208,229,229,
+208,208,208,208, 6, 5,229,229, 208,208, 6,208,208,208,208,208,
+
+// state[718 + 2] 0x023ac0 Byte 4 of 4 (property)
+208,208, 6, 6, 6, 5, 5, 5, 6, 6, 6, 6,208, 5, 6, 6,
+208,208,229,208, 6, 2,208, 6, 208,208,208, 5, 6, 6,229,208,
+ 6,208,229,208,208,208,208,229, 229,208, 6,208,208,208, 5,208,
+208, 6, 2, 6,208, 6, 6, 2, 5,208,208, 6, 5, 5, 5, 5,
+
+// state[719 + 2] 0x023b00 Byte 4 of 4 (property)
+ 5, 5,208,208,208,208,208, 6, 208,208,208,208,208,208,208,208,
+208,208, 5,208,208,208,208, 5, 6, 6,208,208,208, 6, 5, 5,
+ 6,208,208,208, 5, 5, 5, 5, 5,229,229,229,208,229,208, 5,
+ 5, 5, 5, 5, 6,208, 6, 6, 229,229,229,229,229,229,208,229,
+
+// state[720 + 2] 0x023b40 Byte 4 of 4 (property)
+208,229,208, 2,208,208, 6,208, 6, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 5, 2,229,229,208,208,
+229,229,208,208, 6, 5, 5, 5, 5, 5, 5, 5, 5,208,208, 2,
+ 2,229,208,208,229,208,229, 0, 208,229,208,227,208, 5, 5, 5,
+
+// state[721 + 2] 0x023b80 Byte 4 of 4 (property)
+ 5, 5, 5, 6, 6, 6, 6, 2, 208,229,229,208,208,208,208,208,
+229,208,208,208,229,208, 6,208, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6,208,208, 6, 6,229, 208,208,229,208,229,208,208,208,
+208,208,208,208,208, 6, 6, 6, 6, 5, 5, 5, 5, 5, 6, 6,
+
+// state[722 + 2] 0x023bc0 Byte 4 of 4 (property)
+ 6, 6,208, 6, 6,229,229,229, 229,208,208,229,229,229,208,208,
+219,208,208,208, 6,208, 5, 5, 5, 5,219,208,229,208, 6,208,
+ 6, 2,208, 6,208,208,208,208, 229,229,229,208,208,229,208,208,
+208,208,208, 6, 6, 6, 6, 5, 208,208,208,229,208,208,208, 5,
+
+// state[723 + 2] 0x023c00 Byte 4 of 4 (property)
+ 5, 5, 5, 6, 6, 6,229, 6, 6,217,208,229,229,208,208, 2,
+208, 5, 5, 5,208, 6,208, 6, 6, 2,208,229,229, 6,208,208,
+229,208, 5, 5, 5, 6, 6, 6, 229, 6, 5,229,208,208,208, 6,
+ 6, 5,208, 2, 2, 2,229,229, 208, 5, 6,208,229, 6, 5,208,
+
+// state[724 + 2] 0x023c40 Byte 4 of 4 (property)
+208, 6, 6, 6,208,229, 2,208, 229,208,208,208, 5, 6,208,208,
+229,208,208,229, 5,208,229, 6, 5,229,208,208,208, 6, 5, 5,
+ 5,208, 6, 5, 5, 5,208, 6, 3, 6,208,208, 2, 2,208,208,
+ 2, 6, 5, 6,208,229,227,208, 217, 5, 6, 6,218,218, 2,214,
+
+// state[725 + 2] 0x023c80 Byte 4 of 4 (property)
+229,208,229,208,229, 6,208, 5, 5, 5, 6,208, 6, 2,208,208,
+208,229,229,208,208,229, 6, 6, 6, 5, 5, 5, 6,208,208,208,
+227,208, 6, 6, 6,208, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6,208,229,208,208,208,229, 208,227,208,208,229,208,208,208,
+
+// state[726 + 2] 0x023cc0 Byte 4 of 4 (property)
+208,208,208, 6,208,208, 6, 0, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5,216, 5, 5, 5, 208,229,229,208, 6,208, 6, 6,
+ 6, 2, 2,208,208,208,208,208, 208,208,227,208,229,229,227,208,
+208,208,229,208,208,208,208,208, 6, 6, 6, 6, 5, 5,194, 5,
+
+// state[727 + 2] 0x023d00 Byte 4 of 4 (property)
+ 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,194, 5,
+ 5,229,208,218,208,208,208, 6, 3, 2, 2, 2, 2, 5,229,208,
+208,208,208,229,229,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208,229, 208,208,229,208,229, 6,208, 6,
+
+// state[728 + 2] 0x023d40 Byte 4 of 4 (property)
+238, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 6,
+219, 6,208, 6,208, 6,208, 6, 208,208, 6, 6,208, 6, 2, 2,
+ 2,208, 2, 5, 2, 2, 2,229, 208,208,229,229,208,229,208,208,
+
+// state[729 + 2] 0x023d80 Byte 4 of 4 (property)
+208,208,229,208,208,208,229,229, 227,229,229,208,208,208,208,229,
+229,208,208,229,208,208,208, 6, 229,208,208,208,208,208,229,208,
+ 6,208,229,208,208,229,229, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[730 + 2] 0x023dc0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,194, 5, 5, 5, 5, 5, 5, 5,208, 6,229, 6,208,
+218,208,208,208,208, 6,208,208, 6,208, 6, 6, 6, 2, 2, 2,
+ 2, 2, 2, 2, 2,208,208, 2, 5, 4, 4, 5,208,229,229,208,
+
+// state[731 + 2] 0x023e00 Byte 4 of 4 (property)
+229,208,208,208,229,229,208,229, 229,229,208,229,229,229,208,208,
+229,208,208,208,229,208,208,229, 208,208,208,208,208,208,208,208,
+229,208, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[732 + 2] 0x023e40 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 6,208,229, 6,208,208, 6,208,208,208, 6,
+ 6, 6, 6, 2, 2, 2, 2, 2, 2, 2,208, 5, 6,208,229,227,
+208,229,229,208,208,208,229,229, 208,229,208,208,208,208,229,208,
+ 6,208,229,208,208,208,229,208, 229,208,208,208,208,208,208,208,
+
+// state[733 + 2] 0x023e80 Byte 4 of 4 (property)
+208,208, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6, 6,229,
+229,208,208, 6,208, 6,208, 6, 6,208, 2, 2, 2, 2, 2, 5,
+
+// state[734 + 2] 0x023ec0 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 6,217,208, 6,229,
+208,229,208,208,208,227,208,227, 229,208,208,229,229,208,229,208,
+208,208,229,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 2, 2, 5,
+
+// state[735 + 2] 0x023f00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,216, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,229, 6,208, 6,208,
+208,229,208,208, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 5, 2, 2, 2, 2, 2,208, 2, 2, 2, 5,
+
+// state[736 + 2] 0x023f40 Byte 4 of 4 (property)
+ 2, 5, 6,229,208,208, 0,208, 229,229,227,208,229, 6,229,208,
+208,208,229,229,208,229,208,208, 208,208,208,229,208,229,208,229,
+208,208,208,208,229,208,208,229, 208,208,208,208,208,208,208,208,
+208, 6, 6,208,229,208,208, 6, 6, 6, 6, 6, 6,208, 4, 5,
+
+// state[737 + 2] 0x023f80 Byte 4 of 4 (property)
+ 5, 5, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,208, 6, 6, 6, 6, 229,208,208,208,208,208,208, 6,
+208, 6, 6, 6,217, 2, 2, 5, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[738 + 2] 0x023fc0 Byte 4 of 4 (property)
+ 5, 5, 6, 2, 6, 0,208,227, 229,229,208,208,227,229,208,208,
+229,208,229,208,229,229,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[739 + 2] 0x024000 Byte 4 of 4 (property)
+ 5, 5, 6, 6,208,208,208,208, 208, 6, 6,229, 6, 6,208, 2,
+ 2, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 2, 5,
+ 5, 5,208,229,229,208,229,208, 208,229,229,229,208,208,208,208,
+ 0,208,229,208,208,208,208,208, 6, 5, 5, 5, 5, 5, 5, 5,
+
+// state[740 + 2] 0x024040 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208, 6,208,208, 6, 6,
+208, 3, 3, 2, 2, 2, 2, 5, 2, 2, 5, 2, 5, 5, 5,208,
+ 2,229,229,208,208,208,229,229, 208,227,229,229,229,229,229,229,
+229,208,229,229,208,229,208,229, 208,208,208,208,208,229,229,208,
+
+// state[741 + 2] 0x024080 Byte 4 of 4 (property)
+229,208,208,208,208,208,208,208, 6, 6, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,194, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6,208,229,229, 6,208,229, 2, 2, 2, 2, 2, 2, 2, 3, 5,
+ 5,216,208,208,208,208,208, 0, 208,208,208,229, 6,208,208,208,
+
+// state[742 + 2] 0x0240c0 Byte 4 of 4 (property)
+208,208,208, 6,208, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5,208, 6,208, 208,208,208, 2, 2, 2, 2, 2,
+ 2,216, 2,208, 2, 2, 6, 2, 219,229,208,229,208,208, 6,208,
+208,208,208,219,208,208,229,229, 208,208, 6,208,208,208,208, 6,
+
+// state[743 + 2] 0x024100 Byte 4 of 4 (property)
+ 6, 6, 6, 4, 5, 5, 5, 5, 6,208, 6, 2, 2, 2,229,229,
+229,208, 6,208,208,208,229,229, 208,208,208,208,208,208, 5, 5,
+ 5, 5, 5, 5, 5, 6,208, 6, 208,208,208,208,208, 2, 2, 2,
+ 2, 2, 2, 5, 5,208,229,229, 5,208,208,208, 6,208, 0, 5,
+
+// state[744 + 2] 0x024140 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5,208,208,208, 6, 2,208,208,208,208,208, 6,
+ 6,208,208, 6, 6, 5, 5, 5, 5, 5, 5,208,216,208, 6, 5,
+208,208,208,208,208, 6,208, 2, 5,229,208,208, 6, 5, 5, 6,
+208,208,229,208,208, 6, 2,208, 208,208, 5, 5,208,208, 5,208,
+
+// state[745 + 2] 0x024180 Byte 4 of 4 (property)
+229, 6,208,208,208,208,208, 6, 216, 5, 5,208, 6,208,208,229,
+208,229,208, 6, 5, 5, 5, 5, 5, 6,208, 3, 6,229,229,229,
+ 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,
+208,208,208, 6,208,208, 6, 2, 2, 2, 2, 2,229,229,229,208,
+
+// state[746 + 2] 0x0241c0 Byte 4 of 4 (property)
+208,208, 6, 6, 6, 6, 4, 5, 5, 5,216, 5,216, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,208, 6,208, 6,208,
+208,208,216, 2, 2, 2, 2, 2, 2, 2, 2,208, 6, 6, 2,229,
+208,208, 0,227,208,208,229,208, 6, 6, 6, 6, 5, 5,233, 5,
+
+// state[747 + 2] 0x024200 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5,208,208, 6, 6,208,208, 6, 208, 6, 6, 5, 2, 2, 2, 2,
+ 2, 2,208,229, 2,208,229,229, 229,208,227,208,208,208,208,208,
+208,208,208, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5,
+
+// state[748 + 2] 0x024240 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 5, 6, 6, 6, 6,
+ 6,208, 2, 2, 2, 2,216, 2, 2, 5, 2, 2, 2,208, 6,208,
+ 2,208,208,208,208,208,208,208, 6,208,229,208,229,208,208,208,
+ 6, 6, 6, 6, 6,219, 5, 5, 5,217, 5, 5, 5, 5, 5, 5,
+
+// state[749 + 2] 0x024280 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,216, 5, 5, 5, 5,208, 208,208, 6,208,208, 6, 6, 6,
+ 2, 2, 2, 2, 2,216, 2, 2, 2, 2, 2, 2, 2, 2, 6,208,
+ 2, 6,208, 6,229,208,208,208, 229,229,208,229,229,229,229,229,
+
+// state[750 + 2] 0x0242c0 Byte 4 of 4 (property)
+208,208,229,208,208, 6,208,208, 6, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 208,208,208, 6,208, 6,208,208,
+208,229, 6,208, 6, 2, 2, 2, 2, 2, 5, 2, 2, 2, 2, 2,
+
+// state[751 + 2] 0x024300 Byte 4 of 4 (property)
+ 2,208, 6,208, 2, 2, 6,208, 208,208,229, 0,208,229,208,229,
+229,208,208,208,208,208,208, 6, 6, 6, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 208,208,208,208,208,208, 6, 6,
+
+// state[752 + 2] 0x024000 Byte 3 of 4 (relative offsets)
+-13,-12,-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+
+// state[753 + 2] 0x024340 Byte 4 of 4 (property)
+ 6, 6, 6, 2, 2, 2, 2, 2, 5, 2, 2, 2, 2,208, 5, 5,
+229, 2,219,229,208,208,229,229, 208,229,208,208,229,229, 6, 6,
+219, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 6,208,208,
+
+// state[754 + 2] 0x024380 Byte 4 of 4 (property)
+ 6, 6,208, 6,208,208, 6, 6, 6, 6, 6, 6, 5, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 5,208, 208, 6, 5, 2,216,208,208,208,
+208,229,208,229,208,229,208,208, 208,229,208,208,208,208,208,208,
+208,208,208,208,208,208, 6,208, 208, 6, 6, 6, 4, 5, 5, 5,
+
+// state[755 + 2] 0x0243c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6,208, 208,208,208,208, 6,208,208,208,
+208, 6, 2, 2, 2, 2, 2, 2, 2, 5,216, 2, 2,208,208,208,
+ 5, 6, 5, 6,208,208,229,208, 0,208,208,208,208,208,208,208,
+
+// state[756 + 2] 0x024400 Byte 4 of 4 (property)
+229, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,208, 2, 6,208, 6,208, 6, 6, 2, 2, 2, 2,229,208,
+ 2, 2, 5, 6, 6, 6,208,229, 208,208,208,229,208,208, 6,208,
+229,229,208,208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[757 + 2] 0x024440 Byte 4 of 4 (property)
+ 5, 5,208,208, 6,208,208, 6, 2, 2,208, 5, 6, 2, 5, 5,
+ 5, 6,208,208,217, 6,208,208, 6, 6, 5, 5, 5, 5, 5,216,
+ 5, 6,208, 6, 6, 6, 6,208, 208, 6, 6, 2, 2, 2, 2, 2,
+ 5,208, 5,229,208,208,229,229, 229,229,229, 6,208,208,229,229,
+
+// state[758 + 2] 0x024480 Byte 4 of 4 (property)
+208,208,208, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6, 6,208, 6, 6, 6,208, 2, 2, 2, 2,208, 6,208, 2,
+ 2,208,227,208,208, 6, 0,208, 6, 6, 6, 5,208, 6,208, 6,
+ 6, 6, 2, 5, 2, 5, 5, 5, 5, 5,229,208,208,208,208, 6,
+
+// state[759 + 2] 0x0244c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5,208,208, 2, 2, 208,229,208, 6, 6, 6, 5,208,
+208, 6,208, 5,208,208, 5,208, 6, 5,208, 2,208,208, 5,208,
+208,208, 2, 6, 6,208,229, 5, 5, 2,208,208,208,229,208,208,
+ 6,208,208,208,208, 6,208,208, 208, 6, 2, 5, 5,208,208, 6,
+
+// state[760 + 2] 0x024500 Byte 4 of 4 (property)
+208,208, 5, 5, 5, 5, 5,208, 6, 6,208, 6,208, 6, 5, 5,
+208,208,208, 5,229,208, 6,208, 208, 6, 6, 2,208, 6, 6, 6,
+208, 5,208,208,208,208,208,208, 229,208,208, 6,208, 6,208, 2,
+208,208,208, 5, 5, 5, 6, 2, 5,208, 6, 2, 6, 2, 2, 6,
+
+// state[761 + 2] 0x024540 Byte 4 of 4 (property)
+208, 6, 5, 5, 6,208, 6, 6, 5,208, 6,208, 6,208, 5, 5,
+208,208, 6,229, 2,208, 5, 5, 208, 5,208,208, 5,208,229,229,
+229, 5,208,229,229,208,208,208, 229,208,208, 5, 6,208,208,208,
+208,208,208,208,208,208,208,229, 0, 5,208,208,208,208,208, 6,
+
+// state[762 + 2] 0x024580 Byte 4 of 4 (property)
+ 6, 5, 5, 5, 6,208,229,208, 208,208, 5,208,208,208, 5,208,
+208, 5, 6,208, 5,208,229,229, 208,208,208,208,208,208,229, 6,
+229, 2,229,208,208, 5,208,208, 5,208,208,208,208,229,208,208,
+208,229,208,229,208, 6, 2,208, 2, 2,208,208,229,208,208, 6,
+
+// state[763 + 2] 0x0245c0 Byte 4 of 4 (property)
+208,208,208, 6,229,208, 2,208, 208,229,208,208,208,229,229,208,
+ 3, 5, 6, 6, 6, 6, 2,208, 229,208,208,229,208, 6, 6, 6,
+208, 5,208,208, 2,208,208, 5, 208,208,229,208,229,208,208,229,
+ 6,208, 2,208,229,229,229,229, 208, 2,208,229, 5, 5, 6,229,
+
+// state[764 + 2] 0x024600 Byte 4 of 4 (property)
+229, 2, 2,229, 5,229, 6, 6, 208,208, 6, 2,229, 5, 5, 5,
+229,229, 6, 2,208, 5,208, 5, 208, 5,208,208,229,208,229,208,
+229,229,229,229,229,208,229,229, 6, 4, 5, 5, 5, 5, 5, 5,
+ 5, 6,208,208,208, 6, 6, 6, 229,229,208,229,208,229,229,208,
+
+// state[765 + 2] 0x024640 Byte 4 of 4 (property)
+208, 6, 5, 5, 5, 6, 6, 6, 6,208,208, 6,208,208, 6,229,
+ 5,229,229,208,208,208, 6,208, 208, 6, 6,229,229,208,229,208,
+208,229,229, 6,229, 5, 5, 5, 208, 6,208,208, 6, 0, 2, 6,
+229,229,229,229,229,208, 6, 5, 5, 5, 5, 5, 6, 6, 6, 6,
+
+// state[766 + 2] 0x024680 Byte 4 of 4 (property)
+ 6, 6,208,208,208, 6, 2, 2, 208,208,208, 5, 5,229,229,229,
+229,229,208,208,229,208,208, 0, 5, 5, 6, 6, 6, 6,208, 6,
+208, 6, 6, 6, 6,172, 2, 2, 208,229,208,208,208,208,208,229,
+229,208,229,229,208,208,208, 6, 5, 5, 5, 5,208,208,208,208,
+
+// state[767 + 2] 0x0246c0 Byte 4 of 4 (property)
+ 6, 6,208, 6, 6, 6, 6, 2, 5,208,229,208,229,229,229,229,
+229,229, 6, 6, 5, 5, 6, 6, 219, 6, 6,208, 6, 6, 2,229,
+ 2,229,208,229,229,208,208,208, 6, 5, 6,208, 6, 6, 6,208,
+208,208,208,208,208, 6,208, 6, 6,229, 5,229, 6,208,208,208,
+
+// state[768 + 2] 0x024700 Byte 4 of 4 (property)
+ 5, 6,229,208,229,229, 5,229, 208,208, 6,208,229,229, 6,208,
+229,208,208, 6,208, 6,229, 5, 6,208, 5,208,229,208, 6, 6,
+ 2,208,229,229,229, 5, 5, 6, 208, 6, 6,208, 2, 2, 6, 0,
+208,208,229,229,208,229,208,208, 208,208,208,229,208,208,208, 5,
+
+// state[769 + 2] 0x024740 Byte 4 of 4 (property)
+ 5, 5,208,208, 6, 6,229, 6, 6, 6, 6, 2,208,208, 6,229,
+229,227,208,208,208,229,229,229, 229,208,229,208,208, 6, 0,229,
+208,208, 6, 5, 5, 5,208, 6, 208, 6, 6, 2,208, 6,208,208,
+208,208,208,208,208,208,208,229, 208,227,208,208,208,208,208,208,
+
+// state[770 + 2] 0x024780 Byte 4 of 4 (property)
+229,208,208, 6, 5, 5, 5, 6, 208, 6, 6, 6, 6, 6, 6, 5,
+ 2, 2, 2, 2, 2, 2, 2,208, 2,208,229,229,229,208,229,229,
+208,208,229,208, 6,208, 2, 2, 2, 2, 2, 2, 5, 5, 6,208,
+208, 6,229, 6, 6, 6, 6, 6, 208,208, 2, 2, 2, 2,208,208,
+
+// state[771 + 2] 0x0247c0 Byte 4 of 4 (property)
+208, 5, 2,229,208,229,208,208, 208,208,208,229,208,229,229,208,
+208,208, 5, 6,208,208, 6,208, 6,208,208, 2,208,208,229,229,
+208,208,208,208, 0,208,208,229, 208,208,208,229,208,229, 6, 6,
+ 6, 4, 5, 5,208,208,208, 6, 208, 6,208,208, 6,208, 6, 6,
+
+// state[772 + 2] 0x024800 Byte 4 of 4 (property)
+ 6, 6, 2, 2, 2, 2, 2,208, 6, 6, 5, 2, 6,229,229,208,
+229,229,229,208,229,208,208,208, 208,208,227, 6,208,229,229,229,
+208,208, 6, 5,222, 5, 5, 5, 5, 5, 6,208,208, 6, 6, 6,
+ 2, 2, 2, 2, 2,208,218, 2, 6, 0,208,208,208,208,229,208,
+
+// state[773 + 2] 0x024840 Byte 4 of 4 (property)
+208,227,229,208,208,229,229,229, 6, 6, 6, 6, 2, 5,208, 6,
+208,208, 6, 6, 6, 6, 6,208, 208,208, 2, 2,208,208,208,208,
+208,208,208,208,229,229,229,208, 208,208,229,208,229,229, 0, 6,
+208,229,208,208,208,208,208, 6, 6, 5, 5, 5, 5, 5, 6,208,
+
+// state[774 + 2] 0x024880 Byte 4 of 4 (property)
+ 6, 6,208,208, 6,208, 6, 2, 2, 5,208,208,208,208,208,208,
+229,208,229,208,227,229,233,227, 208,208,208, 6,229,208,208, 6,
+208,208,208,208, 2, 2,208,229, 208,229,208, 2, 2, 6, 6,208,
+ 5, 5, 5, 5,229,229,229,208, 208,229,208, 5, 6, 2, 2, 2,
+
+// state[775 + 2] 0x0248c0 Byte 4 of 4 (property)
+ 5, 5, 6,208, 2,208, 0, 6, 5, 5,208,208, 6,229,229, 6,
+ 5,208,208,208,219, 6, 6, 5, 229,208, 2,208,208,208, 6,208,
+229, 2, 5,208,229, 6, 5,208, 208, 5,208,208, 6, 6,208,208,
+229,208,208, 5, 5,208, 6, 5, 208,208,208,229,208, 6, 6, 5,
+
+// state[776 + 2] 0x024900 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 6, 208, 6, 6,208, 5,208, 5,229,
+208,208,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6, 6, 208, 6,208,208,208, 6, 6,217,
+ 2,208,208, 5,208,208,208,229, 208,208,229,208,229, 6, 5, 0,
+
+// state[777 + 2] 0x024940 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 2, 6, 6,229,208,208,229, 208,208,208,208,208, 6, 6, 6,
+ 6, 6, 5,208, 5, 5, 5, 5, 5, 5, 5, 5, 5,227, 6, 6,
+ 6,208, 6, 3, 5, 5, 5,208, 208, 5, 6, 5,208,208,208,208,
+
+// state[778 + 2] 0x024980 Byte 4 of 4 (property)
+208,229,208,208, 6, 5, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+208,208,208, 6,208,208,208, 5, 208,208, 6, 5, 5, 5,229,208,
+208,208,229,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[779 + 2] 0x0249c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 6,229,208, 6,208,208, 6, 6, 6, 5, 5,
+208, 6,208,208, 5, 5, 5,208, 219, 6,229,229,208,229,208,208,
+208, 6,208, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[780 + 2] 0x024a00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,229,
+208, 6,217,208,229,229,208,208, 229,208,208,208,208,208, 6, 6,
+ 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6, 6, 208,208,208,208, 5, 5, 5,219,
+
+// state[781 + 2] 0x024a40 Byte 4 of 4 (property)
+208,208,208,208,229,229,208,208, 208,208,208,208, 6, 4, 0, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,
+ 6, 6, 6,208,208, 5, 5, 5, 229,208,208,208,208,229,208,208,
+208,229,208, 6,208,208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[782 + 2] 0x024a80 Byte 4 of 4 (property)
+ 5, 5, 5,208,208,208,208,208, 6, 6, 2,208,229,208,208,229,
+208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6,208,208, 5, 5, 5, 5, 5,208,208,229,208,208,208, 6,
+ 6, 5, 5, 5, 5, 5, 5, 5, 5,208, 5, 5, 5, 5,208,208,
+
+// state[783 + 2] 0x024ac0 Byte 4 of 4 (property)
+208, 5, 5, 5, 5,208,208, 5, 5,229,229, 6,208,208, 6, 6,
+ 5, 5, 6, 6,229,208, 5, 5, 208, 6, 5, 5, 5, 6, 6, 5,
+ 6, 6,221,208,208,229, 5, 6, 229,229,208,208,229,208, 5,208,
+229,229,229,208,229,229,229,208, 5, 6,208,208,208, 5,208, 2,
+
+// state[784 + 2] 0x024b00 Byte 4 of 4 (property)
+ 2,229,208,208, 5, 6, 6,208, 227,208, 6, 5,208,208,208,229,
+229, 5,208, 6, 6, 6,208,208, 208, 5,208,229,229,208, 6, 6,
+208,208,208,208,208, 6,229, 6, 229,208, 0, 6,229,229,208,229,
+208, 6, 2, 6,208,208,229,229, 2, 5,208,208,208, 6,217,229,
+
+// state[785 + 2] 0x024b40 Byte 4 of 4 (property)
+208,229,208,208, 5, 5, 5, 6, 6, 6, 6, 6,208,229,208,229,
+229,208,229,208,208,208, 4,208, 208, 6, 2,229,229,208,229,229,
+208,208,229,208, 6, 5,208,229, 208,229,208, 6,229, 6, 5, 4,
+ 5, 6, 6,208,208, 2, 2, 2, 2,229,208,229,229,208, 5, 5,
+
+// state[786 + 2] 0x024b80 Byte 4 of 4 (property)
+208, 6, 6, 2, 2,208,208,208, 229,208,229,208, 6,208,208,229,
+229, 2, 2,208,208, 6,208, 2, 227,208,229,208,229,208, 2, 2,
+ 2, 5,229, 6, 6, 2,208,229, 229,208,208, 5, 6,229,208,208,
+229,208,208,208, 6,208, 6,219, 229,208,208,208,208,208, 2, 2,
+
+// state[787 + 2] 0x024bc0 Byte 4 of 4 (property)
+ 5,208, 6, 5, 6, 6, 2,229, 208,208, 2,208,229,208,208, 6,
+229, 2, 6,208,208,229,208,208, 5, 5,208,208, 6,208,208,208,
+208,229,208,208, 5, 6,208,208, 2, 2, 5, 5,208, 2, 5,229,
+ 2, 2,208,229, 5, 5, 2,208, 6, 5, 2, 6, 5,208, 5, 2,
+
+// state[788 + 2] 0x024c00 Byte 4 of 4 (property)
+208, 2, 6, 5, 5,208,208,229, 229, 5, 5, 6, 6, 5,208, 6,
+ 5,208, 6, 6, 6,229, 4, 5, 6,208, 6,208, 6,229,229,208,
+208,208,208,208, 5, 5, 6, 6, 208,208,208,208,208,208,208,208,
+208,229,208,208,208,208,208, 6, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[789 + 2] 0x024c40 Byte 4 of 4 (property)
+ 5, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6,208,208,229,229,229,
+208, 6,208, 6, 6, 5, 5, 5, 5, 5, 5, 6, 6, 6,208,208,
+ 6, 6, 3,208,208,208,208,217, 208,229,208,208,208,208, 5, 5,
+ 5,208, 6,229, 6, 6,208, 6, 6, 6, 6, 6,208,208,229, 6,
+
+// state[790 + 2] 0x024c80 Byte 4 of 4 (property)
+ 6, 6, 2, 5, 5, 5, 5, 5, 5, 6, 6,208, 2,208, 5, 5,
+ 5,229,229,229,229,208,229,208, 208,208,208,208,208,208, 5, 5,
+ 5, 5,219,208, 6, 6, 6,208, 6, 6, 3,229,229,208,208,208,
+208, 5,208, 6, 6,229,208,208, 208,208,229,208, 2,208, 5, 5,
+
+// state[791 + 2] 0x024cc0 Byte 4 of 4 (property)
+ 5, 6, 6, 6, 2,208, 2, 2, 229,229,208,208,208, 5, 6,208,
+ 6, 6,229,208,229,229,229,229, 229, 5, 5,208, 6, 6, 6,229,
+208, 5, 2, 6,208, 6, 6, 2, 2,208,208,208,208,208,208,229,
+ 2, 2, 5,208, 6, 6,208, 2, 2,208, 5,208,208, 2, 5,208,
+
+// state[792 + 2] 0x024d00 Byte 4 of 4 (property)
+229,208, 5, 5,208,208, 5,208, 208, 6, 6,208,208, 6,208, 2,
+208,208,208,229, 4,208, 5,208, 208,229, 6, 6, 5,208,208,208,
+229,229, 6, 6,208, 0,229,208, 229,208,227,208, 2, 2,208,229,
+208,229, 5,208,208, 2, 2,208, 208,208,208,208,208,208,229,229,
+
+// state[793 + 2] 0x024d40 Byte 4 of 4 (property)
+229,229,229,229, 6, 6, 6, 5, 5, 6,218,208,208,208, 6, 6,
+ 6,208, 6, 6,208, 2, 2,208, 229,208,208,229,229,208,229,208,
+208,208,208,208,208,208,208, 5, 5, 5,222,208, 6,208, 6, 6,
+ 6, 6, 6, 2, 2, 6, 2,208, 229,208,227,208,229,208,229,208,
+
+// state[794 + 2] 0x024d80 Byte 4 of 4 (property)
+208,229, 5, 5,216,208,208, 6, 218,208,208,208,229, 6, 2, 2,
+ 2, 2, 2,229,208,229,229,208, 229,208,208,229, 0,208,208,208,
+208,208,208,229,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 6, 6,
+ 6, 6, 6, 6, 6, 6,208,208, 5, 2, 2, 2, 2, 2, 2, 2,
+
+// state[795 + 2] 0x024dc0 Byte 4 of 4 (property)
+208,208,227,229, 0,229,208,229, 229,229,208,208,229,208,208,229,
+208,208,218,208,229,208,229,229, 208,208,208,229,208, 5, 5, 6,
+208, 6, 6, 6, 6,208, 6, 6, 6, 6, 5, 5, 3, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2,208, 229,208, 6,229,208,208,208,229,
+
+// state[796 + 2] 0x024e00 Byte 4 of 4 (property)
+208,208,208,229,208,229,229,229, 229,229,208,208,229,208,228,208,
+ 6,229, 0,229,229,208,208,208, 208,208, 6, 6, 6, 5,208,208,
+208, 6,208, 6,208, 6, 6, 2, 2,208,229,229,229,227,208,229,
+208,229,229,229,229,229,208,228, 208,229,229,208,208,208,208,208,
+
+// state[797 + 2] 0x024e40 Byte 4 of 4 (property)
+ 6, 6, 5, 5, 5, 5, 5, 6, 6,208, 6, 6, 6,208, 6,208,
+ 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,229,208,229,208,208,
+208,229,229,208,229,229,229,208, 208,208,233,208,208, 6, 6, 5,
+ 5,208, 6,208,208, 6,208, 6, 6,208, 6,208, 2, 2, 2, 2,
+
+// state[798 + 2] 0x024e80 Byte 4 of 4 (property)
+208,208, 6,219,229,208,208,208, 208,229,229,233,208,208,229,229,
+208,229,208,229,229,229,208,229, 208,208,208,229,208,208, 6, 6,
+ 6, 5, 6, 6, 6,208,208,208, 208,208, 6, 6, 6, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2,208, 208,208,229,229,208,208,208,208,
+
+// state[799 + 2] 0x024ec0 Byte 4 of 4 (property)
+208,208,229,229,229,208,229,208, 208,208, 6, 6, 6,208, 2, 2,
+ 2, 2, 2, 2, 2,229,229,208, 229,229,229,208,229,208,229, 6,
+ 6, 5,208,208, 6,208, 6, 6, 208, 6, 2, 2, 2, 2,208,208,
+ 6,208,229,227,229,208,229,229, 208, 5,208, 2, 2, 2, 2, 6,
+
+// state[800 + 2] 0x024f00 Byte 4 of 4 (property)
+229,208,208,208,208,208,208, 5, 6, 6, 6,208,208,208, 5, 6,
+229,229,229, 2, 2, 2, 5,208, 229,229,208, 6,208,208, 2,208,
+229,208,208,208,208,208, 6,208, 5,208,208, 5,208, 5, 6,208,
+208,208,208, 6, 6, 6, 6,208, 2,208,208, 6, 5,208,208, 6,
+
+// state[801 + 2] 0x024f40 Byte 4 of 4 (property)
+ 2,208,229,229, 6, 6,208,229, 208,229,229, 6,229,208,208, 6,
+ 5, 5, 5, 6,208, 6,208, 2, 208,208,208,208, 5, 5, 5, 5,
+ 5, 5, 6, 6, 5,208,229,208, 5, 5, 5, 6,208, 6, 6, 5,
+208, 6, 6, 6, 2, 2, 2, 5, 2,229,208,208,229,208, 6,229,
+
+// state[802 + 2] 0x024f80 Byte 4 of 4 (property)
+ 6, 6, 5, 5, 6, 6,208, 6, 229,208,208, 5, 5, 5, 5, 5,
+ 6, 6, 6, 2, 2,229, 6, 5, 5,208, 5,229,208,229,208, 2,
+208,229,229,229,208,229,208,229, 208, 5,208,229,229,229, 6, 2,
+ 2, 2,208,208,229, 6,208, 2, 229, 0, 6,208,208, 5,208, 6,
+
+// state[803 + 2] 0x024fc0 Byte 4 of 4 (property)
+208, 6, 5,208, 6,229,229,229, 229,208,208,208, 6, 2,229,229,
+229,229,208, 6, 6,229,208,208, 208,229,229,208,208,208, 6,229,
+ 0,229, 5, 6,218,208, 2,229, 208,208,229,229, 5,218,208, 6,
+ 2, 2, 5,229,229,229,229,229, 6, 5, 5, 6, 6, 6, 6,208,
+
+// state[804 + 2] 0x025000 Byte 4 of 4 (property)
+ 6,229,229,229,229, 6, 5,208, 229,208,208,208, 2,208,229,229,
+229, 5, 6, 6,229,229,229,229, 208,229, 5,208,208, 6,208,208,
+ 6,208,229,208,208,208,208, 6, 208, 6, 2,229,229,208, 6,208,
+229,229,229, 2,208,229,208, 6, 6,229,208, 6,208,208, 6,208,
+
+// state[805 + 2] 0x025040 Byte 4 of 4 (property)
+ 5,229,208,229,208,208,229,229, 229,208, 4, 5, 5, 6,229,208,
+229,229,229,208,208,228,208, 6, 5, 5,208,208,208, 6,229,208,
+229,208, 6, 5, 5, 5, 5, 5, 5,208,208,208,229,208,229,229,
+208,229, 6, 5, 5, 6,208,227, 208,208,208,208,208, 6, 6, 5,
+
+// state[806 + 2] 0x025080 Byte 4 of 4 (property)
+217,219, 6,208,208, 5, 5,208, 229, 2,208, 6, 5, 5, 5, 5,
+ 5, 5, 6,208,208,208,229,208, 208,208, 5, 5, 5, 5, 5, 5,
+229,208, 6, 6,208,208,229,229, 208,229, 6, 6, 5, 5, 5, 6,
+ 6,208,208,208, 6,208, 5,208, 208,229,208, 6, 5, 6,229, 5,
+
+// state[807 + 2] 0x0250c0 Byte 4 of 4 (property)
+229,208,229,208, 5, 5, 6,229, 208,208, 5, 5, 6,208,208, 6,
+ 6, 2, 5,208,208,208,208, 5, 208,208, 2, 2, 5, 6, 2, 6,
+ 6,208, 6, 6,229, 5,208,229, 0,229,208,229, 5, 6, 6, 6,
+ 6, 2,208,229,229,229,229,208, 208,229,229,208,208,229, 6, 6,
+
+// state[808 + 2] 0x025100 Byte 4 of 4 (property)
+208, 6, 5,208,208,208,208,229, 229,229,208,208,229,229,229,208,
+208,229,229,208,229,229,229,229, 208,208, 6,208,208,229,208, 6,
+ 6, 6, 4, 5, 5,208, 6,208, 6,208, 6,216, 2, 2, 2, 2,
+ 2,229,229,208,229,229,208,229, 229,229,208,229,208,208,208,208,
+
+// state[809 + 2] 0x025140 Byte 4 of 4 (property)
+208,208, 6, 2,229, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6,208, 6, 6, 6,208, 227,208,208,208,208,208, 0,229,
+229,229,208,208, 6,208, 0,229, 229,208,229,208,208,208,208, 6,
+208, 5,216,208, 6, 6,208, 6, 6,208, 6, 6,208, 5, 5, 2,
+
+// state[810 + 2] 0x025180 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2,208,208,208, 208,208, 6,229,229,208,208,208,
+208,229,208,208,229,208,229,229, 229,229,229,229,229,229,229, 0,
+208, 0,208,208,208,229,229, 6, 6, 4, 5, 5, 5, 6, 6, 6,
+208, 6, 6,208, 6,208,208, 6, 6, 2, 2, 2, 2, 2, 2, 2,
+
+// state[811 + 2] 0x0251c0 Byte 4 of 4 (property)
+ 2, 2, 2,208,229,208,208,229, 2,208,208,208,229,228,208,229,
+208,208,208,229,229,229,229, 6, 208,229,229,229,208,208,229,208,
+208,208, 6, 5, 5, 4, 5, 5, 5, 5, 5,208, 6, 6,208, 6,
+ 6, 6, 6, 6,208,208, 2, 2, 2, 2, 2, 2, 2, 2,208,208,
+
+// state[812 + 2] 0x025200 Byte 4 of 4 (property)
+229, 2,229,208, 6,229,229,229, 208,208,229,229,208,208,208,208,
+208,208,208,208,229,229,208,208, 208,229,208,229,208, 6, 4,219,
+ 5, 0,216, 5, 5, 5, 5, 5, 5,208,208, 6, 6,218, 6, 2,
+ 2, 2, 2, 2, 2, 2, 2,208, 208, 2, 2,208,229,229,229,227,
+
+// state[813 + 2] 0x025240 Byte 4 of 4 (property)
+208,229,229,229,208,229,229,229, 229,229,229,229,228,208,229,208,
+229,229,208,229,208,208,229,208, 208,208,208,208,229,208,229,229,
+ 6, 6, 5, 5, 5, 5,208, 6, 6, 2, 2, 2, 2, 2, 2, 2,
+227, 6, 2, 6,229,229,229,229, 0,208,229, 0,229,208,208,229,
+
+// state[814 + 2] 0x025280 Byte 4 of 4 (property)
+227,208,208, 6,208,208,208,208, 208,229,208, 6, 6, 6, 5, 6,
+208, 6, 6, 6, 6,208, 2, 2, 2,216, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2,208, 208, 6,229,229,229,229,229,229,
+208,208, 0,208, 0,229,229,208, 229,229,229,208,229,208,208,208,
+
+// state[815 + 2] 0x0252c0 Byte 4 of 4 (property)
+208,208,208,208,208, 6, 6, 5, 5, 5, 5, 6, 6,208, 6, 6,
+208, 6, 2, 2, 2, 2, 6, 6, 5,229,229,208,208,229,208,227,
+208,208,229,229,208,208, 6, 6, 5, 5, 5,208,208,208, 6, 5,
+229, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 2, 2, 6,229,
+
+// state[816 + 2] 0x025000 Byte 3 of 4 (relative offsets)
+-12,-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+
+// state[817 + 2] 0x025300 Byte 4 of 4 (property)
+ 0,229,208,208,229,229,229,208, 0,208,229,208,208,208,227,229,
+229, 5, 6, 5, 5,208,208,208, 6, 2,229,229,208,208,229,208,
+ 6, 5, 5,208, 6, 2, 2, 2, 208,208,208, 2, 2,208,229,229,
+229,229,208, 6, 6, 5,208, 2, 2, 2,208,229,229,229,208,208,
+
+// state[818 + 2] 0x025340 Byte 4 of 4 (property)
+229,208,208,208,208, 6,208, 6, 6,229,208,208,208, 2, 2,229,
+ 6, 2, 2,208,208,208, 2,208, 2, 5,208, 2, 6, 6,208, 0,
+229,229,229,208,208,208, 6,208, 229, 5,208,229,208,208,208,229,
+208,208,208,208, 6,208,208,208, 208,208,229,229,229,229,208,208,
+
+// state[819 + 2] 0x025380 Byte 4 of 4 (property)
+208, 2,208,229,229,208,208,208, 6,208,229,208,208,208,208,208,
+208, 6,208,208,208,208,208,208, 229,208,208, 6,208, 6,219, 6,
+ 6,208, 6, 6,208,208,208,208, 208,208,208,208,208, 6, 6, 6,
+229,208,208, 5, 5,208,208, 6, 208,229,229,229,208,208,208,208,
+
+// state[820 + 2] 0x0253c0 Byte 4 of 4 (property)
+208,208,208, 6, 6, 6, 6, 6, 6,208, 6, 2, 2, 2,208,208,
+ 6, 5, 6, 6,208, 6, 6, 5, 208,229,208, 6,229,229,208,229,
+229, 5,208, 6, 6, 6, 6, 6, 6, 6,208,229,208, 6, 6,208,
+208, 5,208,208,208,208, 5,208, 6, 6, 6,229,208, 5,208, 2,
+
+// state[821 + 2] 0x025400 Byte 4 of 4 (property)
+208,229, 6,208,208, 6, 2, 2, 2, 2, 6, 2, 6, 2, 6, 2,
+ 2,229, 2,229, 2,229, 5,229, 208,229,208, 5, 5,229,229, 6,
+ 5, 5, 5, 5,208, 5, 2, 2, 2,208,229,208,229, 6, 4, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6,208,208, 6, 6,
+
+// state[822 + 2] 0x025440 Byte 4 of 4 (property)
+208,208, 2, 2,208,208,208,229, 229,208,208,208,208,229,229,208,
+229,208,229,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+218, 6,208,208, 6,218, 6, 6, 6,208, 6, 6, 5, 2, 5,208,
+229, 6,227,208, 0,208,227, 6, 208,229,208,229,208,208,208,208,
+
+// state[823 + 2] 0x025480 Byte 4 of 4 (property)
+208,229,229, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 4, 5,
+ 6, 6,208, 6,208, 6,208, 6, 6, 6,208, 6,229, 6,208, 6,
+ 6, 6, 6, 6,208, 2, 2,229, 229,229,229,229,229,208,227,208,
+229,208,229,208,229,208,208, 6, 6, 5, 5, 5, 5,208, 6, 6,
+
+// state[824 + 2] 0x0254c0 Byte 4 of 4 (property)
+208,208, 6, 6, 2, 2,208,208, 6, 2, 5,208,229,229,208,229,
+229,208,229,229,208,208,208,208, 6, 4, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,208, 6, 6,
+ 6,208, 3, 2, 2, 2, 2, 2, 2, 2,229,208,208,208,208,229,
+
+// state[825 + 2] 0x025500 Byte 4 of 4 (property)
+229,229,229,208,229,208,208,208, 208,229,208, 6, 6, 6, 4, 6,
+208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6,208,208, 3, 3, 2, 2, 208,208,208,208, 5,229,208,208,
+208,229,208,208, 5,238, 5, 5, 5, 5, 5, 6, 6, 6, 6,208,
+
+// state[826 + 2] 0x025540 Byte 4 of 4 (property)
+ 6, 6,219,208, 2,229,208,229, 208,208, 2,208,227,208,229,229,
+229,208, 6,208,208,229,229,229, 229,219,208, 5, 5, 5, 5, 5,
+ 5,229,208, 6, 6,216, 0, 5, 5, 5, 5, 5, 5, 5, 6, 6,
+208,208,229,229,208,208,229,208, 229,229,208,208,208,208,208,208,
+
+// state[827 + 2] 0x025580 Byte 4 of 4 (property)
+ 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208, 6, 6, 5,
+ 2, 2, 2, 2, 2, 2, 2,208, 229,208,208,208,208,229,208,208,
+208,208, 5, 5, 5, 5, 5, 4, 6, 2,208,229,208, 6, 5, 5,
+ 5, 5, 6,208,208, 6, 6,208, 2, 5, 2,229,208,229,208,208,
+
+// state[828 + 2] 0x0255c0 Byte 4 of 4 (property)
+208,208,208, 5, 5, 5, 5, 6, 6,208, 6, 6, 2, 2, 2, 2,
+ 2, 6,229,208, 6, 5, 5, 6, 208,229, 6, 5, 2,208,208,229,
+ 5, 5, 5,208, 6, 6, 6, 6, 2, 2,208,208,208,229,208, 5,
+ 6,208,208, 2,208, 6, 6,208, 5, 2,208, 5, 6, 2,229,208,
+
+// state[829 + 2] 0x025600 Byte 4 of 4 (property)
+ 2,208, 2,208,208,208,208, 6, 5, 5, 6, 6, 6, 5, 5, 6,
+ 6,218, 6, 6, 6,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6, 6,208, 6,208, 6, 6, 6, 2,208,208,208,208, 6,
+208,208, 6, 6, 6, 0, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[830 + 2] 0x025640 Byte 4 of 4 (property)
+ 6, 6,208, 6,208, 6, 6, 6, 6,208,229,208, 5,208,208,208,
+ 6, 5, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,
+ 6, 6, 6, 6,208, 6, 6, 2, 208, 2, 2,229,208,208,229,208,
+229,208,208, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 6, 6, 6,
+
+// state[831 + 2] 0x025680 Byte 4 of 4 (property)
+ 6,219, 6, 5, 2, 2, 2, 2, 208,229,208,229,208,208, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 6,208, 6, 6, 6,
+ 6, 6, 6, 6, 2, 2,229,208, 208,208,229,208, 6, 6, 6, 2,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,
+
+// state[832 + 2] 0x0256c0 Byte 4 of 4 (property)
+208,208,208,208, 6,208,208,208, 208, 2, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6,208, 6, 3, 2, 229,208,229,208,229,229,208,229,
+229,208, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 2,229,229,
+229,208,229,208,208, 5, 5, 5, 5, 5, 5,208,208,208, 6,208,
+
+// state[833 + 2] 0x025700 Byte 4 of 4 (property)
+ 2, 5, 5,229,208,208, 5, 5, 5, 5, 5, 5, 5, 6, 6,208,
+ 6,208,229,208,208, 5, 6,208, 6, 6,208,208, 6, 5, 5,208,
+208, 2, 6,208, 2,208,208,208, 6,208, 5, 5,208, 6, 6,208,
+229,208,208, 5,208, 6, 6,208, 208,208, 5, 5,208,208, 5, 0,
+
+// state[834 + 2] 0x025740 Byte 4 of 4 (property)
+ 6,229, 6,229,208,208, 6, 5, 229, 6,208,208,229,208, 2, 5,
+208, 6,208,208,229,229,229, 6, 229, 6, 5, 5, 5, 5, 5, 6,
+ 6, 6,208,208,208,227,229,229, 229,229,208,229,208,229,229,208,
+ 6,231, 5, 5, 5, 5, 5, 6, 6,208,208,208,208, 2,229,229,
+
+// state[835 + 2] 0x025780 Byte 4 of 4 (property)
+208,229,208,229,208,208,208, 6, 5, 5, 6, 6, 6, 6, 6, 6,
+ 6, 6,208, 6, 6, 6, 2,208, 229,229,229,217,229,208,208, 5,
+ 5, 5, 5, 6, 6, 6, 6, 6, 6,228, 6, 6, 6, 6, 6,208,
+ 6,208,229,229,228,227,229,208, 208,229,208,208,208, 5, 5, 5,
+
+// state[836 + 2] 0x0257c0 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6,208, 6, 5, 2, 2, 2, 6, 2,229,208,208,
+208,229,208,229,229,229,208,208, 208,229,208,208,208, 6, 5,208,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6,208, 208, 2,208,208, 2,229,208,208,
+
+// state[837 + 2] 0x025800 Byte 4 of 4 (property)
+229,208,208,208,229,208,208,208, 229,229,208,208,208,229,208, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6,
+ 6,229,208, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2,
+ 6,229,229,229,208,229,208,229, 219,229,208, 6,208,208,208,208,
+
+// state[838 + 2] 0x025840 Byte 4 of 4 (property)
+208,208, 6,208, 6, 5,217, 5, 5, 5, 5, 5, 5, 6, 6,219,
+ 6, 6, 6, 6,208, 6, 6,216, 2, 6,208,229,229,208, 6,229,
+208,208,208,208,229,229,229,229, 208,208,208,208,208,229,208,208,
+208, 6, 0, 5, 5, 5, 5, 6, 6,208, 6, 6, 6, 6, 6, 6,
+
+// state[839 + 2] 0x025880 Byte 4 of 4 (property)
+208,208, 2, 2, 2, 2, 2,229, 208, 2,229,229,208,208,208,208,
+218,208,229,231,208,229,208, 6, 5, 5, 5, 5, 5, 5,208, 6,
+ 6, 6, 6, 6, 6, 6, 6,208, 6, 6, 3, 2, 2,229,208, 6,
+208, 5, 6, 2,229,229,229,208, 229,208,208,208,208,208, 6, 5,
+
+// state[840 + 2] 0x0258c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 6, 6, 208, 6, 6, 6, 6, 6, 6, 6,
+ 2, 2, 2,208, 6, 6,208,229, 229,229,208, 6,208, 6,208,208,
+208, 5, 6, 6, 6,208,208,208, 208,208,229,208,208,208,229,229,
+229,229,208, 5, 5, 5, 6,208, 6, 6, 6, 6, 6, 2, 5, 6,
+
+// state[841 + 2] 0x025900 Byte 4 of 4 (property)
+208,208,208,208, 6,208,229,208, 6, 6,229,208,208, 6,208, 6,
+ 6,208,208,229,208, 5, 5,208, 229, 5,208, 6,208, 6,229,208,
+ 6, 6,208,229,208, 6, 6,229, 208,229,208,208, 6,208,208, 6,
+ 5,208, 6, 6,208,208,208,208, 229,208,229,208,208,208,208,229,
+
+// state[842 + 2] 0x025940 Byte 4 of 4 (property)
+ 6, 5, 5, 5, 6, 6,208,208, 229,208,229,208,229,229,208,208,
+208,208,208,208,208, 6, 5, 5, 5, 5, 5,208, 6, 6,208, 6,
+208,229,208,208,208,208,208,208, 208,208, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6, 6, 6, 6,229,229, 208,208,229,229,208,229,229,229,
+
+// state[843 + 2] 0x025980 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208, 6,208,208,229,229,208,208,
+208,208, 6, 5, 6, 6, 6, 0, 6, 6, 6,229,229,227,208,229,
+208,208,208, 6,208,208,208,208, 208,208,208, 6, 5, 5, 5, 5,
+ 5, 5,208, 6, 6,208,208, 6, 229,208,208,208,208,208,208,208,
+
+// state[844 + 2] 0x0259c0 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 4, 5,208,208, 6, 6,208, 6,208, 6, 2, 5,
+208,219,229,208,228,208,208,208, 208,208,208,229,208,208,208,208,
+208, 6,208, 6, 6,208, 6, 6, 6, 2, 2, 6,208,229,229,229,
+208,229,229,208,208,229,208,208, 229,229,208,208,208,208, 6, 5,
+
+// state[845 + 2] 0x025a00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 6, 6, 6, 6, 6, 6,229, 2, 6, 0,229,208,
+208,208,229,208,208,229,229,208, 208, 6, 5, 6, 6, 6, 6, 6,
+ 6,208,208, 5, 6, 6, 6, 2, 2, 6,229,208,208,208,208, 3,
+208,229,208,208,208,208,208,208, 5, 6, 6,229,208,208, 6,208,
+
+// state[846 + 2] 0x025a40 Byte 4 of 4 (property)
+208,208,208, 5, 6, 6, 6, 2, 5, 2,208,208,229, 2,208, 5,
+208, 6,229,208,208,208,208,208, 208,229, 6, 5, 5, 6, 6, 6,
+208,229,208,229,208,208,208, 5, 5, 5, 5, 5, 5,208,208, 2,
+ 2,208,208,229,208,229,208, 6, 5, 5, 6,208,208, 6,229,229,
+
+// state[847 + 2] 0x025a80 Byte 4 of 4 (property)
+229,229,229,208,208, 2, 5, 5, 5, 5,229,229,208,229,229,208,
+208,208, 5, 5, 5,208, 6, 6, 229,229,229,208, 0, 2, 2, 5,
+ 5, 5, 6, 6, 5,208,208,208, 5, 5, 5, 6,208, 5, 5,229,
+229,229, 5,208, 6,208,208, 6, 2, 6,219,208,229,208, 5,208,
+
+// state[848 + 2] 0x025ac0 Byte 4 of 4 (property)
+ 6, 6, 6,229, 6, 6, 6, 6, 208,208, 5, 6, 2,208,229, 5,
+208, 5, 5, 6, 6, 6,208, 6, 5,229,208,208,208,229,229, 0,
+208,229,208, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6,216, 5,208,
+208,228, 6,229,229,229,208,208, 208,208,208,208,208,229, 5, 5,
+
+// state[849 + 2] 0x025b00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 6,208, 6,218, 2, 2,208,208,229,208,
+208,208,229,229,229,229,208,208, 6,208,208,208, 6,229,208, 5,
+ 6, 5, 5, 5, 5, 5, 5, 2, 2, 2,229,229,208,229, 0,229,
+229,208,208,208,208,229,208,208, 208,208, 6,208,208,208,208, 5,
+
+// state[850 + 2] 0x025b40 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,218, 2, 2, 2,
+229,229,208,208,229,208,229,229, 208,229,208,229,229,208,208,229,
+208,208,229,208,208,208,208,208, 208, 6,208,208,208,208,208,208,
+208,208, 6, 6, 5, 2, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[851 + 2] 0x025b80 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5,229, 3, 2, 2,216, 2, 2, 5,208,229,229,
+208,229,229,208,208,229,208,229, 229,208,208,229,229,229,208, 6,
+229, 6,208,208,208,208,208,208, 208,208,208,208, 6,208,208,208,
+ 6, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[852 + 2] 0x025bc0 Byte 4 of 4 (property)
+ 5, 5, 5, 6, 6, 3, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 5,229,227,227,208,229,208,229, 208,208,208,208,208,227,208,229,
+229,208,208,208,208,229,208,208, 229,229, 0,208,208,208,229,229,
+208,208,208,208,229,208,208,208, 208,208,208, 6,208,208,208,208,
+
+// state[853 + 2] 0x025c00 Byte 4 of 4 (property)
+ 6, 5,208, 3, 2, 5,216, 2, 2, 5, 2, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 208,208,229,229,229,229,208,229,
+229,229,229,208,208,208,208,208, 229,208,208,208,208,229,208,208,
+208,229,229,208,208,208,208,208, 208,208,208,208,229,208,208,208,
+
+// state[854 + 2] 0x025c40 Byte 4 of 4 (property)
+229,208,208, 6,208,208,208,208, 6, 6, 0, 4,217, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5,217, 5, 5,194, 5, 5, 6, 218,208, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 5, 2,208,208,208, 208,208,208,229,229,208,208,208,
+
+// state[855 + 2] 0x025c80 Byte 4 of 4 (property)
+229,229, 6, 6,229,229,229,208, 229,208,229,208,208,208,208,208,
+208,208,208, 6, 6,208,229,208, 229,208,229,208,208,208,208,229,
+208,208,208,208,208,208,208,208, 208,208,208,208,208, 6, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,229,208, 6, 3,
+
+// state[856 + 2] 0x025cc0 Byte 4 of 4 (property)
+ 5, 5, 2, 2, 2, 2,208,229, 208,227,208,208,208,229,229,208,
+ 0,229,208,227,229,208,208,208, 229,208,208,208,208,229,229,208,
+208,208,208,208,229,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,229,208,208, 208,208,229, 6, 6, 6, 5, 5,
+
+// state[857 + 2] 0x025d00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6,208,208, 6, 6, 2, 2, 2, 2, 2, 2, 5, 5, 5,
+ 5,229,227,208,208,229,208,229, 229,229,208,208,208,229,229,208,
+208,208,229, 6,208,208,208,208, 208,208,208,208, 6,208,208,208,
+
+// state[858 + 2] 0x025d40 Byte 4 of 4 (property)
+ 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+208, 6, 6,208, 3, 2, 2, 2, 2, 2, 2, 2,229,208,208,208,
+208,229,208,229,208,208,229,229, 208,208,208,229,208,208,208,208,
+208,208,208,229,208,208,208, 6, 5, 5, 5, 5, 6,208, 2, 2,
+
+// state[859 + 2] 0x025d80 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 6,208,227, 208, 6,208,208,229,208,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+ 6, 4, 6, 5, 5, 5, 5, 5, 5, 5,208,208, 6, 6, 2, 2,
+ 2, 2, 2, 2,208,208,229,208, 208,208,208,229,208,229,208,208,
+
+// state[860 + 2] 0x025dc0 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208, 6, 6, 5, 5, 5, 5,
+208,217, 2, 2,208,229,208,229, 208,208,229,208,208,208,208,208,
+ 5, 5,208,208,208, 6, 6, 2, 208,208,208,229,229,208,208,208,
+208,208,208, 6, 5, 5, 5, 5, 208, 6, 2, 2,208,208,208,208,
+
+// state[861 + 2] 0x025e00 Byte 4 of 4 (property)
+208, 2, 6,208, 2, 5, 2, 5, 208,229,208,208, 6, 2, 5, 5,
+208,208, 2, 2, 5, 6, 5,208, 208, 5, 6, 6,208, 2, 6, 6,
+ 2,208,208,208, 6,229, 5,229, 208, 5, 6,208,229,208, 4, 5,
+ 5, 5, 6,208,229,208,208,227, 208, 5, 5, 5, 5,208,208, 2,
+
+// state[862 + 2] 0x025e40 Byte 4 of 4 (property)
+ 2,229,208,229,219,208,229,229, 208,208,229, 0,208,208,208,208,
+ 5, 5, 5, 6, 6,208,233, 6, 2, 2,229, 6,229,208,227,208,
+208,208,233, 6,217, 4, 5, 5, 5, 6, 6,208, 6,208, 3, 2,
+ 2, 6,229,208,208,208,229,208, 0,208,208,208,208,208,208,208,
+
+// state[863 + 2] 0x025e80 Byte 4 of 4 (property)
+ 6, 5, 5, 5, 5, 5,216, 5, 208,208,218,208, 6, 6, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 5, 229,208,229,229,208,229,208,208,
+208,208,229,208,208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,208,208, 6,208, 6, 208, 2, 2, 2, 5, 2, 2, 2,
+
+// state[864 + 2] 0x025ec0 Byte 4 of 4 (property)
+ 6,208,228,229,208,229,208,229, 208,208,208,208,208,229,208,208,
+229,208,208,229,208,208,208, 5, 233,216, 5, 5, 5, 6, 6, 6,
+ 2, 2, 2, 2,229,229,229, 0, 228,229,208,208,208,208, 0,208,
+208, 5, 5,208, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 6,229,
+
+// state[865 + 2] 0x025f00 Byte 4 of 4 (property)
+208,208,229,229,208,208,208,208, 208,208,219, 6, 5, 5, 5, 5,
+ 5, 5, 5,208, 6, 2, 2, 6, 208,208,229,208,208,229,208,208,
+208,217, 5, 4,217, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6,
+ 3, 2, 2, 2, 6, 5,229,229, 208,229,229,208,208,208,208,208,
+
+// state[866 + 2] 0x025f40 Byte 4 of 4 (property)
+208,229,208,208, 2, 5, 5,227, 6,208,208, 5, 2, 2, 2,229,
+208,208, 6, 6, 2, 5, 5,208, 229,208,208,208, 4, 5, 5, 6,
+ 6, 2, 2,208, 2,208,229,208, 6, 2, 2, 3,208,208,208, 5,
+229,208,208,208,208, 2,229,229, 208,229, 5,208,208,229, 5,208,
+
+// state[867 + 2] 0x025f80 Byte 4 of 4 (property)
+208,208,208, 2,229,208,208,229, 5, 5,208,218,229,208,208,208,
+208,208,208,208, 5, 6,208, 2, 2,208, 0,227,208,229,208, 6,
+208,208,208,229,208,208,208,208, 208,208,208,208,208,208,208,208,
+208, 6, 5, 5, 5,216, 5, 5, 5, 6,208,208, 6, 2, 2, 2,
+
+// state[868 + 2] 0x025fc0 Byte 4 of 4 (property)
+ 2, 2, 2,229,229,208,229,229, 229,229,208,229,208,208,208,208,
+208,208,208,208, 4, 5, 5,216, 5, 5, 6, 6,208, 6, 6,208,
+214,216, 5, 2, 2, 2,229,229, 229,229,208,208,208,208,208,229,
+229,208,208,208,208,208,208,208, 208,208,227, 4, 5, 5, 5, 5,
+
+// state[869 + 2] 0x026000 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5,216, 5,208, 6,208, 2, 2,214, 2, 2, 2,
+ 2, 2,229,208,208,208,227, 0, 208,229, 0,208,229,208,208,229,
+229,208,208,208,208,208,208,208, 218, 5, 2, 2, 5, 5, 5, 5,
+ 5, 5, 5, 5,216,216, 5, 5, 6, 2, 2, 2, 2, 2, 2, 2,
+
+// state[870 + 2] 0x026040 Byte 4 of 4 (property)
+ 2, 2, 2,208,208, 2,208, 6, 208,229,208,208,208,229,208,229,
+229,208,208,208,208,208,208,208, 208,208,229,208,208,208,208, 6,
+ 4, 5, 2, 2,217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+208,208, 6, 6,208, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[871 + 2] 0x026080 Byte 4 of 4 (property)
+208,227,229,208,229,208,229,208, 227,229, 6,229,208,208,208,208,
+208,208,229,208,229,208, 6,208, 208,208,208,227,208,208,208,208,
+229,208, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217,
+ 5, 5, 6, 6,208,229, 6,208, 3, 3, 2, 2, 2, 2, 2, 2,
+
+// state[872 + 2] 0x0260c0 Byte 4 of 4 (property)
+ 2, 5, 6,208,229,208,208,229, 229,208,208,229,208,208,208,208,
+227,208,229,208,229,208,229,208, 229,208, 6,208,208,208,208,208,
+208, 6,208,208,208,208,208,208, 208, 6, 6, 6, 6, 4, 2, 2,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 2, 2,
+
+// state[873 + 2] 0x026100 Byte 4 of 4 (property)
+ 2, 2,216, 2, 2, 2, 2, 2, 2,229,208,208,227,229,208,208,
+208,229,208,227,229,208,208,208, 208,208,208,208,208, 6, 2, 3,
+ 2, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+ 6,229,208, 6, 2, 2, 2, 2, 2, 2, 2, 2,229,208,227,208,
+
+// state[874 + 2] 0x026140 Byte 4 of 4 (property)
+229,208,208,229,229,208,229,229, 208,229,208,208,229,208,208,208,
+208,208,208,208,208,229, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6,208, 208,219,208, 6,208, 2, 2, 2,
+ 2, 2, 2, 5, 2,229,208,229, 229,208,229,208,229,208,229,229,
+
+// state[875 + 2] 0x026180 Byte 4 of 4 (property)
+208,208,208,229,208,229,208, 6, 229,208,208,208,208,208,208, 5,
+ 5, 5, 5, 5, 5,208,208,208, 6, 2, 2, 5,208,208,229, 0,
+229,208,208,208,208,208,208,208, 6,208,208,208,208, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,208, 6, 6, 2, 2,229,208,208,208,208,
+
+// state[876 + 2] 0x0261c0 Byte 4 of 4 (property)
+208,208,229,208,208,208, 6, 2, 5, 5, 5, 5, 5, 6,229,208,
+ 6,208, 2,208,208,229,229,208, 208,208,208,229,208, 5, 5, 5,
+ 5, 5,208,208, 6,208, 3,229, 208,208,208,208,229, 6, 2, 3,
+208,229,208, 5, 5, 6,208,208, 208,208,208,208,208,208,208, 5,
+
+// state[877 + 2] 0x026200 Byte 4 of 4 (property)
+208,208, 2,208, 6,208, 5,208, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6,229,229, 6, 5,208,208, 208,229, 6,229,208,208,208,208,
+208,229,208, 5,229,229,229, 6, 208,208,208,229,208, 2, 5, 5,
+
+// state[878 + 2] 0x026240 Byte 4 of 4 (property)
+ 5,208,229, 6, 5, 5, 6,208, 208,208, 6, 5, 5, 6,208,208,
+208, 5, 5, 5, 6,229,208, 5, 5,208,208, 5, 6,229,229,229,
+208, 5,229,208, 5,229, 5,229, 6,208,217, 5,208,208,208,208,
+ 4, 2, 5, 5, 5, 5, 6,208, 208,229, 6,208,208, 6,208,208,
+
+// state[879 + 2] 0x026280 Byte 4 of 4 (property)
+ 5,229,229,208, 6, 6, 4, 5, 5, 5, 6,208, 6, 5,208,208,
+229,208,208,208,208,208,208,208, 208, 5, 2, 2, 5, 5, 5, 5,
+ 5, 5, 5,208,208, 6, 6,208, 5, 5,208,229, 6,208,208,208,
+ 6, 6, 6, 5, 5, 5, 5, 6, 6, 6, 3, 6,229,208,208,229,
+
+// state[880 + 2] 0x026000 Byte 3 of 4 (relative offsets)
+-11,-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+
+// state[881 + 2] 0x0262c0 Byte 4 of 4 (property)
+ 6, 6, 5, 6, 5,208,208,208, 229,208,208,208,208,208, 6, 6,
+ 5, 5, 5, 6, 6, 2, 5, 5, 5, 5, 5, 5, 5,208,208,208,
+208,208,208,208,208, 6, 2, 5, 5, 6, 6, 5, 5, 5, 5,229,
+229,208,208,208,229,208, 6, 5, 5,208, 6, 5, 5,208,208,208,
+
+// state[882 + 2] 0x026300 Byte 4 of 4 (property)
+208,208,208,208, 6,208,208, 6, 6,208,229, 6, 6, 5, 5, 5,
+ 5, 5,229,208,229,229,208,208, 6, 5, 6, 5, 5, 5, 5,208,
+229,229,229,208,208, 6, 6, 6, 5,208,229,208,208,208,208, 6,
+ 6, 5, 5, 5, 5,229,208,208, 229, 6,208,208,208, 6,229,229,
+
+// state[883 + 2] 0x026340 Byte 4 of 4 (property)
+208,208, 5, 5, 6, 6,208,208, 208, 2, 5,208,233, 5,208,208,
+208,208,208, 6,208,208, 5,208, 208, 6,208,227,208,208,208,208,
+ 6, 5, 5, 6, 6,208,208,208, 208,229,229, 6,208,208, 5, 5,
+ 5, 6,208, 6, 6, 6, 6, 6, 6,229,229,229,229,208,229,208,
+
+// state[884 + 2] 0x026380 Byte 4 of 4 (property)
+208, 6, 6,208, 6,208,208,208, 208,208,208,208, 6,227, 6, 6,
+ 6, 6, 6, 6, 5, 5, 5,229, 229,208,208, 2, 6,208, 6, 6,
+ 6, 2,208,229,208,208,229,208, 6,208, 6, 5,218, 6, 6,229,
+208,229, 6, 5,208, 6, 6,208, 229,229,208,208,208, 6, 5, 5,
+
+// state[885 + 2] 0x0263c0 Byte 4 of 4 (property)
+ 6,229, 6,208, 2,208,229,208, 208,208,229,208,208,208, 5, 5,
+ 5,208, 2, 2, 6,229,208,229, 208,208, 5, 6,208,208, 5,208,
+208, 5,229,208, 6, 6, 6,208, 229,229,229,208, 6,208, 6,208,
+229, 6, 5, 6,208,208,208,208, 229,229,229,229,208, 5, 5, 6,
+
+// state[886 + 2] 0x026400 Byte 4 of 4 (property)
+208, 6,233,229,229,208,229,229, 229,208,229,229,208,229,208, 6,
+ 5, 5, 5, 5, 5,208,229,208, 208,208,208,208,208, 6,229, 6,
+ 6, 6,208,229,229,208,229,208, 208,208,208, 6,208,208,208,208,
+208,208,208,208, 6, 5, 5,208, 208,208,229,208,208,208,208, 6,
+
+// state[887 + 2] 0x026440 Byte 4 of 4 (property)
+208, 6, 6, 2, 5, 2, 5,229, 208,208,208,229,208,229,229,208,
+208,208, 5, 5, 5, 5, 6, 2, 208,208,208,229,229,229,208,208,
+208,229,208,208,208,208,208,208, 208,208,208, 5, 5, 6, 6, 6,
+208,208,229,208,208,208, 0, 6, 229,229,229,208,208,208,208, 5,
+
+// state[888 + 2] 0x026480 Byte 4 of 4 (property)
+ 6, 6, 6,229,208,229,208,208, 229, 5, 5, 5, 5,229,229,208,
+208,229,208,208,208,208,208, 6, 5, 5, 5, 5,208,208, 6,208,
+ 5, 6,208,208, 5,208,229, 6, 208,208,208, 6,208,208,227, 5,
+ 6,208,208,208, 6, 2,229,233, 208, 2,229,229, 5, 5,229, 6,
+
+// state[889 + 2] 0x0264c0 Byte 4 of 4 (property)
+208, 5, 6,208,208, 2, 6, 2, 6, 2, 2, 6, 5, 6,208,208,
+208, 5,229,229,208, 6,229,229, 208,208,208, 6, 2,229,229, 6,
+208, 2, 6, 6,208,208, 5, 6, 208, 5,208, 5,229, 5,208,229,
+208,208, 6, 6,229,208, 6,208, 229,208,229,208,229,208,229, 2,
+
+// state[890 + 2] 0x026500 Byte 4 of 4 (property)
+ 5,208,229,229,229,229,229, 6, 6,208, 3,229,229,229,208,208,
+229,208, 5,208,229,208, 6,208, 208, 2, 5,229,208,208,208,208,
+208, 5,208, 5, 5,208,208, 6, 208,229,208,208,229,208, 6,208,
+229,208, 5, 5, 6,208, 6,229, 208,208, 6, 6,208,208,229,208,
+
+// state[891 + 2] 0x026540 Byte 4 of 4 (property)
+208,208, 5, 5, 5, 5, 6,208, 208, 6,208,208,208,208,217,208,
+208,208,229,229,208,208, 5,208, 208, 6, 5, 5, 5, 5, 5, 2,
+229,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6,208, 5, 2, 6,208,208,229, 208,229,208,229,229,229,208,208,
+
+// state[892 + 2] 0x026580 Byte 4 of 4 (property)
+208, 6, 5, 5, 5,208, 6, 2, 229,229,229,229,208,208,208, 6,
+208,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 6, 6, 6,
+208, 2,229,208,229,208,208,208, 208,208,208,208,208, 5, 3, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 6,208, 6, 2,229,208,208,229,
+
+// state[893 + 2] 0x0265c0 Byte 4 of 4 (property)
+229,208,208,208, 5, 5, 5, 5, 5, 5,208, 6, 3,208, 6, 2,
+229,229,229,229,229,229,229,229, 5,208,208, 6, 6, 2, 2,208,
+208,229,229,229,208,229,208,208, 208,208, 5, 5, 5, 6, 6,229,
+ 6, 5, 5,229,229,229,208,208, 5, 5, 6, 6, 6, 2,208, 6,
+
+// state[894 + 2] 0x026600 Byte 4 of 4 (property)
+ 5, 5, 5,208,208,229, 5, 5, 208,208,229, 5, 6,229, 5, 5,
+ 5, 5,208,208,208,229, 6, 6, 208, 6, 6, 5, 6,208,208,208,
+208, 6, 5,208, 6, 6, 5, 2, 208,229,208,208,208,208, 5, 5,
+ 6, 2,229,208,208,208,229,208, 208,218,208,208, 6,208,208,208,
+
+// state[895 + 2] 0x026640 Byte 4 of 4 (property)
+ 0,208,208,208,208,208,229,229, 208,208,208,229, 6,208, 6, 2,
+ 5, 5, 5, 5, 5, 5,208,208, 208, 6, 6,208,227, 6, 6,229,
+208,208,208, 2, 2, 2, 2,208, 208,208,208, 0,208,208,208,208,
+208,208,229,208,208,208,229,229, 208,208,208,208,208,219, 4, 5,
+
+// state[896 + 2] 0x026680 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 208,208, 6, 6,208,208,208, 6,
+ 6,208,208,208,216, 2, 2, 2, 208,208,208, 5, 5,229,229,229,
+208,229,208,208,229,208,229,229, 208, 5,208,208,208,227,208, 5,
+ 4, 5, 5, 5, 5, 5, 5, 5, 208,208,208,208, 6,208, 6, 6,
+
+// state[897 + 2] 0x0266c0 Byte 4 of 4 (property)
+ 6,208,208,208,208,208, 6, 6, 3, 2, 2, 2, 2, 2,208,208,
+208, 6,208,208,208,208,208,229, 208,229,208,229, 0,208,229,229,
+229,208, 6,208,208,208,208,208, 5, 5, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6,208,208, 6,208,208, 6,208, 2, 2,216, 2, 2, 2,
+
+// state[898 + 2] 0x026700 Byte 4 of 4 (property)
+ 2, 6,208,208,208, 6,229, 0, 208,219,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 227,208,208,229,229, 4, 2, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 6,208, 6, 6, 6,
+ 6, 6, 6,208, 6,208,208, 6, 6,208,208,208, 6,208,208,208,
+
+// state[899 + 2] 0x026740 Byte 4 of 4 (property)
+ 6, 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,
+208,208,229,208,208,208,208,208, 208, 5,208,229,208,229,229,208,
+208,208,229,208,208,227,229,208, 229,229,208,208,227,229,208,208,
+208,208,208,208,208,208,208,229, 208, 2,216, 5, 5, 5, 5, 5,
+
+// state[900 + 2] 0x026780 Byte 4 of 4 (property)
+ 5,227, 6, 6,208,208, 6,208, 219,208, 6,208, 6, 2, 2, 2,
+ 2, 2, 2, 2, 2,208,208,208, 6,208,208,208,229,208,229,208,
+208,208,227,208,208,208,229,208, 229,208,208,208,208,208, 6,208,
+208,208,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208, 6, 6,
+
+// state[901 + 2] 0x0267c0 Byte 4 of 4 (property)
+208,208,218, 6, 6, 6, 6, 6, 6, 2, 2, 2, 5, 2, 2, 2,
+ 2, 2, 2, 6,208,208, 2, 2, 229,208, 6,229, 0,208,219,208,
+208,208,208,208,208,208,208,229, 6, 5,216, 5, 5, 5, 6,208,
+229, 6, 6,208,208, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[902 + 2] 0x026800 Byte 4 of 4 (property)
+208,208, 5,208,229,229, 0,208, 208,208,208,208,208,208,229,229,
+208,208,208,208,208,208,208,208, 2, 5, 5, 6,208, 6,208,208,
+208, 6, 6, 6,208,208, 6, 6, 208,208, 6,208,208, 6, 6, 2,
+ 2, 2, 2, 2, 2, 2, 2,208, 208,208,229,208, 5, 2,208,208,
+
+// state[903 + 2] 0x026840 Byte 4 of 4 (property)
+229,229,208,208, 6,208,208,208, 229, 6, 6, 2, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5,208, 6,208, 208, 6, 6,208,208, 6,216, 2,
+ 2, 2, 2, 2, 2, 2, 2, 6, 208,208,229,208,227,208,216, 2,
+208,208, 0, 6,229,208, 0,208, 208,208,208,208, 0,208, 5, 5,
+
+// state[904 + 2] 0x026880 Byte 4 of 4 (property)
+ 5, 5, 5, 5,218,208, 6, 6, 5, 2,229,219,229,229,229,208,
+208,208, 6, 5, 5, 5, 5, 5, 5,208,208,208,208,208, 2, 2,
+ 2, 2, 2, 6, 5,208,208,208, 229, 0,208,208, 5,208, 6,208,
+ 6, 6, 3, 2, 2, 2, 2, 2, 229,208,208,208,208,208, 2,208,
+
+// state[905 + 2] 0x0268c0 Byte 4 of 4 (property)
+229,208, 6, 6, 6, 5,208,229, 208,208,208,208, 5,208, 2,208,
+208,208,208,208, 6, 2,208, 2, 218, 2, 5,208,208,233,208,208,
+208, 5, 6, 6,208, 6, 6, 6, 6,208, 4, 5, 5, 5,208, 6,
+ 2, 5,208, 5,208, 5, 5,208, 208,208,208,208,208,208,208,208,
+
+// state[906 + 2] 0x026900 Byte 4 of 4 (property)
+ 5, 6,208,208, 5, 5,208, 6, 6, 6,208, 6,208,208, 5, 5,
+208, 5,208, 6,208,208, 5, 6, 208,208,208, 5, 6,208,208,229,
+ 2,208,208,208, 5, 5,229,208, 208, 5,208,229,208, 2, 2, 2,
+ 2, 2,208,208,208,208,208,217, 6,229, 5,208,208, 6, 2,208,
+
+// state[907 + 2] 0x026940 Byte 4 of 4 (property)
+ 6,208,208, 2, 5,229,208, 5, 208,208,208,208, 5, 6,208,208,
+208,233,208, 5,208, 5, 6, 5, 208, 5, 6,208, 6,208,208,208,
+208,208, 5, 6, 6,208,229,208, 208,208, 6, 5, 6,208,208, 4,
+ 6, 6,208,208,208,208,229,208, 208, 5, 5,229,208, 5, 5,208,
+
+// state[908 + 2] 0x026980 Byte 4 of 4 (property)
+208,208,208,208,208,229,208,208, 208,208, 5, 6,208, 6, 5,208,
+208,208, 6, 5, 6,208,208,208, 208,233,208,208,208, 5, 5, 5,
+ 5,208,208,229,208,208, 6,208, 208,208,208,208,208, 5, 5,208,
+208,208,208, 5, 5, 5,208, 6, 5,208,208,208,208,208,208,208,
+
+// state[909 + 2] 0x0269c0 Byte 4 of 4 (property)
+ 5,208,208, 2,208,208, 5,229, 229,229,229, 6,208, 6,208,229,
+229, 5, 6,208,208,208,208,208, 218, 6,208, 6, 2,228, 6,229,
+229,229,208, 5,208,229,208,208, 5, 6,208,208, 6,208,229, 6,
+208,208, 5, 5,229,208,208, 2, 208,229, 5,208,208, 2, 5,208,
+
+// state[910 + 2] 0x026a00 Byte 4 of 4 (property)
+ 5,208, 2, 5,208, 5,208,229, 229,229,208,208,208,208,208,208,
+208, 2,208,208,208,208,229,208, 208,208,208, 6,227,208,172,208,
+208,229, 0,229,229, 6,208,229, 208, 6, 5, 6,208,208, 5,208,
+208,208,208, 6,229,229,208,208, 6, 6,208, 2, 5,208,229,208,
+
+// state[911 + 2] 0x026a40 Byte 4 of 4 (property)
+ 6, 6, 5, 5,208, 6, 6,208, 208,208,208,229,229,229,208,208,
+ 6, 5, 5, 5, 5, 6, 6, 6, 233,208, 2, 2,208,229,208,208,
+208,229,208,208,229,208,208,208, 208,208,208,208,208, 6, 6, 6,
+ 2,208,208, 2,208,208,208,229, 208,208,208,208,208,208, 5, 6,
+
+// state[912 + 2] 0x026a80 Byte 4 of 4 (property)
+ 6, 6, 2, 2,208,208,208,229, 208,208,208,208, 4,208,208, 2,
+ 2,229,208,208,229,229,229,208, 208,208,229,208,208,208, 6, 6,
+ 6, 6, 6, 6,208,208, 2,229, 2,208,208,208,208,229,208, 5,
+208,208, 5, 6, 6, 2,208,228, 208, 6, 6, 2, 2,229,208,208,
+
+// state[913 + 2] 0x026ac0 Byte 4 of 4 (property)
+208, 5, 5,229,208,208,208,208, 229, 6,229,208, 5, 6,229, 5,
+ 5, 5,208, 6,208,208, 6,208, 2,229, 5,229,208,208,208, 6,
+ 5, 6,208,208,208,208,208, 5, 5, 5,229,229,208,208, 6,229,
+208,208, 5,208,208, 6,208,208, 208,208,208,208,208, 5, 5,233,
+
+// state[914 + 2] 0x026b00 Byte 4 of 4 (property)
+208,208,208,229,208,208,229,208, 208,208, 5, 5, 5, 5, 5, 5,
+ 5, 2, 6,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5,208, 2,208, 229,208,229,208,208,208,208,208,
+
+// state[915 + 2] 0x026b40 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208, 6,208, 6, 6, 6, 6,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208, 6, 6,
+ 6, 6,208, 6, 6, 6, 2, 2, 5, 5, 2, 5, 5,229,208,229,
+208,229,229,208,208,208,208,208, 208,208,208,208,229,208,208,208,
+
+// state[916 + 2] 0x026b80 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,
+ 6, 3, 2, 5, 2, 5, 6,229, 208,208,229,208,229,208,208,208,
+
+// state[917 + 2] 0x026bc0 Byte 4 of 4 (property)
+229,208,208,208,208,208,229,229, 208,229,208,208,208,229,208,208,
+ 6,208,229,208,229,208,208, 6, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208, 6, 208,208,208,208,227,208,208,208,
+208,208,208,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[918 + 2] 0x026c00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,229,208, 208,208, 6,208, 6,208,208, 2,
+ 2, 5, 2, 2, 2, 6, 2, 5, 6, 4,208,229,229,208,208,208,
+208,208,229,208,208,208,208,208, 229,208,208,208,208,208,229,208,
+
+// state[919 + 2] 0x026c40 Byte 4 of 4 (property)
+208,208,229,208,229,208,208,208, 208,208,208,208,229,208,229,208,
+208,208,208,208,229,208,208,208, 208,208,208,208,208, 6,208,208,
+208,208,208,208,208,208,229,208, 208,208,208,208,208,208,208,208,
+208,219,208,233,208,208,208,208, 208,208,208,208,208, 6, 5, 5,
+
+// state[920 + 2] 0x026c80 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6, 6,208,
+208, 6, 6, 6, 6,218,208, 5, 5, 2, 2, 2, 2, 5, 2, 2,
+
+// state[921 + 2] 0x026cc0 Byte 4 of 4 (property)
+ 5,229,208,208,208,208,208,208, 208,208,229,208,208,229,208,208,
+208,208,208,208,208,208,208,208, 208,227,208,208,208,228,229,208,
+229,208,208,208,208,229,208,208, 208,208,208,229,208,208,208, 6,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,229,
+
+// state[922 + 2] 0x026d00 Byte 4 of 4 (property)
+208,208,208, 6, 6,208, 6,208, 208,208,208,227,208,208,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208, 5,
+ 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[923 + 2] 0x026d40 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,219,
+208,208,208,219, 6, 6, 3, 2, 2, 2, 2, 2, 2, 5, 5,208,
+ 2,208,208,208,208,208,229,229, 208,229,229,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 229,208,208,208,208,208,208,208,
+
+// state[924 + 2] 0x026d80 Byte 4 of 4 (property)
+229,208,208, 6,208,208,208,229, 208,208,208,208,208,229,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208, 6, 6, 6, 6,
+ 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[925 + 2] 0x026dc0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,208,208,208, 6,208, 208,208,208,208, 5,208,208,208,
+208,208, 6,208, 6, 6, 6, 3, 2, 2, 5,216, 2, 2, 2, 2,
+ 5,208,208,208,208,208,208,208, 208,208,208,208, 5,208, 2,208,
+
+// state[926 + 2] 0x026e00 Byte 4 of 4 (property)
+208,229,229,229,208,208,208,208, 229,208,229,208,208,208,208,208,
+229,229,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,229,229, 6,208,208, 208,208,208,208,208,208,229,208,
+229,208,208,208,208,208,208,208, 208,208,208, 6, 6, 6, 6, 6,
+
+// state[927 + 2] 0x026e40 Byte 4 of 4 (property)
+ 4, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6,208,208,208,208, 4, 6, 6, 208, 6,208, 6, 6, 6, 5, 2,
+ 2, 2, 5, 2, 2, 2, 2,216, 2, 2, 2, 2, 6,208,208,208,
+
+// state[928 + 2] 0x026e80 Byte 4 of 4 (property)
+208,208,208, 6, 5, 6,208,229, 208,229,208,229,208,208,208,208,
+208,208,208,227,229,208,229,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 208,229,208,208,208,208,208,208,
+208,208,208,208,208,208,208,229, 208,208,208,208,229,229,229,208,
+
+// state[929 + 2] 0x026ec0 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208, 6, 6,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[930 + 2] 0x026f00 Byte 4 of 4 (property)
+ 5, 5, 5,216, 5, 5,208,227, 6, 6,208,208,208,208,208,208,
+208,208,208, 6,218,208, 6, 6, 6,208, 2, 2, 2, 2, 2, 2,
+ 2,208,208, 6,208, 2, 5, 6, 2,208,208, 0,208,208,229,208,
+208,208,208,229,208,208,208,208, 229,229,229,208,208,208,208,208,
+
+// state[931 + 2] 0x026f40 Byte 4 of 4 (property)
+229,208,208,208,229,229,208,229, 208,208,229,208,208, 6,208,208,
+208,208,229,229,227,208,208,229, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208,208, 6,208, 6,
+ 2,208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[932 + 2] 0x026f80 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,
+208, 6,208, 6,233, 6, 6,208, 6, 6, 6,208, 6, 6, 6, 5,
+ 2, 5, 2, 2, 2, 2, 6, 5, 6, 6, 5, 6,208,208,229,208,
+229,208,208,229,208,229,229,208, 208,208,208,208,208,208,208,229,
+
+// state[933 + 2] 0x026fc0 Byte 4 of 4 (property)
+208,208,208,229,229,229,229, 6, 208,229,208,208,229,229,208,208,
+208,208,208,208,208,208,208,208, 208,208,219, 6, 6, 6, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5,
+
+// state[934 + 2] 0x027000 Byte 4 of 4 (property)
+ 5, 5, 5, 6,208,208,208,208, 6, 6, 6, 6, 6,208,217, 2,
+ 2, 2, 2, 2, 0,208,208,208, 208,208,208,208, 2,208, 2, 2,
+208,208,208,229,208,208,229,229, 0,208,208,208, 0,229,208,208,
+229,229,229,208,229,229, 6,208, 208,208,229,208,208,208,208,208,
+
+// state[935 + 2] 0x027040 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,229, 6,217,
+ 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+229, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 5,208,208,229,229,
+
+// state[936 + 2] 0x027080 Byte 4 of 4 (property)
+229,227,208,208, 0,229,208,219, 208,229,208,208,208,208,208,229,
+208,208,229,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+ 6,208, 6,208,208,208, 6,208, 208,208, 6, 6, 6,208, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[937 + 2] 0x0270c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 208,208, 6, 2, 2, 5, 5,208,
+229,208,229,208,208,208,208,208, 208,208,208,208,208,229,208,208,
+208,208,208,208,208,208,208,208, 208,229,208,208,229, 6,208,208,
+208, 6, 6, 6, 4, 2, 2, 2, 5, 5, 5, 5, 5,216, 5, 5,
+
+// state[938 + 2] 0x027100 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 2, 5, 2, 5, 5,228,208,208,
+208,208,229,229,229,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+ 6, 3, 2, 2, 2, 2,208,208, 208,228,229,208,229,229,208,208,
+
+// state[939 + 2] 0x027140 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208, 5, 5,
+ 5, 5, 5,208, 5, 2,229,208, 208,208,208,208,208,208,208,208,
+208,208,208, 6, 5, 5, 5, 5, 5,208,208, 2, 2, 2,208,208,
+208,208,208,208, 5, 5, 5, 5, 208, 6,208,208,208,208,208, 6,
+
+// state[940 + 2] 0x027180 Byte 4 of 4 (property)
+ 6, 5, 6,208, 2, 5,208,229, 208,208,208, 5,219, 5, 5,208,
+229,208,208,229,208,208,208,208, 208,208, 5, 5,229,208,208, 6,
+ 5, 6, 6,208,208,229,229,208, 208,208,208,208,208, 6, 6, 6,
+ 6,208, 5, 5, 5, 5, 5, 6, 6, 6,208,208,229,208,208,208,
+
+// state[941 + 2] 0x0271c0 Byte 4 of 4 (property)
+ 6,208,208,208,208,208,208,208, 208, 5, 5, 5, 6,208, 6, 6,
+208,208,208,229,208, 5, 6, 5, 208, 6, 6, 6,229,229,208,229,
+229,208,208,208,208,229,208,208, 5, 5, 5, 5, 5, 0, 6, 6,
+ 6,208,208, 5, 5, 5, 6, 6, 6, 6, 6,208,229,208,229,208,
+
+// state[942 + 2] 0x027200 Byte 4 of 4 (property)
+208,208, 6, 5, 6, 6,208,208, 229,208, 6,208, 6,208,208, 6,
+208,229,208,208, 6,208,208,208, 208, 6,208, 5,208,229,208,229,
+208, 6, 6,216, 5, 5, 6,208, 229,208,229,229,229,208,208,208,
+208,219, 6, 6, 5, 5, 5, 5, 5,208,208,208,208,208,229,208,
+
+// state[943 + 2] 0x027240 Byte 4 of 4 (property)
+208,208,208,229,208,229,208,229, 229,208,229,208,208,208,208,208,
+208,208, 6, 6, 6, 5, 5,208, 208, 2, 2,208,208, 2,208,229,
+208,208,229,208,208,229,208,229, 208,208,208,229,208,208,208,208,
+208, 6, 6,208,208,208,208,208, 6, 6, 6,216, 5, 5, 5, 5,
+
+// state[944 + 2] 0x027000 Byte 3 of 4 (relative offsets)
+-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+
+// state[945 + 2] 0x027280 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 6,208,208,208, 208, 2, 2,216, 2,208,216,208,
+227,208,208,229,208,227,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 208,208, 6,208,229,208,208,208,
+208,208,208, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6,208,
+
+// state[946 + 2] 0x0272c0 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 208,229,208,208,208,227,208,208,
+208,208,208,208,208,208,229,208, 0,208, 6,208,208,208,208,227,
+229,208, 6, 6, 6, 6, 0, 5, 5, 5, 5, 5, 5, 5, 5,208,
+ 5, 6, 6,208, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[947 + 2] 0x027300 Byte 4 of 4 (property)
+ 2,229, 2,229,229,208,229,208, 229,229,208,208,208,229,227,229,
+208,229,208,229,229,229,229,208, 208,208,208,208,208,208, 6,208,
+208,208,208,208,208, 6,208,208, 208,208,208,208,208,208, 6, 6,
+ 6, 6, 6, 6,217, 5, 5, 5, 5, 5, 5,208, 6,208, 6,208,
+
+// state[948 + 2] 0x027340 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 5,229,208,208,208,208,208, 229,229,208,208,208,208,208,208,
+208,208,208,208,208,208,208,229, 208,208,229,208,208,229,208,208,
+208,208,208,208,227,208,208,208, 208,208,208,208,208,208,208,208,
+
+// state[949 + 2] 0x027380 Byte 4 of 4 (property)
+208,208,208,208,208, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5,208, 6, 6,208, 208, 6, 5, 2, 2, 2,208, 5,
+ 2,229,229,208,229,208,229, 6, 229,229,208,208,208,208,208,208,
+208,208,208,208,227,208,208,227, 208,208,208,229,229,208,208,208,
+
+// state[950 + 2] 0x0273c0 Byte 4 of 4 (property)
+208,208,208,208,229,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208,208, 6, 6, 6, 6, 4, 4, 5, 5, 5, 5,
+ 5, 5, 5, 5,208, 6,208, 6, 208,208,208, 6,208, 3, 3, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 208,229,208,208,208,208,233,229,
+
+// state[951 + 2] 0x027400 Byte 4 of 4 (property)
+208,208,208, 6,229,208,208,229, 208,208,208,229,208,208,208,208,
+228,229,208,208,229,208, 0,208, 208,229,208,208,208,208,208,208,
+ 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208,208,
+ 6,208, 6,208,208,208,208, 6, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[952 + 2] 0x027440 Byte 4 of 4 (property)
+208, 2, 2, 2,229, 0, 0,229, 208,233,208,208,208,229,208,229,
+227,229, 0,229,208,208,208,229, 208,229,208, 6,208,208,208,208,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208,208,208,208,208, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[953 + 2] 0x027480 Byte 4 of 4 (property)
+ 6,208,229,208, 5, 2, 5, 2, 208, 2, 2, 2, 2, 2,208,208,
+208,208,208,229,208,208,229,208, 208, 6,208,208,208,208,208,208,
+ 6,208,208,208,208,208,208,208, 208,208,208, 5, 6, 6, 6, 6,
+ 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208,208,229,
+
+// state[954 + 2] 0x0274c0 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 2, 2, 2, 0,208,208,208,208, 6,208,227,
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+ 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6,208,208, 2, 2, 2,
+ 2,208,208,208,229,208,208,208, 208,229,208,229,208,208,208,208,
+
+// state[955 + 2] 0x027500 Byte 4 of 4 (property)
+208,208,208,208,208, 6,208,208, 208, 6, 6, 6, 6, 6, 5, 5,
+ 5, 5, 5, 5, 5, 5, 6, 6, 208, 2, 2, 2, 2,229,229,208,
+208,208,208,208,208,208,208,208, 208, 6,208,208,208, 6,208,208,
+208,208,208,208,208,208,208,208, 6, 6, 5, 6, 6,208, 2, 2,
+
+// state[956 + 2] 0x027540 Byte 4 of 4 (property)
+ 2, 5, 5,229,229,229,208,229, 229,229,208,229,208,229,208,208,
+208,208,208, 6,208,208, 5, 5, 5, 6, 2,208,208,208,229,208,
+208,208,208,208,208,208,208,208, 208, 6, 6, 6, 2, 2,208,229,
+208,208,229,208,208,208,208,208, 208,208,208, 6,208,208,208,208,
+
+// state[957 + 2] 0x027580 Byte 4 of 4 (property)
+208, 6,208, 2,208,208,208,208, 208, 6,208,208,208,208,208, 5,
+ 2,208,208,208,208,208,208,208, 208,208,208,208,208,208,208, 5,
+ 6, 6, 6,208,208, 6, 2,208, 208,208,208,208,208,208, 6,208,
+ 2, 2, 5,208,208,229,208,208, 208,208,229,229,208,208,208, 6,
+
+// state[958 + 2] 0x0275c0 Byte 4 of 4 (property)
+208,208,208,208,208, 2,208,229, 208,208,208,208,229, 5,229,208,
+208,208,208, 6,208,229,208,228, 208,208,208,208,208,208,208,208,
+ 5,208,208,208, 5, 5,208,208, 208,208,208,229,229,208, 5, 5,
+ 6,229,208,229,208,208,208,194, 208,208,208,208, 6, 5, 5,229,
+
+// state[959 + 2] 0x027600 Byte 4 of 4 (property)
+208, 5,229,208,208,208,208,216, 229,208,208,229, 5,229,208,229,
+229,229,208,208, 4, 4, 5, 6, 6,208,208, 6,208,208,229,208,
+208,229,229,208,208,229,208,208, 208,208,208,208,208,208,208,208,
+208, 4, 5, 5, 5,208,208,208, 2, 5, 5, 5, 5,229,208, 6,
+
+// state[960 + 2] 0x027640 Byte 4 of 4 (property)
+208,229,208,229,208,208,208,208, 208,208,229,208,208,208,208,208,
+208,208,208, 6, 6, 5, 5, 5, 5, 5, 5,208,208,208,208,208,
+208,208,208,229,208,229, 6,208, 208,208,208,208,208,208,208,208,
+ 6, 6, 5, 5, 6,208, 6,208, 6,208, 6, 2, 2, 2,229, 5,
+
+// state[961 + 2] 0x027680 Byte 4 of 4 (property)
+229,229,208,208,228,229,229,229, 208,229,208,208,208,208,208,208,
+208,229,208, 4, 5, 5, 5, 5, 5, 5, 5,208,229,208,208, 2,
+ 2,208, 2, 5,229,208,208,208, 229,208,208,229,208,227,208,208,
+208,208,208,208,229,208,208,208, 208,208,208,219,208, 5, 5, 5,
+
+// state[962 + 2] 0x0276c0 Byte 4 of 4 (property)
+ 5, 6, 6,208, 6, 6, 2, 2, 2, 2, 2, 2, 2, 5, 5,229,
+208,208,208,208,229,229,208,229, 208,208,229,229,229,208, 0,208,
+208,208,208,208,208,208,208,208, 208,229,208,208,208, 6, 6, 6,
+ 5, 5,208,208, 6, 6, 2, 2, 229,208,208,229,208,208,229,208,
+
+// state[963 + 2] 0x027700 Byte 4 of 4 (property)
+229,229,208,208,208,208,208,229, 229, 6,208,208, 6,208, 4, 5,
+ 2, 5, 5, 6, 6,229, 2, 2, 2, 2, 6, 6, 6,208,208,208,
+229,229,208,228,229,208,229,229, 6,208, 6,208,208,229,208,208,
+208,208,208, 6, 6,216, 5, 5, 5, 2, 2, 2, 6,208, 5, 5,
+
+// state[964 + 2] 0x027740 Byte 4 of 4 (property)
+ 5, 5,229,208,208,208,208,208, 208,208,208,229,208,208,219,208,
+229,229,228,208,229,208,208,229, 229,229,208,208, 6, 6,208, 6,
+ 6, 6, 5,208, 6, 6, 2, 2, 2, 2,229,208,208,208,208,208,
+ 5,208,208,229,229,208,208,208, 208,229,208,208,208,208,208,208,
+
+// state[965 + 2] 0x027780 Byte 4 of 4 (property)
+208,208,208, 6, 5, 5, 5, 5, 5, 6, 2, 2, 5,208,208,229,
+208, 6,208,208,229,229,208,229, 6,208,219,229,208,229,208,208,
+ 6, 5,208,218,218, 2,208,208, 208,229,208,208,229,208,208,208,
+208,208,208, 5, 2, 5,229,208, 229,208,208,208,208,208, 2, 2,
+
+// state[966 + 2] 0x0277c0 Byte 4 of 4 (property)
+ 2, 2,208,229, 6, 6, 6, 5, 208,208,229, 6,229,208,208,208,
+208,208,208, 3,208,208,208,208, 208,229,208,208, 6, 2,208,208,
+ 6, 5, 6,208, 6, 6, 5,208, 208,208,208, 5, 6, 5, 5,208,
+ 6,208,208,208,208, 6, 6, 5, 5,208, 5, 5,229,208, 5,208,
+
+// state[967 + 2] 0x027800 Byte 4 of 4 (property)
+ 5, 5,208, 5,208, 2,208,208, 6,208,219,208, 5, 6,229,208,
+208,208,208,208, 6, 5, 5, 5, 208,208,208,208,208,208,229,229,
+208,208,208,208, 6, 6, 6, 6, 208,208,229,229, 6,208,208, 6,
+ 5, 5, 5, 5, 6,208, 6, 6, 6, 6, 6,208,229,229,208,229,
+
+// state[968 + 2] 0x027840 Byte 4 of 4 (property)
+208, 6, 6, 5, 5,208,208, 6, 208, 6, 2,229,208,208,229,229,
+208,229,208,208, 6, 5, 5, 5, 208, 6, 6,208, 6,208, 6, 6,
+208,229,208,229,229,208,229,208, 229,208,229,208,208,208,208,208,
+ 5, 5, 5, 5, 6, 6, 6,208, 229,229,229,208, 6,208,208,208,
+
+// state[969 + 2] 0x027880 Byte 4 of 4 (property)
+ 5, 5,229,229,229,208,208, 6, 5, 5, 5, 6,219,229,208,208,
+ 6, 2,208,208,208, 5,208, 6, 5, 5,208, 6,229,229,229, 2,
+ 5, 5,229,208, 5,208,229,208, 208, 6, 6,208, 6,208,208, 5,
+ 6,208,229,208,208,229, 6,208, 229,229,208,229,229,208,208,208,
+
+// state[970 + 2] 0x0278c0 Byte 4 of 4 (property)
+208,229,208,229,208, 5, 5, 5, 208,208, 6, 6, 6,208,208,208,
+208,208,229,208,208,208,208,208, 208,208,229,208,208, 6, 5, 6,
+ 2,208,208,229, 6,208, 6, 2, 208,229,229,208,229,208,208,208,
+208,208, 2, 2,208,229,208,229, 229,208,208,229,229,208,208, 6,
+
+// state[971 + 2] 0x027900 Byte 4 of 4 (property)
+ 6, 2, 2,229,208,208,208,208, 208,208, 6,208,208,229,229,229,
+208,229,208,208,208,208,229,229, 208, 6, 6,229,208,208,208,208,
+208,208,208,208, 5, 2,208,208, 6,208,208,208,208, 2,208,208,
+ 6,208,208, 6, 6, 6,208,208, 2, 2,208,208,229,229,229,208,
+
+// state[972 + 2] 0x027940 Byte 4 of 4 (property)
+208,208,208, 2,208, 6, 6, 2, 208, 6,208, 6,229,229, 6, 6,
+ 3,208, 6,208, 2,208,208,208, 208, 5,208,208,208,208, 6, 6,
+ 6,208,229,229,229,229,229, 5, 208,208,208,208, 6,208,229,208,
+229,229, 6,229,208,208,219,208, 208,208,229,208,229,208,229,208,
+
+// state[973 + 2] 0x027980 Byte 4 of 4 (property)
+208,208,208, 6, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,
+ 6, 6,208, 6, 2, 2, 2,208, 6,208,208,208,208,229,229,229,
+229,208,208,208,229,229,208,229, 208,208,208,208,229,229,229,208,
+ 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,208,208, 6, 6,208, 2,
+
+// state[974 + 2] 0x0279c0 Byte 4 of 4 (property)
+ 2,208,208, 6,208,208,208,219, 208,229,208,208,229,208,208,208,
+208,208,229,229,229,229,229,229, 208,208,208,208,208,208,208,208,
+208,208, 6, 6, 5, 5, 5,208, 5, 6,208,208, 6, 6, 6, 2,
+208,208,208,208,208,229,229,229, 229,208,229,229,229,208,229,208,
+
+// state[975 + 2] 0x027a00 Byte 4 of 4 (property)
+229,208,208,229,208,208,208,208, 208,208,208,208, 6,219, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 6,208,208,208,208,208,208, 6,
+208, 6, 6,208,208, 6,229,229, 208,229,229,208,229,208,208,229,
+229,229,208,208,208,229,208,208, 208,229,229,208,229,208,229,208,
+
+// state[976 + 2] 0x027a40 Byte 4 of 4 (property)
+208, 6,208,208,208,208,208,208, 6, 5, 5, 5, 5, 5, 5, 5,
+208,208, 6,208, 6, 6,208, 6, 208, 5,229,208,208,208,208,208,
+229,208,208,229,208,208,229,229, 208,208,229,208,208,208,229,229,
+ 6,208,208,208,208,229,229,208, 229,229,208,229,208,229,208,208,
+
+// state[977 + 2] 0x027a80 Byte 4 of 4 (property)
+208,208,229,208,228,229,208,208, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 6,208, 6,208,208, 229,208,229,208,229,229,229,229,
+229,229,229,229,229,208,229,208, 229,208,208,229,229,208,229,208,
+208,208,208,208,208,208,208,208, 6,208,208, 6, 6, 5, 5, 5,
+
+// state[978 + 2] 0x027ac0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,208, 6, 6, 3,208,
+208, 6,229,229,208,229,208,229, 208,229,229,208,229,208,229,208,
+229,229,229,229,229,229,229,229, 208,208,208,208,208,208,208,208,
+208, 6,208, 6, 0, 5, 5, 6, 208, 6,208, 6, 2,208,208,229,
+
+// state[979 + 2] 0x027b00 Byte 4 of 4 (property)
+208,219,229,229,229,208,208,229, 229,229,229,229,229,208,208,229,
+208,229,208,208, 6,229,208, 5, 5, 5, 5, 5,229,208, 6,208,
+208,208,208,208,208, 5, 6,208, 229,229,229,208,208,208,229,229,
+229,208,208,229,208,208,208, 6, 5, 5, 5, 5, 5, 5, 5,208,
+
+// state[980 + 2] 0x027b40 Byte 4 of 4 (property)
+208,208,229,208,208,229,208, 6, 229,229,208,208,208,229,229,229,
+208,208,208,229,208,208,208, 6, 5,217,208,208,229,229,229,208,
+229,208,208,229,208,229, 6,208, 208,208,208,208,208,208,217, 5,
+ 5, 5, 5, 6,208, 6, 6,208, 208,229,229,229,229,208,208,208,
+
+// state[981 + 2] 0x027b80 Byte 4 of 4 (property)
+ 5, 5, 5,208,208,208,229,208, 229,208,208, 5, 5, 6,208,208,
+208,208,229,229,208, 5, 5, 6, 208,208,208,229, 6,208,229,208,
+ 6, 6,229,208, 6,208, 6, 6, 208,208, 6,229,208,208,208,229,
+229,208,208,228, 6,229,208,208, 208, 6,208,208,208,229, 4, 6,
+
+// state[982 + 2] 0x027bc0 Byte 4 of 4 (property)
+208,208,208, 6, 2, 2,229, 4, 6,229,229,208,229,229,229,208,
+208, 6, 6,208,208,229, 6, 6, 229,208,208,208,208, 6, 6, 5,
+229,229,208, 5, 6,208,208,208, 6,229,208,229, 6,208,208,208,
+208,208, 6,208, 5, 2, 2,229, 229,208,208,208,208,208,229,229,
+
+// state[983 + 2] 0x027c00 Byte 4 of 4 (property)
+208,208, 6,208,208,208,229,208, 6,229,229,208,208,208, 6, 3,
+208,208,229,208, 5,208, 5, 2, 208,229, 6, 6, 2, 5, 5, 6,
+208,229, 6,229, 6,208, 2,233, 208,229,229,229,208,208,208,208,
+208, 6,208, 6,229,229,208,229, 208,208,208,229,229,208, 5,229,
+
+// state[984 + 2] 0x027c40 Byte 4 of 4 (property)
+208,229,229,208,208,229,208,208, 208,229,208, 6, 6,208,208,229,
+229,208,229,229,208,208,208,208, 229,208,208,208,229,229,208,208,
+208,208,208,208, 5, 5, 6, 6, 229,229,229,208,229,229,208,208,
+208,208,229,229,229,208,208, 5, 5, 6, 2,229,208, 6,229,208,
+
+// state[985 + 2] 0x027c80 Byte 4 of 4 (property)
+208, 5,229,208,208,229,229,229, 229,208, 5,208,229,229,208,208,
+229,208,208,208,208,208, 6,229, 208, 6,208,208,208,208,208,208,
+ 5,208,229, 5,229,208,229,229, 229,208,208, 2,208,208,208,208,
+208,229,229,208,229,208,208,208, 4, 5, 6, 6, 2, 6,229,208,
+
+// state[986 + 2] 0x027cc0 Byte 4 of 4 (property)
+229,208,208,208,208,229,229,227, 5,208,208,208,208,208,208,229,
+208,208,208, 5,208, 6,229, 5, 2,229,208,208,229,229,208,229,
+229,208, 5, 6, 6,208,229,208, 229,208,229,229,208,208,208,229,
+ 6, 5,208,208, 6,229,208,208, 208,208,229,208,208, 6, 5, 6,
+
+// state[987 + 2] 0x027d00 Byte 4 of 4 (property)
+ 6,229,208,208,229,208,208,208, 6, 6, 6, 2,208,208,229,208,
+208, 6, 6, 6,208,208,208,229, 208,208,208, 6, 6,208,208,208,
+208,229, 6,208, 0,208,208, 6, 5, 6,229,208,229,208, 6, 5,
+ 5,216,229,208, 6,208,208,208, 208,208,229, 5, 5, 5, 5, 5,
+
+// state[988 + 2] 0x027d40 Byte 4 of 4 (property)
+ 5, 5, 5, 6,208,208, 2,208, 208,208,229,229,229,208,208,208,
+208,208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208, 6, 2,
+208, 6, 6,208,208,208,229,229, 208,208,208,229,208,229,208,208,
+208,208,208, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,208,208,208,
+
+// state[989 + 2] 0x027d80 Byte 4 of 4 (property)
+ 6, 3,208, 5,227,229,229,208, 208,208,229,208,208,229,208, 5,
+ 5, 5, 5, 5, 6, 6, 6,208, 217,208,208,229,229,229,229,208,
+228,208,208,208,208, 5, 5, 5, 5, 6,208, 2, 2, 2, 2, 2,
+ 5,208,229,208,229,229,208,208, 229,208,229,208, 5, 5, 5, 5,
+
+// state[990 + 2] 0x027dc0 Byte 4 of 4 (property)
+ 5, 5, 5, 5,208, 6, 6,208, 6,229,208,208,208,208,208,208,
+208,208,208,208,208,208,208,208, 6, 5, 5, 6, 5, 2,208,229,
+208,229,229,208,208,208,208,208, 208,208, 6, 5, 5, 5, 5, 5,
+ 5, 6,208, 6, 6,208, 6, 6, 2, 2, 2,208,208,208,229,229,
+
+// state[991 + 2] 0x027e00 Byte 4 of 4 (property)
+229,229,229,229,208,208,208,208, 208,208,208,208, 5, 5, 5, 5,
+172,208, 2, 2,208,208,229,229, 229,229,208, 6, 6, 2, 5, 6,
+ 5, 5, 5, 5, 5,208,229,208, 208, 6,219,208, 6, 5, 5,208,
+ 2,229,229, 5, 5, 5, 5,208, 208,208, 2, 2, 5,229,208,229,
+
+// state[992 + 2] 0x027e40 Byte 4 of 4 (property)
+208, 6, 6, 5, 6, 2,208,208, 208, 5, 6, 2, 6, 5,208, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 208,208,208,229, 5,208, 0, 2,
+208, 5, 6,229, 5, 2, 2,208, 6, 6, 2, 2,229,229, 5,208,
+ 2, 2,229,229,208, 5,229,208, 208,208, 6, 2, 2,208, 6, 2,
+
+// state[993 + 2] 0x027e80 Byte 4 of 4 (property)
+ 2, 2, 2, 2,208, 6,229,208, 208, 5, 5, 5, 5, 5,229,229,
+208, 6,229,229, 5, 2, 5, 5, 5, 5, 5, 5, 6,229,208,208,
+229,229,229,229,208,208,208,208, 208, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6,208,229, 6, 5,229,208, 208,229,229,229,208,208,229,208,
+
+// state[994 + 2] 0x027ec0 Byte 4 of 4 (property)
+229,229,208,208, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208,208,
+ 2,208,208,229,208,208,229,229, 208,229,227,208,208,208,219, 5,
+ 5, 5, 5, 5, 5, 5, 5,208, 6, 2, 2,208,208, 2, 5,208,
+208,229,229,208,208,229,229,219, 208,208,229,208,208,208, 6,229,
+
+// state[995 + 2] 0x027f00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6, 2, 2, 2,229,229,
+208,229,229,208,229,208,208,208, 208,229,208,208,208,208,208,208,
+208,208, 5, 5, 6,208,208, 5, 229,208,229,208,208,229,208, 0,
+229,229,208,229,208,208,208, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[996 + 2] 0x027f40 Byte 4 of 4 (property)
+ 6,208,208, 6,208,208,208, 2, 2,229,208,229,208,229,229,229,
+229,229,208,208,208,208,229, 5, 5, 5, 5, 5, 5,208,229,229,
+208,208,229,229,229,208,229,229, 229,208,208,208, 5,208, 6,208,
+ 6, 6, 6,208,208, 6,229,229, 229,208,208,229, 0,229,208,208,
+
+// state[997 + 2] 0x027f80 Byte 4 of 4 (property)
+208,229,208, 5, 5, 5, 6, 6, 208, 6, 6,208, 2,229,229,229,
+229,208,208,208,229,208, 5, 5, 5,208,229,229,229,208,208,208,
+208,208,208,229,208,229, 5, 6, 208,208, 5,208,208,208,229,208,
+208,229, 6, 6,208,229,208, 4, 208,208,208,229, 5, 6, 6, 2,
+
+// state[998 + 2] 0x027fc0 Byte 4 of 4 (property)
+208,208,229,208,208,229,208, 6, 6,208, 6, 6, 6,208, 6, 6,
+ 2, 2,229,229,229,208,208,208, 229,229,208,229,208,229,208,208,
+208, 6, 5,208,208, 6,208, 6, 218, 6, 6,218,213, 2,208,229,
+ 6, 5,208,229,229,229,208,229, 208,229, 0,208,229,208,208,208,
+
+// state[999 + 2] 0x028000 Byte 4 of 4 (property)
+208, 6, 5, 5, 6, 6,208, 6, 2, 5, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2,208,208,227,229,208, 229,229,229,208,229,229,208,208,
+229,208,229,208, 0,208,208,208, 229,208,208,208,208,208,229, 6,
+ 2, 5, 6, 6, 6, 6,208,208, 6, 2, 2, 2, 2, 2, 2, 2,
+
+// state[1000 + 2] 0x028040 Byte 4 of 4 (property)
+208,229,208,229,229,208,229,229, 229,208,208, 0,229, 0,227,229,
+229,208,208,229,208,208, 6, 6, 2, 5, 5, 5,208,218,208, 6,
+208,218,208,208, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2,229, 6,208,229,229, 0,229,229,208,227,208,208,208,
+
+// state[1001 + 2] 0x028080 Byte 4 of 4 (property)
+229,208,229,229,208,208,208,208, 208, 6, 4, 5, 5, 5, 5,208,
+208,208, 6,208, 2, 2, 2, 2, 2, 2, 2,208,208,229,229,208,
+208,229,229,229,229,208,208,208, 208,208,208,208,208,229,208,229,
+229,208,229,208,229,229,208,208, 208, 6, 6, 4, 2, 5, 5, 5,
+
+// state[1002 + 2] 0x0280c0 Byte 4 of 4 (property)
+ 5, 5, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 5,208,208, 0,208,229, 229,208,208,208,208,208,229,229,
+229,208,208,208,208,208,208,208, 208, 5, 6, 2, 5, 6,208, 6,
+218, 6, 6,208,216, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208,
+
+// state[1003 + 2] 0x028100 Byte 4 of 4 (property)
+208, 2, 2,229, 6,229,208,229, 229, 0,227,208,208,208,208,229,
+229,227,208,229,208,229,208,208, 208,229,229,208, 6, 6, 2, 2,
+ 2, 5, 5, 5, 5, 5, 5,208, 208,208,208,208,208,208,208, 6,
+ 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[1004 + 2] 0x028140 Byte 4 of 4 (property)
+ 2, 2, 2, 2, 2,208,208, 2, 208, 2,229,229,208,224,208,229,
+227,208,208,208,229,208,208,229, 208,229,208,208,208,208,227,208,
+208,208,208,208,229,227,208,208, 208,208,208, 6, 6, 6, 2, 5,
+ 2,208,208, 6,208,208,208, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[1005 + 2] 0x028180 Byte 4 of 4 (property)
+ 5,229,227,229,229,229,208,208, 208,208,208,208,229,229,229,227,
+208,208, 2, 2, 5, 5, 5, 5, 5, 6,208,208,208, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2,208,208, 208, 6,229,208,229,229,208,229,
+229,208,208,208,208,208, 2, 2, 2, 2, 2, 5, 5, 6,218, 2,
+
+// state[1006 + 2] 0x0281c0 Byte 4 of 4 (property)
+ 2,229,208,208,208,229,229,208, 229, 2, 5, 5, 5,218, 6,208,
+ 6, 2, 2, 2,208, 6,229, 0, 229,208,208, 2, 2,208, 5, 6,
+ 6, 6, 6, 2,229,208,208,208, 229,229,208,208, 2,208,208,229,
+208,229,208,208,208, 2, 2, 2, 2, 2, 5,208,208,208, 6, 6,
+
+// state[1007 + 2] 0x028200 Byte 4 of 4 (property)
+ 6, 6, 6,208,208,208, 6, 5, 229, 6,208,208, 6, 5, 6,208,
+208,208, 2,229,208,208, 6, 6, 229,208,229,208,208,208,208,208,
+208,208,208, 6, 5, 6, 6, 6, 6,229,208,229,229,208,208, 5,
+ 5, 5, 5, 6,208,208, 6, 6, 208,229,208,208,208, 5, 5, 5,
+
+// state[1008 + 2] 0x028000 Byte 3 of 4 (relative offsets)
+ -9, -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
+
+// state[1009 + 2] 0x028240 Byte 4 of 4 (property)
+208, 6,208,208, 6, 6, 6, 6, 229,208,208,208,208,208, 5, 5,
+ 5,218, 6, 2,208,208,208,208, 5, 5, 6,208, 6, 6, 2, 2,
+ 2, 6,229,229,208,208,208, 5, 5, 6, 6, 6, 6, 6,229,208,
+208,208, 5, 5, 6, 6, 6,172, 6,229,208, 5, 5, 6, 6,208,
+
+// state[1010 + 2] 0x028280 Byte 4 of 4 (property)
+208,208, 4, 5, 5, 6,208, 6, 6, 6,208, 2, 2,208,208, 5,
+ 5, 6,208,208,229,208,208,208, 208, 5, 6,229, 6,229, 6, 6,
+229,229,229, 5, 5, 6, 6,229, 6, 5, 5, 5, 5, 6, 6,208,
+ 6,208, 5,229,208,229,208,208, 208,229,208,229,229,208,208,208,
+
+// state[1011 + 2] 0x0282c0 Byte 4 of 4 (property)
+229, 6, 6, 6, 5, 6,208,208, 6, 6, 6, 6,229, 5,229,229,
+229,229,208,208,208,229,229,229, 229,208,208,229, 5, 6, 6, 6,
+ 6, 6, 0, 2, 2, 6,208,208, 208,229,208,208,229,229,208,229,
+229,208,229, 4, 5, 5, 5, 6, 6,208, 6, 6,208, 6, 6, 6,
+
+// state[1012 + 2] 0x028300 Byte 4 of 4 (property)
+ 2,208,208,208,229,208,208,208, 229,208,208,208, 6,208,229,208,
+ 6, 6, 6, 6, 6, 6,208, 6, 208, 5, 5, 5, 5,208, 6,208,
+208,229,229,208,229,229,208,208, 208,229,208,229,229,208,208,208,
+208,208,229,229,229,208,208,208, 208, 6, 5, 5, 5, 5, 5, 6,
+
+// state[1013 + 2] 0x028340 Byte 4 of 4 (property)
+208,208,208,208, 2,208,208,229, 229,229,229,229,229,229,208,229,
+229,208,208,208,208, 6, 5, 5, 5, 5, 5, 6, 6, 6, 6,208,
+208, 6, 6,208, 6, 5, 2,208, 208,208,208,229,208,208,208,208,
+208,208,208,229,229, 6,229,208, 229,229, 6,208, 6, 5, 6, 5,
+
+// state[1014 + 2] 0x028380 Byte 4 of 4 (property)
+ 5, 5, 5, 5,208,208, 2,229, 208,229,229,229,229,208, 6, 6,
+ 5, 5, 5, 5, 6,208,208, 6, 208,208, 6, 6,208, 6, 2, 2,
+ 2,208,208, 6,229,208,208,229, 208,229,208,229,208,208,229,208,
+208,208,208,229,208,208,229,229, 208,229, 6, 6, 5, 6,208,219,
+
+// state[1015 + 2] 0x0283c0 Byte 4 of 4 (property)
+ 6,208, 6, 3, 2,208,208, 5, 229,229,229,208, 6, 4,208,208,
+ 3,208,229,208,208,208,208, 6, 208,208, 6, 6, 6,208,208,229,
+229,208,208,208,208,219,208, 2, 208,208,229,229,229, 6, 6,208,
+208,229,208,229,208,208,208, 6, 208,229,208, 6, 6, 5,208, 6,
+
+// state[1016 + 2] 0x028400 Byte 4 of 4 (property)
+ 6,229,208, 6, 6, 6, 6, 6, 6, 6, 6,208,233,208, 6,208,
+208,208, 5, 6,208,208, 6, 5, 217, 5, 5,208,229,208,208,208,
+208,229,208, 6, 5, 6, 6, 6, 208,208, 5, 5, 5, 5, 2,208,
+229,229,208, 6, 6,208,208, 2, 2,208, 2, 5,208, 5,208, 2,
+
+// state[1017 + 2] 0x028440 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 5,208,208,208, 6,208, 6,208,
+208,229,208,229,208,172, 6, 2, 5, 5, 5,208,218, 6,208, 6,
+208,208,208,208,229,229,208,208, 208,229,208,229,208,208, 2, 5,
+ 5, 5, 5, 5, 5,208, 6,208, 6, 6, 6, 2,208,208,208,208,
+
+// state[1018 + 2] 0x028480 Byte 4 of 4 (property)
+208,208,208,229,208,208,208,208, 208,208,208,208,208,208,208,208,
+ 6, 6, 2, 5, 5, 5, 5, 5, 6, 6,208,229, 6,208, 6,218,
+208, 6,208,208,208,208,208,208, 208,208,208,229,208,208,208,208,
+208, 6,208,208, 6, 6, 6, 3, 2, 2, 2, 2,208,208,208,208,
+
+// state[1019 + 2] 0x0284c0 Byte 4 of 4 (property)
+208,208,208,208,208,208, 6,208, 208,208,229,208, 6,229,208, 6,
+ 2, 2, 5, 5, 5, 5,208,208, 208,208, 6,208,219,208, 6, 6,
+ 6, 2, 2, 6, 5,208,229,208, 208,208, 5,208,229,229,208,208,
+229,229,208,208,229,208,208,208, 208,229,208,208, 6,208,208,208,
+
+// state[1020 + 2] 0x028500 Byte 4 of 4 (property)
+208,208, 6,208, 6,208, 6, 6, 2, 2, 2, 5, 2, 2, 5, 5,
+ 5, 5, 5, 5, 5, 5,208,208, 6,208,208,208,208,229,208,229,
+ 6,208,229,229,208, 6,208,229, 208,208,208,208,208,208,208,208,
+208,208,208, 6, 6,219, 6, 6, 6, 6, 6,219, 5, 5, 2, 2,
+
+// state[1021 + 2] 0x028540 Byte 4 of 4 (property)
+ 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,208, 6,208,
+208,208, 6,208,208, 6,229, 6, 6, 6,208, 5,208,208,208,208,
+208,208,229,208,208,208,208,208, 208,208,208, 4, 5, 2, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 208, 6, 6, 6,208,208, 6, 6,
+
+// state[1022 + 2] 0x028580 Byte 4 of 4 (property)
+ 6,208, 6,208, 6, 2,208,208, 229,208,229,229,208,229,208,208,
+208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6,208,208,208,
+208,208, 6,208,208,208, 6, 2, 2, 2,208,208,208,208,208,208,
+208,208, 2,208,208,229,229,229, 208,208,208,229,227, 6,229,208,
+
+// state[1023 + 2] 0x0285c0 Byte 4 of 4 (property)
+208,208,208,208,208, 6, 6, 6, 4, 4, 2, 5, 5, 5, 5, 5,
+208, 6,208,208,208,208, 6,208, 208,208, 3, 2, 2, 6,208,208,
+ 6,208,208,208, 6,229,229,229, 208,229,229,208,208,208,227,208,
+208, 5, 6, 6, 5, 2, 5, 5, 5, 5,208,208,208,208, 6, 6,
+
+// state[1024 + 2] 0x028600 Byte 4 of 4 (property)
+ 5,208,229,208,208, 5, 6,208, 208,208,208, 5,208, 5, 5, 5,
+ 5,208, 6,208, 6, 6, 6, 2, 2,208,208,208,208,208,208,208,
+ 5,208,208,208, 6, 5, 5, 5, 5, 5,208,208,208,208,208, 6,
+208, 2,208, 6, 6,208, 5,229, 229,208, 6, 5,208, 5, 6,208,
+
+// state[1025 + 2] 0x028640 Byte 4 of 4 (property)
+208,208,229,208,208,229,208, 5, 5, 5, 5, 6, 2, 2,208,208,
+208,208,229,229,229,208, 2,208, 208,229,208, 2,208, 2,208,208,
+208, 6, 6,208,208, 2,208,208, 208,208, 6,208, 6,208,208,208,
+208,208,229, 6, 6,208,208,208, 229,208,208,229,208,208,208, 6,
+
+// state[1026 + 2] 0x028680 Byte 4 of 4 (property)
+ 6, 6, 5, 5, 5, 5, 5, 5, 6,208,208,208, 6,208, 6,208,
+ 6,208,208,229,229,208,208,208, 208,208,208, 5, 5, 5, 5, 5,
+208, 6,208, 6, 6,208, 6, 6, 208, 6, 5, 5,208,208,208,208,
+208,208,229,208,208,208,208, 6, 6, 5, 5,208,208,208, 6,208,
+
+// state[1027 + 2] 0x0286c0 Byte 4 of 4 (property)
+ 6,208, 6,208,208, 6,208,208, 208,208,208,208,208,229,208,208,
+208,208,208,208,229,208,208, 4, 5, 5,208,208,208, 6,208,208,
+208,208, 6,208,208,208,227,208, 5, 5, 5,208,208,208,208,208,
+208,208, 6,208,208,208,208,208, 208, 6, 4, 5, 5, 5, 5,208,
+
+// state[1028 + 2] 0x028700 Byte 4 of 4 (property)
+208,208,208,208, 6,208,208, 6, 208, 6, 5,208,208,208,208,208,
+208,208,229,208,208,208,208,208, 208,208,208,229,208, 6, 6, 5,
+ 5, 5, 5,208,208,208,208,208, 208, 6,208, 6, 6, 5, 5,208,
+208, 6, 5,208,208,208,229,208, 208,208,208,208,208,208,208,208,
+
+// state[1029 + 2] 0x028740 Byte 4 of 4 (property)
+229,208, 6, 5, 5, 6, 6,208, 6,208,208,208,229,208,208,208,
+208,208, 6,208,208,208,219,208, 208, 6, 6,208, 6,208,208,208,
+208,208,208,229, 6,208,208,208, 208, 6,208,208,208,208,208,208,
+229,208, 6,208,208,208,208,208, 229, 6, 6, 6, 6, 5, 5, 5,
+
+// state[1030 + 2] 0x028780 Byte 4 of 4 (property)
+208,208,208,208,208,208, 6,208, 6,208, 6, 6,208,208,208,208,
+ 6,229,208,208,208,208,208,208, 208,208,208,208,208, 6, 5, 5,
+208,208, 6, 6, 6, 6,208,208, 208,208,208,208, 6, 6,208,208,
+208,208, 6,208, 6,208,208,208, 208,208,208,208,208,208,208,208,
+
+// state[1031 + 2] 0x0287c0 Byte 4 of 4 (property)
+208,208,208,208,208,208, 6, 6, 5, 6, 6, 6, 6,208,208,208,
+208,208, 6, 6,208,208,229,208, 208,208,208,208,208, 6,208,208,
+208, 5,208,208,208, 6,208, 5, 208, 6, 6,208,208, 6,208,208,
+208, 5,229, 6,229,208,229,229, 208,208,208,208,229,229,208,208,
+
+// state[1032 + 2] 0x028800 Byte 4 of 4 (property)
+208,208,208, 6, 5, 5,208,208, 208, 2,208,208,229,208,208,208,
+229,229,229,208,208,208,229,229, 208, 6, 6, 5, 5, 5, 5, 6,
+ 6, 6,208, 2,208,229,229,208, 6, 6, 6, 5, 6, 6,229, 6,
+ 6, 6, 2, 2, 2, 2,229,208, 229,229, 5, 5, 5, 5,208, 6,
+
+// state[1033 + 2] 0x028840 Byte 4 of 4 (property)
+ 6,208,208, 6, 6,208, 6, 6, 2, 2, 2, 5,208,208,229,229,
+229,229, 6,208,208,208,208,208, 208, 6, 5, 5, 5,208, 6, 6,
+208, 6,208,208, 6, 6, 6, 2, 2,208,208,229,208,229,208,208,
+208,229,229,208,229,208,208,208, 229, 6, 6,219, 2, 5, 5, 6,
+
+// state[1034 + 2] 0x028880 Byte 4 of 4 (property)
+208,208, 6,208, 5,208,229,227, 208,229,229,229,208,229,229,208,
+229,208,229, 6, 5, 5, 5, 6, 6,208,208,208, 6, 6, 6, 2,
+229,208,229,229,208, 0,229,208, 208,229,208,208,208,208, 6,208,
+208, 5, 5, 5, 6,208,208, 6, 6,208,208, 2, 2,208,208,229,
+
+// state[1035 + 2] 0x0288c0 Byte 4 of 4 (property)
+229,229,229,229,208,208,208,208, 6, 6, 6, 5, 6, 6,208, 6,
+208,208, 6,208,208,208,229,229, 229,229,229,229,208,229, 6, 5,
+208,208, 6,208, 2, 2,229,229, 229,208,208, 5, 6,229,229,229,
+208,229,208,208, 5, 6,208,229, 229,208,229,229,208, 5,229,208,
+
+// state[1036 + 2] 0x028900 Byte 4 of 4 (property)
+ 5,208,229, 6, 6,208, 6,208, 5,208,208, 6,208,229,208,208,
+208,208,208,208, 2,208,208,208, 208, 5,208, 5, 6,208, 5, 6,
+ 5,229,208,208, 6,208,208, 2, 208,208, 5, 6, 5,208, 2, 2,
+ 2, 6, 6, 5,208, 2, 5,208, 5,229,208, 2, 2, 6,208, 5,
+
+// state[1037 + 2] 0x028940 Byte 4 of 4 (property)
+ 5, 3,229,208,208, 6, 4, 6, 0,194,208,208,208,208,208,208,
+208,208,208, 5, 5, 5, 5, 5, 5,208, 6, 6, 6, 6, 6, 6,
+ 6, 6,208, 6,208,208,208,229, 229,208,208, 4, 5, 5, 5, 5,
+ 5, 5,208,208, 6,208,208, 6, 6,208, 6,208, 6, 3, 5, 2,
+
+// state[1038 + 2] 0x028980 Byte 4 of 4 (property)
+ 2, 2,229,219,229,208,229, 4, 4, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 6,208, 6, 6,227, 6, 6,208,208,208,208,
+208, 6, 6,208, 3, 2, 2, 2, 5, 2, 5, 5,208, 0,208,208,
+208,208,208,208,229,219, 6,208, 208, 6, 4, 4, 5, 2, 5, 5,
+
+// state[1039 + 2] 0x0289c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5,208,208,
+ 6, 6, 6,208, 6,208,208, 6, 6,208, 6,208, 5, 2, 5, 2,
+ 2, 5, 2,216, 5,208,208,229, 229,229,229,208,208,208,208,208,
+208,229,208,229,208,229,208, 6, 6, 5, 5, 5, 5, 5, 5, 5,
+
+// state[1040 + 2] 0x028a00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,208,208, 6, 6, 6, 6, 6,208,172, 6,
+218, 6, 6, 3, 2, 5, 2, 2, 2,194, 2, 2,208, 6,208,229,
+229,208,229,208,208,208,208,208, 208,208,229,208,208,208,208, 6,
+
+// state[1041 + 2] 0x028a40 Byte 4 of 4 (property)
+ 6, 6, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+216, 5, 5, 5, 5, 6, 6, 6, 6,208, 6, 6,208, 6, 6, 6,
+ 6,172, 6, 6, 6,208, 6, 6, 6,208,208, 6,208, 6, 3, 3,
+
+// state[1042 + 2] 0x028a80 Byte 4 of 4 (property)
+ 2, 5, 5, 5,208,208, 5,229, 229,208,229,229,229,208,208,229,
+229,208,208,208,208, 6,208,208, 6, 4, 5, 5, 5, 2, 2, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 6, 6, 6, 6, 6,208,208, 6, 6, 6, 6,208,208, 6, 6,
+
+// state[1043 + 2] 0x028ac0 Byte 4 of 4 (property)
+208, 6, 6, 3, 2, 2, 5, 2, 2, 2, 2,216, 5, 4, 5,208,
+229,208,229,208,208,208,208, 6, 208,208, 6, 6, 6, 4, 5, 5,
+ 5, 0, 5, 5, 4, 5, 5, 5, 5, 5,216, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5,216, 5, 5, 5,208, 6,208,229,208, 6, 6,
+
+// state[1044 + 2] 0x028b00 Byte 4 of 4 (property)
+ 6, 6, 6,208,208,208, 6, 6, 2, 2, 2, 2, 5,229,208,208,
+229,208,229,208,208, 6,208,229, 208,208,208,208, 6,229, 6,229,
+ 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6,
+
+// state[1045 + 2] 0x028b40 Byte 4 of 4 (property)
+ 6, 6, 6, 6,208, 6,208,208, 6,208, 6, 6, 5, 2, 5, 2,
+ 5, 2, 2,229, 3,229,229,229, 208,208,229,208,208,208,229,208,
+ 6, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,218, 6,208, 6,208, 6,
+
+// state[1046 + 2] 0x028b80 Byte 4 of 4 (property)
+208, 6, 6,208,208, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 5,
+ 2, 2, 0,208,208,208, 6,208, 6, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,208, 6, 6, 6,208,
+ 6, 6, 6, 6, 6, 2, 2,208, 208, 0, 6,227,208,208,208,208,
+
+// state[1047 + 2] 0x028bc0 Byte 4 of 4 (property)
+ 6, 4, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,208, 6,208, 6, 6, 5, 2, 5, 5, 2, 6, 6,229,208,
+229,208,208,208, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5,194,
+ 5, 6, 6, 6, 6,208, 3, 2, 2, 2,208,208,208, 6, 5, 5,
+
+// state[1048 + 2] 0x028c00 Byte 4 of 4 (property)
+ 5, 5, 6,208, 6,208, 6, 2, 2,229,229,208,208,208,208,229,
+208,208,208, 6, 5, 5, 5, 5, 5, 5, 5, 6,208,217,208,208,
+229, 6, 5, 5,208, 6,208, 2, 5,208, 2, 5, 5, 6,208,208,
+ 5, 5,227,208, 2, 6, 5, 6, 208, 0, 2,208, 5, 5, 6, 6,
+
+// state[1049 + 2] 0x028c40 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6,208, 5,229,229,208,208,229,208, 5,
+ 6, 6,208, 6,229,229,229,229, 208,208, 2,208, 6, 6, 6, 6,
+ 6, 6, 6,229,208,229,208,208, 6, 5,208, 6, 6, 2, 2,229,
+
+// state[1050 + 2] 0x028c80 Byte 4 of 4 (property)
+229,229,208,208, 6, 2,229,229, 208,229,229,229,229, 5, 6, 6,
+ 6, 6, 6,229,208,208, 2,208, 208, 6,208,208, 6,208,229,229,
+229,208,208, 6, 6,208,208,229, 208,208, 6, 6, 6,229,229,229,
+ 6, 6,208,229, 5, 6,208, 2, 229, 6,208,208,208, 6,208, 6,
+
+// state[1051 + 2] 0x028cc0 Byte 4 of 4 (property)
+229,208,229,208,208,208, 6, 6, 3,229,208,208,208, 5, 5,208,
+229,229,216, 5, 6,208,208,229, 229,229,229,208,208,229,229,208,
+208,208,208,208, 6, 5, 2, 5, 5, 5, 5, 5, 5, 5, 6, 6,
+208, 2,229,229,229,229,208,229, 208,229,208,229,208,208,208,229,
+
+// state[1052 + 2] 0x028d00 Byte 4 of 4 (property)
+229,208,208, 6, 5, 5, 6, 6, 6, 6, 6, 6, 6,229,229,208,
+228,229,229,229,208,229,208,229, 208,208, 6, 6, 6,208, 6,208,
+208, 6, 6, 6, 6,208, 2, 6, 208,208,229,208,229,229,208,208,
+208,229, 6, 5, 5, 5, 5, 5, 208, 6, 6, 6, 6, 6, 6,208,
+
+// state[1053 + 2] 0x028d40 Byte 4 of 4 (property)
+208,208, 6, 6,208,229,208, 5, 208,229,229,229,229,208,219,229,
+229,208,208,208, 5, 5, 5,208, 6, 6, 6, 6,208, 6, 6, 6,
+ 6, 6,217, 2, 5,229,229,229, 208,229,229,229,229,229,229,229,
+229, 4, 5, 5, 6,208, 6,208, 6,208, 6, 6, 6, 6, 6, 6,
+
+// state[1054 + 2] 0x028d80 Byte 4 of 4 (property)
+208,208,229,208,208,229,229,229, 229,229,208,208, 5, 5, 5,208,
+ 6, 6,208, 6, 6, 6,208, 6, 6,208,208, 2,208,229,208,208,
+208,208,208, 5, 5, 5, 5, 5, 6, 6, 6, 6,229,208,229,229,
+229,229,229,208,208, 6,208, 5, 5, 5, 6, 6, 6, 6,208, 6,
+
+// state[1055 + 2] 0x028dc0 Byte 4 of 4 (property)
+ 6,208,208,229,229,229,229,229, 2, 5, 5, 5, 5, 5, 6, 6,
+ 6, 2,208,208,229,208, 6, 5, 6,208, 6, 5, 5, 5, 5, 5,
+ 6, 5, 5, 5,208, 6,229, 5, 5, 6,208,208, 6, 6, 6, 2,
+229,208,229,208, 6, 6, 2,208, 5, 6, 6,228, 5, 6, 6, 6,
+
+// state[1056 + 2] 0x028e00 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 2, 6, 6, 6, 6, 2, 6,208,
+208,208,208,229,208, 5,208,208, 208, 6,229,208,208,229,229, 4,
+ 5, 5, 5, 5, 5, 6, 6, 6, 6,208,219,229,208,229,208,208,
+229,208,208,208, 6, 5,194, 6, 5,208, 6, 6, 6, 6, 2, 6,
+
+// state[1057 + 2] 0x028e40 Byte 4 of 4 (property)
+ 6,229,229,229,229,208,208,208, 229,229,208,208,208, 6, 5, 5,
+ 5, 5, 6,208, 6,208, 6, 6, 208,229,208,208,229,229,208,229,
+229,208,208,208, 6, 5, 5,229, 6, 6, 6, 6, 2, 5, 5, 5,
+ 5, 5, 5, 5, 6,208,208,208, 229,208,229,229,208,208,229,208,
+
+// state[1058 + 2] 0x028e80 Byte 4 of 4 (property)
+208,208,208,208,208,229,208, 6, 6, 4, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,217, 6, 6, 6,208, 6, 6, 6, 6, 6, 6, 6,208,
+229,208,208,208,208,229,229,208, 229,208,229,208,229,229,208,208,
+ 6, 5, 5, 5, 5,208, 6, 6, 6, 6, 6, 6, 6, 6, 6,208,
+
+// state[1059 + 2] 0x028ec0 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,229,208,229,208,208,
+208,208,208,208,208,208,208, 6, 208, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 6, 6, 6, 6,208,208, 208, 6,208,214, 2, 6, 2, 5,
+208,208,208,229,208,229,228,208, 208,208,229,229,229,208,208,208,
+
+// state[1060 + 2] 0x028f00 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5,208, 6, 208, 6,208,227,208,229,208,208,
+208,208,208,208,229,208,229,229, 208,208, 2, 5, 5, 5, 5, 5,
+ 5, 6, 6, 6, 6, 6,208, 6, 6, 6,229,229,229,208,229,208,
+208,208, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 2,208,208,208,
+
+// state[1061 + 2] 0x028f40 Byte 4 of 4 (property)
+229,208,208,208,208, 5, 5, 5, 6, 6, 6, 6,208,229,208,208,
+208,208, 5, 5, 5, 5,229,208, 208,208, 6, 6, 5,208, 6,208,
+208,208, 5, 5, 6,208,229,208, 208, 6,208,208,208, 6,208, 6,
+ 6, 5,208, 5,208,208,208, 6, 229,208, 5,208,208,208, 6,229,
+
+// state[1062 + 2] 0x028f80 Byte 4 of 4 (property)
+229,208,208,208, 6,208,208,208, 229,208,229,229, 6,208, 6,229,
+ 6, 5,229,208,229,229,208,208, 208,229,229,208,208,208, 6, 6,
+208,208,208,208,229,229,229,208, 208,208,208,208, 6, 6,208,208,
+ 6, 6,208,208,208,229,229,208, 229,208,208,229,208,208,229,229,
+
+// state[1063 + 2] 0x028fc0 Byte 4 of 4 (property)
+208,229,208, 5, 5, 5,229, 6, 208,208, 6,229,229,229,229,208,
+229,208,208,229,229,229,208,208, 208,208,208, 6, 6, 6,208, 6,
+208,229,208,208,208,229,208,229, 208,208,229,208,208,208,208,208,
+208,229,208,208,229,229,229, 6, 4, 5, 5, 6, 6,229, 5,229,
+
+// state[1064 + 2] 0x029000 Byte 4 of 4 (property)
+208,208,208,208,208,208,208,208, 208,208,208,208,208,208,208,208,
+208, 5, 5, 5,208,208, 6,208, 208,208,208,208,229,208,208,229,
+208,229, 6, 6,208,208,208,208, 208,208,208,229,208,208,208,208,
+208,229, 6, 2,208,208,208,208, 208,208,208,208,229,208,208,208,
+
+// state[1065 + 2] 0x029040 Byte 4 of 4 (property)
+208, 5,208, 6, 6,208,208,208, 208,208,208,208,208,208,208,208,
+208, 5,208,208,208,208,208,208, 5, 5, 6,208,208,208,208,229,
+208,208,208,208,208, 6, 2,208, 208,208, 6,208,208, 5, 6, 6,
+208, 2,208, 6, 6,208,216,208, 5,208,229,208,208, 5, 5, 6,
+
+// state[1066 + 2] 0x029080 Byte 4 of 4 (property)
+ 2, 2,229,229, 0,229,229,208, 229,229, 5, 5, 5,208, 6, 2,
+ 2, 2,229,229,229, 0,229,229, 208, 6, 5, 5, 5, 6, 6, 2,
+ 2,208,229,229,229,229,229,229, 229,208,208, 6,208, 5, 5, 5,
+ 0, 5, 5,233, 6,208, 2,208, 229,229,229,229,229,229,229,208,
+
+// state[1067 + 2] 0x0290c0 Byte 4 of 4 (property)
+229,229,208,229, 6, 5, 5, 5, 5, 5, 5, 6,208, 6, 6, 6,
+208,208,229,208,229,229,229,229, 229,208,229,229,229,208,208, 6,
+229,208, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5,208,208, 6,208,
+ 6, 6, 6, 2,208,229,229,229, 208,229,208, 6,229,208,208,208,
+
+// state[1068 + 2] 0x029100 Byte 4 of 4 (property)
+208,217, 6, 5,208, 6, 6, 6, 6,208, 6, 6, 6, 5, 2, 2,
+216, 2, 2, 2, 2,219,208,208, 208,229,208,208,208,229,229,229,
+229,229,229,208,208,208,208,208, 2, 5, 6,208, 6,208,208, 6,
+ 2, 2, 2, 2, 2, 2, 2,208, 229, 5,208,229,229,229,229,229,
+
+// state[1069 + 2] 0x029140 Byte 4 of 4 (property)
+229,229,229,208,229,229,208,208, 6,208, 5, 5, 6,208,208,208,
+208, 6,208,208, 6, 6, 6, 6, 2, 2, 2,216, 2,208,229,208,
+229,229,229,208,208,208,208,229, 229,208,208, 5, 5, 5, 6, 6,
+229,208,208, 6, 6, 6,208,208, 219, 2, 2, 2,229,229,229,229,
+
+// state[1070 + 2] 0x029180 Byte 4 of 4 (property)
+208,229,229,208,229,229,208,208, 208,208, 6, 2, 6,218, 6, 2,
+ 2,229,208,229,208,208,208,208, 208, 6,208, 5, 5, 6, 6,208,
+ 2, 2, 2, 6,229,208,208, 6, 5,208, 2,229, 2,229,208,208,
+208,208,208, 5, 2,229,229, 0, 208, 6,229, 6, 6,208, 2,229,
+
+// state[1071 + 2] 0x0291c0 Byte 4 of 4 (property)
+208,229, 5, 5,208, 2, 2,208, 208, 5,208,229,208, 2,208, 6,
+ 6,208,208,208,208,229, 6,208, 5, 5, 5,208,229,229,208,208,
+208, 5, 2,229, 5, 6, 5,208, 208,208,208,229, 5,208,229,208,
+208,208, 5, 5, 6,208,208, 5, 208,208,208, 6,208, 6,208,208,
+
+// state[1072 + 2] 0x029000 Byte 3 of 4 (relative offsets)
+ -8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+
+// state[1073 + 2] 0x029200 Byte 4 of 4 (property)
+ 6, 6, 2,208,208,229,229,229, 229,229,208, 6,208,208,229,229,
+ 6,208, 5, 5, 6, 6,208, 6, 2,229,229,229,229,208, 6,208,
+ 5,229,229,229,208,229,229, 5, 208, 2, 2,208,229,229,208,229,
+ 5,208,229,208,229,229,229, 6, 229,229, 6,229,229,208, 5, 6,
+
+// state[1074 + 2] 0x029240 Byte 4 of 4 (property)
+208,229,229,229,229, 5, 6,229, 208,208,229,208,208,208, 5, 6,
+ 6,208, 6, 6,208, 6,208, 6, 6,229,229, 5, 6,208,229,208,
+ 6,229,229,208,208,208,229,229, 208,208,208,229,229, 6, 5, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 229,229,229,229,208,229,229,229,
+
+// state[1075 + 2] 0x029280 Byte 4 of 4 (property)
+208,208,208,208,229,208, 6, 6, 208,208, 6,208, 6, 6, 6, 6,
+208,229,229,229,208,229,229,208, 208,229,229,229,229,229,208,208,
+ 4, 6, 6, 6, 6,208,208, 6, 6,229,229,208,229,229,229,208,
+208, 4,208,208, 6, 6, 6,208, 208,208, 6, 6,208, 2, 2,208,
+
+// state[1076 + 2] 0x0292c0 Byte 4 of 4 (property)
+208,229,229,229,208,208,208,208, 229,229,229,229,229,229,208,208,
+208,208,229,208, 5, 5, 6, 6, 6, 6, 6,208, 6,208,229,229,
+229,229,229,208,229,229,208,229, 208,229,229,229,229,208,229,229,
+229,208,208, 5, 5,208,208, 6, 229,208,208, 6,208,208,208,208,
+
+// state[1077 + 2] 0x029300 Byte 4 of 4 (property)
+208, 6, 2,229,229,229,229,229, 229,208,229,229,229,229,229,229,
+208,229,208, 5, 5, 5, 5, 5, 6, 6,208,208, 6,208, 6, 6,
+208,208,208, 6, 6, 6, 0,229, 229,229,208,208,229,208,208,208,
+229,208,208,208, 6, 6, 5, 5, 6,208,208,208, 6, 6,208,208,
+
+// state[1078 + 2] 0x029340 Byte 4 of 4 (property)
+208,229,208,229,208,229,208,208, 6,208,208,208, 6, 6,208,208,
+ 6,229, 5, 0,208,229,229,208, 208,229,208,208,229,208, 5,208,
+ 6, 6, 2, 2,208, 0,229,229, 208,229,208,208, 6, 6,208,208,
+ 6,208,229,208,208,208, 6,208, 208,208, 6, 6, 6, 6,208,229,
+
+// state[1079 + 2] 0x029380 Byte 4 of 4 (property)
+229,208,208, 6,208,208, 6,208, 208,229,208,208,208,208,208,208,
+ 3,208,208,229,229,229, 6,208, 229,229,229,229,208,208, 5, 6,
+ 6,208, 6, 6,208,208,229,229, 208, 5,208,208,208,208,208,208,
+208,208,208,208, 6,208,208,208, 208,208,208,229,208,229,208, 5,
+
+// state[1080 + 2] 0x0293c0 Byte 4 of 4 (property)
+ 5,208, 6,208, 6,208,229,229, 208,208,208,208, 6, 6,208,208,
+208,208,229,208,208,208,208,208, 208,208,229, 5, 6, 6,208,208,
+229,229,208,229,229,208,208, 6, 208,229,229,208,208,208,208,208,
+208,208,229, 2,229,208,229,208, 208,208,229,208, 6, 6, 6, 6,
+
+// state[1081 + 2] 0x029400 Byte 4 of 4 (property)
+ 6,208,208,208,208,208, 6,208, 6,208,208,208,229,208,219,229,
+ 6, 6, 6, 5,208,208,208, 5, 229,208,229,208,229, 5,229,208,
+229,229,208,208,208,208,208,229, 208, 6, 5,208,208,229,208, 6,
+208,229, 5, 5,208,229,229,208, 6,208,208,208,208, 6,208,208,
+
+// state[1082 + 2] 0x029440 Byte 4 of 4 (property)
+208,208,208,208, 6,208, 5,208, 208,208,229,208,208,208, 6, 6,
+229,229,229,229,229,229,208,229, 208,208,229,208,229,208,229,229,
+208,208,229,229,229,208,229,229, 208,208,208, 5,208, 6,208, 6,
+ 0,208,229,208,229,229,229,229, 229,229,229,229,229,208,229, 6,
+
+// state[1083 + 2] 0x029480 Byte 4 of 4 (property)
+208,208,208,229,208, 5,208,208, 208,208,208, 5,208, 6, 6,229,
+228,208,229,229,208,229,229,208, 208,208,208,208,208,208,208, 5,
+ 5, 5,208, 6,229, 6,208,208, 6, 6,208,208, 3,208,229,208,
+229,229,208,208,229,208,208,229, 229,208,229,229,229,229,229,229,
+
+// state[1084 + 2] 0x0294c0 Byte 4 of 4 (property)
+229,208,229,208,208,208,208, 0, 5, 5, 5, 5, 5, 5, 5, 6,
+208,208, 6,208,208,208,208, 6, 208, 5, 5, 2, 2,229,229,229,
+229,208,229,208,229,229,229,229, 229,208,229,229,208,208,229,208,
+ 5, 5, 5, 5, 5, 5, 5, 5, 208, 6,208,208,208,208,208, 6,
+
+// state[1085 + 2] 0x029500 Byte 4 of 4 (property)
+229,229,229,229,229,229,229,229, 229,229,208,229,208, 5, 5, 5,
+ 5, 6, 6,208,208, 6,219, 6, 6,229,229,208,208,229,229,208,
+208,229,229,208,208,208,208,208, 208,208, 6, 6, 5, 5, 6,208,
+229, 6, 6,229,229,229,229,208, 229,229,208,229,208,208, 5, 5,
+
+// state[1086 + 2] 0x029540 Byte 4 of 4 (property)
+229, 6,208, 6, 6,208,227, 6, 5,229,229,229,229,229,208,229,
+229,208,208,208,208,208,208,229, 5, 5, 5, 5, 6,208,229,208,
+208,229,208,208,208, 5,208, 6, 6,208, 6,208,208,208,208,229,
+ 6,229,229,229,208,229,208, 5, 208, 6,208, 6,208, 6,208,208,
+
+// state[1087 + 2] 0x029580 Byte 4 of 4 (property)
+208,208, 6, 5,208, 2,229,229, 6, 6,229,208,208,229,208,208,
+208, 6, 6,208, 6, 6, 6, 6, 6,208,229,229,208,208, 5, 5,
+ 6,208,229,208,208,229,208,208, 208,229, 6, 2,229, 6,208, 6,
+208, 6,208,208,229,229,229,208, 229,229,229,208,229,208,208, 6,
+
+// state[1088 + 2] 0x0295c0 Byte 4 of 4 (property)
+ 6,208, 2, 2,229,229,229,229, 208,208,229,208, 5, 5, 6,172,
+ 6, 6, 6,229,229,229,229,208, 208,229,229,208, 6,208,208, 5,
+ 5, 5,229,229, 6, 6, 6, 6, 208,208,229,208,229,229,208,229,
+229,229,208,229,229,229,208,229, 208, 6, 5, 5, 6, 6, 6, 6,
+
+// state[1089 + 2] 0x029600 Byte 4 of 4 (property)
+ 6,229, 6,208, 2,229,208,229, 229,208,229,229,229,229,229,229,
+208,229,208,208, 5,208,208,208, 6,208,229, 6,229,229,229,229,
+208,208, 5,216,208,208,208,208, 2, 2, 2, 2, 2,229,229,208,
+229,229,208,208, 5,208,208,229, 208,208,229,229,229,229,208,208,
+
+// state[1090 + 2] 0x029640 Byte 4 of 4 (property)
+ 5,208,208,208,208,208,229, 6, 229, 5,208, 2, 2, 2,208, 6,
+ 6,229,229,208,208, 2,229,208, 6,208,229,229,208,208,208, 5,
+ 6,208, 6, 6,208, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6,208,208, 2,208, 6,208,208, 6, 2,208, 2,208,229, 6, 4,
+
+// state[1091 + 2] 0x029680 Byte 4 of 4 (property)
+208,208,208,208,229,208, 5,229, 229,229,208,208,217,208,208,208,
+ 6, 6,229,208, 2,208,229,208, 229,208,229,229,208,229,208, 6,
+ 5, 6, 6,208,229, 6, 2, 2, 208, 0,229,229,229,229,229,229,
+208,208,229,229,229,229,208,208, 208,208,208,208, 6, 5, 6,208,
+
+// state[1092 + 2] 0x0296c0 Byte 4 of 4 (property)
+208,208, 2, 2, 2,208,208, 6, 5,208,208,229,229,208,208,208,
+208,208,208,229, 6, 6, 5, 5, 6, 6, 6,208, 2,229,208,229,
+208,229,208,208,208,208,229,208, 229,208,208,208,208,208,208,208,
+ 4, 5, 6,208, 6, 6, 6, 2, 2,208,229,208,229,229,208,208,
+
+// state[1093 + 2] 0x029700 Byte 4 of 4 (property)
+229,229,229,229,208,208,229,229, 208,208,208,229,208,208,229,229,
+208, 6,208,208,208,208,208,208, 6, 4, 5, 5, 5, 5, 5,208,
+208, 6,208, 6, 6, 6, 6, 6, 6, 6, 2, 2,208,229, 6,229,
+229,229,229,208,208,229,229,229, 208,208,208,208,208,227,229,208,
+
+// state[1094 + 2] 0x029740 Byte 4 of 4 (property)
+208,208,208,219, 6, 5, 5,218, 6,208, 6, 6, 6, 6, 6, 6,
+172, 6, 6, 6, 6, 6, 6,208, 208,229,229,229,219,229,208,229,
+229,229,229,229,208,229,208,208, 229,208, 6,229,208, 6, 5, 6,
+ 5, 5, 5, 6,208,208, 6, 6, 208,208, 2, 2,229,229,208,229,
+
+// state[1095 + 2] 0x029780 Byte 4 of 4 (property)
+229,229,208,229,229,208,229,208, 229,229,229,229,208,208,208,208,
+208,208, 6, 6, 6, 6, 5, 5, 6,208,208, 6, 6, 2,208, 5,
+ 5,208,208,229,229,208,229,229, 208,229,208,208,229,208,208, 6,
+ 6, 6,229, 6, 6,208, 6,229, 6, 6,208, 6,208,208,229,229,
+
+// state[1096 + 2] 0x0297c0 Byte 4 of 4 (property)
+229,229,227,229,229,229,208,229, 229,229,229,208,229,229, 6, 5,
+ 6, 6, 6,229,229,229,208,208, 208,208, 6,208, 6, 5, 6, 6,
+ 6, 2, 2,208,229,208,208,208, 229, 6,229, 6, 2,229,229,229,
+208,208, 6, 6,229, 2,229,208, 208, 6, 6, 2, 2, 6, 6, 6,
+
+// state[1097 + 2] 0x029800 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+229,208, 6, 2, 2,208,208,208, 2,208,208,208,229, 6, 6, 6,
+ 2, 6,208,208,208,208,208, 2, 6, 2,208,208,208, 2, 6, 6,
+208, 2,229, 2, 2, 2, 6,229, 2,208, 5,229,208, 6,208,208,
+
+// state[1098 + 2] 0x029840 Byte 4 of 4 (property)
+208, 5, 5, 6,208,208, 5, 5, 208, 5,208, 2,208,208,229, 6,
+208,208,208,229,229,229, 6, 5, 208, 6,208, 5, 2,229, 5, 6,
+ 6,208, 6, 6,229,208, 6,229, 208,229,208, 6, 6, 5, 5, 6,
+229,208,208,229, 6,208, 3,229, 208,208,229,229,229,208,229,208,
+
+// state[1099 + 2] 0x029880 Byte 4 of 4 (property)
+ 6, 5, 5, 5,208, 6,208, 6, 208,229, 6, 3,229,229,229,208,
+208,208,208,208,229,208,229,208, 229,229,208,208,229,208,208,208,
+208,208, 6, 5, 5, 5,216,208, 6, 6, 6, 6, 2,208,229,229,
+ 0,208,229,229,229,208,229,229, 229,229,229,229,229,229,229,229,
+
+// state[1100 + 2] 0x0298c0 Byte 4 of 4 (property)
+208,208,208,208,208,208, 4, 5, 5, 5,229, 6,208,208,208, 6,
+208,208,208, 6, 3,208,229,208, 229,229,208,208,208,229,208,208,
+208,208,208,229,208, 6, 6, 5, 5, 6, 5, 6, 6,229,229,229,
+229,229,229,208,229,208,229,229, 229,229,229,229,229,208,208,208,
+
+// state[1101 + 2] 0x029900 Byte 4 of 4 (property)
+208,208, 6, 0, 5, 5, 5, 5, 6,208, 6, 6, 6, 6, 6,208,
+208, 6,208,208, 3, 2,208,208, 229,229,229,229,229,208,229,229,
+229,229,229,229,208,229,229,208, 208,229,208,208,229,208, 5, 5,
+ 5, 5, 6,208, 6, 6, 6, 5, 6, 6, 6,208, 6,229,208,229,
+
+// state[1102 + 2] 0x029940 Byte 4 of 4 (property)
+229,229,208,229,229,229,229,229, 229,208,229,208,229,208,229,229,
+229,229,229,208,229, 6,208,208, 208, 6, 5, 5, 5, 5,208, 6,
+ 6, 6,208, 6,208, 6, 6, 6, 6, 3, 5,229,229,229,229,229,
+208,229,208,208,208, 6, 6, 6, 6, 6, 5, 5, 6, 6, 6, 6,
+
+// state[1103 + 2] 0x029980 Byte 4 of 4 (property)
+208,229,229,229,229,229,208,229, 208,208,229,208,229,208,229,229,
+208,208, 6, 2, 5, 5, 5, 5, 6,208, 6,208,208, 5,208,208,
+208,229, 0,208,229,229,208,208, 208, 6, 6, 6, 5, 5, 6, 6,
+ 6, 6, 6,208, 6, 6,208,208, 229,229,229,229,208,208,208, 5,
+
+// state[1104 + 2] 0x0299c0 Byte 4 of 4 (property)
+229, 6,229,208,229,229,229,208, 208,229,229,208, 6, 2,208,229,
+229, 6, 6, 6,208,208,208, 6, 229,208,208,208, 6, 6, 6, 6,
+ 5,208,208,208,208,208, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[1105 + 2] 0x029a00 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6,229,208, 6,219,208,208,208, 0, 6, 6,208, 0,208,208,208,
+229,229,208,208,208, 6, 5, 5, 208, 6, 6, 6,229,229,208,208,
+229,208,208,229,229,208,208,208, 208, 6,208, 6, 2,229,229,208,
+
+// state[1106 + 2] 0x029a40 Byte 4 of 4 (property)
+208,208,208,208, 6, 6,208, 2, 6,229,208,229,229,229,208,208,
+ 6, 6, 6,208, 6,208, 2,208, 208,208,229,229,208,229,229,229,
+229,229,229,229, 6, 6,208, 6, 6, 2, 2, 2, 2,208,229,229,
+208,208,228,229,229,229,208, 6, 208,208, 6, 6, 6,229,208, 2,
+
+// state[1107 + 2] 0x029a80 Byte 4 of 4 (property)
+229,229,229,208,208,229,208, 6, 6,229,229,229,229,229,229,208,
+229,208,208, 5, 5,208, 6,229, 229,208,208,229,229, 6, 2, 6,
+229,208,229,229,229,229,229,229, 208,208,208, 5, 6,229,229, 6,
+ 6, 6,229,208, 6, 2, 5,229, 229,208, 6,208,229, 2,229,208,
+
+// state[1108 + 2] 0x029ac0 Byte 4 of 4 (property)
+208,229,229,208, 5, 5, 5, 6, 6,208,208, 5, 5, 6,208,208,
+ 5, 5, 6, 6, 6,229, 6, 5, 5, 5,208, 2, 5, 5,208, 5,
+229, 5, 5, 5, 5,229,227,208, 208,208, 5,208, 5,208, 5,208,
+208,208, 6,208,208, 6, 6,208, 6,208,229, 6,217,208, 6,229,
+
+// state[1109 + 2] 0x029b00 Byte 4 of 4 (property)
+229,208, 6,208, 5, 5,218, 6, 6,208,208,208, 6, 6, 5,229,
+229, 0,208,208,229,208,229,229, 208,229,229,229,208,229,208,229,
+229,208,208, 6, 6, 6, 5, 5, 5, 5, 2, 6, 6, 6, 6, 6,
+229,229,208,229,208,229,208,229, 229,229,229,217,208,229, 6, 5,
+
+// state[1110 + 2] 0x029b40 Byte 4 of 4 (property)
+ 5, 5, 5,208, 6, 6, 2,229, 208,208,229,229,229,208,229,229,
+229,229,229,208, 6, 5, 5, 5, 208, 6,208, 6, 2,208,208,208,
+229,208,208,208,229,229,208,229, 208,229,208,208, 6, 5, 5, 6,
+ 6, 6, 6, 6, 6, 2, 2,208, 5, 5,229,208,208,229,229,229,
+
+// state[1111 + 2] 0x029b80 Byte 4 of 4 (property)
+229,208,208,208,229,229,208,208, 229,208, 6, 5, 5, 5, 6, 6,
+ 6, 6, 6, 6, 2, 2,229,208, 229,229,229,229,229,229,229,208,
+229,208,208,208, 6, 6, 5, 5, 5, 5, 6, 6, 6, 2,208,229,
+229,229,208,229,229,229,229,229, 229,229,208, 6, 5,208,208, 2,
+
+// state[1112 + 2] 0x029bc0 Byte 4 of 4 (property)
+ 2, 2, 2,229,229,208,229,229, 229,229,229,229,229,229,208,229,
+208,229, 6, 6, 5, 5, 5, 6, 6, 6, 6,229,229,208,208,208,
+208, 6, 6, 6,208, 5,229,208, 229,208, 6, 6, 6, 5, 2, 2,
+208,208,229,229, 5, 5, 6, 6, 208, 2,229,208, 6, 6, 6,208,
+
+// state[1113 + 2] 0x029c00 Byte 4 of 4 (property)
+ 6,229,229,229, 5,208,208,208, 2, 5,208,208, 5,229,229,208,
+208,208,208,229,229,208,229,229, 6,208, 6, 6,208,229,229,229,
+208,229,229,208,229,229, 5,208, 208, 5,229,229,208,208,208,208,
+ 6,229,229,229,208,208,229,208, 6,229, 6, 6,208, 6,229,208,
+
+// state[1114 + 2] 0x029c40 Byte 4 of 4 (property)
+208,208, 6,208,208,208, 6,229, 208,208,208,208,208,229,208,229,
+208, 5, 5,208,208, 6,208,208, 229,208,229,208,229,208, 6,208,
+208,208,208,208,208,208,208,229, 208,208, 6,208,208,208, 6,208,
+208,208,229,208,208,208, 6,208, 208,208, 5,218, 6,208,229,208,
+
+// state[1115 + 2] 0x029c80 Byte 4 of 4 (property)
+208,208,208,229,208,208, 5, 5, 208, 6,208,208,208,208,208,229,
+208,208, 6, 6,208, 5, 5, 5, 208,208,208,208,208,208,208, 6,
+ 6,227,229,229, 6,227,229,208, 208,229,229,229,229, 5, 5, 5,
+ 5, 6, 6,208, 6, 6,208, 6, 6,208, 6,208,208,208,208,208,
+
+// state[1116 + 2] 0x029cc0 Byte 4 of 4 (property)
+208,229,208, 6, 5,208,208,208, 208,208, 2,208,229,208,229,229,
+229,208,229,229,229,229,208, 6, 5, 5, 5, 5, 5,208, 6,208,
+208,208,229,229,229,208,208,208, 208,208,208, 6, 5,208,208,208,
+208,208,208, 2, 2,229,229,208, 208,208,208, 6, 5, 5, 5, 5,
+
+// state[1117 + 2] 0x029d00 Byte 4 of 4 (property)
+208, 6, 6,229, 6, 2,208,208, 208,208,208,208, 6,208,208, 5,
+229,229,229,229,208,208, 5, 6, 208,208, 2,208,208,229,229,229,
+208,208,208,229,229, 6, 5, 6, 208,208,208, 5, 2,208,229, 5,
+ 5,229,208,229,208, 6,208,208, 208, 6,229,229, 6, 5, 5,208,
+
+// state[1118 + 2] 0x029d40 Byte 4 of 4 (property)
+229,229,229, 2,229,208, 6,208, 208,219, 5,233,208,208,229,208,
+208,208, 6,208,208,208,229,208, 5, 5,208,208, 2, 2, 2, 3,
+229,208,229,229,208,208,208,208, 229,229,208,229,229,229,229,208,
+208,229, 6, 5, 5, 5,229,208, 208,208, 6, 6, 5, 2, 2, 2,
+
+// state[1119 + 2] 0x029d80 Byte 4 of 4 (property)
+208,229,208,208,229,229,229,229, 208,208,208,208,208,208,229, 6,
+ 6, 5, 5, 5, 5, 5,208, 6, 208, 6,208,208, 6, 2,208, 2,
+ 5, 6,208,229,208,229,208,208, 208,208,227,208,229,229,208,229,
+208,229, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5,208, 6,208,
+
+// state[1120 + 2] 0x029dc0 Byte 4 of 4 (property)
+ 6, 6,208,208, 6, 6, 2, 2, 2, 2, 2,208,208,229,229,208,
+208,229,229,208,208, 0,208,229, 229,208,208,214, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5,219, 6, 6, 6, 6,229, 6,208, 6,
+ 6, 6, 3, 2,208,208,227,208, 208,229,229,208,208,208,208,208,
+
+// state[1121 + 2] 0x029e00 Byte 4 of 4 (property)
+229,229,229,208, 0,229,229,208, 208,208,208,229,208,208,208,208,
+208, 6, 5, 6, 6, 4,219, 5, 216, 5, 5, 5, 5, 6, 6,208,
+208, 6,208, 6, 6,208, 6,208, 208, 6, 6, 6, 6, 5, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2,208, 5, 6, 5, 4,229,229,
+
+// state[1122 + 2] 0x029e40 Byte 4 of 4 (property)
+208,208,229,208,208,208,208,208, 208,228,208,208,229,208,208,208,
+229,208,208, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6,208, 6,
+ 6, 6,208, 6,224, 6, 6, 6, 5, 2, 2, 2, 2, 2, 2, 2,
+ 2,229,229,208,208,229,229,229, 208,227,208,229,208,208,229,229,
+
+// state[1123 + 2] 0x029e80 Byte 4 of 4 (property)
+229,208,208,208,208,208,208, 6, 6, 6, 4, 2, 2, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 6, 6,208,208,208,208, 6, 6, 6,
+ 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,208, 5,229,208,227,
+229, 0,229,208,208,229,208,229, 208,208,227,208,208,208,208, 6,
+
+// state[1124 + 2] 0x029ec0 Byte 4 of 4 (property)
+ 6, 6, 6, 5, 4, 5, 5, 5, 5,208,208,208, 6,208, 6, 3,
+ 2, 2, 2, 2, 2, 2,208, 6, 208,208,208, 0,229,229,208,227,
+229,208,208,229,229,229,208,229, 208, 4, 5, 5, 6, 6, 6, 6,
+ 6, 6,208, 6,208,208,208, 6, 208, 2, 2, 2, 6, 6, 6, 6,
+
+// state[1125 + 2] 0x029f00 Byte 4 of 4 (property)
+ 2, 2,208,208,208,229,208,208, 0,208,208,208,208,208,208, 6,
+208,208,208, 6, 6, 6, 5, 5, 208, 6, 6, 6,208,229,208,208,
+229,208, 2, 5, 2,208,229,229, 227,208,208,208,208,208,208, 6,
+ 5, 5, 5, 5,208,208, 6, 2, 2, 6, 6,208,208,208,208,208,
+
+// state[1126 + 2] 0x029f40 Byte 4 of 4 (property)
+208,208, 5, 5, 5, 6, 6, 6, 208, 2, 2, 2,208,208,208,208,
+229, 5, 6,208, 6,208, 2, 2, 208, 6,208,208,208,208,208,208,
+208,208, 6,208, 2, 2, 5,208, 208,208,208,208,208, 5, 5, 6,
+208,208, 6,229, 6,229,208,229, 2, 6, 6, 6, 6, 6, 6, 6,
+
+// state[1127 + 2] 0x029f80 Byte 4 of 4 (property)
+ 5, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 5, 6,208,
+ 5,208,229,208,208,229,208,229, 229,208, 6,208,208,208,229,229,
+208,208,208,208,208,229,208,208, 6, 5, 5, 5,208, 6, 6, 6,
+227,208,208,229,208,208,208,227, 208,208,208,208,229,229,208,208,
+
+// state[1128 + 2] 0x029fc0 Byte 4 of 4 (property)
+229,208,229,208,229,229,208,229, 229,208,208,208,208, 6, 4, 5,
+208,208,208,208,208,208,208,208, 2, 2, 2,208,208,208,208,208,
+227,208,229,229,229,208,208,208, 227,208,229,208,229,208,208, 6,
+ 6, 5, 5, 5, 5, 5, 5,208, 208, 6, 6, 6,208, 6, 6, 6,
+
+// state[1129 + 2] 0x02a000 Byte 4 of 4 (property)
+208,208, 6, 6, 2, 2, 2,208, 229,229,208,208,229,208, 2,208,
+218, 5,228,229,208,229,208,229, 208,208, 0,229, 6,229,229,229,
+229,208,229,208,208,229,229,229, 208,208,208,208,208,208, 6, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 208, 6,208, 6,208,208, 6,208,
+
+// state[1130 + 2] 0x02a040 Byte 4 of 4 (property)
+208, 6, 2, 2, 2, 2, 2, 2, 208,229,229,208,208,208,227,229,
+229,229,229,208,229,229,208,229, 229,208,229,208,229,208,229,229,
+208,208,208,229,208,208,208,229, 208,208,229, 5, 5,208,208, 6,
+208, 6,208, 2, 2, 2, 2,229, 208,208, 6,229,208,229,208,229,
+
+// state[1131 + 2] 0x02a080 Byte 4 of 4 (property)
+208,208,233,208,208,229,229,208, 229,208,208,229,208,229,229,208,
+208,208, 6,208,208,229, 6, 5, 5, 5,208,208, 6,208,208,208,
+218,208, 6,208, 6,208, 6, 6, 6, 6,208, 6, 6,208, 2, 2,
+ 2, 2, 2, 2,229,208,208,208, 208,208,208,208,208,208,229,208,
+
+// state[1132 + 2] 0x02a0c0 Byte 4 of 4 (property)
+208,229,229,229,208,208,208,208, 229,229,229,229,208,229,208, 0,
+229,229,208,229,208,208,208,208, 208,218,208,208,208,208, 6, 6,
+ 5, 5, 5, 6,229, 6,208,208, 6,208,208, 6, 6,208, 6,208,
+ 6, 2, 2, 5, 2, 2,229, 6, 5,233,229,208,229,208,229,208,
+
+// state[1133 + 2] 0x02a100 Byte 4 of 4 (property)
+229,229,208,229,208, 0,208,208, 6,229,229,229, 0,208,208,208,
+208,208, 5, 5, 5, 6, 6,208, 208, 6, 6,208,208, 6,227, 6,
+208, 6, 6, 5, 2, 2, 2, 2, 2,208,208,208, 5,229,208,208,
+208,229,229,229,229,208,229,229, 0,208, 0,229,229,208,208,229,
+
+// state[1134 + 2] 0x02a140 Byte 4 of 4 (property)
+208,208,229,229,229,208,208,208, 229,208,208,208,208,208,208, 6,
+ 5, 5, 5, 5, 5, 5,229, 6, 6, 6,229, 6,208, 6, 6,229,
+ 6,229, 6, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,229,208,
+229,208,229,229, 0,229,229,208, 208,208,229,208,229,208,229,229,
+
+// state[1135 + 2] 0x02a180 Byte 4 of 4 (property)
+229,229, 0,229,208,208,229,208, 208,229,208,208,208,208, 6, 6,
+ 4, 5, 5, 5, 5, 5,208,229, 6, 6,208, 6, 6,208, 6, 6,
+ 6,208, 6, 2, 2, 2, 2, 2, 208,229,208,208, 5,208,208,229,
+229,229,229,229,208,208,208,229, 208,208,208,229, 0,229,208,208,
+
+// state[1136 + 2] 0x02a000 Byte 3 of 4 (relative offsets)
+ -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+
+// state[1137 + 2] 0x02a1c0 Byte 4 of 4 (property)
+ 6,208,208, 5,208, 6,229, 6, 6,208,229, 2, 2, 2, 2, 2,
+ 2,208,208,229,229,229,229,208, 229, 6,208, 6,208,208, 6, 5,
+208,229, 6, 6, 6, 6, 6, 6, 2, 2, 2, 2,229,229,208,229,
+229,229,229,229, 0,229,208,208, 208,229,208, 5,208, 6, 6,208,
+
+// state[1138 + 2] 0x02a200 Byte 4 of 4 (property)
+ 6,208, 6,208,208,208,208,208, 6,208,208,208,208,208, 5, 6,
+208, 6,208,208, 6, 6, 6, 6, 208,208,208,208,208,208,208,208,
+ 5, 6,208, 6,208,208,208,208, 208,208,208,208, 6,219,208,208,
+208,229, 5,208,208,208, 6,208, 208,208,208,208,208, 5, 2,229,
+
+// state[1139 + 2] 0x02a240 Byte 4 of 4 (property)
+208, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6,208,208, 229,208,208,208,229,208,208,208,
+ 5,208, 6,229,208, 2,229,229, 229,228, 6, 6, 6,208,229,229,
+208,208,208,208, 2,229,229,208, 208,219,208, 6,208, 2, 2,229,
+
+// state[1140 + 2] 0x02a280 Byte 4 of 4 (property)
+208,208,208,208,229, 2,229,229, 208,208, 6,208, 5,229, 5,208,
+208,229, 6,208,229,229,208,229, 208,208, 6,208,229, 6, 5, 5,
+ 5, 5, 6, 6, 6,229,229,229, 229, 6,208,208,208,208, 6,208,
+ 6,208, 5,208,229,208,229,208, 229,208,229,208, 5, 5, 5, 5,
+
+// state[1141 + 2] 0x02a2c0 Byte 4 of 4 (property)
+ 6, 6, 6,208,229,208,229,229, 229,229, 6,208, 6,208,208,229,
+208,208, 5, 6,229,208,208,229, 229,208,208, 5,229,229,217, 5,
+ 6,229,208,208, 6,208, 6,208, 6, 6,229,208,208, 5,229, 0,
+229, 6,208,208, 5, 6, 6,208, 208,219,208,208,208,208,208, 5,
+
+// state[1142 + 2] 0x02a300 Byte 4 of 4 (property)
+ 6,229,229,208, 6,208,208,229, 229,229,229,208,208,208,208, 6,
+229,208,208, 6,208,208,208,208, 208, 6,208,208, 5, 5, 6, 6,
+208, 6,208,229,208,208, 5, 6, 208, 6, 6, 6,208,229,229,208,
+229,229,208,229, 5, 6, 6, 6, 208, 6, 6,208,208,208,208,208,
+
+// state[1143 + 2] 0x02a340 Byte 4 of 4 (property)
+208,229,208,208,229,229,208,208, 6,208, 3,208,229,208,208,229,
+208,208,208,208,208, 5, 5, 6, 208, 6,208,229,208,229,208,208,
+208,208, 5,229,229,208,229,208, 208,208,208,229,208,208,208,208,
+208, 6,229,229,229,208,229, 6, 229,229,208,229,208,208,208,229,
+
+// state[1144 + 2] 0x02a380 Byte 4 of 4 (property)
+208,208,208,229,208, 6,208,208, 6, 6, 6, 6, 4, 5, 5, 5,
+ 5, 6,208,229,208,229,208,208, 6, 6,208,229,208,229,208, 5,
+208,229, 5,208,208,208,208,208, 208, 5,208,208,208,229,208,208,
+208, 6,208, 5,208,229,229, 6, 6,208,208,208, 6,229,229, 6,
+
+// state[1145 + 2] 0x02a3c0 Byte 4 of 4 (property)
+ 6,208,208, 5, 5, 6,229,229, 229,229,208,208,208,208, 5, 6,
+ 6,208, 6,229,229,208,208, 6, 208,229,229,208,208,208,208, 6,
+ 6,208, 6, 6,229,208,229,208, 208, 6, 6, 6,208, 5,229,208,
+208,208,229,229,229, 6,208,208, 229,208,229,229,208, 5, 5,229,
+
+// state[1146 + 2] 0x02a400 Byte 4 of 4 (property)
+229, 6,208,229,229, 6, 6,229, 208,229, 5,229,208,229,229,229,
+ 6,208,208,208,229, 5,229,208, 229,208, 6,208,208,229,229,229,
+208,208, 2, 6,229,229,229,229, 229, 6,208,208, 6, 6, 6, 6,
+ 6, 6,229,208,229, 6, 6, 4, 5,208, 6, 6, 6, 6, 2,229,
+
+// state[1147 + 2] 0x02a440 Byte 4 of 4 (property)
+229,208,229,208,229, 6, 6, 6, 229,229,229,229,229,208,208, 6,
+ 6, 2,229,229,208,229,229,229, 208,229,229,208,229,229,208, 6,
+ 6, 5, 6,208, 6,208,229,229, 229,229,229,208,229,229,229,208,
+229,229,229,229,208, 6, 6,208, 6, 6, 6, 6, 6, 6,208,229,
+
+// state[1148 + 2] 0x02a480 Byte 4 of 4 (property)
+229,208,208,208,229,208, 6, 6, 208, 6, 6,208,208, 6, 6,229,
+208,229,229,229,229,208,208, 2, 229,229,208,229,229,229,208,229,
+229,229,229,229, 6, 6, 6,208, 6,229,229,229,229,229,229,229,
+208, 6, 6, 2,229,208, 6, 2, 5,229,208,229, 6, 2,208,229,
+
+// state[1149 + 2] 0x02a4c0 Byte 4 of 4 (property)
+229,208, 6,229,229, 6,208, 2, 2,208,229,229,229, 6,229, 5,
+229,208,208,208,229,208,208, 5, 208, 6,208,229,229,208,208, 0,
+ 3,208,208, 6,208,208, 6,208, 208,208, 2, 2,229,208,229,208,
+229,208,208, 5,208,208,208,208, 208,229, 6,208,208,208,208,208,
+
+// state[1150 + 2] 0x02a500 Byte 4 of 4 (property)
+208,208,208,219,229,208,229, 6, 208, 6,208,229,219,229,208,208,
+208,208, 5, 6,229,208,229,208, 208, 6, 2,208,229,208, 6, 6,
+ 2, 2,208,229,208,208,208,208, 208,208,208,208,208, 6,208,208,
+208,208, 6,229, 6,208,208,229, 208, 6,208,208,229,208,208,208,
+
+// state[1151 + 2] 0x02a540 Byte 4 of 4 (property)
+208,208,208,208,208, 6,208, 6, 208,208,208,229,229,229,208,208,
+ 6,208,208,208,208, 6, 6,208, 208,208,229,229,229,208,229,208,
+ 6,208, 6, 6,208, 6, 6,208, 208,208,208,229,208,229,229,229,
+ 6,208, 6, 6, 6,208,229,229, 229,208,208, 5, 6,208,208,208,
+
+// state[1152 + 2] 0x02a580 Byte 4 of 4 (property)
+229,208,229,208, 6,208,208,229, 229, 6, 6,229,208,208,229,208,
+229,208,208,208,208,229,208,229, 208, 6,229,229, 6, 6, 6, 6,
+ 2,229,229,229,208,208,208,208, 208,208,229, 2, 2, 2,208,208,
+208, 6, 6,229, 5,208,229,229, 6, 6, 2,208,229,208,208, 6,
+
+// state[1153 + 2] 0x02a5c0 Byte 4 of 4 (property)
+ 2,229,229, 6, 5,208,229,208, 6,229,208,229,208,208, 5,229,
+208,208,208, 6,229,208, 6, 6, 208,229, 5,229,229,208,208,208,
+208,208,208,208,208,208,229,229, 229,208,229,229,229,229, 6,208,
+ 6, 4, 5, 6, 6,208,229,229, 208,208,208,229,208,208,229,208,
+
+// state[1154 + 2] 0x02a600 Byte 4 of 4 (property)
+229,208, 4, 6,208,208, 6, 6, 6, 6,208,208, 2,229,208,208,
+229,229,229,229,208,229,208,229, 208, 6, 4,208,208, 6, 6,208,
+208,208,208, 6,208,229,208,208, 229,229,229, 6,229,208,208, 6,
+208, 6,229, 6,208, 2,208,208, 208,229,208,229,229,208,229,208,
+
+// state[1155 + 2] 0x02a640 Byte 4 of 4 (property)
+229,208,208,208, 6, 6,208,208, 208,208,229,208,229,229,229,229,
+208,208,208,208, 6, 6,208,208, 208,208,208,229,208,229,229,229,
+ 6, 6, 6,229,229,208,229,229, 219,208,208, 6,208, 6,208, 6,
+229, 6,208,229,208,208,208,229, 208,208, 6,208,208,229, 6,208,
+
+// state[1156 + 2] 0x02a680 Byte 4 of 4 (property)
+ 5,208,208,208,208, 6,208, 6, 208,208, 6,208,208,208,208, 6,
+ 6,208,208,208, 6,208,208,208, 208,208, 2, 5,208,229,208,208,
+208, 6, 6, 6, 2,231,208,208, 208, 5, 6,208,208,208,229,208,
+229,229, 4, 5, 5,208,229,229, 208,208,208,229,208,208,208,208,
+
+// state[1157 + 2] 0x02a6c0 Byte 4 of 4 (property)
+229,208,208,229,208, 2, 5, 2, 208, 5,229,208, 6,208,229,208,
+208,208,229,208,208,208,208, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[1158 + 2] 0x000080 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[1159 + 2] 0x000080 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[1160 + 2] 0x02f800 Byte 4 of 4 (property)
+ 5, 5, 5, 5,217,217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,217, 5,217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[1161 + 2] 0x02f840 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5, 5,217,
+ 5, 5,217, 5, 5,217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[1162 + 2] 0x02f880 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5,217, 5, 5, 5,217, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5, 5, 5, 5, 5, 5,
+217, 5, 5, 5, 5, 5,217,217, 5, 5, 5, 5, 5,217, 5, 5,
+ 5,217, 5, 5,217, 5, 5,217, 5, 5,217, 5, 5, 5, 5, 5,
+
+// state[1163 + 2] 0x02f8c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+217,217,217, 5, 5,217,217, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5,
+
+// state[1164 + 2] 0x02f900 Byte 4 of 4 (property)
+217,217, 5, 5, 5, 5, 5,217, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5,217, 5, 5, 5,217, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5,217, 5, 217, 5, 5, 5, 5, 5, 5, 5,
+
+// state[1165 + 2] 0x02f940 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5, 5, 5, 5, 5,217,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[1166 + 2] 0x02f980 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 217, 5,217, 5, 5, 5, 5, 5,
+
+// state[1167 + 2] 0x02f9c0 Byte 4 of 4 (property)
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5,217, 5, 5, 5, 5, 5, 5, 5,217,217, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 217, 5,217, 5, 5, 5,217, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+// state[1168 + 2] 0x02f000 Byte 3 of 4 (relative offsets)
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
+ -8, -7, -6, -5, -4, -3, -2, -1, 1, -9, -9, -9, -9, -9, -9, -9,
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
+
+// state[1169 + 2] 0x02fa00 Byte 4 of 4 (property)
+217, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,217, 5, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+};
+
+// Remap base[0] = (del, add, string_offset)
+static const RemapEntry cld_generated_CjkUni_remap_base[] = {
+{0,0,0} };
+
+// Remap string[0]
+static const unsigned char cld_generated_CjkUni_remap_string[] = {
+0 };
+
+extern const UTF8PropObj cld_generated_CjkUni_obj = {
+ cld_generated_CjkUni_STATE0,
+ cld_generated_CjkUni_STATE0_SIZE,
+ cld_generated_CjkUni_TOTAL_SIZE,
+ cld_generated_CjkUni_MAX_EXPAND_X4,
+ cld_generated_CjkUni_SHIFT,
+ cld_generated_CjkUni_BYTES,
+ cld_generated_CjkUni_LOSUB,
+ cld_generated_CjkUni_HIADD,
+ cld_generated_CjkUni,
+ cld_generated_CjkUni_remap_base,
+ cld_generated_CjkUni_remap_string,
+ NULL
+};
+
+
+#undef X__
+#undef RJ_
+#undef S1_
+#undef S2_
+#undef S3_
+#undef S21
+#undef S31
+#undef S32
+#undef T1_
+#undef T2_
+#undef S11
+#undef SP_
+#undef D__
+#undef RJA
+
+// Table has 75008 bytes, Hash = E40D-2DFE
+
+} // End namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/cld_generated_score_quad_octa_0122_2.cc b/browser/components/translation/cld2/internal/cld_generated_score_quad_octa_0122_2.cc
new file mode 100644
index 000000000..c57f26363
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cld_generated_score_quad_octa_0122_2.cc
@@ -0,0 +1,639 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+
+namespace CLD2 {
+
+// Average score per 1024 bytes
+static const int kAvgDeltaOctaScoreSize = 614 * 4;
+extern const short kAvgDeltaOctaScore[kAvgDeltaOctaScoreSize] = {
+// Latn Cyrl Arab Other script
+// Updated 20140202 for CLD2 Chrome 256K entries
+ 1314, 0, 0, 0, // 0 ENGLISH en
+ 1188, 0, 0, 0, // 1 DANISH da
+ 1133, 0, 0, 0, // 2 DUTCH nl
+ 1306, 0, 0, 0, // 3 FINNISH fi
+ 1033, 0, 0, 0, // 4 FRENCH fr
+ 1306, 0, 0, 0, // 5 GERMAN de
+ 0, 0, 0, 776, // 6 HEBREW he
+ 960, 0, 0, 0, // 7 ITALIAN it
+ 0, 0, 0, 3100, // 8 Japanese ja
+ 0, 0, 0, 3669, // 9 Korean ko
+ 1274, 0, 0, 0, // 10 NORWEGIAN no
+ 1313, 0, 0, 0, // 11 POLISH pl
+ 1061, 0, 0, 0, // 12 PORTUGUESE pt
+ 0, 776, 0, 0, // 13 RUSSIAN ru
+ 762, 0, 0, 0, // 14 SPANISH es
+ 1040, 0, 0, 0, // 15 SWEDISH sv
+ 0, 0, 0, 1928, // 16 Chinese zh
+ 1286, 0, 0, 0, // 17 CZECH cs
+ 0, 0, 0, 1024, // 18 GREEK el
+ 1235, 0, 0, 0, // 19 ICELANDIC is
+ 1236, 0, 0, 0, // 20 LATVIAN lv
+ 1157, 0, 0, 0, // 21 LITHUANIAN lt
+ 961, 771, 0, 0, // 22 ROMANIAN ro
+ 1284, 0, 0, 0, // 23 HUNGARIAN hu
+ 1172, 0, 0, 0, // 24 ESTONIAN et
+ 0, 0, 0, 0, // 25 Ignore xxx
+ 0, 0, 0, 0, // 26 Unknown un
+ 0, 793, 0, 0, // 27 BULGARIAN bg
+ 563, 0, 0, 0, // 28 CROATIAN hr
+ 564, 930, 0, 0, // 29 SERBIAN sr
+ 1424, 0, 0, 0, // 30 IRISH ga
+ 888, 0, 0, 0, // 31 GALICIAN gl
+ 1381, 0, 0, 0, // 32 TAGALOG tl
+ 1298, 0, 0, 0, // 33 TURKISH tr
+ 0, 803, 0, 0, // 34 UKRAINIAN uk
+ 0, 0, 0, 744, // 35 HINDI hi
+ 0, 854, 0, 0, // 36 MACEDONIAN mk
+ 0, 0, 0, 600, // 37 BENGALI bn
+ 1418, 0, 0, 0, // 38 INDONESIAN id
+ 0, 0, 0, 0, // 39 LATIN la
+ 1521, 0, 0, 0, // 40 MALAY ms
+ 0, 0, 0, 1024, // 41 MALAYALAM ml
+ 1669, 0, 0, 0, // 42 WELSH cy
+ 0, 0, 0, 545, // 43 NEPALI ne
+ 0, 0, 0, 1024, // 44 TELUGU te
+ 1304, 0, 0, 0, // 45 ALBANIAN sq
+ 0, 0, 0, 1024, // 46 TAMIL ta
+ 0, 594, 0, 0, // 47 BELARUSIAN be
+ 1115, 0, 0, 0, // 48 JAVANESE jw
+ 0, 0, 0, 0, // 49 OCCITAN oc
+ 0, 0, 1033, 0, // 50 URDU ur
+ 0, 0, 0, 527, // 51 BIHARI bh
+ 0, 0, 0, 1024, // 52 GUJARATI gu
+ 0, 0, 0, 1024, // 53 THAI th
+ 0, 0, 843, 0, // 54 ARABIC ar
+ 806, 0, 0, 0, // 55 CATALAN ca
+ 0, 0, 0, 0, // 56 ESPERANTO eo
+ 1425, 0, 0, 0, // 57 BASQUE eu
+ 0, 0, 0, 0, // 58 INTERLINGUA ia
+ 0, 0, 0, 1024, // 59 KANNADA kn
+ 0, 0, 0, 1024, // 60 PUNJABI pa
+ 1583, 0, 0, 0, // 61 SCOTS_GAELIC gd
+ 1396, 0, 0, 0, // 62 SWAHILI sw
+ 718, 0, 0, 0, // 63 SLOVENIAN sl
+ 0, 0, 0, 584, // 64 MARATHI mr
+ 1219, 0, 0, 0, // 65 MALTESE mt
+ 1470, 0, 0, 0, // 66 VIETNAMESE vi
+ 0, 0, 0, 0, // 67 FRISIAN fy
+ 1314, 0, 0, 0, // 68 SLOVAK sk
+ 0, 0, 0, 1908, // 69 ChineseT zh-Hant
+ 0, 0, 0, 0, // 70 FAROESE fo
+ 0, 0, 0, 0, // 71 SUNDANESE su
+ 0, 0, 0, 0, // 72 UZBEK uz
+ 0, 0, 0, 0, // 73 AMHARIC am
+ 1425, 0, 0, 0, // 74 AZERBAIJANI az
+ 0, 0, 0, 1024, // 75 GEORGIAN ka
+ 0, 0, 0, 0, // 76 TIGRINYA ti
+ 0, 0, 1013, 0, // 77 PERSIAN fa
+ 596, 0, 0, 0, // 78 BOSNIAN bs
+ 0, 0, 0, 1024, // 79 SINHALESE si
+ 0, 0, 0, 0, // 80 NORWEGIAN_N nn
+ 0, 0, 0, 0, // 81 81
+ 0, 0, 0, 0, // 82 82
+ 0, 0, 0, 0, // 83 XHOSA xh
+ 1962, 0, 0, 0, // 84 ZULU zu
+ 0, 0, 0, 0, // 85 GUARANI gn
+ 0, 0, 0, 0, // 86 SESOTHO st
+ 0, 0, 0, 0, // 87 TURKMEN tk
+ 0, 0, 0, 0, // 88 KYRGYZ ky
+ 0, 0, 0, 0, // 89 BRETON br
+ 0, 0, 0, 0, // 90 TWI tw
+ 0, 0, 0, 978, // 91 YIDDISH yi
+ 0, 0, 0, 0, // 92 92
+ 1544, 0, 0, 0, // 93 SOMALI so
+ 0, 0, 0, 0, // 94 UIGHUR ug
+ 0, 0, 0, 0, // 95 KURDISH ku
+ 0, 0, 0, 1024, // 96 MONGOLIAN mn
+ 0, 0, 0, 1024, // 97 ARMENIAN hy
+ 0, 0, 0, 1024, // 98 LAOTHIAN lo
+ 0, 0, 0, 0, // 99 SINDHI sd
+ 0, 0, 0, 0, // 100 RHAETO_ROMANCE rm
+ 1179, 0, 0, 0, // 101 AFRIKAANS af
+ 0, 0, 0, 0, // 102 LUXEMBOURGISH lb
+ 0, 0, 0, 1024, // 103 BURMESE my
+ 0, 0, 0, 1024, // 104 KHMER km
+ 0, 0, 0, 0, // 105 TIBETAN bo
+ 0, 0, 0, 1024, // 106 DHIVEHI dv
+ 0, 0, 0, 1024, // 107 CHEROKEE chr
+ 0, 0, 0, 1024, // 108 SYRIAC syr
+ 0, 0, 0, 1024, // 109 LIMBU lif
+ 0, 0, 0, 1024, // 110 ORIYA or
+ 0, 0, 0, 0, // 111 ASSAMESE as
+ 0, 0, 0, 0, // 112 CORSICAN co
+ 0, 0, 0, 0, // 113 INTERLINGUE ie
+ 0, 0, 0, 0, // 114 KAZAKH kk
+ 0, 0, 0, 0, // 115 LINGALA ln
+ 0, 0, 0, 0, // 116 116
+ 0, 0, 0, 0, // 117 PASHTO ps
+ 0, 0, 0, 0, // 118 QUECHUA qu
+ 0, 0, 0, 0, // 119 SHONA sn
+ 0, 0, 0, 0, // 120 TAJIK tg
+ 0, 0, 0, 0, // 121 TATAR tt
+ 0, 0, 0, 0, // 122 TONGA to
+ 929, 0, 0, 0, // 123 YORUBA yo
+ 0, 0, 0, 0, // 124 124
+ 0, 0, 0, 0, // 125 125
+ 0, 0, 0, 0, // 126 126
+ 0, 0, 0, 0, // 127 127
+ 0, 0, 0, 0, // 128 MAORI mi
+ 0, 0, 0, 0, // 129 WOLOF wo
+ 0, 0, 0, 0, // 130 ABKHAZIAN ab
+ 0, 0, 0, 0, // 131 AFAR aa
+ 0, 0, 0, 0, // 132 AYMARA ay
+ 0, 0, 0, 0, // 133 BASHKIR ba
+ 0, 0, 0, 0, // 134 BISLAMA bi
+ 0, 0, 0, 0, // 135 DZONGKHA dz
+ 0, 0, 0, 0, // 136 FIJIAN fj
+ 0, 0, 0, 0, // 137 GREENLANDIC kl
+ 1345, 0, 0, 0, // 138 HAUSA ha
+ 1346, 0, 0, 0, // 139 HAITIAN_CREOLE ht
+ 0, 0, 0, 0, // 140 INUPIAK ik
+ 0, 0, 0, 1024, // 141 INUKTITUT iu
+ 0, 0, 0, 0, // 142 KASHMIRI ks
+ 1862, 0, 0, 0, // 143 KINYARWANDA rw
+ 0, 0, 0, 0, // 144 MALAGASY mg
+ 0, 0, 0, 0, // 145 NAURU na
+ 0, 0, 0, 0, // 146 OROMO om
+ 0, 0, 0, 0, // 147 RUNDI rn
+ 0, 0, 0, 0, // 148 SAMOAN sm
+ 0, 0, 0, 0, // 149 SANGO sg
+ 0, 0, 0, 0, // 150 SANSKRIT sa
+ 0, 0, 0, 0, // 151 SISWANT ss
+ 0, 0, 0, 0, // 152 TSONGA ts
+ 0, 0, 0, 0, // 153 TSWANA tn
+ 0, 0, 0, 0, // 154 VOLAPUK vo
+ 0, 0, 0, 0, // 155 ZHUANG za
+ 0, 0, 0, 0, // 156 KHASI kha
+ 0, 0, 0, 0, // 157 SCOTS sco
+ 1385, 0, 0, 0, // 158 GANDA lg
+ 0, 0, 0, 0, // 159 MANX gv
+ 0, 0, 0, 0, // 160 MONTENEGRIN sr-ME
+ 0, 0, 0, 0, // 161 AKAN ak
+ 1245, 0, 0, 0, // 162 IGBO ig
+ 0, 0, 0, 0, // 163 MAURITIAN_CREOLE mfe
+ 0, 0, 0, 0, // 164 HAWAIIAN haw
+ 1357, 0, 0, 0, // 165 CEBUANO ceb
+ 0, 0, 0, 0, // 166 EWE ee
+ 0, 0, 0, 0, // 167 GA gaa
+ 2053, 0, 0, 0, // 168 HMONG hmn
+ 0, 0, 0, 0, // 169 KRIO kri
+ 0, 0, 0, 0, // 170 LOZI loz
+ 0, 0, 0, 0, // 171 LUBA_LULUA lua
+ 0, 0, 0, 0, // 172 LUO_KENYA_AND_TANZANIA luo
+ 0, 0, 0, 0, // 173 NEWARI new
+ 0, 0, 0, 0, // 174 NYANJA ny
+ 0, 0, 0, 0, // 175 OSSETIAN os
+ 0, 0, 0, 0, // 176 PAMPANGA pam
+ 0, 0, 0, 0, // 177 PEDI nso
+ 0, 0, 0, 0, // 178 RAJASTHANI raj
+ 0, 0, 0, 0, // 179 SESELWA crs
+ 0, 0, 0, 0, // 180 TUMBUKA tum
+ 0, 0, 0, 0, // 181 VENDA ve
+ 0, 0, 0, 0, // 182 WARAY_PHILIPPINES war
+ 0, 0, 0, 0, // 183 183
+ 0, 0, 0, 0, // 184 184
+ 0, 0, 0, 0, // 185 185
+ 0, 0, 0, 0, // 186 186
+ 0, 0, 0, 0, // 187 187
+ 0, 0, 0, 0, // 188 188
+ 0, 0, 0, 0, // 189 189
+ 0, 0, 0, 0, // 190 190
+ 0, 0, 0, 0, // 191 191
+ 0, 0, 0, 0, // 192 192
+ 0, 0, 0, 0, // 193 193
+ 0, 0, 0, 0, // 194 194
+ 0, 0, 0, 0, // 195 195
+ 0, 0, 0, 0, // 196 196
+ 0, 0, 0, 0, // 197 197
+ 0, 0, 0, 0, // 198 198
+ 0, 0, 0, 0, // 199 199
+ 0, 0, 0, 0, // 200 200
+ 0, 0, 0, 0, // 201 201
+ 0, 0, 0, 0, // 202 202
+ 0, 0, 0, 0, // 203 203
+ 0, 0, 0, 0, // 204 204
+ 0, 0, 0, 0, // 205 205
+ 0, 0, 0, 0, // 206 206
+ 0, 0, 0, 0, // 207 207
+ 0, 0, 0, 0, // 208 208
+ 0, 0, 0, 0, // 209 209
+ 0, 0, 0, 0, // 210 210
+ 0, 0, 0, 0, // 211 211
+ 0, 0, 0, 0, // 212 212
+ 0, 0, 0, 0, // 213 213
+ 0, 0, 0, 0, // 214 214
+ 0, 0, 0, 0, // 215 215
+ 0, 0, 0, 0, // 216 216
+ 0, 0, 0, 0, // 217 217
+ 0, 0, 0, 0, // 218 218
+ 0, 0, 0, 0, // 219 219
+ 0, 0, 0, 0, // 220 220
+ 0, 0, 0, 0, // 221 221
+ 0, 0, 0, 0, // 222 222
+ 0, 0, 0, 0, // 223 223
+ 0, 0, 0, 0, // 224 224
+ 0, 0, 0, 0, // 225 225
+ 0, 0, 0, 0, // 226 226
+ 0, 0, 0, 0, // 227 227
+ 0, 0, 0, 0, // 228 228
+ 0, 0, 0, 0, // 229 229
+ 0, 0, 0, 0, // 230 230
+ 0, 0, 0, 0, // 231 231
+ 0, 0, 0, 0, // 232 232
+ 0, 0, 0, 0, // 233 233
+ 0, 0, 0, 0, // 234 234
+ 0, 0, 0, 0, // 235 235
+ 0, 0, 0, 0, // 236 236
+ 0, 0, 0, 0, // 237 237
+ 0, 0, 0, 0, // 238 238
+ 0, 0, 0, 0, // 239 239
+ 0, 0, 0, 0, // 240 240
+ 0, 0, 0, 0, // 241 241
+ 0, 0, 0, 0, // 242 242
+ 0, 0, 0, 0, // 243 243
+ 0, 0, 0, 0, // 244 244
+ 0, 0, 0, 0, // 245 245
+ 0, 0, 0, 0, // 246 246
+ 0, 0, 0, 0, // 247 247
+ 0, 0, 0, 0, // 248 248
+ 0, 0, 0, 0, // 249 249
+ 0, 0, 0, 0, // 250 250
+ 0, 0, 0, 0, // 251 251
+ 0, 0, 0, 0, // 252 252
+ 0, 0, 0, 0, // 253 253
+ 0, 0, 0, 0, // 254 254
+ 0, 0, 0, 0, // 255 255
+ 0, 0, 0, 0, // 256 256
+ 0, 0, 0, 0, // 257 257
+ 0, 0, 0, 0, // 258 258
+ 0, 0, 0, 0, // 259 259
+ 0, 0, 0, 0, // 260 260
+ 0, 0, 0, 0, // 261 261
+ 0, 0, 0, 0, // 262 262
+ 0, 0, 0, 0, // 263 263
+ 0, 0, 0, 0, // 264 264
+ 0, 0, 0, 0, // 265 265
+ 0, 0, 0, 0, // 266 266
+ 0, 0, 0, 0, // 267 267
+ 0, 0, 0, 0, // 268 268
+ 0, 0, 0, 0, // 269 269
+ 0, 0, 0, 0, // 270 270
+ 0, 0, 0, 0, // 271 271
+ 0, 0, 0, 0, // 272 272
+ 0, 0, 0, 0, // 273 273
+ 0, 0, 0, 0, // 274 274
+ 0, 0, 0, 0, // 275 275
+ 0, 0, 0, 0, // 276 276
+ 0, 0, 0, 0, // 277 277
+ 0, 0, 0, 0, // 278 278
+ 0, 0, 0, 0, // 279 279
+ 0, 0, 0, 0, // 280 280
+ 0, 0, 0, 0, // 281 281
+ 0, 0, 0, 0, // 282 282
+ 0, 0, 0, 0, // 283 283
+ 0, 0, 0, 0, // 284 284
+ 0, 0, 0, 0, // 285 285
+ 0, 0, 0, 0, // 286 286
+ 0, 0, 0, 0, // 287 287
+ 0, 0, 0, 0, // 288 288
+ 0, 0, 0, 0, // 289 289
+ 0, 0, 0, 0, // 290 290
+ 0, 0, 0, 0, // 291 291
+ 0, 0, 0, 0, // 292 292
+ 0, 0, 0, 0, // 293 293
+ 0, 0, 0, 0, // 294 294
+ 0, 0, 0, 0, // 295 295
+ 0, 0, 0, 0, // 296 296
+ 0, 0, 0, 0, // 297 297
+ 0, 0, 0, 0, // 298 298
+ 0, 0, 0, 0, // 299 299
+ 0, 0, 0, 0, // 300 300
+ 0, 0, 0, 0, // 301 301
+ 0, 0, 0, 0, // 302 302
+ 0, 0, 0, 0, // 303 303
+ 0, 0, 0, 0, // 304 304
+ 0, 0, 0, 0, // 305 305
+ 0, 0, 0, 0, // 306 306
+ 0, 0, 0, 0, // 307 307
+ 0, 0, 0, 0, // 308 308
+ 0, 0, 0, 0, // 309 309
+ 0, 0, 0, 0, // 310 310
+ 0, 0, 0, 0, // 311 311
+ 0, 0, 0, 0, // 312 312
+ 0, 0, 0, 0, // 313 313
+ 0, 0, 0, 0, // 314 314
+ 0, 0, 0, 0, // 315 315
+ 0, 0, 0, 0, // 316 316
+ 0, 0, 0, 0, // 317 317
+ 0, 0, 0, 0, // 318 318
+ 0, 0, 0, 0, // 319 319
+ 0, 0, 0, 0, // 320 320
+ 0, 0, 0, 0, // 321 321
+ 0, 0, 0, 0, // 322 322
+ 0, 0, 0, 0, // 323 323
+ 0, 0, 0, 0, // 324 324
+ 0, 0, 0, 0, // 325 325
+ 0, 0, 0, 0, // 326 326
+ 0, 0, 0, 0, // 327 327
+ 0, 0, 0, 0, // 328 328
+ 0, 0, 0, 0, // 329 329
+ 0, 0, 0, 0, // 330 330
+ 0, 0, 0, 0, // 331 331
+ 0, 0, 0, 0, // 332 332
+ 0, 0, 0, 0, // 333 333
+ 0, 0, 0, 0, // 334 334
+ 0, 0, 0, 0, // 335 335
+ 0, 0, 0, 0, // 336 336
+ 0, 0, 0, 0, // 337 337
+ 0, 0, 0, 0, // 338 338
+ 0, 0, 0, 0, // 339 339
+ 0, 0, 0, 0, // 340 340
+ 0, 0, 0, 0, // 341 341
+ 0, 0, 0, 0, // 342 342
+ 0, 0, 0, 0, // 343 343
+ 0, 0, 0, 0, // 344 344
+ 0, 0, 0, 0, // 345 345
+ 0, 0, 0, 0, // 346 346
+ 0, 0, 0, 0, // 347 347
+ 0, 0, 0, 0, // 348 348
+ 0, 0, 0, 0, // 349 349
+ 0, 0, 0, 0, // 350 350
+ 0, 0, 0, 0, // 351 351
+ 0, 0, 0, 0, // 352 352
+ 0, 0, 0, 0, // 353 353
+ 0, 0, 0, 0, // 354 354
+ 0, 0, 0, 0, // 355 355
+ 0, 0, 0, 0, // 356 356
+ 0, 0, 0, 0, // 357 357
+ 0, 0, 0, 0, // 358 358
+ 0, 0, 0, 0, // 359 359
+ 0, 0, 0, 0, // 360 360
+ 0, 0, 0, 0, // 361 361
+ 0, 0, 0, 0, // 362 362
+ 0, 0, 0, 0, // 363 363
+ 0, 0, 0, 0, // 364 364
+ 0, 0, 0, 0, // 365 365
+ 0, 0, 0, 0, // 366 366
+ 0, 0, 0, 0, // 367 367
+ 0, 0, 0, 0, // 368 368
+ 0, 0, 0, 0, // 369 369
+ 0, 0, 0, 0, // 370 370
+ 0, 0, 0, 0, // 371 371
+ 0, 0, 0, 0, // 372 372
+ 0, 0, 0, 0, // 373 373
+ 0, 0, 0, 0, // 374 374
+ 0, 0, 0, 0, // 375 375
+ 0, 0, 0, 0, // 376 376
+ 0, 0, 0, 0, // 377 377
+ 0, 0, 0, 0, // 378 378
+ 0, 0, 0, 0, // 379 379
+ 0, 0, 0, 0, // 380 380
+ 0, 0, 0, 0, // 381 381
+ 0, 0, 0, 0, // 382 382
+ 0, 0, 0, 0, // 383 383
+ 0, 0, 0, 0, // 384 384
+ 0, 0, 0, 0, // 385 385
+ 0, 0, 0, 0, // 386 386
+ 0, 0, 0, 0, // 387 387
+ 0, 0, 0, 0, // 388 388
+ 0, 0, 0, 0, // 389 389
+ 0, 0, 0, 0, // 390 390
+ 0, 0, 0, 0, // 391 391
+ 0, 0, 0, 0, // 392 392
+ 0, 0, 0, 0, // 393 393
+ 0, 0, 0, 0, // 394 394
+ 0, 0, 0, 0, // 395 395
+ 0, 0, 0, 0, // 396 396
+ 0, 0, 0, 0, // 397 397
+ 0, 0, 0, 0, // 398 398
+ 0, 0, 0, 0, // 399 399
+ 0, 0, 0, 0, // 400 400
+ 0, 0, 0, 0, // 401 401
+ 0, 0, 0, 0, // 402 402
+ 0, 0, 0, 0, // 403 403
+ 0, 0, 0, 0, // 404 404
+ 0, 0, 0, 0, // 405 405
+ 0, 0, 0, 0, // 406 406
+ 0, 0, 0, 0, // 407 407
+ 0, 0, 0, 0, // 408 408
+ 0, 0, 0, 0, // 409 409
+ 0, 0, 0, 0, // 410 410
+ 0, 0, 0, 0, // 411 411
+ 0, 0, 0, 0, // 412 412
+ 0, 0, 0, 0, // 413 413
+ 0, 0, 0, 0, // 414 414
+ 0, 0, 0, 0, // 415 415
+ 0, 0, 0, 0, // 416 416
+ 0, 0, 0, 0, // 417 417
+ 0, 0, 0, 0, // 418 418
+ 0, 0, 0, 0, // 419 419
+ 0, 0, 0, 0, // 420 420
+ 0, 0, 0, 0, // 421 421
+ 0, 0, 0, 0, // 422 422
+ 0, 0, 0, 0, // 423 423
+ 0, 0, 0, 0, // 424 424
+ 0, 0, 0, 0, // 425 425
+ 0, 0, 0, 0, // 426 426
+ 0, 0, 0, 0, // 427 427
+ 0, 0, 0, 0, // 428 428
+ 0, 0, 0, 0, // 429 429
+ 0, 0, 0, 0, // 430 430
+ 0, 0, 0, 0, // 431 431
+ 0, 0, 0, 0, // 432 432
+ 0, 0, 0, 0, // 433 433
+ 0, 0, 0, 0, // 434 434
+ 0, 0, 0, 0, // 435 435
+ 0, 0, 0, 0, // 436 436
+ 0, 0, 0, 0, // 437 437
+ 0, 0, 0, 0, // 438 438
+ 0, 0, 0, 0, // 439 439
+ 0, 0, 0, 0, // 440 440
+ 0, 0, 0, 0, // 441 441
+ 0, 0, 0, 0, // 442 442
+ 0, 0, 0, 0, // 443 443
+ 0, 0, 0, 0, // 444 444
+ 0, 0, 0, 0, // 445 445
+ 0, 0, 0, 0, // 446 446
+ 0, 0, 0, 0, // 447 447
+ 0, 0, 0, 0, // 448 448
+ 0, 0, 0, 0, // 449 449
+ 0, 0, 0, 0, // 450 450
+ 0, 0, 0, 0, // 451 451
+ 0, 0, 0, 0, // 452 452
+ 0, 0, 0, 0, // 453 453
+ 0, 0, 0, 0, // 454 454
+ 0, 0, 0, 0, // 455 455
+ 0, 0, 0, 0, // 456 456
+ 0, 0, 0, 0, // 457 457
+ 0, 0, 0, 0, // 458 458
+ 0, 0, 0, 0, // 459 459
+ 0, 0, 0, 0, // 460 460
+ 0, 0, 0, 0, // 461 461
+ 0, 0, 0, 0, // 462 462
+ 0, 0, 0, 0, // 463 463
+ 0, 0, 0, 0, // 464 464
+ 0, 0, 0, 0, // 465 465
+ 0, 0, 0, 0, // 466 466
+ 0, 0, 0, 0, // 467 467
+ 0, 0, 0, 0, // 468 468
+ 0, 0, 0, 0, // 469 469
+ 0, 0, 0, 0, // 470 470
+ 0, 0, 0, 0, // 471 471
+ 0, 0, 0, 0, // 472 472
+ 0, 0, 0, 0, // 473 473
+ 0, 0, 0, 0, // 474 474
+ 0, 0, 0, 0, // 475 475
+ 0, 0, 0, 0, // 476 476
+ 0, 0, 0, 0, // 477 477
+ 0, 0, 0, 0, // 478 478
+ 0, 0, 0, 0, // 479 479
+ 0, 0, 0, 0, // 480 480
+ 0, 0, 0, 0, // 481 481
+ 0, 0, 0, 0, // 482 482
+ 0, 0, 0, 0, // 483 483
+ 0, 0, 0, 0, // 484 484
+ 0, 0, 0, 0, // 485 485
+ 0, 0, 0, 0, // 486 486
+ 0, 0, 0, 0, // 487 487
+ 0, 0, 0, 0, // 488 488
+ 0, 0, 0, 0, // 489 489
+ 0, 0, 0, 0, // 490 490
+ 0, 0, 0, 0, // 491 491
+ 0, 0, 0, 0, // 492 492
+ 0, 0, 0, 0, // 493 493
+ 0, 0, 0, 0, // 494 494
+ 0, 0, 0, 0, // 495 495
+ 0, 0, 0, 0, // 496 496
+ 0, 0, 0, 0, // 497 497
+ 0, 0, 0, 0, // 498 498
+ 0, 0, 0, 0, // 499 499
+ 0, 0, 0, 0, // 500 500
+ 0, 0, 0, 0, // 501 501
+ 0, 0, 0, 0, // 502 502
+ 0, 0, 0, 0, // 503 503
+ 0, 0, 0, 0, // 504 504
+ 0, 0, 0, 0, // 505 505
+ 0, 0, 0, 0, // 506 NDEBELE nr
+ 0, 0, 0, 0, // 507 X_BORK_BORK_BORK zzb
+ 0, 0, 0, 0, // 508 X_PIG_LATIN zzp
+ 0, 0, 0, 0, // 509 X_HACKER zzh
+ 0, 0, 0, 0, // 510 X_KLINGON tlh
+ 0, 0, 0, 0, // 511 X_ELMER_FUDD zze
+ 0, 0, 0, 0, // 512 X_Common xx-Zyyy
+ 0, 0, 0, 0, // 513 X_Latin xx-Latn
+ 0, 0, 0, 0, // 514 X_Greek xx-Grek
+ 0, 0, 0, 0, // 515 X_Cyrillic xx-Cyrl
+ 0, 0, 0, 0, // 516 X_Armenian xx-Armn
+ 0, 0, 0, 0, // 517 X_Hebrew xx-Hebr
+ 0, 0, 0, 0, // 518 X_Arabic xx-Arab
+ 0, 0, 0, 0, // 519 X_Syriac xx-Syrc
+ 0, 0, 0, 0, // 520 X_Thaana xx-Thaa
+ 0, 0, 0, 0, // 521 X_Devanagari xx-Deva
+ 0, 0, 0, 0, // 522 X_Bengali xx-Beng
+ 0, 0, 0, 0, // 523 X_Gurmukhi xx-Guru
+ 0, 0, 0, 0, // 524 X_Gujarati xx-Gujr
+ 0, 0, 0, 0, // 525 X_Oriya xx-Orya
+ 0, 0, 0, 0, // 526 X_Tamil xx-Taml
+ 0, 0, 0, 0, // 527 X_Telugu xx-Telu
+ 0, 0, 0, 0, // 528 X_Kannada xx-Knda
+ 0, 0, 0, 0, // 529 X_Malayalam xx-Mlym
+ 0, 0, 0, 0, // 530 X_Sinhala xx-Sinh
+ 0, 0, 0, 0, // 531 X_Thai xx-Thai
+ 0, 0, 0, 0, // 532 X_Lao xx-Laoo
+ 0, 0, 0, 0, // 533 X_Tibetan xx-Tibt
+ 0, 0, 0, 0, // 534 X_Myanmar xx-Mymr
+ 0, 0, 0, 0, // 535 X_Georgian xx-Geor
+ 0, 0, 0, 0, // 536 X_Hangul xx-Hang
+ 0, 0, 0, 0, // 537 X_Ethiopic xx-Ethi
+ 0, 0, 0, 0, // 538 X_Cherokee xx-Cher
+ 0, 0, 0, 0, // 539 X_Canadian_Aboriginal xx-Cans
+ 0, 0, 0, 0, // 540 X_Ogham xx-Ogam
+ 0, 0, 0, 0, // 541 X_Runic xx-Runr
+ 0, 0, 0, 0, // 542 X_Khmer xx-Khmr
+ 0, 0, 0, 0, // 543 X_Mongolian xx-Mong
+ 0, 0, 0, 0, // 544 X_Hiragana xx-Hira
+ 0, 0, 0, 0, // 545 X_Katakana xx-Kana
+ 0, 0, 0, 0, // 546 X_Bopomofo xx-Bopo
+ 0, 0, 0, 0, // 547 X_Han xx-Hani
+ 0, 0, 0, 0, // 548 X_Yi xx-Yiii
+ 0, 0, 0, 0, // 549 X_Old_Italic xx-Ital
+ 0, 0, 0, 0, // 550 X_Gothic xx-Goth
+ 0, 0, 0, 0, // 551 X_Deseret xx-Dsrt
+ 0, 0, 0, 0, // 552 X_Inherited xx-Qaai
+ 0, 0, 0, 0, // 553 X_Tagalog xx-Tglg
+ 0, 0, 0, 0, // 554 X_Hanunoo xx-Hano
+ 0, 0, 0, 0, // 555 X_Buhid xx-Buhd
+ 0, 0, 0, 0, // 556 X_Tagbanwa xx-Tagb
+ 0, 0, 0, 0, // 557 X_Limbu xx-Limb
+ 0, 0, 0, 0, // 558 X_Tai_Le xx-Tale
+ 0, 0, 0, 0, // 559 X_Linear_B xx-Linb
+ 0, 0, 0, 0, // 560 X_Ugaritic xx-Ugar
+ 0, 0, 0, 0, // 561 X_Shavian xx-Shaw
+ 0, 0, 0, 0, // 562 X_Osmanya xx-Osma
+ 0, 0, 0, 0, // 563 X_Cypriot xx-Cprt
+ 0, 0, 0, 0, // 564 X_Braille xx-Brai
+ 0, 0, 0, 0, // 565 X_Buginese xx-Bugi
+ 0, 0, 0, 0, // 566 X_Coptic xx-Copt
+ 0, 0, 0, 0, // 567 X_New_Tai_Lue xx-Talu
+ 0, 0, 0, 0, // 568 X_Glagolitic xx-Glag
+ 0, 0, 0, 0, // 569 X_Tifinagh xx-Tfng
+ 0, 0, 0, 0, // 570 X_Syloti_Nagri xx-Sylo
+ 0, 0, 0, 0, // 571 X_Old_Persian xx-Xpeo
+ 0, 0, 0, 0, // 572 X_Kharoshthi xx-Khar
+ 0, 0, 0, 0, // 573 X_Balinese xx-Bali
+ 0, 0, 0, 0, // 574 X_Cuneiform xx-Xsux
+ 0, 0, 0, 0, // 575 X_Phoenician xx-Phnx
+ 0, 0, 0, 0, // 576 X_Phags_Pa xx-Phag
+ 0, 0, 0, 0, // 577 X_Nko xx-Nkoo
+ 0, 0, 0, 0, // 578 X_Sundanese xx-Sund
+ 0, 0, 0, 0, // 579 X_Lepcha xx-Lepc
+ 0, 0, 0, 0, // 580 X_Ol_Chiki xx-Olck
+ 0, 0, 0, 0, // 581 X_Vai xx-Vaii
+ 0, 0, 0, 0, // 582 X_Saurashtra xx-Saur
+ 0, 0, 0, 0, // 583 X_Kayah_Li xx-Kali
+ 0, 0, 0, 0, // 584 X_Rejang xx-Rjng
+ 0, 0, 0, 0, // 585 X_Lycian xx-Lyci
+ 0, 0, 0, 0, // 586 X_Carian xx-Cari
+ 0, 0, 0, 0, // 587 X_Lydian xx-Lydi
+ 0, 0, 0, 0, // 588 X_Cham xx-Cham
+ 0, 0, 0, 0, // 589 X_Tai_Tham xx-Lana
+ 0, 0, 0, 0, // 590 X_Tai_Viet xx-Tavt
+ 0, 0, 0, 0, // 591 X_Avestan xx-Avst
+ 0, 0, 0, 0, // 592 X_Egyptian_Hieroglyphs xx-Egyp
+ 0, 0, 0, 0, // 593 X_Samaritan xx-Samr
+ 0, 0, 0, 0, // 594 X_Lisu xx-Lisu
+ 0, 0, 0, 0, // 595 X_Bamum xx-Bamu
+ 0, 0, 0, 0, // 596 X_Javanese xx-Java
+ 0, 0, 0, 0, // 597 X_Meetei_Mayek xx-Mtei
+ 0, 0, 0, 0, // 598 X_Imperial_Aramaic xx-Armi
+ 0, 0, 0, 0, // 599 X_Old_South_Arabian xx-Sarb
+ 0, 0, 0, 0, // 600 X_Inscriptional_Parthian xx-Prti
+ 0, 0, 0, 0, // 601 X_Inscriptional_Pahlavi xx-Phli
+ 0, 0, 0, 0, // 602 X_Old_Turkic xx-Orkh
+ 0, 0, 0, 0, // 603 X_Kaithi xx-Kthi
+ 0, 0, 0, 0, // 604 X_Batak xx-Batk
+ 0, 0, 0, 0, // 605 X_Brahmi xx-Brah
+ 0, 0, 0, 0, // 606 X_Mandaic xx-Mand
+ 0, 0, 0, 0, // 607 X_Chakma xx-Cakm
+ 0, 0, 0, 0, // 608 X_Meroitic_Cursive xx-Merc
+ 0, 0, 0, 0, // 609 X_Meroitic_Hieroglyphs xx-Mero
+ 0, 0, 0, 0, // 610 X_Miao xx-Plrd
+ 0, 0, 0, 0, // 611 X_Sharada xx-Shrd
+ 0, 0, 0, 0, // 612 X_Sora_Sompeng xx-Sora
+ 0, 0, 0, 0, // 613 X_Takri xx-Takr
+};
+
+} // End namespace CLD2
diff --git a/browser/components/translation/cld2/internal/cldutil.cc b/browser/components/translation/cld2/internal/cldutil.cc
new file mode 100644
index 000000000..ecda9a53e
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cldutil.cc
@@ -0,0 +1,620 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+// Updated 2014.01 for dual table lookup
+//
+
+#include "cldutil.h"
+#include <string>
+
+#include "cld2tablesummary.h"
+#include "integral_types.h"
+#include "port.h"
+#include "utf8statetable.h"
+
+namespace CLD2 {
+
+// Caller supplies the right tables in scoringcontext
+
+// Runtime routines for hashing, looking up, and scoring
+// unigrams (CJK), bigrams (CJK), quadgrams, and octagrams.
+// Unigrams and bigrams are for CJK languages only, including simplified/
+// traditional Chinese, Japanese, Korean, Vietnamese Han characters, and
+// Zhuang Han characters. Surrounding spaces are not considered.
+// Quadgrams and octagrams for for non-CJK and include two bits indicating
+// preceding and trailing spaces (word boundaries).
+
+
+static const int kMinCJKUTF8CharBytes = 3;
+
+static const int kMinGramCount = 3;
+static const int kMaxGramCount = 16;
+
+static const int UTFmax = 4; // Max number of bytes in a UTF-8 character
+
+ // 1 to skip ASCII space, vowels AEIOU aeiou and UTF-8 continuation bytes 80-BF
+ static const uint8 kSkipSpaceVowelContinue[256] = {
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 1,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,1,0,0,0,1,0,0, 0,1,0,0,0,0,0,1, 0,0,0,0,0,1,0,0, 0,0,0,0,0,0,0,0,
+ 0,1,0,0,0,1,0,0, 0,1,0,0,0,0,0,1, 0,0,0,0,0,1,0,0, 0,0,0,0,0,0,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,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ };
+
+ // 1 to skip ASCII space, and UTF-8 continuation bytes 80-BF
+ static const uint8 kSkipSpaceContinue[256] = {
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 1,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,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,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ };
+
+
+ // Always advances one UTF-8 character
+ static const uint8 kAdvanceOneChar[256] = {
+ 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,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,
+ 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
+ };
+
+ // Advances *only* on space (or illegal byte)
+ static const uint8 kAdvanceOneCharSpace[256] = {
+ 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,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,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,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ };
+
+
+// Routines to access a hash table of <key:wordhash, value:probs> pairs
+// Buckets have 4-byte wordhash for sizes < 32K buckets, but only
+// 2-byte wordhash for sizes >= 32K buckets, with other wordhash bits used as
+// bucket subscript.
+// Probs is a packed: three languages plus a subscript for probability table
+// Buckets have all the keys together, then all the values.Key array never
+// crosses a cache-line boundary, so no-match case takes exactly one cache miss.
+// Match case may sometimes take an additional cache miss on value access.
+//
+// Other possibilites include 5 or 10 6-byte entries plus pad to make 32 or 64
+// byte buckets with single cache miss.
+// Or 2-byte key and 6-byte value, allowing 5 languages instead of three.
+//------------------------------------------------------------------------------
+
+//----------------------------------------------------------------------------//
+// Hashing groups of 1/2/4/8 letters, perhaps with spaces or underscores //
+//----------------------------------------------------------------------------//
+
+//----------------------------------------------------------------------------//
+// Scoring single groups of letters //
+//----------------------------------------------------------------------------//
+
+// BIGRAM, QUADGRAM, OCTAGRAM score one => tote
+// Input: 4-byte entry of 3 language numbers and one probability subscript, plus
+// an accumulator tote. (language 0 means unused entry)
+// Output: running sums in tote updated
+void ProcessProbV2Tote(uint32 probs, Tote* tote) {
+ uint8 prob123 = (probs >> 0) & 0xff;
+ const uint8* prob123_entry = LgProb2TblEntry(prob123);
+
+ uint8 top1 = (probs >> 8) & 0xff;
+ if (top1 > 0) {tote->Add(top1, LgProb3(prob123_entry, 0));}
+ uint8 top2 = (probs >> 16) & 0xff;
+ if (top2 > 0) {tote->Add(top2, LgProb3(prob123_entry, 1));}
+ uint8 top3 = (probs >> 24) & 0xff;
+ if (top3 > 0) {tote->Add(top3, LgProb3(prob123_entry, 2));}
+}
+
+// Return score for a particular per-script language, or zero
+int GetLangScore(uint32 probs, uint8 pslang) {
+ uint8 prob123 = (probs >> 0) & 0xff;
+ const uint8* prob123_entry = LgProb2TblEntry(prob123);
+ int retval = 0;
+ uint8 top1 = (probs >> 8) & 0xff;
+ if (top1 == pslang) {retval += LgProb3(prob123_entry, 0);}
+ uint8 top2 = (probs >> 16) & 0xff;
+ if (top2 == pslang) {retval += LgProb3(prob123_entry, 1);}
+ uint8 top3 = (probs >> 24) & 0xff;
+ if (top3 == pslang) {retval += LgProb3(prob123_entry, 2);}
+ return retval;
+}
+
+//----------------------------------------------------------------------------//
+// Routines to accumulate probabilities //
+//----------------------------------------------------------------------------//
+
+
+// BIGRAM, using hash table, always advancing by 1 char
+// Caller supplies table, such as &kCjkBiTable_obj or &kGibberishTable_obj
+// Score all bigrams in isrc, using languages that have bigrams (CJK)
+// Return number of bigrams that hit in the hash table
+int DoBigramScoreV3(const CLD2TableSummary* bigram_obj,
+ const char* isrc, int srclen, Tote* chunk_tote) {
+ int hit_count = 0;
+ const char* src = isrc;
+
+ // Hashtable-based CJK bigram lookup
+ const uint8* usrc = reinterpret_cast<const uint8*>(src);
+ const uint8* usrclimit1 = usrc + srclen - UTFmax;
+
+ while (usrc < usrclimit1) {
+ int len = kAdvanceOneChar[usrc[0]];
+ int len2 = kAdvanceOneChar[usrc[len]] + len;
+
+ if ((kMinCJKUTF8CharBytes * 2) <= len2) { // Two CJK chars possible
+ // Lookup and score this bigram
+ // Always ignore pre/post spaces
+ uint32 bihash = BiHashV2(reinterpret_cast<const char*>(usrc), len2);
+ uint32 probs = QuadHashV3Lookup4(bigram_obj, bihash);
+ // Now go indirect on the subscript
+ probs = bigram_obj->kCLDTableInd[probs &
+ ~bigram_obj->kCLDTableKeyMask];
+
+ // Process the bigram
+ if (probs != 0) {
+ ProcessProbV2Tote(probs, chunk_tote);
+ ++hit_count;
+ }
+ }
+ usrc += len; // Advance by one char
+ }
+
+ return hit_count;
+}
+
+
+// Score up to 64KB of a single script span in one pass
+// Make a dummy entry off the end to calc length of last span
+// Return offset of first unused input byte
+int GetUniHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer) {
+ const char* isrc = &text[letter_offset];
+ const char* src = isrc;
+ // Limit is end, which has extra 20 20 20 00 past len
+ const char* srclimit = &text[letter_limit];
+
+ // Local copies
+ const UTF8PropObj* unigram_obj =
+ scoringcontext->scoringtables->unigram_obj;
+ int next_base = hitbuffer->next_base;
+ int next_base_limit = hitbuffer->maxscoringhits;
+
+ // Visit all unigrams
+ if (src[0] == ' ') {++src;} // skip any initial space
+ while (src < srclimit) {
+ const uint8* usrc = reinterpret_cast<const uint8*>(src);
+ int len = kAdvanceOneChar[usrc[0]];
+ src += len;
+ // Look up property of one UTF-8 character and advance over it.
+ // Updates usrc and len (bad interface design), hence increment above
+ int propval = UTF8GenericPropertyBigOneByte(unigram_obj, &usrc, &len);
+ if (propval > 0) {
+ // Save indirect subscript for later scoring; 1 or 2 langprobs
+ int indirect_subscr = propval;
+ hitbuffer->base[next_base].offset = src - text; // Offset in text
+ hitbuffer->base[next_base].indirect = indirect_subscr;
+ ++next_base;
+ }
+
+ if (next_base >= next_base_limit) {break;}
+ }
+
+ hitbuffer->next_base = next_base;
+
+ // Make a dummy entry off the end to calc length of last span
+ int dummy_offset = src - text;
+ hitbuffer->base[hitbuffer->next_base].offset = dummy_offset;
+ hitbuffer->base[hitbuffer->next_base].indirect = 0;
+
+ return src - text;
+}
+
+// Score up to 64KB of a single script span, doing both delta-bi and
+// distinct bis in one pass
+void GetBiHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer) {
+ const char* isrc = &text[letter_offset];
+ const char* src = isrc;
+ // Limit is end
+ const char* srclimit1 = &text[letter_limit];
+
+ // Local copies
+ const CLD2TableSummary* deltabi_obj =
+ scoringcontext->scoringtables->deltabi_obj;
+ const CLD2TableSummary* distinctbi_obj =
+ scoringcontext->scoringtables->distinctbi_obj;
+ int next_delta = hitbuffer->next_delta;
+ int next_delta_limit = hitbuffer->maxscoringhits;
+ int next_distinct = hitbuffer->next_distinct;
+ // We can do 2 inserts per loop, so -1
+ int next_distinct_limit = hitbuffer->maxscoringhits - 1;
+
+ while (src < srclimit1) {
+ const uint8* usrc = reinterpret_cast<const uint8*>(src);
+ int len = kAdvanceOneChar[usrc[0]];
+ int len2 = kAdvanceOneChar[usrc[len]] + len;
+
+ if ((kMinCJKUTF8CharBytes * 2) <= len2) { // Two CJK chars possible
+ // Lookup and this bigram and save <offset, indirect>
+ uint32 bihash = BiHashV2(src, len2);
+ uint32 probs = QuadHashV3Lookup4(deltabi_obj, bihash);
+ // Now go indirect on the subscript
+ if (probs != 0) {
+ // Save indirect subscript for later scoring; 1 langprob
+ int indirect_subscr = probs & ~deltabi_obj->kCLDTableKeyMask;
+ hitbuffer->delta[next_delta].offset = src - text;
+ hitbuffer->delta[next_delta].indirect = indirect_subscr;
+ ++next_delta;
+ }
+ // Lookup this distinct bigram and save <offset, indirect>
+ probs = QuadHashV3Lookup4(distinctbi_obj, bihash);
+ if (probs != 0) {
+ int indirect_subscr = probs & ~distinctbi_obj->kCLDTableKeyMask;
+ hitbuffer->distinct[next_distinct].offset = src - text;
+ hitbuffer->distinct[next_distinct].indirect = indirect_subscr;
+ ++next_distinct;
+ }
+ }
+ src += len; // Advance by one char (not two)
+
+ // Almost always srclimit hit first
+ if (next_delta >= next_delta_limit) {break;}
+ if (next_distinct >= next_distinct_limit) {break;}
+ }
+
+ hitbuffer->next_delta = next_delta;
+ hitbuffer->next_distinct = next_distinct;
+
+ // Make a dummy entry off the end to calc length of last span
+ int dummy_offset = src - text;
+ hitbuffer->delta[hitbuffer->next_delta].offset = dummy_offset;
+ hitbuffer->delta[hitbuffer->next_delta].indirect = 0;
+ hitbuffer->distinct[hitbuffer->next_distinct].offset = dummy_offset;
+ hitbuffer->distinct[hitbuffer->next_distinct].indirect = 0;
+}
+
+// Score up to 64KB of a single script span in one pass
+// Make a dummy entry off the end to calc length of last span
+// Return offset of first unused input byte
+int GetQuadHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer) {
+ const char* isrc = &text[letter_offset];
+ const char* src = isrc;
+ // Limit is end, which has extra 20 20 20 00 past len
+ const char* srclimit = &text[letter_limit];
+
+ // Local copies
+ const CLD2TableSummary* quadgram_obj =
+ scoringcontext->scoringtables->quadgram_obj;
+ const CLD2TableSummary* quadgram_obj2 =
+ scoringcontext->scoringtables->quadgram_obj2;
+ int next_base = hitbuffer->next_base;
+ int next_base_limit = hitbuffer->maxscoringhits;
+
+ // Run a little cache of last quad hits to catch overly-repetitive "text"
+ // We don't care if we miss a couple repetitions at scriptspan boundaries
+ int next_prior_quadhash = 0;
+ uint32 prior_quadhash[2] = {0, 0};
+
+ // Visit all quadgrams
+ if (src[0] == ' ') {++src;} // skip any initial space
+ while (src < srclimit) {
+ // Find one quadgram
+ const char* src_end = src;
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ const char* src_mid = src_end;
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ int len = src_end - src;
+ // Hash the quadgram
+ uint32 quadhash = QuadHashV2(src, len);
+
+ // Filter out recent repeats
+ if ((quadhash != prior_quadhash[0]) && (quadhash != prior_quadhash[1])) {
+ // Look up this quadgram and save <offset, indirect>
+ uint32 indirect_flag = 0; // For dual tables
+ const CLD2TableSummary* hit_obj = quadgram_obj;
+ uint32 probs = QuadHashV3Lookup4(quadgram_obj, quadhash);
+ if ((probs == 0) && (quadgram_obj2->kCLDTableSize != 0)) {
+ // Try lookup in dual table if not found in first one
+ // Note: we need to know later which of two indirect tables to use.
+ indirect_flag = 0x80000000u;
+ hit_obj = quadgram_obj2;
+ probs = QuadHashV3Lookup4(quadgram_obj2, quadhash);
+ }
+ if (probs != 0) {
+ // Round-robin two entries of actual hits
+ prior_quadhash[next_prior_quadhash] = quadhash;
+ next_prior_quadhash = (next_prior_quadhash + 1) & 1;
+
+ // Save indirect subscript for later scoring; 1 or 2 langprobs
+ int indirect_subscr = probs & ~hit_obj->kCLDTableKeyMask;
+ hitbuffer->base[next_base].offset = src - text; // Offset in text
+ // Flip the high bit for table2
+ hitbuffer->base[next_base].indirect = indirect_subscr | indirect_flag;
+ ++next_base;
+ }
+ }
+
+ // Advance: all the way past word if at end-of-word, else 2 chars
+ if (src_end[0] == ' ') {
+ src = src_end;
+ } else {
+ src = src_mid;
+ }
+
+ // Skip over space at end of word, or ASCII vowel in middle of word
+ // Use kAdvanceOneCharSpace instead to get rid of vowel hack
+ if (src < srclimit) {
+ src += kAdvanceOneCharSpaceVowel[(uint8)src[0]];
+ } else {
+ // Advancing by 4/8/16 can overshoot, but we are about to exit anyway
+ src = srclimit;
+ }
+
+ if (next_base >= next_base_limit) {break;}
+ }
+
+ hitbuffer->next_base = next_base;
+
+ // Make a dummy entry off the end to calc length of last span
+ int dummy_offset = src - text;
+ hitbuffer->base[hitbuffer->next_base].offset = dummy_offset;
+ hitbuffer->base[hitbuffer->next_base].indirect = 0;
+
+ return src - text;
+}
+
+// inputs:
+// const tables
+// const char* isrc, int srclen (in sscriptbuffer)
+// intermediates:
+// vector of octa <offset, probs> (which need indirect table to decode)
+// vector of distinct <offset, probs> (which need indirect table to decode)
+
+// Score up to 64KB of a single script span, doing both delta-octa and
+// distinct words in one pass
+void GetOctaHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer) {
+ const char* isrc = &text[letter_offset];
+ const char* src = isrc;
+ // Limit is end+1, to include extra space char (0x20) off the end
+ const char* srclimit = &text[letter_limit + 1];
+
+ // Local copies
+ const CLD2TableSummary* deltaocta_obj =
+ scoringcontext->scoringtables->deltaocta_obj;
+ int next_delta = hitbuffer->next_delta;
+ int next_delta_limit = hitbuffer->maxscoringhits;
+
+ const CLD2TableSummary* distinctocta_obj =
+ scoringcontext->scoringtables->distinctocta_obj;
+ int next_distinct = hitbuffer->next_distinct;
+ // We can do 2 inserts per loop, so -1
+ int next_distinct_limit = hitbuffer->maxscoringhits - 1;
+
+ // Run a little cache of last octa hits to catch overly-repetitive "text"
+ // We don't care if we miss a couple repetitions at scriptspan boundaries
+ int next_prior_octahash = 0;
+ uint64 prior_octahash[2] = {0, 0};
+
+ // Score all words truncated to 8 characters
+ int charcount = 0;
+ // Skip any initial space
+ if (src[0] == ' ') {++src;}
+
+ // Begin the first word
+ const char* prior_word_start = src;
+ const char* word_start = src;
+ const char* word_end = word_start;
+ while (src < srclimit) {
+ // Terminate previous word or continue current word
+ if (src[0] == ' ') {
+ int len = word_end - word_start;
+ // Hash the word
+ uint64 wordhash40 = OctaHash40(word_start, len);
+ uint32 probs;
+
+ // Filter out recent repeats. Unlike quads, we update even if no hit,
+ // so we can get hits on same word if separated by non-hit words
+ if ((wordhash40 != prior_octahash[0]) &&
+ (wordhash40 != prior_octahash[1])) {
+ // Round-robin two entries of words
+ prior_octahash[next_prior_octahash] = wordhash40;
+ next_prior_octahash = 1 - next_prior_octahash; // Alternates 0,1,0,1
+
+ // (1) Lookup distinct word PAIR. For a pair, we want an asymmetrical
+ // function of the two word hashs. For words A B C, B-A and C-B are good
+ // enough and fast. We use the same table as distinct single words
+ // Do not look up a pair of identical words -- all pairs hash to zero
+ // Both 1- and 2-word distinct lookups are in distinctocta_obj now
+ // Do this first, because it has the lowest offset
+ uint64 tmp_prior_hash = prior_octahash[next_prior_octahash];
+ if ((tmp_prior_hash != 0) && (tmp_prior_hash != wordhash40)) {
+ uint64 pair_hash = PairHash(tmp_prior_hash, wordhash40);
+ probs = OctaHashV3Lookup4(distinctocta_obj, pair_hash);
+ if (probs != 0) {
+ int indirect_subscr = probs & ~distinctocta_obj->kCLDTableKeyMask;
+ hitbuffer->distinct[next_distinct].offset = prior_word_start - text;
+ hitbuffer->distinct[next_distinct].indirect = indirect_subscr;
+ ++next_distinct;
+ }
+ }
+
+ // (2) Lookup this distinct word and save <offset, indirect>
+ probs = OctaHashV3Lookup4(distinctocta_obj, wordhash40);
+ if (probs != 0) {
+ int indirect_subscr = probs & ~distinctocta_obj->kCLDTableKeyMask;
+ hitbuffer->distinct[next_distinct].offset = word_start - text;
+ hitbuffer->distinct[next_distinct].indirect = indirect_subscr;
+ ++next_distinct;
+ }
+
+ // (3) Lookup this word and save <offset, indirect>
+ probs = OctaHashV3Lookup4(deltaocta_obj, wordhash40);
+ if (probs != 0) {
+ // Save indirect subscript for later scoring; 1 langprob
+ int indirect_subscr = probs & ~deltaocta_obj->kCLDTableKeyMask;
+ hitbuffer->delta[next_delta].offset = word_start - text;
+ hitbuffer->delta[next_delta].indirect = indirect_subscr;
+ ++next_delta;
+ }
+ }
+
+ // Begin the next word
+ charcount = 0;
+ prior_word_start = word_start;
+ word_start = src + 1; // Over the space
+ word_end = word_start;
+ } else {
+ ++charcount;
+ }
+
+ // Advance to next char
+ src += UTF8OneCharLen(src);
+ if (charcount <= 8) {
+ word_end = src;
+ }
+ // Almost always srclimit hit first
+ if (next_delta >= next_delta_limit) {break;}
+ if (next_distinct >= next_distinct_limit) {break;}
+ }
+
+ hitbuffer->next_delta = next_delta;
+ hitbuffer->next_distinct = next_distinct;
+
+ // Make a dummy entry off the end to calc length of last span
+ int dummy_offset = src - text;
+ hitbuffer->delta[hitbuffer->next_delta].offset = dummy_offset;
+ hitbuffer->delta[hitbuffer->next_delta].indirect = 0;
+ hitbuffer->distinct[hitbuffer->next_distinct].offset = dummy_offset;
+ hitbuffer->distinct[hitbuffer->next_distinct].indirect = 0;
+}
+
+
+//----------------------------------------------------------------------------//
+// Reliability calculations, for single language and between languages //
+//----------------------------------------------------------------------------//
+
+// Return reliablity of result 0..100 for top two scores
+// delta==0 is 0% reliable, delta==fully_reliable_thresh is 100% reliable
+// (on a scale where +1 is a factor of 2 ** 1.6 = 3.02)
+// Threshold is uni/quadgram increment count, bounded above and below.
+//
+// Requiring a factor of 3 improvement (e.g. +1 log base 3)
+// for each scored quadgram is too stringent, so I've backed this off to a
+// factor of 2 (e.g. +5/8 log base 3).
+//
+// I also somewhat lowered the Min/MaxGramCount limits above
+//
+// Added: if fewer than 8 quads/unis, max reliability is 12*n percent
+//
+int ReliabilityDelta(int value1, int value2, int gramcount) {
+ int max_reliability_percent = 100;
+ if (gramcount < 8) {
+ max_reliability_percent = 12 * gramcount;
+ }
+ int fully_reliable_thresh = (gramcount * 5) >> 3; // see note above
+ if (fully_reliable_thresh < kMinGramCount) { // Fully = 3..16
+ fully_reliable_thresh = kMinGramCount;
+ } else if (fully_reliable_thresh > kMaxGramCount) {
+ fully_reliable_thresh = kMaxGramCount;
+ }
+
+ int delta = value1 - value2;
+ if (delta >= fully_reliable_thresh) {return max_reliability_percent;}
+ if (delta <= 0) {return 0;}
+ return minint(max_reliability_percent,
+ (100 * delta) / fully_reliable_thresh);
+}
+
+// Return reliablity of result 0..100 for top score vs. expected mainsteam score
+// Values are score per 1024 bytes of input
+// ratio = max(top/mainstream, mainstream/top)
+// ratio > 4.0 is 0% reliable, <= 2.0 is 100% reliable
+// Change: short-text word scoring can give unusually good results.
+// Let top exceed mainstream by 4x at 50% reliable
+//
+// dsites April 2010: These could be tightened up. It would be
+// reasonable with newer data and round-robin table allocation to start ramping
+// down at mean * 1.5 and mean/1.5, while letting mean*2 and mean/2 pass,
+// but just barely.
+//
+// dsites March 2013: Tightened up a bit.
+static const double kRatio100 = 1.5;
+static const double kRatio0 = 4.0;
+int ReliabilityExpected(int actual_score_1kb, int expected_score_1kb) {
+ if (expected_score_1kb == 0) {return 100;} // No reliability data available yet
+ if (actual_score_1kb == 0) {return 0;} // zero score = unreliable
+ double ratio;
+ if (expected_score_1kb > actual_score_1kb) {
+ ratio = (1.0 * expected_score_1kb) / actual_score_1kb;
+ } else {
+ ratio = (1.0 * actual_score_1kb) / expected_score_1kb;
+ }
+ // Ratio 1.0 .. 1.5 scores 100%
+ // Ratio 2.0 scores 80%
+ // Linear decline, to ratio 4.0 scores 0%
+ if (ratio <= kRatio100) {return 100;}
+ if (ratio > kRatio0) {return 0;}
+
+ int percent_good = 100.0 * (kRatio0 - ratio) / (kRatio0 - kRatio100);
+ return percent_good;
+}
+
+// Create a langprob packed value from its parts.
+// qprob is quantized [0..12]
+// We use Latn script to represent any RTypeMany language
+uint32 MakeLangProb(Language lang, int qprob) {
+ uint32 pslang = PerScriptNumber(ULScript_Latin, lang);
+ uint32 retval = (pslang << 8) | kLgProbV2TblBackmap[qprob];
+ return retval;
+}
+
+} // End namespace CLD2
+
+
+
+
+
diff --git a/browser/components/translation/cld2/internal/cldutil.h b/browser/components/translation/cld2/internal/cldutil.h
new file mode 100644
index 000000000..9712b30a9
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cldutil.h
@@ -0,0 +1,80 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+// Stuff used only by online detector, not used offline
+//
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_NEW_CLDUTIL_H__
+#define I18N_ENCODINGS_CLD2_INTERNAL_NEW_CLDUTIL_H__
+
+#include "cldutil_shared.h"
+#include "scoreonescriptspan.h"
+#include "tote.h"
+
+namespace CLD2 {
+
+// Score up to 64KB of a single script span in one pass
+// Make a dummy entry off the end to calc length of last span
+// Return offset of first unused input byte
+int GetUniHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer);
+
+// Score up to 64KB of a single script span, doing both delta-bi and
+// distinct bis in one pass
+void GetBiHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer);
+
+// Score up to 64KB of a single script span in one pass
+// Make a dummy entry off the end to calc length of last span
+// Return offset of first unused input byte
+int GetQuadHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer);
+
+// Score up to 64KB of a single script span, doing both delta-octa and
+// distinct words in one pass
+void GetOctaHits(const char* text,
+ int letter_offset, int letter_limit,
+ ScoringContext* scoringcontext,
+ ScoringHitBuffer* hitbuffer);
+
+// Not sure if these belong here or in scoreonescriptspan.cc
+int ReliabilityDelta(int value1, int value2, int gramcount);
+int ReliabilityExpected(int actual_score_1kb, int expected_score_1kb);
+
+// Create a langprob packed value from its parts.
+uint32 MakeLangProb(Language lang, int qprob);
+
+
+void ProcessProbV2Tote(uint32 probs, Tote* tote);
+
+// Return score for a particular per-script language, or zero
+int GetLangScore(uint32 probs, uint8 pslang);
+
+static inline int minint(int a, int b) {return (a < b) ? a: b;}
+static inline int maxint(int a, int b) {return (a > b) ? a: b;}
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_NEW_CLDUTIL_H__
+
+
diff --git a/browser/components/translation/cld2/internal/cldutil_shared.cc b/browser/components/translation/cld2/internal/cldutil_shared.cc
new file mode 100644
index 000000000..f111473af
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cldutil_shared.cc
@@ -0,0 +1,437 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#include "cldutil_shared.h"
+#include <string>
+
+#include "cld2tablesummary.h"
+#include "integral_types.h"
+#include "port.h"
+#include "utf8statetable.h"
+
+namespace CLD2 {
+
+// Runtime routines for hashing, looking up, and scoring
+// unigrams (CJK), bigrams (CJK), quadgrams, and octagrams.
+// Unigrams and bigrams are for CJK languages only, including simplified/
+// traditional Chinese, Japanese, Korean, Vietnamese Han characters, and
+// Zhuang Han characters. Surrounding spaces are not considered.
+// Quadgrams and octagrams for for non-CJK and include two bits indicating
+// preceding and trailing spaces (word boundaries).
+
+
+// Indicator bits for leading/trailing space around quad/octagram
+// NOTE: 4444 bits are chosen to flip constant bits in hash of four chars of
+// 1-, 2-, or 3-bytes each.
+static const uint32 kPreSpaceIndicator = 0x00004444;
+static const uint32 kPostSpaceIndicator = 0x44440000;
+
+// Little-endian masks for 0..24 bytes picked up as uint32's
+static const uint32 kWordMask0[4] = {
+ 0xFFFFFFFF, 0x000000FF, 0x0000FFFF, 0x00FFFFFF
+};
+
+static const int kMinCJKUTF8CharBytes = 3;
+
+static const int kMinGramCount = 3;
+static const int kMaxGramCount = 16;
+
+static const int UTFmax = 4; // Max number of bytes in a UTF-8 character
+
+
+// Routines to access a hash table of <key:wordhash, value:probs> pairs
+// Buckets have 4-byte wordhash for sizes < 32K buckets, but only
+// 2-byte wordhash for sizes >= 32K buckets, with other wordhash bits used as
+// bucket subscript.
+// Probs is a packed: three languages plus a subscript for probability table
+// Buckets have all the keys together, then all the values.Key array never
+// crosses a cache-line boundary, so no-match case takes exactly one cache miss.
+// Match case may sometimes take an additional cache miss on value access.
+//
+// Other possibilites include 5 or 10 6-byte entries plus pad to make 32 or 64
+// byte buckets with single cache miss.
+// Or 2-byte key and 6-byte value, allowing 5 languages instead of three.
+
+
+//----------------------------------------------------------------------------//
+// Hashing groups of 1/2/4/8 letters, perhaps with spaces or underscores //
+//----------------------------------------------------------------------------//
+
+// Design principles for these hash functions
+// - Few operations
+// - Handle 1-, 2-, and 3-byte UTF-8 scripts, ignoring intermixing except in
+// Latin script expect 1- and 2-byte mixtures.
+// - Last byte of each character has about 5 bits of information
+// - Spread good bits around so they can interact in at least two ways
+// with other characters
+// - Use add for additional mixing thorugh carries
+
+// CJK Three-byte bigram
+// ....dddd..cccccc..bbbbbb....aaaa
+// ..................ffffff..eeeeee
+// make
+// ....dddd..cccccc..bbbbbb....aaaa
+// 000....dddd..cccccc..bbbbbb....a
+// ..................ffffff..eeeeee
+// ffffff..eeeeee000000000000000000
+//
+// CJK Four-byte bigram
+// ..dddddd..cccccc....bbbb....aaaa
+// ..hhhhhh..gggggg....ffff....eeee
+// make
+// ..dddddd..cccccc....bbbb....aaaa
+// 000..dddddd..cccccc....bbbb....a
+// ..hhhhhh..gggggg....ffff....eeee
+// ..ffff....eeee000000000000000000
+
+// BIGRAM
+// Pick up 1..8 bytes and hash them via mask/shift/add. NO pre/post
+// OVERSHOOTS up to 3 bytes
+// For runtime use of tables
+// Does X86 unaligned loads
+uint32 BiHashV2(const char* word_ptr, int bytecount) {
+ if (bytecount == 0) {return 0;}
+ const uint32* word_ptr32 = reinterpret_cast<const uint32*>(word_ptr);
+ uint32 word0, word1;
+ if (bytecount <= 4) {
+ word0 = UNALIGNED_LOAD32(word_ptr32) & kWordMask0[bytecount & 3];
+ word0 = word0 ^ (word0 >> 3);
+ return word0;
+ }
+ // Else do 8 bytes
+ word0 = UNALIGNED_LOAD32(word_ptr32);
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1) & kWordMask0[bytecount & 3];
+ word1 = word1 ^ (word1 << 18);
+ return word0 + word1;
+}
+
+//
+// Ascii-7 One-byte chars
+// ...ddddd...ccccc...bbbbb...aaaaa
+// make
+// ...ddddd...ccccc...bbbbb...aaaaa
+// 000...ddddd...ccccc...bbbbb...aa
+//
+// Latin 1- and 2-byte chars
+// ...ddddd...ccccc...bbbbb...aaaaa
+// ...................fffff...eeeee
+// make
+// ...ddddd...ccccc...bbbbb...aaaaa
+// 000...ddddd...ccccc...bbbbb...aa
+// ...................fffff...eeeee
+// ...............fffff...eeeee0000
+//
+// Non-CJK Two-byte chars
+// ...ddddd...........bbbbb........
+// ...hhhhh...........fffff........
+// make
+// ...ddddd...........bbbbb........
+// 000...ddddd...........bbbbb.....
+// ...hhhhh...........fffff........
+// hhhh...........fffff........0000
+//
+// Non-CJK Three-byte chars
+// ...........ccccc................
+// ...................fffff........
+// ...lllll...................iiiii
+// make
+// ...........ccccc................
+// 000...........ccccc.............
+// ...................fffff........
+// ...............fffff........0000
+// ...lllll...................iiiii
+// .lllll...................iiiii00
+//
+
+// QUADGRAM
+// Pick up 1..12 bytes plus pre/post space and hash them via mask/shift/add
+// OVERSHOOTS up to 3 bytes
+// For runtime use of tables
+// Does X86 unaligned loads
+uint32 QuadHashV2Mix(const char* word_ptr, int bytecount, uint32 prepost) {
+ const uint32* word_ptr32 = reinterpret_cast<const uint32*>(word_ptr);
+ uint32 word0, word1, word2;
+ if (bytecount <= 4) {
+ word0 = UNALIGNED_LOAD32(word_ptr32) & kWordMask0[bytecount & 3];
+ word0 = word0 ^ (word0 >> 3);
+ return word0 ^ prepost;
+ } else if (bytecount <= 8) {
+ word0 = UNALIGNED_LOAD32(word_ptr32);
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1) & kWordMask0[bytecount & 3];
+ word1 = word1 ^ (word1 << 4);
+ return (word0 ^ prepost) + word1;
+ }
+ // else do 12 bytes
+ word0 = UNALIGNED_LOAD32(word_ptr32);
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1);
+ word1 = word1 ^ (word1 << 4);
+ word2 = UNALIGNED_LOAD32(word_ptr32 + 2) & kWordMask0[bytecount & 3];
+ word2 = word2 ^ (word2 << 2);
+ return (word0 ^ prepost) + word1 + word2;
+}
+
+
+// QUADGRAM wrapper with surrounding spaces
+// Pick up 1..12 bytes plus pre/post space and hash them via mask/shift/add
+// UNDERSHOOTS 1 byte, OVERSHOOTS up to 3 bytes
+// For runtime use of tables
+uint32 QuadHashV2(const char* word_ptr, int bytecount) {
+ if (bytecount == 0) {return 0;}
+ uint32 prepost = 0;
+ if (word_ptr[-1] == ' ') {prepost |= kPreSpaceIndicator;}
+ if (word_ptr[bytecount] == ' ') {prepost |= kPostSpaceIndicator;}
+ return QuadHashV2Mix(word_ptr, bytecount, prepost);
+}
+
+// QUADGRAM wrapper with surrounding underscores (offline use)
+// Pick up 1..12 bytes plus pre/post '_' and hash them via mask/shift/add
+// OVERSHOOTS up to 3 bytes
+// For offline construction of tables
+uint32 QuadHashV2Underscore(const char* word_ptr, int bytecount) {
+ if (bytecount == 0) {return 0;}
+ const char* local_word_ptr = word_ptr;
+ int local_bytecount = bytecount;
+ uint32 prepost = 0;
+ if (local_word_ptr[0] == '_') {
+ prepost |= kPreSpaceIndicator;
+ ++local_word_ptr;
+ --local_bytecount;
+ }
+ if (local_word_ptr[local_bytecount - 1] == '_') {
+ prepost |= kPostSpaceIndicator;
+ --local_bytecount;
+ }
+ return QuadHashV2Mix(local_word_ptr, local_bytecount, prepost);
+}
+
+
+// OCTAGRAM
+// Pick up 1..24 bytes plus pre/post space and hash them via mask/shift/add
+// UNDERSHOOTS 1 byte, OVERSHOOTS up to 3 bytes
+//
+// The low 32 bits follow the pattern from above, tuned to different scripts
+// The high 8 bits are a simple sum of all bytes, shifted by 0/1/2/3 bits each
+// For runtime use of tables V3
+// Does X86 unaligned loads
+uint64 OctaHash40Mix(const char* word_ptr, int bytecount, uint64 prepost) {
+ const uint32* word_ptr32 = reinterpret_cast<const uint32*>(word_ptr);
+ uint64 word0;
+ uint64 word1;
+ uint64 sum;
+
+ if (word_ptr[-1] == ' ') {prepost |= kPreSpaceIndicator;}
+ if (word_ptr[bytecount] == ' ') {prepost |= kPostSpaceIndicator;}
+ switch ((bytecount - 1) >> 2) {
+ case 0: // 1..4 bytes
+ word0 = UNALIGNED_LOAD32(word_ptr32) & kWordMask0[bytecount & 3];
+ sum = word0;
+ word0 = word0 ^ (word0 >> 3);
+ break;
+ case 1: // 5..8 bytes
+ word0 = UNALIGNED_LOAD32(word_ptr32);
+ sum = word0;
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1) & kWordMask0[bytecount & 3];
+ sum += word1;
+ word1 = word1 ^ (word1 << 4);
+ word0 += word1;
+ break;
+ case 2: // 9..12 bytes
+ word0 = UNALIGNED_LOAD32(word_ptr32);
+ sum = word0;
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1);
+ sum += word1;
+ word1 = word1 ^ (word1 << 4);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 2) & kWordMask0[bytecount & 3];
+ sum += word1;
+ word1 = word1 ^ (word1 << 2);
+ word0 += word1;
+ break;
+ case 3: // 13..16 bytes
+ word0 =UNALIGNED_LOAD32(word_ptr32);
+ sum = word0;
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1);
+ sum += word1;
+ word1 = word1 ^ (word1 << 4);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 2);
+ sum += word1;
+ word1 = word1 ^ (word1 << 2);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 3) & kWordMask0[bytecount & 3];
+ sum += word1;
+ word1 = word1 ^ (word1 >> 8);
+ word0 += word1;
+ break;
+ case 4: // 17..20 bytes
+ word0 = UNALIGNED_LOAD32(word_ptr32);
+ sum = word0;
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1);
+ sum += word1;
+ word1 = word1 ^ (word1 << 4);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 2);
+ sum += word1;
+ word1 = word1 ^ (word1 << 2);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 3);
+ sum += word1;
+ word1 = word1 ^ (word1 >> 8);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 4) & kWordMask0[bytecount & 3];
+ sum += word1;
+ word1 = word1 ^ (word1 >> 4);
+ word0 += word1;
+ break;
+ default: // 21..24 bytes and higher (ignores beyond 24)
+ word0 = UNALIGNED_LOAD32(word_ptr32);
+ sum = word0;
+ word0 = word0 ^ (word0 >> 3);
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 1);
+ sum += word1;
+ word1 = word1 ^ (word1 << 4);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 2);
+ sum += word1;
+ word1 = word1 ^ (word1 << 2);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 3);
+ sum += word1;
+ word1 = word1 ^ (word1 >> 8);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 4);
+ sum += word1;
+ word1 = word1 ^ (word1 >> 4);
+ word0 += word1;
+ word1 = UNALIGNED_LOAD32(word_ptr32 + 5) & kWordMask0[bytecount & 3];
+ sum += word1;
+ word1 = word1 ^ (word1 >> 6);
+ word0 += word1;
+ break;
+ }
+
+ sum += (sum >> 17); // extra 1-bit shift for bytes 2 & 3
+ sum += (sum >> 9); // extra 1-bit shift for bytes 1 & 3
+ sum = (sum & 0xff) << 32;
+ return (word0 ^ prepost) + sum;
+}
+
+// OCTAGRAM wrapper with surrounding spaces
+// Pick up 1..24 bytes plus pre/post space and hash them via mask/shift/add
+// UNDERSHOOTS 1 byte, OVERSHOOTS up to 3 bytes
+//
+// The low 32 bits follow the pattern from above, tuned to different scripts
+// The high 8 bits are a simple sum of all bytes, shifted by 0/1/2/3 bits each
+// For runtime use of tables V3
+uint64 OctaHash40(const char* word_ptr, int bytecount) {
+ if (bytecount == 0) {return 0;}
+ uint64 prepost = 0;
+ if (word_ptr[-1] == ' ') {prepost |= kPreSpaceIndicator;}
+ if (word_ptr[bytecount] == ' ') {prepost |= kPostSpaceIndicator;}
+ return OctaHash40Mix(word_ptr, bytecount, prepost);
+}
+
+
+// OCTAGRAM wrapper with surrounding underscores (offline use)
+// Pick up 1..24 bytes plus pre/post space and hash them via mask/shift/add
+// UNDERSHOOTS 1 byte, OVERSHOOTS up to 3 bytes
+//
+// The low 32 bits follow the pattern from above, tuned to different scripts
+// The high 8 bits are a simple sum of all bytes, shifted by 0/1/2/3 bits each
+// For offline construction of tables
+uint64 OctaHash40underscore(const char* word_ptr, int bytecount) {
+ if (bytecount == 0) {return 0;}
+ const char* local_word_ptr = word_ptr;
+ int local_bytecount = bytecount;
+ uint64 prepost = 0;
+ if (local_word_ptr[0] == '_') {
+ prepost |= kPreSpaceIndicator;
+ ++local_word_ptr;
+ --local_bytecount;
+ }
+ if (local_word_ptr[local_bytecount - 1] == '_') {
+ prepost |= kPostSpaceIndicator;
+ --local_bytecount;
+ }
+ return OctaHash40Mix(local_word_ptr, local_bytecount, prepost);
+}
+
+// Hash a consecutive pair of tokens/words A B
+// Old: hash is B - A, which gives too many false hits on one-char diffs
+// Now: rotate(A,13) + B
+uint64 PairHash(uint64 worda_hash, uint64 wordb_hash) {
+ return ((worda_hash >> 13) | (worda_hash << (64 - 13))) + wordb_hash;
+}
+
+
+
+
+//----------------------------------------------------------------------------//
+// Finding groups of 1/2/4/8 letters //
+//----------------------------------------------------------------------------//
+
+// src points to a letter. Find the byte length of a unigram starting there.
+int UniLen(const char* src) {
+ const char* src_end = src;
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ return src_end - src;
+}
+
+// src points to a letter. Find the byte length of a bigram starting there.
+int BiLen(const char* src) {
+ const char* src_end = src;
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ return src_end - src;
+}
+
+// src points to a letter. Find the byte length of a quadgram starting there.
+int QuadLen(const char* src) {
+ const char* src_end = src;
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ src_end += kAdvanceOneCharButSpace[(uint8)src_end[0]];
+ return src_end - src;
+}
+
+// src points to a letter. Find the byte length of an octagram starting there.
+int OctaLen(const char* src) {
+ const char* src_end = src;
+ int charcount = 0;
+ while (src_end[0] != ' ') {
+ src_end += UTF8OneCharLen(src);
+ ++charcount;
+ if (charcount == 8) {break;}
+ }
+ return src_end - src;
+}
+
+} // End namespace CLD2
+
+
+
+
+
diff --git a/browser/components/translation/cld2/internal/cldutil_shared.h b/browser/components/translation/cld2/internal/cldutil_shared.h
new file mode 100644
index 000000000..5e3b8dfa8
--- /dev/null
+++ b/browser/components/translation/cld2/internal/cldutil_shared.h
@@ -0,0 +1,509 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+// Just the stuff shared between offline table builder and online detector
+//
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_NEW_CLDUTIL_SHARED_H__
+#define I18N_ENCODINGS_CLD2_INTERNAL_NEW_CLDUTIL_SHARED_H__
+
+#include "integral_types.h"
+#include "cld2tablesummary.h"
+
+namespace CLD2 {
+
+// Runtime routines for hashing, looking up, and scoring
+// unigrams (CJK), bigrams (CJK), quadgrams, and octagrams.
+// Unigrams and bigrams are for CJK languages only, including simplified/
+// traditional Chinese, Japanese, Korean, Vietnamese Han characters, and
+// Zhuang Han characters. Surrounding spaces are not considered.
+// Quadgrams and octagrams for for non-CJK and include two bits indicating
+// preceding and trailing spaces (word boundaries).
+
+
+//----------------------------------------------------------------------------//
+// Main quantized probability table //
+//----------------------------------------------------------------------------//
+
+ // Table has 240 eight-byte entries. Each entry has a five-byte array and
+ // a three-byte array of log base 2 probabilities in the range 1..12.
+ // The intended use is to express five or three probabilities in a single-byte
+ // subscript, then decode via this table. These probabilities are
+ // intended to go with an array of five or three language numbers.
+ //
+ // The corresponding language numbers will have to be sorted by descending
+ // probability, then the actual probability subscript chosen to match the
+ // closest available entry in this table.
+ //
+ // Pattern of probability values:
+ // hi 3/4 1/2 1/4 lo hi mid lo
+ // where "3/4" is (hi*3+lo)/4, "1/2" is (hi+lo)/2, and "1/4" is (hi+lo*3)/4
+ // and mid is one of 3/4 1/2 or 1/4.
+ // There are three groups of 78 (=12*13/2) entries, with hi running 1..12 and
+ // lo running 1..hi. Only the first group is used for five-entry lookups.
+ // The mid value in the first group is 1/2, the second group 3/4, and the
+ // third group 1/4. For three-entry lookups, this allows the mid entry to be
+ // somewhat higher or lower than the midpoint, to allow a better match to the
+ // original probabilities.
+ static const int kLgProbV2TblSize = 240;
+ static const uint8 kLgProbV2Tbl[kLgProbV2TblSize * 8] = {
+ 1,1,1,1,1, 1,1,1, // [0]
+ 2,2,2,1,1, 2,2,1, // [1]
+ 2,2,2,2,2, 2,2,2,
+ 3,3,2,2,1, 3,2,1, // [3]
+ 3,3,3,2,2, 3,3,2,
+ 3,3,3,3,3, 3,3,3,
+ 4,3,3,2,1, 4,3,1, // [6]
+ 4,4,3,3,2, 4,3,2,
+ 4,4,4,3,3, 4,4,3,
+ 4,4,4,4,4, 4,4,4,
+ 5,4,3,2,1, 5,3,1, // [10]
+ 5,4,4,3,2, 5,4,2,
+ 5,5,4,4,3, 5,4,3,
+ 5,5,5,4,4, 5,5,4,
+ 5,5,5,5,5, 5,5,5,
+ 6,5,4,2,1, 6,4,1, // [15]
+ 6,5,4,3,2, 6,4,2,
+ 6,5,5,4,3, 6,5,3,
+ 6,6,5,5,4, 6,5,4,
+ 6,6,6,5,5, 6,6,5,
+ 6,6,6,6,6, 6,6,6,
+ 7,6,4,3,1, 7,4,1, // [21]
+ 7,6,5,3,2, 7,5,2,
+ 7,6,5,4,3, 7,5,3,
+ 7,6,6,5,4, 7,6,4,
+ 7,7,6,6,5, 7,6,5,
+ 7,7,7,6,6, 7,7,6,
+ 7,7,7,7,7, 7,7,7,
+ 8,6,5,3,1, 8,5,1, // [28]
+ 8,7,5,4,2, 8,5,2,
+ 8,7,6,4,3, 8,6,3,
+ 8,7,6,5,4, 8,6,4,
+ 8,7,7,6,5, 8,7,5,
+ 8,8,7,7,6, 8,7,6,
+ 8,8,8,7,7, 8,8,7,
+ 8,8,8,8,8, 8,8,8,
+ 9,7,5,3,1, 9,5,1, // [36]
+ 9,7,6,4,2, 9,6,2,
+ 9,8,6,5,3, 9,6,3,
+ 9,8,7,5,4, 9,7,4,
+ 9,8,7,6,5, 9,7,5,
+ 9,8,8,7,6, 9,8,6,
+ 9,9,8,8,7, 9,8,7,
+ 9,9,9,8,8, 9,9,8,
+ 9,9,9,9,9, 9,9,9,
+ 10,8,6,3,1, 10,6,1, // [45]
+ 10,8,6,4,2, 10,6,2,
+ 10,8,7,5,3, 10,7,3,
+ 10,9,7,6,4, 10,7,4,
+ 10,9,8,6,5, 10,8,5,
+ 10,9,8,7,6, 10,8,6,
+ 10,9,9,8,7, 10,9,7,
+ 10,10,9,9,8, 10,9,8,
+ 10,10,10,9,9, 10,10,9,
+ 10,10,10,10,10, 10,10,10,
+ 11,9,6,4,1, 11,6,1, // [55]
+ 11,9,7,4,2, 11,7,2,
+ 11,9,7,5,3, 11,7,3,
+ 11,9,8,6,4, 11,8,4,
+ 11,10,8,7,5, 11,8,5,
+ 11,10,9,7,6, 11,9,6,
+ 11,10,9,8,7, 11,9,7,
+ 11,10,10,9,8, 11,10,8,
+ 11,11,10,10,9, 11,10,9,
+ 11,11,11,10,10, 11,11,10,
+ 11,11,11,11,11, 11,11,11,
+ 12,9,7,4,1, 12,7,1, // [66]
+ 12,10,7,5,2, 12,7,2,
+ 12,10,8,5,3, 12,8,3,
+ 12,10,8,6,4, 12,8,4,
+ 12,10,9,7,5, 12,9,5,
+ 12,11,9,8,6, 12,9,6,
+ 12,11,10,8,7, 12,10,7,
+ 12,11,10,9,8, 12,10,8,
+ 12,11,11,10,9, 12,11,9,
+ 12,12,11,11,10, 12,11,10,
+ 12,12,12,11,11, 12,12,11,
+ 12,12,12,12,12, 12,12,12,
+
+ 1,1,1,1,1, 1,1,1,
+ 2,2,2,1,1, 2,2,1,
+ 2,2,2,2,2, 2,2,2,
+ 3,3,2,2,1, 3,3,1,
+ 3,3,3,2,2, 3,3,2,
+ 3,3,3,3,3, 3,3,3,
+ 4,3,3,2,1, 4,3,1,
+ 4,4,3,3,2, 4,4,2,
+ 4,4,4,3,3, 4,4,3,
+ 4,4,4,4,4, 4,4,4,
+ 5,4,3,2,1, 5,4,1,
+ 5,4,4,3,2, 5,4,2,
+ 5,5,4,4,3, 5,5,3,
+ 5,5,5,4,4, 5,5,4,
+ 5,5,5,5,5, 5,5,5,
+ 6,5,4,2,1, 6,5,1,
+ 6,5,4,3,2, 6,5,2,
+ 6,5,5,4,3, 6,5,3,
+ 6,6,5,5,4, 6,6,4,
+ 6,6,6,5,5, 6,6,5,
+ 6,6,6,6,6, 6,6,6,
+ 7,6,4,3,1, 7,6,1,
+ 7,6,5,3,2, 7,6,2,
+ 7,6,5,4,3, 7,6,3,
+ 7,6,6,5,4, 7,6,4,
+ 7,7,6,6,5, 7,7,5,
+ 7,7,7,6,6, 7,7,6,
+ 7,7,7,7,7, 7,7,7,
+ 8,6,5,3,1, 8,6,1,
+ 8,7,5,4,2, 8,7,2,
+ 8,7,6,4,3, 8,7,3,
+ 8,7,6,5,4, 8,7,4,
+ 8,7,7,6,5, 8,7,5,
+ 8,8,7,7,6, 8,8,6,
+ 8,8,8,7,7, 8,8,7,
+ 8,8,8,8,8, 8,8,8,
+ 9,7,5,3,1, 9,7,1,
+ 9,7,6,4,2, 9,7,2,
+ 9,8,6,5,3, 9,8,3,
+ 9,8,7,5,4, 9,8,4,
+ 9,8,7,6,5, 9,8,5,
+ 9,8,8,7,6, 9,8,6,
+ 9,9,8,8,7, 9,9,7,
+ 9,9,9,8,8, 9,9,8,
+ 9,9,9,9,9, 9,9,9,
+ 10,8,6,3,1, 10,8,1,
+ 10,8,6,4,2, 10,8,2,
+ 10,8,7,5,3, 10,8,3,
+ 10,9,7,6,4, 10,9,4,
+ 10,9,8,6,5, 10,9,5,
+ 10,9,8,7,6, 10,9,6,
+ 10,9,9,8,7, 10,9,7,
+ 10,10,9,9,8, 10,10,8,
+ 10,10,10,9,9, 10,10,9,
+ 10,10,10,10,10, 10,10,10,
+ 11,9,6,4,1, 11,9,1,
+ 11,9,7,4,2, 11,9,2,
+ 11,9,7,5,3, 11,9,3,
+ 11,9,8,6,4, 11,9,4,
+ 11,10,8,7,5, 11,10,5,
+ 11,10,9,7,6, 11,10,6,
+ 11,10,9,8,7, 11,10,7,
+ 11,10,10,9,8, 11,10,8,
+ 11,11,10,10,9, 11,11,9,
+ 11,11,11,10,10, 11,11,10,
+ 11,11,11,11,11, 11,11,11,
+ 12,9,7,4,1, 12,9,1,
+ 12,10,7,5,2, 12,10,2,
+ 12,10,8,5,3, 12,10,3,
+ 12,10,8,6,4, 12,10,4,
+ 12,10,9,7,5, 12,10,5,
+ 12,11,9,8,6, 12,11,6,
+ 12,11,10,8,7, 12,11,7,
+ 12,11,10,9,8, 12,11,8,
+ 12,11,11,10,9, 12,11,9,
+ 12,12,11,11,10, 12,12,10,
+ 12,12,12,11,11, 12,12,11,
+ 12,12,12,12,12, 12,12,12,
+
+ 1,1,1,1,1, 1,1,1,
+ 2,2,2,1,1, 2,1,1,
+ 2,2,2,2,2, 2,2,2,
+ 3,3,2,2,1, 3,2,1,
+ 3,3,3,2,2, 3,2,2,
+ 3,3,3,3,3, 3,3,3,
+ 4,3,3,2,1, 4,2,1,
+ 4,4,3,3,2, 4,3,2,
+ 4,4,4,3,3, 4,3,3,
+ 4,4,4,4,4, 4,4,4,
+ 5,4,3,2,1, 5,2,1,
+ 5,4,4,3,2, 5,3,2,
+ 5,5,4,4,3, 5,4,3,
+ 5,5,5,4,4, 5,4,4,
+ 5,5,5,5,5, 5,5,5,
+ 6,5,4,2,1, 6,2,1,
+ 6,5,4,3,2, 6,3,2,
+ 6,5,5,4,3, 6,4,3,
+ 6,6,5,5,4, 6,5,4,
+ 6,6,6,5,5, 6,5,5,
+ 6,6,6,6,6, 6,6,6,
+ 7,6,4,3,1, 7,3,1,
+ 7,6,5,3,2, 7,3,2,
+ 7,6,5,4,3, 7,4,3,
+ 7,6,6,5,4, 7,5,4,
+ 7,7,6,6,5, 7,6,5,
+ 7,7,7,6,6, 7,6,6,
+ 7,7,7,7,7, 7,7,7,
+ 8,6,5,3,1, 8,3,1,
+ 8,7,5,4,2, 8,4,2,
+ 8,7,6,4,3, 8,4,3,
+ 8,7,6,5,4, 8,5,4,
+ 8,7,7,6,5, 8,6,5,
+ 8,8,7,7,6, 8,7,6,
+ 8,8,8,7,7, 8,7,7,
+ 8,8,8,8,8, 8,8,8,
+ 9,7,5,3,1, 9,3,1,
+ 9,7,6,4,2, 9,4,2,
+ 9,8,6,5,3, 9,5,3,
+ 9,8,7,5,4, 9,5,4,
+ 9,8,7,6,5, 9,6,5,
+ 9,8,8,7,6, 9,7,6,
+ 9,9,8,8,7, 9,8,7,
+ 9,9,9,8,8, 9,8,8,
+ 9,9,9,9,9, 9,9,9,
+ 10,8,6,3,1, 10,3,1,
+ 10,8,6,4,2, 10,4,2,
+ 10,8,7,5,3, 10,5,3,
+ 10,9,7,6,4, 10,6,4,
+ 10,9,8,6,5, 10,6,5,
+ 10,9,8,7,6, 10,7,6,
+ 10,9,9,8,7, 10,8,7,
+ 10,10,9,9,8, 10,9,8,
+ 10,10,10,9,9, 10,9,9,
+ 10,10,10,10,10, 10,10,10,
+ 11,9,6,4,1, 11,4,1,
+ 11,9,7,4,2, 11,4,2,
+ 11,9,7,5,3, 11,5,3,
+ 11,9,8,6,4, 11,6,4,
+ 11,10,8,7,5, 11,7,5,
+ 11,10,9,7,6, 11,7,6,
+ 11,10,9,8,7, 11,8,7,
+ 11,10,10,9,8, 11,9,8,
+ 11,11,10,10,9, 11,10,9,
+ 11,11,11,10,10, 11,10,10,
+ 11,11,11,11,11, 11,11,11,
+ 12,9,7,4,1, 12,4,1,
+ 12,10,7,5,2, 12,5,2,
+ 12,10,8,5,3, 12,5,3,
+ 12,10,8,6,4, 12,6,4,
+ 12,10,9,7,5, 12,7,5,
+ 12,11,9,8,6, 12,8,6,
+ 12,11,10,8,7, 12,8,7,
+ 12,11,10,9,8, 12,9,8,
+ 12,11,11,10,9, 12,10,9,
+ 12,12,11,11,10, 12,11,10,
+ 12,12,12,11,11, 12,11,11,
+ 12,12,12,12,12, 12,12,12,
+
+ // Added 2013.01.28 for CJK compatible mapping
+ 8,5,2,2,2, 8,2,2,
+ 6,6,6,4,2, 6,6,2,
+ 6,5,4,4,4, 6,4,4,
+ 6,4,2,2,2, 6,2,2,
+ 4,3,2,2,2, 4,2,2,
+ 2,2,2,2,2, 2,2,2,
+ };
+
+ // Backmap a single desired probability into an entry in kLgProbV2Tbl
+ static const uint8 kLgProbV2TblBackmap[13] = {
+ 0,
+ 0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66,
+ };
+
+ // Return address of 8-byte entry[i]
+ inline const uint8* LgProb2TblEntry(int i) {
+ return &kLgProbV2Tbl[i * 8];
+ }
+
+ // Return one of three probabilities in an entry
+ inline uint8 LgProb3(const uint8* entry, int j) {
+ return entry[j + 5];
+ }
+
+
+// Routines to access a hash table of <key:wordhash, value:probs> pairs
+// Buckets have 4-byte wordhash for sizes < 32K buckets, but only
+// 2-byte wordhash for sizes >= 32K buckets, with other wordhash bits used as
+// bucket subscript.
+// Probs is a packed: three languages plus a subscript for probability table
+// Buckets have all the keys together, then all the values.Key array never
+// crosses a cache-line boundary, so no-match case takes exactly one cache miss.
+// Match case may sometimes take an additional cache miss on value access.
+//
+// Other possibilites include 5 or 10 6-byte entries plus pad to make 32 or 64
+// byte buckets with single cache miss.
+// Or 2-byte key and 6-byte value, allowing 5 languages instead of three.
+
+
+//----------------------------------------------------------------------------//
+// Hashing groups of 1/2/4/8 letters, perhaps with spaces or underscores //
+//----------------------------------------------------------------------------//
+
+// BIGRAM
+// Pick up 1..8 bytes and hash them via mask/shift/add. NO pre/post
+// OVERSHOOTS up to 3 bytes
+// For runtime use of tables
+// Does X86 unaligned loads if !defined(NEED_ALIGNED_LOADS)UNALIGNED_LOAD32(_p)
+uint32 BiHashV2(const char* word_ptr, int bytecount);
+
+// QUADGRAM wrapper with surrounding spaces
+// Pick up 1..12 bytes plus pre/post space and hash them via mask/shift/add
+// UNDERSHOOTS 1 byte, OVERSHOOTS up to 3 bytes
+// For runtime use of tables
+uint32 QuadHashV2(const char* word_ptr, int bytecount);
+
+// QUADGRAM wrapper with surrounding underscores (offline use)
+// Pick up 1..12 bytes plus pre/post '_' and hash them via mask/shift/add
+// OVERSHOOTS up to 3 bytes
+// For offline construction of tables
+uint32 QuadHashV2Underscore(const char* word_ptr, int bytecount);
+
+// OCTAGRAM wrapper with surrounding spaces
+// Pick up 1..24 bytes plus pre/post space and hash them via mask/shift/add
+// UNDERSHOOTS 1 byte, OVERSHOOTS up to 3 bytes
+uint64 OctaHash40(const char* word_ptr, int bytecount);
+
+
+// OCTAGRAM wrapper with surrounding underscores (offline use)
+// Pick up 1..24 bytes plus pre/post space and hash them via mask/shift/add
+// UNDERSHOOTS 1 byte, OVERSHOOTS up to 3 bytes
+uint64 OctaHash40underscore(const char* word_ptr, int bytecount);
+
+// Hash a consecutive pair of tokens/words A B
+uint64 PairHash(uint64 worda_hash, uint64 wordb_hash);
+
+
+// From 32-bit gram FP, return hash table subscript and remaining key
+inline void QuadFPJustHash(uint32 quadhash,
+ uint32 keymask,
+ int bucketcount,
+ uint32* subscr, uint32* hashkey) {
+ *subscr = (quadhash + (quadhash >> 12)) & (bucketcount - 1);
+ *hashkey = quadhash & keymask;
+}
+
+// From 40-bit gram FP, return hash table subscript and remaining key
+inline void OctaFPJustHash(uint64 longwordhash,
+ uint32 keymask,
+ int bucketcount,
+ uint32* subscr, uint32* hashkey) {
+ uint32 temp = (longwordhash + (longwordhash >> 12)) & (bucketcount - 1);
+ *subscr = temp;
+ temp = longwordhash >> 4;
+ *hashkey = temp & keymask;
+}
+
+
+// Look up 32-bit gram FP in caller-passed table
+// Typical size 256K entries (1.5MB)
+// Two-byte hashkey
+inline const uint32 QuadHashV3Lookup4(const CLD2TableSummary* gram_obj,
+ uint32 quadhash) {
+ uint32 subscr, hashkey;
+ const IndirectProbBucket4* quadtable = gram_obj->kCLDTable;
+ uint32 keymask = gram_obj->kCLDTableKeyMask;
+ int bucketcount = gram_obj->kCLDTableSize;
+ QuadFPJustHash(quadhash, keymask, bucketcount, &subscr, &hashkey);
+ const IndirectProbBucket4* bucket_ptr = &quadtable[subscr];
+ // Four-way associative, 4 compares
+ if (((hashkey ^ bucket_ptr->keyvalue[0]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[0];
+ }
+ if (((hashkey ^ bucket_ptr->keyvalue[1]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[1];
+ }
+ if (((hashkey ^ bucket_ptr->keyvalue[2]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[2];
+ }
+ if (((hashkey ^ bucket_ptr->keyvalue[3]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[3];
+ }
+ return 0;
+}
+
+// Look up 40-bit gram FP in caller-passed table
+// Typical size 256K-4M entries (1-16MB)
+// 24-12 bit hashkey packed with 8-20 bit indirect lang/probs
+// keymask is 0xfffff000 for 20-bit hashkey and 12-bit indirect
+inline const uint32 OctaHashV3Lookup4(const CLD2TableSummary* gram_obj,
+ uint64 longwordhash) {
+ uint32 subscr, hashkey;
+ const IndirectProbBucket4* octatable = gram_obj->kCLDTable;
+ uint32 keymask = gram_obj->kCLDTableKeyMask;
+ int bucketcount = gram_obj->kCLDTableSize;
+ OctaFPJustHash(longwordhash, keymask, bucketcount,
+ &subscr, &hashkey);
+ const IndirectProbBucket4* bucket_ptr = &octatable[subscr];
+ // Four-way associative, 4 compares
+ if (((hashkey ^ bucket_ptr->keyvalue[0]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[0];
+ }
+ if (((hashkey ^ bucket_ptr->keyvalue[1]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[1];
+ }
+ if (((hashkey ^ bucket_ptr->keyvalue[2]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[2];
+ }
+ if (((hashkey ^ bucket_ptr->keyvalue[3]) & keymask) == 0) {
+ return bucket_ptr->keyvalue[3];
+ }
+ return 0;
+}
+
+
+//----------------------------------------------------------------------------//
+// Finding groups of 1/2/4/8 letters //
+//----------------------------------------------------------------------------//
+
+// Does not advance past space or tab/cr/lf/nul
+static const uint8 kAdvanceOneCharButSpace[256] = {
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,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,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,
+ 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4,
+};
+
+
+// Advances *only* on space or ASCII vowel (or illegal byte)
+static const uint8 kAdvanceOneCharSpaceVowel[256] = {
+ 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,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,1,0,0,0,1,0,0, 0,1,0,0,0,0,0,1, 0,0,0,0,0,1,0,0, 0,0,0,0,0,0,0,0,
+ 0,1,0,0,0,1,0,0, 0,1,0,0,0,0,0,1, 0,0,0,0,0,1,0,0, 0,0,0,0,0,0,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,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+};
+
+
+// src points to a letter. Find the byte length of a unigram starting there.
+int UniLen(const char* src);
+
+// src points to a letter. Find the byte length of a bigram starting there.
+int BiLen(const char* src);
+
+// src points to a letter. Find the byte length of a quadgram starting there.
+int QuadLen(const char* src);
+
+// src points to a letter. Find the byte length of an octagram starting there.
+int OctaLen(const char* src);
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_NEW_CLDUTIL_SHARED_H__
+
+
+
+
+
+
diff --git a/browser/components/translation/cld2/internal/compact_lang_det.cc b/browser/components/translation/cld2/internal/compact_lang_det.cc
new file mode 100644
index 000000000..ccc6588fc
--- /dev/null
+++ b/browser/components/translation/cld2/internal/compact_lang_det.cc
@@ -0,0 +1,322 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../public/compact_lang_det.h"
+#include "../public/encodings.h"
+#include "compact_lang_det_impl.h"
+#include "integral_types.h"
+#include "lang_script.h"
+
+namespace CLD2 {
+
+// String is "code_version - data_scrape_date"
+//static const char* kDetectLanguageVersion = "V2.0 - 20130715";
+
+
+// Large-table version for all ~160 languages
+// Small-table version for all ~60 languages
+
+// Scan interchange-valid UTF-8 bytes and detect most likely language
+Language DetectLanguage(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ bool* is_reliable) {
+ bool allow_extended_lang = false;
+ Language language3[3];
+ int percent3[3];
+ double normalized_score3[3];
+ int text_bytes;
+ int flags = 0;
+ Language plus_one = UNKNOWN_LANGUAGE;
+ const char* tld_hint = "";
+ int encoding_hint = UNKNOWN_ENCODING;
+ Language language_hint = UNKNOWN_LANGUAGE;
+ CLDHints cldhints = {NULL, tld_hint, encoding_hint, language_hint};
+
+ Language lang = DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ &cldhints,
+ allow_extended_lang,
+ flags,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ NULL,
+ &text_bytes,
+ is_reliable);
+ // Default to English
+ if (lang == UNKNOWN_LANGUAGE) {
+ lang = ENGLISH;
+ }
+ return lang;
+}
+
+// Scan interchange-valid UTF-8 bytes and detect list of top 3 languages.
+Language DetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable) {
+ double normalized_score3[3];
+ bool allow_extended_lang = false;
+ int flags = 0;
+ Language plus_one = UNKNOWN_LANGUAGE;
+ const char* tld_hint = "";
+ int encoding_hint = UNKNOWN_ENCODING;
+ Language language_hint = UNKNOWN_LANGUAGE;
+ CLDHints cldhints = {NULL, tld_hint, encoding_hint, language_hint};
+
+ Language lang = DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ &cldhints,
+ allow_extended_lang,
+ flags,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ NULL,
+ text_bytes,
+ is_reliable);
+ // Default to English
+ if (lang == UNKNOWN_LANGUAGE) {
+ lang = ENGLISH;
+ }
+ return lang;
+}
+
+// Same as above, with hints supplied
+// Scan interchange-valid UTF-8 bytes and detect list of top 3 languages.
+Language DetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const char* tld_hint, // "id" boosts Indonesian
+ int encoding_hint, // SJS boosts Japanese
+ Language language_hint, // ITALIAN boosts it
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable) {
+ double normalized_score3[3];
+ bool allow_extended_lang = false;
+ int flags = 0;
+ Language plus_one = UNKNOWN_LANGUAGE;
+ CLDHints cldhints = {NULL, tld_hint, encoding_hint, language_hint};
+
+ Language lang = DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ &cldhints,
+ allow_extended_lang,
+ flags,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ NULL,
+ text_bytes,
+ is_reliable);
+ // Default to English
+ if (lang == UNKNOWN_LANGUAGE) {
+ lang = ENGLISH;
+ }
+ return lang;
+}
+
+
+// Scan interchange-valid UTF-8 bytes and detect list of top 3 extended
+// languages.
+// Extended languages are additional Google interface languages and Unicode
+// single-language scripts, from ext_lang_enc.h
+Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable) {
+ double normalized_score3[3];
+ bool allow_extended_lang = true;
+ int flags = 0;
+ Language plus_one = UNKNOWN_LANGUAGE;
+ const char* tld_hint = "";
+ int encoding_hint = UNKNOWN_ENCODING;
+ Language language_hint = UNKNOWN_LANGUAGE;
+ CLDHints cldhints = {NULL, tld_hint, encoding_hint, language_hint};
+
+ Language lang = DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ &cldhints,
+ allow_extended_lang,
+ flags,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ NULL,
+ text_bytes,
+ is_reliable);
+ // Do not default to English
+ return lang;
+}
+
+// Same as above, with hints supplied
+// Scan interchange-valid UTF-8 bytes and detect list of top 3 extended
+// languages.
+// Extended languages are additional Google interface languages and Unicode
+// single-language scripts, from ext_lang_enc.h
+Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const char* tld_hint, // "id" boosts Indonesian
+ int encoding_hint, // SJS boosts Japanese
+ Language language_hint, // ITALIAN boosts it
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable) {
+ double normalized_score3[3];
+ bool allow_extended_lang = true;
+ int flags = 0;
+ Language plus_one = UNKNOWN_LANGUAGE;
+ CLDHints cldhints = {NULL, tld_hint, encoding_hint, language_hint};
+
+ Language lang = DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ &cldhints,
+ allow_extended_lang,
+ flags,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ NULL,
+ text_bytes,
+ is_reliable);
+ // Do not default to English
+ return lang;
+}
+
+// Same as above, and also returns internal language scores as a ratio to
+// normal score for real text in that language. Scores close to 1.0 indicate
+// normal text, while scores far away from 1.0 indicate badly-skewed text or
+// gibberish
+//
+Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const char* tld_hint, // "id" boosts Indonesian
+ int encoding_hint, // SJS boosts Japanese
+ Language language_hint, // ITALIAN boosts it
+ Language* language3,
+ int* percent3,
+ double* normalized_score3,
+ int* text_bytes,
+ bool* is_reliable) {
+ bool allow_extended_lang = true;
+ int flags = 0;
+ Language plus_one = UNKNOWN_LANGUAGE;
+ CLDHints cldhints = {NULL, tld_hint, encoding_hint, language_hint};
+
+ Language lang = DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ &cldhints,
+ allow_extended_lang,
+ flags,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ NULL,
+ text_bytes,
+ is_reliable);
+ // Do not default to English
+ return lang;
+}
+
+// Use this one.
+// Hints are collected into a struct.
+// Flags are passed in (normally zero).
+//
+// Also returns 3 internal language scores as a ratio to
+// normal score for real text in that language. Scores close to 1.0 indicate
+// normal text, while scores far away from 1.0 indicate badly-skewed text or
+// gibberish
+//
+// Returns a vector of chunks in different languages, so that caller may
+// spell-check, translate, or otherwaise process different parts of the input
+// buffer in language-dependant ways.
+//
+Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const CLDHints* cld_hints,
+ int flags,
+ Language* language3,
+ int* percent3,
+ double* normalized_score3,
+ ResultChunkVector* resultchunkvector,
+ int* text_bytes,
+ bool* is_reliable) {
+ bool allow_extended_lang = true;
+ Language plus_one = UNKNOWN_LANGUAGE;
+
+ Language lang = DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ cld_hints,
+ allow_extended_lang,
+ flags,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ resultchunkvector,
+ text_bytes,
+ is_reliable);
+ // Do not default to English
+ return lang;
+}
+
+} // End namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/compact_lang_det_hint_code.cc b/browser/components/translation/cld2/internal/compact_lang_det_hint_code.cc
new file mode 100644
index 000000000..9bde8a86a
--- /dev/null
+++ b/browser/components/translation/cld2/internal/compact_lang_det_hint_code.cc
@@ -0,0 +1,1649 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#include "compact_lang_det_hint_code.h"
+
+#include <stdlib.h> // for abs()
+#include <stdio.h> // for sprintf()
+#include <string.h> //
+#include "lang_script.h"
+#include "port.h"
+
+using namespace std;
+
+namespace CLD2 {
+
+static const int kCLDPriorEncodingWeight = 4; // 100x more likely
+static const int kCLDPriorLanguageWeight = 8; // 10000x more likely
+
+
+// Tables to map lang="..." language code lists to actual languages.
+// based on scraping and hand-edits, dsites June 2011
+
+// n = f(string, &a) gives list of n<=4 language pairs: primary, secondary
+
+// For close pairs like ms/id, more weight on TLD and lang=
+// Alternately, weaker boost but mark others of set as negative;
+// makes "neither" an easier result.
+// lang=en low weight 4
+// tld=lu boost lu maaybe 4. but lang= alwyas overcomes tld and encoding
+// (except maybe en)
+
+// TLD to separate, e.g., burundi from rwanda
+
+// Encoding lookup: OneLangProb array
+// TLD lookup: tld OneLangProb pairs
+
+
+typedef struct {
+ const char* const langtag; // Lowercased, hyphen only lookup key
+ const char* const langcode; // Canonical language codes; two if ambiguous
+ OneCLDLangPrior onelangprior1;
+ OneCLDLangPrior onelangprior2;
+} LangTagLookup;
+
+typedef struct {
+ const char* const tld; // Lowercased, hyphen only lookup key
+ OneCLDLangPrior onelangprior1;
+ OneCLDLangPrior onelangprior2;
+} TLDLookup;
+
+
+#define W2 (2 << 10) // 3**2 = 10x more likely
+#define W4 (4 << 10) // 3**4 = 100x more likely
+#define W6 (6 << 10) // 3**6 = 1000x more likely
+#define W8 (8 << 10) // 3**8 = 10K x more likely
+#define W10 (10 << 10) // 3**10 = 100K x more likely
+#define W12 (12 << 10) // 3**12 = 1M x more likely
+
+// TODO: more about ba hr sr sr-ME and sl
+// Temporary state of affairs:
+// BOSNIAN CROATIAN MONTENEGRIN SERBIAN detecting just CROATIAN SERBIAN
+// Eventually, we want to do all four, but it requires a CLD change to handle
+// up to six languages per quadgram.
+
+
+// Close pairs boost one of pair, demote other.
+// Statistically close pairs:
+// INDONESIAN/MALAY difficult to distinguish -- extra word-based lookups used
+//
+// INDONESIAN MALAY coef=0.4698 Problematic w/o extra words
+// TIBETAN DZONGKHA coef=0.4571
+// CZECH SLOVAK coef=0.4273
+// NORWEGIAN NORWEGIAN_N coef=0.4182
+//
+// HINDI MARATHI coef=0.3795
+// ZULU XHOSA coef=0.3716
+//
+// DANISH NORWEGIAN coef=0.3672 Usually OK
+// BIHARI HINDI coef=0.3668 Usually OK
+// ICELANDIC FAROESE coef=0.3519 Usually OK
+
+//
+// Table to look up lang= tags longer than three characters
+// Overrides table below, which is truncated at first hyphen
+// In alphabetical order for binary search
+static const int kCLDTable1Size = 213;
+static const LangTagLookup kCLDLangTagsHintTable1[kCLDTable1Size] = {
+ {"abkhazian", "ab", ABKHAZIAN + W10, 0},
+ {"afar", "aa", AFAR + W10, 0},
+ {"afrikaans", "af", AFRIKAANS + W10, 0},
+ {"akan", "ak", AKAN + W10, 0},
+ {"albanian", "sq", ALBANIAN + W10, 0},
+ {"am-am", "hy", ARMENIAN + W10, 0}, // 1:2 Armenian, not ambiguous
+ {"amharic", "am", AMHARIC + W10, 0},
+ {"arabic", "ar", ARABIC + W10, 0},
+ {"argentina", "es", SPANISH + W10, 0},
+ {"armenian", "hy", ARMENIAN + W10, 0},
+ {"assamese", "as", ASSAMESE + W10, 0},
+ {"aymara", "ay", AYMARA + W10, 0},
+ {"azerbaijani", "az", AZERBAIJANI + W10, 0},
+
+ {"bangla", "bn", BENGALI + W10, 0},
+ {"bashkir", "ba", BASHKIR + W10, 0},
+ {"basque", "eu", BASQUE + W10, 0},
+ {"belarusian", "be", BELARUSIAN + W10, 0},
+ {"bengali", "bn", BENGALI + W10, 0},
+ {"bihari", "bh", BIHARI + W10, HINDI - W4},
+ {"bislama", "bi", BISLAMA + W10, 0},
+ {"bosnian", "bs", BOSNIAN + W10, 0}, // Bosnian => Bosnian
+ {"br-br", "pt", PORTUGUESE + W10, 0}, // 1:2 Portuguese, not ambiguous
+ {"br-fr", "br", BRETON + W10, 0}, // 1:2 Breton, not ambiguous
+ {"breton", "br", BRETON + W10, 0},
+ {"bulgarian", "bg", BULGARIAN + W10, 0},
+ {"burmese", "my", BURMESE + W10, 0}, // Myanmar
+
+ {"catalan", "ca", CATALAN + W10, 0},
+ {"cherokee", "chr", CHEROKEE + W10, 0},
+ {"chichewa", "ny", NYANJA + W10, 0},
+
+ {"chinese", "zh", CHINESE + W10, 0},
+ {"chinese-t", "zhT", CHINESE_T + W10, 0},
+ {"chineset", "zhT", CHINESE_T + W10, 0},
+ {"corsican", "co", CORSICAN + W10, 0},
+ {"cpf-hat", "ht", HAITIAN_CREOLE + W10, 0}, // Creole, French-based
+ {"croatian", "hr", CROATIAN + W10, 0},
+ {"czech", "cs", CZECH + W10, SLOVAK - W4},
+
+ {"danish", "da", DANISH + W10, NORWEGIAN - W4},
+ {"deutsch", "de", GERMAN + W10, 0},
+ {"dhivehi", "dv", DHIVEHI + W10, 0},
+ {"dutch", "nl", DUTCH + W10, 0},
+ {"dzongkha", "dz", DZONGKHA + W10, TIBETAN - W4},
+
+ {"ell-gr", "el", GREEK + W10, 0},
+ {"english", "en", ENGLISH + W4, 0},
+ {"esperanto", "eo", ESPERANTO + W10, 0},
+ {"estonian", "et", ESTONIAN + W10, 0},
+ {"euc-jp", "ja", JAPANESE + W10, 0}, // Japanese encoding
+ {"euc-kr", "ko", KOREAN + W10, 0}, // Korean encoding
+
+ {"faroese", "fo", FAROESE + W10, ICELANDIC - W4},
+ {"fijian", "fj", FIJIAN + W10, 0},
+ {"finnish", "fi", FINNISH + W10, 0},
+ {"fran", "fr", FRENCH + W10, 0}, // Truncated at non-ASCII
+ {"francais", "fr", FRENCH + W10, 0},
+ {"french", "fr", FRENCH + W10, 0},
+ {"frisian", "fy", FRISIAN + W10, 0},
+
+ {"ga-es", "gl", GALICIAN + W10, 0}, // 1:2 Galician, not ambiguous
+ {"galician", "gl", GALICIAN + W10, 0},
+ {"ganda", "lg", GANDA + W10, 0},
+ {"georgian", "ka", GEORGIAN + W10, 0},
+ {"german", "de", GERMAN + W10, 0},
+ {"greek", "el", GREEK + W10, 0},
+ {"greenlandic", "kl", GREENLANDIC + W10, 0},
+ {"guarani", "gn", GUARANI + W10, 0},
+ {"gujarati", "gu", GUJARATI + W10, 0},
+
+ {"haitian_creole", "ht", HAITIAN_CREOLE + W10, 0},
+ {"hausa", "ha", HAUSA + W10, 0},
+ {"hawaiian", "haw", HAWAIIAN + W10, 0},
+ {"hebrew", "he", HEBREW + W10, 0},
+ {"hindi", "hi", HINDI + W10, MARATHI - W4},
+ {"hn-in", "hi", HINDI + W10, MARATHI - W4},
+ {"hungarian", "hu", HUNGARIAN + W10, 0},
+
+ {"icelandic", "is", ICELANDIC + W10, FAROESE - W4},
+ {"igbo", "ig", IGBO + W10, 0},
+ {"indonesian", "id", INDONESIAN + W10, MALAY - W4},
+ {"interlingua", "ia", INTERLINGUA + W10, 0},
+ {"interlingue", "ie", INTERLINGUE + W10, 0},
+ // 1:2 iu-Cans ik-Latn
+ {"inuktitut", "iu,ik", INUKTITUT + W10, INUPIAK + W10}, // 1:2
+ {"inupiak", "ik,iu", INUPIAK + W10, INUKTITUT + W10}, // 1:2
+ {"ir-ie", "ga", IRISH + W10, 0}, // Irish
+ {"irish", "ga", IRISH + W10, 0},
+ {"italian", "it", ITALIAN + W10, 0},
+
+ {"ja-euc", "ja", JAPANESE + W10, 0}, // Japanese encoding
+ {"jan-jp", "ja", JAPANESE + W10, 0}, // Japanese encoding
+ {"japanese", "ja", JAPANESE + W10, 0},
+ {"javanese", "jw", JAVANESE + W10, 0},
+
+ {"kannada", "kn", KANNADA + W10, 0},
+ {"kashmiri", "ks", KASHMIRI + W10, 0},
+ {"kazakh", "kk", KAZAKH + W10, 0},
+ {"khasi", "kha", KHASI + W10, 0},
+ {"khmer", "km", KHMER + W10, 0},
+ {"kinyarwanda", "rw", KINYARWANDA + W10, 0},
+ {"klingon", "tlh", X_KLINGON + W10, 0},
+ {"korean", "ko", KOREAN + W10, 0},
+ {"kurdish", "ku", KURDISH + W10, 0},
+ {"kyrgyz", "ky", KYRGYZ + W10, 0},
+
+ {"laothian", "lo", LAOTHIAN + W10, 0},
+ {"latin", "la", LATIN + W10, 0},
+ {"latvian", "lv", LATVIAN + W10, 0},
+ {"limbu", "sit", LIMBU + W10, 0},
+ {"lingala", "ln", LINGALA + W10, 0},
+ {"lithuanian", "lt", LITHUANIAN + W10, 0},
+ {"luxembourgish", "lb", LUXEMBOURGISH + W10, 0},
+
+ {"macedonian", "mk", MACEDONIAN + W10, 0},
+ {"malagasy", "mg", MALAGASY + W10, 0},
+ {"malay", "ms", MALAY + W10, INDONESIAN - W4},
+ {"malayalam", "ml", MALAYALAM + W10, 0},
+ {"maltese", "mt", MALTESE + W10, 0},
+ {"manx", "gv", MANX + W10, 0},
+ {"maori", "mi", MAORI + W10, 0},
+ {"marathi", "mr", MARATHI + W10, HINDI - W4},
+ {"mauritian_creole", "mfe", MAURITIAN_CREOLE + W10, 0},
+ {"moldavian", "mo", ROMANIAN + W10, 0},
+ {"mongolian", "mn", MONGOLIAN + W10, 0},
+ {"montenegrin", "sr-me", MONTENEGRIN + W10, 0},
+ {"myanmar", "my", BURMESE + W10, 0}, // Myanmar
+ {"nauru", "na", NAURU + W10, 0},
+ {"ndebele", "nr", NDEBELE + W10, 0},
+ {"nepali", "ne", NEPALI + W10, 0},
+ {"no-bok", "no", NORWEGIAN + W10, NORWEGIAN_N - W4}, // Bokmaal
+ {"no-bokmaal", "no", NORWEGIAN + W10, NORWEGIAN_N - W4},
+ {"no-nb", "no", NORWEGIAN + W10, NORWEGIAN_N - W4}, // Bokmaal
+ {"no-no", "no", NORWEGIAN + W10, NORWEGIAN_N - W4},
+ {"no-nyn", "nn", NORWEGIAN_N + W10, NORWEGIAN - W4}, // Nynorsk
+ {"no-nynorsk", "nn", NORWEGIAN_N + W10, NORWEGIAN - W4},
+ {"norwegian", "no", NORWEGIAN + W10, NORWEGIAN_N - W4},
+ {"norwegian_n", "nn", NORWEGIAN_N + W10, NORWEGIAN - W4},
+ {"nyanja", "ny", NYANJA + W10, 0},
+
+ {"occitan", "oc", OCCITAN + W10, 0},
+ {"oriya", "or", ORIYA + W10, 0},
+ {"oromo", "om", OROMO + W10, 0},
+ {"parsi", "fa", PERSIAN + W10, 0},
+
+ {"pashto", "ps", PASHTO + W10, 0},
+ {"pedi", "nso", PEDI + W10, 0},
+ {"persian", "fa", PERSIAN + W10, 0},
+ {"polish", "pl", POLISH + W10, 0},
+ {"polska", "pl", POLISH + W10, 0},
+ {"polski", "pl", POLISH + W10, 0},
+ {"portugu", "pt", PORTUGUESE + W10, 0}, // Truncated at non-ASCII
+ {"portuguese", "pt", PORTUGUESE + W10, 0},
+ {"punjabi", "pa", PUNJABI + W10, 0},
+
+ {"quechua", "qu", QUECHUA + W10, 0},
+
+ {"rhaeto_romance", "rm", RHAETO_ROMANCE + W10, 0},
+ {"romanian", "ro", ROMANIAN + W10, 0},
+ {"rundi", "rn", RUNDI + W10, 0},
+ {"russian", "ru", RUSSIAN + W10, 0},
+
+ {"samoan", "sm", SAMOAN + W10, 0},
+ {"sango", "sg", SANGO + W10, 0},
+ {"sanskrit", "sa", SANSKRIT + W10, 0},
+ {"scots", "sco", SCOTS + W10, ENGLISH - W4},
+ {"scots_gaelic", "gd", SCOTS_GAELIC + W10, 0},
+ {"serbian", "sr", SERBIAN + W10, 0},
+ {"seselwa", "crs", SESELWA + W10, 0},
+ {"sesotho", "st", SESOTHO + W10, 0},
+ {"shift-jis", "ja", JAPANESE + W10, 0}, // Japanese encoding
+ {"shift-js", "ja", JAPANESE + W10, 0}, // Japanese encoding
+ {"shona", "sn", SHONA + W10, 0},
+ {"si-lk", "si", SINHALESE + W10, 0}, // 1:2 Sri Lanka, not ambiguous
+ {"si-si", "sl", SLOVENIAN + W10, 0}, // 1:2 Slovenia, not ambiguous
+ {"si-sl", "sl", SLOVENIAN + W10, 0}, // 1:2 Slovenia, not ambiguous
+ {"sindhi", "sd", SINDHI + W10, 0},
+ {"sinhalese", "si", SINHALESE + W10, 0},
+ {"siswant", "ss", SISWANT + W10, 0},
+ {"sit-np", "sit", LIMBU + W10, 0},
+ {"slovak", "sk", SLOVAK + W10, CZECH - W4},
+ {"slovenian", "sl", SLOVENIAN + W10, 0},
+ {"somali", "so", SOMALI + W10, 0},
+ {"spanish", "es", SPANISH + W10, 0},
+ {"sr-me", "sr-me", MONTENEGRIN + W10, 0}, // Montenegrin => Montenegrin
+ {"sundanese", "su", SUNDANESE + W10, 0},
+ {"suomi", "fi", FINNISH + W10, 0}, // Finnish
+ {"swahili", "sw", SWAHILI + W10, 0},
+ {"swedish", "sv", SWEDISH + W10, 0},
+ {"syriac", "syr", SYRIAC + W10, 0},
+
+ {"tagalog", "tl", TAGALOG + W10, 0},
+ {"tajik", "tg", TAJIK + W10, 0},
+ {"tamil", "ta", TAMIL + W10, 0},
+ {"tatar", "tt", TATAR + W10, 0},
+ {"tb-tb", "bo", TIBETAN + W10, DZONGKHA - W4}, // Tibet
+ {"tchinese", "zhT", CHINESE_T + W10, 0},
+ {"telugu", "te", TELUGU + W10, 0},
+ {"thai", "th", THAI + W10, 0},
+ {"tibetan", "bo", TIBETAN + W10, DZONGKHA - W4},
+ {"tigrinya", "ti", TIGRINYA + W10, 0},
+ {"tonga", "to", TONGA + W10, 0},
+ {"tsonga", "ts", TSONGA + W10, 0},
+ {"tswana", "tn", TSWANA + W10, 0},
+ {"tt-ru", "tt", TATAR + W10, 0},
+ {"tur-tr", "tr", TURKISH + W10, 0},
+ {"turkish", "tr", TURKISH + W10, 0},
+ {"turkmen", "tk", TURKMEN + W10, 0},
+ {"uighur", "ug", UIGHUR + W10, 0},
+ {"ukrainian", "uk", UKRAINIAN + W10, 0},
+ {"urdu", "ur", URDU + W10, 0},
+ {"uzbek", "uz", UZBEK + W10, 0},
+
+ {"venda", "ve", VENDA + W10, 0},
+ {"vietnam", "vi", VIETNAMESE + W10, 0},
+ {"vietnamese", "vi", VIETNAMESE + W10, 0},
+ {"volapuk", "vo", VOLAPUK + W10, 0},
+
+ {"welsh", "cy", WELSH + W10, 0},
+ {"wolof", "wo", WOLOF + W10, 0},
+
+ {"xhosa", "xh", XHOSA + W10, ZULU - W4},
+
+ {"yiddish", "yi", YIDDISH + W10, 0},
+ {"yoruba", "yo", YORUBA + W10, 0},
+
+ {"zh-classical", "zhT", CHINESE_T + W10, 0},
+ {"zh-cn", "zh", CHINESE + W10, 0},
+ {"zh-hans", "zh", CHINESE + W10, 0},
+ {"zh-hant", "zhT", CHINESE_T + W10, 0},
+ {"zh-hk", "zhT", CHINESE_T + W10, 0},
+ {"zh-min-nan", "zhT", CHINESE_T + W10, 0}, // Min Nan => ChineseT
+ {"zh-sg", "zhT", CHINESE_T + W10, 0},
+ {"zh-tw", "zhT", CHINESE_T + W10, 0},
+ {"zh-yue", "zh", CHINESE + W10, 0}, // Yue (Cantonese) => Chinese
+ {"zhuang", "za", ZHUANG + W10, 0},
+ {"zulu", "zu", ZULU + W10, XHOSA - W4},
+};
+
+
+
+// Table to look up lang= tags of two/three characters after truncate at hyphen
+// In alphabetical order for binary search
+static const int kCLDTable2Size = 257;
+static const LangTagLookup kCLDLangTagsHintTable2[kCLDTable2Size] = {
+ {"aa", "aa", AFAR + W10, 0},
+ {"ab", "ab", ABKHAZIAN + W10, 0},
+ {"af", "af", AFRIKAANS + W10, 0},
+ {"ak", "ak", AKAN + W10, 0},
+ {"al", "sq", ALBANIAN + W10, 0}, // Albania
+ {"am", "am,hy", AMHARIC + W10, ARMENIAN + W10}, // 1:2 Amharic Armenian
+ {"ar", "ar", ARABIC + W10, 0},
+ {"ara", "ar", ARABIC + W10, 0},
+ {"arm", "hy", ARMENIAN + W10, 0}, // Armenia
+ {"arz", "ar", ARABIC + W10, 0}, // Egyptian Arabic
+ {"as", "as", ASSAMESE + W10, 0},
+ {"at", "de", GERMAN + W10, 0}, // Austria
+ {"au", "de", GERMAN + W10, 0}, // Austria
+ {"ay", "ay", AYMARA + W10, 0},
+ {"az", "az", AZERBAIJANI + W10, 0},
+ {"aze", "az", AZERBAIJANI + W10, 0},
+
+ {"ba", "ba,bs", BASHKIR + W10, BOSNIAN + W10}, // 1:2 Bashkir Bosnia
+ {"be", "be", BELARUSIAN + W10, 0},
+ {"bel", "be", BELARUSIAN + W10, 0},
+ {"bg", "bg", BULGARIAN + W10, 0},
+ {"bh", "bh", BIHARI + W10, HINDI - W4},
+ {"bi", "bi", BISLAMA + W10, 0},
+ {"big", "zhT", CHINESE_T + W10, 0}, // Big5 encoding
+ {"bm", "ms", MALAY + W10, INDONESIAN - W4}, // Bahasa Malaysia
+ {"bn", "bn", BENGALI + W10, 0},
+ {"bo", "bo", TIBETAN + W10, DZONGKHA - W4},
+ // 1:2 Breton, Brazil country code, both Latn .br TLD enough for pt to win
+ {"br", "br,pt", BRETON + W10, PORTUGUESE + W8}, // 1:2 Breton, Brazil
+ {"bs", "bs", BOSNIAN + W10, 0}, // Bosnian => Bosnian
+
+ {"ca", "ca", CATALAN + W10, 0},
+ {"cat", "ca", CATALAN + W10, 0},
+ {"ch", "de,fr", GERMAN + W10, FRENCH + W10}, // 1:2 Switzerland
+ {"chn", "zh", CHINESE + W10, 0},
+ {"chr", "chr", CHEROKEE + W10, 0},
+ {"ckb", "ku", KURDISH + W10, 0}, // Central Kurdish
+ {"cn", "zh,zhT", CHINESE + W6, CHINESE_T + W4}, // Ambiguous, so weaker.
+ // Offset by 2 so that TLD=tw or
+ // enc=big5 will put zhT ahead
+ {"co", "co", CORSICAN + W10, 0},
+ {"cro", "hr", CROATIAN + W10, 0}, // Croatia
+ {"crs", "crs", SESELWA + W10, 0},
+ {"cs", "cs", CZECH + W10, SLOVAK - W4},
+ {"ct", "ca", CATALAN + W10, 0},
+ {"cy", "cy", WELSH + W10, 0},
+ {"cym", "cy", WELSH + W10, 0},
+ {"cz", "cs", CZECH + W10, SLOVAK - W4},
+
+ {"da", "da", DANISH + W10, NORWEGIAN - W4},
+ {"dan", "da", DANISH + W10, NORWEGIAN - W4},
+ {"de", "de", GERMAN + W10, 0},
+ {"deu", "de", GERMAN + W10, 0},
+ {"div", "dv", DHIVEHI + W10, 0},
+ {"dk", "da", DANISH + W10, NORWEGIAN - W4}, // Denmark
+ {"dut", "nl", DUTCH + W10, 0}, // Dutch
+ {"dv", "dv", DHIVEHI + W10, 0},
+ {"dz", "dz", DZONGKHA + W10, TIBETAN - W4},
+
+ {"ee", "et", ESTONIAN + W10, 0}, // Estonia
+ {"eg", "ar", ARABIC + W10, 0}, // Egypt
+ {"el", "el", GREEK + W10, 0},
+ {"en", "en", ENGLISH + W4, 0},
+ {"eng", "en", ENGLISH + W4, 0},
+ {"eo", "eo", ESPERANTO + W10, 0},
+ {"er", "ur", URDU + W10, 0}, // "Erdu"
+ {"es", "es", SPANISH + W10, 0},
+ {"esp", "es", SPANISH + W10, 0},
+ {"est", "et", ESTONIAN + W10, 0},
+ {"et", "et", ESTONIAN + W10, 0},
+ {"eu", "eu", BASQUE + W10, 0},
+
+ {"fa", "fa", PERSIAN + W10, 0},
+ {"far", "fa", PERSIAN + W10, 0},
+ {"fi", "fi", FINNISH + W10, 0},
+ {"fil", "tl", TAGALOG + W10, 0}, // Philippines
+ {"fj", "fj", FIJIAN + W10, 0},
+ {"fo", "fo", FAROESE + W10, ICELANDIC - W4},
+ {"fr", "fr", FRENCH + W10, 0},
+ {"fra", "fr", FRENCH + W10, 0},
+ {"fre", "fr", FRENCH + W10, 0},
+ {"fy", "fy", FRISIAN + W10, 0},
+
+ {"ga", "ga,gl", IRISH + W10, GALICIAN + W10}, // 1:2 Irish, Galician
+ {"gae", "gd,ga", SCOTS_GAELIC + W10, IRISH + W10}, // 1:2 Gaelic, either
+ {"gal", "gl", GALICIAN + W10, 0},
+ {"gb", "zh", CHINESE + W10, 0}, // GB2312 encoding
+ {"gbk", "zh", CHINESE + W10, 0}, // GBK encoding
+ {"gd", "gd", SCOTS_GAELIC + W10, 0},
+ {"ge", "ka", GEORGIAN + W10, 0}, // Georgia
+ {"geo", "ka", GEORGIAN + W10, 0},
+ {"ger", "de", GERMAN + W10, 0},
+ {"gl", "gl", GALICIAN + W10, 0}, // Also Greenland; hard to confuse
+ {"gn", "gn", GUARANI + W10, 0},
+ {"gr", "el", GREEK + W10, 0}, // Greece
+ {"gu", "gu", GUJARATI + W10, 0},
+ {"gv", "gv", MANX + W10, 0},
+
+ {"ha", "ha", HAUSA + W10, 0},
+ {"hat", "ht", HAITIAN_CREOLE + W10, 0}, // Haiti
+ {"haw", "haw", HAWAIIAN + W10, 0},
+ {"hb", "he", HEBREW + W10, 0},
+ {"he", "he", HEBREW + W10, 0},
+ {"heb", "he", HEBREW + W10, 0},
+ {"hi", "hi", HINDI + W10, MARATHI - W4},
+ {"hk", "zhT", CHINESE_T + W10, 0}, // Hong Kong
+ {"hr", "hr", CROATIAN + W10, 0},
+ {"ht", "ht", HAITIAN_CREOLE + W10, 0},
+ {"hu", "hu", HUNGARIAN + W10, 0},
+ {"hun", "hu", HUNGARIAN + W10, 0},
+ {"hy", "hy", ARMENIAN + W10, 0},
+
+ {"ia", "ia", INTERLINGUA + W10, 0},
+ {"ice", "is", ICELANDIC + W10, FAROESE - W4}, // Iceland
+ {"id", "id", INDONESIAN + W10, MALAY - W4},
+ {"ids", "id", INDONESIAN + W10, MALAY - W4},
+ {"ie", "ie", INTERLINGUE + W10, 0},
+ {"ig", "ig", IGBO + W10, 0},
+ // 1:2 iu-Cans ik-Latn
+ {"ik", "ik,iu", INUPIAK + W10, INUKTITUT + W10}, // 1:2
+ {"in", "id", INDONESIAN + W10, MALAY - W4},
+ {"ind", "id", INDONESIAN + W10, MALAY - W4}, // Indonesia
+ {"inu", "iu,ik", INUKTITUT + W10, INUPIAK + W10}, // 1:2
+ {"is", "is", ICELANDIC + W10, FAROESE - W4},
+ {"it", "it", ITALIAN + W10, 0},
+ {"ita", "it", ITALIAN + W10, 0},
+ {"iu", "iu,ik", INUKTITUT + W10, INUPIAK + W10}, // 1:2
+ {"iw", "he", HEBREW + W10, 0},
+
+ {"ja", "ja", JAPANESE + W10, 0},
+ {"jp", "ja", JAPANESE + W10, 0}, // Japan
+ {"jpn", "ja", JAPANESE + W10, 0},
+ {"jv", "jw", JAVANESE + W10, 0},
+ {"jw", "jw", JAVANESE + W10, 0},
+
+ {"ka", "ka", GEORGIAN + W10, 0},
+ {"kc", "qu", QUECHUA + W10, 0}, // (K)Quechua
+ {"kg", "ky", KYRGYZ + W10, 0}, // Kyrgyzstan
+ {"kh", "km", KHMER + W10, 0}, // Country code Khmer (Cambodia)
+ {"kha", "kha", KHASI + W10, 0},
+ {"kk", "kk", KAZAKH + W10, 0}, // Kazakh
+ {"kl", "kl", GREENLANDIC + W10, 0},
+ {"km", "km", KHMER + W10, 0},
+ {"kn", "kn", KANNADA + W10, 0},
+ {"ko", "ko", KOREAN + W10, 0},
+ {"kor", "ko", KOREAN + W10, 0},
+ {"kr", "ko", KOREAN + W10, 0}, // Country code Korea
+ {"ks", "ks", KASHMIRI + W10, 0},
+ {"ksc", "ko", KOREAN + W10, 0}, // KSC encoding
+ {"ku", "ku", KURDISH + W10, 0},
+ {"ky", "ky", KYRGYZ + W10, 0},
+ {"kz", "kk", KAZAKH + W10, 0}, // Kazakhstan
+ {"la", "la", LATIN + W10, 0},
+ {"lao", "lo", LAOTHIAN + W10, 0}, // Laos
+
+ {"lb", "lb", LUXEMBOURGISH + W10, 0},
+ {"lg", "lg", GANDA + W10, 0},
+ {"lit", "lt", LITHUANIAN + W10, 0},
+ {"ln", "ln", LINGALA + W10, 0},
+ {"lo", "lo", LAOTHIAN + W10, 0},
+ {"lt", "lt", LITHUANIAN + W10, 0},
+ {"ltu", "lt", LITHUANIAN + W10, 0},
+ {"lv", "lv", LATVIAN + W10, 0},
+
+ {"mfe", "mfe", MAURITIAN_CREOLE + W10, 0},
+ {"mg", "mg", MALAGASY + W10, 0},
+ {"mi", "mi", MAORI + W10, 0},
+ {"mk", "mk", MACEDONIAN + W10, 0},
+ {"ml", "ml", MALAYALAM + W10, 0},
+ {"mn", "mn", MONGOLIAN + W10, 0},
+ {"mo", "mo", ROMANIAN + W10, 0},
+ {"mon", "mn", MONGOLIAN + W10, 0}, // Mongolian
+ {"mr", "mr", MARATHI + W10, HINDI - W4},
+ {"ms", "ms", MALAY + W10, INDONESIAN - W4},
+ {"mt", "mt", MALTESE + W10, 0},
+ {"mx", "es", SPANISH + W10, 0}, // Mexico
+ {"my", "my,ms", BURMESE + W10, MALAY + W10}, // Myanmar, Malaysia
+
+ {"na", "na", NAURU + W10, 0},
+ {"nb", "no", NORWEGIAN + W10, NORWEGIAN_N - W4},
+ {"ne", "ne", NEPALI + W10, 0},
+ {"nl", "nl", DUTCH + W10, 0},
+ {"nn", "nn", NORWEGIAN_N + W10, NORWEGIAN - W4},
+ {"no", "no", NORWEGIAN + W10, NORWEGIAN_N - W4},
+ {"nr", "nr", NDEBELE + W10, 0},
+ {"nso", "nso", PEDI + W10, 0},
+ {"ny", "ny", NYANJA + W10, 0},
+
+ {"oc", "oc", OCCITAN + W10, 0},
+ {"om", "om", OROMO + W10, 0},
+ {"or", "or", ORIYA + W10, 0},
+
+ {"pa", "pa,ps", PUNJABI + W10, PASHTO + W10}, // 1:2 pa-Guru ps-Arab
+ {"per", "fa", PERSIAN + W10, 0},
+ {"ph", "tl", TAGALOG + W10, 0}, // Philippines
+ {"pk", "ur", URDU + W10, 0}, // Pakistan
+ {"pl", "pl", POLISH + W10, 0},
+ {"pnb", "pa", PUNJABI + W10, 0}, // Western Punjabi
+ {"pol", "pl", POLISH + W10, 0},
+ {"por", "pt", PORTUGUESE + W10, 0},
+ {"ps", "ps", PASHTO + W10, 0},
+ {"pt", "pt", PORTUGUESE + W10, 0},
+ {"ptg", "pt", PORTUGUESE + W10, 0},
+ {"qc", "fr", FRENCH + W10, 0}, // Quebec "country" code
+ {"qu", "qu", QUECHUA + W10, 0},
+
+ {"rm", "rm", RHAETO_ROMANCE + W10, 0},
+ {"rn", "rn", RUNDI + W10, 0},
+ {"ro", "ro", ROMANIAN + W10, 0},
+ {"rs", "sr", SERBIAN + W10, 0}, // Serbia country code
+ {"ru", "ru", RUSSIAN + W10, 0},
+ {"rus", "ru", RUSSIAN + W10, 0},
+ {"rw", "rw", KINYARWANDA + W10, 0},
+
+ {"sa", "sa", SANSKRIT + W10, 0},
+ {"sco", "sco", SCOTS + W10, ENGLISH - W4},
+ {"sd", "sd", SINDHI + W10, 0},
+ {"se", "sv", SWEDISH + W10, 0},
+ {"sg", "sg", SANGO + W10, 0},
+ {"si", "si,sl", SINHALESE + W10, SLOVENIAN + W10}, // 1:2 Sinhalese, Slovinia
+ {"sk", "sk", SLOVAK + W10, CZECH - W4},
+ {"sl", "sl", SLOVENIAN + W10, 0},
+ {"slo", "sl", SLOVENIAN + W10, 0},
+ {"sm", "sm", SAMOAN + W10, 0},
+ {"sn", "sn", SHONA + W10, 0},
+ {"so", "so", SOMALI + W10, 0},
+ {"sp", "es", SPANISH + W10, 0},
+ {"sq", "sq", ALBANIAN + W10, 0},
+ {"sr", "sr", SERBIAN + W10, 0},
+ {"srb", "sr", SERBIAN + W10, 0},
+ {"srl", "sr", SERBIAN + W10, 0}, // Serbian Latin
+ {"srp", "sr", SERBIAN + W10, 0},
+ {"ss", "ss", SISWANT + W10, 0},
+ {"st", "st", SESOTHO + W10, 0},
+ {"su", "su", SUNDANESE + W10, 0},
+ {"sv", "sv", SWEDISH + W10, 0},
+ {"sve", "sv", SWEDISH + W10, 0},
+ {"sw", "sw", SWAHILI + W10, 0},
+ {"swe", "sv", SWEDISH + W10, 0},
+ {"sy", "syr", SYRIAC + W10, 0},
+ {"syr", "syr", SYRIAC + W10, 0},
+
+ {"ta", "ta", TAMIL + W10, 0},
+ {"te", "te", TELUGU + W10, 0},
+ {"tg", "tg", TAJIK + W10, 0},
+ {"th", "th", THAI + W10, 0},
+ {"ti", "ti,bo", TIGRINYA + W10, TIBETAN + W10}, // 1:2 Tigrinya, Tibet
+ {"tj", "tg", TAJIK + W10, 0}, // Tajikistan
+ {"tk", "tk", TURKMEN + W10, 0},
+ {"tl", "tl", TAGALOG + W10, 0},
+ {"tlh", "tlh", X_KLINGON + W10, 0},
+ {"tn", "tn", TSWANA + W10, 0},
+ {"to", "to", TONGA + W10, 0},
+ {"tr", "tr", TURKISH + W10, 0},
+ {"ts", "ts", TSONGA + W10, 0},
+ {"tt", "tt", TATAR + W10, 0},
+ {"tw", "ak,zhT", AKAN + W10, CHINESE_T + W10}, // 1:2 Twi => Akan, Taiwan
+ {"twi", "ak", AKAN + W10, 0}, // Twi => Akan
+
+ {"ua", "uk", UKRAINIAN + W10, 0}, // Ukraine
+ {"ug", "ug", UIGHUR + W10, 0},
+ {"uk", "uk", UKRAINIAN + W10, 0},
+ {"ur", "ur", URDU + W10, 0},
+ {"uz", "uz", UZBEK + W10, 0},
+
+ {"va", "ca", CATALAN + W10, 0}, // Valencia => Catalan
+ {"val", "ca", CATALAN + W10, 0}, // Valencia => Catalan
+ {"ve", "ve", VENDA + W10, 0},
+ {"vi", "vi", VIETNAMESE + W10, 0},
+ {"vie", "vi", VIETNAMESE + W10, 0},
+ {"vn", "vi", VIETNAMESE + W10, 0},
+ {"vo", "vo", VOLAPUK + W10, 0},
+
+ {"wo", "wo", WOLOF + W10, 0},
+
+ {"xh", "xh", XHOSA + W10, ZULU - W4},
+ {"xho", "xh", XHOSA + W10, ZULU - W4},
+
+ {"yi", "yi", YIDDISH + W10, 0},
+ {"yo", "yo", YORUBA + W10, 0},
+
+ {"za", "za", ZHUANG + W10, 0},
+ {"zh", "zh", CHINESE + W10, 0},
+ {"zht", "zhT", CHINESE_T + W10, 0},
+ {"zu", "zu", ZULU + W10, XHOSA - W4},
+};
+
+
+// Possibly map to tl:
+// -LangTags tl-Latn /7val.com/ ,bcl 2 Central Bicolano
+// -LangTags tl-Latn /7val.com/ ,ceb 6 Cebuano
+// -LangTags tl-Latn /7val.com/ ,war 1 Waray
+
+
+
+// Table to look up country TLD (no general TLD)
+// In alphabetical order for binary search
+static const int kCLDTable3Size = 181;
+static const TLDLookup kCLDTLDHintTable[kCLDTable3Size] = {
+ {"ac", JAPANESE + W2, 0},
+ {"ad", CATALAN + W4, 0},
+ {"ae", ARABIC + W4, 0},
+ {"af", PASHTO + W4, PERSIAN + W4},
+ {"ag", GERMAN + W2, 0}, // meager
+ // {"ai", 0, 0}, // meager
+ {"al", ALBANIAN + W4, 0},
+ {"am", ARMENIAN + W4, 0},
+ {"an", DUTCH + W4, 0}, // meager
+ {"ao", PORTUGUESE + W4, 0},
+ // {"aq", 0, 0}, // meager
+ {"ar", SPANISH + W4, 0},
+ // {"as", 0, 0},
+ {"at", GERMAN + W4, 0},
+ {"au", ENGLISH + W2, 0},
+ {"aw", DUTCH + W4, 0},
+ {"ax", SWEDISH + W4, 0},
+ {"az", AZERBAIJANI + W4, 0},
+
+ {"ba", BOSNIAN + W8, CROATIAN - W4},
+ // {"bb", 0, 0},
+ {"bd", BENGALI + W4, 0},
+ {"be", DUTCH + W4, FRENCH + W4},
+ {"bf", FRENCH + W4, 0},
+ {"bg", BULGARIAN + W4, 0},
+ {"bh", ARABIC + W4, 0},
+ {"bi", RUNDI + W4, FRENCH + W4},
+ {"bj", FRENCH + W4, 0},
+ {"bm", ENGLISH + W2, 0},
+ {"bn", MALAY + W4, INDONESIAN - W4},
+ {"bo", SPANISH + W4, AYMARA + W2}, // and GUARANI QUECHUA
+ {"br", PORTUGUESE + W4, 0},
+ // {"bs", 0, 0},
+ {"bt", DZONGKHA + W10, TIBETAN - W10}, // Strong presumption of Dzongha
+ {"bw", TSWANA + W4, 0},
+ {"by", BELARUSIAN + W4, 0},
+ // {"bz", 0, 0},
+
+ {"ca", FRENCH + W4, ENGLISH + W2},
+ {"cat", CATALAN + W4, 0},
+ {"cc", 0, 0},
+ {"cd", FRENCH + W4, 0},
+ {"cf", FRENCH + W4, 0},
+ {"cg", FRENCH + W4, 0},
+ {"ch", GERMAN + W4, FRENCH + W4},
+ {"ci", FRENCH + W4, 0},
+ // {"ck", 0, 0},
+ {"cl", SPANISH + W4, 0},
+ {"cm", FRENCH + W4, 0},
+ {"cn", CHINESE + W4, 0},
+ {"co", SPANISH + W4, 0},
+ {"cr", SPANISH + W4, 0},
+ {"cu", SPANISH + W4, 0},
+ {"cv", PORTUGUESE + W4, 0},
+ // {"cx", 0, 0},
+ {"cy", GREEK + W4, TURKISH + W4},
+ {"cz", CZECH + W4, SLOVAK - W4},
+
+ {"de", GERMAN + W4, 0},
+ {"dj", 0, 0},
+ {"dk", DANISH + W4, NORWEGIAN - W4},
+ {"dm", 0, 0},
+ {"do", SPANISH + W4, 0},
+ {"dz", FRENCH + W4, ARABIC + W4},
+
+ {"ec", SPANISH + W4, 0},
+ {"ee", ESTONIAN + W4, 0},
+ {"eg", ARABIC + W4, 0},
+ {"er", AFAR + W4, 0},
+ {"es", SPANISH + W4, 0},
+ {"et", AMHARIC + W4, AFAR + W4},
+
+ {"fi", FINNISH + W4, 0},
+ {"fj", FIJIAN + W4, 0},
+ // {"fk", 0, 0},
+ // {"fm", 0, 0},
+ {"fo", FAROESE + W4, ICELANDIC - W4},
+ {"fr", FRENCH + W4, 0},
+
+ {"ga", FRENCH + W4, 0},
+ {"gd", 0, 0},
+ {"ge", GEORGIAN + W4, 0},
+ {"gf", FRENCH + W4, 0},
+ // {"gg", 0, 0},
+ // {"gh", 0, 0},
+ // {"gi", 0, 0},
+ {"gl", GREENLANDIC + W4, DANISH + W4},
+ // {"gm", 0, 0},
+ {"gn", FRENCH + W4, 0},
+ // {"gp", 0, 0},
+ // {"gq", 0, 0},
+ {"gr", GREEK + W4, 0},
+ // {"gs", 0, 0},
+ {"gt", SPANISH + W4, 0},
+ // {"gu", 0, 0},
+ // {"gy", 0, 0},
+
+ {"hk", CHINESE_T + W4, 0},
+ // {"hm", 0, 0},
+ {"hn", SPANISH + W4, 0},
+ {"hr", CROATIAN + W8, BOSNIAN - W4},
+ {"ht", HAITIAN_CREOLE + W4, FRENCH + W4},
+ {"hu", HUNGARIAN + W4, 0},
+
+ {"id", INDONESIAN + W4, MALAY - W4},
+ {"ie", IRISH + W4, 0},
+ {"il", HEBREW + W4, 0},
+ {"im", MANX + W4, 0},
+ // {"in", 0, 0},
+ // {"io", 0, 0},
+ {"iq", ARABIC + W4, 0},
+ {"ir", PERSIAN + W4, 0},
+ {"is", ICELANDIC + W4, FAROESE - W4},
+ {"it", ITALIAN + W4, 0},
+
+ // {"je", 0, 0},
+ // {"jm", 0, 0},
+ {"jo", ARABIC + W4, 0},
+ {"jp", JAPANESE + W4, 0},
+
+ // {"ke", 0, 0},
+ {"kg", KYRGYZ + W4, 0},
+ {"kh", KHMER + W4, 0},
+ // {"ki", 0, 0},
+ {"km", FRENCH + W4, 0},
+ // {"kn", 0, 0},
+ {"kp", KOREAN + W4, 0},
+ {"kr", KOREAN + W4, 0},
+ {"kw", ARABIC + W4, 0},
+ // {"ky", 0, 0},
+ {"kz", KAZAKH + W4, 0},
+
+ {"la", LAOTHIAN + W4, 0},
+ {"lb", ARABIC + W4, FRENCH + W4},
+ // {"lc", 0, 0},
+ {"li", GERMAN + W4, 0},
+ {"lk", SINHALESE + W4, 0},
+ // {"lr", 0, 0},
+ {"ls", SESOTHO + W4, 0},
+ {"lt", LITHUANIAN + W4, 0},
+ {"lu", LUXEMBOURGISH + W4},
+ {"lv", LATVIAN + W4, 0},
+ {"ly", ARABIC + W4, 0},
+
+ {"ma", FRENCH + W4, 0},
+ {"mc", FRENCH + W4, 0},
+ {"md", ROMANIAN + W4, 0},
+ {"me", MONTENEGRIN + W8, SERBIAN - W4},
+ {"mg", FRENCH + W4, 0},
+ {"mk", MACEDONIAN + W4, 0},
+ {"ml", FRENCH + W4, 0},
+ {"mm", BURMESE + W4, 0},
+ {"mn", MONGOLIAN + W4, 0},
+ {"mo", CHINESE_T + W4, PORTUGUESE + W4},
+ // {"mp", 0, 0},
+ {"mq", FRENCH + W4, 0},
+ {"mr", FRENCH + W4, ARABIC + W4},
+ // {"ms", 0, 0},
+ {"mt", MALTESE + W4, 0},
+ // {"mu", 0, 0},
+ {"mv", DHIVEHI + W4, 0},
+ // {"mw", 0, 0},
+ {"mx", SPANISH + W4, 0},
+ {"my", MALAY + W4, INDONESIAN - W4},
+ {"mz", PORTUGUESE + W4, 0},
+
+ {"na", 0, 0}, // Namibia
+ {"nc", FRENCH + W4, 0},
+ {"ne", FRENCH + W4, 0},
+ {"nf", FRENCH + W4, 0},
+ // {"ng", 0, 0},
+ {"ni", SPANISH + W4, 0},
+ {"nl", DUTCH + W4, 0},
+ {"no", NORWEGIAN + W4, NORWEGIAN_N + W2},
+ {"np", NEPALI + W4, 0},
+ {"nr", NAURU + W4, 0},
+ {"nu", SWEDISH + W4, 0},
+ {"nz", MAORI + W4, ENGLISH + W2},
+
+ {"om", ARABIC + W4, 0},
+
+ {"pa", SPANISH + W4, 0},
+ {"pe", SPANISH + W4, QUECHUA + W2}, // also AYMARA
+ {"pf", FRENCH + W4, 0},
+ // {"pg", 0, 0},
+ {"ph", TAGALOG + W4, 0},
+ {"pk", URDU + W4, 0},
+ {"pl", POLISH + W4, 0},
+ // {"pn", 0, 0},
+ {"pr", SPANISH + W4, 0},
+ {"ps", ARABIC + W4, 0},
+ {"pt", PORTUGUESE + W4, 0},
+ {"py", SPANISH + W4, GUARANI + W2},
+
+ {"qa", ARABIC + W4, 0},
+
+ {"re", FRENCH + W4, 0},
+ {"ro", ROMANIAN + W4, 0},
+ {"rs", SERBIAN + W8, MONTENEGRIN - W4},
+ {"ru", RUSSIAN + W4, 0},
+ {"rw", KINYARWANDA + W4, FRENCH + W2},
+
+ {"sa", ARABIC + W4, 0},
+ // {"sb", 0, 0},
+ {"sc", SESELWA + W4, 0},
+ {"sd", ARABIC + W4, 0},
+ {"se", SWEDISH + W4, 0},
+ // {"sg", 0, 0},
+ // {"sh", 0, 0},
+ {"si", SLOVENIAN + W4, 0},
+ {"sk", SLOVAK + W4, CZECH - W4},
+ // {"sl", 0, 0},
+ {"sm", ITALIAN + W4, 0},
+ {"sn", FRENCH + W4, 0},
+ // {"sr", 0, 0},
+ {"ss", ARABIC + W4, 0}, // Presumed South Sudan TLD. dsites 2011.07.07
+ // {"st", 0, 0},
+ {"su", RUSSIAN + W4, 0},
+ {"sv", SPANISH + W4, 0},
+ {"sy", ARABIC + W4, 0},
+ // {"sz", 0, 0},
+
+ // {"tc", 0, 0},
+ {"td", FRENCH + W4, 0},
+ // {"tf", 0, 0},
+ {"tg", FRENCH + W4, 0},
+ {"th", THAI + W4, 0},
+ // Tibet has no country code (see .cn)
+ {"tj", TAJIK + W4, 0},
+ // {"tk", 0, 0},
+ // {"tl", 0, 0},
+ {"tm", TURKISH + W4, 0},
+ {"tn", FRENCH + W4, ARABIC + W4},
+ // {"to", 0, 0},
+ {"tp", JAPANESE + W4, 0},
+ {"tr", TURKISH + W4, 0},
+ // {"tt", 0, 0},
+ // {"tv", 0, 0},
+ {"tw", CHINESE_T + W4, 0},
+ {"tz", SWAHILI + W4, AKAN + W4},
+
+ {"ua", UKRAINIAN + W4, 0},
+ {"ug", GANDA + W4, 0},
+ {"uk", ENGLISH + W2, 0},
+ {"us", ENGLISH + W2, 0},
+ {"uy", SPANISH + W4, 0},
+ {"uz", UZBEK + W4, 0},
+
+ {"va", ITALIAN + W4, LATIN + W2},
+ // {"vc", 0, 0},
+ {"ve", SPANISH + W4, 0},
+ // {"vg", 0, 0},
+ // {"vi", 0, 0},
+ {"vn", VIETNAMESE + W4, 0},
+ // {"vu", 0, 0},
+
+ {"wf", FRENCH + W4, 0},
+ // {"ws", 0, 0},
+
+ {"ye", ARABIC + W4, 0},
+
+ {"za", AFRIKAANS + W4, 0},
+ // {"zm", 0, 0},
+ // {"zw", 0, 0},
+};
+
+#undef W2
+#undef W4
+#undef W6
+#undef W8
+#undef W10
+#undef W12
+
+
+
+
+
+inline void SetCLDPriorWeight(int w, OneCLDLangPrior* olp) {
+ *olp = (*olp & 0x3ff) + (w << 10);
+}
+inline void SetCLDPriorLang(Language lang, OneCLDLangPrior* olp) {
+ *olp = (*olp & ~0x3ff) + lang;
+}
+
+OneCLDLangPrior PackCLDPriorLangWeight(Language lang, int w) {
+ return (w << 10) + lang;
+}
+
+inline int MaxInt(int a, int b) {
+ return (a >= b) ? a : b;
+}
+
+// Merge in another language prior, taking max if already there
+void MergeCLDLangPriorsMax(OneCLDLangPrior olp, CLDLangPriors* lps) {
+ if (olp == 0) {return;}
+ Language target_lang = GetCLDPriorLang(olp);
+ for (int i = 0; i < lps->n; ++i) {
+ if (GetCLDPriorLang(lps->prior[i]) == target_lang) {
+ int new_weight = MaxInt(GetCLDPriorWeight(lps->prior[i]),
+ GetCLDPriorWeight(olp));
+ SetCLDPriorWeight(new_weight, &lps->prior[i]);
+ return;
+ }
+ }
+ // Not found; add it if room
+ if (lps->n >= kMaxOneCLDLangPrior) {return;}
+ lps->prior[lps->n++] = olp;
+}
+
+// Merge in another language prior, boosting 10x if already there
+void MergeCLDLangPriorsBoost(OneCLDLangPrior olp, CLDLangPriors* lps) {
+ if (olp == 0) {return;}
+ Language target_lang = GetCLDPriorLang(olp);
+ for (int i = 0; i < lps->n; ++i) {
+ if (GetCLDPriorLang(lps->prior[i]) == target_lang) {
+ int new_weight = GetCLDPriorWeight(lps->prior[i]) + 2;
+ SetCLDPriorWeight(new_weight, &lps->prior[i]);
+ return;
+ }
+ }
+ // Not found; add it if room
+ if (lps->n >= kMaxOneCLDLangPrior) {return;}
+ lps->prior[lps->n++] = olp;
+}
+
+
+// Trim language priors to no more than max_entries, keeping largest abs weights
+void TrimCLDLangPriors(int max_entries, CLDLangPriors* lps) {
+ if (lps->n <= max_entries) {return;}
+
+ // Insertion sort in-place by abs(weight)
+ for (int i = 0; i < lps->n; ++i) {
+ OneCLDLangPrior temp_olp = lps->prior[i];
+ int w = abs(GetCLDPriorWeight(temp_olp));
+ int kk = i;
+ for (; kk > 0; --kk) {
+ if (abs(GetCLDPriorWeight(lps->prior[kk - 1])) < w) {
+ // Move down and continue
+ lps->prior[kk] = lps->prior[kk - 1];
+ } else {
+ // abs(weight[kk - 1]) >= w, time to stop
+ break;
+ }
+ }
+ lps->prior[kk] = temp_olp;
+ }
+
+ lps->n = max_entries;
+}
+
+int CountCommas(const string& langtags) {
+ int commas = 0;
+ for (int i = 0; i < static_cast<int>(langtags.size()); ++i) {
+ if (langtags[i] == ',') {++commas;}
+ }
+ return commas;
+}
+
+// Binary lookup on language tag
+const LangTagLookup* DoLangTagLookup(const char* key,
+ const LangTagLookup* tbl, int tbl_size) {
+ // Key is always in range [lo..hi)
+ int lo = 0;
+ int hi = tbl_size;
+ while (lo < hi) {
+ int mid = (lo + hi) >> 1;
+ int comp = strcmp(tbl[mid].langtag, key);
+ if (comp < 0) {
+ lo = mid + 1;
+ } else if (comp > 0) {
+ hi = mid;
+ } else {
+ return &tbl[mid];
+ }
+ }
+ return NULL;
+}
+
+// Binary lookup on tld
+const TLDLookup* DoTLDLookup(const char* key,
+ const TLDLookup* tbl, int tbl_size) {
+ // Key is always in range [lo..hi)
+ int lo = 0;
+ int hi = tbl_size;
+ while (lo < hi) {
+ int mid = (lo + hi) >> 1;
+ int comp = strcmp(tbl[mid].tld, key);
+ if (comp < 0) {
+ lo = mid + 1;
+ } else if (comp > 0) {
+ hi = mid;
+ } else {
+ return &tbl[mid];
+ }
+ }
+ return NULL;
+}
+
+
+
+// Trim language tag string to canonical form for each language
+// Input is from GetLangTagsFromHtml(), already lowercased
+string TrimCLDLangTagsHint(const string& langtags) {
+ string retval;
+ if (langtags.empty()) {return retval;}
+ int commas = CountCommas(langtags);
+ if (commas > 4) {return retval;} // Ignore if too many language tags
+
+ char temp[20];
+ int pos = 0;
+ while (pos < static_cast<int>(langtags.size())) {
+ int comma = langtags.find(',', pos);
+ if (comma == string::npos) {comma = langtags.size();} // fake trailing comma
+ int len = comma - pos;
+ if (len <= 16) {
+ // Short enough to use
+ memcpy(temp, &langtags[pos], len);
+ temp[len] = '\0';
+ const LangTagLookup* entry = DoLangTagLookup(temp,
+ kCLDLangTagsHintTable1,
+ kCLDTable1Size);
+ if (entry != NULL) {
+ // First table hit
+ retval.append(entry->langcode); // may be "code1,code2"
+ retval.append(1, ',');
+ } else {
+ // Try second table with language code truncated at first hyphen
+ char* hyphen = strchr(temp, '-');
+ if (hyphen != NULL) {*hyphen = '\0';}
+ len = strlen(temp);
+ if (len <= 3) { // Short enough to use
+ entry = DoLangTagLookup(temp,
+ kCLDLangTagsHintTable2,
+ kCLDTable2Size);
+ if (entry != NULL) {
+ // Second table hit
+ retval.append(entry->langcode); // may be "code1,code2"
+ retval.append(1, ',');
+ }
+ }
+ }
+ }
+ pos = comma + 1;
+ }
+
+ // Remove trainling comma, if any
+ if (!retval.empty()) {retval.resize(retval.size() - 1);}
+ return retval;
+}
+
+
+
+//==============================================================================
+
+// Little state machine to scan insides of language attribute quoted-string.
+// Each language code is lowercased and copied to the output string. Underscore
+// is mapped to minus. Space, tab, and comma are all mapped to comma, and
+// multiple consecutive commas are removed.
+// Each language code in the output list will be followed by a single comma.
+
+// There are three states, and we start in state 1:
+// State 0: After a letter.
+// Copy all letters/minus[0], copy comma[1]; all others copy comma and skip [2]
+// State 1: Just after a comma.
+// Copy letter [0], Ignore subsequent commas[1]. minus and all others skip [2]
+// State 2: Skipping.
+// All characters except comma skip and stay in [2]. comma goes to [1]
+
+// The thing that is copied is kLangCodeRemap[c] when going to state 0,
+// and always comma when going to state 1 or 2. The design depends on copying
+// a comma at the *beginning* of skipping, and in state 2 never doing a copy.
+
+// We pack all this into 8 bits:
+// +--+---+---+
+// |78|654|321|
+// +--+---+---+
+//
+// Shift byte right by 3*state, giving [0] 321, [1] 654, [2] .78
+// where . is always zero
+// Of these 3 bits, low two are next state ss, high bit is copy bit C.
+// If C=1 and ss == 0, copy kLangCodeRemap[c], else copy a comma
+
+#define SKIP0 0
+#define SKIP1 1
+#define SKIP2 2
+#define COPY0 4 // copy kLangCodeRemap[c]
+#define COPY1 5 // copy ','
+#define COPY2 6 // copy ','
+
+// These combined actions pack three states into one byte.
+// Ninth bit must be zero, so all state 2 values must be skips.
+// state[2] state[1] state[0]
+#define LTR ((SKIP2 << 6) + (COPY0 << 3) + COPY0)
+#define MINUS ((SKIP2 << 6) + (COPY2 << 3) + COPY0)
+#define COMMA ((SKIP1 << 6) + (SKIP1 << 3) + COPY1)
+#define Bad ((SKIP2 << 6) + (COPY2 << 3) + COPY2)
+
+// Treat as letter: a-z, A-Z
+// Treat as minus: 2D minus, 5F underscore
+// Treat as comma: 09 tab, 20 space, 2C comma
+
+static const unsigned char kLangCodeAction[256] = {
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,COMMA,Bad,Bad,Bad,Bad,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+ COMMA,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,COMMA,MINUS,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+
+ Bad,LTR,LTR,LTR,LTR,LTR,LTR,LTR, LTR,LTR,LTR,LTR,LTR,LTR,LTR,LTR,
+ LTR,LTR,LTR,LTR,LTR,LTR,LTR,LTR, LTR,LTR,LTR,Bad,Bad,Bad,Bad,MINUS,
+ Bad,LTR,LTR,LTR,LTR,LTR,LTR,LTR, LTR,LTR,LTR,LTR,LTR,LTR,LTR,LTR,
+ LTR,LTR,LTR,LTR,LTR,LTR,LTR,LTR, LTR,LTR,LTR,Bad,Bad,Bad,Bad,Bad,
+
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+ Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad, Bad,Bad,Bad,Bad,Bad,Bad,Bad,Bad,
+};
+
+// This does lowercasing, maps underscore to minus, and maps tab/space to comma
+static const unsigned char kLangCodeRemap[256] = {
+ 0,0,0,0,0,0,0,0, 0,',',0,0,0,0,0,0, // 09 tab
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ ',',0,0,0,0,0,0,0, 0,0,0,0,',','-',0,0, // 20 space 2C comma 2D minus
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+
+ 0,'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',0,0,0,0,'-', // 5F underscore
+ 0,'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',0,0,0,0,0,
+
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+};
+
+#undef LTR
+#undef MINUS
+#undef COMMA
+#undef Bad
+
+#undef SKIP0
+#undef SKIP1
+#undef SKIP2
+#undef COPY0
+#undef COPY1
+#undef COPY2
+
+
+// Find opening '<' for HTML tag
+// Note: this is all somewhat insensitive to mismatched quotes
+int32 FindTagStart(const char* utf8_body, int32 pos, int32 max_pos) {
+ int i = pos;
+ // Advance i by 4 if none of the next 4 bytes are '<'
+ for (i = pos; i < (max_pos - 3); i += 4) {
+ // Fast check for any <
+ const char* p = &utf8_body[i];
+ uint32 s0123 = UNALIGNED_LOAD32(p);
+ uint32 temp = s0123 ^ 0x3c3c3c3c; // <<<<
+ if (((temp - 0x01010101) & (~temp & 0x80808080)) != 0) {
+ // At least one byte is '<'
+ break;
+ }
+ }
+ // Continue, advancing i by 1
+ for (; i < max_pos; ++i) {
+ if (utf8_body[i] == '<') {return i;}
+ }
+ return -1;
+}
+
+
+// Find closing '>' for HTML tag. Also stop on < and & (simplistic parsing)
+int32 FindTagEnd(const char* utf8_body, int32 pos, int32 max_pos) {
+ // Always outside quotes
+ for (int i = pos; i < max_pos; ++i) {
+ char c = utf8_body[i];
+ if (c == '>') {return i;}
+ if (c == '<') {return i - 1;}
+ if (c == '&') {return i - 1;}
+ }
+ return -1; // nothing found
+}
+
+// Find opening quote or apostrophe, skipping spaces
+// Note: this is all somewhat insensitive to mismatched quotes
+int32 FindQuoteStart(const char* utf8_body, int32 pos, int32 max_pos) {
+ for (int i = pos; i < max_pos; ++i) {
+ char c = utf8_body[i];
+ if (c == '"') {return i;}
+ if (c == '\'') {return i;}
+ if (c != ' ') {return -1;}
+ }
+ return -1;
+}
+
+// Find closing quot/apos. Also stop on = > < and & (simplistic parsing)
+int32 FindQuoteEnd(const char* utf8_body, int32 pos, int32 max_pos) {
+ // Always outside quotes
+ for (int i = pos; i < max_pos; ++i) {
+ char c = utf8_body[i];
+ if (c == '"') {return i;}
+ if (c == '\'') {return i;}
+ if (c == '>') {return i - 1;}
+ if (c == '=') {return i - 1;}
+ if (c == '<') {return i - 1;}
+ if (c == '&') {return i - 1;}
+ }
+ return -1; // nothing found
+}
+
+int32 FindEqualSign(const char* utf8_body, int32 pos, int32 max_pos) {
+ // Outside quotes/apostrophes loop
+ for (int i = pos; i < max_pos; ++i) {
+ char c = utf8_body[i];
+ if (c == '=') { // Found bare equal sign inside tag
+ return i;
+ } else if (c == '"') {
+ // Inside quotes loop
+ int j;
+ for (j = i + 1; j < max_pos; ++j) {
+ if (utf8_body[j] == '"') {
+ break;
+ } else if (utf8_body[j] == '\\') {
+ ++j;
+ }
+ }
+ i = j;
+ } else if (c == '\'') {
+ // Inside apostrophes loop
+ int j;
+ for (j = i + 1; j < max_pos; ++j) {
+ if (utf8_body[j] == '\'') {
+ break;
+ } else if (utf8_body[j] == '\\') {
+ ++j;
+ }
+ }
+ i = j;
+ }
+
+ }
+ return -1; // nothing found
+}
+
+// Scan backwards for case-insensitive string s in [min_pos..pos)
+// Bytes of s must already be lowercase, i.e. in [20..3f] or [60..7f]
+// Cheap lowercase. Control codes will masquerade as 20..3f
+bool FindBefore(const char* utf8_body,
+ int32 min_pos, int32 pos, const char* s) {
+ int len = strlen(s);
+ if ((pos - min_pos) < len) {return false;} // Too small to fit s
+
+ // Skip trailing spaces
+ int i = pos;
+ while ((i > (min_pos + len)) && (utf8_body[i - 1] == ' ')) {--i;}
+ i -= len;
+ if (i < min_pos) {return false;} // pos - min_pos < len, so s can't be found
+
+ const char* p = &utf8_body[i];
+ for (int j = 0; j < len; ++j) {
+ if ((p[j] | 0x20) != s[j]) {return false;} // Unequal byte
+ }
+ return true; // All bytes equal at i
+}
+
+// Scan forwards for case-insensitive string s in [pos..max_pos)
+// Bytes of s must already be lowercase, i.e. in [20..3f] or [60..7f]
+// Cheap lowercase. Control codes will masquerade as 20..3f
+// Allows but does not require quoted/apostrophe string
+bool FindAfter(const char* utf8_body,
+ int32 pos, int32 max_pos, const char* s) {
+ int len = strlen(s);
+ if ((max_pos - pos) < len) {return false;} // Too small to fit s
+
+ // Skip leading spaces, quote, apostrophe
+ int i = pos;
+ while (i < (max_pos - len)) {
+ unsigned char c = utf8_body[i];
+ if ((c == ' ') || (c == '"') || (c == '\'')) {++i;}
+ else {break;}
+ }
+
+ const char* p = &utf8_body[i];
+ for (int j = 0; j < len; ++j) {
+ if ((p[j] | 0x20) != s[j]) {return false;} // Unequal byte
+ }
+ return true; // All bytes equal
+}
+
+
+
+// Copy attribute value in [pos..max_pos)
+// pos is just after an opening quote/apostrophe and max_pos is the ending one
+// String must all be on a single line.
+// Return slightly-normalized language list, empty or ending in comma
+// Does lowercasing and removes excess punctuation/space
+string CopyOneQuotedString(const char* utf8_body,
+ int32 pos, int32 max_pos) {
+ string s;
+ int state = 1; // Front is logically just after a comma
+ for (int i = pos; i < max_pos; ++i) {
+ unsigned char c = utf8_body[i];
+ int e = kLangCodeAction[c] >> (3 * state);
+ state = e & 3; // Update to next state
+ if ((e & 4) != 0) {
+ // Copy a remapped byte if going to state 0, else copy a comma
+ if (state == 0) {
+ s.append(1, kLangCodeRemap[c]);
+ } else {
+ s.append(1, ',');
+ }
+ }
+ }
+
+ // Add final comma if needed
+ if (state == 0) {
+ s.append(1, ',');
+ }
+ return s;
+}
+
+// Find and copy attribute value: quoted string in [pos..max_pos)
+// Return slightly-normalized language list, empty or ending in comma
+string CopyQuotedString(const char* utf8_body,
+ int32 pos, int32 max_pos) {
+ int32 start_quote = FindQuoteStart(utf8_body, pos, max_pos);
+ if (start_quote < 0) {return string("");}
+ int32 end_quote = FindQuoteEnd(utf8_body, start_quote + 1, max_pos);
+ if (end_quote < 0) {return string("");}
+
+ return CopyOneQuotedString(utf8_body, start_quote + 1, end_quote);
+}
+
+// Add hints to vector of langpriors
+// Input is from GetLangTagsFromHtml(), already lowercased
+void SetCLDLangTagsHint(const string& langtags, CLDLangPriors* langpriors) {
+ if (langtags.empty()) {return;}
+ int commas = CountCommas(langtags);
+ if (commas > 4) {return;} // Ignore if too many language tags
+
+ char temp[20];
+ int pos = 0;
+ while (pos < static_cast<int>(langtags.size())) {
+ int comma = langtags.find(',', pos);
+ if (comma == string::npos) {comma = langtags.size();} // fake trailing comma
+ int len = comma - pos;
+ if (len <= 16) {
+ // Short enough to use
+ memcpy(temp, &langtags[pos], len);
+ temp[len] = '\0';
+ const LangTagLookup* entry = DoLangTagLookup(temp,
+ kCLDLangTagsHintTable1,
+ kCLDTable1Size);
+ if (entry != NULL) {
+ // First table hit
+ MergeCLDLangPriorsMax(entry->onelangprior1, langpriors);
+ MergeCLDLangPriorsMax(entry->onelangprior2, langpriors);
+ } else {
+ // Try second table with language code truncated at first hyphen
+ char* hyphen = strchr(temp, '-');
+ if (hyphen != NULL) {*hyphen = '\0';}
+ len = strlen(temp);
+ if (len <= 3) { // Short enough to use
+ entry = DoLangTagLookup(temp,
+ kCLDLangTagsHintTable2,
+ kCLDTable2Size);
+ if (entry != NULL) {
+ // Second table hit
+ MergeCLDLangPriorsMax(entry->onelangprior1, langpriors);
+ MergeCLDLangPriorsMax(entry->onelangprior2, langpriors);
+ }
+ }
+ }
+ }
+ pos = comma + 1;
+ }
+}
+
+// Add hints to vector of langpriors
+// Input is string after HTTP header Content-Language:
+void SetCLDContentLangHint(const char* contentlang, CLDLangPriors* langpriors) {
+ string langtags = CopyOneQuotedString(contentlang, 0, strlen(contentlang));
+ SetCLDLangTagsHint(langtags, langpriors);
+}
+
+// Add hints to vector of langpriors
+// Input is last element of hostname (no dot), e.g. from GetTLD()
+void SetCLDTLDHint(const char* tld, CLDLangPriors* langpriors) {
+ int len = strlen(tld);
+ if (len > 3) {return;} // Ignore if more than three letters
+ char local_tld[4];
+ strncpy(local_tld, tld, 4);
+ local_tld[3] = '\0'; // Safety move
+ // Lowercase
+ for (int i = 0; i < len; ++i) {local_tld[i] |= 0x20;}
+ const TLDLookup* entry = DoTLDLookup(local_tld,
+ kCLDTLDHintTable,
+ kCLDTable3Size);
+ if (entry != NULL) {
+ // Table hit
+ MergeCLDLangPriorsBoost(entry->onelangprior1, langpriors);
+ MergeCLDLangPriorsBoost(entry->onelangprior2, langpriors);
+ }
+}
+
+// Add hints to vector of langpriors
+// Input is from DetectEncoding()
+void SetCLDEncodingHint(Encoding enc, CLDLangPriors* langpriors) {
+ OneCLDLangPrior olp;
+ switch (enc) {
+ case CHINESE_GB:
+ case GBK:
+ case GB18030:
+ case ISO_2022_CN:
+ case HZ_GB_2312:
+ olp = PackCLDPriorLangWeight(CHINESE, kCLDPriorEncodingWeight);
+ MergeCLDLangPriorsBoost(olp, langpriors);
+ break;
+ case CHINESE_BIG5:
+ case CHINESE_BIG5_CP950:
+ case BIG5_HKSCS:
+ olp = PackCLDPriorLangWeight(CHINESE_T, kCLDPriorEncodingWeight);
+ MergeCLDLangPriorsBoost(olp, langpriors);
+ break;
+ case JAPANESE_EUC_JP:
+ case JAPANESE_SHIFT_JIS:
+ case JAPANESE_CP932:
+ case JAPANESE_JIS: // ISO-2022-JP
+ olp = PackCLDPriorLangWeight(JAPANESE, kCLDPriorEncodingWeight);
+ MergeCLDLangPriorsBoost(olp, langpriors);
+ break;
+ case KOREAN_EUC_KR:
+ case ISO_2022_KR:
+ olp = PackCLDPriorLangWeight(KOREAN, kCLDPriorEncodingWeight);
+ MergeCLDLangPriorsBoost(olp, langpriors);
+ break;
+
+ default:
+ break;
+ }
+}
+
+// Add hints to vector of langpriors
+// Input is from random source
+void SetCLDLanguageHint(Language lang, CLDLangPriors* langpriors) {
+ OneCLDLangPrior olp = PackCLDPriorLangWeight(lang, kCLDPriorLanguageWeight);
+ MergeCLDLangPriorsBoost(olp, langpriors);
+}
+
+
+// Make printable string of priors
+string DumpCLDLangPriors(const CLDLangPriors* langpriors) {
+ string retval;
+ for (int i = 0; i < langpriors->n; ++i) {
+ char temp[64];
+ sprintf(temp, "%s.%d ",
+ LanguageCode(GetCLDPriorLang(langpriors->prior[i])),
+ GetCLDPriorWeight(langpriors->prior[i]));
+ retval.append(temp);
+ }
+ return retval;
+}
+
+
+
+
+// Look for
+// <html lang="en">
+// <doc xml:lang="en">
+// <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-US">
+// <meta http-equiv="content-language" content="en-GB" />
+// <meta name="language" content="Srpski">
+// <meta name="DC.language" scheme="RFCOMMA766" content="en">
+// <SPAN id="msg1" class="info" lang='en'>
+//
+// Do not trigger on
+// <!-- lang=french ...-->
+// <font lang=postscript ...>
+// <link href="index.fr.html" hreflang="fr-FR" xml:lang="fr-FR" />
+// <META name="Author" lang="fr" content="Arnaud Le Hors">
+//
+// Stop fairly quickly on mismatched quotes
+//
+// Allowed language characters
+// a-z A-Z -_ , space\t
+// Think about: GB2312, big5, shift-jis, euc-jp, ksc euc-kr
+// zh-hans zh-TW cmn-Hani zh_cn.gb18030_CN zh-min-nan zh-yue
+// de-x-mtfrom-en zh-tw-x-mtfrom-en (machine translation)
+// GB2312 => gb
+// Big5 => big
+// zh_CN.gb18030_C => zh-cn
+//
+// Remove duplicates and extra spaces as we go
+// Lowercase as we go.
+
+// Get language tag hints from HTML body
+// Normalize: remove spaces and make lowercase comma list
+
+string GetLangTagsFromHtml(const char* utf8_body, int32 utf8_body_len,
+ int32 max_scan_bytes) {
+ string retval;
+ if (max_scan_bytes > utf8_body_len) {
+ max_scan_bytes = utf8_body_len;
+ }
+
+ int32 k = 0;
+ while (k < max_scan_bytes) {
+ int32 start_tag = FindTagStart(utf8_body, k, max_scan_bytes);
+ if (start_tag < 0) {break;}
+ int32 end_tag = FindTagEnd(utf8_body, start_tag + 1, max_scan_bytes);
+ // FindTagEnd exits on < > &
+ if (end_tag < 0) {break;}
+
+ // Skip <!--...>
+ // Skip <font ...>
+ // Skip <script ...>
+ // Skip <link ...>
+ // Skip <img ...>
+ // Skip <a ...>
+ if (FindAfter(utf8_body, start_tag + 1, end_tag, "!--") ||
+ FindAfter(utf8_body, start_tag + 1, end_tag, "font ") ||
+ FindAfter(utf8_body, start_tag + 1, end_tag, "script ") ||
+ FindAfter(utf8_body, start_tag + 1, end_tag, "link ") ||
+ FindAfter(utf8_body, start_tag + 1, end_tag, "img ") ||
+ FindAfter(utf8_body, start_tag + 1, end_tag, "a ")) {
+ k = end_tag + 1;
+ continue;
+ }
+
+ // Remember <meta ...>
+ bool in_meta = false;
+ if (FindAfter(utf8_body, start_tag + 1, end_tag, "meta ")) {
+ in_meta = true;
+ }
+
+ // Scan for each equal sign inside tag
+ bool content_is_lang = false;
+ int32 kk = start_tag + 1;
+ int32 equal_sign;
+ while ((equal_sign = FindEqualSign(utf8_body, kk, end_tag)) >= 0) {
+ // eq exits on < > &
+
+ // Look inside a meta tag
+ // <meta ... http-equiv="content-language" ...>
+ // <meta ... name="language" ...>
+ // <meta ... name="dc.language" ...>
+ if (in_meta) {
+ if (FindBefore(utf8_body, kk, equal_sign, " http-equiv") &&
+ FindAfter(utf8_body, equal_sign + 1, end_tag,
+ "content-language ")) {
+ content_is_lang = true;
+ } else if (FindBefore(utf8_body, kk, equal_sign, " name") &&
+ (FindAfter(utf8_body, equal_sign + 1, end_tag,
+ "dc.language ") ||
+ FindAfter(utf8_body, equal_sign + 1, end_tag,
+ "language "))) {
+ content_is_lang = true;
+ }
+ }
+
+ // Look inside any tag
+ // <meta ... content="lang-list" ...>
+ // <... lang="lang-list" ...>
+ // <... xml:lang="lang-list" ...>
+ if ((content_is_lang && FindBefore(utf8_body, kk, equal_sign,
+ " content")) ||
+ FindBefore(utf8_body, kk, equal_sign, " lang") ||
+ FindBefore(utf8_body, kk, equal_sign, ":lang")) {
+ string temp = CopyQuotedString(utf8_body, equal_sign + 1, end_tag);
+
+ // Append new lang tag(s) if not a duplicate
+ if (!temp.empty() && (retval.find(temp) == string::npos)) {
+ retval.append(temp);
+ }
+ }
+
+ kk = equal_sign + 1;
+ }
+ k = end_tag + 1;
+ }
+
+ // Strip last comma
+ if (retval.size() > 1) {
+ retval.erase(retval.size() - 1);
+ }
+ return retval;
+}
+
+} // End namespace CLD2
+
+//==============================================================================
diff --git a/browser/components/translation/cld2/internal/compact_lang_det_hint_code.h b/browser/components/translation/cld2/internal/compact_lang_det_hint_code.h
new file mode 100644
index 000000000..df8948a27
--- /dev/null
+++ b/browser/components/translation/cld2/internal/compact_lang_det_hint_code.h
@@ -0,0 +1,95 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#ifndef I18N_ENCODINGS_COMPACT_LANG_DET_COMPACT_LANG_DET_HINT_CODE_H__
+#define I18N_ENCODINGS_COMPACT_LANG_DET_COMPACT_LANG_DET_HINT_CODE_H__
+
+
+#include <string>
+#include "integral_types.h"
+#include "lang_script.h"
+#include "../public/encodings.h"
+
+namespace CLD2 {
+
+// Packed <Language, weight>, weight in [-32..31] (powers of 2**1.6 ~=3.03)
+// Full language in bottom 10 bits, weight in top 6 bits
+typedef int16 OneCLDLangPrior;
+
+const int kMaxOneCLDLangPrior = 14;
+typedef struct {
+ int32 n;
+ OneCLDLangPrior prior[kMaxOneCLDLangPrior];
+} CLDLangPriors;
+
+// Reading exposed here; setting hidden in .cc
+inline int GetCLDPriorWeight(OneCLDLangPrior olp) {
+ return olp >> 10;
+}
+inline Language GetCLDPriorLang(OneCLDLangPrior olp) {
+ return static_cast<Language>(olp & 0x3ff);
+}
+
+inline int32 GetCLDLangPriorCount(CLDLangPriors* lps) {
+ return lps->n;
+}
+
+inline void InitCLDLangPriors(CLDLangPriors* lps) {
+ lps->n = 0;
+}
+
+// Trim language priors to no more than max_entries, keeping largest abs weights
+void TrimCLDLangPriors(int max_entries, CLDLangPriors* lps);
+
+// Trim language tag string to canonical form for each language
+// Input is from GetLangTagsFromHtml(), already lowercased
+std::string TrimCLDLangTagsHint(const std::string& langtags);
+
+// Add hints to vector of langpriors
+// Input is from GetLangTagsFromHtml(), already lowercased
+void SetCLDLangTagsHint(const std::string& langtags, CLDLangPriors* langpriors);
+
+// Add hints to vector of langpriors
+// Input is from HTTP content-language
+void SetCLDContentLangHint(const char* contentlang, CLDLangPriors* langpriors);
+
+// Add hints to vector of langpriors
+// Input is from GetTLD(), already lowercased
+void SetCLDTLDHint(const char* tld, CLDLangPriors* langpriors);
+
+// Add hints to vector of langpriors
+// Input is from DetectEncoding()
+void SetCLDEncodingHint(Encoding enc, CLDLangPriors* langpriors);
+
+// Add hints to vector of langpriors
+// Input is from random source
+void SetCLDLanguageHint(Language lang, CLDLangPriors* langpriors);
+
+// Make printable string of priors
+std::string DumpCLDLangPriors(const CLDLangPriors* langpriors);
+
+
+// Get language tag hints from HTML body
+// Normalize: remove spaces and make lowercase comma list
+std::string GetLangTagsFromHtml(const char* utf8_body, int32 utf8_body_len,
+ int32 max_scan_bytes);
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_COMPACT_LANG_DET_COMPACT_LANG_DET_HINT_CODE_H__
+
diff --git a/browser/components/translation/cld2/internal/compact_lang_det_impl.cc b/browser/components/translation/cld2/internal/compact_lang_det_impl.cc
new file mode 100644
index 000000000..e01fdcef4
--- /dev/null
+++ b/browser/components/translation/cld2/internal/compact_lang_det_impl.cc
@@ -0,0 +1,2039 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+// Updated 2014.01 for dual table lookup
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+
+#include "cldutil.h"
+#include "debug.h"
+#include "integral_types.h"
+#include "lang_script.h"
+#include "utf8statetable.h"
+
+#ifdef CLD2_DYNAMIC_MODE
+#include "cld2_dynamic_data.h"
+#include "cld2_dynamic_data_loader.h"
+#endif
+#include "cld2tablesummary.h"
+#include "compact_lang_det_impl.h"
+#include "compact_lang_det_hint_code.h"
+#include "getonescriptspan.h"
+#include "tote.h"
+
+
+namespace CLD2 {
+
+using namespace std;
+
+// Linker supplies the right tables, From files
+// cld_generated_cjk_uni_prop_80.cc cld2_generated_cjk_compatible.cc
+// cld_generated_cjk_delta_bi_32.cc generated_distinct_bi_0.cc
+// cld2_generated_quad*.cc cld2_generated_deltaocta*.cc
+// cld2_generated_distinctocta*.cc
+// cld_generated_score_quad_octa_1024_256.cc
+
+// 2014.01 Now implementing quadgram dual lookup tables, to allow main table
+// sizes that are 1/3/5 times a power of two, instead of just powers of two.
+// Gives more flexibility of total footprint for CLD2.
+
+extern const int kLanguageToPLangSize;
+extern const int kCloseSetSize;
+
+extern const UTF8PropObj cld_generated_CjkUni_obj;
+extern const CLD2TableSummary kCjkCompat_obj;
+extern const CLD2TableSummary kCjkDeltaBi_obj;
+extern const CLD2TableSummary kDistinctBiTable_obj;
+extern const CLD2TableSummary kQuad_obj;
+extern const CLD2TableSummary kQuad_obj2; // Dual lookup tables
+extern const CLD2TableSummary kDeltaOcta_obj;
+extern const CLD2TableSummary kDistinctOcta_obj;
+extern const short kAvgDeltaOctaScore[];
+
+#ifdef CLD2_DYNAMIC_MODE
+ // CLD2_DYNAMIC_MODE is defined:
+ // Data will be read from an mmap opened at runtime.
+ static ScoringTables kScoringtables = {
+ NULL, //&cld_generated_CjkUni_obj,
+ NULL, //&kCjkCompat_obj,
+ NULL, //&kCjkDeltaBi_obj,
+ NULL, //&kDistinctBiTable_obj,
+ NULL, //&kQuad_obj,
+ NULL, //&kQuad_obj2,
+ NULL, //&kDeltaOcta_obj,
+ NULL, //&kDistinctOcta_obj,
+ NULL, //kAvgDeltaOctaScore,
+ };
+ static bool dynamicDataLoaded = false;
+ static ScoringTables* dynamicTables = NULL;
+ static void* mmapAddress = NULL;
+ static int mmapLength = 0;
+
+ bool isDataLoaded() { return dynamicDataLoaded; }
+
+ void loadData(const char* fileName) {
+ if (isDataLoaded()) {
+ unloadData();
+ }
+ dynamicTables = CLD2DynamicDataLoader::loadDataFile(fileName, &mmapAddress, &mmapLength);
+ kScoringtables = *dynamicTables;
+ dynamicDataLoaded = true;
+ };
+
+ void unloadData() {
+ if (!dynamicDataLoaded) return;
+ dynamicDataLoaded = false;
+ // unloading will null all the pointers out.
+ CLD2DynamicDataLoader::unloadData(&dynamicTables, &mmapAddress, &mmapLength);
+ }
+#else
+ // This initializes kScoringtables.quadgram_obj etc.
+ static const ScoringTables kScoringtables = {
+ &cld_generated_CjkUni_obj,
+ &kCjkCompat_obj,
+ &kCjkDeltaBi_obj,
+ &kDistinctBiTable_obj,
+
+ &kQuad_obj,
+ &kQuad_obj2, // Dual lookup tables
+ &kDeltaOcta_obj,
+ &kDistinctOcta_obj,
+
+ kAvgDeltaOctaScore,
+ };
+#endif // #ifdef CLD2_DYNAMIC_MODE
+
+
+static const bool FLAGS_cld_no_minimum_bytes = false;
+static const bool FLAGS_cld_forcewords = true;
+static const bool FLAGS_cld_showme = false;
+static const bool FLAGS_cld_echotext = true;
+static const int32 FLAGS_cld_textlimit = 160;
+static const int32 FLAGS_cld_smoothwidth = 20;
+static const bool FLAGS_cld_2011_hints = true;
+static const int32 FLAGS_cld_max_lang_tag_scan_kb = 8;
+
+static const bool FLAGS_dbgscore = false;
+
+
+static const int kLangHintInitial = 12; // Boost language by N initially
+static const int kLangHintBoost = 12; // Boost language by N/16 per quadgram
+
+static const int kShortSpanThresh = 32; // Bytes
+static const int kMaxSecondChanceLen = 1024; // Look at first 1K of short spans
+
+static const int kCheapSqueezeTestThresh = 4096; // Only look for squeezing
+ // after this many text bytes
+static const int kCheapSqueezeTestLen = 256; // Bytes to test to trigger sqz
+static const int kSpacesTriggerPercent = 25; // Trigger sqz if >=25% spaces
+static const int kPredictTriggerPercent = 67; // Trigger sqz if >=67% predicted
+
+static const int kChunksizeDefault = 48; // Squeeze 48-byte chunks
+static const int kSpacesThreshPercent = 25; // Squeeze if >=25% spaces
+static const int kPredictThreshPercent = 40; // Squeeze if >=40% predicted
+
+static const int kMaxSpaceScan = 32; // Bytes
+
+static const int kGoodLang1Percent = 70;
+static const int kGoodLang1and2Percent = 93;
+static const int kShortTextThresh = 256; // Bytes
+
+static const int kMinChunkSizeQuads = 4; // Chunk is at least four quads
+static const int kMaxChunkSizeQuads = 1024; // Chunk is at most 1K quads
+
+static const int kDefaultWordSpan = 256; // Scan at least this many initial
+ // bytes with word scoring
+static const int kReallyBigWordSpan = 9999999; // Forces word scoring all text
+
+static const int kMinReliableSeq = 50; // Record in seq if >= 50% reliable
+
+static const int kPredictionTableSize = 4096; // Must be exactly 4096 for
+ // cheap compressor
+
+static const int kNonEnBoilerplateMinPercent = 17; // <this => no second
+static const int kNonFIGSBoilerplateMinPercent = 20; // <this => no second
+static const int kGoodFirstMinPercent = 26; // <this => UNK
+static const int kGoodFirstReliableMinPercent = 51; // <this => unreli
+static const int kIgnoreMaxPercent = 20; // >this => unreli
+static const int kKeepMinPercent = 2; // <this => unreli
+
+
+
+// Statistically closest language, based on quadgram table
+// Those that are far from other languges map to UNKNOWN_LANGUAGE
+// Subscripted by Language
+//
+// From lang_correlation.txt and hand-edits
+// sed 's/^\([^ ]*\) \([^ ]*\) coef=0\.\(..\).*$/
+// (\3 >= kMinCorrPercent) ? \2 : UNKNOWN_LANGUAGE,
+// \/\/ \1/' lang_correlation.txt >/tmp/closest_lang_decl.txt
+//
+static const int kMinCorrPercent = 24; // Pick off how close you want
+ // 24 catches PERSIAN <== ARABIC
+ // but not SPANISH <== PORTUGESE
+static Language Unknown = UNKNOWN_LANGUAGE;
+
+// Suspect idea
+// Subscripted by Language
+static const Language kClosestAltLanguage[] = {
+ (28 >= kMinCorrPercent) ? SCOTS : UNKNOWN_LANGUAGE, // ENGLISH
+ (36 >= kMinCorrPercent) ? NORWEGIAN : UNKNOWN_LANGUAGE, // DANISH
+ (31 >= kMinCorrPercent) ? AFRIKAANS : UNKNOWN_LANGUAGE, // DUTCH
+ (15 >= kMinCorrPercent) ? ESTONIAN : UNKNOWN_LANGUAGE, // FINNISH
+ (11 >= kMinCorrPercent) ? OCCITAN : UNKNOWN_LANGUAGE, // FRENCH
+ (17 >= kMinCorrPercent) ? LUXEMBOURGISH : UNKNOWN_LANGUAGE, // GERMAN
+ (27 >= kMinCorrPercent) ? YIDDISH : UNKNOWN_LANGUAGE, // HEBREW
+ (16 >= kMinCorrPercent) ? CORSICAN : UNKNOWN_LANGUAGE, // ITALIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // Japanese
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // Korean
+ (41 >= kMinCorrPercent) ? NORWEGIAN_N : UNKNOWN_LANGUAGE, // NORWEGIAN
+ ( 5 >= kMinCorrPercent) ? SLOVAK : UNKNOWN_LANGUAGE, // POLISH
+ (23 >= kMinCorrPercent) ? SPANISH : UNKNOWN_LANGUAGE, // PORTUGUESE
+ (33 >= kMinCorrPercent) ? BULGARIAN : UNKNOWN_LANGUAGE, // RUSSIAN
+ (28 >= kMinCorrPercent) ? GALICIAN : UNKNOWN_LANGUAGE, // SPANISH
+ (17 >= kMinCorrPercent) ? NORWEGIAN : UNKNOWN_LANGUAGE, // SWEDISH
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // Chinese
+ (42 >= kMinCorrPercent) ? SLOVAK : UNKNOWN_LANGUAGE, // CZECH
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // GREEK
+ (35 >= kMinCorrPercent) ? FAROESE : UNKNOWN_LANGUAGE, // ICELANDIC
+ ( 7 >= kMinCorrPercent) ? LITHUANIAN : UNKNOWN_LANGUAGE, // LATVIAN
+ ( 7 >= kMinCorrPercent) ? LATVIAN : UNKNOWN_LANGUAGE, // LITHUANIAN
+ ( 4 >= kMinCorrPercent) ? LATIN : UNKNOWN_LANGUAGE, // ROMANIAN
+ ( 4 >= kMinCorrPercent) ? SLOVAK : UNKNOWN_LANGUAGE, // HUNGARIAN
+ (15 >= kMinCorrPercent) ? FINNISH : UNKNOWN_LANGUAGE, // ESTONIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // Ignore
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // Unknown
+ (33 >= kMinCorrPercent) ? RUSSIAN : UNKNOWN_LANGUAGE, // BULGARIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // CROATIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // SERBIAN
+ (24 >= kMinCorrPercent) ? SCOTS_GAELIC : UNKNOWN_LANGUAGE, // IRISH
+ (28 >= kMinCorrPercent) ? SPANISH : UNKNOWN_LANGUAGE, // GALICIAN
+ ( 8 >= kMinCorrPercent) ? INDONESIAN : UNKNOWN_LANGUAGE, // TAGALOG
+ (29 >= kMinCorrPercent) ? AZERBAIJANI : UNKNOWN_LANGUAGE, // TURKISH
+ (28 >= kMinCorrPercent) ? RUSSIAN : UNKNOWN_LANGUAGE, // UKRAINIAN
+ (37 >= kMinCorrPercent) ? MARATHI : UNKNOWN_LANGUAGE, // HINDI
+ (29 >= kMinCorrPercent) ? BULGARIAN : UNKNOWN_LANGUAGE, // MACEDONIAN
+ (14 >= kMinCorrPercent) ? ASSAMESE : UNKNOWN_LANGUAGE, // BENGALI
+ (46 >= kMinCorrPercent) ? MALAY : UNKNOWN_LANGUAGE, // INDONESIAN
+ ( 9 >= kMinCorrPercent) ? INTERLINGUA : UNKNOWN_LANGUAGE, // LATIN
+ (46 >= kMinCorrPercent) ? INDONESIAN : UNKNOWN_LANGUAGE, // MALAY
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // MALAYALAM
+ ( 4 >= kMinCorrPercent) ? BRETON : UNKNOWN_LANGUAGE, // WELSH
+ ( 8 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE, // NEPALI
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // TELUGU
+ ( 3 >= kMinCorrPercent) ? ESPERANTO : UNKNOWN_LANGUAGE, // ALBANIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // TAMIL
+ (22 >= kMinCorrPercent) ? UKRAINIAN : UNKNOWN_LANGUAGE, // BELARUSIAN
+ (15 >= kMinCorrPercent) ? SUNDANESE : UNKNOWN_LANGUAGE, // JAVANESE
+ (19 >= kMinCorrPercent) ? CATALAN : UNKNOWN_LANGUAGE, // OCCITAN
+ (27 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE, // URDU
+ (36 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE, // BIHARI
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // GUJARATI
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // THAI
+ (24 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE, // ARABIC
+ (19 >= kMinCorrPercent) ? OCCITAN : UNKNOWN_LANGUAGE, // CATALAN
+ ( 4 >= kMinCorrPercent) ? LATIN : UNKNOWN_LANGUAGE, // ESPERANTO
+ ( 3 >= kMinCorrPercent) ? GERMAN : UNKNOWN_LANGUAGE, // BASQUE
+ ( 9 >= kMinCorrPercent) ? LATIN : UNKNOWN_LANGUAGE, // INTERLINGUA
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // KANNADA
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // PUNJABI
+ (24 >= kMinCorrPercent) ? IRISH : UNKNOWN_LANGUAGE, // SCOTS_GAELIC
+ ( 7 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE, // SWAHILI
+ (28 >= kMinCorrPercent) ? SERBIAN : UNKNOWN_LANGUAGE, // SLOVENIAN
+ (37 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE, // MARATHI
+ ( 3 >= kMinCorrPercent) ? ITALIAN : UNKNOWN_LANGUAGE, // MALTESE
+ ( 1 >= kMinCorrPercent) ? YORUBA : UNKNOWN_LANGUAGE, // VIETNAMESE
+ (15 >= kMinCorrPercent) ? DUTCH : UNKNOWN_LANGUAGE, // FRISIAN
+ (42 >= kMinCorrPercent) ? CZECH : UNKNOWN_LANGUAGE, // SLOVAK
+ // Original ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // ChineseT
+ (24 >= kMinCorrPercent) ? CHINESE : UNKNOWN_LANGUAGE, // ChineseT
+ (35 >= kMinCorrPercent) ? ICELANDIC : UNKNOWN_LANGUAGE, // FAROESE
+ (15 >= kMinCorrPercent) ? JAVANESE : UNKNOWN_LANGUAGE, // SUNDANESE
+ (17 >= kMinCorrPercent) ? TAJIK : UNKNOWN_LANGUAGE, // UZBEK
+ ( 7 >= kMinCorrPercent) ? TIGRINYA : UNKNOWN_LANGUAGE, // AMHARIC
+ (29 >= kMinCorrPercent) ? TURKISH : UNKNOWN_LANGUAGE, // AZERBAIJANI
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // GEORGIAN
+ ( 7 >= kMinCorrPercent) ? AMHARIC : UNKNOWN_LANGUAGE, // TIGRINYA
+ (27 >= kMinCorrPercent) ? URDU : UNKNOWN_LANGUAGE, // PERSIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // BOSNIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // SINHALESE
+ (41 >= kMinCorrPercent) ? NORWEGIAN : UNKNOWN_LANGUAGE, // NORWEGIAN_N
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // PORTUGUESE_P
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // PORTUGUESE_B
+ (37 >= kMinCorrPercent) ? ZULU : UNKNOWN_LANGUAGE, // XHOSA
+ (37 >= kMinCorrPercent) ? XHOSA : UNKNOWN_LANGUAGE, // ZULU
+ ( 2 >= kMinCorrPercent) ? SPANISH : UNKNOWN_LANGUAGE, // GUARANI
+ (29 >= kMinCorrPercent) ? TSWANA : UNKNOWN_LANGUAGE, // SESOTHO
+ ( 7 >= kMinCorrPercent) ? TURKISH : UNKNOWN_LANGUAGE, // TURKMEN
+ ( 8 >= kMinCorrPercent) ? KAZAKH : UNKNOWN_LANGUAGE, // KYRGYZ
+ ( 5 >= kMinCorrPercent) ? FRENCH : UNKNOWN_LANGUAGE, // BRETON
+ ( 3 >= kMinCorrPercent) ? GANDA : UNKNOWN_LANGUAGE, // TWI
+ (27 >= kMinCorrPercent) ? HEBREW : UNKNOWN_LANGUAGE, // YIDDISH
+ (28 >= kMinCorrPercent) ? SLOVENIAN : UNKNOWN_LANGUAGE, // SERBO_CROATIAN
+ (12 >= kMinCorrPercent) ? OROMO : UNKNOWN_LANGUAGE, // SOMALI
+ ( 9 >= kMinCorrPercent) ? UZBEK : UNKNOWN_LANGUAGE, // UIGHUR
+ (15 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE, // KURDISH
+ ( 6 >= kMinCorrPercent) ? KYRGYZ : UNKNOWN_LANGUAGE, // MONGOLIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // ARMENIAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // LAOTHIAN
+ ( 8 >= kMinCorrPercent) ? URDU : UNKNOWN_LANGUAGE, // SINDHI
+ (10 >= kMinCorrPercent) ? ITALIAN : UNKNOWN_LANGUAGE, // RHAETO_ROMANCE
+ (31 >= kMinCorrPercent) ? DUTCH : UNKNOWN_LANGUAGE, // AFRIKAANS
+ (17 >= kMinCorrPercent) ? GERMAN : UNKNOWN_LANGUAGE, // LUXEMBOURGISH
+ ( 2 >= kMinCorrPercent) ? SCOTS : UNKNOWN_LANGUAGE, // BURMESE
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // KHMER
+ (45 >= kMinCorrPercent) ? DZONGKHA : UNKNOWN_LANGUAGE, // TIBETAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // DHIVEHI
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // CHEROKEE
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // SYRIAC
+ ( 8 >= kMinCorrPercent) ? DUTCH : UNKNOWN_LANGUAGE, // LIMBU
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // ORIYA
+ (14 >= kMinCorrPercent) ? BENGALI : UNKNOWN_LANGUAGE, // ASSAMESE
+ (16 >= kMinCorrPercent) ? ITALIAN : UNKNOWN_LANGUAGE, // CORSICAN
+ ( 5 >= kMinCorrPercent) ? INTERLINGUA : UNKNOWN_LANGUAGE, // INTERLINGUE
+ ( 8 >= kMinCorrPercent) ? KYRGYZ : UNKNOWN_LANGUAGE, // KAZAKH
+ ( 4 >= kMinCorrPercent) ? SWAHILI : UNKNOWN_LANGUAGE, // LINGALA
+ (11 >= kMinCorrPercent) ? RUSSIAN : UNKNOWN_LANGUAGE, // MOLDAVIAN
+ (19 >= kMinCorrPercent) ? PERSIAN : UNKNOWN_LANGUAGE, // PASHTO
+ ( 5 >= kMinCorrPercent) ? AYMARA : UNKNOWN_LANGUAGE, // QUECHUA
+ ( 5 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE, // SHONA
+ (17 >= kMinCorrPercent) ? UZBEK : UNKNOWN_LANGUAGE, // TAJIK
+ (13 >= kMinCorrPercent) ? BASHKIR : UNKNOWN_LANGUAGE, // TATAR
+ (11 >= kMinCorrPercent) ? SAMOAN : UNKNOWN_LANGUAGE, // TONGA
+ ( 2 >= kMinCorrPercent) ? TWI : UNKNOWN_LANGUAGE, // YORUBA
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // CREOLES_AND_PIDGINS_ENGLISH_BASED
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // CREOLES_AND_PIDGINS_FRENCH_BASED
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // CREOLES_AND_PIDGINS_PORTUGUESE_BASED
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // CREOLES_AND_PIDGINS_OTHER
+ ( 6 >= kMinCorrPercent) ? TONGA : UNKNOWN_LANGUAGE, // MAORI
+ ( 3 >= kMinCorrPercent) ? OROMO : UNKNOWN_LANGUAGE, // WOLOF
+ ( 1 >= kMinCorrPercent) ? MONGOLIAN : UNKNOWN_LANGUAGE, // ABKHAZIAN
+ ( 8 >= kMinCorrPercent) ? SOMALI : UNKNOWN_LANGUAGE, // AFAR
+ ( 5 >= kMinCorrPercent) ? QUECHUA : UNKNOWN_LANGUAGE, // AYMARA
+ (13 >= kMinCorrPercent) ? TATAR : UNKNOWN_LANGUAGE, // BASHKIR
+ ( 3 >= kMinCorrPercent) ? ENGLISH : UNKNOWN_LANGUAGE, // BISLAMA
+ (45 >= kMinCorrPercent) ? TIBETAN : UNKNOWN_LANGUAGE, // DZONGKHA
+ ( 4 >= kMinCorrPercent) ? TONGA : UNKNOWN_LANGUAGE, // FIJIAN
+ ( 7 >= kMinCorrPercent) ? INUPIAK : UNKNOWN_LANGUAGE, // GREENLANDIC
+ ( 3 >= kMinCorrPercent) ? AFAR : UNKNOWN_LANGUAGE, // HAUSA
+ ( 3 >= kMinCorrPercent) ? OCCITAN : UNKNOWN_LANGUAGE, // HAITIAN_CREOLE
+ ( 7 >= kMinCorrPercent) ? GREENLANDIC : UNKNOWN_LANGUAGE, // INUPIAK
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // INUKTITUT
+ ( 4 >= kMinCorrPercent) ? HINDI : UNKNOWN_LANGUAGE, // KASHMIRI
+ (30 >= kMinCorrPercent) ? RUNDI : UNKNOWN_LANGUAGE, // KINYARWANDA
+ ( 2 >= kMinCorrPercent) ? TAGALOG : UNKNOWN_LANGUAGE, // MALAGASY
+ (17 >= kMinCorrPercent) ? GERMAN : UNKNOWN_LANGUAGE, // NAURU
+ (12 >= kMinCorrPercent) ? SOMALI : UNKNOWN_LANGUAGE, // OROMO
+ (30 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE, // RUNDI
+ (11 >= kMinCorrPercent) ? TONGA : UNKNOWN_LANGUAGE, // SAMOAN
+ ( 1 >= kMinCorrPercent) ? LINGALA : UNKNOWN_LANGUAGE, // SANGO
+ (32 >= kMinCorrPercent) ? MARATHI : UNKNOWN_LANGUAGE, // SANSKRIT
+ (16 >= kMinCorrPercent) ? ZULU : UNKNOWN_LANGUAGE, // SISWANT
+ ( 5 >= kMinCorrPercent) ? SISWANT : UNKNOWN_LANGUAGE, // TSONGA
+ (29 >= kMinCorrPercent) ? SESOTHO : UNKNOWN_LANGUAGE, // TSWANA
+ ( 2 >= kMinCorrPercent) ? ESTONIAN : UNKNOWN_LANGUAGE, // VOLAPUK
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // ZHUANG
+ ( 1 >= kMinCorrPercent) ? MALAY : UNKNOWN_LANGUAGE, // KHASI
+ (28 >= kMinCorrPercent) ? ENGLISH : UNKNOWN_LANGUAGE, // SCOTS
+ (15 >= kMinCorrPercent) ? KINYARWANDA : UNKNOWN_LANGUAGE, // GANDA
+ ( 7 >= kMinCorrPercent) ? ENGLISH : UNKNOWN_LANGUAGE, // MANX
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // MONTENEGRIN
+
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // AKAN
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // IGBO
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // MAURITIAN_CREOLE
+ ( 0 >= kMinCorrPercent) ? Unknown : UNKNOWN_LANGUAGE, // HAWAIIAN
+};
+
+// COMPILE_ASSERT(arraysize(kClosestAltLanguage) == NUM_LANGUAGES,
+// kClosestAltLanguage_has_incorrect_size);
+
+
+inline bool FlagFinish(int flags) {return (flags & kCLDFlagFinish) != 0;}
+inline bool FlagSqueeze(int flags) {return (flags & kCLDFlagSqueeze) != 0;}
+inline bool FlagRepeats(int flags) {return (flags & kCLDFlagRepeats) != 0;}
+inline bool FlagTop40(int flags) {return (flags & kCLDFlagTop40) != 0;}
+inline bool FlagShort(int flags) {return (flags & kCLDFlagShort) != 0;}
+inline bool FlagHint(int flags) {return (flags & kCLDFlagHint) != 0;}
+inline bool FlagUseWords(int flags) {return (flags & kCLDFlagUseWords) != 0;}
+
+
+ // Defines Top40 packed languages
+
+ // Google top 40 languages
+ //
+ // Tier 0/1 Language enum list (16)
+ // ENGLISH, /*no en_GB,*/ FRENCH, ITALIAN, GERMAN, SPANISH, // E - FIGS
+ // DUTCH, CHINESE, CHINESE_T, JAPANESE, KOREAN,
+ // PORTUGUESE, RUSSIAN, POLISH, TURKISH, THAI,
+ // ARABIC,
+ //
+ // Tier 2 Language enum list (22)
+ // SWEDISH, FINNISH, DANISH, /*no pt-PT,*/ ROMANIAN, HUNGARIAN,
+ // HEBREW, INDONESIAN, CZECH, GREEK, NORWEGIAN,
+ // VIETNAMESE, BULGARIAN, CROATIAN, LITHUANIAN, SLOVAK,
+ // TAGALOG, SLOVENIAN, SERBIAN, CATALAN, LATVIAN,
+ // UKRAINIAN, HINDI,
+ //
+ // use SERBO_CROATIAN instead of BOSNIAN, SERBIAN, CROATIAN, MONTENEGRIN(21)
+ //
+ // Include IgnoreMe (TG_UNKNOWN_LANGUAGE, 25+1) as a top 40
+
+
+void DemoteNotTop40(Tote* chunk_tote, uint16 psplus_one) {
+ // REVISIT
+}
+
+void PrintText(FILE* f, Language cur_lang, const string& temp) {
+ if (temp.size() == 0) {return;}
+ fprintf(f, "PrintText[%s]%s<br>\n", LanguageName(cur_lang), temp.c_str());
+}
+
+
+//------------------------------------------------------------------------------
+// For --cld_html debugging output. Not thread safe
+//------------------------------------------------------------------------------
+static Language prior_lang = UNKNOWN_LANGUAGE;
+static bool prior_unreliable = false;
+
+//------------------------------------------------------------------------------
+// End For --cld_html debugging output
+//------------------------------------------------------------------------------
+
+
+// Backscan to word boundary, returning how many bytes n to go back
+// so that src - n is non-space ans src - n - 1 is space.
+// If not found in kMaxSpaceScan bytes, return 0..3 to a clean UTF-8 boundary
+int BackscanToSpace(const char* src, int limit) {
+ int n = 0;
+ limit = minint(limit, kMaxSpaceScan);
+ while (n < limit) {
+ if (src[-n - 1] == ' ') {return n;} // We are at _X
+ ++n;
+ }
+ n = 0;
+ while (n < limit) {
+ if ((src[-n] & 0xc0) != 0x80) {return n;} // We are at char begin
+ ++n;
+ }
+ return 0;
+}
+
+// Forwardscan to word boundary, returning how many bytes n to go forward
+// so that src + n is non-space ans src + n - 1 is space.
+// If not found in kMaxSpaceScan bytes, return 0..3 to a clean UTF-8 boundary
+int ForwardscanToSpace(const char* src, int limit) {
+ int n = 0;
+ limit = minint(limit, kMaxSpaceScan);
+ while (n < limit) {
+ if (src[n] == ' ') {return n + 1;} // We are at _X
+ ++n;
+ }
+ n = 0;
+ while (n < limit) {
+ if ((src[n] & 0xc0) != 0x80) {return n;} // We are at char begin
+ ++n;
+ }
+ return 0;
+}
+
+
+// This uses a cheap predictor to get a measure of compression, and
+// hence a measure of repetitiveness. It works on complete UTF-8 characters
+// instead of bytes, because three-byte UTF-8 Indic, etc. text compress highly
+// all the time when done with a byte-based count. Sigh.
+//
+// To allow running prediction across multiple chunks, caller passes in current
+// 12-bit hash value and int[4096] prediction table. Caller inits these to 0.
+//
+// Returns the number of *bytes* correctly predicted, increments by 1..4 for
+// each correctly-predicted character.
+//
+// NOTE: Overruns by up to three bytes. Not a problem with valid UTF-8 text
+//
+
+// TODO(dsites) make this use just one byte per UTF-8 char and incr by charlen
+
+int CountPredictedBytes(const char* isrc, int src_len, int* hash, int* tbl) {
+ int p_count = 0;
+ const uint8* src = reinterpret_cast<const uint8*>(isrc);
+ const uint8* srclimit = src + src_len;
+ int local_hash = *hash;
+
+ while (src < srclimit) {
+ int c = src[0];
+ int incr = 1;
+
+ // Pick up one char and length
+ if (c < 0xc0) {
+ // One-byte or continuation byte: 00xxxxxx 01xxxxxx 10xxxxxx
+ // Do nothing more
+ } else if ((c & 0xe0) == 0xc0) {
+ // Two-byte
+ c = (c << 8) | src[1];
+ incr = 2;
+ } else if ((c & 0xf0) == 0xe0) {
+ // Three-byte
+ c = (c << 16) | (src[1] << 8) | src[2];
+ incr = 3;
+ } else {
+ // Four-byte
+ c = (c << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
+ incr = 4;
+ }
+ src += incr;
+
+ int p = tbl[local_hash]; // Prediction
+ tbl[local_hash] = c; // Update prediction
+ if (c == p) {
+ p_count += incr; // Count bytes of good predictions
+ }
+
+ local_hash = ((local_hash << 4) ^ c) & 0xfff;
+ }
+ *hash = local_hash;
+ return p_count;
+}
+
+
+
+// Counts number of spaces; a little faster than one-at-a-time
+// Doesn't count odd bytes at end
+int CountSpaces4(const char* src, int src_len) {
+ int s_count = 0;
+ for (int i = 0; i < (src_len & ~3); i += 4) {
+ s_count += (src[i] == ' ');
+ s_count += (src[i+1] == ' ');
+ s_count += (src[i+2] == ' ');
+ s_count += (src[i+3] == ' ');
+ }
+ return s_count;
+}
+
+
+// Remove words of text that have more than half their letters predicted
+// correctly by our cheap predictor, moving the remaining words in-place
+// to the front of the input buffer.
+//
+// To allow running prediction across multiple chunks, caller passes in current
+// 12-bit hash value and int[4096] prediction table. Caller inits these to 0.
+//
+// Return the new, possibly-shorter length
+//
+// Result Buffer ALWAYS has leading space and trailing space space space NUL,
+// if input does
+//
+int CheapRepWordsInplace(char* isrc, int src_len, int* hash, int* tbl) {
+ const uint8* src = reinterpret_cast<const uint8*>(isrc);
+ const uint8* srclimit = src + src_len;
+ char* dst = isrc;
+ int local_hash = *hash;
+ char* word_dst = dst; // Start of next word
+ int good_predict_bytes = 0;
+ int word_length_bytes = 0;
+
+ while (src < srclimit) {
+ int c = src[0];
+ int incr = 1;
+ *dst++ = c;
+
+ if (c == ' ') {
+ if ((good_predict_bytes * 2) > word_length_bytes) {
+ // Word is well-predicted: backup to start of this word
+ dst = word_dst;
+ if (FLAGS_cld_showme) {
+ // Mark the deletion point with period
+ // Don't repeat multiple periods
+ // Cannot mark with more bytes or may overwrite unseen input
+ if ((isrc < (dst - 2)) && (dst[-2] != '.')) {
+ *dst++ = '.';
+ *dst++ = ' ';
+ }
+ }
+ }
+ word_dst = dst; // Start of next word
+ good_predict_bytes = 0;
+ word_length_bytes = 0;
+ }
+
+ // Pick up one char and length
+ if (c < 0xc0) {
+ // One-byte or continuation byte: 00xxxxxx 01xxxxxx 10xxxxxx
+ // Do nothing more
+ } else if ((c & 0xe0) == 0xc0) {
+ // Two-byte
+ *dst++ = src[1];
+ c = (c << 8) | src[1];
+ incr = 2;
+ } else if ((c & 0xf0) == 0xe0) {
+ // Three-byte
+ *dst++ = src[1];
+ *dst++ = src[2];
+ c = (c << 16) | (src[1] << 8) | src[2];
+ incr = 3;
+ } else {
+ // Four-byte
+ *dst++ = src[1];
+ *dst++ = src[2];
+ *dst++ = src[3];
+ c = (c << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
+ incr = 4;
+ }
+ src += incr;
+ word_length_bytes += incr;
+
+ int p = tbl[local_hash]; // Prediction
+ tbl[local_hash] = c; // Update prediction
+ if (c == p) {
+ good_predict_bytes += incr; // Count good predictions
+ }
+
+ local_hash = ((local_hash << 4) ^ c) & 0xfff;
+ }
+
+ *hash = local_hash;
+
+ if ((dst - isrc) < (src_len - 3)) {
+ // Pad and make last char clean UTF-8 by putting following spaces
+ dst[0] = ' ';
+ dst[1] = ' ';
+ dst[2] = ' ';
+ dst[3] = '\0';
+ } else if ((dst - isrc) < src_len) {
+ // Make last char clean UTF-8 by putting following space off the end
+ dst[0] = ' ';
+ }
+
+ return static_cast<int>(dst - isrc);
+}
+
+
+// This alternate form overwrites redundant words, thus avoiding corrupting the
+// backmap for generate a vector of original-text ranges.
+int CheapRepWordsInplaceOverwrite(char* isrc, int src_len, int* hash, int* tbl) {
+ const uint8* src = reinterpret_cast<const uint8*>(isrc);
+ const uint8* srclimit = src + src_len;
+ char* dst = isrc;
+ int local_hash = *hash;
+ char* word_dst = dst; // Start of next word
+ int good_predict_bytes = 0;
+ int word_length_bytes = 0;
+
+ while (src < srclimit) {
+ int c = src[0];
+ int incr = 1;
+ *dst++ = c;
+
+ if (c == ' ') {
+ if ((good_predict_bytes * 2) > word_length_bytes) {
+ // Word [word_dst..dst-1) is well-predicted: overwrite
+ for (char* p = word_dst; p < dst - 1; ++p) {*p = '.';}
+ }
+ word_dst = dst; // Start of next word
+ good_predict_bytes = 0;
+ word_length_bytes = 0;
+ }
+
+ // Pick up one char and length
+ if (c < 0xc0) {
+ // One-byte or continuation byte: 00xxxxxx 01xxxxxx 10xxxxxx
+ // Do nothing more
+ } else if ((c & 0xe0) == 0xc0) {
+ // Two-byte
+ *dst++ = src[1];
+ c = (c << 8) | src[1];
+ incr = 2;
+ } else if ((c & 0xf0) == 0xe0) {
+ // Three-byte
+ *dst++ = src[1];
+ *dst++ = src[2];
+ c = (c << 16) | (src[1] << 8) | src[2];
+ incr = 3;
+ } else {
+ // Four-byte
+ *dst++ = src[1];
+ *dst++ = src[2];
+ *dst++ = src[3];
+ c = (c << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
+ incr = 4;
+ }
+ src += incr;
+ word_length_bytes += incr;
+
+ int p = tbl[local_hash]; // Prediction
+ tbl[local_hash] = c; // Update prediction
+ if (c == p) {
+ good_predict_bytes += incr; // Count good predictions
+ }
+
+ local_hash = ((local_hash << 4) ^ c) & 0xfff;
+ }
+
+ *hash = local_hash;
+
+ if ((dst - isrc) < (src_len - 3)) {
+ // Pad and make last char clean UTF-8 by putting following spaces
+ dst[0] = ' ';
+ dst[1] = ' ';
+ dst[2] = ' ';
+ dst[3] = '\0';
+ } else if ((dst - isrc) < src_len) {
+ // Make last char clean UTF-8 by putting following space off the end
+ dst[0] = ' ';
+ }
+
+ return static_cast<int>(dst - isrc);
+}
+
+
+// Remove portions of text that have a high density of spaces, or that are
+// overly repetitive, squeezing the remaining text in-place to the front of the
+// input buffer.
+//
+// Squeezing looks at density of space/prediced chars in fixed-size chunks,
+// specified by chunksize. A chunksize <= 0 uses the default size of 48 bytes.
+//
+// Return the new, possibly-shorter length
+//
+// Result Buffer ALWAYS has leading space and trailing space space space NUL,
+// if input does
+//
+int CheapSqueezeInplace(char* isrc,
+ int src_len,
+ int ichunksize) {
+ char* src = isrc;
+ char* dst = src;
+ char* srclimit = src + src_len;
+ bool skipping = false;
+
+ int hash = 0;
+ // Allocate local prediction table.
+ int* predict_tbl = new int[kPredictionTableSize];
+ memset(predict_tbl, 0, kPredictionTableSize * sizeof(predict_tbl[0]));
+
+ int chunksize = ichunksize;
+ if (chunksize == 0) {chunksize = kChunksizeDefault;}
+ int space_thresh = (chunksize * kSpacesThreshPercent) / 100;
+ int predict_thresh = (chunksize * kPredictThreshPercent) / 100;
+
+ while (src < srclimit) {
+ int remaining_bytes = srclimit - src;
+ int len = minint(chunksize, remaining_bytes);
+ // Make len land us on a UTF-8 character boundary.
+ // Ah. Also fixes mispredict because we could get out of phase
+ // Loop always terminates at trailing space in buffer
+ while ((src[len] & 0xc0) == 0x80) {++len;} // Move past continuation bytes
+
+ int space_n = CountSpaces4(src, len);
+ int predb_n = CountPredictedBytes(src, len, &hash, predict_tbl);
+ if ((space_n >= space_thresh) || (predb_n >= predict_thresh)) {
+ // Skip the text
+ if (!skipping) {
+ // Keeping-to-skipping transition; do it at a space
+ int n = BackscanToSpace(dst, static_cast<int>(dst - isrc));
+ dst -= n;
+ if (dst == isrc) {
+ // Force a leading space if the first chunk is deleted
+ *dst++ = ' ';
+ }
+ if (FLAGS_cld_showme) {
+ // Mark the deletion point with black square U+25A0
+ *dst++ = static_cast<unsigned char>(0xe2);
+ *dst++ = static_cast<unsigned char>(0x96);
+ *dst++ = static_cast<unsigned char>(0xa0);
+ *dst++ = ' ';
+ }
+ skipping = true;
+ }
+ } else {
+ // Keep the text
+ if (skipping) {
+ // Skipping-to-keeping transition; do it at a space
+ int n = ForwardscanToSpace(src, len);
+ src += n;
+ remaining_bytes -= n; // Shrink remaining length
+ len -= n;
+ skipping = false;
+ }
+ // "len" can be negative in some cases
+ if (len > 0) {
+ memmove(dst, src, len);
+ dst += len;
+ }
+ }
+ src += len;
+ }
+
+ if ((dst - isrc) < (src_len - 3)) {
+ // Pad and make last char clean UTF-8 by putting following spaces
+ dst[0] = ' ';
+ dst[1] = ' ';
+ dst[2] = ' ';
+ dst[3] = '\0';
+ } else if ((dst - isrc) < src_len) {
+ // Make last char clean UTF-8 by putting following space off the end
+ dst[0] = ' ';
+ }
+
+ // Deallocate local prediction table
+ delete[] predict_tbl;
+ return static_cast<int>(dst - isrc);
+}
+
+// This alternate form overwrites redundant words, thus avoiding corrupting the
+// backmap for generate a vector of original-text ranges.
+int CheapSqueezeInplaceOverwrite(char* isrc,
+ int src_len,
+ int ichunksize) {
+ char* src = isrc;
+ char* dst = src;
+ char* srclimit = src + src_len;
+ bool skipping = false;
+
+ int hash = 0;
+ // Allocate local prediction table.
+ int* predict_tbl = new int[kPredictionTableSize];
+ memset(predict_tbl, 0, kPredictionTableSize * sizeof(predict_tbl[0]));
+
+ int chunksize = ichunksize;
+ if (chunksize == 0) {chunksize = kChunksizeDefault;}
+ int space_thresh = (chunksize * kSpacesThreshPercent) / 100;
+ int predict_thresh = (chunksize * kPredictThreshPercent) / 100;
+
+ // Always keep first byte (space)
+ ++src;
+ ++dst;
+ while (src < srclimit) {
+ int remaining_bytes = srclimit - src;
+ int len = minint(chunksize, remaining_bytes);
+ // Make len land us on a UTF-8 character boundary.
+ // Ah. Also fixes mispredict because we could get out of phase
+ // Loop always terminates at trailing space in buffer
+ while ((src[len] & 0xc0) == 0x80) {++len;} // Move past continuation bytes
+
+ int space_n = CountSpaces4(src, len);
+ int predb_n = CountPredictedBytes(src, len, &hash, predict_tbl);
+ if ((space_n >= space_thresh) || (predb_n >= predict_thresh)) {
+ // Overwrite the text [dst-n..dst)
+ if (!skipping) {
+ // Keeping-to-skipping transition; do it at a space
+ int n = BackscanToSpace(dst, static_cast<int>(dst - isrc));
+ // Text [word_dst..dst) is well-predicted: overwrite
+ for (char* p = dst - n; p < dst; ++p) {*p = '.';}
+ skipping = true;
+ }
+ // Overwrite the text [dst..dst+len)
+ for (char* p = dst; p < dst + len; ++p) {*p = '.';}
+ dst[len - 1] = ' '; // Space at end so we can see what is happening
+ } else {
+ // Keep the text
+ if (skipping) {
+ // Skipping-to-keeping transition; do it at a space
+ int n = ForwardscanToSpace(src, len);
+ // Text [dst..dst+n) is well-predicted: overwrite
+ for (char* p = dst; p < dst + n - 1; ++p) {*p = '.';}
+ skipping = false;
+ }
+ }
+ dst += len;
+ src += len;
+ }
+
+ if ((dst - isrc) < (src_len - 3)) {
+ // Pad and make last char clean UTF-8 by putting following spaces
+ dst[0] = ' ';
+ dst[1] = ' ';
+ dst[2] = ' ';
+ dst[3] = '\0';
+ } else if ((dst - isrc) < src_len) {
+ // Make last char clean UTF-8 by putting following space off the end
+ dst[0] = ' ';
+ }
+
+ // Deallocate local prediction table
+ delete[] predict_tbl;
+ return static_cast<int>(dst - isrc);
+}
+
+// Timing 2.8GHz P4 (dsites 2008.03.20) with 170KB input
+// About 90 MB/sec, with or without memcpy, chunksize 48 or 4096
+// Just CountSpaces is about 340 MB/sec
+// Byte-only CountPredictedBytes is about 150 MB/sec
+// Byte-only CountPredictedBytes, conditional tbl[] = is about 85! MB/sec
+// Byte-only CountPredictedBytes is about 180 MB/sec, byte tbl, byte/int c
+// Unjammed byte-only both = 170 MB/sec
+// Jammed byte-only both = 120 MB/sec
+// Back to original w/slight updates, 110 MB/sec
+//
+bool CheapSqueezeTriggerTest(const char* src, int src_len, int testsize) {
+ // Don't trigger at all on short text
+ if (src_len < testsize) {return false;}
+ int space_thresh = (testsize * kSpacesTriggerPercent) / 100;
+ int predict_thresh = (testsize * kPredictTriggerPercent) / 100;
+ int hash = 0;
+ // Allocate local prediction table.
+ int* predict_tbl = new int[kPredictionTableSize];
+ memset(predict_tbl, 0, kPredictionTableSize * sizeof(predict_tbl[0]));
+
+ bool retval = false;
+ if ((CountSpaces4(src, testsize) >= space_thresh) ||
+ (CountPredictedBytes(src, testsize, &hash, predict_tbl) >=
+ predict_thresh)) {
+ retval = true;
+ }
+ // Deallocate local prediction table
+ delete[] predict_tbl;
+ return retval;
+}
+
+
+
+
+// Delete any extended languages from doc_tote
+void RemoveExtendedLanguages(DocTote* doc_tote) {
+ // Now a nop
+}
+
+static const int kMinReliableKeepPercent = 41; // Remove lang if reli < this
+
+// For Tier3 languages, require a minimum number of bytes to be first-place lang
+static const int kGoodFirstT3MinBytes = 24; // <this => no first
+
+// Move bytes for unreliable langs to another lang or UNKNOWN
+// doc_tote is sorted, so cannot Add
+//
+// If both CHINESE and CHINESET are present and unreliable, do not delete both;
+// merge both into CHINESE.
+//
+//dsites 2009.03.19
+// we also want to remove Tier3 languages as the first lang if there is very
+// little text like ej1 ej2 ej3 ej4
+// maybe fold this back in earlier
+//
+void RemoveUnreliableLanguages(DocTote* doc_tote,
+ bool FLAGS_cld2_html, bool FLAGS_cld2_quiet) {
+ // Prepass to merge some low-reliablility languages
+ // TODO: this shouldn't really reach in to the internal structure of doc_tote
+ int total_bytes = 0;
+ for (int sub = 0; sub < doc_tote->MaxSize(); ++sub) {
+ int plang = doc_tote->Key(sub);
+ if (plang == DocTote::kUnusedKey) {continue;} // Empty slot
+
+ Language lang = static_cast<Language>(plang);
+ int bytes = doc_tote->Value(sub);
+ int reli = doc_tote->Reliability(sub);
+ if (bytes == 0) {continue;} // Zero bytes
+ total_bytes += bytes;
+
+ // Reliable percent = stored reliable score over stored bytecount
+ int reliable_percent = reli / bytes;
+ if (reliable_percent >= kMinReliableKeepPercent) {continue;} // Keeper
+
+ // This language is too unreliable to keep, but we might merge it.
+ Language altlang = UNKNOWN_LANGUAGE;
+ if (lang <= HAWAIIAN) {altlang = kClosestAltLanguage[lang];}
+ if (altlang == UNKNOWN_LANGUAGE) {continue;} // No alternative
+
+ // Look for alternative in doc_tote
+ int altsub = doc_tote->Find(altlang);
+ if (altsub < 0) {continue;} // No alternative text
+
+ int bytes2 = doc_tote->Value(altsub);
+ int reli2 = doc_tote->Reliability(altsub);
+ if (bytes2 == 0) {continue;} // Zero bytes
+
+ // Reliable percent is stored reliable score over stored bytecount
+ int reliable_percent2 = reli2 / bytes2;
+
+ // Merge one language into the other. Break ties toward lower lang #
+ int tosub = altsub;
+ int fromsub = sub;
+ bool into_lang = false;
+ if ((reliable_percent2 < reliable_percent) ||
+ ((reliable_percent2 == reliable_percent) && (lang < altlang))) {
+ tosub = sub;
+ fromsub = altsub;
+ into_lang = true;
+ }
+
+ // Make sure merged reliability doesn't drop and is enough to avoid delete
+ int newpercent = maxint(reliable_percent, reliable_percent2);
+ newpercent = maxint(newpercent, kMinReliableKeepPercent);
+ int newbytes = bytes + bytes2;
+ int newreli = newpercent * newbytes;
+
+ doc_tote->SetKey(fromsub, DocTote::kUnusedKey);
+ doc_tote->SetScore(fromsub, 0);
+ doc_tote->SetReliability(fromsub, 0);
+ doc_tote->SetScore(tosub, newbytes);
+ doc_tote->SetReliability(tosub, newreli);
+
+ // Show fate of unreliable languages if at least 10 bytes
+ if (FLAGS_cld2_html && (newbytes >= 10) &&
+ !FLAGS_cld2_quiet) {
+ if (into_lang) {
+ fprintf(stderr, "{Unreli %s.%dR,%dB => %s} ",
+ LanguageCode(altlang), reliable_percent2, bytes2,
+ LanguageCode(lang));
+ } else {
+ fprintf(stderr, "{Unreli %s.%dR,%dB => %s} ",
+ LanguageCode(lang), reliable_percent, bytes,
+ LanguageCode(altlang));
+ }
+ }
+ }
+
+
+ // Pass to delete any remaining unreliable languages
+ for (int sub = 0; sub < doc_tote->MaxSize(); ++sub) {
+ int plang = doc_tote->Key(sub);
+ if (plang == DocTote::kUnusedKey) {continue;} // Empty slot
+
+ Language lang = static_cast<Language>(plang);
+ int bytes = doc_tote->Value(sub);
+ int reli = doc_tote->Reliability(sub);
+ if (bytes == 0) {continue;} // Zero bytes
+
+ // Reliable percent is stored as reliable score over stored bytecount
+ int reliable_percent = reli / bytes;
+ if (reliable_percent >= kMinReliableKeepPercent) { // Keeper?
+ continue; // yes
+ }
+
+ // Delete unreliable entry
+ doc_tote->SetKey(sub, DocTote::kUnusedKey);
+ doc_tote->SetScore(sub, 0);
+ doc_tote->SetReliability(sub, 0);
+
+ // Show fate of unreliable languages if at least 10 bytes
+ if (FLAGS_cld2_html && (bytes >= 10) &&
+ !FLAGS_cld2_quiet) {
+ fprintf(stderr, "{Unreli %s.%dR,%dB} ",
+ LanguageCode(lang), reliable_percent, bytes);
+ }
+ }
+
+ ////if (FLAGS_cld2_html) {fprintf(stderr, "<br>\n");}
+}
+
+
+// Move all the text bytes from lower byte-count to higher one
+void MoveLang1ToLang2(Language lang1, Language lang2,
+ int lang1_sub, int lang2_sub,
+ DocTote* doc_tote,
+ ResultChunkVector* resultchunkvector) {
+ // In doc_tote, move all the bytes lang1 => lang2
+ int sum = doc_tote->Value(lang2_sub) + doc_tote->Value(lang1_sub);
+ doc_tote->SetValue(lang2_sub, sum);
+ sum = doc_tote->Score(lang2_sub) + doc_tote->Score(lang1_sub);
+ doc_tote->SetScore(lang2_sub, sum);
+ sum = doc_tote->Reliability(lang2_sub) + doc_tote->Reliability(lang1_sub);
+ doc_tote->SetReliability(lang2_sub, sum);
+
+ // Delete old entry
+ doc_tote->SetKey(lang1_sub, DocTote::kUnusedKey);
+ doc_tote->SetScore(lang1_sub, 0);
+ doc_tote->SetReliability(lang1_sub, 0);
+
+ // In resultchunkvector, move all the bytes lang1 => lang2
+ if (resultchunkvector == NULL) {return;}
+
+ int k = 0;
+ uint16 prior_lang = UNKNOWN_LANGUAGE;
+ for (int i = 0; i < static_cast<int>(resultchunkvector->size()); ++i) {
+ ResultChunk* rc = &(*resultchunkvector)[i];
+ if (rc->lang1 == lang1) {
+ // Update entry[i] lang1 => lang2
+ rc->lang1 = lang2;
+ }
+ // One change may produce two merges -- entry before and entry after
+ if ((rc->lang1 == prior_lang) && (k > 0)) {
+ // Merge with previous, deleting entry[i]
+ ResultChunk* prior_rc = &(*resultchunkvector)[k - 1];
+ prior_rc->bytes += rc->bytes;
+ // fprintf(stderr, "MoveLang1ToLang2 merged [%d] => [%d]<br>\n", i, k-1);
+ } else {
+ // Keep entry[i]
+ (*resultchunkvector)[k] = (*resultchunkvector)[i];
+ // fprintf(stderr, "MoveLang1ToLang2 keep [%d] => [%d]<br>\n", i, k);
+ ++k;
+ }
+ prior_lang = rc->lang1;
+ }
+ resultchunkvector->resize(k);
+}
+
+
+
+// Move less likely byte count to more likely for close pairs of languages
+// If given, also update resultchunkvector
+void RefineScoredClosePairs(DocTote* doc_tote,
+ ResultChunkVector* resultchunkvector,
+ bool FLAGS_cld2_html, bool FLAGS_cld2_quiet) {
+ for (int sub = 0; sub < doc_tote->MaxSize(); ++sub) {
+ int close_packedlang = doc_tote->Key(sub);
+ int subscr = LanguageCloseSet(static_cast<Language>(close_packedlang));
+ if (subscr == 0) {continue;}
+
+ // We have a close pair language -- if the other one is also scored and the
+ // longword score differs enough, put all our eggs into one basket
+
+ // Nonzero longword score: Go look for the other of this pair
+ for (int sub2 = sub + 1; sub2 < doc_tote->MaxSize(); ++sub2) {
+ if (LanguageCloseSet(static_cast<Language>(doc_tote->Key(sub2))) == subscr) {
+ // We have a matching pair
+ int close_packedlang2 = doc_tote->Key(sub2);
+
+ // Move all the text bytes from lower byte-count to higher one
+ int from_sub, to_sub;
+ Language from_lang, to_lang;
+ if (doc_tote->Value(sub) < doc_tote->Value(sub2)) {
+ from_sub = sub;
+ to_sub = sub2;
+ from_lang = static_cast<Language>(close_packedlang);
+ to_lang = static_cast<Language>(close_packedlang2);
+ } else {
+ from_sub = sub2;
+ to_sub = sub;
+ from_lang = static_cast<Language>(close_packedlang2);
+ to_lang = static_cast<Language>(close_packedlang);
+ }
+
+ if ((FLAGS_cld2_html || FLAGS_dbgscore) && !FLAGS_cld2_quiet) {
+ // Show fate of closepair language
+ int val = doc_tote->Value(from_sub); // byte count
+ int reli = doc_tote->Reliability(from_sub);
+ int reliable_percent = reli / (val ? val : 1); // avoid zdiv
+ fprintf(stderr, "{CloseLangPair: %s.%dR,%dB => %s}<br>\n",
+ LanguageCode(from_lang),
+ reliable_percent,
+ doc_tote->Value(from_sub),
+ LanguageCode(to_lang));
+ }
+ MoveLang1ToLang2(from_lang, to_lang, from_sub, to_sub,
+ doc_tote, resultchunkvector);
+ break; // Exit inner for sub2 loop
+ }
+ } // End for sub2
+ } // End for sub
+}
+
+
+void ApplyAllLanguageHints(Tote* chunk_tote, int tote_grams,
+ uint8* lang_hint_boost) {
+}
+
+
+void PrintHtmlEscapedText(FILE* f, const char* txt, int len) {
+ string temp(txt, len);
+ fprintf(f, "%s", GetHtmlEscapedText(temp).c_str());
+}
+
+void PrintLang(FILE* f, Tote* chunk_tote,
+ Language cur_lang, bool cur_unreliable,
+ Language prior_lang, bool prior_unreliable) {
+ if (cur_lang == prior_lang) {
+ fprintf(f, "[]");
+ } else {
+ fprintf(f, "[%s%s]", LanguageCode(cur_lang), cur_unreliable ? "*" : "");
+ }
+}
+
+
+void PrintTopLang(Language top_lang) {
+ if ((top_lang == prior_lang) && (top_lang != UNKNOWN_LANGUAGE)) {
+ fprintf(stderr, "[] ");
+ } else {
+ fprintf(stderr, "[%s] ", LanguageName(top_lang));
+ prior_lang = top_lang;
+ }
+}
+
+void PrintTopLangSpeculative(Language top_lang) {
+ fprintf(stderr, "<span style=\"color:#%06X;\">", 0xa0a0a0);
+ if ((top_lang == prior_lang) && (top_lang != UNKNOWN_LANGUAGE)) {
+ fprintf(stderr, "[] ");
+ } else {
+ fprintf(stderr, "[%s] ", LanguageName(top_lang));
+ prior_lang = top_lang;
+ }
+ fprintf(stderr, "</span>\n");
+}
+
+void PrintLangs(FILE* f, const Language* language3, const int* percent3,
+ const int* text_bytes, const bool* is_reliable) {
+ fprintf(f, "<br>&nbsp;&nbsp;Initial_Languages ");
+ if (language3[0] != UNKNOWN_LANGUAGE) {
+ fprintf(f, "%s%s(%d%%) ",
+ LanguageName(language3[0]),
+ *is_reliable ? "" : "*",
+ percent3[0]);
+ }
+ if (language3[1] != UNKNOWN_LANGUAGE) {
+ fprintf(f, "%s(%d%%) ", LanguageName(language3[1]), percent3[1]);
+ }
+ if (language3[2] != UNKNOWN_LANGUAGE) {
+ fprintf(f, "%s(%d%%) ", LanguageName(language3[2]), percent3[2]);
+ }
+ fprintf(f, "%d bytes \n", *text_bytes);
+
+ fprintf(f, "<br>\n");
+}
+
+
+// Return internal probability score (sum) per 1024 bytes
+double GetNormalizedScore(Language lang, ULScript ulscript,
+ int bytecount, int score) {
+ if (bytecount <= 0) {return 0.0;}
+ return (score << 10) / bytecount;
+}
+
+// Extract return values before fixups
+void ExtractLangEtc(DocTote* doc_tote, int total_text_bytes,
+ int* reliable_percent3, Language* language3, int* percent3,
+ double* normalized_score3,
+ int* text_bytes, bool* is_reliable) {
+ reliable_percent3[0] = 0;
+ reliable_percent3[1] = 0;
+ reliable_percent3[2] = 0;
+ language3[0] = UNKNOWN_LANGUAGE;
+ language3[1] = UNKNOWN_LANGUAGE;
+ language3[2] = UNKNOWN_LANGUAGE;
+ percent3[0] = 0;
+ percent3[1] = 0;
+ percent3[2] = 0;
+ normalized_score3[0] = 0.0;
+ normalized_score3[1] = 0.0;
+ normalized_score3[2] = 0.0;
+
+ *text_bytes = total_text_bytes;
+ *is_reliable = false;
+
+ int bytecount1 = 0;
+ int bytecount2 = 0;
+ int bytecount3 = 0;
+
+ int lang1 = doc_tote->Key(0);
+ if ((lang1 != DocTote::kUnusedKey) && (lang1 != UNKNOWN_LANGUAGE)) {
+ // We have a top language
+ language3[0] = static_cast<Language>(lang1);
+ bytecount1 = doc_tote->Value(0);
+ int reli1 = doc_tote->Reliability(0);
+ reliable_percent3[0] = reli1 / (bytecount1 ? bytecount1 : 1); // avoid zdiv
+ normalized_score3[0] = GetNormalizedScore(language3[0],
+ ULScript_Common,
+ bytecount1,
+ doc_tote->Score(0));
+ }
+
+ int lang2 = doc_tote->Key(1);
+ if ((lang2 != DocTote::kUnusedKey) && (lang2 != UNKNOWN_LANGUAGE)) {
+ language3[1] = static_cast<Language>(lang2);
+ bytecount2 = doc_tote->Value(1);
+ int reli2 = doc_tote->Reliability(1);
+ reliable_percent3[1] = reli2 / (bytecount2 ? bytecount2 : 1); // avoid zdiv
+ normalized_score3[1] = GetNormalizedScore(language3[1],
+ ULScript_Common,
+ bytecount2,
+ doc_tote->Score(1));
+ }
+
+ int lang3 = doc_tote->Key(2);
+ if ((lang3 != DocTote::kUnusedKey) && (lang3 != UNKNOWN_LANGUAGE)) {
+ language3[2] = static_cast<Language>(lang3);
+ bytecount3 = doc_tote->Value(2);
+ int reli3 = doc_tote->Reliability(2);
+ reliable_percent3[2] = reli3 / (bytecount3 ? bytecount3 : 1); // avoid zdiv
+ normalized_score3[2] = GetNormalizedScore(language3[2],
+ ULScript_Common,
+ bytecount3,
+ doc_tote->Score(2));
+ }
+
+ // Increase total bytes to sum (top 3) if low for some reason
+ int total_bytecount12 = bytecount1 + bytecount2;
+ int total_bytecount123 = total_bytecount12 + bytecount3;
+ if (total_text_bytes < total_bytecount123) {
+ total_text_bytes = total_bytecount123;
+ *text_bytes = total_text_bytes;
+ }
+
+ // Sum minus previous % gives better roundoff behavior than bytecount/total
+ int total_text_bytes_div = maxint(1, total_text_bytes); // Avoid zdiv
+ percent3[0] = (bytecount1 * 100) / total_text_bytes_div;
+ percent3[1] = (total_bytecount12 * 100) / total_text_bytes_div;
+ percent3[2] = (total_bytecount123 * 100) / total_text_bytes_div;
+ percent3[2] -= percent3[1];
+ percent3[1] -= percent3[0];
+
+ // Roundoff, say 96% 1.6% 1.4%, will produce non-obvious 96% 1% 2%
+ // Fix this explicitly
+ if (percent3[1] < percent3[2]) {
+ ++percent3[1];
+ --percent3[2];
+ }
+ if (percent3[0] < percent3[1]) {
+ ++percent3[0];
+ --percent3[1];
+ }
+
+ *text_bytes = total_text_bytes;
+
+ if ((lang1 != DocTote::kUnusedKey) && (lang1 != UNKNOWN_LANGUAGE)) {
+ // We have a top language
+ // Its reliability is overall result reliability
+ int bytecount = doc_tote->Value(0);
+ int reli = doc_tote->Reliability(0);
+ int reliable_percent = reli / (bytecount ? bytecount : 1); // avoid zdiv
+ *is_reliable = (reliable_percent >= kMinReliableKeepPercent);
+ } else {
+ // No top language at all. This can happen with zero text or 100% Klingon
+ // if extended=false. Just return all UNKNOWN_LANGUAGE, unreliable.
+ *is_reliable = false;
+ }
+
+ // If ignore percent is too large, set unreliable.
+ int ignore_percent = 100 - (percent3[0] + percent3[1] + percent3[2]);
+ if ((ignore_percent > kIgnoreMaxPercent)) {
+ *is_reliable = false;
+ }
+}
+
+bool IsFIGS(Language lang) {
+ if (lang == FRENCH) {return true;}
+ if (lang == ITALIAN) {return true;}
+ if (lang == GERMAN) {return true;}
+ if (lang == SPANISH) {return true;}
+ return false;
+}
+
+bool IsEFIGS(Language lang) {
+ if (lang == ENGLISH) {return true;}
+ if (lang == FRENCH) {return true;}
+ if (lang == ITALIAN) {return true;}
+ if (lang == GERMAN) {return true;}
+ if (lang == SPANISH) {return true;}
+ return false;
+}
+
+// For Tier3 languages, require more bytes of text to override
+// the first-place language
+static const int kGoodSecondT1T2MinBytes = 15; // <this => no second
+static const int kGoodSecondT3MinBytes = 128; // <this => no second
+
+// Calculate a single summary language for the document, and its reliability.
+// Returns language3[0] or language3[1] or ENGLISH or UNKNOWN_LANGUAGE
+// This is the heart of matching human-rater perception.
+// reliable_percent3[] is currently unused
+//
+// Do not return Tier3 second language unless there are at least 128 bytes
+void CalcSummaryLang(DocTote* doc_tote, int total_text_bytes,
+ const int* reliable_percent3,
+ const Language* language3,
+ const int* percent3,
+ Language* summary_lang, bool* is_reliable,
+ bool FLAGS_cld2_html, bool FLAGS_cld2_quiet) {
+ // Vector of active languages; changes if we delete some
+ int slot_count = 3;
+ int active_slot[3] = {0, 1, 2};
+
+ int ignore_percent = 0;
+ int return_percent = percent3[0]; // Default to top lang
+ *summary_lang = language3[0];
+ *is_reliable = true;
+ if (percent3[0] < kKeepMinPercent) {*is_reliable = false;}
+
+ // If any of top 3 is IGNORE, remove it and increment ignore_percent
+ for (int i = 0; i < 3; ++i) {
+ if (language3[i] == TG_UNKNOWN_LANGUAGE) {
+ ignore_percent += percent3[i];
+ // Move the rest up, levaing input vectors unchanged
+ for (int j=i+1; j < 3; ++j) {
+ active_slot[j - 1] = active_slot[j];
+ }
+ -- slot_count;
+ // Logically remove Ignore from percentage-text calculation
+ // (extra 1 in 101 avoids zdiv, biases slightly small)
+ return_percent = (percent3[0] * 100) / (101 - ignore_percent);
+ *summary_lang = language3[active_slot[0]];
+ if (percent3[active_slot[0]] < kKeepMinPercent) {*is_reliable = false;}
+ }
+ }
+
+
+ // If English and X, where X (not UNK) is big enough,
+ // assume the English is boilerplate and return X.
+ // Logically remove English from percentage-text calculation
+ int second_bytes = (total_text_bytes * percent3[active_slot[1]]) / 100;
+ // Require more bytes of text for Tier3 languages
+ int minbytesneeded = kGoodSecondT1T2MinBytes;
+ int plang_second = PerScriptNumber(ULScript_Latin, language3[active_slot[1]]);
+
+ if ((language3[active_slot[0]] == ENGLISH) &&
+ (language3[active_slot[1]] != ENGLISH) &&
+ (language3[active_slot[1]] != UNKNOWN_LANGUAGE) &&
+ (percent3[active_slot[1]] >= kNonEnBoilerplateMinPercent) &&
+ (second_bytes >= minbytesneeded)) {
+ ignore_percent += percent3[active_slot[0]];
+ return_percent = (percent3[active_slot[1]] * 100) / (101 - ignore_percent);
+ *summary_lang = language3[active_slot[1]];
+ if (percent3[active_slot[1]] < kKeepMinPercent) {*is_reliable = false;}
+
+ // Else If FIGS and X, where X (not UNK, EFIGS) is big enough,
+ // assume the FIGS is boilerplate and return X.
+ // Logically remove FIGS from percentage-text calculation
+ } else if (IsFIGS(language3[active_slot[0]]) &&
+ !IsEFIGS(language3[active_slot[1]]) &&
+ (language3[active_slot[1]] != UNKNOWN_LANGUAGE) &&
+ (percent3[active_slot[1]] >= kNonFIGSBoilerplateMinPercent) &&
+ (second_bytes >= minbytesneeded)) {
+ ignore_percent += percent3[active_slot[0]];
+ return_percent = (percent3[active_slot[1]] * 100) / (101 - ignore_percent);
+ *summary_lang = language3[active_slot[1]];
+ if (percent3[active_slot[1]] < kKeepMinPercent) {*is_reliable = false;}
+
+ // Else we are returning the first language, but want to improve its
+ // return_percent if the second language should be ignored
+ } else if ((language3[active_slot[1]] == ENGLISH) &&
+ (language3[active_slot[0]] != ENGLISH)) {
+ ignore_percent += percent3[active_slot[1]];
+ return_percent = (percent3[active_slot[0]] * 100) / (101 - ignore_percent);
+ } else if (IsFIGS(language3[active_slot[1]]) &&
+ !IsEFIGS(language3[active_slot[0]])) {
+ ignore_percent += percent3[active_slot[1]];
+ return_percent = (percent3[active_slot[0]] * 100) / (101 - ignore_percent);
+ }
+
+ // If return percent is too small (too many languages), return UNKNOWN
+ if ((return_percent < kGoodFirstMinPercent)) {
+ if (FLAGS_cld2_html && !FLAGS_cld2_quiet) {
+ fprintf(stderr, "{Unreli %s %d%% percent too small} ",
+ LanguageCode(*summary_lang), return_percent);
+ }
+ *summary_lang = UNKNOWN_LANGUAGE;
+ *is_reliable = false;
+ }
+
+ // If return percent is small, return language but set unreliable.
+ if ((return_percent < kGoodFirstReliableMinPercent)) {
+ *is_reliable = false;
+ }
+
+ // If ignore percent is too large, set unreliable.
+ ignore_percent = 100 - (percent3[0] + percent3[1] + percent3[2]);
+ if ((ignore_percent > kIgnoreMaxPercent)) {
+ *is_reliable = false;
+ }
+
+ // If we removed all the active languages, return UNKNOWN
+ if (slot_count == 0) {
+ if (FLAGS_cld2_html && !FLAGS_cld2_quiet) {
+ fprintf(stderr, "{Unreli %s no languages left} ",
+ LanguageCode(*summary_lang));
+ }
+ *summary_lang = UNKNOWN_LANGUAGE;
+ *is_reliable = false;
+ }
+}
+
+void AddLangPriorBoost(Language lang, uint32 langprob,
+ ScoringContext* scoringcontext) {
+ // This is called 0..n times with language hints
+ // but we don't know the script -- so boost either or both Latn, Othr.
+
+ if (IsLatnLanguage(lang)) {
+ LangBoosts* langprior_boost = &scoringcontext->langprior_boost.latn;
+ int n = langprior_boost->n;
+ langprior_boost->langprob[n] = langprob;
+ langprior_boost->n = langprior_boost->wrap(n + 1);
+ }
+
+ if (IsOthrLanguage(lang)) {
+ LangBoosts* langprior_boost = &scoringcontext->langprior_boost.othr;
+ int n = langprior_boost->n;
+ langprior_boost->langprob[n] = langprob;
+ langprior_boost->n = langprior_boost->wrap(n + 1);
+ }
+
+}
+
+void AddOneWhack(Language whacker_lang, Language whackee_lang,
+ ScoringContext* scoringcontext) {
+ uint32 langprob = MakeLangProb(whackee_lang, 1);
+ // This logic avoids hr-Latn whacking sr-Cyrl, but still whacks sr-Latn
+ if (IsLatnLanguage(whacker_lang) && IsLatnLanguage(whackee_lang)) {
+ LangBoosts* langprior_whack = &scoringcontext->langprior_whack.latn;
+ int n = langprior_whack->n;
+ langprior_whack->langprob[n] = langprob;
+ langprior_whack->n = langprior_whack->wrap(n + 1);
+ }
+ if (IsOthrLanguage(whacker_lang) && IsOthrLanguage(whackee_lang)) {
+ LangBoosts* langprior_whack = &scoringcontext->langprior_whack.othr;
+ int n = langprior_whack->n;
+ langprior_whack->langprob[n] = langprob;
+ langprior_whack->n = langprior_whack->wrap(n + 1);
+ }
+}
+
+void AddCloseLangWhack(Language lang, ScoringContext* scoringcontext) {
+ // We do not in general want zh-Hans and zh-Hant to be close pairs,
+ // but we do here.
+ if (lang == CLD2::CHINESE) {
+ AddOneWhack(lang, CLD2::CHINESE_T, scoringcontext);
+ return;
+ }
+ if (lang == CLD2::CHINESE_T) {
+ AddOneWhack(lang, CLD2::CHINESE, scoringcontext);
+ return;
+ }
+
+ int base_lang_set = LanguageCloseSet(lang);
+ if (base_lang_set == 0) {return;}
+ // TODO: add an explicit list of each set to avoid this 512-times loop
+ for (int i = 0; i < kLanguageToPLangSize; ++i) {
+ Language lang2 = static_cast<Language>(i);
+ if ((base_lang_set == LanguageCloseSet(lang2)) && (lang != lang2)) {
+ AddOneWhack(lang, lang2, scoringcontext);
+ }
+ }
+}
+
+
+void ApplyHints(const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const CLDHints* cld_hints,
+ ScoringContext* scoringcontext) {
+ CLDLangPriors lang_priors;
+ InitCLDLangPriors(&lang_priors);
+
+ // We now use lang= tags.
+ // Last look, circa 2008 found only 15% of web pages with lang= tags and
+ // many of those were wrong. Now (July 2011), we find 44% of web pages have
+ // lang= tags, and most of them are correct. So we now give them substantial
+ // weight in each chunk scored.
+ if (!is_plain_text) {
+ // Get any contained language tags in first n KB
+ int32 max_scan_bytes = FLAGS_cld_max_lang_tag_scan_kb << 10;
+ string lang_tags = GetLangTagsFromHtml(buffer, buffer_length,
+ max_scan_bytes);
+ SetCLDLangTagsHint(lang_tags, &lang_priors);
+ if (scoringcontext->flags_cld2_html) {
+ if (!lang_tags.empty()) {
+ fprintf(scoringcontext->debug_file, "<br>lang_tags '%s'<br>\n",
+ lang_tags.c_str());
+ }
+ }
+ }
+
+ if (cld_hints != NULL) {
+ if ((cld_hints->content_language_hint != NULL) &&
+ (cld_hints->content_language_hint[0] != '\0')) {
+ SetCLDContentLangHint(cld_hints->content_language_hint, &lang_priors);
+ }
+
+ // Input is from GetTLD(), already lowercased
+ if ((cld_hints->tld_hint != NULL) && (cld_hints->tld_hint[0] != '\0')) {
+ SetCLDTLDHint(cld_hints->tld_hint, &lang_priors);
+ }
+
+ if (cld_hints->encoding_hint != UNKNOWN_ENCODING) {
+ Encoding enc = static_cast<Encoding>(cld_hints->encoding_hint);
+ SetCLDEncodingHint(enc, &lang_priors);
+ }
+
+ if (cld_hints->language_hint != UNKNOWN_LANGUAGE) {
+ SetCLDLanguageHint(cld_hints->language_hint, &lang_priors);
+ }
+ }
+
+ // Keep no more than four different languages with hints
+ TrimCLDLangPriors(4, &lang_priors);
+
+ if (scoringcontext->flags_cld2_html) {
+ string print_temp = DumpCLDLangPriors(&lang_priors);
+ if (!print_temp.empty()) {
+ fprintf(scoringcontext->debug_file, "DumpCLDLangPriors %s<br>\n",
+ print_temp.c_str());
+ }
+ }
+
+ // Put boosts into ScoringContext
+ for (int i = 0; i < GetCLDLangPriorCount(&lang_priors); ++i) {
+ Language lang = GetCLDPriorLang(lang_priors.prior[i]);
+ int qprob = GetCLDPriorWeight(lang_priors.prior[i]);
+ if (qprob > 0) {
+ uint32 langprob = MakeLangProb(lang, qprob);
+ AddLangPriorBoost(lang, langprob, scoringcontext);
+ }
+ }
+
+ // Put whacks into scoring context
+ // We do not in general want zh-Hans and zh-Hant to be close pairs,
+ // but we do here. Use close_set_count[kCloseSetSize] to count zh, zh-Hant
+ std::vector<int> close_set_count(kCloseSetSize + 1, 0);
+
+ for (int i = 0; i < GetCLDLangPriorCount(&lang_priors); ++i) {
+ Language lang = GetCLDPriorLang(lang_priors.prior[i]);
+ ++close_set_count[LanguageCloseSet(lang)];
+ if (lang == CLD2::CHINESE) {++close_set_count[kCloseSetSize];}
+ if (lang == CLD2::CHINESE_T) {++close_set_count[kCloseSetSize];}
+ }
+
+ // If a boost language is in a close set, force suppressing the others in
+ // that set, if exactly one of the set is present
+ for (int i = 0; i < GetCLDLangPriorCount(&lang_priors); ++i) {
+ Language lang = GetCLDPriorLang(lang_priors.prior[i]);
+ int qprob = GetCLDPriorWeight(lang_priors.prior[i]);
+ if (qprob > 0) {
+ int close_set = LanguageCloseSet(lang);
+ if ((close_set > 0) && (close_set_count[close_set] == 1)) {
+ AddCloseLangWhack(lang, scoringcontext);
+ }
+ if (((lang == CLD2::CHINESE) || (lang == CLD2::CHINESE_T)) &&
+ (close_set_count[kCloseSetSize] == 1)) {
+ AddCloseLangWhack(lang, scoringcontext);
+ }
+ }
+ }
+
+
+
+
+
+
+}
+
+
+
+// Results language3/percent3/text_bytes must be exactly three items
+Language DetectLanguageSummaryV2(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const CLDHints* cld_hints,
+ bool allow_extended_lang,
+ int flags,
+ Language plus_one,
+ Language* language3,
+ int* percent3,
+ double* normalized_score3,
+ ResultChunkVector* resultchunkvector,
+ int* text_bytes,
+ bool* is_reliable) {
+ language3[0] = UNKNOWN_LANGUAGE;
+ language3[1] = UNKNOWN_LANGUAGE;
+ language3[2] = UNKNOWN_LANGUAGE;
+ percent3[0] = 0;
+ percent3[1] = 0;
+ percent3[2] = 0;
+ normalized_score3[0] = 0.0;
+ normalized_score3[1] = 0.0;
+ normalized_score3[2] = 0.0;
+ if (resultchunkvector != NULL) {
+ resultchunkvector->clear();
+ }
+ *text_bytes = 0;
+ *is_reliable = false;
+
+ if ((flags & kCLDFlagEcho) != 0) {
+ string temp(buffer, buffer_length);
+ if ((flags & kCLDFlagHtml) != 0) {
+ fprintf(stderr, "CLD2[%d] '%s'<br>\n",
+ buffer_length, GetHtmlEscapedText(temp).c_str());
+ } else {
+ fprintf(stderr, "CLD2[%d] '%s'\n",
+ buffer_length, GetPlainEscapedText(temp).c_str());
+ }
+ }
+
+#ifdef CLD2_DYNAMIC_MODE
+ // In dynamic mode, we immediately return UNKNOWN_LANGUAGE if the data file
+ // hasn't been loaded yet. This is the only sane thing we can do, as there
+ // are no scoring tables to consult.
+ bool dataLoaded = isDataLoaded();
+ if ((flags & kCLDFlagVerbose) != 0) {
+ fprintf(stderr, "Data loaded: %s\n", (dataLoaded ? "true" : "false"));
+ }
+ if (!dataLoaded) {
+ return UNKNOWN_LANGUAGE;
+ }
+#endif
+
+ // Exit now if no text
+ if (buffer_length == 0) {return UNKNOWN_LANGUAGE;}
+ if (kScoringtables.quadgram_obj == NULL) {return UNKNOWN_LANGUAGE;}
+
+ // Document totals
+ DocTote doc_tote; // Reliability = 0..100
+
+ // ScoringContext carries state across scriptspans
+ ScoringContext scoringcontext;
+ scoringcontext.debug_file = stderr;
+ scoringcontext.flags_cld2_score_as_quads =
+ ((flags & kCLDFlagScoreAsQuads) != 0);
+ scoringcontext.flags_cld2_html = ((flags & kCLDFlagHtml) != 0);
+ scoringcontext.flags_cld2_cr = ((flags & kCLDFlagCr) != 0);
+ scoringcontext.flags_cld2_verbose = ((flags & kCLDFlagVerbose) != 0);
+ scoringcontext.prior_chunk_lang = UNKNOWN_LANGUAGE;
+ scoringcontext.ulscript = ULScript_Common;
+ scoringcontext.scoringtables = &kScoringtables;
+ scoringcontext.scanner = NULL;
+ scoringcontext.init(); // Clear the internal memory arrays
+
+ // Now thread safe.
+ bool FLAGS_cld2_html = ((flags & kCLDFlagHtml) != 0);
+ bool FLAGS_cld2_quiet = ((flags & kCLDFlagQuiet) != 0);
+
+ ApplyHints(buffer, buffer_length, is_plain_text, cld_hints, &scoringcontext);
+
+ // Four individual script totals, Latin, Han, other2, other3
+ int next_other_tote = 2;
+ int tote_num = 0;
+
+ // Four totes for up to four different scripts pending at once
+ Tote totes[4]; // [0] Latn [1] Hani [2] other [3] other
+ bool tote_seen[4] = {false, false, false, false};
+ int tote_grams[4] = {0, 0, 0, 0}; // Number in partial chunk
+ ULScript tote_script[4] =
+ {ULScript_Latin, ULScript_Hani, ULScript_Common, ULScript_Common};
+
+ // Loop through text spans in a single script
+ ScriptScanner ss(buffer, buffer_length, is_plain_text);
+ LangSpan scriptspan;
+
+ scoringcontext.scanner = &ss;
+
+ scriptspan.text = NULL;
+ scriptspan.text_bytes = 0;
+ scriptspan.offset = 0;
+ scriptspan.ulscript = ULScript_Common;
+ scriptspan.lang = UNKNOWN_LANGUAGE;
+
+ int total_text_bytes = 0;
+ int textlimit = FLAGS_cld_textlimit << 10; // in KB
+ if (textlimit == 0) {textlimit = 0x7fffffff;}
+
+ int advance_by = 2; // Advance 2 bytes
+ int advance_limit = textlimit >> 3; // For first 1/8 of max document
+
+ int initial_word_span = kDefaultWordSpan;
+ if (FLAGS_cld_forcewords) {
+ initial_word_span = kReallyBigWordSpan;
+ }
+
+ // Pick up chunk sizes
+ // Smoothwidth is units of quadgrams, about 2.5 chars (unigrams) each
+ // Sanity check -- force into a reasonable range
+ int chunksizequads = FLAGS_cld_smoothwidth;
+ chunksizequads = minint(maxint(chunksizequads, kMinChunkSizeQuads),
+ kMaxChunkSizeQuads);
+ int chunksizeunis = (chunksizequads * 5) >> 1;
+
+ // Varying short-span limit doesn't work well -- skips too much beyond 20KB
+ // int spantooshortlimit = advance_by * FLAGS_cld_smoothwidth;
+ int spantooshortlimit = kShortSpanThresh;
+
+ // For debugging only. Not thread-safe
+ prior_lang = UNKNOWN_LANGUAGE;
+ prior_unreliable = false;
+
+ // Allocate full-document prediction table for finding repeating words
+ int hash = 0;
+ int* predict_tbl = new int[kPredictionTableSize];
+ if (FlagRepeats(flags)) {
+ memset(predict_tbl, 0, kPredictionTableSize * sizeof(predict_tbl[0]));
+ }
+
+
+
+ // Loop through scriptspans accumulating number of text bytes in each language
+ while (ss.GetOneScriptSpanLower(&scriptspan)) {
+ ULScript ulscript = scriptspan.ulscript;
+
+ // Squeeze out big chunks of text span if asked to
+ if (FlagSqueeze(flags)) {
+ // Remove repetitive or mostly-spaces chunks
+ int newlen;
+ int chunksize = 0; // Use the default
+ if (resultchunkvector != NULL) {
+ newlen = CheapSqueezeInplaceOverwrite(scriptspan.text,
+ scriptspan.text_bytes,
+ chunksize);
+ } else {
+ newlen = CheapSqueezeInplace(scriptspan.text, scriptspan.text_bytes,
+ chunksize);
+ }
+ scriptspan.text_bytes = newlen;
+ } else {
+ // Check now and then to see if we should be squeezing
+ if (((kCheapSqueezeTestThresh >> 1) < scriptspan.text_bytes) &&
+ !FlagFinish(flags)) {
+ // fprintf(stderr, "CheapSqueezeTriggerTest, "
+ // "first %d bytes of %d (>%d/2)<br>\n",
+ // kCheapSqueezeTestLen,
+ // scriptspan.text_bytes,
+ // kCheapSqueezeTestThresh);
+
+ if (CheapSqueezeTriggerTest(scriptspan.text,
+ scriptspan.text_bytes,
+ kCheapSqueezeTestLen)) {
+ // Recursive call with big-chunk squeezing set
+ if (FLAGS_cld2_html || FLAGS_dbgscore) {
+ fprintf(stderr,
+ "<br>---text_bytes[%d] Recursive(Squeeze)---<br><br>\n",
+ total_text_bytes);
+ }
+ // Deallocate full-document prediction table
+ delete[] predict_tbl;
+
+ return DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ cld_hints,
+ allow_extended_lang,
+ flags | kCLDFlagSqueeze,
+ plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ resultchunkvector,
+ text_bytes,
+ is_reliable);
+ }
+ }
+ }
+
+ // Remove repetitive words if asked to
+ if (FlagRepeats(flags)) {
+ // Remove repetitive words
+ int newlen;
+ if (resultchunkvector != NULL) {
+ newlen = CheapRepWordsInplaceOverwrite(scriptspan.text,
+ scriptspan.text_bytes,
+ &hash, predict_tbl);
+ } else {
+ newlen = CheapRepWordsInplace(scriptspan.text, scriptspan.text_bytes,
+ &hash, predict_tbl);
+ }
+ scriptspan.text_bytes = newlen;
+ }
+
+ // Scoring depends on scriptspan buffer ALWAYS having
+ // leading space and off-the-end space space space NUL,
+ // DCHECK(scriptspan.text[0] == ' ');
+ // DCHECK(scriptspan.text[scriptspan.text_bytes + 0] == ' ');
+ // DCHECK(scriptspan.text[scriptspan.text_bytes + 1] == ' ');
+ // DCHECK(scriptspan.text[scriptspan.text_bytes + 2] == ' ');
+ // DCHECK(scriptspan.text[scriptspan.text_bytes + 3] == '\0');
+
+ // The real scoring
+ // Accumulate directly into the document total, or accmulate in one of four
+ // chunk totals. The purpose of the multiple chunk totals is to piece
+ // together short choppy pieces of text in alternating scripts. One total is
+ // dedicated to Latin text, one to Han text, and the other two are dynamicly
+ // assigned.
+
+ scoringcontext.ulscript = scriptspan.ulscript;
+ // FLAGS_cld2_html = scoringcontext.flags_cld2_html;
+
+ ScoreOneScriptSpan(scriptspan,
+ &scoringcontext,
+ &doc_tote,
+ resultchunkvector);
+
+ total_text_bytes += scriptspan.text_bytes;
+ } // End while (ss.GetOneScriptSpanLower())
+
+ // Deallocate full-document prediction table
+ delete[] predict_tbl;
+
+ if (FLAGS_cld2_html && !FLAGS_cld2_quiet) {
+ // If no forced <cr>, put one in front of dump
+ if (!scoringcontext.flags_cld2_cr) {fprintf(stderr, "<br>\n");}
+ doc_tote.Dump(stderr);
+ }
+
+
+ // If extended langauges are disallowed, remove them here
+ if (!allow_extended_lang) {
+ RemoveExtendedLanguages(&doc_tote);
+ }
+
+ // Force close pairs to one or the other
+ // If given, also update resultchunkvector
+ RefineScoredClosePairs(&doc_tote, resultchunkvector,
+ FLAGS_cld2_html, FLAGS_cld2_quiet);
+
+
+ // Calculate return results
+ // Find top three byte counts in tote heap
+ int reliable_percent3[3];
+
+ // Cannot use Add, etc. after sorting
+ doc_tote.Sort(3);
+
+ ExtractLangEtc(&doc_tote, total_text_bytes,
+ reliable_percent3, language3, percent3, normalized_score3,
+ text_bytes, is_reliable);
+
+ bool have_good_answer = false;
+ if (FlagFinish(flags)) {
+ // Force a result
+ have_good_answer = true;
+ } else if (total_text_bytes <= kShortTextThresh) {
+ // Don't recurse on short text -- we already did word scores
+ have_good_answer = true;
+ } else if (*is_reliable &&
+ (percent3[0] >= kGoodLang1Percent)) {
+ have_good_answer = true;
+ } else if (*is_reliable &&
+ ((percent3[0] + percent3[1]) >= kGoodLang1and2Percent)) {
+ have_good_answer = true;
+ }
+
+
+ if (have_good_answer) {
+ // This is the real, non-recursive return
+
+ // Move bytes for unreliable langs to another lang or UNKNOWN
+ RemoveUnreliableLanguages(&doc_tote, FLAGS_cld2_html, FLAGS_cld2_quiet);
+
+ // Redo the result extraction after the removal above
+ doc_tote.Sort(3);
+ ExtractLangEtc(&doc_tote, total_text_bytes,
+ reliable_percent3, language3, percent3, normalized_score3,
+ text_bytes, is_reliable);
+
+
+
+ Language summary_lang;
+ CalcSummaryLang(&doc_tote, total_text_bytes,
+ reliable_percent3, language3, percent3,
+ &summary_lang, is_reliable,
+ FLAGS_cld2_html, FLAGS_cld2_quiet);
+
+ if (FLAGS_cld2_html && !FLAGS_cld2_quiet) {
+ for (int i = 0; i < 3; ++i) {
+ if (language3[i] != UNKNOWN_LANGUAGE) {
+ fprintf(stderr, "%s.%dR(%d%%) ",
+ LanguageCode(language3[i]),
+ reliable_percent3[i],
+ percent3[i]);
+ }
+ }
+
+ fprintf(stderr, "%d bytes ", total_text_bytes);
+ fprintf(stderr, "= %s%c ",
+ LanguageName(summary_lang), *is_reliable ? ' ' : '*');
+ fprintf(stderr, "<br><br>\n");
+ }
+
+ // Slightly condensed if quiet
+ if (FLAGS_cld2_html && FLAGS_cld2_quiet) {
+ fprintf(stderr, "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ");
+ for (int i = 0; i < 3; ++i) {
+ if (language3[i] != UNKNOWN_LANGUAGE) {
+ fprintf(stderr, "&nbsp;&nbsp;%s %d%% ",
+ LanguageCode(language3[i]),
+ percent3[i]);
+ }
+ }
+ fprintf(stderr, "= %s%c ",
+ LanguageName(summary_lang), *is_reliable ? ' ' : '*');
+ fprintf(stderr, "<br>\n");
+ }
+
+ return summary_lang;
+ }
+
+ // Not a good answer -- do recursive call to refine
+ if ((FLAGS_cld2_html || FLAGS_dbgscore) && !FLAGS_cld2_quiet) {
+ // This is what we hope to improve on in the recursive call, if any
+ PrintLangs(stderr, language3, percent3, text_bytes, is_reliable);
+ }
+
+ // For restriction to Top40 + one, the one is 1st/2nd lang that is not Top40
+ // For this purpose, we treate "Ignore" as top40
+ Language new_plus_one = UNKNOWN_LANGUAGE;
+
+ if (total_text_bytes < kShortTextThresh) {
+ // Short text: Recursive call with top40 and short set
+ if (FLAGS_cld2_html || FLAGS_dbgscore) {
+ fprintf(stderr, "&nbsp;&nbsp;---text_bytes[%d] "
+ "Recursive(Top40/Rep/Short/Words)---<br><br>\n",
+ total_text_bytes);
+ }
+ return DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ cld_hints,
+ allow_extended_lang,
+ flags | kCLDFlagTop40 | kCLDFlagRepeats |
+ kCLDFlagShort | kCLDFlagUseWords | kCLDFlagFinish,
+ new_plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ resultchunkvector,
+ text_bytes,
+ is_reliable);
+ }
+
+ // Longer text: Recursive call with top40 set
+ if (FLAGS_cld2_html || FLAGS_dbgscore) {
+ fprintf(stderr,
+ "&nbsp;&nbsp;---text_bytes[%d] Recursive(Top40/Rep)---<br><br>\n",
+ total_text_bytes);
+ }
+ return DetectLanguageSummaryV2(
+ buffer,
+ buffer_length,
+ is_plain_text,
+ cld_hints,
+ allow_extended_lang,
+ flags | kCLDFlagTop40 | kCLDFlagRepeats |
+ kCLDFlagFinish,
+ new_plus_one,
+ language3,
+ percent3,
+ normalized_score3,
+ resultchunkvector,
+ text_bytes,
+ is_reliable);
+}
+
+
+// For debugging and wrappers. Not thread safe.
+static char temp_detectlanguageversion[32];
+
+// Return version text string
+// String is "code_version - data_build_date"
+const char* DetectLanguageVersion() {
+ if (kScoringtables.quadgram_obj == NULL) {return "";}
+ sprintf(temp_detectlanguageversion,
+ "V2.0 - %u", kScoringtables.quadgram_obj->kCLDTableBuildDate);
+ return temp_detectlanguageversion;
+}
+
+
+} // End namespace CLD2
diff --git a/browser/components/translation/cld2/internal/compact_lang_det_impl.h b/browser/components/translation/cld2/internal/compact_lang_det_impl.h
new file mode 100644
index 000000000..6cadb3d9f
--- /dev/null
+++ b/browser/components/translation/cld2/internal/compact_lang_det_impl.h
@@ -0,0 +1,183 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#ifndef I18N_ENCODINGS_COMPACT_LANG_DET_COMPACT_LANG_DET_IMPL_H_
+#define I18N_ENCODINGS_COMPACT_LANG_DET_COMPACT_LANG_DET_IMPL_H_
+
+#include <vector>
+
+#include "../public/compact_lang_det.h" // For CLDHints, ResultChunkVector
+#include "integral_types.h"
+#include "lang_script.h"
+
+namespace CLD2 {
+
+// Internal use flags
+static const int kCLDFlagFinish = 1;
+static const int kCLDFlagSqueeze = 2;
+static const int kCLDFlagRepeats = 4;
+static const int kCLDFlagTop40 = 8;
+static const int kCLDFlagShort = 16;
+static const int kCLDFlagHint = 32;
+static const int kCLDFlagUseWords = 64;
+static const int kCLDFlagUNUSED = 128;
+
+// Public use flags, debug output controls, defined in compact_lang_det.h
+// 0x0100 and above
+
+/***
+
+Flag meanings:
+
+Flags are used in the context of a recursive call from Detect to itself,
+trying to deal in a more restrictive way with input that was not reliably
+identified in the top-level call.
+
+Finish -- Do not further recurse; return whatever result ensues, even if it is
+ unreliable. Typically set in any recursive call to take a second try
+ on unreliable text.
+
+Squeeze -- For each text run, do an inplace cheapsqueeze to remove chunks of
+ highly repetitive text and chunks of text with too many 1- and
+ 2-letter words. This avoids scoring repetitive or useless non-text
+ crap in large files such bogus JPEGs within an HTML file.
+
+Repeats -- When scoring a text run, do a cheap prediction of each character
+ and do not score a unigram/quadgram if the last character of same is
+ correctly predicted. This is a slower, finer-grained form of
+ cheapsqueeze, typically used when the first pass got unreliable
+ results.
+
+Top40 -- Restrict the set of scored languages to the Google "Top 40", which is
+ actually 38 languages. This gets rid of about 110 languages that
+ represent about 0.7% of the web. Typically used when the first pass
+ got unreliable results.
+
+Short -- DEPRICATED, unused
+
+Hint -- EXPERIMENTAL flag for compact_lang_det_test.cc to indicate a language
+ hint supplied in parameter plus_one.
+
+UseWords -- In additon to scoring quad/uni/nil-grams, score complete words
+
+
+
+Tentative decision logic:
+
+In the middle of first pass -- After 4KB of text, look at the front 256 bytes
+ of every full 4KB buffer. If it compresses very well (say 3:1) or has
+ lots of spaces (say 1 of every 4 bytes), assume that the input is
+ large and contains lots of bogus non-text. Recurse, passing the
+ Squeeze flag to strip out chunks of this non-text.
+
+At the end of the first pass --
+ If the top language is reliable and >= 70% of the document, return.
+ Else if the top language is reliable and top+2nd >= say 94%, return.
+ Else, either the top language is not reliable or there is a lot of
+ other crap.
+***/
+
+
+ // Scan interchange-valid UTF-8 bytes and detect most likely language,
+ // or set of languages.
+ //
+ // Design goals:
+ // Skip over big stretches of HTML tags
+ // Able to return ranges of different languages
+ // Relatively small tables and relatively fast processing
+ // Thread safe
+ //
+
+ typedef struct {
+ int perscript_count;
+ const Language* perscript_lang;
+ } PerScriptPair;
+
+ typedef struct {
+ // Constants for hashing 4-7 byte quadgram to 32 bits
+ const int kQuadHashB4Shift;
+ const int kQuadHashB4bShift;
+ const int kQuadHashB5Shift;
+ const int kQuadHashB5bShift;
+ // Constants for hashing 32 bits to kQuadKeyTable subscript/key
+ const int kHashvalToSubShift;
+ const uint32 kHashvalToSubMask;
+ const int kHashvalToKeyShift;
+ const uint32 kHashvalToKeyMask;
+ const int kHashvalAssociativity;
+ // Pointers to the actual tables
+ const PerScriptPair* kPerScriptPair;
+ const uint16* kQuadKeyTable;
+ const uint32* kQuadValueTable;
+ } LangDetObj;
+
+ // For HTML documents, tags are skipped, along with <script> ... </script>
+ // and <style> ... </style> sequences, and entities are expanded.
+ //
+ // We distinguish between bytes of the raw input buffer and bytes of non-tag
+ // text letters. Since tags can be over 50% of the bytes of an HTML Page,
+ // and are nearly all seven-bit ASCII English, we prefer to distinguish
+ // language mixture fractions based on just the non-tag text.
+ //
+ // Inputs: text and text_length
+ // is_plain_text if true says to NOT parse/skip HTML tags nor entities
+ // Outputs:
+ // language3 is an array of the top 3 languages or UNKNOWN_LANGUAGE
+ // percent3 is an array of the text percentages 0..100 of the top 3 languages
+ // normalized_score3 is an array of internal scores, normalized to the
+ // average score for each language over a body of training text. A
+ // normalized score significantly away from 1.0 indicates very skewed text
+ // or gibberish.
+ //
+ // text_bytes is the amount of non-tag/letters-only text found
+ // is_reliable set true if the returned Language is at least 2**30 times more
+ // probable then the second-best Language
+ //
+ // Return value: the most likely Language for the majority of the input text
+ // Length 0 input and text with no reliable letter sequences returns
+ // UNKNOWN_LANGUAGE
+ //
+ // Subsetting: For fast detection over large documents, these routines will
+ // only scan up to a fixed limit (currently 160KB of non-tag letters).
+ //
+
+ Language DetectLanguageSummaryV2(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const CLDHints* cld_hints,
+ bool allow_extended_lang,
+ int flags,
+ Language plus_one,
+ Language* language3,
+ int* percent3,
+ double* normalized_score3,
+ ResultChunkVector* resultchunkvector,
+ int* text_bytes,
+ bool* is_reliable);
+
+ // For unit testing:
+ // Remove portions of text that have a high density of spaces, or that are
+ // overly repetitive, squeezing the remaining text in-place to the front
+ // of the input buffer.
+ // Return the new, possibly-shorter length
+ int CheapSqueezeInplace(char* isrc, int srclen, int ichunksize);
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_COMPACT_LANG_DET_COMPACT_LANG_DET_IMPL_H_
diff --git a/browser/components/translation/cld2/internal/debug.h b/browser/components/translation/cld2/internal/debug.h
new file mode 100644
index 000000000..471a35d6c
--- /dev/null
+++ b/browser/components/translation/cld2/internal/debug.h
@@ -0,0 +1,58 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+// Produces debugging output for CLD2. See debug_empty.h for suppressing this.
+
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_DEBUG_H_
+#define I18N_ENCODINGS_CLD2_INTERNAL_DEBUG_H_
+
+#include <string>
+#include "scoreonescriptspan.h"
+
+namespace CLD2 {
+
+// For showing one chunk
+void CLD2_Debug(const char* text,
+ int lo_offset,
+ int hi_offset,
+ bool more_to_come, bool score_cjk,
+ const ScoringHitBuffer* hitbuffer,
+ const ScoringContext* scoringcontext,
+ const ChunkSpan* cspan,
+ const ChunkSummary* chunksummary);
+
+// For showing all chunks
+void CLD2_Debug2(const char* text,
+ bool more_to_come, bool score_cjk,
+ const ScoringHitBuffer* hitbuffer,
+ const ScoringContext* scoringcontext,
+ const SummaryBuffer* summarybuffer);
+
+std::string GetPlainEscapedText(const std::string& txt);
+std::string GetHtmlEscapedText(const std::string& txt);
+std::string GetColorHtmlEscapedText(Language lang, const std::string& txt);
+std::string GetLangColorHtmlEscapedText(Language lang, const std::string& txt);
+
+void DumpResultChunkVector(FILE* f, const char* src,
+ ResultChunkVector* resultchunkvector);
+
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_DEBUG_H_
+
diff --git a/browser/components/translation/cld2/internal/debug_empty.cc b/browser/components/translation/cld2/internal/debug_empty.cc
new file mode 100644
index 000000000..19f17e312
--- /dev/null
+++ b/browser/components/translation/cld2/internal/debug_empty.cc
@@ -0,0 +1,64 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+// Compile this in instead of debug.cc to remove code for debug output
+//
+
+#include "debug.h"
+#include <string>
+
+using namespace std;
+
+namespace CLD2 {
+
+string GetPlainEscapedText(const string& txt) {return string("");}
+
+string GetHtmlEscapedText(const string& txt) {return string("");}
+
+string GetColorHtmlEscapedText(Language lang, const string& txt) {
+ return string("");
+}
+
+string GetLangColorHtmlEscapedText(Language lang, const string& txt) {
+ return string("");
+}
+
+
+// For showing one chunk
+// Print debug output for one scored chunk
+// Optionally print out per-chunk scoring information
+// In degenerate cases, hitbuffer and cspan can be NULL
+void CLD2_Debug(const char* text,
+ int lo_offset,
+ int hi_offset,
+ bool more_to_come, bool score_cjk,
+ const ScoringHitBuffer* hitbuffer,
+ const ScoringContext* scoringcontext,
+ const ChunkSpan* cspan,
+ const ChunkSummary* chunksummary) {}
+
+// For showing all chunks
+void CLD2_Debug2(const char* text,
+ bool more_to_come, bool score_cjk,
+ const ScoringHitBuffer* hitbuffer,
+ const ScoringContext* scoringcontext,
+ const SummaryBuffer* summarybuffer) {}
+
+void DumpResultChunkVector(FILE* f, const char* src,
+ ResultChunkVector* resultchunkvector) {}
+
+} // End namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/fixunicodevalue.cc b/browser/components/translation/cld2/internal/fixunicodevalue.cc
new file mode 100644
index 000000000..03edf7c51
--- /dev/null
+++ b/browser/components/translation/cld2/internal/fixunicodevalue.cc
@@ -0,0 +1,54 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Routine that maps a Unicode code point to an interchange-valid one
+//
+
+#include "fixunicodevalue.h"
+#include "integral_types.h"
+
+namespace CLD2 {
+
+// Guarantees that the resulting output value is interchange valid
+// 00-FF; map to spaces or MS CP1252
+// D800-DFFF; surrogates
+// FDD0-FDEF; non-characters
+// xxFFFE-xxFFFF; non-characters
+char32 FixUnicodeValue(char32 uv) {
+ uint32 uuv = static_cast<uint32>(uv);
+ if (uuv < 0x0100) {
+ return kMapFullMicrosoft1252OrSpace[uuv];
+ }
+ if (uuv < 0xD800) {
+ return uv;
+ }
+ if ((uuv & ~0x0F) == 0xFDD0) { // non-characters
+ return 0xFFFD;
+ }
+ if ((uuv & ~0x0F) == 0xFDE0) { // non-characters
+ return 0xFFFD;
+ }
+ if ((uuv & 0x00FFFE) == 0xFFFE) { // non-characters
+ return 0xFFFD;
+ }
+ if ((0xE000 <= uuv) && (uuv <= 0x10FFFF)) {
+ return uv;
+ }
+ // surrogates and negative and > 0x10FFFF all land here
+ return 0xFFFD;
+}
+
+} // End namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/fixunicodevalue.h b/browser/components/translation/cld2/internal/fixunicodevalue.h
new file mode 100644
index 000000000..5d0f7f55f
--- /dev/null
+++ b/browser/components/translation/cld2/internal/fixunicodevalue.h
@@ -0,0 +1,68 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Routine that maps a Unicode code point to an interchange-valid one
+//
+// Table that maps MS CP1252 bytes 00-FF to their corresponding Unicode
+// code points. C0 and C1 control codes that are not interchange-valid
+// are mapped to spaces.
+
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_FIXUNICODEVALUE_H__
+#define I18N_ENCODINGS_CLD2_INTERNAL_FIXUNICODEVALUE_H__
+
+#include "integral_types.h" // for char32
+#include "port.h"
+
+namespace CLD2 {
+
+// Map byte value 0000-00FF to char32
+// Maps C0 control codes (other than CR LF HT FF) to space [29 instances including DEL=0x7F]
+// Maps C1 control codes to CP1252 [27 instances] or space [5 instances]
+static const char32 kMapFullMicrosoft1252OrSpace[256] = {
+ 0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20, 0x20,0x09,0x0a,0x20, 0x0c,0x0d,0x20,0x20, // 00
+ 0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20, 0x20,0x20,0x20,0x20,
+ 0x20,0x21,0x22,0x23, 0x24,0x25,0x26,0x27, 0x28,0x29,0x2a,0x2b, 0x2c,0x2d,0x2e,0x2f,
+ 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, 0x38,0x39,0x3a,0x3b, 0x3c,0x3d,0x3e,0x3f,
+
+ 0x40,0x41,0x42,0x43, 0x44,0x45,0x46,0x47, 0x48,0x49,0x4a,0x4b, 0x4c,0x4d,0x4e,0x4f, // 40
+ 0x50,0x51,0x52,0x53, 0x54,0x55,0x56,0x57, 0x58,0x59,0x5a,0x5b, 0x5c,0x5d,0x5e,0x5f,
+ 0x60,0x61,0x62,0x63, 0x64,0x65,0x66,0x67, 0x68,0x69,0x6a,0x6b, 0x6c,0x6d,0x6e,0x6f,
+ 0x70,0x71,0x72,0x73, 0x74,0x75,0x76,0x77, 0x78,0x79,0x7a,0x7b, 0x7c,0x7d,0x7e,0x20,
+
+ 0x20ac,0x20,0x201a,0x0192, 0x201e,0x2026,0x2020,0x2021, // 80
+ 0x02c6,0x2030,0x0160,0x2039, 0x0152,0x20,0x017d,0x20,
+ 0x20,0x2018,0x2019,0x201c, 0x201d,0x2022,0x2013,0x2014,
+ 0x02dc,0x2122,0x0161,0x203a, 0x0153,0x20,0x017e,0x0178,
+ 0xa0,0xa1,0xa2,0xa3, 0xa4,0xa5,0xa6,0xa7, 0xa8,0xa9,0xaa,0xab, 0xac,0xad,0xae,0xaf, // A0
+ 0xb0,0xb1,0xb2,0xb3, 0xb4,0xb5,0xb6,0xb7, 0xb8,0xb9,0xba,0xbb, 0xbc,0xbd,0xbe,0xbf,
+
+ 0xc0,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7, 0xc8,0xc9,0xca,0xcb, 0xcc,0xcd,0xce,0xcf, // C0
+ 0xd0,0xd1,0xd2,0xd3, 0xd4,0xd5,0xd6,0xd7, 0xd8,0xd9,0xda,0xdb, 0xdc,0xdd,0xde,0xdf,
+ 0xe0,0xe1,0xe2,0xe3, 0xe4,0xe5,0xe6,0xe7, 0xe8,0xe9,0xea,0xeb, 0xec,0xed,0xee,0xef,
+ 0xf0,0xf1,0xf2,0xf3, 0xf4,0xf5,0xf6,0xf7, 0xf8,0xf9,0xfa,0xfb, 0xfc,0xfd,0xfe,0xff,
+};
+
+// Guarantees that the resulting output value is interchange valid
+// 00-FF; map to spaces or MS CP1252
+// D800-DFFF; surrogates
+// FDD0-FDEF; non-characters
+// xxFFFE-xxFFFF; non-characters
+char32 FixUnicodeValue(char32 uv);
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_FIXUNICODEVALUE_H__
+
diff --git a/browser/components/translation/cld2/internal/generated_distinct_bi_0.cc b/browser/components/translation/cld2/internal/generated_distinct_bi_0.cc
new file mode 100644
index 000000000..e9e763abb
--- /dev/null
+++ b/browser/components/translation/cld2/internal/generated_distinct_bi_0.cc
@@ -0,0 +1,52 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Degenerate CLD2 scoring lookup table, for use as placeholder
+//
+#include "cld2tablesummary.h"
+
+namespace CLD2 {
+
+static const uint32 kDistinctBiTableBuildDate = 20130101; // yyyymmdd
+static const uint32 kDistinctBiTableSize = 1; // Total Bucket count
+static const uint32 kDistinctBiTableKeyMask = 0xffffffff; // Mask hash key
+static const char* const kDistinctBiTableRecognizedLangScripts = "";
+
+// Empty table
+static const IndirectProbBucket4 kDistinctBiTable[kDistinctBiTableSize] = {
+ // key[4], words[4] in UTF-8
+ // value[4]
+ { {0x00000000,0x00000000,0x00000000,0x00000000}}, // [000]
+};
+
+static const uint32 kDistinctBiTableSizeOne = 1; // One-langprob count
+static const uint32 kDistinctBiTableIndSize = 1; // Largest subscript
+static const uint32 kDistinctBiTableInd[kDistinctBiTableIndSize] = {
+ // [0000]
+ 0x00000000, };
+
+extern const CLD2TableSummary kDistinctBiTable_obj = {
+ kDistinctBiTable,
+ kDistinctBiTableInd,
+ kDistinctBiTableSizeOne,
+ kDistinctBiTableSize,
+ kDistinctBiTableKeyMask,
+ kDistinctBiTableBuildDate,
+ kDistinctBiTableRecognizedLangScripts,
+};
+
+} // End namespace CLD2
+
+// End of generated tables
diff --git a/browser/components/translation/cld2/internal/generated_entities.cc b/browser/components/translation/cld2/internal/generated_entities.cc
new file mode 100644
index 000000000..26a6f7bb3
--- /dev/null
+++ b/browser/components/translation/cld2/internal/generated_entities.cc
@@ -0,0 +1,294 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// generated_entities.cc
+// Machine generated. Do Not Edit.
+//
+// Declarations for HTML entities recognized by CLD2
+//
+#include "generated_ulscript.h" // for CharIntPair
+
+namespace CLD2 {
+
+// Alphabetical order for binary search
+extern const int kNameToEntitySize = 265;
+extern const CharIntPair kNameToEntity[kNameToEntitySize] = {
+ {"AElig", 198},
+ {"AMP", 38},
+ {"Aacute", 193},
+ {"Acirc", 194},
+ {"Agrave", 192},
+ {"Alpha", 913},
+ {"Aring", 197},
+ {"Atilde", 195},
+ {"Auml", 196},
+ {"Beta", 914},
+ {"Ccaron", 268},
+ {"Ccedil", 199},
+ {"Chi", 935},
+ {"Dagger", 8225},
+ {"Delta", 916},
+ {"ETH", 208},
+ {"Eacute", 201},
+ {"Ecaron", 282},
+ {"Ecirc", 202},
+ {"Egrave", 200},
+ {"Epsilon", 917},
+ {"Eta", 919},
+ {"Euml", 203},
+ {"GT", 62},
+ {"Gamma", 915},
+ {"Iacute", 205},
+ {"Icirc", 206},
+ {"Igrave", 204},
+ {"Iota", 921},
+ {"Iuml", 207},
+ {"Kappa", 922},
+ {"LT", 60},
+ {"Lambda", 923},
+ {"Mu", 924},
+ {"Ntilde", 209},
+ {"Nu", 925},
+ {"OElig", 338},
+ {"Oacute", 211},
+ {"Ocirc", 212},
+ {"Ograve", 210},
+ {"Omega", 937},
+ {"Omicron", 927},
+ {"Oslash", 216},
+ {"Otilde", 213},
+ {"Ouml", 214},
+ {"Phi", 934},
+ {"Pi", 928},
+ {"Prime", 8243},
+ {"Psi", 936},
+ {"QUOT", 34},
+ {"Rcaron", 344},
+ {"Rho", 929},
+ {"Scaron", 352},
+ {"Sigma", 931},
+ {"THORN", 222},
+ {"Tau", 932},
+ {"Theta", 920},
+ {"Uacute", 218},
+ {"Ucirc", 219},
+ {"Ugrave", 217},
+ {"Upsilon", 933},
+ {"Uuml", 220},
+ {"Xi", 926},
+ {"Yacute", 221},
+ {"Yuml", 376},
+ {"Zeta", 918},
+ {"aacute", 225},
+ {"acirc", 226},
+ {"acute", 180},
+ {"aelig", 230},
+ {"agrave", 224},
+ {"alefsym", 8501},
+ {"alpha", 945},
+ {"amp", 38},
+ {"and", 8743},
+ {"ang", 8736},
+ {"apos", 39},
+ {"aring", 229},
+ {"asymp", 8776},
+ {"atilde", 227},
+ {"auml", 228},
+ {"bdquo", 8222},
+ {"beta", 946},
+ {"brvbar", 166},
+ {"bull", 8226},
+ {"cap", 8745},
+ {"ccaron", 269},
+ {"ccedil", 231},
+ {"cedil", 184},
+ {"cent", 162},
+ {"chi", 967},
+ {"circ", 710},
+ {"clubs", 9827},
+ {"cong", 8773},
+ {"copy", 169},
+ {"crarr", 8629},
+ {"cup", 8746},
+ {"curren", 164},
+ {"dArr", 8659},
+ {"dagger", 8224},
+ {"darr", 8595},
+ {"deg", 176},
+ {"delta", 948},
+ {"diams", 9830},
+ {"divide", 247},
+ {"eacute", 233},
+ {"ecaron", 283},
+ {"ecirc", 234},
+ {"egrave", 232},
+ {"emdash", 8212},
+ {"empty", 8709},
+ {"emsp", 8195},
+ {"endash", 8211},
+ {"ensp", 8194},
+ {"epsilon", 949},
+ {"equiv", 8801},
+ {"eta", 951},
+ {"eth", 240},
+ {"euml", 235},
+ {"euro", 8364},
+ {"exist", 8707},
+ {"fnof", 402},
+ {"forall", 8704},
+ {"frac12", 189},
+ {"frac14", 188},
+ {"frac34", 190},
+ {"frasl", 8260},
+ {"gamma", 947},
+ {"ge", 8805},
+ {"gt", 62},
+ {"hArr", 8660},
+ {"harr", 8596},
+ {"hearts", 9829},
+ {"hellip", 8230},
+ {"iacute", 237},
+ {"icirc", 238},
+ {"iexcl", 161},
+ {"igrave", 236},
+ {"image", 8465},
+ {"infin", 8734},
+ {"int", 8747},
+ {"iota", 953},
+ {"iquest", 191},
+ {"isin", 8712},
+ {"iuml", 239},
+ {"kappa", 954},
+ {"lArr", 8656},
+ {"lambda", 955},
+ {"lang", 9001},
+ {"laquo", 171},
+ {"larr", 8592},
+ {"lceil", 8968},
+ {"ldquo", 8220},
+ {"le", 8804},
+ {"lfloor", 8970},
+ {"lowast", 8727},
+ {"loz", 9674},
+ {"lrm", 8206},
+ {"lsaquo", 8249},
+ {"lsquo", 8216},
+ {"lt", 60},
+ {"macr", 175},
+ {"mdash", 8212},
+ {"micro", 181},
+ {"middot", 183},
+ {"minus", 8722},
+ {"mu", 956},
+ {"nabla", 8711},
+ {"nbsp", 160},
+ {"ndash", 8211},
+ {"ne", 8800},
+ {"ni", 8715},
+ {"not", 172},
+ {"notin", 8713},
+ {"nsub", 8836},
+ {"ntilde", 241},
+ {"nu", 957},
+ {"oacute", 243},
+ {"ocirc", 244},
+ {"oelig", 339},
+ {"ograve", 242},
+ {"oline", 8254},
+ {"omega", 969},
+ {"omicron", 959},
+ {"oplus", 8853},
+ {"or", 8744},
+ {"ordf", 170},
+ {"ordm", 186},
+ {"oslash", 248},
+ {"otilde", 245},
+ {"otimes", 8855},
+ {"ouml", 246},
+ {"para", 182},
+ {"part", 8706},
+ {"permil", 8240},
+ {"perp", 8869},
+ {"phi", 966},
+ {"pi", 960},
+ {"piv", 982},
+ {"plusmn", 177},
+ {"pound", 163},
+ {"prime", 8242},
+ {"prod", 8719},
+ {"prop", 8733},
+ {"psi", 968},
+ {"quot", 34},
+ {"rArr", 8658},
+ {"radic", 8730},
+ {"rang", 9002},
+ {"raquo", 187},
+ {"rarr", 8594},
+ {"rcaron", 345},
+ {"rceil", 8969},
+ {"rdquo", 8221},
+ {"real", 8476},
+ {"reg", 174},
+ {"rfloor", 8971},
+ {"rho", 961},
+ {"rlm", 8207},
+ {"rsaquo", 8250},
+ {"rsquo", 8217},
+ {"sbquo", 8218},
+ {"scaron", 353},
+ {"sdot", 8901},
+ {"sect", 167},
+ {"shy", 173},
+ {"sigma", 963},
+ {"sigmaf", 962},
+ {"sim", 8764},
+ {"spades", 9824},
+ {"sub", 8834},
+ {"sube", 8838},
+ {"sum", 8721},
+ {"sup", 8835},
+ {"sup1", 185},
+ {"sup2", 178},
+ {"sup3", 179},
+ {"supe", 8839},
+ {"szlig", 223},
+ {"tau", 964},
+ {"there4", 8756},
+ {"theta", 952},
+ {"thetasym", 977},
+ {"thinsp", 8201},
+ {"thorn", 254},
+ {"tilde", 732},
+ {"times", 215},
+ {"trade", 8482},
+ {"uArr", 8657},
+ {"uacute", 250},
+ {"uarr", 8593},
+ {"ucirc", 251},
+ {"ugrave", 249},
+ {"uml", 168},
+ {"upsih", 978},
+ {"upsilon", 965},
+ {"uuml", 252},
+ {"weierp", 8472},
+ {"xi", 958},
+ {"yacute", 253},
+ {"yen", 165},
+ {"yuml", 255},
+ {"zeta", 950},
+ {"zwj", 8205},
+ {"zwnj", 8204},
+};
+
+} // namespace CLD2
diff --git a/browser/components/translation/cld2/internal/generated_language.cc b/browser/components/translation/cld2/internal/generated_language.cc
new file mode 100644
index 000000000..dc4df67cc
--- /dev/null
+++ b/browser/components/translation/cld2/internal/generated_language.cc
@@ -0,0 +1,4680 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// generated_language.cc
+// Machine generated. Do Not Edit.
+//
+// Declarations for languages recognized by CLD2
+//
+
+#include "generated_language.h"
+#include "generated_ulscript.h"
+
+namespace CLD2 {
+
+// Subscripted by enum Language
+extern const int kLanguageToNameSize = 614;
+extern const char* const kLanguageToName[kLanguageToNameSize] = {
+ "ENGLISH", // 0 en
+ "DANISH", // 1 da
+ "DUTCH", // 2 nl
+ "FINNISH", // 3 fi
+ "FRENCH", // 4 fr
+ "GERMAN", // 5 de
+ "HEBREW", // 6 he
+ "ITALIAN", // 7 it
+ "Japanese", // 8 ja
+ "Korean", // 9 ko
+ "NORWEGIAN", // 10 no
+ "POLISH", // 11 pl
+ "PORTUGUESE", // 12 pt
+ "RUSSIAN", // 13 ru
+ "SPANISH", // 14 es
+ "SWEDISH", // 15 sv
+ "Chinese", // 16 zh
+ "CZECH", // 17 cs
+ "GREEK", // 18 el
+ "ICELANDIC", // 19 is
+ "LATVIAN", // 20 lv
+ "LITHUANIAN", // 21 lt
+ "ROMANIAN", // 22 ro
+ "HUNGARIAN", // 23 hu
+ "ESTONIAN", // 24 et
+ "Ignore", // 25 xxx
+ "Unknown", // 26 un
+ "BULGARIAN", // 27 bg
+ "CROATIAN", // 28 hr
+ "SERBIAN", // 29 sr
+ "IRISH", // 30 ga
+ "GALICIAN", // 31 gl
+ "TAGALOG", // 32 tl
+ "TURKISH", // 33 tr
+ "UKRAINIAN", // 34 uk
+ "HINDI", // 35 hi
+ "MACEDONIAN", // 36 mk
+ "BENGALI", // 37 bn
+ "INDONESIAN", // 38 id
+ "LATIN", // 39 la
+ "MALAY", // 40 ms
+ "MALAYALAM", // 41 ml
+ "WELSH", // 42 cy
+ "NEPALI", // 43 ne
+ "TELUGU", // 44 te
+ "ALBANIAN", // 45 sq
+ "TAMIL", // 46 ta
+ "BELARUSIAN", // 47 be
+ "JAVANESE", // 48 jw
+ "OCCITAN", // 49 oc
+ "URDU", // 50 ur
+ "BIHARI", // 51 bh
+ "GUJARATI", // 52 gu
+ "THAI", // 53 th
+ "ARABIC", // 54 ar
+ "CATALAN", // 55 ca
+ "ESPERANTO", // 56 eo
+ "BASQUE", // 57 eu
+ "INTERLINGUA", // 58 ia
+ "KANNADA", // 59 kn
+ "PUNJABI", // 60 pa
+ "SCOTS_GAELIC", // 61 gd
+ "SWAHILI", // 62 sw
+ "SLOVENIAN", // 63 sl
+ "MARATHI", // 64 mr
+ "MALTESE", // 65 mt
+ "VIETNAMESE", // 66 vi
+ "FRISIAN", // 67 fy
+ "SLOVAK", // 68 sk
+ "ChineseT", // 69 zh-Hant
+ "FAROESE", // 70 fo
+ "SUNDANESE", // 71 su
+ "UZBEK", // 72 uz
+ "AMHARIC", // 73 am
+ "AZERBAIJANI", // 74 az
+ "GEORGIAN", // 75 ka
+ "TIGRINYA", // 76 ti
+ "PERSIAN", // 77 fa
+ "BOSNIAN", // 78 bs
+ "SINHALESE", // 79 si
+ "NORWEGIAN_N", // 80 nn
+ "81", // 81
+ "82", // 82
+ "XHOSA", // 83 xh
+ "ZULU", // 84 zu
+ "GUARANI", // 85 gn
+ "SESOTHO", // 86 st
+ "TURKMEN", // 87 tk
+ "KYRGYZ", // 88 ky
+ "BRETON", // 89 br
+ "TWI", // 90 tw
+ "YIDDISH", // 91 yi
+ "92", // 92
+ "SOMALI", // 93 so
+ "UIGHUR", // 94 ug
+ "KURDISH", // 95 ku
+ "MONGOLIAN", // 96 mn
+ "ARMENIAN", // 97 hy
+ "LAOTHIAN", // 98 lo
+ "SINDHI", // 99 sd
+ "RHAETO_ROMANCE", // 100 rm
+ "AFRIKAANS", // 101 af
+ "LUXEMBOURGISH", // 102 lb
+ "BURMESE", // 103 my
+ "KHMER", // 104 km
+ "TIBETAN", // 105 bo
+ "DHIVEHI", // 106 dv
+ "CHEROKEE", // 107 chr
+ "SYRIAC", // 108 syr
+ "LIMBU", // 109 lif
+ "ORIYA", // 110 or
+ "ASSAMESE", // 111 as
+ "CORSICAN", // 112 co
+ "INTERLINGUE", // 113 ie
+ "KAZAKH", // 114 kk
+ "LINGALA", // 115 ln
+ "116", // 116
+ "PASHTO", // 117 ps
+ "QUECHUA", // 118 qu
+ "SHONA", // 119 sn
+ "TAJIK", // 120 tg
+ "TATAR", // 121 tt
+ "TONGA", // 122 to
+ "YORUBA", // 123 yo
+ "124", // 124
+ "125", // 125
+ "126", // 126
+ "127", // 127
+ "MAORI", // 128 mi
+ "WOLOF", // 129 wo
+ "ABKHAZIAN", // 130 ab
+ "AFAR", // 131 aa
+ "AYMARA", // 132 ay
+ "BASHKIR", // 133 ba
+ "BISLAMA", // 134 bi
+ "DZONGKHA", // 135 dz
+ "FIJIAN", // 136 fj
+ "GREENLANDIC", // 137 kl
+ "HAUSA", // 138 ha
+ "HAITIAN_CREOLE", // 139 ht
+ "INUPIAK", // 140 ik
+ "INUKTITUT", // 141 iu
+ "KASHMIRI", // 142 ks
+ "KINYARWANDA", // 143 rw
+ "MALAGASY", // 144 mg
+ "NAURU", // 145 na
+ "OROMO", // 146 om
+ "RUNDI", // 147 rn
+ "SAMOAN", // 148 sm
+ "SANGO", // 149 sg
+ "SANSKRIT", // 150 sa
+ "SISWANT", // 151 ss
+ "TSONGA", // 152 ts
+ "TSWANA", // 153 tn
+ "VOLAPUK", // 154 vo
+ "ZHUANG", // 155 za
+ "KHASI", // 156 kha
+ "SCOTS", // 157 sco
+ "GANDA", // 158 lg
+ "MANX", // 159 gv
+ "MONTENEGRIN", // 160 sr-ME
+ "AKAN", // 161 ak
+ "IGBO", // 162 ig
+ "MAURITIAN_CREOLE", // 163 mfe
+ "HAWAIIAN", // 164 haw
+ "CEBUANO", // 165 ceb
+ "EWE", // 166 ee
+ "GA", // 167 gaa
+ "HMONG", // 168 hmn
+ "KRIO", // 169 kri
+ "LOZI", // 170 loz
+ "LUBA_LULUA", // 171 lua
+ "LUO_KENYA_AND_TANZANIA", // 172 luo
+ "NEWARI", // 173 new
+ "NYANJA", // 174 ny
+ "OSSETIAN", // 175 os
+ "PAMPANGA", // 176 pam
+ "PEDI", // 177 nso
+ "RAJASTHANI", // 178 raj
+ "SESELWA", // 179 crs
+ "TUMBUKA", // 180 tum
+ "VENDA", // 181 ve
+ "WARAY_PHILIPPINES", // 182 war
+ "183", // 183
+ "184", // 184
+ "185", // 185
+ "186", // 186
+ "187", // 187
+ "188", // 188
+ "189", // 189
+ "190", // 190
+ "191", // 191
+ "192", // 192
+ "193", // 193
+ "194", // 194
+ "195", // 195
+ "196", // 196
+ "197", // 197
+ "198", // 198
+ "199", // 199
+ "200", // 200
+ "201", // 201
+ "202", // 202
+ "203", // 203
+ "204", // 204
+ "205", // 205
+ "206", // 206
+ "207", // 207
+ "208", // 208
+ "209", // 209
+ "210", // 210
+ "211", // 211
+ "212", // 212
+ "213", // 213
+ "214", // 214
+ "215", // 215
+ "216", // 216
+ "217", // 217
+ "218", // 218
+ "219", // 219
+ "220", // 220
+ "221", // 221
+ "222", // 222
+ "223", // 223
+ "224", // 224
+ "225", // 225
+ "226", // 226
+ "227", // 227
+ "228", // 228
+ "229", // 229
+ "230", // 230
+ "231", // 231
+ "232", // 232
+ "233", // 233
+ "234", // 234
+ "235", // 235
+ "236", // 236
+ "237", // 237
+ "238", // 238
+ "239", // 239
+ "240", // 240
+ "241", // 241
+ "242", // 242
+ "243", // 243
+ "244", // 244
+ "245", // 245
+ "246", // 246
+ "247", // 247
+ "248", // 248
+ "249", // 249
+ "250", // 250
+ "251", // 251
+ "252", // 252
+ "253", // 253
+ "254", // 254
+ "255", // 255
+ "256", // 256
+ "257", // 257
+ "258", // 258
+ "259", // 259
+ "260", // 260
+ "261", // 261
+ "262", // 262
+ "263", // 263
+ "264", // 264
+ "265", // 265
+ "266", // 266
+ "267", // 267
+ "268", // 268
+ "269", // 269
+ "270", // 270
+ "271", // 271
+ "272", // 272
+ "273", // 273
+ "274", // 274
+ "275", // 275
+ "276", // 276
+ "277", // 277
+ "278", // 278
+ "279", // 279
+ "280", // 280
+ "281", // 281
+ "282", // 282
+ "283", // 283
+ "284", // 284
+ "285", // 285
+ "286", // 286
+ "287", // 287
+ "288", // 288
+ "289", // 289
+ "290", // 290
+ "291", // 291
+ "292", // 292
+ "293", // 293
+ "294", // 294
+ "295", // 295
+ "296", // 296
+ "297", // 297
+ "298", // 298
+ "299", // 299
+ "300", // 300
+ "301", // 301
+ "302", // 302
+ "303", // 303
+ "304", // 304
+ "305", // 305
+ "306", // 306
+ "307", // 307
+ "308", // 308
+ "309", // 309
+ "310", // 310
+ "311", // 311
+ "312", // 312
+ "313", // 313
+ "314", // 314
+ "315", // 315
+ "316", // 316
+ "317", // 317
+ "318", // 318
+ "319", // 319
+ "320", // 320
+ "321", // 321
+ "322", // 322
+ "323", // 323
+ "324", // 324
+ "325", // 325
+ "326", // 326
+ "327", // 327
+ "328", // 328
+ "329", // 329
+ "330", // 330
+ "331", // 331
+ "332", // 332
+ "333", // 333
+ "334", // 334
+ "335", // 335
+ "336", // 336
+ "337", // 337
+ "338", // 338
+ "339", // 339
+ "340", // 340
+ "341", // 341
+ "342", // 342
+ "343", // 343
+ "344", // 344
+ "345", // 345
+ "346", // 346
+ "347", // 347
+ "348", // 348
+ "349", // 349
+ "350", // 350
+ "351", // 351
+ "352", // 352
+ "353", // 353
+ "354", // 354
+ "355", // 355
+ "356", // 356
+ "357", // 357
+ "358", // 358
+ "359", // 359
+ "360", // 360
+ "361", // 361
+ "362", // 362
+ "363", // 363
+ "364", // 364
+ "365", // 365
+ "366", // 366
+ "367", // 367
+ "368", // 368
+ "369", // 369
+ "370", // 370
+ "371", // 371
+ "372", // 372
+ "373", // 373
+ "374", // 374
+ "375", // 375
+ "376", // 376
+ "377", // 377
+ "378", // 378
+ "379", // 379
+ "380", // 380
+ "381", // 381
+ "382", // 382
+ "383", // 383
+ "384", // 384
+ "385", // 385
+ "386", // 386
+ "387", // 387
+ "388", // 388
+ "389", // 389
+ "390", // 390
+ "391", // 391
+ "392", // 392
+ "393", // 393
+ "394", // 394
+ "395", // 395
+ "396", // 396
+ "397", // 397
+ "398", // 398
+ "399", // 399
+ "400", // 400
+ "401", // 401
+ "402", // 402
+ "403", // 403
+ "404", // 404
+ "405", // 405
+ "406", // 406
+ "407", // 407
+ "408", // 408
+ "409", // 409
+ "410", // 410
+ "411", // 411
+ "412", // 412
+ "413", // 413
+ "414", // 414
+ "415", // 415
+ "416", // 416
+ "417", // 417
+ "418", // 418
+ "419", // 419
+ "420", // 420
+ "421", // 421
+ "422", // 422
+ "423", // 423
+ "424", // 424
+ "425", // 425
+ "426", // 426
+ "427", // 427
+ "428", // 428
+ "429", // 429
+ "430", // 430
+ "431", // 431
+ "432", // 432
+ "433", // 433
+ "434", // 434
+ "435", // 435
+ "436", // 436
+ "437", // 437
+ "438", // 438
+ "439", // 439
+ "440", // 440
+ "441", // 441
+ "442", // 442
+ "443", // 443
+ "444", // 444
+ "445", // 445
+ "446", // 446
+ "447", // 447
+ "448", // 448
+ "449", // 449
+ "450", // 450
+ "451", // 451
+ "452", // 452
+ "453", // 453
+ "454", // 454
+ "455", // 455
+ "456", // 456
+ "457", // 457
+ "458", // 458
+ "459", // 459
+ "460", // 460
+ "461", // 461
+ "462", // 462
+ "463", // 463
+ "464", // 464
+ "465", // 465
+ "466", // 466
+ "467", // 467
+ "468", // 468
+ "469", // 469
+ "470", // 470
+ "471", // 471
+ "472", // 472
+ "473", // 473
+ "474", // 474
+ "475", // 475
+ "476", // 476
+ "477", // 477
+ "478", // 478
+ "479", // 479
+ "480", // 480
+ "481", // 481
+ "482", // 482
+ "483", // 483
+ "484", // 484
+ "485", // 485
+ "486", // 486
+ "487", // 487
+ "488", // 488
+ "489", // 489
+ "490", // 490
+ "491", // 491
+ "492", // 492
+ "493", // 493
+ "494", // 494
+ "495", // 495
+ "496", // 496
+ "497", // 497
+ "498", // 498
+ "499", // 499
+ "500", // 500
+ "501", // 501
+ "502", // 502
+ "503", // 503
+ "504", // 504
+ "505", // 505
+ "NDEBELE", // 506 nr
+ "X_BORK_BORK_BORK", // 507 zzb
+ "X_PIG_LATIN", // 508 zzp
+ "X_HACKER", // 509 zzh
+ "X_KLINGON", // 510 tlh
+ "X_ELMER_FUDD", // 511 zze
+ "X_Common", // 512 xx-Zyyy
+ "X_Latin", // 513 xx-Latn
+ "X_Greek", // 514 xx-Grek
+ "X_Cyrillic", // 515 xx-Cyrl
+ "X_Armenian", // 516 xx-Armn
+ "X_Hebrew", // 517 xx-Hebr
+ "X_Arabic", // 518 xx-Arab
+ "X_Syriac", // 519 xx-Syrc
+ "X_Thaana", // 520 xx-Thaa
+ "X_Devanagari", // 521 xx-Deva
+ "X_Bengali", // 522 xx-Beng
+ "X_Gurmukhi", // 523 xx-Guru
+ "X_Gujarati", // 524 xx-Gujr
+ "X_Oriya", // 525 xx-Orya
+ "X_Tamil", // 526 xx-Taml
+ "X_Telugu", // 527 xx-Telu
+ "X_Kannada", // 528 xx-Knda
+ "X_Malayalam", // 529 xx-Mlym
+ "X_Sinhala", // 530 xx-Sinh
+ "X_Thai", // 531 xx-Thai
+ "X_Lao", // 532 xx-Laoo
+ "X_Tibetan", // 533 xx-Tibt
+ "X_Myanmar", // 534 xx-Mymr
+ "X_Georgian", // 535 xx-Geor
+ "X_Hangul", // 536 xx-Hang
+ "X_Ethiopic", // 537 xx-Ethi
+ "X_Cherokee", // 538 xx-Cher
+ "X_Canadian_Aboriginal", // 539 xx-Cans
+ "X_Ogham", // 540 xx-Ogam
+ "X_Runic", // 541 xx-Runr
+ "X_Khmer", // 542 xx-Khmr
+ "X_Mongolian", // 543 xx-Mong
+ "X_Hiragana", // 544 xx-Hira
+ "X_Katakana", // 545 xx-Kana
+ "X_Bopomofo", // 546 xx-Bopo
+ "X_Han", // 547 xx-Hani
+ "X_Yi", // 548 xx-Yiii
+ "X_Old_Italic", // 549 xx-Ital
+ "X_Gothic", // 550 xx-Goth
+ "X_Deseret", // 551 xx-Dsrt
+ "X_Inherited", // 552 xx-Qaai
+ "X_Tagalog", // 553 xx-Tglg
+ "X_Hanunoo", // 554 xx-Hano
+ "X_Buhid", // 555 xx-Buhd
+ "X_Tagbanwa", // 556 xx-Tagb
+ "X_Limbu", // 557 xx-Limb
+ "X_Tai_Le", // 558 xx-Tale
+ "X_Linear_B", // 559 xx-Linb
+ "X_Ugaritic", // 560 xx-Ugar
+ "X_Shavian", // 561 xx-Shaw
+ "X_Osmanya", // 562 xx-Osma
+ "X_Cypriot", // 563 xx-Cprt
+ "X_Braille", // 564 xx-Brai
+ "X_Buginese", // 565 xx-Bugi
+ "X_Coptic", // 566 xx-Copt
+ "X_New_Tai_Lue", // 567 xx-Talu
+ "X_Glagolitic", // 568 xx-Glag
+ "X_Tifinagh", // 569 xx-Tfng
+ "X_Syloti_Nagri", // 570 xx-Sylo
+ "X_Old_Persian", // 571 xx-Xpeo
+ "X_Kharoshthi", // 572 xx-Khar
+ "X_Balinese", // 573 xx-Bali
+ "X_Cuneiform", // 574 xx-Xsux
+ "X_Phoenician", // 575 xx-Phnx
+ "X_Phags_Pa", // 576 xx-Phag
+ "X_Nko", // 577 xx-Nkoo
+ "X_Sundanese", // 578 xx-Sund
+ "X_Lepcha", // 579 xx-Lepc
+ "X_Ol_Chiki", // 580 xx-Olck
+ "X_Vai", // 581 xx-Vaii
+ "X_Saurashtra", // 582 xx-Saur
+ "X_Kayah_Li", // 583 xx-Kali
+ "X_Rejang", // 584 xx-Rjng
+ "X_Lycian", // 585 xx-Lyci
+ "X_Carian", // 586 xx-Cari
+ "X_Lydian", // 587 xx-Lydi
+ "X_Cham", // 588 xx-Cham
+ "X_Tai_Tham", // 589 xx-Lana
+ "X_Tai_Viet", // 590 xx-Tavt
+ "X_Avestan", // 591 xx-Avst
+ "X_Egyptian_Hieroglyphs", // 592 xx-Egyp
+ "X_Samaritan", // 593 xx-Samr
+ "X_Lisu", // 594 xx-Lisu
+ "X_Bamum", // 595 xx-Bamu
+ "X_Javanese", // 596 xx-Java
+ "X_Meetei_Mayek", // 597 xx-Mtei
+ "X_Imperial_Aramaic", // 598 xx-Armi
+ "X_Old_South_Arabian", // 599 xx-Sarb
+ "X_Inscriptional_Parthian", // 600 xx-Prti
+ "X_Inscriptional_Pahlavi", // 601 xx-Phli
+ "X_Old_Turkic", // 602 xx-Orkh
+ "X_Kaithi", // 603 xx-Kthi
+ "X_Batak", // 604 xx-Batk
+ "X_Brahmi", // 605 xx-Brah
+ "X_Mandaic", // 606 xx-Mand
+ "X_Chakma", // 607 xx-Cakm
+ "X_Meroitic_Cursive", // 608 xx-Merc
+ "X_Meroitic_Hieroglyphs", // 609 xx-Mero
+ "X_Miao", // 610 xx-Plrd
+ "X_Sharada", // 611 xx-Shrd
+ "X_Sora_Sompeng", // 612 xx-Sora
+ "X_Takri", // 613 xx-Takr
+};
+
+// Subscripted by enum Language
+extern const int kLanguageToCodeSize = 614;
+extern const char* const kLanguageToCode[kLanguageToCodeSize] = {
+ "en", // 0 ENGLISH
+ "da", // 1 DANISH
+ "nl", // 2 DUTCH
+ "fi", // 3 FINNISH
+ "fr", // 4 FRENCH
+ "de", // 5 GERMAN
+ "he", // 6 HEBREW
+ "it", // 7 ITALIAN
+ "ja", // 8 Japanese
+ "ko", // 9 Korean
+ "no", // 10 NORWEGIAN
+ "pl", // 11 POLISH
+ "pt", // 12 PORTUGUESE
+ "ru", // 13 RUSSIAN
+ "es", // 14 SPANISH
+ "sv", // 15 SWEDISH
+ "zh", // 16 Chinese
+ "cs", // 17 CZECH
+ "el", // 18 GREEK
+ "is", // 19 ICELANDIC
+ "lv", // 20 LATVIAN
+ "lt", // 21 LITHUANIAN
+ "ro", // 22 ROMANIAN
+ "hu", // 23 HUNGARIAN
+ "et", // 24 ESTONIAN
+ "xxx", // 25 Ignore
+ "un", // 26 Unknown
+ "bg", // 27 BULGARIAN
+ "hr", // 28 CROATIAN
+ "sr", // 29 SERBIAN
+ "ga", // 30 IRISH
+ "gl", // 31 GALICIAN
+ "tl", // 32 TAGALOG
+ "tr", // 33 TURKISH
+ "uk", // 34 UKRAINIAN
+ "hi", // 35 HINDI
+ "mk", // 36 MACEDONIAN
+ "bn", // 37 BENGALI
+ "id", // 38 INDONESIAN
+ "la", // 39 LATIN
+ "ms", // 40 MALAY
+ "ml", // 41 MALAYALAM
+ "cy", // 42 WELSH
+ "ne", // 43 NEPALI
+ "te", // 44 TELUGU
+ "sq", // 45 ALBANIAN
+ "ta", // 46 TAMIL
+ "be", // 47 BELARUSIAN
+ "jw", // 48 JAVANESE
+ "oc", // 49 OCCITAN
+ "ur", // 50 URDU
+ "bh", // 51 BIHARI
+ "gu", // 52 GUJARATI
+ "th", // 53 THAI
+ "ar", // 54 ARABIC
+ "ca", // 55 CATALAN
+ "eo", // 56 ESPERANTO
+ "eu", // 57 BASQUE
+ "ia", // 58 INTERLINGUA
+ "kn", // 59 KANNADA
+ "pa", // 60 PUNJABI
+ "gd", // 61 SCOTS_GAELIC
+ "sw", // 62 SWAHILI
+ "sl", // 63 SLOVENIAN
+ "mr", // 64 MARATHI
+ "mt", // 65 MALTESE
+ "vi", // 66 VIETNAMESE
+ "fy", // 67 FRISIAN
+ "sk", // 68 SLOVAK
+ "zh-Hant", // 69 ChineseT
+ "fo", // 70 FAROESE
+ "su", // 71 SUNDANESE
+ "uz", // 72 UZBEK
+ "am", // 73 AMHARIC
+ "az", // 74 AZERBAIJANI
+ "ka", // 75 GEORGIAN
+ "ti", // 76 TIGRINYA
+ "fa", // 77 PERSIAN
+ "bs", // 78 BOSNIAN
+ "si", // 79 SINHALESE
+ "nn", // 80 NORWEGIAN_N
+ "", // 81 81
+ "", // 82 82
+ "xh", // 83 XHOSA
+ "zu", // 84 ZULU
+ "gn", // 85 GUARANI
+ "st", // 86 SESOTHO
+ "tk", // 87 TURKMEN
+ "ky", // 88 KYRGYZ
+ "br", // 89 BRETON
+ "tw", // 90 TWI
+ "yi", // 91 YIDDISH
+ "", // 92 92
+ "so", // 93 SOMALI
+ "ug", // 94 UIGHUR
+ "ku", // 95 KURDISH
+ "mn", // 96 MONGOLIAN
+ "hy", // 97 ARMENIAN
+ "lo", // 98 LAOTHIAN
+ "sd", // 99 SINDHI
+ "rm", // 100 RHAETO_ROMANCE
+ "af", // 101 AFRIKAANS
+ "lb", // 102 LUXEMBOURGISH
+ "my", // 103 BURMESE
+ "km", // 104 KHMER
+ "bo", // 105 TIBETAN
+ "dv", // 106 DHIVEHI
+ "chr", // 107 CHEROKEE
+ "syr", // 108 SYRIAC
+ "lif", // 109 LIMBU
+ "or", // 110 ORIYA
+ "as", // 111 ASSAMESE
+ "co", // 112 CORSICAN
+ "ie", // 113 INTERLINGUE
+ "kk", // 114 KAZAKH
+ "ln", // 115 LINGALA
+ "", // 116 116
+ "ps", // 117 PASHTO
+ "qu", // 118 QUECHUA
+ "sn", // 119 SHONA
+ "tg", // 120 TAJIK
+ "tt", // 121 TATAR
+ "to", // 122 TONGA
+ "yo", // 123 YORUBA
+ "", // 124 124
+ "", // 125 125
+ "", // 126 126
+ "", // 127 127
+ "mi", // 128 MAORI
+ "wo", // 129 WOLOF
+ "ab", // 130 ABKHAZIAN
+ "aa", // 131 AFAR
+ "ay", // 132 AYMARA
+ "ba", // 133 BASHKIR
+ "bi", // 134 BISLAMA
+ "dz", // 135 DZONGKHA
+ "fj", // 136 FIJIAN
+ "kl", // 137 GREENLANDIC
+ "ha", // 138 HAUSA
+ "ht", // 139 HAITIAN_CREOLE
+ "ik", // 140 INUPIAK
+ "iu", // 141 INUKTITUT
+ "ks", // 142 KASHMIRI
+ "rw", // 143 KINYARWANDA
+ "mg", // 144 MALAGASY
+ "na", // 145 NAURU
+ "om", // 146 OROMO
+ "rn", // 147 RUNDI
+ "sm", // 148 SAMOAN
+ "sg", // 149 SANGO
+ "sa", // 150 SANSKRIT
+ "ss", // 151 SISWANT
+ "ts", // 152 TSONGA
+ "tn", // 153 TSWANA
+ "vo", // 154 VOLAPUK
+ "za", // 155 ZHUANG
+ "kha", // 156 KHASI
+ "sco", // 157 SCOTS
+ "lg", // 158 GANDA
+ "gv", // 159 MANX
+ "sr-ME", // 160 MONTENEGRIN
+ "ak", // 161 AKAN
+ "ig", // 162 IGBO
+ "mfe", // 163 MAURITIAN_CREOLE
+ "haw", // 164 HAWAIIAN
+ "ceb", // 165 CEBUANO
+ "ee", // 166 EWE
+ "gaa", // 167 GA
+ "hmn", // 168 HMONG
+ "kri", // 169 KRIO
+ "loz", // 170 LOZI
+ "lua", // 171 LUBA_LULUA
+ "luo", // 172 LUO_KENYA_AND_TANZANIA
+ "new", // 173 NEWARI
+ "ny", // 174 NYANJA
+ "os", // 175 OSSETIAN
+ "pam", // 176 PAMPANGA
+ "nso", // 177 PEDI
+ "raj", // 178 RAJASTHANI
+ "crs", // 179 SESELWA
+ "tum", // 180 TUMBUKA
+ "ve", // 181 VENDA
+ "war", // 182 WARAY_PHILIPPINES
+ "", // 183 183
+ "", // 184 184
+ "", // 185 185
+ "", // 186 186
+ "", // 187 187
+ "", // 188 188
+ "", // 189 189
+ "", // 190 190
+ "", // 191 191
+ "", // 192 192
+ "", // 193 193
+ "", // 194 194
+ "", // 195 195
+ "", // 196 196
+ "", // 197 197
+ "", // 198 198
+ "", // 199 199
+ "", // 200 200
+ "", // 201 201
+ "", // 202 202
+ "", // 203 203
+ "", // 204 204
+ "", // 205 205
+ "", // 206 206
+ "", // 207 207
+ "", // 208 208
+ "", // 209 209
+ "", // 210 210
+ "", // 211 211
+ "", // 212 212
+ "", // 213 213
+ "", // 214 214
+ "", // 215 215
+ "", // 216 216
+ "", // 217 217
+ "", // 218 218
+ "", // 219 219
+ "", // 220 220
+ "", // 221 221
+ "", // 222 222
+ "", // 223 223
+ "", // 224 224
+ "", // 225 225
+ "", // 226 226
+ "", // 227 227
+ "", // 228 228
+ "", // 229 229
+ "", // 230 230
+ "", // 231 231
+ "", // 232 232
+ "", // 233 233
+ "", // 234 234
+ "", // 235 235
+ "", // 236 236
+ "", // 237 237
+ "", // 238 238
+ "", // 239 239
+ "", // 240 240
+ "", // 241 241
+ "", // 242 242
+ "", // 243 243
+ "", // 244 244
+ "", // 245 245
+ "", // 246 246
+ "", // 247 247
+ "", // 248 248
+ "", // 249 249
+ "", // 250 250
+ "", // 251 251
+ "", // 252 252
+ "", // 253 253
+ "", // 254 254
+ "", // 255 255
+ "", // 256 256
+ "", // 257 257
+ "", // 258 258
+ "", // 259 259
+ "", // 260 260
+ "", // 261 261
+ "", // 262 262
+ "", // 263 263
+ "", // 264 264
+ "", // 265 265
+ "", // 266 266
+ "", // 267 267
+ "", // 268 268
+ "", // 269 269
+ "", // 270 270
+ "", // 271 271
+ "", // 272 272
+ "", // 273 273
+ "", // 274 274
+ "", // 275 275
+ "", // 276 276
+ "", // 277 277
+ "", // 278 278
+ "", // 279 279
+ "", // 280 280
+ "", // 281 281
+ "", // 282 282
+ "", // 283 283
+ "", // 284 284
+ "", // 285 285
+ "", // 286 286
+ "", // 287 287
+ "", // 288 288
+ "", // 289 289
+ "", // 290 290
+ "", // 291 291
+ "", // 292 292
+ "", // 293 293
+ "", // 294 294
+ "", // 295 295
+ "", // 296 296
+ "", // 297 297
+ "", // 298 298
+ "", // 299 299
+ "", // 300 300
+ "", // 301 301
+ "", // 302 302
+ "", // 303 303
+ "", // 304 304
+ "", // 305 305
+ "", // 306 306
+ "", // 307 307
+ "", // 308 308
+ "", // 309 309
+ "", // 310 310
+ "", // 311 311
+ "", // 312 312
+ "", // 313 313
+ "", // 314 314
+ "", // 315 315
+ "", // 316 316
+ "", // 317 317
+ "", // 318 318
+ "", // 319 319
+ "", // 320 320
+ "", // 321 321
+ "", // 322 322
+ "", // 323 323
+ "", // 324 324
+ "", // 325 325
+ "", // 326 326
+ "", // 327 327
+ "", // 328 328
+ "", // 329 329
+ "", // 330 330
+ "", // 331 331
+ "", // 332 332
+ "", // 333 333
+ "", // 334 334
+ "", // 335 335
+ "", // 336 336
+ "", // 337 337
+ "", // 338 338
+ "", // 339 339
+ "", // 340 340
+ "", // 341 341
+ "", // 342 342
+ "", // 343 343
+ "", // 344 344
+ "", // 345 345
+ "", // 346 346
+ "", // 347 347
+ "", // 348 348
+ "", // 349 349
+ "", // 350 350
+ "", // 351 351
+ "", // 352 352
+ "", // 353 353
+ "", // 354 354
+ "", // 355 355
+ "", // 356 356
+ "", // 357 357
+ "", // 358 358
+ "", // 359 359
+ "", // 360 360
+ "", // 361 361
+ "", // 362 362
+ "", // 363 363
+ "", // 364 364
+ "", // 365 365
+ "", // 366 366
+ "", // 367 367
+ "", // 368 368
+ "", // 369 369
+ "", // 370 370
+ "", // 371 371
+ "", // 372 372
+ "", // 373 373
+ "", // 374 374
+ "", // 375 375
+ "", // 376 376
+ "", // 377 377
+ "", // 378 378
+ "", // 379 379
+ "", // 380 380
+ "", // 381 381
+ "", // 382 382
+ "", // 383 383
+ "", // 384 384
+ "", // 385 385
+ "", // 386 386
+ "", // 387 387
+ "", // 388 388
+ "", // 389 389
+ "", // 390 390
+ "", // 391 391
+ "", // 392 392
+ "", // 393 393
+ "", // 394 394
+ "", // 395 395
+ "", // 396 396
+ "", // 397 397
+ "", // 398 398
+ "", // 399 399
+ "", // 400 400
+ "", // 401 401
+ "", // 402 402
+ "", // 403 403
+ "", // 404 404
+ "", // 405 405
+ "", // 406 406
+ "", // 407 407
+ "", // 408 408
+ "", // 409 409
+ "", // 410 410
+ "", // 411 411
+ "", // 412 412
+ "", // 413 413
+ "", // 414 414
+ "", // 415 415
+ "", // 416 416
+ "", // 417 417
+ "", // 418 418
+ "", // 419 419
+ "", // 420 420
+ "", // 421 421
+ "", // 422 422
+ "", // 423 423
+ "", // 424 424
+ "", // 425 425
+ "", // 426 426
+ "", // 427 427
+ "", // 428 428
+ "", // 429 429
+ "", // 430 430
+ "", // 431 431
+ "", // 432 432
+ "", // 433 433
+ "", // 434 434
+ "", // 435 435
+ "", // 436 436
+ "", // 437 437
+ "", // 438 438
+ "", // 439 439
+ "", // 440 440
+ "", // 441 441
+ "", // 442 442
+ "", // 443 443
+ "", // 444 444
+ "", // 445 445
+ "", // 446 446
+ "", // 447 447
+ "", // 448 448
+ "", // 449 449
+ "", // 450 450
+ "", // 451 451
+ "", // 452 452
+ "", // 453 453
+ "", // 454 454
+ "", // 455 455
+ "", // 456 456
+ "", // 457 457
+ "", // 458 458
+ "", // 459 459
+ "", // 460 460
+ "", // 461 461
+ "", // 462 462
+ "", // 463 463
+ "", // 464 464
+ "", // 465 465
+ "", // 466 466
+ "", // 467 467
+ "", // 468 468
+ "", // 469 469
+ "", // 470 470
+ "", // 471 471
+ "", // 472 472
+ "", // 473 473
+ "", // 474 474
+ "", // 475 475
+ "", // 476 476
+ "", // 477 477
+ "", // 478 478
+ "", // 479 479
+ "", // 480 480
+ "", // 481 481
+ "", // 482 482
+ "", // 483 483
+ "", // 484 484
+ "", // 485 485
+ "", // 486 486
+ "", // 487 487
+ "", // 488 488
+ "", // 489 489
+ "", // 490 490
+ "", // 491 491
+ "", // 492 492
+ "", // 493 493
+ "", // 494 494
+ "", // 495 495
+ "", // 496 496
+ "", // 497 497
+ "", // 498 498
+ "", // 499 499
+ "", // 500 500
+ "", // 501 501
+ "", // 502 502
+ "", // 503 503
+ "", // 504 504
+ "", // 505 505
+ "nr", // 506 NDEBELE
+ "zzb", // 507 X_BORK_BORK_BORK
+ "zzp", // 508 X_PIG_LATIN
+ "zzh", // 509 X_HACKER
+ "tlh", // 510 X_KLINGON
+ "zze", // 511 X_ELMER_FUDD
+ "xx-Zyyy", // 512 X_Common
+ "xx-Latn", // 513 X_Latin
+ "xx-Grek", // 514 X_Greek
+ "xx-Cyrl", // 515 X_Cyrillic
+ "xx-Armn", // 516 X_Armenian
+ "xx-Hebr", // 517 X_Hebrew
+ "xx-Arab", // 518 X_Arabic
+ "xx-Syrc", // 519 X_Syriac
+ "xx-Thaa", // 520 X_Thaana
+ "xx-Deva", // 521 X_Devanagari
+ "xx-Beng", // 522 X_Bengali
+ "xx-Guru", // 523 X_Gurmukhi
+ "xx-Gujr", // 524 X_Gujarati
+ "xx-Orya", // 525 X_Oriya
+ "xx-Taml", // 526 X_Tamil
+ "xx-Telu", // 527 X_Telugu
+ "xx-Knda", // 528 X_Kannada
+ "xx-Mlym", // 529 X_Malayalam
+ "xx-Sinh", // 530 X_Sinhala
+ "xx-Thai", // 531 X_Thai
+ "xx-Laoo", // 532 X_Lao
+ "xx-Tibt", // 533 X_Tibetan
+ "xx-Mymr", // 534 X_Myanmar
+ "xx-Geor", // 535 X_Georgian
+ "xx-Hang", // 536 X_Hangul
+ "xx-Ethi", // 537 X_Ethiopic
+ "xx-Cher", // 538 X_Cherokee
+ "xx-Cans", // 539 X_Canadian_Aboriginal
+ "xx-Ogam", // 540 X_Ogham
+ "xx-Runr", // 541 X_Runic
+ "xx-Khmr", // 542 X_Khmer
+ "xx-Mong", // 543 X_Mongolian
+ "xx-Hira", // 544 X_Hiragana
+ "xx-Kana", // 545 X_Katakana
+ "xx-Bopo", // 546 X_Bopomofo
+ "xx-Hani", // 547 X_Han
+ "xx-Yiii", // 548 X_Yi
+ "xx-Ital", // 549 X_Old_Italic
+ "xx-Goth", // 550 X_Gothic
+ "xx-Dsrt", // 551 X_Deseret
+ "xx-Qaai", // 552 X_Inherited
+ "xx-Tglg", // 553 X_Tagalog
+ "xx-Hano", // 554 X_Hanunoo
+ "xx-Buhd", // 555 X_Buhid
+ "xx-Tagb", // 556 X_Tagbanwa
+ "xx-Limb", // 557 X_Limbu
+ "xx-Tale", // 558 X_Tai_Le
+ "xx-Linb", // 559 X_Linear_B
+ "xx-Ugar", // 560 X_Ugaritic
+ "xx-Shaw", // 561 X_Shavian
+ "xx-Osma", // 562 X_Osmanya
+ "xx-Cprt", // 563 X_Cypriot
+ "xx-Brai", // 564 X_Braille
+ "xx-Bugi", // 565 X_Buginese
+ "xx-Copt", // 566 X_Coptic
+ "xx-Talu", // 567 X_New_Tai_Lue
+ "xx-Glag", // 568 X_Glagolitic
+ "xx-Tfng", // 569 X_Tifinagh
+ "xx-Sylo", // 570 X_Syloti_Nagri
+ "xx-Xpeo", // 571 X_Old_Persian
+ "xx-Khar", // 572 X_Kharoshthi
+ "xx-Bali", // 573 X_Balinese
+ "xx-Xsux", // 574 X_Cuneiform
+ "xx-Phnx", // 575 X_Phoenician
+ "xx-Phag", // 576 X_Phags_Pa
+ "xx-Nkoo", // 577 X_Nko
+ "xx-Sund", // 578 X_Sundanese
+ "xx-Lepc", // 579 X_Lepcha
+ "xx-Olck", // 580 X_Ol_Chiki
+ "xx-Vaii", // 581 X_Vai
+ "xx-Saur", // 582 X_Saurashtra
+ "xx-Kali", // 583 X_Kayah_Li
+ "xx-Rjng", // 584 X_Rejang
+ "xx-Lyci", // 585 X_Lycian
+ "xx-Cari", // 586 X_Carian
+ "xx-Lydi", // 587 X_Lydian
+ "xx-Cham", // 588 X_Cham
+ "xx-Lana", // 589 X_Tai_Tham
+ "xx-Tavt", // 590 X_Tai_Viet
+ "xx-Avst", // 591 X_Avestan
+ "xx-Egyp", // 592 X_Egyptian_Hieroglyphs
+ "xx-Samr", // 593 X_Samaritan
+ "xx-Lisu", // 594 X_Lisu
+ "xx-Bamu", // 595 X_Bamum
+ "xx-Java", // 596 X_Javanese
+ "xx-Mtei", // 597 X_Meetei_Mayek
+ "xx-Armi", // 598 X_Imperial_Aramaic
+ "xx-Sarb", // 599 X_Old_South_Arabian
+ "xx-Prti", // 600 X_Inscriptional_Parthian
+ "xx-Phli", // 601 X_Inscriptional_Pahlavi
+ "xx-Orkh", // 602 X_Old_Turkic
+ "xx-Kthi", // 603 X_Kaithi
+ "xx-Batk", // 604 X_Batak
+ "xx-Brah", // 605 X_Brahmi
+ "xx-Mand", // 606 X_Mandaic
+ "xx-Cakm", // 607 X_Chakma
+ "xx-Merc", // 608 X_Meroitic_Cursive
+ "xx-Mero", // 609 X_Meroitic_Hieroglyphs
+ "xx-Plrd", // 610 X_Miao
+ "xx-Shrd", // 611 X_Sharada
+ "xx-Sora", // 612 X_Sora_Sompeng
+ "xx-Takr", // 613 X_Takri
+};
+
+// Subscripted by enum Language
+extern const int kLanguageToCNameSize = 614;
+extern const char* const kLanguageToCName[kLanguageToCNameSize] = {
+ "ENGLISH", // 0 en
+ "DANISH", // 1 da
+ "DUTCH", // 2 nl
+ "FINNISH", // 3 fi
+ "FRENCH", // 4 fr
+ "GERMAN", // 5 de
+ "HEBREW", // 6 he
+ "ITALIAN", // 7 it
+ "JAPANESE", // 8 ja
+ "KOREAN", // 9 ko
+ "NORWEGIAN", // 10 no
+ "POLISH", // 11 pl
+ "PORTUGUESE", // 12 pt
+ "RUSSIAN", // 13 ru
+ "SPANISH", // 14 es
+ "SWEDISH", // 15 sv
+ "CHINESE", // 16 zh
+ "CZECH", // 17 cs
+ "GREEK", // 18 el
+ "ICELANDIC", // 19 is
+ "LATVIAN", // 20 lv
+ "LITHUANIAN", // 21 lt
+ "ROMANIAN", // 22 ro
+ "HUNGARIAN", // 23 hu
+ "ESTONIAN", // 24 et
+ "TG_UNKNOWN_LANGUAGE", // 25 xxx
+ "UNKNOWN_LANGUAGE", // 26 un
+ "BULGARIAN", // 27 bg
+ "CROATIAN", // 28 hr
+ "SERBIAN", // 29 sr
+ "IRISH", // 30 ga
+ "GALICIAN", // 31 gl
+ "TAGALOG", // 32 tl
+ "TURKISH", // 33 tr
+ "UKRAINIAN", // 34 uk
+ "HINDI", // 35 hi
+ "MACEDONIAN", // 36 mk
+ "BENGALI", // 37 bn
+ "INDONESIAN", // 38 id
+ "LATIN", // 39 la
+ "MALAY", // 40 ms
+ "MALAYALAM", // 41 ml
+ "WELSH", // 42 cy
+ "NEPALI", // 43 ne
+ "TELUGU", // 44 te
+ "ALBANIAN", // 45 sq
+ "TAMIL", // 46 ta
+ "BELARUSIAN", // 47 be
+ "JAVANESE", // 48 jw
+ "OCCITAN", // 49 oc
+ "URDU", // 50 ur
+ "BIHARI", // 51 bh
+ "GUJARATI", // 52 gu
+ "THAI", // 53 th
+ "ARABIC", // 54 ar
+ "CATALAN", // 55 ca
+ "ESPERANTO", // 56 eo
+ "BASQUE", // 57 eu
+ "INTERLINGUA", // 58 ia
+ "KANNADA", // 59 kn
+ "PUNJABI", // 60 pa
+ "SCOTS_GAELIC", // 61 gd
+ "SWAHILI", // 62 sw
+ "SLOVENIAN", // 63 sl
+ "MARATHI", // 64 mr
+ "MALTESE", // 65 mt
+ "VIETNAMESE", // 66 vi
+ "FRISIAN", // 67 fy
+ "SLOVAK", // 68 sk
+ "CHINESE_T", // 69 zh-Hant
+ "FAROESE", // 70 fo
+ "SUNDANESE", // 71 su
+ "UZBEK", // 72 uz
+ "AMHARIC", // 73 am
+ "AZERBAIJANI", // 74 az
+ "GEORGIAN", // 75 ka
+ "TIGRINYA", // 76 ti
+ "PERSIAN", // 77 fa
+ "BOSNIAN", // 78 bs
+ "SINHALESE", // 79 si
+ "NORWEGIAN_N", // 80 nn
+ "X_81", // 81
+ "X_82", // 82
+ "XHOSA", // 83 xh
+ "ZULU", // 84 zu
+ "GUARANI", // 85 gn
+ "SESOTHO", // 86 st
+ "TURKMEN", // 87 tk
+ "KYRGYZ", // 88 ky
+ "BRETON", // 89 br
+ "TWI", // 90 tw
+ "YIDDISH", // 91 yi
+ "X_92", // 92
+ "SOMALI", // 93 so
+ "UIGHUR", // 94 ug
+ "KURDISH", // 95 ku
+ "MONGOLIAN", // 96 mn
+ "ARMENIAN", // 97 hy
+ "LAOTHIAN", // 98 lo
+ "SINDHI", // 99 sd
+ "RHAETO_ROMANCE", // 100 rm
+ "AFRIKAANS", // 101 af
+ "LUXEMBOURGISH", // 102 lb
+ "BURMESE", // 103 my
+ "KHMER", // 104 km
+ "TIBETAN", // 105 bo
+ "DHIVEHI", // 106 dv
+ "CHEROKEE", // 107 chr
+ "SYRIAC", // 108 syr
+ "LIMBU", // 109 lif
+ "ORIYA", // 110 or
+ "ASSAMESE", // 111 as
+ "CORSICAN", // 112 co
+ "INTERLINGUE", // 113 ie
+ "KAZAKH", // 114 kk
+ "LINGALA", // 115 ln
+ "X_116", // 116
+ "PASHTO", // 117 ps
+ "QUECHUA", // 118 qu
+ "SHONA", // 119 sn
+ "TAJIK", // 120 tg
+ "TATAR", // 121 tt
+ "TONGA", // 122 to
+ "YORUBA", // 123 yo
+ "X_124", // 124
+ "X_125", // 125
+ "X_126", // 126
+ "X_127", // 127
+ "MAORI", // 128 mi
+ "WOLOF", // 129 wo
+ "ABKHAZIAN", // 130 ab
+ "AFAR", // 131 aa
+ "AYMARA", // 132 ay
+ "BASHKIR", // 133 ba
+ "BISLAMA", // 134 bi
+ "DZONGKHA", // 135 dz
+ "FIJIAN", // 136 fj
+ "GREENLANDIC", // 137 kl
+ "HAUSA", // 138 ha
+ "HAITIAN_CREOLE", // 139 ht
+ "INUPIAK", // 140 ik
+ "INUKTITUT", // 141 iu
+ "KASHMIRI", // 142 ks
+ "KINYARWANDA", // 143 rw
+ "MALAGASY", // 144 mg
+ "NAURU", // 145 na
+ "OROMO", // 146 om
+ "RUNDI", // 147 rn
+ "SAMOAN", // 148 sm
+ "SANGO", // 149 sg
+ "SANSKRIT", // 150 sa
+ "SISWANT", // 151 ss
+ "TSONGA", // 152 ts
+ "TSWANA", // 153 tn
+ "VOLAPUK", // 154 vo
+ "ZHUANG", // 155 za
+ "KHASI", // 156 kha
+ "SCOTS", // 157 sco
+ "GANDA", // 158 lg
+ "MANX", // 159 gv
+ "MONTENEGRIN", // 160 sr-ME
+ "AKAN", // 161 ak
+ "IGBO", // 162 ig
+ "MAURITIAN_CREOLE", // 163 mfe
+ "HAWAIIAN", // 164 haw
+ "CEBUANO", // 165 ceb
+ "EWE", // 166 ee
+ "GA", // 167 gaa
+ "HMONG", // 168 hmn
+ "KRIO", // 169 kri
+ "LOZI", // 170 loz
+ "LUBA_LULUA", // 171 lua
+ "LUO_KENYA_AND_TANZANIA", // 172 luo
+ "NEWARI", // 173 new
+ "NYANJA", // 174 ny
+ "OSSETIAN", // 175 os
+ "PAMPANGA", // 176 pam
+ "PEDI", // 177 nso
+ "RAJASTHANI", // 178 raj
+ "SESELWA", // 179 crs
+ "TUMBUKA", // 180 tum
+ "VENDA", // 181 ve
+ "WARAY_PHILIPPINES", // 182 war
+ "X_183", // 183
+ "X_184", // 184
+ "X_185", // 185
+ "X_186", // 186
+ "X_187", // 187
+ "X_188", // 188
+ "X_189", // 189
+ "X_190", // 190
+ "X_191", // 191
+ "X_192", // 192
+ "X_193", // 193
+ "X_194", // 194
+ "X_195", // 195
+ "X_196", // 196
+ "X_197", // 197
+ "X_198", // 198
+ "X_199", // 199
+ "X_200", // 200
+ "X_201", // 201
+ "X_202", // 202
+ "X_203", // 203
+ "X_204", // 204
+ "X_205", // 205
+ "X_206", // 206
+ "X_207", // 207
+ "X_208", // 208
+ "X_209", // 209
+ "X_210", // 210
+ "X_211", // 211
+ "X_212", // 212
+ "X_213", // 213
+ "X_214", // 214
+ "X_215", // 215
+ "X_216", // 216
+ "X_217", // 217
+ "X_218", // 218
+ "X_219", // 219
+ "X_220", // 220
+ "X_221", // 221
+ "X_222", // 222
+ "X_223", // 223
+ "X_224", // 224
+ "X_225", // 225
+ "X_226", // 226
+ "X_227", // 227
+ "X_228", // 228
+ "X_229", // 229
+ "X_230", // 230
+ "X_231", // 231
+ "X_232", // 232
+ "X_233", // 233
+ "X_234", // 234
+ "X_235", // 235
+ "X_236", // 236
+ "X_237", // 237
+ "X_238", // 238
+ "X_239", // 239
+ "X_240", // 240
+ "X_241", // 241
+ "X_242", // 242
+ "X_243", // 243
+ "X_244", // 244
+ "X_245", // 245
+ "X_246", // 246
+ "X_247", // 247
+ "X_248", // 248
+ "X_249", // 249
+ "X_250", // 250
+ "X_251", // 251
+ "X_252", // 252
+ "X_253", // 253
+ "X_254", // 254
+ "X_255", // 255
+ "X_256", // 256
+ "X_257", // 257
+ "X_258", // 258
+ "X_259", // 259
+ "X_260", // 260
+ "X_261", // 261
+ "X_262", // 262
+ "X_263", // 263
+ "X_264", // 264
+ "X_265", // 265
+ "X_266", // 266
+ "X_267", // 267
+ "X_268", // 268
+ "X_269", // 269
+ "X_270", // 270
+ "X_271", // 271
+ "X_272", // 272
+ "X_273", // 273
+ "X_274", // 274
+ "X_275", // 275
+ "X_276", // 276
+ "X_277", // 277
+ "X_278", // 278
+ "X_279", // 279
+ "X_280", // 280
+ "X_281", // 281
+ "X_282", // 282
+ "X_283", // 283
+ "X_284", // 284
+ "X_285", // 285
+ "X_286", // 286
+ "X_287", // 287
+ "X_288", // 288
+ "X_289", // 289
+ "X_290", // 290
+ "X_291", // 291
+ "X_292", // 292
+ "X_293", // 293
+ "X_294", // 294
+ "X_295", // 295
+ "X_296", // 296
+ "X_297", // 297
+ "X_298", // 298
+ "X_299", // 299
+ "X_300", // 300
+ "X_301", // 301
+ "X_302", // 302
+ "X_303", // 303
+ "X_304", // 304
+ "X_305", // 305
+ "X_306", // 306
+ "X_307", // 307
+ "X_308", // 308
+ "X_309", // 309
+ "X_310", // 310
+ "X_311", // 311
+ "X_312", // 312
+ "X_313", // 313
+ "X_314", // 314
+ "X_315", // 315
+ "X_316", // 316
+ "X_317", // 317
+ "X_318", // 318
+ "X_319", // 319
+ "X_320", // 320
+ "X_321", // 321
+ "X_322", // 322
+ "X_323", // 323
+ "X_324", // 324
+ "X_325", // 325
+ "X_326", // 326
+ "X_327", // 327
+ "X_328", // 328
+ "X_329", // 329
+ "X_330", // 330
+ "X_331", // 331
+ "X_332", // 332
+ "X_333", // 333
+ "X_334", // 334
+ "X_335", // 335
+ "X_336", // 336
+ "X_337", // 337
+ "X_338", // 338
+ "X_339", // 339
+ "X_340", // 340
+ "X_341", // 341
+ "X_342", // 342
+ "X_343", // 343
+ "X_344", // 344
+ "X_345", // 345
+ "X_346", // 346
+ "X_347", // 347
+ "X_348", // 348
+ "X_349", // 349
+ "X_350", // 350
+ "X_351", // 351
+ "X_352", // 352
+ "X_353", // 353
+ "X_354", // 354
+ "X_355", // 355
+ "X_356", // 356
+ "X_357", // 357
+ "X_358", // 358
+ "X_359", // 359
+ "X_360", // 360
+ "X_361", // 361
+ "X_362", // 362
+ "X_363", // 363
+ "X_364", // 364
+ "X_365", // 365
+ "X_366", // 366
+ "X_367", // 367
+ "X_368", // 368
+ "X_369", // 369
+ "X_370", // 370
+ "X_371", // 371
+ "X_372", // 372
+ "X_373", // 373
+ "X_374", // 374
+ "X_375", // 375
+ "X_376", // 376
+ "X_377", // 377
+ "X_378", // 378
+ "X_379", // 379
+ "X_380", // 380
+ "X_381", // 381
+ "X_382", // 382
+ "X_383", // 383
+ "X_384", // 384
+ "X_385", // 385
+ "X_386", // 386
+ "X_387", // 387
+ "X_388", // 388
+ "X_389", // 389
+ "X_390", // 390
+ "X_391", // 391
+ "X_392", // 392
+ "X_393", // 393
+ "X_394", // 394
+ "X_395", // 395
+ "X_396", // 396
+ "X_397", // 397
+ "X_398", // 398
+ "X_399", // 399
+ "X_400", // 400
+ "X_401", // 401
+ "X_402", // 402
+ "X_403", // 403
+ "X_404", // 404
+ "X_405", // 405
+ "X_406", // 406
+ "X_407", // 407
+ "X_408", // 408
+ "X_409", // 409
+ "X_410", // 410
+ "X_411", // 411
+ "X_412", // 412
+ "X_413", // 413
+ "X_414", // 414
+ "X_415", // 415
+ "X_416", // 416
+ "X_417", // 417
+ "X_418", // 418
+ "X_419", // 419
+ "X_420", // 420
+ "X_421", // 421
+ "X_422", // 422
+ "X_423", // 423
+ "X_424", // 424
+ "X_425", // 425
+ "X_426", // 426
+ "X_427", // 427
+ "X_428", // 428
+ "X_429", // 429
+ "X_430", // 430
+ "X_431", // 431
+ "X_432", // 432
+ "X_433", // 433
+ "X_434", // 434
+ "X_435", // 435
+ "X_436", // 436
+ "X_437", // 437
+ "X_438", // 438
+ "X_439", // 439
+ "X_440", // 440
+ "X_441", // 441
+ "X_442", // 442
+ "X_443", // 443
+ "X_444", // 444
+ "X_445", // 445
+ "X_446", // 446
+ "X_447", // 447
+ "X_448", // 448
+ "X_449", // 449
+ "X_450", // 450
+ "X_451", // 451
+ "X_452", // 452
+ "X_453", // 453
+ "X_454", // 454
+ "X_455", // 455
+ "X_456", // 456
+ "X_457", // 457
+ "X_458", // 458
+ "X_459", // 459
+ "X_460", // 460
+ "X_461", // 461
+ "X_462", // 462
+ "X_463", // 463
+ "X_464", // 464
+ "X_465", // 465
+ "X_466", // 466
+ "X_467", // 467
+ "X_468", // 468
+ "X_469", // 469
+ "X_470", // 470
+ "X_471", // 471
+ "X_472", // 472
+ "X_473", // 473
+ "X_474", // 474
+ "X_475", // 475
+ "X_476", // 476
+ "X_477", // 477
+ "X_478", // 478
+ "X_479", // 479
+ "X_480", // 480
+ "X_481", // 481
+ "X_482", // 482
+ "X_483", // 483
+ "X_484", // 484
+ "X_485", // 485
+ "X_486", // 486
+ "X_487", // 487
+ "X_488", // 488
+ "X_489", // 489
+ "X_490", // 490
+ "X_491", // 491
+ "X_492", // 492
+ "X_493", // 493
+ "X_494", // 494
+ "X_495", // 495
+ "X_496", // 496
+ "X_497", // 497
+ "X_498", // 498
+ "X_499", // 499
+ "X_500", // 500
+ "X_501", // 501
+ "X_502", // 502
+ "X_503", // 503
+ "X_504", // 504
+ "X_505", // 505
+ "NDEBELE", // 506 nr
+ "X_BORK_BORK_BORK", // 507 zzb
+ "X_PIG_LATIN", // 508 zzp
+ "X_HACKER", // 509 zzh
+ "X_KLINGON", // 510 tlh
+ "X_ELMER_FUDD", // 511 zze
+ "X_Common", // 512 xx-Zyyy
+ "X_Latin", // 513 xx-Latn
+ "X_Greek", // 514 xx-Grek
+ "X_Cyrillic", // 515 xx-Cyrl
+ "X_Armenian", // 516 xx-Armn
+ "X_Hebrew", // 517 xx-Hebr
+ "X_Arabic", // 518 xx-Arab
+ "X_Syriac", // 519 xx-Syrc
+ "X_Thaana", // 520 xx-Thaa
+ "X_Devanagari", // 521 xx-Deva
+ "X_Bengali", // 522 xx-Beng
+ "X_Gurmukhi", // 523 xx-Guru
+ "X_Gujarati", // 524 xx-Gujr
+ "X_Oriya", // 525 xx-Orya
+ "X_Tamil", // 526 xx-Taml
+ "X_Telugu", // 527 xx-Telu
+ "X_Kannada", // 528 xx-Knda
+ "X_Malayalam", // 529 xx-Mlym
+ "X_Sinhala", // 530 xx-Sinh
+ "X_Thai", // 531 xx-Thai
+ "X_Lao", // 532 xx-Laoo
+ "X_Tibetan", // 533 xx-Tibt
+ "X_Myanmar", // 534 xx-Mymr
+ "X_Georgian", // 535 xx-Geor
+ "X_Hangul", // 536 xx-Hang
+ "X_Ethiopic", // 537 xx-Ethi
+ "X_Cherokee", // 538 xx-Cher
+ "X_Canadian_Aboriginal", // 539 xx-Cans
+ "X_Ogham", // 540 xx-Ogam
+ "X_Runic", // 541 xx-Runr
+ "X_Khmer", // 542 xx-Khmr
+ "X_Mongolian", // 543 xx-Mong
+ "X_Hiragana", // 544 xx-Hira
+ "X_Katakana", // 545 xx-Kana
+ "X_Bopomofo", // 546 xx-Bopo
+ "X_Han", // 547 xx-Hani
+ "X_Yi", // 548 xx-Yiii
+ "X_Old_Italic", // 549 xx-Ital
+ "X_Gothic", // 550 xx-Goth
+ "X_Deseret", // 551 xx-Dsrt
+ "X_Inherited", // 552 xx-Qaai
+ "X_Tagalog", // 553 xx-Tglg
+ "X_Hanunoo", // 554 xx-Hano
+ "X_Buhid", // 555 xx-Buhd
+ "X_Tagbanwa", // 556 xx-Tagb
+ "X_Limbu", // 557 xx-Limb
+ "X_Tai_Le", // 558 xx-Tale
+ "X_Linear_B", // 559 xx-Linb
+ "X_Ugaritic", // 560 xx-Ugar
+ "X_Shavian", // 561 xx-Shaw
+ "X_Osmanya", // 562 xx-Osma
+ "X_Cypriot", // 563 xx-Cprt
+ "X_Braille", // 564 xx-Brai
+ "X_Buginese", // 565 xx-Bugi
+ "X_Coptic", // 566 xx-Copt
+ "X_New_Tai_Lue", // 567 xx-Talu
+ "X_Glagolitic", // 568 xx-Glag
+ "X_Tifinagh", // 569 xx-Tfng
+ "X_Syloti_Nagri", // 570 xx-Sylo
+ "X_Old_Persian", // 571 xx-Xpeo
+ "X_Kharoshthi", // 572 xx-Khar
+ "X_Balinese", // 573 xx-Bali
+ "X_Cuneiform", // 574 xx-Xsux
+ "X_Phoenician", // 575 xx-Phnx
+ "X_Phags_Pa", // 576 xx-Phag
+ "X_Nko", // 577 xx-Nkoo
+ "X_Sundanese", // 578 xx-Sund
+ "X_Lepcha", // 579 xx-Lepc
+ "X_Ol_Chiki", // 580 xx-Olck
+ "X_Vai", // 581 xx-Vaii
+ "X_Saurashtra", // 582 xx-Saur
+ "X_Kayah_Li", // 583 xx-Kali
+ "X_Rejang", // 584 xx-Rjng
+ "X_Lycian", // 585 xx-Lyci
+ "X_Carian", // 586 xx-Cari
+ "X_Lydian", // 587 xx-Lydi
+ "X_Cham", // 588 xx-Cham
+ "X_Tai_Tham", // 589 xx-Lana
+ "X_Tai_Viet", // 590 xx-Tavt
+ "X_Avestan", // 591 xx-Avst
+ "X_Egyptian_Hieroglyphs", // 592 xx-Egyp
+ "X_Samaritan", // 593 xx-Samr
+ "X_Lisu", // 594 xx-Lisu
+ "X_Bamum", // 595 xx-Bamu
+ "X_Javanese", // 596 xx-Java
+ "X_Meetei_Mayek", // 597 xx-Mtei
+ "X_Imperial_Aramaic", // 598 xx-Armi
+ "X_Old_South_Arabian", // 599 xx-Sarb
+ "X_Inscriptional_Parthian", // 600 xx-Prti
+ "X_Inscriptional_Pahlavi", // 601 xx-Phli
+ "X_Old_Turkic", // 602 xx-Orkh
+ "X_Kaithi", // 603 xx-Kthi
+ "X_Batak", // 604 xx-Batk
+ "X_Brahmi", // 605 xx-Brah
+ "X_Mandaic", // 606 xx-Mand
+ "X_Chakma", // 607 xx-Cakm
+ "X_Meroitic_Cursive", // 608 xx-Merc
+ "X_Meroitic_Hieroglyphs", // 609 xx-Mero
+ "X_Miao", // 610 xx-Plrd
+ "X_Sharada", // 611 xx-Shrd
+ "X_Sora_Sompeng", // 612 xx-Sora
+ "X_Takri", // 613 xx-Takr
+};
+
+// Subscripted by enum Language
+extern const int kLanguageToScriptsSize = 614;
+#define None ULScript_Common
+extern const FourScripts kLanguageToScripts[kLanguageToScriptsSize] = {
+ {ULScript_Latin, None, None, None, }, // 0 en
+ {ULScript_Latin, None, None, None, }, // 1 da
+ {ULScript_Latin, None, None, None, }, // 2 nl
+ {ULScript_Latin, None, None, None, }, // 3 fi
+ {ULScript_Latin, None, None, None, }, // 4 fr
+ {ULScript_Latin, None, None, None, }, // 5 de
+ {ULScript_Hebrew, None, None, None, }, // 6 he
+ {ULScript_Latin, None, None, None, }, // 7 it
+ {ULScript_Hani, None, None, None, }, // 8 ja
+ {ULScript_Hani, None, None, None, }, // 9 ko
+ {ULScript_Latin, None, None, None, }, // 10 no
+ {ULScript_Latin, None, None, None, }, // 11 pl
+ {ULScript_Latin, None, None, None, }, // 12 pt
+ {ULScript_Cyrillic, None, None, None, }, // 13 ru
+ {ULScript_Latin, None, None, None, }, // 14 es
+ {ULScript_Latin, None, None, None, }, // 15 sv
+ {ULScript_Hani, None, None, None, }, // 16 zh
+ {ULScript_Latin, None, None, None, }, // 17 cs
+ {ULScript_Greek, None, None, None, }, // 18 el
+ {ULScript_Latin, None, None, None, }, // 19 is
+ {ULScript_Latin, None, None, None, }, // 20 lv
+ {ULScript_Latin, None, None, None, }, // 21 lt
+ {ULScript_Latin, ULScript_Cyrillic, None, None, }, // 22 ro
+ {ULScript_Latin, None, None, None, }, // 23 hu
+ {ULScript_Latin, None, None, None, }, // 24 et
+ {ULScript_Latin, ULScript_Cyrillic, ULScript_Arabic, ULScript_Devanagari, }, // 25 xxx
+ {ULScript_Latin, None, None, None, }, // 26 un
+ {ULScript_Cyrillic, None, None, None, }, // 27 bg
+ {ULScript_Latin, None, None, None, }, // 28 hr
+ {ULScript_Latin, ULScript_Cyrillic, None, None, }, // 29 sr
+ {ULScript_Latin, None, None, None, }, // 30 ga
+ {ULScript_Latin, None, None, None, }, // 31 gl
+ {ULScript_Latin, ULScript_Tagalog, None, None, }, // 32 tl
+ {ULScript_Latin, None, None, None, }, // 33 tr
+ {ULScript_Cyrillic, None, None, None, }, // 34 uk
+ {ULScript_Devanagari, None, None, None, }, // 35 hi
+ {ULScript_Cyrillic, None, None, None, }, // 36 mk
+ {ULScript_Bengali, None, None, None, }, // 37 bn
+ {ULScript_Latin, None, None, None, }, // 38 id
+ {ULScript_Latin, None, None, None, }, // 39 la
+ {ULScript_Latin, None, None, None, }, // 40 ms
+ {ULScript_Malayalam, None, None, None, }, // 41 ml
+ {ULScript_Latin, None, None, None, }, // 42 cy
+ {ULScript_Devanagari, None, None, None, }, // 43 ne
+ {ULScript_Telugu, None, None, None, }, // 44 te
+ {ULScript_Latin, None, None, None, }, // 45 sq
+ {ULScript_Tamil, None, None, None, }, // 46 ta
+ {ULScript_Cyrillic, None, None, None, }, // 47 be
+ {ULScript_Latin, None, None, None, }, // 48 jw
+ {ULScript_Latin, None, None, None, }, // 49 oc
+ {ULScript_Arabic, None, None, None, }, // 50 ur
+ {ULScript_Devanagari, None, None, None, }, // 51 bh
+ {ULScript_Gujarati, None, None, None, }, // 52 gu
+ {ULScript_Thai, None, None, None, }, // 53 th
+ {ULScript_Arabic, None, None, None, }, // 54 ar
+ {ULScript_Latin, None, None, None, }, // 55 ca
+ {ULScript_Latin, None, None, None, }, // 56 eo
+ {ULScript_Latin, None, None, None, }, // 57 eu
+ {ULScript_Latin, None, None, None, }, // 58 ia
+ {ULScript_Kannada, None, None, None, }, // 59 kn
+ {ULScript_Gurmukhi, None, None, None, }, // 60 pa
+ {ULScript_Latin, None, None, None, }, // 61 gd
+ {ULScript_Latin, None, None, None, }, // 62 sw
+ {ULScript_Latin, None, None, None, }, // 63 sl
+ {ULScript_Devanagari, None, None, None, }, // 64 mr
+ {ULScript_Latin, None, None, None, }, // 65 mt
+ {ULScript_Latin, None, None, None, }, // 66 vi
+ {ULScript_Latin, None, None, None, }, // 67 fy
+ {ULScript_Latin, None, None, None, }, // 68 sk
+ {ULScript_Hani, None, None, None, }, // 69 zh-Hant
+ {ULScript_Latin, None, None, None, }, // 70 fo
+ {ULScript_Latin, None, None, None, }, // 71 su
+ {ULScript_Latin, ULScript_Cyrillic, ULScript_Arabic, None, }, // 72 uz
+ {ULScript_Ethiopic, None, None, None, }, // 73 am
+ {ULScript_Latin, ULScript_Cyrillic, ULScript_Arabic, None, }, // 74 az
+ {ULScript_Georgian, None, None, None, }, // 75 ka
+ {ULScript_Ethiopic, None, None, None, }, // 76 ti
+ {ULScript_Arabic, None, None, None, }, // 77 fa
+ {ULScript_Latin, ULScript_Cyrillic, None, None, }, // 78 bs
+ {ULScript_Sinhala, None, None, None, }, // 79 si
+ {ULScript_Latin, None, None, None, }, // 80 nn
+ {None, None, None, None, }, // 81
+ {None, None, None, None, }, // 82
+ {ULScript_Latin, None, None, None, }, // 83 xh
+ {ULScript_Latin, None, None, None, }, // 84 zu
+ {ULScript_Latin, None, None, None, }, // 85 gn
+ {ULScript_Latin, None, None, None, }, // 86 st
+ {ULScript_Latin, ULScript_Cyrillic, ULScript_Arabic, None, }, // 87 tk
+ {ULScript_Cyrillic, ULScript_Arabic, None, None, }, // 88 ky
+ {ULScript_Latin, None, None, None, }, // 89 br
+ {ULScript_Latin, None, None, None, }, // 90 tw
+ {ULScript_Hebrew, None, None, None, }, // 91 yi
+ {None, None, None, None, }, // 92
+ {ULScript_Latin, None, None, None, }, // 93 so
+ {ULScript_Latin, ULScript_Cyrillic, ULScript_Arabic, None, }, // 94 ug
+ {ULScript_Latin, ULScript_Arabic, None, None, }, // 95 ku
+ {ULScript_Cyrillic, ULScript_Mongolian, None, None, }, // 96 mn
+ {ULScript_Armenian, None, None, None, }, // 97 hy
+ {ULScript_Lao, None, None, None, }, // 98 lo
+ {ULScript_Arabic, ULScript_Devanagari, None, None, }, // 99 sd
+ {ULScript_Latin, None, None, None, }, // 100 rm
+ {ULScript_Latin, None, None, None, }, // 101 af
+ {ULScript_Latin, None, None, None, }, // 102 lb
+ {ULScript_Latin, ULScript_Myanmar, None, None, }, // 103 my
+ {ULScript_Khmer, None, None, None, }, // 104 km
+ {ULScript_Tibetan, None, None, None, }, // 105 bo
+ {ULScript_Thaana, None, None, None, }, // 106 dv
+ {ULScript_Cherokee, None, None, None, }, // 107 chr
+ {ULScript_Syriac, None, None, None, }, // 108 syr
+ {ULScript_Limbu, None, None, None, }, // 109 lif
+ {ULScript_Oriya, None, None, None, }, // 110 or
+ {ULScript_Bengali, None, None, None, }, // 111 as
+ {ULScript_Latin, None, None, None, }, // 112 co
+ {ULScript_Latin, None, None, None, }, // 113 ie
+ {ULScript_Latin, ULScript_Cyrillic, ULScript_Arabic, None, }, // 114 kk
+ {ULScript_Latin, None, None, None, }, // 115 ln
+ {None, None, None, None, }, // 116
+ {ULScript_Arabic, None, None, None, }, // 117 ps
+ {ULScript_Latin, None, None, None, }, // 118 qu
+ {ULScript_Latin, None, None, None, }, // 119 sn
+ {ULScript_Cyrillic, ULScript_Arabic, None, None, }, // 120 tg
+ {ULScript_Latin, ULScript_Cyrillic, ULScript_Arabic, None, }, // 121 tt
+ {ULScript_Latin, None, None, None, }, // 122 to
+ {ULScript_Latin, None, None, None, }, // 123 yo
+ {None, None, None, None, }, // 124
+ {None, None, None, None, }, // 125
+ {None, None, None, None, }, // 126
+ {None, None, None, None, }, // 127
+ {ULScript_Latin, None, None, None, }, // 128 mi
+ {ULScript_Latin, None, None, None, }, // 129 wo
+ {ULScript_Cyrillic, None, None, None, }, // 130 ab
+ {ULScript_Latin, None, None, None, }, // 131 aa
+ {ULScript_Latin, None, None, None, }, // 132 ay
+ {ULScript_Cyrillic, None, None, None, }, // 133 ba
+ {ULScript_Latin, None, None, None, }, // 134 bi
+ {ULScript_Tibetan, None, None, None, }, // 135 dz
+ {ULScript_Latin, None, None, None, }, // 136 fj
+ {ULScript_Latin, None, None, None, }, // 137 kl
+ {ULScript_Latin, ULScript_Arabic, None, None, }, // 138 ha
+ {ULScript_Latin, None, None, None, }, // 139 ht
+ {ULScript_Latin, None, None, None, }, // 140 ik
+ {ULScript_Canadian_Aboriginal, None, None, None, }, // 141 iu
+ {ULScript_Arabic, ULScript_Devanagari, None, None, }, // 142 ks
+ {ULScript_Latin, None, None, None, }, // 143 rw
+ {ULScript_Latin, None, None, None, }, // 144 mg
+ {ULScript_Latin, None, None, None, }, // 145 na
+ {ULScript_Latin, None, None, None, }, // 146 om
+ {ULScript_Latin, None, None, None, }, // 147 rn
+ {ULScript_Latin, None, None, None, }, // 148 sm
+ {ULScript_Latin, None, None, None, }, // 149 sg
+ {ULScript_Latin, ULScript_Devanagari, None, None, }, // 150 sa
+ {ULScript_Latin, None, None, None, }, // 151 ss
+ {ULScript_Latin, None, None, None, }, // 152 ts
+ {ULScript_Latin, None, None, None, }, // 153 tn
+ {ULScript_Latin, None, None, None, }, // 154 vo
+ {ULScript_Latin, ULScript_Hani, None, None, }, // 155 za
+ {ULScript_Latin, None, None, None, }, // 156 kha
+ {ULScript_Latin, None, None, None, }, // 157 sco
+ {ULScript_Latin, None, None, None, }, // 158 lg
+ {ULScript_Latin, None, None, None, }, // 159 gv
+ {ULScript_Latin, None, None, None, }, // 160 sr-ME
+ {ULScript_Latin, None, None, None, }, // 161 ak
+ {ULScript_Latin, None, None, None, }, // 162 ig
+ {ULScript_Latin, None, None, None, }, // 163 mfe
+ {ULScript_Latin, None, None, None, }, // 164 haw
+ {ULScript_Latin, None, None, None, }, // 165 ceb
+ {ULScript_Latin, None, None, None, }, // 166 ee
+ {ULScript_Latin, None, None, None, }, // 167 gaa
+ {ULScript_Latin, None, None, None, }, // 168 hmn
+ {ULScript_Latin, None, None, None, }, // 169 kri
+ {ULScript_Latin, None, None, None, }, // 170 loz
+ {ULScript_Latin, None, None, None, }, // 171 lua
+ {ULScript_Latin, None, None, None, }, // 172 luo
+ {ULScript_Devanagari, None, None, None, }, // 173 new
+ {ULScript_Latin, None, None, None, }, // 174 ny
+ {ULScript_Cyrillic, None, None, None, }, // 175 os
+ {ULScript_Latin, None, None, None, }, // 176 pam
+ {ULScript_Latin, None, None, None, }, // 177 nso
+ {ULScript_Devanagari, None, None, None, }, // 178 raj
+ {ULScript_Latin, None, None, None, }, // 179 crs
+ {ULScript_Latin, None, None, None, }, // 180 tum
+ {ULScript_Latin, None, None, None, }, // 181 ve
+ {ULScript_Latin, None, None, None, }, // 182 war
+ {None, None, None, None, }, // 183
+ {None, None, None, None, }, // 184
+ {None, None, None, None, }, // 185
+ {None, None, None, None, }, // 186
+ {None, None, None, None, }, // 187
+ {None, None, None, None, }, // 188
+ {None, None, None, None, }, // 189
+ {None, None, None, None, }, // 190
+ {None, None, None, None, }, // 191
+ {None, None, None, None, }, // 192
+ {None, None, None, None, }, // 193
+ {None, None, None, None, }, // 194
+ {None, None, None, None, }, // 195
+ {None, None, None, None, }, // 196
+ {None, None, None, None, }, // 197
+ {None, None, None, None, }, // 198
+ {None, None, None, None, }, // 199
+ {None, None, None, None, }, // 200
+ {None, None, None, None, }, // 201
+ {None, None, None, None, }, // 202
+ {None, None, None, None, }, // 203
+ {None, None, None, None, }, // 204
+ {None, None, None, None, }, // 205
+ {None, None, None, None, }, // 206
+ {None, None, None, None, }, // 207
+ {None, None, None, None, }, // 208
+ {None, None, None, None, }, // 209
+ {None, None, None, None, }, // 210
+ {None, None, None, None, }, // 211
+ {None, None, None, None, }, // 212
+ {None, None, None, None, }, // 213
+ {None, None, None, None, }, // 214
+ {None, None, None, None, }, // 215
+ {None, None, None, None, }, // 216
+ {None, None, None, None, }, // 217
+ {None, None, None, None, }, // 218
+ {None, None, None, None, }, // 219
+ {None, None, None, None, }, // 220
+ {None, None, None, None, }, // 221
+ {None, None, None, None, }, // 222
+ {None, None, None, None, }, // 223
+ {None, None, None, None, }, // 224
+ {None, None, None, None, }, // 225
+ {None, None, None, None, }, // 226
+ {None, None, None, None, }, // 227
+ {None, None, None, None, }, // 228
+ {None, None, None, None, }, // 229
+ {None, None, None, None, }, // 230
+ {None, None, None, None, }, // 231
+ {None, None, None, None, }, // 232
+ {None, None, None, None, }, // 233
+ {None, None, None, None, }, // 234
+ {None, None, None, None, }, // 235
+ {None, None, None, None, }, // 236
+ {None, None, None, None, }, // 237
+ {None, None, None, None, }, // 238
+ {None, None, None, None, }, // 239
+ {None, None, None, None, }, // 240
+ {None, None, None, None, }, // 241
+ {None, None, None, None, }, // 242
+ {None, None, None, None, }, // 243
+ {None, None, None, None, }, // 244
+ {None, None, None, None, }, // 245
+ {None, None, None, None, }, // 246
+ {None, None, None, None, }, // 247
+ {None, None, None, None, }, // 248
+ {None, None, None, None, }, // 249
+ {None, None, None, None, }, // 250
+ {None, None, None, None, }, // 251
+ {None, None, None, None, }, // 252
+ {None, None, None, None, }, // 253
+ {None, None, None, None, }, // 254
+ {None, None, None, None, }, // 255
+ {None, None, None, None, }, // 256
+ {None, None, None, None, }, // 257
+ {None, None, None, None, }, // 258
+ {None, None, None, None, }, // 259
+ {None, None, None, None, }, // 260
+ {None, None, None, None, }, // 261
+ {None, None, None, None, }, // 262
+ {None, None, None, None, }, // 263
+ {None, None, None, None, }, // 264
+ {None, None, None, None, }, // 265
+ {None, None, None, None, }, // 266
+ {None, None, None, None, }, // 267
+ {None, None, None, None, }, // 268
+ {None, None, None, None, }, // 269
+ {None, None, None, None, }, // 270
+ {None, None, None, None, }, // 271
+ {None, None, None, None, }, // 272
+ {None, None, None, None, }, // 273
+ {None, None, None, None, }, // 274
+ {None, None, None, None, }, // 275
+ {None, None, None, None, }, // 276
+ {None, None, None, None, }, // 277
+ {None, None, None, None, }, // 278
+ {None, None, None, None, }, // 279
+ {None, None, None, None, }, // 280
+ {None, None, None, None, }, // 281
+ {None, None, None, None, }, // 282
+ {None, None, None, None, }, // 283
+ {None, None, None, None, }, // 284
+ {None, None, None, None, }, // 285
+ {None, None, None, None, }, // 286
+ {None, None, None, None, }, // 287
+ {None, None, None, None, }, // 288
+ {None, None, None, None, }, // 289
+ {None, None, None, None, }, // 290
+ {None, None, None, None, }, // 291
+ {None, None, None, None, }, // 292
+ {None, None, None, None, }, // 293
+ {None, None, None, None, }, // 294
+ {None, None, None, None, }, // 295
+ {None, None, None, None, }, // 296
+ {None, None, None, None, }, // 297
+ {None, None, None, None, }, // 298
+ {None, None, None, None, }, // 299
+ {None, None, None, None, }, // 300
+ {None, None, None, None, }, // 301
+ {None, None, None, None, }, // 302
+ {None, None, None, None, }, // 303
+ {None, None, None, None, }, // 304
+ {None, None, None, None, }, // 305
+ {None, None, None, None, }, // 306
+ {None, None, None, None, }, // 307
+ {None, None, None, None, }, // 308
+ {None, None, None, None, }, // 309
+ {None, None, None, None, }, // 310
+ {None, None, None, None, }, // 311
+ {None, None, None, None, }, // 312
+ {None, None, None, None, }, // 313
+ {None, None, None, None, }, // 314
+ {None, None, None, None, }, // 315
+ {None, None, None, None, }, // 316
+ {None, None, None, None, }, // 317
+ {None, None, None, None, }, // 318
+ {None, None, None, None, }, // 319
+ {None, None, None, None, }, // 320
+ {None, None, None, None, }, // 321
+ {None, None, None, None, }, // 322
+ {None, None, None, None, }, // 323
+ {None, None, None, None, }, // 324
+ {None, None, None, None, }, // 325
+ {None, None, None, None, }, // 326
+ {None, None, None, None, }, // 327
+ {None, None, None, None, }, // 328
+ {None, None, None, None, }, // 329
+ {None, None, None, None, }, // 330
+ {None, None, None, None, }, // 331
+ {None, None, None, None, }, // 332
+ {None, None, None, None, }, // 333
+ {None, None, None, None, }, // 334
+ {None, None, None, None, }, // 335
+ {None, None, None, None, }, // 336
+ {None, None, None, None, }, // 337
+ {None, None, None, None, }, // 338
+ {None, None, None, None, }, // 339
+ {None, None, None, None, }, // 340
+ {None, None, None, None, }, // 341
+ {None, None, None, None, }, // 342
+ {None, None, None, None, }, // 343
+ {None, None, None, None, }, // 344
+ {None, None, None, None, }, // 345
+ {None, None, None, None, }, // 346
+ {None, None, None, None, }, // 347
+ {None, None, None, None, }, // 348
+ {None, None, None, None, }, // 349
+ {None, None, None, None, }, // 350
+ {None, None, None, None, }, // 351
+ {None, None, None, None, }, // 352
+ {None, None, None, None, }, // 353
+ {None, None, None, None, }, // 354
+ {None, None, None, None, }, // 355
+ {None, None, None, None, }, // 356
+ {None, None, None, None, }, // 357
+ {None, None, None, None, }, // 358
+ {None, None, None, None, }, // 359
+ {None, None, None, None, }, // 360
+ {None, None, None, None, }, // 361
+ {None, None, None, None, }, // 362
+ {None, None, None, None, }, // 363
+ {None, None, None, None, }, // 364
+ {None, None, None, None, }, // 365
+ {None, None, None, None, }, // 366
+ {None, None, None, None, }, // 367
+ {None, None, None, None, }, // 368
+ {None, None, None, None, }, // 369
+ {None, None, None, None, }, // 370
+ {None, None, None, None, }, // 371
+ {None, None, None, None, }, // 372
+ {None, None, None, None, }, // 373
+ {None, None, None, None, }, // 374
+ {None, None, None, None, }, // 375
+ {None, None, None, None, }, // 376
+ {None, None, None, None, }, // 377
+ {None, None, None, None, }, // 378
+ {None, None, None, None, }, // 379
+ {None, None, None, None, }, // 380
+ {None, None, None, None, }, // 381
+ {None, None, None, None, }, // 382
+ {None, None, None, None, }, // 383
+ {None, None, None, None, }, // 384
+ {None, None, None, None, }, // 385
+ {None, None, None, None, }, // 386
+ {None, None, None, None, }, // 387
+ {None, None, None, None, }, // 388
+ {None, None, None, None, }, // 389
+ {None, None, None, None, }, // 390
+ {None, None, None, None, }, // 391
+ {None, None, None, None, }, // 392
+ {None, None, None, None, }, // 393
+ {None, None, None, None, }, // 394
+ {None, None, None, None, }, // 395
+ {None, None, None, None, }, // 396
+ {None, None, None, None, }, // 397
+ {None, None, None, None, }, // 398
+ {None, None, None, None, }, // 399
+ {None, None, None, None, }, // 400
+ {None, None, None, None, }, // 401
+ {None, None, None, None, }, // 402
+ {None, None, None, None, }, // 403
+ {None, None, None, None, }, // 404
+ {None, None, None, None, }, // 405
+ {None, None, None, None, }, // 406
+ {None, None, None, None, }, // 407
+ {None, None, None, None, }, // 408
+ {None, None, None, None, }, // 409
+ {None, None, None, None, }, // 410
+ {None, None, None, None, }, // 411
+ {None, None, None, None, }, // 412
+ {None, None, None, None, }, // 413
+ {None, None, None, None, }, // 414
+ {None, None, None, None, }, // 415
+ {None, None, None, None, }, // 416
+ {None, None, None, None, }, // 417
+ {None, None, None, None, }, // 418
+ {None, None, None, None, }, // 419
+ {None, None, None, None, }, // 420
+ {None, None, None, None, }, // 421
+ {None, None, None, None, }, // 422
+ {None, None, None, None, }, // 423
+ {None, None, None, None, }, // 424
+ {None, None, None, None, }, // 425
+ {None, None, None, None, }, // 426
+ {None, None, None, None, }, // 427
+ {None, None, None, None, }, // 428
+ {None, None, None, None, }, // 429
+ {None, None, None, None, }, // 430
+ {None, None, None, None, }, // 431
+ {None, None, None, None, }, // 432
+ {None, None, None, None, }, // 433
+ {None, None, None, None, }, // 434
+ {None, None, None, None, }, // 435
+ {None, None, None, None, }, // 436
+ {None, None, None, None, }, // 437
+ {None, None, None, None, }, // 438
+ {None, None, None, None, }, // 439
+ {None, None, None, None, }, // 440
+ {None, None, None, None, }, // 441
+ {None, None, None, None, }, // 442
+ {None, None, None, None, }, // 443
+ {None, None, None, None, }, // 444
+ {None, None, None, None, }, // 445
+ {None, None, None, None, }, // 446
+ {None, None, None, None, }, // 447
+ {None, None, None, None, }, // 448
+ {None, None, None, None, }, // 449
+ {None, None, None, None, }, // 450
+ {None, None, None, None, }, // 451
+ {None, None, None, None, }, // 452
+ {None, None, None, None, }, // 453
+ {None, None, None, None, }, // 454
+ {None, None, None, None, }, // 455
+ {None, None, None, None, }, // 456
+ {None, None, None, None, }, // 457
+ {None, None, None, None, }, // 458
+ {None, None, None, None, }, // 459
+ {None, None, None, None, }, // 460
+ {None, None, None, None, }, // 461
+ {None, None, None, None, }, // 462
+ {None, None, None, None, }, // 463
+ {None, None, None, None, }, // 464
+ {None, None, None, None, }, // 465
+ {None, None, None, None, }, // 466
+ {None, None, None, None, }, // 467
+ {None, None, None, None, }, // 468
+ {None, None, None, None, }, // 469
+ {None, None, None, None, }, // 470
+ {None, None, None, None, }, // 471
+ {None, None, None, None, }, // 472
+ {None, None, None, None, }, // 473
+ {None, None, None, None, }, // 474
+ {None, None, None, None, }, // 475
+ {None, None, None, None, }, // 476
+ {None, None, None, None, }, // 477
+ {None, None, None, None, }, // 478
+ {None, None, None, None, }, // 479
+ {None, None, None, None, }, // 480
+ {None, None, None, None, }, // 481
+ {None, None, None, None, }, // 482
+ {None, None, None, None, }, // 483
+ {None, None, None, None, }, // 484
+ {None, None, None, None, }, // 485
+ {None, None, None, None, }, // 486
+ {None, None, None, None, }, // 487
+ {None, None, None, None, }, // 488
+ {None, None, None, None, }, // 489
+ {None, None, None, None, }, // 490
+ {None, None, None, None, }, // 491
+ {None, None, None, None, }, // 492
+ {None, None, None, None, }, // 493
+ {None, None, None, None, }, // 494
+ {None, None, None, None, }, // 495
+ {None, None, None, None, }, // 496
+ {None, None, None, None, }, // 497
+ {None, None, None, None, }, // 498
+ {None, None, None, None, }, // 499
+ {None, None, None, None, }, // 500
+ {None, None, None, None, }, // 501
+ {None, None, None, None, }, // 502
+ {None, None, None, None, }, // 503
+ {None, None, None, None, }, // 504
+ {None, None, None, None, }, // 505
+ {ULScript_Latin, None, None, None, }, // 506 nr
+ {ULScript_Latin, None, None, None, }, // 507 zzb
+ {ULScript_Latin, None, None, None, }, // 508 zzp
+ {ULScript_Latin, None, None, None, }, // 509 zzh
+ {ULScript_Latin, None, None, None, }, // 510 tlh
+ {ULScript_Latin, None, None, None, }, // 511 zze
+ {None, None, None, None, }, // 512 xx-Zyyy
+ {ULScript_Latin, None, None, None, }, // 513 xx-Latn
+ {ULScript_Greek, None, None, None, }, // 514 xx-Grek
+ {ULScript_Cyrillic, None, None, None, }, // 515 xx-Cyrl
+ {ULScript_Armenian, None, None, None, }, // 516 xx-Armn
+ {ULScript_Hebrew, None, None, None, }, // 517 xx-Hebr
+ {ULScript_Arabic, None, None, None, }, // 518 xx-Arab
+ {ULScript_Syriac, None, None, None, }, // 519 xx-Syrc
+ {ULScript_Thaana, None, None, None, }, // 520 xx-Thaa
+ {ULScript_Devanagari, None, None, None, }, // 521 xx-Deva
+ {ULScript_Bengali, None, None, None, }, // 522 xx-Beng
+ {ULScript_Gurmukhi, None, None, None, }, // 523 xx-Guru
+ {ULScript_Gujarati, None, None, None, }, // 524 xx-Gujr
+ {ULScript_Oriya, None, None, None, }, // 525 xx-Orya
+ {ULScript_Tamil, None, None, None, }, // 526 xx-Taml
+ {ULScript_Telugu, None, None, None, }, // 527 xx-Telu
+ {ULScript_Kannada, None, None, None, }, // 528 xx-Knda
+ {ULScript_Malayalam, None, None, None, }, // 529 xx-Mlym
+ {ULScript_Sinhala, None, None, None, }, // 530 xx-Sinh
+ {ULScript_Thai, None, None, None, }, // 531 xx-Thai
+ {ULScript_Lao, None, None, None, }, // 532 xx-Laoo
+ {ULScript_Tibetan, None, None, None, }, // 533 xx-Tibt
+ {ULScript_Myanmar, None, None, None, }, // 534 xx-Mymr
+ {ULScript_Georgian, None, None, None, }, // 535 xx-Geor
+ {None, None, None, None, }, // 536 xx-Hang
+ {ULScript_Ethiopic, None, None, None, }, // 537 xx-Ethi
+ {ULScript_Cherokee, None, None, None, }, // 538 xx-Cher
+ {ULScript_Canadian_Aboriginal, None, None, None, }, // 539 xx-Cans
+ {ULScript_Ogham, None, None, None, }, // 540 xx-Ogam
+ {ULScript_Runic, None, None, None, }, // 541 xx-Runr
+ {ULScript_Khmer, None, None, None, }, // 542 xx-Khmr
+ {ULScript_Mongolian, None, None, None, }, // 543 xx-Mong
+ {None, None, None, None, }, // 544 xx-Hira
+ {None, None, None, None, }, // 545 xx-Kana
+ {ULScript_Bopomofo, None, None, None, }, // 546 xx-Bopo
+ {ULScript_Hani, None, None, None, }, // 547 xx-Hani
+ {ULScript_Yi, None, None, None, }, // 548 xx-Yiii
+ {ULScript_Old_Italic, None, None, None, }, // 549 xx-Ital
+ {ULScript_Gothic, None, None, None, }, // 550 xx-Goth
+ {ULScript_Deseret, None, None, None, }, // 551 xx-Dsrt
+ {None, None, None, None, }, // 552 xx-Qaai
+ {ULScript_Tagalog, None, None, None, }, // 553 xx-Tglg
+ {ULScript_Hanunoo, None, None, None, }, // 554 xx-Hano
+ {ULScript_Buhid, None, None, None, }, // 555 xx-Buhd
+ {ULScript_Tagbanwa, None, None, None, }, // 556 xx-Tagb
+ {ULScript_Limbu, None, None, None, }, // 557 xx-Limb
+ {ULScript_Tai_Le, None, None, None, }, // 558 xx-Tale
+ {ULScript_Linear_B, None, None, None, }, // 559 xx-Linb
+ {ULScript_Ugaritic, None, None, None, }, // 560 xx-Ugar
+ {ULScript_Shavian, None, None, None, }, // 561 xx-Shaw
+ {ULScript_Osmanya, None, None, None, }, // 562 xx-Osma
+ {ULScript_Cypriot, None, None, None, }, // 563 xx-Cprt
+ {ULScript_Braille, None, None, None, }, // 564 xx-Brai
+ {ULScript_Buginese, None, None, None, }, // 565 xx-Bugi
+ {ULScript_Coptic, None, None, None, }, // 566 xx-Copt
+ {ULScript_New_Tai_Lue, None, None, None, }, // 567 xx-Talu
+ {ULScript_Glagolitic, None, None, None, }, // 568 xx-Glag
+ {ULScript_Tifinagh, None, None, None, }, // 569 xx-Tfng
+ {ULScript_Syloti_Nagri, None, None, None, }, // 570 xx-Sylo
+ {ULScript_Old_Persian, None, None, None, }, // 571 xx-Xpeo
+ {ULScript_Kharoshthi, None, None, None, }, // 572 xx-Khar
+ {ULScript_Balinese, None, None, None, }, // 573 xx-Bali
+ {ULScript_Cuneiform, None, None, None, }, // 574 xx-Xsux
+ {ULScript_Phoenician, None, None, None, }, // 575 xx-Phnx
+ {ULScript_Phags_Pa, None, None, None, }, // 576 xx-Phag
+ {ULScript_Nko, None, None, None, }, // 577 xx-Nkoo
+ {ULScript_Sundanese, None, None, None, }, // 578 xx-Sund
+ {ULScript_Lepcha, None, None, None, }, // 579 xx-Lepc
+ {ULScript_Ol_Chiki, None, None, None, }, // 580 xx-Olck
+ {ULScript_Vai, None, None, None, }, // 581 xx-Vaii
+ {ULScript_Saurashtra, None, None, None, }, // 582 xx-Saur
+ {ULScript_Kayah_Li, None, None, None, }, // 583 xx-Kali
+ {ULScript_Rejang, None, None, None, }, // 584 xx-Rjng
+ {ULScript_Lycian, None, None, None, }, // 585 xx-Lyci
+ {ULScript_Carian, None, None, None, }, // 586 xx-Cari
+ {ULScript_Lydian, None, None, None, }, // 587 xx-Lydi
+ {ULScript_Cham, None, None, None, }, // 588 xx-Cham
+ {ULScript_Tai_Tham, None, None, None, }, // 589 xx-Lana
+ {ULScript_Tai_Viet, None, None, None, }, // 590 xx-Tavt
+ {ULScript_Avestan, None, None, None, }, // 591 xx-Avst
+ {ULScript_Egyptian_Hieroglyphs, None, None, None, }, // 592 xx-Egyp
+ {ULScript_Samaritan, None, None, None, }, // 593 xx-Samr
+ {ULScript_Lisu, None, None, None, }, // 594 xx-Lisu
+ {ULScript_Bamum, None, None, None, }, // 595 xx-Bamu
+ {ULScript_Javanese, None, None, None, }, // 596 xx-Java
+ {ULScript_Meetei_Mayek, None, None, None, }, // 597 xx-Mtei
+ {ULScript_Imperial_Aramaic, None, None, None, }, // 598 xx-Armi
+ {ULScript_Old_South_Arabian, None, None, None, }, // 599 xx-Sarb
+ {ULScript_Inscriptional_Parthian, None, None, None, }, // 600 xx-Prti
+ {ULScript_Inscriptional_Pahlavi, None, None, None, }, // 601 xx-Phli
+ {ULScript_Old_Turkic, None, None, None, }, // 602 xx-Orkh
+ {ULScript_Kaithi, None, None, None, }, // 603 xx-Kthi
+ {ULScript_Batak, None, None, None, }, // 604 xx-Batk
+ {ULScript_Brahmi, None, None, None, }, // 605 xx-Brah
+ {ULScript_Mandaic, None, None, None, }, // 606 xx-Mand
+ {ULScript_Chakma, None, None, None, }, // 607 xx-Cakm
+ {ULScript_Meroitic_Cursive, None, None, None, }, // 608 xx-Merc
+ {ULScript_Meroitic_Hieroglyphs, None, None, None, }, // 609 xx-Mero
+ {ULScript_Miao, None, None, None, }, // 610 xx-Plrd
+ {ULScript_Sharada, None, None, None, }, // 611 xx-Shrd
+ {ULScript_Sora_Sompeng, None, None, None, }, // 612 xx-Sora
+ {ULScript_Takri, None, None, None, }, // 613 xx-Takr
+};
+#undef None
+
+// Subscripted by enum Language
+extern const int kLanguageToPLangSize = 512;
+extern const uint8 kLanguageToPLang[kLanguageToPLangSize] = {
+ 1, // 0 en
+ 2, // 1 da
+ 3, // 2 nl
+ 4, // 3 fi
+ 5, // 4 fr
+ 6, // 5 de
+ 1, // 6 he
+ 7, // 7 it
+ 2, // 8 ja
+ 3, // 9 ko
+ 8, // 10 no
+ 9, // 11 pl
+ 10, // 12 pt
+ 4, // 13 ru
+ 11, // 14 es
+ 12, // 15 sv
+ 5, // 16 zh
+ 13, // 17 cs
+ 6, // 18 el
+ 14, // 19 is
+ 15, // 20 lv
+ 16, // 21 lt
+ 17, // 22 ro
+ 18, // 23 hu
+ 19, // 24 et
+ 20, // 25 xxx
+ 21, // 26 un
+ 7, // 27 bg
+ 22, // 28 hr
+ 23, // 29 sr
+ 24, // 30 ga
+ 25, // 31 gl
+ 26, // 32 tl
+ 27, // 33 tr
+ 8, // 34 uk
+ 9, // 35 hi
+ 10, // 36 mk
+ 11, // 37 bn
+ 28, // 38 id
+ 29, // 39 la
+ 30, // 40 ms
+ 12, // 41 ml
+ 31, // 42 cy
+ 13, // 43 ne
+ 14, // 44 te
+ 32, // 45 sq
+ 15, // 46 ta
+ 16, // 47 be
+ 33, // 48 jw
+ 34, // 49 oc
+ 18, // 50 ur
+ 19, // 51 bh
+ 21, // 52 gu
+ 22, // 53 th
+ 24, // 54 ar
+ 35, // 55 ca
+ 36, // 56 eo
+ 37, // 57 eu
+ 38, // 58 ia
+ 25, // 59 kn
+ 27, // 60 pa
+ 39, // 61 gd
+ 40, // 62 sw
+ 41, // 63 sl
+ 28, // 64 mr
+ 42, // 65 mt
+ 43, // 66 vi
+ 44, // 67 fy
+ 45, // 68 sk
+ 29, // 69 zh-Hant
+ 46, // 70 fo
+ 47, // 71 su
+ 48, // 72 uz
+ 30, // 73 am
+ 49, // 74 az
+ 31, // 75 ka
+ 32, // 76 ti
+ 33, // 77 fa
+ 50, // 78 bs
+ 34, // 79 si
+ 51, // 80 nn
+ 0, // 81
+ 0, // 82
+ 52, // 83 xh
+ 53, // 84 zu
+ 54, // 85 gn
+ 55, // 86 st
+ 56, // 87 tk
+ 35, // 88 ky
+ 57, // 89 br
+ 58, // 90 tw
+ 36, // 91 yi
+ 0, // 92
+ 59, // 93 so
+ 60, // 94 ug
+ 61, // 95 ku
+ 37, // 96 mn
+ 38, // 97 hy
+ 39, // 98 lo
+ 40, // 99 sd
+ 62, // 100 rm
+ 63, // 101 af
+ 64, // 102 lb
+ 65, // 103 my
+ 41, // 104 km
+ 42, // 105 bo
+ 43, // 106 dv
+ 44, // 107 chr
+ 45, // 108 syr
+ 46, // 109 lif
+ 47, // 110 or
+ 51, // 111 as
+ 66, // 112 co
+ 67, // 113 ie
+ 68, // 114 kk
+ 69, // 115 ln
+ 0, // 116
+ 52, // 117 ps
+ 70, // 118 qu
+ 71, // 119 sn
+ 53, // 120 tg
+ 72, // 121 tt
+ 73, // 122 to
+ 74, // 123 yo
+ 0, // 124
+ 0, // 125
+ 0, // 126
+ 0, // 127
+ 75, // 128 mi
+ 76, // 129 wo
+ 54, // 130 ab
+ 77, // 131 aa
+ 78, // 132 ay
+ 55, // 133 ba
+ 79, // 134 bi
+ 57, // 135 dz
+ 80, // 136 fj
+ 81, // 137 kl
+ 82, // 138 ha
+ 83, // 139 ht
+ 84, // 140 ik
+ 58, // 141 iu
+ 59, // 142 ks
+ 85, // 143 rw
+ 86, // 144 mg
+ 87, // 145 na
+ 88, // 146 om
+ 89, // 147 rn
+ 90, // 148 sm
+ 91, // 149 sg
+ 92, // 150 sa
+ 93, // 151 ss
+ 94, // 152 ts
+ 95, // 153 tn
+ 96, // 154 vo
+ 97, // 155 za
+ 98, // 156 kha
+ 99, // 157 sco
+ 100, // 158 lg
+ 101, // 159 gv
+ 102, // 160 sr-ME
+ 103, // 161 ak
+ 104, // 162 ig
+ 105, // 163 mfe
+ 106, // 164 haw
+ 107, // 165 ceb
+ 108, // 166 ee
+ 109, // 167 gaa
+ 110, // 168 hmn
+ 111, // 169 kri
+ 112, // 170 loz
+ 113, // 171 lua
+ 114, // 172 luo
+ 62, // 173 new
+ 115, // 174 ny
+ 63, // 175 os
+ 116, // 176 pam
+ 117, // 177 nso
+ 64, // 178 raj
+ 118, // 179 crs
+ 119, // 180 tum
+ 120, // 181 ve
+ 121, // 182 war
+ 0, // 183
+ 0, // 184
+ 0, // 185
+ 0, // 186
+ 0, // 187
+ 0, // 188
+ 0, // 189
+ 0, // 190
+ 0, // 191
+ 0, // 192
+ 0, // 193
+ 0, // 194
+ 0, // 195
+ 0, // 196
+ 0, // 197
+ 0, // 198
+ 0, // 199
+ 0, // 200
+ 0, // 201
+ 0, // 202
+ 0, // 203
+ 0, // 204
+ 0, // 205
+ 0, // 206
+ 0, // 207
+ 0, // 208
+ 0, // 209
+ 0, // 210
+ 0, // 211
+ 0, // 212
+ 0, // 213
+ 0, // 214
+ 0, // 215
+ 0, // 216
+ 0, // 217
+ 0, // 218
+ 0, // 219
+ 0, // 220
+ 0, // 221
+ 0, // 222
+ 0, // 223
+ 0, // 224
+ 0, // 225
+ 0, // 226
+ 0, // 227
+ 0, // 228
+ 0, // 229
+ 0, // 230
+ 0, // 231
+ 0, // 232
+ 0, // 233
+ 0, // 234
+ 0, // 235
+ 0, // 236
+ 0, // 237
+ 0, // 238
+ 0, // 239
+ 0, // 240
+ 0, // 241
+ 0, // 242
+ 0, // 243
+ 0, // 244
+ 0, // 245
+ 0, // 246
+ 0, // 247
+ 0, // 248
+ 0, // 249
+ 0, // 250
+ 0, // 251
+ 0, // 252
+ 0, // 253
+ 0, // 254
+ 0, // 255
+ 0, // 256
+ 0, // 257
+ 0, // 258
+ 0, // 259
+ 0, // 260
+ 0, // 261
+ 0, // 262
+ 0, // 263
+ 0, // 264
+ 0, // 265
+ 0, // 266
+ 0, // 267
+ 0, // 268
+ 0, // 269
+ 0, // 270
+ 0, // 271
+ 0, // 272
+ 0, // 273
+ 0, // 274
+ 0, // 275
+ 0, // 276
+ 0, // 277
+ 0, // 278
+ 0, // 279
+ 0, // 280
+ 0, // 281
+ 0, // 282
+ 0, // 283
+ 0, // 284
+ 0, // 285
+ 0, // 286
+ 0, // 287
+ 0, // 288
+ 0, // 289
+ 0, // 290
+ 0, // 291
+ 0, // 292
+ 0, // 293
+ 0, // 294
+ 0, // 295
+ 0, // 296
+ 0, // 297
+ 0, // 298
+ 0, // 299
+ 0, // 300
+ 0, // 301
+ 0, // 302
+ 0, // 303
+ 0, // 304
+ 0, // 305
+ 0, // 306
+ 0, // 307
+ 0, // 308
+ 0, // 309
+ 0, // 310
+ 0, // 311
+ 0, // 312
+ 0, // 313
+ 0, // 314
+ 0, // 315
+ 0, // 316
+ 0, // 317
+ 0, // 318
+ 0, // 319
+ 0, // 320
+ 0, // 321
+ 0, // 322
+ 0, // 323
+ 0, // 324
+ 0, // 325
+ 0, // 326
+ 0, // 327
+ 0, // 328
+ 0, // 329
+ 0, // 330
+ 0, // 331
+ 0, // 332
+ 0, // 333
+ 0, // 334
+ 0, // 335
+ 0, // 336
+ 0, // 337
+ 0, // 338
+ 0, // 339
+ 0, // 340
+ 0, // 341
+ 0, // 342
+ 0, // 343
+ 0, // 344
+ 0, // 345
+ 0, // 346
+ 0, // 347
+ 0, // 348
+ 0, // 349
+ 0, // 350
+ 0, // 351
+ 0, // 352
+ 0, // 353
+ 0, // 354
+ 0, // 355
+ 0, // 356
+ 0, // 357
+ 0, // 358
+ 0, // 359
+ 0, // 360
+ 0, // 361
+ 0, // 362
+ 0, // 363
+ 0, // 364
+ 0, // 365
+ 0, // 366
+ 0, // 367
+ 0, // 368
+ 0, // 369
+ 0, // 370
+ 0, // 371
+ 0, // 372
+ 0, // 373
+ 0, // 374
+ 0, // 375
+ 0, // 376
+ 0, // 377
+ 0, // 378
+ 0, // 379
+ 0, // 380
+ 0, // 381
+ 0, // 382
+ 0, // 383
+ 0, // 384
+ 0, // 385
+ 0, // 386
+ 0, // 387
+ 0, // 388
+ 0, // 389
+ 0, // 390
+ 0, // 391
+ 0, // 392
+ 0, // 393
+ 0, // 394
+ 0, // 395
+ 0, // 396
+ 0, // 397
+ 0, // 398
+ 0, // 399
+ 0, // 400
+ 0, // 401
+ 0, // 402
+ 0, // 403
+ 0, // 404
+ 0, // 405
+ 0, // 406
+ 0, // 407
+ 0, // 408
+ 0, // 409
+ 0, // 410
+ 0, // 411
+ 0, // 412
+ 0, // 413
+ 0, // 414
+ 0, // 415
+ 0, // 416
+ 0, // 417
+ 0, // 418
+ 0, // 419
+ 0, // 420
+ 0, // 421
+ 0, // 422
+ 0, // 423
+ 0, // 424
+ 0, // 425
+ 0, // 426
+ 0, // 427
+ 0, // 428
+ 0, // 429
+ 0, // 430
+ 0, // 431
+ 0, // 432
+ 0, // 433
+ 0, // 434
+ 0, // 435
+ 0, // 436
+ 0, // 437
+ 0, // 438
+ 0, // 439
+ 0, // 440
+ 0, // 441
+ 0, // 442
+ 0, // 443
+ 0, // 444
+ 0, // 445
+ 0, // 446
+ 0, // 447
+ 0, // 448
+ 0, // 449
+ 0, // 450
+ 0, // 451
+ 0, // 452
+ 0, // 453
+ 0, // 454
+ 0, // 455
+ 0, // 456
+ 0, // 457
+ 0, // 458
+ 0, // 459
+ 0, // 460
+ 0, // 461
+ 0, // 462
+ 0, // 463
+ 0, // 464
+ 0, // 465
+ 0, // 466
+ 0, // 467
+ 0, // 468
+ 0, // 469
+ 0, // 470
+ 0, // 471
+ 0, // 472
+ 0, // 473
+ 0, // 474
+ 0, // 475
+ 0, // 476
+ 0, // 477
+ 0, // 478
+ 0, // 479
+ 0, // 480
+ 0, // 481
+ 0, // 482
+ 0, // 483
+ 0, // 484
+ 0, // 485
+ 0, // 486
+ 0, // 487
+ 0, // 488
+ 0, // 489
+ 0, // 490
+ 0, // 491
+ 0, // 492
+ 0, // 493
+ 0, // 494
+ 0, // 495
+ 0, // 496
+ 0, // 497
+ 0, // 498
+ 0, // 499
+ 0, // 500
+ 0, // 501
+ 0, // 502
+ 0, // 503
+ 0, // 504
+ 0, // 505
+ 250, // 506 nr
+ 251, // 507 zzb
+ 252, // 508 zzp
+ 253, // 509 zzh
+ 254, // 510 tlh
+ 255, // 511 zze
+};
+
+// Subscripted by PLang, for ULScript = Latn
+extern const uint16 kPLangToLanguageLatn[256] = {
+ UNKNOWN_LANGUAGE, // 0
+ ENGLISH, // 1
+ DANISH, // 2
+ DUTCH, // 3
+ FINNISH, // 4
+ FRENCH, // 5
+ GERMAN, // 6
+ ITALIAN, // 7
+ NORWEGIAN, // 8
+ POLISH, // 9
+ PORTUGUESE, // 10
+ SPANISH, // 11
+ SWEDISH, // 12
+ CZECH, // 13
+ ICELANDIC, // 14
+ LATVIAN, // 15
+ LITHUANIAN, // 16
+ ROMANIAN, // 17
+ HUNGARIAN, // 18
+ ESTONIAN, // 19
+ TG_UNKNOWN_LANGUAGE, // 20
+ UNKNOWN_LANGUAGE, // 21
+ CROATIAN, // 22
+ SERBIAN, // 23
+ IRISH, // 24
+ GALICIAN, // 25
+ TAGALOG, // 26
+ TURKISH, // 27
+ INDONESIAN, // 28
+ LATIN, // 29
+ MALAY, // 30
+ WELSH, // 31
+ ALBANIAN, // 32
+ JAVANESE, // 33
+ OCCITAN, // 34
+ CATALAN, // 35
+ ESPERANTO, // 36
+ BASQUE, // 37
+ INTERLINGUA, // 38
+ SCOTS_GAELIC, // 39
+ SWAHILI, // 40
+ SLOVENIAN, // 41
+ MALTESE, // 42
+ VIETNAMESE, // 43
+ FRISIAN, // 44
+ SLOVAK, // 45
+ FAROESE, // 46
+ SUNDANESE, // 47
+ UZBEK, // 48
+ AZERBAIJANI, // 49
+ BOSNIAN, // 50
+ NORWEGIAN_N, // 51
+ XHOSA, // 52
+ ZULU, // 53
+ GUARANI, // 54
+ SESOTHO, // 55
+ TURKMEN, // 56
+ BRETON, // 57
+ TWI, // 58
+ SOMALI, // 59
+ UIGHUR, // 60
+ KURDISH, // 61
+ RHAETO_ROMANCE, // 62
+ AFRIKAANS, // 63
+ LUXEMBOURGISH, // 64
+ BURMESE, // 65
+ CORSICAN, // 66
+ INTERLINGUE, // 67
+ KAZAKH, // 68
+ LINGALA, // 69
+ QUECHUA, // 70
+ SHONA, // 71
+ TATAR, // 72
+ TONGA, // 73
+ YORUBA, // 74
+ MAORI, // 75
+ WOLOF, // 76
+ AFAR, // 77
+ AYMARA, // 78
+ BISLAMA, // 79
+ FIJIAN, // 80
+ GREENLANDIC, // 81
+ HAUSA, // 82
+ HAITIAN_CREOLE, // 83
+ INUPIAK, // 84
+ KINYARWANDA, // 85
+ MALAGASY, // 86
+ NAURU, // 87
+ OROMO, // 88
+ RUNDI, // 89
+ SAMOAN, // 90
+ SANGO, // 91
+ SANSKRIT, // 92
+ SISWANT, // 93
+ TSONGA, // 94
+ TSWANA, // 95
+ VOLAPUK, // 96
+ ZHUANG, // 97
+ KHASI, // 98
+ SCOTS, // 99
+ GANDA, // 100
+ MANX, // 101
+ MONTENEGRIN, // 102
+ AKAN, // 103
+ IGBO, // 104
+ MAURITIAN_CREOLE, // 105
+ HAWAIIAN, // 106
+ CEBUANO, // 107
+ EWE, // 108
+ GA, // 109
+ HMONG, // 110
+ KRIO, // 111
+ LOZI, // 112
+ LUBA_LULUA, // 113
+ LUO_KENYA_AND_TANZANIA, // 114
+ NYANJA, // 115
+ PAMPANGA, // 116
+ PEDI, // 117
+ SESELWA, // 118
+ TUMBUKA, // 119
+ VENDA, // 120
+ WARAY_PHILIPPINES, // 121
+ UNKNOWN_LANGUAGE, // 122
+ UNKNOWN_LANGUAGE, // 123
+ UNKNOWN_LANGUAGE, // 124
+ UNKNOWN_LANGUAGE, // 125
+ UNKNOWN_LANGUAGE, // 126
+ UNKNOWN_LANGUAGE, // 127
+ UNKNOWN_LANGUAGE, // 128
+ UNKNOWN_LANGUAGE, // 129
+ UNKNOWN_LANGUAGE, // 130
+ UNKNOWN_LANGUAGE, // 131
+ UNKNOWN_LANGUAGE, // 132
+ UNKNOWN_LANGUAGE, // 133
+ UNKNOWN_LANGUAGE, // 134
+ UNKNOWN_LANGUAGE, // 135
+ UNKNOWN_LANGUAGE, // 136
+ UNKNOWN_LANGUAGE, // 137
+ UNKNOWN_LANGUAGE, // 138
+ UNKNOWN_LANGUAGE, // 139
+ UNKNOWN_LANGUAGE, // 140
+ UNKNOWN_LANGUAGE, // 141
+ UNKNOWN_LANGUAGE, // 142
+ UNKNOWN_LANGUAGE, // 143
+ UNKNOWN_LANGUAGE, // 144
+ UNKNOWN_LANGUAGE, // 145
+ UNKNOWN_LANGUAGE, // 146
+ UNKNOWN_LANGUAGE, // 147
+ UNKNOWN_LANGUAGE, // 148
+ UNKNOWN_LANGUAGE, // 149
+ UNKNOWN_LANGUAGE, // 150
+ UNKNOWN_LANGUAGE, // 151
+ UNKNOWN_LANGUAGE, // 152
+ UNKNOWN_LANGUAGE, // 153
+ UNKNOWN_LANGUAGE, // 154
+ UNKNOWN_LANGUAGE, // 155
+ UNKNOWN_LANGUAGE, // 156
+ UNKNOWN_LANGUAGE, // 157
+ UNKNOWN_LANGUAGE, // 158
+ UNKNOWN_LANGUAGE, // 159
+ UNKNOWN_LANGUAGE, // 160
+ UNKNOWN_LANGUAGE, // 161
+ UNKNOWN_LANGUAGE, // 162
+ UNKNOWN_LANGUAGE, // 163
+ UNKNOWN_LANGUAGE, // 164
+ UNKNOWN_LANGUAGE, // 165
+ UNKNOWN_LANGUAGE, // 166
+ UNKNOWN_LANGUAGE, // 167
+ UNKNOWN_LANGUAGE, // 168
+ UNKNOWN_LANGUAGE, // 169
+ UNKNOWN_LANGUAGE, // 170
+ UNKNOWN_LANGUAGE, // 171
+ UNKNOWN_LANGUAGE, // 172
+ UNKNOWN_LANGUAGE, // 173
+ UNKNOWN_LANGUAGE, // 174
+ UNKNOWN_LANGUAGE, // 175
+ UNKNOWN_LANGUAGE, // 176
+ UNKNOWN_LANGUAGE, // 177
+ UNKNOWN_LANGUAGE, // 178
+ UNKNOWN_LANGUAGE, // 179
+ UNKNOWN_LANGUAGE, // 180
+ UNKNOWN_LANGUAGE, // 181
+ UNKNOWN_LANGUAGE, // 182
+ UNKNOWN_LANGUAGE, // 183
+ UNKNOWN_LANGUAGE, // 184
+ UNKNOWN_LANGUAGE, // 185
+ UNKNOWN_LANGUAGE, // 186
+ UNKNOWN_LANGUAGE, // 187
+ UNKNOWN_LANGUAGE, // 188
+ UNKNOWN_LANGUAGE, // 189
+ UNKNOWN_LANGUAGE, // 190
+ UNKNOWN_LANGUAGE, // 191
+ UNKNOWN_LANGUAGE, // 192
+ UNKNOWN_LANGUAGE, // 193
+ UNKNOWN_LANGUAGE, // 194
+ UNKNOWN_LANGUAGE, // 195
+ UNKNOWN_LANGUAGE, // 196
+ UNKNOWN_LANGUAGE, // 197
+ UNKNOWN_LANGUAGE, // 198
+ UNKNOWN_LANGUAGE, // 199
+ UNKNOWN_LANGUAGE, // 200
+ UNKNOWN_LANGUAGE, // 201
+ UNKNOWN_LANGUAGE, // 202
+ UNKNOWN_LANGUAGE, // 203
+ UNKNOWN_LANGUAGE, // 204
+ UNKNOWN_LANGUAGE, // 205
+ UNKNOWN_LANGUAGE, // 206
+ UNKNOWN_LANGUAGE, // 207
+ UNKNOWN_LANGUAGE, // 208
+ UNKNOWN_LANGUAGE, // 209
+ UNKNOWN_LANGUAGE, // 210
+ UNKNOWN_LANGUAGE, // 211
+ UNKNOWN_LANGUAGE, // 212
+ UNKNOWN_LANGUAGE, // 213
+ UNKNOWN_LANGUAGE, // 214
+ UNKNOWN_LANGUAGE, // 215
+ UNKNOWN_LANGUAGE, // 216
+ UNKNOWN_LANGUAGE, // 217
+ UNKNOWN_LANGUAGE, // 218
+ UNKNOWN_LANGUAGE, // 219
+ UNKNOWN_LANGUAGE, // 220
+ UNKNOWN_LANGUAGE, // 221
+ UNKNOWN_LANGUAGE, // 222
+ UNKNOWN_LANGUAGE, // 223
+ UNKNOWN_LANGUAGE, // 224
+ UNKNOWN_LANGUAGE, // 225
+ UNKNOWN_LANGUAGE, // 226
+ UNKNOWN_LANGUAGE, // 227
+ UNKNOWN_LANGUAGE, // 228
+ UNKNOWN_LANGUAGE, // 229
+ UNKNOWN_LANGUAGE, // 230
+ UNKNOWN_LANGUAGE, // 231
+ UNKNOWN_LANGUAGE, // 232
+ UNKNOWN_LANGUAGE, // 233
+ UNKNOWN_LANGUAGE, // 234
+ UNKNOWN_LANGUAGE, // 235
+ UNKNOWN_LANGUAGE, // 236
+ UNKNOWN_LANGUAGE, // 237
+ UNKNOWN_LANGUAGE, // 238
+ UNKNOWN_LANGUAGE, // 239
+ UNKNOWN_LANGUAGE, // 240
+ UNKNOWN_LANGUAGE, // 241
+ UNKNOWN_LANGUAGE, // 242
+ UNKNOWN_LANGUAGE, // 243
+ UNKNOWN_LANGUAGE, // 244
+ UNKNOWN_LANGUAGE, // 245
+ UNKNOWN_LANGUAGE, // 246
+ UNKNOWN_LANGUAGE, // 247
+ UNKNOWN_LANGUAGE, // 248
+ UNKNOWN_LANGUAGE, // 249
+ NDEBELE, // 250
+ X_BORK_BORK_BORK, // 251
+ X_PIG_LATIN, // 252
+ X_HACKER, // 253
+ X_KLINGON, // 254
+ X_ELMER_FUDD, // 255
+};
+
+// Subscripted by PLang, for ULScript != Latn
+extern const uint16 kPLangToLanguageOthr[256] = {
+ UNKNOWN_LANGUAGE, // 0
+ HEBREW, // 1
+ JAPANESE, // 2
+ KOREAN, // 3
+ RUSSIAN, // 4
+ CHINESE, // 5
+ GREEK, // 6
+ BULGARIAN, // 7
+ UKRAINIAN, // 8
+ HINDI, // 9
+ MACEDONIAN, // 10
+ BENGALI, // 11
+ MALAYALAM, // 12
+ NEPALI, // 13
+ TELUGU, // 14
+ TAMIL, // 15
+ BELARUSIAN, // 16
+ ROMANIAN, // 17
+ URDU, // 18
+ BIHARI, // 19
+ TG_UNKNOWN_LANGUAGE, // 20
+ UNKNOWN_LANGUAGE, // 21 (updated 2013.09.07 dsites)
+ THAI, // 22
+ SERBIAN, // 23
+ ARABIC, // 24
+ KANNADA, // 25
+ TAGALOG, // 26
+ PUNJABI, // 27
+ MARATHI, // 28
+ CHINESE_T, // 29
+ AMHARIC, // 30
+ GEORGIAN, // 31
+ TIGRINYA, // 32
+ PERSIAN, // 33
+ SINHALESE, // 34
+ KYRGYZ, // 35
+ YIDDISH, // 36
+ MONGOLIAN, // 37
+ ARMENIAN, // 38
+ LAOTHIAN, // 39
+ SINDHI, // 40
+ KHMER, // 41
+ TIBETAN, // 42
+ DHIVEHI, // 43
+ CHEROKEE, // 44
+ SYRIAC, // 45
+ LIMBU, // 46
+ ORIYA, // 47
+ UZBEK, // 48
+ AZERBAIJANI, // 49
+ BOSNIAN, // 50
+ ASSAMESE, // 51
+ PASHTO, // 52
+ TAJIK, // 53
+ ABKHAZIAN, // 54
+ BASHKIR, // 55
+ TURKMEN, // 56
+ DZONGKHA, // 57
+ INUKTITUT, // 58
+ KASHMIRI, // 59
+ UIGHUR, // 60
+ KURDISH, // 61
+ NEWARI, // 62
+ OSSETIAN, // 63
+ RAJASTHANI, // 64
+ BURMESE, // 65
+ UNKNOWN_LANGUAGE, // 66
+ UNKNOWN_LANGUAGE, // 67
+ KAZAKH, // 68
+ UNKNOWN_LANGUAGE, // 69
+ UNKNOWN_LANGUAGE, // 70
+ UNKNOWN_LANGUAGE, // 71
+ TATAR, // 72
+ UNKNOWN_LANGUAGE, // 73
+ UNKNOWN_LANGUAGE, // 74
+ UNKNOWN_LANGUAGE, // 75
+ UNKNOWN_LANGUAGE, // 76
+ UNKNOWN_LANGUAGE, // 77
+ UNKNOWN_LANGUAGE, // 78
+ UNKNOWN_LANGUAGE, // 79
+ UNKNOWN_LANGUAGE, // 80
+ UNKNOWN_LANGUAGE, // 81
+ HAUSA, // 82
+ UNKNOWN_LANGUAGE, // 83
+ UNKNOWN_LANGUAGE, // 84
+ UNKNOWN_LANGUAGE, // 85
+ UNKNOWN_LANGUAGE, // 86
+ UNKNOWN_LANGUAGE, // 87
+ UNKNOWN_LANGUAGE, // 88
+ UNKNOWN_LANGUAGE, // 89
+ UNKNOWN_LANGUAGE, // 90
+ UNKNOWN_LANGUAGE, // 91
+ SANSKRIT, // 92
+ UNKNOWN_LANGUAGE, // 93
+ UNKNOWN_LANGUAGE, // 94
+ UNKNOWN_LANGUAGE, // 95
+ UNKNOWN_LANGUAGE, // 96
+ ZHUANG, // 97
+ UNKNOWN_LANGUAGE, // 98
+ UNKNOWN_LANGUAGE, // 99
+ UNKNOWN_LANGUAGE, // 100
+ UNKNOWN_LANGUAGE, // 101
+ UNKNOWN_LANGUAGE, // 102
+ UNKNOWN_LANGUAGE, // 103
+ UNKNOWN_LANGUAGE, // 104
+ UNKNOWN_LANGUAGE, // 105
+ UNKNOWN_LANGUAGE, // 106
+ UNKNOWN_LANGUAGE, // 107
+ UNKNOWN_LANGUAGE, // 108
+ UNKNOWN_LANGUAGE, // 109
+ UNKNOWN_LANGUAGE, // 110
+ UNKNOWN_LANGUAGE, // 111
+ UNKNOWN_LANGUAGE, // 112
+ UNKNOWN_LANGUAGE, // 113
+ UNKNOWN_LANGUAGE, // 114
+ UNKNOWN_LANGUAGE, // 115
+ UNKNOWN_LANGUAGE, // 116
+ UNKNOWN_LANGUAGE, // 117
+ UNKNOWN_LANGUAGE, // 118
+ UNKNOWN_LANGUAGE, // 119
+ UNKNOWN_LANGUAGE, // 120
+ UNKNOWN_LANGUAGE, // 121
+ UNKNOWN_LANGUAGE, // 122
+ UNKNOWN_LANGUAGE, // 123
+ UNKNOWN_LANGUAGE, // 124
+ UNKNOWN_LANGUAGE, // 125
+ UNKNOWN_LANGUAGE, // 126
+ UNKNOWN_LANGUAGE, // 127
+ UNKNOWN_LANGUAGE, // 128
+ UNKNOWN_LANGUAGE, // 129
+ UNKNOWN_LANGUAGE, // 130
+ UNKNOWN_LANGUAGE, // 131
+ UNKNOWN_LANGUAGE, // 132
+ UNKNOWN_LANGUAGE, // 133
+ UNKNOWN_LANGUAGE, // 134
+ UNKNOWN_LANGUAGE, // 135
+ UNKNOWN_LANGUAGE, // 136
+ UNKNOWN_LANGUAGE, // 137
+ UNKNOWN_LANGUAGE, // 138
+ UNKNOWN_LANGUAGE, // 139
+ UNKNOWN_LANGUAGE, // 140
+ UNKNOWN_LANGUAGE, // 141
+ UNKNOWN_LANGUAGE, // 142
+ UNKNOWN_LANGUAGE, // 143
+ UNKNOWN_LANGUAGE, // 144
+ UNKNOWN_LANGUAGE, // 145
+ UNKNOWN_LANGUAGE, // 146
+ UNKNOWN_LANGUAGE, // 147
+ UNKNOWN_LANGUAGE, // 148
+ UNKNOWN_LANGUAGE, // 149
+ UNKNOWN_LANGUAGE, // 150
+ UNKNOWN_LANGUAGE, // 151
+ UNKNOWN_LANGUAGE, // 152
+ UNKNOWN_LANGUAGE, // 153
+ UNKNOWN_LANGUAGE, // 154
+ UNKNOWN_LANGUAGE, // 155
+ UNKNOWN_LANGUAGE, // 156
+ UNKNOWN_LANGUAGE, // 157
+ UNKNOWN_LANGUAGE, // 158
+ UNKNOWN_LANGUAGE, // 159
+ UNKNOWN_LANGUAGE, // 160
+ UNKNOWN_LANGUAGE, // 161
+ UNKNOWN_LANGUAGE, // 162
+ UNKNOWN_LANGUAGE, // 163
+ UNKNOWN_LANGUAGE, // 164
+ UNKNOWN_LANGUAGE, // 165
+ UNKNOWN_LANGUAGE, // 166
+ UNKNOWN_LANGUAGE, // 167
+ UNKNOWN_LANGUAGE, // 168
+ UNKNOWN_LANGUAGE, // 169
+ UNKNOWN_LANGUAGE, // 170
+ UNKNOWN_LANGUAGE, // 171
+ UNKNOWN_LANGUAGE, // 172
+ UNKNOWN_LANGUAGE, // 173
+ UNKNOWN_LANGUAGE, // 174
+ UNKNOWN_LANGUAGE, // 175
+ UNKNOWN_LANGUAGE, // 176
+ UNKNOWN_LANGUAGE, // 177
+ UNKNOWN_LANGUAGE, // 178
+ UNKNOWN_LANGUAGE, // 179
+ UNKNOWN_LANGUAGE, // 180
+ UNKNOWN_LANGUAGE, // 181
+ UNKNOWN_LANGUAGE, // 182
+ UNKNOWN_LANGUAGE, // 183
+ UNKNOWN_LANGUAGE, // 184
+ UNKNOWN_LANGUAGE, // 185
+ UNKNOWN_LANGUAGE, // 186
+ UNKNOWN_LANGUAGE, // 187
+ UNKNOWN_LANGUAGE, // 188
+ UNKNOWN_LANGUAGE, // 189
+ UNKNOWN_LANGUAGE, // 190
+ UNKNOWN_LANGUAGE, // 191
+ UNKNOWN_LANGUAGE, // 192
+ UNKNOWN_LANGUAGE, // 193
+ UNKNOWN_LANGUAGE, // 194
+ UNKNOWN_LANGUAGE, // 195
+ UNKNOWN_LANGUAGE, // 196
+ UNKNOWN_LANGUAGE, // 197
+ UNKNOWN_LANGUAGE, // 198
+ UNKNOWN_LANGUAGE, // 199
+ UNKNOWN_LANGUAGE, // 200
+ UNKNOWN_LANGUAGE, // 201
+ UNKNOWN_LANGUAGE, // 202
+ UNKNOWN_LANGUAGE, // 203
+ UNKNOWN_LANGUAGE, // 204
+ UNKNOWN_LANGUAGE, // 205
+ UNKNOWN_LANGUAGE, // 206
+ UNKNOWN_LANGUAGE, // 207
+ UNKNOWN_LANGUAGE, // 208
+ UNKNOWN_LANGUAGE, // 209
+ UNKNOWN_LANGUAGE, // 210
+ UNKNOWN_LANGUAGE, // 211
+ UNKNOWN_LANGUAGE, // 212
+ UNKNOWN_LANGUAGE, // 213
+ UNKNOWN_LANGUAGE, // 214
+ UNKNOWN_LANGUAGE, // 215
+ UNKNOWN_LANGUAGE, // 216
+ UNKNOWN_LANGUAGE, // 217
+ UNKNOWN_LANGUAGE, // 218
+ UNKNOWN_LANGUAGE, // 219
+ UNKNOWN_LANGUAGE, // 220
+ UNKNOWN_LANGUAGE, // 221
+ UNKNOWN_LANGUAGE, // 222
+ UNKNOWN_LANGUAGE, // 223
+ UNKNOWN_LANGUAGE, // 224
+ UNKNOWN_LANGUAGE, // 225
+ UNKNOWN_LANGUAGE, // 226
+ UNKNOWN_LANGUAGE, // 227
+ UNKNOWN_LANGUAGE, // 228
+ UNKNOWN_LANGUAGE, // 229
+ UNKNOWN_LANGUAGE, // 230
+ UNKNOWN_LANGUAGE, // 231
+ UNKNOWN_LANGUAGE, // 232
+ UNKNOWN_LANGUAGE, // 233
+ UNKNOWN_LANGUAGE, // 234
+ UNKNOWN_LANGUAGE, // 235
+ UNKNOWN_LANGUAGE, // 236
+ UNKNOWN_LANGUAGE, // 237
+ UNKNOWN_LANGUAGE, // 238
+ UNKNOWN_LANGUAGE, // 239
+ UNKNOWN_LANGUAGE, // 240
+ UNKNOWN_LANGUAGE, // 241
+ UNKNOWN_LANGUAGE, // 242
+ UNKNOWN_LANGUAGE, // 243
+ UNKNOWN_LANGUAGE, // 244
+ UNKNOWN_LANGUAGE, // 245
+ UNKNOWN_LANGUAGE, // 246
+ UNKNOWN_LANGUAGE, // 247
+ UNKNOWN_LANGUAGE, // 248
+ UNKNOWN_LANGUAGE, // 249
+ UNKNOWN_LANGUAGE, // 250
+ UNKNOWN_LANGUAGE, // 251
+ UNKNOWN_LANGUAGE, // 252
+ UNKNOWN_LANGUAGE, // 253
+ UNKNOWN_LANGUAGE, // 254
+ UNKNOWN_LANGUAGE, // 255
+};
+
+// Subscripted by PLang, for ULScript = Latn
+extern const uint8 kPLangToCloseSetLatn[256] = {
+ 0, // 0
+ 0, // 1
+ 7, // 2 da
+ 0, // 3
+ 0, // 4
+ 0, // 5
+ 0, // 6
+ 0, // 7
+ 7, // 8 no
+ 0, // 9
+ 8, // 10 pt
+ 8, // 11 es
+ 0, // 12
+ 3, // 13 cs
+ 0, // 14
+ 0, // 15
+ 0, // 16
+ 0, // 17
+ 0, // 18
+ 0, // 19
+ 0, // 20
+ 0, // 21
+ 5, // 22 hr
+ 5, // 23 sr
+ 0, // 24
+ 8, // 25 gl
+ 0, // 26
+ 0, // 27
+ 1, // 28 id
+ 0, // 29
+ 1, // 30 ms
+ 0, // 31
+ 0, // 32
+ 0, // 33
+ 0, // 34
+ 0, // 35
+ 0, // 36
+ 0, // 37
+ 0, // 38
+ 0, // 39
+ 0, // 40
+ 0, // 41
+ 0, // 42
+ 0, // 43
+ 0, // 44
+ 3, // 45 sk
+ 0, // 46
+ 0, // 47
+ 0, // 48
+ 0, // 49
+ 0, // 50
+ 7, // 51 nn
+ 4, // 52 xh
+ 4, // 53 zu
+ 0, // 54
+ 0, // 55
+ 0, // 56
+ 0, // 57
+ 0, // 58
+ 0, // 59
+ 0, // 60
+ 0, // 61
+ 0, // 62
+ 0, // 63
+ 0, // 64
+ 0, // 65
+ 0, // 66
+ 0, // 67
+ 0, // 68
+ 0, // 69
+ 0, // 70
+ 0, // 71
+ 0, // 72
+ 0, // 73
+ 0, // 74
+ 0, // 75
+ 0, // 76
+ 0, // 77
+ 0, // 78
+ 0, // 79
+ 0, // 80
+ 0, // 81
+ 0, // 82
+ 0, // 83
+ 0, // 84
+ 9, // 85 rw
+ 0, // 86
+ 0, // 87
+ 0, // 88
+ 9, // 89 rn
+ 0, // 90
+ 0, // 91
+ 0, // 92
+ 0, // 93
+ 0, // 94
+ 0, // 95
+ 0, // 96
+ 0, // 97
+ 0, // 98
+ 0, // 99
+ 0, // 100
+ 0, // 101
+ 0, // 102
+ 0, // 103
+ 0, // 104
+ 0, // 105
+ 0, // 106
+ 0, // 107
+ 0, // 108
+ 0, // 109
+ 0, // 110
+ 0, // 111
+ 0, // 112
+ 0, // 113
+ 0, // 114
+ 0, // 115
+ 0, // 116
+ 0, // 117
+ 0, // 118
+ 0, // 119
+ 0, // 120
+ 0, // 121
+ 0, // 122
+ 0, // 123
+ 0, // 124
+ 0, // 125
+ 0, // 126
+ 0, // 127
+ 0, // 128
+ 0, // 129
+ 0, // 130
+ 0, // 131
+ 0, // 132
+ 0, // 133
+ 0, // 134
+ 0, // 135
+ 0, // 136
+ 0, // 137
+ 0, // 138
+ 0, // 139
+ 0, // 140
+ 0, // 141
+ 0, // 142
+ 0, // 143
+ 0, // 144
+ 0, // 145
+ 0, // 146
+ 0, // 147
+ 0, // 148
+ 0, // 149
+ 0, // 150
+ 0, // 151
+ 0, // 152
+ 0, // 153
+ 0, // 154
+ 0, // 155
+ 0, // 156
+ 0, // 157
+ 0, // 158
+ 0, // 159
+ 0, // 160
+ 0, // 161
+ 0, // 162
+ 0, // 163
+ 0, // 164
+ 0, // 165
+ 0, // 166
+ 0, // 167
+ 0, // 168
+ 0, // 169
+ 0, // 170
+ 0, // 171
+ 0, // 172
+ 0, // 173
+ 0, // 174
+ 0, // 175
+ 0, // 176
+ 0, // 177
+ 0, // 178
+ 0, // 179
+ 0, // 180
+ 0, // 181
+ 0, // 182
+ 0, // 183
+ 0, // 184
+ 0, // 185
+ 0, // 186
+ 0, // 187
+ 0, // 188
+ 0, // 189
+ 0, // 190
+ 0, // 191
+ 0, // 192
+ 0, // 193
+ 0, // 194
+ 0, // 195
+ 0, // 196
+ 0, // 197
+ 0, // 198
+ 0, // 199
+ 0, // 200
+ 0, // 201
+ 0, // 202
+ 0, // 203
+ 0, // 204
+ 0, // 205
+ 0, // 206
+ 0, // 207
+ 0, // 208
+ 0, // 209
+ 0, // 210
+ 0, // 211
+ 0, // 212
+ 0, // 213
+ 0, // 214
+ 0, // 215
+ 0, // 216
+ 0, // 217
+ 0, // 218
+ 0, // 219
+ 0, // 220
+ 0, // 221
+ 0, // 222
+ 0, // 223
+ 0, // 224
+ 0, // 225
+ 0, // 226
+ 0, // 227
+ 0, // 228
+ 0, // 229
+ 0, // 230
+ 0, // 231
+ 0, // 232
+ 0, // 233
+ 0, // 234
+ 0, // 235
+ 0, // 236
+ 0, // 237
+ 0, // 238
+ 0, // 239
+ 0, // 240
+ 0, // 241
+ 0, // 242
+ 0, // 243
+ 0, // 244
+ 0, // 245
+ 0, // 246
+ 0, // 247
+ 0, // 248
+ 0, // 249
+ 0, // 250
+ 0, // 251
+ 0, // 252
+ 0, // 253
+ 0, // 254
+ 0, // 255
+};
+
+// Subscripted by PLang, for ULScript != Latn
+extern const uint8 kPLangToCloseSetOthr[256] = {
+ 0, // 0
+ 0, // 1
+ 0, // 2
+ 0, // 3
+ 0, // 4
+ 0, // 5
+ 0, // 6
+ 0, // 7
+ 0, // 8
+ 6, // 9 hi
+ 0, // 10
+ 0, // 11
+ 0, // 12
+ 6, // 13 ne
+ 0, // 14
+ 0, // 15
+ 0, // 16
+ 0, // 17
+ 0, // 18
+ 6, // 19 bh
+ 0, // 20
+ 0, // 21
+ 0, // 22
+ 0, // 23
+ 0, // 24
+ 0, // 25
+ 0, // 26
+ 0, // 27
+ 6, // 28 mr
+ 0, // 29
+ 0, // 30
+ 0, // 31
+ 0, // 32
+ 0, // 33
+ 0, // 34
+ 0, // 35
+ 0, // 36
+ 0, // 37
+ 0, // 38
+ 0, // 39
+ 0, // 40
+ 0, // 41
+ 2, // 42 bo
+ 0, // 43
+ 0, // 44
+ 0, // 45
+ 0, // 46
+ 0, // 47
+ 0, // 48
+ 0, // 49
+ 0, // 50
+ 0, // 51
+ 0, // 52
+ 0, // 53
+ 0, // 54
+ 0, // 55
+ 0, // 56
+ 2, // 57 dz
+ 0, // 58
+ 0, // 59
+ 0, // 60
+ 0, // 61
+ 0, // 62
+ 0, // 63
+ 0, // 64
+ 0, // 65
+ 0, // 66
+ 0, // 67
+ 0, // 68
+ 0, // 69
+ 0, // 70
+ 0, // 71
+ 0, // 72
+ 0, // 73
+ 0, // 74
+ 0, // 75
+ 0, // 76
+ 0, // 77
+ 0, // 78
+ 0, // 79
+ 0, // 80
+ 0, // 81
+ 0, // 82
+ 0, // 83
+ 0, // 84
+ 0, // 85
+ 0, // 86
+ 0, // 87
+ 0, // 88
+ 0, // 89
+ 0, // 90
+ 0, // 91
+ 0, // 92
+ 0, // 93
+ 0, // 94
+ 0, // 95
+ 0, // 96
+ 0, // 97
+ 0, // 98
+ 0, // 99
+ 0, // 100
+ 0, // 101
+ 0, // 102
+ 0, // 103
+ 0, // 104
+ 0, // 105
+ 0, // 106
+ 0, // 107
+ 0, // 108
+ 0, // 109
+ 0, // 110
+ 0, // 111
+ 0, // 112
+ 0, // 113
+ 0, // 114
+ 0, // 115
+ 0, // 116
+ 0, // 117
+ 0, // 118
+ 0, // 119
+ 0, // 120
+ 0, // 121
+ 0, // 122
+ 0, // 123
+ 0, // 124
+ 0, // 125
+ 0, // 126
+ 0, // 127
+ 0, // 128
+ 0, // 129
+ 0, // 130
+ 0, // 131
+ 0, // 132
+ 0, // 133
+ 0, // 134
+ 0, // 135
+ 0, // 136
+ 0, // 137
+ 0, // 138
+ 0, // 139
+ 0, // 140
+ 0, // 141
+ 0, // 142
+ 0, // 143
+ 0, // 144
+ 0, // 145
+ 0, // 146
+ 0, // 147
+ 0, // 148
+ 0, // 149
+ 0, // 150
+ 0, // 151
+ 0, // 152
+ 0, // 153
+ 0, // 154
+ 0, // 155
+ 0, // 156
+ 0, // 157
+ 0, // 158
+ 0, // 159
+ 0, // 160
+ 0, // 161
+ 0, // 162
+ 0, // 163
+ 0, // 164
+ 0, // 165
+ 0, // 166
+ 0, // 167
+ 0, // 168
+ 0, // 169
+ 0, // 170
+ 0, // 171
+ 0, // 172
+ 0, // 173
+ 0, // 174
+ 0, // 175
+ 0, // 176
+ 0, // 177
+ 0, // 178
+ 0, // 179
+ 0, // 180
+ 0, // 181
+ 0, // 182
+ 0, // 183
+ 0, // 184
+ 0, // 185
+ 0, // 186
+ 0, // 187
+ 0, // 188
+ 0, // 189
+ 0, // 190
+ 0, // 191
+ 0, // 192
+ 0, // 193
+ 0, // 194
+ 0, // 195
+ 0, // 196
+ 0, // 197
+ 0, // 198
+ 0, // 199
+ 0, // 200
+ 0, // 201
+ 0, // 202
+ 0, // 203
+ 0, // 204
+ 0, // 205
+ 0, // 206
+ 0, // 207
+ 0, // 208
+ 0, // 209
+ 0, // 210
+ 0, // 211
+ 0, // 212
+ 0, // 213
+ 0, // 214
+ 0, // 215
+ 0, // 216
+ 0, // 217
+ 0, // 218
+ 0, // 219
+ 0, // 220
+ 0, // 221
+ 0, // 222
+ 0, // 223
+ 0, // 224
+ 0, // 225
+ 0, // 226
+ 0, // 227
+ 0, // 228
+ 0, // 229
+ 0, // 230
+ 0, // 231
+ 0, // 232
+ 0, // 233
+ 0, // 234
+ 0, // 235
+ 0, // 236
+ 0, // 237
+ 0, // 238
+ 0, // 239
+ 0, // 240
+ 0, // 241
+ 0, // 242
+ 0, // 243
+ 0, // 244
+ 0, // 245
+ 0, // 246
+ 0, // 247
+ 0, // 248
+ 0, // 249
+ 0, // 250
+ 0, // 251
+ 0, // 252
+ 0, // 253
+ 0, // 254
+ 0, // 255
+};
+
+// Alphabetical order for binary search
+extern const int kNameToLanguageSize = 304;
+extern const CharIntPair kNameToLanguage[kNameToLanguageSize] = {
+ {"ABKHAZIAN", 130}, // ab
+ {"AFAR", 131}, // aa
+ {"AFRIKAANS", 101}, // af
+ {"AKAN", 161}, // ak
+ {"ALBANIAN", 45}, // sq
+ {"AMHARIC", 73}, // am
+ {"ARABIC", 54}, // ar
+ {"ARMENIAN", 97}, // hy
+ {"ASSAMESE", 111}, // as
+ {"AYMARA", 132}, // ay
+ {"AZERBAIJANI", 74}, // az
+ {"BASHKIR", 133}, // ba
+ {"BASQUE", 57}, // eu
+ {"BELARUSIAN", 47}, // be
+ {"BENGALI", 37}, // bn
+ {"BIHARI", 51}, // bh
+ {"BISLAMA", 134}, // bi
+ {"BOSNIAN", 78}, // bs
+ {"BRETON", 89}, // br
+ {"BULGARIAN", 27}, // bg
+ {"BURMESE", 103}, // my
+ {"CATALAN", 55}, // ca
+ {"CEBUANO", 165}, // ceb
+ {"CHEROKEE", 107}, // chr
+ {"CHICHEWA", 174}, // ny
+ {"CORSICAN", 112}, // co
+ {"CROATIAN", 28}, // hr
+ {"CROATIAN", 28}, // sh-Latn
+ {"CZECH", 17}, // cs
+ {"Chinese", 16}, // zh-CN
+ {"Chinese", 16}, // zh-Hans
+ {"Chinese", 16}, // zh-Hani
+ {"Chinese", 16}, // zh
+ {"ChineseT", 69}, // zht
+ {"ChineseT", 69}, // zhT
+ {"ChineseT", 69}, // zh-SG
+ {"ChineseT", 69}, // zh-HK
+ {"ChineseT", 69}, // zh-TW
+ {"ChineseT", 69}, // zh-Hant
+ {"DANISH", 1}, // da
+ {"DHIVEHI", 106}, // dv
+ {"DUTCH", 2}, // nl
+ {"DZONGKHA", 135}, // dz
+ {"ENGLISH", 0}, // en
+ {"ESPERANTO", 56}, // eo
+ {"ESTONIAN", 24}, // et
+ {"EWE", 166}, // ee
+ {"FAROESE", 70}, // fo
+ {"FIJIAN", 136}, // fj
+ {"FINNISH", 3}, // fi
+ {"FRENCH", 4}, // fr
+ {"FRISIAN", 67}, // fy
+ {"GA", 167}, // gaa
+ {"GALICIAN", 31}, // gl
+ {"GANDA", 158}, // lg
+ {"GEORGIAN", 75}, // ka
+ {"GERMAN", 5}, // de
+ {"GREEK", 18}, // el
+ {"GREENLANDIC", 137}, // kl
+ {"GUARANI", 85}, // gn
+ {"GUJARATI", 52}, // gu
+ {"HAITIAN_CREOLE", 139}, // ht
+ {"HAUSA", 138}, // ha
+ {"HAWAIIAN", 164}, // haw
+ {"HEBREW", 6}, // he
+ {"HEBREW", 6}, // iw
+ {"HINDI", 35}, // hi
+ {"HMONG", 168}, // hmn
+ {"HUNGARIAN", 23}, // hu
+ {"ICELANDIC", 19}, // is
+ {"IGBO", 162}, // ig
+ {"INDONESIAN", 38}, // id
+ {"INTERLINGUA", 58}, // ia
+ {"INTERLINGUE", 113}, // ie
+ {"INUKTITUT", 141}, // iu
+ {"INUPIAK", 140}, // ik
+ {"IRISH", 30}, // ga
+ {"ITALIAN", 7}, // it
+ {"Ignore", 25}, // xxx
+ {"JAVANESE", 48}, // jv
+ {"JAVANESE", 48}, // jw
+ {"Japanese", 8}, // ja
+ {"KANNADA", 59}, // kn
+ {"KASHMIRI", 142}, // ks
+ {"KAZAKH", 114}, // kk
+ {"KHASI", 156}, // kha
+ {"KHMER", 104}, // km
+ {"KINYARWANDA", 143}, // rw
+ {"KRIO", 169}, // kri
+ {"KURDISH", 95}, // ku
+ {"KYRGYZ", 88}, // ky
+ {"Korean", 9}, // ko
+ {"LAOTHIAN", 98}, // lo
+ {"LATIN", 39}, // la
+ {"LATVIAN", 20}, // lv
+ {"LIMBU", 109}, // sit-Limb
+ {"LIMBU", 109}, // sit-NP
+ {"LIMBU", 109}, // lif
+ {"LINGALA", 115}, // ln
+ {"LITHUANIAN", 21}, // lt
+ {"LOZI", 170}, // loz
+ {"LUBA_LULUA", 171}, // lua
+ {"LUO_KENYA_AND_TANZANIA", 172}, // luo
+ {"LUXEMBOURGISH", 102}, // lb
+ {"MACEDONIAN", 36}, // mk
+ {"MALAGASY", 144}, // mg
+ {"MALAY", 40}, // ms
+ {"MALAYALAM", 41}, // ml
+ {"MALTESE", 65}, // mt
+ {"MANX", 159}, // gv
+ {"MAORI", 128}, // mi
+ {"MARATHI", 64}, // mr
+ {"MAURITIAN_CREOLE", 163}, // mfe
+ {"MOLDAVIAN", 22}, // mo
+ {"MONGOLIAN", 96}, // mn
+ {"MONTENEGRIN", 160}, // srm
+ {"MONTENEGRIN", 160}, // sr-Latn-ME
+ {"MONTENEGRIN", 160}, // sr-ME
+ {"MONTENEGRIN", 160}, // srM
+ {"NAURU", 145}, // na
+ {"NDEBELE", 506}, // nr
+ {"NEPALI", 43}, // ne
+ {"NEWARI", 173}, // new
+ {"NORWEGIAN", 10}, // nb
+ {"NORWEGIAN", 10}, // no
+ {"NORWEGIAN_N", 80}, // nn
+ {"NYANJA", 174}, // ny
+ {"OCCITAN", 49}, // oc
+ {"ORIYA", 110}, // or
+ {"OROMO", 146}, // om
+ {"OSSETIAN", 175}, // os
+ {"PAMPANGA", 176}, // pam
+ {"PASHTO", 117}, // ps
+ {"PEDI", 177}, // nso
+ {"PERSIAN", 77}, // fa
+ {"POLISH", 11}, // pl
+ {"PORTUGUESE", 12}, // pt
+ {"PUNJABI", 60}, // pa
+ {"QUECHUA", 118}, // qu
+ {"RAJASTHANI", 178}, // raj
+ {"RHAETO_ROMANCE", 100}, // rm
+ {"ROMANIAN", 22}, // ro
+ {"RUNDI", 147}, // rn
+ {"RUSSIAN", 13}, // ru
+ {"SAMOAN", 148}, // sm
+ {"SANGO", 149}, // sg
+ {"SANSKRIT", 150}, // sa
+ {"SCOTS", 157}, // sco
+ {"SCOTS_GAELIC", 61}, // gd
+ {"SERBIAN", 29}, // sh-Cyrl
+ {"SERBIAN", 29}, // sr
+ {"SESELWA", 179}, // crs
+ {"SESELWA_CREOLE_FRENCH", 179}, // crs
+ {"SESOTHO", 86}, // st
+ {"SHONA", 119}, // sn
+ {"SINDHI", 99}, // sd
+ {"SINHALESE", 79}, // si
+ {"SISWANT", 151}, // ss
+ {"SLOVAK", 68}, // sk
+ {"SLOVENIAN", 63}, // sl
+ {"SOMALI", 93}, // so
+ {"SPANISH", 14}, // es
+ {"SUNDANESE", 71}, // su
+ {"SWAHILI", 62}, // sw
+ {"SWEDISH", 15}, // sv
+ {"SYRIAC", 108}, // syr
+ {"TAGALOG", 32}, // tl
+ {"TAJIK", 120}, // tg
+ {"TAMIL", 46}, // ta
+ {"TATAR", 121}, // tt
+ {"TELUGU", 44}, // te
+ {"THAI", 53}, // th
+ {"TIBETAN", 105}, // bo
+ {"TIGRINYA", 76}, // ti
+ {"TONGA", 122}, // to
+ {"TSONGA", 152}, // ts
+ {"TSWANA", 153}, // tn
+ {"TUMBUKA", 180}, // tum
+ {"TURKISH", 33}, // tr
+ {"TURKMEN", 87}, // tk
+ {"TWI", 90}, // tw
+ {"UIGHUR", 94}, // ug
+ {"UKRAINIAN", 34}, // uk
+ {"URDU", 50}, // ur
+ {"UZBEK", 72}, // uz
+ {"Unknown", 26}, // un
+ {"VENDA", 181}, // ve
+ {"VIETNAMESE", 66}, // vi
+ {"VOLAPUK", 154}, // vo
+ {"WARAY_PHILIPPINES", 182}, // war
+ {"WELSH", 42}, // cy
+ {"WOLOF", 129}, // wo
+ {"XHOSA", 83}, // xh
+ {"X_Arabic", 518}, // xx-Arab
+ {"X_Armenian", 516}, // xx-Armn
+ {"X_Avestan", 591}, // xx-Avst
+ {"X_BORK_BORK_BORK", 507}, // zzb
+ {"X_Balinese", 573}, // xx-Bali
+ {"X_Bamum", 595}, // xx-Bamu
+ {"X_Batak", 604}, // xx-Batk
+ {"X_Bengali", 522}, // xx-Beng
+ {"X_Bopomofo", 546}, // xx-Bopo
+ {"X_Brahmi", 605}, // xx-Brah
+ {"X_Braille", 564}, // xx-Brai
+ {"X_Buginese", 565}, // xx-Bugi
+ {"X_Buhid", 555}, // xx-Buhd
+ {"X_Canadian_Aboriginal", 539}, // xx-Cans
+ {"X_Carian", 586}, // xx-Cari
+ {"X_Chakma", 607}, // xx-Cakm
+ {"X_Cham", 588}, // xx-Cham
+ {"X_Cherokee", 538}, // xx-Cher
+ {"X_Common", 512}, // xx-Zyyy
+ {"X_Coptic", 566}, // xx-Copt
+ {"X_Cuneiform", 574}, // xx-Xsux
+ {"X_Cypriot", 563}, // xx-Cprt
+ {"X_Cyrillic", 515}, // xx-Cyrl
+ {"X_Deseret", 551}, // xx-Dsrt
+ {"X_Devanagari", 521}, // xx-Deva
+ {"X_ELMER_FUDD", 511}, // zze
+ {"X_Egyptian_Hieroglyphs", 592}, // xx-Egyp
+ {"X_Ethiopic", 537}, // xx-Ethi
+ {"X_Georgian", 535}, // xx-Geor
+ {"X_Glagolitic", 568}, // xx-Glag
+ {"X_Gothic", 550}, // xx-Goth
+ {"X_Greek", 514}, // xx-Grek
+ {"X_Gujarati", 524}, // xx-Gujr
+ {"X_Gurmukhi", 523}, // xx-Guru
+ {"X_HACKER", 509}, // zzh
+ {"X_Han", 547}, // xx-Hani
+ {"X_Hangul", 536}, // xx-Hang
+ {"X_Hanunoo", 554}, // xx-Hano
+ {"X_Hebrew", 517}, // xx-Hebr
+ {"X_Hiragana", 544}, // xx-Hira
+ {"X_Imperial_Aramaic", 598}, // xx-Armi
+ {"X_Inherited", 552}, // xx-Qaai
+ {"X_Inscriptional_Pahlavi", 601}, // xx-Phli
+ {"X_Inscriptional_Parthian", 600}, // xx-Prti
+ {"X_Javanese", 596}, // xx-Java
+ {"X_KLINGON", 510}, // tlh
+ {"X_Kaithi", 603}, // xx-Kthi
+ {"X_Kannada", 528}, // xx-Knda
+ {"X_Katakana", 545}, // xx-Kana
+ {"X_Kayah_Li", 583}, // xx-Kali
+ {"X_Kharoshthi", 572}, // xx-Khar
+ {"X_Khmer", 542}, // xx-Khmr
+ {"X_Lao", 532}, // xx-Laoo
+ {"X_Latin", 513}, // xx-Latn
+ {"X_Lepcha", 579}, // xx-Lepc
+ {"X_Limbu", 557}, // xx-Limb
+ {"X_Linear_B", 559}, // xx-Linb
+ {"X_Lisu", 594}, // xx-Lisu
+ {"X_Lycian", 585}, // xx-Lyci
+ {"X_Lydian", 587}, // xx-Lydi
+ {"X_Malayalam", 529}, // xx-Mlym
+ {"X_Mandaic", 606}, // xx-Mand
+ {"X_Meetei_Mayek", 597}, // xx-Mtei
+ {"X_Meroitic_Cursive", 608}, // xx-Merc
+ {"X_Meroitic_Hieroglyphs", 609}, // xx-Mero
+ {"X_Miao", 610}, // xx-Plrd
+ {"X_Mongolian", 543}, // xx-Mong
+ {"X_Myanmar", 534}, // xx-Mymr
+ {"X_New_Tai_Lue", 567}, // xx-Talu
+ {"X_Nko", 577}, // xx-Nkoo
+ {"X_Ogham", 540}, // xx-Ogam
+ {"X_Ol_Chiki", 580}, // xx-Olck
+ {"X_Old_Italic", 549}, // xx-Ital
+ {"X_Old_Persian", 571}, // xx-Xpeo
+ {"X_Old_South_Arabian", 599}, // xx-Sarb
+ {"X_Old_Turkic", 602}, // xx-Orkh
+ {"X_Oriya", 525}, // xx-Orya
+ {"X_Osmanya", 562}, // xx-Osma
+ {"X_PIG_LATIN", 508}, // zzp
+ {"X_Phags_Pa", 576}, // xx-Phag
+ {"X_Phoenician", 575}, // xx-Phnx
+ {"X_Rejang", 584}, // xx-Rjng
+ {"X_Runic", 541}, // xx-Runr
+ {"X_Samaritan", 593}, // xx-Samr
+ {"X_Saurashtra", 582}, // xx-Saur
+ {"X_Sharada", 611}, // xx-Shrd
+ {"X_Shavian", 561}, // xx-Shaw
+ {"X_Sinhala", 530}, // xx-Sinh
+ {"X_Sora_Sompeng", 612}, // xx-Sora
+ {"X_Sundanese", 578}, // xx-Sund
+ {"X_Syloti_Nagri", 570}, // xx-Sylo
+ {"X_Syriac", 519}, // xx-Syrc
+ {"X_Tagalog", 553}, // xx-Tglg
+ {"X_Tagbanwa", 556}, // xx-Tagb
+ {"X_Tai_Le", 558}, // xx-Tale
+ {"X_Tai_Tham", 589}, // xx-Lana
+ {"X_Tai_Viet", 590}, // xx-Tavt
+ {"X_Takri", 613}, // xx-Takr
+ {"X_Tamil", 526}, // xx-Taml
+ {"X_Telugu", 527}, // xx-Telu
+ {"X_Thaana", 520}, // xx-Thaa
+ {"X_Thai", 531}, // xx-Thai
+ {"X_Tibetan", 533}, // xx-Tibt
+ {"X_Tifinagh", 569}, // xx-Tfng
+ {"X_Ugaritic", 560}, // xx-Ugar
+ {"X_Vai", 581}, // xx-Vaii
+ {"X_Yi", 548}, // xx-Yiii
+ {"YIDDISH", 91}, // yi
+ {"YORUBA", 123}, // yo
+ {"ZHUANG", 155}, // za
+ {"ZULU", 84}, // zu
+};
+
+// Alphabetical order for binary search
+extern const int kCodeToLanguageSize = 304;
+extern const CharIntPair kCodeToLanguage[kCodeToLanguageSize] = {
+ {"aa", 131}, // aa
+ {"ab", 130}, // ab
+ {"af", 101}, // af
+ {"ak", 161}, // ak
+ {"am", 73}, // am
+ {"ar", 54}, // ar
+ {"as", 111}, // as
+ {"ay", 132}, // ay
+ {"az", 74}, // az
+ {"ba", 133}, // ba
+ {"be", 47}, // be
+ {"bg", 27}, // bg
+ {"bh", 51}, // bh
+ {"bi", 134}, // bi
+ //{"hmn", 168}, // hmn used to be blu
+ {"bn", 37}, // bn
+ {"bo", 105}, // bo
+ {"br", 89}, // br
+ {"bs", 78}, // bs
+ {"ca", 55}, // ca
+ {"ceb", 165}, // ceb
+ {"chr", 107}, // chr
+ {"co", 112}, // co
+ {"crs", 179}, // crs
+ {"crs", 179}, // crs
+ {"cs", 17}, // cs
+ {"cy", 42}, // cy
+ {"da", 1}, // da
+ {"de", 5}, // de
+ {"dv", 106}, // dv
+ {"dz", 135}, // dz
+ {"ee", 166}, // ee
+ {"el", 18}, // el
+ {"en", 0}, // en
+ {"eo", 56}, // eo
+ {"es", 14}, // es
+ {"et", 24}, // et
+ {"eu", 57}, // eu
+ {"fa", 77}, // fa
+ {"fi", 3}, // fi
+ {"fj", 136}, // fj
+ {"fo", 70}, // fo
+ {"fr", 4}, // fr
+ {"fy", 67}, // fy
+ {"ga", 30}, // ga
+ {"gaa", 167}, // gaa
+ {"gd", 61}, // gd
+ {"gl", 31}, // gl
+ {"gn", 85}, // gn
+ {"gu", 52}, // gu
+ {"gv", 159}, // gv
+ {"ha", 138}, // ha
+ {"haw", 164}, // haw
+ {"he", 6}, // he
+ {"hi", 35}, // hi
+ {"hmn", 168}, // hmn used to be blu
+ {"hr", 28}, // hr
+ {"ht", 139}, // ht
+ {"hu", 23}, // hu
+ {"hy", 97}, // hy
+ {"ia", 58}, // ia
+ {"id", 38}, // id
+ {"ie", 113}, // ie
+ {"ig", 162}, // ig
+ {"ik", 140}, // ik
+ {"is", 19}, // is
+ {"it", 7}, // it
+ {"iu", 141}, // iu
+ {"iw", 6}, // iw
+ {"ja", 8}, // ja
+ {"jv", 48}, // jv
+ {"jw", 48}, // jw
+ {"ka", 75}, // ka
+ {"kha", 156}, // kha
+ {"kk", 114}, // kk
+ {"kl", 137}, // kl
+ {"km", 104}, // km
+ {"kn", 59}, // kn
+ {"ko", 9}, // ko
+ {"kri", 169}, // kri
+ {"ks", 142}, // ks
+ {"ku", 95}, // ku
+ {"ky", 88}, // ky
+ {"la", 39}, // la
+ {"lb", 102}, // lb
+ {"lg", 158}, // lg
+ {"lif", 109}, // lif
+ {"ln", 115}, // ln
+ {"lo", 98}, // lo
+ {"loz", 170}, // loz
+ {"lt", 21}, // lt
+ {"lua", 171}, // lua
+ {"luo", 172}, // luo
+ {"lv", 20}, // lv
+ {"mfe", 163}, // mfe
+ {"mg", 144}, // mg
+ {"mi", 128}, // mi
+ {"mk", 36}, // mk
+ {"ml", 41}, // ml
+ {"mn", 96}, // mn
+ {"mo", 22}, // mo
+ {"mr", 64}, // mr
+ {"ms", 40}, // ms
+ {"mt", 65}, // mt
+ {"my", 103}, // my
+ {"na", 145}, // na
+ {"nb", 10}, // nb
+ {"ne", 43}, // ne
+ {"new", 173}, // new
+ {"nl", 2}, // nl
+ {"nn", 80}, // nn
+ {"no", 10}, // no
+ {"nr", 506}, // nr
+ {"nso", 177}, // nso
+ {"ny", 174}, // ny
+ {"ny", 174}, // ny
+ {"oc", 49}, // oc
+ {"om", 146}, // om
+ {"or", 110}, // or
+ {"os", 175}, // os
+ {"pa", 60}, // pa
+ {"pam", 176}, // pam
+ {"pl", 11}, // pl
+ {"ps", 117}, // ps
+ {"pt", 12}, // pt
+ {"qu", 118}, // qu
+ {"raj", 178}, // raj
+ {"rm", 100}, // rm
+ {"rn", 147}, // rn
+ {"ro", 22}, // ro
+ {"ru", 13}, // ru
+ {"rw", 143}, // rw
+ {"sa", 150}, // sa
+ {"sco", 157}, // sco
+ {"sd", 99}, // sd
+ {"sg", 149}, // sg
+ {"sh-Cyrl", 29}, // sh-Cyrl
+ {"sh-Latn", 28}, // sh-Latn
+ {"si", 79}, // si
+ {"sit-Limb", 109}, // sit-Limb
+ {"sit-NP", 109}, // sit-NP
+ {"sk", 68}, // sk
+ {"sl", 63}, // sl
+ {"sm", 148}, // sm
+ {"sn", 119}, // sn
+ {"so", 93}, // so
+ {"sq", 45}, // sq
+ {"sr", 29}, // sr
+ {"sr-Latn-ME", 160}, // sr-Latn-ME
+ {"sr-ME", 160}, // sr-ME
+ {"srM", 160}, // srM
+ {"srm", 160}, // srm
+ {"ss", 151}, // ss
+ {"st", 86}, // st
+ {"su", 71}, // su
+ {"sv", 15}, // sv
+ {"sw", 62}, // sw
+ {"syr", 108}, // syr
+ {"ta", 46}, // ta
+ {"te", 44}, // te
+ {"tg", 120}, // tg
+ {"th", 53}, // th
+ {"ti", 76}, // ti
+ {"tk", 87}, // tk
+ {"tl", 32}, // tl
+ {"tlh", 510}, // tlh
+ {"tn", 153}, // tn
+ {"to", 122}, // to
+ {"tr", 33}, // tr
+ {"ts", 152}, // ts
+ {"tt", 121}, // tt
+ {"tum", 180}, // tum
+ {"tw", 90}, // tw
+ {"ug", 94}, // ug
+ {"uk", 34}, // uk
+ {"un", 26}, // un
+ {"ur", 50}, // ur
+ {"uz", 72}, // uz
+ {"ve", 181}, // ve
+ {"vi", 66}, // vi
+ {"vo", 154}, // vo
+ {"war", 182}, // war
+ {"wo", 129}, // wo
+ {"xh", 83}, // xh
+ {"xx-Arab", 518}, // xx-Arab
+ {"xx-Armi", 598}, // xx-Armi
+ {"xx-Armn", 516}, // xx-Armn
+ {"xx-Avst", 591}, // xx-Avst
+ {"xx-Bali", 573}, // xx-Bali
+ {"xx-Bamu", 595}, // xx-Bamu
+ {"xx-Batk", 604}, // xx-Batk
+ {"xx-Beng", 522}, // xx-Beng
+ {"xx-Bopo", 546}, // xx-Bopo
+ {"xx-Brah", 605}, // xx-Brah
+ {"xx-Brai", 564}, // xx-Brai
+ {"xx-Bugi", 565}, // xx-Bugi
+ {"xx-Buhd", 555}, // xx-Buhd
+ {"xx-Cakm", 607}, // xx-Cakm
+ {"xx-Cans", 539}, // xx-Cans
+ {"xx-Cari", 586}, // xx-Cari
+ {"xx-Cham", 588}, // xx-Cham
+ {"xx-Cher", 538}, // xx-Cher
+ {"xx-Copt", 566}, // xx-Copt
+ {"xx-Cprt", 563}, // xx-Cprt
+ {"xx-Cyrl", 515}, // xx-Cyrl
+ {"xx-Deva", 521}, // xx-Deva
+ {"xx-Dsrt", 551}, // xx-Dsrt
+ {"xx-Egyp", 592}, // xx-Egyp
+ {"xx-Ethi", 537}, // xx-Ethi
+ {"xx-Geor", 535}, // xx-Geor
+ {"xx-Glag", 568}, // xx-Glag
+ {"xx-Goth", 550}, // xx-Goth
+ {"xx-Grek", 514}, // xx-Grek
+ {"xx-Gujr", 524}, // xx-Gujr
+ {"xx-Guru", 523}, // xx-Guru
+ {"xx-Hang", 536}, // xx-Hang
+ {"xx-Hani", 547}, // xx-Hani
+ {"xx-Hano", 554}, // xx-Hano
+ {"xx-Hebr", 517}, // xx-Hebr
+ {"xx-Hira", 544}, // xx-Hira
+ {"xx-Ital", 549}, // xx-Ital
+ {"xx-Java", 596}, // xx-Java
+ {"xx-Kali", 583}, // xx-Kali
+ {"xx-Kana", 545}, // xx-Kana
+ {"xx-Khar", 572}, // xx-Khar
+ {"xx-Khmr", 542}, // xx-Khmr
+ {"xx-Knda", 528}, // xx-Knda
+ {"xx-Kthi", 603}, // xx-Kthi
+ {"xx-Lana", 589}, // xx-Lana
+ {"xx-Laoo", 532}, // xx-Laoo
+ {"xx-Latn", 513}, // xx-Latn
+ {"xx-Lepc", 579}, // xx-Lepc
+ {"xx-Limb", 557}, // xx-Limb
+ {"xx-Linb", 559}, // xx-Linb
+ {"xx-Lisu", 594}, // xx-Lisu
+ {"xx-Lyci", 585}, // xx-Lyci
+ {"xx-Lydi", 587}, // xx-Lydi
+ {"xx-Mand", 606}, // xx-Mand
+ {"xx-Merc", 608}, // xx-Merc
+ {"xx-Mero", 609}, // xx-Mero
+ {"xx-Mlym", 529}, // xx-Mlym
+ {"xx-Mong", 543}, // xx-Mong
+ {"xx-Mtei", 597}, // xx-Mtei
+ {"xx-Mymr", 534}, // xx-Mymr
+ {"xx-Nkoo", 577}, // xx-Nkoo
+ {"xx-Ogam", 540}, // xx-Ogam
+ {"xx-Olck", 580}, // xx-Olck
+ {"xx-Orkh", 602}, // xx-Orkh
+ {"xx-Orya", 525}, // xx-Orya
+ {"xx-Osma", 562}, // xx-Osma
+ {"xx-Phag", 576}, // xx-Phag
+ {"xx-Phli", 601}, // xx-Phli
+ {"xx-Phnx", 575}, // xx-Phnx
+ {"xx-Plrd", 610}, // xx-Plrd
+ {"xx-Prti", 600}, // xx-Prti
+ {"xx-Qaai", 552}, // xx-Qaai
+ {"xx-Rjng", 584}, // xx-Rjng
+ {"xx-Runr", 541}, // xx-Runr
+ {"xx-Samr", 593}, // xx-Samr
+ {"xx-Sarb", 599}, // xx-Sarb
+ {"xx-Saur", 582}, // xx-Saur
+ {"xx-Shaw", 561}, // xx-Shaw
+ {"xx-Shrd", 611}, // xx-Shrd
+ {"xx-Sinh", 530}, // xx-Sinh
+ {"xx-Sora", 612}, // xx-Sora
+ {"xx-Sund", 578}, // xx-Sund
+ {"xx-Sylo", 570}, // xx-Sylo
+ {"xx-Syrc", 519}, // xx-Syrc
+ {"xx-Tagb", 556}, // xx-Tagb
+ {"xx-Takr", 613}, // xx-Takr
+ {"xx-Tale", 558}, // xx-Tale
+ {"xx-Talu", 567}, // xx-Talu
+ {"xx-Taml", 526}, // xx-Taml
+ {"xx-Tavt", 590}, // xx-Tavt
+ {"xx-Telu", 527}, // xx-Telu
+ {"xx-Tfng", 569}, // xx-Tfng
+ {"xx-Tglg", 553}, // xx-Tglg
+ {"xx-Thaa", 520}, // xx-Thaa
+ {"xx-Thai", 531}, // xx-Thai
+ {"xx-Tibt", 533}, // xx-Tibt
+ {"xx-Ugar", 560}, // xx-Ugar
+ {"xx-Vaii", 581}, // xx-Vaii
+ {"xx-Xpeo", 571}, // xx-Xpeo
+ {"xx-Xsux", 574}, // xx-Xsux
+ {"xx-Yiii", 548}, // xx-Yiii
+ {"xx-Zyyy", 512}, // xx-Zyyy
+ {"xxx", 25}, // xxx
+ {"yi", 91}, // yi
+ {"yo", 123}, // yo
+ {"za", 155}, // za
+ {"zh", 16}, // zh
+ {"zh-CN", 16}, // zh-CN
+ {"zh-HK", 69}, // zh-HK
+ {"zh-Hani", 16}, // zh-Hani
+ {"zh-Hans", 16}, // zh-Hans
+ {"zh-Hant", 69}, // zh-Hant
+ {"zh-SG", 69}, // zh-SG
+ {"zh-TW", 69}, // zh-TW
+ {"zhT", 69}, // zhT
+ {"zht", 69}, // zht
+ {"zu", 84}, // zu
+ {"zzb", 507}, // zzb
+ {"zze", 511}, // zze
+ {"zzh", 509}, // zzh
+ {"zzp", 508}, // zzp
+};
+
+} // namespace CLD2
diff --git a/browser/components/translation/cld2/internal/generated_language.h b/browser/components/translation/cld2/internal/generated_language.h
new file mode 100644
index 000000000..4e9dbc441
--- /dev/null
+++ b/browser/components/translation/cld2/internal/generated_language.h
@@ -0,0 +1,651 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// generated_language.h
+// Machine generated. Do Not Edit.
+//
+// Declarations for languages recognized by CLD2
+//
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_GENERATED_LANGUAGE_H__
+#define I18N_ENCODINGS_CLD2_INTERNAL_GENERATED_LANGUAGE_H__
+
+#include "generated_ulscript.h"
+#include "integral_types.h"
+
+namespace CLD2 {
+
+typedef uint16 FourScripts[4];
+
+typedef enum {
+ ENGLISH = 0, // en
+ DANISH = 1, // da
+ DUTCH = 2, // nl
+ FINNISH = 3, // fi
+ FRENCH = 4, // fr
+ GERMAN = 5, // de
+ HEBREW = 6, // he
+ ITALIAN = 7, // it
+ JAPANESE = 8, // ja
+ KOREAN = 9, // ko
+ NORWEGIAN = 10, // no
+ POLISH = 11, // pl
+ PORTUGUESE = 12, // pt
+ RUSSIAN = 13, // ru
+ SPANISH = 14, // es
+ SWEDISH = 15, // sv
+ CHINESE = 16, // zh
+ CZECH = 17, // cs
+ GREEK = 18, // el
+ ICELANDIC = 19, // is
+ LATVIAN = 20, // lv
+ LITHUANIAN = 21, // lt
+ ROMANIAN = 22, // ro
+ HUNGARIAN = 23, // hu
+ ESTONIAN = 24, // et
+ TG_UNKNOWN_LANGUAGE = 25, // xxx
+ UNKNOWN_LANGUAGE = 26, // un
+ BULGARIAN = 27, // bg
+ CROATIAN = 28, // hr
+ SERBIAN = 29, // sr
+ IRISH = 30, // ga
+ GALICIAN = 31, // gl
+ TAGALOG = 32, // tl
+ TURKISH = 33, // tr
+ UKRAINIAN = 34, // uk
+ HINDI = 35, // hi
+ MACEDONIAN = 36, // mk
+ BENGALI = 37, // bn
+ INDONESIAN = 38, // id
+ LATIN = 39, // la
+ MALAY = 40, // ms
+ MALAYALAM = 41, // ml
+ WELSH = 42, // cy
+ NEPALI = 43, // ne
+ TELUGU = 44, // te
+ ALBANIAN = 45, // sq
+ TAMIL = 46, // ta
+ BELARUSIAN = 47, // be
+ JAVANESE = 48, // jw
+ OCCITAN = 49, // oc
+ URDU = 50, // ur
+ BIHARI = 51, // bh
+ GUJARATI = 52, // gu
+ THAI = 53, // th
+ ARABIC = 54, // ar
+ CATALAN = 55, // ca
+ ESPERANTO = 56, // eo
+ BASQUE = 57, // eu
+ INTERLINGUA = 58, // ia
+ KANNADA = 59, // kn
+ PUNJABI = 60, // pa
+ SCOTS_GAELIC = 61, // gd
+ SWAHILI = 62, // sw
+ SLOVENIAN = 63, // sl
+ MARATHI = 64, // mr
+ MALTESE = 65, // mt
+ VIETNAMESE = 66, // vi
+ FRISIAN = 67, // fy
+ SLOVAK = 68, // sk
+ CHINESE_T = 69, // zh-Hant
+ FAROESE = 70, // fo
+ SUNDANESE = 71, // su
+ UZBEK = 72, // uz
+ AMHARIC = 73, // am
+ AZERBAIJANI = 74, // az
+ GEORGIAN = 75, // ka
+ TIGRINYA = 76, // ti
+ PERSIAN = 77, // fa
+ BOSNIAN = 78, // bs
+ SINHALESE = 79, // si
+ NORWEGIAN_N = 80, // nn
+ X_81 = 81, //
+ X_82 = 82, //
+ XHOSA = 83, // xh
+ ZULU = 84, // zu
+ GUARANI = 85, // gn
+ SESOTHO = 86, // st
+ TURKMEN = 87, // tk
+ KYRGYZ = 88, // ky
+ BRETON = 89, // br
+ TWI = 90, // tw
+ YIDDISH = 91, // yi
+ X_92 = 92, //
+ SOMALI = 93, // so
+ UIGHUR = 94, // ug
+ KURDISH = 95, // ku
+ MONGOLIAN = 96, // mn
+ ARMENIAN = 97, // hy
+ LAOTHIAN = 98, // lo
+ SINDHI = 99, // sd
+ RHAETO_ROMANCE = 100, // rm
+ AFRIKAANS = 101, // af
+ LUXEMBOURGISH = 102, // lb
+ BURMESE = 103, // my
+ KHMER = 104, // km
+ TIBETAN = 105, // bo
+ DHIVEHI = 106, // dv
+ CHEROKEE = 107, // chr
+ SYRIAC = 108, // syr
+ LIMBU = 109, // lif
+ ORIYA = 110, // or
+ ASSAMESE = 111, // as
+ CORSICAN = 112, // co
+ INTERLINGUE = 113, // ie
+ KAZAKH = 114, // kk
+ LINGALA = 115, // ln
+ X_116 = 116, //
+ PASHTO = 117, // ps
+ QUECHUA = 118, // qu
+ SHONA = 119, // sn
+ TAJIK = 120, // tg
+ TATAR = 121, // tt
+ TONGA = 122, // to
+ YORUBA = 123, // yo
+ X_124 = 124, //
+ X_125 = 125, //
+ X_126 = 126, //
+ X_127 = 127, //
+ MAORI = 128, // mi
+ WOLOF = 129, // wo
+ ABKHAZIAN = 130, // ab
+ AFAR = 131, // aa
+ AYMARA = 132, // ay
+ BASHKIR = 133, // ba
+ BISLAMA = 134, // bi
+ DZONGKHA = 135, // dz
+ FIJIAN = 136, // fj
+ GREENLANDIC = 137, // kl
+ HAUSA = 138, // ha
+ HAITIAN_CREOLE = 139, // ht
+ INUPIAK = 140, // ik
+ INUKTITUT = 141, // iu
+ KASHMIRI = 142, // ks
+ KINYARWANDA = 143, // rw
+ MALAGASY = 144, // mg
+ NAURU = 145, // na
+ OROMO = 146, // om
+ RUNDI = 147, // rn
+ SAMOAN = 148, // sm
+ SANGO = 149, // sg
+ SANSKRIT = 150, // sa
+ SISWANT = 151, // ss
+ TSONGA = 152, // ts
+ TSWANA = 153, // tn
+ VOLAPUK = 154, // vo
+ ZHUANG = 155, // za
+ KHASI = 156, // kha
+ SCOTS = 157, // sco
+ GANDA = 158, // lg
+ MANX = 159, // gv
+ MONTENEGRIN = 160, // sr-ME
+ AKAN = 161, // ak
+ IGBO = 162, // ig
+ MAURITIAN_CREOLE = 163, // mfe
+ HAWAIIAN = 164, // haw
+ CEBUANO = 165, // ceb
+ EWE = 166, // ee
+ GA = 167, // gaa
+ HMONG = 168, // blu
+ KRIO = 169, // kri
+ LOZI = 170, // loz
+ LUBA_LULUA = 171, // lua
+ LUO_KENYA_AND_TANZANIA = 172, // luo
+ NEWARI = 173, // new
+ NYANJA = 174, // ny
+ OSSETIAN = 175, // os
+ PAMPANGA = 176, // pam
+ PEDI = 177, // nso
+ RAJASTHANI = 178, // raj
+ SESELWA = 179, // crs
+ TUMBUKA = 180, // tum
+ VENDA = 181, // ve
+ WARAY_PHILIPPINES = 182, // war
+ X_183 = 183, //
+ X_184 = 184, //
+ X_185 = 185, //
+ X_186 = 186, //
+ X_187 = 187, //
+ X_188 = 188, //
+ X_189 = 189, //
+ X_190 = 190, //
+ X_191 = 191, //
+ X_192 = 192, //
+ X_193 = 193, //
+ X_194 = 194, //
+ X_195 = 195, //
+ X_196 = 196, //
+ X_197 = 197, //
+ X_198 = 198, //
+ X_199 = 199, //
+ X_200 = 200, //
+ X_201 = 201, //
+ X_202 = 202, //
+ X_203 = 203, //
+ X_204 = 204, //
+ X_205 = 205, //
+ X_206 = 206, //
+ X_207 = 207, //
+ X_208 = 208, //
+ X_209 = 209, //
+ X_210 = 210, //
+ X_211 = 211, //
+ X_212 = 212, //
+ X_213 = 213, //
+ X_214 = 214, //
+ X_215 = 215, //
+ X_216 = 216, //
+ X_217 = 217, //
+ X_218 = 218, //
+ X_219 = 219, //
+ X_220 = 220, //
+ X_221 = 221, //
+ X_222 = 222, //
+ X_223 = 223, //
+ X_224 = 224, //
+ X_225 = 225, //
+ X_226 = 226, //
+ X_227 = 227, //
+ X_228 = 228, //
+ X_229 = 229, //
+ X_230 = 230, //
+ X_231 = 231, //
+ X_232 = 232, //
+ X_233 = 233, //
+ X_234 = 234, //
+ X_235 = 235, //
+ X_236 = 236, //
+ X_237 = 237, //
+ X_238 = 238, //
+ X_239 = 239, //
+ X_240 = 240, //
+ X_241 = 241, //
+ X_242 = 242, //
+ X_243 = 243, //
+ X_244 = 244, //
+ X_245 = 245, //
+ X_246 = 246, //
+ X_247 = 247, //
+ X_248 = 248, //
+ X_249 = 249, //
+ X_250 = 250, //
+ X_251 = 251, //
+ X_252 = 252, //
+ X_253 = 253, //
+ X_254 = 254, //
+ X_255 = 255, //
+ X_256 = 256, //
+ X_257 = 257, //
+ X_258 = 258, //
+ X_259 = 259, //
+ X_260 = 260, //
+ X_261 = 261, //
+ X_262 = 262, //
+ X_263 = 263, //
+ X_264 = 264, //
+ X_265 = 265, //
+ X_266 = 266, //
+ X_267 = 267, //
+ X_268 = 268, //
+ X_269 = 269, //
+ X_270 = 270, //
+ X_271 = 271, //
+ X_272 = 272, //
+ X_273 = 273, //
+ X_274 = 274, //
+ X_275 = 275, //
+ X_276 = 276, //
+ X_277 = 277, //
+ X_278 = 278, //
+ X_279 = 279, //
+ X_280 = 280, //
+ X_281 = 281, //
+ X_282 = 282, //
+ X_283 = 283, //
+ X_284 = 284, //
+ X_285 = 285, //
+ X_286 = 286, //
+ X_287 = 287, //
+ X_288 = 288, //
+ X_289 = 289, //
+ X_290 = 290, //
+ X_291 = 291, //
+ X_292 = 292, //
+ X_293 = 293, //
+ X_294 = 294, //
+ X_295 = 295, //
+ X_296 = 296, //
+ X_297 = 297, //
+ X_298 = 298, //
+ X_299 = 299, //
+ X_300 = 300, //
+ X_301 = 301, //
+ X_302 = 302, //
+ X_303 = 303, //
+ X_304 = 304, //
+ X_305 = 305, //
+ X_306 = 306, //
+ X_307 = 307, //
+ X_308 = 308, //
+ X_309 = 309, //
+ X_310 = 310, //
+ X_311 = 311, //
+ X_312 = 312, //
+ X_313 = 313, //
+ X_314 = 314, //
+ X_315 = 315, //
+ X_316 = 316, //
+ X_317 = 317, //
+ X_318 = 318, //
+ X_319 = 319, //
+ X_320 = 320, //
+ X_321 = 321, //
+ X_322 = 322, //
+ X_323 = 323, //
+ X_324 = 324, //
+ X_325 = 325, //
+ X_326 = 326, //
+ X_327 = 327, //
+ X_328 = 328, //
+ X_329 = 329, //
+ X_330 = 330, //
+ X_331 = 331, //
+ X_332 = 332, //
+ X_333 = 333, //
+ X_334 = 334, //
+ X_335 = 335, //
+ X_336 = 336, //
+ X_337 = 337, //
+ X_338 = 338, //
+ X_339 = 339, //
+ X_340 = 340, //
+ X_341 = 341, //
+ X_342 = 342, //
+ X_343 = 343, //
+ X_344 = 344, //
+ X_345 = 345, //
+ X_346 = 346, //
+ X_347 = 347, //
+ X_348 = 348, //
+ X_349 = 349, //
+ X_350 = 350, //
+ X_351 = 351, //
+ X_352 = 352, //
+ X_353 = 353, //
+ X_354 = 354, //
+ X_355 = 355, //
+ X_356 = 356, //
+ X_357 = 357, //
+ X_358 = 358, //
+ X_359 = 359, //
+ X_360 = 360, //
+ X_361 = 361, //
+ X_362 = 362, //
+ X_363 = 363, //
+ X_364 = 364, //
+ X_365 = 365, //
+ X_366 = 366, //
+ X_367 = 367, //
+ X_368 = 368, //
+ X_369 = 369, //
+ X_370 = 370, //
+ X_371 = 371, //
+ X_372 = 372, //
+ X_373 = 373, //
+ X_374 = 374, //
+ X_375 = 375, //
+ X_376 = 376, //
+ X_377 = 377, //
+ X_378 = 378, //
+ X_379 = 379, //
+ X_380 = 380, //
+ X_381 = 381, //
+ X_382 = 382, //
+ X_383 = 383, //
+ X_384 = 384, //
+ X_385 = 385, //
+ X_386 = 386, //
+ X_387 = 387, //
+ X_388 = 388, //
+ X_389 = 389, //
+ X_390 = 390, //
+ X_391 = 391, //
+ X_392 = 392, //
+ X_393 = 393, //
+ X_394 = 394, //
+ X_395 = 395, //
+ X_396 = 396, //
+ X_397 = 397, //
+ X_398 = 398, //
+ X_399 = 399, //
+ X_400 = 400, //
+ X_401 = 401, //
+ X_402 = 402, //
+ X_403 = 403, //
+ X_404 = 404, //
+ X_405 = 405, //
+ X_406 = 406, //
+ X_407 = 407, //
+ X_408 = 408, //
+ X_409 = 409, //
+ X_410 = 410, //
+ X_411 = 411, //
+ X_412 = 412, //
+ X_413 = 413, //
+ X_414 = 414, //
+ X_415 = 415, //
+ X_416 = 416, //
+ X_417 = 417, //
+ X_418 = 418, //
+ X_419 = 419, //
+ X_420 = 420, //
+ X_421 = 421, //
+ X_422 = 422, //
+ X_423 = 423, //
+ X_424 = 424, //
+ X_425 = 425, //
+ X_426 = 426, //
+ X_427 = 427, //
+ X_428 = 428, //
+ X_429 = 429, //
+ X_430 = 430, //
+ X_431 = 431, //
+ X_432 = 432, //
+ X_433 = 433, //
+ X_434 = 434, //
+ X_435 = 435, //
+ X_436 = 436, //
+ X_437 = 437, //
+ X_438 = 438, //
+ X_439 = 439, //
+ X_440 = 440, //
+ X_441 = 441, //
+ X_442 = 442, //
+ X_443 = 443, //
+ X_444 = 444, //
+ X_445 = 445, //
+ X_446 = 446, //
+ X_447 = 447, //
+ X_448 = 448, //
+ X_449 = 449, //
+ X_450 = 450, //
+ X_451 = 451, //
+ X_452 = 452, //
+ X_453 = 453, //
+ X_454 = 454, //
+ X_455 = 455, //
+ X_456 = 456, //
+ X_457 = 457, //
+ X_458 = 458, //
+ X_459 = 459, //
+ X_460 = 460, //
+ X_461 = 461, //
+ X_462 = 462, //
+ X_463 = 463, //
+ X_464 = 464, //
+ X_465 = 465, //
+ X_466 = 466, //
+ X_467 = 467, //
+ X_468 = 468, //
+ X_469 = 469, //
+ X_470 = 470, //
+ X_471 = 471, //
+ X_472 = 472, //
+ X_473 = 473, //
+ X_474 = 474, //
+ X_475 = 475, //
+ X_476 = 476, //
+ X_477 = 477, //
+ X_478 = 478, //
+ X_479 = 479, //
+ X_480 = 480, //
+ X_481 = 481, //
+ X_482 = 482, //
+ X_483 = 483, //
+ X_484 = 484, //
+ X_485 = 485, //
+ X_486 = 486, //
+ X_487 = 487, //
+ X_488 = 488, //
+ X_489 = 489, //
+ X_490 = 490, //
+ X_491 = 491, //
+ X_492 = 492, //
+ X_493 = 493, //
+ X_494 = 494, //
+ X_495 = 495, //
+ X_496 = 496, //
+ X_497 = 497, //
+ X_498 = 498, //
+ X_499 = 499, //
+ X_500 = 500, //
+ X_501 = 501, //
+ X_502 = 502, //
+ X_503 = 503, //
+ X_504 = 504, //
+ X_505 = 505, //
+ NDEBELE = 506, // nr
+ X_BORK_BORK_BORK = 507, // zzb
+ X_PIG_LATIN = 508, // zzp
+ X_HACKER = 509, // zzh
+ X_KLINGON = 510, // tlh
+ X_ELMER_FUDD = 511, // zze
+ X_Common = 512, // xx-Zyyy
+ X_Latin = 513, // xx-Latn
+ X_Greek = 514, // xx-Grek
+ X_Cyrillic = 515, // xx-Cyrl
+ X_Armenian = 516, // xx-Armn
+ X_Hebrew = 517, // xx-Hebr
+ X_Arabic = 518, // xx-Arab
+ X_Syriac = 519, // xx-Syrc
+ X_Thaana = 520, // xx-Thaa
+ X_Devanagari = 521, // xx-Deva
+ X_Bengali = 522, // xx-Beng
+ X_Gurmukhi = 523, // xx-Guru
+ X_Gujarati = 524, // xx-Gujr
+ X_Oriya = 525, // xx-Orya
+ X_Tamil = 526, // xx-Taml
+ X_Telugu = 527, // xx-Telu
+ X_Kannada = 528, // xx-Knda
+ X_Malayalam = 529, // xx-Mlym
+ X_Sinhala = 530, // xx-Sinh
+ X_Thai = 531, // xx-Thai
+ X_Lao = 532, // xx-Laoo
+ X_Tibetan = 533, // xx-Tibt
+ X_Myanmar = 534, // xx-Mymr
+ X_Georgian = 535, // xx-Geor
+ X_Hangul = 536, // xx-Hang
+ X_Ethiopic = 537, // xx-Ethi
+ X_Cherokee = 538, // xx-Cher
+ X_Canadian_Aboriginal = 539, // xx-Cans
+ X_Ogham = 540, // xx-Ogam
+ X_Runic = 541, // xx-Runr
+ X_Khmer = 542, // xx-Khmr
+ X_Mongolian = 543, // xx-Mong
+ X_Hiragana = 544, // xx-Hira
+ X_Katakana = 545, // xx-Kana
+ X_Bopomofo = 546, // xx-Bopo
+ X_Han = 547, // xx-Hani
+ X_Yi = 548, // xx-Yiii
+ X_Old_Italic = 549, // xx-Ital
+ X_Gothic = 550, // xx-Goth
+ X_Deseret = 551, // xx-Dsrt
+ X_Inherited = 552, // xx-Qaai
+ X_Tagalog = 553, // xx-Tglg
+ X_Hanunoo = 554, // xx-Hano
+ X_Buhid = 555, // xx-Buhd
+ X_Tagbanwa = 556, // xx-Tagb
+ X_Limbu = 557, // xx-Limb
+ X_Tai_Le = 558, // xx-Tale
+ X_Linear_B = 559, // xx-Linb
+ X_Ugaritic = 560, // xx-Ugar
+ X_Shavian = 561, // xx-Shaw
+ X_Osmanya = 562, // xx-Osma
+ X_Cypriot = 563, // xx-Cprt
+ X_Braille = 564, // xx-Brai
+ X_Buginese = 565, // xx-Bugi
+ X_Coptic = 566, // xx-Copt
+ X_New_Tai_Lue = 567, // xx-Talu
+ X_Glagolitic = 568, // xx-Glag
+ X_Tifinagh = 569, // xx-Tfng
+ X_Syloti_Nagri = 570, // xx-Sylo
+ X_Old_Persian = 571, // xx-Xpeo
+ X_Kharoshthi = 572, // xx-Khar
+ X_Balinese = 573, // xx-Bali
+ X_Cuneiform = 574, // xx-Xsux
+ X_Phoenician = 575, // xx-Phnx
+ X_Phags_Pa = 576, // xx-Phag
+ X_Nko = 577, // xx-Nkoo
+ X_Sundanese = 578, // xx-Sund
+ X_Lepcha = 579, // xx-Lepc
+ X_Ol_Chiki = 580, // xx-Olck
+ X_Vai = 581, // xx-Vaii
+ X_Saurashtra = 582, // xx-Saur
+ X_Kayah_Li = 583, // xx-Kali
+ X_Rejang = 584, // xx-Rjng
+ X_Lycian = 585, // xx-Lyci
+ X_Carian = 586, // xx-Cari
+ X_Lydian = 587, // xx-Lydi
+ X_Cham = 588, // xx-Cham
+ X_Tai_Tham = 589, // xx-Lana
+ X_Tai_Viet = 590, // xx-Tavt
+ X_Avestan = 591, // xx-Avst
+ X_Egyptian_Hieroglyphs = 592, // xx-Egyp
+ X_Samaritan = 593, // xx-Samr
+ X_Lisu = 594, // xx-Lisu
+ X_Bamum = 595, // xx-Bamu
+ X_Javanese = 596, // xx-Java
+ X_Meetei_Mayek = 597, // xx-Mtei
+ X_Imperial_Aramaic = 598, // xx-Armi
+ X_Old_South_Arabian = 599, // xx-Sarb
+ X_Inscriptional_Parthian = 600, // xx-Prti
+ X_Inscriptional_Pahlavi = 601, // xx-Phli
+ X_Old_Turkic = 602, // xx-Orkh
+ X_Kaithi = 603, // xx-Kthi
+ X_Batak = 604, // xx-Batk
+ X_Brahmi = 605, // xx-Brah
+ X_Mandaic = 606, // xx-Mand
+ X_Chakma = 607, // xx-Cakm
+ X_Meroitic_Cursive = 608, // xx-Merc
+ X_Meroitic_Hieroglyphs = 609, // xx-Mero
+ X_Miao = 610, // xx-Plrd
+ X_Sharada = 611, // xx-Shrd
+ X_Sora_Sompeng = 612, // xx-Sora
+ X_Takri = 613, // xx-Takr
+ NUM_LANGUAGES
+} Language;
+
+} // namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_GENERATED_LANGUAGE_H__
diff --git a/browser/components/translation/cld2/internal/generated_ulscript.cc b/browser/components/translation/cld2/internal/generated_ulscript.cc
new file mode 100644
index 000000000..f15941d4e
--- /dev/null
+++ b/browser/components/translation/cld2/internal/generated_ulscript.cc
@@ -0,0 +1,781 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// generated_ulscript.cc
+// Machine generated. Do Not Edit.
+//
+// Declarations for scripts recognized by CLD2
+//
+
+#include "generated_ulscript.h"
+#include "generated_language.h"
+
+namespace CLD2 {
+
+// Subscripted by enum ULScript
+extern const int kULScriptToNameSize = 102;
+extern const char* const kULScriptToName[kULScriptToNameSize] = {
+ "Common", // 0 Zyyy
+ "Latin", // 1 Latn
+ "Greek", // 2 Grek
+ "Cyrillic", // 3 Cyrl
+ "Armenian", // 4 Armn
+ "Hebrew", // 5 Hebr
+ "Arabic", // 6 Arab
+ "Syriac", // 7 Syrc
+ "Thaana", // 8 Thaa
+ "Devanagari", // 9 Deva
+ "Bengali", // 10 Beng
+ "Gurmukhi", // 11 Guru
+ "Gujarati", // 12 Gujr
+ "Oriya", // 13 Orya
+ "Tamil", // 14 Taml
+ "Telugu", // 15 Telu
+ "Kannada", // 16 Knda
+ "Malayalam", // 17 Mlym
+ "Sinhala", // 18 Sinh
+ "Thai", // 19 Thai
+ "Lao", // 20 Laoo
+ "Tibetan", // 21 Tibt
+ "Myanmar", // 22 Mymr
+ "Georgian", // 23 Geor
+ "Hani", // 24 Hani
+ "Ethiopic", // 25 Ethi
+ "Cherokee", // 26 Cher
+ "Canadian_Aboriginal", // 27 Cans
+ "Ogham", // 28 Ogam
+ "Runic", // 29 Runr
+ "Khmer", // 30 Khmr
+ "Mongolian", // 31 Mong
+ "", // 32
+ "", // 33
+ "Bopomofo", // 34 Bopo
+ "", // 35
+ "Yi", // 36 Yiii
+ "Old_Italic", // 37 Ital
+ "Gothic", // 38 Goth
+ "Deseret", // 39 Dsrt
+ "Inherited", // 40 Zinh
+ "Tagalog", // 41 Tglg
+ "Hanunoo", // 42 Hano
+ "Buhid", // 43 Buhd
+ "Tagbanwa", // 44 Tagb
+ "Limbu", // 45 Limb
+ "Tai_Le", // 46 Tale
+ "Linear_B", // 47 Linb
+ "Ugaritic", // 48 Ugar
+ "Shavian", // 49 Shaw
+ "Osmanya", // 50 Osma
+ "Cypriot", // 51 Cprt
+ "Braille", // 52 Brai
+ "Buginese", // 53 Bugi
+ "Coptic", // 54 Copt
+ "New_Tai_Lue", // 55 Talu
+ "Glagolitic", // 56 Glag
+ "Tifinagh", // 57 Tfng
+ "Syloti_Nagri", // 58 Sylo
+ "Old_Persian", // 59 Xpeo
+ "Kharoshthi", // 60 Khar
+ "Balinese", // 61 Bali
+ "Cuneiform", // 62 Xsux
+ "Phoenician", // 63 Phnx
+ "Phags_Pa", // 64 Phag
+ "Nko", // 65 Nkoo
+ "Sundanese", // 66 Sund
+ "Lepcha", // 67 Lepc
+ "Ol_Chiki", // 68 Olck
+ "Vai", // 69 Vaii
+ "Saurashtra", // 70 Saur
+ "Kayah_Li", // 71 Kali
+ "Rejang", // 72 Rjng
+ "Lycian", // 73 Lyci
+ "Carian", // 74 Cari
+ "Lydian", // 75 Lydi
+ "Cham", // 76 Cham
+ "Tai_Tham", // 77 Lana
+ "Tai_Viet", // 78 Tavt
+ "Avestan", // 79 Avst
+ "Egyptian_Hieroglyphs", // 80 Egyp
+ "Samaritan", // 81 Samr
+ "Lisu", // 82 Lisu
+ "Bamum", // 83 Bamu
+ "Javanese", // 84 Java
+ "Meetei_Mayek", // 85 Mtei
+ "Imperial_Aramaic", // 86 Armi
+ "Old_South_Arabian", // 87 Sarb
+ "Inscriptional_Parthian", // 88 Prti
+ "Inscriptional_Pahlavi", // 89 Phli
+ "Old_Turkic", // 90 Orkh
+ "Kaithi", // 91 Kthi
+ "Batak", // 92 Batk
+ "Brahmi", // 93 Brah
+ "Mandaic", // 94 Mand
+ "Chakma", // 95 Cakm
+ "Meroitic_Cursive", // 96 Merc
+ "Meroitic_Hieroglyphs", // 97 Mero
+ "Miao", // 98 Plrd
+ "Sharada", // 99 Shrd
+ "Sora_Sompeng", // 100 Sora
+ "Takri", // 101 Takr
+};
+
+// Subscripted by enum ULScript
+extern const int kULScriptToCodeSize = 102;
+extern const char* const kULScriptToCode[kULScriptToCodeSize] = {
+ "Zyyy", // 0 Common
+ "Latn", // 1 Latin
+ "Grek", // 2 Greek
+ "Cyrl", // 3 Cyrillic
+ "Armn", // 4 Armenian
+ "Hebr", // 5 Hebrew
+ "Arab", // 6 Arabic
+ "Syrc", // 7 Syriac
+ "Thaa", // 8 Thaana
+ "Deva", // 9 Devanagari
+ "Beng", // 10 Bengali
+ "Guru", // 11 Gurmukhi
+ "Gujr", // 12 Gujarati
+ "Orya", // 13 Oriya
+ "Taml", // 14 Tamil
+ "Telu", // 15 Telugu
+ "Knda", // 16 Kannada
+ "Mlym", // 17 Malayalam
+ "Sinh", // 18 Sinhala
+ "Thai", // 19 Thai
+ "Laoo", // 20 Lao
+ "Tibt", // 21 Tibetan
+ "Mymr", // 22 Myanmar
+ "Geor", // 23 Georgian
+ "Hani", // 24 Hani
+ "Ethi", // 25 Ethiopic
+ "Cher", // 26 Cherokee
+ "Cans", // 27 Canadian_Aboriginal
+ "Ogam", // 28 Ogham
+ "Runr", // 29 Runic
+ "Khmr", // 30 Khmer
+ "Mong", // 31 Mongolian
+ "", // 32
+ "", // 33
+ "Bopo", // 34 Bopomofo
+ "", // 35
+ "Yiii", // 36 Yi
+ "Ital", // 37 Old_Italic
+ "Goth", // 38 Gothic
+ "Dsrt", // 39 Deseret
+ "Zinh", // 40 Inherited
+ "Tglg", // 41 Tagalog
+ "Hano", // 42 Hanunoo
+ "Buhd", // 43 Buhid
+ "Tagb", // 44 Tagbanwa
+ "Limb", // 45 Limbu
+ "Tale", // 46 Tai_Le
+ "Linb", // 47 Linear_B
+ "Ugar", // 48 Ugaritic
+ "Shaw", // 49 Shavian
+ "Osma", // 50 Osmanya
+ "Cprt", // 51 Cypriot
+ "Brai", // 52 Braille
+ "Bugi", // 53 Buginese
+ "Copt", // 54 Coptic
+ "Talu", // 55 New_Tai_Lue
+ "Glag", // 56 Glagolitic
+ "Tfng", // 57 Tifinagh
+ "Sylo", // 58 Syloti_Nagri
+ "Xpeo", // 59 Old_Persian
+ "Khar", // 60 Kharoshthi
+ "Bali", // 61 Balinese
+ "Xsux", // 62 Cuneiform
+ "Phnx", // 63 Phoenician
+ "Phag", // 64 Phags_Pa
+ "Nkoo", // 65 Nko
+ "Sund", // 66 Sundanese
+ "Lepc", // 67 Lepcha
+ "Olck", // 68 Ol_Chiki
+ "Vaii", // 69 Vai
+ "Saur", // 70 Saurashtra
+ "Kali", // 71 Kayah_Li
+ "Rjng", // 72 Rejang
+ "Lyci", // 73 Lycian
+ "Cari", // 74 Carian
+ "Lydi", // 75 Lydian
+ "Cham", // 76 Cham
+ "Lana", // 77 Tai_Tham
+ "Tavt", // 78 Tai_Viet
+ "Avst", // 79 Avestan
+ "Egyp", // 80 Egyptian_Hieroglyphs
+ "Samr", // 81 Samaritan
+ "Lisu", // 82 Lisu
+ "Bamu", // 83 Bamum
+ "Java", // 84 Javanese
+ "Mtei", // 85 Meetei_Mayek
+ "Armi", // 86 Imperial_Aramaic
+ "Sarb", // 87 Old_South_Arabian
+ "Prti", // 88 Inscriptional_Parthian
+ "Phli", // 89 Inscriptional_Pahlavi
+ "Orkh", // 90 Old_Turkic
+ "Kthi", // 91 Kaithi
+ "Batk", // 92 Batak
+ "Brah", // 93 Brahmi
+ "Mand", // 94 Mandaic
+ "Cakm", // 95 Chakma
+ "Merc", // 96 Meroitic_Cursive
+ "Mero", // 97 Meroitic_Hieroglyphs
+ "Plrd", // 98 Miao
+ "Shrd", // 99 Sharada
+ "Sora", // 100 Sora_Sompeng
+ "Takr", // 101 Takri
+};
+
+// Subscripted by enum ULScript
+extern const int kULScriptToCNameSize = 102;
+extern const char* const kULScriptToCName[kULScriptToCNameSize] = {
+ "ULScript_Common", // 0 Zyyy
+ "ULScript_Latin", // 1 Latn
+ "ULScript_Greek", // 2 Grek
+ "ULScript_Cyrillic", // 3 Cyrl
+ "ULScript_Armenian", // 4 Armn
+ "ULScript_Hebrew", // 5 Hebr
+ "ULScript_Arabic", // 6 Arab
+ "ULScript_Syriac", // 7 Syrc
+ "ULScript_Thaana", // 8 Thaa
+ "ULScript_Devanagari", // 9 Deva
+ "ULScript_Bengali", // 10 Beng
+ "ULScript_Gurmukhi", // 11 Guru
+ "ULScript_Gujarati", // 12 Gujr
+ "ULScript_Oriya", // 13 Orya
+ "ULScript_Tamil", // 14 Taml
+ "ULScript_Telugu", // 15 Telu
+ "ULScript_Kannada", // 16 Knda
+ "ULScript_Malayalam", // 17 Mlym
+ "ULScript_Sinhala", // 18 Sinh
+ "ULScript_Thai", // 19 Thai
+ "ULScript_Lao", // 20 Laoo
+ "ULScript_Tibetan", // 21 Tibt
+ "ULScript_Myanmar", // 22 Mymr
+ "ULScript_Georgian", // 23 Geor
+ "ULScript_Hani", // 24 Hani
+ "ULScript_Ethiopic", // 25 Ethi
+ "ULScript_Cherokee", // 26 Cher
+ "ULScript_Canadian_Aboriginal", // 27 Cans
+ "ULScript_Ogham", // 28 Ogam
+ "ULScript_Runic", // 29 Runr
+ "ULScript_Khmer", // 30 Khmr
+ "ULScript_Mongolian", // 31 Mong
+ "ULScript_32", // 32
+ "ULScript_33", // 33
+ "ULScript_Bopomofo", // 34 Bopo
+ "ULScript_35", // 35
+ "ULScript_Yi", // 36 Yiii
+ "ULScript_Old_Italic", // 37 Ital
+ "ULScript_Gothic", // 38 Goth
+ "ULScript_Deseret", // 39 Dsrt
+ "ULScript_Inherited", // 40 Zinh
+ "ULScript_Tagalog", // 41 Tglg
+ "ULScript_Hanunoo", // 42 Hano
+ "ULScript_Buhid", // 43 Buhd
+ "ULScript_Tagbanwa", // 44 Tagb
+ "ULScript_Limbu", // 45 Limb
+ "ULScript_Tai_Le", // 46 Tale
+ "ULScript_Linear_B", // 47 Linb
+ "ULScript_Ugaritic", // 48 Ugar
+ "ULScript_Shavian", // 49 Shaw
+ "ULScript_Osmanya", // 50 Osma
+ "ULScript_Cypriot", // 51 Cprt
+ "ULScript_Braille", // 52 Brai
+ "ULScript_Buginese", // 53 Bugi
+ "ULScript_Coptic", // 54 Copt
+ "ULScript_New_Tai_Lue", // 55 Talu
+ "ULScript_Glagolitic", // 56 Glag
+ "ULScript_Tifinagh", // 57 Tfng
+ "ULScript_Syloti_Nagri", // 58 Sylo
+ "ULScript_Old_Persian", // 59 Xpeo
+ "ULScript_Kharoshthi", // 60 Khar
+ "ULScript_Balinese", // 61 Bali
+ "ULScript_Cuneiform", // 62 Xsux
+ "ULScript_Phoenician", // 63 Phnx
+ "ULScript_Phags_Pa", // 64 Phag
+ "ULScript_Nko", // 65 Nkoo
+ "ULScript_Sundanese", // 66 Sund
+ "ULScript_Lepcha", // 67 Lepc
+ "ULScript_Ol_Chiki", // 68 Olck
+ "ULScript_Vai", // 69 Vaii
+ "ULScript_Saurashtra", // 70 Saur
+ "ULScript_Kayah_Li", // 71 Kali
+ "ULScript_Rejang", // 72 Rjng
+ "ULScript_Lycian", // 73 Lyci
+ "ULScript_Carian", // 74 Cari
+ "ULScript_Lydian", // 75 Lydi
+ "ULScript_Cham", // 76 Cham
+ "ULScript_Tai_Tham", // 77 Lana
+ "ULScript_Tai_Viet", // 78 Tavt
+ "ULScript_Avestan", // 79 Avst
+ "ULScript_Egyptian_Hieroglyphs", // 80 Egyp
+ "ULScript_Samaritan", // 81 Samr
+ "ULScript_Lisu", // 82 Lisu
+ "ULScript_Bamum", // 83 Bamu
+ "ULScript_Javanese", // 84 Java
+ "ULScript_Meetei_Mayek", // 85 Mtei
+ "ULScript_Imperial_Aramaic", // 86 Armi
+ "ULScript_Old_South_Arabian", // 87 Sarb
+ "ULScript_Inscriptional_Parthian", // 88 Prti
+ "ULScript_Inscriptional_Pahlavi", // 89 Phli
+ "ULScript_Old_Turkic", // 90 Orkh
+ "ULScript_Kaithi", // 91 Kthi
+ "ULScript_Batak", // 92 Batk
+ "ULScript_Brahmi", // 93 Brah
+ "ULScript_Mandaic", // 94 Mand
+ "ULScript_Chakma", // 95 Cakm
+ "ULScript_Meroitic_Cursive", // 96 Merc
+ "ULScript_Meroitic_Hieroglyphs", // 97 Mero
+ "ULScript_Miao", // 98 Plrd
+ "ULScript_Sharada", // 99 Shrd
+ "ULScript_Sora_Sompeng", // 100 Sora
+ "ULScript_Takri", // 101 Takr
+};
+
+// Subscripted by enum ULScript
+extern const int kULScriptToRtypeSize = 102;
+extern const ULScriptRType kULScriptToRtype[kULScriptToRtypeSize] = {
+ RTypeNone, // 0 Zyyy
+ RTypeMany, // 1 Latn
+ RTypeOne, // 2 Grek
+ RTypeMany, // 3 Cyrl
+ RTypeOne, // 4 Armn
+ RTypeMany, // 5 Hebr
+ RTypeMany, // 6 Arab
+ RTypeOne, // 7 Syrc
+ RTypeOne, // 8 Thaa
+ RTypeMany, // 9 Deva
+ RTypeMany, // 10 Beng
+ RTypeOne, // 11 Guru
+ RTypeOne, // 12 Gujr
+ RTypeOne, // 13 Orya
+ RTypeOne, // 14 Taml
+ RTypeOne, // 15 Telu
+ RTypeOne, // 16 Knda
+ RTypeOne, // 17 Mlym
+ RTypeOne, // 18 Sinh
+ RTypeOne, // 19 Thai
+ RTypeOne, // 20 Laoo
+ RTypeMany, // 21 Tibt
+ RTypeOne, // 22 Mymr
+ RTypeOne, // 23 Geor
+ RTypeCJK, // 24 Hani
+ RTypeMany, // 25 Ethi
+ RTypeOne, // 26 Cher
+ RTypeOne, // 27 Cans
+ RTypeNone, // 28 Ogam
+ RTypeNone, // 29 Runr
+ RTypeOne, // 30 Khmr
+ RTypeOne, // 31 Mong
+ RTypeNone, // 32
+ RTypeNone, // 33
+ RTypeNone, // 34 Bopo
+ RTypeNone, // 35
+ RTypeNone, // 36 Yiii
+ RTypeNone, // 37 Ital
+ RTypeNone, // 38 Goth
+ RTypeNone, // 39 Dsrt
+ RTypeNone, // 40 Zinh
+ RTypeOne, // 41 Tglg
+ RTypeNone, // 42 Hano
+ RTypeNone, // 43 Buhd
+ RTypeNone, // 44 Tagb
+ RTypeOne, // 45 Limb
+ RTypeNone, // 46 Tale
+ RTypeNone, // 47 Linb
+ RTypeNone, // 48 Ugar
+ RTypeNone, // 49 Shaw
+ RTypeNone, // 50 Osma
+ RTypeNone, // 51 Cprt
+ RTypeNone, // 52 Brai
+ RTypeNone, // 53 Bugi
+ RTypeNone, // 54 Copt
+ RTypeNone, // 55 Talu
+ RTypeNone, // 56 Glag
+ RTypeNone, // 57 Tfng
+ RTypeNone, // 58 Sylo
+ RTypeNone, // 59 Xpeo
+ RTypeNone, // 60 Khar
+ RTypeNone, // 61 Bali
+ RTypeNone, // 62 Xsux
+ RTypeNone, // 63 Phnx
+ RTypeNone, // 64 Phag
+ RTypeNone, // 65 Nkoo
+ RTypeNone, // 66 Sund
+ RTypeNone, // 67 Lepc
+ RTypeNone, // 68 Olck
+ RTypeNone, // 69 Vaii
+ RTypeNone, // 70 Saur
+ RTypeNone, // 71 Kali
+ RTypeNone, // 72 Rjng
+ RTypeNone, // 73 Lyci
+ RTypeNone, // 74 Cari
+ RTypeNone, // 75 Lydi
+ RTypeNone, // 76 Cham
+ RTypeNone, // 77 Lana
+ RTypeNone, // 78 Tavt
+ RTypeNone, // 79 Avst
+ RTypeNone, // 80 Egyp
+ RTypeNone, // 81 Samr
+ RTypeNone, // 82 Lisu
+ RTypeNone, // 83 Bamu
+ RTypeNone, // 84 Java
+ RTypeNone, // 85 Mtei
+ RTypeNone, // 86 Armi
+ RTypeNone, // 87 Sarb
+ RTypeNone, // 88 Prti
+ RTypeNone, // 89 Phli
+ RTypeNone, // 90 Orkh
+ RTypeNone, // 91 Kthi
+ RTypeNone, // 92 Batk
+ RTypeNone, // 93 Brah
+ RTypeNone, // 94 Mand
+ RTypeNone, // 95 Cakm
+ RTypeNone, // 96 Merc
+ RTypeNone, // 97 Mero
+ RTypeNone, // 98 Plrd
+ RTypeNone, // 99 Shrd
+ RTypeNone, // 100 Sora
+ RTypeNone, // 101 Takr
+};
+
+// Subscripted by enum ULScript
+extern const int kULScriptToDefaultLangSize = 102;
+extern const Language kULScriptToDefaultLang[kULScriptToDefaultLangSize] = {
+ X_Common, // 0 Zyyy RTypeNone
+ ENGLISH, // 1 Latn RTypeMany
+ GREEK, // 2 Grek RTypeOne
+ RUSSIAN, // 3 Cyrl RTypeMany
+ ARMENIAN, // 4 Armn RTypeOne
+ HEBREW, // 5 Hebr RTypeMany
+ ARABIC, // 6 Arab RTypeMany
+ SYRIAC, // 7 Syrc RTypeOne
+ DHIVEHI, // 8 Thaa RTypeOne
+ HINDI, // 9 Deva RTypeMany
+ BENGALI, // 10 Beng RTypeMany
+ PUNJABI, // 11 Guru RTypeOne
+ GUJARATI, // 12 Gujr RTypeOne
+ ORIYA, // 13 Orya RTypeOne
+ TAMIL, // 14 Taml RTypeOne
+ TELUGU, // 15 Telu RTypeOne
+ KANNADA, // 16 Knda RTypeOne
+ MALAYALAM, // 17 Mlym RTypeOne
+ SINHALESE, // 18 Sinh RTypeOne
+ THAI, // 19 Thai RTypeOne
+ LAOTHIAN, // 20 Laoo RTypeOne
+ TIBETAN, // 21 Tibt RTypeMany
+ BURMESE, // 22 Mymr RTypeOne
+ GEORGIAN, // 23 Geor RTypeOne
+ JAPANESE, // 24 Hani RTypeCJK
+ AMHARIC, // 25 Ethi RTypeMany
+ CHEROKEE, // 26 Cher RTypeOne
+ INUKTITUT, // 27 Cans RTypeOne
+ X_Ogham, // 28 Ogam RTypeNone
+ X_Runic, // 29 Runr RTypeNone
+ KHMER, // 30 Khmr RTypeOne
+ MONGOLIAN, // 31 Mong RTypeOne
+ UNKNOWN_LANGUAGE, // 32 RTypeNone
+ UNKNOWN_LANGUAGE, // 33 RTypeNone
+ X_Bopomofo, // 34 Bopo RTypeNone
+ UNKNOWN_LANGUAGE, // 35 RTypeNone
+ X_Yi, // 36 Yiii RTypeNone
+ X_Old_Italic, // 37 Ital RTypeNone
+ X_Gothic, // 38 Goth RTypeNone
+ X_Deseret, // 39 Dsrt RTypeNone
+ X_Inherited, // 40 Zinh RTypeNone
+ TAGALOG, // 41 Tglg RTypeOne
+ X_Hanunoo, // 42 Hano RTypeNone
+ X_Buhid, // 43 Buhd RTypeNone
+ X_Tagbanwa, // 44 Tagb RTypeNone
+ LIMBU, // 45 Limb RTypeOne
+ X_Tai_Le, // 46 Tale RTypeNone
+ X_Linear_B, // 47 Linb RTypeNone
+ X_Ugaritic, // 48 Ugar RTypeNone
+ X_Shavian, // 49 Shaw RTypeNone
+ X_Osmanya, // 50 Osma RTypeNone
+ X_Cypriot, // 51 Cprt RTypeNone
+ X_Braille, // 52 Brai RTypeNone
+ X_Buginese, // 53 Bugi RTypeNone
+ X_Coptic, // 54 Copt RTypeNone
+ X_New_Tai_Lue, // 55 Talu RTypeNone
+ X_Glagolitic, // 56 Glag RTypeNone
+ X_Tifinagh, // 57 Tfng RTypeNone
+ X_Syloti_Nagri, // 58 Sylo RTypeNone
+ X_Old_Persian, // 59 Xpeo RTypeNone
+ X_Kharoshthi, // 60 Khar RTypeNone
+ X_Balinese, // 61 Bali RTypeNone
+ X_Cuneiform, // 62 Xsux RTypeNone
+ X_Phoenician, // 63 Phnx RTypeNone
+ X_Phags_Pa, // 64 Phag RTypeNone
+ X_Nko, // 65 Nkoo RTypeNone
+ X_Sundanese, // 66 Sund RTypeNone
+ X_Lepcha, // 67 Lepc RTypeNone
+ X_Ol_Chiki, // 68 Olck RTypeNone
+ X_Vai, // 69 Vaii RTypeNone
+ X_Saurashtra, // 70 Saur RTypeNone
+ X_Kayah_Li, // 71 Kali RTypeNone
+ X_Rejang, // 72 Rjng RTypeNone
+ X_Lycian, // 73 Lyci RTypeNone
+ X_Carian, // 74 Cari RTypeNone
+ X_Lydian, // 75 Lydi RTypeNone
+ X_Cham, // 76 Cham RTypeNone
+ X_Tai_Tham, // 77 Lana RTypeNone
+ X_Tai_Viet, // 78 Tavt RTypeNone
+ X_Avestan, // 79 Avst RTypeNone
+ X_Egyptian_Hieroglyphs, // 80 Egyp RTypeNone
+ X_Samaritan, // 81 Samr RTypeNone
+ X_Lisu, // 82 Lisu RTypeNone
+ X_Bamum, // 83 Bamu RTypeNone
+ X_Javanese, // 84 Java RTypeNone
+ X_Meetei_Mayek, // 85 Mtei RTypeNone
+ X_Imperial_Aramaic, // 86 Armi RTypeNone
+ X_Old_South_Arabian, // 87 Sarb RTypeNone
+ X_Inscriptional_Parthian, // 88 Prti RTypeNone
+ X_Inscriptional_Pahlavi, // 89 Phli RTypeNone
+ X_Old_Turkic, // 90 Orkh RTypeNone
+ X_Kaithi, // 91 Kthi RTypeNone
+ X_Batak, // 92 Batk RTypeNone
+ X_Brahmi, // 93 Brah RTypeNone
+ X_Mandaic, // 94 Mand RTypeNone
+ X_Chakma, // 95 Cakm RTypeNone
+ X_Meroitic_Cursive, // 96 Merc RTypeNone
+ X_Meroitic_Hieroglyphs, // 97 Mero RTypeNone
+ X_Miao, // 98 Plrd RTypeNone
+ X_Sharada, // 99 Shrd RTypeNone
+ X_Sora_Sompeng, // 100 Sora RTypeNone
+ X_Takri, // 101 Takr RTypeNone
+};
+
+// Alphabetical order for binary search
+extern const int kNameToULScriptSize = 105;
+extern const CharIntPair kNameToULScript[kNameToULScriptSize] = {
+ {"Arabic", 6}, // Arab
+ {"Armenian", 4}, // Armn
+ {"Avestan", 79}, // Avst
+ {"Balinese", 61}, // Bali
+ {"Bamum", 83}, // Bamu
+ {"Batak", 92}, // Batk
+ {"Bengali", 10}, // Beng
+ {"Bopomofo", 34}, // Bopo
+ {"Brahmi", 93}, // Brah
+ {"Braille", 52}, // Brai
+ {"Buginese", 53}, // Bugi
+ {"Buhid", 43}, // Buhd
+ {"Canadian_Aboriginal", 27}, // Cans
+ {"Carian", 74}, // Cari
+ {"Chakma", 95}, // Cakm
+ {"Cham", 76}, // Cham
+ {"Cherokee", 26}, // Cher
+ {"Common", 0}, // Zyyy
+ {"Coptic", 54}, // Copt
+ {"Cuneiform", 62}, // Xsux
+ {"Cypriot", 51}, // Cprt
+ {"Cyrillic", 3}, // Cyrl
+ {"Deseret", 39}, // Dsrt
+ {"Devanagari", 9}, // Deva
+ {"Egyptian_Hieroglyphs", 80}, // Egyp
+ {"Ethiopic", 25}, // Ethi
+ {"Georgian", 23}, // Geor
+ {"Glagolitic", 56}, // Glag
+ {"Gothic", 38}, // Goth
+ {"Greek", 2}, // Grek
+ {"Gujarati", 12}, // Gujr
+ {"Gurmukhi", 11}, // Guru
+ {"Han", 24}, // Hant
+ {"Han", 24}, // Hans
+ {"Han", 24}, // Hani
+ {"Hangul", 24}, // Hang
+ {"Hani", 24}, // Hani
+ {"Hanunoo", 42}, // Hano
+ {"Hebrew", 5}, // Hebr
+ {"Hiragana", 24}, // Hira
+ {"Imperial_Aramaic", 86}, // Armi
+ {"Inherited", 40}, // Zinh
+ {"Inscriptional_Pahlavi", 89}, // Phli
+ {"Inscriptional_Parthian", 88}, // Prti
+ {"Javanese", 84}, // Java
+ {"Kaithi", 91}, // Kthi
+ {"Kannada", 16}, // Knda
+ {"Katakana", 24}, // Kana
+ {"Kayah_Li", 71}, // Kali
+ {"Kharoshthi", 60}, // Khar
+ {"Khmer", 30}, // Khmr
+ {"Lao", 20}, // Laoo
+ {"Latin", 1}, // Latn
+ {"Lepcha", 67}, // Lepc
+ {"Limbu", 45}, // Limb
+ {"Linear_B", 47}, // Linb
+ {"Lisu", 82}, // Lisu
+ {"Lycian", 73}, // Lyci
+ {"Lydian", 75}, // Lydi
+ {"Malayalam", 17}, // Mlym
+ {"Mandaic", 94}, // Mand
+ {"Meetei_Mayek", 85}, // Mtei
+ {"Meroitic_Cursive", 96}, // Merc
+ {"Meroitic_Hieroglyphs", 97}, // Mero
+ {"Miao", 98}, // Plrd
+ {"Mongolian", 31}, // Mong
+ {"Myanmar", 22}, // Mymr
+ {"New_Tai_Lue", 55}, // Talu
+ {"Nko", 65}, // Nkoo
+ {"Ogham", 28}, // Ogam
+ {"Ol_Chiki", 68}, // Olck
+ {"Old_Italic", 37}, // Ital
+ {"Old_Persian", 59}, // Xpeo
+ {"Old_South_Arabian", 87}, // Sarb
+ {"Old_Turkic", 90}, // Orkh
+ {"Oriya", 13}, // Orya
+ {"Osmanya", 50}, // Osma
+ {"Phags_Pa", 64}, // Phag
+ {"Phoenician", 63}, // Phnx
+ {"Rejang", 72}, // Rjng
+ {"Runic", 29}, // Runr
+ {"Samaritan", 81}, // Samr
+ {"Saurashtra", 70}, // Saur
+ {"Sharada", 99}, // Shrd
+ {"Shavian", 49}, // Shaw
+ {"Sinhala", 18}, // Sinh
+ {"Sora_Sompeng", 100}, // Sora
+ {"Sundanese", 66}, // Sund
+ {"Syloti_Nagri", 58}, // Sylo
+ {"Syriac", 7}, // Syrc
+ {"Tagalog", 41}, // Tglg
+ {"Tagbanwa", 44}, // Tagb
+ {"Tai_Le", 46}, // Tale
+ {"Tai_Tham", 77}, // Lana
+ {"Tai_Viet", 78}, // Tavt
+ {"Takri", 101}, // Takr
+ {"Tamil", 14}, // Taml
+ {"Telugu", 15}, // Telu
+ {"Thaana", 8}, // Thaa
+ {"Thai", 19}, // Thai
+ {"Tibetan", 21}, // Tibt
+ {"Tifinagh", 57}, // Tfng
+ {"Ugaritic", 48}, // Ugar
+ {"Vai", 69}, // Vaii
+ {"Yi", 36}, // Yiii
+};
+
+// Alphabetical order for binary search
+extern const int kCodeToULScriptSize = 105;
+extern const CharIntPair kCodeToULScript[kNameToULScriptSize] = {
+ {"Arab", 6}, // Arab
+ {"Armi", 86}, // Armi
+ {"Armn", 4}, // Armn
+ {"Avst", 79}, // Avst
+ {"Bali", 61}, // Bali
+ {"Bamu", 83}, // Bamu
+ {"Batk", 92}, // Batk
+ {"Beng", 10}, // Beng
+ {"Bopo", 34}, // Bopo
+ {"Brah", 93}, // Brah
+ {"Brai", 52}, // Brai
+ {"Bugi", 53}, // Bugi
+ {"Buhd", 43}, // Buhd
+ {"Cakm", 95}, // Cakm
+ {"Cans", 27}, // Cans
+ {"Cari", 74}, // Cari
+ {"Cham", 76}, // Cham
+ {"Cher", 26}, // Cher
+ {"Copt", 54}, // Copt
+ {"Cprt", 51}, // Cprt
+ {"Cyrl", 3}, // Cyrl
+ {"Deva", 9}, // Deva
+ {"Dsrt", 39}, // Dsrt
+ {"Egyp", 80}, // Egyp
+ {"Ethi", 25}, // Ethi
+ {"Geor", 23}, // Geor
+ {"Glag", 56}, // Glag
+ {"Goth", 38}, // Goth
+ {"Grek", 2}, // Grek
+ {"Gujr", 12}, // Gujr
+ {"Guru", 11}, // Guru
+ {"Hang", 24}, // Hang
+ {"Hani", 24}, // Hani
+ {"Hani", 24}, // Hani
+ {"Hano", 42}, // Hano
+ {"Hans", 24}, // Hans
+ {"Hant", 24}, // Hant
+ {"Hebr", 5}, // Hebr
+ {"Hira", 24}, // Hira
+ {"Ital", 37}, // Ital
+ {"Java", 84}, // Java
+ {"Kali", 71}, // Kali
+ {"Kana", 24}, // Kana
+ {"Khar", 60}, // Khar
+ {"Khmr", 30}, // Khmr
+ {"Knda", 16}, // Knda
+ {"Kthi", 91}, // Kthi
+ {"Lana", 77}, // Lana
+ {"Laoo", 20}, // Laoo
+ {"Latn", 1}, // Latn
+ {"Lepc", 67}, // Lepc
+ {"Limb", 45}, // Limb
+ {"Linb", 47}, // Linb
+ {"Lisu", 82}, // Lisu
+ {"Lyci", 73}, // Lyci
+ {"Lydi", 75}, // Lydi
+ {"Mand", 94}, // Mand
+ {"Merc", 96}, // Merc
+ {"Mero", 97}, // Mero
+ {"Mlym", 17}, // Mlym
+ {"Mong", 31}, // Mong
+ {"Mtei", 85}, // Mtei
+ {"Mymr", 22}, // Mymr
+ {"Nkoo", 65}, // Nkoo
+ {"Ogam", 28}, // Ogam
+ {"Olck", 68}, // Olck
+ {"Orkh", 90}, // Orkh
+ {"Orya", 13}, // Orya
+ {"Osma", 50}, // Osma
+ {"Phag", 64}, // Phag
+ {"Phli", 89}, // Phli
+ {"Phnx", 63}, // Phnx
+ {"Plrd", 98}, // Plrd
+ {"Prti", 88}, // Prti
+ {"Rjng", 72}, // Rjng
+ {"Runr", 29}, // Runr
+ {"Samr", 81}, // Samr
+ {"Sarb", 87}, // Sarb
+ {"Saur", 70}, // Saur
+ {"Shaw", 49}, // Shaw
+ {"Shrd", 99}, // Shrd
+ {"Sinh", 18}, // Sinh
+ {"Sora", 100}, // Sora
+ {"Sund", 66}, // Sund
+ {"Sylo", 58}, // Sylo
+ {"Syrc", 7}, // Syrc
+ {"Tagb", 44}, // Tagb
+ {"Takr", 101}, // Takr
+ {"Tale", 46}, // Tale
+ {"Talu", 55}, // Talu
+ {"Taml", 14}, // Taml
+ {"Tavt", 78}, // Tavt
+ {"Telu", 15}, // Telu
+ {"Tfng", 57}, // Tfng
+ {"Tglg", 41}, // Tglg
+ {"Thaa", 8}, // Thaa
+ {"Thai", 19}, // Thai
+ {"Tibt", 21}, // Tibt
+ {"Ugar", 48}, // Ugar
+ {"Vaii", 69}, // Vaii
+ {"Xpeo", 59}, // Xpeo
+ {"Xsux", 62}, // Xsux
+ {"Yiii", 36}, // Yiii
+ {"Zinh", 40}, // Zinh
+ {"Zyyy", 0}, // Zyyy
+};
+
+} // namespace CLD2
diff --git a/browser/components/translation/cld2/internal/generated_ulscript.h b/browser/components/translation/cld2/internal/generated_ulscript.h
new file mode 100644
index 000000000..845b9c191
--- /dev/null
+++ b/browser/components/translation/cld2/internal/generated_ulscript.h
@@ -0,0 +1,140 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// generated_ulscript.h
+// Machine generated. Do Not Edit.
+//
+// Declarations for scripts recognized by CLD2
+//
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_GENERATED_ULSCRIPT_H__
+#define I18N_ENCODINGS_CLD2_INTERNAL_GENERATED_ULSCRIPT_H__
+
+namespace CLD2 {
+
+typedef enum {RTypeNone = 0, RTypeOne, RTypeMany, RTypeCJK} ULScriptRType;
+
+typedef struct {const char* s; int i;} CharIntPair;
+
+typedef enum {
+ ULScript_Common = 0, // Zyyy
+ ULScript_Latin = 1, // Latn
+ ULScript_Greek = 2, // Grek
+ ULScript_Cyrillic = 3, // Cyrl
+ ULScript_Armenian = 4, // Armn
+ ULScript_Hebrew = 5, // Hebr
+ ULScript_Arabic = 6, // Arab
+ ULScript_Syriac = 7, // Syrc
+ ULScript_Thaana = 8, // Thaa
+ ULScript_Devanagari = 9, // Deva
+ ULScript_Bengali = 10, // Beng
+ ULScript_Gurmukhi = 11, // Guru
+ ULScript_Gujarati = 12, // Gujr
+ ULScript_Oriya = 13, // Orya
+ ULScript_Tamil = 14, // Taml
+ ULScript_Telugu = 15, // Telu
+ ULScript_Kannada = 16, // Knda
+ ULScript_Malayalam = 17, // Mlym
+ ULScript_Sinhala = 18, // Sinh
+ ULScript_Thai = 19, // Thai
+ ULScript_Lao = 20, // Laoo
+ ULScript_Tibetan = 21, // Tibt
+ ULScript_Myanmar = 22, // Mymr
+ ULScript_Georgian = 23, // Geor
+ ULScript_Hani = 24, // Hani
+ ULScript_Ethiopic = 25, // Ethi
+ ULScript_Cherokee = 26, // Cher
+ ULScript_Canadian_Aboriginal = 27, // Cans
+ ULScript_Ogham = 28, // Ogam
+ ULScript_Runic = 29, // Runr
+ ULScript_Khmer = 30, // Khmr
+ ULScript_Mongolian = 31, // Mong
+ ULScript_32 = 32, //
+ ULScript_33 = 33, //
+ ULScript_Bopomofo = 34, // Bopo
+ ULScript_35 = 35, //
+ ULScript_Yi = 36, // Yiii
+ ULScript_Old_Italic = 37, // Ital
+ ULScript_Gothic = 38, // Goth
+ ULScript_Deseret = 39, // Dsrt
+ ULScript_Inherited = 40, // Zinh
+ ULScript_Tagalog = 41, // Tglg
+ ULScript_Hanunoo = 42, // Hano
+ ULScript_Buhid = 43, // Buhd
+ ULScript_Tagbanwa = 44, // Tagb
+ ULScript_Limbu = 45, // Limb
+ ULScript_Tai_Le = 46, // Tale
+ ULScript_Linear_B = 47, // Linb
+ ULScript_Ugaritic = 48, // Ugar
+ ULScript_Shavian = 49, // Shaw
+ ULScript_Osmanya = 50, // Osma
+ ULScript_Cypriot = 51, // Cprt
+ ULScript_Braille = 52, // Brai
+ ULScript_Buginese = 53, // Bugi
+ ULScript_Coptic = 54, // Copt
+ ULScript_New_Tai_Lue = 55, // Talu
+ ULScript_Glagolitic = 56, // Glag
+ ULScript_Tifinagh = 57, // Tfng
+ ULScript_Syloti_Nagri = 58, // Sylo
+ ULScript_Old_Persian = 59, // Xpeo
+ ULScript_Kharoshthi = 60, // Khar
+ ULScript_Balinese = 61, // Bali
+ ULScript_Cuneiform = 62, // Xsux
+ ULScript_Phoenician = 63, // Phnx
+ ULScript_Phags_Pa = 64, // Phag
+ ULScript_Nko = 65, // Nkoo
+ ULScript_Sundanese = 66, // Sund
+ ULScript_Lepcha = 67, // Lepc
+ ULScript_Ol_Chiki = 68, // Olck
+ ULScript_Vai = 69, // Vaii
+ ULScript_Saurashtra = 70, // Saur
+ ULScript_Kayah_Li = 71, // Kali
+ ULScript_Rejang = 72, // Rjng
+ ULScript_Lycian = 73, // Lyci
+ ULScript_Carian = 74, // Cari
+ ULScript_Lydian = 75, // Lydi
+ ULScript_Cham = 76, // Cham
+ ULScript_Tai_Tham = 77, // Lana
+ ULScript_Tai_Viet = 78, // Tavt
+ ULScript_Avestan = 79, // Avst
+ ULScript_Egyptian_Hieroglyphs = 80, // Egyp
+ ULScript_Samaritan = 81, // Samr
+ ULScript_Lisu = 82, // Lisu
+ ULScript_Bamum = 83, // Bamu
+ ULScript_Javanese = 84, // Java
+ ULScript_Meetei_Mayek = 85, // Mtei
+ ULScript_Imperial_Aramaic = 86, // Armi
+ ULScript_Old_South_Arabian = 87, // Sarb
+ ULScript_Inscriptional_Parthian = 88, // Prti
+ ULScript_Inscriptional_Pahlavi = 89, // Phli
+ ULScript_Old_Turkic = 90, // Orkh
+ ULScript_Kaithi = 91, // Kthi
+ ULScript_Batak = 92, // Batk
+ ULScript_Brahmi = 93, // Brah
+ ULScript_Mandaic = 94, // Mand
+ ULScript_Chakma = 95, // Cakm
+ ULScript_Meroitic_Cursive = 96, // Merc
+ ULScript_Meroitic_Hieroglyphs = 97, // Mero
+ ULScript_Miao = 98, // Plrd
+ ULScript_Sharada = 99, // Shrd
+ ULScript_Sora_Sompeng = 100, // Sora
+ ULScript_Takri = 101, // Takr
+ NUM_ULSCRIPTS
+} ULScript;
+
+#define UNKNOWN_ULSCRIPT ULScript_Common
+
+} // namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_GENERATED_ULSCRIPT_H__
diff --git a/browser/components/translation/cld2/internal/getonescriptspan.cc b/browser/components/translation/cld2/internal/getonescriptspan.cc
new file mode 100644
index 000000000..6bdd4871b
--- /dev/null
+++ b/browser/components/translation/cld2/internal/getonescriptspan.cc
@@ -0,0 +1,1086 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+
+#include "getonescriptspan.h"
+#include <string.h>
+
+#include "fixunicodevalue.h"
+#include "lang_script.h"
+#include "port.h"
+#include "utf8statetable.h"
+
+#include "utf8prop_lettermarkscriptnum.h"
+#include "utf8repl_lettermarklower.h"
+#include "utf8scannot_lettermarkspecial.h"
+
+
+namespace CLD2 {
+
+// Alphabetical order for binary search, from
+// generated_entities.cc
+extern const int kNameToEntitySize;
+extern const CharIntPair kNameToEntity[];
+
+static const int kMaxUpToWordBoundary = 50; // span < this make longer,
+ // else make shorter
+static const int kMaxAdvanceToWordBoundary = 10; // +/- this many bytes
+ // to round to word boundary,
+ // direction above
+
+static const char kSpecialSymbol[256] = { // true for < > &
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,1,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,1,0,1,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+};
+
+
+
+#define LT 0 // <
+#define GT 1 // >
+#define EX 2 // !
+#define HY 3 // -
+#define QU 4 // "
+#define AP 5 // '
+#define SL 6 // /
+#define S_ 7
+#define C_ 8
+#define R_ 9
+#define I_ 10
+#define P_ 11
+#define T_ 12
+#define Y_ 13
+#define L_ 14
+#define E_ 15
+#define CR 16 // <cr> or <lf>
+#define NL 17 // non-letter: ASCII whitespace, digit, punctuation
+#define PL 18 // possible letter, incl. &
+#define xx 19 // <unused>
+
+// Map byte to one of ~20 interesting categories for cheap tag parsing
+static const uint8 kCharToSub[256] = {
+ NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,CR,NL, NL,CR,NL,NL,
+ NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
+ NL,EX,QU,NL, NL,NL,PL,AP, NL,NL,NL,NL, NL,HY,NL,SL,
+ NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, LT,NL,GT,NL,
+
+ PL,PL,PL,C_, PL,E_,PL,PL, PL,I_,PL,PL, L_,PL,PL,PL,
+ P_,PL,R_,S_, T_,PL,PL,PL, PL,Y_,PL,NL, NL,NL,NL,NL,
+ PL,PL,PL,C_, PL,E_,PL,PL, PL,I_,PL,PL, L_,PL,PL,PL,
+ P_,PL,R_,S_, T_,PL,PL,PL, PL,Y_,PL,NL, NL,NL,NL,NL,
+
+ NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
+ NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
+ NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
+ NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
+
+ PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
+ PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
+ PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
+ PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
+};
+
+#undef LT
+#undef GT
+#undef EX
+#undef HY
+#undef QU
+#undef AP
+#undef SL
+#undef S_
+#undef C_
+#undef R_
+#undef I_
+#undef P_
+#undef T_
+#undef Y_
+#undef L_
+#undef E_
+#undef CR
+#undef NL
+#undef PL
+#undef xx
+
+
+#define OK 0
+#define X_ 1
+
+
+static const int kMaxExitStateLettersMarksOnly = 1;
+static const int kMaxExitStateAllText = 2;
+
+
+// State machine to do cheap parse of non-letter strings incl. tags
+// advances <tag>
+// | |
+// advances <tag> ... </tag> for <script> <style>
+// | |
+// advances <!-- ... <tag> ... -->
+// | |
+// advances <tag
+// || (0)
+// advances <tag <tag2>
+// || (0)
+//
+// We start in state [0] at a non-letter and make at least one transition
+// When scanning for just letters, arriving back at state [0] or [1] exits
+// the state machine.
+// When scanning for any non-tag text, arriving at state [2] also exits
+static const uint8 kTagParseTbl_0[] = {
+// < > ! - " ' / S C R I P T Y L E CR NL PL xx
+ 3, 2, 2, 2, 2, 2, 2,OK, OK,OK,OK,OK, OK,OK,OK,OK, 2, 2,OK,X_, // [0] OK exit state
+ X_,X_,X_,X_, X_,X_,X_,X_, X_,X_,X_,X_, X_,X_,X_,X_, X_,X_,X_,X_, // [1] error exit state
+ 3, 2, 2, 2, 2, 2, 2,OK, OK,OK,OK,OK, OK,OK,OK,OK, 2, 2,OK,X_, // [2] NL* [exit state]
+ X_, 2, 4, 9, 10,11, 9,13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [3] <
+ X_, 2, 9, 5, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [4] <!
+ X_, 2, 9, 6, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [5] <!-
+ 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,X_, // [6] <!--.*
+ 6, 6, 6, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,X_, // [7] <!--.*-
+ 6, 2, 6, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,X_, // [8] <!--.*--
+ X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [9] <.*
+ 10,10,10,10, 9,10,10,10, 10,10,10,10, 10,10,10,10, 12,10,10,X_, // [10] <.*"
+ 11,11,11,11, 11, 9,11,11, 11,11,11,11, 11,11,11,11, 12,11,11,X_, // [11] <.*'
+ X_, 2,12,12, 12,12,12,12, 12,12,12,12, 12,12,12,12, 12,12,12,X_, // [12] <.* no " '
+
+// < > ! - " ' / S C R I P T Y L E CR NL PL xx
+ X_, 2, 9, 9, 10,11, 9, 9, 14, 9, 9, 9, 28, 9, 9, 9, 9, 9, 9,X_, // [13] <S
+ X_, 2, 9, 9, 10,11, 9, 9, 9,15, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [14] <SC
+ X_, 2, 9, 9, 10,11, 9, 9, 9, 9,16, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [15] <SCR
+ X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9,17, 9, 9, 9, 9, 9, 9, 9,X_, // [16] <SCRI
+ X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 18, 9, 9, 9, 9, 9, 9,X_, // [17] <SCRIP
+ X_,19, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 19,19, 9,X_, // [18] <SCRIPT
+ 20,19,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,X_, // [19] <SCRIPT .*
+ 19,19,19,19, 19,19,21,19, 19,19,19,19, 19,19,19,19, 19,19,19,X_, // [20] <SCRIPT .*<
+ 19,19,19,19, 19,19,19,22, 19,19,19,19, 19,19,19,19, 21,21,19,X_, // [21] <SCRIPT .*</ allow SP CR LF
+ 19,19,19,19, 19,19,19,19, 23,19,19,19, 19,19,19,19, 19,19,19,X_, // [22] <SCRIPT .*</S
+ 19,19,19,19, 19,19,19,19, 19,24,19,19, 19,19,19,19, 19,19,19,X_, // [23] <SCRIPT .*</SC
+ 19,19,19,19, 19,19,19,19, 19,19,25,19, 19,19,19,19, 19,19,19,X_, // [24] <SCRIPT .*</SCR
+ 19,19,19,19, 19,19,19,19, 19,19,19,26, 19,19,19,19, 19,19,19,X_, // [25] <SCRIPT .*</SCRI
+ 19,19,19,19, 19,19,19,19, 19,19,19,19, 27,19,19,19, 19,19,19,X_, // [26] <SCRIPT .*</SCRIP
+ 19, 2,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,X_, // [27] <SCRIPT .*</SCRIPT
+
+// < > ! - " ' / S C R I P T Y L E CR NL PL xx
+ X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9,29, 9, 9, 9, 9, 9,X_, // [28] <ST
+ X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9,30, 9, 9, 9, 9,X_, // [29] <STY
+ X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9,31, 9, 9, 9,X_, // [30] <STYL
+ X_,32, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 32,32, 9,X_, // [31] <STYLE
+ 33,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,X_, // [32] <STYLE .*
+ 32,32,32,32, 32,32,34,32, 32,32,32,32, 32,32,32,32, 32,32,32,X_, // [33] <STYLE .*<
+ 32,32,32,32, 32,32,32,35, 32,32,32,32, 32,32,32,32, 34,34,32,X_, // [34] <STYLE .*</ allow SP CR LF
+ 32,32,32,32, 32,32,32,32, 32,32,32,32, 36,32,32,32, 32,32,32,X_, // [35] <STYLE .*</S
+ 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,37,32,32, 32,32,32,X_, // [36] <STYLE .*</ST
+ 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,38,32, 32,32,32,X_, // [37] <STYLE .*</STY
+ 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,39, 32,32,32,X_, // [38] <STYLE .*</STYL
+ 32, 2,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,X_, // [39] <STYLE .*</STYLE
+};
+
+#undef OK
+#undef X_
+
+enum
+{
+ UTFmax = 4, // maximum bytes per rune
+ Runesync = 0x80, // cannot represent part of a UTF sequence (<)
+ Runeself = 0x80, // rune and UTF sequences are the same (<)
+ Runeerror = 0xFFFD, // decoding error in UTF
+ Runemax = 0x10FFFF, // maximum rune value
+};
+
+// Debugging. Not thread safe.
+static char gDisplayPiece[32];
+const uint8 gCharlen[16] = {1,1,1,1, 1,1,1,1, 1,1,1,1, 2,2,3,4};
+char* DisplayPiece(const char* next_byte_, int byte_length_) {
+ // Copy up to 8 UTF-8 chars to buffer
+ int k = 0; // byte count
+ int n = 0; // character count
+ for (int i = 0; i < byte_length_; ++i) {
+ char c = next_byte_[i];
+ if ((c & 0xc0) != 0x80) {
+ // Beginning of a UTF-8 character
+ int charlen = gCharlen[static_cast<uint8>(c) >> 4];
+ if (i + charlen > byte_length_) {break;} // Not enough room for full char
+ if (k >= (32 - 7)) {break;} // Not necessarily enough room
+ if (n >= 8) {break;} // Enough characters already
+ ++n;
+ }
+ if (c == '<') {
+ memcpy(&gDisplayPiece[k], "&lt;", 4); k += 4;
+ } else if (c == '>') {
+ memcpy(&gDisplayPiece[k], "&gt;", 4); k += 4;
+ } else if (c == '&') {
+ memcpy(&gDisplayPiece[k], "&amp;", 5); k += 5;
+ } else if (c == '\'') {
+ memcpy(&gDisplayPiece[k], "&apos;", 6); k += 6;
+ } else if (c == '"') {
+ memcpy(&gDisplayPiece[k], "&quot;", 6); k += 6;
+ } else {
+ gDisplayPiece[k++] = c;
+ }
+ }
+ gDisplayPiece[k++] = '\0';
+ return gDisplayPiece;
+}
+
+
+
+// runetochar copies (encodes) one rune, pointed to by r, to at most
+// UTFmax bytes starting at s and returns the number of bytes generated.
+int runetochar(char *str, const char32 *rune) {
+ // Convert to unsigned for range check.
+ unsigned long c;
+
+ // 1 char 00-7F
+ c = *rune;
+ if(c <= 0x7F) {
+ str[0] = c;
+ return 1;
+ }
+
+ // 2 char 0080-07FF
+ if(c <= 0x07FF) {
+ str[0] = 0xC0 | (c >> 1*6);
+ str[1] = 0x80 | (c & 0x3F);
+ return 2;
+ }
+
+ // Range check
+ if (c > Runemax) {
+ c = Runeerror;
+ }
+
+ // 3 char 0800-FFFF
+ if (c <= 0xFFFF) {
+ str[0] = 0xE0 | (c >> 2*6);
+ str[1] = 0x80 | ((c >> 1*6) & 0x3F);
+ str[2] = 0x80 | (c & 0x3F);
+ return 3;
+ }
+
+ // 4 char 10000-1FFFFF
+ str[0] = 0xF0 | (c >> 3*6);
+ str[1] = 0x80 | ((c >> 2*6) & 0x3F);
+ str[2] = 0x80 | ((c >> 1*6) & 0x3F);
+ str[3] = 0x80 | (c & 0x3F);
+ return 4;
+}
+
+
+
+// Useful for converting an entity to an ascii value.
+// RETURNS unicode value, or -1 if entity isn't valid. Don't include & or ;
+int LookupEntity(const char* entity_name, int entity_len) {
+ // Make a C string
+ if (entity_len >= 16) {return -1;} // All real entities are shorter
+ char temp[16];
+ memcpy(temp, entity_name, entity_len);
+ temp[entity_len] = '\0';
+ int match = BinarySearch(temp, 0, kNameToEntitySize, kNameToEntity);
+ if (match >= 0) {return kNameToEntity[match].i;}
+ return -1;
+}
+
+bool ascii_isdigit(char c) {
+ return ('0' <= c) && (c <= '9');
+}
+bool ascii_isxdigit(char c) {
+ if (('0' <= c) && (c <= '9')) {return true;}
+ if (('a' <= c) && (c <= 'f')) {return true;}
+ if (('A' <= c) && (c <= 'F')) {return true;}
+ return false;
+}
+bool ascii_isalnum(char c) {
+ if (('0' <= c) && (c <= '9')) {return true;}
+ if (('a' <= c) && (c <= 'z')) {return true;}
+ if (('A' <= c) && (c <= 'Z')) {return true;}
+ return false;
+}
+int hex_digit_to_int(char c) {
+ if (('0' <= c) && (c <= '9')) {return c - '0';}
+ if (('a' <= c) && (c <= 'f')) {return c - 'a' + 10;}
+ if (('A' <= c) && (c <= 'F')) {return c - 'A' + 10;}
+ return 0;
+}
+
+static int32 strto32_base10(const char* nptr, const char* limit,
+ const char **endptr) {
+ *endptr = nptr;
+ while (nptr < limit && *nptr == '0') {
+ ++nptr;
+ }
+ if (nptr == limit || !ascii_isdigit(*nptr))
+ return -1;
+ const char* end_digits_run = nptr;
+ while (end_digits_run < limit && ascii_isdigit(*end_digits_run)) {
+ ++end_digits_run;
+ }
+ *endptr = end_digits_run;
+ const int num_digits = end_digits_run - nptr;
+ // kint32max == 2147483647.
+ if (num_digits < 9 ||
+ (num_digits == 10 && memcmp(nptr, "2147483647", 10) <= 0)) {
+ int value = 0;
+ for (; nptr < end_digits_run; ++nptr) {
+ value *= 10;
+ value += *nptr - '0';
+ }
+ // Overflow past the last valid unicode codepoint
+ // (0x10ffff) is converted to U+FFFD by FixUnicodeValue().
+ return FixUnicodeValue(value);
+ } else {
+ // Overflow: can't fit in an int32;
+ // returns the replacement character 0xFFFD.
+ return 0xFFFD;
+ }
+}
+
+static int32 strto32_base16(const char* nptr, const char* limit,
+ const char **endptr) {
+ *endptr = nptr;
+ while (nptr < limit && *nptr == '0') {
+ ++nptr;
+ }
+ if (nptr == limit || !ascii_isxdigit(*nptr)) {
+ return -1;
+ }
+ const char* end_xdigits_run = nptr;
+ while (end_xdigits_run < limit && ascii_isxdigit(*end_xdigits_run)) {
+ ++end_xdigits_run;
+ }
+ *endptr = end_xdigits_run;
+ const int num_xdigits = end_xdigits_run - nptr;
+ // kint32max == 0x7FFFFFFF.
+ if (num_xdigits < 8 || (num_xdigits == 8 && nptr[0] < '8')) {
+ int value = 0;
+ for (; nptr < end_xdigits_run; ++nptr) {
+ value <<= 4;
+ value += hex_digit_to_int(*nptr);
+ }
+ // Overflow past the last valid unicode codepoint
+ // (0x10ffff) is converted to U+FFFD by FixUnicodeValue().
+ return FixUnicodeValue(value);
+ } else {
+ // Overflow: can't fit in an int32;
+ // returns the replacement character 0xFFFD.
+ return 0xFFFD;
+ }
+}
+
+// Unescape the current character pointed to by src. SETS the number
+// of chars read for the conversion (in UTF8). If src isn't a valid entity,
+// just consume the & and RETURN -1. If src doesn't point to & -- which it
+// should -- set src_consumed to 0 and RETURN -1.
+int ReadEntity(const char* src, int srcn, int* src_consumed) {
+ const char* const srcend = src + srcn;
+
+ if (srcn == 0 || *src != '&') { // input should start with an ampersand
+ *src_consumed = 0;
+ return -1;
+ }
+ *src_consumed = 1; // we'll get the & at least
+
+ // The standards are a bit unclear on when an entity ends. Certainly a ";"
+ // ends one, but spaces probably do too. We follow the lead of both IE and
+ // Netscape, which as far as we can tell end numeric entities (1st case below)
+ // at any non-digit, and end character entities (2nd case) at any non-alnum.
+ const char* entstart, *entend; // where the entity starts and ends
+ entstart = src + 1; // read past the &
+ int entval; // UCS2 value of the entity
+ if ( *entstart == '#' ) { // -- 1st case: numeric entity
+ if ( entstart + 2 >= srcend ) {
+ return -1; // no way a legitimate number could fit
+ } else if ( entstart[1] == 'x' || entstart[1] == 'X' ) { // hex numeric
+ entval = strto32_base16(entstart + 2, srcend, &entend);
+ } else { // decimal numeric entity
+ entval = strto32_base10(entstart+1, srcend, &entend);
+ }
+ if (entval == -1 || entend > srcend) {
+ return -1; // not entirely correct, but close enough
+ }
+ } else { // -- 2nd case: character entity
+ for (entend = entstart;
+ entend < srcend && ascii_isalnum(*entend);
+ ++entend ) {
+ // entity consists of alphanumeric chars
+ }
+ entval = LookupEntity(entstart, entend - entstart);
+ if (entval < 0) {
+ return -1; // not a legal entity name
+ }
+ // Now we do a strange-seeming IE6-compatibility check: if entval is
+ // >= 256, it *must* be followed by a semicolon or it's not considered
+ // an entity. The problem is lots of the newfangled entity names, like
+ // "lang", also occur in URL CGI arguments: "/search?q=test&lang=en".
+ // When these links are written in HTML, it would be really bad if the
+ // "&lang" were treated as an entity, which is what the spec says
+ // *should* happen (even when the HTML is inside an "A HREF" tag!)
+ // IE ignores the spec for these new, high-value entities, so we do too.
+ if ( entval >= 256 && !(entend < srcend && *entend == ';') ) {
+ return -1; // make non-;-terminated entity illegal
+ }
+ }
+
+ // Finally, figure out how much src was consumed
+ if ( entend < srcend && *entend == ';' ) {
+ entend++; // standard says ; terminator is special
+ }
+ *src_consumed = entend - src;
+ return entval;
+}
+
+
+// Src points to '&'
+// Writes entity value to dst. Returns take(src), put(dst) byte counts
+void EntityToBuffer(const char* src, int len, char* dst,
+ int* tlen, int* plen) {
+ char32 entval = ReadEntity(src, len, tlen);
+
+ // ReadEntity does this already: entval = FixUnicodeValue(entval);
+
+ // Convert UTF-32 to UTF-8
+ if (entval > 0) {
+ *plen = runetochar(dst, &entval);
+ } else {
+ // Illegal entity; ignore the '&'
+ *tlen = 1;
+ *plen = 0;
+ }
+}
+
+// Returns true if character is < > or &, none of which are letters
+bool inline IsSpecial(char c) {
+ if ((c & 0xe0) == 0x20) {
+ return kSpecialSymbol[static_cast<uint8>(c)];
+ }
+ return false;
+}
+
+// Quick Skip to next letter or < > & or to end of string (eos)
+// Always return is_letter for eos
+int ScanToLetterOrSpecial(const char* src, int len) {
+ int bytes_consumed;
+ StringPiece str(src, len);
+ UTF8GenericScan(&utf8scannot_lettermarkspecial_obj, str, &bytes_consumed);
+ return bytes_consumed;
+}
+
+
+
+
+// src points to non-letter, such as tag-opening '<'
+// Return length from here to next possible letter
+// On another < before >, return 1
+// advances <tag>
+// | |
+// advances <tag> ... </tag> for <script> <style>
+// | |
+// advances <!-- ... <tag> ... -->
+// | |
+// advances <tag
+// | | end of string
+// advances <tag <tag2>
+// ||
+int ScanToPossibleLetter(const char* isrc, int len, int max_exit_state) {
+ const uint8* src = reinterpret_cast<const uint8*>(isrc);
+ const uint8* srclimit = src + len;
+ const uint8* tagParseTbl = kTagParseTbl_0;
+ int e = 0;
+ while (src < srclimit) {
+ e = tagParseTbl[kCharToSub[*src++]];
+ if (e <= max_exit_state) {
+ // We overshot by one byte
+ --src;
+ break;
+ }
+ tagParseTbl = &kTagParseTbl_0[e * 20];
+ }
+
+ if (src >= srclimit) {
+ // We fell off the end of the text.
+ // It looks like the most common case for this is a truncated file, not
+ // mismatched angle brackets. So we pretend that the last char was '>'
+ return len;
+ }
+
+ // OK to be in state 0 or state 2 at exit
+ if ((e != 0) && (e != 2)) {
+ // Error, '<' followed by '<'
+ // We want to back up to first <, then advance by one byte past it
+ int offset = src - reinterpret_cast<const uint8*>(isrc);
+
+ // Backscan to first '<' and return enough length to just get past it
+ --offset; // back up over the second '<', which caused us to stop
+ while ((0 < offset) && (isrc[offset] != '<')) {
+ // Find the first '<', which is unmatched
+ --offset;
+ }
+ // skip to just beyond first '<'
+ return offset + 1;
+ }
+
+ return src - reinterpret_cast<const uint8*>(isrc);
+}
+
+
+ScriptScanner::ScriptScanner(const char* buffer,
+ int buffer_length,
+ bool is_plain_text)
+ : start_byte_(buffer),
+ next_byte_(buffer),
+ next_byte_limit_(buffer + buffer_length),
+ byte_length_(buffer_length),
+ is_plain_text_(is_plain_text),
+ letters_marks_only_(true),
+ one_script_only_(true),
+ exit_state_(kMaxExitStateLettersMarksOnly) {
+ script_buffer_ = new char[kMaxScriptBuffer];
+ script_buffer_lower_ = new char[kMaxScriptLowerBuffer];
+ map2original_.Clear(); // map from script_buffer_ to buffer
+ map2uplow_.Clear(); // map from script_buffer_lower_ to script_buffer_
+}
+
+// Extended version to allow spans of any non-tag text and spans of mixed script
+ScriptScanner::ScriptScanner(const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ bool any_text,
+ bool any_script)
+ : start_byte_(buffer),
+ next_byte_(buffer),
+ next_byte_limit_(buffer + buffer_length),
+ byte_length_(buffer_length),
+ is_plain_text_(is_plain_text),
+ letters_marks_only_(!any_text),
+ one_script_only_(!any_script),
+ exit_state_(any_text ? kMaxExitStateAllText : kMaxExitStateLettersMarksOnly) {
+ script_buffer_ = new char[kMaxScriptBuffer];
+ script_buffer_lower_ = new char[kMaxScriptLowerBuffer];
+ map2original_.Clear(); // map from script_buffer_ to buffer
+ map2uplow_.Clear(); // map from script_buffer_lower_ to script_buffer_
+}
+
+
+ScriptScanner::~ScriptScanner() {
+ delete[] script_buffer_;
+ delete[] script_buffer_lower_;
+}
+
+
+
+
+// Get to the first real non-tag letter or entity that is a letter
+// Sets script of that letter
+// Return len if no more letters
+int ScriptScanner::SkipToFrontOfSpan(const char* src, int len, int* script) {
+ int sc = UNKNOWN_ULSCRIPT;
+ int skip = 0;
+ int tlen, plen;
+
+ // Do run of non-letters (tag | &NL | NL)*
+ tlen = 0;
+ while (skip < len) {
+ // Do fast scan to next interesting byte
+ // int oldskip = skip;
+ skip += ScanToLetterOrSpecial(src + skip, len - skip);
+
+ // Check for no more letters/specials
+ if (skip >= len) {
+ // All done
+ *script = sc;
+ return len;
+ }
+
+ // We are at a letter, nonletter, tag, or entity
+ if (IsSpecial(src[skip]) && !is_plain_text_) {
+ if (src[skip] == '<') {
+ // Begining of tag; skip to end and go around again
+ tlen = ScanToPossibleLetter(src + skip, len - skip,
+ exit_state_);
+ sc = 0;
+ } else if (src[skip] == '>') {
+ // Unexpected end of tag; skip it and go around again
+ tlen = 1; // Over the >
+ sc = 0;
+ } else if (src[skip] == '&') {
+ // Expand entity, no advance
+ char temp[4];
+ EntityToBuffer(src + skip, len - skip,
+ temp, &tlen, &plen);
+ sc = GetUTF8LetterScriptNum(temp);
+ }
+ } else {
+ // Update 1..4 bytes
+ tlen = UTF8OneCharLen(src + skip);
+ sc = GetUTF8LetterScriptNum(src + skip);
+ }
+ if (sc != 0) {break;} // Letter found
+ skip += tlen; // Else advance
+ }
+
+ *script = sc;
+ return skip;
+}
+
+
+// These are for ASCII-only tag names
+// Compare one letter uplow to c, ignoring case of uplowp
+inline bool EqCase(char uplow, char c) {
+ return (uplow | 0x20) == c;
+}
+
+// These are for ASCII-only tag names
+// Return true for space / < > etc. all less than 0x40
+inline bool NeqLetter(char c) {
+ return c < 0x40;
+}
+
+// These are for ASCII-only tag names
+// Return true for space \n false for \r
+inline bool WS(char c) {
+ return (c == ' ') || (c == '\n');
+}
+
+// Canonical CR or LF
+static const char LF = '\n';
+
+
+// The naive loop scans from next_byte_ to script_buffer_ until full.
+// But this can leave an awkward hard-to-identify short fragment at the
+// end of the input. We would prefer to make the next-to-last fragment
+// shorter and the last fragment longer.
+
+// Copy next run of non-tag characters to buffer [NUL terminated]
+// This just replaces tags with space or \n and removes entities.
+// Tags <br> <p> and <tr> are replaced with \n. Non-letter sequences
+// including \r or \n are replaced by \n. All other tags and skipped text
+// are replaced with ASCII space.
+//
+// Buffer ALWAYS has leading space and trailing space space space NUL
+bool ScriptScanner::GetOneTextSpan(LangSpan* span) {
+ span->text = script_buffer_;
+ span->text_bytes = 0;
+ span->offset = next_byte_ - start_byte_;
+ span->ulscript = UNKNOWN_ULSCRIPT;
+ span->lang = UNKNOWN_LANGUAGE;
+ span->truncated = false;
+
+ int put_soft_limit = kMaxScriptBytes - kWithinScriptTail;
+ if ((kMaxScriptBytes <= byte_length_) &&
+ (byte_length_ < (2 * kMaxScriptBytes))) {
+ // Try to split the last two fragments in half
+ put_soft_limit = byte_length_ / 2;
+ }
+
+ script_buffer_[0] = ' '; // Always a space at front of output
+ script_buffer_[1] = '\0';
+ int take = 0;
+ int put = 1; // Start after the initial space
+ int tlen, plen;
+
+ if (byte_length_ <= 0) {
+ return false; // No more text to be found
+ }
+
+ // Go over alternating spans of text and tags,
+ // copying letters to buffer with single spaces for each run of non-letters
+ bool last_byte_was_space = false;
+ while (take < byte_length_) {
+ char c = next_byte_[take];
+ if (c == '\r') {c = LF;} // Canonical CR or LF
+ if (c == '\n') {c = LF;} // Canonical CR or LF
+
+ if (IsSpecial(c) && !is_plain_text_) {
+ if (c == '<') {
+ // Replace tag with space
+ c = ' '; // for almost-full test below
+ // or if <p> <br> <tr>, replace with \n
+ if (take < (byte_length_ - 3)) {
+ if (EqCase(next_byte_[take + 1], 'p') &&
+ NeqLetter(next_byte_[take + 2])) {
+ c = LF;
+ }
+ if (EqCase(next_byte_[take + 1], 'b') &&
+ EqCase(next_byte_[take + 2], 'r') &&
+ NeqLetter(next_byte_[take + 3])) {
+ c = LF;
+ }
+ if (EqCase(next_byte_[take + 1], 't') &&
+ EqCase(next_byte_[take + 2], 'r') &&
+ NeqLetter(next_byte_[take + 3])) {
+ c = LF;
+ }
+ }
+ // Begining of tag; skip to end and go around again
+ tlen = 1 + ScanToPossibleLetter(next_byte_ + take, byte_length_ - take,
+ exit_state_);
+ // Copy one byte, compressing spaces
+ if (!last_byte_was_space || !WS(c)) {
+ script_buffer_[put++] = c; // Advance dest
+ last_byte_was_space = WS(c);
+ }
+ } else if (c == '>') {
+ // Unexpected end of tag; copy it and go around again
+ tlen = 1; // Over the >
+ script_buffer_[put++] = c; // Advance dest
+ } else if (c == '&') {
+ // Expand entity, no advance
+ EntityToBuffer(next_byte_ + take, byte_length_ - take,
+ script_buffer_ + put, &tlen, &plen);
+ put += plen; // Advance dest
+ }
+ take += tlen; // Advance source
+ } else {
+ // Copy one byte, compressing spaces
+ if (!last_byte_was_space || !WS(c)) {
+ script_buffer_[put++] = c; // Advance dest
+ last_byte_was_space = WS(c);
+ }
+ ++take; // Advance source
+ }
+
+ if (WS(c) &&
+ (put >= put_soft_limit)) {
+ // Buffer is almost full
+ span->truncated = true;
+ break;
+ }
+ if (put >= kMaxScriptBytes) {
+ // Buffer is completely full
+ span->truncated = true;
+ break;
+ }
+ }
+
+ // Almost done. Back up to a character boundary if needed
+ while ((0 < take) && ((next_byte_[take] & 0xc0) == 0x80)) {
+ // Back up over continuation byte
+ --take;
+ --put;
+ }
+
+ // Update input position
+ next_byte_ += take;
+ byte_length_ -= take;
+
+ // Put four more spaces/NUL. Worst case is abcd _ _ _ \0
+ // kMaxScriptBytes | | put
+ script_buffer_[put + 0] = ' ';
+ script_buffer_[put + 1] = ' ';
+ script_buffer_[put + 2] = ' ';
+ script_buffer_[put + 3] = '\0';
+
+ span->text_bytes = put; // Does not include the last four chars above
+ return true;
+}
+
+
+// Copy next run of same-script non-tag letters to buffer [NUL terminated]
+// Buffer ALWAYS has leading space and trailing space space space NUL
+bool ScriptScanner::GetOneScriptSpan(LangSpan* span) {
+ if (!letters_marks_only_) {
+ // Return non-tag text, including punctuation and digits
+ return GetOneTextSpan(span);
+ }
+
+ span->text = script_buffer_;
+ span->text_bytes = 0;
+ span->offset = next_byte_ - start_byte_;
+ span->ulscript = UNKNOWN_ULSCRIPT;
+ span->lang = UNKNOWN_LANGUAGE;
+ span->truncated = false;
+
+ // struct timeval script_start, script_mid, script_end;
+
+ int put_soft_limit = kMaxScriptBytes - kWithinScriptTail;
+ if ((kMaxScriptBytes <= byte_length_) &&
+ (byte_length_ < (2 * kMaxScriptBytes))) {
+ // Try to split the last two fragments in half
+ put_soft_limit = byte_length_ / 2;
+ }
+
+
+ int spanscript; // The script of this span
+ int sc = UNKNOWN_ULSCRIPT; // The script of next character
+ int tlen = 0;
+ int plen = 0;
+
+ script_buffer_[0] = ' '; // Always a space at front of output
+ script_buffer_[1] = '\0';
+ int take = 0;
+ int put = 1; // Start after the initial space
+
+ // Build offsets from span->text back to start_byte_ + span->offset
+ // This mapping reflects deletion of non-letters, expansion of
+ // entities, etc.
+ map2original_.Clear();
+ map2original_.Delete(span->offset); // So that MapBack(0) gives offset
+
+ // Get to the first real non-tag letter or entity that is a letter
+ int skip = SkipToFrontOfSpan(next_byte_, byte_length_, &spanscript);
+ next_byte_ += skip;
+ byte_length_ -= skip;
+
+ if (skip != 1) {
+ map2original_.Delete(skip);
+ map2original_.Insert(1);
+ } else {
+ map2original_.Copy(1);
+ }
+ if (byte_length_ <= 0) {
+ map2original_.Reset();
+ return false; // No more letters to be found
+ }
+
+ // There is at least one letter, so we know the script for this span
+ span->ulscript = (ULScript)spanscript;
+
+
+ // Go over alternating spans of same-script letters and non-letters,
+ // copying letters to buffer with single spaces for each run of non-letters
+ while (take < byte_length_) {
+ // Copy run of letters in same script (&LS | LS)*
+ int letter_count = 0; // Keep track of word length
+ bool need_break = false;
+
+ while (take < byte_length_) {
+ // We are at a letter, nonletter, tag, or entity
+ if (IsSpecial(next_byte_[take]) && !is_plain_text_) {
+ if (next_byte_[take] == '<') {
+ // Begining of tag
+ sc = 0;
+ break;
+ } else if (next_byte_[take] == '>') {
+ // Unexpected end of tag
+ sc = 0;
+ break;
+ } else if (next_byte_[take] == '&') {
+ // Copy entity, no advance
+ EntityToBuffer(next_byte_ + take, byte_length_ - take,
+ script_buffer_ + put, &tlen, &plen);
+ sc = GetUTF8LetterScriptNum(script_buffer_ + put);
+ }
+ } else {
+ // Real letter, safely copy up to 4 bytes, increment by 1..4
+ // Will update by 1..4 bytes at Advance, below
+ tlen = plen = UTF8OneCharLen(next_byte_ + take);
+ if (take < (byte_length_ - 3)) {
+ // X86 fast case, does unaligned load/store
+ UNALIGNED_STORE32(script_buffer_ + put,
+ UNALIGNED_LOAD32(next_byte_ + take));
+
+ } else {
+ // Slow case, happens 1-3 times per input document
+ memcpy(script_buffer_ + put, next_byte_ + take, plen);
+ }
+ sc = GetUTF8LetterScriptNum(next_byte_ + take);
+ }
+
+ // Allow continue across a single letter in a different script:
+ // A B D = three scripts, c = common script, i = inherited script,
+ // - = don't care, ( = take position before the += below
+ // AAA(A- continue
+ //
+ // AAA(BA continue
+ // AAA(BB break
+ // AAA(Bc continue (breaks after B)
+ // AAA(BD break
+ // AAA(Bi break
+ //
+ // AAA(c- break
+ //
+ // AAA(i- continue
+ //
+
+ if ((sc != spanscript) && (sc != ULScript_Inherited)) {
+ // Might need to break this script span
+ if (sc == ULScript_Common) {
+ need_break = true;
+ } else {
+ // Look at next following character, ignoring entity as Common
+ int sc2 = GetUTF8LetterScriptNum(next_byte_ + take + tlen);
+ if ((sc2 != ULScript_Common) && (sc2 != spanscript)) {
+ // We found a non-trivial change of script
+ if (one_script_only_) {
+ need_break = true;
+ }
+ }
+ }
+ }
+ if (need_break) {break;} // Non-letter or letter in wrong script
+
+ take += tlen; // Advance
+ put += plen; // Advance
+
+ // Update the offset map to reflect take/put lengths
+ if (tlen == plen) {
+ map2original_.Copy(tlen);
+ } else if (tlen < plen) {
+ map2original_.Copy(tlen);
+ map2original_.Insert(plen - tlen);
+ } else { // plen < tlen
+ map2original_.Copy(plen);
+ map2original_.Delete(tlen - plen);
+ }
+
+ ++letter_count;
+ if (put >= kMaxScriptBytes) {
+ // Buffer is full
+ span->truncated = true;
+ break;
+ }
+ } // End while letters
+
+ // Do run of non-letters (tag | &NL | NL)*
+ while (take < byte_length_) {
+ // Do fast scan to next interesting byte
+ tlen = ScanToLetterOrSpecial(next_byte_ + take, byte_length_ - take);
+ take += tlen;
+ map2original_.Delete(tlen);
+ if (take >= byte_length_) {break;} // Might have scanned to end
+
+ // We are at a letter, nonletter, tag, or entity
+ if (IsSpecial(next_byte_[take]) && !is_plain_text_) {
+ if (next_byte_[take] == '<') {
+ // Begining of tag; skip to end and go around again
+ tlen = ScanToPossibleLetter(next_byte_ + take, byte_length_ - take,
+ exit_state_);
+ sc = 0;
+ } else if (next_byte_[take] == '>') {
+ // Unexpected end of tag; skip it and go around again
+ tlen = 1; // Over the >
+ sc = 0;
+ } else if (next_byte_[take] == '&') {
+ // Expand entity, no advance
+ EntityToBuffer(next_byte_ + take, byte_length_ - take,
+ script_buffer_ + put, &tlen, &plen);
+ sc = GetUTF8LetterScriptNum(script_buffer_ + put);
+ }
+ } else {
+ // Update 1..4
+ tlen = UTF8OneCharLen(next_byte_ + take);
+ sc = GetUTF8LetterScriptNum(next_byte_ + take);
+ }
+ if (sc != 0) {break;} // Letter found
+ take += tlen; // Else advance
+ map2original_.Delete(tlen);
+ } // End while not-letters
+
+ script_buffer_[put++] = ' ';
+ map2original_.Insert(1);
+
+ // Letter in wrong script ?
+ if ((sc != spanscript) && (sc != ULScript_Inherited)) {break;}
+ if (put >= put_soft_limit) {
+ // Buffer is almost full
+ span->truncated = true;
+ break;
+ }
+ }
+
+ // Almost done. Back up to a character boundary if needed
+ while ((0 < take) && (take < byte_length_) &&
+ ((next_byte_[take] & 0xc0) == 0x80)) {
+ // Back up over continuation byte
+ --take;
+ --put;
+ }
+
+ // Update input position
+ next_byte_ += take;
+ byte_length_ -= take;
+
+ // Put four more spaces/NUL. Worst case is abcd _ _ _ \0
+ // kMaxScriptBytes | | put
+ script_buffer_[put + 0] = ' ';
+ script_buffer_[put + 1] = ' ';
+ script_buffer_[put + 2] = ' ';
+ script_buffer_[put + 3] = '\0';
+ map2original_.Insert(4);
+ map2original_.Reset();
+
+ span->text_bytes = put; // Does not include the last four chars above
+ return true;
+}
+
+// Force Latin, Cyrillic, Armenian, Greek scripts to be lowercase
+// List changes with each version of Unicode, so just always lowercase
+// Unicode 6.2.0:
+// ARMENIAN COPTIC CYRILLIC DESERET GEORGIAN GLAGOLITIC GREEK LATIN
+void ScriptScanner::LowerScriptSpan(LangSpan* span) {
+ // If needed, lowercase all the text. If we do it sooner, might miss
+ // lowercasing an entity such as &Aacute;
+ // We only need to do this for Latn and Cyrl scripts
+ map2uplow_.Clear();
+ // Full Unicode lowercase of the entire buffer, including
+ // four pad bytes off the end.
+ // Ahhh. But the last byte 0x00 is not interchange-valid, so we do 3 pad
+ // bytes and put the 0x00 in explicitly.
+ // Build an offset map from script_buffer_lower_ back to script_buffer_
+ int consumed, filled, changed;
+ StringPiece istr(span->text, span->text_bytes + 3);
+ StringPiece ostr(script_buffer_lower_, kMaxScriptLowerBuffer);
+
+ UTF8GenericReplace(&utf8repl_lettermarklower_obj,
+ istr, ostr, is_plain_text_,
+ &consumed, &filled, &changed, &map2uplow_);
+ script_buffer_lower_[filled] = '\0';
+ span->text = script_buffer_lower_;
+ span->text_bytes = filled - 3;
+ map2uplow_.Reset();
+}
+
+// Copy next run of same-script non-tag letters to buffer [NUL terminated]
+// Force Latin, Cyrillic, Greek scripts to be lowercase
+// Buffer ALWAYS has leading space and trailing space space space NUL
+bool ScriptScanner::GetOneScriptSpanLower(LangSpan* span) {
+ bool ok = GetOneScriptSpan(span);
+ LowerScriptSpan(span);
+ return ok;
+}
+
+
+// Maps byte offset in most recent GetOneScriptSpan/Lower
+// span->text [0..text_bytes] into an additional byte offset from
+// span->offset, to get back to corresponding text in the original
+// input buffer.
+// text_offset must be the first byte
+// of a UTF-8 character, or just beyond the last character. Normally this
+// routine is called with the first byte of an interesting range and
+// again with the first byte of the following range.
+int ScriptScanner::MapBack(int text_offset) {
+ return map2original_.MapBack(map2uplow_.MapBack(text_offset));
+}
+
+
+// Gets lscript number for letters; always returns
+// 0 (common script) for non-letters
+int GetUTF8LetterScriptNum(const char* src) {
+ int srclen = UTF8OneCharLen(src);
+ const uint8* usrc = reinterpret_cast<const uint8*>(src);
+ return UTF8GenericPropertyTwoByte(&utf8prop_lettermarkscriptnum_obj,
+ &usrc, &srclen);
+}
+
+} // namespace CLD2
+
+
diff --git a/browser/components/translation/cld2/internal/getonescriptspan.h b/browser/components/translation/cld2/internal/getonescriptspan.h
new file mode 100644
index 000000000..a8999d069
--- /dev/null
+++ b/browser/components/translation/cld2/internal/getonescriptspan.h
@@ -0,0 +1,110 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_GETONESCRIPTSPAN_H_
+#define I18N_ENCODINGS_CLD2_INTERNAL_GETONESCRIPTSPAN_H_
+
+#include "integral_types.h"
+#include "langspan.h"
+#include "offsetmap.h"
+
+namespace CLD2 {
+
+static const int kMaxScriptBuffer = 40960;
+static const int kMaxScriptLowerBuffer = (kMaxScriptBuffer * 3) / 2;
+static const int kMaxScriptBytes = kMaxScriptBuffer - 32; // Leave some room
+static const int kWithinScriptTail = 32; // Stop at word space in last
+ // N bytes of script buffer
+
+
+static inline bool IsContinuationByte(char c) {
+ return static_cast<signed char>(c) < -64;
+}
+
+// Gets lscript number for letters; always returns
+// 0 (common script) for non-letters
+int GetUTF8LetterScriptNum(const char* src);
+
+// Update src pointer to point to next quadgram, +2..+5
+// Looks at src[0..4]
+const char* AdvanceQuad(const char* src);
+
+
+class ScriptScanner {
+ public:
+ ScriptScanner(const char* buffer, int buffer_length, bool is_plain_text);
+ ScriptScanner(const char* buffer, int buffer_length, bool is_plain_text,
+ bool any_text, bool any_script);
+ ~ScriptScanner();
+
+ // Copy next run of same-script non-tag letters to buffer [NUL terminated]
+ bool GetOneScriptSpan(LangSpan* span);
+
+ // Force Latin and Cyrillic scripts to be lowercase
+ void LowerScriptSpan(LangSpan* span);
+
+ // Copy next run of same-script non-tag letters to buffer [NUL terminated]
+ // Force Latin and Cyrillic scripts to be lowercase
+ bool GetOneScriptSpanLower(LangSpan* span);
+
+ // Copy next run of non-tag characters to buffer [NUL terminated]
+ // This just removes tags and removes entities
+ // Buffer has leading space
+ bool GetOneTextSpan(LangSpan* span);
+
+ // Maps byte offset in most recent GetOneScriptSpan/Lower
+ // span->text [0..text_bytes] into an additional byte offset from
+ // span->offset, to get back to corresponding text in the original
+ // input buffer.
+ // text_offset must be the first byte
+ // of a UTF-8 character, or just beyond the last character. Normally this
+ // routine is called with the first byte of an interesting range and
+ // again with the first byte of the following range.
+ int MapBack(int text_offset);
+
+ const char* GetBufferStart() {return start_byte_;};
+
+ private:
+ // Skip over tags and non-letters
+ int SkipToFrontOfSpan(const char* src, int len, int* script);
+
+ const char* start_byte_; // Starting byte of buffer to scan
+ const char* next_byte_; // First unscanned byte
+ const char* next_byte_limit_; // Last byte + 1
+ int byte_length_; // Bytes left: next_byte_limit_ - next_byte_
+
+ bool is_plain_text_; // true fo text, false for HTML
+ char* script_buffer_; // Holds text with expanded entities
+ char* script_buffer_lower_; // Holds lowercased text
+ bool letters_marks_only_; // To distinguish scriptspan of one
+ // letters/marks vs. any mixture of text
+ bool one_script_only_; // To distinguish scriptspan of one
+ // script vs. any mixture of scripts
+ int exit_state_; // For tag parser kTagParseTbl_0, based
+ // on letters_marks_only_
+ public :
+ // Expose for debugging
+ OffsetMap map2original_; // map from script_buffer_ to buffer
+ OffsetMap map2uplow_; // map from script_buffer_lower_ to script_buffer_
+};
+
+} // namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_GETONESCRIPTSPAN_H_
+
diff --git a/browser/components/translation/cld2/internal/integral_types.h b/browser/components/translation/cld2/internal/integral_types.h
new file mode 100644
index 000000000..1e63adb67
--- /dev/null
+++ b/browser/components/translation/cld2/internal/integral_types.h
@@ -0,0 +1,31 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+// Cheap version
+namespace CLD2 {
+
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef unsigned int uint32;
+typedef unsigned long long int uint64;
+
+typedef signed char int8;
+typedef signed short int16;
+typedef signed int int32;
+typedef signed long long int int64;
+
+typedef int32 char32;
+
+} // End namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/lang_script.cc b/browser/components/translation/cld2/internal/lang_script.cc
new file mode 100644
index 000000000..10fc4557e
--- /dev/null
+++ b/browser/components/translation/cld2/internal/lang_script.cc
@@ -0,0 +1,560 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// File: lang_script.cc
+// ================
+//
+// Author: dsites@google.com (Dick Sites)
+//
+// This file declares language and script numbers and names for CLD2
+//
+
+#include "lang_script.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "generated_language.h"
+#include "generated_ulscript.h"
+
+namespace CLD2 {
+
+// Language tables
+// Subscripted by enum Language
+extern const int kLanguageToNameSize;
+extern const char* const kLanguageToName[];
+extern const int kLanguageToCodeSize;
+extern const char* const kLanguageToCode[];
+extern const int kLanguageToCNameSize;
+extern const char* const kLanguageToCName[];
+extern const int kLanguageToScriptsSize;
+extern const FourScripts kLanguageToScripts[];
+
+// Subscripted by Language
+extern const int kLanguageToPLangSize;
+extern const uint8 kLanguageToPLang[];
+// Subscripted by per-script language
+extern const uint16 kPLangToLanguageLatn[];
+extern const uint16 kPLangToLanguageOthr[];
+
+// Alphabetical order for binary search
+extern const int kNameToLanguageSize;
+extern const CharIntPair kNameToLanguage[];
+extern const int kCodeToLanguageSize;
+extern const CharIntPair kCodeToLanguage[];
+
+// ULScript tables
+// Subscripted by enum ULScript
+extern const int kULScriptToNameSize;
+extern const char* const kULScriptToName[];
+extern const int kULScriptToCodeSize;
+extern const char* const kULScriptToCode[];
+extern const int kULScriptToCNameSize;
+extern const char* const kULScriptToCName[];
+extern const int kULScriptToRtypeSize;
+extern const ULScriptRType kULScriptToRtype[];
+extern const int kULScriptToDefaultLangSize;
+extern const Language kULScriptToDefaultLang[];
+
+// Alphabetical order for binary search
+extern const int kNameToULScriptSize;
+extern const CharIntPair kNameToULScript[];
+extern const int kCodeToULScriptSize;
+extern const CharIntPair kCodeToULScript[];
+
+
+//
+// File: lang_script.h
+// ================
+//
+// Author: dsites@google.com (Dick Sites)
+//
+// This file declares language and script numbers and names for CLD2
+//
+
+
+// NOTE: The script numbers and language numbers here are not guaranteed to be
+// stable. If you want to record a result for posterity, save the ISO codes
+// as character strings.
+//
+//
+// The Unicode scripts recognized by CLD2 are numbered almost arbitrarily,
+// specified in an enum. Each script has human-readable script name and a
+// 4-letter ISO 15924 script code. Each has a C name (largely for use by
+// programs that generate declarations in cld2_generated_scripts.h). Each
+// also has a recognition type
+// r_type: 0 script-only, 1 nilgrams, 2 quadgrams, 3 CJK
+//
+// The declarations for a particular version of Unicode are machine-generated in
+// cld2_generated_scripts.h
+//
+// This file includes that one and declares the access routines. The type
+// involved is called "ULScript" to signify Unicode Letters-Marks Scripts,
+// which are not quite Unicode Scripts. In particular, the CJK scripts are
+// merged into a single number because CLD2 recognizes the CJK languages from
+// four scripts intermixed: Hani (both Hans and Hant), Hangul, Hiragana, and
+// Katakana.
+
+// Each script has one of these four recognition types.
+// RTypeNone: There is no language associated with this script. In extended
+// language recognition calls, return a fake language number that maps to
+// xx-Cham, with literally "xx" for the language code,and with the script
+// code instead of "Cham". In non-extended calls, return UNKNOWN_LANGUAGE.
+// RTypeOne: The script maps 1:1 to a single language. No letters are examined
+// during recognition and no lookups done.
+// RTypeMany: The usual quadgram + delta-octagram + distinctive-words scoring
+// is done to determine the languages involved.
+// RTypeCJK: The CJK unigram + delta-bigram scoring is done to determine the
+// languages involved.
+//
+// Note that the choice of recognition type is a function of script, not
+// language. In particular, some languges are recognized in multiple scripts
+// and those have different recognition types (Mongolian mn-Latn vs. mn-Mong
+// for example).
+
+//----------------------------------------------------------------------------//
+// Functions of ULScript //
+//----------------------------------------------------------------------------//
+
+// If the input is out of range or otherwise unrecognized, it is treated
+// as UNKNOWN_ULSCRIPT (which never participates in language recognition)
+const char* ULScriptName(ULScript ulscript) {
+ int i_ulscript = ulscript;
+ if (i_ulscript < 0) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ if (i_ulscript >= NUM_ULSCRIPTS) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ return kULScriptToName[i_ulscript];
+}
+
+const char* ULScriptCode(ULScript ulscript) {
+ int i_ulscript = ulscript;
+ if (i_ulscript < 0) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ if (i_ulscript >= NUM_ULSCRIPTS) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ return kULScriptToCode[i_ulscript];
+}
+
+const char* ULScriptDeclaredName(ULScript ulscript) {
+ int i_ulscript = ulscript;
+ if (i_ulscript < 0) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ if (i_ulscript >= NUM_ULSCRIPTS) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ return kULScriptToCName[i_ulscript];
+}
+
+ULScriptRType ULScriptRecognitionType(ULScript ulscript) {
+ int i_ulscript = ulscript;
+ if (i_ulscript < 0) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ if (i_ulscript >= NUM_ULSCRIPTS) {i_ulscript = UNKNOWN_ULSCRIPT;}
+ return kULScriptToRtype[i_ulscript];
+}
+
+
+
+// The languages recognized by CLD2 are numbered almost arbitrarily,
+// specified in an enum. Each language has human-readable language name and a
+// 2- or 3-letter ISO 639 language code. Each has a C name (largely for use by
+// programs that generate declarations in cld2_generated_languagess.h).
+// Each has a list of up to four scripts in which it is currently recognized.
+//
+// The declarations for a particular set of recognized languages are
+// machine-generated in
+// cld2_generated_languages.h
+//
+// The Language enum is intended to match the internal Google Language enum
+// in i18n/languages/proto/languages.proto up to NUM_LANGUAGES, with additional
+// languages assigned above that. Over time, some languages may be renumbered
+// if they are moved into the Language enum.
+//
+// The Language enum includes the fake language numbers for RTypeNone above.
+//
+// In an open-source environment, the Google-specific Language enum is not
+// available. Language decouples the two environments while maintaining
+// internal compatibility.
+
+
+// If the input is out of range or otherwise unrecognized, it is treated
+// as UNKNOWN_LANGUAGE
+//
+// LanguageCode
+// ------------
+// Given the Language, return the language code, e.g. "ko"
+// This is determined by
+// the following (in order of preference):
+// - ISO-639-1 two-letter language code
+// (all except those mentioned below)
+// - ISO-639-2 three-letter bibliographic language code
+// (Tibetan, Dhivehi, Cherokee, Syriac)
+// - Google-specific language code
+// (ChineseT ("zh-TW"), Teragram Unknown, Unknown,
+// Portuguese-Portugal, Portuguese-Brazil, Limbu)
+// - Fake RTypeNone names.
+
+//----------------------------------------------------------------------------//
+// Functions of Language //
+//----------------------------------------------------------------------------//
+
+const char* LanguageName(Language lang) {
+ int i_lang = lang;
+ if (i_lang < 0) {i_lang = UNKNOWN_LANGUAGE;}
+ if (i_lang >= NUM_LANGUAGES) {i_lang = UNKNOWN_LANGUAGE;}
+ return kLanguageToName[i_lang];
+}
+const char* LanguageCode(Language lang) {
+ int i_lang = lang;
+ if (i_lang < 0) {i_lang = UNKNOWN_LANGUAGE;}
+ if (i_lang >= NUM_LANGUAGES) {i_lang = UNKNOWN_LANGUAGE;}
+ return kLanguageToCode[i_lang];
+}
+
+const char* LanguageDeclaredName(Language lang) {
+ int i_lang = lang;
+ if (i_lang < 0) {i_lang = UNKNOWN_LANGUAGE;}
+ if (i_lang >= NUM_LANGUAGES) {i_lang = UNKNOWN_LANGUAGE;}
+ return kLanguageToCName[i_lang];
+}
+
+// n is in 0..3. Trailing entries are filled with
+// UNKNOWN_LANGUAGE (which never participates in language recognition)
+ULScript LanguageRecognizedScript(Language lang, int n) {
+ int i_lang = lang;
+ if (i_lang < 0) {i_lang = UNKNOWN_LANGUAGE;}
+ if (i_lang >= NUM_LANGUAGES) {i_lang = UNKNOWN_LANGUAGE;}
+ return static_cast<ULScript>(kLanguageToScripts[i_lang][n]);
+}
+
+// Given the Language, returns its string name used as the output by
+// the lang/enc identifier, e.g. "Korean"
+// "invalid_language" if the input is invalid.
+// TG_UNKNOWN_LANGUAGE is used as a placeholder for the "ignore me" language,
+// used to subtract out HTML, link farms, DNA strings, and alittle English porn
+const char* ExtLanguageName(const Language lang) {
+ return LanguageName(lang);
+}
+
+// Given the Language, return the language code, e.g. "ko"
+const char* ExtLanguageCode(const Language lang) {
+ return LanguageCode(lang);
+}
+
+
+// Given the Language, returns its Language enum spelling, for use by
+// programs that create C declarations, e.g. "KOREAN"
+// "UNKNOWN_LANGUAGE" if the input is invalid.
+const char* ExtLanguageDeclaredName(const Language lang) {
+ return LanguageDeclaredName(lang);
+}
+
+
+extern const int kCloseSetSize = 10;
+
+// Returns which set of statistically-close languages lang is in. 0 means none.
+int LanguageCloseSet(Language lang) {
+ // Scaffolding
+ // id ms # INDONESIAN MALAY coef=0.4698 Problematic w/o extra words
+ // bo dz # TIBETAN DZONGKHA coef=0.4571
+ // cs sk # CZECH SLOVAK coef=0.4273
+ // zu xh # ZULU XHOSA coef=0.3716
+ //
+ // bs hr sr srm # BOSNIAN CROATIAN SERBIAN MONTENEGRIN
+ // hi mr bh ne # HINDI MARATHI BIHARI NEPALI
+ // no nn da # NORWEGIAN NORWEGIAN_N DANISH
+ // gl es pt # GALICIAN SPANISH PORTUGUESE
+ // rw rn # KINYARWANDA RUNDI
+
+ if (lang == INDONESIAN) {return 1;}
+ if (lang == MALAY) {return 1;}
+
+ if (lang == TIBETAN) {return 2;}
+ if (lang == DZONGKHA) {return 2;}
+
+ if (lang == CZECH) {return 3;}
+ if (lang == SLOVAK) {return 3;}
+
+ if (lang == ZULU) {return 4;}
+ if (lang == XHOSA) {return 4;}
+
+ if (lang == BOSNIAN) {return 5;}
+ if (lang == CROATIAN) {return 5;}
+ if (lang == SERBIAN) {return 5;}
+ if (lang == MONTENEGRIN) {return 5;}
+
+ if (lang == HINDI) {return 6;}
+ if (lang == MARATHI) {return 6;}
+ if (lang == BIHARI) {return 6;}
+ if (lang == NEPALI) {return 6;}
+
+ if (lang == NORWEGIAN) {return 7;}
+ if (lang == NORWEGIAN_N) {return 7;}
+ if (lang == DANISH) {return 7;}
+
+ if (lang == GALICIAN) {return 8;}
+ if (lang == SPANISH) {return 8;}
+ if (lang == PORTUGUESE) {return 8;}
+
+ if (lang == KINYARWANDA) {return 9;}
+ if (lang == RUNDI) {return 9;}
+
+ return 0;
+}
+
+//----------------------------------------------------------------------------//
+// Functions of ULScript and Language //
+//----------------------------------------------------------------------------//
+
+Language DefaultLanguage(ULScript ulscript) {
+ if (ulscript < 0) {return UNKNOWN_LANGUAGE;}
+ if (ulscript >= NUM_ULSCRIPTS) {return UNKNOWN_LANGUAGE;}
+ return kULScriptToDefaultLang[ulscript];
+}
+
+uint8 PerScriptNumber(ULScript ulscript, Language lang) {
+ if (ulscript < 0) {return 0;}
+ if (ulscript >= NUM_ULSCRIPTS) {return 0;}
+ if (kULScriptToRtype[ulscript] == RTypeNone) {return 1;}
+ if (lang >= kLanguageToPLangSize) {return 0;}
+ return kLanguageToPLang[lang];
+}
+
+Language FromPerScriptNumber(ULScript ulscript, uint8 perscript_number) {
+ if (ulscript < 0) {return UNKNOWN_LANGUAGE;}
+ if (ulscript >= NUM_ULSCRIPTS) {return UNKNOWN_LANGUAGE;}
+ if ((kULScriptToRtype[ulscript] == RTypeNone) ||
+ (kULScriptToRtype[ulscript] == RTypeOne)) {
+ return kULScriptToDefaultLang[ulscript];
+ }
+
+ if (ulscript == ULScript_Latin) {
+ return static_cast<Language>(kPLangToLanguageLatn[perscript_number]);
+ } else {
+ return static_cast<Language>(kPLangToLanguageOthr[perscript_number]);
+ }
+}
+
+// Return true if language can be in the Latin script
+bool IsLatnLanguage(Language lang) {
+ if (lang >= kLanguageToPLangSize) {return false;}
+ return (lang == kPLangToLanguageLatn[kLanguageToPLang[lang]]);
+}
+
+// Return true if language can be in a non-Latin script
+bool IsOthrLanguage(Language lang) {
+ if (lang >= kLanguageToPLangSize) {return false;}
+ return (lang == kPLangToLanguageOthr[kLanguageToPLang[lang]]);
+}
+
+
+//----------------------------------------------------------------------------//
+// Other //
+//----------------------------------------------------------------------------//
+
+// Returns mid if key found in lo <= mid < hi, else -1
+int BinarySearch(const char* key, int lo, int hi, const CharIntPair* cipair) {
+ // binary search
+ while (lo < hi) {
+ int mid = (lo + hi) >> 1;
+ if (strcmp(key, cipair[mid].s) < 0) {
+ hi = mid;
+ } else if (strcmp(key, cipair[mid].s) > 0) {
+ lo = mid + 1;
+ } else {
+ return mid;
+ }
+ }
+ return -1;
+}
+
+Language MakeLang(int i) {return static_cast<Language>(i);}
+
+// Name can be either full name or ISO code, or can be ISO code embedded in
+// a language-script combination such as "ABKHAZIAN", "en", "en-Latn-GB"
+Language GetLanguageFromName(const char* src) {
+ const char* hyphen1 = strchr(src, '-');
+ const char* hyphen2 = NULL;
+ if (hyphen1 != NULL) {hyphen2 = strchr(hyphen1 + 1, '-');}
+
+ int match = -1;
+ if (hyphen1 == NULL) {
+ // Bare name. Look at full name, then code
+ match = BinarySearch(src, 0, kNameToLanguageSize, kNameToLanguage);
+ if (match >= 0) {return MakeLang(kNameToLanguage[match].i);} // aa
+ match = BinarySearch(src, 0, kCodeToLanguageSize, kCodeToLanguage);
+ if (match >= 0) {return MakeLang(kCodeToLanguage[match].i);} // aa
+ return UNKNOWN_LANGUAGE;
+ }
+
+ if (hyphen2 == NULL) {
+ // aa-bb. Not a full name; must be code-something. Try zh-TW then bare zh
+ match = BinarySearch(src, 0, kCodeToLanguageSize, kCodeToLanguage);
+ if (match >= 0) {return MakeLang(kCodeToLanguage[match].i);} // aa-bb
+
+ int len = strlen(src);
+ if (len >= 16) {return UNKNOWN_LANGUAGE;} // Real codes are shorter
+
+ char temp[16];
+ int hyphen1_offset = hyphen1 - src;
+ // Take off part after hyphen1
+ memcpy(temp, src, len);
+ temp[hyphen1_offset] = '\0';
+ match = BinarySearch(temp, 0, kCodeToLanguageSize, kCodeToLanguage);
+ if (match >= 0) {return MakeLang(kCodeToLanguage[match].i);} // aa
+
+ return UNKNOWN_LANGUAGE;
+ }
+
+ // aa-bb-cc. Must be code-something. Try en-Latn-US, en-Latn, en-US, en
+ match = BinarySearch(src, 0, kCodeToLanguageSize, kCodeToLanguage);
+ if (match >= 0) {return MakeLang(kCodeToLanguage[match].i);} // aa-bb-cc
+
+
+ int len = strlen(src);
+ if (len >= 16) {return UNKNOWN_LANGUAGE;} // Real codes are shorter
+
+ char temp[16];
+ int hyphen1_offset = hyphen1 - src;
+ int hyphen2_offset = hyphen2 - src;
+ // Take off part after hyphen2
+ memcpy(temp, src, len);
+ temp[hyphen2_offset] = '\0';
+ match = BinarySearch(temp, 0, kCodeToLanguageSize, kCodeToLanguage);
+ if (match >= 0) {return MakeLang(kCodeToLanguage[match].i);} // aa-bb
+
+
+ // Take off part between hyphen1 and hyphen2
+ int len2 = len - hyphen2_offset;
+ memcpy(temp, src, len);
+ memcpy(&temp[hyphen1_offset], hyphen2, len2);
+ temp[hyphen1_offset + len2] = '\0';
+ match = BinarySearch(temp, 0, kCodeToLanguageSize, kCodeToLanguage);
+ if (match >= 0) {return MakeLang(kCodeToLanguage[match].i);} // aa-cc
+
+
+ // Take off everything after hyphen1
+ memcpy(temp, src, len);
+ temp[hyphen1_offset] = '\0';
+ match = BinarySearch(temp, 0, kCodeToLanguageSize, kCodeToLanguage);
+ if (match >= 0) {return MakeLang(kCodeToLanguage[match].i);} // aa
+
+
+ return UNKNOWN_LANGUAGE;
+}
+
+
+// Name can be either full name or ISO code, or can be ISO code embedded in
+// a language-script combination such as "en-Latn-GB"
+// MORE WORK to do here. also kLanguageToScripts [4] is bogus
+// if bare language name, no script, want zh, ja, ko to Hani, pt to Latn, etc.
+// Something like map code to Language, then Language to kLanguageToScripts[x][0]
+// ADD BIAS: kLanguageToScripts lists default script first
+// If total mismatch, reutrn Latn
+// if (strcmp(src, "nd") == 0) {return NDEBELE;} // [nd was wrong]
+// if (strcmp(src, "sit-NP-Limb") == 0) {return ULScript_Limbu;}
+
+ULScript MakeULScr(int i) {return static_cast<ULScript>(i);}
+
+ULScript GetULScriptFromName(const char* src) {
+ const char* hyphen1 = strchr(src, '-');
+ const char* hyphen2 = NULL;
+ if (hyphen1 != NULL) {hyphen2 = strchr(hyphen1 + 1, '-');}
+
+ int match = -1;
+ if (hyphen1 == NULL) {
+ // Bare name. Look at full name, then code, then try backmapping as Language
+ match = BinarySearch(src, 0, kNameToULScriptSize, kNameToULScript);
+ if (match >= 0) {return MakeULScr(kNameToULScript[match].i);} // aa
+ match = BinarySearch(src, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // aa
+
+ Language backmap_me = GetLanguageFromName(src);
+ if (backmap_me != UNKNOWN_LANGUAGE) {
+ return static_cast<ULScript>(kLanguageToScripts[backmap_me][0]);
+ }
+ return ULScript_Latin;
+ }
+
+ if (hyphen2 == NULL) {
+ // aa-bb. Not a full name; must be code-something. Try en-Latn, bare Latn
+ if (strcmp(src, "zh-TW") == 0) {return ULScript_Hani;}
+ if (strcmp(src, "zh-CN") == 0) {return ULScript_Hani;}
+ if (strcmp(src, "sit-NP") == 0) {return ULScript_Limbu;}
+ if (strcmp(src, "sit-Limb") == 0) {return ULScript_Limbu;}
+ if (strcmp(src, "sr-ME") == 0) {return ULScript_Latin;}
+ match = BinarySearch(src, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // aa-bb
+
+ int len = strlen(src);
+ if (len >= 16) {return ULScript_Latin;} // Real codes are shorter
+
+ char temp[16];
+ int hyphen1_offset = hyphen1 - src;
+ int len1 = len - hyphen1_offset - 1; // Exclude the hyphen
+ // Take off part before hyphen1
+ memcpy(temp, hyphen1 + 1, len1);
+ temp[len1] = '\0';
+ match = BinarySearch(temp, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // bb
+
+ // Take off part after hyphen1
+ memcpy(temp, src, len);
+ temp[hyphen1_offset] = '\0';
+ match = BinarySearch(temp, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // aa
+
+ return ULScript_Latin;
+ }
+
+ // aa-bb-cc. Must be code-something. Try en-Latn-US, en-Latn, en-US, en
+ if (strcmp(src, "sit-NP-Limb") == 0) {return ULScript_Limbu;}
+ if (strcmp(src, "sr-ME-Latn") == 0) {return ULScript_Latin;}
+ if (strcmp(src, "sr-ME-Cyrl") == 0) {return ULScript_Cyrillic;}
+ match = BinarySearch(src, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // aa-bb-cc
+
+ int len = strlen(src);
+ if (len >= 16) {return ULScript_Latin;} // Real codes are shorter
+
+ char temp[16];
+ int hyphen1_offset = hyphen1 - src;
+ int hyphen2_offset = hyphen2 - src;
+ int len2 = len - hyphen2_offset - 1; // Exclude the hyphen
+ int lenmid = hyphen2_offset - hyphen1_offset - 1; // Exclude the hyphen
+ // Keep part between hyphen1 and hyphen2
+ memcpy(temp, hyphen1 + 1, lenmid);
+ temp[lenmid] = '\0';
+ match = BinarySearch(temp, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // bb
+
+ // Keep part after hyphen2
+ memcpy(temp, hyphen2 + 1, len2);
+ temp[len2] = '\0';
+ match = BinarySearch(temp, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // cc
+
+ // Keep part before hyphen1
+ memcpy(temp, src, len);
+ temp[hyphen1_offset] = '\0';
+ match = BinarySearch(temp, 0, kCodeToULScriptSize, kCodeToULScript);
+ if (match >= 0) {return MakeULScr(kCodeToULScript[match].i);} // aa
+
+ return ULScript_Latin;
+}
+
+// Map script into Latin, Cyrillic, Arabic, Other
+int LScript4(ULScript ulscript) {
+ if (ulscript == ULScript_Latin) {return 0;}
+ if (ulscript == ULScript_Cyrillic) {return 1;}
+ if (ulscript == ULScript_Arabic) {return 2;}
+ return 3;
+}
+
+} // namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/lang_script.h b/browser/components/translation/cld2/internal/lang_script.h
new file mode 100644
index 000000000..9311707e4
--- /dev/null
+++ b/browser/components/translation/cld2/internal/lang_script.h
@@ -0,0 +1,187 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// File: lang_script.h
+// ================
+//
+// Author: dsites@google.com (Dick Sites)
+//
+// This file declares language and script numbers and names for CLD2,
+// plus routines that access side tables based on these
+//
+
+#ifndef I18N_ENCODINGS_CLD2_LANG_SCRIPT_H__
+#define I18N_ENCODINGS_CLD2_LANG_SCRIPT_H__
+
+#include "generated_language.h"
+#include "generated_ulscript.h"
+#include "integral_types.h"
+
+
+// NOTE: The script numbers and language numbers here are not guaranteed to be
+// stable. If you want to record a result for posterity, save the
+// ULScriptCode(ULScript ulscript) result as character strings.
+//
+// The Unicode scripts recognized by CLD2 are numbered almost arbitrarily,
+// specified in an enum. Each script has human-readable script name and a
+// 4-letter ISO 15924 script code. Each has a C name (largely for use by
+// programs that generate declarations in cld2_generated_scripts.h). Each
+// also has a recognition type
+// r_type: 0 script-only, 1 nilgrams, 2 quadgrams, 3 CJK
+//
+// The declarations for a particular version of Unicode are machine-generated in
+// generated_scripts.h
+//
+// This file includes that one and declares the access routines. The type
+// involved is called "ULScript" to signify Unicode Letters-Marks Scripts,
+// which are not quite Unicode Scripts. In particular, the CJK scripts are
+// merged into a single number because CLD2 recognizes the CJK languages from
+// four scripts intermixed: Hani (both Hans and Hant), Hangul, Hiragana, and
+// Katakana.
+
+// Each script has one of these four recognition types.
+// RTypeNone: There is no language associated with this script. In extended
+// language recognition calls, return a fake language number that maps to
+// xx-Cham, with literally "xx" for the language code,and with the script
+// code instead of "Cham". In non-extended calls, return UNKNOWN_LANGUAGE.
+// RTypeOne: The script maps 1:1 to a single language. No letters are examined
+// during recognition and no lookups done.
+// RTypeMany: The usual quadgram + delta-octagram + distinctive-words scoring
+// is done to determine the languages involved.
+// RTypeCJK: The CJK unigram + delta-bigram scoring is done to determine the
+// languages involved.
+//
+// Note that the choice of recognition type is a function of script, not
+// language. In particular, some languges are recognized in multiple scripts
+// and those have different recognition types (Mongolian mn-Latn vs. mn-Mong
+// for example).
+
+namespace CLD2 {
+
+//----------------------------------------------------------------------------//
+// Functions of ULScript //
+//----------------------------------------------------------------------------//
+
+// If the input is out of range or otherwise unrecognized, it is treated
+// as ULScript_Common (which never participates in language recognition)
+const char* ULScriptName(ULScript ulscript);
+const char* ULScriptCode(ULScript ulscript);
+const char* ULScriptDeclaredName(ULScript ulscript);
+ULScriptRType ULScriptRecognitionType(ULScript ulscript);
+
+// Name can be either full name or ISO code, or can be ISO code embedded in
+// a language-script combination such as "en-Latn-GB"
+ULScript GetULScriptFromName(const char* src);
+
+// Map script into Latin, Cyrillic, Arabic, Other
+int LScript4(ULScript ulscript);
+
+//----------------------------------------------------------------------------//
+// Functions of Language //
+//----------------------------------------------------------------------------//
+
+// The languages recognized by CLD2 are numbered almost arbitrarily,
+// specified in an enum. Each language has human-readable language name and a
+// 2- or 3-letter ISO 639 language code. Each has a C name (largely for use by
+// programs that generate declarations in cld2_generated_languagess.h).
+// Each has a list of up to four scripts in which it is currently recognized.
+//
+// The declarations for a particular set of recognized languages are
+// machine-generated in
+// generated_languages.h
+//
+// The Language enum is intended to match the internal Google Language enum
+// in i18n/languages/proto/languages.proto up to NUM_LANGUAGES, with additional
+// languages assigned above that. Over time, some languages may be renumbered
+// if they are moved into the Language enum.
+//
+// The Language enum includes the fake language numbers for RTypeNone above.
+//
+
+
+// If the input is out of range or otherwise unrecognized, it is treated
+// as UNKNOWN_LANGUAGE
+//
+// LanguageCode
+// ------------
+// Given the Language, return the language code, e.g. "ko"
+// This is determined by
+// the following (in order of preference):
+// - ISO-639-1 two-letter language code
+// (all except those mentioned below)
+// - ISO-639-2 three-letter bibliographic language code
+// (Tibetan, Dhivehi, Cherokee, Syriac)
+// - Google-specific language code
+// (ChineseT ("zh-TW"), Teragram Unknown, Unknown,
+// Portuguese-Portugal, Portuguese-Brazil, Limbu)
+// - Fake RTypeNone names.
+
+const char* LanguageName(Language lang);
+const char* LanguageCode(Language lang);
+const char* LanguageShortCode(Language lang);
+const char* LanguageDeclaredName(Language lang);
+
+// n is in 0..3. Trailing entries are filled with
+// ULScript_Common (which never participates in language recognition)
+ULScript LanguageRecognizedScript(Language lang, int n);
+
+// Name can be either full name or ISO code, or can be ISO code embedded in
+// a language-script combination such as "en-Latn-GB"
+Language GetLanguageFromName(const char* src);
+
+// Returns which set of statistically-close languages lang is in. 0 means none.
+int LanguageCloseSet(Language lang);
+
+//----------------------------------------------------------------------------//
+// Functions of ULScript and Language //
+//----------------------------------------------------------------------------//
+
+// Most common language in each script
+Language DefaultLanguage(ULScript ulscript);
+
+// For RTypeMany recognition,
+// the CLD2 lookup tables are kept small by encoding a language into one byte.
+// To avoid limiting CLD2 to at most 256 languages, a larger range of external
+// Language numbers is mapped to a smaller range of per-script numbers. At
+// the moment (January 2013) the Latin script has about 90 languages to be
+// recognized, while all the other scripts total about 50 more languages. In
+// addition, the RTypeNone scripts map to about 100 fake languages.
+// So we map all Latin-script languages to one range of 1..255 per-script
+// numbers and map all the other RTypeMany languages to an overlapping range
+// 1..255 of per-script numbers.
+
+uint8 PerScriptNumber(ULScript ulscript, Language lang);
+Language FromPerScriptNumber(ULScript ulscript, uint8 perscript_number);
+
+// While the speed-sensitive processing deals with per-script language numbers,
+// there is a need for low-performance dealing with original language numbers
+// and unknown scripts, mostly for processing language hints.
+// These routines let one derive a script class from a bare language.
+// For languages written in multiple scripts, both of these can return true.
+
+bool IsLatnLanguage(Language lang);
+bool IsOthrLanguage(Language lang);
+
+
+//----------------------------------------------------------------------------//
+// Other //
+//----------------------------------------------------------------------------//
+
+// Utility routine to search alphabetical tables
+int BinarySearch(const char* key, int lo, int hi, const CharIntPair* cipair);
+
+} // namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_LANG_SCRIPT_H__
diff --git a/browser/components/translation/cld2/internal/langspan.h b/browser/components/translation/cld2/internal/langspan.h
new file mode 100644
index 000000000..be2889038
--- /dev/null
+++ b/browser/components/translation/cld2/internal/langspan.h
@@ -0,0 +1,40 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_LANGSPAN_H_
+#define I18N_ENCODINGS_CLD2_INTERNAL_LANGSPAN_H_
+
+#include "generated_language.h"
+#include "generated_ulscript.h"
+
+namespace CLD2 {
+
+typedef struct {
+ char* text; // Pointer to the span, somewhere
+ int text_bytes; // Number of bytes of text in the span
+ int offset; // Offset of start of span in original input buffer
+ ULScript ulscript; // Unicode Letters Script of this span
+ Language lang; // Language identified for this span
+ bool truncated; // true if buffer filled up before a
+ // different script or EOF was found
+} LangSpan;
+
+} // namespace CLD2
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_LANGSPAN_H_
+
diff --git a/browser/components/translation/cld2/internal/offsetmap.cc b/browser/components/translation/cld2/internal/offsetmap.cc
new file mode 100644
index 000000000..84609a71f
--- /dev/null
+++ b/browser/components/translation/cld2/internal/offsetmap.cc
@@ -0,0 +1,569 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+//
+
+#include "offsetmap.h"
+
+#include <string.h> // for strcmp
+#include <stdio.h> // for fprintf, stderr, fclose, etc
+#include <algorithm> // for min
+
+using namespace std;
+
+namespace CLD2 {
+
+// Constructor, destructor
+OffsetMap::OffsetMap() {
+ Clear();
+}
+
+OffsetMap::~OffsetMap() {
+}
+
+// Clear the map
+// After:
+// next_diff_sub_ is 0
+// Windows are the a and a' ranges covered by diffs_[next_diff_sub_-1]
+// which is a fake range of width 0 mapping 0=>0
+void OffsetMap::Clear() {
+ diffs_.clear();
+ pending_op_ = COPY_OP;
+ pending_length_ = 0;
+ next_diff_sub_ = 0;
+ current_lo_aoffset_ = 0;
+ current_hi_aoffset_ = 0;
+ current_lo_aprimeoffset_ = 0;
+ current_hi_aprimeoffset_ = 0;
+ current_diff_ = 0;
+ max_aoffset_ = 0; // Largest seen so far
+ max_aprimeoffset_ = 0; // Largest seen so far
+}
+
+static inline char OpPart(const char c) {
+ return (c >> 6) & 3;
+}
+static inline char LenPart(const char c) {
+ return c & 0x3f;
+}
+
+// Print map to file, for debugging
+void OffsetMap::Printmap(const char* filename) {
+ FILE* fout;
+ bool needs_close = false;
+ if (strcmp(filename, "stdout") == 0) {
+ fout = stdout;
+ } else if (strcmp(filename, "stderr") == 0) {
+ fout = stderr;
+ } else {
+ fout = fopen(filename, "w");
+ needs_close = true;
+ }
+ if (fout == NULL) {
+ fprintf(stderr, "%s did not open\n", filename);
+ return;
+ }
+
+ Flush(); // Make sure any pending entry gets printed
+ fprintf(fout, "Offsetmap: %ld bytes\n", diffs_.size());
+ for (int i = 0; i < static_cast<int>(diffs_.size()); ++i) {
+ fprintf(fout, "%c%02d ", "&=+-"[OpPart(diffs_[i])], LenPart(diffs_[i]));
+ if ((i % 20) == 19) {fprintf(fout, "\n");}
+ }
+ fprintf(fout, "\n");
+ if (needs_close) {
+ fclose(fout);
+ }
+}
+
+// Reset to offset 0
+void OffsetMap::Reset() {
+ MaybeFlushAll();
+
+ next_diff_sub_ = 0;
+ current_lo_aoffset_ = 0;
+ current_hi_aoffset_ = 0;
+ current_lo_aprimeoffset_ = 0;
+ current_hi_aprimeoffset_ = 0;
+ current_diff_ = 0;
+}
+
+// Add to mapping from A to A', specifying how many next bytes are
+// identical in A and A'
+void OffsetMap::Copy(int bytes) {
+ if (bytes == 0) {return;}
+ max_aoffset_ += bytes; // Largest seen so far
+ max_aprimeoffset_ += bytes; // Largest seen so far
+ if (pending_op_ == COPY_OP) {
+ pending_length_ += bytes;
+ } else {
+ Flush();
+ pending_op_ = COPY_OP;
+ pending_length_ = bytes;
+ }
+}
+
+// Add to mapping from A to A', specifying how many next bytes are
+// inserted in A' while not advancing in A at all
+void OffsetMap::Insert(int bytes){
+ if (bytes == 0) {return;}
+ max_aprimeoffset_ += bytes; // Largest seen so far
+ if (pending_op_ == INSERT_OP) {
+ pending_length_ += bytes;
+ } else if ((bytes == 1) &&
+ (pending_op_ == DELETE_OP) && (pending_length_ == 1)) {
+ // Special-case exactly delete(1) insert(1) +> copy(1);
+ // all others backmap inserts to after deletes
+ pending_op_ = COPY_OP;
+ } else {
+ Flush();
+ pending_op_ = INSERT_OP;
+ pending_length_ = bytes;
+ }
+}
+
+// Add to mapping from A to A', specifying how many next bytes are
+// deleted from A while not advancing in A' at all
+void OffsetMap::Delete(int bytes){
+ if (bytes == 0) {return;}
+ max_aoffset_ += bytes; // Largest seen so far
+ if (pending_op_ == DELETE_OP) {
+ pending_length_ += bytes;
+ } else if ((bytes == 1) &&
+ (pending_op_ == INSERT_OP) && (pending_length_ == 1)) {
+ // Special-case exactly insert(1) delete(1) => copy(1);
+ // all others backmap deletes to after insertss
+ pending_op_ = COPY_OP;
+ } else {
+ Flush();
+ pending_op_ = DELETE_OP;
+ pending_length_ = bytes;
+ }
+}
+
+void OffsetMap::Flush() {
+ if (pending_length_ == 0) {
+ return;
+ }
+ // We may be emitting a copy op just after a copy op because +1 -1 cancelled
+ // inbetween. If the lengths don't need a prefix byte, combine them
+ if ((pending_op_ == COPY_OP) && !diffs_.empty()) {
+ char c = diffs_[diffs_.size() - 1];
+ MapOp prior_op = static_cast<MapOp>(OpPart(c));
+ int prior_len = LenPart(c);
+ if ((prior_op == COPY_OP) && ((prior_len + pending_length_) <= 0x3f)) {
+ diffs_[diffs_.size() - 1] += pending_length_;
+ pending_length_ = 0;
+ return;
+ }
+ }
+ if (pending_length_ > 0x3f) {
+ bool non_zero_emitted = false;
+ for (int shift = 30; shift > 0; shift -= 6) {
+ int prefix = (pending_length_ >> shift) & 0x3f;
+ if ((prefix > 0) || non_zero_emitted) {
+ Emit(PREFIX_OP, prefix);
+ non_zero_emitted = true;
+ }
+ }
+ }
+ Emit(pending_op_, pending_length_ & 0x3f);
+ pending_length_ = 0;
+}
+
+
+// Add one more entry to copy one byte off the end, then flush
+void OffsetMap::FlushAll() {
+ Copy(1);
+ Flush();
+}
+
+// Flush all if necessary
+void OffsetMap::MaybeFlushAll() {
+ if ((0 < pending_length_) || diffs_.empty()) {
+ FlushAll();
+ }
+}
+
+// Len may be 0, for example as the low piece of length=64
+void OffsetMap::Emit(MapOp op, int len) {
+ char c = (static_cast<char>(op) << 6) | (len & 0x3f);
+ diffs_.push_back(c);
+}
+
+void OffsetMap::DumpString() {
+ for (int i = 0; i < static_cast<int>(diffs_.size()); ++i) {
+ fprintf(stderr, "%c%02d ", "&=+-"[OpPart(diffs_[i])], LenPart(diffs_[i]));
+ }
+ fprintf(stderr, "\n");
+
+ // Print running table of correspondences
+ fprintf(stderr, " op A => A' (A forward-maps to A')\n");
+ int aoffset = 0;
+ int aprimeoffset = 0;
+ int length = 0;
+ for (int i = 0; i < static_cast<int>(diffs_.size()); ++i) {
+ char c = diffs_[i];
+ MapOp op = static_cast<MapOp>(OpPart(c));
+ int len = LenPart(c);
+ length = (length << 6) + len;
+ if (op == COPY_OP) {
+ aoffset += length;
+ aprimeoffset += length;
+ length = 0;
+ } else if (op == INSERT_OP) {
+ aoffset += 0;
+ aprimeoffset += length;
+ length = 0;
+ } else if (op == DELETE_OP) {
+ aoffset += length;
+ aprimeoffset += 0;
+ length = 0;
+ } else { // (op == PREFIX_OP)
+ // Do nothing else
+ }
+ fprintf(stderr, "[%3d] %c%02d %6d %6d%s\n",
+ i, "&=+-"[op], len,
+ aoffset, aprimeoffset,
+ (next_diff_sub_ == i) ? " <==next_diff_sub_" : "");
+
+ }
+ fprintf(stderr, "\n");
+}
+
+void OffsetMap::DumpWindow() {
+ fprintf(stderr, "DumpWindow(A => A'): max_aoffset_ = %d, "
+ "max_aprimeoffset_ = %d, next_diff_sub_ = %d<br>\n",
+ max_aoffset_, max_aprimeoffset_, next_diff_sub_);
+ fprintf(stderr, "A [%u..%u)\n",
+ current_lo_aoffset_, current_hi_aoffset_);
+ fprintf(stderr, "A' [%u..%u)\n",
+ current_lo_aprimeoffset_, current_hi_aprimeoffset_);
+ fprintf(stderr, " diff = %d\n", current_diff_);
+ DumpString();
+}
+
+//----------------------------------------------------------------------------//
+// The guts of the 2013 design //
+// If there are three ranges a b c in diffs_, we can be in one of five //
+// states: LEFT of a, in ranges a b c, or RIGHT of c //
+// In each state, there are windows A[Alo..Ahi), A'[A'lo..A'hi) and diffs_ //
+// position next_diff_sub_ //
+// There also are mapping constants max_aoffset_ and max_aprimeoffset_ //
+// If LEFT, Alo=Ahi=0, A'lo=A'hi=0 and next_diff_sub_=0 //
+// If RIGHT, Alo=Ahi=max_aoffset_, A'lo=A'hi=max_aprimeoffset_ and //
+// next_diff_sub_=diffs_.size() //
+// Otherwise, at least one of A[) and A'[) is non-empty and the first bytes //
+// correspond to each other. If range i is active, next_diff_sub_ is at //
+// the first byte of range i+1. Because of the length-prefix operator, //
+// an individual range item in diffs_ may be multiple bytes //
+// In all cases aprimeoffset = aoffset + current_diff_ //
+// i.e. current_diff_ = aprimeoffset - aoffset //
+// //
+// In the degenerate case of diffs_.empty(), there are only two states //
+// LEFT and RIGHT and the mapping is the identity mapping. //
+// The initial state is LEFT. //
+// It is an error to move left into LEFT or right into RIGHT, but the code //
+// below is robust in these cases. //
+//----------------------------------------------------------------------------//
+
+void OffsetMap::SetLeft() {
+ current_lo_aoffset_ = 0;
+ current_hi_aoffset_ = 0;
+ current_lo_aprimeoffset_ = 0;
+ current_hi_aprimeoffset_ = 0;
+ current_diff_ = 0;
+ next_diff_sub_ = 0;
+}
+
+void OffsetMap::SetRight() {
+ current_lo_aoffset_ = max_aoffset_;
+ current_hi_aoffset_ = max_aoffset_;
+ current_lo_aprimeoffset_ = max_aprimeoffset_;
+ current_hi_aprimeoffset_ = max_aprimeoffset_;
+ current_diff_ = max_aprimeoffset_ - max_aoffset_;
+ next_diff_sub_ = 0;
+}
+
+// Back up over previous range, 1..5 bytes
+// Return subscript at the beginning of that. Pins at 0
+int OffsetMap::Backup(int sub) {
+ if (sub <= 0) {return 0;}
+ --sub;
+ while ((0 < sub) &&
+ (static_cast<MapOp>(OpPart(diffs_[sub - 1]) == PREFIX_OP))) {
+ --sub;
+ }
+ return sub;
+}
+
+// Parse next range, 1..5 bytes
+// Return subscript just off the end of that
+int OffsetMap::ParseNext(int sub, MapOp* op, int* length) {
+ *op = PREFIX_OP;
+ *length = 0;
+ char c;
+ while ((sub < static_cast<int>(diffs_.size())) && (*op == PREFIX_OP)) {
+ c = diffs_[sub++];
+ *op = static_cast<MapOp>(OpPart(c));
+ int len = LenPart(c);
+ *length = (*length << 6) + len;
+ }
+ // If mal-formed or in RIGHT, this will return with op = PREFIX_OP
+ // Mal-formed can include a trailing prefix byte with no following op
+ return sub;
+}
+
+// Parse previous range, 1..5 bytes
+// Return current subscript
+int OffsetMap::ParsePrevious(int sub, MapOp* op, int* length) {
+ sub = Backup(sub);
+ return ParseNext(sub, op, length);
+}
+
+// Quick debugging dump; does not parse multi-byte items, so just length & 0x3f
+void OffsetMap::PrintPosition(const char* str) {
+ MapOp op = PREFIX_OP;
+ int length = 0;
+ if ((0 < next_diff_sub_) && (next_diff_sub_ <= static_cast<int>(diffs_.size()))) {
+ op = static_cast<MapOp>(OpPart(diffs_[next_diff_sub_ - 1]));
+ length = LenPart(diffs_[next_diff_sub_ - 1]);
+ }
+ fprintf(stderr, "%s[%d] %c%02d = A[%d..%d) ==> A'[%d..%d)\n",
+ str,
+ next_diff_sub_, "&=+-"[op], length,
+ current_lo_aoffset_, current_hi_aoffset_,
+ current_lo_aprimeoffset_, current_hi_aprimeoffset_);
+}
+
+// Move active window one range to the right
+// Return true if move was OK
+bool OffsetMap::MoveRight() {
+ // If at last range or RIGHT, set to RIGHT, return error
+ if (next_diff_sub_ >= static_cast<int>(diffs_.size())) {
+ SetRight();
+ return false;
+ }
+ // Actually OK to move right
+ MapOp op;
+ int length;
+ bool retval = true;
+ // If mal-formed or in RIGHT, this will return with op = PREFIX_OP
+ next_diff_sub_ = ParseNext(next_diff_sub_, &op, &length);
+
+ current_lo_aoffset_ = current_hi_aoffset_;
+ current_lo_aprimeoffset_ = current_hi_aprimeoffset_;
+ if (op == COPY_OP) {
+ current_hi_aoffset_ = current_lo_aoffset_ + length;
+ current_hi_aprimeoffset_ = current_lo_aprimeoffset_ + length;
+ } else if (op == INSERT_OP) {
+ current_hi_aoffset_ = current_lo_aoffset_ + 0;
+ current_hi_aprimeoffset_ = current_lo_aprimeoffset_ + length;
+ } else if (op == DELETE_OP) {
+ current_hi_aoffset_ = current_lo_aoffset_ + length;
+ current_hi_aprimeoffset_ = current_lo_aprimeoffset_ + 0;
+ } else {
+ SetRight();
+ retval = false;
+ }
+ current_diff_ = current_lo_aprimeoffset_ - current_lo_aoffset_;
+ return retval;
+}
+
+// Move active window one range to the left
+// Return true if move was OK
+bool OffsetMap::MoveLeft() {
+ // If at first range or LEFT, set to LEFT, return error
+ if (next_diff_sub_ <= 0) {
+ SetLeft();
+ return false;
+ }
+ // Back up over current active window
+ next_diff_sub_ = Backup(next_diff_sub_);
+ if (next_diff_sub_ <= 0) {
+ SetLeft();
+ return false;
+ }
+ // Actually OK to move left
+ MapOp op;
+ int length;
+ bool retval = true;
+ // If mal-formed or in LEFT, this will return with op = PREFIX_OP
+ next_diff_sub_ = ParsePrevious(next_diff_sub_, &op, &length);
+
+ current_hi_aoffset_ = current_lo_aoffset_;
+ current_hi_aprimeoffset_ = current_lo_aprimeoffset_;
+ if (op == COPY_OP) {
+ current_lo_aoffset_ = current_hi_aoffset_ - length;
+ current_lo_aprimeoffset_ = current_hi_aprimeoffset_ - length;
+ } else if (op == INSERT_OP) {
+ current_lo_aoffset_ = current_hi_aoffset_ - 0;
+ current_lo_aprimeoffset_ = current_hi_aprimeoffset_ - length;
+ } else if (op == DELETE_OP) {
+ current_lo_aoffset_ = current_hi_aoffset_ - length;
+ current_lo_aprimeoffset_ = current_hi_aprimeoffset_ - 0;
+ } else {
+ SetLeft();
+ retval = false;
+ }
+ current_diff_ = current_lo_aprimeoffset_ - current_lo_aoffset_;
+ return true;
+}
+
+// Map an offset in A' to the corresponding offset in A
+int OffsetMap::MapBack(int aprimeoffset){
+ MaybeFlushAll();
+ if (aprimeoffset < 0) {return 0;}
+ if (max_aprimeoffset_ <= aprimeoffset) {
+ return (aprimeoffset - max_aprimeoffset_) + max_aoffset_;
+ }
+
+ // If current_lo_aprimeoffset_ <= aprimeoffset < current_hi_aprimeoffset_,
+ // use current mapping, else move window left/right
+ bool ok = true;
+ while (ok && (aprimeoffset < current_lo_aprimeoffset_)) {
+ ok = MoveLeft();
+ }
+ while (ok && (current_hi_aprimeoffset_ <= aprimeoffset)) {
+ ok = MoveRight();
+ }
+ // So now current_lo_aprimeoffset_ <= aprimeoffset < current_hi_aprimeoffset_
+
+ int aoffset = aprimeoffset - current_diff_;
+ if (aoffset >= current_hi_aoffset_) {
+ // A' is in an insert region, all bytes of which backmap to A=hi_aoffset_
+ aoffset = current_hi_aoffset_;
+ }
+ return aoffset;
+}
+
+// Map an offset in A to the corresponding offset in A'
+int OffsetMap::MapForward(int aoffset){
+ MaybeFlushAll();
+ if (aoffset < 0) {return 0;}
+ if (max_aoffset_ <= aoffset) {
+ return (aoffset - max_aoffset_) + max_aprimeoffset_;
+ }
+
+ // If current_lo_aoffset_ <= aoffset < current_hi_aoffset_,
+ // use current mapping, else move window left/right
+ bool ok = true;
+ while (ok && (aoffset < current_lo_aoffset_)) {
+ ok = MoveLeft();
+ }
+ while (ok && (current_hi_aoffset_ <= aoffset)) {
+ ok = MoveRight();
+ }
+
+ int aprimeoffset = aoffset + current_diff_;
+ if (aprimeoffset >= current_hi_aprimeoffset_) {
+ // A is in a delete region, all bytes of which map to A'=hi_aprimeoffset_
+ aprimeoffset = current_hi_aprimeoffset_;
+ }
+ return aprimeoffset;
+}
+
+
+// static
+bool OffsetMap::CopyInserts(OffsetMap* source, OffsetMap* dest) {
+ bool ok = true;
+ while (ok && (source->next_diff_sub_ != source->diffs_.size())) {
+ ok = source->MoveRight();
+ if (source->current_lo_aoffset_ != source->current_hi_aoffset_) {
+ return false;
+ }
+ dest->Insert(
+ source->current_hi_aprimeoffset_ - source->current_lo_aprimeoffset_);
+ }
+ return true;
+}
+
+// static
+bool OffsetMap::CopyDeletes(OffsetMap* source, OffsetMap* dest) {
+ bool ok = true;
+ while (ok && (source->next_diff_sub_ != source->diffs_.size())) {
+ ok = source->MoveRight();
+ if (source->current_lo_aprimeoffset_ != source->current_hi_aprimeoffset_) {
+ return false;
+ }
+ dest->Delete(source->current_hi_aoffset_ - source->current_lo_aoffset_);
+ }
+ return true;
+}
+
+// static
+void OffsetMap::ComposeOffsetMap(
+ OffsetMap* g, OffsetMap* f, OffsetMap* h) {
+ h->Clear();
+ f->Reset();
+ g->Reset();
+
+ int lo = 0;
+ for (;;) {
+ // Consume delete operations in f. This moves A without moving
+ // A' and A''.
+ if (lo >= g->current_hi_aoffset_ && CopyInserts(g, h)) {
+ if (lo >= f->current_hi_aprimeoffset_ && CopyDeletes(f, h)) {
+ // fprintf(stderr,
+ // "ComposeOffsetMap ERROR, f is longer than g.<br>\n");
+ }
+
+ // FlushAll(), called by Reset(), MapForward() or MapBack(), has
+ // added an extra COPY_OP to f and g, so this function has
+ // composed an extra COPY_OP in h from those. To avoid
+ // FlushAll() adds one more extra COPY_OP to h later, dispatch
+ // Flush() right now.
+ h->Flush();
+ return;
+ }
+
+ // Consume insert operations in g. This moves A'' without moving A
+ // and A'.
+ if (lo >= f->current_hi_aprimeoffset_) {
+ if (!CopyDeletes(f, h)) {
+ // fprintf(stderr,
+ // "ComposeOffsetMap ERROR, g is longer than f.<br>\n");
+ }
+ }
+
+ // Compose one operation which moves A' from lo to hi.
+ int hi = min(f->current_hi_aprimeoffset_, g->current_hi_aoffset_);
+ if (f->current_lo_aoffset_ != f->current_hi_aoffset_ &&
+ g->current_lo_aprimeoffset_ != g->current_hi_aprimeoffset_) {
+ h->Copy(hi - lo);
+ } else if (f->current_lo_aoffset_ != f->current_hi_aoffset_) {
+ h->Delete(hi - lo);
+ } else if (g->current_lo_aprimeoffset_ != g->current_hi_aprimeoffset_) {
+ h->Insert(hi - lo);
+ }
+
+ lo = hi;
+ }
+}
+
+// For testing only -- force a mapping
+void OffsetMap::StuffIt(const string& diffs,
+ int max_aoffset, int max_aprimeoffset) {
+ Clear();
+ diffs_ = diffs;
+ max_aoffset_ = max_aoffset;
+ max_aprimeoffset_ = max_aprimeoffset;
+}
+
+
+} // namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/offsetmap.h b/browser/components/translation/cld2/internal/offsetmap.h
new file mode 100644
index 000000000..632555400
--- /dev/null
+++ b/browser/components/translation/cld2/internal/offsetmap.h
@@ -0,0 +1,175 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#ifndef UTIL_UTF8_OFFSETMAP_H_
+#define UTIL_UTF8_OFFSETMAP_H_
+
+#include <string> // for string
+#include "integral_types.h" // for uint32
+
+// ***************************** OffsetMap **************************
+//
+// An OffsetMap object is a container for a mapping from offsets in one text
+// buffer A' to offsets in another text buffer A. It is most useful when A' is
+// built from A via substitutions that occasionally do not preserve byte length.
+//
+// A series of operators are used to build the correspondence map, then
+// calls can be made to map an offset in A' to an offset in A, or vice versa.
+// The map starts with offset 0 in A corresponding to offset 0 in A'.
+// The mapping is then built sequentially, adding on byte ranges that are
+// identical in A and A', byte ranges that are inserted in A', and byte ranges
+// that are deleted from A. All bytes beyond those specified when building the
+// map are assumed to correspond, i.e. a Copy(infinity) is assumed at the
+// end of the map.
+//
+// The internal data structure records positions at which bytes are added or
+// deleted. Using the map is O(1) when increasing the A' or A offset
+// monotonically, and O(n) when accessing random offsets, where n is the
+// number of differences.
+//
+
+namespace CLD2 {
+
+class OffsetMap {
+ public:
+ // Constructor, destructor
+ OffsetMap();
+ ~OffsetMap();
+
+ // Clear the map
+ void Clear();
+
+ // Add to mapping from A to A', specifying how many next bytes correspond
+ // in A and A'
+ void Copy(int bytes);
+
+ // Add to mapping from A to A', specifying how many next bytes are
+ // inserted in A' while not advancing in A at all
+ void Insert(int bytes);
+
+ // Add to mapping from A to A', specifying how many next bytes are
+ // deleted from A while not advancing in A' at all
+ void Delete(int bytes);
+
+ // Print map to file, for debugging
+ void Printmap(const char* filename);
+
+ // [Finish building map,] Re-position to offset 0
+ // This call is optional; MapForward and MapBack finish building the map
+ // if necessary
+ void Reset();
+
+ // Map an offset in A' to the corresponding offset in A
+ int MapBack(int aprimeoffset);
+
+ // Map an offset in A to the corresponding offset in A'
+ int MapForward(int aoffset);
+
+ // h = ComposeOffsetMap(g, f), where f is a map from A to A', g is
+ // from A' to A'' and h is from A to A''.
+ //
+ // Note that g->MoveForward(f->MoveForward(aoffset)) always equals
+ // to h->MoveForward(aoffset), while
+ // f->MoveBack(g->MoveBack(aprimeprimeoffset)) doesn't always equals
+ // to h->MoveBack(aprimeprimeoffset). This happens when deletion in
+ // f and insertion in g are at the same place. For example,
+ //
+ // A 1 2 3 4
+ // ^ | ^ ^
+ // | | / | f
+ // v vv v
+ // A' 1' 2' 3'
+ // ^ ^^ ^
+ // | | \ | g
+ // v | v v
+ // A'' 1'' 2'' 3'' 4''
+ //
+ // results in:
+ //
+ // A 1 2 3 4
+ // ^ ^\ ^ ^
+ // | | \ | | h
+ // v | vv v
+ // A'' 1'' 2'' 3'' 4''
+ //
+ // 2'' is mapped 3 in the former figure, while 2'' is mapped to 2 in
+ // the latter figure.
+ static void ComposeOffsetMap(OffsetMap* g, OffsetMap* f, OffsetMap* h);
+
+ // For debugging only; writes to stderr
+ void DumpWindow();
+
+ // For testing only -- force a mapping
+ void StuffIt(const std::string& diffs, int max_aoffset, int max_aprimeoffset);
+
+ private:
+ enum MapOp {PREFIX_OP, COPY_OP, INSERT_OP, DELETE_OP};
+
+ void Flush();
+ void FlushAll();
+ void MaybeFlushAll();
+ void Emit(MapOp op, int len);
+
+ void SetLeft();
+ void SetRight();
+
+ // Back up over previous range, 1..5 bytes
+ // Return subscript at the beginning of that. Pins at 0
+ int Backup(int sub);
+
+ // Parse next range, 1..5 bytes
+ // Return subscript just off the end of that
+ int ParseNext(int sub, MapOp* op, int* length);
+
+ // Parse previous range, 1..5 bytes
+ // Return current subscript
+ int ParsePrevious(int sub, MapOp* op, int* length);
+
+ void PrintPosition(const char* str);
+
+ bool MoveRight(); // Returns true if OK
+ bool MoveLeft(); // Returns true if OK
+ void DumpString();
+
+ // Copies insert operations from source to dest. Returns true if no
+ // other operations are found.
+ static bool CopyInserts(OffsetMap* source, OffsetMap* dest);
+
+ // Copies delete operations from source to dest. Returns true if no other
+ // operations are found.
+ static bool CopyDeletes(OffsetMap* source, OffsetMap* dest);
+
+ std::string diffs_;
+ MapOp pending_op_;
+ uint32 pending_length_;
+
+ // Offsets in the ranges below correspond to each other, with A' = A + diff
+ int next_diff_sub_;
+ int current_lo_aoffset_;
+ int current_hi_aoffset_;
+ int current_lo_aprimeoffset_;
+ int current_hi_aprimeoffset_;
+ int current_diff_;
+ int max_aoffset_;
+ int max_aprimeoffset_;
+};
+
+} // namespace CLD2
+
+#endif // UTIL_UTF8_OFFSETMAP_H_
+
diff --git a/browser/components/translation/cld2/internal/port.h b/browser/components/translation/cld2/internal/port.h
new file mode 100644
index 000000000..077e35a2f
--- /dev/null
+++ b/browser/components/translation/cld2/internal/port.h
@@ -0,0 +1,128 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// These are weird things we need to do to get this compiling on
+// random systems [subset].
+
+#ifndef BASE_PORT_H_
+#define BASE_PORT_H_
+
+#include <string.h> // for memcpy()
+#include "integral_types.h"
+
+namespace CLD2 {
+
+// Portable handling of unaligned loads, stores, and copies.
+// On some platforms, like ARM, the copy functions can be more efficient
+// then a load and a store.
+
+#if defined(ARCH_PIII) || defined(ARCH_ATHLON) || defined(ARCH_K8) || defined(_ARCH_PPC)
+
+// x86 and x86-64 can perform unaligned loads/stores directly;
+// modern PowerPC hardware can also do unaligned integer loads and stores;
+// but note: the FPU still sends unaligned loads and stores to a trap handler!
+
+#define UNALIGNED_LOAD16(_p) (*reinterpret_cast<const uint16 *>(_p))
+#define UNALIGNED_LOAD32(_p) (*reinterpret_cast<const uint32 *>(_p))
+#define UNALIGNED_LOAD64(_p) (*reinterpret_cast<const uint64 *>(_p))
+
+#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast<uint16 *>(_p) = (_val))
+#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast<uint32 *>(_p) = (_val))
+#define UNALIGNED_STORE64(_p, _val) (*reinterpret_cast<uint64 *>(_p) = (_val))
+
+#elif defined(__arm__) && \
+ !defined(__ARM_ARCH_5__) && \
+ !defined(__ARM_ARCH_5T__) && \
+ !defined(__ARM_ARCH_5TE__) && \
+ !defined(__ARM_ARCH_5TEJ__) && \
+ !defined(__ARM_ARCH_6__) && \
+ !defined(__ARM_ARCH_6J__) && \
+ !defined(__ARM_ARCH_6K__) && \
+ !defined(__ARM_ARCH_6Z__) && \
+ !defined(__ARM_ARCH_6ZK__) && \
+ !defined(__ARM_ARCH_6T2__)
+
+// ARMv7 and newer support native unaligned accesses, but only of 16-bit
+// and 32-bit values (not 64-bit); older versions either raise a fatal signal,
+// do an unaligned read and rotate the words around a bit, or do the reads very
+// slowly (trip through kernel mode). There's no simple #define that says just
+// “ARMv7 or higher”, so we have to filter away all ARMv5 and ARMv6
+// sub-architectures. Newer gcc (>= 4.6) set an __ARM_FEATURE_ALIGNED #define,
+// so in time, maybe we can move on to that.
+//
+// This is a mess, but there's not much we can do about it.
+
+#define UNALIGNED_LOAD16(_p) (*reinterpret_cast<const uint16 *>(_p))
+#define UNALIGNED_LOAD32(_p) (*reinterpret_cast<const uint32 *>(_p))
+
+#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast<uint16 *>(_p) = (_val))
+#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast<uint32 *>(_p) = (_val))
+
+// TODO(sesse): NEON supports unaligned 64-bit loads and stores.
+// See if that would be more efficient on platforms supporting it,
+// at least for copies.
+
+inline uint64 UNALIGNED_LOAD64(const void *p) {
+ uint64 t;
+ memcpy(&t, p, sizeof t);
+ return t;
+}
+
+inline void UNALIGNED_STORE64(void *p, uint64 v) {
+ memcpy(p, &v, sizeof v);
+}
+
+#else
+
+#define NEED_ALIGNED_LOADS
+
+// These functions are provided for architectures that don't support
+// unaligned loads and stores.
+
+inline uint16 UNALIGNED_LOAD16(const void *p) {
+ uint16 t;
+ memcpy(&t, p, sizeof t);
+ return t;
+}
+
+inline uint32 UNALIGNED_LOAD32(const void *p) {
+ uint32 t;
+ memcpy(&t, p, sizeof t);
+ return t;
+}
+
+inline uint64 UNALIGNED_LOAD64(const void *p) {
+ uint64 t;
+ memcpy(&t, p, sizeof t);
+ return t;
+}
+
+inline void UNALIGNED_STORE16(void *p, uint16 v) {
+ memcpy(p, &v, sizeof v);
+}
+
+inline void UNALIGNED_STORE32(void *p, uint32 v) {
+ memcpy(p, &v, sizeof v);
+}
+
+inline void UNALIGNED_STORE64(void *p, uint64 v) {
+ memcpy(p, &v, sizeof v);
+}
+
+#endif
+
+} // End namespace CLD2
+
+#endif // BASE_PORT_H_
diff --git a/browser/components/translation/cld2/internal/scoreonescriptspan.cc b/browser/components/translation/cld2/internal/scoreonescriptspan.cc
new file mode 100644
index 000000000..b2cebc02e
--- /dev/null
+++ b/browser/components/translation/cld2/internal/scoreonescriptspan.cc
@@ -0,0 +1,1334 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+// Updated 2014.01 for dual table lookup
+//
+
+#include "scoreonescriptspan.h"
+
+#include "cldutil.h"
+#include "debug.h"
+#include "lang_script.h"
+
+#include <stdio.h>
+
+using namespace std;
+
+namespace CLD2 {
+
+static const int kUnreliablePercentThreshold = 75;
+
+void AddLangProb(uint32 langprob, Tote* chunk_tote) {
+ ProcessProbV2Tote(langprob, chunk_tote);
+}
+
+void ZeroPSLang(uint32 langprob, Tote* chunk_tote) {
+ uint8 top1 = (langprob >> 8) & 0xff;
+ chunk_tote->SetScore(top1, 0);
+}
+
+bool SameCloseSet(uint16 lang1, uint16 lang2) {
+ int lang1_close_set = LanguageCloseSet(static_cast<Language>(lang1));
+ if (lang1_close_set == 0) {return false;}
+ int lang2_close_set = LanguageCloseSet(static_cast<Language>(lang2));
+ return (lang1_close_set == lang2_close_set);
+}
+
+bool SameCloseSet(Language lang1, Language lang2) {
+ int lang1_close_set = LanguageCloseSet(lang1);
+ if (lang1_close_set == 0) {return false;}
+ int lang2_close_set = LanguageCloseSet(lang2);
+ return (lang1_close_set == lang2_close_set);
+}
+
+
+// Needs expected score per 1KB in scoring context
+void SetChunkSummary(ULScript ulscript, int first_linear_in_chunk,
+ int offset, int len,
+ const ScoringContext* scoringcontext,
+ const Tote* chunk_tote,
+ ChunkSummary* chunksummary) {
+ int key3[3];
+ chunk_tote->CurrentTopThreeKeys(key3);
+ Language lang1 = FromPerScriptNumber(ulscript, key3[0]);
+ Language lang2 = FromPerScriptNumber(ulscript, key3[1]);
+
+ int actual_score_per_kb = 0;
+ if (len > 0) {
+ actual_score_per_kb = (chunk_tote->GetScore(key3[0]) << 10) / len;
+ }
+ int expected_subscr = lang1 * 4 + LScript4(ulscript);
+ int expected_score_per_kb =
+ scoringcontext->scoringtables->kExpectedScore[expected_subscr];
+
+ chunksummary->offset = offset;
+ chunksummary->chunk_start = first_linear_in_chunk;
+ chunksummary->lang1 = lang1;
+ chunksummary->lang2 = lang2;
+ chunksummary->score1 = chunk_tote->GetScore(key3[0]);
+ chunksummary->score2 = chunk_tote->GetScore(key3[1]);
+ chunksummary->bytes = len;
+ chunksummary->grams = chunk_tote->GetScoreCount();
+ chunksummary->ulscript = ulscript;
+ chunksummary->reliability_delta = ReliabilityDelta(chunksummary->score1,
+ chunksummary->score2,
+ chunksummary->grams);
+ // If lang1/lang2 in same close set, set delta reliability to 100%
+ if (SameCloseSet(lang1, lang2)) {
+ chunksummary->reliability_delta = 100;
+ }
+ chunksummary->reliability_score =
+ ReliabilityExpected(actual_score_per_kb, expected_score_per_kb);
+}
+
+// Return true if just lang1 is there: lang2=0 and lang3=0
+bool IsSingleLang(uint32 langprob) {
+ // Probably a bug -- which end is lang1? But only used to call empty Boost1
+ return ((langprob & 0x00ffff00) == 0);
+}
+
+// Update scoring context distinct_boost for single language quad
+void AddDistinctBoost1(uint32 langprob, ScoringContext* scoringcontext) {
+ // Probably keep this empty -- not a good enough signal
+}
+
+// Update scoring context distinct_boost for distinct octagram
+// Keep last 4 used. Since these are mostly (except at splices) in
+// hitbuffer, we might be able to just use a subscript and splice
+void AddDistinctBoost2(uint32 langprob, ScoringContext* scoringcontext) {
+// this is called 0..n times per chunk with decoded hitbuffer->distinct...
+ LangBoosts* distinct_boost = &scoringcontext->distinct_boost.latn;
+ if (scoringcontext->ulscript != ULScript_Latin) {
+ distinct_boost = &scoringcontext->distinct_boost.othr;
+ }
+ int n = distinct_boost->n;
+ distinct_boost->langprob[n] = langprob;
+ distinct_boost->n = distinct_boost->wrap(n + 1);
+}
+
+// For each chunk, add extra weight for language priors (from content-lang and
+// meta lang=xx) and distinctive tokens
+void ScoreBoosts(const ScoringContext* scoringcontext, Tote* chunk_tote) {
+ // Get boosts for current script
+ const LangBoosts* langprior_boost = &scoringcontext->langprior_boost.latn;
+ const LangBoosts* langprior_whack = &scoringcontext->langprior_whack.latn;
+ const LangBoosts* distinct_boost = &scoringcontext->distinct_boost.latn;
+ if (scoringcontext->ulscript != ULScript_Latin) {
+ langprior_boost = &scoringcontext->langprior_boost.othr;
+ langprior_whack = &scoringcontext->langprior_whack.othr;
+ distinct_boost = &scoringcontext->distinct_boost.othr;
+ }
+
+ for (int k = 0; k < kMaxBoosts; ++k) {
+ uint32 langprob = langprior_boost->langprob[k];
+ if (langprob > 0) {AddLangProb(langprob, chunk_tote);}
+ }
+ for (int k = 0; k < kMaxBoosts; ++k) {
+ uint32 langprob = distinct_boost->langprob[k];
+ if (langprob > 0) {AddLangProb(langprob, chunk_tote);}
+ }
+ // boost has a packed set of per-script langs and probabilites
+ // whack has a packed set of per-script lang to be suppressed (zeroed)
+ // When a language in a close set is given as an explicit hint, others in
+ // that set will be whacked here.
+ for (int k = 0; k < kMaxBoosts; ++k) {
+ uint32 langprob = langprior_whack->langprob[k];
+ if (langprob > 0) {ZeroPSLang(langprob, chunk_tote);}
+ }
+}
+
+
+
+// At this point, The chunk is described by
+// hitbuffer->base[cspan->chunk_base .. cspan->chunk_base + cspan->base_len)
+// hitbuffer->delta[cspan->chunk_delta ... )
+// hitbuffer->distinct[cspan->chunk_distinct ... )
+// Scored text is in text[lo..hi) where
+// lo is 0 or the min of first base/delta/distinct hitbuffer offset and
+// hi is the min of next base/delta/distinct hitbuffer offset after
+// base_len, etc.
+void GetTextSpanOffsets(const ScoringHitBuffer* hitbuffer,
+ const ChunkSpan* cspan, int* lo, int* hi) {
+ // Front of this span
+ int lo_base = hitbuffer->base[cspan->chunk_base].offset;
+ int lo_delta = hitbuffer->delta[cspan->chunk_delta].offset;
+ int lo_distinct = hitbuffer->distinct[cspan->chunk_distinct].offset;
+ // Front of next span
+ int hi_base = hitbuffer->base[cspan->chunk_base +
+ cspan->base_len].offset;
+ int hi_delta = hitbuffer->delta[cspan->chunk_delta +
+ cspan->delta_len].offset;
+ int hi_distinct = hitbuffer->distinct[cspan->chunk_distinct +
+ cspan->distinct_len].offset;
+
+ *lo = 0;
+// if (cspan->chunk_base > 0) {
+// *lo = minint(minint(lo_base, lo_delta), lo_distinct);
+// }
+ *lo = minint(minint(lo_base, lo_delta), lo_distinct);
+ *hi = minint(minint(hi_base, hi_delta), hi_distinct);
+}
+
+
+int DiffScore(const CLD2TableSummary* obj, int indirect,
+ uint16 lang1, uint16 lang2) {
+ if (indirect < static_cast<int>(obj->kCLDTableSizeOne)) {
+ // Up to three languages at indirect
+ uint32 langprob = obj->kCLDTableInd[indirect];
+ return GetLangScore(langprob, lang1) - GetLangScore(langprob, lang2);
+ } else {
+ // Up to six languages at start + 2 * (indirect - start)
+ indirect += (indirect - obj->kCLDTableSizeOne);
+ uint32 langprob = obj->kCLDTableInd[indirect];
+ uint32 langprob2 = obj->kCLDTableInd[indirect + 1];
+ return (GetLangScore(langprob, lang1) + GetLangScore(langprob2, lang1)) -
+ (GetLangScore(langprob, lang2) + GetLangScore(langprob2, lang2));
+ }
+
+}
+
+// Score all the bases, deltas, distincts, boosts for one chunk into chunk_tote
+// After last chunk there is always a hitbuffer entry with an offset just off
+// the end of the text.
+// Sets delta_len, and distinct_len
+void ScoreOneChunk(const char* text, ULScript ulscript,
+ const ScoringHitBuffer* hitbuffer,
+ int chunk_i,
+ ScoringContext* scoringcontext,
+ ChunkSpan* cspan, Tote* chunk_tote,
+ ChunkSummary* chunksummary) {
+ int first_linear_in_chunk = hitbuffer->chunk_start[chunk_i];
+ int first_linear_in_next_chunk = hitbuffer->chunk_start[chunk_i + 1];
+
+ chunk_tote->Reinit();
+ cspan->delta_len = 0;
+ cspan->distinct_len = 0;
+ if (scoringcontext->flags_cld2_verbose) {
+ fprintf(scoringcontext->debug_file, "<br>ScoreOneChunk[%d..%d) ",
+ first_linear_in_chunk, first_linear_in_next_chunk);
+ }
+
+ // 2013.02.05 linear design: just use base and base_len for the span
+ cspan->chunk_base = first_linear_in_chunk;
+ cspan->base_len = first_linear_in_next_chunk - first_linear_in_chunk;
+ for (int i = first_linear_in_chunk; i < first_linear_in_next_chunk; ++i) {
+ uint32 langprob = hitbuffer->linear[i].langprob;
+ AddLangProb(langprob, chunk_tote);
+ if (hitbuffer->linear[i].type <= QUADHIT) {
+ chunk_tote->AddScoreCount(); // Just count quads, not octas
+ }
+ if (hitbuffer->linear[i].type == DISTINCTHIT) {
+ AddDistinctBoost2(langprob, scoringcontext);
+ }
+ }
+
+ // Score language prior boosts
+ // Score distinct word boost
+ ScoreBoosts(scoringcontext, chunk_tote);
+
+ int lo = hitbuffer->linear[first_linear_in_chunk].offset;
+ int hi = hitbuffer->linear[first_linear_in_next_chunk].offset;
+
+ // Chunk_tote: get top langs, scores, etc. and fill in chunk summary
+ SetChunkSummary(ulscript, first_linear_in_chunk, lo, hi - lo,
+ scoringcontext, chunk_tote, chunksummary);
+
+ bool more_to_come = false;
+ bool score_cjk = false;
+ if (scoringcontext->flags_cld2_html) {
+ // Show one chunk in readable output
+ CLD2_Debug(text, lo, hi, more_to_come, score_cjk, hitbuffer,
+ scoringcontext, cspan, chunksummary);
+ }
+
+ scoringcontext->prior_chunk_lang = static_cast<Language>(chunksummary->lang1);
+}
+
+
+// Score chunks of text described by hitbuffer, allowing each to be in a
+// different language, and optionally adjusting the boundaries inbetween.
+// Set last_cspan to the last chunkspan used
+void ScoreAllHits(const char* text, ULScript ulscript,
+ bool more_to_come, bool score_cjk,
+ const ScoringHitBuffer* hitbuffer,
+ ScoringContext* scoringcontext,
+ SummaryBuffer* summarybuffer, ChunkSpan* last_cspan) {
+ ChunkSpan prior_cspan = {0, 0, 0, 0, 0, 0};
+ ChunkSpan cspan = {0, 0, 0, 0, 0, 0};
+
+ for (int i = 0; i < hitbuffer->next_chunk_start; ++i) {
+ // Score one chunk
+ // Sets delta_len, and distinct_len
+ Tote chunk_tote;
+ ChunkSummary chunksummary;
+ ScoreOneChunk(text, ulscript,
+ hitbuffer, i,
+ scoringcontext, &cspan, &chunk_tote, &chunksummary);
+
+ // Put result in summarybuffer
+ if (summarybuffer->n < kMaxSummaries) {
+ summarybuffer->chunksummary[summarybuffer->n] = chunksummary;
+ summarybuffer->n += 1;
+ }
+
+ prior_cspan = cspan;
+ cspan.chunk_base += cspan.base_len;
+ cspan.chunk_delta += cspan.delta_len;
+ cspan.chunk_distinct += cspan.distinct_len;
+ }
+
+ // Add one dummy off the end to hold first unused linear_in_chunk
+ int linear_off_end = hitbuffer->next_linear;
+ int offset_off_end = hitbuffer->linear[linear_off_end].offset;
+ ChunkSummary* cs = &summarybuffer->chunksummary[summarybuffer->n];
+ memset(cs, 0, sizeof(ChunkSummary));
+ cs->offset = offset_off_end;
+ cs->chunk_start = linear_off_end;
+ *last_cspan = prior_cspan;
+}
+
+
+void SummaryBufferToDocTote(const SummaryBuffer* summarybuffer,
+ bool more_to_come, DocTote* doc_tote) {
+ int cs_bytes_sum = 0;
+ for (int i = 0; i < summarybuffer->n; ++i) {
+ const ChunkSummary* cs = &summarybuffer->chunksummary[i];
+ int reliability = minint(cs->reliability_delta, cs->reliability_score);
+ // doc_tote uses full languages
+ doc_tote->Add(cs->lang1, cs->bytes, cs->score1, reliability);
+ cs_bytes_sum += cs->bytes;
+ }
+}
+
+// Turn on for debugging vectors
+static const bool kShowLettersOriginal = false;
+
+
+// If next chunk language matches last vector language, extend last element
+// Otherwise add new element to vector
+void ItemToVector(ScriptScanner* scanner,
+ ResultChunkVector* vec, Language new_lang,
+ int mapped_offset, int mapped_len) {
+ uint16 last_vec_lang = static_cast<uint16>(UNKNOWN_LANGUAGE);
+ int last_vec_subscr = vec->size() - 1;
+ if (last_vec_subscr >= 0) {
+ ResultChunk* priorrc = &(*vec)[last_vec_subscr];
+ last_vec_lang = priorrc->lang1;
+ if (new_lang == last_vec_lang) {
+ // Extend prior. Current mapped_offset may be beyond prior end, so do
+ // the arithmetic to include any such gap
+ priorrc->bytes = minint((mapped_offset + mapped_len) - priorrc->offset,
+ kMaxResultChunkBytes);
+ if (kShowLettersOriginal) {
+ // Optionally print the new chunk original text
+ string temp2(&scanner->GetBufferStart()[priorrc->offset],
+ priorrc->bytes);
+ fprintf(stderr, "Item[%d..%d) '%s'<br>\n",
+ priorrc->offset, priorrc->offset + priorrc->bytes,
+ GetHtmlEscapedText(temp2).c_str());
+ }
+ return;
+ }
+ }
+ // Add new vector element
+ ResultChunk rc;
+ rc.offset = mapped_offset;
+ rc.bytes = minint(mapped_len, kMaxResultChunkBytes);
+ rc.lang1 = static_cast<uint16>(new_lang);
+ vec->push_back(rc);
+ if (kShowLettersOriginal) {
+ // Optionally print the new chunk original text
+ string temp2(&scanner->GetBufferStart()[rc.offset], rc.bytes);
+ fprintf(stderr, "Item[%d..%d) '%s'<br>\n",
+ rc.offset, rc.offset + rc.bytes,
+ GetHtmlEscapedText(temp2).c_str());
+ }
+}
+
+uint16 PriorVecLang(const ResultChunkVector* vec) {
+ if (vec->empty()) {return static_cast<uint16>(UNKNOWN_LANGUAGE);}
+ return (*vec)[vec->size() - 1].lang1;
+}
+
+uint16 NextChunkLang(const SummaryBuffer* summarybuffer, int i) {
+ if ((i + 1) >= summarybuffer->n) {
+ return static_cast<uint16>(UNKNOWN_LANGUAGE);
+ }
+ return summarybuffer->chunksummary[i + 1].lang1;
+}
+
+
+
+// Add n elements of summarybuffer to resultchunk vector:
+// Each element is letters-only text [offset..offset+bytes)
+// This maps back to original[Back(offset)..Back(offset+bytes))
+//
+// We go out of our way to minimize the variation in the ResultChunkVector,
+// so that the caller has fewer but more meaningful spans in different
+// lanaguges, for the likely purpose of translation or spell-check.
+//
+// The language of each chunk is lang1, but it might be unreliable for
+// either of two reasons: its score is relatively too close to the score of
+// lang2, or its score is too far away from the expected score of real text in
+// the given language. Unreliable languages are mapped to Unknown.
+//
+void SummaryBufferToVector(ScriptScanner* scanner, const char* text,
+ const SummaryBuffer* summarybuffer,
+ bool more_to_come, ResultChunkVector* vec) {
+ if (vec == NULL) {return;}
+
+ if (kShowLettersOriginal) {
+ fprintf(stderr, "map2original_ ");
+ scanner->map2original_.DumpWindow();
+ fprintf(stderr, "<br>\n");
+ fprintf(stderr, "map2uplow_ ");
+ scanner->map2uplow_.DumpWindow();
+ fprintf(stderr, "<br>\n");
+ }
+
+ for (int i = 0; i < summarybuffer->n; ++i) {
+ const ChunkSummary* cs = &summarybuffer->chunksummary[i];
+ int unmapped_offset = cs->offset;
+ int unmapped_len = cs->bytes;
+
+ if (kShowLettersOriginal) {
+ // Optionally print the chunk lowercase letters/marks text
+ string temp(&text[unmapped_offset], unmapped_len);
+ fprintf(stderr, "Letters [%d..%d) '%s'<br>\n",
+ unmapped_offset, unmapped_offset + unmapped_len,
+ GetHtmlEscapedText(temp).c_str());
+ }
+
+ int mapped_offset = scanner->MapBack(unmapped_offset);
+
+ // Trim back a little to prefer splicing original at word boundaries
+ if (mapped_offset > 0) {
+ // Size of prior vector entry, if any
+ int prior_size = 0;
+ if (!vec->empty()) {
+ ResultChunk* rc = &(*vec)[vec->size() - 1];
+ prior_size = rc->bytes;
+ }
+ // Maximum back up size to leave at least 3 bytes in prior,
+ // and not entire buffer, and no more than 12 bytes total backup
+ int n_limit = minint(prior_size - 3, mapped_offset);
+ n_limit = minint(n_limit, 12);
+
+ // Backscan over letters, stopping if prior byte is < 0x41
+ // There is some possibility that we will backscan over a different script
+ const char* s = &scanner->GetBufferStart()[mapped_offset];
+ const unsigned char* us = reinterpret_cast<const unsigned char*>(s);
+ int n = 0;
+ while ((n < n_limit) && (us[-n - 1] >= 0x41)) {++n;}
+ if (n >= n_limit) {n = 0;} // New boundary not found within range
+
+ // Also back up exactly one leading punctuation character if '"#@
+ if (n < n_limit) {
+ unsigned char c = us[-n - 1];
+ if ((c == '\'') || (c == '"') || (c == '#') || (c == '@')) {++n;}
+ }
+ // Shrink the previous chunk slightly
+ if (n > 0) {
+ ResultChunk* rc = &(*vec)[vec->size() - 1];
+ rc->bytes -= n;
+ mapped_offset -= n;
+ if (kShowLettersOriginal) {
+ fprintf(stderr, "Back up %d bytes<br>\n", n);
+ // Optionally print the prior chunk original text
+ string temp2(&scanner->GetBufferStart()[rc->offset], rc->bytes);
+ fprintf(stderr, "Prior [%d..%d) '%s'<br>\n",
+ rc->offset, rc->offset + rc->bytes,
+ GetHtmlEscapedText(temp2).c_str());
+ }
+ }
+ }
+
+ int mapped_len =
+ scanner->MapBack(unmapped_offset + unmapped_len) - mapped_offset;
+
+ if (kShowLettersOriginal) {
+ // Optionally print the chunk original text
+ string temp2(&scanner->GetBufferStart()[mapped_offset], mapped_len);
+ fprintf(stderr, "Original[%d..%d) '%s'<br>\n",
+ mapped_offset, mapped_offset + mapped_len,
+ GetHtmlEscapedText(temp2).c_str());
+ }
+
+ Language new_lang = static_cast<Language>(cs->lang1);
+ bool reliability_delta_bad =
+ (cs->reliability_delta < kUnreliablePercentThreshold);
+ bool reliability_score_bad =
+ (cs->reliability_score < kUnreliablePercentThreshold);
+
+ // If the top language matches last vector, ignore reliability_delta
+ uint16 prior_lang = PriorVecLang(vec);
+ if (prior_lang == cs->lang1) {
+ reliability_delta_bad = false;
+ }
+ // If the top language is in same close set as last vector, set up to merge
+ if (SameCloseSet(cs->lang1, prior_lang)) {
+ new_lang = static_cast<Language>(prior_lang);
+ reliability_delta_bad = false;
+ }
+ // If the top two languages are in the same close set and the last vector
+ // language is the second language, set up to merge
+ if (SameCloseSet(cs->lang1, cs->lang2) &&
+ (prior_lang == cs->lang2)) {
+ new_lang = static_cast<Language>(prior_lang);
+ reliability_delta_bad = false;
+ }
+ // If unreliable and the last and next vector languages are both
+ // the second language, set up to merge
+ uint16 next_lang = NextChunkLang(summarybuffer, i);
+ if (reliability_delta_bad &&
+ (prior_lang == cs->lang2) && (next_lang == cs->lang2)) {
+ new_lang = static_cast<Language>(prior_lang);
+ reliability_delta_bad = false;
+ }
+
+ if (reliability_delta_bad || reliability_score_bad) {
+ new_lang = UNKNOWN_LANGUAGE;
+ }
+ ItemToVector(scanner, vec, new_lang, mapped_offset, mapped_len);
+ }
+}
+
+// Add just one element to resultchunk vector:
+// For RTypeNone or RTypeOne
+void JustOneItemToVector(ScriptScanner* scanner, const char* text,
+ Language lang1, int unmapped_offset, int unmapped_len,
+ ResultChunkVector* vec) {
+ if (vec == NULL) {return;}
+
+ if (kShowLettersOriginal) {
+ fprintf(stderr, "map2original_ ");
+ scanner->map2original_.DumpWindow();
+ fprintf(stderr, "<br>\n");
+ fprintf(stderr, "map2uplow_ ");
+ scanner->map2uplow_.DumpWindow();
+ fprintf(stderr, "<br>\n");
+ }
+
+ if (kShowLettersOriginal) {
+ // Optionally print the chunk lowercase letters/marks text
+ string temp(&text[unmapped_offset], unmapped_len);
+ fprintf(stderr, "Letters1 [%d..%d) '%s'<br>\n",
+ unmapped_offset, unmapped_offset + unmapped_len,
+ GetHtmlEscapedText(temp).c_str());
+ }
+
+ int mapped_offset = scanner->MapBack(unmapped_offset);
+ int mapped_len =
+ scanner->MapBack(unmapped_offset + unmapped_len) - mapped_offset;
+
+ if (kShowLettersOriginal) {
+ // Optionally print the chunk original text
+ string temp2(&scanner->GetBufferStart()[mapped_offset], mapped_len);
+ fprintf(stderr, "Original1[%d..%d) '%s'<br>\n",
+ mapped_offset, mapped_offset + mapped_len,
+ GetHtmlEscapedText(temp2).c_str());
+ }
+
+ ItemToVector(scanner, vec, lang1, mapped_offset, mapped_len);
+}
+
+
+// Debugging. Not thread safe. Defined in getonescriptspan
+char* DisplayPiece(const char* next_byte_, int byte_length_);
+
+// If high bit is on, take out high bit and add 2B to make table2 entries easy
+inline int PrintableIndirect(int x) {
+ if ((x & 0x80000000u) != 0) {
+ return (x & ~0x80000000u) + 2000000000;
+ }
+ return x;
+}
+void DumpHitBuffer(FILE* df, const char* text,
+ const ScoringHitBuffer* hitbuffer) {
+ fprintf(df,
+ "<br>DumpHitBuffer[%s, next_base/delta/distinct %d, %d, %d)<br>\n",
+ ULScriptCode(hitbuffer->ulscript),
+ hitbuffer->next_base, hitbuffer->next_delta,
+ hitbuffer->next_distinct);
+ for (int i = 0; i < hitbuffer->maxscoringhits; ++i) {
+ if (i < hitbuffer->next_base) {
+ fprintf(df, "Q[%d]%d,%d,%s ",
+ i, hitbuffer->base[i].offset,
+ PrintableIndirect(hitbuffer->base[i].indirect),
+ DisplayPiece(&text[hitbuffer->base[i].offset], 6));
+ }
+ if (i < hitbuffer->next_delta) {
+ fprintf(df, "DL[%d]%d,%d,%s ",
+ i, hitbuffer->delta[i].offset, hitbuffer->delta[i].indirect,
+ DisplayPiece(&text[hitbuffer->delta[i].offset], 12));
+ }
+ if (i < hitbuffer->next_distinct) {
+ fprintf(df, "D[%d]%d,%d,%s ",
+ i, hitbuffer->distinct[i].offset, hitbuffer->distinct[i].indirect,
+ DisplayPiece(&text[hitbuffer->distinct[i].offset], 12));
+ }
+ if (i < hitbuffer->next_base) {
+ fprintf(df, "<br>\n");
+ }
+ if (i > 50) {break;}
+ }
+ if (hitbuffer->next_base > 50) {
+ int i = hitbuffer->next_base;
+ fprintf(df, "Q[%d]%d,%d,%s ",
+ i, hitbuffer->base[i].offset,
+ PrintableIndirect(hitbuffer->base[i].indirect),
+ DisplayPiece(&text[hitbuffer->base[i].offset], 6));
+ }
+ if (hitbuffer->next_delta > 50) {
+ int i = hitbuffer->next_delta;
+ fprintf(df, "DL[%d]%d,%d,%s ",
+ i, hitbuffer->delta[i].offset, hitbuffer->delta[i].indirect,
+ DisplayPiece(&text[hitbuffer->delta[i].offset], 12));
+ }
+ if (hitbuffer->next_distinct > 50) {
+ int i = hitbuffer->next_distinct;
+ fprintf(df, "D[%d]%d,%d,%s ",
+ i, hitbuffer->distinct[i].offset, hitbuffer->distinct[i].indirect,
+ DisplayPiece(&text[hitbuffer->distinct[i].offset], 12));
+ }
+ fprintf(df, "<br>\n");
+}
+
+
+void DumpLinearBuffer(FILE* df, const char* text,
+ const ScoringHitBuffer* hitbuffer) {
+ fprintf(df, "<br>DumpLinearBuffer[%d)<br>\n",
+ hitbuffer->next_linear);
+ // Include the dummy entry off the end
+ for (int i = 0; i < hitbuffer->next_linear + 1; ++i) {
+ if ((50 < i) && (i < (hitbuffer->next_linear - 1))) {continue;}
+ fprintf(df, "[%d]%d,%c=%08x,%s<br>\n",
+ i, hitbuffer->linear[i].offset,
+ "UQLD"[hitbuffer->linear[i].type],
+ hitbuffer->linear[i].langprob,
+ DisplayPiece(&text[hitbuffer->linear[i].offset], 6));
+ }
+ fprintf(df, "<br>\n");
+
+ fprintf(df, "DumpChunkStart[%d]<br>\n", hitbuffer->next_chunk_start);
+ for (int i = 0; i < hitbuffer->next_chunk_start + 1; ++i) {
+ fprintf(df, "[%d]%d\n", i, hitbuffer->chunk_start[i]);
+ }
+ fprintf(df, "<br>\n");
+}
+
+// Move this verbose debugging output to debug.cc eventually
+void DumpChunkSummary(FILE* df, const ChunkSummary* cs) {
+ // Print chunksummary
+ fprintf(df, "%d lin[%d] %s.%d %s.%d %dB %d# %s %dRd %dRs<br>\n",
+ cs->offset,
+ cs->chunk_start,
+ LanguageCode(static_cast<Language>(cs->lang1)),
+ cs->score1,
+ LanguageCode(static_cast<Language>(cs->lang2)),
+ cs->score2,
+ cs->bytes,
+ cs->grams,
+ ULScriptCode(static_cast<ULScript>(cs->ulscript)),
+ cs->reliability_delta,
+ cs->reliability_score);
+}
+
+void DumpSummaryBuffer(FILE* df, const SummaryBuffer* summarybuffer) {
+ fprintf(df, "<br>DumpSummaryBuffer[%d]<br>\n", summarybuffer->n);
+ fprintf(df, "[i] offset linear[chunk_start] lang.score1 lang.score2 "
+ "bytesB ngrams# script rel_delta rel_score<br>\n");
+ for (int i = 0; i <= summarybuffer->n; ++i) {
+ fprintf(df, "[%d] ", i);
+ DumpChunkSummary(df, &summarybuffer->chunksummary[i]);
+ }
+ fprintf(df, "<br>\n");
+}
+
+
+
+// Within hitbufer->linear[]
+// <-- prior chunk --><-- this chunk -->
+// | | |
+// linear0 linear1 linear2
+// lang0 lang1
+// The goal of sharpening is to move this_linear to better separate langs
+int BetterBoundary(const char* text,
+ ScoringHitBuffer* hitbuffer,
+ ScoringContext* scoringcontext,
+ uint16 pslang0, uint16 pslang1,
+ int linear0, int linear1, int linear2) {
+ // Degenerate case, no change
+ if ((linear2 - linear0) <= 8) {return linear1;}
+
+ // Each diff gives pslang0 score - pslang1 score
+ // Running diff has four entries + + + + followed by four entries - - - -
+ // so that this value is maximal at the sharpest boundary between pslang0
+ // (positive diffs) and pslang1 (negative diffs)
+ int running_diff = 0;
+ int diff[8]; // Ring buffer of pslang0-pslang1 differences
+ // Initialize with first 8 diffs
+ for (int i = linear0; i < linear0 + 8; ++i) {
+ int j = i & 7;
+ uint32 langprob = hitbuffer->linear[i].langprob;
+ diff[j] = GetLangScore(langprob, pslang0) -
+ GetLangScore(langprob, pslang1);
+ if (i < linear0 + 4) {
+ // First four diffs pslang0 - pslang1
+ running_diff += diff[j];
+ } else {
+ // Second four diffs -(pslang0 - pslang1)
+ running_diff -= diff[j];
+ }
+ }
+
+ // Now scan for sharpest boundary. j is at left end of 8 entries
+ // To be a boundary, there must be both >0 and <0 entries in the window
+ int better_boundary_value = 0;
+ int better_boundary = linear1;
+ for (int i = linear0; i < linear2 - 8; ++i) {
+ int j = i & 7;
+ if (better_boundary_value < running_diff) {
+ bool has_plus = false;
+ bool has_minus = false;
+ for (int kk = 0; kk < 8; ++kk) {
+ if (diff[kk] > 0) {has_plus = true;}
+ if (diff[kk] < 0) {has_minus = true;}
+ }
+ if (has_plus && has_minus) {
+ better_boundary_value = running_diff;
+ better_boundary = i + 4;
+ }
+ }
+ // Shift right one entry
+ uint32 langprob = hitbuffer->linear[i + 8].langprob;
+ int newdiff = GetLangScore(langprob, pslang0) -
+ GetLangScore(langprob, pslang1);
+ int middiff = diff[(i + 4) & 7];
+ int olddiff = diff[j];
+ diff[j] = newdiff;
+ running_diff -= olddiff; // Remove left
+ running_diff += 2 * middiff; // Convert middle from - to +
+ running_diff -= newdiff; // Insert right
+ }
+
+ if (scoringcontext->flags_cld2_verbose && (linear1 != better_boundary)) {
+ Language lang0 = FromPerScriptNumber(scoringcontext->ulscript, pslang0);
+ Language lang1 = FromPerScriptNumber(scoringcontext->ulscript, pslang1);
+ fprintf(scoringcontext->debug_file, " Better lin[%d=>%d] %s^^%s <br>\n",
+ linear1, better_boundary,
+ LanguageCode(lang0), LanguageCode(lang1));
+ int lin0_off = hitbuffer->linear[linear0].offset;
+ int lin1_off = hitbuffer->linear[linear1].offset;
+ int lin2_off = hitbuffer->linear[linear2].offset;
+ int better_offm1 = hitbuffer->linear[better_boundary - 1].offset;
+ int better_off = hitbuffer->linear[better_boundary].offset;
+ int better_offp1 = hitbuffer->linear[better_boundary + 1].offset;
+ string old0(&text[lin0_off], lin1_off - lin0_off);
+ string old1(&text[lin1_off], lin2_off - lin1_off);
+ string new0(&text[lin0_off], better_offm1 - lin0_off);
+ string new0m1(&text[better_offm1], better_off - better_offm1);
+ string new1(&text[better_off], better_offp1 - better_off);
+ string new1p1(&text[better_offp1], lin2_off - better_offp1);
+ fprintf(scoringcontext->debug_file, "%s^^%s => <br>\n%s^%s^^%s^%s<br>\n",
+ GetHtmlEscapedText(old0).c_str(),
+ GetHtmlEscapedText(old1).c_str(),
+ GetHtmlEscapedText(new0).c_str(),
+ GetHtmlEscapedText(new0m1).c_str(),
+ GetHtmlEscapedText(new1).c_str(),
+ GetHtmlEscapedText(new1p1).c_str());
+ // Slow picture of differences per linear entry
+ int d;
+ for (int i = linear0; i < linear2; ++i) {
+ if (i == better_boundary) {
+ fprintf(scoringcontext->debug_file, "^^ ");
+ }
+ uint32 langprob = hitbuffer->linear[i].langprob;
+ d = GetLangScore(langprob, pslang0) - GetLangScore(langprob, pslang1);
+ const char* s = "=";
+ //if (d > 2) {s = "\xc2\xaf";} // Macron
+ if (d > 2) {s = "#";}
+ else if (d > 0) {s = "+";}
+ else if (d < -2) {s = "_";}
+ else if (d < 0) {s = "-";}
+ fprintf(scoringcontext->debug_file, "%s ", s);
+ }
+ fprintf(scoringcontext->debug_file, " &nbsp;&nbsp;(scale: #+=-_)<br>\n");
+ }
+ return better_boundary;
+}
+
+
+// For all but the first summary, if its top language differs from
+// the previous chunk, refine the boundary
+// Linearized version
+void SharpenBoundaries(const char* text,
+ bool more_to_come,
+ ScoringHitBuffer* hitbuffer,
+ ScoringContext* scoringcontext,
+ SummaryBuffer* summarybuffer) {
+
+ int prior_linear = summarybuffer->chunksummary[0].chunk_start;
+ uint16 prior_lang = summarybuffer->chunksummary[0].lang1;
+
+ if (scoringcontext->flags_cld2_verbose) {
+ fprintf(scoringcontext->debug_file, "<br>SharpenBoundaries<br>\n");
+ }
+ for (int i = 1; i < summarybuffer->n; ++i) {
+ ChunkSummary* cs = &summarybuffer->chunksummary[i];
+ uint16 this_lang = cs->lang1;
+ if (this_lang == prior_lang) {
+ prior_linear = cs->chunk_start;
+ continue;
+ }
+
+ int this_linear = cs->chunk_start;
+ int next_linear = summarybuffer->chunksummary[i + 1].chunk_start;
+
+ // If this/prior in same close set, don't move boundary
+ if (SameCloseSet(prior_lang, this_lang)) {
+ prior_linear = this_linear;
+ prior_lang = this_lang;
+ continue;
+ }
+
+
+ // Within hitbuffer->linear[]
+ // <-- prior chunk --><-- this chunk -->
+ // | | |
+ // prior_linear this_linear next_linear
+ // prior_lang this_lang
+ // The goal of sharpening is to move this_linear to better separate langs
+
+ uint8 pslang0 = PerScriptNumber(scoringcontext->ulscript,
+ static_cast<Language>(prior_lang));
+ uint8 pslang1 = PerScriptNumber(scoringcontext->ulscript,
+ static_cast<Language>(this_lang));
+ int better_linear = BetterBoundary(text,
+ hitbuffer,
+ scoringcontext,
+ pslang0, pslang1,
+ prior_linear, this_linear, next_linear);
+
+ int old_offset = hitbuffer->linear[this_linear].offset;
+ int new_offset = hitbuffer->linear[better_linear].offset;
+ cs->chunk_start = better_linear;
+ cs->offset = new_offset;
+ // If this_linear moved right, make bytes smaller for this, larger for prior
+ // If this_linear moved left, make bytes larger for this, smaller for prior
+ cs->bytes -= (new_offset - old_offset);
+ summarybuffer->chunksummary[i - 1].bytes += (new_offset - old_offset);
+
+ this_linear = better_linear; // Update so that next chunk doesn't intrude
+
+ // Consider rescoring the two chunks
+
+ // Update for next round (note: using pre-updated boundary)
+ prior_linear = this_linear;
+ prior_lang = this_lang;
+ }
+}
+
+// Make a langprob that gives small weight to the default language for ulscript
+uint32 DefaultLangProb(ULScript ulscript) {
+ Language default_lang = DefaultLanguage(ulscript);
+ return MakeLangProb(default_lang, 1);
+}
+
+// Effectively, do a merge-sort based on text offsets
+// Look up each indirect value in appropriate scoring table and keep
+// just the resulting langprobs
+void LinearizeAll(ScoringContext* scoringcontext, bool score_cjk,
+ ScoringHitBuffer* hitbuffer) {
+ const CLD2TableSummary* base_obj; // unigram or quadgram
+ const CLD2TableSummary* base_obj2; // quadgram dual table
+ const CLD2TableSummary* delta_obj; // bigram or octagram
+ const CLD2TableSummary* distinct_obj; // bigram or octagram
+ uint16 base_hit;
+ if (score_cjk) {
+ base_obj = scoringcontext->scoringtables->unigram_compat_obj;
+ base_obj2 = scoringcontext->scoringtables->unigram_compat_obj;
+ delta_obj = scoringcontext->scoringtables->deltabi_obj;
+ distinct_obj = scoringcontext->scoringtables->distinctbi_obj;
+ base_hit = UNIHIT;
+ } else {
+ base_obj = scoringcontext->scoringtables->quadgram_obj;
+ base_obj2 = scoringcontext->scoringtables->quadgram_obj2;
+ delta_obj = scoringcontext->scoringtables->deltaocta_obj;
+ distinct_obj = scoringcontext->scoringtables->distinctocta_obj;
+ base_hit = QUADHIT;
+ }
+
+ int base_limit = hitbuffer->next_base;
+ int delta_limit = hitbuffer->next_delta;
+ int distinct_limit = hitbuffer->next_distinct;
+ int base_i = 0;
+ int delta_i = 0;
+ int distinct_i = 0;
+ int linear_i = 0;
+
+ // Start with an initial base hit for the default language for this script
+ // Inserting this avoids edge effects with no hits at all
+ hitbuffer->linear[linear_i].offset = hitbuffer->lowest_offset;
+ hitbuffer->linear[linear_i].type = base_hit;
+ hitbuffer->linear[linear_i].langprob =
+ DefaultLangProb(scoringcontext->ulscript);
+ ++linear_i;
+
+ while ((base_i < base_limit) || (delta_i < delta_limit) ||
+ (distinct_i < distinct_limit)) {
+ int base_off = hitbuffer->base[base_i].offset;
+ int delta_off = hitbuffer->delta[delta_i].offset;
+ int distinct_off = hitbuffer->distinct[distinct_i].offset;
+
+ // Do delta and distinct first, so that they are not lost at base_limit
+ if ((delta_i < delta_limit) &&
+ (delta_off <= base_off) && (delta_off <= distinct_off)) {
+ // Add delta entry
+ int indirect = hitbuffer->delta[delta_i].indirect;
+ ++delta_i;
+ uint32 langprob = delta_obj->kCLDTableInd[indirect];
+ if (langprob > 0) {
+ hitbuffer->linear[linear_i].offset = delta_off;
+ hitbuffer->linear[linear_i].type = DELTAHIT;
+ hitbuffer->linear[linear_i].langprob = langprob;
+ ++linear_i;
+ }
+ }
+ else if ((distinct_i < distinct_limit) &&
+ (distinct_off <= base_off) && (distinct_off <= delta_off)) {
+ // Add distinct entry
+ int indirect = hitbuffer->distinct[distinct_i].indirect;
+ ++distinct_i;
+ uint32 langprob = distinct_obj->kCLDTableInd[indirect];
+ if (langprob > 0) {
+ hitbuffer->linear[linear_i].offset = distinct_off;
+ hitbuffer->linear[linear_i].type = DISTINCTHIT;
+ hitbuffer->linear[linear_i].langprob = langprob;
+ ++linear_i;
+ }
+ }
+ else {
+ // Add one or two base entries
+ int indirect = hitbuffer->base[base_i].indirect;
+ // First, get right scoring table
+ const CLD2TableSummary* local_base_obj = base_obj;
+ if ((indirect & 0x80000000u) != 0) {
+ local_base_obj = base_obj2;
+ indirect &= ~0x80000000u;
+ }
+ ++base_i;
+ // One langprob in kQuadInd[0..SingleSize),
+ // two in kQuadInd[SingleSize..Size)
+ if (indirect < static_cast<int>(local_base_obj->kCLDTableSizeOne)) {
+ // Up to three languages at indirect
+ uint32 langprob = local_base_obj->kCLDTableInd[indirect];
+ if (langprob > 0) {
+ hitbuffer->linear[linear_i].offset = base_off;
+ hitbuffer->linear[linear_i].type = base_hit;
+ hitbuffer->linear[linear_i].langprob = langprob;
+ ++linear_i;
+ }
+ } else {
+ // Up to six languages at start + 2 * (indirect - start)
+ indirect += (indirect - local_base_obj->kCLDTableSizeOne);
+ uint32 langprob = local_base_obj->kCLDTableInd[indirect];
+ uint32 langprob2 = local_base_obj->kCLDTableInd[indirect + 1];
+ if (langprob > 0) {
+ hitbuffer->linear[linear_i].offset = base_off;
+ hitbuffer->linear[linear_i].type = base_hit;
+ hitbuffer->linear[linear_i].langprob = langprob;
+ ++linear_i;
+ }
+ if (langprob2 > 0) {
+ hitbuffer->linear[linear_i].offset = base_off;
+ hitbuffer->linear[linear_i].type = base_hit;
+ hitbuffer->linear[linear_i].langprob = langprob2;
+ ++linear_i;
+ }
+ }
+ }
+ }
+
+ // Update
+ hitbuffer->next_linear = linear_i;
+
+ // Add a dummy entry off the end, just to capture final offset
+ hitbuffer->linear[linear_i].offset =
+ hitbuffer->base[hitbuffer->next_base].offset;
+ hitbuffer->linear[linear_i].langprob = 0;
+}
+
+// Break linear array into chunks of ~20 quadgram hits or ~50 CJK unigram hits
+void ChunkAll(int letter_offset, bool score_cjk, ScoringHitBuffer* hitbuffer) {
+ int chunksize;
+ uint16 base_hit;
+ if (score_cjk) {
+ chunksize = kChunksizeUnis;
+ base_hit = UNIHIT;
+ } else {
+ chunksize = kChunksizeQuads;
+ base_hit = QUADHIT;
+ }
+
+ int linear_i = 0;
+ int linear_off_end = hitbuffer->next_linear;
+ int text_i = letter_offset; // Next unseen text offset
+ int next_chunk_start = 0;
+ int bases_left = hitbuffer->next_base;
+ while (bases_left > 0) {
+ // Linearize one chunk
+ int base_len = chunksize; // Default; may be changed below
+ if (bases_left < (chunksize + (chunksize >> 1))) {
+ // If within 1.5 chunks of the end, avoid runts by using it all
+ base_len = bases_left;
+ } else if (bases_left < (2 * chunksize)) {
+ // Avoid runts by splitting 1.5 to 2 chunks in half (about 3/4 each)
+ base_len = (bases_left + 1) >> 1;
+ }
+
+ hitbuffer->chunk_start[next_chunk_start] = linear_i;
+ hitbuffer->chunk_offset[next_chunk_start] = text_i;
+ ++next_chunk_start;
+
+ int base_count = 0;
+ while ((base_count < base_len) && (linear_i < linear_off_end)) {
+ if (hitbuffer->linear[linear_i].type == base_hit) {++base_count;}
+ ++linear_i;
+ }
+ text_i = hitbuffer->linear[linear_i].offset; // Next unseen text offset
+ bases_left -= base_len;
+ }
+
+ // If no base hits at all, make a single dummy chunk
+ if (next_chunk_start == 0) {
+ hitbuffer->chunk_start[next_chunk_start] = 0;
+ hitbuffer->chunk_offset[next_chunk_start] = hitbuffer->linear[0].offset;
+ ++next_chunk_start;
+ }
+
+ // Remember the linear array start of dummy entry
+ hitbuffer->next_chunk_start = next_chunk_start;
+
+ // Add a dummy entry off the end, just to capture final linear subscr
+ hitbuffer->chunk_start[next_chunk_start] = hitbuffer->next_linear;
+ hitbuffer->chunk_offset[next_chunk_start] = text_i;
+}
+
+
+// Merge-sort the individual hit arrays, go indirect on the scoring subscripts,
+// break linear array into chunks.
+//
+// Input:
+// hitbuffer base, delta, distinct arrays
+// Output:
+// linear array
+// chunk_start array
+//
+void LinearizeHitBuffer(int letter_offset,
+ ScoringContext* scoringcontext,
+ bool more_to_come, bool score_cjk,
+ ScoringHitBuffer* hitbuffer) {
+ LinearizeAll(scoringcontext, score_cjk, hitbuffer);
+ ChunkAll(letter_offset, score_cjk, hitbuffer);
+}
+
+
+
+// The hitbuffer is in an awkward form -- three sets of base/delta/distinct
+// scores, each with an indirect subscript to one of six scoring tables, some
+// of which can yield two langprobs for six languages, others one langprob for
+// three languages. The only correlation between base/delta/distinct is their
+// offsets into the letters-only text buffer.
+//
+// SummaryBuffer needs to be built to linear, giving linear offset of start of
+// each chunk
+//
+// So we first do all the langprob lookups and merge-sort by offset to make
+// a single linear vector, building a side vector of chunk beginnings as we go.
+// The sharpening is simply moving the beginnings, scoring is a simple linear
+// sweep, etc.
+
+void ProcessHitBuffer(const LangSpan& scriptspan,
+ int letter_offset,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec,
+ bool more_to_come, bool score_cjk,
+ ScoringHitBuffer* hitbuffer) {
+ if (scoringcontext->flags_cld2_verbose) {
+ fprintf(scoringcontext->debug_file, "Hitbuffer[) ");
+ DumpHitBuffer(scoringcontext->debug_file, scriptspan.text, hitbuffer);
+ }
+
+ LinearizeHitBuffer(letter_offset, scoringcontext, more_to_come, score_cjk,
+ hitbuffer);
+
+ if (scoringcontext->flags_cld2_verbose) {
+ fprintf(scoringcontext->debug_file, "Linear[) ");
+ DumpLinearBuffer(scoringcontext->debug_file, scriptspan.text, hitbuffer);
+ }
+
+ SummaryBuffer summarybuffer;
+ summarybuffer.n = 0;
+ ChunkSpan last_cspan;
+ ScoreAllHits(scriptspan.text, scriptspan.ulscript,
+ more_to_come, score_cjk, hitbuffer,
+ scoringcontext,
+ &summarybuffer, &last_cspan);
+
+ if (scoringcontext->flags_cld2_verbose) {
+ DumpSummaryBuffer(scoringcontext->debug_file, &summarybuffer);
+ }
+
+ if (vec != NULL) {
+ // Sharpen boundaries of summarybuffer
+ // This is not a high-performance path
+ SharpenBoundaries(scriptspan.text, more_to_come, hitbuffer, scoringcontext,
+ &summarybuffer);
+ // Show after the sharpening
+ // CLD2_Debug2(scriptspan.text, more_to_come, score_cjk,
+ // hitbuffer, scoringcontext, &summarybuffer);
+
+ if (scoringcontext->flags_cld2_verbose) {
+ DumpSummaryBuffer(scoringcontext->debug_file, &summarybuffer);
+ }
+ }
+
+ SummaryBufferToDocTote(&summarybuffer, more_to_come, doc_tote);
+ SummaryBufferToVector(scoringcontext->scanner, scriptspan.text,
+ &summarybuffer, more_to_come, vec);
+}
+
+void SpliceHitBuffer(ScoringHitBuffer* hitbuffer, int next_offset) {
+ // Splice hitbuffer and summarybuffer for next round. With big chunks and
+ // distinctive-word state carried across chunks, we might not need to do this.
+ hitbuffer->next_base = 0;
+ hitbuffer->next_delta = 0;
+ hitbuffer->next_distinct = 0;
+ hitbuffer->next_linear = 0;
+ hitbuffer->next_chunk_start = 0;
+ hitbuffer->lowest_offset = next_offset;
+}
+
+
+// Score RTypeNone or RTypeOne scriptspan into doc_tote and vec, updating
+// scoringcontext
+void ScoreEntireScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec) {
+ int bytes = scriptspan.text_bytes;
+ // Artificially set score to 1024 per 1KB, or 1 per byte
+ int score = bytes;
+ int reliability = 100;
+ // doc_tote uses full languages
+ Language one_one_lang = DefaultLanguage(scriptspan.ulscript);
+ doc_tote->Add(one_one_lang, bytes, score, reliability);
+
+ if (scoringcontext->flags_cld2_html) {
+ ChunkSummary chunksummary = {
+ 1, 0,
+ one_one_lang, UNKNOWN_LANGUAGE, score, 1,
+ bytes, 0, scriptspan.ulscript, reliability, reliability
+ };
+ CLD2_Debug(scriptspan.text, 1, scriptspan.text_bytes,
+ false, false, NULL,
+ scoringcontext, NULL, &chunksummary);
+ }
+
+ // First byte is always a space
+ JustOneItemToVector(scoringcontext->scanner, scriptspan.text,
+ one_one_lang, 1, bytes - 1, vec);
+
+ scoringcontext->prior_chunk_lang = UNKNOWN_LANGUAGE;
+}
+
+// Score RTypeCJK scriptspan into doc_tote and vec, updating scoringcontext
+void ScoreCJKScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec) {
+ // Allocate three parallel arrays of scoring hits
+ ScoringHitBuffer* hitbuffer = new ScoringHitBuffer;
+ hitbuffer->init();
+ hitbuffer->ulscript = scriptspan.ulscript;
+
+ scoringcontext->prior_chunk_lang = UNKNOWN_LANGUAGE;
+ scoringcontext->oldest_distinct_boost = 0;
+
+ // Incoming scriptspan has a single leading space at scriptspan.text[0]
+ // and three trailing spaces then NUL at scriptspan.text[text_bytes + 0/1/2/3]
+
+ int letter_offset = 1; // Skip initial space
+ hitbuffer->lowest_offset = letter_offset;
+ int letter_limit = scriptspan.text_bytes;
+ while (letter_offset < letter_limit) {
+ if (scoringcontext->flags_cld2_verbose) {
+ fprintf(scoringcontext->debug_file, " ScoreCJKScriptSpan[%d,%d)<br>\n",
+ letter_offset, letter_limit);
+ }
+ //
+ // Fill up one hitbuffer, possibly splicing onto previous fragment
+ //
+ // NOTE: GetUniHits deals with close repeats
+ // NOTE: After last chunk there is always a hitbuffer entry with an offset
+ // just off the end of the text = next_offset.
+ int next_offset = GetUniHits(scriptspan.text, letter_offset, letter_limit,
+ scoringcontext, hitbuffer);
+ // NOTE: GetBiHitVectors deals with close repeats,
+ // does one hash and two lookups (delta and distinct) per word
+ GetBiHits(scriptspan.text, letter_offset, next_offset,
+ scoringcontext, hitbuffer);
+
+ //
+ // Score one hitbuffer in chunks to summarybuffer
+ //
+ bool more_to_come = next_offset < letter_limit;
+ bool score_cjk = true;
+ ProcessHitBuffer(scriptspan, letter_offset, scoringcontext, doc_tote, vec,
+ more_to_come, score_cjk, hitbuffer);
+ SpliceHitBuffer(hitbuffer, next_offset);
+
+ letter_offset = next_offset;
+ }
+
+ delete hitbuffer;
+ // Context across buffers is not connected yet
+ scoringcontext->prior_chunk_lang = UNKNOWN_LANGUAGE;
+}
+
+
+
+// Score RTypeMany scriptspan into doc_tote and vec, updating scoringcontext
+// We have a scriptspan with all lowercase text in one script. Look up
+// quadgrams and octagrams, saving the hits in three parallel vectors.
+// Score from those vectors in chunks, toting each chunk to get a single
+// language, and combining into the overall document score. The hit vectors
+// in general are not big enough to handle and entire scriptspan, so
+// repeat until the entire scriptspan is scored.
+// Caller deals with minimizing numbr of runt scriptspans
+// This routine deals with minimizing number of runt chunks.
+//
+// Returns updated scoringcontext
+// Returns updated doc_tote
+// If vec != NULL, appends to that vector of ResultChunk's
+void ScoreQuadScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec) {
+ // Allocate three parallel arrays of scoring hits
+ ScoringHitBuffer* hitbuffer = new ScoringHitBuffer;
+ hitbuffer->init();
+ hitbuffer->ulscript = scriptspan.ulscript;
+
+ scoringcontext->prior_chunk_lang = UNKNOWN_LANGUAGE;
+ scoringcontext->oldest_distinct_boost = 0;
+
+ // Incoming scriptspan has a single leading space at scriptspan.text[0]
+ // and three trailing spaces then NUL at scriptspan.text[text_bytes + 0/1/2/3]
+
+ int letter_offset = 1; // Skip initial space
+ hitbuffer->lowest_offset = letter_offset;
+ int letter_limit = scriptspan.text_bytes;
+ while (letter_offset < letter_limit) {
+ //
+ // Fill up one hitbuffer, possibly splicing onto previous fragment
+ //
+ // NOTE: GetQuadHits deals with close repeats
+ // NOTE: After last chunk there is always a hitbuffer entry with an offset
+ // just off the end of the text = next_offset.
+ int next_offset = GetQuadHits(scriptspan.text, letter_offset, letter_limit,
+ scoringcontext, hitbuffer);
+ // If true, there is more text to process in this scriptspan
+ // NOTE: GetOctaHitVectors deals with close repeats,
+ // does one hash and two lookups (delta and distinct) per word
+ GetOctaHits(scriptspan.text, letter_offset, next_offset,
+ scoringcontext, hitbuffer);
+
+ //
+ // Score one hitbuffer in chunks to summarybuffer
+ //
+ bool more_to_come = next_offset < letter_limit;
+ bool score_cjk = false;
+ ProcessHitBuffer(scriptspan, letter_offset, scoringcontext, doc_tote, vec,
+ more_to_come, score_cjk, hitbuffer);
+ SpliceHitBuffer(hitbuffer, next_offset);
+
+ letter_offset = next_offset;
+ }
+
+ delete hitbuffer;
+}
+
+
+// Score one scriptspan into doc_tote and vec, updating scoringcontext
+// Inputs:
+// One scriptspan of perhaps 40-60KB, all same script lower-case letters
+// and single ASCII spaces. First character is a space to allow simple
+// begining-of-word detect. End of buffer has three spaces and NUL to
+// allow easy scan-to-end-of-word.
+// Scoring context of
+// scoring tables
+// flags
+// running boosts
+// Outputs:
+// Updated doc_tote giving overall languages and byte counts
+// Optional updated chunk vector giving offset, length, language
+//
+// Caller initializes flags, boosts, doc_tote and vec.
+// Caller aggregates across multiple scriptspans
+// Caller calculates final document result
+// Caller deals with detecting and triggering suppression of repeated text.
+//
+// This top-level routine just chooses the recognition type and calls one of
+// the next-level-down routines.
+//
+void ScoreOneScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec) {
+ if (scoringcontext->flags_cld2_verbose) {
+ fprintf(scoringcontext->debug_file, "<br>ScoreOneScriptSpan(%s,%d) ",
+ ULScriptCode(scriptspan.ulscript), scriptspan.text_bytes);
+ // Optionally print the chunk lowercase letters/marks text
+ string temp(&scriptspan.text[0], scriptspan.text_bytes);
+ fprintf(scoringcontext->debug_file, "'%s'",
+ GetHtmlEscapedText(temp).c_str());
+ fprintf(scoringcontext->debug_file, "<br>\n");
+ }
+ scoringcontext->prior_chunk_lang = UNKNOWN_LANGUAGE;
+ scoringcontext->oldest_distinct_boost = 0;
+ ULScriptRType rtype = ULScriptRecognitionType(scriptspan.ulscript);
+ if (scoringcontext->flags_cld2_score_as_quads && (rtype != RTypeCJK)) {
+ rtype = RTypeMany;
+ }
+ switch (rtype) {
+ case RTypeNone:
+ case RTypeOne:
+ ScoreEntireScriptSpan(scriptspan, scoringcontext, doc_tote, vec);
+ break;
+ case RTypeCJK:
+ ScoreCJKScriptSpan(scriptspan, scoringcontext, doc_tote, vec);
+ break;
+ case RTypeMany:
+ ScoreQuadScriptSpan(scriptspan, scoringcontext, doc_tote, vec);
+ break;
+ }
+}
+
+} // End namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/scoreonescriptspan.h b/browser/components/translation/cld2/internal/scoreonescriptspan.h
new file mode 100644
index 000000000..8fe717b8f
--- /dev/null
+++ b/browser/components/translation/cld2/internal/scoreonescriptspan.h
@@ -0,0 +1,297 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+//
+// Terminology:
+// Incoming original text has HTML tags and entities removed, all but letters
+// removed, and letters lowercased. Strings of non-letters are mapped to a
+// single ASCII space.
+//
+// One scriptspan has a run of letters/spaces in a single script. This is the
+// fundamental text unit that is scored. There is an optional backmap from
+// scriptspan text to the original document text, so that the language ranges
+// reported in ResultChunkVector refer to byte ranges inthe original text.
+//
+// Scripts come in two forms, the full Unicode scripts described by
+// http://www.unicode.org/Public/UNIDATA/Scripts.txt
+// and a modified list used exclusively in CLD2. The modified form maps all
+// the CJK scripts to one, Hani. The current version description is in
+// i18n/encodings/cld2/builddata/script_summary.txt
+// In addition, all non-letters are mapped to the Common script.
+//
+// ULScript describes this Unicode Letter script.
+//
+// Scoring uses text nil-grams, uni-grams, bi-grams, quad-grams, and octa-grams.
+// Nilgrams (no text lookup at all) are for script-based pseudo-languages and
+// for languages that are 1:1 with a given script. Unigrams and bigrams are
+// used to score the CJK languages, all in the Hani script. Quadgrams and
+// octagrams are used to score all other languages.
+//
+// RType is the Recognition Type per ulscript.
+//
+// The scoring tables map various grams to language-probability scores.
+// A given gram that hits in scoring table maps to an indirect subscript into
+// a list of packed languages and log probabilities.
+//
+// Languages are stored in two forms: 10-bit values in the Languge enum, and
+// shorter 8-bit per-ulscript values in the scoring tables.
+//
+// Language refers to the full 10-bit range.
+// pslang refers to the per-ulscript shorter values.
+//
+// Log probabilities also come in two forms. The full range uses values 0..255
+// to represent minus log base 10th-root-of-2, covering 1 .. 1/2**25.5 or about
+// TODO BOGUS description, 24 vs 12
+// 1/47.5M. The second form quantizes these into multiples of 8 that can be
+// added together to represent probability products. The quantized form uses
+// values 24..0 with 0 now least likely instead of most likely, thus making
+// larger sums for more probable results. 24 maps to original 1/2**4.8 (~1/28)
+// and 0 maps to original 1/2**24.0 (~1/16M).
+//
+// qprob refers to quantized log probabilities.
+//
+// langprob is a uint32 holding three 1-byte pslangs and a 1-byte subscript to
+// a list of three qprobs. It always nees a companion ulscript
+//
+// A scriptspan is scored via one or more hitbuffers
+
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_SCOREONESCRIPTSPAN_H__
+#define I18N_ENCODINGS_CLD2_INTERNAL_SCOREONESCRIPTSPAN_H__
+
+#include <stdio.h>
+
+#include "integral_types.h" // for uint8 etc.
+
+#include "cld2tablesummary.h"
+#include "compact_lang_det_impl.h" // for ResultChunkVector
+#include "getonescriptspan.h"
+#include "langspan.h"
+#include "tote.h"
+#include "utf8statetable.h"
+
+namespace CLD2 {
+
+static const int kMaxBoosts = 4; // For each of PerScriptLangBoosts
+ // must be power of two for wrap()
+static const int kChunksizeQuads = 20; // For non-CJK
+static const int kChunksizeUnis = 50; // For CJK
+static const int kMaxScoringHits = 1000;
+static const int kMaxSummaries = kMaxScoringHits / kChunksizeQuads;
+
+
+// The first four tables are for CJK languages,
+// the next three for quadgram languages, and
+// the last for expected scores.
+typedef struct {
+ const UTF8PropObj* unigram_obj; // 80K CJK characters
+ const CLD2TableSummary* unigram_compat_obj; // 256 CJK lookup probabilities
+ const CLD2TableSummary* deltabi_obj;
+ const CLD2TableSummary* distinctbi_obj;
+
+ const CLD2TableSummary* quadgram_obj; // Primary quadgram lookup table
+ const CLD2TableSummary* quadgram_obj2; // Secondary "
+ const CLD2TableSummary* deltaocta_obj;
+ const CLD2TableSummary* distinctocta_obj;
+
+ const short* kExpectedScore; // Expected base + delta + distinct score
+ // per 1KB input
+ // Subscripted by language and script4
+} ScoringTables;
+
+// Context for boosting several languages
+typedef struct {
+ int32 n;
+ uint32 langprob[kMaxBoosts];
+ int wrap(int32 n) {return n & (kMaxBoosts - 1);}
+} LangBoosts;
+
+typedef struct {
+ LangBoosts latn;
+ LangBoosts othr;
+} PerScriptLangBoosts;
+
+
+
+// ScoringContext carries state across scriptspans
+// ScoringContext also has read-only scoring tables mapping grams to qprobs
+typedef struct {
+ FILE* debug_file; // Non-NULL if debug output wanted
+ bool flags_cld2_score_as_quads;
+ bool flags_cld2_html;
+ bool flags_cld2_cr;
+ bool flags_cld2_verbose;
+ ULScript ulscript; // langprobs below are with respect to this script
+ Language prior_chunk_lang; // Mostly for debug output
+ // boost has a packed set of per-script langs and probabilites
+ // whack has a per-script lang to be suppressed from ever scoring (zeroed)
+ // When a language in a close set is given as an explicit hint, others in
+ // that set will be whacked.
+ PerScriptLangBoosts langprior_boost; // From http content-lang or meta lang=
+ PerScriptLangBoosts langprior_whack; // From http content-lang or meta lang=
+ PerScriptLangBoosts distinct_boost; // From distinctive letter groups
+ int oldest_distinct_boost; // Subscript in hitbuffer of oldest
+ // distinct score to use
+ const ScoringTables* scoringtables; // Probability lookup tables
+ ScriptScanner* scanner; // For ResultChunkVector backmap
+
+ // Inits boosts
+ void init() {
+ memset(&langprior_boost, 0, sizeof(langprior_boost));
+ memset(&langprior_whack, 0, sizeof(langprior_whack));
+ memset(&distinct_boost, 0, sizeof(distinct_boost));
+ };
+} ScoringContext;
+
+
+
+// Begin private
+
+// Holds one scoring-table lookup hit. We hold indirect subscript instead of
+// langprob to allow a single hit to use a variable number of langprobs.
+typedef struct {
+ int offset; // First byte of quad/octa etc. in scriptspan
+ int indirect; // subscript of langprobs in scoring table
+} ScoringHit;
+
+typedef enum {
+ UNIHIT = 0,
+ QUADHIT = 1,
+ DELTAHIT = 2,
+ DISTINCTHIT = 3
+} LinearHitType;
+
+// Holds one scoring-table lookup hit resolved into a langprob.
+typedef struct {
+ uint16 offset; // First byte of quad/octa etc. in scriptspan
+ uint16 type; // LinearHitType
+ uint32 langprob; // langprob from scoring table
+} LangprobHit;
+
+// Holds arrays of scoring-table lookup hits for (part of) a scriptspan
+typedef struct {
+ ULScript ulscript; // langprobs below are with respect to this script
+ int maxscoringhits; // determines size of arrays below
+ int next_base; // First unused entry in each array
+ int next_delta; // "
+ int next_distinct; // "
+ int next_linear; // "
+ int next_chunk_start; // First unused chunk_start entry
+ int lowest_offset; // First byte of text span used to fill hitbuffer
+ // Dummy entry at the end of each giving offset of first unused text byte
+ ScoringHit base[kMaxScoringHits + 1]; // Uni/quad hits
+ ScoringHit delta[kMaxScoringHits + 1]; // delta-bi/delta-octa hits
+ ScoringHit distinct[kMaxScoringHits + 1]; // distinct-word hits
+ LangprobHit linear[4 * kMaxScoringHits + 1]; // Above three merge-sorted
+ // (4: some bases => 2 linear)
+ int chunk_start[kMaxSummaries + 1]; // First linear[] subscr of
+ // each scored chunk
+ int chunk_offset[kMaxSummaries + 1]; // First text subscr of
+ // each scored chunk
+
+ void init() {
+ ulscript = ULScript_Common;
+ maxscoringhits = kMaxScoringHits;
+ next_base = 0;
+ next_delta = 0;
+ next_distinct = 0;
+ next_linear = 0;
+ next_chunk_start = 0;
+ lowest_offset = 0;
+ base[0].offset = 0;
+ base[0].indirect = 0;
+ delta[0].offset = 0;
+ delta[0].indirect = 0;
+ distinct[0].offset = 0;
+ distinct[0].indirect = 0;
+ linear[0].offset = 0;
+ linear[0].langprob = 0;
+ chunk_start[0] = 0;
+ chunk_offset[0] = 0;
+ };
+} ScoringHitBuffer;
+
+// TODO: Explain here why we need both ChunkSpan and ChunkSummary
+typedef struct {
+ int chunk_base; // Subscript of first hitbuffer.base[] in chunk
+ int chunk_delta; // Subscript of first hitbuffer.delta[]
+ int chunk_distinct; // Subscript of first hitbuffer.distinct[]
+ int base_len; // Number of hitbuffer.base[] in chunk
+ int delta_len; // Number of hitbuffer.delta[] in chunk
+ int distinct_len; // Number of hitbuffer.distinct[] in chunk
+} ChunkSpan;
+
+
+// Packed into 20 bytes for space
+typedef struct {
+ uint16 offset; // Text offset within current scriptspan.text
+ uint16 chunk_start; // Scoring subscr within hitbuffer->linear[]
+ uint16 lang1; // Top lang, mapped to full Language
+ uint16 lang2; // Second lang, mapped to full Language
+ uint16 score1; // Top lang raw score
+ uint16 score2; // Second lang raw score
+ uint16 bytes; // Number of lower letters bytes in chunk
+ uint16 grams; // Number of scored base quad- uni-grams in chunk
+ uint16 ulscript; // ULScript of chunk
+ uint8 reliability_delta; // Reliability 0..100, delta top:second scores
+ uint8 reliability_score; // Reliability 0..100, top:expected score
+} ChunkSummary;
+
+
+// We buffer up ~50 chunk summaries, corresponding to chunks of 20 quads in a
+// 1000-quad hit buffer, so we can do boundary adjustment on them
+// when adjacent entries are different languages. After that, we add them
+// all into the document score
+//
+// About 50 * 20 = 1000 bytes. OK for stack alloc
+typedef struct {
+ int n;
+ ChunkSummary chunksummary[kMaxSummaries + 1];
+} SummaryBuffer;
+
+// End private
+
+
+// Score RTypeNone or RTypeOne scriptspan into doc_tote and vec, updating
+// scoringcontext
+void ScoreEntireScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec);
+
+// Score RTypeCJK scriptspan into doc_tote and vec, updating scoringcontext
+void ScoreCJKScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec);
+
+// Score RTypeMany scriptspan into doc_tote and vec, updating scoringcontext
+void ScoreQuadScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec);
+
+// Score one scriptspan into doc_tote and vec, updating scoringcontext
+void ScoreOneScriptSpan(const LangSpan& scriptspan,
+ ScoringContext* scoringcontext,
+ DocTote* doc_tote,
+ ResultChunkVector* vec);
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_SCOREONESCRIPTSPAN_H__
+
diff --git a/browser/components/translation/cld2/internal/stringpiece.h b/browser/components/translation/cld2/internal/stringpiece.h
new file mode 100644
index 000000000..18cc4a7cc
--- /dev/null
+++ b/browser/components/translation/cld2/internal/stringpiece.h
@@ -0,0 +1,78 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// A StringPiece points to part or all of a string, double-quoted string
+// literal, or other string-like object. A StringPiece does *not* own the
+// string to which it points. A StringPiece is not null-terminated. [subset]
+//
+
+#ifndef STRINGS_STRINGPIECE_H_
+#define STRINGS_STRINGPIECE_H_
+
+#include <string.h>
+#include <string>
+
+
+typedef int stringpiece_ssize_type;
+
+class StringPiece {
+ private:
+ const char* ptr_;
+ stringpiece_ssize_type length_;
+
+ public:
+ // We provide non-explicit singleton constructors so users can pass
+ // in a "const char*" or a "string" wherever a "StringPiece" is
+ // expected.
+ StringPiece() : ptr_(NULL), length_(0) {}
+
+ StringPiece(const char* str) // NOLINT(runtime/explicit)
+ : ptr_(str), length_(0) {
+ if (str != NULL) {
+ length_ = strlen(str);
+ }
+ }
+
+ StringPiece(const std::string& str) // NOLINT(runtime/explicit)
+ : ptr_(str.data()), length_(0) {
+ length_ = str.size();
+ }
+
+ StringPiece(const char* offset, stringpiece_ssize_type len)
+ : ptr_(offset), length_(len) {
+ }
+
+ void remove_prefix(stringpiece_ssize_type n) {
+ ptr_ += n;
+ length_ -= n;
+ }
+
+ void remove_suffix(stringpiece_ssize_type n) {
+ length_ -= n;
+ }
+
+ // data() may return a pointer to a buffer with embedded NULs, and the
+ // returned buffer may or may not be null terminated. Therefore it is
+ // typically a mistake to pass data() to a routine that expects a NUL
+ // terminated string.
+ const char* data() const { return ptr_; }
+ stringpiece_ssize_type size() const { return length_; }
+ stringpiece_ssize_type length() const { return length_; }
+ bool empty() const { return length_ == 0; }
+};
+
+class StringPiece;
+
+#endif // STRINGS_STRINGPIECE_H__
diff --git a/browser/components/translation/cld2/internal/tote.cc b/browser/components/translation/cld2/internal/tote.cc
new file mode 100644
index 000000000..fbaba7d5c
--- /dev/null
+++ b/browser/components/translation/cld2/internal/tote.cc
@@ -0,0 +1,265 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#include "tote.h"
+#include "lang_script.h" // For LanguageCode in Dump
+
+#include <stdio.h>
+#include <string.h> // For memset
+
+namespace CLD2 {
+
+// Take a set of <key, value> pairs and tote them up.
+// After explicitly sorting, retrieve top key, value pairs
+// Normal use is key=per-script language and value = probability score
+Tote::Tote() {
+ in_use_mask_ = 0;
+ byte_count_ = 0;
+ score_count_ = 0;
+ // No need to initialize values
+}
+
+Tote::~Tote() {
+}
+
+void Tote::Reinit() {
+ in_use_mask_ = 0;
+ byte_count_ = 0;
+ score_count_ = 0;
+ // No need to initialize values
+}
+// Increment count of quadgrams/trigrams/unigrams scored
+void Tote::AddScoreCount() {
+ ++score_count_;
+}
+
+
+void Tote::Add(uint8 ikey, int idelta) {
+ int key_group = ikey >> 2;
+ uint64 groupmask = (1ULL << key_group);
+ if ((in_use_mask_ & groupmask) == 0) {
+ // Initialize this group
+ gscore_[key_group] = 0;
+ in_use_mask_ |= groupmask;
+ }
+ score_[ikey] += idelta;
+}
+
+
+// Return current top three keys
+void Tote::CurrentTopThreeKeys(int* key3) const {
+ key3[0] = -1;
+ key3[1] = -1;
+ key3[2] = -1;
+ int score3[3] = {-1, -1, -1};
+ uint64 tempmask = in_use_mask_;
+ int base = 0;
+ while (tempmask != 0) {
+ if (tempmask & 1) {
+ // Look at four in-use keys
+ for (int i = 0; i < 4; ++i) {
+ int insert_me = score_[base + i];
+ // Favor lower numbers on ties
+ if (insert_me > score3[2]) {
+ // Insert
+ int insert_at = 2;
+ if (insert_me > score3[1]) {
+ score3[2] = score3[1];
+ key3[2] = key3[1];
+ insert_at = 1;
+ if (insert_me > score3[0]) {
+ score3[1] = score3[0];
+ key3[1] = key3[0];
+ insert_at = 0;
+ }
+ }
+ score3[insert_at] = insert_me;
+ key3[insert_at] = base + i;
+ }
+ }
+ }
+ tempmask >>= 1;
+ base += 4;
+ }
+}
+
+
+// Take a set of <key, value> pairs and tote them up.
+// After explicitly sorting, retrieve top key, value pairs
+// 0xFFFF in key signifies unused
+DocTote::DocTote() {
+ // No need to initialize score_ or value_
+ incr_count_ = 0;
+ sorted_ = 0;
+ memset(closepair_, 0, sizeof(closepair_));
+ memset(key_, 0xFF, sizeof(key_));
+}
+
+DocTote::~DocTote() {
+}
+
+void DocTote::Reinit() {
+ // No need to initialize score_ or value_
+ incr_count_ = 0;
+ sorted_ = 0;
+ memset(closepair_, 0, sizeof(closepair_));
+ memset(key_, 0xFF, sizeof(key_));
+ runningscore_.Reinit();
+}
+
+// Weight reliability by ibytes
+// Also see three-way associative comments above for Tote
+void DocTote::Add(uint16 ikey, int ibytes,
+ int score, int ireliability) {
+ ++incr_count_;
+
+ // Look for existing entry in top 2 positions of 3, times 8 columns
+ int sub0 = ikey & 15;
+ if (key_[sub0] == ikey) {
+ value_[sub0] += ibytes;
+ score_[sub0] += score;
+ reliability_[sub0] += ireliability * ibytes;
+ return;
+ }
+ // Look for existing entry in other of top 2 positions of 3, times 8 columns
+ int sub1 = sub0 ^ 8;
+ if (key_[sub1] == ikey) {
+ value_[sub1] += ibytes;
+ score_[sub1] += score;
+ reliability_[sub1] += ireliability * ibytes;
+ return;
+ }
+ // Look for existing entry in third position of 3, times 8 columns
+ int sub2 = (ikey & 7) + 16;
+ if (key_[sub2] == ikey) {
+ value_[sub2] += ibytes;
+ score_[sub2] += score;
+ reliability_[sub2] += ireliability * ibytes;
+ return;
+ }
+
+ // Allocate new entry
+ int alloc = -1;
+ if (key_[sub0] == kUnusedKey) {
+ alloc = sub0;
+ } else if (key_[sub1] == kUnusedKey) {
+ alloc = sub1;
+ } else if (key_[sub2] == kUnusedKey) {
+ alloc = sub2;
+ } else {
+ // All choices allocated, need to replace smallest one
+ alloc = sub0;
+ if (value_[sub1] < value_[alloc]) {alloc = sub1;}
+ if (value_[sub2] < value_[alloc]) {alloc = sub2;}
+ }
+ key_[alloc] = ikey;
+ value_[alloc] = ibytes;
+ score_[alloc] = score;
+ reliability_[alloc] = ireliability * ibytes;
+ return;
+}
+
+// Find subscript of a given packed language, or -1
+int DocTote::Find(uint16 ikey) {
+ if (sorted_) {
+ // Linear search if sorted
+ for (int sub = 0; sub < kMaxSize_; ++sub) {
+ if (key_[sub] == ikey) {return sub;}
+ }
+ return -1;
+ }
+
+ // Look for existing entry
+ int sub0 = ikey & 15;
+ if (key_[sub0] == ikey) {
+ return sub0;
+ }
+ int sub1 = sub0 ^ 8;
+ if (key_[sub1] == ikey) {
+ return sub1;
+ }
+ int sub2 = (ikey & 7) + 16;
+ if (key_[sub2] == ikey) {
+ return sub2;
+ }
+
+ return -1;
+}
+
+// Return current top key
+int DocTote::CurrentTopKey() {
+ int top_key = 0;
+ int top_value = -1;
+ for (int sub = 0; sub < kMaxSize_; ++sub) {
+ if (key_[sub] == kUnusedKey) {continue;}
+ if (top_value < value_[sub]) {
+ top_value = value_[sub];
+ top_key = key_[sub];
+ }
+ }
+ return top_key;
+}
+
+
+// Sort first n entries by decreasing order of value
+// If key==0 other fields are not valid, treat value as -1
+void DocTote::Sort(int n) {
+ // This is n**2, but n is small
+ for (int sub = 0; sub < n; ++sub) {
+ if (key_[sub] == kUnusedKey) {value_[sub] = -1;}
+
+ // Bubble sort key[sub] and entry[sub]
+ for (int sub2 = sub + 1; sub2 < kMaxSize_; ++sub2) {
+ if (key_[sub2] == kUnusedKey) {value_[sub2] = -1;}
+ if (value_[sub] < value_[sub2]) {
+ // swap
+ uint16 tmpk = key_[sub];
+ key_[sub] = key_[sub2];
+ key_[sub2] = tmpk;
+
+ int tmpv = value_[sub];
+ value_[sub] = value_[sub2];
+ value_[sub2] = tmpv;
+
+ double tmps = score_[sub];
+ score_[sub] = score_[sub2];
+ score_[sub2] = tmps;
+
+ int tmpr = reliability_[sub];
+ reliability_[sub] = reliability_[sub2];
+ reliability_[sub2] = tmpr;
+ }
+ }
+ }
+ sorted_ = 1;
+}
+
+void DocTote::Dump(FILE* f) {
+ fprintf(f, "DocTote::Dump\n");
+ for (int sub = 0; sub < kMaxSize_; ++sub) {
+ if (key_[sub] != kUnusedKey) {
+ Language lang = static_cast<Language>(key_[sub]);
+ fprintf(f, "[%2d] %3s %6dB %5dp %4dR,\n", sub, LanguageCode(lang),
+ value_[sub], score_[sub], reliability_[sub]);
+ }
+ }
+ fprintf(f, " %d chunks scored<br>\n", incr_count_);
+}
+
+} // End namespace CLD2
+
diff --git a/browser/components/translation/cld2/internal/tote.h b/browser/components/translation/cld2/internal/tote.h
new file mode 100644
index 000000000..cd45592ec
--- /dev/null
+++ b/browser/components/translation/cld2/internal/tote.h
@@ -0,0 +1,112 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#ifndef I18N_ENCODINGS_CLD2_INTERNAL_TOTE_H_
+#define I18N_ENCODINGS_CLD2_INTERNAL_TOTE_H_
+
+#include <stdio.h>
+#include "integral_types.h" // for uint8 etc
+
+namespace CLD2 {
+
+
+// Take a set of <key, score> pairs and tote them up.
+// Key is an 8-bit per-script language
+// After explicitly sorting, retrieve top key, score pairs
+// Normal use is key=per-script language
+// The main data structure is an array of 256 uint16 counts. We normally
+// expect this to be initialized, added-to about 60 times, then the top three
+// items found. The reduce the initial and final time, we also keep a bit vector
+// of unused (and uninitialized) parts, each of 64 bits covering four keys.
+class Tote {
+ public:
+ Tote();
+ ~Tote();
+ void Reinit();
+ void AddScoreCount();
+ void Add(uint8 ikey, int idelta);
+ void AddBytes(int ibytes) {byte_count_ += ibytes;}
+ void CurrentTopThreeKeys(int* key3) const;
+ int GetScoreCount() const {return score_count_;}
+ int GetByteCount() const {return byte_count_;}
+ int GetScore(int i) const {return score_[i];}
+ void SetScoreCount(uint16 v) {score_count_ = v;}
+ void SetScore(int i, int v) {score_[i] = v;}
+
+ private:
+ uint64 in_use_mask_; // 64 bits, one for each group of 4 scores.
+ // 0 = not initialized,not used
+ int byte_count_; // Bytes of text scored
+ int score_count_; // Number of quadgrams/etc. scored
+ union {
+ uint64 gscore_[64]; // For alignment and clearing quickly
+ uint16 score_[256]; // Probability score sum
+ };
+
+};
+
+
+// Take a set of <key, score, reliability> triples and tote them up.
+// Key is a 16-bit full language
+// After explicitly sorting, retrieve top key, score, reliability triples
+class DocTote {
+ public:
+ DocTote();
+ ~DocTote();
+ void Reinit();
+ void Add(uint16 ikey, int ibytes, int score, int ireliability);
+ int Find(uint16 ikey);
+ void AddClosePair(int subscr, int val) {closepair_[subscr] += val;}
+ int CurrentTopKey();
+ Tote* RunningScore() {return &runningscore_;}
+ void Sort(int n);
+ void Dump(FILE* f);
+
+ int GetIncrCount() const {return incr_count_;}
+ int GetClosePair(int subscr) const {return closepair_[subscr];}
+ int MaxSize() const {return kMaxSize_;}
+ uint16 Key(int i) const {return key_[i];}
+ int Value(int i) const {return value_[i];} // byte count
+ int Score(int i) const {return score_[i];} // sum lg prob
+ int Reliability(int i) const {return reliability_[i];}
+ void SetKey(int i, int v) {key_[i] = v;}
+ void SetValue(int i, int v) {value_[i] = v;}
+ void SetScore(int i, int v) {score_[i] = v;}
+ void SetReliability(int i, int v) {reliability_[i] = v;}
+
+ static const uint16 kUnusedKey = 0xFFFF;
+
+ private:
+ static const int kMaxSize_ = 24;
+ static const int kMaxClosePairSize_ = 8;
+
+ int incr_count_; // Number of Add calls
+ int sorted_; // Contents have been sorted, cannot Add
+ Tote runningscore_; // Top lang scores across entire doc, for
+ // helping resolve close pairs
+ // Align at multiple of 8 bytes
+ int closepair_[kMaxClosePairSize_];
+ uint16 key_[kMaxSize_]; // Lang unassigned = 0xFFFF, valid = 1..1023
+ int value_[kMaxSize_]; // Bytecount this lang
+ int score_[kMaxSize_]; // Probability score sum
+ int reliability_[kMaxSize_]; // Percentage 0..100
+};
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_INTERNAL_TOTE_H_
diff --git a/browser/components/translation/cld2/internal/utf8prop_lettermarkscriptnum.h b/browser/components/translation/cld2/internal/utf8prop_lettermarkscriptnum.h
new file mode 100644
index 000000000..2eeedf840
--- /dev/null
+++ b/browser/components/translation/cld2/internal/utf8prop_lettermarkscriptnum.h
@@ -0,0 +1,1629 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Created by utf8tablebuilder version 2.9
+//
+// Maps properties of all codes from file:
+// lettermarkscriptnum_6.2.0.txt
+// Accepts all other UTF-8 codes 0000..10FFFF
+// Space optimized
+//
+// ** ASSUMES INPUT IS STRUCTURALLY VALID UTF-8 **
+//
+// Table entries are absolute statetable subscripts
+// Table entries are two bytes each
+
+#ifndef UTF8PROP_LETTERMARKSCRIPTNUM_H__
+#define UTF8PROP_LETTERMARKSCRIPTNUM_H__
+
+#include "integral_types.h"
+#include "utf8statetable.h"
+
+namespace CLD2 {
+
+#define X__ (kExitIllegalStructure_2)
+#define RJ_ (kExitReject_2)
+#define S1_ (kExitReplace1_2)
+#define S2_ (kExitReplace2_2)
+#define S3_ (kExitReplace3_2)
+#define S21 (kExitReplace21_2)
+#define S31 (kExitReplace31_2)
+#define S32 (kExitReplace32_2)
+#define T1_ (kExitReplaceOffset1_2)
+#define T2_ (kExitReplaceOffset2_2)
+#define S11 (kExitReplace1S0_2)
+#define SP_ (kExitSpecial_2)
+#define D__ (kExitDoAgain_2)
+#define RJA (kExitRejectAlt_2)
+
+// Entire table has 254 state blocks of 64 entries each
+
+static const unsigned int utf8prop_lettermarkscriptnum_STATE0 = 0; // state[0]
+static const unsigned int utf8prop_lettermarkscriptnum_STATE0_SIZE = 64; // =[1]
+static const unsigned int utf8prop_lettermarkscriptnum_TOTAL_SIZE = 16256;
+static const unsigned int utf8prop_lettermarkscriptnum_MAX_EXPAND_X4 = 0;
+static const unsigned int utf8prop_lettermarkscriptnum_SHIFT = 6;
+static const unsigned int utf8prop_lettermarkscriptnum_BYTES = 2;
+static const unsigned int utf8prop_lettermarkscriptnum_LOSUB = 0x80808080;
+static const unsigned int utf8prop_lettermarkscriptnum_HIADD = 0x00000000;
+
+static const unsigned short utf8prop_lettermarkscriptnum[] = {
+// state[0] 0x000000 Byte 1 (rows 0x-7x property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0,
+
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+X__,X__, 6, 7, 8, 8, 8, 8, 8, 8, 9, 10, 11, 12, 13, 14,
+ 15, 15, 16, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
+ 29, 62,111,126,134,136,136,136, 136,137,139,136,136,165, 2,168,
+186, 4, 4,249, 5,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+// state[2 + 2] 0x00e000 Byte 2 of 3
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[3 + 2] 0x001ac0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[4 + 2] 0x040000 Byte 2 of 4
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[5 + 2] 0x100000 Byte 2 of 4
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+// state[6 + 2] 0x000080 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+
+// state[7 + 2] 0x0000c0 Byte 2 of 2 (property)
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 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, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+
+// state[8 + 2] 0x000100 Byte 2 of 2 (property)
+ 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,
+
+// state[9 + 2] 0x000280 Byte 2 of 2 (property)
+ 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, 0, 0, 0, 0, 0, 0, 0,
+
+// state[10 + 2] 0x0002c0 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[11 + 2] 0x000300 Byte 2 of 2 (property)
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+
+// state[12 + 2] 0x000340 Byte 2 of 2 (property)
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 0, 0,
+
+// state[13 + 2] 0x000380 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 2, 0, 2, 0, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[14 + 2] 0x0003c0 Byte 2 of 2 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[15 + 2] 0x000400 Byte 2 of 2 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[16 + 2] 0x000480 Byte 2 of 2 (property)
+ 3, 3, 0, 3, 3, 40, 40, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[17 + 2] 0x000500 Byte 2 of 2 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+
+// state[18 + 2] 0x000540 Byte 2 of 2 (property)
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 0, 0, 4, 0, 0, 0, 0, 0, 0,
+ 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+
+// state[19 + 2] 0x000580 Byte 2 of 2 (property)
+ 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5,
+
+// state[20 + 2] 0x0005c0 Byte 2 of 2 (property)
+ 0, 5, 5, 0, 5, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0,
+ 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[21 + 2] 0x000600 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[22 + 2] 0x000640 Byte 2 of 2 (property)
+ 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6,
+ 40, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[23 + 2] 0x000680 Byte 2 of 2 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[24 + 2] 0x0006c0 Byte 2 of 2 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 0, 0, 6,
+
+// state[25 + 2] 0x000700 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+
+// state[26 + 2] 0x000740 Byte 2 of 2 (property)
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 7, 7, 7,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[27 + 2] 0x000780 Byte 2 of 2 (property)
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[28 + 2] 0x0007c0 Byte 2 of 2 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 65, 65, 65, 65, 65,
+ 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
+ 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
+ 65, 65, 65, 65, 65, 65, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0,
+
+// state[29 + 2] 0x000000 Byte 2 of 3
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
+ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+
+// state[30 + 2] 0x000800 Byte 3 of 3 (property)
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81,
+ 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[31 + 2] 0x000840 Byte 3 of 3 (property)
+ 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94,
+ 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[32 + 2] 0x000880 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[33 + 2] 0x0008c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0,
+
+// state[34 + 2] 0x000900 Byte 3 of 3 (property)
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+
+// state[35 + 2] 0x000940 Byte 3 of 3 (property)
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 40, 40, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9,
+
+// state[36 + 2] 0x000980 Byte 3 of 3 (property)
+ 0, 10, 10, 10, 0, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10,
+ 10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 10, 10, 10, 10, 10, 10,
+ 10, 0, 10, 0, 0, 0, 10, 10, 10, 10, 0, 0, 10, 10, 10, 10,
+
+// state[37 + 2] 0x0009c0 Byte 3 of 3 (property)
+ 10, 10, 10, 10, 10, 0, 0, 10, 10, 0, 0, 10, 10, 10, 10, 0,
+ 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 10, 0, 10,
+ 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[38 + 2] 0x000a00 Byte 3 of 3 (property)
+ 0, 11, 11, 11, 0, 11, 11, 11, 11, 11, 11, 0, 0, 0, 0, 11,
+ 11, 0, 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 11, 11, 0, 11, 11, 11, 11, 11, 11,
+ 11, 0, 11, 11, 0, 11, 11, 0, 11, 11, 0, 0, 11, 0, 11, 11,
+
+// state[39 + 2] 0x000a40 Byte 3 of 3 (property)
+ 11, 11, 11, 0, 0, 0, 0, 11, 11, 0, 0, 11, 11, 11, 0, 0,
+ 0, 11, 0, 0, 0, 0, 0, 0, 0, 11, 11, 11, 11, 0, 11, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 11, 11, 11, 11, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[40 + 2] 0x000a80 Byte 3 of 3 (property)
+ 0, 12, 12, 12, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12,
+ 12, 12, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 12,
+ 12, 0, 12, 12, 0, 12, 12, 12, 12, 12, 0, 0, 12, 12, 12, 12,
+
+// state[41 + 2] 0x000ac0 Byte 3 of 3 (property)
+ 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 0, 12, 12, 12, 0, 0,
+ 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[42 + 2] 0x000b00 Byte 3 of 3 (property)
+ 0, 13, 13, 13, 0, 13, 13, 13, 13, 13, 13, 13, 13, 0, 0, 13,
+ 13, 0, 0, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 0, 13, 13, 13, 13, 13, 13,
+ 13, 0, 13, 13, 0, 13, 13, 13, 13, 13, 0, 0, 13, 13, 13, 13,
+
+// state[43 + 2] 0x000b40 Byte 3 of 3 (property)
+ 13, 13, 13, 13, 13, 0, 0, 13, 13, 0, 0, 13, 13, 13, 0, 0,
+ 0, 0, 0, 0, 0, 0, 13, 13, 0, 0, 0, 0, 13, 13, 0, 13,
+ 13, 13, 13, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[44 + 2] 0x000b80 Byte 3 of 3 (property)
+ 0, 0, 14, 14, 0, 14, 14, 14, 14, 14, 14, 0, 0, 0, 14, 14,
+ 14, 0, 14, 14, 14, 14, 0, 0, 0, 14, 14, 0, 14, 0, 14, 14,
+ 0, 0, 0, 14, 14, 0, 0, 0, 14, 14, 14, 0, 0, 0, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 0, 0, 0, 0, 14, 14,
+
+// state[45 + 2] 0x000bc0 Byte 3 of 3 (property)
+ 14, 14, 14, 0, 0, 0, 14, 14, 14, 0, 14, 14, 14, 14, 0, 0,
+ 14, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[46 + 2] 0x000c00 Byte 3 of 3 (property)
+ 0, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15,
+ 15, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 0, 15, 15, 15, 15, 15, 0, 0, 0, 15, 15, 15,
+
+// state[47 + 2] 0x000c40 Byte 3 of 3 (property)
+ 15, 15, 15, 15, 15, 0, 15, 15, 15, 0, 15, 15, 15, 15, 0, 0,
+ 0, 0, 0, 0, 0, 15, 15, 0, 15, 15, 0, 0, 0, 0, 0, 0,
+ 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[48 + 2] 0x000c80 Byte 3 of 3 (property)
+ 0, 0, 16, 16, 0, 16, 16, 16, 16, 16, 16, 16, 16, 0, 16, 16,
+ 16, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 0, 16, 16, 16, 16,
+
+// state[49 + 2] 0x000cc0 Byte 3 of 3 (property)
+ 16, 16, 16, 16, 16, 0, 16, 16, 16, 0, 16, 16, 16, 16, 0, 0,
+ 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0,
+ 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[50 + 2] 0x000d00 Byte 3 of 3 (property)
+ 0, 0, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17,
+ 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 17, 17, 17,
+
+// state[51 + 2] 0x000d40 Byte 3 of 3 (property)
+ 17, 17, 17, 17, 17, 0, 17, 17, 17, 0, 17, 17, 17, 17, 17, 0,
+ 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0,
+ 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17,
+
+// state[52 + 2] 0x000d80 Byte 3 of 3 (property)
+ 0, 0, 18, 18, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 0, 18, 18, 18, 18, 18, 18, 18, 18, 18, 0, 18, 0, 0,
+
+// state[53 + 2] 0x000dc0 Byte 3 of 3 (property)
+ 18, 18, 18, 18, 18, 18, 18, 0, 0, 0, 18, 0, 0, 0, 0, 18,
+ 18, 18, 18, 18, 18, 0, 18, 0, 18, 18, 18, 18, 18, 18, 18, 18,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[54 + 2] 0x000e00 Byte 3 of 3 (property)
+ 0, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 0, 0, 0, 0, 0,
+
+// state[55 + 2] 0x000e40 Byte 3 of 3 (property)
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[56 + 2] 0x000e80 Byte 3 of 3 (property)
+ 0, 20, 20, 0, 20, 0, 0, 20, 20, 0, 20, 0, 0, 20, 0, 0,
+ 0, 0, 0, 0, 20, 20, 20, 20, 0, 20, 20, 20, 20, 20, 20, 20,
+ 0, 20, 20, 20, 0, 20, 0, 20, 0, 0, 20, 20, 0, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 20, 20, 20, 0, 0,
+
+// state[57 + 2] 0x000ec0 Byte 3 of 3 (property)
+ 20, 20, 20, 20, 20, 0, 20, 0, 20, 20, 20, 20, 20, 20, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 20, 20, 20,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[58 + 2] 0x000f00 Byte 3 of 3 (property)
+ 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 21, 21, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 21, 0, 21, 0, 21, 0, 0, 0, 0, 21, 21,
+
+// state[59 + 2] 0x000f40 Byte 3 of 3 (property)
+ 21, 21, 21, 21, 21, 21, 21, 21, 0, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0,
+ 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+
+// state[60 + 2] 0x000f80 Byte 3 of 3 (property)
+ 21, 21, 21, 21, 21, 0, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 0, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
+ 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 0, 0, 0,
+
+// state[61 + 2] 0x000fc0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[62 + 2] 0x001000 Byte 2 of 3
+ 63, 64, 65, 66, 67, 67, 67, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 77, 77, 77, 77, 77, 77, 77, 78, 79, 80, 81, 82, 83, 84,
+ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 3, 96, 97, 98, 99,
+100,101, 3,102,103,104,105,106, 8, 8, 8, 8,107,108,109,110,
+
+// state[63 + 2] 0x001000 Byte 3 of 3 (property)
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+
+// state[64 + 2] 0x001040 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+
+// state[65 + 2] 0x001080 Byte 3 of 3 (property)
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 22, 22, 0, 0,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+
+// state[66 + 2] 0x0010c0 Byte 3 of 3 (property)
+ 23, 23, 23, 23, 23, 23, 0, 23, 0, 0, 0, 0, 0, 23, 0, 0,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 0, 23, 23, 23, 23,
+
+// state[67 + 2] 0x001100 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[68 + 2] 0x001200 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+
+// state[69 + 2] 0x001240 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 0, 0,
+ 25, 25, 25, 25, 25, 25, 25, 0, 25, 0, 25, 25, 25, 25, 0, 0,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+
+// state[70 + 2] 0x001280 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 0, 0,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 0, 25, 25, 25, 25, 0, 0, 25, 25, 25, 25, 25, 25, 25, 0,
+
+// state[71 + 2] 0x0012c0 Byte 3 of 3 (property)
+ 25, 0, 25, 25, 25, 25, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+
+// state[72 + 2] 0x001300 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 0, 25, 25, 25, 25, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+
+// state[73 + 2] 0x001340 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 25, 25, 25,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[74 + 2] 0x001380 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+
+// state[75 + 2] 0x0013c0 Byte 3 of 3 (property)
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[76 + 2] 0x001400 Byte 3 of 3 (property)
+ 0, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+
+// state[77 + 2] 0x001440 Byte 3 of 3 (property)
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+
+// state[78 + 2] 0x001640 Byte 3 of 3 (property)
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 0, 0, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+
+// state[79 + 2] 0x001680 Byte 3 of 3 (property)
+ 0, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 0, 0, 0, 0, 0,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+
+// state[80 + 2] 0x0016c0 Byte 3 of 3 (property)
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[81 + 2] 0x001700 Byte 3 of 3 (property)
+ 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 0, 41, 41,
+ 41, 41, 41, 41, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
+ 42, 42, 42, 42, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[82 + 2] 0x001740 Byte 3 of 3 (property)
+ 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43,
+ 43, 43, 43, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 0, 44, 44,
+ 44, 0, 44, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[83 + 2] 0x001780 Byte 3 of 3 (property)
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+
+// state[84 + 2] 0x0017c0 Byte 3 of 3 (property)
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 0, 0, 0, 30, 0, 0, 0, 0, 30, 30, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[85 + 2] 0x001800 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 31, 31, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+
+// state[86 + 2] 0x001840 Byte 3 of 3 (property)
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[87 + 2] 0x001880 Byte 3 of 3 (property)
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+
+// state[88 + 2] 0x0018c0 Byte 3 of 3 (property)
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[89 + 2] 0x001900 Byte 3 of 3 (property)
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 0, 0, 0,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 0, 0, 0, 0,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 0, 0, 0, 0,
+
+// state[90 + 2] 0x001940 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
+ 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 0, 0,
+ 46, 46, 46, 46, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[91 + 2] 0x001980 Byte 3 of 3 (property)
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 0, 0, 0, 0,
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
+
+// state[92 + 2] 0x0019c0 Byte 3 of 3 (property)
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[93 + 2] 0x001a00 Byte 3 of 3 (property)
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53,
+ 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 0, 0, 0, 0,
+ 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+ 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+
+// state[94 + 2] 0x001a40 Byte 3 of 3 (property)
+ 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+ 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 0,
+ 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+ 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 0, 0, 77,
+
+// state[95 + 2] 0x001a80 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[96 + 2] 0x001b00 Byte 3 of 3 (property)
+ 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+ 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+ 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+ 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61,
+
+// state[97 + 2] 0x001b40 Byte 3 of 3 (property)
+ 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61, 61, 61, 61, 61,
+ 61, 61, 61, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[98 + 2] 0x001b80 Byte 3 of 3 (property)
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66, 66, 66, 66, 66, 66,
+
+// state[99 + 2] 0x001bc0 Byte 3 of 3 (property)
+ 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
+ 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
+ 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
+ 92, 92, 92, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[100 + 2] 0x001c00 Byte 3 of 3 (property)
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[101 + 2] 0x001c40 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 67, 67,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 0, 0,
+
+// state[102 + 2] 0x001cc0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 40, 40, 40, 0, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 0, 40, 40, 40, 40, 40, 40, 40, 0, 0, 0, 0, 40, 0, 0,
+ 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[103 + 2] 0x001d00 Byte 3 of 3 (property)
+ 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, 2, 2, 2, 2, 2, 3, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+// state[104 + 2] 0x001d40 Byte 3 of 3 (property)
+ 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, 2, 2, 2,
+ 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1,
+
+// state[105 + 2] 0x001d80 Byte 3 of 3 (property)
+ 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, 2,
+
+// state[106 + 2] 0x001dc0 Byte 3 of 3 (property)
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 40, 40, 40,
+
+// state[107 + 2] 0x001f00 Byte 3 of 3 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[108 + 2] 0x001f40 Byte 3 of 3 (property)
+ 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0,
+
+// state[109 + 2] 0x001f80 Byte 3 of 3 (property)
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0,
+
+// state[110 + 2] 0x001fc0 Byte 3 of 3 (property)
+ 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
+ 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
+ 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0,
+
+// state[111 + 2] 0x002000 Byte 2 of 3
+ 3,112,113,114,115,116,117, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+118,119,120,121,122,123,124,125, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[112 + 2] 0x002040 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+
+// state[113 + 2] 0x002080 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[114 + 2] 0x0020c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[115 + 2] 0x002100 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[116 + 2] 0x002140 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[117 + 2] 0x002180 Byte 3 of 3 (property)
+ 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[118 + 2] 0x002c00 Byte 3 of 3 (property)
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 0,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+
+// state[119 + 2] 0x002c40 Byte 3 of 3 (property)
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 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,
+
+// state[120 + 2] 0x002c80 Byte 3 of 3 (property)
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+
+// state[121 + 2] 0x002cc0 Byte 3 of 3 (property)
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 0, 0, 0, 0, 0, 0, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[122 + 2] 0x002d00 Byte 3 of 3 (property)
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 0, 23, 0, 0, 0, 0, 0, 23, 0, 0,
+ 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+
+// state[123 + 2] 0x002d40 Byte 3 of 3 (property)
+ 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+ 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+ 57, 57, 57, 57, 57, 57, 57, 57, 0, 0, 0, 0, 0, 0, 0, 57,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57,
+
+// state[124 + 2] 0x002d80 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 0,
+ 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 0,
+
+// state[125 + 2] 0x002dc0 Byte 3 of 3 (property)
+ 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 0,
+ 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 0,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[126 + 2] 0x003000 Byte 2 of 3
+127,128,129,130,131, 67,132,133, 3, 3, 3, 3, 3, 3, 3, 3,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+
+// state[127 + 2] 0x003000 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 40, 40, 40, 24, 24,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0,
+
+// state[128 + 2] 0x003040 Byte 3 of 3 (property)
+ 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[129 + 2] 0x003080 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 0, 0, 40, 40, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[130 + 2] 0x0030c0 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 24, 24, 24,
+
+// state[131 + 2] 0x003100 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 0, 0,
+ 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[132 + 2] 0x003180 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0,
+
+// state[133 + 2] 0x0031c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[134 + 2] 0x004000 Byte 2 of 3
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67,135, 3, 67, 67, 67, 67, 67, 67, 67, 67,
+
+// state[135 + 2] 0x004d80 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[136 + 2] 0x005000 Byte 2 of 3
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+
+// state[137 + 2] 0x009000 Byte 2 of 3
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,138,
+
+// state[138 + 2] 0x009fc0 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[139 + 2] 0x00a000 Byte 2 of 3
+140,140,140,140,140,140,140,140, 140,140,140,140,140,140,140,140,
+140,140,141,142,143,143,143,143, 144,145,146,147,148, 8,149,150,
+151,152,153,154,155,156,157,158, 159,160,161,162,163, 3, 3,164,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+
+// state[140 + 2] 0x00a000 Byte 3 of 3 (property)
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+
+// state[141 + 2] 0x00a480 Byte 3 of 3 (property)
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[142 + 2] 0x00a4c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82,
+ 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82,
+ 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 0, 0,
+
+// state[143 + 2] 0x00a500 Byte 3 of 3 (property)
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+
+// state[144 + 2] 0x00a600 Byte 3 of 3 (property)
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 0, 0, 0,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 69, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[145 + 2] 0x00a640 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3,
+
+// state[146 + 2] 0x00a680 Byte 3 of 3 (property)
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 3,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+
+// state[147 + 2] 0x00a6c0 Byte 3 of 3 (property)
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 83, 83, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[148 + 2] 0x00a700 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 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,
+
+// state[149 + 2] 0x00a780 Byte 3 of 3 (property)
+ 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[150 + 2] 0x00a7c0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+
+// state[151 + 2] 0x00a800 Byte 3 of 3 (property)
+ 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+ 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+ 58, 58, 58, 58, 58, 58, 58, 58, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[152 + 2] 0x00a840 Byte 3 of 3 (property)
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[153 + 2] 0x00a880 Byte 3 of 3 (property)
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+ 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70,
+
+// state[154 + 2] 0x00a8c0 Byte 3 of 3 (property)
+ 70, 70, 70, 70, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 9, 0, 0, 0, 0,
+
+// state[155 + 2] 0x00a900 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 71, 71, 71, 71, 71,
+ 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71,
+ 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 0, 0,
+ 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72,
+
+// state[156 + 2] 0x00a940 Byte 3 of 3 (property)
+ 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72,
+ 72, 72, 72, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 0,
+
+// state[157 + 2] 0x00a980 Byte 3 of 3 (property)
+ 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+ 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+ 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+ 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+
+// state[158 + 2] 0x00a9c0 Byte 3 of 3 (property)
+ 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[159 + 2] 0x00aa00 Byte 3 of 3 (property)
+ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76,
+ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76,
+ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76,
+ 76, 76, 76, 76, 76, 76, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[160 + 2] 0x00aa40 Byte 3 of 3 (property)
+ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 22, 22, 22, 22, 22, 22, 22, 0, 0, 0, 22, 22, 0, 0, 0, 0,
+
+// state[161 + 2] 0x00aa80 Byte 3 of 3 (property)
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78,
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78,
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78,
+ 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78,
+
+// state[162 + 2] 0x00aac0 Byte 3 of 3 (property)
+ 78, 78, 78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 78, 78, 0, 0,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 0, 0, 85, 85, 85, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[163 + 2] 0x00ab00 Byte 3 of 3 (property)
+ 0, 25, 25, 25, 25, 25, 25, 0, 0, 25, 25, 25, 25, 25, 25, 0,
+ 0, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 25, 25, 25, 25, 25, 25, 25, 0, 25, 25, 25, 25, 25, 25, 25, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[164 + 2] 0x00abc0 Byte 3 of 3 (property)
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 0, 85, 85, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[165 + 2] 0x00d000 Byte 2 of 3
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,166,167,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[166 + 2] 0x00d780 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[167 + 2] 0x00d7c0 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 0, 0, 0, 0, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 0, 0,
+
+// state[168 + 2] 0x00f000 Byte 2 of 3
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 67, 67, 67, 67, 67,169, 67,170,171,172,173,174,
+ 23, 23, 23, 23,175,176,177,178, 179,180, 23,181,182,183,184,185,
+
+// state[169 + 2] 0x00fa40 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[170 + 2] 0x00fac0 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[171 + 2] 0x00fb00 Byte 3 of 3 (property)
+ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 0, 5, 5, 5, 5, 5, 0, 5, 0,
+
+// state[172 + 2] 0x00fb40 Byte 3 of 3 (property)
+ 5, 5, 0, 5, 5, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[173 + 2] 0x00fb80 Byte 3 of 3 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[174 + 2] 0x00fbc0 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[175 + 2] 0x00fd00 Byte 3 of 3 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0,
+
+// state[176 + 2] 0x00fd40 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[177 + 2] 0x00fd80 Byte 3 of 3 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[178 + 2] 0x00fdc0 Byte 3 of 3 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0,
+
+// state[179 + 2] 0x00fe00 Byte 3 of 3 (property)
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 40, 40, 40, 40, 40, 40, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[180 + 2] 0x00fe40 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[181 + 2] 0x00fec0 Byte 3 of 3 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0,
+
+// state[182 + 2] 0x00ff00 Byte 3 of 3 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0,
+
+// state[183 + 2] 0x00ff40 Byte 3 of 3 (property)
+ 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, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+
+// state[184 + 2] 0x00ff80 Byte 3 of 3 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0,
+
+// state[185 + 2] 0x00ffc0 Byte 3 of 3 (property)
+ 0, 0, 24, 24, 24, 24, 24, 24, 0, 0, 24, 24, 24, 24, 24, 24,
+ 0, 0, 24, 24, 24, 24, 24, 24, 0, 0, 24, 24, 24, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[186 + 2] 0x000000 Byte 2 of 4
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+187,212,221,224, 2, 2,227, 2, 2, 2, 2,233, 2,235,239, 2,
+136,136,136,136,136,136,136,136, 136,136,243,245, 2, 2, 2,248,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[187 + 2] 0x010000 Byte 3 of 4
+188,189,190,191, 3, 3, 3,192, 3, 3,193,194,195,196,197,198,
+199,200,201, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+202,203, 3, 3,204, 3,205, 3, 206,207, 3, 3,208,209, 3, 3,
+210,211, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[188 + 2] 0x010000 Byte 4 of 4 (property)
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 0, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 0, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 0, 47, 47, 0, 47,
+
+// state[189 + 2] 0x010040 Byte 4 of 4 (property)
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 0, 0,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[190 + 2] 0x010080 Byte 4 of 4 (property)
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+
+// state[191 + 2] 0x0100c0 Byte 4 of 4 (property)
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 0, 0, 0, 0, 0,
+
+// state[192 + 2] 0x0101c0 Byte 4 of 4 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0,
+
+// state[193 + 2] 0x010280 Byte 4 of 4 (property)
+ 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
+ 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 0, 0, 0,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+
+// state[194 + 2] 0x0102c0 Byte 4 of 4 (property)
+ 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74,
+ 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[195 + 2] 0x010300 Byte 4 of 4 (property)
+ 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
+ 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+
+// state[196 + 2] 0x010340 Byte 4 of 4 (property)
+ 38, 0, 38, 38, 38, 38, 38, 38, 38, 38, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[197 + 2] 0x010380 Byte 4 of 4 (property)
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 0, 0,
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
+
+// state[198 + 2] 0x0103c0 Byte 4 of 4 (property)
+ 59, 59, 59, 59, 0, 0, 0, 0, 59, 59, 59, 59, 59, 59, 59, 59,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[199 + 2] 0x010400 Byte 4 of 4 (property)
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+
+// state[200 + 2] 0x010440 Byte 4 of 4 (property)
+ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+ 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
+
+// state[201 + 2] 0x010480 Byte 4 of 4 (property)
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[202 + 2] 0x010800 Byte 4 of 4 (property)
+ 51, 51, 51, 51, 51, 51, 0, 0, 51, 0, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 0, 51, 51, 0, 0, 0, 51, 0, 0, 51,
+
+// state[203 + 2] 0x010840 Byte 4 of 4 (property)
+ 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86, 86,
+ 86, 86, 86, 86, 86, 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, 0, 0, 0,
+
+// state[204 + 2] 0x010900 Byte 4 of 4 (property)
+ 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75,
+ 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 0, 0, 0, 0, 0, 0,
+
+// state[205 + 2] 0x010980 Byte 4 of 4 (property)
+ 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97,
+ 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97,
+ 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
+ 96, 96, 96, 96, 96, 96, 96, 96, 0, 0, 0, 0, 0, 0, 96, 96,
+
+// state[206 + 2] 0x010a00 Byte 4 of 4 (property)
+ 60, 60, 60, 60, 0, 60, 60, 0, 0, 0, 0, 0, 60, 60, 60, 60,
+ 60, 60, 60, 60, 0, 60, 60, 60, 0, 60, 60, 60, 60, 60, 60, 60,
+ 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60,
+ 60, 60, 60, 60, 0, 0, 0, 0, 60, 60, 60, 0, 0, 0, 0, 60,
+
+// state[207 + 2] 0x010a40 Byte 4 of 4 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87,
+ 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 0, 0, 0,
+
+// state[208 + 2] 0x010b00 Byte 4 of 4 (property)
+ 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79,
+ 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79,
+ 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79,
+ 79, 79, 79, 79, 79, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[209 + 2] 0x010b40 Byte 4 of 4 (property)
+ 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,
+ 89, 89, 89, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[210 + 2] 0x010c00 Byte 4 of 4 (property)
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
+
+// state[211 + 2] 0x010c40 Byte 4 of 4 (property)
+ 90, 90, 90, 90, 90, 90, 90, 90, 90, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[212 + 2] 0x011000 Byte 3 of 4
+213,214,215,216,217, 3,218,219, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,220, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[213 + 2] 0x011000 Byte 4 of 4 (property)
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,
+
+// state[214 + 2] 0x011040 Byte 4 of 4 (property)
+ 93, 93, 93, 93, 93, 93, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[215 + 2] 0x011080 Byte 4 of 4 (property)
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 0, 0, 0, 0, 0,
+
+// state[216 + 2] 0x0110c0 Byte 4 of 4 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+100,100,100,100,100,100,100,100, 100,100,100,100,100,100,100,100,
+100,100,100,100,100,100,100,100, 100, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[217 + 2] 0x011100 Byte 4 of 4 (property)
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[218 + 2] 0x011180 Byte 4 of 4 (property)
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+
+// state[219 + 2] 0x0111c0 Byte 4 of 4 (property)
+ 99, 99, 99, 99, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[220 + 2] 0x011680 Byte 4 of 4 (property)
+101,101,101,101,101,101,101,101, 101,101,101,101,101,101,101,101,
+101,101,101,101,101,101,101,101, 101,101,101,101,101,101,101,101,
+101,101,101,101,101,101,101,101, 101,101,101,101,101,101,101,101,
+101,101,101,101,101,101,101,101, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[221 + 2] 0x012000 Byte 3 of 4
+222,222,222,222,222,222,222,222, 222,222,222,222,222,223, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[222 + 2] 0x012000 Byte 4 of 4 (property)
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+
+// state[223 + 2] 0x012340 Byte 4 of 4 (property)
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[224 + 2] 0x013000 Byte 3 of 4
+225,225,225,225,225,225,225,225, 225,225,225,225,225,225,225,225,
+226, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[225 + 2] 0x013000 Byte 4 of 4 (property)
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+
+// state[226 + 2] 0x013400 Byte 4 of 4 (property)
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[227 + 2] 0x016000 Byte 3 of 4
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+228,228,228,228,228,228,228,228, 229, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,230,231,232, 3,
+
+// state[228 + 2] 0x016800 Byte 4 of 4 (property)
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+
+// state[229 + 2] 0x016a00 Byte 4 of 4 (property)
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83,
+ 83, 83, 83, 83, 83, 83, 83, 83, 83, 0, 0, 0, 0, 0, 0, 0,
+
+// state[230 + 2] 0x016f00 Byte 4 of 4 (property)
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+
+// state[231 + 2] 0x016f40 Byte 4 of 4 (property)
+ 98, 98, 98, 98, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 0,
+
+// state[232 + 2] 0x016f80 Byte 4 of 4 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 98,
+ 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[233 + 2] 0x01b000 Byte 3 of 4
+234, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[234 + 2] 0x01b000 Byte 4 of 4 (property)
+ 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[235 + 2] 0x01d000 Byte 3 of 4
+ 3, 3, 3, 3, 3,236,237, 3, 3,238, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[236 + 2] 0x01d140 Byte 4 of 4 (property)
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 40, 40, 40, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 40, 40, 40, 40,
+
+// state[237 + 2] 0x01d180 Byte 4 of 4 (property)
+ 40, 40, 40, 0, 0, 40, 40, 40, 40, 40, 40, 40, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 40, 40, 40, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[238 + 2] 0x01d240 Byte 4 of 4 (property)
+ 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[239 + 2] 0x01e000 Byte 3 of 4
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 240,241,242, 3, 3, 3, 3, 3,
+
+// state[240 + 2] 0x01ee00 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 0, 6, 6, 0, 6, 0, 0, 6, 0, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 0, 6, 0, 0, 0, 0,
+
+// state[241 + 2] 0x01ee40 Byte 4 of 4 (property)
+ 0, 0, 6, 0, 0, 0, 0, 6, 0, 6, 0, 6, 0, 6, 6, 6,
+ 0, 6, 6, 0, 6, 0, 0, 6, 0, 6, 0, 6, 0, 6, 0, 6,
+ 0, 6, 6, 0, 6, 0, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6,
+ 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 0,
+
+// state[242 + 2] 0x01ee80 Byte 4 of 4 (property)
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0,
+ 0, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0,
+
+// state[243 + 2] 0x02a000 Byte 3 of 4
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,244, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+
+// state[244 + 2] 0x02a6c0 Byte 4 of 4 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[245 + 2] 0x02b000 Byte 3 of 4
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67,246, 67, 67, 67,
+247, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[246 + 2] 0x02b700 Byte 4 of 4 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[247 + 2] 0x02b800 Byte 4 of 4 (property)
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+ 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[248 + 2] 0x02f000 Byte 3 of 4
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 67, 67, 67, 67, 67, 67, 67, 67, 247, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[249 + 2] 0x0c0000 Byte 2 of 4
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+250, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[250 + 2] 0x0e0000 Byte 3 of 4
+ 3, 3, 3, 3, 11, 11, 11,251, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[251 + 2] 0x0e01c0 Byte 4 of 4 (property)
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+};
+
+// Remap base[0] = (del, add, string_offset)
+static const RemapEntry utf8prop_lettermarkscriptnum_remap_base[] = {
+{0,0,0} };
+
+// Remap string[0]
+static const unsigned char utf8prop_lettermarkscriptnum_remap_string[] = {
+0 };
+
+static const UTF8PropObj_2 utf8prop_lettermarkscriptnum_obj = {
+ utf8prop_lettermarkscriptnum_STATE0,
+ utf8prop_lettermarkscriptnum_STATE0_SIZE,
+ utf8prop_lettermarkscriptnum_TOTAL_SIZE,
+ utf8prop_lettermarkscriptnum_MAX_EXPAND_X4,
+ utf8prop_lettermarkscriptnum_SHIFT,
+ utf8prop_lettermarkscriptnum_BYTES,
+ utf8prop_lettermarkscriptnum_LOSUB,
+ utf8prop_lettermarkscriptnum_HIADD,
+ utf8prop_lettermarkscriptnum,
+ utf8prop_lettermarkscriptnum_remap_base,
+ utf8prop_lettermarkscriptnum_remap_string,
+ NULL
+};
+
+
+#undef X__
+#undef RJ_
+#undef S1_
+#undef S2_
+#undef S3_
+#undef S21
+#undef S31
+#undef S32
+#undef T1_
+#undef T2_
+#undef S11
+#undef SP_
+#undef D__
+#undef RJA
+
+// Table has 32512 bytes, Hash = E250-03E6
+
+} // End namespace CLD2
+
+#endif // UTF8PROP_LETTERMARKSCRIPTNUM_H__
diff --git a/browser/components/translation/cld2/internal/utf8repl_lettermarklower.h b/browser/components/translation/cld2/internal/utf8repl_lettermarklower.h
new file mode 100644
index 000000000..bfef21d65
--- /dev/null
+++ b/browser/components/translation/cld2/internal/utf8repl_lettermarklower.h
@@ -0,0 +1,756 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Created by utf8tablebuilder version 2.9
+//
+// Replaces all codes from file:
+// lettermarklower_6.2.0.txt
+// Accepts all other UTF-8 codes 0000..10FFFF
+// Space optimized
+//
+// ** ASSUMES INPUT IS STRUCTURALLY VALID UTF-8 **
+//
+// Table entries are absolute statetable subscripts
+
+#ifndef UTF8REPL_LETTERMARKLOWER_H__
+#define UTF8REPL_LETTERMARKLOWER_H__
+
+#include "integral_types.h"
+#include "utf8statetable.h"
+
+namespace CLD2 {
+
+#define X__ (kExitIllegalStructure)
+#define RJ_ (kExitReject)
+#define S1_ (kExitReplace1)
+#define S2_ (kExitReplace2)
+#define S3_ (kExitReplace3)
+#define S21 (kExitReplace21)
+#define S31 (kExitReplace31)
+#define S32 (kExitReplace32)
+#define T1_ (kExitReplaceOffset1)
+#define T2_ (kExitReplaceOffset2)
+#define S11 (kExitReplace1S0)
+#define SP_ (kExitSpecial)
+#define D__ (kExitDoAgain)
+#define RJA (kExitRejectAlt)
+
+// Entire table has 111 state blocks of 64 entries each
+
+static const unsigned int utf8repl_lettermarklower_STATE0 = 0; // state[0]
+static const unsigned int utf8repl_lettermarklower_STATE0_SIZE = 320; // =[5]
+static const unsigned int utf8repl_lettermarklower_TOTAL_SIZE = 7104;
+static const unsigned int utf8repl_lettermarklower_MAX_EXPAND_X4 = 12;
+static const unsigned int utf8repl_lettermarklower_SHIFT = 6;
+static const unsigned int utf8repl_lettermarklower_BYTES = 1;
+static const unsigned int utf8repl_lettermarklower_LOSUB = 0x5b5b5b5b;
+static const unsigned int utf8repl_lettermarklower_HIADD = 0x00000000;
+
+static const uint8 utf8repl_lettermarklower[] = {
+// state[0] 0x000000 Byte 1
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0,S11,S11,S11,S11,S11,S11,S11, S11,S11,S11,S11,S11,S11,S11,S11,
+S11,S11,S11,S11,S11,S11,S11,S11, S11,S11,S11, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+X__,X__, 6, 11, 13, 16, 19, 22, 25, 28, 6, 6, 6, 31, 33, 36,
+ 39, 42, 44, 46, 48, 51, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 54, 74, 8, 8, 8, 8, 8, 8, 8, 88, 8, 8, 8, 8,100,
+104, 9, 9, 9, 10,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x61,0x62,0x63,0x64,0x65,0x66,0x67, 0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
+0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, 0x78,0x79,0x7a,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[6 + 2] 0x000080 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[7 + 2] 0x000000 Byte 2 of 3
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[8 + 2] 0x003000 Byte 2 of 3
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[9 + 2] 0x040000 Byte 2 of 4
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+
+// state[10 + 2] 0x100000 Byte 2 of 4
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+// state[11 + 2] 0x0000c0 Byte 2 of 2
+S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+S1_,S1_,S1_,S1_,S1_,S1_,S1_, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, 0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
+0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0x00, 0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[13 + 2] 0x000100 Byte 2 of 2
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S21, 0,S1_, 0,S1_, 0,S1_, 0, 0,S1_, 0,S1_, 0,S1_, 0,S2_,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0x69,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0x00,0xba,0x00,0xbc,0x00,0xbe,0x00,0x80,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc5,
+
+// state[16 + 2] 0x000140 Byte 2 of 2
+ 0,S1_, 0,S1_, 0,S1_, 0,S1_, 0, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S2_,S1_, 0,S1_, 0,S1_, 0, 0,
+
+0x00,0x82,0x00,0x84,0x00,0x86,0x00,0x88, 0x00,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0xb1,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0xbf,0xba,0x00,0xbc,0x00,0xbe,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xc3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[19 + 2] 0x000180 Byte 2 of 2
+ 0,S2_,S1_, 0,S1_, 0,S2_,S1_, 0,S2_,S2_,S1_, 0, 0,S2_,S2_,
+S2_,S1_, 0,S2_,S2_, 0,S2_,S2_, S1_, 0, 0, 0,S2_,S2_, 0,S2_,
+S1_, 0,S1_, 0,S1_, 0,S2_,S1_, 0,S2_, 0, 0,S1_, 0,S2_,S1_,
+ 0,S2_,S2_,S1_, 0,S1_, 0,S2_, S1_, 0, 0, 0,S1_, 0, 0, 0,
+
+0x00,0x93,0x83,0x00,0x85,0x00,0x94,0x88, 0x00,0x96,0x97,0x8c,0x00,0x00,0x9d,0x99,
+0x9b,0x92,0x00,0xa0,0xa3,0x00,0xa9,0xa8, 0x99,0x00,0x00,0x00,0xaf,0xb2,0x00,0xb5,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0x80,0xa8, 0x00,0x83,0x00,0x00,0xad,0x00,0x88,0xb0,
+0x00,0x8a,0x8b,0xb4,0x00,0xb6,0x00,0x92, 0xb9,0x00,0x00,0x00,0xbd,0x00,0x00,0x00,
+
+0x00,0xc9,0x00,0x00,0x00,0x00,0xc9,0x00, 0x00,0xc9,0xc9,0x00,0x00,0x00,0xc7,0xc9,
+0xc9,0x00,0x00,0xc9,0xc9,0x00,0xc9,0xc9, 0x00,0x00,0x00,0x00,0xc9,0xc9,0x00,0xc9,
+0x00,0x00,0x00,0x00,0x00,0x00,0xca,0x00, 0x00,0xca,0x00,0x00,0x00,0x00,0xca,0x00,
+0x00,0xca,0xca,0x00,0x00,0x00,0x00,0xca, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[22 + 2] 0x0001c0 Byte 2 of 2
+ 0, 0, 0, 0,S1_,S1_, 0,S1_, S1_, 0,S1_,S1_, 0,S1_, 0,S1_,
+ 0,S1_, 0,S1_, 0,S1_, 0,S1_, 0,S1_, 0,S1_, 0, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+ 0,S1_,S1_, 0,S1_, 0,S2_,S2_, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+
+0x00,0x00,0x00,0x00,0x86,0x86,0x00,0x89, 0x89,0x00,0x8c,0x8c,0x00,0x8e,0x00,0x90,
+0x00,0x92,0x00,0x94,0x00,0x96,0x00,0x98, 0x00,0x9a,0x00,0x9c,0x00,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0x00,0xb3,0xb3,0x00,0xb5,0x00,0x95,0xbf, 0xb9,0x00,0xbb,0x00,0xbd,0x00,0xbf,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0xc6,0xc6, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[25 + 2] 0x000200 Byte 2 of 2
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S2_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0, 0, 0, 0, 0, 0, 0,T1_,S1_, 0,S2_,T1_, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0x9e,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0xb1,0x00,0xb3,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0xbc,0x00,0x9a,0x01,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0xc6,0x00,0x00,
+
+// state[28 + 2] 0x000240 Byte 2 of 2
+ 0,S1_, 0,S2_,S2_,S2_,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x00,0x82,0x00,0x80,0x89,0x8c,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0xc6,0xca,0xca,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[31 + 2] 0x000340 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+S1_, 0,S1_, 0, 0, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xb1,0x00,0xb3,0x00,0x00,0x00,0xb7,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[33 + 2] 0x000380 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0,S1_, 0, S1_,S1_,S1_, 0,S2_, 0,S2_,S2_,
+ 0,S1_,S1_,S1_,S1_,S1_,S1_,S1_, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+S2_,S2_, 0,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_,S2_, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0xac,0x00, 0xad,0xae,0xaf,0x00,0x8c,0x00,0x8d,0x8e,
+0x00,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7, 0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
+0x80,0x81,0x00,0x83,0x84,0x85,0x86,0x87, 0x88,0x89,0x8a,0x8b,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0xcf,0x00,0xcf,0xcf,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xcf,0xcf,0x00,0xcf,0xcf,0xcf,0xcf,0xcf, 0xcf,0xcf,0xcf,0xcf,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[36 + 2] 0x0003c0 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+ 0, 0, 0, 0,S2_, 0, 0,S1_, 0,S1_,S1_, 0, 0,S2_,S2_,S2_,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x97,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0x00,0x00,0x00,0x00,0xb8,0x00,0x00,0xb8, 0x00,0xb2,0xbb,0x00,0x00,0xbb,0xbc,0xbd,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0xce,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0xcd,0xcd,0xcd,
+
+// state[39 + 2] 0x000400 Byte 2 of 2
+S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_,
+S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7, 0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
+0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1, 0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1, 0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[42 + 2] 0x000440 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0xb1,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0xb9,0x00,0xbb,0x00,0xbd,0x00,0xbf,0x00,
+
+// state[44 + 2] 0x000480 Byte 2 of 2
+S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+
+0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0xb1,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0xb9,0x00,0xbb,0x00,0xbd,0x00,0xbf,0x00,
+
+// state[46 + 2] 0x0004c0 Byte 2 of 2
+S1_,S1_, 0,S1_, 0,S1_, 0,S1_, 0,S1_, 0,S1_, 0,S1_, 0, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+
+0x8f,0x82,0x00,0x84,0x00,0x86,0x00,0x88, 0x00,0x8a,0x00,0x8c,0x00,0x8e,0x00,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0xb1,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0xb9,0x00,0xbb,0x00,0xbd,0x00,0xbf,0x00,
+
+// state[48 + 2] 0x000500 Byte 2 of 2
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,S2_,S2_,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, 0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5, 0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,0xd5,
+
+// state[51 + 2] 0x000540 Byte 2 of 2
+S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+S2_,S2_,S2_,S2_,S2_,S2_,S2_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7, 0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
+0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0xd6,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[54 + 2] 0x001000 Byte 2 of 3
+ 6, 6, 55, 57, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 59, 59, 61, 59, 64, 66, 68, 71,
+
+// state[55 + 2] 0x001080 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+T1_,T1_,T1_,T1_,T1_,T1_,T1_,T1_, T1_,T1_,T1_,T1_,T1_,T1_,T1_,T1_,
+T1_,T1_,T1_,T1_,T1_,T1_,T1_,T1_, T1_,T1_,T1_,T1_,T1_,T1_,T1_,T1_,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, 0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,
+0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19, 0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,
+
+// state[57 + 2] 0x0010c0 Byte 3 of 3
+T1_,T1_,T1_,T1_,T1_,T1_, 0,T1_, 0, 0, 0, 0, 0,T1_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x22,0x23,0x24,0x25,0x26,0x27,0x00,0x28, 0x00,0x00,0x00,0x00,0x00,0x29,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[59 + 2] 0x001e00 Byte 3 of 3
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0xb1,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0xb9,0x00,0xbb,0x00,0xbd,0x00,0xbf,0x00,
+
+// state[61 + 2] 0x001e80 Byte 3 of 3
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0,S32, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0xb1,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0xb9,0x00,0xbb,0x00,0xbd,0x00,0xbf,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc3,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[64 + 2] 0x001f00 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x90,0x91,0x92,0x93,0x94,0x95,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,
+
+// state[66 + 2] 0x001f40 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,S1_, 0,S1_, 0,S1_, 0,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x80,0x81,0x82,0x83,0x84,0x85,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x91,0x00,0x93,0x00,0x95,0x00,0x97,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[68 + 2] 0x001f80 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S2_,S2_,S1_, 0, 0, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xb0,0xb1,0xb0,0xb1,0xb3,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xbd,0xbd,0x00,0x00,0x00,0x00,
+
+// state[71 + 2] 0x001fc0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, S2_,S2_,S2_,S2_,S1_, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S2_,S2_, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, S1_,S1_,S2_,S2_,S1_, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, S2_,S2_,S2_,S2_,S1_, 0, 0, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xb2,0xb3,0xb4,0xb5,0x83,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x90,0x91,0xb6,0xb7,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xa0,0xa1,0xba,0xbb,0xa5,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xb8,0xb9,0xbc,0xbd,0xb3,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xbd,0xbd,0xbd,0xbd,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xbd,0xbd,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xbd,0xbd,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xbd,0xbd,0xbd,0xbd,0x00,0x00,0x00,0x00,
+
+// state[74 + 2] 0x002000 Byte 2 of 3
+ 6, 6, 6, 6, 75, 6, 78, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 80, 83, 59, 86, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[75 + 2] 0x002100 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,S32, 0, 0, 0,S31,S32, 0, 0, 0, 0,
+ 0, 0,S2_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x89,0x00, 0x00,0x00,0x6b,0xa5,0x00,0x00,0x00,0x00,
+0x00,0x00,0x8e,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0x00, 0x00,0x00,0x00,0xc3,0x00,0x00,0x00,0x00,
+0x00,0x00,0x85,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[78 + 2] 0x002180 Byte 3 of 3
+ 0, 0, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x00,0x00,0x00,0x84,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[80 + 2] 0x002c00 Byte 3 of 3
+S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_,
+S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_,S2_,S2_,S2_,S2_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7, 0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
+0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1, 0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,
+0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1, 0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0xb1,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[83 + 2] 0x002c40 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+S1_, 0,S32,T1_,S32, 0, 0,S1_, 0,S1_, 0,S1_, 0,S32,S32,S32,
+S32, 0,S1_, 0, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0,S32,S32,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xa1,0x00,0xab,0x2a,0xbd,0x00,0x00,0xa8, 0x00,0xaa,0x00,0xac,0x00,0x91,0xb1,0x90,
+0x92,0x00,0xb3,0x00,0x00,0xb6,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xbf,0x80,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0xc9,0x00,0xc9,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0xc9,0xc9,0xc9,
+0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0xc8,0xc9,
+
+// state[86 + 2] 0x002cc0 Byte 3 of 3
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0,S1_, 0,S1_, 0, 0,
+ 0, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0xac,0x00,0xae,0x00,0x00,
+0x00,0x00,0xb3,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[88 + 2] 0x00a000 Byte 2 of 3
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 89, 91, 6, 93, 95, 97, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[89 + 2] 0x00a640 Byte 3 of 3
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[91 + 2] 0x00a680 Byte 3 of 3
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[93 + 2] 0x00a700 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+ 0, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0x00,0x00,0xb3,0x00,0xb5,0x00,0xb7,0x00, 0xb9,0x00,0xbb,0x00,0xbd,0x00,0xbf,0x00,
+
+// state[95 + 2] 0x00a740 Byte 3 of 3
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S1_, 0,S1_, 0,S1_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0,S1_, 0,S1_, 0,T1_,S1_, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x89,0x00,0x8b,0x00,0x8d,0x00,0x8f,0x00,
+0x91,0x00,0x93,0x00,0x95,0x00,0x97,0x00, 0x99,0x00,0x9b,0x00,0x9d,0x00,0x9f,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xab,0x00,0xad,0x00,0xaf,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0xba,0x00,0xbc,0x00,0x2b,0xbf,0x00,
+
+// state[97 + 2] 0x00a780 Byte 3 of 3
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, 0, 0, 0,S1_, 0,S32, 0, 0,
+S1_, 0,S1_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+S1_, 0,S1_, 0,S1_, 0,S1_, 0, S1_, 0,S32, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0x81,0x00,0x83,0x00,0x85,0x00,0x87,0x00, 0x00,0x00,0x00,0x8c,0x00,0xa5,0x00,0x00,
+0x91,0x00,0x93,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xa1,0x00,0xa3,0x00,0xa5,0x00,0xa7,0x00, 0xa9,0x00,0xa6,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+// state[100 + 2] 0x00f000 Byte 2 of 3
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,101, 6, 6, 6,
+
+// state[101 + 2] 0x00ff00 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,S2_,S2_,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_,
+S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_, S2_,S2_,S2_, 0, 0, 0, 0, 0,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, 0x98,0x99,0x9a,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd, 0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,
+0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd,0xbd, 0xbd,0xbd,0xbd,0x00,0x00,0x00,0x00,0x00,
+
+// state[104 + 2] 0x000000 Byte 2 of 4
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+105, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+
+// state[105 + 2] 0x010000 Byte 3 of 4
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+106, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+
+// state[106 + 2] 0x010400 Byte 4 of 4
+S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_, S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_,
+S1_,S1_,S1_,S1_,S1_,S1_,S1_,S1_, S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_,
+S2_,S2_,S2_,S2_,S2_,S2_,S2_,S2_, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,
+0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
+0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91,
+0x91,0x91,0x91,0x91,0x91,0x91,0x91,0x91, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+
+};
+
+// Remap base[44] = (del, add, string_offset)
+static const RemapEntry utf8repl_lettermarklower_remap_base[] = {
+{2,3, 0}, {2,3, 3}, {3,3, 6}, {3,3, 9},
+{3,3, 12}, {3,3, 15}, {3,3, 18}, {3,3, 21},
+{3,3, 24}, {3,3, 27}, {3,3, 30}, {3,3, 33},
+{3,3, 36}, {3,3, 39}, {3,3, 42}, {3,3, 45},
+
+{3,3, 48}, {3,3, 51}, {3,3, 54}, {3,3, 57},
+{3,3, 60}, {3,3, 63}, {3,3, 66}, {3,3, 69},
+{3,3, 72}, {3,3, 75}, {3,3, 78}, {3,3, 81},
+{3,3, 84}, {3,3, 87}, {3,3, 90}, {3,3, 93},
+
+{3,3, 96}, {3,3, 99}, {3,3, 102}, {3,3, 105},
+{3,3, 108}, {3,3, 111}, {3,3, 114}, {3,3, 117},
+{3,3, 120}, {3,3, 123}, {3,3, 126}, {3,3, 129},
+{0,0,0} };
+
+// Remap string[132]
+static const unsigned char utf8repl_lettermarklower_remap_string[] = {
+0xe2,0xb1,0xa5,0xe2,0xb1,0xa6,0xe2,0xb4, 0x80,0xe2,0xb4,0x81,0xe2,0xb4,0x82,0xe2,
+0xb4,0x83,0xe2,0xb4,0x84,0xe2,0xb4,0x85, 0xe2,0xb4,0x86,0xe2,0xb4,0x87,0xe2,0xb4,
+0x88,0xe2,0xb4,0x89,0xe2,0xb4,0x8a,0xe2, 0xb4,0x8b,0xe2,0xb4,0x8c,0xe2,0xb4,0x8d,
+0xe2,0xb4,0x8e,0xe2,0xb4,0x8f,0xe2,0xb4, 0x90,0xe2,0xb4,0x91,0xe2,0xb4,0x92,0xe2,
+
+0xb4,0x93,0xe2,0xb4,0x94,0xe2,0xb4,0x95, 0xe2,0xb4,0x96,0xe2,0xb4,0x97,0xe2,0xb4,
+0x98,0xe2,0xb4,0x99,0xe2,0xb4,0x9a,0xe2, 0xb4,0x9b,0xe2,0xb4,0x9c,0xe2,0xb4,0x9d,
+0xe2,0xb4,0x9e,0xe2,0xb4,0x9f,0xe2,0xb4, 0xa0,0xe2,0xb4,0xa1,0xe2,0xb4,0xa2,0xe2,
+0xb4,0xa3,0xe2,0xb4,0xa4,0xe2,0xb4,0xa5, 0xe2,0xb4,0xa7,0xe2,0xb4,0xad,0xe1,0xb5,
+
+0xbd,0xe1,0xb5,0xb9,0 };
+
+static const unsigned char utf8repl_lettermarklower_fast[256] = {
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,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,0,0,0,0,0,
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,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,1,1, 1,1,1,1,1,1,1,1,
+
+};
+
+static const UTF8ReplaceObj utf8repl_lettermarklower_obj = {
+ utf8repl_lettermarklower_STATE0,
+ utf8repl_lettermarklower_STATE0_SIZE,
+ utf8repl_lettermarklower_TOTAL_SIZE,
+ utf8repl_lettermarklower_MAX_EXPAND_X4,
+ utf8repl_lettermarklower_SHIFT,
+ utf8repl_lettermarklower_BYTES,
+ utf8repl_lettermarklower_LOSUB,
+ utf8repl_lettermarklower_HIADD,
+ utf8repl_lettermarklower,
+ utf8repl_lettermarklower_remap_base,
+ utf8repl_lettermarklower_remap_string,
+ utf8repl_lettermarklower_fast
+};
+
+
+#undef X__
+#undef RJ_
+#undef S1_
+#undef S2_
+#undef S3_
+#undef S21
+#undef S31
+#undef S32
+#undef T1_
+#undef T2_
+#undef S11
+#undef SP_
+#undef D__
+#undef RJA
+
+// Table has 7668 bytes, Hash = 07A2-C4E3
+
+} // End namespace CLD2
+
+#endif // UTF8REPL_LETTERMARKLOWER_H__
diff --git a/browser/components/translation/cld2/internal/utf8scannot_lettermarkspecial.h b/browser/components/translation/cld2/internal/utf8scannot_lettermarkspecial.h
new file mode 100644
index 000000000..c71d47bf6
--- /dev/null
+++ b/browser/components/translation/cld2/internal/utf8scannot_lettermarkspecial.h
@@ -0,0 +1,1453 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Created by utf8tablebuilder version 2.9
+//
+// Rejects all codes from file:
+// lettermarkspecial_6.2.0.txt
+// Accepts all other UTF-8 codes 0000..10FFFF
+// Space optimized
+//
+// ** ASSUMES INPUT IS STRUCTURALLY VALID UTF-8 **
+//
+// Table entries are absolute statetable subscripts
+
+#ifndef UTF8SCANNOT_LETTERMARKSPECIAL_H__
+#define UTF8SCANNOT_LETTERMARKSPECIAL_H__
+
+#include "integral_types.h"
+#include "utf8statetable.h"
+
+namespace CLD2 {
+
+#define X__ (kExitIllegalStructure)
+#define RJ_ (kExitReject)
+#define S1_ (kExitReplace1)
+#define S2_ (kExitReplace2)
+#define S3_ (kExitReplace3)
+#define S21 (kExitReplace21)
+#define S31 (kExitReplace31)
+#define S32 (kExitReplace32)
+#define T1_ (kExitReplaceOffset1)
+#define T2_ (kExitReplaceOffset2)
+#define S11 (kExitReplace1S0)
+#define SP_ (kExitSpecial)
+#define D__ (kExitDoAgain)
+#define RJA (kExitRejectAlt)
+
+// Entire table has 221 state blocks of 64 entries each
+
+static const unsigned int utf8scannot_lettermarkspecial_STATE0 = 0; // state[0]
+static const unsigned int utf8scannot_lettermarkspecial_STATE0_SIZE = 64; // =[1]
+static const unsigned int utf8scannot_lettermarkspecial_TOTAL_SIZE = 14144;
+static const unsigned int utf8scannot_lettermarkspecial_MAX_EXPAND_X4 = 0;
+static const unsigned int utf8scannot_lettermarkspecial_SHIFT = 6;
+static const unsigned int utf8scannot_lettermarkspecial_BYTES = 1;
+static const unsigned int utf8scannot_lettermarkspecial_LOSUB = 0x27272727;
+static const unsigned int utf8scannot_lettermarkspecial_HIADD = 0x44444444;
+
+static const uint8 utf8scannot_lettermarkspecial[] = {
+// state[0] 0x000000 Byte 1
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_, 0,RJ_, 0,
+
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+X__,X__, 6, 7, 8, 8, 8, 8, 8, 8, 8, 9, 8, 10, 11, 12,
+ 8, 8, 13, 8, 14, 15, 16, 17, 18, 19, 8, 20, 21, 22, 23, 24,
+ 25, 57, 95,110,117,118,118,118, 118,119,121,118,118,140, 2,143,
+159, 4, 4,216, 5,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+// state[2 + 2] 0x00e000 Byte 2 of 3
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[3 + 2] 0x001ac0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[4 + 2] 0x040000 Byte 2 of 4
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[5 + 2] 0x100000 Byte 2 of 4
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+
+// state[6 + 2] 0x000080 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0,
+
+// state[7 + 2] 0x0000c0 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[8 + 2] 0x000100 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[9 + 2] 0x0002c0 Byte 2 of 2
+RJ_,RJ_, 0, 0, 0, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0,RJ_, 0,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[10 + 2] 0x000340 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+
+// state[11 + 2] 0x000380 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0,RJ_, 0, RJ_,RJ_,RJ_, 0,RJ_, 0,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[12 + 2] 0x0003c0 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[13 + 2] 0x000480 Byte 2 of 2
+RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[14 + 2] 0x000500 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[15 + 2] 0x000540 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_, 0, 0, 0, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[16 + 2] 0x000580 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,
+
+// state[17 + 2] 0x0005c0 Byte 2 of 2
+ 0,RJ_,RJ_, 0,RJ_,RJ_, 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[18 + 2] 0x000600 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[19 + 2] 0x000640 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[20 + 2] 0x0006c0 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_, 0, 0,RJ_,
+
+// state[21 + 2] 0x000700 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[22 + 2] 0x000740 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[23 + 2] 0x000780 Byte 2 of 2
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[24 + 2] 0x0007c0 Byte 2 of 2
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0,
+
+// state[25 + 2] 0x000000 Byte 2 of 3
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+ 26, 27, 28, 29, 8, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+
+// state[26 + 2] 0x000800 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[27 + 2] 0x000840 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[28 + 2] 0x000880 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[29 + 2] 0x0008c0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+
+// state[30 + 2] 0x000940 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[31 + 2] 0x000980 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,
+RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0,RJ_, 0, 0, 0,RJ_,RJ_, RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[32 + 2] 0x0009c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_, RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0,RJ_,RJ_, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[33 + 2] 0x000a00 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0,RJ_,
+RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_, 0,RJ_,RJ_, 0, RJ_,RJ_, 0, 0,RJ_, 0,RJ_,RJ_,
+
+// state[34 + 2] 0x000a40 Byte 3 of 3
+RJ_,RJ_,RJ_, 0, 0, 0, 0,RJ_, RJ_, 0, 0,RJ_,RJ_,RJ_, 0, 0,
+ 0,RJ_, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[35 + 2] 0x000a80 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,
+RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[36 + 2] 0x000ac0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, RJ_,RJ_, 0,RJ_,RJ_,RJ_, 0, 0,
+RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[37 + 2] 0x000b00 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,
+RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[38 + 2] 0x000b40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_, RJ_, 0, 0,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0,RJ_,RJ_, 0, 0, 0, 0,RJ_,RJ_, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[39 + 2] 0x000b80 Byte 3 of 3
+ 0, 0,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,RJ_,RJ_, 0,RJ_, 0,RJ_,RJ_,
+ 0, 0, 0,RJ_,RJ_, 0, 0, 0, RJ_,RJ_,RJ_, 0, 0, 0,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0, 0,RJ_,RJ_,
+
+// state[40 + 2] 0x000bc0 Byte 3 of 3
+RJ_,RJ_,RJ_, 0, 0, 0,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_, 0, 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[41 + 2] 0x000c00 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0,RJ_,RJ_,RJ_,
+
+// state[42 + 2] 0x000c40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0,RJ_,RJ_, 0, RJ_,RJ_, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[43 + 2] 0x000c80 Byte 3 of 3
+ 0, 0,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[44 + 2] 0x000cc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[45 + 2] 0x000d00 Byte 3 of 3
+ 0, 0,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,
+
+// state[46 + 2] 0x000d40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[47 + 2] 0x000d80 Byte 3 of 3
+ 0, 0,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0, 0,
+
+// state[48 + 2] 0x000dc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,RJ_, 0, 0, 0, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[49 + 2] 0x000e00 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+
+// state[50 + 2] 0x000e40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[51 + 2] 0x000e80 Byte 3 of 3
+ 0,RJ_,RJ_, 0,RJ_, 0, 0,RJ_, RJ_, 0,RJ_, 0, 0,RJ_, 0, 0,
+ 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0,RJ_,RJ_,RJ_, 0,RJ_, 0,RJ_, 0, 0,RJ_,RJ_, 0,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0,RJ_,RJ_,RJ_, 0, 0,
+
+// state[52 + 2] 0x000ec0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[53 + 2] 0x000f00 Byte 3 of 3
+RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, RJ_,RJ_, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,RJ_, 0,RJ_, 0,RJ_, 0, 0, 0, 0,RJ_,RJ_,
+
+// state[54 + 2] 0x000f40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[55 + 2] 0x000f80 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+
+// state[56 + 2] 0x000fc0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[57 + 2] 0x001000 Byte 2 of 3
+ 8, 21, 58, 59, 8, 8, 8, 8, 8, 60, 61, 62, 63, 64, 65, 66,
+ 67, 8, 8, 8, 8, 8, 8, 8, 8, 68, 69, 70, 71, 72, 8, 73,
+ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 3, 8, 85, 86, 87,
+ 75, 88, 3, 89, 8, 8, 8, 90, 8, 8, 8, 8, 91, 92, 93, 94,
+
+// state[58 + 2] 0x001080 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[59 + 2] 0x0010c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0, 0, 0, 0, 0,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[60 + 2] 0x001240 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[61 + 2] 0x001280 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+
+// state[62 + 2] 0x0012c0 Byte 3 of 3
+RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[63 + 2] 0x001300 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[64 + 2] 0x001340 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[65 + 2] 0x001380 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[66 + 2] 0x0013c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[67 + 2] 0x001400 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[68 + 2] 0x001640 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[69 + 2] 0x001680 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[70 + 2] 0x0016c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[71 + 2] 0x001700 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[72 + 2] 0x001740 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[73 + 2] 0x0017c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0,RJ_, 0, 0, 0, 0,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[74 + 2] 0x001800 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[75 + 2] 0x001840 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[76 + 2] 0x001880 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[77 + 2] 0x0018c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[78 + 2] 0x001900 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+
+// state[79 + 2] 0x001940 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[80 + 2] 0x001980 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[81 + 2] 0x0019c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[82 + 2] 0x001a00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[83 + 2] 0x001a40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,
+
+// state[84 + 2] 0x001a80 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[85 + 2] 0x001b40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[86 + 2] 0x001b80 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[87 + 2] 0x001bc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[88 + 2] 0x001c40 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+
+// state[89 + 2] 0x001cc0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[90 + 2] 0x001dc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[91 + 2] 0x001f00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[92 + 2] 0x001f40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0,RJ_, 0,RJ_, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+
+// state[93 + 2] 0x001f80 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0,
+
+// state[94 + 2] 0x001fc0 Byte 3 of 3
+ 0, 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+ 0, 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+
+// state[95 + 2] 0x002000 Byte 2 of 3
+ 3, 96, 97, 98, 99,100,101, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+102,103, 8,104,105,106,107,108, 109, 3, 3, 3, 3, 3, 3, 3,
+
+// state[96 + 2] 0x002040 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,
+
+// state[97 + 2] 0x002080 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[98 + 2] 0x0020c0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[99 + 2] 0x002100 Byte 3 of 3
+ 0, 0,RJ_, 0, 0, 0, 0,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0,RJ_, 0,RJ_, 0, RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[100 + 2] 0x002140 Byte 3 of 3
+ 0, 0, 0, 0, 0,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0, 0,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[101 + 2] 0x002180 Byte 3 of 3
+ 0, 0, 0,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[102 + 2] 0x002c00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[103 + 2] 0x002c40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[104 + 2] 0x002cc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[105 + 2] 0x002d00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0, 0, 0, 0, 0,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[106 + 2] 0x002d40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,
+
+// state[107 + 2] 0x002d80 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+
+// state[108 + 2] 0x002dc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[109 + 2] 0x002e00 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[110 + 2] 0x003000 Byte 2 of 3
+111, 67,112,113,114, 8,115,116, 3, 3, 3, 3, 3, 3, 3, 3,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+
+// state[111 + 2] 0x003000 Byte 3 of 3
+ 0, 0, 0, 0, 0,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,RJ_,RJ_, 0, 0, 0,
+
+// state[112 + 2] 0x003080 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[113 + 2] 0x0030c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[114 + 2] 0x003100 Byte 3 of 3
+ 0, 0, 0, 0, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[115 + 2] 0x003180 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+
+// state[116 + 2] 0x0031c0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[117 + 2] 0x004000 Byte 2 of 3
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 77, 3, 8, 8, 8, 8, 8, 8, 8, 8,
+
+// state[118 + 2] 0x005000 Byte 2 of 3
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+
+// state[119 + 2] 0x009000 Byte 2 of 3
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,120,
+
+// state[120 + 2] 0x009fc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[121 + 2] 0x00a000 Byte 2 of 3
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8,120,122, 8, 8, 8, 8, 123,124,125,126,127, 8,128,129,
+130, 87, 8,131,132,133, 8,134, 135,136, 8,137,138, 3, 3,139,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+
+// state[122 + 2] 0x00a4c0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+
+// state[123 + 2] 0x00a600 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[124 + 2] 0x00a640 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,
+
+// state[125 + 2] 0x00a680 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[126 + 2] 0x00a6c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[127 + 2] 0x00a700 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[128 + 2] 0x00a780 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[129 + 2] 0x00a7c0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[130 + 2] 0x00a800 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[131 + 2] 0x00a8c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,RJ_, 0, 0, 0, 0,
+
+// state[132 + 2] 0x00a900 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[133 + 2] 0x00a940 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+
+// state[134 + 2] 0x00a9c0 Byte 3 of 3
+RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[135 + 2] 0x00aa00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[136 + 2] 0x00aa40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,RJ_,RJ_, 0, 0, 0, 0,
+
+// state[137 + 2] 0x00aac0 Byte 3 of 3
+RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[138 + 2] 0x00ab00 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[139 + 2] 0x00abc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[140 + 2] 0x00d000 Byte 2 of 3
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,141,142,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[141 + 2] 0x00d780 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[142 + 2] 0x00d7c0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+
+// state[143 + 2] 0x00f000 Byte 2 of 3
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 8, 8, 8, 8, 8,144, 8,145,146,147, 23,148,
+ 8, 8, 8, 8,149, 21,150,151, 152,153, 8,154,155,156,157,158,
+
+// state[144 + 2] 0x00fa40 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[145 + 2] 0x00fac0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[146 + 2] 0x00fb00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0,
+
+// state[147 + 2] 0x00fb40 Byte 3 of 3
+RJ_,RJ_, 0,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[148 + 2] 0x00fbc0 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[149 + 2] 0x00fd00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+
+// state[150 + 2] 0x00fd80 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[151 + 2] 0x00fdc0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+
+// state[152 + 2] 0x00fe00 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[153 + 2] 0x00fe40 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[154 + 2] 0x00fec0 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+
+// state[155 + 2] 0x00ff00 Byte 3 of 3
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+
+// state[156 + 2] 0x00ff40 Byte 3 of 3
+ 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[157 + 2] 0x00ff80 Byte 3 of 3
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+
+// state[158 + 2] 0x00ffc0 Byte 3 of 3
+ 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[159 + 2] 0x000000 Byte 2 of 4
+X__,X__,X__,X__,X__,X__,X__,X__, X__,X__,X__,X__,X__,X__,X__,X__,
+160,180,184,186, 2, 2,187, 2, 2, 2, 2,191, 2,193,208, 2,
+118,118,118,118,118,118,118,118, 118,118,212,214, 2, 2, 2,215,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[160 + 2] 0x010000 Byte 3 of 4
+161,162, 8,163, 3, 3, 3,164, 3, 3,165,166,167,168,169,170,
+ 8, 8,171, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+172,173, 3, 3,174, 3,175, 3, 176,177, 3, 3, 77,178, 3, 3,
+ 8,179, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[161 + 2] 0x010000 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0,RJ_,RJ_, 0,RJ_,
+
+// state[162 + 2] 0x010040 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[163 + 2] 0x0100c0 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0, 0, 0, 0, 0,
+
+// state[164 + 2] 0x0101c0 Byte 4 of 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_, 0, 0,
+
+// state[165 + 2] 0x010280 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[166 + 2] 0x0102c0 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[167 + 2] 0x010300 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[168 + 2] 0x010340 Byte 4 of 4
+RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[169 + 2] 0x010380 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[170 + 2] 0x0103c0 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[171 + 2] 0x010480 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[172 + 2] 0x010800 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, RJ_, 0, 0, 0,RJ_, 0, 0,RJ_,
+
+// state[173 + 2] 0x010840 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[174 + 2] 0x010900 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0, 0, 0, 0,
+
+// state[175 + 2] 0x010980 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0,RJ_,RJ_,
+
+// state[176 + 2] 0x010a00 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, RJ_,RJ_,RJ_, 0, 0, 0, 0,RJ_,
+
+// state[177 + 2] 0x010a40 Byte 4 of 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0,
+
+// state[178 + 2] 0x010b40 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[179 + 2] 0x010c40 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[180 + 2] 0x011000 Byte 3 of 4
+ 8,181,163,182, 66, 3, 8,183, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 75, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[181 + 2] 0x011040 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[182 + 2] 0x0110c0 Byte 4 of 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[183 + 2] 0x0111c0 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[184 + 2] 0x012000 Byte 3 of 4
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,185, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[185 + 2] 0x012340 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[186 + 2] 0x013000 Byte 3 of 4
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+185, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[187 + 2] 0x016000 Byte 3 of 4
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 8, 8, 8, 8, 8, 8, 8, 8, 188, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8,189,190, 3,
+
+// state[188 + 2] 0x016a00 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0, 0, 0, 0, 0, 0, 0,
+
+// state[189 + 2] 0x016f40 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+
+// state[190 + 2] 0x016f80 Byte 4 of 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[191 + 2] 0x01b000 Byte 3 of 4
+192, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[192 + 2] 0x01b000 Byte 4 of 4
+RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[193 + 2] 0x01d000 Byte 3 of 4
+ 3, 3, 3, 3, 3,194,195, 3, 3,196, 3, 3, 3, 3, 3, 3,
+ 8,197,198,199,200,201, 8, 8, 8, 8,202,203,204,205,206,207,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[194 + 2] 0x01d140 Byte 4 of 4
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,RJ_,RJ_,RJ_, RJ_,RJ_, 0, 0, 0,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[195 + 2] 0x01d180 Byte 4 of 4
+RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,RJ_,RJ_,RJ_,RJ_, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[196 + 2] 0x01d240 Byte 4 of 4
+ 0, 0,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[197 + 2] 0x01d440 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[198 + 2] 0x01d480 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+ 0, 0,RJ_, 0, 0,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0,RJ_, 0,RJ_,RJ_,RJ_,
+
+// state[199 + 2] 0x01d4c0 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[200 + 2] 0x01d500 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, RJ_,RJ_,RJ_, 0, 0,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0,
+
+// state[201 + 2] 0x01d540 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0, 0, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[202 + 2] 0x01d680 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[203 + 2] 0x01d6c0 Byte 4 of 4
+RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,
+
+// state[204 + 2] 0x01d700 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[205 + 2] 0x01d740 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[206 + 2] 0x01d780 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+
+// state[207 + 2] 0x01d7c0 Byte 4 of 4
+RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[208 + 2] 0x01e000 Byte 3 of 4
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 209,210,211, 3, 3, 3, 3, 3,
+
+// state[209 + 2] 0x01ee00 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0,RJ_,RJ_, 0,RJ_, 0, 0,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0,RJ_, 0, 0, 0, 0,
+
+// state[210 + 2] 0x01ee40 Byte 4 of 4
+ 0, 0,RJ_, 0, 0, 0, 0,RJ_, 0,RJ_, 0,RJ_, 0,RJ_,RJ_,RJ_,
+ 0,RJ_,RJ_, 0,RJ_, 0, 0,RJ_, 0,RJ_, 0,RJ_, 0,RJ_, 0,RJ_,
+ 0,RJ_,RJ_, 0,RJ_, 0, 0,RJ_, RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_, 0,RJ_, 0,
+
+// state[211 + 2] 0x01ee80 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+ 0,RJ_,RJ_,RJ_, 0,RJ_,RJ_,RJ_, RJ_,RJ_, 0,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0,
+
+// state[212 + 2] 0x02a000 Byte 3 of 4
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,213, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+
+// state[213 + 2] 0x02a6c0 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+// state[214 + 2] 0x02b000 Byte 3 of 4
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 66, 8, 8, 8,
+171, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[215 + 2] 0x02f000 Byte 3 of 4
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 8, 8, 8, 8, 8, 8, 8, 8, 171, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[216 + 2] 0x0c0000 Byte 2 of 4
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+217, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+// state[217 + 2] 0x0e0000 Byte 3 of 4
+ 3, 3, 3, 3, 8, 8, 8,218, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+// state[218 + 2] 0x0e01c0 Byte 4 of 4
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_, RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,RJ_,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+};
+
+// Remap base[0] = (del, add, string_offset)
+static const RemapEntry utf8scannot_lettermarkspecial_remap_base[] = {
+{0,0,0} };
+
+// Remap string[0]
+static const unsigned char utf8scannot_lettermarkspecial_remap_string[] = {
+0 };
+
+static const unsigned char utf8scannot_lettermarkspecial_fast[256] = {
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,1,0, 0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0, 0,0,0,0,1,0,1,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,0,0,0,0,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,0,0,0,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,1,1, 1,1,1,1,1,1,1,1,
+
+};
+
+static const UTF8ScanObj utf8scannot_lettermarkspecial_obj = {
+ utf8scannot_lettermarkspecial_STATE0,
+ utf8scannot_lettermarkspecial_STATE0_SIZE,
+ utf8scannot_lettermarkspecial_TOTAL_SIZE,
+ utf8scannot_lettermarkspecial_MAX_EXPAND_X4,
+ utf8scannot_lettermarkspecial_SHIFT,
+ utf8scannot_lettermarkspecial_BYTES,
+ utf8scannot_lettermarkspecial_LOSUB,
+ utf8scannot_lettermarkspecial_HIADD,
+ utf8scannot_lettermarkspecial,
+ utf8scannot_lettermarkspecial_remap_base,
+ utf8scannot_lettermarkspecial_remap_string,
+ utf8scannot_lettermarkspecial_fast
+};
+
+
+#undef X__
+#undef RJ_
+#undef S1_
+#undef S2_
+#undef S3_
+#undef S21
+#undef S31
+#undef S32
+#undef T1_
+#undef T2_
+#undef S11
+#undef SP_
+#undef D__
+#undef RJA
+
+// Table has 14400 bytes, Hash = 9E4D-F2F2
+
+} // End namespace CLD2
+
+#endif // UTF8SCANNOT_LETTERMARKSPECIAL_H__
diff --git a/browser/components/translation/cld2/internal/utf8statetable.cc b/browser/components/translation/cld2/internal/utf8statetable.cc
new file mode 100644
index 000000000..8c97123d8
--- /dev/null
+++ b/browser/components/translation/cld2/internal/utf8statetable.cc
@@ -0,0 +1,1369 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// State Table follower for scanning UTF-8 strings without converting to
+// 32- or 16-bit Unicode values.
+//
+
+#ifdef COMPILER_MSVC
+// MSVC warns: warning C4309: 'initializing' : truncation of constant value
+// But the value is in fact not truncated. 0xFF still comes out 0xFF at
+// runtime.
+#pragma warning ( disable : 4309 )
+#endif
+
+#include "utf8statetable.h"
+
+#include <stdint.h> // for uintptr_t
+#include <string.h> // for NULL, memcpy, memmove
+
+#include "integral_types.h" // for uint8, uint32, int8
+#include "stringpiece.h"
+#include "offsetmap.h"
+
+
+namespace CLD2 {
+
+static const int kReplaceAndResumeFlag = 0x80; // Bit in del byte to distinguish
+ // optional next-state field
+ // after replacement text
+static const int kHtmlPlaintextFlag = 0x80; // Bit in add byte to distinguish
+ // HTML replacement vs. plaintext
+
+
+/**
+ * This code implements a little interpreter for UTF8 state
+ * tables. There are three kinds of quite-similar state tables,
+ * property, scanning, and replacement. Each state in one of
+ * these tables consists of an array of 256 or 64 one-byte
+ * entries. The state is subscripted by an incoming source byte,
+ * and the entry either specifies the next state or specifies an
+ * action. Space-optimized tables have full 256-entry states for
+ * the first byte of a UTF-8 character, but only 64-entry states
+ * for continuation bytes. Space-optimized tables may only be
+ * used with source input that has been checked to be
+ * structurally- (or stronger interchange-) valid.
+ *
+ * A property state table has an unsigned one-byte property for
+ * each possible UTF-8 character. One-byte character properties
+ * are in the state[0] array, while for other lengths the
+ * state[0] array gives the next state, which contains the
+ * property value for two-byte characters or yet another state
+ * for longer ones. The code simply loads the right number of
+ * next-state values, then returns the final byte as property
+ * value. There are no actions specified in property tables.
+ * States are typically shared for multi-byte UTF-8 characters
+ * that all have the same property value.
+ *
+ * A scanning state table has entries that are either a
+ * next-state specifier for bytes that are accepted by the
+ * scanner, or an exit action for the last byte of each
+ * character that is rejected by the scanner.
+ *
+ * Scanning long strings involves a tight loop that picks up one
+ * byte at a time and follows next-state value back to state[0]
+ * for each accepted UTF-8 character. Scanning stops at the end
+ * of the string or at the first character encountered that has
+ * an exit action such as "reject". Timing information is given
+ * below.
+ *
+ * Since so much of Google's text is 7-bit-ASCII values
+ * (approximately 94% of the bytes of web documents), the
+ * scanning interpreter has two speed optimizations. One checks
+ * 8 bytes at a time to see if they are all in the range lo..hi,
+ * as specified in constants in the overall statetable object.
+ * The check involves ORing together four 4-byte values that
+ * overflow into the high bit of some byte when a byte is out of
+ * range. For seven-bit-ASCII, lo is 0x20 and hi is 0x7E. This
+ * loop is about 8x faster than the one-byte-at-a-time loop.
+ *
+ * If checking for exit bytes in the 0x00-0x1F and 7F range is
+ * unneeded, an even faster loop just looks at the high bits of
+ * 8 bytes at once, and is about 1.33x faster than the lo..hi
+ * loop.
+ *
+ * Exit from the scanning routines backs up to the first byte of
+ * the rejected character, so the text spanned is always a
+ * complete number of UTF-8 characters. The normal scanning exit
+ * is at the first rejected character, or at the end of the
+ * input text. Scanning also exits on any detected ill-formed
+ * character or at a special do-again action built into some
+ * exit-optimized tables. The do-again action gets back to the
+ * top of the scanning loop to retry eight-byte ASCII scans. It
+ * is typically put into state tables after four seven-bit-ASCII
+ * characters in a row are seen, to allow restarting the fast
+ * scan after some slower processing of multi-byte characters.
+ *
+ * A replacement state table is similar to a scanning state
+ * table but has more extensive actions. The default
+ * byte-at-a-time loop copies one byte from source to
+ * destination and goes to the next state. The replacement
+ * actions overwrite 1-3 bytes of the destination with different
+ * bytes, possibly shortening the output by 1 or 2 bytes. The
+ * replacement bytes come from within the state table, from
+ * dummy states inserted just after any state that contains a
+ * replacement action. This gives a quick address calculation for
+ * the replacement byte(s) and gives some cache locality.
+ *
+ * Additional replacement actions use one or two bytes from
+ * within dummy states to index a side table of more-extensive
+ * replacements. The side table specifies a length of 0..15
+ * destination bytes to overwrite and a length of 0..127 bytes
+ * to overwrite them with, plus the actual replacement bytes.
+ *
+ * This side table uses one extra bit to specify a pair of
+ * replacements, the first to be used in an HTML context and the
+ * second to be used in a plaintext context. This allows
+ * replacements that are spelled with "&lt;" in the former
+ * context and "<" in the latter.
+ *
+ * The side table also uses an extra bit to specify a non-zero
+ * next state after a replacement. This allows a combination
+ * replacement and state change, used to implement a limited
+ * version of the Boyer-Moore algorithm for multi-character
+ * replacement without backtracking. This is useful when there
+ * are overlapping replacements, such as ch => x and also c =>
+ * y, the latter to be used only if the character after c is not
+ * h. in this case, the state[0] table's entry for c would
+ * change c to y and also have a next-state of say n, and the
+ * state[n] entry for h would specify a replacement of the two
+ * bytes yh by x. No backtracking is needed.
+ *
+ * A replacement table may also include the exit actions of a
+ * scanning state table, so some character sequences can
+ * terminate early.
+ *
+ * During replacement, an optional data structure called an
+ * offset map can be updated to reflect each change in length
+ * between source and destination. This offset map can later be
+ * used to map destination-string offsets to corresponding
+ * source-string offsets or vice versa.
+ *
+ * The routines below also have variants in which state-table
+ * entries are all two bytes instead of one byte. This allows
+ * tables with more than 240 total states, but takes up twice as
+ * much space per state.
+ *
+**/
+
+// Return true if current Tbl pointer is within state0 range
+// Note that unsigned compare checks both ends of range simultaneously
+static inline bool InStateZero(const UTF8ScanObj* st, const uint8* Tbl) {
+ const uint8* Tbl0 = &st->state_table[st->state0];
+ return (static_cast<uint32>(Tbl - Tbl0) < st->state0_size);
+}
+
+static inline bool InStateZero_2(const UTF8ReplaceObj_2* st,
+ const unsigned short int* Tbl) {
+ const unsigned short int* Tbl0 = &st->state_table[st->state0];
+ // Word difference, not byte difference
+ return (static_cast<uint32>(Tbl - Tbl0) < st->state0_size);
+}
+
+// UTF8PropObj, UTF8ScanObj, UTF8ReplaceObj are all typedefs of
+// UTF8MachineObj.
+
+static bool IsPropObj(const UTF8StateMachineObj& obj) {
+ return obj.fast_state == NULL
+ && obj.max_expand == 0;
+}
+
+static bool IsPropObj_2(const UTF8StateMachineObj_2& obj) {
+ return obj.fast_state == NULL
+ && obj.max_expand == 0;
+}
+
+static bool IsScanObj(const UTF8StateMachineObj& obj) {
+ return obj.fast_state != NULL
+ && obj.max_expand == 0;
+}
+
+static bool IsReplaceObj(const UTF8StateMachineObj& obj) {
+ // Normally, obj.fast_state != NULL, but the handwritten tables
+ // in utf8statetable_unittest don't handle fast_states.
+ return obj.max_expand > 0;
+}
+
+static bool IsReplaceObj_2(const UTF8StateMachineObj_2& obj) {
+ return obj.max_expand > 0;
+}
+
+// Look up property of one UTF-8 character and advance over it
+// Return 0 if input length is zero
+// Return 0 and advance one byte if input is ill-formed
+uint8 UTF8GenericProperty(const UTF8PropObj* st,
+ const uint8** src,
+ int* srclen) {
+ if (*srclen <= 0) {
+ return 0;
+ }
+
+ const uint8* lsrc = *src;
+ const uint8* Tbl_0 = &st->state_table[st->state0];
+ const uint8* Tbl = Tbl_0;
+ int e;
+ int eshift = st->entry_shift;
+
+ // Short series of tests faster than switch, optimizes 7-bit ASCII
+ unsigned char c = lsrc[0];
+ if (static_cast<signed char>(c) >= 0) { // one byte
+ e = Tbl[c];
+ *src += 1;
+ *srclen -= 1;
+ } else if (((c & 0xe0) == 0xc0) && (*srclen >= 2)) { // two bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ *src += 2;
+ *srclen -= 2;
+ } else if (((c & 0xf0) == 0xe0) && (*srclen >= 3)) { // three bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ *src += 3;
+ *srclen -= 3;
+ }else if (((c & 0xf8) == 0xf0) && (*srclen >= 4)) { // four bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[3]];
+ *src += 4;
+ *srclen -= 4;
+ } else { // Ill-formed
+ e = 0;
+ *src += 1;
+ *srclen -= 1;
+ }
+ return e;
+}
+
+bool UTF8HasGenericProperty(const UTF8PropObj& st, const char* src) {
+ const uint8* lsrc = reinterpret_cast<const uint8*>(src);
+ const uint8* Tbl_0 = &st.state_table[st.state0];
+ const uint8* Tbl = Tbl_0;
+ int e;
+ int eshift = st.entry_shift;
+
+ // Short series of tests faster than switch, optimizes 7-bit ASCII
+ unsigned char c = lsrc[0];
+ if (static_cast<signed char>(c) >= 0) { // one byte
+ e = Tbl[c];
+ } else if ((c & 0xe0) == 0xc0) { // two bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ } else if ((c & 0xf0) == 0xe0) { // three bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ } else { // four bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[3]];
+ }
+ return e;
+}
+
+
+// BigOneByte versions are needed for tables > 240 states, but most
+// won't need the TwoByte versions.
+// Internally, to next-to-last offset is multiplied by 16 and the last
+// offset is relative instead of absolute.
+// Look up property of one UTF-8 character and advance over it
+// Return 0 if input length is zero
+// Return 0 and advance one byte if input is ill-formed
+uint8 UTF8GenericPropertyBigOneByte(const UTF8PropObj* st,
+ const uint8** src,
+ int* srclen) {
+ if (*srclen <= 0) {
+ return 0;
+ }
+
+ const uint8* lsrc = *src;
+ const uint8* Tbl_0 = &st->state_table[st->state0];
+ const uint8* Tbl = Tbl_0;
+ int e;
+ int eshift = st->entry_shift;
+
+ // Short series of tests faster than switch, optimizes 7-bit ASCII
+ unsigned char c = lsrc[0];
+ if (static_cast<signed char>(c) >= 0) { // one byte
+ e = Tbl[c];
+ *src += 1;
+ *srclen -= 1;
+ } else if (((c & 0xe0) == 0xc0) && (*srclen >= 2)) { // two bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ *src += 2;
+ *srclen -= 2;
+ } else if (((c & 0xf0) == 0xe0) && (*srclen >= 3)) { // three bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range
+ e = (reinterpret_cast<const int8*>(Tbl))[lsrc[1]];
+ Tbl = &Tbl[e << eshift]; // Relative +/-
+ e = Tbl[lsrc[2]];
+ *src += 3;
+ *srclen -= 3;
+ }else if (((c & 0xf8) == 0xf0) && (*srclen >= 4)) { // four bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range
+ e = (reinterpret_cast<const int8*>(Tbl))[lsrc[2]];
+ Tbl = &Tbl[e << eshift]; // Relative +/-
+ e = Tbl[lsrc[3]];
+ *src += 4;
+ *srclen -= 4;
+ } else { // Ill-formed
+ e = 0;
+ *src += 1;
+ *srclen -= 1;
+ }
+ return e;
+}
+
+// BigOneByte versions are needed for tables > 240 states, but most
+// won't need the TwoByte versions.
+bool UTF8HasGenericPropertyBigOneByte(const UTF8PropObj& st, const char* src) {
+ const uint8* lsrc = reinterpret_cast<const uint8*>(src);
+ const uint8* Tbl_0 = &st.state_table[st.state0];
+ const uint8* Tbl = Tbl_0;
+ int e;
+ int eshift = st.entry_shift;
+
+ // Short series of tests faster than switch, optimizes 7-bit ASCII
+ unsigned char c = lsrc[0];
+ if (static_cast<signed char>(c) >= 0) { // one byte
+ e = Tbl[c];
+ } else if ((c & 0xe0) == 0xc0) { // two bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ } else if ((c & 0xf0) == 0xe0) { // three bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range
+ e = (reinterpret_cast<const int8*>(Tbl))[lsrc[1]];
+ Tbl = &Tbl[e << eshift]; // Relative +/-
+ e = Tbl[lsrc[2]];
+ } else { // four bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range
+ e = (reinterpret_cast<const int8*>(Tbl))[lsrc[2]];
+ Tbl = &Tbl[e << eshift]; // Relative +/-
+ e = Tbl[lsrc[3]];
+ }
+ return e;
+}
+
+
+// TwoByte versions are needed for tables > 240 states
+// Look up property of one UTF-8 character and advance over it
+// Return 0 if input length is zero
+// Return 0 and advance one byte if input is ill-formed
+uint8 UTF8GenericPropertyTwoByte(const UTF8PropObj_2* st,
+ const uint8** src,
+ int* srclen) {
+ if (*srclen <= 0) {
+ return 0;
+ }
+
+ const uint8* lsrc = *src;
+ const unsigned short* Tbl_0 = &st->state_table[st->state0];
+ const unsigned short* Tbl = Tbl_0;
+ int e;
+ int eshift = st->entry_shift;
+
+ // Short series of tests faster than switch, optimizes 7-bit ASCII
+ unsigned char c = lsrc[0];
+ if (static_cast<signed char>(c) >= 0) { // one byte
+ e = Tbl[c];
+ *src += 1;
+ *srclen -= 1;
+ } else if (((c & 0xe0) == 0xc0) && (*srclen >= 2)) { // two bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ *src += 2;
+ *srclen -= 2;
+ } else if (((c & 0xf0) == 0xe0) && (*srclen >= 3)) { // three bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ *src += 3;
+ *srclen -= 3;
+ }else if (((c & 0xf8) == 0xf0) && (*srclen >= 4)) { // four bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[3]];
+ *src += 4;
+ *srclen -= 4;
+ } else { // Ill-formed
+ e = 0;
+ *src += 1;
+ *srclen -= 1;
+ }
+ return e;
+}
+
+// TwoByte versions are needed for tables > 240 states
+bool UTF8HasGenericPropertyTwoByte(const UTF8PropObj_2& st, const char* src) {
+ const uint8* lsrc = reinterpret_cast<const uint8*>(src);
+ const unsigned short* Tbl_0 = &st.state_table[st.state0];
+ const unsigned short* Tbl = Tbl_0;
+ int e;
+ int eshift = st.entry_shift;
+
+ // Short series of tests faster than switch, optimizes 7-bit ASCII
+ unsigned char c = lsrc[0];
+ if (static_cast<signed char>(c) >= 0) { // one byte
+ e = Tbl[c];
+ } else if ((c & 0xe0) == 0xc0) { // two bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ } else if ((c & 0xf0) == 0xe0) { // three bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ } else { // four bytes
+ e = Tbl[c];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[1]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[2]];
+ Tbl = &Tbl_0[e << eshift];
+ e = Tbl[lsrc[3]];
+ }
+ return e;
+}
+
+
+// Approximate speeds on 2.8 GHz Pentium 4:
+// GenericScan 1-byte loop 300 MB/sec *
+// GenericScan 4-byte loop 1200 MB/sec
+// GenericScan 8-byte loop 2400 MB/sec *
+// GenericScanFastAscii 4-byte loop 3000 MB/sec
+// GenericScanFastAscii 8-byte loop 3200 MB/sec *
+//
+// * Implemented below. FastAscii loop is memory-bandwidth constrained.
+
+// Scan a UTF-8 stringpiece based on state table.
+// Always scan complete UTF-8 characters
+// Set number of bytes scanned. Return reason for exiting
+int UTF8GenericScan(const UTF8ScanObj* st,
+ const StringPiece& str,
+ int* bytes_consumed) {
+ int eshift = st->entry_shift; // 6 (space optimized) or 8
+ // int nEntries = (1 << eshift); // 64 or 256 entries per state
+
+ const uint8* isrc =
+ reinterpret_cast<const uint8*>(str.data());
+ const uint8* src = isrc;
+ const int len = str.length();
+ const uint8* srclimit = isrc + len;
+ const uint8* srclimit8 = srclimit - 7;
+ *bytes_consumed = 0;
+ if (len == 0) return kExitOK;
+
+ const uint8* Tbl_0 = &st->state_table[st->state0];
+
+DoAgain:
+ // Do state-table scan
+ int e = 0;
+ uint8 c;
+
+ // Do fast for groups of 8 identity bytes.
+ // This covers a lot of 7-bit ASCII ~8x faster than the 1-byte loop,
+ // including slowing slightly on cr/lf/ht
+ //----------------------------
+ const uint8* Tbl2 = &st->fast_state[0];
+ uint32 losub = st->losub;
+ uint32 hiadd = st->hiadd;
+ while (src < srclimit8) {
+ uint32 s0123 = (reinterpret_cast<const uint32 *>(src))[0];
+ uint32 s4567 = (reinterpret_cast<const uint32 *>(src))[1];
+ src += 8;
+ // This is a fast range check for all bytes in [lowsub..0x80-hiadd)
+ uint32 temp = (s0123 - losub) | (s0123 + hiadd) |
+ (s4567 - losub) | (s4567 + hiadd);
+ if ((temp & 0x80808080) != 0) {
+ // We typically end up here on cr/lf/ht; src was incremented
+ int e0123 = (Tbl2[src[-8]] | Tbl2[src[-7]]) |
+ (Tbl2[src[-6]] | Tbl2[src[-5]]);
+ if (e0123 != 0) {src -= 8; break;} // Exit on Non-interchange
+ e0123 = (Tbl2[src[-4]] | Tbl2[src[-3]]) |
+ (Tbl2[src[-2]] | Tbl2[src[-1]]);
+ if (e0123 != 0) {src -= 4; break;} // Exit on Non-interchange
+ // Else OK, go around again
+ }
+ }
+ //----------------------------
+
+ // Byte-at-a-time scan
+ //----------------------------
+ const uint8* Tbl = Tbl_0;
+ while (src < srclimit) {
+ c = *src;
+ e = Tbl[c];
+ src++;
+ if (e >= kExitIllegalStructure) {break;}
+ Tbl = &Tbl_0[e << eshift];
+ }
+ //----------------------------
+
+
+ // Exit possibilities:
+ // Some exit code, !state0, back up over last char
+ // Some exit code, state0, back up one byte exactly
+ // source consumed, !state0, back up over partial char
+ // source consumed, state0, exit OK
+ // For illegal byte in state0, avoid backup up over PREVIOUS char
+ // For truncated last char, back up to beginning of it
+
+ if (e >= kExitIllegalStructure) {
+ // Back up over exactly one byte of rejected/illegal UTF-8 character
+ src--;
+ // Back up more if needed
+ if (!InStateZero(st, Tbl)) {
+ do {src--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80));
+ }
+ } else if (!InStateZero(st, Tbl)) {
+ // Back up over truncated UTF-8 character
+ e = kExitIllegalStructure;
+ do {src--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80));
+ } else {
+ // Normal termination, source fully consumed
+ e = kExitOK;
+ }
+
+ if (e == kExitDoAgain) {
+ // Loop back up to the fast scan
+ goto DoAgain;
+ }
+
+ *bytes_consumed = src - isrc;
+ return e;
+}
+
+// Scan a UTF-8 stringpiece based on state table.
+// Always scan complete UTF-8 characters
+// Set number of bytes scanned. Return reason for exiting
+// OPTIMIZED for case of 7-bit ASCII 0000..007f all valid
+int UTF8GenericScanFastAscii(const UTF8ScanObj* st,
+ const StringPiece& str,
+ int* bytes_consumed) {
+ const uint8* isrc =
+ reinterpret_cast<const uint8*>(str.data());
+ const uint8* src = isrc;
+ const int len = str.length();
+ const uint8* srclimit = isrc + len;
+ const uint8* srclimit8 = srclimit - 7;
+ *bytes_consumed = 0;
+ if (len == 0) return kExitOK;
+
+ int n;
+ int rest_consumed;
+ int exit_reason;
+ do {
+ // Skip 8 bytes of ASCII at a whack; no endianness issue
+ while ((src < srclimit8) &&
+ (((reinterpret_cast<const uint32*>(src)[0] |
+ reinterpret_cast<const uint32*>(src)[1]) & 0x80808080) == 0)) {
+ src += 8;
+ }
+ // Run state table on the rest
+ n = src - isrc;
+ StringPiece str2(str.data() + n, str.length() - n);
+ exit_reason = UTF8GenericScan(st, str2, &rest_consumed);
+ src += rest_consumed;
+ } while ( exit_reason == kExitDoAgain );
+
+ *bytes_consumed = src - isrc;
+ return exit_reason;
+}
+
+// Hack to change halfwidth katakana to match an old UTF8CharToLower()
+
+// Return number of src bytes skipped
+static int DoSpecialFixup(const unsigned char c,
+ const unsigned char** srcp, const unsigned char* srclimit,
+ unsigned char** dstp, unsigned char* dstlimit) {
+ return 0;
+}
+
+
+// Scan a UTF-8 stringpiece based on state table, copying to output stringpiece
+// and doing text replacements.
+// DO NOT CALL DIRECTLY. Use UTF8GenericReplace() below
+// Needs caller to loop on kExitDoAgain
+static int UTF8GenericReplaceInternal(const UTF8ReplaceObj* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed,
+ OffsetMap* offsetmap) {
+ int eshift = st->entry_shift;
+ int nEntries = (1 << eshift); // 64 or 256 entries per state
+ const uint8* isrc = reinterpret_cast<const uint8*>(istr.data());
+ const int ilen = istr.length();
+ const uint8* copystart = isrc;
+ const uint8* src = isrc;
+ const uint8* srclimit = src + ilen;
+ *bytes_consumed = 0;
+ *bytes_filled = 0;
+ *chars_changed = 0;
+
+ const uint8* odst = reinterpret_cast<const uint8*>(ostr.data());
+ const int olen = ostr.length();
+ uint8* dst = const_cast<uint8*>(odst);
+ uint8* dstlimit = dst + olen;
+
+ int total_changed = 0;
+
+ // Invariant condition during replacements:
+ // remaining dst size >= remaining src size
+ if ((dstlimit - dst) < (srclimit - src)) {
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart);
+ copystart = src;
+ }
+ return kExitDstSpaceFull;
+ }
+ const uint8* Tbl_0 = &st->state_table[st->state0];
+
+ Do_state_table:
+ // Do state-table scan, copying as we go
+ const uint8* Tbl = Tbl_0;
+ int e = 0;
+ uint8 c = 0;
+
+ Do_state_table_newe:
+
+ //----------------------------
+ while (src < srclimit) {
+ c = *src;
+ e = Tbl[c];
+ *dst = c;
+ src++;
+ dst++;
+ if (e >= kExitIllegalStructure) {break;}
+ Tbl = &Tbl_0[e << eshift];
+ }
+ //----------------------------
+
+ // Exit possibilities:
+ // Replacement code, do the replacement and loop
+ // Some other exit code, state0, back up one byte exactly
+ // Some other exit code, !state0, back up over last char
+ // source consumed, state0, exit OK
+ // source consumed, !state0, back up over partial char
+ // For illegal byte in state0, avoid backup up over PREVIOUS char
+ // For truncated last char, back up to beginning of it
+
+ if (e >= kExitIllegalStructure) {
+ // Switch on exit code; most loop back to top
+ int offset = 0;
+ switch (e) {
+ // These all make the output string the same size or shorter
+ // No checking needed
+ case kExitReplace31: // del 2, add 1 bytes to change
+ dst -= 2;
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart - 2);
+ offsetmap->Delete(2);
+ copystart = src;
+ }
+ dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)];
+ total_changed++;
+ goto Do_state_table;
+ case kExitReplace32: // del 3, add 2 bytes to change
+ dst--;
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart - 1);
+ offsetmap->Delete(1);
+ copystart = src;
+ }
+ dst[-2] = (unsigned char)Tbl[c + (nEntries * 2)];
+ dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)];
+ total_changed++;
+ goto Do_state_table;
+ case kExitReplace21: // del 2, add 1 bytes to change
+ dst--;
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart - 1);
+ offsetmap->Delete(1);
+ copystart = src;
+ }
+ dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)];
+ total_changed++;
+ goto Do_state_table;
+ case kExitReplace3: // update 3 bytes to change
+ dst[-3] = (unsigned char)Tbl[c + (nEntries * 3)];
+ // Fall into next case
+ case kExitReplace2: // update 2 bytes to change
+ dst[-2] = (unsigned char)Tbl[c + (nEntries * 2)];
+ // Fall into next case
+ case kExitReplace1: // update 1 byte to change
+ dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)];
+ total_changed++;
+ goto Do_state_table;
+ case kExitReplace1S0: // update 1 byte to change, 256-entry state
+ dst[-1] = (unsigned char)Tbl[c + (256 * 1)];
+ total_changed++;
+ goto Do_state_table;
+ // These can make the output string longer than the input
+ case kExitReplaceOffset2:
+ if ((nEntries != 256) && InStateZero(st, Tbl)) {
+ // For space-optimized table, we need multiples of 256 bytes
+ // in state0 and multiples of nEntries in other states
+ offset += ((unsigned char)Tbl[c + (256 * 2)] << 8);
+ } else {
+ offset += ((unsigned char)Tbl[c + (nEntries * 2)] << 8);
+ }
+ // Fall into next case
+ case kExitSpecial: // Apply special fixups [read: hacks]
+ case kExitReplaceOffset1:
+ if ((nEntries != 256) && InStateZero(st, Tbl)) {
+ // For space-optimized table, we need multiples of 256 bytes
+ // in state0 and multiples of nEntries in other states
+ offset += (unsigned char)Tbl[c + (256 * 1)];
+ } else {
+ offset += (unsigned char)Tbl[c + (nEntries * 1)];
+ }
+ {
+ const RemapEntry* re = &st->remap_base[offset];
+ int del_len = re->delete_bytes & ~kReplaceAndResumeFlag;
+ int add_len = re->add_bytes & ~kHtmlPlaintextFlag;
+
+ // Special-case non-HTML replacement of five sensitive entities
+ // &quot; &amp; &apos; &lt; &gt;
+ // 0022 0026 0027 003c 003e
+ // A replacement creating one of these is expressed as a pair of
+ // entries, one for HTML output and one for plaintext output.
+ // The first of the pair has the high bit of add_bytes set.
+ if (re->add_bytes & kHtmlPlaintextFlag) {
+ // Use this entry for plain text
+ if (!is_plain_text) {
+ // Use very next entry for HTML text (same back/delete length)
+ re = &st->remap_base[offset + 1];
+ add_len = re->add_bytes & ~kHtmlPlaintextFlag;
+ }
+ }
+
+ int string_offset = re->bytes_offset;
+ // After the replacement, need (dstlimit - newdst) >= (srclimit - src)
+ uint8* newdst = dst - del_len + add_len;
+ if ((dstlimit - newdst) < (srclimit - src)) {
+ // Won't fit; don't do the replacement. Caller may realloc and retry
+ e = kExitDstSpaceFull;
+ break; // exit, backing up over this char for later retry
+ }
+ dst -= del_len;
+ memcpy(dst, &st->remap_string[string_offset], add_len);
+ dst += add_len;
+ total_changed++;
+ if (offsetmap != NULL) {
+ if (add_len > del_len) {
+ offsetmap->Copy(src - copystart);
+ offsetmap->Insert(add_len - del_len);
+ copystart = src;
+ } else if (add_len < del_len) {
+ offsetmap->Copy(src - copystart + add_len - del_len);
+ offsetmap->Delete(del_len - add_len);
+ copystart = src;
+ }
+ }
+ if (re->delete_bytes & kReplaceAndResumeFlag) {
+ // There is a non-zero target state at the end of the
+ // replacement string
+ e = st->remap_string[string_offset + add_len];
+ Tbl = &Tbl_0[e << eshift];
+ goto Do_state_table_newe;
+ }
+ }
+ if (e == kExitRejectAlt) {break;}
+ if (e != kExitSpecial) {goto Do_state_table;}
+
+ // case kExitSpecial: // Apply special fixups [read: hacks]
+ // In this routine, do either UTF8CharToLower()
+ // fullwidth/halfwidth mapping or
+ // voiced mapping or
+ // semi-voiced mapping
+
+ // First, do EXIT_REPLACE_OFFSET1 action (above)
+ // Second: do additional code fixup
+ {
+ int srcdel = DoSpecialFixup(c, &src, srclimit, &dst, dstlimit);
+ if (offsetmap != NULL) {
+ if (srcdel != 0) {
+ offsetmap->Copy(src - copystart - srcdel);
+ offsetmap->Delete(srcdel);
+ copystart = src;
+ }
+ }
+ }
+ goto Do_state_table;
+
+ case kExitIllegalStructure: // structurally illegal byte; quit
+ case kExitReject: // NUL or illegal code encountered; quit
+ case kExitRejectAlt: // Apply replacement, then exit
+ default: // and all other exits
+ break;
+ } // End switch (e)
+
+ // Exit possibilities:
+ // Some other exit code, state0, back up one byte exactly
+ // Some other exit code, !state0, back up over last char
+
+ // Back up over exactly one byte of rejected/illegal UTF-8 character
+ src--;
+ dst--;
+ // Back up more if needed
+ if (!InStateZero(st, Tbl)) {
+ do {src--;dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80));
+ }
+ } else if (!InStateZero(st, Tbl)) {
+ // src >= srclimit, !state0
+ // Back up over truncated UTF-8 character
+ e = kExitIllegalStructure;
+ do {src--; dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80));
+ } else {
+ // src >= srclimit, state0
+ // Normal termination, source fully consumed
+ e = kExitOK;
+ }
+
+ if (offsetmap != NULL) {
+ if (src > copystart) {
+ offsetmap->Copy(src - copystart);
+ copystart = src;
+ }
+ }
+
+ // Possible return values here:
+ // kExitDstSpaceFull caller may realloc and retry from middle
+ // kExitIllegalStructure caller my overwrite/truncate
+ // kExitOK all done and happy
+ // kExitReject caller may overwrite/truncate
+ // kExitDoAgain LOOP NOT DONE; caller must retry from middle
+ // (may do fast ASCII loop first)
+ // kExitPlaceholder -unused-
+ // kExitNone -unused-
+ *bytes_consumed = src - isrc;
+ *bytes_filled = dst - odst;
+ *chars_changed = total_changed;
+ return e;
+}
+
+// TwoByte versions are needed for tables > 240 states, such
+// as the table for full Unicode 4.1 canonical + compatibility mapping
+
+// Scan a UTF-8 stringpiece based on state table with two-byte entries,
+// copying to output stringpiece
+// and doing text replacements.
+// DO NOT CALL DIRECTLY. Use UTF8GenericReplace() below
+// Needs caller to loop on kExitDoAgain
+static int UTF8GenericReplaceInternalTwoByte(const UTF8ReplaceObj_2* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed,
+ OffsetMap* offsetmap) {
+ int eshift = st->entry_shift;
+ int nEntries = (1 << eshift); // 64 or 256 entries per state
+ const uint8* isrc = reinterpret_cast<const uint8*>(istr.data());
+ const int ilen = istr.length();
+ const uint8* copystart = isrc;
+ const uint8* src = isrc;
+ const uint8* srclimit = src + ilen;
+ *bytes_consumed = 0;
+ *bytes_filled = 0;
+ *chars_changed = 0;
+
+ const uint8* odst = reinterpret_cast<const uint8*>(ostr.data());
+ const int olen = ostr.length();
+ uint8* dst = const_cast<uint8*>(odst);
+ uint8* dstlimit = dst + olen;
+
+ *chars_changed = 0;
+
+ int total_changed = 0;
+
+ int src_lll = srclimit - src;
+ int dst_lll = dstlimit - dst;
+
+
+ // Invariant condition during replacements:
+ // remaining dst size >= remaining src size
+ if ((dstlimit - dst) < (srclimit - src)) {
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart);
+ copystart = src;
+ }
+ return kExitDstSpaceFull_2;
+ }
+ const unsigned short* Tbl_0 = &st->state_table[st->state0];
+
+ Do_state_table_2:
+ // Do state-table scan, copying as we go
+ const unsigned short* Tbl = Tbl_0;
+ int e = 0;
+ uint8 c = 0;
+
+ Do_state_table_newe_2:
+
+ //----------------------------
+ while (src < srclimit) {
+ c = *src;
+ e = Tbl[c];
+ *dst = c;
+ src++;
+ dst++;
+ if (e >= kExitIllegalStructure_2) {break;}
+ Tbl = &Tbl_0[e << eshift];
+ }
+ //----------------------------
+ src_lll = src - isrc;
+ dst_lll = dst - odst;
+
+ // Exit possibilities:
+ // Replacement code, do the replacement and loop
+ // Some other exit code, state0, back up one byte exactly
+ // Some other exit code, !state0, back up over last char
+ // source consumed, state0, exit OK
+ // source consumed, !state0, back up over partial char
+ // For illegal byte in state0, avoid backup up over PREVIOUS char
+ // For truncated last char, back up to beginning of it
+
+ if (e >= kExitIllegalStructure_2) {
+ // Switch on exit code; most loop back to top
+ int offset = 0;
+ switch (e) {
+ // These all make the output string the same size or shorter
+ // No checking needed
+ case kExitReplace31_2: // del 2, add 1 bytes to change
+ dst -= 2;
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart - 2);
+ offsetmap->Delete(2);
+ copystart = src;
+ }
+ dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff);
+ total_changed++;
+ goto Do_state_table_2;
+ case kExitReplace32_2: // del 3, add 2 bytes to change
+ dst--;
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart - 1);
+ offsetmap->Delete(1);
+ copystart = src;
+ }
+ dst[-2] = (unsigned char)(Tbl[c + (nEntries * 1)] >> 8 & 0xff);
+ dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff);
+ total_changed++;
+ goto Do_state_table_2;
+ case kExitReplace21_2: // del 2, add 1 bytes to change
+ dst--;
+ if (offsetmap != NULL) {
+ offsetmap->Copy(src - copystart - 1);
+ offsetmap->Delete(1);
+ copystart = src;
+ }
+ dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff);
+ total_changed++;
+ goto Do_state_table_2;
+ case kExitReplace3_2: // update 3 bytes to change
+ dst[-3] = (unsigned char)(Tbl[c + (nEntries * 2)] & 0xff);
+ // Fall into next case
+ case kExitReplace2_2: // update 2 bytes to change
+ dst[-2] = (unsigned char)(Tbl[c + (nEntries * 1)] >> 8 & 0xff);
+ // Fall into next case
+ case kExitReplace1_2: // update 1 byte to change
+ dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff);
+ total_changed++;
+ goto Do_state_table_2;
+ case kExitReplace1S0_2: // update 1 byte to change, 256-entry state
+ dst[-1] = (unsigned char)(Tbl[c + (256 * 1)] & 0xff);
+ total_changed++;
+ goto Do_state_table_2;
+ // These can make the output string longer than the input
+ case kExitReplaceOffset2_2:
+ if ((nEntries != 256) && InStateZero_2(st, Tbl)) {
+ // For space-optimized table, we need multiples of 256 bytes
+ // in state0 and multiples of nEntries in other states
+ offset += ((unsigned char)(Tbl[c + (256 * 1)] >> 8 & 0xff) << 8);
+ } else {
+ offset += ((unsigned char)(Tbl[c + (nEntries * 1)] >> 8 & 0xff) << 8);
+ }
+ // Fall into next case
+ case kExitReplaceOffset1_2:
+ if ((nEntries != 256) && InStateZero_2(st, Tbl)) {
+ // For space-optimized table, we need multiples of 256 bytes
+ // in state0 and multiples of nEntries in other states
+ offset += (unsigned char)(Tbl[c + (256 * 1)] & 0xff);
+ } else {
+ offset += (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff);
+ }
+ {
+ const RemapEntry* re = &st->remap_base[offset];
+ int del_len = re->delete_bytes & ~kReplaceAndResumeFlag;
+ int add_len = re->add_bytes & ~kHtmlPlaintextFlag;
+ // Special-case non-HTML replacement of five sensitive entities
+ // &quot; &amp; &apos; &lt; &gt;
+ // 0022 0026 0027 003c 003e
+ // A replacement creating one of these is expressed as a pair of
+ // entries, one for HTML output and one for plaintext output.
+ // The first of the pair has the high bit of add_bytes set.
+ if (re->add_bytes & kHtmlPlaintextFlag) {
+ // Use this entry for plain text
+ if (!is_plain_text) {
+ // Use very next entry for HTML text (same back/delete length)
+ re = &st->remap_base[offset + 1];
+ add_len = re->add_bytes & ~kHtmlPlaintextFlag;
+ }
+ }
+
+ // After the replacement, need (dstlimit - dst) >= (srclimit - src)
+ int string_offset = re->bytes_offset;
+ // After the replacement, need (dstlimit - newdst) >= (srclimit - src)
+ uint8* newdst = dst - del_len + add_len;
+ if ((dstlimit - newdst) < (srclimit - src)) {
+ // Won't fit; don't do the replacement. Caller may realloc and retry
+ e = kExitDstSpaceFull_2;
+ break; // exit, backing up over this char for later retry
+ }
+ dst -= del_len;
+ memcpy(dst, &st->remap_string[string_offset], add_len);
+ dst += add_len;
+ if (offsetmap != NULL) {
+ if (add_len > del_len) {
+ offsetmap->Copy(src - copystart);
+ offsetmap->Insert(add_len - del_len);
+ copystart = src;
+ } else if (add_len < del_len) {
+ offsetmap->Copy(src - copystart + add_len - del_len);
+ offsetmap->Delete(del_len - add_len);
+ copystart = src;
+ }
+ }
+ if (re->delete_bytes & kReplaceAndResumeFlag) {
+ // There is a two-byte non-zero target state at the end of the
+ // replacement string
+ uint8 c1 = st->remap_string[string_offset + add_len];
+ uint8 c2 = st->remap_string[string_offset + add_len + 1];
+ e = (c1 << 8) | c2;
+ Tbl = &Tbl_0[e << eshift];
+ total_changed++;
+ goto Do_state_table_newe_2;
+ }
+ }
+ total_changed++;
+ if (e == kExitRejectAlt_2) {break;}
+ goto Do_state_table_2;
+
+ case kExitSpecial_2: // NO special fixups [read: hacks]
+ case kExitIllegalStructure_2: // structurally illegal byte; quit
+ case kExitReject_2: // NUL or illegal code encountered; quit
+ // and all other exits
+ default:
+ break;
+ } // End switch (e)
+
+ // Exit possibilities:
+ // Some other exit code, state0, back up one byte exactly
+ // Some other exit code, !state0, back up over last char
+
+ // Back up over exactly one byte of rejected/illegal UTF-8 character
+ src--;
+ dst--;
+ // Back up more if needed
+ if (!InStateZero_2(st, Tbl)) {
+ do {src--;dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80));
+ }
+ } else if (!InStateZero_2(st, Tbl)) {
+ // src >= srclimit, !state0
+ // Back up over truncated UTF-8 character
+ e = kExitIllegalStructure_2;
+
+ do {src--; dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80));
+ } else {
+ // src >= srclimit, state0
+ // Normal termination, source fully consumed
+ e = kExitOK_2;
+ }
+
+ if (offsetmap != NULL) {
+ if (src > copystart) {
+ offsetmap->Copy(src - copystart);
+ copystart = src;
+ }
+ }
+
+
+ // Possible return values here:
+ // kExitDstSpaceFull_2 caller may realloc and retry from middle
+ // kExitIllegalStructure_2 caller my overwrite/truncate
+ // kExitOK_2 all done and happy
+ // kExitReject_2 caller may overwrite/truncate
+ // kExitDoAgain_2 LOOP NOT DONE; caller must retry from middle
+ // (may do fast ASCII loop first)
+ // kExitPlaceholder_2 -unused-
+ // kExitNone_2 -unused-
+ *bytes_consumed = src - isrc;
+ *bytes_filled = dst - odst;
+ *chars_changed = total_changed;
+ return e;
+}
+
+
+// Scan a UTF-8 stringpiece based on state table, copying to output stringpiece
+// and doing text replacements.
+// Also writes an optional OffsetMap. Pass NULL to skip writing one.
+// Always scan complete UTF-8 characters
+// Set number of bytes consumed from input, number filled to output.
+// Return reason for exiting
+int UTF8GenericReplace(const UTF8ReplaceObj* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed,
+ OffsetMap* offsetmap) {
+ StringPiece local_istr(istr.data(), istr.length());
+ StringPiece local_ostr(ostr.data(), ostr.length());
+ int total_consumed = 0;
+ int total_filled = 0;
+ int total_changed = 0;
+ int local_bytes_consumed, local_bytes_filled, local_chars_changed;
+ int e;
+ do {
+ e = UTF8GenericReplaceInternal(st,
+ local_istr, local_ostr, is_plain_text,
+ &local_bytes_consumed, &local_bytes_filled,
+ &local_chars_changed,
+ offsetmap);
+ local_istr.remove_prefix(local_bytes_consumed);
+ local_ostr.remove_prefix(local_bytes_filled);
+ total_consumed += local_bytes_consumed;
+ total_filled += local_bytes_filled;
+ total_changed += local_chars_changed;
+ } while ( e == kExitDoAgain );
+ *bytes_consumed = total_consumed;
+ *bytes_filled = total_filled;
+ *chars_changed = total_changed;
+ return e;
+}
+
+// Older version without offsetmap
+int UTF8GenericReplace(const UTF8ReplaceObj* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed) {
+ return UTF8GenericReplace(st,
+ istr,
+ ostr,
+ is_plain_text,
+ bytes_consumed,
+ bytes_filled,
+ chars_changed,
+ NULL);
+}
+
+// Older version without is_plain_text or offsetmap
+int UTF8GenericReplace(const UTF8ReplaceObj* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed) {
+ bool is_plain_text = false;
+ return UTF8GenericReplace(st,
+ istr,
+ ostr,
+ is_plain_text,
+ bytes_consumed,
+ bytes_filled,
+ chars_changed,
+ NULL);
+}
+
+// Scan a UTF-8 stringpiece based on state table with two-byte entries,
+// copying to output stringpiece
+// and doing text replacements.
+// Also writes an optional OffsetMap. Pass NULL to skip writing one.
+// Always scan complete UTF-8 characters
+// Set number of bytes consumed from input, number filled to output.
+// Return reason for exiting
+int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed,
+ OffsetMap* offsetmap) {
+ StringPiece local_istr(istr.data(), istr.length());
+ StringPiece local_ostr(ostr.data(), ostr.length());
+ int total_consumed = 0;
+ int total_filled = 0;
+ int total_changed = 0;
+ int local_bytes_consumed, local_bytes_filled, local_chars_changed;
+ int e;
+ do {
+ e = UTF8GenericReplaceInternalTwoByte(st,
+ local_istr, local_ostr, is_plain_text,
+ &local_bytes_consumed,
+ &local_bytes_filled,
+ &local_chars_changed,
+ offsetmap);
+ local_istr.remove_prefix(local_bytes_consumed);
+ local_ostr.remove_prefix(local_bytes_filled);
+ total_consumed += local_bytes_consumed;
+ total_filled += local_bytes_filled;
+ total_changed += local_chars_changed;
+ } while ( e == kExitDoAgain_2 );
+ *bytes_consumed = total_consumed;
+ *bytes_filled = total_filled;
+ *chars_changed = total_changed;
+
+ return e - kExitOK_2 + kExitOK;
+}
+
+// Older version without offsetmap
+int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed) {
+ return UTF8GenericReplaceTwoByte(st,
+ istr,
+ ostr,
+ is_plain_text,
+ bytes_consumed,
+ bytes_filled,
+ chars_changed,
+ NULL);
+}
+
+// Older version without is_plain_text or offsetmap
+int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed) {
+ bool is_plain_text = false;
+ return UTF8GenericReplaceTwoByte(st,
+ istr,
+ ostr,
+ is_plain_text,
+ bytes_consumed,
+ bytes_filled,
+ chars_changed,
+ NULL);
+}
+
+
+
+// Adjust a stringpiece to encompass complete UTF-8 characters.
+// The data pointer will be increased by 0..3 bytes to get to a character
+// boundary, and the length will then be decreased by 0..3 bytes
+// to encompass the last complete character.
+void UTF8TrimToChars(StringPiece* istr) {
+ const char* src = istr->data();
+ int len = istr->length();
+ // Exit if empty string
+ if (len == 0) {
+ return;
+ }
+
+ // Exit on simple, common case
+ if ( ((src[0] & 0xc0) != 0x80) &&
+ (static_cast<signed char>(src[len - 1]) >= 0) ) {
+ // First byte is not a continuation and last byte is 7-bit ASCII -- done
+ return;
+ }
+
+ // Adjust the back end, len > 0
+ const char* srclimit = src + len;
+ // Backscan over any ending continuation bytes to find last char start
+ const char* s = srclimit - 1; // Last byte of the string
+ while ((src <= s) && ((*s & 0xc0) == 0x80)) {
+ s--;
+ }
+ // Include entire last char if it fits
+ if (src <= s) {
+ int last_char_len = UTF8OneCharLen(s);
+ if (s + last_char_len <= srclimit) {
+ // Last char fits, so include it, else exclude it
+ s += last_char_len;
+ }
+ }
+ if (s != srclimit) {
+ // s is one byte beyond the last full character, if any
+ istr->remove_suffix(srclimit - s);
+ // Exit if now empty string
+ if (istr->length() == 0) {
+ return;
+ }
+ }
+
+ // Adjust the front end, len > 0
+ len = istr->length();
+ srclimit = src + len;
+ s = src; // First byte of the string
+ // Scan over any beginning continuation bytes to find first char start
+ while ((s < srclimit) && ((*s & 0xc0) == 0x80)) {
+ s++;
+ }
+ if (s != src) {
+ // s is at the first full character, if any
+ istr->remove_prefix(s - src);
+ }
+}
+
+} // End namespace CLD2
diff --git a/browser/components/translation/cld2/internal/utf8statetable.h b/browser/components/translation/cld2/internal/utf8statetable.h
new file mode 100644
index 000000000..55c00f45e
--- /dev/null
+++ b/browser/components/translation/cld2/internal/utf8statetable.h
@@ -0,0 +1,283 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// State Table follower for scanning UTF-8 strings without converting to
+// 32- or 16-bit Unicode values.
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#ifndef UTIL_UTF8_UTF8STATETABLE_H_
+#define UTIL_UTF8_UTF8STATETABLE_H_
+
+#include <string>
+#include "integral_types.h" // for uint8, uint32, uint16
+#include "stringpiece.h"
+
+
+namespace CLD2 {
+
+class OffsetMap;
+
+
+// These four-byte entries compactly encode how many bytes 0..255 to delete
+// in making a string replacement, how many bytes to add 0..255, and the offset
+// 0..64k-1 of the replacement string in remap_string.
+struct RemapEntry {
+ uint8 delete_bytes;
+ uint8 add_bytes;
+ uint16 bytes_offset;
+};
+
+// Exit type codes for state tables. All but the first get stuffed into
+// signed one-byte entries. The first is only generated by executable code.
+// To distinguish from next-state entries, these must be contiguous and
+// all <= kExitNone
+typedef enum {
+ kExitDstSpaceFull = 239,
+ kExitIllegalStructure, // 240
+ kExitOK, // 241
+ kExitReject, // ...
+ kExitReplace1,
+ kExitReplace2,
+ kExitReplace3,
+ kExitReplace21,
+ kExitReplace31,
+ kExitReplace32,
+ kExitReplaceOffset1,
+ kExitReplaceOffset2,
+ kExitReplace1S0,
+ kExitSpecial,
+ kExitDoAgain,
+ kExitRejectAlt,
+ kExitNone // 255
+} ExitReason;
+
+typedef enum {
+ kExitDstSpaceFull_2 = 32767, // 0x7fff
+ kExitIllegalStructure_2, // 32768 0x8000
+ kExitOK_2, // 32769 0x8001
+ kExitReject_2, // ...
+ kExitReplace1_2,
+ kExitReplace2_2,
+ kExitReplace3_2,
+ kExitReplace21_2,
+ kExitReplace31_2,
+ kExitReplace32_2,
+ kExitReplaceOffset1_2,
+ kExitReplaceOffset2_2,
+ kExitReplace1S0_2,
+ kExitSpecial_2,
+ kExitDoAgain_2,
+ kExitRejectAlt_2,
+ kExitNone_2 // 32783 0x800f
+} ExitReason_2;
+
+
+// This struct represents one entire state table. The three initialized byte
+// areas are state_table, remap_base, and remap_string. state0 and state0_size
+// give the byte offset and length within state_table of the initial state --
+// table lookups are expected to start and end in this state, but for
+// truncated UTF-8 strings, may end in a different state. These allow a quick
+// test for that condition. entry_shift is 8 for tables subscripted by a full
+// byte value and 6 for space-optimized tables subscripted by only six
+// significant bits in UTF-8 continuation bytes.
+typedef struct {
+ const uint32 state0;
+ const uint32 state0_size;
+ const uint32 total_size;
+ const int max_expand;
+ const int entry_shift;
+ const int bytes_per_entry;
+ const uint32 losub;
+ const uint32 hiadd;
+ const uint8* state_table;
+ const RemapEntry* remap_base;
+ const uint8* remap_string;
+ const uint8* fast_state;
+} UTF8StateMachineObj;
+
+// Near-duplicate declaration for tables with two-byte entries
+typedef struct {
+ const uint32 state0;
+ const uint32 state0_size;
+ const uint32 total_size;
+ const int max_expand;
+ const int entry_shift;
+ const int bytes_per_entry;
+ const uint32 losub;
+ const uint32 hiadd;
+ const unsigned short* state_table;
+ const RemapEntry* remap_base;
+ const uint8* remap_string;
+ const uint8* fast_state;
+} UTF8StateMachineObj_2;
+
+
+typedef UTF8StateMachineObj UTF8PropObj;
+typedef UTF8StateMachineObj UTF8ScanObj;
+typedef UTF8StateMachineObj UTF8ReplaceObj;
+typedef UTF8StateMachineObj_2 UTF8PropObj_2;
+typedef UTF8StateMachineObj_2 UTF8ReplaceObj_2;
+// NOT IMPLEMENTED typedef UTF8StateMachineObj_2 UTF8ScanObj_2;
+
+
+// Look up property of one UTF-8 character and advance over it
+// Return 0 if input length is zero
+// Return 0 and advance one byte if input is ill-formed
+uint8 UTF8GenericProperty(const UTF8PropObj* st,
+ const uint8** src,
+ int* srclen);
+
+// Look up property of one UTF-8 character (assumed to be valid).
+// (This is a faster version of UTF8GenericProperty.)
+bool UTF8HasGenericProperty(const UTF8PropObj& st, const char* src);
+
+
+// BigOneByte versions are needed for tables > 240 states, but most
+// won't need the TwoByte versions.
+
+// Look up property of one UTF-8 character and advance over it
+// Return 0 if input length is zero
+// Return 0 and advance one byte if input is ill-formed
+uint8 UTF8GenericPropertyBigOneByte(const UTF8PropObj* st,
+ const uint8** src,
+ int* srclen);
+
+
+// TwoByte versions are needed for tables > 240 states that don't fit onto
+// BigOneByte -- rare ultimate fallback
+
+// Look up property of one UTF-8 character (assumed to be valid).
+// (This is a faster version of UTF8GenericProperty.)
+bool UTF8HasGenericPropertyBigOneByte(const UTF8PropObj& st, const char* src);
+
+// Look up property of one UTF-8 character and advance over it
+// Return 0 if input length is zero
+// Return 0 and advance one byte if input is ill-formed
+uint8 UTF8GenericPropertyTwoByte(const UTF8PropObj_2* st,
+ const uint8** src,
+ int* srclen);
+
+// Look up property of one UTF-8 character (assumed to be valid).
+// (This is a faster version of UTF8GenericProperty.)
+bool UTF8HasGenericPropertyTwoByte(const UTF8PropObj_2& st, const char* src);
+
+// Scan a UTF-8 stringpiece based on a state table.
+// Always scan complete UTF-8 characters
+// Set number of bytes scanned. Return reason for exiting
+int UTF8GenericScan(const UTF8ScanObj* st,
+ const StringPiece& str,
+ int* bytes_consumed);
+
+
+
+// Scan a UTF-8 stringpiece based on state table, copying to output stringpiece
+// and doing text replacements.
+// Always scan complete UTF-8 characters
+// Set number of bytes consumed from input, number filled to output.
+// Return reason for exiting
+// Also writes an optional OffsetMap. Pass NULL to skip writing one.
+int UTF8GenericReplace(const UTF8ReplaceObj* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed,
+ OffsetMap* offsetmap);
+
+// Older version without offsetmap
+int UTF8GenericReplace(const UTF8ReplaceObj* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed);
+
+// Older version without is_plain_text or offsetmap
+int UTF8GenericReplace(const UTF8ReplaceObj* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed);
+
+
+// TwoByte version is needed for tables > about 256 states, such
+// as the table for full Unicode 4.1 canonical + compatibility mapping
+
+// Scan a UTF-8 stringpiece based on state table with two-byte entries,
+// copying to output stringpiece
+// and doing text replacements.
+// Always scan complete UTF-8 characters
+// Set number of bytes consumed from input, number filled to output.
+// Return reason for exiting
+// Also writes an optional OffsetMap. Pass NULL to skip writing one.
+int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed,
+ OffsetMap* offsetmap);
+
+// Older version without offsetmap
+int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ bool is_plain_text,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed);
+
+// Older version without is_plain_text or offsetmap
+int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st,
+ const StringPiece& istr,
+ StringPiece& ostr,
+ int* bytes_consumed,
+ int* bytes_filled,
+ int* chars_changed);
+
+
+static const unsigned char kUTF8LenTbl[256] = {
+ 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,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,
+ 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4
+};
+
+inline int UTF8OneCharLen(const char* in) {
+ return kUTF8LenTbl[*reinterpret_cast<const uint8*>(in)];
+}
+
+// Adjust a stringpiece to encompass complete UTF-8 characters.
+// The data pointer will be increased by 0..3 bytes to get to a character
+// boundary, and the length will then be decreased by 0..3 bytes
+// to encompass the last complete character.
+// This is useful especially when a UTF-8 string must be put into a fixed-
+// maximum-size buffer cleanly, such as a MySQL buffer.
+void UTF8TrimToChars(StringPiece* istr);
+
+} // End namespace CLD2
+
+#endif // UTIL_UTF8_UTF8STATETABLE_H_
diff --git a/browser/components/translation/cld2/post.js b/browser/components/translation/cld2/post.js
new file mode 100644
index 000000000..a3e8b8522
--- /dev/null
+++ b/browser/components/translation/cld2/post.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/. */
+
+// The WebIDL binder places static methods on the prototype, rather than
+// on the constructor, which is a bit clumsy, and is definitely not
+// idiomatic.
+LanguageInfo.detectLanguage = LanguageInfo.prototype.detectLanguage;
+
+// Closure is overzealous in its function call optimization, and tries
+// to turn these singleton methods into unbound function calls.
+ensureCache.alloc = ensureCache.alloc.bind(ensureCache);
+ensureCache.prepare = ensureCache.prepare.bind(ensureCache);
+
+// From public/encodings.h. Unfortunately, the WebIDL binder doesn't
+// allow us to define or automatically derive these in the IDL.
+var Encodings = {
+ 'ISO_8859_1' : 0,
+ 'ISO_8859_2' : 1,
+ 'ISO_8859_3' : 2,
+ 'ISO_8859_4' : 3,
+ 'ISO_8859_5' : 4,
+ 'ISO_8859_6' : 5,
+ 'ISO_8859_7' : 6,
+ 'ISO_8859_8' : 7,
+ 'ISO_8859_9' : 8,
+ 'ISO_8859_10' : 9,
+ 'JAPANESE_EUC_JP' : 10,
+ 'EUC_JP' : 10,
+ 'JAPANESE_SHIFT_JIS' : 11,
+ 'SHIFT_JIS' : 11,
+ 'JAPANESE_JIS' : 12,
+ 'JIS' : 12,
+ 'CHINESE_BIG5' : 13,
+ 'BIG5' : 13,
+ 'CHINESE_GB' : 14,
+ 'CHINESE_EUC_CN' : 15,
+ 'EUC_CN' : 15,
+ 'KOREAN_EUC_KR' : 16,
+ 'EUC_KR' : 16,
+ 'UNICODE_UNUSED' : 17,
+ 'CHINESE_EUC_DEC' : 18,
+ 'EUC_DEC' : 18,
+ 'CHINESE_CNS' : 19,
+ 'CNS' : 19,
+ 'CHINESE_BIG5_CP950' : 20,
+ 'BIG5_CP950' : 20,
+ 'JAPANESE_CP932' : 21,
+ 'CP932' : 21,
+ 'UTF8' : 22,
+ 'UNKNOWN_ENCODING' : 23,
+ 'ASCII_7BIT' : 24,
+ 'RUSSIAN_KOI8_R' : 25,
+ 'KOI8_R' : 25,
+ 'RUSSIAN_CP1251' : 26,
+ 'CP1251' : 26,
+ 'MSFT_CP1252' : 27,
+ 'CP1252' : 27,
+ 'RUSSIAN_KOI8_RU' : 28,
+ 'KOI8_RU' : 28,
+ 'MSFT_CP1250' : 29,
+ 'CP1250' : 29,
+ 'ISO_8859_15' : 30,
+ 'MSFT_CP1254' : 31,
+ 'CP1254' : 31,
+ 'MSFT_CP1257' : 32,
+ 'CP1257' : 32,
+ 'ISO_8859_11' : 33,
+ 'MSFT_CP874' : 34,
+ 'CP874' : 34,
+ 'MSFT_CP1256' : 35,
+ 'CP1256' : 35,
+ 'MSFT_CP1255' : 36,
+ 'CP1255' : 36,
+ 'ISO_8859_8_I' : 37,
+ 'HEBREW_VISUAL' : 38,
+ 'CZECH_CP852' : 39,
+ 'CP852' : 39,
+ 'CZECH_CSN_369103' : 40,
+ 'CSN_369103' : 40,
+ 'MSFT_CP1253' : 41,
+ 'CP1253' : 41,
+ 'RUSSIAN_CP866' : 42,
+ 'CP866' : 42,
+ 'ISO_8859_13' : 43,
+ 'ISO_2022_KR' : 44,
+ 'GBK' : 45,
+ 'GB18030' : 46,
+ 'BIG5_HKSCS' : 47,
+ 'ISO_2022_CN' : 48,
+ 'TSCII' : 49,
+ 'TAMIL_MONO' : 50,
+ 'TAMIL_BI' : 51,
+ 'JAGRAN' : 52,
+ 'MACINTOSH_ROMAN' : 53,
+ 'UTF7' : 54,
+ 'BHASKAR' : 55,
+ 'HTCHANAKYA' : 56,
+ 'UTF16BE' : 57,
+ 'UTF16LE' : 58,
+ 'UTF32BE' : 59,
+ 'UTF32LE' : 60,
+ 'BINARYENC' : 61,
+ 'HZ_GB_2312' : 62,
+ 'UTF8UTF8' : 63,
+ 'TAM_ELANGO' : 64,
+ 'TAM_LTTMBARANI' : 65,
+ 'TAM_SHREE' : 66,
+ 'TAM_TBOOMIS' : 67,
+ 'TAM_TMNEWS' : 68,
+ 'TAM_WEBTAMIL' : 69,
+ 'KDDI_SHIFT_JIS' : 70,
+ 'DOCOMO_SHIFT_JIS' : 71,
+ 'SOFTBANK_SHIFT_JIS' : 72,
+ 'KDDI_ISO_2022_JP' : 73,
+ 'ISO_2022_JP' : 73,
+ 'SOFTBANK_ISO_2022_JP' : 74,
+};
+
+// Accept forms both with and without underscores/hypens.
+for (let code of Object.keys(Encodings)) {
+ if (code['includes']("_"))
+ Encodings[code.replace(/_/g, "")] = Encodings[code];
+}
+
+addOnPreMain(function() {
+
+ onmessage = function(aMsg) {
+ let data = aMsg['data'];
+
+ let langInfo;
+ if (data['tld'] == undefined && data['encoding'] == undefined && data['language'] == undefined) {
+ langInfo = LanguageInfo.detectLanguage(data['text'], !data['isHTML']);
+ } else {
+ // Do our best to find the given encoding in the encodings table.
+ // Otherwise, just fall back to unknown.
+ let enc = String(data['encoding']).toUpperCase().replace(/[_-]/g, "");
+
+ let encoding;
+ if (Encodings.hasOwnProperty(enc))
+ encoding = Encodings[enc];
+ else
+ encoding = Encodings['UNKNOWN_ENCODING'];
+
+ langInfo = LanguageInfo.detectLanguage(data['text'], !data['isHTML'],
+ data['tld'] || null,
+ encoding,
+ data['language'] || null);
+ }
+
+ postMessage({
+ 'language': langInfo.getLanguageCode(),
+ 'confident': langInfo.getIsReliable(),
+
+ 'languages': new Array(3).fill(0).map((_, index) => {
+ let lang = langInfo.get_languages(index);
+ return {
+ 'languageCode': lang.getLanguageCode(),
+ 'percent': lang.getPercent(),
+ };
+ }).filter(lang => {
+ // Ignore empty results.
+ return lang['languageCode'] != "un" || lang['percent'] > 0;
+ }),
+ });
+
+ Module.destroy(langInfo);
+ };
+
+ postMessage("ready");
+});
diff --git a/browser/components/translation/cld2/public/compact_lang_det.h b/browser/components/translation/cld2/public/compact_lang_det.h
new file mode 100644
index 000000000..da59abd63
--- /dev/null
+++ b/browser/components/translation/cld2/public/compact_lang_det.h
@@ -0,0 +1,320 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+// NOTE:
+// Baybayin (ancient script of the Philippines) is detected as TAGALOG.
+// Chu Nom (Vietnamese ancient Han characters) is detected as VIETNAMESE.
+// HAITIAN_CREOLE is detected as such.
+// NORWEGIAN and NORWEGIAN_N are detected separately (but not robustly)
+// PORTUGUESE, PORTUGUESE_P, and PORTUGUESE_B are all detected as PORTUGUESE.
+// ROMANIAN-Latin is detected as ROMANIAN; ROMANIAN-Cyrillic as ROMANIAN.
+// BOSNIAN is not detected as such, but likely scores as Croatian or Serbian.
+// MONTENEGRIN is not detected as such, but likely scores as Serbian.
+// CROATIAN is detected in the Latin script
+// SERBIAN is detected in the Cyrililc and Latin scripts
+// Zhuang is detected in the Latin script only.
+//
+// The languages X_PIG_LATIN and X_KLINGON are detected in the
+// extended calls ExtDetectLanguageSummary().
+//
+// UNKNOWN_LANGUAGE is returned if no language's internal reliablity measure
+// is high enough. This happens with non-text input such as the bytes of a
+// JPEG, and also with text in languages outside training set.
+//
+// The following languages are to be detected in multiple scripts:
+// AZERBAIJANI (Latin, Cyrillic*, Arabic*)
+// BURMESE (Latin, Myanmar)
+// HAUSA (Latin, Arabic)
+// KASHMIRI (Arabic, Devanagari)
+// KAZAKH (Latin, Cyrillic, Arabic)
+// KURDISH (Latin*, Arabic)
+// KYRGYZ (Cyrillic, Arabic)
+// LIMBU (Devanagari, Limbu)
+// MONGOLIAN (Cyrillic, Mongolian)
+// SANSKRIT (Latin, Devanagari)
+// SINDHI (Arabic, Devanagari)
+// TAGALOG (Latin, Tagalog)
+// TAJIK (Cyrillic, Arabic*)
+// TATAR (Latin, Cyrillic, Arabic)
+// TURKMEN (Latin, Cyrillic, Arabic)
+// UIGHUR (Latin, Cyrillic, Arabic)
+// UZBEK (Latin, Cyrillic, Arabic)
+//
+// * Due to a shortage of training text, AZERBAIJANI is not currently detected
+// in Arabic or Cyrillic scripts, nor KURDISH in Latin script, nor TAJIK in
+// Arabic script.
+//
+
+#ifndef I18N_ENCODINGS_CLD2_PUBLIC_COMPACT_LANG_DET_H_
+#define I18N_ENCODINGS_CLD2_PUBLIC_COMPACT_LANG_DET_H_
+
+#include <vector>
+#include "../internal/lang_script.h" // For Language
+
+namespace CLD2 {
+
+ // Scan interchange-valid UTF-8 bytes and detect most likely language,
+ // or set of languages.
+ //
+ // Design goals:
+ // Skip over big stretches of HTML tags
+ // Able to return ranges of different languages
+ // Relatively small tables and relatively fast processing
+ // Thread safe
+ //
+ // For HTML documents, tags are skipped, along with <script> ... </script>
+ // and <style> ... </style> sequences, and entities are expanded.
+ //
+ // We distinguish between bytes of the raw input buffer and bytes of non-tag
+ // text letters. Since tags can be over 50% of the bytes of an HTML Page,
+ // and are nearly all seven-bit ASCII English, we prefer to distinguish
+ // language mixture fractions based on just the non-tag text.
+ //
+ // Inputs: text and text_length
+ // Code skips HTML tags and expands HTML entities, unless
+ // is_plain_text is true
+ // Outputs:
+ // language3 is an array of the top 3 languages or UNKNOWN_LANGUAGE
+ // percent3 is an array of the text percentages 0..100 of the top 3 languages
+ // text_bytes is the amount of non-tag/letters-only text found
+ // is_reliable set true if the returned Language is some amount more
+ // probable then the second-best Language. Calculation is a complex function
+ // of the length of the text and the different-script runs of text.
+ // Return value: the most likely Language for the majority of the input text
+ // Length 0 input returns UNKNOWN_LANGUAGE. Very short indeterminate text
+ // defaults to ENGLISH.
+ //
+ // The first two versions return ENGLISH instead of UNKNOWN_LANGUAGE, for
+ // backwards compatibility with a different detector.
+ //
+ // The third version may return UNKNOWN_LANGUAGE, and also returns extended
+ // language codes from lang_script.h
+ //
+
+
+ // Instead of individual arguments, pass in hints as an initialized struct
+ // Init to {NULL, NULL, UNKNOWN_ENCODING, UNKNOWN_LANGUAGE} if not known.
+ //
+ // Pass in hints whenever possible; doing so improves detection accuracy. The
+ // set of passed-in hints are all information that is external to the text
+ // itself.
+ //
+ // The content_language_hint is intended to come from an HTTP header
+ // Content-Language: field, the tld_hint from the hostname of a URL, the
+ // encoding-hint from an encoding detector applied to the input
+ // document, and the language hint from any other context you might have.
+ // The lang= tags inside an HTML document will be picked up as hints
+ // by code within the compact language detector.
+
+ typedef struct {
+ const char* content_language_hint; // "mi,en" boosts Maori and English
+ const char* tld_hint; // "id" boosts Indonesian
+ int encoding_hint; // SJS boosts Japanese
+ Language language_hint; // ITALIAN boosts it
+ } CLDHints;
+
+ static const int kMaxResultChunkBytes = 65535;
+
+ // For returning a vector of per-language pieces of the input buffer
+ // Unreliable and too-short are mapped to UNKNOWN_LANGUAGE
+ typedef struct {
+ int offset; // Starting byte offset in original buffer
+ uint16 bytes; // Number of bytes in chunk
+ uint16 lang1; // Top lang, as full Language. Apply
+ // static_cast<Language>() to this short value.
+ } ResultChunk;
+ typedef std::vector<ResultChunk> ResultChunkVector;
+
+
+ // Scan interchange-valid UTF-8 bytes and detect most likely language
+ Language DetectLanguage(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ bool* is_reliable);
+
+ // Scan interchange-valid UTF-8 bytes and detect list of top 3 languages.
+ // language3[0] is usually also the return value
+ Language DetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable);
+
+ // Same as above, with hints supplied
+ // Scan interchange-valid UTF-8 bytes and detect list of top 3 languages.
+ // language3[0] is usually also the return value
+ Language DetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const char* tld_hint, // "id" boosts Indonesian
+ int encoding_hint, // SJS boosts Japanese
+ Language language_hint, // ITALIAN boosts it
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable);
+
+ // Scan interchange-valid UTF-8 bytes and detect list of top 3 extended
+ // languages.
+ //
+ // Extended languages are additional interface languages and Unicode
+ // single-language scripts, from lang_script.h
+ //
+ // language3[0] is usually also the return value
+ Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable);
+
+ // Same as above, with hints supplied
+ // Scan interchange-valid UTF-8 bytes and detect list of top 3 extended
+ // languages.
+ //
+ // Extended languages are additional Google interface languages and Unicode
+ // single-language scripts, from lang_script.h
+ //
+ // language3[0] is usually also the return value
+ Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const char* tld_hint, // "id" boosts Indonesian
+ int encoding_hint, // SJS boosts Japanese
+ Language language_hint, // ITALIAN boosts it
+ Language* language3,
+ int* percent3,
+ int* text_bytes,
+ bool* is_reliable);
+
+ // Same as above, and also returns 3 internal language scores as a ratio to
+ // normal score for real text in that language. Scores close to 1.0 indicate
+ // normal text, while scores far away from 1.0 indicate badly-skewed text or
+ // gibberish
+ //
+ Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const char* tld_hint, // "id" boosts Indonesian
+ int encoding_hint, // SJS boosts Japanese
+ Language language_hint, // ITALIAN boosts it
+ Language* language3,
+ int* percent3,
+ double* normalized_score3,
+ int* text_bytes,
+ bool* is_reliable);
+
+
+ // Use this one.
+ // Hints are collected into a struct.
+ // Flags are passed in (normally zero).
+ //
+ // Also returns 3 internal language scores as a ratio to
+ // normal score for real text in that language. Scores close to 1.0 indicate
+ // normal text, while scores far away from 1.0 indicate badly-skewed text or
+ // gibberish
+ //
+ // Returns a vector of chunks in different languages, so that caller may
+ // spell-check, translate, or otherwaise process different parts of the input
+ // buffer in language-dependant ways.
+ //
+ Language ExtDetectLanguageSummary(
+ const char* buffer,
+ int buffer_length,
+ bool is_plain_text,
+ const CLDHints* cld_hints,
+ int flags,
+ Language* language3,
+ int* percent3,
+ double* normalized_score3,
+ ResultChunkVector* resultchunkvector,
+ int* text_bytes,
+ bool* is_reliable);
+
+ // Return version text string
+ // String is "code_version - data_build_date"
+ const char* DetectLanguageVersion();
+
+
+ // Public use flags, debug output controls
+ static const int kCLDFlagScoreAsQuads = 0x0100; // Force Greek, etc. => quads
+ static const int kCLDFlagHtml = 0x0200; // Debug HTML => stderr
+ static const int kCLDFlagCr = 0x0400; // <cr> per chunk if HTML
+ static const int kCLDFlagVerbose = 0x0800; // More debug HTML => stderr
+ static const int kCLDFlagQuiet = 0x1000; // Less debug HTML => stderr
+ static const int kCLDFlagEcho = 0x2000; // Echo input => stderr
+
+
+/***
+
+Flag meanings:
+ kCLDFlagScoreAsQuads
+ Normally, several languages are detected solely by their Unicode script.
+ Combined with appropritate lookup tables, this flag forces them instead
+ to be detected via quadgrams. This can be a useful refinement when looking
+ for meaningful text in these languages, instead of just character sets.
+ The default tables do not support this use.
+ kCLDFlagHtml
+ For each detection call, write an HTML file to stderr, showing the text
+ chunks and their detected languages.
+ kCLDFlagCr
+ In that HTML file, force a new line for each chunk.
+ kCLDFlagVerbose
+ In that HTML file, show every lookup entry.
+ kCLDFlagQuiet
+ In that HTML file, suppress most of the output detail.
+ kCLDFlagEcho
+ Echo every input buffer to stderr.
+***/
+
+// Debug output: Print the resultchunkvector to file f
+void DumpResultChunkVector(FILE* f, const char* src,
+ ResultChunkVector* resultchunkvector);
+
+#ifdef CLD2_DYNAMIC_MODE
+
+// If compiled with dynamic mode, load data from the specified file location.
+// If other data has already been loaded, it is discarded and the data is read
+// in from the specified file location again (even if the file has not changed).
+// WARNING: Before calling this method, language detection will always fail
+// and will always return the unknown language.
+void loadData(const char* fileName);
+
+// If compiled with dynamic mode, unload the previously-loaded data.
+// WARNING: After calling this method, language detection will no longer work
+// and will always return the unknown language.
+void unloadData();
+
+// Returns true if and only if data has been loaded via a call to loadData(...)
+// and has not been subsequently unladed via a call to unloadDate().
+bool isDataLoaded();
+
+#endif // #ifdef CLD2_DYNAMIC_MODE
+
+}; // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_PUBLIC_COMPACT_LANG_DET_H_
diff --git a/browser/components/translation/cld2/public/encodings.h b/browser/components/translation/cld2/public/encodings.h
new file mode 100644
index 000000000..1eb8f0a15
--- /dev/null
+++ b/browser/components/translation/cld2/public/encodings.h
@@ -0,0 +1,169 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+
+#ifndef I18N_ENCODINGS_CLD2_PUBLIC_ENCODINGS_H__
+#define I18N_ENCODINGS_CLD2_PUBLIC_ENCODINGS_H__
+
+namespace CLD2 {
+
+enum Encoding {
+ ISO_8859_1 = 0, // ASCII
+ ISO_8859_2 = 1, // Latin2
+ ISO_8859_3 = 2, //
+ ISO_8859_4 = 3, // Latin4
+ ISO_8859_5 = 4, // ISO-8859-5
+ ISO_8859_6 = 5, // Arabic
+ ISO_8859_7 = 6, // Greek
+ ISO_8859_8 = 7, // Hebrew
+ ISO_8859_9 = 8, //
+ ISO_8859_10 = 9, //
+ JAPANESE_EUC_JP = 10, // EUC_JP
+ JAPANESE_SHIFT_JIS = 11, // SJS
+ JAPANESE_JIS = 12, // JIS
+ CHINESE_BIG5 = 13, // BIG5
+ CHINESE_GB = 14, // GB
+ CHINESE_EUC_CN = 15, // Misnamed. Should be EUC_TW. Was Basis Tech
+ // CNS11643EUC, before that EUC-CN(!)
+ KOREAN_EUC_KR = 16, // KSC
+ UNICODE_UNUSED = 17, // Unicode
+ CHINESE_EUC_DEC = 18, // Misnamed. Should be EUC_TW. Was
+ // CNS11643EUC, before that EUC.
+ CHINESE_CNS = 19, // Misnamed. Should be EUC_TW. Was
+ // CNS11643EUC, before that CNS.
+ CHINESE_BIG5_CP950 = 20, // BIG5_CP950
+ JAPANESE_CP932 = 21, // CP932
+ UTF8 = 22,
+ UNKNOWN_ENCODING = 23,
+ ASCII_7BIT = 24, // ISO_8859_1 with all characters <= 127.
+ RUSSIAN_KOI8_R = 25, // KOI8R
+ RUSSIAN_CP1251 = 26, // CP1251
+
+ //----------------------------------------------------------
+ MSFT_CP1252 = 27, // 27: CP1252 aka MSFT euro ascii
+ RUSSIAN_KOI8_RU = 28, // CP21866 aka KOI8-U, used for Ukrainian.
+ // Misnamed, this is _not_ KOI8-RU but KOI8-U.
+ // KOI8-U is used much more often than KOI8-RU.
+ MSFT_CP1250 = 29, // CP1250 aka MSFT eastern european
+ ISO_8859_15 = 30, // aka ISO_8859_0 aka ISO_8859_1 euroized
+ //----------------------------------------------------------
+
+ //----------------------------------------------------------
+ MSFT_CP1254 = 31, // used for Turkish
+ MSFT_CP1257 = 32, // used in Baltic countries
+ //----------------------------------------------------------
+
+ //----------------------------------------------------------
+ //----------------------------------------------------------
+ ISO_8859_11 = 33, // aka TIS-620, used for Thai
+ MSFT_CP874 = 34, // used for Thai
+ MSFT_CP1256 = 35, // used for Arabic
+
+ //----------------------------------------------------------
+ MSFT_CP1255 = 36, // Logical Hebrew Microsoft
+ ISO_8859_8_I = 37, // Iso Hebrew Logical
+ HEBREW_VISUAL = 38, // Iso Hebrew Visual
+ //----------------------------------------------------------
+
+ //----------------------------------------------------------
+ CZECH_CP852 = 39,
+ CZECH_CSN_369103 = 40, // aka ISO_IR_139 aka KOI8_CS
+ MSFT_CP1253 = 41, // used for Greek
+ RUSSIAN_CP866 = 42,
+ //----------------------------------------------------------
+
+ //----------------------------------------------------------
+ // Handled by iconv in glibc
+ ISO_8859_13 = 43,
+ ISO_2022_KR = 44,
+ GBK = 45,
+ GB18030 = 46,
+ BIG5_HKSCS = 47,
+ ISO_2022_CN = 48,
+
+ //-----------------------------------------------------------
+ // Following 4 encodings are deprecated (font encodings)
+ TSCII = 49,
+ TAMIL_MONO = 50,
+ TAMIL_BI = 51,
+ JAGRAN = 52,
+
+
+ MACINTOSH_ROMAN = 53,
+ UTF7 = 54,
+
+ //-----------------------------------------------------------
+ // Following 2 encodings are deprecated (font encodings)
+ BHASKAR = 55, // Indic encoding - Devanagari
+ HTCHANAKYA = 56, // 56 Indic encoding - Devanagari
+
+ //-----------------------------------------------------------
+ UTF16BE = 57, // big-endian UTF-16
+ UTF16LE = 58, // little-endian UTF-16
+ UTF32BE = 59, // big-endian UTF-32
+ UTF32LE = 60, // little-endian UTF-32
+ //-----------------------------------------------------------
+
+ //-----------------------------------------------------------
+ // An encoding that means "This is not text, but it may have some
+ // simple ASCII text embedded". Intended input conversion
+ // is to keep strings of >=4 seven-bit ASCII characters
+ BINARYENC = 61,
+ //-----------------------------------------------------------
+
+ //-----------------------------------------------------------
+ // Some Web pages allow a mixture of HZ-GB and GB-2312 by using
+ // ~{ ... ~} for 2-byte pairs, and the browsers support this.
+ HZ_GB_2312 = 62,
+ //-----------------------------------------------------------
+
+ //-----------------------------------------------------------
+ // Some external vendors make the common input error of
+ // converting MSFT_CP1252 to UTF8 *twice*.
+ UTF8UTF8 = 63,
+ //-----------------------------------------------------------
+
+ //-----------------------------------------------------------
+ // Following 6 encodings are deprecated (font encodings)
+ TAM_ELANGO = 64, // Elango - Tamil
+ TAM_LTTMBARANI = 65, // Barani - Tamil
+ TAM_SHREE = 66, // Shree - Tamil
+ TAM_TBOOMIS = 67, // TBoomis - Tamil
+ TAM_TMNEWS = 68, // TMNews - Tamil
+ TAM_WEBTAMIL = 69, // Webtamil - Tamil
+ //-----------------------------------------------------------
+
+ //-----------------------------------------------------------
+ // Shift_JIS variants used by Japanese cell phone carriers.
+ KDDI_SHIFT_JIS = 70,
+ DOCOMO_SHIFT_JIS = 71,
+ SOFTBANK_SHIFT_JIS = 72,
+ // ISO-2022-JP variants used by KDDI and SoftBank.
+ KDDI_ISO_2022_JP = 73,
+ SOFTBANK_ISO_2022_JP = 74,
+ //-----------------------------------------------------------
+
+ NUM_ENCODINGS = 75, // Always keep this at the end. It is not a
+ // valid Encoding enum, it is only used to
+ // indicate the total number of Encodings.
+};
+
+} // End namespace CLD2
+
+#endif // I18N_ENCODINGS_CLD2_PUBLIC_ENCODINGS_H__
+
+
diff --git a/browser/components/translation/jar.mn b/browser/components/translation/jar.mn
new file mode 100644
index 000000000..be744cb9e
--- /dev/null
+++ b/browser/components/translation/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+browser.jar:
+ content/browser/translation-infobar.xml
+ content/browser/microsoft-translator-attribution.png
diff --git a/browser/components/translation/microsoft-translator-attribution.png b/browser/components/translation/microsoft-translator-attribution.png
new file mode 100644
index 000000000..d9d277461
--- /dev/null
+++ b/browser/components/translation/microsoft-translator-attribution.png
Binary files differ
diff --git a/browser/components/translation/moz.build b/browser/components/translation/moz.build
new file mode 100644
index 000000000..468f2af20
--- /dev/null
+++ b/browser/components/translation/moz.build
@@ -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/.
+
+EXTRA_JS_MODULES.translation = [
+ 'BingTranslator.jsm',
+ 'cld2/cld-worker.js',
+ 'cld2/cld-worker.js.mem',
+ 'LanguageDetector.jsm',
+ 'Translation.jsm',
+ 'TranslationContentHandler.jsm',
+ 'TranslationDocument.jsm',
+ 'YandexTranslator.jsm'
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser.ini'
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'test/unit/xpcshell.ini'
+]
diff --git a/browser/components/translation/test/.eslintrc.js b/browser/components/translation/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/translation/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/translation/test/bing.sjs b/browser/components/translation/test/bing.sjs
new file mode 100644
index 000000000..ce3b96855
--- /dev/null
+++ b/browser/components/translation/test/bing.sjs
@@ -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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, Constructor: CC} = Components;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(req, res) {
+ try {
+ reallyHandleRequest(req, res);
+ } catch (ex) {
+ res.setStatusLine("1.0", 200, "AlmostOK");
+ let msg = "Error handling request: " + ex + "\n" + ex.stack;
+ log(msg);
+ res.write(msg);
+ }
+}
+
+function log(msg) {
+ // dump("BING-SERVER-MOCK: " + msg + "\n");
+}
+
+const statusCodes = {
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 503: "Service Unavailable"
+};
+
+function HTTPError(code = 500, message) {
+ this.code = code;
+ this.name = statusCodes[code] || "HTTPError";
+ this.message = message || this.name;
+}
+HTTPError.prototype = new Error();
+HTTPError.prototype.constructor = HTTPError;
+
+function sendError(res, err) {
+ if (!(err instanceof HTTPError)) {
+ err = new HTTPError(typeof err == "number" ? err : 500,
+ err.message || typeof err == "string" ? err : "");
+ }
+ res.setStatusLine("1.1", err.code, err.name);
+ res.write(err.message);
+}
+
+function parseQuery(query) {
+ let ret = {};
+ for (let param of query.replace(/^[?&]/, "").split("&")) {
+ param = param.split("=");
+ if (!param[0])
+ continue;
+ ret[unescape(param[0])] = unescape(param[1]);
+ }
+ return ret;
+}
+
+function getRequestBody(req) {
+ let avail;
+ let bytes = [];
+ let body = new BinaryInputStream(req.bodyInputStream);
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ return String.fromCharCode.apply(null, bytes);
+}
+
+function sha1(str) {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ // `result` is an out parameter, `result.value` will contain the array length.
+ let result = {};
+ // `data` is an array of bytes.
+ let data = converter.convertToByteArray(str, result);
+ let ch = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ ch.init(ch.SHA1);
+ ch.update(data, data.length);
+ let hash = ch.finish(false);
+
+ // Return the two-digit hexadecimal code for a byte.
+ function toHexString(charCode) {
+ return ("0" + charCode.toString(16)).slice(-2);
+ }
+
+ // Convert the binary hash data to a hex string.
+ return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+}
+
+function parseXml(body) {
+ let DOMParser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+ let xml = DOMParser.parseFromString(body, "text/xml");
+ if (xml.documentElement.localName == "parsererror")
+ throw new Error("Invalid XML");
+ return xml;
+}
+
+function getInputStream(path) {
+ let file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("CurWorkD", Ci.nsILocalFile);
+ for (let part of path.split("/"))
+ file.append(part);
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, 1, 0, false);
+ return fileStream;
+}
+
+function checkAuth(req) {
+ let err = new Error("Authorization failed");
+ err.code = 401;
+
+ if (!req.hasHeader("Authorization"))
+ throw new HTTPError(401, "No Authorization header provided.");
+
+ let auth = req.getHeader("Authorization");
+ if (!auth.startsWith("Bearer "))
+ throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
+
+ // Rejecting inactive subscriptions.
+ if (auth.includes("inactive")) {
+ const INACTIVE_STATE_RESPONSE = "<html><body><h1>TranslateApiException</h1><p>Method: TranslateArray()</p><p>Message: The Azure Market Place Translator Subscription associated with the request credentials is not in an active state.</p><code></code><p>message id=5641.V2_Rest.TranslateArray.48CC6470</p></body></html>";
+ throw new HTTPError(401, INACTIVE_STATE_RESPONSE);
+ }
+
+}
+
+function reallyHandleRequest(req, res) {
+ log("method: " + req.method);
+ if (req.method != "POST") {
+ sendError(res, "Bing only deals with POST requests, not '" + req.method + "'.");
+ return;
+ }
+
+ let body = getRequestBody(req);
+ log("body: " + body);
+
+ // First, we'll see if we're dealing with an XML body:
+ let contentType = req.hasHeader("Content-Type") ? req.getHeader("Content-Type") : null;
+ log("contentType: " + contentType);
+
+ if (contentType.startsWith("text/xml")) {
+ try {
+ // For all these requests the client needs to supply the correct
+ // authentication headers.
+ checkAuth(req);
+
+ let xml = parseXml(body);
+ let method = xml.documentElement.localName;
+ log("invoking method: " + method);
+ // If the requested method is supported, delegate it to its handler.
+ if (methodHandlers[method])
+ methodHandlers[method](res, xml);
+ else
+ throw new HTTPError(501);
+ } catch (ex) {
+ sendError(res, ex, ex.code);
+ }
+ } else {
+ // Not XML, so it must be a query-string.
+ let params = parseQuery(body);
+
+ // Delegate an authentication request to the correct handler.
+ if ("grant_type" in params && params.grant_type == "client_credentials")
+ methodHandlers.authenticate(res, params);
+ else
+ sendError(res, 501);
+ }
+}
+
+const methodHandlers = {
+ authenticate: function(res, params) {
+ // Validate a few required parameters.
+ if (params.scope != "http://api.microsofttranslator.com") {
+ sendError(res, "Invalid scope.");
+ return;
+ }
+ if (!params.client_id) {
+ sendError(res, "Missing client_id param.");
+ return;
+ }
+ if (!params.client_secret) {
+ sendError(res, "Missing client_secret param.");
+ return;
+ }
+
+ // Defines the tokens for certain client ids.
+ const TOKEN_MAP = {
+ 'testInactive' : 'inactive',
+ 'testClient' : 'test'
+ };
+ let token = 'test'; // Default token.
+ if((params.client_id in TOKEN_MAP)){
+ token = TOKEN_MAP[params.client_id];
+ }
+ let content = JSON.stringify({
+ access_token: token,
+ expires_in: 600
+ });
+
+ res.setStatusLine("1.1", 200, "OK");
+ res.setHeader("Content-Length", String(content.length));
+ res.setHeader("Content-Type", "application/json");
+ res.write(content);
+ },
+
+ TranslateArrayRequest: function(res, xml, body) {
+ let from = xml.querySelector("From").firstChild.nodeValue;
+ let to = xml.querySelector("To").firstChild.nodeValue
+ log("translating from '" + from + "' to '" + to + "'");
+
+ res.setStatusLine("1.1", 200, "OK");
+ res.setHeader("Content-Type", "text/xml");
+
+ let hash = sha1(body).substr(0, 10);
+ log("SHA1 hash of content: " + hash);
+ let inputStream = getInputStream(
+ "browser/browser/components/translation/test/fixtures/result-" + hash + ".txt");
+ res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
+ inputStream.close();
+ }
+};
diff --git a/browser/components/translation/test/browser.ini b/browser/components/translation/test/browser.ini
new file mode 100644
index 000000000..59e481855
--- /dev/null
+++ b/browser/components/translation/test/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ bing.sjs
+ yandex.sjs
+ fixtures/bug1022725-fr.html
+ fixtures/result-da39a3ee5e.txt
+ fixtures/result-yandex-d448894848.json
+
+[browser_translation_bing.js]
+[browser_translation_yandex.js]
+[browser_translation_telemetry.js]
+[browser_translation_infobar.js]
+[browser_translation_exceptions.js]
diff --git a/browser/components/translation/test/browser_translation_bing.js b/browser/components/translation/test/browser_translation_bing.js
new file mode 100644
index 000000000..399a67022
--- /dev/null
+++ b/browser/components/translation/test/browser_translation_bing.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/. */
+
+// Test the Bing Translator client against a mock Bing service, bing.sjs.
+
+"use strict";
+
+const kClientIdPref = "browser.translation.bing.clientIdOverride";
+const kClientSecretPref = "browser.translation.bing.apiKeyOverride";
+
+const {BingTranslator} = Cu.import("resource:///modules/translation/BingTranslator.jsm", {});
+const {TranslationDocument} = Cu.import("resource:///modules/translation/TranslationDocument.jsm", {});
+const {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+
+add_task(function* setup() {
+ Services.prefs.setCharPref(kClientIdPref, "testClient");
+ Services.prefs.setCharPref(kClientSecretPref, "testSecret");
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(kClientIdPref);
+ Services.prefs.clearUserPref(kClientSecretPref);
+ });
+});
+
+/**
+ * Checks if the translation is happening.
+ */
+add_task(function* test_bing_translation() {
+
+ // Ensure the correct client id is used for authentication.
+ Services.prefs.setCharPref(kClientIdPref, "testClient");
+
+ // Loading the fixture page.
+ let url = constructFixtureURL("bug1022725-fr.html");
+ let tab = yield promiseTestPageLoad(url);
+
+ // Translating the contents of the loaded tab.
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ yield ContentTask.spawn(browser, null, function*() {
+ Cu.import("resource:///modules/translation/BingTranslator.jsm");
+ Cu.import("resource:///modules/translation/TranslationDocument.jsm");
+
+ let client = new BingTranslator(
+ new TranslationDocument(content.document), "fr", "en");
+ let result = yield client.translate();
+
+ // XXXmikedeboer; here you would continue the test/ content inspection.
+ Assert.ok(result, "There should be a result");
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensures that the BingTranslator handles out-of-valid-key response
+ * correctly. Sometimes Bing Translate replies with
+ * "request credentials is not in an active state" error. BingTranslator
+ * should catch this error and classify it as Service Unavailable.
+ *
+ */
+add_task(function* test_handling_out_of_valid_key_error() {
+
+ // Simulating request from inactive subscription.
+ Services.prefs.setCharPref(kClientIdPref, "testInactive");
+
+ // Loading the fixture page.
+ let url = constructFixtureURL("bug1022725-fr.html");
+ let tab = yield promiseTestPageLoad(url);
+
+ // Translating the contents of the loaded tab.
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ yield ContentTask.spawn(browser, null, function*() {
+ Cu.import("resource:///modules/translation/BingTranslator.jsm");
+ Cu.import("resource:///modules/translation/TranslationDocument.jsm");
+
+ let client = new BingTranslator(
+ new TranslationDocument(content.document), "fr", "en");
+ client._resetToken();
+ try {
+ yield client.translate();
+ } catch (ex) {
+ // It is alright that the translation fails.
+ }
+ client._resetToken();
+
+ // Checking if the client detected service and unavailable.
+ Assert.ok(client._serviceUnavailable, "Service should be detected unavailable.");
+ });
+
+ // Cleaning up.
+ Services.prefs.setCharPref(kClientIdPref, "testClient");
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * A helper function for constructing a URL to a page stored in the
+ * local fixture folder.
+ *
+ * @param filename Name of a fixture file.
+ */
+function constructFixtureURL(filename) {
+ // Deduce the Mochitest server address in use from a pref that was pre-processed.
+ let server = Services.prefs.getCharPref("browser.translation.bing.authURL")
+ .replace("http://", "");
+ server = server.substr(0, server.indexOf("/"));
+ let url = "http://" + server +
+ "/browser/browser/components/translation/test/fixtures/" + filename;
+ return url;
+}
+
+/**
+ * A helper function to open a new tab and wait for its content to load.
+ *
+ * @param String url A URL to be loaded in the new tab.
+ */
+function promiseTestPageLoad(url) {
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ let browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function listener() {
+ if (browser.currentURI.spec == "about:blank")
+ return;
+ info("Page loaded: " + browser.currentURI.spec);
+ browser.removeEventListener("load", listener, true);
+ deferred.resolve(tab);
+ }, true);
+ return deferred.promise;
+}
diff --git a/browser/components/translation/test/browser_translation_exceptions.js b/browser/components/translation/test/browser_translation_exceptions.js
new file mode 100644
index 000000000..bf6875768
--- /dev/null
+++ b/browser/components/translation/test/browser_translation_exceptions.js
@@ -0,0 +1,327 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// tests the translation infobar, using a fake 'Translation' implementation.
+
+var tmp = {};
+Cu.import("resource:///modules/translation/Translation.jsm", tmp);
+Cu.import("resource://gre/modules/Promise.jsm", tmp);
+var {Translation, Promise} = tmp;
+
+const kLanguagesPref = "browser.translation.neverForLanguages";
+const kShowUIPref = "browser.translation.ui.show";
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(kShowUIPref, true);
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ Services.prefs.clearUserPref(kShowUIPref);
+ });
+ tab.linkedBrowser.addEventListener("load", function onload() {
+ tab.linkedBrowser.removeEventListener("load", onload, true);
+ Task.spawn(function* () {
+ for (let test of gTests) {
+ info(test.desc);
+ yield test.run();
+ }
+ }).then(finish, ex => {
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+ }, true);
+
+ content.location = "http://example.com/";
+}
+
+function getLanguageExceptions() {
+ let langs = Services.prefs.getCharPref(kLanguagesPref);
+ return langs ? langs.split(",") : [];
+}
+
+function getDomainExceptions() {
+ let results = [];
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+
+ if (perm.type == "translate" &&
+ perm.capability == Services.perms.DENY_ACTION)
+ results.push(perm.principal);
+ }
+
+ return results;
+}
+
+function getInfoBar() {
+ let deferred = Promise.defer();
+ let infobar =
+ gBrowser.getNotificationBox().getNotificationWithValue("translation");
+
+ if (!infobar) {
+ deferred.resolve();
+ } else {
+ // Wait for all animations to finish
+ Promise.all(infobar.getAnimations().map(animation => animation.finished))
+ .then(() => deferred.resolve(infobar));
+ }
+
+ return deferred.promise;
+}
+
+function openPopup(aPopup) {
+ let deferred = Promise.defer();
+
+ aPopup.addEventListener("popupshown", function popupShown() {
+ aPopup.removeEventListener("popupshown", popupShown);
+ deferred.resolve();
+ });
+
+ aPopup.focus();
+ // One down event to open the popup.
+ EventUtils.synthesizeKey("VK_DOWN",
+ { altKey: !navigator.platform.includes("Mac") });
+
+ return deferred.promise;
+}
+
+function waitForWindowLoad(aWin) {
+ let deferred = Promise.defer();
+
+ aWin.addEventListener("load", function onload() {
+ aWin.removeEventListener("load", onload, true);
+ deferred.resolve();
+ }, true);
+
+ return deferred.promise;
+}
+
+
+var gTests = [
+
+{
+ desc: "clean exception lists at startup",
+ run: function checkNeverForLanguage() {
+ is(getLanguageExceptions().length, 0,
+ "we start with an empty list of languages to never translate");
+ is(getDomainExceptions().length, 0,
+ "we start with an empty list of sites to never translate");
+ }
+},
+
+{
+ desc: "never for language",
+ run: function* checkNeverForLanguage() {
+ // Show the infobar for example.com and fr.
+ Translation.documentStateReceived(gBrowser.selectedBrowser,
+ {state: Translation.STATE_OFFER,
+ originalShown: true,
+ detectedLanguage: "fr"});
+ let notif = yield getInfoBar();
+ ok(notif, "the infobar is visible");
+ let ui = gBrowser.selectedBrowser.translationUI;
+ let uri = gBrowser.selectedBrowser.currentURI;
+ ok(ui.shouldShowInfoBar(uri, "fr"),
+ "check shouldShowInfoBar initially returns true");
+
+ // Open the "options" drop down.
+ yield openPopup(notif._getAnonElt("options"));
+ ok(notif._getAnonElt("options").getAttribute("open"),
+ "the options menu is open");
+
+ // Check that the item is not disabled.
+ ok(!notif._getAnonElt("neverForLanguage").disabled,
+ "The 'Never translate <language>' item isn't disabled");
+
+ // Click the 'Never for French' item.
+ notif._getAnonElt("neverForLanguage").click();
+ notif = yield getInfoBar();
+ ok(!notif, "infobar hidden");
+
+ // Check this has been saved to the exceptions list.
+ let langs = getLanguageExceptions();
+ is(langs.length, 1, "one language in the exception list");
+ is(langs[0], "fr", "correct language in the exception list");
+ ok(!ui.shouldShowInfoBar(uri, "fr"),
+ "the infobar wouldn't be shown anymore");
+
+ // Reopen the infobar.
+ PopupNotifications.getNotification("translate").anchorElement.click();
+ notif = yield getInfoBar();
+ // Open the "options" drop down.
+ yield openPopup(notif._getAnonElt("options"));
+ ok(notif._getAnonElt("neverForLanguage").disabled,
+ "The 'Never translate French' item is disabled");
+
+ // Cleanup.
+ Services.prefs.setCharPref(kLanguagesPref, "");
+ notif.close();
+ }
+},
+
+{
+ desc: "never for site",
+ run: function* checkNeverForSite() {
+ // Show the infobar for example.com and fr.
+ Translation.documentStateReceived(gBrowser.selectedBrowser,
+ {state: Translation.STATE_OFFER,
+ originalShown: true,
+ detectedLanguage: "fr"});
+ let notif = yield getInfoBar();
+ ok(notif, "the infobar is visible");
+ let ui = gBrowser.selectedBrowser.translationUI;
+ let uri = gBrowser.selectedBrowser.currentURI;
+ ok(ui.shouldShowInfoBar(uri, "fr"),
+ "check shouldShowInfoBar initially returns true");
+
+ // Open the "options" drop down.
+ yield openPopup(notif._getAnonElt("options"));
+ ok(notif._getAnonElt("options").getAttribute("open"),
+ "the options menu is open");
+
+ // Check that the item is not disabled.
+ ok(!notif._getAnonElt("neverForSite").disabled,
+ "The 'Never translate site' item isn't disabled");
+
+ // Click the 'Never for French' item.
+ notif._getAnonElt("neverForSite").click();
+ notif = yield getInfoBar();
+ ok(!notif, "infobar hidden");
+
+ // Check this has been saved to the exceptions list.
+ let sites = getDomainExceptions();
+ is(sites.length, 1, "one site in the exception list");
+ is(sites[0].origin, "http://example.com", "correct site in the exception list");
+ ok(!ui.shouldShowInfoBar(uri, "fr"),
+ "the infobar wouldn't be shown anymore");
+
+ // Reopen the infobar.
+ PopupNotifications.getNotification("translate").anchorElement.click();
+ notif = yield getInfoBar();
+ // Open the "options" drop down.
+ yield openPopup(notif._getAnonElt("options"));
+ ok(notif._getAnonElt("neverForSite").disabled,
+ "The 'Never translate French' item is disabled");
+
+ // Cleanup.
+ Services.perms.remove(makeURI("http://example.com"), "translate");
+ notif.close();
+ }
+},
+
+{
+ desc: "language exception list",
+ run: function* checkLanguageExceptions() {
+ // Put 2 languages in the pref before opening the window to check
+ // the list is displayed on load.
+ Services.prefs.setCharPref(kLanguagesPref, "fr,de");
+
+ // Open the translation exceptions dialog.
+ let win = openDialog("chrome://browser/content/preferences/translation.xul",
+ "Browser:TranslationExceptions",
+ "", null);
+ yield waitForWindowLoad(win);
+
+ // Check that the list of language exceptions is loaded.
+ let getById = win.document.getElementById.bind(win.document);
+ let tree = getById("languagesTree");
+ let remove = getById("removeLanguage");
+ let removeAll = getById("removeAllLanguages");
+ is(tree.view.rowCount, 2, "The language exceptions list has 2 items");
+ ok(remove.disabled, "The 'Remove Language' button is disabled");
+ ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled");
+
+ // Select the first item.
+ tree.view.selection.select(0);
+ ok(!remove.disabled, "The 'Remove Language' button is enabled");
+
+ // Click the 'Remove' button.
+ remove.click();
+ is(tree.view.rowCount, 1, "The language exceptions now contains 1 item");
+ is(getLanguageExceptions().length, 1, "One exception in the pref");
+
+ // Clear the pref, and check the last item is removed from the display.
+ Services.prefs.setCharPref(kLanguagesPref, "");
+ is(tree.view.rowCount, 0, "The language exceptions list is empty");
+ ok(remove.disabled, "The 'Remove Language' button is disabled");
+ ok(removeAll.disabled, "The 'Remove All Languages' button is disabled");
+
+ // Add an item and check it appears.
+ Services.prefs.setCharPref(kLanguagesPref, "fr");
+ is(tree.view.rowCount, 1, "The language exceptions list has 1 item");
+ ok(remove.disabled, "The 'Remove Language' button is disabled");
+ ok(!removeAll.disabled, "The 'Remove All Languages' button is enabled");
+
+ // Click the 'Remove All' button.
+ removeAll.click();
+ is(tree.view.rowCount, 0, "The language exceptions list is empty");
+ ok(remove.disabled, "The 'Remove Language' button is disabled");
+ ok(removeAll.disabled, "The 'Remove All Languages' button is disabled");
+ is(Services.prefs.getCharPref(kLanguagesPref), "", "The pref is empty");
+
+ win.close();
+ }
+},
+
+{
+ desc: "domains exception list",
+ run: function* checkDomainExceptions() {
+ // Put 2 exceptions before opening the window to check the list is
+ // displayed on load.
+ let perms = Services.perms;
+ perms.add(makeURI("http://example.org"), "translate", perms.DENY_ACTION);
+ perms.add(makeURI("http://example.com"), "translate", perms.DENY_ACTION);
+
+ // Open the translation exceptions dialog.
+ let win = openDialog("chrome://browser/content/preferences/translation.xul",
+ "Browser:TranslationExceptions",
+ "", null);
+ yield waitForWindowLoad(win);
+
+ // Check that the list of language exceptions is loaded.
+ let getById = win.document.getElementById.bind(win.document);
+ let tree = getById("sitesTree");
+ let remove = getById("removeSite");
+ let removeAll = getById("removeAllSites");
+ is(tree.view.rowCount, 2, "The sites exceptions list has 2 items");
+ ok(remove.disabled, "The 'Remove Site' button is disabled");
+ ok(!removeAll.disabled, "The 'Remove All Sites' button is enabled");
+
+ // Select the first item.
+ tree.view.selection.select(0);
+ ok(!remove.disabled, "The 'Remove Site' button is enabled");
+
+ // Click the 'Remove' button.
+ remove.click();
+ is(tree.view.rowCount, 1, "The site exceptions now contains 1 item");
+ is(getDomainExceptions().length, 1, "One exception in the permissions");
+
+ // Clear the permissions, and check the last item is removed from the display.
+ perms.remove(makeURI("http://example.org"), "translate");
+ perms.remove(makeURI("http://example.com"), "translate");
+ is(tree.view.rowCount, 0, "The site exceptions list is empty");
+ ok(remove.disabled, "The 'Remove Site' button is disabled");
+ ok(removeAll.disabled, "The 'Remove All Site' button is disabled");
+
+ // Add an item and check it appears.
+ perms.add(makeURI("http://example.com"), "translate", perms.DENY_ACTION);
+ is(tree.view.rowCount, 1, "The site exceptions list has 1 item");
+ ok(remove.disabled, "The 'Remove Site' button is disabled");
+ ok(!removeAll.disabled, "The 'Remove All Sites' button is enabled");
+
+ // Click the 'Remove All' button.
+ removeAll.click();
+ is(tree.view.rowCount, 0, "The site exceptions list is empty");
+ ok(remove.disabled, "The 'Remove Site' button is disabled");
+ ok(removeAll.disabled, "The 'Remove All Sites' button is disabled");
+ is(getDomainExceptions().length, 0, "No exceptions in the permissions");
+
+ win.close();
+ }
+}
+
+];
diff --git a/browser/components/translation/test/browser_translation_infobar.js b/browser/components/translation/test/browser_translation_infobar.js
new file mode 100644
index 000000000..c16b3939c
--- /dev/null
+++ b/browser/components/translation/test/browser_translation_infobar.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// tests the translation infobar, using a fake 'Translation' implementation.
+
+var tmp = {};
+Cu.import("resource:///modules/translation/Translation.jsm", tmp);
+var {Translation} = tmp;
+
+const kShowUIPref = "browser.translation.ui.show";
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+var TranslationStub = {
+ translate: function(aFrom, aTo) {
+ this.state = Translation.STATE_TRANSLATING;
+ this.translatedFrom = aFrom;
+ this.translatedTo = aTo;
+ },
+
+ _reset: function() {
+ this.translatedFrom = "";
+ this.translatedTo = "";
+ },
+
+ failTranslation: function() {
+ this.state = Translation.STATE_ERROR;
+ this._reset();
+ },
+
+ finishTranslation: function() {
+ this.showTranslatedContent();
+ this.state = Translation.STATE_TRANSLATED;
+ this._reset();
+ }
+};
+
+function showTranslationUI(aDetectedLanguage) {
+ let browser = gBrowser.selectedBrowser;
+ Translation.documentStateReceived(browser, {state: Translation.STATE_OFFER,
+ originalShown: true,
+ detectedLanguage: aDetectedLanguage});
+ let ui = browser.translationUI;
+ for (let name of ["translate", "_reset", "failTranslation", "finishTranslation"])
+ ui[name] = TranslationStub[name];
+ return ui.notificationBox.getNotificationWithValue("translation");
+}
+
+function hasTranslationInfoBar() {
+ return !!gBrowser.getNotificationBox().getNotificationWithValue("translation");
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(kShowUIPref, true);
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", function onload() {
+ tab.linkedBrowser.removeEventListener("load", onload, true);
+ TranslationStub.browser = gBrowser.selectedBrowser;
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ Services.prefs.clearUserPref(kShowUIPref);
+ });
+ run_tests(() => {
+ finish();
+ });
+ }, true);
+
+ content.location = "data:text/plain,test page";
+}
+
+function checkURLBarIcon(aExpectTranslated = false) {
+ is(!PopupNotifications.getNotification("translate"), aExpectTranslated,
+ "translate icon " + (aExpectTranslated ? "not " : "") + "shown");
+ is(!!PopupNotifications.getNotification("translated"), aExpectTranslated,
+ "translated icon " + (aExpectTranslated ? "" : "not ") + "shown");
+}
+
+function run_tests(aFinishCallback) {
+ info("Show an info bar saying the current page is in French");
+ let notif = showTranslationUI("fr");
+ is(notif.state, Translation.STATE_OFFER, "the infobar is offering translation");
+ is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
+ checkURLBarIcon();
+
+ info("Click the 'Translate' button");
+ notif._getAnonElt("translate").click();
+ is(notif.state, Translation.STATE_TRANSLATING, "the infobar is in the translating state");
+ ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
+ is(notif.translation.translatedFrom, "fr", "from language correct");
+ is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
+ checkURLBarIcon();
+
+ info("Make the translation fail and check we are in the error state.");
+ notif.translation.failTranslation();
+ is(notif.state, Translation.STATE_ERROR, "infobar in the error state");
+ checkURLBarIcon();
+
+ info("Click the try again button");
+ notif._getAnonElt("tryAgain").click();
+ is(notif.state, Translation.STATE_TRANSLATING, "infobar in the translating state");
+ ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
+ is(notif.translation.translatedFrom, "fr", "from language correct");
+ is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "from language correct");
+ checkURLBarIcon();
+
+ info("Make the translation succeed and check we are in the 'translated' state.");
+ notif.translation.finishTranslation();
+ is(notif.state, Translation.STATE_TRANSLATED, "infobar in the translated state");
+ checkURLBarIcon(true);
+
+ info("Test 'Show original' / 'Show Translation' buttons.");
+ // First check 'Show Original' is visible and 'Show Translation' is hidden.
+ ok(!notif._getAnonElt("showOriginal").hidden, "'Show Original' button visible");
+ ok(notif._getAnonElt("showTranslation").hidden, "'Show Translation' button hidden");
+ // Click the button.
+ notif._getAnonElt("showOriginal").click();
+ // Check that the url bar icon shows the original content is displayed.
+ checkURLBarIcon();
+ // And the 'Show Translation' button is now visible.
+ ok(notif._getAnonElt("showOriginal").hidden, "'Show Original' button hidden");
+ ok(!notif._getAnonElt("showTranslation").hidden, "'Show Translation' button visible");
+ // Click the 'Show Translation' button
+ notif._getAnonElt("showTranslation").click();
+ // Check that the url bar icon shows the page is translated.
+ checkURLBarIcon(true);
+ // Check that the 'Show Original' button is visible again.
+ ok(!notif._getAnonElt("showOriginal").hidden, "'Show Original' button visible");
+ ok(notif._getAnonElt("showTranslation").hidden, "'Show Translation' button hidden");
+
+ info("Check that changing the source language causes a re-translation");
+ let from = notif._getAnonElt("fromLanguage");
+ from.value = "es";
+ from.doCommand();
+ is(notif.state, Translation.STATE_TRANSLATING, "infobar in the translating state");
+ ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
+ is(notif.translation.translatedFrom, "es", "from language correct");
+ is(notif.translation.translatedTo, Translation.defaultTargetLanguage, "to language correct");
+ // We want to show the 'translated' icon while re-translating,
+ // because we are still displaying the previous translation.
+ checkURLBarIcon(true);
+ notif.translation.finishTranslation();
+ checkURLBarIcon(true);
+
+ info("Check that changing the target language causes a re-translation");
+ let to = notif._getAnonElt("toLanguage");
+ to.value = "pl";
+ to.doCommand();
+ is(notif.state, Translation.STATE_TRANSLATING, "infobar in the translating state");
+ ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
+ is(notif.translation.translatedFrom, "es", "from language correct");
+ is(notif.translation.translatedTo, "pl", "to language correct");
+ checkURLBarIcon(true);
+ notif.translation.finishTranslation();
+ checkURLBarIcon(true);
+
+ // Cleanup.
+ notif.close();
+
+ info("Reopen the info bar to check that it's possible to override the detected language.");
+ notif = showTranslationUI("fr");
+ is(notif.state, Translation.STATE_OFFER, "the infobar is offering translation");
+ is(notif._getAnonElt("detectedLanguage").value, "fr", "The detected language is displayed");
+ // Change the language and click 'Translate'
+ notif._getAnonElt("detectedLanguage").value = "ja";
+ notif._getAnonElt("translate").click();
+ is(notif.state, Translation.STATE_TRANSLATING, "the infobar is in the translating state");
+ ok(!!notif.translation.translatedFrom, "Translation.translate has been called");
+ is(notif.translation.translatedFrom, "ja", "from language correct");
+ notif.close();
+
+ info("Reopen to check the 'Not Now' button closes the notification.");
+ notif = showTranslationUI("fr");
+ is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
+ notif._getAnonElt("notNow").click();
+ is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking 'not now'");
+
+ info("Reopen to check the url bar icon closes the notification.");
+ notif = showTranslationUI("fr");
+ is(hasTranslationInfoBar(), true, "there's a 'translate' notification");
+ PopupNotifications.getNotification("translate").anchorElement.click();
+ is(hasTranslationInfoBar(), false, "no 'translate' notification after clicking the url bar icon");
+
+ info("Check that clicking the url bar icon reopens the info bar");
+ checkURLBarIcon();
+ // Clicking the anchor element causes a 'showing' event to be sent
+ // asynchronously to our callback that will then show the infobar.
+ PopupNotifications.getNotification("translate").anchorElement.click();
+ waitForCondition(hasTranslationInfoBar, () => {
+ ok(hasTranslationInfoBar(), "there's a 'translate' notification");
+ aFinishCallback();
+ }, "timeout waiting for the info bar to reappear");
+}
diff --git a/browser/components/translation/test/browser_translation_telemetry.js b/browser/components/translation/test/browser_translation_telemetry.js
new file mode 100644
index 000000000..e60bc17ef
--- /dev/null
+++ b/browser/components/translation/test/browser_translation_telemetry.js
@@ -0,0 +1,300 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var tmp = {};
+Cu.import("resource:///modules/translation/Translation.jsm", tmp);
+var {Translation, TranslationTelemetry} = tmp;
+const Telemetry = Services.telemetry;
+
+var MetricsChecker = {
+ HISTOGRAMS: {
+ OPPORTUNITIES : Services.telemetry.getHistogramById("TRANSLATION_OPPORTUNITIES"),
+ OPPORTUNITIES_BY_LANG : Services.telemetry.getKeyedHistogramById("TRANSLATION_OPPORTUNITIES_BY_LANGUAGE"),
+ PAGES : Services.telemetry.getHistogramById("TRANSLATED_PAGES"),
+ PAGES_BY_LANG : Services.telemetry.getKeyedHistogramById("TRANSLATED_PAGES_BY_LANGUAGE"),
+ CHARACTERS : Services.telemetry.getHistogramById("TRANSLATED_CHARACTERS"),
+ DENIED : Services.telemetry.getHistogramById("DENIED_TRANSLATION_OFFERS"),
+ AUTO_REJECTED : Services.telemetry.getHistogramById("AUTO_REJECTED_TRANSLATION_OFFERS"),
+ SHOW_ORIGINAL : Services.telemetry.getHistogramById("REQUESTS_OF_ORIGINAL_CONTENT"),
+ TARGET_CHANGES : Services.telemetry.getHistogramById("CHANGES_OF_TARGET_LANGUAGE"),
+ DETECTION_CHANGES : Services.telemetry.getHistogramById("CHANGES_OF_DETECTED_LANGUAGE"),
+ SHOW_UI : Services.telemetry.getHistogramById("SHOULD_TRANSLATION_UI_APPEAR"),
+ DETECT_LANG : Services.telemetry.getHistogramById("SHOULD_AUTO_DETECT_LANGUAGE"),
+ },
+
+ reset: function() {
+ for (let i of Object.keys(this.HISTOGRAMS)) {
+ this.HISTOGRAMS[i].clear();
+ }
+ this.updateMetrics();
+ },
+
+ updateMetrics: function () {
+ this._metrics = {
+ opportunitiesCount: this.HISTOGRAMS.OPPORTUNITIES.snapshot().sum || 0,
+ pageCount: this.HISTOGRAMS.PAGES.snapshot().sum || 0,
+ charCount: this.HISTOGRAMS.CHARACTERS.snapshot().sum || 0,
+ deniedOffers: this.HISTOGRAMS.DENIED.snapshot().sum || 0,
+ autoRejectedOffers: this.HISTOGRAMS.AUTO_REJECTED.snapshot().sum || 0,
+ showOriginal: this.HISTOGRAMS.SHOW_ORIGINAL.snapshot().sum || 0,
+ detectedLanguageChangedBefore: this.HISTOGRAMS.DETECTION_CHANGES.snapshot().counts[1] || 0,
+ detectedLanguageChangeAfter: this.HISTOGRAMS.DETECTION_CHANGES.snapshot().counts[0] || 0,
+ targetLanguageChanged: this.HISTOGRAMS.TARGET_CHANGES.snapshot().sum || 0,
+ showUI: this.HISTOGRAMS.SHOW_UI.snapshot().sum || 0,
+ detectLang: this.HISTOGRAMS.DETECT_LANG.snapshot().sum || 0,
+ // Metrics for Keyed histograms are estimated below.
+ opportunitiesCountByLang: {},
+ pageCountByLang: {}
+ };
+
+ let opportunities = this.HISTOGRAMS.OPPORTUNITIES_BY_LANG.snapshot();
+ let pages = this.HISTOGRAMS.PAGES_BY_LANG.snapshot();
+ for (let source of Translation.supportedSourceLanguages) {
+ this._metrics.opportunitiesCountByLang[source] = opportunities[source] ?
+ opportunities[source].sum : 0;
+ for (let target of Translation.supportedTargetLanguages) {
+ if (source === target) continue;
+ let key = source + " -> " + target;
+ this._metrics.pageCountByLang[key] = pages[key] ? pages[key].sum : 0;
+ }
+ }
+ },
+
+ /**
+ * A recurrent loop for making assertions about collected metrics.
+ */
+ _assertionLoop: function (prevMetrics, metrics, additions) {
+ for (let metric of Object.keys(additions)) {
+ let addition = additions[metric];
+ // Allows nesting metrics. Useful for keyed histograms.
+ if (typeof addition === 'object') {
+ this._assertionLoop(prevMetrics[metric], metrics[metric], addition);
+ continue;
+ }
+ Assert.equal(prevMetrics[metric] + addition, metrics[metric]);
+ }
+ },
+
+ checkAdditions: function (additions) {
+ let prevMetrics = this._metrics;
+ this.updateMetrics();
+ this._assertionLoop(prevMetrics, this._metrics, additions);
+ }
+
+};
+
+function getInfobarElement(browser, anonid) {
+ let notif = browser.translationUI
+ .notificationBox.getNotificationWithValue("translation");
+ return notif._getAnonElt(anonid);
+}
+
+var offerTranslationFor = Task.async(function*(text, from) {
+ // Create some content to translate.
+ const dataUrl = "data:text/html;charset=utf-8," + text;
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, dataUrl);
+
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ // Send a translation offer.
+ Translation.documentStateReceived(browser, {state: Translation.STATE_OFFER,
+ originalShown: true,
+ detectedLanguage: from});
+
+ return tab;
+});
+
+var acceptTranslationOffer = Task.async(function*(tab) {
+ let browser = tab.linkedBrowser;
+ getInfobarElement(browser, "translate").doCommand();
+ yield waitForMessage(browser, "Translation:Finished");
+});
+
+var translate = Task.async(function*(text, from, closeTab = true) {
+ let tab = yield offerTranslationFor(text, from);
+ yield acceptTranslationOffer(tab);
+ if (closeTab) {
+ gBrowser.removeTab(tab);
+ return null;
+ }
+ return tab;
+});
+
+function waitForMessage({messageManager}, name) {
+ return new Promise(resolve => {
+ messageManager.addMessageListener(name, function onMessage() {
+ messageManager.removeMessageListener(name, onMessage);
+ resolve();
+ });
+ });
+}
+
+function simulateUserSelectInMenulist(menulist, value) {
+ menulist.value = value;
+ menulist.doCommand();
+}
+
+add_task(function* setup() {
+ const setupPrefs = prefs => {
+ let prefsBackup = {};
+ for (let p of prefs) {
+ prefsBackup[p] = Services.prefs.setBoolPref;
+ Services.prefs.setBoolPref(p, true);
+ }
+ return prefsBackup;
+ };
+
+ const restorePrefs = (prefs, backup) => {
+ for (let p of prefs) {
+ Services.prefs.setBoolPref(p, backup[p]);
+ }
+ };
+
+ const prefs = [
+ "toolkit.telemetry.enabled",
+ "browser.translation.detectLanguage",
+ "browser.translation.ui.show"
+ ];
+
+ let prefsBackup = setupPrefs(prefs);
+
+ let oldCanRecord = Telemetry.canRecordExtended;
+ Telemetry.canRecordExtended = true;
+
+ registerCleanupFunction(() => {
+ restorePrefs(prefs, prefsBackup);
+ Telemetry.canRecordExtended = oldCanRecord;
+ });
+
+ // Reset histogram metrics.
+ MetricsChecker.reset();
+});
+
+add_task(function* test_telemetry() {
+ // Translate a page.
+ yield translate("<h1>Привет, мир!</h1>", "ru");
+
+ // Translate another page.
+ yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de");
+ yield MetricsChecker.checkAdditions({
+ opportunitiesCount: 2,
+ opportunitiesCountByLang: { "ru" : 1, "de" : 1 },
+ pageCount: 1,
+ pageCountByLang: { "de -> en" : 1 },
+ charCount: 21,
+ deniedOffers: 0
+ });
+});
+
+add_task(function* test_deny_translation_metric() {
+ function* offerAndDeny(elementAnonid) {
+ let tab = yield offerTranslationFor("<h1>Hallo Welt!</h1>", "de", "en");
+ getInfobarElement(tab.linkedBrowser, elementAnonid).doCommand();
+ yield MetricsChecker.checkAdditions({ deniedOffers: 1 });
+ gBrowser.removeTab(tab);
+ }
+
+ yield offerAndDeny("notNow");
+ yield offerAndDeny("neverForSite");
+ yield offerAndDeny("neverForLanguage");
+ yield offerAndDeny("closeButton");
+
+ // Test that the close button doesn't record a denied translation if
+ // the infobar is not in its "offer" state.
+ let tab = yield translate("<h1>Hallo Welt!</h1>", "de", false);
+ yield MetricsChecker.checkAdditions({ deniedOffers: 0 });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_show_original() {
+ let tab =
+ yield translate("<h1>Hallo Welt!</h1><h1>Bratwurst!</h1>", "de", false);
+ yield MetricsChecker.checkAdditions({ pageCount: 1, showOriginal: 0 });
+ getInfobarElement(tab.linkedBrowser, "showOriginal").doCommand();
+ yield MetricsChecker.checkAdditions({ pageCount: 0, showOriginal: 1 });
+ gBrowser.removeTab(tab);
+});
+
+add_task(function* test_language_change() {
+ // This is run 4 times, the total additions are checked afterwards.
+ for (let i of Array(4)) { // eslint-disable-line no-unused-vars
+ let tab = yield offerTranslationFor("<h1>Hallo Welt!</h1>", "fr");
+ let browser = tab.linkedBrowser;
+ // In the offer state, translation is executed by the Translate button,
+ // so we expect just a single recoding.
+ let detectedLangMenulist = getInfobarElement(browser, "detectedLanguage");
+ simulateUserSelectInMenulist(detectedLangMenulist, "de");
+ simulateUserSelectInMenulist(detectedLangMenulist, "it");
+ simulateUserSelectInMenulist(detectedLangMenulist, "de");
+ yield acceptTranslationOffer(tab);
+
+ // In the translated state, a change in the form or to menulists
+ // triggers re-translation right away.
+ let fromLangMenulist = getInfobarElement(browser, "fromLanguage");
+ simulateUserSelectInMenulist(fromLangMenulist, "it");
+ simulateUserSelectInMenulist(fromLangMenulist, "de");
+
+ // Selecting the same item shouldn't count.
+ simulateUserSelectInMenulist(fromLangMenulist, "de");
+
+ let toLangMenulist = getInfobarElement(browser, "toLanguage");
+ simulateUserSelectInMenulist(toLangMenulist, "fr");
+ simulateUserSelectInMenulist(toLangMenulist, "en");
+ simulateUserSelectInMenulist(toLangMenulist, "it");
+
+ // Selecting the same item shouldn't count.
+ simulateUserSelectInMenulist(toLangMenulist, "it");
+
+ // Setting the target language to the source language is a no-op,
+ // so it shouldn't count.
+ simulateUserSelectInMenulist(toLangMenulist, "de");
+
+ gBrowser.removeTab(tab);
+ }
+ yield MetricsChecker.checkAdditions({
+ detectedLanguageChangedBefore: 4,
+ detectedLanguageChangeAfter: 8,
+ targetLanguageChanged: 12
+ });
+});
+
+add_task(function* test_never_offer_translation() {
+ Services.prefs.setCharPref("browser.translation.neverForLanguages", "fr");
+
+ let tab = yield offerTranslationFor("<h1>Hallo Welt!</h1>", "fr");
+
+ yield MetricsChecker.checkAdditions({
+ autoRejectedOffers: 1,
+ });
+
+ gBrowser.removeTab(tab);
+ Services.prefs.clearUserPref("browser.translation.neverForLanguages");
+});
+
+add_task(function* test_translation_preferences() {
+
+ let preferenceChecks = {
+ "browser.translation.ui.show" : [
+ {value: false, expected: {showUI: 0}},
+ {value: true, expected: {showUI: 1}}
+ ],
+ "browser.translation.detectLanguage" : [
+ {value: false, expected: {detectLang: 0}},
+ {value: true, expected: {detectLang: 1}}
+ ],
+ };
+
+ for (let preference of Object.keys(preferenceChecks)) {
+ for (let check of preferenceChecks[preference]) {
+ MetricsChecker.reset();
+ Services.prefs.setBoolPref(preference, check.value);
+ // Preference metrics are collected once when the provider is initialized.
+ TranslationTelemetry.init();
+ yield MetricsChecker.checkAdditions(check.expected);
+ }
+ Services.prefs.clearUserPref(preference);
+ }
+
+});
diff --git a/browser/components/translation/test/browser_translation_yandex.js b/browser/components/translation/test/browser_translation_yandex.js
new file mode 100644
index 000000000..6e0af18e6
--- /dev/null
+++ b/browser/components/translation/test/browser_translation_yandex.js
@@ -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/. */
+
+// Test the Yandex Translator client against a mock Yandex service, yandex.sjs.
+
+"use strict";
+
+const kEnginePref = "browser.translation.engine";
+const kApiKeyPref = "browser.translation.yandex.apiKeyOverride";
+const kShowUIPref = "browser.translation.ui.show";
+
+const {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+const {Translation} = Cu.import("resource:///modules/translation/Translation.jsm", {});
+
+add_task(function* setup() {
+ Services.prefs.setCharPref(kEnginePref, "yandex");
+ Services.prefs.setCharPref(kApiKeyPref, "yandexValidKey");
+ Services.prefs.setBoolPref(kShowUIPref, true);
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(kEnginePref);
+ Services.prefs.clearUserPref(kApiKeyPref);
+ Services.prefs.clearUserPref(kShowUIPref);
+ });
+});
+
+/**
+ * Ensure that the translation engine behaives as expected when translating
+ * a sample page.
+ */
+add_task(function* test_yandex_translation() {
+
+ // Loading the fixture page.
+ let url = constructFixtureURL("bug1022725-fr.html");
+ let tab = yield promiseTestPageLoad(url);
+
+ // Translating the contents of the loaded tab.
+ gBrowser.selectedTab = tab;
+ let browser = tab.linkedBrowser;
+
+ yield ContentTask.spawn(browser, null, function*() {
+ Cu.import("resource:///modules/translation/TranslationDocument.jsm");
+ Cu.import("resource:///modules/translation/YandexTranslator.jsm");
+
+ let client = new YandexTranslator(
+ new TranslationDocument(content.document), "fr", "en");
+ let result = yield client.translate();
+
+ Assert.ok(result, "There should be a result.");
+ });
+
+ gBrowser.removeTab(tab);
+});
+
+/**
+ * Ensure that Yandex.Translate is propertly attributed.
+ */
+add_task(function* test_yandex_attribution() {
+ // Loading the fixture page.
+ let url = constructFixtureURL("bug1022725-fr.html");
+ let tab = yield promiseTestPageLoad(url);
+
+ info("Show an info bar saying the current page is in French");
+ let notif = showTranslationUI(tab, "fr");
+ let attribution = notif._getAnonElt("translationEngine").selectedIndex;
+ Assert.equal(attribution, 1, "Yandex attribution should be shown.");
+
+ gBrowser.removeTab(tab);
+});
+
+
+add_task(function* test_preference_attribution() {
+
+ let prefUrl = "about:preferences#content";
+ let tab = yield promiseTestPageLoad(prefUrl);
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ let win = browser.contentWindow;
+ let bingAttribution = win.document.getElementById("bingAttribution");
+ ok(bingAttribution, "Bing attribution should exist.");
+ ok(bingAttribution.hidden, "Bing attribution should be hidden.");
+
+ gBrowser.removeTab(tab);
+
+});
+
+/**
+ * A helper function for constructing a URL to a page stored in the
+ * local fixture folder.
+ *
+ * @param filename Name of a fixture file.
+ */
+function constructFixtureURL(filename) {
+ // Deduce the Mochitest server address in use from a pref that was pre-processed.
+ let server = Services.prefs.getCharPref("browser.translation.yandex.translateURLOverride")
+ .replace("http://", "");
+ server = server.substr(0, server.indexOf("/"));
+ let url = "http://" + server +
+ "/browser/browser/components/translation/test/fixtures/" + filename;
+ return url;
+}
+
+/**
+ * A helper function to open a new tab and wait for its content to load.
+ *
+ * @param String url A URL to be loaded in the new tab.
+ */
+function promiseTestPageLoad(url) {
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ let browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function listener() {
+ if (browser.currentURI.spec == "about:blank")
+ return;
+ info("Page loaded: " + browser.currentURI.spec);
+ browser.removeEventListener("load", listener, true);
+ deferred.resolve(tab);
+ }, true);
+ return deferred.promise;
+}
+
+function showTranslationUI(tab, aDetectedLanguage) {
+ let browser = gBrowser.selectedBrowser;
+ Translation.documentStateReceived(browser, {state: Translation.STATE_OFFER,
+ originalShown: true,
+ detectedLanguage: aDetectedLanguage});
+ let ui = browser.translationUI;
+ return ui.notificationBox.getNotificationWithValue("translation");
+}
diff --git a/browser/components/translation/test/fixtures/bug1022725-fr.html b/browser/components/translation/test/fixtures/bug1022725-fr.html
new file mode 100644
index 000000000..f30edf52e
--- /dev/null
+++ b/browser/components/translation/test/fixtures/bug1022725-fr.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="fr">
+ <head>
+ <!--
+ - Text retrieved from http://fr.wikipedia.org/wiki/Coupe_du_monde_de_football_de_2014
+ - at 06/13/2014, Creative Commons Attribution-ShareAlike License.
+ -->
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>test</title>
+ </head>
+ <body>
+ <h1>Coupe du monde de football de 2014</h1>
+ <div>La Coupe du monde de football de 2014 est la 20e édition de la Coupe du monde de football, compétition organisée par la FIFA et qui réunit les trente-deux meilleures sélections nationales. Sa phase finale a lieu à l'été 2014 au Brésil. Avec le pays organisateur, toutes les équipes championnes du monde depuis 1930 (Uruguay, Italie, Allemagne, Angleterre, Argentine, France et Espagne) se sont qualifiées pour cette compétition. Elle est aussi la première compétition internationale de la Bosnie-Herzégovine.</div>
+ </body>
+</html>
diff --git a/browser/components/translation/test/fixtures/result-da39a3ee5e.txt b/browser/components/translation/test/fixtures/result-da39a3ee5e.txt
new file mode 100644
index 000000000..d2d14c788
--- /dev/null
+++ b/browser/components/translation/test/fixtures/result-da39a3ee5e.txt
@@ -0,0 +1,22 @@
+<ArrayOfTranslateArrayResponse xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <TranslateArrayResponse>
+ <From>fr</From>
+ <OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+ <a:int>34</a:int>
+ </OriginalTextSentenceLengths>
+ <TranslatedText>Football's 2014 World Cup</TranslatedText>
+ <TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+ <a:int>25</a:int>
+ </TranslatedTextSentenceLengths>
+ </TranslateArrayResponse>
+ <TranslateArrayResponse>
+ <From>fr</From>
+ <OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+ <a:int>508</a:int>
+ </OriginalTextSentenceLengths>
+ <TranslatedText>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus diam sem, porttitor eget neque sit amet, ultricies posuere metus. Cras placerat rutrum risus, nec dignissim magna dictum vitae. Fusce eleifend fermentum lacinia. Nulla sagittis cursus nibh. Praesent adipiscing, elit at pulvinar dapibus, neque massa tincidunt sapien, eu consectetur lectus metus sit amet odio. Proin blandit consequat porttitor. Pellentesque vehicula justo sed luctus vestibulum. Donec metus.</TranslatedText>
+ <TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+ <a:int>475</a:int>
+ </TranslatedTextSentenceLengths>
+ </TranslateArrayResponse>
+</ArrayOfTranslateArrayResponse>
diff --git a/browser/components/translation/test/fixtures/result-yandex-d448894848.json b/browser/components/translation/test/fixtures/result-yandex-d448894848.json
new file mode 100644
index 000000000..de2f5650e
--- /dev/null
+++ b/browser/components/translation/test/fixtures/result-yandex-d448894848.json
@@ -0,0 +1 @@
+{"code":200,"lang":"fr-en","text":["Football's 2014 World Cup","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus diam sem, porttitor eget neque sit amet, ultricies posuere metus. Cras placerat rutrum risus, nec dignissim magna dictum vitae. Fusce eleifend fermentum lacinia. Nulla sagittis cursus nibh. Praesent adipiscing, elit at pulvinar dapibus, neque massa tincidunt sapien, eu consectetur lectus metus sit amet odio. Proin blandit consequat porttitor. Pellentesque vehicula justo sed luctus vestibulum. Donec metus."]}
diff --git a/browser/components/translation/test/unit/.eslintrc.js b/browser/components/translation/test/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/components/translation/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/components/translation/test/unit/test_cld2.js b/browser/components/translation/test/unit/test_cld2.js
new file mode 100644
index 000000000..9ebc4d766
--- /dev/null
+++ b/browser/components/translation/test/unit/test_cld2.js
@@ -0,0 +1,463 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// 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.
+
+//
+// Author: dsites@google.com (Dick Sites)
+//
+// Unit test compact language detector, CLD2
+//
+
+// Test strings.
+const kTeststr_en =
+ "confiscation of goods is assigned as the penalty part most of the courts " +
+ "consist of members and when it is necessary to bring public cases before a " +
+ "jury of members two courts combine for the purpose the most important cases " +
+ "of all are brought jurors or";
+
+const kTeststr_aa_Latn = " nagay tanito nagay tanto nagayna naharsi nahrur nake nala nammay nammay haytu nanu narig ne ni num numu o obare obe obe obisse oggole ogli olloyta ongorowe orbise othoga r rabe rade ra e rage rakub rasitte rasu reyta rog ruddi ruga s sa al bada sa ala";
+const kTeststr_ab_Cyrl = " а зуа абзиара дақәшәоит ан лыбзиабара ахә амаӡам ауаҩы игәы иҭоу ихы иҿы ианубаалоит аҧҳәыс ҧшӡа ахацәа лышьҭоуп аҿаасҭа лара дрышьҭоуп";
+const kTeststr_af_Latn = " aam skukuza die naam beteken hy wat skoonvee of hy wat alles onderstebo keer wysig bosveldkampe boskampe is kleiner afgeleë ruskampe wat oor min fasiliteite beskik daar is geen restaurante of winkels nie en slegs oornagbesoekers word toegelaat bateleur";
+const kTeststr_ak_Latn = "Wɔwoo Hilla Limann Mumu-Ɔpɛnimba 12 afe 1934. Wɔwoo no wɔ Gwollu wɔ Sisala Mantaw mu Nna ne maame yɛ Mma Hayawah. Ne papa so nna ɔyɛ Babini Yomu. Ɔwarr Fulera Limann ? Ne mba yɛ esuon-- Lariba Montia [wɔwoo no Limann]; Baba Limann; Sibi Andan [wɔwoo no Limann]; Lida Limann; Danni Limann; Zilla Limann na Salma Limann. Ɔtenaa ase kɔpemm Sanda-Kwakwa da ɛtɔ so 23 wɔ afe 1998 wɔ ?.";
+const kTeststr_am_Ethi = " ለመጠይቅ ወደ እስክንድርያ ላኩዋቸውና የእስክንድርያ ጳጳስ አቴናስዮስ ፍሬምንጦስን እራሳቸውን ሾመው ልከዋል ከዚያ እስከ ዓ ም ድረስ የኢትዮጵያ አቡነ";
+const kTeststr_ar_Arab = "احتيالية بيع أي حساب";
+const kTeststr_as_Beng = "অঞ্চল নতুন সদস্যবৃন্দ সকলোৱে ভৰ্তি হব পাৰে মুল পৃষ্ঠা জন লেখক গুগ ল দল সাৰাংশ ই পত্ৰ টা বাৰ্তা এজন";
+const kTeststr_ay_Latn = " aru wijar aru ispañula ukaran aru witanam aru kurti aru kalis aru warani aru malta aru yatiyawi niya jakitanaka isluwiñ aru lmir phuran aru masirunan aru purtukal aru kruwat aru jakira urtu aru inklisa pirsan aru suyku aru malay aru jisk aptayma thaya";
+const kTeststr_az_Arab = " آذربایجان دا انسان حاقلاری ائوی آچیلاجاق ب م ت ائلچيسي برمه موخاليفتي نين ليدئري ايله گؤروشه بيليب ترس شوونيسم فارس از آزادي ملتهاي تورکمن";
+const kTeststr_az_Latn = " a az qalıb breyn rinq intellektual oyunu üzrə yarışın zona mərhələləri keçirilib miq un qalıqlarının dənizdən çıxarılması davam edir məhəmməd peyğəmbərin karikaturalarını çap edən qəzetin baş redaktoru iş otağında ölüb";
+const kTeststr_ba_Cyrl = " арналђан бындай ђилми эш тіркињлњ тњјге тапєыр нњшер ителњ ғинуар бєхет именлектє етешлектє ауыл ўќмерџєре хеџмєт юлын ћайлаѓанда";
+const kTeststr_be_Cyrl = " а друкаваць іх не было тэхнічна магчыма бліжэй за вільню тым самым часам нямецкае кіраўніцтва прапаноўвала апроч ўвядзення лацінкі яе";
+const kTeststr_bg_Cyrl = " а дума попада в състояние на изпитание ключовите думи с предсказана малко под то изискване на страниците за търсене в";
+// From 10% testing part of new lang=bh scrape
+const kTeststr_bh_Deva = "काल में उनका हमला से बचे खाती एहिजा भाग के अइले आ भोजपुर नाम से नगर बसवले. एकरा बारे में विस्तार से जानकारी नीचे दीहल गइल बा. बाकिर आश्चर्यजनक रूप से मालवा के राजा भोज के बिहार आवे आ भोजपुर नगर बसावे आ चाहे भोजपुरी के साथे उनकर कवनो संबंध होखे के कवनो जानकारी भोपाल के भोज संस्थान आ चाहे मध्य प्रदेश के इतिहासकार लोगन के तनिको नइखे. हालांकि ऊ सब लोग एह बात के मानत बा कि एकरा बारे में अबहीं तकले मूर्ति बनवइलें. राजा भोज के जवना जगहा पऽ वाग्देवी के दर्शन भइल रहे, ओही स्थान पऽ एह मूर्ति के स्थापना कइल गइल. अब अगर एह मंदिर के एह शिलालेख के तस्वीर (पृष्ठ संख्या 33 पऽ प्रकाशित) रउआ धेयान से देखीं तऽ एकरा पऽ कैथी लिपि में -सीताराम- लिखल साफ लउकत बा. कैथी भोजपुरी के बहुत प्रचलित लिपि रहल बिया. एकरा बारे में कवनो शंका संदेह बिहार-यूपी के जानकार लोगन में नइखे. एल. एस. एस. वो माले के लिखल पढ़ीं ";
+
+const kTeststr_bi_Latn = " king wantaem nomo hem i sakem setan mo ol rabis enjel blong hem oli aot long heven oli kamdaon long wol taswe ol samting oli kam nogud olgeta long wol ya stat long revelesen ol faet kakae i sot ol sik mo fasin blong brekem loa oli kam antap olgeta samting";
+const kTeststr_blu_Latn = " Kuv hlub koj txawm lub ntuj yuav si ntshi nphaus los kuv tsis ua siab nkaug txawm ntiab teb yuav si ntshi nphaus los kuv tseem ua lon tsaug vim kuv hlub koj tag lub siab";
+const kTeststr_blu_Latn2 = "Kuv hnov Txhiaj Xeeb Vaj, co-owner of Hmong Village Shopping Center, hais ua hnub ua hmo tias kom Hmoob yuav tsum txhawb Hmoob thiab listed cov mini-shops uas nyob rau hauv nws lub MALL txhua txhua kom sawv daws mus txhawb, tiam sis uas cas zaum twg twb pom nws mus kav kiav hauv taj laj qhabmeem (Sun Foods) xwb tiag. Nag hmo kuv pom nws mus shopping nrog nws poj niam hauv Sun Foods. Thaum tawm mus txog nraum parking lot kuv thiaj txhob txwm mus ze ze seb ua li nws mus yuav dab tsi tiag, thiab seb tej uas nws yuav ntawd puas muaj nyob ntawm tej kiab khw Hmoob. Surprised!!! Vuag.... txhua yam nws yuav hauv Sun Foods peb Hmoob cov khw yeej muaj tag nrho. Peb niaj hnub nqua hu kom Hmoob yuav tsum pab Hmoob yog pab li no lod?";
+// From 10% testing part of new lang=bn scrape
+const kTeststr_bn_Beng = "গ্যালারির ৩৮ বছর পূর্তিতে মূল্যছাড় অর্থনীতি বিএনপির ওয়াক আউট তপন চৌধুরী হারবাল অ্যাসোসিয়েশনের সভাপতি আন্তর্জাতিক পরামর্শক বোর্ড দিয়ে শরিয়াহ্ ইনন্ডেক্স করবে সিএসই মালিকপক্ষের কান্না, শ্রমিকের অনিশ্চয়তা মতিঝিলে সমাবেশ নিষিদ্ধ: এফবিসিসিআইয়ের ধন্যবাদ বিনোদন বিশেষ প্রতিবেদন বাংলালিংকের গ্র্যান্ডমাস্টার সিজন-৩ ব্রাজিলে বিশ্বকাপ ফুটবল আয়োজনবিরোধী বিক্ষোভ দেশের নিরাপত্তার চেয়ে অনেক বেশি সচেতন । প্রার্থীদের দক্ষতা ও যোগ্যতার পাশাপাশি তারা জাতীয় ইস্যুগুলোতে প্রাধান্য দিয়েছেন । ” পাঁচটি সিটিতে ২০ লাখ ভোটারদের দিয়ে জাতীয় নির্বাচনে ৮ কোটি ভোটারদের সঙ্গে তুলনা করা যাবে কি একজন দর্শকের এমন প্রশ্নে জবাবে আব্দুল্লাহ আল নোমান বলেন , “ এই পাঁচটি সিটি কর্পোরেশন নির্বাচন দেশের পাঁচটি বড় বিভাগের প্রতিনিধিত্ব করছে । এছাড়া এখানকার ভোটার রা সবাই সচেতন । তারা";
+
+// From 10% testing part of new lang=bo scrape
+const kTeststr_bo_Tibt = " ་གྱིས་ཁ་ཆེའི་ཕྱག་འཚལ་ཁང་ཞིག་བཤིག་སྲིད་པ། ཡར་ཀླུང་གཙང་པོར་ཆ ུ་མཛོང་བརྒྱག་རྒྱུའི་ལས་འཆར་ལ་རྒྱ་གར་གྱི་སེམས་ཚབས། རྒྱ་གརགྱི་མཚོ་འོག་དམག་གྲུར་སྦར་གས་བྱུང་བ། པ་ཀི་སི་ཏན་གྱིས་རྒྱ་གར་ལ་མི་སེར་བསད་པའི་སྐྱོན་འཛུགས་བྱས་པ། རྩོམ་ཡིག་མང་བ། འབྲེལ་མཐུད་བརྒྱུད་ལམ། ཐོན་སྐྱེད་དང་སྲི་ཞུ། ་ཐོག་དེབ་བཞི་ དཔར་འགྲེམས་གནང་ཡོད་པ་དང་བོད་ཡིག་དྲ་ཚིགས་ཁག་ནང་ལ་ཡང་རྩོམ་ཡང་ཡང་བྲིས་གནང་མཁན་རེད། ལེ་ཚན་ཁག ལེ་ཚན་ཁག འབྲེལ་ཡོད། འགྲེམ་སྟོན། རྒྱུད་ལམ་སྣ་མང་ཡིག་མཛོད། བཀོལ་སྤྱོད་པའི་འཇོག་ཡུལ་དྲ་ངོས། སྔོན་མ། རྗེས་མ། བསྟན་འཛིན་བདེ་སྐྱིད། ཚེ་རིང་རྣམ་རྒྱལ། བསྟན་འཛིན་ངག་དབང་། ཡོལ་གདོང་ཚེ་རིང་ལྷག་པ། ་དབང་ ཕྱུག་གཉིས་ཀྱིས་བརྗོད་གཞི་བྱེ་བྲག་པ་ཞིག་ལ་བགྲོ་གླེང་གཏིང་ཟབ་བྱེད་པའི་གཟའ་ འཁོར་གཉིས་རེའི་མཚམས་ཀྱི་ལེ་ཚན་ཞིག་ཡིན། དཔྱད་ཞིབ་ཀྱིས་རྒྱ་ནག་ནང་ཁུལ་གྱི་འགྱུར་ལྡོག་དང༌། རྒྱ་ནག་དང་རྒྱལ་སྤྱིའི་འབྲེལ་བར་དམིགས་སུ་བཀར་ནས་བགྲོ་གླེང་བྱེད་ཀྱི་ཡོད།། རྒྱང་སྲིང་དུས་ཚོད།";
+
+const kTeststr_br_Latn = " a chom met leuskel a ra e blas da jack irons dilabour hag aet kuit eus what is this dibab a reont da c houde michael beinhorn evit produiñ an trede pladenn kavet e vez ar ganaouennoù buhan ha buhan ganto setu stummet ar bladenn adkavet e vez enni funk";
+const kTeststr_bs_Cyrl = "историја босне књ историја босне књ историја босне књ историја босне књ ";
+// From 10% testing part of new lang=bs scrape
+const kTeststr_bs_Latn = "Novi predsjednik Mešihata Islamske zajednice u Srbiji (IZuS) i muftija dr. Mevlud ef. Dudić izjavio je u intervjuu za Anadolu Agency (AA) kako je uvjeren da će doći do vraćanja jedinstva među muslimanima i unutar Islamske zajednice na prostoru Sandžaka, te da je njegova ruka pružena za povratak svih u okrilje Islamske zajednice u Srbiji nakon skoro sedam godina podjela u tom dijelu Srbije. Dudić je za predsjednika Mešihata IZ u Srbiji izabran 4. januara, a zvanična inauguracija će biti obavljena u prvoj polovini februara. Kako se očekuje, prisustvovat će joj i reisu-l-ulema Islamske zajednice u Srbiji Husein ef. Kavazović koji će i zvanično promovirati Dudića u novog prvog čovjeka IZ u Srbiji. Dudić će danas boraviti u prvoj zvaničnoj posjeti reisu Kavazoviću, što je njegov privi simbolični potez nakon imenovanja. ";
+
+const kTeststr_ca_Latn = "al final en un únic lloc nhorabona l correu electrònic està concebut com a eina de productivitat aleshores per què perdre el temps arxivant missatges per després intentar recordar on els veu desar i per què heu d eliminar missatges importants per l";
+const kTeststr_ceb_Latn = "Ang Sugbo usa sa mga labing ugmad nga lalawigan sa nasod. Kini ang sentro sa komersyo, edukasyon ug industriya sa sentral ug habagatang dapit sa kapupod-an. Ang mipadayag sa Sugbo isip ikapito nga labing nindot nga pulo sa , ang nag-inusarang pulo sa Pilipinas nga napasidunggan sa maong magasin sukad pa sa tuig";
+const kTeststr_ceb_Latn2 = "Ang mga komyun sa Pransiya duol-duol sa inkorporadong mga lungsod ug mga dakbayan sa Estados Unidos. Wala kini susamang istruktura sa Hiniusang Gingharian (UK) tungod kay ang estado niini taliwala sa di-metropolitan nga distrito ug sa sibil nga parokya. Wala usab kini susamang istruktura sa Pilipinas.";
+const kTeststr_chr_Cher = "ᎠᎢᏍᎩ ᎠᏟᎶᏍᏗ ᏥᏄᏍᏛᎩ ᎦᎫᏍᏛᏅᎯ ᎾᎥᎢ";
+const kTeststr_co_Latn = " a prupusitu di risultati for utilizà a scatula per ricercà ind issi risultati servore errore u servore ha incuntratu una errore pruvisoria é ùn ha pussutu compie a vostra dumanda per piacè acimenta dinò ind una minuta tuttu listessu ligami truvà i";
+const kTeststr_crs_Latn = "Sesel ou menm nou sel patri. Kot nou viv dan larmoni. Lazwa, lanmour ek lape. Nou remersye Bondye. Preserv labote nou pei. Larises nou losean. En leritaz byen presye. Pour boner nou zanfan. Reste touzour dan linite. Fer monte nou paviyon. Ansanm pou tou leternite. Koste Seselwa!";
+const kTeststr_cs_Latn = " a akci opakujte film uložen vykreslit gmail tokio smazat obsah adresáře nelze načíst systémový profil jednotky smoot okud používáte pro určení polokoule značky z západ nebo v východ používejte nezáporné hodnoty zeměpisné délky nelze";
+const kTeststr_cy_Latn = " a chofrestru eich cyfrif ymwelwch a unwaith i chi greu eich cyfrif mi fydd yn cael ei hysbysu o ch cyfeiriad ebost newydd fel eich bod yn gallu cadw mewn cysylltiad drwy gmail os nad ydych chi wedi clywed yn barod am gmail mae n gwasanaeth gwebost";
+const kTeststr_da_Latn = " a z tallene og punktummer der er tilladte log ud angiv den ønskede adgangskode igen november gem personlige oplysninger kontrolspørgsmål det sidste tegn i dit brugernavn skal være et bogstav a z eller tal skriv de tegn du kan se i billedet nedenfor";
+const kTeststr_de_Latn = " abschnitt ordner aktivieren werden die ordnereinstellungen im farbabschnitt deaktiviert öchten sie wirklich fortfahren eldtypen angeben optional n diesem schritt geben sie für jedesfeld aus dem datenset den typ an ieser schritt ist optional eldtypen";
+const kTeststr_dv_Thaa = " ހިންދީ ބަހުން ވާހަކަ ދައްކާއިރު ދެވަނަ ބަހެއްގެ ގޮތުގައާއި އެނޫން ގޮތްގޮތުން ހިންދީ ބަހުން ވާހަކަ ދައްކާ މީހުންގެ އަދަދު މިލިއަނަށް";
+const kTeststr_dz_Tibt = " རྩིས བརྐྱབ ཚུལ ལྡན དང ངེས བདེན སྦ སྟོན ནིའི དོན ལུ ཁྱོད གུག ཤད ལག ལེན འཐབ དགོ ག དང ཨིན པུཊི གྲལ ཐིག གུ";
+const kTeststr_ee_Latn = "Yi (Di tanya sia) tatia akɔ wò ayi axa yeye dzi kple tanya si sɔ kple esi wòŋlɔ ɖe goa me, negbe axaa ɖe li kpakple tanya mawo xoxo ko. Teƒe le axa yeye sia dzi si wòateŋu atia na kpekpeɖeŋu kple nuwoŋlɔŋlɔ ne anɔ hahiãm na wò. Mehiã be na gbugbɔ ava afii na axa yeye gɔmedzedze o. Woateŋu adze wo gɔme kple nuŋɔŋlɔ dzẽwo tatia. Megavɔ̃ na nuyeyewo gɔmedzedze kroa o.";
+const kTeststr_el_Grek = " ή αρνητική αναζήτηση λέξης κλειδιού καταστήστε τις μεμονωμένες λέξεις κλειδιά περισσότερο στοχοθετημένες με τη μετατροπή τους σε";
+const kTeststr_en_Latn = " a backup credit card by visiting your billing preferences page or visit the adwords help centre for more details https adwords google com support bin answer py answer hl en we were unable to process the payment of for your outstanding google adwords";
+const kTeststr_eo_Latn = " a jarcento refoje per enmetado de koncerna pastro tiam de reformita konfesio ekde refoje ekzistis luteranaj komunumanoj tamen tiuj fondis propran komunumon nur en ambaŭ apartenis ekde al la evangela eklezio en prusio resp ties rejnlanda provinceklezio en";
+const kTeststr_es_Latn = " a continuación haz clic en el botón obtener ruta también puedes desplazarte hasta el final de la página para cambiar tus opciones de búsqueda gráfico y detalles ésta es una lista de los vídeos que te recomendamos nuestras recomendaciones se basan";
+const kTeststr_et_Latn = " a niipea kui sinu maksimaalne igakuine krediidi limiit on meie poolt heaks kiidetud on sinu kohustuseks see krediidilimiit";
+const kTeststr_eu_Latn = " a den eraso bat honen kontra hortaz eragiketa bakarrik behar dituen eraso batek aes apurtuko luke nahiz eta oraingoz eraso bideraezina izan gaur egungo teknologiaren mugak direla eta oraingoz kezka hauek alde batera utzi daitezke orain arteko indar";
+const kTeststr_fa_Arab = " آب خوردن عجله می کردند به جای باز ی کتک کاری می کردند و همه چيز مثل قبل بود فقط من ماندم و يک دنيا حرف و انتظار تا عاقبت رسيد احضاريه ی ای با";
+const kTeststr_fi_Latn = " a joilla olet käynyt tämä kerro meille kuka ä olet ei tunnistettavia käyttötietoja kuten virheraportteja käytetään google desktopin parantamiseen etsi näyttää mukautettuja uutisia google desktop keskivaihto leikkaa voit kaksoisnapsauttaa";
+const kTeststr_fj_Latn = " i kina na i iri ka duatani na matana main a meke wesi se meke mada na meke ni yaqona oqo na meke ka dau vakayagataki ena yaqona vakaturaga e dau caka toka ga kina na vucu ka dau lagati tiko kina na ka e yaco tiko na talo ni wai ni yaqona na lewai ni wai";
+const kTeststr_fo_Latn = " at verða átaluverdar óhóskandi ella áloypandi vit kunnu ikki garanterða at google leitanin ikki finnur naka sum er áloypandi óhóskandi ella átaluvert og google tekur onga ábyrgd yvir tær síður sum koma við í okkara leitiskipan fá tær ein";
+const kTeststr_fr_Latn = " a accès aux collections et aux frontaux qui lui ont été attribués il peut consulter et modifier ses collections et exporter des configurations de collection toutefois il ne peut pas créer ni supprimer des collections enfin il a accès aux fonctions";
+const kTeststr_fy_Latn = " adfertinsjes gewoan lytse adfertinsjes mei besibbe siden dy t fan belang binne foar de ynhâld fan jo berjochten wolle jo mear witte fan gmail foardat jo jo oanmelde gean dan nei wy wurkje eltse dei om gmail te ferbetterjen dêrta sille wy jo sa út en";
+const kTeststr_ga_Latn = " a bhfuil na focail go léir i do cheist le fáil orthu ní gá ach focail breise a chur leis na cinn a cuardaíodh cheana chun an cuardach a bheachtú nó a chúngú má chuirtear focal breise isteach aimseofar fo aicme ar leith de na torthaí a fuarthas";
+const kTeststr_gaa_Latn = "Akε mlawookpeehe kε Maŋhiεnyiεlכ oshikifככ lε eba naagbee ni maŋ lε nitsumכ ni kwεכ oshikifככ nכ lε etsככ mכ ni ye kunim ni akε lε eta esεŋ nכ. Dani nomεi baaba nכ lε, maŋ nכkwεmכ kui wuji enyכ ni yככ wכ maŋ lε mli, NPP kε NDC mli bii fכfכi wiemכi kεmaje majee amεhe. Ekomεi kwraa po yafee hiεkwεmכi ni ha ni gidigidi, pilamכ kε la shishwiemכ aaba yε heikomεi. ";
+const kTeststr_gd_Latn = " air son is gum bi casg air a h uile briosgaid no gum faigh thu brath nuair a tha briosgaid a tighinn gad rannsachadh ghoogle gu ceart mura bheil briosgaidean ceadaichte cuiridh google briosgaid dha do neach cleachdaidh fa leth tha google a cleachdadh";
+const kTeststr_gl_Latn = " debe ser como mínimo taranto tendas de venda polo miúdo cociñas servizos bordado canadá viaxes parques de vehículos de recreo hotel oriental habitación recibir unha postal no enderezo indicado anteriormente";
+const kTeststr_gn_Latn = " aháta añe ë ne mbo ehára ndive ajeruréta chupe oporandujey haĝua peëme mba épa pekaru ha áĝa oporandúvo nde eréta avei re paraguaýpe kachíke he i leúpe ndépa re úma kure tatakuápe ha leu ombohovái héë ha ujepéma kachíke he ijey";
+const kTeststr_gu_Gujr = " આના પરિણામ પ્રમાણસર ફોન્ટ અવતરણ ચિન્હવાળા પાઠને છુપાવો બધા સમૂહો શોધાયા હાલનો જ સંદેશ વિષયની";
+const kTeststr_gv_Latn = " and not ripe as i thought yn assyl yn shynnagh as yn lion the ass the fox and the lion va assyl as shynnagh ayns commee son nyn vendeilys as sauchys hie ad magh ayns y cheyll dy shelg cha row ad er gholl feer foddey tra veeit ad rish lion yn shynnagh";
+const kTeststr_ha_Latn = " a cikin a kan sakamako daga sakwannin a kan sakamako daga sakwannin daga ranar zuwa a kan sakamako daga guda daga ranar zuwa a kan sakamako daga shafukan daga ranar zuwa a kan sakamako daga guda a cikin last hour a kan sakamako daga guda daga kafar";
+const kTeststr_haw_Latn = "He puke noiʻi kūʻikena kūnoa ʻo Wikipikia. E ʻoluʻolu nō, e hāʻawi mai i kāu ʻike, kāu manaʻo, a me kou leo no ke kūkulu ʻana a me ke kākoʻo ʻana mai i ka Wikipikia Hawaiʻi. He kahua pūnaewele Hawaiʻi kēia no ka hoʻoulu ʻana i ka ʻike Hawaiʻi. Inā hiki iā ʻoe ke ʻōlelo Hawaiʻi, e ʻoluʻolu nō, e kōkua mai a e hoʻololi i nā ʻatikala ma ʻaneʻi, a pono e haʻi aku i kou mau hoa aloha e pili ana i ka Wikipikia Hawaiʻi. E ola mau nō ka ʻōlelo Hawaiʻi a mau loa aku.";
+const kTeststr_hi_Deva = " ं ऐडवर्ड्स विज्ञापनों के अनुभव पर आधारित हैं और इनकी मदद से आपको अपने विज्ञापनों का अधिकतम लाभ";
+const kTeststr_hr_Latn = "Posljednja dva vladara su Kijaksar (Κυαξαρης; 625-585 prije Krista), fraortov sin koji će proširiti teritorij Medije i Astijag. Kijaksar je imao kćer ili unuku koja se zvala Amitis a postala je ženom Nabukodonosora II. kojoj je ovaj izgradio Viseće vrtove Babilona. Kijaksar je modernizirao svoju vojsku i uništio Ninivu 612. prije Krista. Naslijedio ga je njegov sin, posljednji medijski kralj, Astijag, kojega je detronizirao (srušio sa vlasti) njegov unuk Kir Veliki. Zemljom su zavladali Perzijanci.";
+const kTeststr_ht_Latn = " ak pitit tout sosyete a chita se pou sa leta dwe pwoteje yo nimewo leta fèt pou li pwoteje tout paran ak pitit nan peyi a menm jan kit paran yo marye kit yo pa marye tout manman ki fè pitit leta fèt pou ba yo konkoul menm jan tou pou timoun piti ak pou";
+const kTeststr_hu_Latn = " a felhasználóim a google azonosító szöveget ikor látják a felhasználóim a google azonosító szöveget felhasználók a google azonosító szöveget fogják látni minden tranzakció után ha a vásárlását regisztrációját oldalunk";
+const kTeststr_hy_Armn = " ա յ եվ նա հիացած աչքերով նայում է հինգհարկանի շենքի տարօրինակ փոքրիկ քառակուսի պատուհաններին դեռ մենք շատ ենք հետամնաց ասում է նա այսպես է";
+const kTeststr_ia_Latn = " super le sitos que tu visita isto es necessari pro render disponibile alcun functionalitates del barra de utensiles a fin que nos pote monstrar informationes ulterior super un sito le barra de utensiles debe dicer a nos le";
+// From 10% testing part of new lang=id scrape
+const kTeststr_id_Latn = "berdiri setelah pengurusnya yang berusia 83 tahun, Fayzrahman Satarov, mendeklarasikan diri sebagai nabi dan rumahnya sebagai negara Islam Satarov digambarkan sebagai mantan ulama Islam tahun 1970-an. Pengikutnya didorong membaca manuskripnya dan kebanyakan dilarang meninggalkan tempat persembunyian bawah tanah di dasar gedung delapan lantai mereka. Jaksa membuka penyelidikan kasus kriminal pada kelompok itu dan menyatakan akan membubarkan kelompok kalau tetap melakukan kegiatan ilegal seperti mencegah anggotanya mencari bantuan medis atau pendidikan. Sampai sekarang pihak berwajib belum melakukan penangkapan meskipun polisi mencurigai adanya tindak kekerasan pada anak. Pengadilan selanjutnya akan memutuskan apakah anak-anak diizinkan tetap tinggal dengan orang tua mereka. Kazan yang berada sekitar 800 kilometer di timur Moskow merupakan wilayah Tatarstan yang";
+
+const kTeststr_ie_Latn = " abhorre exceptiones in li derivation plu cardinal por un l i es li regularità del flexion conjugation ples comparar latino sine flexione e li antiqui projectes naturalistic queles have quasi null regules de derivation ma si on nu examina li enunciationes";
+const kTeststr_ig_Latn = "Chineke bụ aha ọzọ ndï omenala Igbo kpọro Chukwu. Mgbe ndị bekee bịara, ha mee ya nke ndi Christian. N'echiche ndi ekpere chi Omenala Ndi Igbo, Christianity, Judaism, ma Islam, Chineke nwere ọtụtụ utu aha, ma nwee nanị otu aha. Ụzọ abụọ e si akpọ aha ahụ bụ Jehovah ma Ọ bụ Yahweh. Na ọtụtụ Akwụkwọ Nsọ, e wepụla aha Chineke ma jiri utu aha bụ Onyenwe Anyị ma ọ bụ Chineke dochie ya. Ma mgbe e dere akwụkwọ nsọ, aha ahụ bụ Jehova pụtara n’ime ya, ihe dị ka ugboro pụkụ asaa(7,000).";
+// From 10% testing part of new lang=ik scrape
+const kTeststr_ik_Latn = "sabvaqjuktuq sabvaba atiqaqpa atiqaqpa ibiq iebiq ixafich niuqtulgiññatif uvani natural gas tatpikka ufasiksigiruaq maaffa savaannafarufa mi tatkivani navy qanuqjugugguuq taaptuma inna uqsrunik ivaqjiqhutik taktuk allualiuqtuq sigukun nanuq puuvraatuq taktuum amugaa kalumnitigun nanuq agliruq allualiuqtuq";
+
+const kTeststr_is_Latn = " a afköst leitarorða þinna leitarorð neikvæð leitarorð auglýsingahópa byggja upp aðallista yfir ný leitarorð fyrir auglýsingahópana og skoða ítarleg gögn um árangur leitarorða eins og samkeppni auglýsenda og leitarmagn er krafist notkun";
+const kTeststr_it_Latn = " a causa di un intervento di manutenzione del sistema fino alle ore circa ora legale costa del pacifico del novembre le campagne esistenti continueranno a essere pubblicate come di consueto anche durante questo breve periodo di inattività ci scusiamo per";
+const kTeststr_iu_Cans = "ᐃᑯᒪᒻᒪᑦ ᕿᓈᖏᓐᓇᓲᖑᒻᒪᑦ ᑎᑎᖅᑕᓕᒫᖅᓃᕕᑦ ᑎᑦᕆᐊᑐᓐᖏᑦᑕᑎᑦ ᑎᑎᖅᑕᑉᐱᑦ ᓯᕗᓂᖓᓂ ᑎᑎᖅᖃᖅ ᑎᑎᕆᐊᑐᓐᖏᑕᐃᑦ ᕿᓂᓲᖑᔪᒍᑦ ᑎᑎᖅᑕᓕᒫᖅᓃᕕᑦ";
+const kTeststr_he_Hebr = " או לערוך את העדפות ההפצה אנא עקוב אחרי השלבים הבאים כנס לחשבון האישי שלך ב";
+const kTeststr_ja_Hani = " このペ ジでは アカウントに指定された予算の履歴を一覧にしています それぞれの項目には 予算額と特定期間のステ タスが表示されます 現在または今後の予算を設定するには";
+const kTeststr_jw_Latn = " account ten server niki kalian username meniko tanpo judul cacahe account nggonanmu wes pol pesen mu wes diguwak pesenan mu wes di simpen sante wae pesenan mu wes ke kirim mbuh tekan ora pesenan e ke kethok pesenan mu wes ke kirim mbuh tekan ora pesenan";
+const kTeststr_ka_Geor = " ა ბირთვიდან მიღებული ელემენტი მენდელეევის პერიოდულ სიტემაში გადაინაცვლებს ორი უჯრით";
+const kTeststr_kha_Latn = " kaba jem jai sa sngap thuh ia ki bynta ba sharum naka sohbuin jong phi nangta sa pynhiar ia ka kti kadiang jong phi sha ka krung jong phi bad da kaba pyndonkam kumjuh ia ki shympriahti jong phi sa sngap thuh shapoh ka tohtit jong phi pyndonkam ia kajuh ka";
+const kTeststr_kk_Arab = " ﺎ ﻗﻴﺎﻧﺎﺕ ﺑﻮﻟﻤﺎﻳﺪﻯ ﺑﯘﻝ ﭘﺮﻭﺗﺴﻪﺳﯩﻦ ﻳﺎﻋﻨﻲ ﻗﺎﻻ ﻭﻣﯩﺮﯨﻨﺪﻩ ﻗﺎﺯﺍﻕ ء ﺗﯩﻠﯩﻨﯩﯔ ﻗﻮﻟﺪﺍﻧﯩﻠﻤﺎﯞﻯ ﻗﺎﺯﺍﻕ ﺟﻪﺭﯨﻨﺪﻩ";
+const kTeststr_kk_Cyrl = " а билердің өзіне рұқсат берілмеген егер халық талап етсе ғана хан келісім берген өздеріңіз білесіздер қр қыл мыс тық кодексінде жазаның";
+const kTeststr_kk_Latn = " bolsa da otanyna qaityp keledi al oralmandar basqa elderde diasporasy ote az bolghandyqtan bir birine komektesip bauyrmal bolady birde men poezben oralmandardyng qazaqstangha keluin kordim monghol qazaqtary poezdan tuse sala jerdi suip jylap keletin biraq";
+const kTeststr_kl_Latn = " at nittartakkalli uani toqqarsimasatta akornanni nittartakkanut allanut ingerlaqqittoqarsinnaavoq kanukoka tassaavoq kommuneqarfiit kattuffiat nuna tamakkerlugu kommunit nittartagaannut ingerlaqqiffiusinnaasoq kisitsiserpassuit nunatsinnut tunngasut";
+const kTeststr_km_Khmr = " ក ខ គ ឃ ង ច ឆ ជ ឈ ញ ដ ឋ ឌ ឍ ណ ត ថ ទ ធ ន ប ផ ព ភ ម យ រ ល វ ស ហ ឡ អ ឥ ឦ ឧ ឪ ឫ ឬ ឯ ឱ ទាំងអស់";
+const kTeststr_kn_Knda = " ಂಠಯ್ಯನವರು ತುಮಕೂರು ಜಿಲ್ಲೆಯ ಚಿಕ್ಕನಾಯಕನಹಳ್ಳಿ ತಾಲ್ಲೂಕಿನ ತೀರ್ಥಪುರ ವೆಂಬ ಸಾಧಾರಣ ಹಳ್ಳಿಯ ಶ್ಯಾನುಭೋಗರ";
+const kTeststr_ko_Hani = " 개별적으로 리포트 액세스 권한을 부여할 수 있습니다 액세스 권한 부여사용자에게 프로필 리포트에 액세스할 수 있는 권한을 부여하시려면 가용 프로필 상자에서 프로필 이름을 선택한 다음";
+// From 10% testing part of new lang=ks scrape
+const kTeststr_ks_Arab = " ژماں سرابن منز گرٲن چھِہ خابٕک کھلونہٕ ؤڈراواں تُلتِھ نِیَس تہٕ گوشہِ گوشہِ مندچھاوى۪س دِلس چھُہ وون٘ت وُچھان از ستم قلم صبوٝرٕ وول مسٲفر لیۆکھُن بێتابن منز ورل سوال چھُہ تراواں جوابن منز کالہٕ پھۯستہٕ پھن٘ب پگَہہ پہ پۆت نظر دِژ نہٕ ژھالہٕ مٔت آرن مٲنز مسول متھان چھےٚ مس والن وۅن چھےٚ غارن تہِ نارٕ ژھٹھ ژاپان رێش تۅرگ تراوٕہن تہٕ ون رٹہٕ ہن ہوشہِ ہێۆچھ نہٕ پوشنوٝلس نِش مۅہرٕ دی دی زٕلاں چھِ زى۪و حرفن لۆدرٕ پھٔل ہى۪تھ ملر عازمؔ سۆدرٕ کھۅنہِ منز منگاں چھُہ ندرى۪ن پن ژے تھى۪کی یہِ مسٲفر پنن وُڈو تہٕ پڑاو گٕتَو گٕتَو چھےٚ یہِ کۅل بُتھ تہٕ بانہٕ سٕہہ گۅردٕ چھہِ سپداں دمہٕ پُھٹ چھِٹہ پونپر پکھہٕ داران سُہ یتى۪ن تۯاوِ کم نظر دۯاکھ تہٕ باسیوے سُہ مۆہ ہیو یێران مےٚ ژى۪تُرمُت چھُہ سُلی تس چھےٚ کتى۪ن تھپھ شاد مس کراں وُچھ مےٚ خون ژٕ خبر کیازِ کراں دۯاکھ تمِس پى۪ٹھ ماتم أز کہِ شبہٕ آو مےٚ بێیہِ پیش سفر زانہِ خدا دارِ پى۪ٹھ ژٲنگ ہنا تھو زِ ژے چھےٚ مێون أنہٕ کپٹاں چھُہ زٕژن سون مظفّر عازمؔ پوشہ برگن چھُہ سُواں چاکھ سُہ الماس قلم لوِ کٔ ڈ نوِ سرٕ سونتس کل پروِ بۆر بێیہ از بانبرِ ہۆت یمبرزلہِ ٹارى۪ن منز نار وزملہِ کۅسہٕ کتھ کٔر اظہار کچھہِ منزٕ ؤن رووُم اچھہِ چشمو ژوپُم کٔنڈ انبار تماشہِ چھہِ تگاں";
+
+const kTeststr_ks_Deva = "नमस्ते शारदे देवि काश्मिरपुर्वासिनि त्वामहम प्रार्थये देवि विद्य दानम च देहि मे कॉशुर लेख॒नुक सारिव॒य खॊत॒ आसान तरीक॒ छु यि देवनागरी टाइपराइटर इस्तिमाल करुन. अथ मंज़ छि कॉशुर लेख॒न॒चि सारॆय मात्रायि. अमि अलाव॒ हॆकिव तॊह्य् यिम॒ यूनिकोड एडिटर ति वरतॉविथ मगर कॉशिरि मात्रायि लेख॒नस गछ़ि हना दिकथ: अक्षरमालाछु अख मुफ़्त त॒ सॅहॅल सोफ्टवेर यॆमि स॒त्य् युनिकोड देवनागरी मंज़ ITRANS scheme स॒त्य् छु यिवान लेख॒न॒. वुछिव: सहायता. अथ स॒त्य् जुडिथ जालपृष्ठ (वेबपेज) (सॉरी अँग्रीज़ी पॉठ्य)";
+const kTeststr_ku_Arab = " بۆ به ڕێوه بردنی نامه ی که دێتن ڕاسته وخۆ ڕه وان بکه نامه کانی گ مایل بۆ حسابی پۆستێکی تر هێنانی په یوه ندکاره کان له";
+const kTeststr_ku_Latn = " be zmaneki ter le inglis werdegeretewe em srvise heshta le cor beta daye wate hest a taqi dekrete u bashtr dekret tewawwzmanekan wernegrawnetewe u ne hemu laperakn ke eme pshtiwan dekayn be teaweti wergerawete nermwalley wergeran teksti new wene nasnatewe";
+const kTeststr_ky_Arab = " جانا انى تانۇۇ ۇلۇتۇن تانۇۇ قىرعىزدى بئلۉۉ دەگەندىك اچىق ايتساق ماناستى تاانىعاندىق ۅزۉڭدۉ تاانىعاندىق بۉگۉن تەما جۉكتۅمۅ ق ى رع ى ز ت ى ل ى";
+const kTeststr_ky_Cyrl = " агай эле оболу мен садыбакас аганын өзү менен эмес эмгектери менен тааныштым жылдары ташкенде өзбекстан илимдер академиясынын баяны";
+const kTeststr_la_Latn = " a deo qui enim nocendi causa mentiri solet si iam consulendi causa mentiatur multum profecit sed aliud est quod per se ipsum laudabile proponitur aliud quod in deterioris comparatione praeponitur aliter enim gratulamur cum sanus est homo aliter cum melius";
+const kTeststr_lb_Latn = " a gewerkschaften och hei gefuerdert dir dammen an dir häre vun de gewerkschaften denkt un déi aarm wann der äer fuerderunge formuléiert d sechst congés woch an aarbechtszäitverkierzung hëllefen hinnen net d unhiewe vun de steigerungssäz bei de";
+const kTeststr_lg_Latn = " abaana ba bani lukaaga mu ana mu babiri abaana ba bebayi lukaaga mu abiri mu basatu abaana ba azugaadi lukumi mu ebikumi bibiri mu abiri mu babiri abaana ba adonikamu lukaaga mu nltaaga mu mukaaga abaana ba biguvaayi enkumi bbiri mu ataano mu mukaaga";
+const kTeststr_lif_Limb = "ᤁᤡᤖᤠᤳ ᤕᤠᤰᤌᤢᤱ ᤆᤢᤶᤗᤢᤱᤖᤧ ᤛᤥᤎᤢᤱᤃᤧᤴ ᤀᤡᤔᤠᤴᤛᤡᤱ ᤆᤧᤶᤈᤱᤗᤧ ᤁᤢᤔᤡᤱᤅᤥ ᤏᤠᤈᤡᤖᤡ ᤋᤱᤒᤣ ᥈᥆᥆᥉ ᤒᤠ ᤈᤏᤘᤖᤡ ᤗᤠᤏᤢᤀᤠᤱ ᤁ᤹ᤏᤠ ᤋᤱᤒᤣ ᤁᤠᤰ ᤏᤠ᤺ᤳᤋᤢ ᤕᤢᤖᤢᤒᤠ ᤀᤡᤔᤠᤴᤛᤡᤱ ᤋᤱᤃᤡᤵᤛᤡᤱ ᤌᤡᤶᤒᤣᤴ ᤂᤠᤃᤴ ᤛᤡᤛᤣ᤺ᤰᤗᤠ ᥇᥍ ᤂᤧᤴ ᤀᤡᤛᤡᤰ ᥇ ᤈᤏᤘᤖᤡ ᥈᥆᥆᥊ ᤀᤥ ᤏᤠᤛᤢᤵ ᤆᤥ᤺ᤰᤔᤠ ᤌᤡᤶᤒᤣ ᤋᤱᤃᤠᤶᤛᤡᤱᤗ ᤐᤳᤐᤠ ᤀᤡᤱᤄᤱ ᤘᤠ᤹";
+const kTeststr_ln_Latn = " abakisamaki ndenge esengeli moyebami abongisamaki solo mpenza kombo ya moyebami elonguamaki kombo ya bayebami elonguamaki kombo eleki molayi po na esika epesameli limbisa esika ya kotia ba kombo esuki boye esengeli olimbola ndako na yo ya mikanda kombo";
+const kTeststr_lo_Laoo = " ກຫາທົ່ວທັງເວັບ ແລະໃນເວັບໄຮ້ສາຍ ທຳອິດໃຫ້ທຳການຊອກຫາກ່ອນ ຈາກນັ້ນ ໃຫ້ກົດປຸ່ມເມນູ ໃນໜ້າຜົນໄດ້";
+const kTeststr_lt_Latn = " a išsijungia mano idėja dėl geriausio laiko po pastarųjų savo santykių pasimokiau penki dalykai be kurių negaliu gyventi mano miegamajame tu surasi ideali pora išsilavinimas aukštoji mokykla koledžas universitetas pagrindinis laipsnis metai";
+const kTeststr_lv_Latn = " a gadskārtējā izpārdošana slēpošana jāņi atlaide izmaiņas trafikā kas saistītas ar sezonas izpārdošanu speciālajām atlaidēm u c ir parastas un atslēgvārdi kas ir populāri noteiktos laika posmos šajā laikā saņems lielāku klikšķu";
+const kTeststr_mfe_Latn = "Anz dir mwa, Sa bann delo ki to trouve la, kot fam prostitie asize, samem bann pep, bann lafoul dimoun, bann nasion ek bann langaz. Sa dis korn ki to finn trouve, ansam avek bebet la, zot pou ena laenn pou prostitie la; zot pou pran tou seki li ena e met li touni, zot pou manz so laser e bril seki reste dan dife. Parski Bondie finn met dan zot leker proze pou realiz so plan. Zot pou met zot dakor pou sed zot pouvwar bebet la ziska ki parol Bondie fini realize.";
+const kTeststr_mg_Latn = " amporisihin i ianao mba hijery ny dika teksta ranofotsiny an ity lahatsoratra ity tsy ilaina ny opérateur efa karohina daholo ny teny rehetra nosoratanao ampiasao anaovana dokambarotra i google telugu datin ny takelaka fikarohana sary renitakelak i";
+const kTeststr_mi_Latn = " haere ki te kainga o o haere ki te kainga o o haere ki te kainga o te rapunga ahua o haere ki te kainga o ka tangohia he ki to rapunga kaore au mohio te tikanga whakatiki o te ra he whakaharuru te pai rapunga a te rapunga ahua a e kainga o nga awhina o te";
+const kTeststr_mk_Cyrl = " гласовите коалицијата на вмро дпмне како партија со најмногу освоени гласови ќе добие евра а на сметката на коализијата за македонија";
+const kTeststr_ml_Mlym = " ം അങ്ങനെ ഞങ്ങള് അവരുടെ മുമ്പില് നിന്നു ഔടും ഉടനെ നിങ്ങള് പതിയിരിപ്പില് നിന്നു എഴുന്നേറ്റു";
+const kTeststr_mn_Cyrl = " а боловсронгуй болгох орон нутгийн ажил үйлсийг уялдуулж зохицуулах дүрэм журам боловсруулах орон нутгийн өмч хөрөнгө санхүүгийн";
+const kTeststr_mn_Mong = "ᠦᠭᠡ ᠵᠢᠨ ᠴᠢᠨᠭ᠎ᠠ ᠬᠦᠨᠳᠡᠢ ᠵᠢ ᠢᠯᠭᠠᠬᠣ";
+const kTeststr_mr_Deva = "हैदराबाद उच्चार ऐका (सहाय्य·माहिती)तेलुगू: హైదరాబాదు , उर्दू: حیدر آباد हे भारतातील आंध्र प्रदेश राज्याच्या राजधानीचे शहर आहे. हैदराबादची लोकसंख्या ७७ लाख ४० हजार ३३४ आहे. मोत्यांचे शहर अशी एकेकाळी ओळख असलेल्या या शहराला ऐतिहासिक, सांस्कृतिक आणि स्थापत्यशास्त्रीय वारसा लाभला आहे. १९९० नंतर शिक्षण आणि माहिती तंत्रज्ञान त्याचप्रमाणे औषधनिर्मिती आणि जैवतंत्रज्ञान क्षेत्रातील उद्योगधंद्यांची वाढ शहरात झाली. दक्षिण मध्य भारतातील पर्यटन आणि तेलुगू चित्रपटनिर्मितीचे हैदराबाद हे केंद्र आहे";
+// From 10% testing part of new lang=ms scrape
+const kTeststr_ms_Latn = "pengampunan beramai-ramai supaya mereka pulang ke rumah masing-masing. Orang-orang besarnya enggan mengiktiraf sultan yang dilantik oleh Belanda sebagai Yang DiPertuan Selangor. Orang ramai pula tidak mahu menjalankan perniagaan bijih timah dengan Belanda, selagi raja yang berhak tidak ditabalkan. Perdagang yang lain dibekukan terus kerana untuk membalas jasa beliau yang membantu Belanda menentang Riau, Johor dan Selangor. Di antara tiga orang Sultan juga dipandang oleh rakyat sebagai seorang sultan yang paling gigih. 1 | 2 SULTAN Sebagai ganti Sultan Ibrahim ditabalkan Raja Muhammad iaitu Raja Muda. Walaupun baginda bukan anak isteri pertama bergelar Sultan Muhammad bersemayam di Kuala Selangor juga. Pentadbiran baginda yang lemah itu menyebabkan Kuala Selangor menjadi sarang ioleh Cina di Lukut tidak diambil tindakan, sedangkan baginda sendiri banyak berhutang kepada 1";
+
+const kTeststr_ms_Latn2 = "bilik sebelah berkata julai pada pm ladymariah hmm sume ni terpulang kepada individu mungkin anda bernasib baik selama ini dalam membeli hp yang bagus deli berkata julai pada pm walaupun bukan bahsa baku tp tetap bahasa melayu kan perubahan boleh dibuat";
+const kTeststr_mt_Latn = " ata ikteb messaġġ lil indirizzi differenti billi tagħżilhom u tagħfas il buttuna ikteb żid numri tfittxijja tal kotba mur print home kotba minn pagni ghal pagna minn ghall ktieb ta aċċessa stieden habib iehor grazzi it tim tal gruppi google";
+const kTeststr_my_Latn = " jyk ef oif gawgodcsifayvdrfhrnf bmawgrsm topf dsvj g mail tamumif avhvm atmif txjwgif yxrqhk avhvm efae m pwifavhvm ef ufkyfwdky help center odkyvmyg drsm ar avh dswjhar cgef rsm udkawdkifygw f tajzawgudk smedkifygw f jyd awmh g mail cool features rsm";
+const kTeststr_my_Mymr = " တက္ကသုိလ္ မ္ဟ ပ္ရန္ လာ္ရပီးေနာက္ န္ဟစ္ အရ္ဝယ္ ဦးသန္ ့သည္ ပန္ းတနော္ အမ္ယုိးသား ေက္ယာင္ း";
+const kTeststr_na_Latn = " arcol obabakaen riringa itorere ibibokiei ababaro min kuduwa airumena baoin tokin rowiowet itiket keram damadamit eigirow etoreiy row keitsito boney ibingo itsiw dorerin naoerodelaporte s nauruan dictionary a c a c d g h o p s t y aiquen ion eins aiquen";
+const kTeststr_ne_Deva = "अरू ठाऊँबाटपनि खुलेको छ यो खाता अर अरू ठाऊँबाटपनि खुलेको छ यो खाता अर ू";
+const kTeststr_nl_Latn = " a als volgt te werk om een configuratiebestand te maken sitemap gen py ebruik filters om de s op te geven die moeten worden toegevoegd of uitgesloten op basis van de opmaaktaal elke sitemap mag alleen de s bevatten voor een bepaalde opmaaktaal dit";
+const kTeststr_nn_Latn = " a for verktylina til å hjelpa deg å nå oss merk at pagerank syninga ikkje automatisk kjem til å henta inn informasjon frå sider med argument dvs frå sider med eit i en dersom datamaskina di er plassert bak ein mellomtenar for vevsider kan det verka";
+const kTeststr_no_Latn = " a er obligatorisk tidsforskyvning plassering av katalogsøk planinformasjon loggfilbane gruppenavn kontoinformasjon passord domene gruppeinformasjon alle kampanjesporing alternativ bruker grupper oppgaveplanlegger oppgavehistorikk kontosammendrag antall";
+const kTeststr_nr_Latn = "ikomiti elawulako yegatja emhlanganweni walo ]imithetho mgomo ye anc ibekwa malunga wayo begodu ubudosiphambili kugandelela lokho okutjhiwo yi lokha nayithi abantu ngibo ";
+
+const kTeststr_nso_Latn = "Bophara bja Asia ekaba 8.6% bja lefase goba 29.4% bja naga ya lefase (ntle le mawatle). Asia enale badudu bao bakabago dimillione millione tše nne (4 billion) yeo e bago 60% ya badudi ba lefase ka bophara. A bapolelwa rena sefapanong mehleng ya Pontius Pilatus. A hlokofatšwa, A bolokwa, A tsoga ka letšatši la boraro, ka mo mangwalo a bolelago ka gona, a rotogela magodimong, ";
+const kTeststr_ny_Latn = "Boma ndi gawo la dziko lomwe linapangidwa ndi cholinga chothandiza ntchito yolamulira. Kuŵalako kulikuunikabe mandita, Edipo nyima unalephera kugonjetsa kuŵalako.";
+const kTeststr_oc_Latn = " Pasmens, la classificacion pus admesa uei (segon Juli Ronjat e Pèire Bèc) agropa lei parlars deis Aups dins l'occitan vivaroaupenc e non dins lo dialècte provençau.";
+const kTeststr_om_Latn = " afaan katalaa bork bork bork hiikaa jira hin argamne gareen barbaadame hin argamne gargarsa qube en gar bayee jira garee walitti firooman gareewwan walitti firooman fuula web akka tartiiba qubeetiin agarsiisi akka tartiiba qubeetiin agarsiisaa jira akka";
+const kTeststr_or_Orya = "ଅକ୍ଟୋବର ଡିସେମ୍ବର";
+const kTeststr_pa_Guru = " ਂ ਦਿਨਾਂ ਵਿਚ ਭਾਈ ਸਾਹਿਬ ਦੀ ਬੁੱਚੜ ਗੋਬਿੰਦ ਰਾਮ ਨਾਲ ਅੜਫਸ ਚੱਲ ਰਹੀ ਸੀ ਗੋਬਿੰਦ ਰਾਮ ਨੇ ਭਾਈ ਸਾਹਿਬ ਦੀਆਂ ਭੈਣਾ";
+const kTeststr_pl_Latn = " a australii będzie widział inne reklamy niż użytkownik z kanady kierowanie geograficzne sprawia że reklamy są lepiej dopasowane do użytkownika twojej strony oznacza to także że możesz nie zobaczyć wszystkich reklam które są wyświetlane na";
+const kTeststr_ps_Arab = " اتو مستقل رياست جوړ شو او د پخواني ادبي انجمن څانګې ددې رياست جز شوی او ددې انجمن د ژبې مديريت د پښتو ټولنې په لوی مديريت واوښت لوی مدير يې د";
+const kTeststr_pt_Latn = " a abit prevê que a entrada desses produtos estrangeiros no mercado têxtil e vestuário do brasil possa reduzir os preços em cerca de a partir de má notícia para os empresários que terão que lutar para garantir suas margens de lucro mas boa notícia";
+const kTeststr_qu_Latn = " is t ipanakunatapis rikuchinankupaq qanpa simiykipi noqaykoqpa uya jllanakunamanta kunan jamoq simikunaman qelqan tiyan watukuy qpa uyata qanpa llaqtaykipi llank anakuna simimanta yanapakuna simimanta mayqen llaqtallapis kay simimanta t ijray qpa qelqa";
+const kTeststr_rm_Latn = " Cur ch’il chantun Turitg ha dà il dretg da votar a las dunnas (1970) è ella vegnida elegida en il cussegl da vischnanca da Zumikon per la Partida liberaldemocratica svizra (PLD). Da 1974 enfin 1982 è ella stada presidenta da vischnanca da Zumikon. L’onn 1979 è Elisabeth Kopp vegnida elegida en il Cussegl naziunal e reelegida quatter onns pli tard cun in resultat da sur 100 000 vuschs. L’onn 1984 è ella daventada vicepresidenta da la PLD.";
+const kTeststr_rn_Latn = " ishaka mu ndero y abana bawe ganira n abigisha nimba hari ingorane izo ari zo zose ushobora gusaba kubonana n umwigisha canke kuvugana nawe kuri terefone inyuma y uko babarungikira urutonde rw amanota i muhira mu bisanzwe amashure aratumira abavyeyi";
+const kTeststr_ro_Latn = " a anunţurilor reţineţi nu plătiţi pentru clicuri sau impresii ci numai atunci când pe site ul dvs survine o acţiune dorită site urile negative nu pot avea uri de destinaţie daţi instrucţiuni societăţii dvs bancare sau constructoare să";
+const kTeststr_ro_Cyrl = "оперативэ а органелор ши институциилор екзекутиве ши а органелор жудичиаре але путерий де стат фиекэруй орган ал путерий де стат и се";
+const kTeststr_ru_Cyrl = " а неправильный формат идентификатора дн назад";
+const kTeststr_rw_Latn = " dore ibyo ukeneye kumenya ukwo watubona ibibazo byinshi abandi babaza ububonero byibibina google onjela ho izina dyikyibina kyawe onjela ho yawe mulugo kulaho ibyandiko byawe shyilaho tegula yawe tulubaka tukongeraho iyanya mishya buliko tulambula";
+const kTeststr_sa_Deva = " ं क र्मणस् त स्य य त्कि ङ्चेह करो त्यय ं त स्माल् लोका त्पु नरै ति अस्मै लोका य क र्मण इ ति नु काम";
+const kTeststr_sa_Latn = " brahmā tatraivāntaradhīyata tataḥ saśiṣyo vālmīkir munir vismayam āyayau tasya śiṣyās tataḥ sarve jaguḥ ślokam imaṃ punaḥ muhur muhuḥ prīyamāṇāḥ prāhuś ca bhṛśavismitāḥ samākṣaraiś caturbhir yaḥ pādair gīto";
+const kTeststr_sco_Latn = " a gless an geordie runciman ower a gless an tamson their man preached a hale hoor aboot the glorious memories o forty three an backsliders an profane persons like esau an aboot jeroboam the son o nebat that gaed stravagin to anither kirk an made aa israel";
+const kTeststr_sd_Arab = " اضافو ٿي ٿيو پر اها خبر عثمان کي بعد پيئي ته سگريٽ ڇڪيندڙ مسلمان نه هو بلڪ هندو هو دڪان تي پهچي عثمان ڪسبت کولي گراهڪن جي سيرب لاهڻ شروع ڪئي پر";
+const kTeststr_sg_Latn = " atâa na âkotta zo me lâkwê angbâ gï tarrango nî âkotta zo tî koddoro nî âde agbû tenne nî na kate töngana mbênî kotta kpalle tî nzönî dutï tî halëzo pëpe atâa sô âla lü gbâ tî ândya tî mâi na sahngo asâra gbâ tî";
+const kTeststr_si_Sinh = " අනුරාධ මිහිඳුකුල නමින් සකුරා ට ලිපියක් තැපෑලෙන් එවා තිබුණා කි ් රස්ටි ෂෙල්ටන් ප ් රනාන්දු ද";
+const kTeststr_sit_NP = " dialekten in de roerstreek pierre bakkes oet roerstreek blz bewirk waordebook zónjig oktoeaber is t ieëste mofers waordebook oetgekaome dit waordebook is samegestèldj";
+const kTeststr_sk_Latn = " a aktivovať reklamnú kampaň ak chcete kampaň pred spustením ešte prispôsobiť uložte ju ako šablónu a pokračujte v úprave vyberte si jednu z možností nižšie a kliknite na tlačidlo uložiť kampaň nastavenia kampane môžete ľubovoľne";
+const kTeststr_sl_Latn = " adsense stanje prijave za google adsense google adsense račun je bil začasno zamrznjen pozdravljeni hvala za vaše zanimanje v google adsense po pregledu vaše prijavnice so naši strokovnjaki ugotovili da spletna stran ki je trenutno povezana z vašim";
+const kTeststr_sm_Latn = " autu mea o lo totonu le e le minaomia matou te tuu i totonu i le faamatalaina o le suesuega i taimi uma mea o lo totonu fuafua i mea e tatau fa afoi tala mai le newsgroup mataupu fa afoi mai tala e ai le mataupu e ai totonu tusitala o le itu o faamatalaga";
+const kTeststr_sn_Latn = " chete vanyori vanotevera vakabatsira kunyora zvikamu zvino kumba home tinyorere tsamba chikamu chakumbirwa hachina kuwanikwa chikamu ichi cheninge chakayiswa kuimwe nzvimbo mudhairekitori rino chimwe chikamu chopadhuze pane chinhu chatadza kushanda bad";
+const kTeststr_so_Latn = " a oo maanta bogga koobaad ugu qoran yahey beesha caalamka laakiin si kata oo beesha caalamku ula guntato soomaaliya waxa aan shaki ku jirin in aakhirataanka dadka soomaalida oo kaliya ay yihiin ku soomaaliya ka saari kara dhibka ay ku jirto";
+const kTeststr_sq_Latn = " a do të kërkoni nga beogradi që të njohë pavarësinë e kosovës zoti thaçi prishtina është gati ta njoh pavarësinë e serbisë ndërsa natyrisht se do të kërkohet një gjë e tillë që edhe beogradi ta njoh shtetin e pavarur dhe sovran të";
+const kTeststr_sr_Cyrl = "балчак балчак на мапи србије уреди демографија у насељу балчак живи пунолетна становника а просечна старост становништва износи година";
+const kTeststr_sr_Latn = "Društvo | četvrtak 1.08.2013 | 13:43 Krade se i izvorska voda Izvor: Gornji Milanovac -- U gružanskom selu Belo Polje prošle noći ukradeno je više od 10.000 litara kojima je obijen bazen. Bazen je bio zaključan i propisno obezbeđen.";
+
+const kTeststr_sr_ME_Latn = "savjet pobjeda a radi bržeg rada pošto rom radi sporije nego ram izvorni rom se isključuje a dio ram a se rezerviše te se u njega ne ploča procesor ram memorija grafička kartica zvučna kartica modem mrežna kartica napojna jedinica uređaji za pohranjivanje";
+const kTeststr_ss_Latn = " bakhokhintsela yesikhashana bafake imininingwane ye akhawunti leliciniso kulelifomu nangabe akukafakwa imininingwane leliciniso imali lekhokhiwe angeke ifakwe kumkhokhintsela lofanele imininingwane ye akhawunti ime ngalendlela lelandzelako inombolo";
+const kTeststr_st_Latn = " bang ba nang le thahasello matshwao a sehlooho thuto e thehilweng hodima diphetho ke tsela ya ho ruta le ho ithuta e totobatsang hantle seo baithuti ba lokelang ho se fihlella ntlhatheo eo e sebetsang ka yona ke ya hore titjhere o hlakisa pele seo";
+const kTeststr_su_Latn = "Nu ngatur kahirupan warga, keur kapentingan pamarentahan diatur ku RT, RW jeung Kepala Dusun, sedengkeun urusan adat dipupuhuan ku Kuncen jeung kepala adat. Sanajan Kampung Kuta teu pati anggang jeung lembur sejenna nu aya di wewengkon Desa Pasir Angin, tapi boh wangunan imah atawa tradisi kahirupan masarakatna nenggang ti nu lian.";
+const kTeststr_sv_Latn = " a bort objekt från google desktop post äldst meny öretag dress etaljer alternativ för vad är inne yaste google skrivbord plugin program för nyheter google visa nyheter som är anpassade efter de artiklar som du läser om du till exempel läser";
+const kTeststr_sw_Latn = " a ujumbe mpya jumla unda tafuta na angalia vikundi vya kujadiliana na kushiriki mawazo iliyopangwa kwa tarehe watumiaji wapya futa orodha hizi lugha hoja vishikanisho vilivyo dhaminiwa ujumbe sanaa na tamasha toka udhibitisho wa neno kwa haraka fikia";
+const kTeststr_syr_Syrc = "ܐܕܪܝܣ ܓܛܘ ܫܘܪܝܐ ܡܢ ܦܪܢܣܐ ܡܢ ܐܣܦܢܝܐ ܚܐܪܘܬܐ ܒܐܕܪ ܒܢܝܣܢ ܫܛܝܚܘܬܐ ܟܠܢܝܐ ܡܝ̈ܐ ܒܥܠܡܐ";
+const kTeststr_ta_Taml = " அங்கு ராஜேந்திர சோழனால் கட்டப்பட்ட பிரம்மாண்டமான சிவன் கோவில் ஒன்றும் உள்ளது தொகு";
+const kTeststr_te_Telu = " ఁ దనర జయించిన తత్వ మరసి చూడఁ దాన యగును రాజయోగి యిట్లు తేజరిల్లుచు నుండు విశ్వదాభిరామ వినర వేమ";
+const kTeststr_tg_Arab = "رادیو فردا راديوى آزادى";
+const kTeststr_tg_Cyrl = " адолат ва инсондӯстиро бар фашизм нажодпарастӣ ва адоват тарҷеҳ додааст чоп кунед ба дигарон фиристед чоп кунед ба дигарон фиристед";
+const kTeststr_th_Thai = " กฏในการค้นหา หรือหน้าเนื้อหา หากท่านเลือกลงโฆษณา ท่านอาจจะปรับต้องเพิ่มงบประมาณรายวันตา";
+const kTeststr_ti_Ethi = " ሃገር ተረፎም ዘለዉ ኢትዮጵያውያን ኣብቲ ምስ ኢትዮጵያ ዝዳውብ ኣውራጃ ደቡብ ንኽነብሩ ኣይፍቀደሎምን እዩ ካብ ሃገር ንኽትወጽእ ዜጋ ኹን ወጻእተኛ ናይ";
+const kTeststr_tk_Cyrl = " айдянларына ынанярмыка эхли боз мейданлары сурулип гутарылан тебигы ота гарып гумлукларда миллиондан да артыкмач ири шахлы малы миллиона";
+const kTeststr_tk_Latn = " akyllylyk çyn söýgi üçin böwet däl de tebigylykdyr duýgularyň gödeňsiligi aç açanlygy bahyllygy söýgini betnyşanlyk derejesine düşürýändir söýeni söý söýmedige süýkenme özüni söýmeýändigini görmek ýigit üçin uly";
+const kTeststr_tl_Latn = " a na ugma sa google ay nakaka bantog sa gitna nang kliks na nangyayari sa pamamagitan nang ordinaryong paggagamit at sa kliks na likha nang pandaraya o hindi tunay na paggamit bunga nito nasasala namin ang mga kliks na hindi kailangan o hindi gusto nang";
+const kTeststr_tl_Tglg = " ᜋᜇ᜔ ᜐᜓᜎᜆ᜔ ᜃ ᜈᜅ᜔ ᜊᜌ᜔ᜊᜌᜒᜈ᜔ ᜂᜉᜅ᜔᜔ ᜋᜐᜈᜌ᜔ ᜎᜅ᜔ ᜁᜐ ᜉᜅ᜔ ᜀᜃ᜔ᜎᜆ᜔ ᜆᜓᜅ᜔ᜃᜓᜎ᜔ ᜐ ᜊᜌ᜔ᜊᜌᜒᜈ᜔ ᜐ ᜆᜒᜅᜒᜈ᜔ ᜃᜓ";
+const kTeststr_tlh_Latn = " a ghuv bid soh naq jih lodni yisov chich wo vamvo qeylis lunge pu chah povpu vodleh a dah ghah cho ej dah wo che pujwi bommu tlhegh darinmohlahchu pu majqa horey so lom qa ip quv law may vad suvtahbogh wa sanid utlh quv pus datu pu a vitu chu pu johwi tar";
+const kTeststr_tn_Latn = " go etela batla ditsebe tsa web tse di nang le le batla ditsebe tse di golaganya le tswang mo leka go batla web yotlhe batla mo web yotlhe go bona home page ya google batla mo a o ne o batla gore a o ne o batla ditsebe tsa bihari batla mo re maswabi ga go";
+const kTeststr_to_Latn = " a ke kumi oku ikai ke ma u vakai ki hono hokohoko faka alafapeti api pe ko e uluaki peesi a ho o fekumi faka malatihi fekumi ki he lea oku fakaha atu pe ko ha fonua fekumi ki he fekumi ki he peesi oku ngaahi me a oku sai imisi alu ki he ki he ulu aki";
+const kTeststr_tr_Latn = " a ayarlarınızı görmeniz ve yönetmeniz içindir eğer kampanyanız için günlük bütçenizi gözden geçirebileceğiniz yeri arıyorsanız kampanya yönetimi ne gidin kampanyanızı seçin ve kampanya ayarlarını düzenle yi tıklayın sunumu";
+const kTeststr_ts_Latn = " a ku na timhaka leti nga ta vulavuriwa na google google yi hlonipha yi tlhela yi sirheleta vanhu hinkwavo lava tirhisaka google toolbar ku dyondza hi vusireleli eka system ya hina hi kombela u hlaya vusireleli bya hina eka toolbar mbulavulo wu tshikiwile";
+const kTeststr_tt_Cyrl = "ачарга да бирмәде чәт чәт килеп тора безнең абыйнымы олы абыйнымы эштән";
+const kTeststr_tt_Latn = " alarnı eşkärtü proğramnarın eşläwen däwam itü tatar söylämen buldıru wä sizep alu sistemnarın eşläwen däwat itü häm başqalar yılnıñ mayında tatar internetı ictimağıy oyışması milli ts isemle berençe däräcäle häm tat";
+const kTeststr_tw_Latn = " amammui tumidifo no bɛtow ahyɛ atoro som so mpofirim na wɔasɛe no pasaa ma ayɛ nwonwa dɛn na ɛbɛka wɔn ma wɔayɛ saa bible no ma ho mmuae wɔ adiyisɛm nhoma no mu sɛ onyankopɔn na ɔde hyɛɛ wɔn komam sɛ wɔmma ne nsusuwii mmra mu";
+const kTeststr_ug_Arab = " ئالەملەرنىڭ پەرۋەردىگارىدىن تىلەيمەن سىلەر بۇ يەرلەردە باغچىلاردىن بۇلاقلاردىن زىرائەتلەردىن يۇمشاق پىشقان خورمىلاردىن بەھرىمەن بولۇپ";
+const kTeststr_ug_Cyrl = " а башлиди әмма бу қетимқи канада мәтбуатлириниң хәвәрлиридә илгирикидәк хитай һөкүмәт мәтбуатлиридин нәқил алидиған вә уни көчүрүп";
+const kTeststr_ug_Latn = " adawet bolghachqa hazir musherrepmu bu ikki partiyining birleshme hökümet qurushta pikir birliki hasil qilalmasliqini kütüwatqan iken wehalenki pakistan xelq partiyisining rehbiri asif eli zerdari pakistandiki bashqa ushshaq partiyilerning rehberliri";
+const kTeststr_uk_Cyrl = " а більший бюджет щоб забезпечити собі максимум прибутків від переходів відстежуйте свої об яви за датою географічним розташуванням";
+const kTeststr_ur_Arab = " آپ کو کم سے کم ممکنہ رقم چارج کرتا ہے اس کی مثال کے طور پر فرض کریں اگر آپ کی زیادہ سے زیادہ قیمت فی کلِک امریکی ڈالر اور کلِک کرنے کی شرح ہو تو";
+const kTeststr_uz_Arab = " آرقلی بوتون سیاسی حزب و گروه لرفعالیتیگه رخصت بیرگن اخبارات واسطه لری شو ییل مدتیده مثال سیز ترقی تاپکن و اهالی نینگ اقتصادی وضعیتی اوتمیش";
+const kTeststr_uz_Cyrl = " а гапирадиган бўлсак бунинг иккита йўли бор биринчиси мана шу қуриган сатҳини қумликларни тўхтатиш учун экотизимни мустаҳкамлаш қумга";
+const kTeststr_uz_Latn = " abadiylashtirildi aqsh ayol prezidentga tayyormi markaziy osiyo afg onistonga qanday yordam berishi mumkin ukrainada o zbekistonlik muhojirlar tazyiqdan shikoyat qilmoqda gruziya va ukraina hozircha natoga qabul qilinmaydi afg oniston o zbekistonni g";
+const kTeststr_ve_Latn = "Vho ṱanganedzwa kha Wikipedia nga tshiVenḓa. Vhadivhi vha manwalo a TshiVenda vha talusa divhazwakale na vhubvo ha Vhavenda ngau fhambana. Vha tikedza mbuno dzavho uya nga mawanwa a thoduluso dze vha ita. Vhanwe vha vhatodulusi vhari Vhavenda vho tumbuka Afrika vhukati vha tshimbila vha tshiya Tshipembe ha Afrika, Rhodesia hune ha vho vhidzwa Zimbagwe namusi.";
+const kTeststr_vi_Latn = " adsense cho nội dung nhà cung cấp dịch vụ di động xác minh tín dụng thay đổi nhãn kg các ô xem chi phí cho từ chối các đơn đặt hàng dạng cấp dữ liệu ác minh trang web của bạn để xem";
+const kTeststr_vo_Latn = " brefik se volapükavol nüm balid äpubon ün dü lif lölik okas redakans älaipübons gasedi at nomöfiko äd ai mu kuratiko pläo timü koup nedäna fa ns deutän kü päproibon fa koupanef me gased at ästeifülom ad propagidön volapüki as sam ün";
+const kTeststr_war_Latn = "Amo ini an balay han Winaray o Binisaya nga Lineyte-Samarnon nga Wikipedia, an libre ngan gawasnon nga ensayklopedya nga bisan hin-o puyde magliwat o mag-edit. An Wikipedia syahan gintikang ha Iningles nga yinaknan han tuig 2001. Ini nga bersyon Winaray gintikang han ika-25 han Septyembre 2005 ngan ha yana mayda 514,613 nga artikulo. Kon karuyag niyo magsari o magprobar, pakadto ha . An Gastrotheca pulchra[2] in uska species han Anura nga ginhulagway ni Ulisses Caramaschi ngan Rodrigues hadton 2007. An Gastrotheca pulchra in nahilalakip ha genus nga Gastrotheca, ngan familia nga Hemiphractidae.[3][4] Ginklasipika han IUCN an species komo kulang hin datos.[1] Waray hini subspecies nga nakalista.[3]";
+const kTeststr_wo_Latn = " am ak dëgg dëggam ak gëm aji bind ji te gëstu ko te jëfandikoo tegtalu xel ci saxal ko sokraat nag jëfandikoo woon na xeltu ngir tas jikko yu rafet ci biir nit ñi ak dëggu ak soppante sokraat nag ñëw na mook aflaton platon sukkandiku ci ñaari";
+const kTeststr_xh_Latn = " a naynga zonke futhi libhengezwa kwiwebsite yebond yasemzantsi afrika izinga elisebenzayo xa usenza olu tyalo mali liya kusebenza de liphele ixesha lotyalo mali lwakho inzala ihlawulwa rhoqo emva kweenyanga ezintandathu ngomhla wamashumi amathathu ananye";
+const kTeststr_xx_Bugi = "ᨄᨛᨑᨊᨒ ᨑᨗ ᨔᨒᨗᨓᨛ ᨕᨗᨋᨗᨔᨗ ᨒᨛᨄ ᨑᨛᨔᨛᨆᨗᨊ";
+const kTeststr_xx_Goth = "𐌰 𐌰𐌱𐍂𐌰𐌷𐌰𐌼 𐌰𐌲𐌲𐌹𐌻𐌹𐍃𐌺𐍃 𐌸𐌹𐌿𐌳𐌹𐍃𐌺𐍃 𐍆𐍂𐌰𐌲𐌺𐌹𐍃𐌺𐍃";
+const kTeststr_yi_Hebr = "און פאנטאזיע ער איז באקאנט צים מערסטן פאר זיינע באַלאַדעס ער האָט געוווינט אין ווארשע יעס פאריס ליווערפול און לאנדאן סוף כל סוף איז ער";
+const kTeststr_yo_Latn = " abinibi han ikawe alantakun le ni opolopo ede abinibi ti a to lesese bi eniyan to fe lo se fe lati se atunse jowo mo pe awon oju iwe itakunagbaye miran ti ako ni oniruru ede abinibi le faragba nipa atunse ninu se iwadi blogs ni ori itakun agbaye ti e ba";
+const kTeststr_za_Hani = " 两个宾语的字数较少时 只带一个动词 否则就带两个动词 三句子类 从句子方面去谈汉 壮语结构格式相异的类型的 叫句子类 汉 壮语中 句子类结构格式有差别的自然不少";
+const kTeststr_za_Latn = " dih yinzminz ndaej daengz bujbienq youjyau dih cingzyin caeuq cinhingz diuz daihit boux boux ma daengz lajmbwn couh miz cwyouz cinhyenz caeuq genzli bouxboux bingzdaengj gyoengq vunz miz lijsing caeuq liengzsim wngdang daih gyoengq de lumj beixnuengx";
+const kTeststr_zh_Hans = "产品的简报和公告 提交该申请后无法进行更改 请确认您的选择是正确的 对于要提交的图书 我确认 我是版权所有者或已得到版权所有者的授权 要更改您的国家 地区 请在此表的最上端更改您的";
+const kTeststr_zh_Hant = " 之前為 帳單交易作業區 已變更 廣告內容 之前為 銷售代表 之前為 張貼日期為 百分比之前為 合約 為 目標對象條件已刪除 結束日期之前為";
+const kTeststr_zu_Latn = " ana engu uma inkinga iqhubeka siza ubike kwi isexwayiso ngenxa yephutha lomlekeleli sikwazi ukubuyisela emuva kuphela imiphumela engaqediwe ukuthola imiphumela eqediwe zama ukulayisha kabusha leli khasi emizuzwini engu uma inkinga iqhubeka siza uthumele";
+const kTeststr_zzb_Latn = "becoose a ve a leemit qooereees tu vurds um gesh dee bork bork nu peges vere a fuoond cunteeening is a fery cummun vurd und ves nut inclooded in yuoor seerch zee ooperetur is unnecessery ve a incloode a ell seerch terms by deffoolt um de hur de hur de hur";
+const kTeststr_zze_Latn = " a diffewent type of seawch send feedback about google wiwewess seawch to wap google com wesuwts found on de entiwe web fow wesuwts found on de mobiwe web fow de functionawity of de toolbar up button has been expanded swightwy it now considews fow exampwe";
+const kTeststr_zzh_Latn = " b x z un b e t und rs n a dr ss p as ry an th r a dr ss ry us n a l ss mb gu us c ti n l ke a z p c d n a dr ss nt r d pl as en r n a dr ss y ur s ar h f r n ar d d n t m tch ny l c ti n w th n m l s nd m r r at d p g s th l c ti ns b l w w r ut m t ca y";
+const kTeststr_zzp_Latn = " away ackupbay editcray ardcay ybay isitingvay ouryay illingbay eferencespray agepay orway isitvay ethay adwordsway elphay entrecay orfay oremay etailsday adwordsway ooglegay omcay upportsay";
+
+// Two very close Wikipedia page beginnings
+const kTeststr_ms_close = "sukiyaki wikipedia bahasa melayu ensiklopedia bebas sukiyaki dari wikipedia bahasa melayu ensiklopedia bebas lompat ke navigasi gelintar sukiyaki sukiyaki hirisan tipis daging lembu sayur sayuran dan tauhu di dalam periuk besi yang dimasak di atas meja makan dengan cara rebusan sukiyaki dimakan dengan mence";
+const kTeststr_id_close = "sukiyaki wikipedia indonesia ensiklopedia bebas berbahasa bebas berbahasa indonesia langsung ke navigasi cari untuk pengertian lain dari sukiyaki lihat sukiyaki irisan tipis daging sapi sayur sayuran dan tahu di dalam panci besi yang dimasak di atas meja makan dengan cara direbus sukiyaki dimakan dengan mence";
+
+// Simple intermixed French/English text
+const kTeststr_fr_en_Latn = "France is the largest country in Western Europe and the third-largest in Europe as a whole. " +
+ "A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter " +
+ "Cet article concerne le pays européen aujourd’hui appelé République française. Pour d’autres usages du nom France, " +
+ "Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus." +
+ "Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumped over the lazy dog";
+
+// This can be used to cross-check the build date of the main quadgram table
+const kTeststr_version = "qpdbmrmxyzptlkuuddlrlrbas las les qpdbmrmxyzptlkuuddlrlrbas el la qpdbmrmxyzptlkuuddlrlrbas";
+
+const kTestPairs = [
+// A simple case to begin
+ ["en", "ENGLISH", kTeststr_en],
+
+// 20 languages recognized via Unicode script
+ ["hy", "ARMENIAN", kTeststr_hy_Armn],
+ ["chr", "CHEROKEE", kTeststr_chr_Cher],
+ ["dv", "DHIVEHI", kTeststr_dv_Thaa],
+ ["ka", "GEORGIAN", kTeststr_ka_Geor],
+ ["el", "GREEK", kTeststr_el_Grek],
+ ["gu", "GUJARATI", kTeststr_gu_Gujr],
+ ["iu", "INUKTITUT", kTeststr_iu_Cans],
+ ["kn", "KANNADA", kTeststr_kn_Knda],
+ ["km", "KHMER", kTeststr_km_Khmr],
+ ["lo", "LAOTHIAN", kTeststr_lo_Laoo],
+ ["lif", "LIMBU", kTeststr_lif_Limb],
+ ["ml", "MALAYALAM", kTeststr_ml_Mlym],
+ ["or", "ORIYA", kTeststr_or_Orya],
+ ["pa", "PUNJABI", kTeststr_pa_Guru],
+ ["si", "SINHALESE", kTeststr_si_Sinh],
+ ["syr", "SYRIAC", kTeststr_syr_Syrc],
+ ["tl", "TAGALOG", kTeststr_tl_Tglg], // Also in quadgram list below
+ ["ta", "TAMIL", kTeststr_ta_Taml],
+ ["te", "TELUGU", kTeststr_te_Telu],
+ ["th", "THAI", kTeststr_th_Thai],
+
+// 4 languages regognized via single letters
+ ["zh", "CHINESE", kTeststr_zh_Hans],
+ ["zh-Hant", "CHINESET", kTeststr_zh_Hant],
+ ["ja", "JAPANESE", kTeststr_ja_Hani],
+ ["ko", "KOREAN", kTeststr_ko_Hani],
+
+// 60 languages recognized via combinations of four letters
+ ["af", "AFRIKAANS", kTeststr_af_Latn],
+ ["sq", "ALBANIAN", kTeststr_sq_Latn],
+ ["ar", "ARABIC", kTeststr_ar_Arab],
+ ["az", "AZERBAIJANI", kTeststr_az_Latn],
+ ["eu", "BASQUE", kTeststr_eu_Latn],
+ ["be", "BELARUSIAN", kTeststr_be_Cyrl],
+ ["bn", "BENGALI", kTeststr_bn_Beng], // No Assamese in subset
+ ["bh", "BIHARI", kTeststr_bh_Deva],
+ ["bg", "BULGARIAN", kTeststr_bg_Cyrl],
+ ["ca", "CATALAN", kTeststr_ca_Latn],
+ ["ceb", "CEBUANO", kTeststr_ceb_Latn],
+ ["hr", "CROATIAN", kTeststr_hr_Latn, [false, 0, "el", 4]],
+ ["cs", "CZECH", kTeststr_cs_Latn],
+ ["da", "DANISH", kTeststr_da_Latn],
+ ["nl", "DUTCH", kTeststr_nl_Latn],
+ ["en", "ENGLISH", kTeststr_en_Latn],
+ ["et", "ESTONIAN", kTeststr_et_Latn],
+ ["fi", "FINNISH", kTeststr_fi_Latn],
+ ["fr", "FRENCH", kTeststr_fr_Latn],
+ ["gl", "GALICIAN", kTeststr_gl_Latn],
+ ["lg", "GANDA", kTeststr_lg_Latn],
+ ["de", "GERMAN", kTeststr_de_Latn],
+ ["ht", "HAITIAN_CREOLE", kTeststr_ht_Latn],
+ ["he", "HEBREW", kTeststr_he_Hebr],
+ ["hi", "HINDI", kTeststr_hi_Deva],
+ ["hmn", "HMONG", kTeststr_blu_Latn],
+ ["hu", "HUNGARIAN", kTeststr_hu_Latn],
+ ["is", "ICELANDIC", kTeststr_is_Latn],
+ ["id", "INDONESIAN", kTeststr_id_Latn],
+ ["ga", "IRISH", kTeststr_ga_Latn],
+ ["it", "ITALIAN", kTeststr_it_Latn],
+ ["jw", "JAVANESE", kTeststr_jw_Latn],
+ ["rw", "KINYARWANDA", kTeststr_rw_Latn],
+ ["lv", "LATVIAN", kTeststr_lv_Latn],
+ ["lt", "LITHUANIAN", kTeststr_lt_Latn],
+ ["mk", "MACEDONIAN", kTeststr_mk_Cyrl],
+ ["ms", "MALAY", kTeststr_ms_Latn],
+ ["mt", "MALTESE", kTeststr_mt_Latn],
+ ["mr", "MARATHI", kTeststr_mr_Deva, [false, 0, "te", 3]],
+ ["ne", "NEPALI", kTeststr_ne_Deva],
+ ["no", "NORWEGIAN", kTeststr_no_Latn],
+ ["fa", "PERSIAN", kTeststr_fa_Arab],
+ ["pl", "POLISH", kTeststr_pl_Latn],
+ ["pt", "PORTUGUESE", kTeststr_pt_Latn],
+ ["ro", "ROMANIAN", kTeststr_ro_Latn],
+ ["ro", "ROMANIAN", kTeststr_ro_Cyrl],
+ ["ru", "RUSSIAN", kTeststr_ru_Cyrl],
+ ["gd", "SCOTS_GAELIC", kTeststr_gd_Latn],
+ ["sr", "SERBIAN", kTeststr_sr_Cyrl],
+ ["sr", "SERBIAN", kTeststr_sr_Latn],
+ ["sk", "SLOVAK", kTeststr_sk_Latn],
+ ["sl", "SLOVENIAN", kTeststr_sl_Latn],
+ ["es", "SPANISH", kTeststr_es_Latn],
+ ["sw", "SWAHILI", kTeststr_sw_Latn],
+ ["sv", "SWEDISH", kTeststr_sv_Latn],
+ ["tl", "TAGALOG", kTeststr_tl_Latn],
+ ["tr", "TURKISH", kTeststr_tr_Latn],
+ ["uk", "UKRAINIAN", kTeststr_uk_Cyrl],
+ ["ur", "URDU", kTeststr_ur_Arab],
+ ["vi", "VIETNAMESE", kTeststr_vi_Latn],
+ ["cy", "WELSH", kTeststr_cy_Latn],
+ ["yi", "YIDDISH", kTeststr_yi_Hebr],
+
+ // Added 2013.08.31 so-Latn ig-Latn ha-Latn yo-Latn zu-Latn
+ ["so", "SOMALI", kTeststr_so_Latn],
+ ["ig", "IGBO", kTeststr_ig_Latn],
+ ["ha", "HAUSA", kTeststr_ha_Latn],
+ ["yo", "YORUBA", kTeststr_yo_Latn],
+ ["zu", "ZULU", kTeststr_zu_Latn],
+ // Added 2014.01.22 bs-Latn
+ ["bs", "BOSNIAN", kTeststr_bs_Latn],
+
+// 2 statistically-close languages
+ ["id", "INDONESIAN", kTeststr_id_close, [true, 80], []],
+ ["ms", "MALAY", kTeststr_ms_close],
+
+// Simple intermixed French/English text
+ ["fr", "FRENCH", kTeststr_fr_en_Latn, [false, 80, "en", 32]],
+
+// Cross-check the main quadgram table build date
+// Change the expected language each time it is rebuilt
+ ["az", "AZERBAIJANI", kTeststr_version] // 2014.01.31
+];
+
+Components.utils.import("resource://gre/modules/Timer.jsm");
+let detectorModule = Components.utils.import("resource:///modules/translation/LanguageDetector.jsm");
+
+function check_result(result, langCode, expected) {
+ equal(result.language, langCode, "Expected language code");
+
+ // Round percentage up to the nearest 5%, since most strings are
+ // detected at slightly less than 100%, and we don't want to
+ // encode each exact value.
+ let percent = result.languages[0].percent;
+ percent = Math.ceil(percent / 20) * 20;
+
+ equal(result.languages[0].languageCode, langCode, "Expected first guess language code");
+ equal(percent, expected[1] || 100, "Expected first guess language percent");
+
+ if (expected.length < 3) {
+ // We're not expecting a second language.
+ equal(result.languages.length, 1, "Expected only one language result");
+ } else {
+ equal(result.languages.length, 2, "Expected two language results");
+
+ equal(result.languages[1].languageCode, expected[2], "Expected second guess language code");
+ equal(result.languages[1].percent, expected[3], "Expected second guess language percent");
+ }
+
+ equal(result.confident, !expected[0], "Expected confidence");
+}
+
+add_task(function* test_pairs() {
+ for (let item of kTestPairs) {
+ let params = [item[2],
+ { text: item[2], tld: "com", language: item[0], encoding: "utf-8" }]
+
+ for (let [i, param] of params.entries()) {
+ // For test items with different expected results when using the
+ // language hint, use those for the hinted version of the API.
+ // Otherwise, fall back to the first set of expected values.
+ let expected = item[3 + i] || item[3] || [];
+
+ let result = yield LanguageDetector.detectLanguage(param);
+ check_result(result, item[0], expected);
+ }
+ }
+});
+
+// Test that the worker is flushed shortly after processing a large
+// string.
+add_task(function* test_worker_flush() {
+ let test_string = kTeststr_fr_en_Latn;
+ let test_item = kTestPairs.find(item => item[2] == test_string);
+
+ // Set shorter timeouts and lower string lengths to make things easier
+ // on the test infrastructure.
+ detectorModule.LARGE_STRING = test_string.length - 1;
+ detectorModule.IDLE_TIMEOUT = 1000;
+
+ equal(detectorModule.workerManager._idleTimeout, null,
+ "Should have no idle timeout to start with");
+
+ let result = yield LanguageDetector.detectLanguage(test_string);
+
+ // Make sure the results are still correct.
+ check_result(result, test_item[0], test_item[3]);
+
+ // We should have an idle timeout after processing the string.
+ ok(detectorModule.workerManager._idleTimeout != null,
+ "Should have an idle timeout");
+ ok(detectorModule.workerManager._worker != null,
+ "Should have a worker instance");
+ ok(detectorModule.workerManager._workerReadyPromise != null,
+ "Should have a worker promise");
+
+ // Wait for the idle timeout to elapse.
+ yield new Promise(resolve => setTimeout(resolve, detectorModule.IDLE_TIMEOUT));
+
+ equal(detectorModule.workerManager._idleTimeout, null,
+ "Should have no idle timeout after it has elapsed");
+ equal(detectorModule.workerManager._worker, null,
+ "Should have no worker instance after idle timeout");
+ equal(detectorModule.workerManager._workerReadyPromise, null,
+ "Should have no worker promise after idle timeout");
+
+ // We should still be able to use the language detector after its
+ // worker has been flushed.
+ result = yield LanguageDetector.detectLanguage(test_string);
+
+ // Make sure the results are still correct.
+ check_result(result, test_item[0], test_item[3]);
+});
diff --git a/browser/components/translation/test/unit/xpcshell.ini b/browser/components/translation/test/unit/xpcshell.ini
new file mode 100644
index 000000000..8431a8c4e
--- /dev/null
+++ b/browser/components/translation/test/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head =
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_cld2.js]
diff --git a/browser/components/translation/test/yandex.sjs b/browser/components/translation/test/yandex.sjs
new file mode 100644
index 000000000..ed515beab
--- /dev/null
+++ b/browser/components/translation/test/yandex.sjs
@@ -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";
+
+const {classes: Cc, interfaces: Ci, Constructor: CC} = Components;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(req, res) {
+ try {
+ reallyHandleRequest(req, res);
+ } catch (ex) {
+ res.setStatusLine("1.0", 200, "AlmostOK");
+ let msg = "Error handling request: " + ex + "\n" + ex.stack;
+ log(msg);
+ res.write(msg);
+ }
+}
+
+function log(msg) {
+ dump("YANDEX-SERVER-MOCK: " + msg + "\n");
+}
+
+const statusCodes = {
+ 400: "Bad Request",
+ 401: "Invalid API key",
+ 402: "This API key has been blocked",
+ 403: "Daily limit for requests reached",
+ 404: "Daily limit for chars reached",
+ 413: "The text size exceeds the maximum",
+ 422: "The text could not be translated",
+ 500: "Internal Server Error",
+ 501: "The specified translation direction is not supported",
+ 503: "Service Unavailable"
+};
+
+function HTTPError(code = 500, message) {
+ this.code = code;
+ this.name = statusCodes[code] || "HTTPError";
+ this.message = message || this.name;
+}
+HTTPError.prototype = new Error();
+HTTPError.prototype.constructor = HTTPError;
+
+function sendError(res, err) {
+ if (!(err instanceof HTTPError)) {
+ err = new HTTPError(typeof err == "number" ? err : 500,
+ err.message || typeof err == "string" ? err : "");
+ }
+ res.setStatusLine("1.1", err.code, err.name);
+ res.write(err.message);
+}
+
+// Based on the code borrowed from:
+// http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
+function parseQuery(query) {
+ let match,
+ params = {},
+ pl = /\+/g,
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); };
+
+ while (match = search.exec(query)) {
+ let k = decode(match[1]),
+ v = decode(match[2]);
+ if (k in params) {
+ if(params[k] instanceof Array)
+ params[k].push(v);
+ else
+ params[k] = [params[k], v];
+ } else {
+ params[k] = v;
+ }
+ }
+
+ return params;
+}
+
+function sha1(str) {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ // `result` is an out parameter, `result.value` will contain the array length.
+ let result = {};
+ // `data` is an array of bytes.
+ let data = converter.convertToByteArray(str, result);
+ let ch = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ ch.init(ch.SHA1);
+ ch.update(data, data.length);
+ let hash = ch.finish(false);
+
+ // Return the two-digit hexadecimal code for a byte.
+ function toHexString(charCode) {
+ return ("0" + charCode.toString(16)).slice(-2);
+ }
+
+ // Convert the binary hash data to a hex string.
+ return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+}
+
+function getRequestBody(req) {
+ let avail;
+ let bytes = [];
+ let body = new BinaryInputStream(req.bodyInputStream);
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ return String.fromCharCode.apply(null, bytes);
+}
+
+function getInputStream(path) {
+ let file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("CurWorkD", Ci.nsILocalFile);
+ for (let part of path.split("/"))
+ file.append(part);
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, 1, 0, false);
+ return fileStream;
+}
+
+/**
+ * Yandex Requests have to be signed with an API Key. This mock server
+ * supports the following keys:
+ *
+ * yandexValidKey Always passes the authentication,
+ * yandexInvalidKey Never passes authentication and fails with 401 code,
+ * yandexBlockedKey Never passes authentication and fails with 402 code,
+ * yandexOutOfRequestsKey Never passes authentication and fails with 403 code,
+ * yandexOutOfCharsKey Never passes authentication and fails with 404 code.
+ *
+ * If any other key is used the server reponds with 401 error code.
+ */
+function checkAuth(params) {
+ if(!("key" in params))
+ throw new HTTPError(400);
+
+ let key = params.key;
+ if(key === "yandexValidKey")
+ return true;
+
+ let invalidKeys = {
+ "yandexInvalidKey" : 401,
+ "yandexBlockedKey" : 402,
+ "yandexOutOfRequestsKey" : 403,
+ "yandexOutOfCharsKey" : 404,
+ };
+
+ if(key in invalidKeys)
+ throw new HTTPError(invalidKeys[key]);
+
+ throw new HTTPError(401);
+}
+
+function reallyHandleRequest(req, res) {
+
+ try {
+
+ // Preparing the query parameters.
+ let params = {};
+ if(req.method == 'POST') {
+ params = parseQuery(getRequestBody(req));
+ }
+
+ // Extracting the API key and attempting to authenticate the request.
+ log(JSON.stringify(params));
+
+ checkAuth(params);
+ methodHandlers['translate'](res, params);
+
+ } catch (ex) {
+ sendError(res, ex, ex.code);
+ }
+
+}
+
+const methodHandlers = {
+ translate: function(res, params) {
+ res.setStatusLine("1.1", 200, "OK");
+ res.setHeader("Content-Type", "application/json");
+
+ let hash = sha1(JSON.stringify(params)).substr(0, 10);
+ log("SHA1 hash of content: " + hash);
+
+ let fixture = "browser/browser/components/translation/test/fixtures/result-yandex-" + hash + ".json";
+ log("PATH: " + fixture);
+
+ let inputStream = getInputStream(fixture);
+ res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
+ inputStream.close();
+ }
+
+};
diff --git a/browser/components/translation/translation-infobar.xml b/browser/components/translation/translation-infobar.xml
new file mode 100644
index 000000000..db0695c03
--- /dev/null
+++ b/browser/components/translation/translation-infobar.xml
@@ -0,0 +1,441 @@
+<?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;
+<!ENTITY % translationDTD SYSTEM "chrome://browser/locale/translation.dtd" >
+%translationDTD;
+]>
+
+<bindings id="translationBindings"
+ 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="translationbar" extends="chrome://global/content/bindings/notification.xml#notification" role="xul:alert">
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <content>
+ <xul:hbox class="notification-inner" flex="1" xbl:inherits="type">
+ <xul:hbox anonid="details" align="center" flex="1">
+ <xul:image class="translate-infobar-element messageImage"
+ anonid="messageImage"/>
+ <xul:panel anonid="welcomePanel" class="translation-welcome-panel"
+ type="arrow" align="start">
+ <xul:image class="translation-welcome-logo"/>
+ <xul:vbox flex="1" class="translation-welcome-content">
+ <xul:description class="translation-welcome-headline"
+ anonid="welcomeHeadline"/>
+ <xul:description class="translation-welcome-body" anonid="welcomeBody"/>
+ <xul:hbox align="center">
+ <xul:label anonid="learnMore" class="plain text-link"
+ onclick="openUILinkIn('https://support.mozilla.org/kb/automatic-translation', 'tab'); this.parentNode.parentNode.parentNode.hidePopup();"/>
+ <xul:spacer flex="1"/>
+ <xul:button class="translate-infobar-element" anonid="thanksButton"
+ onclick="this.parentNode.parentNode.parentNode.hidePopup();"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:panel>
+ <xul:deck anonid="translationStates" selectedIndex="0">
+
+ <!-- offer to translate -->
+ <xul:hbox class="translate-offer-box" align="center">
+ <xul:label class="translate-infobar-element" value="&translation.thisPageIsIn.label;"/>
+ <xul:menulist class="translate-infobar-element" anonid="detectedLanguage">
+ <xul:menupopup/>
+ </xul:menulist>
+ <xul:label class="translate-infobar-element" value="&translation.translateThisPage.label;"/>
+ <xul:button class="translate-infobar-element"
+ label="&translation.translate.button;"
+ anonid="translate"
+ oncommand="document.getBindingParent(this).translate();"/>
+ <xul:button class="translate-infobar-element"
+ label="&translation.notNow.button;" anonid="notNow"
+ oncommand="document.getBindingParent(this).closeCommand();"/>
+ </xul:hbox>
+
+ <!-- translating -->
+ <xul:vbox class="translating-box" pack="center">
+ <xul:label class="translate-infobar-element"
+ value="&translation.translatingContent.label;"/>
+ </xul:vbox>
+
+ <!-- translated -->
+ <xul:hbox class="translated-box" align="center">
+ <xul:label class="translate-infobar-element"
+ value="&translation.translatedFrom.label;"/>
+ <xul:menulist class="translate-infobar-element"
+ anonid="fromLanguage"
+ oncommand="document.getBindingParent(this).translate()">
+ <xul:menupopup/>
+ </xul:menulist>
+ <xul:label class="translate-infobar-element"
+ value="&translation.translatedTo.label;"/>
+ <xul:menulist class="translate-infobar-element"
+ anonid="toLanguage"
+ oncommand="document.getBindingParent(this).translate()">
+ <xul:menupopup/>
+ </xul:menulist>
+ <xul:label class="translate-infobar-element"
+ value="&translation.translatedToSuffix.label;"/>
+ <xul:button anonid="showOriginal"
+ class="translate-infobar-element"
+ label="&translation.showOriginal.button;"
+ oncommand="document.getBindingParent(this).showOriginal();"/>
+ <xul:button anonid="showTranslation"
+ class="translate-infobar-element"
+ label="&translation.showTranslation.button;"
+ oncommand="document.getBindingParent(this).showTranslation();"/>
+ </xul:hbox>
+
+ <!-- error -->
+ <xul:hbox class="translation-error" align="center">
+ <xul:label class="translate-infobar-element"
+ value="&translation.errorTranslating.label;"/>
+ <xul:button class="translate-infobar-element"
+ label="&translation.tryAgain.button;"
+ anonid="tryAgain"
+ oncommand="document.getBindingParent(this).translate();"/>
+ </xul:hbox>
+
+ <!-- unavailable -->
+ <xul:vbox class="translation-unavailable" pack="center">
+ <xul:label class="translate-infobar-element"
+ value="&translation.serviceUnavailable.label;"/>
+ </xul:vbox>
+
+ </xul:deck>
+ <xul:spacer flex="1"/>
+
+ <xul:button type="menu"
+ class="translate-infobar-element options-menu-button"
+ anonid="options"
+ label="&translation.options.menu;">
+ <xul:menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
+ cui-widget-panelWithFooter PanelUI-subView"
+ onpopupshowing="document.getBindingParent(this).optionsShowing();">
+ <xul:menuitem anonid="neverForLanguage"
+ oncommand="document.getBindingParent(this).neverForLanguage();"/>
+ <xul:menuitem anonid="neverForSite"
+ oncommand="document.getBindingParent(this).neverForSite();"
+ label="&translation.options.neverForSite.label;"
+ accesskey="&translation.options.neverForSite.accesskey;"/>
+ <xul:menuseparator/>
+ <xul:menuitem oncommand="openPreferences('paneContent');"
+ label="&translation.options.preferences.label;"
+ accesskey="&translation.options.preferences.accesskey;"/>
+ <xul:menuitem class="subviewbutton panel-subview-footer"
+ oncommand="document.getBindingParent(this).openProviderAttribution();">
+ <xul:deck anonid="translationEngine" selectedIndex="0">
+ <xul:hbox class="translation-attribution">
+ <xul:label>&translation.options.attribution.beforeLogo;</xul:label>
+ <xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
+ aria-label="Microsoft Translator"/>
+ <xul:label>&translation.options.attribution.afterLogo;</xul:label>
+ </xul:hbox>
+ <xul:label class="translation-attribution">&translation.options.attribution.yandexTranslate;</xul:label>
+ </xul:deck>
+ </xul:menuitem>
+ </xul:menupopup>
+ </xul:button>
+
+ </xul:hbox>
+ <xul:toolbarbutton ondblclick="event.stopPropagation();"
+ anonid="closeButton"
+ class="messageCloseButton close-icon tabbable"
+ xbl:inherits="hidden=hideclose"
+ tooltiptext="&closeNotification.tooltip;"
+ oncommand="document.getBindingParent(this).closeCommand();"/>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <property name="state"
+ onget="return this._getAnonElt('translationStates').selectedIndex;">
+ <setter>
+ <![CDATA[
+ let deck = this._getAnonElt('translationStates');
+
+ let activeElt = document.activeElement;
+ if (activeElt && deck.contains(activeElt))
+ activeElt.blur();
+
+ let stateName;
+ for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
+ if (Translation["STATE_" + name] == val) {
+ stateName = name.toLowerCase();
+ break;
+ }
+ }
+ this.setAttribute("state", stateName);
+
+ if (val == Translation.STATE_TRANSLATED)
+ this._handleButtonHiding();
+
+ deck.selectedIndex = val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="init">
+ <parameter name="aTranslation"/>
+ <body>
+ <![CDATA[
+ this.translation = aTranslation;
+ let bundle = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://global/locale/languageNames.properties");
+ let sortByLocalizedName = function(aList) {
+ return aList.map(code => [code, bundle.GetStringFromName(code)])
+ .sort((a, b) => a[1].localeCompare(b[1]));
+ };
+
+ // Fill the lists of supported source languages.
+ let detectedLanguage = this._getAnonElt("detectedLanguage");
+ let fromLanguage = this._getAnonElt("fromLanguage");
+ let sourceLanguages =
+ sortByLocalizedName(Translation.supportedSourceLanguages);
+ for (let [code, name] of sourceLanguages) {
+ detectedLanguage.appendItem(name, code);
+ fromLanguage.appendItem(name, code);
+ }
+ detectedLanguage.value = this.translation.detectedLanguage;
+
+ // translatedFrom is only set if we have already translated this page.
+ if (aTranslation.translatedFrom)
+ fromLanguage.value = aTranslation.translatedFrom;
+
+ // Fill the list of supported target languages.
+ let toLanguage = this._getAnonElt("toLanguage");
+ let targetLanguages =
+ sortByLocalizedName(Translation.supportedTargetLanguages);
+ for (let [code, name] of targetLanguages)
+ toLanguage.appendItem(name, code);
+
+ if (aTranslation.translatedTo)
+ toLanguage.value = aTranslation.translatedTo;
+
+ if (aTranslation.state)
+ this.state = aTranslation.state;
+
+ // Show attribution for the preferred translator.
+ let engineIndex = Object.keys(Translation.supportedEngines)
+ .indexOf(Translation.translationEngine);
+ if (engineIndex != -1) {
+ this._getAnonElt('translationEngine').selectedIndex = engineIndex;
+ }
+
+ const kWelcomePref = "browser.translation.ui.welcomeMessageShown";
+ if (Services.prefs.prefHasUserValue(kWelcomePref) ||
+ this.translation.browser != gBrowser.selectedBrowser)
+ return;
+
+ this.addEventListener("transitionend", function onShown() {
+ this.removeEventListener("transitionend", onShown);
+
+ // These strings are hardcoded because they need to reach beta
+ // without riding the trains.
+ let localizedStrings = {
+ en: ["Hey look! It's something new!",
+ "Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!",
+ "Learn more.",
+ "Thanks"],
+ "es-AR": ["\xA1Mir\xE1! \xA1Hay algo nuevo!",
+ "Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!",
+ "Conoc\xE9 m\xE1s.",
+ "Gracias"],
+ "es-ES": ["\xA1Mira! \xA1Hay algo nuevo!",
+ "Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!",
+ "M\xE1s informaci\xF3n.",
+ "Gracias"],
+ pl: ["Sp\xF3jrz tutaj! To co\u015B nowego!",
+ "Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!",
+ "Dowiedz si\u0119 wi\u0119cej",
+ "Dzi\u0119kuj\u0119"],
+ tr: ["Bak\u0131n, burada yeni bir \u015Fey var!",
+ "Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!",
+ "Daha fazla bilgi al\u0131n.",
+ "Te\u015Fekk\xFCrler"],
+ vi: ["Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!",
+ "Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang. Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!",
+ "T\xECm hi\u1EC3u th\xEAm.",
+ "C\u1EA3m \u01A1n"]
+ };
+
+ let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("browser");
+ if (!(locale in localizedStrings))
+ locale = "en";
+ let strings = localizedStrings[locale];
+
+ this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]);
+ this._getAnonElt("welcomeBody").textContent = strings[1];
+ this._getAnonElt("learnMore").setAttribute("value", strings[2]);
+ this._getAnonElt("thanksButton").setAttribute("label", strings[3]);
+
+ let panel = this._getAnonElt("welcomePanel");
+ panel.openPopup(this._getAnonElt("messageImage"),
+ "bottomcenter topleft");
+
+ Services.prefs.setBoolPref(kWelcomePref, true);
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getAnonElt">
+ <parameter name="aAnonId"/>
+ <body>
+ return document.getAnonymousElementByAttribute(this, "anonid", aAnonId);
+ </body>
+ </method>
+
+ <method name="translate">
+ <body>
+ <![CDATA[
+ if (this.state == Translation.STATE_OFFER) {
+ this._getAnonElt("fromLanguage").value =
+ this._getAnonElt("detectedLanguage").value;
+ this._getAnonElt("toLanguage").value =
+ Translation.defaultTargetLanguage;
+ }
+
+ this.translation.translate(this._getAnonElt("fromLanguage").value,
+ this._getAnonElt("toLanguage").value);
+ ]]>
+ </body>
+ </method>
+
+ <!-- To be called when the infobar should be closed per user's wish (e.g.
+ by clicking the notification's close button -->
+ <method name="closeCommand">
+ <body>
+ <![CDATA[
+ this.close();
+ this.translation.infobarClosed();
+ ]]>
+ </body>
+ </method>
+ <method name="_handleButtonHiding">
+ <body>
+ <![CDATA[
+ let originalShown = this.translation.originalShown;
+ this._getAnonElt("showOriginal").hidden = originalShown;
+ this._getAnonElt("showTranslation").hidden = !originalShown;
+ ]]>
+ </body>
+ </method>
+
+ <method name="showOriginal">
+ <body>
+ <![CDATA[
+ this.translation.showOriginalContent();
+ this._handleButtonHiding();
+ ]]>
+ </body>
+ </method>
+
+ <method name="showTranslation">
+ <body>
+ <![CDATA[
+ this.translation.showTranslatedContent();
+ this._handleButtonHiding();
+ ]]>
+ </body>
+ </method>
+
+ <method name="optionsShowing">
+ <body>
+ <![CDATA[
+ // Get the source language name.
+ let lang;
+ if (this.state == Translation.STATE_OFFER)
+ lang = this._getAnonElt("detectedLanguage").value;
+ else {
+ lang = this._getAnonElt("fromLanguage").value;
+
+ // If we have never attempted to translate the page before the
+ // service became unavailable, "fromLanguage" isn't set.
+ if (!lang && this.state == Translation.STATE_UNAVAILABLE)
+ lang = this.translation.detectedLanguage;
+ }
+
+ let langBundle =
+ Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://global/locale/languageNames.properties");
+ let langName = langBundle.GetStringFromName(lang);
+
+ // Set the label and accesskey on the menuitem.
+ let bundle =
+ Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://browser/locale/translation.properties");
+ let item = this._getAnonElt("neverForLanguage");
+ const kStrId = "translation.options.neverForLanguage";
+ item.setAttribute("label",
+ bundle.formatStringFromName(kStrId + ".label",
+ [langName], 1));
+ item.setAttribute("accesskey",
+ bundle.GetStringFromName(kStrId + ".accesskey"));
+ item.langCode = lang;
+
+ // We may need to disable the menuitems if they have already been used.
+ // Check if translation is already disabled for this language:
+ let neverForLangs =
+ Services.prefs.getCharPref("browser.translation.neverForLanguages");
+ item.disabled = neverForLangs.split(",").indexOf(lang) != -1;
+
+ // Check if translation is disabled for the domain:
+ let uri = this.translation.browser.currentURI;
+ let perms = Services.perms;
+ item = this._getAnonElt("neverForSite");
+ item.disabled =
+ perms.testExactPermission(uri, "translate") == perms.DENY_ACTION;
+ ]]>
+ </body>
+ </method>
+
+ <method name="neverForLanguage">
+ <body>
+ <![CDATA[
+ const kPrefName = "browser.translation.neverForLanguages";
+
+ let val = Services.prefs.getCharPref(kPrefName);
+ if (val)
+ val += ",";
+ val += this._getAnonElt("neverForLanguage").langCode;
+
+ Services.prefs.setCharPref(kPrefName, val);
+
+ this.closeCommand();
+ ]]>
+ </body>
+ </method>
+
+ <method name="neverForSite">
+ <body>
+ <![CDATA[
+ let uri = this.translation.browser.currentURI;
+ let perms = Services.perms;
+ perms.add(uri, "translate", perms.DENY_ACTION);
+
+ this.closeCommand();
+ ]]>
+ </body>
+ </method>
+
+ <method name="openProviderAttribution">
+ <body>
+ <![CDATA[
+ Translation.openProviderAttribution();
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+</bindings>
diff --git a/browser/components/uitour/UITour-lib.js b/browser/components/uitour/UITour-lib.js
new file mode 100644
index 000000000..7fe820185
--- /dev/null
+++ b/browser/components/uitour/UITour-lib.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/. */
+
+// create namespace
+if (typeof Mozilla == 'undefined') {
+ var Mozilla = {};
+}
+
+(function($) {
+ 'use strict';
+
+ // create namespace
+ if (typeof Mozilla.UITour == 'undefined') {
+ Mozilla.UITour = {};
+ }
+
+ var themeIntervalId = null;
+ function _stopCyclingThemes() {
+ if (themeIntervalId) {
+ clearInterval(themeIntervalId);
+ themeIntervalId = null;
+ }
+ }
+
+ function _sendEvent(action, data) {
+ var event = new CustomEvent('mozUITour', {
+ bubbles: true,
+ detail: {
+ action: action,
+ data: data || {}
+ }
+ });
+
+ document.dispatchEvent(event);
+ }
+
+ function _generateCallbackID() {
+ return Math.random().toString(36).replace(/[^a-z]+/g, '');
+ }
+
+ function _waitForCallback(callback) {
+ var id = _generateCallbackID();
+
+ function listener(event) {
+ if (typeof event.detail != 'object')
+ return;
+ if (event.detail.callbackID != id)
+ return;
+
+ document.removeEventListener('mozUITourResponse', listener);
+ callback(event.detail.data);
+ }
+ document.addEventListener('mozUITourResponse', listener);
+
+ return id;
+ }
+
+ var notificationListener = null;
+ function _notificationListener(event) {
+ if (typeof event.detail != 'object')
+ return;
+ if (typeof notificationListener != 'function')
+ return;
+
+ notificationListener(event.detail.event, event.detail.params);
+ }
+
+ Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY = 10 * 1000;
+
+ Mozilla.UITour.CONFIGNAME_SYNC = 'sync';
+ Mozilla.UITour.CONFIGNAME_AVAILABLETARGETS = 'availableTargets';
+
+ Mozilla.UITour.ping = function(callback) {
+ var data = {};
+ if (callback) {
+ data.callbackID = _waitForCallback(callback);
+ }
+ _sendEvent('ping', data);
+ };
+
+ Mozilla.UITour.observe = function(listener, callback) {
+ notificationListener = listener;
+
+ if (listener) {
+ document.addEventListener('mozUITourNotification',
+ _notificationListener);
+ Mozilla.UITour.ping(callback);
+ } else {
+ document.removeEventListener('mozUITourNotification',
+ _notificationListener);
+ }
+ };
+
+ Mozilla.UITour.registerPageID = function(pageID) {
+ _sendEvent('registerPageID', {
+ pageID: pageID
+ });
+ };
+
+ Mozilla.UITour.showHeartbeat = function(message, thankyouMessage, flowId, engagementURL,
+ learnMoreLabel, learnMoreURL, options) {
+ var args = {
+ message: message,
+ thankyouMessage: thankyouMessage,
+ flowId: flowId,
+ engagementURL: engagementURL,
+ learnMoreLabel: learnMoreLabel,
+ learnMoreURL: learnMoreURL,
+ };
+
+ if (options) {
+ for (var option in options) {
+ if (!options.hasOwnProperty(option)) {
+ continue;
+ }
+ args[option] = options[option];
+ }
+ }
+
+ _sendEvent('showHeartbeat', args);
+ };
+
+ Mozilla.UITour.showHighlight = function(target, effect) {
+ _sendEvent('showHighlight', {
+ target: target,
+ effect: effect
+ });
+ };
+
+ Mozilla.UITour.hideHighlight = function() {
+ _sendEvent('hideHighlight');
+ };
+
+ Mozilla.UITour.showInfo = function(target, title, text, icon, buttons, options) {
+ var buttonData = [];
+ if (Array.isArray(buttons)) {
+ for (var i = 0; i < buttons.length; i++) {
+ buttonData.push({
+ label: buttons[i].label,
+ icon: buttons[i].icon,
+ style: buttons[i].style,
+ callbackID: _waitForCallback(buttons[i].callback)
+ });
+ }
+ }
+
+ var closeButtonCallbackID, targetCallbackID;
+ if (options && options.closeButtonCallback)
+ closeButtonCallbackID = _waitForCallback(options.closeButtonCallback);
+ if (options && options.targetCallback)
+ targetCallbackID = _waitForCallback(options.targetCallback);
+
+ _sendEvent('showInfo', {
+ target: target,
+ title: title,
+ text: text,
+ icon: icon,
+ buttons: buttonData,
+ closeButtonCallbackID: closeButtonCallbackID,
+ targetCallbackID: targetCallbackID
+ });
+ };
+
+ Mozilla.UITour.hideInfo = function() {
+ _sendEvent('hideInfo');
+ };
+
+ Mozilla.UITour.previewTheme = function(theme) {
+ _stopCyclingThemes();
+
+ _sendEvent('previewTheme', {
+ theme: JSON.stringify(theme)
+ });
+ };
+
+ Mozilla.UITour.resetTheme = function() {
+ _stopCyclingThemes();
+
+ _sendEvent('resetTheme');
+ };
+
+ Mozilla.UITour.cycleThemes = function(themes, delay, callback) {
+ _stopCyclingThemes();
+
+ if (!delay) {
+ delay = Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY;
+ }
+
+ function nextTheme() {
+ var theme = themes.shift();
+ themes.push(theme);
+
+ _sendEvent('previewTheme', {
+ theme: JSON.stringify(theme),
+ state: true
+ });
+
+ callback(theme);
+ }
+
+ themeIntervalId = setInterval(nextTheme, delay);
+ nextTheme();
+ };
+
+ Mozilla.UITour.showMenu = function(name, callback) {
+ var showCallbackID;
+ if (callback)
+ showCallbackID = _waitForCallback(callback);
+
+ _sendEvent('showMenu', {
+ name: name,
+ showCallbackID: showCallbackID,
+ });
+ };
+
+ Mozilla.UITour.hideMenu = function(name) {
+ _sendEvent('hideMenu', {
+ name: name
+ });
+ };
+
+ Mozilla.UITour.showNewTab = function() {
+ _sendEvent('showNewTab');
+ };
+
+ Mozilla.UITour.getConfiguration = function(configName, callback) {
+ _sendEvent('getConfiguration', {
+ callbackID: _waitForCallback(callback),
+ configuration: configName,
+ });
+ };
+
+ Mozilla.UITour.setConfiguration = function(configName, configValue) {
+ _sendEvent('setConfiguration', {
+ configuration: configName,
+ value: configValue,
+ });
+ };
+
+ /**
+ * Request the browser open the Firefox Accounts page.
+ *
+ * @param {Object} extraURLCampaignParams - An object containing additional
+ * paramaters for the URL opened by the browser for reasons of promotional
+ * campaign tracking. Each attribute of the object must have a name that
+ * is a string, begins with "utm_" and contains only only alphanumeric
+ * characters, dashes or underscores. The values may be any string and will
+ * automatically be encoded.
+ */
+ Mozilla.UITour.showFirefoxAccounts = function(extraURLCampaignParams) {
+ _sendEvent('showFirefoxAccounts', {
+ extraURLCampaignParams: JSON.stringify(extraURLCampaignParams),
+ });
+ };
+
+ Mozilla.UITour.resetFirefox = function() {
+ _sendEvent('resetFirefox');
+ };
+
+ Mozilla.UITour.addNavBarWidget= function(name, callback) {
+ _sendEvent('addNavBarWidget', {
+ name: name,
+ callbackID: _waitForCallback(callback),
+ });
+ };
+
+ Mozilla.UITour.setDefaultSearchEngine = function(identifier) {
+ _sendEvent('setDefaultSearchEngine', {
+ identifier: identifier,
+ });
+ };
+
+ Mozilla.UITour.setTreatmentTag = function(name, value) {
+ _sendEvent('setTreatmentTag', {
+ name: name,
+ value: value
+ });
+ };
+
+ Mozilla.UITour.getTreatmentTag = function(name, callback) {
+ _sendEvent('getTreatmentTag', {
+ name: name,
+ callbackID: _waitForCallback(callback)
+ });
+ };
+
+ Mozilla.UITour.setSearchTerm = function(term) {
+ _sendEvent('setSearchTerm', {
+ term: term
+ });
+ };
+
+ Mozilla.UITour.openSearchPanel = function(callback) {
+ _sendEvent('openSearchPanel', {
+ callbackID: _waitForCallback(callback)
+ });
+ };
+
+ Mozilla.UITour.forceShowReaderIcon = function() {
+ _sendEvent('forceShowReaderIcon');
+ };
+
+ Mozilla.UITour.toggleReaderMode = function() {
+ _sendEvent('toggleReaderMode');
+ };
+
+ Mozilla.UITour.openPreferences = function(pane) {
+ _sendEvent('openPreferences', {
+ pane: pane
+ });
+ };
+
+ /**
+ * Closes the tab where this code is running. As usual, if the tab is in the
+ * foreground, the tab that was displayed before is selected.
+ *
+ * The last tab in the current window will never be closed, in which case
+ * this call will have no effect. The calling code is expected to take an
+ * action after a small timeout in order to handle this case, for example by
+ * displaying a goodbye message or a button to restart the tour.
+ */
+ Mozilla.UITour.closeTab = function() {
+ _sendEvent('closeTab');
+ };
+})();
+
+// Make this library Require-able.
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = Mozilla.UITour;
+}
diff --git a/browser/components/uitour/UITour.jsm b/browser/components/uitour/UITour.jsm
new file mode 100644
index 000000000..b92715963
--- /dev/null
+++ b/browser/components/uitour/UITour.jsm
@@ -0,0 +1,2111 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If 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 = ["UITour"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource:///modules/RecentWindow.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+Cu.importGlobalProperties(["URL"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
+ "resource://gre/modules/LightweightThemeManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ResetProfile",
+ "resource://gre/modules/ResetProfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
+ "resource://gre/modules/UITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
+ "resource:///modules/BrowserUITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+ "resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
+ "resource:///modules/ReaderParent.jsm");
+
+// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
+const PREF_LOG_LEVEL = "browser.uitour.loglevel";
+const PREF_SEENPAGEIDS = "browser.uitour.seenPageIDs";
+const PREF_READERVIEW_TRIGGER = "browser.uitour.readerViewTrigger";
+const PREF_SURVEY_DURATION = "browser.uitour.surveyDuration";
+
+const BACKGROUND_PAGE_ACTIONS_ALLOWED = new Set([
+ "forceShowReaderIcon",
+ "getConfiguration",
+ "getTreatmentTag",
+ "hideHighlight",
+ "hideInfo",
+ "hideMenu",
+ "ping",
+ "registerPageID",
+ "setConfiguration",
+ "setTreatmentTag",
+]);
+const MAX_BUTTONS = 4;
+
+const BUCKET_NAME = "UITour";
+const BUCKET_TIMESTEPS = [
+ 1 * 60 * 1000, // Until 1 minute after tab is closed/inactive.
+ 3 * 60 * 1000, // Until 3 minutes after tab is closed/inactive.
+ 10 * 60 * 1000, // Until 10 minutes after tab is closed/inactive.
+ 60 * 60 * 1000, // Until 1 hour after tab is closed/inactive.
+];
+
+// Time after which seen Page IDs expire.
+const SEENPAGEID_EXPIRY = 8 * 7 * 24 * 60 * 60 * 1000; // 8 weeks.
+
+// Prefix for any target matching a search engine.
+const TARGET_SEARCHENGINE_PREFIX = "searchEngine-";
+
+// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
+ let consoleOptions = {
+ maxLogLevelPref: PREF_LOG_LEVEL,
+ prefix: "UITour",
+ };
+ return new ConsoleAPI(consoleOptions);
+});
+
+this.UITour = {
+ url: null,
+ seenPageIDs: null,
+ // This map is not persisted and is used for
+ // building the content source of a potential tour.
+ pageIDsForSession: new Map(),
+ pageIDSourceBrowsers: new WeakMap(),
+ /* Map from browser chrome windows to a Set of <browser>s in which a tour is open (both visible and hidden) */
+ tourBrowsersByWindow: new WeakMap(),
+ appMenuOpenForAnnotation: new Set(),
+ availableTargetsCache: new WeakMap(),
+ clearAvailableTargetsCache() {
+ this.availableTargetsCache = new WeakMap();
+ },
+
+ _annotationPanelMutationObservers: new WeakMap(),
+
+ highlightEffects: ["random", "wobble", "zoom", "color"],
+ targets: new Map([
+ ["accountStatus", {
+ query: (aDocument) => {
+ // If the user is logged in, use the avatar element.
+ let fxAFooter = aDocument.getElementById("PanelUI-footer-fxa");
+ if (fxAFooter.getAttribute("fxastatus")) {
+ return aDocument.getElementById("PanelUI-fxa-avatar");
+ }
+
+ // Otherwise use the sync setup icon.
+ let statusButton = aDocument.getElementById("PanelUI-fxa-label");
+ return aDocument.getAnonymousElementByAttribute(statusButton,
+ "class",
+ "toolbarbutton-icon");
+ },
+ // This is a fake widgetName starting with the "PanelUI-" prefix so we know
+ // to automatically open the appMenu when annotating this target.
+ widgetName: "PanelUI-fxa-label",
+ }],
+ ["addons", {query: "#add-ons-button"}],
+ ["appMenu", {
+ addTargetListener: (aDocument, aCallback) => {
+ let panelPopup = aDocument.getElementById("PanelUI-popup");
+ panelPopup.addEventListener("popupshown", aCallback);
+ },
+ query: "#PanelUI-button",
+ removeTargetListener: (aDocument, aCallback) => {
+ let panelPopup = aDocument.getElementById("PanelUI-popup");
+ panelPopup.removeEventListener("popupshown", aCallback);
+ },
+ }],
+ ["backForward", {
+ query: "#back-button",
+ widgetName: "urlbar-container",
+ }],
+ ["bookmarks", {query: "#bookmarks-menu-button"}],
+ ["controlCenter-trackingUnblock", controlCenterTrackingToggleTarget(true)],
+ ["controlCenter-trackingBlock", controlCenterTrackingToggleTarget(false)],
+ ["customize", {
+ query: (aDocument) => {
+ let customizeButton = aDocument.getElementById("PanelUI-customize");
+ return aDocument.getAnonymousElementByAttribute(customizeButton,
+ "class",
+ "toolbarbutton-icon");
+ },
+ widgetName: "PanelUI-customize",
+ }],
+ ["devtools", {query: "#developer-button"}],
+ ["help", {query: "#PanelUI-help"}],
+ ["home", {query: "#home-button"}],
+ ["forget", {
+ allowAdd: true,
+ query: "#panic-button",
+ widgetName: "panic-button",
+ }],
+ ["pocket", {
+ allowAdd: true,
+ query: "#pocket-button",
+ widgetName: "pocket-button",
+ }],
+ ["privateWindow", {query: "#privatebrowsing-button"}],
+ ["quit", {query: "#PanelUI-quit"}],
+ ["readerMode-urlBar", {query: "#reader-mode-button"}],
+ ["search", {
+ infoPanelOffsetX: 18,
+ infoPanelPosition: "after_start",
+ query: "#searchbar",
+ widgetName: "search-container",
+ }],
+ ["searchIcon", {
+ query: (aDocument) => {
+ let searchbar = aDocument.getElementById("searchbar");
+ return aDocument.getAnonymousElementByAttribute(searchbar,
+ "anonid",
+ "searchbar-search-button");
+ },
+ widgetName: "search-container",
+ }],
+ ["searchPrefsLink", {
+ query: (aDocument) => {
+ let element = null;
+ let popup = aDocument.getElementById("PopupSearchAutoComplete");
+ if (popup.state != "open")
+ return null;
+ element = aDocument.getAnonymousElementByAttribute(popup,
+ "anonid",
+ "search-settings");
+ if (!element || !UITour.isElementVisible(element)) {
+ return null;
+ }
+ return element;
+ },
+ }],
+ ["selectedTabIcon", {
+ query: (aDocument) => {
+ let selectedtab = aDocument.defaultView.gBrowser.selectedTab;
+ let element = aDocument.getAnonymousElementByAttribute(selectedtab,
+ "anonid",
+ "tab-icon-image");
+ if (!element || !UITour.isElementVisible(element)) {
+ return null;
+ }
+ return element;
+ },
+ }],
+ ["trackingProtection", {
+ query: "#tracking-protection-icon",
+ }],
+ ["urlbar", {
+ query: "#urlbar",
+ widgetName: "urlbar-container",
+ }],
+ ["webide", {query: "#webide-button"}],
+ ]),
+
+ init: function() {
+ log.debug("Initializing UITour");
+ // Lazy getter is initialized here so it can be replicated any time
+ // in a test.
+ delete this.seenPageIDs;
+ Object.defineProperty(this, "seenPageIDs", {
+ get: this.restoreSeenPageIDs.bind(this),
+ configurable: true,
+ });
+
+ delete this.url;
+ XPCOMUtils.defineLazyGetter(this, "url", function () {
+ return Services.urlFormatter.formatURLPref("browser.uitour.url");
+ });
+
+ // Clear the availableTargetsCache on widget changes.
+ let listenerMethods = [
+ "onWidgetAdded",
+ "onWidgetMoved",
+ "onWidgetRemoved",
+ "onWidgetReset",
+ "onAreaReset",
+ ];
+ CustomizableUI.addListener(listenerMethods.reduce((listener, method) => {
+ listener[method] = () => this.clearAvailableTargetsCache();
+ return listener;
+ }, {}));
+ },
+
+ restoreSeenPageIDs: function() {
+ delete this.seenPageIDs;
+
+ if (UITelemetry.enabled) {
+ let dateThreshold = Date.now() - SEENPAGEID_EXPIRY;
+
+ try {
+ let data = Services.prefs.getCharPref(PREF_SEENPAGEIDS);
+ data = new Map(JSON.parse(data));
+
+ for (let [pageID, details] of data) {
+
+ if (typeof pageID != "string" ||
+ typeof details != "object" ||
+ typeof details.lastSeen != "number" ||
+ details.lastSeen < dateThreshold) {
+
+ data.delete(pageID);
+ }
+ }
+
+ this.seenPageIDs = data;
+ } catch (e) {}
+ }
+
+ if (!this.seenPageIDs)
+ this.seenPageIDs = new Map();
+
+ this.persistSeenIDs();
+
+ return this.seenPageIDs;
+ },
+
+ addSeenPageID: function(aPageID) {
+ if (!UITelemetry.enabled)
+ return;
+
+ this.seenPageIDs.set(aPageID, {
+ lastSeen: Date.now(),
+ });
+
+ this.persistSeenIDs();
+ },
+
+ persistSeenIDs: function() {
+ if (this.seenPageIDs.size === 0) {
+ Services.prefs.clearUserPref(PREF_SEENPAGEIDS);
+ return;
+ }
+
+ Services.prefs.setCharPref(PREF_SEENPAGEIDS,
+ JSON.stringify([...this.seenPageIDs]));
+ },
+
+ get _readerViewTriggerRegEx() {
+ delete this._readerViewTriggerRegEx;
+ let readerViewUITourTrigger = Services.prefs.getCharPref(PREF_READERVIEW_TRIGGER);
+ return this._readerViewTriggerRegEx = new RegExp(readerViewUITourTrigger, "i");
+ },
+
+ onLocationChange: function(aLocation) {
+ // The ReaderView tour page is expected to run in Reader View,
+ // which disables JavaScript on the page. To get around that, we
+ // automatically start a pre-defined tour on page load (for hysterical
+ // raisins the ReaderView tour is known as "readinglist")
+ let originalUrl = ReaderMode.getOriginalUrl(aLocation);
+ if (this._readerViewTriggerRegEx.test(originalUrl)) {
+ this.startSubTour("readinglist");
+ }
+ },
+
+ onPageEvent: function(aMessage, aEvent) {
+ let browser = aMessage.target;
+ let window = browser.ownerGlobal;
+
+ // Does the window have tabs? We need to make sure since windowless browsers do
+ // not have tabs.
+ if (!window.gBrowser) {
+ // When using windowless browsers we don't have a valid |window|. If that's the case,
+ // use the most recent window as a target for UITour functions (see Bug 1111022).
+ window = Services.wm.getMostRecentWindow("navigator:browser");
+ }
+
+ let messageManager = browser.messageManager;
+
+ log.debug("onPageEvent:", aEvent.detail, aMessage);
+
+ if (typeof aEvent.detail != "object") {
+ log.warn("Malformed event - detail not an object");
+ return false;
+ }
+
+ let action = aEvent.detail.action;
+ if (typeof action != "string" || !action) {
+ log.warn("Action not defined");
+ return false;
+ }
+
+ let data = aEvent.detail.data;
+ if (typeof data != "object") {
+ log.warn("Malformed event - data not an object");
+ return false;
+ }
+
+ if ((aEvent.pageVisibilityState == "hidden" ||
+ aEvent.pageVisibilityState == "unloaded") &&
+ !BACKGROUND_PAGE_ACTIONS_ALLOWED.has(action)) {
+ log.warn("Ignoring disallowed action from a hidden page:", action);
+ return false;
+ }
+
+ switch (action) {
+ case "registerPageID": {
+ if (typeof data.pageID != "string") {
+ log.warn("registerPageID: pageID must be a string");
+ break;
+ }
+
+ this.pageIDsForSession.set(data.pageID, {lastSeen: Date.now()});
+
+ // The rest is only relevant if Telemetry is enabled.
+ if (!UITelemetry.enabled) {
+ log.debug("registerPageID: Telemetry disabled, not doing anything");
+ break;
+ }
+
+ // We don't want to allow BrowserUITelemetry.BUCKET_SEPARATOR in the
+ // pageID, as it could make parsing the telemetry bucket name difficult.
+ if (data.pageID.includes(BrowserUITelemetry.BUCKET_SEPARATOR)) {
+ log.warn("registerPageID: Invalid page ID specified");
+ break;
+ }
+
+ this.addSeenPageID(data.pageID);
+ this.pageIDSourceBrowsers.set(browser, data.pageID);
+ this.setTelemetryBucket(data.pageID);
+
+ break;
+ }
+
+ case "showHeartbeat": {
+ // Validate the input parameters.
+ if (typeof data.message !== "string" || data.message === "") {
+ log.error("showHeartbeat: Invalid message specified.");
+ return false;
+ }
+
+ if (typeof data.thankyouMessage !== "string" || data.thankyouMessage === "") {
+ log.error("showHeartbeat: Invalid thank you message specified.");
+ return false;
+ }
+
+ if (typeof data.flowId !== "string" || data.flowId === "") {
+ log.error("showHeartbeat: Invalid flowId specified.");
+ return false;
+ }
+
+ if (data.engagementButtonLabel && typeof data.engagementButtonLabel != "string") {
+ log.error("showHeartbeat: Invalid engagementButtonLabel specified");
+ return false;
+ }
+
+ let heartbeatWindow = window;
+ if (data.privateWindowsOnly && !PrivateBrowsingUtils.isWindowPrivate(heartbeatWindow)) {
+ heartbeatWindow = RecentWindow.getMostRecentBrowserWindow({ private: true });
+ if (!heartbeatWindow) {
+ log.debug("showHeartbeat: No private window found");
+ return false;
+ }
+ }
+
+ // Finally show the Heartbeat UI.
+ this.showHeartbeat(heartbeatWindow, data);
+ break;
+ }
+
+ case "showHighlight": {
+ let targetPromise = this.getTarget(window, data.target);
+ targetPromise.then(target => {
+ if (!target.node) {
+ log.error("UITour: Target could not be resolved: " + data.target);
+ return;
+ }
+ let effect = undefined;
+ if (this.highlightEffects.indexOf(data.effect) !== -1) {
+ effect = data.effect;
+ }
+ this.showHighlight(window, target, effect);
+ }).catch(log.error);
+ break;
+ }
+
+ case "hideHighlight": {
+ this.hideHighlight(window);
+ break;
+ }
+
+ case "showInfo": {
+ let targetPromise = this.getTarget(window, data.target, true);
+ targetPromise.then(target => {
+ if (!target.node) {
+ log.error("UITour: Target could not be resolved: " + data.target);
+ return;
+ }
+
+ let iconURL = null;
+ if (typeof data.icon == "string")
+ iconURL = this.resolveURL(browser, data.icon);
+
+ let buttons = [];
+ if (Array.isArray(data.buttons) && data.buttons.length > 0) {
+ for (let buttonData of data.buttons) {
+ if (typeof buttonData == "object" &&
+ typeof buttonData.label == "string" &&
+ typeof buttonData.callbackID == "string") {
+ let callback = buttonData.callbackID;
+ let button = {
+ label: buttonData.label,
+ callback: event => {
+ this.sendPageCallback(messageManager, callback);
+ },
+ };
+
+ if (typeof buttonData.icon == "string")
+ button.iconURL = this.resolveURL(browser, buttonData.icon);
+
+ if (typeof buttonData.style == "string")
+ button.style = buttonData.style;
+
+ buttons.push(button);
+
+ if (buttons.length == MAX_BUTTONS) {
+ log.warn("showInfo: Reached limit of allowed number of buttons");
+ break;
+ }
+ }
+ }
+ }
+
+ let infoOptions = {};
+ if (typeof data.closeButtonCallbackID == "string") {
+ infoOptions.closeButtonCallback = () => {
+ this.sendPageCallback(messageManager, data.closeButtonCallbackID);
+ };
+ }
+ if (typeof data.targetCallbackID == "string") {
+ infoOptions.targetCallback = details => {
+ this.sendPageCallback(messageManager, data.targetCallbackID, details);
+ };
+ }
+
+ this.showInfo(window, target, data.title, data.text, iconURL, buttons, infoOptions);
+ }).catch(log.error);
+ break;
+ }
+
+ case "hideInfo": {
+ this.hideInfo(window);
+ break;
+ }
+
+ case "previewTheme": {
+ this.previewTheme(data.theme);
+ break;
+ }
+
+ case "resetTheme": {
+ this.resetTheme();
+ break;
+ }
+
+ case "showMenu": {
+ this.showMenu(window, data.name, () => {
+ if (typeof data.showCallbackID == "string")
+ this.sendPageCallback(messageManager, data.showCallbackID);
+ });
+ break;
+ }
+
+ case "hideMenu": {
+ this.hideMenu(window, data.name);
+ break;
+ }
+
+ case "showNewTab": {
+ this.showNewTab(window, browser);
+ break;
+ }
+
+ case "getConfiguration": {
+ if (typeof data.configuration != "string") {
+ log.warn("getConfiguration: No configuration option specified");
+ return false;
+ }
+
+ this.getConfiguration(messageManager, window, data.configuration, data.callbackID);
+ break;
+ }
+
+ case "setConfiguration": {
+ if (typeof data.configuration != "string") {
+ log.warn("setConfiguration: No configuration option specified");
+ return false;
+ }
+
+ this.setConfiguration(window, data.configuration, data.value);
+ break;
+ }
+
+ case "openPreferences": {
+ if (typeof data.pane != "string" && typeof data.pane != "undefined") {
+ log.warn("openPreferences: Invalid pane specified");
+ return false;
+ }
+
+ window.openPreferences(data.pane);
+ break;
+ }
+
+ case "showFirefoxAccounts": {
+ // 'signup' is the only action that makes sense currently, so we don't
+ // accept arbitrary actions just to be safe...
+ let p = new URLSearchParams("action=signup&entrypoint=uitour");
+ // Call our helper to validate extraURLCampaignParams and populate URLSearchParams
+ if (!this._populateCampaignParams(p, data.extraURLCampaignParams)) {
+ log.warn("showFirefoxAccounts: invalid campaign args specified");
+ return false;
+ }
+
+ // We want to replace the current tab.
+ browser.loadURI("about:accounts?" + p.toString());
+ break;
+ }
+
+ case "resetFirefox": {
+ // Open a reset profile dialog window.
+ if (ResetProfile.resetSupported()) {
+ ResetProfile.openConfirmationDialog(window);
+ }
+ break;
+ }
+
+ case "addNavBarWidget": {
+ // Add a widget to the toolbar
+ let targetPromise = this.getTarget(window, data.name);
+ targetPromise.then(target => {
+ this.addNavBarWidget(target, messageManager, data.callbackID);
+ }).catch(log.error);
+ break;
+ }
+
+ case "setDefaultSearchEngine": {
+ let enginePromise = this.selectSearchEngine(data.identifier);
+ enginePromise.catch(Cu.reportError);
+ break;
+ }
+
+ case "setTreatmentTag": {
+ let name = data.name;
+ let value = data.value;
+ let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ string.data = value;
+ Services.prefs.setComplexValue("browser.uitour.treatment." + name,
+ Ci.nsISupportsString, string);
+ // The notification is only meant to be used in tests.
+ UITourHealthReport.recordTreatmentTag(name, value)
+ .then(() => this.notify("TreatmentTag:TelemetrySent"));
+ break;
+ }
+
+ case "getTreatmentTag": {
+ let name = data.name;
+ let value;
+ try {
+ value = Services.prefs.getComplexValue("browser.uitour.treatment." + name,
+ Ci.nsISupportsString).data;
+ } catch (ex) {}
+ this.sendPageCallback(messageManager, data.callbackID, { value: value });
+ break;
+ }
+
+ case "setSearchTerm": {
+ let targetPromise = this.getTarget(window, "search");
+ targetPromise.then(target => {
+ let searchbar = target.node;
+ searchbar.value = data.term;
+ searchbar.updateGoButtonVisibility();
+ });
+ break;
+ }
+
+ case "openSearchPanel": {
+ let targetPromise = this.getTarget(window, "search");
+ targetPromise.then(target => {
+ let searchbar = target.node;
+
+ if (searchbar.textbox.open) {
+ this.sendPageCallback(messageManager, data.callbackID);
+ } else {
+ let onPopupShown = () => {
+ searchbar.textbox.popup.removeEventListener("popupshown", onPopupShown);
+ this.sendPageCallback(messageManager, data.callbackID);
+ };
+
+ searchbar.textbox.popup.addEventListener("popupshown", onPopupShown);
+ searchbar.openSuggestionsPanel();
+ }
+ }).then(null, Cu.reportError);
+ break;
+ }
+
+ case "ping": {
+ if (typeof data.callbackID == "string")
+ this.sendPageCallback(messageManager, data.callbackID);
+ break;
+ }
+
+ case "forceShowReaderIcon": {
+ ReaderParent.forceShowReaderIcon(browser);
+ break;
+ }
+
+ case "toggleReaderMode": {
+ let targetPromise = this.getTarget(window, "readerMode-urlBar");
+ targetPromise.then(target => {
+ ReaderParent.toggleReaderMode({target: target.node});
+ });
+ break;
+ }
+
+ case "closeTab": {
+ // Find the <tabbrowser> element of the <browser> for which the event
+ // was generated originally. If the browser where the UI tour is loaded
+ // is windowless, just ignore the request to close the tab. The request
+ // is also ignored if this is the only tab in the window.
+ let tabBrowser = browser.ownerGlobal.gBrowser;
+ if (tabBrowser && tabBrowser.browsers.length > 1) {
+ tabBrowser.removeTab(tabBrowser.getTabForBrowser(browser));
+ }
+ break;
+ }
+ }
+
+ this.initForBrowser(browser, window);
+
+ return true;
+ },
+
+ initForBrowser(aBrowser, window) {
+ let gBrowser = window.gBrowser;
+
+ if (gBrowser) {
+ gBrowser.tabContainer.addEventListener("TabSelect", this);
+ }
+
+ if (!this.tourBrowsersByWindow.has(window)) {
+ this.tourBrowsersByWindow.set(window, new Set());
+ }
+ this.tourBrowsersByWindow.get(window).add(aBrowser);
+
+ Services.obs.addObserver(this, "message-manager-close", false);
+
+ window.addEventListener("SSWindowClosing", this);
+ },
+
+ handleEvent: function(aEvent) {
+ log.debug("handleEvent: type =", aEvent.type, "event =", aEvent);
+ switch (aEvent.type) {
+ case "TabSelect": {
+ let window = aEvent.target.ownerGlobal;
+
+ // Teardown the browser of the tab we just switched away from.
+ if (aEvent.detail && aEvent.detail.previousTab) {
+ let previousTab = aEvent.detail.previousTab;
+ let openTourWindows = this.tourBrowsersByWindow.get(window);
+ if (openTourWindows.has(previousTab.linkedBrowser)) {
+ this.teardownTourForBrowser(window, previousTab.linkedBrowser, false);
+ }
+ }
+
+ break;
+ }
+
+ case "SSWindowClosing": {
+ let window = aEvent.target;
+ this.teardownTourForWindow(window);
+ break;
+ }
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ log.debug("observe: aTopic =", aTopic);
+ switch (aTopic) {
+ // The browser message manager is disconnected when the <browser> is
+ // destroyed and we want to teardown at that point.
+ case "message-manager-close": {
+ let winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let window = winEnum.getNext();
+ if (window.closed)
+ continue;
+
+ let tourBrowsers = this.tourBrowsersByWindow.get(window);
+ if (!tourBrowsers)
+ continue;
+
+ for (let browser of tourBrowsers) {
+ let messageManager = browser.messageManager;
+ if (aSubject != messageManager) {
+ continue;
+ }
+
+ this.teardownTourForBrowser(window, browser, true);
+ return;
+ }
+ }
+ break;
+ }
+ }
+ },
+
+ // Given a string that is a JSONified represenation of an object with
+ // additional utm_* URL params that should be appended, validate and append
+ // them to the passed URLSearchParams object. Returns true if the params
+ // were validated and appended, and false if the request should be ignored.
+ _populateCampaignParams: function(urlSearchParams, extraURLCampaignParams) {
+ // We are extra paranoid about what params we allow to be appended.
+ if (typeof extraURLCampaignParams == "undefined") {
+ // no params, so it's all good.
+ return true;
+ }
+ if (typeof extraURLCampaignParams != "string") {
+ log.warn("_populateCampaignParams: extraURLCampaignParams is not a string");
+ return false;
+ }
+ let campaignParams;
+ try {
+ if (extraURLCampaignParams) {
+ campaignParams = JSON.parse(extraURLCampaignParams);
+ if (typeof campaignParams != "object") {
+ log.warn("_populateCampaignParams: extraURLCampaignParams is not a stringified object");
+ return false;
+ }
+ }
+ } catch (ex) {
+ log.warn("_populateCampaignParams: extraURLCampaignParams is not a JSON object");
+ return false;
+ }
+ if (campaignParams) {
+ // The regex that the name of each param must match - there's no
+ // character restriction on the value - they will be escaped as necessary.
+ let reSimpleString = /^[-_a-zA-Z0-9]*$/;
+ for (let name in campaignParams) {
+ let value = campaignParams[name];
+ if (typeof name != "string" || typeof value != "string" ||
+ !name.startsWith("utm_") ||
+ value.length == 0 ||
+ !reSimpleString.test(name)) {
+ log.warn("_populateCampaignParams: invalid campaign param specified");
+ return false;
+ }
+ urlSearchParams.append(name, value);
+ }
+ }
+ return true;
+ },
+
+ setTelemetryBucket: function(aPageID) {
+ let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID;
+ BrowserUITelemetry.setBucket(bucket);
+ },
+
+ setExpiringTelemetryBucket: function(aPageID, aType) {
+ let bucket = BUCKET_NAME + BrowserUITelemetry.BUCKET_SEPARATOR + aPageID +
+ BrowserUITelemetry.BUCKET_SEPARATOR + aType;
+
+ BrowserUITelemetry.setExpiringBucket(bucket,
+ BUCKET_TIMESTEPS);
+ },
+
+ // This is registered with UITelemetry by BrowserUITelemetry, so that UITour
+ // can remain lazy-loaded on-demand.
+ getTelemetry: function() {
+ return {
+ seenPageIDs: [...this.seenPageIDs.keys()],
+ };
+ },
+
+ /**
+ * Tear down a tour from a tab e.g. upon switching/closing tabs.
+ */
+ teardownTourForBrowser: function(aWindow, aBrowser, aTourPageClosing = false) {
+ log.debug("teardownTourForBrowser: aBrowser = ", aBrowser, aTourPageClosing);
+
+ if (this.pageIDSourceBrowsers.has(aBrowser)) {
+ let pageID = this.pageIDSourceBrowsers.get(aBrowser);
+ this.setExpiringTelemetryBucket(pageID, aTourPageClosing ? "closed" : "inactive");
+ }
+
+ let openTourBrowsers = this.tourBrowsersByWindow.get(aWindow);
+ if (aTourPageClosing && openTourBrowsers) {
+ openTourBrowsers.delete(aBrowser);
+ }
+
+ this.hideHighlight(aWindow);
+ this.hideInfo(aWindow);
+ // Ensure the menu panel is hidden before calling recreatePopup so popup events occur.
+ this.hideMenu(aWindow, "appMenu");
+ this.hideMenu(aWindow, "controlCenter");
+
+ // Clean up panel listeners after calling hideMenu above.
+ aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hideAppMenuAnnotations);
+ aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hideAppMenuAnnotations);
+ aWindow.PanelUI.panel.removeEventListener("popuphidden", this.onPanelHidden);
+ let controlCenterPanel = aWindow.gIdentityHandler._identityPopup;
+ controlCenterPanel.removeEventListener("popuphidden", this.onPanelHidden);
+ controlCenterPanel.removeEventListener("popuphiding", this.hideControlCenterAnnotations);
+
+ this.resetTheme();
+
+ // If there are no more tour tabs left in the window, teardown the tour for the whole window.
+ if (!openTourBrowsers || openTourBrowsers.size == 0) {
+ this.teardownTourForWindow(aWindow);
+ }
+ },
+
+ /**
+ * Tear down all tours for a ChromeWindow.
+ */
+ teardownTourForWindow: function(aWindow) {
+ log.debug("teardownTourForWindow");
+ aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
+ aWindow.removeEventListener("SSWindowClosing", this);
+
+ let openTourBrowsers = this.tourBrowsersByWindow.get(aWindow);
+ if (openTourBrowsers) {
+ for (let browser of openTourBrowsers) {
+ if (this.pageIDSourceBrowsers.has(browser)) {
+ let pageID = this.pageIDSourceBrowsers.get(browser);
+ this.setExpiringTelemetryBucket(pageID, "closed");
+ }
+ }
+ }
+
+ this.tourBrowsersByWindow.delete(aWindow);
+ },
+
+ // This function is copied to UITourListener.
+ isSafeScheme: function(aURI) {
+ let allowedSchemes = new Set(["https", "about"]);
+ if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
+ allowedSchemes.add("http");
+
+ if (!allowedSchemes.has(aURI.scheme)) {
+ log.error("Unsafe scheme:", aURI.scheme);
+ return false;
+ }
+
+ return true;
+ },
+
+ resolveURL: function(aBrowser, aURL) {
+ try {
+ let uri = Services.io.newURI(aURL, null, aBrowser.currentURI);
+
+ if (!this.isSafeScheme(uri))
+ return null;
+
+ return uri.spec;
+ } catch (e) {}
+
+ return null;
+ },
+
+ sendPageCallback: function(aMessageManager, aCallbackID, aData = {}) {
+ let detail = {data: aData, callbackID: aCallbackID};
+ log.debug("sendPageCallback", detail);
+ aMessageManager.sendAsyncMessage("UITour:SendPageCallback", detail);
+ },
+
+ isElementVisible: function(aElement) {
+ let targetStyle = aElement.ownerGlobal.getComputedStyle(aElement);
+ return !aElement.ownerDocument.hidden &&
+ targetStyle.display != "none" &&
+ targetStyle.visibility == "visible";
+ },
+
+ getTarget: function(aWindow, aTargetName, aSticky = false) {
+ log.debug("getTarget:", aTargetName);
+ let deferred = Promise.defer();
+ if (typeof aTargetName != "string" || !aTargetName) {
+ log.warn("getTarget: Invalid target name specified");
+ deferred.reject("Invalid target name specified");
+ return deferred.promise;
+ }
+
+ let targetObject = this.targets.get(aTargetName);
+ if (!targetObject) {
+ log.warn("getTarget: The specified target name is not in the allowed set");
+ deferred.reject("The specified target name is not in the allowed set");
+ return deferred.promise;
+ }
+
+ let targetQuery = targetObject.query;
+ aWindow.PanelUI.ensureReady().then(() => {
+ let node;
+ if (typeof targetQuery == "function") {
+ try {
+ node = targetQuery(aWindow.document);
+ } catch (ex) {
+ log.warn("getTarget: Error running target query:", ex);
+ node = null;
+ }
+ } else {
+ node = aWindow.document.querySelector(targetQuery);
+ }
+
+ deferred.resolve({
+ addTargetListener: targetObject.addTargetListener,
+ infoPanelOffsetX: targetObject.infoPanelOffsetX,
+ infoPanelOffsetY: targetObject.infoPanelOffsetY,
+ infoPanelPosition: targetObject.infoPanelPosition,
+ node: node,
+ removeTargetListener: targetObject.removeTargetListener,
+ targetName: aTargetName,
+ widgetName: targetObject.widgetName,
+ allowAdd: targetObject.allowAdd,
+ });
+ }).catch(log.error);
+ return deferred.promise;
+ },
+
+ targetIsInAppMenu: function(aTarget) {
+ let placement = CustomizableUI.getPlacementOfWidget(aTarget.widgetName || aTarget.node.id);
+ if (placement && placement.area == CustomizableUI.AREA_PANEL) {
+ return true;
+ }
+
+ let targetElement = aTarget.node;
+ // Use the widget for filtering if it exists since the target may be the icon inside.
+ if (aTarget.widgetName) {
+ targetElement = aTarget.node.ownerDocument.getElementById(aTarget.widgetName);
+ }
+
+ // Handle the non-customizable buttons at the bottom of the menu which aren't proper widgets.
+ return targetElement.id.startsWith("PanelUI-")
+ && targetElement.id != "PanelUI-button";
+ },
+
+ /**
+ * Called before opening or after closing a highlight or info panel to see if
+ * we need to open or close the appMenu to see the annotation's anchor.
+ */
+ _setAppMenuStateForAnnotation: function(aWindow, aAnnotationType, aShouldOpenForHighlight, aCallback = null) {
+ log.debug("_setAppMenuStateForAnnotation:", aAnnotationType);
+ log.debug("_setAppMenuStateForAnnotation: Menu is expected to be:", aShouldOpenForHighlight ? "open" : "closed");
+
+ // If the panel is in the desired state, we're done.
+ let panelIsOpen = aWindow.PanelUI.panel.state != "closed";
+ if (aShouldOpenForHighlight == panelIsOpen) {
+ log.debug("_setAppMenuStateForAnnotation: Panel already in expected state");
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ // Don't close the menu if it wasn't opened by us (e.g. via showmenu instead).
+ if (!aShouldOpenForHighlight && !this.appMenuOpenForAnnotation.has(aAnnotationType)) {
+ log.debug("_setAppMenuStateForAnnotation: Menu not opened by us, not closing");
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ if (aShouldOpenForHighlight) {
+ this.appMenuOpenForAnnotation.add(aAnnotationType);
+ } else {
+ this.appMenuOpenForAnnotation.delete(aAnnotationType);
+ }
+
+ // Actually show or hide the menu
+ if (this.appMenuOpenForAnnotation.size) {
+ log.debug("_setAppMenuStateForAnnotation: Opening the menu");
+ this.showMenu(aWindow, "appMenu", aCallback);
+ } else {
+ log.debug("_setAppMenuStateForAnnotation: Closing the menu");
+ this.hideMenu(aWindow, "appMenu");
+ if (aCallback)
+ aCallback();
+ }
+
+ },
+
+ previewTheme: function(aTheme) {
+ let origin = Services.prefs.getCharPref("browser.uitour.themeOrigin");
+ let data = LightweightThemeManager.parseTheme(aTheme, origin);
+ if (data)
+ LightweightThemeManager.previewTheme(data);
+ },
+
+ resetTheme: function() {
+ LightweightThemeManager.resetPreview();
+ },
+
+ /**
+ * Show the Heartbeat UI to request user feedback. This function reports back to the
+ * caller using |notify|. The notification event name reflects the current status the UI
+ * is in (either "Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed",
+ * "Heartbeat:LearnMore", "Heartbeat:Engaged", "Heartbeat:Voted",
+ * "Heartbeat:SurveyExpired" or "Heartbeat:WindowClosed").
+ * When a "Heartbeat:Voted" event is notified
+ * the data payload contains a |score| field which holds the rating picked by the user.
+ * Please note that input parameters are already validated by the caller.
+ *
+ * @param aChromeWindow
+ * The chrome window that the heartbeat notification is displayed in.
+ * @param {Object} aOptions Options object.
+ * @param {String} aOptions.message
+ * The message, or question, to display on the notification.
+ * @param {String} aOptions.thankyouMessage
+ * The thank you message to display after user votes.
+ * @param {String} aOptions.flowId
+ * An identifier for this rating flow. Please note that this is only used to
+ * identify the notification box.
+ * @param {String} [aOptions.engagementButtonLabel=null]
+ * The text of the engagement button to use instad of stars. If this is null
+ * or invalid, rating stars are used.
+ * @param {String} [aOptions.engagementURL=null]
+ * The engagement URL to open in a new tab once user has engaged. If this is null
+ * or invalid, no new tab is opened.
+ * @param {String} [aOptions.learnMoreLabel=null]
+ * The label of the learn more link. No link will be shown if this is null.
+ * @param {String} [aOptions.learnMoreURL=null]
+ * The learn more URL to open when clicking on the learn more link. No learn more
+ * will be shown if this is an invalid URL.
+ * @param {boolean} [aOptions.privateWindowsOnly=false]
+ * Whether the heartbeat UI should only be targeted at a private window (if one exists).
+ * No notifications should be fired when this is true.
+ * @param {String} [aOptions.surveyId]
+ * An ID for the survey, reflected in the Telemetry ping.
+ * @param {Number} [aOptions.surveyVersion]
+ * Survey's version number, reflected in the Telemetry ping.
+ * @param {boolean} [aOptions.testing]
+ * Whether this is a test survey, reflected in the Telemetry ping.
+ */
+ showHeartbeat(aChromeWindow, aOptions) {
+ // Initialize survey state
+ let pingSent = false;
+ let surveyResults = {};
+ let surveyEndTimer = null;
+
+ /**
+ * Accumulates survey events and submits to Telemetry after the survey ends.
+ *
+ * @param {String} aEventName
+ * Heartbeat event name
+ * @param {Object} aParams
+ * Additional parameters and their values
+ */
+ let maybeNotifyHeartbeat = (aEventName, aParams = {}) => {
+ // Return if event occurred after the ping was sent
+ if (pingSent) {
+ log.warn("maybeNotifyHeartbeat: event occurred after ping sent:", aEventName, aParams);
+ return;
+ }
+
+ // No Telemetry from private-window-only Heartbeats
+ if (aOptions.privateWindowsOnly) {
+ return;
+ }
+
+ let ts = Date.now();
+ let sendPing = false;
+ switch (aEventName) {
+ case "Heartbeat:NotificationOffered":
+ surveyResults.flowId = aOptions.flowId;
+ surveyResults.offeredTS = ts;
+ break;
+ case "Heartbeat:LearnMore":
+ // record only the first click
+ if (!surveyResults.learnMoreTS) {
+ surveyResults.learnMoreTS = ts;
+ }
+ break;
+ case "Heartbeat:Engaged":
+ surveyResults.engagedTS = ts;
+ break;
+ case "Heartbeat:Voted":
+ surveyResults.votedTS = ts;
+ surveyResults.score = aParams.score;
+ break;
+ case "Heartbeat:SurveyExpired":
+ surveyResults.expiredTS = ts;
+ break;
+ case "Heartbeat:NotificationClosed":
+ // this is the final event in most surveys
+ surveyResults.closedTS = ts;
+ sendPing = true;
+ break;
+ case "Heartbeat:WindowClosed":
+ surveyResults.windowClosedTS = ts;
+ sendPing = true;
+ break;
+ default:
+ log.error("maybeNotifyHeartbeat: unrecognized event:", aEventName);
+ break;
+ }
+
+ aParams.timestamp = ts;
+ aParams.flowId = aOptions.flowId;
+ this.notify(aEventName, aParams);
+
+ if (!sendPing) {
+ return;
+ }
+
+ // Send the ping to Telemetry
+ let payload = Object.assign({}, surveyResults);
+ payload.version = 1;
+ for (let meta of ["surveyId", "surveyVersion", "testing"]) {
+ if (aOptions.hasOwnProperty(meta)) {
+ payload[meta] = aOptions[meta];
+ }
+ }
+
+ log.debug("Sending payload to Telemetry: aEventName:", aEventName,
+ "payload:", payload);
+
+ TelemetryController.submitExternalPing("heartbeat", payload, {
+ addClientId: true,
+ addEnvironment: true,
+ });
+
+ // only for testing
+ this.notify("Heartbeat:TelemetrySent", payload);
+
+ // Survey is complete, clear out the expiry timer & survey configuration
+ if (surveyEndTimer) {
+ clearTimeout(surveyEndTimer);
+ surveyEndTimer = null;
+ }
+
+ pingSent = true;
+ surveyResults = {};
+ };
+
+ let nb = aChromeWindow.document.getElementById("high-priority-global-notificationbox");
+ let buttons = null;
+
+ if (aOptions.engagementButtonLabel) {
+ buttons = [{
+ label: aOptions.engagementButtonLabel,
+ callback: () => {
+ // Let the consumer know user engaged.
+ maybeNotifyHeartbeat("Heartbeat:Engaged");
+
+ userEngaged(new Map([
+ ["type", "button"],
+ ["flowid", aOptions.flowId]
+ ]));
+
+ // Return true so that the notification bar doesn't close itself since
+ // we have a thank you message to show.
+ return true;
+ },
+ }];
+ }
+
+ let defaultIcon = "chrome://browser/skin/heartbeat-icon.svg";
+ let iconURL = defaultIcon;
+ try {
+ // Take the optional icon URL if specified
+ if (aOptions.iconURL) {
+ iconURL = new URL(aOptions.iconURL);
+ // For now, only allow chrome URIs.
+ if (iconURL.protocol != "chrome:") {
+ iconURL = defaultIcon;
+ throw new Error("Invalid protocol");
+ }
+ }
+ } catch (error) {
+ log.error("showHeartbeat: Invalid icon URL specified.");
+ }
+
+ // Create the notification. Prefix its ID to decrease the chances of collisions.
+ let notice = nb.appendNotification(aOptions.message, "heartbeat-" + aOptions.flowId,
+ iconURL,
+ nb.PRIORITY_INFO_HIGH, buttons,
+ (aEventType) => {
+ if (aEventType != "removed") {
+ return;
+ }
+ // Let the consumer know the notification bar was closed.
+ // This also happens after voting.
+ maybeNotifyHeartbeat("Heartbeat:NotificationClosed");
+ });
+
+ // Get the elements we need to style.
+ let messageImage =
+ aChromeWindow.document.getAnonymousElementByAttribute(notice, "anonid", "messageImage");
+ let messageText =
+ aChromeWindow.document.getAnonymousElementByAttribute(notice, "anonid", "messageText");
+
+ function userEngaged(aEngagementParams) {
+ // Make the heartbeat icon pulse twice.
+ notice.label = aOptions.thankyouMessage;
+ messageImage.classList.remove("pulse-onshow");
+ messageImage.classList.add("pulse-twice");
+
+ // Remove all the children of the notice (rating container
+ // and the flex).
+ while (notice.firstChild) {
+ notice.removeChild(notice.firstChild);
+ }
+
+ // Make sure that we have a valid URL. If we haven't, do not open the engagement page.
+ let engagementURL = null;
+ try {
+ engagementURL = new URL(aOptions.engagementURL);
+ } catch (error) {
+ log.error("showHeartbeat: Invalid URL specified.");
+ }
+
+ // Just open the engagement tab if we have a valid engagement URL.
+ if (engagementURL) {
+ for (let [param, value] of aEngagementParams) {
+ engagementURL.searchParams.append(param, value);
+ }
+
+ // Open the engagement URL in a new tab.
+ aChromeWindow.gBrowser.selectedTab =
+ aChromeWindow.gBrowser.addTab(engagementURL.toString(), {
+ owner: aChromeWindow.gBrowser.selectedTab,
+ relatedToCurrent: true
+ });
+ }
+
+ // Remove the notification bar after 3 seconds.
+ aChromeWindow.setTimeout(() => {
+ nb.removeNotification(notice);
+ }, 3000);
+ }
+
+ // Create the fragment holding the rating UI.
+ let frag = aChromeWindow.document.createDocumentFragment();
+
+ // Build the Heartbeat star rating.
+ const numStars = aOptions.engagementButtonLabel ? 0 : 5;
+ let ratingContainer = aChromeWindow.document.createElement("hbox");
+ ratingContainer.id = "star-rating-container";
+
+ for (let i = 0; i < numStars; i++) {
+ // Create a star rating element.
+ let ratingElement = aChromeWindow.document.createElement("toolbarbutton");
+
+ // Style it.
+ let starIndex = numStars - i;
+ ratingElement.className = "plain star-x";
+ ratingElement.id = "star" + starIndex;
+ ratingElement.setAttribute("data-score", starIndex);
+
+ // Add the click handler.
+ ratingElement.addEventListener("click", function (evt) {
+ let rating = Number(evt.target.getAttribute("data-score"), 10);
+
+ // Let the consumer know user voted.
+ maybeNotifyHeartbeat("Heartbeat:Voted", { score: rating });
+
+ // Append the score data to the engagement URL.
+ userEngaged(new Map([
+ ["type", "stars"],
+ ["score", rating],
+ ["flowid", aOptions.flowId]
+ ]));
+ }.bind(this));
+
+ // Add it to the container.
+ ratingContainer.appendChild(ratingElement);
+ }
+
+ frag.appendChild(ratingContainer);
+
+ // Make sure the stars are not pushed to the right by the spacer.
+ let rightSpacer = aChromeWindow.document.createElement("spacer");
+ rightSpacer.flex = 20;
+ frag.appendChild(rightSpacer);
+
+ messageText.flex = 0; // Collapse the space before the stars.
+ let leftSpacer = messageText.nextSibling;
+ leftSpacer.flex = 0;
+
+ // Make sure that we have a valid learn more URL.
+ let learnMoreURL = null;
+ try {
+ learnMoreURL = new URL(aOptions.learnMoreURL);
+ } catch (error) {
+ log.error("showHeartbeat: Invalid learnMore URL specified.");
+ }
+
+ // Add the learn more link.
+ if (aOptions.learnMoreLabel && learnMoreURL) {
+ let learnMore = aChromeWindow.document.createElement("label");
+ learnMore.className = "text-link";
+ learnMore.href = learnMoreURL.toString();
+ learnMore.setAttribute("value", aOptions.learnMoreLabel);
+ learnMore.addEventListener("click", () => maybeNotifyHeartbeat("Heartbeat:LearnMore"));
+ frag.appendChild(learnMore);
+ }
+
+ // Append the fragment and apply the styling.
+ notice.appendChild(frag);
+ notice.classList.add("heartbeat");
+ messageImage.classList.add("heartbeat", "pulse-onshow");
+ messageText.classList.add("heartbeat");
+
+ // Let the consumer know the notification was shown.
+ maybeNotifyHeartbeat("Heartbeat:NotificationOffered");
+
+ // End the survey if the user quits, closes the window, or
+ // hasn't responded before expiration.
+ if (!aOptions.privateWindowsOnly) {
+ function handleWindowClosed(aTopic) {
+ maybeNotifyHeartbeat("Heartbeat:WindowClosed");
+ aChromeWindow.removeEventListener("SSWindowClosing", handleWindowClosed);
+ }
+ aChromeWindow.addEventListener("SSWindowClosing", handleWindowClosed);
+
+ let surveyDuration = Services.prefs.getIntPref(PREF_SURVEY_DURATION) * 1000;
+ surveyEndTimer = setTimeout(() => {
+ maybeNotifyHeartbeat("Heartbeat:SurveyExpired");
+ nb.removeNotification(notice);
+ }, surveyDuration);
+ }
+ },
+
+ /**
+ * The node to which a highlight or notification(-popup) is anchored is sometimes
+ * obscured because it may be inside an overflow menu. This function should figure
+ * that out and offer the overflow chevron as an alternative.
+ *
+ * @param {Node} aAnchor The element that's supposed to be the anchor
+ * @type {Node}
+ */
+ _correctAnchor: function(aAnchor) {
+ // If the target is in the overflow panel, just return the overflow button.
+ if (aAnchor.getAttribute("overflowedItem")) {
+ let doc = aAnchor.ownerDocument;
+ let placement = CustomizableUI.getPlacementOfWidget(aAnchor.id);
+ let areaNode = doc.getElementById(placement.area);
+ return areaNode.overflowable._chevron;
+ }
+
+ return aAnchor;
+ },
+
+ /**
+ * @param aChromeWindow The chrome window that the highlight is in. Necessary since some targets
+ * are in a sub-frame so the defaultView is not the same as the chrome
+ * window.
+ * @param aTarget The element to highlight.
+ * @param aEffect (optional) The effect to use from UITour.highlightEffects or "none".
+ * @see UITour.highlightEffects
+ */
+ showHighlight: function(aChromeWindow, aTarget, aEffect = "none") {
+ function showHighlightPanel() {
+ let highlighter = aChromeWindow.document.getElementById("UITourHighlight");
+
+ let effect = aEffect;
+ if (effect == "random") {
+ // Exclude "random" from the randomly selected effects.
+ let randomEffect = 1 + Math.floor(Math.random() * (this.highlightEffects.length - 1));
+ if (randomEffect == this.highlightEffects.length)
+ randomEffect--; // On the order of 1 in 2^62 chance of this happening.
+ effect = this.highlightEffects[randomEffect];
+ }
+ // Toggle the effect attribute to "none" and flush layout before setting it so the effect plays.
+ highlighter.setAttribute("active", "none");
+ aChromeWindow.getComputedStyle(highlighter).animationName;
+ highlighter.setAttribute("active", effect);
+ highlighter.parentElement.setAttribute("targetName", aTarget.targetName);
+ highlighter.parentElement.hidden = false;
+
+ let highlightAnchor = this._correctAnchor(aTarget.node);
+ let targetRect = highlightAnchor.getBoundingClientRect();
+ let highlightHeight = targetRect.height;
+ let highlightWidth = targetRect.width;
+ let minDimension = Math.min(highlightHeight, highlightWidth);
+ let maxDimension = Math.max(highlightHeight, highlightWidth);
+
+ // If the dimensions are within 200% of each other (to include the bookmarks button),
+ // make the highlight a circle with the largest dimension as the diameter.
+ if (maxDimension / minDimension <= 3.0) {
+ highlightHeight = highlightWidth = maxDimension;
+ highlighter.style.borderRadius = "100%";
+ } else {
+ highlighter.style.borderRadius = "";
+ }
+
+ highlighter.style.height = highlightHeight + "px";
+ highlighter.style.width = highlightWidth + "px";
+
+ // Close a previous highlight so we can relocate the panel.
+ if (highlighter.parentElement.state == "showing" || highlighter.parentElement.state == "open") {
+ log.debug("showHighlight: Closing previous highlight first");
+ highlighter.parentElement.hidePopup();
+ }
+ /* The "overlap" position anchors from the top-left but we want to centre highlights at their
+ minimum size. */
+ let highlightWindow = aChromeWindow;
+ let containerStyle = highlightWindow.getComputedStyle(highlighter.parentElement);
+ let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
+ let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
+ let highlightStyle = highlightWindow.getComputedStyle(highlighter);
+ let highlightHeightWithMin = Math.max(highlightHeight, parseFloat(highlightStyle.minHeight));
+ let highlightWidthWithMin = Math.max(highlightWidth, parseFloat(highlightStyle.minWidth));
+ let offsetX = paddingTopPx
+ - (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
+ let offsetY = paddingLeftPx
+ - (Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
+ this._addAnnotationPanelMutationObserver(highlighter.parentElement);
+ highlighter.parentElement.openPopup(highlightAnchor, "overlap", offsetX, offsetY);
+ }
+
+ // Prevent showing a panel at an undefined position.
+ if (!this.isElementVisible(aTarget.node)) {
+ log.warn("showHighlight: Not showing a highlight since the target isn't visible", aTarget);
+ return;
+ }
+
+ this._setAppMenuStateForAnnotation(aChromeWindow, "highlight",
+ this.targetIsInAppMenu(aTarget),
+ showHighlightPanel.bind(this));
+ },
+
+ hideHighlight: function(aWindow) {
+ let highlighter = aWindow.document.getElementById("UITourHighlight");
+ this._removeAnnotationPanelMutationObserver(highlighter.parentElement);
+ highlighter.parentElement.hidePopup();
+ highlighter.removeAttribute("active");
+
+ this._setAppMenuStateForAnnotation(aWindow, "highlight", false);
+ },
+
+ /**
+ * Show an info panel.
+ *
+ * @param {ChromeWindow} aChromeWindow
+ * @param {Node} aAnchor
+ * @param {String} [aTitle=""]
+ * @param {String} [aDescription=""]
+ * @param {String} [aIconURL=""]
+ * @param {Object[]} [aButtons=[]]
+ * @param {Object} [aOptions={}]
+ * @param {String} [aOptions.closeButtonCallback]
+ * @param {String} [aOptions.targetCallback]
+ */
+ showInfo(aChromeWindow, aAnchor, aTitle = "", aDescription = "",
+ aIconURL = "", aButtons = [], aOptions = {}) {
+ function showInfoPanel(aAnchorEl) {
+ aAnchorEl.focus();
+
+ let document = aChromeWindow.document;
+ let tooltip = document.getElementById("UITourTooltip");
+ let tooltipTitle = document.getElementById("UITourTooltipTitle");
+ let tooltipDesc = document.getElementById("UITourTooltipDescription");
+ let tooltipIcon = document.getElementById("UITourTooltipIcon");
+ let tooltipButtons = document.getElementById("UITourTooltipButtons");
+
+ if (tooltip.state == "showing" || tooltip.state == "open") {
+ tooltip.hidePopup();
+ }
+
+ tooltipTitle.textContent = aTitle || "";
+ tooltipDesc.textContent = aDescription || "";
+ tooltipIcon.src = aIconURL || "";
+ tooltipIcon.hidden = !aIconURL;
+
+ while (tooltipButtons.firstChild)
+ tooltipButtons.firstChild.remove();
+
+ for (let button of aButtons) {
+ let isButton = button.style != "text";
+ let el = document.createElement(isButton ? "button" : "label");
+ el.setAttribute(isButton ? "label" : "value", button.label);
+
+ if (isButton) {
+ if (button.iconURL)
+ el.setAttribute("image", button.iconURL);
+
+ if (button.style == "link")
+ el.setAttribute("class", "button-link");
+
+ if (button.style == "primary")
+ el.setAttribute("class", "button-primary");
+
+ // Don't close the popup or call the callback for style=text as they
+ // aren't links/buttons.
+ let callback = button.callback;
+ el.addEventListener("command", event => {
+ tooltip.hidePopup();
+ callback(event);
+ });
+ }
+
+ tooltipButtons.appendChild(el);
+ }
+
+ tooltipButtons.hidden = !aButtons.length;
+
+ let tooltipClose = document.getElementById("UITourTooltipClose");
+ let closeButtonCallback = (event) => {
+ this.hideInfo(document.defaultView);
+ if (aOptions && aOptions.closeButtonCallback) {
+ aOptions.closeButtonCallback();
+ }
+ };
+ tooltipClose.addEventListener("command", closeButtonCallback);
+
+ let targetCallback = (event) => {
+ let details = {
+ target: aAnchor.targetName,
+ type: event.type,
+ };
+ aOptions.targetCallback(details);
+ };
+ if (aOptions.targetCallback && aAnchor.addTargetListener) {
+ aAnchor.addTargetListener(document, targetCallback);
+ }
+
+ tooltip.addEventListener("popuphiding", function tooltipHiding(event) {
+ tooltip.removeEventListener("popuphiding", tooltipHiding);
+ tooltipClose.removeEventListener("command", closeButtonCallback);
+ if (aOptions.targetCallback && aAnchor.removeTargetListener) {
+ aAnchor.removeTargetListener(document, targetCallback);
+ }
+ });
+
+ tooltip.setAttribute("targetName", aAnchor.targetName);
+ tooltip.hidden = false;
+ let alignment = "bottomcenter topright";
+ if (aAnchor.infoPanelPosition) {
+ alignment = aAnchor.infoPanelPosition;
+ }
+
+ let { infoPanelOffsetX: xOffset, infoPanelOffsetY: yOffset } = aAnchor;
+
+ this._addAnnotationPanelMutationObserver(tooltip);
+ tooltip.openPopup(aAnchorEl, alignment, xOffset || 0, yOffset || 0);
+ if (tooltip.state == "closed") {
+ document.defaultView.addEventListener("endmodalstate", function endModalStateHandler() {
+ document.defaultView.removeEventListener("endmodalstate", endModalStateHandler);
+ tooltip.openPopup(aAnchorEl, alignment);
+ }, false);
+ }
+ }
+
+ // Prevent showing a panel at an undefined position.
+ if (!this.isElementVisible(aAnchor.node)) {
+ log.warn("showInfo: Not showing since the target isn't visible", aAnchor);
+ return;
+ }
+
+ this._setAppMenuStateForAnnotation(aChromeWindow, "info",
+ this.targetIsInAppMenu(aAnchor),
+ showInfoPanel.bind(this, this._correctAnchor(aAnchor.node)));
+ },
+
+ isInfoOnTarget(aChromeWindow, aTargetName) {
+ let document = aChromeWindow.document;
+ let tooltip = document.getElementById("UITourTooltip");
+ return tooltip.getAttribute("targetName") == aTargetName && tooltip.state != "closed";
+ },
+
+ hideInfo: function(aWindow) {
+ let document = aWindow.document;
+
+ let tooltip = document.getElementById("UITourTooltip");
+ this._removeAnnotationPanelMutationObserver(tooltip);
+ tooltip.hidePopup();
+ this._setAppMenuStateForAnnotation(aWindow, "info", false);
+
+ let tooltipButtons = document.getElementById("UITourTooltipButtons");
+ while (tooltipButtons.firstChild)
+ tooltipButtons.firstChild.remove();
+ },
+
+ showMenu: function(aWindow, aMenuName, aOpenCallback = null) {
+ log.debug("showMenu:", aMenuName);
+ function openMenuButton(aMenuBtn) {
+ if (!aMenuBtn || !aMenuBtn.boxObject || aMenuBtn.open) {
+ if (aOpenCallback)
+ aOpenCallback();
+ return;
+ }
+ if (aOpenCallback)
+ aMenuBtn.addEventListener("popupshown", onPopupShown);
+ aMenuBtn.boxObject.openMenu(true);
+ }
+ function onPopupShown(event) {
+ this.removeEventListener("popupshown", onPopupShown);
+ aOpenCallback(event);
+ }
+
+ if (aMenuName == "appMenu") {
+ aWindow.PanelUI.panel.setAttribute("noautohide", "true");
+ // If the popup is already opened, don't recreate the widget as it may cause a flicker.
+ if (aWindow.PanelUI.panel.state != "open") {
+ this.recreatePopup(aWindow.PanelUI.panel);
+ }
+ aWindow.PanelUI.panel.addEventListener("popuphiding", this.hideAppMenuAnnotations);
+ aWindow.PanelUI.panel.addEventListener("ViewShowing", this.hideAppMenuAnnotations);
+ aWindow.PanelUI.panel.addEventListener("popuphidden", this.onPanelHidden);
+ if (aOpenCallback) {
+ aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
+ }
+ aWindow.PanelUI.show();
+ } else if (aMenuName == "bookmarks") {
+ let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
+ openMenuButton(menuBtn);
+ } else if (aMenuName == "controlCenter") {
+ let popup = aWindow.gIdentityHandler._identityPopup;
+
+ // Add the listener even if the panel is already open since it will still
+ // only get registered once even if it was UITour that opened it.
+ popup.addEventListener("popuphiding", this.hideControlCenterAnnotations);
+ popup.addEventListener("popuphidden", this.onPanelHidden);
+
+ popup.setAttribute("noautohide", true);
+ this.clearAvailableTargetsCache();
+
+ if (popup.state == "open") {
+ if (aOpenCallback) {
+ aOpenCallback();
+ }
+ return;
+ }
+
+ this.recreatePopup(popup);
+
+ // Open the control center
+ if (aOpenCallback) {
+ popup.addEventListener("popupshown", onPopupShown);
+ }
+ aWindow.document.getElementById("identity-box").click();
+ } else if (aMenuName == "pocket") {
+ this.getTarget(aWindow, "pocket").then(Task.async(function* onPocketTarget(target) {
+ let widgetGroupWrapper = CustomizableUI.getWidget(target.widgetName);
+ if (widgetGroupWrapper.type != "view" || !widgetGroupWrapper.viewId) {
+ log.error("Can't open the pocket menu without a view");
+ return;
+ }
+ let placement = CustomizableUI.getPlacementOfWidget(target.widgetName);
+ if (!placement || !placement.area) {
+ log.error("Can't open the pocket menu without a placement");
+ return;
+ }
+
+ if (placement.area == CustomizableUI.AREA_PANEL) {
+ // Open the appMenu and wait for it if it's not already opened or showing a subview.
+ yield new Promise((resolve, reject) => {
+ if (aWindow.PanelUI.panel.state != "closed") {
+ if (aWindow.PanelUI.multiView.showingSubView) {
+ reject("A subview is already showing");
+ return;
+ }
+
+ resolve();
+ return;
+ }
+
+ aWindow.PanelUI.panel.addEventListener("popupshown", function onShown() {
+ aWindow.PanelUI.panel.removeEventListener("popupshown", onShown);
+ resolve();
+ });
+
+ aWindow.PanelUI.show();
+ });
+ }
+
+ let widgetWrapper = widgetGroupWrapper.forWindow(aWindow);
+ aWindow.PanelUI.showSubView(widgetGroupWrapper.viewId,
+ widgetWrapper.anchor,
+ placement.area);
+ })).catch(log.error);
+ }
+ },
+
+ hideMenu: function(aWindow, aMenuName) {
+ log.debug("hideMenu:", aMenuName);
+ function closeMenuButton(aMenuBtn) {
+ if (aMenuBtn && aMenuBtn.boxObject)
+ aMenuBtn.boxObject.openMenu(false);
+ }
+
+ if (aMenuName == "appMenu") {
+ aWindow.PanelUI.hide();
+ } else if (aMenuName == "bookmarks") {
+ let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
+ closeMenuButton(menuBtn);
+ } else if (aMenuName == "controlCenter") {
+ let panel = aWindow.gIdentityHandler._identityPopup;
+ panel.hidePopup();
+ }
+ },
+
+ showNewTab: function(aWindow, aBrowser) {
+ aWindow.openLinkIn("about:newtab", "current", {targetBrowser: aBrowser});
+ },
+
+ hideAnnotationsForPanel: function(aEvent, aTargetPositionCallback) {
+ let win = aEvent.target.ownerGlobal;
+ let annotationElements = new Map([
+ // [annotationElement (panel), method to hide the annotation]
+ [win.document.getElementById("UITourHighlightContainer"), UITour.hideHighlight.bind(UITour)],
+ [win.document.getElementById("UITourTooltip"), UITour.hideInfo.bind(UITour)],
+ ]);
+ annotationElements.forEach((hideMethod, annotationElement) => {
+ if (annotationElement.state != "closed") {
+ let targetName = annotationElement.getAttribute("targetName");
+ UITour.getTarget(win, targetName).then((aTarget) => {
+ // Since getTarget is async, we need to make sure that the target hasn't
+ // changed since it may have just moved to somewhere outside of the app menu.
+ if (annotationElement.getAttribute("targetName") != aTarget.targetName ||
+ annotationElement.state == "closed" ||
+ !aTargetPositionCallback(aTarget)) {
+ return;
+ }
+ hideMethod(win);
+ }).catch(log.error);
+ }
+ });
+ UITour.appMenuOpenForAnnotation.clear();
+ },
+
+ hideAppMenuAnnotations: function(aEvent) {
+ UITour.hideAnnotationsForPanel(aEvent, UITour.targetIsInAppMenu);
+ },
+
+ hideControlCenterAnnotations(aEvent) {
+ UITour.hideAnnotationsForPanel(aEvent, (aTarget) => {
+ return aTarget.targetName.startsWith("controlCenter-");
+ });
+ },
+
+ onPanelHidden: function(aEvent) {
+ aEvent.target.removeAttribute("noautohide");
+ UITour.recreatePopup(aEvent.target);
+ UITour.clearAvailableTargetsCache();
+ },
+
+ recreatePopup: function(aPanel) {
+ // After changing popup attributes that relate to how the native widget is created
+ // (e.g. @noautohide) we need to re-create the frame/widget for it to take effect.
+ if (aPanel.hidden) {
+ // If the panel is already hidden, we don't need to recreate it but flush
+ // in case someone just hid it.
+ aPanel.clientWidth; // flush
+ return;
+ }
+ aPanel.hidden = true;
+ aPanel.clientWidth; // flush
+ aPanel.hidden = false;
+ },
+
+ getConfiguration: function(aMessageManager, aWindow, aConfiguration, aCallbackID) {
+ switch (aConfiguration) {
+ case "appinfo":
+ let props = ["defaultUpdateChannel", "version"];
+ let appinfo = {};
+ props.forEach(property => appinfo[property] = Services.appinfo[property]);
+
+ // Identifier of the partner repack, as stored in preference "distribution.id"
+ // and included in Firefox and other update pings. Note this is not the same as
+ // Services.appinfo.distributionID (value of MOZ_DISTRIBUTION_ID is set at build time).
+ let distribution = "default";
+ try {
+ distribution = Services.prefs.getDefaultBranch("distribution.").getCharPref("id");
+ } catch (e) {}
+ appinfo["distribution"] = distribution;
+
+ let isDefaultBrowser = null;
+ try {
+ let shell = aWindow.getShellService();
+ if (shell) {
+ isDefaultBrowser = shell.isDefaultBrowser(false);
+ }
+ } catch (e) {}
+ appinfo["defaultBrowser"] = isDefaultBrowser;
+
+ let canSetDefaultBrowserInBackground = true;
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2") ||
+ AppConstants.isPlatformAndVersionAtLeast("macosx", "10.10")) {
+ canSetDefaultBrowserInBackground = false;
+ } else if (AppConstants.platform == "linux") {
+ // The ShellService may not exist on some versions of Linux.
+ try {
+ aWindow.getShellService();
+ } catch (e) {
+ canSetDefaultBrowserInBackground = null;
+ }
+ }
+
+ appinfo["canSetDefaultBrowserInBackground"] =
+ canSetDefaultBrowserInBackground;
+
+ this.sendPageCallback(aMessageManager, aCallbackID, appinfo);
+ break;
+ case "availableTargets":
+ this.getAvailableTargets(aMessageManager, aWindow, aCallbackID);
+ break;
+ case "search":
+ case "selectedSearchEngine":
+ Services.search.init(rv => {
+ let data;
+ if (Components.isSuccessCode(rv)) {
+ let engines = Services.search.getVisibleEngines();
+ data = {
+ searchEngineIdentifier: Services.search.defaultEngine.identifier,
+ engines: engines.filter((engine) => engine.identifier)
+ .map((engine) => TARGET_SEARCHENGINE_PREFIX + engine.identifier)
+ };
+ } else {
+ data = {engines: [], searchEngineIdentifier: ""};
+ }
+ this.sendPageCallback(aMessageManager, aCallbackID, data);
+ });
+ break;
+ case "sync":
+ this.sendPageCallback(aMessageManager, aCallbackID, {
+ setup: Services.prefs.prefHasUserValue("services.sync.username"),
+ desktopDevices: Preferences.get("services.sync.clients.devices.desktop", 0),
+ mobileDevices: Preferences.get("services.sync.clients.devices.mobile", 0),
+ totalDevices: Preferences.get("services.sync.numClients", 0),
+ });
+ break;
+ case "canReset":
+ this.sendPageCallback(aMessageManager, aCallbackID, ResetProfile.resetSupported());
+ break;
+ default:
+ log.error("getConfiguration: Unknown configuration requested: " + aConfiguration);
+ break;
+ }
+ },
+
+ setConfiguration: function(aWindow, aConfiguration, aValue) {
+ switch (aConfiguration) {
+ case "defaultBrowser":
+ // Ignore aValue in this case because the default browser can only
+ // be set, not unset.
+ try {
+ let shell = aWindow.getShellService();
+ if (shell) {
+ shell.setDefaultBrowser(true, false);
+ }
+ } catch (e) {}
+ break;
+ default:
+ log.error("setConfiguration: Unknown configuration requested: " + aConfiguration);
+ break;
+ }
+ },
+
+ getAvailableTargets: function(aMessageManager, aChromeWindow, aCallbackID) {
+ Task.spawn(function*() {
+ let window = aChromeWindow;
+ let data = this.availableTargetsCache.get(window);
+ if (data) {
+ log.debug("getAvailableTargets: Using cached targets list", data.targets.join(","));
+ this.sendPageCallback(aMessageManager, aCallbackID, data);
+ return;
+ }
+
+ let promises = [];
+ for (let targetName of this.targets.keys()) {
+ promises.push(this.getTarget(window, targetName));
+ }
+ let targetObjects = yield Promise.all(promises);
+
+ let targetNames = [];
+ for (let targetObject of targetObjects) {
+ if (targetObject.node)
+ targetNames.push(targetObject.targetName);
+ }
+
+ data = {
+ targets: targetNames,
+ };
+ this.availableTargetsCache.set(window, data);
+ this.sendPageCallback(aMessageManager, aCallbackID, data);
+ }.bind(this)).catch(err => {
+ log.error(err);
+ this.sendPageCallback(aMessageManager, aCallbackID, {
+ targets: [],
+ });
+ });
+ },
+
+ startSubTour: function (aFeature) {
+ if (aFeature != "string") {
+ log.error("startSubTour: No feature option specified");
+ return;
+ }
+
+ if (aFeature == "readinglist") {
+ ReaderParent.showReaderModeInfoPanel(browser);
+ } else {
+ log.error("startSubTour: Unknown feature option specified");
+ return;
+ }
+ },
+
+ addNavBarWidget: function (aTarget, aMessageManager, aCallbackID) {
+ if (aTarget.node) {
+ log.error("addNavBarWidget: can't add a widget already present:", aTarget);
+ return;
+ }
+ if (!aTarget.allowAdd) {
+ log.error("addNavBarWidget: not allowed to add this widget:", aTarget);
+ return;
+ }
+ if (!aTarget.widgetName) {
+ log.error("addNavBarWidget: can't add a widget without a widgetName property:", aTarget);
+ return;
+ }
+
+ CustomizableUI.addWidgetToArea(aTarget.widgetName, CustomizableUI.AREA_NAVBAR);
+ this.sendPageCallback(aMessageManager, aCallbackID);
+ },
+
+ _addAnnotationPanelMutationObserver: function(aPanelEl) {
+ if (AppConstants.platform == "linux") {
+ let observer = this._annotationPanelMutationObservers.get(aPanelEl);
+ if (observer) {
+ return;
+ }
+ let win = aPanelEl.ownerGlobal;
+ observer = new win.MutationObserver(this._annotationMutationCallback);
+ this._annotationPanelMutationObservers.set(aPanelEl, observer);
+ let observerOptions = {
+ attributeFilter: ["height", "width"],
+ attributes: true,
+ };
+ observer.observe(aPanelEl, observerOptions);
+ }
+ },
+
+ _removeAnnotationPanelMutationObserver: function(aPanelEl) {
+ if (AppConstants.platform == "linux") {
+ let observer = this._annotationPanelMutationObservers.get(aPanelEl);
+ if (observer) {
+ observer.disconnect();
+ this._annotationPanelMutationObservers.delete(aPanelEl);
+ }
+ }
+ },
+
+/**
+ * Workaround for Ubuntu panel craziness in bug 970788 where incorrect sizes get passed to
+ * nsXULPopupManager::PopupResized and lead to incorrect width and height attributes getting
+ * set on the panel.
+ */
+ _annotationMutationCallback: function(aMutations) {
+ for (let mutation of aMutations) {
+ // Remove both attributes at once and ignore remaining mutations to be proccessed.
+ mutation.target.removeAttribute("width");
+ mutation.target.removeAttribute("height");
+ return;
+ }
+ },
+
+ selectSearchEngine(aID) {
+ return new Promise((resolve, reject) => {
+ Services.search.init((rv) => {
+ if (!Components.isSuccessCode(rv)) {
+ reject("selectSearchEngine: search service init failed: " + rv);
+ return;
+ }
+
+ let engines = Services.search.getVisibleEngines();
+ for (let engine of engines) {
+ if (engine.identifier == aID) {
+ Services.search.defaultEngine = engine;
+ resolve();
+ return;
+ }
+ }
+ reject("selectSearchEngine could not find engine with given ID");
+ return;
+ });
+ });
+ },
+
+ notify(eventName, params) {
+ let winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let window = winEnum.getNext();
+ if (window.closed)
+ continue;
+
+ let openTourBrowsers = this.tourBrowsersByWindow.get(window);
+ if (!openTourBrowsers)
+ continue;
+
+ for (let browser of openTourBrowsers) {
+ let messageManager = browser.messageManager;
+ if (!messageManager) {
+ log.error("notify: Trying to notify a browser without a messageManager", browser);
+ continue;
+ }
+ let detail = {
+ event: eventName,
+ params: params,
+ };
+ messageManager.sendAsyncMessage("UITour:SendPageNotification", detail);
+ }
+ }
+ },
+};
+
+function controlCenterTrackingToggleTarget(aUnblock) {
+ return {
+ infoPanelPosition: "rightcenter topleft",
+ query(aDocument) {
+ let popup = aDocument.defaultView.gIdentityHandler._identityPopup;
+ if (popup.state != "open") {
+ return null;
+ }
+ let buttonId = null;
+ if (aUnblock) {
+ if (PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView)) {
+ buttonId = "tracking-action-unblock-private";
+ } else {
+ buttonId = "tracking-action-unblock";
+ }
+ } else {
+ buttonId = "tracking-action-block";
+ }
+ let element = aDocument.getElementById(buttonId);
+ return UITour.isElementVisible(element) ? element : null;
+ },
+ };
+}
+
+this.UITour.init();
+
+/**
+ * UITour Health Report
+ */
+/**
+ * Public API to be called by the UITour code
+ */
+const UITourHealthReport = {
+ recordTreatmentTag: function(tag, value) {
+ return TelemetryController.submitExternalPing("uitour-tag",
+ {
+ version: 1,
+ tagName: tag,
+ tagValue: value,
+ },
+ {
+ addClientId: true,
+ addEnvironment: true,
+ });
+ }
+};
diff --git a/browser/components/uitour/content-UITour.js b/browser/components/uitour/content-UITour.js
new file mode 100644
index 000000000..c33d687e8
--- /dev/null
+++ b/browser/components/uitour/content-UITour.js
@@ -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/. */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const PREF_TEST_WHITELIST = "browser.uitour.testingOrigins";
+const UITOUR_PERMISSION = "uitour";
+
+var UITourListener = {
+ handleEvent: function (event) {
+ if (!Services.prefs.getBoolPref("browser.uitour.enabled")) {
+ return;
+ }
+ if (!this.ensureTrustedOrigin()) {
+ return;
+ }
+ addMessageListener("UITour:SendPageCallback", this);
+ addMessageListener("UITour:SendPageNotification", this);
+ sendAsyncMessage("UITour:onPageEvent", {
+ detail: event.detail,
+ type: event.type,
+ pageVisibilityState: content.document.visibilityState,
+ });
+ },
+
+ isTestingOrigin: function(aURI) {
+ if (Services.prefs.getPrefType(PREF_TEST_WHITELIST) != Services.prefs.PREF_STRING) {
+ return false;
+ }
+
+ // Add any testing origins (comma-seperated) to the whitelist for the session.
+ for (let origin of Services.prefs.getCharPref(PREF_TEST_WHITELIST).split(",")) {
+ try {
+ let testingURI = Services.io.newURI(origin, null, null);
+ if (aURI.prePath == testingURI.prePath) {
+ return true;
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ return false;
+ },
+
+ // This function is copied from UITour.jsm.
+ isSafeScheme: function(aURI) {
+ let allowedSchemes = new Set(["https", "about"]);
+ if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
+ allowedSchemes.add("http");
+
+ if (!allowedSchemes.has(aURI.scheme))
+ return false;
+
+ return true;
+ },
+
+ ensureTrustedOrigin: function() {
+ if (content.top != content)
+ return false;
+
+ let uri = content.document.documentURIObject;
+
+ if (uri.schemeIs("chrome"))
+ return true;
+
+ if (!this.isSafeScheme(uri))
+ return false;
+
+ let permission = Services.perms.testPermission(uri, UITOUR_PERMISSION);
+ if (permission == Services.perms.ALLOW_ACTION)
+ return true;
+
+ return this.isTestingOrigin(uri);
+ },
+
+ receiveMessage: function(aMessage) {
+ switch (aMessage.name) {
+ case "UITour:SendPageCallback":
+ this.sendPageEvent("Response", aMessage.data);
+ break;
+ case "UITour:SendPageNotification":
+ this.sendPageEvent("Notification", aMessage.data);
+ break;
+ }
+ },
+
+ sendPageEvent: function (type, detail) {
+ if (!this.ensureTrustedOrigin()) {
+ return;
+ }
+
+ let doc = content.document;
+ let eventName = "mozUITour" + type;
+ let event = new doc.defaultView.CustomEvent(eventName, {
+ bubbles: true,
+ detail: Cu.cloneInto(detail, doc.defaultView)
+ });
+ doc.dispatchEvent(event);
+ }
+};
+
+addEventListener("mozUITour", UITourListener, false, true);
diff --git a/browser/components/uitour/jar.mn b/browser/components/uitour/jar.mn
new file mode 100644
index 000000000..966a69c96
--- /dev/null
+++ b/browser/components/uitour/jar.mn
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+ content/browser/content-UITour.js
diff --git a/browser/components/uitour/moz.build b/browser/components/uitour/moz.build
new file mode 100644
index 000000000..51e6037cc
--- /dev/null
+++ b/browser/components/uitour/moz.build
@@ -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/.
+
+EXTRA_JS_MODULES += [
+ 'UITour.jsm',
+]
+
+JAR_MANIFESTS += ['jar.mn']
+
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser.ini',
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Tours')
diff --git a/browser/components/uitour/test/.eslintrc.js b/browser/components/uitour/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/components/uitour/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/components/uitour/test/browser.ini b/browser/components/uitour/test/browser.ini
new file mode 100644
index 000000000..ae027a738
--- /dev/null
+++ b/browser/components/uitour/test/browser.ini
@@ -0,0 +1,49 @@
+[DEFAULT]
+support-files =
+ head.js
+ image.png
+ uitour.html
+ ../UITour-lib.js
+
+[browser_backgroundTab.js]
+[browser_closeTab.js]
+[browser_fxa.js]
+skip-if = debug || asan # updateAppMenuItem leaks
+[browser_no_tabs.js]
+[browser_openPreferences.js]
+[browser_openSearchPanel.js]
+skip-if = true # Bug 1113038 - Intermittent "Popup was opened"
+[browser_trackingProtection.js]
+skip-if = os == "linux" # Intermittent NS_ERROR_NOT_AVAILABLE [nsIUrlClassifierDBService.beginUpdate]
+tag = trackingprotection
+support-files =
+ !/browser/base/content/test/general/benignPage.html
+ !/browser/base/content/test/general/trackingPage.html
+[browser_trackingProtection_tour.js]
+tag = trackingprotection
+[browser_showMenu_controlCenter.js]
+tag = trackingprotection
+[browser_UITour.js]
+skip-if = os == "linux" # Intermittent failures, bug 951965
+[browser_UITour2.js]
+[browser_UITour3.js]
+skip-if = os == "linux" # Linux: Bug 986760, Bug 989101.
+[browser_UITour_availableTargets.js]
+[browser_UITour_annotation_size_attributes.js]
+[browser_UITour_defaultBrowser.js]
+[browser_UITour_detach_tab.js]
+[browser_UITour_forceReaderMode.js]
+[browser_UITour_heartbeat.js]
+skip-if = os == "win" # Bug 1277107
+[browser_UITour_modalDialog.js]
+skip-if = os != "mac" # modal dialog disabling only working on OS X.
+[browser_UITour_observe.js]
+[browser_UITour_panel_close_annotation.js]
+skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
+[browser_UITour_pocket.js]
+skip-if = true # Disabled pending removal of pocket UI Tour
+[browser_UITour_registerPageID.js]
+[browser_UITour_resetProfile.js]
+[browser_UITour_showNewTab.js]
+[browser_UITour_sync.js]
+[browser_UITour_toggleReaderMode.js]
diff --git a/browser/components/uitour/test/browser_UITour.js b/browser/components/uitour/test/browser_UITour.js
new file mode 100644
index 000000000..964be0215
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -0,0 +1,408 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+Components.utils.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
+
+function test() {
+ UITourTest();
+}
+
+var tests = [
+ function test_untrusted_host(done) {
+ loadUITourTestPage(function() {
+ let bookmarksMenu = document.getElementById("bookmarks-menu-button");
+ is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
+
+ gContentAPI.showMenu("bookmarks");
+ is(bookmarksMenu.open, false, "Bookmark menu should not open on a untrusted host");
+
+ done();
+ }, "http://mochi.test:8888/");
+ },
+ function test_testing_host(done) {
+ // Add two testing origins intentionally surrounded by whitespace to be ignored.
+ Services.prefs.setCharPref("browser.uitour.testingOrigins",
+ "https://test1.example.org, https://test2.example.org:443 ");
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.uitour.testingOrigins");
+ });
+ function callback(result) {
+ ok(result, "Callback should be called on a testing origin");
+ done();
+ }
+
+ loadUITourTestPage(function() {
+ gContentAPI.getConfiguration("appinfo", callback);
+ }, "https://test2.example.org/");
+ },
+ function test_unsecure_host(done) {
+ loadUITourTestPage(function() {
+ let bookmarksMenu = document.getElementById("bookmarks-menu-button");
+ is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
+
+ gContentAPI.showMenu("bookmarks");
+ is(bookmarksMenu.open, false, "Bookmark menu should not open on a unsecure host");
+
+ done();
+ }, "http://example.org/");
+ },
+ function test_unsecure_host_override(done) {
+ Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
+ loadUITourTestPage(function() {
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar");
+ waitForElementToBeVisible(highlight, done, "Highlight should be shown on a unsecure host when override pref is set");
+
+ Services.prefs.setBoolPref("browser.uitour.requireSecure", true);
+ }, "http://example.org/");
+ },
+ function test_disabled(done) {
+ Services.prefs.setBoolPref("browser.uitour.enabled", false);
+
+ let bookmarksMenu = document.getElementById("bookmarks-menu-button");
+ is(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
+
+ gContentAPI.showMenu("bookmarks");
+ is(bookmarksMenu.open, false, "Bookmark menu should not open when feature is disabled");
+
+ Services.prefs.setBoolPref("browser.uitour.enabled", true);
+ done();
+ },
+ function test_highlight(done) {
+ function test_highlight_2() {
+ let highlight = document.getElementById("UITourHighlight");
+ gContentAPI.hideHighlight();
+
+ waitForElementToBeHidden(highlight, test_highlight_3, "Highlight should be hidden after hideHighlight()");
+ }
+ function test_highlight_3() {
+ is_element_hidden(highlight, "Highlight should be hidden after hideHighlight()");
+
+ gContentAPI.showHighlight("urlbar");
+ waitForElementToBeVisible(highlight, test_highlight_4, "Highlight should be shown after showHighlight()");
+ }
+ function test_highlight_4() {
+ let highlight = document.getElementById("UITourHighlight");
+ gContentAPI.showHighlight("backForward");
+ waitForElementToBeVisible(highlight, done, "Highlight should be shown after showHighlight()");
+ }
+
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar");
+ waitForElementToBeVisible(highlight, test_highlight_2, "Highlight should be shown after showHighlight()");
+ },
+ function test_highlight_circle(done) {
+ function check_highlight_size() {
+ let panel = highlight.parentElement;
+ let anchor = panel.anchorNode;
+ let anchorRect = anchor.getBoundingClientRect();
+ info("addons target: width: " + anchorRect.width + " height: " + anchorRect.height);
+ let maxDimension = Math.round(Math.max(anchorRect.width, anchorRect.height));
+ let highlightRect = highlight.getBoundingClientRect();
+ info("highlight: width: " + highlightRect.width + " height: " + highlightRect.height);
+ is(Math.round(highlightRect.width), maxDimension, "The width of the highlight should be equal to the largest dimension of the target");
+ is(Math.round(highlightRect.height), maxDimension, "The height of the highlight should be equal to the largest dimension of the target");
+ is(Math.round(highlightRect.height), Math.round(highlightRect.width), "The height and width of the highlight should be the same to create a circle");
+ is(highlight.style.borderRadius, "100%", "The border-radius should be 100% to create a circle");
+ done();
+ }
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("addons");
+ waitForElementToBeVisible(highlight, check_highlight_size, "Highlight should be shown after showHighlight()");
+ },
+ function test_highlight_customize_auto_open_close(done) {
+ let highlight = document.getElementById("UITourHighlight");
+ gContentAPI.showHighlight("customize");
+ waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Move the highlight outside which should close the app menu.
+ gContentAPI.showHighlight("appMenu");
+ waitForElementToBeVisible(highlight, function checkPanelIsClosed() {
+ isnot(PanelUI.panel.state, "open",
+ "Panel should have closed after the highlight moved elsewhere.");
+ done();
+ }, "Highlight should move to the appMenu button");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ },
+ function test_highlight_customize_manual_open_close(done) {
+ let highlight = document.getElementById("UITourHighlight");
+ // Manually open the app menu then show a highlight there. The menu should remain open.
+ let shownPromise = promisePanelShown(window);
+ gContentAPI.showMenu("appMenu");
+ shownPromise.then(() => {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+ gContentAPI.showHighlight("customize");
+
+ waitForElementToBeVisible(highlight, function checkPanelIsStillOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should still be open");
+
+ // Move the highlight outside which shouldn't close the app menu since it was manually opened.
+ gContentAPI.showHighlight("appMenu");
+ waitForElementToBeVisible(highlight, function () {
+ isnot(PanelUI.panel.state, "closed",
+ "Panel should remain open since UITour didn't open it in the first place");
+ gContentAPI.hideMenu("appMenu");
+ done();
+ }, "Highlight should move to the appMenu button");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ }).then(null, Components.utils.reportError);
+ },
+ function test_highlight_effect(done) {
+ function waitForHighlightWithEffect(highlightEl, effect, next, error) {
+ return waitForCondition(() => highlightEl.getAttribute("active") == effect,
+ next,
+ error);
+ }
+ function checkDefaultEffect() {
+ is(highlight.getAttribute("active"), "none", "The default should be no effect");
+
+ gContentAPI.showHighlight("urlbar", "none");
+ waitForHighlightWithEffect(highlight, "none", checkZoomEffect, "There should be no effect");
+ }
+ function checkZoomEffect() {
+ gContentAPI.showHighlight("urlbar", "zoom");
+ waitForHighlightWithEffect(highlight, "zoom", () => {
+ let style = window.getComputedStyle(highlight);
+ is(style.animationName, "uitour-zoom", "The animation-name should be uitour-zoom");
+ checkSameEffectOnDifferentTarget();
+ }, "There should be a zoom effect");
+ }
+ function checkSameEffectOnDifferentTarget() {
+ gContentAPI.showHighlight("appMenu", "wobble");
+ waitForHighlightWithEffect(highlight, "wobble", () => {
+ highlight.addEventListener("animationstart", function onAnimationStart(aEvent) {
+ highlight.removeEventListener("animationstart", onAnimationStart);
+ ok(true, "Animation occurred again even though the effect was the same");
+ checkRandomEffect();
+ });
+ gContentAPI.showHighlight("backForward", "wobble");
+ }, "There should be a wobble effect");
+ }
+ function checkRandomEffect() {
+ function waitForActiveHighlight(highlightEl, next, error) {
+ return waitForCondition(() => highlightEl.hasAttribute("active"),
+ next,
+ error);
+ }
+
+ gContentAPI.hideHighlight();
+ gContentAPI.showHighlight("urlbar", "random");
+ waitForActiveHighlight(highlight, () => {
+ ok(highlight.hasAttribute("active"), "The highlight should be active");
+ isnot(highlight.getAttribute("active"), "none", "A random effect other than none should have been chosen");
+ isnot(highlight.getAttribute("active"), "random", "The random effect shouldn't be 'random'");
+ isnot(UITour.highlightEffects.indexOf(highlight.getAttribute("active")), -1, "Check that a supported effect was randomly chosen");
+ done();
+ }, "There should be an active highlight with a random effect");
+ }
+
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar");
+ waitForElementToBeVisible(highlight, checkDefaultEffect, "Highlight should be shown after showHighlight()");
+ },
+ function test_highlight_effect_unsupported(done) {
+ function checkUnsupportedEffect() {
+ is(highlight.getAttribute("active"), "none", "No effect should be used when an unsupported effect is requested");
+ done();
+ }
+
+ let highlight = document.getElementById("UITourHighlight");
+ is_element_hidden(highlight, "Highlight should initially be hidden");
+
+ gContentAPI.showHighlight("urlbar", "__UNSUPPORTED__");
+ waitForElementToBeVisible(highlight, checkUnsupportedEffect, "Highlight should be shown after showHighlight()");
+ },
+ function test_info_1(done) {
+ let popup = document.getElementById("UITourTooltip");
+ let title = document.getElementById("UITourTooltipTitle");
+ let desc = document.getElementById("UITourTooltipDescription");
+ let icon = document.getElementById("UITourTooltipIcon");
+ let buttons = document.getElementById("UITourTooltipButtons");
+
+ popup.addEventListener("popupshown", function onPopupShown() {
+ popup.removeEventListener("popupshown", onPopupShown);
+ is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
+ is(title.textContent, "test title", "Popup should have correct title");
+ is(desc.textContent, "test text", "Popup should have correct description text");
+ is(icon.src, "", "Popup should have no icon");
+ is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+
+ popup.addEventListener("popuphidden", function onPopupHidden() {
+ popup.removeEventListener("popuphidden", onPopupHidden);
+
+ popup.addEventListener("popupshown", function onPopupShown() {
+ popup.removeEventListener("popupshown", onPopupShown);
+ done();
+ });
+
+ gContentAPI.showInfo("urlbar", "test title", "test text");
+
+ });
+ gContentAPI.hideInfo();
+ });
+
+ gContentAPI.showInfo("urlbar", "test title", "test text");
+ },
+ taskify(function* test_info_2() {
+ let popup = document.getElementById("UITourTooltip");
+ let title = document.getElementById("UITourTooltipTitle");
+ let desc = document.getElementById("UITourTooltipDescription");
+ let icon = document.getElementById("UITourTooltipIcon");
+ let buttons = document.getElementById("UITourTooltipButtons");
+
+ yield showInfoPromise("urlbar", "urlbar title", "urlbar text");
+
+ is(popup.popupBoxObject.anchorNode, document.getElementById("urlbar"), "Popup should be anchored to the urlbar");
+ is(title.textContent, "urlbar title", "Popup should have correct title");
+ is(desc.textContent, "urlbar text", "Popup should have correct description text");
+ is(icon.src, "", "Popup should have no icon");
+ is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+
+ yield showInfoPromise("search", "search title", "search text");
+
+ is(popup.popupBoxObject.anchorNode, document.getElementById("searchbar"), "Popup should be anchored to the searchbar");
+ is(title.textContent, "search title", "Popup should have correct title");
+ is(desc.textContent, "search text", "Popup should have correct description text");
+ }),
+ function test_getConfigurationVersion(done) {
+ function callback(result) {
+ let props = ["defaultUpdateChannel", "version"];
+ for (let property of props) {
+ ok(typeof(result[property]) !== "undefined", "Check " + property + " isn't undefined.");
+ is(result[property], Services.appinfo[property], "Should have the same " + property + " property.");
+ }
+ done();
+ }
+
+ gContentAPI.getConfiguration("appinfo", callback);
+ },
+ function test_getConfigurationDistribution(done) {
+ gContentAPI.getConfiguration("appinfo", (result) => {
+ ok(typeof(result.distribution) !== "undefined", "Check distribution isn't undefined.");
+ is(result.distribution, "default", "Should be \"default\" without preference set.");
+
+ let defaults = Services.prefs.getDefaultBranch("distribution.");
+ let testDistributionID = "TestDistribution";
+ defaults.setCharPref("id", testDistributionID);
+ gContentAPI.getConfiguration("appinfo", (result) => {
+ ok(typeof(result.distribution) !== "undefined", "Check distribution isn't undefined.");
+ is(result.distribution, testDistributionID, "Should have the distribution as set in preference.");
+
+ done();
+ });
+ });
+ },
+ function test_addToolbarButton(done) {
+ let placement = CustomizableUI.getPlacementOfWidget("panic-button");
+ is(placement, null, "default UI has panic button in the palette");
+
+ gContentAPI.getConfiguration("availableTargets", (data) => {
+ let available = (data.targets.indexOf("forget") != -1);
+ ok(!available, "Forget button should not be available by default");
+
+ gContentAPI.addNavBarWidget("forget", () => {
+ info("addNavBarWidget callback successfully called");
+
+ let placement = CustomizableUI.getPlacementOfWidget("panic-button");
+ is(placement.area, CustomizableUI.AREA_NAVBAR);
+
+ gContentAPI.getConfiguration("availableTargets", (data) => {
+ let available = (data.targets.indexOf("forget") != -1);
+ ok(available, "Forget button should now be available");
+
+ // Cleanup
+ CustomizableUI.removeWidgetFromArea("panic-button");
+ done();
+ });
+ });
+ });
+ },
+ function test_search(done) {
+ Services.search.init(rv => {
+ if (!Components.isSuccessCode(rv)) {
+ ok(false, "search service init failed: " + rv);
+ done();
+ return;
+ }
+ let defaultEngine = Services.search.defaultEngine;
+ gContentAPI.getConfiguration("search", data => {
+ let visibleEngines = Services.search.getVisibleEngines();
+ let expectedEngines = visibleEngines.filter((engine) => engine.identifier)
+ .map((engine) => "searchEngine-" + engine.identifier);
+
+ let engines = data.engines;
+ ok(Array.isArray(engines), "data.engines should be an array");
+ is(engines.sort().toString(), expectedEngines.sort().toString(),
+ "Engines should be as expected");
+
+ is(data.searchEngineIdentifier, defaultEngine.identifier,
+ "the searchEngineIdentifier property should contain the defaultEngine's identifier");
+
+ let someOtherEngineID = data.engines.filter(t => t != "searchEngine-" + defaultEngine.identifier)[0];
+ someOtherEngineID = someOtherEngineID.replace(/^searchEngine-/, "");
+
+ let observe = function (subject, topic, verb) {
+ info("browser-search-engine-modified: " + verb);
+ if (verb == "engine-current") {
+ is(Services.search.defaultEngine.identifier, someOtherEngineID, "correct engine was switched to");
+ done();
+ }
+ };
+ Services.obs.addObserver(observe, "browser-search-engine-modified", false);
+ registerCleanupFunction(() => {
+ // Clean up
+ Services.obs.removeObserver(observe, "browser-search-engine-modified");
+ Services.search.defaultEngine = defaultEngine;
+ });
+
+ gContentAPI.setDefaultSearchEngine(someOtherEngineID);
+ });
+ });
+ },
+ taskify(function* test_treatment_tag() {
+ let ac = new TelemetryArchiveTesting.Checker();
+ yield ac.promiseInit();
+ yield gContentAPI.setTreatmentTag("foobar", "baz");
+ // Wait until the treatment telemetry is sent before looking in the archive.
+ yield BrowserTestUtils.waitForContentEvent(gTestTab.linkedBrowser, "mozUITourNotification", false,
+ event => event.detail.event === "TreatmentTag:TelemetrySent");
+ yield new Promise((resolve) => {
+ gContentAPI.getTreatmentTag("foobar", (data) => {
+ is(data.value, "baz", "set and retrieved treatmentTag");
+ ac.promiseFindPing("uitour-tag", [
+ [["payload", "tagName"], "foobar"],
+ [["payload", "tagValue"], "baz"],
+ ]).then((found) => {
+ ok(found, "Telemetry ping submitted for setTreatmentTag");
+ resolve();
+ }, (err) => {
+ ok(false, "Exception finding uitour telemetry ping: " + err);
+ resolve();
+ });
+ });
+ });
+ }),
+
+ // Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
+ taskify(function* cleanupMenus() {
+ let shownPromise = promisePanelShown(window);
+ gContentAPI.showMenu("appMenu");
+ yield shownPromise;
+ }),
+];
diff --git a/browser/components/uitour/test/browser_UITour2.js b/browser/components/uitour/test/browser_UITour2.js
new file mode 100644
index 000000000..e74a71afa
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour2.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+function test() {
+ UITourTest();
+}
+
+var tests = [
+ function test_info_customize_auto_open_close(done) {
+ let popup = document.getElementById("UITourTooltip");
+ gContentAPI.showInfo("customize", "Customization", "Customize me please!");
+ UITour.getTarget(window, "customize").then((customizeTarget) => {
+ waitForPopupAtAnchor(popup, customizeTarget.node, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened before the popup anchored");
+ ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
+
+ // Move the info outside which should close the app menu.
+ gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
+ UITour.getTarget(window, "appMenu").then((target) => {
+ waitForPopupAtAnchor(popup, target.node, function checkPanelIsClosed() {
+ isnot(PanelUI.panel.state, "open",
+ "Panel should have closed after the info moved elsewhere.");
+ ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
+ done();
+ }, "Info should move to the appMenu button");
+ });
+ }, "Info panel should be anchored to the customize button");
+ });
+ },
+ function test_info_customize_manual_open_close(done) {
+ let popup = document.getElementById("UITourTooltip");
+ // Manually open the app menu then show an info panel there. The menu should remain open.
+ let shownPromise = promisePanelShown(window);
+ gContentAPI.showMenu("appMenu");
+ shownPromise.then(() => {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+ ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been set");
+ gContentAPI.showInfo("customize", "Customization", "Customize me please!");
+
+ UITour.getTarget(window, "customize").then((customizeTarget) => {
+ waitForPopupAtAnchor(popup, customizeTarget.node, function checkMenuIsStillOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should still be open");
+ ok(PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should still be set");
+
+ // Move the info outside which shouldn't close the app menu since it was manually opened.
+ gContentAPI.showInfo("appMenu", "Open Me", "You know you want to");
+ UITour.getTarget(window, "appMenu").then((target) => {
+ waitForPopupAtAnchor(popup, target.node, function checkMenuIsStillOpen() {
+ isnot(PanelUI.panel.state, "closed",
+ "Menu should remain open since UITour didn't open it in the first place");
+ waitForElementToBeHidden(window.PanelUI.panel, () => {
+ ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
+ done();
+ });
+ gContentAPI.hideMenu("appMenu");
+ }, "Info should move to the appMenu button");
+ });
+ }, "Info should be shown after showInfo() for fixed menu panel items");
+ });
+ }).then(null, Components.utils.reportError);
+ },
+ taskify(function* test_bookmarks_menu() {
+ let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
+
+ is(bookmarksMenuButton.open, false, "Menu should initially be closed");
+ gContentAPI.showMenu("bookmarks");
+
+ yield waitForConditionPromise(() => {
+ return bookmarksMenuButton.open;
+ }, "Menu should be visible after showMenu()");
+
+ gContentAPI.hideMenu("bookmarks");
+ yield waitForConditionPromise(() => {
+ return !bookmarksMenuButton.open;
+ }, "Menu should be hidden after hideMenu()");
+ }),
+];
diff --git a/browser/components/uitour/test/browser_UITour3.js b/browser/components/uitour/test/browser_UITour3.js
new file mode 100644
index 000000000..b852339f1
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour3.js
@@ -0,0 +1,181 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+requestLongerTimeout(2);
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_info_icon() {
+ let popup = document.getElementById("UITourTooltip");
+ let title = document.getElementById("UITourTooltipTitle");
+ let desc = document.getElementById("UITourTooltipDescription");
+ let icon = document.getElementById("UITourTooltipIcon");
+ let buttons = document.getElementById("UITourTooltipButtons");
+
+ // Disable the animation to prevent the mouse clicks from hitting the main
+ // window during the transition instead of the buttons in the popup.
+ popup.setAttribute("animate", "false");
+
+ yield showInfoPromise("urlbar", "a title", "some text", "image.png");
+
+ is(title.textContent, "a title", "Popup should have correct title");
+ is(desc.textContent, "some text", "Popup should have correct description text");
+
+ let imageURL = getRootDirectory(gTestPath) + "image.png";
+ imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
+ is(icon.src, imageURL, "Popup should have correct icon shown");
+
+ is(buttons.hasChildNodes(), false, "Popup should have no buttons");
+}),
+
+add_UITour_task(function* test_info_buttons_1() {
+ let popup = document.getElementById("UITourTooltip");
+ let title = document.getElementById("UITourTooltipTitle");
+ let desc = document.getElementById("UITourTooltipDescription");
+ let icon = document.getElementById("UITourTooltipIcon");
+
+ yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", "makeButtons");
+
+ is(title.textContent, "another title", "Popup should have correct title");
+ is(desc.textContent, "moar text", "Popup should have correct description text");
+
+ let imageURL = getRootDirectory(gTestPath) + "image.png";
+ imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
+ is(icon.src, imageURL, "Popup should have correct icon shown");
+
+ let buttons = document.getElementById("UITourTooltipButtons");
+ is(buttons.childElementCount, 4, "Popup should have four buttons");
+
+ is(buttons.childNodes[0].nodeName, "label", "Text label should be a <label>");
+ is(buttons.childNodes[0].getAttribute("value"), "Regular text", "Text label should have correct value");
+ is(buttons.childNodes[0].getAttribute("image"), "", "Text should have no image");
+ is(buttons.childNodes[0].className, "", "Text should have no class");
+
+ is(buttons.childNodes[1].nodeName, "button", "Link should be a <button>");
+ is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
+ is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
+ is(buttons.childNodes[1].className, "button-link", "Check link class");
+
+ is(buttons.childNodes[2].nodeName, "button", "Button 1 should be a <button>");
+ is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
+ is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
+ is(buttons.childNodes[2].className, "", "Button 1 should have no class");
+
+ is(buttons.childNodes[3].nodeName, "button", "Button 2 should be a <button>");
+ is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
+ is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
+ is(buttons.childNodes[3].className, "button-primary", "Check button 2 class");
+
+ let promiseHidden = promisePanelElementHidden(window, popup);
+ EventUtils.synthesizeMouseAtCenter(buttons.childNodes[2], {}, window);
+ yield promiseHidden;
+
+ ok(true, "Popup should close automatically");
+
+ let returnValue = yield waitForCallbackResultPromise();
+ is(returnValue.result, "button1", "Correct callback should have been called");
+});
+
+add_UITour_task(function* test_info_buttons_2() {
+ let popup = document.getElementById("UITourTooltip");
+ let title = document.getElementById("UITourTooltipTitle");
+ let desc = document.getElementById("UITourTooltipDescription");
+ let icon = document.getElementById("UITourTooltipIcon");
+
+ yield showInfoPromise("urlbar", "another title", "moar text", "./image.png", "makeButtons");
+
+ is(title.textContent, "another title", "Popup should have correct title");
+ is(desc.textContent, "moar text", "Popup should have correct description text");
+
+ let imageURL = getRootDirectory(gTestPath) + "image.png";
+ imageURL = imageURL.replace("chrome://mochitests/content/", "https://example.org/");
+ is(icon.src, imageURL, "Popup should have correct icon shown");
+
+ let buttons = document.getElementById("UITourTooltipButtons");
+ is(buttons.childElementCount, 4, "Popup should have four buttons");
+
+ is(buttons.childNodes[1].getAttribute("label"), "Link", "Link should have correct label");
+ is(buttons.childNodes[1].getAttribute("image"), "", "Link should have no image");
+ ok(buttons.childNodes[1].classList.contains("button-link"), "Link should have button-link class");
+
+ is(buttons.childNodes[2].getAttribute("label"), "Button 1", "First button should have correct label");
+ is(buttons.childNodes[2].getAttribute("image"), "", "First button should have no image");
+
+ is(buttons.childNodes[3].getAttribute("label"), "Button 2", "Second button should have correct label");
+ is(buttons.childNodes[3].getAttribute("image"), imageURL, "Second button should have correct image");
+
+ let promiseHidden = promisePanelElementHidden(window, popup);
+ EventUtils.synthesizeMouseAtCenter(buttons.childNodes[3], {}, window);
+ yield promiseHidden;
+
+ ok(true, "Popup should close automatically");
+
+ let returnValue = yield waitForCallbackResultPromise();
+
+ is(returnValue.result, "button2", "Correct callback should have been called");
+}),
+
+add_UITour_task(function* test_info_close_button() {
+ let closeButton = document.getElementById("UITourTooltipClose");
+
+ yield showInfoPromise("urlbar", "Close me", "X marks the spot", null, null, "makeInfoOptions");
+
+ EventUtils.synthesizeMouseAtCenter(closeButton, {}, window);
+
+ let returnValue = yield waitForCallbackResultPromise();
+
+ is(returnValue.result, "closeButton", "Close button callback called");
+}),
+
+add_UITour_task(function* test_info_target_callback() {
+ let popup = document.getElementById("UITourTooltip");
+
+ yield showInfoPromise("appMenu", "I want to know when the target is clicked", "*click*", null, null, "makeInfoOptions");
+
+ yield PanelUI.show();
+
+ let returnValue = yield waitForCallbackResultPromise();
+
+ is(returnValue.result, "target", "target callback called");
+ is(returnValue.data.target, "appMenu", "target callback was from the appMenu");
+ is(returnValue.data.type, "popupshown", "target callback was from the mousedown");
+
+ // Cleanup.
+ yield hideInfoPromise();
+
+ popup.removeAttribute("animate");
+}),
+
+add_UITour_task(function* test_getConfiguration_selectedSearchEngine() {
+ yield new Promise((resolve) => {
+ Services.search.init(Task.async(function*(rv) {
+ ok(Components.isSuccessCode(rv), "Search service initialized");
+ let engine = Services.search.defaultEngine;
+ let data = yield getConfigurationPromise("selectedSearchEngine");
+ is(data.searchEngineIdentifier, engine.identifier, "Correct engine identifier");
+ resolve();
+ }));
+ });
+});
+
+add_UITour_task(function* test_setSearchTerm() {
+ const TERM = "UITour Search Term";
+ yield gContentAPI.setSearchTerm(TERM);
+
+ let searchbar = document.getElementById("searchbar");
+ // The UITour gets to the searchbar element through a promise, so the value setting
+ // only happens after a tick.
+ yield waitForConditionPromise(() => searchbar.value == TERM, "Correct term set");
+});
+
+add_UITour_task(function* test_clearSearchTerm() {
+ yield gContentAPI.setSearchTerm("");
+
+ let searchbar = document.getElementById("searchbar");
+ // The UITour gets to the searchbar element through a promise, so the value setting
+ // only happens after a tick.
+ yield waitForConditionPromise(() => searchbar.value == "", "Search term cleared");
+});
diff --git a/browser/components/uitour/test/browser_UITour_annotation_size_attributes.js b/browser/components/uitour/test/browser_UITour_annotation_size_attributes.js
new file mode 100644
index 000000000..dbdeb9589
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_annotation_size_attributes.js
@@ -0,0 +1,42 @@
+/*
+ * Test that width and height attributes don't get set by widget code on the highlight panel.
+ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+var highlight = document.getElementById("UITourHighlightContainer");
+var tooltip = document.getElementById("UITourTooltip");
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_highlight_size_attributes() {
+ yield gContentAPI.showHighlight("appMenu");
+ yield elementVisiblePromise(highlight,
+ "Highlight should be shown after showHighlight() for the appMenu");
+ yield gContentAPI.showHighlight("urlbar");
+ yield elementVisiblePromise(highlight, "Highlight should be moved to the urlbar");
+ yield new Promise((resolve) => {
+ SimpleTest.executeSoon(() => {
+ is(highlight.height, "", "Highlight panel should have no explicit height set");
+ is(highlight.width, "", "Highlight panel should have no explicit width set");
+ resolve();
+ });
+ });
+});
+
+add_UITour_task(function* test_info_size_attributes() {
+ yield gContentAPI.showInfo("appMenu", "test title", "test text");
+ yield elementVisiblePromise(tooltip, "Tooltip should be shown after showInfo() for the appMenu");
+ yield gContentAPI.showInfo("urlbar", "new title", "new text");
+ yield elementVisiblePromise(tooltip, "Tooltip should be moved to the urlbar");
+ yield new Promise((resolve) => {
+ SimpleTest.executeSoon(() => {
+ is(tooltip.height, "", "Info panel should have no explicit height set");
+ is(tooltip.width, "", "Info panel should have no explicit width set");
+ resolve();
+ });
+ });
+});
diff --git a/browser/components/uitour/test/browser_UITour_availableTargets.js b/browser/components/uitour/test/browser_UITour_availableTargets.js
new file mode 100644
index 000000000..a6e96e31f
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_availableTargets.js
@@ -0,0 +1,114 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+var hasWebIDE = Services.prefs.getBoolPref("devtools.webide.widget.enabled");
+var hasPocket = Services.prefs.getBoolPref("extensions.pocket.enabled");
+
+requestLongerTimeout(2);
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_availableTargets() {
+ let data = yield getConfigurationPromise("availableTargets");
+ ok_targets(data, [
+ "accountStatus",
+ "addons",
+ "appMenu",
+ "backForward",
+ "bookmarks",
+ "customize",
+ "help",
+ "home",
+ "devtools",
+ ...(hasPocket ? ["pocket"] : []),
+ "privateWindow",
+ "quit",
+ "readerMode-urlBar",
+ "search",
+ "searchIcon",
+ "trackingProtection",
+ "urlbar",
+ ...(hasWebIDE ? ["webide"] : [])
+ ]);
+
+ ok(UITour.availableTargetsCache.has(window),
+ "Targets should now be cached");
+});
+
+add_UITour_task(function* test_availableTargets_changeWidgets() {
+ CustomizableUI.removeWidgetFromArea("bookmarks-menu-button");
+ ok(!UITour.availableTargetsCache.has(window),
+ "Targets should be evicted from cache after widget change");
+ let data = yield getConfigurationPromise("availableTargets");
+ ok_targets(data, [
+ "accountStatus",
+ "addons",
+ "appMenu",
+ "backForward",
+ "customize",
+ "help",
+ "devtools",
+ "home",
+ ...(hasPocket ? ["pocket"] : []),
+ "privateWindow",
+ "quit",
+ "readerMode-urlBar",
+ "search",
+ "searchIcon",
+ "trackingProtection",
+ "urlbar",
+ ...(hasWebIDE ? ["webide"] : [])
+ ]);
+
+ ok(UITour.availableTargetsCache.has(window),
+ "Targets should now be cached again");
+ CustomizableUI.reset();
+ ok(!UITour.availableTargetsCache.has(window),
+ "Targets should not be cached after reset");
+});
+
+add_UITour_task(function* test_availableTargets_exceptionFromGetTarget() {
+ // The query function for the "search" target will throw if it's not found.
+ // Make sure the callback still fires with the other available targets.
+ CustomizableUI.removeWidgetFromArea("search-container");
+ let data = yield getConfigurationPromise("availableTargets");
+ // Default minus "search" and "searchIcon"
+ ok_targets(data, [
+ "accountStatus",
+ "addons",
+ "appMenu",
+ "backForward",
+ "bookmarks",
+ "customize",
+ "help",
+ "home",
+ "devtools",
+ ...(hasPocket ? ["pocket"] : []),
+ "privateWindow",
+ "quit",
+ "readerMode-urlBar",
+ "trackingProtection",
+ "urlbar",
+ ...(hasWebIDE ? ["webide"] : [])
+ ]);
+
+ CustomizableUI.reset();
+});
+
+function ok_targets(actualData, expectedTargets) {
+ // Depending on how soon after page load this is called, the selected tab icon
+ // may or may not be showing the loading throbber. Check for its presence and
+ // insert it into expectedTargets if it's visible.
+ let selectedTabIcon =
+ document.getAnonymousElementByAttribute(gBrowser.selectedTab,
+ "anonid",
+ "tab-icon-image");
+ if (selectedTabIcon && UITour.isElementVisible(selectedTabIcon))
+ expectedTargets.push("selectedTabIcon");
+
+ ok(Array.isArray(actualData.targets), "data.targets should be an array");
+ is(actualData.targets.sort().toString(), expectedTargets.sort().toString(),
+ "Targets should be as expected");
+}
diff --git a/browser/components/uitour/test/browser_UITour_defaultBrowser.js b/browser/components/uitour/test/browser_UITour_defaultBrowser.js
new file mode 100644
index 000000000..5ebf553b0
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_defaultBrowser.js
@@ -0,0 +1,61 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+var setDefaultBrowserCalled = false;
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochikit/content/tests/SimpleTest/MockObjects.js", this);
+
+function MockShellService() {}
+MockShellService.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIShellService]),
+ isDefaultBrowser: function(aStartupCheck, aForAllTypes) { return false; },
+ setDefaultBrowser: function(aClaimAllTypes, aForAllUsers) {
+ setDefaultBrowserCalled = true;
+ },
+ shouldCheckDefaultBrowser: false,
+ canSetDesktopBackground: false,
+ BACKGROUND_TILE : 1,
+ BACKGROUND_STRETCH : 2,
+ BACKGROUND_CENTER : 3,
+ BACKGROUND_FILL : 4,
+ BACKGROUND_FIT : 5,
+ setDesktopBackground: function(aElement, aPosition) {},
+ APPLICATION_MAIL : 0,
+ APPLICATION_NEWS : 1,
+ openApplication: function(aApplication) {},
+ desktopBackgroundColor: 0,
+ openApplicationWithURI: function(aApplication, aURI) {},
+ defaultFeedReader: 0,
+};
+
+var mockShellService = new MockObjectRegisterer("@mozilla.org/browser/shell-service;1",
+ MockShellService);
+
+// Temporarily disabled, see note at test_setDefaultBrowser.
+// mockShellService.register();
+
+add_task(setup_UITourTest);
+
+/* This test is disabled (bug 1180714) since the MockObjectRegisterer
+ is not actually replacing the original ShellService.
+add_UITour_task(function* test_setDefaultBrowser() {
+ try {
+ yield gContentAPI.setConfiguration("defaultBrowser");
+ ok(setDefaultBrowserCalled, "setDefaultBrowser called");
+ } finally {
+ mockShellService.unregister();
+ }
+});
+*/
+
+add_UITour_task(function* test_isDefaultBrowser() {
+ let shell = Components.classes["@mozilla.org/browser/shell-service;1"]
+ .getService(Components.interfaces.nsIShellService);
+ let isDefault = shell.isDefaultBrowser(false);
+ let data = yield getConfigurationPromise("appinfo");
+ is(isDefault, data.defaultBrowser, "gContentAPI result should match shellService.isDefaultBrowser");
+});
diff --git a/browser/components/uitour/test/browser_UITour_detach_tab.js b/browser/components/uitour/test/browser_UITour_detach_tab.js
new file mode 100644
index 000000000..b8edf6dc4
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_detach_tab.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Detaching a tab to a new window shouldn't break the menu panel.
+ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+var gContentDoc;
+
+function test() {
+ registerCleanupFunction(function() {
+ gContentDoc = null;
+ });
+ UITourTest();
+}
+
+/**
+ * When tab is changed we're tearing the tour down. So the UITour client has to always be aware of this
+ * fact and therefore listens to visibilitychange events.
+ * In particular this scenario happens for detaching the tab (ie. moving it to a new window).
+ */
+var tests = [
+ taskify(function* test_move_tab_to_new_window() {
+ const myDocIdentifier = "Hello, I'm a unique expando to identify this document.";
+
+ let highlight = document.getElementById("UITourHighlight");
+ let windowDestroyedDeferred = Promise.defer();
+ let onDOMWindowDestroyed = (aWindow) => {
+ if (gContentWindow && aWindow == gContentWindow) {
+ Services.obs.removeObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
+ windowDestroyedDeferred.resolve();
+ }
+ };
+
+ let browserStartupDeferred = Promise.defer();
+ Services.obs.addObserver(function onBrowserDelayedStartup(aWindow) {
+ Services.obs.removeObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished");
+ browserStartupDeferred.resolve(aWindow);
+ }, "browser-delayed-startup-finished", false);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, myDocIdentifier, myDocIdentifier => {
+ let onVisibilityChange = () => {
+ if (!content.document.hidden) {
+ let win = Cu.waiveXrays(content);
+ win.Mozilla.UITour.showHighlight("appMenu");
+ }
+ };
+ content.document.addEventListener("visibilitychange", onVisibilityChange);
+ content.document.myExpando = myDocIdentifier;
+ });
+ gContentAPI.showHighlight("appMenu");
+
+ yield elementVisiblePromise(highlight);
+
+ gContentWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ yield browserStartupDeferred.promise;
+
+ // This highlight should be shown thanks to the visibilitychange listener.
+ let newWindowHighlight = gContentWindow.document.getElementById("UITourHighlight");
+ yield elementVisiblePromise(newWindowHighlight);
+
+ let selectedTab = gContentWindow.gBrowser.selectedTab;
+ yield ContentTask.spawn(selectedTab.linkedBrowser, myDocIdentifier, myDocIdentifier => {
+ is(content.document.myExpando, myDocIdentifier, "Document should be selected in new window");
+ });
+ ok(UITour.tourBrowsersByWindow && UITour.tourBrowsersByWindow.has(gContentWindow), "Window should be known");
+ ok(UITour.tourBrowsersByWindow.get(gContentWindow).has(selectedTab.linkedBrowser), "Selected browser should be known");
+
+ // Need this because gContentAPI in e10s land will try to use gTestTab to
+ // spawn a content task, which doesn't work if the tab is dead, for obvious
+ // reasons.
+ gTestTab = gContentWindow.gBrowser.selectedTab;
+
+ let shownPromise = promisePanelShown(gContentWindow);
+ gContentAPI.showMenu("appMenu");
+ yield shownPromise;
+
+ isnot(gContentWindow.PanelUI.panel.state, "closed", "Panel should be open");
+ ok(gContentWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
+ gContentAPI.hideHighlight();
+ gContentAPI.hideMenu("appMenu");
+ gTestTab = null;
+
+ Services.obs.addObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
+ gContentWindow.close();
+
+ yield windowDestroyedDeferred.promise;
+ }),
+];
diff --git a/browser/components/uitour/test/browser_UITour_forceReaderMode.js b/browser/components/uitour/test/browser_UITour_forceReaderMode.js
new file mode 100644
index 000000000..5b5e883c3
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_forceReaderMode.js
@@ -0,0 +1,17 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function*() {
+ ok(!gBrowser.selectedBrowser.isArticle, "Should not be an article when we start");
+ ok(document.getElementById("reader-mode-button").hidden, "Button should be hidden.");
+ yield gContentAPI.forceShowReaderIcon();
+ yield waitForConditionPromise(() => gBrowser.selectedBrowser.isArticle);
+ ok(gBrowser.selectedBrowser.isArticle, "Should suddenly be an article.");
+ ok(!document.getElementById("reader-mode-button").hidden, "Button should now be visible.");
+});
+
diff --git a/browser/components/uitour/test/browser_UITour_heartbeat.js b/browser/components/uitour/test/browser_UITour_heartbeat.js
new file mode 100644
index 000000000..61be1d44b
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_heartbeat.js
@@ -0,0 +1,755 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+function getHeartbeatNotification(aId, aChromeWindow = window) {
+ let notificationBox = aChromeWindow.document.getElementById("high-priority-global-notificationbox");
+ // UITour.jsm prefixes the notification box ID with "heartbeat-" to prevent collisions.
+ return notificationBox.getNotificationWithValue("heartbeat-" + aId);
+}
+
+/**
+ * Simulate a click on a rating element in the Heartbeat notification.
+ *
+ * @param aId
+ * The id of the notification box.
+ * @param aScore
+ * The score related to the rating element we want to click on.
+ */
+function simulateVote(aId, aScore) {
+ let notification = getHeartbeatNotification(aId);
+
+ let ratingContainer = notification.childNodes[0];
+ ok(ratingContainer, "The notification has a valid rating container.");
+
+ let ratingElement = ratingContainer.getElementsByAttribute("data-score", aScore);
+ ok(ratingElement[0], "The rating container contains the requested rating element.");
+
+ ratingElement[0].click();
+}
+
+/**
+ * Simulate a click on the learn-more link.
+ *
+ * @param aId
+ * The id of the notification box.
+ */
+function clickLearnMore(aId) {
+ let notification = getHeartbeatNotification(aId);
+
+ let learnMoreLabel = notification.childNodes[2];
+ ok(learnMoreLabel, "The notification has a valid learn more label.");
+
+ learnMoreLabel.click();
+}
+
+/**
+ * Remove the notification box.
+ *
+ * @param aId
+ * The id of the notification box to remove.
+ * @param [aChromeWindow=window]
+ * The chrome window the notification box is in.
+ */
+function cleanUpNotification(aId, aChromeWindow = window) {
+ let notification = getHeartbeatNotification(aId, aChromeWindow);
+ notification.close();
+}
+
+/**
+ * Check telemetry payload for proper format and expected content.
+ *
+ * @param aPayload
+ * The Telemetry payload to verify
+ * @param aFlowId
+ * Expected value of the flowId field.
+ * @param aExpectedFields
+ * Array of expected fields. No other fields are allowed.
+ */
+function checkTelemetry(aPayload, aFlowId, aExpectedFields) {
+ // Basic payload format
+ is(aPayload.version, 1, "Telemetry ping must have heartbeat version=1");
+ is(aPayload.flowId, aFlowId, "Flow ID in the Telemetry ping must match");
+
+ // Check for superfluous fields
+ let extraKeys = new Set(Object.keys(aPayload));
+ extraKeys.delete("version");
+ extraKeys.delete("flowId");
+
+ // Check for expected fields
+ for (let field of aExpectedFields) {
+ ok(field in aPayload, "The payload should have the field '" + field + "'");
+ if (field.endsWith("TS")) {
+ let ts = aPayload[field];
+ ok(Number.isInteger(ts) && ts > 0, "Timestamp '" + field + "' must be a natural number");
+ }
+ extraKeys.delete(field);
+ }
+
+ is(extraKeys.size, 0, "No unexpected fields in the Telemetry payload");
+}
+
+/**
+ * Waits for an UITour notification dispatched through |UITour.notify|. This should be
+ * done with |gContentAPI.observe|. Unfortunately, in e10s, |gContentAPI.observe| doesn't
+ * allow for multiple calls to the same callback, allowing to catch just the first
+ * notification.
+ *
+ * @param aEventName
+ * The notification name to wait for.
+ * @return {Promise} Resolved with the data that comes with the event.
+ */
+function promiseWaitHeartbeatNotification(aEventName) {
+ return ContentTask.spawn(gTestTab.linkedBrowser, { aEventName },
+ function({ aEventName }) {
+ return new Promise(resolve => {
+ addEventListener("mozUITourNotification", function listener(event) {
+ if (event.detail.event !== aEventName) {
+ return;
+ }
+ removeEventListener("mozUITourNotification", listener, false);
+ resolve(event.detail.params);
+ }, false);
+ });
+ });
+}
+
+/**
+ * Waits for UITour notifications dispatched through |UITour.notify|. This works like
+ * |promiseWaitHeartbeatNotification|, but waits for all the passed notifications to
+ * be received before resolving. If it receives an unaccounted notification, it rejects.
+ *
+ * @param events
+ * An array of expected notification names to wait for.
+ * @return {Promise} Resolved with the data that comes with the event. Rejects with the
+ * name of an undesired notification if received.
+ */
+function promiseWaitExpectedNotifications(events) {
+ return ContentTask.spawn(gTestTab.linkedBrowser, { events },
+ function({ events }) {
+ let stillToReceive = events;
+ return new Promise((res, rej) => {
+ addEventListener("mozUITourNotification", function listener(event) {
+ if (stillToReceive.includes(event.detail.event)) {
+ // Filter out the received event.
+ stillToReceive = stillToReceive.filter(x => x !== event.detail.event);
+ } else {
+ removeEventListener("mozUITourNotification", listener, false);
+ rej(event.detail.event);
+ }
+ // We still need to catch some notifications. Don't do anything.
+ if (stillToReceive.length > 0) {
+ return;
+ }
+ // We don't need to listen for other notifications. Resolve the promise.
+ removeEventListener("mozUITourNotification", listener, false);
+ res();
+ }, false);
+ });
+ });
+}
+
+function validateTimestamp(eventName, timestamp) {
+ info("'" + eventName + "' notification received (timestamp " + timestamp.toString() + ").");
+ ok(Number.isFinite(timestamp), "Timestamp must be a number.");
+}
+
+add_task(function* test_setup() {
+ yield setup_UITourTest();
+ requestLongerTimeout(2);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.uitour.surveyDuration");
+ });
+});
+
+/**
+ * Check that the "stars" heartbeat UI correctly shows and closes.
+ */
+add_UITour_task(function* test_heartbeat_stars_show() {
+ let flowId = "ui-ratefirefox-" + Math.random();
+ let engagementURL = "http://example.com";
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(
+ ["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL);
+
+ // Validate the returned timestamp.
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Close the heartbeat notification.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+ cleanUpNotification(flowId);
+
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received");
+ checkTelemetry(data, flowId, ["offeredTS", "closedTS"]);
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Check that the heartbeat UI correctly takes optional icon URL.
+ */
+add_UITour_task(function* test_heartbeat_take_optional_icon_URL() {
+ let flowId = "ui-ratefirefox-" + Math.random();
+ let engagementURL = "http://example.com";
+ let iconURL = "chrome://branding/content/icon48.png";
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(
+ ["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL, null, null, {
+ iconURL: iconURL
+ });
+
+ // Validate the returned timestamp.
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Check the icon URL
+ let notification = getHeartbeatNotification(flowId);
+ is(notification.image, iconURL, "The optional icon URL is not taken correctly");
+
+ // Close the heartbeat notification.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+ cleanUpNotification(flowId);
+
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received");
+ checkTelemetry(data, flowId, ["offeredTS", "closedTS"]);
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Test that the heartbeat UI correctly works with null engagement URL.
+ */
+add_UITour_task(function* test_heartbeat_null_engagementURL() {
+ let flowId = "ui-ratefirefox-" + Math.random();
+ let originalTabCount = gBrowser.tabs.length;
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered",
+ "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null);
+
+ // Validate the returned timestamp.
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so
+ // wait for them here.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+
+ // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+ simulateVote(flowId, 2);
+ data = yield votedPromise;
+ validateTimestamp('Heartbeat:Voted', data.timestamp);
+
+ // Validate the closing timestamp.
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+ is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened.");
+
+ // Validate the data we send out.
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received.");
+ checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]);
+ is(data.score, 2, "Checking Telemetry payload.score");
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Test that the heartbeat UI correctly works with an invalid, but non null, engagement URL.
+ */
+add_UITour_task(function* test_heartbeat_invalid_engagement_URL() {
+ let flowId = "ui-ratefirefox-" + Math.random();
+ let originalTabCount = gBrowser.tabs.length;
+ let invalidEngagementURL = "invalidEngagement";
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered",
+ "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, invalidEngagementURL);
+
+ // Validate the returned timestamp.
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so
+ // wait for them here.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+
+ // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+ simulateVote(flowId, 2);
+ data = yield votedPromise;
+ validateTimestamp('Heartbeat:Voted', data.timestamp);
+
+ // Validate the closing timestamp.
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+ is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened.");
+
+ // Validate the data we send out.
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received.");
+ checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]);
+ is(data.score, 2, "Checking Telemetry payload.score");
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Test that the score is correctly reported.
+ */
+add_UITour_task(function* test_heartbeat_stars_vote() {
+ const expectedScore = 4;
+ let originalTabCount = gBrowser.tabs.length;
+ let flowId = "ui-ratefirefox-" + Math.random();
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered",
+ "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null);
+
+ // Validate the returned timestamp.
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so
+ // wait for them here.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+
+ // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+ simulateVote(flowId, expectedScore);
+ data = yield votedPromise;
+ validateTimestamp('Heartbeat:Voted', data.timestamp);
+ is(data.score, expectedScore, "Should report a score of " + expectedScore);
+
+ // Validate the closing timestamp and vote.
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+ is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened.");
+
+ // Validate the data we send out.
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received.");
+ checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]);
+ is(data.score, expectedScore, "Checking Telemetry payload.score");
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Test that the engagement page is correctly opened when voting.
+ */
+add_UITour_task(function* test_heartbeat_engagement_tab() {
+ let engagementURL = "http://example.com";
+ let flowId = "ui-ratefirefox-" + Math.random();
+ let originalTabCount = gBrowser.tabs.length;
+ const expectedTabCount = originalTabCount + 1;
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered",
+ "Heartbeat:NotificationClosed", "Heartbeat:Voted", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL);
+
+ // Validate the returned timestamp.
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Wait an the Voted, Closed and Telemetry Sent events. They are fired together, so
+ // wait for them here.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let votedPromise = promiseWaitHeartbeatNotification("Heartbeat:Voted");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+
+ // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+ simulateVote(flowId, 1);
+ data = yield votedPromise;
+ validateTimestamp('Heartbeat:Voted', data.timestamp);
+
+ // Validate the closing timestamp, vote and make sure the engagement page was opened.
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+ is(gBrowser.tabs.length, expectedTabCount, "Engagement URL should open in a new tab.");
+ gBrowser.removeCurrentTab();
+
+ // Validate the data we send out.
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received.");
+ checkTelemetry(data, flowId, ["offeredTS", "votedTS", "closedTS", "score"]);
+ is(data.score, 1, "Checking Telemetry payload.score");
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Test that the engagement button opens the engagement URL.
+ */
+add_UITour_task(function* test_heartbeat_engagement_button() {
+ let engagementURL = "http://example.com";
+ let flowId = "ui-engagewithfirefox-" + Math.random();
+ let originalTabCount = gBrowser.tabs.length;
+ const expectedTabCount = originalTabCount + 1;
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered",
+ "Heartbeat:NotificationClosed", "Heartbeat:Engaged", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL, null, null, {
+ engagementButtonLabel: "Engage Me",
+ });
+
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Wait an the Engaged, Closed and Telemetry Sent events. They are fired together, so
+ // wait for them here.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let engagedPromise = promiseWaitHeartbeatNotification("Heartbeat:Engaged");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+
+ // Simulate user engagement.
+ let notification = getHeartbeatNotification(flowId);
+ is(notification.querySelectorAll(".star-x").length, 0, "No stars should be present");
+ // The UI was just shown. We can simulate a click on the engagement button.
+ let engagementButton = notification.querySelector(".notification-button");
+ is(engagementButton.label, "Engage Me", "Check engagement button text");
+ engagementButton.doCommand();
+
+ data = yield engagedPromise;
+ validateTimestamp('Heartbeat:Engaged', data.timestamp);
+
+ // Validate the closing timestamp, vote and make sure the engagement page was opened.
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+ is(gBrowser.tabs.length, expectedTabCount, "Engagement URL should open in a new tab.");
+ gBrowser.removeCurrentTab();
+
+ // Validate the data we send out.
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received.");
+ checkTelemetry(data, flowId, ["offeredTS", "engagedTS", "closedTS"]);
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Test that the learn more link is displayed and that the page is correctly opened when
+ * clicking on it.
+ */
+add_UITour_task(function* test_heartbeat_learnmore() {
+ let dummyURL = "http://example.com";
+ let flowId = "ui-ratefirefox-" + Math.random();
+ let originalTabCount = gBrowser.tabs.length;
+ const expectedTabCount = originalTabCount + 1;
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered",
+ "Heartbeat:NotificationClosed", "Heartbeat:LearnMore", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, dummyURL,
+ "What is this?", dummyURL);
+
+ let data = yield shownPromise;
+ validateTimestamp('Heartbeat:Offered', data.timestamp);
+
+ // Wait an the LearnMore, Closed and Telemetry Sent events. They are fired together, so
+ // wait for them here.
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let learnMorePromise = promiseWaitHeartbeatNotification("Heartbeat:LearnMore");
+ let pingSentPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+
+ // The UI was just shown. Simulate a click on the learn more link.
+ clickLearnMore(flowId);
+
+ data = yield learnMorePromise;
+ validateTimestamp('Heartbeat:LearnMore', data.timestamp);
+ cleanUpNotification(flowId);
+
+ // The notification was closed.
+ data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+ is(gBrowser.tabs.length, expectedTabCount, "Learn more URL should open in a new tab.");
+ gBrowser.removeCurrentTab();
+
+ // Validate the data we send out.
+ data = yield pingSentPromise;
+ info("'Heartbeat:TelemetrySent' notification received.");
+ checkTelemetry(data, flowId, ["offeredTS", "learnMoreTS", "closedTS"]);
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+add_UITour_task(function* test_invalidEngagementButtonLabel() {
+ let engagementURL = "http://example.com";
+ let flowId = "invalidEngagementButtonLabel-" + Math.random();
+
+ let eventPromise = promisePageEvent();
+
+ gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL,
+ null, null, {
+ engagementButtonLabel: 42,
+ });
+
+ yield eventPromise;
+ ok(!isTourBrowser(gBrowser.selectedBrowser),
+ "Invalid engagementButtonLabel should prevent init");
+
+})
+
+add_UITour_task(function* test_privateWindowsOnly_noneOpen() {
+ let engagementURL = "http://example.com";
+ let flowId = "privateWindowsOnly_noneOpen-" + Math.random();
+
+ let eventPromise = promisePageEvent();
+
+ gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL,
+ null, null, {
+ engagementButtonLabel: "Yes!",
+ privateWindowsOnly: true,
+ });
+
+ yield eventPromise;
+ ok(!isTourBrowser(gBrowser.selectedBrowser),
+ "If there are no private windows opened, tour init should be prevented");
+})
+
+add_UITour_task(function* test_privateWindowsOnly_notMostRecent() {
+ let engagementURL = "http://example.com";
+ let flowId = "notMostRecent-" + Math.random();
+
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+ let mostRecentWin = yield BrowserTestUtils.openNewBrowserWindow();
+
+ let eventPromise = promisePageEvent();
+
+ gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL,
+ null, null, {
+ engagementButtonLabel: "Yes!",
+ privateWindowsOnly: true,
+ });
+
+ yield eventPromise;
+ is(getHeartbeatNotification(flowId, window), null,
+ "Heartbeat shouldn't appear in the default window");
+ is(!!getHeartbeatNotification(flowId, privateWin), true,
+ "Heartbeat should appear in the most recent private window");
+ is(getHeartbeatNotification(flowId, mostRecentWin), null,
+ "Heartbeat shouldn't appear in the most recent non-private window");
+
+ yield BrowserTestUtils.closeWindow(mostRecentWin);
+ yield BrowserTestUtils.closeWindow(privateWin);
+})
+
+add_UITour_task(function* test_privateWindowsOnly() {
+ let engagementURL = "http://example.com";
+ let learnMoreURL = "http://example.org/learnmore/";
+ let flowId = "ui-privateWindowsOnly-" + Math.random();
+
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ yield new Promise((resolve) => {
+ gContentAPI.observe(function(aEventName, aData) {
+ info(aEventName + " notification received: " + JSON.stringify(aData, null, 2));
+ ok(false, "No heartbeat notifications should arrive for privateWindowsOnly");
+ }, resolve);
+ });
+
+ gContentAPI.showHeartbeat("Do you want to engage with us?", "Thank you!", flowId, engagementURL,
+ "Learn More", learnMoreURL, {
+ engagementButtonLabel: "Yes!",
+ privateWindowsOnly: true,
+ });
+
+ yield promisePageEvent();
+
+ ok(isTourBrowser(gBrowser.selectedBrowser), "UITour should have been init for the browser");
+
+ let notification = getHeartbeatNotification(flowId, privateWin);
+
+ is(notification.querySelectorAll(".star-x").length, 0, "No stars should be present");
+
+ info("Test the learn more link.");
+ let learnMoreLink = notification.querySelector(".text-link");
+ is(learnMoreLink.value, "Learn More", "Check learn more label");
+ let learnMoreTabPromise = BrowserTestUtils.waitForNewTab(privateWin.gBrowser, null);
+ learnMoreLink.click();
+ let learnMoreTab = yield learnMoreTabPromise;
+ is(learnMoreTab.linkedBrowser.currentURI.host, "example.org", "Check learn more site opened");
+ ok(PrivateBrowsingUtils.isBrowserPrivate(learnMoreTab.linkedBrowser), "Ensure the learn more tab is private");
+ yield BrowserTestUtils.removeTab(learnMoreTab);
+
+ info("Test the engagement button's new tab.");
+ let engagementButton = notification.querySelector(".notification-button");
+ is(engagementButton.label, "Yes!", "Check engagement button text");
+ let engagementTabPromise = BrowserTestUtils.waitForNewTab(privateWin.gBrowser, null);
+ engagementButton.doCommand();
+ let engagementTab = yield engagementTabPromise;
+ is(engagementTab.linkedBrowser.currentURI.host, "example.com", "Check enagement site opened");
+ ok(PrivateBrowsingUtils.isBrowserPrivate(engagementTab.linkedBrowser), "Ensure the engagement tab is private");
+ yield BrowserTestUtils.removeTab(engagementTab);
+
+ yield BrowserTestUtils.closeWindow(privateWin);
+})
+
+/**
+ * Test that the survey closes itself after a while and submits Telemetry
+ */
+add_UITour_task(function* test_telemetry_surveyExpired() {
+ let flowId = "survey-expired-" + Math.random();
+ let engagementURL = "http://example.com";
+ let surveyDuration = 1; // 1 second (pref is in seconds)
+ Services.prefs.setIntPref("browser.uitour.surveyDuration", surveyDuration);
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(["Heartbeat:NotificationOffered",
+ "Heartbeat:NotificationClosed", "Heartbeat:SurveyExpired", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL);
+
+ let expiredPromise = promiseWaitHeartbeatNotification("Heartbeat:SurveyExpired");
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let pingPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+
+ yield Promise.all([shownPromise, expiredPromise, closedPromise]);
+ // Validate the ping data.
+ let data = yield pingPromise;
+ checkTelemetry(data, flowId, ["offeredTS", "expiredTS", "closedTS"]);
+
+ Services.prefs.clearUserPref("browser.uitour.surveyDuration");
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
+
+/**
+ * Check that certain whitelisted experiment parameters get reflected in the
+ * Telemetry ping
+ */
+add_UITour_task(function* test_telemetry_params() {
+ let flowId = "telemetry-params-" + Math.random();
+ let engagementURL = "http://example.com";
+ let extraParams = {
+ "surveyId": "foo",
+ "surveyVersion": 1.5,
+ "testing": true,
+ "notWhitelisted": 123,
+ };
+ let expectedFields = ["surveyId", "surveyVersion", "testing"];
+
+ // We need to call |gContentAPI.observe| at least once to set a valid |notificationListener|
+ // in UITour-lib.js, otherwise no message will get propagated.
+ gContentAPI.observe(() => {});
+
+ let receivedExpectedPromise = promiseWaitExpectedNotifications(
+ ["Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed", "Heartbeat:TelemetrySent"]);
+
+ // Show the Heartbeat notification and wait for it to be displayed.
+ let shownPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationOffered");
+ gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!",
+ flowId, engagementURL, null, null, extraParams);
+ yield shownPromise;
+
+ let closedPromise = promiseWaitHeartbeatNotification("Heartbeat:NotificationClosed");
+ let pingPromise = promiseWaitHeartbeatNotification("Heartbeat:TelemetrySent");
+ cleanUpNotification(flowId);
+
+ // The notification was closed.
+ let data = yield closedPromise;
+ validateTimestamp('Heartbeat:NotificationClosed', data.timestamp);
+
+ // Validate the data we send out.
+ data = yield pingPromise;
+ info("'Heartbeat:TelemetrySent' notification received.");
+ checkTelemetry(data, flowId, ["offeredTS", "closedTS"].concat(expectedFields));
+ for (let param of expectedFields) {
+ is(data[param], extraParams[param],
+ "Whitelisted experiment configs should be copied into Telemetry pings");
+ }
+
+ // This rejects whenever an unexpected notification is received.
+ yield receivedExpectedPromise;
+})
diff --git a/browser/components/uitour/test/browser_UITour_modalDialog.js b/browser/components/uitour/test/browser_UITour_modalDialog.js
new file mode 100644
index 000000000..1890739c4
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_modalDialog.js
@@ -0,0 +1,104 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+var handleDialog;
+
+// Modified from toolkit/components/passwordmgr/test/prompt_common.js
+var didDialog;
+
+var timer; // keep in outer scope so it's not GC'd before firing
+function startCallbackTimer() {
+ didDialog = false;
+
+ // Delay before the callback twiddles the prompt.
+ const dialogDelay = 10;
+
+ // Use a timer to invoke a callback to twiddle the authentication dialog
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(observer, dialogDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+
+var observer = SpecialPowers.wrapCallbackObject({
+ QueryInterface : function (iid) {
+ const interfaces = [Ci.nsIObserver,
+ Ci.nsISupports, Ci.nsISupportsWeakReference];
+
+ if (!interfaces.some( function(v) { return iid.equals(v) } ))
+ throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+ observe : function (subject, topic, data) {
+ var doc = getDialogDoc();
+ if (doc)
+ handleDialog(doc);
+ else
+ startCallbackTimer(); // try again in a bit
+ }
+});
+
+function getDialogDoc() {
+ // Find the <browser> which contains notifyWindow, by looking
+ // through all the open windows and all the <browsers> in each.
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ // var enumerator = wm.getEnumerator("navigator:browser");
+ var enumerator = wm.getXULWindowEnumerator(null);
+
+ while (enumerator.hasMoreElements()) {
+ var win = enumerator.getNext();
+ var windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
+
+ var containedDocShells = windowDocShell.getDocShellEnumerator(
+ Ci.nsIDocShellTreeItem.typeChrome,
+ Ci.nsIDocShell.ENUMERATE_FORWARDS);
+ while (containedDocShells.hasMoreElements()) {
+ // Get the corresponding document for this docshell
+ var childDocShell = containedDocShells.getNext();
+ // We don't want it if it's not done loading.
+ if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+ continue;
+ var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
+ .contentViewer
+ .DOMDocument;
+
+ // ok(true, "Got window: " + childDoc.location.href);
+ if (childDoc.location.href == "chrome://global/content/commonDialog.xul")
+ return childDoc;
+ }
+ }
+
+ return null;
+}
+
+function test() {
+ UITourTest();
+}
+
+
+var tests = [
+ taskify(function* test_modal_dialog_while_opening_tooltip() {
+ let panelShown;
+ let popup;
+
+ handleDialog = (doc) => {
+ popup = document.getElementById("UITourTooltip");
+ gContentAPI.showInfo("appMenu", "test title", "test text");
+ doc.defaultView.setTimeout(function() {
+ is(popup.state, "closed", "Popup shouldn't be shown while dialog is up");
+ panelShown = promisePanelElementShown(window, popup);
+ let dialog = doc.getElementById("commonDialog");
+ dialog.acceptDialog();
+ }, 1000);
+ };
+ startCallbackTimer();
+ executeSoon(() => alert("test"));
+ yield waitForConditionPromise(() => panelShown, "Timed out waiting for panel promise to be assigned", 100);
+ yield panelShown;
+
+ yield hideInfoPromise();
+ })
+];
diff --git a/browser/components/uitour/test/browser_UITour_observe.js b/browser/components/uitour/test/browser_UITour_observe.js
new file mode 100644
index 000000000..b4b435659
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_observe.js
@@ -0,0 +1,85 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+function test() {
+ requestLongerTimeout(2);
+ UITourTest();
+}
+
+var tests = [
+ function test_no_params(done) {
+ function listener(event, params) {
+ is(event, "test-event-1", "Correct event name");
+ is(params, null, "No param object");
+ gContentAPI.observe(null);
+ done();
+ }
+
+ gContentAPI.observe(listener, () => {
+ UITour.notify("test-event-1");
+ });
+ },
+ function test_param_string(done) {
+ function listener(event, params) {
+ is(event, "test-event-2", "Correct event name");
+ is(params, "a param", "Correct param string");
+ gContentAPI.observe(null);
+ done();
+ }
+
+ gContentAPI.observe(listener, () => {
+ UITour.notify("test-event-2", "a param");
+ });
+ },
+ function test_param_object(done) {
+ function listener(event, params) {
+ is(event, "test-event-3", "Correct event name");
+ is(JSON.stringify(params), JSON.stringify({key: "something"}), "Correct param object");
+ gContentAPI.observe(null);
+ done();
+ }
+
+ gContentAPI.observe(listener, () => {
+ UITour.notify("test-event-3", {key: "something"});
+ });
+ },
+ function test_background_tab(done) {
+ function listener(event, params) {
+ is(event, "test-event-background-1", "Correct event name");
+ is(params, null, "No param object");
+ gContentAPI.observe(null);
+ gBrowser.removeCurrentTab();
+ done();
+ }
+
+ gContentAPI.observe(listener, () => {
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ isnot(gBrowser.selectedTab, gTestTab, "Make sure the selected tab changed");
+
+ UITour.notify("test-event-background-1");
+ });
+ },
+ // Make sure the tab isn't torn down when switching back to the tour one.
+ function test_background_then_foreground_tab(done) {
+ let blankTab = null;
+ function listener(event, params) {
+ is(event, "test-event-4", "Correct event name");
+ is(params, null, "No param object");
+ gContentAPI.observe(null);
+ gBrowser.removeTab(blankTab);
+ done();
+ }
+
+ gContentAPI.observe(listener, () => {
+ blankTab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ isnot(gBrowser.selectedTab, gTestTab, "Make sure the selected tab changed");
+ gBrowser.selectedTab = gTestTab;
+ is(gBrowser.selectedTab, gTestTab, "Switch back to the test tab");
+
+ UITour.notify("test-event-4");
+ });
+ },
+];
diff --git a/browser/components/uitour/test/browser_UITour_panel_close_annotation.js b/browser/components/uitour/test/browser_UITour_panel_close_annotation.js
new file mode 100644
index 000000000..cff446573
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_panel_close_annotation.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that annotations disappear when their target is hidden.
+ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+var highlight = document.getElementById("UITourHighlight");
+var tooltip = document.getElementById("UITourTooltip");
+
+function test() {
+ registerCleanupFunction(() => {
+ // Close the find bar in case it's open in the remaining tab
+ gBrowser.getFindBar(gBrowser.selectedTab).close();
+ });
+ UITourTest();
+}
+
+var tests = [
+ function test_highlight_move_outside_panel(done) {
+ gContentAPI.showInfo("urlbar", "test title", "test text");
+ gContentAPI.showHighlight("customize");
+ waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Move the highlight outside which should close the app menu.
+ gContentAPI.showHighlight("appMenu");
+ waitForPopupAtAnchor(highlight.parentElement, document.getElementById("PanelUI-button"), () => {
+ isnot(PanelUI.panel.state, "open",
+ "Panel should have closed after the highlight moved elsewhere.");
+ ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
+ done();
+ }, "Highlight should move to the appMenu button and still be visible");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ },
+
+ function test_highlight_panel_hideMenu(done) {
+ gContentAPI.showHighlight("customize");
+ gContentAPI.showInfo("search", "test title", "test text");
+ waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Close the app menu and make sure the highlight also disappeared.
+ gContentAPI.hideMenu("appMenu");
+ waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
+ isnot(PanelUI.panel.state, "open",
+ "Panel still should have closed");
+ ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
+ done();
+ }, "Highlight should have disappeared when panel closed");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ },
+
+ function test_highlight_panel_click_find(done) {
+ gContentAPI.showHighlight("help");
+ gContentAPI.showInfo("searchIcon", "test title", "test text");
+ waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Click the find button which should close the panel.
+ let findButton = document.getElementById("find-button");
+ EventUtils.synthesizeMouseAtCenter(findButton, {});
+ waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
+ isnot(PanelUI.panel.state, "open",
+ "Panel should have closed when the find bar opened");
+ ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
+ done();
+ }, "Highlight should have disappeared when panel closed");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ },
+
+ function test_highlight_info_panel_click_find(done) {
+ gContentAPI.showHighlight("help");
+ gContentAPI.showInfo("customize", "customize me!", "awesome!");
+ waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Click the find button which should close the panel.
+ let findButton = document.getElementById("find-button");
+ EventUtils.synthesizeMouseAtCenter(findButton, {});
+ waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
+ isnot(PanelUI.panel.state, "open",
+ "Panel should have closed when the find bar opened");
+ waitForElementToBeHidden(tooltip, function checkTooltipIsClosed() {
+ isnot(tooltip.state, "open", "The info panel should have closed too");
+ done();
+ }, "Tooltip should hide with the menu");
+ }, "Highlight should have disappeared when panel closed");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ },
+
+ function test_highlight_panel_open_subview(done) {
+ gContentAPI.showHighlight("customize");
+ gContentAPI.showInfo("backForward", "test title", "test text");
+ waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Click the help button which should open the subview in the panel menu.
+ let helpButton = document.getElementById("PanelUI-help");
+ EventUtils.synthesizeMouseAtCenter(helpButton, {});
+ waitForElementToBeHidden(highlight, function highlightHidden() {
+ is(PanelUI.panel.state, "open",
+ "Panel should have stayed open when the subview opened");
+ ok(tooltip.state == "showing" || tooltip.state == "open", "The info panel should have remained open");
+ PanelUI.hide();
+ done();
+ }, "Highlight should have disappeared when the subview opened");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ },
+
+ function test_info_panel_open_subview(done) {
+ gContentAPI.showHighlight("urlbar");
+ gContentAPI.showInfo("customize", "customize me!", "Open a subview");
+ waitForElementToBeVisible(tooltip, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Click the help button which should open the subview in the panel menu.
+ let helpButton = document.getElementById("PanelUI-help");
+ EventUtils.synthesizeMouseAtCenter(helpButton, {});
+ waitForElementToBeHidden(tooltip, function tooltipHidden() {
+ is(PanelUI.panel.state, "open",
+ "Panel should have stayed open when the subview opened");
+ is(highlight.parentElement.state, "open", "The highlight should have remained open");
+ PanelUI.hide();
+ done();
+ }, "Tooltip should have disappeared when the subview opened");
+ }, "Highlight should be shown after showHighlight() for fixed panel items");
+ },
+
+ function test_info_move_outside_panel(done) {
+ gContentAPI.showInfo("addons", "test title", "test text");
+ gContentAPI.showHighlight("urlbar");
+ let addonsButton = document.getElementById("add-ons-button");
+ waitForPopupAtAnchor(tooltip, addonsButton, function checkPanelIsOpen() {
+ isnot(PanelUI.panel.state, "closed", "Panel should have opened");
+
+ // Move the info panel outside which should close the app menu.
+ gContentAPI.showInfo("appMenu", "Cool menu button", "It's three lines");
+ waitForPopupAtAnchor(tooltip, document.getElementById("PanelUI-button"), () => {
+ isnot(PanelUI.panel.state, "open",
+ "Menu should have closed after the highlight moved elsewhere.");
+ is(highlight.parentElement.state, "open", "The highlight should have remained visible");
+ done();
+ }, "Tooltip should move to the appMenu button and still be visible");
+ }, "Tooltip should be shown after showInfo() for a panel item");
+ },
+
+];
diff --git a/browser/components/uitour/test/browser_UITour_pocket.js b/browser/components/uitour/test/browser_UITour_pocket.js
new file mode 100644
index 000000000..29548a475
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_pocket.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+var button;
+
+function test() {
+ UITourTest();
+}
+
+var tests = [
+ taskify(function* test_menu_show_navbar() {
+ is(button.open, false, "Menu should initially be closed");
+ gContentAPI.showMenu("pocket");
+
+ // The panel gets created dynamically.
+ let widgetPanel = null;
+ yield waitForConditionPromise(() => {
+ widgetPanel = document.getElementById("customizationui-widget-panel");
+ return widgetPanel && widgetPanel.state == "open";
+ }, "Menu should be visible after showMenu()");
+
+ ok(button.open, "Button should know its view is open");
+ ok(!widgetPanel.hasAttribute("noautohide"), "@noautohide shouldn't be on the pocket panel");
+ ok(button.hasAttribute("open"), "Pocket button should know that the menu is open");
+
+ widgetPanel.hidePopup();
+ checkPanelIsHidden(widgetPanel);
+ }),
+ taskify(function* test_menu_show_appMenu() {
+ CustomizableUI.addWidgetToArea("pocket-button", CustomizableUI.AREA_PANEL);
+
+ is(PanelUI.multiView.hasAttribute("panelopen"), false, "Multiview should initially be closed");
+ gContentAPI.showMenu("pocket");
+
+ yield waitForConditionPromise(() => {
+ return PanelUI.panel.state == "open";
+ }, "Menu should be visible after showMenu()");
+
+ ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide shouldn't be on the pocket panel");
+ ok(PanelUI.multiView.showingSubView, "Subview should be open");
+ ok(PanelUI.multiView.hasAttribute("panelopen"), "Multiview should know it's open");
+
+ PanelUI.showMainView();
+ PanelUI.panel.hidePopup();
+ checkPanelIsHidden(PanelUI.panel);
+ }),
+];
+
+// End tests
+
+function checkPanelIsHidden(aPanel) {
+ if (aPanel.parentElement) {
+ is_hidden(aPanel);
+ } else {
+ ok(!aPanel.parentElement, "Widget panel should have been removed");
+ }
+ is(button.hasAttribute("open"), false, "Pocket button should know that the panel is closed");
+}
+
+if (Services.prefs.getBoolPref("extensions.pocket.enabled")) {
+ let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
+
+ // Add the button to the nav-bar by default.
+ if (!placement || placement.area != CustomizableUI.AREA_NAVBAR) {
+ CustomizableUI.addWidgetToArea("pocket-button", CustomizableUI.AREA_NAVBAR);
+ }
+ registerCleanupFunction(() => {
+ CustomizableUI.reset();
+ });
+
+ let widgetGroupWrapper = CustomizableUI.getWidget("pocket-button");
+ button = widgetGroupWrapper.forWindow(window).node;
+ ok(button, "Got button node");
+} else {
+ todo(false, "Pocket is disabled so skip its UITour tests");
+ tests = [];
+}
diff --git a/browser/components/uitour/test/browser_UITour_registerPageID.js b/browser/components/uitour/test/browser_UITour_registerPageID.js
new file mode 100644
index 000000000..369abb1ed
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_registerPageID.js
@@ -0,0 +1,108 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+Components.utils.import("resource://gre/modules/UITelemetry.jsm");
+Components.utils.import("resource:///modules/BrowserUITelemetry.jsm");
+
+add_task(function* setup_telemetry() {
+ UITelemetry._enabled = true;
+
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("browser.uitour.seenPageIDs");
+ resetSeenPageIDsLazyGetter();
+ UITelemetry._enabled = undefined;
+ BrowserUITelemetry.setBucket(null);
+ delete window.UITelemetry;
+ delete window.BrowserUITelemetry;
+ });
+});
+
+add_task(setup_UITourTest);
+
+function resetSeenPageIDsLazyGetter() {
+ delete UITour.seenPageIDs;
+ // This should be kept in sync with how UITour.init() sets this.
+ Object.defineProperty(UITour, "seenPageIDs", {
+ get: UITour.restoreSeenPageIDs.bind(UITour),
+ configurable: true,
+ });
+}
+
+function checkExpectedSeenPageIDs(expected) {
+ is(UITour.seenPageIDs.size, expected.length, "Should be " + expected.length + " total seen page IDs");
+
+ for (let id of expected)
+ ok(UITour.seenPageIDs.has(id), "Should have seen '" + id + "' page ID");
+
+ let prefData = Services.prefs.getCharPref("browser.uitour.seenPageIDs");
+ prefData = new Map(JSON.parse(prefData));
+
+ is(prefData.size, expected.length, "Should be " + expected.length + " total seen page IDs persisted");
+
+ for (let id of expected)
+ ok(prefData.has(id), "Should have seen '" + id + "' page ID persisted");
+}
+
+
+add_UITour_task(function test_seenPageIDs_restore() {
+ info("Setting up seenPageIDs to be restored from pref");
+ let data = JSON.stringify([
+ ["savedID1", { lastSeen: Date.now() }],
+ ["savedID2", { lastSeen: Date.now() }],
+ // 9 weeks ago, should auto expire.
+ ["savedID3", { lastSeen: Date.now() - 9 * 7 * 24 * 60 * 60 * 1000 }],
+ ]);
+ Services.prefs.setCharPref("browser.uitour.seenPageIDs",
+ data);
+
+ resetSeenPageIDsLazyGetter();
+ checkExpectedSeenPageIDs(["savedID1", "savedID2"]);
+});
+
+add_UITour_task(function* test_seenPageIDs_set_1() {
+ yield gContentAPI.registerPageID("testpage1");
+
+ yield waitForConditionPromise(() => UITour.seenPageIDs.size == 3, "Waiting for page to be registered.");
+
+ checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1"]);
+
+ const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
+ const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
+
+ let bucket = PREFIX + "UITour" + SEP + "testpage1";
+ is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ bucket = PREFIX + "UITour" + SEP + "testpage1" + SEP + "inactive" + SEP + "1m";
+ is(BrowserUITelemetry.currentBucket, bucket,
+ "After switching tabs, bucket should be expiring");
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = gTestTab;
+ BrowserUITelemetry.setBucket(null);
+});
+
+add_UITour_task(function* test_seenPageIDs_set_2() {
+ yield gContentAPI.registerPageID("testpage2");
+
+ yield waitForConditionPromise(() => UITour.seenPageIDs.size == 4, "Waiting for page to be registered.");
+
+ checkExpectedSeenPageIDs(["savedID1", "savedID2", "testpage1", "testpage2"]);
+
+ const PREFIX = BrowserUITelemetry.BUCKET_PREFIX;
+ const SEP = BrowserUITelemetry.BUCKET_SEPARATOR;
+
+ let bucket = PREFIX + "UITour" + SEP + "testpage2";
+ is(BrowserUITelemetry.currentBucket, bucket, "Bucket should have correct name");
+
+ gBrowser.removeTab(gTestTab);
+ gTestTab = null;
+ bucket = PREFIX + "UITour" + SEP + "testpage2" + SEP + "closed" + SEP + "1m";
+ is(BrowserUITelemetry.currentBucket, bucket,
+ "After closing tab, bucket should be expiring");
+
+ BrowserUITelemetry.setBucket(null);
+});
diff --git a/browser/components/uitour/test/browser_UITour_resetProfile.js b/browser/components/uitour/test/browser_UITour_resetProfile.js
new file mode 100644
index 000000000..c91d0a4f2
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_resetProfile.js
@@ -0,0 +1,48 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+add_task(setup_UITourTest);
+
+// Test that a reset profile dialog appears when "resetFirefox" event is triggered
+add_UITour_task(function* test_resetFirefox() {
+ let canReset = yield getConfigurationPromise("canReset");
+ ok(!canReset, "Shouldn't be able to reset from mochitest's temporary profile.");
+ let dialogPromise = new Promise((resolve) => {
+ let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ winWatcher.registerNotification(function onOpen(subj, topic, data) {
+ if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+ subj.addEventListener("load", function onLoad() {
+ subj.removeEventListener("load", onLoad);
+ if (subj.document.documentURI ==
+ "chrome://global/content/resetProfile.xul") {
+ winWatcher.unregisterNotification(onOpen);
+ ok(true, "Observed search manager window open");
+ is(subj.opener, window,
+ "Reset Firefox event opened a reset profile window.");
+ subj.close();
+ resolve();
+ }
+ });
+ }
+ });
+ });
+
+ // make reset possible.
+ let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].
+ getService(Ci.nsIToolkitProfileService);
+ let currentProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let profileName = "mochitest-test-profile-temp-" + Date.now();
+ let tempProfile = profileService.createProfile(currentProfileDir, profileName);
+ canReset = yield getConfigurationPromise("canReset");
+ ok(canReset, "Should be able to reset from mochitest's temporary profile once it's in the profile manager.");
+ yield gContentAPI.resetFirefox();
+ yield dialogPromise;
+ tempProfile.remove(false);
+ canReset = yield getConfigurationPromise("canReset");
+ ok(!canReset, "Shouldn't be able to reset from mochitest's temporary profile once removed from the profile manager.");
+});
+
diff --git a/browser/components/uitour/test/browser_UITour_showNewTab.js b/browser/components/uitour/test/browser_UITour_showNewTab.js
new file mode 100644
index 000000000..2deb08148
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_showNewTab.js
@@ -0,0 +1,17 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+add_task(setup_UITourTest);
+
+// Test that we can switch to about:newtab
+add_UITour_task(function* test_aboutNewTab() {
+ let newTabLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, "about:newtab");
+ info("Showing about:newtab");
+ yield gContentAPI.showNewTab();
+ info("Waiting for about:newtab to load");
+ yield newTabLoaded;
+ is(gBrowser.selectedBrowser.currentURI.spec, "about:newtab", "Loaded about:newtab");
+});
diff --git a/browser/components/uitour/test/browser_UITour_sync.js b/browser/components/uitour/test/browser_UITour_sync.js
new file mode 100644
index 000000000..14ac0c1f6
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_sync.js
@@ -0,0 +1,105 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("services.sync.username");
+});
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_checkSyncSetup_disabled() {
+ let result = yield getConfigurationPromise("sync");
+ is(result.setup, false, "Sync shouldn't be setup by default");
+});
+
+add_UITour_task(function* test_checkSyncSetup_enabled() {
+ Services.prefs.setCharPref("services.sync.username", "uitour@tests.mozilla.org");
+ let result = yield getConfigurationPromise("sync");
+ is(result.setup, true, "Sync should be setup");
+});
+
+add_UITour_task(function* test_checkSyncCounts() {
+ Services.prefs.setIntPref("services.sync.clients.devices.desktop", 4);
+ Services.prefs.setIntPref("services.sync.clients.devices.mobile", 5);
+ Services.prefs.setIntPref("services.sync.numClients", 9);
+ let result = yield getConfigurationPromise("sync");
+ is(result.mobileDevices, 5, "mobileDevices should be set");
+ is(result.desktopDevices, 4, "desktopDevices should be set");
+ is(result.totalDevices, 9, "totalDevices should be set");
+
+ Services.prefs.clearUserPref("services.sync.clients.devices.desktop");
+ result = yield getConfigurationPromise("sync");
+ is(result.mobileDevices, 5, "mobileDevices should be set");
+ is(result.desktopDevices, 0, "desktopDevices should be 0");
+ is(result.totalDevices, 9, "totalDevices should be set");
+
+ Services.prefs.clearUserPref("services.sync.clients.devices.mobile");
+ result = yield getConfigurationPromise("sync");
+ is(result.mobileDevices, 0, "mobileDevices should be 0");
+ is(result.desktopDevices, 0, "desktopDevices should be 0");
+ is(result.totalDevices, 9, "totalDevices should be set");
+
+ Services.prefs.clearUserPref("services.sync.numClients");
+ result = yield getConfigurationPromise("sync");
+ is(result.mobileDevices, 0, "mobileDevices should be 0");
+ is(result.desktopDevices, 0, "desktopDevices should be 0");
+ is(result.totalDevices, 0, "totalDevices should be 0");
+});
+
+// The showFirefoxAccounts API is sync related, so we test that here too...
+add_UITour_task(function* test_firefoxAccountsNoParams() {
+ yield gContentAPI.showFirefoxAccounts();
+ yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
+ "about:accounts?action=signup&entrypoint=uitour");
+});
+
+add_UITour_task(function* test_firefoxAccountsValidParams() {
+ yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", utm_bar: "bar" });
+ yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
+ "about:accounts?action=signup&entrypoint=uitour&utm_foo=foo&utm_bar=bar");
+});
+
+add_UITour_task(function* test_firefoxAccountsNonAlphaValue() {
+ // All characters in the value are allowed, but they must be automatically escaped.
+ // (we throw a unicode character in there too - it's not auto-utf8 encoded,
+ // but that's ok, so long as it is escaped correctly.)
+ let value = "foo& /=?:\\\xa9";
+ // encodeURIComponent encodes spaces to %20 but we want "+"
+ let expected = encodeURIComponent(value).replace(/%20/g, "+");
+ yield gContentAPI.showFirefoxAccounts({ utm_foo: value });
+ yield BrowserTestUtils.browserLoaded(gTestTab.linkedBrowser, false,
+ "about:accounts?action=signup&entrypoint=uitour&utm_foo=" + expected);
+});
+
+// A helper to check the request was ignored due to invalid params.
+function* checkAboutAccountsNotLoaded() {
+ try {
+ yield waitForConditionPromise(() => {
+ return gBrowser.selectedBrowser.currentURI.spec.startsWith("about:accounts");
+ }, "Check if about:accounts opened");
+ ok(false, "No about:accounts tab should have opened");
+ } catch (ex) {
+ ok(true, "No about:accounts tab opened");
+ }
+}
+
+add_UITour_task(function* test_firefoxAccountsNonObject() {
+ // non-string should be rejected.
+ yield gContentAPI.showFirefoxAccounts(99);
+ yield checkAboutAccountsNotLoaded();
+});
+
+add_UITour_task(function* test_firefoxAccountsNonUtmPrefix() {
+ // Any non "utm_" name should should be rejected.
+ yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", bar: "bar" });
+ yield checkAboutAccountsNotLoaded();
+});
+
+add_UITour_task(function* test_firefoxAccountsNonAlphaName() {
+ // Any "utm_" name which includes non-alpha chars should be rejected.
+ yield gContentAPI.showFirefoxAccounts({ utm_foo: "foo", "utm_bar=": "bar" });
+ yield checkAboutAccountsNotLoaded();
+});
diff --git a/browser/components/uitour/test/browser_UITour_toggleReaderMode.js b/browser/components/uitour/test/browser_UITour_toggleReaderMode.js
new file mode 100644
index 000000000..58313e74b
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_toggleReaderMode.js
@@ -0,0 +1,16 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function*() {
+ ok(!gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"),
+ "Should not be in reader mode at start of test.");
+ yield gContentAPI.toggleReaderMode();
+ yield waitForConditionPromise(() => gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"));
+ ok(gBrowser.selectedBrowser.currentURI.spec.startsWith("about:reader"),
+ "Should be in reader mode now.");
+});
diff --git a/browser/components/uitour/test/browser_backgroundTab.js b/browser/components/uitour/test/browser_backgroundTab.js
new file mode 100644
index 000000000..c4117c698
--- /dev/null
+++ b/browser/components/uitour/test/browser_backgroundTab.js
@@ -0,0 +1,46 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+requestLongerTimeout(2);
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_bg_getConfiguration() {
+ info("getConfiguration is on the allowed list so should work");
+ yield* loadForegroundTab();
+ let data = yield getConfigurationPromise("availableTargets");
+ ok(data, "Got data from getConfiguration");
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_UITour_task(function* test_bg_showInfo() {
+ info("showInfo isn't on the allowed action list so should be denied");
+ yield* loadForegroundTab();
+
+ yield showInfoPromise("appMenu", "Hello from the background", "Surprise!").then(
+ () => ok(false, "panel shouldn't have shown from a background tab"),
+ () => ok(true, "panel wasn't shown from a background tab"));
+
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+
+function* loadForegroundTab() {
+ // Spawn a content task that resolves once we're sure the visibilityState was
+ // changed. This state is what the tests in this file rely on.
+ let promise = ContentTask.spawn(gBrowser.selectedTab.linkedBrowser, null, function* () {
+ return new Promise(resolve => {
+ let document = content.document;
+ document.addEventListener("visibilitychange", function onStateChange() {
+ Assert.equal(document.visibilityState, "hidden", "UITour page should be hidden now.");
+ document.removeEventListener("visibilitychange", onStateChange);
+ resolve();
+ });
+ });
+ });
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+ yield promise;
+ isnot(gBrowser.selectedTab, gTestTab, "Make sure tour tab isn't selected");
+}
diff --git a/browser/components/uitour/test/browser_closeTab.js b/browser/components/uitour/test/browser_closeTab.js
new file mode 100644
index 000000000..2b998347a
--- /dev/null
+++ b/browser/components/uitour/test/browser_closeTab.js
@@ -0,0 +1,18 @@
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_closeTab() {
+ // Setting gTestTab to null indicates that the tab has already been closed,
+ // and if this does not happen the test run will fail.
+ let closePromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabClose");
+ yield gContentAPI.closeTab();
+ yield closePromise;
+ gTestTab = null;
+});
diff --git a/browser/components/uitour/test/browser_fxa.js b/browser/components/uitour/test/browser_fxa.js
new file mode 100644
index 000000000..36ac45a62
--- /dev/null
+++ b/browser/components/uitour/test/browser_fxa.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+function test() {
+ UITourTest();
+}
+
+registerCleanupFunction(function*() {
+ yield signOut();
+ gFxAccounts.updateAppMenuItem();
+});
+
+var tests = [
+ taskify(function* test_highlight_accountStatus_loggedOut() {
+ let userData = yield fxAccounts.getSignedInUser();
+ is(userData, null, "Not logged in initially");
+ yield showMenuPromise("appMenu");
+ yield showHighlightPromise("accountStatus");
+ let highlight = document.getElementById("UITourHighlightContainer");
+ is(highlight.getAttribute("targetName"), "accountStatus", "Correct highlight target");
+ }),
+
+ taskify(function* test_highlight_accountStatus_loggedIn() {
+ yield setSignedInUser();
+ let userData = yield fxAccounts.getSignedInUser();
+ isnot(userData, null, "Logged in now");
+ gFxAccounts.updateAppMenuItem(); // Causes a leak
+ yield showMenuPromise("appMenu");
+ yield showHighlightPromise("accountStatus");
+ let highlight = document.getElementById("UITourHighlightContainer");
+ is(highlight.popupBoxObject.anchorNode.id, "PanelUI-fxa-avatar", "Anchored on avatar");
+ is(highlight.getAttribute("targetName"), "accountStatus", "Correct highlight target");
+ }),
+];
+
+// Helpers copied from browser_aboutAccounts.js
+// watch out - these will fire observers which if you aren't careful, may
+// interfere with the tests.
+function setSignedInUser(data) {
+ if (!data) {
+ data = {
+ email: "foo@example.com",
+ uid: "1234@lcip.org",
+ assertion: "foobar",
+ sessionToken: "dead",
+ kA: "beef",
+ kB: "cafe",
+ verified: true
+ };
+ }
+ return fxAccounts.setSignedInUser(data);
+}
+
+function signOut() {
+ // we always want a "localOnly" signout here...
+ return fxAccounts.signOut(true);
+}
diff --git a/browser/components/uitour/test/browser_no_tabs.js b/browser/components/uitour/test/browser_no_tabs.js
new file mode 100644
index 000000000..62048b156
--- /dev/null
+++ b/browser/components/uitour/test/browser_no_tabs.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var HiddenFrame = Cu.import("resource:///modules/HiddenFrame.jsm", {}).HiddenFrame;
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+/**
+ * Create a frame in the |hiddenDOMWindow| to host a |browser|, then load the URL in the
+ * latter.
+ *
+ * @param aURL
+ * The URL to open in the browser.
+ **/
+function createHiddenBrowser(aURL) {
+ let frame = new HiddenFrame();
+ return new Promise(resolve =>
+ frame.get().then(aFrame => {
+ let doc = aFrame.document;
+ let browser = doc.createElementNS(XUL_NS, "browser");
+ browser.setAttribute("type", "content");
+ browser.setAttribute("disableglobalhistory", "true");
+ browser.setAttribute("src", aURL);
+
+ doc.documentElement.appendChild(browser);
+ resolve({frame: frame, browser: browser});
+ }));
+}
+
+/**
+ * Remove the browser and the HiddenFrame.
+ *
+ * @param aFrame
+ * The HiddenFrame to dismiss.
+ * @param aBrowser
+ * The browser to dismiss.
+ */
+function destroyHiddenBrowser(aFrame, aBrowser) {
+ // Dispose of the hidden browser.
+ aBrowser.remove();
+
+ // Take care of the frame holding our invisible browser.
+ aFrame.destroy();
+}
+
+/**
+ * Test that UITour works when called when no tabs are available (e.g., when using windowless
+ * browsers).
+ */
+add_task(function* test_windowless_UITour() {
+ // Get the URL for the test page.
+ let pageURL = getRootDirectory(gTestPath) + "uitour.html";
+
+ // Allow the URL to use the UITour.
+ info("Adding UITour permission to the test page.");
+ let pageURI = Services.io.newURI(pageURL, null, null);
+ Services.perms.add(pageURI, "uitour", Services.perms.ALLOW_ACTION);
+
+ // UITour's ping will resolve this promise.
+ let deferredPing = Promise.defer();
+
+ // Create a windowless browser and test that UITour works in it.
+ let browserPromise = createHiddenBrowser(pageURL);
+ browserPromise.then(frameInfo => {
+ isnot(frameInfo.browser, null, "The browser must exist and not be null.");
+
+ // Load UITour frame script.
+ frameInfo.browser.messageManager.loadFrameScript(
+ "chrome://browser/content/content-UITour.js", false);
+
+ // When the page loads, try to use UITour API.
+ frameInfo.browser.addEventListener("load", function loadListener() {
+ info("The test page was correctly loaded.");
+
+ frameInfo.browser.removeEventListener("load", loadListener, true);
+
+ // Get a reference to the UITour API.
+ info("Testing access to the UITour API.");
+ let contentWindow = Cu.waiveXrays(frameInfo.browser.contentDocument.defaultView);
+ isnot(contentWindow, null, "The content window must exist and not be null.");
+
+ let uitourAPI = contentWindow.Mozilla.UITour;
+
+ // Test the UITour API with a ping.
+ uitourAPI.ping(function() {
+ info("Ping response received from the UITour API.");
+
+ // Make sure to clean up.
+ destroyHiddenBrowser(frameInfo.frame, frameInfo.browser);
+
+ // Resolve our promise.
+ deferredPing.resolve();
+ });
+ }, true);
+ });
+
+ // Wait for the UITour ping to complete.
+ yield deferredPing.promise;
+});
diff --git a/browser/components/uitour/test/browser_openPreferences.js b/browser/components/uitour/test/browser_openPreferences.js
new file mode 100644
index 000000000..c41865120
--- /dev/null
+++ b/browser/components/uitour/test/browser_openPreferences.js
@@ -0,0 +1,36 @@
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_openPreferences() {
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences");
+ yield gContentAPI.openPreferences();
+ let tab = yield promiseTabOpened;
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_UITour_task(function* test_openInvalidPreferences() {
+ yield gContentAPI.openPreferences(999);
+
+ try {
+ yield waitForConditionPromise(() => {
+ return gBrowser.selectedBrowser.currentURI.spec.startsWith("about:preferences");
+ }, "Check if about:preferences opened");
+ ok(false, "No about:preferences tab should have opened");
+ } catch (ex) {
+ ok(true, "No about:preferences tab opened: " + ex);
+ }
+});
+
+add_UITour_task(function* test_openPrivacyPreferences() {
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#privacy");
+ yield gContentAPI.openPreferences("privacy");
+ let tab = yield promiseTabOpened;
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/uitour/test/browser_openSearchPanel.js b/browser/components/uitour/test/browser_openSearchPanel.js
new file mode 100644
index 000000000..5faa9db02
--- /dev/null
+++ b/browser/components/uitour/test/browser_openSearchPanel.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+function test() {
+ UITourTest();
+}
+
+var tests = [
+ function test_openSearchPanel(done) {
+ let searchbar = document.getElementById("searchbar");
+
+ // If suggestions are enabled, the panel will attempt to use the network to connect
+ // to the suggestions provider, causing the test suite to fail.
+ Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.search.suggest.enabled");
+ });
+
+ ok(!searchbar.textbox.open, "Popup starts as closed");
+ gContentAPI.openSearchPanel(() => {
+ ok(searchbar.textbox.open, "Popup was opened");
+ searchbar.textbox.closePopup();
+ ok(!searchbar.textbox.open, "Popup was closed");
+ done();
+ });
+ },
+];
diff --git a/browser/components/uitour/test/browser_showMenu_controlCenter.js b/browser/components/uitour/test/browser_showMenu_controlCenter.js
new file mode 100644
index 000000000..0faa5f862
--- /dev/null
+++ b/browser/components/uitour/test/browser_showMenu_controlCenter.js
@@ -0,0 +1,44 @@
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const CONTROL_CENTER_PANEL = gIdentityHandler._identityPopup;
+const CONTROL_CENTER_MENU_NAME = "controlCenter";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+add_task(setup_UITourTest);
+
+add_UITour_task(function* test_showMenu() {
+ is_element_hidden(CONTROL_CENTER_PANEL, "Panel should initially be hidden");
+ yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
+ is_element_visible(CONTROL_CENTER_PANEL, "Panel should be visible after showMenu");
+
+ yield gURLBar.focus();
+ is_element_visible(CONTROL_CENTER_PANEL, "Panel should remain visible after focus outside");
+
+ yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
+ is_element_visible(CONTROL_CENTER_PANEL,
+ "Panel should remain visible and callback called after a 2nd showMenu");
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "about:blank"
+ }, function*() {
+ ok(true, "Tab opened");
+ });
+
+ is_element_hidden(CONTROL_CENTER_PANEL, "Panel should hide upon tab switch");
+});
+
+add_UITour_task(function* test_hideMenu() {
+ is_element_hidden(CONTROL_CENTER_PANEL, "Panel should initially be hidden");
+ yield showMenuPromise(CONTROL_CENTER_MENU_NAME);
+ is_element_visible(CONTROL_CENTER_PANEL, "Panel should be visible after showMenu");
+ let hidePromise = promisePanelElementHidden(window, CONTROL_CENTER_PANEL);
+ yield gContentAPI.hideMenu(CONTROL_CENTER_MENU_NAME);
+ yield hidePromise;
+
+ is_element_hidden(CONTROL_CENTER_PANEL, "Panel should hide after hideMenu");
+});
diff --git a/browser/components/uitour/test/browser_trackingProtection.js b/browser/components/uitour/test/browser_trackingProtection.js
new file mode 100644
index 000000000..32a9920ec
--- /dev/null
+++ b/browser/components/uitour/test/browser_trackingProtection.js
@@ -0,0 +1,90 @@
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const PREF_INTRO_COUNT = "privacy.trackingprotection.introCount";
+const PREF_TP_ENABLED = "privacy.trackingprotection.enabled";
+const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/benignPage.html";
+const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/general/trackingPage.html";
+const TOOLTIP_PANEL = document.getElementById("UITourTooltip");
+const TOOLTIP_ANCHOR = document.getElementById("tracking-protection-icon");
+
+var {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+registerCleanupFunction(function() {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref(PREF_TP_ENABLED);
+ Services.prefs.clearUserPref(PREF_INTRO_COUNT);
+});
+
+function allowOneIntro() {
+ Services.prefs.setIntPref(PREF_INTRO_COUNT, TrackingProtection.MAX_INTROS - 1);
+}
+
+add_task(function* setup_test() {
+ Services.prefs.setBoolPref(PREF_TP_ENABLED, true);
+ yield UrlClassifierTestUtils.addTestTrackers();
+});
+
+add_task(function* test_benignPage() {
+ info("Load a test page not containing tracking elements");
+ allowOneIntro();
+ yield BrowserTestUtils.withNewTab({gBrowser, url: BENIGN_PAGE}, function*() {
+ yield waitForConditionPromise(() => {
+ return is_visible(TOOLTIP_PANEL);
+ }, "Info panel shouldn't appear on a benign page").
+ then(() => ok(false, "Info panel shouldn't appear"),
+ () => {
+ ok(true, "Info panel didn't appear on a benign page");
+ });
+
+ });
+});
+
+add_task(function* test_trackingPages() {
+ info("Load a test page containing tracking elements");
+ allowOneIntro();
+ yield BrowserTestUtils.withNewTab({gBrowser, url: TRACKING_PAGE}, function*() {
+ yield new Promise((resolve, reject) => {
+ waitForPopupAtAnchor(TOOLTIP_PANEL, TOOLTIP_ANCHOR, resolve,
+ "Intro panel should appear");
+ });
+
+ is(Services.prefs.getIntPref(PREF_INTRO_COUNT), TrackingProtection.MAX_INTROS, "Check intro count increased");
+
+ let step2URL = Services.urlFormatter.formatURLPref("privacy.trackingprotection.introURL") +
+ "?step=2&newtab=true";
+ let buttons = document.getElementById("UITourTooltipButtons");
+
+ info("Click the step text and nothing should happen");
+ let tabCount = gBrowser.tabs.length;
+ yield EventUtils.synthesizeMouseAtCenter(buttons.children[0], {});
+ is(gBrowser.tabs.length, tabCount, "Same number of tabs should be open");
+
+ info("Resetting count to test that viewing the tour prevents future panels");
+ allowOneIntro();
+
+ let panelHiddenPromise = promisePanelElementHidden(window, TOOLTIP_PANEL);
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, step2URL);
+ info("Clicking the main button");
+ EventUtils.synthesizeMouseAtCenter(buttons.children[1], {});
+ let tab = yield tabPromise;
+ is(Services.prefs.getIntPref(PREF_INTRO_COUNT), TrackingProtection.MAX_INTROS,
+ "Check intro count is at the max after opening step 2");
+ is(gBrowser.tabs.length, tabCount + 1, "Tour step 2 tab opened");
+ yield panelHiddenPromise;
+ ok(true, "Panel hid when the button was clicked");
+ yield BrowserTestUtils.removeTab(tab);
+ });
+
+ info("Open another tracking page and make sure we don't show the panel again");
+ yield BrowserTestUtils.withNewTab({gBrowser, url: TRACKING_PAGE}, function*() {
+ yield waitForConditionPromise(() => {
+ return is_visible(TOOLTIP_PANEL);
+ }, "Info panel shouldn't appear more than MAX_INTROS").
+ then(() => ok(false, "Info panel shouldn't appear again"),
+ () => {
+ ok(true, "Info panel didn't appear more than MAX_INTROS on tracking pages");
+ });
+
+ });
+});
diff --git a/browser/components/uitour/test/browser_trackingProtection_tour.js b/browser/components/uitour/test/browser_trackingProtection_tour.js
new file mode 100644
index 000000000..0ee0e1686
--- /dev/null
+++ b/browser/components/uitour/test/browser_trackingProtection_tour.js
@@ -0,0 +1,77 @@
+"use strict";
+
+var gTestTab;
+var gContentAPI;
+var gContentWindow;
+
+const { UrlClassifierTestUtils } = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+const TP_ENABLED_PREF = "privacy.trackingprotection.enabled";
+
+add_task(setup_UITourTest);
+
+add_task(function* test_setup() {
+ Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ registerCleanupFunction(function() {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref("privacy.trackingprotection.enabled");
+ });
+});
+
+add_UITour_task(function* test_unblock_target() {
+ yield* checkToggleTarget("controlCenter-trackingUnblock");
+});
+
+add_UITour_task(function* setup_block_target() {
+ // Preparation for test_block_target. These are separate since the reload
+ // interferes with UITour as it does a teardown. All we really care about
+ // is the permission manager entry but UITour tests shouldn't rely on that
+ // implementation detail.
+ TrackingProtection.disableForCurrentPage();
+});
+
+add_UITour_task(function* test_block_target() {
+ yield* checkToggleTarget("controlCenter-trackingBlock");
+ TrackingProtection.enableForCurrentPage();
+});
+
+
+function* checkToggleTarget(targetID) {
+ let popup = document.getElementById("UITourTooltip");
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
+ let doc = content.document;
+ let iframe = doc.createElement("iframe");
+ iframe.setAttribute("id", "tracking-element");
+ iframe.setAttribute("src", "https://tracking.example.com/");
+ doc.body.insertBefore(iframe, doc.body.firstChild);
+ });
+
+ let testTargetAvailability = function* (expectedAvailable) {
+ let data = yield getConfigurationPromise("availableTargets");
+ let available = (data.targets.indexOf(targetID) != -1);
+ is(available, expectedAvailable, "Target has expected availability.");
+ };
+ yield testTargetAvailability(false);
+ yield showMenuPromise("controlCenter");
+ yield testTargetAvailability(true);
+
+ yield showInfoPromise(targetID, "This is " + targetID,
+ "My arrow should be on the side");
+ is(popup.popupBoxObject.alignmentPosition, "end_before",
+ "Check " + targetID + " position");
+
+ let hideMenuPromise =
+ promisePanelElementHidden(window, gIdentityHandler._identityPopup);
+ yield gContentAPI.hideMenu("controlCenter");
+ yield hideMenuPromise;
+
+ ok(!is_visible(popup), "The tooltip should now be hidden.");
+ yield testTargetAvailability(false);
+
+ yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
+ content.document.getElementById("tracking-element").remove();
+ });
+}
diff --git a/browser/components/uitour/test/head.js b/browser/components/uitour/test/head.js
new file mode 100644
index 000000000..2b5b994ae
--- /dev/null
+++ b/browser/components/uitour/test/head.js
@@ -0,0 +1,449 @@
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UITour",
+ "resource:///modules/UITour.jsm");
+
+
+const SINGLE_TRY_TIMEOUT = 100;
+const NUMBER_OF_TRIES = 30;
+
+function waitForConditionPromise(condition, timeoutMsg, tryCount=NUMBER_OF_TRIES) {
+ let defer = Promise.defer();
+ let tries = 0;
+ function checkCondition() {
+ if (tries >= tryCount) {
+ defer.reject(timeoutMsg);
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ return defer.reject(e);
+ }
+ if (conditionPassed) {
+ return defer.resolve();
+ }
+ tries++;
+ setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+ return undefined;
+ }
+ setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+ return defer.promise;
+}
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ waitForConditionPromise(condition, errorMsg).then(nextTest, (reason) => {
+ ok(false, reason + (reason.stack ? "\n" + reason.stack : ""));
+ });
+}
+
+/**
+ * Wrapper to partially transition tests to Task. Use `add_UITour_task` instead for new tests.
+ */
+function taskify(fun) {
+ return (done) => {
+ // Output the inner function name otherwise no name will be output.
+ info("\t" + fun.name);
+ return Task.spawn(fun).then(done, (reason) => {
+ ok(false, reason);
+ done();
+ });
+ };
+}
+
+function is_hidden(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+ if (style.display == "-moz-popup")
+ return ["hiding", "closed"].indexOf(element.state) != -1;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_hidden(element.parentNode);
+
+ return false;
+}
+
+function is_visible(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none")
+ return false;
+ if (style.visibility != "visible")
+ return false;
+ if (style.display == "-moz-popup" && element.state != "open")
+ return false;
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument)
+ return is_visible(element.parentNode);
+
+ return true;
+}
+
+function is_element_visible(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_visible(element), msg);
+}
+
+function waitForElementToBeVisible(element, nextTest, msg) {
+ waitForCondition(() => is_visible(element),
+ () => {
+ ok(true, msg);
+ nextTest();
+ },
+ "Timeout waiting for visibility: " + msg);
+}
+
+function waitForElementToBeHidden(element, nextTest, msg) {
+ waitForCondition(() => is_hidden(element),
+ () => {
+ ok(true, msg);
+ nextTest();
+ },
+ "Timeout waiting for invisibility: " + msg);
+}
+
+function elementVisiblePromise(element, msg) {
+ return waitForConditionPromise(() => is_visible(element), "Timeout waiting for visibility: " + msg);
+}
+
+function elementHiddenPromise(element, msg) {
+ return waitForConditionPromise(() => is_hidden(element), "Timeout waiting for invisibility: " + msg);
+}
+
+function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
+ waitForCondition(() => is_visible(popup) && popup.popupBoxObject.anchorNode == anchorNode,
+ () => {
+ ok(true, msg);
+ is_element_visible(popup, "Popup should be visible");
+ nextTest();
+ },
+ "Timeout waiting for popup at anchor: " + msg);
+}
+
+function getConfigurationPromise(configName) {
+ return ContentTask.spawn(gTestTab.linkedBrowser, configName, configName => {
+ return new Promise((resolve) => {
+ let contentWin = Components.utils.waiveXrays(content);
+ contentWin.Mozilla.UITour.getConfiguration(configName, resolve);
+ });
+ });
+}
+
+function hideInfoPromise(...args) {
+ let popup = document.getElementById("UITourTooltip");
+ gContentAPI.hideInfo.apply(gContentAPI, args);
+ return promisePanelElementHidden(window, popup);
+}
+
+/**
+ * `buttons` and `options` require functions from the content scope so we take a
+ * function name to call to generate the buttons/options instead of the
+ * buttons/options themselves. This makes the signature differ from the content one.
+ */
+function showInfoPromise(target, title, text, icon, buttonsFunctionName, optionsFunctionName) {
+ let popup = document.getElementById("UITourTooltip");
+ let shownPromise = promisePanelElementShown(window, popup);
+ return ContentTask.spawn(gTestTab.linkedBrowser, [...arguments], args => {
+ let contentWin = Components.utils.waiveXrays(content);
+ let [target, title, text, icon, buttonsFunctionName, optionsFunctionName] = args;
+ let buttons = buttonsFunctionName ? contentWin[buttonsFunctionName]() : null;
+ let options = optionsFunctionName ? contentWin[optionsFunctionName]() : null;
+ contentWin.Mozilla.UITour.showInfo(target, title, text, icon, buttons, options);
+ }).then(() => shownPromise);
+}
+
+function showHighlightPromise(...args) {
+ let popup = document.getElementById("UITourHighlightContainer");
+ gContentAPI.showHighlight.apply(gContentAPI, args);
+ return promisePanelElementShown(window, popup);
+}
+
+function showMenuPromise(name) {
+ return ContentTask.spawn(gTestTab.linkedBrowser, name, name => {
+ return new Promise((resolve) => {
+ let contentWin = Components.utils.waiveXrays(content);
+ contentWin.Mozilla.UITour.showMenu(name, resolve);
+ });
+ });
+}
+
+function waitForCallbackResultPromise() {
+ return ContentTask.spawn(gTestTab.linkedBrowser, null, function*() {
+ let contentWin = Components.utils.waiveXrays(content);
+ yield ContentTaskUtils.waitForCondition(() => {
+ return contentWin.callbackResult;
+ }, "callback should be called");
+ return {
+ data: contentWin.callbackData,
+ result: contentWin.callbackResult,
+ };
+ });
+}
+
+function promisePanelShown(win) {
+ let panelEl = win.PanelUI.panel;
+ return promisePanelElementShown(win, panelEl);
+}
+
+function promisePanelElementEvent(win, aPanel, aEvent) {
+ return new Promise((resolve, reject) => {
+ let timeoutId = win.setTimeout(() => {
+ aPanel.removeEventListener(aEvent, onPanelEvent);
+ reject(aEvent + " event did not happen within 5 seconds.");
+ }, 5000);
+
+ function onPanelEvent(e) {
+ aPanel.removeEventListener(aEvent, onPanelEvent);
+ win.clearTimeout(timeoutId);
+ // Wait one tick to let UITour.jsm process the event as well.
+ executeSoon(resolve);
+ }
+
+ aPanel.addEventListener(aEvent, onPanelEvent);
+ });
+}
+
+function promisePanelElementShown(win, aPanel) {
+ return promisePanelElementEvent(win, aPanel, "popupshown");
+}
+
+function promisePanelElementHidden(win, aPanel) {
+ return promisePanelElementEvent(win, aPanel, "popuphidden");
+}
+
+function is_element_hidden(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(is_hidden(element), msg);
+}
+
+function isTourBrowser(aBrowser) {
+ let chromeWindow = aBrowser.ownerGlobal;
+ return UITour.tourBrowsersByWindow.has(chromeWindow) &&
+ UITour.tourBrowsersByWindow.get(chromeWindow).has(aBrowser);
+}
+
+function promisePageEvent() {
+ return new Promise((resolve) => {
+ Services.mm.addMessageListener("UITour:onPageEvent", function onPageEvent(aMessage) {
+ Services.mm.removeMessageListener("UITour:onPageEvent", onPageEvent);
+ SimpleTest.executeSoon(resolve);
+ });
+ });
+}
+
+function loadUITourTestPage(callback, host = "https://example.org/") {
+ if (gTestTab)
+ gBrowser.removeTab(gTestTab);
+
+ let url = getRootDirectory(gTestPath) + "uitour.html";
+ url = url.replace("chrome://mochitests/content/", host);
+
+ gTestTab = gBrowser.addTab(url);
+ gBrowser.selectedTab = gTestTab;
+
+ gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
+ gTestTab.linkedBrowser.removeEventListener("load", onLoad, true);
+
+ if (gMultiProcessBrowser) {
+ // When e10s is enabled, make gContentAPI and gContentWindow proxies which has every property
+ // return a function which calls the method of the same name on
+ // contentWin.Mozilla.UITour/contentWin in a ContentTask.
+ let contentWinHandler = {
+ get(target, prop, receiver) {
+ return (...args) => {
+ let taskArgs = {
+ methodName: prop,
+ args,
+ };
+ return ContentTask.spawn(gTestTab.linkedBrowser, taskArgs, args => {
+ let contentWin = Components.utils.waiveXrays(content);
+ return contentWin[args.methodName].apply(contentWin, args.args);
+ });
+ };
+ },
+ };
+ gContentWindow = new Proxy({}, contentWinHandler);
+
+ let UITourHandler = {
+ get(target, prop, receiver) {
+ return (...args) => {
+ let browser = gTestTab.linkedBrowser;
+ const proxyFunctionName = "UITourHandler:proxiedfunction-";
+ // We need to proxy any callback functions using messages:
+ let callbackMap = new Map();
+ let fnIndices = [];
+ args = args.map((arg, index) => {
+ // Replace function arguments with "", and add them to the list of
+ // forwarded functions. We'll construct a function on the content-side
+ // that forwards all its arguments to a message, and we'll listen for
+ // those messages on our side and call the corresponding function with
+ // the arguments we got from the content side.
+ if (typeof arg == "function") {
+ callbackMap.set(index, arg);
+ fnIndices.push(index);
+ let handler = function(msg) {
+ // Please note that this handler assumes that the callback is used only once.
+ // That means that a single gContentAPI.observer() call can't be used to observe
+ // multiple events.
+ browser.messageManager.removeMessageListener(proxyFunctionName + index, handler);
+ callbackMap.get(index).apply(null, msg.data);
+ };
+ browser.messageManager.addMessageListener(proxyFunctionName + index, handler);
+ return "";
+ }
+ return arg;
+ });
+ let taskArgs = {
+ methodName: prop,
+ args,
+ fnIndices,
+ };
+ return ContentTask.spawn(browser, taskArgs, function*(args) {
+ let contentWin = Components.utils.waiveXrays(content);
+ let callbacksCalled = 0;
+ let resolveCallbackPromise;
+ let allCallbacksCalledPromise = new Promise(resolve => resolveCallbackPromise = resolve);
+ let argumentsWithFunctions = args.args.map((arg, index) => {
+ if (arg === "" && args.fnIndices.includes(index)) {
+ return function() {
+ callbacksCalled++;
+ sendAsyncMessage("UITourHandler:proxiedfunction-" + index, Array.from(arguments));
+ if (callbacksCalled >= args.fnIndices.length) {
+ resolveCallbackPromise();
+ }
+ };
+ }
+ return arg;
+ });
+ let rv = contentWin.Mozilla.UITour[args.methodName].apply(contentWin.Mozilla.UITour,
+ argumentsWithFunctions);
+ if (args.fnIndices.length) {
+ yield allCallbacksCalledPromise;
+ }
+ return rv;
+ });
+ };
+ },
+ };
+ gContentAPI = new Proxy({}, UITourHandler);
+ } else {
+ gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
+ gContentAPI = gContentWindow.Mozilla.UITour;
+ }
+
+ waitForFocus(callback, gTestTab.linkedBrowser);
+ }, true);
+}
+
+// Wrapper for UITourTest to be used by add_task tests.
+function* setup_UITourTest() {
+ return UITourTest(true);
+}
+
+// Use `add_task(setup_UITourTest);` instead as we will fold this into `setup_UITourTest` once all tests are using `add_UITour_task`.
+function UITourTest(usingAddTask = false) {
+ Services.prefs.setBoolPref("browser.uitour.enabled", true);
+ let testHttpsUri = Services.io.newURI("https://example.org", null, null);
+ let testHttpUri = Services.io.newURI("http://example.org", null, null);
+ Services.perms.add(testHttpsUri, "uitour", Services.perms.ALLOW_ACTION);
+ Services.perms.add(testHttpUri, "uitour", Services.perms.ALLOW_ACTION);
+
+ // If a test file is using add_task, we don't need to have a test function or
+ // call `waitForExplicitFinish`.
+ if (!usingAddTask) {
+ waitForExplicitFinish();
+ }
+
+ registerCleanupFunction(function() {
+ delete window.gContentWindow;
+ delete window.gContentAPI;
+ if (gTestTab)
+ gBrowser.removeTab(gTestTab);
+ delete window.gTestTab;
+ Services.prefs.clearUserPref("browser.uitour.enabled");
+ Services.perms.remove(testHttpsUri, "uitour");
+ Services.perms.remove(testHttpUri, "uitour");
+ });
+
+ // When using tasks, the harness will call the next added task for us.
+ if (!usingAddTask) {
+ nextTest();
+ }
+}
+
+function done(usingAddTask = false) {
+ info("== Done test, doing shared checks before teardown ==");
+ return new Promise((resolve) => {
+ executeSoon(() => {
+ if (gTestTab)
+ gBrowser.removeTab(gTestTab);
+ gTestTab = null;
+
+ let highlight = document.getElementById("UITourHighlightContainer");
+ is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
+
+ let tooltip = document.getElementById("UITourTooltip");
+ is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
+
+ ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
+ ok(!PanelUI.panel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
+ isnot(PanelUI.panel.state, "open", "The panel shouldn't be open");
+ is(document.getElementById("PanelUI-menu-button").hasAttribute("open"), false, "Menu button should know that the menu is closed");
+
+ info("Done shared checks");
+ if (usingAddTask) {
+ executeSoon(resolve);
+ } else {
+ executeSoon(nextTest);
+ }
+ });
+ });
+}
+
+function nextTest() {
+ if (tests.length == 0) {
+ info("finished tests in this file");
+ finish();
+ return;
+ }
+ let test = tests.shift();
+ info("Starting " + test.name);
+ waitForFocus(function() {
+ loadUITourTestPage(function() {
+ test(done);
+ });
+ });
+}
+
+/**
+ * All new tests that need the help of `loadUITourTestPage` should use this
+ * wrapper around their test's generator function to reduce boilerplate.
+ */
+function add_UITour_task(func) {
+ let genFun = function*() {
+ yield new Promise((resolve) => {
+ waitForFocus(function() {
+ loadUITourTestPage(function() {
+ let funcPromise = Task.spawn(func)
+ .then(() => done(true),
+ (reason) => {
+ ok(false, reason);
+ return done(true);
+ });
+ resolve(funcPromise);
+ });
+ });
+ });
+ };
+ Object.defineProperty(genFun, "name", {
+ configurable: true,
+ value: func.name,
+ });
+ add_task(genFun);
+}
diff --git a/browser/components/uitour/test/image.png b/browser/components/uitour/test/image.png
new file mode 100644
index 000000000..597c7fd2c
--- /dev/null
+++ b/browser/components/uitour/test/image.png
Binary files differ
diff --git a/browser/components/uitour/test/uitour.html b/browser/components/uitour/test/uitour.html
new file mode 100644
index 000000000..6c42ac7f8
--- /dev/null
+++ b/browser/components/uitour/test/uitour.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>UITour test</title>
+ <script type="application/javascript" src="UITour-lib.js">
+ </script>
+ <script type="application/javascript">
+ var callbackResult, callbackData;
+ function makeCallback(name) {
+ return (function(data) {
+ callbackResult = name;
+ callbackData = data;
+ });
+ }
+
+ // Defined in content to avoid weird issues when crossing between chrome/content.
+ function makeButtons() {
+ return [
+ {label: "Regular text", style: "text"},
+ {label: "Link", callback: makeCallback("link"), style: "link"},
+ {label: "Button 1", callback: makeCallback("button1")},
+ {label: "Button 2", callback: makeCallback("button2"), icon: "image.png",
+ style: "primary"}
+ ];
+ }
+
+ function makeInfoOptions() {
+ return {
+ closeButtonCallback: makeCallback("closeButton"),
+ targetCallback: makeCallback("target"),
+ };
+ }
+ </script>
+ </head>
+ <body>
+ <h1>UITour tests</h1>
+ <p>Because Firefox is...</p>
+ <p>Never gonna let you down</p>
+ <p>Never gonna give you up</p>
+ </body>
+</html>
diff --git a/browser/config/mozconfig b/browser/config/mozconfig
new file mode 100644
index 000000000..ad2e3a4ea
--- /dev/null
+++ b/browser/config/mozconfig
@@ -0,0 +1,9 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file specifies the build flags for Firefox. You can use it by adding:
+# . $topsrcdir/browser/config/mozconfig
+# to the top of your mozconfig file.
+
+ac_add_options --enable-application=browser
diff --git a/browser/config/mozconfigs/common b/browser/config/mozconfigs/common
new file mode 100644
index 000000000..febf5622f
--- /dev/null
+++ b/browser/config/mozconfigs/common
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file is included by all browser mozconfigs
+
+. "$topsrcdir/build/mozconfig.common"
diff --git a/browser/config/mozconfigs/linux32/artifact b/browser/config/mozconfigs/linux32/artifact
new file mode 100644
index 000000000..29eb8ba1c
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/artifact
@@ -0,0 +1,10 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
diff --git a/browser/config/mozconfigs/linux32/beta b/browser/config/mozconfigs/linux32/beta
new file mode 100644
index 000000000..315c32b84
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/beta
@@ -0,0 +1,15 @@
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+mk_add_options MOZ_PGO=1
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux32/common-opt b/browser/config/mozconfigs/linux32/common-opt
new file mode 100644
index 000000000..3bf3e530e
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/common-opt
@@ -0,0 +1,15 @@
+# This file is sourced by nightly, beta, and release mozconfigs.
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
+
+. $topsrcdir/build/unix/mozconfig.linux32
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/browser/config/mozconfigs/linux32/debug b/browser/config/mozconfigs/linux32/debug
new file mode 100644
index 000000000..de97087d2
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/debug
@@ -0,0 +1,24 @@
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+ac_add_options --enable-verify-mar
+
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. $topsrcdir/build/unix/mozconfig.linux32
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+#Use ccache
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/linux32/debug-artifact b/browser/config/mozconfigs/linux32/debug-artifact
new file mode 100644
index 000000000..3fc3faf89
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/debug-artifact
@@ -0,0 +1,12 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/unix/mozconfig.linux32"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
+
+ac_add_options --enable-debug
diff --git a/browser/config/mozconfigs/linux32/debug-asan b/browser/config/mozconfigs/linux32/debug-asan
new file mode 100644
index 000000000..9fc7b9bff
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/debug-asan
@@ -0,0 +1,23 @@
+# Use at least -O1 for optimization to avoid stack space
+# exhaustions caused by Clang function inlining.
+ac_add_options --enable-debug
+ac_add_options --enable-optimize="-O1"
+
+# ASan specific options on Linux
+ac_add_options --enable-valgrind
+
+. $topsrcdir/build/unix/mozconfig.asan
+
+export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux32/l10n-mozconfig b/browser/config/mozconfigs/linux32/l10n-mozconfig
new file mode 100644
index 000000000..ae7cc933b
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/l10n-mozconfig
@@ -0,0 +1,20 @@
+no_sccache=1
+
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-official-branding
+
+. $topsrcdir/build/unix/mozconfig.linux32
+
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+ac_add_options --disable-stdcxx-compat
+
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux32/nightly b/browser/config/mozconfigs/linux32/nightly
new file mode 100644
index 000000000..6d605ca7d
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/nightly
@@ -0,0 +1,15 @@
+. "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
+
+
+ac_add_options --enable-verify-mar
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/linux32/nightly-asan b/browser/config/mozconfigs/linux32/nightly-asan
new file mode 100644
index 000000000..0930c184f
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/nightly-asan
@@ -0,0 +1,22 @@
+# We still need to build with debug symbols
+ac_add_options --disable-debug
+ac_add_options --enable-optimize="-O2 -g"
+
+# ASan specific options on Linux
+ac_add_options --enable-valgrind
+
+. $topsrcdir/build/unix/mozconfig.asan
+
+export PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux32/release b/browser/config/mozconfigs/linux32/release
new file mode 100644
index 000000000..b17186666
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/release
@@ -0,0 +1,22 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+mk_add_options MOZ_PGO=1
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux32/valgrind b/browser/config/mozconfigs/linux32/valgrind
new file mode 100644
index 000000000..c8fb4f574
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/valgrind
@@ -0,0 +1,9 @@
+. $topsrcdir/browser/config/mozconfigs/linux32/nightly
+
+ac_add_options --enable-valgrind
+ac_add_options --disable-jemalloc
+ac_add_options --disable-install-strip
+
+# Include the override mozconfig again (even though the above includes it)
+# since it's supposed to override everything.
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/add-on-devel b/browser/config/mozconfigs/linux64/add-on-devel
new file mode 100644
index 000000000..0a3dfeb3f
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/add-on-devel
@@ -0,0 +1,9 @@
+. $topsrcdir/browser/config/mozconfigs/linux64/nightly
+
+#add-on signing is checked but not enforced
+MOZ_REQUIRE_SIGNING=0
+ac_add_options --with-branding=browser/branding/unofficial
+ac_add_options --enable-update-channel=default
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=add-on-devel
diff --git a/browser/config/mozconfigs/linux64/artifact b/browser/config/mozconfigs/linux64/artifact
new file mode 100644
index 000000000..524a25e69
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/artifact
@@ -0,0 +1,10 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
diff --git a/browser/config/mozconfigs/linux64/beta b/browser/config/mozconfigs/linux64/beta
new file mode 100644
index 000000000..f349ee9f8
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/beta
@@ -0,0 +1,15 @@
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+mk_add_options MOZ_PGO=1
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/code-coverage b/browser/config/mozconfigs/linux64/code-coverage
new file mode 100644
index 000000000..48ffa9b17
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/code-coverage
@@ -0,0 +1,13 @@
+. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
+
+TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
+
+ac_add_options --disable-install-strip
+ac_add_options --disable-jemalloc
+ac_add_options --disable-crashreporter
+ac_add_options --disable-elf-hack
+
+MOZ_CODE_COVERAGE=1
+export CFLAGS="-fprofile-arcs -ftest-coverage"
+export CXXFLAGS="-fprofile-arcs -ftest-coverage"
+export LDFLAGS="-fprofile-arcs -ftest-coverage -lgcov -L$TOOLTOOL_DIR/gtk3/usr/local/lib"
diff --git a/browser/config/mozconfigs/linux64/common-opt b/browser/config/mozconfigs/linux64/common-opt
new file mode 100644
index 000000000..0d605ff9e
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/common-opt
@@ -0,0 +1,15 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
+
+. $topsrcdir/build/unix/mozconfig.linux
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/browser/config/mozconfigs/linux64/debug b/browser/config/mozconfigs/linux64/debug
new file mode 100644
index 000000000..a879da75e
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/debug
@@ -0,0 +1,22 @@
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+ac_add_options --enable-verify-mar
+
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. $topsrcdir/build/unix/mozconfig.linux
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/linux64/debug-artifact b/browser/config/mozconfigs/linux64/debug-artifact
new file mode 100644
index 000000000..6dc4d13ce
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/debug-artifact
@@ -0,0 +1,13 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/unix/mozconfig.linux"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
+
+ac_add_options --enable-debug
+
diff --git a/browser/config/mozconfigs/linux64/debug-asan b/browser/config/mozconfigs/linux64/debug-asan
new file mode 100644
index 000000000..a772dbb51
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/debug-asan
@@ -0,0 +1,23 @@
+# Use at least -O1 for optimization to avoid stack space
+# exhaustions caused by Clang function inlining.
+ac_add_options --enable-debug
+ac_add_options --enable-optimize="-O1"
+
+# ASan specific options on Linux
+ac_add_options --enable-valgrind
+
+. $topsrcdir/build/unix/mozconfig.asan
+
+export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/debug-static-analysis-clang b/browser/config/mozconfigs/linux64/debug-static-analysis-clang
new file mode 100644
index 000000000..ff41e13f9
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/debug-static-analysis-clang
@@ -0,0 +1,23 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/mozconfig.common"
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+
+# Use Clang as specified in manifest
+export CC="$topsrcdir/clang/bin/clang"
+export CXX="$topsrcdir/clang/bin/clang++"
+
+# Add the static checker
+ac_add_options --enable-clang-plugin
+
+. "$topsrcdir/build/unix/mozconfig.stdcxx"
+
+export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/linux64/hazards b/browser/config/mozconfigs/linux64/hazards
new file mode 100644
index 000000000..03d72ef7e
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/hazards
@@ -0,0 +1,35 @@
+# This mozconfig is used when compiling the browser for the rooting hazard
+# analysis build (labeled H on treeherder). See
+# https://wiki.mozilla.org/Javascript:SpiderMonkey:ExactStackRooting
+
+# Do NOT include build/unix/mozconfig.linux because it points directly at the
+# tooltool-installed gcc, and the analysis works by wrapping the gcc invocation
+# with a script that invokes the real gcc with -fplugin and its configuration
+# directives. Instead, duplicate the contents of that mozconfig here:
+
+. "$topsrcdir/build/mozconfig.common"
+ac_add_options --enable-elf-hack
+
+. "$topsrcdir/build/unix/mozconfig.stdcxx"
+
+# The objdir must be at a known location so its path can be stripped from the
+# filenames stored by the analysis
+mk_add_options MOZ_OBJDIR=obj-analyzed
+
+# The configuration options are chosen to compile the most code
+# (--enable-debug, --enable-tests) in the trickiest way possible
+# (--enable-optimize) to maximize the chance of seeing tricky static orderings.
+ac_add_options --enable-debug
+ac_add_options --enable-tests
+ac_add_options --enable-optimize
+ac_add_options --with-compiler-wrapper=$TOOLTOOL_DIR/sixgill/usr/libexec/sixgill/scripts/wrap_gcc/basecc
+ac_add_options --without-ccache
+
+CFLAGS="$CFLAGS -Wno-attributes"
+CPPFLAGS="$CPPFLAGS -Wno-attributes"
+CXXFLAGS="$CXXFLAGS -Wno-attributes"
+
+export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/l10n-mozconfig b/browser/config/mozconfigs/linux64/l10n-mozconfig
new file mode 100644
index 000000000..4df482dc3
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/l10n-mozconfig
@@ -0,0 +1,20 @@
+no_sccache=1
+
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-official-branding
+
+. $topsrcdir/build/unix/mozconfig.linux
+
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+ac_add_options --disable-stdcxx-compat
+
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/nightly b/browser/config/mozconfigs/linux64/nightly
new file mode 100644
index 000000000..ec360a99a
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/nightly
@@ -0,0 +1,15 @@
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+
+
+ac_add_options --enable-verify-mar
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/linux64/nightly-asan b/browser/config/mozconfigs/linux64/nightly-asan
new file mode 100644
index 000000000..833b530d2
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/nightly-asan
@@ -0,0 +1,19 @@
+# We still need to build with debug symbols
+ac_add_options --disable-debug
+ac_add_options --enable-optimize="-O2 -gline-tables-only"
+
+# ASan specific options on Linux
+ac_add_options --enable-valgrind
+
+. $topsrcdir/build/unix/mozconfig.asan
+
+export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/opt-static-analysis-clang b/browser/config/mozconfigs/linux64/opt-static-analysis-clang
new file mode 100644
index 000000000..936cc46f7
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/opt-static-analysis-clang
@@ -0,0 +1,22 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/mozconfig.common"
+
+ac_add_options --enable-dmd
+
+# Use Clang as specified in manifest
+CC="$topsrcdir/clang/bin/clang"
+CXX="$topsrcdir/clang/bin/clang++"
+
+# Add the static checker
+ac_add_options --enable-clang-plugin
+
+. "$topsrcdir/build/unix/mozconfig.stdcxx"
+
+export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/linux64/opt-tsan b/browser/config/mozconfigs/linux64/opt-tsan
new file mode 100644
index 000000000..12b156633
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/opt-tsan
@@ -0,0 +1,9 @@
+. $topsrcdir/build/unix/mozconfig.tsan
+
+export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig
+. $topsrcdir/build/unix/mozconfig.gtk
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=tsan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/release b/browser/config/mozconfigs/linux64/release
new file mode 100644
index 000000000..2090f3525
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/release
@@ -0,0 +1,22 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+mk_add_options MOZ_PGO=1
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/linux64/source b/browser/config/mozconfigs/linux64/source
new file mode 100644
index 000000000..4a3c6996b
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/source
@@ -0,0 +1,4 @@
+# The source "build" only needs a mozconfig because we use the build system as
+# our script for generating it. This allows us to run configure without any
+# extra dependencies on specific toolchains, e.g. gtk3.
+ac_add_options --disable-compile-environment
diff --git a/browser/config/mozconfigs/linux64/valgrind b/browser/config/mozconfigs/linux64/valgrind
new file mode 100644
index 000000000..2efc6e3bb
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/valgrind
@@ -0,0 +1,9 @@
+. $topsrcdir/browser/config/mozconfigs/linux64/nightly
+
+ac_add_options --enable-valgrind
+ac_add_options --disable-jemalloc
+ac_add_options --disable-install-strip
+
+# Include the override mozconfig again (even though the above includes it)
+# since it's supposed to override everything.
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/macosx-universal/beta b/browser/config/mozconfigs/macosx-universal/beta
new file mode 100644
index 000000000..937185e94
--- /dev/null
+++ b/browser/config/mozconfigs/macosx-universal/beta
@@ -0,0 +1,15 @@
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/macosx-universal/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx-universal/common-opt b/browser/config/mozconfigs/macosx-universal/common-opt
new file mode 100644
index 000000000..145d32fc9
--- /dev/null
+++ b/browser/config/mozconfigs/macosx-universal/common-opt
@@ -0,0 +1,18 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+. $topsrcdir/build/macosx/universal/mozconfig
+
+# Universal builds override the default of browser (bug 575283 comment 29)
+ac_add_options --enable-application=browser
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/browser/config/mozconfigs/macosx-universal/l10n-mozconfig b/browser/config/mozconfigs/macosx-universal/l10n-mozconfig
new file mode 100644
index 000000000..80a06ed65
--- /dev/null
+++ b/browser/config/mozconfigs/macosx-universal/l10n-mozconfig
@@ -0,0 +1,22 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/build/macosx/mozconfig.common"
+
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-official-branding
+
+if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
+ac_add_options --with-macbundlename-prefix=Firefox
+fi
+
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx-universal/nightly b/browser/config/mozconfigs/macosx-universal/nightly
new file mode 100644
index 000000000..7af737e56
--- /dev/null
+++ b/browser/config/mozconfigs/macosx-universal/nightly
@@ -0,0 +1,21 @@
+. "$topsrcdir/browser/config/mozconfigs/macosx-universal/common-opt"
+
+ac_add_options --disable-install-strip
+ac_add_options --enable-verify-mar
+
+ac_add_options --enable-instruments
+
+# Cross-universal builds fail when dtrace is enabled
+if test `uname -s` != Linux; then
+ ac_add_options --enable-dtrace
+fi
+
+if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
+ac_add_options --with-macbundlename-prefix=Firefox
+fi
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx-universal/release b/browser/config/mozconfigs/macosx-universal/release
new file mode 100644
index 000000000..4700886aa
--- /dev/null
+++ b/browser/config/mozconfigs/macosx-universal/release
@@ -0,0 +1,21 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/macosx-universal/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx64/add-on-devel b/browser/config/mozconfigs/macosx64/add-on-devel
new file mode 100644
index 000000000..e76df4dfa
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/add-on-devel
@@ -0,0 +1,9 @@
+. $topsrcdir/browser/config/mozconfigs/macosx64/nightly
+
+#add-on signing is checked but not enforced
+MOZ_REQUIRE_SIGNING=0
+ac_add_options --with-branding=browser/branding/unofficial
+ac_add_options --enable-update-channel=default
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=add-on-devel
diff --git a/browser/config/mozconfigs/macosx64/artifact b/browser/config/mozconfigs/macosx64/artifact
new file mode 100644
index 000000000..c25b21122
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/artifact
@@ -0,0 +1,10 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/macosx/mozconfig.common"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
diff --git a/browser/config/mozconfigs/macosx64/debug b/browser/config/mozconfigs/macosx64/debug
new file mode 100644
index 000000000..4b052aa00
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/debug
@@ -0,0 +1,24 @@
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+ac_add_options --enable-verify-mar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
+ac_add_options --with-macbundlename-prefix=Firefox
+fi
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx64/debug-artifact b/browser/config/mozconfigs/macosx64/debug-artifact
new file mode 100644
index 000000000..3afa8e4bf
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/debug-artifact
@@ -0,0 +1,12 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/macosx/mozconfig.common"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
+
+ac_add_options --enable-debug
diff --git a/browser/config/mozconfigs/macosx64/debug-asan b/browser/config/mozconfigs/macosx64/debug-asan
new file mode 100644
index 000000000..28d23798b
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/debug-asan
@@ -0,0 +1,20 @@
+. $topsrcdir/build/unix/mozconfig.asan
+
+ac_add_options --enable-application=browser
+ac_add_options --enable-debug
+ac_add_options --enable-optimize="-O1"
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
+ac_add_options --with-macbundlename-prefix=Firefox
+fi
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/macosx64/debug-static-analysis b/browser/config/mozconfigs/macosx64/debug-static-analysis
new file mode 100644
index 000000000..bb19af612
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/debug-static-analysis
@@ -0,0 +1,13 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+
+ac_add_options --enable-clang-plugin
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx64/l10n-mozconfig b/browser/config/mozconfigs/macosx64/l10n-mozconfig
new file mode 100644
index 000000000..40ad84086
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/l10n-mozconfig
@@ -0,0 +1,12 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-official-branding
+
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx64/nightly b/browser/config/mozconfigs/macosx64/nightly
new file mode 100644
index 000000000..12fec0474
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/nightly
@@ -0,0 +1,22 @@
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-verify-mar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
+ac_add_options --with-macbundlename-prefix=Firefox
+fi
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx64/opt-static-analysis b/browser/config/mozconfigs/macosx64/opt-static-analysis
new file mode 100644
index 000000000..72ed3d6bd
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/opt-static-analysis
@@ -0,0 +1,16 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --disable-debug
+ac_add_options --enable-optimize
+ac_add_options --enable-dmd
+
+ac_add_options --enable-clang-plugin
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
+
diff --git a/browser/config/mozconfigs/whitelist b/browser/config/mozconfigs/whitelist
new file mode 100644
index 000000000..65b66b6f2
--- /dev/null
+++ b/browser/config/mozconfigs/whitelist
@@ -0,0 +1,100 @@
+# 'nightly' contains things that are in nightly mozconfigs and allowed to be missing from release builds.
+# Other keys in whitelist contain things are in that branches mozconfigs and allowed to be missing from nightly builds.
+whitelist = {
+ 'release': {},
+ 'nightly': {},
+ }
+
+all_platforms = ['win64', 'win32', 'linux32', 'linux64', 'macosx-universal']
+
+for platform in all_platforms:
+ whitelist['nightly'][platform] = [
+ 'ac_add_options --enable-update-channel=nightly',
+ 'ac_add_options --with-branding=browser/branding/nightly',
+ 'ac_add_options --enable-profiling',
+ 'mk_add_options CLIENT_PY_ARGS="--hg-options=\'--verbose --time\' --hgtool=../tools/buildfarm/utils/hgtool.py --skip-chatzilla --skip-comm --skip-inspector --tinderbox-print"'
+ ]
+
+for platform in ['linux32', 'linux64', 'macosx-universal']:
+ whitelist['nightly'][platform] += [
+ 'mk_add_options MOZ_MAKE_FLAGS="-j4"',
+ ]
+
+whitelist['nightly']['linux32'] += [
+ 'CXX=$REAL_CXX',
+ 'CXX="ccache $REAL_CXX"',
+ 'CC="ccache $REAL_CC"',
+ 'mk_add_options PROFILE_GEN_SCRIPT=@TOPSRCDIR@/build/profile_pageloader.pl',
+ 'ac_add_options --with-ccache=/usr/bin/ccache',
+ '. "$topsrcdir/build/mozconfig.cache"',
+ 'export MOZILLA_OFFICIAL=1',
+ 'export MOZ_TELEMETRY_REPORTING=1',
+ "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
+ 'STRIP_FLAGS="--strip-debug"',
+]
+
+whitelist['nightly']['linux64'] += [
+ 'export MOZILLA_OFFICIAL=1',
+ 'export MOZ_TELEMETRY_REPORTING=1',
+ "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
+ 'STRIP_FLAGS="--strip-debug"',
+ 'ac_add_options --with-ccache=/usr/bin/ccache',
+ '. "$topsrcdir/build/mozconfig.cache"',
+]
+
+whitelist['nightly']['macosx-universal'] += [
+ 'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then',
+ 'ac_add_options --with-macbundlename-prefix=Firefox',
+ 'fi',
+ 'mk_add_options MOZ_MAKE_FLAGS="-j12"',
+ 'ac_add_options --with-ccache',
+ '. "$topsrcdir/build/mozconfig.cache"',
+ 'ac_add_options --disable-install-strip',
+ 'ac_add_options --enable-instruments',
+ 'ac_add_options --enable-dtrace',
+]
+
+whitelist['nightly']['win32'] += [
+ '. $topsrcdir/configs/mozilla2/win32/include/choose-make-flags',
+ 'mk_add_options MOZ_MAKE_FLAGS=-j1',
+ '. "$topsrcdir/build/mozconfig.cache"',
+ 'if test "$IS_NIGHTLY" != ""; then',
+ 'ac_add_options --disable-auto-deps',
+ 'fi',
+]
+whitelist['nightly']['win64'] += [
+ '. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"',
+ '. "$topsrcdir/build/mozconfig.cache"',
+]
+
+for platform in all_platforms:
+ whitelist['release'][platform] = [
+ 'ac_add_options --enable-update-channel=release',
+ 'ac_add_options --enable-official-branding',
+ 'mk_add_options MOZ_MAKE_FLAGS="-j4"',
+ 'export BUILDING_RELEASE=1',
+ 'if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then',
+ 'MOZ_AUTOMATION_UPLOAD_SYMBOLS=1',
+ 'MOZ_AUTOMATION_UPDATE_PACKAGING=1',
+ 'fi',
+ 'MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}',
+ ]
+whitelist['release']['win32'] += ['mk_add_options MOZ_PGO=1']
+whitelist['release']['win64'] += ['mk_add_options MOZ_PGO=1']
+
+whitelist['release']['linux32'] += [
+ 'export MOZILLA_OFFICIAL=1',
+ 'export MOZ_TELEMETRY_REPORTING=1',
+ 'mk_add_options MOZ_PGO=1',
+ "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
+]
+whitelist['release']['linux64'] += [
+ 'export MOZILLA_OFFICIAL=1',
+ 'export MOZ_TELEMETRY_REPORTING=1',
+ 'mk_add_options MOZ_PGO=1',
+ "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
+]
+
+if __name__ == '__main__':
+ import pprint
+ pprint.pprint(whitelist)
diff --git a/browser/config/mozconfigs/win32/add-on-devel b/browser/config/mozconfigs/win32/add-on-devel
new file mode 100644
index 000000000..d84c1f5e4
--- /dev/null
+++ b/browser/config/mozconfigs/win32/add-on-devel
@@ -0,0 +1,9 @@
+. $topsrcdir/browser/config/mozconfigs/win32/nightly
+
+#add-on signing is checked but not enforced
+MOZ_REQUIRE_SIGNING=0
+ac_add_options --with-branding=browser/branding/unofficial
+ac_add_options --enable-update-channel=default
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=add-on-devel
diff --git a/browser/config/mozconfigs/win32/artifact b/browser/config/mozconfigs/win32/artifact
new file mode 100644
index 000000000..888c99668
--- /dev/null
+++ b/browser/config/mozconfigs/win32/artifact
@@ -0,0 +1,10 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/build/win32/mozconfig.vs-latest"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
diff --git a/browser/config/mozconfigs/win32/beta b/browser/config/mozconfigs/win32/beta
new file mode 100644
index 000000000..cc14d6800
--- /dev/null
+++ b/browser/config/mozconfigs/win32/beta
@@ -0,0 +1,18 @@
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
+
+mk_add_options MOZ_PGO=1
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/win32/common-opt b/browser/config/mozconfigs/win32/common-opt
new file mode 100644
index 000000000..d43a9878b
--- /dev/null
+++ b/browser/config/mozconfigs/win32/common-opt
@@ -0,0 +1,26 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+ac_add_options --enable-require-all-d3dc-versions
+
+if [ -f /c/builds/gapi.data ]; then
+ _gapi_keyfile=c:/builds/gapi.data
+else
+ _gapi_keyfile=e:/builds/gapi.data
+fi
+ac_add_options --with-google-api-keyfile=${_gapi_keyfile}
+
+ac_add_options --with-mozilla-api-keyfile=c:/builds/mozilla-desktop-geoloc-api.key
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/browser/config/mozconfigs/win32/debug b/browser/config/mozconfigs/win32/debug
new file mode 100644
index 000000000..6beee93c2
--- /dev/null
+++ b/browser/config/mozconfigs/win32/debug
@@ -0,0 +1,27 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+ac_add_options --enable-profiling # needed for --enable-dmd to work on Windows
+ac_add_options --enable-verify-mar
+ac_add_options --enable-require-all-d3dc-versions
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/win32/debug-artifact b/browser/config/mozconfigs/win32/debug-artifact
new file mode 100644
index 000000000..5a31b8b3e
--- /dev/null
+++ b/browser/config/mozconfigs/win32/debug-artifact
@@ -0,0 +1,12 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/build/win32/mozconfig.vs-latest"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+
+ac_add_options --enable-debug
diff --git a/browser/config/mozconfigs/win32/debug-static-analysis b/browser/config/mozconfigs/win32/debug-static-analysis
new file mode 100644
index 000000000..efc0820b5
--- /dev/null
+++ b/browser/config/mozconfigs/win32/debug-static-analysis
@@ -0,0 +1,19 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+
+ac_add_options --enable-clang-plugin
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
+. "$topsrcdir/build/mozconfig.clang-cl"
diff --git a/browser/config/mozconfigs/win32/l10n-mozconfig b/browser/config/mozconfigs/win32/l10n-mozconfig
new file mode 100644
index 000000000..d05067e03
--- /dev/null
+++ b/browser/config/mozconfigs/win32/l10n-mozconfig
@@ -0,0 +1,19 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --with-windows-version=603
+ac_add_options --enable-official-branding
+
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/win32/nightly b/browser/config/mozconfigs/win32/nightly
new file mode 100644
index 000000000..210004905
--- /dev/null
+++ b/browser/config/mozconfigs/win32/nightly
@@ -0,0 +1,12 @@
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
+
+
+ac_add_options --enable-verify-mar
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/win32/release b/browser/config/mozconfigs/win32/release
new file mode 100644
index 000000000..daaa45f89
--- /dev/null
+++ b/browser/config/mozconfigs/win32/release
@@ -0,0 +1,24 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
+
+mk_add_options MOZ_PGO=1
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/win64/add-on-devel b/browser/config/mozconfigs/win64/add-on-devel
new file mode 100644
index 000000000..47d0bf589
--- /dev/null
+++ b/browser/config/mozconfigs/win64/add-on-devel
@@ -0,0 +1,9 @@
+. $topsrcdir/browser/config/mozconfigs/win64/nightly
+
+#add-on signing is checked but not enforced
+MOZ_REQUIRE_SIGNING=0
+ac_add_options --with-branding=browser/branding/unofficial
+ac_add_options --enable-update-channel=default
+
+#Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=add-on-devel
diff --git a/browser/config/mozconfigs/win64/artifact b/browser/config/mozconfigs/win64/artifact
new file mode 100644
index 000000000..ce6969667
--- /dev/null
+++ b/browser/config/mozconfigs/win64/artifact
@@ -0,0 +1,11 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/build/win64/mozconfig.vs-latest"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
diff --git a/browser/config/mozconfigs/win64/beta b/browser/config/mozconfigs/win64/beta
new file mode 100644
index 000000000..6fc4ebe36
--- /dev/null
+++ b/browser/config/mozconfigs/win64/beta
@@ -0,0 +1,19 @@
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+. "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
+
+mk_add_options MOZ_PGO=1
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/win64/common-opt b/browser/config/mozconfigs/win64/common-opt
new file mode 100644
index 000000000..40fba7f76
--- /dev/null
+++ b/browser/config/mozconfigs/win64/common-opt
@@ -0,0 +1,24 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+if [ -f /c/builds/gapi.data ]; then
+ _gapi_keyfile=c:/builds/gapi.data
+else
+ _gapi_keyfile=e:/builds/gapi.data
+fi
+ac_add_options --with-google-api-keyfile=${_gapi_keyfile}
+
+ac_add_options --with-mozilla-api-keyfile=c:/builds/mozilla-desktop-geoloc-api.key
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+. $topsrcdir/build/win64/mozconfig.vs-latest
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/browser/config/mozconfigs/win64/common-win64 b/browser/config/mozconfigs/win64/common-win64
new file mode 100644
index 000000000..8f167cad1
--- /dev/null
+++ b/browser/config/mozconfigs/win64/common-win64
@@ -0,0 +1,5 @@
+# This file is used by all Win64 builds
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
diff --git a/browser/config/mozconfigs/win64/debug b/browser/config/mozconfigs/win64/debug
new file mode 100644
index 000000000..db4eb5940
--- /dev/null
+++ b/browser/config/mozconfigs/win64/debug
@@ -0,0 +1,29 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+ac_add_options --enable-profiling # needed for --enable-dmd to work on Windows
+ac_add_options --enable-verify-mar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. $topsrcdir/build/win64/mozconfig.vs-latest
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/win64/debug-artifact b/browser/config/mozconfigs/win64/debug-artifact
new file mode 100644
index 000000000..bbba268fb
--- /dev/null
+++ b/browser/config/mozconfigs/win64/debug-artifact
@@ -0,0 +1,13 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+
+. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/build/win64/mozconfig.vs-latest"
+. "$topsrcdir/build/mozconfig.common.override"
+
+ac_add_options --enable-artifact-builds
+ac_add_options --enable-artifact-build-symbols
+
+ac_add_options --enable-debug
diff --git a/browser/config/mozconfigs/win64/l10n-mozconfig b/browser/config/mozconfigs/win64/l10n-mozconfig
new file mode 100644
index 000000000..3b9b4af1c
--- /dev/null
+++ b/browser/config/mozconfigs/win64/l10n-mozconfig
@@ -0,0 +1,20 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --with-windows-version=603
+ac_add_options --enable-official-branding
+
+export MOZILLA_OFFICIAL=1
+
+# Enable Telemetry
+export MOZ_TELEMETRY_REPORTING=1
+
+# Don't autoclobber l10n, as this can lead to missing binaries and broken builds
+# Bug 1283438
+mk_add_options AUTOCLOBBER=
+
+. $topsrcdir/build/win64/mozconfig.vs-latest
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/mozconfigs/win64/nightly b/browser/config/mozconfigs/win64/nightly
new file mode 100644
index 000000000..aee22f1c0
--- /dev/null
+++ b/browser/config/mozconfigs/win64/nightly
@@ -0,0 +1,13 @@
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+. "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
+
+
+ac_add_options --enable-verify-mar
+
+ac_add_options --with-branding=browser/branding/nightly
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/win64/release b/browser/config/mozconfigs/win64/release
new file mode 100644
index 000000000..62e83205f
--- /dev/null
+++ b/browser/config/mozconfigs/win64/release
@@ -0,0 +1,25 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/build/mozconfig.win-common"
+. "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
+. "$topsrcdir/browser/config/mozconfigs/win64/common-opt"
+
+mk_add_options MOZ_PGO=1
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.rust"
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/browser/config/tooltool-manifests/linux32/clang.manifest b/browser/config/tooltool-manifests/linux32/clang.manifest
new file mode 100644
index 000000000..07173985c
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux32/clang.manifest
@@ -0,0 +1,10 @@
+[
+{
+"version": "clang 3.8.0, libgcc 4.8.5",
+"size": 140319580,
+"digest": "34e219d7e8eaffa81710631c34d21355563d06335b3c00851e94c1f42f9098788fded8463dd0f67dd699f77b47a0245dd7aff754943a7a03fb5fd145a808254f",
+"algorithm": "sha512",
+"filename": "clang.tar.xz",
+"unpack": true,
+}
+]
diff --git a/browser/config/tooltool-manifests/linux32/releng.manifest b/browser/config/tooltool-manifests/linux32/releng.manifest
new file mode 100644
index 000000000..fc946eadf
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux32/releng.manifest
@@ -0,0 +1,41 @@
+[
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"size": 11189216,
+"digest": "18bc52b0599b1308b667e282abb45f47597bfc98a5140cfcab8da71dacf89dd76d0dee22a04ce26fe7ad1f04e2d6596991f9e5b01fd2aaaab5542965f596b0e6",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack x86_64+i586",
+"size": 68921028,
+"digest": "9a9ceccc02d4be445ffa64617683419a4f47990b1f2689980ac8db13d6369435ef4af1a3714d77377fb7b3b0ec213856ab7144ff22cbe0881d49aed44d82c0fc",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 3027932,
+"digest": "a5c99eeb12b3b9b49632c259c762e34ec13cf72dadf90a0608b8ab1dc66b36cb114c5b45f71d326e12d31d9e88a41b029e6a728ca64cef392c0a8d211c2fe191",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/linux64/asan.manifest b/browser/config/tooltool-manifests/linux64/asan.manifest
new file mode 100644
index 000000000..9642de0b0
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/asan.manifest
@@ -0,0 +1,26 @@
+[
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"version": "clang 3.8.0, libgcc 4.8.5",
+"size": 139183100,
+"digest": "a056a151d4f25f415b6d905136c3fa8d51d12a5a815c3df37d5663c67d59571736641a4c990884a69f78ea6b5e37a6a7bfff0417dfe38936d842d6fa0776ae54",
+"algorithm": "sha512",
+"filename": "clang.tar.xz",
+"unpack": true
+},
+{
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/linux64/clang.manifest b/browser/config/tooltool-manifests/linux64/clang.manifest
new file mode 100644
index 000000000..97496f2c2
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/clang.manifest
@@ -0,0 +1,25 @@
+[
+{
+"version": "clang 3.8.0, libgcc 4.8.5",
+"size": 140319580,
+"digest": "34e219d7e8eaffa81710631c34d21355563d06335b3c00851e94c1f42f9098788fded8463dd0f67dd699f77b47a0245dd7aff754943a7a03fb5fd145a808254f",
+"algorithm": "sha512",
+"filename": "clang.tar.xz",
+"unpack": true
+},
+{
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/linux64/clang.manifest.centos6 b/browser/config/tooltool-manifests/linux64/clang.manifest.centos6
new file mode 100644
index 000000000..a7173b592
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/clang.manifest.centos6
@@ -0,0 +1,18 @@
+[
+{
+"version": "clang 3.8.0, libgcc 4.8.5",
+"size": 140319580,
+"digest": "34e219d7e8eaffa81710631c34d21355563d06335b3c00851e94c1f42f9098788fded8463dd0f67dd699f77b47a0245dd7aff754943a7a03fb5fd145a808254f",
+"algorithm": "sha512",
+"filename": "clang.tar.xz",
+"unpack": true
+},
+{
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/linux64/hazard.manifest b/browser/config/tooltool-manifests/linux64/hazard.manifest
new file mode 100644
index 000000000..d2a4998b1
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/hazard.manifest
@@ -0,0 +1,41 @@
+[
+{
+"size" : 102421980,
+"version" : "gcc 4.9.3",
+"filename" : "gcc.tar.xz",
+"algorithm" : "sha512",
+"digest" : "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+"unpack" : true
+},
+{
+"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
+"unpack" : true,
+"algorithm" : "sha512",
+"filename" : "sixgill.tar.xz",
+"size" : 2631908,
+"hg_id" : "8cb9c3fb039a+ tip"
+},
+{
+"digest" : "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"unpack" : true,
+"setup" : "setup.sh",
+"algorithm" : "sha512",
+"filename" : "gtk3.tar.xz",
+"size" : 12072532
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 68921028,
+"digest": "9a9ceccc02d4be445ffa64617683419a4f47990b1f2689980ac8db13d6369435ef4af1a3714d77377fb7b3b0ec213856ab7144ff22cbe0881d49aed44d82c0fc",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
+"filename" : "sccache.tar.bz2",
+"algorithm" : "sha512",
+"digest" : "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"unpack" : true,
+"size" : 167175
+}
+]
diff --git a/browser/config/tooltool-manifests/linux64/msan.manifest b/browser/config/tooltool-manifests/linux64/msan.manifest
new file mode 100644
index 000000000..9642de0b0
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/msan.manifest
@@ -0,0 +1,26 @@
+[
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"version": "clang 3.8.0, libgcc 4.8.5",
+"size": 139183100,
+"digest": "a056a151d4f25f415b6d905136c3fa8d51d12a5a815c3df37d5663c67d59571736641a4c990884a69f78ea6b5e37a6a7bfff0417dfe38936d842d6fa0776ae54",
+"algorithm": "sha512",
+"filename": "clang.tar.xz",
+"unpack": true
+},
+{
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/linux64/releng.manifest b/browser/config/tooltool-manifests/linux64/releng.manifest
new file mode 100644
index 000000000..eb7293cee
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/releng.manifest
@@ -0,0 +1,41 @@
+[
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 68921028,
+"digest": "9a9ceccc02d4be445ffa64617683419a4f47990b1f2689980ac8db13d6369435ef4af1a3714d77377fb7b3b0ec213856ab7144ff22cbe0881d49aed44d82c0fc",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 3027932,
+"digest": "a5c99eeb12b3b9b49632c259c762e34ec13cf72dadf90a0608b8ab1dc66b36cb114c5b45f71d326e12d31d9e88a41b029e6a728ca64cef392c0a8d211c2fe191",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/linux64/tsan.manifest b/browser/config/tooltool-manifests/linux64/tsan.manifest
new file mode 100644
index 000000000..9642de0b0
--- /dev/null
+++ b/browser/config/tooltool-manifests/linux64/tsan.manifest
@@ -0,0 +1,26 @@
+[
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"version": "clang 3.8.0, libgcc 4.8.5",
+"size": 139183100,
+"digest": "a056a151d4f25f415b6d905136c3fa8d51d12a5a815c3df37d5663c67d59571736641a4c990884a69f78ea6b5e37a6a7bfff0417dfe38936d842d6fa0776ae54",
+"algorithm": "sha512",
+"filename": "clang.tar.xz",
+"unpack": true
+},
+{
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/macosx64/asan.manifest b/browser/config/tooltool-manifests/macosx64/asan.manifest
new file mode 100644
index 000000000..e00596925
--- /dev/null
+++ b/browser/config/tooltool-manifests/macosx64/asan.manifest
@@ -0,0 +1,10 @@
+[
+{
+"version": "clang 3.8.0",
+"size": 133060926,
+"digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
+"algorithm": "sha512",
+"filename": "clang.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/macosx64/clang.manifest b/browser/config/tooltool-manifests/macosx64/clang.manifest
new file mode 100644
index 000000000..a768012c4
--- /dev/null
+++ b/browser/config/tooltool-manifests/macosx64/clang.manifest
@@ -0,0 +1,25 @@
+[
+{
+"version": "clang 3.8.0",
+"size": 133060926,
+"digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
+"algorithm": "sha512",
+"filename": "clang.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"version": "cctools port from commit hash db1f8d906cb28, ld only",
+"size": 634496,
+"digest": "037f31fcf29e7bb7fada0d2bdd5e95c7d4cb2692f2a5c98ed6f6a7561b9d81622d015f0d12b291d3667719655f1369e8ce8a0a4a4773aa0ee4753e04a8821173",
+"algorithm": "sha512",
+"filename": "cctools.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
new file mode 100644
index 000000000..fc356233d
--- /dev/null
+++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
@@ -0,0 +1,65 @@
+[
+{
+"version": "clang 3.8.0, libgcc 4.8.5",
+"size": 140319580,
+"digest": "34e219d7e8eaffa81710631c34d21355563d06335b3c00851e94c1f42f9098788fded8463dd0f67dd699f77b47a0245dd7aff754943a7a03fb5fd145a808254f",
+"algorithm": "sha512",
+"filename": "clang.tar.xz",
+"unpack": true
+},
+{
+"size": 3008804,
+"visibility": "public",
+"digest": "ba6937f14f3d8b26dcb2d39490dee6b0a8afb60f672f5debb71d7b62c1ec52103201b4b1a3d258f945567de531384b36ddb2ce4aa73dc63d72305b11c146847c",
+"algorithm": "sha512",
+"unpack": true,
+"filename": "cctools.tar.gz"
+},
+{
+"size": 35215976,
+"visibility": "internal",
+"digest": "8be736545ddab25ebded188458ce974d5c9a7e29f3c50d2ebfbcb878f6aff853dd2ff5a3528bdefc64396a10101a1b50fd2fe52000140df33643cebe1ea759da",
+"algorithm": "sha512",
+"unpack": true,
+"filename": "MacOSX10.7.sdk.tar.bz2"
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 3027932,
+"digest": "a5c99eeb12b3b9b49632c259c762e34ec13cf72dadf90a0608b8ab1dc66b36cb114c5b45f71d326e12d31d9e88a41b029e6a728ca64cef392c0a8d211c2fe191",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"unpack": true,
+"filename": "sccache.tar.bz2"
+},
+{
+"size": 57060,
+"visibility": "public",
+"digest": "9649ca595f4cf088d118da26201f92cc94cda7af49c7c48112ee31cd13c83b2935b3e145de9dd78060cff2480b4c2e7ff5fb24235876956fed13c87852071998",
+"algorithm": "sha512",
+"unpack": true,
+"filename": "dmg.tar.xz"
+},
+{
+"size": 188880,
+"visibility": "public",
+"digest": "1ffddd43efb03aed897ee42035d9d8d758a8d66ab6c867599ef755e1a586768fc22011ce03698af61454920b00fe8bed08c9a681e7bd324d7f8f78c026c83943",
+"algorithm": "sha512",
+"unpack": true,
+"filename": "genisoimage.tar.xz"
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 119642844,
+"digest": "b219c07d78819b9ea930024205ac905f89b958948fbfb3146864724bf4994401911fdb1636d6f32e46de1f587f4ee9ae7975a886bfacc5f0bde8ea5955b4e42a",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/macosx64/releng.manifest b/browser/config/tooltool-manifests/macosx64/releng.manifest
new file mode 100644
index 000000000..98cccce71
--- /dev/null
+++ b/browser/config/tooltool-manifests/macosx64/releng.manifest
@@ -0,0 +1,41 @@
+[
+{
+"version": "clang 3.8.0",
+"size": 133060926,
+"digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
+"algorithm": "sha512",
+"filename": "clang.tar.bz2",
+"unpack": true
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 104593379,
+"digest": "9cbc4a6d4d647dd79629e97c0e7b177443d30e669ccd761ab520728d8c2b7e1cc4ab38ec444c1957649338c4088861db3bfe4f840ec3fedcc01f9f1a74da200a",
+"algorithm": "sha512",
+"filename": "rustc.tar.bz2",
+"unpack": true
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 2351877,
+"digest": "76283ceda49015f66b03d18b3c28f70ed4baa09accdfc17b2ec935c7af3e9471d140256a33737563baa2545c34e556a125ade6d4858b9226b8c770dbd7c0756f",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
+"version": "cctools port from commit hash db1f8d906cb28, ld only",
+"size": 634496,
+"digest": "037f31fcf29e7bb7fada0d2bdd5e95c7d4cb2692f2a5c98ed6f6a7561b9d81622d015f0d12b291d3667719655f1369e8ce8a0a4a4773aa0ee4753e04a8821173",
+"algorithm": "sha512",
+"filename": "cctools.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/win32/build-clang-cl.manifest b/browser/config/tooltool-manifests/win32/build-clang-cl.manifest
new file mode 100644
index 000000000..2ead16fd9
--- /dev/null
+++ b/browser/config/tooltool-manifests/win32/build-clang-cl.manifest
@@ -0,0 +1,63 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 58997576,
+"digest": "be97bb7f60fea39b9b0411b7ce247036a9373b01ed8cc60f30ed3c6254473ab7ef1881f222f10845253e0608c6f3d21add0871d0485d9de413297906d5c5409c",
+"algorithm": "sha512",
+"filename": "rustc.tar.bz2",
+"unpack": true
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 2214397,
+"digest": "4f378fc4178d72d9e0434fca3df342d9dd7619c7c524ec6aedeee78a19583f2a675dfc54224be87030d72a36cef77f997e5275fe1cebac065c38949fa464d842",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
+"size": 326656969,
+"digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
+"algorithm": "sha512",
+"filename": "vs2015u3.zip",
+"unpack": true
+},
+{
+"version": "SVN 1.9.4, repacked from SlikSvn (https://sliksvn.com/download/)",
+"size": 3934520,
+"digest": "d3b8f74936857ecbf542e403ed6835938a31d65302985729cbfa7191bf2cf94138565cefcc2f31517098013fbfc51868348863a55b588250902f9dec214dbc42",
+"algorithm": "sha512",
+"filename": "svn194.zip",
+"unpack": true
+},
+{
+"version": "CMake 3.6.2 repack",
+"size": 19832889,
+"digest": "39b0508b60f655969d1b54c76753b14b5b2e92dab58613c835aed798a6aeb9077a7df78aebc011c2c753661fdc15007d3353c0c8773a53148380e2ec02afb629",
+"algorithm": "sha512",
+"filename": "cmake362.zip",
+"unpack": true
+},
+{
+"version": "Ninja 1.7.1",
+"size": 184821,
+"digest": "e4f9a1ae624a2630e75264ba37d396d9c7407d6e6aea3763056210ba6e1387908bd31cf4037a6a3661a418e86c4d2761e0c333e6a3bd0d66549d2b0d72d3f43b",
+"algorithm": "sha512",
+"filename": "ninja171.zip",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/win32/clang.manifest b/browser/config/tooltool-manifests/win32/clang.manifest
new file mode 100644
index 000000000..fb4309466
--- /dev/null
+++ b/browser/config/tooltool-manifests/win32/clang.manifest
@@ -0,0 +1,31 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 58997576,
+"digest": "be97bb7f60fea39b9b0411b7ce247036a9373b01ed8cc60f30ed3c6254473ab7ef1881f222f10845253e0608c6f3d21add0871d0485d9de413297906d5c5409c",
+"algorithm": "sha512",
+"filename": "rustc.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"version": "clang 3.9.0/r262971",
+"size": 169861843,
+"digest": "8caa5a89aea981b618233d39f01bb934b79b85ee8167104bfa4f07936145df5e8ca5e8e007123d75ccc12d2baa926ffc827b40bf793fa9d4bc2670fa519b0ec0",
+"algorithm": "sha512",
+"filename": "clang32.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/win32/l10n.manifest b/browser/config/tooltool-manifests/win32/l10n.manifest
new file mode 100644
index 000000000..2e802b78d
--- /dev/null
+++ b/browser/config/tooltool-manifests/win32/l10n.manifest
@@ -0,0 +1,8 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+}
+]
diff --git a/browser/config/tooltool-manifests/win32/releng.manifest b/browser/config/tooltool-manifests/win32/releng.manifest
new file mode 100644
index 000000000..9124644f1
--- /dev/null
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -0,0 +1,39 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 58997576,
+"digest": "be97bb7f60fea39b9b0411b7ce247036a9373b01ed8cc60f30ed3c6254473ab7ef1881f222f10845253e0608c6f3d21add0871d0485d9de413297906d5c5409c",
+"algorithm": "sha512",
+"filename": "rustc.tar.bz2",
+"unpack": true
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 2214397,
+"digest": "4f378fc4178d72d9e0434fca3df342d9dd7619c7c524ec6aedeee78a19583f2a675dfc54224be87030d72a36cef77f997e5275fe1cebac065c38949fa464d842",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
+"size": 326656969,
+"digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
+"algorithm": "sha512",
+"filename": "vs2015u3.zip",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/win64/clang.manifest b/browser/config/tooltool-manifests/win64/clang.manifest
new file mode 100644
index 000000000..a91d2dbff
--- /dev/null
+++ b/browser/config/tooltool-manifests/win64/clang.manifest
@@ -0,0 +1,32 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 63966131,
+"digest": "a431492ca5ae3454e7d5de3962ff0b40d96f69a9046428a1fe310397a5dda9ba2f6b697958e8386b119d56b700563ffe1b58c63f84b527b2df320893306854cf",
+"algorithm": "sha512",
+"visibility": "public",
+"filename": "rustc.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"version": "clang 3.9.0/r262971",
+"size": 173306045,
+"digest": "806413640a964dad44c0c6055d2a89a1075730fb6f659ff37341378a14a7dc032e672941765225608b71f6b385ac721ed36bfa04eb38c211b07e2776cb72a0ee",
+"algorithm": "sha512",
+"filename": "clang64.tar.bz2",
+"unpack": true
+}
+]
diff --git a/browser/config/tooltool-manifests/win64/l10n.manifest b/browser/config/tooltool-manifests/win64/l10n.manifest
new file mode 100644
index 000000000..2e802b78d
--- /dev/null
+++ b/browser/config/tooltool-manifests/win64/l10n.manifest
@@ -0,0 +1,8 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+}
+]
diff --git a/browser/config/tooltool-manifests/win64/releng.manifest b/browser/config/tooltool-manifests/win64/releng.manifest
new file mode 100644
index 000000000..207d8e5d0
--- /dev/null
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -0,0 +1,40 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 63966131,
+"digest": "a431492ca5ae3454e7d5de3962ff0b40d96f69a9046428a1fe310397a5dda9ba2f6b697958e8386b119d56b700563ffe1b58c63f84b527b2df320893306854cf",
+"algorithm": "sha512",
+"visibility": "public",
+"filename": "rustc.tar.bz2",
+"unpack": true
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 2466539,
+"digest": "78b129bd5c933d77c1f09a24c57a652c7cf228fc986000c162f892a46a53fc0f56b8fe1ac924c7e5aaefc4728fd07b679fbf149feb4ed5f2f30c4fd2f776ab7e",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"version": "Visual Studio 2015 Update 3 14.0.25425.01 / SDK 10.0.14393.0",
+"size": 326656969,
+"digest": "babc414ffc0457d27f5a1ed24a8e4873afbe2f1c1a4075469a27c005e1babc3b2a788f643f825efedff95b79686664c67ec4340ed535487168a3482e68559bc7",
+"algorithm": "sha512",
+"filename": "vs2015u3.zip",
+"unpack": true
+}
+]
diff --git a/browser/config/version.txt b/browser/config/version.txt
new file mode 100644
index 000000000..4e9247c69
--- /dev/null
+++ b/browser/config/version.txt
@@ -0,0 +1 @@
+52.6.0
diff --git a/browser/config/version_display.txt b/browser/config/version_display.txt
new file mode 100644
index 000000000..4e9247c69
--- /dev/null
+++ b/browser/config/version_display.txt
@@ -0,0 +1 @@
+52.6.0
diff --git a/browser/confvars.sh b/browser/confvars.sh
new file mode 100755
index 000000000..dabd06ea7
--- /dev/null
+++ b/browser/confvars.sh
@@ -0,0 +1,64 @@
+#! /bin/sh
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOZ_APP_BASENAME=Firefox
+MOZ_APP_VENDOR=Mozilla
+MOZ_UPDATER=1
+MOZ_PHOENIX=1
+
+if test "$OS_ARCH" = "WINNT" -o \
+ "$OS_ARCH" = "Linux"; then
+ MOZ_BUNDLED_FONTS=1
+fi
+
+if test "$OS_ARCH" = "WINNT"; then
+ MOZ_MAINTENANCE_SERVICE=1
+ if ! test "$HAVE_64BIT_BUILD"; then
+ if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \
+ "$MOZ_UPDATE_CHANNEL" = "aurora" -o \
+ "$MOZ_UPDATE_CHANNEL" = "beta" -o \
+ "$MOZ_UPDATE_CHANNEL" = "beta-dev" -o \
+ "$MOZ_UPDATE_CHANNEL" = "release" -o \
+ "$MOZ_UPDATE_CHANNEL" = "release-dev"; then
+ if ! test "$MOZ_DEBUG"; then
+ MOZ_STUB_INSTALLER=1
+ fi
+ fi
+ fi
+fi
+
+# Enable building ./signmar and running libmar signature tests
+MOZ_ENABLE_SIGNMAR=1
+
+MOZ_APP_VERSION=$FIREFOX_VERSION
+MOZ_APP_VERSION_DISPLAY=$FIREFOX_VERSION_DISPLAY
+MOZ_EXTENSIONS_DEFAULT=" gio"
+# MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
+# MOZ_BRANDING_DIRECTORY is the default branding directory used when none is
+# specified. It should never point to the "official" branding directory.
+# For mozilla-beta, mozilla-release, or mozilla-central repositories, use
+# "unofficial" branding.
+# For the mozilla-aurora repository, use "aurora".
+MOZ_BRANDING_DIRECTORY=browser/branding/unofficial
+MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official
+MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+# This should usually be the same as the value MAR_CHANNEL_ID.
+# If more than one ID is needed, then you should use a comma separated list
+# of values.
+ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-esr
+# The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
+MAR_CHANNEL_ID=firefox-mozilla-esr
+MOZ_PROFILE_MIGRATOR=1
+MOZ_APP_STATIC_INI=1
+MOZ_WEBGL_CONFORMANT=1
+MOZ_JSDOWNLOADS=1
+MOZ_RUST_MP4PARSE=1
+MOZ_RUST_URLPARSE=1
+
+# Enable checking that add-ons are signed by the trusted root
+MOZ_ADDON_SIGNING=1
+
+# Include the DevTools client, not just the server (which is the default)
+MOZ_DEVTOOLS=all
diff --git a/browser/defs.mk b/browser/defs.mk
new file mode 100644
index 000000000..fd88c6585
--- /dev/null
+++ b/browser/defs.mk
@@ -0,0 +1 @@
+XPI_ROOT_APPID=$(MOZ_APP_ID)
diff --git a/browser/docs/BrowserUsageTelemetry.rst b/browser/docs/BrowserUsageTelemetry.rst
new file mode 100644
index 000000000..c49cf7a3a
--- /dev/null
+++ b/browser/docs/BrowserUsageTelemetry.rst
@@ -0,0 +1,28 @@
+.. _browserusagetelemetry:
+
+=======================
+Browser Usage Telemetry
+=======================
+
+The `BrowserUsageTelemetry.jsm <https://dxr.mozilla.org/mozilla-central/source/browser/modules/BrowserUsageTelemetry.jsm>`_ module is the main module for measurements regarding the browser usage (e.g. tab and window counts, search counts, ...).
+
+The measurements recording begins right after the ``SessionStore`` has finished restoring the session (i.e. restoring tabs/windows after Firefox starts).
+
+Search telemetry
+================
+This module exposes the ``recordSearch`` method, which serves as the main entry point for recording search related Telemetry. It records only the search *counts* per engine and the origin of the search, but nothing pertaining the search contents themselves.
+
+As the transition to the ``BrowserUsageTelemetry`` happens, the ``recordSearch`` calls are dispatched through `BrowserSearch.recordSearchInTelemetry <https://dxr.mozilla.org/mozilla-central/rev/3e73fd638e687a4d7f46613586e5156b8e2af846/browser/base/content/browser.js#3752>`_, that is called by all the search related UI components (urlbar, searchbar, context menu and about\:\* pages).
+
+A list of the components recording search Telemetry can be found using the following `DXR search <https://dxr.mozilla.org/mozilla-central/search?q=recordSearchInTelemetry>`_.
+
+Measured interactions
+=====================
+The usage telemetry module currently measures these interactions with the browser:
+
+- *tab and window engagement*: counts the number of non-private tabs and windows opened in a subsession, after the session is restored (see e.g. ``browser.engagement.max_concurrent_tab_count``);
+- *URI loads*: counts the number of page loads (doesn't track and send the addresses, just the counts) directly triggered by the users (see ``browser.engagement.total_uri_count``);
+- *navigation events*: at this time, this only counts the number of time a page load is triggered by a particular UI interaction (e.g. by searching through the URL bar, see ``browser.engagement.navigation.urlbar``).
+
+
+Please see `Scalars.yaml <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Scalars.yaml>`_ for the full list of tracked interactions.
diff --git a/browser/docs/DirectoryLinksProvider.rst b/browser/docs/DirectoryLinksProvider.rst
new file mode 100644
index 000000000..c6f55c3d4
--- /dev/null
+++ b/browser/docs/DirectoryLinksProvider.rst
@@ -0,0 +1,278 @@
+=============================================
+Directory Links Architecture and Data Formats
+=============================================
+
+Directory links are enhancements to the new tab experience that combine content
+Firefox already knows about from user browsing with external content. There are
+3 kinds of links:
+
+- directory links fill in additional tiles on the new tab page if there would
+ have been empty tiles because the user has a clean profile or cleared history
+- suggested links are shown if certain triggering criteria matches the user's
+ browsing behavior, i.e., if the user has a top site that matches one of
+ several possible sites. E.g., only show a sports suggestion if the user has a
+ sport site as a top site
+- enhanced links replace a matching user's visible history tile from the same
+ site but only the visual aspects: title, image, and rollover image
+
+To power the above features, DirectoryLinksProvider module downloads, at most
+once per 24 hours, the directory source links as JSON with enough data for
+Firefox to determine what should be shown or not. This module also handles
+reporting back data about the tiles via asynchronous pings that don't return
+data from the server.
+
+For the directory source and ping endpoints, the default preference values point
+to Mozilla key-pinned servers with encryption. No cookies are set by the servers
+and Firefox enforces this by making anonymous requests.
+
+- default directory source endpoint:
+ https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%
+- default directory ping endpoint: https://tiles.services.mozilla.com/v3/links/
+
+
+Preferences
+===========
+
+There are two main preferences that control downloading links and reporting
+metrics.
+
+``browser.newtabpage.directory.source``
+---------------------------------------
+
+This endpoint tells Firefox where to download directory source file as a GET
+request. It should return JSON of the appropriate format containing the relevant
+links data. The value can be a data URI, e.g., an empty JSON object effectively
+turns off remote downloading: ``data:text/plain,{}``
+
+The preference value will have %LOCALE% and %CHANNEL% replaced by the
+appropriate values for the build of Firefox, e.g.,
+
+- directory source endpoint:
+ https://tiles.services.mozilla.com/v3/links/fetch/en-US/release
+
+``browser.newtabpage.directory.ping``
+-------------------------------------
+
+This endpoint tells Firefox where to report Tiles metrics as a POST request. The
+data is sent as a JSON blob. Setting it to empty effectively turns off reporting
+of Tiles data.
+
+A path segment will be appended to the endpoint of "view" or "click" depending
+on the type of ping, e.g.,
+
+- ``view`` ping endpoint: https://tiles.services.mozilla.com/v3/links/view
+- ``click`` ping endpoint: https://tiles.services.mozilla.com/v3/links/click
+
+
+Data Flow
+=========
+
+When Firefox starts, it checks for a cached directory source file. If one
+exists, it checks for its timestamp to determine if a new file should be
+downloaded.
+
+If a directory source file needs to be downloaded, a GET request is made then
+cacheed and unpacked the JSON into the different types of links. Various checks
+filter out invalid links, e.g., those with http-hosted images or those that
+don't fit the allowed suggestions.
+
+When a new tab page is built, DirectoryLinksProvider module provides additional
+link data that is combined with history link data to determine which links can
+be displayed or not.
+
+When a new tab page is shown, a ``view`` ping is sent with relevant tiles data.
+Similarly, when the user clicks on various parts of tiles (to load the page,
+pin, block, etc.), a ``click`` ping is sent with similar data. Both of these can
+trigger downloading of fresh directory source links if 24 hours have elapsed
+since last download.
+
+Users can turn off the ping with in-new-tab-page controls.
+
+As the new tab page is rendered, any images for tiles are downloaded if not
+already cached. The default servers hosting the images are Mozilla CDN that
+don't use cookies: https://tiles.cdn.mozilla.net/ and Firefox enforces that the
+images come from mozilla.net or data URIs when using the default directory
+source.
+
+
+Source JSON Format
+==================
+
+Firefox expects links data in a JSON object with top level keys each providing
+an array of tile objects. The keys correspond to the different types of links:
+``directory``, ``suggested``, and ``enhanced``.
+
+Example
+-------
+
+Below is an example directory source file::
+
+ {
+ "directory": [
+ {
+ "bgColor": "",
+ "directoryId": 498,
+ "enhancedImageURI": "https://tiles.cdn.mozilla.net/images/d11ba0b3095bb19d8092cd29be9cbb9e197671ea.28088.png",
+ "imageURI": "https://tiles.cdn.mozilla.net/images/1332a68badf11e3f7f69bf7364e79c0a7e2753bc.5316.png",
+ "title": "Mozilla Community",
+ "type": "affiliate",
+ "url": "http://contribute.mozilla.org/"
+ }
+ ],
+ "enhanced": [
+ {
+ "bgColor": "",
+ "directoryId": 776,
+ "enhancedImageURI": "https://tiles.cdn.mozilla.net/images/44a14fc405cebc299ead86514dff0e3735c8cf65.10814.png",
+ "imageURI": "https://tiles.cdn.mozilla.net/images/20e24aa2219ec7542cc8cf0fd79f0c81e16ebeac.11859.png",
+ "title": "TurboTax",
+ "type": "sponsored",
+ "url": "https://turbotax.intuit.com/"
+ }
+ ],
+ "suggested": [
+ {
+ "adgroup_name": "open-source browser",
+ "bgColor": "#cae1f4",
+ "check_inadjacency": true,
+ "directoryId": 702,
+ "explanation": "Suggested for %1$S enthusiasts who visit sites like %2$S",
+ "frecent_sites": [
+ "addons.mozilla.org",
+ "air.mozilla.org",
+ "blog.mozilla.org",
+ "bugzilla.mozilla.org",
+ "developer.mozilla.org",
+ "etherpad.mozilla.org",
+ "hacks.mozilla.org",
+ "hg.mozilla.org",
+ "mozilla.org",
+ "planet.mozilla.org",
+ "quality.mozilla.org",
+ "support.mozilla.org",
+ "treeherder.mozilla.org",
+ "wiki.mozilla.org"
+ ],
+ "frequency_caps": {"daily": 3, "total": 10},
+ "imageURI": "https://tiles.cdn.mozilla.net/images/9ee2b265678f2775de2e4bf680df600b502e6038.3875.png",
+ "time_limits": {"start": "2014-01-01T00:00:00.000Z", "end": "2014-02-01T00:00:00.000Z"},
+ "title": "Thanks for testing!",
+ "type": "affiliate",
+ "url": "https://www.mozilla.com/firefox/tiles"
+ }
+ ]
+ }
+
+Link Object
+-----------
+
+Each link object has various values that Firefox uses to display a tile:
+
+- ``url`` - string url for the page to be loaded when the tile is clicked. Only
+ https and http URLs are allowed.
+- ``title`` - string that appears below the tile.
+- ``type`` - string relationship of the link to Mozilla. Expected values:
+ affiliate, organic, sponsored.
+- ``imageURI`` - string url for the tile image to show. Only https and data URIs
+ are allowed.
+- ``enhancedImageURI`` - string url for the image to be shown before the user
+ hovers. Only https and data URIs are allowed.
+- ``bgColor`` - string css color for additional fill background color.
+- ``directoryId`` - id of the tile to be used during ping reporting
+
+Suggested Link Object Extras
+----------------------------
+
+A suggested link has additional values:
+
+- ``adgroup_name`` - string to override the hardcoded display name of the
+ triggering set of sites in Firefox.
+- ``check_inadjacency`` - boolean if true prevents the suggested link from being
+ shown if the new tab page is showing a site from an inadjacency list.
+- ``explanation`` - string to override the default explanation that appears
+ below a Suggested Tile. %1$S is replaced by the triggering adgroup name and
+ %2$S is replaced by the triggering site.
+- ``frecent_sites`` - array of strings of the sites that can trigger showing a
+ Suggested Tile if the user has the site in one of the top 100 most-frecent
+ pages.
+- ``frequency_caps`` - an object consisting of daily and total frequency caps
+ that limit the number of times a Suggested Tile can be shown in the new tab
+ per day and overall.
+- ``time_limits`` - an object consisting of start and end timestamps specifying
+ when a Suggested Tile may start and has to stop showing in the newtab.
+ The timestamp is expected in ISO_8601 format: '2014-01-10T20:00:00.000Z'
+
+The inadjacency list is packaged with Firefox as base64-encoded 1-way-hashed
+sites that tend to have adult, gambling, alcohol, drug, and similar content.
+Its location: chrome://browser/content/newtab/newTab.inadjacent.json
+
+The preapproved arrays follow a policy for determining what topic grouping is
+allowed as well as the composition of a grouping. The topics are broad
+uncontroversial categories, e.g., Mobile Phone, News, Technology, Video Game,
+Web Development. There are at least 5 sites within a grouping, and as many
+popular sites relevant to the topic are included to avoid having one site be
+clearly dominant. These requirements provide some deniability of which site
+actually triggered a suggestion during ping reporting, so it's more difficult to
+determine if a user has gone to a specific site.
+
+
+Ping JSON Format
+================
+
+Firefox reports back an action and the state of tiles on the new tab page based
+on the user opening a new tab or clicking a tile. The top level keys of the
+ping:
+
+- ``locale`` - string locale of the Firefox build
+- ``tiles`` - array of tiles ping objects
+
+An additional key at the top level indicates which action triggered the ping.
+The value associated to the action key is the 0-based index into the tiles array
+of which tile triggered the action. Valid actions: block, click, pin, sponsored,
+sponsored_link, unpin, view. E.g., if the second tile is being clicked, the ping
+will have ``"click": 1``
+
+Example
+-------
+
+Below is an example ``click`` ping with 3 tiles: a pinned suggested tile
+followed by a history tile and a directory tile. The first tile is being
+blocked::
+
+ {
+ "locale": "en-US",
+ "tiles": [
+ {
+ "id": 702,
+ "pin": 1,
+ "past_impressions": {"total": 5, "daily": 1},
+ },
+ {},
+ {
+ "id": 498,
+ }
+ ],
+ "block": 0
+ }
+
+Tiles Ping Object
+-----------------
+
+Each tile of the new tab page is reported back as part of the ping with some or
+none of the following optional values:
+
+- ``id`` - id that was provided as part of the downloaded link object (for all
+ types of links: directory, suggested, enhanced); not present if the tile was
+ created from user behavior, e.g., visiting pages
+- ``past_impressions`` - number of impressions (new tab "views") a suggested
+ tile was shown before it was clicked, pinned or blocked. Where the "total"
+ counter is the overall number of impressions accumulated prior to a click action,
+ and "daily" counter is the number impressions occurred on same calendar day of
+ a click. This infomration is submitted once per a suggested tile upon click,
+ pin or block
+- ``pinned`` - 1 if the tile is pinned; not present otherwise
+- ``pos`` - integer position if the tile is not in the natural order, e.g., a
+ pinned tile after an empty slot; not present otherwise
+- ``score`` - integer truncated score based on the tile's frecency; not present
+ if 0
+- ``url`` - empty string if it's an enhanced tile; not present otherwise
diff --git a/browser/docs/UITelemetry.rst b/browser/docs/UITelemetry.rst
new file mode 100644
index 000000000..0b3302f8f
--- /dev/null
+++ b/browser/docs/UITelemetry.rst
@@ -0,0 +1,143 @@
+.. _uitelemetry:
+
+=======================
+UITelemetry data format
+=======================
+
+UI Telemetry sends its data as a JSON blob. This document describes the different parts
+of the JSON blob.
+
+``toolbars``
+============
+
+This tracks the state of the user's UI customizations. It has the following properties:
+
+- ``sizemode`` - string indicating whether the window is in maximized, normal (restored) or
+ fullscreen mode;
+- ``bookmarksBarEnabled`` - boolean indicating whether the bookmarks bar is visible;
+- ``menuBarEnabled`` - boolean indicating whether the menu bar is visible (always false on OS X);
+- ``titleBarEnabled`` - boolean indicating whether the (real) titlebar is visible (rather than
+ having tabs in the titlebar);
+- ``defaultKept`` - list of strings identifying toolbar buttons and items that are still in their
+ default position. Only the IDs of builtin widgets are sent (ie not add-on widgets);
+- ``defaultMoved`` - list of strings identifying toolbar buttons and items that are no longer in
+ their default position, but have not been removed to the palette. Only the IDs of builtin widgets
+ are sent (ie not add-on widgets);
+- ``nondefaultAdded`` - list of strings identifying toolbar buttons and items that have been added
+ from the palette. Only the IDs of builtin widgets are sent (ie not add-on widgets);
+- ``defaultRemoved`` - list of strings identifying toolbar buttons and items that are in the
+ palette that are elsewhere by default. Only the IDs of builtin widgets are sent
+ (ie not add-on widgets);
+- ``addonToolbars`` - the number of non-default toolbars that are customizable. 1 by default
+ because it counts the add-on bar shim;
+- ``visibleTabs`` - array of the number of visible tabs per window;
+- ``hiddenTabs`` - array of the number of hidden tabs per window (ie tabs in panorama groups which
+ are not the current group);
+- ``countableEvents`` - please refer to the next section.
+- ``durations`` - an object mapping descriptions to duration records, which records the amount of
+ time a user spent doing something. Currently only has one property:
+
+ - ``customization`` - how long a user spent customizing the browser. This is an array of
+ objects, where each object has a ``duration`` property indicating the time in milliseconds,
+ and a ``bucket`` property indicating a bucket in which the duration info falls.
+
+
+.. _UITelemetry_countableEvents:
+
+``countableEvents``
+===================
+
+Countable events are stored under the ``toolbars`` section. They count the number of times certain
+events happen. No timing or other correlating information is stored - purely the number of times
+things happen.
+
+``countableEvents`` contains a list of buckets as its properties. A bucket represents the state the browser was in when these events occurred, such as currently running an interactive tour. There are 3 types of buckets:
+
+- ``__DEFAULT__`` - No bucket, for times when the browser is not in any special state.
+- ``bucket_<NAME>`` - Normal buckets, for when the browser is in a special state. The ``<NAME>`` in the bucket ID is the name associated with the bucket and may be further broken down into parts by the ``|`` character.
+- ``bucket_<NAME>|<INTERVAL>`` - Expiring buckets, which are similar to a countdown timer. The ``<INTERVAL>`` in the bucket ID describes the time interval the recorded event happened in. The intervals are ``1m`` (one minute), ``3m`` (three minutes), ``10m`` (ten minutes), and ``1h`` (one hour). After one hour, the ``__DEFAULT__`` bucket is automatically used again.
+
+Each bucket is an object with the following properties:
+
+- ``click-builtin-item`` is an object tracking clicks on builtin customizable toolbar items, keyed
+ off the item IDs, with an object for each item with keys ``left``, ``middle`` and ``right`` each
+ storing a number indicating how often the respective type of click has happened.
+- ``click-menu-button`` is the same, except the item ID is always 'button'.
+- ``click-bookmarks-bar`` is the same, with the item IDs being replaced by either ``container`` for
+ clicks on bookmark or livemark folders, and ``item`` for individual bookmarks.
+- ``click-menubar`` is similar, with the item IDs being replaced by one of ``menu``, ``menuitem``
+ or ``other``, depending on the kind of item clicked. Note that this is not tracked on OS X, where
+ we can't listen for these events because of the global menubar.
+- ``click-bookmarks-menu-button`` is also similar, with the item IDs being replaced by:
+
+ - ``menu`` for clicks on the 'menu' part of the item;
+ - ``add`` for clicks that add a bookmark;
+ - ``edit`` for clicks that open the panel to edit an existing bookmark;
+ - ``in-panel`` for clicks when the button is in the menu panel, and clicking it does none of the
+ above;
+- ``customize`` tracks different types of customization events without the ``left``, ``middle`` and
+ ``right`` distinctions. The different events are the following, with each storing a count of the
+ number of times they occurred:
+
+ - ``start`` counts the number of times the user starts customizing;
+ - ``add`` counts the number of times an item is added somewhere from the palette;
+ - ``move`` counts the number of times an item is moved somewhere else (but not to the palette);
+ - ``remove`` counts the number of times an item is removed to the palette;
+ - ``reset`` counts the number of times the 'restore defaults' button is used;
+- ``search`` is an object tracking searches of various types, keyed off the search
+ location, storing a number indicating how often the respective type of search
+ has happened.
+
+ - There are also two special keys that mean slightly different things.
+
+ - ``urlbar-keyword`` records searches that would have been an invalid-protocol
+ error, but are now keyword searches. They are also counted in the ``urlbar``
+ keyword (along with all the other urlbar searches).
+ - ``selection`` searches records selections of search suggestions. They include
+ the source, the index of the selection, and the kind of selection (mouse or
+ enter key). Selection searches are also counted in their sources.
+
+
+
+``UITour``
+==========
+
+The UITour API provides ways for pages on trusted domains to safely interact with the browser UI and request it to perform actions such as opening menus and showing highlights over the browser chrome - for the purposes of interactive tours. We track some usage of this API via the ``UITour`` object in the UI Telemetry output.
+
+Each page is able to register itself with an identifier, a ``Page ID``. A list of Page IDs that have been seen over the last 8 weeks is available via ``seenPageIDs``.
+
+Page IDs are also used to identify buckets for :ref:`UITelemetry_countableEvents`, in the following circumstances:
+
+- The current tab is a tour page. This will be a normal bucket with the name ``UITour|<PAGEID>``, where ``<PAGEID>`` is the page's registered ID. This will result in bucket IDs such as ``bucket_UITour|australis-tour``.
+- A tour tab is open but another tab is active. This will be an expiring bucket with the name ``UITour|<PAGEID>|inactive``. This will result in bucket IDs such as ``bucket_UITour|australis-tour|inactive|1m``.
+- A tour tab has recently been open but has been closed. This will be an expiring bucket with the name ``UITour|<PAGEID>|closed``. This will result in bucket IDs such as ``bucket_UITour|australis-tour|closed|10m``.
+
+
+
+``contextmenu``
+===============
+
+We track context menu interactions to figure out which ones are most often used and/or how
+effective they are. In the ``contextmenu`` object, we first store things per-bucket. Next, we
+divide the following different context menu situations:
+
+- ``selection`` if there is content on the page that's selected on which the user clicks;
+- ``link`` if the user opened the context menu for a link
+- ``image-link`` if the user opened the context menu on an image or canvas that's a link;
+- ``image`` if the user opened the context menu on an image (that isn't a link);
+- ``canvas`` if the user opened the context menu on a canvas (that isn't a link);
+- ``media`` if the user opened the context menu on an HTML video or audio element;
+- ``input`` if the user opened the context menu on a text input element;
+- ``social`` if the user opened the context menu inside a social frame;
+- ``other`` for all other openings of the content menu;
+
+Each of these objects (if they exist) then gets a "withcustom" and/or a "withoutcustom" property
+for context menus opened with custom page-created items and without them, and each of those
+properties holds an object with IDs corresponding to a count of how often an item with that ID was
+activated in the context menu. Only builtin context menu items are tracked, and besides those items
+there are four special items which get counts:
+
+- ``close-without-interaction`` is incremented when the user closes the context menu without interacting with it;
+- ``custom-page-item`` is incremented when the user clicks an item that was created by the page;
+- ``unknown`` is incremented when an item without an ID was clicked;
+- ``other-item`` is incremented when an add-on-provided menuitem is clicked.
diff --git a/browser/docs/index.rst b/browser/docs/index.rst
new file mode 100644
index 000000000..bfdd78e04
--- /dev/null
+++ b/browser/docs/index.rst
@@ -0,0 +1,12 @@
+=======
+Firefox
+=======
+
+This is the nascent documentation of the Firefox front-end code.
+
+.. toctree::
+ :maxdepth: 1
+
+ DirectoryLinksProvider
+ UITelemetry
+ BrowserUsageTelemetry
diff --git a/browser/experiments/.eslintrc.js b/browser/experiments/.eslintrc.js
new file mode 100644
index 000000000..1f6b11d67
--- /dev/null
+++ b/browser/experiments/.eslintrc.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = {
+ "rules": {
+ "no-unused-vars": ["error", {
+ "vars": "all",
+ "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
+ "args": "none"
+ }]
+ }
+};
diff --git a/browser/experiments/Experiments.jsm b/browser/experiments/Experiments.jsm
new file mode 100644
index 000000000..e9a63f19f
--- /dev/null
+++ b/browser/experiments/Experiments.jsm
@@ -0,0 +1,2354 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = [
+ "Experiments",
+];
+
+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/Task.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
+ "resource://gre/modules/TelemetryEnvironment.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
+ "resource://gre/modules/TelemetryLog.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryUtils",
+ "resource://gre/modules/TelemetryUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
+ "resource://services-common/utils.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
+ "@mozilla.org/xre/app-info;1",
+ "nsICrashReporter");
+
+const FILE_CACHE = "experiments.json";
+const EXPERIMENTS_CHANGED_TOPIC = "experiments-changed";
+const MANIFEST_VERSION = 1;
+const CACHE_VERSION = 1;
+
+const KEEP_HISTORY_N_DAYS = 180;
+
+const PREF_BRANCH = "experiments.";
+const PREF_ENABLED = "enabled"; // experiments.enabled
+const PREF_ACTIVE_EXPERIMENT = "activeExperiment"; // whether we have an active experiment
+const PREF_LOGGING = "logging";
+const PREF_LOGGING_LEVEL = PREF_LOGGING + ".level"; // experiments.logging.level
+const PREF_LOGGING_DUMP = PREF_LOGGING + ".dump"; // experiments.logging.dump
+const PREF_MANIFEST_URI = "manifest.uri"; // experiments.logging.manifest.uri
+const PREF_FORCE_SAMPLE = "force-sample-value"; // experiments.force-sample-value
+
+const PREF_BRANCH_TELEMETRY = "toolkit.telemetry.";
+const PREF_TELEMETRY_ENABLED = "enabled";
+
+const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
+const STRING_TYPE_NAME = "type.%ID%.name";
+
+const CACHE_WRITE_RETRY_DELAY_SEC = 60 * 3;
+const MANIFEST_FETCH_TIMEOUT_MSEC = 60 * 3 * 1000; // 3 minutes
+
+const TELEMETRY_LOG = {
+ // log(key, [kind, experimentId, details])
+ ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
+ ACTIVATION: {
+ // Successfully activated.
+ ACTIVATED: "ACTIVATED",
+ // Failed to install the add-on.
+ INSTALL_FAILURE: "INSTALL_FAILURE",
+ // Experiment does not meet activation requirements. Details will
+ // be provided.
+ REJECTED: "REJECTED",
+ },
+
+ // log(key, [kind, experimentId, optionalDetails...])
+ TERMINATION_KEY: "EXPERIMENT_TERMINATION",
+ TERMINATION: {
+ // The Experiments service was disabled.
+ SERVICE_DISABLED: "SERVICE_DISABLED",
+ // Add-on uninstalled.
+ ADDON_UNINSTALLED: "ADDON_UNINSTALLED",
+ // The experiment disabled itself.
+ FROM_API: "FROM_API",
+ // The experiment expired (e.g. by exceeding the end date).
+ EXPIRED: "EXPIRED",
+ // Disabled after re-evaluating conditions. If this is specified,
+ // details will be provided.
+ RECHECK: "RECHECK",
+ },
+};
+XPCOMUtils.defineConstant(this, "TELEMETRY_LOG", TELEMETRY_LOG);
+
+const gPrefs = new Preferences(PREF_BRANCH);
+const gPrefsTelemetry = new Preferences(PREF_BRANCH_TELEMETRY);
+var gExperimentsEnabled = false;
+var gAddonProvider = null;
+var gExperiments = null;
+var gLogAppenderDump = null;
+var gPolicyCounter = 0;
+var gExperimentsCounter = 0;
+var gExperimentEntryCounter = 0;
+var gPreviousProviderCounter = 0;
+
+// Tracks active AddonInstall we know about so we can deny external
+// installs.
+var gActiveInstallURLs = new Set();
+
+// Tracks add-on IDs that are being uninstalled by us. This allows us
+// to differentiate between expected uninstalled and user-driven uninstalls.
+var gActiveUninstallAddonIDs = new Set();
+
+var gLogger;
+var gLogDumping = false;
+
+function configureLogging() {
+ if (!gLogger) {
+ gLogger = Log.repository.getLogger("Browser.Experiments");
+ gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
+ }
+ gLogger.level = gPrefs.get(PREF_LOGGING_LEVEL, Log.Level.Warn);
+
+ let logDumping = gPrefs.get(PREF_LOGGING_DUMP, false);
+ if (logDumping != gLogDumping) {
+ if (logDumping) {
+ gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
+ gLogger.addAppender(gLogAppenderDump);
+ } else {
+ gLogger.removeAppender(gLogAppenderDump);
+ gLogAppenderDump = null;
+ }
+ gLogDumping = logDumping;
+ }
+}
+
+// Loads a JSON file using OS.file. file is a string representing the path
+// of the file to be read, options contains additional options to pass to
+// OS.File.read.
+// Returns a Promise resolved with the json payload or rejected with
+// OS.File.Error or JSON.parse() errors.
+function loadJSONAsync(file, options) {
+ return Task.spawn(function*() {
+ let rawData = yield OS.File.read(file, options);
+ // Read json file into a string
+ let data;
+ try {
+ // Obtain a converter to read from a UTF-8 encoded input stream.
+ let converter = new TextDecoder();
+ data = JSON.parse(converter.decode(rawData));
+ } catch (ex) {
+ gLogger.error("Experiments: Could not parse JSON: " + file + " " + ex);
+ throw ex;
+ }
+ return data;
+ });
+}
+
+// Returns a promise that is resolved with the AddonInstall for that URL.
+function addonInstallForURL(url, hash) {
+ let deferred = Promise.defer();
+ AddonManager.getInstallForURL(url, install => deferred.resolve(install),
+ "application/x-xpinstall", hash);
+ return deferred.promise;
+}
+
+// Returns a promise that is resolved with an Array<Addon> of the installed
+// experiment addons.
+function installedExperimentAddons() {
+ let deferred = Promise.defer();
+ AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+ deferred.resolve(addons.filter(a => !a.appDisabled));
+ });
+ return deferred.promise;
+}
+
+// Takes an Array<Addon> and returns a promise that is resolved when the
+// addons are uninstalled.
+function uninstallAddons(addons) {
+ let ids = new Set(addons.map(addon => addon.id));
+ let deferred = Promise.defer();
+
+ let listener = {};
+ listener.onUninstalled = addon => {
+ if (!ids.has(addon.id)) {
+ return;
+ }
+
+ ids.delete(addon.id);
+ if (ids.size == 0) {
+ AddonManager.removeAddonListener(listener);
+ deferred.resolve();
+ }
+ };
+
+ AddonManager.addAddonListener(listener);
+
+ for (let addon of addons) {
+ // Disabling the add-on before uninstalling is necessary to cause tests to
+ // pass. This might be indicative of a bug in XPIProvider.
+ // TODO follow up in bug 992396.
+ addon.userDisabled = true;
+ addon.uninstall();
+ }
+
+ return deferred.promise;
+}
+
+/**
+ * The experiments module.
+ */
+
+var Experiments = {
+ /**
+ * Provides access to the global `Experiments.Experiments` instance.
+ */
+ instance: function () {
+ if (!gExperiments) {
+ gExperiments = new Experiments.Experiments();
+ }
+
+ return gExperiments;
+ },
+};
+
+/*
+ * The policy object allows us to inject fake enviroment data from the
+ * outside by monkey-patching.
+ */
+
+Experiments.Policy = function () {
+ this._log = Log.repository.getLoggerWithMessagePrefix(
+ "Browser.Experiments.Policy",
+ "Policy #" + gPolicyCounter++ + "::");
+
+ // Set to true to ignore hash verification on downloaded XPIs. This should
+ // not be used outside of testing.
+ this.ignoreHashes = false;
+};
+
+Experiments.Policy.prototype = {
+ now: function () {
+ return new Date();
+ },
+
+ random: function () {
+ let pref = gPrefs.get(PREF_FORCE_SAMPLE);
+ if (pref !== undefined) {
+ let val = Number.parseFloat(pref);
+ this._log.debug("random sample forced: " + val);
+ if (isNaN(val) || val < 0) {
+ return 0;
+ }
+ if (val > 1) {
+ return 1;
+ }
+ return val;
+ }
+ return Math.random();
+ },
+
+ futureDate: function (offset) {
+ return new Date(this.now().getTime() + offset);
+ },
+
+ oneshotTimer: function (callback, timeout, thisObj, name) {
+ return CommonUtils.namedTimer(callback, timeout, thisObj, name);
+ },
+
+ updatechannel: function () {
+ return UpdateUtils.UpdateChannel;
+ },
+
+ locale: function () {
+ let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+ return chrome.getSelectedLocale("global");
+ },
+
+ /**
+ * For testing a race condition, one of the tests delays the callback of
+ * writing the cache by replacing this policy function.
+ */
+ delayCacheWrite: function(promise) {
+ return promise;
+ },
+};
+
+function AlreadyShutdownError(message="already shut down") {
+ Error.call(this, message);
+ let error = new Error();
+ this.name = "AlreadyShutdownError";
+ this.message = message;
+ this.stack = error.stack;
+}
+AlreadyShutdownError.prototype = Object.create(Error.prototype);
+AlreadyShutdownError.prototype.constructor = AlreadyShutdownError;
+
+function CacheWriteError(message="Error writing cache file") {
+ Error.call(this, message);
+ let error = new Error();
+ this.name = "CacheWriteError";
+ this.message = message;
+ this.stack = error.stack;
+}
+CacheWriteError.prototype = Object.create(Error.prototype);
+CacheWriteError.prototype.constructor = CacheWriteError;
+
+/**
+ * Manages the experiments and provides an interface to control them.
+ */
+
+Experiments.Experiments = function (policy=new Experiments.Policy()) {
+ let log = Log.repository.getLoggerWithMessagePrefix(
+ "Browser.Experiments.Experiments",
+ "Experiments #" + gExperimentsCounter++ + "::");
+
+ // At the time of this writing, Experiments.jsm has severe
+ // crashes. For forensics purposes, keep the last few log
+ // messages in memory and upload them in case of crash.
+ this._forensicsLogs = [];
+ this._forensicsLogs.length = 30;
+ this._log = Object.create(log);
+ this._log.log = (level, string, params) => {
+ this._addToForensicsLog("Experiments", string);
+ log.log(level, string, params);
+ };
+
+ this._log.trace("constructor");
+
+ // Capture the latest error, for forensics purposes.
+ this._latestError = null;
+
+
+ this._policy = policy;
+
+ // This is a Map of (string -> ExperimentEntry), keyed with the experiment id.
+ // It holds both the current experiments and history.
+ // Map() preserves insertion order, which means we preserve the manifest order.
+ // This is null until we've successfully completed loading the cache from
+ // disk the first time.
+ this._experiments = null;
+ this._refresh = false;
+ this._terminateReason = null; // or TELEMETRY_LOG.TERMINATION....
+ this._dirty = false;
+
+ // Loading the cache happens once asynchronously on startup
+ this._loadTask = null;
+
+ // The _main task handles all other actions:
+ // * refreshing the manifest off the network (if _refresh)
+ // * disabling/enabling experiments
+ // * saving the cache (if _dirty)
+ this._mainTask = null;
+
+ // Timer for re-evaluating experiment status.
+ this._timer = null;
+
+ this._shutdown = false;
+ this._networkRequest = null;
+
+ // We need to tell when we first evaluated the experiments to fire an
+ // experiments-changed notification when we only loaded completed experiments.
+ this._firstEvaluate = true;
+
+ this.init();
+};
+
+Experiments.Experiments.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
+
+ /**
+ * `true` if the experiments manager is currently setup (has been fully initialized
+ * and not uninitialized yet).
+ */
+ get isReady() {
+ return !this._shutdown;
+ },
+
+ init: function () {
+ this._shutdown = false;
+ configureLogging();
+
+ gExperimentsEnabled = gPrefs.get(PREF_ENABLED, false) && TelemetryUtils.isTelemetryEnabled;
+ this._log.trace("enabled=" + gExperimentsEnabled + ", " + this.enabled);
+
+ gPrefs.observe(PREF_LOGGING, configureLogging);
+ gPrefs.observe(PREF_MANIFEST_URI, this.updateManifest, this);
+ gPrefs.observe(PREF_ENABLED, this._toggleExperimentsEnabled, this);
+
+ gPrefsTelemetry.observe(PREF_TELEMETRY_ENABLED, this._telemetryStatusChanged, this);
+
+ AddonManager.shutdown.addBlocker("Experiments.jsm shutdown",
+ this.uninit.bind(this),
+ this._getState.bind(this)
+ );
+
+ this._registerWithAddonManager();
+
+ this._loadTask = this._loadFromCache();
+
+ return this._loadTask.then(
+ () => {
+ this._log.trace("_loadTask finished ok");
+ this._loadTask = null;
+ return this._run();
+ },
+ (e) => {
+ this._log.error("_loadFromCache caught error: " + e);
+ this._latestError = e;
+ throw e;
+ }
+ );
+ },
+
+ /**
+ * Uninitialize this instance.
+ *
+ * This function is susceptible to race conditions. If it is called multiple
+ * times before the previous uninit() has completed or if it is called while
+ * an init() operation is being performed, the object may get in bad state
+ * and/or deadlock could occur.
+ *
+ * @return Promise<>
+ * The promise is fulfilled when all pending tasks are finished.
+ */
+ uninit: Task.async(function* () {
+ this._log.trace("uninit: started");
+ yield this._loadTask;
+ this._log.trace("uninit: finished with _loadTask");
+
+ if (!this._shutdown) {
+ this._log.trace("uninit: no previous shutdown");
+ this._unregisterWithAddonManager();
+
+ gPrefs.ignore(PREF_LOGGING, configureLogging);
+ gPrefs.ignore(PREF_MANIFEST_URI, this.updateManifest, this);
+ gPrefs.ignore(PREF_ENABLED, this._toggleExperimentsEnabled, this);
+
+ gPrefsTelemetry.ignore(PREF_TELEMETRY_ENABLED, this._telemetryStatusChanged, this);
+
+ if (this._timer) {
+ this._timer.clear();
+ }
+ }
+
+ this._shutdown = true;
+ if (this._mainTask) {
+ if (this._networkRequest) {
+ try {
+ this._log.trace("Aborting pending network request: " + this._networkRequest);
+ this._networkRequest.abort();
+ } catch (e) {
+ // pass
+ }
+ }
+ try {
+ this._log.trace("uninit: waiting on _mainTask");
+ yield this._mainTask;
+ } catch (e) {
+ // We error out of tasks after shutdown via this exception.
+ this._log.trace(`uninit: caught error - ${e}`);
+ if (!(e instanceof AlreadyShutdownError)) {
+ this._latestError = e;
+ throw e;
+ }
+ }
+ }
+
+ this._log.info("Completed uninitialization.");
+ }),
+
+ // Return state information, for debugging purposes.
+ _getState: function() {
+ let activeExperiment = this._getActiveExperiment();
+ let state = {
+ isShutdown: this._shutdown,
+ isEnabled: gExperimentsEnabled,
+ isRefresh: this._refresh,
+ isDirty: this._dirty,
+ isFirstEvaluate: this._firstEvaluate,
+ hasLoadTask: !!this._loadTask,
+ hasMainTask: !!this._mainTask,
+ hasTimer: !!this._hasTimer,
+ hasAddonProvider: !!gAddonProvider,
+ latestLogs: this._forensicsLogs,
+ experiments: this._experiments ? [...this._experiments.keys()] : null,
+ terminateReason: this._terminateReason,
+ activeExperiment: activeExperiment ? activeExperiment.id : null,
+ };
+ if (this._latestError) {
+ if (typeof this._latestError == "object") {
+ state.latestError = {
+ message: this._latestError.message,
+ stack: this._latestError.stack
+ };
+ } else {
+ state.latestError = "" + this._latestError;
+ }
+ }
+ return state;
+ },
+
+ _addToForensicsLog: function (what, string) {
+ this._forensicsLogs.shift();
+ let timeInSec = Math.floor(Services.telemetry.msSinceProcessStart() / 1000);
+ this._forensicsLogs.push(`${timeInSec}: ${what} - ${string}`);
+ },
+
+ _registerWithAddonManager: function (previousExperimentsProvider) {
+ this._log.trace("Registering instance with Addon Manager.");
+
+ AddonManager.addAddonListener(this);
+ AddonManager.addInstallListener(this);
+
+ if (!gAddonProvider) {
+ // The properties of this AddonType should be kept in sync with the
+ // experiment AddonType registered in XPIProvider.
+ this._log.trace("Registering previous experiment add-on provider.");
+ gAddonProvider = previousExperimentsProvider || new Experiments.PreviousExperimentProvider(this);
+ AddonManagerPrivate.registerProvider(gAddonProvider, [
+ new AddonManagerPrivate.AddonType("experiment",
+ URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST,
+ 11000,
+ AddonManager.TYPE_UI_HIDE_EMPTY),
+ ]);
+ }
+
+ },
+
+ _unregisterWithAddonManager: function () {
+ this._log.trace("Unregistering instance with Addon Manager.");
+
+ this._log.trace("Removing install listener from add-on manager.");
+ AddonManager.removeInstallListener(this);
+ this._log.trace("Removing addon listener from add-on manager.");
+ AddonManager.removeAddonListener(this);
+ this._log.trace("Finished unregistering with addon manager.");
+
+ if (gAddonProvider) {
+ this._log.trace("Unregistering previous experiment add-on provider.");
+ AddonManagerPrivate.unregisterProvider(gAddonProvider);
+ gAddonProvider = null;
+ }
+ },
+
+ /*
+ * Change the PreviousExperimentsProvider that this instance uses.
+ * For testing only.
+ */
+ _setPreviousExperimentsProvider: function (provider) {
+ this._unregisterWithAddonManager();
+ this._registerWithAddonManager(provider);
+ },
+
+ /**
+ * Throws an exception if we've already shut down.
+ */
+ _checkForShutdown: function() {
+ if (this._shutdown) {
+ throw new AlreadyShutdownError("uninit() already called");
+ }
+ },
+
+ /**
+ * Whether the experiments feature is enabled.
+ */
+ get enabled() {
+ return gExperimentsEnabled;
+ },
+
+ /**
+ * Toggle whether the experiments feature is enabled or not.
+ */
+ set enabled(enabled) {
+ this._log.trace("set enabled(" + enabled + ")");
+ gPrefs.set(PREF_ENABLED, enabled);
+ },
+
+ _toggleExperimentsEnabled: Task.async(function* (enabled) {
+ this._log.trace("_toggleExperimentsEnabled(" + enabled + ")");
+ let wasEnabled = gExperimentsEnabled;
+ gExperimentsEnabled = enabled && TelemetryUtils.isTelemetryEnabled;
+
+ if (wasEnabled == gExperimentsEnabled) {
+ return;
+ }
+
+ if (gExperimentsEnabled) {
+ yield this.updateManifest();
+ } else {
+ yield this.disableExperiment(TELEMETRY_LOG.TERMINATION.SERVICE_DISABLED);
+ if (this._timer) {
+ this._timer.clear();
+ }
+ }
+ }),
+
+ _telemetryStatusChanged: function () {
+ this._toggleExperimentsEnabled(gExperimentsEnabled);
+ },
+
+ /**
+ * Returns a promise that is resolved with an array of `ExperimentInfo` objects,
+ * which provide info on the currently and recently active experiments.
+ * The array is in chronological order.
+ *
+ * The experiment info is of the form:
+ * {
+ * id: <string>,
+ * name: <string>,
+ * description: <string>,
+ * active: <boolean>,
+ * endDate: <integer>, // epoch ms
+ * detailURL: <string>,
+ * ... // possibly extended later
+ * }
+ *
+ * @return Promise<Array<ExperimentInfo>> Array of experiment info objects.
+ */
+ getExperiments: function () {
+ return Task.spawn(function*() {
+ yield this._loadTask;
+ let list = [];
+
+ for (let [id, experiment] of this._experiments) {
+ if (!experiment.startDate) {
+ // We only collect experiments that are or were active.
+ continue;
+ }
+
+ list.push({
+ id: id,
+ name: experiment._name,
+ description: experiment._description,
+ active: experiment.enabled,
+ endDate: experiment.endDate.getTime(),
+ detailURL: experiment._homepageURL,
+ branch: experiment.branch,
+ });
+ }
+
+ // Sort chronologically, descending.
+ list.sort((a, b) => b.endDate - a.endDate);
+ return list;
+ }.bind(this));
+ },
+
+ /**
+ * Returns the ExperimentInfo for the active experiment, or null
+ * if there is none.
+ */
+ getActiveExperiment: function () {
+ let experiment = this._getActiveExperiment();
+ if (!experiment) {
+ return null;
+ }
+
+ let info = {
+ id: experiment.id,
+ name: experiment._name,
+ description: experiment._description,
+ active: experiment.enabled,
+ endDate: experiment.endDate.getTime(),
+ detailURL: experiment._homepageURL,
+ };
+
+ return info;
+ },
+
+ /**
+ * Experiment "branch" support. If an experiment has multiple branches, it
+ * can record the branch with the experiment system and it will
+ * automatically be included in data reporting (FHR/telemetry payloads).
+ */
+
+ /**
+ * Set the experiment branch for the specified experiment ID.
+ * @returns Promise<>
+ */
+ setExperimentBranch: Task.async(function*(id, branchstr) {
+ yield this._loadTask;
+ let e = this._experiments.get(id);
+ if (!e) {
+ throw new Error("Experiment not found");
+ }
+ e.branch = String(branchstr);
+ this._log.trace("setExperimentBranch(" + id + ", " + e.branch + ") _dirty=" + this._dirty);
+ this._dirty = true;
+ Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC, null);
+ yield this._run();
+ }),
+ /**
+ * Get the branch of the specified experiment. If the experiment is unknown,
+ * throws an error.
+ *
+ * @param id The ID of the experiment. Pass null for the currently running
+ * experiment.
+ * @returns Promise<string|null>
+ * @throws Error if the specified experiment ID is unknown, or if there is no
+ * current experiment.
+ */
+ getExperimentBranch: Task.async(function*(id=null) {
+ yield this._loadTask;
+ let e;
+ if (id) {
+ e = this._experiments.get(id);
+ if (!e) {
+ throw new Error("Experiment not found");
+ }
+ } else {
+ e = this._getActiveExperiment();
+ if (e === null) {
+ throw new Error("No active experiment");
+ }
+ }
+ return e.branch;
+ }),
+
+ /**
+ * Determine whether another date has the same UTC day as now().
+ */
+ _dateIsTodayUTC: function (d) {
+ let now = this._policy.now();
+
+ return stripDateToMidnight(now).getTime() == stripDateToMidnight(d).getTime();
+ },
+
+ /**
+ * Obtain the entry of the most recent active experiment that was active
+ * today.
+ *
+ * If no experiment was active today, this resolves to nothing.
+ *
+ * Assumption: Only a single experiment can be active at a time.
+ *
+ * @return Promise<object>
+ */
+ lastActiveToday: function () {
+ return Task.spawn(function* getMostRecentActiveExperimentTask() {
+ let experiments = yield this.getExperiments();
+
+ // Assumption: Ordered chronologically, descending, with active always
+ // first.
+ for (let experiment of experiments) {
+ if (experiment.active) {
+ return experiment;
+ }
+
+ if (experiment.endDate && this._dateIsTodayUTC(experiment.endDate)) {
+ return experiment;
+ }
+ }
+ return null;
+ }.bind(this));
+ },
+
+ _run: function() {
+ this._log.trace("_run");
+ this._checkForShutdown();
+ if (!this._mainTask) {
+ this._mainTask = Task.spawn(function*() {
+ try {
+ yield this._main();
+ } catch (e) {
+ // In the CacheWriteError case we want to reschedule
+ if (!(e instanceof CacheWriteError)) {
+ this._log.error("_main caught error: " + e);
+ return;
+ }
+ } finally {
+ this._mainTask = null;
+ }
+ this._log.trace("_main finished, scheduling next run");
+ try {
+ yield this._scheduleNextRun();
+ } catch (ex) {
+ // We error out of tasks after shutdown via this exception.
+ if (!(ex instanceof AlreadyShutdownError)) {
+ throw ex;
+ }
+ }
+ }.bind(this));
+ }
+ return this._mainTask;
+ },
+
+ _main: function*() {
+ do {
+ this._log.trace("_main iteration");
+ yield this._loadTask;
+ if (!gExperimentsEnabled) {
+ this._refresh = false;
+ }
+
+ if (this._refresh) {
+ yield this._loadManifest();
+ }
+ yield this._evaluateExperiments();
+ if (this._dirty) {
+ yield this._saveToCache();
+ }
+ // If somebody called .updateManifest() or disableExperiment()
+ // while we were running, go again right now.
+ }
+ while (this._refresh || this._terminateReason || this._dirty);
+ },
+
+ _loadManifest: function*() {
+ this._log.trace("_loadManifest");
+ let uri = Services.urlFormatter.formatURLPref(PREF_BRANCH + PREF_MANIFEST_URI);
+
+ this._checkForShutdown();
+
+ this._refresh = false;
+ try {
+ let responseText = yield this._httpGetRequest(uri);
+ this._log.trace("_loadManifest() - responseText=\"" + responseText + "\"");
+
+ if (this._shutdown) {
+ return;
+ }
+
+ let data = JSON.parse(responseText);
+ this._updateExperiments(data);
+ } catch (e) {
+ this._log.error("_loadManifest - failure to fetch/parse manifest (continuing anyway): " + e);
+ }
+ },
+
+ /**
+ * Fetch an updated list of experiments and trigger experiment updates.
+ * Do only use when experiments are enabled.
+ *
+ * @return Promise<>
+ * The promise is resolved when the manifest and experiment list is updated.
+ */
+ updateManifest: function () {
+ this._log.trace("updateManifest()");
+
+ if (!gExperimentsEnabled) {
+ return Promise.reject(new Error("experiments are disabled"));
+ }
+
+ if (this._shutdown) {
+ return Promise.reject(Error("uninit() alrady called"));
+ }
+
+ this._refresh = true;
+ return this._run();
+ },
+
+ notify: function (timer) {
+ this._log.trace("notify()");
+ this._checkForShutdown();
+ return this._run();
+ },
+
+ // START OF ADD-ON LISTENERS
+
+ onUninstalled: function (addon) {
+ this._log.trace("onUninstalled() - addon id: " + addon.id);
+ if (gActiveUninstallAddonIDs.has(addon.id)) {
+ this._log.trace("matches pending uninstall");
+ return;
+ }
+ let activeExperiment = this._getActiveExperiment();
+ if (!activeExperiment || activeExperiment._addonId != addon.id) {
+ return;
+ }
+
+ this.disableExperiment(TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED);
+ },
+
+ /**
+ * @returns {Boolean} returns false when we cancel the install.
+ */
+ onInstallStarted: function (install) {
+ if (install.addon.type != "experiment") {
+ return true;
+ }
+
+ this._log.trace("onInstallStarted() - " + install.addon.id);
+ if (install.addon.appDisabled) {
+ // This is a PreviousExperiment
+ return true;
+ }
+
+ // We want to be in control of all experiment add-ons: reject installs
+ // for add-ons that we don't know about.
+
+ // We have a race condition of sorts to worry about here. We have 2
+ // onInstallStarted listeners. This one (the global one) and the one
+ // created as part of ExperimentEntry._installAddon. Because of the order
+ // they are registered in, this one likely executes first. Unfortunately,
+ // this means that the add-on ID is not yet set on the ExperimentEntry.
+ // So, we can't just look at this._trackedAddonIds because the new experiment
+ // will have its add-on ID set to null. We work around this by storing a
+ // identifying field - the source URL of the install - in a module-level
+ // variable (so multiple Experiments instances doesn't cancel each other
+ // out).
+
+ if (this._trackedAddonIds.has(install.addon.id)) {
+ this._log.info("onInstallStarted allowing install because add-on ID " +
+ "tracked by us.");
+ return true;
+ }
+
+ if (gActiveInstallURLs.has(install.sourceURI.spec)) {
+ this._log.info("onInstallStarted allowing install because install " +
+ "tracked by us.");
+ return true;
+ }
+
+ this._log.warn("onInstallStarted cancelling install of unknown " +
+ "experiment add-on: " + install.addon.id);
+ return false;
+ },
+
+ // END OF ADD-ON LISTENERS.
+
+ _getExperimentByAddonId: function (addonId) {
+ for (let [, entry] of this._experiments) {
+ if (entry._addonId === addonId) {
+ return entry;
+ }
+ }
+
+ return null;
+ },
+
+ /*
+ * Helper function to make HTTP GET requests. Returns a promise that is resolved with
+ * the responseText when the request is complete.
+ */
+ _httpGetRequest: function (url) {
+ this._log.trace("httpGetRequest(" + url + ")");
+ let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
+
+ this._networkRequest = xhr;
+ let deferred = Promise.defer();
+
+ let log = this._log;
+ let errorhandler = (evt) => {
+ log.error("httpGetRequest::onError() - Error making request to " + url + ": " + evt.type);
+ deferred.reject(new Error("Experiments - XHR error for " + url + " - " + evt.type));
+ this._networkRequest = null;
+ };
+ xhr.onerror = errorhandler;
+ xhr.ontimeout = errorhandler;
+ xhr.onabort = errorhandler;
+
+ xhr.onload = (event) => {
+ if (xhr.status !== 200 && xhr.state !== 0) {
+ log.error("httpGetRequest::onLoad() - Request to " + url + " returned status " + xhr.status);
+ deferred.reject(new Error("Experiments - XHR status for " + url + " is " + xhr.status));
+ this._networkRequest = null;
+ return;
+ }
+
+ deferred.resolve(xhr.responseText);
+ this._networkRequest = null;
+ };
+
+ try {
+ xhr.open("GET", url);
+
+ if (xhr.channel instanceof Ci.nsISupportsPriority) {
+ xhr.channel.priority = Ci.nsISupportsPriority.PRIORITY_LOWEST;
+ }
+
+ xhr.timeout = MANIFEST_FETCH_TIMEOUT_MSEC;
+ xhr.send(null);
+ } catch (e) {
+ this._log.error("httpGetRequest() - Error opening request to " + url + ": " + e);
+ return Promise.reject(new Error("Experiments - Error opening XHR for " + url));
+ }
+ return deferred.promise;
+ },
+
+ /*
+ * Path of the cache file we use in the profile.
+ */
+ get _cacheFilePath() {
+ return OS.Path.join(OS.Constants.Path.profileDir, FILE_CACHE);
+ },
+
+ /*
+ * Part of the main task to save the cache to disk, called from _main.
+ */
+ _saveToCache: function* () {
+ this._log.trace("_saveToCache");
+ let path = this._cacheFilePath;
+ this._dirty = false;
+ try {
+ let textData = JSON.stringify({
+ version: CACHE_VERSION,
+ data: [...this._experiments.values()].map(e => e.toJSON()),
+ });
+
+ let encoder = new TextEncoder();
+ let data = encoder.encode(textData);
+ let options = { tmpPath: path + ".tmp", compression: "lz4" };
+ yield this._policy.delayCacheWrite(OS.File.writeAtomic(path, data, options));
+ } catch (e) {
+ // We failed to write the cache, it's still dirty.
+ this._dirty = true;
+ this._log.error("_saveToCache failed and caught error: " + e);
+ throw new CacheWriteError();
+ }
+
+ this._log.debug("_saveToCache saved to " + path);
+ },
+
+ /*
+ * Task function, load the cached experiments manifest file from disk.
+ */
+ _loadFromCache: Task.async(function* () {
+ this._log.trace("_loadFromCache");
+ let path = this._cacheFilePath;
+ try {
+ let result = yield loadJSONAsync(path, { compression: "lz4" });
+ this._populateFromCache(result);
+ } catch (e) {
+ if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
+ // No cached manifest yet.
+ this._experiments = new Map();
+ } else {
+ throw e;
+ }
+ }
+ }),
+
+ _populateFromCache: function (data) {
+ this._log.trace("populateFromCache() - data: " + JSON.stringify(data));
+
+ // If the user has a newer cache version than we can understand, we fail
+ // hard; no experiments should be active in this older client.
+ if (CACHE_VERSION !== data.version) {
+ throw new Error("Experiments::_populateFromCache() - invalid cache version");
+ }
+
+ let experiments = new Map();
+ for (let item of data.data) {
+ let entry = new Experiments.ExperimentEntry(this._policy);
+ if (!entry.initFromCacheData(item)) {
+ continue;
+ }
+
+ // Discard old experiments if they ended more than 180 days ago.
+ if (entry.shouldDiscard()) {
+ // We discarded an experiment, the cache needs to be updated.
+ this._dirty = true;
+ continue;
+ }
+
+ experiments.set(entry.id, entry);
+ }
+
+ this._experiments = experiments;
+ },
+
+ /*
+ * Update the experiment entries from the experiments
+ * array in the manifest
+ */
+ _updateExperiments: function (manifestObject) {
+ this._log.trace("_updateExperiments() - experiments: " + JSON.stringify(manifestObject));
+
+ if (manifestObject.version !== MANIFEST_VERSION) {
+ this._log.warning("updateExperiments() - unsupported version " + manifestObject.version);
+ }
+
+ let experiments = new Map(); // The new experiments map
+
+ // Collect new and updated experiments.
+ for (let data of manifestObject.experiments) {
+ let entry = this._experiments.get(data.id);
+
+ if (entry) {
+ if (!entry.updateFromManifestData(data)) {
+ this._log.error("updateExperiments() - Invalid manifest data for " + data.id);
+ continue;
+ }
+ } else {
+ entry = new Experiments.ExperimentEntry(this._policy);
+ if (!entry.initFromManifestData(data)) {
+ continue;
+ }
+ }
+
+ if (entry.shouldDiscard()) {
+ continue;
+ }
+
+ experiments.set(entry.id, entry);
+ }
+
+ // Make sure we keep experiments that are or were running.
+ // We remove them after KEEP_HISTORY_N_DAYS.
+ for (let [id, entry] of this._experiments) {
+ if (experiments.has(id)) {
+ continue;
+ }
+
+ if (!entry.startDate || entry.shouldDiscard()) {
+ this._log.trace("updateExperiments() - discarding entry for " + id);
+ continue;
+ }
+
+ experiments.set(id, entry);
+ }
+
+ this._experiments = experiments;
+ this._dirty = true;
+ },
+
+ getActiveExperimentID: function() {
+ if (!this._experiments) {
+ return null;
+ }
+ let e = this._getActiveExperiment();
+ if (!e) {
+ return null;
+ }
+ return e.id;
+ },
+
+ getActiveExperimentBranch: function() {
+ if (!this._experiments) {
+ return null;
+ }
+ let e = this._getActiveExperiment();
+ if (!e) {
+ return null;
+ }
+ return e.branch;
+ },
+
+ _getActiveExperiment: function () {
+ let enabled = [...this._experiments.values()].filter(experiment => experiment._enabled);
+
+ if (enabled.length == 1) {
+ return enabled[0];
+ }
+
+ if (enabled.length > 1) {
+ this._log.error("getActiveExperimentId() - should not have more than 1 active experiment");
+ throw new Error("have more than 1 active experiment");
+ }
+
+ return null;
+ },
+
+ /**
+ * Disables all active experiments.
+ *
+ * @return Promise<> Promise that will get resolved once the task is done or failed.
+ */
+ disableExperiment: function (reason) {
+ if (!reason) {
+ throw new Error("Must specify a termination reason.");
+ }
+
+ this._log.trace("disableExperiment()");
+ this._terminateReason = reason;
+ return this._run();
+ },
+
+ /**
+ * The Set of add-on IDs that we know about from manifests.
+ */
+ get _trackedAddonIds() {
+ if (!this._experiments) {
+ return new Set();
+ }
+
+ return new Set([...this._experiments.values()].map(e => e._addonId));
+ },
+
+ /*
+ * Task function to check applicability of experiments, disable the active
+ * experiment if needed and activate the first applicable candidate.
+ */
+ _evaluateExperiments: function*() {
+ this._log.trace("_evaluateExperiments");
+
+ this._checkForShutdown();
+
+ // The first thing we do is reconcile our state against what's in the
+ // Addon Manager. It's possible that the Addon Manager knows of experiment
+ // add-ons that we don't. This could happen if an experiment gets installed
+ // when we're not listening or if there is a bug in our synchronization
+ // code.
+ //
+ // We have a few options of what to do with unknown experiment add-ons
+ // coming from the Addon Manager. Ideally, we'd convert these to
+ // ExperimentEntry instances and stuff them inside this._experiments.
+ // However, since ExperimentEntry contain lots of metadata from the
+ // manifest and trying to make up data could be error prone, it's safer
+ // to not try. Furthermore, if an experiment really did come from us, we
+ // should have some record of it. In the end, we decide to discard all
+ // knowledge for these unknown experiment add-ons.
+ let installedExperiments = yield installedExperimentAddons();
+ let expectedAddonIds = this._trackedAddonIds;
+ let unknownAddons = installedExperiments.filter(a => !expectedAddonIds.has(a.id));
+ if (unknownAddons.length) {
+ this._log.warn("_evaluateExperiments() - unknown add-ons in AddonManager: " +
+ unknownAddons.map(a => a.id).join(", "));
+
+ yield uninstallAddons(unknownAddons);
+ }
+
+ let activeExperiment = this._getActiveExperiment();
+ let activeChanged = false;
+
+ if (!activeExperiment) {
+ // Avoid this pref staying out of sync if there were e.g. crashes.
+ gPrefs.set(PREF_ACTIVE_EXPERIMENT, false);
+ }
+
+ // Ensure the active experiment is in the proper state. This may install,
+ // uninstall, upgrade, or enable the experiment add-on. What exactly is
+ // abstracted away from us by design.
+ if (activeExperiment) {
+ let changes;
+ let shouldStopResult = yield activeExperiment.shouldStop();
+ if (shouldStopResult.shouldStop) {
+ let expireReasons = ["endTime", "maxActiveSeconds"];
+ let kind, reason;
+
+ if (expireReasons.indexOf(shouldStopResult.reason[0]) != -1) {
+ kind = TELEMETRY_LOG.TERMINATION.EXPIRED;
+ reason = null;
+ } else {
+ kind = TELEMETRY_LOG.TERMINATION.RECHECK;
+ reason = shouldStopResult.reason;
+ }
+ changes = yield activeExperiment.stop(kind, reason);
+ }
+ else if (this._terminateReason) {
+ changes = yield activeExperiment.stop(this._terminateReason);
+ }
+ else {
+ changes = yield activeExperiment.reconcileAddonState();
+ }
+
+ if (changes) {
+ this._dirty = true;
+ activeChanged = true;
+ }
+
+ if (!activeExperiment._enabled) {
+ activeExperiment = null;
+ activeChanged = true;
+ }
+ }
+
+ this._terminateReason = null;
+
+ if (!activeExperiment && gExperimentsEnabled) {
+ for (let [id, experiment] of this._experiments) {
+ let applicable;
+ let reason = null;
+ try {
+ applicable = yield experiment.isApplicable();
+ }
+ catch (e) {
+ applicable = false;
+ reason = e;
+ }
+
+ if (!applicable && reason && reason[0] != "was-active") {
+ // Report this from here to avoid over-reporting.
+ let data = [TELEMETRY_LOG.ACTIVATION.REJECTED, id];
+ data = data.concat(reason);
+ const key = TELEMETRY_LOG.ACTIVATION_KEY;
+ TelemetryLog.log(key, data);
+ this._log.trace("evaluateExperiments() - added " + key + " to TelemetryLog: " + JSON.stringify(data));
+ }
+
+ if (!applicable) {
+ continue;
+ }
+
+ this._log.debug("evaluateExperiments() - activating experiment " + id);
+ try {
+ yield experiment.start();
+ activeChanged = true;
+ activeExperiment = experiment;
+ this._dirty = true;
+ break;
+ } catch (e) {
+ // On failure, clean up the best we can and try the next experiment.
+ this._log.error("evaluateExperiments() - Unable to start experiment: " + e.message);
+ experiment._enabled = false;
+ yield experiment.reconcileAddonState();
+ }
+ }
+ }
+
+ gPrefs.set(PREF_ACTIVE_EXPERIMENT, activeExperiment != null);
+
+ if (activeChanged || this._firstEvaluate) {
+ Services.obs.notifyObservers(null, EXPERIMENTS_CHANGED_TOPIC, null);
+ this._firstEvaluate = false;
+ }
+
+ if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) {
+ try {
+ gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id);
+ gCrashReporter.annotateCrashReport("ActiveExperimentBranch", activeExperiment.branch);
+ } catch (e) {
+ // It's ok if crash reporting is disabled.
+ }
+ }
+ },
+
+ /*
+ * Schedule the soonest re-check of experiment applicability that is needed.
+ */
+ _scheduleNextRun: function () {
+ this._checkForShutdown();
+
+ if (this._timer) {
+ this._timer.clear();
+ }
+
+ if (!gExperimentsEnabled || this._experiments.length == 0) {
+ return;
+ }
+
+ let time = null;
+ let now = this._policy.now().getTime();
+ if (this._dirty) {
+ // If we failed to write the cache, we should try again periodically
+ time = now + 1000 * CACHE_WRITE_RETRY_DELAY_SEC;
+ }
+
+ for (let [, experiment] of this._experiments) {
+ let scheduleTime = experiment.getScheduleTime();
+ if (scheduleTime > now) {
+ if (time !== null) {
+ time = Math.min(time, scheduleTime);
+ } else {
+ time = scheduleTime;
+ }
+ }
+ }
+
+ if (time === null) {
+ // No schedule time found.
+ return;
+ }
+
+ this._log.trace("scheduleExperimentEvaluation() - scheduling for "+time+", now: "+now);
+ this._policy.oneshotTimer(this.notify, time - now, this, "_timer");
+ },
+};
+
+
+/*
+ * Represents a single experiment.
+ */
+
+Experiments.ExperimentEntry = function (policy) {
+ this._policy = policy || new Experiments.Policy();
+ let log = Log.repository.getLoggerWithMessagePrefix(
+ "Browser.Experiments.Experiments",
+ "ExperimentEntry #" + gExperimentEntryCounter++ + "::");
+ this._log = Object.create(log);
+ this._log.log = (level, string, params) => {
+ if (gExperiments) {
+ gExperiments._addToForensicsLog("ExperimentEntry", string);
+ }
+ log.log(level, string, params);
+ };
+
+ // Is the experiment supposed to be running.
+ this._enabled = false;
+ // When this experiment was started, if ever.
+ this._startDate = null;
+ // When this experiment was ended, if ever.
+ this._endDate = null;
+ // The condition data from the manifest.
+ this._manifestData = null;
+ // For an active experiment, signifies whether we need to update the xpi.
+ this._needsUpdate = false;
+ // A random sample value for comparison against the manifest conditions.
+ this._randomValue = null;
+ // When this entry was last changed for respecting history retention duration.
+ this._lastChangedDate = null;
+ // Has this experiment failed to activate before?
+ this._failedStart = false;
+ // The experiment branch
+ this._branch = null;
+
+ // We grab these from the addon after download.
+ this._name = null;
+ this._description = null;
+ this._homepageURL = null;
+ this._addonId = null;
+};
+
+Experiments.ExperimentEntry.prototype = {
+ MANIFEST_REQUIRED_FIELDS: new Set([
+ "id",
+ "xpiURL",
+ "xpiHash",
+ "startTime",
+ "endTime",
+ "maxActiveSeconds",
+ "appName",
+ "channel",
+ ]),
+
+ MANIFEST_OPTIONAL_FIELDS: new Set([
+ "maxStartTime",
+ "minVersion",
+ "maxVersion",
+ "version",
+ "minBuildID",
+ "maxBuildID",
+ "buildIDs",
+ "os",
+ "locale",
+ "sample",
+ "disabled",
+ "frozen",
+ "jsfilter",
+ ]),
+
+ SERIALIZE_KEYS: new Set([
+ "_enabled",
+ "_manifestData",
+ "_needsUpdate",
+ "_randomValue",
+ "_failedStart",
+ "_name",
+ "_description",
+ "_homepageURL",
+ "_addonId",
+ "_startDate",
+ "_endDate",
+ "_branch",
+ ]),
+
+ DATE_KEYS: new Set([
+ "_startDate",
+ "_endDate",
+ ]),
+
+ UPGRADE_KEYS: new Map([
+ ["_branch", null],
+ ]),
+
+ ADDON_CHANGE_NONE: 0,
+ ADDON_CHANGE_INSTALL: 1,
+ ADDON_CHANGE_UNINSTALL: 2,
+ ADDON_CHANGE_ENABLE: 4,
+
+ /*
+ * Initialize entry from the manifest.
+ * @param data The experiment data from the manifest.
+ * @return boolean Whether initialization succeeded.
+ */
+ initFromManifestData: function (data) {
+ if (!this._isManifestDataValid(data)) {
+ return false;
+ }
+
+ this._manifestData = data;
+
+ this._randomValue = this._policy.random();
+ this._lastChangedDate = this._policy.now();
+
+ return true;
+ },
+
+ get enabled() {
+ return this._enabled;
+ },
+
+ get id() {
+ return this._manifestData.id;
+ },
+
+ get branch() {
+ return this._branch;
+ },
+
+ set branch(v) {
+ this._branch = v;
+ },
+
+ get startDate() {
+ return this._startDate;
+ },
+
+ get endDate() {
+ if (!this._startDate) {
+ return null;
+ }
+
+ let endTime = 0;
+
+ if (!this._enabled) {
+ return this._endDate;
+ }
+
+ let maxActiveMs = 1000 * this._manifestData.maxActiveSeconds;
+ endTime = Math.min(1000 * this._manifestData.endTime,
+ this._startDate.getTime() + maxActiveMs);
+
+ return new Date(endTime);
+ },
+
+ get needsUpdate() {
+ return this._needsUpdate;
+ },
+
+ /*
+ * Initialize entry from the cache.
+ * @param data The entry data from the cache.
+ * @return boolean Whether initialization succeeded.
+ */
+ initFromCacheData: function (data) {
+ for (let [key, dval] of this.UPGRADE_KEYS) {
+ if (!(key in data)) {
+ data[key] = dval;
+ }
+ }
+
+ for (let key of this.SERIALIZE_KEYS) {
+ if (!(key in data) && !this.DATE_KEYS.has(key)) {
+ this._log.error("initFromCacheData() - missing required key " + key);
+ return false;
+ }
+ }
+
+ if (!this._isManifestDataValid(data._manifestData)) {
+ return false;
+ }
+
+ // Dates are restored separately from epoch ms, everything else is just
+ // copied in.
+
+ this.SERIALIZE_KEYS.forEach(key => {
+ if (!this.DATE_KEYS.has(key)) {
+ this[key] = data[key];
+ }
+ });
+
+ this.DATE_KEYS.forEach(key => {
+ if (key in data) {
+ let date = new Date();
+ date.setTime(data[key]);
+ this[key] = date;
+ }
+ });
+
+ // In order for the experiment's data expiration mechanism to work, use the experiment's
+ // |_endData| as the |_lastChangedDate| (if available).
+ this._lastChangedDate = this._endDate ? this._endDate : this._policy.now();
+
+ return true;
+ },
+
+ /*
+ * Returns a JSON representation of this object.
+ */
+ toJSON: function () {
+ let obj = {};
+
+ // Dates are serialized separately as epoch ms.
+
+ this.SERIALIZE_KEYS.forEach(key => {
+ if (!this.DATE_KEYS.has(key)) {
+ obj[key] = this[key];
+ }
+ });
+
+ this.DATE_KEYS.forEach(key => {
+ if (this[key]) {
+ obj[key] = this[key].getTime();
+ }
+ });
+
+ return obj;
+ },
+
+ /*
+ * Update from the experiment data from the manifest.
+ * @param data The experiment data from the manifest.
+ * @return boolean Whether updating succeeded.
+ */
+ updateFromManifestData: function (data) {
+ let old = this._manifestData;
+
+ if (!this._isManifestDataValid(data)) {
+ return false;
+ }
+
+ if (this._enabled) {
+ if (old.xpiHash !== data.xpiHash) {
+ // A changed hash means we need to update active experiments.
+ this._needsUpdate = true;
+ }
+ } else if (this._failedStart &&
+ (old.xpiHash !== data.xpiHash) ||
+ (old.xpiURL !== data.xpiURL)) {
+ // Retry installation of previously invalid experiments
+ // if hash or url changed.
+ this._failedStart = false;
+ }
+
+ this._manifestData = data;
+ this._lastChangedDate = this._policy.now();
+
+ return true;
+ },
+
+ /*
+ * Is this experiment applicable?
+ * @return Promise<> Resolved if the experiment is applicable.
+ * If it is not applicable it is rejected with
+ * a Promise<string> which contains the reason.
+ */
+ isApplicable: function () {
+ let versionCmp = Cc["@mozilla.org/xpcom/version-comparator;1"]
+ .getService(Ci.nsIVersionComparator);
+ let app = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
+ let runtime = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime);
+
+ let locale = this._policy.locale();
+ let channel = this._policy.updatechannel();
+ let data = this._manifestData;
+
+ let now = this._policy.now() / 1000; // The manifest times are in seconds.
+ let maxActive = data.maxActiveSeconds || 0;
+ let startSec = (this.startDate || 0) / 1000;
+
+ this._log.trace("isApplicable() - now=" + now
+ + ", randomValue=" + this._randomValue);
+
+ // Not applicable if it already ran.
+
+ if (!this.enabled && this._endDate) {
+ return Promise.reject(["was-active"]);
+ }
+
+ // Define and run the condition checks.
+
+ let simpleChecks = [
+ { name: "failedStart",
+ condition: () => !this._failedStart },
+ { name: "disabled",
+ condition: () => !data.disabled },
+ { name: "frozen",
+ condition: () => !data.frozen || this._enabled },
+ { name: "startTime",
+ condition: () => now >= data.startTime },
+ { name: "endTime",
+ condition: () => now < data.endTime },
+ { name: "maxStartTime",
+ condition: () => this._startDate || !data.maxStartTime || now <= data.maxStartTime },
+ { name: "maxActiveSeconds",
+ condition: () => !this._startDate || now <= (startSec + maxActive) },
+ { name: "appName",
+ condition: () => !data.appName || data.appName.indexOf(app.name) != -1 },
+ { name: "minBuildID",
+ condition: () => !data.minBuildID || app.platformBuildID >= data.minBuildID },
+ { name: "maxBuildID",
+ condition: () => !data.maxBuildID || app.platformBuildID <= data.maxBuildID },
+ { name: "buildIDs",
+ condition: () => !data.buildIDs || data.buildIDs.indexOf(app.platformBuildID) != -1 },
+ { name: "os",
+ condition: () => !data.os || data.os.indexOf(runtime.OS) != -1 },
+ { name: "channel",
+ condition: () => !data.channel || data.channel.indexOf(channel) != -1 },
+ { name: "locale",
+ condition: () => !data.locale || data.locale.indexOf(locale) != -1 },
+ { name: "sample",
+ condition: () => data.sample === undefined || this._randomValue <= data.sample },
+ { name: "version",
+ condition: () => !data.version || data.version.indexOf(app.version) != -1 },
+ { name: "minVersion",
+ condition: () => !data.minVersion || versionCmp.compare(app.version, data.minVersion) >= 0 },
+ { name: "maxVersion",
+ condition: () => !data.maxVersion || versionCmp.compare(app.version, data.maxVersion) <= 0 },
+ ];
+
+ for (let check of simpleChecks) {
+ let result = check.condition();
+ if (!result) {
+ this._log.debug("isApplicable() - id="
+ + data.id + " - test '" + check.name + "' failed");
+ return Promise.reject([check.name]);
+ }
+ }
+
+ if (data.jsfilter) {
+ return this._runFilterFunction(data.jsfilter);
+ }
+
+ return Promise.resolve(true);
+ },
+
+ /*
+ * Run the jsfilter function from the manifest in a sandbox and return the
+ * result (forced to boolean).
+ */
+ _runFilterFunction: Task.async(function* (jsfilter) {
+ this._log.trace("runFilterFunction() - filter: " + jsfilter);
+
+ let ssm = Services.scriptSecurityManager;
+ const nullPrincipal = ssm.createNullPrincipal({});
+ let options = {
+ sandboxName: "telemetry experiments jsfilter sandbox",
+ wantComponents: false,
+ };
+
+ let sandbox = Cu.Sandbox(nullPrincipal, options);
+ try {
+ Cu.evalInSandbox(jsfilter, sandbox);
+ } catch (e) {
+ this._log.error("runFilterFunction() - failed to eval jsfilter: " + e.message);
+ throw ["jsfilter-evalfailed"];
+ }
+
+ let currentEnvironment = yield TelemetryEnvironment.onInitialized();
+
+ Object.defineProperty(sandbox, "_e",
+ { get: () => Cu.cloneInto(currentEnvironment, sandbox) });
+
+ let result = false;
+ try {
+ result = !!Cu.evalInSandbox("filter({get telemetryEnvironment() { return _e; } })", sandbox);
+ }
+ catch (e) {
+ this._log.debug("runFilterFunction() - filter function failed: "
+ + e.message + ", " + e.stack);
+ throw ["jsfilter-threw", e.message];
+ }
+ finally {
+ Cu.nukeSandbox(sandbox);
+ }
+
+ if (!result) {
+ throw ["jsfilter-false"];
+ }
+
+ return true;
+ }),
+
+ /*
+ * Start running the experiment.
+ *
+ * @return Promise<> Resolved when the operation is complete.
+ */
+ start: Task.async(function* () {
+ this._log.trace("start() for " + this.id);
+
+ this._enabled = true;
+ return yield this.reconcileAddonState();
+ }),
+
+ // Async install of the addon for this experiment, part of the start task above.
+ _installAddon: Task.async(function* () {
+ let deferred = Promise.defer();
+
+ let hash = this._policy.ignoreHashes ? null : this._manifestData.xpiHash;
+
+ let install = yield addonInstallForURL(this._manifestData.xpiURL, hash);
+ gActiveInstallURLs.add(install.sourceURI.spec);
+
+ let failureHandler = (install, handler) => {
+ let message = "AddonInstall " + handler + " for " + this.id + ", state=" +
+ (install.state || "?") + ", error=" + install.error;
+ this._log.error("_installAddon() - " + message);
+ this._failedStart = true;
+ gActiveInstallURLs.delete(install.sourceURI.spec);
+
+ TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
+ [TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]);
+
+ deferred.reject(new Error(message));
+ };
+
+ let listener = {
+ _expectedID: null,
+
+ onDownloadEnded: install => {
+ this._log.trace("_installAddon() - onDownloadEnded for " + this.id);
+
+ if (install.existingAddon) {
+ this._log.warn("_installAddon() - onDownloadEnded, addon already installed");
+ }
+
+ if (install.addon.type !== "experiment") {
+ this._log.error("_installAddon() - onDownloadEnded, wrong addon type");
+ install.cancel();
+ }
+ },
+
+ onInstallStarted: install => {
+ this._log.trace("_installAddon() - onInstallStarted for " + this.id);
+
+ if (install.existingAddon) {
+ this._log.warn("_installAddon() - onInstallStarted, addon already installed");
+ }
+
+ if (install.addon.type !== "experiment") {
+ this._log.error("_installAddon() - onInstallStarted, wrong addon type");
+ return false;
+ }
+ return undefined;
+ },
+
+ onInstallEnded: install => {
+ this._log.trace("_installAddon() - install ended for " + this.id);
+ gActiveInstallURLs.delete(install.sourceURI.spec);
+
+ this._lastChangedDate = this._policy.now();
+ this._startDate = this._policy.now();
+ this._enabled = true;
+
+ TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
+ [TELEMETRY_LOG.ACTIVATION.ACTIVATED, this.id]);
+
+ let addon = install.addon;
+ this._name = addon.name;
+ this._addonId = addon.id;
+ this._description = addon.description || "";
+ this._homepageURL = addon.homepageURL || "";
+
+ // Experiment add-ons default to userDisabled=true. Enable if needed.
+ if (addon.userDisabled) {
+ this._log.trace("Add-on is disabled. Enabling.");
+ listener._expectedID = addon.id;
+ AddonManager.addAddonListener(listener);
+ addon.userDisabled = false;
+ } else {
+ this._log.trace("Add-on is enabled. start() completed.");
+ deferred.resolve();
+ }
+ },
+
+ onEnabled: addon => {
+ this._log.info("onEnabled() for " + addon.id);
+
+ if (addon.id != listener._expectedID) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ deferred.resolve();
+ },
+ };
+
+ ["onDownloadCancelled", "onDownloadFailed", "onInstallCancelled", "onInstallFailed"]
+ .forEach(what => {
+ listener[what] = install => failureHandler(install, what)
+ });
+
+ install.addListener(listener);
+ install.install();
+
+ return yield deferred.promise;
+ }),
+
+ /**
+ * Stop running the experiment if it is active.
+ *
+ * @param terminationKind (optional)
+ * The termination kind, e.g. ADDON_UNINSTALLED or EXPIRED.
+ * @param terminationReason (optional)
+ * The termination reason details for termination kind RECHECK.
+ * @return Promise<> Resolved when the operation is complete.
+ */
+ stop: Task.async(function* (terminationKind, terminationReason) {
+ this._log.trace("stop() - id=" + this.id + ", terminationKind=" + terminationKind);
+ if (!this._enabled) {
+ throw new Error("Must not call stop() on an inactive experiment.");
+ }
+
+ this._enabled = false;
+ let now = this._policy.now();
+ this._lastChangedDate = now;
+ this._endDate = now;
+
+ let changes = yield this.reconcileAddonState();
+ this._logTermination(terminationKind, terminationReason);
+
+ if (terminationKind == TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED) {
+ changes |= this.ADDON_CHANGE_UNINSTALL;
+ }
+
+ return changes;
+ }),
+
+ /**
+ * Reconcile the state of the add-on against what it's supposed to be.
+ *
+ * If we are active, ensure the add-on is enabled and up to date.
+ *
+ * If we are inactive, ensure the add-on is not installed.
+ */
+ reconcileAddonState: Task.async(function* () {
+ this._log.trace("reconcileAddonState()");
+
+ if (!this._enabled) {
+ if (!this._addonId) {
+ this._log.trace("reconcileAddonState() - Experiment is not enabled and " +
+ "has no add-on. Doing nothing.");
+ return this.ADDON_CHANGE_NONE;
+ }
+
+ let addon = yield this._getAddon();
+ if (!addon) {
+ this._log.trace("reconcileAddonState() - Inactive experiment has no " +
+ "add-on. Doing nothing.");
+ return this.ADDON_CHANGE_NONE;
+ }
+
+ this._log.info("reconcileAddonState() - Uninstalling add-on for inactive " +
+ "experiment: " + addon.id);
+ gActiveUninstallAddonIDs.add(addon.id);
+ yield uninstallAddons([addon]);
+ gActiveUninstallAddonIDs.delete(addon.id);
+ return this.ADDON_CHANGE_UNINSTALL;
+ }
+
+ // If we get here, we're supposed to be active.
+
+ let changes = 0;
+
+ // That requires an add-on.
+ let currentAddon = yield this._getAddon();
+
+ // If we have an add-on but it isn't up to date, uninstall it
+ // (to prepare for reinstall).
+ if (currentAddon && this._needsUpdate) {
+ this._log.info("reconcileAddonState() - Uninstalling add-on because update " +
+ "needed: " + currentAddon.id);
+ gActiveUninstallAddonIDs.add(currentAddon.id);
+ yield uninstallAddons([currentAddon]);
+ gActiveUninstallAddonIDs.delete(currentAddon.id);
+ changes |= this.ADDON_CHANGE_UNINSTALL;
+ }
+
+ if (!currentAddon || this._needsUpdate) {
+ this._log.info("reconcileAddonState() - Installing add-on.");
+ yield this._installAddon();
+ changes |= this.ADDON_CHANGE_INSTALL;
+ }
+
+ let addon = yield this._getAddon();
+ if (!addon) {
+ throw new Error("Could not obtain add-on for experiment that should be " +
+ "enabled.");
+ }
+
+ // If we have the add-on and it is enabled, we are done.
+ if (!addon.userDisabled) {
+ return changes;
+ }
+
+ // Check permissions to see if we can enable the addon.
+ if (!(addon.permissions & AddonManager.PERM_CAN_ENABLE)) {
+ throw new Error("Don't have permission to enable addon " + addon.id + ", perm=" + addon.permission);
+ }
+
+ // Experiment addons should not require a restart.
+ if (addon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE) {
+ throw new Error("Experiment addon requires a restart: " + addon.id);
+ }
+
+ let deferred = Promise.defer();
+
+ // Else we need to enable it.
+ let listener = {
+ onEnabled: enabledAddon => {
+ if (enabledAddon.id != addon.id) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ deferred.resolve();
+ },
+ };
+
+ for (let handler of ["onDisabled", "onOperationCancelled", "onUninstalled"]) {
+ listener[handler] = (evtAddon) => {
+ if (evtAddon.id != addon.id) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ deferred.reject("Failed to enable addon " + addon.id + " due to: " + handler);
+ };
+ }
+
+ this._log.info("reconcileAddonState() - Activating add-on: " + addon.id);
+ AddonManager.addAddonListener(listener);
+ addon.userDisabled = false;
+ yield deferred.promise;
+ changes |= this.ADDON_CHANGE_ENABLE;
+
+ this._log.info("reconcileAddonState() - Add-on has been enabled: " + addon.id);
+ return changes;
+ }),
+
+ /**
+ * Obtain the underlying Addon from the Addon Manager.
+ *
+ * @return Promise<Addon|null>
+ */
+ _getAddon: function () {
+ if (!this._addonId) {
+ return Promise.resolve(null);
+ }
+
+ let deferred = Promise.defer();
+
+ AddonManager.getAddonByID(this._addonId, (addon) => {
+ if (addon && addon.appDisabled) {
+ // Don't return PreviousExperiments.
+ addon = null;
+ }
+
+ deferred.resolve(addon);
+ });
+
+ return deferred.promise;
+ },
+
+ _logTermination: function (terminationKind, terminationReason) {
+ if (terminationKind === undefined) {
+ return;
+ }
+
+ if (!(terminationKind in TELEMETRY_LOG.TERMINATION)) {
+ this._log.warn("stop() - unknown terminationKind " + terminationKind);
+ return;
+ }
+
+ let data = [terminationKind, this.id];
+ if (terminationReason) {
+ data = data.concat(terminationReason);
+ }
+
+ TelemetryLog.log(TELEMETRY_LOG.TERMINATION_KEY, data);
+ },
+
+ /**
+ * Determine whether an active experiment should be stopped.
+ */
+ shouldStop: function () {
+ if (!this._enabled) {
+ throw new Error("shouldStop must not be called on disabled experiments.");
+ }
+
+ let deferred = Promise.defer();
+ this.isApplicable().then(
+ () => deferred.resolve({shouldStop: false}),
+ reason => deferred.resolve({shouldStop: true, reason: reason})
+ );
+
+ return deferred.promise;
+ },
+
+ /*
+ * Should this be discarded from the cache due to age?
+ */
+ shouldDiscard: function () {
+ let limit = this._policy.now();
+ limit.setDate(limit.getDate() - KEEP_HISTORY_N_DAYS);
+ return (this._lastChangedDate < limit);
+ },
+
+ /*
+ * Get next date (in epoch-ms) to schedule a re-evaluation for this.
+ * Returns 0 if it doesn't need one.
+ */
+ getScheduleTime: function () {
+ if (this._enabled) {
+ let startTime = this._startDate.getTime();
+ let maxActiveTime = startTime + 1000 * this._manifestData.maxActiveSeconds;
+ return Math.min(1000 * this._manifestData.endTime, maxActiveTime);
+ }
+
+ if (this._endDate) {
+ return this._endDate.getTime();
+ }
+
+ return 1000 * this._manifestData.startTime;
+ },
+
+ /*
+ * Perform sanity checks on the experiment data.
+ */
+ _isManifestDataValid: function (data) {
+ this._log.trace("isManifestDataValid() - data: " + JSON.stringify(data));
+
+ for (let key of this.MANIFEST_REQUIRED_FIELDS) {
+ if (!(key in data)) {
+ this._log.error("isManifestDataValid() - missing required key: " + key);
+ return false;
+ }
+ }
+
+ for (let key in data) {
+ if (!this.MANIFEST_OPTIONAL_FIELDS.has(key) &&
+ !this.MANIFEST_REQUIRED_FIELDS.has(key)) {
+ this._log.error("isManifestDataValid() - unknown key: " + key);
+ return false;
+ }
+ }
+
+ return true;
+ },
+};
+
+/**
+ * Strip a Date down to its UTC midnight.
+ *
+ * This will return a cloned Date object. The original is unchanged.
+ */
+var stripDateToMidnight = function (d) {
+ let m = new Date(d);
+ m.setUTCHours(0, 0, 0, 0);
+
+ return m;
+};
+
+/**
+ * An Add-ons Manager provider that knows about old experiments.
+ *
+ * This provider exposes read-only add-ons corresponding to previously-active
+ * experiments. The existence of this provider (and the add-ons it knows about)
+ * facilitates the display of old experiments in the Add-ons Manager UI with
+ * very little custom code in that component.
+ */
+this.Experiments.PreviousExperimentProvider = function (experiments) {
+ this._experiments = experiments;
+ this._experimentList = [];
+ this._log = Log.repository.getLoggerWithMessagePrefix(
+ "Browser.Experiments.Experiments",
+ "PreviousExperimentProvider #" + gPreviousProviderCounter++ + "::");
+}
+
+this.Experiments.PreviousExperimentProvider.prototype = Object.freeze({
+ name: "PreviousExperimentProvider",
+
+ startup: function () {
+ this._log.trace("startup()");
+ Services.obs.addObserver(this, EXPERIMENTS_CHANGED_TOPIC, false);
+ },
+
+ shutdown: function () {
+ this._log.trace("shutdown()");
+ try {
+ Services.obs.removeObserver(this, EXPERIMENTS_CHANGED_TOPIC);
+ } catch (e) {
+ // Prevent crash in mochitest-browser3 on Mulet
+ }
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case EXPERIMENTS_CHANGED_TOPIC:
+ this._updateExperimentList();
+ break;
+ }
+ },
+
+ getAddonByID: function (id, cb) {
+ for (let experiment of this._experimentList) {
+ if (experiment.id == id) {
+ cb(new PreviousExperimentAddon(experiment));
+ return;
+ }
+ }
+
+ cb(null);
+ },
+
+ getAddonsByTypes: function (types, cb) {
+ if (types && types.length > 0 && types.indexOf("experiment") == -1) {
+ cb([]);
+ return;
+ }
+
+ cb(this._experimentList.map(e => new PreviousExperimentAddon(e)));
+ },
+
+ _updateExperimentList: function () {
+ return this._experiments.getExperiments().then((experiments) => {
+ let list = experiments.filter(e => !e.active);
+
+ let newMap = new Map(list.map(e => [e.id, e]));
+ let oldMap = new Map(this._experimentList.map(e => [e.id, e]));
+
+ let added = [...newMap.keys()].filter(id => !oldMap.has(id));
+ let removed = [...oldMap.keys()].filter(id => !newMap.has(id));
+
+ for (let id of added) {
+ this._log.trace("updateExperimentList() - adding " + id);
+ let wrapper = new PreviousExperimentAddon(newMap.get(id));
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null, wrapper, null, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false);
+ }
+
+ for (let id of removed) {
+ this._log.trace("updateExperimentList() - removing " + id);
+ let wrapper = new PreviousExperimentAddon(oldMap.get(id));
+ AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
+ }
+
+ this._experimentList = list;
+
+ for (let id of added) {
+ let wrapper = new PreviousExperimentAddon(newMap.get(id));
+ AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
+ }
+
+ for (let id of removed) {
+ let wrapper = new PreviousExperimentAddon(oldMap.get(id));
+ AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+ }
+
+ return this._experimentList;
+ });
+ },
+});
+
+/**
+ * An add-on that represents a previously-installed experiment.
+ */
+function PreviousExperimentAddon(experiment) {
+ this._id = experiment.id;
+ this._name = experiment.name;
+ this._endDate = experiment.endDate;
+ this._description = experiment.description;
+}
+
+PreviousExperimentAddon.prototype = Object.freeze({
+ // BEGIN REQUIRED ADDON PROPERTIES
+
+ get appDisabled() {
+ return true;
+ },
+
+ get blocklistState() {
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ },
+
+ get creator() {
+ return new AddonManagerPrivate.AddonAuthor("");
+ },
+
+ get foreignInstall() {
+ return false;
+ },
+
+ get id() {
+ return this._id;
+ },
+
+ get isActive() {
+ return false;
+ },
+
+ get isCompatible() {
+ return true;
+ },
+
+ get isPlatformCompatible() {
+ return true;
+ },
+
+ get name() {
+ return this._name;
+ },
+
+ get pendingOperations() {
+ return AddonManager.PENDING_NONE;
+ },
+
+ get permissions() {
+ return 0;
+ },
+
+ get providesUpdatesSecurely() {
+ return true;
+ },
+
+ get scope() {
+ return AddonManager.SCOPE_PROFILE;
+ },
+
+ get type() {
+ return "experiment";
+ },
+
+ get userDisabled() {
+ return true;
+ },
+
+ get version() {
+ return null;
+ },
+
+ // END REQUIRED PROPERTIES
+
+ // BEGIN OPTIONAL PROPERTIES
+
+ get description() {
+ return this._description;
+ },
+
+ get updateDate() {
+ return new Date(this._endDate);
+ },
+
+ // END OPTIONAL PROPERTIES
+
+ // BEGIN REQUIRED METHODS
+
+ isCompatibleWith: function (appVersion, platformVersion) {
+ return true;
+ },
+
+ findUpdates: function (listener, reason, appVersion, platformVersion) {
+ AddonManagerPrivate.callNoUpdateListeners(this, listener, reason,
+ appVersion, platformVersion);
+ },
+
+ // END REQUIRED METHODS
+
+ /**
+ * The end-date of the experiment, required for the Addon Manager UI.
+ */
+
+ get endDate() {
+ return this._endDate;
+ },
+
+});
diff --git a/browser/experiments/Experiments.manifest b/browser/experiments/Experiments.manifest
new file mode 100644
index 000000000..4a6a05a60
--- /dev/null
+++ b/browser/experiments/Experiments.manifest
@@ -0,0 +1,6 @@
+component {f7800463-3b97-47f9-9341-b7617e6d8d49} ExperimentsService.js
+contract @mozilla.org/browser/experiments-service;1 {f7800463-3b97-47f9-9341-b7617e6d8d49}
+category update-timer ExperimentsService @mozilla.org/browser/experiments-service;1,getService,experiments-update-timer,experiments.manifest.fetchIntervalSeconds,86400
+category profile-after-change ExperimentsService @mozilla.org/browser/experiments-service;1
+
+
diff --git a/browser/experiments/ExperimentsService.js b/browser/experiments/ExperimentsService.js
new file mode 100644
index 000000000..53e811251
--- /dev/null
+++ b/browser/experiments/ExperimentsService.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";
+
+const {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");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
+ "resource:///modules/experiments/Experiments.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
+ "resource://services-common/utils.js");
+
+const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
+const PREF_ACTIVE_EXPERIMENT = "experiments.activeExperiment"; // whether we have an active experiment
+const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
+const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
+const DELAY_INIT_MS = 30 * 1000;
+
+// Whether the FHR/Telemetry unification features are enabled.
+// Changing this pref requires a restart.
+const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_TELEMETRY_UNIFIED, false);
+
+XPCOMUtils.defineLazyGetter(
+ this, "gPrefs", () => {
+ return new Preferences();
+ });
+
+XPCOMUtils.defineLazyGetter(
+ this, "gExperimentsEnabled", () => {
+ // We can enable experiments if either unified Telemetry or FHR is on, and the user
+ // has opted into Telemetry.
+ return gPrefs.get(PREF_EXPERIMENTS_ENABLED, false) &&
+ IS_UNIFIED_TELEMETRY && gPrefs.get(PREF_TELEMETRY_ENABLED, false);
+ });
+
+XPCOMUtils.defineLazyGetter(
+ this, "gActiveExperiment", () => {
+ return gPrefs.get(PREF_ACTIVE_EXPERIMENT);
+ });
+
+function ExperimentsService() {
+ this._initialized = false;
+ this._delayedInitTimer = null;
+}
+
+ExperimentsService.prototype = {
+ classID: Components.ID("{f7800463-3b97-47f9-9341-b7617e6d8d49}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
+
+ notify: function (timer) {
+ if (!gExperimentsEnabled) {
+ return;
+ }
+ if (OS.Constants.Path.profileDir === undefined) {
+ throw Error("Update timer fired before profile was initialized?");
+ }
+ let instance = Experiments.instance();
+ if (instance.isReady) {
+ instance.updateManifest();
+ }
+ },
+
+ _delayedInit: function () {
+ if (!this._initialized) {
+ this._initialized = true;
+ Experiments.instance(); // for side effects
+ }
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case "profile-after-change":
+ if (gExperimentsEnabled) {
+ Services.obs.addObserver(this, "quit-application", false);
+ Services.obs.addObserver(this, "sessionstore-state-finalized", false);
+ Services.obs.addObserver(this, "EM-loaded", false);
+
+ if (gActiveExperiment) {
+ this._initialized = true;
+ Experiments.instance(); // for side effects
+ }
+ }
+ break;
+ case "sessionstore-state-finalized":
+ if (!this._initialized) {
+ CommonUtils.namedTimer(this._delayedInit, DELAY_INIT_MS, this, "_delayedInitTimer");
+ }
+ break;
+ case "EM-loaded":
+ if (!this._initialized) {
+ Experiments.instance(); // for side effects
+ this._initialized = true;
+
+ if (this._delayedInitTimer) {
+ this._delayedInitTimer.clear();
+ }
+ }
+ break;
+ case "quit-application":
+ Services.obs.removeObserver(this, "quit-application");
+ Services.obs.removeObserver(this, "sessionstore-state-finalized");
+ Services.obs.removeObserver(this, "EM-loaded");
+ if (this._delayedInitTimer) {
+ this._delayedInitTimer.clear();
+ }
+ break;
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExperimentsService]);
diff --git a/browser/experiments/Makefile.in b/browser/experiments/Makefile.in
new file mode 100644
index 000000000..5558582a6
--- /dev/null
+++ b/browser/experiments/Makefile.in
@@ -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/.
+
+include $(topsrcdir)/config/rules.mk
+
+# This is so hacky. Waiting on bug 988938.
+addondir = $(srcdir)/test/addons
+testdir = $(topobjdir)/_tests/xpcshell/browser/experiments/test/xpcshell
+
+misc:: $(call mkdir_deps,$(testdir))
+ $(EXIT_ON_ERROR) \
+ for dir in $(addondir)/*; do \
+ base=`basename $$dir`; \
+ (cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
+ done
diff --git a/browser/experiments/docs/index.rst b/browser/experiments/docs/index.rst
new file mode 100644
index 000000000..11e5d4faa
--- /dev/null
+++ b/browser/experiments/docs/index.rst
@@ -0,0 +1,13 @@
+=====================
+Telemetry Experiments
+=====================
+
+Telemetry Experiments is a feature of Firefox that allows the installation
+of add-ons called experiments to a subset of the Firefox population for
+the purposes of experimenting with changes and collecting data on specific
+aspects of application usage.
+
+.. toctree::
+ :maxdepth: 1
+
+ manifest
diff --git a/browser/experiments/docs/manifest.rst b/browser/experiments/docs/manifest.rst
new file mode 100644
index 000000000..d4fad5243
--- /dev/null
+++ b/browser/experiments/docs/manifest.rst
@@ -0,0 +1,429 @@
+.. _experiments_manifests:
+
+=====================
+Experiments Manifests
+=====================
+
+*Experiments Manifests* are documents that describe the set of active
+experiments a client may run.
+
+*Experiments Manifests* are fetched periodically by clients. When
+fetched, clients look at the experiments within the manifest and
+determine which experiments are applicable. If an experiment is
+applicable, the client may download and start the experiment.
+
+Manifest Format
+===============
+
+Manifests are JSON documents where the main element is an object.
+
+The *schema* of the object is versioned and defined by the presence
+of a top-level ``version`` property, whose integer value is the
+schema version used by that manifest. Each version is documented
+in the sections below.
+
+Version 1
+---------
+
+Version 1 is the original manifest format.
+
+The following properties may exist in the root object:
+
+experiments
+ An array of objects describing candidate experiments. The format of
+ these objects is documented below.
+
+ An array is used to create an explicit priority of experiments.
+ Experiments listed at the beginning of the array take priority over
+ experiments that follow.
+
+Experiments Objects
+^^^^^^^^^^^^^^^^^^^
+
+Each object in the ``experiments`` array may contain the following
+properties:
+
+id
+ (required) String identifier of this experiment. The identifier should
+ be treated as opaque by clients. It is used to uniquely identify an
+ experiment for all of time.
+
+xpiURL
+ (required) String URL of the XPI that implements this experiment.
+
+ If the experiment is activated, the client will download and install this
+ XPI.
+
+xpiHash
+ (required) String hash of the XPI that implements this experiment.
+
+ The value is composed of a hash identifier followed by a colon
+ followed by the hash value. e.g.
+ `sha1:f677428b9172e22e9911039aef03f3736e7f78a7`. `sha1` and `sha256`
+ are the two supported hashing mechanisms. The hash value is the hex
+ encoding of the binary hash.
+
+ When the client downloads the XPI for the experiment, it should compare
+ the hash of that XPI against this value. If the hashes don't match,
+ the client should not install the XPI.
+
+ Clients may also use this hash as a means of determining when an
+ experiment's XPI has changed and should be refreshed.
+
+startTime
+ Integer seconds since UNIX epoch that this experiment should
+ start. Clients should not start an experiment if *now()* is less than
+ this value.
+
+maxStartTime
+ (optional) Integer seconds since UNIX epoch after which this experiment
+ should no longer start.
+
+ Some experiments may wish to impose hard deadlines after which no new
+ clients should activate the experiment. This property may be used to
+ facilitate that.
+
+endTime
+ Integer seconds since UNIX epoch after which this experiment
+ should no longer run. Clients should cease an experiment when the current
+ time is beyond this value.
+
+maxActiveSeconds
+ Integer seconds defining the max wall time this experiment should be
+ active for.
+
+ The client should deactivate the experiment this many seconds after
+ initial activation.
+
+ This value only involves wall time, not browser activity or session time.
+
+appName
+ Array of application names this experiment should run on.
+
+ An application name comes from ``nsIXULAppInfo.name``. It is a value
+ like ``Firefox``, ``Fennec``, or `B2G`.
+
+ The client should compare its application name against the members of
+ this array. If a match is found, the experiment is applicable.
+
+minVersion
+ (optional) String version number of the minimum application version this
+ experiment should run on.
+
+ A version number is something like ``27.0.0`` or ``28``.
+
+ The client should compare its version number to this value. If the client's
+ version is greater or equal to this version (using a version-aware comparison
+ function), the experiment is applicable.
+
+ If this is not specified, there is no lower bound to versions this
+ experiment should run on.
+
+maxVersion
+ (optional) String version number of the maximum application version this
+ experiment should run on.
+
+ This is similar to ``minVersion`` except it sets the upper bound for
+ application versions.
+
+ If the client's version is less than or equal to this version, the
+ experiment is applicable.
+
+ If this is not specified, there is no upper bound to versions this
+ experiment should run on.
+
+version
+ (optional) Array of application versions this experiment should run on.
+
+ This is similar to ``minVersion`` and ``maxVersion`` except only a
+ whitelisted set of specific versions are allowed.
+
+ The client should compare its version to members of this array. If a match
+ is found, the experiment is applicable.
+
+minBuildID
+ (optional) String minimum Build ID this experiment should run on.
+
+ Build IDs are values like ``201402261424``.
+
+ The client should perform a string comparison of its Build ID against this
+ value. If its value is greater than or equal to this value, the experiment
+ is applicable.
+
+maxBuildID
+ (optional) String maximum Build ID this experiment should run on.
+
+ This is similar to ``minBuildID`` except it sets the upper bound
+ for Build IDs.
+
+ The client should perform a string comparison of its Build ID against
+ this value. If its value is less than or equal to this value, the
+ experiment is applicable.
+
+buildIDs
+ (optional) Array of Build IDs this experiment should run on.
+
+ This is similar to ``minBuildID`` and ``maxBuildID`` except only a
+ whitelisted set of Build IDs are considered.
+
+ The client should compare its Build ID to members of this array. If a
+ match is found, the experiment is applicable.
+
+os
+ (optional) Array of operating system identifiers this experiment should
+ run on.
+
+ Values for this array come from ``nsIXULRuntime.OS``.
+
+ The client will compare its operating system identifier to members
+ of this array. If a match is found, the experiment is applicable to the
+ client.
+
+channel
+ (optional) Array of release channel identifiers this experiment should run
+ on.
+
+ The client will compare its channel to members of this array. If a match
+ is found, the experiment is applicable.
+
+ If this property is not defined, the client should assume the experiment
+ is to run on all channels.
+
+locale
+ (optional) Array of locale identifiers this experiment should run on.
+
+ A locale identifier is a string like ``en-US`` or ``zh-CN`` and is
+ obtained by looking at
+ ``nsIXULChromeRegistry.getSelectedLocale("global")``.
+
+ The client should compare its locale identifier to members of this array.
+ If a match is found, the experiment is applicable.
+
+ If this property is not defined, the client should assume the experiment
+ is to run on all locales.
+
+sample
+ (optional) Decimal number indicating the sampling rate for this experiment.
+
+ This will contain a value between ``0.0`` and ``1.0``. The client should
+ generate a random decimal between ``0.0`` and ``1.0``. If the randomly
+ generated number is less than or equal to the value of this field, the
+ experiment is applicable.
+
+disabled
+ (optional) Boolean value indicating whether an experiment is disabled.
+
+ Normally, experiments are deactivated after a certain time has passed or
+ after the experiment itself determines it no longer needs to run (perhaps
+ it collected sufficient data already).
+
+ This property serves as a backup mechanism to remotely disable an
+ experiment before it was scheduled to be disabled. It can be used to
+ kill experiments that are found to be doing wrong or bad things or that
+ aren't useful.
+
+ If this property is not defined or is false, the client should assume
+ the experiment is active and a candidate for activation.
+
+frozen
+ (optional) Boolean value indicating this experiment is frozen and no
+ longer accepting new enrollments.
+
+ If a client sees a true value in this field, it should not attempt to
+ activate an experiment.
+
+jsfilter
+ (optional) JavaScript code that will be evaluated to determine experiment
+ applicability.
+
+ This property contains the string representation of JavaScript code that
+ will be evaluated in a sandboxed environment using JavaScript's
+ ``eval()``.
+
+ The string is expected to contain the definition of a JavaScript function
+ ``filter(context)``. This function receives as its argument an object
+ holding application state. See the section below for the definition of
+ this object.
+
+ The purpose of this property is to allow experiments to define complex
+ rules and logic for evaluating experiment applicability in a manner
+ that is privacy conscious and doesn't require the transmission of
+ excessive data.
+
+ The return value of this filter indicates whether the experiment is
+ applicable. Functions should return true if the experiment is
+ applicable.
+
+ If an experiment is not applicable, they should throw an Error whose
+ message contains the reason the experiment is not applicable. This
+ message may be logged and sent to remote servers, so it should not
+ contain private or otherwise sensitive data that wouldn't normally
+ be submitted.
+
+ If a falsey (or undefined) value is returned, the client should
+ assume the experiment is not applicable.
+
+ If this property is not defined, the client does not consider a custom
+ JavaScript filter function when determining whether an experiment is
+ applicable.
+
+JavaScript Filter Context Objects
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The object passed to a ``jsfilter`` ``filter()`` function contains the
+following properties:
+
+healthReportSubmissionEnabled
+ This property contains a boolean indicating whether Firefox Health
+ Report has its data submission flag enabled (whether Firefox Health
+ Report is sending data to remote servers).
+
+healthReportPayload
+ This property contains the current Firefox Health Report payload.
+
+ The payload format is documented at :ref:`healthreport_dataformat`.
+
+telemetryPayload
+ This property contains the current Telemetry payload.
+
+The evaluation sandbox for the JavaScript filters may be destroyed
+immediately after ``filter()`` returns. This function should not assume
+async code will finish.
+
+Experiment Applicability and Client Behavior
+============================================
+
+The point of an experiment manifest is to define which experiments are
+available and where and how to run them. This section explains those
+rules in more detail.
+
+Many of the properties in *Experiment Objects* are related to determining
+whether an experiment should run on a given client. This evaluation is
+performed client side.
+
+1. Multiple conditions in an experiment
+---------------------------------------
+
+If multiple conditions are defined for an experiment, the client should
+combine each condition with a logical *AND*: all conditions must be
+satisfied for an experiment to run. If one condition fails, the experiment
+is not applicable.
+
+2. Active experiment disappears from manifest
+---------------------------------------------
+
+If a specific experiment disappears from the manifest, the client should
+continue conducting an already-active experiment. Furthermore, the
+client should remember what the expiration events were for an experiment
+and honor them.
+
+The rationale here is that we want to prevent an accidental deletion
+or temporary failure on the server to inadvertantly deactivate
+supposed-to-be-active experiments. We also don't want premature deletion
+of an experiment from the manifest to result in indefinite activation
+periods.
+
+3. Inactive experiment disappears from manifest
+-----------------------------------------------
+
+If an inactive but scheduled-to-be-active experiment disappears from the
+manifest, the client should not activate the experiment.
+
+If that experiment reappears in the manifest, the client should not
+treat that experiment any differently than any other new experiment. Put
+another way, the fact an inactive experiment disappears and then
+reappears should not be significant.
+
+The rationale here is that server operators should have complete
+control of an inactive experiment up to it's go-live date.
+
+4. Re-evaluating applicability on manifest refresh
+--------------------------------------------------
+
+When an experiment manifest is refreshed or updated, the client should
+re-evaluate the applicability of each experiment therein.
+
+The rationale here is that the server may change the parameters of an
+experiment and want clients to pick those up.
+
+5. Activating a previously non-applicable experiment
+----------------------------------------------------
+
+If the conditions of an experiment change or the state of the client
+changes to allow an experiment to transition from previously
+non-applicable to applicable, the experiment should be activated.
+
+For example, if a client is running version 28 and the experiment
+initially requires version 29 or above, the client will not mark the
+experiment as applicable. But if the client upgrades to version 29 or if
+the manifest is updated to require 28 or above, the experiment will
+become applicable.
+
+6. Deactivating a previously active experiment
+----------------------------------------------
+
+If the conditions of an experiment change or the state of the client
+changes and an active experiment is no longer applicable, that
+experiment should be deactivated.
+
+7. Calculation of sampling-based applicability
+----------------------------------------------
+
+For calculating sampling-based applicability, the client will associate
+a random value between ``0.0`` and ``1.0`` for each observed experiment
+ID. This random value will be generated the first time sampling
+applicability is evaluated. This random value will be persisted and used
+in future applicability evaluations for this experiment.
+
+By saving and re-using the value, the client is able to reliably and
+consistently evaluate applicability, even if the sampling threshold
+in the manifest changes.
+
+Clients should retain the randomly-generated sampling value for
+experiments that no longer appear in a manifest for a period of at least
+30 days. The rationale is that if an experiment disappears and reappears
+from a manifest, the client will not have multiple opportunities to
+generate a random value that satisfies the sampling criteria.
+
+8. Incompatible version numbers
+-------------------------------
+
+If a client receives a manifest with a version number that it doesn't
+recognize, it should ignore the manifest.
+
+9. Usage of old manifests
+-------------------------
+
+If a client experiences an error fetching a manifest (server not
+available) or if the manifest is corrupt, not readable, or compatible,
+the client may use a previously-fetched (cached) manifest.
+
+10. Updating XPIs
+-----------------
+
+If the URL or hash of an active experiment's XPI changes, the client
+should fetch the new XPI, uninstall the old XPI, and install the new
+XPI.
+
+Examples
+========
+
+Here is an example manifest::
+
+ {
+ "version": 1,
+ "experiments": [
+ {
+ "id": "da9d7f4f-f3f9-4f81-bacd-6f0626ffa360",
+ "xpiURL": "https://experiments.mozilla.org/foo.xpi",
+ "xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099",
+ "startTime": 1393000000,
+ "endTime": 1394000000,
+ "appName": ["Firefox", "Fennec"],
+ "minVersion": "28",
+ "maxVersion": "30",
+ "os": ["windows", "linux", "osx"],
+ "jsfilter": "function filter(context) { return context.healthReportEnabled; }"
+ }
+ ]
+ }
diff --git a/browser/experiments/moz.build b/browser/experiments/moz.build
new file mode 100644
index 000000000..a11e4b725
--- /dev/null
+++ b/browser/experiments/moz.build
@@ -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/.
+
+HAS_MISC_RULE = True
+
+EXTRA_COMPONENTS += [
+ 'Experiments.manifest',
+ 'ExperimentsService.js',
+]
+
+EXTRA_JS_MODULES.experiments += [
+ 'Experiments.jsm',
+]
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
+
+SPHINX_TREES['experiments'] = 'docs'
diff --git a/browser/experiments/test/addons/experiment-1/install.rdf b/browser/experiments/test/addons/experiment-1/install.rdf
new file mode 100644
index 000000000..f9d70054a
--- /dev/null
+++ b/browser/experiments/test/addons/experiment-1/install.rdf
@@ -0,0 +1,16 @@
+<?xml version="1.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>test-experiment-1@tests.mozilla.org</em:id>
+ <em:version>1</em:version>
+ <em:type>128</em:type>
+
+ <!-- Front End MetaData -->
+ <em:name>Test experiment 1</em:name>
+ <em:description>Yet another experiment that experiments experimentally.</em:description>
+
+ </Description>
+</RDF>
diff --git a/browser/experiments/test/addons/experiment-1a/install.rdf b/browser/experiments/test/addons/experiment-1a/install.rdf
new file mode 100644
index 000000000..7806b11b1
--- /dev/null
+++ b/browser/experiments/test/addons/experiment-1a/install.rdf
@@ -0,0 +1,16 @@
+<?xml version="1.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>test-experiment-1@tests.mozilla.org</em:id>
+ <em:version>1.1</em:version>
+ <em:type>128</em:type>
+
+ <!-- Front End MetaData -->
+ <em:name>Test experiment 1.1</em:name>
+ <em:description>And yet another experiment that experiments experimentally.</em:description>
+
+ </Description>
+</RDF>
diff --git a/browser/experiments/test/addons/experiment-2/install.rdf b/browser/experiments/test/addons/experiment-2/install.rdf
new file mode 100644
index 000000000..69122c0ef
--- /dev/null
+++ b/browser/experiments/test/addons/experiment-2/install.rdf
@@ -0,0 +1,16 @@
+<?xml version="1.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>test-experiment-2@tests.mozilla.org</em:id>
+ <em:version>1</em:version>
+ <em:type>128</em:type>
+
+ <!-- Front End MetaData -->
+ <em:name>Test experiment 2</em:name>
+ <em:description>And yet another experiment that experiments experimentally.</em:description>
+
+ </Description>
+</RDF>
diff --git a/browser/experiments/test/addons/experiment-racybranch/bootstrap.js b/browser/experiments/test/addons/experiment-racybranch/bootstrap.js
new file mode 100644
index 000000000..e8278f50f
--- /dev/null
+++ b/browser/experiments/test/addons/experiment-racybranch/bootstrap.js
@@ -0,0 +1,35 @@
+/* exported startup, shutdown, install, uninstall */
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+
+var gStarted = false;
+
+function startup(data, reasonCode) {
+ if (gStarted) {
+ return;
+ }
+ gStarted = true;
+
+ // delay realstartup to trigger the race condition
+ Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager)
+ .mainThread.dispatch(realstartup, 0);
+}
+
+function realstartup() {
+ let experiments = Experiments.instance();
+ let experiment = experiments._getActiveExperiment();
+ if (experiment.branch) {
+ Cu.reportError("Found pre-existing branch: " + experiment.branch);
+ return;
+ }
+
+ let branch = "racy-set";
+ experiments.setExperimentBranch(experiment.id, branch)
+ .then(null, Cu.reportError);
+}
+
+function shutdown() { }
+function install() { }
+function uninstall() { }
diff --git a/browser/experiments/test/addons/experiment-racybranch/install.rdf b/browser/experiments/test/addons/experiment-racybranch/install.rdf
new file mode 100644
index 000000000..cebaede56
--- /dev/null
+++ b/browser/experiments/test/addons/experiment-racybranch/install.rdf
@@ -0,0 +1,16 @@
+<?xml version="1.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>test-experiment-racybranch@tests.mozilla.org</em:id>
+ <em:version>1</em:version>
+ <em:type>128</em:type>
+
+ <!-- Front End MetaData -->
+ <em:name>Test experiment racybranch</em:name>
+ <em:description>An experiment that sets the experiment branch in a potentially racy way.</em:description>
+
+ </Description>
+</RDF>
diff --git a/browser/experiments/test/xpcshell/.eslintrc.js b/browser/experiments/test/xpcshell/.eslintrc.js
new file mode 100644
index 000000000..1f540a05b
--- /dev/null
+++ b/browser/experiments/test/xpcshell/.eslintrc.js
@@ -0,0 +1,15 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ],
+
+ "rules": {
+ "no-unused-vars": ["error", {
+ "vars": "all",
+ "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
+ "args": "none"
+ }]
+ }
+};
diff --git a/browser/experiments/test/xpcshell/experiments_1.manifest b/browser/experiments/test/xpcshell/experiments_1.manifest
new file mode 100644
index 000000000..0401ea328
--- /dev/null
+++ b/browser/experiments/test/xpcshell/experiments_1.manifest
@@ -0,0 +1,19 @@
+{
+ "version": 1,
+ "experiments": [
+ {
+ "id": "test-experiment-1@tests.mozilla.org",
+ "xpiURL": "https://experiments.mozilla.org/foo.xpi",
+ "xpiHash": "sha1:cb1eb32b89d86d78b7326f416cf404548c5e0099",
+ "startTime": 1393000000,
+ "endTime": 1394000000,
+ "appName": ["Firefox", "Fennec"],
+ "minVersion": "28",
+ "maxVersion": "30",
+ "maxActiveSeconds": 60,
+ "os": ["windows", "linux", "osx"],
+ "channel": ["daily", "weekly", "nightly"],
+ "jsfilter": "function filter(context) { return true; }"
+ }
+ ]
+}
diff --git a/browser/experiments/test/xpcshell/head.js b/browser/experiments/test/xpcshell/head.js
new file mode 100644
index 000000000..ae356ea2d
--- /dev/null
+++ b/browser/experiments/test/xpcshell/head.js
@@ -0,0 +1,199 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* exported PREF_EXPERIMENTS_ENABLED, PREF_LOGGING_LEVEL, PREF_LOGGING_DUMP
+ PREF_MANIFEST_URI, PREF_FETCHINTERVAL, EXPERIMENT1_ID,
+ EXPERIMENT1_NAME, EXPERIMENT1_XPI_SHA1, EXPERIMENT1A_NAME,
+ EXPERIMENT1A_XPI_SHA1, EXPERIMENT2_ID, EXPERIMENT2_XPI_SHA1,
+ EXPERIMENT3_ID, EXPERIMENT4_ID, FAKE_EXPERIMENTS_1,
+ FAKE_EXPERIMENTS_2, gAppInfo, removeCacheFile, defineNow,
+ futureDate, dateToSeconds, loadAddonManager, promiseRestartManager,
+ startAddonManagerOnly, getExperimentAddons, replaceExperiments */
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://testing-common/AddonManagerTesting.jsm");
+Cu.import("resource://testing-common/AddonTestUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+
+const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
+const PREF_LOGGING_LEVEL = "experiments.logging.level";
+const PREF_LOGGING_DUMP = "experiments.logging.dump";
+const PREF_MANIFEST_URI = "experiments.manifest.uri";
+const PREF_FETCHINTERVAL = "experiments.manifest.fetchIntervalSeconds";
+const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
+
+function getExperimentPath(base) {
+ let p = do_get_cwd();
+ p.append(base);
+ return p.path;
+}
+
+function sha1File(path) {
+ let f = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ f.initWithPath(path);
+ let hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA1);
+
+ let is = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ is.init(f, -1, 0, 0);
+ hasher.updateFromStream(is, Math.pow(2, 32) - 1);
+ is.close();
+ let bytes = hasher.finish(false);
+
+ let rv = "";
+ for (let i = 0; i < bytes.length; i++) {
+ rv += ("0" + bytes.charCodeAt(i).toString(16)).substr(-2);
+ }
+ return rv;
+}
+
+const EXPERIMENT1_ID = "test-experiment-1@tests.mozilla.org";
+const EXPERIMENT1_XPI_NAME = "experiment-1.xpi";
+const EXPERIMENT1_NAME = "Test experiment 1";
+const EXPERIMENT1_PATH = getExperimentPath(EXPERIMENT1_XPI_NAME);
+const EXPERIMENT1_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1_PATH);
+
+
+const EXPERIMENT1A_XPI_NAME = "experiment-1a.xpi";
+const EXPERIMENT1A_NAME = "Test experiment 1.1";
+const EXPERIMENT1A_PATH = getExperimentPath(EXPERIMENT1A_XPI_NAME);
+const EXPERIMENT1A_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT1A_PATH);
+
+const EXPERIMENT2_ID = "test-experiment-2@tests.mozilla.org"
+const EXPERIMENT2_XPI_NAME = "experiment-2.xpi";
+const EXPERIMENT2_PATH = getExperimentPath(EXPERIMENT2_XPI_NAME);
+const EXPERIMENT2_XPI_SHA1 = "sha1:" + sha1File(EXPERIMENT2_PATH);
+
+const EXPERIMENT3_ID = "test-experiment-3@tests.mozilla.org";
+const EXPERIMENT4_ID = "test-experiment-4@tests.mozilla.org";
+
+const FAKE_EXPERIMENTS_1 = [
+ {
+ id: "id1",
+ name: "experiment1",
+ description: "experiment 1",
+ active: true,
+ detailUrl: "https://dummy/experiment1",
+ branch: "foo",
+ },
+];
+
+const FAKE_EXPERIMENTS_2 = [
+ {
+ id: "id2",
+ name: "experiment2",
+ description: "experiment 2",
+ active: false,
+ endDate: new Date(2014, 2, 11, 2, 4, 35, 42).getTime(),
+ detailUrl: "https://dummy/experiment2",
+ branch: null,
+ },
+ {
+ id: "id1",
+ name: "experiment1",
+ description: "experiment 1",
+ active: false,
+ endDate: new Date(2014, 2, 10, 0, 0, 0, 0).getTime(),
+ detailURL: "https://dummy/experiment1",
+ branch: null,
+ },
+];
+
+var gAppInfo = null;
+
+function removeCacheFile() {
+ let path = OS.Path.join(OS.Constants.Path.profileDir, "experiments.json");
+ return OS.File.remove(path);
+}
+
+function patchPolicy(policy, data) {
+ for (let key of Object.keys(data)) {
+ Object.defineProperty(policy, key, {
+ value: data[key],
+ writable: true,
+ });
+ }
+}
+
+function defineNow(policy, time) {
+ patchPolicy(policy, { now: () => new Date(time) });
+}
+
+function futureDate(date, offset) {
+ return new Date(date.getTime() + offset);
+}
+
+function dateToSeconds(date) {
+ return date.getTime() / 1000;
+}
+
+var gGlobalScope = this;
+function loadAddonManager() {
+ AddonTestUtils.init(gGlobalScope);
+ AddonTestUtils.overrideCertDB();
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+ return AddonTestUtils.promiseStartupManager();
+}
+
+const {
+ promiseRestartManager,
+} = AddonTestUtils;
+
+// Starts the addon manager without creating app info. We can't directly use
+// |loadAddonManager| defined above in test_conditions.js as it would make the test fail.
+function startAddonManagerOnly() {
+ let addonManager = Cc["@mozilla.org/addons/integration;1"]
+ .getService(Ci.nsIObserver)
+ .QueryInterface(Ci.nsITimerCallback);
+ addonManager.observe(null, "addons-startup", null);
+}
+
+function getExperimentAddons(previous=false) {
+ let deferred = Promise.defer();
+
+ AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+ if (previous) {
+ deferred.resolve(addons);
+ } else {
+ deferred.resolve(addons.filter(a => !a.appDisabled));
+ }
+ });
+
+ return deferred.promise;
+}
+
+function createAppInfo(ID="xpcshell@tests.mozilla.org", name="XPCShell",
+ version="1.0", platformVersion="1.0") {
+ AddonTestUtils.createAppInfo(ID, name, version, platformVersion);
+ gAppInfo = AddonTestUtils.appInfo;
+}
+
+/**
+ * Replace the experiments on an Experiments with a new list.
+ *
+ * This monkeypatches getExperiments(). It doesn't monkeypatch the internal
+ * experiments list. So its utility is not as great as it could be.
+ */
+function replaceExperiments(experiment, list) {
+ Object.defineProperty(experiment, "getExperiments", {
+ writable: true,
+ value: () => {
+ return Promise.resolve(list);
+ },
+ });
+}
+
+// Experiments require Telemetry to be enabled, and that's not true for debug
+// builds. Let's just enable it here instead of going through each test.
+Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
diff --git a/browser/experiments/test/xpcshell/test_activate.js b/browser/experiments/test/xpcshell/test_activate.js
new file mode 100644
index 000000000..60deafbfb
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_activate.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+
+const SEC_IN_ONE_DAY = 24 * 60 * 60;
+const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
+
+var gHttpServer = null;
+var gHttpRoot = null;
+var gPolicy = null;
+
+function ManifestEntry(data) {
+ this.id = data.id || EXPERIMENT1_ID;
+ this.xpiURL = data.xpiURL || gHttpRoot + EXPERIMENT1_XPI_NAME;
+ this.xpiHash = data.xpiHash || EXPERIMENT1_XPI_SHA1;
+ this.appName = data.appName || ["XPCShell"];
+ this.channel = data.appName || ["nightly"];
+ this.startTime = data.startTime || new Date(2010, 0, 1, 12).getTime() / 1000;
+ this.endTime = data.endTime || new Date(9001, 0, 1, 12).getTime() / 1000;
+ this.maxActiveSeconds = data.maxActiveSeconds || 5 * SEC_IN_ONE_DAY;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ loadAddonManager();
+ gPolicy = new Experiments.Policy();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gHttpServer.registerDirectory("/", do_get_cwd());
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ });
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+});
+
+function isApplicable(experiment) {
+ let deferred = Promise.defer();
+ experiment.isApplicable().then(
+ result => deferred.resolve({ applicable: true, reason: null }),
+ reason => deferred.resolve({ applicable: false, reason: reason })
+ );
+
+ return deferred.promise;
+}
+
+add_task(function* test_startStop() {
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 30 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 60 * MS_IN_ONE_DAY);
+ let manifestData = new ManifestEntry({
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ });
+ let experiment = new Experiments.ExperimentEntry(gPolicy);
+ experiment.initFromManifestData(manifestData);
+
+ // We need to associate it with the singleton so the onInstallStarted
+ // Addon Manager listener will know about it.
+ Experiments.instance()._experiments = new Map();
+ Experiments.instance()._experiments.set(experiment.id, experiment);
+
+ let result;
+
+ defineNow(gPolicy, baseDate);
+ result = yield isApplicable(experiment);
+ Assert.equal(result.applicable, false, "Experiment should not be applicable.");
+ Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
+
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
+
+ defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY));
+ result = yield isApplicable(experiment);
+ Assert.equal(result.applicable, true, "Experiment should now be applicable.");
+ Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
+
+ let changes = yield experiment.start();
+ Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
+ addons = yield getExperimentAddons();
+ Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
+ Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
+ Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
+ Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
+ Assert.ok(addons[0].isActive, "The add-on is active.");
+
+ changes = yield experiment.stop();
+ Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on was uninstalled.");
+ addons = yield getExperimentAddons();
+ Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
+ Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager.");
+
+ changes = yield experiment.start();
+ Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
+ addons = yield getExperimentAddons();
+ Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
+ Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
+ Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
+ Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
+ Assert.ok(addons[0].isActive, "The add-on is active.");
+
+ result = yield experiment.shouldStop();
+ Assert.equal(result.shouldStop, false, "shouldStop should be false.");
+ Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "Experiment still in add-ons manager.");
+ Assert.ok(addons[0].isActive, "The add-on is still active.");
+
+ defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
+ result = yield experiment.shouldStop();
+ Assert.equal(result.shouldStop, true, "shouldStop should now be true.");
+ changes = yield experiment.stop();
+ Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on should be uninstalled.");
+ Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");
+
+ // Ensure hash validation works.
+ // We set an incorrect hash and expect the install to fail.
+ experiment._manifestData.xpiHash = "sha1:41014dcc66b4dcedcd973491a1530a32f0517d8a";
+ let errored = false;
+ try {
+ yield experiment.start();
+ } catch (ex) {
+ errored = true;
+ }
+ Assert.ok(experiment._failedStart, "Experiment failed to start.");
+ Assert.ok(errored, "start() threw an exception.");
+
+ // Make sure "ignore hashes" mode works.
+ gPolicy.ignoreHashes = true;
+ changes = yield experiment.start();
+ Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL);
+ yield experiment.stop();
+ gPolicy.ignoreHashes = false;
+});
diff --git a/browser/experiments/test/xpcshell/test_api.js b/browser/experiments/test/xpcshell/test_api.js
new file mode 100644
index 000000000..9f0112570
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_api.js
@@ -0,0 +1,1647 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://testing-common/AddonManagerTesting.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
+ "resource:///modules/experiments/Experiments.jsm");
+
+const MANIFEST_HANDLER = "manifests/handler";
+
+const SEC_IN_ONE_DAY = 24 * 60 * 60;
+const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
+
+var gHttpServer = null;
+var gHttpRoot = null;
+var gDataRoot = null;
+var gPolicy = null;
+var gManifestObject = null;
+var gManifestHandlerURI = null;
+var gTimerScheduleOffset = -1;
+
+function uninstallExperimentAddons() {
+ return Task.spawn(function* () {
+ let addons = yield getExperimentAddons();
+ for (let a of addons) {
+ yield AddonManagerTesting.uninstallAddonByID(a.id);
+ }
+ });
+}
+
+function testCleanup(experimentsInstance) {
+ return Task.spawn(function* () {
+ yield promiseRestartManager();
+ yield uninstallExperimentAddons();
+ yield removeCacheFile();
+ });
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ loadAddonManager();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gDataRoot = gHttpRoot + "data/";
+ gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
+ gHttpServer.registerDirectory("/data/", do_get_cwd());
+ gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write(JSON.stringify(gManifestObject));
+ response.processAsync();
+ response.finish();
+ });
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
+ Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
+
+ gPolicy = new Experiments.Policy();
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ oneshotTimer: (callback, timeout, thisObj, name) => gTimerScheduleOffset = timeout,
+ });
+});
+
+add_task(function* test_contract() {
+ Cc["@mozilla.org/browser/experiments-service;1"].getService();
+});
+
+// Test basic starting and stopping of experiments.
+
+add_task(function* test_getExperiments() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
+ let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
+ let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ startTime: dateToSeconds(startDate2),
+ endTime: dateToSeconds(endDate2),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate1),
+ endTime: dateToSeconds(endDate1),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ // Data to compare the result of Experiments.getExperiments() against.
+
+ let experimentListData = [
+ {
+ id: EXPERIMENT2_ID,
+ name: "Test experiment 2",
+ description: "And yet another experiment that experiments experimentally.",
+ },
+ {
+ id: EXPERIMENT1_ID,
+ name: EXPERIMENT1_NAME,
+ description: "Yet another experiment that experiments experimentally.",
+ },
+ ];
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+ // Use updateManifest() to provide for coverage of that path.
+
+ let now = baseDate;
+ gTimerScheduleOffset = -1;
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ Assert.equal(experiments.getActiveExperimentID(), null,
+ "getActiveExperimentID should return null");
+
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
+
+ try {
+ yield experiments.getExperimentBranch();
+ Assert.ok(false, "getExperimentBranch should fail with no experiment");
+ }
+ catch (e) {
+ Assert.ok(true, "getExperimentBranch correctly threw");
+ }
+
+ // Trigger update, clock set for experiment 1 to start.
+
+ now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
+ gTimerScheduleOffset = -1;
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
+ "getActiveExperimentID should return the active experiment1");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "An experiment add-on was installed.");
+
+ experimentListData[1].active = true;
+ experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
+ for (let k of Object.keys(experimentListData[1])) {
+ Assert.equal(experimentListData[1][k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ let b = yield experiments.getExperimentBranch();
+ Assert.strictEqual(b, null, "getExperimentBranch should return null by default");
+
+ b = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
+ Assert.strictEqual(b, null, "getExperimentsBranch should return null (with id)");
+
+ yield experiments.setExperimentBranch(EXPERIMENT1_ID, "foo");
+ b = yield experiments.getExperimentBranch();
+ Assert.strictEqual(b, "foo", "getExperimentsBranch should return the set value");
+
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ Assert.equal(gTimerScheduleOffset, 10 * MS_IN_ONE_DAY,
+ "Experiment re-evaluation should have been scheduled correctly.");
+
+ // Trigger update, clock set for experiment 1 to stop.
+
+ now = futureDate(endDate1, 1000);
+ gTimerScheduleOffset = -1;
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ Assert.equal(experiments.getActiveExperimentID(), null,
+ "getActiveExperimentID should return null again");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
+
+ experimentListData[1].active = false;
+ experimentListData[1].endDate = now.getTime();
+ for (let k of Object.keys(experimentListData[1])) {
+ Assert.equal(experimentListData[1][k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ Assert.equal(gTimerScheduleOffset, startDate2 - now,
+ "Experiment re-evaluation should have been scheduled correctly.");
+
+ // Trigger update, clock set for experiment 2 to start.
+ // Use notify() to provide for coverage of that path.
+
+ now = startDate2;
+ gTimerScheduleOffset = -1;
+ defineNow(gPolicy, now);
+
+ yield experiments.notify();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT2_ID,
+ "getActiveExperimentID should return the active experiment2");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "An experiment add-on is installed.");
+
+ experimentListData[0].active = true;
+ experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
+ for (let i=0; i<experimentListData.length; ++i) {
+ let entry = experimentListData[i];
+ for (let k of Object.keys(entry)) {
+ Assert.equal(entry[k], list[i][k],
+ "Entry " + i + " - Property '" + k + "' should match reference data.");
+ }
+ }
+
+ Assert.equal(gTimerScheduleOffset, 10 * MS_IN_ONE_DAY,
+ "Experiment re-evaluation should have been scheduled correctly.");
+
+ // Trigger update, clock set for experiment 2 to stop.
+
+ now = futureDate(startDate2, 10 * MS_IN_ONE_DAY + 1000);
+ gTimerScheduleOffset = -1;
+ defineNow(gPolicy, now);
+ yield experiments.notify();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ Assert.equal(experiments.getActiveExperimentID(), null,
+ "getActiveExperimentID should return null again2");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "No experiments add-ons are installed.");
+
+ experimentListData[0].active = false;
+ experimentListData[0].endDate = now.getTime();
+ for (let i=0; i<experimentListData.length; ++i) {
+ let entry = experimentListData[i];
+ for (let k of Object.keys(entry)) {
+ Assert.equal(entry[k], list[i][k],
+ "Entry " + i + " - Property '" + k + "' should match reference data.");
+ }
+ }
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+add_task(function* test_getActiveExperimentID() {
+ // Check that getActiveExperimentID returns the correct result even
+ // after .uninit()
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
+ let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate1),
+ endTime: dateToSeconds(endDate1),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
+ gTimerScheduleOffset = -1;
+ defineNow(gPolicy, now);
+
+ let experiments = new Experiments.Experiments(gPolicy);
+ yield experiments.updateManifest();
+
+ Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
+ "getActiveExperimentID should return the active experiment1");
+
+ yield promiseRestartManager();
+ Assert.equal(experiments.getActiveExperimentID(), EXPERIMENT1_ID,
+ "getActiveExperimentID should return the active experiment1 after uninit()");
+
+ yield testCleanup(experiments);
+});
+
+// Test that we handle the experiments addon already being
+// installed properly.
+// We should just pave over them.
+
+add_task(function* test_addonAlreadyInstalled() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for the experiment to start.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "1 add-on is installed.");
+
+ // Install conflicting addon.
+
+ yield AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "1 add-on is installed.");
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should still have 1 entry.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+add_task(function* test_lastActiveToday() {
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ replaceExperiments(experiments, FAKE_EXPERIMENTS_1);
+
+ let e = yield experiments.getExperiments();
+ Assert.equal(e.length, 1, "Monkeypatch successful.");
+ Assert.equal(e[0].id, "id1", "ID looks sane");
+ Assert.ok(e[0].active, "Experiment is active.");
+
+ let lastActive = yield experiments.lastActiveToday();
+ Assert.equal(e[0], lastActive, "Last active object is expected.");
+
+ replaceExperiments(experiments, FAKE_EXPERIMENTS_2);
+ e = yield experiments.getExperiments();
+ Assert.equal(e.length, 2, "Monkeypatch successful.");
+
+ defineNow(gPolicy, e[0].endDate);
+
+ lastActive = yield experiments.lastActiveToday();
+ Assert.ok(lastActive, "Have a last active experiment");
+ Assert.equal(lastActive, e[0], "Last active object is expected.");
+
+ yield testCleanup(experiments);
+});
+
+// Test explicitly disabling experiments.
+
+add_task(function* test_disableExperiment() {
+ // Dates this test is based on.
+
+ let startDate = new Date(2004, 10, 9, 12);
+ let endDate = futureDate(startDate, 100 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ // Data to compare the result of Experiments.getExperiments() against.
+
+ let experimentInfo = {
+ id: EXPERIMENT1_ID,
+ name: EXPERIMENT1_NAME,
+ description: "Yet another experiment that experiments experimentally.",
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set for the experiment to start.
+
+ let now = futureDate(startDate, 5 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+
+ experimentInfo.active = true;
+ experimentInfo.endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
+ for (let k of Object.keys(experimentInfo)) {
+ Assert.equal(experimentInfo[k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ // Test disabling the experiment.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.disableExperiment("foo");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+
+ experimentInfo.active = false;
+ experimentInfo.endDate = now.getTime();
+ for (let k of Object.keys(experimentInfo)) {
+ Assert.equal(experimentInfo[k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ // Test that updating the list doesn't re-enable it.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+
+ for (let k of Object.keys(experimentInfo)) {
+ Assert.equal(experimentInfo[k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ yield testCleanup(experiments);
+});
+
+add_task(function* test_disableExperimentsFeature() {
+ // Dates this test is based on.
+
+ let startDate = new Date(2004, 10, 9, 12);
+ let endDate = futureDate(startDate, 100 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ // Data to compare the result of Experiments.getExperiments() against.
+
+ let experimentInfo = {
+ id: EXPERIMENT1_ID,
+ name: EXPERIMENT1_NAME,
+ description: "Yet another experiment that experiments experimentally.",
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+ Assert.equal(experiments.enabled, true, "Experiments feature should be enabled.");
+
+ // Trigger update, clock set for the experiment to start.
+
+ let now = futureDate(startDate, 5 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+
+ experimentInfo.active = true;
+ experimentInfo.endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
+ for (let k of Object.keys(experimentInfo)) {
+ Assert.equal(experimentInfo[k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ // Test disabling experiments.
+
+ experiments._toggleExperimentsEnabled(false);
+ yield experiments.notify();
+ Assert.equal(experiments.enabled, false, "Experiments feature should be disabled now.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+
+ experimentInfo.active = false;
+ experimentInfo.endDate = now.getTime();
+ for (let k of Object.keys(experimentInfo)) {
+ Assert.equal(experimentInfo[k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ // Test that updating the list doesn't re-enable it.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ try {
+ yield experiments.updateManifest();
+ } catch (e) {
+ // Exception expected, the feature is disabled.
+ }
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+
+ for (let k of Object.keys(experimentInfo)) {
+ Assert.equal(experimentInfo[k], list[0][k],
+ "Property " + k + " should match reference data.");
+ }
+
+ yield testCleanup(experiments);
+});
+
+// Test that after a failed experiment install:
+// * the next applicable experiment gets installed
+// * changing the experiments data later triggers re-evaluation
+
+add_task(function* test_installFailure() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ // Data to compare the result of Experiments.getExperiments() against.
+
+ let experimentListData = [
+ {
+ id: EXPERIMENT1_ID,
+ name: EXPERIMENT1_NAME,
+ description: "Yet another experiment that experiments experimentally.",
+ },
+ {
+ id: EXPERIMENT2_ID,
+ name: "Test experiment 2",
+ description: "And yet another experiment that experiments experimentally.",
+ },
+ ];
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for experiment 1 & 2 to start,
+ // invalid hash for experiment 1.
+ // Order in the manifest matters, so we should start experiment 1,
+ // fail to install it & start experiment 2 instead.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].xpiHash = "sha1:0000000000000000000000000000000000000000";
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT2_ID, "Experiment 2 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 2 should be active.");
+
+ // Trigger update, clock set for experiment 2 to stop.
+
+ now = futureDate(now, 20 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ experimentListData[0].active = false;
+ experimentListData[0].endDate = now;
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT2_ID, "Experiment 2 should be the sole entry.");
+ Assert.equal(list[0].active, false, "Experiment should not be active.");
+
+ // Trigger update with a fixed entry for experiment 1,
+ // which should get re-evaluated & started now.
+
+ now = futureDate(now, 20 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].xpiHash = EXPERIMENT1_XPI_SHA1;
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ experimentListData[0].active = true;
+ experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries now.");
+
+ for (let i=0; i<experimentListData.length; ++i) {
+ let entry = experimentListData[i];
+ for (let k of Object.keys(entry)) {
+ Assert.equal(entry[k], list[i][k],
+ "Entry " + i + " - Property '" + k + "' should match reference data.");
+ }
+ }
+
+ yield testCleanup(experiments);
+});
+
+// Test that after an experiment was disabled by user action,
+// the experiment is not activated again if manifest data changes.
+
+add_task(function* test_userDisabledAndUpdated() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for experiment 1 to start.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+ let todayActive = yield experiments.lastActiveToday();
+ Assert.ok(todayActive, "Last active for today reports a value.");
+ Assert.equal(todayActive.id, list[0].id, "The entry is what we expect.");
+
+ // Explicitly disable an experiment.
+
+ now = futureDate(now, 20 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.disableExperiment("foo");
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, false, "Experiment should not be active anymore.");
+ todayActive = yield experiments.lastActiveToday();
+ Assert.ok(todayActive, "Last active for today still returns a value.");
+ Assert.equal(todayActive.id, list[0].id, "The ID is still the same.");
+
+ // Trigger an update with a faked change for experiment 1.
+
+ now = futureDate(now, 20 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ experiments._experiments.get(EXPERIMENT1_ID)._manifestData.xpiHash =
+ "sha1:0000000000000000000000000000000000000000";
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, expectedObserverFireCount,
+ "Experiments observer should not have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, false, "Experiment should still be inactive.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// Test that changing the hash for an active experiments triggers an
+// update for it.
+
+add_task(function* test_updateActiveExperiment() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ let todayActive = yield experiments.lastActiveToday();
+ Assert.equal(todayActive, null, "No experiment active today.");
+
+ // Trigger update, clock set for the experiment to start.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+ Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match.");
+ todayActive = yield experiments.lastActiveToday();
+ Assert.ok(todayActive, "todayActive() returns a value.");
+ Assert.equal(todayActive.id, list[0].id, "It returns the active experiment.");
+
+ // Trigger an update for the active experiment by changing it's hash (and xpi)
+ // in the manifest.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].xpiHash = EXPERIMENT1A_XPI_SHA1;
+ gManifestObject.experiments[0].xpiURL = gDataRoot + EXPERIMENT1A_XPI_NAME;
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should still be active.");
+ Assert.equal(list[0].name, EXPERIMENT1A_NAME, "Experiments name should have been updated.");
+ todayActive = yield experiments.lastActiveToday();
+ Assert.equal(todayActive.id, list[0].id, "last active today is still sane.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// Tests that setting the disable flag for an active experiment
+// stops it.
+
+add_task(function* test_disableActiveExperiment() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for the experiment to start.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+
+ // Trigger an update with the experiment being disabled.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].disabled = true;
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, false, "Experiment 1 should be disabled.");
+
+ // Check that the experiment stays disabled.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ delete gManifestObject.experiments[0].disabled;
+ yield experiments.updateManifest();
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, false, "Experiment 1 should still be disabled.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// Test that:
+// * setting the frozen flag for a not-yet-started experiment keeps
+// it from starting
+// * after a removing the frozen flag, the experiment can still start
+
+add_task(function* test_freezePendingExperiment() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for the experiment to start but frozen.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].frozen = true;
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, expectedObserverFireCount,
+ "Experiments observer should have not been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should have no entries yet.");
+
+ // Trigger an update with the experiment not being frozen anymore.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ delete gManifestObject.experiments[0].frozen;
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active now.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// Test that setting the frozen flag for an active experiment doesn't
+// stop it.
+
+add_task(function* test_freezeActiveExperiment() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for the experiment to start.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+ Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match.");
+
+ // Trigger an update with the experiment being disabled.
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].frozen = true;
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should still be active.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// Test that removing an active experiment from the manifest doesn't
+// stop it.
+
+add_task(function* test_removeActiveExperiment() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+ let startDate2 = futureDate(baseDate, 20000 * MS_IN_ONE_DAY);
+ let endDate2 = futureDate(baseDate, 30000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ startTime: dateToSeconds(startDate2),
+ endTime: dateToSeconds(endDate2),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for the experiment to start.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+ Assert.equal(list[0].name, EXPERIMENT1_NAME, "Experiments name should match.");
+
+ // Trigger an update with experiment 1 missing from the manifest
+
+ now = futureDate(now, 1 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].frozen = true;
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should still be active.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// Test that we correctly handle experiment start & install failures.
+
+add_task(function* test_invalidUrl() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME + ".invalid",
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: 0,
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set for the experiment to start.
+
+ let now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gTimerScheduleOffset = null;
+
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ Assert.equal(gTimerScheduleOffset, null, "No new timer should have been scheduled.");
+
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// Test that we handle it properly when active experiment addons are being
+// uninstalled.
+
+add_task(function* test_unexpectedUninstall() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Trigger update, clock set for the experiment to start.
+
+ now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, true, "Experiment 1 should be active.");
+
+ // Uninstall the addon through the addon manager instead of stopping it through
+ // the experiments API.
+
+ yield AddonManagerTesting.uninstallAddonByID(EXPERIMENT1_ID);
+ yield experiments._mainTask;
+
+ yield experiments.notify();
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.equal(list[0].active, false, "Experiment 1 should not be active anymore.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield testCleanup(experiments);
+});
+
+// If the Addon Manager knows of an experiment that we don't, it should get
+// uninstalled.
+add_task(function* testUnknownExperimentsUninstalled() {
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are present.");
+
+ // Simulate us not listening.
+ experiments._unregisterWithAddonManager();
+ yield AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
+ experiments._registerWithAddonManager();
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "Experiment 1 installed via AddonManager");
+
+ // Simulate no known experiments.
+ gManifestObject = {
+ "version": 1,
+ experiments: [],
+ };
+
+ yield experiments.updateManifest();
+ let fromManifest = yield experiments.getExperiments();
+ Assert.equal(fromManifest.length, 0, "No experiments known in manifest.");
+
+ // And the unknown add-on should be gone.
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Experiment 1 was uninstalled.");
+
+ yield testCleanup(experiments);
+});
+
+// If someone else installs an experiment add-on, we detect and stop that.
+add_task(function* testForeignExperimentInstall() {
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [],
+ };
+
+ yield experiments.init();
+
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Precondition: No experiment add-ons present.");
+
+ let failed = false;
+ try {
+ yield AddonManagerTesting.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
+ } catch (ex) {
+ failed = true;
+ }
+ Assert.ok(failed, "Add-on install should not have completed successfully");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Add-on install should have been cancelled.");
+
+ yield testCleanup(experiments);
+});
+
+// Experiment add-ons will be disabled after Addon Manager restarts. Ensure
+// we enable them automatically.
+add_task(function* testEnabledAfterRestart() {
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: gPolicy.now().getTime() / 1000 - 60,
+ endTime: gPolicy.now().getTime() / 1000 + 60,
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
+
+ yield experiments.updateManifest();
+ let fromManifest = yield experiments.getExperiments();
+ Assert.equal(fromManifest.length, 1, "A single experiment is known.");
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
+ Assert.ok(addons[0].isActive, "That experiment is active.");
+
+ dump("Restarting Addon Manager\n");
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "The experiment is still there after restart.");
+ Assert.ok(addons[0].userDisabled, "But it is disabled.");
+ Assert.equal(addons[0].isActive, false, "And not active.");
+
+ yield experiments.updateManifest();
+ Assert.ok(addons[0].isActive, "It activates when the manifest is evaluated.");
+
+ yield testCleanup(experiments);
+});
+
+// If experiment add-ons were ever started, maxStartTime shouldn't be evaluated
+// anymore. Ensure that if maxStartTime is passed but experiment has started
+// already, maxStartTime does not cause deactivation.
+
+add_task(function* testMaxStartTimeEvaluation() {
+
+ // Dates the following tests are based on.
+
+ let startDate = new Date(2014, 5, 1, 12);
+ let now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
+ let maxStartDate = futureDate(startDate, 100 * MS_IN_ONE_DAY);
+ let endDate = futureDate(startDate, 1000 * MS_IN_ONE_DAY);
+
+ defineNow(gPolicy, now);
+
+ // The manifest data we test with.
+ // We set a value for maxStartTime.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate),
+ endTime: dateToSeconds(endDate),
+ maxActiveSeconds: 1000 * SEC_IN_ONE_DAY,
+ maxStartTime: dateToSeconds(maxStartDate),
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
+
+ yield experiments.updateManifest();
+ let fromManifest = yield experiments.getExperiments();
+ Assert.equal(fromManifest.length, 1, "A single experiment is known.");
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
+ Assert.ok(addons[0].isActive, "That experiment is active.");
+
+ dump("Setting current time to maxStartTime + 100 days and reloading manifest\n");
+ now = futureDate(maxStartDate, 100 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ yield experiments.updateManifest();
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "The experiment is still there.");
+ Assert.ok(addons[0].isActive, "It is still active.");
+
+ yield testCleanup(experiments);
+});
+
+// Test coverage for an add-on uninstall disabling the experiment and that it stays
+// disabled over restarts.
+add_task(function* test_foreignUninstallAndRestart() {
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: gPolicy.now().getTime() / 1000 - 60,
+ endTime: gPolicy.now().getTime() / 1000 + 60,
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Precondition: No experiment add-ons installed.");
+
+ yield experiments.updateManifest();
+ let experimentList = yield experiments.getExperiments();
+ Assert.equal(experimentList.length, 1, "A single experiment is known.");
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "A single experiment add-on is installed.");
+ Assert.ok(addons[0].isActive, "That experiment is active.");
+
+ yield AddonManagerTesting.uninstallAddonByID(EXPERIMENT1_ID);
+ yield experiments._mainTask;
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Experiment add-on should have been removed.");
+
+ experimentList = yield experiments.getExperiments();
+ Assert.equal(experimentList.length, 1, "A single experiment is known.");
+ Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.ok(!experimentList[0].active, "Experiment 1 should not be active anymore.");
+
+ // Fake restart behaviour.
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+ yield experiments.updateManifest();
+
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "No experiment add-ons installed.");
+
+ experimentList = yield experiments.getExperiments();
+ Assert.equal(experimentList.length, 1, "A single experiment is known.");
+ Assert.equal(experimentList[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
+ Assert.ok(!experimentList[0].active, "Experiment 1 should not be active.");
+
+ yield testCleanup(experiments);
+});
diff --git a/browser/experiments/test/xpcshell/test_cache.js b/browser/experiments/test/xpcshell/test_cache.js
new file mode 100644
index 000000000..4f2bce881
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_cache.js
@@ -0,0 +1,399 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
+ "resource:///modules/experiments/Experiments.jsm");
+
+const MANIFEST_HANDLER = "manifests/handler";
+
+const SEC_IN_ONE_DAY = 24 * 60 * 60;
+const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
+
+var gHttpServer = null;
+var gHttpRoot = null;
+var gDataRoot = null;
+var gPolicy = null;
+var gManifestObject = null;
+var gManifestHandlerURI = null;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ loadAddonManager();
+ yield removeCacheFile();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gDataRoot = gHttpRoot + "data/";
+ gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
+ gHttpServer.registerDirectory("/data/", do_get_cwd());
+ gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write(JSON.stringify(gManifestObject));
+ response.processAsync();
+ response.finish();
+ });
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
+ Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
+
+ gPolicy = new Experiments.Policy();
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ oneshotTimer: (callback, timeout, thisObj, name) => {},
+ });
+});
+
+function checkExperimentListsEqual(list, list2) {
+ Assert.equal(list.length, list2.length, "Lists should have the same length.")
+
+ for (let i=0; i<list.length; ++i) {
+ for (let k of Object.keys(list[i])) {
+ Assert.equal(list[i][k], list2[i][k],
+ "Field '" + k + "' should match for list entry " + i + ".");
+ }
+ }
+}
+
+function checkExperimentSerializations(experimentEntryIterator) {
+ for (let experiment of experimentEntryIterator) {
+ let experiment2 = new Experiments.ExperimentEntry(gPolicy);
+ let jsonStr = JSON.stringify(experiment.toJSON());
+ Assert.ok(experiment2.initFromCacheData(JSON.parse(jsonStr)),
+ "Should have initialized successfully from JSON serialization.");
+ Assert.equal(JSON.stringify(experiment), JSON.stringify(experiment2),
+ "Object stringifications should match.");
+ }
+}
+
+function validateCache(cachedExperiments, experimentIds) {
+ let cachedExperimentIds = new Set(cachedExperiments);
+ Assert.equal(cachedExperimentIds.size, experimentIds.length,
+ "The number of cached experiments does not match with the provided list");
+ for (let id of experimentIds) {
+ Assert.ok(cachedExperimentIds.has(id), "The cache must contain the experiment with id " + id);
+ }
+}
+
+// Set up an experiments instance and check if it is properly restored from cache.
+
+add_task(function* test_cache() {
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT3_ID,
+ xpiURL: "https://inval.id/foo.xpi",
+ xpiHash: "sha1:0000000000000000000000000000000000000000",
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ // Setup dates for the experiments.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDates = [];
+ let endDates = [];
+
+ for (let i=0; i<gManifestObject.experiments.length; ++i) {
+ let experiment = gManifestObject.experiments[i];
+ startDates.push(futureDate(baseDate, (50 + (150 * i)) * MS_IN_ONE_DAY));
+ endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY));
+ experiment.startTime = dateToSeconds(startDates[i]);
+ experiment.endTime = dateToSeconds(endDates[i]);
+ }
+
+ // Data to compare the result of Experiments.getExperiments() against.
+
+ let experimentListData = [
+ {
+ id: EXPERIMENT2_ID,
+ name: "Test experiment 2",
+ description: "And yet another experiment that experiments experimentally.",
+ },
+ {
+ id: EXPERIMENT1_ID,
+ name: EXPERIMENT1_NAME,
+ description: "Yet another experiment that experiments experimentally.",
+ },
+ ];
+
+ // Trigger update & re-init, clock set to before any activation.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+
+ let experiments = new Experiments.Experiments(gPolicy);
+ yield experiments.updateManifest();
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+ checkExperimentSerializations(experiments._experiments.values());
+
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+
+ yield experiments._run();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+ checkExperimentSerializations(experiments._experiments.values());
+
+ // Re-init, clock set for experiment 1 to start.
+
+ now = futureDate(startDates[0], 5 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+ yield experiments._run();
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+
+ experimentListData[1].active = true;
+ experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
+ checkExperimentListsEqual(experimentListData.slice(1), list);
+ checkExperimentSerializations(experiments._experiments.values());
+
+ let branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
+ Assert.strictEqual(branch, null);
+
+ yield experiments.setExperimentBranch(EXPERIMENT1_ID, "testbranch");
+ branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
+ Assert.strictEqual(branch, "testbranch");
+
+ // Re-init, clock set for experiment 1 to stop.
+
+ now = futureDate(now, 20 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+ yield experiments._run();
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+
+ experimentListData[1].active = false;
+ experimentListData[1].endDate = now.getTime();
+ checkExperimentListsEqual(experimentListData.slice(1), list);
+ checkExperimentSerializations(experiments._experiments.values());
+
+ branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID);
+ Assert.strictEqual(branch, "testbranch");
+
+ // Re-init, clock set for experiment 2 to start.
+
+ now = futureDate(startDates[1], 20 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+ yield experiments._run();
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
+
+ experimentListData[0].active = true;
+ experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY;
+ checkExperimentListsEqual(experimentListData, list);
+ checkExperimentSerializations(experiments._experiments.values());
+
+ // Re-init, clock set for experiment 2 to stop.
+
+ now = futureDate(now, 20 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+ yield experiments._run();
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
+
+ experimentListData[0].active = false;
+ experimentListData[0].endDate = now.getTime();
+ checkExperimentListsEqual(experimentListData, list);
+ checkExperimentSerializations(experiments._experiments.values());
+
+ // Cleanup.
+
+ yield experiments._toggleExperimentsEnabled(false);
+ yield promiseRestartManager();
+ yield removeCacheFile();
+});
+
+add_task(function* test_expiration() {
+ // The manifest data we test with.
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ maxActiveSeconds: 50 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ // The 3rd experiment will never run, so it's ok to use experiment's 2 data.
+ {
+ id: EXPERIMENT3_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ }
+ ],
+ };
+
+ // Data to compare the result of Experiments.getExperiments() against.
+ let experimentListData = [
+ {
+ id: EXPERIMENT2_ID,
+ name: "Test experiment 2",
+ description: "And yet another experiment that experiments experimentally.",
+ },
+ {
+ id: EXPERIMENT1_ID,
+ name: EXPERIMENT1_NAME,
+ description: "Yet another experiment that experiments experimentally.",
+ },
+ ];
+
+ // Setup dates for the experiments.
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDates = [];
+ let endDates = [];
+
+ for (let i=0; i<gManifestObject.experiments.length; ++i) {
+ let experiment = gManifestObject.experiments[i];
+ // Spread out experiments in time so that one experiment can end and expire while
+ // the next is still running.
+ startDates.push(futureDate(baseDate, (50 + (200 * i)) * MS_IN_ONE_DAY));
+ endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY));
+ experiment.startTime = dateToSeconds(startDates[i]);
+ experiment.endTime = dateToSeconds(endDates[i]);
+ }
+
+ let now = null;
+ let experiments = null;
+
+ let setDateAndRestartExperiments = new Task.async(function* (newDate) {
+ now = newDate;
+ defineNow(gPolicy, now);
+
+ yield promiseRestartManager();
+ experiments = new Experiments.Experiments(gPolicy);
+ yield experiments._run();
+ });
+
+ // Trigger update & re-init, clock set to before any activation.
+ now = baseDate;
+ defineNow(gPolicy, now);
+
+ experiments = new Experiments.Experiments(gPolicy);
+ yield experiments.updateManifest();
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ // Re-init, clock set for experiment 1 to start...
+ yield setDateAndRestartExperiments(startDates[0]);
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "The first experiment should have started.");
+
+ // ... init again, and set the clock so that the first experiment ends.
+ yield setDateAndRestartExperiments(endDates[0]);
+
+ // The experiment just ended, it should still be in the cache, but marked
+ // as finished.
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+
+ experimentListData[1].active = false;
+ experimentListData[1].endDate = now.getTime();
+ checkExperimentListsEqual(experimentListData.slice(1), list);
+ validateCache([...experiments._experiments.keys()], [EXPERIMENT1_ID, EXPERIMENT2_ID, EXPERIMENT3_ID]);
+
+ // Start the second experiment.
+ yield setDateAndRestartExperiments(startDates[1]);
+
+ // The experiments cache should contain the finished experiment and the
+ // one that's still running.
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
+
+ experimentListData[0].active = true;
+ experimentListData[0].endDate = now.getTime() + 50 * MS_IN_ONE_DAY;
+ checkExperimentListsEqual(experimentListData, list);
+
+ // Move the clock in the future, just 31 days after the start date of the second experiment,
+ // so that the cache for the first experiment expires and the second experiment is still running.
+ yield setDateAndRestartExperiments(futureDate(startDates[1], 31 * MS_IN_ONE_DAY));
+ validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]);
+
+ // Make sure that the expired experiment is not reported anymore.
+ let history = yield experiments.getExperiments();
+ Assert.equal(history.length, 1, "Experiments older than 180 days must be removed from the cache.");
+
+ // Test that we don't write expired experiments in the cache.
+ yield setDateAndRestartExperiments(now);
+ validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]);
+
+ // The first experiment should be expired and not in the cache, it ended more than
+ // 180 days ago. We should see the one still running in the cache.
+ history = yield experiments.getExperiments();
+ Assert.equal(history.length, 1, "Expired experiments must not be saved to cache.");
+ checkExperimentListsEqual(experimentListData.slice(0, 1), history);
+
+ // Test that experiments that are cached locally but never ran are removed from cache
+ // when they are removed from the manifest (this is cached data, not really history).
+ gManifestObject["experiments"] = gManifestObject["experiments"].slice(1, 1);
+ yield experiments.updateManifest();
+ validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID]);
+
+ // Cleanup.
+ yield experiments._toggleExperimentsEnabled(false);
+ yield promiseRestartManager();
+ yield removeCacheFile();
+});
diff --git a/browser/experiments/test/xpcshell/test_cacherace.js b/browser/experiments/test/xpcshell/test_cacherace.js
new file mode 100644
index 000000000..ff77cfdc4
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_cacherace.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+const MANIFEST_HANDLER = "manifests/handler";
+
+const SEC_IN_ONE_DAY = 24 * 60 * 60;
+const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
+
+var gHttpServer = null;
+var gHttpRoot = null;
+var gDataRoot = null;
+var gPolicy = null;
+var gManifestObject = null;
+var gManifestHandlerURI = null;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ loadAddonManager();
+ yield removeCacheFile();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gDataRoot = gHttpRoot + "data/";
+ gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
+ gHttpServer.registerDirectory("/data/", do_get_cwd());
+ gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write(JSON.stringify(gManifestObject));
+ response.processAsync();
+ response.finish();
+ });
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
+ Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
+
+ let ExperimentsScope = Cu.import("resource:///modules/experiments/Experiments.jsm");
+ let Experiments = ExperimentsScope.Experiments;
+
+ gPolicy = new Experiments.Policy();
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ delayCacheWrite: (promise) => {
+ return new Promise((resolve, reject) => {
+ promise.then(
+ (result) => { setTimeout(() => resolve(result), 500); },
+ (err) => { reject(err); }
+ );
+ });
+ },
+ });
+
+ let now = new Date(2014, 5, 1, 12);
+ defineNow(gPolicy, now);
+
+ let experimentName = "experiment-racybranch.xpi";
+ let experimentPath = getExperimentPath(experimentName);
+ let experimentHash = "sha1:" + sha1File(experimentPath);
+
+ gManifestObject = {
+ version: 1,
+ experiments: [
+ {
+ id: "test-experiment-racybranch@tests.mozilla.org",
+ xpiURL: gDataRoot + "experiment-racybranch.xpi",
+ xpiHash: experimentHash,
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ startTime: dateToSeconds(futureDate(now, -MS_IN_ONE_DAY)),
+ endTime: dateToSeconds(futureDate(now, MS_IN_ONE_DAY)),
+ },
+ ],
+ };
+
+ do_print("gManifestObject: " + JSON.stringify(gManifestObject));
+
+ // In order for the addon manager to work properly, we hack
+ // Experiments.instance which is used by the XPIProvider
+ let experiments = new Experiments.Experiments(gPolicy);
+ Assert.strictEqual(ExperimentsScope.gExperiments, null);
+ ExperimentsScope.gExperiments = experiments;
+
+ yield experiments.updateManifest();
+ let active = experiments._getActiveExperiment();
+ Assert.ok(active);
+ Assert.equal(active.branch, "racy-set");
+ Assert.ok(!experiments._dirty);
+});
diff --git a/browser/experiments/test/xpcshell/test_conditions.js b/browser/experiments/test/xpcshell/test_conditions.js
new file mode 100644
index 000000000..23c147fdb
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_conditions.js
@@ -0,0 +1,325 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm", this);
+
+const SEC_IN_ONE_DAY = 24 * 60 * 60;
+
+var gPolicy = null;
+
+function ManifestEntry(data) {
+ this.id = EXPERIMENT1_ID;
+ this.xpiURL = "http://localhost:1/dummy.xpi";
+ this.xpiHash = EXPERIMENT1_XPI_SHA1;
+ this.startTime = new Date(2010, 0, 1, 12).getTime() / 1000;
+ this.endTime = new Date(9001, 0, 1, 12).getTime() / 1000;
+ this.maxActiveSeconds = SEC_IN_ONE_DAY;
+ this.appName = ["XPCShell"];
+ this.channel = ["nightly"];
+
+ data = data || {};
+ for (let k of Object.keys(data)) {
+ this[k] = data[k];
+ }
+
+ if (!this.endTime) {
+ this.endTime = this.startTime + 5 * SEC_IN_ONE_DAY;
+ }
+}
+
+function applicableFromManifestData(data, policy) {
+ let manifestData = new ManifestEntry(data);
+ let entry = new Experiments.ExperimentEntry(policy);
+ entry.initFromManifestData(manifestData);
+ return entry.isApplicable();
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ createAppInfo();
+ do_get_profile();
+ startAddonManagerOnly();
+ yield TelemetryController.testSetup();
+ gPolicy = new Experiments.Policy();
+
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ locale: () => "en-US",
+ random: () => 0.5,
+ });
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+});
+
+function arraysEqual(a, b) {
+ if (a.length !== b.length) {
+ return false;
+ }
+
+ for (let i=0; i<a.length; ++i) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// This function exists solely to be .toSource()d
+const sanityFilter = function filter(c) {
+ if (c.telemetryEnvironment === undefined) {
+ throw Error("No .telemetryEnvironment");
+ }
+ if (c.telemetryEnvironment.build == undefined) {
+ throw Error("No .telemetryEnvironment.build");
+ }
+ return true;
+}
+
+// Utility function to generate build ID for previous/next date.
+function addDate(buildId, diff) {
+ let m = /^([0-9]{4})([0-9]{2})([0-9]{2})(.*)$/.exec(buildId);
+ if (!m) {
+ throw Error("Unsupported build ID: " + buildId);
+ }
+ let year = Number.parseInt(m[1], 10);
+ let month = Number.parseInt(m[2], 10);
+ let date = Number.parseInt(m[3], 10);
+ let remainingParts = m[4];
+
+ let d = new Date();
+ d.setUTCFullYear(year, month - 1, date);
+ d.setTime(d.getTime() + diff * 24 * 60 * 60 * 1000);
+
+ let yearStr = String(d.getUTCFullYear());
+ let monthStr = ("0" + String(d.getUTCMonth() + 1)).slice(-2);
+ let dateStr = ("0" + String(d.getUTCDate())).slice(-2);
+ return yearStr + monthStr + dateStr + remainingParts;
+}
+function prevDate(buildId) {
+ return addDate(buildId, -1);
+}
+function nextDate(buildId) {
+ return addDate(buildId, 1);
+}
+
+add_task(function* test_simpleFields() {
+ let testData = [
+ // "expected applicable?", failure reason or null, manifest data
+
+ // misc. environment
+
+ [false, ["appName"], {appName: []}],
+ [false, ["appName"], {appName: ["foo", gAppInfo.name + "-invalid"]}],
+ [true, null, {appName: ["not-an-app-name", gAppInfo.name]}],
+
+ [false, ["os"], {os: []}],
+ [false, ["os"], {os: ["42", "abcdef"]}],
+ [true, null, {os: [gAppInfo.OS, "plan9"]}],
+
+ [false, ["channel"], {channel: []}],
+ [false, ["channel"], {channel: ["foo", gPolicy.updatechannel() + "-invalid"]}],
+ [true, null, {channel: ["not-a-channel", gPolicy.updatechannel()]}],
+
+ [false, ["locale"], {locale: []}],
+ [false, ["locale"], {locale: ["foo", gPolicy.locale + "-invalid"]}],
+ [true, null, {locale: ["not-a-locale", gPolicy.locale()]}],
+
+ // version
+
+ [false, ["version"], {version: []}],
+ [false, ["version"], {version: ["-1", gAppInfo.version + "-invalid", "asdf", "0,4", "99.99", "0.1.1.1"]}],
+ [true, null, {version: ["99999999.999", "-1", gAppInfo.version]}],
+
+ [false, ["minVersion"], {minVersion: "1.0.1"}],
+ [true, null, {minVersion: "1.0b1"}],
+ [true, null, {minVersion: "1.0"}],
+ [true, null, {minVersion: "0.9"}],
+
+ [false, ["maxVersion"], {maxVersion: "0.1"}],
+ [false, ["maxVersion"], {maxVersion: "0.9.9"}],
+ [false, ["maxVersion"], {maxVersion: "1.0b1"}],
+ [true, ["maxVersion"], {maxVersion: "1.0"}],
+ [true, ["maxVersion"], {maxVersion: "1.7pre"}],
+
+ // build id
+
+ [false, ["buildIDs"], {buildIDs: []}],
+ [false, ["buildIDs"], {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID + "-invalid"]}],
+ [true, null, {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID]}],
+
+ [true, null, {minBuildID: prevDate(gAppInfo.platformBuildID)}],
+ [true, null, {minBuildID: gAppInfo.platformBuildID}],
+ [false, ["minBuildID"], {minBuildID: nextDate(gAppInfo.platformBuildID)}],
+
+ [false, ["maxBuildID"], {maxBuildID: prevDate(gAppInfo.platformBuildID)}],
+ [true, null, {maxBuildID: gAppInfo.platformBuildID}],
+ [true, null, {maxBuildID: nextDate(gAppInfo.platformBuildID)}],
+
+ // sample
+
+ [false, ["sample"], {sample: -1 }],
+ [false, ["sample"], {sample: 0.0}],
+ [false, ["sample"], {sample: 0.1}],
+ [true, null, {sample: 0.5}],
+ [true, null, {sample: 0.6}],
+ [true, null, {sample: 1.0}],
+ [true, null, {sample: 0.5}],
+
+ // experiment control
+
+ [false, ["disabled"], {disabled: true}],
+ [true, null, {disabled: false}],
+
+ [false, ["frozen"], {frozen: true}],
+ [true, null, {frozen: false}],
+
+ [false, null, {frozen: true, disabled: true}],
+ [false, null, {frozen: true, disabled: false}],
+ [false, null, {frozen: false, disabled: true}],
+ [true, null, {frozen: false, disabled: false}],
+
+ // jsfilter
+
+ [true, null, {jsfilter: "function filter(c) { return true; }"}],
+ [false, ["jsfilter-false"], {jsfilter: "function filter(c) { return false; }"}],
+ [true, null, {jsfilter: "function filter(c) { return 123; }"}], // truthy
+ [false, ["jsfilter-false"], {jsfilter: "function filter(c) { return ''; }"}], // falsy
+ [false, ["jsfilter-false"], {jsfilter: "function filter(c) { var a = []; }"}], // undefined
+ [false, ["jsfilter-threw", "some error"], {jsfilter: "function filter(c) { throw new Error('some error'); }"}],
+ [false, ["jsfilter-evalfailed"], {jsfilter: "123, this won't work"}],
+ [true, null, {jsfilter: "var filter = " + sanityFilter.toSource()}],
+ ];
+
+ for (let i=0; i<testData.length; ++i) {
+ let entry = testData[i];
+ let applicable;
+ let reason = null;
+
+ yield applicableFromManifestData(entry[2], gPolicy).then(
+ value => applicable = value,
+ value => {
+ applicable = false;
+ reason = value;
+ }
+ );
+
+ Assert.equal(applicable, entry[0],
+ "Experiment entry applicability should match for test "
+ + i + ": " + JSON.stringify(entry[2]));
+
+ let expectedReason = entry[1];
+ if (!applicable && expectedReason) {
+ Assert.ok(arraysEqual(reason, expectedReason),
+ "Experiment rejection reasons should match for test " + i + ". "
+ + "Got " + JSON.stringify(reason) + ", expected "
+ + JSON.stringify(expectedReason));
+ }
+ }
+});
+
+add_task(function* test_times() {
+ let now = new Date(2014, 5, 6, 12);
+ let nowSec = now.getTime() / 1000;
+ let testData = [
+ // "expected applicable?", rejection reason or null, fake now date, manifest data
+
+ // start time
+
+ [true, null, now,
+ {startTime: nowSec - 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {startTime: nowSec,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [false, "startTime", now,
+ {startTime: nowSec + 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+
+ // end time
+
+ [false, "endTime", now,
+ {startTime: nowSec - 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec - 10 * SEC_IN_ONE_DAY}],
+ [false, "endTime", now,
+ {startTime: nowSec - 5 * SEC_IN_ONE_DAY,
+ endTime: nowSec - 5 * SEC_IN_ONE_DAY}],
+
+ // max start time
+
+ [false, "maxStartTime", now,
+ {maxStartTime: nowSec - 15 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [false, "maxStartTime", now,
+ {maxStartTime: nowSec - 1 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [false, "maxStartTime", now,
+ {maxStartTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxStartTime: nowSec,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxStartTime: nowSec + 1 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+
+ // max active seconds
+
+ [true, null, now,
+ {maxActiveSeconds: 5 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxActiveSeconds: 15 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ [true, null, now,
+ {maxActiveSeconds: 20 * SEC_IN_ONE_DAY,
+ startTime: nowSec - 10 * SEC_IN_ONE_DAY,
+ endTime: nowSec + 10 * SEC_IN_ONE_DAY}],
+ ];
+
+ for (let i=0; i<testData.length; ++i) {
+ let entry = testData[i];
+ let applicable;
+ let reason = null;
+ defineNow(gPolicy, entry[2]);
+
+ yield applicableFromManifestData(entry[3], gPolicy).then(
+ value => applicable = value,
+ value => {
+ applicable = false;
+ reason = value;
+ }
+ );
+
+ Assert.equal(applicable, entry[0],
+ "Experiment entry applicability should match for test "
+ + i + ": " + JSON.stringify([entry[2], entry[3]]));
+ if (!applicable && entry[1]) {
+ Assert.equal(reason, entry[1], "Experiment rejection reason should match for test " + i);
+ }
+ }
+});
+
+add_task(function* test_shutdown() {
+ yield TelemetryController.testShutdown();
+});
diff --git a/browser/experiments/test/xpcshell/test_disableExperiments.js b/browser/experiments/test/xpcshell/test_disableExperiments.js
new file mode 100644
index 000000000..8441b922d
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_disableExperiments.js
@@ -0,0 +1,180 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://testing-common/AddonManagerTesting.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
+ "resource:///modules/experiments/Experiments.jsm");
+
+const MANIFEST_HANDLER = "manifests/handler";
+
+const SEC_IN_ONE_DAY = 24 * 60 * 60;
+const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
+
+var gHttpServer = null;
+var gHttpRoot = null;
+var gDataRoot = null;
+var gPolicy = null;
+var gManifestObject = null;
+var gManifestHandlerURI = null;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ loadAddonManager();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gDataRoot = gHttpRoot + "data/";
+ gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
+ gHttpServer.registerDirectory("/data/", do_get_cwd());
+ gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write(JSON.stringify(gManifestObject));
+ response.processAsync();
+ response.finish();
+ });
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
+ Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
+
+ gPolicy = new Experiments.Policy();
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ oneshotTimer: (callback, timeout, thisObj, name) => {},
+ });
+});
+
+// Test disabling the feature stops current and future experiments.
+
+add_task(function* test_disableExperiments() {
+ const OBSERVER_TOPIC = "experiments-changed";
+ let observerFireCount = 0;
+ let expectedObserverFireCount = 0;
+ let observer = () => ++observerFireCount;
+ Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
+ let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
+ let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ startTime: dateToSeconds(startDate2),
+ endTime: dateToSeconds(endDate2),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate1),
+ endTime: dateToSeconds(endDate1),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+ // Use updateManifest() to provide for coverage of that path.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+ let addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "Precondition: No experiment add-ons are installed.");
+
+ // Trigger update, clock set for experiment 1 to start.
+
+ now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+ Assert.equal(list[0].active, true, "Experiment should be active.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 1, "An experiment add-on was installed.");
+
+ // Disable the experiments feature. Check that we stop the running experiment.
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, false);
+ yield experiments._mainTask;
+
+ Assert.equal(observerFireCount, ++expectedObserverFireCount,
+ "Experiments observer should have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+ Assert.equal(list[0].active, false, "Experiment entry should not be active.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "The experiment add-on should be uninstalled.");
+
+ // Trigger update, clock set for experiment 2 to start. Verify we don't start it.
+
+ now = startDate2;
+ defineNow(gPolicy, now);
+
+ try {
+ yield experiments.updateManifest();
+ } catch (e) {
+ // This exception is expected, we rethrow everything else
+ if (e.message != "experiments are disabled") {
+ throw e;
+ }
+ }
+
+ experiments.notify();
+ yield experiments._mainTask;
+
+ Assert.equal(observerFireCount, expectedObserverFireCount,
+ "Experiments observer should not have been called.");
+
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should still have 1 entry.");
+ Assert.equal(list[0].active, false, "Experiment entry should not be active.");
+ addons = yield getExperimentAddons();
+ Assert.equal(addons.length, 0, "There should still be no experiment add-on installed.");
+
+ // Cleanup.
+
+ Services.obs.removeObserver(observer, OBSERVER_TOPIC);
+ yield promiseRestartManager();
+ yield removeCacheFile();
+});
diff --git a/browser/experiments/test/xpcshell/test_fetch.js b/browser/experiments/test/xpcshell/test_fetch.js
new file mode 100644
index 000000000..e8d76fa35
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_fetch.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+
+var gHttpServer = null;
+var gHttpRoot = null;
+var gPolicy = new Experiments.Policy();
+
+function run_test() {
+ loadAddonManager();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gHttpServer.registerDirectory("/", do_get_cwd());
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ });
+
+ run_next_test();
+}
+
+add_task(function* test_fetchAndCache() {
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest");
+ let ex = new Experiments.Experiments(gPolicy);
+
+ Assert.equal(ex._experiments, null, "There should be no cached experiments yet.");
+ yield ex.updateManifest();
+ Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
+
+ yield promiseRestartManager();
+});
+
+add_task(function* test_checkCache() {
+ let ex = new Experiments.Experiments(gPolicy);
+ yield ex.notify();
+ Assert.notEqual(ex._experiments.size, 0, "There should be cached experiments now.");
+
+ yield promiseRestartManager();
+});
+
+add_task(function* test_fetchInvalid() {
+ yield removeCacheFile();
+
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "experiments_1.manifest");
+ let ex = new Experiments.Experiments(gPolicy);
+ yield ex.updateManifest();
+ Assert.notEqual(ex._experiments.size, 0, "There should be experiments");
+
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gHttpRoot + "invalid.manifest");
+ yield ex.updateManifest()
+ Assert.notEqual(ex._experiments.size, 0, "There should still be experiments: fetch failure shouldn't remove them.");
+
+ yield promiseRestartManager();
+});
diff --git a/browser/experiments/test/xpcshell/test_nethang_bug1012924.js b/browser/experiments/test/xpcshell/test_nethang_bug1012924.js
new file mode 100644
index 000000000..7ef604901
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_nethang_bug1012924.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+
+const MANIFEST_HANDLER = "manifests/handler";
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ loadAddonManager();
+ do_get_profile();
+
+ let httpServer = new HttpServer();
+ httpServer.start(-1);
+ let port = httpServer.identity.primaryPort;
+ let httpRoot = "http://localhost:" + port + "/";
+ let handlerURI = httpRoot + MANIFEST_HANDLER;
+ httpServer.registerPathHandler("/" + MANIFEST_HANDLER,
+ (request, response) => {
+ response.processAsync();
+ response.setStatus(null, 200, "OK");
+ response.write("["); // never finish!
+ });
+
+ do_register_cleanup(() => httpServer.stop(() => {}));
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, handlerURI);
+ Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
+
+ let experiments = Experiments.instance();
+ experiments.updateManifest().then(
+ () => {
+ Assert.ok(true, "updateManifest finished successfully");
+ },
+ (e) => {
+ do_throw("updateManifest should not have failed: got error " + e);
+ });
+ yield experiments.uninit();
+});
diff --git a/browser/experiments/test/xpcshell/test_previous_provider.js b/browser/experiments/test/xpcshell/test_previous_provider.js
new file mode 100644
index 000000000..f7186e159
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_previous_provider.js
@@ -0,0 +1,179 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+var gDataRoot;
+var gHttpServer;
+var gManifestObject;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test_setup() {
+ loadAddonManager();
+ do_get_profile();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let httpRoot = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
+ gDataRoot = httpRoot + "data/";
+ gHttpServer.registerDirectory("/data/", do_get_cwd());
+ gHttpServer.registerPathHandler("/manifests/handler", (req, res) => {
+ res.setStatusLine(null, 200, "OK");
+ res.write(JSON.stringify(gManifestObject));
+ res.processAsync();
+ res.finish();
+ });
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ Services.prefs.setBoolPref("experiments.enabled", true);
+ Services.prefs.setCharPref("experiments.manifest.uri",
+ httpRoot + "manifests/handler");
+ Services.prefs.setBoolPref("experiments.logging.dump", true);
+ Services.prefs.setCharPref("experiments.logging.level", "Trace");
+});
+
+add_task(function* test_provider_basic() {
+ let e = Experiments.instance();
+
+ let provider = new Experiments.PreviousExperimentProvider(e);
+ e._setPreviousExperimentsProvider(provider);
+
+ let deferred = Promise.defer();
+ provider.getAddonsByTypes(["experiment"], (addons) => {
+ deferred.resolve(addons);
+ });
+ let experimentAddons = yield deferred.promise;
+ Assert.ok(Array.isArray(experimentAddons), "getAddonsByTypes returns an Array.");
+ Assert.equal(experimentAddons.length, 0, "No previous add-ons returned.");
+
+ gManifestObject = {
+ version: 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: Date.now() / 1000 - 60,
+ endTime: Date.now() / 1000 + 60,
+ maxActiveSeconds: 60,
+ appName: ["XPCShell"],
+ channel: [e._policy.updatechannel()],
+ },
+ ],
+ };
+
+ yield e.updateManifest();
+
+ deferred = Promise.defer();
+ provider.getAddonsByTypes(["experiment"], (addons) => {
+ deferred.resolve(addons);
+ });
+ experimentAddons = yield deferred.promise;
+ Assert.equal(experimentAddons.length, 0, "Still no previous experiment.");
+
+ let experiments = yield e.getExperiments();
+ Assert.equal(experiments.length, 1, "1 experiment present.");
+ Assert.ok(experiments[0].active, "It is active.");
+
+ // Deactivate it.
+ defineNow(e._policy, new Date(gManifestObject.experiments[0].endTime * 1000 + 1000));
+ yield e.updateManifest();
+
+ experiments = yield e.getExperiments();
+ Assert.equal(experiments.length, 1, "1 experiment present.");
+ Assert.equal(experiments[0].active, false, "It isn't active.");
+
+ deferred = Promise.defer();
+ provider.getAddonsByTypes(["experiment"], (addons) => {
+ deferred.resolve(addons);
+ });
+ experimentAddons = yield deferred.promise;
+ Assert.equal(experimentAddons.length, 1, "1 previous add-on known.");
+ Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected.");
+
+ deferred = Promise.defer();
+ provider.getAddonByID(EXPERIMENT1_ID, (addon) => {
+ deferred.resolve(addon);
+ });
+ let addon = yield deferred.promise;
+ Assert.ok(addon, "We got an add-on from its ID.");
+ Assert.equal(addon.id, EXPERIMENT1_ID, "ID matches expected.");
+ Assert.ok(addon.appDisabled, "Add-on is a previous experiment.");
+ Assert.ok(addon.userDisabled, "Add-on is disabled.");
+ Assert.equal(addon.type, "experiment", "Add-on is an experiment.");
+ Assert.equal(addon.isActive, false, "Add-on is not active.");
+ Assert.equal(addon.permissions, 0, "Add-on has no permissions.");
+
+ deferred = Promise.defer();
+ AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+ deferred.resolve(addons);
+ });
+ experimentAddons = yield deferred.promise;
+ Assert.equal(experimentAddons.length, 1, "Got 1 experiment from add-on manager.");
+ Assert.equal(experimentAddons[0].id, EXPERIMENT1_ID, "ID matches expected.");
+ Assert.ok(experimentAddons[0].appDisabled, "It is a previous experiment add-on.");
+});
+
+add_task(function* test_active_and_previous() {
+ // Building on the previous test, activate experiment 2.
+ let e = Experiments.instance();
+ let provider = new Experiments.PreviousExperimentProvider(e);
+ e._setPreviousExperimentsProvider(provider);
+
+ gManifestObject = {
+ version: 1,
+ experiments: [
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ startTime: Date.now() / 1000 - 60,
+ endTime: Date.now() / 1000 + 60,
+ maxActiveSeconds: 60,
+ appName: ["XPCShell"],
+ channel: [e._policy.updatechannel()],
+ },
+ ],
+ };
+
+ defineNow(e._policy, new Date());
+ yield e.updateManifest();
+
+ let experiments = yield e.getExperiments();
+ Assert.equal(experiments.length, 2, "2 experiments known.");
+
+ let deferred = Promise.defer();
+ provider.getAddonsByTypes(["experiment"], (addons) => {
+ deferred.resolve(addons);
+ });
+ let experimentAddons = yield deferred.promise;
+ Assert.equal(experimentAddons.length, 1, "1 previous experiment.");
+
+ deferred = Promise.defer();
+ AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+ deferred.resolve(addons);
+ });
+ experimentAddons = yield deferred.promise;
+ Assert.equal(experimentAddons.length, 2, "2 experiment add-ons known.");
+
+ for (let addon of experimentAddons) {
+ if (addon.id == EXPERIMENT1_ID) {
+ Assert.equal(addon.isActive, false, "Add-on is not active.");
+ Assert.ok(addon.appDisabled, "Should be a previous experiment.");
+ }
+ else if (addon.id == EXPERIMENT2_ID) {
+ Assert.ok(addon.isActive, "Add-on is active.");
+ Assert.ok(!addon.appDisabled, "Should not be a previous experiment.");
+ }
+ else {
+ throw new Error("Unexpected add-on ID: " + addon.id);
+ }
+ }
+});
diff --git a/browser/experiments/test/xpcshell/test_telemetry.js b/browser/experiments/test/xpcshell/test_telemetry.js
new file mode 100644
index 000000000..02bd15d2b
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_telemetry.js
@@ -0,0 +1,294 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/TelemetryLog.jsm");
+var bsp = Cu.import("resource:///modules/experiments/Experiments.jsm");
+
+
+const MANIFEST_HANDLER = "manifests/handler";
+
+const SEC_IN_ONE_DAY = 24 * 60 * 60;
+const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
+
+
+var gHttpServer = null;
+var gHttpRoot = null;
+var gDataRoot = null;
+var gPolicy = null;
+var gManifestObject = null;
+var gManifestHandlerURI = null;
+
+const TLOG = bsp.TELEMETRY_LOG;
+
+function checkEvent(event, id, data)
+{
+ do_print("Checking message " + id);
+ Assert.equal(event[0], id, "id should match");
+ Assert.ok(event[1] > 0, "timestamp should be greater than 0");
+
+ if (data === undefined) {
+ Assert.equal(event.length, 2, "event array should have 2 entries");
+ } else {
+ Assert.equal(event.length, data.length + 2, "event entry count should match expected count");
+ for (var i = 0; i < data.length; ++i) {
+ Assert.equal(typeof(event[i + 2]), "string", "event entry should be a string");
+ Assert.equal(event[i + 2], data[i], "event entry should match expected entry");
+ }
+ }
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test_setup() {
+ loadAddonManager();
+
+ gHttpServer = new HttpServer();
+ gHttpServer.start(-1);
+ let port = gHttpServer.identity.primaryPort;
+ gHttpRoot = "http://localhost:" + port + "/";
+ gDataRoot = gHttpRoot + "data/";
+ gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
+ gHttpServer.registerDirectory("/data/", do_get_cwd());
+ gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
+ response.setStatusLine(null, 200, "OK");
+ response.write(JSON.stringify(gManifestObject));
+ response.processAsync();
+ response.finish();
+ });
+ do_register_cleanup(() => gHttpServer.stop(() => {}));
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
+ Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
+ Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
+ Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
+
+ gPolicy = new Experiments.Policy();
+ let dummyTimer = { cancel: () => {}, clear: () => {} };
+ patchPolicy(gPolicy, {
+ updatechannel: () => "nightly",
+ oneshotTimer: (callback, timeout, thisObj, name) => dummyTimer,
+ });
+
+ yield removeCacheFile();
+});
+
+// Test basic starting and stopping of experiments.
+
+add_task(function* test_telemetryBasics() {
+ // Check TelemetryLog instead of TelemetrySession.getPayload().log because
+ // TelemetrySession gets Experiments.instance() and side-effects log entries.
+
+ let expectedLogLength = 0;
+
+ // Dates the following tests are based on.
+
+ let baseDate = new Date(2014, 5, 1, 12);
+ let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
+ let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
+ let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
+ let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
+
+ // The manifest data we test with.
+
+ gManifestObject = {
+ "version": 1,
+ experiments: [
+ {
+ id: EXPERIMENT1_ID,
+ xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
+ xpiHash: EXPERIMENT1_XPI_SHA1,
+ startTime: dateToSeconds(startDate1),
+ endTime: dateToSeconds(endDate1),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ {
+ id: EXPERIMENT2_ID,
+ xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
+ xpiHash: EXPERIMENT2_XPI_SHA1,
+ startTime: dateToSeconds(startDate2),
+ endTime: dateToSeconds(endDate2),
+ maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
+ appName: ["XPCShell"],
+ channel: ["nightly"],
+ },
+ ],
+ };
+
+ let experiments = new Experiments.Experiments(gPolicy);
+
+ // Trigger update, clock set to before any activation.
+ // Use updateManifest() to provide for coverage of that path.
+
+ let now = baseDate;
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ let list = yield experiments.getExperiments();
+ Assert.equal(list.length, 0, "Experiment list should be empty.");
+
+ expectedLogLength += 2;
+ let log = TelemetryLog.entries();
+ do_print("Telemetry log: " + JSON.stringify(log));
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-2], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.REJECTED, EXPERIMENT1_ID, "startTime"]);
+ checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
+
+ // Trigger update, clock set for experiment 1 to start.
+
+ now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries. Got " + log.toSource());
+ checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT1_ID]);
+
+ // Trigger update, clock set for experiment 1 to stop.
+
+ now = futureDate(endDate1, 1000);
+ defineNow(gPolicy, now);
+
+ yield experiments.updateManifest();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
+
+ expectedLogLength += 2;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-2], TLOG.TERMINATION_KEY,
+ [TLOG.TERMINATION.EXPIRED, EXPERIMENT1_ID]);
+ checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
+
+ // Trigger update, clock set for experiment 2 to start with invalid hash.
+
+ now = startDate2;
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[1].xpiHash = "sha1:0000000000000000000000000000000000000000";
+
+ yield experiments.updateManifest();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 1, "Experiment list should have 1 entries.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.INSTALL_FAILURE, EXPERIMENT2_ID]);
+
+ // Trigger update, clock set for experiment 2 to properly start now.
+
+ now = futureDate(now, MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[1].xpiHash = EXPERIMENT2_XPI_SHA1;
+
+ yield experiments.updateManifest();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]);
+
+ // Fake user uninstall of experiment via add-on manager.
+
+ now = futureDate(now, MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED);
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
+ [TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]);
+
+ // Trigger update with experiment 1a ready to start.
+
+ now = futureDate(now, MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].id = EXPERIMENT3_ID;
+ gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
+
+ yield experiments.updateManifest();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]);
+
+ // Trigger disable of an experiment via the API.
+
+ now = futureDate(now, MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+
+ yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API);
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
+ [TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]);
+
+ // Trigger update with experiment 1a ready to start.
+
+ now = futureDate(now, MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].id = EXPERIMENT4_ID;
+ gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
+
+ yield experiments.updateManifest();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
+ [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT4_ID]);
+
+ // Trigger experiment termination by something other than expiry via the manifest.
+
+ now = futureDate(now, MS_IN_ONE_DAY);
+ defineNow(gPolicy, now);
+ gManifestObject.experiments[0].os = "Plan9";
+
+ yield experiments.updateManifest();
+ list = yield experiments.getExperiments();
+ Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
+
+ expectedLogLength += 1;
+ log = TelemetryLog.entries();
+ Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
+ checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
+ [TLOG.TERMINATION.RECHECK, EXPERIMENT4_ID, "os"]);
+
+ // Cleanup.
+
+ yield promiseRestartManager();
+ yield removeCacheFile();
+});
diff --git a/browser/experiments/test/xpcshell/test_telemetry_disabled.js b/browser/experiments/test/xpcshell/test_telemetry_disabled.js
new file mode 100644
index 000000000..74f85ccfc
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_telemetry_disabled.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+
+add_test(function test_experiments_activation() {
+ do_get_profile();
+ loadAddonManager();
+
+ Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
+
+ let experiments = Experiments.instance();
+ Assert.ok(!experiments.enabled, "Experiments must be disabled if Telemetry is disabled.");
+
+ // TODO: Test that Experiments are turned back on when bug 1232648 lands.
+
+ run_next_test();
+});
diff --git a/browser/experiments/test/xpcshell/test_upgrade.js b/browser/experiments/test/xpcshell/test_upgrade.js
new file mode 100644
index 000000000..f094a406d
--- /dev/null
+++ b/browser/experiments/test/xpcshell/test_upgrade.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+Cu.import("resource:///modules/experiments/Experiments.jsm");
+
+var cacheData = {
+ _enabled: true,
+ _manifestData: {
+ id: "foobartestid",
+ xpiURL: "http://example.com/foo.xpi",
+ xpiHash: "sha256:abcde",
+ startTime: 0,
+ endTime: 2000000000,
+ maxActiveSeconds: 40000000,
+ appName: "TestApp",
+ channel: "test-foo",
+ },
+ _needsUpdate: false,
+ _randomValue: 0.5,
+ _failedStart: false,
+ _name: "Foo",
+ _description: "Foobar",
+ _homepageURL: "",
+ _addonId: "foo@test",
+ _startDate: 0,
+ _endDate: 2000000000,
+ _branch: null
+};
+
+add_task(function* test_valid() {
+ let e = new Experiments.ExperimentEntry();
+ Assert.ok(e.initFromCacheData(cacheData));
+ Assert.ok(e.enabled);
+});
+
+add_task(function* test_upgrade() {
+ let e = new Experiments.ExperimentEntry();
+ delete cacheData._branch;
+ Assert.ok(e.initFromCacheData(cacheData));
+ Assert.ok(e.enabled);
+});
+
+add_task(function* test_missing() {
+ let e = new Experiments.ExperimentEntry();
+ delete cacheData._name;
+ Assert.ok(!e.initFromCacheData(cacheData));
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/browser/experiments/test/xpcshell/xpcshell.ini b/browser/experiments/test/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..5ea30976c
--- /dev/null
+++ b/browser/experiments/test/xpcshell/xpcshell.ini
@@ -0,0 +1,31 @@
+[DEFAULT]
+head = head.js
+tail =
+tags = addons
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ experiments_1.manifest
+ experiment-1.xpi
+ experiment-1a.xpi
+ experiment-2.xpi
+ experiment-racybranch.xpi
+ !/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+generated-files =
+ experiment-1.xpi
+ experiment-1a.xpi
+ experiment-2.xpi
+ experiment-racybranch.xpi
+
+[test_activate.js]
+[test_api.js]
+[test_cache.js]
+[test_cacherace.js]
+[test_conditions.js]
+[test_disableExperiments.js]
+[test_fetch.js]
+[test_telemetry.js]
+[test_telemetry_disabled.js]
+[test_previous_provider.js]
+[test_upgrade.js]
+[test_nethang_bug1012924.js]
diff --git a/browser/extensions/aushelper/bootstrap.js b/browser/extensions/aushelper/bootstrap.js
new file mode 100644
index 000000000..f57f621e1
--- /dev/null
+++ b/browser/extensions/aushelper/bootstrap.js
@@ -0,0 +1,189 @@
+/* -*- 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const APP_UPDATE_URL_PREF = "app.update.url";
+const REPLACE_KEY = "%OS_VERSION%";
+
+const AUSHELPER_CPU_RESULT_CODE_HISTOGRAM_ID = "AUSHELPER_CPU_RESULT_CODE";
+// The system is not vulnerable to Bug 1296630.
+const CPU_NO_BUG1296630 = 1;
+// The system is vulnerable to Bug 1296630.
+const CPU_YES_BUG1296630 = 2;
+// An error occured when checking if the system is vulnerable to Bug 1296630.
+const CPU_ERR_BUG1296630 = 3;
+// It is unknown whether the system is vulnerable to Bug 1296630 (should never happen).
+const CPU_UNKNOWN_BUG1296630 = 4;
+
+const AUSHELPER_CPU_ERROR_CODE_HISTOGRAM_ID = "AUSHELPER_CPU_ERROR_CODE";
+const CPU_SUCCESS = 0;
+const CPU_REG_OPEN_ERROR = 1;
+const CPU_VENDOR_ID_ERROR = 2;
+const CPU_ID_ERROR = 4;
+const CPU_REV_ERROR = 8;
+
+const AUSHELPER_WEBSENSE_REG_VERSION_SCALAR_NAME = "aushelper.websense_reg_version";
+const AUSHELPER_WEBSENSE_REG_EXISTS_HISTOGRAM_ID = "AUSHELPER_WEBSENSE_REG_EXISTS";
+
+const AUSHELPER_WEBSENSE_ERROR_CODE_HISTOGRAM_ID = "AUSHELPER_WEBSENSE_ERROR_CODE";
+const WEBSENSE_SUCCESS = 0;
+const WEBSENSE_REG_OPEN_ERROR = 1;
+const WEBSENSE_REG_READ_ERROR = 2;
+const WEBSENSE_ALREADY_MODIFIED = 4;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function startup() {
+ if (Services.appinfo.OS != "WINNT") {
+ return;
+ }
+
+ const regCPUPath = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
+ let wrk;
+ let cpuErrorCode = CPU_SUCCESS;
+ try {
+ wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey);
+ wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, regCPUPath, wrk.ACCESS_READ);
+ } catch (e) {
+ Cu.reportError("AUSHelper - unable to open registry. Exception: " + e);
+ cpuErrorCode |= CPU_REG_OPEN_ERROR;
+ }
+
+ // If any of the following values are successfully retrieved and they don't
+ // match the condition for that value then it is safe to update. Hence why the
+ // following checks are somewhat convoluted. The possible values for the
+ // variable set by each check is as follows:
+ //
+ // | Match | No Match | Error |
+ // variable | true | false | null |
+
+ let cpuVendorIDMatch = false;
+ try {
+ let cpuVendorID = wrk.readStringValue("VendorIdentifier");
+ if (cpuVendorID.toLowerCase() == "genuineintel") {
+ cpuVendorIDMatch = true;
+ }
+ } catch (e) {
+ Cu.reportError("AUSHelper - error getting CPU vendor indentifier. Exception: " + e);
+ cpuVendorIDMatch = null;
+ cpuErrorCode |= CPU_VENDOR_ID_ERROR;
+ }
+
+ let cpuIDMatch = false;
+ try {
+ let cpuID = wrk.readStringValue("Identifier");
+ if (cpuID.toLowerCase().indexOf("family 6 model 61 stepping 4") != -1) {
+ cpuIDMatch = true;
+ }
+ } catch (e) {
+ Cu.reportError("AUSHelper - error getting CPU indentifier. Exception: " + e);
+ cpuIDMatch = null;
+ cpuErrorCode |= CPU_ID_ERROR;
+ }
+
+ let microCodeVersions = [0xe, 0x11, 0x12, 0x13, 0x16, 0x18, 0x19];
+ let cpuRevMatch = null;
+ try {
+ let keyNames = ["Update Revision", "Update Signature"];
+ for (let i = 0; i < keyNames.length; ++i) {
+ try {
+ let regVal = wrk.readBinaryValue(keyNames[i]);
+ if (regVal.length == 8) {
+ let hexVal = [];
+ // We are only inyterested in the upper 4 bytes and the little endian
+ // value for it.
+ for (let j = 4; j < 8; j++) {
+ let c = regVal.charCodeAt(j).toString(16);
+ if (c.length == 1) {
+ c = "0" + c;
+ }
+ hexVal.unshift(c);
+ }
+ cpuRevMatch = false;
+ if (microCodeVersions.indexOf(parseInt(hexVal.join(''))) != -1) {
+ cpuRevMatch = true;
+ }
+ break;
+ }
+ } catch (e) {
+ if (i == keyNames.length - 1) {
+ // The registry key name's value was not successfully queried.
+ cpuRevMatch = null;
+ cpuErrorCode |= CPU_REV_ERROR;
+ }
+ }
+ }
+ wrk.close();
+ } catch (ex) {
+ Cu.reportError("AUSHelper - error getting CPU revision. Exception: " + ex);
+ cpuRevMatch = null;
+ cpuErrorCode |= CPU_REV_ERROR;
+ }
+
+ let cpuResult = CPU_UNKNOWN_BUG1296630;
+ let cpuValue = "(unkBug1296630v1)";
+ // The following uses strict equality checks since the values can be true,
+ // false, or null.
+ if (cpuVendorIDMatch === false || cpuIDMatch === false || cpuRevMatch === false) {
+ // Since one of the values is false then the system won't be affected by
+ // bug 1296630 according to the conditions set out in bug 1311515.
+ cpuValue = "(noBug1296630v1)";
+ cpuResult = CPU_NO_BUG1296630;
+ } else if (cpuVendorIDMatch === null || cpuIDMatch === null || cpuRevMatch === null) {
+ // Since one of the values is null we can't say for sure if the system will
+ // be affected by bug 1296630.
+ cpuValue = "(errBug1296630v1)";
+ cpuResult = CPU_ERR_BUG1296630;
+ } else if (cpuVendorIDMatch === true && cpuIDMatch === true && cpuRevMatch === true) {
+ // Since all of the values are true we can say that the system will be
+ // affected by bug 1296630.
+ cpuValue = "(yesBug1296630v1)";
+ cpuResult = CPU_YES_BUG1296630;
+ }
+
+ Services.telemetry.getHistogramById(AUSHELPER_CPU_RESULT_CODE_HISTOGRAM_ID).add(cpuResult);
+ Services.telemetry.getHistogramById(AUSHELPER_CPU_ERROR_CODE_HISTOGRAM_ID).add(cpuErrorCode);
+
+ const regWebsensePath = "Websense\\Agent";
+ let websenseErrorCode = WEBSENSE_SUCCESS;
+ let websenseVersion = "";
+ try {
+ let regModes = [wrk.ACCESS_READ, wrk.ACCESS_READ | wrk.WOW64_64];
+ for (let i = 0; i < regModes.length; ++i) {
+ wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, "SOFTWARE", regModes[i]);
+ try {
+ if (wrk.hasChild(regWebsensePath)) {
+ let childKey = wrk.openChild(regWebsensePath, wrk.ACCESS_READ);
+ websenseVersion = childKey.readStringValue("InstallVersion");
+ Services.telemetry.scalarSet(AUSHELPER_WEBSENSE_REG_VERSION_SCALAR_NAME, websenseVersion);
+ }
+ wrk.close();
+ } catch (e) {
+ Cu.reportError("AUSHelper - unable to read registry. Exception: " + e);
+ websenseErrorCode |= WEBSENSE_REG_READ_ERROR;
+ }
+ }
+ } catch (ex) {
+ Cu.reportError("AUSHelper - unable to open registry. Exception: " + ex);
+ websenseErrorCode |= WEBSENSE_REG_OPEN_ERROR;
+ }
+
+ Services.telemetry.getHistogramById(AUSHELPER_WEBSENSE_REG_EXISTS_HISTOGRAM_ID).add(!!websenseVersion);
+ let websenseValue = "(" + (websenseVersion ? "websense-" + websenseVersion : "nowebsense") + ")";
+
+ let branch = Services.prefs.getDefaultBranch("");
+ let curValue = branch.getCharPref(APP_UPDATE_URL_PREF);
+ if (curValue.indexOf(REPLACE_KEY + "/") > -1) {
+ let newValue = curValue.replace(REPLACE_KEY + "/", REPLACE_KEY + cpuValue + websenseValue + "/");
+ branch.setCharPref(APP_UPDATE_URL_PREF, newValue);
+ } else {
+ websenseErrorCode |= WEBSENSE_ALREADY_MODIFIED;
+ }
+ Services.telemetry.getHistogramById(AUSHELPER_WEBSENSE_ERROR_CODE_HISTOGRAM_ID).add(websenseErrorCode);
+}
+function shutdown() {}
+function install() {}
+function uninstall() {}
diff --git a/browser/extensions/aushelper/install.rdf.in b/browser/extensions/aushelper/install.rdf.in
new file mode 100644
index 000000000..78e841d88
--- /dev/null
+++ b/browser/extensions/aushelper/install.rdf.in
@@ -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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>aushelper@mozilla.org</em:id>
+ <em:version>2.0</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Target Application this extension can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Application Update Service Helper</em:name>
+ <em:description>Sets value(s) in the update url based on custom checks.</em:description>
+ </Description>
+</RDF>
diff --git a/browser/extensions/aushelper/moz.build b/browser/extensions/aushelper/moz.build
new file mode 100644
index 000000000..a4ff06055
--- /dev/null
+++ b/browser/extensions/aushelper/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['aushelper@mozilla.org'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['aushelper@mozilla.org'] += [
+ 'install.rdf.in'
+]
diff --git a/browser/extensions/e10srollout/bootstrap.js b/browser/extensions/e10srollout/bootstrap.js
new file mode 100644
index 000000000..0da7ac225
--- /dev/null
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -0,0 +1,197 @@
+/* -*- 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, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+ // The amount of people to be part of e10s
+const TEST_THRESHOLD = {
+ "beta" : 0.5, // 50%
+ "release" : 1.0, // 100%
+ "esr" : 1.0, // 100%
+};
+
+const ADDON_ROLLOUT_POLICY = {
+ "beta" : "51alladdons", // Any WebExtension or addon except with mpc = false
+ "release" : "51set1",
+ "esr" : "esrA", // WebExtensions and Addons with mpc=true
+};
+
+if (AppConstants.RELEASE_OR_BETA) {
+ // Bug 1348576 - e10s is never enabled for non-official release builds
+ // This is hacky, but the problem it solves is the following:
+ // the e10s rollout is controlled by the channel name, which
+ // is the only way to distinguish between Beta and Release.
+ // However, non-official release builds (like the ones done by distros
+ // to ship Firefox on their package managers) do not set a value
+ // for the release channel, which gets them to the default value
+ // of.. (drumroll) "default".
+ // But we can't just always configure the same settings for the
+ // "default" channel because that's also the name that a locally
+ // built Firefox gets, and e10s is managed in a different way
+ // there (directly by prefs, on Nightly and Aurora).
+ TEST_THRESHOLD.default = TEST_THRESHOLD.release;
+ ADDON_ROLLOUT_POLICY.default = ADDON_ROLLOUT_POLICY.release;
+}
+
+
+const PREF_COHORT_SAMPLE = "e10s.rollout.cohortSample";
+const PREF_COHORT_NAME = "e10s.rollout.cohort";
+const PREF_E10S_OPTED_IN = "browser.tabs.remote.autostart";
+const PREF_E10S_FORCE_ENABLED = "browser.tabs.remote.force-enable";
+const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable";
+const PREF_TOGGLE_E10S = "browser.tabs.remote.autostart.2";
+const PREF_E10S_ADDON_POLICY = "extensions.e10s.rollout.policy";
+const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
+const PREF_E10S_HAS_NONEXEMPT_ADDON = "extensions.e10s.rollout.hasAddon";
+
+function startup() {
+ // In theory we only need to run this once (on install()), but
+ // it's better to also run it on every startup. If the user has
+ // made manual changes to the prefs, this will keep the data
+ // reported more accurate.
+ // It's also fine (and preferred) to just do it here on startup
+ // (instead of observing prefs), because e10s takes a restart
+ // to take effect, so we keep the data based on how it was when
+ // the session started.
+ defineCohort();
+}
+
+function install() {
+ defineCohort();
+}
+
+let cohortDefinedOnThisSession = false;
+
+function defineCohort() {
+ // Avoid running twice when it was called by install() first
+ if (cohortDefinedOnThisSession) {
+ return;
+ }
+ cohortDefinedOnThisSession = true;
+
+ let updateChannel = UpdateUtils.getUpdateChannel(false);
+ if (!(updateChannel in TEST_THRESHOLD)) {
+ setCohort("unsupportedChannel");
+ return;
+ }
+
+ let addonPolicy = "unknown";
+ if (updateChannel in ADDON_ROLLOUT_POLICY) {
+ addonPolicy = ADDON_ROLLOUT_POLICY[updateChannel];
+ Preferences.set(PREF_E10S_ADDON_POLICY, addonPolicy);
+ // This is also the proper place to set the blocklist pref
+ // in case it is necessary.
+
+ Preferences.set(PREF_E10S_ADDON_BLOCKLIST,
+ // bug 1185672 - Tab Mix Plus
+ "{dc572301-7619-498c-a57d-39143191b318};" +
+ // bug 1332692 - LastPass
+ "support@lastpass.com;");
+ } else {
+ Preferences.reset(PREF_E10S_ADDON_POLICY);
+ }
+
+ let userOptedOut = optedOut();
+ let userOptedIn = optedIn();
+ let disqualified = (Services.appinfo.multiprocessBlockPolicy != 0);
+ let testGroup = (getUserSample() < TEST_THRESHOLD[updateChannel]);
+ let hasNonExemptAddon = Preferences.get(PREF_E10S_HAS_NONEXEMPT_ADDON, false);
+ let temporaryDisqualification = getTemporaryDisqualification();
+
+ let cohortPrefix = "";
+ if (disqualified) {
+ cohortPrefix = "disqualified-";
+ } else if (hasNonExemptAddon) {
+ cohortPrefix = `addons-set${addonPolicy}-`;
+ }
+
+ if (userOptedOut) {
+ setCohort("optedOut");
+ } else if (userOptedIn) {
+ setCohort("optedIn");
+ } else if (temporaryDisqualification != "") {
+ // Users who are disqualified by the backend (from multiprocessBlockPolicy)
+ // can be put into either the test or control groups, because e10s will
+ // still be denied by the backend, which is useful so that the E10S_STATUS
+ // telemetry probe can be correctly set.
+
+ // For these volatile disqualification reasons, however, we must not try
+ // to activate e10s because the backend doesn't know about it. E10S_STATUS
+ // here will be accumulated as "2 - Disabled", which is fine too.
+ setCohort(`temp-disqualified-${temporaryDisqualification}`);
+ Preferences.reset(PREF_TOGGLE_E10S);
+ } else if (testGroup) {
+ setCohort(`${cohortPrefix}test`);
+ Preferences.set(PREF_TOGGLE_E10S, true);
+ } else {
+ setCohort(`${cohortPrefix}control`);
+ Preferences.reset(PREF_TOGGLE_E10S);
+ }
+}
+
+function shutdown(data, reason) {
+}
+
+function uninstall() {
+}
+
+function getUserSample() {
+ let prefValue = Preferences.get(PREF_COHORT_SAMPLE, undefined);
+ let value = 0.0;
+
+ if (typeof(prefValue) == "string") {
+ value = parseFloat(prefValue, 10);
+ return value;
+ }
+
+ if (typeof(prefValue) == "number") {
+ // convert old integer value
+ value = prefValue / 100;
+ } else {
+ value = Math.random();
+ }
+
+ Preferences.set(PREF_COHORT_SAMPLE, value.toString().substr(0, 8));
+ return value;
+}
+
+function setCohort(cohortName) {
+ Preferences.set(PREF_COHORT_NAME, cohortName);
+ try {
+ if (Ci.nsICrashReporter) {
+ Services.appinfo.QueryInterface(Ci.nsICrashReporter).annotateCrashReport("E10SCohort", cohortName);
+ }
+ } catch (e) {}
+}
+
+function optedIn() {
+ return Preferences.get(PREF_E10S_OPTED_IN, false) ||
+ Preferences.get(PREF_E10S_FORCE_ENABLED, false);
+}
+
+function optedOut() {
+ // Users can also opt-out by toggling back the pref to false.
+ // If they reset the pref instead they might be re-enabled if
+ // they are still part of the threshold.
+ return Preferences.get(PREF_E10S_FORCE_DISABLED, false) ||
+ (Preferences.isSet(PREF_TOGGLE_E10S) &&
+ Preferences.get(PREF_TOGGLE_E10S) == false);
+}
+
+/* If this function returns a non-empty string, it
+ * means that this particular user should be temporarily
+ * disqualified due to some particular reason.
+ * If a user shouldn't be disqualified, then an empty
+ * string must be returned.
+ */
+function getTemporaryDisqualification() {
+ return "";
+}
diff --git a/browser/extensions/e10srollout/install.rdf.in b/browser/extensions/e10srollout/install.rdf.in
new file mode 100644
index 000000000..19ff58471
--- /dev/null
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>e10srollout@mozilla.org</em:id>
+ <em:version>1.10</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Target Application this theme can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Multi-process staged rollout</em:name>
+ <em:description>Staged rollout of Firefox multi-process feature.</em:description>
+ </Description>
+</RDF>
diff --git a/browser/extensions/e10srollout/moz.build b/browser/extensions/e10srollout/moz.build
new file mode 100644
index 000000000..9b5a16d5a
--- /dev/null
+++ b/browser/extensions/e10srollout/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['e10srollout@mozilla.org'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['e10srollout@mozilla.org'] += [
+ 'install.rdf.in'
+]
diff --git a/browser/extensions/flyweb/bootstrap.js b/browser/extensions/flyweb/bootstrap.js
new file mode 100644
index 000000000..089226519
--- /dev/null
+++ b/browser/extensions/flyweb/bootstrap.js
@@ -0,0 +1,297 @@
+/* -*- 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Integration",
+ "resource://gre/modules/Integration.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PermissionUI",
+ "resource:///modules/PermissionUI.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gFlyWebBundle", function() {
+ const tns = {
+ "flyweb-button.label": "FlyWeb",
+ "flyweb-button.tooltiptext": "Discover nearby FlyWeb services",
+ "flyweb-items-empty": "There are no FlyWeb services currently nearby"
+ };
+ return {
+ GetStringFromName(name) {
+ return tns[name];
+ }
+ };
+});
+
+const FLYWEB_ENABLED_PREF = "dom.flyweb.enabled";
+
+function install(aData, aReason) {}
+
+function uninstall(aData, aReason) {}
+
+function startup(aData, aReason) {
+ // Observe pref changes and enable/disable as necessary.
+ Services.prefs.addObserver(FLYWEB_ENABLED_PREF, prefObserver, false);
+
+ // Only initialize if pref is enabled.
+ let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
+ if (enabled) {
+ FlyWebView.init();
+ }
+}
+
+function shutdown(aData, aReason) {
+ Services.prefs.removeObserver(FLYWEB_ENABLED_PREF, prefObserver);
+
+ let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
+ if (enabled) {
+ FlyWebView.uninit();
+ }
+}
+
+// use enabled pref as a way for tests (e.g. test_contextmenu.html) to disable
+// the addon when running.
+function prefObserver(aSubject, aTopic, aData) {
+ let enabled = Services.prefs.getBoolPref(FLYWEB_ENABLED_PREF);
+ if (enabled) {
+ FlyWebView.init();
+ } else {
+ FlyWebView.uninit();
+ }
+}
+
+let gDiscoveryManagerInstance;
+
+class DiscoveryManager {
+ constructor(aWindow) {
+ this._discoveryManager = new aWindow.FlyWebDiscoveryManager();
+ }
+
+ destroy() {
+ if (this._id) {
+ this.stop();
+ }
+
+ this._discoveryManager = null;
+ }
+
+ start(callback) {
+ if (!this._id) {
+ this._id = this._discoveryManager.startDiscovery(this);
+ }
+
+ this._callback = callback;
+ }
+
+ stop() {
+ this._discoveryManager.stopDiscovery(this._id);
+
+ this._id = null;
+ }
+
+ pairWith(serviceId, callback) {
+ this._discoveryManager.pairWithService(serviceId, {
+ pairingSucceeded(service) {
+ callback(service);
+ },
+
+ pairingFailed(error) {
+ console.error("FlyWeb failed to pair with service " + serviceId, error);
+ }
+ });
+ }
+
+ onDiscoveredServicesChanged(services) {
+ if (!this._id || !this._callback) {
+ return;
+ }
+
+ this._callback(services);
+ }
+}
+
+const FlyWebPermissionPromptIntegration = (base) => ({
+ __proto__: base,
+ createPermissionPrompt(type, request) {
+ if (type != "flyweb-publish-server") {
+ return super.createPermissionPrompt(...arguments);
+ }
+
+ return {
+ __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+ get request() {
+ return request;
+ },
+ get permissionKey() {
+ return "flyweb-publish-server";
+ },
+ get popupOptions() {
+ return {
+ learnMoreURL: "https://flyweb.github.io",
+ popupIconURL: "chrome://flyweb/skin/icon-64.png",
+ };
+ },
+ get notificationID() {
+ return "flyweb-publish-server";
+ },
+ get anchorID() {
+ const kAnchorID = "flyweb-publish-server-notification-icon";
+ let chromeDoc = this.browser.ownerDocument;
+ let anchor = chromeDoc.getElementById(kAnchorID);
+ if (!anchor) {
+ let notificationPopupBox =
+ chromeDoc.getElementById("notification-popup-box");
+ let notificationIcon = chromeDoc.createElement("image");
+ notificationIcon.id = kAnchorID;
+ notificationIcon.setAttribute("src",
+ "chrome://flyweb/skin/icon-64.png");
+ notificationIcon.classList.add("notification-anchor-icon");
+ notificationIcon.setAttribute("role", "button");
+ notificationIcon.setAttribute("aria-label",
+ "View the publish-server request");
+ notificationIcon.style.filter =
+ "url('chrome://browser/skin/filters.svg#fill')";
+ notificationIcon.style.fill = "currentcolor";
+ notificationIcon.style.opacity = "0.4";
+ notificationPopupBox.appendChild(notificationIcon);
+ }
+
+ return kAnchorID;
+ },
+ get message() {
+ return "Would you like to let this site start a server accessible " +
+ "to nearby devices and people?";
+ },
+ get promptActions() {
+ return [{
+ label: "Allow Server",
+ accessKey: "A",
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ }, {
+ label: "Block Server",
+ accessKey: "B",
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ }];
+ },
+ };
+ },
+});
+
+let FlyWebView = {
+ init() {
+ // Create widget and add it to the menu panel.
+ CustomizableUI.createWidget({
+ id: "flyweb-button",
+ type: "view",
+ viewId: "flyweb-panel",
+ label: gFlyWebBundle.GetStringFromName("flyweb-button.label"),
+ tooltiptext: gFlyWebBundle.GetStringFromName("flyweb-button.tooltiptext"),
+
+ onBeforeCreated(aDocument) {
+ let panel = aDocument.createElement("panelview");
+ panel.id = "flyweb-panel";
+ panel.setAttribute("class", "PanelUI-subView");
+ panel.setAttribute("flex", "1");
+
+ let label = aDocument.createElement("label");
+ label.setAttribute("class", "panel-subview-header");
+ label.setAttribute("value", gFlyWebBundle.GetStringFromName("flyweb-button.label"));
+
+ let empty = aDocument.createElement("description");
+ empty.id = "flyweb-items-empty";
+ empty.setAttribute("mousethrough", "always");
+ empty.textContent = gFlyWebBundle.GetStringFromName("flyweb-items-empty");
+
+ let items = aDocument.createElement("vbox");
+ items.id = "flyweb-items";
+ items.setAttribute("class", "panel-subview-body");
+
+ panel.appendChild(label);
+ panel.appendChild(empty);
+ panel.appendChild(items);
+
+ panel.addEventListener("command", this);
+
+ aDocument.getElementById("PanelUI-multiView").appendChild(panel);
+
+ this._sheetURI = Services.io.newURI("chrome://flyweb/skin/flyweb.css", null, null);
+ aDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).loadSheet(this._sheetURI, 1);
+ },
+
+ onDestroyed(aDocument) {
+ aDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).removeSheet(this._sheetURI, 1);
+ },
+
+ onViewShowing(aEvent) {
+ let doc = aEvent.target.ownerDocument;
+
+ let items = doc.getElementById("flyweb-items");
+ let empty = doc.getElementById("flyweb-items-empty");
+
+ if (!gDiscoveryManagerInstance) {
+ gDiscoveryManagerInstance = new DiscoveryManager(doc.defaultView);
+ }
+
+ gDiscoveryManagerInstance.start((services) => {
+ while (items.firstChild) {
+ items.firstChild.remove();
+ }
+
+ let fragment = doc.createDocumentFragment();
+
+ for (let service of services) {
+ let button = doc.createElement("toolbarbutton");
+ button.setAttribute("class", "subviewbutton cui-withicon");
+ button.setAttribute("label", service.displayName);
+ button.setAttribute("data-service-id", service.serviceId);
+ fragment.appendChild(button);
+ }
+
+ items.appendChild(fragment);
+
+ empty.hidden = services.length > 0;
+ });
+ },
+
+ onViewHiding(aEvent) {
+ gDiscoveryManagerInstance.stop();
+ },
+
+ handleEvent(aEvent) {
+ if (aEvent.type === "command") {
+ let serviceId = aEvent.target.getAttribute("data-service-id");
+ gDiscoveryManagerInstance.pairWith(serviceId, (service) => {
+ aEvent.view.openUILinkIn(service.uiUrl, "tab");
+ });
+ }
+ }
+ });
+
+ Integration.contentPermission
+ .register(FlyWebPermissionPromptIntegration);
+ },
+
+ uninit() {
+ CustomizableUI.destroyWidget("flyweb-button");
+
+ if (gDiscoveryManagerInstance) {
+ gDiscoveryManagerInstance.destroy();
+ gDiscoveryManagerInstance = null;
+ }
+
+ Integration.contentPermission
+ .unregister(FlyWebPermissionPromptIntegration);
+ }
+};
diff --git a/browser/extensions/flyweb/install.rdf.in b/browser/extensions/flyweb/install.rdf.in
new file mode 100644
index 000000000..17f746f9b
--- /dev/null
+++ b/browser/extensions/flyweb/install.rdf.in
@@ -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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>flyweb@mozilla.org</em:id>
+ <em:version>1.0.0</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Target Application this theme can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>FlyWeb</em:name>
+ <em:description>Discover nearby services in the browser</em:description>
+ </Description>
+</RDF>
diff --git a/browser/extensions/flyweb/jar.mn b/browser/extensions/flyweb/jar.mn
new file mode 100644
index 000000000..8eed2135b
--- /dev/null
+++ b/browser/extensions/flyweb/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/.
+
+[features/flyweb@mozilla.org] chrome.jar:
+% skin flyweb classic/1.0 %skin/linux/
+% skin flyweb classic/1.0 %skin/osx/ os=Darwin
+% skin flyweb classic/1.0 %skin/windows/ os=WINNT
+% skin flyweb-shared classic/1.0 %skin/shared/
+ skin/ (skin/*)
diff --git a/browser/extensions/flyweb/moz.build b/browser/extensions/flyweb/moz.build
new file mode 100644
index 000000000..7f71bfb55
--- /dev/null
+++ b/browser/extensions/flyweb/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['flyweb@mozilla.org'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['flyweb@mozilla.org'] += [
+ 'install.rdf.in'
+]
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/extensions/flyweb/skin/flyweb-icon.svg b/browser/extensions/flyweb/skin/flyweb-icon.svg
new file mode 100644
index 000000000..1b247e645
--- /dev/null
+++ b/browser/extensions/flyweb/skin/flyweb-icon.svg
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
+<circle fill="#797C80" cx="32" cy="52" r="6"/>
+<g>
+ <path fill="#797C80" d="M6.894,15.255c-2.254,1.547-4.386,3.304-6.361,5.279L0.18,20.887l3.536,3.536l0.354-0.354
+ c1.621-1.62,3.363-3.072,5.196-4.369C8.126,18.464,7.296,16.943,6.894,15.255z"/>
+ <path fill="#797C80" d="M63.465,20.532C55.061,12.128,43.887,7.5,32,7.5c-2.265,0-4.504,0.17-6.703,0.501
+ c0.822,1.44,1.3,3.1,1.312,4.87C28.382,12.631,30.181,12.5,32,12.5c10.55,0,20.468,4.108,27.928,11.567l0.354,0.354l3.537-3.535
+ L63.465,20.532z"/>
+</g>
+<g>
+ <path fill="#797C80" d="M16.613,10.94c1.103,0,2,0.897,2,2s-0.897,2-2,2s-2-0.897-2-2S15.51,10.94,16.613,10.94 M16.613,6.94
+ c-3.313,0-6,2.687-6,6s2.687,6,6,6s6-2.687,6-6S19.926,6.94,16.613,6.94L16.613,6.94z"/>
+</g>
+<g>
+ <path fill="#797C80" d="M46.492,37.502c-1.853-1.852-4.002-3.292-6.334-4.305c0.031,0.324,0.05,0.652,0.05,0.984
+ c0,1.477-0.33,2.874-0.906,4.137c1.329,0.712,2.561,1.623,3.657,2.719l0.354,0.354l3.533-3.535L46.492,37.502z"/>
+ <path fill="#797C80" d="M20.262,35.207c-0.972,0.683-1.9,1.439-2.758,2.297l-0.354,0.354l3.536,3.537l0.354-0.354
+ c0.35-0.35,0.715-0.679,1.091-0.99C21.118,38.66,20.446,37.007,20.262,35.207z"/>
+</g>
+<g>
+ <path fill="#797C80" d="M30.209,32.182c1.102,0,1.999,0.897,1.999,2s-0.896,2-1.999,2c-1.103,0-2-0.897-2-2
+ S29.106,32.182,30.209,32.182 M30.209,28.182c-3.313,0-6,2.686-6,6c0,3.312,2.687,6,6,6c3.313,0,5.999-2.688,5.999-6
+ C36.208,30.867,33.522,28.182,30.209,28.182L30.209,28.182z"/>
+</g>
+<g>
+ <path fill="#797C80" d="M32.207,23.716c0-1.497,0.34-2.912,0.932-4.188C32.76,19.515,32.381,19.5,32,19.5
+ c-8.681,0-16.843,3.381-22.981,9.52l-0.354,0.354l3.535,3.535l0.354-0.354C17.748,27.36,24.654,24.5,32,24.5
+ c0.083,0,0.165,0.005,0.247,0.006C32.227,24.245,32.207,23.982,32.207,23.716z"/>
+ <path fill="#797C80" d="M54.98,29.018c-0.987-0.987-2.033-1.896-3.119-2.738c-0.447,1.68-1.313,3.188-2.491,4.399
+ c0.717,0.586,1.409,1.21,2.073,1.874l0.354,0.354l3.537-3.535L54.98,29.018z"/>
+</g>
+<g>
+ <path fill="#797C80" d="M42.207,21.716c1.103,0,2,0.897,2,2s-0.897,2-2,2s-2-0.897-2-2S41.104,21.716,42.207,21.716 M42.207,17.716
+ c-3.313,0-6,2.687-6,6s2.687,6,6,6s6-2.687,6-6S45.521,17.716,42.207,17.716L42.207,17.716z"/>
+</g>
+</svg>
diff --git a/browser/extensions/flyweb/skin/linux/flyweb.css b/browser/extensions/flyweb/skin/linux/flyweb.css
new file mode 100644
index 000000000..c549248da
--- /dev/null
+++ b/browser/extensions/flyweb/skin/linux/flyweb.css
@@ -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/. */
+
+@import url("chrome://flyweb-shared/skin/flyweb.css");
diff --git a/browser/extensions/flyweb/skin/linux/icon-16.png b/browser/extensions/flyweb/skin/linux/icon-16.png
new file mode 100644
index 000000000..3a6c2e43a
--- /dev/null
+++ b/browser/extensions/flyweb/skin/linux/icon-16.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/linux/icon-32-anchored.png b/browser/extensions/flyweb/skin/linux/icon-32-anchored.png
new file mode 100644
index 000000000..b05590dda
--- /dev/null
+++ b/browser/extensions/flyweb/skin/linux/icon-32-anchored.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/linux/icon-32.png b/browser/extensions/flyweb/skin/linux/icon-32.png
new file mode 100644
index 000000000..a04ec20b9
--- /dev/null
+++ b/browser/extensions/flyweb/skin/linux/icon-32.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/linux/icon-64-anchored.png b/browser/extensions/flyweb/skin/linux/icon-64-anchored.png
new file mode 100644
index 000000000..b617b9208
--- /dev/null
+++ b/browser/extensions/flyweb/skin/linux/icon-64-anchored.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/linux/icon-64.png b/browser/extensions/flyweb/skin/linux/icon-64.png
new file mode 100644
index 000000000..be8ece467
--- /dev/null
+++ b/browser/extensions/flyweb/skin/linux/icon-64.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/osx/flyweb.css b/browser/extensions/flyweb/skin/osx/flyweb.css
new file mode 100644
index 000000000..c549248da
--- /dev/null
+++ b/browser/extensions/flyweb/skin/osx/flyweb.css
@@ -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/. */
+
+@import url("chrome://flyweb-shared/skin/flyweb.css");
diff --git a/browser/extensions/flyweb/skin/osx/icon-16.png b/browser/extensions/flyweb/skin/osx/icon-16.png
new file mode 100644
index 000000000..7c87435a4
--- /dev/null
+++ b/browser/extensions/flyweb/skin/osx/icon-16.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/osx/icon-32-anchored.png b/browser/extensions/flyweb/skin/osx/icon-32-anchored.png
new file mode 100644
index 000000000..b05590dda
--- /dev/null
+++ b/browser/extensions/flyweb/skin/osx/icon-32-anchored.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/osx/icon-32.png b/browser/extensions/flyweb/skin/osx/icon-32.png
new file mode 100644
index 000000000..5b0140ffa
--- /dev/null
+++ b/browser/extensions/flyweb/skin/osx/icon-32.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/osx/icon-64-anchored.png b/browser/extensions/flyweb/skin/osx/icon-64-anchored.png
new file mode 100644
index 000000000..b617b9208
--- /dev/null
+++ b/browser/extensions/flyweb/skin/osx/icon-64-anchored.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/osx/icon-64.png b/browser/extensions/flyweb/skin/osx/icon-64.png
new file mode 100644
index 000000000..eb67c29ec
--- /dev/null
+++ b/browser/extensions/flyweb/skin/osx/icon-64.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/shared/flyweb.css b/browser/extensions/flyweb/skin/shared/flyweb.css
new file mode 100644
index 000000000..acc8a743d
--- /dev/null
+++ b/browser/extensions/flyweb/skin/shared/flyweb.css
@@ -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/. */
+
+#flyweb-panel {
+ width: 20em;
+}
+
+#flyweb-items-empty {
+ box-sizing: border-box;
+ color: GrayText;
+ padding: 10px 20px;
+ text-align: center;
+}
+
+#flyweb-button {
+ list-style-image: url("chrome://flyweb/skin/icon-16.png");
+}
+
+#flyweb-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #flyweb-button {
+ list-style-image: url("chrome://flyweb/skin/icon-32.png");
+}
+
+#flyweb-button[cui-areatype="menu-panel"][panel-multiview-anchor="true"] {
+ list-style-image: url("chrome://flyweb/skin/icon-32-anchored.png");
+}
+
+#flyweb-items > toolbarbutton {
+ list-style-image: url("chrome://flyweb/skin/icon-16.png");
+}
+
+@media (min-resolution: 2dppx) {
+ #flyweb-button {
+ list-style-image: url("chrome://flyweb/skin/icon-32.png");
+ }
+
+ #flyweb-button[cui-areatype="menu-panel"],
+ toolbarpaletteitem[place="palette"] > #flyweb-button {
+ list-style-image: url("chrome://flyweb/skin/icon-64.png");
+ }
+
+ #flyweb-button[cui-areatype="menu-panel"][panel-multiview-anchor="true"] {
+ list-style-image: url("chrome://flyweb/skin/icon-64-anchored.png");
+ }
+
+ #flyweb-items > toolbarbutton {
+ list-style-image: url("chrome://flyweb/skin/icon-32.png");
+ }
+
+ #flyweb-items > toolbarbutton > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
diff --git a/browser/extensions/flyweb/skin/windows/flyweb.css b/browser/extensions/flyweb/skin/windows/flyweb.css
new file mode 100644
index 000000000..c549248da
--- /dev/null
+++ b/browser/extensions/flyweb/skin/windows/flyweb.css
@@ -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/. */
+
+@import url("chrome://flyweb-shared/skin/flyweb.css");
diff --git a/browser/extensions/flyweb/skin/windows/icon-16.png b/browser/extensions/flyweb/skin/windows/icon-16.png
new file mode 100644
index 000000000..3a6c2e43a
--- /dev/null
+++ b/browser/extensions/flyweb/skin/windows/icon-16.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/windows/icon-32-anchored.png b/browser/extensions/flyweb/skin/windows/icon-32-anchored.png
new file mode 100644
index 000000000..b05590dda
--- /dev/null
+++ b/browser/extensions/flyweb/skin/windows/icon-32-anchored.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/windows/icon-32.png b/browser/extensions/flyweb/skin/windows/icon-32.png
new file mode 100644
index 000000000..a04ec20b9
--- /dev/null
+++ b/browser/extensions/flyweb/skin/windows/icon-32.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/windows/icon-64-anchored.png b/browser/extensions/flyweb/skin/windows/icon-64-anchored.png
new file mode 100644
index 000000000..b617b9208
--- /dev/null
+++ b/browser/extensions/flyweb/skin/windows/icon-64-anchored.png
Binary files differ
diff --git a/browser/extensions/flyweb/skin/windows/icon-64.png b/browser/extensions/flyweb/skin/windows/icon-64.png
new file mode 100644
index 000000000..be8ece467
--- /dev/null
+++ b/browser/extensions/flyweb/skin/windows/icon-64.png
Binary files differ
diff --git a/browser/extensions/formautofill/.eslintrc.js b/browser/extensions/formautofill/.eslintrc.js
new file mode 100644
index 000000000..ec89029e5
--- /dev/null
+++ b/browser/extensions/formautofill/.eslintrc.js
@@ -0,0 +1,474 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": "../../.eslintrc.js",
+
+ "globals": {
+ "Components": true,
+ "dump": true,
+ "TextDecoder": false,
+ "TextEncoder": false,
+ },
+
+ "rules": {
+ // Rules from the mozilla plugin
+ "mozilla/balanced-listeners": "error",
+ "mozilla/no-aArgs": "warn",
+ "mozilla/no-cpows-in-tests": "warn",
+ "mozilla/var-only-at-top-level": "warn",
+
+ "valid-jsdoc": ["error", {
+ "prefer": {
+ "return": "returns",
+ },
+ "preferType": {
+ "Boolean": "boolean",
+ "Number": "number",
+ "String": "string",
+ "bool": "boolean",
+ },
+ "requireParamDescription": false,
+ "requireReturn": false,
+ "requireReturnDescription": false,
+ }],
+
+ // 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",
+
+ // Forbid spaces inside the square brackets of array literals.
+ "array-bracket-spacing": ["error", "never"],
+
+ // Forbid spaces inside the curly brackets of object literals.
+ "object-curly-spacing": ["error", "never"],
+
+ // No space padding in parentheses
+ "space-in-parens": ["error", "never"],
+
+ // Enforce one true brace style (opening brace on the same line) and avoid
+ // start and end braces on the same line.
+ "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",
+
+ // Don't require spaces around computed properties
+ "computed-property-spacing": ["warn", "never"],
+
+ // Functions are not required to consistently return something or nothing
+ "consistent-return": "off",
+
+ // Require braces around blocks that start a new line
+ "curly": ["error", "all"],
+
+ // 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"}],
+
+ // Require spaces before and after finally, catch, etc.
+ "keyword-spacing": "error",
+
+ // 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",
+
+ // If an if block ends with a return no need for an else block
+ // "no-else-return": "error",
+
+ // Disallow empty statements. This will report an error for:
+ // try { something(); } catch (e) {}
+ // but will not report it for:
+ // try { something(); } catch (e) { /* Silencing the error because ...*/ }
+ // which is a valid use case.
+ "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": "warn",
+
+ // No double semicolon
+ "no-extra-semi": "error",
+
+ // No overwriting defined functions
+ "no-func-assign": "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": "warn",
+
+ // No mixing spaces and tabs in indent
+ "no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
+
+ // Disallow use of multiple spaces (sometimes used to align const values,
+ // array or object items, etc.). It's hard to maintain and doesn't add that
+ // much benefit.
+ "no-multi-spaces": "warn",
+
+ // No reassigning native JS objects
+ "no-native-reassign": "error",
+
+ // No (!foo in bar)
+ "no-negated-in-lhs": "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 spaces between function name and parentheses
+ "no-spaced-func": "warn",
+
+ // No trailing whitespace
+ "no-trailing-spaces": "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", {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$"}],
+
+ // No using variables before defined
+ "no-use-before-define": "error",
+
+ // No using with
+ "no-with": "error",
+
+ // Always require semicolon at end of statement
+ "semi": ["error", "always"],
+
+ // Require space before blocks
+ "space-before-blocks": "error",
+
+ // Never use spaces before function parentheses
+ "space-before-function-paren": ["error", {"anonymous": "never", "named": "never"}],
+
+ // Require spaces around operators, except for a|"off".
+ "space-infix-ops": ["error", {"int32Hint": true}],
+
+ // ++ and -- should not need spacing
+ "space-unary-ops": ["warn", {"nonwords": false}],
+
+ // No comparisons to NaN
+ "use-isnan": "error",
+
+ // Only check typeof against valid results
+ "valid-typeof": "error",
+
+ // Disallow using variables outside the blocks they are defined (especially
+ // since only let and const are used, see "no-var").
+ "block-scoped-var": "error",
+
+ // Allow trailing commas for easy list extension. Having them does not
+ // impair readability, but also not required either.
+ "comma-dangle": ["error", "always-multiline"],
+
+ // Warn about cyclomatic complexity in functions.
+ "complexity": "warn",
+
+ // Don't warn for inconsistent naming when capturing this (not so important
+ // with auto-binding fat arrow functions).
+ // "consistent-this": ["error", "self"],
+
+ // Don't require a default case in switch statements. Avoid being forced to
+ // add a bogus default when you know all possible cases are handled.
+ "default-case": "off",
+
+ // Enforce dots on the next line with property name.
+ "dot-location": ["error", "property"],
+
+ // Encourage the use of dot notation whenever possible.
+ "dot-notation": "error",
+
+ // Allow using == instead of ===, in the interest of landing something since
+ // the devtools codebase is split on convention here.
+ "eqeqeq": "off",
+
+ // Don't require function expressions to have a name.
+ // This makes the code more verbose and hard to read. Our engine already
+ // does a fantastic job assigning a name to the function, which includes
+ // the enclosing function name, and worst case you have a line number that
+ // you can just look up.
+ "func-names": "off",
+
+ // Allow use of function declarations and expressions.
+ "func-style": "off",
+
+ // Don't enforce the maximum depth that blocks can be nested. The complexity
+ // rule is a better rule to check this.
+ "max-depth": "off",
+
+ // Maximum length of a line.
+ // Disabled because we exceed this in too many places.
+ "max-len": ["off", 80],
+
+ // Maximum depth callbacks can be nested.
+ "max-nested-callbacks": ["error", 4],
+
+ // Don't limit the number of parameters that can be used in a function.
+ "max-params": "off",
+
+ // Don't limit the maximum number of statement allowed in a function. We
+ // already have the complexity rule that's a better measurement.
+ "max-statements": "off",
+
+ // Don't require a capital letter for constructors, only check if all new
+ // operators are followed by a capital letter. Don't warn when capitalized
+ // functions are used without the new operator.
+ "new-cap": ["off", {"capIsNew": false}],
+
+ // Allow use of bitwise operators.
+ "no-bitwise": "off",
+
+ // Disallow use of arguments.caller or arguments.callee.
+ "no-caller": "error",
+
+ // Disallow the catch clause parameter name being the same as a variable in
+ // the outer scope, to avoid confusion.
+ "no-catch-shadow": "off",
+
+ // Disallow assignment in conditional expressions.
+ "no-cond-assign": "error",
+
+ // Disallow using the console API.
+ "no-console": "error",
+
+ // Allow using constant expressions in conditions like while (true)
+ "no-constant-condition": "off",
+
+ // Allow use of the continue statement.
+ "no-continue": "off",
+
+ // Disallow control characters in regular expressions.
+ "no-control-regex": "error",
+
+ // Disallow use of debugger.
+ "no-debugger": "error",
+
+ // Disallow deletion of variables (deleting properties is fine).
+ "no-delete-var": "error",
+
+ // Allow division operators explicitly at beginning of regular expression.
+ "no-div-regex": "off",
+
+ // Disallow use of eval(). We have other APIs to evaluate code in content.
+ "no-eval": "error",
+
+ // Disallow adding to native types
+ "no-extend-native": "error",
+
+ // Disallow unnecessary function binding.
+ "no-extra-bind": "error",
+
+ // Allow unnecessary parentheses, as they may make the code more readable.
+ "no-extra-parens": "off",
+
+ // Disallow fallthrough of case statements, except if there is a comment.
+ "no-fallthrough": "error",
+
+ // Allow the use of leading or trailing decimal points in numeric literals.
+ "no-floating-decimal": "off",
+
+ // Allow comments inline after code.
+ "no-inline-comments": "off",
+
+ // Disallow use of labels for anything other then loops and switches.
+ "no-labels": ["error", {"allowLoop": true}],
+
+ // Disallow use of multiline strings (use template strings instead).
+ "no-multi-str": "warn",
+
+ // Disallow multiple empty lines.
+ "no-multiple-empty-lines": ["warn", {"max": 2}],
+
+ // Allow reassignment of function parameters.
+ "no-param-reassign": "off",
+
+ // Allow string concatenation with __dirname and __filename (not a node env).
+ "no-path-concat": "off",
+
+ // Allow use of unary operators, ++ and --.
+ "no-plusplus": "off",
+
+ // Allow using process.env (not a node environment).
+ "no-process-env": "off",
+
+ // Allow using process.exit (not a node environment).
+ "no-process-exit": "off",
+
+ // Disallow usage of __proto__ property.
+ "no-proto": "error",
+
+ // Disallow multiple spaces in a regular expression literal.
+ "no-regex-spaces": "error",
+
+ // Allow reserved words being used as object literal keys.
+ "no-reserved-keys": "off",
+
+ // Don't restrict usage of specified node modules (not a node environment).
+ "no-restricted-modules": "off",
+
+ // Disallow use of assignment in return statement. It is preferable for a
+ // single line of code to have only one easily predictable effect.
+ "no-return-assign": "error",
+
+ // Don't warn about declaration of variables already declared in the outer scope.
+ "no-shadow": "off",
+
+ // Disallow shadowing of names such as arguments.
+ "no-shadow-restricted-names": "error",
+
+ // Allow use of synchronous methods (not a node environment).
+ "no-sync": "off",
+
+ // Allow the use of ternary operators.
+ "no-ternary": "off",
+
+ // Disallow throwing literals (eg. throw "error" instead of
+ // throw new Error("error")).
+ "no-throw-literal": "error",
+
+ // Disallow use of undeclared variables unless mentioned in a /* global */
+ // block. Note that globals from head.js are automatically imported in tests
+ // by the import-headjs-globals rule form the mozilla eslint plugin.
+ "no-undef": "error",
+
+ // Allow dangling underscores in identifiers (for privates).
+ "no-underscore-dangle": "off",
+
+ // Allow use of undefined variable.
+ "no-undefined": "off",
+
+ // Disallow the use of Boolean literals in conditional expressions.
+ "no-unneeded-ternary": "error",
+
+ // We use var-only-at-top-level instead of no-var as we allow top level
+ // vars.
+ "no-var": "off",
+
+ // Allow using TODO/FIXME comments.
+ "no-warning-comments": "off",
+
+ // Don't require method and property shorthand syntax for object literals.
+ // We use this in the code a lot, but not consistently, and this seems more
+ // like something to check at code review time.
+ "object-shorthand": "off",
+
+ // Allow more than one variable declaration per function.
+ "one-var": "off",
+
+ // Disallow padding within blocks.
+ "padded-blocks": ["warn", "never"],
+
+ // Don't require quotes around object literal property names.
+ "quote-props": "off",
+
+ // Double quotes should be used.
+ "quotes": ["warn", "double", {"avoidEscape": true, "allowTemplateLiterals": true}],
+
+ // Require use of the second argument for parseInt().
+ "radix": "error",
+
+ // Enforce spacing after semicolons.
+ "semi-spacing": ["error", {"before": false, "after": true}],
+
+ // Don't require to sort variables within the same declaration block.
+ // Anyway, one-var is disabled.
+ "sort-vars": "off",
+
+ // Require a space immediately following the // in a line comment.
+ "spaced-comment": ["error", "always"],
+
+ // Require "use strict" to be defined globally in the script.
+ "strict": ["error", "global"],
+
+ // Allow vars to be declared anywhere in the scope.
+ "vars-on-top": "off",
+
+ // Don't require immediate function invocation to be wrapped in parentheses.
+ "wrap-iife": "off",
+
+ // Don't require regex literals to be wrapped in parentheses (which
+ // supposedly prevent them from being mistaken for division operators).
+ "wrap-regex": "off",
+
+ // Disallow Yoda conditions (where literal value comes first).
+ "yoda": "error",
+
+ // disallow use of eval()-like methods
+ "no-implied-eval": "error",
+
+ // Disallow function or variable declarations in nested blocks
+ "no-inner-declarations": "error",
+
+ // Disallow usage of __iterator__ property
+ "no-iterator": "error",
+
+ // Disallow labels that share a name with a variable
+ "no-label-var": "error",
+
+ // Disallow creating new instances of String, Number, and Boolean
+ "no-new-wrappers": "error",
+ },
+};
diff --git a/browser/extensions/formautofill/bootstrap.js b/browser/extensions/formautofill/bootstrap.js
new file mode 100644
index 000000000..0b3f355bd
--- /dev/null
+++ b/browser/extensions/formautofill/bootstrap.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";
+
+/* exported startup, shutdown, install, uninstall */
+
+function startup() {}
+function shutdown() {}
+function install() {}
+function uninstall() {}
diff --git a/browser/extensions/formautofill/content/FormAutofillContent.jsm b/browser/extensions/formautofill/content/FormAutofillContent.jsm
new file mode 100644
index 000000000..bde397580
--- /dev/null
+++ b/browser/extensions/formautofill/content/FormAutofillContent.jsm
@@ -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/. */
+
+/*
+ * Implements a service used by DOM content to request Form Autofill.
+ */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+/**
+ * Handles profile autofill for a DOM Form element.
+ * @param {HTMLFormElement} form Form that need to be auto filled
+ */
+function FormAutofillHandler(form) {
+ this.form = form;
+ this.fieldDetails = [];
+}
+
+FormAutofillHandler.prototype = {
+ /**
+ * DOM Form element to which this object is attached.
+ */
+ form: 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 backend. 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,
+
+ /**
+ * Returns information from the form about fields that can be autofilled, and
+ * populates the fieldDetails array on this object accordingly.
+ *
+ * @returns {Array<Object>} 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() {
+ let autofillData = [];
+
+ 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"].includes(info.fieldName)) {
+ 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;
+ }
+
+ let inputFormat = {
+ section: info.section,
+ addressType: info.addressType,
+ contactType: info.contactType,
+ fieldName: info.fieldName,
+ };
+ // Clone the inputFormat for caching the fields and elements together
+ let formatWithElement = Object.assign({}, inputFormat);
+
+ inputFormat.index = autofillData.length;
+ autofillData.push(inputFormat);
+
+ formatWithElement.element = element;
+ this.fieldDetails.push(formatWithElement);
+ }
+
+ return autofillData;
+ },
+
+ /**
+ * Processes form fields that can be autofilled, and populates them with the
+ * data provided by backend.
+ *
+ * @param {Array<Object>} autofillResult
+ * Data returned by the user interface.
+ * [{
+ * 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.
+ * index: Index to match the input in fieldDetails
+ * }],
+ * }
+ */
+ autofillFormFields(autofillResult) {
+ for (let field of autofillResult) {
+ // Get the field details, if it was processed by the user interface.
+ let fieldDetail = this.fieldDetails[field.index];
+
+ // Avoid the invalid value set
+ if (!fieldDetail || !field.value) {
+ continue;
+ }
+
+ let info = fieldDetail.element.getAutocompleteInfo();
+ if (field.section != info.section ||
+ field.addressType != info.addressType ||
+ field.contactType != info.contactType ||
+ field.fieldName != info.fieldName) {
+ Cu.reportError("Autocomplete tokens mismatched");
+ continue;
+ }
+
+ fieldDetail.element.setUserInput(field.value);
+ }
+ },
+};
+
+this.EXPORTED_SYMBOLS = ["FormAutofillHandler"];
diff --git a/browser/extensions/formautofill/content/FormAutofillParent.jsm b/browser/extensions/formautofill/content/FormAutofillParent.jsm
new file mode 100644
index 000000000..bdfe0f478
--- /dev/null
+++ b/browser/extensions/formautofill/content/FormAutofillParent.jsm
@@ -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/. */
+
+/*
+ * Implements a service used to access storage and communicate with content.
+ *
+ * A "fields" array is used to communicate with FormAutofillContent. Each item
+ * represents a single input field in the content page as well as its
+ * @autocomplete properties. The schema is as below. Please refer to
+ * FormAutofillContent.jsm for more details.
+ *
+ * [
+ * {
+ * section,
+ * addressType,
+ * contactType,
+ * fieldName,
+ * value,
+ * index
+ * },
+ * {
+ * // ...
+ * }
+ * ]
+ */
+
+/* exported FormAutofillParent */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ProfileStorage",
+ "resource://formautofill/ProfileStorage.jsm");
+
+const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
+
+let FormAutofillParent = {
+ _profileStore: null,
+
+ /**
+ * Initializes ProfileStorage and registers the message handler.
+ */
+ init: function() {
+ let storePath =
+ OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME);
+
+ this._profileStore = new ProfileStorage(storePath);
+ this._profileStore.initialize();
+
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("FormAutofill:PopulateFieldValues", this);
+ },
+
+ /**
+ * Handles the message coming from FormAutofillContent.
+ *
+ * @param {string} message.name The name of the message.
+ * @param {object} message.data The data of the message.
+ * @param {nsIFrameMessageManager} message.target Caller's message manager.
+ */
+ receiveMessage: function({name, data, target}) {
+ switch (name) {
+ case "FormAutofill:PopulateFieldValues":
+ this._populateFieldValues(data, target);
+ break;
+ }
+ },
+
+ /**
+ * Returns the instance of ProfileStorage. To avoid syncing issues, anyone
+ * who needs to access the profile should request the instance by this instead
+ * of creating a new one.
+ *
+ * @returns {ProfileStorage}
+ */
+ getProfileStore: function() {
+ return this._profileStore;
+ },
+
+ /**
+ * Uninitializes FormAutofillParent. This is for testing only.
+ *
+ * @private
+ */
+ _uninit: function() {
+ if (this._profileStore) {
+ this._profileStore._saveImmediately();
+ this._profileStore = null;
+ }
+
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
+ },
+
+ /**
+ * Populates the field values and notifies content to fill in. Exception will
+ * be thrown if there's no matching profile.
+ *
+ * @private
+ * @param {string} data.guid
+ * Indicates which profile to populate
+ * @param {Fields} data.fields
+ * The "fields" array collected from content.
+ * @param {nsIFrameMessageManager} target
+ * Content's message manager.
+ */
+ _populateFieldValues({guid, fields}, target) {
+ this._profileStore.notifyUsed(guid);
+ this._fillInFields(this._profileStore.get(guid), fields);
+ target.sendAsyncMessage("FormAutofill:fillForm", {fields});
+ },
+
+ /**
+ * Transforms a word with hyphen into camel case.
+ * (e.g. transforms "address-type" into "addressType".)
+ *
+ * @private
+ * @param {string} str The original string with hyphen.
+ * @returns {string} The camel-cased output string.
+ */
+ _camelCase(str) {
+ return str.toLowerCase().replace(/-([a-z])/g, s => s[1].toUpperCase());
+ },
+
+ /**
+ * Get the corresponding value from the specified profile according to a valid
+ * @autocomplete field name.
+ *
+ * Note that the field name doesn't need to match the property name defined in
+ * Profile object. This method can transform the raw data to fulfill it. (e.g.
+ * inputting "country-name" as "fieldName" will get a full name transformed
+ * from the country code that is recorded in "country" field.)
+ *
+ * @private
+ * @param {Profile} profile The specified profile.
+ * @param {string} fieldName A valid @autocomplete field name.
+ * @returns {string} The corresponding value. Returns "undefined" if there's
+ * no matching field.
+ */
+ _getDataByFieldName(profile, fieldName) {
+ let key = this._camelCase(fieldName);
+
+ // TODO: Transform the raw profile data to fulfill "fieldName" here.
+
+ return profile[key];
+ },
+
+ /**
+ * Fills in the "fields" array by the specified profile.
+ *
+ * @private
+ * @param {Profile} profile The specified profile to fill in.
+ * @param {Fields} fields The "fields" array collected from content.
+ */
+ _fillInFields(profile, fields) {
+ for (let field of fields) {
+ let value = this._getDataByFieldName(profile, field.fieldName);
+ if (value !== undefined) {
+ field.value = value;
+ }
+ }
+ },
+};
+
+this.EXPORTED_SYMBOLS = ["FormAutofillParent"];
diff --git a/browser/extensions/formautofill/content/ProfileStorage.jsm b/browser/extensions/formautofill/content/ProfileStorage.jsm
new file mode 100644
index 000000000..843177d4e
--- /dev/null
+++ b/browser/extensions/formautofill/content/ProfileStorage.jsm
@@ -0,0 +1,251 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implements an interface of the storage of Form Autofill.
+ *
+ * The data is stored in JSON format, without indentation, using UTF-8 encoding.
+ * With indentation applied, the file would look like this:
+ *
+ * {
+ * version: 1,
+ * profiles: [
+ * {
+ * guid, // 12 character...
+ *
+ * // profile
+ * organization, // Company
+ * streetAddress, // (Multiline)
+ * addressLevel2, // City/Town
+ * addressLevel1, // Province (Standardized code if possible)
+ * postalCode,
+ * country, // ISO 3166
+ * tel,
+ * email,
+ *
+ * // metadata
+ * timeCreated, // in ms
+ * timeLastUsed, // in ms
+ * timeLastModified, // in ms
+ * timesUsed
+ * },
+ * {
+ * // ...
+ * }
+ * ]
+ * }
+ */
+
+"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, "JSONFile",
+ "resource://gre/modules/JSONFile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+const SCHEMA_VERSION = 1;
+
+// Name-related fields will be handled in follow-up bugs due to the complexity.
+const VALID_FIELDS = [
+ "organization",
+ "streetAddress",
+ "addressLevel2",
+ "addressLevel1",
+ "postalCode",
+ "country",
+ "tel",
+ "email",
+];
+
+function ProfileStorage(path) {
+ this._path = path;
+}
+
+ProfileStorage.prototype = {
+ /**
+ * Loads the profile data from file to memory.
+ *
+ * @returns {Promise}
+ * @resolves When the operation finished successfully.
+ * @rejects JavaScript exception.
+ */
+ initialize() {
+ this._store = new JSONFile({
+ path: this._path,
+ dataPostProcessor: this._dataPostProcessor.bind(this),
+ });
+ return this._store.load();
+ },
+
+ /**
+ * Adds a new profile.
+ *
+ * @param {Profile} profile
+ * The new profile for saving.
+ */
+ add(profile) {
+ this._store.ensureDataReady();
+
+ let profileToSave = this._normalizeProfile(profile);
+
+ profileToSave.guid = gUUIDGenerator.generateUUID().toString()
+ .replace(/[{}-]/g, "").substring(0, 12);
+
+ // Metadata
+ let now = Date.now();
+ profileToSave.timeCreated = now;
+ profileToSave.timeLastModified = now;
+ profileToSave.timeLastUsed = 0;
+ profileToSave.timesUsed = 0;
+
+ this._store.data.profiles.push(profileToSave);
+
+ this._store.saveSoon();
+ },
+
+ /**
+ * Update the specified profile.
+ *
+ * @param {string} guid
+ * Indicates which profile to update.
+ * @param {Profile} profile
+ * The new profile used to overwrite the old one.
+ */
+ update(guid, profile) {
+ this._store.ensureDataReady();
+
+ let profileFound = this._findByGUID(guid);
+ if (!profileFound) {
+ throw new Error("No matching profile.");
+ }
+
+ let profileToUpdate = this._normalizeProfile(profile);
+ for (let field of VALID_FIELDS) {
+ if (profileToUpdate[field] !== undefined) {
+ profileFound[field] = profileToUpdate[field];
+ } else {
+ delete profileFound[field];
+ }
+ }
+
+ profileFound.timeLastModified = Date.now();
+
+ this._store.saveSoon();
+ },
+
+ /**
+ * Notifies the stroage of the use of the specified profile, so we can update
+ * the metadata accordingly.
+ *
+ * @param {string} guid
+ * Indicates which profile to be notified.
+ */
+ notifyUsed(guid) {
+ this._store.ensureDataReady();
+
+ let profileFound = this._findByGUID(guid);
+ if (!profileFound) {
+ throw new Error("No matching profile.");
+ }
+
+ profileFound.timesUsed++;
+ profileFound.timeLastUsed = Date.now();
+
+ this._store.saveSoon();
+ },
+
+ /**
+ * Removes the specified profile. No error occurs if the profile isn't found.
+ *
+ * @param {string} guid
+ * Indicates which profile to remove.
+ */
+ remove(guid) {
+ this._store.ensureDataReady();
+
+ this._store.data.profiles =
+ this._store.data.profiles.filter(profile => profile.guid != guid);
+ this._store.saveSoon();
+ },
+
+ /**
+ * Returns the profile with the specified GUID.
+ *
+ * @param {string} guid
+ * Indicates which profile to retrieve.
+ * @returns {Profile}
+ * A clone of the profile.
+ */
+ get(guid) {
+ this._store.ensureDataReady();
+
+ let profileFound = this._findByGUID(guid);
+ if (!profileFound) {
+ throw new Error("No matching profile.");
+ }
+
+ // Profile is cloned to avoid accidental modifications from outside.
+ return this._clone(profileFound);
+ },
+
+ /**
+ * Returns all profiles.
+ *
+ * @returns {Array.<Profile>}
+ * An array containing clones of all profiles.
+ */
+ getAll() {
+ this._store.ensureDataReady();
+
+ // Profiles are cloned to avoid accidental modifications from outside.
+ return this._store.data.profiles.map(this._clone);
+ },
+
+ _clone(profile) {
+ return Object.assign({}, profile);
+ },
+
+ _findByGUID(guid) {
+ return this._store.data.profiles.find(profile => profile.guid == guid);
+ },
+
+ _normalizeProfile(profile) {
+ let result = {};
+ for (let key in profile) {
+ if (!VALID_FIELDS.includes(key)) {
+ throw new Error(`"${key}" is not a valid field.`);
+ }
+ if (typeof profile[key] !== "string" &&
+ typeof profile[key] !== "number") {
+ throw new Error(`"${key}" contains invalid data type.`);
+ }
+
+ result[key] = profile[key];
+ }
+ return result;
+ },
+
+ _dataPostProcessor(data) {
+ data.version = SCHEMA_VERSION;
+ if (!data.profiles) {
+ data.profiles = [];
+ }
+ return data;
+ },
+
+ // For test only.
+ _saveImmediately() {
+ return this._store._save();
+ },
+};
+
+this.EXPORTED_SYMBOLS = ["ProfileStorage"];
diff --git a/browser/extensions/formautofill/install.rdf.in b/browser/extensions/formautofill/install.rdf.in
new file mode 100644
index 000000000..5e34051ba
--- /dev/null
+++ b/browser/extensions/formautofill/install.rdf.in
@@ -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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>formautofill@mozilla.org</em:id>
+ <em:version>1.0</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Target Application this extension can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Form Autofill</em:name>
+ <em:description>Autofill forms with saved profiles</em:description>
+ </Description>
+</RDF>
diff --git a/browser/extensions/formautofill/jar.mn b/browser/extensions/formautofill/jar.mn
new file mode 100644
index 000000000..0cba721ef
--- /dev/null
+++ b/browser/extensions/formautofill/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/.
+
+[features/formautofill@mozilla.org] chrome.jar:
+% resource formautofill %content/
+ content/ (content/*)
diff --git a/browser/extensions/formautofill/moz.build b/browser/extensions/formautofill/moz.build
new file mode 100644
index 000000000..92577db53
--- /dev/null
+++ b/browser/extensions/formautofill/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['formautofill@mozilla.org'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['formautofill@mozilla.org'] += [
+ 'install.rdf.in'
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/extensions/formautofill/test/browser/.eslintrc.js b/browser/extensions/formautofill/test/browser/.eslintrc.js
new file mode 100644
index 000000000..52a2004c9
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js",
+ ],
+};
diff --git a/browser/extensions/formautofill/test/browser/browser.ini b/browser/extensions/formautofill/test/browser/browser.ini
new file mode 100644
index 000000000..500224636
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[browser_check_installed.js]
diff --git a/browser/extensions/formautofill/test/browser/browser_check_installed.js b/browser/extensions/formautofill/test/browser/browser_check_installed.js
new file mode 100644
index 000000000..b018c0f71
--- /dev/null
+++ b/browser/extensions/formautofill/test/browser/browser_check_installed.js
@@ -0,0 +1,14 @@
+"use strict";
+
+add_task(function* test_enabled() {
+ let addon = yield new Promise(
+ resolve => AddonManager.getAddonByID("formautofill@mozilla.org", resolve)
+ );
+ isnot(addon, null, "Check addon exists");
+ is(addon.version, "1.0", "Check version");
+ is(addon.name, "Form Autofill", "Check name");
+ ok(addon.isCompatible, "Check application compatibility");
+ ok(!addon.appDisabled, "Check not app disabled");
+ ok(addon.isActive, "Check addon is active");
+ is(addon.type, "extension", "Check type is 'extension'");
+});
diff --git a/browser/extensions/formautofill/test/unit/.eslintrc b/browser/extensions/formautofill/test/unit/.eslintrc
new file mode 100644
index 000000000..8e33fb0c6
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ],
+}
diff --git a/browser/extensions/formautofill/test/unit/head.js b/browser/extensions/formautofill/test/unit/head.js
new file mode 100644
index 000000000..67e3bd60b
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -0,0 +1,81 @@
+/**
+ * Provides infrastructure for automated login components tests.
+ */
+
+ /* exported importAutofillModule, getTempFile */
+
+"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://testing-common/MockDocument.jsm");
+
+// Redirect the path of the resouce in addon to the exact file path.
+let defineLazyModuleGetter = XPCOMUtils.defineLazyModuleGetter;
+XPCOMUtils.defineLazyModuleGetter = function() {
+ let result = /^resource\:\/\/formautofill\/(.+)$/.exec(arguments[2]);
+ if (result) {
+ arguments[2] = Services.io.newFileURI(do_get_file(result[1])).spec;
+ }
+ return defineLazyModuleGetter.apply(this, arguments);
+};
+
+// Load the module by Service newFileURI API for running extension's XPCShell test
+function importAutofillModule(module) {
+ return Cu.import(Services.io.newFileURI(do_get_file(module)).spec);
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadPaths",
+ "resource://gre/modules/DownloadPaths.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+// While the previous test file should have deleted all the temporary files it
+// used, on Windows these might still be pending deletion on the physical file
+// system. Thus, start from a new base number every time, to make a collision
+// with a file that is still pending deletion highly unlikely.
+let gFileCounter = Math.floor(Math.random() * 1000000);
+
+/**
+ * Returns a reference to a temporary file, that is guaranteed not to exist, and
+ * to have never been created before.
+ *
+ * @param {string} leafName
+ * Suggested leaf name for the file to be created.
+ *
+ * @returns {nsIFile} pointing to a non-existent file in a temporary directory.
+ *
+ * @note It is not enough to delete the file if it exists, or to delete the file
+ * after calling nsIFile.createUnique, because on Windows the delete
+ * operation in the file system may still be pending, preventing a new
+ * file with the same name to be created.
+ */
+function getTempFile(leafName) {
+ // Prepend a serial number to the extension in the suggested leaf name.
+ let [base, ext] = DownloadPaths.splitBaseNameAndExtension(leafName);
+ let finalLeafName = base + "-" + gFileCounter + ext;
+ gFileCounter++;
+
+ // Get a file reference under the temporary directory for this test file.
+ let file = FileUtils.getFile("TmpD", [finalLeafName]);
+ do_check_false(file.exists());
+
+ do_register_cleanup(function() {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ });
+
+ return file;
+}
+
+add_task(function* test_common_initialize() {
+ Services.prefs.setBoolPref("dom.forms.autocomplete.experimental", true);
+
+ // Clean up after every test.
+ do_register_cleanup(() => {
+ Services.prefs.setBoolPref("dom.forms.autocomplete.experimental", false);
+ });
+});
diff --git a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
new file mode 100644
index 000000000..a842e84e8
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -0,0 +1,200 @@
+/*
+ * Test for form auto fill content helper fill all inputs function.
+ */
+
+"use strict";
+
+let {FormAutofillHandler} = importAutofillModule("FormAutofillContent.jsm");
+
+const TESTCASES = [
+ {
+ description: "Form without autocomplete property",
+ document: `<form><input id="given-name"><input id="family-name">
+ <input id="street-addr"><input id="city"><input id="country">
+ <input id='email'><input id="tel"></form>`,
+ fieldDetails: [],
+ profileData: [],
+ expectedResult: {
+ "given-name": "",
+ "family-name": "",
+ "street-addr": "",
+ "city": "",
+ "country": "",
+ "email": "",
+ "tel": "",
+ },
+ },
+ {
+ description: "Form with autocomplete properties and 1 token",
+ document: `<form><input id="given-name" autocomplete="given-name">
+ <input id="family-name" autocomplete="family-name">
+ <input id="street-addr" autocomplete="street-address">
+ <input id="city" autocomplete="address-level2">
+ <input id="country" autocomplete="country">
+ <input id="email" autocomplete="email">
+ <input id="tel" autocomplete="tel"></form>`,
+ fieldDetails: [
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "email", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "element": {}},
+ ],
+ profileData: [
+ {"section": "", "addressType": "", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
+ {"section": "", "addressType": "", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
+ {"section": "", "addressType": "", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
+ {"section": "", "addressType": "", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
+ {"section": "", "addressType": "", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
+ {"section": "", "addressType": "", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
+ {"section": "", "addressType": "", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
+ ],
+ expectedResult: {
+ "given-name": "foo",
+ "family-name": "bar",
+ "street-addr": "2 Harrison St",
+ "city": "San Francisco",
+ "country": "US",
+ "email": "foo@mozilla.com",
+ "tel": "1234567",
+ },
+ },
+ {
+ description: "Form with autocomplete properties and 2 tokens",
+ document: `<form><input id="given-name" autocomplete="shipping given-name">
+ <input id="family-name" autocomplete="shipping family-name">
+ <input id="street-addr" autocomplete="shipping street-address">
+ <input id="city" autocomplete="shipping address-level2">
+ <input id="country" autocomplete="shipping country">
+ <input id='email' autocomplete="shipping email">
+ <input id="tel" autocomplete="shipping tel"></form>`,
+ fieldDetails: [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
+ ],
+ profileData: [
+ {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
+ {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
+ {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
+ {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
+ {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
+ {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
+ {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
+ ],
+ expectedResult: {
+ "given-name": "foo",
+ "family-name": "bar",
+ "street-addr": "2 Harrison St",
+ "city": "San Francisco",
+ "country": "US",
+ "email": "foo@mozilla.com",
+ "tel": "1234567",
+ },
+ },
+ {
+ description: "Form with autocomplete properties and profile is partly matched",
+ document: `<form><input id="given-name" autocomplete="shipping given-name">
+ <input id="family-name" autocomplete="shipping family-name">
+ <input id="street-addr" autocomplete="shipping street-address">
+ <input id="city" autocomplete="shipping address-level2">
+ <input id="country" autocomplete="shipping country">
+ <input id='email' autocomplete="shipping email">
+ <input id="tel" autocomplete="shipping tel"></form>`,
+ fieldDetails: [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
+ ],
+ profileData: [
+ {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
+ {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
+ {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
+ {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
+ {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
+ {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5},
+ {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6},
+ ],
+ expectedResult: {
+ "given-name": "foo",
+ "family-name": "bar",
+ "street-addr": "2 Harrison St",
+ "city": "San Francisco",
+ "country": "US",
+ "email": "",
+ "tel": "",
+ },
+ },
+ {
+ description: "Form with autocomplete properties but mismatched",
+ document: `<form><input id="given-name" autocomplete="shipping given-name">
+ <input id="family-name" autocomplete="shipping family-name">
+ <input id="street-addr" autocomplete="billing street-address">
+ <input id="city" autocomplete="billing address-level2">
+ <input id="country" autocomplete="billing country">
+ <input id='email' autocomplete="shipping email">
+ <input id="tel" autocomplete="shipping tel"></form>`,
+ fieldDetails: [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
+ ],
+ profileData: [
+ {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
+ {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
+ {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
+ {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
+ {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
+ {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
+ {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
+ ],
+ expectedResult: {
+ "given-name": "foo",
+ "family-name": "bar",
+ "street-addr": "",
+ "city": "",
+ "country": "",
+ "email": "foo@mozilla.com",
+ "tel": "1234567",
+ },
+ },
+];
+
+for (let tc of TESTCASES) {
+ (function() {
+ let testcase = tc;
+ add_task(function* () {
+ do_print("Starting testcase: " + testcase.description);
+
+ let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
+ testcase.document);
+ let form = doc.querySelector("form");
+ let handler = new FormAutofillHandler(form);
+
+ handler.fieldDetails = testcase.fieldDetails;
+ handler.fieldDetails.forEach((field, index) => {
+ field.element = doc.querySelectorAll("input")[index];
+ });
+
+ handler.autofillFormFields(testcase.profileData);
+ for (let id in testcase.expectedResult) {
+ Assert.equal(doc.getElementById(id).value, testcase.expectedResult[id],
+ "Check the " + id + " fields were filled with correct data");
+ }
+ });
+ })();
+}
diff --git a/browser/extensions/formautofill/test/unit/test_collectFormFields.js b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
new file mode 100644
index 000000000..52549b746
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -0,0 +1,122 @@
+/*
+ * Test for form auto fill content helper collectFormFields functions.
+ */
+
+"use strict";
+
+let {FormAutofillHandler} = importAutofillModule("FormAutofillContent.jsm");
+
+const TESTCASES = [
+ {
+ description: "Form without autocomplete property",
+ document: `<form><input id="given-name"><input id="family-name">
+ <input id="street-addr"><input id="city"><input id="country">
+ <input id='email'><input id="tel"></form>`,
+ returnedFormat: [],
+ fieldDetails: [],
+ },
+ {
+ description: "Form with autocomplete properties and 1 token",
+ document: `<form><input id="given-name" autocomplete="given-name">
+ <input id="family-name" autocomplete="family-name">
+ <input id="street-addr" autocomplete="street-address">
+ <input id="city" autocomplete="address-level2">
+ <input id="country" autocomplete="country">
+ <input id="email" autocomplete="email">
+ <input id="tel" autocomplete="tel"></form>`,
+ returnedFormat: [
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "index": 0},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "index": 1},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "index": 2},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "index": 3},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "index": 4},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "email", "index": 5},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "index": 6},
+ ],
+ fieldDetails: [
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "email", "element": {}},
+ {"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "element": {}},
+ ],
+ },
+ {
+ description: "Form with autocomplete properties and 2 tokens",
+ document: `<form><input id="given-name" autocomplete="shipping given-name">
+ <input id="family-name" autocomplete="shipping family-name">
+ <input id="street-addr" autocomplete="shipping street-address">
+ <input id="city" autocomplete="shipping address-level2">
+ <input id="country" autocomplete="shipping country">
+ <input id='email' autocomplete="shipping email">
+ <input id="tel" autocomplete="shipping tel"></form>`,
+ returnedFormat: [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "index": 0},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "index": 1},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "index": 2},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "index": 3},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "index": 4},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "index": 5},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "index": 6},
+ ],
+ fieldDetails: [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
+ ],
+ },
+ {
+ description: "Form with autocomplete properties and profile is partly matched",
+ document: `<form><input id="given-name" autocomplete="shipping given-name">
+ <input id="family-name" autocomplete="shipping family-name">
+ <input id="street-addr" autocomplete="shipping street-address">
+ <input id="city" autocomplete="shipping address-level2">
+ <input id="country" autocomplete="shipping country">
+ <input id='email' autocomplete="shipping email">
+ <input id="tel" autocomplete="shipping tel"></form>`,
+ returnedFormat: [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "index": 0},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "index": 1},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "index": 2},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "index": 3},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "index": 4},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "index": 5},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "index": 6},
+ ],
+ fieldDetails: [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
+ ],
+ },
+];
+
+for (let tc of TESTCASES) {
+ (function() {
+ let testcase = tc;
+ add_task(function* () {
+ do_print("Starting testcase: " + testcase.description);
+
+ let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
+ testcase.document);
+ let form = doc.querySelector("form");
+ let handler = new FormAutofillHandler(form);
+
+ Assert.deepEqual(handler.collectFormFields(), testcase.returnedFormat,
+ "Check the format of form autofill were returned correctly");
+
+ Assert.deepEqual(handler.fieldDetails, testcase.fieldDetails,
+ "Check the fieldDetails were set correctly");
+ });
+ })();
+}
diff --git a/browser/extensions/formautofill/test/unit/test_populateFieldValues.js b/browser/extensions/formautofill/test/unit/test_populateFieldValues.js
new file mode 100644
index 000000000..1215cbd16
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_populateFieldValues.js
@@ -0,0 +1,106 @@
+/*
+ * Test for populating field values in Form Autofill Parent.
+ */
+
+/* global FormAutofillParent */
+
+"use strict";
+
+importAutofillModule("FormAutofillParent.jsm");
+
+do_get_profile();
+
+const TEST_FIELDS = [
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "organization"},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level1"},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "postal-code"},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+ {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+];
+
+const TEST_GUID = "test-guid";
+
+const TEST_PROFILE = {
+ guid: TEST_GUID,
+ organization: "World Wide Web Consortium",
+ streetAddress: "32 Vassar Street\nMIT Room 32-G524",
+ addressLevel2: "Cambridge",
+ addressLevel1: "MA",
+ postalCode: "02139",
+ country: "US",
+ tel: "+1 617 253 5702",
+ email: "timbl@w3.org",
+};
+
+function camelCase(str) {
+ return str.toLowerCase().replace(/-([a-z])/g, s => s[1].toUpperCase());
+}
+
+add_task(function* test_populateFieldValues() {
+ FormAutofillParent.init();
+
+ let store = FormAutofillParent.getProfileStore();
+ do_check_neq(store, null);
+
+ store.get = function(guid) {
+ do_check_eq(guid, TEST_GUID);
+ return store._clone(TEST_PROFILE);
+ };
+
+ let notifyUsedCalledCount = 0;
+ store.notifyUsed = function(guid) {
+ do_check_eq(guid, TEST_GUID);
+ notifyUsedCalledCount++;
+ };
+
+ yield new Promise((resolve) => {
+ FormAutofillParent.receiveMessage({
+ name: "FormAutofill:PopulateFieldValues",
+ data: {
+ guid: TEST_GUID,
+ fields: TEST_FIELDS,
+ },
+ target: {
+ sendAsyncMessage: function(name, data) {
+ do_check_eq(name, "FormAutofill:fillForm");
+
+ let fields = data.fields;
+ do_check_eq(fields.length, TEST_FIELDS.length);
+
+ for (let i = 0; i < fields.length; i++) {
+ do_check_eq(fields[i].fieldName, TEST_FIELDS[i].fieldName);
+ do_check_eq(fields[i].value,
+ TEST_PROFILE[camelCase(fields[i].fieldName)]);
+ }
+
+ resolve();
+ },
+ },
+ });
+ });
+
+ do_check_eq(notifyUsedCalledCount, 1);
+
+ FormAutofillParent._uninit();
+ do_check_null(FormAutofillParent.getProfileStore());
+});
+
+add_task(function* test_populateFieldValues_with_invalid_guid() {
+ FormAutofillParent.init();
+
+ Assert.throws(() => {
+ FormAutofillParent.receiveMessage({
+ name: "FormAutofill:PopulateFieldValues",
+ data: {
+ guid: "invalid-guid",
+ fields: TEST_FIELDS,
+ },
+ target: {},
+ });
+ }, /No matching profile\./);
+
+ FormAutofillParent._uninit();
+});
diff --git a/browser/extensions/formautofill/test/unit/test_profileStorage.js b/browser/extensions/formautofill/test/unit/test_profileStorage.js
new file mode 100644
index 000000000..018adedb8
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_profileStorage.js
@@ -0,0 +1,222 @@
+/**
+ * Tests ProfileStorage object.
+ */
+
+/* global ProfileStorage */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import(Services.io.newFileURI(do_get_file("ProfileStorage.jsm")).spec);
+
+const TEST_STORE_FILE_NAME = "test-profile.json";
+
+const TEST_PROFILE_1 = {
+ organization: "World Wide Web Consortium",
+ streetAddress: "32 Vassar Street\nMIT Room 32-G524",
+ addressLevel2: "Cambridge",
+ addressLevel1: "MA",
+ postalCode: "02139",
+ country: "US",
+ tel: "+1 617 253 5702",
+ email: "timbl@w3.org",
+};
+
+const TEST_PROFILE_2 = {
+ streetAddress: "Some Address",
+ country: "US",
+};
+
+const TEST_PROFILE_3 = {
+ streetAddress: "Other Address",
+ postalCode: "12345",
+};
+
+const TEST_PROFILE_WITH_INVALID_FIELD = {
+ streetAddress: "Another Address",
+ invalidField: "INVALID",
+};
+
+let prepareTestProfiles = Task.async(function* (path) {
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ profileStorage.add(TEST_PROFILE_1);
+ profileStorage.add(TEST_PROFILE_2);
+ yield profileStorage._saveImmediately();
+});
+
+let do_check_profile_matches = (profileWithMeta, profile) => {
+ for (let key in profile) {
+ do_check_eq(profileWithMeta[key], profile[key]);
+ }
+};
+
+add_task(function* test_initialize() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ do_check_eq(profileStorage._store.data.version, 1);
+ do_check_eq(profileStorage._store.data.profiles.length, 0);
+
+ let data = profileStorage._store.data;
+
+ yield profileStorage._saveImmediately();
+
+ profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ Assert.deepEqual(profileStorage._store.data, data);
+});
+
+add_task(function* test_getAll() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+ yield prepareTestProfiles(path);
+
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profiles = profileStorage.getAll();
+
+ do_check_eq(profiles.length, 2);
+ do_check_profile_matches(profiles[0], TEST_PROFILE_1);
+ do_check_profile_matches(profiles[1], TEST_PROFILE_2);
+
+ // Modifying output shouldn't affect the storage.
+ profiles[0].organization = "test";
+ do_check_profile_matches(profileStorage.getAll()[0], TEST_PROFILE_1);
+});
+
+add_task(function* test_get() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+ yield prepareTestProfiles(path);
+
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profiles = profileStorage.getAll();
+ let guid = profiles[0].guid;
+
+ let profile = profileStorage.get(guid);
+ do_check_profile_matches(profile, TEST_PROFILE_1);
+
+ // Modifying output shouldn't affect the storage.
+ profile.organization = "test";
+ do_check_profile_matches(profileStorage.get(guid), TEST_PROFILE_1);
+
+ Assert.throws(() => profileStorage.get("INVALID_GUID"),
+ /No matching profile\./);
+});
+
+add_task(function* test_add() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+ yield prepareTestProfiles(path);
+
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profiles = profileStorage.getAll();
+
+ do_check_eq(profiles.length, 2);
+
+ do_check_profile_matches(profiles[0], TEST_PROFILE_1);
+ do_check_profile_matches(profiles[1], TEST_PROFILE_2);
+
+ do_check_neq(profiles[0].guid, undefined);
+ do_check_neq(profiles[0].timeCreated, undefined);
+ do_check_eq(profiles[0].timeLastModified, profiles[0].timeCreated);
+ do_check_eq(profiles[0].timeLastUsed, 0);
+ do_check_eq(profiles[0].timesUsed, 0);
+
+ Assert.throws(() => profileStorage.add(TEST_PROFILE_WITH_INVALID_FIELD),
+ /"invalidField" is not a valid field\./);
+});
+
+add_task(function* test_update() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+ yield prepareTestProfiles(path);
+
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profiles = profileStorage.getAll();
+ let guid = profiles[1].guid;
+ let timeLastModified = profiles[1].timeLastModified;
+
+ do_check_neq(profiles[1].country, undefined);
+
+ profileStorage.update(guid, TEST_PROFILE_3);
+ yield profileStorage._saveImmediately();
+
+ profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profile = profileStorage.get(guid);
+
+ do_check_eq(profile.country, undefined);
+ do_check_neq(profile.timeLastModified, timeLastModified);
+ do_check_profile_matches(profile, TEST_PROFILE_3);
+
+ Assert.throws(
+ () => profileStorage.update("INVALID_GUID", TEST_PROFILE_3),
+ /No matching profile\./
+ );
+
+ Assert.throws(
+ () => profileStorage.update(guid, TEST_PROFILE_WITH_INVALID_FIELD),
+ /"invalidField" is not a valid field\./
+ );
+});
+
+add_task(function* test_notifyUsed() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+ yield prepareTestProfiles(path);
+
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profiles = profileStorage.getAll();
+ let guid = profiles[1].guid;
+ let timeLastUsed = profiles[1].timeLastUsed;
+ let timesUsed = profiles[1].timesUsed;
+
+ profileStorage.notifyUsed(guid);
+ yield profileStorage._saveImmediately();
+
+ profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profile = profileStorage.get(guid);
+
+ do_check_eq(profile.timesUsed, timesUsed + 1);
+ do_check_neq(profile.timeLastUsed, timeLastUsed);
+
+ Assert.throws(() => profileStorage.notifyUsed("INVALID_GUID"),
+ /No matching profile\./);
+});
+
+add_task(function* test_remove() {
+ let path = getTempFile(TEST_STORE_FILE_NAME).path;
+ yield prepareTestProfiles(path);
+
+ let profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ let profiles = profileStorage.getAll();
+ let guid = profiles[1].guid;
+
+ do_check_eq(profiles.length, 2);
+
+ profileStorage.remove(guid);
+ yield profileStorage._saveImmediately();
+
+ profileStorage = new ProfileStorage(path);
+ yield profileStorage.initialize();
+
+ profiles = profileStorage.getAll();
+
+ do_check_eq(profiles.length, 1);
+
+ Assert.throws(() => profileStorage.get(guid), /No matching profile\./);
+});
diff --git a/browser/extensions/formautofill/test/unit/xpcshell.ini b/browser/extensions/formautofill/test/unit/xpcshell.ini
new file mode 100644
index 000000000..2c5763681
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+head = head.js
+tail =
+support-files =
+ ../../content/FormAutofillContent.jsm
+ ../../content/FormAutofillParent.jsm
+ ../../content/ProfileStorage.jsm
+
+[test_autofillFormFields.js]
+[test_collectFormFields.js]
+[test_populateFieldValues.js]
+[test_profileStorage.js]
diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build
new file mode 100644
index 000000000..9b01ed095
--- /dev/null
+++ b/browser/extensions/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'aushelper',
+ 'e10srollout',
+ 'pdfjs',
+ 'pocket',
+ 'webcompat',
+]
+
+# Only include the following system add-ons if building Aurora or Nightly
+if 'a' in CONFIG['GRE_MILESTONE']:
+ DIRS += [
+ 'flyweb',
+ 'formautofill',
+ ]
diff --git a/browser/extensions/pdfjs/LICENSE b/browser/extensions/pdfjs/LICENSE
new file mode 100644
index 000000000..f433b1a53
--- /dev/null
+++ b/browser/extensions/pdfjs/LICENSE
@@ -0,0 +1,177 @@
+
+ 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
diff --git a/browser/extensions/pdfjs/README.mozilla b/browser/extensions/pdfjs/README.mozilla
new file mode 100644
index 000000000..884f85eb3
--- /dev/null
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -0,0 +1,3 @@
+This is the pdf.js project output, https://github.com/mozilla/pdf.js
+
+Current extension version is: 1.6.315
diff --git a/browser/extensions/pdfjs/chrome.manifest b/browser/extensions/pdfjs/chrome.manifest
new file mode 100644
index 000000000..1aef83abc
--- /dev/null
+++ b/browser/extensions/pdfjs/chrome.manifest
@@ -0,0 +1 @@
+resource pdf.js content/
diff --git a/browser/extensions/pdfjs/content/PdfJs.jsm b/browser/extensions/pdfjs/content/PdfJs.jsm
new file mode 100644
index 000000000..b3d85436e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -0,0 +1,348 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+/* jshint esnext:true */
+/* globals Components, Services, XPCOMUtils, PdfjsChromeUtils,
+ PdfjsContentUtils, PdfStreamConverter */
+
+'use strict';
+
+var EXPORTED_SYMBOLS = ['PdfJs'];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cm = Components.manager;
+const Cu = Components.utils;
+
+const PREF_PREFIX = 'pdfjs';
+const PREF_DISABLED = PREF_PREFIX + '.disabled';
+const PREF_MIGRATION_VERSION = PREF_PREFIX + '.migrationVersion';
+const PREF_PREVIOUS_ACTION = PREF_PREFIX + '.previousHandler.preferredAction';
+const PREF_PREVIOUS_ASK = PREF_PREFIX +
+ '.previousHandler.alwaysAskBeforeHandling';
+const PREF_DISABLED_PLUGIN_TYPES = 'plugin.disable_full_page_plugin_for_types';
+const TOPIC_PDFJS_HANDLER_CHANGED = 'pdfjs:handlerChanged';
+const TOPIC_PLUGINS_LIST_UPDATED = 'plugins-list-updated';
+const TOPIC_PLUGIN_INFO_UPDATED = 'plugin-info-updated';
+const PDF_CONTENT_TYPE = 'application/pdf';
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+var Svc = {};
+XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
+ '@mozilla.org/mime;1',
+ 'nsIMIMEService');
+XPCOMUtils.defineLazyServiceGetter(Svc, 'pluginHost',
+ '@mozilla.org/plugin/host;1',
+ 'nsIPluginHost');
+XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsChromeUtils',
+ 'resource://pdf.js/PdfjsChromeUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsContentUtils',
+ 'resource://pdf.js/PdfjsContentUtils.jsm');
+
+function getBoolPref(aPref, aDefaultValue) {
+ try {
+ return Services.prefs.getBoolPref(aPref);
+ } catch (ex) {
+ return aDefaultValue;
+ }
+}
+
+function getIntPref(aPref, aDefaultValue) {
+ try {
+ return Services.prefs.getIntPref(aPref);
+ } catch (ex) {
+ return aDefaultValue;
+ }
+}
+
+function isDefaultHandler() {
+ if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+ return PdfjsContentUtils.isDefaultHandlerApp();
+ }
+ return PdfjsChromeUtils.isDefaultHandlerApp();
+}
+
+function initializeDefaultPreferences() {
+ var DEFAULT_PREFERENCES =
+{
+ "showPreviousViewOnLoad": true,
+ "defaultZoomValue": "",
+ "sidebarViewOnLoad": 0,
+ "enableHandToolOnLoad": false,
+ "enableWebGL": false,
+ "pdfBugEnabled": false,
+ "disableRange": false,
+ "disableStream": false,
+ "disableAutoFetch": false,
+ "disableFontFace": false,
+ "disableTextLayer": false,
+ "useOnlyCssZoom": false,
+ "externalLinkTarget": 0,
+ "enhanceTextSelection": false,
+ "renderInteractiveForms": false,
+ "disablePageLabels": false
+}
+
+
+ var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
+ var defaultValue;
+ for (var key in DEFAULT_PREFERENCES) {
+ defaultValue = DEFAULT_PREFERENCES[key];
+ switch (typeof defaultValue) {
+ case 'boolean':
+ defaultBranch.setBoolPref(key, defaultValue);
+ break;
+ case 'number':
+ defaultBranch.setIntPref(key, defaultValue);
+ break;
+ case 'string':
+ defaultBranch.setCharPref(key, defaultValue);
+ break;
+ }
+ }
+}
+
+// Register/unregister a constructor as a factory.
+function Factory() {}
+Factory.prototype = {
+ register: function register(targetConstructor) {
+ var proto = targetConstructor.prototype;
+ this._classID = proto.classID;
+
+ var factory = XPCOMUtils._getFactory(targetConstructor);
+ this._factory = factory;
+
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(proto.classID, proto.classDescription,
+ proto.contractID, factory);
+
+ if (proto.classID2) {
+ this._classID2 = proto.classID2;
+ registrar.registerFactory(proto.classID2, proto.classDescription,
+ proto.contractID2, factory);
+ }
+ },
+
+ unregister: function unregister() {
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(this._classID, this._factory);
+ if (this._classID2) {
+ registrar.unregisterFactory(this._classID2, this._factory);
+ }
+ this._factory = null;
+ }
+};
+
+var PdfJs = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+ _registered: false,
+ _initialized: false,
+
+ init: function init(remote) {
+ if (Services.appinfo.processType !==
+ Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ throw new Error('PdfJs.init should only get called ' +
+ 'in the parent process.');
+ }
+ PdfjsChromeUtils.init();
+ if (!remote) {
+ PdfjsContentUtils.init();
+ }
+ this.initPrefs();
+ this.updateRegistration();
+ },
+
+ initPrefs: function initPrefs() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+
+ if (!getBoolPref(PREF_DISABLED, true)) {
+ this._migrate();
+ }
+
+ // Listen for when pdf.js is completely disabled or a different pdf handler
+ // is chosen.
+ Services.prefs.addObserver(PREF_DISABLED, this, false);
+ Services.prefs.addObserver(PREF_DISABLED_PLUGIN_TYPES, this, false);
+ Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false);
+ Services.obs.addObserver(this, TOPIC_PLUGINS_LIST_UPDATED, false);
+ Services.obs.addObserver(this, TOPIC_PLUGIN_INFO_UPDATED, false);
+
+ initializeDefaultPreferences();
+ },
+
+ updateRegistration: function updateRegistration() {
+ if (this.enabled) {
+ this._ensureRegistered();
+ } else {
+ this._ensureUnregistered();
+ }
+ },
+
+ uninit: function uninit() {
+ if (this._initialized) {
+ Services.prefs.removeObserver(PREF_DISABLED, this, false);
+ Services.prefs.removeObserver(PREF_DISABLED_PLUGIN_TYPES, this, false);
+ Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false);
+ Services.obs.removeObserver(this, TOPIC_PLUGINS_LIST_UPDATED, false);
+ Services.obs.removeObserver(this, TOPIC_PLUGIN_INFO_UPDATED, false);
+ this._initialized = false;
+ }
+ this._ensureUnregistered();
+ },
+
+ _migrate: function migrate() {
+ const VERSION = 2;
+ var currentVersion = getIntPref(PREF_MIGRATION_VERSION, 0);
+ if (currentVersion >= VERSION) {
+ return;
+ }
+ // Make pdf.js the default pdf viewer on the first migration.
+ if (currentVersion < 1) {
+ this._becomeHandler();
+ }
+ if (currentVersion < 2) {
+ // cleaning up of unused database preference (see #3994)
+ Services.prefs.clearUserPref(PREF_PREFIX + '.database');
+ }
+ Services.prefs.setIntPref(PREF_MIGRATION_VERSION, VERSION);
+ },
+
+ _becomeHandler: function _becomeHandler() {
+ let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf');
+ let prefs = Services.prefs;
+ if (handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally &&
+ handlerInfo.preferredAction !== false) {
+ // Store the previous settings of preferredAction and
+ // alwaysAskBeforeHandling in case we need to revert them in a hotfix that
+ // would turn pdf.js off.
+ prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction);
+ prefs.setBoolPref(PREF_PREVIOUS_ASK, handlerInfo.alwaysAskBeforeHandling);
+ }
+
+ let handlerService = Cc['@mozilla.org/uriloader/handler-service;1'].
+ getService(Ci.nsIHandlerService);
+
+ // Change and save mime handler settings.
+ handlerInfo.alwaysAskBeforeHandling = false;
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
+ handlerService.store(handlerInfo);
+
+ // Also disable any plugins for pdfs.
+ var stringTypes = '';
+ var types = [];
+ if (prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
+ stringTypes = prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
+ }
+ if (stringTypes !== '') {
+ types = stringTypes.split(',');
+ }
+
+ if (types.indexOf(PDF_CONTENT_TYPE) === -1) {
+ types.push(PDF_CONTENT_TYPE);
+ }
+ prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES, types.join(','));
+
+ // Update the category manager in case the plugins are already loaded.
+ let categoryManager = Cc['@mozilla.org/categorymanager;1'];
+ categoryManager.getService(Ci.nsICategoryManager).
+ deleteCategoryEntry('Gecko-Content-Viewers',
+ PDF_CONTENT_TYPE,
+ false);
+ },
+
+ // nsIObserver
+ observe: function observe(aSubject, aTopic, aData) {
+ this.updateRegistration();
+ if (Services.appinfo.processType ===
+ Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ let jsm = 'resource://pdf.js/PdfjsChromeUtils.jsm';
+ let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
+ PdfjsChromeUtils.notifyChildOfSettingsChange();
+ }
+ },
+
+ /**
+ * pdf.js is only enabled if it is both selected as the pdf viewer and if the
+ * global switch enabling it is true.
+ * @return {boolean} Whether or not it's enabled.
+ */
+ get enabled() {
+ var disabled = getBoolPref(PREF_DISABLED, true);
+ if (disabled) {
+ return false;
+ }
+
+ // Check if the 'application/pdf' preview handler is configured properly.
+ if (!isDefaultHandler()) {
+ return false;
+ }
+
+ // Check if we have disabled plugin handling of 'application/pdf' in prefs
+ if (Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) {
+ let disabledPluginTypes =
+ Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES).split(',');
+ if (disabledPluginTypes.indexOf(PDF_CONTENT_TYPE) >= 0) {
+ return true;
+ }
+ }
+
+ // Check if there is an enabled pdf plugin.
+ // Note: this check is performed last because getPluginTags() triggers
+ // costly plugin list initialization (bug 881575)
+ let tags = Cc['@mozilla.org/plugin/host;1'].
+ getService(Ci.nsIPluginHost).
+ getPluginTags();
+ let enabledPluginFound = tags.some(function(tag) {
+ if (tag.disabled) {
+ return false;
+ }
+ let mimeTypes = tag.getMimeTypes();
+ return mimeTypes.some(function(mimeType) {
+ return mimeType === PDF_CONTENT_TYPE;
+ });
+ });
+
+ // Use pdf.js if pdf plugin is not present or disabled
+ return !enabledPluginFound;
+ },
+
+ _ensureRegistered: function _ensureRegistered() {
+ if (this._registered) {
+ return;
+ }
+ this._pdfStreamConverterFactory = new Factory();
+ Cu.import('resource://pdf.js/PdfStreamConverter.jsm');
+ this._pdfStreamConverterFactory.register(PdfStreamConverter);
+
+ this._registered = true;
+ },
+
+ _ensureUnregistered: function _ensureUnregistered() {
+ if (!this._registered) {
+ return;
+ }
+ this._pdfStreamConverterFactory.unregister();
+ Cu.unload('resource://pdf.js/PdfStreamConverter.jsm');
+ delete this._pdfStreamConverterFactory;
+
+ this._registered = false;
+ }
+};
+
diff --git a/browser/extensions/pdfjs/content/PdfJsNetwork.jsm b/browser/extensions/pdfjs/content/PdfJsNetwork.jsm
new file mode 100644
index 000000000..c3aefecb2
--- /dev/null
+++ b/browser/extensions/pdfjs/content/PdfJsNetwork.jsm
@@ -0,0 +1,257 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+/* globals Components, Services */
+
+'use strict';
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+var EXPORTED_SYMBOLS = ['NetworkManager'];
+
+function log(aMsg) {
+ var msg = 'network.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
+ Services.console.logStringMessage(msg);
+}
+
+var NetworkManager = (function NetworkManagerClosure() {
+
+ var OK_RESPONSE = 200;
+ var PARTIAL_CONTENT_RESPONSE = 206;
+
+ function NetworkManager(url, args) {
+ this.url = url;
+ args = args || {};
+ this.isHttp = /^https?:/i.test(url);
+ this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
+ this.withCredentials = args.withCredentials || false;
+ this.getXhr = args.getXhr ||
+ function NetworkManager_getXhr() {
+ return new XMLHttpRequest();
+ };
+
+ this.currXhrId = 0;
+ this.pendingRequests = Object.create(null);
+ this.loadedRequests = Object.create(null);
+ }
+
+ function getArrayBuffer(xhr) {
+ var data = xhr.response;
+ if (typeof data !== 'string') {
+ return data;
+ }
+ var length = data.length;
+ var array = new Uint8Array(length);
+ for (var i = 0; i < length; i++) {
+ array[i] = data.charCodeAt(i) & 0xFF;
+ }
+ return array.buffer;
+ }
+
+ NetworkManager.prototype = {
+ requestRange: function NetworkManager_requestRange(begin, end, listeners) {
+ var args = {
+ begin: begin,
+ end: end
+ };
+ for (var prop in listeners) {
+ args[prop] = listeners[prop];
+ }
+ return this.request(args);
+ },
+
+ requestFull: function NetworkManager_requestFull(listeners) {
+ return this.request(listeners);
+ },
+
+ request: function NetworkManager_request(args) {
+ var xhr = this.getXhr();
+ var xhrId = this.currXhrId++;
+ var pendingRequest = this.pendingRequests[xhrId] = {
+ xhr: xhr
+ };
+
+ xhr.open('GET', this.url);
+ xhr.withCredentials = this.withCredentials;
+ for (var property in this.httpHeaders) {
+ var value = this.httpHeaders[property];
+ if (typeof value === 'undefined') {
+ continue;
+ }
+ xhr.setRequestHeader(property, value);
+ }
+ if (this.isHttp && 'begin' in args && 'end' in args) {
+ var rangeStr = args.begin + '-' + (args.end - 1);
+ xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
+ pendingRequest.expectedStatus = 206;
+ } else {
+ pendingRequest.expectedStatus = 200;
+ }
+
+ var useMozChunkedLoading = !!args.onProgressiveData;
+ if (useMozChunkedLoading) {
+ xhr.responseType = 'moz-chunked-arraybuffer';
+ pendingRequest.onProgressiveData = args.onProgressiveData;
+ pendingRequest.mozChunked = true;
+ } else {
+ xhr.responseType = 'arraybuffer';
+ }
+
+ if (args.onError) {
+ xhr.onerror = function(evt) {
+ args.onError(xhr.status);
+ };
+ }
+ xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
+ xhr.onprogress = this.onProgress.bind(this, xhrId);
+
+ pendingRequest.onHeadersReceived = args.onHeadersReceived;
+ pendingRequest.onDone = args.onDone;
+ pendingRequest.onError = args.onError;
+ pendingRequest.onProgress = args.onProgress;
+
+ xhr.send(null);
+
+ return xhrId;
+ },
+
+ onProgress: function NetworkManager_onProgress(xhrId, evt) {
+ var pendingRequest = this.pendingRequests[xhrId];
+ if (!pendingRequest) {
+ // Maybe abortRequest was called...
+ return;
+ }
+
+ if (pendingRequest.mozChunked) {
+ var chunk = getArrayBuffer(pendingRequest.xhr);
+ pendingRequest.onProgressiveData(chunk);
+ }
+
+ var onProgress = pendingRequest.onProgress;
+ if (onProgress) {
+ onProgress(evt);
+ }
+ },
+
+ onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
+ var pendingRequest = this.pendingRequests[xhrId];
+ if (!pendingRequest) {
+ // Maybe abortRequest was called...
+ return;
+ }
+
+ var xhr = pendingRequest.xhr;
+ if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
+ pendingRequest.onHeadersReceived();
+ delete pendingRequest.onHeadersReceived;
+ }
+
+ if (xhr.readyState !== 4) {
+ return;
+ }
+
+ if (!(xhrId in this.pendingRequests)) {
+ // The XHR request might have been aborted in onHeadersReceived()
+ // callback, in which case we should abort request
+ return;
+ }
+
+ delete this.pendingRequests[xhrId];
+
+ // success status == 0 can be on ftp, file and other protocols
+ if (xhr.status === 0 && this.isHttp) {
+ if (pendingRequest.onError) {
+ pendingRequest.onError(xhr.status);
+ }
+ return;
+ }
+ var xhrStatus = xhr.status || OK_RESPONSE;
+
+ // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
+ // "A server MAY ignore the Range header". This means it's possible to
+ // get a 200 rather than a 206 response from a range request.
+ var ok_response_on_range_request =
+ xhrStatus === OK_RESPONSE &&
+ pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
+
+ if (!ok_response_on_range_request &&
+ xhrStatus !== pendingRequest.expectedStatus) {
+ if (pendingRequest.onError) {
+ pendingRequest.onError(xhr.status);
+ }
+ return;
+ }
+
+ this.loadedRequests[xhrId] = true;
+
+ var chunk = getArrayBuffer(xhr);
+ if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
+ var rangeHeader = xhr.getResponseHeader('Content-Range');
+ var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
+ var begin = parseInt(matches[1], 10);
+ pendingRequest.onDone({
+ begin: begin,
+ chunk: chunk
+ });
+ } else if (pendingRequest.onProgressiveData) {
+ pendingRequest.onDone(null);
+ } else if (chunk) {
+ pendingRequest.onDone({
+ begin: 0,
+ chunk: chunk
+ });
+ } else if (pendingRequest.onError) {
+ pendingRequest.onError(xhr.status);
+ }
+ },
+
+ hasPendingRequests: function NetworkManager_hasPendingRequests() {
+ for (var xhrId in this.pendingRequests) {
+ return true;
+ }
+ return false;
+ },
+
+ getRequestXhr: function NetworkManager_getXhr(xhrId) {
+ return this.pendingRequests[xhrId].xhr;
+ },
+
+ isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
+ return !!(this.pendingRequests[xhrId].onProgressiveData);
+ },
+
+ isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
+ return xhrId in this.pendingRequests;
+ },
+
+ isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
+ return xhrId in this.loadedRequests;
+ },
+
+ abortAllRequests: function NetworkManager_abortAllRequests() {
+ for (var xhrId in this.pendingRequests) {
+ this.abortRequest(xhrId | 0);
+ }
+ },
+
+ abortRequest: function NetworkManager_abortRequest(xhrId) {
+ var xhr = this.pendingRequests[xhrId].xhr;
+ delete this.pendingRequests[xhrId];
+ xhr.abort();
+ }
+ };
+
+ return NetworkManager;
+})();
+
diff --git a/browser/extensions/pdfjs/content/PdfJsTelemetry.jsm b/browser/extensions/pdfjs/content/PdfJsTelemetry.jsm
new file mode 100644
index 000000000..275da9d87
--- /dev/null
+++ b/browser/extensions/pdfjs/content/PdfJsTelemetry.jsm
@@ -0,0 +1,70 @@
+/* Copyright 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+/* jshint esnext:true, maxlen: 100 */
+/* globals Components, Services */
+
+'use strict';
+
+this.EXPORTED_SYMBOLS = ['PdfJsTelemetry'];
+
+const Cu = Components.utils;
+Cu.import('resource://gre/modules/Services.jsm');
+
+this.PdfJsTelemetry = {
+ onViewerIsUsed: function () {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_USED');
+ histogram.add(true);
+ },
+ onFallback: function () {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FALLBACK_SHOWN');
+ histogram.add(true);
+ },
+ onDocumentSize: function (size) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_SIZE_KB');
+ histogram.add(size / 1024);
+ },
+ onDocumentVersion: function (versionId) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_VERSION');
+ histogram.add(versionId);
+ },
+ onDocumentGenerator: function (generatorId) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_DOCUMENT_GENERATOR');
+ histogram.add(generatorId);
+ },
+ onEmbed: function (isObject) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_EMBED');
+ histogram.add(isObject);
+ },
+ onFontType: function (fontTypeId) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FONT_TYPES');
+ histogram.add(fontTypeId);
+ },
+ onForm: function (isAcroform) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_FORM');
+ histogram.add(isAcroform);
+ },
+ onPrint: function () {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_PRINT');
+ histogram.add(true);
+ },
+ onStreamType: function (streamTypeId) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_STREAM_TYPES');
+ histogram.add(streamTypeId);
+ },
+ onTimeToView: function (ms) {
+ let histogram = Services.telemetry.getHistogramById('PDF_VIEWER_TIME_TO_VIEW_MS');
+ histogram.add(ms);
+ }
+};
diff --git a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
new file mode 100644
index 000000000..3b9f9de26
--- /dev/null
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -0,0 +1,1045 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+/* jshint esnext:true */
+/* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
+ dump, NetworkManager, PdfJsTelemetry, PdfjsContentUtils */
+
+'use strict';
+
+var EXPORTED_SYMBOLS = ['PdfStreamConverter'];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+// True only if this is the version of pdf.js that is included with firefox.
+const MOZ_CENTRAL = JSON.parse('true');
+const PDFJS_EVENT_ID = 'pdf.js.message';
+const PDF_CONTENT_TYPE = 'application/pdf';
+const PREF_PREFIX = 'pdfjs';
+const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
+const MAX_NUMBER_OF_PREFS = 50;
+const MAX_STRING_PREF_LENGTH = 128;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/NetUtil.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'NetworkManager',
+ 'resource://pdf.js/PdfJsNetwork.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
+ 'resource://gre/modules/PrivateBrowsingUtils.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'PdfJsTelemetry',
+ 'resource://pdf.js/PdfJsTelemetry.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsContentUtils',
+ 'resource://pdf.js/PdfjsContentUtils.jsm');
+
+var Svc = {};
+XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
+ '@mozilla.org/mime;1',
+ 'nsIMIMEService');
+
+function getContainingBrowser(domWindow) {
+ return domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+}
+
+function getFindBar(domWindow) {
+ if (PdfjsContentUtils.isRemote) {
+ throw new Error('FindBar is not accessible from the content process.');
+ }
+ try {
+ var browser = getContainingBrowser(domWindow);
+ var tabbrowser = browser.getTabBrowser();
+ var tab = tabbrowser.getTabForBrowser(browser);
+ return tabbrowser.getFindBar(tab);
+ } catch (e) {
+ // Suppress errors for PDF files opened in the bookmark sidebar, see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1248959.
+ return null;
+ }
+}
+
+function getBoolPref(pref, def) {
+ try {
+ return Services.prefs.getBoolPref(pref);
+ } catch (ex) {
+ return def;
+ }
+}
+
+function getIntPref(pref, def) {
+ try {
+ return Services.prefs.getIntPref(pref);
+ } catch (ex) {
+ return def;
+ }
+}
+
+function getStringPref(pref, def) {
+ try {
+ return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data;
+ } catch (ex) {
+ return def;
+ }
+}
+
+function log(aMsg) {
+ if (!getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false)) {
+ return;
+ }
+ var msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
+ Services.console.logStringMessage(msg);
+ dump(msg + '\n');
+}
+
+function getDOMWindow(aChannel) {
+ var requestor = aChannel.notificationCallbacks ?
+ aChannel.notificationCallbacks :
+ aChannel.loadGroup.notificationCallbacks;
+ var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
+ return win;
+}
+
+function getLocalizedStrings(path) {
+ var stringBundle = Cc['@mozilla.org/intl/stringbundle;1'].
+ getService(Ci.nsIStringBundleService).
+ createBundle('chrome://pdf.js/locale/' + path);
+
+ var map = {};
+ var enumerator = stringBundle.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ var string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ var key = string.key, property = 'textContent';
+ var i = key.lastIndexOf('.');
+ if (i >= 0) {
+ property = key.substring(i + 1);
+ key = key.substring(0, i);
+ }
+ if (!(key in map)) {
+ map[key] = {};
+ }
+ map[key][property] = string.value;
+ }
+ return map;
+}
+function getLocalizedString(strings, id, property) {
+ property = property || 'textContent';
+ if (id in strings) {
+ return strings[id][property];
+ }
+ return id;
+}
+
+function createNewChannel(uri, node) {
+ return NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function asyncOpenChannel(channel, listener, context) {
+ return channel.asyncOpen2(listener);
+}
+
+function asyncFetchChannel(channel, callback) {
+ return NetUtil.asyncFetch(channel, callback);
+}
+
+// PDF data storage
+function PdfDataListener(length) {
+ this.length = length; // less than 0, if length is unknown
+ this.buffer = null;
+ this.loaded = 0;
+}
+
+PdfDataListener.prototype = {
+ append: function PdfDataListener_append(chunk) {
+ // In most of the cases we will pass data as we receive it, but at the
+ // beginning of the loading we may accumulate some data.
+ if (!this.buffer) {
+ this.buffer = new Uint8Array(chunk);
+ } else {
+ var buffer = this.buffer;
+ var newBuffer = new Uint8Array(buffer.length + chunk.length);
+ newBuffer.set(buffer);
+ newBuffer.set(chunk, buffer.length);
+ this.buffer = newBuffer;
+ }
+ this.loaded += chunk.length;
+ if (this.length >= 0 && this.length < this.loaded) {
+ this.length = -1; // reset the length, server is giving incorrect one
+ }
+ this.onprogress(this.loaded, this.length >= 0 ? this.length : void(0));
+ },
+ readData: function PdfDataListener_readData() {
+ var result = this.buffer;
+ this.buffer = null;
+ return result;
+ },
+ finish: function PdfDataListener_finish() {
+ this.isDataReady = true;
+ if (this.oncompleteCallback) {
+ this.oncompleteCallback(this.readData());
+ }
+ },
+ error: function PdfDataListener_error(errorCode) {
+ this.errorCode = errorCode;
+ if (this.oncompleteCallback) {
+ this.oncompleteCallback(null, errorCode);
+ }
+ },
+ onprogress: function() {},
+ get oncomplete() {
+ return this.oncompleteCallback;
+ },
+ set oncomplete(value) {
+ this.oncompleteCallback = value;
+ if (this.isDataReady) {
+ value(this.readData());
+ }
+ if (this.errorCode) {
+ value(null, this.errorCode);
+ }
+ }
+};
+
+// All the priviledged actions.
+function ChromeActions(domWindow, contentDispositionFilename) {
+ this.domWindow = domWindow;
+ this.contentDispositionFilename = contentDispositionFilename;
+ this.telemetryState = {
+ documentInfo: false,
+ firstPageInfo: false,
+ streamTypesUsed: [],
+ fontTypesUsed: [],
+ startAt: Date.now()
+ };
+}
+
+ChromeActions.prototype = {
+ isInPrivateBrowsing: function() {
+ return PrivateBrowsingUtils.isContentWindowPrivate(this.domWindow);
+ },
+ download: function(data, sendResponse) {
+ var self = this;
+ var originalUrl = data.originalUrl;
+ var blobUrl = data.blobUrl || originalUrl;
+ // The data may not be downloaded so we need just retry getting the pdf with
+ // the original url.
+ var originalUri = NetUtil.newURI(originalUrl);
+ var filename = data.filename;
+ if (typeof filename !== 'string' ||
+ (!/\.pdf$/i.test(filename) && !data.isAttachment)) {
+ filename = 'document.pdf';
+ }
+ var blobUri = NetUtil.newURI(blobUrl);
+ var extHelperAppSvc =
+ Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
+ getService(Ci.nsIExternalHelperAppService);
+
+ var docIsPrivate = this.isInPrivateBrowsing();
+ var netChannel = createNewChannel(blobUri, this.domWindow.document);
+ if ('nsIPrivateBrowsingChannel' in Ci &&
+ netChannel instanceof Ci.nsIPrivateBrowsingChannel) {
+ netChannel.setPrivate(docIsPrivate);
+ }
+ asyncFetchChannel(netChannel, function(aInputStream, aResult) {
+ if (!Components.isSuccessCode(aResult)) {
+ if (sendResponse) {
+ sendResponse(true);
+ }
+ return;
+ }
+ // Create a nsIInputStreamChannel so we can set the url on the channel
+ // so the filename will be correct.
+ var channel = Cc['@mozilla.org/network/input-stream-channel;1'].
+ createInstance(Ci.nsIInputStreamChannel);
+ channel.QueryInterface(Ci.nsIChannel);
+ try {
+ // contentDisposition/contentDispositionFilename is readonly before FF18
+ channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
+ if (self.contentDispositionFilename && !data.isAttachment) {
+ channel.contentDispositionFilename = self.contentDispositionFilename;
+ } else {
+ channel.contentDispositionFilename = filename;
+ }
+ } catch (e) {}
+ channel.setURI(originalUri);
+ channel.loadInfo = netChannel.loadInfo;
+ channel.contentStream = aInputStream;
+ if ('nsIPrivateBrowsingChannel' in Ci &&
+ channel instanceof Ci.nsIPrivateBrowsingChannel) {
+ channel.setPrivate(docIsPrivate);
+ }
+
+ var listener = {
+ extListener: null,
+ onStartRequest: function(aRequest, aContext) {
+ var loadContext = self.domWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+ this.extListener = extHelperAppSvc.doContent(
+ (data.isAttachment ? 'application/octet-stream' :
+ 'application/pdf'),
+ aRequest, loadContext, false);
+ this.extListener.onStartRequest(aRequest, aContext);
+ },
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ if (this.extListener) {
+ this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+ }
+ // Notify the content code we're done downloading.
+ if (sendResponse) {
+ sendResponse(false);
+ }
+ },
+ onDataAvailable: function(aRequest, aContext, aInputStream, aOffset,
+ aCount) {
+ this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ }
+ };
+
+ asyncOpenChannel(channel, listener, null);
+ });
+ },
+ getLocale: function() {
+ return getStringPref('general.useragent.locale', 'en-US');
+ },
+ getStrings: function(data) {
+ try {
+ // Lazy initialization of localizedStrings
+ if (!('localizedStrings' in this)) {
+ this.localizedStrings = getLocalizedStrings('viewer.properties');
+ }
+ var result = this.localizedStrings[data];
+ return JSON.stringify(result || null);
+ } catch (e) {
+ log('Unable to retrieve localized strings: ' + e);
+ return 'null';
+ }
+ },
+ supportsIntegratedFind: function() {
+ // Integrated find is only supported when we're not in a frame
+ if (this.domWindow.frameElement !== null) {
+ return false;
+ }
+
+ // ... and we are in a child process
+ if (PdfjsContentUtils.isRemote) {
+ return true;
+ }
+
+ // ... or when the new find events code exists.
+ var findBar = getFindBar(this.domWindow);
+ return !!findBar && ('updateControlState' in findBar);
+ },
+ supportsDocumentFonts: function() {
+ var prefBrowser = getIntPref('browser.display.use_document_fonts', 1);
+ var prefGfx = getBoolPref('gfx.downloadable_fonts.enabled', true);
+ return (!!prefBrowser && prefGfx);
+ },
+ supportsDocumentColors: function() {
+ return getIntPref('browser.display.document_color_use', 0) !== 2;
+ },
+ supportedMouseWheelZoomModifierKeys: function() {
+ return {
+ ctrlKey: getIntPref('mousewheel.with_control.action', 3) === 3,
+ metaKey: getIntPref('mousewheel.with_meta.action', 1) === 3,
+ };
+ },
+ reportTelemetry: function (data) {
+ var probeInfo = JSON.parse(data);
+ switch (probeInfo.type) {
+ case 'documentInfo':
+ if (!this.telemetryState.documentInfo) {
+ PdfJsTelemetry.onDocumentVersion(probeInfo.version | 0);
+ PdfJsTelemetry.onDocumentGenerator(probeInfo.generator | 0);
+ if (probeInfo.formType) {
+ PdfJsTelemetry.onForm(probeInfo.formType === 'acroform');
+ }
+ this.telemetryState.documentInfo = true;
+ }
+ break;
+ case 'pageInfo':
+ if (!this.telemetryState.firstPageInfo) {
+ var duration = Date.now() - this.telemetryState.startAt;
+ PdfJsTelemetry.onTimeToView(duration);
+ this.telemetryState.firstPageInfo = true;
+ }
+ break;
+ case 'documentStats':
+ // documentStats can be called several times for one documents.
+ // if stream/font types are reported, trying not to submit the same
+ // enumeration value multiple times.
+ var documentStats = probeInfo.stats;
+ if (!documentStats || typeof documentStats !== 'object') {
+ break;
+ }
+ var i, streamTypes = documentStats.streamTypes;
+ if (Array.isArray(streamTypes)) {
+ var STREAM_TYPE_ID_LIMIT = 20;
+ for (i = 0; i < STREAM_TYPE_ID_LIMIT; i++) {
+ if (streamTypes[i] &&
+ !this.telemetryState.streamTypesUsed[i]) {
+ PdfJsTelemetry.onStreamType(i);
+ this.telemetryState.streamTypesUsed[i] = true;
+ }
+ }
+ }
+ var fontTypes = documentStats.fontTypes;
+ if (Array.isArray(fontTypes)) {
+ var FONT_TYPE_ID_LIMIT = 20;
+ for (i = 0; i < FONT_TYPE_ID_LIMIT; i++) {
+ if (fontTypes[i] &&
+ !this.telemetryState.fontTypesUsed[i]) {
+ PdfJsTelemetry.onFontType(i);
+ this.telemetryState.fontTypesUsed[i] = true;
+ }
+ }
+ }
+ break;
+ case 'print':
+ PdfJsTelemetry.onPrint();
+ break;
+ }
+ },
+ fallback: function(args, sendResponse) {
+ var featureId = args.featureId;
+ var url = args.url;
+
+ var self = this;
+ var domWindow = this.domWindow;
+ var strings = getLocalizedStrings('chrome.properties');
+ var message;
+ if (featureId === 'forms') {
+ message = getLocalizedString(strings, 'unsupported_feature_forms');
+ } else {
+ message = getLocalizedString(strings, 'unsupported_feature');
+ }
+ PdfJsTelemetry.onFallback();
+ PdfjsContentUtils.displayWarning(domWindow, message,
+ getLocalizedString(strings, 'open_with_different_viewer'),
+ getLocalizedString(strings, 'open_with_different_viewer', 'accessKey'));
+
+ let winmm = domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ winmm.addMessageListener('PDFJS:Child:fallbackDownload',
+ function fallbackDownload(msg) {
+ let data = msg.data;
+ sendResponse(data.download);
+
+ winmm.removeMessageListener('PDFJS:Child:fallbackDownload',
+ fallbackDownload);
+ });
+ },
+ updateFindControlState: function(data) {
+ if (!this.supportsIntegratedFind()) {
+ return;
+ }
+ // Verify what we're sending to the findbar.
+ var result = data.result;
+ var findPrevious = data.findPrevious;
+ var findPreviousType = typeof findPrevious;
+ if ((typeof result !== 'number' || result < 0 || result > 3) ||
+ (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
+ return;
+ }
+
+ var winmm = this.domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ winmm.sendAsyncMessage('PDFJS:Parent:updateControlState', data);
+ },
+ setPreferences: function(prefs, sendResponse) {
+ var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
+ var numberOfPrefs = 0;
+ var prefValue, prefName;
+ for (var key in prefs) {
+ if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
+ log('setPreferences - Exceeded the maximum number of preferences ' +
+ 'that is allowed to be set at once.');
+ break;
+ } else if (!defaultBranch.getPrefType(key)) {
+ continue;
+ }
+ prefValue = prefs[key];
+ prefName = (PREF_PREFIX + '.' + key);
+ switch (typeof prefValue) {
+ case 'boolean':
+ PdfjsContentUtils.setBoolPref(prefName, prefValue);
+ break;
+ case 'number':
+ PdfjsContentUtils.setIntPref(prefName, prefValue);
+ break;
+ case 'string':
+ if (prefValue.length > MAX_STRING_PREF_LENGTH) {
+ log('setPreferences - Exceeded the maximum allowed length ' +
+ 'for a string preference.');
+ } else {
+ PdfjsContentUtils.setStringPref(prefName, prefValue);
+ }
+ break;
+ }
+ }
+ if (sendResponse) {
+ sendResponse(true);
+ }
+ },
+ getPreferences: function(prefs, sendResponse) {
+ var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
+ var currentPrefs = {}, numberOfPrefs = 0;
+ var prefValue, prefName;
+ for (var key in prefs) {
+ if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
+ log('getPreferences - Exceeded the maximum number of preferences ' +
+ 'that is allowed to be fetched at once.');
+ break;
+ } else if (!defaultBranch.getPrefType(key)) {
+ continue;
+ }
+ prefValue = prefs[key];
+ prefName = (PREF_PREFIX + '.' + key);
+ switch (typeof prefValue) {
+ case 'boolean':
+ currentPrefs[key] = getBoolPref(prefName, prefValue);
+ break;
+ case 'number':
+ currentPrefs[key] = getIntPref(prefName, prefValue);
+ break;
+ case 'string':
+ currentPrefs[key] = getStringPref(prefName, prefValue);
+ break;
+ }
+ }
+ if (sendResponse) {
+ sendResponse(JSON.stringify(currentPrefs));
+ } else {
+ return JSON.stringify(currentPrefs);
+ }
+ }
+};
+
+var RangedChromeActions = (function RangedChromeActionsClosure() {
+ /**
+ * This is for range requests
+ */
+ function RangedChromeActions(
+ domWindow, contentDispositionFilename, originalRequest,
+ rangeEnabled, streamingEnabled, dataListener) {
+
+ ChromeActions.call(this, domWindow, contentDispositionFilename);
+ this.dataListener = dataListener;
+ this.originalRequest = originalRequest;
+ this.rangeEnabled = rangeEnabled;
+ this.streamingEnabled = streamingEnabled;
+
+ this.pdfUrl = originalRequest.URI.spec;
+ this.contentLength = originalRequest.contentLength;
+
+ // Pass all the headers from the original request through
+ var httpHeaderVisitor = {
+ headers: {},
+ visitHeader: function(aHeader, aValue) {
+ if (aHeader === 'Range') {
+ // When loading the PDF from cache, firefox seems to set the Range
+ // request header to fetch only the unfetched portions of the file
+ // (e.g. 'Range: bytes=1024-'). However, we want to set this header
+ // manually to fetch the PDF in chunks.
+ return;
+ }
+ this.headers[aHeader] = aValue;
+ }
+ };
+ if (originalRequest.visitRequestHeaders) {
+ originalRequest.visitRequestHeaders(httpHeaderVisitor);
+ }
+
+ var self = this;
+ var xhr_onreadystatechange = function xhr_onreadystatechange() {
+ if (this.readyState === 1) { // LOADING
+ var netChannel = this.channel;
+ if ('nsIPrivateBrowsingChannel' in Ci &&
+ netChannel instanceof Ci.nsIPrivateBrowsingChannel) {
+ var docIsPrivate = self.isInPrivateBrowsing();
+ netChannel.setPrivate(docIsPrivate);
+ }
+ }
+ };
+ var getXhr = function getXhr() {
+ const XMLHttpRequest = Components.Constructor(
+ '@mozilla.org/xmlextras/xmlhttprequest;1');
+ var xhr = new XMLHttpRequest();
+ xhr.addEventListener('readystatechange', xhr_onreadystatechange);
+ return xhr;
+ };
+
+ this.networkManager = new NetworkManager(this.pdfUrl, {
+ httpHeaders: httpHeaderVisitor.headers,
+ getXhr: getXhr
+ });
+
+ // If we are in range request mode, this means we manually issued xhr
+ // requests, which we need to abort when we leave the page
+ domWindow.addEventListener('unload', function unload(e) {
+ domWindow.removeEventListener(e.type, unload);
+ self.abortLoading();
+ });
+ }
+
+ RangedChromeActions.prototype = Object.create(ChromeActions.prototype);
+ var proto = RangedChromeActions.prototype;
+ proto.constructor = RangedChromeActions;
+
+ proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
+ var self = this;
+ var data;
+ if (!this.streamingEnabled) {
+ this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
+ this.originalRequest = null;
+ data = this.dataListener.readData();
+ this.dataListener = null;
+ } else {
+ data = this.dataListener.readData();
+
+ this.dataListener.onprogress = function (loaded, total) {
+ self.domWindow.postMessage({
+ pdfjsLoadAction: 'progressiveRead',
+ loaded: loaded,
+ total: total,
+ chunk: self.dataListener.readData()
+ }, '*');
+ };
+ this.dataListener.oncomplete = function () {
+ self.dataListener = null;
+ };
+ }
+
+ this.domWindow.postMessage({
+ pdfjsLoadAction: 'supportsRangedLoading',
+ rangeEnabled: this.rangeEnabled,
+ streamingEnabled: this.streamingEnabled,
+ pdfUrl: this.pdfUrl,
+ length: this.contentLength,
+ data: data
+ }, '*');
+
+ return true;
+ };
+
+ proto.requestDataRange = function RangedChromeActions_requestDataRange(args) {
+ if (!this.rangeEnabled) {
+ return;
+ }
+
+ var begin = args.begin;
+ var end = args.end;
+ var domWindow = this.domWindow;
+ // TODO(mack): Support error handler. We're not currently not handling
+ // errors from chrome code for non-range requests, so this doesn't
+ // seem high-pri
+ this.networkManager.requestRange(begin, end, {
+ onDone: function RangedChromeActions_onDone(args) {
+ domWindow.postMessage({
+ pdfjsLoadAction: 'range',
+ begin: args.begin,
+ chunk: args.chunk
+ }, '*');
+ },
+ onProgress: function RangedChromeActions_onProgress(evt) {
+ domWindow.postMessage({
+ pdfjsLoadAction: 'rangeProgress',
+ loaded: evt.loaded,
+ }, '*');
+ }
+ });
+ };
+
+ proto.abortLoading = function RangedChromeActions_abortLoading() {
+ this.networkManager.abortAllRequests();
+ if (this.originalRequest) {
+ this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
+ this.originalRequest = null;
+ }
+ this.dataListener = null;
+ };
+
+ return RangedChromeActions;
+})();
+
+var StandardChromeActions = (function StandardChromeActionsClosure() {
+
+ /**
+ * This is for a single network stream
+ */
+ function StandardChromeActions(domWindow, contentDispositionFilename,
+ originalRequest, dataListener) {
+
+ ChromeActions.call(this, domWindow, contentDispositionFilename);
+ this.originalRequest = originalRequest;
+ this.dataListener = dataListener;
+ }
+
+ StandardChromeActions.prototype = Object.create(ChromeActions.prototype);
+ var proto = StandardChromeActions.prototype;
+ proto.constructor = StandardChromeActions;
+
+ proto.initPassiveLoading =
+ function StandardChromeActions_initPassiveLoading() {
+
+ if (!this.dataListener) {
+ return false;
+ }
+
+ var self = this;
+
+ this.dataListener.onprogress = function ChromeActions_dataListenerProgress(
+ loaded, total) {
+ self.domWindow.postMessage({
+ pdfjsLoadAction: 'progress',
+ loaded: loaded,
+ total: total
+ }, '*');
+ };
+
+ this.dataListener.oncomplete =
+ function StandardChromeActions_dataListenerComplete(data, errorCode) {
+ self.domWindow.postMessage({
+ pdfjsLoadAction: 'complete',
+ data: data,
+ errorCode: errorCode
+ }, '*');
+
+ self.dataListener = null;
+ self.originalRequest = null;
+ };
+
+ return true;
+ };
+
+ proto.abortLoading = function StandardChromeActions_abortLoading() {
+ if (this.originalRequest) {
+ this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
+ this.originalRequest = null;
+ }
+ this.dataListener = null;
+ };
+
+ return StandardChromeActions;
+})();
+
+// Event listener to trigger chrome privileged code.
+function RequestListener(actions) {
+ this.actions = actions;
+}
+// Receive an event and synchronously or asynchronously responds.
+RequestListener.prototype.receive = function(event) {
+ var message = event.target;
+ var doc = message.ownerDocument;
+ var action = event.detail.action;
+ var data = event.detail.data;
+ var sync = event.detail.sync;
+ var actions = this.actions;
+ if (!(action in actions)) {
+ log('Unknown action: ' + action);
+ return;
+ }
+ var response;
+ if (sync) {
+ response = actions[action].call(this.actions, data);
+ event.detail.response = Cu.cloneInto(response, doc.defaultView);
+ } else {
+ if (!event.detail.responseExpected) {
+ doc.documentElement.removeChild(message);
+ response = null;
+ } else {
+ response = function sendResponse(response) {
+ try {
+ var listener = doc.createEvent('CustomEvent');
+ let detail = Cu.cloneInto({ response: response }, doc.defaultView);
+ listener.initCustomEvent('pdf.js.response', true, false, detail);
+ return message.dispatchEvent(listener);
+ } catch (e) {
+ // doc is no longer accessible because the requestor is already
+ // gone. unloaded content cannot receive the response anyway.
+ return false;
+ }
+ };
+ }
+ actions[action].call(this.actions, data, response);
+ }
+};
+
+// Forwards events from the eventElement to the contentWindow only if the
+// content window matches the currently selected browser window.
+function FindEventManager(contentWindow) {
+ this.contentWindow = contentWindow;
+ this.winmm = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+}
+
+FindEventManager.prototype.bind = function() {
+ var unload = function(e) {
+ this.unbind();
+ this.contentWindow.removeEventListener(e.type, unload);
+ }.bind(this);
+ this.contentWindow.addEventListener('unload', unload);
+
+ // We cannot directly attach listeners to for the find events
+ // since the FindBar is in the parent process. Instead we're
+ // asking the PdfjsChromeUtils to do it for us and forward
+ // all the find events to us.
+ this.winmm.sendAsyncMessage('PDFJS:Parent:addEventListener');
+ this.winmm.addMessageListener('PDFJS:Child:handleEvent', this);
+};
+
+FindEventManager.prototype.receiveMessage = function(msg) {
+ var detail = msg.data.detail;
+ var type = msg.data.type;
+ var contentWindow = this.contentWindow;
+
+ detail = Cu.cloneInto(detail, contentWindow);
+ var forward = contentWindow.document.createEvent('CustomEvent');
+ forward.initCustomEvent(type, true, true, detail);
+ contentWindow.dispatchEvent(forward);
+};
+
+FindEventManager.prototype.unbind = function() {
+ this.winmm.sendAsyncMessage('PDFJS:Parent:removeEventListener');
+};
+
+function PdfStreamConverter() {
+}
+
+PdfStreamConverter.prototype = {
+
+ // properties required for XPCOM registration:
+ classID: Components.ID('{d0c5195d-e798-49d4-b1d3-9324328b2291}'),
+ classDescription: 'pdf.js Component',
+ contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*',
+
+ classID2: Components.ID('{d0c5195d-e798-49d4-b1d3-9324328b2292}'),
+ contractID2: '@mozilla.org/streamconv;1?from=application/pdf&to=text/html',
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISupports,
+ Ci.nsIStreamConverter,
+ Ci.nsIStreamListener,
+ Ci.nsIRequestObserver
+ ]),
+
+ /*
+ * This component works as such:
+ * 1. asyncConvertData stores the listener
+ * 2. onStartRequest creates a new channel, streams the viewer
+ * 3. If range requests are supported:
+ * 3.1. Leave the request open until the viewer is ready to switch to
+ * range requests.
+ *
+ * If range rquests are not supported:
+ * 3.1. Read the stream as it's loaded in onDataAvailable to send
+ * to the viewer
+ *
+ * The convert function just returns the stream, it's just the synchronous
+ * version of asyncConvertData.
+ */
+
+ // nsIStreamConverter::convert
+ convert: function(aFromStream, aFromType, aToType, aCtxt) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ // nsIStreamConverter::asyncConvertData
+ asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
+ // Store the listener passed to us
+ this.listener = aListener;
+ },
+
+ // nsIStreamListener::onDataAvailable
+ onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+ if (!this.dataListener) {
+ return;
+ }
+
+ var binaryStream = this.binaryStream;
+ binaryStream.setInputStream(aInputStream);
+ var chunk = binaryStream.readByteArray(aCount);
+ this.dataListener.append(chunk);
+ },
+
+ // nsIRequestObserver::onStartRequest
+ onStartRequest: function(aRequest, aContext) {
+ // Setup the request so we can use it below.
+ var isHttpRequest = false;
+ try {
+ aRequest.QueryInterface(Ci.nsIHttpChannel);
+ isHttpRequest = true;
+ } catch (e) {}
+
+ var rangeRequest = false;
+ var streamRequest = false;
+ if (isHttpRequest) {
+ var contentEncoding = 'identity';
+ try {
+ contentEncoding = aRequest.getResponseHeader('Content-Encoding');
+ } catch (e) {}
+
+ var acceptRanges;
+ try {
+ acceptRanges = aRequest.getResponseHeader('Accept-Ranges');
+ } catch (e) {}
+
+ var hash = aRequest.URI.ref;
+ var isPDFBugEnabled = getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
+ rangeRequest = contentEncoding === 'identity' &&
+ acceptRanges === 'bytes' &&
+ aRequest.contentLength >= 0 &&
+ !getBoolPref(PREF_PREFIX + '.disableRange', false) &&
+ (!isPDFBugEnabled ||
+ hash.toLowerCase().indexOf('disablerange=true') < 0);
+ streamRequest = contentEncoding === 'identity' &&
+ aRequest.contentLength >= 0 &&
+ !getBoolPref(PREF_PREFIX + '.disableStream', false) &&
+ (!isPDFBugEnabled ||
+ hash.toLowerCase().indexOf('disablestream=true') < 0);
+ }
+
+ aRequest.QueryInterface(Ci.nsIChannel);
+
+ aRequest.QueryInterface(Ci.nsIWritablePropertyBag);
+
+ var contentDispositionFilename;
+ try {
+ contentDispositionFilename = aRequest.contentDispositionFilename;
+ } catch (e) {}
+
+ // Change the content type so we don't get stuck in a loop.
+ aRequest.setProperty('contentType', aRequest.contentType);
+ aRequest.contentType = 'text/html';
+ if (isHttpRequest) {
+ // We trust PDF viewer, using no CSP
+ aRequest.setResponseHeader('Content-Security-Policy', '', false);
+ aRequest.setResponseHeader('Content-Security-Policy-Report-Only', '',
+ false);
+ // The viewer does not need to handle HTTP Refresh header.
+ aRequest.setResponseHeader('Refresh', '', false);
+ }
+
+ PdfJsTelemetry.onViewerIsUsed();
+ PdfJsTelemetry.onDocumentSize(aRequest.contentLength);
+
+ // Creating storage for PDF data
+ var contentLength = aRequest.contentLength;
+ this.dataListener = new PdfDataListener(contentLength);
+ this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
+ .createInstance(Ci.nsIBinaryInputStream);
+
+ // Create a new channel that is viewer loaded as a resource.
+ var channel = createNewChannel(PDF_VIEWER_WEB_PAGE, null);
+
+ var listener = this.listener;
+ var dataListener = this.dataListener;
+ // Proxy all the request observer calls, when it gets to onStopRequest
+ // we can get the dom window. We also intentionally pass on the original
+ // request(aRequest) below so we don't overwrite the original channel and
+ // trigger an assertion.
+ var proxy = {
+ onStartRequest: function(request, context) {
+ listener.onStartRequest(aRequest, aContext);
+ },
+ onDataAvailable: function(request, context, inputStream, offset, count) {
+ listener.onDataAvailable(aRequest, aContext, inputStream,
+ offset, count);
+ },
+ onStopRequest: function(request, context, statusCode) {
+ // We get the DOM window here instead of before the request since it
+ // may have changed during a redirect.
+ var domWindow = getDOMWindow(channel);
+ var actions;
+ if (rangeRequest || streamRequest) {
+ actions = new RangedChromeActions(
+ domWindow, contentDispositionFilename, aRequest,
+ rangeRequest, streamRequest, dataListener);
+ } else {
+ actions = new StandardChromeActions(
+ domWindow, contentDispositionFilename, aRequest, dataListener);
+ }
+ var requestListener = new RequestListener(actions);
+ domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
+ requestListener.receive(event);
+ }, false, true);
+ if (actions.supportsIntegratedFind()) {
+ var findEventManager = new FindEventManager(domWindow);
+ findEventManager.bind();
+ }
+ listener.onStopRequest(aRequest, aContext, statusCode);
+
+ if (domWindow.frameElement) {
+ var isObjectEmbed = domWindow.frameElement.tagName !== 'IFRAME' ||
+ domWindow.frameElement.className === 'previewPluginContentFrame';
+ PdfJsTelemetry.onEmbed(isObjectEmbed);
+ }
+ }
+ };
+
+ // Keep the URL the same so the browser sees it as the same.
+ channel.originalURI = aRequest.URI;
+ channel.loadGroup = aRequest.loadGroup;
+ channel.loadInfo.originAttributes = aRequest.loadInfo.originAttributes;
+
+ // We can use the resource principal when data is fetched by the chrome,
+ // e.g. useful for NoScript. Make make sure we reuse the origin attributes
+ // from the request channel to keep isolation consistent.
+ var ssm = Cc['@mozilla.org/scriptsecuritymanager;1']
+ .getService(Ci.nsIScriptSecurityManager);
+ var uri = NetUtil.newURI(PDF_VIEWER_WEB_PAGE, null, null);
+ var resourcePrincipal;
+ resourcePrincipal =
+ ssm.createCodebasePrincipal(uri, aRequest.loadInfo.originAttributes);
+ aRequest.owner = resourcePrincipal;
+ asyncOpenChannel(channel, proxy, aContext);
+ },
+
+ // nsIRequestObserver::onStopRequest
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ if (!this.dataListener) {
+ // Do nothing
+ return;
+ }
+
+ if (Components.isSuccessCode(aStatusCode)) {
+ this.dataListener.finish();
+ } else {
+ this.dataListener.error(aStatusCode);
+ }
+ delete this.dataListener;
+ delete this.binaryStream;
+ }
+};
+
diff --git a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
new file mode 100644
index 000000000..7a082b6af
--- /dev/null
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -0,0 +1,357 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+/* jshint esnext:true */
+/* globals Components, Services, XPCOMUtils */
+
+'use strict';
+
+var EXPORTED_SYMBOLS = ['PdfjsChromeUtils'];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+const PREF_PREFIX = 'pdfjs';
+const PDF_CONTENT_TYPE = 'application/pdf';
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+var Svc = {};
+XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
+ '@mozilla.org/mime;1',
+ 'nsIMIMEService');
+
+var DEFAULT_PREFERENCES =
+{
+ "showPreviousViewOnLoad": true,
+ "defaultZoomValue": "",
+ "sidebarViewOnLoad": 0,
+ "enableHandToolOnLoad": false,
+ "enableWebGL": false,
+ "pdfBugEnabled": false,
+ "disableRange": false,
+ "disableStream": false,
+ "disableAutoFetch": false,
+ "disableFontFace": false,
+ "disableTextLayer": false,
+ "useOnlyCssZoom": false,
+ "externalLinkTarget": 0,
+ "enhanceTextSelection": false,
+ "renderInteractiveForms": false,
+ "disablePageLabels": false
+}
+
+
+var PdfjsChromeUtils = {
+ // For security purposes when running remote, we restrict preferences
+ // content can access.
+ _allowedPrefNames: Object.keys(DEFAULT_PREFERENCES),
+ _ppmm: null,
+ _mmg: null,
+
+ /*
+ * Public API
+ */
+
+ init: function () {
+ this._browsers = new WeakSet();
+ if (!this._ppmm) {
+ // global parent process message manager (PPMM)
+ this._ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
+ getService(Ci.nsIMessageBroadcaster);
+ this._ppmm.addMessageListener('PDFJS:Parent:clearUserPref', this);
+ this._ppmm.addMessageListener('PDFJS:Parent:setIntPref', this);
+ this._ppmm.addMessageListener('PDFJS:Parent:setBoolPref', this);
+ this._ppmm.addMessageListener('PDFJS:Parent:setCharPref', this);
+ this._ppmm.addMessageListener('PDFJS:Parent:setStringPref', this);
+ this._ppmm.addMessageListener('PDFJS:Parent:isDefaultHandlerApp', this);
+
+ // global dom message manager (MMg)
+ this._mmg = Cc['@mozilla.org/globalmessagemanager;1'].
+ getService(Ci.nsIMessageListenerManager);
+ this._mmg.addMessageListener('PDFJS:Parent:displayWarning', this);
+
+ this._mmg.addMessageListener('PDFJS:Parent:addEventListener', this);
+ this._mmg.addMessageListener('PDFJS:Parent:removeEventListener', this);
+ this._mmg.addMessageListener('PDFJS:Parent:updateControlState', this);
+
+ // observer to handle shutdown
+ Services.obs.addObserver(this, 'quit-application', false);
+ }
+ },
+
+ uninit: function () {
+ if (this._ppmm) {
+ this._ppmm.removeMessageListener('PDFJS:Parent:clearUserPref', this);
+ this._ppmm.removeMessageListener('PDFJS:Parent:setIntPref', this);
+ this._ppmm.removeMessageListener('PDFJS:Parent:setBoolPref', this);
+ this._ppmm.removeMessageListener('PDFJS:Parent:setCharPref', this);
+ this._ppmm.removeMessageListener('PDFJS:Parent:setStringPref', this);
+ this._ppmm.removeMessageListener('PDFJS:Parent:isDefaultHandlerApp',
+ this);
+
+ this._mmg.removeMessageListener('PDFJS:Parent:displayWarning', this);
+
+ this._mmg.removeMessageListener('PDFJS:Parent:addEventListener', this);
+ this._mmg.removeMessageListener('PDFJS:Parent:removeEventListener', this);
+ this._mmg.removeMessageListener('PDFJS:Parent:updateControlState', this);
+
+ Services.obs.removeObserver(this, 'quit-application', false);
+
+ this._mmg = null;
+ this._ppmm = null;
+ }
+ },
+
+ /*
+ * Called by the main module when preference changes are picked up
+ * in the parent process. Observers don't propagate so we need to
+ * instruct the child to refresh its configuration and (possibly)
+ * the module's registration.
+ */
+ notifyChildOfSettingsChange: function () {
+ if (Services.appinfo.processType ===
+ Services.appinfo.PROCESS_TYPE_DEFAULT && this._ppmm) {
+ // XXX kinda bad, we want to get the parent process mm associated
+ // with the content process. _ppmm is currently the global process
+ // manager, which means this is going to fire to every child process
+ // we have open. Unfortunately I can't find a way to get at that
+ // process specific mm from js.
+ this._ppmm.broadcastAsyncMessage('PDFJS:Child:refreshSettings', {});
+ }
+ },
+
+ /*
+ * Events
+ */
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic === 'quit-application') {
+ this.uninit();
+ }
+ },
+
+ receiveMessage: function (aMsg) {
+ switch (aMsg.name) {
+ case 'PDFJS:Parent:clearUserPref':
+ this._clearUserPref(aMsg.data.name);
+ break;
+ case 'PDFJS:Parent:setIntPref':
+ this._setIntPref(aMsg.data.name, aMsg.data.value);
+ break;
+ case 'PDFJS:Parent:setBoolPref':
+ this._setBoolPref(aMsg.data.name, aMsg.data.value);
+ break;
+ case 'PDFJS:Parent:setCharPref':
+ this._setCharPref(aMsg.data.name, aMsg.data.value);
+ break;
+ case 'PDFJS:Parent:setStringPref':
+ this._setStringPref(aMsg.data.name, aMsg.data.value);
+ break;
+ case 'PDFJS:Parent:isDefaultHandlerApp':
+ return this.isDefaultHandlerApp();
+ case 'PDFJS:Parent:displayWarning':
+ this._displayWarning(aMsg);
+ break;
+
+
+ case 'PDFJS:Parent:updateControlState':
+ return this._updateControlState(aMsg);
+ case 'PDFJS:Parent:addEventListener':
+ return this._addEventListener(aMsg);
+ case 'PDFJS:Parent:removeEventListener':
+ return this._removeEventListener(aMsg);
+ }
+ },
+
+ /*
+ * Internal
+ */
+
+ _findbarFromMessage: function(aMsg) {
+ let browser = aMsg.target;
+ let tabbrowser = browser.getTabBrowser();
+ let tab = tabbrowser.getTabForBrowser(browser);
+ return tabbrowser.getFindBar(tab);
+ },
+
+ _updateControlState: function (aMsg) {
+ let data = aMsg.data;
+ this._findbarFromMessage(aMsg)
+ .updateControlState(data.result, data.findPrevious);
+ },
+
+ handleEvent: function(aEvent) {
+ // To avoid forwarding the message as a CPOW, create a structured cloneable
+ // version of the event for both performance, and ease of usage, reasons.
+ let type = aEvent.type;
+ let detail = {
+ query: aEvent.detail.query,
+ caseSensitive: aEvent.detail.caseSensitive,
+ highlightAll: aEvent.detail.highlightAll,
+ findPrevious: aEvent.detail.findPrevious
+ };
+
+ let browser = aEvent.currentTarget.browser;
+ if (!this._browsers.has(browser)) {
+ throw new Error('FindEventManager was not bound ' +
+ 'for the current browser.');
+ }
+ // Only forward the events if the current browser is a registered browser.
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage('PDFJS:Child:handleEvent',
+ { type: type, detail: detail });
+ aEvent.preventDefault();
+ },
+
+ _types: ['find',
+ 'findagain',
+ 'findhighlightallchange',
+ 'findcasesensitivitychange'],
+
+ _addEventListener: function (aMsg) {
+ let browser = aMsg.target;
+ if (this._browsers.has(browser)) {
+ throw new Error('FindEventManager was bound 2nd time ' +
+ 'without unbinding it first.');
+ }
+
+ // Since this jsm is global, we need to store all the browsers
+ // we have to forward the messages for.
+ this._browsers.add(browser);
+
+ // And we need to start listening to find events.
+ for (var i = 0; i < this._types.length; i++) {
+ var type = this._types[i];
+ this._findbarFromMessage(aMsg)
+ .addEventListener(type, this, true);
+ }
+ },
+
+ _removeEventListener: function (aMsg) {
+ let browser = aMsg.target;
+ if (!this._browsers.has(browser)) {
+ throw new Error('FindEventManager was unbound without binding it first.');
+ }
+
+ this._browsers.delete(browser);
+
+ // No reason to listen to find events any longer.
+ for (var i = 0; i < this._types.length; i++) {
+ var type = this._types[i];
+ this._findbarFromMessage(aMsg)
+ .removeEventListener(type, this, true);
+ }
+ },
+
+ _ensurePreferenceAllowed: function (aPrefName) {
+ let unPrefixedName = aPrefName.split(PREF_PREFIX + '.');
+ if (unPrefixedName[0] !== '' ||
+ this._allowedPrefNames.indexOf(unPrefixedName[1]) === -1) {
+ let msg = '"' + aPrefName + '" ' +
+ 'can\'t be accessed from content. See PdfjsChromeUtils.';
+ throw new Error(msg);
+ }
+ },
+
+ _clearUserPref: function (aPrefName) {
+ this._ensurePreferenceAllowed(aPrefName);
+ Services.prefs.clearUserPref(aPrefName);
+ },
+
+ _setIntPref: function (aPrefName, aPrefValue) {
+ this._ensurePreferenceAllowed(aPrefName);
+ Services.prefs.setIntPref(aPrefName, aPrefValue);
+ },
+
+ _setBoolPref: function (aPrefName, aPrefValue) {
+ this._ensurePreferenceAllowed(aPrefName);
+ Services.prefs.setBoolPref(aPrefName, aPrefValue);
+ },
+
+ _setCharPref: function (aPrefName, aPrefValue) {
+ this._ensurePreferenceAllowed(aPrefName);
+ Services.prefs.setCharPref(aPrefName, aPrefValue);
+ },
+
+ _setStringPref: function (aPrefName, aPrefValue) {
+ this._ensurePreferenceAllowed(aPrefName);
+ let str = Cc['@mozilla.org/supports-string;1']
+ .createInstance(Ci.nsISupportsString);
+ str.data = aPrefValue;
+ Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, str);
+ },
+
+ /*
+ * Svc.mime doesn't have profile information in the child, so
+ * we bounce this pdfjs enabled configuration check over to the
+ * parent.
+ */
+ isDefaultHandlerApp: function () {
+ var handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf');
+ return (!handlerInfo.alwaysAskBeforeHandling &&
+ handlerInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally);
+ },
+
+ /*
+ * Display a notification warning when the renderer isn't sure
+ * a pdf displayed correctly.
+ */
+ _displayWarning: function (aMsg) {
+ let data = aMsg.data;
+ let browser = aMsg.target;
+
+ let tabbrowser = browser.getTabBrowser();
+ let notificationBox = tabbrowser.getNotificationBox(browser);
+
+ // Flag so we don't send the message twice, since if the user clicks
+ // "open with different viewer" both the button callback and
+ // eventCallback will be called.
+ let messageSent = false;
+ function sendMessage(download) {
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage('PDFJS:Child:fallbackDownload',
+ { download: download });
+ }
+ let buttons = [{
+ label: data.label,
+ accessKey: data.accessKey,
+ callback: function() {
+ messageSent = true;
+ sendMessage(true);
+ }
+ }];
+ notificationBox.appendNotification(data.message, 'pdfjs-fallback', null,
+ notificationBox.PRIORITY_INFO_LOW,
+ buttons,
+ function eventsCallback(eventType) {
+ // Currently there is only one event "removed" but if there are any other
+ // added in the future we still only care about removed at the moment.
+ if (eventType !== 'removed') {
+ return;
+ }
+ // Don't send a response again if we already responded when the button was
+ // clicked.
+ if (messageSent) {
+ return;
+ }
+ sendMessage(false);
+ });
+ }
+};
+
+
diff --git a/browser/extensions/pdfjs/content/PdfjsContentUtils.jsm b/browser/extensions/pdfjs/content/PdfjsContentUtils.jsm
new file mode 100644
index 000000000..3dec5f389
--- /dev/null
+++ b/browser/extensions/pdfjs/content/PdfjsContentUtils.jsm
@@ -0,0 +1,150 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+/* jshint esnext:true */
+/* globals Components, Services, XPCOMUtils */
+
+'use strict';
+
+var EXPORTED_SYMBOLS = ['PdfjsContentUtils'];
+
+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');
+
+var PdfjsContentUtils = {
+ _mm: null,
+
+ /*
+ * Public API
+ */
+
+ get isRemote() {
+ return (Services.appinfo.processType ===
+ Services.appinfo.PROCESS_TYPE_CONTENT);
+ },
+
+ init: function () {
+ // child *process* mm, or when loaded into the parent for in-content
+ // support the psuedo child process mm 'child PPMM'.
+ if (!this._mm) {
+ this._mm = Cc['@mozilla.org/childprocessmessagemanager;1'].
+ getService(Ci.nsISyncMessageSender);
+ this._mm.addMessageListener('PDFJS:Child:refreshSettings', this);
+ Services.obs.addObserver(this, 'quit-application', false);
+ }
+ },
+
+ uninit: function () {
+ if (this._mm) {
+ this._mm.removeMessageListener('PDFJS:Child:refreshSettings', this);
+ Services.obs.removeObserver(this, 'quit-application');
+ }
+ this._mm = null;
+ },
+
+ /*
+ * prefs utilities - the child does not have write access to prefs.
+ * note, the pref names here are cross-checked against a list of
+ * approved pdfjs prefs in chrome utils.
+ */
+
+ clearUserPref: function (aPrefName) {
+ this._mm.sendSyncMessage('PDFJS:Parent:clearUserPref', {
+ name: aPrefName
+ });
+ },
+
+ setIntPref: function (aPrefName, aPrefValue) {
+ this._mm.sendSyncMessage('PDFJS:Parent:setIntPref', {
+ name: aPrefName,
+ value: aPrefValue
+ });
+ },
+
+ setBoolPref: function (aPrefName, aPrefValue) {
+ this._mm.sendSyncMessage('PDFJS:Parent:setBoolPref', {
+ name: aPrefName,
+ value: aPrefValue
+ });
+ },
+
+ setCharPref: function (aPrefName, aPrefValue) {
+ this._mm.sendSyncMessage('PDFJS:Parent:setCharPref', {
+ name: aPrefName,
+ value: aPrefValue
+ });
+ },
+
+ setStringPref: function (aPrefName, aPrefValue) {
+ this._mm.sendSyncMessage('PDFJS:Parent:setStringPref', {
+ name: aPrefName,
+ value: aPrefValue
+ });
+ },
+
+ /*
+ * Forwards default app query to the parent where we check various
+ * handler app settings only available in the parent process.
+ */
+ isDefaultHandlerApp: function () {
+ return this._mm.sendSyncMessage('PDFJS:Parent:isDefaultHandlerApp')[0];
+ },
+
+ /*
+ * Request the display of a notification warning in the associated window
+ * when the renderer isn't sure a pdf displayed correctly.
+ */
+ displayWarning: function (aWindow, aMessage, aLabel, accessKey) {
+ // the child's dom frame mm associated with the window.
+ let winmm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ winmm.sendAsyncMessage('PDFJS:Parent:displayWarning', {
+ message: aMessage,
+ label: aLabel,
+ accessKey: accessKey
+ });
+ },
+
+ /*
+ * Events
+ */
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic === 'quit-application') {
+ this.uninit();
+ }
+ },
+
+ receiveMessage: function (aMsg) {
+ switch (aMsg.name) {
+ case 'PDFJS:Child:refreshSettings':
+ // Only react to this if we are remote.
+ if (Services.appinfo.processType ===
+ Services.appinfo.PROCESS_TYPE_CONTENT) {
+ let jsm = 'resource://pdf.js/PdfJs.jsm';
+ let pdfjs = Components.utils.import(jsm, {}).PdfJs;
+ pdfjs.updateRegistration();
+ }
+ break;
+ }
+ }
+};
+
diff --git a/browser/extensions/pdfjs/content/build/pdf.js b/browser/extensions/pdfjs/content/build/pdf.js
new file mode 100644
index 000000000..98a3bd392
--- /dev/null
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -0,0 +1,8387 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+(function (root, factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ define('pdfjs-dist/build/pdf', ['exports'], factory);
+ } else if (typeof exports !== 'undefined') {
+ factory(exports);
+ } else {
+ factory(root['pdfjsDistBuildPdf'] = {});
+ }
+}(this, function (exports) {
+ // Use strict in our context only - users might not want it
+ 'use strict';
+ var pdfjsVersion = '1.6.315';
+ var pdfjsBuild = 'a139c75';
+ var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
+ var pdfjsLibs = {};
+ (function pdfjsWrapper() {
+ (function (root, factory) {
+ factory(root.pdfjsSharedUtil = {});
+ }(this, function (exports) {
+ var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this;
+ var FONT_IDENTITY_MATRIX = [
+ 0.001,
+ 0,
+ 0,
+ 0.001,
+ 0,
+ 0
+ ];
+ var TextRenderingMode = {
+ FILL: 0,
+ STROKE: 1,
+ FILL_STROKE: 2,
+ INVISIBLE: 3,
+ FILL_ADD_TO_PATH: 4,
+ STROKE_ADD_TO_PATH: 5,
+ FILL_STROKE_ADD_TO_PATH: 6,
+ ADD_TO_PATH: 7,
+ FILL_STROKE_MASK: 3,
+ ADD_TO_PATH_FLAG: 4
+ };
+ var ImageKind = {
+ GRAYSCALE_1BPP: 1,
+ RGB_24BPP: 2,
+ RGBA_32BPP: 3
+ };
+ var AnnotationType = {
+ TEXT: 1,
+ LINK: 2,
+ FREETEXT: 3,
+ LINE: 4,
+ SQUARE: 5,
+ CIRCLE: 6,
+ POLYGON: 7,
+ POLYLINE: 8,
+ HIGHLIGHT: 9,
+ UNDERLINE: 10,
+ SQUIGGLY: 11,
+ STRIKEOUT: 12,
+ STAMP: 13,
+ CARET: 14,
+ INK: 15,
+ POPUP: 16,
+ FILEATTACHMENT: 17,
+ SOUND: 18,
+ MOVIE: 19,
+ WIDGET: 20,
+ SCREEN: 21,
+ PRINTERMARK: 22,
+ TRAPNET: 23,
+ WATERMARK: 24,
+ THREED: 25,
+ REDACT: 26
+ };
+ var AnnotationFlag = {
+ INVISIBLE: 0x01,
+ HIDDEN: 0x02,
+ PRINT: 0x04,
+ NOZOOM: 0x08,
+ NOROTATE: 0x10,
+ NOVIEW: 0x20,
+ READONLY: 0x40,
+ LOCKED: 0x80,
+ TOGGLENOVIEW: 0x100,
+ LOCKEDCONTENTS: 0x200
+ };
+ var AnnotationFieldFlag = {
+ READONLY: 0x0000001,
+ REQUIRED: 0x0000002,
+ NOEXPORT: 0x0000004,
+ MULTILINE: 0x0001000,
+ PASSWORD: 0x0002000,
+ NOTOGGLETOOFF: 0x0004000,
+ RADIO: 0x0008000,
+ PUSHBUTTON: 0x0010000,
+ COMBO: 0x0020000,
+ EDIT: 0x0040000,
+ SORT: 0x0080000,
+ FILESELECT: 0x0100000,
+ MULTISELECT: 0x0200000,
+ DONOTSPELLCHECK: 0x0400000,
+ DONOTSCROLL: 0x0800000,
+ COMB: 0x1000000,
+ RICHTEXT: 0x2000000,
+ RADIOSINUNISON: 0x2000000,
+ COMMITONSELCHANGE: 0x4000000
+ };
+ var AnnotationBorderStyleType = {
+ SOLID: 1,
+ DASHED: 2,
+ BEVELED: 3,
+ INSET: 4,
+ UNDERLINE: 5
+ };
+ var StreamType = {
+ UNKNOWN: 0,
+ FLATE: 1,
+ LZW: 2,
+ DCT: 3,
+ JPX: 4,
+ JBIG: 5,
+ A85: 6,
+ AHX: 7,
+ CCF: 8,
+ RL: 9
+ };
+ var FontType = {
+ UNKNOWN: 0,
+ TYPE1: 1,
+ TYPE1C: 2,
+ CIDFONTTYPE0: 3,
+ CIDFONTTYPE0C: 4,
+ TRUETYPE: 5,
+ CIDFONTTYPE2: 6,
+ TYPE3: 7,
+ OPENTYPE: 8,
+ TYPE0: 9,
+ MMTYPE1: 10
+ };
+ var VERBOSITY_LEVELS = {
+ errors: 0,
+ warnings: 1,
+ infos: 5
+ };
+ // All the possible operations for an operator list.
+ var OPS = {
+ // Intentionally start from 1 so it is easy to spot bad operators that will be
+ // 0's.
+ dependency: 1,
+ setLineWidth: 2,
+ setLineCap: 3,
+ setLineJoin: 4,
+ setMiterLimit: 5,
+ setDash: 6,
+ setRenderingIntent: 7,
+ setFlatness: 8,
+ setGState: 9,
+ save: 10,
+ restore: 11,
+ transform: 12,
+ moveTo: 13,
+ lineTo: 14,
+ curveTo: 15,
+ curveTo2: 16,
+ curveTo3: 17,
+ closePath: 18,
+ rectangle: 19,
+ stroke: 20,
+ closeStroke: 21,
+ fill: 22,
+ eoFill: 23,
+ fillStroke: 24,
+ eoFillStroke: 25,
+ closeFillStroke: 26,
+ closeEOFillStroke: 27,
+ endPath: 28,
+ clip: 29,
+ eoClip: 30,
+ beginText: 31,
+ endText: 32,
+ setCharSpacing: 33,
+ setWordSpacing: 34,
+ setHScale: 35,
+ setLeading: 36,
+ setFont: 37,
+ setTextRenderingMode: 38,
+ setTextRise: 39,
+ moveText: 40,
+ setLeadingMoveText: 41,
+ setTextMatrix: 42,
+ nextLine: 43,
+ showText: 44,
+ showSpacedText: 45,
+ nextLineShowText: 46,
+ nextLineSetSpacingShowText: 47,
+ setCharWidth: 48,
+ setCharWidthAndBounds: 49,
+ setStrokeColorSpace: 50,
+ setFillColorSpace: 51,
+ setStrokeColor: 52,
+ setStrokeColorN: 53,
+ setFillColor: 54,
+ setFillColorN: 55,
+ setStrokeGray: 56,
+ setFillGray: 57,
+ setStrokeRGBColor: 58,
+ setFillRGBColor: 59,
+ setStrokeCMYKColor: 60,
+ setFillCMYKColor: 61,
+ shadingFill: 62,
+ beginInlineImage: 63,
+ beginImageData: 64,
+ endInlineImage: 65,
+ paintXObject: 66,
+ markPoint: 67,
+ markPointProps: 68,
+ beginMarkedContent: 69,
+ beginMarkedContentProps: 70,
+ endMarkedContent: 71,
+ beginCompat: 72,
+ endCompat: 73,
+ paintFormXObjectBegin: 74,
+ paintFormXObjectEnd: 75,
+ beginGroup: 76,
+ endGroup: 77,
+ beginAnnotations: 78,
+ endAnnotations: 79,
+ beginAnnotation: 80,
+ endAnnotation: 81,
+ paintJpegXObject: 82,
+ paintImageMaskXObject: 83,
+ paintImageMaskXObjectGroup: 84,
+ paintImageXObject: 85,
+ paintInlineImageXObject: 86,
+ paintInlineImageXObjectGroup: 87,
+ paintImageXObjectRepeat: 88,
+ paintImageMaskXObjectRepeat: 89,
+ paintSolidColorImageMask: 90,
+ constructPath: 91
+ };
+ var verbosity = VERBOSITY_LEVELS.warnings;
+ function setVerbosityLevel(level) {
+ verbosity = level;
+ }
+ function getVerbosityLevel() {
+ return verbosity;
+ }
+ // A notice for devs. These are good for things that are helpful to devs, such
+ // as warning that Workers were disabled, which is important to devs but not
+ // end users.
+ function info(msg) {
+ if (verbosity >= VERBOSITY_LEVELS.infos) {
+ console.log('Info: ' + msg);
+ }
+ }
+ // Non-fatal warnings.
+ function warn(msg) {
+ if (verbosity >= VERBOSITY_LEVELS.warnings) {
+ console.log('Warning: ' + msg);
+ }
+ }
+ // Deprecated API function -- display regardless of the PDFJS.verbosity setting.
+ function deprecated(details) {
+ console.log('Deprecated API usage: ' + details);
+ }
+ // Fatal errors that should trigger the fallback UI and halt execution by
+ // throwing an exception.
+ function error(msg) {
+ if (verbosity >= VERBOSITY_LEVELS.errors) {
+ console.log('Error: ' + msg);
+ console.log(backtrace());
+ }
+ throw new Error(msg);
+ }
+ function backtrace() {
+ try {
+ throw new Error();
+ } catch (e) {
+ return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+ }
+ }
+ function assert(cond, msg) {
+ if (!cond) {
+ error(msg);
+ }
+ }
+ var UNSUPPORTED_FEATURES = {
+ unknown: 'unknown',
+ forms: 'forms',
+ javaScript: 'javaScript',
+ smask: 'smask',
+ shadingPattern: 'shadingPattern',
+ font: 'font'
+ };
+ // Checks if URLs have the same origin. For non-HTTP based URLs, returns false.
+ function isSameOrigin(baseUrl, otherUrl) {
+ try {
+ var base = new URL(baseUrl);
+ if (!base.origin || base.origin === 'null') {
+ return false;
+ }
+ } // non-HTTP url
+ catch (e) {
+ return false;
+ }
+ var other = new URL(otherUrl, base);
+ return base.origin === other.origin;
+ }
+ // Checks if URLs use one of the whitelisted protocols, e.g. to avoid XSS.
+ function isValidProtocol(url) {
+ if (!url) {
+ return false;
+ }
+ switch (url.protocol) {
+ case 'http:':
+ case 'https:':
+ case 'ftp:':
+ case 'mailto:':
+ case 'tel:':
+ return true;
+ default:
+ return false;
+ }
+ }
+ /**
+ * Attempts to create a valid absolute URL (utilizing `isValidProtocol`).
+ * @param {URL|string} url - An absolute, or relative, URL.
+ * @param {URL|string} baseUrl - An absolute URL.
+ * @returns Either a valid {URL}, or `null` otherwise.
+ */
+ function createValidAbsoluteUrl(url, baseUrl) {
+ if (!url) {
+ return null;
+ }
+ try {
+ var absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
+ if (isValidProtocol(absoluteUrl)) {
+ return absoluteUrl;
+ }
+ } catch (ex) {
+ }
+ return null;
+ }
+ function shadow(obj, prop, value) {
+ Object.defineProperty(obj, prop, {
+ value: value,
+ enumerable: true,
+ configurable: true,
+ writable: false
+ });
+ return value;
+ }
+ function getLookupTableFactory(initializer) {
+ var lookup;
+ return function () {
+ if (initializer) {
+ lookup = Object.create(null);
+ initializer(lookup);
+ initializer = null;
+ }
+ return lookup;
+ };
+ }
+ var PasswordResponses = {
+ NEED_PASSWORD: 1,
+ INCORRECT_PASSWORD: 2
+ };
+ var PasswordException = function PasswordExceptionClosure() {
+ function PasswordException(msg, code) {
+ this.name = 'PasswordException';
+ this.message = msg;
+ this.code = code;
+ }
+ PasswordException.prototype = new Error();
+ PasswordException.constructor = PasswordException;
+ return PasswordException;
+ }();
+ var UnknownErrorException = function UnknownErrorExceptionClosure() {
+ function UnknownErrorException(msg, details) {
+ this.name = 'UnknownErrorException';
+ this.message = msg;
+ this.details = details;
+ }
+ UnknownErrorException.prototype = new Error();
+ UnknownErrorException.constructor = UnknownErrorException;
+ return UnknownErrorException;
+ }();
+ var InvalidPDFException = function InvalidPDFExceptionClosure() {
+ function InvalidPDFException(msg) {
+ this.name = 'InvalidPDFException';
+ this.message = msg;
+ }
+ InvalidPDFException.prototype = new Error();
+ InvalidPDFException.constructor = InvalidPDFException;
+ return InvalidPDFException;
+ }();
+ var MissingPDFException = function MissingPDFExceptionClosure() {
+ function MissingPDFException(msg) {
+ this.name = 'MissingPDFException';
+ this.message = msg;
+ }
+ MissingPDFException.prototype = new Error();
+ MissingPDFException.constructor = MissingPDFException;
+ return MissingPDFException;
+ }();
+ var UnexpectedResponseException = function UnexpectedResponseExceptionClosure() {
+ function UnexpectedResponseException(msg, status) {
+ this.name = 'UnexpectedResponseException';
+ this.message = msg;
+ this.status = status;
+ }
+ UnexpectedResponseException.prototype = new Error();
+ UnexpectedResponseException.constructor = UnexpectedResponseException;
+ return UnexpectedResponseException;
+ }();
+ var NotImplementedException = function NotImplementedExceptionClosure() {
+ function NotImplementedException(msg) {
+ this.message = msg;
+ }
+ NotImplementedException.prototype = new Error();
+ NotImplementedException.prototype.name = 'NotImplementedException';
+ NotImplementedException.constructor = NotImplementedException;
+ return NotImplementedException;
+ }();
+ var MissingDataException = function MissingDataExceptionClosure() {
+ function MissingDataException(begin, end) {
+ this.begin = begin;
+ this.end = end;
+ this.message = 'Missing data [' + begin + ', ' + end + ')';
+ }
+ MissingDataException.prototype = new Error();
+ MissingDataException.prototype.name = 'MissingDataException';
+ MissingDataException.constructor = MissingDataException;
+ return MissingDataException;
+ }();
+ var XRefParseException = function XRefParseExceptionClosure() {
+ function XRefParseException(msg) {
+ this.message = msg;
+ }
+ XRefParseException.prototype = new Error();
+ XRefParseException.prototype.name = 'XRefParseException';
+ XRefParseException.constructor = XRefParseException;
+ return XRefParseException;
+ }();
+ var NullCharactersRegExp = /\x00/g;
+ function removeNullCharacters(str) {
+ if (typeof str !== 'string') {
+ warn('The argument for removeNullCharacters must be a string.');
+ return str;
+ }
+ return str.replace(NullCharactersRegExp, '');
+ }
+ function bytesToString(bytes) {
+ assert(bytes !== null && typeof bytes === 'object' && bytes.length !== undefined, 'Invalid argument for bytesToString');
+ var length = bytes.length;
+ var MAX_ARGUMENT_COUNT = 8192;
+ if (length < MAX_ARGUMENT_COUNT) {
+ return String.fromCharCode.apply(null, bytes);
+ }
+ var strBuf = [];
+ for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
+ var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
+ var chunk = bytes.subarray(i, chunkEnd);
+ strBuf.push(String.fromCharCode.apply(null, chunk));
+ }
+ return strBuf.join('');
+ }
+ function stringToBytes(str) {
+ assert(typeof str === 'string', 'Invalid argument for stringToBytes');
+ var length = str.length;
+ var bytes = new Uint8Array(length);
+ for (var i = 0; i < length; ++i) {
+ bytes[i] = str.charCodeAt(i) & 0xFF;
+ }
+ return bytes;
+ }
+ /**
+ * Gets length of the array (Array, Uint8Array, or string) in bytes.
+ * @param {Array|Uint8Array|string} arr
+ * @returns {number}
+ */
+ function arrayByteLength(arr) {
+ if (arr.length !== undefined) {
+ return arr.length;
+ }
+ assert(arr.byteLength !== undefined);
+ return arr.byteLength;
+ }
+ /**
+ * Combines array items (arrays) into single Uint8Array object.
+ * @param {Array} arr - the array of the arrays (Array, Uint8Array, or string).
+ * @returns {Uint8Array}
+ */
+ function arraysToBytes(arr) {
+ // Shortcut: if first and only item is Uint8Array, return it.
+ if (arr.length === 1 && arr[0] instanceof Uint8Array) {
+ return arr[0];
+ }
+ var resultLength = 0;
+ var i, ii = arr.length;
+ var item, itemLength;
+ for (i = 0; i < ii; i++) {
+ item = arr[i];
+ itemLength = arrayByteLength(item);
+ resultLength += itemLength;
+ }
+ var pos = 0;
+ var data = new Uint8Array(resultLength);
+ for (i = 0; i < ii; i++) {
+ item = arr[i];
+ if (!(item instanceof Uint8Array)) {
+ if (typeof item === 'string') {
+ item = stringToBytes(item);
+ } else {
+ item = new Uint8Array(item);
+ }
+ }
+ itemLength = item.byteLength;
+ data.set(item, pos);
+ pos += itemLength;
+ }
+ return data;
+ }
+ function string32(value) {
+ return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
+ }
+ function log2(x) {
+ var n = 1, i = 0;
+ while (x > n) {
+ n <<= 1;
+ i++;
+ }
+ return i;
+ }
+ function readInt8(data, start) {
+ return data[start] << 24 >> 24;
+ }
+ function readUint16(data, offset) {
+ return data[offset] << 8 | data[offset + 1];
+ }
+ function readUint32(data, offset) {
+ return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
+ }
+ // Lazy test the endianness of the platform
+ // NOTE: This will be 'true' for simulated TypedArrays
+ function isLittleEndian() {
+ var buffer8 = new Uint8Array(2);
+ buffer8[0] = 1;
+ var buffer16 = new Uint16Array(buffer8.buffer);
+ return buffer16[0] === 1;
+ }
+ // Checks if it's possible to eval JS expressions.
+ function isEvalSupported() {
+ try {
+ new Function('');
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+ var IDENTITY_MATRIX = [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0
+ ];
+ var Util = function UtilClosure() {
+ function Util() {
+ }
+ var rgbBuf = [
+ 'rgb(',
+ 0,
+ ',',
+ 0,
+ ',',
+ 0,
+ ')'
+ ];
+ // makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids
+ // creating many intermediate strings.
+ Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
+ rgbBuf[1] = r;
+ rgbBuf[3] = g;
+ rgbBuf[5] = b;
+ return rgbBuf.join('');
+ };
+ // Concatenates two transformation matrices together and returns the result.
+ Util.transform = function Util_transform(m1, m2) {
+ return [
+ m1[0] * m2[0] + m1[2] * m2[1],
+ m1[1] * m2[0] + m1[3] * m2[1],
+ m1[0] * m2[2] + m1[2] * m2[3],
+ m1[1] * m2[2] + m1[3] * m2[3],
+ m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
+ m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
+ ];
+ };
+ // For 2d affine transforms
+ Util.applyTransform = function Util_applyTransform(p, m) {
+ var xt = p[0] * m[0] + p[1] * m[2] + m[4];
+ var yt = p[0] * m[1] + p[1] * m[3] + m[5];
+ return [
+ xt,
+ yt
+ ];
+ };
+ Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+ var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+ return [
+ xt,
+ yt
+ ];
+ };
+ // Applies the transform to the rectangle and finds the minimum axially
+ // aligned bounding box.
+ Util.getAxialAlignedBoundingBox = function Util_getAxialAlignedBoundingBox(r, m) {
+ var p1 = Util.applyTransform(r, m);
+ var p2 = Util.applyTransform(r.slice(2, 4), m);
+ var p3 = Util.applyTransform([
+ r[0],
+ r[3]
+ ], m);
+ var p4 = Util.applyTransform([
+ r[2],
+ r[1]
+ ], m);
+ return [
+ Math.min(p1[0], p2[0], p3[0], p4[0]),
+ Math.min(p1[1], p2[1], p3[1], p4[1]),
+ Math.max(p1[0], p2[0], p3[0], p4[0]),
+ Math.max(p1[1], p2[1], p3[1], p4[1])
+ ];
+ };
+ Util.inverseTransform = function Util_inverseTransform(m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ return [
+ m[3] / d,
+ -m[1] / d,
+ -m[2] / d,
+ m[0] / d,
+ (m[2] * m[5] - m[4] * m[3]) / d,
+ (m[4] * m[1] - m[5] * m[0]) / d
+ ];
+ };
+ // Apply a generic 3d matrix M on a 3-vector v:
+ // | a b c | | X |
+ // | d e f | x | Y |
+ // | g h i | | Z |
+ // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
+ // with v as [X,Y,Z]
+ Util.apply3dTransform = function Util_apply3dTransform(m, v) {
+ return [
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
+ ];
+ };
+ // This calculation uses Singular Value Decomposition.
+ // The SVD can be represented with formula A = USV. We are interested in the
+ // matrix S here because it represents the scale values.
+ Util.singularValueDecompose2dScale = function Util_singularValueDecompose2dScale(m) {
+ var transpose = [
+ m[0],
+ m[2],
+ m[1],
+ m[3]
+ ];
+ // Multiply matrix m with its transpose.
+ var a = m[0] * transpose[0] + m[1] * transpose[2];
+ var b = m[0] * transpose[1] + m[1] * transpose[3];
+ var c = m[2] * transpose[0] + m[3] * transpose[2];
+ var d = m[2] * transpose[1] + m[3] * transpose[3];
+ // Solve the second degree polynomial to get roots.
+ var first = (a + d) / 2;
+ var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
+ var sx = first + second || 1;
+ var sy = first - second || 1;
+ // Scale values are the square roots of the eigenvalues.
+ return [
+ Math.sqrt(sx),
+ Math.sqrt(sy)
+ ];
+ };
+ // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
+ // For coordinate systems whose origin lies in the bottom-left, this
+ // means normalization to (BL,TR) ordering. For systems with origin in the
+ // top-left, this means (TL,BR) ordering.
+ Util.normalizeRect = function Util_normalizeRect(rect) {
+ var r = rect.slice(0);
+ // clone rect
+ if (rect[0] > rect[2]) {
+ r[0] = rect[2];
+ r[2] = rect[0];
+ }
+ if (rect[1] > rect[3]) {
+ r[1] = rect[3];
+ r[3] = rect[1];
+ }
+ return r;
+ };
+ // Returns a rectangle [x1, y1, x2, y2] corresponding to the
+ // intersection of rect1 and rect2. If no intersection, returns 'false'
+ // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
+ Util.intersect = function Util_intersect(rect1, rect2) {
+ function compare(a, b) {
+ return a - b;
+ }
+ // Order points along the axes
+ var orderedX = [
+ rect1[0],
+ rect1[2],
+ rect2[0],
+ rect2[2]
+ ].sort(compare), orderedY = [
+ rect1[1],
+ rect1[3],
+ rect2[1],
+ rect2[3]
+ ].sort(compare), result = [];
+ rect1 = Util.normalizeRect(rect1);
+ rect2 = Util.normalizeRect(rect2);
+ // X: first and second points belong to different rectangles?
+ if (orderedX[0] === rect1[0] && orderedX[1] === rect2[0] || orderedX[0] === rect2[0] && orderedX[1] === rect1[0]) {
+ // Intersection must be between second and third points
+ result[0] = orderedX[1];
+ result[2] = orderedX[2];
+ } else {
+ return false;
+ }
+ // Y: first and second points belong to different rectangles?
+ if (orderedY[0] === rect1[1] && orderedY[1] === rect2[1] || orderedY[0] === rect2[1] && orderedY[1] === rect1[1]) {
+ // Intersection must be between second and third points
+ result[1] = orderedY[1];
+ result[3] = orderedY[2];
+ } else {
+ return false;
+ }
+ return result;
+ };
+ Util.sign = function Util_sign(num) {
+ return num < 0 ? -1 : 1;
+ };
+ var ROMAN_NUMBER_MAP = [
+ '',
+ 'C',
+ 'CC',
+ 'CCC',
+ 'CD',
+ 'D',
+ 'DC',
+ 'DCC',
+ 'DCCC',
+ 'CM',
+ '',
+ 'X',
+ 'XX',
+ 'XXX',
+ 'XL',
+ 'L',
+ 'LX',
+ 'LXX',
+ 'LXXX',
+ 'XC',
+ '',
+ 'I',
+ 'II',
+ 'III',
+ 'IV',
+ 'V',
+ 'VI',
+ 'VII',
+ 'VIII',
+ 'IX'
+ ];
+ /**
+ * Converts positive integers to (upper case) Roman numerals.
+ * @param {integer} number - The number that should be converted.
+ * @param {boolean} lowerCase - Indicates if the result should be converted
+ * to lower case letters. The default is false.
+ * @return {string} The resulting Roman number.
+ */
+ Util.toRoman = function Util_toRoman(number, lowerCase) {
+ assert(isInt(number) && number > 0, 'The number should be a positive integer.');
+ var pos, romanBuf = [];
+ // Thousands
+ while (number >= 1000) {
+ number -= 1000;
+ romanBuf.push('M');
+ }
+ // Hundreds
+ pos = number / 100 | 0;
+ number %= 100;
+ romanBuf.push(ROMAN_NUMBER_MAP[pos]);
+ // Tens
+ pos = number / 10 | 0;
+ number %= 10;
+ romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
+ // Ones
+ romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
+ var romanStr = romanBuf.join('');
+ return lowerCase ? romanStr.toLowerCase() : romanStr;
+ };
+ Util.appendToArray = function Util_appendToArray(arr1, arr2) {
+ Array.prototype.push.apply(arr1, arr2);
+ };
+ Util.prependToArray = function Util_prependToArray(arr1, arr2) {
+ Array.prototype.unshift.apply(arr1, arr2);
+ };
+ Util.extendObj = function extendObj(obj1, obj2) {
+ for (var key in obj2) {
+ obj1[key] = obj2[key];
+ }
+ };
+ Util.getInheritableProperty = function Util_getInheritableProperty(dict, name, getArray) {
+ while (dict && !dict.has(name)) {
+ dict = dict.get('Parent');
+ }
+ if (!dict) {
+ return null;
+ }
+ return getArray ? dict.getArray(name) : dict.get(name);
+ };
+ Util.inherit = function Util_inherit(sub, base, prototype) {
+ sub.prototype = Object.create(base.prototype);
+ sub.prototype.constructor = sub;
+ for (var prop in prototype) {
+ sub.prototype[prop] = prototype[prop];
+ }
+ };
+ Util.loadScript = function Util_loadScript(src, callback) {
+ var script = document.createElement('script');
+ var loaded = false;
+ script.setAttribute('src', src);
+ if (callback) {
+ script.onload = function () {
+ if (!loaded) {
+ callback();
+ }
+ loaded = true;
+ };
+ }
+ document.getElementsByTagName('head')[0].appendChild(script);
+ };
+ return Util;
+ }();
+ /**
+ * PDF page viewport created based on scale, rotation and offset.
+ * @class
+ * @alias PageViewport
+ */
+ var PageViewport = function PageViewportClosure() {
+ /**
+ * @constructor
+ * @private
+ * @param viewBox {Array} xMin, yMin, xMax and yMax coordinates.
+ * @param scale {number} scale of the viewport.
+ * @param rotation {number} rotations of the viewport in degrees.
+ * @param offsetX {number} offset X
+ * @param offsetY {number} offset Y
+ * @param dontFlip {boolean} if true, axis Y will not be flipped.
+ */
+ function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
+ this.viewBox = viewBox;
+ this.scale = scale;
+ this.rotation = rotation;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ // creating transform to convert pdf coordinate system to the normal
+ // canvas like coordinates taking in account scale and rotation
+ var centerX = (viewBox[2] + viewBox[0]) / 2;
+ var centerY = (viewBox[3] + viewBox[1]) / 2;
+ var rotateA, rotateB, rotateC, rotateD;
+ rotation = rotation % 360;
+ rotation = rotation < 0 ? rotation + 360 : rotation;
+ switch (rotation) {
+ case 180:
+ rotateA = -1;
+ rotateB = 0;
+ rotateC = 0;
+ rotateD = 1;
+ break;
+ case 90:
+ rotateA = 0;
+ rotateB = 1;
+ rotateC = 1;
+ rotateD = 0;
+ break;
+ case 270:
+ rotateA = 0;
+ rotateB = -1;
+ rotateC = -1;
+ rotateD = 0;
+ break;
+ //case 0:
+ default:
+ rotateA = 1;
+ rotateB = 0;
+ rotateC = 0;
+ rotateD = -1;
+ break;
+ }
+ if (dontFlip) {
+ rotateC = -rotateC;
+ rotateD = -rotateD;
+ }
+ var offsetCanvasX, offsetCanvasY;
+ var width, height;
+ if (rotateA === 0) {
+ offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+ width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ } else {
+ offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+ width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ }
+ // creating transform for the following operations:
+ // translate(-centerX, -centerY), rotate and flip vertically,
+ // scale, and translate(offsetCanvasX, offsetCanvasY)
+ this.transform = [
+ rotateA * scale,
+ rotateB * scale,
+ rotateC * scale,
+ rotateD * scale,
+ offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+ offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+ ];
+ this.width = width;
+ this.height = height;
+ this.fontScale = scale;
+ }
+ PageViewport.prototype = /** @lends PageViewport.prototype */
+ {
+ /**
+ * Clones viewport with additional properties.
+ * @param args {Object} (optional) If specified, may contain the 'scale' or
+ * 'rotation' properties to override the corresponding properties in
+ * the cloned viewport.
+ * @returns {PageViewport} Cloned viewport.
+ */
+ clone: function PageViewPort_clone(args) {
+ args = args || {};
+ var scale = 'scale' in args ? args.scale : this.scale;
+ var rotation = 'rotation' in args ? args.rotation : this.rotation;
+ return new PageViewport(this.viewBox.slice(), scale, rotation, this.offsetX, this.offsetY, args.dontFlip);
+ },
+ /**
+ * Converts PDF point to the viewport coordinates. For examples, useful for
+ * converting PDF location into canvas pixel coordinates.
+ * @param x {number} X coordinate.
+ * @param y {number} Y coordinate.
+ * @returns {Object} Object that contains 'x' and 'y' properties of the
+ * point in the viewport coordinate space.
+ * @see {@link convertToPdfPoint}
+ * @see {@link convertToViewportRectangle}
+ */
+ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+ return Util.applyTransform([
+ x,
+ y
+ ], this.transform);
+ },
+ /**
+ * Converts PDF rectangle to the viewport coordinates.
+ * @param rect {Array} xMin, yMin, xMax and yMax coordinates.
+ * @returns {Array} Contains corresponding coordinates of the rectangle
+ * in the viewport coordinate space.
+ * @see {@link convertToViewportPoint}
+ */
+ convertToViewportRectangle: function PageViewport_convertToViewportRectangle(rect) {
+ var tl = Util.applyTransform([
+ rect[0],
+ rect[1]
+ ], this.transform);
+ var br = Util.applyTransform([
+ rect[2],
+ rect[3]
+ ], this.transform);
+ return [
+ tl[0],
+ tl[1],
+ br[0],
+ br[1]
+ ];
+ },
+ /**
+ * Converts viewport coordinates to the PDF location. For examples, useful
+ * for converting canvas pixel location into PDF one.
+ * @param x {number} X coordinate.
+ * @param y {number} Y coordinate.
+ * @returns {Object} Object that contains 'x' and 'y' properties of the
+ * point in the PDF coordinate space.
+ * @see {@link convertToViewportPoint}
+ */
+ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+ return Util.applyInverseTransform([
+ x,
+ y
+ ], this.transform);
+ }
+ };
+ return PageViewport;
+ }();
+ var PDFStringTranslateTable = [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0x2D8,
+ 0x2C7,
+ 0x2C6,
+ 0x2D9,
+ 0x2DD,
+ 0x2DB,
+ 0x2DA,
+ 0x2DC,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0x2022,
+ 0x2020,
+ 0x2021,
+ 0x2026,
+ 0x2014,
+ 0x2013,
+ 0x192,
+ 0x2044,
+ 0x2039,
+ 0x203A,
+ 0x2212,
+ 0x2030,
+ 0x201E,
+ 0x201C,
+ 0x201D,
+ 0x2018,
+ 0x2019,
+ 0x201A,
+ 0x2122,
+ 0xFB01,
+ 0xFB02,
+ 0x141,
+ 0x152,
+ 0x160,
+ 0x178,
+ 0x17D,
+ 0x131,
+ 0x142,
+ 0x153,
+ 0x161,
+ 0x17E,
+ 0,
+ 0x20AC
+ ];
+ function stringToPDFString(str) {
+ var i, n = str.length, strBuf = [];
+ if (str[0] === '\xFE' && str[1] === '\xFF') {
+ // UTF16BE BOM
+ for (i = 2; i < n; i += 2) {
+ strBuf.push(String.fromCharCode(str.charCodeAt(i) << 8 | str.charCodeAt(i + 1)));
+ }
+ } else {
+ for (i = 0; i < n; ++i) {
+ var code = PDFStringTranslateTable[str.charCodeAt(i)];
+ strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
+ }
+ }
+ return strBuf.join('');
+ }
+ function stringToUTF8String(str) {
+ return decodeURIComponent(escape(str));
+ }
+ function utf8StringToString(str) {
+ return unescape(encodeURIComponent(str));
+ }
+ function isEmptyObj(obj) {
+ for (var key in obj) {
+ return false;
+ }
+ return true;
+ }
+ function isBool(v) {
+ return typeof v === 'boolean';
+ }
+ function isInt(v) {
+ return typeof v === 'number' && (v | 0) === v;
+ }
+ function isNum(v) {
+ return typeof v === 'number';
+ }
+ function isString(v) {
+ return typeof v === 'string';
+ }
+ function isArray(v) {
+ return v instanceof Array;
+ }
+ function isArrayBuffer(v) {
+ return typeof v === 'object' && v !== null && v.byteLength !== undefined;
+ }
+ // Checks if ch is one of the following characters: SPACE, TAB, CR or LF.
+ function isSpace(ch) {
+ return ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A;
+ }
+ /**
+ * Promise Capability object.
+ *
+ * @typedef {Object} PromiseCapability
+ * @property {Promise} promise - A promise object.
+ * @property {function} resolve - Fulfills the promise.
+ * @property {function} reject - Rejects the promise.
+ */
+ /**
+ * Creates a promise capability object.
+ * @alias createPromiseCapability
+ *
+ * @return {PromiseCapability} A capability object contains:
+ * - a Promise, resolve and reject methods.
+ */
+ function createPromiseCapability() {
+ var capability = {};
+ capability.promise = new Promise(function (resolve, reject) {
+ capability.resolve = resolve;
+ capability.reject = reject;
+ });
+ return capability;
+ }
+ /**
+ * Polyfill for Promises:
+ * The following promise implementation tries to generally implement the
+ * Promise/A+ spec. Some notable differences from other promise libraries are:
+ * - There currently isn't a separate deferred and promise object.
+ * - Unhandled rejections eventually show an error if they aren't handled.
+ *
+ * Based off of the work in:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
+ */
+ (function PromiseClosure() {
+ if (globalScope.Promise) {
+ // Promises existing in the DOM/Worker, checking presence of all/resolve
+ if (typeof globalScope.Promise.all !== 'function') {
+ globalScope.Promise.all = function (iterable) {
+ var count = 0, results = [], resolve, reject;
+ var promise = new globalScope.Promise(function (resolve_, reject_) {
+ resolve = resolve_;
+ reject = reject_;
+ });
+ iterable.forEach(function (p, i) {
+ count++;
+ p.then(function (result) {
+ results[i] = result;
+ count--;
+ if (count === 0) {
+ resolve(results);
+ }
+ }, reject);
+ });
+ if (count === 0) {
+ resolve(results);
+ }
+ return promise;
+ };
+ }
+ if (typeof globalScope.Promise.resolve !== 'function') {
+ globalScope.Promise.resolve = function (value) {
+ return new globalScope.Promise(function (resolve) {
+ resolve(value);
+ });
+ };
+ }
+ if (typeof globalScope.Promise.reject !== 'function') {
+ globalScope.Promise.reject = function (reason) {
+ return new globalScope.Promise(function (resolve, reject) {
+ reject(reason);
+ });
+ };
+ }
+ if (typeof globalScope.Promise.prototype.catch !== 'function') {
+ globalScope.Promise.prototype.catch = function (onReject) {
+ return globalScope.Promise.prototype.then(undefined, onReject);
+ };
+ }
+ return;
+ }
+ throw new Error('DOM Promise is not present');
+ }());
+ var StatTimer = function StatTimerClosure() {
+ function rpad(str, pad, length) {
+ while (str.length < length) {
+ str += pad;
+ }
+ return str;
+ }
+ function StatTimer() {
+ this.started = Object.create(null);
+ this.times = [];
+ this.enabled = true;
+ }
+ StatTimer.prototype = {
+ time: function StatTimer_time(name) {
+ if (!this.enabled) {
+ return;
+ }
+ if (name in this.started) {
+ warn('Timer is already running for ' + name);
+ }
+ this.started[name] = Date.now();
+ },
+ timeEnd: function StatTimer_timeEnd(name) {
+ if (!this.enabled) {
+ return;
+ }
+ if (!(name in this.started)) {
+ warn('Timer has not been started for ' + name);
+ }
+ this.times.push({
+ 'name': name,
+ 'start': this.started[name],
+ 'end': Date.now()
+ });
+ // Remove timer from started so it can be called again.
+ delete this.started[name];
+ },
+ toString: function StatTimer_toString() {
+ var i, ii;
+ var times = this.times;
+ var out = '';
+ // Find the longest name for padding purposes.
+ var longest = 0;
+ for (i = 0, ii = times.length; i < ii; ++i) {
+ var name = times[i]['name'];
+ if (name.length > longest) {
+ longest = name.length;
+ }
+ }
+ for (i = 0, ii = times.length; i < ii; ++i) {
+ var span = times[i];
+ var duration = span.end - span.start;
+ out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
+ }
+ return out;
+ }
+ };
+ return StatTimer;
+ }();
+ var createBlob = function createBlob(data, contentType) {
+ if (typeof Blob !== 'undefined') {
+ return new Blob([data], { type: contentType });
+ }
+ warn('The "Blob" constructor is not supported.');
+ };
+ var createObjectURL = function createObjectURLClosure() {
+ // Blob/createObjectURL is not available, falling back to data schema.
+ var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+ return function createObjectURL(data, contentType, forceDataSchema) {
+ if (!forceDataSchema && typeof URL !== 'undefined' && URL.createObjectURL) {
+ var blob = createBlob(data, contentType);
+ return URL.createObjectURL(blob);
+ }
+ var buffer = 'data:' + contentType + ';base64,';
+ for (var i = 0, ii = data.length; i < ii; i += 3) {
+ var b1 = data[i] & 0xFF;
+ var b2 = data[i + 1] & 0xFF;
+ var b3 = data[i + 2] & 0xFF;
+ var d1 = b1 >> 2, d2 = (b1 & 3) << 4 | b2 >> 4;
+ var d3 = i + 1 < ii ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
+ var d4 = i + 2 < ii ? b3 & 0x3F : 64;
+ buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
+ }
+ return buffer;
+ };
+ }();
+ function MessageHandler(sourceName, targetName, comObj) {
+ this.sourceName = sourceName;
+ this.targetName = targetName;
+ this.comObj = comObj;
+ this.callbackIndex = 1;
+ this.postMessageTransfers = true;
+ var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
+ var ah = this.actionHandler = Object.create(null);
+ this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
+ var data = event.data;
+ if (data.targetName !== this.sourceName) {
+ return;
+ }
+ if (data.isReply) {
+ var callbackId = data.callbackId;
+ if (data.callbackId in callbacksCapabilities) {
+ var callback = callbacksCapabilities[callbackId];
+ delete callbacksCapabilities[callbackId];
+ if ('error' in data) {
+ callback.reject(data.error);
+ } else {
+ callback.resolve(data.data);
+ }
+ } else {
+ error('Cannot resolve callback ' + callbackId);
+ }
+ } else if (data.action in ah) {
+ var action = ah[data.action];
+ if (data.callbackId) {
+ var sourceName = this.sourceName;
+ var targetName = data.sourceName;
+ Promise.resolve().then(function () {
+ return action[0].call(action[1], data.data);
+ }).then(function (result) {
+ comObj.postMessage({
+ sourceName: sourceName,
+ targetName: targetName,
+ isReply: true,
+ callbackId: data.callbackId,
+ data: result
+ });
+ }, function (reason) {
+ if (reason instanceof Error) {
+ // Serialize error to avoid "DataCloneError"
+ reason = reason + '';
+ }
+ comObj.postMessage({
+ sourceName: sourceName,
+ targetName: targetName,
+ isReply: true,
+ callbackId: data.callbackId,
+ error: reason
+ });
+ });
+ } else {
+ action[0].call(action[1], data.data);
+ }
+ } else {
+ error('Unknown action from worker: ' + data.action);
+ }
+ }.bind(this);
+ comObj.addEventListener('message', this._onComObjOnMessage);
+ }
+ MessageHandler.prototype = {
+ on: function messageHandlerOn(actionName, handler, scope) {
+ var ah = this.actionHandler;
+ if (ah[actionName]) {
+ error('There is already an actionName called "' + actionName + '"');
+ }
+ ah[actionName] = [
+ handler,
+ scope
+ ];
+ },
+ /**
+ * Sends a message to the comObj to invoke the action with the supplied data.
+ * @param {String} actionName Action to call.
+ * @param {JSON} data JSON data to send.
+ * @param {Array} [transfers] Optional list of transfers/ArrayBuffers
+ */
+ send: function messageHandlerSend(actionName, data, transfers) {
+ var message = {
+ sourceName: this.sourceName,
+ targetName: this.targetName,
+ action: actionName,
+ data: data
+ };
+ this.postMessage(message, transfers);
+ },
+ /**
+ * Sends a message to the comObj to invoke the action with the supplied data.
+ * Expects that other side will callback with the response.
+ * @param {String} actionName Action to call.
+ * @param {JSON} data JSON data to send.
+ * @param {Array} [transfers] Optional list of transfers/ArrayBuffers.
+ * @returns {Promise} Promise to be resolved with response data.
+ */
+ sendWithPromise: function messageHandlerSendWithPromise(actionName, data, transfers) {
+ var callbackId = this.callbackIndex++;
+ var message = {
+ sourceName: this.sourceName,
+ targetName: this.targetName,
+ action: actionName,
+ data: data,
+ callbackId: callbackId
+ };
+ var capability = createPromiseCapability();
+ this.callbacksCapabilities[callbackId] = capability;
+ try {
+ this.postMessage(message, transfers);
+ } catch (e) {
+ capability.reject(e);
+ }
+ return capability.promise;
+ },
+ /**
+ * Sends raw message to the comObj.
+ * @private
+ * @param message {Object} Raw message.
+ * @param transfers List of transfers/ArrayBuffers, or undefined.
+ */
+ postMessage: function (message, transfers) {
+ if (transfers && this.postMessageTransfers) {
+ this.comObj.postMessage(message, transfers);
+ } else {
+ this.comObj.postMessage(message);
+ }
+ },
+ destroy: function () {
+ this.comObj.removeEventListener('message', this._onComObjOnMessage);
+ }
+ };
+ function loadJpegStream(id, imageUrl, objs) {
+ var img = new Image();
+ img.onload = function loadJpegStream_onloadClosure() {
+ objs.resolve(id, img);
+ };
+ img.onerror = function loadJpegStream_onerrorClosure() {
+ objs.resolve(id, null);
+ warn('Error during JPEG image loading');
+ };
+ img.src = imageUrl;
+ }
+ exports.FONT_IDENTITY_MATRIX = FONT_IDENTITY_MATRIX;
+ exports.IDENTITY_MATRIX = IDENTITY_MATRIX;
+ exports.OPS = OPS;
+ exports.VERBOSITY_LEVELS = VERBOSITY_LEVELS;
+ exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES;
+ exports.AnnotationBorderStyleType = AnnotationBorderStyleType;
+ exports.AnnotationFieldFlag = AnnotationFieldFlag;
+ exports.AnnotationFlag = AnnotationFlag;
+ exports.AnnotationType = AnnotationType;
+ exports.FontType = FontType;
+ exports.ImageKind = ImageKind;
+ exports.InvalidPDFException = InvalidPDFException;
+ exports.MessageHandler = MessageHandler;
+ exports.MissingDataException = MissingDataException;
+ exports.MissingPDFException = MissingPDFException;
+ exports.NotImplementedException = NotImplementedException;
+ exports.PageViewport = PageViewport;
+ exports.PasswordException = PasswordException;
+ exports.PasswordResponses = PasswordResponses;
+ exports.StatTimer = StatTimer;
+ exports.StreamType = StreamType;
+ exports.TextRenderingMode = TextRenderingMode;
+ exports.UnexpectedResponseException = UnexpectedResponseException;
+ exports.UnknownErrorException = UnknownErrorException;
+ exports.Util = Util;
+ exports.XRefParseException = XRefParseException;
+ exports.arrayByteLength = arrayByteLength;
+ exports.arraysToBytes = arraysToBytes;
+ exports.assert = assert;
+ exports.bytesToString = bytesToString;
+ exports.createBlob = createBlob;
+ exports.createPromiseCapability = createPromiseCapability;
+ exports.createObjectURL = createObjectURL;
+ exports.deprecated = deprecated;
+ exports.error = error;
+ exports.getLookupTableFactory = getLookupTableFactory;
+ exports.getVerbosityLevel = getVerbosityLevel;
+ exports.globalScope = globalScope;
+ exports.info = info;
+ exports.isArray = isArray;
+ exports.isArrayBuffer = isArrayBuffer;
+ exports.isBool = isBool;
+ exports.isEmptyObj = isEmptyObj;
+ exports.isInt = isInt;
+ exports.isNum = isNum;
+ exports.isString = isString;
+ exports.isSpace = isSpace;
+ exports.isSameOrigin = isSameOrigin;
+ exports.createValidAbsoluteUrl = createValidAbsoluteUrl;
+ exports.isLittleEndian = isLittleEndian;
+ exports.isEvalSupported = isEvalSupported;
+ exports.loadJpegStream = loadJpegStream;
+ exports.log2 = log2;
+ exports.readInt8 = readInt8;
+ exports.readUint16 = readUint16;
+ exports.readUint32 = readUint32;
+ exports.removeNullCharacters = removeNullCharacters;
+ exports.setVerbosityLevel = setVerbosityLevel;
+ exports.shadow = shadow;
+ exports.string32 = string32;
+ exports.stringToBytes = stringToBytes;
+ exports.stringToPDFString = stringToPDFString;
+ exports.stringToUTF8String = stringToUTF8String;
+ exports.utf8StringToString = utf8StringToString;
+ exports.warn = warn;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayDOMUtils = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var removeNullCharacters = sharedUtil.removeNullCharacters;
+ var warn = sharedUtil.warn;
+ var deprecated = sharedUtil.deprecated;
+ var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
+ /**
+ * Optimised CSS custom property getter/setter.
+ * @class
+ */
+ var CustomStyle = function CustomStyleClosure() {
+ // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
+ // animate-css-transforms-firefox-webkit.html
+ // in some versions of IE9 it is critical that ms appear in this list
+ // before Moz
+ var prefixes = [
+ 'ms',
+ 'Moz',
+ 'Webkit',
+ 'O'
+ ];
+ var _cache = Object.create(null);
+ function CustomStyle() {
+ }
+ CustomStyle.getProp = function get(propName, element) {
+ // check cache only when no element is given
+ if (arguments.length === 1 && typeof _cache[propName] === 'string') {
+ return _cache[propName];
+ }
+ element = element || document.documentElement;
+ var style = element.style, prefixed, uPropName;
+ // test standard property first
+ if (typeof style[propName] === 'string') {
+ return _cache[propName] = propName;
+ }
+ // capitalize
+ uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
+ // test vendor specific properties
+ for (var i = 0, l = prefixes.length; i < l; i++) {
+ prefixed = prefixes[i] + uPropName;
+ if (typeof style[prefixed] === 'string') {
+ return _cache[propName] = prefixed;
+ }
+ }
+ //if all fails then set to undefined
+ return _cache[propName] = 'undefined';
+ };
+ CustomStyle.setProp = function set(propName, element, str) {
+ var prop = this.getProp(propName);
+ if (prop !== 'undefined') {
+ element.style[prop] = str;
+ }
+ };
+ return CustomStyle;
+ }();
+ var hasCanvasTypedArrays;
+ hasCanvasTypedArrays = function () {
+ return true;
+ };
+ var LinkTarget = {
+ NONE: 0,
+ // Default value.
+ SELF: 1,
+ BLANK: 2,
+ PARENT: 3,
+ TOP: 4
+ };
+ var LinkTargetStringMap = [
+ '',
+ '_self',
+ '_blank',
+ '_parent',
+ '_top'
+ ];
+ /**
+ * @typedef ExternalLinkParameters
+ * @typedef {Object} ExternalLinkParameters
+ * @property {string} url - An absolute URL.
+ * @property {LinkTarget} target - The link target.
+ * @property {string} rel - The link relationship.
+ */
+ /**
+ * Adds various attributes (href, title, target, rel) to hyperlinks.
+ * @param {HTMLLinkElement} link - The link element.
+ * @param {ExternalLinkParameters} params
+ */
+ function addLinkAttributes(link, params) {
+ var url = params && params.url;
+ link.href = link.title = url ? removeNullCharacters(url) : '';
+ if (url) {
+ var target = params.target;
+ if (typeof target === 'undefined') {
+ target = getDefaultSetting('externalLinkTarget');
+ }
+ link.target = LinkTargetStringMap[target];
+ var rel = params.rel;
+ if (typeof rel === 'undefined') {
+ rel = getDefaultSetting('externalLinkRel');
+ }
+ link.rel = rel;
+ }
+ }
+ // Gets the file name from a given URL.
+ function getFilenameFromUrl(url) {
+ var anchor = url.indexOf('#');
+ var query = url.indexOf('?');
+ var end = Math.min(anchor > 0 ? anchor : url.length, query > 0 ? query : url.length);
+ return url.substring(url.lastIndexOf('/', end) + 1, end);
+ }
+ function getDefaultSetting(id) {
+ // The list of the settings and their default is maintained for backward
+ // compatibility and shall not be extended or modified. See also global.js.
+ var globalSettings = sharedUtil.globalScope.PDFJS;
+ switch (id) {
+ case 'pdfBug':
+ return globalSettings ? globalSettings.pdfBug : false;
+ case 'disableAutoFetch':
+ return globalSettings ? globalSettings.disableAutoFetch : false;
+ case 'disableStream':
+ return globalSettings ? globalSettings.disableStream : false;
+ case 'disableRange':
+ return globalSettings ? globalSettings.disableRange : false;
+ case 'disableFontFace':
+ return globalSettings ? globalSettings.disableFontFace : false;
+ case 'disableCreateObjectURL':
+ return globalSettings ? globalSettings.disableCreateObjectURL : false;
+ case 'disableWebGL':
+ return globalSettings ? globalSettings.disableWebGL : true;
+ case 'cMapUrl':
+ return globalSettings ? globalSettings.cMapUrl : null;
+ case 'cMapPacked':
+ return globalSettings ? globalSettings.cMapPacked : false;
+ case 'postMessageTransfers':
+ return globalSettings ? globalSettings.postMessageTransfers : true;
+ case 'workerSrc':
+ return globalSettings ? globalSettings.workerSrc : null;
+ case 'disableWorker':
+ return globalSettings ? globalSettings.disableWorker : false;
+ case 'maxImageSize':
+ return globalSettings ? globalSettings.maxImageSize : -1;
+ case 'imageResourcesPath':
+ return globalSettings ? globalSettings.imageResourcesPath : '';
+ case 'isEvalSupported':
+ return globalSettings ? globalSettings.isEvalSupported : true;
+ case 'externalLinkTarget':
+ if (!globalSettings) {
+ return LinkTarget.NONE;
+ }
+ switch (globalSettings.externalLinkTarget) {
+ case LinkTarget.NONE:
+ case LinkTarget.SELF:
+ case LinkTarget.BLANK:
+ case LinkTarget.PARENT:
+ case LinkTarget.TOP:
+ return globalSettings.externalLinkTarget;
+ }
+ warn('PDFJS.externalLinkTarget is invalid: ' + globalSettings.externalLinkTarget);
+ // Reset the external link target, to suppress further warnings.
+ globalSettings.externalLinkTarget = LinkTarget.NONE;
+ return LinkTarget.NONE;
+ case 'externalLinkRel':
+ return globalSettings ? globalSettings.externalLinkRel : 'noreferrer';
+ case 'enableStats':
+ return !!(globalSettings && globalSettings.enableStats);
+ default:
+ throw new Error('Unknown default setting: ' + id);
+ }
+ }
+ function isExternalLinkTargetSet() {
+ var externalLinkTarget = getDefaultSetting('externalLinkTarget');
+ switch (externalLinkTarget) {
+ case LinkTarget.NONE:
+ return false;
+ case LinkTarget.SELF:
+ case LinkTarget.BLANK:
+ case LinkTarget.PARENT:
+ case LinkTarget.TOP:
+ return true;
+ }
+ }
+ function isValidUrl(url, allowRelative) {
+ deprecated('isValidUrl(), please use createValidAbsoluteUrl() instead.');
+ var baseUrl = allowRelative ? 'http://example.com' : null;
+ return createValidAbsoluteUrl(url, baseUrl) !== null;
+ }
+ exports.CustomStyle = CustomStyle;
+ exports.addLinkAttributes = addLinkAttributes;
+ exports.isExternalLinkTargetSet = isExternalLinkTargetSet;
+ exports.isValidUrl = isValidUrl;
+ exports.getFilenameFromUrl = getFilenameFromUrl;
+ exports.LinkTarget = LinkTarget;
+ exports.hasCanvasTypedArrays = hasCanvasTypedArrays;
+ exports.getDefaultSetting = getDefaultSetting;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayFontLoader = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var assert = sharedUtil.assert;
+ var bytesToString = sharedUtil.bytesToString;
+ var string32 = sharedUtil.string32;
+ var shadow = sharedUtil.shadow;
+ var warn = sharedUtil.warn;
+ function FontLoader(docId) {
+ this.docId = docId;
+ this.styleElement = null;
+ }
+ FontLoader.prototype = {
+ insertRule: function fontLoaderInsertRule(rule) {
+ var styleElement = this.styleElement;
+ if (!styleElement) {
+ styleElement = this.styleElement = document.createElement('style');
+ styleElement.id = 'PDFJS_FONT_STYLE_TAG_' + this.docId;
+ document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement);
+ }
+ var styleSheet = styleElement.sheet;
+ styleSheet.insertRule(rule, styleSheet.cssRules.length);
+ },
+ clear: function fontLoaderClear() {
+ var styleElement = this.styleElement;
+ if (styleElement) {
+ styleElement.parentNode.removeChild(styleElement);
+ styleElement = this.styleElement = null;
+ }
+ }
+ };
+ FontLoader.prototype.bind = function fontLoaderBind(fonts, callback) {
+ for (var i = 0, ii = fonts.length; i < ii; i++) {
+ var font = fonts[i];
+ if (font.attached) {
+ continue;
+ }
+ font.attached = true;
+ var rule = font.createFontFaceRule();
+ if (rule) {
+ this.insertRule(rule);
+ }
+ }
+ setTimeout(callback);
+ };
+ var IsEvalSupportedCached = {
+ get value() {
+ return shadow(this, 'value', sharedUtil.isEvalSupported());
+ }
+ };
+ var FontFaceObject = function FontFaceObjectClosure() {
+ function FontFaceObject(translatedData, options) {
+ this.compiledGlyphs = Object.create(null);
+ // importing translated data
+ for (var i in translatedData) {
+ this[i] = translatedData[i];
+ }
+ this.options = options;
+ }
+ FontFaceObject.prototype = {
+ createNativeFontFace: function FontFaceObject_createNativeFontFace() {
+ throw new Error('Not implemented: createNativeFontFace');
+ },
+ createFontFaceRule: function FontFaceObject_createFontFaceRule() {
+ if (!this.data) {
+ return null;
+ }
+ if (this.options.disableFontFace) {
+ this.disableFontFace = true;
+ return null;
+ }
+ var data = bytesToString(new Uint8Array(this.data));
+ var fontName = this.loadedName;
+ // Add the font-face rule to the document
+ var url = 'url(data:' + this.mimetype + ';base64,' + btoa(data) + ');';
+ var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
+ if (this.options.fontRegistry) {
+ this.options.fontRegistry.registerFont(this, url);
+ }
+ return rule;
+ },
+ getPathGenerator: function FontFaceObject_getPathGenerator(objs, character) {
+ if (!(character in this.compiledGlyphs)) {
+ var cmds = objs.get(this.loadedName + '_path_' + character);
+ var current, i, len;
+ // If we can, compile cmds into JS for MAXIMUM SPEED
+ if (this.options.isEvalSupported && IsEvalSupportedCached.value) {
+ var args, js = '';
+ for (i = 0, len = cmds.length; i < len; i++) {
+ current = cmds[i];
+ if (current.args !== undefined) {
+ args = current.args.join(',');
+ } else {
+ args = '';
+ }
+ js += 'c.' + current.cmd + '(' + args + ');\n';
+ }
+ this.compiledGlyphs[character] = new Function('c', 'size', js);
+ } else {
+ // But fall back on using Function.prototype.apply() if we're
+ // blocked from using eval() for whatever reason (like CSP policies)
+ this.compiledGlyphs[character] = function (c, size) {
+ for (i = 0, len = cmds.length; i < len; i++) {
+ current = cmds[i];
+ if (current.cmd === 'scale') {
+ current.args = [
+ size,
+ -size
+ ];
+ }
+ c[current.cmd].apply(c, current.args);
+ }
+ };
+ }
+ }
+ return this.compiledGlyphs[character];
+ }
+ };
+ return FontFaceObject;
+ }();
+ exports.FontFaceObject = FontFaceObject;
+ exports.FontLoader = FontLoader;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayMetadata = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var error = sharedUtil.error;
+ function fixMetadata(meta) {
+ return meta.replace(/>\\376\\377([^<]+)/g, function (all, codes) {
+ var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
+ return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
+ });
+ var chars = '';
+ for (var i = 0; i < bytes.length; i += 2) {
+ var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
+ chars += code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38 && false ? String.fromCharCode(code) : '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
+ }
+ return '>' + chars;
+ });
+ }
+ function Metadata(meta) {
+ if (typeof meta === 'string') {
+ // Ghostscript produces invalid metadata
+ meta = fixMetadata(meta);
+ var parser = new DOMParser();
+ meta = parser.parseFromString(meta, 'application/xml');
+ } else if (!(meta instanceof Document)) {
+ error('Metadata: Invalid metadata object');
+ }
+ this.metaDocument = meta;
+ this.metadata = Object.create(null);
+ this.parse();
+ }
+ Metadata.prototype = {
+ parse: function Metadata_parse() {
+ var doc = this.metaDocument;
+ var rdf = doc.documentElement;
+ if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
+ // Wrapped in <xmpmeta>
+ rdf = rdf.firstChild;
+ while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') {
+ rdf = rdf.nextSibling;
+ }
+ }
+ var nodeName = rdf ? rdf.nodeName.toLowerCase() : null;
+ if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) {
+ return;
+ }
+ var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength;
+ for (i = 0, length = children.length; i < length; i++) {
+ desc = children[i];
+ if (desc.nodeName.toLowerCase() !== 'rdf:description') {
+ continue;
+ }
+ for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) {
+ if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') {
+ entry = desc.childNodes[ii];
+ name = entry.nodeName.toLowerCase();
+ this.metadata[name] = entry.textContent.trim();
+ }
+ }
+ }
+ },
+ get: function Metadata_get(name) {
+ return this.metadata[name] || null;
+ },
+ has: function Metadata_has(name) {
+ return typeof this.metadata[name] !== 'undefined';
+ }
+ };
+ exports.Metadata = Metadata;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplaySVG = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayAnnotationLayer = {}, root.pdfjsSharedUtil, root.pdfjsDisplayDOMUtils);
+ }(this, function (exports, sharedUtil, displayDOMUtils) {
+ var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
+ var AnnotationType = sharedUtil.AnnotationType;
+ var Util = sharedUtil.Util;
+ var addLinkAttributes = displayDOMUtils.addLinkAttributes;
+ var LinkTarget = displayDOMUtils.LinkTarget;
+ var getFilenameFromUrl = displayDOMUtils.getFilenameFromUrl;
+ var warn = sharedUtil.warn;
+ var CustomStyle = displayDOMUtils.CustomStyle;
+ var getDefaultSetting = displayDOMUtils.getDefaultSetting;
+ /**
+ * @typedef {Object} AnnotationElementParameters
+ * @property {Object} data
+ * @property {HTMLDivElement} layer
+ * @property {PDFPage} page
+ * @property {PageViewport} viewport
+ * @property {IPDFLinkService} linkService
+ * @property {DownloadManager} downloadManager
+ * @property {string} imageResourcesPath
+ * @property {boolean} renderInteractiveForms
+ */
+ /**
+ * @class
+ * @alias AnnotationElementFactory
+ */
+ function AnnotationElementFactory() {
+ }
+ AnnotationElementFactory.prototype = /** @lends AnnotationElementFactory.prototype */
+ {
+ /**
+ * @param {AnnotationElementParameters} parameters
+ * @returns {AnnotationElement}
+ */
+ create: function AnnotationElementFactory_create(parameters) {
+ var subtype = parameters.data.annotationType;
+ switch (subtype) {
+ case AnnotationType.LINK:
+ return new LinkAnnotationElement(parameters);
+ case AnnotationType.TEXT:
+ return new TextAnnotationElement(parameters);
+ case AnnotationType.WIDGET:
+ var fieldType = parameters.data.fieldType;
+ switch (fieldType) {
+ case 'Tx':
+ return new TextWidgetAnnotationElement(parameters);
+ case 'Ch':
+ return new ChoiceWidgetAnnotationElement(parameters);
+ }
+ return new WidgetAnnotationElement(parameters);
+ case AnnotationType.POPUP:
+ return new PopupAnnotationElement(parameters);
+ case AnnotationType.HIGHLIGHT:
+ return new HighlightAnnotationElement(parameters);
+ case AnnotationType.UNDERLINE:
+ return new UnderlineAnnotationElement(parameters);
+ case AnnotationType.SQUIGGLY:
+ return new SquigglyAnnotationElement(parameters);
+ case AnnotationType.STRIKEOUT:
+ return new StrikeOutAnnotationElement(parameters);
+ case AnnotationType.FILEATTACHMENT:
+ return new FileAttachmentAnnotationElement(parameters);
+ default:
+ return new AnnotationElement(parameters);
+ }
+ }
+ };
+ /**
+ * @class
+ * @alias AnnotationElement
+ */
+ var AnnotationElement = function AnnotationElementClosure() {
+ function AnnotationElement(parameters, isRenderable) {
+ this.isRenderable = isRenderable || false;
+ this.data = parameters.data;
+ this.layer = parameters.layer;
+ this.page = parameters.page;
+ this.viewport = parameters.viewport;
+ this.linkService = parameters.linkService;
+ this.downloadManager = parameters.downloadManager;
+ this.imageResourcesPath = parameters.imageResourcesPath;
+ this.renderInteractiveForms = parameters.renderInteractiveForms;
+ if (isRenderable) {
+ this.container = this._createContainer();
+ }
+ }
+ AnnotationElement.prototype = /** @lends AnnotationElement.prototype */
+ {
+ /**
+ * Create an empty container for the annotation's HTML element.
+ *
+ * @private
+ * @memberof AnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ _createContainer: function AnnotationElement_createContainer() {
+ var data = this.data, page = this.page, viewport = this.viewport;
+ var container = document.createElement('section');
+ var width = data.rect[2] - data.rect[0];
+ var height = data.rect[3] - data.rect[1];
+ container.setAttribute('data-annotation-id', data.id);
+ // Do *not* modify `data.rect`, since that will corrupt the annotation
+ // position on subsequent calls to `_createContainer` (see issue 6804).
+ var rect = Util.normalizeRect([
+ data.rect[0],
+ page.view[3] - data.rect[1] + page.view[1],
+ data.rect[2],
+ page.view[3] - data.rect[3] + page.view[1]
+ ]);
+ CustomStyle.setProp('transform', container, 'matrix(' + viewport.transform.join(',') + ')');
+ CustomStyle.setProp('transformOrigin', container, -rect[0] + 'px ' + -rect[1] + 'px');
+ if (data.borderStyle.width > 0) {
+ container.style.borderWidth = data.borderStyle.width + 'px';
+ if (data.borderStyle.style !== AnnotationBorderStyleType.UNDERLINE) {
+ // Underline styles only have a bottom border, so we do not need
+ // to adjust for all borders. This yields a similar result as
+ // Adobe Acrobat/Reader.
+ width = width - 2 * data.borderStyle.width;
+ height = height - 2 * data.borderStyle.width;
+ }
+ var horizontalRadius = data.borderStyle.horizontalCornerRadius;
+ var verticalRadius = data.borderStyle.verticalCornerRadius;
+ if (horizontalRadius > 0 || verticalRadius > 0) {
+ var radius = horizontalRadius + 'px / ' + verticalRadius + 'px';
+ CustomStyle.setProp('borderRadius', container, radius);
+ }
+ switch (data.borderStyle.style) {
+ case AnnotationBorderStyleType.SOLID:
+ container.style.borderStyle = 'solid';
+ break;
+ case AnnotationBorderStyleType.DASHED:
+ container.style.borderStyle = 'dashed';
+ break;
+ case AnnotationBorderStyleType.BEVELED:
+ warn('Unimplemented border style: beveled');
+ break;
+ case AnnotationBorderStyleType.INSET:
+ warn('Unimplemented border style: inset');
+ break;
+ case AnnotationBorderStyleType.UNDERLINE:
+ container.style.borderBottomStyle = 'solid';
+ break;
+ default:
+ break;
+ }
+ if (data.color) {
+ container.style.borderColor = Util.makeCssRgb(data.color[0] | 0, data.color[1] | 0, data.color[2] | 0);
+ } else {
+ // Transparent (invisible) border, so do not draw it at all.
+ container.style.borderWidth = 0;
+ }
+ }
+ container.style.left = rect[0] + 'px';
+ container.style.top = rect[1] + 'px';
+ container.style.width = width + 'px';
+ container.style.height = height + 'px';
+ return container;
+ },
+ /**
+ * Create a popup for the annotation's HTML element. This is used for
+ * annotations that do not have a Popup entry in the dictionary, but
+ * are of a type that works with popups (such as Highlight annotations).
+ *
+ * @private
+ * @param {HTMLSectionElement} container
+ * @param {HTMLDivElement|HTMLImageElement|null} trigger
+ * @param {Object} data
+ * @memberof AnnotationElement
+ */
+ _createPopup: function AnnotationElement_createPopup(container, trigger, data) {
+ // If no trigger element is specified, create it.
+ if (!trigger) {
+ trigger = document.createElement('div');
+ trigger.style.height = container.style.height;
+ trigger.style.width = container.style.width;
+ container.appendChild(trigger);
+ }
+ var popupElement = new PopupElement({
+ container: container,
+ trigger: trigger,
+ color: data.color,
+ title: data.title,
+ contents: data.contents,
+ hideWrapper: true
+ });
+ var popup = popupElement.render();
+ // Position the popup next to the annotation's container.
+ popup.style.left = container.style.width;
+ container.appendChild(popup);
+ },
+ /**
+ * Render the annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof AnnotationElement
+ */
+ render: function AnnotationElement_render() {
+ throw new Error('Abstract method AnnotationElement.render called');
+ }
+ };
+ return AnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias LinkAnnotationElement
+ */
+ var LinkAnnotationElement = function LinkAnnotationElementClosure() {
+ function LinkAnnotationElement(parameters) {
+ AnnotationElement.call(this, parameters, true);
+ }
+ Util.inherit(LinkAnnotationElement, AnnotationElement, {
+ /**
+ * Render the link annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof LinkAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function LinkAnnotationElement_render() {
+ this.container.className = 'linkAnnotation';
+ var link = document.createElement('a');
+ addLinkAttributes(link, {
+ url: this.data.url,
+ target: this.data.newWindow ? LinkTarget.BLANK : undefined
+ });
+ if (!this.data.url) {
+ if (this.data.action) {
+ this._bindNamedAction(link, this.data.action);
+ } else {
+ this._bindLink(link, this.data.dest);
+ }
+ }
+ this.container.appendChild(link);
+ return this.container;
+ },
+ /**
+ * Bind internal links to the link element.
+ *
+ * @private
+ * @param {Object} link
+ * @param {Object} destination
+ * @memberof LinkAnnotationElement
+ */
+ _bindLink: function LinkAnnotationElement_bindLink(link, destination) {
+ var self = this;
+ link.href = this.linkService.getDestinationHash(destination);
+ link.onclick = function () {
+ if (destination) {
+ self.linkService.navigateTo(destination);
+ }
+ return false;
+ };
+ if (destination) {
+ link.className = 'internalLink';
+ }
+ },
+ /**
+ * Bind named actions to the link element.
+ *
+ * @private
+ * @param {Object} link
+ * @param {Object} action
+ * @memberof LinkAnnotationElement
+ */
+ _bindNamedAction: function LinkAnnotationElement_bindNamedAction(link, action) {
+ var self = this;
+ link.href = this.linkService.getAnchorUrl('');
+ link.onclick = function () {
+ self.linkService.executeNamedAction(action);
+ return false;
+ };
+ link.className = 'internalLink';
+ }
+ });
+ return LinkAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias TextAnnotationElement
+ */
+ var TextAnnotationElement = function TextAnnotationElementClosure() {
+ function TextAnnotationElement(parameters) {
+ var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+ AnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(TextAnnotationElement, AnnotationElement, {
+ /**
+ * Render the text annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof TextAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function TextAnnotationElement_render() {
+ this.container.className = 'textAnnotation';
+ var image = document.createElement('img');
+ image.style.height = this.container.style.height;
+ image.style.width = this.container.style.width;
+ image.src = this.imageResourcesPath + 'annotation-' + this.data.name.toLowerCase() + '.svg';
+ image.alt = '[{{type}} Annotation]';
+ image.dataset.l10nId = 'text_annotation_type';
+ image.dataset.l10nArgs = JSON.stringify({ type: this.data.name });
+ if (!this.data.hasPopup) {
+ this._createPopup(this.container, image, this.data);
+ }
+ this.container.appendChild(image);
+ return this.container;
+ }
+ });
+ return TextAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias WidgetAnnotationElement
+ */
+ var WidgetAnnotationElement = function WidgetAnnotationElementClosure() {
+ function WidgetAnnotationElement(parameters, isRenderable) {
+ AnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(WidgetAnnotationElement, AnnotationElement, {
+ /**
+ * Render the widget annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof WidgetAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function WidgetAnnotationElement_render() {
+ // Show only the container for unsupported field types.
+ return this.container;
+ }
+ });
+ return WidgetAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias TextWidgetAnnotationElement
+ */
+ var TextWidgetAnnotationElement = function TextWidgetAnnotationElementClosure() {
+ var TEXT_ALIGNMENT = [
+ 'left',
+ 'center',
+ 'right'
+ ];
+ function TextWidgetAnnotationElement(parameters) {
+ var isRenderable = parameters.renderInteractiveForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
+ WidgetAnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(TextWidgetAnnotationElement, WidgetAnnotationElement, {
+ /**
+ * Render the text widget annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof TextWidgetAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function TextWidgetAnnotationElement_render() {
+ this.container.className = 'textWidgetAnnotation';
+ var element = null;
+ if (this.renderInteractiveForms) {
+ // NOTE: We cannot set the values using `element.value` below, since it
+ // prevents the AnnotationLayer rasterizer in `test/driver.js`
+ // from parsing the elements correctly for the reference tests.
+ if (this.data.multiLine) {
+ element = document.createElement('textarea');
+ element.textContent = this.data.fieldValue;
+ } else {
+ element = document.createElement('input');
+ element.type = 'text';
+ element.setAttribute('value', this.data.fieldValue);
+ }
+ element.disabled = this.data.readOnly;
+ if (this.data.maxLen !== null) {
+ element.maxLength = this.data.maxLen;
+ }
+ if (this.data.comb) {
+ var fieldWidth = this.data.rect[2] - this.data.rect[0];
+ var combWidth = fieldWidth / this.data.maxLen;
+ element.classList.add('comb');
+ element.style.letterSpacing = 'calc(' + combWidth + 'px - 1ch)';
+ }
+ } else {
+ element = document.createElement('div');
+ element.textContent = this.data.fieldValue;
+ element.style.verticalAlign = 'middle';
+ element.style.display = 'table-cell';
+ var font = null;
+ if (this.data.fontRefName) {
+ font = this.page.commonObjs.getData(this.data.fontRefName);
+ }
+ this._setTextStyle(element, font);
+ }
+ if (this.data.textAlignment !== null) {
+ element.style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
+ }
+ this.container.appendChild(element);
+ return this.container;
+ },
+ /**
+ * Apply text styles to the text in the element.
+ *
+ * @private
+ * @param {HTMLDivElement} element
+ * @param {Object} font
+ * @memberof TextWidgetAnnotationElement
+ */
+ _setTextStyle: function TextWidgetAnnotationElement_setTextStyle(element, font) {
+ // TODO: This duplicates some of the logic in CanvasGraphics.setFont().
+ var style = element.style;
+ style.fontSize = this.data.fontSize + 'px';
+ style.direction = this.data.fontDirection < 0 ? 'rtl' : 'ltr';
+ if (!font) {
+ return;
+ }
+ style.fontWeight = font.black ? font.bold ? '900' : 'bold' : font.bold ? 'bold' : 'normal';
+ style.fontStyle = font.italic ? 'italic' : 'normal';
+ // Use a reasonable default font if the font doesn't specify a fallback.
+ var fontFamily = font.loadedName ? '"' + font.loadedName + '", ' : '';
+ var fallbackName = font.fallbackName || 'Helvetica, sans-serif';
+ style.fontFamily = fontFamily + fallbackName;
+ }
+ });
+ return TextWidgetAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias ChoiceWidgetAnnotationElement
+ */
+ var ChoiceWidgetAnnotationElement = function ChoiceWidgetAnnotationElementClosure() {
+ function ChoiceWidgetAnnotationElement(parameters) {
+ WidgetAnnotationElement.call(this, parameters, parameters.renderInteractiveForms);
+ }
+ Util.inherit(ChoiceWidgetAnnotationElement, WidgetAnnotationElement, {
+ /**
+ * Render the choice widget annotation's HTML element in the empty
+ * container.
+ *
+ * @public
+ * @memberof ChoiceWidgetAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function ChoiceWidgetAnnotationElement_render() {
+ this.container.className = 'choiceWidgetAnnotation';
+ var selectElement = document.createElement('select');
+ selectElement.disabled = this.data.readOnly;
+ if (!this.data.combo) {
+ // List boxes have a size and (optionally) multiple selection.
+ selectElement.size = this.data.options.length;
+ if (this.data.multiSelect) {
+ selectElement.multiple = true;
+ }
+ }
+ // Insert the options into the choice field.
+ for (var i = 0, ii = this.data.options.length; i < ii; i++) {
+ var option = this.data.options[i];
+ var optionElement = document.createElement('option');
+ optionElement.textContent = option.displayValue;
+ optionElement.value = option.exportValue;
+ if (this.data.fieldValue.indexOf(option.displayValue) >= 0) {
+ optionElement.setAttribute('selected', true);
+ }
+ selectElement.appendChild(optionElement);
+ }
+ this.container.appendChild(selectElement);
+ return this.container;
+ }
+ });
+ return ChoiceWidgetAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias PopupAnnotationElement
+ */
+ var PopupAnnotationElement = function PopupAnnotationElementClosure() {
+ function PopupAnnotationElement(parameters) {
+ var isRenderable = !!(parameters.data.title || parameters.data.contents);
+ AnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(PopupAnnotationElement, AnnotationElement, {
+ /**
+ * Render the popup annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof PopupAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function PopupAnnotationElement_render() {
+ this.container.className = 'popupAnnotation';
+ var selector = '[data-annotation-id="' + this.data.parentId + '"]';
+ var parentElement = this.layer.querySelector(selector);
+ if (!parentElement) {
+ return this.container;
+ }
+ var popup = new PopupElement({
+ container: this.container,
+ trigger: parentElement,
+ color: this.data.color,
+ title: this.data.title,
+ contents: this.data.contents
+ });
+ // Position the popup next to the parent annotation's container.
+ // PDF viewers ignore a popup annotation's rectangle.
+ var parentLeft = parseFloat(parentElement.style.left);
+ var parentWidth = parseFloat(parentElement.style.width);
+ CustomStyle.setProp('transformOrigin', this.container, -(parentLeft + parentWidth) + 'px -' + parentElement.style.top);
+ this.container.style.left = parentLeft + parentWidth + 'px';
+ this.container.appendChild(popup.render());
+ return this.container;
+ }
+ });
+ return PopupAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias PopupElement
+ */
+ var PopupElement = function PopupElementClosure() {
+ var BACKGROUND_ENLIGHT = 0.7;
+ function PopupElement(parameters) {
+ this.container = parameters.container;
+ this.trigger = parameters.trigger;
+ this.color = parameters.color;
+ this.title = parameters.title;
+ this.contents = parameters.contents;
+ this.hideWrapper = parameters.hideWrapper || false;
+ this.pinned = false;
+ }
+ PopupElement.prototype = /** @lends PopupElement.prototype */
+ {
+ /**
+ * Render the popup's HTML element.
+ *
+ * @public
+ * @memberof PopupElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function PopupElement_render() {
+ var wrapper = document.createElement('div');
+ wrapper.className = 'popupWrapper';
+ // For Popup annotations we hide the entire section because it contains
+ // only the popup. However, for Text annotations without a separate Popup
+ // annotation, we cannot hide the entire container as the image would
+ // disappear too. In that special case, hiding the wrapper suffices.
+ this.hideElement = this.hideWrapper ? wrapper : this.container;
+ this.hideElement.setAttribute('hidden', true);
+ var popup = document.createElement('div');
+ popup.className = 'popup';
+ var color = this.color;
+ if (color) {
+ // Enlighten the color.
+ var r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
+ var g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
+ var b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
+ popup.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
+ }
+ var contents = this._formatContents(this.contents);
+ var title = document.createElement('h1');
+ title.textContent = this.title;
+ // Attach the event listeners to the trigger element.
+ this.trigger.addEventListener('click', this._toggle.bind(this));
+ this.trigger.addEventListener('mouseover', this._show.bind(this, false));
+ this.trigger.addEventListener('mouseout', this._hide.bind(this, false));
+ popup.addEventListener('click', this._hide.bind(this, true));
+ popup.appendChild(title);
+ popup.appendChild(contents);
+ wrapper.appendChild(popup);
+ return wrapper;
+ },
+ /**
+ * Format the contents of the popup by adding newlines where necessary.
+ *
+ * @private
+ * @param {string} contents
+ * @memberof PopupElement
+ * @returns {HTMLParagraphElement}
+ */
+ _formatContents: function PopupElement_formatContents(contents) {
+ var p = document.createElement('p');
+ var lines = contents.split(/(?:\r\n?|\n)/);
+ for (var i = 0, ii = lines.length; i < ii; ++i) {
+ var line = lines[i];
+ p.appendChild(document.createTextNode(line));
+ if (i < ii - 1) {
+ p.appendChild(document.createElement('br'));
+ }
+ }
+ return p;
+ },
+ /**
+ * Toggle the visibility of the popup.
+ *
+ * @private
+ * @memberof PopupElement
+ */
+ _toggle: function PopupElement_toggle() {
+ if (this.pinned) {
+ this._hide(true);
+ } else {
+ this._show(true);
+ }
+ },
+ /**
+ * Show the popup.
+ *
+ * @private
+ * @param {boolean} pin
+ * @memberof PopupElement
+ */
+ _show: function PopupElement_show(pin) {
+ if (pin) {
+ this.pinned = true;
+ }
+ if (this.hideElement.hasAttribute('hidden')) {
+ this.hideElement.removeAttribute('hidden');
+ this.container.style.zIndex += 1;
+ }
+ },
+ /**
+ * Hide the popup.
+ *
+ * @private
+ * @param {boolean} unpin
+ * @memberof PopupElement
+ */
+ _hide: function PopupElement_hide(unpin) {
+ if (unpin) {
+ this.pinned = false;
+ }
+ if (!this.hideElement.hasAttribute('hidden') && !this.pinned) {
+ this.hideElement.setAttribute('hidden', true);
+ this.container.style.zIndex -= 1;
+ }
+ }
+ };
+ return PopupElement;
+ }();
+ /**
+ * @class
+ * @alias HighlightAnnotationElement
+ */
+ var HighlightAnnotationElement = function HighlightAnnotationElementClosure() {
+ function HighlightAnnotationElement(parameters) {
+ var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+ AnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(HighlightAnnotationElement, AnnotationElement, {
+ /**
+ * Render the highlight annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof HighlightAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function HighlightAnnotationElement_render() {
+ this.container.className = 'highlightAnnotation';
+ if (!this.data.hasPopup) {
+ this._createPopup(this.container, null, this.data);
+ }
+ return this.container;
+ }
+ });
+ return HighlightAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias UnderlineAnnotationElement
+ */
+ var UnderlineAnnotationElement = function UnderlineAnnotationElementClosure() {
+ function UnderlineAnnotationElement(parameters) {
+ var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+ AnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(UnderlineAnnotationElement, AnnotationElement, {
+ /**
+ * Render the underline annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof UnderlineAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function UnderlineAnnotationElement_render() {
+ this.container.className = 'underlineAnnotation';
+ if (!this.data.hasPopup) {
+ this._createPopup(this.container, null, this.data);
+ }
+ return this.container;
+ }
+ });
+ return UnderlineAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias SquigglyAnnotationElement
+ */
+ var SquigglyAnnotationElement = function SquigglyAnnotationElementClosure() {
+ function SquigglyAnnotationElement(parameters) {
+ var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+ AnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(SquigglyAnnotationElement, AnnotationElement, {
+ /**
+ * Render the squiggly annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof SquigglyAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function SquigglyAnnotationElement_render() {
+ this.container.className = 'squigglyAnnotation';
+ if (!this.data.hasPopup) {
+ this._createPopup(this.container, null, this.data);
+ }
+ return this.container;
+ }
+ });
+ return SquigglyAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias StrikeOutAnnotationElement
+ */
+ var StrikeOutAnnotationElement = function StrikeOutAnnotationElementClosure() {
+ function StrikeOutAnnotationElement(parameters) {
+ var isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+ AnnotationElement.call(this, parameters, isRenderable);
+ }
+ Util.inherit(StrikeOutAnnotationElement, AnnotationElement, {
+ /**
+ * Render the strikeout annotation's HTML element in the empty container.
+ *
+ * @public
+ * @memberof StrikeOutAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function StrikeOutAnnotationElement_render() {
+ this.container.className = 'strikeoutAnnotation';
+ if (!this.data.hasPopup) {
+ this._createPopup(this.container, null, this.data);
+ }
+ return this.container;
+ }
+ });
+ return StrikeOutAnnotationElement;
+ }();
+ /**
+ * @class
+ * @alias FileAttachmentAnnotationElement
+ */
+ var FileAttachmentAnnotationElement = function FileAttachmentAnnotationElementClosure() {
+ function FileAttachmentAnnotationElement(parameters) {
+ AnnotationElement.call(this, parameters, true);
+ this.filename = getFilenameFromUrl(parameters.data.file.filename);
+ this.content = parameters.data.file.content;
+ }
+ Util.inherit(FileAttachmentAnnotationElement, AnnotationElement, {
+ /**
+ * Render the file attachment annotation's HTML element in the empty
+ * container.
+ *
+ * @public
+ * @memberof FileAttachmentAnnotationElement
+ * @returns {HTMLSectionElement}
+ */
+ render: function FileAttachmentAnnotationElement_render() {
+ this.container.className = 'fileAttachmentAnnotation';
+ var trigger = document.createElement('div');
+ trigger.style.height = this.container.style.height;
+ trigger.style.width = this.container.style.width;
+ trigger.addEventListener('dblclick', this._download.bind(this));
+ if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
+ this._createPopup(this.container, trigger, this.data);
+ }
+ this.container.appendChild(trigger);
+ return this.container;
+ },
+ /**
+ * Download the file attachment associated with this annotation.
+ *
+ * @private
+ * @memberof FileAttachmentAnnotationElement
+ */
+ _download: function FileAttachmentAnnotationElement_download() {
+ if (!this.downloadManager) {
+ warn('Download cannot be started due to unavailable download manager');
+ return;
+ }
+ this.downloadManager.downloadData(this.content, this.filename, '');
+ }
+ });
+ return FileAttachmentAnnotationElement;
+ }();
+ /**
+ * @typedef {Object} AnnotationLayerParameters
+ * @property {PageViewport} viewport
+ * @property {HTMLDivElement} div
+ * @property {Array} annotations
+ * @property {PDFPage} page
+ * @property {IPDFLinkService} linkService
+ * @property {string} imageResourcesPath
+ * @property {boolean} renderInteractiveForms
+ */
+ /**
+ * @class
+ * @alias AnnotationLayer
+ */
+ var AnnotationLayer = function AnnotationLayerClosure() {
+ return {
+ /**
+ * Render a new annotation layer with all annotation elements.
+ *
+ * @public
+ * @param {AnnotationLayerParameters} parameters
+ * @memberof AnnotationLayer
+ */
+ render: function AnnotationLayer_render(parameters) {
+ var annotationElementFactory = new AnnotationElementFactory();
+ for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
+ var data = parameters.annotations[i];
+ if (!data) {
+ continue;
+ }
+ var properties = {
+ data: data,
+ layer: parameters.div,
+ page: parameters.page,
+ viewport: parameters.viewport,
+ linkService: parameters.linkService,
+ downloadManager: parameters.downloadManager,
+ imageResourcesPath: parameters.imageResourcesPath || getDefaultSetting('imageResourcesPath'),
+ renderInteractiveForms: parameters.renderInteractiveForms || false
+ };
+ var element = annotationElementFactory.create(properties);
+ if (element.isRenderable) {
+ parameters.div.appendChild(element.render());
+ }
+ }
+ },
+ /**
+ * Update the annotation elements on existing annotation layer.
+ *
+ * @public
+ * @param {AnnotationLayerParameters} parameters
+ * @memberof AnnotationLayer
+ */
+ update: function AnnotationLayer_update(parameters) {
+ for (var i = 0, ii = parameters.annotations.length; i < ii; i++) {
+ var data = parameters.annotations[i];
+ var element = parameters.div.querySelector('[data-annotation-id="' + data.id + '"]');
+ if (element) {
+ CustomStyle.setProp('transform', element, 'matrix(' + parameters.viewport.transform.join(',') + ')');
+ }
+ }
+ parameters.div.removeAttribute('hidden');
+ }
+ };
+ }();
+ exports.AnnotationLayer = AnnotationLayer;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayTextLayer = {}, root.pdfjsSharedUtil, root.pdfjsDisplayDOMUtils);
+ }(this, function (exports, sharedUtil, displayDOMUtils) {
+ var Util = sharedUtil.Util;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var CustomStyle = displayDOMUtils.CustomStyle;
+ var getDefaultSetting = displayDOMUtils.getDefaultSetting;
+ /**
+ * Text layer render parameters.
+ *
+ * @typedef {Object} TextLayerRenderParameters
+ * @property {TextContent} textContent - Text content to render (the object is
+ * returned by the page's getTextContent() method).
+ * @property {HTMLElement} container - HTML element that will contain text runs.
+ * @property {PageViewport} viewport - The target viewport to properly
+ * layout the text runs.
+ * @property {Array} textDivs - (optional) HTML elements that are correspond
+ * the text items of the textContent input. This is output and shall be
+ * initially be set to empty array.
+ * @property {number} timeout - (optional) Delay in milliseconds before
+ * rendering of the text runs occurs.
+ * @property {boolean} enhanceTextSelection - (optional) Whether to turn on the
+ * text selection enhancement.
+ */
+ var renderTextLayer = function renderTextLayerClosure() {
+ var MAX_TEXT_DIVS_TO_RENDER = 100000;
+ var NonWhitespaceRegexp = /\S/;
+ function isAllWhitespace(str) {
+ return !NonWhitespaceRegexp.test(str);
+ }
+ // Text layers may contain many thousand div's, and using `styleBuf` avoids
+ // creating many intermediate strings when building their 'style' properties.
+ var styleBuf = [
+ 'left: ',
+ 0,
+ 'px; top: ',
+ 0,
+ 'px; font-size: ',
+ 0,
+ 'px; font-family: ',
+ '',
+ ';'
+ ];
+ function appendText(task, geom, styles) {
+ // Initialize all used properties to keep the caches monomorphic.
+ var textDiv = document.createElement('div');
+ var textDivProperties = {
+ style: null,
+ angle: 0,
+ canvasWidth: 0,
+ isWhitespace: false,
+ originalTransform: null,
+ paddingBottom: 0,
+ paddingLeft: 0,
+ paddingRight: 0,
+ paddingTop: 0,
+ scale: 1
+ };
+ task._textDivs.push(textDiv);
+ if (isAllWhitespace(geom.str)) {
+ textDivProperties.isWhitespace = true;
+ task._textDivProperties.set(textDiv, textDivProperties);
+ return;
+ }
+ var tx = Util.transform(task._viewport.transform, geom.transform);
+ var angle = Math.atan2(tx[1], tx[0]);
+ var style = styles[geom.fontName];
+ if (style.vertical) {
+ angle += Math.PI / 2;
+ }
+ var fontHeight = Math.sqrt(tx[2] * tx[2] + tx[3] * tx[3]);
+ var fontAscent = fontHeight;
+ if (style.ascent) {
+ fontAscent = style.ascent * fontAscent;
+ } else if (style.descent) {
+ fontAscent = (1 + style.descent) * fontAscent;
+ }
+ var left;
+ var top;
+ if (angle === 0) {
+ left = tx[4];
+ top = tx[5] - fontAscent;
+ } else {
+ left = tx[4] + fontAscent * Math.sin(angle);
+ top = tx[5] - fontAscent * Math.cos(angle);
+ }
+ styleBuf[1] = left;
+ styleBuf[3] = top;
+ styleBuf[5] = fontHeight;
+ styleBuf[7] = style.fontFamily;
+ textDivProperties.style = styleBuf.join('');
+ textDiv.setAttribute('style', textDivProperties.style);
+ textDiv.textContent = geom.str;
+ // |fontName| is only used by the Font Inspector. This test will succeed
+ // when e.g. the Font Inspector is off but the Stepper is on, but it's
+ // not worth the effort to do a more accurate test. We only use `dataset`
+ // here to make the font name available for the debugger.
+ if (getDefaultSetting('pdfBug')) {
+ textDiv.dataset.fontName = geom.fontName;
+ }
+ if (angle !== 0) {
+ textDivProperties.angle = angle * (180 / Math.PI);
+ }
+ // We don't bother scaling single-char text divs, because it has very
+ // little effect on text highlighting. This makes scrolling on docs with
+ // lots of such divs a lot faster.
+ if (geom.str.length > 1) {
+ if (style.vertical) {
+ textDivProperties.canvasWidth = geom.height * task._viewport.scale;
+ } else {
+ textDivProperties.canvasWidth = geom.width * task._viewport.scale;
+ }
+ }
+ task._textDivProperties.set(textDiv, textDivProperties);
+ if (task._enhanceTextSelection) {
+ var angleCos = 1, angleSin = 0;
+ if (angle !== 0) {
+ angleCos = Math.cos(angle);
+ angleSin = Math.sin(angle);
+ }
+ var divWidth = (style.vertical ? geom.height : geom.width) * task._viewport.scale;
+ var divHeight = fontHeight;
+ var m, b;
+ if (angle !== 0) {
+ m = [
+ angleCos,
+ angleSin,
+ -angleSin,
+ angleCos,
+ left,
+ top
+ ];
+ b = Util.getAxialAlignedBoundingBox([
+ 0,
+ 0,
+ divWidth,
+ divHeight
+ ], m);
+ } else {
+ b = [
+ left,
+ top,
+ left + divWidth,
+ top + divHeight
+ ];
+ }
+ task._bounds.push({
+ left: b[0],
+ top: b[1],
+ right: b[2],
+ bottom: b[3],
+ div: textDiv,
+ size: [
+ divWidth,
+ divHeight
+ ],
+ m: m
+ });
+ }
+ }
+ function render(task) {
+ if (task._canceled) {
+ return;
+ }
+ var textLayerFrag = task._container;
+ var textDivs = task._textDivs;
+ var capability = task._capability;
+ var textDivsLength = textDivs.length;
+ // No point in rendering many divs as it would make the browser
+ // unusable even after the divs are rendered.
+ if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
+ task._renderingDone = true;
+ capability.resolve();
+ return;
+ }
+ var canvas = document.createElement('canvas');
+ canvas.mozOpaque = true;
+ var ctx = canvas.getContext('2d', { alpha: false });
+ var lastFontSize;
+ var lastFontFamily;
+ for (var i = 0; i < textDivsLength; i++) {
+ var textDiv = textDivs[i];
+ var textDivProperties = task._textDivProperties.get(textDiv);
+ if (textDivProperties.isWhitespace) {
+ continue;
+ }
+ var fontSize = textDiv.style.fontSize;
+ var fontFamily = textDiv.style.fontFamily;
+ // Only build font string and set to context if different from last.
+ if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
+ ctx.font = fontSize + ' ' + fontFamily;
+ lastFontSize = fontSize;
+ lastFontFamily = fontFamily;
+ }
+ var width = ctx.measureText(textDiv.textContent).width;
+ textLayerFrag.appendChild(textDiv);
+ var transform = '';
+ if (textDivProperties.canvasWidth !== 0 && width > 0) {
+ textDivProperties.scale = textDivProperties.canvasWidth / width;
+ transform = 'scaleX(' + textDivProperties.scale + ')';
+ }
+ if (textDivProperties.angle !== 0) {
+ transform = 'rotate(' + textDivProperties.angle + 'deg) ' + transform;
+ }
+ if (transform !== '') {
+ textDivProperties.originalTransform = transform;
+ CustomStyle.setProp('transform', textDiv, transform);
+ }
+ task._textDivProperties.set(textDiv, textDivProperties);
+ }
+ task._renderingDone = true;
+ capability.resolve();
+ }
+ function expand(task) {
+ var bounds = task._bounds;
+ var viewport = task._viewport;
+ var expanded = expandBounds(viewport.width, viewport.height, bounds);
+ for (var i = 0; i < expanded.length; i++) {
+ var div = bounds[i].div;
+ var divProperties = task._textDivProperties.get(div);
+ if (divProperties.angle === 0) {
+ divProperties.paddingLeft = bounds[i].left - expanded[i].left;
+ divProperties.paddingTop = bounds[i].top - expanded[i].top;
+ divProperties.paddingRight = expanded[i].right - bounds[i].right;
+ divProperties.paddingBottom = expanded[i].bottom - bounds[i].bottom;
+ task._textDivProperties.set(div, divProperties);
+ continue;
+ }
+ // Box is rotated -- trying to find padding so rotated div will not
+ // exceed its expanded bounds.
+ var e = expanded[i], b = bounds[i];
+ var m = b.m, c = m[0], s = m[1];
+ // Finding intersections with expanded box.
+ var points = [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0,
+ b.size[1]
+ ],
+ [
+ b.size[0],
+ 0
+ ],
+ b.size
+ ];
+ var ts = new Float64Array(64);
+ points.forEach(function (p, i) {
+ var t = Util.applyTransform(p, m);
+ ts[i + 0] = c && (e.left - t[0]) / c;
+ ts[i + 4] = s && (e.top - t[1]) / s;
+ ts[i + 8] = c && (e.right - t[0]) / c;
+ ts[i + 12] = s && (e.bottom - t[1]) / s;
+ ts[i + 16] = s && (e.left - t[0]) / -s;
+ ts[i + 20] = c && (e.top - t[1]) / c;
+ ts[i + 24] = s && (e.right - t[0]) / -s;
+ ts[i + 28] = c && (e.bottom - t[1]) / c;
+ ts[i + 32] = c && (e.left - t[0]) / -c;
+ ts[i + 36] = s && (e.top - t[1]) / -s;
+ ts[i + 40] = c && (e.right - t[0]) / -c;
+ ts[i + 44] = s && (e.bottom - t[1]) / -s;
+ ts[i + 48] = s && (e.left - t[0]) / s;
+ ts[i + 52] = c && (e.top - t[1]) / -c;
+ ts[i + 56] = s && (e.right - t[0]) / s;
+ ts[i + 60] = c && (e.bottom - t[1]) / -c;
+ });
+ var findPositiveMin = function (ts, offset, count) {
+ var result = 0;
+ for (var i = 0; i < count; i++) {
+ var t = ts[offset++];
+ if (t > 0) {
+ result = result ? Math.min(t, result) : t;
+ }
+ }
+ return result;
+ };
+ // Not based on math, but to simplify calculations, using cos and sin
+ // absolute values to not exceed the box (it can but insignificantly).
+ var boxScale = 1 + Math.min(Math.abs(c), Math.abs(s));
+ divProperties.paddingLeft = findPositiveMin(ts, 32, 16) / boxScale;
+ divProperties.paddingTop = findPositiveMin(ts, 48, 16) / boxScale;
+ divProperties.paddingRight = findPositiveMin(ts, 0, 16) / boxScale;
+ divProperties.paddingBottom = findPositiveMin(ts, 16, 16) / boxScale;
+ task._textDivProperties.set(div, divProperties);
+ }
+ }
+ function expandBounds(width, height, boxes) {
+ var bounds = boxes.map(function (box, i) {
+ return {
+ x1: box.left,
+ y1: box.top,
+ x2: box.right,
+ y2: box.bottom,
+ index: i,
+ x1New: undefined,
+ x2New: undefined
+ };
+ });
+ expandBoundsLTR(width, bounds);
+ var expanded = new Array(boxes.length);
+ bounds.forEach(function (b) {
+ var i = b.index;
+ expanded[i] = {
+ left: b.x1New,
+ top: 0,
+ right: b.x2New,
+ bottom: 0
+ };
+ });
+ // Rotating on 90 degrees and extending extended boxes. Reusing the bounds
+ // array and objects.
+ boxes.map(function (box, i) {
+ var e = expanded[i], b = bounds[i];
+ b.x1 = box.top;
+ b.y1 = width - e.right;
+ b.x2 = box.bottom;
+ b.y2 = width - e.left;
+ b.index = i;
+ b.x1New = undefined;
+ b.x2New = undefined;
+ });
+ expandBoundsLTR(height, bounds);
+ bounds.forEach(function (b) {
+ var i = b.index;
+ expanded[i].top = b.x1New;
+ expanded[i].bottom = b.x2New;
+ });
+ return expanded;
+ }
+ function expandBoundsLTR(width, bounds) {
+ // Sorting by x1 coordinate and walk by the bounds in the same order.
+ bounds.sort(function (a, b) {
+ return a.x1 - b.x1 || a.index - b.index;
+ });
+ // First we see on the horizon is a fake boundary.
+ var fakeBoundary = {
+ x1: -Infinity,
+ y1: -Infinity,
+ x2: 0,
+ y2: Infinity,
+ index: -1,
+ x1New: 0,
+ x2New: 0
+ };
+ var horizon = [{
+ start: -Infinity,
+ end: Infinity,
+ boundary: fakeBoundary
+ }];
+ bounds.forEach(function (boundary) {
+ // Searching for the affected part of horizon.
+ // TODO red-black tree or simple binary search
+ var i = 0;
+ while (i < horizon.length && horizon[i].end <= boundary.y1) {
+ i++;
+ }
+ var j = horizon.length - 1;
+ while (j >= 0 && horizon[j].start >= boundary.y2) {
+ j--;
+ }
+ var horizonPart, affectedBoundary;
+ var q, k, maxXNew = -Infinity;
+ for (q = i; q <= j; q++) {
+ horizonPart = horizon[q];
+ affectedBoundary = horizonPart.boundary;
+ var xNew;
+ if (affectedBoundary.x2 > boundary.x1) {
+ // In the middle of the previous element, new x shall be at the
+ // boundary start. Extending if further if the affected bondary
+ // placed on top of the current one.
+ xNew = affectedBoundary.index > boundary.index ? affectedBoundary.x1New : boundary.x1;
+ } else if (affectedBoundary.x2New === undefined) {
+ // We have some space in between, new x in middle will be a fair
+ // choice.
+ xNew = (affectedBoundary.x2 + boundary.x1) / 2;
+ } else {
+ // Affected boundary has x2new set, using it as new x.
+ xNew = affectedBoundary.x2New;
+ }
+ if (xNew > maxXNew) {
+ maxXNew = xNew;
+ }
+ }
+ // Set new x1 for current boundary.
+ boundary.x1New = maxXNew;
+ // Adjusts new x2 for the affected boundaries.
+ for (q = i; q <= j; q++) {
+ horizonPart = horizon[q];
+ affectedBoundary = horizonPart.boundary;
+ if (affectedBoundary.x2New === undefined) {
+ // Was not set yet, choosing new x if possible.
+ if (affectedBoundary.x2 > boundary.x1) {
+ // Current and affected boundaries intersect. If affected boundary
+ // is placed on top of the current, shrinking the affected.
+ if (affectedBoundary.index > boundary.index) {
+ affectedBoundary.x2New = affectedBoundary.x2;
+ }
+ } else {
+ affectedBoundary.x2New = maxXNew;
+ }
+ } else if (affectedBoundary.x2New > maxXNew) {
+ // Affected boundary is touching new x, pushing it back.
+ affectedBoundary.x2New = Math.max(maxXNew, affectedBoundary.x2);
+ }
+ }
+ // Fixing the horizon.
+ var changedHorizon = [], lastBoundary = null;
+ for (q = i; q <= j; q++) {
+ horizonPart = horizon[q];
+ affectedBoundary = horizonPart.boundary;
+ // Checking which boundary will be visible.
+ var useBoundary = affectedBoundary.x2 > boundary.x2 ? affectedBoundary : boundary;
+ if (lastBoundary === useBoundary) {
+ // Merging with previous.
+ changedHorizon[changedHorizon.length - 1].end = horizonPart.end;
+ } else {
+ changedHorizon.push({
+ start: horizonPart.start,
+ end: horizonPart.end,
+ boundary: useBoundary
+ });
+ lastBoundary = useBoundary;
+ }
+ }
+ if (horizon[i].start < boundary.y1) {
+ changedHorizon[0].start = boundary.y1;
+ changedHorizon.unshift({
+ start: horizon[i].start,
+ end: boundary.y1,
+ boundary: horizon[i].boundary
+ });
+ }
+ if (boundary.y2 < horizon[j].end) {
+ changedHorizon[changedHorizon.length - 1].end = boundary.y2;
+ changedHorizon.push({
+ start: boundary.y2,
+ end: horizon[j].end,
+ boundary: horizon[j].boundary
+ });
+ }
+ // Set x2 new of boundary that is no longer visible (see overlapping case
+ // above).
+ // TODO more efficient, e.g. via reference counting.
+ for (q = i; q <= j; q++) {
+ horizonPart = horizon[q];
+ affectedBoundary = horizonPart.boundary;
+ if (affectedBoundary.x2New !== undefined) {
+ continue;
+ }
+ var used = false;
+ for (k = i - 1; !used && k >= 0 && horizon[k].start >= affectedBoundary.y1; k--) {
+ used = horizon[k].boundary === affectedBoundary;
+ }
+ for (k = j + 1; !used && k < horizon.length && horizon[k].end <= affectedBoundary.y2; k++) {
+ used = horizon[k].boundary === affectedBoundary;
+ }
+ for (k = 0; !used && k < changedHorizon.length; k++) {
+ used = changedHorizon[k].boundary === affectedBoundary;
+ }
+ if (!used) {
+ affectedBoundary.x2New = maxXNew;
+ }
+ }
+ Array.prototype.splice.apply(horizon, [
+ i,
+ j - i + 1
+ ].concat(changedHorizon));
+ });
+ // Set new x2 for all unset boundaries.
+ horizon.forEach(function (horizonPart) {
+ var affectedBoundary = horizonPart.boundary;
+ if (affectedBoundary.x2New === undefined) {
+ affectedBoundary.x2New = Math.max(width, affectedBoundary.x2);
+ }
+ });
+ }
+ /**
+ * Text layer rendering task.
+ *
+ * @param {TextContent} textContent
+ * @param {HTMLElement} container
+ * @param {PageViewport} viewport
+ * @param {Array} textDivs
+ * @param {boolean} enhanceTextSelection
+ * @private
+ */
+ function TextLayerRenderTask(textContent, container, viewport, textDivs, enhanceTextSelection) {
+ this._textContent = textContent;
+ this._container = container;
+ this._viewport = viewport;
+ this._textDivs = textDivs || [];
+ this._textDivProperties = new WeakMap();
+ this._renderingDone = false;
+ this._canceled = false;
+ this._capability = createPromiseCapability();
+ this._renderTimer = null;
+ this._bounds = [];
+ this._enhanceTextSelection = !!enhanceTextSelection;
+ }
+ TextLayerRenderTask.prototype = {
+ get promise() {
+ return this._capability.promise;
+ },
+ cancel: function TextLayer_cancel() {
+ this._canceled = true;
+ if (this._renderTimer !== null) {
+ clearTimeout(this._renderTimer);
+ this._renderTimer = null;
+ }
+ this._capability.reject('canceled');
+ },
+ _render: function TextLayer_render(timeout) {
+ var textItems = this._textContent.items;
+ var textStyles = this._textContent.styles;
+ for (var i = 0, len = textItems.length; i < len; i++) {
+ appendText(this, textItems[i], textStyles);
+ }
+ if (!timeout) {
+ // Render right away
+ render(this);
+ } else {
+ // Schedule
+ var self = this;
+ this._renderTimer = setTimeout(function () {
+ render(self);
+ self._renderTimer = null;
+ }, timeout);
+ }
+ },
+ expandTextDivs: function TextLayer_expandTextDivs(expandDivs) {
+ if (!this._enhanceTextSelection || !this._renderingDone) {
+ return;
+ }
+ if (this._bounds !== null) {
+ expand(this);
+ this._bounds = null;
+ }
+ for (var i = 0, ii = this._textDivs.length; i < ii; i++) {
+ var div = this._textDivs[i];
+ var divProperties = this._textDivProperties.get(div);
+ if (divProperties.isWhitespace) {
+ continue;
+ }
+ if (expandDivs) {
+ var transform = '', padding = '';
+ if (divProperties.scale !== 1) {
+ transform = 'scaleX(' + divProperties.scale + ')';
+ }
+ if (divProperties.angle !== 0) {
+ transform = 'rotate(' + divProperties.angle + 'deg) ' + transform;
+ }
+ if (divProperties.paddingLeft !== 0) {
+ padding += ' padding-left: ' + divProperties.paddingLeft / divProperties.scale + 'px;';
+ transform += ' translateX(' + -divProperties.paddingLeft / divProperties.scale + 'px)';
+ }
+ if (divProperties.paddingTop !== 0) {
+ padding += ' padding-top: ' + divProperties.paddingTop + 'px;';
+ transform += ' translateY(' + -divProperties.paddingTop + 'px)';
+ }
+ if (divProperties.paddingRight !== 0) {
+ padding += ' padding-right: ' + divProperties.paddingRight / divProperties.scale + 'px;';
+ }
+ if (divProperties.paddingBottom !== 0) {
+ padding += ' padding-bottom: ' + divProperties.paddingBottom + 'px;';
+ }
+ if (padding !== '') {
+ div.setAttribute('style', divProperties.style + padding);
+ }
+ if (transform !== '') {
+ CustomStyle.setProp('transform', div, transform);
+ }
+ } else {
+ div.style.padding = 0;
+ CustomStyle.setProp('transform', div, divProperties.originalTransform || '');
+ }
+ }
+ }
+ };
+ /**
+ * Starts rendering of the text layer.
+ *
+ * @param {TextLayerRenderParameters} renderParameters
+ * @returns {TextLayerRenderTask}
+ */
+ function renderTextLayer(renderParameters) {
+ var task = new TextLayerRenderTask(renderParameters.textContent, renderParameters.container, renderParameters.viewport, renderParameters.textDivs, renderParameters.enhanceTextSelection);
+ task._render(renderParameters.timeout);
+ return task;
+ }
+ return renderTextLayer;
+ }();
+ exports.renderTextLayer = renderTextLayer;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayWebGL = {}, root.pdfjsSharedUtil, root.pdfjsDisplayDOMUtils);
+ }(this, function (exports, sharedUtil, displayDOMUtils) {
+ var shadow = sharedUtil.shadow;
+ var getDefaultSetting = displayDOMUtils.getDefaultSetting;
+ var WebGLUtils = function WebGLUtilsClosure() {
+ function loadShader(gl, code, shaderType) {
+ var shader = gl.createShader(shaderType);
+ gl.shaderSource(shader, code);
+ gl.compileShader(shader);
+ var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+ if (!compiled) {
+ var errorMsg = gl.getShaderInfoLog(shader);
+ throw new Error('Error during shader compilation: ' + errorMsg);
+ }
+ return shader;
+ }
+ function createVertexShader(gl, code) {
+ return loadShader(gl, code, gl.VERTEX_SHADER);
+ }
+ function createFragmentShader(gl, code) {
+ return loadShader(gl, code, gl.FRAGMENT_SHADER);
+ }
+ function createProgram(gl, shaders) {
+ var program = gl.createProgram();
+ for (var i = 0, ii = shaders.length; i < ii; ++i) {
+ gl.attachShader(program, shaders[i]);
+ }
+ gl.linkProgram(program);
+ var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+ if (!linked) {
+ var errorMsg = gl.getProgramInfoLog(program);
+ throw new Error('Error during program linking: ' + errorMsg);
+ }
+ return program;
+ }
+ function createTexture(gl, image, textureId) {
+ gl.activeTexture(textureId);
+ var texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ // Set the parameters so we can render any size image.
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ // Upload the image into the texture.
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
+ return texture;
+ }
+ var currentGL, currentCanvas;
+ function generateGL() {
+ if (currentGL) {
+ return;
+ }
+ currentCanvas = document.createElement('canvas');
+ currentGL = currentCanvas.getContext('webgl', { premultipliedalpha: false });
+ }
+ var smaskVertexShaderCode = '\
+ attribute vec2 a_position; \
+ attribute vec2 a_texCoord; \
+ \
+ uniform vec2 u_resolution; \
+ \
+ varying vec2 v_texCoord; \
+ \
+ void main() { \
+ vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \
+ gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
+ \
+ v_texCoord = a_texCoord; \
+ } ';
+ var smaskFragmentShaderCode = '\
+ precision mediump float; \
+ \
+ uniform vec4 u_backdrop; \
+ uniform int u_subtype; \
+ uniform sampler2D u_image; \
+ uniform sampler2D u_mask; \
+ \
+ varying vec2 v_texCoord; \
+ \
+ void main() { \
+ vec4 imageColor = texture2D(u_image, v_texCoord); \
+ vec4 maskColor = texture2D(u_mask, v_texCoord); \
+ if (u_backdrop.a > 0.0) { \
+ maskColor.rgb = maskColor.rgb * maskColor.a + \
+ u_backdrop.rgb * (1.0 - maskColor.a); \
+ } \
+ float lum; \
+ if (u_subtype == 0) { \
+ lum = maskColor.a; \
+ } else { \
+ lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \
+ maskColor.b * 0.11; \
+ } \
+ imageColor.a *= lum; \
+ imageColor.rgb *= imageColor.a; \
+ gl_FragColor = imageColor; \
+ } ';
+ var smaskCache = null;
+ function initSmaskGL() {
+ var canvas, gl;
+ generateGL();
+ canvas = currentCanvas;
+ currentCanvas = null;
+ gl = currentGL;
+ currentGL = null;
+ // setup a GLSL program
+ var vertexShader = createVertexShader(gl, smaskVertexShaderCode);
+ var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode);
+ var program = createProgram(gl, [
+ vertexShader,
+ fragmentShader
+ ]);
+ gl.useProgram(program);
+ var cache = {};
+ cache.gl = gl;
+ cache.canvas = canvas;
+ cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
+ cache.positionLocation = gl.getAttribLocation(program, 'a_position');
+ cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop');
+ cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype');
+ var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
+ var texLayerLocation = gl.getUniformLocation(program, 'u_image');
+ var texMaskLocation = gl.getUniformLocation(program, 'u_mask');
+ // provide texture coordinates for the rectangle.
+ var texCoordBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 1.0,
+ 1.0,
+ 0.0,
+ 1.0,
+ 1.0
+ ]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(texCoordLocation);
+ gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
+ gl.uniform1i(texLayerLocation, 0);
+ gl.uniform1i(texMaskLocation, 1);
+ smaskCache = cache;
+ }
+ function composeSMask(layer, mask, properties) {
+ var width = layer.width, height = layer.height;
+ if (!smaskCache) {
+ initSmaskGL();
+ }
+ var cache = smaskCache, canvas = cache.canvas, gl = cache.gl;
+ canvas.width = width;
+ canvas.height = height;
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
+ gl.uniform2f(cache.resolutionLocation, width, height);
+ if (properties.backdrop) {
+ gl.uniform4f(cache.resolutionLocation, properties.backdrop[0], properties.backdrop[1], properties.backdrop[2], 1);
+ } else {
+ gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0);
+ }
+ gl.uniform1i(cache.subtypeLocation, properties.subtype === 'Luminosity' ? 1 : 0);
+ // Create a textures
+ var texture = createTexture(gl, layer, gl.TEXTURE0);
+ var maskTexture = createTexture(gl, mask, gl.TEXTURE1);
+ // Create a buffer and put a single clipspace rectangle in
+ // it (2 triangles)
+ var buffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 0,
+ 0,
+ width,
+ 0,
+ 0,
+ height,
+ 0,
+ height,
+ width,
+ 0,
+ width,
+ height
+ ]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(cache.positionLocation);
+ gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
+ // draw
+ gl.clearColor(0, 0, 0, 0);
+ gl.enable(gl.BLEND);
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ gl.flush();
+ gl.deleteTexture(texture);
+ gl.deleteTexture(maskTexture);
+ gl.deleteBuffer(buffer);
+ return canvas;
+ }
+ var figuresVertexShaderCode = '\
+ attribute vec2 a_position; \
+ attribute vec3 a_color; \
+ \
+ uniform vec2 u_resolution; \
+ uniform vec2 u_scale; \
+ uniform vec2 u_offset; \
+ \
+ varying vec4 v_color; \
+ \
+ void main() { \
+ vec2 position = (a_position + u_offset) * u_scale; \
+ vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \
+ gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \
+ \
+ v_color = vec4(a_color / 255.0, 1.0); \
+ } ';
+ var figuresFragmentShaderCode = '\
+ precision mediump float; \
+ \
+ varying vec4 v_color; \
+ \
+ void main() { \
+ gl_FragColor = v_color; \
+ } ';
+ var figuresCache = null;
+ function initFiguresGL() {
+ var canvas, gl;
+ generateGL();
+ canvas = currentCanvas;
+ currentCanvas = null;
+ gl = currentGL;
+ currentGL = null;
+ // setup a GLSL program
+ var vertexShader = createVertexShader(gl, figuresVertexShaderCode);
+ var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode);
+ var program = createProgram(gl, [
+ vertexShader,
+ fragmentShader
+ ]);
+ gl.useProgram(program);
+ var cache = {};
+ cache.gl = gl;
+ cache.canvas = canvas;
+ cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
+ cache.scaleLocation = gl.getUniformLocation(program, 'u_scale');
+ cache.offsetLocation = gl.getUniformLocation(program, 'u_offset');
+ cache.positionLocation = gl.getAttribLocation(program, 'a_position');
+ cache.colorLocation = gl.getAttribLocation(program, 'a_color');
+ figuresCache = cache;
+ }
+ function drawFigures(width, height, backgroundColor, figures, context) {
+ if (!figuresCache) {
+ initFiguresGL();
+ }
+ var cache = figuresCache, canvas = cache.canvas, gl = cache.gl;
+ canvas.width = width;
+ canvas.height = height;
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
+ gl.uniform2f(cache.resolutionLocation, width, height);
+ // count triangle points
+ var count = 0;
+ var i, ii, rows;
+ for (i = 0, ii = figures.length; i < ii; i++) {
+ switch (figures[i].type) {
+ case 'lattice':
+ rows = figures[i].coords.length / figures[i].verticesPerRow | 0;
+ count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6;
+ break;
+ case 'triangles':
+ count += figures[i].coords.length;
+ break;
+ }
+ }
+ // transfer data
+ var coords = new Float32Array(count * 2);
+ var colors = new Uint8Array(count * 3);
+ var coordsMap = context.coords, colorsMap = context.colors;
+ var pIndex = 0, cIndex = 0;
+ for (i = 0, ii = figures.length; i < ii; i++) {
+ var figure = figures[i], ps = figure.coords, cs = figure.colors;
+ switch (figure.type) {
+ case 'lattice':
+ var cols = figure.verticesPerRow;
+ rows = ps.length / cols | 0;
+ for (var row = 1; row < rows; row++) {
+ var offset = row * cols + 1;
+ for (var col = 1; col < cols; col++, offset++) {
+ coords[pIndex] = coordsMap[ps[offset - cols - 1]];
+ coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1];
+ coords[pIndex + 2] = coordsMap[ps[offset - cols]];
+ coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1];
+ coords[pIndex + 4] = coordsMap[ps[offset - 1]];
+ coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1];
+ colors[cIndex] = colorsMap[cs[offset - cols - 1]];
+ colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1];
+ colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2];
+ colors[cIndex + 3] = colorsMap[cs[offset - cols]];
+ colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1];
+ colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2];
+ colors[cIndex + 6] = colorsMap[cs[offset - 1]];
+ colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1];
+ colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2];
+ coords[pIndex + 6] = coords[pIndex + 2];
+ coords[pIndex + 7] = coords[pIndex + 3];
+ coords[pIndex + 8] = coords[pIndex + 4];
+ coords[pIndex + 9] = coords[pIndex + 5];
+ coords[pIndex + 10] = coordsMap[ps[offset]];
+ coords[pIndex + 11] = coordsMap[ps[offset] + 1];
+ colors[cIndex + 9] = colors[cIndex + 3];
+ colors[cIndex + 10] = colors[cIndex + 4];
+ colors[cIndex + 11] = colors[cIndex + 5];
+ colors[cIndex + 12] = colors[cIndex + 6];
+ colors[cIndex + 13] = colors[cIndex + 7];
+ colors[cIndex + 14] = colors[cIndex + 8];
+ colors[cIndex + 15] = colorsMap[cs[offset]];
+ colors[cIndex + 16] = colorsMap[cs[offset] + 1];
+ colors[cIndex + 17] = colorsMap[cs[offset] + 2];
+ pIndex += 12;
+ cIndex += 18;
+ }
+ }
+ break;
+ case 'triangles':
+ for (var j = 0, jj = ps.length; j < jj; j++) {
+ coords[pIndex] = coordsMap[ps[j]];
+ coords[pIndex + 1] = coordsMap[ps[j] + 1];
+ colors[cIndex] = colorsMap[cs[j]];
+ colors[cIndex + 1] = colorsMap[cs[j] + 1];
+ colors[cIndex + 2] = colorsMap[cs[j] + 2];
+ pIndex += 2;
+ cIndex += 3;
+ }
+ break;
+ }
+ }
+ // draw
+ if (backgroundColor) {
+ gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255, backgroundColor[2] / 255, 1.0);
+ } else {
+ gl.clearColor(0, 0, 0, 0);
+ }
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ var coordsBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(cache.positionLocation);
+ gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0);
+ var colorsBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(cache.colorLocation);
+ gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false, 0, 0);
+ gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY);
+ gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY);
+ gl.drawArrays(gl.TRIANGLES, 0, count);
+ gl.flush();
+ gl.deleteBuffer(coordsBuffer);
+ gl.deleteBuffer(colorsBuffer);
+ return canvas;
+ }
+ function cleanup() {
+ if (smaskCache && smaskCache.canvas) {
+ smaskCache.canvas.width = 0;
+ smaskCache.canvas.height = 0;
+ }
+ if (figuresCache && figuresCache.canvas) {
+ figuresCache.canvas.width = 0;
+ figuresCache.canvas.height = 0;
+ }
+ smaskCache = null;
+ figuresCache = null;
+ }
+ return {
+ get isEnabled() {
+ if (getDefaultSetting('disableWebGL')) {
+ return false;
+ }
+ var enabled = false;
+ try {
+ generateGL();
+ enabled = !!currentGL;
+ } catch (e) {
+ }
+ return shadow(this, 'isEnabled', enabled);
+ },
+ composeSMask: composeSMask,
+ drawFigures: drawFigures,
+ clear: cleanup
+ };
+ }();
+ exports.WebGLUtils = WebGLUtils;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayPatternHelper = {}, root.pdfjsSharedUtil, root.pdfjsDisplayWebGL);
+ }(this, function (exports, sharedUtil, displayWebGL) {
+ var Util = sharedUtil.Util;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var error = sharedUtil.error;
+ var WebGLUtils = displayWebGL.WebGLUtils;
+ var ShadingIRs = {};
+ ShadingIRs.RadialAxial = {
+ fromIR: function RadialAxial_fromIR(raw) {
+ var type = raw[1];
+ var colorStops = raw[2];
+ var p0 = raw[3];
+ var p1 = raw[4];
+ var r0 = raw[5];
+ var r1 = raw[6];
+ return {
+ type: 'Pattern',
+ getPattern: function RadialAxial_getPattern(ctx) {
+ var grad;
+ if (type === 'axial') {
+ grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
+ } else if (type === 'radial') {
+ grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
+ }
+ for (var i = 0, ii = colorStops.length; i < ii; ++i) {
+ var c = colorStops[i];
+ grad.addColorStop(c[0], c[1]);
+ }
+ return grad;
+ }
+ };
+ }
+ };
+ var createMeshCanvas = function createMeshCanvasClosure() {
+ function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
+ // Very basic Gouraud-shaded triangle rasterization algorithm.
+ var coords = context.coords, colors = context.colors;
+ var bytes = data.data, rowSize = data.width * 4;
+ var tmp;
+ if (coords[p1 + 1] > coords[p2 + 1]) {
+ tmp = p1;
+ p1 = p2;
+ p2 = tmp;
+ tmp = c1;
+ c1 = c2;
+ c2 = tmp;
+ }
+ if (coords[p2 + 1] > coords[p3 + 1]) {
+ tmp = p2;
+ p2 = p3;
+ p3 = tmp;
+ tmp = c2;
+ c2 = c3;
+ c3 = tmp;
+ }
+ if (coords[p1 + 1] > coords[p2 + 1]) {
+ tmp = p1;
+ p1 = p2;
+ p2 = tmp;
+ tmp = c1;
+ c1 = c2;
+ c2 = tmp;
+ }
+ var x1 = (coords[p1] + context.offsetX) * context.scaleX;
+ var y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY;
+ var x2 = (coords[p2] + context.offsetX) * context.scaleX;
+ var y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY;
+ var x3 = (coords[p3] + context.offsetX) * context.scaleX;
+ var y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY;
+ if (y1 >= y3) {
+ return;
+ }
+ var c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2];
+ var c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2];
+ var c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2];
+ var minY = Math.round(y1), maxY = Math.round(y3);
+ var xa, car, cag, cab;
+ var xb, cbr, cbg, cbb;
+ var k;
+ for (var y = minY; y <= maxY; y++) {
+ if (y < y2) {
+ k = y < y1 ? 0 : y1 === y2 ? 1 : (y1 - y) / (y1 - y2);
+ xa = x1 - (x1 - x2) * k;
+ car = c1r - (c1r - c2r) * k;
+ cag = c1g - (c1g - c2g) * k;
+ cab = c1b - (c1b - c2b) * k;
+ } else {
+ k = y > y3 ? 1 : y2 === y3 ? 0 : (y2 - y) / (y2 - y3);
+ xa = x2 - (x2 - x3) * k;
+ car = c2r - (c2r - c3r) * k;
+ cag = c2g - (c2g - c3g) * k;
+ cab = c2b - (c2b - c3b) * k;
+ }
+ k = y < y1 ? 0 : y > y3 ? 1 : (y1 - y) / (y1 - y3);
+ xb = x1 - (x1 - x3) * k;
+ cbr = c1r - (c1r - c3r) * k;
+ cbg = c1g - (c1g - c3g) * k;
+ cbb = c1b - (c1b - c3b) * k;
+ var x1_ = Math.round(Math.min(xa, xb));
+ var x2_ = Math.round(Math.max(xa, xb));
+ var j = rowSize * y + x1_ * 4;
+ for (var x = x1_; x <= x2_; x++) {
+ k = (xa - x) / (xa - xb);
+ k = k < 0 ? 0 : k > 1 ? 1 : k;
+ bytes[j++] = car - (car - cbr) * k | 0;
+ bytes[j++] = cag - (cag - cbg) * k | 0;
+ bytes[j++] = cab - (cab - cbb) * k | 0;
+ bytes[j++] = 255;
+ }
+ }
+ }
+ function drawFigure(data, figure, context) {
+ var ps = figure.coords;
+ var cs = figure.colors;
+ var i, ii;
+ switch (figure.type) {
+ case 'lattice':
+ var verticesPerRow = figure.verticesPerRow;
+ var rows = Math.floor(ps.length / verticesPerRow) - 1;
+ var cols = verticesPerRow - 1;
+ for (i = 0; i < rows; i++) {
+ var q = i * verticesPerRow;
+ for (var j = 0; j < cols; j++, q++) {
+ drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]);
+ drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]);
+ }
+ }
+ break;
+ case 'triangles':
+ for (i = 0, ii = ps.length; i < ii; i += 3) {
+ drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]);
+ }
+ break;
+ default:
+ error('illigal figure');
+ break;
+ }
+ }
+ function createMeshCanvas(bounds, combinesScale, coords, colors, figures, backgroundColor, cachedCanvases) {
+ // we will increase scale on some weird factor to let antialiasing take
+ // care of "rough" edges
+ var EXPECTED_SCALE = 1.1;
+ // MAX_PATTERN_SIZE is used to avoid OOM situation.
+ var MAX_PATTERN_SIZE = 3000;
+ // 10in @ 300dpi shall be enough
+ // We need to keep transparent border around our pattern for fill():
+ // createPattern with 'no-repeat' will bleed edges across entire area.
+ var BORDER_SIZE = 2;
+ var offsetX = Math.floor(bounds[0]);
+ var offsetY = Math.floor(bounds[1]);
+ var boundsWidth = Math.ceil(bounds[2]) - offsetX;
+ var boundsHeight = Math.ceil(bounds[3]) - offsetY;
+ var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
+ var height = Math.min(Math.ceil(Math.abs(boundsHeight * combinesScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
+ var scaleX = boundsWidth / width;
+ var scaleY = boundsHeight / height;
+ var context = {
+ coords: coords,
+ colors: colors,
+ offsetX: -offsetX,
+ offsetY: -offsetY,
+ scaleX: 1 / scaleX,
+ scaleY: 1 / scaleY
+ };
+ var paddedWidth = width + BORDER_SIZE * 2;
+ var paddedHeight = height + BORDER_SIZE * 2;
+ var canvas, tmpCanvas, i, ii;
+ if (WebGLUtils.isEnabled) {
+ canvas = WebGLUtils.drawFigures(width, height, backgroundColor, figures, context);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=972126
+ tmpCanvas = cachedCanvases.getCanvas('mesh', paddedWidth, paddedHeight, false);
+ tmpCanvas.context.drawImage(canvas, BORDER_SIZE, BORDER_SIZE);
+ canvas = tmpCanvas.canvas;
+ } else {
+ tmpCanvas = cachedCanvases.getCanvas('mesh', paddedWidth, paddedHeight, false);
+ var tmpCtx = tmpCanvas.context;
+ var data = tmpCtx.createImageData(width, height);
+ if (backgroundColor) {
+ var bytes = data.data;
+ for (i = 0, ii = bytes.length; i < ii; i += 4) {
+ bytes[i] = backgroundColor[0];
+ bytes[i + 1] = backgroundColor[1];
+ bytes[i + 2] = backgroundColor[2];
+ bytes[i + 3] = 255;
+ }
+ }
+ for (i = 0; i < figures.length; i++) {
+ drawFigure(data, figures[i], context);
+ }
+ tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
+ canvas = tmpCanvas.canvas;
+ }
+ return {
+ canvas: canvas,
+ offsetX: offsetX - BORDER_SIZE * scaleX,
+ offsetY: offsetY - BORDER_SIZE * scaleY,
+ scaleX: scaleX,
+ scaleY: scaleY
+ };
+ }
+ return createMeshCanvas;
+ }();
+ ShadingIRs.Mesh = {
+ fromIR: function Mesh_fromIR(raw) {
+ //var type = raw[1];
+ var coords = raw[2];
+ var colors = raw[3];
+ var figures = raw[4];
+ var bounds = raw[5];
+ var matrix = raw[6];
+ //var bbox = raw[7];
+ var background = raw[8];
+ return {
+ type: 'Pattern',
+ getPattern: function Mesh_getPattern(ctx, owner, shadingFill) {
+ var scale;
+ if (shadingFill) {
+ scale = Util.singularValueDecompose2dScale(ctx.mozCurrentTransform);
+ } else {
+ // Obtain scale from matrix and current transformation matrix.
+ scale = Util.singularValueDecompose2dScale(owner.baseTransform);
+ if (matrix) {
+ var matrixScale = Util.singularValueDecompose2dScale(matrix);
+ scale = [
+ scale[0] * matrixScale[0],
+ scale[1] * matrixScale[1]
+ ];
+ }
+ }
+ // Rasterizing on the main thread since sending/queue large canvases
+ // might cause OOM.
+ var temporaryPatternCanvas = createMeshCanvas(bounds, scale, coords, colors, figures, shadingFill ? null : background, owner.cachedCanvases);
+ if (!shadingFill) {
+ ctx.setTransform.apply(ctx, owner.baseTransform);
+ if (matrix) {
+ ctx.transform.apply(ctx, matrix);
+ }
+ }
+ ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
+ ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY);
+ return ctx.createPattern(temporaryPatternCanvas.canvas, 'no-repeat');
+ }
+ };
+ }
+ };
+ ShadingIRs.Dummy = {
+ fromIR: function Dummy_fromIR() {
+ return {
+ type: 'Pattern',
+ getPattern: function Dummy_fromIR_getPattern() {
+ return 'hotpink';
+ }
+ };
+ }
+ };
+ function getShadingPatternFromIR(raw) {
+ var shadingIR = ShadingIRs[raw[0]];
+ if (!shadingIR) {
+ error('Unknown IR type: ' + raw[0]);
+ }
+ return shadingIR.fromIR(raw);
+ }
+ var TilingPattern = function TilingPatternClosure() {
+ var PaintType = {
+ COLORED: 1,
+ UNCOLORED: 2
+ };
+ var MAX_PATTERN_SIZE = 3000;
+ // 10in @ 300dpi shall be enough
+ function TilingPattern(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
+ this.operatorList = IR[2];
+ this.matrix = IR[3] || [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0
+ ];
+ this.bbox = IR[4];
+ this.xstep = IR[5];
+ this.ystep = IR[6];
+ this.paintType = IR[7];
+ this.tilingType = IR[8];
+ this.color = color;
+ this.canvasGraphicsFactory = canvasGraphicsFactory;
+ this.baseTransform = baseTransform;
+ this.type = 'Pattern';
+ this.ctx = ctx;
+ }
+ TilingPattern.prototype = {
+ createPatternCanvas: function TilinPattern_createPatternCanvas(owner) {
+ var operatorList = this.operatorList;
+ var bbox = this.bbox;
+ var xstep = this.xstep;
+ var ystep = this.ystep;
+ var paintType = this.paintType;
+ var tilingType = this.tilingType;
+ var color = this.color;
+ var canvasGraphicsFactory = this.canvasGraphicsFactory;
+ info('TilingType: ' + tilingType);
+ var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
+ var topLeft = [
+ x0,
+ y0
+ ];
+ // we want the canvas to be as large as the step size
+ var botRight = [
+ x0 + xstep,
+ y0 + ystep
+ ];
+ var width = botRight[0] - topLeft[0];
+ var height = botRight[1] - topLeft[1];
+ // Obtain scale from matrix and current transformation matrix.
+ var matrixScale = Util.singularValueDecompose2dScale(this.matrix);
+ var curMatrixScale = Util.singularValueDecompose2dScale(this.baseTransform);
+ var combinedScale = [
+ matrixScale[0] * curMatrixScale[0],
+ matrixScale[1] * curMatrixScale[1]
+ ];
+ // MAX_PATTERN_SIZE is used to avoid OOM situation.
+ // Use width and height values that are as close as possible to the end
+ // result when the pattern is used. Too low value makes the pattern look
+ // blurry. Too large value makes it look too crispy.
+ width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])), MAX_PATTERN_SIZE);
+ height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])), MAX_PATTERN_SIZE);
+ var tmpCanvas = owner.cachedCanvases.getCanvas('pattern', width, height, true);
+ var tmpCtx = tmpCanvas.context;
+ var graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx);
+ graphics.groupLevel = owner.groupLevel;
+ this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color);
+ this.setScale(width, height, xstep, ystep);
+ this.transformToScale(graphics);
+ // transform coordinates to pattern space
+ var tmpTranslate = [
+ 1,
+ 0,
+ 0,
+ 1,
+ -topLeft[0],
+ -topLeft[1]
+ ];
+ graphics.transform.apply(graphics, tmpTranslate);
+ this.clipBbox(graphics, bbox, x0, y0, x1, y1);
+ graphics.executeOperatorList(operatorList);
+ return tmpCanvas.canvas;
+ },
+ setScale: function TilingPattern_setScale(width, height, xstep, ystep) {
+ this.scale = [
+ width / xstep,
+ height / ystep
+ ];
+ },
+ transformToScale: function TilingPattern_transformToScale(graphics) {
+ var scale = this.scale;
+ var tmpScale = [
+ scale[0],
+ 0,
+ 0,
+ scale[1],
+ 0,
+ 0
+ ];
+ graphics.transform.apply(graphics, tmpScale);
+ },
+ scaleToContext: function TilingPattern_scaleToContext() {
+ var scale = this.scale;
+ this.ctx.scale(1 / scale[0], 1 / scale[1]);
+ },
+ clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) {
+ if (bbox && isArray(bbox) && bbox.length === 4) {
+ var bboxWidth = x1 - x0;
+ var bboxHeight = y1 - y0;
+ graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
+ graphics.clip();
+ graphics.endPath();
+ }
+ },
+ setFillAndStrokeStyleToContext: function setFillAndStrokeStyleToContext(context, paintType, color) {
+ switch (paintType) {
+ case PaintType.COLORED:
+ var ctx = this.ctx;
+ context.fillStyle = ctx.fillStyle;
+ context.strokeStyle = ctx.strokeStyle;
+ break;
+ case PaintType.UNCOLORED:
+ var cssColor = Util.makeCssRgb(color[0], color[1], color[2]);
+ context.fillStyle = cssColor;
+ context.strokeStyle = cssColor;
+ break;
+ default:
+ error('Unsupported paint type: ' + paintType);
+ }
+ },
+ getPattern: function TilingPattern_getPattern(ctx, owner) {
+ var temporaryPatternCanvas = this.createPatternCanvas(owner);
+ ctx = this.ctx;
+ ctx.setTransform.apply(ctx, this.baseTransform);
+ ctx.transform.apply(ctx, this.matrix);
+ this.scaleToContext();
+ return ctx.createPattern(temporaryPatternCanvas, 'repeat');
+ }
+ };
+ return TilingPattern;
+ }();
+ exports.getShadingPatternFromIR = getShadingPatternFromIR;
+ exports.TilingPattern = TilingPattern;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayCanvas = {}, root.pdfjsSharedUtil, root.pdfjsDisplayDOMUtils, root.pdfjsDisplayPatternHelper, root.pdfjsDisplayWebGL);
+ }(this, function (exports, sharedUtil, displayDOMUtils, displayPatternHelper, displayWebGL) {
+ var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
+ var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
+ var ImageKind = sharedUtil.ImageKind;
+ var OPS = sharedUtil.OPS;
+ var TextRenderingMode = sharedUtil.TextRenderingMode;
+ var Uint32ArrayView = sharedUtil.Uint32ArrayView;
+ var Util = sharedUtil.Util;
+ var assert = sharedUtil.assert;
+ var info = sharedUtil.info;
+ var isNum = sharedUtil.isNum;
+ var isArray = sharedUtil.isArray;
+ var isLittleEndian = sharedUtil.isLittleEndian;
+ var error = sharedUtil.error;
+ var shadow = sharedUtil.shadow;
+ var warn = sharedUtil.warn;
+ var TilingPattern = displayPatternHelper.TilingPattern;
+ var getShadingPatternFromIR = displayPatternHelper.getShadingPatternFromIR;
+ var WebGLUtils = displayWebGL.WebGLUtils;
+ var hasCanvasTypedArrays = displayDOMUtils.hasCanvasTypedArrays;
+ // <canvas> contexts store most of the state we need natively.
+ // However, PDF needs a bit more state, which we store here.
+ // Minimal font size that would be used during canvas fillText operations.
+ var MIN_FONT_SIZE = 16;
+ // Maximum font size that would be used during canvas fillText operations.
+ var MAX_FONT_SIZE = 100;
+ var MAX_GROUP_SIZE = 4096;
+ // Heuristic value used when enforcing minimum line widths.
+ var MIN_WIDTH_FACTOR = 0.65;
+ var COMPILE_TYPE3_GLYPHS = true;
+ var MAX_SIZE_TO_COMPILE = 1000;
+ var FULL_CHUNK_HEIGHT = 16;
+ var HasCanvasTypedArraysCached = {
+ get value() {
+ return shadow(HasCanvasTypedArraysCached, 'value', hasCanvasTypedArrays());
+ }
+ };
+ var IsLittleEndianCached = {
+ get value() {
+ return shadow(IsLittleEndianCached, 'value', isLittleEndian());
+ }
+ };
+ function createScratchCanvas(width, height) {
+ var canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ return canvas;
+ }
+ function addContextCurrentTransform(ctx) {
+ // If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
+ if (!ctx.mozCurrentTransform) {
+ ctx._originalSave = ctx.save;
+ ctx._originalRestore = ctx.restore;
+ ctx._originalRotate = ctx.rotate;
+ ctx._originalScale = ctx.scale;
+ ctx._originalTranslate = ctx.translate;
+ ctx._originalTransform = ctx.transform;
+ ctx._originalSetTransform = ctx.setTransform;
+ ctx._transformMatrix = ctx._transformMatrix || [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0
+ ];
+ ctx._transformStack = [];
+ Object.defineProperty(ctx, 'mozCurrentTransform', {
+ get: function getCurrentTransform() {
+ return this._transformMatrix;
+ }
+ });
+ Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
+ get: function getCurrentTransformInverse() {
+ // Calculation done using WolframAlpha:
+ // http://www.wolframalpha.com/input/?
+ // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
+ var m = this._transformMatrix;
+ var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
+ var ad_bc = a * d - b * c;
+ var bc_ad = b * c - a * d;
+ return [
+ d / ad_bc,
+ b / bc_ad,
+ c / bc_ad,
+ a / ad_bc,
+ (d * e - c * f) / bc_ad,
+ (b * e - a * f) / ad_bc
+ ];
+ }
+ });
+ ctx.save = function ctxSave() {
+ var old = this._transformMatrix;
+ this._transformStack.push(old);
+ this._transformMatrix = old.slice(0, 6);
+ this._originalSave();
+ };
+ ctx.restore = function ctxRestore() {
+ var prev = this._transformStack.pop();
+ if (prev) {
+ this._transformMatrix = prev;
+ this._originalRestore();
+ }
+ };
+ ctx.translate = function ctxTranslate(x, y) {
+ var m = this._transformMatrix;
+ m[4] = m[0] * x + m[2] * y + m[4];
+ m[5] = m[1] * x + m[3] * y + m[5];
+ this._originalTranslate(x, y);
+ };
+ ctx.scale = function ctxScale(x, y) {
+ var m = this._transformMatrix;
+ m[0] = m[0] * x;
+ m[1] = m[1] * x;
+ m[2] = m[2] * y;
+ m[3] = m[3] * y;
+ this._originalScale(x, y);
+ };
+ ctx.transform = function ctxTransform(a, b, c, d, e, f) {
+ var m = this._transformMatrix;
+ this._transformMatrix = [
+ m[0] * a + m[2] * b,
+ m[1] * a + m[3] * b,
+ m[0] * c + m[2] * d,
+ m[1] * c + m[3] * d,
+ m[0] * e + m[2] * f + m[4],
+ m[1] * e + m[3] * f + m[5]
+ ];
+ ctx._originalTransform(a, b, c, d, e, f);
+ };
+ ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
+ this._transformMatrix = [
+ a,
+ b,
+ c,
+ d,
+ e,
+ f
+ ];
+ ctx._originalSetTransform(a, b, c, d, e, f);
+ };
+ ctx.rotate = function ctxRotate(angle) {
+ var cosValue = Math.cos(angle);
+ var sinValue = Math.sin(angle);
+ var m = this._transformMatrix;
+ this._transformMatrix = [
+ m[0] * cosValue + m[2] * sinValue,
+ m[1] * cosValue + m[3] * sinValue,
+ m[0] * -sinValue + m[2] * cosValue,
+ m[1] * -sinValue + m[3] * cosValue,
+ m[4],
+ m[5]
+ ];
+ this._originalRotate(angle);
+ };
+ }
+ }
+ var CachedCanvases = function CachedCanvasesClosure() {
+ function CachedCanvases() {
+ this.cache = Object.create(null);
+ }
+ CachedCanvases.prototype = {
+ getCanvas: function CachedCanvases_getCanvas(id, width, height, trackTransform) {
+ var canvasEntry;
+ if (this.cache[id] !== undefined) {
+ canvasEntry = this.cache[id];
+ canvasEntry.canvas.width = width;
+ canvasEntry.canvas.height = height;
+ // reset canvas transform for emulated mozCurrentTransform, if needed
+ canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
+ } else {
+ var canvas = createScratchCanvas(width, height);
+ var ctx = canvas.getContext('2d');
+ if (trackTransform) {
+ addContextCurrentTransform(ctx);
+ }
+ this.cache[id] = canvasEntry = {
+ canvas: canvas,
+ context: ctx
+ };
+ }
+ return canvasEntry;
+ },
+ clear: function () {
+ for (var id in this.cache) {
+ var canvasEntry = this.cache[id];
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ canvasEntry.canvas.width = 0;
+ canvasEntry.canvas.height = 0;
+ delete this.cache[id];
+ }
+ }
+ };
+ return CachedCanvases;
+ }();
+ function compileType3Glyph(imgData) {
+ var POINT_TO_PROCESS_LIMIT = 1000;
+ var width = imgData.width, height = imgData.height;
+ var i, j, j0, width1 = width + 1;
+ var points = new Uint8Array(width1 * (height + 1));
+ var POINT_TYPES = new Uint8Array([
+ 0,
+ 2,
+ 4,
+ 0,
+ 1,
+ 0,
+ 5,
+ 4,
+ 8,
+ 10,
+ 0,
+ 8,
+ 0,
+ 2,
+ 1,
+ 0
+ ]);
+ // decodes bit-packed mask data
+ var lineSize = width + 7 & ~7, data0 = imgData.data;
+ var data = new Uint8Array(lineSize * height), pos = 0, ii;
+ for (i = 0, ii = data0.length; i < ii; i++) {
+ var mask = 128, elem = data0[i];
+ while (mask > 0) {
+ data[pos++] = elem & mask ? 0 : 255;
+ mask >>= 1;
+ }
+ }
+ // finding iteresting points: every point is located between mask pixels,
+ // so there will be points of the (width + 1)x(height + 1) grid. Every point
+ // will have flags assigned based on neighboring mask pixels:
+ // 4 | 8
+ // --P--
+ // 2 | 1
+ // We are interested only in points with the flags:
+ // - outside corners: 1, 2, 4, 8;
+ // - inside corners: 7, 11, 13, 14;
+ // - and, intersections: 5, 10.
+ var count = 0;
+ pos = 0;
+ if (data[pos] !== 0) {
+ points[0] = 1;
+ ++count;
+ }
+ for (j = 1; j < width; j++) {
+ if (data[pos] !== data[pos + 1]) {
+ points[j] = data[pos] ? 2 : 1;
+ ++count;
+ }
+ pos++;
+ }
+ if (data[pos] !== 0) {
+ points[j] = 2;
+ ++count;
+ }
+ for (i = 1; i < height; i++) {
+ pos = i * lineSize;
+ j0 = i * width1;
+ if (data[pos - lineSize] !== data[pos]) {
+ points[j0] = data[pos] ? 1 : 8;
+ ++count;
+ }
+ // 'sum' is the position of the current pixel configuration in the 'TYPES'
+ // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
+ var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
+ for (j = 1; j < width; j++) {
+ sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0);
+ if (POINT_TYPES[sum]) {
+ points[j0 + j] = POINT_TYPES[sum];
+ ++count;
+ }
+ pos++;
+ }
+ if (data[pos - lineSize] !== data[pos]) {
+ points[j0 + j] = data[pos] ? 2 : 4;
+ ++count;
+ }
+ if (count > POINT_TO_PROCESS_LIMIT) {
+ return null;
+ }
+ }
+ pos = lineSize * (height - 1);
+ j0 = i * width1;
+ if (data[pos] !== 0) {
+ points[j0] = 8;
+ ++count;
+ }
+ for (j = 1; j < width; j++) {
+ if (data[pos] !== data[pos + 1]) {
+ points[j0 + j] = data[pos] ? 4 : 8;
+ ++count;
+ }
+ pos++;
+ }
+ if (data[pos] !== 0) {
+ points[j0 + j] = 4;
+ ++count;
+ }
+ if (count > POINT_TO_PROCESS_LIMIT) {
+ return null;
+ }
+ // building outlines
+ var steps = new Int32Array([
+ 0,
+ width1,
+ -1,
+ 0,
+ -width1,
+ 0,
+ 0,
+ 0,
+ 1
+ ]);
+ var outlines = [];
+ for (i = 0; count && i <= height; i++) {
+ var p = i * width1;
+ var end = p + width;
+ while (p < end && !points[p]) {
+ p++;
+ }
+ if (p === end) {
+ continue;
+ }
+ var coords = [
+ p % width1,
+ i
+ ];
+ var type = points[p], p0 = p, pp;
+ do {
+ var step = steps[type];
+ do {
+ p += step;
+ } while (!points[p]);
+ pp = points[p];
+ if (pp !== 5 && pp !== 10) {
+ // set new direction
+ type = pp;
+ // delete mark
+ points[p] = 0;
+ } else {
+ // type is 5 or 10, ie, a crossing
+ // set new direction
+ type = pp & 0x33 * type >> 4;
+ // set new type for "future hit"
+ points[p] &= type >> 2 | type << 2;
+ }
+ coords.push(p % width1);
+ coords.push(p / width1 | 0);
+ --count;
+ } while (p0 !== p);
+ outlines.push(coords);
+ --i;
+ }
+ var drawOutline = function (c) {
+ c.save();
+ // the path shall be painted in [0..1]x[0..1] space
+ c.scale(1 / width, -1 / height);
+ c.translate(0, -height);
+ c.beginPath();
+ for (var i = 0, ii = outlines.length; i < ii; i++) {
+ var o = outlines[i];
+ c.moveTo(o[0], o[1]);
+ for (var j = 2, jj = o.length; j < jj; j += 2) {
+ c.lineTo(o[j], o[j + 1]);
+ }
+ }
+ c.fill();
+ c.beginPath();
+ c.restore();
+ };
+ return drawOutline;
+ }
+ var CanvasExtraState = function CanvasExtraStateClosure() {
+ function CanvasExtraState(old) {
+ // Are soft masks and alpha values shapes or opacities?
+ this.alphaIsShape = false;
+ this.fontSize = 0;
+ this.fontSizeScale = 1;
+ this.textMatrix = IDENTITY_MATRIX;
+ this.textMatrixScale = 1;
+ this.fontMatrix = FONT_IDENTITY_MATRIX;
+ this.leading = 0;
+ // Current point (in user coordinates)
+ this.x = 0;
+ this.y = 0;
+ // Start of text line (in text coordinates)
+ this.lineX = 0;
+ this.lineY = 0;
+ // Character and word spacing
+ this.charSpacing = 0;
+ this.wordSpacing = 0;
+ this.textHScale = 1;
+ this.textRenderingMode = TextRenderingMode.FILL;
+ this.textRise = 0;
+ // Default fore and background colors
+ this.fillColor = '#000000';
+ this.strokeColor = '#000000';
+ this.patternFill = false;
+ // Note: fill alpha applies to all non-stroking operations
+ this.fillAlpha = 1;
+ this.strokeAlpha = 1;
+ this.lineWidth = 1;
+ this.activeSMask = null;
+ this.resumeSMaskCtx = null;
+ // nonclonable field (see the save method below)
+ this.old = old;
+ }
+ CanvasExtraState.prototype = {
+ clone: function CanvasExtraState_clone() {
+ return Object.create(this);
+ },
+ setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+ };
+ return CanvasExtraState;
+ }();
+ var CanvasGraphics = function CanvasGraphicsClosure() {
+ // Defines the time the executeOperatorList is going to be executing
+ // before it stops and shedules a continue of execution.
+ var EXECUTION_TIME = 15;
+ // Defines the number of steps before checking the execution time
+ var EXECUTION_STEPS = 10;
+ function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) {
+ this.ctx = canvasCtx;
+ this.current = new CanvasExtraState();
+ this.stateStack = [];
+ this.pendingClip = null;
+ this.pendingEOFill = false;
+ this.res = null;
+ this.xobjs = null;
+ this.commonObjs = commonObjs;
+ this.objs = objs;
+ this.imageLayer = imageLayer;
+ this.groupStack = [];
+ this.processingType3 = null;
+ // Patterns are painted relative to the initial page/form transform, see pdf
+ // spec 8.7.2 NOTE 1.
+ this.baseTransform = null;
+ this.baseTransformStack = [];
+ this.groupLevel = 0;
+ this.smaskStack = [];
+ this.smaskCounter = 0;
+ this.tempSMask = null;
+ this.cachedCanvases = new CachedCanvases();
+ if (canvasCtx) {
+ // NOTE: if mozCurrentTransform is polyfilled, then the current state of
+ // the transformation must already be set in canvasCtx._transformMatrix.
+ addContextCurrentTransform(canvasCtx);
+ }
+ this.cachedGetSinglePixelWidth = null;
+ }
+ function putBinaryImageData(ctx, imgData) {
+ if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
+ ctx.putImageData(imgData, 0, 0);
+ return;
+ }
+ // Put the image data to the canvas in chunks, rather than putting the
+ // whole image at once. This saves JS memory, because the ImageData object
+ // is smaller. It also possibly saves C++ memory within the implementation
+ // of putImageData(). (E.g. in Firefox we make two short-lived copies of
+ // the data passed to putImageData()). |n| shouldn't be too small, however,
+ // because too many putImageData() calls will slow things down.
+ //
+ // Note: as written, if the last chunk is partial, the putImageData() call
+ // will (conceptually) put pixels past the bounds of the canvas. But
+ // that's ok; any such pixels are ignored.
+ var height = imgData.height, width = imgData.width;
+ var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+ var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+ var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
+ var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
+ var srcPos = 0, destPos;
+ var src = imgData.data;
+ var dest = chunkImgData.data;
+ var i, j, thisChunkHeight, elemsInThisChunk;
+ // There are multiple forms in which the pixel data can be passed, and
+ // imgData.kind tells us which one this is.
+ if (imgData.kind === ImageKind.GRAYSCALE_1BPP) {
+ // Grayscale, 1 bit per pixel (i.e. black-and-white).
+ var srcLength = src.byteLength;
+ var dest32 = HasCanvasTypedArraysCached.value ? new Uint32Array(dest.buffer) : new Uint32ArrayView(dest);
+ var dest32DataLength = dest32.length;
+ var fullSrcDiff = width + 7 >> 3;
+ var white = 0xFFFFFFFF;
+ var black = IsLittleEndianCached.value || !HasCanvasTypedArraysCached.value ? 0xFF000000 : 0x000000FF;
+ for (i = 0; i < totalChunks; i++) {
+ thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
+ destPos = 0;
+ for (j = 0; j < thisChunkHeight; j++) {
+ var srcDiff = srcLength - srcPos;
+ var k = 0;
+ var kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
+ var kEndUnrolled = kEnd & ~7;
+ var mask = 0;
+ var srcByte = 0;
+ for (; k < kEndUnrolled; k += 8) {
+ srcByte = src[srcPos++];
+ dest32[destPos++] = srcByte & 128 ? white : black;
+ dest32[destPos++] = srcByte & 64 ? white : black;
+ dest32[destPos++] = srcByte & 32 ? white : black;
+ dest32[destPos++] = srcByte & 16 ? white : black;
+ dest32[destPos++] = srcByte & 8 ? white : black;
+ dest32[destPos++] = srcByte & 4 ? white : black;
+ dest32[destPos++] = srcByte & 2 ? white : black;
+ dest32[destPos++] = srcByte & 1 ? white : black;
+ }
+ for (; k < kEnd; k++) {
+ if (mask === 0) {
+ srcByte = src[srcPos++];
+ mask = 128;
+ }
+ dest32[destPos++] = srcByte & mask ? white : black;
+ mask >>= 1;
+ }
+ }
+ // We ran out of input. Make all remaining pixels transparent.
+ while (destPos < dest32DataLength) {
+ dest32[destPos++] = 0;
+ }
+ ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
+ }
+ } else if (imgData.kind === ImageKind.RGBA_32BPP) {
+ // RGBA, 32-bits per pixel.
+ j = 0;
+ elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
+ for (i = 0; i < fullChunks; i++) {
+ dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+ srcPos += elemsInThisChunk;
+ ctx.putImageData(chunkImgData, 0, j);
+ j += FULL_CHUNK_HEIGHT;
+ }
+ if (i < totalChunks) {
+ elemsInThisChunk = width * partialChunkHeight * 4;
+ dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+ ctx.putImageData(chunkImgData, 0, j);
+ }
+ } else if (imgData.kind === ImageKind.RGB_24BPP) {
+ // RGB, 24-bits per pixel.
+ thisChunkHeight = FULL_CHUNK_HEIGHT;
+ elemsInThisChunk = width * thisChunkHeight;
+ for (i = 0; i < totalChunks; i++) {
+ if (i >= fullChunks) {
+ thisChunkHeight = partialChunkHeight;
+ elemsInThisChunk = width * thisChunkHeight;
+ }
+ destPos = 0;
+ for (j = elemsInThisChunk; j--;) {
+ dest[destPos++] = src[srcPos++];
+ dest[destPos++] = src[srcPos++];
+ dest[destPos++] = src[srcPos++];
+ dest[destPos++] = 255;
+ }
+ ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
+ }
+ } else {
+ error('bad image kind: ' + imgData.kind);
+ }
+ }
+ function putBinaryImageMask(ctx, imgData) {
+ var height = imgData.height, width = imgData.width;
+ var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+ var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+ var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
+ var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
+ var srcPos = 0;
+ var src = imgData.data;
+ var dest = chunkImgData.data;
+ for (var i = 0; i < totalChunks; i++) {
+ var thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
+ // Expand the mask so it can be used by the canvas. Any required
+ // inversion has already been handled.
+ var destPos = 3;
+ // alpha component offset
+ for (var j = 0; j < thisChunkHeight; j++) {
+ var mask = 0;
+ for (var k = 0; k < width; k++) {
+ if (!mask) {
+ var elem = src[srcPos++];
+ mask = 128;
+ }
+ dest[destPos] = elem & mask ? 0 : 255;
+ destPos += 4;
+ mask >>= 1;
+ }
+ }
+ ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
+ }
+ }
+ function copyCtxState(sourceCtx, destCtx) {
+ var properties = [
+ 'strokeStyle',
+ 'fillStyle',
+ 'fillRule',
+ 'globalAlpha',
+ 'lineWidth',
+ 'lineCap',
+ 'lineJoin',
+ 'miterLimit',
+ 'globalCompositeOperation',
+ 'font'
+ ];
+ for (var i = 0, ii = properties.length; i < ii; i++) {
+ var property = properties[i];
+ if (sourceCtx[property] !== undefined) {
+ destCtx[property] = sourceCtx[property];
+ }
+ }
+ if (sourceCtx.setLineDash !== undefined) {
+ destCtx.setLineDash(sourceCtx.getLineDash());
+ destCtx.lineDashOffset = sourceCtx.lineDashOffset;
+ }
+ }
+ function composeSMaskBackdrop(bytes, r0, g0, b0) {
+ var length = bytes.length;
+ for (var i = 3; i < length; i += 4) {
+ var alpha = bytes[i];
+ if (alpha === 0) {
+ bytes[i - 3] = r0;
+ bytes[i - 2] = g0;
+ bytes[i - 1] = b0;
+ } else if (alpha < 255) {
+ var alpha_ = 255 - alpha;
+ bytes[i - 3] = bytes[i - 3] * alpha + r0 * alpha_ >> 8;
+ bytes[i - 2] = bytes[i - 2] * alpha + g0 * alpha_ >> 8;
+ bytes[i - 1] = bytes[i - 1] * alpha + b0 * alpha_ >> 8;
+ }
+ }
+ }
+ function composeSMaskAlpha(maskData, layerData, transferMap) {
+ var length = maskData.length;
+ var scale = 1 / 255;
+ for (var i = 3; i < length; i += 4) {
+ var alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
+ layerData[i] = layerData[i] * alpha * scale | 0;
+ }
+ }
+ function composeSMaskLuminosity(maskData, layerData, transferMap) {
+ var length = maskData.length;
+ for (var i = 3; i < length; i += 4) {
+ var y = maskData[i - 3] * 77 + // * 0.3 / 255 * 0x10000
+ maskData[i - 2] * 152 + // * 0.59 ....
+ maskData[i - 1] * 28;
+ // * 0.11 ....
+ layerData[i] = transferMap ? layerData[i] * transferMap[y >> 8] >> 8 : layerData[i] * y >> 16;
+ }
+ }
+ function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap) {
+ var hasBackdrop = !!backdrop;
+ var r0 = hasBackdrop ? backdrop[0] : 0;
+ var g0 = hasBackdrop ? backdrop[1] : 0;
+ var b0 = hasBackdrop ? backdrop[2] : 0;
+ var composeFn;
+ if (subtype === 'Luminosity') {
+ composeFn = composeSMaskLuminosity;
+ } else {
+ composeFn = composeSMaskAlpha;
+ }
+ // processing image in chunks to save memory
+ var PIXELS_TO_PROCESS = 1048576;
+ var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
+ for (var row = 0; row < height; row += chunkSize) {
+ var chunkHeight = Math.min(chunkSize, height - row);
+ var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
+ var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
+ if (hasBackdrop) {
+ composeSMaskBackdrop(maskData.data, r0, g0, b0);
+ }
+ composeFn(maskData.data, layerData.data, transferMap);
+ maskCtx.putImageData(layerData, 0, row);
+ }
+ }
+ function composeSMask(ctx, smask, layerCtx) {
+ var mask = smask.canvas;
+ var maskCtx = smask.context;
+ ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, smask.offsetX, smask.offsetY);
+ var backdrop = smask.backdrop || null;
+ if (!smask.transferMap && WebGLUtils.isEnabled) {
+ var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask, {
+ subtype: smask.subtype,
+ backdrop: backdrop
+ });
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+ ctx.drawImage(composed, smask.offsetX, smask.offsetY);
+ return;
+ }
+ genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, smask.subtype, backdrop, smask.transferMap);
+ ctx.drawImage(mask, 0, 0);
+ }
+ var LINE_CAP_STYLES = [
+ 'butt',
+ 'round',
+ 'square'
+ ];
+ var LINE_JOIN_STYLES = [
+ 'miter',
+ 'round',
+ 'bevel'
+ ];
+ var NORMAL_CLIP = {};
+ var EO_CLIP = {};
+ CanvasGraphics.prototype = {
+ beginDrawing: function CanvasGraphics_beginDrawing(transform, viewport, transparency) {
+ // For pdfs that use blend modes we have to clear the canvas else certain
+ // blend modes can look wrong since we'd be blending with a white
+ // backdrop. The problem with a transparent backdrop though is we then
+ // don't get sub pixel anti aliasing on text, creating temporary
+ // transparent canvas when we have blend modes.
+ var width = this.ctx.canvas.width;
+ var height = this.ctx.canvas.height;
+ this.ctx.save();
+ this.ctx.fillStyle = 'rgb(255, 255, 255)';
+ this.ctx.fillRect(0, 0, width, height);
+ this.ctx.restore();
+ if (transparency) {
+ var transparentCanvas = this.cachedCanvases.getCanvas('transparent', width, height, true);
+ this.compositeCtx = this.ctx;
+ this.transparentCanvas = transparentCanvas.canvas;
+ this.ctx = transparentCanvas.context;
+ this.ctx.save();
+ // The transform can be applied before rendering, transferring it to
+ // the new canvas.
+ this.ctx.transform.apply(this.ctx, this.compositeCtx.mozCurrentTransform);
+ }
+ this.ctx.save();
+ if (transform) {
+ this.ctx.transform.apply(this.ctx, transform);
+ }
+ this.ctx.transform.apply(this.ctx, viewport.transform);
+ this.baseTransform = this.ctx.mozCurrentTransform.slice();
+ if (this.imageLayer) {
+ this.imageLayer.beginLayout();
+ }
+ },
+ executeOperatorList: function CanvasGraphics_executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) {
+ var argsArray = operatorList.argsArray;
+ var fnArray = operatorList.fnArray;
+ var i = executionStartIdx || 0;
+ var argsArrayLen = argsArray.length;
+ // Sometimes the OperatorList to execute is empty.
+ if (argsArrayLen === i) {
+ return i;
+ }
+ var chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === 'function';
+ var endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
+ var steps = 0;
+ var commonObjs = this.commonObjs;
+ var objs = this.objs;
+ var fnId;
+ while (true) {
+ if (stepper !== undefined && i === stepper.nextBreakPoint) {
+ stepper.breakIt(i, continueCallback);
+ return i;
+ }
+ fnId = fnArray[i];
+ if (fnId !== OPS.dependency) {
+ this[fnId].apply(this, argsArray[i]);
+ } else {
+ var deps = argsArray[i];
+ for (var n = 0, nn = deps.length; n < nn; n++) {
+ var depObjId = deps[n];
+ var common = depObjId[0] === 'g' && depObjId[1] === '_';
+ var objsPool = common ? commonObjs : objs;
+ // If the promise isn't resolved yet, add the continueCallback
+ // to the promise and bail out.
+ if (!objsPool.isResolved(depObjId)) {
+ objsPool.get(depObjId, continueCallback);
+ return i;
+ }
+ }
+ }
+ i++;
+ // If the entire operatorList was executed, stop as were done.
+ if (i === argsArrayLen) {
+ return i;
+ }
+ // If the execution took longer then a certain amount of time and
+ // `continueCallback` is specified, interrupt the execution.
+ if (chunkOperations && ++steps > EXECUTION_STEPS) {
+ if (Date.now() > endTime) {
+ continueCallback();
+ return i;
+ }
+ steps = 0;
+ }
+ }
+ },
+ // If the operatorList isn't executed completely yet OR the execution
+ // time was short enough, do another execution round.
+ endDrawing: function CanvasGraphics_endDrawing() {
+ // Finishing all opened operations such as SMask group painting.
+ if (this.current.activeSMask !== null) {
+ this.endSMaskGroup();
+ }
+ this.ctx.restore();
+ if (this.transparentCanvas) {
+ this.ctx = this.compositeCtx;
+ this.ctx.save();
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
+ // Avoid apply transform twice
+ this.ctx.drawImage(this.transparentCanvas, 0, 0);
+ this.ctx.restore();
+ this.transparentCanvas = null;
+ }
+ this.cachedCanvases.clear();
+ WebGLUtils.clear();
+ if (this.imageLayer) {
+ this.imageLayer.endLayout();
+ }
+ },
+ // Graphics state
+ setLineWidth: function CanvasGraphics_setLineWidth(width) {
+ this.current.lineWidth = width;
+ this.ctx.lineWidth = width;
+ },
+ setLineCap: function CanvasGraphics_setLineCap(style) {
+ this.ctx.lineCap = LINE_CAP_STYLES[style];
+ },
+ setLineJoin: function CanvasGraphics_setLineJoin(style) {
+ this.ctx.lineJoin = LINE_JOIN_STYLES[style];
+ },
+ setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
+ this.ctx.miterLimit = limit;
+ },
+ setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
+ var ctx = this.ctx;
+ if (ctx.setLineDash !== undefined) {
+ ctx.setLineDash(dashArray);
+ ctx.lineDashOffset = dashPhase;
+ }
+ },
+ setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
+ },
+ setFlatness: function CanvasGraphics_setFlatness(flatness) {
+ },
+ setGState: function CanvasGraphics_setGState(states) {
+ for (var i = 0, ii = states.length; i < ii; i++) {
+ var state = states[i];
+ var key = state[0];
+ var value = state[1];
+ switch (key) {
+ case 'LW':
+ this.setLineWidth(value);
+ break;
+ case 'LC':
+ this.setLineCap(value);
+ break;
+ case 'LJ':
+ this.setLineJoin(value);
+ break;
+ case 'ML':
+ this.setMiterLimit(value);
+ break;
+ case 'D':
+ this.setDash(value[0], value[1]);
+ break;
+ case 'RI':
+ this.setRenderingIntent(value);
+ break;
+ case 'FL':
+ this.setFlatness(value);
+ break;
+ case 'Font':
+ this.setFont(value[0], value[1]);
+ break;
+ case 'CA':
+ this.current.strokeAlpha = state[1];
+ break;
+ case 'ca':
+ this.current.fillAlpha = state[1];
+ this.ctx.globalAlpha = state[1];
+ break;
+ case 'BM':
+ if (value && value.name && value.name !== 'Normal') {
+ var mode = value.name.replace(/([A-Z])/g, function (c) {
+ return '-' + c.toLowerCase();
+ }).substring(1);
+ this.ctx.globalCompositeOperation = mode;
+ if (this.ctx.globalCompositeOperation !== mode) {
+ warn('globalCompositeOperation "' + mode + '" is not supported');
+ }
+ } else {
+ this.ctx.globalCompositeOperation = 'source-over';
+ }
+ break;
+ case 'SMask':
+ if (this.current.activeSMask) {
+ // If SMask is currrenly used, it needs to be suspended or
+ // finished. Suspend only makes sense when at least one save()
+ // was performed and state needs to be reverted on restore().
+ if (this.stateStack.length > 0 && this.stateStack[this.stateStack.length - 1].activeSMask === this.current.activeSMask) {
+ this.suspendSMaskGroup();
+ } else {
+ this.endSMaskGroup();
+ }
+ }
+ this.current.activeSMask = value ? this.tempSMask : null;
+ if (this.current.activeSMask) {
+ this.beginSMaskGroup();
+ }
+ this.tempSMask = null;
+ break;
+ }
+ }
+ },
+ beginSMaskGroup: function CanvasGraphics_beginSMaskGroup() {
+ var activeSMask = this.current.activeSMask;
+ var drawnWidth = activeSMask.canvas.width;
+ var drawnHeight = activeSMask.canvas.height;
+ var cacheId = 'smaskGroupAt' + this.groupLevel;
+ var scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
+ var currentCtx = this.ctx;
+ var currentTransform = currentCtx.mozCurrentTransform;
+ this.ctx.save();
+ var groupCtx = scratchCanvas.context;
+ groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
+ groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
+ groupCtx.transform.apply(groupCtx, currentTransform);
+ activeSMask.startTransformInverse = groupCtx.mozCurrentTransformInverse;
+ copyCtxState(currentCtx, groupCtx);
+ this.ctx = groupCtx;
+ this.setGState([
+ [
+ 'BM',
+ 'Normal'
+ ],
+ [
+ 'ca',
+ 1
+ ],
+ [
+ 'CA',
+ 1
+ ]
+ ]);
+ this.groupStack.push(currentCtx);
+ this.groupLevel++;
+ },
+ suspendSMaskGroup: function CanvasGraphics_endSMaskGroup() {
+ // Similar to endSMaskGroup, the intermediate canvas has to be composed
+ // and future ctx state restored.
+ var groupCtx = this.ctx;
+ this.groupLevel--;
+ this.ctx = this.groupStack.pop();
+ composeSMask(this.ctx, this.current.activeSMask, groupCtx);
+ this.ctx.restore();
+ this.ctx.save();
+ // save is needed since SMask will be resumed.
+ copyCtxState(groupCtx, this.ctx);
+ // Saving state for resuming.
+ this.current.resumeSMaskCtx = groupCtx;
+ // Transform was changed in the SMask canvas, reflecting this change on
+ // this.ctx.
+ var deltaTransform = Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
+ this.ctx.transform.apply(this.ctx, deltaTransform);
+ // SMask was composed, the results at the groupCtx can be cleared.
+ groupCtx.save();
+ groupCtx.setTransform(1, 0, 0, 1, 0, 0);
+ groupCtx.clearRect(0, 0, groupCtx.canvas.width, groupCtx.canvas.height);
+ groupCtx.restore();
+ },
+ resumeSMaskGroup: function CanvasGraphics_endSMaskGroup() {
+ // Resuming state saved by suspendSMaskGroup. We don't need to restore
+ // any groupCtx state since restore() command (the only caller) will do
+ // that for us. See also beginSMaskGroup.
+ var groupCtx = this.current.resumeSMaskCtx;
+ var currentCtx = this.ctx;
+ this.ctx = groupCtx;
+ this.groupStack.push(currentCtx);
+ this.groupLevel++;
+ },
+ endSMaskGroup: function CanvasGraphics_endSMaskGroup() {
+ var groupCtx = this.ctx;
+ this.groupLevel--;
+ this.ctx = this.groupStack.pop();
+ composeSMask(this.ctx, this.current.activeSMask, groupCtx);
+ this.ctx.restore();
+ copyCtxState(groupCtx, this.ctx);
+ // Transform was changed in the SMask canvas, reflecting this change on
+ // this.ctx.
+ var deltaTransform = Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
+ this.ctx.transform.apply(this.ctx, deltaTransform);
+ },
+ save: function CanvasGraphics_save() {
+ this.ctx.save();
+ var old = this.current;
+ this.stateStack.push(old);
+ this.current = old.clone();
+ this.current.resumeSMaskCtx = null;
+ },
+ restore: function CanvasGraphics_restore() {
+ // SMask was suspended, we just need to resume it.
+ if (this.current.resumeSMaskCtx) {
+ this.resumeSMaskGroup();
+ }
+ // SMask has to be finished once there is no states that are using the
+ // same SMask.
+ if (this.current.activeSMask !== null && (this.stateStack.length === 0 || this.stateStack[this.stateStack.length - 1].activeSMask !== this.current.activeSMask)) {
+ this.endSMaskGroup();
+ }
+ if (this.stateStack.length !== 0) {
+ this.current = this.stateStack.pop();
+ this.ctx.restore();
+ // Ensure that the clipping path is reset (fixes issue6413.pdf).
+ this.pendingClip = null;
+ this.cachedGetSinglePixelWidth = null;
+ }
+ },
+ transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
+ this.ctx.transform(a, b, c, d, e, f);
+ this.cachedGetSinglePixelWidth = null;
+ },
+ // Path
+ constructPath: function CanvasGraphics_constructPath(ops, args) {
+ var ctx = this.ctx;
+ var current = this.current;
+ var x = current.x, y = current.y;
+ for (var i = 0, j = 0, ii = ops.length; i < ii; i++) {
+ switch (ops[i] | 0) {
+ case OPS.rectangle:
+ x = args[j++];
+ y = args[j++];
+ var width = args[j++];
+ var height = args[j++];
+ if (width === 0) {
+ width = this.getSinglePixelWidth();
+ }
+ if (height === 0) {
+ height = this.getSinglePixelWidth();
+ }
+ var xw = x + width;
+ var yh = y + height;
+ this.ctx.moveTo(x, y);
+ this.ctx.lineTo(xw, y);
+ this.ctx.lineTo(xw, yh);
+ this.ctx.lineTo(x, yh);
+ this.ctx.lineTo(x, y);
+ this.ctx.closePath();
+ break;
+ case OPS.moveTo:
+ x = args[j++];
+ y = args[j++];
+ ctx.moveTo(x, y);
+ break;
+ case OPS.lineTo:
+ x = args[j++];
+ y = args[j++];
+ ctx.lineTo(x, y);
+ break;
+ case OPS.curveTo:
+ x = args[j + 4];
+ y = args[j + 5];
+ ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
+ j += 6;
+ break;
+ case OPS.curveTo2:
+ ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]);
+ x = args[j + 2];
+ y = args[j + 3];
+ j += 4;
+ break;
+ case OPS.curveTo3:
+ x = args[j + 2];
+ y = args[j + 3];
+ ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
+ j += 4;
+ break;
+ case OPS.closePath:
+ ctx.closePath();
+ break;
+ }
+ }
+ current.setCurrentPoint(x, y);
+ },
+ closePath: function CanvasGraphics_closePath() {
+ this.ctx.closePath();
+ },
+ stroke: function CanvasGraphics_stroke(consumePath) {
+ consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
+ var ctx = this.ctx;
+ var strokeColor = this.current.strokeColor;
+ // Prevent drawing too thin lines by enforcing a minimum line width.
+ ctx.lineWidth = Math.max(this.getSinglePixelWidth() * MIN_WIDTH_FACTOR, this.current.lineWidth);
+ // For stroke we want to temporarily change the global alpha to the
+ // stroking alpha.
+ ctx.globalAlpha = this.current.strokeAlpha;
+ if (strokeColor && strokeColor.hasOwnProperty('type') && strokeColor.type === 'Pattern') {
+ // for patterns, we transform to pattern space, calculate
+ // the pattern, call stroke, and restore to user space
+ ctx.save();
+ ctx.strokeStyle = strokeColor.getPattern(ctx, this);
+ ctx.stroke();
+ ctx.restore();
+ } else {
+ ctx.stroke();
+ }
+ if (consumePath) {
+ this.consumePath();
+ }
+ // Restore the global alpha to the fill alpha
+ ctx.globalAlpha = this.current.fillAlpha;
+ },
+ closeStroke: function CanvasGraphics_closeStroke() {
+ this.closePath();
+ this.stroke();
+ },
+ fill: function CanvasGraphics_fill(consumePath) {
+ consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
+ var ctx = this.ctx;
+ var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
+ var needRestore = false;
+ if (isPatternFill) {
+ ctx.save();
+ if (this.baseTransform) {
+ ctx.setTransform.apply(ctx, this.baseTransform);
+ }
+ ctx.fillStyle = fillColor.getPattern(ctx, this);
+ needRestore = true;
+ }
+ if (this.pendingEOFill) {
+ if (ctx.mozFillRule !== undefined) {
+ ctx.mozFillRule = 'evenodd';
+ ctx.fill();
+ ctx.mozFillRule = 'nonzero';
+ } else {
+ ctx.fill('evenodd');
+ }
+ this.pendingEOFill = false;
+ } else {
+ ctx.fill();
+ }
+ if (needRestore) {
+ ctx.restore();
+ }
+ if (consumePath) {
+ this.consumePath();
+ }
+ },
+ eoFill: function CanvasGraphics_eoFill() {
+ this.pendingEOFill = true;
+ this.fill();
+ },
+ fillStroke: function CanvasGraphics_fillStroke() {
+ this.fill(false);
+ this.stroke(false);
+ this.consumePath();
+ },
+ eoFillStroke: function CanvasGraphics_eoFillStroke() {
+ this.pendingEOFill = true;
+ this.fillStroke();
+ },
+ closeFillStroke: function CanvasGraphics_closeFillStroke() {
+ this.closePath();
+ this.fillStroke();
+ },
+ closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
+ this.pendingEOFill = true;
+ this.closePath();
+ this.fillStroke();
+ },
+ endPath: function CanvasGraphics_endPath() {
+ this.consumePath();
+ },
+ // Clipping
+ clip: function CanvasGraphics_clip() {
+ this.pendingClip = NORMAL_CLIP;
+ },
+ eoClip: function CanvasGraphics_eoClip() {
+ this.pendingClip = EO_CLIP;
+ },
+ // Text
+ beginText: function CanvasGraphics_beginText() {
+ this.current.textMatrix = IDENTITY_MATRIX;
+ this.current.textMatrixScale = 1;
+ this.current.x = this.current.lineX = 0;
+ this.current.y = this.current.lineY = 0;
+ },
+ endText: function CanvasGraphics_endText() {
+ var paths = this.pendingTextPaths;
+ var ctx = this.ctx;
+ if (paths === undefined) {
+ ctx.beginPath();
+ return;
+ }
+ ctx.save();
+ ctx.beginPath();
+ for (var i = 0; i < paths.length; i++) {
+ var path = paths[i];
+ ctx.setTransform.apply(ctx, path.transform);
+ ctx.translate(path.x, path.y);
+ path.addToPath(ctx, path.fontSize);
+ }
+ ctx.restore();
+ ctx.clip();
+ ctx.beginPath();
+ delete this.pendingTextPaths;
+ },
+ setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
+ this.current.charSpacing = spacing;
+ },
+ setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
+ this.current.wordSpacing = spacing;
+ },
+ setHScale: function CanvasGraphics_setHScale(scale) {
+ this.current.textHScale = scale / 100;
+ },
+ setLeading: function CanvasGraphics_setLeading(leading) {
+ this.current.leading = -leading;
+ },
+ setFont: function CanvasGraphics_setFont(fontRefName, size) {
+ var fontObj = this.commonObjs.get(fontRefName);
+ var current = this.current;
+ if (!fontObj) {
+ error('Can\'t find font for ' + fontRefName);
+ }
+ current.fontMatrix = fontObj.fontMatrix ? fontObj.fontMatrix : FONT_IDENTITY_MATRIX;
+ // A valid matrix needs all main diagonal elements to be non-zero
+ // This also ensures we bypass FF bugzilla bug #719844.
+ if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
+ warn('Invalid font matrix for font ' + fontRefName);
+ }
+ // The spec for Tf (setFont) says that 'size' specifies the font 'scale',
+ // and in some docs this can be negative (inverted x-y axes).
+ if (size < 0) {
+ size = -size;
+ current.fontDirection = -1;
+ } else {
+ current.fontDirection = 1;
+ }
+ this.current.font = fontObj;
+ this.current.fontSize = size;
+ if (fontObj.isType3Font) {
+ return;
+ }
+ // we don't need ctx.font for Type3 fonts
+ var name = fontObj.loadedName || 'sans-serif';
+ var bold = fontObj.black ? fontObj.bold ? '900' : 'bold' : fontObj.bold ? 'bold' : 'normal';
+ var italic = fontObj.italic ? 'italic' : 'normal';
+ var typeface = '"' + name + '", ' + fontObj.fallbackName;
+ // Some font backends cannot handle fonts below certain size.
+ // Keeping the font at minimal size and using the fontSizeScale to change
+ // the current transformation matrix before the fillText/strokeText.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
+ var browserFontSize = size < MIN_FONT_SIZE ? MIN_FONT_SIZE : size > MAX_FONT_SIZE ? MAX_FONT_SIZE : size;
+ this.current.fontSizeScale = size / browserFontSize;
+ var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
+ this.ctx.font = rule;
+ },
+ setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
+ this.current.textRenderingMode = mode;
+ },
+ setTextRise: function CanvasGraphics_setTextRise(rise) {
+ this.current.textRise = rise;
+ },
+ moveText: function CanvasGraphics_moveText(x, y) {
+ this.current.x = this.current.lineX += x;
+ this.current.y = this.current.lineY += y;
+ },
+ setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) {
+ this.setLeading(-y);
+ this.moveText(x, y);
+ },
+ setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) {
+ this.current.textMatrix = [
+ a,
+ b,
+ c,
+ d,
+ e,
+ f
+ ];
+ this.current.textMatrixScale = Math.sqrt(a * a + b * b);
+ this.current.x = this.current.lineX = 0;
+ this.current.y = this.current.lineY = 0;
+ },
+ nextLine: function CanvasGraphics_nextLine() {
+ this.moveText(0, this.current.leading);
+ },
+ paintChar: function CanvasGraphics_paintChar(character, x, y) {
+ var ctx = this.ctx;
+ var current = this.current;
+ var font = current.font;
+ var textRenderingMode = current.textRenderingMode;
+ var fontSize = current.fontSize / current.fontSizeScale;
+ var fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
+ var isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
+ var addToPath;
+ if (font.disableFontFace || isAddToPathSet) {
+ addToPath = font.getPathGenerator(this.commonObjs, character);
+ }
+ if (font.disableFontFace) {
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.beginPath();
+ addToPath(ctx, fontSize);
+ if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+ ctx.fill();
+ }
+ if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+ ctx.stroke();
+ }
+ ctx.restore();
+ } else {
+ if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+ ctx.fillText(character, x, y);
+ }
+ if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+ ctx.strokeText(character, x, y);
+ }
+ }
+ if (isAddToPathSet) {
+ var paths = this.pendingTextPaths || (this.pendingTextPaths = []);
+ paths.push({
+ transform: ctx.mozCurrentTransform,
+ x: x,
+ y: y,
+ fontSize: fontSize,
+ addToPath: addToPath
+ });
+ }
+ },
+ get isFontSubpixelAAEnabled() {
+ // Checks if anti-aliasing is enabled when scaled text is painted.
+ // On Windows GDI scaled fonts looks bad.
+ var ctx = document.createElement('canvas').getContext('2d');
+ ctx.scale(1.5, 1);
+ ctx.fillText('I', 0, 10);
+ var data = ctx.getImageData(0, 0, 10, 10).data;
+ var enabled = false;
+ for (var i = 3; i < data.length; i += 4) {
+ if (data[i] > 0 && data[i] < 255) {
+ enabled = true;
+ break;
+ }
+ }
+ return shadow(this, 'isFontSubpixelAAEnabled', enabled);
+ },
+ showText: function CanvasGraphics_showText(glyphs) {
+ var current = this.current;
+ var font = current.font;
+ if (font.isType3Font) {
+ return this.showType3Text(glyphs);
+ }
+ var fontSize = current.fontSize;
+ if (fontSize === 0) {
+ return;
+ }
+ var ctx = this.ctx;
+ var fontSizeScale = current.fontSizeScale;
+ var charSpacing = current.charSpacing;
+ var wordSpacing = current.wordSpacing;
+ var fontDirection = current.fontDirection;
+ var textHScale = current.textHScale * fontDirection;
+ var glyphsLength = glyphs.length;
+ var vertical = font.vertical;
+ var spacingDir = vertical ? 1 : -1;
+ var defaultVMetrics = font.defaultVMetrics;
+ var widthAdvanceScale = fontSize * current.fontMatrix[0];
+ var simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace;
+ ctx.save();
+ ctx.transform.apply(ctx, current.textMatrix);
+ ctx.translate(current.x, current.y + current.textRise);
+ if (current.patternFill) {
+ // TODO: Some shading patterns are not applied correctly to text,
+ // e.g. issues 3988 and 5432, and ShowText-ShadingPattern.pdf.
+ ctx.fillStyle = current.fillColor.getPattern(ctx, this);
+ }
+ if (fontDirection > 0) {
+ ctx.scale(textHScale, -1);
+ } else {
+ ctx.scale(textHScale, 1);
+ }
+ var lineWidth = current.lineWidth;
+ var scale = current.textMatrixScale;
+ if (scale === 0 || lineWidth === 0) {
+ var fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
+ if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+ this.cachedGetSinglePixelWidth = null;
+ lineWidth = this.getSinglePixelWidth() * MIN_WIDTH_FACTOR;
+ }
+ } else {
+ lineWidth /= scale;
+ }
+ if (fontSizeScale !== 1.0) {
+ ctx.scale(fontSizeScale, fontSizeScale);
+ lineWidth /= fontSizeScale;
+ }
+ ctx.lineWidth = lineWidth;
+ var x = 0, i;
+ for (i = 0; i < glyphsLength; ++i) {
+ var glyph = glyphs[i];
+ if (isNum(glyph)) {
+ x += spacingDir * glyph * fontSize / 1000;
+ continue;
+ }
+ var restoreNeeded = false;
+ var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
+ var character = glyph.fontChar;
+ var accent = glyph.accent;
+ var scaledX, scaledY, scaledAccentX, scaledAccentY;
+ var width = glyph.width;
+ if (vertical) {
+ var vmetric, vx, vy;
+ vmetric = glyph.vmetric || defaultVMetrics;
+ vx = glyph.vmetric ? vmetric[1] : width * 0.5;
+ vx = -vx * widthAdvanceScale;
+ vy = vmetric[2] * widthAdvanceScale;
+ width = vmetric ? -vmetric[0] : width;
+ scaledX = vx / fontSizeScale;
+ scaledY = (x + vy) / fontSizeScale;
+ } else {
+ scaledX = x / fontSizeScale;
+ scaledY = 0;
+ }
+ if (font.remeasure && width > 0) {
+ // Some standard fonts may not have the exact width: rescale per
+ // character if measured width is greater than expected glyph width
+ // and subpixel-aa is enabled, otherwise just center the glyph.
+ var measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
+ if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
+ var characterScaleX = width / measuredWidth;
+ restoreNeeded = true;
+ ctx.save();
+ ctx.scale(characterScaleX, 1);
+ scaledX /= characterScaleX;
+ } else if (width !== measuredWidth) {
+ scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
+ }
+ }
+ // Only attempt to draw the glyph if it is actually in the embedded font
+ // file or if there isn't a font file so the fallback font is shown.
+ if (glyph.isInFont || font.missingFile) {
+ if (simpleFillText && !accent) {
+ // common case
+ ctx.fillText(character, scaledX, scaledY);
+ } else {
+ this.paintChar(character, scaledX, scaledY);
+ if (accent) {
+ scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
+ scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
+ this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
+ }
+ }
+ }
+ var charWidth = width * widthAdvanceScale + spacing * fontDirection;
+ x += charWidth;
+ if (restoreNeeded) {
+ ctx.restore();
+ }
+ }
+ if (vertical) {
+ current.y -= x * textHScale;
+ } else {
+ current.x += x * textHScale;
+ }
+ ctx.restore();
+ },
+ showType3Text: function CanvasGraphics_showType3Text(glyphs) {
+ // Type3 fonts - each glyph is a "mini-PDF"
+ var ctx = this.ctx;
+ var current = this.current;
+ var font = current.font;
+ var fontSize = current.fontSize;
+ var fontDirection = current.fontDirection;
+ var spacingDir = font.vertical ? 1 : -1;
+ var charSpacing = current.charSpacing;
+ var wordSpacing = current.wordSpacing;
+ var textHScale = current.textHScale * fontDirection;
+ var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
+ var glyphsLength = glyphs.length;
+ var isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE;
+ var i, glyph, width, spacingLength;
+ if (isTextInvisible || fontSize === 0) {
+ return;
+ }
+ this.cachedGetSinglePixelWidth = null;
+ ctx.save();
+ ctx.transform.apply(ctx, current.textMatrix);
+ ctx.translate(current.x, current.y);
+ ctx.scale(textHScale, fontDirection);
+ for (i = 0; i < glyphsLength; ++i) {
+ glyph = glyphs[i];
+ if (isNum(glyph)) {
+ spacingLength = spacingDir * glyph * fontSize / 1000;
+ this.ctx.translate(spacingLength, 0);
+ current.x += spacingLength * textHScale;
+ continue;
+ }
+ var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
+ var operatorList = font.charProcOperatorList[glyph.operatorListId];
+ if (!operatorList) {
+ warn('Type3 character \"' + glyph.operatorListId + '\" is not available');
+ continue;
+ }
+ this.processingType3 = glyph;
+ this.save();
+ ctx.scale(fontSize, fontSize);
+ ctx.transform.apply(ctx, fontMatrix);
+ this.executeOperatorList(operatorList);
+ this.restore();
+ var transformed = Util.applyTransform([
+ glyph.width,
+ 0
+ ], fontMatrix);
+ width = transformed[0] * fontSize + spacing;
+ ctx.translate(width, 0);
+ current.x += width * textHScale;
+ }
+ ctx.restore();
+ this.processingType3 = null;
+ },
+ // Type3 fonts
+ setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) {
+ },
+ setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
+ // TODO According to the spec we're also suppose to ignore any operators
+ // that set color or include images while processing this type3 font.
+ this.ctx.rect(llx, lly, urx - llx, ury - lly);
+ this.clip();
+ this.endPath();
+ },
+ // Color
+ getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR) {
+ var pattern;
+ if (IR[0] === 'TilingPattern') {
+ var color = IR[1];
+ var baseTransform = this.baseTransform || this.ctx.mozCurrentTransform.slice();
+ var self = this;
+ var canvasGraphicsFactory = {
+ createCanvasGraphics: function (ctx) {
+ return new CanvasGraphics(ctx, self.commonObjs, self.objs);
+ }
+ };
+ pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform);
+ } else {
+ pattern = getShadingPatternFromIR(IR);
+ }
+ return pattern;
+ },
+ setStrokeColorN: function CanvasGraphics_setStrokeColorN()
+ /*...*/
+ {
+ this.current.strokeColor = this.getColorN_Pattern(arguments);
+ },
+ setFillColorN: function CanvasGraphics_setFillColorN()
+ /*...*/
+ {
+ this.current.fillColor = this.getColorN_Pattern(arguments);
+ this.current.patternFill = true;
+ },
+ setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
+ var color = Util.makeCssRgb(r, g, b);
+ this.ctx.strokeStyle = color;
+ this.current.strokeColor = color;
+ },
+ setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
+ var color = Util.makeCssRgb(r, g, b);
+ this.ctx.fillStyle = color;
+ this.current.fillColor = color;
+ this.current.patternFill = false;
+ },
+ shadingFill: function CanvasGraphics_shadingFill(patternIR) {
+ var ctx = this.ctx;
+ this.save();
+ var pattern = getShadingPatternFromIR(patternIR);
+ ctx.fillStyle = pattern.getPattern(ctx, this, true);
+ var inv = ctx.mozCurrentTransformInverse;
+ if (inv) {
+ var canvas = ctx.canvas;
+ var width = canvas.width;
+ var height = canvas.height;
+ var bl = Util.applyTransform([
+ 0,
+ 0
+ ], inv);
+ var br = Util.applyTransform([
+ 0,
+ height
+ ], inv);
+ var ul = Util.applyTransform([
+ width,
+ 0
+ ], inv);
+ var ur = Util.applyTransform([
+ width,
+ height
+ ], inv);
+ var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
+ var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
+ var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
+ var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
+ this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
+ } else {
+ // HACK to draw the gradient onto an infinite rectangle.
+ // PDF gradients are drawn across the entire image while
+ // Canvas only allows gradients to be drawn in a rectangle
+ // The following bug should allow us to remove this.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
+ this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
+ }
+ this.restore();
+ },
+ // Images
+ beginInlineImage: function CanvasGraphics_beginInlineImage() {
+ error('Should not call beginInlineImage');
+ },
+ beginImageData: function CanvasGraphics_beginImageData() {
+ error('Should not call beginImageData');
+ },
+ paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix, bbox) {
+ this.save();
+ this.baseTransformStack.push(this.baseTransform);
+ if (isArray(matrix) && 6 === matrix.length) {
+ this.transform.apply(this, matrix);
+ }
+ this.baseTransform = this.ctx.mozCurrentTransform;
+ if (isArray(bbox) && 4 === bbox.length) {
+ var width = bbox[2] - bbox[0];
+ var height = bbox[3] - bbox[1];
+ this.ctx.rect(bbox[0], bbox[1], width, height);
+ this.clip();
+ this.endPath();
+ }
+ },
+ paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
+ this.restore();
+ this.baseTransform = this.baseTransformStack.pop();
+ },
+ beginGroup: function CanvasGraphics_beginGroup(group) {
+ this.save();
+ var currentCtx = this.ctx;
+ // TODO non-isolated groups - according to Rik at adobe non-isolated
+ // group results aren't usually that different and they even have tools
+ // that ignore this setting. Notes from Rik on implementing:
+ // - When you encounter an transparency group, create a new canvas with
+ // the dimensions of the bbox
+ // - copy the content from the previous canvas to the new canvas
+ // - draw as usual
+ // - remove the backdrop alpha:
+ // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
+ // value of your transparency group and 'alphaBackdrop' the alpha of the
+ // backdrop
+ // - remove background color:
+ // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
+ if (!group.isolated) {
+ info('TODO: Support non-isolated groups.');
+ }
+ // TODO knockout - supposedly possible with the clever use of compositing
+ // modes.
+ if (group.knockout) {
+ warn('Knockout groups not supported.');
+ }
+ var currentTransform = currentCtx.mozCurrentTransform;
+ if (group.matrix) {
+ currentCtx.transform.apply(currentCtx, group.matrix);
+ }
+ assert(group.bbox, 'Bounding box is required.');
+ // Based on the current transform figure out how big the bounding box
+ // will actually be.
+ var bounds = Util.getAxialAlignedBoundingBox(group.bbox, currentCtx.mozCurrentTransform);
+ // Clip the bounding box to the current canvas.
+ var canvasBounds = [
+ 0,
+ 0,
+ currentCtx.canvas.width,
+ currentCtx.canvas.height
+ ];
+ bounds = Util.intersect(bounds, canvasBounds) || [
+ 0,
+ 0,
+ 0,
+ 0
+ ];
+ // Use ceil in case we're between sizes so we don't create canvas that is
+ // too small and make the canvas at least 1x1 pixels.
+ var offsetX = Math.floor(bounds[0]);
+ var offsetY = Math.floor(bounds[1]);
+ var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
+ var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
+ var scaleX = 1, scaleY = 1;
+ if (drawnWidth > MAX_GROUP_SIZE) {
+ scaleX = drawnWidth / MAX_GROUP_SIZE;
+ drawnWidth = MAX_GROUP_SIZE;
+ }
+ if (drawnHeight > MAX_GROUP_SIZE) {
+ scaleY = drawnHeight / MAX_GROUP_SIZE;
+ drawnHeight = MAX_GROUP_SIZE;
+ }
+ var cacheId = 'groupAt' + this.groupLevel;
+ if (group.smask) {
+ // Using two cache entries is case if masks are used one after another.
+ cacheId += '_smask_' + this.smaskCounter++ % 2;
+ }
+ var scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
+ var groupCtx = scratchCanvas.context;
+ // Since we created a new canvas that is just the size of the bounding box
+ // we have to translate the group ctx.
+ groupCtx.scale(1 / scaleX, 1 / scaleY);
+ groupCtx.translate(-offsetX, -offsetY);
+ groupCtx.transform.apply(groupCtx, currentTransform);
+ if (group.smask) {
+ // Saving state and cached mask to be used in setGState.
+ this.smaskStack.push({
+ canvas: scratchCanvas.canvas,
+ context: groupCtx,
+ offsetX: offsetX,
+ offsetY: offsetY,
+ scaleX: scaleX,
+ scaleY: scaleY,
+ subtype: group.smask.subtype,
+ backdrop: group.smask.backdrop,
+ transferMap: group.smask.transferMap || null,
+ startTransformInverse: null
+ });
+ } else
+ // used during suspend operation
+ {
+ // Setup the current ctx so when the group is popped we draw it at the
+ // right location.
+ currentCtx.setTransform(1, 0, 0, 1, 0, 0);
+ currentCtx.translate(offsetX, offsetY);
+ currentCtx.scale(scaleX, scaleY);
+ }
+ // The transparency group inherits all off the current graphics state
+ // except the blend mode, soft mask, and alpha constants.
+ copyCtxState(currentCtx, groupCtx);
+ this.ctx = groupCtx;
+ this.setGState([
+ [
+ 'BM',
+ 'Normal'
+ ],
+ [
+ 'ca',
+ 1
+ ],
+ [
+ 'CA',
+ 1
+ ]
+ ]);
+ this.groupStack.push(currentCtx);
+ this.groupLevel++;
+ // Reseting mask state, masks will be applied on restore of the group.
+ this.current.activeSMask = null;
+ },
+ endGroup: function CanvasGraphics_endGroup(group) {
+ this.groupLevel--;
+ var groupCtx = this.ctx;
+ this.ctx = this.groupStack.pop();
+ // Turn off image smoothing to avoid sub pixel interpolation which can
+ // look kind of blurry for some pdfs.
+ if (this.ctx.imageSmoothingEnabled !== undefined) {
+ this.ctx.imageSmoothingEnabled = false;
+ } else {
+ this.ctx.mozImageSmoothingEnabled = false;
+ }
+ if (group.smask) {
+ this.tempSMask = this.smaskStack.pop();
+ } else {
+ this.ctx.drawImage(groupCtx.canvas, 0, 0);
+ }
+ this.restore();
+ },
+ beginAnnotations: function CanvasGraphics_beginAnnotations() {
+ this.save();
+ this.current = new CanvasExtraState();
+ if (this.baseTransform) {
+ this.ctx.setTransform.apply(this.ctx, this.baseTransform);
+ }
+ },
+ endAnnotations: function CanvasGraphics_endAnnotations() {
+ this.restore();
+ },
+ beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform, matrix) {
+ this.save();
+ if (isArray(rect) && 4 === rect.length) {
+ var width = rect[2] - rect[0];
+ var height = rect[3] - rect[1];
+ this.ctx.rect(rect[0], rect[1], width, height);
+ this.clip();
+ this.endPath();
+ }
+ this.transform.apply(this, transform);
+ this.transform.apply(this, matrix);
+ },
+ endAnnotation: function CanvasGraphics_endAnnotation() {
+ this.restore();
+ },
+ paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
+ var domImage = this.objs.get(objId);
+ if (!domImage) {
+ warn('Dependent image isn\'t ready yet');
+ return;
+ }
+ this.save();
+ var ctx = this.ctx;
+ // scale the image to the unit square
+ ctx.scale(1 / w, -1 / h);
+ ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, 0, -h, w, h);
+ if (this.imageLayer) {
+ var currentTransform = ctx.mozCurrentTransformInverse;
+ var position = this.getCanvasPosition(0, 0);
+ this.imageLayer.appendImage({
+ objId: objId,
+ left: position[0],
+ top: position[1],
+ width: w / currentTransform[0],
+ height: h / currentTransform[3]
+ });
+ }
+ this.restore();
+ },
+ paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
+ var ctx = this.ctx;
+ var width = img.width, height = img.height;
+ var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
+ var glyph = this.processingType3;
+ if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
+ if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
+ glyph.compiled = compileType3Glyph({
+ data: img.data,
+ width: width,
+ height: height
+ });
+ } else {
+ glyph.compiled = null;
+ }
+ }
+ if (glyph && glyph.compiled) {
+ glyph.compiled(ctx);
+ return;
+ }
+ var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', width, height);
+ var maskCtx = maskCanvas.context;
+ maskCtx.save();
+ putBinaryImageMask(maskCtx, img);
+ maskCtx.globalCompositeOperation = 'source-in';
+ maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor;
+ maskCtx.fillRect(0, 0, width, height);
+ maskCtx.restore();
+ this.paintInlineImageXObject(maskCanvas.canvas);
+ },
+ paintImageMaskXObjectRepeat: function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX, scaleY, positions) {
+ var width = imgData.width;
+ var height = imgData.height;
+ var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
+ var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', width, height);
+ var maskCtx = maskCanvas.context;
+ maskCtx.save();
+ putBinaryImageMask(maskCtx, imgData);
+ maskCtx.globalCompositeOperation = 'source-in';
+ maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor;
+ maskCtx.fillRect(0, 0, width, height);
+ maskCtx.restore();
+ var ctx = this.ctx;
+ for (var i = 0, ii = positions.length; i < ii; i += 2) {
+ ctx.save();
+ ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
+ ctx.scale(1, -1);
+ ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
+ ctx.restore();
+ }
+ },
+ paintImageMaskXObjectGroup: function CanvasGraphics_paintImageMaskXObjectGroup(images) {
+ var ctx = this.ctx;
+ var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
+ for (var i = 0, ii = images.length; i < ii; i++) {
+ var image = images[i];
+ var width = image.width, height = image.height;
+ var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas', width, height);
+ var maskCtx = maskCanvas.context;
+ maskCtx.save();
+ putBinaryImageMask(maskCtx, image);
+ maskCtx.globalCompositeOperation = 'source-in';
+ maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor;
+ maskCtx.fillRect(0, 0, width, height);
+ maskCtx.restore();
+ ctx.save();
+ ctx.transform.apply(ctx, image.transform);
+ ctx.scale(1, -1);
+ ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
+ ctx.restore();
+ }
+ },
+ paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
+ var imgData = this.objs.get(objId);
+ if (!imgData) {
+ warn('Dependent image isn\'t ready yet');
+ return;
+ }
+ this.paintInlineImageXObject(imgData);
+ },
+ paintImageXObjectRepeat: function CanvasGraphics_paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
+ var imgData = this.objs.get(objId);
+ if (!imgData) {
+ warn('Dependent image isn\'t ready yet');
+ return;
+ }
+ var width = imgData.width;
+ var height = imgData.height;
+ var map = [];
+ for (var i = 0, ii = positions.length; i < ii; i += 2) {
+ map.push({
+ transform: [
+ scaleX,
+ 0,
+ 0,
+ scaleY,
+ positions[i],
+ positions[i + 1]
+ ],
+ x: 0,
+ y: 0,
+ w: width,
+ h: height
+ });
+ }
+ this.paintInlineImageXObjectGroup(imgData, map);
+ },
+ paintInlineImageXObject: function CanvasGraphics_paintInlineImageXObject(imgData) {
+ var width = imgData.width;
+ var height = imgData.height;
+ var ctx = this.ctx;
+ this.save();
+ // scale the image to the unit square
+ ctx.scale(1 / width, -1 / height);
+ var currentTransform = ctx.mozCurrentTransformInverse;
+ var a = currentTransform[0], b = currentTransform[1];
+ var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
+ var c = currentTransform[2], d = currentTransform[3];
+ var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
+ var imgToPaint, tmpCanvas;
+ // instanceof HTMLElement does not work in jsdom node.js module
+ if (imgData instanceof HTMLElement || !imgData.data) {
+ imgToPaint = imgData;
+ } else {
+ tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', width, height);
+ var tmpCtx = tmpCanvas.context;
+ putBinaryImageData(tmpCtx, imgData);
+ imgToPaint = tmpCanvas.canvas;
+ }
+ var paintWidth = width, paintHeight = height;
+ var tmpCanvasId = 'prescale1';
+ // Vertial or horizontal scaling shall not be more than 2 to not loose the
+ // pixels during drawImage operation, painting on the temporary canvas(es)
+ // that are twice smaller in size
+ while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) {
+ var newWidth = paintWidth, newHeight = paintHeight;
+ if (widthScale > 2 && paintWidth > 1) {
+ newWidth = Math.ceil(paintWidth / 2);
+ widthScale /= paintWidth / newWidth;
+ }
+ if (heightScale > 2 && paintHeight > 1) {
+ newHeight = Math.ceil(paintHeight / 2);
+ heightScale /= paintHeight / newHeight;
+ }
+ tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
+ tmpCtx = tmpCanvas.context;
+ tmpCtx.clearRect(0, 0, newWidth, newHeight);
+ tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight);
+ imgToPaint = tmpCanvas.canvas;
+ paintWidth = newWidth;
+ paintHeight = newHeight;
+ tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1';
+ }
+ ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, 0, -height, width, height);
+ if (this.imageLayer) {
+ var position = this.getCanvasPosition(0, -height);
+ this.imageLayer.appendImage({
+ imgData: imgData,
+ left: position[0],
+ top: position[1],
+ width: width / currentTransform[0],
+ height: height / currentTransform[3]
+ });
+ }
+ this.restore();
+ },
+ paintInlineImageXObjectGroup: function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
+ var ctx = this.ctx;
+ var w = imgData.width;
+ var h = imgData.height;
+ var tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', w, h);
+ var tmpCtx = tmpCanvas.context;
+ putBinaryImageData(tmpCtx, imgData);
+ for (var i = 0, ii = map.length; i < ii; i++) {
+ var entry = map[i];
+ ctx.save();
+ ctx.transform.apply(ctx, entry.transform);
+ ctx.scale(1, -1);
+ ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1);
+ if (this.imageLayer) {
+ var position = this.getCanvasPosition(entry.x, entry.y);
+ this.imageLayer.appendImage({
+ imgData: imgData,
+ left: position[0],
+ top: position[1],
+ width: w,
+ height: h
+ });
+ }
+ ctx.restore();
+ }
+ },
+ paintSolidColorImageMask: function CanvasGraphics_paintSolidColorImageMask() {
+ this.ctx.fillRect(0, 0, 1, 1);
+ },
+ paintXObject: function CanvasGraphics_paintXObject() {
+ warn('Unsupported \'paintXObject\' command.');
+ },
+ // Marked content
+ markPoint: function CanvasGraphics_markPoint(tag) {
+ },
+ markPointProps: function CanvasGraphics_markPointProps(tag, properties) {
+ },
+ beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
+ },
+ beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(tag, properties) {
+ },
+ endMarkedContent: function CanvasGraphics_endMarkedContent() {
+ },
+ // Compatibility
+ beginCompat: function CanvasGraphics_beginCompat() {
+ },
+ endCompat: function CanvasGraphics_endCompat() {
+ },
+ // Helper functions
+ consumePath: function CanvasGraphics_consumePath() {
+ var ctx = this.ctx;
+ if (this.pendingClip) {
+ if (this.pendingClip === EO_CLIP) {
+ if (ctx.mozFillRule !== undefined) {
+ ctx.mozFillRule = 'evenodd';
+ ctx.clip();
+ ctx.mozFillRule = 'nonzero';
+ } else {
+ ctx.clip('evenodd');
+ }
+ } else {
+ ctx.clip();
+ }
+ this.pendingClip = null;
+ }
+ ctx.beginPath();
+ },
+ getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
+ if (this.cachedGetSinglePixelWidth === null) {
+ // NOTE: The `save` and `restore` commands used below is a workaround
+ // that is necessary in order to prevent `mozCurrentTransformInverse`
+ // from intermittently returning incorrect values in Firefox, see:
+ // https://github.com/mozilla/pdf.js/issues/7188.
+ this.ctx.save();
+ var inverse = this.ctx.mozCurrentTransformInverse;
+ this.ctx.restore();
+ // max of the current horizontal and vertical scale
+ this.cachedGetSinglePixelWidth = Math.sqrt(Math.max(inverse[0] * inverse[0] + inverse[1] * inverse[1], inverse[2] * inverse[2] + inverse[3] * inverse[3]));
+ }
+ return this.cachedGetSinglePixelWidth;
+ },
+ getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
+ var transform = this.ctx.mozCurrentTransform;
+ return [
+ transform[0] * x + transform[2] * y + transform[4],
+ transform[1] * x + transform[3] * y + transform[5]
+ ];
+ }
+ };
+ for (var op in OPS) {
+ CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
+ }
+ return CanvasGraphics;
+ }();
+ exports.CanvasGraphics = CanvasGraphics;
+ exports.createScratchCanvas = createScratchCanvas;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayAPI = {}, root.pdfjsSharedUtil, root.pdfjsDisplayFontLoader, root.pdfjsDisplayCanvas, root.pdfjsDisplayMetadata, root.pdfjsDisplayDOMUtils);
+ }(this, function (exports, sharedUtil, displayFontLoader, displayCanvas, displayMetadata, displayDOMUtils, amdRequire) {
+ var InvalidPDFException = sharedUtil.InvalidPDFException;
+ var MessageHandler = sharedUtil.MessageHandler;
+ var MissingPDFException = sharedUtil.MissingPDFException;
+ var PageViewport = sharedUtil.PageViewport;
+ var PasswordResponses = sharedUtil.PasswordResponses;
+ var PasswordException = sharedUtil.PasswordException;
+ var StatTimer = sharedUtil.StatTimer;
+ var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
+ var UnknownErrorException = sharedUtil.UnknownErrorException;
+ var Util = sharedUtil.Util;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var error = sharedUtil.error;
+ var deprecated = sharedUtil.deprecated;
+ var getVerbosityLevel = sharedUtil.getVerbosityLevel;
+ var info = sharedUtil.info;
+ var isInt = sharedUtil.isInt;
+ var isArray = sharedUtil.isArray;
+ var isArrayBuffer = sharedUtil.isArrayBuffer;
+ var isSameOrigin = sharedUtil.isSameOrigin;
+ var loadJpegStream = sharedUtil.loadJpegStream;
+ var stringToBytes = sharedUtil.stringToBytes;
+ var globalScope = sharedUtil.globalScope;
+ var warn = sharedUtil.warn;
+ var FontFaceObject = displayFontLoader.FontFaceObject;
+ var FontLoader = displayFontLoader.FontLoader;
+ var CanvasGraphics = displayCanvas.CanvasGraphics;
+ var createScratchCanvas = displayCanvas.createScratchCanvas;
+ var Metadata = displayMetadata.Metadata;
+ var getDefaultSetting = displayDOMUtils.getDefaultSetting;
+ var DEFAULT_RANGE_CHUNK_SIZE = 65536;
+ // 2^16 = 65536
+ var isWorkerDisabled = false;
+ var workerSrc;
+ var isPostMessageTransfersDisabled = false;
+ var fakeWorkerFilesLoader = null;
+ var useRequireEnsure = false;
+ /**
+ * Document initialization / loading parameters object.
+ *
+ * @typedef {Object} DocumentInitParameters
+ * @property {string} url - The URL of the PDF.
+ * @property {TypedArray|Array|string} data - Binary PDF data. Use typed arrays
+ * (Uint8Array) to improve the memory usage. If PDF data is BASE64-encoded,
+ * use atob() to convert it to a binary string first.
+ * @property {Object} httpHeaders - Basic authentication headers.
+ * @property {boolean} withCredentials - Indicates whether or not cross-site
+ * Access-Control requests should be made using credentials such as cookies
+ * or authorization headers. The default is false.
+ * @property {string} password - For decrypting password-protected PDFs.
+ * @property {TypedArray} initialData - A typed array with the first portion or
+ * all of the pdf data. Used by the extension since some data is already
+ * loaded before the switch to range requests.
+ * @property {number} length - The PDF file length. It's used for progress
+ * reports and range requests operations.
+ * @property {PDFDataRangeTransport} range
+ * @property {number} rangeChunkSize - Optional parameter to specify
+ * maximum number of bytes fetched per range request. The default value is
+ * 2^16 = 65536.
+ * @property {PDFWorker} worker - The worker that will be used for the loading
+ * and parsing of the PDF data.
+ * @property {string} docBaseUrl - (optional) The base URL of the document,
+ * used when attempting to recover valid absolute URLs for annotations, and
+ * outline items, that (incorrectly) only specify relative URLs.
+ */
+ /**
+ * @typedef {Object} PDFDocumentStats
+ * @property {Array} streamTypes - Used stream types in the document (an item
+ * is set to true if specific stream ID was used in the document).
+ * @property {Array} fontTypes - Used font type in the document (an item is set
+ * to true if specific font ID was used in the document).
+ */
+ /**
+ * This is the main entry point for loading a PDF and interacting with it.
+ * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
+ * is used, which means it must follow the same origin rules that any XHR does
+ * e.g. No cross domain requests without CORS.
+ *
+ * @param {string|TypedArray|DocumentInitParameters|PDFDataRangeTransport} src
+ * Can be a url to where a PDF is located, a typed array (Uint8Array)
+ * already populated with data or parameter object.
+ *
+ * @param {PDFDataRangeTransport} pdfDataRangeTransport (deprecated) It is used
+ * if you want to manually serve range requests for data in the PDF.
+ *
+ * @param {function} passwordCallback (deprecated) It is used to request a
+ * password if wrong or no password was provided. The callback receives two
+ * parameters: function that needs to be called with new password and reason
+ * (see {PasswordResponses}).
+ *
+ * @param {function} progressCallback (deprecated) It is used to be able to
+ * monitor the loading progress of the PDF file (necessary to implement e.g.
+ * a loading bar). The callback receives an {Object} with the properties:
+ * {number} loaded and {number} total.
+ *
+ * @return {PDFDocumentLoadingTask}
+ */
+ function getDocument(src, pdfDataRangeTransport, passwordCallback, progressCallback) {
+ var task = new PDFDocumentLoadingTask();
+ // Support of the obsolete arguments (for compatibility with API v1.0)
+ if (arguments.length > 1) {
+ deprecated('getDocument is called with pdfDataRangeTransport, ' + 'passwordCallback or progressCallback argument');
+ }
+ if (pdfDataRangeTransport) {
+ if (!(pdfDataRangeTransport instanceof PDFDataRangeTransport)) {
+ // Not a PDFDataRangeTransport instance, trying to add missing properties.
+ pdfDataRangeTransport = Object.create(pdfDataRangeTransport);
+ pdfDataRangeTransport.length = src.length;
+ pdfDataRangeTransport.initialData = src.initialData;
+ if (!pdfDataRangeTransport.abort) {
+ pdfDataRangeTransport.abort = function () {
+ };
+ }
+ }
+ src = Object.create(src);
+ src.range = pdfDataRangeTransport;
+ }
+ task.onPassword = passwordCallback || null;
+ task.onProgress = progressCallback || null;
+ var source;
+ if (typeof src === 'string') {
+ source = { url: src };
+ } else if (isArrayBuffer(src)) {
+ source = { data: src };
+ } else if (src instanceof PDFDataRangeTransport) {
+ source = { range: src };
+ } else {
+ if (typeof src !== 'object') {
+ error('Invalid parameter in getDocument, need either Uint8Array, ' + 'string or a parameter object');
+ }
+ if (!src.url && !src.data && !src.range) {
+ error('Invalid parameter object: need either .data, .range or .url');
+ }
+ source = src;
+ }
+ var params = {};
+ var rangeTransport = null;
+ var worker = null;
+ for (var key in source) {
+ if (key === 'url' && typeof window !== 'undefined') {
+ // The full path is required in the 'url' field.
+ params[key] = new URL(source[key], window.location).href;
+ continue;
+ } else if (key === 'range') {
+ rangeTransport = source[key];
+ continue;
+ } else if (key === 'worker') {
+ worker = source[key];
+ continue;
+ } else if (key === 'data' && !(source[key] instanceof Uint8Array)) {
+ // Converting string or array-like data to Uint8Array.
+ var pdfBytes = source[key];
+ if (typeof pdfBytes === 'string') {
+ params[key] = stringToBytes(pdfBytes);
+ } else if (typeof pdfBytes === 'object' && pdfBytes !== null && !isNaN(pdfBytes.length)) {
+ params[key] = new Uint8Array(pdfBytes);
+ } else if (isArrayBuffer(pdfBytes)) {
+ params[key] = new Uint8Array(pdfBytes);
+ } else {
+ error('Invalid PDF binary data: either typed array, string or ' + 'array-like object is expected in the data property.');
+ }
+ continue;
+ }
+ params[key] = source[key];
+ }
+ params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
+ if (!worker) {
+ // Worker was not provided -- creating and owning our own.
+ worker = new PDFWorker();
+ task._worker = worker;
+ }
+ var docId = task.docId;
+ worker.promise.then(function () {
+ if (task.destroyed) {
+ throw new Error('Loading aborted');
+ }
+ return _fetchDocument(worker, params, rangeTransport, docId).then(function (workerId) {
+ if (task.destroyed) {
+ throw new Error('Loading aborted');
+ }
+ var messageHandler = new MessageHandler(docId, workerId, worker.port);
+ var transport = new WorkerTransport(messageHandler, task, rangeTransport);
+ task._transport = transport;
+ messageHandler.send('Ready', null);
+ });
+ }).catch(task._capability.reject);
+ return task;
+ }
+ /**
+ * Starts fetching of specified PDF document/data.
+ * @param {PDFWorker} worker
+ * @param {Object} source
+ * @param {PDFDataRangeTransport} pdfDataRangeTransport
+ * @param {string} docId Unique document id, used as MessageHandler id.
+ * @returns {Promise} The promise, which is resolved when worker id of
+ * MessageHandler is known.
+ * @private
+ */
+ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
+ if (worker.destroyed) {
+ return Promise.reject(new Error('Worker was destroyed'));
+ }
+ source.disableAutoFetch = getDefaultSetting('disableAutoFetch');
+ source.disableStream = getDefaultSetting('disableStream');
+ source.chunkedViewerLoading = !!pdfDataRangeTransport;
+ if (pdfDataRangeTransport) {
+ source.length = pdfDataRangeTransport.length;
+ source.initialData = pdfDataRangeTransport.initialData;
+ }
+ return worker.messageHandler.sendWithPromise('GetDocRequest', {
+ docId: docId,
+ source: source,
+ disableRange: getDefaultSetting('disableRange'),
+ maxImageSize: getDefaultSetting('maxImageSize'),
+ cMapUrl: getDefaultSetting('cMapUrl'),
+ cMapPacked: getDefaultSetting('cMapPacked'),
+ disableFontFace: getDefaultSetting('disableFontFace'),
+ disableCreateObjectURL: getDefaultSetting('disableCreateObjectURL'),
+ postMessageTransfers: getDefaultSetting('postMessageTransfers') && !isPostMessageTransfersDisabled,
+ docBaseUrl: source.docBaseUrl
+ }).then(function (workerId) {
+ if (worker.destroyed) {
+ throw new Error('Worker was destroyed');
+ }
+ return workerId;
+ });
+ }
+ /**
+ * PDF document loading operation.
+ * @class
+ * @alias PDFDocumentLoadingTask
+ */
+ var PDFDocumentLoadingTask = function PDFDocumentLoadingTaskClosure() {
+ var nextDocumentId = 0;
+ /** @constructs PDFDocumentLoadingTask */
+ function PDFDocumentLoadingTask() {
+ this._capability = createPromiseCapability();
+ this._transport = null;
+ this._worker = null;
+ /**
+ * Unique document loading task id -- used in MessageHandlers.
+ * @type {string}
+ */
+ this.docId = 'd' + nextDocumentId++;
+ /**
+ * Shows if loading task is destroyed.
+ * @type {boolean}
+ */
+ this.destroyed = false;
+ /**
+ * Callback to request a password if wrong or no password was provided.
+ * The callback receives two parameters: function that needs to be called
+ * with new password and reason (see {PasswordResponses}).
+ */
+ this.onPassword = null;
+ /**
+ * Callback to be able to monitor the loading progress of the PDF file
+ * (necessary to implement e.g. a loading bar). The callback receives
+ * an {Object} with the properties: {number} loaded and {number} total.
+ */
+ this.onProgress = null;
+ /**
+ * Callback to when unsupported feature is used. The callback receives
+ * an {UNSUPPORTED_FEATURES} argument.
+ */
+ this.onUnsupportedFeature = null;
+ }
+ PDFDocumentLoadingTask.prototype = /** @lends PDFDocumentLoadingTask.prototype */
+ {
+ /**
+ * @return {Promise}
+ */
+ get promise() {
+ return this._capability.promise;
+ },
+ /**
+ * Aborts all network requests and destroys worker.
+ * @return {Promise} A promise that is resolved after destruction activity
+ * is completed.
+ */
+ destroy: function () {
+ this.destroyed = true;
+ var transportDestroyed = !this._transport ? Promise.resolve() : this._transport.destroy();
+ return transportDestroyed.then(function () {
+ this._transport = null;
+ if (this._worker) {
+ this._worker.destroy();
+ this._worker = null;
+ }
+ }.bind(this));
+ },
+ /**
+ * Registers callbacks to indicate the document loading completion.
+ *
+ * @param {function} onFulfilled The callback for the loading completion.
+ * @param {function} onRejected The callback for the loading failure.
+ * @return {Promise} A promise that is resolved after the onFulfilled or
+ * onRejected callback.
+ */
+ then: function PDFDocumentLoadingTask_then(onFulfilled, onRejected) {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+ };
+ return PDFDocumentLoadingTask;
+ }();
+ /**
+ * Abstract class to support range requests file loading.
+ * @class
+ * @alias PDFDataRangeTransport
+ * @param {number} length
+ * @param {Uint8Array} initialData
+ */
+ var PDFDataRangeTransport = function pdfDataRangeTransportClosure() {
+ function PDFDataRangeTransport(length, initialData) {
+ this.length = length;
+ this.initialData = initialData;
+ this._rangeListeners = [];
+ this._progressListeners = [];
+ this._progressiveReadListeners = [];
+ this._readyCapability = createPromiseCapability();
+ }
+ PDFDataRangeTransport.prototype = /** @lends PDFDataRangeTransport.prototype */
+ {
+ addRangeListener: function PDFDataRangeTransport_addRangeListener(listener) {
+ this._rangeListeners.push(listener);
+ },
+ addProgressListener: function PDFDataRangeTransport_addProgressListener(listener) {
+ this._progressListeners.push(listener);
+ },
+ addProgressiveReadListener: function PDFDataRangeTransport_addProgressiveReadListener(listener) {
+ this._progressiveReadListeners.push(listener);
+ },
+ onDataRange: function PDFDataRangeTransport_onDataRange(begin, chunk) {
+ var listeners = this._rangeListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](begin, chunk);
+ }
+ },
+ onDataProgress: function PDFDataRangeTransport_onDataProgress(loaded) {
+ this._readyCapability.promise.then(function () {
+ var listeners = this._progressListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](loaded);
+ }
+ }.bind(this));
+ },
+ onDataProgressiveRead: function PDFDataRangeTransport_onDataProgress(chunk) {
+ this._readyCapability.promise.then(function () {
+ var listeners = this._progressiveReadListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](chunk);
+ }
+ }.bind(this));
+ },
+ transportReady: function PDFDataRangeTransport_transportReady() {
+ this._readyCapability.resolve();
+ },
+ requestDataRange: function PDFDataRangeTransport_requestDataRange(begin, end) {
+ throw new Error('Abstract method PDFDataRangeTransport.requestDataRange');
+ },
+ abort: function PDFDataRangeTransport_abort() {
+ }
+ };
+ return PDFDataRangeTransport;
+ }();
+ /**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ * @class
+ * @alias PDFDocumentProxy
+ */
+ var PDFDocumentProxy = function PDFDocumentProxyClosure() {
+ function PDFDocumentProxy(pdfInfo, transport, loadingTask) {
+ this.pdfInfo = pdfInfo;
+ this.transport = transport;
+ this.loadingTask = loadingTask;
+ }
+ PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */
+ {
+ /**
+ * @return {number} Total number of pages the PDF contains.
+ */
+ get numPages() {
+ return this.pdfInfo.numPages;
+ },
+ /**
+ * @return {string} A unique ID to identify a PDF. Not guaranteed to be
+ * unique.
+ */
+ get fingerprint() {
+ return this.pdfInfo.fingerprint;
+ },
+ /**
+ * @param {number} pageNumber The page number to get. The first page is 1.
+ * @return {Promise} A promise that is resolved with a {@link PDFPageProxy}
+ * object.
+ */
+ getPage: function PDFDocumentProxy_getPage(pageNumber) {
+ return this.transport.getPage(pageNumber);
+ },
+ /**
+ * @param {{num: number, gen: number}} ref The page reference. Must have
+ * the 'num' and 'gen' properties.
+ * @return {Promise} A promise that is resolved with the page index that is
+ * associated with the reference.
+ */
+ getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
+ return this.transport.getPageIndex(ref);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named destinations to reference numbers.
+ *
+ * This can be slow for large documents: use getDestination instead
+ */
+ getDestinations: function PDFDocumentProxy_getDestinations() {
+ return this.transport.getDestinations();
+ },
+ /**
+ * @param {string} id The named destination to get.
+ * @return {Promise} A promise that is resolved with all information
+ * of the given named destination.
+ */
+ getDestination: function PDFDocumentProxy_getDestination(id) {
+ return this.transport.getDestination(id);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with:
+ * an Array containing the pageLabels that correspond to the pageIndexes,
+ * or `null` when no pageLabels are present in the PDF file.
+ */
+ getPageLabels: function PDFDocumentProxy_getPageLabels() {
+ return this.transport.getPageLabels();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named attachments to their content.
+ */
+ getAttachments: function PDFDocumentProxy_getAttachments() {
+ return this.transport.getAttachments();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an array of all the
+ * JavaScript strings in the name tree.
+ */
+ getJavaScript: function PDFDocumentProxy_getJavaScript() {
+ return this.transport.getJavaScript();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {Array} that is a
+ * tree outline (if it has one) of the PDF. The tree is in the format of:
+ * [
+ * {
+ * title: string,
+ * bold: boolean,
+ * italic: boolean,
+ * color: rgb Uint8Array,
+ * dest: dest obj,
+ * url: string,
+ * items: array of more items like this
+ * },
+ * ...
+ * ].
+ */
+ getOutline: function PDFDocumentProxy_getOutline() {
+ return this.transport.getOutline();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {Object} that has
+ * info and metadata properties. Info is an {Object} filled with anything
+ * available in the information dictionary and similarly metadata is a
+ * {Metadata} object with information from the metadata section of the PDF.
+ */
+ getMetadata: function PDFDocumentProxy_getMetadata() {
+ return this.transport.getMetadata();
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a TypedArray that has
+ * the raw data from the PDF.
+ */
+ getData: function PDFDocumentProxy_getData() {
+ return this.transport.getData();
+ },
+ /**
+ * @return {Promise} A promise that is resolved when the document's data
+ * is loaded. It is resolved with an {Object} that contains the length
+ * property that indicates size of the PDF data in bytes.
+ */
+ getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() {
+ return this.transport.downloadInfoCapability.promise;
+ },
+ /**
+ * @return {Promise} A promise this is resolved with current stats about
+ * document structures (see {@link PDFDocumentStats}).
+ */
+ getStats: function PDFDocumentProxy_getStats() {
+ return this.transport.getStats();
+ },
+ /**
+ * Cleans up resources allocated by the document, e.g. created @font-face.
+ */
+ cleanup: function PDFDocumentProxy_cleanup() {
+ this.transport.startCleanup();
+ },
+ /**
+ * Destroys current document instance and terminates worker.
+ */
+ destroy: function PDFDocumentProxy_destroy() {
+ return this.loadingTask.destroy();
+ }
+ };
+ return PDFDocumentProxy;
+ }();
+ /**
+ * Page getTextContent parameters.
+ *
+ * @typedef {Object} getTextContentParameters
+ * @property {boolean} normalizeWhitespace - replaces all occurrences of
+ * whitespace with standard spaces (0x20). The default value is `false`.
+ * @property {boolean} disableCombineTextItems - do not attempt to combine
+ * same line {@link TextItem}'s. The default value is `false`.
+ */
+ /**
+ * Page text content.
+ *
+ * @typedef {Object} TextContent
+ * @property {array} items - array of {@link TextItem}
+ * @property {Object} styles - {@link TextStyles} objects, indexed by font name.
+ */
+ /**
+ * Page text content part.
+ *
+ * @typedef {Object} TextItem
+ * @property {string} str - text content.
+ * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'.
+ * @property {array} transform - transformation matrix.
+ * @property {number} width - width in device space.
+ * @property {number} height - height in device space.
+ * @property {string} fontName - font name used by pdf.js for converted font.
+ */
+ /**
+ * Text style.
+ *
+ * @typedef {Object} TextStyle
+ * @property {number} ascent - font ascent.
+ * @property {number} descent - font descent.
+ * @property {boolean} vertical - text is in vertical mode.
+ * @property {string} fontFamily - possible font family
+ */
+ /**
+ * Page annotation parameters.
+ *
+ * @typedef {Object} GetAnnotationsParameters
+ * @property {string} intent - Determines the annotations that will be fetched,
+ * can be either 'display' (viewable annotations) or 'print'
+ * (printable annotations).
+ * If the parameter is omitted, all annotations are fetched.
+ */
+ /**
+ * Page render parameters.
+ *
+ * @typedef {Object} RenderParameters
+ * @property {Object} canvasContext - A 2D context of a DOM Canvas object.
+ * @property {PageViewport} viewport - Rendering viewport obtained by
+ * calling of PDFPage.getViewport method.
+ * @property {string} intent - Rendering intent, can be 'display' or 'print'
+ * (default value is 'display').
+ * @property {boolean} renderInteractiveForms - (optional) Whether or not
+ * interactive form elements are rendered in the display
+ * layer. If so, we do not render them on canvas as well.
+ * @property {Array} transform - (optional) Additional transform, applied
+ * just before viewport transform.
+ * @property {Object} imageLayer - (optional) An object that has beginLayout,
+ * endLayout and appendImage functions.
+ * @property {function} continueCallback - (deprecated) A function that will be
+ * called each time the rendering is paused. To continue
+ * rendering call the function that is the first argument
+ * to the callback.
+ */
+ /**
+ * PDF page operator list.
+ *
+ * @typedef {Object} PDFOperatorList
+ * @property {Array} fnArray - Array containing the operator functions.
+ * @property {Array} argsArray - Array containing the arguments of the
+ * functions.
+ */
+ /**
+ * Proxy to a PDFPage in the worker thread.
+ * @class
+ * @alias PDFPageProxy
+ */
+ var PDFPageProxy = function PDFPageProxyClosure() {
+ function PDFPageProxy(pageIndex, pageInfo, transport) {
+ this.pageIndex = pageIndex;
+ this.pageInfo = pageInfo;
+ this.transport = transport;
+ this.stats = new StatTimer();
+ this.stats.enabled = getDefaultSetting('enableStats');
+ this.commonObjs = transport.commonObjs;
+ this.objs = new PDFObjects();
+ this.cleanupAfterRender = false;
+ this.pendingCleanup = false;
+ this.intentStates = Object.create(null);
+ this.destroyed = false;
+ }
+ PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */
+ {
+ /**
+ * @return {number} Page number of the page. First page is 1.
+ */
+ get pageNumber() {
+ return this.pageIndex + 1;
+ },
+ /**
+ * @return {number} The number of degrees the page is rotated clockwise.
+ */
+ get rotate() {
+ return this.pageInfo.rotate;
+ },
+ /**
+ * @return {Object} The reference that points to this page. It has 'num' and
+ * 'gen' properties.
+ */
+ get ref() {
+ return this.pageInfo.ref;
+ },
+ /**
+ * @return {Array} An array of the visible portion of the PDF page in the
+ * user space units - [x1, y1, x2, y2].
+ */
+ get view() {
+ return this.pageInfo.view;
+ },
+ /**
+ * @param {number} scale The desired scale of the viewport.
+ * @param {number} rotate Degrees to rotate the viewport. If omitted this
+ * defaults to the page rotation.
+ * @return {PageViewport} Contains 'width' and 'height' properties
+ * along with transforms required for rendering.
+ */
+ getViewport: function PDFPageProxy_getViewport(scale, rotate) {
+ if (arguments.length < 2) {
+ rotate = this.rotate;
+ }
+ return new PageViewport(this.view, scale, rotate, 0, 0);
+ },
+ /**
+ * @param {GetAnnotationsParameters} params - Annotation parameters.
+ * @return {Promise} A promise that is resolved with an {Array} of the
+ * annotation objects.
+ */
+ getAnnotations: function PDFPageProxy_getAnnotations(params) {
+ var intent = params && params.intent || null;
+ if (!this.annotationsPromise || this.annotationsIntent !== intent) {
+ this.annotationsPromise = this.transport.getAnnotations(this.pageIndex, intent);
+ this.annotationsIntent = intent;
+ }
+ return this.annotationsPromise;
+ },
+ /**
+ * Begins the process of rendering a page to the desired context.
+ * @param {RenderParameters} params Page render parameters.
+ * @return {RenderTask} An object that contains the promise, which
+ * is resolved when the page finishes rendering.
+ */
+ render: function PDFPageProxy_render(params) {
+ var stats = this.stats;
+ stats.time('Overall');
+ // If there was a pending destroy cancel it so no cleanup happens during
+ // this call to render.
+ this.pendingCleanup = false;
+ var renderingIntent = params.intent === 'print' ? 'print' : 'display';
+ var renderInteractiveForms = params.renderInteractiveForms === true ? true : /* Default */
+ false;
+ if (!this.intentStates[renderingIntent]) {
+ this.intentStates[renderingIntent] = Object.create(null);
+ }
+ var intentState = this.intentStates[renderingIntent];
+ // If there's no displayReadyCapability yet, then the operatorList
+ // was never requested before. Make the request and create the promise.
+ if (!intentState.displayReadyCapability) {
+ intentState.receivingOperatorList = true;
+ intentState.displayReadyCapability = createPromiseCapability();
+ intentState.operatorList = {
+ fnArray: [],
+ argsArray: [],
+ lastChunk: false
+ };
+ this.stats.time('Page Request');
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageNumber - 1,
+ intent: renderingIntent,
+ renderInteractiveForms: renderInteractiveForms
+ });
+ }
+ var internalRenderTask = new InternalRenderTask(complete, params, this.objs, this.commonObjs, intentState.operatorList, this.pageNumber);
+ internalRenderTask.useRequestAnimationFrame = renderingIntent !== 'print';
+ if (!intentState.renderTasks) {
+ intentState.renderTasks = [];
+ }
+ intentState.renderTasks.push(internalRenderTask);
+ var renderTask = internalRenderTask.task;
+ // Obsolete parameter support
+ if (params.continueCallback) {
+ deprecated('render is used with continueCallback parameter');
+ renderTask.onContinue = params.continueCallback;
+ }
+ var self = this;
+ intentState.displayReadyCapability.promise.then(function pageDisplayReadyPromise(transparency) {
+ if (self.pendingCleanup) {
+ complete();
+ return;
+ }
+ stats.time('Rendering');
+ internalRenderTask.initializeGraphics(transparency);
+ internalRenderTask.operatorListChanged();
+ }, function pageDisplayReadPromiseError(reason) {
+ complete(reason);
+ });
+ function complete(error) {
+ var i = intentState.renderTasks.indexOf(internalRenderTask);
+ if (i >= 0) {
+ intentState.renderTasks.splice(i, 1);
+ }
+ if (self.cleanupAfterRender) {
+ self.pendingCleanup = true;
+ }
+ self._tryCleanup();
+ if (error) {
+ internalRenderTask.capability.reject(error);
+ } else {
+ internalRenderTask.capability.resolve();
+ }
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
+ }
+ return renderTask;
+ },
+ /**
+ * @return {Promise} A promise resolved with an {@link PDFOperatorList}
+ * object that represents page's operator list.
+ */
+ getOperatorList: function PDFPageProxy_getOperatorList() {
+ function operatorListChanged() {
+ if (intentState.operatorList.lastChunk) {
+ intentState.opListReadCapability.resolve(intentState.operatorList);
+ var i = intentState.renderTasks.indexOf(opListTask);
+ if (i >= 0) {
+ intentState.renderTasks.splice(i, 1);
+ }
+ }
+ }
+ var renderingIntent = 'oplist';
+ if (!this.intentStates[renderingIntent]) {
+ this.intentStates[renderingIntent] = Object.create(null);
+ }
+ var intentState = this.intentStates[renderingIntent];
+ var opListTask;
+ if (!intentState.opListReadCapability) {
+ opListTask = {};
+ opListTask.operatorListChanged = operatorListChanged;
+ intentState.receivingOperatorList = true;
+ intentState.opListReadCapability = createPromiseCapability();
+ intentState.renderTasks = [];
+ intentState.renderTasks.push(opListTask);
+ intentState.operatorList = {
+ fnArray: [],
+ argsArray: [],
+ lastChunk: false
+ };
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageIndex,
+ intent: renderingIntent
+ });
+ }
+ return intentState.opListReadCapability.promise;
+ },
+ /**
+ * @param {getTextContentParameters} params - getTextContent parameters.
+ * @return {Promise} That is resolved a {@link TextContent}
+ * object that represent the page text content.
+ */
+ getTextContent: function PDFPageProxy_getTextContent(params) {
+ return this.transport.messageHandler.sendWithPromise('GetTextContent', {
+ pageIndex: this.pageNumber - 1,
+ normalizeWhitespace: params && params.normalizeWhitespace === true ? true : /* Default */
+ false,
+ combineTextItems: params && params.disableCombineTextItems === true ? false : /* Default */
+ true
+ });
+ },
+ /**
+ * Destroys page object.
+ */
+ _destroy: function PDFPageProxy_destroy() {
+ this.destroyed = true;
+ this.transport.pageCache[this.pageIndex] = null;
+ var waitOn = [];
+ Object.keys(this.intentStates).forEach(function (intent) {
+ if (intent === 'oplist') {
+ // Avoid errors below, since the renderTasks are just stubs.
+ return;
+ }
+ var intentState = this.intentStates[intent];
+ intentState.renderTasks.forEach(function (renderTask) {
+ var renderCompleted = renderTask.capability.promise.catch(function () {
+ });
+ // ignoring failures
+ waitOn.push(renderCompleted);
+ renderTask.cancel();
+ });
+ }, this);
+ this.objs.clear();
+ this.annotationsPromise = null;
+ this.pendingCleanup = false;
+ return Promise.all(waitOn);
+ },
+ /**
+ * Cleans up resources allocated by the page. (deprecated)
+ */
+ destroy: function () {
+ deprecated('page destroy method, use cleanup() instead');
+ this.cleanup();
+ },
+ /**
+ * Cleans up resources allocated by the page.
+ */
+ cleanup: function PDFPageProxy_cleanup() {
+ this.pendingCleanup = true;
+ this._tryCleanup();
+ },
+ /**
+ * For internal use only. Attempts to clean up if rendering is in a state
+ * where that's possible.
+ * @ignore
+ */
+ _tryCleanup: function PDFPageProxy_tryCleanup() {
+ if (!this.pendingCleanup || Object.keys(this.intentStates).some(function (intent) {
+ var intentState = this.intentStates[intent];
+ return intentState.renderTasks.length !== 0 || intentState.receivingOperatorList;
+ }, this)) {
+ return;
+ }
+ Object.keys(this.intentStates).forEach(function (intent) {
+ delete this.intentStates[intent];
+ }, this);
+ this.objs.clear();
+ this.annotationsPromise = null;
+ this.pendingCleanup = false;
+ },
+ /**
+ * For internal use only.
+ * @ignore
+ */
+ _startRenderPage: function PDFPageProxy_startRenderPage(transparency, intent) {
+ var intentState = this.intentStates[intent];
+ // TODO Refactor RenderPageRequest to separate rendering
+ // and operator list logic
+ if (intentState.displayReadyCapability) {
+ intentState.displayReadyCapability.resolve(transparency);
+ }
+ },
+ /**
+ * For internal use only.
+ * @ignore
+ */
+ _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk, intent) {
+ var intentState = this.intentStates[intent];
+ var i, ii;
+ // Add the new chunk to the current operator list.
+ for (i = 0, ii = operatorListChunk.length; i < ii; i++) {
+ intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
+ intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
+ }
+ intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
+ // Notify all the rendering tasks there are more operators to be consumed.
+ for (i = 0; i < intentState.renderTasks.length; i++) {
+ intentState.renderTasks[i].operatorListChanged();
+ }
+ if (operatorListChunk.lastChunk) {
+ intentState.receivingOperatorList = false;
+ this._tryCleanup();
+ }
+ }
+ };
+ return PDFPageProxy;
+ }();
+ /**
+ * PDF.js web worker abstraction, it controls instantiation of PDF documents and
+ * WorkerTransport for them. If creation of a web worker is not possible,
+ * a "fake" worker will be used instead.
+ * @class
+ */
+ var PDFWorker = function PDFWorkerClosure() {
+ var nextFakeWorkerId = 0;
+ function getWorkerSrc() {
+ if (typeof workerSrc !== 'undefined') {
+ return workerSrc;
+ }
+ if (getDefaultSetting('workerSrc')) {
+ return getDefaultSetting('workerSrc');
+ }
+ error('No PDFJS.workerSrc specified');
+ }
+ var fakeWorkerFilesLoadedCapability;
+ // Loads worker code into main thread.
+ function setupFakeWorkerGlobal() {
+ var WorkerMessageHandler;
+ if (fakeWorkerFilesLoadedCapability) {
+ return fakeWorkerFilesLoadedCapability.promise;
+ }
+ fakeWorkerFilesLoadedCapability = createPromiseCapability();
+ var loader = fakeWorkerFilesLoader || function (callback) {
+ Util.loadScript(getWorkerSrc(), function () {
+ callback(window.pdfjsDistBuildPdfWorker.WorkerMessageHandler);
+ });
+ };
+ loader(fakeWorkerFilesLoadedCapability.resolve);
+ return fakeWorkerFilesLoadedCapability.promise;
+ }
+ function FakeWorkerPort(defer) {
+ this._listeners = [];
+ this._defer = defer;
+ this._deferred = Promise.resolve(undefined);
+ }
+ FakeWorkerPort.prototype = {
+ postMessage: function (obj, transfers) {
+ function cloneValue(value) {
+ // Trying to perform a structured clone close to the spec, including
+ // transfers.
+ if (typeof value !== 'object' || value === null) {
+ return value;
+ }
+ if (cloned.has(value)) {
+ // already cloned the object
+ return cloned.get(value);
+ }
+ var result;
+ var buffer;
+ if ((buffer = value.buffer) && isArrayBuffer(buffer)) {
+ // We found object with ArrayBuffer (typed array).
+ var transferable = transfers && transfers.indexOf(buffer) >= 0;
+ if (value === buffer) {
+ // Special case when we are faking typed arrays in compatibility.js.
+ result = value;
+ } else if (transferable) {
+ result = new value.constructor(buffer, value.byteOffset, value.byteLength);
+ } else {
+ result = new value.constructor(value);
+ }
+ cloned.set(value, result);
+ return result;
+ }
+ result = isArray(value) ? [] : {};
+ cloned.set(value, result);
+ // adding to cache now for cyclic references
+ // Cloning all value and object properties, however ignoring properties
+ // defined via getter.
+ for (var i in value) {
+ var desc, p = value;
+ while (!(desc = Object.getOwnPropertyDescriptor(p, i))) {
+ p = Object.getPrototypeOf(p);
+ }
+ if (typeof desc.value === 'undefined' || typeof desc.value === 'function') {
+ continue;
+ }
+ result[i] = cloneValue(desc.value);
+ }
+ return result;
+ }
+ if (!this._defer) {
+ this._listeners.forEach(function (listener) {
+ listener.call(this, { data: obj });
+ }, this);
+ return;
+ }
+ var cloned = new WeakMap();
+ var e = { data: cloneValue(obj) };
+ this._deferred.then(function () {
+ this._listeners.forEach(function (listener) {
+ listener.call(this, e);
+ }, this);
+ }.bind(this));
+ },
+ addEventListener: function (name, listener) {
+ this._listeners.push(listener);
+ },
+ removeEventListener: function (name, listener) {
+ var i = this._listeners.indexOf(listener);
+ this._listeners.splice(i, 1);
+ },
+ terminate: function () {
+ this._listeners = [];
+ }
+ };
+ function createCDNWrapper(url) {
+ // We will rely on blob URL's property to specify origin.
+ // We want this function to fail in case if createObjectURL or Blob do not
+ // exist or fail for some reason -- our Worker creation will fail anyway.
+ var wrapper = 'importScripts(\'' + url + '\');';
+ return URL.createObjectURL(new Blob([wrapper]));
+ }
+ function PDFWorker(name) {
+ this.name = name;
+ this.destroyed = false;
+ this._readyCapability = createPromiseCapability();
+ this._port = null;
+ this._webWorker = null;
+ this._messageHandler = null;
+ this._initialize();
+ }
+ PDFWorker.prototype = /** @lends PDFWorker.prototype */
+ {
+ get promise() {
+ return this._readyCapability.promise;
+ },
+ get port() {
+ return this._port;
+ },
+ get messageHandler() {
+ return this._messageHandler;
+ },
+ _initialize: function PDFWorker_initialize() {
+ // If worker support isn't disabled explicit and the browser has worker
+ // support, create a new web worker and test if it/the browser fulfills
+ // all requirements to run parts of pdf.js in a web worker.
+ // Right now, the requirement is, that an Uint8Array is still an
+ // Uint8Array as it arrives on the worker. (Chrome added this with v.15.)
+ if (!isWorkerDisabled && !getDefaultSetting('disableWorker') && typeof Worker !== 'undefined') {
+ var workerSrc = getWorkerSrc();
+ try {
+ // Some versions of FF can't create a worker on localhost, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+ var worker = new Worker(workerSrc);
+ var messageHandler = new MessageHandler('main', 'worker', worker);
+ var terminateEarly = function () {
+ worker.removeEventListener('error', onWorkerError);
+ messageHandler.destroy();
+ worker.terminate();
+ if (this.destroyed) {
+ this._readyCapability.reject(new Error('Worker was destroyed'));
+ } else {
+ // Fall back to fake worker if the termination is caused by an
+ // error (e.g. NetworkError / SecurityError).
+ this._setupFakeWorker();
+ }
+ }.bind(this);
+ var onWorkerError = function (event) {
+ if (!this._webWorker) {
+ // Worker failed to initialize due to an error. Clean up and fall
+ // back to the fake worker.
+ terminateEarly();
+ }
+ }.bind(this);
+ worker.addEventListener('error', onWorkerError);
+ messageHandler.on('test', function PDFWorker_test(data) {
+ worker.removeEventListener('error', onWorkerError);
+ if (this.destroyed) {
+ terminateEarly();
+ return;
+ }
+ // worker was destroyed
+ var supportTypedArray = data && data.supportTypedArray;
+ if (supportTypedArray) {
+ this._messageHandler = messageHandler;
+ this._port = worker;
+ this._webWorker = worker;
+ if (!data.supportTransfers) {
+ isPostMessageTransfersDisabled = true;
+ }
+ this._readyCapability.resolve();
+ // Send global setting, e.g. verbosity level.
+ messageHandler.send('configure', { verbosity: getVerbosityLevel() });
+ } else {
+ this._setupFakeWorker();
+ messageHandler.destroy();
+ worker.terminate();
+ }
+ }.bind(this));
+ messageHandler.on('console_log', function (data) {
+ console.log.apply(console, data);
+ });
+ messageHandler.on('console_error', function (data) {
+ console.error.apply(console, data);
+ });
+ messageHandler.on('ready', function (data) {
+ worker.removeEventListener('error', onWorkerError);
+ if (this.destroyed) {
+ terminateEarly();
+ return;
+ }
+ // worker was destroyed
+ try {
+ sendTest();
+ } catch (e) {
+ // We need fallback to a faked worker.
+ this._setupFakeWorker();
+ }
+ }.bind(this));
+ var sendTest = function () {
+ var postMessageTransfers = getDefaultSetting('postMessageTransfers') && !isPostMessageTransfersDisabled;
+ var testObj = new Uint8Array([postMessageTransfers ? 255 : 0]);
+ // Some versions of Opera throw a DATA_CLONE_ERR on serializing the
+ // typed array. Also, checking if we can use transfers.
+ try {
+ messageHandler.send('test', testObj, [testObj.buffer]);
+ } catch (ex) {
+ info('Cannot use postMessage transfers');
+ testObj[0] = 0;
+ messageHandler.send('test', testObj);
+ }
+ };
+ // It might take time for worker to initialize (especially when AMD
+ // loader is used). We will try to send test immediately, and then
+ // when 'ready' message will arrive. The worker shall process only
+ // first received 'test'.
+ sendTest();
+ return;
+ } catch (e) {
+ info('The worker has been disabled.');
+ }
+ }
+ // Either workers are disabled, not supported or have thrown an exception.
+ // Thus, we fallback to a faked worker.
+ this._setupFakeWorker();
+ },
+ _setupFakeWorker: function PDFWorker_setupFakeWorker() {
+ if (!isWorkerDisabled && !getDefaultSetting('disableWorker')) {
+ warn('Setting up fake worker.');
+ isWorkerDisabled = true;
+ }
+ setupFakeWorkerGlobal().then(function (WorkerMessageHandler) {
+ if (this.destroyed) {
+ this._readyCapability.reject(new Error('Worker was destroyed'));
+ return;
+ }
+ // We cannot turn on proper fake port simulation (this includes
+ // structured cloning) when typed arrays are not supported. Relying
+ // on a chance that messages will be sent in proper order.
+ var isTypedArraysPresent = Uint8Array !== Float32Array;
+ var port = new FakeWorkerPort(isTypedArraysPresent);
+ this._port = port;
+ // All fake workers use the same port, making id unique.
+ var id = 'fake' + nextFakeWorkerId++;
+ // If the main thread is our worker, setup the handling for the
+ // messages -- the main thread sends to it self.
+ var workerHandler = new MessageHandler(id + '_worker', id, port);
+ WorkerMessageHandler.setup(workerHandler, port);
+ var messageHandler = new MessageHandler(id, id + '_worker', port);
+ this._messageHandler = messageHandler;
+ this._readyCapability.resolve();
+ }.bind(this));
+ },
+ /**
+ * Destroys the worker instance.
+ */
+ destroy: function PDFWorker_destroy() {
+ this.destroyed = true;
+ if (this._webWorker) {
+ // We need to terminate only web worker created resource.
+ this._webWorker.terminate();
+ this._webWorker = null;
+ }
+ this._port = null;
+ if (this._messageHandler) {
+ this._messageHandler.destroy();
+ this._messageHandler = null;
+ }
+ }
+ };
+ return PDFWorker;
+ }();
+ /**
+ * For internal use only.
+ * @ignore
+ */
+ var WorkerTransport = function WorkerTransportClosure() {
+ function WorkerTransport(messageHandler, loadingTask, pdfDataRangeTransport) {
+ this.messageHandler = messageHandler;
+ this.loadingTask = loadingTask;
+ this.pdfDataRangeTransport = pdfDataRangeTransport;
+ this.commonObjs = new PDFObjects();
+ this.fontLoader = new FontLoader(loadingTask.docId);
+ this.destroyed = false;
+ this.destroyCapability = null;
+ this.pageCache = [];
+ this.pagePromises = [];
+ this.downloadInfoCapability = createPromiseCapability();
+ this.setupMessageHandler();
+ }
+ WorkerTransport.prototype = {
+ destroy: function WorkerTransport_destroy() {
+ if (this.destroyCapability) {
+ return this.destroyCapability.promise;
+ }
+ this.destroyed = true;
+ this.destroyCapability = createPromiseCapability();
+ var waitOn = [];
+ // We need to wait for all renderings to be completed, e.g.
+ // timeout/rAF can take a long time.
+ this.pageCache.forEach(function (page) {
+ if (page) {
+ waitOn.push(page._destroy());
+ }
+ });
+ this.pageCache = [];
+ this.pagePromises = [];
+ var self = this;
+ // We also need to wait for the worker to finish its long running tasks.
+ var terminated = this.messageHandler.sendWithPromise('Terminate', null);
+ waitOn.push(terminated);
+ Promise.all(waitOn).then(function () {
+ self.fontLoader.clear();
+ if (self.pdfDataRangeTransport) {
+ self.pdfDataRangeTransport.abort();
+ self.pdfDataRangeTransport = null;
+ }
+ if (self.messageHandler) {
+ self.messageHandler.destroy();
+ self.messageHandler = null;
+ }
+ self.destroyCapability.resolve();
+ }, this.destroyCapability.reject);
+ return this.destroyCapability.promise;
+ },
+ setupMessageHandler: function WorkerTransport_setupMessageHandler() {
+ var messageHandler = this.messageHandler;
+ function updatePassword(password) {
+ messageHandler.send('UpdatePassword', password);
+ }
+ var pdfDataRangeTransport = this.pdfDataRangeTransport;
+ if (pdfDataRangeTransport) {
+ pdfDataRangeTransport.addRangeListener(function (begin, chunk) {
+ messageHandler.send('OnDataRange', {
+ begin: begin,
+ chunk: chunk
+ });
+ });
+ pdfDataRangeTransport.addProgressListener(function (loaded) {
+ messageHandler.send('OnDataProgress', { loaded: loaded });
+ });
+ pdfDataRangeTransport.addProgressiveReadListener(function (chunk) {
+ messageHandler.send('OnDataRange', { chunk: chunk });
+ });
+ messageHandler.on('RequestDataRange', function transportDataRange(data) {
+ pdfDataRangeTransport.requestDataRange(data.begin, data.end);
+ }, this);
+ }
+ messageHandler.on('GetDoc', function transportDoc(data) {
+ var pdfInfo = data.pdfInfo;
+ this.numPages = data.pdfInfo.numPages;
+ var loadingTask = this.loadingTask;
+ var pdfDocument = new PDFDocumentProxy(pdfInfo, this, loadingTask);
+ this.pdfDocument = pdfDocument;
+ loadingTask._capability.resolve(pdfDocument);
+ }, this);
+ messageHandler.on('NeedPassword', function transportNeedPassword(exception) {
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onPassword) {
+ return loadingTask.onPassword(updatePassword, PasswordResponses.NEED_PASSWORD);
+ }
+ loadingTask._capability.reject(new PasswordException(exception.message, exception.code));
+ }, this);
+ messageHandler.on('IncorrectPassword', function transportIncorrectPassword(exception) {
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onPassword) {
+ return loadingTask.onPassword(updatePassword, PasswordResponses.INCORRECT_PASSWORD);
+ }
+ loadingTask._capability.reject(new PasswordException(exception.message, exception.code));
+ }, this);
+ messageHandler.on('InvalidPDF', function transportInvalidPDF(exception) {
+ this.loadingTask._capability.reject(new InvalidPDFException(exception.message));
+ }, this);
+ messageHandler.on('MissingPDF', function transportMissingPDF(exception) {
+ this.loadingTask._capability.reject(new MissingPDFException(exception.message));
+ }, this);
+ messageHandler.on('UnexpectedResponse', function transportUnexpectedResponse(exception) {
+ this.loadingTask._capability.reject(new UnexpectedResponseException(exception.message, exception.status));
+ }, this);
+ messageHandler.on('UnknownError', function transportUnknownError(exception) {
+ this.loadingTask._capability.reject(new UnknownErrorException(exception.message, exception.details));
+ }, this);
+ messageHandler.on('DataLoaded', function transportPage(data) {
+ this.downloadInfoCapability.resolve(data);
+ }, this);
+ messageHandler.on('PDFManagerReady', function transportPage(data) {
+ if (this.pdfDataRangeTransport) {
+ this.pdfDataRangeTransport.transportReady();
+ }
+ }, this);
+ messageHandler.on('StartRenderPage', function transportRender(data) {
+ if (this.destroyed) {
+ return;
+ }
+ // Ignore any pending requests if the worker was terminated.
+ var page = this.pageCache[data.pageIndex];
+ page.stats.timeEnd('Page Request');
+ page._startRenderPage(data.transparency, data.intent);
+ }, this);
+ messageHandler.on('RenderPageChunk', function transportRender(data) {
+ if (this.destroyed) {
+ return;
+ }
+ // Ignore any pending requests if the worker was terminated.
+ var page = this.pageCache[data.pageIndex];
+ page._renderPageChunk(data.operatorList, data.intent);
+ }, this);
+ messageHandler.on('commonobj', function transportObj(data) {
+ if (this.destroyed) {
+ return;
+ }
+ // Ignore any pending requests if the worker was terminated.
+ var id = data[0];
+ var type = data[1];
+ if (this.commonObjs.hasData(id)) {
+ return;
+ }
+ switch (type) {
+ case 'Font':
+ var exportedData = data[2];
+ if ('error' in exportedData) {
+ var exportedError = exportedData.error;
+ warn('Error during font loading: ' + exportedError);
+ this.commonObjs.resolve(id, exportedError);
+ break;
+ }
+ var fontRegistry = null;
+ if (getDefaultSetting('pdfBug') && globalScope.FontInspector && globalScope['FontInspector'].enabled) {
+ fontRegistry = {
+ registerFont: function (font, url) {
+ globalScope['FontInspector'].fontAdded(font, url);
+ }
+ };
+ }
+ var font = new FontFaceObject(exportedData, {
+ isEvalSuported: getDefaultSetting('isEvalSupported'),
+ disableFontFace: getDefaultSetting('disableFontFace'),
+ fontRegistry: fontRegistry
+ });
+ this.fontLoader.bind([font], function fontReady(fontObjs) {
+ this.commonObjs.resolve(id, font);
+ }.bind(this));
+ break;
+ case 'FontPath':
+ this.commonObjs.resolve(id, data[2]);
+ break;
+ default:
+ error('Got unknown common object type ' + type);
+ }
+ }, this);
+ messageHandler.on('obj', function transportObj(data) {
+ if (this.destroyed) {
+ return;
+ }
+ // Ignore any pending requests if the worker was terminated.
+ var id = data[0];
+ var pageIndex = data[1];
+ var type = data[2];
+ var pageProxy = this.pageCache[pageIndex];
+ var imageData;
+ if (pageProxy.objs.hasData(id)) {
+ return;
+ }
+ switch (type) {
+ case 'JpegStream':
+ imageData = data[3];
+ loadJpegStream(id, imageData, pageProxy.objs);
+ break;
+ case 'Image':
+ imageData = data[3];
+ pageProxy.objs.resolve(id, imageData);
+ // heuristics that will allow not to store large data
+ var MAX_IMAGE_SIZE_TO_STORE = 8000000;
+ if (imageData && 'data' in imageData && imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) {
+ pageProxy.cleanupAfterRender = true;
+ }
+ break;
+ default:
+ error('Got unknown object type ' + type);
+ }
+ }, this);
+ messageHandler.on('DocProgress', function transportDocProgress(data) {
+ if (this.destroyed) {
+ return;
+ }
+ // Ignore any pending requests if the worker was terminated.
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onProgress) {
+ loadingTask.onProgress({
+ loaded: data.loaded,
+ total: data.total
+ });
+ }
+ }, this);
+ messageHandler.on('PageError', function transportError(data) {
+ if (this.destroyed) {
+ return;
+ }
+ // Ignore any pending requests if the worker was terminated.
+ var page = this.pageCache[data.pageNum - 1];
+ var intentState = page.intentStates[data.intent];
+ if (intentState.displayReadyCapability) {
+ intentState.displayReadyCapability.reject(data.error);
+ } else {
+ error(data.error);
+ }
+ if (intentState.operatorList) {
+ // Mark operator list as complete.
+ intentState.operatorList.lastChunk = true;
+ for (var i = 0; i < intentState.renderTasks.length; i++) {
+ intentState.renderTasks[i].operatorListChanged();
+ }
+ }
+ }, this);
+ messageHandler.on('UnsupportedFeature', function transportUnsupportedFeature(data) {
+ if (this.destroyed) {
+ return;
+ }
+ // Ignore any pending requests if the worker was terminated.
+ var featureId = data.featureId;
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onUnsupportedFeature) {
+ loadingTask.onUnsupportedFeature(featureId);
+ }
+ _UnsupportedManager.notify(featureId);
+ }, this);
+ messageHandler.on('JpegDecode', function (data) {
+ if (this.destroyed) {
+ return Promise.reject(new Error('Worker was destroyed'));
+ }
+ var imageUrl = data[0];
+ var components = data[1];
+ if (components !== 3 && components !== 1) {
+ return Promise.reject(new Error('Only 3 components or 1 component can be returned'));
+ }
+ return new Promise(function (resolve, reject) {
+ var img = new Image();
+ img.onload = function () {
+ var width = img.width;
+ var height = img.height;
+ var size = width * height;
+ var rgbaLength = size * 4;
+ var buf = new Uint8Array(size * components);
+ var tmpCanvas = createScratchCanvas(width, height);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ tmpCtx.drawImage(img, 0, 0);
+ var data = tmpCtx.getImageData(0, 0, width, height).data;
+ var i, j;
+ if (components === 3) {
+ for (i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
+ buf[j] = data[i];
+ buf[j + 1] = data[i + 1];
+ buf[j + 2] = data[i + 2];
+ }
+ } else if (components === 1) {
+ for (i = 0, j = 0; i < rgbaLength; i += 4, j++) {
+ buf[j] = data[i];
+ }
+ }
+ resolve({
+ data: buf,
+ width: width,
+ height: height
+ });
+ };
+ img.onerror = function () {
+ reject(new Error('JpegDecode failed to load image'));
+ };
+ img.src = imageUrl;
+ });
+ }, this);
+ },
+ getData: function WorkerTransport_getData() {
+ return this.messageHandler.sendWithPromise('GetData', null);
+ },
+ getPage: function WorkerTransport_getPage(pageNumber, capability) {
+ if (!isInt(pageNumber) || pageNumber <= 0 || pageNumber > this.numPages) {
+ return Promise.reject(new Error('Invalid page request'));
+ }
+ var pageIndex = pageNumber - 1;
+ if (pageIndex in this.pagePromises) {
+ return this.pagePromises[pageIndex];
+ }
+ var promise = this.messageHandler.sendWithPromise('GetPage', { pageIndex: pageIndex }).then(function (pageInfo) {
+ if (this.destroyed) {
+ throw new Error('Transport destroyed');
+ }
+ var page = new PDFPageProxy(pageIndex, pageInfo, this);
+ this.pageCache[pageIndex] = page;
+ return page;
+ }.bind(this));
+ this.pagePromises[pageIndex] = promise;
+ return promise;
+ },
+ getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
+ return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref }).catch(function (reason) {
+ return Promise.reject(new Error(reason));
+ });
+ },
+ getAnnotations: function WorkerTransport_getAnnotations(pageIndex, intent) {
+ return this.messageHandler.sendWithPromise('GetAnnotations', {
+ pageIndex: pageIndex,
+ intent: intent
+ });
+ },
+ getDestinations: function WorkerTransport_getDestinations() {
+ return this.messageHandler.sendWithPromise('GetDestinations', null);
+ },
+ getDestination: function WorkerTransport_getDestination(id) {
+ return this.messageHandler.sendWithPromise('GetDestination', { id: id });
+ },
+ getPageLabels: function WorkerTransport_getPageLabels() {
+ return this.messageHandler.sendWithPromise('GetPageLabels', null);
+ },
+ getAttachments: function WorkerTransport_getAttachments() {
+ return this.messageHandler.sendWithPromise('GetAttachments', null);
+ },
+ getJavaScript: function WorkerTransport_getJavaScript() {
+ return this.messageHandler.sendWithPromise('GetJavaScript', null);
+ },
+ getOutline: function WorkerTransport_getOutline() {
+ return this.messageHandler.sendWithPromise('GetOutline', null);
+ },
+ getMetadata: function WorkerTransport_getMetadata() {
+ return this.messageHandler.sendWithPromise('GetMetadata', null).then(function transportMetadata(results) {
+ return {
+ info: results[0],
+ metadata: results[1] ? new Metadata(results[1]) : null
+ };
+ });
+ },
+ getStats: function WorkerTransport_getStats() {
+ return this.messageHandler.sendWithPromise('GetStats', null);
+ },
+ startCleanup: function WorkerTransport_startCleanup() {
+ this.messageHandler.sendWithPromise('Cleanup', null).then(function endCleanup() {
+ for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
+ var page = this.pageCache[i];
+ if (page) {
+ page.cleanup();
+ }
+ }
+ this.commonObjs.clear();
+ this.fontLoader.clear();
+ }.bind(this));
+ }
+ };
+ return WorkerTransport;
+ }();
+ /**
+ * A PDF document and page is built of many objects. E.g. there are objects
+ * for fonts, images, rendering code and such. These objects might get processed
+ * inside of a worker. The `PDFObjects` implements some basic functions to
+ * manage these objects.
+ * @ignore
+ */
+ var PDFObjects = function PDFObjectsClosure() {
+ function PDFObjects() {
+ this.objs = Object.create(null);
+ }
+ PDFObjects.prototype = {
+ /**
+ * Internal function.
+ * Ensures there is an object defined for `objId`.
+ */
+ ensureObj: function PDFObjects_ensureObj(objId) {
+ if (this.objs[objId]) {
+ return this.objs[objId];
+ }
+ var obj = {
+ capability: createPromiseCapability(),
+ data: null,
+ resolved: false
+ };
+ this.objs[objId] = obj;
+ return obj;
+ },
+ /**
+ * If called *without* callback, this returns the data of `objId` but the
+ * object needs to be resolved. If it isn't, this function throws.
+ *
+ * If called *with* a callback, the callback is called with the data of the
+ * object once the object is resolved. That means, if you call this
+ * function and the object is already resolved, the callback gets called
+ * right away.
+ */
+ get: function PDFObjects_get(objId, callback) {
+ // If there is a callback, then the get can be async and the object is
+ // not required to be resolved right now
+ if (callback) {
+ this.ensureObj(objId).capability.promise.then(callback);
+ return null;
+ }
+ // If there isn't a callback, the user expects to get the resolved data
+ // directly.
+ var obj = this.objs[objId];
+ // If there isn't an object yet or the object isn't resolved, then the
+ // data isn't ready yet!
+ if (!obj || !obj.resolved) {
+ error('Requesting object that isn\'t resolved yet ' + objId);
+ }
+ return obj.data;
+ },
+ /**
+ * Resolves the object `objId` with optional `data`.
+ */
+ resolve: function PDFObjects_resolve(objId, data) {
+ var obj = this.ensureObj(objId);
+ obj.resolved = true;
+ obj.data = data;
+ obj.capability.resolve(data);
+ },
+ isResolved: function PDFObjects_isResolved(objId) {
+ var objs = this.objs;
+ if (!objs[objId]) {
+ return false;
+ } else {
+ return objs[objId].resolved;
+ }
+ },
+ hasData: function PDFObjects_hasData(objId) {
+ return this.isResolved(objId);
+ },
+ /**
+ * Returns the data of `objId` if object exists, null otherwise.
+ */
+ getData: function PDFObjects_getData(objId) {
+ var objs = this.objs;
+ if (!objs[objId] || !objs[objId].resolved) {
+ return null;
+ } else {
+ return objs[objId].data;
+ }
+ },
+ clear: function PDFObjects_clear() {
+ this.objs = Object.create(null);
+ }
+ };
+ return PDFObjects;
+ }();
+ /**
+ * Allows controlling of the rendering tasks.
+ * @class
+ * @alias RenderTask
+ */
+ var RenderTask = function RenderTaskClosure() {
+ function RenderTask(internalRenderTask) {
+ this._internalRenderTask = internalRenderTask;
+ /**
+ * Callback for incremental rendering -- a function that will be called
+ * each time the rendering is paused. To continue rendering call the
+ * function that is the first argument to the callback.
+ * @type {function}
+ */
+ this.onContinue = null;
+ }
+ RenderTask.prototype = /** @lends RenderTask.prototype */
+ {
+ /**
+ * Promise for rendering task completion.
+ * @return {Promise}
+ */
+ get promise() {
+ return this._internalRenderTask.capability.promise;
+ },
+ /**
+ * Cancels the rendering task. If the task is currently rendering it will
+ * not be cancelled until graphics pauses with a timeout. The promise that
+ * this object extends will resolved when cancelled.
+ */
+ cancel: function RenderTask_cancel() {
+ this._internalRenderTask.cancel();
+ },
+ /**
+ * Registers callbacks to indicate the rendering task completion.
+ *
+ * @param {function} onFulfilled The callback for the rendering completion.
+ * @param {function} onRejected The callback for the rendering failure.
+ * @return {Promise} A promise that is resolved after the onFulfilled or
+ * onRejected callback.
+ */
+ then: function RenderTask_then(onFulfilled, onRejected) {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+ };
+ return RenderTask;
+ }();
+ /**
+ * For internal use only.
+ * @ignore
+ */
+ var InternalRenderTask = function InternalRenderTaskClosure() {
+ function InternalRenderTask(callback, params, objs, commonObjs, operatorList, pageNumber) {
+ this.callback = callback;
+ this.params = params;
+ this.objs = objs;
+ this.commonObjs = commonObjs;
+ this.operatorListIdx = null;
+ this.operatorList = operatorList;
+ this.pageNumber = pageNumber;
+ this.running = false;
+ this.graphicsReadyCallback = null;
+ this.graphicsReady = false;
+ this.useRequestAnimationFrame = false;
+ this.cancelled = false;
+ this.capability = createPromiseCapability();
+ this.task = new RenderTask(this);
+ // caching this-bound methods
+ this._continueBound = this._continue.bind(this);
+ this._scheduleNextBound = this._scheduleNext.bind(this);
+ this._nextBound = this._next.bind(this);
+ }
+ InternalRenderTask.prototype = {
+ initializeGraphics: function InternalRenderTask_initializeGraphics(transparency) {
+ if (this.cancelled) {
+ return;
+ }
+ if (getDefaultSetting('pdfBug') && globalScope.StepperManager && globalScope.StepperManager.enabled) {
+ this.stepper = globalScope.StepperManager.create(this.pageNumber - 1);
+ this.stepper.init(this.operatorList);
+ this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
+ }
+ var params = this.params;
+ this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, this.objs, params.imageLayer);
+ this.gfx.beginDrawing(params.transform, params.viewport, transparency);
+ this.operatorListIdx = 0;
+ this.graphicsReady = true;
+ if (this.graphicsReadyCallback) {
+ this.graphicsReadyCallback();
+ }
+ },
+ cancel: function InternalRenderTask_cancel() {
+ this.running = false;
+ this.cancelled = true;
+ this.callback('cancelled');
+ },
+ operatorListChanged: function InternalRenderTask_operatorListChanged() {
+ if (!this.graphicsReady) {
+ if (!this.graphicsReadyCallback) {
+ this.graphicsReadyCallback = this._continueBound;
+ }
+ return;
+ }
+ if (this.stepper) {
+ this.stepper.updateOperatorList(this.operatorList);
+ }
+ if (this.running) {
+ return;
+ }
+ this._continue();
+ },
+ _continue: function InternalRenderTask__continue() {
+ this.running = true;
+ if (this.cancelled) {
+ return;
+ }
+ if (this.task.onContinue) {
+ this.task.onContinue.call(this.task, this._scheduleNextBound);
+ } else {
+ this._scheduleNext();
+ }
+ },
+ _scheduleNext: function InternalRenderTask__scheduleNext() {
+ if (this.useRequestAnimationFrame && typeof window !== 'undefined') {
+ window.requestAnimationFrame(this._nextBound);
+ } else {
+ Promise.resolve(undefined).then(this._nextBound);
+ }
+ },
+ _next: function InternalRenderTask__next() {
+ if (this.cancelled) {
+ return;
+ }
+ this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper);
+ if (this.operatorListIdx === this.operatorList.argsArray.length) {
+ this.running = false;
+ if (this.operatorList.lastChunk) {
+ this.gfx.endDrawing();
+ this.callback();
+ }
+ }
+ }
+ };
+ return InternalRenderTask;
+ }();
+ /**
+ * (Deprecated) Global observer of unsupported feature usages. Use
+ * onUnsupportedFeature callback of the {PDFDocumentLoadingTask} instance.
+ */
+ var _UnsupportedManager = function UnsupportedManagerClosure() {
+ var listeners = [];
+ return {
+ listen: function (cb) {
+ deprecated('Global UnsupportedManager.listen is used: ' + ' use PDFDocumentLoadingTask.onUnsupportedFeature instead');
+ listeners.push(cb);
+ },
+ notify: function (featureId) {
+ for (var i = 0, ii = listeners.length; i < ii; i++) {
+ listeners[i](featureId);
+ }
+ }
+ };
+ }();
+ if (typeof pdfjsVersion !== 'undefined') {
+ exports.version = pdfjsVersion;
+ }
+ if (typeof pdfjsBuild !== 'undefined') {
+ exports.build = pdfjsBuild;
+ }
+ exports.getDocument = getDocument;
+ exports.PDFDataRangeTransport = PDFDataRangeTransport;
+ exports.PDFWorker = PDFWorker;
+ exports.PDFDocumentProxy = PDFDocumentProxy;
+ exports.PDFPageProxy = PDFPageProxy;
+ exports._UnsupportedManager = _UnsupportedManager;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsDisplayGlobal = {}, root.pdfjsSharedUtil, root.pdfjsDisplayDOMUtils, root.pdfjsDisplayAPI, root.pdfjsDisplayAnnotationLayer, root.pdfjsDisplayTextLayer, root.pdfjsDisplayMetadata, root.pdfjsDisplaySVG);
+ }(this, function (exports, sharedUtil, displayDOMUtils, displayAPI, displayAnnotationLayer, displayTextLayer, displayMetadata, displaySVG) {
+ var globalScope = sharedUtil.globalScope;
+ var deprecated = sharedUtil.deprecated;
+ var warn = sharedUtil.warn;
+ var LinkTarget = displayDOMUtils.LinkTarget;
+ var isWorker = typeof window === 'undefined';
+ // The global PDFJS object is now deprecated and will not be supported in
+ // the future. The members below are maintained for backward compatibility
+ // and shall not be extended or modified. If the global.js is included as
+ // a module, we will create a global PDFJS object instance or use existing.
+ if (!globalScope.PDFJS) {
+ globalScope.PDFJS = {};
+ }
+ var PDFJS = globalScope.PDFJS;
+ if (typeof pdfjsVersion !== 'undefined') {
+ PDFJS.version = pdfjsVersion;
+ }
+ if (typeof pdfjsBuild !== 'undefined') {
+ PDFJS.build = pdfjsBuild;
+ }
+ PDFJS.pdfBug = false;
+ if (PDFJS.verbosity !== undefined) {
+ sharedUtil.setVerbosityLevel(PDFJS.verbosity);
+ }
+ delete PDFJS.verbosity;
+ Object.defineProperty(PDFJS, 'verbosity', {
+ get: function () {
+ return sharedUtil.getVerbosityLevel();
+ },
+ set: function (level) {
+ sharedUtil.setVerbosityLevel(level);
+ },
+ enumerable: true,
+ configurable: true
+ });
+ PDFJS.VERBOSITY_LEVELS = sharedUtil.VERBOSITY_LEVELS;
+ PDFJS.OPS = sharedUtil.OPS;
+ PDFJS.UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
+ PDFJS.isValidUrl = displayDOMUtils.isValidUrl;
+ PDFJS.shadow = sharedUtil.shadow;
+ PDFJS.createBlob = sharedUtil.createBlob;
+ PDFJS.createObjectURL = function PDFJS_createObjectURL(data, contentType) {
+ return sharedUtil.createObjectURL(data, contentType, PDFJS.disableCreateObjectURL);
+ };
+ Object.defineProperty(PDFJS, 'isLittleEndian', {
+ configurable: true,
+ get: function PDFJS_isLittleEndian() {
+ var value = sharedUtil.isLittleEndian();
+ return sharedUtil.shadow(PDFJS, 'isLittleEndian', value);
+ }
+ });
+ PDFJS.removeNullCharacters = sharedUtil.removeNullCharacters;
+ PDFJS.PasswordResponses = sharedUtil.PasswordResponses;
+ PDFJS.PasswordException = sharedUtil.PasswordException;
+ PDFJS.UnknownErrorException = sharedUtil.UnknownErrorException;
+ PDFJS.InvalidPDFException = sharedUtil.InvalidPDFException;
+ PDFJS.MissingPDFException = sharedUtil.MissingPDFException;
+ PDFJS.UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
+ PDFJS.Util = sharedUtil.Util;
+ PDFJS.PageViewport = sharedUtil.PageViewport;
+ PDFJS.createPromiseCapability = sharedUtil.createPromiseCapability;
+ /**
+ * The maximum allowed image size in total pixels e.g. width * height. Images
+ * above this value will not be drawn. Use -1 for no limit.
+ * @var {number}
+ */
+ PDFJS.maxImageSize = PDFJS.maxImageSize === undefined ? -1 : PDFJS.maxImageSize;
+ /**
+ * The url of where the predefined Adobe CMaps are located. Include trailing
+ * slash.
+ * @var {string}
+ */
+ PDFJS.cMapUrl = PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl;
+ /**
+ * Specifies if CMaps are binary packed.
+ * @var {boolean}
+ */
+ PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked;
+ /**
+ * By default fonts are converted to OpenType fonts and loaded via font face
+ * rules. If disabled, the font will be rendered using a built in font
+ * renderer that constructs the glyphs with primitive path commands.
+ * @var {boolean}
+ */
+ PDFJS.disableFontFace = PDFJS.disableFontFace === undefined ? false : PDFJS.disableFontFace;
+ /**
+ * Path for image resources, mainly for annotation icons. Include trailing
+ * slash.
+ * @var {string}
+ */
+ PDFJS.imageResourcesPath = PDFJS.imageResourcesPath === undefined ? '' : PDFJS.imageResourcesPath;
+ /**
+ * Disable the web worker and run all code on the main thread. This will
+ * happen automatically if the browser doesn't support workers or sending
+ * typed arrays to workers.
+ * @var {boolean}
+ */
+ PDFJS.disableWorker = PDFJS.disableWorker === undefined ? false : PDFJS.disableWorker;
+ /**
+ * Path and filename of the worker file. Required when the worker is enabled
+ * in development mode. If unspecified in the production build, the worker
+ * will be loaded based on the location of the pdf.js file. It is recommended
+ * that the workerSrc is set in a custom application to prevent issues caused
+ * by third-party frameworks and libraries.
+ * @var {string}
+ */
+ PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc;
+ /**
+ * Disable range request loading of PDF files. When enabled and if the server
+ * supports partial content requests then the PDF will be fetched in chunks.
+ * Enabled (false) by default.
+ * @var {boolean}
+ */
+ PDFJS.disableRange = PDFJS.disableRange === undefined ? false : PDFJS.disableRange;
+ /**
+ * Disable streaming of PDF file data. By default PDF.js attempts to load PDF
+ * in chunks. This default behavior can be disabled.
+ * @var {boolean}
+ */
+ PDFJS.disableStream = PDFJS.disableStream === undefined ? false : PDFJS.disableStream;
+ /**
+ * Disable pre-fetching of PDF file data. When range requests are enabled
+ * PDF.js will automatically keep fetching more data even if it isn't needed
+ * to display the current page. This default behavior can be disabled.
+ *
+ * NOTE: It is also necessary to disable streaming, see above,
+ * in order for disabling of pre-fetching to work correctly.
+ * @var {boolean}
+ */
+ PDFJS.disableAutoFetch = PDFJS.disableAutoFetch === undefined ? false : PDFJS.disableAutoFetch;
+ /**
+ * Enables special hooks for debugging PDF.js.
+ * @var {boolean}
+ */
+ PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
+ /**
+ * Enables transfer usage in postMessage for ArrayBuffers.
+ * @var {boolean}
+ */
+ PDFJS.postMessageTransfers = PDFJS.postMessageTransfers === undefined ? true : PDFJS.postMessageTransfers;
+ /**
+ * Disables URL.createObjectURL usage.
+ * @var {boolean}
+ */
+ PDFJS.disableCreateObjectURL = PDFJS.disableCreateObjectURL === undefined ? false : PDFJS.disableCreateObjectURL;
+ /**
+ * Disables WebGL usage.
+ * @var {boolean}
+ */
+ PDFJS.disableWebGL = PDFJS.disableWebGL === undefined ? true : PDFJS.disableWebGL;
+ /**
+ * Specifies the |target| attribute for external links.
+ * The constants from PDFJS.LinkTarget should be used:
+ * - NONE [default]
+ * - SELF
+ * - BLANK
+ * - PARENT
+ * - TOP
+ * @var {number}
+ */
+ PDFJS.externalLinkTarget = PDFJS.externalLinkTarget === undefined ? LinkTarget.NONE : PDFJS.externalLinkTarget;
+ /**
+ * Specifies the |rel| attribute for external links. Defaults to stripping
+ * the referrer.
+ * @var {string}
+ */
+ PDFJS.externalLinkRel = PDFJS.externalLinkRel === undefined ? 'noreferrer' : PDFJS.externalLinkRel;
+ /**
+ * Determines if we can eval strings as JS. Primarily used to improve
+ * performance for font rendering.
+ * @var {boolean}
+ */
+ PDFJS.isEvalSupported = PDFJS.isEvalSupported === undefined ? true : PDFJS.isEvalSupported;
+ PDFJS.getDocument = displayAPI.getDocument;
+ PDFJS.PDFDataRangeTransport = displayAPI.PDFDataRangeTransport;
+ PDFJS.PDFWorker = displayAPI.PDFWorker;
+ Object.defineProperty(PDFJS, 'hasCanvasTypedArrays', {
+ configurable: true,
+ get: function PDFJS_hasCanvasTypedArrays() {
+ var value = displayDOMUtils.hasCanvasTypedArrays();
+ return sharedUtil.shadow(PDFJS, 'hasCanvasTypedArrays', value);
+ }
+ });
+ PDFJS.CustomStyle = displayDOMUtils.CustomStyle;
+ PDFJS.LinkTarget = LinkTarget;
+ PDFJS.addLinkAttributes = displayDOMUtils.addLinkAttributes;
+ PDFJS.getFilenameFromUrl = displayDOMUtils.getFilenameFromUrl;
+ PDFJS.isExternalLinkTargetSet = displayDOMUtils.isExternalLinkTargetSet;
+ PDFJS.AnnotationLayer = displayAnnotationLayer.AnnotationLayer;
+ PDFJS.renderTextLayer = displayTextLayer.renderTextLayer;
+ PDFJS.Metadata = displayMetadata.Metadata;
+ PDFJS.SVGGraphics = displaySVG.SVGGraphics;
+ PDFJS.UnsupportedManager = displayAPI._UnsupportedManager;
+ exports.globalScope = globalScope;
+ exports.isWorker = isWorker;
+ exports.PDFJS = globalScope.PDFJS;
+ }));
+ }.call(pdfjsLibs));
+ exports.PDFJS = pdfjsLibs.pdfjsDisplayGlobal.PDFJS;
+ exports.build = pdfjsLibs.pdfjsDisplayAPI.build;
+ exports.version = pdfjsLibs.pdfjsDisplayAPI.version;
+ exports.getDocument = pdfjsLibs.pdfjsDisplayAPI.getDocument;
+ exports.PDFDataRangeTransport = pdfjsLibs.pdfjsDisplayAPI.PDFDataRangeTransport;
+ exports.PDFWorker = pdfjsLibs.pdfjsDisplayAPI.PDFWorker;
+ exports.renderTextLayer = pdfjsLibs.pdfjsDisplayTextLayer.renderTextLayer;
+ exports.AnnotationLayer = pdfjsLibs.pdfjsDisplayAnnotationLayer.AnnotationLayer;
+ exports.CustomStyle = pdfjsLibs.pdfjsDisplayDOMUtils.CustomStyle;
+ exports.PasswordResponses = pdfjsLibs.pdfjsSharedUtil.PasswordResponses;
+ exports.InvalidPDFException = pdfjsLibs.pdfjsSharedUtil.InvalidPDFException;
+ exports.MissingPDFException = pdfjsLibs.pdfjsSharedUtil.MissingPDFException;
+ exports.SVGGraphics = pdfjsLibs.pdfjsDisplaySVG.SVGGraphics;
+ exports.UnexpectedResponseException = pdfjsLibs.pdfjsSharedUtil.UnexpectedResponseException;
+ exports.OPS = pdfjsLibs.pdfjsSharedUtil.OPS;
+ exports.UNSUPPORTED_FEATURES = pdfjsLibs.pdfjsSharedUtil.UNSUPPORTED_FEATURES;
+ exports.isValidUrl = pdfjsLibs.pdfjsDisplayDOMUtils.isValidUrl;
+ exports.createValidAbsoluteUrl = pdfjsLibs.pdfjsSharedUtil.createValidAbsoluteUrl;
+ exports.createObjectURL = pdfjsLibs.pdfjsSharedUtil.createObjectURL;
+ exports.removeNullCharacters = pdfjsLibs.pdfjsSharedUtil.removeNullCharacters;
+ exports.shadow = pdfjsLibs.pdfjsSharedUtil.shadow;
+ exports.createBlob = pdfjsLibs.pdfjsSharedUtil.createBlob;
+ exports.getFilenameFromUrl = pdfjsLibs.pdfjsDisplayDOMUtils.getFilenameFromUrl;
+ exports.addLinkAttributes = pdfjsLibs.pdfjsDisplayDOMUtils.addLinkAttributes;
+})); \ No newline at end of file
diff --git a/browser/extensions/pdfjs/content/build/pdf.worker.js b/browser/extensions/pdfjs/content/build/pdf.worker.js
new file mode 100644
index 000000000..4c35bd401
--- /dev/null
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -0,0 +1,52491 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+(function (root, factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ define('pdfjs-dist/build/pdf.worker', ['exports'], factory);
+ } else if (typeof exports !== 'undefined') {
+ factory(exports);
+ } else {
+ factory(root['pdfjsDistBuildPdfWorker'] = {});
+ }
+}(this, function (exports) {
+ // Use strict in our context only - users might not want it
+ 'use strict';
+ var pdfjsVersion = '1.6.315';
+ var pdfjsBuild = 'a139c75';
+ var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
+ var pdfjsLibs = {};
+ (function pdfjsWrapper() {
+ (function (root, factory) {
+ factory(root.pdfjsCoreArithmeticDecoder = {});
+ }(this, function (exports) {
+ /* This class implements the QM Coder decoding as defined in
+ * JPEG 2000 Part I Final Committee Draft Version 1.0
+ * Annex C.3 Arithmetic decoding procedure
+ * available at http://www.jpeg.org/public/fcd15444-1.pdf
+ *
+ * The arithmetic decoder is used in conjunction with context models to decode
+ * JPEG2000 and JBIG2 streams.
+ */
+ var ArithmeticDecoder = function ArithmeticDecoderClosure() {
+ // Table C-2
+ var QeTable = [
+ {
+ qe: 0x5601,
+ nmps: 1,
+ nlps: 1,
+ switchFlag: 1
+ },
+ {
+ qe: 0x3401,
+ nmps: 2,
+ nlps: 6,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1801,
+ nmps: 3,
+ nlps: 9,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0AC1,
+ nmps: 4,
+ nlps: 12,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0521,
+ nmps: 5,
+ nlps: 29,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0221,
+ nmps: 38,
+ nlps: 33,
+ switchFlag: 0
+ },
+ {
+ qe: 0x5601,
+ nmps: 7,
+ nlps: 6,
+ switchFlag: 1
+ },
+ {
+ qe: 0x5401,
+ nmps: 8,
+ nlps: 14,
+ switchFlag: 0
+ },
+ {
+ qe: 0x4801,
+ nmps: 9,
+ nlps: 14,
+ switchFlag: 0
+ },
+ {
+ qe: 0x3801,
+ nmps: 10,
+ nlps: 14,
+ switchFlag: 0
+ },
+ {
+ qe: 0x3001,
+ nmps: 11,
+ nlps: 17,
+ switchFlag: 0
+ },
+ {
+ qe: 0x2401,
+ nmps: 12,
+ nlps: 18,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1C01,
+ nmps: 13,
+ nlps: 20,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1601,
+ nmps: 29,
+ nlps: 21,
+ switchFlag: 0
+ },
+ {
+ qe: 0x5601,
+ nmps: 15,
+ nlps: 14,
+ switchFlag: 1
+ },
+ {
+ qe: 0x5401,
+ nmps: 16,
+ nlps: 14,
+ switchFlag: 0
+ },
+ {
+ qe: 0x5101,
+ nmps: 17,
+ nlps: 15,
+ switchFlag: 0
+ },
+ {
+ qe: 0x4801,
+ nmps: 18,
+ nlps: 16,
+ switchFlag: 0
+ },
+ {
+ qe: 0x3801,
+ nmps: 19,
+ nlps: 17,
+ switchFlag: 0
+ },
+ {
+ qe: 0x3401,
+ nmps: 20,
+ nlps: 18,
+ switchFlag: 0
+ },
+ {
+ qe: 0x3001,
+ nmps: 21,
+ nlps: 19,
+ switchFlag: 0
+ },
+ {
+ qe: 0x2801,
+ nmps: 22,
+ nlps: 19,
+ switchFlag: 0
+ },
+ {
+ qe: 0x2401,
+ nmps: 23,
+ nlps: 20,
+ switchFlag: 0
+ },
+ {
+ qe: 0x2201,
+ nmps: 24,
+ nlps: 21,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1C01,
+ nmps: 25,
+ nlps: 22,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1801,
+ nmps: 26,
+ nlps: 23,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1601,
+ nmps: 27,
+ nlps: 24,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1401,
+ nmps: 28,
+ nlps: 25,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1201,
+ nmps: 29,
+ nlps: 26,
+ switchFlag: 0
+ },
+ {
+ qe: 0x1101,
+ nmps: 30,
+ nlps: 27,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0AC1,
+ nmps: 31,
+ nlps: 28,
+ switchFlag: 0
+ },
+ {
+ qe: 0x09C1,
+ nmps: 32,
+ nlps: 29,
+ switchFlag: 0
+ },
+ {
+ qe: 0x08A1,
+ nmps: 33,
+ nlps: 30,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0521,
+ nmps: 34,
+ nlps: 31,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0441,
+ nmps: 35,
+ nlps: 32,
+ switchFlag: 0
+ },
+ {
+ qe: 0x02A1,
+ nmps: 36,
+ nlps: 33,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0221,
+ nmps: 37,
+ nlps: 34,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0141,
+ nmps: 38,
+ nlps: 35,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0111,
+ nmps: 39,
+ nlps: 36,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0085,
+ nmps: 40,
+ nlps: 37,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0049,
+ nmps: 41,
+ nlps: 38,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0025,
+ nmps: 42,
+ nlps: 39,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0015,
+ nmps: 43,
+ nlps: 40,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0009,
+ nmps: 44,
+ nlps: 41,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0005,
+ nmps: 45,
+ nlps: 42,
+ switchFlag: 0
+ },
+ {
+ qe: 0x0001,
+ nmps: 45,
+ nlps: 43,
+ switchFlag: 0
+ },
+ {
+ qe: 0x5601,
+ nmps: 46,
+ nlps: 46,
+ switchFlag: 0
+ }
+ ];
+ // C.3.5 Initialisation of the decoder (INITDEC)
+ function ArithmeticDecoder(data, start, end) {
+ this.data = data;
+ this.bp = start;
+ this.dataEnd = end;
+ this.chigh = data[start];
+ this.clow = 0;
+ this.byteIn();
+ this.chigh = this.chigh << 7 & 0xFFFF | this.clow >> 9 & 0x7F;
+ this.clow = this.clow << 7 & 0xFFFF;
+ this.ct -= 7;
+ this.a = 0x8000;
+ }
+ ArithmeticDecoder.prototype = {
+ // C.3.4 Compressed data input (BYTEIN)
+ byteIn: function ArithmeticDecoder_byteIn() {
+ var data = this.data;
+ var bp = this.bp;
+ if (data[bp] === 0xFF) {
+ var b1 = data[bp + 1];
+ if (b1 > 0x8F) {
+ this.clow += 0xFF00;
+ this.ct = 8;
+ } else {
+ bp++;
+ this.clow += data[bp] << 9;
+ this.ct = 7;
+ this.bp = bp;
+ }
+ } else {
+ bp++;
+ this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xFF00;
+ this.ct = 8;
+ this.bp = bp;
+ }
+ if (this.clow > 0xFFFF) {
+ this.chigh += this.clow >> 16;
+ this.clow &= 0xFFFF;
+ }
+ },
+ // C.3.2 Decoding a decision (DECODE)
+ readBit: function ArithmeticDecoder_readBit(contexts, pos) {
+ // contexts are packed into 1 byte:
+ // highest 7 bits carry cx.index, lowest bit carries cx.mps
+ var cx_index = contexts[pos] >> 1, cx_mps = contexts[pos] & 1;
+ var qeTableIcx = QeTable[cx_index];
+ var qeIcx = qeTableIcx.qe;
+ var d;
+ var a = this.a - qeIcx;
+ if (this.chigh < qeIcx) {
+ // exchangeLps
+ if (a < qeIcx) {
+ a = qeIcx;
+ d = cx_mps;
+ cx_index = qeTableIcx.nmps;
+ } else {
+ a = qeIcx;
+ d = 1 ^ cx_mps;
+ if (qeTableIcx.switchFlag === 1) {
+ cx_mps = d;
+ }
+ cx_index = qeTableIcx.nlps;
+ }
+ } else {
+ this.chigh -= qeIcx;
+ if ((a & 0x8000) !== 0) {
+ this.a = a;
+ return cx_mps;
+ }
+ // exchangeMps
+ if (a < qeIcx) {
+ d = 1 ^ cx_mps;
+ if (qeTableIcx.switchFlag === 1) {
+ cx_mps = d;
+ }
+ cx_index = qeTableIcx.nlps;
+ } else {
+ d = cx_mps;
+ cx_index = qeTableIcx.nmps;
+ }
+ }
+ // C.3.3 renormD;
+ do {
+ if (this.ct === 0) {
+ this.byteIn();
+ }
+ a <<= 1;
+ this.chigh = this.chigh << 1 & 0xFFFF | this.clow >> 15 & 1;
+ this.clow = this.clow << 1 & 0xFFFF;
+ this.ct--;
+ } while ((a & 0x8000) === 0);
+ this.a = a;
+ contexts[pos] = cx_index << 1 | cx_mps;
+ return d;
+ }
+ };
+ return ArithmeticDecoder;
+ }();
+ exports.ArithmeticDecoder = ArithmeticDecoder;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreBidi = {});
+ }(this, function (exports) {
+ // Character types for symbols from 0000 to 00FF.
+ var baseTypes = [
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'S',
+ 'B',
+ 'S',
+ 'WS',
+ 'B',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'B',
+ 'B',
+ 'B',
+ 'S',
+ 'WS',
+ 'ON',
+ 'ON',
+ 'ET',
+ 'ET',
+ 'ET',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'CS',
+ 'ON',
+ 'CS',
+ 'ON',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'EN',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'B',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'BN',
+ 'CS',
+ 'ON',
+ 'ET',
+ 'ET',
+ 'ET',
+ 'ET',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'L',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ET',
+ 'ET',
+ 'EN',
+ 'EN',
+ 'ON',
+ 'L',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'EN',
+ 'L',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'ON',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'ON',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'ON',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L',
+ 'L'
+ ];
+ // Character types for symbols from 0600 to 06FF
+ var arabicTypes = [
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'CS',
+ 'AL',
+ 'ON',
+ 'ON',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'AN',
+ 'ET',
+ 'AN',
+ 'AN',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'NSM',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'ON',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'NSM',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL',
+ 'AL'
+ ];
+ function isOdd(i) {
+ return (i & 1) !== 0;
+ }
+ function isEven(i) {
+ return (i & 1) === 0;
+ }
+ function findUnequal(arr, start, value) {
+ for (var j = start, jj = arr.length; j < jj; ++j) {
+ if (arr[j] !== value) {
+ return j;
+ }
+ }
+ return j;
+ }
+ function setValues(arr, start, end, value) {
+ for (var j = start; j < end; ++j) {
+ arr[j] = value;
+ }
+ }
+ function reverseValues(arr, start, end) {
+ for (var i = start, j = end - 1; i < j; ++i, --j) {
+ var temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
+ }
+ }
+ function createBidiText(str, isLTR, vertical) {
+ return {
+ str: str,
+ dir: vertical ? 'ttb' : isLTR ? 'ltr' : 'rtl'
+ };
+ }
+ // These are used in bidi(), which is called frequently. We re-use them on
+ // each call to avoid unnecessary allocations.
+ var chars = [];
+ var types = [];
+ function bidi(str, startLevel, vertical) {
+ var isLTR = true;
+ var strLength = str.length;
+ if (strLength === 0 || vertical) {
+ return createBidiText(str, isLTR, vertical);
+ }
+ // Get types and fill arrays
+ chars.length = strLength;
+ types.length = strLength;
+ var numBidi = 0;
+ var i, ii;
+ for (i = 0; i < strLength; ++i) {
+ chars[i] = str.charAt(i);
+ var charCode = str.charCodeAt(i);
+ var charType = 'L';
+ if (charCode <= 0x00ff) {
+ charType = baseTypes[charCode];
+ } else if (0x0590 <= charCode && charCode <= 0x05f4) {
+ charType = 'R';
+ } else if (0x0600 <= charCode && charCode <= 0x06ff) {
+ charType = arabicTypes[charCode & 0xff];
+ } else if (0x0700 <= charCode && charCode <= 0x08AC) {
+ charType = 'AL';
+ }
+ if (charType === 'R' || charType === 'AL' || charType === 'AN') {
+ numBidi++;
+ }
+ types[i] = charType;
+ }
+ // Detect the bidi method
+ // - If there are no rtl characters then no bidi needed
+ // - If less than 30% chars are rtl then string is primarily ltr
+ // - If more than 30% chars are rtl then string is primarily rtl
+ if (numBidi === 0) {
+ isLTR = true;
+ return createBidiText(str, isLTR);
+ }
+ if (startLevel === -1) {
+ if (strLength / numBidi < 0.3) {
+ isLTR = true;
+ startLevel = 0;
+ } else {
+ isLTR = false;
+ startLevel = 1;
+ }
+ }
+ var levels = [];
+ for (i = 0; i < strLength; ++i) {
+ levels[i] = startLevel;
+ }
+ /*
+ X1-X10: skip most of this, since we are NOT doing the embeddings.
+ */
+ var e = isOdd(startLevel) ? 'R' : 'L';
+ var sor = e;
+ var eor = sor;
+ /*
+ W1. Examine each non-spacing mark (NSM) in the level run, and change the
+ type of the NSM to the type of the previous character. If the NSM is at the
+ start of the level run, it will get the type of sor.
+ */
+ var lastType = sor;
+ for (i = 0; i < strLength; ++i) {
+ if (types[i] === 'NSM') {
+ types[i] = lastType;
+ } else {
+ lastType = types[i];
+ }
+ }
+ /*
+ W2. Search backwards from each instance of a European number until the
+ first strong type (R, L, AL, or sor) is found. If an AL is found, change
+ the type of the European number to Arabic number.
+ */
+ lastType = sor;
+ var t;
+ for (i = 0; i < strLength; ++i) {
+ t = types[i];
+ if (t === 'EN') {
+ types[i] = lastType === 'AL' ? 'AN' : 'EN';
+ } else if (t === 'R' || t === 'L' || t === 'AL') {
+ lastType = t;
+ }
+ }
+ /*
+ W3. Change all ALs to R.
+ */
+ for (i = 0; i < strLength; ++i) {
+ t = types[i];
+ if (t === 'AL') {
+ types[i] = 'R';
+ }
+ }
+ /*
+ W4. A single European separator between two European numbers changes to a
+ European number. A single common separator between two numbers of the same
+ type changes to that type:
+ */
+ for (i = 1; i < strLength - 1; ++i) {
+ if (types[i] === 'ES' && types[i - 1] === 'EN' && types[i + 1] === 'EN') {
+ types[i] = 'EN';
+ }
+ if (types[i] === 'CS' && (types[i - 1] === 'EN' || types[i - 1] === 'AN') && types[i + 1] === types[i - 1]) {
+ types[i] = types[i - 1];
+ }
+ }
+ /*
+ W5. A sequence of European terminators adjacent to European numbers changes
+ to all European numbers:
+ */
+ for (i = 0; i < strLength; ++i) {
+ if (types[i] === 'EN') {
+ // do before
+ var j;
+ for (j = i - 1; j >= 0; --j) {
+ if (types[j] !== 'ET') {
+ break;
+ }
+ types[j] = 'EN';
+ }
+ // do after
+ for (j = i + 1; j < strLength; ++j) {
+ if (types[j] !== 'ET') {
+ break;
+ }
+ types[j] = 'EN';
+ }
+ }
+ }
+ /*
+ W6. Otherwise, separators and terminators change to Other Neutral:
+ */
+ for (i = 0; i < strLength; ++i) {
+ t = types[i];
+ if (t === 'WS' || t === 'ES' || t === 'ET' || t === 'CS') {
+ types[i] = 'ON';
+ }
+ }
+ /*
+ W7. Search backwards from each instance of a European number until the
+ first strong type (R, L, or sor) is found. If an L is found, then change
+ the type of the European number to L.
+ */
+ lastType = sor;
+ for (i = 0; i < strLength; ++i) {
+ t = types[i];
+ if (t === 'EN') {
+ types[i] = lastType === 'L' ? 'L' : 'EN';
+ } else if (t === 'R' || t === 'L') {
+ lastType = t;
+ }
+ }
+ /*
+ N1. A sequence of neutrals takes the direction of the surrounding strong
+ text if the text on both sides has the same direction. European and Arabic
+ numbers are treated as though they were R. Start-of-level-run (sor) and
+ end-of-level-run (eor) are used at level run boundaries.
+ */
+ for (i = 0; i < strLength; ++i) {
+ if (types[i] === 'ON') {
+ var end = findUnequal(types, i + 1, 'ON');
+ var before = sor;
+ if (i > 0) {
+ before = types[i - 1];
+ }
+ var after = eor;
+ if (end + 1 < strLength) {
+ after = types[end + 1];
+ }
+ if (before !== 'L') {
+ before = 'R';
+ }
+ if (after !== 'L') {
+ after = 'R';
+ }
+ if (before === after) {
+ setValues(types, i, end, before);
+ }
+ i = end - 1;
+ }
+ }
+ // reset to end (-1 so next iteration is ok)
+ /*
+ N2. Any remaining neutrals take the embedding direction.
+ */
+ for (i = 0; i < strLength; ++i) {
+ if (types[i] === 'ON') {
+ types[i] = e;
+ }
+ }
+ /*
+ I1. For all characters with an even (left-to-right) embedding direction,
+ those of type R go up one level and those of type AN or EN go up two
+ levels.
+ I2. For all characters with an odd (right-to-left) embedding direction,
+ those of type L, EN or AN go up one level.
+ */
+ for (i = 0; i < strLength; ++i) {
+ t = types[i];
+ if (isEven(levels[i])) {
+ if (t === 'R') {
+ levels[i] += 1;
+ } else if (t === 'AN' || t === 'EN') {
+ levels[i] += 2;
+ }
+ } else {
+ // isOdd
+ if (t === 'L' || t === 'AN' || t === 'EN') {
+ levels[i] += 1;
+ }
+ }
+ }
+ /*
+ L1. On each line, reset the embedding level of the following characters to
+ the paragraph embedding level:
+
+ segment separators,
+ paragraph separators,
+ any sequence of whitespace characters preceding a segment separator or
+ paragraph separator, and any sequence of white space characters at the end
+ of the line.
+ */
+ // don't bother as text is only single line
+ /*
+ L2. From the highest level found in the text to the lowest odd level on
+ each line, reverse any contiguous sequence of characters that are at that
+ level or higher.
+ */
+ // find highest level & lowest odd level
+ var highestLevel = -1;
+ var lowestOddLevel = 99;
+ var level;
+ for (i = 0, ii = levels.length; i < ii; ++i) {
+ level = levels[i];
+ if (highestLevel < level) {
+ highestLevel = level;
+ }
+ if (lowestOddLevel > level && isOdd(level)) {
+ lowestOddLevel = level;
+ }
+ }
+ // now reverse between those limits
+ for (level = highestLevel; level >= lowestOddLevel; --level) {
+ // find segments to reverse
+ var start = -1;
+ for (i = 0, ii = levels.length; i < ii; ++i) {
+ if (levels[i] < level) {
+ if (start >= 0) {
+ reverseValues(chars, start, i);
+ start = -1;
+ }
+ } else if (start < 0) {
+ start = i;
+ }
+ }
+ if (start >= 0) {
+ reverseValues(chars, start, levels.length);
+ }
+ }
+ /*
+ L3. Combining marks applied to a right-to-left base character will at this
+ point precede their base character. If the rendering engine expects them to
+ follow the base characters in the final display process, then the ordering
+ of the marks and the base character must be reversed.
+ */
+ // don't bother for now
+ /*
+ L4. A character that possesses the mirrored property as specified by
+ Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved
+ directionality of that character is R.
+ */
+ // don't mirror as characters are already mirrored in the pdf
+ // Finally, return string
+ for (i = 0, ii = chars.length; i < ii; ++i) {
+ var ch = chars[i];
+ if (ch === '<' || ch === '>') {
+ chars[i] = '';
+ }
+ }
+ return createBidiText(chars.join(''), isLTR);
+ }
+ exports.bidi = bidi;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreCharsets = {});
+ }(this, function (exports) {
+ var ISOAdobeCharset = [
+ '.notdef',
+ 'space',
+ 'exclam',
+ 'quotedbl',
+ 'numbersign',
+ 'dollar',
+ 'percent',
+ 'ampersand',
+ 'quoteright',
+ 'parenleft',
+ 'parenright',
+ 'asterisk',
+ 'plus',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'slash',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'at',
+ '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',
+ 'bracketleft',
+ 'backslash',
+ 'bracketright',
+ 'asciicircum',
+ 'underscore',
+ 'quoteleft',
+ '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',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'asciitilde',
+ 'exclamdown',
+ 'cent',
+ 'sterling',
+ 'fraction',
+ 'yen',
+ 'florin',
+ 'section',
+ 'currency',
+ 'quotesingle',
+ 'quotedblleft',
+ 'guillemotleft',
+ 'guilsinglleft',
+ 'guilsinglright',
+ 'fi',
+ 'fl',
+ 'endash',
+ 'dagger',
+ 'daggerdbl',
+ 'periodcentered',
+ 'paragraph',
+ 'bullet',
+ 'quotesinglbase',
+ 'quotedblbase',
+ 'quotedblright',
+ 'guillemotright',
+ 'ellipsis',
+ 'perthousand',
+ 'questiondown',
+ 'grave',
+ 'acute',
+ 'circumflex',
+ 'tilde',
+ 'macron',
+ 'breve',
+ 'dotaccent',
+ 'dieresis',
+ 'ring',
+ 'cedilla',
+ 'hungarumlaut',
+ 'ogonek',
+ 'caron',
+ 'emdash',
+ 'AE',
+ 'ordfeminine',
+ 'Lslash',
+ 'Oslash',
+ 'OE',
+ 'ordmasculine',
+ 'ae',
+ 'dotlessi',
+ 'lslash',
+ 'oslash',
+ 'oe',
+ 'germandbls',
+ 'onesuperior',
+ 'logicalnot',
+ 'mu',
+ 'trademark',
+ 'Eth',
+ 'onehalf',
+ 'plusminus',
+ 'Thorn',
+ 'onequarter',
+ 'divide',
+ 'brokenbar',
+ 'degree',
+ 'thorn',
+ 'threequarters',
+ 'twosuperior',
+ 'registered',
+ 'minus',
+ 'eth',
+ 'multiply',
+ 'threesuperior',
+ 'copyright',
+ 'Aacute',
+ 'Acircumflex',
+ 'Adieresis',
+ 'Agrave',
+ 'Aring',
+ 'Atilde',
+ 'Ccedilla',
+ 'Eacute',
+ 'Ecircumflex',
+ 'Edieresis',
+ 'Egrave',
+ 'Iacute',
+ 'Icircumflex',
+ 'Idieresis',
+ 'Igrave',
+ 'Ntilde',
+ 'Oacute',
+ 'Ocircumflex',
+ 'Odieresis',
+ 'Ograve',
+ 'Otilde',
+ 'Scaron',
+ 'Uacute',
+ 'Ucircumflex',
+ 'Udieresis',
+ 'Ugrave',
+ 'Yacute',
+ 'Ydieresis',
+ 'Zcaron',
+ 'aacute',
+ 'acircumflex',
+ 'adieresis',
+ 'agrave',
+ 'aring',
+ 'atilde',
+ 'ccedilla',
+ 'eacute',
+ 'ecircumflex',
+ 'edieresis',
+ 'egrave',
+ 'iacute',
+ 'icircumflex',
+ 'idieresis',
+ 'igrave',
+ 'ntilde',
+ 'oacute',
+ 'ocircumflex',
+ 'odieresis',
+ 'ograve',
+ 'otilde',
+ 'scaron',
+ 'uacute',
+ 'ucircumflex',
+ 'udieresis',
+ 'ugrave',
+ 'yacute',
+ 'ydieresis',
+ 'zcaron'
+ ];
+ var ExpertCharset = [
+ '.notdef',
+ 'space',
+ 'exclamsmall',
+ 'Hungarumlautsmall',
+ 'dollaroldstyle',
+ 'dollarsuperior',
+ 'ampersandsmall',
+ 'Acutesmall',
+ 'parenleftsuperior',
+ 'parenrightsuperior',
+ 'twodotenleader',
+ 'onedotenleader',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'fraction',
+ 'zerooldstyle',
+ 'oneoldstyle',
+ 'twooldstyle',
+ 'threeoldstyle',
+ 'fouroldstyle',
+ 'fiveoldstyle',
+ 'sixoldstyle',
+ 'sevenoldstyle',
+ 'eightoldstyle',
+ 'nineoldstyle',
+ 'colon',
+ 'semicolon',
+ 'commasuperior',
+ 'threequartersemdash',
+ 'periodsuperior',
+ 'questionsmall',
+ 'asuperior',
+ 'bsuperior',
+ 'centsuperior',
+ 'dsuperior',
+ 'esuperior',
+ 'isuperior',
+ 'lsuperior',
+ 'msuperior',
+ 'nsuperior',
+ 'osuperior',
+ 'rsuperior',
+ 'ssuperior',
+ 'tsuperior',
+ 'ff',
+ 'fi',
+ 'fl',
+ 'ffi',
+ 'ffl',
+ 'parenleftinferior',
+ 'parenrightinferior',
+ 'Circumflexsmall',
+ 'hyphensuperior',
+ 'Gravesmall',
+ 'Asmall',
+ 'Bsmall',
+ 'Csmall',
+ 'Dsmall',
+ 'Esmall',
+ 'Fsmall',
+ 'Gsmall',
+ 'Hsmall',
+ 'Ismall',
+ 'Jsmall',
+ 'Ksmall',
+ 'Lsmall',
+ 'Msmall',
+ 'Nsmall',
+ 'Osmall',
+ 'Psmall',
+ 'Qsmall',
+ 'Rsmall',
+ 'Ssmall',
+ 'Tsmall',
+ 'Usmall',
+ 'Vsmall',
+ 'Wsmall',
+ 'Xsmall',
+ 'Ysmall',
+ 'Zsmall',
+ 'colonmonetary',
+ 'onefitted',
+ 'rupiah',
+ 'Tildesmall',
+ 'exclamdownsmall',
+ 'centoldstyle',
+ 'Lslashsmall',
+ 'Scaronsmall',
+ 'Zcaronsmall',
+ 'Dieresissmall',
+ 'Brevesmall',
+ 'Caronsmall',
+ 'Dotaccentsmall',
+ 'Macronsmall',
+ 'figuredash',
+ 'hypheninferior',
+ 'Ogoneksmall',
+ 'Ringsmall',
+ 'Cedillasmall',
+ 'onequarter',
+ 'onehalf',
+ 'threequarters',
+ 'questiondownsmall',
+ 'oneeighth',
+ 'threeeighths',
+ 'fiveeighths',
+ 'seveneighths',
+ 'onethird',
+ 'twothirds',
+ 'zerosuperior',
+ 'onesuperior',
+ 'twosuperior',
+ 'threesuperior',
+ 'foursuperior',
+ 'fivesuperior',
+ 'sixsuperior',
+ 'sevensuperior',
+ 'eightsuperior',
+ 'ninesuperior',
+ 'zeroinferior',
+ 'oneinferior',
+ 'twoinferior',
+ 'threeinferior',
+ 'fourinferior',
+ 'fiveinferior',
+ 'sixinferior',
+ 'seveninferior',
+ 'eightinferior',
+ 'nineinferior',
+ 'centinferior',
+ 'dollarinferior',
+ 'periodinferior',
+ 'commainferior',
+ 'Agravesmall',
+ 'Aacutesmall',
+ 'Acircumflexsmall',
+ 'Atildesmall',
+ 'Adieresissmall',
+ 'Aringsmall',
+ 'AEsmall',
+ 'Ccedillasmall',
+ 'Egravesmall',
+ 'Eacutesmall',
+ 'Ecircumflexsmall',
+ 'Edieresissmall',
+ 'Igravesmall',
+ 'Iacutesmall',
+ 'Icircumflexsmall',
+ 'Idieresissmall',
+ 'Ethsmall',
+ 'Ntildesmall',
+ 'Ogravesmall',
+ 'Oacutesmall',
+ 'Ocircumflexsmall',
+ 'Otildesmall',
+ 'Odieresissmall',
+ 'OEsmall',
+ 'Oslashsmall',
+ 'Ugravesmall',
+ 'Uacutesmall',
+ 'Ucircumflexsmall',
+ 'Udieresissmall',
+ 'Yacutesmall',
+ 'Thornsmall',
+ 'Ydieresissmall'
+ ];
+ var ExpertSubsetCharset = [
+ '.notdef',
+ 'space',
+ 'dollaroldstyle',
+ 'dollarsuperior',
+ 'parenleftsuperior',
+ 'parenrightsuperior',
+ 'twodotenleader',
+ 'onedotenleader',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'fraction',
+ 'zerooldstyle',
+ 'oneoldstyle',
+ 'twooldstyle',
+ 'threeoldstyle',
+ 'fouroldstyle',
+ 'fiveoldstyle',
+ 'sixoldstyle',
+ 'sevenoldstyle',
+ 'eightoldstyle',
+ 'nineoldstyle',
+ 'colon',
+ 'semicolon',
+ 'commasuperior',
+ 'threequartersemdash',
+ 'periodsuperior',
+ 'asuperior',
+ 'bsuperior',
+ 'centsuperior',
+ 'dsuperior',
+ 'esuperior',
+ 'isuperior',
+ 'lsuperior',
+ 'msuperior',
+ 'nsuperior',
+ 'osuperior',
+ 'rsuperior',
+ 'ssuperior',
+ 'tsuperior',
+ 'ff',
+ 'fi',
+ 'fl',
+ 'ffi',
+ 'ffl',
+ 'parenleftinferior',
+ 'parenrightinferior',
+ 'hyphensuperior',
+ 'colonmonetary',
+ 'onefitted',
+ 'rupiah',
+ 'centoldstyle',
+ 'figuredash',
+ 'hypheninferior',
+ 'onequarter',
+ 'onehalf',
+ 'threequarters',
+ 'oneeighth',
+ 'threeeighths',
+ 'fiveeighths',
+ 'seveneighths',
+ 'onethird',
+ 'twothirds',
+ 'zerosuperior',
+ 'onesuperior',
+ 'twosuperior',
+ 'threesuperior',
+ 'foursuperior',
+ 'fivesuperior',
+ 'sixsuperior',
+ 'sevensuperior',
+ 'eightsuperior',
+ 'ninesuperior',
+ 'zeroinferior',
+ 'oneinferior',
+ 'twoinferior',
+ 'threeinferior',
+ 'fourinferior',
+ 'fiveinferior',
+ 'sixinferior',
+ 'seveninferior',
+ 'eightinferior',
+ 'nineinferior',
+ 'centinferior',
+ 'dollarinferior',
+ 'periodinferior',
+ 'commainferior'
+ ];
+ exports.ISOAdobeCharset = ISOAdobeCharset;
+ exports.ExpertCharset = ExpertCharset;
+ exports.ExpertSubsetCharset = ExpertSubsetCharset;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreEncodings = {});
+ }(this, function (exports) {
+ var ExpertEncoding = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'space',
+ 'exclamsmall',
+ 'Hungarumlautsmall',
+ '',
+ 'dollaroldstyle',
+ 'dollarsuperior',
+ 'ampersandsmall',
+ 'Acutesmall',
+ 'parenleftsuperior',
+ 'parenrightsuperior',
+ 'twodotenleader',
+ 'onedotenleader',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'fraction',
+ 'zerooldstyle',
+ 'oneoldstyle',
+ 'twooldstyle',
+ 'threeoldstyle',
+ 'fouroldstyle',
+ 'fiveoldstyle',
+ 'sixoldstyle',
+ 'sevenoldstyle',
+ 'eightoldstyle',
+ 'nineoldstyle',
+ 'colon',
+ 'semicolon',
+ 'commasuperior',
+ 'threequartersemdash',
+ 'periodsuperior',
+ 'questionsmall',
+ '',
+ 'asuperior',
+ 'bsuperior',
+ 'centsuperior',
+ 'dsuperior',
+ 'esuperior',
+ '',
+ '',
+ 'isuperior',
+ '',
+ '',
+ 'lsuperior',
+ 'msuperior',
+ 'nsuperior',
+ 'osuperior',
+ '',
+ '',
+ 'rsuperior',
+ 'ssuperior',
+ 'tsuperior',
+ '',
+ 'ff',
+ 'fi',
+ 'fl',
+ 'ffi',
+ 'ffl',
+ 'parenleftinferior',
+ '',
+ 'parenrightinferior',
+ 'Circumflexsmall',
+ 'hyphensuperior',
+ 'Gravesmall',
+ 'Asmall',
+ 'Bsmall',
+ 'Csmall',
+ 'Dsmall',
+ 'Esmall',
+ 'Fsmall',
+ 'Gsmall',
+ 'Hsmall',
+ 'Ismall',
+ 'Jsmall',
+ 'Ksmall',
+ 'Lsmall',
+ 'Msmall',
+ 'Nsmall',
+ 'Osmall',
+ 'Psmall',
+ 'Qsmall',
+ 'Rsmall',
+ 'Ssmall',
+ 'Tsmall',
+ 'Usmall',
+ 'Vsmall',
+ 'Wsmall',
+ 'Xsmall',
+ 'Ysmall',
+ 'Zsmall',
+ 'colonmonetary',
+ 'onefitted',
+ 'rupiah',
+ 'Tildesmall',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'exclamdownsmall',
+ 'centoldstyle',
+ 'Lslashsmall',
+ '',
+ '',
+ 'Scaronsmall',
+ 'Zcaronsmall',
+ 'Dieresissmall',
+ 'Brevesmall',
+ 'Caronsmall',
+ '',
+ 'Dotaccentsmall',
+ '',
+ '',
+ 'Macronsmall',
+ '',
+ '',
+ 'figuredash',
+ 'hypheninferior',
+ '',
+ '',
+ 'Ogoneksmall',
+ 'Ringsmall',
+ 'Cedillasmall',
+ '',
+ '',
+ '',
+ 'onequarter',
+ 'onehalf',
+ 'threequarters',
+ 'questiondownsmall',
+ 'oneeighth',
+ 'threeeighths',
+ 'fiveeighths',
+ 'seveneighths',
+ 'onethird',
+ 'twothirds',
+ '',
+ '',
+ 'zerosuperior',
+ 'onesuperior',
+ 'twosuperior',
+ 'threesuperior',
+ 'foursuperior',
+ 'fivesuperior',
+ 'sixsuperior',
+ 'sevensuperior',
+ 'eightsuperior',
+ 'ninesuperior',
+ 'zeroinferior',
+ 'oneinferior',
+ 'twoinferior',
+ 'threeinferior',
+ 'fourinferior',
+ 'fiveinferior',
+ 'sixinferior',
+ 'seveninferior',
+ 'eightinferior',
+ 'nineinferior',
+ 'centinferior',
+ 'dollarinferior',
+ 'periodinferior',
+ 'commainferior',
+ 'Agravesmall',
+ 'Aacutesmall',
+ 'Acircumflexsmall',
+ 'Atildesmall',
+ 'Adieresissmall',
+ 'Aringsmall',
+ 'AEsmall',
+ 'Ccedillasmall',
+ 'Egravesmall',
+ 'Eacutesmall',
+ 'Ecircumflexsmall',
+ 'Edieresissmall',
+ 'Igravesmall',
+ 'Iacutesmall',
+ 'Icircumflexsmall',
+ 'Idieresissmall',
+ 'Ethsmall',
+ 'Ntildesmall',
+ 'Ogravesmall',
+ 'Oacutesmall',
+ 'Ocircumflexsmall',
+ 'Otildesmall',
+ 'Odieresissmall',
+ 'OEsmall',
+ 'Oslashsmall',
+ 'Ugravesmall',
+ 'Uacutesmall',
+ 'Ucircumflexsmall',
+ 'Udieresissmall',
+ 'Yacutesmall',
+ 'Thornsmall',
+ 'Ydieresissmall'
+ ];
+ var MacExpertEncoding = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'space',
+ 'exclamsmall',
+ 'Hungarumlautsmall',
+ 'centoldstyle',
+ 'dollaroldstyle',
+ 'dollarsuperior',
+ 'ampersandsmall',
+ 'Acutesmall',
+ 'parenleftsuperior',
+ 'parenrightsuperior',
+ 'twodotenleader',
+ 'onedotenleader',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'fraction',
+ 'zerooldstyle',
+ 'oneoldstyle',
+ 'twooldstyle',
+ 'threeoldstyle',
+ 'fouroldstyle',
+ 'fiveoldstyle',
+ 'sixoldstyle',
+ 'sevenoldstyle',
+ 'eightoldstyle',
+ 'nineoldstyle',
+ 'colon',
+ 'semicolon',
+ '',
+ 'threequartersemdash',
+ '',
+ 'questionsmall',
+ '',
+ '',
+ '',
+ '',
+ 'Ethsmall',
+ '',
+ '',
+ 'onequarter',
+ 'onehalf',
+ 'threequarters',
+ 'oneeighth',
+ 'threeeighths',
+ 'fiveeighths',
+ 'seveneighths',
+ 'onethird',
+ 'twothirds',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'ff',
+ 'fi',
+ 'fl',
+ 'ffi',
+ 'ffl',
+ 'parenleftinferior',
+ '',
+ 'parenrightinferior',
+ 'Circumflexsmall',
+ 'hypheninferior',
+ 'Gravesmall',
+ 'Asmall',
+ 'Bsmall',
+ 'Csmall',
+ 'Dsmall',
+ 'Esmall',
+ 'Fsmall',
+ 'Gsmall',
+ 'Hsmall',
+ 'Ismall',
+ 'Jsmall',
+ 'Ksmall',
+ 'Lsmall',
+ 'Msmall',
+ 'Nsmall',
+ 'Osmall',
+ 'Psmall',
+ 'Qsmall',
+ 'Rsmall',
+ 'Ssmall',
+ 'Tsmall',
+ 'Usmall',
+ 'Vsmall',
+ 'Wsmall',
+ 'Xsmall',
+ 'Ysmall',
+ 'Zsmall',
+ 'colonmonetary',
+ 'onefitted',
+ 'rupiah',
+ 'Tildesmall',
+ '',
+ '',
+ 'asuperior',
+ 'centsuperior',
+ '',
+ '',
+ '',
+ '',
+ 'Aacutesmall',
+ 'Agravesmall',
+ 'Acircumflexsmall',
+ 'Adieresissmall',
+ 'Atildesmall',
+ 'Aringsmall',
+ 'Ccedillasmall',
+ 'Eacutesmall',
+ 'Egravesmall',
+ 'Ecircumflexsmall',
+ 'Edieresissmall',
+ 'Iacutesmall',
+ 'Igravesmall',
+ 'Icircumflexsmall',
+ 'Idieresissmall',
+ 'Ntildesmall',
+ 'Oacutesmall',
+ 'Ogravesmall',
+ 'Ocircumflexsmall',
+ 'Odieresissmall',
+ 'Otildesmall',
+ 'Uacutesmall',
+ 'Ugravesmall',
+ 'Ucircumflexsmall',
+ 'Udieresissmall',
+ '',
+ 'eightsuperior',
+ 'fourinferior',
+ 'threeinferior',
+ 'sixinferior',
+ 'eightinferior',
+ 'seveninferior',
+ 'Scaronsmall',
+ '',
+ 'centinferior',
+ 'twoinferior',
+ '',
+ 'Dieresissmall',
+ '',
+ 'Caronsmall',
+ 'osuperior',
+ 'fiveinferior',
+ '',
+ 'commainferior',
+ 'periodinferior',
+ 'Yacutesmall',
+ '',
+ 'dollarinferior',
+ '',
+ 'Thornsmall',
+ '',
+ 'nineinferior',
+ 'zeroinferior',
+ 'Zcaronsmall',
+ 'AEsmall',
+ 'Oslashsmall',
+ 'questiondownsmall',
+ 'oneinferior',
+ 'Lslashsmall',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'Cedillasmall',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'OEsmall',
+ 'figuredash',
+ 'hyphensuperior',
+ '',
+ '',
+ '',
+ '',
+ 'exclamdownsmall',
+ '',
+ 'Ydieresissmall',
+ '',
+ 'onesuperior',
+ 'twosuperior',
+ 'threesuperior',
+ 'foursuperior',
+ 'fivesuperior',
+ 'sixsuperior',
+ 'sevensuperior',
+ 'ninesuperior',
+ 'zerosuperior',
+ '',
+ 'esuperior',
+ 'rsuperior',
+ 'tsuperior',
+ '',
+ '',
+ 'isuperior',
+ 'ssuperior',
+ 'dsuperior',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'lsuperior',
+ 'Ogoneksmall',
+ 'Brevesmall',
+ 'Macronsmall',
+ 'bsuperior',
+ 'nsuperior',
+ 'msuperior',
+ 'commasuperior',
+ 'periodsuperior',
+ 'Dotaccentsmall',
+ 'Ringsmall'
+ ];
+ var MacRomanEncoding = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'space',
+ 'exclam',
+ 'quotedbl',
+ 'numbersign',
+ 'dollar',
+ 'percent',
+ 'ampersand',
+ 'quotesingle',
+ 'parenleft',
+ 'parenright',
+ 'asterisk',
+ 'plus',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'slash',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'at',
+ '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',
+ 'bracketleft',
+ 'backslash',
+ 'bracketright',
+ 'asciicircum',
+ 'underscore',
+ 'grave',
+ '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',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'asciitilde',
+ '',
+ 'Adieresis',
+ 'Aring',
+ 'Ccedilla',
+ 'Eacute',
+ 'Ntilde',
+ 'Odieresis',
+ 'Udieresis',
+ 'aacute',
+ 'agrave',
+ 'acircumflex',
+ 'adieresis',
+ 'atilde',
+ 'aring',
+ 'ccedilla',
+ 'eacute',
+ 'egrave',
+ 'ecircumflex',
+ 'edieresis',
+ 'iacute',
+ 'igrave',
+ 'icircumflex',
+ 'idieresis',
+ 'ntilde',
+ 'oacute',
+ 'ograve',
+ 'ocircumflex',
+ 'odieresis',
+ 'otilde',
+ 'uacute',
+ 'ugrave',
+ 'ucircumflex',
+ 'udieresis',
+ 'dagger',
+ 'degree',
+ 'cent',
+ 'sterling',
+ 'section',
+ 'bullet',
+ 'paragraph',
+ 'germandbls',
+ 'registered',
+ 'copyright',
+ 'trademark',
+ 'acute',
+ 'dieresis',
+ 'notequal',
+ 'AE',
+ 'Oslash',
+ 'infinity',
+ 'plusminus',
+ 'lessequal',
+ 'greaterequal',
+ 'yen',
+ 'mu',
+ 'partialdiff',
+ 'summation',
+ 'product',
+ 'pi',
+ 'integral',
+ 'ordfeminine',
+ 'ordmasculine',
+ 'Omega',
+ 'ae',
+ 'oslash',
+ 'questiondown',
+ 'exclamdown',
+ 'logicalnot',
+ 'radical',
+ 'florin',
+ 'approxequal',
+ 'Delta',
+ 'guillemotleft',
+ 'guillemotright',
+ 'ellipsis',
+ 'space',
+ 'Agrave',
+ 'Atilde',
+ 'Otilde',
+ 'OE',
+ 'oe',
+ 'endash',
+ 'emdash',
+ 'quotedblleft',
+ 'quotedblright',
+ 'quoteleft',
+ 'quoteright',
+ 'divide',
+ 'lozenge',
+ 'ydieresis',
+ 'Ydieresis',
+ 'fraction',
+ 'currency',
+ 'guilsinglleft',
+ 'guilsinglright',
+ 'fi',
+ 'fl',
+ 'daggerdbl',
+ 'periodcentered',
+ 'quotesinglbase',
+ 'quotedblbase',
+ 'perthousand',
+ 'Acircumflex',
+ 'Ecircumflex',
+ 'Aacute',
+ 'Edieresis',
+ 'Egrave',
+ 'Iacute',
+ 'Icircumflex',
+ 'Idieresis',
+ 'Igrave',
+ 'Oacute',
+ 'Ocircumflex',
+ 'apple',
+ 'Ograve',
+ 'Uacute',
+ 'Ucircumflex',
+ 'Ugrave',
+ 'dotlessi',
+ 'circumflex',
+ 'tilde',
+ 'macron',
+ 'breve',
+ 'dotaccent',
+ 'ring',
+ 'cedilla',
+ 'hungarumlaut',
+ 'ogonek',
+ 'caron'
+ ];
+ var StandardEncoding = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'space',
+ 'exclam',
+ 'quotedbl',
+ 'numbersign',
+ 'dollar',
+ 'percent',
+ 'ampersand',
+ 'quoteright',
+ 'parenleft',
+ 'parenright',
+ 'asterisk',
+ 'plus',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'slash',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'at',
+ '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',
+ 'bracketleft',
+ 'backslash',
+ 'bracketright',
+ 'asciicircum',
+ 'underscore',
+ 'quoteleft',
+ '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',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'asciitilde',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'exclamdown',
+ 'cent',
+ 'sterling',
+ 'fraction',
+ 'yen',
+ 'florin',
+ 'section',
+ 'currency',
+ 'quotesingle',
+ 'quotedblleft',
+ 'guillemotleft',
+ 'guilsinglleft',
+ 'guilsinglright',
+ 'fi',
+ 'fl',
+ '',
+ 'endash',
+ 'dagger',
+ 'daggerdbl',
+ 'periodcentered',
+ '',
+ 'paragraph',
+ 'bullet',
+ 'quotesinglbase',
+ 'quotedblbase',
+ 'quotedblright',
+ 'guillemotright',
+ 'ellipsis',
+ 'perthousand',
+ '',
+ 'questiondown',
+ '',
+ 'grave',
+ 'acute',
+ 'circumflex',
+ 'tilde',
+ 'macron',
+ 'breve',
+ 'dotaccent',
+ 'dieresis',
+ '',
+ 'ring',
+ 'cedilla',
+ '',
+ 'hungarumlaut',
+ 'ogonek',
+ 'caron',
+ 'emdash',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'AE',
+ '',
+ 'ordfeminine',
+ '',
+ '',
+ '',
+ '',
+ 'Lslash',
+ 'Oslash',
+ 'OE',
+ 'ordmasculine',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'ae',
+ '',
+ '',
+ '',
+ 'dotlessi',
+ '',
+ '',
+ 'lslash',
+ 'oslash',
+ 'oe',
+ 'germandbls'
+ ];
+ var WinAnsiEncoding = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'space',
+ 'exclam',
+ 'quotedbl',
+ 'numbersign',
+ 'dollar',
+ 'percent',
+ 'ampersand',
+ 'quotesingle',
+ 'parenleft',
+ 'parenright',
+ 'asterisk',
+ 'plus',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'slash',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'at',
+ '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',
+ 'bracketleft',
+ 'backslash',
+ 'bracketright',
+ 'asciicircum',
+ 'underscore',
+ 'grave',
+ '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',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'asciitilde',
+ 'bullet',
+ 'Euro',
+ 'bullet',
+ 'quotesinglbase',
+ 'florin',
+ 'quotedblbase',
+ 'ellipsis',
+ 'dagger',
+ 'daggerdbl',
+ 'circumflex',
+ 'perthousand',
+ 'Scaron',
+ 'guilsinglleft',
+ 'OE',
+ 'bullet',
+ 'Zcaron',
+ 'bullet',
+ 'bullet',
+ 'quoteleft',
+ 'quoteright',
+ 'quotedblleft',
+ 'quotedblright',
+ 'bullet',
+ 'endash',
+ 'emdash',
+ 'tilde',
+ 'trademark',
+ 'scaron',
+ 'guilsinglright',
+ 'oe',
+ 'bullet',
+ 'zcaron',
+ 'Ydieresis',
+ 'space',
+ 'exclamdown',
+ 'cent',
+ 'sterling',
+ 'currency',
+ 'yen',
+ 'brokenbar',
+ 'section',
+ 'dieresis',
+ 'copyright',
+ 'ordfeminine',
+ 'guillemotleft',
+ 'logicalnot',
+ 'hyphen',
+ 'registered',
+ 'macron',
+ 'degree',
+ 'plusminus',
+ 'twosuperior',
+ 'threesuperior',
+ 'acute',
+ 'mu',
+ 'paragraph',
+ 'periodcentered',
+ 'cedilla',
+ 'onesuperior',
+ 'ordmasculine',
+ 'guillemotright',
+ 'onequarter',
+ 'onehalf',
+ 'threequarters',
+ 'questiondown',
+ 'Agrave',
+ 'Aacute',
+ 'Acircumflex',
+ 'Atilde',
+ 'Adieresis',
+ 'Aring',
+ 'AE',
+ 'Ccedilla',
+ 'Egrave',
+ 'Eacute',
+ 'Ecircumflex',
+ 'Edieresis',
+ 'Igrave',
+ 'Iacute',
+ 'Icircumflex',
+ 'Idieresis',
+ 'Eth',
+ 'Ntilde',
+ 'Ograve',
+ 'Oacute',
+ 'Ocircumflex',
+ 'Otilde',
+ 'Odieresis',
+ 'multiply',
+ 'Oslash',
+ 'Ugrave',
+ 'Uacute',
+ 'Ucircumflex',
+ 'Udieresis',
+ 'Yacute',
+ 'Thorn',
+ 'germandbls',
+ 'agrave',
+ 'aacute',
+ 'acircumflex',
+ 'atilde',
+ 'adieresis',
+ 'aring',
+ 'ae',
+ 'ccedilla',
+ 'egrave',
+ 'eacute',
+ 'ecircumflex',
+ 'edieresis',
+ 'igrave',
+ 'iacute',
+ 'icircumflex',
+ 'idieresis',
+ 'eth',
+ 'ntilde',
+ 'ograve',
+ 'oacute',
+ 'ocircumflex',
+ 'otilde',
+ 'odieresis',
+ 'divide',
+ 'oslash',
+ 'ugrave',
+ 'uacute',
+ 'ucircumflex',
+ 'udieresis',
+ 'yacute',
+ 'thorn',
+ 'ydieresis'
+ ];
+ var SymbolSetEncoding = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'space',
+ 'exclam',
+ 'universal',
+ 'numbersign',
+ 'existential',
+ 'percent',
+ 'ampersand',
+ 'suchthat',
+ 'parenleft',
+ 'parenright',
+ 'asteriskmath',
+ 'plus',
+ 'comma',
+ 'minus',
+ 'period',
+ 'slash',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'congruent',
+ 'Alpha',
+ 'Beta',
+ 'Chi',
+ 'Delta',
+ 'Epsilon',
+ 'Phi',
+ 'Gamma',
+ 'Eta',
+ 'Iota',
+ 'theta1',
+ 'Kappa',
+ 'Lambda',
+ 'Mu',
+ 'Nu',
+ 'Omicron',
+ 'Pi',
+ 'Theta',
+ 'Rho',
+ 'Sigma',
+ 'Tau',
+ 'Upsilon',
+ 'sigma1',
+ 'Omega',
+ 'Xi',
+ 'Psi',
+ 'Zeta',
+ 'bracketleft',
+ 'therefore',
+ 'bracketright',
+ 'perpendicular',
+ 'underscore',
+ 'radicalex',
+ 'alpha',
+ 'beta',
+ 'chi',
+ 'delta',
+ 'epsilon',
+ 'phi',
+ 'gamma',
+ 'eta',
+ 'iota',
+ 'phi1',
+ 'kappa',
+ 'lambda',
+ 'mu',
+ 'nu',
+ 'omicron',
+ 'pi',
+ 'theta',
+ 'rho',
+ 'sigma',
+ 'tau',
+ 'upsilon',
+ 'omega1',
+ 'omega',
+ 'xi',
+ 'psi',
+ 'zeta',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'similar',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'Euro',
+ 'Upsilon1',
+ 'minute',
+ 'lessequal',
+ 'fraction',
+ 'infinity',
+ 'florin',
+ 'club',
+ 'diamond',
+ 'heart',
+ 'spade',
+ 'arrowboth',
+ 'arrowleft',
+ 'arrowup',
+ 'arrowright',
+ 'arrowdown',
+ 'degree',
+ 'plusminus',
+ 'second',
+ 'greaterequal',
+ 'multiply',
+ 'proportional',
+ 'partialdiff',
+ 'bullet',
+ 'divide',
+ 'notequal',
+ 'equivalence',
+ 'approxequal',
+ 'ellipsis',
+ 'arrowvertex',
+ 'arrowhorizex',
+ 'carriagereturn',
+ 'aleph',
+ 'Ifraktur',
+ 'Rfraktur',
+ 'weierstrass',
+ 'circlemultiply',
+ 'circleplus',
+ 'emptyset',
+ 'intersection',
+ 'union',
+ 'propersuperset',
+ 'reflexsuperset',
+ 'notsubset',
+ 'propersubset',
+ 'reflexsubset',
+ 'element',
+ 'notelement',
+ 'angle',
+ 'gradient',
+ 'registerserif',
+ 'copyrightserif',
+ 'trademarkserif',
+ 'product',
+ 'radical',
+ 'dotmath',
+ 'logicalnot',
+ 'logicaland',
+ 'logicalor',
+ 'arrowdblboth',
+ 'arrowdblleft',
+ 'arrowdblup',
+ 'arrowdblright',
+ 'arrowdbldown',
+ 'lozenge',
+ 'angleleft',
+ 'registersans',
+ 'copyrightsans',
+ 'trademarksans',
+ 'summation',
+ 'parenlefttp',
+ 'parenleftex',
+ 'parenleftbt',
+ 'bracketlefttp',
+ 'bracketleftex',
+ 'bracketleftbt',
+ 'bracelefttp',
+ 'braceleftmid',
+ 'braceleftbt',
+ 'braceex',
+ '',
+ 'angleright',
+ 'integral',
+ 'integraltp',
+ 'integralex',
+ 'integralbt',
+ 'parenrighttp',
+ 'parenrightex',
+ 'parenrightbt',
+ 'bracketrighttp',
+ 'bracketrightex',
+ 'bracketrightbt',
+ 'bracerighttp',
+ 'bracerightmid',
+ 'bracerightbt'
+ ];
+ var ZapfDingbatsEncoding = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'space',
+ 'a1',
+ 'a2',
+ 'a202',
+ 'a3',
+ 'a4',
+ 'a5',
+ 'a119',
+ 'a118',
+ 'a117',
+ 'a11',
+ 'a12',
+ 'a13',
+ 'a14',
+ 'a15',
+ 'a16',
+ 'a105',
+ 'a17',
+ 'a18',
+ 'a19',
+ 'a20',
+ 'a21',
+ 'a22',
+ 'a23',
+ 'a24',
+ 'a25',
+ 'a26',
+ 'a27',
+ 'a28',
+ 'a6',
+ 'a7',
+ 'a8',
+ 'a9',
+ 'a10',
+ 'a29',
+ 'a30',
+ 'a31',
+ 'a32',
+ 'a33',
+ 'a34',
+ 'a35',
+ 'a36',
+ 'a37',
+ 'a38',
+ 'a39',
+ 'a40',
+ 'a41',
+ 'a42',
+ 'a43',
+ 'a44',
+ 'a45',
+ 'a46',
+ 'a47',
+ 'a48',
+ 'a49',
+ 'a50',
+ 'a51',
+ 'a52',
+ 'a53',
+ 'a54',
+ 'a55',
+ 'a56',
+ 'a57',
+ 'a58',
+ 'a59',
+ 'a60',
+ 'a61',
+ 'a62',
+ 'a63',
+ 'a64',
+ 'a65',
+ 'a66',
+ 'a67',
+ 'a68',
+ 'a69',
+ 'a70',
+ 'a71',
+ 'a72',
+ 'a73',
+ 'a74',
+ 'a203',
+ 'a75',
+ 'a204',
+ 'a76',
+ 'a77',
+ 'a78',
+ 'a79',
+ 'a81',
+ 'a82',
+ 'a83',
+ 'a84',
+ 'a97',
+ 'a98',
+ 'a99',
+ 'a100',
+ '',
+ 'a89',
+ 'a90',
+ 'a93',
+ 'a94',
+ 'a91',
+ 'a92',
+ 'a205',
+ 'a85',
+ 'a206',
+ 'a86',
+ 'a87',
+ 'a88',
+ 'a95',
+ 'a96',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'a101',
+ 'a102',
+ 'a103',
+ 'a104',
+ 'a106',
+ 'a107',
+ 'a108',
+ 'a112',
+ 'a111',
+ 'a110',
+ 'a109',
+ 'a120',
+ 'a121',
+ 'a122',
+ 'a123',
+ 'a124',
+ 'a125',
+ 'a126',
+ 'a127',
+ 'a128',
+ 'a129',
+ 'a130',
+ 'a131',
+ 'a132',
+ 'a133',
+ 'a134',
+ 'a135',
+ 'a136',
+ 'a137',
+ 'a138',
+ 'a139',
+ 'a140',
+ 'a141',
+ 'a142',
+ 'a143',
+ 'a144',
+ 'a145',
+ 'a146',
+ 'a147',
+ 'a148',
+ 'a149',
+ 'a150',
+ 'a151',
+ 'a152',
+ 'a153',
+ 'a154',
+ 'a155',
+ 'a156',
+ 'a157',
+ 'a158',
+ 'a159',
+ 'a160',
+ 'a161',
+ 'a163',
+ 'a164',
+ 'a196',
+ 'a165',
+ 'a192',
+ 'a166',
+ 'a167',
+ 'a168',
+ 'a169',
+ 'a170',
+ 'a171',
+ 'a172',
+ 'a173',
+ 'a162',
+ 'a174',
+ 'a175',
+ 'a176',
+ 'a177',
+ 'a178',
+ 'a179',
+ 'a193',
+ 'a180',
+ 'a199',
+ 'a181',
+ 'a200',
+ 'a182',
+ '',
+ 'a201',
+ 'a183',
+ 'a184',
+ 'a197',
+ 'a185',
+ 'a194',
+ 'a198',
+ 'a186',
+ 'a195',
+ 'a187',
+ 'a188',
+ 'a189',
+ 'a190',
+ 'a191'
+ ];
+ function getEncoding(encodingName) {
+ switch (encodingName) {
+ case 'WinAnsiEncoding':
+ return WinAnsiEncoding;
+ case 'StandardEncoding':
+ return StandardEncoding;
+ case 'MacRomanEncoding':
+ return MacRomanEncoding;
+ case 'SymbolSetEncoding':
+ return SymbolSetEncoding;
+ case 'ZapfDingbatsEncoding':
+ return ZapfDingbatsEncoding;
+ case 'ExpertEncoding':
+ return ExpertEncoding;
+ case 'MacExpertEncoding':
+ return MacExpertEncoding;
+ default:
+ return null;
+ }
+ }
+ exports.WinAnsiEncoding = WinAnsiEncoding;
+ exports.StandardEncoding = StandardEncoding;
+ exports.MacRomanEncoding = MacRomanEncoding;
+ exports.SymbolSetEncoding = SymbolSetEncoding;
+ exports.ZapfDingbatsEncoding = ZapfDingbatsEncoding;
+ exports.ExpertEncoding = ExpertEncoding;
+ exports.getEncoding = getEncoding;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsSharedUtil = {});
+ }(this, function (exports) {
+ var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this;
+ var FONT_IDENTITY_MATRIX = [
+ 0.001,
+ 0,
+ 0,
+ 0.001,
+ 0,
+ 0
+ ];
+ var TextRenderingMode = {
+ FILL: 0,
+ STROKE: 1,
+ FILL_STROKE: 2,
+ INVISIBLE: 3,
+ FILL_ADD_TO_PATH: 4,
+ STROKE_ADD_TO_PATH: 5,
+ FILL_STROKE_ADD_TO_PATH: 6,
+ ADD_TO_PATH: 7,
+ FILL_STROKE_MASK: 3,
+ ADD_TO_PATH_FLAG: 4
+ };
+ var ImageKind = {
+ GRAYSCALE_1BPP: 1,
+ RGB_24BPP: 2,
+ RGBA_32BPP: 3
+ };
+ var AnnotationType = {
+ TEXT: 1,
+ LINK: 2,
+ FREETEXT: 3,
+ LINE: 4,
+ SQUARE: 5,
+ CIRCLE: 6,
+ POLYGON: 7,
+ POLYLINE: 8,
+ HIGHLIGHT: 9,
+ UNDERLINE: 10,
+ SQUIGGLY: 11,
+ STRIKEOUT: 12,
+ STAMP: 13,
+ CARET: 14,
+ INK: 15,
+ POPUP: 16,
+ FILEATTACHMENT: 17,
+ SOUND: 18,
+ MOVIE: 19,
+ WIDGET: 20,
+ SCREEN: 21,
+ PRINTERMARK: 22,
+ TRAPNET: 23,
+ WATERMARK: 24,
+ THREED: 25,
+ REDACT: 26
+ };
+ var AnnotationFlag = {
+ INVISIBLE: 0x01,
+ HIDDEN: 0x02,
+ PRINT: 0x04,
+ NOZOOM: 0x08,
+ NOROTATE: 0x10,
+ NOVIEW: 0x20,
+ READONLY: 0x40,
+ LOCKED: 0x80,
+ TOGGLENOVIEW: 0x100,
+ LOCKEDCONTENTS: 0x200
+ };
+ var AnnotationFieldFlag = {
+ READONLY: 0x0000001,
+ REQUIRED: 0x0000002,
+ NOEXPORT: 0x0000004,
+ MULTILINE: 0x0001000,
+ PASSWORD: 0x0002000,
+ NOTOGGLETOOFF: 0x0004000,
+ RADIO: 0x0008000,
+ PUSHBUTTON: 0x0010000,
+ COMBO: 0x0020000,
+ EDIT: 0x0040000,
+ SORT: 0x0080000,
+ FILESELECT: 0x0100000,
+ MULTISELECT: 0x0200000,
+ DONOTSPELLCHECK: 0x0400000,
+ DONOTSCROLL: 0x0800000,
+ COMB: 0x1000000,
+ RICHTEXT: 0x2000000,
+ RADIOSINUNISON: 0x2000000,
+ COMMITONSELCHANGE: 0x4000000
+ };
+ var AnnotationBorderStyleType = {
+ SOLID: 1,
+ DASHED: 2,
+ BEVELED: 3,
+ INSET: 4,
+ UNDERLINE: 5
+ };
+ var StreamType = {
+ UNKNOWN: 0,
+ FLATE: 1,
+ LZW: 2,
+ DCT: 3,
+ JPX: 4,
+ JBIG: 5,
+ A85: 6,
+ AHX: 7,
+ CCF: 8,
+ RL: 9
+ };
+ var FontType = {
+ UNKNOWN: 0,
+ TYPE1: 1,
+ TYPE1C: 2,
+ CIDFONTTYPE0: 3,
+ CIDFONTTYPE0C: 4,
+ TRUETYPE: 5,
+ CIDFONTTYPE2: 6,
+ TYPE3: 7,
+ OPENTYPE: 8,
+ TYPE0: 9,
+ MMTYPE1: 10
+ };
+ var VERBOSITY_LEVELS = {
+ errors: 0,
+ warnings: 1,
+ infos: 5
+ };
+ // All the possible operations for an operator list.
+ var OPS = {
+ // Intentionally start from 1 so it is easy to spot bad operators that will be
+ // 0's.
+ dependency: 1,
+ setLineWidth: 2,
+ setLineCap: 3,
+ setLineJoin: 4,
+ setMiterLimit: 5,
+ setDash: 6,
+ setRenderingIntent: 7,
+ setFlatness: 8,
+ setGState: 9,
+ save: 10,
+ restore: 11,
+ transform: 12,
+ moveTo: 13,
+ lineTo: 14,
+ curveTo: 15,
+ curveTo2: 16,
+ curveTo3: 17,
+ closePath: 18,
+ rectangle: 19,
+ stroke: 20,
+ closeStroke: 21,
+ fill: 22,
+ eoFill: 23,
+ fillStroke: 24,
+ eoFillStroke: 25,
+ closeFillStroke: 26,
+ closeEOFillStroke: 27,
+ endPath: 28,
+ clip: 29,
+ eoClip: 30,
+ beginText: 31,
+ endText: 32,
+ setCharSpacing: 33,
+ setWordSpacing: 34,
+ setHScale: 35,
+ setLeading: 36,
+ setFont: 37,
+ setTextRenderingMode: 38,
+ setTextRise: 39,
+ moveText: 40,
+ setLeadingMoveText: 41,
+ setTextMatrix: 42,
+ nextLine: 43,
+ showText: 44,
+ showSpacedText: 45,
+ nextLineShowText: 46,
+ nextLineSetSpacingShowText: 47,
+ setCharWidth: 48,
+ setCharWidthAndBounds: 49,
+ setStrokeColorSpace: 50,
+ setFillColorSpace: 51,
+ setStrokeColor: 52,
+ setStrokeColorN: 53,
+ setFillColor: 54,
+ setFillColorN: 55,
+ setStrokeGray: 56,
+ setFillGray: 57,
+ setStrokeRGBColor: 58,
+ setFillRGBColor: 59,
+ setStrokeCMYKColor: 60,
+ setFillCMYKColor: 61,
+ shadingFill: 62,
+ beginInlineImage: 63,
+ beginImageData: 64,
+ endInlineImage: 65,
+ paintXObject: 66,
+ markPoint: 67,
+ markPointProps: 68,
+ beginMarkedContent: 69,
+ beginMarkedContentProps: 70,
+ endMarkedContent: 71,
+ beginCompat: 72,
+ endCompat: 73,
+ paintFormXObjectBegin: 74,
+ paintFormXObjectEnd: 75,
+ beginGroup: 76,
+ endGroup: 77,
+ beginAnnotations: 78,
+ endAnnotations: 79,
+ beginAnnotation: 80,
+ endAnnotation: 81,
+ paintJpegXObject: 82,
+ paintImageMaskXObject: 83,
+ paintImageMaskXObjectGroup: 84,
+ paintImageXObject: 85,
+ paintInlineImageXObject: 86,
+ paintInlineImageXObjectGroup: 87,
+ paintImageXObjectRepeat: 88,
+ paintImageMaskXObjectRepeat: 89,
+ paintSolidColorImageMask: 90,
+ constructPath: 91
+ };
+ var verbosity = VERBOSITY_LEVELS.warnings;
+ function setVerbosityLevel(level) {
+ verbosity = level;
+ }
+ function getVerbosityLevel() {
+ return verbosity;
+ }
+ // A notice for devs. These are good for things that are helpful to devs, such
+ // as warning that Workers were disabled, which is important to devs but not
+ // end users.
+ function info(msg) {
+ if (verbosity >= VERBOSITY_LEVELS.infos) {
+ console.log('Info: ' + msg);
+ }
+ }
+ // Non-fatal warnings.
+ function warn(msg) {
+ if (verbosity >= VERBOSITY_LEVELS.warnings) {
+ console.log('Warning: ' + msg);
+ }
+ }
+ // Deprecated API function -- display regardless of the PDFJS.verbosity setting.
+ function deprecated(details) {
+ console.log('Deprecated API usage: ' + details);
+ }
+ // Fatal errors that should trigger the fallback UI and halt execution by
+ // throwing an exception.
+ function error(msg) {
+ if (verbosity >= VERBOSITY_LEVELS.errors) {
+ console.log('Error: ' + msg);
+ console.log(backtrace());
+ }
+ throw new Error(msg);
+ }
+ function backtrace() {
+ try {
+ throw new Error();
+ } catch (e) {
+ return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+ }
+ }
+ function assert(cond, msg) {
+ if (!cond) {
+ error(msg);
+ }
+ }
+ var UNSUPPORTED_FEATURES = {
+ unknown: 'unknown',
+ forms: 'forms',
+ javaScript: 'javaScript',
+ smask: 'smask',
+ shadingPattern: 'shadingPattern',
+ font: 'font'
+ };
+ // Checks if URLs have the same origin. For non-HTTP based URLs, returns false.
+ function isSameOrigin(baseUrl, otherUrl) {
+ try {
+ var base = new URL(baseUrl);
+ if (!base.origin || base.origin === 'null') {
+ return false;
+ }
+ } // non-HTTP url
+ catch (e) {
+ return false;
+ }
+ var other = new URL(otherUrl, base);
+ return base.origin === other.origin;
+ }
+ // Checks if URLs use one of the whitelisted protocols, e.g. to avoid XSS.
+ function isValidProtocol(url) {
+ if (!url) {
+ return false;
+ }
+ switch (url.protocol) {
+ case 'http:':
+ case 'https:':
+ case 'ftp:':
+ case 'mailto:':
+ case 'tel:':
+ return true;
+ default:
+ return false;
+ }
+ }
+ /**
+ * Attempts to create a valid absolute URL (utilizing `isValidProtocol`).
+ * @param {URL|string} url - An absolute, or relative, URL.
+ * @param {URL|string} baseUrl - An absolute URL.
+ * @returns Either a valid {URL}, or `null` otherwise.
+ */
+ function createValidAbsoluteUrl(url, baseUrl) {
+ if (!url) {
+ return null;
+ }
+ try {
+ var absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
+ if (isValidProtocol(absoluteUrl)) {
+ return absoluteUrl;
+ }
+ } catch (ex) {
+ }
+ return null;
+ }
+ function shadow(obj, prop, value) {
+ Object.defineProperty(obj, prop, {
+ value: value,
+ enumerable: true,
+ configurable: true,
+ writable: false
+ });
+ return value;
+ }
+ function getLookupTableFactory(initializer) {
+ var lookup;
+ return function () {
+ if (initializer) {
+ lookup = Object.create(null);
+ initializer(lookup);
+ initializer = null;
+ }
+ return lookup;
+ };
+ }
+ var PasswordResponses = {
+ NEED_PASSWORD: 1,
+ INCORRECT_PASSWORD: 2
+ };
+ var PasswordException = function PasswordExceptionClosure() {
+ function PasswordException(msg, code) {
+ this.name = 'PasswordException';
+ this.message = msg;
+ this.code = code;
+ }
+ PasswordException.prototype = new Error();
+ PasswordException.constructor = PasswordException;
+ return PasswordException;
+ }();
+ var UnknownErrorException = function UnknownErrorExceptionClosure() {
+ function UnknownErrorException(msg, details) {
+ this.name = 'UnknownErrorException';
+ this.message = msg;
+ this.details = details;
+ }
+ UnknownErrorException.prototype = new Error();
+ UnknownErrorException.constructor = UnknownErrorException;
+ return UnknownErrorException;
+ }();
+ var InvalidPDFException = function InvalidPDFExceptionClosure() {
+ function InvalidPDFException(msg) {
+ this.name = 'InvalidPDFException';
+ this.message = msg;
+ }
+ InvalidPDFException.prototype = new Error();
+ InvalidPDFException.constructor = InvalidPDFException;
+ return InvalidPDFException;
+ }();
+ var MissingPDFException = function MissingPDFExceptionClosure() {
+ function MissingPDFException(msg) {
+ this.name = 'MissingPDFException';
+ this.message = msg;
+ }
+ MissingPDFException.prototype = new Error();
+ MissingPDFException.constructor = MissingPDFException;
+ return MissingPDFException;
+ }();
+ var UnexpectedResponseException = function UnexpectedResponseExceptionClosure() {
+ function UnexpectedResponseException(msg, status) {
+ this.name = 'UnexpectedResponseException';
+ this.message = msg;
+ this.status = status;
+ }
+ UnexpectedResponseException.prototype = new Error();
+ UnexpectedResponseException.constructor = UnexpectedResponseException;
+ return UnexpectedResponseException;
+ }();
+ var NotImplementedException = function NotImplementedExceptionClosure() {
+ function NotImplementedException(msg) {
+ this.message = msg;
+ }
+ NotImplementedException.prototype = new Error();
+ NotImplementedException.prototype.name = 'NotImplementedException';
+ NotImplementedException.constructor = NotImplementedException;
+ return NotImplementedException;
+ }();
+ var MissingDataException = function MissingDataExceptionClosure() {
+ function MissingDataException(begin, end) {
+ this.begin = begin;
+ this.end = end;
+ this.message = 'Missing data [' + begin + ', ' + end + ')';
+ }
+ MissingDataException.prototype = new Error();
+ MissingDataException.prototype.name = 'MissingDataException';
+ MissingDataException.constructor = MissingDataException;
+ return MissingDataException;
+ }();
+ var XRefParseException = function XRefParseExceptionClosure() {
+ function XRefParseException(msg) {
+ this.message = msg;
+ }
+ XRefParseException.prototype = new Error();
+ XRefParseException.prototype.name = 'XRefParseException';
+ XRefParseException.constructor = XRefParseException;
+ return XRefParseException;
+ }();
+ var NullCharactersRegExp = /\x00/g;
+ function removeNullCharacters(str) {
+ if (typeof str !== 'string') {
+ warn('The argument for removeNullCharacters must be a string.');
+ return str;
+ }
+ return str.replace(NullCharactersRegExp, '');
+ }
+ function bytesToString(bytes) {
+ assert(bytes !== null && typeof bytes === 'object' && bytes.length !== undefined, 'Invalid argument for bytesToString');
+ var length = bytes.length;
+ var MAX_ARGUMENT_COUNT = 8192;
+ if (length < MAX_ARGUMENT_COUNT) {
+ return String.fromCharCode.apply(null, bytes);
+ }
+ var strBuf = [];
+ for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
+ var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
+ var chunk = bytes.subarray(i, chunkEnd);
+ strBuf.push(String.fromCharCode.apply(null, chunk));
+ }
+ return strBuf.join('');
+ }
+ function stringToBytes(str) {
+ assert(typeof str === 'string', 'Invalid argument for stringToBytes');
+ var length = str.length;
+ var bytes = new Uint8Array(length);
+ for (var i = 0; i < length; ++i) {
+ bytes[i] = str.charCodeAt(i) & 0xFF;
+ }
+ return bytes;
+ }
+ /**
+ * Gets length of the array (Array, Uint8Array, or string) in bytes.
+ * @param {Array|Uint8Array|string} arr
+ * @returns {number}
+ */
+ function arrayByteLength(arr) {
+ if (arr.length !== undefined) {
+ return arr.length;
+ }
+ assert(arr.byteLength !== undefined);
+ return arr.byteLength;
+ }
+ /**
+ * Combines array items (arrays) into single Uint8Array object.
+ * @param {Array} arr - the array of the arrays (Array, Uint8Array, or string).
+ * @returns {Uint8Array}
+ */
+ function arraysToBytes(arr) {
+ // Shortcut: if first and only item is Uint8Array, return it.
+ if (arr.length === 1 && arr[0] instanceof Uint8Array) {
+ return arr[0];
+ }
+ var resultLength = 0;
+ var i, ii = arr.length;
+ var item, itemLength;
+ for (i = 0; i < ii; i++) {
+ item = arr[i];
+ itemLength = arrayByteLength(item);
+ resultLength += itemLength;
+ }
+ var pos = 0;
+ var data = new Uint8Array(resultLength);
+ for (i = 0; i < ii; i++) {
+ item = arr[i];
+ if (!(item instanceof Uint8Array)) {
+ if (typeof item === 'string') {
+ item = stringToBytes(item);
+ } else {
+ item = new Uint8Array(item);
+ }
+ }
+ itemLength = item.byteLength;
+ data.set(item, pos);
+ pos += itemLength;
+ }
+ return data;
+ }
+ function string32(value) {
+ return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
+ }
+ function log2(x) {
+ var n = 1, i = 0;
+ while (x > n) {
+ n <<= 1;
+ i++;
+ }
+ return i;
+ }
+ function readInt8(data, start) {
+ return data[start] << 24 >> 24;
+ }
+ function readUint16(data, offset) {
+ return data[offset] << 8 | data[offset + 1];
+ }
+ function readUint32(data, offset) {
+ return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
+ }
+ // Lazy test the endianness of the platform
+ // NOTE: This will be 'true' for simulated TypedArrays
+ function isLittleEndian() {
+ var buffer8 = new Uint8Array(2);
+ buffer8[0] = 1;
+ var buffer16 = new Uint16Array(buffer8.buffer);
+ return buffer16[0] === 1;
+ }
+ // Checks if it's possible to eval JS expressions.
+ function isEvalSupported() {
+ try {
+ new Function('');
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+ var IDENTITY_MATRIX = [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0
+ ];
+ var Util = function UtilClosure() {
+ function Util() {
+ }
+ var rgbBuf = [
+ 'rgb(',
+ 0,
+ ',',
+ 0,
+ ',',
+ 0,
+ ')'
+ ];
+ // makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids
+ // creating many intermediate strings.
+ Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
+ rgbBuf[1] = r;
+ rgbBuf[3] = g;
+ rgbBuf[5] = b;
+ return rgbBuf.join('');
+ };
+ // Concatenates two transformation matrices together and returns the result.
+ Util.transform = function Util_transform(m1, m2) {
+ return [
+ m1[0] * m2[0] + m1[2] * m2[1],
+ m1[1] * m2[0] + m1[3] * m2[1],
+ m1[0] * m2[2] + m1[2] * m2[3],
+ m1[1] * m2[2] + m1[3] * m2[3],
+ m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
+ m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
+ ];
+ };
+ // For 2d affine transforms
+ Util.applyTransform = function Util_applyTransform(p, m) {
+ var xt = p[0] * m[0] + p[1] * m[2] + m[4];
+ var yt = p[0] * m[1] + p[1] * m[3] + m[5];
+ return [
+ xt,
+ yt
+ ];
+ };
+ Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+ var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+ return [
+ xt,
+ yt
+ ];
+ };
+ // Applies the transform to the rectangle and finds the minimum axially
+ // aligned bounding box.
+ Util.getAxialAlignedBoundingBox = function Util_getAxialAlignedBoundingBox(r, m) {
+ var p1 = Util.applyTransform(r, m);
+ var p2 = Util.applyTransform(r.slice(2, 4), m);
+ var p3 = Util.applyTransform([
+ r[0],
+ r[3]
+ ], m);
+ var p4 = Util.applyTransform([
+ r[2],
+ r[1]
+ ], m);
+ return [
+ Math.min(p1[0], p2[0], p3[0], p4[0]),
+ Math.min(p1[1], p2[1], p3[1], p4[1]),
+ Math.max(p1[0], p2[0], p3[0], p4[0]),
+ Math.max(p1[1], p2[1], p3[1], p4[1])
+ ];
+ };
+ Util.inverseTransform = function Util_inverseTransform(m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ return [
+ m[3] / d,
+ -m[1] / d,
+ -m[2] / d,
+ m[0] / d,
+ (m[2] * m[5] - m[4] * m[3]) / d,
+ (m[4] * m[1] - m[5] * m[0]) / d
+ ];
+ };
+ // Apply a generic 3d matrix M on a 3-vector v:
+ // | a b c | | X |
+ // | d e f | x | Y |
+ // | g h i | | Z |
+ // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
+ // with v as [X,Y,Z]
+ Util.apply3dTransform = function Util_apply3dTransform(m, v) {
+ return [
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
+ ];
+ };
+ // This calculation uses Singular Value Decomposition.
+ // The SVD can be represented with formula A = USV. We are interested in the
+ // matrix S here because it represents the scale values.
+ Util.singularValueDecompose2dScale = function Util_singularValueDecompose2dScale(m) {
+ var transpose = [
+ m[0],
+ m[2],
+ m[1],
+ m[3]
+ ];
+ // Multiply matrix m with its transpose.
+ var a = m[0] * transpose[0] + m[1] * transpose[2];
+ var b = m[0] * transpose[1] + m[1] * transpose[3];
+ var c = m[2] * transpose[0] + m[3] * transpose[2];
+ var d = m[2] * transpose[1] + m[3] * transpose[3];
+ // Solve the second degree polynomial to get roots.
+ var first = (a + d) / 2;
+ var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
+ var sx = first + second || 1;
+ var sy = first - second || 1;
+ // Scale values are the square roots of the eigenvalues.
+ return [
+ Math.sqrt(sx),
+ Math.sqrt(sy)
+ ];
+ };
+ // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
+ // For coordinate systems whose origin lies in the bottom-left, this
+ // means normalization to (BL,TR) ordering. For systems with origin in the
+ // top-left, this means (TL,BR) ordering.
+ Util.normalizeRect = function Util_normalizeRect(rect) {
+ var r = rect.slice(0);
+ // clone rect
+ if (rect[0] > rect[2]) {
+ r[0] = rect[2];
+ r[2] = rect[0];
+ }
+ if (rect[1] > rect[3]) {
+ r[1] = rect[3];
+ r[3] = rect[1];
+ }
+ return r;
+ };
+ // Returns a rectangle [x1, y1, x2, y2] corresponding to the
+ // intersection of rect1 and rect2. If no intersection, returns 'false'
+ // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
+ Util.intersect = function Util_intersect(rect1, rect2) {
+ function compare(a, b) {
+ return a - b;
+ }
+ // Order points along the axes
+ var orderedX = [
+ rect1[0],
+ rect1[2],
+ rect2[0],
+ rect2[2]
+ ].sort(compare), orderedY = [
+ rect1[1],
+ rect1[3],
+ rect2[1],
+ rect2[3]
+ ].sort(compare), result = [];
+ rect1 = Util.normalizeRect(rect1);
+ rect2 = Util.normalizeRect(rect2);
+ // X: first and second points belong to different rectangles?
+ if (orderedX[0] === rect1[0] && orderedX[1] === rect2[0] || orderedX[0] === rect2[0] && orderedX[1] === rect1[0]) {
+ // Intersection must be between second and third points
+ result[0] = orderedX[1];
+ result[2] = orderedX[2];
+ } else {
+ return false;
+ }
+ // Y: first and second points belong to different rectangles?
+ if (orderedY[0] === rect1[1] && orderedY[1] === rect2[1] || orderedY[0] === rect2[1] && orderedY[1] === rect1[1]) {
+ // Intersection must be between second and third points
+ result[1] = orderedY[1];
+ result[3] = orderedY[2];
+ } else {
+ return false;
+ }
+ return result;
+ };
+ Util.sign = function Util_sign(num) {
+ return num < 0 ? -1 : 1;
+ };
+ var ROMAN_NUMBER_MAP = [
+ '',
+ 'C',
+ 'CC',
+ 'CCC',
+ 'CD',
+ 'D',
+ 'DC',
+ 'DCC',
+ 'DCCC',
+ 'CM',
+ '',
+ 'X',
+ 'XX',
+ 'XXX',
+ 'XL',
+ 'L',
+ 'LX',
+ 'LXX',
+ 'LXXX',
+ 'XC',
+ '',
+ 'I',
+ 'II',
+ 'III',
+ 'IV',
+ 'V',
+ 'VI',
+ 'VII',
+ 'VIII',
+ 'IX'
+ ];
+ /**
+ * Converts positive integers to (upper case) Roman numerals.
+ * @param {integer} number - The number that should be converted.
+ * @param {boolean} lowerCase - Indicates if the result should be converted
+ * to lower case letters. The default is false.
+ * @return {string} The resulting Roman number.
+ */
+ Util.toRoman = function Util_toRoman(number, lowerCase) {
+ assert(isInt(number) && number > 0, 'The number should be a positive integer.');
+ var pos, romanBuf = [];
+ // Thousands
+ while (number >= 1000) {
+ number -= 1000;
+ romanBuf.push('M');
+ }
+ // Hundreds
+ pos = number / 100 | 0;
+ number %= 100;
+ romanBuf.push(ROMAN_NUMBER_MAP[pos]);
+ // Tens
+ pos = number / 10 | 0;
+ number %= 10;
+ romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
+ // Ones
+ romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
+ var romanStr = romanBuf.join('');
+ return lowerCase ? romanStr.toLowerCase() : romanStr;
+ };
+ Util.appendToArray = function Util_appendToArray(arr1, arr2) {
+ Array.prototype.push.apply(arr1, arr2);
+ };
+ Util.prependToArray = function Util_prependToArray(arr1, arr2) {
+ Array.prototype.unshift.apply(arr1, arr2);
+ };
+ Util.extendObj = function extendObj(obj1, obj2) {
+ for (var key in obj2) {
+ obj1[key] = obj2[key];
+ }
+ };
+ Util.getInheritableProperty = function Util_getInheritableProperty(dict, name, getArray) {
+ while (dict && !dict.has(name)) {
+ dict = dict.get('Parent');
+ }
+ if (!dict) {
+ return null;
+ }
+ return getArray ? dict.getArray(name) : dict.get(name);
+ };
+ Util.inherit = function Util_inherit(sub, base, prototype) {
+ sub.prototype = Object.create(base.prototype);
+ sub.prototype.constructor = sub;
+ for (var prop in prototype) {
+ sub.prototype[prop] = prototype[prop];
+ }
+ };
+ Util.loadScript = function Util_loadScript(src, callback) {
+ var script = document.createElement('script');
+ var loaded = false;
+ script.setAttribute('src', src);
+ if (callback) {
+ script.onload = function () {
+ if (!loaded) {
+ callback();
+ }
+ loaded = true;
+ };
+ }
+ document.getElementsByTagName('head')[0].appendChild(script);
+ };
+ return Util;
+ }();
+ /**
+ * PDF page viewport created based on scale, rotation and offset.
+ * @class
+ * @alias PageViewport
+ */
+ var PageViewport = function PageViewportClosure() {
+ /**
+ * @constructor
+ * @private
+ * @param viewBox {Array} xMin, yMin, xMax and yMax coordinates.
+ * @param scale {number} scale of the viewport.
+ * @param rotation {number} rotations of the viewport in degrees.
+ * @param offsetX {number} offset X
+ * @param offsetY {number} offset Y
+ * @param dontFlip {boolean} if true, axis Y will not be flipped.
+ */
+ function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
+ this.viewBox = viewBox;
+ this.scale = scale;
+ this.rotation = rotation;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ // creating transform to convert pdf coordinate system to the normal
+ // canvas like coordinates taking in account scale and rotation
+ var centerX = (viewBox[2] + viewBox[0]) / 2;
+ var centerY = (viewBox[3] + viewBox[1]) / 2;
+ var rotateA, rotateB, rotateC, rotateD;
+ rotation = rotation % 360;
+ rotation = rotation < 0 ? rotation + 360 : rotation;
+ switch (rotation) {
+ case 180:
+ rotateA = -1;
+ rotateB = 0;
+ rotateC = 0;
+ rotateD = 1;
+ break;
+ case 90:
+ rotateA = 0;
+ rotateB = 1;
+ rotateC = 1;
+ rotateD = 0;
+ break;
+ case 270:
+ rotateA = 0;
+ rotateB = -1;
+ rotateC = -1;
+ rotateD = 0;
+ break;
+ //case 0:
+ default:
+ rotateA = 1;
+ rotateB = 0;
+ rotateC = 0;
+ rotateD = -1;
+ break;
+ }
+ if (dontFlip) {
+ rotateC = -rotateC;
+ rotateD = -rotateD;
+ }
+ var offsetCanvasX, offsetCanvasY;
+ var width, height;
+ if (rotateA === 0) {
+ offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+ width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ } else {
+ offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+ width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ }
+ // creating transform for the following operations:
+ // translate(-centerX, -centerY), rotate and flip vertically,
+ // scale, and translate(offsetCanvasX, offsetCanvasY)
+ this.transform = [
+ rotateA * scale,
+ rotateB * scale,
+ rotateC * scale,
+ rotateD * scale,
+ offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+ offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+ ];
+ this.width = width;
+ this.height = height;
+ this.fontScale = scale;
+ }
+ PageViewport.prototype = /** @lends PageViewport.prototype */
+ {
+ /**
+ * Clones viewport with additional properties.
+ * @param args {Object} (optional) If specified, may contain the 'scale' or
+ * 'rotation' properties to override the corresponding properties in
+ * the cloned viewport.
+ * @returns {PageViewport} Cloned viewport.
+ */
+ clone: function PageViewPort_clone(args) {
+ args = args || {};
+ var scale = 'scale' in args ? args.scale : this.scale;
+ var rotation = 'rotation' in args ? args.rotation : this.rotation;
+ return new PageViewport(this.viewBox.slice(), scale, rotation, this.offsetX, this.offsetY, args.dontFlip);
+ },
+ /**
+ * Converts PDF point to the viewport coordinates. For examples, useful for
+ * converting PDF location into canvas pixel coordinates.
+ * @param x {number} X coordinate.
+ * @param y {number} Y coordinate.
+ * @returns {Object} Object that contains 'x' and 'y' properties of the
+ * point in the viewport coordinate space.
+ * @see {@link convertToPdfPoint}
+ * @see {@link convertToViewportRectangle}
+ */
+ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+ return Util.applyTransform([
+ x,
+ y
+ ], this.transform);
+ },
+ /**
+ * Converts PDF rectangle to the viewport coordinates.
+ * @param rect {Array} xMin, yMin, xMax and yMax coordinates.
+ * @returns {Array} Contains corresponding coordinates of the rectangle
+ * in the viewport coordinate space.
+ * @see {@link convertToViewportPoint}
+ */
+ convertToViewportRectangle: function PageViewport_convertToViewportRectangle(rect) {
+ var tl = Util.applyTransform([
+ rect[0],
+ rect[1]
+ ], this.transform);
+ var br = Util.applyTransform([
+ rect[2],
+ rect[3]
+ ], this.transform);
+ return [
+ tl[0],
+ tl[1],
+ br[0],
+ br[1]
+ ];
+ },
+ /**
+ * Converts viewport coordinates to the PDF location. For examples, useful
+ * for converting canvas pixel location into PDF one.
+ * @param x {number} X coordinate.
+ * @param y {number} Y coordinate.
+ * @returns {Object} Object that contains 'x' and 'y' properties of the
+ * point in the PDF coordinate space.
+ * @see {@link convertToViewportPoint}
+ */
+ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+ return Util.applyInverseTransform([
+ x,
+ y
+ ], this.transform);
+ }
+ };
+ return PageViewport;
+ }();
+ var PDFStringTranslateTable = [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0x2D8,
+ 0x2C7,
+ 0x2C6,
+ 0x2D9,
+ 0x2DD,
+ 0x2DB,
+ 0x2DA,
+ 0x2DC,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0x2022,
+ 0x2020,
+ 0x2021,
+ 0x2026,
+ 0x2014,
+ 0x2013,
+ 0x192,
+ 0x2044,
+ 0x2039,
+ 0x203A,
+ 0x2212,
+ 0x2030,
+ 0x201E,
+ 0x201C,
+ 0x201D,
+ 0x2018,
+ 0x2019,
+ 0x201A,
+ 0x2122,
+ 0xFB01,
+ 0xFB02,
+ 0x141,
+ 0x152,
+ 0x160,
+ 0x178,
+ 0x17D,
+ 0x131,
+ 0x142,
+ 0x153,
+ 0x161,
+ 0x17E,
+ 0,
+ 0x20AC
+ ];
+ function stringToPDFString(str) {
+ var i, n = str.length, strBuf = [];
+ if (str[0] === '\xFE' && str[1] === '\xFF') {
+ // UTF16BE BOM
+ for (i = 2; i < n; i += 2) {
+ strBuf.push(String.fromCharCode(str.charCodeAt(i) << 8 | str.charCodeAt(i + 1)));
+ }
+ } else {
+ for (i = 0; i < n; ++i) {
+ var code = PDFStringTranslateTable[str.charCodeAt(i)];
+ strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
+ }
+ }
+ return strBuf.join('');
+ }
+ function stringToUTF8String(str) {
+ return decodeURIComponent(escape(str));
+ }
+ function utf8StringToString(str) {
+ return unescape(encodeURIComponent(str));
+ }
+ function isEmptyObj(obj) {
+ for (var key in obj) {
+ return false;
+ }
+ return true;
+ }
+ function isBool(v) {
+ return typeof v === 'boolean';
+ }
+ function isInt(v) {
+ return typeof v === 'number' && (v | 0) === v;
+ }
+ function isNum(v) {
+ return typeof v === 'number';
+ }
+ function isString(v) {
+ return typeof v === 'string';
+ }
+ function isArray(v) {
+ return v instanceof Array;
+ }
+ function isArrayBuffer(v) {
+ return typeof v === 'object' && v !== null && v.byteLength !== undefined;
+ }
+ // Checks if ch is one of the following characters: SPACE, TAB, CR or LF.
+ function isSpace(ch) {
+ return ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A;
+ }
+ /**
+ * Promise Capability object.
+ *
+ * @typedef {Object} PromiseCapability
+ * @property {Promise} promise - A promise object.
+ * @property {function} resolve - Fulfills the promise.
+ * @property {function} reject - Rejects the promise.
+ */
+ /**
+ * Creates a promise capability object.
+ * @alias createPromiseCapability
+ *
+ * @return {PromiseCapability} A capability object contains:
+ * - a Promise, resolve and reject methods.
+ */
+ function createPromiseCapability() {
+ var capability = {};
+ capability.promise = new Promise(function (resolve, reject) {
+ capability.resolve = resolve;
+ capability.reject = reject;
+ });
+ return capability;
+ }
+ /**
+ * Polyfill for Promises:
+ * The following promise implementation tries to generally implement the
+ * Promise/A+ spec. Some notable differences from other promise libraries are:
+ * - There currently isn't a separate deferred and promise object.
+ * - Unhandled rejections eventually show an error if they aren't handled.
+ *
+ * Based off of the work in:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
+ */
+ (function PromiseClosure() {
+ if (globalScope.Promise) {
+ // Promises existing in the DOM/Worker, checking presence of all/resolve
+ if (typeof globalScope.Promise.all !== 'function') {
+ globalScope.Promise.all = function (iterable) {
+ var count = 0, results = [], resolve, reject;
+ var promise = new globalScope.Promise(function (resolve_, reject_) {
+ resolve = resolve_;
+ reject = reject_;
+ });
+ iterable.forEach(function (p, i) {
+ count++;
+ p.then(function (result) {
+ results[i] = result;
+ count--;
+ if (count === 0) {
+ resolve(results);
+ }
+ }, reject);
+ });
+ if (count === 0) {
+ resolve(results);
+ }
+ return promise;
+ };
+ }
+ if (typeof globalScope.Promise.resolve !== 'function') {
+ globalScope.Promise.resolve = function (value) {
+ return new globalScope.Promise(function (resolve) {
+ resolve(value);
+ });
+ };
+ }
+ if (typeof globalScope.Promise.reject !== 'function') {
+ globalScope.Promise.reject = function (reason) {
+ return new globalScope.Promise(function (resolve, reject) {
+ reject(reason);
+ });
+ };
+ }
+ if (typeof globalScope.Promise.prototype.catch !== 'function') {
+ globalScope.Promise.prototype.catch = function (onReject) {
+ return globalScope.Promise.prototype.then(undefined, onReject);
+ };
+ }
+ return;
+ }
+ throw new Error('DOM Promise is not present');
+ }());
+ var StatTimer = function StatTimerClosure() {
+ function rpad(str, pad, length) {
+ while (str.length < length) {
+ str += pad;
+ }
+ return str;
+ }
+ function StatTimer() {
+ this.started = Object.create(null);
+ this.times = [];
+ this.enabled = true;
+ }
+ StatTimer.prototype = {
+ time: function StatTimer_time(name) {
+ if (!this.enabled) {
+ return;
+ }
+ if (name in this.started) {
+ warn('Timer is already running for ' + name);
+ }
+ this.started[name] = Date.now();
+ },
+ timeEnd: function StatTimer_timeEnd(name) {
+ if (!this.enabled) {
+ return;
+ }
+ if (!(name in this.started)) {
+ warn('Timer has not been started for ' + name);
+ }
+ this.times.push({
+ 'name': name,
+ 'start': this.started[name],
+ 'end': Date.now()
+ });
+ // Remove timer from started so it can be called again.
+ delete this.started[name];
+ },
+ toString: function StatTimer_toString() {
+ var i, ii;
+ var times = this.times;
+ var out = '';
+ // Find the longest name for padding purposes.
+ var longest = 0;
+ for (i = 0, ii = times.length; i < ii; ++i) {
+ var name = times[i]['name'];
+ if (name.length > longest) {
+ longest = name.length;
+ }
+ }
+ for (i = 0, ii = times.length; i < ii; ++i) {
+ var span = times[i];
+ var duration = span.end - span.start;
+ out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
+ }
+ return out;
+ }
+ };
+ return StatTimer;
+ }();
+ var createBlob = function createBlob(data, contentType) {
+ if (typeof Blob !== 'undefined') {
+ return new Blob([data], { type: contentType });
+ }
+ warn('The "Blob" constructor is not supported.');
+ };
+ var createObjectURL = function createObjectURLClosure() {
+ // Blob/createObjectURL is not available, falling back to data schema.
+ var digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+ return function createObjectURL(data, contentType, forceDataSchema) {
+ if (!forceDataSchema && typeof URL !== 'undefined' && URL.createObjectURL) {
+ var blob = createBlob(data, contentType);
+ return URL.createObjectURL(blob);
+ }
+ var buffer = 'data:' + contentType + ';base64,';
+ for (var i = 0, ii = data.length; i < ii; i += 3) {
+ var b1 = data[i] & 0xFF;
+ var b2 = data[i + 1] & 0xFF;
+ var b3 = data[i + 2] & 0xFF;
+ var d1 = b1 >> 2, d2 = (b1 & 3) << 4 | b2 >> 4;
+ var d3 = i + 1 < ii ? (b2 & 0xF) << 2 | b3 >> 6 : 64;
+ var d4 = i + 2 < ii ? b3 & 0x3F : 64;
+ buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
+ }
+ return buffer;
+ };
+ }();
+ function MessageHandler(sourceName, targetName, comObj) {
+ this.sourceName = sourceName;
+ this.targetName = targetName;
+ this.comObj = comObj;
+ this.callbackIndex = 1;
+ this.postMessageTransfers = true;
+ var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
+ var ah = this.actionHandler = Object.create(null);
+ this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
+ var data = event.data;
+ if (data.targetName !== this.sourceName) {
+ return;
+ }
+ if (data.isReply) {
+ var callbackId = data.callbackId;
+ if (data.callbackId in callbacksCapabilities) {
+ var callback = callbacksCapabilities[callbackId];
+ delete callbacksCapabilities[callbackId];
+ if ('error' in data) {
+ callback.reject(data.error);
+ } else {
+ callback.resolve(data.data);
+ }
+ } else {
+ error('Cannot resolve callback ' + callbackId);
+ }
+ } else if (data.action in ah) {
+ var action = ah[data.action];
+ if (data.callbackId) {
+ var sourceName = this.sourceName;
+ var targetName = data.sourceName;
+ Promise.resolve().then(function () {
+ return action[0].call(action[1], data.data);
+ }).then(function (result) {
+ comObj.postMessage({
+ sourceName: sourceName,
+ targetName: targetName,
+ isReply: true,
+ callbackId: data.callbackId,
+ data: result
+ });
+ }, function (reason) {
+ if (reason instanceof Error) {
+ // Serialize error to avoid "DataCloneError"
+ reason = reason + '';
+ }
+ comObj.postMessage({
+ sourceName: sourceName,
+ targetName: targetName,
+ isReply: true,
+ callbackId: data.callbackId,
+ error: reason
+ });
+ });
+ } else {
+ action[0].call(action[1], data.data);
+ }
+ } else {
+ error('Unknown action from worker: ' + data.action);
+ }
+ }.bind(this);
+ comObj.addEventListener('message', this._onComObjOnMessage);
+ }
+ MessageHandler.prototype = {
+ on: function messageHandlerOn(actionName, handler, scope) {
+ var ah = this.actionHandler;
+ if (ah[actionName]) {
+ error('There is already an actionName called "' + actionName + '"');
+ }
+ ah[actionName] = [
+ handler,
+ scope
+ ];
+ },
+ /**
+ * Sends a message to the comObj to invoke the action with the supplied data.
+ * @param {String} actionName Action to call.
+ * @param {JSON} data JSON data to send.
+ * @param {Array} [transfers] Optional list of transfers/ArrayBuffers
+ */
+ send: function messageHandlerSend(actionName, data, transfers) {
+ var message = {
+ sourceName: this.sourceName,
+ targetName: this.targetName,
+ action: actionName,
+ data: data
+ };
+ this.postMessage(message, transfers);
+ },
+ /**
+ * Sends a message to the comObj to invoke the action with the supplied data.
+ * Expects that other side will callback with the response.
+ * @param {String} actionName Action to call.
+ * @param {JSON} data JSON data to send.
+ * @param {Array} [transfers] Optional list of transfers/ArrayBuffers.
+ * @returns {Promise} Promise to be resolved with response data.
+ */
+ sendWithPromise: function messageHandlerSendWithPromise(actionName, data, transfers) {
+ var callbackId = this.callbackIndex++;
+ var message = {
+ sourceName: this.sourceName,
+ targetName: this.targetName,
+ action: actionName,
+ data: data,
+ callbackId: callbackId
+ };
+ var capability = createPromiseCapability();
+ this.callbacksCapabilities[callbackId] = capability;
+ try {
+ this.postMessage(message, transfers);
+ } catch (e) {
+ capability.reject(e);
+ }
+ return capability.promise;
+ },
+ /**
+ * Sends raw message to the comObj.
+ * @private
+ * @param message {Object} Raw message.
+ * @param transfers List of transfers/ArrayBuffers, or undefined.
+ */
+ postMessage: function (message, transfers) {
+ if (transfers && this.postMessageTransfers) {
+ this.comObj.postMessage(message, transfers);
+ } else {
+ this.comObj.postMessage(message);
+ }
+ },
+ destroy: function () {
+ this.comObj.removeEventListener('message', this._onComObjOnMessage);
+ }
+ };
+ function loadJpegStream(id, imageUrl, objs) {
+ var img = new Image();
+ img.onload = function loadJpegStream_onloadClosure() {
+ objs.resolve(id, img);
+ };
+ img.onerror = function loadJpegStream_onerrorClosure() {
+ objs.resolve(id, null);
+ warn('Error during JPEG image loading');
+ };
+ img.src = imageUrl;
+ }
+ exports.FONT_IDENTITY_MATRIX = FONT_IDENTITY_MATRIX;
+ exports.IDENTITY_MATRIX = IDENTITY_MATRIX;
+ exports.OPS = OPS;
+ exports.VERBOSITY_LEVELS = VERBOSITY_LEVELS;
+ exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES;
+ exports.AnnotationBorderStyleType = AnnotationBorderStyleType;
+ exports.AnnotationFieldFlag = AnnotationFieldFlag;
+ exports.AnnotationFlag = AnnotationFlag;
+ exports.AnnotationType = AnnotationType;
+ exports.FontType = FontType;
+ exports.ImageKind = ImageKind;
+ exports.InvalidPDFException = InvalidPDFException;
+ exports.MessageHandler = MessageHandler;
+ exports.MissingDataException = MissingDataException;
+ exports.MissingPDFException = MissingPDFException;
+ exports.NotImplementedException = NotImplementedException;
+ exports.PageViewport = PageViewport;
+ exports.PasswordException = PasswordException;
+ exports.PasswordResponses = PasswordResponses;
+ exports.StatTimer = StatTimer;
+ exports.StreamType = StreamType;
+ exports.TextRenderingMode = TextRenderingMode;
+ exports.UnexpectedResponseException = UnexpectedResponseException;
+ exports.UnknownErrorException = UnknownErrorException;
+ exports.Util = Util;
+ exports.XRefParseException = XRefParseException;
+ exports.arrayByteLength = arrayByteLength;
+ exports.arraysToBytes = arraysToBytes;
+ exports.assert = assert;
+ exports.bytesToString = bytesToString;
+ exports.createBlob = createBlob;
+ exports.createPromiseCapability = createPromiseCapability;
+ exports.createObjectURL = createObjectURL;
+ exports.deprecated = deprecated;
+ exports.error = error;
+ exports.getLookupTableFactory = getLookupTableFactory;
+ exports.getVerbosityLevel = getVerbosityLevel;
+ exports.globalScope = globalScope;
+ exports.info = info;
+ exports.isArray = isArray;
+ exports.isArrayBuffer = isArrayBuffer;
+ exports.isBool = isBool;
+ exports.isEmptyObj = isEmptyObj;
+ exports.isInt = isInt;
+ exports.isNum = isNum;
+ exports.isString = isString;
+ exports.isSpace = isSpace;
+ exports.isSameOrigin = isSameOrigin;
+ exports.createValidAbsoluteUrl = createValidAbsoluteUrl;
+ exports.isLittleEndian = isLittleEndian;
+ exports.isEvalSupported = isEvalSupported;
+ exports.loadJpegStream = loadJpegStream;
+ exports.log2 = log2;
+ exports.readInt8 = readInt8;
+ exports.readUint16 = readUint16;
+ exports.readUint32 = readUint32;
+ exports.removeNullCharacters = removeNullCharacters;
+ exports.setVerbosityLevel = setVerbosityLevel;
+ exports.shadow = shadow;
+ exports.string32 = string32;
+ exports.stringToBytes = stringToBytes;
+ exports.stringToPDFString = stringToPDFString;
+ exports.stringToUTF8String = stringToUTF8String;
+ exports.utf8StringToString = utf8StringToString;
+ exports.warn = warn;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreCFFParser = {}, root.pdfjsSharedUtil, root.pdfjsCoreCharsets, root.pdfjsCoreEncodings);
+ }(this, function (exports, sharedUtil, coreCharsets, coreEncodings) {
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var bytesToString = sharedUtil.bytesToString;
+ var warn = sharedUtil.warn;
+ var isArray = sharedUtil.isArray;
+ var Util = sharedUtil.Util;
+ var stringToBytes = sharedUtil.stringToBytes;
+ var assert = sharedUtil.assert;
+ var ISOAdobeCharset = coreCharsets.ISOAdobeCharset;
+ var ExpertCharset = coreCharsets.ExpertCharset;
+ var ExpertSubsetCharset = coreCharsets.ExpertSubsetCharset;
+ var StandardEncoding = coreEncodings.StandardEncoding;
+ var ExpertEncoding = coreEncodings.ExpertEncoding;
+ // Maximum subroutine call depth of type 2 chartrings. Matches OTS.
+ var MAX_SUBR_NESTING = 10;
+ /**
+ * The CFF class takes a Type1 file and wrap it into a
+ * 'Compact Font Format' which itself embed Type2 charstrings.
+ */
+ var CFFStandardStrings = [
+ '.notdef',
+ 'space',
+ 'exclam',
+ 'quotedbl',
+ 'numbersign',
+ 'dollar',
+ 'percent',
+ 'ampersand',
+ 'quoteright',
+ 'parenleft',
+ 'parenright',
+ 'asterisk',
+ 'plus',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'slash',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'at',
+ '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',
+ 'bracketleft',
+ 'backslash',
+ 'bracketright',
+ 'asciicircum',
+ 'underscore',
+ 'quoteleft',
+ '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',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'asciitilde',
+ 'exclamdown',
+ 'cent',
+ 'sterling',
+ 'fraction',
+ 'yen',
+ 'florin',
+ 'section',
+ 'currency',
+ 'quotesingle',
+ 'quotedblleft',
+ 'guillemotleft',
+ 'guilsinglleft',
+ 'guilsinglright',
+ 'fi',
+ 'fl',
+ 'endash',
+ 'dagger',
+ 'daggerdbl',
+ 'periodcentered',
+ 'paragraph',
+ 'bullet',
+ 'quotesinglbase',
+ 'quotedblbase',
+ 'quotedblright',
+ 'guillemotright',
+ 'ellipsis',
+ 'perthousand',
+ 'questiondown',
+ 'grave',
+ 'acute',
+ 'circumflex',
+ 'tilde',
+ 'macron',
+ 'breve',
+ 'dotaccent',
+ 'dieresis',
+ 'ring',
+ 'cedilla',
+ 'hungarumlaut',
+ 'ogonek',
+ 'caron',
+ 'emdash',
+ 'AE',
+ 'ordfeminine',
+ 'Lslash',
+ 'Oslash',
+ 'OE',
+ 'ordmasculine',
+ 'ae',
+ 'dotlessi',
+ 'lslash',
+ 'oslash',
+ 'oe',
+ 'germandbls',
+ 'onesuperior',
+ 'logicalnot',
+ 'mu',
+ 'trademark',
+ 'Eth',
+ 'onehalf',
+ 'plusminus',
+ 'Thorn',
+ 'onequarter',
+ 'divide',
+ 'brokenbar',
+ 'degree',
+ 'thorn',
+ 'threequarters',
+ 'twosuperior',
+ 'registered',
+ 'minus',
+ 'eth',
+ 'multiply',
+ 'threesuperior',
+ 'copyright',
+ 'Aacute',
+ 'Acircumflex',
+ 'Adieresis',
+ 'Agrave',
+ 'Aring',
+ 'Atilde',
+ 'Ccedilla',
+ 'Eacute',
+ 'Ecircumflex',
+ 'Edieresis',
+ 'Egrave',
+ 'Iacute',
+ 'Icircumflex',
+ 'Idieresis',
+ 'Igrave',
+ 'Ntilde',
+ 'Oacute',
+ 'Ocircumflex',
+ 'Odieresis',
+ 'Ograve',
+ 'Otilde',
+ 'Scaron',
+ 'Uacute',
+ 'Ucircumflex',
+ 'Udieresis',
+ 'Ugrave',
+ 'Yacute',
+ 'Ydieresis',
+ 'Zcaron',
+ 'aacute',
+ 'acircumflex',
+ 'adieresis',
+ 'agrave',
+ 'aring',
+ 'atilde',
+ 'ccedilla',
+ 'eacute',
+ 'ecircumflex',
+ 'edieresis',
+ 'egrave',
+ 'iacute',
+ 'icircumflex',
+ 'idieresis',
+ 'igrave',
+ 'ntilde',
+ 'oacute',
+ 'ocircumflex',
+ 'odieresis',
+ 'ograve',
+ 'otilde',
+ 'scaron',
+ 'uacute',
+ 'ucircumflex',
+ 'udieresis',
+ 'ugrave',
+ 'yacute',
+ 'ydieresis',
+ 'zcaron',
+ 'exclamsmall',
+ 'Hungarumlautsmall',
+ 'dollaroldstyle',
+ 'dollarsuperior',
+ 'ampersandsmall',
+ 'Acutesmall',
+ 'parenleftsuperior',
+ 'parenrightsuperior',
+ 'twodotenleader',
+ 'onedotenleader',
+ 'zerooldstyle',
+ 'oneoldstyle',
+ 'twooldstyle',
+ 'threeoldstyle',
+ 'fouroldstyle',
+ 'fiveoldstyle',
+ 'sixoldstyle',
+ 'sevenoldstyle',
+ 'eightoldstyle',
+ 'nineoldstyle',
+ 'commasuperior',
+ 'threequartersemdash',
+ 'periodsuperior',
+ 'questionsmall',
+ 'asuperior',
+ 'bsuperior',
+ 'centsuperior',
+ 'dsuperior',
+ 'esuperior',
+ 'isuperior',
+ 'lsuperior',
+ 'msuperior',
+ 'nsuperior',
+ 'osuperior',
+ 'rsuperior',
+ 'ssuperior',
+ 'tsuperior',
+ 'ff',
+ 'ffi',
+ 'ffl',
+ 'parenleftinferior',
+ 'parenrightinferior',
+ 'Circumflexsmall',
+ 'hyphensuperior',
+ 'Gravesmall',
+ 'Asmall',
+ 'Bsmall',
+ 'Csmall',
+ 'Dsmall',
+ 'Esmall',
+ 'Fsmall',
+ 'Gsmall',
+ 'Hsmall',
+ 'Ismall',
+ 'Jsmall',
+ 'Ksmall',
+ 'Lsmall',
+ 'Msmall',
+ 'Nsmall',
+ 'Osmall',
+ 'Psmall',
+ 'Qsmall',
+ 'Rsmall',
+ 'Ssmall',
+ 'Tsmall',
+ 'Usmall',
+ 'Vsmall',
+ 'Wsmall',
+ 'Xsmall',
+ 'Ysmall',
+ 'Zsmall',
+ 'colonmonetary',
+ 'onefitted',
+ 'rupiah',
+ 'Tildesmall',
+ 'exclamdownsmall',
+ 'centoldstyle',
+ 'Lslashsmall',
+ 'Scaronsmall',
+ 'Zcaronsmall',
+ 'Dieresissmall',
+ 'Brevesmall',
+ 'Caronsmall',
+ 'Dotaccentsmall',
+ 'Macronsmall',
+ 'figuredash',
+ 'hypheninferior',
+ 'Ogoneksmall',
+ 'Ringsmall',
+ 'Cedillasmall',
+ 'questiondownsmall',
+ 'oneeighth',
+ 'threeeighths',
+ 'fiveeighths',
+ 'seveneighths',
+ 'onethird',
+ 'twothirds',
+ 'zerosuperior',
+ 'foursuperior',
+ 'fivesuperior',
+ 'sixsuperior',
+ 'sevensuperior',
+ 'eightsuperior',
+ 'ninesuperior',
+ 'zeroinferior',
+ 'oneinferior',
+ 'twoinferior',
+ 'threeinferior',
+ 'fourinferior',
+ 'fiveinferior',
+ 'sixinferior',
+ 'seveninferior',
+ 'eightinferior',
+ 'nineinferior',
+ 'centinferior',
+ 'dollarinferior',
+ 'periodinferior',
+ 'commainferior',
+ 'Agravesmall',
+ 'Aacutesmall',
+ 'Acircumflexsmall',
+ 'Atildesmall',
+ 'Adieresissmall',
+ 'Aringsmall',
+ 'AEsmall',
+ 'Ccedillasmall',
+ 'Egravesmall',
+ 'Eacutesmall',
+ 'Ecircumflexsmall',
+ 'Edieresissmall',
+ 'Igravesmall',
+ 'Iacutesmall',
+ 'Icircumflexsmall',
+ 'Idieresissmall',
+ 'Ethsmall',
+ 'Ntildesmall',
+ 'Ogravesmall',
+ 'Oacutesmall',
+ 'Ocircumflexsmall',
+ 'Otildesmall',
+ 'Odieresissmall',
+ 'OEsmall',
+ 'Oslashsmall',
+ 'Ugravesmall',
+ 'Uacutesmall',
+ 'Ucircumflexsmall',
+ 'Udieresissmall',
+ 'Yacutesmall',
+ 'Thornsmall',
+ 'Ydieresissmall',
+ '001.000',
+ '001.001',
+ '001.002',
+ '001.003',
+ 'Black',
+ 'Bold',
+ 'Book',
+ 'Light',
+ 'Medium',
+ 'Regular',
+ 'Roman',
+ 'Semibold'
+ ];
+ var CFFParser = function CFFParserClosure() {
+ var CharstringValidationData = [
+ null,
+ {
+ id: 'hstem',
+ min: 2,
+ stackClearing: true,
+ stem: true
+ },
+ null,
+ {
+ id: 'vstem',
+ min: 2,
+ stackClearing: true,
+ stem: true
+ },
+ {
+ id: 'vmoveto',
+ min: 1,
+ stackClearing: true
+ },
+ {
+ id: 'rlineto',
+ min: 2,
+ resetStack: true
+ },
+ {
+ id: 'hlineto',
+ min: 1,
+ resetStack: true
+ },
+ {
+ id: 'vlineto',
+ min: 1,
+ resetStack: true
+ },
+ {
+ id: 'rrcurveto',
+ min: 6,
+ resetStack: true
+ },
+ null,
+ {
+ id: 'callsubr',
+ min: 1,
+ undefStack: true
+ },
+ {
+ id: 'return',
+ min: 0,
+ undefStack: true
+ },
+ null,
+ // 12
+ null,
+ {
+ id: 'endchar',
+ min: 0,
+ stackClearing: true
+ },
+ null,
+ null,
+ null,
+ {
+ id: 'hstemhm',
+ min: 2,
+ stackClearing: true,
+ stem: true
+ },
+ {
+ id: 'hintmask',
+ min: 0,
+ stackClearing: true
+ },
+ {
+ id: 'cntrmask',
+ min: 0,
+ stackClearing: true
+ },
+ {
+ id: 'rmoveto',
+ min: 2,
+ stackClearing: true
+ },
+ {
+ id: 'hmoveto',
+ min: 1,
+ stackClearing: true
+ },
+ {
+ id: 'vstemhm',
+ min: 2,
+ stackClearing: true,
+ stem: true
+ },
+ {
+ id: 'rcurveline',
+ min: 8,
+ resetStack: true
+ },
+ {
+ id: 'rlinecurve',
+ min: 8,
+ resetStack: true
+ },
+ {
+ id: 'vvcurveto',
+ min: 4,
+ resetStack: true
+ },
+ {
+ id: 'hhcurveto',
+ min: 4,
+ resetStack: true
+ },
+ null,
+ // shortint
+ {
+ id: 'callgsubr',
+ min: 1,
+ undefStack: true
+ },
+ {
+ id: 'vhcurveto',
+ min: 4,
+ resetStack: true
+ },
+ {
+ id: 'hvcurveto',
+ min: 4,
+ resetStack: true
+ }
+ ];
+ var CharstringValidationData12 = [
+ null,
+ null,
+ null,
+ {
+ id: 'and',
+ min: 2,
+ stackDelta: -1
+ },
+ {
+ id: 'or',
+ min: 2,
+ stackDelta: -1
+ },
+ {
+ id: 'not',
+ min: 1,
+ stackDelta: 0
+ },
+ null,
+ null,
+ null,
+ {
+ id: 'abs',
+ min: 1,
+ stackDelta: 0
+ },
+ {
+ id: 'add',
+ min: 2,
+ stackDelta: -1,
+ stackFn: function stack_div(stack, index) {
+ stack[index - 2] = stack[index - 2] + stack[index - 1];
+ }
+ },
+ {
+ id: 'sub',
+ min: 2,
+ stackDelta: -1,
+ stackFn: function stack_div(stack, index) {
+ stack[index - 2] = stack[index - 2] - stack[index - 1];
+ }
+ },
+ {
+ id: 'div',
+ min: 2,
+ stackDelta: -1,
+ stackFn: function stack_div(stack, index) {
+ stack[index - 2] = stack[index - 2] / stack[index - 1];
+ }
+ },
+ null,
+ {
+ id: 'neg',
+ min: 1,
+ stackDelta: 0,
+ stackFn: function stack_div(stack, index) {
+ stack[index - 1] = -stack[index - 1];
+ }
+ },
+ {
+ id: 'eq',
+ min: 2,
+ stackDelta: -1
+ },
+ null,
+ null,
+ {
+ id: 'drop',
+ min: 1,
+ stackDelta: -1
+ },
+ null,
+ {
+ id: 'put',
+ min: 2,
+ stackDelta: -2
+ },
+ {
+ id: 'get',
+ min: 1,
+ stackDelta: 0
+ },
+ {
+ id: 'ifelse',
+ min: 4,
+ stackDelta: -3
+ },
+ {
+ id: 'random',
+ min: 0,
+ stackDelta: 1
+ },
+ {
+ id: 'mul',
+ min: 2,
+ stackDelta: -1,
+ stackFn: function stack_div(stack, index) {
+ stack[index - 2] = stack[index - 2] * stack[index - 1];
+ }
+ },
+ null,
+ {
+ id: 'sqrt',
+ min: 1,
+ stackDelta: 0
+ },
+ {
+ id: 'dup',
+ min: 1,
+ stackDelta: 1
+ },
+ {
+ id: 'exch',
+ min: 2,
+ stackDelta: 0
+ },
+ {
+ id: 'index',
+ min: 2,
+ stackDelta: 0
+ },
+ {
+ id: 'roll',
+ min: 3,
+ stackDelta: -2
+ },
+ null,
+ null,
+ null,
+ {
+ id: 'hflex',
+ min: 7,
+ resetStack: true
+ },
+ {
+ id: 'flex',
+ min: 13,
+ resetStack: true
+ },
+ {
+ id: 'hflex1',
+ min: 9,
+ resetStack: true
+ },
+ {
+ id: 'flex1',
+ min: 11,
+ resetStack: true
+ }
+ ];
+ function CFFParser(file, properties, seacAnalysisEnabled) {
+ this.bytes = file.getBytes();
+ this.properties = properties;
+ this.seacAnalysisEnabled = !!seacAnalysisEnabled;
+ }
+ CFFParser.prototype = {
+ parse: function CFFParser_parse() {
+ var properties = this.properties;
+ var cff = new CFF();
+ this.cff = cff;
+ // The first five sections must be in order, all the others are reached
+ // via offsets contained in one of the below.
+ var header = this.parseHeader();
+ var nameIndex = this.parseIndex(header.endPos);
+ var topDictIndex = this.parseIndex(nameIndex.endPos);
+ var stringIndex = this.parseIndex(topDictIndex.endPos);
+ var globalSubrIndex = this.parseIndex(stringIndex.endPos);
+ var topDictParsed = this.parseDict(topDictIndex.obj.get(0));
+ var topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings);
+ cff.header = header.obj;
+ cff.names = this.parseNameIndex(nameIndex.obj);
+ cff.strings = this.parseStringIndex(stringIndex.obj);
+ cff.topDict = topDict;
+ cff.globalSubrIndex = globalSubrIndex.obj;
+ this.parsePrivateDict(cff.topDict);
+ cff.isCIDFont = topDict.hasName('ROS');
+ var charStringOffset = topDict.getByName('CharStrings');
+ var charStringIndex = this.parseIndex(charStringOffset).obj;
+ var fontMatrix = topDict.getByName('FontMatrix');
+ if (fontMatrix) {
+ properties.fontMatrix = fontMatrix;
+ }
+ var fontBBox = topDict.getByName('FontBBox');
+ if (fontBBox) {
+ // adjusting ascent/descent
+ properties.ascent = fontBBox[3];
+ properties.descent = fontBBox[1];
+ properties.ascentScaled = true;
+ }
+ var charset, encoding;
+ if (cff.isCIDFont) {
+ var fdArrayIndex = this.parseIndex(topDict.getByName('FDArray')).obj;
+ for (var i = 0, ii = fdArrayIndex.count; i < ii; ++i) {
+ var dictRaw = fdArrayIndex.get(i);
+ var fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw), cff.strings);
+ this.parsePrivateDict(fontDict);
+ cff.fdArray.push(fontDict);
+ }
+ // cid fonts don't have an encoding
+ encoding = null;
+ charset = this.parseCharsets(topDict.getByName('charset'), charStringIndex.count, cff.strings, true);
+ cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect'), charStringIndex.count);
+ } else {
+ charset = this.parseCharsets(topDict.getByName('charset'), charStringIndex.count, cff.strings, false);
+ encoding = this.parseEncoding(topDict.getByName('Encoding'), properties, cff.strings, charset.charset);
+ }
+ cff.charset = charset;
+ cff.encoding = encoding;
+ var charStringsAndSeacs = this.parseCharStrings(charStringIndex, topDict.privateDict.subrsIndex, globalSubrIndex.obj, cff.fdSelect, cff.fdArray);
+ cff.charStrings = charStringsAndSeacs.charStrings;
+ cff.seacs = charStringsAndSeacs.seacs;
+ cff.widths = charStringsAndSeacs.widths;
+ return cff;
+ },
+ parseHeader: function CFFParser_parseHeader() {
+ var bytes = this.bytes;
+ var bytesLength = bytes.length;
+ var offset = 0;
+ // Prevent an infinite loop, by checking that the offset is within the
+ // bounds of the bytes array. Necessary in empty, or invalid, font files.
+ while (offset < bytesLength && bytes[offset] !== 1) {
+ ++offset;
+ }
+ if (offset >= bytesLength) {
+ error('Invalid CFF header');
+ } else if (offset !== 0) {
+ info('cff data is shifted');
+ bytes = bytes.subarray(offset);
+ this.bytes = bytes;
+ }
+ var major = bytes[0];
+ var minor = bytes[1];
+ var hdrSize = bytes[2];
+ var offSize = bytes[3];
+ var header = new CFFHeader(major, minor, hdrSize, offSize);
+ return {
+ obj: header,
+ endPos: hdrSize
+ };
+ },
+ parseDict: function CFFParser_parseDict(dict) {
+ var pos = 0;
+ function parseOperand() {
+ var value = dict[pos++];
+ if (value === 30) {
+ return parseFloatOperand();
+ } else if (value === 28) {
+ value = dict[pos++];
+ value = (value << 24 | dict[pos++] << 16) >> 16;
+ return value;
+ } else if (value === 29) {
+ value = dict[pos++];
+ value = value << 8 | dict[pos++];
+ value = value << 8 | dict[pos++];
+ value = value << 8 | dict[pos++];
+ return value;
+ } else if (value >= 32 && value <= 246) {
+ return value - 139;
+ } else if (value >= 247 && value <= 250) {
+ return (value - 247) * 256 + dict[pos++] + 108;
+ } else if (value >= 251 && value <= 254) {
+ return -((value - 251) * 256) - dict[pos++] - 108;
+ } else {
+ warn('CFFParser_parseDict: "' + value + '" is a reserved command.');
+ return NaN;
+ }
+ }
+ function parseFloatOperand() {
+ var str = '';
+ var eof = 15;
+ var lookup = [
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '.',
+ 'E',
+ 'E-',
+ null,
+ '-'
+ ];
+ var length = dict.length;
+ while (pos < length) {
+ var b = dict[pos++];
+ var b1 = b >> 4;
+ var b2 = b & 15;
+ if (b1 === eof) {
+ break;
+ }
+ str += lookup[b1];
+ if (b2 === eof) {
+ break;
+ }
+ str += lookup[b2];
+ }
+ return parseFloat(str);
+ }
+ var operands = [];
+ var entries = [];
+ pos = 0;
+ var end = dict.length;
+ while (pos < end) {
+ var b = dict[pos];
+ if (b <= 21) {
+ if (b === 12) {
+ b = b << 8 | dict[++pos];
+ }
+ entries.push([
+ b,
+ operands
+ ]);
+ operands = [];
+ ++pos;
+ } else {
+ operands.push(parseOperand());
+ }
+ }
+ return entries;
+ },
+ parseIndex: function CFFParser_parseIndex(pos) {
+ var cffIndex = new CFFIndex();
+ var bytes = this.bytes;
+ var count = bytes[pos++] << 8 | bytes[pos++];
+ var offsets = [];
+ var end = pos;
+ var i, ii;
+ if (count !== 0) {
+ var offsetSize = bytes[pos++];
+ // add 1 for offset to determine size of last object
+ var startPos = pos + (count + 1) * offsetSize - 1;
+ for (i = 0, ii = count + 1; i < ii; ++i) {
+ var offset = 0;
+ for (var j = 0; j < offsetSize; ++j) {
+ offset <<= 8;
+ offset += bytes[pos++];
+ }
+ offsets.push(startPos + offset);
+ }
+ end = offsets[count];
+ }
+ for (i = 0, ii = offsets.length - 1; i < ii; ++i) {
+ var offsetStart = offsets[i];
+ var offsetEnd = offsets[i + 1];
+ cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
+ }
+ return {
+ obj: cffIndex,
+ endPos: end
+ };
+ },
+ parseNameIndex: function CFFParser_parseNameIndex(index) {
+ var names = [];
+ for (var i = 0, ii = index.count; i < ii; ++i) {
+ var name = index.get(i);
+ // OTS doesn't allow names to be over 127 characters.
+ var length = Math.min(name.length, 127);
+ var data = [];
+ // OTS also only permits certain characters in the name.
+ for (var j = 0; j < length; ++j) {
+ var c = name[j];
+ if (j === 0 && c === 0) {
+ data[j] = c;
+ continue;
+ }
+ if (c < 33 || c > 126 || c === 91 || /* [ */
+ c === 93 || /* ] */
+ c === 40 || /* ( */
+ c === 41 || /* ) */
+ c === 123 || /* { */
+ c === 125 || /* } */
+ c === 60 || /* < */
+ c === 62 || /* > */
+ c === 47 || /* / */
+ c === 37 || /* % */
+ c === 35)
+ /* # */
+ {
+ data[j] = 95;
+ continue;
+ }
+ data[j] = c;
+ }
+ names.push(bytesToString(data));
+ }
+ return names;
+ },
+ parseStringIndex: function CFFParser_parseStringIndex(index) {
+ var strings = new CFFStrings();
+ for (var i = 0, ii = index.count; i < ii; ++i) {
+ var data = index.get(i);
+ strings.add(bytesToString(data));
+ }
+ return strings;
+ },
+ createDict: function CFFParser_createDict(Type, dict, strings) {
+ var cffDict = new Type(strings);
+ for (var i = 0, ii = dict.length; i < ii; ++i) {
+ var pair = dict[i];
+ var key = pair[0];
+ var value = pair[1];
+ cffDict.setByKey(key, value);
+ }
+ return cffDict;
+ },
+ parseCharString: function CFFParser_parseCharString(state, data, localSubrIndex, globalSubrIndex) {
+ if (state.callDepth > MAX_SUBR_NESTING) {
+ return false;
+ }
+ var stackSize = state.stackSize;
+ var stack = state.stack;
+ var length = data.length;
+ for (var j = 0; j < length;) {
+ var value = data[j++];
+ var validationCommand = null;
+ if (value === 12) {
+ var q = data[j++];
+ if (q === 0) {
+ // The CFF specification state that the 'dotsection' command
+ // (12, 0) is deprecated and treated as a no-op, but all Type2
+ // charstrings processors should support them. Unfortunately
+ // the font sanitizer don't. As a workaround the sequence (12, 0)
+ // is replaced by a useless (0, hmoveto).
+ data[j - 2] = 139;
+ data[j - 1] = 22;
+ stackSize = 0;
+ } else {
+ validationCommand = CharstringValidationData12[q];
+ }
+ } else if (value === 28) {
+ // number (16 bit)
+ stack[stackSize] = (data[j] << 24 | data[j + 1] << 16) >> 16;
+ j += 2;
+ stackSize++;
+ } else if (value === 14) {
+ if (stackSize >= 4) {
+ stackSize -= 4;
+ if (this.seacAnalysisEnabled) {
+ state.seac = stack.slice(stackSize, stackSize + 4);
+ return false;
+ }
+ }
+ validationCommand = CharstringValidationData[value];
+ } else if (value >= 32 && value <= 246) {
+ // number
+ stack[stackSize] = value - 139;
+ stackSize++;
+ } else if (value >= 247 && value <= 254) {
+ // number (+1 bytes)
+ stack[stackSize] = value < 251 ? (value - 247 << 8) + data[j] + 108 : -(value - 251 << 8) - data[j] - 108;
+ j++;
+ stackSize++;
+ } else if (value === 255) {
+ // number (32 bit)
+ stack[stackSize] = (data[j] << 24 | data[j + 1] << 16 | data[j + 2] << 8 | data[j + 3]) / 65536;
+ j += 4;
+ stackSize++;
+ } else if (value === 19 || value === 20) {
+ state.hints += stackSize >> 1;
+ // skipping right amount of hints flag data
+ j += state.hints + 7 >> 3;
+ stackSize %= 2;
+ validationCommand = CharstringValidationData[value];
+ } else if (value === 10 || value === 29) {
+ var subrsIndex;
+ if (value === 10) {
+ subrsIndex = localSubrIndex;
+ } else {
+ subrsIndex = globalSubrIndex;
+ }
+ if (!subrsIndex) {
+ validationCommand = CharstringValidationData[value];
+ warn('Missing subrsIndex for ' + validationCommand.id);
+ return false;
+ }
+ var bias = 32768;
+ if (subrsIndex.count < 1240) {
+ bias = 107;
+ } else if (subrsIndex.count < 33900) {
+ bias = 1131;
+ }
+ var subrNumber = stack[--stackSize] + bias;
+ if (subrNumber < 0 || subrNumber >= subrsIndex.count) {
+ validationCommand = CharstringValidationData[value];
+ warn('Out of bounds subrIndex for ' + validationCommand.id);
+ return false;
+ }
+ state.stackSize = stackSize;
+ state.callDepth++;
+ var valid = this.parseCharString(state, subrsIndex.get(subrNumber), localSubrIndex, globalSubrIndex);
+ if (!valid) {
+ return false;
+ }
+ state.callDepth--;
+ stackSize = state.stackSize;
+ continue;
+ } else if (value === 11) {
+ state.stackSize = stackSize;
+ return true;
+ } else {
+ validationCommand = CharstringValidationData[value];
+ }
+ if (validationCommand) {
+ if (validationCommand.stem) {
+ state.hints += stackSize >> 1;
+ }
+ if ('min' in validationCommand) {
+ if (!state.undefStack && stackSize < validationCommand.min) {
+ warn('Not enough parameters for ' + validationCommand.id + '; actual: ' + stackSize + ', expected: ' + validationCommand.min);
+ return false;
+ }
+ }
+ if (state.firstStackClearing && validationCommand.stackClearing) {
+ state.firstStackClearing = false;
+ // the optional character width can be found before the first
+ // stack-clearing command arguments
+ stackSize -= validationCommand.min;
+ if (stackSize >= 2 && validationCommand.stem) {
+ // there are even amount of arguments for stem commands
+ stackSize %= 2;
+ } else if (stackSize > 1) {
+ warn('Found too many parameters for stack-clearing command');
+ }
+ if (stackSize > 0 && stack[stackSize - 1] >= 0) {
+ state.width = stack[stackSize - 1];
+ }
+ }
+ if ('stackDelta' in validationCommand) {
+ if ('stackFn' in validationCommand) {
+ validationCommand.stackFn(stack, stackSize);
+ }
+ stackSize += validationCommand.stackDelta;
+ } else if (validationCommand.stackClearing) {
+ stackSize = 0;
+ } else if (validationCommand.resetStack) {
+ stackSize = 0;
+ state.undefStack = false;
+ } else if (validationCommand.undefStack) {
+ stackSize = 0;
+ state.undefStack = true;
+ state.firstStackClearing = false;
+ }
+ }
+ }
+ state.stackSize = stackSize;
+ return true;
+ },
+ parseCharStrings: function CFFParser_parseCharStrings(charStrings, localSubrIndex, globalSubrIndex, fdSelect, fdArray) {
+ var seacs = [];
+ var widths = [];
+ var count = charStrings.count;
+ for (var i = 0; i < count; i++) {
+ var charstring = charStrings.get(i);
+ var state = {
+ callDepth: 0,
+ stackSize: 0,
+ stack: [],
+ undefStack: true,
+ hints: 0,
+ firstStackClearing: true,
+ seac: null,
+ width: null
+ };
+ var valid = true;
+ var localSubrToUse = null;
+ if (fdSelect && fdArray.length) {
+ var fdIndex = fdSelect.getFDIndex(i);
+ if (fdIndex === -1) {
+ warn('Glyph index is not in fd select.');
+ valid = false;
+ }
+ if (fdIndex >= fdArray.length) {
+ warn('Invalid fd index for glyph index.');
+ valid = false;
+ }
+ if (valid) {
+ localSubrToUse = fdArray[fdIndex].privateDict.subrsIndex;
+ }
+ } else if (localSubrIndex) {
+ localSubrToUse = localSubrIndex;
+ }
+ if (valid) {
+ valid = this.parseCharString(state, charstring, localSubrToUse, globalSubrIndex);
+ }
+ if (state.width !== null) {
+ widths[i] = state.width;
+ }
+ if (state.seac !== null) {
+ seacs[i] = state.seac;
+ }
+ if (!valid) {
+ // resetting invalid charstring to single 'endchar'
+ charStrings.set(i, new Uint8Array([14]));
+ }
+ }
+ return {
+ charStrings: charStrings,
+ seacs: seacs,
+ widths: widths
+ };
+ },
+ emptyPrivateDictionary: function CFFParser_emptyPrivateDictionary(parentDict) {
+ var privateDict = this.createDict(CFFPrivateDict, [], parentDict.strings);
+ parentDict.setByKey(18, [
+ 0,
+ 0
+ ]);
+ parentDict.privateDict = privateDict;
+ },
+ parsePrivateDict: function CFFParser_parsePrivateDict(parentDict) {
+ // no private dict, do nothing
+ if (!parentDict.hasName('Private')) {
+ this.emptyPrivateDictionary(parentDict);
+ return;
+ }
+ var privateOffset = parentDict.getByName('Private');
+ // make sure the params are formatted correctly
+ if (!isArray(privateOffset) || privateOffset.length !== 2) {
+ parentDict.removeByName('Private');
+ return;
+ }
+ var size = privateOffset[0];
+ var offset = privateOffset[1];
+ // remove empty dicts or ones that refer to invalid location
+ if (size === 0 || offset >= this.bytes.length) {
+ this.emptyPrivateDictionary(parentDict);
+ return;
+ }
+ var privateDictEnd = offset + size;
+ var dictData = this.bytes.subarray(offset, privateDictEnd);
+ var dict = this.parseDict(dictData);
+ var privateDict = this.createDict(CFFPrivateDict, dict, parentDict.strings);
+ parentDict.privateDict = privateDict;
+ // Parse the Subrs index also since it's relative to the private dict.
+ if (!privateDict.getByName('Subrs')) {
+ return;
+ }
+ var subrsOffset = privateDict.getByName('Subrs');
+ var relativeOffset = offset + subrsOffset;
+ // Validate the offset.
+ if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
+ this.emptyPrivateDictionary(parentDict);
+ return;
+ }
+ var subrsIndex = this.parseIndex(relativeOffset);
+ privateDict.subrsIndex = subrsIndex.obj;
+ },
+ parseCharsets: function CFFParser_parseCharsets(pos, length, strings, cid) {
+ if (pos === 0) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, ISOAdobeCharset);
+ } else if (pos === 1) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT, ExpertCharset);
+ } else if (pos === 2) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET, ExpertSubsetCharset);
+ }
+ var bytes = this.bytes;
+ var start = pos;
+ var format = bytes[pos++];
+ var charset = ['.notdef'];
+ var id, count, i;
+ // subtract 1 for the .notdef glyph
+ length -= 1;
+ switch (format) {
+ case 0:
+ for (i = 0; i < length; i++) {
+ id = bytes[pos++] << 8 | bytes[pos++];
+ charset.push(cid ? id : strings.get(id));
+ }
+ break;
+ case 1:
+ while (charset.length <= length) {
+ id = bytes[pos++] << 8 | bytes[pos++];
+ count = bytes[pos++];
+ for (i = 0; i <= count; i++) {
+ charset.push(cid ? id++ : strings.get(id++));
+ }
+ }
+ break;
+ case 2:
+ while (charset.length <= length) {
+ id = bytes[pos++] << 8 | bytes[pos++];
+ count = bytes[pos++] << 8 | bytes[pos++];
+ for (i = 0; i <= count; i++) {
+ charset.push(cid ? id++ : strings.get(id++));
+ }
+ }
+ break;
+ default:
+ error('Unknown charset format');
+ }
+ // Raw won't be needed if we actually compile the charset.
+ var end = pos;
+ var raw = bytes.subarray(start, end);
+ return new CFFCharset(false, format, charset, raw);
+ },
+ parseEncoding: function CFFParser_parseEncoding(pos, properties, strings, charset) {
+ var encoding = Object.create(null);
+ var bytes = this.bytes;
+ var predefined = false;
+ var hasSupplement = false;
+ var format, i, ii;
+ var raw = null;
+ function readSupplement() {
+ var supplementsCount = bytes[pos++];
+ for (i = 0; i < supplementsCount; i++) {
+ var code = bytes[pos++];
+ var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
+ encoding[code] = charset.indexOf(strings.get(sid));
+ }
+ }
+ if (pos === 0 || pos === 1) {
+ predefined = true;
+ format = pos;
+ var baseEncoding = pos ? ExpertEncoding : StandardEncoding;
+ for (i = 0, ii = charset.length; i < ii; i++) {
+ var index = baseEncoding.indexOf(charset[i]);
+ if (index !== -1) {
+ encoding[index] = i;
+ }
+ }
+ } else {
+ var dataStart = pos;
+ format = bytes[pos++];
+ switch (format & 0x7f) {
+ case 0:
+ var glyphsCount = bytes[pos++];
+ for (i = 1; i <= glyphsCount; i++) {
+ encoding[bytes[pos++]] = i;
+ }
+ break;
+ case 1:
+ var rangesCount = bytes[pos++];
+ var gid = 1;
+ for (i = 0; i < rangesCount; i++) {
+ var start = bytes[pos++];
+ var left = bytes[pos++];
+ for (var j = start; j <= start + left; j++) {
+ encoding[j] = gid++;
+ }
+ }
+ break;
+ default:
+ error('Unknown encoding format: ' + format + ' in CFF');
+ break;
+ }
+ var dataEnd = pos;
+ if (format & 0x80) {
+ // The font sanitizer does not support CFF encoding with a
+ // supplement, since the encoding is not really used to map
+ // between gid to glyph, let's overwrite what is declared in
+ // the top dictionary to let the sanitizer think the font use
+ // StandardEncoding, that's a lie but that's ok.
+ bytes[dataStart] &= 0x7f;
+ readSupplement();
+ hasSupplement = true;
+ }
+ raw = bytes.subarray(dataStart, dataEnd);
+ }
+ format = format & 0x7f;
+ return new CFFEncoding(predefined, format, encoding, raw);
+ },
+ parseFDSelect: function CFFParser_parseFDSelect(pos, length) {
+ var start = pos;
+ var bytes = this.bytes;
+ var format = bytes[pos++];
+ var fdSelect = [], rawBytes;
+ var i, invalidFirstGID = false;
+ switch (format) {
+ case 0:
+ for (i = 0; i < length; ++i) {
+ var id = bytes[pos++];
+ fdSelect.push(id);
+ }
+ rawBytes = bytes.subarray(start, pos);
+ break;
+ case 3:
+ var rangesCount = bytes[pos++] << 8 | bytes[pos++];
+ for (i = 0; i < rangesCount; ++i) {
+ var first = bytes[pos++] << 8 | bytes[pos++];
+ if (i === 0 && first !== 0) {
+ warn('parseFDSelect: The first range must have a first GID of 0' + ' -- trying to recover.');
+ invalidFirstGID = true;
+ first = 0;
+ }
+ var fdIndex = bytes[pos++];
+ var next = bytes[pos] << 8 | bytes[pos + 1];
+ for (var j = first; j < next; ++j) {
+ fdSelect.push(fdIndex);
+ }
+ }
+ // Advance past the sentinel(next).
+ pos += 2;
+ rawBytes = bytes.subarray(start, pos);
+ if (invalidFirstGID) {
+ rawBytes[3] = rawBytes[4] = 0;
+ }
+ // Adjust the first range, first GID.
+ break;
+ default:
+ error('parseFDSelect: Unknown format "' + format + '".');
+ break;
+ }
+ assert(fdSelect.length === length, 'parseFDSelect: Invalid font data.');
+ return new CFFFDSelect(fdSelect, rawBytes);
+ }
+ };
+ return CFFParser;
+ }();
+ // Compact Font Format
+ var CFF = function CFFClosure() {
+ function CFF() {
+ this.header = null;
+ this.names = [];
+ this.topDict = null;
+ this.strings = new CFFStrings();
+ this.globalSubrIndex = null;
+ // The following could really be per font, but since we only have one font
+ // store them here.
+ this.encoding = null;
+ this.charset = null;
+ this.charStrings = null;
+ this.fdArray = [];
+ this.fdSelect = null;
+ this.isCIDFont = false;
+ }
+ return CFF;
+ }();
+ var CFFHeader = function CFFHeaderClosure() {
+ function CFFHeader(major, minor, hdrSize, offSize) {
+ this.major = major;
+ this.minor = minor;
+ this.hdrSize = hdrSize;
+ this.offSize = offSize;
+ }
+ return CFFHeader;
+ }();
+ var CFFStrings = function CFFStringsClosure() {
+ function CFFStrings() {
+ this.strings = [];
+ }
+ CFFStrings.prototype = {
+ get: function CFFStrings_get(index) {
+ if (index >= 0 && index <= 390) {
+ return CFFStandardStrings[index];
+ }
+ if (index - 391 <= this.strings.length) {
+ return this.strings[index - 391];
+ }
+ return CFFStandardStrings[0];
+ },
+ add: function CFFStrings_add(value) {
+ this.strings.push(value);
+ },
+ get count() {
+ return this.strings.length;
+ }
+ };
+ return CFFStrings;
+ }();
+ var CFFIndex = function CFFIndexClosure() {
+ function CFFIndex() {
+ this.objects = [];
+ this.length = 0;
+ }
+ CFFIndex.prototype = {
+ add: function CFFIndex_add(data) {
+ this.length += data.length;
+ this.objects.push(data);
+ },
+ set: function CFFIndex_set(index, data) {
+ this.length += data.length - this.objects[index].length;
+ this.objects[index] = data;
+ },
+ get: function CFFIndex_get(index) {
+ return this.objects[index];
+ },
+ get count() {
+ return this.objects.length;
+ }
+ };
+ return CFFIndex;
+ }();
+ var CFFDict = function CFFDictClosure() {
+ function CFFDict(tables, strings) {
+ this.keyToNameMap = tables.keyToNameMap;
+ this.nameToKeyMap = tables.nameToKeyMap;
+ this.defaults = tables.defaults;
+ this.types = tables.types;
+ this.opcodes = tables.opcodes;
+ this.order = tables.order;
+ this.strings = strings;
+ this.values = Object.create(null);
+ }
+ CFFDict.prototype = {
+ // value should always be an array
+ setByKey: function CFFDict_setByKey(key, value) {
+ if (!(key in this.keyToNameMap)) {
+ return false;
+ }
+ var valueLength = value.length;
+ // ignore empty values
+ if (valueLength === 0) {
+ return true;
+ }
+ // Ignore invalid values (fixes bug1068432.pdf and bug1308536.pdf).
+ for (var i = 0; i < valueLength; i++) {
+ if (isNaN(value[i])) {
+ warn('Invalid CFFDict value: "' + value + '" for key "' + key + '".');
+ return true;
+ }
+ }
+ var type = this.types[key];
+ // remove the array wrapping these types of values
+ if (type === 'num' || type === 'sid' || type === 'offset') {
+ value = value[0];
+ }
+ this.values[key] = value;
+ return true;
+ },
+ setByName: function CFFDict_setByName(name, value) {
+ if (!(name in this.nameToKeyMap)) {
+ error('Invalid dictionary name "' + name + '"');
+ }
+ this.values[this.nameToKeyMap[name]] = value;
+ },
+ hasName: function CFFDict_hasName(name) {
+ return this.nameToKeyMap[name] in this.values;
+ },
+ getByName: function CFFDict_getByName(name) {
+ if (!(name in this.nameToKeyMap)) {
+ error('Invalid dictionary name "' + name + '"');
+ }
+ var key = this.nameToKeyMap[name];
+ if (!(key in this.values)) {
+ return this.defaults[key];
+ }
+ return this.values[key];
+ },
+ removeByName: function CFFDict_removeByName(name) {
+ delete this.values[this.nameToKeyMap[name]];
+ }
+ };
+ CFFDict.createTables = function CFFDict_createTables(layout) {
+ var tables = {
+ keyToNameMap: {},
+ nameToKeyMap: {},
+ defaults: {},
+ types: {},
+ opcodes: {},
+ order: []
+ };
+ for (var i = 0, ii = layout.length; i < ii; ++i) {
+ var entry = layout[i];
+ var key = isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
+ tables.keyToNameMap[key] = entry[1];
+ tables.nameToKeyMap[entry[1]] = key;
+ tables.types[key] = entry[2];
+ tables.defaults[key] = entry[3];
+ tables.opcodes[key] = isArray(entry[0]) ? entry[0] : [entry[0]];
+ tables.order.push(key);
+ }
+ return tables;
+ };
+ return CFFDict;
+ }();
+ var CFFTopDict = function CFFTopDictClosure() {
+ var layout = [
+ [
+ [
+ 12,
+ 30
+ ],
+ 'ROS',
+ [
+ 'sid',
+ 'sid',
+ 'num'
+ ],
+ null
+ ],
+ [
+ [
+ 12,
+ 20
+ ],
+ 'SyntheticBase',
+ 'num',
+ null
+ ],
+ [
+ 0,
+ 'version',
+ 'sid',
+ null
+ ],
+ [
+ 1,
+ 'Notice',
+ 'sid',
+ null
+ ],
+ [
+ [
+ 12,
+ 0
+ ],
+ 'Copyright',
+ 'sid',
+ null
+ ],
+ [
+ 2,
+ 'FullName',
+ 'sid',
+ null
+ ],
+ [
+ 3,
+ 'FamilyName',
+ 'sid',
+ null
+ ],
+ [
+ 4,
+ 'Weight',
+ 'sid',
+ null
+ ],
+ [
+ [
+ 12,
+ 1
+ ],
+ 'isFixedPitch',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 2
+ ],
+ 'ItalicAngle',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 3
+ ],
+ 'UnderlinePosition',
+ 'num',
+ -100
+ ],
+ [
+ [
+ 12,
+ 4
+ ],
+ 'UnderlineThickness',
+ 'num',
+ 50
+ ],
+ [
+ [
+ 12,
+ 5
+ ],
+ 'PaintType',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 6
+ ],
+ 'CharstringType',
+ 'num',
+ 2
+ ],
+ [
+ [
+ 12,
+ 7
+ ],
+ 'FontMatrix',
+ [
+ 'num',
+ 'num',
+ 'num',
+ 'num',
+ 'num',
+ 'num'
+ ],
+ [
+ 0.001,
+ 0,
+ 0,
+ 0.001,
+ 0,
+ 0
+ ]
+ ],
+ [
+ 13,
+ 'UniqueID',
+ 'num',
+ null
+ ],
+ [
+ 5,
+ 'FontBBox',
+ [
+ 'num',
+ 'num',
+ 'num',
+ 'num'
+ ],
+ [
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ ],
+ [
+ [
+ 12,
+ 8
+ ],
+ 'StrokeWidth',
+ 'num',
+ 0
+ ],
+ [
+ 14,
+ 'XUID',
+ 'array',
+ null
+ ],
+ [
+ 15,
+ 'charset',
+ 'offset',
+ 0
+ ],
+ [
+ 16,
+ 'Encoding',
+ 'offset',
+ 0
+ ],
+ [
+ 17,
+ 'CharStrings',
+ 'offset',
+ 0
+ ],
+ [
+ 18,
+ 'Private',
+ [
+ 'offset',
+ 'offset'
+ ],
+ null
+ ],
+ [
+ [
+ 12,
+ 21
+ ],
+ 'PostScript',
+ 'sid',
+ null
+ ],
+ [
+ [
+ 12,
+ 22
+ ],
+ 'BaseFontName',
+ 'sid',
+ null
+ ],
+ [
+ [
+ 12,
+ 23
+ ],
+ 'BaseFontBlend',
+ 'delta',
+ null
+ ],
+ [
+ [
+ 12,
+ 31
+ ],
+ 'CIDFontVersion',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 32
+ ],
+ 'CIDFontRevision',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 33
+ ],
+ 'CIDFontType',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 34
+ ],
+ 'CIDCount',
+ 'num',
+ 8720
+ ],
+ [
+ [
+ 12,
+ 35
+ ],
+ 'UIDBase',
+ 'num',
+ null
+ ],
+ // XXX: CID Fonts on DirectWrite 6.1 only seem to work if FDSelect comes
+ // before FDArray.
+ [
+ [
+ 12,
+ 37
+ ],
+ 'FDSelect',
+ 'offset',
+ null
+ ],
+ [
+ [
+ 12,
+ 36
+ ],
+ 'FDArray',
+ 'offset',
+ null
+ ],
+ [
+ [
+ 12,
+ 38
+ ],
+ 'FontName',
+ 'sid',
+ null
+ ]
+ ];
+ var tables = null;
+ function CFFTopDict(strings) {
+ if (tables === null) {
+ tables = CFFDict.createTables(layout);
+ }
+ CFFDict.call(this, tables, strings);
+ this.privateDict = null;
+ }
+ CFFTopDict.prototype = Object.create(CFFDict.prototype);
+ return CFFTopDict;
+ }();
+ var CFFPrivateDict = function CFFPrivateDictClosure() {
+ var layout = [
+ [
+ 6,
+ 'BlueValues',
+ 'delta',
+ null
+ ],
+ [
+ 7,
+ 'OtherBlues',
+ 'delta',
+ null
+ ],
+ [
+ 8,
+ 'FamilyBlues',
+ 'delta',
+ null
+ ],
+ [
+ 9,
+ 'FamilyOtherBlues',
+ 'delta',
+ null
+ ],
+ [
+ [
+ 12,
+ 9
+ ],
+ 'BlueScale',
+ 'num',
+ 0.039625
+ ],
+ [
+ [
+ 12,
+ 10
+ ],
+ 'BlueShift',
+ 'num',
+ 7
+ ],
+ [
+ [
+ 12,
+ 11
+ ],
+ 'BlueFuzz',
+ 'num',
+ 1
+ ],
+ [
+ 10,
+ 'StdHW',
+ 'num',
+ null
+ ],
+ [
+ 11,
+ 'StdVW',
+ 'num',
+ null
+ ],
+ [
+ [
+ 12,
+ 12
+ ],
+ 'StemSnapH',
+ 'delta',
+ null
+ ],
+ [
+ [
+ 12,
+ 13
+ ],
+ 'StemSnapV',
+ 'delta',
+ null
+ ],
+ [
+ [
+ 12,
+ 14
+ ],
+ 'ForceBold',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 17
+ ],
+ 'LanguageGroup',
+ 'num',
+ 0
+ ],
+ [
+ [
+ 12,
+ 18
+ ],
+ 'ExpansionFactor',
+ 'num',
+ 0.06
+ ],
+ [
+ [
+ 12,
+ 19
+ ],
+ 'initialRandomSeed',
+ 'num',
+ 0
+ ],
+ [
+ 20,
+ 'defaultWidthX',
+ 'num',
+ 0
+ ],
+ [
+ 21,
+ 'nominalWidthX',
+ 'num',
+ 0
+ ],
+ [
+ 19,
+ 'Subrs',
+ 'offset',
+ null
+ ]
+ ];
+ var tables = null;
+ function CFFPrivateDict(strings) {
+ if (tables === null) {
+ tables = CFFDict.createTables(layout);
+ }
+ CFFDict.call(this, tables, strings);
+ this.subrsIndex = null;
+ }
+ CFFPrivateDict.prototype = Object.create(CFFDict.prototype);
+ return CFFPrivateDict;
+ }();
+ var CFFCharsetPredefinedTypes = {
+ ISO_ADOBE: 0,
+ EXPERT: 1,
+ EXPERT_SUBSET: 2
+ };
+ var CFFCharset = function CFFCharsetClosure() {
+ function CFFCharset(predefined, format, charset, raw) {
+ this.predefined = predefined;
+ this.format = format;
+ this.charset = charset;
+ this.raw = raw;
+ }
+ return CFFCharset;
+ }();
+ var CFFEncoding = function CFFEncodingClosure() {
+ function CFFEncoding(predefined, format, encoding, raw) {
+ this.predefined = predefined;
+ this.format = format;
+ this.encoding = encoding;
+ this.raw = raw;
+ }
+ return CFFEncoding;
+ }();
+ var CFFFDSelect = function CFFFDSelectClosure() {
+ function CFFFDSelect(fdSelect, raw) {
+ this.fdSelect = fdSelect;
+ this.raw = raw;
+ }
+ CFFFDSelect.prototype = {
+ getFDIndex: function CFFFDSelect_get(glyphIndex) {
+ if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
+ return -1;
+ }
+ return this.fdSelect[glyphIndex];
+ }
+ };
+ return CFFFDSelect;
+ }();
+ // Helper class to keep track of where an offset is within the data and helps
+ // filling in that offset once it's known.
+ var CFFOffsetTracker = function CFFOffsetTrackerClosure() {
+ function CFFOffsetTracker() {
+ this.offsets = Object.create(null);
+ }
+ CFFOffsetTracker.prototype = {
+ isTracking: function CFFOffsetTracker_isTracking(key) {
+ return key in this.offsets;
+ },
+ track: function CFFOffsetTracker_track(key, location) {
+ if (key in this.offsets) {
+ error('Already tracking location of ' + key);
+ }
+ this.offsets[key] = location;
+ },
+ offset: function CFFOffsetTracker_offset(value) {
+ for (var key in this.offsets) {
+ this.offsets[key] += value;
+ }
+ },
+ setEntryLocation: function CFFOffsetTracker_setEntryLocation(key, values, output) {
+ if (!(key in this.offsets)) {
+ error('Not tracking location of ' + key);
+ }
+ var data = output.data;
+ var dataOffset = this.offsets[key];
+ var size = 5;
+ for (var i = 0, ii = values.length; i < ii; ++i) {
+ var offset0 = i * size + dataOffset;
+ var offset1 = offset0 + 1;
+ var offset2 = offset0 + 2;
+ var offset3 = offset0 + 3;
+ var offset4 = offset0 + 4;
+ // It's easy to screw up offsets so perform this sanity check.
+ if (data[offset0] !== 0x1d || data[offset1] !== 0 || data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) {
+ error('writing to an offset that is not empty');
+ }
+ var value = values[i];
+ data[offset0] = 0x1d;
+ data[offset1] = value >> 24 & 0xFF;
+ data[offset2] = value >> 16 & 0xFF;
+ data[offset3] = value >> 8 & 0xFF;
+ data[offset4] = value & 0xFF;
+ }
+ }
+ };
+ return CFFOffsetTracker;
+ }();
+ // Takes a CFF and converts it to the binary representation.
+ var CFFCompiler = function CFFCompilerClosure() {
+ function CFFCompiler(cff) {
+ this.cff = cff;
+ }
+ CFFCompiler.prototype = {
+ compile: function CFFCompiler_compile() {
+ var cff = this.cff;
+ var output = {
+ data: [],
+ length: 0,
+ add: function CFFCompiler_add(data) {
+ this.data = this.data.concat(data);
+ this.length = this.data.length;
+ }
+ };
+ // Compile the five entries that must be in order.
+ var header = this.compileHeader(cff.header);
+ output.add(header);
+ var nameIndex = this.compileNameIndex(cff.names);
+ output.add(nameIndex);
+ if (cff.isCIDFont) {
+ // The spec is unclear on how font matrices should relate to each other
+ // when there is one in the main top dict and the sub top dicts.
+ // Windows handles this differently than linux and osx so we have to
+ // normalize to work on all.
+ // Rules based off of some mailing list discussions:
+ // - If main font has a matrix and subfont doesn't, use the main matrix.
+ // - If no main font matrix and there is a subfont matrix, use the
+ // subfont matrix.
+ // - If both have matrices, concat together.
+ // - If neither have matrices, use default.
+ // To make this work on all platforms we move the top matrix into each
+ // sub top dict and concat if necessary.
+ if (cff.topDict.hasName('FontMatrix')) {
+ var base = cff.topDict.getByName('FontMatrix');
+ cff.topDict.removeByName('FontMatrix');
+ for (var i = 0, ii = cff.fdArray.length; i < ii; i++) {
+ var subDict = cff.fdArray[i];
+ var matrix = base.slice(0);
+ if (subDict.hasName('FontMatrix')) {
+ matrix = Util.transform(matrix, subDict.getByName('FontMatrix'));
+ }
+ subDict.setByName('FontMatrix', matrix);
+ }
+ }
+ }
+ var compiled = this.compileTopDicts([cff.topDict], output.length, cff.isCIDFont);
+ output.add(compiled.output);
+ var topDictTracker = compiled.trackers[0];
+ var stringIndex = this.compileStringIndex(cff.strings.strings);
+ output.add(stringIndex);
+ var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
+ output.add(globalSubrIndex);
+ // Now start on the other entries that have no specific order.
+ if (cff.encoding && cff.topDict.hasName('Encoding')) {
+ if (cff.encoding.predefined) {
+ topDictTracker.setEntryLocation('Encoding', [cff.encoding.format], output);
+ } else {
+ var encoding = this.compileEncoding(cff.encoding);
+ topDictTracker.setEntryLocation('Encoding', [output.length], output);
+ output.add(encoding);
+ }
+ }
+ if (cff.charset && cff.topDict.hasName('charset')) {
+ if (cff.charset.predefined) {
+ topDictTracker.setEntryLocation('charset', [cff.charset.format], output);
+ } else {
+ var charset = this.compileCharset(cff.charset);
+ topDictTracker.setEntryLocation('charset', [output.length], output);
+ output.add(charset);
+ }
+ }
+ var charStrings = this.compileCharStrings(cff.charStrings);
+ topDictTracker.setEntryLocation('CharStrings', [output.length], output);
+ output.add(charStrings);
+ if (cff.isCIDFont) {
+ // For some reason FDSelect must be in front of FDArray on windows. OSX
+ // and linux don't seem to care.
+ topDictTracker.setEntryLocation('FDSelect', [output.length], output);
+ var fdSelect = this.compileFDSelect(cff.fdSelect.raw);
+ output.add(fdSelect);
+ // It is unclear if the sub font dictionary can have CID related
+ // dictionary keys, but the sanitizer doesn't like them so remove them.
+ compiled = this.compileTopDicts(cff.fdArray, output.length, true);
+ topDictTracker.setEntryLocation('FDArray', [output.length], output);
+ output.add(compiled.output);
+ var fontDictTrackers = compiled.trackers;
+ this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
+ }
+ this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
+ // If the font data ends with INDEX whose object data is zero-length,
+ // the sanitizer will bail out. Add a dummy byte to avoid that.
+ output.add([0]);
+ return output.data;
+ },
+ encodeNumber: function CFFCompiler_encodeNumber(value) {
+ if (parseFloat(value) === parseInt(value, 10) && !isNaN(value)) {
+ // isInt
+ return this.encodeInteger(value);
+ } else {
+ return this.encodeFloat(value);
+ }
+ },
+ encodeFloat: function CFFCompiler_encodeFloat(num) {
+ var value = num.toString();
+ // rounding inaccurate doubles
+ var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);
+ if (m) {
+ var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length));
+ value = (Math.round(num * epsilon) / epsilon).toString();
+ }
+ var nibbles = '';
+ var i, ii;
+ for (i = 0, ii = value.length; i < ii; ++i) {
+ var a = value[i];
+ if (a === 'e') {
+ nibbles += value[++i] === '-' ? 'c' : 'b';
+ } else if (a === '.') {
+ nibbles += 'a';
+ } else if (a === '-') {
+ nibbles += 'e';
+ } else {
+ nibbles += a;
+ }
+ }
+ nibbles += nibbles.length & 1 ? 'f' : 'ff';
+ var out = [30];
+ for (i = 0, ii = nibbles.length; i < ii; i += 2) {
+ out.push(parseInt(nibbles.substr(i, 2), 16));
+ }
+ return out;
+ },
+ encodeInteger: function CFFCompiler_encodeInteger(value) {
+ var code;
+ if (value >= -107 && value <= 107) {
+ code = [value + 139];
+ } else if (value >= 108 && value <= 1131) {
+ value = value - 108;
+ code = [
+ (value >> 8) + 247,
+ value & 0xFF
+ ];
+ } else if (value >= -1131 && value <= -108) {
+ value = -value - 108;
+ code = [
+ (value >> 8) + 251,
+ value & 0xFF
+ ];
+ } else if (value >= -32768 && value <= 32767) {
+ code = [
+ 0x1c,
+ value >> 8 & 0xFF,
+ value & 0xFF
+ ];
+ } else {
+ code = [
+ 0x1d,
+ value >> 24 & 0xFF,
+ value >> 16 & 0xFF,
+ value >> 8 & 0xFF,
+ value & 0xFF
+ ];
+ }
+ return code;
+ },
+ compileHeader: function CFFCompiler_compileHeader(header) {
+ return [
+ header.major,
+ header.minor,
+ header.hdrSize,
+ header.offSize
+ ];
+ },
+ compileNameIndex: function CFFCompiler_compileNameIndex(names) {
+ var nameIndex = new CFFIndex();
+ for (var i = 0, ii = names.length; i < ii; ++i) {
+ nameIndex.add(stringToBytes(names[i]));
+ }
+ return this.compileIndex(nameIndex);
+ },
+ compileTopDicts: function CFFCompiler_compileTopDicts(dicts, length, removeCidKeys) {
+ var fontDictTrackers = [];
+ var fdArrayIndex = new CFFIndex();
+ for (var i = 0, ii = dicts.length; i < ii; ++i) {
+ var fontDict = dicts[i];
+ if (removeCidKeys) {
+ fontDict.removeByName('CIDFontVersion');
+ fontDict.removeByName('CIDFontRevision');
+ fontDict.removeByName('CIDFontType');
+ fontDict.removeByName('CIDCount');
+ fontDict.removeByName('UIDBase');
+ }
+ var fontDictTracker = new CFFOffsetTracker();
+ var fontDictData = this.compileDict(fontDict, fontDictTracker);
+ fontDictTrackers.push(fontDictTracker);
+ fdArrayIndex.add(fontDictData);
+ fontDictTracker.offset(length);
+ }
+ fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
+ return {
+ trackers: fontDictTrackers,
+ output: fdArrayIndex
+ };
+ },
+ compilePrivateDicts: function CFFCompiler_compilePrivateDicts(dicts, trackers, output) {
+ for (var i = 0, ii = dicts.length; i < ii; ++i) {
+ var fontDict = dicts[i];
+ assert(fontDict.privateDict && fontDict.hasName('Private'), 'There must be an private dictionary.');
+ var privateDict = fontDict.privateDict;
+ var privateDictTracker = new CFFOffsetTracker();
+ var privateDictData = this.compileDict(privateDict, privateDictTracker);
+ var outputLength = output.length;
+ privateDictTracker.offset(outputLength);
+ if (!privateDictData.length) {
+ // The private dictionary was empty, set the output length to zero to
+ // ensure the offset length isn't out of bounds in the eyes of the
+ // sanitizer.
+ outputLength = 0;
+ }
+ trackers[i].setEntryLocation('Private', [
+ privateDictData.length,
+ outputLength
+ ], output);
+ output.add(privateDictData);
+ if (privateDict.subrsIndex && privateDict.hasName('Subrs')) {
+ var subrs = this.compileIndex(privateDict.subrsIndex);
+ privateDictTracker.setEntryLocation('Subrs', [privateDictData.length], output);
+ output.add(subrs);
+ }
+ }
+ },
+ compileDict: function CFFCompiler_compileDict(dict, offsetTracker) {
+ var out = [];
+ // The dictionary keys must be in a certain order.
+ var order = dict.order;
+ for (var i = 0; i < order.length; ++i) {
+ var key = order[i];
+ if (!(key in dict.values)) {
+ continue;
+ }
+ var values = dict.values[key];
+ var types = dict.types[key];
+ if (!isArray(types)) {
+ types = [types];
+ }
+ if (!isArray(values)) {
+ values = [values];
+ }
+ // Remove any empty dict values.
+ if (values.length === 0) {
+ continue;
+ }
+ for (var j = 0, jj = types.length; j < jj; ++j) {
+ var type = types[j];
+ var value = values[j];
+ switch (type) {
+ case 'num':
+ case 'sid':
+ out = out.concat(this.encodeNumber(value));
+ break;
+ case 'offset':
+ // For offsets we just insert a 32bit integer so we don't have to
+ // deal with figuring out the length of the offset when it gets
+ // replaced later on by the compiler.
+ var name = dict.keyToNameMap[key];
+ // Some offsets have the offset and the length, so just record the
+ // position of the first one.
+ if (!offsetTracker.isTracking(name)) {
+ offsetTracker.track(name, out.length);
+ }
+ out = out.concat([
+ 0x1d,
+ 0,
+ 0,
+ 0,
+ 0
+ ]);
+ break;
+ case 'array':
+ case 'delta':
+ out = out.concat(this.encodeNumber(value));
+ for (var k = 1, kk = values.length; k < kk; ++k) {
+ out = out.concat(this.encodeNumber(values[k]));
+ }
+ break;
+ default:
+ error('Unknown data type of ' + type);
+ break;
+ }
+ }
+ out = out.concat(dict.opcodes[key]);
+ }
+ return out;
+ },
+ compileStringIndex: function CFFCompiler_compileStringIndex(strings) {
+ var stringIndex = new CFFIndex();
+ for (var i = 0, ii = strings.length; i < ii; ++i) {
+ stringIndex.add(stringToBytes(strings[i]));
+ }
+ return this.compileIndex(stringIndex);
+ },
+ compileGlobalSubrIndex: function CFFCompiler_compileGlobalSubrIndex() {
+ var globalSubrIndex = this.cff.globalSubrIndex;
+ this.out.writeByteArray(this.compileIndex(globalSubrIndex));
+ },
+ compileCharStrings: function CFFCompiler_compileCharStrings(charStrings) {
+ return this.compileIndex(charStrings);
+ },
+ compileCharset: function CFFCompiler_compileCharset(charset) {
+ return this.compileTypedArray(charset.raw);
+ },
+ compileEncoding: function CFFCompiler_compileEncoding(encoding) {
+ return this.compileTypedArray(encoding.raw);
+ },
+ compileFDSelect: function CFFCompiler_compileFDSelect(fdSelect) {
+ return this.compileTypedArray(fdSelect);
+ },
+ compileTypedArray: function CFFCompiler_compileTypedArray(data) {
+ var out = [];
+ for (var i = 0, ii = data.length; i < ii; ++i) {
+ out[i] = data[i];
+ }
+ return out;
+ },
+ compileIndex: function CFFCompiler_compileIndex(index, trackers) {
+ trackers = trackers || [];
+ var objects = index.objects;
+ // First 2 bytes contains the number of objects contained into this index
+ var count = objects.length;
+ // If there is no object, just create an index. This technically
+ // should just be [0, 0] but OTS has an issue with that.
+ if (count === 0) {
+ return [
+ 0,
+ 0,
+ 0
+ ];
+ }
+ var data = [
+ count >> 8 & 0xFF,
+ count & 0xff
+ ];
+ var lastOffset = 1, i;
+ for (i = 0; i < count; ++i) {
+ lastOffset += objects[i].length;
+ }
+ var offsetSize;
+ if (lastOffset < 0x100) {
+ offsetSize = 1;
+ } else if (lastOffset < 0x10000) {
+ offsetSize = 2;
+ } else if (lastOffset < 0x1000000) {
+ offsetSize = 3;
+ } else {
+ offsetSize = 4;
+ }
+ // Next byte contains the offset size use to reference object in the file
+ data.push(offsetSize);
+ // Add another offset after this one because we need a new offset
+ var relativeOffset = 1;
+ for (i = 0; i < count + 1; i++) {
+ if (offsetSize === 1) {
+ data.push(relativeOffset & 0xFF);
+ } else if (offsetSize === 2) {
+ data.push(relativeOffset >> 8 & 0xFF, relativeOffset & 0xFF);
+ } else if (offsetSize === 3) {
+ data.push(relativeOffset >> 16 & 0xFF, relativeOffset >> 8 & 0xFF, relativeOffset & 0xFF);
+ } else {
+ data.push(relativeOffset >>> 24 & 0xFF, relativeOffset >> 16 & 0xFF, relativeOffset >> 8 & 0xFF, relativeOffset & 0xFF);
+ }
+ if (objects[i]) {
+ relativeOffset += objects[i].length;
+ }
+ }
+ for (i = 0; i < count; i++) {
+ // Notify the tracker where the object will be offset in the data.
+ if (trackers[i]) {
+ trackers[i].offset(data.length);
+ }
+ for (var j = 0, jj = objects[i].length; j < jj; j++) {
+ data.push(objects[i][j]);
+ }
+ }
+ return data;
+ }
+ };
+ return CFFCompiler;
+ }();
+ exports.CFFStandardStrings = CFFStandardStrings;
+ exports.CFFParser = CFFParser;
+ exports.CFF = CFF;
+ exports.CFFHeader = CFFHeader;
+ exports.CFFStrings = CFFStrings;
+ exports.CFFIndex = CFFIndex;
+ exports.CFFCharset = CFFCharset;
+ exports.CFFTopDict = CFFTopDict;
+ exports.CFFPrivateDict = CFFPrivateDict;
+ exports.CFFCompiler = CFFCompiler;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreChunkedStream = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var MissingDataException = sharedUtil.MissingDataException;
+ var arrayByteLength = sharedUtil.arrayByteLength;
+ var arraysToBytes = sharedUtil.arraysToBytes;
+ var assert = sharedUtil.assert;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var isInt = sharedUtil.isInt;
+ var isEmptyObj = sharedUtil.isEmptyObj;
+ var ChunkedStream = function ChunkedStreamClosure() {
+ function ChunkedStream(length, chunkSize, manager) {
+ this.bytes = new Uint8Array(length);
+ this.start = 0;
+ this.pos = 0;
+ this.end = length;
+ this.chunkSize = chunkSize;
+ this.loadedChunks = [];
+ this.numChunksLoaded = 0;
+ this.numChunks = Math.ceil(length / chunkSize);
+ this.manager = manager;
+ this.progressiveDataLength = 0;
+ this.lastSuccessfulEnsureByteChunk = -1;
+ }
+ // a single-entry cache
+ // required methods for a stream. if a particular stream does not
+ // implement these, an error should be thrown
+ ChunkedStream.prototype = {
+ getMissingChunks: function ChunkedStream_getMissingChunks() {
+ var chunks = [];
+ for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
+ if (!this.loadedChunks[chunk]) {
+ chunks.push(chunk);
+ }
+ }
+ return chunks;
+ },
+ getBaseStreams: function ChunkedStream_getBaseStreams() {
+ return [this];
+ },
+ allChunksLoaded: function ChunkedStream_allChunksLoaded() {
+ return this.numChunksLoaded === this.numChunks;
+ },
+ onReceiveData: function ChunkedStream_onReceiveData(begin, chunk) {
+ var end = begin + chunk.byteLength;
+ assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin);
+ // Using this.length is inaccurate here since this.start can be moved
+ // See ChunkedStream.moveStart()
+ var length = this.bytes.length;
+ assert(end % this.chunkSize === 0 || end === length, 'Bad end offset: ' + end);
+ this.bytes.set(new Uint8Array(chunk), begin);
+ var chunkSize = this.chunkSize;
+ var beginChunk = Math.floor(begin / chunkSize);
+ var endChunk = Math.floor((end - 1) / chunkSize) + 1;
+ var curChunk;
+ for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
+ if (!this.loadedChunks[curChunk]) {
+ this.loadedChunks[curChunk] = true;
+ ++this.numChunksLoaded;
+ }
+ }
+ },
+ onReceiveProgressiveData: function ChunkedStream_onReceiveProgressiveData(data) {
+ var position = this.progressiveDataLength;
+ var beginChunk = Math.floor(position / this.chunkSize);
+ this.bytes.set(new Uint8Array(data), position);
+ position += data.byteLength;
+ this.progressiveDataLength = position;
+ var endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize);
+ var curChunk;
+ for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
+ if (!this.loadedChunks[curChunk]) {
+ this.loadedChunks[curChunk] = true;
+ ++this.numChunksLoaded;
+ }
+ }
+ },
+ ensureByte: function ChunkedStream_ensureByte(pos) {
+ var chunk = Math.floor(pos / this.chunkSize);
+ if (chunk === this.lastSuccessfulEnsureByteChunk) {
+ return;
+ }
+ if (!this.loadedChunks[chunk]) {
+ throw new MissingDataException(pos, pos + 1);
+ }
+ this.lastSuccessfulEnsureByteChunk = chunk;
+ },
+ ensureRange: function ChunkedStream_ensureRange(begin, end) {
+ if (begin >= end) {
+ return;
+ }
+ if (end <= this.progressiveDataLength) {
+ return;
+ }
+ var chunkSize = this.chunkSize;
+ var beginChunk = Math.floor(begin / chunkSize);
+ var endChunk = Math.floor((end - 1) / chunkSize) + 1;
+ for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+ if (!this.loadedChunks[chunk]) {
+ throw new MissingDataException(begin, end);
+ }
+ }
+ },
+ nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
+ var chunk, numChunks = this.numChunks;
+ for (var i = 0; i < numChunks; ++i) {
+ chunk = (beginChunk + i) % numChunks;
+ // Wrap around to beginning
+ if (!this.loadedChunks[chunk]) {
+ return chunk;
+ }
+ }
+ return null;
+ },
+ hasChunk: function ChunkedStream_hasChunk(chunk) {
+ return !!this.loadedChunks[chunk];
+ },
+ get length() {
+ return this.end - this.start;
+ },
+ get isEmpty() {
+ return this.length === 0;
+ },
+ getByte: function ChunkedStream_getByte() {
+ var pos = this.pos;
+ if (pos >= this.end) {
+ return -1;
+ }
+ this.ensureByte(pos);
+ return this.bytes[this.pos++];
+ },
+ getUint16: function ChunkedStream_getUint16() {
+ var b0 = this.getByte();
+ var b1 = this.getByte();
+ if (b0 === -1 || b1 === -1) {
+ return -1;
+ }
+ return (b0 << 8) + b1;
+ },
+ getInt32: function ChunkedStream_getInt32() {
+ var b0 = this.getByte();
+ var b1 = this.getByte();
+ var b2 = this.getByte();
+ var b3 = this.getByte();
+ return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
+ },
+ // returns subarray of original buffer
+ // should only be read
+ getBytes: function ChunkedStream_getBytes(length) {
+ var bytes = this.bytes;
+ var pos = this.pos;
+ var strEnd = this.end;
+ if (!length) {
+ this.ensureRange(pos, strEnd);
+ return bytes.subarray(pos, strEnd);
+ }
+ var end = pos + length;
+ if (end > strEnd) {
+ end = strEnd;
+ }
+ this.ensureRange(pos, end);
+ this.pos = end;
+ return bytes.subarray(pos, end);
+ },
+ peekByte: function ChunkedStream_peekByte() {
+ var peekedByte = this.getByte();
+ this.pos--;
+ return peekedByte;
+ },
+ peekBytes: function ChunkedStream_peekBytes(length) {
+ var bytes = this.getBytes(length);
+ this.pos -= bytes.length;
+ return bytes;
+ },
+ getByteRange: function ChunkedStream_getBytes(begin, end) {
+ this.ensureRange(begin, end);
+ return this.bytes.subarray(begin, end);
+ },
+ skip: function ChunkedStream_skip(n) {
+ if (!n) {
+ n = 1;
+ }
+ this.pos += n;
+ },
+ reset: function ChunkedStream_reset() {
+ this.pos = this.start;
+ },
+ moveStart: function ChunkedStream_moveStart() {
+ this.start = this.pos;
+ },
+ makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
+ this.ensureRange(start, start + length);
+ function ChunkedStreamSubstream() {
+ }
+ ChunkedStreamSubstream.prototype = Object.create(this);
+ ChunkedStreamSubstream.prototype.getMissingChunks = function () {
+ var chunkSize = this.chunkSize;
+ var beginChunk = Math.floor(this.start / chunkSize);
+ var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
+ var missingChunks = [];
+ for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+ if (!this.loadedChunks[chunk]) {
+ missingChunks.push(chunk);
+ }
+ }
+ return missingChunks;
+ };
+ var subStream = new ChunkedStreamSubstream();
+ subStream.pos = subStream.start = start;
+ subStream.end = start + length || this.end;
+ subStream.dict = dict;
+ return subStream;
+ },
+ isStream: true
+ };
+ return ChunkedStream;
+ }();
+ var ChunkedStreamManager = function ChunkedStreamManagerClosure() {
+ function ChunkedStreamManager(pdfNetworkStream, args) {
+ var chunkSize = args.rangeChunkSize;
+ var length = args.length;
+ this.stream = new ChunkedStream(length, chunkSize, this);
+ this.length = length;
+ this.chunkSize = chunkSize;
+ this.pdfNetworkStream = pdfNetworkStream;
+ this.url = args.url;
+ this.disableAutoFetch = args.disableAutoFetch;
+ this.msgHandler = args.msgHandler;
+ this.currRequestId = 0;
+ this.chunksNeededByRequest = Object.create(null);
+ this.requestsByChunk = Object.create(null);
+ this.promisesByRequest = Object.create(null);
+ this.progressiveDataLength = 0;
+ this.aborted = false;
+ this._loadedStreamCapability = createPromiseCapability();
+ }
+ ChunkedStreamManager.prototype = {
+ onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
+ return this._loadedStreamCapability.promise;
+ },
+ sendRequest: function ChunkedStreamManager_sendRequest(begin, end) {
+ var rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
+ if (!rangeReader.isStreamingSupported) {
+ rangeReader.onProgress = this.onProgress.bind(this);
+ }
+ var chunks = [], loaded = 0;
+ var manager = this;
+ var promise = new Promise(function (resolve, reject) {
+ var readChunk = function (chunk) {
+ try {
+ if (!chunk.done) {
+ var data = chunk.value;
+ chunks.push(data);
+ loaded += arrayByteLength(data);
+ if (rangeReader.isStreamingSupported) {
+ manager.onProgress({ loaded: loaded });
+ }
+ rangeReader.read().then(readChunk, reject);
+ return;
+ }
+ var chunkData = arraysToBytes(chunks);
+ chunks = null;
+ resolve(chunkData);
+ } catch (e) {
+ reject(e);
+ }
+ };
+ rangeReader.read().then(readChunk, reject);
+ });
+ promise.then(function (data) {
+ if (this.aborted) {
+ return;
+ }
+ // ignoring any data after abort
+ this.onReceiveData({
+ chunk: data,
+ begin: begin
+ });
+ }.bind(this));
+ },
+ // TODO check errors
+ // Get all the chunks that are not yet loaded and groups them into
+ // contiguous ranges to load in as few requests as possible
+ requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
+ var missingChunks = this.stream.getMissingChunks();
+ this._requestChunks(missingChunks);
+ return this._loadedStreamCapability.promise;
+ },
+ _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
+ var requestId = this.currRequestId++;
+ var i, ii;
+ var chunksNeeded = Object.create(null);
+ this.chunksNeededByRequest[requestId] = chunksNeeded;
+ for (i = 0, ii = chunks.length; i < ii; i++) {
+ if (!this.stream.hasChunk(chunks[i])) {
+ chunksNeeded[chunks[i]] = true;
+ }
+ }
+ if (isEmptyObj(chunksNeeded)) {
+ return Promise.resolve();
+ }
+ var capability = createPromiseCapability();
+ this.promisesByRequest[requestId] = capability;
+ var chunksToRequest = [];
+ for (var chunk in chunksNeeded) {
+ chunk = chunk | 0;
+ if (!(chunk in this.requestsByChunk)) {
+ this.requestsByChunk[chunk] = [];
+ chunksToRequest.push(chunk);
+ }
+ this.requestsByChunk[chunk].push(requestId);
+ }
+ if (!chunksToRequest.length) {
+ return capability.promise;
+ }
+ var groupedChunksToRequest = this.groupChunks(chunksToRequest);
+ for (i = 0; i < groupedChunksToRequest.length; ++i) {
+ var groupedChunk = groupedChunksToRequest[i];
+ var begin = groupedChunk.beginChunk * this.chunkSize;
+ var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
+ this.sendRequest(begin, end);
+ }
+ return capability.promise;
+ },
+ getStream: function ChunkedStreamManager_getStream() {
+ return this.stream;
+ },
+ // Loads any chunks in the requested range that are not yet loaded
+ requestRange: function ChunkedStreamManager_requestRange(begin, end) {
+ end = Math.min(end, this.length);
+ var beginChunk = this.getBeginChunk(begin);
+ var endChunk = this.getEndChunk(end);
+ var chunks = [];
+ for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+ chunks.push(chunk);
+ }
+ return this._requestChunks(chunks);
+ },
+ requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
+ ranges = ranges || [];
+ var chunksToRequest = [];
+ for (var i = 0; i < ranges.length; i++) {
+ var beginChunk = this.getBeginChunk(ranges[i].begin);
+ var endChunk = this.getEndChunk(ranges[i].end);
+ for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+ if (chunksToRequest.indexOf(chunk) < 0) {
+ chunksToRequest.push(chunk);
+ }
+ }
+ }
+ chunksToRequest.sort(function (a, b) {
+ return a - b;
+ });
+ return this._requestChunks(chunksToRequest);
+ },
+ // Groups a sorted array of chunks into as few contiguous larger
+ // chunks as possible
+ groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
+ var groupedChunks = [];
+ var beginChunk = -1;
+ var prevChunk = -1;
+ for (var i = 0; i < chunks.length; ++i) {
+ var chunk = chunks[i];
+ if (beginChunk < 0) {
+ beginChunk = chunk;
+ }
+ if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
+ groupedChunks.push({
+ beginChunk: beginChunk,
+ endChunk: prevChunk + 1
+ });
+ beginChunk = chunk;
+ }
+ if (i + 1 === chunks.length) {
+ groupedChunks.push({
+ beginChunk: beginChunk,
+ endChunk: chunk + 1
+ });
+ }
+ prevChunk = chunk;
+ }
+ return groupedChunks;
+ },
+ onProgress: function ChunkedStreamManager_onProgress(args) {
+ var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize + args.loaded;
+ this.msgHandler.send('DocProgress', {
+ loaded: bytesLoaded,
+ total: this.length
+ });
+ },
+ onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
+ var chunk = args.chunk;
+ var isProgressive = args.begin === undefined;
+ var begin = isProgressive ? this.progressiveDataLength : args.begin;
+ var end = begin + chunk.byteLength;
+ var beginChunk = Math.floor(begin / this.chunkSize);
+ var endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
+ if (isProgressive) {
+ this.stream.onReceiveProgressiveData(chunk);
+ this.progressiveDataLength = end;
+ } else {
+ this.stream.onReceiveData(begin, chunk);
+ }
+ if (this.stream.allChunksLoaded()) {
+ this._loadedStreamCapability.resolve(this.stream);
+ }
+ var loadedRequests = [];
+ var i, requestId;
+ for (chunk = beginChunk; chunk < endChunk; ++chunk) {
+ // The server might return more chunks than requested
+ var requestIds = this.requestsByChunk[chunk] || [];
+ delete this.requestsByChunk[chunk];
+ for (i = 0; i < requestIds.length; ++i) {
+ requestId = requestIds[i];
+ var chunksNeeded = this.chunksNeededByRequest[requestId];
+ if (chunk in chunksNeeded) {
+ delete chunksNeeded[chunk];
+ }
+ if (!isEmptyObj(chunksNeeded)) {
+ continue;
+ }
+ loadedRequests.push(requestId);
+ }
+ }
+ // If there are no pending requests, automatically fetch the next
+ // unfetched chunk of the PDF
+ if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
+ var nextEmptyChunk;
+ if (this.stream.numChunksLoaded === 1) {
+ // This is a special optimization so that after fetching the first
+ // chunk, rather than fetching the second chunk, we fetch the last
+ // chunk.
+ var lastChunk = this.stream.numChunks - 1;
+ if (!this.stream.hasChunk(lastChunk)) {
+ nextEmptyChunk = lastChunk;
+ }
+ } else {
+ nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
+ }
+ if (isInt(nextEmptyChunk)) {
+ this._requestChunks([nextEmptyChunk]);
+ }
+ }
+ for (i = 0; i < loadedRequests.length; ++i) {
+ requestId = loadedRequests[i];
+ var capability = this.promisesByRequest[requestId];
+ delete this.promisesByRequest[requestId];
+ capability.resolve();
+ }
+ this.msgHandler.send('DocProgress', {
+ loaded: this.stream.numChunksLoaded * this.chunkSize,
+ total: this.length
+ });
+ },
+ onError: function ChunkedStreamManager_onError(err) {
+ this._loadedStreamCapability.reject(err);
+ },
+ getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
+ var chunk = Math.floor(begin / this.chunkSize);
+ return chunk;
+ },
+ getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
+ var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
+ return chunk;
+ },
+ abort: function ChunkedStreamManager_abort() {
+ this.aborted = true;
+ if (this.pdfNetworkStream) {
+ this.pdfNetworkStream.cancelAllRequests('abort');
+ }
+ for (var requestId in this.promisesByRequest) {
+ var capability = this.promisesByRequest[requestId];
+ capability.reject(new Error('Request was aborted'));
+ }
+ }
+ };
+ return ChunkedStreamManager;
+ }();
+ exports.ChunkedStream = ChunkedStream;
+ exports.ChunkedStreamManager = ChunkedStreamManager;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreGlyphList = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var getLookupTableFactory = sharedUtil.getLookupTableFactory;
+ var getGlyphsUnicode = getLookupTableFactory(function (t) {
+ t['A'] = 0x0041;
+ t['AE'] = 0x00C6;
+ t['AEacute'] = 0x01FC;
+ t['AEmacron'] = 0x01E2;
+ t['AEsmall'] = 0xF7E6;
+ t['Aacute'] = 0x00C1;
+ t['Aacutesmall'] = 0xF7E1;
+ t['Abreve'] = 0x0102;
+ t['Abreveacute'] = 0x1EAE;
+ t['Abrevecyrillic'] = 0x04D0;
+ t['Abrevedotbelow'] = 0x1EB6;
+ t['Abrevegrave'] = 0x1EB0;
+ t['Abrevehookabove'] = 0x1EB2;
+ t['Abrevetilde'] = 0x1EB4;
+ t['Acaron'] = 0x01CD;
+ t['Acircle'] = 0x24B6;
+ t['Acircumflex'] = 0x00C2;
+ t['Acircumflexacute'] = 0x1EA4;
+ t['Acircumflexdotbelow'] = 0x1EAC;
+ t['Acircumflexgrave'] = 0x1EA6;
+ t['Acircumflexhookabove'] = 0x1EA8;
+ t['Acircumflexsmall'] = 0xF7E2;
+ t['Acircumflextilde'] = 0x1EAA;
+ t['Acute'] = 0xF6C9;
+ t['Acutesmall'] = 0xF7B4;
+ t['Acyrillic'] = 0x0410;
+ t['Adblgrave'] = 0x0200;
+ t['Adieresis'] = 0x00C4;
+ t['Adieresiscyrillic'] = 0x04D2;
+ t['Adieresismacron'] = 0x01DE;
+ t['Adieresissmall'] = 0xF7E4;
+ t['Adotbelow'] = 0x1EA0;
+ t['Adotmacron'] = 0x01E0;
+ t['Agrave'] = 0x00C0;
+ t['Agravesmall'] = 0xF7E0;
+ t['Ahookabove'] = 0x1EA2;
+ t['Aiecyrillic'] = 0x04D4;
+ t['Ainvertedbreve'] = 0x0202;
+ t['Alpha'] = 0x0391;
+ t['Alphatonos'] = 0x0386;
+ t['Amacron'] = 0x0100;
+ t['Amonospace'] = 0xFF21;
+ t['Aogonek'] = 0x0104;
+ t['Aring'] = 0x00C5;
+ t['Aringacute'] = 0x01FA;
+ t['Aringbelow'] = 0x1E00;
+ t['Aringsmall'] = 0xF7E5;
+ t['Asmall'] = 0xF761;
+ t['Atilde'] = 0x00C3;
+ t['Atildesmall'] = 0xF7E3;
+ t['Aybarmenian'] = 0x0531;
+ t['B'] = 0x0042;
+ t['Bcircle'] = 0x24B7;
+ t['Bdotaccent'] = 0x1E02;
+ t['Bdotbelow'] = 0x1E04;
+ t['Becyrillic'] = 0x0411;
+ t['Benarmenian'] = 0x0532;
+ t['Beta'] = 0x0392;
+ t['Bhook'] = 0x0181;
+ t['Blinebelow'] = 0x1E06;
+ t['Bmonospace'] = 0xFF22;
+ t['Brevesmall'] = 0xF6F4;
+ t['Bsmall'] = 0xF762;
+ t['Btopbar'] = 0x0182;
+ t['C'] = 0x0043;
+ t['Caarmenian'] = 0x053E;
+ t['Cacute'] = 0x0106;
+ t['Caron'] = 0xF6CA;
+ t['Caronsmall'] = 0xF6F5;
+ t['Ccaron'] = 0x010C;
+ t['Ccedilla'] = 0x00C7;
+ t['Ccedillaacute'] = 0x1E08;
+ t['Ccedillasmall'] = 0xF7E7;
+ t['Ccircle'] = 0x24B8;
+ t['Ccircumflex'] = 0x0108;
+ t['Cdot'] = 0x010A;
+ t['Cdotaccent'] = 0x010A;
+ t['Cedillasmall'] = 0xF7B8;
+ t['Chaarmenian'] = 0x0549;
+ t['Cheabkhasiancyrillic'] = 0x04BC;
+ t['Checyrillic'] = 0x0427;
+ t['Chedescenderabkhasiancyrillic'] = 0x04BE;
+ t['Chedescendercyrillic'] = 0x04B6;
+ t['Chedieresiscyrillic'] = 0x04F4;
+ t['Cheharmenian'] = 0x0543;
+ t['Chekhakassiancyrillic'] = 0x04CB;
+ t['Cheverticalstrokecyrillic'] = 0x04B8;
+ t['Chi'] = 0x03A7;
+ t['Chook'] = 0x0187;
+ t['Circumflexsmall'] = 0xF6F6;
+ t['Cmonospace'] = 0xFF23;
+ t['Coarmenian'] = 0x0551;
+ t['Csmall'] = 0xF763;
+ t['D'] = 0x0044;
+ t['DZ'] = 0x01F1;
+ t['DZcaron'] = 0x01C4;
+ t['Daarmenian'] = 0x0534;
+ t['Dafrican'] = 0x0189;
+ t['Dcaron'] = 0x010E;
+ t['Dcedilla'] = 0x1E10;
+ t['Dcircle'] = 0x24B9;
+ t['Dcircumflexbelow'] = 0x1E12;
+ t['Dcroat'] = 0x0110;
+ t['Ddotaccent'] = 0x1E0A;
+ t['Ddotbelow'] = 0x1E0C;
+ t['Decyrillic'] = 0x0414;
+ t['Deicoptic'] = 0x03EE;
+ t['Delta'] = 0x2206;
+ t['Deltagreek'] = 0x0394;
+ t['Dhook'] = 0x018A;
+ t['Dieresis'] = 0xF6CB;
+ t['DieresisAcute'] = 0xF6CC;
+ t['DieresisGrave'] = 0xF6CD;
+ t['Dieresissmall'] = 0xF7A8;
+ t['Digammagreek'] = 0x03DC;
+ t['Djecyrillic'] = 0x0402;
+ t['Dlinebelow'] = 0x1E0E;
+ t['Dmonospace'] = 0xFF24;
+ t['Dotaccentsmall'] = 0xF6F7;
+ t['Dslash'] = 0x0110;
+ t['Dsmall'] = 0xF764;
+ t['Dtopbar'] = 0x018B;
+ t['Dz'] = 0x01F2;
+ t['Dzcaron'] = 0x01C5;
+ t['Dzeabkhasiancyrillic'] = 0x04E0;
+ t['Dzecyrillic'] = 0x0405;
+ t['Dzhecyrillic'] = 0x040F;
+ t['E'] = 0x0045;
+ t['Eacute'] = 0x00C9;
+ t['Eacutesmall'] = 0xF7E9;
+ t['Ebreve'] = 0x0114;
+ t['Ecaron'] = 0x011A;
+ t['Ecedillabreve'] = 0x1E1C;
+ t['Echarmenian'] = 0x0535;
+ t['Ecircle'] = 0x24BA;
+ t['Ecircumflex'] = 0x00CA;
+ t['Ecircumflexacute'] = 0x1EBE;
+ t['Ecircumflexbelow'] = 0x1E18;
+ t['Ecircumflexdotbelow'] = 0x1EC6;
+ t['Ecircumflexgrave'] = 0x1EC0;
+ t['Ecircumflexhookabove'] = 0x1EC2;
+ t['Ecircumflexsmall'] = 0xF7EA;
+ t['Ecircumflextilde'] = 0x1EC4;
+ t['Ecyrillic'] = 0x0404;
+ t['Edblgrave'] = 0x0204;
+ t['Edieresis'] = 0x00CB;
+ t['Edieresissmall'] = 0xF7EB;
+ t['Edot'] = 0x0116;
+ t['Edotaccent'] = 0x0116;
+ t['Edotbelow'] = 0x1EB8;
+ t['Efcyrillic'] = 0x0424;
+ t['Egrave'] = 0x00C8;
+ t['Egravesmall'] = 0xF7E8;
+ t['Eharmenian'] = 0x0537;
+ t['Ehookabove'] = 0x1EBA;
+ t['Eightroman'] = 0x2167;
+ t['Einvertedbreve'] = 0x0206;
+ t['Eiotifiedcyrillic'] = 0x0464;
+ t['Elcyrillic'] = 0x041B;
+ t['Elevenroman'] = 0x216A;
+ t['Emacron'] = 0x0112;
+ t['Emacronacute'] = 0x1E16;
+ t['Emacrongrave'] = 0x1E14;
+ t['Emcyrillic'] = 0x041C;
+ t['Emonospace'] = 0xFF25;
+ t['Encyrillic'] = 0x041D;
+ t['Endescendercyrillic'] = 0x04A2;
+ t['Eng'] = 0x014A;
+ t['Enghecyrillic'] = 0x04A4;
+ t['Enhookcyrillic'] = 0x04C7;
+ t['Eogonek'] = 0x0118;
+ t['Eopen'] = 0x0190;
+ t['Epsilon'] = 0x0395;
+ t['Epsilontonos'] = 0x0388;
+ t['Ercyrillic'] = 0x0420;
+ t['Ereversed'] = 0x018E;
+ t['Ereversedcyrillic'] = 0x042D;
+ t['Escyrillic'] = 0x0421;
+ t['Esdescendercyrillic'] = 0x04AA;
+ t['Esh'] = 0x01A9;
+ t['Esmall'] = 0xF765;
+ t['Eta'] = 0x0397;
+ t['Etarmenian'] = 0x0538;
+ t['Etatonos'] = 0x0389;
+ t['Eth'] = 0x00D0;
+ t['Ethsmall'] = 0xF7F0;
+ t['Etilde'] = 0x1EBC;
+ t['Etildebelow'] = 0x1E1A;
+ t['Euro'] = 0x20AC;
+ t['Ezh'] = 0x01B7;
+ t['Ezhcaron'] = 0x01EE;
+ t['Ezhreversed'] = 0x01B8;
+ t['F'] = 0x0046;
+ t['Fcircle'] = 0x24BB;
+ t['Fdotaccent'] = 0x1E1E;
+ t['Feharmenian'] = 0x0556;
+ t['Feicoptic'] = 0x03E4;
+ t['Fhook'] = 0x0191;
+ t['Fitacyrillic'] = 0x0472;
+ t['Fiveroman'] = 0x2164;
+ t['Fmonospace'] = 0xFF26;
+ t['Fourroman'] = 0x2163;
+ t['Fsmall'] = 0xF766;
+ t['G'] = 0x0047;
+ t['GBsquare'] = 0x3387;
+ t['Gacute'] = 0x01F4;
+ t['Gamma'] = 0x0393;
+ t['Gammaafrican'] = 0x0194;
+ t['Gangiacoptic'] = 0x03EA;
+ t['Gbreve'] = 0x011E;
+ t['Gcaron'] = 0x01E6;
+ t['Gcedilla'] = 0x0122;
+ t['Gcircle'] = 0x24BC;
+ t['Gcircumflex'] = 0x011C;
+ t['Gcommaaccent'] = 0x0122;
+ t['Gdot'] = 0x0120;
+ t['Gdotaccent'] = 0x0120;
+ t['Gecyrillic'] = 0x0413;
+ t['Ghadarmenian'] = 0x0542;
+ t['Ghemiddlehookcyrillic'] = 0x0494;
+ t['Ghestrokecyrillic'] = 0x0492;
+ t['Gheupturncyrillic'] = 0x0490;
+ t['Ghook'] = 0x0193;
+ t['Gimarmenian'] = 0x0533;
+ t['Gjecyrillic'] = 0x0403;
+ t['Gmacron'] = 0x1E20;
+ t['Gmonospace'] = 0xFF27;
+ t['Grave'] = 0xF6CE;
+ t['Gravesmall'] = 0xF760;
+ t['Gsmall'] = 0xF767;
+ t['Gsmallhook'] = 0x029B;
+ t['Gstroke'] = 0x01E4;
+ t['H'] = 0x0048;
+ t['H18533'] = 0x25CF;
+ t['H18543'] = 0x25AA;
+ t['H18551'] = 0x25AB;
+ t['H22073'] = 0x25A1;
+ t['HPsquare'] = 0x33CB;
+ t['Haabkhasiancyrillic'] = 0x04A8;
+ t['Hadescendercyrillic'] = 0x04B2;
+ t['Hardsigncyrillic'] = 0x042A;
+ t['Hbar'] = 0x0126;
+ t['Hbrevebelow'] = 0x1E2A;
+ t['Hcedilla'] = 0x1E28;
+ t['Hcircle'] = 0x24BD;
+ t['Hcircumflex'] = 0x0124;
+ t['Hdieresis'] = 0x1E26;
+ t['Hdotaccent'] = 0x1E22;
+ t['Hdotbelow'] = 0x1E24;
+ t['Hmonospace'] = 0xFF28;
+ t['Hoarmenian'] = 0x0540;
+ t['Horicoptic'] = 0x03E8;
+ t['Hsmall'] = 0xF768;
+ t['Hungarumlaut'] = 0xF6CF;
+ t['Hungarumlautsmall'] = 0xF6F8;
+ t['Hzsquare'] = 0x3390;
+ t['I'] = 0x0049;
+ t['IAcyrillic'] = 0x042F;
+ t['IJ'] = 0x0132;
+ t['IUcyrillic'] = 0x042E;
+ t['Iacute'] = 0x00CD;
+ t['Iacutesmall'] = 0xF7ED;
+ t['Ibreve'] = 0x012C;
+ t['Icaron'] = 0x01CF;
+ t['Icircle'] = 0x24BE;
+ t['Icircumflex'] = 0x00CE;
+ t['Icircumflexsmall'] = 0xF7EE;
+ t['Icyrillic'] = 0x0406;
+ t['Idblgrave'] = 0x0208;
+ t['Idieresis'] = 0x00CF;
+ t['Idieresisacute'] = 0x1E2E;
+ t['Idieresiscyrillic'] = 0x04E4;
+ t['Idieresissmall'] = 0xF7EF;
+ t['Idot'] = 0x0130;
+ t['Idotaccent'] = 0x0130;
+ t['Idotbelow'] = 0x1ECA;
+ t['Iebrevecyrillic'] = 0x04D6;
+ t['Iecyrillic'] = 0x0415;
+ t['Ifraktur'] = 0x2111;
+ t['Igrave'] = 0x00CC;
+ t['Igravesmall'] = 0xF7EC;
+ t['Ihookabove'] = 0x1EC8;
+ t['Iicyrillic'] = 0x0418;
+ t['Iinvertedbreve'] = 0x020A;
+ t['Iishortcyrillic'] = 0x0419;
+ t['Imacron'] = 0x012A;
+ t['Imacroncyrillic'] = 0x04E2;
+ t['Imonospace'] = 0xFF29;
+ t['Iniarmenian'] = 0x053B;
+ t['Iocyrillic'] = 0x0401;
+ t['Iogonek'] = 0x012E;
+ t['Iota'] = 0x0399;
+ t['Iotaafrican'] = 0x0196;
+ t['Iotadieresis'] = 0x03AA;
+ t['Iotatonos'] = 0x038A;
+ t['Ismall'] = 0xF769;
+ t['Istroke'] = 0x0197;
+ t['Itilde'] = 0x0128;
+ t['Itildebelow'] = 0x1E2C;
+ t['Izhitsacyrillic'] = 0x0474;
+ t['Izhitsadblgravecyrillic'] = 0x0476;
+ t['J'] = 0x004A;
+ t['Jaarmenian'] = 0x0541;
+ t['Jcircle'] = 0x24BF;
+ t['Jcircumflex'] = 0x0134;
+ t['Jecyrillic'] = 0x0408;
+ t['Jheharmenian'] = 0x054B;
+ t['Jmonospace'] = 0xFF2A;
+ t['Jsmall'] = 0xF76A;
+ t['K'] = 0x004B;
+ t['KBsquare'] = 0x3385;
+ t['KKsquare'] = 0x33CD;
+ t['Kabashkircyrillic'] = 0x04A0;
+ t['Kacute'] = 0x1E30;
+ t['Kacyrillic'] = 0x041A;
+ t['Kadescendercyrillic'] = 0x049A;
+ t['Kahookcyrillic'] = 0x04C3;
+ t['Kappa'] = 0x039A;
+ t['Kastrokecyrillic'] = 0x049E;
+ t['Kaverticalstrokecyrillic'] = 0x049C;
+ t['Kcaron'] = 0x01E8;
+ t['Kcedilla'] = 0x0136;
+ t['Kcircle'] = 0x24C0;
+ t['Kcommaaccent'] = 0x0136;
+ t['Kdotbelow'] = 0x1E32;
+ t['Keharmenian'] = 0x0554;
+ t['Kenarmenian'] = 0x053F;
+ t['Khacyrillic'] = 0x0425;
+ t['Kheicoptic'] = 0x03E6;
+ t['Khook'] = 0x0198;
+ t['Kjecyrillic'] = 0x040C;
+ t['Klinebelow'] = 0x1E34;
+ t['Kmonospace'] = 0xFF2B;
+ t['Koppacyrillic'] = 0x0480;
+ t['Koppagreek'] = 0x03DE;
+ t['Ksicyrillic'] = 0x046E;
+ t['Ksmall'] = 0xF76B;
+ t['L'] = 0x004C;
+ t['LJ'] = 0x01C7;
+ t['LL'] = 0xF6BF;
+ t['Lacute'] = 0x0139;
+ t['Lambda'] = 0x039B;
+ t['Lcaron'] = 0x013D;
+ t['Lcedilla'] = 0x013B;
+ t['Lcircle'] = 0x24C1;
+ t['Lcircumflexbelow'] = 0x1E3C;
+ t['Lcommaaccent'] = 0x013B;
+ t['Ldot'] = 0x013F;
+ t['Ldotaccent'] = 0x013F;
+ t['Ldotbelow'] = 0x1E36;
+ t['Ldotbelowmacron'] = 0x1E38;
+ t['Liwnarmenian'] = 0x053C;
+ t['Lj'] = 0x01C8;
+ t['Ljecyrillic'] = 0x0409;
+ t['Llinebelow'] = 0x1E3A;
+ t['Lmonospace'] = 0xFF2C;
+ t['Lslash'] = 0x0141;
+ t['Lslashsmall'] = 0xF6F9;
+ t['Lsmall'] = 0xF76C;
+ t['M'] = 0x004D;
+ t['MBsquare'] = 0x3386;
+ t['Macron'] = 0xF6D0;
+ t['Macronsmall'] = 0xF7AF;
+ t['Macute'] = 0x1E3E;
+ t['Mcircle'] = 0x24C2;
+ t['Mdotaccent'] = 0x1E40;
+ t['Mdotbelow'] = 0x1E42;
+ t['Menarmenian'] = 0x0544;
+ t['Mmonospace'] = 0xFF2D;
+ t['Msmall'] = 0xF76D;
+ t['Mturned'] = 0x019C;
+ t['Mu'] = 0x039C;
+ t['N'] = 0x004E;
+ t['NJ'] = 0x01CA;
+ t['Nacute'] = 0x0143;
+ t['Ncaron'] = 0x0147;
+ t['Ncedilla'] = 0x0145;
+ t['Ncircle'] = 0x24C3;
+ t['Ncircumflexbelow'] = 0x1E4A;
+ t['Ncommaaccent'] = 0x0145;
+ t['Ndotaccent'] = 0x1E44;
+ t['Ndotbelow'] = 0x1E46;
+ t['Nhookleft'] = 0x019D;
+ t['Nineroman'] = 0x2168;
+ t['Nj'] = 0x01CB;
+ t['Njecyrillic'] = 0x040A;
+ t['Nlinebelow'] = 0x1E48;
+ t['Nmonospace'] = 0xFF2E;
+ t['Nowarmenian'] = 0x0546;
+ t['Nsmall'] = 0xF76E;
+ t['Ntilde'] = 0x00D1;
+ t['Ntildesmall'] = 0xF7F1;
+ t['Nu'] = 0x039D;
+ t['O'] = 0x004F;
+ t['OE'] = 0x0152;
+ t['OEsmall'] = 0xF6FA;
+ t['Oacute'] = 0x00D3;
+ t['Oacutesmall'] = 0xF7F3;
+ t['Obarredcyrillic'] = 0x04E8;
+ t['Obarreddieresiscyrillic'] = 0x04EA;
+ t['Obreve'] = 0x014E;
+ t['Ocaron'] = 0x01D1;
+ t['Ocenteredtilde'] = 0x019F;
+ t['Ocircle'] = 0x24C4;
+ t['Ocircumflex'] = 0x00D4;
+ t['Ocircumflexacute'] = 0x1ED0;
+ t['Ocircumflexdotbelow'] = 0x1ED8;
+ t['Ocircumflexgrave'] = 0x1ED2;
+ t['Ocircumflexhookabove'] = 0x1ED4;
+ t['Ocircumflexsmall'] = 0xF7F4;
+ t['Ocircumflextilde'] = 0x1ED6;
+ t['Ocyrillic'] = 0x041E;
+ t['Odblacute'] = 0x0150;
+ t['Odblgrave'] = 0x020C;
+ t['Odieresis'] = 0x00D6;
+ t['Odieresiscyrillic'] = 0x04E6;
+ t['Odieresissmall'] = 0xF7F6;
+ t['Odotbelow'] = 0x1ECC;
+ t['Ogoneksmall'] = 0xF6FB;
+ t['Ograve'] = 0x00D2;
+ t['Ogravesmall'] = 0xF7F2;
+ t['Oharmenian'] = 0x0555;
+ t['Ohm'] = 0x2126;
+ t['Ohookabove'] = 0x1ECE;
+ t['Ohorn'] = 0x01A0;
+ t['Ohornacute'] = 0x1EDA;
+ t['Ohorndotbelow'] = 0x1EE2;
+ t['Ohorngrave'] = 0x1EDC;
+ t['Ohornhookabove'] = 0x1EDE;
+ t['Ohorntilde'] = 0x1EE0;
+ t['Ohungarumlaut'] = 0x0150;
+ t['Oi'] = 0x01A2;
+ t['Oinvertedbreve'] = 0x020E;
+ t['Omacron'] = 0x014C;
+ t['Omacronacute'] = 0x1E52;
+ t['Omacrongrave'] = 0x1E50;
+ t['Omega'] = 0x2126;
+ t['Omegacyrillic'] = 0x0460;
+ t['Omegagreek'] = 0x03A9;
+ t['Omegaroundcyrillic'] = 0x047A;
+ t['Omegatitlocyrillic'] = 0x047C;
+ t['Omegatonos'] = 0x038F;
+ t['Omicron'] = 0x039F;
+ t['Omicrontonos'] = 0x038C;
+ t['Omonospace'] = 0xFF2F;
+ t['Oneroman'] = 0x2160;
+ t['Oogonek'] = 0x01EA;
+ t['Oogonekmacron'] = 0x01EC;
+ t['Oopen'] = 0x0186;
+ t['Oslash'] = 0x00D8;
+ t['Oslashacute'] = 0x01FE;
+ t['Oslashsmall'] = 0xF7F8;
+ t['Osmall'] = 0xF76F;
+ t['Ostrokeacute'] = 0x01FE;
+ t['Otcyrillic'] = 0x047E;
+ t['Otilde'] = 0x00D5;
+ t['Otildeacute'] = 0x1E4C;
+ t['Otildedieresis'] = 0x1E4E;
+ t['Otildesmall'] = 0xF7F5;
+ t['P'] = 0x0050;
+ t['Pacute'] = 0x1E54;
+ t['Pcircle'] = 0x24C5;
+ t['Pdotaccent'] = 0x1E56;
+ t['Pecyrillic'] = 0x041F;
+ t['Peharmenian'] = 0x054A;
+ t['Pemiddlehookcyrillic'] = 0x04A6;
+ t['Phi'] = 0x03A6;
+ t['Phook'] = 0x01A4;
+ t['Pi'] = 0x03A0;
+ t['Piwrarmenian'] = 0x0553;
+ t['Pmonospace'] = 0xFF30;
+ t['Psi'] = 0x03A8;
+ t['Psicyrillic'] = 0x0470;
+ t['Psmall'] = 0xF770;
+ t['Q'] = 0x0051;
+ t['Qcircle'] = 0x24C6;
+ t['Qmonospace'] = 0xFF31;
+ t['Qsmall'] = 0xF771;
+ t['R'] = 0x0052;
+ t['Raarmenian'] = 0x054C;
+ t['Racute'] = 0x0154;
+ t['Rcaron'] = 0x0158;
+ t['Rcedilla'] = 0x0156;
+ t['Rcircle'] = 0x24C7;
+ t['Rcommaaccent'] = 0x0156;
+ t['Rdblgrave'] = 0x0210;
+ t['Rdotaccent'] = 0x1E58;
+ t['Rdotbelow'] = 0x1E5A;
+ t['Rdotbelowmacron'] = 0x1E5C;
+ t['Reharmenian'] = 0x0550;
+ t['Rfraktur'] = 0x211C;
+ t['Rho'] = 0x03A1;
+ t['Ringsmall'] = 0xF6FC;
+ t['Rinvertedbreve'] = 0x0212;
+ t['Rlinebelow'] = 0x1E5E;
+ t['Rmonospace'] = 0xFF32;
+ t['Rsmall'] = 0xF772;
+ t['Rsmallinverted'] = 0x0281;
+ t['Rsmallinvertedsuperior'] = 0x02B6;
+ t['S'] = 0x0053;
+ t['SF010000'] = 0x250C;
+ t['SF020000'] = 0x2514;
+ t['SF030000'] = 0x2510;
+ t['SF040000'] = 0x2518;
+ t['SF050000'] = 0x253C;
+ t['SF060000'] = 0x252C;
+ t['SF070000'] = 0x2534;
+ t['SF080000'] = 0x251C;
+ t['SF090000'] = 0x2524;
+ t['SF100000'] = 0x2500;
+ t['SF110000'] = 0x2502;
+ t['SF190000'] = 0x2561;
+ t['SF200000'] = 0x2562;
+ t['SF210000'] = 0x2556;
+ t['SF220000'] = 0x2555;
+ t['SF230000'] = 0x2563;
+ t['SF240000'] = 0x2551;
+ t['SF250000'] = 0x2557;
+ t['SF260000'] = 0x255D;
+ t['SF270000'] = 0x255C;
+ t['SF280000'] = 0x255B;
+ t['SF360000'] = 0x255E;
+ t['SF370000'] = 0x255F;
+ t['SF380000'] = 0x255A;
+ t['SF390000'] = 0x2554;
+ t['SF400000'] = 0x2569;
+ t['SF410000'] = 0x2566;
+ t['SF420000'] = 0x2560;
+ t['SF430000'] = 0x2550;
+ t['SF440000'] = 0x256C;
+ t['SF450000'] = 0x2567;
+ t['SF460000'] = 0x2568;
+ t['SF470000'] = 0x2564;
+ t['SF480000'] = 0x2565;
+ t['SF490000'] = 0x2559;
+ t['SF500000'] = 0x2558;
+ t['SF510000'] = 0x2552;
+ t['SF520000'] = 0x2553;
+ t['SF530000'] = 0x256B;
+ t['SF540000'] = 0x256A;
+ t['Sacute'] = 0x015A;
+ t['Sacutedotaccent'] = 0x1E64;
+ t['Sampigreek'] = 0x03E0;
+ t['Scaron'] = 0x0160;
+ t['Scarondotaccent'] = 0x1E66;
+ t['Scaronsmall'] = 0xF6FD;
+ t['Scedilla'] = 0x015E;
+ t['Schwa'] = 0x018F;
+ t['Schwacyrillic'] = 0x04D8;
+ t['Schwadieresiscyrillic'] = 0x04DA;
+ t['Scircle'] = 0x24C8;
+ t['Scircumflex'] = 0x015C;
+ t['Scommaaccent'] = 0x0218;
+ t['Sdotaccent'] = 0x1E60;
+ t['Sdotbelow'] = 0x1E62;
+ t['Sdotbelowdotaccent'] = 0x1E68;
+ t['Seharmenian'] = 0x054D;
+ t['Sevenroman'] = 0x2166;
+ t['Shaarmenian'] = 0x0547;
+ t['Shacyrillic'] = 0x0428;
+ t['Shchacyrillic'] = 0x0429;
+ t['Sheicoptic'] = 0x03E2;
+ t['Shhacyrillic'] = 0x04BA;
+ t['Shimacoptic'] = 0x03EC;
+ t['Sigma'] = 0x03A3;
+ t['Sixroman'] = 0x2165;
+ t['Smonospace'] = 0xFF33;
+ t['Softsigncyrillic'] = 0x042C;
+ t['Ssmall'] = 0xF773;
+ t['Stigmagreek'] = 0x03DA;
+ t['T'] = 0x0054;
+ t['Tau'] = 0x03A4;
+ t['Tbar'] = 0x0166;
+ t['Tcaron'] = 0x0164;
+ t['Tcedilla'] = 0x0162;
+ t['Tcircle'] = 0x24C9;
+ t['Tcircumflexbelow'] = 0x1E70;
+ t['Tcommaaccent'] = 0x0162;
+ t['Tdotaccent'] = 0x1E6A;
+ t['Tdotbelow'] = 0x1E6C;
+ t['Tecyrillic'] = 0x0422;
+ t['Tedescendercyrillic'] = 0x04AC;
+ t['Tenroman'] = 0x2169;
+ t['Tetsecyrillic'] = 0x04B4;
+ t['Theta'] = 0x0398;
+ t['Thook'] = 0x01AC;
+ t['Thorn'] = 0x00DE;
+ t['Thornsmall'] = 0xF7FE;
+ t['Threeroman'] = 0x2162;
+ t['Tildesmall'] = 0xF6FE;
+ t['Tiwnarmenian'] = 0x054F;
+ t['Tlinebelow'] = 0x1E6E;
+ t['Tmonospace'] = 0xFF34;
+ t['Toarmenian'] = 0x0539;
+ t['Tonefive'] = 0x01BC;
+ t['Tonesix'] = 0x0184;
+ t['Tonetwo'] = 0x01A7;
+ t['Tretroflexhook'] = 0x01AE;
+ t['Tsecyrillic'] = 0x0426;
+ t['Tshecyrillic'] = 0x040B;
+ t['Tsmall'] = 0xF774;
+ t['Twelveroman'] = 0x216B;
+ t['Tworoman'] = 0x2161;
+ t['U'] = 0x0055;
+ t['Uacute'] = 0x00DA;
+ t['Uacutesmall'] = 0xF7FA;
+ t['Ubreve'] = 0x016C;
+ t['Ucaron'] = 0x01D3;
+ t['Ucircle'] = 0x24CA;
+ t['Ucircumflex'] = 0x00DB;
+ t['Ucircumflexbelow'] = 0x1E76;
+ t['Ucircumflexsmall'] = 0xF7FB;
+ t['Ucyrillic'] = 0x0423;
+ t['Udblacute'] = 0x0170;
+ t['Udblgrave'] = 0x0214;
+ t['Udieresis'] = 0x00DC;
+ t['Udieresisacute'] = 0x01D7;
+ t['Udieresisbelow'] = 0x1E72;
+ t['Udieresiscaron'] = 0x01D9;
+ t['Udieresiscyrillic'] = 0x04F0;
+ t['Udieresisgrave'] = 0x01DB;
+ t['Udieresismacron'] = 0x01D5;
+ t['Udieresissmall'] = 0xF7FC;
+ t['Udotbelow'] = 0x1EE4;
+ t['Ugrave'] = 0x00D9;
+ t['Ugravesmall'] = 0xF7F9;
+ t['Uhookabove'] = 0x1EE6;
+ t['Uhorn'] = 0x01AF;
+ t['Uhornacute'] = 0x1EE8;
+ t['Uhorndotbelow'] = 0x1EF0;
+ t['Uhorngrave'] = 0x1EEA;
+ t['Uhornhookabove'] = 0x1EEC;
+ t['Uhorntilde'] = 0x1EEE;
+ t['Uhungarumlaut'] = 0x0170;
+ t['Uhungarumlautcyrillic'] = 0x04F2;
+ t['Uinvertedbreve'] = 0x0216;
+ t['Ukcyrillic'] = 0x0478;
+ t['Umacron'] = 0x016A;
+ t['Umacroncyrillic'] = 0x04EE;
+ t['Umacrondieresis'] = 0x1E7A;
+ t['Umonospace'] = 0xFF35;
+ t['Uogonek'] = 0x0172;
+ t['Upsilon'] = 0x03A5;
+ t['Upsilon1'] = 0x03D2;
+ t['Upsilonacutehooksymbolgreek'] = 0x03D3;
+ t['Upsilonafrican'] = 0x01B1;
+ t['Upsilondieresis'] = 0x03AB;
+ t['Upsilondieresishooksymbolgreek'] = 0x03D4;
+ t['Upsilonhooksymbol'] = 0x03D2;
+ t['Upsilontonos'] = 0x038E;
+ t['Uring'] = 0x016E;
+ t['Ushortcyrillic'] = 0x040E;
+ t['Usmall'] = 0xF775;
+ t['Ustraightcyrillic'] = 0x04AE;
+ t['Ustraightstrokecyrillic'] = 0x04B0;
+ t['Utilde'] = 0x0168;
+ t['Utildeacute'] = 0x1E78;
+ t['Utildebelow'] = 0x1E74;
+ t['V'] = 0x0056;
+ t['Vcircle'] = 0x24CB;
+ t['Vdotbelow'] = 0x1E7E;
+ t['Vecyrillic'] = 0x0412;
+ t['Vewarmenian'] = 0x054E;
+ t['Vhook'] = 0x01B2;
+ t['Vmonospace'] = 0xFF36;
+ t['Voarmenian'] = 0x0548;
+ t['Vsmall'] = 0xF776;
+ t['Vtilde'] = 0x1E7C;
+ t['W'] = 0x0057;
+ t['Wacute'] = 0x1E82;
+ t['Wcircle'] = 0x24CC;
+ t['Wcircumflex'] = 0x0174;
+ t['Wdieresis'] = 0x1E84;
+ t['Wdotaccent'] = 0x1E86;
+ t['Wdotbelow'] = 0x1E88;
+ t['Wgrave'] = 0x1E80;
+ t['Wmonospace'] = 0xFF37;
+ t['Wsmall'] = 0xF777;
+ t['X'] = 0x0058;
+ t['Xcircle'] = 0x24CD;
+ t['Xdieresis'] = 0x1E8C;
+ t['Xdotaccent'] = 0x1E8A;
+ t['Xeharmenian'] = 0x053D;
+ t['Xi'] = 0x039E;
+ t['Xmonospace'] = 0xFF38;
+ t['Xsmall'] = 0xF778;
+ t['Y'] = 0x0059;
+ t['Yacute'] = 0x00DD;
+ t['Yacutesmall'] = 0xF7FD;
+ t['Yatcyrillic'] = 0x0462;
+ t['Ycircle'] = 0x24CE;
+ t['Ycircumflex'] = 0x0176;
+ t['Ydieresis'] = 0x0178;
+ t['Ydieresissmall'] = 0xF7FF;
+ t['Ydotaccent'] = 0x1E8E;
+ t['Ydotbelow'] = 0x1EF4;
+ t['Yericyrillic'] = 0x042B;
+ t['Yerudieresiscyrillic'] = 0x04F8;
+ t['Ygrave'] = 0x1EF2;
+ t['Yhook'] = 0x01B3;
+ t['Yhookabove'] = 0x1EF6;
+ t['Yiarmenian'] = 0x0545;
+ t['Yicyrillic'] = 0x0407;
+ t['Yiwnarmenian'] = 0x0552;
+ t['Ymonospace'] = 0xFF39;
+ t['Ysmall'] = 0xF779;
+ t['Ytilde'] = 0x1EF8;
+ t['Yusbigcyrillic'] = 0x046A;
+ t['Yusbigiotifiedcyrillic'] = 0x046C;
+ t['Yuslittlecyrillic'] = 0x0466;
+ t['Yuslittleiotifiedcyrillic'] = 0x0468;
+ t['Z'] = 0x005A;
+ t['Zaarmenian'] = 0x0536;
+ t['Zacute'] = 0x0179;
+ t['Zcaron'] = 0x017D;
+ t['Zcaronsmall'] = 0xF6FF;
+ t['Zcircle'] = 0x24CF;
+ t['Zcircumflex'] = 0x1E90;
+ t['Zdot'] = 0x017B;
+ t['Zdotaccent'] = 0x017B;
+ t['Zdotbelow'] = 0x1E92;
+ t['Zecyrillic'] = 0x0417;
+ t['Zedescendercyrillic'] = 0x0498;
+ t['Zedieresiscyrillic'] = 0x04DE;
+ t['Zeta'] = 0x0396;
+ t['Zhearmenian'] = 0x053A;
+ t['Zhebrevecyrillic'] = 0x04C1;
+ t['Zhecyrillic'] = 0x0416;
+ t['Zhedescendercyrillic'] = 0x0496;
+ t['Zhedieresiscyrillic'] = 0x04DC;
+ t['Zlinebelow'] = 0x1E94;
+ t['Zmonospace'] = 0xFF3A;
+ t['Zsmall'] = 0xF77A;
+ t['Zstroke'] = 0x01B5;
+ t['a'] = 0x0061;
+ t['aabengali'] = 0x0986;
+ t['aacute'] = 0x00E1;
+ t['aadeva'] = 0x0906;
+ t['aagujarati'] = 0x0A86;
+ t['aagurmukhi'] = 0x0A06;
+ t['aamatragurmukhi'] = 0x0A3E;
+ t['aarusquare'] = 0x3303;
+ t['aavowelsignbengali'] = 0x09BE;
+ t['aavowelsigndeva'] = 0x093E;
+ t['aavowelsigngujarati'] = 0x0ABE;
+ t['abbreviationmarkarmenian'] = 0x055F;
+ t['abbreviationsigndeva'] = 0x0970;
+ t['abengali'] = 0x0985;
+ t['abopomofo'] = 0x311A;
+ t['abreve'] = 0x0103;
+ t['abreveacute'] = 0x1EAF;
+ t['abrevecyrillic'] = 0x04D1;
+ t['abrevedotbelow'] = 0x1EB7;
+ t['abrevegrave'] = 0x1EB1;
+ t['abrevehookabove'] = 0x1EB3;
+ t['abrevetilde'] = 0x1EB5;
+ t['acaron'] = 0x01CE;
+ t['acircle'] = 0x24D0;
+ t['acircumflex'] = 0x00E2;
+ t['acircumflexacute'] = 0x1EA5;
+ t['acircumflexdotbelow'] = 0x1EAD;
+ t['acircumflexgrave'] = 0x1EA7;
+ t['acircumflexhookabove'] = 0x1EA9;
+ t['acircumflextilde'] = 0x1EAB;
+ t['acute'] = 0x00B4;
+ t['acutebelowcmb'] = 0x0317;
+ t['acutecmb'] = 0x0301;
+ t['acutecomb'] = 0x0301;
+ t['acutedeva'] = 0x0954;
+ t['acutelowmod'] = 0x02CF;
+ t['acutetonecmb'] = 0x0341;
+ t['acyrillic'] = 0x0430;
+ t['adblgrave'] = 0x0201;
+ t['addakgurmukhi'] = 0x0A71;
+ t['adeva'] = 0x0905;
+ t['adieresis'] = 0x00E4;
+ t['adieresiscyrillic'] = 0x04D3;
+ t['adieresismacron'] = 0x01DF;
+ t['adotbelow'] = 0x1EA1;
+ t['adotmacron'] = 0x01E1;
+ t['ae'] = 0x00E6;
+ t['aeacute'] = 0x01FD;
+ t['aekorean'] = 0x3150;
+ t['aemacron'] = 0x01E3;
+ t['afii00208'] = 0x2015;
+ t['afii08941'] = 0x20A4;
+ t['afii10017'] = 0x0410;
+ t['afii10018'] = 0x0411;
+ t['afii10019'] = 0x0412;
+ t['afii10020'] = 0x0413;
+ t['afii10021'] = 0x0414;
+ t['afii10022'] = 0x0415;
+ t['afii10023'] = 0x0401;
+ t['afii10024'] = 0x0416;
+ t['afii10025'] = 0x0417;
+ t['afii10026'] = 0x0418;
+ t['afii10027'] = 0x0419;
+ t['afii10028'] = 0x041A;
+ t['afii10029'] = 0x041B;
+ t['afii10030'] = 0x041C;
+ t['afii10031'] = 0x041D;
+ t['afii10032'] = 0x041E;
+ t['afii10033'] = 0x041F;
+ t['afii10034'] = 0x0420;
+ t['afii10035'] = 0x0421;
+ t['afii10036'] = 0x0422;
+ t['afii10037'] = 0x0423;
+ t['afii10038'] = 0x0424;
+ t['afii10039'] = 0x0425;
+ t['afii10040'] = 0x0426;
+ t['afii10041'] = 0x0427;
+ t['afii10042'] = 0x0428;
+ t['afii10043'] = 0x0429;
+ t['afii10044'] = 0x042A;
+ t['afii10045'] = 0x042B;
+ t['afii10046'] = 0x042C;
+ t['afii10047'] = 0x042D;
+ t['afii10048'] = 0x042E;
+ t['afii10049'] = 0x042F;
+ t['afii10050'] = 0x0490;
+ t['afii10051'] = 0x0402;
+ t['afii10052'] = 0x0403;
+ t['afii10053'] = 0x0404;
+ t['afii10054'] = 0x0405;
+ t['afii10055'] = 0x0406;
+ t['afii10056'] = 0x0407;
+ t['afii10057'] = 0x0408;
+ t['afii10058'] = 0x0409;
+ t['afii10059'] = 0x040A;
+ t['afii10060'] = 0x040B;
+ t['afii10061'] = 0x040C;
+ t['afii10062'] = 0x040E;
+ t['afii10063'] = 0xF6C4;
+ t['afii10064'] = 0xF6C5;
+ t['afii10065'] = 0x0430;
+ t['afii10066'] = 0x0431;
+ t['afii10067'] = 0x0432;
+ t['afii10068'] = 0x0433;
+ t['afii10069'] = 0x0434;
+ t['afii10070'] = 0x0435;
+ t['afii10071'] = 0x0451;
+ t['afii10072'] = 0x0436;
+ t['afii10073'] = 0x0437;
+ t['afii10074'] = 0x0438;
+ t['afii10075'] = 0x0439;
+ t['afii10076'] = 0x043A;
+ t['afii10077'] = 0x043B;
+ t['afii10078'] = 0x043C;
+ t['afii10079'] = 0x043D;
+ t['afii10080'] = 0x043E;
+ t['afii10081'] = 0x043F;
+ t['afii10082'] = 0x0440;
+ t['afii10083'] = 0x0441;
+ t['afii10084'] = 0x0442;
+ t['afii10085'] = 0x0443;
+ t['afii10086'] = 0x0444;
+ t['afii10087'] = 0x0445;
+ t['afii10088'] = 0x0446;
+ t['afii10089'] = 0x0447;
+ t['afii10090'] = 0x0448;
+ t['afii10091'] = 0x0449;
+ t['afii10092'] = 0x044A;
+ t['afii10093'] = 0x044B;
+ t['afii10094'] = 0x044C;
+ t['afii10095'] = 0x044D;
+ t['afii10096'] = 0x044E;
+ t['afii10097'] = 0x044F;
+ t['afii10098'] = 0x0491;
+ t['afii10099'] = 0x0452;
+ t['afii10100'] = 0x0453;
+ t['afii10101'] = 0x0454;
+ t['afii10102'] = 0x0455;
+ t['afii10103'] = 0x0456;
+ t['afii10104'] = 0x0457;
+ t['afii10105'] = 0x0458;
+ t['afii10106'] = 0x0459;
+ t['afii10107'] = 0x045A;
+ t['afii10108'] = 0x045B;
+ t['afii10109'] = 0x045C;
+ t['afii10110'] = 0x045E;
+ t['afii10145'] = 0x040F;
+ t['afii10146'] = 0x0462;
+ t['afii10147'] = 0x0472;
+ t['afii10148'] = 0x0474;
+ t['afii10192'] = 0xF6C6;
+ t['afii10193'] = 0x045F;
+ t['afii10194'] = 0x0463;
+ t['afii10195'] = 0x0473;
+ t['afii10196'] = 0x0475;
+ t['afii10831'] = 0xF6C7;
+ t['afii10832'] = 0xF6C8;
+ t['afii10846'] = 0x04D9;
+ t['afii299'] = 0x200E;
+ t['afii300'] = 0x200F;
+ t['afii301'] = 0x200D;
+ t['afii57381'] = 0x066A;
+ t['afii57388'] = 0x060C;
+ t['afii57392'] = 0x0660;
+ t['afii57393'] = 0x0661;
+ t['afii57394'] = 0x0662;
+ t['afii57395'] = 0x0663;
+ t['afii57396'] = 0x0664;
+ t['afii57397'] = 0x0665;
+ t['afii57398'] = 0x0666;
+ t['afii57399'] = 0x0667;
+ t['afii57400'] = 0x0668;
+ t['afii57401'] = 0x0669;
+ t['afii57403'] = 0x061B;
+ t['afii57407'] = 0x061F;
+ t['afii57409'] = 0x0621;
+ t['afii57410'] = 0x0622;
+ t['afii57411'] = 0x0623;
+ t['afii57412'] = 0x0624;
+ t['afii57413'] = 0x0625;
+ t['afii57414'] = 0x0626;
+ t['afii57415'] = 0x0627;
+ t['afii57416'] = 0x0628;
+ t['afii57417'] = 0x0629;
+ t['afii57418'] = 0x062A;
+ t['afii57419'] = 0x062B;
+ t['afii57420'] = 0x062C;
+ t['afii57421'] = 0x062D;
+ t['afii57422'] = 0x062E;
+ t['afii57423'] = 0x062F;
+ t['afii57424'] = 0x0630;
+ t['afii57425'] = 0x0631;
+ t['afii57426'] = 0x0632;
+ t['afii57427'] = 0x0633;
+ t['afii57428'] = 0x0634;
+ t['afii57429'] = 0x0635;
+ t['afii57430'] = 0x0636;
+ t['afii57431'] = 0x0637;
+ t['afii57432'] = 0x0638;
+ t['afii57433'] = 0x0639;
+ t['afii57434'] = 0x063A;
+ t['afii57440'] = 0x0640;
+ t['afii57441'] = 0x0641;
+ t['afii57442'] = 0x0642;
+ t['afii57443'] = 0x0643;
+ t['afii57444'] = 0x0644;
+ t['afii57445'] = 0x0645;
+ t['afii57446'] = 0x0646;
+ t['afii57448'] = 0x0648;
+ t['afii57449'] = 0x0649;
+ t['afii57450'] = 0x064A;
+ t['afii57451'] = 0x064B;
+ t['afii57452'] = 0x064C;
+ t['afii57453'] = 0x064D;
+ t['afii57454'] = 0x064E;
+ t['afii57455'] = 0x064F;
+ t['afii57456'] = 0x0650;
+ t['afii57457'] = 0x0651;
+ t['afii57458'] = 0x0652;
+ t['afii57470'] = 0x0647;
+ t['afii57505'] = 0x06A4;
+ t['afii57506'] = 0x067E;
+ t['afii57507'] = 0x0686;
+ t['afii57508'] = 0x0698;
+ t['afii57509'] = 0x06AF;
+ t['afii57511'] = 0x0679;
+ t['afii57512'] = 0x0688;
+ t['afii57513'] = 0x0691;
+ t['afii57514'] = 0x06BA;
+ t['afii57519'] = 0x06D2;
+ t['afii57534'] = 0x06D5;
+ t['afii57636'] = 0x20AA;
+ t['afii57645'] = 0x05BE;
+ t['afii57658'] = 0x05C3;
+ t['afii57664'] = 0x05D0;
+ t['afii57665'] = 0x05D1;
+ t['afii57666'] = 0x05D2;
+ t['afii57667'] = 0x05D3;
+ t['afii57668'] = 0x05D4;
+ t['afii57669'] = 0x05D5;
+ t['afii57670'] = 0x05D6;
+ t['afii57671'] = 0x05D7;
+ t['afii57672'] = 0x05D8;
+ t['afii57673'] = 0x05D9;
+ t['afii57674'] = 0x05DA;
+ t['afii57675'] = 0x05DB;
+ t['afii57676'] = 0x05DC;
+ t['afii57677'] = 0x05DD;
+ t['afii57678'] = 0x05DE;
+ t['afii57679'] = 0x05DF;
+ t['afii57680'] = 0x05E0;
+ t['afii57681'] = 0x05E1;
+ t['afii57682'] = 0x05E2;
+ t['afii57683'] = 0x05E3;
+ t['afii57684'] = 0x05E4;
+ t['afii57685'] = 0x05E5;
+ t['afii57686'] = 0x05E6;
+ t['afii57687'] = 0x05E7;
+ t['afii57688'] = 0x05E8;
+ t['afii57689'] = 0x05E9;
+ t['afii57690'] = 0x05EA;
+ t['afii57694'] = 0xFB2A;
+ t['afii57695'] = 0xFB2B;
+ t['afii57700'] = 0xFB4B;
+ t['afii57705'] = 0xFB1F;
+ t['afii57716'] = 0x05F0;
+ t['afii57717'] = 0x05F1;
+ t['afii57718'] = 0x05F2;
+ t['afii57723'] = 0xFB35;
+ t['afii57793'] = 0x05B4;
+ t['afii57794'] = 0x05B5;
+ t['afii57795'] = 0x05B6;
+ t['afii57796'] = 0x05BB;
+ t['afii57797'] = 0x05B8;
+ t['afii57798'] = 0x05B7;
+ t['afii57799'] = 0x05B0;
+ t['afii57800'] = 0x05B2;
+ t['afii57801'] = 0x05B1;
+ t['afii57802'] = 0x05B3;
+ t['afii57803'] = 0x05C2;
+ t['afii57804'] = 0x05C1;
+ t['afii57806'] = 0x05B9;
+ t['afii57807'] = 0x05BC;
+ t['afii57839'] = 0x05BD;
+ t['afii57841'] = 0x05BF;
+ t['afii57842'] = 0x05C0;
+ t['afii57929'] = 0x02BC;
+ t['afii61248'] = 0x2105;
+ t['afii61289'] = 0x2113;
+ t['afii61352'] = 0x2116;
+ t['afii61573'] = 0x202C;
+ t['afii61574'] = 0x202D;
+ t['afii61575'] = 0x202E;
+ t['afii61664'] = 0x200C;
+ t['afii63167'] = 0x066D;
+ t['afii64937'] = 0x02BD;
+ t['agrave'] = 0x00E0;
+ t['agujarati'] = 0x0A85;
+ t['agurmukhi'] = 0x0A05;
+ t['ahiragana'] = 0x3042;
+ t['ahookabove'] = 0x1EA3;
+ t['aibengali'] = 0x0990;
+ t['aibopomofo'] = 0x311E;
+ t['aideva'] = 0x0910;
+ t['aiecyrillic'] = 0x04D5;
+ t['aigujarati'] = 0x0A90;
+ t['aigurmukhi'] = 0x0A10;
+ t['aimatragurmukhi'] = 0x0A48;
+ t['ainarabic'] = 0x0639;
+ t['ainfinalarabic'] = 0xFECA;
+ t['aininitialarabic'] = 0xFECB;
+ t['ainmedialarabic'] = 0xFECC;
+ t['ainvertedbreve'] = 0x0203;
+ t['aivowelsignbengali'] = 0x09C8;
+ t['aivowelsigndeva'] = 0x0948;
+ t['aivowelsigngujarati'] = 0x0AC8;
+ t['akatakana'] = 0x30A2;
+ t['akatakanahalfwidth'] = 0xFF71;
+ t['akorean'] = 0x314F;
+ t['alef'] = 0x05D0;
+ t['alefarabic'] = 0x0627;
+ t['alefdageshhebrew'] = 0xFB30;
+ t['aleffinalarabic'] = 0xFE8E;
+ t['alefhamzaabovearabic'] = 0x0623;
+ t['alefhamzaabovefinalarabic'] = 0xFE84;
+ t['alefhamzabelowarabic'] = 0x0625;
+ t['alefhamzabelowfinalarabic'] = 0xFE88;
+ t['alefhebrew'] = 0x05D0;
+ t['aleflamedhebrew'] = 0xFB4F;
+ t['alefmaddaabovearabic'] = 0x0622;
+ t['alefmaddaabovefinalarabic'] = 0xFE82;
+ t['alefmaksuraarabic'] = 0x0649;
+ t['alefmaksurafinalarabic'] = 0xFEF0;
+ t['alefmaksurainitialarabic'] = 0xFEF3;
+ t['alefmaksuramedialarabic'] = 0xFEF4;
+ t['alefpatahhebrew'] = 0xFB2E;
+ t['alefqamatshebrew'] = 0xFB2F;
+ t['aleph'] = 0x2135;
+ t['allequal'] = 0x224C;
+ t['alpha'] = 0x03B1;
+ t['alphatonos'] = 0x03AC;
+ t['amacron'] = 0x0101;
+ t['amonospace'] = 0xFF41;
+ t['ampersand'] = 0x0026;
+ t['ampersandmonospace'] = 0xFF06;
+ t['ampersandsmall'] = 0xF726;
+ t['amsquare'] = 0x33C2;
+ t['anbopomofo'] = 0x3122;
+ t['angbopomofo'] = 0x3124;
+ t['angbracketleft'] = 0x3008;
+ // Glyph is missing from Adobe's original list.
+ t['angbracketright'] = 0x3009;
+ // Glyph is missing from Adobe's original list.
+ t['angkhankhuthai'] = 0x0E5A;
+ t['angle'] = 0x2220;
+ t['anglebracketleft'] = 0x3008;
+ t['anglebracketleftvertical'] = 0xFE3F;
+ t['anglebracketright'] = 0x3009;
+ t['anglebracketrightvertical'] = 0xFE40;
+ t['angleleft'] = 0x2329;
+ t['angleright'] = 0x232A;
+ t['angstrom'] = 0x212B;
+ t['anoteleia'] = 0x0387;
+ t['anudattadeva'] = 0x0952;
+ t['anusvarabengali'] = 0x0982;
+ t['anusvaradeva'] = 0x0902;
+ t['anusvaragujarati'] = 0x0A82;
+ t['aogonek'] = 0x0105;
+ t['apaatosquare'] = 0x3300;
+ t['aparen'] = 0x249C;
+ t['apostrophearmenian'] = 0x055A;
+ t['apostrophemod'] = 0x02BC;
+ t['apple'] = 0xF8FF;
+ t['approaches'] = 0x2250;
+ t['approxequal'] = 0x2248;
+ t['approxequalorimage'] = 0x2252;
+ t['approximatelyequal'] = 0x2245;
+ t['araeaekorean'] = 0x318E;
+ t['araeakorean'] = 0x318D;
+ t['arc'] = 0x2312;
+ t['arighthalfring'] = 0x1E9A;
+ t['aring'] = 0x00E5;
+ t['aringacute'] = 0x01FB;
+ t['aringbelow'] = 0x1E01;
+ t['arrowboth'] = 0x2194;
+ t['arrowdashdown'] = 0x21E3;
+ t['arrowdashleft'] = 0x21E0;
+ t['arrowdashright'] = 0x21E2;
+ t['arrowdashup'] = 0x21E1;
+ t['arrowdblboth'] = 0x21D4;
+ t['arrowdbldown'] = 0x21D3;
+ t['arrowdblleft'] = 0x21D0;
+ t['arrowdblright'] = 0x21D2;
+ t['arrowdblup'] = 0x21D1;
+ t['arrowdown'] = 0x2193;
+ t['arrowdownleft'] = 0x2199;
+ t['arrowdownright'] = 0x2198;
+ t['arrowdownwhite'] = 0x21E9;
+ t['arrowheaddownmod'] = 0x02C5;
+ t['arrowheadleftmod'] = 0x02C2;
+ t['arrowheadrightmod'] = 0x02C3;
+ t['arrowheadupmod'] = 0x02C4;
+ t['arrowhorizex'] = 0xF8E7;
+ t['arrowleft'] = 0x2190;
+ t['arrowleftdbl'] = 0x21D0;
+ t['arrowleftdblstroke'] = 0x21CD;
+ t['arrowleftoverright'] = 0x21C6;
+ t['arrowleftwhite'] = 0x21E6;
+ t['arrowright'] = 0x2192;
+ t['arrowrightdblstroke'] = 0x21CF;
+ t['arrowrightheavy'] = 0x279E;
+ t['arrowrightoverleft'] = 0x21C4;
+ t['arrowrightwhite'] = 0x21E8;
+ t['arrowtableft'] = 0x21E4;
+ t['arrowtabright'] = 0x21E5;
+ t['arrowup'] = 0x2191;
+ t['arrowupdn'] = 0x2195;
+ t['arrowupdnbse'] = 0x21A8;
+ t['arrowupdownbase'] = 0x21A8;
+ t['arrowupleft'] = 0x2196;
+ t['arrowupleftofdown'] = 0x21C5;
+ t['arrowupright'] = 0x2197;
+ t['arrowupwhite'] = 0x21E7;
+ t['arrowvertex'] = 0xF8E6;
+ t['asciicircum'] = 0x005E;
+ t['asciicircummonospace'] = 0xFF3E;
+ t['asciitilde'] = 0x007E;
+ t['asciitildemonospace'] = 0xFF5E;
+ t['ascript'] = 0x0251;
+ t['ascriptturned'] = 0x0252;
+ t['asmallhiragana'] = 0x3041;
+ t['asmallkatakana'] = 0x30A1;
+ t['asmallkatakanahalfwidth'] = 0xFF67;
+ t['asterisk'] = 0x002A;
+ t['asteriskaltonearabic'] = 0x066D;
+ t['asteriskarabic'] = 0x066D;
+ t['asteriskmath'] = 0x2217;
+ t['asteriskmonospace'] = 0xFF0A;
+ t['asterisksmall'] = 0xFE61;
+ t['asterism'] = 0x2042;
+ t['asuperior'] = 0xF6E9;
+ t['asymptoticallyequal'] = 0x2243;
+ t['at'] = 0x0040;
+ t['atilde'] = 0x00E3;
+ t['atmonospace'] = 0xFF20;
+ t['atsmall'] = 0xFE6B;
+ t['aturned'] = 0x0250;
+ t['aubengali'] = 0x0994;
+ t['aubopomofo'] = 0x3120;
+ t['audeva'] = 0x0914;
+ t['augujarati'] = 0x0A94;
+ t['augurmukhi'] = 0x0A14;
+ t['aulengthmarkbengali'] = 0x09D7;
+ t['aumatragurmukhi'] = 0x0A4C;
+ t['auvowelsignbengali'] = 0x09CC;
+ t['auvowelsigndeva'] = 0x094C;
+ t['auvowelsigngujarati'] = 0x0ACC;
+ t['avagrahadeva'] = 0x093D;
+ t['aybarmenian'] = 0x0561;
+ t['ayin'] = 0x05E2;
+ t['ayinaltonehebrew'] = 0xFB20;
+ t['ayinhebrew'] = 0x05E2;
+ t['b'] = 0x0062;
+ t['babengali'] = 0x09AC;
+ t['backslash'] = 0x005C;
+ t['backslashmonospace'] = 0xFF3C;
+ t['badeva'] = 0x092C;
+ t['bagujarati'] = 0x0AAC;
+ t['bagurmukhi'] = 0x0A2C;
+ t['bahiragana'] = 0x3070;
+ t['bahtthai'] = 0x0E3F;
+ t['bakatakana'] = 0x30D0;
+ t['bar'] = 0x007C;
+ t['barmonospace'] = 0xFF5C;
+ t['bbopomofo'] = 0x3105;
+ t['bcircle'] = 0x24D1;
+ t['bdotaccent'] = 0x1E03;
+ t['bdotbelow'] = 0x1E05;
+ t['beamedsixteenthnotes'] = 0x266C;
+ t['because'] = 0x2235;
+ t['becyrillic'] = 0x0431;
+ t['beharabic'] = 0x0628;
+ t['behfinalarabic'] = 0xFE90;
+ t['behinitialarabic'] = 0xFE91;
+ t['behiragana'] = 0x3079;
+ t['behmedialarabic'] = 0xFE92;
+ t['behmeeminitialarabic'] = 0xFC9F;
+ t['behmeemisolatedarabic'] = 0xFC08;
+ t['behnoonfinalarabic'] = 0xFC6D;
+ t['bekatakana'] = 0x30D9;
+ t['benarmenian'] = 0x0562;
+ t['bet'] = 0x05D1;
+ t['beta'] = 0x03B2;
+ t['betasymbolgreek'] = 0x03D0;
+ t['betdagesh'] = 0xFB31;
+ t['betdageshhebrew'] = 0xFB31;
+ t['bethebrew'] = 0x05D1;
+ t['betrafehebrew'] = 0xFB4C;
+ t['bhabengali'] = 0x09AD;
+ t['bhadeva'] = 0x092D;
+ t['bhagujarati'] = 0x0AAD;
+ t['bhagurmukhi'] = 0x0A2D;
+ t['bhook'] = 0x0253;
+ t['bihiragana'] = 0x3073;
+ t['bikatakana'] = 0x30D3;
+ t['bilabialclick'] = 0x0298;
+ t['bindigurmukhi'] = 0x0A02;
+ t['birusquare'] = 0x3331;
+ t['blackcircle'] = 0x25CF;
+ t['blackdiamond'] = 0x25C6;
+ t['blackdownpointingtriangle'] = 0x25BC;
+ t['blackleftpointingpointer'] = 0x25C4;
+ t['blackleftpointingtriangle'] = 0x25C0;
+ t['blacklenticularbracketleft'] = 0x3010;
+ t['blacklenticularbracketleftvertical'] = 0xFE3B;
+ t['blacklenticularbracketright'] = 0x3011;
+ t['blacklenticularbracketrightvertical'] = 0xFE3C;
+ t['blacklowerlefttriangle'] = 0x25E3;
+ t['blacklowerrighttriangle'] = 0x25E2;
+ t['blackrectangle'] = 0x25AC;
+ t['blackrightpointingpointer'] = 0x25BA;
+ t['blackrightpointingtriangle'] = 0x25B6;
+ t['blacksmallsquare'] = 0x25AA;
+ t['blacksmilingface'] = 0x263B;
+ t['blacksquare'] = 0x25A0;
+ t['blackstar'] = 0x2605;
+ t['blackupperlefttriangle'] = 0x25E4;
+ t['blackupperrighttriangle'] = 0x25E5;
+ t['blackuppointingsmalltriangle'] = 0x25B4;
+ t['blackuppointingtriangle'] = 0x25B2;
+ t['blank'] = 0x2423;
+ t['blinebelow'] = 0x1E07;
+ t['block'] = 0x2588;
+ t['bmonospace'] = 0xFF42;
+ t['bobaimaithai'] = 0x0E1A;
+ t['bohiragana'] = 0x307C;
+ t['bokatakana'] = 0x30DC;
+ t['bparen'] = 0x249D;
+ t['bqsquare'] = 0x33C3;
+ t['braceex'] = 0xF8F4;
+ t['braceleft'] = 0x007B;
+ t['braceleftbt'] = 0xF8F3;
+ t['braceleftmid'] = 0xF8F2;
+ t['braceleftmonospace'] = 0xFF5B;
+ t['braceleftsmall'] = 0xFE5B;
+ t['bracelefttp'] = 0xF8F1;
+ t['braceleftvertical'] = 0xFE37;
+ t['braceright'] = 0x007D;
+ t['bracerightbt'] = 0xF8FE;
+ t['bracerightmid'] = 0xF8FD;
+ t['bracerightmonospace'] = 0xFF5D;
+ t['bracerightsmall'] = 0xFE5C;
+ t['bracerighttp'] = 0xF8FC;
+ t['bracerightvertical'] = 0xFE38;
+ t['bracketleft'] = 0x005B;
+ t['bracketleftbt'] = 0xF8F0;
+ t['bracketleftex'] = 0xF8EF;
+ t['bracketleftmonospace'] = 0xFF3B;
+ t['bracketlefttp'] = 0xF8EE;
+ t['bracketright'] = 0x005D;
+ t['bracketrightbt'] = 0xF8FB;
+ t['bracketrightex'] = 0xF8FA;
+ t['bracketrightmonospace'] = 0xFF3D;
+ t['bracketrighttp'] = 0xF8F9;
+ t['breve'] = 0x02D8;
+ t['brevebelowcmb'] = 0x032E;
+ t['brevecmb'] = 0x0306;
+ t['breveinvertedbelowcmb'] = 0x032F;
+ t['breveinvertedcmb'] = 0x0311;
+ t['breveinverteddoublecmb'] = 0x0361;
+ t['bridgebelowcmb'] = 0x032A;
+ t['bridgeinvertedbelowcmb'] = 0x033A;
+ t['brokenbar'] = 0x00A6;
+ t['bstroke'] = 0x0180;
+ t['bsuperior'] = 0xF6EA;
+ t['btopbar'] = 0x0183;
+ t['buhiragana'] = 0x3076;
+ t['bukatakana'] = 0x30D6;
+ t['bullet'] = 0x2022;
+ t['bulletinverse'] = 0x25D8;
+ t['bulletoperator'] = 0x2219;
+ t['bullseye'] = 0x25CE;
+ t['c'] = 0x0063;
+ t['caarmenian'] = 0x056E;
+ t['cabengali'] = 0x099A;
+ t['cacute'] = 0x0107;
+ t['cadeva'] = 0x091A;
+ t['cagujarati'] = 0x0A9A;
+ t['cagurmukhi'] = 0x0A1A;
+ t['calsquare'] = 0x3388;
+ t['candrabindubengali'] = 0x0981;
+ t['candrabinducmb'] = 0x0310;
+ t['candrabindudeva'] = 0x0901;
+ t['candrabindugujarati'] = 0x0A81;
+ t['capslock'] = 0x21EA;
+ t['careof'] = 0x2105;
+ t['caron'] = 0x02C7;
+ t['caronbelowcmb'] = 0x032C;
+ t['caroncmb'] = 0x030C;
+ t['carriagereturn'] = 0x21B5;
+ t['cbopomofo'] = 0x3118;
+ t['ccaron'] = 0x010D;
+ t['ccedilla'] = 0x00E7;
+ t['ccedillaacute'] = 0x1E09;
+ t['ccircle'] = 0x24D2;
+ t['ccircumflex'] = 0x0109;
+ t['ccurl'] = 0x0255;
+ t['cdot'] = 0x010B;
+ t['cdotaccent'] = 0x010B;
+ t['cdsquare'] = 0x33C5;
+ t['cedilla'] = 0x00B8;
+ t['cedillacmb'] = 0x0327;
+ t['cent'] = 0x00A2;
+ t['centigrade'] = 0x2103;
+ t['centinferior'] = 0xF6DF;
+ t['centmonospace'] = 0xFFE0;
+ t['centoldstyle'] = 0xF7A2;
+ t['centsuperior'] = 0xF6E0;
+ t['chaarmenian'] = 0x0579;
+ t['chabengali'] = 0x099B;
+ t['chadeva'] = 0x091B;
+ t['chagujarati'] = 0x0A9B;
+ t['chagurmukhi'] = 0x0A1B;
+ t['chbopomofo'] = 0x3114;
+ t['cheabkhasiancyrillic'] = 0x04BD;
+ t['checkmark'] = 0x2713;
+ t['checyrillic'] = 0x0447;
+ t['chedescenderabkhasiancyrillic'] = 0x04BF;
+ t['chedescendercyrillic'] = 0x04B7;
+ t['chedieresiscyrillic'] = 0x04F5;
+ t['cheharmenian'] = 0x0573;
+ t['chekhakassiancyrillic'] = 0x04CC;
+ t['cheverticalstrokecyrillic'] = 0x04B9;
+ t['chi'] = 0x03C7;
+ t['chieuchacirclekorean'] = 0x3277;
+ t['chieuchaparenkorean'] = 0x3217;
+ t['chieuchcirclekorean'] = 0x3269;
+ t['chieuchkorean'] = 0x314A;
+ t['chieuchparenkorean'] = 0x3209;
+ t['chochangthai'] = 0x0E0A;
+ t['chochanthai'] = 0x0E08;
+ t['chochingthai'] = 0x0E09;
+ t['chochoethai'] = 0x0E0C;
+ t['chook'] = 0x0188;
+ t['cieucacirclekorean'] = 0x3276;
+ t['cieucaparenkorean'] = 0x3216;
+ t['cieuccirclekorean'] = 0x3268;
+ t['cieuckorean'] = 0x3148;
+ t['cieucparenkorean'] = 0x3208;
+ t['cieucuparenkorean'] = 0x321C;
+ t['circle'] = 0x25CB;
+ t['circlecopyrt'] = 0x00A9;
+ // Glyph is missing from Adobe's original list.
+ t['circlemultiply'] = 0x2297;
+ t['circleot'] = 0x2299;
+ t['circleplus'] = 0x2295;
+ t['circlepostalmark'] = 0x3036;
+ t['circlewithlefthalfblack'] = 0x25D0;
+ t['circlewithrighthalfblack'] = 0x25D1;
+ t['circumflex'] = 0x02C6;
+ t['circumflexbelowcmb'] = 0x032D;
+ t['circumflexcmb'] = 0x0302;
+ t['clear'] = 0x2327;
+ t['clickalveolar'] = 0x01C2;
+ t['clickdental'] = 0x01C0;
+ t['clicklateral'] = 0x01C1;
+ t['clickretroflex'] = 0x01C3;
+ t['club'] = 0x2663;
+ t['clubsuitblack'] = 0x2663;
+ t['clubsuitwhite'] = 0x2667;
+ t['cmcubedsquare'] = 0x33A4;
+ t['cmonospace'] = 0xFF43;
+ t['cmsquaredsquare'] = 0x33A0;
+ t['coarmenian'] = 0x0581;
+ t['colon'] = 0x003A;
+ t['colonmonetary'] = 0x20A1;
+ t['colonmonospace'] = 0xFF1A;
+ t['colonsign'] = 0x20A1;
+ t['colonsmall'] = 0xFE55;
+ t['colontriangularhalfmod'] = 0x02D1;
+ t['colontriangularmod'] = 0x02D0;
+ t['comma'] = 0x002C;
+ t['commaabovecmb'] = 0x0313;
+ t['commaaboverightcmb'] = 0x0315;
+ t['commaaccent'] = 0xF6C3;
+ t['commaarabic'] = 0x060C;
+ t['commaarmenian'] = 0x055D;
+ t['commainferior'] = 0xF6E1;
+ t['commamonospace'] = 0xFF0C;
+ t['commareversedabovecmb'] = 0x0314;
+ t['commareversedmod'] = 0x02BD;
+ t['commasmall'] = 0xFE50;
+ t['commasuperior'] = 0xF6E2;
+ t['commaturnedabovecmb'] = 0x0312;
+ t['commaturnedmod'] = 0x02BB;
+ t['compass'] = 0x263C;
+ t['congruent'] = 0x2245;
+ t['contourintegral'] = 0x222E;
+ t['control'] = 0x2303;
+ t['controlACK'] = 0x0006;
+ t['controlBEL'] = 0x0007;
+ t['controlBS'] = 0x0008;
+ t['controlCAN'] = 0x0018;
+ t['controlCR'] = 0x000D;
+ t['controlDC1'] = 0x0011;
+ t['controlDC2'] = 0x0012;
+ t['controlDC3'] = 0x0013;
+ t['controlDC4'] = 0x0014;
+ t['controlDEL'] = 0x007F;
+ t['controlDLE'] = 0x0010;
+ t['controlEM'] = 0x0019;
+ t['controlENQ'] = 0x0005;
+ t['controlEOT'] = 0x0004;
+ t['controlESC'] = 0x001B;
+ t['controlETB'] = 0x0017;
+ t['controlETX'] = 0x0003;
+ t['controlFF'] = 0x000C;
+ t['controlFS'] = 0x001C;
+ t['controlGS'] = 0x001D;
+ t['controlHT'] = 0x0009;
+ t['controlLF'] = 0x000A;
+ t['controlNAK'] = 0x0015;
+ t['controlRS'] = 0x001E;
+ t['controlSI'] = 0x000F;
+ t['controlSO'] = 0x000E;
+ t['controlSOT'] = 0x0002;
+ t['controlSTX'] = 0x0001;
+ t['controlSUB'] = 0x001A;
+ t['controlSYN'] = 0x0016;
+ t['controlUS'] = 0x001F;
+ t['controlVT'] = 0x000B;
+ t['copyright'] = 0x00A9;
+ t['copyrightsans'] = 0xF8E9;
+ t['copyrightserif'] = 0xF6D9;
+ t['cornerbracketleft'] = 0x300C;
+ t['cornerbracketlefthalfwidth'] = 0xFF62;
+ t['cornerbracketleftvertical'] = 0xFE41;
+ t['cornerbracketright'] = 0x300D;
+ t['cornerbracketrighthalfwidth'] = 0xFF63;
+ t['cornerbracketrightvertical'] = 0xFE42;
+ t['corporationsquare'] = 0x337F;
+ t['cosquare'] = 0x33C7;
+ t['coverkgsquare'] = 0x33C6;
+ t['cparen'] = 0x249E;
+ t['cruzeiro'] = 0x20A2;
+ t['cstretched'] = 0x0297;
+ t['curlyand'] = 0x22CF;
+ t['curlyor'] = 0x22CE;
+ t['currency'] = 0x00A4;
+ t['cyrBreve'] = 0xF6D1;
+ t['cyrFlex'] = 0xF6D2;
+ t['cyrbreve'] = 0xF6D4;
+ t['cyrflex'] = 0xF6D5;
+ t['d'] = 0x0064;
+ t['daarmenian'] = 0x0564;
+ t['dabengali'] = 0x09A6;
+ t['dadarabic'] = 0x0636;
+ t['dadeva'] = 0x0926;
+ t['dadfinalarabic'] = 0xFEBE;
+ t['dadinitialarabic'] = 0xFEBF;
+ t['dadmedialarabic'] = 0xFEC0;
+ t['dagesh'] = 0x05BC;
+ t['dageshhebrew'] = 0x05BC;
+ t['dagger'] = 0x2020;
+ t['daggerdbl'] = 0x2021;
+ t['dagujarati'] = 0x0AA6;
+ t['dagurmukhi'] = 0x0A26;
+ t['dahiragana'] = 0x3060;
+ t['dakatakana'] = 0x30C0;
+ t['dalarabic'] = 0x062F;
+ t['dalet'] = 0x05D3;
+ t['daletdagesh'] = 0xFB33;
+ t['daletdageshhebrew'] = 0xFB33;
+ t['dalethebrew'] = 0x05D3;
+ t['dalfinalarabic'] = 0xFEAA;
+ t['dammaarabic'] = 0x064F;
+ t['dammalowarabic'] = 0x064F;
+ t['dammatanaltonearabic'] = 0x064C;
+ t['dammatanarabic'] = 0x064C;
+ t['danda'] = 0x0964;
+ t['dargahebrew'] = 0x05A7;
+ t['dargalefthebrew'] = 0x05A7;
+ t['dasiapneumatacyrilliccmb'] = 0x0485;
+ t['dblGrave'] = 0xF6D3;
+ t['dblanglebracketleft'] = 0x300A;
+ t['dblanglebracketleftvertical'] = 0xFE3D;
+ t['dblanglebracketright'] = 0x300B;
+ t['dblanglebracketrightvertical'] = 0xFE3E;
+ t['dblarchinvertedbelowcmb'] = 0x032B;
+ t['dblarrowleft'] = 0x21D4;
+ t['dblarrowright'] = 0x21D2;
+ t['dbldanda'] = 0x0965;
+ t['dblgrave'] = 0xF6D6;
+ t['dblgravecmb'] = 0x030F;
+ t['dblintegral'] = 0x222C;
+ t['dbllowline'] = 0x2017;
+ t['dbllowlinecmb'] = 0x0333;
+ t['dbloverlinecmb'] = 0x033F;
+ t['dblprimemod'] = 0x02BA;
+ t['dblverticalbar'] = 0x2016;
+ t['dblverticallineabovecmb'] = 0x030E;
+ t['dbopomofo'] = 0x3109;
+ t['dbsquare'] = 0x33C8;
+ t['dcaron'] = 0x010F;
+ t['dcedilla'] = 0x1E11;
+ t['dcircle'] = 0x24D3;
+ t['dcircumflexbelow'] = 0x1E13;
+ t['dcroat'] = 0x0111;
+ t['ddabengali'] = 0x09A1;
+ t['ddadeva'] = 0x0921;
+ t['ddagujarati'] = 0x0AA1;
+ t['ddagurmukhi'] = 0x0A21;
+ t['ddalarabic'] = 0x0688;
+ t['ddalfinalarabic'] = 0xFB89;
+ t['dddhadeva'] = 0x095C;
+ t['ddhabengali'] = 0x09A2;
+ t['ddhadeva'] = 0x0922;
+ t['ddhagujarati'] = 0x0AA2;
+ t['ddhagurmukhi'] = 0x0A22;
+ t['ddotaccent'] = 0x1E0B;
+ t['ddotbelow'] = 0x1E0D;
+ t['decimalseparatorarabic'] = 0x066B;
+ t['decimalseparatorpersian'] = 0x066B;
+ t['decyrillic'] = 0x0434;
+ t['degree'] = 0x00B0;
+ t['dehihebrew'] = 0x05AD;
+ t['dehiragana'] = 0x3067;
+ t['deicoptic'] = 0x03EF;
+ t['dekatakana'] = 0x30C7;
+ t['deleteleft'] = 0x232B;
+ t['deleteright'] = 0x2326;
+ t['delta'] = 0x03B4;
+ t['deltaturned'] = 0x018D;
+ t['denominatorminusonenumeratorbengali'] = 0x09F8;
+ t['dezh'] = 0x02A4;
+ t['dhabengali'] = 0x09A7;
+ t['dhadeva'] = 0x0927;
+ t['dhagujarati'] = 0x0AA7;
+ t['dhagurmukhi'] = 0x0A27;
+ t['dhook'] = 0x0257;
+ t['dialytikatonos'] = 0x0385;
+ t['dialytikatonoscmb'] = 0x0344;
+ t['diamond'] = 0x2666;
+ t['diamondsuitwhite'] = 0x2662;
+ t['dieresis'] = 0x00A8;
+ t['dieresisacute'] = 0xF6D7;
+ t['dieresisbelowcmb'] = 0x0324;
+ t['dieresiscmb'] = 0x0308;
+ t['dieresisgrave'] = 0xF6D8;
+ t['dieresistonos'] = 0x0385;
+ t['dihiragana'] = 0x3062;
+ t['dikatakana'] = 0x30C2;
+ t['dittomark'] = 0x3003;
+ t['divide'] = 0x00F7;
+ t['divides'] = 0x2223;
+ t['divisionslash'] = 0x2215;
+ t['djecyrillic'] = 0x0452;
+ t['dkshade'] = 0x2593;
+ t['dlinebelow'] = 0x1E0F;
+ t['dlsquare'] = 0x3397;
+ t['dmacron'] = 0x0111;
+ t['dmonospace'] = 0xFF44;
+ t['dnblock'] = 0x2584;
+ t['dochadathai'] = 0x0E0E;
+ t['dodekthai'] = 0x0E14;
+ t['dohiragana'] = 0x3069;
+ t['dokatakana'] = 0x30C9;
+ t['dollar'] = 0x0024;
+ t['dollarinferior'] = 0xF6E3;
+ t['dollarmonospace'] = 0xFF04;
+ t['dollaroldstyle'] = 0xF724;
+ t['dollarsmall'] = 0xFE69;
+ t['dollarsuperior'] = 0xF6E4;
+ t['dong'] = 0x20AB;
+ t['dorusquare'] = 0x3326;
+ t['dotaccent'] = 0x02D9;
+ t['dotaccentcmb'] = 0x0307;
+ t['dotbelowcmb'] = 0x0323;
+ t['dotbelowcomb'] = 0x0323;
+ t['dotkatakana'] = 0x30FB;
+ t['dotlessi'] = 0x0131;
+ t['dotlessj'] = 0xF6BE;
+ t['dotlessjstrokehook'] = 0x0284;
+ t['dotmath'] = 0x22C5;
+ t['dottedcircle'] = 0x25CC;
+ t['doubleyodpatah'] = 0xFB1F;
+ t['doubleyodpatahhebrew'] = 0xFB1F;
+ t['downtackbelowcmb'] = 0x031E;
+ t['downtackmod'] = 0x02D5;
+ t['dparen'] = 0x249F;
+ t['dsuperior'] = 0xF6EB;
+ t['dtail'] = 0x0256;
+ t['dtopbar'] = 0x018C;
+ t['duhiragana'] = 0x3065;
+ t['dukatakana'] = 0x30C5;
+ t['dz'] = 0x01F3;
+ t['dzaltone'] = 0x02A3;
+ t['dzcaron'] = 0x01C6;
+ t['dzcurl'] = 0x02A5;
+ t['dzeabkhasiancyrillic'] = 0x04E1;
+ t['dzecyrillic'] = 0x0455;
+ t['dzhecyrillic'] = 0x045F;
+ t['e'] = 0x0065;
+ t['eacute'] = 0x00E9;
+ t['earth'] = 0x2641;
+ t['ebengali'] = 0x098F;
+ t['ebopomofo'] = 0x311C;
+ t['ebreve'] = 0x0115;
+ t['ecandradeva'] = 0x090D;
+ t['ecandragujarati'] = 0x0A8D;
+ t['ecandravowelsigndeva'] = 0x0945;
+ t['ecandravowelsigngujarati'] = 0x0AC5;
+ t['ecaron'] = 0x011B;
+ t['ecedillabreve'] = 0x1E1D;
+ t['echarmenian'] = 0x0565;
+ t['echyiwnarmenian'] = 0x0587;
+ t['ecircle'] = 0x24D4;
+ t['ecircumflex'] = 0x00EA;
+ t['ecircumflexacute'] = 0x1EBF;
+ t['ecircumflexbelow'] = 0x1E19;
+ t['ecircumflexdotbelow'] = 0x1EC7;
+ t['ecircumflexgrave'] = 0x1EC1;
+ t['ecircumflexhookabove'] = 0x1EC3;
+ t['ecircumflextilde'] = 0x1EC5;
+ t['ecyrillic'] = 0x0454;
+ t['edblgrave'] = 0x0205;
+ t['edeva'] = 0x090F;
+ t['edieresis'] = 0x00EB;
+ t['edot'] = 0x0117;
+ t['edotaccent'] = 0x0117;
+ t['edotbelow'] = 0x1EB9;
+ t['eegurmukhi'] = 0x0A0F;
+ t['eematragurmukhi'] = 0x0A47;
+ t['efcyrillic'] = 0x0444;
+ t['egrave'] = 0x00E8;
+ t['egujarati'] = 0x0A8F;
+ t['eharmenian'] = 0x0567;
+ t['ehbopomofo'] = 0x311D;
+ t['ehiragana'] = 0x3048;
+ t['ehookabove'] = 0x1EBB;
+ t['eibopomofo'] = 0x311F;
+ t['eight'] = 0x0038;
+ t['eightarabic'] = 0x0668;
+ t['eightbengali'] = 0x09EE;
+ t['eightcircle'] = 0x2467;
+ t['eightcircleinversesansserif'] = 0x2791;
+ t['eightdeva'] = 0x096E;
+ t['eighteencircle'] = 0x2471;
+ t['eighteenparen'] = 0x2485;
+ t['eighteenperiod'] = 0x2499;
+ t['eightgujarati'] = 0x0AEE;
+ t['eightgurmukhi'] = 0x0A6E;
+ t['eighthackarabic'] = 0x0668;
+ t['eighthangzhou'] = 0x3028;
+ t['eighthnotebeamed'] = 0x266B;
+ t['eightideographicparen'] = 0x3227;
+ t['eightinferior'] = 0x2088;
+ t['eightmonospace'] = 0xFF18;
+ t['eightoldstyle'] = 0xF738;
+ t['eightparen'] = 0x247B;
+ t['eightperiod'] = 0x248F;
+ t['eightpersian'] = 0x06F8;
+ t['eightroman'] = 0x2177;
+ t['eightsuperior'] = 0x2078;
+ t['eightthai'] = 0x0E58;
+ t['einvertedbreve'] = 0x0207;
+ t['eiotifiedcyrillic'] = 0x0465;
+ t['ekatakana'] = 0x30A8;
+ t['ekatakanahalfwidth'] = 0xFF74;
+ t['ekonkargurmukhi'] = 0x0A74;
+ t['ekorean'] = 0x3154;
+ t['elcyrillic'] = 0x043B;
+ t['element'] = 0x2208;
+ t['elevencircle'] = 0x246A;
+ t['elevenparen'] = 0x247E;
+ t['elevenperiod'] = 0x2492;
+ t['elevenroman'] = 0x217A;
+ t['ellipsis'] = 0x2026;
+ t['ellipsisvertical'] = 0x22EE;
+ t['emacron'] = 0x0113;
+ t['emacronacute'] = 0x1E17;
+ t['emacrongrave'] = 0x1E15;
+ t['emcyrillic'] = 0x043C;
+ t['emdash'] = 0x2014;
+ t['emdashvertical'] = 0xFE31;
+ t['emonospace'] = 0xFF45;
+ t['emphasismarkarmenian'] = 0x055B;
+ t['emptyset'] = 0x2205;
+ t['enbopomofo'] = 0x3123;
+ t['encyrillic'] = 0x043D;
+ t['endash'] = 0x2013;
+ t['endashvertical'] = 0xFE32;
+ t['endescendercyrillic'] = 0x04A3;
+ t['eng'] = 0x014B;
+ t['engbopomofo'] = 0x3125;
+ t['enghecyrillic'] = 0x04A5;
+ t['enhookcyrillic'] = 0x04C8;
+ t['enspace'] = 0x2002;
+ t['eogonek'] = 0x0119;
+ t['eokorean'] = 0x3153;
+ t['eopen'] = 0x025B;
+ t['eopenclosed'] = 0x029A;
+ t['eopenreversed'] = 0x025C;
+ t['eopenreversedclosed'] = 0x025E;
+ t['eopenreversedhook'] = 0x025D;
+ t['eparen'] = 0x24A0;
+ t['epsilon'] = 0x03B5;
+ t['epsilontonos'] = 0x03AD;
+ t['equal'] = 0x003D;
+ t['equalmonospace'] = 0xFF1D;
+ t['equalsmall'] = 0xFE66;
+ t['equalsuperior'] = 0x207C;
+ t['equivalence'] = 0x2261;
+ t['erbopomofo'] = 0x3126;
+ t['ercyrillic'] = 0x0440;
+ t['ereversed'] = 0x0258;
+ t['ereversedcyrillic'] = 0x044D;
+ t['escyrillic'] = 0x0441;
+ t['esdescendercyrillic'] = 0x04AB;
+ t['esh'] = 0x0283;
+ t['eshcurl'] = 0x0286;
+ t['eshortdeva'] = 0x090E;
+ t['eshortvowelsigndeva'] = 0x0946;
+ t['eshreversedloop'] = 0x01AA;
+ t['eshsquatreversed'] = 0x0285;
+ t['esmallhiragana'] = 0x3047;
+ t['esmallkatakana'] = 0x30A7;
+ t['esmallkatakanahalfwidth'] = 0xFF6A;
+ t['estimated'] = 0x212E;
+ t['esuperior'] = 0xF6EC;
+ t['eta'] = 0x03B7;
+ t['etarmenian'] = 0x0568;
+ t['etatonos'] = 0x03AE;
+ t['eth'] = 0x00F0;
+ t['etilde'] = 0x1EBD;
+ t['etildebelow'] = 0x1E1B;
+ t['etnahtafoukhhebrew'] = 0x0591;
+ t['etnahtafoukhlefthebrew'] = 0x0591;
+ t['etnahtahebrew'] = 0x0591;
+ t['etnahtalefthebrew'] = 0x0591;
+ t['eturned'] = 0x01DD;
+ t['eukorean'] = 0x3161;
+ t['euro'] = 0x20AC;
+ t['evowelsignbengali'] = 0x09C7;
+ t['evowelsigndeva'] = 0x0947;
+ t['evowelsigngujarati'] = 0x0AC7;
+ t['exclam'] = 0x0021;
+ t['exclamarmenian'] = 0x055C;
+ t['exclamdbl'] = 0x203C;
+ t['exclamdown'] = 0x00A1;
+ t['exclamdownsmall'] = 0xF7A1;
+ t['exclammonospace'] = 0xFF01;
+ t['exclamsmall'] = 0xF721;
+ t['existential'] = 0x2203;
+ t['ezh'] = 0x0292;
+ t['ezhcaron'] = 0x01EF;
+ t['ezhcurl'] = 0x0293;
+ t['ezhreversed'] = 0x01B9;
+ t['ezhtail'] = 0x01BA;
+ t['f'] = 0x0066;
+ t['fadeva'] = 0x095E;
+ t['fagurmukhi'] = 0x0A5E;
+ t['fahrenheit'] = 0x2109;
+ t['fathaarabic'] = 0x064E;
+ t['fathalowarabic'] = 0x064E;
+ t['fathatanarabic'] = 0x064B;
+ t['fbopomofo'] = 0x3108;
+ t['fcircle'] = 0x24D5;
+ t['fdotaccent'] = 0x1E1F;
+ t['feharabic'] = 0x0641;
+ t['feharmenian'] = 0x0586;
+ t['fehfinalarabic'] = 0xFED2;
+ t['fehinitialarabic'] = 0xFED3;
+ t['fehmedialarabic'] = 0xFED4;
+ t['feicoptic'] = 0x03E5;
+ t['female'] = 0x2640;
+ t['ff'] = 0xFB00;
+ t['ffi'] = 0xFB03;
+ t['ffl'] = 0xFB04;
+ t['fi'] = 0xFB01;
+ t['fifteencircle'] = 0x246E;
+ t['fifteenparen'] = 0x2482;
+ t['fifteenperiod'] = 0x2496;
+ t['figuredash'] = 0x2012;
+ t['filledbox'] = 0x25A0;
+ t['filledrect'] = 0x25AC;
+ t['finalkaf'] = 0x05DA;
+ t['finalkafdagesh'] = 0xFB3A;
+ t['finalkafdageshhebrew'] = 0xFB3A;
+ t['finalkafhebrew'] = 0x05DA;
+ t['finalmem'] = 0x05DD;
+ t['finalmemhebrew'] = 0x05DD;
+ t['finalnun'] = 0x05DF;
+ t['finalnunhebrew'] = 0x05DF;
+ t['finalpe'] = 0x05E3;
+ t['finalpehebrew'] = 0x05E3;
+ t['finaltsadi'] = 0x05E5;
+ t['finaltsadihebrew'] = 0x05E5;
+ t['firsttonechinese'] = 0x02C9;
+ t['fisheye'] = 0x25C9;
+ t['fitacyrillic'] = 0x0473;
+ t['five'] = 0x0035;
+ t['fivearabic'] = 0x0665;
+ t['fivebengali'] = 0x09EB;
+ t['fivecircle'] = 0x2464;
+ t['fivecircleinversesansserif'] = 0x278E;
+ t['fivedeva'] = 0x096B;
+ t['fiveeighths'] = 0x215D;
+ t['fivegujarati'] = 0x0AEB;
+ t['fivegurmukhi'] = 0x0A6B;
+ t['fivehackarabic'] = 0x0665;
+ t['fivehangzhou'] = 0x3025;
+ t['fiveideographicparen'] = 0x3224;
+ t['fiveinferior'] = 0x2085;
+ t['fivemonospace'] = 0xFF15;
+ t['fiveoldstyle'] = 0xF735;
+ t['fiveparen'] = 0x2478;
+ t['fiveperiod'] = 0x248C;
+ t['fivepersian'] = 0x06F5;
+ t['fiveroman'] = 0x2174;
+ t['fivesuperior'] = 0x2075;
+ t['fivethai'] = 0x0E55;
+ t['fl'] = 0xFB02;
+ t['florin'] = 0x0192;
+ t['fmonospace'] = 0xFF46;
+ t['fmsquare'] = 0x3399;
+ t['fofanthai'] = 0x0E1F;
+ t['fofathai'] = 0x0E1D;
+ t['fongmanthai'] = 0x0E4F;
+ t['forall'] = 0x2200;
+ t['four'] = 0x0034;
+ t['fourarabic'] = 0x0664;
+ t['fourbengali'] = 0x09EA;
+ t['fourcircle'] = 0x2463;
+ t['fourcircleinversesansserif'] = 0x278D;
+ t['fourdeva'] = 0x096A;
+ t['fourgujarati'] = 0x0AEA;
+ t['fourgurmukhi'] = 0x0A6A;
+ t['fourhackarabic'] = 0x0664;
+ t['fourhangzhou'] = 0x3024;
+ t['fourideographicparen'] = 0x3223;
+ t['fourinferior'] = 0x2084;
+ t['fourmonospace'] = 0xFF14;
+ t['fournumeratorbengali'] = 0x09F7;
+ t['fouroldstyle'] = 0xF734;
+ t['fourparen'] = 0x2477;
+ t['fourperiod'] = 0x248B;
+ t['fourpersian'] = 0x06F4;
+ t['fourroman'] = 0x2173;
+ t['foursuperior'] = 0x2074;
+ t['fourteencircle'] = 0x246D;
+ t['fourteenparen'] = 0x2481;
+ t['fourteenperiod'] = 0x2495;
+ t['fourthai'] = 0x0E54;
+ t['fourthtonechinese'] = 0x02CB;
+ t['fparen'] = 0x24A1;
+ t['fraction'] = 0x2044;
+ t['franc'] = 0x20A3;
+ t['g'] = 0x0067;
+ t['gabengali'] = 0x0997;
+ t['gacute'] = 0x01F5;
+ t['gadeva'] = 0x0917;
+ t['gafarabic'] = 0x06AF;
+ t['gaffinalarabic'] = 0xFB93;
+ t['gafinitialarabic'] = 0xFB94;
+ t['gafmedialarabic'] = 0xFB95;
+ t['gagujarati'] = 0x0A97;
+ t['gagurmukhi'] = 0x0A17;
+ t['gahiragana'] = 0x304C;
+ t['gakatakana'] = 0x30AC;
+ t['gamma'] = 0x03B3;
+ t['gammalatinsmall'] = 0x0263;
+ t['gammasuperior'] = 0x02E0;
+ t['gangiacoptic'] = 0x03EB;
+ t['gbopomofo'] = 0x310D;
+ t['gbreve'] = 0x011F;
+ t['gcaron'] = 0x01E7;
+ t['gcedilla'] = 0x0123;
+ t['gcircle'] = 0x24D6;
+ t['gcircumflex'] = 0x011D;
+ t['gcommaaccent'] = 0x0123;
+ t['gdot'] = 0x0121;
+ t['gdotaccent'] = 0x0121;
+ t['gecyrillic'] = 0x0433;
+ t['gehiragana'] = 0x3052;
+ t['gekatakana'] = 0x30B2;
+ t['geometricallyequal'] = 0x2251;
+ t['gereshaccenthebrew'] = 0x059C;
+ t['gereshhebrew'] = 0x05F3;
+ t['gereshmuqdamhebrew'] = 0x059D;
+ t['germandbls'] = 0x00DF;
+ t['gershayimaccenthebrew'] = 0x059E;
+ t['gershayimhebrew'] = 0x05F4;
+ t['getamark'] = 0x3013;
+ t['ghabengali'] = 0x0998;
+ t['ghadarmenian'] = 0x0572;
+ t['ghadeva'] = 0x0918;
+ t['ghagujarati'] = 0x0A98;
+ t['ghagurmukhi'] = 0x0A18;
+ t['ghainarabic'] = 0x063A;
+ t['ghainfinalarabic'] = 0xFECE;
+ t['ghaininitialarabic'] = 0xFECF;
+ t['ghainmedialarabic'] = 0xFED0;
+ t['ghemiddlehookcyrillic'] = 0x0495;
+ t['ghestrokecyrillic'] = 0x0493;
+ t['gheupturncyrillic'] = 0x0491;
+ t['ghhadeva'] = 0x095A;
+ t['ghhagurmukhi'] = 0x0A5A;
+ t['ghook'] = 0x0260;
+ t['ghzsquare'] = 0x3393;
+ t['gihiragana'] = 0x304E;
+ t['gikatakana'] = 0x30AE;
+ t['gimarmenian'] = 0x0563;
+ t['gimel'] = 0x05D2;
+ t['gimeldagesh'] = 0xFB32;
+ t['gimeldageshhebrew'] = 0xFB32;
+ t['gimelhebrew'] = 0x05D2;
+ t['gjecyrillic'] = 0x0453;
+ t['glottalinvertedstroke'] = 0x01BE;
+ t['glottalstop'] = 0x0294;
+ t['glottalstopinverted'] = 0x0296;
+ t['glottalstopmod'] = 0x02C0;
+ t['glottalstopreversed'] = 0x0295;
+ t['glottalstopreversedmod'] = 0x02C1;
+ t['glottalstopreversedsuperior'] = 0x02E4;
+ t['glottalstopstroke'] = 0x02A1;
+ t['glottalstopstrokereversed'] = 0x02A2;
+ t['gmacron'] = 0x1E21;
+ t['gmonospace'] = 0xFF47;
+ t['gohiragana'] = 0x3054;
+ t['gokatakana'] = 0x30B4;
+ t['gparen'] = 0x24A2;
+ t['gpasquare'] = 0x33AC;
+ t['gradient'] = 0x2207;
+ t['grave'] = 0x0060;
+ t['gravebelowcmb'] = 0x0316;
+ t['gravecmb'] = 0x0300;
+ t['gravecomb'] = 0x0300;
+ t['gravedeva'] = 0x0953;
+ t['gravelowmod'] = 0x02CE;
+ t['gravemonospace'] = 0xFF40;
+ t['gravetonecmb'] = 0x0340;
+ t['greater'] = 0x003E;
+ t['greaterequal'] = 0x2265;
+ t['greaterequalorless'] = 0x22DB;
+ t['greatermonospace'] = 0xFF1E;
+ t['greaterorequivalent'] = 0x2273;
+ t['greaterorless'] = 0x2277;
+ t['greateroverequal'] = 0x2267;
+ t['greatersmall'] = 0xFE65;
+ t['gscript'] = 0x0261;
+ t['gstroke'] = 0x01E5;
+ t['guhiragana'] = 0x3050;
+ t['guillemotleft'] = 0x00AB;
+ t['guillemotright'] = 0x00BB;
+ t['guilsinglleft'] = 0x2039;
+ t['guilsinglright'] = 0x203A;
+ t['gukatakana'] = 0x30B0;
+ t['guramusquare'] = 0x3318;
+ t['gysquare'] = 0x33C9;
+ t['h'] = 0x0068;
+ t['haabkhasiancyrillic'] = 0x04A9;
+ t['haaltonearabic'] = 0x06C1;
+ t['habengali'] = 0x09B9;
+ t['hadescendercyrillic'] = 0x04B3;
+ t['hadeva'] = 0x0939;
+ t['hagujarati'] = 0x0AB9;
+ t['hagurmukhi'] = 0x0A39;
+ t['haharabic'] = 0x062D;
+ t['hahfinalarabic'] = 0xFEA2;
+ t['hahinitialarabic'] = 0xFEA3;
+ t['hahiragana'] = 0x306F;
+ t['hahmedialarabic'] = 0xFEA4;
+ t['haitusquare'] = 0x332A;
+ t['hakatakana'] = 0x30CF;
+ t['hakatakanahalfwidth'] = 0xFF8A;
+ t['halantgurmukhi'] = 0x0A4D;
+ t['hamzaarabic'] = 0x0621;
+ t['hamzalowarabic'] = 0x0621;
+ t['hangulfiller'] = 0x3164;
+ t['hardsigncyrillic'] = 0x044A;
+ t['harpoonleftbarbup'] = 0x21BC;
+ t['harpoonrightbarbup'] = 0x21C0;
+ t['hasquare'] = 0x33CA;
+ t['hatafpatah'] = 0x05B2;
+ t['hatafpatah16'] = 0x05B2;
+ t['hatafpatah23'] = 0x05B2;
+ t['hatafpatah2f'] = 0x05B2;
+ t['hatafpatahhebrew'] = 0x05B2;
+ t['hatafpatahnarrowhebrew'] = 0x05B2;
+ t['hatafpatahquarterhebrew'] = 0x05B2;
+ t['hatafpatahwidehebrew'] = 0x05B2;
+ t['hatafqamats'] = 0x05B3;
+ t['hatafqamats1b'] = 0x05B3;
+ t['hatafqamats28'] = 0x05B3;
+ t['hatafqamats34'] = 0x05B3;
+ t['hatafqamatshebrew'] = 0x05B3;
+ t['hatafqamatsnarrowhebrew'] = 0x05B3;
+ t['hatafqamatsquarterhebrew'] = 0x05B3;
+ t['hatafqamatswidehebrew'] = 0x05B3;
+ t['hatafsegol'] = 0x05B1;
+ t['hatafsegol17'] = 0x05B1;
+ t['hatafsegol24'] = 0x05B1;
+ t['hatafsegol30'] = 0x05B1;
+ t['hatafsegolhebrew'] = 0x05B1;
+ t['hatafsegolnarrowhebrew'] = 0x05B1;
+ t['hatafsegolquarterhebrew'] = 0x05B1;
+ t['hatafsegolwidehebrew'] = 0x05B1;
+ t['hbar'] = 0x0127;
+ t['hbopomofo'] = 0x310F;
+ t['hbrevebelow'] = 0x1E2B;
+ t['hcedilla'] = 0x1E29;
+ t['hcircle'] = 0x24D7;
+ t['hcircumflex'] = 0x0125;
+ t['hdieresis'] = 0x1E27;
+ t['hdotaccent'] = 0x1E23;
+ t['hdotbelow'] = 0x1E25;
+ t['he'] = 0x05D4;
+ t['heart'] = 0x2665;
+ t['heartsuitblack'] = 0x2665;
+ t['heartsuitwhite'] = 0x2661;
+ t['hedagesh'] = 0xFB34;
+ t['hedageshhebrew'] = 0xFB34;
+ t['hehaltonearabic'] = 0x06C1;
+ t['heharabic'] = 0x0647;
+ t['hehebrew'] = 0x05D4;
+ t['hehfinalaltonearabic'] = 0xFBA7;
+ t['hehfinalalttwoarabic'] = 0xFEEA;
+ t['hehfinalarabic'] = 0xFEEA;
+ t['hehhamzaabovefinalarabic'] = 0xFBA5;
+ t['hehhamzaaboveisolatedarabic'] = 0xFBA4;
+ t['hehinitialaltonearabic'] = 0xFBA8;
+ t['hehinitialarabic'] = 0xFEEB;
+ t['hehiragana'] = 0x3078;
+ t['hehmedialaltonearabic'] = 0xFBA9;
+ t['hehmedialarabic'] = 0xFEEC;
+ t['heiseierasquare'] = 0x337B;
+ t['hekatakana'] = 0x30D8;
+ t['hekatakanahalfwidth'] = 0xFF8D;
+ t['hekutaarusquare'] = 0x3336;
+ t['henghook'] = 0x0267;
+ t['herutusquare'] = 0x3339;
+ t['het'] = 0x05D7;
+ t['hethebrew'] = 0x05D7;
+ t['hhook'] = 0x0266;
+ t['hhooksuperior'] = 0x02B1;
+ t['hieuhacirclekorean'] = 0x327B;
+ t['hieuhaparenkorean'] = 0x321B;
+ t['hieuhcirclekorean'] = 0x326D;
+ t['hieuhkorean'] = 0x314E;
+ t['hieuhparenkorean'] = 0x320D;
+ t['hihiragana'] = 0x3072;
+ t['hikatakana'] = 0x30D2;
+ t['hikatakanahalfwidth'] = 0xFF8B;
+ t['hiriq'] = 0x05B4;
+ t['hiriq14'] = 0x05B4;
+ t['hiriq21'] = 0x05B4;
+ t['hiriq2d'] = 0x05B4;
+ t['hiriqhebrew'] = 0x05B4;
+ t['hiriqnarrowhebrew'] = 0x05B4;
+ t['hiriqquarterhebrew'] = 0x05B4;
+ t['hiriqwidehebrew'] = 0x05B4;
+ t['hlinebelow'] = 0x1E96;
+ t['hmonospace'] = 0xFF48;
+ t['hoarmenian'] = 0x0570;
+ t['hohipthai'] = 0x0E2B;
+ t['hohiragana'] = 0x307B;
+ t['hokatakana'] = 0x30DB;
+ t['hokatakanahalfwidth'] = 0xFF8E;
+ t['holam'] = 0x05B9;
+ t['holam19'] = 0x05B9;
+ t['holam26'] = 0x05B9;
+ t['holam32'] = 0x05B9;
+ t['holamhebrew'] = 0x05B9;
+ t['holamnarrowhebrew'] = 0x05B9;
+ t['holamquarterhebrew'] = 0x05B9;
+ t['holamwidehebrew'] = 0x05B9;
+ t['honokhukthai'] = 0x0E2E;
+ t['hookabovecomb'] = 0x0309;
+ t['hookcmb'] = 0x0309;
+ t['hookpalatalizedbelowcmb'] = 0x0321;
+ t['hookretroflexbelowcmb'] = 0x0322;
+ t['hoonsquare'] = 0x3342;
+ t['horicoptic'] = 0x03E9;
+ t['horizontalbar'] = 0x2015;
+ t['horncmb'] = 0x031B;
+ t['hotsprings'] = 0x2668;
+ t['house'] = 0x2302;
+ t['hparen'] = 0x24A3;
+ t['hsuperior'] = 0x02B0;
+ t['hturned'] = 0x0265;
+ t['huhiragana'] = 0x3075;
+ t['huiitosquare'] = 0x3333;
+ t['hukatakana'] = 0x30D5;
+ t['hukatakanahalfwidth'] = 0xFF8C;
+ t['hungarumlaut'] = 0x02DD;
+ t['hungarumlautcmb'] = 0x030B;
+ t['hv'] = 0x0195;
+ t['hyphen'] = 0x002D;
+ t['hypheninferior'] = 0xF6E5;
+ t['hyphenmonospace'] = 0xFF0D;
+ t['hyphensmall'] = 0xFE63;
+ t['hyphensuperior'] = 0xF6E6;
+ t['hyphentwo'] = 0x2010;
+ t['i'] = 0x0069;
+ t['iacute'] = 0x00ED;
+ t['iacyrillic'] = 0x044F;
+ t['ibengali'] = 0x0987;
+ t['ibopomofo'] = 0x3127;
+ t['ibreve'] = 0x012D;
+ t['icaron'] = 0x01D0;
+ t['icircle'] = 0x24D8;
+ t['icircumflex'] = 0x00EE;
+ t['icyrillic'] = 0x0456;
+ t['idblgrave'] = 0x0209;
+ t['ideographearthcircle'] = 0x328F;
+ t['ideographfirecircle'] = 0x328B;
+ t['ideographicallianceparen'] = 0x323F;
+ t['ideographiccallparen'] = 0x323A;
+ t['ideographiccentrecircle'] = 0x32A5;
+ t['ideographicclose'] = 0x3006;
+ t['ideographiccomma'] = 0x3001;
+ t['ideographiccommaleft'] = 0xFF64;
+ t['ideographiccongratulationparen'] = 0x3237;
+ t['ideographiccorrectcircle'] = 0x32A3;
+ t['ideographicearthparen'] = 0x322F;
+ t['ideographicenterpriseparen'] = 0x323D;
+ t['ideographicexcellentcircle'] = 0x329D;
+ t['ideographicfestivalparen'] = 0x3240;
+ t['ideographicfinancialcircle'] = 0x3296;
+ t['ideographicfinancialparen'] = 0x3236;
+ t['ideographicfireparen'] = 0x322B;
+ t['ideographichaveparen'] = 0x3232;
+ t['ideographichighcircle'] = 0x32A4;
+ t['ideographiciterationmark'] = 0x3005;
+ t['ideographiclaborcircle'] = 0x3298;
+ t['ideographiclaborparen'] = 0x3238;
+ t['ideographicleftcircle'] = 0x32A7;
+ t['ideographiclowcircle'] = 0x32A6;
+ t['ideographicmedicinecircle'] = 0x32A9;
+ t['ideographicmetalparen'] = 0x322E;
+ t['ideographicmoonparen'] = 0x322A;
+ t['ideographicnameparen'] = 0x3234;
+ t['ideographicperiod'] = 0x3002;
+ t['ideographicprintcircle'] = 0x329E;
+ t['ideographicreachparen'] = 0x3243;
+ t['ideographicrepresentparen'] = 0x3239;
+ t['ideographicresourceparen'] = 0x323E;
+ t['ideographicrightcircle'] = 0x32A8;
+ t['ideographicsecretcircle'] = 0x3299;
+ t['ideographicselfparen'] = 0x3242;
+ t['ideographicsocietyparen'] = 0x3233;
+ t['ideographicspace'] = 0x3000;
+ t['ideographicspecialparen'] = 0x3235;
+ t['ideographicstockparen'] = 0x3231;
+ t['ideographicstudyparen'] = 0x323B;
+ t['ideographicsunparen'] = 0x3230;
+ t['ideographicsuperviseparen'] = 0x323C;
+ t['ideographicwaterparen'] = 0x322C;
+ t['ideographicwoodparen'] = 0x322D;
+ t['ideographiczero'] = 0x3007;
+ t['ideographmetalcircle'] = 0x328E;
+ t['ideographmooncircle'] = 0x328A;
+ t['ideographnamecircle'] = 0x3294;
+ t['ideographsuncircle'] = 0x3290;
+ t['ideographwatercircle'] = 0x328C;
+ t['ideographwoodcircle'] = 0x328D;
+ t['ideva'] = 0x0907;
+ t['idieresis'] = 0x00EF;
+ t['idieresisacute'] = 0x1E2F;
+ t['idieresiscyrillic'] = 0x04E5;
+ t['idotbelow'] = 0x1ECB;
+ t['iebrevecyrillic'] = 0x04D7;
+ t['iecyrillic'] = 0x0435;
+ t['ieungacirclekorean'] = 0x3275;
+ t['ieungaparenkorean'] = 0x3215;
+ t['ieungcirclekorean'] = 0x3267;
+ t['ieungkorean'] = 0x3147;
+ t['ieungparenkorean'] = 0x3207;
+ t['igrave'] = 0x00EC;
+ t['igujarati'] = 0x0A87;
+ t['igurmukhi'] = 0x0A07;
+ t['ihiragana'] = 0x3044;
+ t['ihookabove'] = 0x1EC9;
+ t['iibengali'] = 0x0988;
+ t['iicyrillic'] = 0x0438;
+ t['iideva'] = 0x0908;
+ t['iigujarati'] = 0x0A88;
+ t['iigurmukhi'] = 0x0A08;
+ t['iimatragurmukhi'] = 0x0A40;
+ t['iinvertedbreve'] = 0x020B;
+ t['iishortcyrillic'] = 0x0439;
+ t['iivowelsignbengali'] = 0x09C0;
+ t['iivowelsigndeva'] = 0x0940;
+ t['iivowelsigngujarati'] = 0x0AC0;
+ t['ij'] = 0x0133;
+ t['ikatakana'] = 0x30A4;
+ t['ikatakanahalfwidth'] = 0xFF72;
+ t['ikorean'] = 0x3163;
+ t['ilde'] = 0x02DC;
+ t['iluyhebrew'] = 0x05AC;
+ t['imacron'] = 0x012B;
+ t['imacroncyrillic'] = 0x04E3;
+ t['imageorapproximatelyequal'] = 0x2253;
+ t['imatragurmukhi'] = 0x0A3F;
+ t['imonospace'] = 0xFF49;
+ t['increment'] = 0x2206;
+ t['infinity'] = 0x221E;
+ t['iniarmenian'] = 0x056B;
+ t['integral'] = 0x222B;
+ t['integralbottom'] = 0x2321;
+ t['integralbt'] = 0x2321;
+ t['integralex'] = 0xF8F5;
+ t['integraltop'] = 0x2320;
+ t['integraltp'] = 0x2320;
+ t['intersection'] = 0x2229;
+ t['intisquare'] = 0x3305;
+ t['invbullet'] = 0x25D8;
+ t['invcircle'] = 0x25D9;
+ t['invsmileface'] = 0x263B;
+ t['iocyrillic'] = 0x0451;
+ t['iogonek'] = 0x012F;
+ t['iota'] = 0x03B9;
+ t['iotadieresis'] = 0x03CA;
+ t['iotadieresistonos'] = 0x0390;
+ t['iotalatin'] = 0x0269;
+ t['iotatonos'] = 0x03AF;
+ t['iparen'] = 0x24A4;
+ t['irigurmukhi'] = 0x0A72;
+ t['ismallhiragana'] = 0x3043;
+ t['ismallkatakana'] = 0x30A3;
+ t['ismallkatakanahalfwidth'] = 0xFF68;
+ t['issharbengali'] = 0x09FA;
+ t['istroke'] = 0x0268;
+ t['isuperior'] = 0xF6ED;
+ t['iterationhiragana'] = 0x309D;
+ t['iterationkatakana'] = 0x30FD;
+ t['itilde'] = 0x0129;
+ t['itildebelow'] = 0x1E2D;
+ t['iubopomofo'] = 0x3129;
+ t['iucyrillic'] = 0x044E;
+ t['ivowelsignbengali'] = 0x09BF;
+ t['ivowelsigndeva'] = 0x093F;
+ t['ivowelsigngujarati'] = 0x0ABF;
+ t['izhitsacyrillic'] = 0x0475;
+ t['izhitsadblgravecyrillic'] = 0x0477;
+ t['j'] = 0x006A;
+ t['jaarmenian'] = 0x0571;
+ t['jabengali'] = 0x099C;
+ t['jadeva'] = 0x091C;
+ t['jagujarati'] = 0x0A9C;
+ t['jagurmukhi'] = 0x0A1C;
+ t['jbopomofo'] = 0x3110;
+ t['jcaron'] = 0x01F0;
+ t['jcircle'] = 0x24D9;
+ t['jcircumflex'] = 0x0135;
+ t['jcrossedtail'] = 0x029D;
+ t['jdotlessstroke'] = 0x025F;
+ t['jecyrillic'] = 0x0458;
+ t['jeemarabic'] = 0x062C;
+ t['jeemfinalarabic'] = 0xFE9E;
+ t['jeeminitialarabic'] = 0xFE9F;
+ t['jeemmedialarabic'] = 0xFEA0;
+ t['jeharabic'] = 0x0698;
+ t['jehfinalarabic'] = 0xFB8B;
+ t['jhabengali'] = 0x099D;
+ t['jhadeva'] = 0x091D;
+ t['jhagujarati'] = 0x0A9D;
+ t['jhagurmukhi'] = 0x0A1D;
+ t['jheharmenian'] = 0x057B;
+ t['jis'] = 0x3004;
+ t['jmonospace'] = 0xFF4A;
+ t['jparen'] = 0x24A5;
+ t['jsuperior'] = 0x02B2;
+ t['k'] = 0x006B;
+ t['kabashkircyrillic'] = 0x04A1;
+ t['kabengali'] = 0x0995;
+ t['kacute'] = 0x1E31;
+ t['kacyrillic'] = 0x043A;
+ t['kadescendercyrillic'] = 0x049B;
+ t['kadeva'] = 0x0915;
+ t['kaf'] = 0x05DB;
+ t['kafarabic'] = 0x0643;
+ t['kafdagesh'] = 0xFB3B;
+ t['kafdageshhebrew'] = 0xFB3B;
+ t['kaffinalarabic'] = 0xFEDA;
+ t['kafhebrew'] = 0x05DB;
+ t['kafinitialarabic'] = 0xFEDB;
+ t['kafmedialarabic'] = 0xFEDC;
+ t['kafrafehebrew'] = 0xFB4D;
+ t['kagujarati'] = 0x0A95;
+ t['kagurmukhi'] = 0x0A15;
+ t['kahiragana'] = 0x304B;
+ t['kahookcyrillic'] = 0x04C4;
+ t['kakatakana'] = 0x30AB;
+ t['kakatakanahalfwidth'] = 0xFF76;
+ t['kappa'] = 0x03BA;
+ t['kappasymbolgreek'] = 0x03F0;
+ t['kapyeounmieumkorean'] = 0x3171;
+ t['kapyeounphieuphkorean'] = 0x3184;
+ t['kapyeounpieupkorean'] = 0x3178;
+ t['kapyeounssangpieupkorean'] = 0x3179;
+ t['karoriisquare'] = 0x330D;
+ t['kashidaautoarabic'] = 0x0640;
+ t['kashidaautonosidebearingarabic'] = 0x0640;
+ t['kasmallkatakana'] = 0x30F5;
+ t['kasquare'] = 0x3384;
+ t['kasraarabic'] = 0x0650;
+ t['kasratanarabic'] = 0x064D;
+ t['kastrokecyrillic'] = 0x049F;
+ t['katahiraprolongmarkhalfwidth'] = 0xFF70;
+ t['kaverticalstrokecyrillic'] = 0x049D;
+ t['kbopomofo'] = 0x310E;
+ t['kcalsquare'] = 0x3389;
+ t['kcaron'] = 0x01E9;
+ t['kcedilla'] = 0x0137;
+ t['kcircle'] = 0x24DA;
+ t['kcommaaccent'] = 0x0137;
+ t['kdotbelow'] = 0x1E33;
+ t['keharmenian'] = 0x0584;
+ t['kehiragana'] = 0x3051;
+ t['kekatakana'] = 0x30B1;
+ t['kekatakanahalfwidth'] = 0xFF79;
+ t['kenarmenian'] = 0x056F;
+ t['kesmallkatakana'] = 0x30F6;
+ t['kgreenlandic'] = 0x0138;
+ t['khabengali'] = 0x0996;
+ t['khacyrillic'] = 0x0445;
+ t['khadeva'] = 0x0916;
+ t['khagujarati'] = 0x0A96;
+ t['khagurmukhi'] = 0x0A16;
+ t['khaharabic'] = 0x062E;
+ t['khahfinalarabic'] = 0xFEA6;
+ t['khahinitialarabic'] = 0xFEA7;
+ t['khahmedialarabic'] = 0xFEA8;
+ t['kheicoptic'] = 0x03E7;
+ t['khhadeva'] = 0x0959;
+ t['khhagurmukhi'] = 0x0A59;
+ t['khieukhacirclekorean'] = 0x3278;
+ t['khieukhaparenkorean'] = 0x3218;
+ t['khieukhcirclekorean'] = 0x326A;
+ t['khieukhkorean'] = 0x314B;
+ t['khieukhparenkorean'] = 0x320A;
+ t['khokhaithai'] = 0x0E02;
+ t['khokhonthai'] = 0x0E05;
+ t['khokhuatthai'] = 0x0E03;
+ t['khokhwaithai'] = 0x0E04;
+ t['khomutthai'] = 0x0E5B;
+ t['khook'] = 0x0199;
+ t['khorakhangthai'] = 0x0E06;
+ t['khzsquare'] = 0x3391;
+ t['kihiragana'] = 0x304D;
+ t['kikatakana'] = 0x30AD;
+ t['kikatakanahalfwidth'] = 0xFF77;
+ t['kiroguramusquare'] = 0x3315;
+ t['kiromeetorusquare'] = 0x3316;
+ t['kirosquare'] = 0x3314;
+ t['kiyeokacirclekorean'] = 0x326E;
+ t['kiyeokaparenkorean'] = 0x320E;
+ t['kiyeokcirclekorean'] = 0x3260;
+ t['kiyeokkorean'] = 0x3131;
+ t['kiyeokparenkorean'] = 0x3200;
+ t['kiyeoksioskorean'] = 0x3133;
+ t['kjecyrillic'] = 0x045C;
+ t['klinebelow'] = 0x1E35;
+ t['klsquare'] = 0x3398;
+ t['kmcubedsquare'] = 0x33A6;
+ t['kmonospace'] = 0xFF4B;
+ t['kmsquaredsquare'] = 0x33A2;
+ t['kohiragana'] = 0x3053;
+ t['kohmsquare'] = 0x33C0;
+ t['kokaithai'] = 0x0E01;
+ t['kokatakana'] = 0x30B3;
+ t['kokatakanahalfwidth'] = 0xFF7A;
+ t['kooposquare'] = 0x331E;
+ t['koppacyrillic'] = 0x0481;
+ t['koreanstandardsymbol'] = 0x327F;
+ t['koroniscmb'] = 0x0343;
+ t['kparen'] = 0x24A6;
+ t['kpasquare'] = 0x33AA;
+ t['ksicyrillic'] = 0x046F;
+ t['ktsquare'] = 0x33CF;
+ t['kturned'] = 0x029E;
+ t['kuhiragana'] = 0x304F;
+ t['kukatakana'] = 0x30AF;
+ t['kukatakanahalfwidth'] = 0xFF78;
+ t['kvsquare'] = 0x33B8;
+ t['kwsquare'] = 0x33BE;
+ t['l'] = 0x006C;
+ t['labengali'] = 0x09B2;
+ t['lacute'] = 0x013A;
+ t['ladeva'] = 0x0932;
+ t['lagujarati'] = 0x0AB2;
+ t['lagurmukhi'] = 0x0A32;
+ t['lakkhangyaothai'] = 0x0E45;
+ t['lamaleffinalarabic'] = 0xFEFC;
+ t['lamalefhamzaabovefinalarabic'] = 0xFEF8;
+ t['lamalefhamzaaboveisolatedarabic'] = 0xFEF7;
+ t['lamalefhamzabelowfinalarabic'] = 0xFEFA;
+ t['lamalefhamzabelowisolatedarabic'] = 0xFEF9;
+ t['lamalefisolatedarabic'] = 0xFEFB;
+ t['lamalefmaddaabovefinalarabic'] = 0xFEF6;
+ t['lamalefmaddaaboveisolatedarabic'] = 0xFEF5;
+ t['lamarabic'] = 0x0644;
+ t['lambda'] = 0x03BB;
+ t['lambdastroke'] = 0x019B;
+ t['lamed'] = 0x05DC;
+ t['lameddagesh'] = 0xFB3C;
+ t['lameddageshhebrew'] = 0xFB3C;
+ t['lamedhebrew'] = 0x05DC;
+ t['lamfinalarabic'] = 0xFEDE;
+ t['lamhahinitialarabic'] = 0xFCCA;
+ t['laminitialarabic'] = 0xFEDF;
+ t['lamjeeminitialarabic'] = 0xFCC9;
+ t['lamkhahinitialarabic'] = 0xFCCB;
+ t['lamlamhehisolatedarabic'] = 0xFDF2;
+ t['lammedialarabic'] = 0xFEE0;
+ t['lammeemhahinitialarabic'] = 0xFD88;
+ t['lammeeminitialarabic'] = 0xFCCC;
+ t['largecircle'] = 0x25EF;
+ t['lbar'] = 0x019A;
+ t['lbelt'] = 0x026C;
+ t['lbopomofo'] = 0x310C;
+ t['lcaron'] = 0x013E;
+ t['lcedilla'] = 0x013C;
+ t['lcircle'] = 0x24DB;
+ t['lcircumflexbelow'] = 0x1E3D;
+ t['lcommaaccent'] = 0x013C;
+ t['ldot'] = 0x0140;
+ t['ldotaccent'] = 0x0140;
+ t['ldotbelow'] = 0x1E37;
+ t['ldotbelowmacron'] = 0x1E39;
+ t['leftangleabovecmb'] = 0x031A;
+ t['lefttackbelowcmb'] = 0x0318;
+ t['less'] = 0x003C;
+ t['lessequal'] = 0x2264;
+ t['lessequalorgreater'] = 0x22DA;
+ t['lessmonospace'] = 0xFF1C;
+ t['lessorequivalent'] = 0x2272;
+ t['lessorgreater'] = 0x2276;
+ t['lessoverequal'] = 0x2266;
+ t['lesssmall'] = 0xFE64;
+ t['lezh'] = 0x026E;
+ t['lfblock'] = 0x258C;
+ t['lhookretroflex'] = 0x026D;
+ t['lira'] = 0x20A4;
+ t['liwnarmenian'] = 0x056C;
+ t['lj'] = 0x01C9;
+ t['ljecyrillic'] = 0x0459;
+ t['ll'] = 0xF6C0;
+ t['lladeva'] = 0x0933;
+ t['llagujarati'] = 0x0AB3;
+ t['llinebelow'] = 0x1E3B;
+ t['llladeva'] = 0x0934;
+ t['llvocalicbengali'] = 0x09E1;
+ t['llvocalicdeva'] = 0x0961;
+ t['llvocalicvowelsignbengali'] = 0x09E3;
+ t['llvocalicvowelsigndeva'] = 0x0963;
+ t['lmiddletilde'] = 0x026B;
+ t['lmonospace'] = 0xFF4C;
+ t['lmsquare'] = 0x33D0;
+ t['lochulathai'] = 0x0E2C;
+ t['logicaland'] = 0x2227;
+ t['logicalnot'] = 0x00AC;
+ t['logicalnotreversed'] = 0x2310;
+ t['logicalor'] = 0x2228;
+ t['lolingthai'] = 0x0E25;
+ t['longs'] = 0x017F;
+ t['lowlinecenterline'] = 0xFE4E;
+ t['lowlinecmb'] = 0x0332;
+ t['lowlinedashed'] = 0xFE4D;
+ t['lozenge'] = 0x25CA;
+ t['lparen'] = 0x24A7;
+ t['lslash'] = 0x0142;
+ t['lsquare'] = 0x2113;
+ t['lsuperior'] = 0xF6EE;
+ t['ltshade'] = 0x2591;
+ t['luthai'] = 0x0E26;
+ t['lvocalicbengali'] = 0x098C;
+ t['lvocalicdeva'] = 0x090C;
+ t['lvocalicvowelsignbengali'] = 0x09E2;
+ t['lvocalicvowelsigndeva'] = 0x0962;
+ t['lxsquare'] = 0x33D3;
+ t['m'] = 0x006D;
+ t['mabengali'] = 0x09AE;
+ t['macron'] = 0x00AF;
+ t['macronbelowcmb'] = 0x0331;
+ t['macroncmb'] = 0x0304;
+ t['macronlowmod'] = 0x02CD;
+ t['macronmonospace'] = 0xFFE3;
+ t['macute'] = 0x1E3F;
+ t['madeva'] = 0x092E;
+ t['magujarati'] = 0x0AAE;
+ t['magurmukhi'] = 0x0A2E;
+ t['mahapakhhebrew'] = 0x05A4;
+ t['mahapakhlefthebrew'] = 0x05A4;
+ t['mahiragana'] = 0x307E;
+ t['maichattawalowleftthai'] = 0xF895;
+ t['maichattawalowrightthai'] = 0xF894;
+ t['maichattawathai'] = 0x0E4B;
+ t['maichattawaupperleftthai'] = 0xF893;
+ t['maieklowleftthai'] = 0xF88C;
+ t['maieklowrightthai'] = 0xF88B;
+ t['maiekthai'] = 0x0E48;
+ t['maiekupperleftthai'] = 0xF88A;
+ t['maihanakatleftthai'] = 0xF884;
+ t['maihanakatthai'] = 0x0E31;
+ t['maitaikhuleftthai'] = 0xF889;
+ t['maitaikhuthai'] = 0x0E47;
+ t['maitholowleftthai'] = 0xF88F;
+ t['maitholowrightthai'] = 0xF88E;
+ t['maithothai'] = 0x0E49;
+ t['maithoupperleftthai'] = 0xF88D;
+ t['maitrilowleftthai'] = 0xF892;
+ t['maitrilowrightthai'] = 0xF891;
+ t['maitrithai'] = 0x0E4A;
+ t['maitriupperleftthai'] = 0xF890;
+ t['maiyamokthai'] = 0x0E46;
+ t['makatakana'] = 0x30DE;
+ t['makatakanahalfwidth'] = 0xFF8F;
+ t['male'] = 0x2642;
+ t['mansyonsquare'] = 0x3347;
+ t['maqafhebrew'] = 0x05BE;
+ t['mars'] = 0x2642;
+ t['masoracirclehebrew'] = 0x05AF;
+ t['masquare'] = 0x3383;
+ t['mbopomofo'] = 0x3107;
+ t['mbsquare'] = 0x33D4;
+ t['mcircle'] = 0x24DC;
+ t['mcubedsquare'] = 0x33A5;
+ t['mdotaccent'] = 0x1E41;
+ t['mdotbelow'] = 0x1E43;
+ t['meemarabic'] = 0x0645;
+ t['meemfinalarabic'] = 0xFEE2;
+ t['meeminitialarabic'] = 0xFEE3;
+ t['meemmedialarabic'] = 0xFEE4;
+ t['meemmeeminitialarabic'] = 0xFCD1;
+ t['meemmeemisolatedarabic'] = 0xFC48;
+ t['meetorusquare'] = 0x334D;
+ t['mehiragana'] = 0x3081;
+ t['meizierasquare'] = 0x337E;
+ t['mekatakana'] = 0x30E1;
+ t['mekatakanahalfwidth'] = 0xFF92;
+ t['mem'] = 0x05DE;
+ t['memdagesh'] = 0xFB3E;
+ t['memdageshhebrew'] = 0xFB3E;
+ t['memhebrew'] = 0x05DE;
+ t['menarmenian'] = 0x0574;
+ t['merkhahebrew'] = 0x05A5;
+ t['merkhakefulahebrew'] = 0x05A6;
+ t['merkhakefulalefthebrew'] = 0x05A6;
+ t['merkhalefthebrew'] = 0x05A5;
+ t['mhook'] = 0x0271;
+ t['mhzsquare'] = 0x3392;
+ t['middledotkatakanahalfwidth'] = 0xFF65;
+ t['middot'] = 0x00B7;
+ t['mieumacirclekorean'] = 0x3272;
+ t['mieumaparenkorean'] = 0x3212;
+ t['mieumcirclekorean'] = 0x3264;
+ t['mieumkorean'] = 0x3141;
+ t['mieumpansioskorean'] = 0x3170;
+ t['mieumparenkorean'] = 0x3204;
+ t['mieumpieupkorean'] = 0x316E;
+ t['mieumsioskorean'] = 0x316F;
+ t['mihiragana'] = 0x307F;
+ t['mikatakana'] = 0x30DF;
+ t['mikatakanahalfwidth'] = 0xFF90;
+ t['minus'] = 0x2212;
+ t['minusbelowcmb'] = 0x0320;
+ t['minuscircle'] = 0x2296;
+ t['minusmod'] = 0x02D7;
+ t['minusplus'] = 0x2213;
+ t['minute'] = 0x2032;
+ t['miribaarusquare'] = 0x334A;
+ t['mirisquare'] = 0x3349;
+ t['mlonglegturned'] = 0x0270;
+ t['mlsquare'] = 0x3396;
+ t['mmcubedsquare'] = 0x33A3;
+ t['mmonospace'] = 0xFF4D;
+ t['mmsquaredsquare'] = 0x339F;
+ t['mohiragana'] = 0x3082;
+ t['mohmsquare'] = 0x33C1;
+ t['mokatakana'] = 0x30E2;
+ t['mokatakanahalfwidth'] = 0xFF93;
+ t['molsquare'] = 0x33D6;
+ t['momathai'] = 0x0E21;
+ t['moverssquare'] = 0x33A7;
+ t['moverssquaredsquare'] = 0x33A8;
+ t['mparen'] = 0x24A8;
+ t['mpasquare'] = 0x33AB;
+ t['mssquare'] = 0x33B3;
+ t['msuperior'] = 0xF6EF;
+ t['mturned'] = 0x026F;
+ t['mu'] = 0x00B5;
+ t['mu1'] = 0x00B5;
+ t['muasquare'] = 0x3382;
+ t['muchgreater'] = 0x226B;
+ t['muchless'] = 0x226A;
+ t['mufsquare'] = 0x338C;
+ t['mugreek'] = 0x03BC;
+ t['mugsquare'] = 0x338D;
+ t['muhiragana'] = 0x3080;
+ t['mukatakana'] = 0x30E0;
+ t['mukatakanahalfwidth'] = 0xFF91;
+ t['mulsquare'] = 0x3395;
+ t['multiply'] = 0x00D7;
+ t['mumsquare'] = 0x339B;
+ t['munahhebrew'] = 0x05A3;
+ t['munahlefthebrew'] = 0x05A3;
+ t['musicalnote'] = 0x266A;
+ t['musicalnotedbl'] = 0x266B;
+ t['musicflatsign'] = 0x266D;
+ t['musicsharpsign'] = 0x266F;
+ t['mussquare'] = 0x33B2;
+ t['muvsquare'] = 0x33B6;
+ t['muwsquare'] = 0x33BC;
+ t['mvmegasquare'] = 0x33B9;
+ t['mvsquare'] = 0x33B7;
+ t['mwmegasquare'] = 0x33BF;
+ t['mwsquare'] = 0x33BD;
+ t['n'] = 0x006E;
+ t['nabengali'] = 0x09A8;
+ t['nabla'] = 0x2207;
+ t['nacute'] = 0x0144;
+ t['nadeva'] = 0x0928;
+ t['nagujarati'] = 0x0AA8;
+ t['nagurmukhi'] = 0x0A28;
+ t['nahiragana'] = 0x306A;
+ t['nakatakana'] = 0x30CA;
+ t['nakatakanahalfwidth'] = 0xFF85;
+ t['napostrophe'] = 0x0149;
+ t['nasquare'] = 0x3381;
+ t['nbopomofo'] = 0x310B;
+ t['nbspace'] = 0x00A0;
+ t['ncaron'] = 0x0148;
+ t['ncedilla'] = 0x0146;
+ t['ncircle'] = 0x24DD;
+ t['ncircumflexbelow'] = 0x1E4B;
+ t['ncommaaccent'] = 0x0146;
+ t['ndotaccent'] = 0x1E45;
+ t['ndotbelow'] = 0x1E47;
+ t['nehiragana'] = 0x306D;
+ t['nekatakana'] = 0x30CD;
+ t['nekatakanahalfwidth'] = 0xFF88;
+ t['newsheqelsign'] = 0x20AA;
+ t['nfsquare'] = 0x338B;
+ t['ngabengali'] = 0x0999;
+ t['ngadeva'] = 0x0919;
+ t['ngagujarati'] = 0x0A99;
+ t['ngagurmukhi'] = 0x0A19;
+ t['ngonguthai'] = 0x0E07;
+ t['nhiragana'] = 0x3093;
+ t['nhookleft'] = 0x0272;
+ t['nhookretroflex'] = 0x0273;
+ t['nieunacirclekorean'] = 0x326F;
+ t['nieunaparenkorean'] = 0x320F;
+ t['nieuncieuckorean'] = 0x3135;
+ t['nieuncirclekorean'] = 0x3261;
+ t['nieunhieuhkorean'] = 0x3136;
+ t['nieunkorean'] = 0x3134;
+ t['nieunpansioskorean'] = 0x3168;
+ t['nieunparenkorean'] = 0x3201;
+ t['nieunsioskorean'] = 0x3167;
+ t['nieuntikeutkorean'] = 0x3166;
+ t['nihiragana'] = 0x306B;
+ t['nikatakana'] = 0x30CB;
+ t['nikatakanahalfwidth'] = 0xFF86;
+ t['nikhahitleftthai'] = 0xF899;
+ t['nikhahitthai'] = 0x0E4D;
+ t['nine'] = 0x0039;
+ t['ninearabic'] = 0x0669;
+ t['ninebengali'] = 0x09EF;
+ t['ninecircle'] = 0x2468;
+ t['ninecircleinversesansserif'] = 0x2792;
+ t['ninedeva'] = 0x096F;
+ t['ninegujarati'] = 0x0AEF;
+ t['ninegurmukhi'] = 0x0A6F;
+ t['ninehackarabic'] = 0x0669;
+ t['ninehangzhou'] = 0x3029;
+ t['nineideographicparen'] = 0x3228;
+ t['nineinferior'] = 0x2089;
+ t['ninemonospace'] = 0xFF19;
+ t['nineoldstyle'] = 0xF739;
+ t['nineparen'] = 0x247C;
+ t['nineperiod'] = 0x2490;
+ t['ninepersian'] = 0x06F9;
+ t['nineroman'] = 0x2178;
+ t['ninesuperior'] = 0x2079;
+ t['nineteencircle'] = 0x2472;
+ t['nineteenparen'] = 0x2486;
+ t['nineteenperiod'] = 0x249A;
+ t['ninethai'] = 0x0E59;
+ t['nj'] = 0x01CC;
+ t['njecyrillic'] = 0x045A;
+ t['nkatakana'] = 0x30F3;
+ t['nkatakanahalfwidth'] = 0xFF9D;
+ t['nlegrightlong'] = 0x019E;
+ t['nlinebelow'] = 0x1E49;
+ t['nmonospace'] = 0xFF4E;
+ t['nmsquare'] = 0x339A;
+ t['nnabengali'] = 0x09A3;
+ t['nnadeva'] = 0x0923;
+ t['nnagujarati'] = 0x0AA3;
+ t['nnagurmukhi'] = 0x0A23;
+ t['nnnadeva'] = 0x0929;
+ t['nohiragana'] = 0x306E;
+ t['nokatakana'] = 0x30CE;
+ t['nokatakanahalfwidth'] = 0xFF89;
+ t['nonbreakingspace'] = 0x00A0;
+ t['nonenthai'] = 0x0E13;
+ t['nonuthai'] = 0x0E19;
+ t['noonarabic'] = 0x0646;
+ t['noonfinalarabic'] = 0xFEE6;
+ t['noonghunnaarabic'] = 0x06BA;
+ t['noonghunnafinalarabic'] = 0xFB9F;
+ t['nooninitialarabic'] = 0xFEE7;
+ t['noonjeeminitialarabic'] = 0xFCD2;
+ t['noonjeemisolatedarabic'] = 0xFC4B;
+ t['noonmedialarabic'] = 0xFEE8;
+ t['noonmeeminitialarabic'] = 0xFCD5;
+ t['noonmeemisolatedarabic'] = 0xFC4E;
+ t['noonnoonfinalarabic'] = 0xFC8D;
+ t['notcontains'] = 0x220C;
+ t['notelement'] = 0x2209;
+ t['notelementof'] = 0x2209;
+ t['notequal'] = 0x2260;
+ t['notgreater'] = 0x226F;
+ t['notgreaternorequal'] = 0x2271;
+ t['notgreaternorless'] = 0x2279;
+ t['notidentical'] = 0x2262;
+ t['notless'] = 0x226E;
+ t['notlessnorequal'] = 0x2270;
+ t['notparallel'] = 0x2226;
+ t['notprecedes'] = 0x2280;
+ t['notsubset'] = 0x2284;
+ t['notsucceeds'] = 0x2281;
+ t['notsuperset'] = 0x2285;
+ t['nowarmenian'] = 0x0576;
+ t['nparen'] = 0x24A9;
+ t['nssquare'] = 0x33B1;
+ t['nsuperior'] = 0x207F;
+ t['ntilde'] = 0x00F1;
+ t['nu'] = 0x03BD;
+ t['nuhiragana'] = 0x306C;
+ t['nukatakana'] = 0x30CC;
+ t['nukatakanahalfwidth'] = 0xFF87;
+ t['nuktabengali'] = 0x09BC;
+ t['nuktadeva'] = 0x093C;
+ t['nuktagujarati'] = 0x0ABC;
+ t['nuktagurmukhi'] = 0x0A3C;
+ t['numbersign'] = 0x0023;
+ t['numbersignmonospace'] = 0xFF03;
+ t['numbersignsmall'] = 0xFE5F;
+ t['numeralsigngreek'] = 0x0374;
+ t['numeralsignlowergreek'] = 0x0375;
+ t['numero'] = 0x2116;
+ t['nun'] = 0x05E0;
+ t['nundagesh'] = 0xFB40;
+ t['nundageshhebrew'] = 0xFB40;
+ t['nunhebrew'] = 0x05E0;
+ t['nvsquare'] = 0x33B5;
+ t['nwsquare'] = 0x33BB;
+ t['nyabengali'] = 0x099E;
+ t['nyadeva'] = 0x091E;
+ t['nyagujarati'] = 0x0A9E;
+ t['nyagurmukhi'] = 0x0A1E;
+ t['o'] = 0x006F;
+ t['oacute'] = 0x00F3;
+ t['oangthai'] = 0x0E2D;
+ t['obarred'] = 0x0275;
+ t['obarredcyrillic'] = 0x04E9;
+ t['obarreddieresiscyrillic'] = 0x04EB;
+ t['obengali'] = 0x0993;
+ t['obopomofo'] = 0x311B;
+ t['obreve'] = 0x014F;
+ t['ocandradeva'] = 0x0911;
+ t['ocandragujarati'] = 0x0A91;
+ t['ocandravowelsigndeva'] = 0x0949;
+ t['ocandravowelsigngujarati'] = 0x0AC9;
+ t['ocaron'] = 0x01D2;
+ t['ocircle'] = 0x24DE;
+ t['ocircumflex'] = 0x00F4;
+ t['ocircumflexacute'] = 0x1ED1;
+ t['ocircumflexdotbelow'] = 0x1ED9;
+ t['ocircumflexgrave'] = 0x1ED3;
+ t['ocircumflexhookabove'] = 0x1ED5;
+ t['ocircumflextilde'] = 0x1ED7;
+ t['ocyrillic'] = 0x043E;
+ t['odblacute'] = 0x0151;
+ t['odblgrave'] = 0x020D;
+ t['odeva'] = 0x0913;
+ t['odieresis'] = 0x00F6;
+ t['odieresiscyrillic'] = 0x04E7;
+ t['odotbelow'] = 0x1ECD;
+ t['oe'] = 0x0153;
+ t['oekorean'] = 0x315A;
+ t['ogonek'] = 0x02DB;
+ t['ogonekcmb'] = 0x0328;
+ t['ograve'] = 0x00F2;
+ t['ogujarati'] = 0x0A93;
+ t['oharmenian'] = 0x0585;
+ t['ohiragana'] = 0x304A;
+ t['ohookabove'] = 0x1ECF;
+ t['ohorn'] = 0x01A1;
+ t['ohornacute'] = 0x1EDB;
+ t['ohorndotbelow'] = 0x1EE3;
+ t['ohorngrave'] = 0x1EDD;
+ t['ohornhookabove'] = 0x1EDF;
+ t['ohorntilde'] = 0x1EE1;
+ t['ohungarumlaut'] = 0x0151;
+ t['oi'] = 0x01A3;
+ t['oinvertedbreve'] = 0x020F;
+ t['okatakana'] = 0x30AA;
+ t['okatakanahalfwidth'] = 0xFF75;
+ t['okorean'] = 0x3157;
+ t['olehebrew'] = 0x05AB;
+ t['omacron'] = 0x014D;
+ t['omacronacute'] = 0x1E53;
+ t['omacrongrave'] = 0x1E51;
+ t['omdeva'] = 0x0950;
+ t['omega'] = 0x03C9;
+ t['omega1'] = 0x03D6;
+ t['omegacyrillic'] = 0x0461;
+ t['omegalatinclosed'] = 0x0277;
+ t['omegaroundcyrillic'] = 0x047B;
+ t['omegatitlocyrillic'] = 0x047D;
+ t['omegatonos'] = 0x03CE;
+ t['omgujarati'] = 0x0AD0;
+ t['omicron'] = 0x03BF;
+ t['omicrontonos'] = 0x03CC;
+ t['omonospace'] = 0xFF4F;
+ t['one'] = 0x0031;
+ t['onearabic'] = 0x0661;
+ t['onebengali'] = 0x09E7;
+ t['onecircle'] = 0x2460;
+ t['onecircleinversesansserif'] = 0x278A;
+ t['onedeva'] = 0x0967;
+ t['onedotenleader'] = 0x2024;
+ t['oneeighth'] = 0x215B;
+ t['onefitted'] = 0xF6DC;
+ t['onegujarati'] = 0x0AE7;
+ t['onegurmukhi'] = 0x0A67;
+ t['onehackarabic'] = 0x0661;
+ t['onehalf'] = 0x00BD;
+ t['onehangzhou'] = 0x3021;
+ t['oneideographicparen'] = 0x3220;
+ t['oneinferior'] = 0x2081;
+ t['onemonospace'] = 0xFF11;
+ t['onenumeratorbengali'] = 0x09F4;
+ t['oneoldstyle'] = 0xF731;
+ t['oneparen'] = 0x2474;
+ t['oneperiod'] = 0x2488;
+ t['onepersian'] = 0x06F1;
+ t['onequarter'] = 0x00BC;
+ t['oneroman'] = 0x2170;
+ t['onesuperior'] = 0x00B9;
+ t['onethai'] = 0x0E51;
+ t['onethird'] = 0x2153;
+ t['oogonek'] = 0x01EB;
+ t['oogonekmacron'] = 0x01ED;
+ t['oogurmukhi'] = 0x0A13;
+ t['oomatragurmukhi'] = 0x0A4B;
+ t['oopen'] = 0x0254;
+ t['oparen'] = 0x24AA;
+ t['openbullet'] = 0x25E6;
+ t['option'] = 0x2325;
+ t['ordfeminine'] = 0x00AA;
+ t['ordmasculine'] = 0x00BA;
+ t['orthogonal'] = 0x221F;
+ t['oshortdeva'] = 0x0912;
+ t['oshortvowelsigndeva'] = 0x094A;
+ t['oslash'] = 0x00F8;
+ t['oslashacute'] = 0x01FF;
+ t['osmallhiragana'] = 0x3049;
+ t['osmallkatakana'] = 0x30A9;
+ t['osmallkatakanahalfwidth'] = 0xFF6B;
+ t['ostrokeacute'] = 0x01FF;
+ t['osuperior'] = 0xF6F0;
+ t['otcyrillic'] = 0x047F;
+ t['otilde'] = 0x00F5;
+ t['otildeacute'] = 0x1E4D;
+ t['otildedieresis'] = 0x1E4F;
+ t['oubopomofo'] = 0x3121;
+ t['overline'] = 0x203E;
+ t['overlinecenterline'] = 0xFE4A;
+ t['overlinecmb'] = 0x0305;
+ t['overlinedashed'] = 0xFE49;
+ t['overlinedblwavy'] = 0xFE4C;
+ t['overlinewavy'] = 0xFE4B;
+ t['overscore'] = 0x00AF;
+ t['ovowelsignbengali'] = 0x09CB;
+ t['ovowelsigndeva'] = 0x094B;
+ t['ovowelsigngujarati'] = 0x0ACB;
+ t['p'] = 0x0070;
+ t['paampssquare'] = 0x3380;
+ t['paasentosquare'] = 0x332B;
+ t['pabengali'] = 0x09AA;
+ t['pacute'] = 0x1E55;
+ t['padeva'] = 0x092A;
+ t['pagedown'] = 0x21DF;
+ t['pageup'] = 0x21DE;
+ t['pagujarati'] = 0x0AAA;
+ t['pagurmukhi'] = 0x0A2A;
+ t['pahiragana'] = 0x3071;
+ t['paiyannoithai'] = 0x0E2F;
+ t['pakatakana'] = 0x30D1;
+ t['palatalizationcyrilliccmb'] = 0x0484;
+ t['palochkacyrillic'] = 0x04C0;
+ t['pansioskorean'] = 0x317F;
+ t['paragraph'] = 0x00B6;
+ t['parallel'] = 0x2225;
+ t['parenleft'] = 0x0028;
+ t['parenleftaltonearabic'] = 0xFD3E;
+ t['parenleftbt'] = 0xF8ED;
+ t['parenleftex'] = 0xF8EC;
+ t['parenleftinferior'] = 0x208D;
+ t['parenleftmonospace'] = 0xFF08;
+ t['parenleftsmall'] = 0xFE59;
+ t['parenleftsuperior'] = 0x207D;
+ t['parenlefttp'] = 0xF8EB;
+ t['parenleftvertical'] = 0xFE35;
+ t['parenright'] = 0x0029;
+ t['parenrightaltonearabic'] = 0xFD3F;
+ t['parenrightbt'] = 0xF8F8;
+ t['parenrightex'] = 0xF8F7;
+ t['parenrightinferior'] = 0x208E;
+ t['parenrightmonospace'] = 0xFF09;
+ t['parenrightsmall'] = 0xFE5A;
+ t['parenrightsuperior'] = 0x207E;
+ t['parenrighttp'] = 0xF8F6;
+ t['parenrightvertical'] = 0xFE36;
+ t['partialdiff'] = 0x2202;
+ t['paseqhebrew'] = 0x05C0;
+ t['pashtahebrew'] = 0x0599;
+ t['pasquare'] = 0x33A9;
+ t['patah'] = 0x05B7;
+ t['patah11'] = 0x05B7;
+ t['patah1d'] = 0x05B7;
+ t['patah2a'] = 0x05B7;
+ t['patahhebrew'] = 0x05B7;
+ t['patahnarrowhebrew'] = 0x05B7;
+ t['patahquarterhebrew'] = 0x05B7;
+ t['patahwidehebrew'] = 0x05B7;
+ t['pazerhebrew'] = 0x05A1;
+ t['pbopomofo'] = 0x3106;
+ t['pcircle'] = 0x24DF;
+ t['pdotaccent'] = 0x1E57;
+ t['pe'] = 0x05E4;
+ t['pecyrillic'] = 0x043F;
+ t['pedagesh'] = 0xFB44;
+ t['pedageshhebrew'] = 0xFB44;
+ t['peezisquare'] = 0x333B;
+ t['pefinaldageshhebrew'] = 0xFB43;
+ t['peharabic'] = 0x067E;
+ t['peharmenian'] = 0x057A;
+ t['pehebrew'] = 0x05E4;
+ t['pehfinalarabic'] = 0xFB57;
+ t['pehinitialarabic'] = 0xFB58;
+ t['pehiragana'] = 0x307A;
+ t['pehmedialarabic'] = 0xFB59;
+ t['pekatakana'] = 0x30DA;
+ t['pemiddlehookcyrillic'] = 0x04A7;
+ t['perafehebrew'] = 0xFB4E;
+ t['percent'] = 0x0025;
+ t['percentarabic'] = 0x066A;
+ t['percentmonospace'] = 0xFF05;
+ t['percentsmall'] = 0xFE6A;
+ t['period'] = 0x002E;
+ t['periodarmenian'] = 0x0589;
+ t['periodcentered'] = 0x00B7;
+ t['periodhalfwidth'] = 0xFF61;
+ t['periodinferior'] = 0xF6E7;
+ t['periodmonospace'] = 0xFF0E;
+ t['periodsmall'] = 0xFE52;
+ t['periodsuperior'] = 0xF6E8;
+ t['perispomenigreekcmb'] = 0x0342;
+ t['perpendicular'] = 0x22A5;
+ t['perthousand'] = 0x2030;
+ t['peseta'] = 0x20A7;
+ t['pfsquare'] = 0x338A;
+ t['phabengali'] = 0x09AB;
+ t['phadeva'] = 0x092B;
+ t['phagujarati'] = 0x0AAB;
+ t['phagurmukhi'] = 0x0A2B;
+ t['phi'] = 0x03C6;
+ t['phi1'] = 0x03D5;
+ t['phieuphacirclekorean'] = 0x327A;
+ t['phieuphaparenkorean'] = 0x321A;
+ t['phieuphcirclekorean'] = 0x326C;
+ t['phieuphkorean'] = 0x314D;
+ t['phieuphparenkorean'] = 0x320C;
+ t['philatin'] = 0x0278;
+ t['phinthuthai'] = 0x0E3A;
+ t['phisymbolgreek'] = 0x03D5;
+ t['phook'] = 0x01A5;
+ t['phophanthai'] = 0x0E1E;
+ t['phophungthai'] = 0x0E1C;
+ t['phosamphaothai'] = 0x0E20;
+ t['pi'] = 0x03C0;
+ t['pieupacirclekorean'] = 0x3273;
+ t['pieupaparenkorean'] = 0x3213;
+ t['pieupcieuckorean'] = 0x3176;
+ t['pieupcirclekorean'] = 0x3265;
+ t['pieupkiyeokkorean'] = 0x3172;
+ t['pieupkorean'] = 0x3142;
+ t['pieupparenkorean'] = 0x3205;
+ t['pieupsioskiyeokkorean'] = 0x3174;
+ t['pieupsioskorean'] = 0x3144;
+ t['pieupsiostikeutkorean'] = 0x3175;
+ t['pieupthieuthkorean'] = 0x3177;
+ t['pieuptikeutkorean'] = 0x3173;
+ t['pihiragana'] = 0x3074;
+ t['pikatakana'] = 0x30D4;
+ t['pisymbolgreek'] = 0x03D6;
+ t['piwrarmenian'] = 0x0583;
+ t['plus'] = 0x002B;
+ t['plusbelowcmb'] = 0x031F;
+ t['pluscircle'] = 0x2295;
+ t['plusminus'] = 0x00B1;
+ t['plusmod'] = 0x02D6;
+ t['plusmonospace'] = 0xFF0B;
+ t['plussmall'] = 0xFE62;
+ t['plussuperior'] = 0x207A;
+ t['pmonospace'] = 0xFF50;
+ t['pmsquare'] = 0x33D8;
+ t['pohiragana'] = 0x307D;
+ t['pointingindexdownwhite'] = 0x261F;
+ t['pointingindexleftwhite'] = 0x261C;
+ t['pointingindexrightwhite'] = 0x261E;
+ t['pointingindexupwhite'] = 0x261D;
+ t['pokatakana'] = 0x30DD;
+ t['poplathai'] = 0x0E1B;
+ t['postalmark'] = 0x3012;
+ t['postalmarkface'] = 0x3020;
+ t['pparen'] = 0x24AB;
+ t['precedes'] = 0x227A;
+ t['prescription'] = 0x211E;
+ t['primemod'] = 0x02B9;
+ t['primereversed'] = 0x2035;
+ t['product'] = 0x220F;
+ t['projective'] = 0x2305;
+ t['prolongedkana'] = 0x30FC;
+ t['propellor'] = 0x2318;
+ t['propersubset'] = 0x2282;
+ t['propersuperset'] = 0x2283;
+ t['proportion'] = 0x2237;
+ t['proportional'] = 0x221D;
+ t['psi'] = 0x03C8;
+ t['psicyrillic'] = 0x0471;
+ t['psilipneumatacyrilliccmb'] = 0x0486;
+ t['pssquare'] = 0x33B0;
+ t['puhiragana'] = 0x3077;
+ t['pukatakana'] = 0x30D7;
+ t['pvsquare'] = 0x33B4;
+ t['pwsquare'] = 0x33BA;
+ t['q'] = 0x0071;
+ t['qadeva'] = 0x0958;
+ t['qadmahebrew'] = 0x05A8;
+ t['qafarabic'] = 0x0642;
+ t['qaffinalarabic'] = 0xFED6;
+ t['qafinitialarabic'] = 0xFED7;
+ t['qafmedialarabic'] = 0xFED8;
+ t['qamats'] = 0x05B8;
+ t['qamats10'] = 0x05B8;
+ t['qamats1a'] = 0x05B8;
+ t['qamats1c'] = 0x05B8;
+ t['qamats27'] = 0x05B8;
+ t['qamats29'] = 0x05B8;
+ t['qamats33'] = 0x05B8;
+ t['qamatsde'] = 0x05B8;
+ t['qamatshebrew'] = 0x05B8;
+ t['qamatsnarrowhebrew'] = 0x05B8;
+ t['qamatsqatanhebrew'] = 0x05B8;
+ t['qamatsqatannarrowhebrew'] = 0x05B8;
+ t['qamatsqatanquarterhebrew'] = 0x05B8;
+ t['qamatsqatanwidehebrew'] = 0x05B8;
+ t['qamatsquarterhebrew'] = 0x05B8;
+ t['qamatswidehebrew'] = 0x05B8;
+ t['qarneyparahebrew'] = 0x059F;
+ t['qbopomofo'] = 0x3111;
+ t['qcircle'] = 0x24E0;
+ t['qhook'] = 0x02A0;
+ t['qmonospace'] = 0xFF51;
+ t['qof'] = 0x05E7;
+ t['qofdagesh'] = 0xFB47;
+ t['qofdageshhebrew'] = 0xFB47;
+ t['qofhebrew'] = 0x05E7;
+ t['qparen'] = 0x24AC;
+ t['quarternote'] = 0x2669;
+ t['qubuts'] = 0x05BB;
+ t['qubuts18'] = 0x05BB;
+ t['qubuts25'] = 0x05BB;
+ t['qubuts31'] = 0x05BB;
+ t['qubutshebrew'] = 0x05BB;
+ t['qubutsnarrowhebrew'] = 0x05BB;
+ t['qubutsquarterhebrew'] = 0x05BB;
+ t['qubutswidehebrew'] = 0x05BB;
+ t['question'] = 0x003F;
+ t['questionarabic'] = 0x061F;
+ t['questionarmenian'] = 0x055E;
+ t['questiondown'] = 0x00BF;
+ t['questiondownsmall'] = 0xF7BF;
+ t['questiongreek'] = 0x037E;
+ t['questionmonospace'] = 0xFF1F;
+ t['questionsmall'] = 0xF73F;
+ t['quotedbl'] = 0x0022;
+ t['quotedblbase'] = 0x201E;
+ t['quotedblleft'] = 0x201C;
+ t['quotedblmonospace'] = 0xFF02;
+ t['quotedblprime'] = 0x301E;
+ t['quotedblprimereversed'] = 0x301D;
+ t['quotedblright'] = 0x201D;
+ t['quoteleft'] = 0x2018;
+ t['quoteleftreversed'] = 0x201B;
+ t['quotereversed'] = 0x201B;
+ t['quoteright'] = 0x2019;
+ t['quoterightn'] = 0x0149;
+ t['quotesinglbase'] = 0x201A;
+ t['quotesingle'] = 0x0027;
+ t['quotesinglemonospace'] = 0xFF07;
+ t['r'] = 0x0072;
+ t['raarmenian'] = 0x057C;
+ t['rabengali'] = 0x09B0;
+ t['racute'] = 0x0155;
+ t['radeva'] = 0x0930;
+ t['radical'] = 0x221A;
+ t['radicalex'] = 0xF8E5;
+ t['radoverssquare'] = 0x33AE;
+ t['radoverssquaredsquare'] = 0x33AF;
+ t['radsquare'] = 0x33AD;
+ t['rafe'] = 0x05BF;
+ t['rafehebrew'] = 0x05BF;
+ t['ragujarati'] = 0x0AB0;
+ t['ragurmukhi'] = 0x0A30;
+ t['rahiragana'] = 0x3089;
+ t['rakatakana'] = 0x30E9;
+ t['rakatakanahalfwidth'] = 0xFF97;
+ t['ralowerdiagonalbengali'] = 0x09F1;
+ t['ramiddlediagonalbengali'] = 0x09F0;
+ t['ramshorn'] = 0x0264;
+ t['ratio'] = 0x2236;
+ t['rbopomofo'] = 0x3116;
+ t['rcaron'] = 0x0159;
+ t['rcedilla'] = 0x0157;
+ t['rcircle'] = 0x24E1;
+ t['rcommaaccent'] = 0x0157;
+ t['rdblgrave'] = 0x0211;
+ t['rdotaccent'] = 0x1E59;
+ t['rdotbelow'] = 0x1E5B;
+ t['rdotbelowmacron'] = 0x1E5D;
+ t['referencemark'] = 0x203B;
+ t['reflexsubset'] = 0x2286;
+ t['reflexsuperset'] = 0x2287;
+ t['registered'] = 0x00AE;
+ t['registersans'] = 0xF8E8;
+ t['registerserif'] = 0xF6DA;
+ t['reharabic'] = 0x0631;
+ t['reharmenian'] = 0x0580;
+ t['rehfinalarabic'] = 0xFEAE;
+ t['rehiragana'] = 0x308C;
+ t['rekatakana'] = 0x30EC;
+ t['rekatakanahalfwidth'] = 0xFF9A;
+ t['resh'] = 0x05E8;
+ t['reshdageshhebrew'] = 0xFB48;
+ t['reshhebrew'] = 0x05E8;
+ t['reversedtilde'] = 0x223D;
+ t['reviahebrew'] = 0x0597;
+ t['reviamugrashhebrew'] = 0x0597;
+ t['revlogicalnot'] = 0x2310;
+ t['rfishhook'] = 0x027E;
+ t['rfishhookreversed'] = 0x027F;
+ t['rhabengali'] = 0x09DD;
+ t['rhadeva'] = 0x095D;
+ t['rho'] = 0x03C1;
+ t['rhook'] = 0x027D;
+ t['rhookturned'] = 0x027B;
+ t['rhookturnedsuperior'] = 0x02B5;
+ t['rhosymbolgreek'] = 0x03F1;
+ t['rhotichookmod'] = 0x02DE;
+ t['rieulacirclekorean'] = 0x3271;
+ t['rieulaparenkorean'] = 0x3211;
+ t['rieulcirclekorean'] = 0x3263;
+ t['rieulhieuhkorean'] = 0x3140;
+ t['rieulkiyeokkorean'] = 0x313A;
+ t['rieulkiyeoksioskorean'] = 0x3169;
+ t['rieulkorean'] = 0x3139;
+ t['rieulmieumkorean'] = 0x313B;
+ t['rieulpansioskorean'] = 0x316C;
+ t['rieulparenkorean'] = 0x3203;
+ t['rieulphieuphkorean'] = 0x313F;
+ t['rieulpieupkorean'] = 0x313C;
+ t['rieulpieupsioskorean'] = 0x316B;
+ t['rieulsioskorean'] = 0x313D;
+ t['rieulthieuthkorean'] = 0x313E;
+ t['rieultikeutkorean'] = 0x316A;
+ t['rieulyeorinhieuhkorean'] = 0x316D;
+ t['rightangle'] = 0x221F;
+ t['righttackbelowcmb'] = 0x0319;
+ t['righttriangle'] = 0x22BF;
+ t['rihiragana'] = 0x308A;
+ t['rikatakana'] = 0x30EA;
+ t['rikatakanahalfwidth'] = 0xFF98;
+ t['ring'] = 0x02DA;
+ t['ringbelowcmb'] = 0x0325;
+ t['ringcmb'] = 0x030A;
+ t['ringhalfleft'] = 0x02BF;
+ t['ringhalfleftarmenian'] = 0x0559;
+ t['ringhalfleftbelowcmb'] = 0x031C;
+ t['ringhalfleftcentered'] = 0x02D3;
+ t['ringhalfright'] = 0x02BE;
+ t['ringhalfrightbelowcmb'] = 0x0339;
+ t['ringhalfrightcentered'] = 0x02D2;
+ t['rinvertedbreve'] = 0x0213;
+ t['rittorusquare'] = 0x3351;
+ t['rlinebelow'] = 0x1E5F;
+ t['rlongleg'] = 0x027C;
+ t['rlonglegturned'] = 0x027A;
+ t['rmonospace'] = 0xFF52;
+ t['rohiragana'] = 0x308D;
+ t['rokatakana'] = 0x30ED;
+ t['rokatakanahalfwidth'] = 0xFF9B;
+ t['roruathai'] = 0x0E23;
+ t['rparen'] = 0x24AD;
+ t['rrabengali'] = 0x09DC;
+ t['rradeva'] = 0x0931;
+ t['rragurmukhi'] = 0x0A5C;
+ t['rreharabic'] = 0x0691;
+ t['rrehfinalarabic'] = 0xFB8D;
+ t['rrvocalicbengali'] = 0x09E0;
+ t['rrvocalicdeva'] = 0x0960;
+ t['rrvocalicgujarati'] = 0x0AE0;
+ t['rrvocalicvowelsignbengali'] = 0x09C4;
+ t['rrvocalicvowelsigndeva'] = 0x0944;
+ t['rrvocalicvowelsigngujarati'] = 0x0AC4;
+ t['rsuperior'] = 0xF6F1;
+ t['rtblock'] = 0x2590;
+ t['rturned'] = 0x0279;
+ t['rturnedsuperior'] = 0x02B4;
+ t['ruhiragana'] = 0x308B;
+ t['rukatakana'] = 0x30EB;
+ t['rukatakanahalfwidth'] = 0xFF99;
+ t['rupeemarkbengali'] = 0x09F2;
+ t['rupeesignbengali'] = 0x09F3;
+ t['rupiah'] = 0xF6DD;
+ t['ruthai'] = 0x0E24;
+ t['rvocalicbengali'] = 0x098B;
+ t['rvocalicdeva'] = 0x090B;
+ t['rvocalicgujarati'] = 0x0A8B;
+ t['rvocalicvowelsignbengali'] = 0x09C3;
+ t['rvocalicvowelsigndeva'] = 0x0943;
+ t['rvocalicvowelsigngujarati'] = 0x0AC3;
+ t['s'] = 0x0073;
+ t['sabengali'] = 0x09B8;
+ t['sacute'] = 0x015B;
+ t['sacutedotaccent'] = 0x1E65;
+ t['sadarabic'] = 0x0635;
+ t['sadeva'] = 0x0938;
+ t['sadfinalarabic'] = 0xFEBA;
+ t['sadinitialarabic'] = 0xFEBB;
+ t['sadmedialarabic'] = 0xFEBC;
+ t['sagujarati'] = 0x0AB8;
+ t['sagurmukhi'] = 0x0A38;
+ t['sahiragana'] = 0x3055;
+ t['sakatakana'] = 0x30B5;
+ t['sakatakanahalfwidth'] = 0xFF7B;
+ t['sallallahoualayhewasallamarabic'] = 0xFDFA;
+ t['samekh'] = 0x05E1;
+ t['samekhdagesh'] = 0xFB41;
+ t['samekhdageshhebrew'] = 0xFB41;
+ t['samekhhebrew'] = 0x05E1;
+ t['saraaathai'] = 0x0E32;
+ t['saraaethai'] = 0x0E41;
+ t['saraaimaimalaithai'] = 0x0E44;
+ t['saraaimaimuanthai'] = 0x0E43;
+ t['saraamthai'] = 0x0E33;
+ t['saraathai'] = 0x0E30;
+ t['saraethai'] = 0x0E40;
+ t['saraiileftthai'] = 0xF886;
+ t['saraiithai'] = 0x0E35;
+ t['saraileftthai'] = 0xF885;
+ t['saraithai'] = 0x0E34;
+ t['saraothai'] = 0x0E42;
+ t['saraueeleftthai'] = 0xF888;
+ t['saraueethai'] = 0x0E37;
+ t['saraueleftthai'] = 0xF887;
+ t['sarauethai'] = 0x0E36;
+ t['sarauthai'] = 0x0E38;
+ t['sarauuthai'] = 0x0E39;
+ t['sbopomofo'] = 0x3119;
+ t['scaron'] = 0x0161;
+ t['scarondotaccent'] = 0x1E67;
+ t['scedilla'] = 0x015F;
+ t['schwa'] = 0x0259;
+ t['schwacyrillic'] = 0x04D9;
+ t['schwadieresiscyrillic'] = 0x04DB;
+ t['schwahook'] = 0x025A;
+ t['scircle'] = 0x24E2;
+ t['scircumflex'] = 0x015D;
+ t['scommaaccent'] = 0x0219;
+ t['sdotaccent'] = 0x1E61;
+ t['sdotbelow'] = 0x1E63;
+ t['sdotbelowdotaccent'] = 0x1E69;
+ t['seagullbelowcmb'] = 0x033C;
+ t['second'] = 0x2033;
+ t['secondtonechinese'] = 0x02CA;
+ t['section'] = 0x00A7;
+ t['seenarabic'] = 0x0633;
+ t['seenfinalarabic'] = 0xFEB2;
+ t['seeninitialarabic'] = 0xFEB3;
+ t['seenmedialarabic'] = 0xFEB4;
+ t['segol'] = 0x05B6;
+ t['segol13'] = 0x05B6;
+ t['segol1f'] = 0x05B6;
+ t['segol2c'] = 0x05B6;
+ t['segolhebrew'] = 0x05B6;
+ t['segolnarrowhebrew'] = 0x05B6;
+ t['segolquarterhebrew'] = 0x05B6;
+ t['segoltahebrew'] = 0x0592;
+ t['segolwidehebrew'] = 0x05B6;
+ t['seharmenian'] = 0x057D;
+ t['sehiragana'] = 0x305B;
+ t['sekatakana'] = 0x30BB;
+ t['sekatakanahalfwidth'] = 0xFF7E;
+ t['semicolon'] = 0x003B;
+ t['semicolonarabic'] = 0x061B;
+ t['semicolonmonospace'] = 0xFF1B;
+ t['semicolonsmall'] = 0xFE54;
+ t['semivoicedmarkkana'] = 0x309C;
+ t['semivoicedmarkkanahalfwidth'] = 0xFF9F;
+ t['sentisquare'] = 0x3322;
+ t['sentosquare'] = 0x3323;
+ t['seven'] = 0x0037;
+ t['sevenarabic'] = 0x0667;
+ t['sevenbengali'] = 0x09ED;
+ t['sevencircle'] = 0x2466;
+ t['sevencircleinversesansserif'] = 0x2790;
+ t['sevendeva'] = 0x096D;
+ t['seveneighths'] = 0x215E;
+ t['sevengujarati'] = 0x0AED;
+ t['sevengurmukhi'] = 0x0A6D;
+ t['sevenhackarabic'] = 0x0667;
+ t['sevenhangzhou'] = 0x3027;
+ t['sevenideographicparen'] = 0x3226;
+ t['seveninferior'] = 0x2087;
+ t['sevenmonospace'] = 0xFF17;
+ t['sevenoldstyle'] = 0xF737;
+ t['sevenparen'] = 0x247A;
+ t['sevenperiod'] = 0x248E;
+ t['sevenpersian'] = 0x06F7;
+ t['sevenroman'] = 0x2176;
+ t['sevensuperior'] = 0x2077;
+ t['seventeencircle'] = 0x2470;
+ t['seventeenparen'] = 0x2484;
+ t['seventeenperiod'] = 0x2498;
+ t['seventhai'] = 0x0E57;
+ t['sfthyphen'] = 0x00AD;
+ t['shaarmenian'] = 0x0577;
+ t['shabengali'] = 0x09B6;
+ t['shacyrillic'] = 0x0448;
+ t['shaddaarabic'] = 0x0651;
+ t['shaddadammaarabic'] = 0xFC61;
+ t['shaddadammatanarabic'] = 0xFC5E;
+ t['shaddafathaarabic'] = 0xFC60;
+ t['shaddakasraarabic'] = 0xFC62;
+ t['shaddakasratanarabic'] = 0xFC5F;
+ t['shade'] = 0x2592;
+ t['shadedark'] = 0x2593;
+ t['shadelight'] = 0x2591;
+ t['shademedium'] = 0x2592;
+ t['shadeva'] = 0x0936;
+ t['shagujarati'] = 0x0AB6;
+ t['shagurmukhi'] = 0x0A36;
+ t['shalshelethebrew'] = 0x0593;
+ t['shbopomofo'] = 0x3115;
+ t['shchacyrillic'] = 0x0449;
+ t['sheenarabic'] = 0x0634;
+ t['sheenfinalarabic'] = 0xFEB6;
+ t['sheeninitialarabic'] = 0xFEB7;
+ t['sheenmedialarabic'] = 0xFEB8;
+ t['sheicoptic'] = 0x03E3;
+ t['sheqel'] = 0x20AA;
+ t['sheqelhebrew'] = 0x20AA;
+ t['sheva'] = 0x05B0;
+ t['sheva115'] = 0x05B0;
+ t['sheva15'] = 0x05B0;
+ t['sheva22'] = 0x05B0;
+ t['sheva2e'] = 0x05B0;
+ t['shevahebrew'] = 0x05B0;
+ t['shevanarrowhebrew'] = 0x05B0;
+ t['shevaquarterhebrew'] = 0x05B0;
+ t['shevawidehebrew'] = 0x05B0;
+ t['shhacyrillic'] = 0x04BB;
+ t['shimacoptic'] = 0x03ED;
+ t['shin'] = 0x05E9;
+ t['shindagesh'] = 0xFB49;
+ t['shindageshhebrew'] = 0xFB49;
+ t['shindageshshindot'] = 0xFB2C;
+ t['shindageshshindothebrew'] = 0xFB2C;
+ t['shindageshsindot'] = 0xFB2D;
+ t['shindageshsindothebrew'] = 0xFB2D;
+ t['shindothebrew'] = 0x05C1;
+ t['shinhebrew'] = 0x05E9;
+ t['shinshindot'] = 0xFB2A;
+ t['shinshindothebrew'] = 0xFB2A;
+ t['shinsindot'] = 0xFB2B;
+ t['shinsindothebrew'] = 0xFB2B;
+ t['shook'] = 0x0282;
+ t['sigma'] = 0x03C3;
+ t['sigma1'] = 0x03C2;
+ t['sigmafinal'] = 0x03C2;
+ t['sigmalunatesymbolgreek'] = 0x03F2;
+ t['sihiragana'] = 0x3057;
+ t['sikatakana'] = 0x30B7;
+ t['sikatakanahalfwidth'] = 0xFF7C;
+ t['siluqhebrew'] = 0x05BD;
+ t['siluqlefthebrew'] = 0x05BD;
+ t['similar'] = 0x223C;
+ t['sindothebrew'] = 0x05C2;
+ t['siosacirclekorean'] = 0x3274;
+ t['siosaparenkorean'] = 0x3214;
+ t['sioscieuckorean'] = 0x317E;
+ t['sioscirclekorean'] = 0x3266;
+ t['sioskiyeokkorean'] = 0x317A;
+ t['sioskorean'] = 0x3145;
+ t['siosnieunkorean'] = 0x317B;
+ t['siosparenkorean'] = 0x3206;
+ t['siospieupkorean'] = 0x317D;
+ t['siostikeutkorean'] = 0x317C;
+ t['six'] = 0x0036;
+ t['sixarabic'] = 0x0666;
+ t['sixbengali'] = 0x09EC;
+ t['sixcircle'] = 0x2465;
+ t['sixcircleinversesansserif'] = 0x278F;
+ t['sixdeva'] = 0x096C;
+ t['sixgujarati'] = 0x0AEC;
+ t['sixgurmukhi'] = 0x0A6C;
+ t['sixhackarabic'] = 0x0666;
+ t['sixhangzhou'] = 0x3026;
+ t['sixideographicparen'] = 0x3225;
+ t['sixinferior'] = 0x2086;
+ t['sixmonospace'] = 0xFF16;
+ t['sixoldstyle'] = 0xF736;
+ t['sixparen'] = 0x2479;
+ t['sixperiod'] = 0x248D;
+ t['sixpersian'] = 0x06F6;
+ t['sixroman'] = 0x2175;
+ t['sixsuperior'] = 0x2076;
+ t['sixteencircle'] = 0x246F;
+ t['sixteencurrencydenominatorbengali'] = 0x09F9;
+ t['sixteenparen'] = 0x2483;
+ t['sixteenperiod'] = 0x2497;
+ t['sixthai'] = 0x0E56;
+ t['slash'] = 0x002F;
+ t['slashmonospace'] = 0xFF0F;
+ t['slong'] = 0x017F;
+ t['slongdotaccent'] = 0x1E9B;
+ t['smileface'] = 0x263A;
+ t['smonospace'] = 0xFF53;
+ t['sofpasuqhebrew'] = 0x05C3;
+ t['softhyphen'] = 0x00AD;
+ t['softsigncyrillic'] = 0x044C;
+ t['sohiragana'] = 0x305D;
+ t['sokatakana'] = 0x30BD;
+ t['sokatakanahalfwidth'] = 0xFF7F;
+ t['soliduslongoverlaycmb'] = 0x0338;
+ t['solidusshortoverlaycmb'] = 0x0337;
+ t['sorusithai'] = 0x0E29;
+ t['sosalathai'] = 0x0E28;
+ t['sosothai'] = 0x0E0B;
+ t['sosuathai'] = 0x0E2A;
+ t['space'] = 0x0020;
+ t['spacehackarabic'] = 0x0020;
+ t['spade'] = 0x2660;
+ t['spadesuitblack'] = 0x2660;
+ t['spadesuitwhite'] = 0x2664;
+ t['sparen'] = 0x24AE;
+ t['squarebelowcmb'] = 0x033B;
+ t['squarecc'] = 0x33C4;
+ t['squarecm'] = 0x339D;
+ t['squarediagonalcrosshatchfill'] = 0x25A9;
+ t['squarehorizontalfill'] = 0x25A4;
+ t['squarekg'] = 0x338F;
+ t['squarekm'] = 0x339E;
+ t['squarekmcapital'] = 0x33CE;
+ t['squareln'] = 0x33D1;
+ t['squarelog'] = 0x33D2;
+ t['squaremg'] = 0x338E;
+ t['squaremil'] = 0x33D5;
+ t['squaremm'] = 0x339C;
+ t['squaremsquared'] = 0x33A1;
+ t['squareorthogonalcrosshatchfill'] = 0x25A6;
+ t['squareupperlefttolowerrightfill'] = 0x25A7;
+ t['squareupperrighttolowerleftfill'] = 0x25A8;
+ t['squareverticalfill'] = 0x25A5;
+ t['squarewhitewithsmallblack'] = 0x25A3;
+ t['srsquare'] = 0x33DB;
+ t['ssabengali'] = 0x09B7;
+ t['ssadeva'] = 0x0937;
+ t['ssagujarati'] = 0x0AB7;
+ t['ssangcieuckorean'] = 0x3149;
+ t['ssanghieuhkorean'] = 0x3185;
+ t['ssangieungkorean'] = 0x3180;
+ t['ssangkiyeokkorean'] = 0x3132;
+ t['ssangnieunkorean'] = 0x3165;
+ t['ssangpieupkorean'] = 0x3143;
+ t['ssangsioskorean'] = 0x3146;
+ t['ssangtikeutkorean'] = 0x3138;
+ t['ssuperior'] = 0xF6F2;
+ t['sterling'] = 0x00A3;
+ t['sterlingmonospace'] = 0xFFE1;
+ t['strokelongoverlaycmb'] = 0x0336;
+ t['strokeshortoverlaycmb'] = 0x0335;
+ t['subset'] = 0x2282;
+ t['subsetnotequal'] = 0x228A;
+ t['subsetorequal'] = 0x2286;
+ t['succeeds'] = 0x227B;
+ t['suchthat'] = 0x220B;
+ t['suhiragana'] = 0x3059;
+ t['sukatakana'] = 0x30B9;
+ t['sukatakanahalfwidth'] = 0xFF7D;
+ t['sukunarabic'] = 0x0652;
+ t['summation'] = 0x2211;
+ t['sun'] = 0x263C;
+ t['superset'] = 0x2283;
+ t['supersetnotequal'] = 0x228B;
+ t['supersetorequal'] = 0x2287;
+ t['svsquare'] = 0x33DC;
+ t['syouwaerasquare'] = 0x337C;
+ t['t'] = 0x0074;
+ t['tabengali'] = 0x09A4;
+ t['tackdown'] = 0x22A4;
+ t['tackleft'] = 0x22A3;
+ t['tadeva'] = 0x0924;
+ t['tagujarati'] = 0x0AA4;
+ t['tagurmukhi'] = 0x0A24;
+ t['taharabic'] = 0x0637;
+ t['tahfinalarabic'] = 0xFEC2;
+ t['tahinitialarabic'] = 0xFEC3;
+ t['tahiragana'] = 0x305F;
+ t['tahmedialarabic'] = 0xFEC4;
+ t['taisyouerasquare'] = 0x337D;
+ t['takatakana'] = 0x30BF;
+ t['takatakanahalfwidth'] = 0xFF80;
+ t['tatweelarabic'] = 0x0640;
+ t['tau'] = 0x03C4;
+ t['tav'] = 0x05EA;
+ t['tavdages'] = 0xFB4A;
+ t['tavdagesh'] = 0xFB4A;
+ t['tavdageshhebrew'] = 0xFB4A;
+ t['tavhebrew'] = 0x05EA;
+ t['tbar'] = 0x0167;
+ t['tbopomofo'] = 0x310A;
+ t['tcaron'] = 0x0165;
+ t['tccurl'] = 0x02A8;
+ t['tcedilla'] = 0x0163;
+ t['tcheharabic'] = 0x0686;
+ t['tchehfinalarabic'] = 0xFB7B;
+ t['tchehinitialarabic'] = 0xFB7C;
+ t['tchehmedialarabic'] = 0xFB7D;
+ t['tcircle'] = 0x24E3;
+ t['tcircumflexbelow'] = 0x1E71;
+ t['tcommaaccent'] = 0x0163;
+ t['tdieresis'] = 0x1E97;
+ t['tdotaccent'] = 0x1E6B;
+ t['tdotbelow'] = 0x1E6D;
+ t['tecyrillic'] = 0x0442;
+ t['tedescendercyrillic'] = 0x04AD;
+ t['teharabic'] = 0x062A;
+ t['tehfinalarabic'] = 0xFE96;
+ t['tehhahinitialarabic'] = 0xFCA2;
+ t['tehhahisolatedarabic'] = 0xFC0C;
+ t['tehinitialarabic'] = 0xFE97;
+ t['tehiragana'] = 0x3066;
+ t['tehjeeminitialarabic'] = 0xFCA1;
+ t['tehjeemisolatedarabic'] = 0xFC0B;
+ t['tehmarbutaarabic'] = 0x0629;
+ t['tehmarbutafinalarabic'] = 0xFE94;
+ t['tehmedialarabic'] = 0xFE98;
+ t['tehmeeminitialarabic'] = 0xFCA4;
+ t['tehmeemisolatedarabic'] = 0xFC0E;
+ t['tehnoonfinalarabic'] = 0xFC73;
+ t['tekatakana'] = 0x30C6;
+ t['tekatakanahalfwidth'] = 0xFF83;
+ t['telephone'] = 0x2121;
+ t['telephoneblack'] = 0x260E;
+ t['telishagedolahebrew'] = 0x05A0;
+ t['telishaqetanahebrew'] = 0x05A9;
+ t['tencircle'] = 0x2469;
+ t['tenideographicparen'] = 0x3229;
+ t['tenparen'] = 0x247D;
+ t['tenperiod'] = 0x2491;
+ t['tenroman'] = 0x2179;
+ t['tesh'] = 0x02A7;
+ t['tet'] = 0x05D8;
+ t['tetdagesh'] = 0xFB38;
+ t['tetdageshhebrew'] = 0xFB38;
+ t['tethebrew'] = 0x05D8;
+ t['tetsecyrillic'] = 0x04B5;
+ t['tevirhebrew'] = 0x059B;
+ t['tevirlefthebrew'] = 0x059B;
+ t['thabengali'] = 0x09A5;
+ t['thadeva'] = 0x0925;
+ t['thagujarati'] = 0x0AA5;
+ t['thagurmukhi'] = 0x0A25;
+ t['thalarabic'] = 0x0630;
+ t['thalfinalarabic'] = 0xFEAC;
+ t['thanthakhatlowleftthai'] = 0xF898;
+ t['thanthakhatlowrightthai'] = 0xF897;
+ t['thanthakhatthai'] = 0x0E4C;
+ t['thanthakhatupperleftthai'] = 0xF896;
+ t['theharabic'] = 0x062B;
+ t['thehfinalarabic'] = 0xFE9A;
+ t['thehinitialarabic'] = 0xFE9B;
+ t['thehmedialarabic'] = 0xFE9C;
+ t['thereexists'] = 0x2203;
+ t['therefore'] = 0x2234;
+ t['theta'] = 0x03B8;
+ t['theta1'] = 0x03D1;
+ t['thetasymbolgreek'] = 0x03D1;
+ t['thieuthacirclekorean'] = 0x3279;
+ t['thieuthaparenkorean'] = 0x3219;
+ t['thieuthcirclekorean'] = 0x326B;
+ t['thieuthkorean'] = 0x314C;
+ t['thieuthparenkorean'] = 0x320B;
+ t['thirteencircle'] = 0x246C;
+ t['thirteenparen'] = 0x2480;
+ t['thirteenperiod'] = 0x2494;
+ t['thonangmonthothai'] = 0x0E11;
+ t['thook'] = 0x01AD;
+ t['thophuthaothai'] = 0x0E12;
+ t['thorn'] = 0x00FE;
+ t['thothahanthai'] = 0x0E17;
+ t['thothanthai'] = 0x0E10;
+ t['thothongthai'] = 0x0E18;
+ t['thothungthai'] = 0x0E16;
+ t['thousandcyrillic'] = 0x0482;
+ t['thousandsseparatorarabic'] = 0x066C;
+ t['thousandsseparatorpersian'] = 0x066C;
+ t['three'] = 0x0033;
+ t['threearabic'] = 0x0663;
+ t['threebengali'] = 0x09E9;
+ t['threecircle'] = 0x2462;
+ t['threecircleinversesansserif'] = 0x278C;
+ t['threedeva'] = 0x0969;
+ t['threeeighths'] = 0x215C;
+ t['threegujarati'] = 0x0AE9;
+ t['threegurmukhi'] = 0x0A69;
+ t['threehackarabic'] = 0x0663;
+ t['threehangzhou'] = 0x3023;
+ t['threeideographicparen'] = 0x3222;
+ t['threeinferior'] = 0x2083;
+ t['threemonospace'] = 0xFF13;
+ t['threenumeratorbengali'] = 0x09F6;
+ t['threeoldstyle'] = 0xF733;
+ t['threeparen'] = 0x2476;
+ t['threeperiod'] = 0x248A;
+ t['threepersian'] = 0x06F3;
+ t['threequarters'] = 0x00BE;
+ t['threequartersemdash'] = 0xF6DE;
+ t['threeroman'] = 0x2172;
+ t['threesuperior'] = 0x00B3;
+ t['threethai'] = 0x0E53;
+ t['thzsquare'] = 0x3394;
+ t['tihiragana'] = 0x3061;
+ t['tikatakana'] = 0x30C1;
+ t['tikatakanahalfwidth'] = 0xFF81;
+ t['tikeutacirclekorean'] = 0x3270;
+ t['tikeutaparenkorean'] = 0x3210;
+ t['tikeutcirclekorean'] = 0x3262;
+ t['tikeutkorean'] = 0x3137;
+ t['tikeutparenkorean'] = 0x3202;
+ t['tilde'] = 0x02DC;
+ t['tildebelowcmb'] = 0x0330;
+ t['tildecmb'] = 0x0303;
+ t['tildecomb'] = 0x0303;
+ t['tildedoublecmb'] = 0x0360;
+ t['tildeoperator'] = 0x223C;
+ t['tildeoverlaycmb'] = 0x0334;
+ t['tildeverticalcmb'] = 0x033E;
+ t['timescircle'] = 0x2297;
+ t['tipehahebrew'] = 0x0596;
+ t['tipehalefthebrew'] = 0x0596;
+ t['tippigurmukhi'] = 0x0A70;
+ t['titlocyrilliccmb'] = 0x0483;
+ t['tiwnarmenian'] = 0x057F;
+ t['tlinebelow'] = 0x1E6F;
+ t['tmonospace'] = 0xFF54;
+ t['toarmenian'] = 0x0569;
+ t['tohiragana'] = 0x3068;
+ t['tokatakana'] = 0x30C8;
+ t['tokatakanahalfwidth'] = 0xFF84;
+ t['tonebarextrahighmod'] = 0x02E5;
+ t['tonebarextralowmod'] = 0x02E9;
+ t['tonebarhighmod'] = 0x02E6;
+ t['tonebarlowmod'] = 0x02E8;
+ t['tonebarmidmod'] = 0x02E7;
+ t['tonefive'] = 0x01BD;
+ t['tonesix'] = 0x0185;
+ t['tonetwo'] = 0x01A8;
+ t['tonos'] = 0x0384;
+ t['tonsquare'] = 0x3327;
+ t['topatakthai'] = 0x0E0F;
+ t['tortoiseshellbracketleft'] = 0x3014;
+ t['tortoiseshellbracketleftsmall'] = 0xFE5D;
+ t['tortoiseshellbracketleftvertical'] = 0xFE39;
+ t['tortoiseshellbracketright'] = 0x3015;
+ t['tortoiseshellbracketrightsmall'] = 0xFE5E;
+ t['tortoiseshellbracketrightvertical'] = 0xFE3A;
+ t['totaothai'] = 0x0E15;
+ t['tpalatalhook'] = 0x01AB;
+ t['tparen'] = 0x24AF;
+ t['trademark'] = 0x2122;
+ t['trademarksans'] = 0xF8EA;
+ t['trademarkserif'] = 0xF6DB;
+ t['tretroflexhook'] = 0x0288;
+ t['triagdn'] = 0x25BC;
+ t['triaglf'] = 0x25C4;
+ t['triagrt'] = 0x25BA;
+ t['triagup'] = 0x25B2;
+ t['ts'] = 0x02A6;
+ t['tsadi'] = 0x05E6;
+ t['tsadidagesh'] = 0xFB46;
+ t['tsadidageshhebrew'] = 0xFB46;
+ t['tsadihebrew'] = 0x05E6;
+ t['tsecyrillic'] = 0x0446;
+ t['tsere'] = 0x05B5;
+ t['tsere12'] = 0x05B5;
+ t['tsere1e'] = 0x05B5;
+ t['tsere2b'] = 0x05B5;
+ t['tserehebrew'] = 0x05B5;
+ t['tserenarrowhebrew'] = 0x05B5;
+ t['tserequarterhebrew'] = 0x05B5;
+ t['tserewidehebrew'] = 0x05B5;
+ t['tshecyrillic'] = 0x045B;
+ t['tsuperior'] = 0xF6F3;
+ t['ttabengali'] = 0x099F;
+ t['ttadeva'] = 0x091F;
+ t['ttagujarati'] = 0x0A9F;
+ t['ttagurmukhi'] = 0x0A1F;
+ t['tteharabic'] = 0x0679;
+ t['ttehfinalarabic'] = 0xFB67;
+ t['ttehinitialarabic'] = 0xFB68;
+ t['ttehmedialarabic'] = 0xFB69;
+ t['tthabengali'] = 0x09A0;
+ t['tthadeva'] = 0x0920;
+ t['tthagujarati'] = 0x0AA0;
+ t['tthagurmukhi'] = 0x0A20;
+ t['tturned'] = 0x0287;
+ t['tuhiragana'] = 0x3064;
+ t['tukatakana'] = 0x30C4;
+ t['tukatakanahalfwidth'] = 0xFF82;
+ t['tusmallhiragana'] = 0x3063;
+ t['tusmallkatakana'] = 0x30C3;
+ t['tusmallkatakanahalfwidth'] = 0xFF6F;
+ t['twelvecircle'] = 0x246B;
+ t['twelveparen'] = 0x247F;
+ t['twelveperiod'] = 0x2493;
+ t['twelveroman'] = 0x217B;
+ t['twentycircle'] = 0x2473;
+ t['twentyhangzhou'] = 0x5344;
+ t['twentyparen'] = 0x2487;
+ t['twentyperiod'] = 0x249B;
+ t['two'] = 0x0032;
+ t['twoarabic'] = 0x0662;
+ t['twobengali'] = 0x09E8;
+ t['twocircle'] = 0x2461;
+ t['twocircleinversesansserif'] = 0x278B;
+ t['twodeva'] = 0x0968;
+ t['twodotenleader'] = 0x2025;
+ t['twodotleader'] = 0x2025;
+ t['twodotleadervertical'] = 0xFE30;
+ t['twogujarati'] = 0x0AE8;
+ t['twogurmukhi'] = 0x0A68;
+ t['twohackarabic'] = 0x0662;
+ t['twohangzhou'] = 0x3022;
+ t['twoideographicparen'] = 0x3221;
+ t['twoinferior'] = 0x2082;
+ t['twomonospace'] = 0xFF12;
+ t['twonumeratorbengali'] = 0x09F5;
+ t['twooldstyle'] = 0xF732;
+ t['twoparen'] = 0x2475;
+ t['twoperiod'] = 0x2489;
+ t['twopersian'] = 0x06F2;
+ t['tworoman'] = 0x2171;
+ t['twostroke'] = 0x01BB;
+ t['twosuperior'] = 0x00B2;
+ t['twothai'] = 0x0E52;
+ t['twothirds'] = 0x2154;
+ t['u'] = 0x0075;
+ t['uacute'] = 0x00FA;
+ t['ubar'] = 0x0289;
+ t['ubengali'] = 0x0989;
+ t['ubopomofo'] = 0x3128;
+ t['ubreve'] = 0x016D;
+ t['ucaron'] = 0x01D4;
+ t['ucircle'] = 0x24E4;
+ t['ucircumflex'] = 0x00FB;
+ t['ucircumflexbelow'] = 0x1E77;
+ t['ucyrillic'] = 0x0443;
+ t['udattadeva'] = 0x0951;
+ t['udblacute'] = 0x0171;
+ t['udblgrave'] = 0x0215;
+ t['udeva'] = 0x0909;
+ t['udieresis'] = 0x00FC;
+ t['udieresisacute'] = 0x01D8;
+ t['udieresisbelow'] = 0x1E73;
+ t['udieresiscaron'] = 0x01DA;
+ t['udieresiscyrillic'] = 0x04F1;
+ t['udieresisgrave'] = 0x01DC;
+ t['udieresismacron'] = 0x01D6;
+ t['udotbelow'] = 0x1EE5;
+ t['ugrave'] = 0x00F9;
+ t['ugujarati'] = 0x0A89;
+ t['ugurmukhi'] = 0x0A09;
+ t['uhiragana'] = 0x3046;
+ t['uhookabove'] = 0x1EE7;
+ t['uhorn'] = 0x01B0;
+ t['uhornacute'] = 0x1EE9;
+ t['uhorndotbelow'] = 0x1EF1;
+ t['uhorngrave'] = 0x1EEB;
+ t['uhornhookabove'] = 0x1EED;
+ t['uhorntilde'] = 0x1EEF;
+ t['uhungarumlaut'] = 0x0171;
+ t['uhungarumlautcyrillic'] = 0x04F3;
+ t['uinvertedbreve'] = 0x0217;
+ t['ukatakana'] = 0x30A6;
+ t['ukatakanahalfwidth'] = 0xFF73;
+ t['ukcyrillic'] = 0x0479;
+ t['ukorean'] = 0x315C;
+ t['umacron'] = 0x016B;
+ t['umacroncyrillic'] = 0x04EF;
+ t['umacrondieresis'] = 0x1E7B;
+ t['umatragurmukhi'] = 0x0A41;
+ t['umonospace'] = 0xFF55;
+ t['underscore'] = 0x005F;
+ t['underscoredbl'] = 0x2017;
+ t['underscoremonospace'] = 0xFF3F;
+ t['underscorevertical'] = 0xFE33;
+ t['underscorewavy'] = 0xFE4F;
+ t['union'] = 0x222A;
+ t['universal'] = 0x2200;
+ t['uogonek'] = 0x0173;
+ t['uparen'] = 0x24B0;
+ t['upblock'] = 0x2580;
+ t['upperdothebrew'] = 0x05C4;
+ t['upsilon'] = 0x03C5;
+ t['upsilondieresis'] = 0x03CB;
+ t['upsilondieresistonos'] = 0x03B0;
+ t['upsilonlatin'] = 0x028A;
+ t['upsilontonos'] = 0x03CD;
+ t['uptackbelowcmb'] = 0x031D;
+ t['uptackmod'] = 0x02D4;
+ t['uragurmukhi'] = 0x0A73;
+ t['uring'] = 0x016F;
+ t['ushortcyrillic'] = 0x045E;
+ t['usmallhiragana'] = 0x3045;
+ t['usmallkatakana'] = 0x30A5;
+ t['usmallkatakanahalfwidth'] = 0xFF69;
+ t['ustraightcyrillic'] = 0x04AF;
+ t['ustraightstrokecyrillic'] = 0x04B1;
+ t['utilde'] = 0x0169;
+ t['utildeacute'] = 0x1E79;
+ t['utildebelow'] = 0x1E75;
+ t['uubengali'] = 0x098A;
+ t['uudeva'] = 0x090A;
+ t['uugujarati'] = 0x0A8A;
+ t['uugurmukhi'] = 0x0A0A;
+ t['uumatragurmukhi'] = 0x0A42;
+ t['uuvowelsignbengali'] = 0x09C2;
+ t['uuvowelsigndeva'] = 0x0942;
+ t['uuvowelsigngujarati'] = 0x0AC2;
+ t['uvowelsignbengali'] = 0x09C1;
+ t['uvowelsigndeva'] = 0x0941;
+ t['uvowelsigngujarati'] = 0x0AC1;
+ t['v'] = 0x0076;
+ t['vadeva'] = 0x0935;
+ t['vagujarati'] = 0x0AB5;
+ t['vagurmukhi'] = 0x0A35;
+ t['vakatakana'] = 0x30F7;
+ t['vav'] = 0x05D5;
+ t['vavdagesh'] = 0xFB35;
+ t['vavdagesh65'] = 0xFB35;
+ t['vavdageshhebrew'] = 0xFB35;
+ t['vavhebrew'] = 0x05D5;
+ t['vavholam'] = 0xFB4B;
+ t['vavholamhebrew'] = 0xFB4B;
+ t['vavvavhebrew'] = 0x05F0;
+ t['vavyodhebrew'] = 0x05F1;
+ t['vcircle'] = 0x24E5;
+ t['vdotbelow'] = 0x1E7F;
+ t['vecyrillic'] = 0x0432;
+ t['veharabic'] = 0x06A4;
+ t['vehfinalarabic'] = 0xFB6B;
+ t['vehinitialarabic'] = 0xFB6C;
+ t['vehmedialarabic'] = 0xFB6D;
+ t['vekatakana'] = 0x30F9;
+ t['venus'] = 0x2640;
+ t['verticalbar'] = 0x007C;
+ t['verticallineabovecmb'] = 0x030D;
+ t['verticallinebelowcmb'] = 0x0329;
+ t['verticallinelowmod'] = 0x02CC;
+ t['verticallinemod'] = 0x02C8;
+ t['vewarmenian'] = 0x057E;
+ t['vhook'] = 0x028B;
+ t['vikatakana'] = 0x30F8;
+ t['viramabengali'] = 0x09CD;
+ t['viramadeva'] = 0x094D;
+ t['viramagujarati'] = 0x0ACD;
+ t['visargabengali'] = 0x0983;
+ t['visargadeva'] = 0x0903;
+ t['visargagujarati'] = 0x0A83;
+ t['vmonospace'] = 0xFF56;
+ t['voarmenian'] = 0x0578;
+ t['voicediterationhiragana'] = 0x309E;
+ t['voicediterationkatakana'] = 0x30FE;
+ t['voicedmarkkana'] = 0x309B;
+ t['voicedmarkkanahalfwidth'] = 0xFF9E;
+ t['vokatakana'] = 0x30FA;
+ t['vparen'] = 0x24B1;
+ t['vtilde'] = 0x1E7D;
+ t['vturned'] = 0x028C;
+ t['vuhiragana'] = 0x3094;
+ t['vukatakana'] = 0x30F4;
+ t['w'] = 0x0077;
+ t['wacute'] = 0x1E83;
+ t['waekorean'] = 0x3159;
+ t['wahiragana'] = 0x308F;
+ t['wakatakana'] = 0x30EF;
+ t['wakatakanahalfwidth'] = 0xFF9C;
+ t['wakorean'] = 0x3158;
+ t['wasmallhiragana'] = 0x308E;
+ t['wasmallkatakana'] = 0x30EE;
+ t['wattosquare'] = 0x3357;
+ t['wavedash'] = 0x301C;
+ t['wavyunderscorevertical'] = 0xFE34;
+ t['wawarabic'] = 0x0648;
+ t['wawfinalarabic'] = 0xFEEE;
+ t['wawhamzaabovearabic'] = 0x0624;
+ t['wawhamzaabovefinalarabic'] = 0xFE86;
+ t['wbsquare'] = 0x33DD;
+ t['wcircle'] = 0x24E6;
+ t['wcircumflex'] = 0x0175;
+ t['wdieresis'] = 0x1E85;
+ t['wdotaccent'] = 0x1E87;
+ t['wdotbelow'] = 0x1E89;
+ t['wehiragana'] = 0x3091;
+ t['weierstrass'] = 0x2118;
+ t['wekatakana'] = 0x30F1;
+ t['wekorean'] = 0x315E;
+ t['weokorean'] = 0x315D;
+ t['wgrave'] = 0x1E81;
+ t['whitebullet'] = 0x25E6;
+ t['whitecircle'] = 0x25CB;
+ t['whitecircleinverse'] = 0x25D9;
+ t['whitecornerbracketleft'] = 0x300E;
+ t['whitecornerbracketleftvertical'] = 0xFE43;
+ t['whitecornerbracketright'] = 0x300F;
+ t['whitecornerbracketrightvertical'] = 0xFE44;
+ t['whitediamond'] = 0x25C7;
+ t['whitediamondcontainingblacksmalldiamond'] = 0x25C8;
+ t['whitedownpointingsmalltriangle'] = 0x25BF;
+ t['whitedownpointingtriangle'] = 0x25BD;
+ t['whiteleftpointingsmalltriangle'] = 0x25C3;
+ t['whiteleftpointingtriangle'] = 0x25C1;
+ t['whitelenticularbracketleft'] = 0x3016;
+ t['whitelenticularbracketright'] = 0x3017;
+ t['whiterightpointingsmalltriangle'] = 0x25B9;
+ t['whiterightpointingtriangle'] = 0x25B7;
+ t['whitesmallsquare'] = 0x25AB;
+ t['whitesmilingface'] = 0x263A;
+ t['whitesquare'] = 0x25A1;
+ t['whitestar'] = 0x2606;
+ t['whitetelephone'] = 0x260F;
+ t['whitetortoiseshellbracketleft'] = 0x3018;
+ t['whitetortoiseshellbracketright'] = 0x3019;
+ t['whiteuppointingsmalltriangle'] = 0x25B5;
+ t['whiteuppointingtriangle'] = 0x25B3;
+ t['wihiragana'] = 0x3090;
+ t['wikatakana'] = 0x30F0;
+ t['wikorean'] = 0x315F;
+ t['wmonospace'] = 0xFF57;
+ t['wohiragana'] = 0x3092;
+ t['wokatakana'] = 0x30F2;
+ t['wokatakanahalfwidth'] = 0xFF66;
+ t['won'] = 0x20A9;
+ t['wonmonospace'] = 0xFFE6;
+ t['wowaenthai'] = 0x0E27;
+ t['wparen'] = 0x24B2;
+ t['wring'] = 0x1E98;
+ t['wsuperior'] = 0x02B7;
+ t['wturned'] = 0x028D;
+ t['wynn'] = 0x01BF;
+ t['x'] = 0x0078;
+ t['xabovecmb'] = 0x033D;
+ t['xbopomofo'] = 0x3112;
+ t['xcircle'] = 0x24E7;
+ t['xdieresis'] = 0x1E8D;
+ t['xdotaccent'] = 0x1E8B;
+ t['xeharmenian'] = 0x056D;
+ t['xi'] = 0x03BE;
+ t['xmonospace'] = 0xFF58;
+ t['xparen'] = 0x24B3;
+ t['xsuperior'] = 0x02E3;
+ t['y'] = 0x0079;
+ t['yaadosquare'] = 0x334E;
+ t['yabengali'] = 0x09AF;
+ t['yacute'] = 0x00FD;
+ t['yadeva'] = 0x092F;
+ t['yaekorean'] = 0x3152;
+ t['yagujarati'] = 0x0AAF;
+ t['yagurmukhi'] = 0x0A2F;
+ t['yahiragana'] = 0x3084;
+ t['yakatakana'] = 0x30E4;
+ t['yakatakanahalfwidth'] = 0xFF94;
+ t['yakorean'] = 0x3151;
+ t['yamakkanthai'] = 0x0E4E;
+ t['yasmallhiragana'] = 0x3083;
+ t['yasmallkatakana'] = 0x30E3;
+ t['yasmallkatakanahalfwidth'] = 0xFF6C;
+ t['yatcyrillic'] = 0x0463;
+ t['ycircle'] = 0x24E8;
+ t['ycircumflex'] = 0x0177;
+ t['ydieresis'] = 0x00FF;
+ t['ydotaccent'] = 0x1E8F;
+ t['ydotbelow'] = 0x1EF5;
+ t['yeharabic'] = 0x064A;
+ t['yehbarreearabic'] = 0x06D2;
+ t['yehbarreefinalarabic'] = 0xFBAF;
+ t['yehfinalarabic'] = 0xFEF2;
+ t['yehhamzaabovearabic'] = 0x0626;
+ t['yehhamzaabovefinalarabic'] = 0xFE8A;
+ t['yehhamzaaboveinitialarabic'] = 0xFE8B;
+ t['yehhamzaabovemedialarabic'] = 0xFE8C;
+ t['yehinitialarabic'] = 0xFEF3;
+ t['yehmedialarabic'] = 0xFEF4;
+ t['yehmeeminitialarabic'] = 0xFCDD;
+ t['yehmeemisolatedarabic'] = 0xFC58;
+ t['yehnoonfinalarabic'] = 0xFC94;
+ t['yehthreedotsbelowarabic'] = 0x06D1;
+ t['yekorean'] = 0x3156;
+ t['yen'] = 0x00A5;
+ t['yenmonospace'] = 0xFFE5;
+ t['yeokorean'] = 0x3155;
+ t['yeorinhieuhkorean'] = 0x3186;
+ t['yerahbenyomohebrew'] = 0x05AA;
+ t['yerahbenyomolefthebrew'] = 0x05AA;
+ t['yericyrillic'] = 0x044B;
+ t['yerudieresiscyrillic'] = 0x04F9;
+ t['yesieungkorean'] = 0x3181;
+ t['yesieungpansioskorean'] = 0x3183;
+ t['yesieungsioskorean'] = 0x3182;
+ t['yetivhebrew'] = 0x059A;
+ t['ygrave'] = 0x1EF3;
+ t['yhook'] = 0x01B4;
+ t['yhookabove'] = 0x1EF7;
+ t['yiarmenian'] = 0x0575;
+ t['yicyrillic'] = 0x0457;
+ t['yikorean'] = 0x3162;
+ t['yinyang'] = 0x262F;
+ t['yiwnarmenian'] = 0x0582;
+ t['ymonospace'] = 0xFF59;
+ t['yod'] = 0x05D9;
+ t['yoddagesh'] = 0xFB39;
+ t['yoddageshhebrew'] = 0xFB39;
+ t['yodhebrew'] = 0x05D9;
+ t['yodyodhebrew'] = 0x05F2;
+ t['yodyodpatahhebrew'] = 0xFB1F;
+ t['yohiragana'] = 0x3088;
+ t['yoikorean'] = 0x3189;
+ t['yokatakana'] = 0x30E8;
+ t['yokatakanahalfwidth'] = 0xFF96;
+ t['yokorean'] = 0x315B;
+ t['yosmallhiragana'] = 0x3087;
+ t['yosmallkatakana'] = 0x30E7;
+ t['yosmallkatakanahalfwidth'] = 0xFF6E;
+ t['yotgreek'] = 0x03F3;
+ t['yoyaekorean'] = 0x3188;
+ t['yoyakorean'] = 0x3187;
+ t['yoyakthai'] = 0x0E22;
+ t['yoyingthai'] = 0x0E0D;
+ t['yparen'] = 0x24B4;
+ t['ypogegrammeni'] = 0x037A;
+ t['ypogegrammenigreekcmb'] = 0x0345;
+ t['yr'] = 0x01A6;
+ t['yring'] = 0x1E99;
+ t['ysuperior'] = 0x02B8;
+ t['ytilde'] = 0x1EF9;
+ t['yturned'] = 0x028E;
+ t['yuhiragana'] = 0x3086;
+ t['yuikorean'] = 0x318C;
+ t['yukatakana'] = 0x30E6;
+ t['yukatakanahalfwidth'] = 0xFF95;
+ t['yukorean'] = 0x3160;
+ t['yusbigcyrillic'] = 0x046B;
+ t['yusbigiotifiedcyrillic'] = 0x046D;
+ t['yuslittlecyrillic'] = 0x0467;
+ t['yuslittleiotifiedcyrillic'] = 0x0469;
+ t['yusmallhiragana'] = 0x3085;
+ t['yusmallkatakana'] = 0x30E5;
+ t['yusmallkatakanahalfwidth'] = 0xFF6D;
+ t['yuyekorean'] = 0x318B;
+ t['yuyeokorean'] = 0x318A;
+ t['yyabengali'] = 0x09DF;
+ t['yyadeva'] = 0x095F;
+ t['z'] = 0x007A;
+ t['zaarmenian'] = 0x0566;
+ t['zacute'] = 0x017A;
+ t['zadeva'] = 0x095B;
+ t['zagurmukhi'] = 0x0A5B;
+ t['zaharabic'] = 0x0638;
+ t['zahfinalarabic'] = 0xFEC6;
+ t['zahinitialarabic'] = 0xFEC7;
+ t['zahiragana'] = 0x3056;
+ t['zahmedialarabic'] = 0xFEC8;
+ t['zainarabic'] = 0x0632;
+ t['zainfinalarabic'] = 0xFEB0;
+ t['zakatakana'] = 0x30B6;
+ t['zaqefgadolhebrew'] = 0x0595;
+ t['zaqefqatanhebrew'] = 0x0594;
+ t['zarqahebrew'] = 0x0598;
+ t['zayin'] = 0x05D6;
+ t['zayindagesh'] = 0xFB36;
+ t['zayindageshhebrew'] = 0xFB36;
+ t['zayinhebrew'] = 0x05D6;
+ t['zbopomofo'] = 0x3117;
+ t['zcaron'] = 0x017E;
+ t['zcircle'] = 0x24E9;
+ t['zcircumflex'] = 0x1E91;
+ t['zcurl'] = 0x0291;
+ t['zdot'] = 0x017C;
+ t['zdotaccent'] = 0x017C;
+ t['zdotbelow'] = 0x1E93;
+ t['zecyrillic'] = 0x0437;
+ t['zedescendercyrillic'] = 0x0499;
+ t['zedieresiscyrillic'] = 0x04DF;
+ t['zehiragana'] = 0x305C;
+ t['zekatakana'] = 0x30BC;
+ t['zero'] = 0x0030;
+ t['zeroarabic'] = 0x0660;
+ t['zerobengali'] = 0x09E6;
+ t['zerodeva'] = 0x0966;
+ t['zerogujarati'] = 0x0AE6;
+ t['zerogurmukhi'] = 0x0A66;
+ t['zerohackarabic'] = 0x0660;
+ t['zeroinferior'] = 0x2080;
+ t['zeromonospace'] = 0xFF10;
+ t['zerooldstyle'] = 0xF730;
+ t['zeropersian'] = 0x06F0;
+ t['zerosuperior'] = 0x2070;
+ t['zerothai'] = 0x0E50;
+ t['zerowidthjoiner'] = 0xFEFF;
+ t['zerowidthnonjoiner'] = 0x200C;
+ t['zerowidthspace'] = 0x200B;
+ t['zeta'] = 0x03B6;
+ t['zhbopomofo'] = 0x3113;
+ t['zhearmenian'] = 0x056A;
+ t['zhebrevecyrillic'] = 0x04C2;
+ t['zhecyrillic'] = 0x0436;
+ t['zhedescendercyrillic'] = 0x0497;
+ t['zhedieresiscyrillic'] = 0x04DD;
+ t['zihiragana'] = 0x3058;
+ t['zikatakana'] = 0x30B8;
+ t['zinorhebrew'] = 0x05AE;
+ t['zlinebelow'] = 0x1E95;
+ t['zmonospace'] = 0xFF5A;
+ t['zohiragana'] = 0x305E;
+ t['zokatakana'] = 0x30BE;
+ t['zparen'] = 0x24B5;
+ t['zretroflexhook'] = 0x0290;
+ t['zstroke'] = 0x01B6;
+ t['zuhiragana'] = 0x305A;
+ t['zukatakana'] = 0x30BA;
+ t['.notdef'] = 0x0000;
+ });
+ var getDingbatsGlyphsUnicode = getLookupTableFactory(function (t) {
+ t['space'] = 0x0020;
+ t['a1'] = 0x2701;
+ t['a2'] = 0x2702;
+ t['a202'] = 0x2703;
+ t['a3'] = 0x2704;
+ t['a4'] = 0x260E;
+ t['a5'] = 0x2706;
+ t['a119'] = 0x2707;
+ t['a118'] = 0x2708;
+ t['a117'] = 0x2709;
+ t['a11'] = 0x261B;
+ t['a12'] = 0x261E;
+ t['a13'] = 0x270C;
+ t['a14'] = 0x270D;
+ t['a15'] = 0x270E;
+ t['a16'] = 0x270F;
+ t['a105'] = 0x2710;
+ t['a17'] = 0x2711;
+ t['a18'] = 0x2712;
+ t['a19'] = 0x2713;
+ t['a20'] = 0x2714;
+ t['a21'] = 0x2715;
+ t['a22'] = 0x2716;
+ t['a23'] = 0x2717;
+ t['a24'] = 0x2718;
+ t['a25'] = 0x2719;
+ t['a26'] = 0x271A;
+ t['a27'] = 0x271B;
+ t['a28'] = 0x271C;
+ t['a6'] = 0x271D;
+ t['a7'] = 0x271E;
+ t['a8'] = 0x271F;
+ t['a9'] = 0x2720;
+ t['a10'] = 0x2721;
+ t['a29'] = 0x2722;
+ t['a30'] = 0x2723;
+ t['a31'] = 0x2724;
+ t['a32'] = 0x2725;
+ t['a33'] = 0x2726;
+ t['a34'] = 0x2727;
+ t['a35'] = 0x2605;
+ t['a36'] = 0x2729;
+ t['a37'] = 0x272A;
+ t['a38'] = 0x272B;
+ t['a39'] = 0x272C;
+ t['a40'] = 0x272D;
+ t['a41'] = 0x272E;
+ t['a42'] = 0x272F;
+ t['a43'] = 0x2730;
+ t['a44'] = 0x2731;
+ t['a45'] = 0x2732;
+ t['a46'] = 0x2733;
+ t['a47'] = 0x2734;
+ t['a48'] = 0x2735;
+ t['a49'] = 0x2736;
+ t['a50'] = 0x2737;
+ t['a51'] = 0x2738;
+ t['a52'] = 0x2739;
+ t['a53'] = 0x273A;
+ t['a54'] = 0x273B;
+ t['a55'] = 0x273C;
+ t['a56'] = 0x273D;
+ t['a57'] = 0x273E;
+ t['a58'] = 0x273F;
+ t['a59'] = 0x2740;
+ t['a60'] = 0x2741;
+ t['a61'] = 0x2742;
+ t['a62'] = 0x2743;
+ t['a63'] = 0x2744;
+ t['a64'] = 0x2745;
+ t['a65'] = 0x2746;
+ t['a66'] = 0x2747;
+ t['a67'] = 0x2748;
+ t['a68'] = 0x2749;
+ t['a69'] = 0x274A;
+ t['a70'] = 0x274B;
+ t['a71'] = 0x25CF;
+ t['a72'] = 0x274D;
+ t['a73'] = 0x25A0;
+ t['a74'] = 0x274F;
+ t['a203'] = 0x2750;
+ t['a75'] = 0x2751;
+ t['a204'] = 0x2752;
+ t['a76'] = 0x25B2;
+ t['a77'] = 0x25BC;
+ t['a78'] = 0x25C6;
+ t['a79'] = 0x2756;
+ t['a81'] = 0x25D7;
+ t['a82'] = 0x2758;
+ t['a83'] = 0x2759;
+ t['a84'] = 0x275A;
+ t['a97'] = 0x275B;
+ t['a98'] = 0x275C;
+ t['a99'] = 0x275D;
+ t['a100'] = 0x275E;
+ t['a101'] = 0x2761;
+ t['a102'] = 0x2762;
+ t['a103'] = 0x2763;
+ t['a104'] = 0x2764;
+ t['a106'] = 0x2765;
+ t['a107'] = 0x2766;
+ t['a108'] = 0x2767;
+ t['a112'] = 0x2663;
+ t['a111'] = 0x2666;
+ t['a110'] = 0x2665;
+ t['a109'] = 0x2660;
+ t['a120'] = 0x2460;
+ t['a121'] = 0x2461;
+ t['a122'] = 0x2462;
+ t['a123'] = 0x2463;
+ t['a124'] = 0x2464;
+ t['a125'] = 0x2465;
+ t['a126'] = 0x2466;
+ t['a127'] = 0x2467;
+ t['a128'] = 0x2468;
+ t['a129'] = 0x2469;
+ t['a130'] = 0x2776;
+ t['a131'] = 0x2777;
+ t['a132'] = 0x2778;
+ t['a133'] = 0x2779;
+ t['a134'] = 0x277A;
+ t['a135'] = 0x277B;
+ t['a136'] = 0x277C;
+ t['a137'] = 0x277D;
+ t['a138'] = 0x277E;
+ t['a139'] = 0x277F;
+ t['a140'] = 0x2780;
+ t['a141'] = 0x2781;
+ t['a142'] = 0x2782;
+ t['a143'] = 0x2783;
+ t['a144'] = 0x2784;
+ t['a145'] = 0x2785;
+ t['a146'] = 0x2786;
+ t['a147'] = 0x2787;
+ t['a148'] = 0x2788;
+ t['a149'] = 0x2789;
+ t['a150'] = 0x278A;
+ t['a151'] = 0x278B;
+ t['a152'] = 0x278C;
+ t['a153'] = 0x278D;
+ t['a154'] = 0x278E;
+ t['a155'] = 0x278F;
+ t['a156'] = 0x2790;
+ t['a157'] = 0x2791;
+ t['a158'] = 0x2792;
+ t['a159'] = 0x2793;
+ t['a160'] = 0x2794;
+ t['a161'] = 0x2192;
+ t['a163'] = 0x2194;
+ t['a164'] = 0x2195;
+ t['a196'] = 0x2798;
+ t['a165'] = 0x2799;
+ t['a192'] = 0x279A;
+ t['a166'] = 0x279B;
+ t['a167'] = 0x279C;
+ t['a168'] = 0x279D;
+ t['a169'] = 0x279E;
+ t['a170'] = 0x279F;
+ t['a171'] = 0x27A0;
+ t['a172'] = 0x27A1;
+ t['a173'] = 0x27A2;
+ t['a162'] = 0x27A3;
+ t['a174'] = 0x27A4;
+ t['a175'] = 0x27A5;
+ t['a176'] = 0x27A6;
+ t['a177'] = 0x27A7;
+ t['a178'] = 0x27A8;
+ t['a179'] = 0x27A9;
+ t['a193'] = 0x27AA;
+ t['a180'] = 0x27AB;
+ t['a199'] = 0x27AC;
+ t['a181'] = 0x27AD;
+ t['a200'] = 0x27AE;
+ t['a182'] = 0x27AF;
+ t['a201'] = 0x27B1;
+ t['a183'] = 0x27B2;
+ t['a184'] = 0x27B3;
+ t['a197'] = 0x27B4;
+ t['a185'] = 0x27B5;
+ t['a194'] = 0x27B6;
+ t['a198'] = 0x27B7;
+ t['a186'] = 0x27B8;
+ t['a195'] = 0x27B9;
+ t['a187'] = 0x27BA;
+ t['a188'] = 0x27BB;
+ t['a189'] = 0x27BC;
+ t['a190'] = 0x27BD;
+ t['a191'] = 0x27BE;
+ t['a89'] = 0x2768;
+ // 0xF8D7
+ t['a90'] = 0x2769;
+ // 0xF8D8
+ t['a93'] = 0x276A;
+ // 0xF8D9
+ t['a94'] = 0x276B;
+ // 0xF8DA
+ t['a91'] = 0x276C;
+ // 0xF8DB
+ t['a92'] = 0x276D;
+ // 0xF8DC
+ t['a205'] = 0x276E;
+ // 0xF8DD
+ t['a85'] = 0x276F;
+ // 0xF8DE
+ t['a206'] = 0x2770;
+ // 0xF8DF
+ t['a86'] = 0x2771;
+ // 0xF8E0
+ t['a87'] = 0x2772;
+ // 0xF8E1
+ t['a88'] = 0x2773;
+ // 0xF8E2
+ t['a95'] = 0x2774;
+ // 0xF8E3
+ t['a96'] = 0x2775;
+ // 0xF8E4
+ t['.notdef'] = 0x0000;
+ });
+ exports.getGlyphsUnicode = getGlyphsUnicode;
+ exports.getDingbatsGlyphsUnicode = getDingbatsGlyphsUnicode;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreJbig2 = {}, root.pdfjsSharedUtil, root.pdfjsCoreArithmeticDecoder);
+ }(this, function (exports, sharedUtil, coreArithmeticDecoder) {
+ var error = sharedUtil.error;
+ var log2 = sharedUtil.log2;
+ var readInt8 = sharedUtil.readInt8;
+ var readUint16 = sharedUtil.readUint16;
+ var readUint32 = sharedUtil.readUint32;
+ var shadow = sharedUtil.shadow;
+ var ArithmeticDecoder = coreArithmeticDecoder.ArithmeticDecoder;
+ var Jbig2Image = function Jbig2ImageClosure() {
+ // Utility data structures
+ function ContextCache() {
+ }
+ ContextCache.prototype = {
+ getContexts: function (id) {
+ if (id in this) {
+ return this[id];
+ }
+ return this[id] = new Int8Array(1 << 16);
+ }
+ };
+ function DecodingContext(data, start, end) {
+ this.data = data;
+ this.start = start;
+ this.end = end;
+ }
+ DecodingContext.prototype = {
+ get decoder() {
+ var decoder = new ArithmeticDecoder(this.data, this.start, this.end);
+ return shadow(this, 'decoder', decoder);
+ },
+ get contextCache() {
+ var cache = new ContextCache();
+ return shadow(this, 'contextCache', cache);
+ }
+ };
+ // Annex A. Arithmetic Integer Decoding Procedure
+ // A.2 Procedure for decoding values
+ function decodeInteger(contextCache, procedure, decoder) {
+ var contexts = contextCache.getContexts(procedure);
+ var prev = 1;
+ function readBits(length) {
+ var v = 0;
+ for (var i = 0; i < length; i++) {
+ var bit = decoder.readBit(contexts, prev);
+ prev = prev < 256 ? prev << 1 | bit : (prev << 1 | bit) & 511 | 256;
+ v = v << 1 | bit;
+ }
+ return v >>> 0;
+ }
+ var sign = readBits(1);
+ var value = readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(32) + 4436 : readBits(12) + 340 : readBits(8) + 84 : readBits(6) + 20 : readBits(4) + 4 : readBits(2);
+ return sign === 0 ? value : value > 0 ? -value : null;
+ }
+ // A.3 The IAID decoding procedure
+ function decodeIAID(contextCache, decoder, codeLength) {
+ var contexts = contextCache.getContexts('IAID');
+ var prev = 1;
+ for (var i = 0; i < codeLength; i++) {
+ var bit = decoder.readBit(contexts, prev);
+ prev = prev << 1 | bit;
+ }
+ if (codeLength < 31) {
+ return prev & (1 << codeLength) - 1;
+ }
+ return prev & 0x7FFFFFFF;
+ }
+ // 7.3 Segment types
+ var SegmentTypes = [
+ 'SymbolDictionary',
+ null,
+ null,
+ null,
+ 'IntermediateTextRegion',
+ null,
+ 'ImmediateTextRegion',
+ 'ImmediateLosslessTextRegion',
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 'patternDictionary',
+ null,
+ null,
+ null,
+ 'IntermediateHalftoneRegion',
+ null,
+ 'ImmediateHalftoneRegion',
+ 'ImmediateLosslessHalftoneRegion',
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 'IntermediateGenericRegion',
+ null,
+ 'ImmediateGenericRegion',
+ 'ImmediateLosslessGenericRegion',
+ 'IntermediateGenericRefinementRegion',
+ null,
+ 'ImmediateGenericRefinementRegion',
+ 'ImmediateLosslessGenericRefinementRegion',
+ null,
+ null,
+ null,
+ null,
+ 'PageInformation',
+ 'EndOfPage',
+ 'EndOfStripe',
+ 'EndOfFile',
+ 'Profiles',
+ 'Tables',
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 'Extension'
+ ];
+ var CodingTemplates = [
+ [
+ {
+ x: -1,
+ y: -2
+ },
+ {
+ x: 0,
+ y: -2
+ },
+ {
+ x: 1,
+ y: -2
+ },
+ {
+ x: -2,
+ y: -1
+ },
+ {
+ x: -1,
+ y: -1
+ },
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: 1,
+ y: -1
+ },
+ {
+ x: 2,
+ y: -1
+ },
+ {
+ x: -4,
+ y: 0
+ },
+ {
+ x: -3,
+ y: 0
+ },
+ {
+ x: -2,
+ y: 0
+ },
+ {
+ x: -1,
+ y: 0
+ }
+ ],
+ [
+ {
+ x: -1,
+ y: -2
+ },
+ {
+ x: 0,
+ y: -2
+ },
+ {
+ x: 1,
+ y: -2
+ },
+ {
+ x: 2,
+ y: -2
+ },
+ {
+ x: -2,
+ y: -1
+ },
+ {
+ x: -1,
+ y: -1
+ },
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: 1,
+ y: -1
+ },
+ {
+ x: 2,
+ y: -1
+ },
+ {
+ x: -3,
+ y: 0
+ },
+ {
+ x: -2,
+ y: 0
+ },
+ {
+ x: -1,
+ y: 0
+ }
+ ],
+ [
+ {
+ x: -1,
+ y: -2
+ },
+ {
+ x: 0,
+ y: -2
+ },
+ {
+ x: 1,
+ y: -2
+ },
+ {
+ x: -2,
+ y: -1
+ },
+ {
+ x: -1,
+ y: -1
+ },
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: 1,
+ y: -1
+ },
+ {
+ x: -2,
+ y: 0
+ },
+ {
+ x: -1,
+ y: 0
+ }
+ ],
+ [
+ {
+ x: -3,
+ y: -1
+ },
+ {
+ x: -2,
+ y: -1
+ },
+ {
+ x: -1,
+ y: -1
+ },
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: 1,
+ y: -1
+ },
+ {
+ x: -4,
+ y: 0
+ },
+ {
+ x: -3,
+ y: 0
+ },
+ {
+ x: -2,
+ y: 0
+ },
+ {
+ x: -1,
+ y: 0
+ }
+ ]
+ ];
+ var RefinementTemplates = [
+ {
+ coding: [
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: 1,
+ y: -1
+ },
+ {
+ x: -1,
+ y: 0
+ }
+ ],
+ reference: [
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: 1,
+ y: -1
+ },
+ {
+ x: -1,
+ y: 0
+ },
+ {
+ x: 0,
+ y: 0
+ },
+ {
+ x: 1,
+ y: 0
+ },
+ {
+ x: -1,
+ y: 1
+ },
+ {
+ x: 0,
+ y: 1
+ },
+ {
+ x: 1,
+ y: 1
+ }
+ ]
+ },
+ {
+ coding: [
+ {
+ x: -1,
+ y: -1
+ },
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: 1,
+ y: -1
+ },
+ {
+ x: -1,
+ y: 0
+ }
+ ],
+ reference: [
+ {
+ x: 0,
+ y: -1
+ },
+ {
+ x: -1,
+ y: 0
+ },
+ {
+ x: 0,
+ y: 0
+ },
+ {
+ x: 1,
+ y: 0
+ },
+ {
+ x: 0,
+ y: 1
+ },
+ {
+ x: 1,
+ y: 1
+ }
+ ]
+ }
+ ];
+ // See 6.2.5.7 Decoding the bitmap.
+ var ReusedContexts = [
+ 0x9B25,
+ // 10011 0110010 0101
+ 0x0795,
+ // 0011 110010 101
+ 0x00E5,
+ // 001 11001 01
+ 0x0195
+ ];
+ // 011001 0101
+ var RefinementReusedContexts = [
+ 0x0020,
+ // '000' + '0' (coding) + '00010000' + '0' (reference)
+ 0x0008
+ ];
+ // '0000' + '001000'
+ function decodeBitmapTemplate0(width, height, decodingContext) {
+ var decoder = decodingContext.decoder;
+ var contexts = decodingContext.contextCache.getContexts('GB');
+ var contextLabel, i, j, pixel, row, row1, row2, bitmap = [];
+ // ...ooooo....
+ // ..ooooooo... Context template for current pixel (X)
+ // .ooooX...... (concatenate values of 'o'-pixels to get contextLabel)
+ var OLD_PIXEL_MASK = 0x7BF7;
+ // 01111 0111111 0111
+ for (i = 0; i < height; i++) {
+ row = bitmap[i] = new Uint8Array(width);
+ row1 = i < 1 ? row : bitmap[i - 1];
+ row2 = i < 2 ? row : bitmap[i - 2];
+ // At the beginning of each row:
+ // Fill contextLabel with pixels that are above/right of (X)
+ contextLabel = row2[0] << 13 | row2[1] << 12 | row2[2] << 11 | row1[0] << 7 | row1[1] << 6 | row1[2] << 5 | row1[3] << 4;
+ for (j = 0; j < width; j++) {
+ row[j] = pixel = decoder.readBit(contexts, contextLabel);
+ // At each pixel: Clear contextLabel pixels that are shifted
+ // out of the context, then add new ones.
+ contextLabel = (contextLabel & OLD_PIXEL_MASK) << 1 | (j + 3 < width ? row2[j + 3] << 11 : 0) | (j + 4 < width ? row1[j + 4] << 4 : 0) | pixel;
+ }
+ }
+ return bitmap;
+ }
+ // 6.2 Generic Region Decoding Procedure
+ function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at, decodingContext) {
+ if (mmr) {
+ error('JBIG2 error: MMR encoding is not supported');
+ }
+ // Use optimized version for the most common case
+ if (templateIndex === 0 && !skip && !prediction && at.length === 4 && at[0].x === 3 && at[0].y === -1 && at[1].x === -3 && at[1].y === -1 && at[2].x === 2 && at[2].y === -2 && at[3].x === -2 && at[3].y === -2) {
+ return decodeBitmapTemplate0(width, height, decodingContext);
+ }
+ var useskip = !!skip;
+ var template = CodingTemplates[templateIndex].concat(at);
+ // Sorting is non-standard, and it is not required. But sorting increases
+ // the number of template bits that can be reused from the previous
+ // contextLabel in the main loop.
+ template.sort(function (a, b) {
+ return a.y - b.y || a.x - b.x;
+ });
+ var templateLength = template.length;
+ var templateX = new Int8Array(templateLength);
+ var templateY = new Int8Array(templateLength);
+ var changingTemplateEntries = [];
+ var reuseMask = 0, minX = 0, maxX = 0, minY = 0;
+ var c, k;
+ for (k = 0; k < templateLength; k++) {
+ templateX[k] = template[k].x;
+ templateY[k] = template[k].y;
+ minX = Math.min(minX, template[k].x);
+ maxX = Math.max(maxX, template[k].x);
+ minY = Math.min(minY, template[k].y);
+ // Check if the template pixel appears in two consecutive context labels,
+ // so it can be reused. Otherwise, we add it to the list of changing
+ // template entries.
+ if (k < templateLength - 1 && template[k].y === template[k + 1].y && template[k].x === template[k + 1].x - 1) {
+ reuseMask |= 1 << templateLength - 1 - k;
+ } else {
+ changingTemplateEntries.push(k);
+ }
+ }
+ var changingEntriesLength = changingTemplateEntries.length;
+ var changingTemplateX = new Int8Array(changingEntriesLength);
+ var changingTemplateY = new Int8Array(changingEntriesLength);
+ var changingTemplateBit = new Uint16Array(changingEntriesLength);
+ for (c = 0; c < changingEntriesLength; c++) {
+ k = changingTemplateEntries[c];
+ changingTemplateX[c] = template[k].x;
+ changingTemplateY[c] = template[k].y;
+ changingTemplateBit[c] = 1 << templateLength - 1 - k;
+ }
+ // Get the safe bounding box edges from the width, height, minX, maxX, minY
+ var sbb_left = -minX;
+ var sbb_top = -minY;
+ var sbb_right = width - maxX;
+ var pseudoPixelContext = ReusedContexts[templateIndex];
+ var row = new Uint8Array(width);
+ var bitmap = [];
+ var decoder = decodingContext.decoder;
+ var contexts = decodingContext.contextCache.getContexts('GB');
+ var ltp = 0, j, i0, j0, contextLabel = 0, bit, shift;
+ for (var i = 0; i < height; i++) {
+ if (prediction) {
+ var sltp = decoder.readBit(contexts, pseudoPixelContext);
+ ltp ^= sltp;
+ if (ltp) {
+ bitmap.push(row);
+ // duplicate previous row
+ continue;
+ }
+ }
+ row = new Uint8Array(row);
+ bitmap.push(row);
+ for (j = 0; j < width; j++) {
+ if (useskip && skip[i][j]) {
+ row[j] = 0;
+ continue;
+ }
+ // Are we in the middle of a scanline, so we can reuse contextLabel
+ // bits?
+ if (j >= sbb_left && j < sbb_right && i >= sbb_top) {
+ // If yes, we can just shift the bits that are reusable and only
+ // fetch the remaining ones.
+ contextLabel = contextLabel << 1 & reuseMask;
+ for (k = 0; k < changingEntriesLength; k++) {
+ i0 = i + changingTemplateY[k];
+ j0 = j + changingTemplateX[k];
+ bit = bitmap[i0][j0];
+ if (bit) {
+ bit = changingTemplateBit[k];
+ contextLabel |= bit;
+ }
+ }
+ } else {
+ // compute the contextLabel from scratch
+ contextLabel = 0;
+ shift = templateLength - 1;
+ for (k = 0; k < templateLength; k++, shift--) {
+ j0 = j + templateX[k];
+ if (j0 >= 0 && j0 < width) {
+ i0 = i + templateY[k];
+ if (i0 >= 0) {
+ bit = bitmap[i0][j0];
+ if (bit) {
+ contextLabel |= bit << shift;
+ }
+ }
+ }
+ }
+ }
+ var pixel = decoder.readBit(contexts, contextLabel);
+ row[j] = pixel;
+ }
+ }
+ return bitmap;
+ }
+ // 6.3.2 Generic Refinement Region Decoding Procedure
+ function decodeRefinement(width, height, templateIndex, referenceBitmap, offsetX, offsetY, prediction, at, decodingContext) {
+ var codingTemplate = RefinementTemplates[templateIndex].coding;
+ if (templateIndex === 0) {
+ codingTemplate = codingTemplate.concat([at[0]]);
+ }
+ var codingTemplateLength = codingTemplate.length;
+ var codingTemplateX = new Int32Array(codingTemplateLength);
+ var codingTemplateY = new Int32Array(codingTemplateLength);
+ var k;
+ for (k = 0; k < codingTemplateLength; k++) {
+ codingTemplateX[k] = codingTemplate[k].x;
+ codingTemplateY[k] = codingTemplate[k].y;
+ }
+ var referenceTemplate = RefinementTemplates[templateIndex].reference;
+ if (templateIndex === 0) {
+ referenceTemplate = referenceTemplate.concat([at[1]]);
+ }
+ var referenceTemplateLength = referenceTemplate.length;
+ var referenceTemplateX = new Int32Array(referenceTemplateLength);
+ var referenceTemplateY = new Int32Array(referenceTemplateLength);
+ for (k = 0; k < referenceTemplateLength; k++) {
+ referenceTemplateX[k] = referenceTemplate[k].x;
+ referenceTemplateY[k] = referenceTemplate[k].y;
+ }
+ var referenceWidth = referenceBitmap[0].length;
+ var referenceHeight = referenceBitmap.length;
+ var pseudoPixelContext = RefinementReusedContexts[templateIndex];
+ var bitmap = [];
+ var decoder = decodingContext.decoder;
+ var contexts = decodingContext.contextCache.getContexts('GR');
+ var ltp = 0;
+ for (var i = 0; i < height; i++) {
+ if (prediction) {
+ var sltp = decoder.readBit(contexts, pseudoPixelContext);
+ ltp ^= sltp;
+ if (ltp) {
+ error('JBIG2 error: prediction is not supported');
+ }
+ }
+ var row = new Uint8Array(width);
+ bitmap.push(row);
+ for (var j = 0; j < width; j++) {
+ var i0, j0;
+ var contextLabel = 0;
+ for (k = 0; k < codingTemplateLength; k++) {
+ i0 = i + codingTemplateY[k];
+ j0 = j + codingTemplateX[k];
+ if (i0 < 0 || j0 < 0 || j0 >= width) {
+ contextLabel <<= 1;
+ } else
+ // out of bound pixel
+ {
+ contextLabel = contextLabel << 1 | bitmap[i0][j0];
+ }
+ }
+ for (k = 0; k < referenceTemplateLength; k++) {
+ i0 = i + referenceTemplateY[k] + offsetY;
+ j0 = j + referenceTemplateX[k] + offsetX;
+ if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) {
+ contextLabel <<= 1;
+ } else
+ // out of bound pixel
+ {
+ contextLabel = contextLabel << 1 | referenceBitmap[i0][j0];
+ }
+ }
+ var pixel = decoder.readBit(contexts, contextLabel);
+ row[j] = pixel;
+ }
+ }
+ return bitmap;
+ }
+ // 6.5.5 Decoding the symbol dictionary
+ function decodeSymbolDictionary(huffman, refinement, symbols, numberOfNewSymbols, numberOfExportedSymbols, huffmanTables, templateIndex, at, refinementTemplateIndex, refinementAt, decodingContext) {
+ if (huffman) {
+ error('JBIG2 error: huffman is not supported');
+ }
+ var newSymbols = [];
+ var currentHeight = 0;
+ var symbolCodeLength = log2(symbols.length + numberOfNewSymbols);
+ var decoder = decodingContext.decoder;
+ var contextCache = decodingContext.contextCache;
+ while (newSymbols.length < numberOfNewSymbols) {
+ var deltaHeight = decodeInteger(contextCache, 'IADH', decoder);
+ // 6.5.6
+ currentHeight += deltaHeight;
+ var currentWidth = 0;
+ var totalWidth = 0;
+ while (true) {
+ var deltaWidth = decodeInteger(contextCache, 'IADW', decoder);
+ // 6.5.7
+ if (deltaWidth === null) {
+ break;
+ }
+ // OOB
+ currentWidth += deltaWidth;
+ totalWidth += currentWidth;
+ var bitmap;
+ if (refinement) {
+ // 6.5.8.2 Refinement/aggregate-coded symbol bitmap
+ var numberOfInstances = decodeInteger(contextCache, 'IAAI', decoder);
+ if (numberOfInstances > 1) {
+ bitmap = decodeTextRegion(huffman, refinement, currentWidth, currentHeight, 0, numberOfInstances, 1, //strip size
+ symbols.concat(newSymbols), symbolCodeLength, 0, //transposed
+ 0, //ds offset
+ 1, //top left 7.4.3.1.1
+ 0, //OR operator
+ huffmanTables, refinementTemplateIndex, refinementAt, decodingContext);
+ } else {
+ var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
+ var rdx = decodeInteger(contextCache, 'IARDX', decoder);
+ // 6.4.11.3
+ var rdy = decodeInteger(contextCache, 'IARDY', decoder);
+ // 6.4.11.4
+ var symbol = symbolId < symbols.length ? symbols[symbolId] : newSymbols[symbolId - symbols.length];
+ bitmap = decodeRefinement(currentWidth, currentHeight, refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt, decodingContext);
+ }
+ } else {
+ // 6.5.8.1 Direct-coded symbol bitmap
+ bitmap = decodeBitmap(false, currentWidth, currentHeight, templateIndex, false, null, at, decodingContext);
+ }
+ newSymbols.push(bitmap);
+ }
+ }
+ // 6.5.10 Exported symbols
+ var exportedSymbols = [];
+ var flags = [], currentFlag = false;
+ var totalSymbolsLength = symbols.length + numberOfNewSymbols;
+ while (flags.length < totalSymbolsLength) {
+ var runLength = decodeInteger(contextCache, 'IAEX', decoder);
+ while (runLength--) {
+ flags.push(currentFlag);
+ }
+ currentFlag = !currentFlag;
+ }
+ for (var i = 0, ii = symbols.length; i < ii; i++) {
+ if (flags[i]) {
+ exportedSymbols.push(symbols[i]);
+ }
+ }
+ for (var j = 0; j < numberOfNewSymbols; i++, j++) {
+ if (flags[i]) {
+ exportedSymbols.push(newSymbols[j]);
+ }
+ }
+ return exportedSymbols;
+ }
+ function decodeTextRegion(huffman, refinement, width, height, defaultPixelValue, numberOfSymbolInstances, stripSize, inputSymbols, symbolCodeLength, transposed, dsOffset, referenceCorner, combinationOperator, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext) {
+ if (huffman) {
+ error('JBIG2 error: huffman is not supported');
+ }
+ // Prepare bitmap
+ var bitmap = [];
+ var i, row;
+ for (i = 0; i < height; i++) {
+ row = new Uint8Array(width);
+ if (defaultPixelValue) {
+ for (var j = 0; j < width; j++) {
+ row[j] = defaultPixelValue;
+ }
+ }
+ bitmap.push(row);
+ }
+ var decoder = decodingContext.decoder;
+ var contextCache = decodingContext.contextCache;
+ var stripT = -decodeInteger(contextCache, 'IADT', decoder);
+ // 6.4.6
+ var firstS = 0;
+ i = 0;
+ while (i < numberOfSymbolInstances) {
+ var deltaT = decodeInteger(contextCache, 'IADT', decoder);
+ // 6.4.6
+ stripT += deltaT;
+ var deltaFirstS = decodeInteger(contextCache, 'IAFS', decoder);
+ // 6.4.7
+ firstS += deltaFirstS;
+ var currentS = firstS;
+ do {
+ var currentT = stripSize === 1 ? 0 : decodeInteger(contextCache, 'IAIT', decoder);
+ // 6.4.9
+ var t = stripSize * stripT + currentT;
+ var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
+ var applyRefinement = refinement && decodeInteger(contextCache, 'IARI', decoder);
+ var symbolBitmap = inputSymbols[symbolId];
+ var symbolWidth = symbolBitmap[0].length;
+ var symbolHeight = symbolBitmap.length;
+ if (applyRefinement) {
+ var rdw = decodeInteger(contextCache, 'IARDW', decoder);
+ // 6.4.11.1
+ var rdh = decodeInteger(contextCache, 'IARDH', decoder);
+ // 6.4.11.2
+ var rdx = decodeInteger(contextCache, 'IARDX', decoder);
+ // 6.4.11.3
+ var rdy = decodeInteger(contextCache, 'IARDY', decoder);
+ // 6.4.11.4
+ symbolWidth += rdw;
+ symbolHeight += rdh;
+ symbolBitmap = decodeRefinement(symbolWidth, symbolHeight, refinementTemplateIndex, symbolBitmap, (rdw >> 1) + rdx, (rdh >> 1) + rdy, false, refinementAt, decodingContext);
+ }
+ var offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight);
+ var offsetS = currentS - (referenceCorner & 2 ? symbolWidth : 0);
+ var s2, t2, symbolRow;
+ if (transposed) {
+ // Place Symbol Bitmap from T1,S1
+ for (s2 = 0; s2 < symbolHeight; s2++) {
+ row = bitmap[offsetS + s2];
+ if (!row) {
+ continue;
+ }
+ symbolRow = symbolBitmap[s2];
+ // To ignore Parts of Symbol bitmap which goes
+ // outside bitmap region
+ var maxWidth = Math.min(width - offsetT, symbolWidth);
+ switch (combinationOperator) {
+ case 0:
+ // OR
+ for (t2 = 0; t2 < maxWidth; t2++) {
+ row[offsetT + t2] |= symbolRow[t2];
+ }
+ break;
+ case 2:
+ // XOR
+ for (t2 = 0; t2 < maxWidth; t2++) {
+ row[offsetT + t2] ^= symbolRow[t2];
+ }
+ break;
+ default:
+ error('JBIG2 error: operator ' + combinationOperator + ' is not supported');
+ }
+ }
+ currentS += symbolHeight - 1;
+ } else {
+ for (t2 = 0; t2 < symbolHeight; t2++) {
+ row = bitmap[offsetT + t2];
+ if (!row) {
+ continue;
+ }
+ symbolRow = symbolBitmap[t2];
+ switch (combinationOperator) {
+ case 0:
+ // OR
+ for (s2 = 0; s2 < symbolWidth; s2++) {
+ row[offsetS + s2] |= symbolRow[s2];
+ }
+ break;
+ case 2:
+ // XOR
+ for (s2 = 0; s2 < symbolWidth; s2++) {
+ row[offsetS + s2] ^= symbolRow[s2];
+ }
+ break;
+ default:
+ error('JBIG2 error: operator ' + combinationOperator + ' is not supported');
+ }
+ }
+ currentS += symbolWidth - 1;
+ }
+ i++;
+ var deltaS = decodeInteger(contextCache, 'IADS', decoder);
+ // 6.4.8
+ if (deltaS === null) {
+ break;
+ }
+ // OOB
+ currentS += deltaS + dsOffset;
+ } while (true);
+ }
+ return bitmap;
+ }
+ function readSegmentHeader(data, start) {
+ var segmentHeader = {};
+ segmentHeader.number = readUint32(data, start);
+ var flags = data[start + 4];
+ var segmentType = flags & 0x3F;
+ if (!SegmentTypes[segmentType]) {
+ error('JBIG2 error: invalid segment type: ' + segmentType);
+ }
+ segmentHeader.type = segmentType;
+ segmentHeader.typeName = SegmentTypes[segmentType];
+ segmentHeader.deferredNonRetain = !!(flags & 0x80);
+ var pageAssociationFieldSize = !!(flags & 0x40);
+ var referredFlags = data[start + 5];
+ var referredToCount = referredFlags >> 5 & 7;
+ var retainBits = [referredFlags & 31];
+ var position = start + 6;
+ if (referredFlags === 7) {
+ referredToCount = readUint32(data, position - 1) & 0x1FFFFFFF;
+ position += 3;
+ var bytes = referredToCount + 7 >> 3;
+ retainBits[0] = data[position++];
+ while (--bytes > 0) {
+ retainBits.push(data[position++]);
+ }
+ } else if (referredFlags === 5 || referredFlags === 6) {
+ error('JBIG2 error: invalid referred-to flags');
+ }
+ segmentHeader.retainBits = retainBits;
+ var referredToSegmentNumberSize = segmentHeader.number <= 256 ? 1 : segmentHeader.number <= 65536 ? 2 : 4;
+ var referredTo = [];
+ var i, ii;
+ for (i = 0; i < referredToCount; i++) {
+ var number = referredToSegmentNumberSize === 1 ? data[position] : referredToSegmentNumberSize === 2 ? readUint16(data, position) : readUint32(data, position);
+ referredTo.push(number);
+ position += referredToSegmentNumberSize;
+ }
+ segmentHeader.referredTo = referredTo;
+ if (!pageAssociationFieldSize) {
+ segmentHeader.pageAssociation = data[position++];
+ } else {
+ segmentHeader.pageAssociation = readUint32(data, position);
+ position += 4;
+ }
+ segmentHeader.length = readUint32(data, position);
+ position += 4;
+ if (segmentHeader.length === 0xFFFFFFFF) {
+ // 7.2.7 Segment data length, unknown segment length
+ if (segmentType === 38) {
+ // ImmediateGenericRegion
+ var genericRegionInfo = readRegionSegmentInformation(data, position);
+ var genericRegionSegmentFlags = data[position + RegionSegmentInformationFieldLength];
+ var genericRegionMmr = !!(genericRegionSegmentFlags & 1);
+ // searching for the segment end
+ var searchPatternLength = 6;
+ var searchPattern = new Uint8Array(searchPatternLength);
+ if (!genericRegionMmr) {
+ searchPattern[0] = 0xFF;
+ searchPattern[1] = 0xAC;
+ }
+ searchPattern[2] = genericRegionInfo.height >>> 24 & 0xFF;
+ searchPattern[3] = genericRegionInfo.height >> 16 & 0xFF;
+ searchPattern[4] = genericRegionInfo.height >> 8 & 0xFF;
+ searchPattern[5] = genericRegionInfo.height & 0xFF;
+ for (i = position, ii = data.length; i < ii; i++) {
+ var j = 0;
+ while (j < searchPatternLength && searchPattern[j] === data[i + j]) {
+ j++;
+ }
+ if (j === searchPatternLength) {
+ segmentHeader.length = i + searchPatternLength;
+ break;
+ }
+ }
+ if (segmentHeader.length === 0xFFFFFFFF) {
+ error('JBIG2 error: segment end was not found');
+ }
+ } else {
+ error('JBIG2 error: invalid unknown segment length');
+ }
+ }
+ segmentHeader.headerEnd = position;
+ return segmentHeader;
+ }
+ function readSegments(header, data, start, end) {
+ var segments = [];
+ var position = start;
+ while (position < end) {
+ var segmentHeader = readSegmentHeader(data, position);
+ position = segmentHeader.headerEnd;
+ var segment = {
+ header: segmentHeader,
+ data: data
+ };
+ if (!header.randomAccess) {
+ segment.start = position;
+ position += segmentHeader.length;
+ segment.end = position;
+ }
+ segments.push(segment);
+ if (segmentHeader.type === 51) {
+ break;
+ }
+ }
+ // end of file is found
+ if (header.randomAccess) {
+ for (var i = 0, ii = segments.length; i < ii; i++) {
+ segments[i].start = position;
+ position += segments[i].header.length;
+ segments[i].end = position;
+ }
+ }
+ return segments;
+ }
+ // 7.4.1 Region segment information field
+ function readRegionSegmentInformation(data, start) {
+ return {
+ width: readUint32(data, start),
+ height: readUint32(data, start + 4),
+ x: readUint32(data, start + 8),
+ y: readUint32(data, start + 12),
+ combinationOperator: data[start + 16] & 7
+ };
+ }
+ var RegionSegmentInformationFieldLength = 17;
+ function processSegment(segment, visitor) {
+ var header = segment.header;
+ var data = segment.data, position = segment.start, end = segment.end;
+ var args, at, i, atLength;
+ switch (header.type) {
+ case 0:
+ // SymbolDictionary
+ // 7.4.2 Symbol dictionary segment syntax
+ var dictionary = {};
+ var dictionaryFlags = readUint16(data, position);
+ // 7.4.2.1.1
+ dictionary.huffman = !!(dictionaryFlags & 1);
+ dictionary.refinement = !!(dictionaryFlags & 2);
+ dictionary.huffmanDHSelector = dictionaryFlags >> 2 & 3;
+ dictionary.huffmanDWSelector = dictionaryFlags >> 4 & 3;
+ dictionary.bitmapSizeSelector = dictionaryFlags >> 6 & 1;
+ dictionary.aggregationInstancesSelector = dictionaryFlags >> 7 & 1;
+ dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256);
+ dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512);
+ dictionary.template = dictionaryFlags >> 10 & 3;
+ dictionary.refinementTemplate = dictionaryFlags >> 12 & 1;
+ position += 2;
+ if (!dictionary.huffman) {
+ atLength = dictionary.template === 0 ? 4 : 1;
+ at = [];
+ for (i = 0; i < atLength; i++) {
+ at.push({
+ x: readInt8(data, position),
+ y: readInt8(data, position + 1)
+ });
+ position += 2;
+ }
+ dictionary.at = at;
+ }
+ if (dictionary.refinement && !dictionary.refinementTemplate) {
+ at = [];
+ for (i = 0; i < 2; i++) {
+ at.push({
+ x: readInt8(data, position),
+ y: readInt8(data, position + 1)
+ });
+ position += 2;
+ }
+ dictionary.refinementAt = at;
+ }
+ dictionary.numberOfExportedSymbols = readUint32(data, position);
+ position += 4;
+ dictionary.numberOfNewSymbols = readUint32(data, position);
+ position += 4;
+ args = [
+ dictionary,
+ header.number,
+ header.referredTo,
+ data,
+ position,
+ end
+ ];
+ break;
+ case 6:
+ // ImmediateTextRegion
+ case 7:
+ // ImmediateLosslessTextRegion
+ var textRegion = {};
+ textRegion.info = readRegionSegmentInformation(data, position);
+ position += RegionSegmentInformationFieldLength;
+ var textRegionSegmentFlags = readUint16(data, position);
+ position += 2;
+ textRegion.huffman = !!(textRegionSegmentFlags & 1);
+ textRegion.refinement = !!(textRegionSegmentFlags & 2);
+ textRegion.stripSize = 1 << (textRegionSegmentFlags >> 2 & 3);
+ textRegion.referenceCorner = textRegionSegmentFlags >> 4 & 3;
+ textRegion.transposed = !!(textRegionSegmentFlags & 64);
+ textRegion.combinationOperator = textRegionSegmentFlags >> 7 & 3;
+ textRegion.defaultPixelValue = textRegionSegmentFlags >> 9 & 1;
+ textRegion.dsOffset = textRegionSegmentFlags << 17 >> 27;
+ textRegion.refinementTemplate = textRegionSegmentFlags >> 15 & 1;
+ if (textRegion.huffman) {
+ var textRegionHuffmanFlags = readUint16(data, position);
+ position += 2;
+ textRegion.huffmanFS = textRegionHuffmanFlags & 3;
+ textRegion.huffmanDS = textRegionHuffmanFlags >> 2 & 3;
+ textRegion.huffmanDT = textRegionHuffmanFlags >> 4 & 3;
+ textRegion.huffmanRefinementDW = textRegionHuffmanFlags >> 6 & 3;
+ textRegion.huffmanRefinementDH = textRegionHuffmanFlags >> 8 & 3;
+ textRegion.huffmanRefinementDX = textRegionHuffmanFlags >> 10 & 3;
+ textRegion.huffmanRefinementDY = textRegionHuffmanFlags >> 12 & 3;
+ textRegion.huffmanRefinementSizeSelector = !!(textRegionHuffmanFlags & 14);
+ }
+ if (textRegion.refinement && !textRegion.refinementTemplate) {
+ at = [];
+ for (i = 0; i < 2; i++) {
+ at.push({
+ x: readInt8(data, position),
+ y: readInt8(data, position + 1)
+ });
+ position += 2;
+ }
+ textRegion.refinementAt = at;
+ }
+ textRegion.numberOfSymbolInstances = readUint32(data, position);
+ position += 4;
+ // TODO 7.4.3.1.7 Symbol ID Huffman table decoding
+ if (textRegion.huffman) {
+ error('JBIG2 error: huffman is not supported');
+ }
+ args = [
+ textRegion,
+ header.referredTo,
+ data,
+ position,
+ end
+ ];
+ break;
+ case 38:
+ // ImmediateGenericRegion
+ case 39:
+ // ImmediateLosslessGenericRegion
+ var genericRegion = {};
+ genericRegion.info = readRegionSegmentInformation(data, position);
+ position += RegionSegmentInformationFieldLength;
+ var genericRegionSegmentFlags = data[position++];
+ genericRegion.mmr = !!(genericRegionSegmentFlags & 1);
+ genericRegion.template = genericRegionSegmentFlags >> 1 & 3;
+ genericRegion.prediction = !!(genericRegionSegmentFlags & 8);
+ if (!genericRegion.mmr) {
+ atLength = genericRegion.template === 0 ? 4 : 1;
+ at = [];
+ for (i = 0; i < atLength; i++) {
+ at.push({
+ x: readInt8(data, position),
+ y: readInt8(data, position + 1)
+ });
+ position += 2;
+ }
+ genericRegion.at = at;
+ }
+ args = [
+ genericRegion,
+ data,
+ position,
+ end
+ ];
+ break;
+ case 48:
+ // PageInformation
+ var pageInfo = {
+ width: readUint32(data, position),
+ height: readUint32(data, position + 4),
+ resolutionX: readUint32(data, position + 8),
+ resolutionY: readUint32(data, position + 12)
+ };
+ if (pageInfo.height === 0xFFFFFFFF) {
+ delete pageInfo.height;
+ }
+ var pageSegmentFlags = data[position + 16];
+ var pageStripingInformation = readUint16(data, position + 17);
+ pageInfo.lossless = !!(pageSegmentFlags & 1);
+ pageInfo.refinement = !!(pageSegmentFlags & 2);
+ pageInfo.defaultPixelValue = pageSegmentFlags >> 2 & 1;
+ pageInfo.combinationOperator = pageSegmentFlags >> 3 & 3;
+ pageInfo.requiresBuffer = !!(pageSegmentFlags & 32);
+ pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64);
+ args = [pageInfo];
+ break;
+ case 49:
+ // EndOfPage
+ break;
+ case 50:
+ // EndOfStripe
+ break;
+ case 51:
+ // EndOfFile
+ break;
+ case 62:
+ // 7.4.15 defines 2 extension types which
+ // are comments and can be ignored.
+ break;
+ default:
+ error('JBIG2 error: segment type ' + header.typeName + '(' + header.type + ') is not implemented');
+ }
+ var callbackName = 'on' + header.typeName;
+ if (callbackName in visitor) {
+ visitor[callbackName].apply(visitor, args);
+ }
+ }
+ function processSegments(segments, visitor) {
+ for (var i = 0, ii = segments.length; i < ii; i++) {
+ processSegment(segments[i], visitor);
+ }
+ }
+ function parseJbig2(data, start, end) {
+ var position = start;
+ if (data[position] !== 0x97 || data[position + 1] !== 0x4A || data[position + 2] !== 0x42 || data[position + 3] !== 0x32 || data[position + 4] !== 0x0D || data[position + 5] !== 0x0A || data[position + 6] !== 0x1A || data[position + 7] !== 0x0A) {
+ error('JBIG2 error: invalid header');
+ }
+ var header = {};
+ position += 8;
+ var flags = data[position++];
+ header.randomAccess = !(flags & 1);
+ if (!(flags & 2)) {
+ header.numberOfPages = readUint32(data, position);
+ position += 4;
+ }
+ var segments = readSegments(header, data, position, end);
+ error('Not implemented');
+ }
+ // processSegments(segments, new SimpleSegmentVisitor());
+ function parseJbig2Chunks(chunks) {
+ var visitor = new SimpleSegmentVisitor();
+ for (var i = 0, ii = chunks.length; i < ii; i++) {
+ var chunk = chunks[i];
+ var segments = readSegments({}, chunk.data, chunk.start, chunk.end);
+ processSegments(segments, visitor);
+ }
+ return visitor.buffer;
+ }
+ function SimpleSegmentVisitor() {
+ }
+ SimpleSegmentVisitor.prototype = {
+ onPageInformation: function SimpleSegmentVisitor_onPageInformation(info) {
+ this.currentPageInfo = info;
+ var rowSize = info.width + 7 >> 3;
+ var buffer = new Uint8Array(rowSize * info.height);
+ // The contents of ArrayBuffers are initialized to 0.
+ // Fill the buffer with 0xFF only if info.defaultPixelValue is set
+ if (info.defaultPixelValue) {
+ for (var i = 0, ii = buffer.length; i < ii; i++) {
+ buffer[i] = 0xFF;
+ }
+ }
+ this.buffer = buffer;
+ },
+ drawBitmap: function SimpleSegmentVisitor_drawBitmap(regionInfo, bitmap) {
+ var pageInfo = this.currentPageInfo;
+ var width = regionInfo.width, height = regionInfo.height;
+ var rowSize = pageInfo.width + 7 >> 3;
+ var combinationOperator = pageInfo.combinationOperatorOverride ? regionInfo.combinationOperator : pageInfo.combinationOperator;
+ var buffer = this.buffer;
+ var mask0 = 128 >> (regionInfo.x & 7);
+ var offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3);
+ var i, j, mask, offset;
+ switch (combinationOperator) {
+ case 0:
+ // OR
+ for (i = 0; i < height; i++) {
+ mask = mask0;
+ offset = offset0;
+ for (j = 0; j < width; j++) {
+ if (bitmap[i][j]) {
+ buffer[offset] |= mask;
+ }
+ mask >>= 1;
+ if (!mask) {
+ mask = 128;
+ offset++;
+ }
+ }
+ offset0 += rowSize;
+ }
+ break;
+ case 2:
+ // XOR
+ for (i = 0; i < height; i++) {
+ mask = mask0;
+ offset = offset0;
+ for (j = 0; j < width; j++) {
+ if (bitmap[i][j]) {
+ buffer[offset] ^= mask;
+ }
+ mask >>= 1;
+ if (!mask) {
+ mask = 128;
+ offset++;
+ }
+ }
+ offset0 += rowSize;
+ }
+ break;
+ default:
+ error('JBIG2 error: operator ' + combinationOperator + ' is not supported');
+ }
+ },
+ onImmediateGenericRegion: function SimpleSegmentVisitor_onImmediateGenericRegion(region, data, start, end) {
+ var regionInfo = region.info;
+ var decodingContext = new DecodingContext(data, start, end);
+ var bitmap = decodeBitmap(region.mmr, regionInfo.width, regionInfo.height, region.template, region.prediction, null, region.at, decodingContext);
+ this.drawBitmap(regionInfo, bitmap);
+ },
+ onImmediateLosslessGenericRegion: function SimpleSegmentVisitor_onImmediateLosslessGenericRegion() {
+ this.onImmediateGenericRegion.apply(this, arguments);
+ },
+ onSymbolDictionary: function SimpleSegmentVisitor_onSymbolDictionary(dictionary, currentSegment, referredSegments, data, start, end) {
+ var huffmanTables;
+ if (dictionary.huffman) {
+ error('JBIG2 error: huffman is not supported');
+ }
+ // Combines exported symbols from all referred segments
+ var symbols = this.symbols;
+ if (!symbols) {
+ this.symbols = symbols = {};
+ }
+ var inputSymbols = [];
+ for (var i = 0, ii = referredSegments.length; i < ii; i++) {
+ inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
+ }
+ var decodingContext = new DecodingContext(data, start, end);
+ symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman, dictionary.refinement, inputSymbols, dictionary.numberOfNewSymbols, dictionary.numberOfExportedSymbols, huffmanTables, dictionary.template, dictionary.at, dictionary.refinementTemplate, dictionary.refinementAt, decodingContext);
+ },
+ onImmediateTextRegion: function SimpleSegmentVisitor_onImmediateTextRegion(region, referredSegments, data, start, end) {
+ var regionInfo = region.info;
+ var huffmanTables;
+ // Combines exported symbols from all referred segments
+ var symbols = this.symbols;
+ var inputSymbols = [];
+ for (var i = 0, ii = referredSegments.length; i < ii; i++) {
+ inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
+ }
+ var symbolCodeLength = log2(inputSymbols.length);
+ var decodingContext = new DecodingContext(data, start, end);
+ var bitmap = decodeTextRegion(region.huffman, region.refinement, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.numberOfSymbolInstances, region.stripSize, inputSymbols, symbolCodeLength, region.transposed, region.dsOffset, region.referenceCorner, region.combinationOperator, huffmanTables, region.refinementTemplate, region.refinementAt, decodingContext);
+ this.drawBitmap(regionInfo, bitmap);
+ },
+ onImmediateLosslessTextRegion: function SimpleSegmentVisitor_onImmediateLosslessTextRegion() {
+ this.onImmediateTextRegion.apply(this, arguments);
+ }
+ };
+ function Jbig2Image() {
+ }
+ Jbig2Image.prototype = {
+ parseChunks: function Jbig2Image_parseChunks(chunks) {
+ return parseJbig2Chunks(chunks);
+ }
+ };
+ return Jbig2Image;
+ }();
+ exports.Jbig2Image = Jbig2Image;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreJpg = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var error = sharedUtil.error;
+ /**
+ * This code was forked from https://github.com/notmasteryet/jpgjs.
+ * The original version was created by GitHub user notmasteryet.
+ *
+ * - The JPEG specification can be found in the ITU CCITT Recommendation T.81
+ * (www.w3.org/Graphics/JPEG/itu-t81.pdf)
+ * - The JFIF specification can be found in the JPEG File Interchange Format
+ * (www.w3.org/Graphics/JPEG/jfif3.pdf)
+ * - The Adobe Application-Specific JPEG markers in the
+ * Supporting the DCT Filters in PostScript Level 2, Technical Note #5116
+ * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
+ */
+ var JpegImage = function JpegImageClosure() {
+ var dctZigZag = new Uint8Array([
+ 0,
+ 1,
+ 8,
+ 16,
+ 9,
+ 2,
+ 3,
+ 10,
+ 17,
+ 24,
+ 32,
+ 25,
+ 18,
+ 11,
+ 4,
+ 5,
+ 12,
+ 19,
+ 26,
+ 33,
+ 40,
+ 48,
+ 41,
+ 34,
+ 27,
+ 20,
+ 13,
+ 6,
+ 7,
+ 14,
+ 21,
+ 28,
+ 35,
+ 42,
+ 49,
+ 56,
+ 57,
+ 50,
+ 43,
+ 36,
+ 29,
+ 22,
+ 15,
+ 23,
+ 30,
+ 37,
+ 44,
+ 51,
+ 58,
+ 59,
+ 52,
+ 45,
+ 38,
+ 31,
+ 39,
+ 46,
+ 53,
+ 60,
+ 61,
+ 54,
+ 47,
+ 55,
+ 62,
+ 63
+ ]);
+ var dctCos1 = 4017;
+ // cos(pi/16)
+ var dctSin1 = 799;
+ // sin(pi/16)
+ var dctCos3 = 3406;
+ // cos(3*pi/16)
+ var dctSin3 = 2276;
+ // sin(3*pi/16)
+ var dctCos6 = 1567;
+ // cos(6*pi/16)
+ var dctSin6 = 3784;
+ // sin(6*pi/16)
+ var dctSqrt2 = 5793;
+ // sqrt(2)
+ var dctSqrt1d2 = 2896;
+ // sqrt(2) / 2
+ function JpegImage() {
+ this.decodeTransform = null;
+ this.colorTransform = -1;
+ }
+ function buildHuffmanTable(codeLengths, values) {
+ var k = 0, code = [], i, j, length = 16;
+ while (length > 0 && !codeLengths[length - 1]) {
+ length--;
+ }
+ code.push({
+ children: [],
+ index: 0
+ });
+ var p = code[0], q;
+ for (i = 0; i < length; i++) {
+ for (j = 0; j < codeLengths[i]; j++) {
+ p = code.pop();
+ p.children[p.index] = values[k];
+ while (p.index > 0) {
+ p = code.pop();
+ }
+ p.index++;
+ code.push(p);
+ while (code.length <= i) {
+ code.push(q = {
+ children: [],
+ index: 0
+ });
+ p.children[p.index] = q.children;
+ p = q;
+ }
+ k++;
+ }
+ if (i + 1 < length) {
+ // p here points to last code
+ code.push(q = {
+ children: [],
+ index: 0
+ });
+ p.children[p.index] = q.children;
+ p = q;
+ }
+ }
+ return code[0].children;
+ }
+ function getBlockBufferOffset(component, row, col) {
+ return 64 * ((component.blocksPerLine + 1) * row + col);
+ }
+ function decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successivePrev, successive) {
+ var mcusPerLine = frame.mcusPerLine;
+ var progressive = frame.progressive;
+ var startOffset = offset, bitsData = 0, bitsCount = 0;
+ function readBit() {
+ if (bitsCount > 0) {
+ bitsCount--;
+ return bitsData >> bitsCount & 1;
+ }
+ bitsData = data[offset++];
+ if (bitsData === 0xFF) {
+ var nextByte = data[offset++];
+ if (nextByte) {
+ error('JPEG error: unexpected marker ' + (bitsData << 8 | nextByte).toString(16));
+ }
+ }
+ // unstuff 0
+ bitsCount = 7;
+ return bitsData >>> 7;
+ }
+ function decodeHuffman(tree) {
+ var node = tree;
+ while (true) {
+ node = node[readBit()];
+ if (typeof node === 'number') {
+ return node;
+ }
+ if (typeof node !== 'object') {
+ error('JPEG error: invalid huffman sequence');
+ }
+ }
+ }
+ function receive(length) {
+ var n = 0;
+ while (length > 0) {
+ n = n << 1 | readBit();
+ length--;
+ }
+ return n;
+ }
+ function receiveAndExtend(length) {
+ if (length === 1) {
+ return readBit() === 1 ? 1 : -1;
+ }
+ var n = receive(length);
+ if (n >= 1 << length - 1) {
+ return n;
+ }
+ return n + (-1 << length) + 1;
+ }
+ function decodeBaseline(component, offset) {
+ var t = decodeHuffman(component.huffmanTableDC);
+ var diff = t === 0 ? 0 : receiveAndExtend(t);
+ component.blockData[offset] = component.pred += diff;
+ var k = 1;
+ while (k < 64) {
+ var rs = decodeHuffman(component.huffmanTableAC);
+ var s = rs & 15, r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ break;
+ }
+ k += 16;
+ continue;
+ }
+ k += r;
+ var z = dctZigZag[k];
+ component.blockData[offset + z] = receiveAndExtend(s);
+ k++;
+ }
+ }
+ function decodeDCFirst(component, offset) {
+ var t = decodeHuffman(component.huffmanTableDC);
+ var diff = t === 0 ? 0 : receiveAndExtend(t) << successive;
+ component.blockData[offset] = component.pred += diff;
+ }
+ function decodeDCSuccessive(component, offset) {
+ component.blockData[offset] |= readBit() << successive;
+ }
+ var eobrun = 0;
+ function decodeACFirst(component, offset) {
+ if (eobrun > 0) {
+ eobrun--;
+ return;
+ }
+ var k = spectralStart, e = spectralEnd;
+ while (k <= e) {
+ var rs = decodeHuffman(component.huffmanTableAC);
+ var s = rs & 15, r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r) - 1;
+ break;
+ }
+ k += 16;
+ continue;
+ }
+ k += r;
+ var z = dctZigZag[k];
+ component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive);
+ k++;
+ }
+ }
+ var successiveACState = 0, successiveACNextValue;
+ function decodeACSuccessive(component, offset) {
+ var k = spectralStart;
+ var e = spectralEnd;
+ var r = 0;
+ var s;
+ var rs;
+ while (k <= e) {
+ var z = dctZigZag[k];
+ switch (successiveACState) {
+ case 0:
+ // initial state
+ rs = decodeHuffman(component.huffmanTableAC);
+ s = rs & 15;
+ r = rs >> 4;
+ if (s === 0) {
+ if (r < 15) {
+ eobrun = receive(r) + (1 << r);
+ successiveACState = 4;
+ } else {
+ r = 16;
+ successiveACState = 1;
+ }
+ } else {
+ if (s !== 1) {
+ error('JPEG error: invalid ACn encoding');
+ }
+ successiveACNextValue = receiveAndExtend(s);
+ successiveACState = r ? 2 : 3;
+ }
+ continue;
+ case 1:
+ // skipping r zero items
+ case 2:
+ if (component.blockData[offset + z]) {
+ component.blockData[offset + z] += readBit() << successive;
+ } else {
+ r--;
+ if (r === 0) {
+ successiveACState = successiveACState === 2 ? 3 : 0;
+ }
+ }
+ break;
+ case 3:
+ // set value for a zero item
+ if (component.blockData[offset + z]) {
+ component.blockData[offset + z] += readBit() << successive;
+ } else {
+ component.blockData[offset + z] = successiveACNextValue << successive;
+ successiveACState = 0;
+ }
+ break;
+ case 4:
+ // eob
+ if (component.blockData[offset + z]) {
+ component.blockData[offset + z] += readBit() << successive;
+ }
+ break;
+ }
+ k++;
+ }
+ if (successiveACState === 4) {
+ eobrun--;
+ if (eobrun === 0) {
+ successiveACState = 0;
+ }
+ }
+ }
+ function decodeMcu(component, decode, mcu, row, col) {
+ var mcuRow = mcu / mcusPerLine | 0;
+ var mcuCol = mcu % mcusPerLine;
+ var blockRow = mcuRow * component.v + row;
+ var blockCol = mcuCol * component.h + col;
+ var offset = getBlockBufferOffset(component, blockRow, blockCol);
+ decode(component, offset);
+ }
+ function decodeBlock(component, decode, mcu) {
+ var blockRow = mcu / component.blocksPerLine | 0;
+ var blockCol = mcu % component.blocksPerLine;
+ var offset = getBlockBufferOffset(component, blockRow, blockCol);
+ decode(component, offset);
+ }
+ var componentsLength = components.length;
+ var component, i, j, k, n;
+ var decodeFn;
+ if (progressive) {
+ if (spectralStart === 0) {
+ decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
+ } else {
+ decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
+ }
+ } else {
+ decodeFn = decodeBaseline;
+ }
+ var mcu = 0, marker;
+ var mcuExpected;
+ if (componentsLength === 1) {
+ mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
+ } else {
+ mcuExpected = mcusPerLine * frame.mcusPerColumn;
+ }
+ if (!resetInterval) {
+ resetInterval = mcuExpected;
+ }
+ var h, v;
+ while (mcu < mcuExpected) {
+ // reset interval stuff
+ for (i = 0; i < componentsLength; i++) {
+ components[i].pred = 0;
+ }
+ eobrun = 0;
+ if (componentsLength === 1) {
+ component = components[0];
+ for (n = 0; n < resetInterval; n++) {
+ decodeBlock(component, decodeFn, mcu);
+ mcu++;
+ }
+ } else {
+ for (n = 0; n < resetInterval; n++) {
+ for (i = 0; i < componentsLength; i++) {
+ component = components[i];
+ h = component.h;
+ v = component.v;
+ for (j = 0; j < v; j++) {
+ for (k = 0; k < h; k++) {
+ decodeMcu(component, decodeFn, mcu, j, k);
+ }
+ }
+ }
+ mcu++;
+ }
+ }
+ // find marker
+ bitsCount = 0;
+ marker = data[offset] << 8 | data[offset + 1];
+ // Some bad images seem to pad Scan blocks with zero bytes, skip past
+ // those to attempt to find a valid marker (fixes issue4090.pdf).
+ while (data[offset] === 0x00 && offset < data.length - 1) {
+ offset++;
+ marker = data[offset] << 8 | data[offset + 1];
+ }
+ if (marker <= 0xFF00) {
+ error('JPEG error: marker was not found');
+ }
+ if (marker >= 0xFFD0 && marker <= 0xFFD7) {
+ // RSTx
+ offset += 2;
+ } else {
+ break;
+ }
+ }
+ return offset - startOffset;
+ }
+ // A port of poppler's IDCT method which in turn is taken from:
+ // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+ // 'Practical Fast 1-D DCT Algorithms with 11 Multiplications',
+ // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
+ // 988-991.
+ function quantizeAndInverse(component, blockBufferOffset, p) {
+ var qt = component.quantizationTable, blockData = component.blockData;
+ var v0, v1, v2, v3, v4, v5, v6, v7;
+ var p0, p1, p2, p3, p4, p5, p6, p7;
+ var t;
+ if (!qt) {
+ error('JPEG error: missing required Quantization Table.');
+ }
+ // inverse DCT on rows
+ for (var row = 0; row < 64; row += 8) {
+ // gather block data
+ p0 = blockData[blockBufferOffset + row];
+ p1 = blockData[blockBufferOffset + row + 1];
+ p2 = blockData[blockBufferOffset + row + 2];
+ p3 = blockData[blockBufferOffset + row + 3];
+ p4 = blockData[blockBufferOffset + row + 4];
+ p5 = blockData[blockBufferOffset + row + 5];
+ p6 = blockData[blockBufferOffset + row + 6];
+ p7 = blockData[blockBufferOffset + row + 7];
+ // dequant p0
+ p0 *= qt[row];
+ // check for all-zero AC coefficients
+ if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
+ t = dctSqrt2 * p0 + 512 >> 10;
+ p[row] = t;
+ p[row + 1] = t;
+ p[row + 2] = t;
+ p[row + 3] = t;
+ p[row + 4] = t;
+ p[row + 5] = t;
+ p[row + 6] = t;
+ p[row + 7] = t;
+ continue;
+ }
+ // dequant p1 ... p7
+ p1 *= qt[row + 1];
+ p2 *= qt[row + 2];
+ p3 *= qt[row + 3];
+ p4 *= qt[row + 4];
+ p5 *= qt[row + 5];
+ p6 *= qt[row + 6];
+ p7 *= qt[row + 7];
+ // stage 4
+ v0 = dctSqrt2 * p0 + 128 >> 8;
+ v1 = dctSqrt2 * p4 + 128 >> 8;
+ v2 = p2;
+ v3 = p6;
+ v4 = dctSqrt1d2 * (p1 - p7) + 128 >> 8;
+ v7 = dctSqrt1d2 * (p1 + p7) + 128 >> 8;
+ v5 = p3 << 4;
+ v6 = p5 << 4;
+ // stage 3
+ v0 = v0 + v1 + 1 >> 1;
+ v1 = v0 - v1;
+ t = v2 * dctSin6 + v3 * dctCos6 + 128 >> 8;
+ v2 = v2 * dctCos6 - v3 * dctSin6 + 128 >> 8;
+ v3 = t;
+ v4 = v4 + v6 + 1 >> 1;
+ v6 = v4 - v6;
+ v7 = v7 + v5 + 1 >> 1;
+ v5 = v7 - v5;
+ // stage 2
+ v0 = v0 + v3 + 1 >> 1;
+ v3 = v0 - v3;
+ v1 = v1 + v2 + 1 >> 1;
+ v2 = v1 - v2;
+ t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
+ v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
+ v7 = t;
+ t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
+ v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
+ v6 = t;
+ // stage 1
+ p[row] = v0 + v7;
+ p[row + 7] = v0 - v7;
+ p[row + 1] = v1 + v6;
+ p[row + 6] = v1 - v6;
+ p[row + 2] = v2 + v5;
+ p[row + 5] = v2 - v5;
+ p[row + 3] = v3 + v4;
+ p[row + 4] = v3 - v4;
+ }
+ // inverse DCT on columns
+ for (var col = 0; col < 8; ++col) {
+ p0 = p[col];
+ p1 = p[col + 8];
+ p2 = p[col + 16];
+ p3 = p[col + 24];
+ p4 = p[col + 32];
+ p5 = p[col + 40];
+ p6 = p[col + 48];
+ p7 = p[col + 56];
+ // check for all-zero AC coefficients
+ if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
+ t = dctSqrt2 * p0 + 8192 >> 14;
+ // convert to 8 bit
+ t = t < -2040 ? 0 : t >= 2024 ? 255 : t + 2056 >> 4;
+ blockData[blockBufferOffset + col] = t;
+ blockData[blockBufferOffset + col + 8] = t;
+ blockData[blockBufferOffset + col + 16] = t;
+ blockData[blockBufferOffset + col + 24] = t;
+ blockData[blockBufferOffset + col + 32] = t;
+ blockData[blockBufferOffset + col + 40] = t;
+ blockData[blockBufferOffset + col + 48] = t;
+ blockData[blockBufferOffset + col + 56] = t;
+ continue;
+ }
+ // stage 4
+ v0 = dctSqrt2 * p0 + 2048 >> 12;
+ v1 = dctSqrt2 * p4 + 2048 >> 12;
+ v2 = p2;
+ v3 = p6;
+ v4 = dctSqrt1d2 * (p1 - p7) + 2048 >> 12;
+ v7 = dctSqrt1d2 * (p1 + p7) + 2048 >> 12;
+ v5 = p3;
+ v6 = p5;
+ // stage 3
+ // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when
+ // converting to UInt8 range later.
+ v0 = (v0 + v1 + 1 >> 1) + 4112;
+ v1 = v0 - v1;
+ t = v2 * dctSin6 + v3 * dctCos6 + 2048 >> 12;
+ v2 = v2 * dctCos6 - v3 * dctSin6 + 2048 >> 12;
+ v3 = t;
+ v4 = v4 + v6 + 1 >> 1;
+ v6 = v4 - v6;
+ v7 = v7 + v5 + 1 >> 1;
+ v5 = v7 - v5;
+ // stage 2
+ v0 = v0 + v3 + 1 >> 1;
+ v3 = v0 - v3;
+ v1 = v1 + v2 + 1 >> 1;
+ v2 = v1 - v2;
+ t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
+ v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
+ v7 = t;
+ t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
+ v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
+ v6 = t;
+ // stage 1
+ p0 = v0 + v7;
+ p7 = v0 - v7;
+ p1 = v1 + v6;
+ p6 = v1 - v6;
+ p2 = v2 + v5;
+ p5 = v2 - v5;
+ p3 = v3 + v4;
+ p4 = v3 - v4;
+ // convert to 8-bit integers
+ p0 = p0 < 16 ? 0 : p0 >= 4080 ? 255 : p0 >> 4;
+ p1 = p1 < 16 ? 0 : p1 >= 4080 ? 255 : p1 >> 4;
+ p2 = p2 < 16 ? 0 : p2 >= 4080 ? 255 : p2 >> 4;
+ p3 = p3 < 16 ? 0 : p3 >= 4080 ? 255 : p3 >> 4;
+ p4 = p4 < 16 ? 0 : p4 >= 4080 ? 255 : p4 >> 4;
+ p5 = p5 < 16 ? 0 : p5 >= 4080 ? 255 : p5 >> 4;
+ p6 = p6 < 16 ? 0 : p6 >= 4080 ? 255 : p6 >> 4;
+ p7 = p7 < 16 ? 0 : p7 >= 4080 ? 255 : p7 >> 4;
+ // store block data
+ blockData[blockBufferOffset + col] = p0;
+ blockData[blockBufferOffset + col + 8] = p1;
+ blockData[blockBufferOffset + col + 16] = p2;
+ blockData[blockBufferOffset + col + 24] = p3;
+ blockData[blockBufferOffset + col + 32] = p4;
+ blockData[blockBufferOffset + col + 40] = p5;
+ blockData[blockBufferOffset + col + 48] = p6;
+ blockData[blockBufferOffset + col + 56] = p7;
+ }
+ }
+ function buildComponentData(frame, component) {
+ var blocksPerLine = component.blocksPerLine;
+ var blocksPerColumn = component.blocksPerColumn;
+ var computationBuffer = new Int16Array(64);
+ for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+ for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+ var offset = getBlockBufferOffset(component, blockRow, blockCol);
+ quantizeAndInverse(component, offset, computationBuffer);
+ }
+ }
+ return component.blockData;
+ }
+ function clamp0to255(a) {
+ return a <= 0 ? 0 : a >= 255 ? 255 : a;
+ }
+ JpegImage.prototype = {
+ parse: function parse(data) {
+ function readUint16() {
+ var value = data[offset] << 8 | data[offset + 1];
+ offset += 2;
+ return value;
+ }
+ function readDataBlock() {
+ var length = readUint16();
+ var array = data.subarray(offset, offset + length - 2);
+ offset += array.length;
+ return array;
+ }
+ function prepareComponents(frame) {
+ var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
+ var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
+ for (var i = 0; i < frame.components.length; i++) {
+ component = frame.components[i];
+ var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
+ var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV);
+ var blocksPerLineForMcu = mcusPerLine * component.h;
+ var blocksPerColumnForMcu = mcusPerColumn * component.v;
+ var blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
+ component.blockData = new Int16Array(blocksBufferSize);
+ component.blocksPerLine = blocksPerLine;
+ component.blocksPerColumn = blocksPerColumn;
+ }
+ frame.mcusPerLine = mcusPerLine;
+ frame.mcusPerColumn = mcusPerColumn;
+ }
+ var offset = 0;
+ var jfif = null;
+ var adobe = null;
+ var frame, resetInterval;
+ var quantizationTables = [];
+ var huffmanTablesAC = [], huffmanTablesDC = [];
+ var fileMarker = readUint16();
+ if (fileMarker !== 0xFFD8) {
+ // SOI (Start of Image)
+ error('JPEG error: SOI not found');
+ }
+ fileMarker = readUint16();
+ while (fileMarker !== 0xFFD9) {
+ // EOI (End of image)
+ var i, j, l;
+ switch (fileMarker) {
+ case 0xFFE0:
+ // APP0 (Application Specific)
+ case 0xFFE1:
+ // APP1
+ case 0xFFE2:
+ // APP2
+ case 0xFFE3:
+ // APP3
+ case 0xFFE4:
+ // APP4
+ case 0xFFE5:
+ // APP5
+ case 0xFFE6:
+ // APP6
+ case 0xFFE7:
+ // APP7
+ case 0xFFE8:
+ // APP8
+ case 0xFFE9:
+ // APP9
+ case 0xFFEA:
+ // APP10
+ case 0xFFEB:
+ // APP11
+ case 0xFFEC:
+ // APP12
+ case 0xFFED:
+ // APP13
+ case 0xFFEE:
+ // APP14
+ case 0xFFEF:
+ // APP15
+ case 0xFFFE:
+ // COM (Comment)
+ var appData = readDataBlock();
+ if (fileMarker === 0xFFE0) {
+ if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && appData[3] === 0x46 && appData[4] === 0) {
+ // 'JFIF\x00'
+ jfif = {
+ version: {
+ major: appData[5],
+ minor: appData[6]
+ },
+ densityUnits: appData[7],
+ xDensity: appData[8] << 8 | appData[9],
+ yDensity: appData[10] << 8 | appData[11],
+ thumbWidth: appData[12],
+ thumbHeight: appData[13],
+ thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
+ };
+ }
+ }
+ // TODO APP1 - Exif
+ if (fileMarker === 0xFFEE) {
+ if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && appData[3] === 0x62 && appData[4] === 0x65) {
+ // 'Adobe'
+ adobe = {
+ version: appData[5] << 8 | appData[6],
+ flags0: appData[7] << 8 | appData[8],
+ flags1: appData[9] << 8 | appData[10],
+ transformCode: appData[11]
+ };
+ }
+ }
+ break;
+ case 0xFFDB:
+ // DQT (Define Quantization Tables)
+ var quantizationTablesLength = readUint16();
+ var quantizationTablesEnd = quantizationTablesLength + offset - 2;
+ var z;
+ while (offset < quantizationTablesEnd) {
+ var quantizationTableSpec = data[offset++];
+ var tableData = new Uint16Array(64);
+ if (quantizationTableSpec >> 4 === 0) {
+ // 8 bit values
+ for (j = 0; j < 64; j++) {
+ z = dctZigZag[j];
+ tableData[z] = data[offset++];
+ }
+ } else if (quantizationTableSpec >> 4 === 1) {
+ //16 bit
+ for (j = 0; j < 64; j++) {
+ z = dctZigZag[j];
+ tableData[z] = readUint16();
+ }
+ } else {
+ error('JPEG error: DQT - invalid table spec');
+ }
+ quantizationTables[quantizationTableSpec & 15] = tableData;
+ }
+ break;
+ case 0xFFC0:
+ // SOF0 (Start of Frame, Baseline DCT)
+ case 0xFFC1:
+ // SOF1 (Start of Frame, Extended DCT)
+ case 0xFFC2:
+ // SOF2 (Start of Frame, Progressive DCT)
+ if (frame) {
+ error('JPEG error: Only single frame JPEGs supported');
+ }
+ readUint16();
+ // skip data length
+ frame = {};
+ frame.extended = fileMarker === 0xFFC1;
+ frame.progressive = fileMarker === 0xFFC2;
+ frame.precision = data[offset++];
+ frame.scanLines = readUint16();
+ frame.samplesPerLine = readUint16();
+ frame.components = [];
+ frame.componentIds = {};
+ var componentsCount = data[offset++], componentId;
+ var maxH = 0, maxV = 0;
+ for (i = 0; i < componentsCount; i++) {
+ componentId = data[offset];
+ var h = data[offset + 1] >> 4;
+ var v = data[offset + 1] & 15;
+ if (maxH < h) {
+ maxH = h;
+ }
+ if (maxV < v) {
+ maxV = v;
+ }
+ var qId = data[offset + 2];
+ l = frame.components.push({
+ h: h,
+ v: v,
+ quantizationId: qId,
+ quantizationTable: null
+ });
+ // See comment below.
+ frame.componentIds[componentId] = l - 1;
+ offset += 3;
+ }
+ frame.maxH = maxH;
+ frame.maxV = maxV;
+ prepareComponents(frame);
+ break;
+ case 0xFFC4:
+ // DHT (Define Huffman Tables)
+ var huffmanLength = readUint16();
+ for (i = 2; i < huffmanLength;) {
+ var huffmanTableSpec = data[offset++];
+ var codeLengths = new Uint8Array(16);
+ var codeLengthSum = 0;
+ for (j = 0; j < 16; j++, offset++) {
+ codeLengthSum += codeLengths[j] = data[offset];
+ }
+ var huffmanValues = new Uint8Array(codeLengthSum);
+ for (j = 0; j < codeLengthSum; j++, offset++) {
+ huffmanValues[j] = data[offset];
+ }
+ i += 17 + codeLengthSum;
+ (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = buildHuffmanTable(codeLengths, huffmanValues);
+ }
+ break;
+ case 0xFFDD:
+ // DRI (Define Restart Interval)
+ readUint16();
+ // skip data length
+ resetInterval = readUint16();
+ break;
+ case 0xFFDA:
+ // SOS (Start of Scan)
+ var scanLength = readUint16();
+ var selectorsCount = data[offset++];
+ var components = [], component;
+ for (i = 0; i < selectorsCount; i++) {
+ var componentIndex = frame.componentIds[data[offset++]];
+ component = frame.components[componentIndex];
+ var tableSpec = data[offset++];
+ component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
+ component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
+ components.push(component);
+ }
+ var spectralStart = data[offset++];
+ var spectralEnd = data[offset++];
+ var successiveApproximation = data[offset++];
+ var processed = decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, successiveApproximation & 15);
+ offset += processed;
+ break;
+ case 0xFFFF:
+ // Fill bytes
+ if (data[offset] !== 0xFF) {
+ // Avoid skipping a valid marker.
+ offset--;
+ }
+ break;
+ default:
+ if (data[offset - 3] === 0xFF && data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
+ // could be incorrect encoding -- last 0xFF byte of the previous
+ // block was eaten by the encoder
+ offset -= 3;
+ break;
+ }
+ error('JPEG error: unknown marker ' + fileMarker.toString(16));
+ }
+ fileMarker = readUint16();
+ }
+ this.width = frame.samplesPerLine;
+ this.height = frame.scanLines;
+ this.jfif = jfif;
+ this.adobe = adobe;
+ this.components = [];
+ for (i = 0; i < frame.components.length; i++) {
+ component = frame.components[i];
+ // Prevent errors when DQT markers are placed after SOF{n} markers,
+ // by assigning the `quantizationTable` entry after the entire image
+ // has been parsed (fixes issue7406.pdf).
+ var quantizationTable = quantizationTables[component.quantizationId];
+ if (quantizationTable) {
+ component.quantizationTable = quantizationTable;
+ }
+ this.components.push({
+ output: buildComponentData(frame, component),
+ scaleX: component.h / frame.maxH,
+ scaleY: component.v / frame.maxV,
+ blocksPerLine: component.blocksPerLine,
+ blocksPerColumn: component.blocksPerColumn
+ });
+ }
+ this.numComponents = this.components.length;
+ },
+ _getLinearizedBlockData: function getLinearizedBlockData(width, height) {
+ var scaleX = this.width / width, scaleY = this.height / height;
+ var component, componentScaleX, componentScaleY, blocksPerScanline;
+ var x, y, i, j, k;
+ var index;
+ var offset = 0;
+ var output;
+ var numComponents = this.components.length;
+ var dataLength = width * height * numComponents;
+ var data = new Uint8Array(dataLength);
+ var xScaleBlockOffset = new Uint32Array(width);
+ var mask3LSB = 0xfffffff8;
+ // used to clear the 3 LSBs
+ for (i = 0; i < numComponents; i++) {
+ component = this.components[i];
+ componentScaleX = component.scaleX * scaleX;
+ componentScaleY = component.scaleY * scaleY;
+ offset = i;
+ output = component.output;
+ blocksPerScanline = component.blocksPerLine + 1 << 3;
+ // precalculate the xScaleBlockOffset
+ for (x = 0; x < width; x++) {
+ j = 0 | x * componentScaleX;
+ xScaleBlockOffset[x] = (j & mask3LSB) << 3 | j & 7;
+ }
+ // linearize the blocks of the component
+ for (y = 0; y < height; y++) {
+ j = 0 | y * componentScaleY;
+ index = blocksPerScanline * (j & mask3LSB) | (j & 7) << 3;
+ for (x = 0; x < width; x++) {
+ data[offset] = output[index + xScaleBlockOffset[x]];
+ offset += numComponents;
+ }
+ }
+ }
+ // decodeTransform contains pairs of multiplier (-256..256) and additive
+ var transform = this.decodeTransform;
+ if (transform) {
+ for (i = 0; i < dataLength;) {
+ for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) {
+ data[i] = (data[i] * transform[k] >> 8) + transform[k + 1];
+ }
+ }
+ }
+ return data;
+ },
+ _isColorConversionNeeded: function isColorConversionNeeded() {
+ if (this.adobe && this.adobe.transformCode) {
+ // The adobe transform marker overrides any previous setting
+ return true;
+ } else if (this.numComponents === 3) {
+ if (!this.adobe && this.colorTransform === 0) {
+ // If the Adobe transform marker is not present and the image
+ // dictionary has a 'ColorTransform' entry, explicitly set to `0`,
+ // then the colours should *not* be transformed.
+ return false;
+ }
+ return true;
+ } else {
+ // `this.numComponents !== 3`
+ if (!this.adobe && this.colorTransform === 1) {
+ // If the Adobe transform marker is not present and the image
+ // dictionary has a 'ColorTransform' entry, explicitly set to `1`,
+ // then the colours should be transformed.
+ return true;
+ }
+ return false;
+ }
+ },
+ _convertYccToRgb: function convertYccToRgb(data) {
+ var Y, Cb, Cr;
+ for (var i = 0, length = data.length; i < length; i += 3) {
+ Y = data[i];
+ Cb = data[i + 1];
+ Cr = data[i + 2];
+ data[i] = clamp0to255(Y - 179.456 + 1.402 * Cr);
+ data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr);
+ data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb);
+ }
+ return data;
+ },
+ _convertYcckToRgb: function convertYcckToRgb(data) {
+ var Y, Cb, Cr, k;
+ var offset = 0;
+ for (var i = 0, length = data.length; i < length; i += 4) {
+ Y = data[i];
+ Cb = data[i + 1];
+ Cr = data[i + 2];
+ k = data[i + 3];
+ var r = -122.67195406894 + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - 5.4080610064599e-5 * Y + 0.00048449797120281 * k - 0.154362151871126) + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - 0.00477271405408747 * k + 1.53380253221734) + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + 0.48357088451265) + k * (-0.000336197177618394 * k + 0.484791561490776);
+ var g = 107.268039397724 + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + 0.000659397001245577 * Y + 0.000426105652938837 * k - 0.176491792462875) + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + 0.000770482631801132 * k - 0.151051492775562) + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + 0.25802910206845) + k * (-0.000318913117588328 * k - 0.213742400323665);
+ var b = -20.810012546947 + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + 0.0020741088115012 * Y - 0.00288260236853442 * k + 0.814272968359295) + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + 0.000560833691242812 * k - 0.195152027534049) + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + 0.116935020465145) + k * (-0.000343531996510555 * k + 0.24165260232407);
+ data[offset++] = clamp0to255(r);
+ data[offset++] = clamp0to255(g);
+ data[offset++] = clamp0to255(b);
+ }
+ return data;
+ },
+ _convertYcckToCmyk: function convertYcckToCmyk(data) {
+ var Y, Cb, Cr;
+ for (var i = 0, length = data.length; i < length; i += 4) {
+ Y = data[i];
+ Cb = data[i + 1];
+ Cr = data[i + 2];
+ data[i] = clamp0to255(434.456 - Y - 1.402 * Cr);
+ data[i + 1] = clamp0to255(119.541 - Y + 0.344 * Cb + 0.714 * Cr);
+ data[i + 2] = clamp0to255(481.816 - Y - 1.772 * Cb);
+ }
+ // K in data[i + 3] is unchanged
+ return data;
+ },
+ _convertCmykToRgb: function convertCmykToRgb(data) {
+ var c, m, y, k;
+ var offset = 0;
+ var min = -255 * 255 * 255;
+ var scale = 1 / 255 / 255;
+ for (var i = 0, length = data.length; i < length; i += 4) {
+ c = data[i];
+ m = data[i + 1];
+ y = data[i + 2];
+ k = data[i + 3];
+ var r = c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 72734.4411664936) + m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 1401.7366389350734) + y * (-2.5217340131683033 * y - 21.248923337353073 * k + 4465.541406466231) - k * (21.86122147463605 * k + 48317.86113160301);
+ var g = c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 20220.756542821975) + m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 48691.05921601825) + y * (4.444339102852739 * y + 9.8632861493405 * k - 6341.191035517494) - k * (20.737325471181034 * k + 47890.15695978492);
+ var b = c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 3616.812083916688) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 28620.90484698408) + y * (0.03296041114873217 * y + 115.60384449646641 * k - 49363.43385999684) - k * (22.33816807309886 * k + 45932.16563550634);
+ data[offset++] = r >= 0 ? 255 : r <= min ? 0 : 255 + r * scale | 0;
+ data[offset++] = g >= 0 ? 255 : g <= min ? 0 : 255 + g * scale | 0;
+ data[offset++] = b >= 0 ? 255 : b <= min ? 0 : 255 + b * scale | 0;
+ }
+ return data;
+ },
+ getData: function getData(width, height, forceRGBoutput) {
+ if (this.numComponents > 4) {
+ error('JPEG error: Unsupported color mode');
+ }
+ // type of data: Uint8Array(width * height * numComponents)
+ var data = this._getLinearizedBlockData(width, height);
+ if (this.numComponents === 1 && forceRGBoutput) {
+ var dataLength = data.length;
+ var rgbData = new Uint8Array(dataLength * 3);
+ var offset = 0;
+ for (var i = 0; i < dataLength; i++) {
+ var grayColor = data[i];
+ rgbData[offset++] = grayColor;
+ rgbData[offset++] = grayColor;
+ rgbData[offset++] = grayColor;
+ }
+ return rgbData;
+ } else if (this.numComponents === 3 && this._isColorConversionNeeded()) {
+ return this._convertYccToRgb(data);
+ } else if (this.numComponents === 4) {
+ if (this._isColorConversionNeeded()) {
+ if (forceRGBoutput) {
+ return this._convertYcckToRgb(data);
+ } else {
+ return this._convertYcckToCmyk(data);
+ }
+ } else if (forceRGBoutput) {
+ return this._convertCmykToRgb(data);
+ }
+ }
+ return data;
+ }
+ };
+ return JpegImage;
+ }();
+ exports.JpegImage = JpegImage;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreJpx = {}, root.pdfjsSharedUtil, root.pdfjsCoreArithmeticDecoder);
+ }(this, function (exports, sharedUtil, coreArithmeticDecoder) {
+ var info = sharedUtil.info;
+ var warn = sharedUtil.warn;
+ var error = sharedUtil.error;
+ var log2 = sharedUtil.log2;
+ var readUint16 = sharedUtil.readUint16;
+ var readUint32 = sharedUtil.readUint32;
+ var ArithmeticDecoder = coreArithmeticDecoder.ArithmeticDecoder;
+ var JpxImage = function JpxImageClosure() {
+ // Table E.1
+ var SubbandsGainLog2 = {
+ 'LL': 0,
+ 'LH': 1,
+ 'HL': 1,
+ 'HH': 2
+ };
+ function JpxImage() {
+ this.failOnCorruptedImage = false;
+ }
+ JpxImage.prototype = {
+ parse: function JpxImage_parse(data) {
+ var head = readUint16(data, 0);
+ // No box header, immediate start of codestream (SOC)
+ if (head === 0xFF4F) {
+ this.parseCodestream(data, 0, data.length);
+ return;
+ }
+ var position = 0, length = data.length;
+ while (position < length) {
+ var headerSize = 8;
+ var lbox = readUint32(data, position);
+ var tbox = readUint32(data, position + 4);
+ position += headerSize;
+ if (lbox === 1) {
+ // XLBox: read UInt64 according to spec.
+ // JavaScript's int precision of 53 bit should be sufficient here.
+ lbox = readUint32(data, position) * 4294967296 + readUint32(data, position + 4);
+ position += 8;
+ headerSize += 8;
+ }
+ if (lbox === 0) {
+ lbox = length - position + headerSize;
+ }
+ if (lbox < headerSize) {
+ error('JPX Error: Invalid box field size');
+ }
+ var dataLength = lbox - headerSize;
+ var jumpDataLength = true;
+ switch (tbox) {
+ case 0x6A703268:
+ // 'jp2h'
+ jumpDataLength = false;
+ // parsing child boxes
+ break;
+ case 0x636F6C72:
+ // 'colr'
+ // Colorspaces are not used, the CS from the PDF is used.
+ var method = data[position];
+ if (method === 1) {
+ // enumerated colorspace
+ var colorspace = readUint32(data, position + 3);
+ switch (colorspace) {
+ case 16:
+ // this indicates a sRGB colorspace
+ case 17:
+ // this indicates a grayscale colorspace
+ case 18:
+ // this indicates a YUV colorspace
+ break;
+ default:
+ warn('Unknown colorspace ' + colorspace);
+ break;
+ }
+ } else if (method === 2) {
+ info('ICC profile not supported');
+ }
+ break;
+ case 0x6A703263:
+ // 'jp2c'
+ this.parseCodestream(data, position, position + dataLength);
+ break;
+ case 0x6A502020:
+ // 'jP\024\024'
+ if (0x0d0a870a !== readUint32(data, position)) {
+ warn('Invalid JP2 signature');
+ }
+ break;
+ // The following header types are valid but currently not used:
+ case 0x6A501A1A:
+ // 'jP\032\032'
+ case 0x66747970:
+ // 'ftyp'
+ case 0x72726571:
+ // 'rreq'
+ case 0x72657320:
+ // 'res '
+ case 0x69686472:
+ // 'ihdr'
+ break;
+ default:
+ var headerType = String.fromCharCode(tbox >> 24 & 0xFF, tbox >> 16 & 0xFF, tbox >> 8 & 0xFF, tbox & 0xFF);
+ warn('Unsupported header type ' + tbox + ' (' + headerType + ')');
+ break;
+ }
+ if (jumpDataLength) {
+ position += dataLength;
+ }
+ }
+ },
+ parseImageProperties: function JpxImage_parseImageProperties(stream) {
+ var newByte = stream.getByte();
+ while (newByte >= 0) {
+ var oldByte = newByte;
+ newByte = stream.getByte();
+ var code = oldByte << 8 | newByte;
+ // Image and tile size (SIZ)
+ if (code === 0xFF51) {
+ stream.skip(4);
+ var Xsiz = stream.getInt32() >>> 0;
+ // Byte 4
+ var Ysiz = stream.getInt32() >>> 0;
+ // Byte 8
+ var XOsiz = stream.getInt32() >>> 0;
+ // Byte 12
+ var YOsiz = stream.getInt32() >>> 0;
+ // Byte 16
+ stream.skip(16);
+ var Csiz = stream.getUint16();
+ // Byte 36
+ this.width = Xsiz - XOsiz;
+ this.height = Ysiz - YOsiz;
+ this.componentsCount = Csiz;
+ // Results are always returned as Uint8Arrays
+ this.bitsPerComponent = 8;
+ return;
+ }
+ }
+ error('JPX Error: No size marker found in JPX stream');
+ },
+ parseCodestream: function JpxImage_parseCodestream(data, start, end) {
+ var context = {};
+ var doNotRecover = false;
+ try {
+ var position = start;
+ while (position + 1 < end) {
+ var code = readUint16(data, position);
+ position += 2;
+ var length = 0, j, sqcd, spqcds, spqcdSize, scalarExpounded, tile;
+ switch (code) {
+ case 0xFF4F:
+ // Start of codestream (SOC)
+ context.mainHeader = true;
+ break;
+ case 0xFFD9:
+ // End of codestream (EOC)
+ break;
+ case 0xFF51:
+ // Image and tile size (SIZ)
+ length = readUint16(data, position);
+ var siz = {};
+ siz.Xsiz = readUint32(data, position + 4);
+ siz.Ysiz = readUint32(data, position + 8);
+ siz.XOsiz = readUint32(data, position + 12);
+ siz.YOsiz = readUint32(data, position + 16);
+ siz.XTsiz = readUint32(data, position + 20);
+ siz.YTsiz = readUint32(data, position + 24);
+ siz.XTOsiz = readUint32(data, position + 28);
+ siz.YTOsiz = readUint32(data, position + 32);
+ var componentsCount = readUint16(data, position + 36);
+ siz.Csiz = componentsCount;
+ var components = [];
+ j = position + 38;
+ for (var i = 0; i < componentsCount; i++) {
+ var component = {
+ precision: (data[j] & 0x7F) + 1,
+ isSigned: !!(data[j] & 0x80),
+ XRsiz: data[j + 1],
+ YRsiz: data[j + 1]
+ };
+ calculateComponentDimensions(component, siz);
+ components.push(component);
+ }
+ context.SIZ = siz;
+ context.components = components;
+ calculateTileGrids(context, components);
+ context.QCC = [];
+ context.COC = [];
+ break;
+ case 0xFF5C:
+ // Quantization default (QCD)
+ length = readUint16(data, position);
+ var qcd = {};
+ j = position + 2;
+ sqcd = data[j++];
+ switch (sqcd & 0x1F) {
+ case 0:
+ spqcdSize = 8;
+ scalarExpounded = true;
+ break;
+ case 1:
+ spqcdSize = 16;
+ scalarExpounded = false;
+ break;
+ case 2:
+ spqcdSize = 16;
+ scalarExpounded = true;
+ break;
+ default:
+ throw new Error('Invalid SQcd value ' + sqcd);
+ }
+ qcd.noQuantization = spqcdSize === 8;
+ qcd.scalarExpounded = scalarExpounded;
+ qcd.guardBits = sqcd >> 5;
+ spqcds = [];
+ while (j < length + position) {
+ var spqcd = {};
+ if (spqcdSize === 8) {
+ spqcd.epsilon = data[j++] >> 3;
+ spqcd.mu = 0;
+ } else {
+ spqcd.epsilon = data[j] >> 3;
+ spqcd.mu = (data[j] & 0x7) << 8 | data[j + 1];
+ j += 2;
+ }
+ spqcds.push(spqcd);
+ }
+ qcd.SPqcds = spqcds;
+ if (context.mainHeader) {
+ context.QCD = qcd;
+ } else {
+ context.currentTile.QCD = qcd;
+ context.currentTile.QCC = [];
+ }
+ break;
+ case 0xFF5D:
+ // Quantization component (QCC)
+ length = readUint16(data, position);
+ var qcc = {};
+ j = position + 2;
+ var cqcc;
+ if (context.SIZ.Csiz < 257) {
+ cqcc = data[j++];
+ } else {
+ cqcc = readUint16(data, j);
+ j += 2;
+ }
+ sqcd = data[j++];
+ switch (sqcd & 0x1F) {
+ case 0:
+ spqcdSize = 8;
+ scalarExpounded = true;
+ break;
+ case 1:
+ spqcdSize = 16;
+ scalarExpounded = false;
+ break;
+ case 2:
+ spqcdSize = 16;
+ scalarExpounded = true;
+ break;
+ default:
+ throw new Error('Invalid SQcd value ' + sqcd);
+ }
+ qcc.noQuantization = spqcdSize === 8;
+ qcc.scalarExpounded = scalarExpounded;
+ qcc.guardBits = sqcd >> 5;
+ spqcds = [];
+ while (j < length + position) {
+ spqcd = {};
+ if (spqcdSize === 8) {
+ spqcd.epsilon = data[j++] >> 3;
+ spqcd.mu = 0;
+ } else {
+ spqcd.epsilon = data[j] >> 3;
+ spqcd.mu = (data[j] & 0x7) << 8 | data[j + 1];
+ j += 2;
+ }
+ spqcds.push(spqcd);
+ }
+ qcc.SPqcds = spqcds;
+ if (context.mainHeader) {
+ context.QCC[cqcc] = qcc;
+ } else {
+ context.currentTile.QCC[cqcc] = qcc;
+ }
+ break;
+ case 0xFF52:
+ // Coding style default (COD)
+ length = readUint16(data, position);
+ var cod = {};
+ j = position + 2;
+ var scod = data[j++];
+ cod.entropyCoderWithCustomPrecincts = !!(scod & 1);
+ cod.sopMarkerUsed = !!(scod & 2);
+ cod.ephMarkerUsed = !!(scod & 4);
+ cod.progressionOrder = data[j++];
+ cod.layersCount = readUint16(data, j);
+ j += 2;
+ cod.multipleComponentTransform = data[j++];
+ cod.decompositionLevelsCount = data[j++];
+ cod.xcb = (data[j++] & 0xF) + 2;
+ cod.ycb = (data[j++] & 0xF) + 2;
+ var blockStyle = data[j++];
+ cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1);
+ cod.resetContextProbabilities = !!(blockStyle & 2);
+ cod.terminationOnEachCodingPass = !!(blockStyle & 4);
+ cod.verticalyStripe = !!(blockStyle & 8);
+ cod.predictableTermination = !!(blockStyle & 16);
+ cod.segmentationSymbolUsed = !!(blockStyle & 32);
+ cod.reversibleTransformation = data[j++];
+ if (cod.entropyCoderWithCustomPrecincts) {
+ var precinctsSizes = [];
+ while (j < length + position) {
+ var precinctsSize = data[j++];
+ precinctsSizes.push({
+ PPx: precinctsSize & 0xF,
+ PPy: precinctsSize >> 4
+ });
+ }
+ cod.precinctsSizes = precinctsSizes;
+ }
+ var unsupported = [];
+ if (cod.selectiveArithmeticCodingBypass) {
+ unsupported.push('selectiveArithmeticCodingBypass');
+ }
+ if (cod.resetContextProbabilities) {
+ unsupported.push('resetContextProbabilities');
+ }
+ if (cod.terminationOnEachCodingPass) {
+ unsupported.push('terminationOnEachCodingPass');
+ }
+ if (cod.verticalyStripe) {
+ unsupported.push('verticalyStripe');
+ }
+ if (cod.predictableTermination) {
+ unsupported.push('predictableTermination');
+ }
+ if (unsupported.length > 0) {
+ doNotRecover = true;
+ throw new Error('Unsupported COD options (' + unsupported.join(', ') + ')');
+ }
+ if (context.mainHeader) {
+ context.COD = cod;
+ } else {
+ context.currentTile.COD = cod;
+ context.currentTile.COC = [];
+ }
+ break;
+ case 0xFF90:
+ // Start of tile-part (SOT)
+ length = readUint16(data, position);
+ tile = {};
+ tile.index = readUint16(data, position + 2);
+ tile.length = readUint32(data, position + 4);
+ tile.dataEnd = tile.length + position - 2;
+ tile.partIndex = data[position + 8];
+ tile.partsCount = data[position + 9];
+ context.mainHeader = false;
+ if (tile.partIndex === 0) {
+ // reset component specific settings
+ tile.COD = context.COD;
+ tile.COC = context.COC.slice(0);
+ // clone of the global COC
+ tile.QCD = context.QCD;
+ tile.QCC = context.QCC.slice(0);
+ }
+ // clone of the global COC
+ context.currentTile = tile;
+ break;
+ case 0xFF93:
+ // Start of data (SOD)
+ tile = context.currentTile;
+ if (tile.partIndex === 0) {
+ initializeTile(context, tile.index);
+ buildPackets(context);
+ }
+ // moving to the end of the data
+ length = tile.dataEnd - position;
+ parseTilePackets(context, data, position, length);
+ break;
+ case 0xFF55:
+ // Tile-part lengths, main header (TLM)
+ case 0xFF57:
+ // Packet length, main header (PLM)
+ case 0xFF58:
+ // Packet length, tile-part header (PLT)
+ case 0xFF64:
+ // Comment (COM)
+ length = readUint16(data, position);
+ // skipping content
+ break;
+ case 0xFF53:
+ // Coding style component (COC)
+ throw new Error('Codestream code 0xFF53 (COC) is ' + 'not implemented');
+ default:
+ throw new Error('Unknown codestream code: ' + code.toString(16));
+ }
+ position += length;
+ }
+ } catch (e) {
+ if (doNotRecover || this.failOnCorruptedImage) {
+ error('JPX Error: ' + e.message);
+ } else {
+ warn('JPX: Trying to recover from: ' + e.message);
+ }
+ }
+ this.tiles = transformComponents(context);
+ this.width = context.SIZ.Xsiz - context.SIZ.XOsiz;
+ this.height = context.SIZ.Ysiz - context.SIZ.YOsiz;
+ this.componentsCount = context.SIZ.Csiz;
+ }
+ };
+ function calculateComponentDimensions(component, siz) {
+ // Section B.2 Component mapping
+ component.x0 = Math.ceil(siz.XOsiz / component.XRsiz);
+ component.x1 = Math.ceil(siz.Xsiz / component.XRsiz);
+ component.y0 = Math.ceil(siz.YOsiz / component.YRsiz);
+ component.y1 = Math.ceil(siz.Ysiz / component.YRsiz);
+ component.width = component.x1 - component.x0;
+ component.height = component.y1 - component.y0;
+ }
+ function calculateTileGrids(context, components) {
+ var siz = context.SIZ;
+ // Section B.3 Division into tile and tile-components
+ var tile, tiles = [];
+ var numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz);
+ var numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz);
+ for (var q = 0; q < numYtiles; q++) {
+ for (var p = 0; p < numXtiles; p++) {
+ tile = {};
+ tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz);
+ tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz);
+ tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz);
+ tile.ty1 = Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz);
+ tile.width = tile.tx1 - tile.tx0;
+ tile.height = tile.ty1 - tile.ty0;
+ tile.components = [];
+ tiles.push(tile);
+ }
+ }
+ context.tiles = tiles;
+ var componentsCount = siz.Csiz;
+ for (var i = 0, ii = componentsCount; i < ii; i++) {
+ var component = components[i];
+ for (var j = 0, jj = tiles.length; j < jj; j++) {
+ var tileComponent = {};
+ tile = tiles[j];
+ tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz);
+ tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz);
+ tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz);
+ tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz);
+ tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0;
+ tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0;
+ tile.components[i] = tileComponent;
+ }
+ }
+ }
+ function getBlocksDimensions(context, component, r) {
+ var codOrCoc = component.codingStyleParameters;
+ var result = {};
+ if (!codOrCoc.entropyCoderWithCustomPrecincts) {
+ result.PPx = 15;
+ result.PPy = 15;
+ } else {
+ result.PPx = codOrCoc.precinctsSizes[r].PPx;
+ result.PPy = codOrCoc.precinctsSizes[r].PPy;
+ }
+ // calculate codeblock size as described in section B.7
+ result.xcb_ = r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) : Math.min(codOrCoc.xcb, result.PPx);
+ result.ycb_ = r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) : Math.min(codOrCoc.ycb, result.PPy);
+ return result;
+ }
+ function buildPrecincts(context, resolution, dimensions) {
+ // Section B.6 Division resolution to precincts
+ var precinctWidth = 1 << dimensions.PPx;
+ var precinctHeight = 1 << dimensions.PPy;
+ // Jasper introduces codeblock groups for mapping each subband codeblocks
+ // to precincts. Precinct partition divides a resolution according to width
+ // and height parameters. The subband that belongs to the resolution level
+ // has a different size than the level, unless it is the zero resolution.
+ // From Jasper documentation: jpeg2000.pdf, section K: Tier-2 coding:
+ // The precinct partitioning for a particular subband is derived from a
+ // partitioning of its parent LL band (i.e., the LL band at the next higher
+ // resolution level)... The LL band associated with each resolution level is
+ // divided into precincts... Each of the resulting precinct regions is then
+ // mapped into its child subbands (if any) at the next lower resolution
+ // level. This is accomplished by using the coordinate transformation
+ // (u, v) = (ceil(x/2), ceil(y/2)) where (x, y) and (u, v) are the
+ // coordinates of a point in the LL band and child subband, respectively.
+ var isZeroRes = resolution.resLevel === 0;
+ var precinctWidthInSubband = 1 << dimensions.PPx + (isZeroRes ? 0 : -1);
+ var precinctHeightInSubband = 1 << dimensions.PPy + (isZeroRes ? 0 : -1);
+ var numprecinctswide = resolution.trx1 > resolution.trx0 ? Math.ceil(resolution.trx1 / precinctWidth) - Math.floor(resolution.trx0 / precinctWidth) : 0;
+ var numprecinctshigh = resolution.try1 > resolution.try0 ? Math.ceil(resolution.try1 / precinctHeight) - Math.floor(resolution.try0 / precinctHeight) : 0;
+ var numprecincts = numprecinctswide * numprecinctshigh;
+ resolution.precinctParameters = {
+ precinctWidth: precinctWidth,
+ precinctHeight: precinctHeight,
+ numprecinctswide: numprecinctswide,
+ numprecinctshigh: numprecinctshigh,
+ numprecincts: numprecincts,
+ precinctWidthInSubband: precinctWidthInSubband,
+ precinctHeightInSubband: precinctHeightInSubband
+ };
+ }
+ function buildCodeblocks(context, subband, dimensions) {
+ // Section B.7 Division sub-band into code-blocks
+ var xcb_ = dimensions.xcb_;
+ var ycb_ = dimensions.ycb_;
+ var codeblockWidth = 1 << xcb_;
+ var codeblockHeight = 1 << ycb_;
+ var cbx0 = subband.tbx0 >> xcb_;
+ var cby0 = subband.tby0 >> ycb_;
+ var cbx1 = subband.tbx1 + codeblockWidth - 1 >> xcb_;
+ var cby1 = subband.tby1 + codeblockHeight - 1 >> ycb_;
+ var precinctParameters = subband.resolution.precinctParameters;
+ var codeblocks = [];
+ var precincts = [];
+ var i, j, codeblock, precinctNumber;
+ for (j = cby0; j < cby1; j++) {
+ for (i = cbx0; i < cbx1; i++) {
+ codeblock = {
+ cbx: i,
+ cby: j,
+ tbx0: codeblockWidth * i,
+ tby0: codeblockHeight * j,
+ tbx1: codeblockWidth * (i + 1),
+ tby1: codeblockHeight * (j + 1)
+ };
+ codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0);
+ codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0);
+ codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1);
+ codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1);
+ // Calculate precinct number for this codeblock, codeblock position
+ // should be relative to its subband, use actual dimension and position
+ // See comment about codeblock group width and height
+ var pi = Math.floor((codeblock.tbx0_ - subband.tbx0) / precinctParameters.precinctWidthInSubband);
+ var pj = Math.floor((codeblock.tby0_ - subband.tby0) / precinctParameters.precinctHeightInSubband);
+ precinctNumber = pi + pj * precinctParameters.numprecinctswide;
+ codeblock.precinctNumber = precinctNumber;
+ codeblock.subbandType = subband.type;
+ codeblock.Lblock = 3;
+ if (codeblock.tbx1_ <= codeblock.tbx0_ || codeblock.tby1_ <= codeblock.tby0_) {
+ continue;
+ }
+ codeblocks.push(codeblock);
+ // building precinct for the sub-band
+ var precinct = precincts[precinctNumber];
+ if (precinct !== undefined) {
+ if (i < precinct.cbxMin) {
+ precinct.cbxMin = i;
+ } else if (i > precinct.cbxMax) {
+ precinct.cbxMax = i;
+ }
+ if (j < precinct.cbyMin) {
+ precinct.cbxMin = j;
+ } else if (j > precinct.cbyMax) {
+ precinct.cbyMax = j;
+ }
+ } else {
+ precincts[precinctNumber] = precinct = {
+ cbxMin: i,
+ cbyMin: j,
+ cbxMax: i,
+ cbyMax: j
+ };
+ }
+ codeblock.precinct = precinct;
+ }
+ }
+ subband.codeblockParameters = {
+ codeblockWidth: xcb_,
+ codeblockHeight: ycb_,
+ numcodeblockwide: cbx1 - cbx0 + 1,
+ numcodeblockhigh: cby1 - cby0 + 1
+ };
+ subband.codeblocks = codeblocks;
+ subband.precincts = precincts;
+ }
+ function createPacket(resolution, precinctNumber, layerNumber) {
+ var precinctCodeblocks = [];
+ // Section B.10.8 Order of info in packet
+ var subbands = resolution.subbands;
+ // sub-bands already ordered in 'LL', 'HL', 'LH', and 'HH' sequence
+ for (var i = 0, ii = subbands.length; i < ii; i++) {
+ var subband = subbands[i];
+ var codeblocks = subband.codeblocks;
+ for (var j = 0, jj = codeblocks.length; j < jj; j++) {
+ var codeblock = codeblocks[j];
+ if (codeblock.precinctNumber !== precinctNumber) {
+ continue;
+ }
+ precinctCodeblocks.push(codeblock);
+ }
+ }
+ return {
+ layerNumber: layerNumber,
+ codeblocks: precinctCodeblocks
+ };
+ }
+ function LayerResolutionComponentPositionIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var maxDecompositionLevelsCount = 0;
+ for (var q = 0; q < componentsCount; q++) {
+ maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount);
+ }
+ var l = 0, r = 0, i = 0, k = 0;
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.1 Layer-resolution-component-position
+ for (; l < layersCount; l++) {
+ for (; r <= maxDecompositionLevelsCount; r++) {
+ for (; i < componentsCount; i++) {
+ var component = tile.components[i];
+ if (r > component.codingStyleParameters.decompositionLevelsCount) {
+ continue;
+ }
+ var resolution = component.resolutions[r];
+ var numprecincts = resolution.precinctParameters.numprecincts;
+ for (; k < numprecincts;) {
+ var packet = createPacket(resolution, k, l);
+ k++;
+ return packet;
+ }
+ k = 0;
+ }
+ i = 0;
+ }
+ r = 0;
+ }
+ error('JPX Error: Out of packets');
+ };
+ }
+ function ResolutionLayerComponentPositionIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var maxDecompositionLevelsCount = 0;
+ for (var q = 0; q < componentsCount; q++) {
+ maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount);
+ }
+ var r = 0, l = 0, i = 0, k = 0;
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.2 Resolution-layer-component-position
+ for (; r <= maxDecompositionLevelsCount; r++) {
+ for (; l < layersCount; l++) {
+ for (; i < componentsCount; i++) {
+ var component = tile.components[i];
+ if (r > component.codingStyleParameters.decompositionLevelsCount) {
+ continue;
+ }
+ var resolution = component.resolutions[r];
+ var numprecincts = resolution.precinctParameters.numprecincts;
+ for (; k < numprecincts;) {
+ var packet = createPacket(resolution, k, l);
+ k++;
+ return packet;
+ }
+ k = 0;
+ }
+ i = 0;
+ }
+ l = 0;
+ }
+ error('JPX Error: Out of packets');
+ };
+ }
+ function ResolutionPositionComponentLayerIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var l, r, c, p;
+ var maxDecompositionLevelsCount = 0;
+ for (c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, component.codingStyleParameters.decompositionLevelsCount);
+ }
+ var maxNumPrecinctsInLevel = new Int32Array(maxDecompositionLevelsCount + 1);
+ for (r = 0; r <= maxDecompositionLevelsCount; ++r) {
+ var maxNumPrecincts = 0;
+ for (c = 0; c < componentsCount; ++c) {
+ var resolutions = tile.components[c].resolutions;
+ if (r < resolutions.length) {
+ maxNumPrecincts = Math.max(maxNumPrecincts, resolutions[r].precinctParameters.numprecincts);
+ }
+ }
+ maxNumPrecinctsInLevel[r] = maxNumPrecincts;
+ }
+ l = 0;
+ r = 0;
+ c = 0;
+ p = 0;
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.3 Resolution-position-component-layer
+ for (; r <= maxDecompositionLevelsCount; r++) {
+ for (; p < maxNumPrecinctsInLevel[r]; p++) {
+ for (; c < componentsCount; c++) {
+ var component = tile.components[c];
+ if (r > component.codingStyleParameters.decompositionLevelsCount) {
+ continue;
+ }
+ var resolution = component.resolutions[r];
+ var numprecincts = resolution.precinctParameters.numprecincts;
+ if (p >= numprecincts) {
+ continue;
+ }
+ for (; l < layersCount;) {
+ var packet = createPacket(resolution, p, l);
+ l++;
+ return packet;
+ }
+ l = 0;
+ }
+ c = 0;
+ }
+ p = 0;
+ }
+ error('JPX Error: Out of packets');
+ };
+ }
+ function PositionComponentResolutionLayerIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var precinctsSizes = getPrecinctSizesInImageScale(tile);
+ var precinctsIterationSizes = precinctsSizes;
+ var l = 0, r = 0, c = 0, px = 0, py = 0;
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.4 Position-component-resolution-layer
+ for (; py < precinctsIterationSizes.maxNumHigh; py++) {
+ for (; px < precinctsIterationSizes.maxNumWide; px++) {
+ for (; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
+ for (; r <= decompositionLevelsCount; r++) {
+ var resolution = component.resolutions[r];
+ var sizeInImageScale = precinctsSizes.components[c].resolutions[r];
+ var k = getPrecinctIndexIfExist(px, py, sizeInImageScale, precinctsIterationSizes, resolution);
+ if (k === null) {
+ continue;
+ }
+ for (; l < layersCount;) {
+ var packet = createPacket(resolution, k, l);
+ l++;
+ return packet;
+ }
+ l = 0;
+ }
+ r = 0;
+ }
+ c = 0;
+ }
+ px = 0;
+ }
+ error('JPX Error: Out of packets');
+ };
+ }
+ function ComponentPositionResolutionLayerIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var precinctsSizes = getPrecinctSizesInImageScale(tile);
+ var l = 0, r = 0, c = 0, px = 0, py = 0;
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.5 Component-position-resolution-layer
+ for (; c < componentsCount; ++c) {
+ var component = tile.components[c];
+ var precinctsIterationSizes = precinctsSizes.components[c];
+ var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
+ for (; py < precinctsIterationSizes.maxNumHigh; py++) {
+ for (; px < precinctsIterationSizes.maxNumWide; px++) {
+ for (; r <= decompositionLevelsCount; r++) {
+ var resolution = component.resolutions[r];
+ var sizeInImageScale = precinctsIterationSizes.resolutions[r];
+ var k = getPrecinctIndexIfExist(px, py, sizeInImageScale, precinctsIterationSizes, resolution);
+ if (k === null) {
+ continue;
+ }
+ for (; l < layersCount;) {
+ var packet = createPacket(resolution, k, l);
+ l++;
+ return packet;
+ }
+ l = 0;
+ }
+ r = 0;
+ }
+ px = 0;
+ }
+ py = 0;
+ }
+ error('JPX Error: Out of packets');
+ };
+ }
+ function getPrecinctIndexIfExist(pxIndex, pyIndex, sizeInImageScale, precinctIterationSizes, resolution) {
+ var posX = pxIndex * precinctIterationSizes.minWidth;
+ var posY = pyIndex * precinctIterationSizes.minHeight;
+ if (posX % sizeInImageScale.width !== 0 || posY % sizeInImageScale.height !== 0) {
+ return null;
+ }
+ var startPrecinctRowIndex = posY / sizeInImageScale.width * resolution.precinctParameters.numprecinctswide;
+ return posX / sizeInImageScale.height + startPrecinctRowIndex;
+ }
+ function getPrecinctSizesInImageScale(tile) {
+ var componentsCount = tile.components.length;
+ var minWidth = Number.MAX_VALUE;
+ var minHeight = Number.MAX_VALUE;
+ var maxNumWide = 0;
+ var maxNumHigh = 0;
+ var sizePerComponent = new Array(componentsCount);
+ for (var c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
+ var sizePerResolution = new Array(decompositionLevelsCount + 1);
+ var minWidthCurrentComponent = Number.MAX_VALUE;
+ var minHeightCurrentComponent = Number.MAX_VALUE;
+ var maxNumWideCurrentComponent = 0;
+ var maxNumHighCurrentComponent = 0;
+ var scale = 1;
+ for (var r = decompositionLevelsCount; r >= 0; --r) {
+ var resolution = component.resolutions[r];
+ var widthCurrentResolution = scale * resolution.precinctParameters.precinctWidth;
+ var heightCurrentResolution = scale * resolution.precinctParameters.precinctHeight;
+ minWidthCurrentComponent = Math.min(minWidthCurrentComponent, widthCurrentResolution);
+ minHeightCurrentComponent = Math.min(minHeightCurrentComponent, heightCurrentResolution);
+ maxNumWideCurrentComponent = Math.max(maxNumWideCurrentComponent, resolution.precinctParameters.numprecinctswide);
+ maxNumHighCurrentComponent = Math.max(maxNumHighCurrentComponent, resolution.precinctParameters.numprecinctshigh);
+ sizePerResolution[r] = {
+ width: widthCurrentResolution,
+ height: heightCurrentResolution
+ };
+ scale <<= 1;
+ }
+ minWidth = Math.min(minWidth, minWidthCurrentComponent);
+ minHeight = Math.min(minHeight, minHeightCurrentComponent);
+ maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent);
+ maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent);
+ sizePerComponent[c] = {
+ resolutions: sizePerResolution,
+ minWidth: minWidthCurrentComponent,
+ minHeight: minHeightCurrentComponent,
+ maxNumWide: maxNumWideCurrentComponent,
+ maxNumHigh: maxNumHighCurrentComponent
+ };
+ }
+ return {
+ components: sizePerComponent,
+ minWidth: minWidth,
+ minHeight: minHeight,
+ maxNumWide: maxNumWide,
+ maxNumHigh: maxNumHigh
+ };
+ }
+ function buildPackets(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var componentsCount = siz.Csiz;
+ // Creating resolutions and sub-bands for each component
+ for (var c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount;
+ // Section B.5 Resolution levels and sub-bands
+ var resolutions = [];
+ var subbands = [];
+ for (var r = 0; r <= decompositionLevelsCount; r++) {
+ var blocksDimensions = getBlocksDimensions(context, component, r);
+ var resolution = {};
+ var scale = 1 << decompositionLevelsCount - r;
+ resolution.trx0 = Math.ceil(component.tcx0 / scale);
+ resolution.try0 = Math.ceil(component.tcy0 / scale);
+ resolution.trx1 = Math.ceil(component.tcx1 / scale);
+ resolution.try1 = Math.ceil(component.tcy1 / scale);
+ resolution.resLevel = r;
+ buildPrecincts(context, resolution, blocksDimensions);
+ resolutions.push(resolution);
+ var subband;
+ if (r === 0) {
+ // one sub-band (LL) with last decomposition
+ subband = {};
+ subband.type = 'LL';
+ subband.tbx0 = Math.ceil(component.tcx0 / scale);
+ subband.tby0 = Math.ceil(component.tcy0 / scale);
+ subband.tbx1 = Math.ceil(component.tcx1 / scale);
+ subband.tby1 = Math.ceil(component.tcy1 / scale);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolution.subbands = [subband];
+ } else {
+ var bscale = 1 << decompositionLevelsCount - r + 1;
+ var resolutionSubbands = [];
+ // three sub-bands (HL, LH and HH) with rest of decompositions
+ subband = {};
+ subband.type = 'HL';
+ subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
+ subband.tby0 = Math.ceil(component.tcy0 / bscale);
+ subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
+ subband.tby1 = Math.ceil(component.tcy1 / bscale);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolutionSubbands.push(subband);
+ subband = {};
+ subband.type = 'LH';
+ subband.tbx0 = Math.ceil(component.tcx0 / bscale);
+ subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
+ subband.tbx1 = Math.ceil(component.tcx1 / bscale);
+ subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolutionSubbands.push(subband);
+ subband = {};
+ subband.type = 'HH';
+ subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
+ subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
+ subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
+ subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
+ subband.resolution = resolution;
+ buildCodeblocks(context, subband, blocksDimensions);
+ subbands.push(subband);
+ resolutionSubbands.push(subband);
+ resolution.subbands = resolutionSubbands;
+ }
+ }
+ component.resolutions = resolutions;
+ component.subbands = subbands;
+ }
+ // Generate the packets sequence
+ var progressionOrder = tile.codingStyleDefaultParameters.progressionOrder;
+ switch (progressionOrder) {
+ case 0:
+ tile.packetsIterator = new LayerResolutionComponentPositionIterator(context);
+ break;
+ case 1:
+ tile.packetsIterator = new ResolutionLayerComponentPositionIterator(context);
+ break;
+ case 2:
+ tile.packetsIterator = new ResolutionPositionComponentLayerIterator(context);
+ break;
+ case 3:
+ tile.packetsIterator = new PositionComponentResolutionLayerIterator(context);
+ break;
+ case 4:
+ tile.packetsIterator = new ComponentPositionResolutionLayerIterator(context);
+ break;
+ default:
+ error('JPX Error: Unsupported progression order ' + progressionOrder);
+ }
+ }
+ function parseTilePackets(context, data, offset, dataLength) {
+ var position = 0;
+ var buffer, bufferSize = 0, skipNextBit = false;
+ function readBits(count) {
+ while (bufferSize < count) {
+ var b = data[offset + position];
+ position++;
+ if (skipNextBit) {
+ buffer = buffer << 7 | b;
+ bufferSize += 7;
+ skipNextBit = false;
+ } else {
+ buffer = buffer << 8 | b;
+ bufferSize += 8;
+ }
+ if (b === 0xFF) {
+ skipNextBit = true;
+ }
+ }
+ bufferSize -= count;
+ return buffer >>> bufferSize & (1 << count) - 1;
+ }
+ function skipMarkerIfEqual(value) {
+ if (data[offset + position - 1] === 0xFF && data[offset + position] === value) {
+ skipBytes(1);
+ return true;
+ } else if (data[offset + position] === 0xFF && data[offset + position + 1] === value) {
+ skipBytes(2);
+ return true;
+ }
+ return false;
+ }
+ function skipBytes(count) {
+ position += count;
+ }
+ function alignToByte() {
+ bufferSize = 0;
+ if (skipNextBit) {
+ position++;
+ skipNextBit = false;
+ }
+ }
+ function readCodingpasses() {
+ if (readBits(1) === 0) {
+ return 1;
+ }
+ if (readBits(1) === 0) {
+ return 2;
+ }
+ var value = readBits(2);
+ if (value < 3) {
+ return value + 3;
+ }
+ value = readBits(5);
+ if (value < 31) {
+ return value + 6;
+ }
+ value = readBits(7);
+ return value + 37;
+ }
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var sopMarkerUsed = context.COD.sopMarkerUsed;
+ var ephMarkerUsed = context.COD.ephMarkerUsed;
+ var packetsIterator = tile.packetsIterator;
+ while (position < dataLength) {
+ alignToByte();
+ if (sopMarkerUsed && skipMarkerIfEqual(0x91)) {
+ // Skip also marker segment length and packet sequence ID
+ skipBytes(4);
+ }
+ var packet = packetsIterator.nextPacket();
+ if (!readBits(1)) {
+ continue;
+ }
+ var layerNumber = packet.layerNumber;
+ var queue = [], codeblock;
+ for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) {
+ codeblock = packet.codeblocks[i];
+ var precinct = codeblock.precinct;
+ var codeblockColumn = codeblock.cbx - precinct.cbxMin;
+ var codeblockRow = codeblock.cby - precinct.cbyMin;
+ var codeblockIncluded = false;
+ var firstTimeInclusion = false;
+ var valueReady;
+ if (codeblock['included'] !== undefined) {
+ codeblockIncluded = !!readBits(1);
+ } else {
+ // reading inclusion tree
+ precinct = codeblock.precinct;
+ var inclusionTree, zeroBitPlanesTree;
+ if (precinct['inclusionTree'] !== undefined) {
+ inclusionTree = precinct.inclusionTree;
+ } else {
+ // building inclusion and zero bit-planes trees
+ var width = precinct.cbxMax - precinct.cbxMin + 1;
+ var height = precinct.cbyMax - precinct.cbyMin + 1;
+ inclusionTree = new InclusionTree(width, height, layerNumber);
+ zeroBitPlanesTree = new TagTree(width, height);
+ precinct.inclusionTree = inclusionTree;
+ precinct.zeroBitPlanesTree = zeroBitPlanesTree;
+ }
+ if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) {
+ while (true) {
+ if (readBits(1)) {
+ valueReady = !inclusionTree.nextLevel();
+ if (valueReady) {
+ codeblock.included = true;
+ codeblockIncluded = firstTimeInclusion = true;
+ break;
+ }
+ } else {
+ inclusionTree.incrementValue(layerNumber);
+ break;
+ }
+ }
+ }
+ }
+ if (!codeblockIncluded) {
+ continue;
+ }
+ if (firstTimeInclusion) {
+ zeroBitPlanesTree = precinct.zeroBitPlanesTree;
+ zeroBitPlanesTree.reset(codeblockColumn, codeblockRow);
+ while (true) {
+ if (readBits(1)) {
+ valueReady = !zeroBitPlanesTree.nextLevel();
+ if (valueReady) {
+ break;
+ }
+ } else {
+ zeroBitPlanesTree.incrementValue();
+ }
+ }
+ codeblock.zeroBitPlanes = zeroBitPlanesTree.value;
+ }
+ var codingpasses = readCodingpasses();
+ while (readBits(1)) {
+ codeblock.Lblock++;
+ }
+ var codingpassesLog2 = log2(codingpasses);
+ // rounding down log2
+ var bits = (codingpasses < 1 << codingpassesLog2 ? codingpassesLog2 - 1 : codingpassesLog2) + codeblock.Lblock;
+ var codedDataLength = readBits(bits);
+ queue.push({
+ codeblock: codeblock,
+ codingpasses: codingpasses,
+ dataLength: codedDataLength
+ });
+ }
+ alignToByte();
+ if (ephMarkerUsed) {
+ skipMarkerIfEqual(0x92);
+ }
+ while (queue.length > 0) {
+ var packetItem = queue.shift();
+ codeblock = packetItem.codeblock;
+ if (codeblock['data'] === undefined) {
+ codeblock.data = [];
+ }
+ codeblock.data.push({
+ data: data,
+ start: offset + position,
+ end: offset + position + packetItem.dataLength,
+ codingpasses: packetItem.codingpasses
+ });
+ position += packetItem.dataLength;
+ }
+ }
+ return position;
+ }
+ function copyCoefficients(coefficients, levelWidth, levelHeight, subband, delta, mb, reversible, segmentationSymbolUsed) {
+ var x0 = subband.tbx0;
+ var y0 = subband.tby0;
+ var width = subband.tbx1 - subband.tbx0;
+ var codeblocks = subband.codeblocks;
+ var right = subband.type.charAt(0) === 'H' ? 1 : 0;
+ var bottom = subband.type.charAt(1) === 'H' ? levelWidth : 0;
+ for (var i = 0, ii = codeblocks.length; i < ii; ++i) {
+ var codeblock = codeblocks[i];
+ var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
+ var blockHeight = codeblock.tby1_ - codeblock.tby0_;
+ if (blockWidth === 0 || blockHeight === 0) {
+ continue;
+ }
+ if (codeblock['data'] === undefined) {
+ continue;
+ }
+ var bitModel, currentCodingpassType;
+ bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType, codeblock.zeroBitPlanes, mb);
+ currentCodingpassType = 2;
+ // first bit plane starts from cleanup
+ // collect data
+ var data = codeblock.data, totalLength = 0, codingpasses = 0;
+ var j, jj, dataItem;
+ for (j = 0, jj = data.length; j < jj; j++) {
+ dataItem = data[j];
+ totalLength += dataItem.end - dataItem.start;
+ codingpasses += dataItem.codingpasses;
+ }
+ var encodedData = new Uint8Array(totalLength);
+ var position = 0;
+ for (j = 0, jj = data.length; j < jj; j++) {
+ dataItem = data[j];
+ var chunk = dataItem.data.subarray(dataItem.start, dataItem.end);
+ encodedData.set(chunk, position);
+ position += chunk.length;
+ }
+ // decoding the item
+ var decoder = new ArithmeticDecoder(encodedData, 0, totalLength);
+ bitModel.setDecoder(decoder);
+ for (j = 0; j < codingpasses; j++) {
+ switch (currentCodingpassType) {
+ case 0:
+ bitModel.runSignificancePropagationPass();
+ break;
+ case 1:
+ bitModel.runMagnitudeRefinementPass();
+ break;
+ case 2:
+ bitModel.runCleanupPass();
+ if (segmentationSymbolUsed) {
+ bitModel.checkSegmentationSymbol();
+ }
+ break;
+ }
+ currentCodingpassType = (currentCodingpassType + 1) % 3;
+ }
+ var offset = codeblock.tbx0_ - x0 + (codeblock.tby0_ - y0) * width;
+ var sign = bitModel.coefficentsSign;
+ var magnitude = bitModel.coefficentsMagnitude;
+ var bitsDecoded = bitModel.bitsDecoded;
+ var magnitudeCorrection = reversible ? 0 : 0.5;
+ var k, n, nb;
+ position = 0;
+ // Do the interleaving of Section F.3.3 here, so we do not need
+ // to copy later. LL level is not interleaved, just copied.
+ var interleave = subband.type !== 'LL';
+ for (j = 0; j < blockHeight; j++) {
+ var row = offset / width | 0;
+ // row in the non-interleaved subband
+ var levelOffset = 2 * row * (levelWidth - width) + right + bottom;
+ for (k = 0; k < blockWidth; k++) {
+ n = magnitude[position];
+ if (n !== 0) {
+ n = (n + magnitudeCorrection) * delta;
+ if (sign[position] !== 0) {
+ n = -n;
+ }
+ nb = bitsDecoded[position];
+ var pos = interleave ? levelOffset + (offset << 1) : offset;
+ if (reversible && nb >= mb) {
+ coefficients[pos] = n;
+ } else {
+ coefficients[pos] = n * (1 << mb - nb);
+ }
+ }
+ offset++;
+ position++;
+ }
+ offset += width - blockWidth;
+ }
+ }
+ }
+ function transformTile(context, tile, c) {
+ var component = tile.components[c];
+ var codingStyleParameters = component.codingStyleParameters;
+ var quantizationParameters = component.quantizationParameters;
+ var decompositionLevelsCount = codingStyleParameters.decompositionLevelsCount;
+ var spqcds = quantizationParameters.SPqcds;
+ var scalarExpounded = quantizationParameters.scalarExpounded;
+ var guardBits = quantizationParameters.guardBits;
+ var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed;
+ var precision = context.components[c].precision;
+ var reversible = codingStyleParameters.reversibleTransformation;
+ var transform = reversible ? new ReversibleTransform() : new IrreversibleTransform();
+ var subbandCoefficients = [];
+ var b = 0;
+ for (var i = 0; i <= decompositionLevelsCount; i++) {
+ var resolution = component.resolutions[i];
+ var width = resolution.trx1 - resolution.trx0;
+ var height = resolution.try1 - resolution.try0;
+ // Allocate space for the whole sublevel.
+ var coefficients = new Float32Array(width * height);
+ for (var j = 0, jj = resolution.subbands.length; j < jj; j++) {
+ var mu, epsilon;
+ if (!scalarExpounded) {
+ // formula E-5
+ mu = spqcds[0].mu;
+ epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0);
+ } else {
+ mu = spqcds[b].mu;
+ epsilon = spqcds[b].epsilon;
+ b++;
+ }
+ var subband = resolution.subbands[j];
+ var gainLog2 = SubbandsGainLog2[subband.type];
+ // calculate quantization coefficient (Section E.1.1.1)
+ var delta = reversible ? 1 : Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048);
+ var mb = guardBits + epsilon - 1;
+ // In the first resolution level, copyCoefficients will fill the
+ // whole array with coefficients. In the succeeding passes,
+ // copyCoefficients will consecutively fill in the values that belong
+ // to the interleaved positions of the HL, LH, and HH coefficients.
+ // The LL coefficients will then be interleaved in Transform.iterate().
+ copyCoefficients(coefficients, width, height, subband, delta, mb, reversible, segmentationSymbolUsed);
+ }
+ subbandCoefficients.push({
+ width: width,
+ height: height,
+ items: coefficients
+ });
+ }
+ var result = transform.calculate(subbandCoefficients, component.tcx0, component.tcy0);
+ return {
+ left: component.tcx0,
+ top: component.tcy0,
+ width: result.width,
+ height: result.height,
+ items: result.items
+ };
+ }
+ function transformComponents(context) {
+ var siz = context.SIZ;
+ var components = context.components;
+ var componentsCount = siz.Csiz;
+ var resultImages = [];
+ for (var i = 0, ii = context.tiles.length; i < ii; i++) {
+ var tile = context.tiles[i];
+ var transformedTiles = [];
+ var c;
+ for (c = 0; c < componentsCount; c++) {
+ transformedTiles[c] = transformTile(context, tile, c);
+ }
+ var tile0 = transformedTiles[0];
+ var out = new Uint8Array(tile0.items.length * componentsCount);
+ var result = {
+ left: tile0.left,
+ top: tile0.top,
+ width: tile0.width,
+ height: tile0.height,
+ items: out
+ };
+ // Section G.2.2 Inverse multi component transform
+ var shift, offset, max, min, maxK;
+ var pos = 0, j, jj, y0, y1, y2, r, g, b, k, val;
+ if (tile.codingStyleDefaultParameters.multipleComponentTransform) {
+ var fourComponents = componentsCount === 4;
+ var y0items = transformedTiles[0].items;
+ var y1items = transformedTiles[1].items;
+ var y2items = transformedTiles[2].items;
+ var y3items = fourComponents ? transformedTiles[3].items : null;
+ // HACK: The multiple component transform formulas below assume that
+ // all components have the same precision. With this in mind, we
+ // compute shift and offset only once.
+ shift = components[0].precision - 8;
+ offset = (128 << shift) + 0.5;
+ max = 255 * (1 << shift);
+ maxK = max * 0.5;
+ min = -maxK;
+ var component0 = tile.components[0];
+ var alpha01 = componentsCount - 3;
+ jj = y0items.length;
+ if (!component0.codingStyleParameters.reversibleTransformation) {
+ // inverse irreversible multiple component transform
+ for (j = 0; j < jj; j++, pos += alpha01) {
+ y0 = y0items[j] + offset;
+ y1 = y1items[j];
+ y2 = y2items[j];
+ r = y0 + 1.402 * y2;
+ g = y0 - 0.34413 * y1 - 0.71414 * y2;
+ b = y0 + 1.772 * y1;
+ out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift;
+ out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift;
+ out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift;
+ }
+ } else {
+ // inverse reversible multiple component transform
+ for (j = 0; j < jj; j++, pos += alpha01) {
+ y0 = y0items[j] + offset;
+ y1 = y1items[j];
+ y2 = y2items[j];
+ g = y0 - (y2 + y1 >> 2);
+ r = g + y2;
+ b = g + y1;
+ out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift;
+ out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift;
+ out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift;
+ }
+ }
+ if (fourComponents) {
+ for (j = 0, pos = 3; j < jj; j++, pos += 4) {
+ k = y3items[j];
+ out[pos] = k <= min ? 0 : k >= maxK ? 255 : k + offset >> shift;
+ }
+ }
+ } else {
+ // no multi-component transform
+ for (c = 0; c < componentsCount; c++) {
+ var items = transformedTiles[c].items;
+ shift = components[c].precision - 8;
+ offset = (128 << shift) + 0.5;
+ max = 127.5 * (1 << shift);
+ min = -max;
+ for (pos = c, j = 0, jj = items.length; j < jj; j++) {
+ val = items[j];
+ out[pos] = val <= min ? 0 : val >= max ? 255 : val + offset >> shift;
+ pos += componentsCount;
+ }
+ }
+ }
+ resultImages.push(result);
+ }
+ return resultImages;
+ }
+ function initializeTile(context, tileIndex) {
+ var siz = context.SIZ;
+ var componentsCount = siz.Csiz;
+ var tile = context.tiles[tileIndex];
+ for (var c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var qcdOrQcc = context.currentTile.QCC[c] !== undefined ? context.currentTile.QCC[c] : context.currentTile.QCD;
+ component.quantizationParameters = qcdOrQcc;
+ var codOrCoc = context.currentTile.COC[c] !== undefined ? context.currentTile.COC[c] : context.currentTile.COD;
+ component.codingStyleParameters = codOrCoc;
+ }
+ tile.codingStyleDefaultParameters = context.currentTile.COD;
+ }
+ // Section B.10.2 Tag trees
+ var TagTree = function TagTreeClosure() {
+ function TagTree(width, height) {
+ var levelsLength = log2(Math.max(width, height)) + 1;
+ this.levels = [];
+ for (var i = 0; i < levelsLength; i++) {
+ var level = {
+ width: width,
+ height: height,
+ items: []
+ };
+ this.levels.push(level);
+ width = Math.ceil(width / 2);
+ height = Math.ceil(height / 2);
+ }
+ }
+ TagTree.prototype = {
+ reset: function TagTree_reset(i, j) {
+ var currentLevel = 0, value = 0, level;
+ while (currentLevel < this.levels.length) {
+ level = this.levels[currentLevel];
+ var index = i + j * level.width;
+ if (level.items[index] !== undefined) {
+ value = level.items[index];
+ break;
+ }
+ level.index = index;
+ i >>= 1;
+ j >>= 1;
+ currentLevel++;
+ }
+ currentLevel--;
+ level = this.levels[currentLevel];
+ level.items[level.index] = value;
+ this.currentLevel = currentLevel;
+ delete this.value;
+ },
+ incrementValue: function TagTree_incrementValue() {
+ var level = this.levels[this.currentLevel];
+ level.items[level.index]++;
+ },
+ nextLevel: function TagTree_nextLevel() {
+ var currentLevel = this.currentLevel;
+ var level = this.levels[currentLevel];
+ var value = level.items[level.index];
+ currentLevel--;
+ if (currentLevel < 0) {
+ this.value = value;
+ return false;
+ }
+ this.currentLevel = currentLevel;
+ level = this.levels[currentLevel];
+ level.items[level.index] = value;
+ return true;
+ }
+ };
+ return TagTree;
+ }();
+ var InclusionTree = function InclusionTreeClosure() {
+ function InclusionTree(width, height, defaultValue) {
+ var levelsLength = log2(Math.max(width, height)) + 1;
+ this.levels = [];
+ for (var i = 0; i < levelsLength; i++) {
+ var items = new Uint8Array(width * height);
+ for (var j = 0, jj = items.length; j < jj; j++) {
+ items[j] = defaultValue;
+ }
+ var level = {
+ width: width,
+ height: height,
+ items: items
+ };
+ this.levels.push(level);
+ width = Math.ceil(width / 2);
+ height = Math.ceil(height / 2);
+ }
+ }
+ InclusionTree.prototype = {
+ reset: function InclusionTree_reset(i, j, stopValue) {
+ var currentLevel = 0;
+ while (currentLevel < this.levels.length) {
+ var level = this.levels[currentLevel];
+ var index = i + j * level.width;
+ level.index = index;
+ var value = level.items[index];
+ if (value === 0xFF) {
+ break;
+ }
+ if (value > stopValue) {
+ this.currentLevel = currentLevel;
+ // already know about this one, propagating the value to top levels
+ this.propagateValues();
+ return false;
+ }
+ i >>= 1;
+ j >>= 1;
+ currentLevel++;
+ }
+ this.currentLevel = currentLevel - 1;
+ return true;
+ },
+ incrementValue: function InclusionTree_incrementValue(stopValue) {
+ var level = this.levels[this.currentLevel];
+ level.items[level.index] = stopValue + 1;
+ this.propagateValues();
+ },
+ propagateValues: function InclusionTree_propagateValues() {
+ var levelIndex = this.currentLevel;
+ var level = this.levels[levelIndex];
+ var currentValue = level.items[level.index];
+ while (--levelIndex >= 0) {
+ level = this.levels[levelIndex];
+ level.items[level.index] = currentValue;
+ }
+ },
+ nextLevel: function InclusionTree_nextLevel() {
+ var currentLevel = this.currentLevel;
+ var level = this.levels[currentLevel];
+ var value = level.items[level.index];
+ level.items[level.index] = 0xFF;
+ currentLevel--;
+ if (currentLevel < 0) {
+ return false;
+ }
+ this.currentLevel = currentLevel;
+ level = this.levels[currentLevel];
+ level.items[level.index] = value;
+ return true;
+ }
+ };
+ return InclusionTree;
+ }();
+ // Section D. Coefficient bit modeling
+ var BitModel = function BitModelClosure() {
+ var UNIFORM_CONTEXT = 17;
+ var RUNLENGTH_CONTEXT = 18;
+ // Table D-1
+ // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4),
+ // vv - sum of Vi (0..2), and hh - sum of Hi (0..2)
+ var LLAndLHContextsLabel = new Uint8Array([
+ 0,
+ 5,
+ 8,
+ 0,
+ 3,
+ 7,
+ 8,
+ 0,
+ 4,
+ 7,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 6,
+ 8,
+ 0,
+ 3,
+ 7,
+ 8,
+ 0,
+ 4,
+ 7,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 6,
+ 8,
+ 0,
+ 3,
+ 7,
+ 8,
+ 0,
+ 4,
+ 7,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 6,
+ 8,
+ 0,
+ 3,
+ 7,
+ 8,
+ 0,
+ 4,
+ 7,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 6,
+ 8,
+ 0,
+ 3,
+ 7,
+ 8,
+ 0,
+ 4,
+ 7,
+ 8
+ ]);
+ var HLContextLabel = new Uint8Array([
+ 0,
+ 3,
+ 4,
+ 0,
+ 5,
+ 7,
+ 7,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 3,
+ 4,
+ 0,
+ 6,
+ 7,
+ 7,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 3,
+ 4,
+ 0,
+ 6,
+ 7,
+ 7,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 3,
+ 4,
+ 0,
+ 6,
+ 7,
+ 7,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 3,
+ 4,
+ 0,
+ 6,
+ 7,
+ 7,
+ 0,
+ 8,
+ 8,
+ 8
+ ]);
+ var HHContextLabel = new Uint8Array([
+ 0,
+ 1,
+ 2,
+ 0,
+ 1,
+ 2,
+ 2,
+ 0,
+ 2,
+ 2,
+ 2,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 3,
+ 4,
+ 5,
+ 0,
+ 4,
+ 5,
+ 5,
+ 0,
+ 5,
+ 5,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 6,
+ 7,
+ 7,
+ 0,
+ 7,
+ 7,
+ 7,
+ 0,
+ 7,
+ 7,
+ 7,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 8,
+ 8,
+ 8,
+ 0,
+ 8,
+ 8,
+ 8
+ ]);
+ function BitModel(width, height, subband, zeroBitPlanes, mb) {
+ this.width = width;
+ this.height = height;
+ this.contextLabelTable = subband === 'HH' ? HHContextLabel : subband === 'HL' ? HLContextLabel : LLAndLHContextsLabel;
+ var coefficientCount = width * height;
+ // coefficients outside the encoding region treated as insignificant
+ // add border state cells for significanceState
+ this.neighborsSignificance = new Uint8Array(coefficientCount);
+ this.coefficentsSign = new Uint8Array(coefficientCount);
+ this.coefficentsMagnitude = mb > 14 ? new Uint32Array(coefficientCount) : mb > 6 ? new Uint16Array(coefficientCount) : new Uint8Array(coefficientCount);
+ this.processingFlags = new Uint8Array(coefficientCount);
+ var bitsDecoded = new Uint8Array(coefficientCount);
+ if (zeroBitPlanes !== 0) {
+ for (var i = 0; i < coefficientCount; i++) {
+ bitsDecoded[i] = zeroBitPlanes;
+ }
+ }
+ this.bitsDecoded = bitsDecoded;
+ this.reset();
+ }
+ BitModel.prototype = {
+ setDecoder: function BitModel_setDecoder(decoder) {
+ this.decoder = decoder;
+ },
+ reset: function BitModel_reset() {
+ // We have 17 contexts that are accessed via context labels,
+ // plus the uniform and runlength context.
+ this.contexts = new Int8Array(19);
+ // Contexts are packed into 1 byte:
+ // highest 7 bits carry the index, lowest bit carries mps
+ this.contexts[0] = 4 << 1 | 0;
+ this.contexts[UNIFORM_CONTEXT] = 46 << 1 | 0;
+ this.contexts[RUNLENGTH_CONTEXT] = 3 << 1 | 0;
+ },
+ setNeighborsSignificance: function BitModel_setNeighborsSignificance(row, column, index) {
+ var neighborsSignificance = this.neighborsSignificance;
+ var width = this.width, height = this.height;
+ var left = column > 0;
+ var right = column + 1 < width;
+ var i;
+ if (row > 0) {
+ i = index - width;
+ if (left) {
+ neighborsSignificance[i - 1] += 0x10;
+ }
+ if (right) {
+ neighborsSignificance[i + 1] += 0x10;
+ }
+ neighborsSignificance[i] += 0x04;
+ }
+ if (row + 1 < height) {
+ i = index + width;
+ if (left) {
+ neighborsSignificance[i - 1] += 0x10;
+ }
+ if (right) {
+ neighborsSignificance[i + 1] += 0x10;
+ }
+ neighborsSignificance[i] += 0x04;
+ }
+ if (left) {
+ neighborsSignificance[index - 1] += 0x01;
+ }
+ if (right) {
+ neighborsSignificance[index + 1] += 0x01;
+ }
+ neighborsSignificance[index] |= 0x80;
+ },
+ runSignificancePropagationPass: function BitModel_runSignificancePropagationPass() {
+ var decoder = this.decoder;
+ var width = this.width, height = this.height;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var coefficentsSign = this.coefficentsSign;
+ var neighborsSignificance = this.neighborsSignificance;
+ var processingFlags = this.processingFlags;
+ var contexts = this.contexts;
+ var labels = this.contextLabelTable;
+ var bitsDecoded = this.bitsDecoded;
+ var processedInverseMask = ~1;
+ var processedMask = 1;
+ var firstMagnitudeBitMask = 2;
+ for (var i0 = 0; i0 < height; i0 += 4) {
+ for (var j = 0; j < width; j++) {
+ var index = i0 * width + j;
+ for (var i1 = 0; i1 < 4; i1++, index += width) {
+ var i = i0 + i1;
+ if (i >= height) {
+ break;
+ }
+ // clear processed flag first
+ processingFlags[index] &= processedInverseMask;
+ if (coefficentsMagnitude[index] || !neighborsSignificance[index]) {
+ continue;
+ }
+ var contextLabel = labels[neighborsSignificance[index]];
+ var decision = decoder.readBit(contexts, contextLabel);
+ if (decision) {
+ var sign = this.decodeSignBit(i, j, index);
+ coefficentsSign[index] = sign;
+ coefficentsMagnitude[index] = 1;
+ this.setNeighborsSignificance(i, j, index);
+ processingFlags[index] |= firstMagnitudeBitMask;
+ }
+ bitsDecoded[index]++;
+ processingFlags[index] |= processedMask;
+ }
+ }
+ }
+ },
+ decodeSignBit: function BitModel_decodeSignBit(row, column, index) {
+ var width = this.width, height = this.height;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var coefficentsSign = this.coefficentsSign;
+ var contribution, sign0, sign1, significance1;
+ var contextLabel, decoded;
+ // calculate horizontal contribution
+ significance1 = column > 0 && coefficentsMagnitude[index - 1] !== 0;
+ if (column + 1 < width && coefficentsMagnitude[index + 1] !== 0) {
+ sign1 = coefficentsSign[index + 1];
+ if (significance1) {
+ sign0 = coefficentsSign[index - 1];
+ contribution = 1 - sign1 - sign0;
+ } else {
+ contribution = 1 - sign1 - sign1;
+ }
+ } else if (significance1) {
+ sign0 = coefficentsSign[index - 1];
+ contribution = 1 - sign0 - sign0;
+ } else {
+ contribution = 0;
+ }
+ var horizontalContribution = 3 * contribution;
+ // calculate vertical contribution and combine with the horizontal
+ significance1 = row > 0 && coefficentsMagnitude[index - width] !== 0;
+ if (row + 1 < height && coefficentsMagnitude[index + width] !== 0) {
+ sign1 = coefficentsSign[index + width];
+ if (significance1) {
+ sign0 = coefficentsSign[index - width];
+ contribution = 1 - sign1 - sign0 + horizontalContribution;
+ } else {
+ contribution = 1 - sign1 - sign1 + horizontalContribution;
+ }
+ } else if (significance1) {
+ sign0 = coefficentsSign[index - width];
+ contribution = 1 - sign0 - sign0 + horizontalContribution;
+ } else {
+ contribution = horizontalContribution;
+ }
+ if (contribution >= 0) {
+ contextLabel = 9 + contribution;
+ decoded = this.decoder.readBit(this.contexts, contextLabel);
+ } else {
+ contextLabel = 9 - contribution;
+ decoded = this.decoder.readBit(this.contexts, contextLabel) ^ 1;
+ }
+ return decoded;
+ },
+ runMagnitudeRefinementPass: function BitModel_runMagnitudeRefinementPass() {
+ var decoder = this.decoder;
+ var width = this.width, height = this.height;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var neighborsSignificance = this.neighborsSignificance;
+ var contexts = this.contexts;
+ var bitsDecoded = this.bitsDecoded;
+ var processingFlags = this.processingFlags;
+ var processedMask = 1;
+ var firstMagnitudeBitMask = 2;
+ var length = width * height;
+ var width4 = width * 4;
+ for (var index0 = 0, indexNext; index0 < length; index0 = indexNext) {
+ indexNext = Math.min(length, index0 + width4);
+ for (var j = 0; j < width; j++) {
+ for (var index = index0 + j; index < indexNext; index += width) {
+ // significant but not those that have just become
+ if (!coefficentsMagnitude[index] || (processingFlags[index] & processedMask) !== 0) {
+ continue;
+ }
+ var contextLabel = 16;
+ if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) {
+ processingFlags[index] ^= firstMagnitudeBitMask;
+ // first refinement
+ var significance = neighborsSignificance[index] & 127;
+ contextLabel = significance === 0 ? 15 : 14;
+ }
+ var bit = decoder.readBit(contexts, contextLabel);
+ coefficentsMagnitude[index] = coefficentsMagnitude[index] << 1 | bit;
+ bitsDecoded[index]++;
+ processingFlags[index] |= processedMask;
+ }
+ }
+ }
+ },
+ runCleanupPass: function BitModel_runCleanupPass() {
+ var decoder = this.decoder;
+ var width = this.width, height = this.height;
+ var neighborsSignificance = this.neighborsSignificance;
+ var coefficentsMagnitude = this.coefficentsMagnitude;
+ var coefficentsSign = this.coefficentsSign;
+ var contexts = this.contexts;
+ var labels = this.contextLabelTable;
+ var bitsDecoded = this.bitsDecoded;
+ var processingFlags = this.processingFlags;
+ var processedMask = 1;
+ var firstMagnitudeBitMask = 2;
+ var oneRowDown = width;
+ var twoRowsDown = width * 2;
+ var threeRowsDown = width * 3;
+ var iNext;
+ for (var i0 = 0; i0 < height; i0 = iNext) {
+ iNext = Math.min(i0 + 4, height);
+ var indexBase = i0 * width;
+ var checkAllEmpty = i0 + 3 < height;
+ for (var j = 0; j < width; j++) {
+ var index0 = indexBase + j;
+ // using the property: labels[neighborsSignificance[index]] === 0
+ // when neighborsSignificance[index] === 0
+ var allEmpty = checkAllEmpty && processingFlags[index0] === 0 && processingFlags[index0 + oneRowDown] === 0 && processingFlags[index0 + twoRowsDown] === 0 && processingFlags[index0 + threeRowsDown] === 0 && neighborsSignificance[index0] === 0 && neighborsSignificance[index0 + oneRowDown] === 0 && neighborsSignificance[index0 + twoRowsDown] === 0 && neighborsSignificance[index0 + threeRowsDown] === 0;
+ var i1 = 0, index = index0;
+ var i = i0, sign;
+ if (allEmpty) {
+ var hasSignificantCoefficent = decoder.readBit(contexts, RUNLENGTH_CONTEXT);
+ if (!hasSignificantCoefficent) {
+ bitsDecoded[index0]++;
+ bitsDecoded[index0 + oneRowDown]++;
+ bitsDecoded[index0 + twoRowsDown]++;
+ bitsDecoded[index0 + threeRowsDown]++;
+ continue;
+ }
+ // next column
+ i1 = decoder.readBit(contexts, UNIFORM_CONTEXT) << 1 | decoder.readBit(contexts, UNIFORM_CONTEXT);
+ if (i1 !== 0) {
+ i = i0 + i1;
+ index += i1 * width;
+ }
+ sign = this.decodeSignBit(i, j, index);
+ coefficentsSign[index] = sign;
+ coefficentsMagnitude[index] = 1;
+ this.setNeighborsSignificance(i, j, index);
+ processingFlags[index] |= firstMagnitudeBitMask;
+ index = index0;
+ for (var i2 = i0; i2 <= i; i2++, index += width) {
+ bitsDecoded[index]++;
+ }
+ i1++;
+ }
+ for (i = i0 + i1; i < iNext; i++, index += width) {
+ if (coefficentsMagnitude[index] || (processingFlags[index] & processedMask) !== 0) {
+ continue;
+ }
+ var contextLabel = labels[neighborsSignificance[index]];
+ var decision = decoder.readBit(contexts, contextLabel);
+ if (decision === 1) {
+ sign = this.decodeSignBit(i, j, index);
+ coefficentsSign[index] = sign;
+ coefficentsMagnitude[index] = 1;
+ this.setNeighborsSignificance(i, j, index);
+ processingFlags[index] |= firstMagnitudeBitMask;
+ }
+ bitsDecoded[index]++;
+ }
+ }
+ }
+ },
+ checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() {
+ var decoder = this.decoder;
+ var contexts = this.contexts;
+ var symbol = decoder.readBit(contexts, UNIFORM_CONTEXT) << 3 | decoder.readBit(contexts, UNIFORM_CONTEXT) << 2 | decoder.readBit(contexts, UNIFORM_CONTEXT) << 1 | decoder.readBit(contexts, UNIFORM_CONTEXT);
+ if (symbol !== 0xA) {
+ error('JPX Error: Invalid segmentation symbol');
+ }
+ }
+ };
+ return BitModel;
+ }();
+ // Section F, Discrete wavelet transformation
+ var Transform = function TransformClosure() {
+ function Transform() {
+ }
+ Transform.prototype.calculate = function transformCalculate(subbands, u0, v0) {
+ var ll = subbands[0];
+ for (var i = 1, ii = subbands.length; i < ii; i++) {
+ ll = this.iterate(ll, subbands[i], u0, v0);
+ }
+ return ll;
+ };
+ Transform.prototype.extend = function extend(buffer, offset, size) {
+ // Section F.3.7 extending... using max extension of 4
+ var i1 = offset - 1, j1 = offset + 1;
+ var i2 = offset + size - 2, j2 = offset + size;
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1--] = buffer[j1++];
+ buffer[j2++] = buffer[i2--];
+ buffer[i1] = buffer[j1];
+ buffer[j2] = buffer[i2];
+ };
+ Transform.prototype.iterate = function Transform_iterate(ll, hl_lh_hh, u0, v0) {
+ var llWidth = ll.width, llHeight = ll.height, llItems = ll.items;
+ var width = hl_lh_hh.width;
+ var height = hl_lh_hh.height;
+ var items = hl_lh_hh.items;
+ var i, j, k, l, u, v;
+ // Interleave LL according to Section F.3.3
+ for (k = 0, i = 0; i < llHeight; i++) {
+ l = i * 2 * width;
+ for (j = 0; j < llWidth; j++, k++, l += 2) {
+ items[l] = llItems[k];
+ }
+ }
+ // The LL band is not needed anymore.
+ llItems = ll.items = null;
+ var bufferPadding = 4;
+ var rowBuffer = new Float32Array(width + 2 * bufferPadding);
+ // Section F.3.4 HOR_SR
+ if (width === 1) {
+ // if width = 1, when u0 even keep items as is, when odd divide by 2
+ if ((u0 & 1) !== 0) {
+ for (v = 0, k = 0; v < height; v++, k += width) {
+ items[k] *= 0.5;
+ }
+ }
+ } else {
+ for (v = 0, k = 0; v < height; v++, k += width) {
+ rowBuffer.set(items.subarray(k, k + width), bufferPadding);
+ this.extend(rowBuffer, bufferPadding, width);
+ this.filter(rowBuffer, bufferPadding, width);
+ items.set(rowBuffer.subarray(bufferPadding, bufferPadding + width), k);
+ }
+ }
+ // Accesses to the items array can take long, because it may not fit into
+ // CPU cache and has to be fetched from main memory. Since subsequent
+ // accesses to the items array are not local when reading columns, we
+ // have a cache miss every time. To reduce cache misses, get up to
+ // 'numBuffers' items at a time and store them into the individual
+ // buffers. The colBuffers should be small enough to fit into CPU cache.
+ var numBuffers = 16;
+ var colBuffers = [];
+ for (i = 0; i < numBuffers; i++) {
+ colBuffers.push(new Float32Array(height + 2 * bufferPadding));
+ }
+ var b, currentBuffer = 0;
+ ll = bufferPadding + height;
+ // Section F.3.5 VER_SR
+ if (height === 1) {
+ // if height = 1, when v0 even keep items as is, when odd divide by 2
+ if ((v0 & 1) !== 0) {
+ for (u = 0; u < width; u++) {
+ items[u] *= 0.5;
+ }
+ }
+ } else {
+ for (u = 0; u < width; u++) {
+ // if we ran out of buffers, copy several image columns at once
+ if (currentBuffer === 0) {
+ numBuffers = Math.min(width - u, numBuffers);
+ for (k = u, l = bufferPadding; l < ll; k += width, l++) {
+ for (b = 0; b < numBuffers; b++) {
+ colBuffers[b][l] = items[k + b];
+ }
+ }
+ currentBuffer = numBuffers;
+ }
+ currentBuffer--;
+ var buffer = colBuffers[currentBuffer];
+ this.extend(buffer, bufferPadding, height);
+ this.filter(buffer, bufferPadding, height);
+ // If this is last buffer in this group of buffers, flush all buffers.
+ if (currentBuffer === 0) {
+ k = u - numBuffers + 1;
+ for (l = bufferPadding; l < ll; k += width, l++) {
+ for (b = 0; b < numBuffers; b++) {
+ items[k + b] = colBuffers[b][l];
+ }
+ }
+ }
+ }
+ }
+ return {
+ width: width,
+ height: height,
+ items: items
+ };
+ };
+ return Transform;
+ }();
+ // Section 3.8.2 Irreversible 9-7 filter
+ var IrreversibleTransform = function IrreversibleTransformClosure() {
+ function IrreversibleTransform() {
+ Transform.call(this);
+ }
+ IrreversibleTransform.prototype = Object.create(Transform.prototype);
+ IrreversibleTransform.prototype.filter = function irreversibleTransformFilter(x, offset, length) {
+ var len = length >> 1;
+ offset = offset | 0;
+ var j, n, current, next;
+ var alpha = -1.586134342059924;
+ var beta = -0.052980118572961;
+ var gamma = 0.882911075530934;
+ var delta = 0.443506852043971;
+ var K = 1.230174104914001;
+ var K_ = 1 / K;
+ // step 1 is combined with step 3
+ // step 2
+ j = offset - 3;
+ for (n = len + 4; n--; j += 2) {
+ x[j] *= K_;
+ }
+ // step 1 & 3
+ j = offset - 2;
+ current = delta * x[j - 1];
+ for (n = len + 3; n--; j += 2) {
+ next = delta * x[j + 1];
+ x[j] = K * x[j] - current - next;
+ if (n--) {
+ j += 2;
+ current = delta * x[j + 1];
+ x[j] = K * x[j] - current - next;
+ } else {
+ break;
+ }
+ }
+ // step 4
+ j = offset - 1;
+ current = gamma * x[j - 1];
+ for (n = len + 2; n--; j += 2) {
+ next = gamma * x[j + 1];
+ x[j] -= current + next;
+ if (n--) {
+ j += 2;
+ current = gamma * x[j + 1];
+ x[j] -= current + next;
+ } else {
+ break;
+ }
+ }
+ // step 5
+ j = offset;
+ current = beta * x[j - 1];
+ for (n = len + 1; n--; j += 2) {
+ next = beta * x[j + 1];
+ x[j] -= current + next;
+ if (n--) {
+ j += 2;
+ current = beta * x[j + 1];
+ x[j] -= current + next;
+ } else {
+ break;
+ }
+ }
+ // step 6
+ if (len !== 0) {
+ j = offset + 1;
+ current = alpha * x[j - 1];
+ for (n = len; n--; j += 2) {
+ next = alpha * x[j + 1];
+ x[j] -= current + next;
+ if (n--) {
+ j += 2;
+ current = alpha * x[j + 1];
+ x[j] -= current + next;
+ } else {
+ break;
+ }
+ }
+ }
+ };
+ return IrreversibleTransform;
+ }();
+ // Section 3.8.1 Reversible 5-3 filter
+ var ReversibleTransform = function ReversibleTransformClosure() {
+ function ReversibleTransform() {
+ Transform.call(this);
+ }
+ ReversibleTransform.prototype = Object.create(Transform.prototype);
+ ReversibleTransform.prototype.filter = function reversibleTransformFilter(x, offset, length) {
+ var len = length >> 1;
+ offset = offset | 0;
+ var j, n;
+ for (j = offset, n = len + 1; n--; j += 2) {
+ x[j] -= x[j - 1] + x[j + 1] + 2 >> 2;
+ }
+ for (j = offset + 1, n = len; n--; j += 2) {
+ x[j] += x[j - 1] + x[j + 1] >> 1;
+ }
+ };
+ return ReversibleTransform;
+ }();
+ return JpxImage;
+ }();
+ exports.JpxImage = JpxImage;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreMetrics = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var getLookupTableFactory = sharedUtil.getLookupTableFactory;
+ // The Metrics object contains glyph widths (in glyph space units).
+ // As per PDF spec, for most fonts (Type 3 being an exception) a glyph
+ // space unit corresponds to 1/1000th of text space unit.
+ var getMetrics = getLookupTableFactory(function (t) {
+ t['Courier'] = 600;
+ t['Courier-Bold'] = 600;
+ t['Courier-BoldOblique'] = 600;
+ t['Courier-Oblique'] = 600;
+ t['Helvetica'] = getLookupTableFactory(function (t) {
+ t['space'] = 278;
+ t['exclam'] = 278;
+ t['quotedbl'] = 355;
+ t['numbersign'] = 556;
+ t['dollar'] = 556;
+ t['percent'] = 889;
+ t['ampersand'] = 667;
+ t['quoteright'] = 222;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 389;
+ t['plus'] = 584;
+ t['comma'] = 278;
+ t['hyphen'] = 333;
+ t['period'] = 278;
+ t['slash'] = 278;
+ t['zero'] = 556;
+ t['one'] = 556;
+ t['two'] = 556;
+ t['three'] = 556;
+ t['four'] = 556;
+ t['five'] = 556;
+ t['six'] = 556;
+ t['seven'] = 556;
+ t['eight'] = 556;
+ t['nine'] = 556;
+ t['colon'] = 278;
+ t['semicolon'] = 278;
+ t['less'] = 584;
+ t['equal'] = 584;
+ t['greater'] = 584;
+ t['question'] = 556;
+ t['at'] = 1015;
+ t['A'] = 667;
+ t['B'] = 667;
+ t['C'] = 722;
+ t['D'] = 722;
+ t['E'] = 667;
+ t['F'] = 611;
+ t['G'] = 778;
+ t['H'] = 722;
+ t['I'] = 278;
+ t['J'] = 500;
+ t['K'] = 667;
+ t['L'] = 556;
+ t['M'] = 833;
+ t['N'] = 722;
+ t['O'] = 778;
+ t['P'] = 667;
+ t['Q'] = 778;
+ t['R'] = 722;
+ t['S'] = 667;
+ t['T'] = 611;
+ t['U'] = 722;
+ t['V'] = 667;
+ t['W'] = 944;
+ t['X'] = 667;
+ t['Y'] = 667;
+ t['Z'] = 611;
+ t['bracketleft'] = 278;
+ t['backslash'] = 278;
+ t['bracketright'] = 278;
+ t['asciicircum'] = 469;
+ t['underscore'] = 556;
+ t['quoteleft'] = 222;
+ t['a'] = 556;
+ t['b'] = 556;
+ t['c'] = 500;
+ t['d'] = 556;
+ t['e'] = 556;
+ t['f'] = 278;
+ t['g'] = 556;
+ t['h'] = 556;
+ t['i'] = 222;
+ t['j'] = 222;
+ t['k'] = 500;
+ t['l'] = 222;
+ t['m'] = 833;
+ t['n'] = 556;
+ t['o'] = 556;
+ t['p'] = 556;
+ t['q'] = 556;
+ t['r'] = 333;
+ t['s'] = 500;
+ t['t'] = 278;
+ t['u'] = 556;
+ t['v'] = 500;
+ t['w'] = 722;
+ t['x'] = 500;
+ t['y'] = 500;
+ t['z'] = 500;
+ t['braceleft'] = 334;
+ t['bar'] = 260;
+ t['braceright'] = 334;
+ t['asciitilde'] = 584;
+ t['exclamdown'] = 333;
+ t['cent'] = 556;
+ t['sterling'] = 556;
+ t['fraction'] = 167;
+ t['yen'] = 556;
+ t['florin'] = 556;
+ t['section'] = 556;
+ t['currency'] = 556;
+ t['quotesingle'] = 191;
+ t['quotedblleft'] = 333;
+ t['guillemotleft'] = 556;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 500;
+ t['fl'] = 500;
+ t['endash'] = 556;
+ t['dagger'] = 556;
+ t['daggerdbl'] = 556;
+ t['periodcentered'] = 278;
+ t['paragraph'] = 537;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 222;
+ t['quotedblbase'] = 333;
+ t['quotedblright'] = 333;
+ t['guillemotright'] = 556;
+ t['ellipsis'] = 1000;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 611;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 1000;
+ t['AE'] = 1000;
+ t['ordfeminine'] = 370;
+ t['Lslash'] = 556;
+ t['Oslash'] = 778;
+ t['OE'] = 1000;
+ t['ordmasculine'] = 365;
+ t['ae'] = 889;
+ t['dotlessi'] = 278;
+ t['lslash'] = 222;
+ t['oslash'] = 611;
+ t['oe'] = 944;
+ t['germandbls'] = 611;
+ t['Idieresis'] = 278;
+ t['eacute'] = 556;
+ t['abreve'] = 556;
+ t['uhungarumlaut'] = 556;
+ t['ecaron'] = 556;
+ t['Ydieresis'] = 667;
+ t['divide'] = 584;
+ t['Yacute'] = 667;
+ t['Acircumflex'] = 667;
+ t['aacute'] = 556;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 500;
+ t['scommaaccent'] = 500;
+ t['ecircumflex'] = 556;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 556;
+ t['Uacute'] = 722;
+ t['uogonek'] = 556;
+ t['Edieresis'] = 667;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 737;
+ t['Emacron'] = 667;
+ t['ccaron'] = 500;
+ t['aring'] = 556;
+ t['Ncommaaccent'] = 722;
+ t['lacute'] = 222;
+ t['agrave'] = 556;
+ t['Tcommaaccent'] = 611;
+ t['Cacute'] = 722;
+ t['atilde'] = 556;
+ t['Edotaccent'] = 667;
+ t['scaron'] = 500;
+ t['scedilla'] = 500;
+ t['iacute'] = 278;
+ t['lozenge'] = 471;
+ t['Rcaron'] = 722;
+ t['Gcommaaccent'] = 778;
+ t['ucircumflex'] = 556;
+ t['acircumflex'] = 556;
+ t['Amacron'] = 667;
+ t['rcaron'] = 333;
+ t['ccedilla'] = 500;
+ t['Zdotaccent'] = 611;
+ t['Thorn'] = 667;
+ t['Omacron'] = 778;
+ t['Racute'] = 722;
+ t['Sacute'] = 667;
+ t['dcaron'] = 643;
+ t['Umacron'] = 722;
+ t['uring'] = 556;
+ t['threesuperior'] = 333;
+ t['Ograve'] = 778;
+ t['Agrave'] = 667;
+ t['Abreve'] = 667;
+ t['multiply'] = 584;
+ t['uacute'] = 556;
+ t['Tcaron'] = 611;
+ t['partialdiff'] = 476;
+ t['ydieresis'] = 500;
+ t['Nacute'] = 722;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 667;
+ t['adieresis'] = 556;
+ t['edieresis'] = 556;
+ t['cacute'] = 500;
+ t['nacute'] = 556;
+ t['umacron'] = 556;
+ t['Ncaron'] = 722;
+ t['Iacute'] = 278;
+ t['plusminus'] = 584;
+ t['brokenbar'] = 260;
+ t['registered'] = 737;
+ t['Gbreve'] = 778;
+ t['Idotaccent'] = 278;
+ t['summation'] = 600;
+ t['Egrave'] = 667;
+ t['racute'] = 333;
+ t['omacron'] = 556;
+ t['Zacute'] = 611;
+ t['Zcaron'] = 611;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 722;
+ t['lcommaaccent'] = 222;
+ t['tcaron'] = 317;
+ t['eogonek'] = 556;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 667;
+ t['Adieresis'] = 667;
+ t['egrave'] = 556;
+ t['zacute'] = 500;
+ t['iogonek'] = 222;
+ t['Oacute'] = 778;
+ t['oacute'] = 556;
+ t['amacron'] = 556;
+ t['sacute'] = 500;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 778;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 556;
+ t['twosuperior'] = 333;
+ t['Odieresis'] = 778;
+ t['mu'] = 556;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 556;
+ t['Eogonek'] = 667;
+ t['dcroat'] = 556;
+ t['threequarters'] = 834;
+ t['Scedilla'] = 667;
+ t['lcaron'] = 299;
+ t['Kcommaaccent'] = 667;
+ t['Lacute'] = 556;
+ t['trademark'] = 1000;
+ t['edotaccent'] = 556;
+ t['Igrave'] = 278;
+ t['Imacron'] = 278;
+ t['Lcaron'] = 556;
+ t['onehalf'] = 834;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 556;
+ t['ntilde'] = 556;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 667;
+ t['emacron'] = 556;
+ t['gbreve'] = 556;
+ t['onequarter'] = 834;
+ t['Scaron'] = 667;
+ t['Scommaaccent'] = 667;
+ t['Ohungarumlaut'] = 778;
+ t['degree'] = 400;
+ t['ograve'] = 556;
+ t['Ccaron'] = 722;
+ t['ugrave'] = 556;
+ t['radical'] = 453;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 333;
+ t['Ntilde'] = 722;
+ t['otilde'] = 556;
+ t['Rcommaaccent'] = 722;
+ t['Lcommaaccent'] = 556;
+ t['Atilde'] = 667;
+ t['Aogonek'] = 667;
+ t['Aring'] = 667;
+ t['Otilde'] = 778;
+ t['zdotaccent'] = 500;
+ t['Ecaron'] = 667;
+ t['Iogonek'] = 278;
+ t['kcommaaccent'] = 500;
+ t['minus'] = 584;
+ t['Icircumflex'] = 278;
+ t['ncaron'] = 556;
+ t['tcommaaccent'] = 278;
+ t['logicalnot'] = 584;
+ t['odieresis'] = 556;
+ t['udieresis'] = 556;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 556;
+ t['eth'] = 556;
+ t['zcaron'] = 500;
+ t['ncommaaccent'] = 556;
+ t['onesuperior'] = 333;
+ t['imacron'] = 278;
+ t['Euro'] = 556;
+ });
+ t['Helvetica-Bold'] = getLookupTableFactory(function (t) {
+ t['space'] = 278;
+ t['exclam'] = 333;
+ t['quotedbl'] = 474;
+ t['numbersign'] = 556;
+ t['dollar'] = 556;
+ t['percent'] = 889;
+ t['ampersand'] = 722;
+ t['quoteright'] = 278;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 389;
+ t['plus'] = 584;
+ t['comma'] = 278;
+ t['hyphen'] = 333;
+ t['period'] = 278;
+ t['slash'] = 278;
+ t['zero'] = 556;
+ t['one'] = 556;
+ t['two'] = 556;
+ t['three'] = 556;
+ t['four'] = 556;
+ t['five'] = 556;
+ t['six'] = 556;
+ t['seven'] = 556;
+ t['eight'] = 556;
+ t['nine'] = 556;
+ t['colon'] = 333;
+ t['semicolon'] = 333;
+ t['less'] = 584;
+ t['equal'] = 584;
+ t['greater'] = 584;
+ t['question'] = 611;
+ t['at'] = 975;
+ t['A'] = 722;
+ t['B'] = 722;
+ t['C'] = 722;
+ t['D'] = 722;
+ t['E'] = 667;
+ t['F'] = 611;
+ t['G'] = 778;
+ t['H'] = 722;
+ t['I'] = 278;
+ t['J'] = 556;
+ t['K'] = 722;
+ t['L'] = 611;
+ t['M'] = 833;
+ t['N'] = 722;
+ t['O'] = 778;
+ t['P'] = 667;
+ t['Q'] = 778;
+ t['R'] = 722;
+ t['S'] = 667;
+ t['T'] = 611;
+ t['U'] = 722;
+ t['V'] = 667;
+ t['W'] = 944;
+ t['X'] = 667;
+ t['Y'] = 667;
+ t['Z'] = 611;
+ t['bracketleft'] = 333;
+ t['backslash'] = 278;
+ t['bracketright'] = 333;
+ t['asciicircum'] = 584;
+ t['underscore'] = 556;
+ t['quoteleft'] = 278;
+ t['a'] = 556;
+ t['b'] = 611;
+ t['c'] = 556;
+ t['d'] = 611;
+ t['e'] = 556;
+ t['f'] = 333;
+ t['g'] = 611;
+ t['h'] = 611;
+ t['i'] = 278;
+ t['j'] = 278;
+ t['k'] = 556;
+ t['l'] = 278;
+ t['m'] = 889;
+ t['n'] = 611;
+ t['o'] = 611;
+ t['p'] = 611;
+ t['q'] = 611;
+ t['r'] = 389;
+ t['s'] = 556;
+ t['t'] = 333;
+ t['u'] = 611;
+ t['v'] = 556;
+ t['w'] = 778;
+ t['x'] = 556;
+ t['y'] = 556;
+ t['z'] = 500;
+ t['braceleft'] = 389;
+ t['bar'] = 280;
+ t['braceright'] = 389;
+ t['asciitilde'] = 584;
+ t['exclamdown'] = 333;
+ t['cent'] = 556;
+ t['sterling'] = 556;
+ t['fraction'] = 167;
+ t['yen'] = 556;
+ t['florin'] = 556;
+ t['section'] = 556;
+ t['currency'] = 556;
+ t['quotesingle'] = 238;
+ t['quotedblleft'] = 500;
+ t['guillemotleft'] = 556;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 611;
+ t['fl'] = 611;
+ t['endash'] = 556;
+ t['dagger'] = 556;
+ t['daggerdbl'] = 556;
+ t['periodcentered'] = 278;
+ t['paragraph'] = 556;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 278;
+ t['quotedblbase'] = 500;
+ t['quotedblright'] = 500;
+ t['guillemotright'] = 556;
+ t['ellipsis'] = 1000;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 611;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 1000;
+ t['AE'] = 1000;
+ t['ordfeminine'] = 370;
+ t['Lslash'] = 611;
+ t['Oslash'] = 778;
+ t['OE'] = 1000;
+ t['ordmasculine'] = 365;
+ t['ae'] = 889;
+ t['dotlessi'] = 278;
+ t['lslash'] = 278;
+ t['oslash'] = 611;
+ t['oe'] = 944;
+ t['germandbls'] = 611;
+ t['Idieresis'] = 278;
+ t['eacute'] = 556;
+ t['abreve'] = 556;
+ t['uhungarumlaut'] = 611;
+ t['ecaron'] = 556;
+ t['Ydieresis'] = 667;
+ t['divide'] = 584;
+ t['Yacute'] = 667;
+ t['Acircumflex'] = 722;
+ t['aacute'] = 556;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 556;
+ t['scommaaccent'] = 556;
+ t['ecircumflex'] = 556;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 556;
+ t['Uacute'] = 722;
+ t['uogonek'] = 611;
+ t['Edieresis'] = 667;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 737;
+ t['Emacron'] = 667;
+ t['ccaron'] = 556;
+ t['aring'] = 556;
+ t['Ncommaaccent'] = 722;
+ t['lacute'] = 278;
+ t['agrave'] = 556;
+ t['Tcommaaccent'] = 611;
+ t['Cacute'] = 722;
+ t['atilde'] = 556;
+ t['Edotaccent'] = 667;
+ t['scaron'] = 556;
+ t['scedilla'] = 556;
+ t['iacute'] = 278;
+ t['lozenge'] = 494;
+ t['Rcaron'] = 722;
+ t['Gcommaaccent'] = 778;
+ t['ucircumflex'] = 611;
+ t['acircumflex'] = 556;
+ t['Amacron'] = 722;
+ t['rcaron'] = 389;
+ t['ccedilla'] = 556;
+ t['Zdotaccent'] = 611;
+ t['Thorn'] = 667;
+ t['Omacron'] = 778;
+ t['Racute'] = 722;
+ t['Sacute'] = 667;
+ t['dcaron'] = 743;
+ t['Umacron'] = 722;
+ t['uring'] = 611;
+ t['threesuperior'] = 333;
+ t['Ograve'] = 778;
+ t['Agrave'] = 722;
+ t['Abreve'] = 722;
+ t['multiply'] = 584;
+ t['uacute'] = 611;
+ t['Tcaron'] = 611;
+ t['partialdiff'] = 494;
+ t['ydieresis'] = 556;
+ t['Nacute'] = 722;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 667;
+ t['adieresis'] = 556;
+ t['edieresis'] = 556;
+ t['cacute'] = 556;
+ t['nacute'] = 611;
+ t['umacron'] = 611;
+ t['Ncaron'] = 722;
+ t['Iacute'] = 278;
+ t['plusminus'] = 584;
+ t['brokenbar'] = 280;
+ t['registered'] = 737;
+ t['Gbreve'] = 778;
+ t['Idotaccent'] = 278;
+ t['summation'] = 600;
+ t['Egrave'] = 667;
+ t['racute'] = 389;
+ t['omacron'] = 611;
+ t['Zacute'] = 611;
+ t['Zcaron'] = 611;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 722;
+ t['lcommaaccent'] = 278;
+ t['tcaron'] = 389;
+ t['eogonek'] = 556;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 722;
+ t['Adieresis'] = 722;
+ t['egrave'] = 556;
+ t['zacute'] = 500;
+ t['iogonek'] = 278;
+ t['Oacute'] = 778;
+ t['oacute'] = 611;
+ t['amacron'] = 556;
+ t['sacute'] = 556;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 778;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 611;
+ t['twosuperior'] = 333;
+ t['Odieresis'] = 778;
+ t['mu'] = 611;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 611;
+ t['Eogonek'] = 667;
+ t['dcroat'] = 611;
+ t['threequarters'] = 834;
+ t['Scedilla'] = 667;
+ t['lcaron'] = 400;
+ t['Kcommaaccent'] = 722;
+ t['Lacute'] = 611;
+ t['trademark'] = 1000;
+ t['edotaccent'] = 556;
+ t['Igrave'] = 278;
+ t['Imacron'] = 278;
+ t['Lcaron'] = 611;
+ t['onehalf'] = 834;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 611;
+ t['ntilde'] = 611;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 667;
+ t['emacron'] = 556;
+ t['gbreve'] = 611;
+ t['onequarter'] = 834;
+ t['Scaron'] = 667;
+ t['Scommaaccent'] = 667;
+ t['Ohungarumlaut'] = 778;
+ t['degree'] = 400;
+ t['ograve'] = 611;
+ t['Ccaron'] = 722;
+ t['ugrave'] = 611;
+ t['radical'] = 549;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 389;
+ t['Ntilde'] = 722;
+ t['otilde'] = 611;
+ t['Rcommaaccent'] = 722;
+ t['Lcommaaccent'] = 611;
+ t['Atilde'] = 722;
+ t['Aogonek'] = 722;
+ t['Aring'] = 722;
+ t['Otilde'] = 778;
+ t['zdotaccent'] = 500;
+ t['Ecaron'] = 667;
+ t['Iogonek'] = 278;
+ t['kcommaaccent'] = 556;
+ t['minus'] = 584;
+ t['Icircumflex'] = 278;
+ t['ncaron'] = 611;
+ t['tcommaaccent'] = 333;
+ t['logicalnot'] = 584;
+ t['odieresis'] = 611;
+ t['udieresis'] = 611;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 611;
+ t['eth'] = 611;
+ t['zcaron'] = 500;
+ t['ncommaaccent'] = 611;
+ t['onesuperior'] = 333;
+ t['imacron'] = 278;
+ t['Euro'] = 556;
+ });
+ t['Helvetica-BoldOblique'] = getLookupTableFactory(function (t) {
+ t['space'] = 278;
+ t['exclam'] = 333;
+ t['quotedbl'] = 474;
+ t['numbersign'] = 556;
+ t['dollar'] = 556;
+ t['percent'] = 889;
+ t['ampersand'] = 722;
+ t['quoteright'] = 278;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 389;
+ t['plus'] = 584;
+ t['comma'] = 278;
+ t['hyphen'] = 333;
+ t['period'] = 278;
+ t['slash'] = 278;
+ t['zero'] = 556;
+ t['one'] = 556;
+ t['two'] = 556;
+ t['three'] = 556;
+ t['four'] = 556;
+ t['five'] = 556;
+ t['six'] = 556;
+ t['seven'] = 556;
+ t['eight'] = 556;
+ t['nine'] = 556;
+ t['colon'] = 333;
+ t['semicolon'] = 333;
+ t['less'] = 584;
+ t['equal'] = 584;
+ t['greater'] = 584;
+ t['question'] = 611;
+ t['at'] = 975;
+ t['A'] = 722;
+ t['B'] = 722;
+ t['C'] = 722;
+ t['D'] = 722;
+ t['E'] = 667;
+ t['F'] = 611;
+ t['G'] = 778;
+ t['H'] = 722;
+ t['I'] = 278;
+ t['J'] = 556;
+ t['K'] = 722;
+ t['L'] = 611;
+ t['M'] = 833;
+ t['N'] = 722;
+ t['O'] = 778;
+ t['P'] = 667;
+ t['Q'] = 778;
+ t['R'] = 722;
+ t['S'] = 667;
+ t['T'] = 611;
+ t['U'] = 722;
+ t['V'] = 667;
+ t['W'] = 944;
+ t['X'] = 667;
+ t['Y'] = 667;
+ t['Z'] = 611;
+ t['bracketleft'] = 333;
+ t['backslash'] = 278;
+ t['bracketright'] = 333;
+ t['asciicircum'] = 584;
+ t['underscore'] = 556;
+ t['quoteleft'] = 278;
+ t['a'] = 556;
+ t['b'] = 611;
+ t['c'] = 556;
+ t['d'] = 611;
+ t['e'] = 556;
+ t['f'] = 333;
+ t['g'] = 611;
+ t['h'] = 611;
+ t['i'] = 278;
+ t['j'] = 278;
+ t['k'] = 556;
+ t['l'] = 278;
+ t['m'] = 889;
+ t['n'] = 611;
+ t['o'] = 611;
+ t['p'] = 611;
+ t['q'] = 611;
+ t['r'] = 389;
+ t['s'] = 556;
+ t['t'] = 333;
+ t['u'] = 611;
+ t['v'] = 556;
+ t['w'] = 778;
+ t['x'] = 556;
+ t['y'] = 556;
+ t['z'] = 500;
+ t['braceleft'] = 389;
+ t['bar'] = 280;
+ t['braceright'] = 389;
+ t['asciitilde'] = 584;
+ t['exclamdown'] = 333;
+ t['cent'] = 556;
+ t['sterling'] = 556;
+ t['fraction'] = 167;
+ t['yen'] = 556;
+ t['florin'] = 556;
+ t['section'] = 556;
+ t['currency'] = 556;
+ t['quotesingle'] = 238;
+ t['quotedblleft'] = 500;
+ t['guillemotleft'] = 556;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 611;
+ t['fl'] = 611;
+ t['endash'] = 556;
+ t['dagger'] = 556;
+ t['daggerdbl'] = 556;
+ t['periodcentered'] = 278;
+ t['paragraph'] = 556;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 278;
+ t['quotedblbase'] = 500;
+ t['quotedblright'] = 500;
+ t['guillemotright'] = 556;
+ t['ellipsis'] = 1000;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 611;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 1000;
+ t['AE'] = 1000;
+ t['ordfeminine'] = 370;
+ t['Lslash'] = 611;
+ t['Oslash'] = 778;
+ t['OE'] = 1000;
+ t['ordmasculine'] = 365;
+ t['ae'] = 889;
+ t['dotlessi'] = 278;
+ t['lslash'] = 278;
+ t['oslash'] = 611;
+ t['oe'] = 944;
+ t['germandbls'] = 611;
+ t['Idieresis'] = 278;
+ t['eacute'] = 556;
+ t['abreve'] = 556;
+ t['uhungarumlaut'] = 611;
+ t['ecaron'] = 556;
+ t['Ydieresis'] = 667;
+ t['divide'] = 584;
+ t['Yacute'] = 667;
+ t['Acircumflex'] = 722;
+ t['aacute'] = 556;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 556;
+ t['scommaaccent'] = 556;
+ t['ecircumflex'] = 556;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 556;
+ t['Uacute'] = 722;
+ t['uogonek'] = 611;
+ t['Edieresis'] = 667;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 737;
+ t['Emacron'] = 667;
+ t['ccaron'] = 556;
+ t['aring'] = 556;
+ t['Ncommaaccent'] = 722;
+ t['lacute'] = 278;
+ t['agrave'] = 556;
+ t['Tcommaaccent'] = 611;
+ t['Cacute'] = 722;
+ t['atilde'] = 556;
+ t['Edotaccent'] = 667;
+ t['scaron'] = 556;
+ t['scedilla'] = 556;
+ t['iacute'] = 278;
+ t['lozenge'] = 494;
+ t['Rcaron'] = 722;
+ t['Gcommaaccent'] = 778;
+ t['ucircumflex'] = 611;
+ t['acircumflex'] = 556;
+ t['Amacron'] = 722;
+ t['rcaron'] = 389;
+ t['ccedilla'] = 556;
+ t['Zdotaccent'] = 611;
+ t['Thorn'] = 667;
+ t['Omacron'] = 778;
+ t['Racute'] = 722;
+ t['Sacute'] = 667;
+ t['dcaron'] = 743;
+ t['Umacron'] = 722;
+ t['uring'] = 611;
+ t['threesuperior'] = 333;
+ t['Ograve'] = 778;
+ t['Agrave'] = 722;
+ t['Abreve'] = 722;
+ t['multiply'] = 584;
+ t['uacute'] = 611;
+ t['Tcaron'] = 611;
+ t['partialdiff'] = 494;
+ t['ydieresis'] = 556;
+ t['Nacute'] = 722;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 667;
+ t['adieresis'] = 556;
+ t['edieresis'] = 556;
+ t['cacute'] = 556;
+ t['nacute'] = 611;
+ t['umacron'] = 611;
+ t['Ncaron'] = 722;
+ t['Iacute'] = 278;
+ t['plusminus'] = 584;
+ t['brokenbar'] = 280;
+ t['registered'] = 737;
+ t['Gbreve'] = 778;
+ t['Idotaccent'] = 278;
+ t['summation'] = 600;
+ t['Egrave'] = 667;
+ t['racute'] = 389;
+ t['omacron'] = 611;
+ t['Zacute'] = 611;
+ t['Zcaron'] = 611;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 722;
+ t['lcommaaccent'] = 278;
+ t['tcaron'] = 389;
+ t['eogonek'] = 556;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 722;
+ t['Adieresis'] = 722;
+ t['egrave'] = 556;
+ t['zacute'] = 500;
+ t['iogonek'] = 278;
+ t['Oacute'] = 778;
+ t['oacute'] = 611;
+ t['amacron'] = 556;
+ t['sacute'] = 556;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 778;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 611;
+ t['twosuperior'] = 333;
+ t['Odieresis'] = 778;
+ t['mu'] = 611;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 611;
+ t['Eogonek'] = 667;
+ t['dcroat'] = 611;
+ t['threequarters'] = 834;
+ t['Scedilla'] = 667;
+ t['lcaron'] = 400;
+ t['Kcommaaccent'] = 722;
+ t['Lacute'] = 611;
+ t['trademark'] = 1000;
+ t['edotaccent'] = 556;
+ t['Igrave'] = 278;
+ t['Imacron'] = 278;
+ t['Lcaron'] = 611;
+ t['onehalf'] = 834;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 611;
+ t['ntilde'] = 611;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 667;
+ t['emacron'] = 556;
+ t['gbreve'] = 611;
+ t['onequarter'] = 834;
+ t['Scaron'] = 667;
+ t['Scommaaccent'] = 667;
+ t['Ohungarumlaut'] = 778;
+ t['degree'] = 400;
+ t['ograve'] = 611;
+ t['Ccaron'] = 722;
+ t['ugrave'] = 611;
+ t['radical'] = 549;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 389;
+ t['Ntilde'] = 722;
+ t['otilde'] = 611;
+ t['Rcommaaccent'] = 722;
+ t['Lcommaaccent'] = 611;
+ t['Atilde'] = 722;
+ t['Aogonek'] = 722;
+ t['Aring'] = 722;
+ t['Otilde'] = 778;
+ t['zdotaccent'] = 500;
+ t['Ecaron'] = 667;
+ t['Iogonek'] = 278;
+ t['kcommaaccent'] = 556;
+ t['minus'] = 584;
+ t['Icircumflex'] = 278;
+ t['ncaron'] = 611;
+ t['tcommaaccent'] = 333;
+ t['logicalnot'] = 584;
+ t['odieresis'] = 611;
+ t['udieresis'] = 611;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 611;
+ t['eth'] = 611;
+ t['zcaron'] = 500;
+ t['ncommaaccent'] = 611;
+ t['onesuperior'] = 333;
+ t['imacron'] = 278;
+ t['Euro'] = 556;
+ });
+ t['Helvetica-Oblique'] = getLookupTableFactory(function (t) {
+ t['space'] = 278;
+ t['exclam'] = 278;
+ t['quotedbl'] = 355;
+ t['numbersign'] = 556;
+ t['dollar'] = 556;
+ t['percent'] = 889;
+ t['ampersand'] = 667;
+ t['quoteright'] = 222;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 389;
+ t['plus'] = 584;
+ t['comma'] = 278;
+ t['hyphen'] = 333;
+ t['period'] = 278;
+ t['slash'] = 278;
+ t['zero'] = 556;
+ t['one'] = 556;
+ t['two'] = 556;
+ t['three'] = 556;
+ t['four'] = 556;
+ t['five'] = 556;
+ t['six'] = 556;
+ t['seven'] = 556;
+ t['eight'] = 556;
+ t['nine'] = 556;
+ t['colon'] = 278;
+ t['semicolon'] = 278;
+ t['less'] = 584;
+ t['equal'] = 584;
+ t['greater'] = 584;
+ t['question'] = 556;
+ t['at'] = 1015;
+ t['A'] = 667;
+ t['B'] = 667;
+ t['C'] = 722;
+ t['D'] = 722;
+ t['E'] = 667;
+ t['F'] = 611;
+ t['G'] = 778;
+ t['H'] = 722;
+ t['I'] = 278;
+ t['J'] = 500;
+ t['K'] = 667;
+ t['L'] = 556;
+ t['M'] = 833;
+ t['N'] = 722;
+ t['O'] = 778;
+ t['P'] = 667;
+ t['Q'] = 778;
+ t['R'] = 722;
+ t['S'] = 667;
+ t['T'] = 611;
+ t['U'] = 722;
+ t['V'] = 667;
+ t['W'] = 944;
+ t['X'] = 667;
+ t['Y'] = 667;
+ t['Z'] = 611;
+ t['bracketleft'] = 278;
+ t['backslash'] = 278;
+ t['bracketright'] = 278;
+ t['asciicircum'] = 469;
+ t['underscore'] = 556;
+ t['quoteleft'] = 222;
+ t['a'] = 556;
+ t['b'] = 556;
+ t['c'] = 500;
+ t['d'] = 556;
+ t['e'] = 556;
+ t['f'] = 278;
+ t['g'] = 556;
+ t['h'] = 556;
+ t['i'] = 222;
+ t['j'] = 222;
+ t['k'] = 500;
+ t['l'] = 222;
+ t['m'] = 833;
+ t['n'] = 556;
+ t['o'] = 556;
+ t['p'] = 556;
+ t['q'] = 556;
+ t['r'] = 333;
+ t['s'] = 500;
+ t['t'] = 278;
+ t['u'] = 556;
+ t['v'] = 500;
+ t['w'] = 722;
+ t['x'] = 500;
+ t['y'] = 500;
+ t['z'] = 500;
+ t['braceleft'] = 334;
+ t['bar'] = 260;
+ t['braceright'] = 334;
+ t['asciitilde'] = 584;
+ t['exclamdown'] = 333;
+ t['cent'] = 556;
+ t['sterling'] = 556;
+ t['fraction'] = 167;
+ t['yen'] = 556;
+ t['florin'] = 556;
+ t['section'] = 556;
+ t['currency'] = 556;
+ t['quotesingle'] = 191;
+ t['quotedblleft'] = 333;
+ t['guillemotleft'] = 556;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 500;
+ t['fl'] = 500;
+ t['endash'] = 556;
+ t['dagger'] = 556;
+ t['daggerdbl'] = 556;
+ t['periodcentered'] = 278;
+ t['paragraph'] = 537;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 222;
+ t['quotedblbase'] = 333;
+ t['quotedblright'] = 333;
+ t['guillemotright'] = 556;
+ t['ellipsis'] = 1000;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 611;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 1000;
+ t['AE'] = 1000;
+ t['ordfeminine'] = 370;
+ t['Lslash'] = 556;
+ t['Oslash'] = 778;
+ t['OE'] = 1000;
+ t['ordmasculine'] = 365;
+ t['ae'] = 889;
+ t['dotlessi'] = 278;
+ t['lslash'] = 222;
+ t['oslash'] = 611;
+ t['oe'] = 944;
+ t['germandbls'] = 611;
+ t['Idieresis'] = 278;
+ t['eacute'] = 556;
+ t['abreve'] = 556;
+ t['uhungarumlaut'] = 556;
+ t['ecaron'] = 556;
+ t['Ydieresis'] = 667;
+ t['divide'] = 584;
+ t['Yacute'] = 667;
+ t['Acircumflex'] = 667;
+ t['aacute'] = 556;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 500;
+ t['scommaaccent'] = 500;
+ t['ecircumflex'] = 556;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 556;
+ t['Uacute'] = 722;
+ t['uogonek'] = 556;
+ t['Edieresis'] = 667;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 737;
+ t['Emacron'] = 667;
+ t['ccaron'] = 500;
+ t['aring'] = 556;
+ t['Ncommaaccent'] = 722;
+ t['lacute'] = 222;
+ t['agrave'] = 556;
+ t['Tcommaaccent'] = 611;
+ t['Cacute'] = 722;
+ t['atilde'] = 556;
+ t['Edotaccent'] = 667;
+ t['scaron'] = 500;
+ t['scedilla'] = 500;
+ t['iacute'] = 278;
+ t['lozenge'] = 471;
+ t['Rcaron'] = 722;
+ t['Gcommaaccent'] = 778;
+ t['ucircumflex'] = 556;
+ t['acircumflex'] = 556;
+ t['Amacron'] = 667;
+ t['rcaron'] = 333;
+ t['ccedilla'] = 500;
+ t['Zdotaccent'] = 611;
+ t['Thorn'] = 667;
+ t['Omacron'] = 778;
+ t['Racute'] = 722;
+ t['Sacute'] = 667;
+ t['dcaron'] = 643;
+ t['Umacron'] = 722;
+ t['uring'] = 556;
+ t['threesuperior'] = 333;
+ t['Ograve'] = 778;
+ t['Agrave'] = 667;
+ t['Abreve'] = 667;
+ t['multiply'] = 584;
+ t['uacute'] = 556;
+ t['Tcaron'] = 611;
+ t['partialdiff'] = 476;
+ t['ydieresis'] = 500;
+ t['Nacute'] = 722;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 667;
+ t['adieresis'] = 556;
+ t['edieresis'] = 556;
+ t['cacute'] = 500;
+ t['nacute'] = 556;
+ t['umacron'] = 556;
+ t['Ncaron'] = 722;
+ t['Iacute'] = 278;
+ t['plusminus'] = 584;
+ t['brokenbar'] = 260;
+ t['registered'] = 737;
+ t['Gbreve'] = 778;
+ t['Idotaccent'] = 278;
+ t['summation'] = 600;
+ t['Egrave'] = 667;
+ t['racute'] = 333;
+ t['omacron'] = 556;
+ t['Zacute'] = 611;
+ t['Zcaron'] = 611;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 722;
+ t['lcommaaccent'] = 222;
+ t['tcaron'] = 317;
+ t['eogonek'] = 556;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 667;
+ t['Adieresis'] = 667;
+ t['egrave'] = 556;
+ t['zacute'] = 500;
+ t['iogonek'] = 222;
+ t['Oacute'] = 778;
+ t['oacute'] = 556;
+ t['amacron'] = 556;
+ t['sacute'] = 500;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 778;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 556;
+ t['twosuperior'] = 333;
+ t['Odieresis'] = 778;
+ t['mu'] = 556;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 556;
+ t['Eogonek'] = 667;
+ t['dcroat'] = 556;
+ t['threequarters'] = 834;
+ t['Scedilla'] = 667;
+ t['lcaron'] = 299;
+ t['Kcommaaccent'] = 667;
+ t['Lacute'] = 556;
+ t['trademark'] = 1000;
+ t['edotaccent'] = 556;
+ t['Igrave'] = 278;
+ t['Imacron'] = 278;
+ t['Lcaron'] = 556;
+ t['onehalf'] = 834;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 556;
+ t['ntilde'] = 556;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 667;
+ t['emacron'] = 556;
+ t['gbreve'] = 556;
+ t['onequarter'] = 834;
+ t['Scaron'] = 667;
+ t['Scommaaccent'] = 667;
+ t['Ohungarumlaut'] = 778;
+ t['degree'] = 400;
+ t['ograve'] = 556;
+ t['Ccaron'] = 722;
+ t['ugrave'] = 556;
+ t['radical'] = 453;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 333;
+ t['Ntilde'] = 722;
+ t['otilde'] = 556;
+ t['Rcommaaccent'] = 722;
+ t['Lcommaaccent'] = 556;
+ t['Atilde'] = 667;
+ t['Aogonek'] = 667;
+ t['Aring'] = 667;
+ t['Otilde'] = 778;
+ t['zdotaccent'] = 500;
+ t['Ecaron'] = 667;
+ t['Iogonek'] = 278;
+ t['kcommaaccent'] = 500;
+ t['minus'] = 584;
+ t['Icircumflex'] = 278;
+ t['ncaron'] = 556;
+ t['tcommaaccent'] = 278;
+ t['logicalnot'] = 584;
+ t['odieresis'] = 556;
+ t['udieresis'] = 556;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 556;
+ t['eth'] = 556;
+ t['zcaron'] = 500;
+ t['ncommaaccent'] = 556;
+ t['onesuperior'] = 333;
+ t['imacron'] = 278;
+ t['Euro'] = 556;
+ });
+ t['Symbol'] = getLookupTableFactory(function (t) {
+ t['space'] = 250;
+ t['exclam'] = 333;
+ t['universal'] = 713;
+ t['numbersign'] = 500;
+ t['existential'] = 549;
+ t['percent'] = 833;
+ t['ampersand'] = 778;
+ t['suchthat'] = 439;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asteriskmath'] = 500;
+ t['plus'] = 549;
+ t['comma'] = 250;
+ t['minus'] = 549;
+ t['period'] = 250;
+ t['slash'] = 278;
+ t['zero'] = 500;
+ t['one'] = 500;
+ t['two'] = 500;
+ t['three'] = 500;
+ t['four'] = 500;
+ t['five'] = 500;
+ t['six'] = 500;
+ t['seven'] = 500;
+ t['eight'] = 500;
+ t['nine'] = 500;
+ t['colon'] = 278;
+ t['semicolon'] = 278;
+ t['less'] = 549;
+ t['equal'] = 549;
+ t['greater'] = 549;
+ t['question'] = 444;
+ t['congruent'] = 549;
+ t['Alpha'] = 722;
+ t['Beta'] = 667;
+ t['Chi'] = 722;
+ t['Delta'] = 612;
+ t['Epsilon'] = 611;
+ t['Phi'] = 763;
+ t['Gamma'] = 603;
+ t['Eta'] = 722;
+ t['Iota'] = 333;
+ t['theta1'] = 631;
+ t['Kappa'] = 722;
+ t['Lambda'] = 686;
+ t['Mu'] = 889;
+ t['Nu'] = 722;
+ t['Omicron'] = 722;
+ t['Pi'] = 768;
+ t['Theta'] = 741;
+ t['Rho'] = 556;
+ t['Sigma'] = 592;
+ t['Tau'] = 611;
+ t['Upsilon'] = 690;
+ t['sigma1'] = 439;
+ t['Omega'] = 768;
+ t['Xi'] = 645;
+ t['Psi'] = 795;
+ t['Zeta'] = 611;
+ t['bracketleft'] = 333;
+ t['therefore'] = 863;
+ t['bracketright'] = 333;
+ t['perpendicular'] = 658;
+ t['underscore'] = 500;
+ t['radicalex'] = 500;
+ t['alpha'] = 631;
+ t['beta'] = 549;
+ t['chi'] = 549;
+ t['delta'] = 494;
+ t['epsilon'] = 439;
+ t['phi'] = 521;
+ t['gamma'] = 411;
+ t['eta'] = 603;
+ t['iota'] = 329;
+ t['phi1'] = 603;
+ t['kappa'] = 549;
+ t['lambda'] = 549;
+ t['mu'] = 576;
+ t['nu'] = 521;
+ t['omicron'] = 549;
+ t['pi'] = 549;
+ t['theta'] = 521;
+ t['rho'] = 549;
+ t['sigma'] = 603;
+ t['tau'] = 439;
+ t['upsilon'] = 576;
+ t['omega1'] = 713;
+ t['omega'] = 686;
+ t['xi'] = 493;
+ t['psi'] = 686;
+ t['zeta'] = 494;
+ t['braceleft'] = 480;
+ t['bar'] = 200;
+ t['braceright'] = 480;
+ t['similar'] = 549;
+ t['Euro'] = 750;
+ t['Upsilon1'] = 620;
+ t['minute'] = 247;
+ t['lessequal'] = 549;
+ t['fraction'] = 167;
+ t['infinity'] = 713;
+ t['florin'] = 500;
+ t['club'] = 753;
+ t['diamond'] = 753;
+ t['heart'] = 753;
+ t['spade'] = 753;
+ t['arrowboth'] = 1042;
+ t['arrowleft'] = 987;
+ t['arrowup'] = 603;
+ t['arrowright'] = 987;
+ t['arrowdown'] = 603;
+ t['degree'] = 400;
+ t['plusminus'] = 549;
+ t['second'] = 411;
+ t['greaterequal'] = 549;
+ t['multiply'] = 549;
+ t['proportional'] = 713;
+ t['partialdiff'] = 494;
+ t['bullet'] = 460;
+ t['divide'] = 549;
+ t['notequal'] = 549;
+ t['equivalence'] = 549;
+ t['approxequal'] = 549;
+ t['ellipsis'] = 1000;
+ t['arrowvertex'] = 603;
+ t['arrowhorizex'] = 1000;
+ t['carriagereturn'] = 658;
+ t['aleph'] = 823;
+ t['Ifraktur'] = 686;
+ t['Rfraktur'] = 795;
+ t['weierstrass'] = 987;
+ t['circlemultiply'] = 768;
+ t['circleplus'] = 768;
+ t['emptyset'] = 823;
+ t['intersection'] = 768;
+ t['union'] = 768;
+ t['propersuperset'] = 713;
+ t['reflexsuperset'] = 713;
+ t['notsubset'] = 713;
+ t['propersubset'] = 713;
+ t['reflexsubset'] = 713;
+ t['element'] = 713;
+ t['notelement'] = 713;
+ t['angle'] = 768;
+ t['gradient'] = 713;
+ t['registerserif'] = 790;
+ t['copyrightserif'] = 790;
+ t['trademarkserif'] = 890;
+ t['product'] = 823;
+ t['radical'] = 549;
+ t['dotmath'] = 250;
+ t['logicalnot'] = 713;
+ t['logicaland'] = 603;
+ t['logicalor'] = 603;
+ t['arrowdblboth'] = 1042;
+ t['arrowdblleft'] = 987;
+ t['arrowdblup'] = 603;
+ t['arrowdblright'] = 987;
+ t['arrowdbldown'] = 603;
+ t['lozenge'] = 494;
+ t['angleleft'] = 329;
+ t['registersans'] = 790;
+ t['copyrightsans'] = 790;
+ t['trademarksans'] = 786;
+ t['summation'] = 713;
+ t['parenlefttp'] = 384;
+ t['parenleftex'] = 384;
+ t['parenleftbt'] = 384;
+ t['bracketlefttp'] = 384;
+ t['bracketleftex'] = 384;
+ t['bracketleftbt'] = 384;
+ t['bracelefttp'] = 494;
+ t['braceleftmid'] = 494;
+ t['braceleftbt'] = 494;
+ t['braceex'] = 494;
+ t['angleright'] = 329;
+ t['integral'] = 274;
+ t['integraltp'] = 686;
+ t['integralex'] = 686;
+ t['integralbt'] = 686;
+ t['parenrighttp'] = 384;
+ t['parenrightex'] = 384;
+ t['parenrightbt'] = 384;
+ t['bracketrighttp'] = 384;
+ t['bracketrightex'] = 384;
+ t['bracketrightbt'] = 384;
+ t['bracerighttp'] = 494;
+ t['bracerightmid'] = 494;
+ t['bracerightbt'] = 494;
+ t['apple'] = 790;
+ });
+ t['Times-Roman'] = getLookupTableFactory(function (t) {
+ t['space'] = 250;
+ t['exclam'] = 333;
+ t['quotedbl'] = 408;
+ t['numbersign'] = 500;
+ t['dollar'] = 500;
+ t['percent'] = 833;
+ t['ampersand'] = 778;
+ t['quoteright'] = 333;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 500;
+ t['plus'] = 564;
+ t['comma'] = 250;
+ t['hyphen'] = 333;
+ t['period'] = 250;
+ t['slash'] = 278;
+ t['zero'] = 500;
+ t['one'] = 500;
+ t['two'] = 500;
+ t['three'] = 500;
+ t['four'] = 500;
+ t['five'] = 500;
+ t['six'] = 500;
+ t['seven'] = 500;
+ t['eight'] = 500;
+ t['nine'] = 500;
+ t['colon'] = 278;
+ t['semicolon'] = 278;
+ t['less'] = 564;
+ t['equal'] = 564;
+ t['greater'] = 564;
+ t['question'] = 444;
+ t['at'] = 921;
+ t['A'] = 722;
+ t['B'] = 667;
+ t['C'] = 667;
+ t['D'] = 722;
+ t['E'] = 611;
+ t['F'] = 556;
+ t['G'] = 722;
+ t['H'] = 722;
+ t['I'] = 333;
+ t['J'] = 389;
+ t['K'] = 722;
+ t['L'] = 611;
+ t['M'] = 889;
+ t['N'] = 722;
+ t['O'] = 722;
+ t['P'] = 556;
+ t['Q'] = 722;
+ t['R'] = 667;
+ t['S'] = 556;
+ t['T'] = 611;
+ t['U'] = 722;
+ t['V'] = 722;
+ t['W'] = 944;
+ t['X'] = 722;
+ t['Y'] = 722;
+ t['Z'] = 611;
+ t['bracketleft'] = 333;
+ t['backslash'] = 278;
+ t['bracketright'] = 333;
+ t['asciicircum'] = 469;
+ t['underscore'] = 500;
+ t['quoteleft'] = 333;
+ t['a'] = 444;
+ t['b'] = 500;
+ t['c'] = 444;
+ t['d'] = 500;
+ t['e'] = 444;
+ t['f'] = 333;
+ t['g'] = 500;
+ t['h'] = 500;
+ t['i'] = 278;
+ t['j'] = 278;
+ t['k'] = 500;
+ t['l'] = 278;
+ t['m'] = 778;
+ t['n'] = 500;
+ t['o'] = 500;
+ t['p'] = 500;
+ t['q'] = 500;
+ t['r'] = 333;
+ t['s'] = 389;
+ t['t'] = 278;
+ t['u'] = 500;
+ t['v'] = 500;
+ t['w'] = 722;
+ t['x'] = 500;
+ t['y'] = 500;
+ t['z'] = 444;
+ t['braceleft'] = 480;
+ t['bar'] = 200;
+ t['braceright'] = 480;
+ t['asciitilde'] = 541;
+ t['exclamdown'] = 333;
+ t['cent'] = 500;
+ t['sterling'] = 500;
+ t['fraction'] = 167;
+ t['yen'] = 500;
+ t['florin'] = 500;
+ t['section'] = 500;
+ t['currency'] = 500;
+ t['quotesingle'] = 180;
+ t['quotedblleft'] = 444;
+ t['guillemotleft'] = 500;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 556;
+ t['fl'] = 556;
+ t['endash'] = 500;
+ t['dagger'] = 500;
+ t['daggerdbl'] = 500;
+ t['periodcentered'] = 250;
+ t['paragraph'] = 453;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 333;
+ t['quotedblbase'] = 444;
+ t['quotedblright'] = 444;
+ t['guillemotright'] = 500;
+ t['ellipsis'] = 1000;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 444;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 1000;
+ t['AE'] = 889;
+ t['ordfeminine'] = 276;
+ t['Lslash'] = 611;
+ t['Oslash'] = 722;
+ t['OE'] = 889;
+ t['ordmasculine'] = 310;
+ t['ae'] = 667;
+ t['dotlessi'] = 278;
+ t['lslash'] = 278;
+ t['oslash'] = 500;
+ t['oe'] = 722;
+ t['germandbls'] = 500;
+ t['Idieresis'] = 333;
+ t['eacute'] = 444;
+ t['abreve'] = 444;
+ t['uhungarumlaut'] = 500;
+ t['ecaron'] = 444;
+ t['Ydieresis'] = 722;
+ t['divide'] = 564;
+ t['Yacute'] = 722;
+ t['Acircumflex'] = 722;
+ t['aacute'] = 444;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 500;
+ t['scommaaccent'] = 389;
+ t['ecircumflex'] = 444;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 444;
+ t['Uacute'] = 722;
+ t['uogonek'] = 500;
+ t['Edieresis'] = 611;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 760;
+ t['Emacron'] = 611;
+ t['ccaron'] = 444;
+ t['aring'] = 444;
+ t['Ncommaaccent'] = 722;
+ t['lacute'] = 278;
+ t['agrave'] = 444;
+ t['Tcommaaccent'] = 611;
+ t['Cacute'] = 667;
+ t['atilde'] = 444;
+ t['Edotaccent'] = 611;
+ t['scaron'] = 389;
+ t['scedilla'] = 389;
+ t['iacute'] = 278;
+ t['lozenge'] = 471;
+ t['Rcaron'] = 667;
+ t['Gcommaaccent'] = 722;
+ t['ucircumflex'] = 500;
+ t['acircumflex'] = 444;
+ t['Amacron'] = 722;
+ t['rcaron'] = 333;
+ t['ccedilla'] = 444;
+ t['Zdotaccent'] = 611;
+ t['Thorn'] = 556;
+ t['Omacron'] = 722;
+ t['Racute'] = 667;
+ t['Sacute'] = 556;
+ t['dcaron'] = 588;
+ t['Umacron'] = 722;
+ t['uring'] = 500;
+ t['threesuperior'] = 300;
+ t['Ograve'] = 722;
+ t['Agrave'] = 722;
+ t['Abreve'] = 722;
+ t['multiply'] = 564;
+ t['uacute'] = 500;
+ t['Tcaron'] = 611;
+ t['partialdiff'] = 476;
+ t['ydieresis'] = 500;
+ t['Nacute'] = 722;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 611;
+ t['adieresis'] = 444;
+ t['edieresis'] = 444;
+ t['cacute'] = 444;
+ t['nacute'] = 500;
+ t['umacron'] = 500;
+ t['Ncaron'] = 722;
+ t['Iacute'] = 333;
+ t['plusminus'] = 564;
+ t['brokenbar'] = 200;
+ t['registered'] = 760;
+ t['Gbreve'] = 722;
+ t['Idotaccent'] = 333;
+ t['summation'] = 600;
+ t['Egrave'] = 611;
+ t['racute'] = 333;
+ t['omacron'] = 500;
+ t['Zacute'] = 611;
+ t['Zcaron'] = 611;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 667;
+ t['lcommaaccent'] = 278;
+ t['tcaron'] = 326;
+ t['eogonek'] = 444;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 722;
+ t['Adieresis'] = 722;
+ t['egrave'] = 444;
+ t['zacute'] = 444;
+ t['iogonek'] = 278;
+ t['Oacute'] = 722;
+ t['oacute'] = 500;
+ t['amacron'] = 444;
+ t['sacute'] = 389;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 722;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 500;
+ t['twosuperior'] = 300;
+ t['Odieresis'] = 722;
+ t['mu'] = 500;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 500;
+ t['Eogonek'] = 611;
+ t['dcroat'] = 500;
+ t['threequarters'] = 750;
+ t['Scedilla'] = 556;
+ t['lcaron'] = 344;
+ t['Kcommaaccent'] = 722;
+ t['Lacute'] = 611;
+ t['trademark'] = 980;
+ t['edotaccent'] = 444;
+ t['Igrave'] = 333;
+ t['Imacron'] = 333;
+ t['Lcaron'] = 611;
+ t['onehalf'] = 750;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 500;
+ t['ntilde'] = 500;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 611;
+ t['emacron'] = 444;
+ t['gbreve'] = 500;
+ t['onequarter'] = 750;
+ t['Scaron'] = 556;
+ t['Scommaaccent'] = 556;
+ t['Ohungarumlaut'] = 722;
+ t['degree'] = 400;
+ t['ograve'] = 500;
+ t['Ccaron'] = 667;
+ t['ugrave'] = 500;
+ t['radical'] = 453;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 333;
+ t['Ntilde'] = 722;
+ t['otilde'] = 500;
+ t['Rcommaaccent'] = 667;
+ t['Lcommaaccent'] = 611;
+ t['Atilde'] = 722;
+ t['Aogonek'] = 722;
+ t['Aring'] = 722;
+ t['Otilde'] = 722;
+ t['zdotaccent'] = 444;
+ t['Ecaron'] = 611;
+ t['Iogonek'] = 333;
+ t['kcommaaccent'] = 500;
+ t['minus'] = 564;
+ t['Icircumflex'] = 333;
+ t['ncaron'] = 500;
+ t['tcommaaccent'] = 278;
+ t['logicalnot'] = 564;
+ t['odieresis'] = 500;
+ t['udieresis'] = 500;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 500;
+ t['eth'] = 500;
+ t['zcaron'] = 444;
+ t['ncommaaccent'] = 500;
+ t['onesuperior'] = 300;
+ t['imacron'] = 278;
+ t['Euro'] = 500;
+ });
+ t['Times-Bold'] = getLookupTableFactory(function (t) {
+ t['space'] = 250;
+ t['exclam'] = 333;
+ t['quotedbl'] = 555;
+ t['numbersign'] = 500;
+ t['dollar'] = 500;
+ t['percent'] = 1000;
+ t['ampersand'] = 833;
+ t['quoteright'] = 333;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 500;
+ t['plus'] = 570;
+ t['comma'] = 250;
+ t['hyphen'] = 333;
+ t['period'] = 250;
+ t['slash'] = 278;
+ t['zero'] = 500;
+ t['one'] = 500;
+ t['two'] = 500;
+ t['three'] = 500;
+ t['four'] = 500;
+ t['five'] = 500;
+ t['six'] = 500;
+ t['seven'] = 500;
+ t['eight'] = 500;
+ t['nine'] = 500;
+ t['colon'] = 333;
+ t['semicolon'] = 333;
+ t['less'] = 570;
+ t['equal'] = 570;
+ t['greater'] = 570;
+ t['question'] = 500;
+ t['at'] = 930;
+ t['A'] = 722;
+ t['B'] = 667;
+ t['C'] = 722;
+ t['D'] = 722;
+ t['E'] = 667;
+ t['F'] = 611;
+ t['G'] = 778;
+ t['H'] = 778;
+ t['I'] = 389;
+ t['J'] = 500;
+ t['K'] = 778;
+ t['L'] = 667;
+ t['M'] = 944;
+ t['N'] = 722;
+ t['O'] = 778;
+ t['P'] = 611;
+ t['Q'] = 778;
+ t['R'] = 722;
+ t['S'] = 556;
+ t['T'] = 667;
+ t['U'] = 722;
+ t['V'] = 722;
+ t['W'] = 1000;
+ t['X'] = 722;
+ t['Y'] = 722;
+ t['Z'] = 667;
+ t['bracketleft'] = 333;
+ t['backslash'] = 278;
+ t['bracketright'] = 333;
+ t['asciicircum'] = 581;
+ t['underscore'] = 500;
+ t['quoteleft'] = 333;
+ t['a'] = 500;
+ t['b'] = 556;
+ t['c'] = 444;
+ t['d'] = 556;
+ t['e'] = 444;
+ t['f'] = 333;
+ t['g'] = 500;
+ t['h'] = 556;
+ t['i'] = 278;
+ t['j'] = 333;
+ t['k'] = 556;
+ t['l'] = 278;
+ t['m'] = 833;
+ t['n'] = 556;
+ t['o'] = 500;
+ t['p'] = 556;
+ t['q'] = 556;
+ t['r'] = 444;
+ t['s'] = 389;
+ t['t'] = 333;
+ t['u'] = 556;
+ t['v'] = 500;
+ t['w'] = 722;
+ t['x'] = 500;
+ t['y'] = 500;
+ t['z'] = 444;
+ t['braceleft'] = 394;
+ t['bar'] = 220;
+ t['braceright'] = 394;
+ t['asciitilde'] = 520;
+ t['exclamdown'] = 333;
+ t['cent'] = 500;
+ t['sterling'] = 500;
+ t['fraction'] = 167;
+ t['yen'] = 500;
+ t['florin'] = 500;
+ t['section'] = 500;
+ t['currency'] = 500;
+ t['quotesingle'] = 278;
+ t['quotedblleft'] = 500;
+ t['guillemotleft'] = 500;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 556;
+ t['fl'] = 556;
+ t['endash'] = 500;
+ t['dagger'] = 500;
+ t['daggerdbl'] = 500;
+ t['periodcentered'] = 250;
+ t['paragraph'] = 540;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 333;
+ t['quotedblbase'] = 500;
+ t['quotedblright'] = 500;
+ t['guillemotright'] = 500;
+ t['ellipsis'] = 1000;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 500;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 1000;
+ t['AE'] = 1000;
+ t['ordfeminine'] = 300;
+ t['Lslash'] = 667;
+ t['Oslash'] = 778;
+ t['OE'] = 1000;
+ t['ordmasculine'] = 330;
+ t['ae'] = 722;
+ t['dotlessi'] = 278;
+ t['lslash'] = 278;
+ t['oslash'] = 500;
+ t['oe'] = 722;
+ t['germandbls'] = 556;
+ t['Idieresis'] = 389;
+ t['eacute'] = 444;
+ t['abreve'] = 500;
+ t['uhungarumlaut'] = 556;
+ t['ecaron'] = 444;
+ t['Ydieresis'] = 722;
+ t['divide'] = 570;
+ t['Yacute'] = 722;
+ t['Acircumflex'] = 722;
+ t['aacute'] = 500;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 500;
+ t['scommaaccent'] = 389;
+ t['ecircumflex'] = 444;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 500;
+ t['Uacute'] = 722;
+ t['uogonek'] = 556;
+ t['Edieresis'] = 667;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 747;
+ t['Emacron'] = 667;
+ t['ccaron'] = 444;
+ t['aring'] = 500;
+ t['Ncommaaccent'] = 722;
+ t['lacute'] = 278;
+ t['agrave'] = 500;
+ t['Tcommaaccent'] = 667;
+ t['Cacute'] = 722;
+ t['atilde'] = 500;
+ t['Edotaccent'] = 667;
+ t['scaron'] = 389;
+ t['scedilla'] = 389;
+ t['iacute'] = 278;
+ t['lozenge'] = 494;
+ t['Rcaron'] = 722;
+ t['Gcommaaccent'] = 778;
+ t['ucircumflex'] = 556;
+ t['acircumflex'] = 500;
+ t['Amacron'] = 722;
+ t['rcaron'] = 444;
+ t['ccedilla'] = 444;
+ t['Zdotaccent'] = 667;
+ t['Thorn'] = 611;
+ t['Omacron'] = 778;
+ t['Racute'] = 722;
+ t['Sacute'] = 556;
+ t['dcaron'] = 672;
+ t['Umacron'] = 722;
+ t['uring'] = 556;
+ t['threesuperior'] = 300;
+ t['Ograve'] = 778;
+ t['Agrave'] = 722;
+ t['Abreve'] = 722;
+ t['multiply'] = 570;
+ t['uacute'] = 556;
+ t['Tcaron'] = 667;
+ t['partialdiff'] = 494;
+ t['ydieresis'] = 500;
+ t['Nacute'] = 722;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 667;
+ t['adieresis'] = 500;
+ t['edieresis'] = 444;
+ t['cacute'] = 444;
+ t['nacute'] = 556;
+ t['umacron'] = 556;
+ t['Ncaron'] = 722;
+ t['Iacute'] = 389;
+ t['plusminus'] = 570;
+ t['brokenbar'] = 220;
+ t['registered'] = 747;
+ t['Gbreve'] = 778;
+ t['Idotaccent'] = 389;
+ t['summation'] = 600;
+ t['Egrave'] = 667;
+ t['racute'] = 444;
+ t['omacron'] = 500;
+ t['Zacute'] = 667;
+ t['Zcaron'] = 667;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 722;
+ t['lcommaaccent'] = 278;
+ t['tcaron'] = 416;
+ t['eogonek'] = 444;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 722;
+ t['Adieresis'] = 722;
+ t['egrave'] = 444;
+ t['zacute'] = 444;
+ t['iogonek'] = 278;
+ t['Oacute'] = 778;
+ t['oacute'] = 500;
+ t['amacron'] = 500;
+ t['sacute'] = 389;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 778;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 556;
+ t['twosuperior'] = 300;
+ t['Odieresis'] = 778;
+ t['mu'] = 556;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 500;
+ t['Eogonek'] = 667;
+ t['dcroat'] = 556;
+ t['threequarters'] = 750;
+ t['Scedilla'] = 556;
+ t['lcaron'] = 394;
+ t['Kcommaaccent'] = 778;
+ t['Lacute'] = 667;
+ t['trademark'] = 1000;
+ t['edotaccent'] = 444;
+ t['Igrave'] = 389;
+ t['Imacron'] = 389;
+ t['Lcaron'] = 667;
+ t['onehalf'] = 750;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 500;
+ t['ntilde'] = 556;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 667;
+ t['emacron'] = 444;
+ t['gbreve'] = 500;
+ t['onequarter'] = 750;
+ t['Scaron'] = 556;
+ t['Scommaaccent'] = 556;
+ t['Ohungarumlaut'] = 778;
+ t['degree'] = 400;
+ t['ograve'] = 500;
+ t['Ccaron'] = 722;
+ t['ugrave'] = 556;
+ t['radical'] = 549;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 444;
+ t['Ntilde'] = 722;
+ t['otilde'] = 500;
+ t['Rcommaaccent'] = 722;
+ t['Lcommaaccent'] = 667;
+ t['Atilde'] = 722;
+ t['Aogonek'] = 722;
+ t['Aring'] = 722;
+ t['Otilde'] = 778;
+ t['zdotaccent'] = 444;
+ t['Ecaron'] = 667;
+ t['Iogonek'] = 389;
+ t['kcommaaccent'] = 556;
+ t['minus'] = 570;
+ t['Icircumflex'] = 389;
+ t['ncaron'] = 556;
+ t['tcommaaccent'] = 333;
+ t['logicalnot'] = 570;
+ t['odieresis'] = 500;
+ t['udieresis'] = 556;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 500;
+ t['eth'] = 500;
+ t['zcaron'] = 444;
+ t['ncommaaccent'] = 556;
+ t['onesuperior'] = 300;
+ t['imacron'] = 278;
+ t['Euro'] = 500;
+ });
+ t['Times-BoldItalic'] = getLookupTableFactory(function (t) {
+ t['space'] = 250;
+ t['exclam'] = 389;
+ t['quotedbl'] = 555;
+ t['numbersign'] = 500;
+ t['dollar'] = 500;
+ t['percent'] = 833;
+ t['ampersand'] = 778;
+ t['quoteright'] = 333;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 500;
+ t['plus'] = 570;
+ t['comma'] = 250;
+ t['hyphen'] = 333;
+ t['period'] = 250;
+ t['slash'] = 278;
+ t['zero'] = 500;
+ t['one'] = 500;
+ t['two'] = 500;
+ t['three'] = 500;
+ t['four'] = 500;
+ t['five'] = 500;
+ t['six'] = 500;
+ t['seven'] = 500;
+ t['eight'] = 500;
+ t['nine'] = 500;
+ t['colon'] = 333;
+ t['semicolon'] = 333;
+ t['less'] = 570;
+ t['equal'] = 570;
+ t['greater'] = 570;
+ t['question'] = 500;
+ t['at'] = 832;
+ t['A'] = 667;
+ t['B'] = 667;
+ t['C'] = 667;
+ t['D'] = 722;
+ t['E'] = 667;
+ t['F'] = 667;
+ t['G'] = 722;
+ t['H'] = 778;
+ t['I'] = 389;
+ t['J'] = 500;
+ t['K'] = 667;
+ t['L'] = 611;
+ t['M'] = 889;
+ t['N'] = 722;
+ t['O'] = 722;
+ t['P'] = 611;
+ t['Q'] = 722;
+ t['R'] = 667;
+ t['S'] = 556;
+ t['T'] = 611;
+ t['U'] = 722;
+ t['V'] = 667;
+ t['W'] = 889;
+ t['X'] = 667;
+ t['Y'] = 611;
+ t['Z'] = 611;
+ t['bracketleft'] = 333;
+ t['backslash'] = 278;
+ t['bracketright'] = 333;
+ t['asciicircum'] = 570;
+ t['underscore'] = 500;
+ t['quoteleft'] = 333;
+ t['a'] = 500;
+ t['b'] = 500;
+ t['c'] = 444;
+ t['d'] = 500;
+ t['e'] = 444;
+ t['f'] = 333;
+ t['g'] = 500;
+ t['h'] = 556;
+ t['i'] = 278;
+ t['j'] = 278;
+ t['k'] = 500;
+ t['l'] = 278;
+ t['m'] = 778;
+ t['n'] = 556;
+ t['o'] = 500;
+ t['p'] = 500;
+ t['q'] = 500;
+ t['r'] = 389;
+ t['s'] = 389;
+ t['t'] = 278;
+ t['u'] = 556;
+ t['v'] = 444;
+ t['w'] = 667;
+ t['x'] = 500;
+ t['y'] = 444;
+ t['z'] = 389;
+ t['braceleft'] = 348;
+ t['bar'] = 220;
+ t['braceright'] = 348;
+ t['asciitilde'] = 570;
+ t['exclamdown'] = 389;
+ t['cent'] = 500;
+ t['sterling'] = 500;
+ t['fraction'] = 167;
+ t['yen'] = 500;
+ t['florin'] = 500;
+ t['section'] = 500;
+ t['currency'] = 500;
+ t['quotesingle'] = 278;
+ t['quotedblleft'] = 500;
+ t['guillemotleft'] = 500;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 556;
+ t['fl'] = 556;
+ t['endash'] = 500;
+ t['dagger'] = 500;
+ t['daggerdbl'] = 500;
+ t['periodcentered'] = 250;
+ t['paragraph'] = 500;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 333;
+ t['quotedblbase'] = 500;
+ t['quotedblright'] = 500;
+ t['guillemotright'] = 500;
+ t['ellipsis'] = 1000;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 500;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 1000;
+ t['AE'] = 944;
+ t['ordfeminine'] = 266;
+ t['Lslash'] = 611;
+ t['Oslash'] = 722;
+ t['OE'] = 944;
+ t['ordmasculine'] = 300;
+ t['ae'] = 722;
+ t['dotlessi'] = 278;
+ t['lslash'] = 278;
+ t['oslash'] = 500;
+ t['oe'] = 722;
+ t['germandbls'] = 500;
+ t['Idieresis'] = 389;
+ t['eacute'] = 444;
+ t['abreve'] = 500;
+ t['uhungarumlaut'] = 556;
+ t['ecaron'] = 444;
+ t['Ydieresis'] = 611;
+ t['divide'] = 570;
+ t['Yacute'] = 611;
+ t['Acircumflex'] = 667;
+ t['aacute'] = 500;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 444;
+ t['scommaaccent'] = 389;
+ t['ecircumflex'] = 444;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 500;
+ t['Uacute'] = 722;
+ t['uogonek'] = 556;
+ t['Edieresis'] = 667;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 747;
+ t['Emacron'] = 667;
+ t['ccaron'] = 444;
+ t['aring'] = 500;
+ t['Ncommaaccent'] = 722;
+ t['lacute'] = 278;
+ t['agrave'] = 500;
+ t['Tcommaaccent'] = 611;
+ t['Cacute'] = 667;
+ t['atilde'] = 500;
+ t['Edotaccent'] = 667;
+ t['scaron'] = 389;
+ t['scedilla'] = 389;
+ t['iacute'] = 278;
+ t['lozenge'] = 494;
+ t['Rcaron'] = 667;
+ t['Gcommaaccent'] = 722;
+ t['ucircumflex'] = 556;
+ t['acircumflex'] = 500;
+ t['Amacron'] = 667;
+ t['rcaron'] = 389;
+ t['ccedilla'] = 444;
+ t['Zdotaccent'] = 611;
+ t['Thorn'] = 611;
+ t['Omacron'] = 722;
+ t['Racute'] = 667;
+ t['Sacute'] = 556;
+ t['dcaron'] = 608;
+ t['Umacron'] = 722;
+ t['uring'] = 556;
+ t['threesuperior'] = 300;
+ t['Ograve'] = 722;
+ t['Agrave'] = 667;
+ t['Abreve'] = 667;
+ t['multiply'] = 570;
+ t['uacute'] = 556;
+ t['Tcaron'] = 611;
+ t['partialdiff'] = 494;
+ t['ydieresis'] = 444;
+ t['Nacute'] = 722;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 667;
+ t['adieresis'] = 500;
+ t['edieresis'] = 444;
+ t['cacute'] = 444;
+ t['nacute'] = 556;
+ t['umacron'] = 556;
+ t['Ncaron'] = 722;
+ t['Iacute'] = 389;
+ t['plusminus'] = 570;
+ t['brokenbar'] = 220;
+ t['registered'] = 747;
+ t['Gbreve'] = 722;
+ t['Idotaccent'] = 389;
+ t['summation'] = 600;
+ t['Egrave'] = 667;
+ t['racute'] = 389;
+ t['omacron'] = 500;
+ t['Zacute'] = 611;
+ t['Zcaron'] = 611;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 667;
+ t['lcommaaccent'] = 278;
+ t['tcaron'] = 366;
+ t['eogonek'] = 444;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 667;
+ t['Adieresis'] = 667;
+ t['egrave'] = 444;
+ t['zacute'] = 389;
+ t['iogonek'] = 278;
+ t['Oacute'] = 722;
+ t['oacute'] = 500;
+ t['amacron'] = 500;
+ t['sacute'] = 389;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 722;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 500;
+ t['twosuperior'] = 300;
+ t['Odieresis'] = 722;
+ t['mu'] = 576;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 500;
+ t['Eogonek'] = 667;
+ t['dcroat'] = 500;
+ t['threequarters'] = 750;
+ t['Scedilla'] = 556;
+ t['lcaron'] = 382;
+ t['Kcommaaccent'] = 667;
+ t['Lacute'] = 611;
+ t['trademark'] = 1000;
+ t['edotaccent'] = 444;
+ t['Igrave'] = 389;
+ t['Imacron'] = 389;
+ t['Lcaron'] = 611;
+ t['onehalf'] = 750;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 500;
+ t['ntilde'] = 556;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 667;
+ t['emacron'] = 444;
+ t['gbreve'] = 500;
+ t['onequarter'] = 750;
+ t['Scaron'] = 556;
+ t['Scommaaccent'] = 556;
+ t['Ohungarumlaut'] = 722;
+ t['degree'] = 400;
+ t['ograve'] = 500;
+ t['Ccaron'] = 667;
+ t['ugrave'] = 556;
+ t['radical'] = 549;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 389;
+ t['Ntilde'] = 722;
+ t['otilde'] = 500;
+ t['Rcommaaccent'] = 667;
+ t['Lcommaaccent'] = 611;
+ t['Atilde'] = 667;
+ t['Aogonek'] = 667;
+ t['Aring'] = 667;
+ t['Otilde'] = 722;
+ t['zdotaccent'] = 389;
+ t['Ecaron'] = 667;
+ t['Iogonek'] = 389;
+ t['kcommaaccent'] = 500;
+ t['minus'] = 606;
+ t['Icircumflex'] = 389;
+ t['ncaron'] = 556;
+ t['tcommaaccent'] = 278;
+ t['logicalnot'] = 606;
+ t['odieresis'] = 500;
+ t['udieresis'] = 556;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 500;
+ t['eth'] = 500;
+ t['zcaron'] = 389;
+ t['ncommaaccent'] = 556;
+ t['onesuperior'] = 300;
+ t['imacron'] = 278;
+ t['Euro'] = 500;
+ });
+ t['Times-Italic'] = getLookupTableFactory(function (t) {
+ t['space'] = 250;
+ t['exclam'] = 333;
+ t['quotedbl'] = 420;
+ t['numbersign'] = 500;
+ t['dollar'] = 500;
+ t['percent'] = 833;
+ t['ampersand'] = 778;
+ t['quoteright'] = 333;
+ t['parenleft'] = 333;
+ t['parenright'] = 333;
+ t['asterisk'] = 500;
+ t['plus'] = 675;
+ t['comma'] = 250;
+ t['hyphen'] = 333;
+ t['period'] = 250;
+ t['slash'] = 278;
+ t['zero'] = 500;
+ t['one'] = 500;
+ t['two'] = 500;
+ t['three'] = 500;
+ t['four'] = 500;
+ t['five'] = 500;
+ t['six'] = 500;
+ t['seven'] = 500;
+ t['eight'] = 500;
+ t['nine'] = 500;
+ t['colon'] = 333;
+ t['semicolon'] = 333;
+ t['less'] = 675;
+ t['equal'] = 675;
+ t['greater'] = 675;
+ t['question'] = 500;
+ t['at'] = 920;
+ t['A'] = 611;
+ t['B'] = 611;
+ t['C'] = 667;
+ t['D'] = 722;
+ t['E'] = 611;
+ t['F'] = 611;
+ t['G'] = 722;
+ t['H'] = 722;
+ t['I'] = 333;
+ t['J'] = 444;
+ t['K'] = 667;
+ t['L'] = 556;
+ t['M'] = 833;
+ t['N'] = 667;
+ t['O'] = 722;
+ t['P'] = 611;
+ t['Q'] = 722;
+ t['R'] = 611;
+ t['S'] = 500;
+ t['T'] = 556;
+ t['U'] = 722;
+ t['V'] = 611;
+ t['W'] = 833;
+ t['X'] = 611;
+ t['Y'] = 556;
+ t['Z'] = 556;
+ t['bracketleft'] = 389;
+ t['backslash'] = 278;
+ t['bracketright'] = 389;
+ t['asciicircum'] = 422;
+ t['underscore'] = 500;
+ t['quoteleft'] = 333;
+ t['a'] = 500;
+ t['b'] = 500;
+ t['c'] = 444;
+ t['d'] = 500;
+ t['e'] = 444;
+ t['f'] = 278;
+ t['g'] = 500;
+ t['h'] = 500;
+ t['i'] = 278;
+ t['j'] = 278;
+ t['k'] = 444;
+ t['l'] = 278;
+ t['m'] = 722;
+ t['n'] = 500;
+ t['o'] = 500;
+ t['p'] = 500;
+ t['q'] = 500;
+ t['r'] = 389;
+ t['s'] = 389;
+ t['t'] = 278;
+ t['u'] = 500;
+ t['v'] = 444;
+ t['w'] = 667;
+ t['x'] = 444;
+ t['y'] = 444;
+ t['z'] = 389;
+ t['braceleft'] = 400;
+ t['bar'] = 275;
+ t['braceright'] = 400;
+ t['asciitilde'] = 541;
+ t['exclamdown'] = 389;
+ t['cent'] = 500;
+ t['sterling'] = 500;
+ t['fraction'] = 167;
+ t['yen'] = 500;
+ t['florin'] = 500;
+ t['section'] = 500;
+ t['currency'] = 500;
+ t['quotesingle'] = 214;
+ t['quotedblleft'] = 556;
+ t['guillemotleft'] = 500;
+ t['guilsinglleft'] = 333;
+ t['guilsinglright'] = 333;
+ t['fi'] = 500;
+ t['fl'] = 500;
+ t['endash'] = 500;
+ t['dagger'] = 500;
+ t['daggerdbl'] = 500;
+ t['periodcentered'] = 250;
+ t['paragraph'] = 523;
+ t['bullet'] = 350;
+ t['quotesinglbase'] = 333;
+ t['quotedblbase'] = 556;
+ t['quotedblright'] = 556;
+ t['guillemotright'] = 500;
+ t['ellipsis'] = 889;
+ t['perthousand'] = 1000;
+ t['questiondown'] = 500;
+ t['grave'] = 333;
+ t['acute'] = 333;
+ t['circumflex'] = 333;
+ t['tilde'] = 333;
+ t['macron'] = 333;
+ t['breve'] = 333;
+ t['dotaccent'] = 333;
+ t['dieresis'] = 333;
+ t['ring'] = 333;
+ t['cedilla'] = 333;
+ t['hungarumlaut'] = 333;
+ t['ogonek'] = 333;
+ t['caron'] = 333;
+ t['emdash'] = 889;
+ t['AE'] = 889;
+ t['ordfeminine'] = 276;
+ t['Lslash'] = 556;
+ t['Oslash'] = 722;
+ t['OE'] = 944;
+ t['ordmasculine'] = 310;
+ t['ae'] = 667;
+ t['dotlessi'] = 278;
+ t['lslash'] = 278;
+ t['oslash'] = 500;
+ t['oe'] = 667;
+ t['germandbls'] = 500;
+ t['Idieresis'] = 333;
+ t['eacute'] = 444;
+ t['abreve'] = 500;
+ t['uhungarumlaut'] = 500;
+ t['ecaron'] = 444;
+ t['Ydieresis'] = 556;
+ t['divide'] = 675;
+ t['Yacute'] = 556;
+ t['Acircumflex'] = 611;
+ t['aacute'] = 500;
+ t['Ucircumflex'] = 722;
+ t['yacute'] = 444;
+ t['scommaaccent'] = 389;
+ t['ecircumflex'] = 444;
+ t['Uring'] = 722;
+ t['Udieresis'] = 722;
+ t['aogonek'] = 500;
+ t['Uacute'] = 722;
+ t['uogonek'] = 500;
+ t['Edieresis'] = 611;
+ t['Dcroat'] = 722;
+ t['commaaccent'] = 250;
+ t['copyright'] = 760;
+ t['Emacron'] = 611;
+ t['ccaron'] = 444;
+ t['aring'] = 500;
+ t['Ncommaaccent'] = 667;
+ t['lacute'] = 278;
+ t['agrave'] = 500;
+ t['Tcommaaccent'] = 556;
+ t['Cacute'] = 667;
+ t['atilde'] = 500;
+ t['Edotaccent'] = 611;
+ t['scaron'] = 389;
+ t['scedilla'] = 389;
+ t['iacute'] = 278;
+ t['lozenge'] = 471;
+ t['Rcaron'] = 611;
+ t['Gcommaaccent'] = 722;
+ t['ucircumflex'] = 500;
+ t['acircumflex'] = 500;
+ t['Amacron'] = 611;
+ t['rcaron'] = 389;
+ t['ccedilla'] = 444;
+ t['Zdotaccent'] = 556;
+ t['Thorn'] = 611;
+ t['Omacron'] = 722;
+ t['Racute'] = 611;
+ t['Sacute'] = 500;
+ t['dcaron'] = 544;
+ t['Umacron'] = 722;
+ t['uring'] = 500;
+ t['threesuperior'] = 300;
+ t['Ograve'] = 722;
+ t['Agrave'] = 611;
+ t['Abreve'] = 611;
+ t['multiply'] = 675;
+ t['uacute'] = 500;
+ t['Tcaron'] = 556;
+ t['partialdiff'] = 476;
+ t['ydieresis'] = 444;
+ t['Nacute'] = 667;
+ t['icircumflex'] = 278;
+ t['Ecircumflex'] = 611;
+ t['adieresis'] = 500;
+ t['edieresis'] = 444;
+ t['cacute'] = 444;
+ t['nacute'] = 500;
+ t['umacron'] = 500;
+ t['Ncaron'] = 667;
+ t['Iacute'] = 333;
+ t['plusminus'] = 675;
+ t['brokenbar'] = 275;
+ t['registered'] = 760;
+ t['Gbreve'] = 722;
+ t['Idotaccent'] = 333;
+ t['summation'] = 600;
+ t['Egrave'] = 611;
+ t['racute'] = 389;
+ t['omacron'] = 500;
+ t['Zacute'] = 556;
+ t['Zcaron'] = 556;
+ t['greaterequal'] = 549;
+ t['Eth'] = 722;
+ t['Ccedilla'] = 667;
+ t['lcommaaccent'] = 278;
+ t['tcaron'] = 300;
+ t['eogonek'] = 444;
+ t['Uogonek'] = 722;
+ t['Aacute'] = 611;
+ t['Adieresis'] = 611;
+ t['egrave'] = 444;
+ t['zacute'] = 389;
+ t['iogonek'] = 278;
+ t['Oacute'] = 722;
+ t['oacute'] = 500;
+ t['amacron'] = 500;
+ t['sacute'] = 389;
+ t['idieresis'] = 278;
+ t['Ocircumflex'] = 722;
+ t['Ugrave'] = 722;
+ t['Delta'] = 612;
+ t['thorn'] = 500;
+ t['twosuperior'] = 300;
+ t['Odieresis'] = 722;
+ t['mu'] = 500;
+ t['igrave'] = 278;
+ t['ohungarumlaut'] = 500;
+ t['Eogonek'] = 611;
+ t['dcroat'] = 500;
+ t['threequarters'] = 750;
+ t['Scedilla'] = 500;
+ t['lcaron'] = 300;
+ t['Kcommaaccent'] = 667;
+ t['Lacute'] = 556;
+ t['trademark'] = 980;
+ t['edotaccent'] = 444;
+ t['Igrave'] = 333;
+ t['Imacron'] = 333;
+ t['Lcaron'] = 611;
+ t['onehalf'] = 750;
+ t['lessequal'] = 549;
+ t['ocircumflex'] = 500;
+ t['ntilde'] = 500;
+ t['Uhungarumlaut'] = 722;
+ t['Eacute'] = 611;
+ t['emacron'] = 444;
+ t['gbreve'] = 500;
+ t['onequarter'] = 750;
+ t['Scaron'] = 500;
+ t['Scommaaccent'] = 500;
+ t['Ohungarumlaut'] = 722;
+ t['degree'] = 400;
+ t['ograve'] = 500;
+ t['Ccaron'] = 667;
+ t['ugrave'] = 500;
+ t['radical'] = 453;
+ t['Dcaron'] = 722;
+ t['rcommaaccent'] = 389;
+ t['Ntilde'] = 667;
+ t['otilde'] = 500;
+ t['Rcommaaccent'] = 611;
+ t['Lcommaaccent'] = 556;
+ t['Atilde'] = 611;
+ t['Aogonek'] = 611;
+ t['Aring'] = 611;
+ t['Otilde'] = 722;
+ t['zdotaccent'] = 389;
+ t['Ecaron'] = 611;
+ t['Iogonek'] = 333;
+ t['kcommaaccent'] = 444;
+ t['minus'] = 675;
+ t['Icircumflex'] = 333;
+ t['ncaron'] = 500;
+ t['tcommaaccent'] = 278;
+ t['logicalnot'] = 675;
+ t['odieresis'] = 500;
+ t['udieresis'] = 500;
+ t['notequal'] = 549;
+ t['gcommaaccent'] = 500;
+ t['eth'] = 500;
+ t['zcaron'] = 389;
+ t['ncommaaccent'] = 500;
+ t['onesuperior'] = 300;
+ t['imacron'] = 278;
+ t['Euro'] = 500;
+ });
+ t['ZapfDingbats'] = getLookupTableFactory(function (t) {
+ t['space'] = 278;
+ t['a1'] = 974;
+ t['a2'] = 961;
+ t['a202'] = 974;
+ t['a3'] = 980;
+ t['a4'] = 719;
+ t['a5'] = 789;
+ t['a119'] = 790;
+ t['a118'] = 791;
+ t['a117'] = 690;
+ t['a11'] = 960;
+ t['a12'] = 939;
+ t['a13'] = 549;
+ t['a14'] = 855;
+ t['a15'] = 911;
+ t['a16'] = 933;
+ t['a105'] = 911;
+ t['a17'] = 945;
+ t['a18'] = 974;
+ t['a19'] = 755;
+ t['a20'] = 846;
+ t['a21'] = 762;
+ t['a22'] = 761;
+ t['a23'] = 571;
+ t['a24'] = 677;
+ t['a25'] = 763;
+ t['a26'] = 760;
+ t['a27'] = 759;
+ t['a28'] = 754;
+ t['a6'] = 494;
+ t['a7'] = 552;
+ t['a8'] = 537;
+ t['a9'] = 577;
+ t['a10'] = 692;
+ t['a29'] = 786;
+ t['a30'] = 788;
+ t['a31'] = 788;
+ t['a32'] = 790;
+ t['a33'] = 793;
+ t['a34'] = 794;
+ t['a35'] = 816;
+ t['a36'] = 823;
+ t['a37'] = 789;
+ t['a38'] = 841;
+ t['a39'] = 823;
+ t['a40'] = 833;
+ t['a41'] = 816;
+ t['a42'] = 831;
+ t['a43'] = 923;
+ t['a44'] = 744;
+ t['a45'] = 723;
+ t['a46'] = 749;
+ t['a47'] = 790;
+ t['a48'] = 792;
+ t['a49'] = 695;
+ t['a50'] = 776;
+ t['a51'] = 768;
+ t['a52'] = 792;
+ t['a53'] = 759;
+ t['a54'] = 707;
+ t['a55'] = 708;
+ t['a56'] = 682;
+ t['a57'] = 701;
+ t['a58'] = 826;
+ t['a59'] = 815;
+ t['a60'] = 789;
+ t['a61'] = 789;
+ t['a62'] = 707;
+ t['a63'] = 687;
+ t['a64'] = 696;
+ t['a65'] = 689;
+ t['a66'] = 786;
+ t['a67'] = 787;
+ t['a68'] = 713;
+ t['a69'] = 791;
+ t['a70'] = 785;
+ t['a71'] = 791;
+ t['a72'] = 873;
+ t['a73'] = 761;
+ t['a74'] = 762;
+ t['a203'] = 762;
+ t['a75'] = 759;
+ t['a204'] = 759;
+ t['a76'] = 892;
+ t['a77'] = 892;
+ t['a78'] = 788;
+ t['a79'] = 784;
+ t['a81'] = 438;
+ t['a82'] = 138;
+ t['a83'] = 277;
+ t['a84'] = 415;
+ t['a97'] = 392;
+ t['a98'] = 392;
+ t['a99'] = 668;
+ t['a100'] = 668;
+ t['a89'] = 390;
+ t['a90'] = 390;
+ t['a93'] = 317;
+ t['a94'] = 317;
+ t['a91'] = 276;
+ t['a92'] = 276;
+ t['a205'] = 509;
+ t['a85'] = 509;
+ t['a206'] = 410;
+ t['a86'] = 410;
+ t['a87'] = 234;
+ t['a88'] = 234;
+ t['a95'] = 334;
+ t['a96'] = 334;
+ t['a101'] = 732;
+ t['a102'] = 544;
+ t['a103'] = 544;
+ t['a104'] = 910;
+ t['a106'] = 667;
+ t['a107'] = 760;
+ t['a108'] = 760;
+ t['a112'] = 776;
+ t['a111'] = 595;
+ t['a110'] = 694;
+ t['a109'] = 626;
+ t['a120'] = 788;
+ t['a121'] = 788;
+ t['a122'] = 788;
+ t['a123'] = 788;
+ t['a124'] = 788;
+ t['a125'] = 788;
+ t['a126'] = 788;
+ t['a127'] = 788;
+ t['a128'] = 788;
+ t['a129'] = 788;
+ t['a130'] = 788;
+ t['a131'] = 788;
+ t['a132'] = 788;
+ t['a133'] = 788;
+ t['a134'] = 788;
+ t['a135'] = 788;
+ t['a136'] = 788;
+ t['a137'] = 788;
+ t['a138'] = 788;
+ t['a139'] = 788;
+ t['a140'] = 788;
+ t['a141'] = 788;
+ t['a142'] = 788;
+ t['a143'] = 788;
+ t['a144'] = 788;
+ t['a145'] = 788;
+ t['a146'] = 788;
+ t['a147'] = 788;
+ t['a148'] = 788;
+ t['a149'] = 788;
+ t['a150'] = 788;
+ t['a151'] = 788;
+ t['a152'] = 788;
+ t['a153'] = 788;
+ t['a154'] = 788;
+ t['a155'] = 788;
+ t['a156'] = 788;
+ t['a157'] = 788;
+ t['a158'] = 788;
+ t['a159'] = 788;
+ t['a160'] = 894;
+ t['a161'] = 838;
+ t['a163'] = 1016;
+ t['a164'] = 458;
+ t['a196'] = 748;
+ t['a165'] = 924;
+ t['a192'] = 748;
+ t['a166'] = 918;
+ t['a167'] = 927;
+ t['a168'] = 928;
+ t['a169'] = 928;
+ t['a170'] = 834;
+ t['a171'] = 873;
+ t['a172'] = 828;
+ t['a173'] = 924;
+ t['a162'] = 924;
+ t['a174'] = 917;
+ t['a175'] = 930;
+ t['a176'] = 931;
+ t['a177'] = 463;
+ t['a178'] = 883;
+ t['a179'] = 836;
+ t['a193'] = 836;
+ t['a180'] = 867;
+ t['a199'] = 867;
+ t['a181'] = 696;
+ t['a200'] = 696;
+ t['a182'] = 874;
+ t['a201'] = 874;
+ t['a183'] = 760;
+ t['a184'] = 946;
+ t['a197'] = 771;
+ t['a185'] = 865;
+ t['a194'] = 771;
+ t['a198'] = 888;
+ t['a186'] = 967;
+ t['a195'] = 888;
+ t['a187'] = 831;
+ t['a188'] = 873;
+ t['a189'] = 927;
+ t['a190'] = 970;
+ t['a191'] = 918;
+ });
+ });
+ exports.getMetrics = getMetrics;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreMurmurHash3 = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var Uint32ArrayView = sharedUtil.Uint32ArrayView;
+ var MurmurHash3_64 = function MurmurHash3_64Closure(seed) {
+ // Workaround for missing math precision in JS.
+ var MASK_HIGH = 0xffff0000;
+ var MASK_LOW = 0xffff;
+ function MurmurHash3_64(seed) {
+ var SEED = 0xc3d2e1f0;
+ this.h1 = seed ? seed & 0xffffffff : SEED;
+ this.h2 = seed ? seed & 0xffffffff : SEED;
+ }
+ var alwaysUseUint32ArrayView = false;
+ MurmurHash3_64.prototype = {
+ update: function MurmurHash3_64_update(input) {
+ var useUint32ArrayView = alwaysUseUint32ArrayView;
+ var i;
+ if (typeof input === 'string') {
+ var data = new Uint8Array(input.length * 2);
+ var length = 0;
+ for (i = 0; i < input.length; i++) {
+ var code = input.charCodeAt(i);
+ if (code <= 0xff) {
+ data[length++] = code;
+ } else {
+ data[length++] = code >>> 8;
+ data[length++] = code & 0xff;
+ }
+ }
+ } else if (input instanceof Uint8Array) {
+ data = input;
+ length = data.length;
+ } else if (typeof input === 'object' && 'length' in input) {
+ // processing regular arrays as well, e.g. for IE9
+ data = input;
+ length = data.length;
+ useUint32ArrayView = true;
+ } else {
+ throw new Error('Wrong data format in MurmurHash3_64_update. ' + 'Input must be a string or array.');
+ }
+ var blockCounts = length >> 2;
+ var tailLength = length - blockCounts * 4;
+ // we don't care about endianness here
+ var dataUint32 = useUint32ArrayView ? new Uint32ArrayView(data, blockCounts) : new Uint32Array(data.buffer, 0, blockCounts);
+ var k1 = 0;
+ var k2 = 0;
+ var h1 = this.h1;
+ var h2 = this.h2;
+ var C1 = 0xcc9e2d51;
+ var C2 = 0x1b873593;
+ var C1_LOW = C1 & MASK_LOW;
+ var C2_LOW = C2 & MASK_LOW;
+ for (i = 0; i < blockCounts; i++) {
+ if (i & 1) {
+ k1 = dataUint32[i];
+ k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
+ k1 = k1 << 15 | k1 >>> 17;
+ k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
+ h1 ^= k1;
+ h1 = h1 << 13 | h1 >>> 19;
+ h1 = h1 * 5 + 0xe6546b64;
+ } else {
+ k2 = dataUint32[i];
+ k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW;
+ k2 = k2 << 15 | k2 >>> 17;
+ k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW;
+ h2 ^= k2;
+ h2 = h2 << 13 | h2 >>> 19;
+ h2 = h2 * 5 + 0xe6546b64;
+ }
+ }
+ k1 = 0;
+ switch (tailLength) {
+ case 3:
+ k1 ^= data[blockCounts * 4 + 2] << 16;
+ case 2:
+ k1 ^= data[blockCounts * 4 + 1] << 8;
+ case 1:
+ k1 ^= data[blockCounts * 4];
+ k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
+ k1 = k1 << 15 | k1 >>> 17;
+ k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
+ if (blockCounts & 1) {
+ h1 ^= k1;
+ } else {
+ h2 ^= k1;
+ }
+ }
+ this.h1 = h1;
+ this.h2 = h2;
+ return this;
+ },
+ hexdigest: function MurmurHash3_64_hexdigest() {
+ var h1 = this.h1;
+ var h2 = this.h2;
+ h1 ^= h2 >>> 1;
+ h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW;
+ h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16;
+ h1 ^= h2 >>> 1;
+ h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW;
+ h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16;
+ h1 ^= h2 >>> 1;
+ for (var i = 0, arr = [
+ h1,
+ h2
+ ], str = ''; i < arr.length; i++) {
+ var hex = (arr[i] >>> 0).toString(16);
+ while (hex.length < 8) {
+ hex = '0' + hex;
+ }
+ str += hex;
+ }
+ return str;
+ }
+ };
+ return MurmurHash3_64;
+ }();
+ exports.MurmurHash3_64 = MurmurHash3_64;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCorePrimitives = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var isArray = sharedUtil.isArray;
+ var Name = function NameClosure() {
+ function Name(name) {
+ this.name = name;
+ }
+ Name.prototype = {};
+ var nameCache = Object.create(null);
+ Name.get = function Name_get(name) {
+ var nameValue = nameCache[name];
+ return nameValue ? nameValue : nameCache[name] = new Name(name);
+ };
+ return Name;
+ }();
+ var Cmd = function CmdClosure() {
+ function Cmd(cmd) {
+ this.cmd = cmd;
+ }
+ Cmd.prototype = {};
+ var cmdCache = Object.create(null);
+ Cmd.get = function Cmd_get(cmd) {
+ var cmdValue = cmdCache[cmd];
+ return cmdValue ? cmdValue : cmdCache[cmd] = new Cmd(cmd);
+ };
+ return Cmd;
+ }();
+ var Dict = function DictClosure() {
+ var nonSerializable = function nonSerializableClosure() {
+ return nonSerializable;
+ };
+ // creating closure on some variable
+ // xref is optional
+ function Dict(xref) {
+ // Map should only be used internally, use functions below to access.
+ this.map = Object.create(null);
+ this.xref = xref;
+ this.objId = null;
+ this.suppressEncryption = false;
+ this.__nonSerializable__ = nonSerializable;
+ }
+ // disable cloning of the Dict
+ Dict.prototype = {
+ assignXref: function Dict_assignXref(newXref) {
+ this.xref = newXref;
+ },
+ // automatically dereferences Ref objects
+ get: function Dict_get(key1, key2, key3) {
+ var value;
+ var xref = this.xref, suppressEncryption = this.suppressEncryption;
+ if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') {
+ return xref ? xref.fetchIfRef(value, suppressEncryption) : value;
+ }
+ if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') {
+ return xref ? xref.fetchIfRef(value, suppressEncryption) : value;
+ }
+ value = this.map[key3] || null;
+ return xref ? xref.fetchIfRef(value, suppressEncryption) : value;
+ },
+ // Same as get(), but returns a promise and uses fetchIfRefAsync().
+ getAsync: function Dict_getAsync(key1, key2, key3) {
+ var value;
+ var xref = this.xref, suppressEncryption = this.suppressEncryption;
+ if (typeof (value = this.map[key1]) !== 'undefined' || key1 in this.map || typeof key2 === 'undefined') {
+ if (xref) {
+ return xref.fetchIfRefAsync(value, suppressEncryption);
+ }
+ return Promise.resolve(value);
+ }
+ if (typeof (value = this.map[key2]) !== 'undefined' || key2 in this.map || typeof key3 === 'undefined') {
+ if (xref) {
+ return xref.fetchIfRefAsync(value, suppressEncryption);
+ }
+ return Promise.resolve(value);
+ }
+ value = this.map[key3] || null;
+ if (xref) {
+ return xref.fetchIfRefAsync(value, suppressEncryption);
+ }
+ return Promise.resolve(value);
+ },
+ // Same as get(), but dereferences all elements if the result is an Array.
+ getArray: function Dict_getArray(key1, key2, key3) {
+ var value = this.get(key1, key2, key3);
+ var xref = this.xref, suppressEncryption = this.suppressEncryption;
+ if (!isArray(value) || !xref) {
+ return value;
+ }
+ value = value.slice();
+ // Ensure that we don't modify the Dict data.
+ for (var i = 0, ii = value.length; i < ii; i++) {
+ if (!isRef(value[i])) {
+ continue;
+ }
+ value[i] = xref.fetch(value[i], suppressEncryption);
+ }
+ return value;
+ },
+ // no dereferencing
+ getRaw: function Dict_getRaw(key) {
+ return this.map[key];
+ },
+ getKeys: function Dict_getKeys() {
+ return Object.keys(this.map);
+ },
+ set: function Dict_set(key, value) {
+ this.map[key] = value;
+ },
+ has: function Dict_has(key) {
+ return key in this.map;
+ },
+ forEach: function Dict_forEach(callback) {
+ for (var key in this.map) {
+ callback(key, this.get(key));
+ }
+ }
+ };
+ Dict.empty = new Dict(null);
+ Dict.merge = function Dict_merge(xref, dictArray) {
+ var mergedDict = new Dict(xref);
+ for (var i = 0, ii = dictArray.length; i < ii; i++) {
+ var dict = dictArray[i];
+ if (!isDict(dict)) {
+ continue;
+ }
+ for (var keyName in dict.map) {
+ if (mergedDict.map[keyName]) {
+ continue;
+ }
+ mergedDict.map[keyName] = dict.map[keyName];
+ }
+ }
+ return mergedDict;
+ };
+ return Dict;
+ }();
+ var Ref = function RefClosure() {
+ function Ref(num, gen) {
+ this.num = num;
+ this.gen = gen;
+ }
+ Ref.prototype = {
+ toString: function Ref_toString() {
+ // This function is hot, so we make the string as compact as possible.
+ // |this.gen| is almost always zero, so we treat that case specially.
+ var str = this.num + 'R';
+ if (this.gen !== 0) {
+ str += this.gen;
+ }
+ return str;
+ }
+ };
+ return Ref;
+ }();
+ // The reference is identified by number and generation.
+ // This structure stores only one instance of the reference.
+ var RefSet = function RefSetClosure() {
+ function RefSet() {
+ this.dict = Object.create(null);
+ }
+ RefSet.prototype = {
+ has: function RefSet_has(ref) {
+ return ref.toString() in this.dict;
+ },
+ put: function RefSet_put(ref) {
+ this.dict[ref.toString()] = true;
+ },
+ remove: function RefSet_remove(ref) {
+ delete this.dict[ref.toString()];
+ }
+ };
+ return RefSet;
+ }();
+ var RefSetCache = function RefSetCacheClosure() {
+ function RefSetCache() {
+ this.dict = Object.create(null);
+ }
+ RefSetCache.prototype = {
+ get: function RefSetCache_get(ref) {
+ return this.dict[ref.toString()];
+ },
+ has: function RefSetCache_has(ref) {
+ return ref.toString() in this.dict;
+ },
+ put: function RefSetCache_put(ref, obj) {
+ this.dict[ref.toString()] = obj;
+ },
+ putAlias: function RefSetCache_putAlias(ref, aliasRef) {
+ this.dict[ref.toString()] = this.get(aliasRef);
+ },
+ forEach: function RefSetCache_forEach(fn, thisArg) {
+ for (var i in this.dict) {
+ fn.call(thisArg, this.dict[i]);
+ }
+ },
+ clear: function RefSetCache_clear() {
+ this.dict = Object.create(null);
+ }
+ };
+ return RefSetCache;
+ }();
+ function isName(v, name) {
+ return v instanceof Name && (name === undefined || v.name === name);
+ }
+ function isCmd(v, cmd) {
+ return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
+ }
+ function isDict(v, type) {
+ return v instanceof Dict && (type === undefined || isName(v.get('Type'), type));
+ }
+ function isRef(v) {
+ return v instanceof Ref;
+ }
+ function isRefsEqual(v1, v2) {
+ return v1.num === v2.num && v1.gen === v2.gen;
+ }
+ function isStream(v) {
+ return typeof v === 'object' && v !== null && v.getBytes !== undefined;
+ }
+ exports.Cmd = Cmd;
+ exports.Dict = Dict;
+ exports.Name = Name;
+ exports.Ref = Ref;
+ exports.RefSet = RefSet;
+ exports.RefSetCache = RefSetCache;
+ exports.isCmd = isCmd;
+ exports.isDict = isDict;
+ exports.isName = isName;
+ exports.isRef = isRef;
+ exports.isRefsEqual = isRefsEqual;
+ exports.isStream = isStream;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreStandardFonts = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var getLookupTableFactory = sharedUtil.getLookupTableFactory;
+ /**
+ * Hold a map of decoded fonts and of the standard fourteen Type1
+ * fonts and their acronyms.
+ */
+ var getStdFontMap = getLookupTableFactory(function (t) {
+ t['ArialNarrow'] = 'Helvetica';
+ t['ArialNarrow-Bold'] = 'Helvetica-Bold';
+ t['ArialNarrow-BoldItalic'] = 'Helvetica-BoldOblique';
+ t['ArialNarrow-Italic'] = 'Helvetica-Oblique';
+ t['ArialBlack'] = 'Helvetica';
+ t['ArialBlack-Bold'] = 'Helvetica-Bold';
+ t['ArialBlack-BoldItalic'] = 'Helvetica-BoldOblique';
+ t['ArialBlack-Italic'] = 'Helvetica-Oblique';
+ t['Arial'] = 'Helvetica';
+ t['Arial-Bold'] = 'Helvetica-Bold';
+ t['Arial-BoldItalic'] = 'Helvetica-BoldOblique';
+ t['Arial-Italic'] = 'Helvetica-Oblique';
+ t['Arial-BoldItalicMT'] = 'Helvetica-BoldOblique';
+ t['Arial-BoldMT'] = 'Helvetica-Bold';
+ t['Arial-ItalicMT'] = 'Helvetica-Oblique';
+ t['ArialMT'] = 'Helvetica';
+ t['Courier-Bold'] = 'Courier-Bold';
+ t['Courier-BoldItalic'] = 'Courier-BoldOblique';
+ t['Courier-Italic'] = 'Courier-Oblique';
+ t['CourierNew'] = 'Courier';
+ t['CourierNew-Bold'] = 'Courier-Bold';
+ t['CourierNew-BoldItalic'] = 'Courier-BoldOblique';
+ t['CourierNew-Italic'] = 'Courier-Oblique';
+ t['CourierNewPS-BoldItalicMT'] = 'Courier-BoldOblique';
+ t['CourierNewPS-BoldMT'] = 'Courier-Bold';
+ t['CourierNewPS-ItalicMT'] = 'Courier-Oblique';
+ t['CourierNewPSMT'] = 'Courier';
+ t['Helvetica'] = 'Helvetica';
+ t['Helvetica-Bold'] = 'Helvetica-Bold';
+ t['Helvetica-BoldItalic'] = 'Helvetica-BoldOblique';
+ t['Helvetica-BoldOblique'] = 'Helvetica-BoldOblique';
+ t['Helvetica-Italic'] = 'Helvetica-Oblique';
+ t['Helvetica-Oblique'] = 'Helvetica-Oblique';
+ t['Symbol-Bold'] = 'Symbol';
+ t['Symbol-BoldItalic'] = 'Symbol';
+ t['Symbol-Italic'] = 'Symbol';
+ t['TimesNewRoman'] = 'Times-Roman';
+ t['TimesNewRoman-Bold'] = 'Times-Bold';
+ t['TimesNewRoman-BoldItalic'] = 'Times-BoldItalic';
+ t['TimesNewRoman-Italic'] = 'Times-Italic';
+ t['TimesNewRomanPS'] = 'Times-Roman';
+ t['TimesNewRomanPS-Bold'] = 'Times-Bold';
+ t['TimesNewRomanPS-BoldItalic'] = 'Times-BoldItalic';
+ t['TimesNewRomanPS-BoldItalicMT'] = 'Times-BoldItalic';
+ t['TimesNewRomanPS-BoldMT'] = 'Times-Bold';
+ t['TimesNewRomanPS-Italic'] = 'Times-Italic';
+ t['TimesNewRomanPS-ItalicMT'] = 'Times-Italic';
+ t['TimesNewRomanPSMT'] = 'Times-Roman';
+ t['TimesNewRomanPSMT-Bold'] = 'Times-Bold';
+ t['TimesNewRomanPSMT-BoldItalic'] = 'Times-BoldItalic';
+ t['TimesNewRomanPSMT-Italic'] = 'Times-Italic';
+ });
+ /**
+ * Holds the map of the non-standard fonts that might be included as
+ * a standard fonts without glyph data.
+ */
+ var getNonStdFontMap = getLookupTableFactory(function (t) {
+ t['CenturyGothic'] = 'Helvetica';
+ t['CenturyGothic-Bold'] = 'Helvetica-Bold';
+ t['CenturyGothic-BoldItalic'] = 'Helvetica-BoldOblique';
+ t['CenturyGothic-Italic'] = 'Helvetica-Oblique';
+ t['ComicSansMS'] = 'Comic Sans MS';
+ t['ComicSansMS-Bold'] = 'Comic Sans MS-Bold';
+ t['ComicSansMS-BoldItalic'] = 'Comic Sans MS-BoldItalic';
+ t['ComicSansMS-Italic'] = 'Comic Sans MS-Italic';
+ t['LucidaConsole'] = 'Courier';
+ t['LucidaConsole-Bold'] = 'Courier-Bold';
+ t['LucidaConsole-BoldItalic'] = 'Courier-BoldOblique';
+ t['LucidaConsole-Italic'] = 'Courier-Oblique';
+ t['MS-Gothic'] = 'MS Gothic';
+ t['MS-Gothic-Bold'] = 'MS Gothic-Bold';
+ t['MS-Gothic-BoldItalic'] = 'MS Gothic-BoldItalic';
+ t['MS-Gothic-Italic'] = 'MS Gothic-Italic';
+ t['MS-Mincho'] = 'MS Mincho';
+ t['MS-Mincho-Bold'] = 'MS Mincho-Bold';
+ t['MS-Mincho-BoldItalic'] = 'MS Mincho-BoldItalic';
+ t['MS-Mincho-Italic'] = 'MS Mincho-Italic';
+ t['MS-PGothic'] = 'MS PGothic';
+ t['MS-PGothic-Bold'] = 'MS PGothic-Bold';
+ t['MS-PGothic-BoldItalic'] = 'MS PGothic-BoldItalic';
+ t['MS-PGothic-Italic'] = 'MS PGothic-Italic';
+ t['MS-PMincho'] = 'MS PMincho';
+ t['MS-PMincho-Bold'] = 'MS PMincho-Bold';
+ t['MS-PMincho-BoldItalic'] = 'MS PMincho-BoldItalic';
+ t['MS-PMincho-Italic'] = 'MS PMincho-Italic';
+ t['Wingdings'] = 'ZapfDingbats';
+ });
+ var getSerifFonts = getLookupTableFactory(function (t) {
+ t['Adobe Jenson'] = true;
+ t['Adobe Text'] = true;
+ t['Albertus'] = true;
+ t['Aldus'] = true;
+ t['Alexandria'] = true;
+ t['Algerian'] = true;
+ t['American Typewriter'] = true;
+ t['Antiqua'] = true;
+ t['Apex'] = true;
+ t['Arno'] = true;
+ t['Aster'] = true;
+ t['Aurora'] = true;
+ t['Baskerville'] = true;
+ t['Bell'] = true;
+ t['Bembo'] = true;
+ t['Bembo Schoolbook'] = true;
+ t['Benguiat'] = true;
+ t['Berkeley Old Style'] = true;
+ t['Bernhard Modern'] = true;
+ t['Berthold City'] = true;
+ t['Bodoni'] = true;
+ t['Bauer Bodoni'] = true;
+ t['Book Antiqua'] = true;
+ t['Bookman'] = true;
+ t['Bordeaux Roman'] = true;
+ t['Californian FB'] = true;
+ t['Calisto'] = true;
+ t['Calvert'] = true;
+ t['Capitals'] = true;
+ t['Cambria'] = true;
+ t['Cartier'] = true;
+ t['Caslon'] = true;
+ t['Catull'] = true;
+ t['Centaur'] = true;
+ t['Century Old Style'] = true;
+ t['Century Schoolbook'] = true;
+ t['Chaparral'] = true;
+ t['Charis SIL'] = true;
+ t['Cheltenham'] = true;
+ t['Cholla Slab'] = true;
+ t['Clarendon'] = true;
+ t['Clearface'] = true;
+ t['Cochin'] = true;
+ t['Colonna'] = true;
+ t['Computer Modern'] = true;
+ t['Concrete Roman'] = true;
+ t['Constantia'] = true;
+ t['Cooper Black'] = true;
+ t['Corona'] = true;
+ t['Ecotype'] = true;
+ t['Egyptienne'] = true;
+ t['Elephant'] = true;
+ t['Excelsior'] = true;
+ t['Fairfield'] = true;
+ t['FF Scala'] = true;
+ t['Folkard'] = true;
+ t['Footlight'] = true;
+ t['FreeSerif'] = true;
+ t['Friz Quadrata'] = true;
+ t['Garamond'] = true;
+ t['Gentium'] = true;
+ t['Georgia'] = true;
+ t['Gloucester'] = true;
+ t['Goudy Old Style'] = true;
+ t['Goudy Schoolbook'] = true;
+ t['Goudy Pro Font'] = true;
+ t['Granjon'] = true;
+ t['Guardian Egyptian'] = true;
+ t['Heather'] = true;
+ t['Hercules'] = true;
+ t['High Tower Text'] = true;
+ t['Hiroshige'] = true;
+ t['Hoefler Text'] = true;
+ t['Humana Serif'] = true;
+ t['Imprint'] = true;
+ t['Ionic No. 5'] = true;
+ t['Janson'] = true;
+ t['Joanna'] = true;
+ t['Korinna'] = true;
+ t['Lexicon'] = true;
+ t['Liberation Serif'] = true;
+ t['Linux Libertine'] = true;
+ t['Literaturnaya'] = true;
+ t['Lucida'] = true;
+ t['Lucida Bright'] = true;
+ t['Melior'] = true;
+ t['Memphis'] = true;
+ t['Miller'] = true;
+ t['Minion'] = true;
+ t['Modern'] = true;
+ t['Mona Lisa'] = true;
+ t['Mrs Eaves'] = true;
+ t['MS Serif'] = true;
+ t['Museo Slab'] = true;
+ t['New York'] = true;
+ t['Nimbus Roman'] = true;
+ t['NPS Rawlinson Roadway'] = true;
+ t['Palatino'] = true;
+ t['Perpetua'] = true;
+ t['Plantin'] = true;
+ t['Plantin Schoolbook'] = true;
+ t['Playbill'] = true;
+ t['Poor Richard'] = true;
+ t['Rawlinson Roadway'] = true;
+ t['Renault'] = true;
+ t['Requiem'] = true;
+ t['Rockwell'] = true;
+ t['Roman'] = true;
+ t['Rotis Serif'] = true;
+ t['Sabon'] = true;
+ t['Scala'] = true;
+ t['Seagull'] = true;
+ t['Sistina'] = true;
+ t['Souvenir'] = true;
+ t['STIX'] = true;
+ t['Stone Informal'] = true;
+ t['Stone Serif'] = true;
+ t['Sylfaen'] = true;
+ t['Times'] = true;
+ t['Trajan'] = true;
+ t['Trinité'] = true;
+ t['Trump Mediaeval'] = true;
+ t['Utopia'] = true;
+ t['Vale Type'] = true;
+ t['Bitstream Vera'] = true;
+ t['Vera Serif'] = true;
+ t['Versailles'] = true;
+ t['Wanted'] = true;
+ t['Weiss'] = true;
+ t['Wide Latin'] = true;
+ t['Windsor'] = true;
+ t['XITS'] = true;
+ });
+ var getSymbolsFonts = getLookupTableFactory(function (t) {
+ t['Dingbats'] = true;
+ t['Symbol'] = true;
+ t['ZapfDingbats'] = true;
+ });
+ // Glyph map for well-known standard fonts. Sometimes Ghostscript uses CID
+ // fonts, but does not embed the CID to GID mapping. The mapping is incomplete
+ // for all glyphs, but common for some set of the standard fonts.
+ var getGlyphMapForStandardFonts = getLookupTableFactory(function (t) {
+ t[2] = 10;
+ t[3] = 32;
+ t[4] = 33;
+ t[5] = 34;
+ t[6] = 35;
+ t[7] = 36;
+ t[8] = 37;
+ t[9] = 38;
+ t[10] = 39;
+ t[11] = 40;
+ t[12] = 41;
+ t[13] = 42;
+ t[14] = 43;
+ t[15] = 44;
+ t[16] = 45;
+ t[17] = 46;
+ t[18] = 47;
+ t[19] = 48;
+ t[20] = 49;
+ t[21] = 50;
+ t[22] = 51;
+ t[23] = 52;
+ t[24] = 53;
+ t[25] = 54;
+ t[26] = 55;
+ t[27] = 56;
+ t[28] = 57;
+ t[29] = 58;
+ t[30] = 894;
+ t[31] = 60;
+ t[32] = 61;
+ t[33] = 62;
+ t[34] = 63;
+ t[35] = 64;
+ t[36] = 65;
+ t[37] = 66;
+ t[38] = 67;
+ t[39] = 68;
+ t[40] = 69;
+ t[41] = 70;
+ t[42] = 71;
+ t[43] = 72;
+ t[44] = 73;
+ t[45] = 74;
+ t[46] = 75;
+ t[47] = 76;
+ t[48] = 77;
+ t[49] = 78;
+ t[50] = 79;
+ t[51] = 80;
+ t[52] = 81;
+ t[53] = 82;
+ t[54] = 83;
+ t[55] = 84;
+ t[56] = 85;
+ t[57] = 86;
+ t[58] = 87;
+ t[59] = 88;
+ t[60] = 89;
+ t[61] = 90;
+ t[62] = 91;
+ t[63] = 92;
+ t[64] = 93;
+ t[65] = 94;
+ t[66] = 95;
+ t[67] = 96;
+ t[68] = 97;
+ t[69] = 98;
+ t[70] = 99;
+ t[71] = 100;
+ t[72] = 101;
+ t[73] = 102;
+ t[74] = 103;
+ t[75] = 104;
+ t[76] = 105;
+ t[77] = 106;
+ t[78] = 107;
+ t[79] = 108;
+ t[80] = 109;
+ t[81] = 110;
+ t[82] = 111;
+ t[83] = 112;
+ t[84] = 113;
+ t[85] = 114;
+ t[86] = 115;
+ t[87] = 116;
+ t[88] = 117;
+ t[89] = 118;
+ t[90] = 119;
+ t[91] = 120;
+ t[92] = 121;
+ t[93] = 122;
+ t[94] = 123;
+ t[95] = 124;
+ t[96] = 125;
+ t[97] = 126;
+ t[98] = 196;
+ t[99] = 197;
+ t[100] = 199;
+ t[101] = 201;
+ t[102] = 209;
+ t[103] = 214;
+ t[104] = 220;
+ t[105] = 225;
+ t[106] = 224;
+ t[107] = 226;
+ t[108] = 228;
+ t[109] = 227;
+ t[110] = 229;
+ t[111] = 231;
+ t[112] = 233;
+ t[113] = 232;
+ t[114] = 234;
+ t[115] = 235;
+ t[116] = 237;
+ t[117] = 236;
+ t[118] = 238;
+ t[119] = 239;
+ t[120] = 241;
+ t[121] = 243;
+ t[122] = 242;
+ t[123] = 244;
+ t[124] = 246;
+ t[125] = 245;
+ t[126] = 250;
+ t[127] = 249;
+ t[128] = 251;
+ t[129] = 252;
+ t[130] = 8224;
+ t[131] = 176;
+ t[132] = 162;
+ t[133] = 163;
+ t[134] = 167;
+ t[135] = 8226;
+ t[136] = 182;
+ t[137] = 223;
+ t[138] = 174;
+ t[139] = 169;
+ t[140] = 8482;
+ t[141] = 180;
+ t[142] = 168;
+ t[143] = 8800;
+ t[144] = 198;
+ t[145] = 216;
+ t[146] = 8734;
+ t[147] = 177;
+ t[148] = 8804;
+ t[149] = 8805;
+ t[150] = 165;
+ t[151] = 181;
+ t[152] = 8706;
+ t[153] = 8721;
+ t[154] = 8719;
+ t[156] = 8747;
+ t[157] = 170;
+ t[158] = 186;
+ t[159] = 8486;
+ t[160] = 230;
+ t[161] = 248;
+ t[162] = 191;
+ t[163] = 161;
+ t[164] = 172;
+ t[165] = 8730;
+ t[166] = 402;
+ t[167] = 8776;
+ t[168] = 8710;
+ t[169] = 171;
+ t[170] = 187;
+ t[171] = 8230;
+ t[210] = 218;
+ t[223] = 711;
+ t[224] = 321;
+ t[225] = 322;
+ t[227] = 353;
+ t[229] = 382;
+ t[234] = 253;
+ t[252] = 263;
+ t[253] = 268;
+ t[254] = 269;
+ t[258] = 258;
+ t[260] = 260;
+ t[261] = 261;
+ t[265] = 280;
+ t[266] = 281;
+ t[268] = 283;
+ t[269] = 313;
+ t[275] = 323;
+ t[276] = 324;
+ t[278] = 328;
+ t[284] = 345;
+ t[285] = 346;
+ t[286] = 347;
+ t[292] = 367;
+ t[295] = 377;
+ t[296] = 378;
+ t[298] = 380;
+ t[305] = 963;
+ t[306] = 964;
+ t[307] = 966;
+ t[308] = 8215;
+ t[309] = 8252;
+ t[310] = 8319;
+ t[311] = 8359;
+ t[312] = 8592;
+ t[313] = 8593;
+ t[337] = 9552;
+ t[493] = 1039;
+ t[494] = 1040;
+ t[705] = 1524;
+ t[706] = 8362;
+ t[710] = 64288;
+ t[711] = 64298;
+ t[759] = 1617;
+ t[761] = 1776;
+ t[763] = 1778;
+ t[775] = 1652;
+ t[777] = 1764;
+ t[778] = 1780;
+ t[779] = 1781;
+ t[780] = 1782;
+ t[782] = 771;
+ t[783] = 64726;
+ t[786] = 8363;
+ t[788] = 8532;
+ t[790] = 768;
+ t[791] = 769;
+ t[792] = 768;
+ t[795] = 803;
+ t[797] = 64336;
+ t[798] = 64337;
+ t[799] = 64342;
+ t[800] = 64343;
+ t[801] = 64344;
+ t[802] = 64345;
+ t[803] = 64362;
+ t[804] = 64363;
+ t[805] = 64364;
+ t[2424] = 7821;
+ t[2425] = 7822;
+ t[2426] = 7823;
+ t[2427] = 7824;
+ t[2428] = 7825;
+ t[2429] = 7826;
+ t[2430] = 7827;
+ t[2433] = 7682;
+ t[2678] = 8045;
+ t[2679] = 8046;
+ t[2830] = 1552;
+ t[2838] = 686;
+ t[2840] = 751;
+ t[2842] = 753;
+ t[2843] = 754;
+ t[2844] = 755;
+ t[2846] = 757;
+ t[2856] = 767;
+ t[2857] = 848;
+ t[2858] = 849;
+ t[2862] = 853;
+ t[2863] = 854;
+ t[2864] = 855;
+ t[2865] = 861;
+ t[2866] = 862;
+ t[2906] = 7460;
+ t[2908] = 7462;
+ t[2909] = 7463;
+ t[2910] = 7464;
+ t[2912] = 7466;
+ t[2913] = 7467;
+ t[2914] = 7468;
+ t[2916] = 7470;
+ t[2917] = 7471;
+ t[2918] = 7472;
+ t[2920] = 7474;
+ t[2921] = 7475;
+ t[2922] = 7476;
+ t[2924] = 7478;
+ t[2925] = 7479;
+ t[2926] = 7480;
+ t[2928] = 7482;
+ t[2929] = 7483;
+ t[2930] = 7484;
+ t[2932] = 7486;
+ t[2933] = 7487;
+ t[2934] = 7488;
+ t[2936] = 7490;
+ t[2937] = 7491;
+ t[2938] = 7492;
+ t[2940] = 7494;
+ t[2941] = 7495;
+ t[2942] = 7496;
+ t[2944] = 7498;
+ t[2946] = 7500;
+ t[2948] = 7502;
+ t[2950] = 7504;
+ t[2951] = 7505;
+ t[2952] = 7506;
+ t[2954] = 7508;
+ t[2955] = 7509;
+ t[2956] = 7510;
+ t[2958] = 7512;
+ t[2959] = 7513;
+ t[2960] = 7514;
+ t[2962] = 7516;
+ t[2963] = 7517;
+ t[2964] = 7518;
+ t[2966] = 7520;
+ t[2967] = 7521;
+ t[2968] = 7522;
+ t[2970] = 7524;
+ t[2971] = 7525;
+ t[2972] = 7526;
+ t[2974] = 7528;
+ t[2975] = 7529;
+ t[2976] = 7530;
+ t[2978] = 1537;
+ t[2979] = 1538;
+ t[2980] = 1539;
+ t[2982] = 1549;
+ t[2983] = 1551;
+ t[2984] = 1552;
+ t[2986] = 1554;
+ t[2987] = 1555;
+ t[2988] = 1556;
+ t[2990] = 1623;
+ t[2991] = 1624;
+ t[2995] = 1775;
+ t[2999] = 1791;
+ t[3002] = 64290;
+ t[3003] = 64291;
+ t[3004] = 64292;
+ t[3006] = 64294;
+ t[3007] = 64295;
+ t[3008] = 64296;
+ t[3011] = 1900;
+ t[3014] = 8223;
+ t[3015] = 8244;
+ t[3017] = 7532;
+ t[3018] = 7533;
+ t[3019] = 7534;
+ t[3075] = 7590;
+ t[3076] = 7591;
+ t[3079] = 7594;
+ t[3080] = 7595;
+ t[3083] = 7598;
+ t[3084] = 7599;
+ t[3087] = 7602;
+ t[3088] = 7603;
+ t[3091] = 7606;
+ t[3092] = 7607;
+ t[3095] = 7610;
+ t[3096] = 7611;
+ t[3099] = 7614;
+ t[3100] = 7615;
+ t[3103] = 7618;
+ t[3104] = 7619;
+ t[3107] = 8337;
+ t[3108] = 8338;
+ t[3116] = 1884;
+ t[3119] = 1885;
+ t[3120] = 1885;
+ t[3123] = 1886;
+ t[3124] = 1886;
+ t[3127] = 1887;
+ t[3128] = 1887;
+ t[3131] = 1888;
+ t[3132] = 1888;
+ t[3135] = 1889;
+ t[3136] = 1889;
+ t[3139] = 1890;
+ t[3140] = 1890;
+ t[3143] = 1891;
+ t[3144] = 1891;
+ t[3147] = 1892;
+ t[3148] = 1892;
+ t[3153] = 580;
+ t[3154] = 581;
+ t[3157] = 584;
+ t[3158] = 585;
+ t[3161] = 588;
+ t[3162] = 589;
+ t[3165] = 891;
+ t[3166] = 892;
+ t[3169] = 1274;
+ t[3170] = 1275;
+ t[3173] = 1278;
+ t[3174] = 1279;
+ t[3181] = 7622;
+ t[3182] = 7623;
+ t[3282] = 11799;
+ t[3316] = 578;
+ t[3379] = 42785;
+ t[3393] = 1159;
+ t[3416] = 8377;
+ });
+ // The glyph map for ArialBlack differs slightly from the glyph map used for
+ // other well-known standard fonts. Hence we use this (incomplete) CID to GID
+ // mapping to adjust the glyph map for non-embedded ArialBlack fonts.
+ var getSupplementalGlyphMapForArialBlack = getLookupTableFactory(function (t) {
+ t[227] = 322;
+ t[264] = 261;
+ t[291] = 346;
+ });
+ exports.getStdFontMap = getStdFontMap;
+ exports.getNonStdFontMap = getNonStdFontMap;
+ exports.getSerifFonts = getSerifFonts;
+ exports.getSymbolsFonts = getSymbolsFonts;
+ exports.getGlyphMapForStandardFonts = getGlyphMapForStandardFonts;
+ exports.getSupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreUnicode = {}, root.pdfjsSharedUtil);
+ }(this, function (exports, sharedUtil) {
+ var getLookupTableFactory = sharedUtil.getLookupTableFactory;
+ // Some characters, e.g. copyrightserif, are mapped to the private use area
+ // and might not be displayed using standard fonts. Mapping/hacking well-known
+ // chars to the similar equivalents in the normal characters range.
+ var getSpecialPUASymbols = getLookupTableFactory(function (t) {
+ t[63721] = 0x00A9;
+ // copyrightsans (0xF8E9) => copyright
+ t[63193] = 0x00A9;
+ // copyrightserif (0xF6D9) => copyright
+ t[63720] = 0x00AE;
+ // registersans (0xF8E8) => registered
+ t[63194] = 0x00AE;
+ // registerserif (0xF6DA) => registered
+ t[63722] = 0x2122;
+ // trademarksans (0xF8EA) => trademark
+ t[63195] = 0x2122;
+ // trademarkserif (0xF6DB) => trademark
+ t[63729] = 0x23A7;
+ // bracelefttp (0xF8F1)
+ t[63730] = 0x23A8;
+ // braceleftmid (0xF8F2)
+ t[63731] = 0x23A9;
+ // braceleftbt (0xF8F3)
+ t[63740] = 0x23AB;
+ // bracerighttp (0xF8FC)
+ t[63741] = 0x23AC;
+ // bracerightmid (0xF8FD)
+ t[63742] = 0x23AD;
+ // bracerightbt (0xF8FE)
+ t[63726] = 0x23A1;
+ // bracketlefttp (0xF8EE)
+ t[63727] = 0x23A2;
+ // bracketleftex (0xF8EF)
+ t[63728] = 0x23A3;
+ // bracketleftbt (0xF8F0)
+ t[63737] = 0x23A4;
+ // bracketrighttp (0xF8F9)
+ t[63738] = 0x23A5;
+ // bracketrightex (0xF8FA)
+ t[63739] = 0x23A6;
+ // bracketrightbt (0xF8FB)
+ t[63723] = 0x239B;
+ // parenlefttp (0xF8EB)
+ t[63724] = 0x239C;
+ // parenleftex (0xF8EC)
+ t[63725] = 0x239D;
+ // parenleftbt (0xF8ED)
+ t[63734] = 0x239E;
+ // parenrighttp (0xF8F6)
+ t[63735] = 0x239F;
+ // parenrightex (0xF8F7)
+ t[63736] = 0x23A0;
+ });
+ // parenrightbt (0xF8F8)
+ function mapSpecialUnicodeValues(code) {
+ if (code >= 0xFFF0 && code <= 0xFFFF) {
+ // Specials unicode block.
+ return 0;
+ } else if (code >= 0xF600 && code <= 0xF8FF) {
+ return getSpecialPUASymbols()[code] || code;
+ }
+ return code;
+ }
+ function getUnicodeForGlyph(name, glyphsUnicodeMap) {
+ var unicode = glyphsUnicodeMap[name];
+ if (unicode !== undefined) {
+ return unicode;
+ }
+ if (!name) {
+ return -1;
+ }
+ // Try to recover valid Unicode values from 'uniXXXX'/'uXXXX{XX}' glyphs.
+ if (name[0] === 'u') {
+ var nameLen = name.length, hexStr;
+ if (nameLen === 7 && name[1] === 'n' && name[2] === 'i') {
+ // 'uniXXXX'
+ hexStr = name.substr(3);
+ } else if (nameLen >= 5 && nameLen <= 7) {
+ // 'uXXXX{XX}'
+ hexStr = name.substr(1);
+ } else {
+ return -1;
+ }
+ // Check for upper-case hexadecimal characters, to avoid false positives.
+ if (hexStr === hexStr.toUpperCase()) {
+ unicode = parseInt(hexStr, 16);
+ if (unicode >= 0) {
+ return unicode;
+ }
+ }
+ }
+ return -1;
+ }
+ var UnicodeRanges = [
+ {
+ 'begin': 0x0000,
+ 'end': 0x007F
+ },
+ // Basic Latin
+ {
+ 'begin': 0x0080,
+ 'end': 0x00FF
+ },
+ // Latin-1 Supplement
+ {
+ 'begin': 0x0100,
+ 'end': 0x017F
+ },
+ // Latin Extended-A
+ {
+ 'begin': 0x0180,
+ 'end': 0x024F
+ },
+ // Latin Extended-B
+ {
+ 'begin': 0x0250,
+ 'end': 0x02AF
+ },
+ // IPA Extensions
+ {
+ 'begin': 0x02B0,
+ 'end': 0x02FF
+ },
+ // Spacing Modifier Letters
+ {
+ 'begin': 0x0300,
+ 'end': 0x036F
+ },
+ // Combining Diacritical Marks
+ {
+ 'begin': 0x0370,
+ 'end': 0x03FF
+ },
+ // Greek and Coptic
+ {
+ 'begin': 0x2C80,
+ 'end': 0x2CFF
+ },
+ // Coptic
+ {
+ 'begin': 0x0400,
+ 'end': 0x04FF
+ },
+ // Cyrillic
+ {
+ 'begin': 0x0530,
+ 'end': 0x058F
+ },
+ // Armenian
+ {
+ 'begin': 0x0590,
+ 'end': 0x05FF
+ },
+ // Hebrew
+ {
+ 'begin': 0xA500,
+ 'end': 0xA63F
+ },
+ // Vai
+ {
+ 'begin': 0x0600,
+ 'end': 0x06FF
+ },
+ // Arabic
+ {
+ 'begin': 0x07C0,
+ 'end': 0x07FF
+ },
+ // NKo
+ {
+ 'begin': 0x0900,
+ 'end': 0x097F
+ },
+ // Devanagari
+ {
+ 'begin': 0x0980,
+ 'end': 0x09FF
+ },
+ // Bengali
+ {
+ 'begin': 0x0A00,
+ 'end': 0x0A7F
+ },
+ // Gurmukhi
+ {
+ 'begin': 0x0A80,
+ 'end': 0x0AFF
+ },
+ // Gujarati
+ {
+ 'begin': 0x0B00,
+ 'end': 0x0B7F
+ },
+ // Oriya
+ {
+ 'begin': 0x0B80,
+ 'end': 0x0BFF
+ },
+ // Tamil
+ {
+ 'begin': 0x0C00,
+ 'end': 0x0C7F
+ },
+ // Telugu
+ {
+ 'begin': 0x0C80,
+ 'end': 0x0CFF
+ },
+ // Kannada
+ {
+ 'begin': 0x0D00,
+ 'end': 0x0D7F
+ },
+ // Malayalam
+ {
+ 'begin': 0x0E00,
+ 'end': 0x0E7F
+ },
+ // Thai
+ {
+ 'begin': 0x0E80,
+ 'end': 0x0EFF
+ },
+ // Lao
+ {
+ 'begin': 0x10A0,
+ 'end': 0x10FF
+ },
+ // Georgian
+ {
+ 'begin': 0x1B00,
+ 'end': 0x1B7F
+ },
+ // Balinese
+ {
+ 'begin': 0x1100,
+ 'end': 0x11FF
+ },
+ // Hangul Jamo
+ {
+ 'begin': 0x1E00,
+ 'end': 0x1EFF
+ },
+ // Latin Extended Additional
+ {
+ 'begin': 0x1F00,
+ 'end': 0x1FFF
+ },
+ // Greek Extended
+ {
+ 'begin': 0x2000,
+ 'end': 0x206F
+ },
+ // General Punctuation
+ {
+ 'begin': 0x2070,
+ 'end': 0x209F
+ },
+ // Superscripts And Subscripts
+ {
+ 'begin': 0x20A0,
+ 'end': 0x20CF
+ },
+ // Currency Symbol
+ {
+ 'begin': 0x20D0,
+ 'end': 0x20FF
+ },
+ // Combining Diacritical Marks
+ {
+ 'begin': 0x2100,
+ 'end': 0x214F
+ },
+ // Letterlike Symbols
+ {
+ 'begin': 0x2150,
+ 'end': 0x218F
+ },
+ // Number Forms
+ {
+ 'begin': 0x2190,
+ 'end': 0x21FF
+ },
+ // Arrows
+ {
+ 'begin': 0x2200,
+ 'end': 0x22FF
+ },
+ // Mathematical Operators
+ {
+ 'begin': 0x2300,
+ 'end': 0x23FF
+ },
+ // Miscellaneous Technical
+ {
+ 'begin': 0x2400,
+ 'end': 0x243F
+ },
+ // Control Pictures
+ {
+ 'begin': 0x2440,
+ 'end': 0x245F
+ },
+ // Optical Character Recognition
+ {
+ 'begin': 0x2460,
+ 'end': 0x24FF
+ },
+ // Enclosed Alphanumerics
+ {
+ 'begin': 0x2500,
+ 'end': 0x257F
+ },
+ // Box Drawing
+ {
+ 'begin': 0x2580,
+ 'end': 0x259F
+ },
+ // Block Elements
+ {
+ 'begin': 0x25A0,
+ 'end': 0x25FF
+ },
+ // Geometric Shapes
+ {
+ 'begin': 0x2600,
+ 'end': 0x26FF
+ },
+ // Miscellaneous Symbols
+ {
+ 'begin': 0x2700,
+ 'end': 0x27BF
+ },
+ // Dingbats
+ {
+ 'begin': 0x3000,
+ 'end': 0x303F
+ },
+ // CJK Symbols And Punctuation
+ {
+ 'begin': 0x3040,
+ 'end': 0x309F
+ },
+ // Hiragana
+ {
+ 'begin': 0x30A0,
+ 'end': 0x30FF
+ },
+ // Katakana
+ {
+ 'begin': 0x3100,
+ 'end': 0x312F
+ },
+ // Bopomofo
+ {
+ 'begin': 0x3130,
+ 'end': 0x318F
+ },
+ // Hangul Compatibility Jamo
+ {
+ 'begin': 0xA840,
+ 'end': 0xA87F
+ },
+ // Phags-pa
+ {
+ 'begin': 0x3200,
+ 'end': 0x32FF
+ },
+ // Enclosed CJK Letters And Months
+ {
+ 'begin': 0x3300,
+ 'end': 0x33FF
+ },
+ // CJK Compatibility
+ {
+ 'begin': 0xAC00,
+ 'end': 0xD7AF
+ },
+ // Hangul Syllables
+ {
+ 'begin': 0xD800,
+ 'end': 0xDFFF
+ },
+ // Non-Plane 0 *
+ {
+ 'begin': 0x10900,
+ 'end': 0x1091F
+ },
+ // Phoenicia
+ {
+ 'begin': 0x4E00,
+ 'end': 0x9FFF
+ },
+ // CJK Unified Ideographs
+ {
+ 'begin': 0xE000,
+ 'end': 0xF8FF
+ },
+ // Private Use Area (plane 0)
+ {
+ 'begin': 0x31C0,
+ 'end': 0x31EF
+ },
+ // CJK Strokes
+ {
+ 'begin': 0xFB00,
+ 'end': 0xFB4F
+ },
+ // Alphabetic Presentation Forms
+ {
+ 'begin': 0xFB50,
+ 'end': 0xFDFF
+ },
+ // Arabic Presentation Forms-A
+ {
+ 'begin': 0xFE20,
+ 'end': 0xFE2F
+ },
+ // Combining Half Marks
+ {
+ 'begin': 0xFE10,
+ 'end': 0xFE1F
+ },
+ // Vertical Forms
+ {
+ 'begin': 0xFE50,
+ 'end': 0xFE6F
+ },
+ // Small Form Variants
+ {
+ 'begin': 0xFE70,
+ 'end': 0xFEFF
+ },
+ // Arabic Presentation Forms-B
+ {
+ 'begin': 0xFF00,
+ 'end': 0xFFEF
+ },
+ // Halfwidth And Fullwidth Forms
+ {
+ 'begin': 0xFFF0,
+ 'end': 0xFFFF
+ },
+ // Specials
+ {
+ 'begin': 0x0F00,
+ 'end': 0x0FFF
+ },
+ // Tibetan
+ {
+ 'begin': 0x0700,
+ 'end': 0x074F
+ },
+ // Syriac
+ {
+ 'begin': 0x0780,
+ 'end': 0x07BF
+ },
+ // Thaana
+ {
+ 'begin': 0x0D80,
+ 'end': 0x0DFF
+ },
+ // Sinhala
+ {
+ 'begin': 0x1000,
+ 'end': 0x109F
+ },
+ // Myanmar
+ {
+ 'begin': 0x1200,
+ 'end': 0x137F
+ },
+ // Ethiopic
+ {
+ 'begin': 0x13A0,
+ 'end': 0x13FF
+ },
+ // Cherokee
+ {
+ 'begin': 0x1400,
+ 'end': 0x167F
+ },
+ // Unified Canadian Aboriginal Syllabics
+ {
+ 'begin': 0x1680,
+ 'end': 0x169F
+ },
+ // Ogham
+ {
+ 'begin': 0x16A0,
+ 'end': 0x16FF
+ },
+ // Runic
+ {
+ 'begin': 0x1780,
+ 'end': 0x17FF
+ },
+ // Khmer
+ {
+ 'begin': 0x1800,
+ 'end': 0x18AF
+ },
+ // Mongolian
+ {
+ 'begin': 0x2800,
+ 'end': 0x28FF
+ },
+ // Braille Patterns
+ {
+ 'begin': 0xA000,
+ 'end': 0xA48F
+ },
+ // Yi Syllables
+ {
+ 'begin': 0x1700,
+ 'end': 0x171F
+ },
+ // Tagalog
+ {
+ 'begin': 0x10300,
+ 'end': 0x1032F
+ },
+ // Old Italic
+ {
+ 'begin': 0x10330,
+ 'end': 0x1034F
+ },
+ // Gothic
+ {
+ 'begin': 0x10400,
+ 'end': 0x1044F
+ },
+ // Deseret
+ {
+ 'begin': 0x1D000,
+ 'end': 0x1D0FF
+ },
+ // Byzantine Musical Symbols
+ {
+ 'begin': 0x1D400,
+ 'end': 0x1D7FF
+ },
+ // Mathematical Alphanumeric Symbols
+ {
+ 'begin': 0xFF000,
+ 'end': 0xFFFFD
+ },
+ // Private Use (plane 15)
+ {
+ 'begin': 0xFE00,
+ 'end': 0xFE0F
+ },
+ // Variation Selectors
+ {
+ 'begin': 0xE0000,
+ 'end': 0xE007F
+ },
+ // Tags
+ {
+ 'begin': 0x1900,
+ 'end': 0x194F
+ },
+ // Limbu
+ {
+ 'begin': 0x1950,
+ 'end': 0x197F
+ },
+ // Tai Le
+ {
+ 'begin': 0x1980,
+ 'end': 0x19DF
+ },
+ // New Tai Lue
+ {
+ 'begin': 0x1A00,
+ 'end': 0x1A1F
+ },
+ // Buginese
+ {
+ 'begin': 0x2C00,
+ 'end': 0x2C5F
+ },
+ // Glagolitic
+ {
+ 'begin': 0x2D30,
+ 'end': 0x2D7F
+ },
+ // Tifinagh
+ {
+ 'begin': 0x4DC0,
+ 'end': 0x4DFF
+ },
+ // Yijing Hexagram Symbols
+ {
+ 'begin': 0xA800,
+ 'end': 0xA82F
+ },
+ // Syloti Nagri
+ {
+ 'begin': 0x10000,
+ 'end': 0x1007F
+ },
+ // Linear B Syllabary
+ {
+ 'begin': 0x10140,
+ 'end': 0x1018F
+ },
+ // Ancient Greek Numbers
+ {
+ 'begin': 0x10380,
+ 'end': 0x1039F
+ },
+ // Ugaritic
+ {
+ 'begin': 0x103A0,
+ 'end': 0x103DF
+ },
+ // Old Persian
+ {
+ 'begin': 0x10450,
+ 'end': 0x1047F
+ },
+ // Shavian
+ {
+ 'begin': 0x10480,
+ 'end': 0x104AF
+ },
+ // Osmanya
+ {
+ 'begin': 0x10800,
+ 'end': 0x1083F
+ },
+ // Cypriot Syllabary
+ {
+ 'begin': 0x10A00,
+ 'end': 0x10A5F
+ },
+ // Kharoshthi
+ {
+ 'begin': 0x1D300,
+ 'end': 0x1D35F
+ },
+ // Tai Xuan Jing Symbols
+ {
+ 'begin': 0x12000,
+ 'end': 0x123FF
+ },
+ // Cuneiform
+ {
+ 'begin': 0x1D360,
+ 'end': 0x1D37F
+ },
+ // Counting Rod Numerals
+ {
+ 'begin': 0x1B80,
+ 'end': 0x1BBF
+ },
+ // Sundanese
+ {
+ 'begin': 0x1C00,
+ 'end': 0x1C4F
+ },
+ // Lepcha
+ {
+ 'begin': 0x1C50,
+ 'end': 0x1C7F
+ },
+ // Ol Chiki
+ {
+ 'begin': 0xA880,
+ 'end': 0xA8DF
+ },
+ // Saurashtra
+ {
+ 'begin': 0xA900,
+ 'end': 0xA92F
+ },
+ // Kayah Li
+ {
+ 'begin': 0xA930,
+ 'end': 0xA95F
+ },
+ // Rejang
+ {
+ 'begin': 0xAA00,
+ 'end': 0xAA5F
+ },
+ // Cham
+ {
+ 'begin': 0x10190,
+ 'end': 0x101CF
+ },
+ // Ancient Symbols
+ {
+ 'begin': 0x101D0,
+ 'end': 0x101FF
+ },
+ // Phaistos Disc
+ {
+ 'begin': 0x102A0,
+ 'end': 0x102DF
+ },
+ // Carian
+ {
+ 'begin': 0x1F030,
+ 'end': 0x1F09F
+ }
+ ];
+ // Domino Tiles
+ function getUnicodeRangeFor(value) {
+ for (var i = 0, ii = UnicodeRanges.length; i < ii; i++) {
+ var range = UnicodeRanges[i];
+ if (value >= range.begin && value < range.end) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ function isRTLRangeFor(value) {
+ var range = UnicodeRanges[13];
+ if (value >= range.begin && value < range.end) {
+ return true;
+ }
+ range = UnicodeRanges[11];
+ if (value >= range.begin && value < range.end) {
+ return true;
+ }
+ return false;
+ }
+ // The normalization table is obtained by filtering the Unicode characters
+ // database with <compat> entries.
+ var getNormalizedUnicodes = getLookupTableFactory(function (t) {
+ t['\u00A8'] = '\u0020\u0308';
+ t['\u00AF'] = '\u0020\u0304';
+ t['\u00B4'] = '\u0020\u0301';
+ t['\u00B5'] = '\u03BC';
+ t['\u00B8'] = '\u0020\u0327';
+ t['\u0132'] = '\u0049\u004A';
+ t['\u0133'] = '\u0069\u006A';
+ t['\u013F'] = '\u004C\u00B7';
+ t['\u0140'] = '\u006C\u00B7';
+ t['\u0149'] = '\u02BC\u006E';
+ t['\u017F'] = '\u0073';
+ t['\u01C4'] = '\u0044\u017D';
+ t['\u01C5'] = '\u0044\u017E';
+ t['\u01C6'] = '\u0064\u017E';
+ t['\u01C7'] = '\u004C\u004A';
+ t['\u01C8'] = '\u004C\u006A';
+ t['\u01C9'] = '\u006C\u006A';
+ t['\u01CA'] = '\u004E\u004A';
+ t['\u01CB'] = '\u004E\u006A';
+ t['\u01CC'] = '\u006E\u006A';
+ t['\u01F1'] = '\u0044\u005A';
+ t['\u01F2'] = '\u0044\u007A';
+ t['\u01F3'] = '\u0064\u007A';
+ t['\u02D8'] = '\u0020\u0306';
+ t['\u02D9'] = '\u0020\u0307';
+ t['\u02DA'] = '\u0020\u030A';
+ t['\u02DB'] = '\u0020\u0328';
+ t['\u02DC'] = '\u0020\u0303';
+ t['\u02DD'] = '\u0020\u030B';
+ t['\u037A'] = '\u0020\u0345';
+ t['\u0384'] = '\u0020\u0301';
+ t['\u03D0'] = '\u03B2';
+ t['\u03D1'] = '\u03B8';
+ t['\u03D2'] = '\u03A5';
+ t['\u03D5'] = '\u03C6';
+ t['\u03D6'] = '\u03C0';
+ t['\u03F0'] = '\u03BA';
+ t['\u03F1'] = '\u03C1';
+ t['\u03F2'] = '\u03C2';
+ t['\u03F4'] = '\u0398';
+ t['\u03F5'] = '\u03B5';
+ t['\u03F9'] = '\u03A3';
+ t['\u0587'] = '\u0565\u0582';
+ t['\u0675'] = '\u0627\u0674';
+ t['\u0676'] = '\u0648\u0674';
+ t['\u0677'] = '\u06C7\u0674';
+ t['\u0678'] = '\u064A\u0674';
+ t['\u0E33'] = '\u0E4D\u0E32';
+ t['\u0EB3'] = '\u0ECD\u0EB2';
+ t['\u0EDC'] = '\u0EAB\u0E99';
+ t['\u0EDD'] = '\u0EAB\u0EA1';
+ t['\u0F77'] = '\u0FB2\u0F81';
+ t['\u0F79'] = '\u0FB3\u0F81';
+ t['\u1E9A'] = '\u0061\u02BE';
+ t['\u1FBD'] = '\u0020\u0313';
+ t['\u1FBF'] = '\u0020\u0313';
+ t['\u1FC0'] = '\u0020\u0342';
+ t['\u1FFE'] = '\u0020\u0314';
+ t['\u2002'] = '\u0020';
+ t['\u2003'] = '\u0020';
+ t['\u2004'] = '\u0020';
+ t['\u2005'] = '\u0020';
+ t['\u2006'] = '\u0020';
+ t['\u2008'] = '\u0020';
+ t['\u2009'] = '\u0020';
+ t['\u200A'] = '\u0020';
+ t['\u2017'] = '\u0020\u0333';
+ t['\u2024'] = '\u002E';
+ t['\u2025'] = '\u002E\u002E';
+ t['\u2026'] = '\u002E\u002E\u002E';
+ t['\u2033'] = '\u2032\u2032';
+ t['\u2034'] = '\u2032\u2032\u2032';
+ t['\u2036'] = '\u2035\u2035';
+ t['\u2037'] = '\u2035\u2035\u2035';
+ t['\u203C'] = '\u0021\u0021';
+ t['\u203E'] = '\u0020\u0305';
+ t['\u2047'] = '\u003F\u003F';
+ t['\u2048'] = '\u003F\u0021';
+ t['\u2049'] = '\u0021\u003F';
+ t['\u2057'] = '\u2032\u2032\u2032\u2032';
+ t['\u205F'] = '\u0020';
+ t['\u20A8'] = '\u0052\u0073';
+ t['\u2100'] = '\u0061\u002F\u0063';
+ t['\u2101'] = '\u0061\u002F\u0073';
+ t['\u2103'] = '\u00B0\u0043';
+ t['\u2105'] = '\u0063\u002F\u006F';
+ t['\u2106'] = '\u0063\u002F\u0075';
+ t['\u2107'] = '\u0190';
+ t['\u2109'] = '\u00B0\u0046';
+ t['\u2116'] = '\u004E\u006F';
+ t['\u2121'] = '\u0054\u0045\u004C';
+ t['\u2135'] = '\u05D0';
+ t['\u2136'] = '\u05D1';
+ t['\u2137'] = '\u05D2';
+ t['\u2138'] = '\u05D3';
+ t['\u213B'] = '\u0046\u0041\u0058';
+ t['\u2160'] = '\u0049';
+ t['\u2161'] = '\u0049\u0049';
+ t['\u2162'] = '\u0049\u0049\u0049';
+ t['\u2163'] = '\u0049\u0056';
+ t['\u2164'] = '\u0056';
+ t['\u2165'] = '\u0056\u0049';
+ t['\u2166'] = '\u0056\u0049\u0049';
+ t['\u2167'] = '\u0056\u0049\u0049\u0049';
+ t['\u2168'] = '\u0049\u0058';
+ t['\u2169'] = '\u0058';
+ t['\u216A'] = '\u0058\u0049';
+ t['\u216B'] = '\u0058\u0049\u0049';
+ t['\u216C'] = '\u004C';
+ t['\u216D'] = '\u0043';
+ t['\u216E'] = '\u0044';
+ t['\u216F'] = '\u004D';
+ t['\u2170'] = '\u0069';
+ t['\u2171'] = '\u0069\u0069';
+ t['\u2172'] = '\u0069\u0069\u0069';
+ t['\u2173'] = '\u0069\u0076';
+ t['\u2174'] = '\u0076';
+ t['\u2175'] = '\u0076\u0069';
+ t['\u2176'] = '\u0076\u0069\u0069';
+ t['\u2177'] = '\u0076\u0069\u0069\u0069';
+ t['\u2178'] = '\u0069\u0078';
+ t['\u2179'] = '\u0078';
+ t['\u217A'] = '\u0078\u0069';
+ t['\u217B'] = '\u0078\u0069\u0069';
+ t['\u217C'] = '\u006C';
+ t['\u217D'] = '\u0063';
+ t['\u217E'] = '\u0064';
+ t['\u217F'] = '\u006D';
+ t['\u222C'] = '\u222B\u222B';
+ t['\u222D'] = '\u222B\u222B\u222B';
+ t['\u222F'] = '\u222E\u222E';
+ t['\u2230'] = '\u222E\u222E\u222E';
+ t['\u2474'] = '\u0028\u0031\u0029';
+ t['\u2475'] = '\u0028\u0032\u0029';
+ t['\u2476'] = '\u0028\u0033\u0029';
+ t['\u2477'] = '\u0028\u0034\u0029';
+ t['\u2478'] = '\u0028\u0035\u0029';
+ t['\u2479'] = '\u0028\u0036\u0029';
+ t['\u247A'] = '\u0028\u0037\u0029';
+ t['\u247B'] = '\u0028\u0038\u0029';
+ t['\u247C'] = '\u0028\u0039\u0029';
+ t['\u247D'] = '\u0028\u0031\u0030\u0029';
+ t['\u247E'] = '\u0028\u0031\u0031\u0029';
+ t['\u247F'] = '\u0028\u0031\u0032\u0029';
+ t['\u2480'] = '\u0028\u0031\u0033\u0029';
+ t['\u2481'] = '\u0028\u0031\u0034\u0029';
+ t['\u2482'] = '\u0028\u0031\u0035\u0029';
+ t['\u2483'] = '\u0028\u0031\u0036\u0029';
+ t['\u2484'] = '\u0028\u0031\u0037\u0029';
+ t['\u2485'] = '\u0028\u0031\u0038\u0029';
+ t['\u2486'] = '\u0028\u0031\u0039\u0029';
+ t['\u2487'] = '\u0028\u0032\u0030\u0029';
+ t['\u2488'] = '\u0031\u002E';
+ t['\u2489'] = '\u0032\u002E';
+ t['\u248A'] = '\u0033\u002E';
+ t['\u248B'] = '\u0034\u002E';
+ t['\u248C'] = '\u0035\u002E';
+ t['\u248D'] = '\u0036\u002E';
+ t['\u248E'] = '\u0037\u002E';
+ t['\u248F'] = '\u0038\u002E';
+ t['\u2490'] = '\u0039\u002E';
+ t['\u2491'] = '\u0031\u0030\u002E';
+ t['\u2492'] = '\u0031\u0031\u002E';
+ t['\u2493'] = '\u0031\u0032\u002E';
+ t['\u2494'] = '\u0031\u0033\u002E';
+ t['\u2495'] = '\u0031\u0034\u002E';
+ t['\u2496'] = '\u0031\u0035\u002E';
+ t['\u2497'] = '\u0031\u0036\u002E';
+ t['\u2498'] = '\u0031\u0037\u002E';
+ t['\u2499'] = '\u0031\u0038\u002E';
+ t['\u249A'] = '\u0031\u0039\u002E';
+ t['\u249B'] = '\u0032\u0030\u002E';
+ t['\u249C'] = '\u0028\u0061\u0029';
+ t['\u249D'] = '\u0028\u0062\u0029';
+ t['\u249E'] = '\u0028\u0063\u0029';
+ t['\u249F'] = '\u0028\u0064\u0029';
+ t['\u24A0'] = '\u0028\u0065\u0029';
+ t['\u24A1'] = '\u0028\u0066\u0029';
+ t['\u24A2'] = '\u0028\u0067\u0029';
+ t['\u24A3'] = '\u0028\u0068\u0029';
+ t['\u24A4'] = '\u0028\u0069\u0029';
+ t['\u24A5'] = '\u0028\u006A\u0029';
+ t['\u24A6'] = '\u0028\u006B\u0029';
+ t['\u24A7'] = '\u0028\u006C\u0029';
+ t['\u24A8'] = '\u0028\u006D\u0029';
+ t['\u24A9'] = '\u0028\u006E\u0029';
+ t['\u24AA'] = '\u0028\u006F\u0029';
+ t['\u24AB'] = '\u0028\u0070\u0029';
+ t['\u24AC'] = '\u0028\u0071\u0029';
+ t['\u24AD'] = '\u0028\u0072\u0029';
+ t['\u24AE'] = '\u0028\u0073\u0029';
+ t['\u24AF'] = '\u0028\u0074\u0029';
+ t['\u24B0'] = '\u0028\u0075\u0029';
+ t['\u24B1'] = '\u0028\u0076\u0029';
+ t['\u24B2'] = '\u0028\u0077\u0029';
+ t['\u24B3'] = '\u0028\u0078\u0029';
+ t['\u24B4'] = '\u0028\u0079\u0029';
+ t['\u24B5'] = '\u0028\u007A\u0029';
+ t['\u2A0C'] = '\u222B\u222B\u222B\u222B';
+ t['\u2A74'] = '\u003A\u003A\u003D';
+ t['\u2A75'] = '\u003D\u003D';
+ t['\u2A76'] = '\u003D\u003D\u003D';
+ t['\u2E9F'] = '\u6BCD';
+ t['\u2EF3'] = '\u9F9F';
+ t['\u2F00'] = '\u4E00';
+ t['\u2F01'] = '\u4E28';
+ t['\u2F02'] = '\u4E36';
+ t['\u2F03'] = '\u4E3F';
+ t['\u2F04'] = '\u4E59';
+ t['\u2F05'] = '\u4E85';
+ t['\u2F06'] = '\u4E8C';
+ t['\u2F07'] = '\u4EA0';
+ t['\u2F08'] = '\u4EBA';
+ t['\u2F09'] = '\u513F';
+ t['\u2F0A'] = '\u5165';
+ t['\u2F0B'] = '\u516B';
+ t['\u2F0C'] = '\u5182';
+ t['\u2F0D'] = '\u5196';
+ t['\u2F0E'] = '\u51AB';
+ t['\u2F0F'] = '\u51E0';
+ t['\u2F10'] = '\u51F5';
+ t['\u2F11'] = '\u5200';
+ t['\u2F12'] = '\u529B';
+ t['\u2F13'] = '\u52F9';
+ t['\u2F14'] = '\u5315';
+ t['\u2F15'] = '\u531A';
+ t['\u2F16'] = '\u5338';
+ t['\u2F17'] = '\u5341';
+ t['\u2F18'] = '\u535C';
+ t['\u2F19'] = '\u5369';
+ t['\u2F1A'] = '\u5382';
+ t['\u2F1B'] = '\u53B6';
+ t['\u2F1C'] = '\u53C8';
+ t['\u2F1D'] = '\u53E3';
+ t['\u2F1E'] = '\u56D7';
+ t['\u2F1F'] = '\u571F';
+ t['\u2F20'] = '\u58EB';
+ t['\u2F21'] = '\u5902';
+ t['\u2F22'] = '\u590A';
+ t['\u2F23'] = '\u5915';
+ t['\u2F24'] = '\u5927';
+ t['\u2F25'] = '\u5973';
+ t['\u2F26'] = '\u5B50';
+ t['\u2F27'] = '\u5B80';
+ t['\u2F28'] = '\u5BF8';
+ t['\u2F29'] = '\u5C0F';
+ t['\u2F2A'] = '\u5C22';
+ t['\u2F2B'] = '\u5C38';
+ t['\u2F2C'] = '\u5C6E';
+ t['\u2F2D'] = '\u5C71';
+ t['\u2F2E'] = '\u5DDB';
+ t['\u2F2F'] = '\u5DE5';
+ t['\u2F30'] = '\u5DF1';
+ t['\u2F31'] = '\u5DFE';
+ t['\u2F32'] = '\u5E72';
+ t['\u2F33'] = '\u5E7A';
+ t['\u2F34'] = '\u5E7F';
+ t['\u2F35'] = '\u5EF4';
+ t['\u2F36'] = '\u5EFE';
+ t['\u2F37'] = '\u5F0B';
+ t['\u2F38'] = '\u5F13';
+ t['\u2F39'] = '\u5F50';
+ t['\u2F3A'] = '\u5F61';
+ t['\u2F3B'] = '\u5F73';
+ t['\u2F3C'] = '\u5FC3';
+ t['\u2F3D'] = '\u6208';
+ t['\u2F3E'] = '\u6236';
+ t['\u2F3F'] = '\u624B';
+ t['\u2F40'] = '\u652F';
+ t['\u2F41'] = '\u6534';
+ t['\u2F42'] = '\u6587';
+ t['\u2F43'] = '\u6597';
+ t['\u2F44'] = '\u65A4';
+ t['\u2F45'] = '\u65B9';
+ t['\u2F46'] = '\u65E0';
+ t['\u2F47'] = '\u65E5';
+ t['\u2F48'] = '\u66F0';
+ t['\u2F49'] = '\u6708';
+ t['\u2F4A'] = '\u6728';
+ t['\u2F4B'] = '\u6B20';
+ t['\u2F4C'] = '\u6B62';
+ t['\u2F4D'] = '\u6B79';
+ t['\u2F4E'] = '\u6BB3';
+ t['\u2F4F'] = '\u6BCB';
+ t['\u2F50'] = '\u6BD4';
+ t['\u2F51'] = '\u6BDB';
+ t['\u2F52'] = '\u6C0F';
+ t['\u2F53'] = '\u6C14';
+ t['\u2F54'] = '\u6C34';
+ t['\u2F55'] = '\u706B';
+ t['\u2F56'] = '\u722A';
+ t['\u2F57'] = '\u7236';
+ t['\u2F58'] = '\u723B';
+ t['\u2F59'] = '\u723F';
+ t['\u2F5A'] = '\u7247';
+ t['\u2F5B'] = '\u7259';
+ t['\u2F5C'] = '\u725B';
+ t['\u2F5D'] = '\u72AC';
+ t['\u2F5E'] = '\u7384';
+ t['\u2F5F'] = '\u7389';
+ t['\u2F60'] = '\u74DC';
+ t['\u2F61'] = '\u74E6';
+ t['\u2F62'] = '\u7518';
+ t['\u2F63'] = '\u751F';
+ t['\u2F64'] = '\u7528';
+ t['\u2F65'] = '\u7530';
+ t['\u2F66'] = '\u758B';
+ t['\u2F67'] = '\u7592';
+ t['\u2F68'] = '\u7676';
+ t['\u2F69'] = '\u767D';
+ t['\u2F6A'] = '\u76AE';
+ t['\u2F6B'] = '\u76BF';
+ t['\u2F6C'] = '\u76EE';
+ t['\u2F6D'] = '\u77DB';
+ t['\u2F6E'] = '\u77E2';
+ t['\u2F6F'] = '\u77F3';
+ t['\u2F70'] = '\u793A';
+ t['\u2F71'] = '\u79B8';
+ t['\u2F72'] = '\u79BE';
+ t['\u2F73'] = '\u7A74';
+ t['\u2F74'] = '\u7ACB';
+ t['\u2F75'] = '\u7AF9';
+ t['\u2F76'] = '\u7C73';
+ t['\u2F77'] = '\u7CF8';
+ t['\u2F78'] = '\u7F36';
+ t['\u2F79'] = '\u7F51';
+ t['\u2F7A'] = '\u7F8A';
+ t['\u2F7B'] = '\u7FBD';
+ t['\u2F7C'] = '\u8001';
+ t['\u2F7D'] = '\u800C';
+ t['\u2F7E'] = '\u8012';
+ t['\u2F7F'] = '\u8033';
+ t['\u2F80'] = '\u807F';
+ t['\u2F81'] = '\u8089';
+ t['\u2F82'] = '\u81E3';
+ t['\u2F83'] = '\u81EA';
+ t['\u2F84'] = '\u81F3';
+ t['\u2F85'] = '\u81FC';
+ t['\u2F86'] = '\u820C';
+ t['\u2F87'] = '\u821B';
+ t['\u2F88'] = '\u821F';
+ t['\u2F89'] = '\u826E';
+ t['\u2F8A'] = '\u8272';
+ t['\u2F8B'] = '\u8278';
+ t['\u2F8C'] = '\u864D';
+ t['\u2F8D'] = '\u866B';
+ t['\u2F8E'] = '\u8840';
+ t['\u2F8F'] = '\u884C';
+ t['\u2F90'] = '\u8863';
+ t['\u2F91'] = '\u897E';
+ t['\u2F92'] = '\u898B';
+ t['\u2F93'] = '\u89D2';
+ t['\u2F94'] = '\u8A00';
+ t['\u2F95'] = '\u8C37';
+ t['\u2F96'] = '\u8C46';
+ t['\u2F97'] = '\u8C55';
+ t['\u2F98'] = '\u8C78';
+ t['\u2F99'] = '\u8C9D';
+ t['\u2F9A'] = '\u8D64';
+ t['\u2F9B'] = '\u8D70';
+ t['\u2F9C'] = '\u8DB3';
+ t['\u2F9D'] = '\u8EAB';
+ t['\u2F9E'] = '\u8ECA';
+ t['\u2F9F'] = '\u8F9B';
+ t['\u2FA0'] = '\u8FB0';
+ t['\u2FA1'] = '\u8FB5';
+ t['\u2FA2'] = '\u9091';
+ t['\u2FA3'] = '\u9149';
+ t['\u2FA4'] = '\u91C6';
+ t['\u2FA5'] = '\u91CC';
+ t['\u2FA6'] = '\u91D1';
+ t['\u2FA7'] = '\u9577';
+ t['\u2FA8'] = '\u9580';
+ t['\u2FA9'] = '\u961C';
+ t['\u2FAA'] = '\u96B6';
+ t['\u2FAB'] = '\u96B9';
+ t['\u2FAC'] = '\u96E8';
+ t['\u2FAD'] = '\u9751';
+ t['\u2FAE'] = '\u975E';
+ t['\u2FAF'] = '\u9762';
+ t['\u2FB0'] = '\u9769';
+ t['\u2FB1'] = '\u97CB';
+ t['\u2FB2'] = '\u97ED';
+ t['\u2FB3'] = '\u97F3';
+ t['\u2FB4'] = '\u9801';
+ t['\u2FB5'] = '\u98A8';
+ t['\u2FB6'] = '\u98DB';
+ t['\u2FB7'] = '\u98DF';
+ t['\u2FB8'] = '\u9996';
+ t['\u2FB9'] = '\u9999';
+ t['\u2FBA'] = '\u99AC';
+ t['\u2FBB'] = '\u9AA8';
+ t['\u2FBC'] = '\u9AD8';
+ t['\u2FBD'] = '\u9ADF';
+ t['\u2FBE'] = '\u9B25';
+ t['\u2FBF'] = '\u9B2F';
+ t['\u2FC0'] = '\u9B32';
+ t['\u2FC1'] = '\u9B3C';
+ t['\u2FC2'] = '\u9B5A';
+ t['\u2FC3'] = '\u9CE5';
+ t['\u2FC4'] = '\u9E75';
+ t['\u2FC5'] = '\u9E7F';
+ t['\u2FC6'] = '\u9EA5';
+ t['\u2FC7'] = '\u9EBB';
+ t['\u2FC8'] = '\u9EC3';
+ t['\u2FC9'] = '\u9ECD';
+ t['\u2FCA'] = '\u9ED1';
+ t['\u2FCB'] = '\u9EF9';
+ t['\u2FCC'] = '\u9EFD';
+ t['\u2FCD'] = '\u9F0E';
+ t['\u2FCE'] = '\u9F13';
+ t['\u2FCF'] = '\u9F20';
+ t['\u2FD0'] = '\u9F3B';
+ t['\u2FD1'] = '\u9F4A';
+ t['\u2FD2'] = '\u9F52';
+ t['\u2FD3'] = '\u9F8D';
+ t['\u2FD4'] = '\u9F9C';
+ t['\u2FD5'] = '\u9FA0';
+ t['\u3036'] = '\u3012';
+ t['\u3038'] = '\u5341';
+ t['\u3039'] = '\u5344';
+ t['\u303A'] = '\u5345';
+ t['\u309B'] = '\u0020\u3099';
+ t['\u309C'] = '\u0020\u309A';
+ t['\u3131'] = '\u1100';
+ t['\u3132'] = '\u1101';
+ t['\u3133'] = '\u11AA';
+ t['\u3134'] = '\u1102';
+ t['\u3135'] = '\u11AC';
+ t['\u3136'] = '\u11AD';
+ t['\u3137'] = '\u1103';
+ t['\u3138'] = '\u1104';
+ t['\u3139'] = '\u1105';
+ t['\u313A'] = '\u11B0';
+ t['\u313B'] = '\u11B1';
+ t['\u313C'] = '\u11B2';
+ t['\u313D'] = '\u11B3';
+ t['\u313E'] = '\u11B4';
+ t['\u313F'] = '\u11B5';
+ t['\u3140'] = '\u111A';
+ t['\u3141'] = '\u1106';
+ t['\u3142'] = '\u1107';
+ t['\u3143'] = '\u1108';
+ t['\u3144'] = '\u1121';
+ t['\u3145'] = '\u1109';
+ t['\u3146'] = '\u110A';
+ t['\u3147'] = '\u110B';
+ t['\u3148'] = '\u110C';
+ t['\u3149'] = '\u110D';
+ t['\u314A'] = '\u110E';
+ t['\u314B'] = '\u110F';
+ t['\u314C'] = '\u1110';
+ t['\u314D'] = '\u1111';
+ t['\u314E'] = '\u1112';
+ t['\u314F'] = '\u1161';
+ t['\u3150'] = '\u1162';
+ t['\u3151'] = '\u1163';
+ t['\u3152'] = '\u1164';
+ t['\u3153'] = '\u1165';
+ t['\u3154'] = '\u1166';
+ t['\u3155'] = '\u1167';
+ t['\u3156'] = '\u1168';
+ t['\u3157'] = '\u1169';
+ t['\u3158'] = '\u116A';
+ t['\u3159'] = '\u116B';
+ t['\u315A'] = '\u116C';
+ t['\u315B'] = '\u116D';
+ t['\u315C'] = '\u116E';
+ t['\u315D'] = '\u116F';
+ t['\u315E'] = '\u1170';
+ t['\u315F'] = '\u1171';
+ t['\u3160'] = '\u1172';
+ t['\u3161'] = '\u1173';
+ t['\u3162'] = '\u1174';
+ t['\u3163'] = '\u1175';
+ t['\u3164'] = '\u1160';
+ t['\u3165'] = '\u1114';
+ t['\u3166'] = '\u1115';
+ t['\u3167'] = '\u11C7';
+ t['\u3168'] = '\u11C8';
+ t['\u3169'] = '\u11CC';
+ t['\u316A'] = '\u11CE';
+ t['\u316B'] = '\u11D3';
+ t['\u316C'] = '\u11D7';
+ t['\u316D'] = '\u11D9';
+ t['\u316E'] = '\u111C';
+ t['\u316F'] = '\u11DD';
+ t['\u3170'] = '\u11DF';
+ t['\u3171'] = '\u111D';
+ t['\u3172'] = '\u111E';
+ t['\u3173'] = '\u1120';
+ t['\u3174'] = '\u1122';
+ t['\u3175'] = '\u1123';
+ t['\u3176'] = '\u1127';
+ t['\u3177'] = '\u1129';
+ t['\u3178'] = '\u112B';
+ t['\u3179'] = '\u112C';
+ t['\u317A'] = '\u112D';
+ t['\u317B'] = '\u112E';
+ t['\u317C'] = '\u112F';
+ t['\u317D'] = '\u1132';
+ t['\u317E'] = '\u1136';
+ t['\u317F'] = '\u1140';
+ t['\u3180'] = '\u1147';
+ t['\u3181'] = '\u114C';
+ t['\u3182'] = '\u11F1';
+ t['\u3183'] = '\u11F2';
+ t['\u3184'] = '\u1157';
+ t['\u3185'] = '\u1158';
+ t['\u3186'] = '\u1159';
+ t['\u3187'] = '\u1184';
+ t['\u3188'] = '\u1185';
+ t['\u3189'] = '\u1188';
+ t['\u318A'] = '\u1191';
+ t['\u318B'] = '\u1192';
+ t['\u318C'] = '\u1194';
+ t['\u318D'] = '\u119E';
+ t['\u318E'] = '\u11A1';
+ t['\u3200'] = '\u0028\u1100\u0029';
+ t['\u3201'] = '\u0028\u1102\u0029';
+ t['\u3202'] = '\u0028\u1103\u0029';
+ t['\u3203'] = '\u0028\u1105\u0029';
+ t['\u3204'] = '\u0028\u1106\u0029';
+ t['\u3205'] = '\u0028\u1107\u0029';
+ t['\u3206'] = '\u0028\u1109\u0029';
+ t['\u3207'] = '\u0028\u110B\u0029';
+ t['\u3208'] = '\u0028\u110C\u0029';
+ t['\u3209'] = '\u0028\u110E\u0029';
+ t['\u320A'] = '\u0028\u110F\u0029';
+ t['\u320B'] = '\u0028\u1110\u0029';
+ t['\u320C'] = '\u0028\u1111\u0029';
+ t['\u320D'] = '\u0028\u1112\u0029';
+ t['\u320E'] = '\u0028\u1100\u1161\u0029';
+ t['\u320F'] = '\u0028\u1102\u1161\u0029';
+ t['\u3210'] = '\u0028\u1103\u1161\u0029';
+ t['\u3211'] = '\u0028\u1105\u1161\u0029';
+ t['\u3212'] = '\u0028\u1106\u1161\u0029';
+ t['\u3213'] = '\u0028\u1107\u1161\u0029';
+ t['\u3214'] = '\u0028\u1109\u1161\u0029';
+ t['\u3215'] = '\u0028\u110B\u1161\u0029';
+ t['\u3216'] = '\u0028\u110C\u1161\u0029';
+ t['\u3217'] = '\u0028\u110E\u1161\u0029';
+ t['\u3218'] = '\u0028\u110F\u1161\u0029';
+ t['\u3219'] = '\u0028\u1110\u1161\u0029';
+ t['\u321A'] = '\u0028\u1111\u1161\u0029';
+ t['\u321B'] = '\u0028\u1112\u1161\u0029';
+ t['\u321C'] = '\u0028\u110C\u116E\u0029';
+ t['\u321D'] = '\u0028\u110B\u1169\u110C\u1165\u11AB\u0029';
+ t['\u321E'] = '\u0028\u110B\u1169\u1112\u116E\u0029';
+ t['\u3220'] = '\u0028\u4E00\u0029';
+ t['\u3221'] = '\u0028\u4E8C\u0029';
+ t['\u3222'] = '\u0028\u4E09\u0029';
+ t['\u3223'] = '\u0028\u56DB\u0029';
+ t['\u3224'] = '\u0028\u4E94\u0029';
+ t['\u3225'] = '\u0028\u516D\u0029';
+ t['\u3226'] = '\u0028\u4E03\u0029';
+ t['\u3227'] = '\u0028\u516B\u0029';
+ t['\u3228'] = '\u0028\u4E5D\u0029';
+ t['\u3229'] = '\u0028\u5341\u0029';
+ t['\u322A'] = '\u0028\u6708\u0029';
+ t['\u322B'] = '\u0028\u706B\u0029';
+ t['\u322C'] = '\u0028\u6C34\u0029';
+ t['\u322D'] = '\u0028\u6728\u0029';
+ t['\u322E'] = '\u0028\u91D1\u0029';
+ t['\u322F'] = '\u0028\u571F\u0029';
+ t['\u3230'] = '\u0028\u65E5\u0029';
+ t['\u3231'] = '\u0028\u682A\u0029';
+ t['\u3232'] = '\u0028\u6709\u0029';
+ t['\u3233'] = '\u0028\u793E\u0029';
+ t['\u3234'] = '\u0028\u540D\u0029';
+ t['\u3235'] = '\u0028\u7279\u0029';
+ t['\u3236'] = '\u0028\u8CA1\u0029';
+ t['\u3237'] = '\u0028\u795D\u0029';
+ t['\u3238'] = '\u0028\u52B4\u0029';
+ t['\u3239'] = '\u0028\u4EE3\u0029';
+ t['\u323A'] = '\u0028\u547C\u0029';
+ t['\u323B'] = '\u0028\u5B66\u0029';
+ t['\u323C'] = '\u0028\u76E3\u0029';
+ t['\u323D'] = '\u0028\u4F01\u0029';
+ t['\u323E'] = '\u0028\u8CC7\u0029';
+ t['\u323F'] = '\u0028\u5354\u0029';
+ t['\u3240'] = '\u0028\u796D\u0029';
+ t['\u3241'] = '\u0028\u4F11\u0029';
+ t['\u3242'] = '\u0028\u81EA\u0029';
+ t['\u3243'] = '\u0028\u81F3\u0029';
+ t['\u32C0'] = '\u0031\u6708';
+ t['\u32C1'] = '\u0032\u6708';
+ t['\u32C2'] = '\u0033\u6708';
+ t['\u32C3'] = '\u0034\u6708';
+ t['\u32C4'] = '\u0035\u6708';
+ t['\u32C5'] = '\u0036\u6708';
+ t['\u32C6'] = '\u0037\u6708';
+ t['\u32C7'] = '\u0038\u6708';
+ t['\u32C8'] = '\u0039\u6708';
+ t['\u32C9'] = '\u0031\u0030\u6708';
+ t['\u32CA'] = '\u0031\u0031\u6708';
+ t['\u32CB'] = '\u0031\u0032\u6708';
+ t['\u3358'] = '\u0030\u70B9';
+ t['\u3359'] = '\u0031\u70B9';
+ t['\u335A'] = '\u0032\u70B9';
+ t['\u335B'] = '\u0033\u70B9';
+ t['\u335C'] = '\u0034\u70B9';
+ t['\u335D'] = '\u0035\u70B9';
+ t['\u335E'] = '\u0036\u70B9';
+ t['\u335F'] = '\u0037\u70B9';
+ t['\u3360'] = '\u0038\u70B9';
+ t['\u3361'] = '\u0039\u70B9';
+ t['\u3362'] = '\u0031\u0030\u70B9';
+ t['\u3363'] = '\u0031\u0031\u70B9';
+ t['\u3364'] = '\u0031\u0032\u70B9';
+ t['\u3365'] = '\u0031\u0033\u70B9';
+ t['\u3366'] = '\u0031\u0034\u70B9';
+ t['\u3367'] = '\u0031\u0035\u70B9';
+ t['\u3368'] = '\u0031\u0036\u70B9';
+ t['\u3369'] = '\u0031\u0037\u70B9';
+ t['\u336A'] = '\u0031\u0038\u70B9';
+ t['\u336B'] = '\u0031\u0039\u70B9';
+ t['\u336C'] = '\u0032\u0030\u70B9';
+ t['\u336D'] = '\u0032\u0031\u70B9';
+ t['\u336E'] = '\u0032\u0032\u70B9';
+ t['\u336F'] = '\u0032\u0033\u70B9';
+ t['\u3370'] = '\u0032\u0034\u70B9';
+ t['\u33E0'] = '\u0031\u65E5';
+ t['\u33E1'] = '\u0032\u65E5';
+ t['\u33E2'] = '\u0033\u65E5';
+ t['\u33E3'] = '\u0034\u65E5';
+ t['\u33E4'] = '\u0035\u65E5';
+ t['\u33E5'] = '\u0036\u65E5';
+ t['\u33E6'] = '\u0037\u65E5';
+ t['\u33E7'] = '\u0038\u65E5';
+ t['\u33E8'] = '\u0039\u65E5';
+ t['\u33E9'] = '\u0031\u0030\u65E5';
+ t['\u33EA'] = '\u0031\u0031\u65E5';
+ t['\u33EB'] = '\u0031\u0032\u65E5';
+ t['\u33EC'] = '\u0031\u0033\u65E5';
+ t['\u33ED'] = '\u0031\u0034\u65E5';
+ t['\u33EE'] = '\u0031\u0035\u65E5';
+ t['\u33EF'] = '\u0031\u0036\u65E5';
+ t['\u33F0'] = '\u0031\u0037\u65E5';
+ t['\u33F1'] = '\u0031\u0038\u65E5';
+ t['\u33F2'] = '\u0031\u0039\u65E5';
+ t['\u33F3'] = '\u0032\u0030\u65E5';
+ t['\u33F4'] = '\u0032\u0031\u65E5';
+ t['\u33F5'] = '\u0032\u0032\u65E5';
+ t['\u33F6'] = '\u0032\u0033\u65E5';
+ t['\u33F7'] = '\u0032\u0034\u65E5';
+ t['\u33F8'] = '\u0032\u0035\u65E5';
+ t['\u33F9'] = '\u0032\u0036\u65E5';
+ t['\u33FA'] = '\u0032\u0037\u65E5';
+ t['\u33FB'] = '\u0032\u0038\u65E5';
+ t['\u33FC'] = '\u0032\u0039\u65E5';
+ t['\u33FD'] = '\u0033\u0030\u65E5';
+ t['\u33FE'] = '\u0033\u0031\u65E5';
+ t['\uFB00'] = '\u0066\u0066';
+ t['\uFB01'] = '\u0066\u0069';
+ t['\uFB02'] = '\u0066\u006C';
+ t['\uFB03'] = '\u0066\u0066\u0069';
+ t['\uFB04'] = '\u0066\u0066\u006C';
+ t['\uFB05'] = '\u017F\u0074';
+ t['\uFB06'] = '\u0073\u0074';
+ t['\uFB13'] = '\u0574\u0576';
+ t['\uFB14'] = '\u0574\u0565';
+ t['\uFB15'] = '\u0574\u056B';
+ t['\uFB16'] = '\u057E\u0576';
+ t['\uFB17'] = '\u0574\u056D';
+ t['\uFB4F'] = '\u05D0\u05DC';
+ t['\uFB50'] = '\u0671';
+ t['\uFB51'] = '\u0671';
+ t['\uFB52'] = '\u067B';
+ t['\uFB53'] = '\u067B';
+ t['\uFB54'] = '\u067B';
+ t['\uFB55'] = '\u067B';
+ t['\uFB56'] = '\u067E';
+ t['\uFB57'] = '\u067E';
+ t['\uFB58'] = '\u067E';
+ t['\uFB59'] = '\u067E';
+ t['\uFB5A'] = '\u0680';
+ t['\uFB5B'] = '\u0680';
+ t['\uFB5C'] = '\u0680';
+ t['\uFB5D'] = '\u0680';
+ t['\uFB5E'] = '\u067A';
+ t['\uFB5F'] = '\u067A';
+ t['\uFB60'] = '\u067A';
+ t['\uFB61'] = '\u067A';
+ t['\uFB62'] = '\u067F';
+ t['\uFB63'] = '\u067F';
+ t['\uFB64'] = '\u067F';
+ t['\uFB65'] = '\u067F';
+ t['\uFB66'] = '\u0679';
+ t['\uFB67'] = '\u0679';
+ t['\uFB68'] = '\u0679';
+ t['\uFB69'] = '\u0679';
+ t['\uFB6A'] = '\u06A4';
+ t['\uFB6B'] = '\u06A4';
+ t['\uFB6C'] = '\u06A4';
+ t['\uFB6D'] = '\u06A4';
+ t['\uFB6E'] = '\u06A6';
+ t['\uFB6F'] = '\u06A6';
+ t['\uFB70'] = '\u06A6';
+ t['\uFB71'] = '\u06A6';
+ t['\uFB72'] = '\u0684';
+ t['\uFB73'] = '\u0684';
+ t['\uFB74'] = '\u0684';
+ t['\uFB75'] = '\u0684';
+ t['\uFB76'] = '\u0683';
+ t['\uFB77'] = '\u0683';
+ t['\uFB78'] = '\u0683';
+ t['\uFB79'] = '\u0683';
+ t['\uFB7A'] = '\u0686';
+ t['\uFB7B'] = '\u0686';
+ t['\uFB7C'] = '\u0686';
+ t['\uFB7D'] = '\u0686';
+ t['\uFB7E'] = '\u0687';
+ t['\uFB7F'] = '\u0687';
+ t['\uFB80'] = '\u0687';
+ t['\uFB81'] = '\u0687';
+ t['\uFB82'] = '\u068D';
+ t['\uFB83'] = '\u068D';
+ t['\uFB84'] = '\u068C';
+ t['\uFB85'] = '\u068C';
+ t['\uFB86'] = '\u068E';
+ t['\uFB87'] = '\u068E';
+ t['\uFB88'] = '\u0688';
+ t['\uFB89'] = '\u0688';
+ t['\uFB8A'] = '\u0698';
+ t['\uFB8B'] = '\u0698';
+ t['\uFB8C'] = '\u0691';
+ t['\uFB8D'] = '\u0691';
+ t['\uFB8E'] = '\u06A9';
+ t['\uFB8F'] = '\u06A9';
+ t['\uFB90'] = '\u06A9';
+ t['\uFB91'] = '\u06A9';
+ t['\uFB92'] = '\u06AF';
+ t['\uFB93'] = '\u06AF';
+ t['\uFB94'] = '\u06AF';
+ t['\uFB95'] = '\u06AF';
+ t['\uFB96'] = '\u06B3';
+ t['\uFB97'] = '\u06B3';
+ t['\uFB98'] = '\u06B3';
+ t['\uFB99'] = '\u06B3';
+ t['\uFB9A'] = '\u06B1';
+ t['\uFB9B'] = '\u06B1';
+ t['\uFB9C'] = '\u06B1';
+ t['\uFB9D'] = '\u06B1';
+ t['\uFB9E'] = '\u06BA';
+ t['\uFB9F'] = '\u06BA';
+ t['\uFBA0'] = '\u06BB';
+ t['\uFBA1'] = '\u06BB';
+ t['\uFBA2'] = '\u06BB';
+ t['\uFBA3'] = '\u06BB';
+ t['\uFBA4'] = '\u06C0';
+ t['\uFBA5'] = '\u06C0';
+ t['\uFBA6'] = '\u06C1';
+ t['\uFBA7'] = '\u06C1';
+ t['\uFBA8'] = '\u06C1';
+ t['\uFBA9'] = '\u06C1';
+ t['\uFBAA'] = '\u06BE';
+ t['\uFBAB'] = '\u06BE';
+ t['\uFBAC'] = '\u06BE';
+ t['\uFBAD'] = '\u06BE';
+ t['\uFBAE'] = '\u06D2';
+ t['\uFBAF'] = '\u06D2';
+ t['\uFBB0'] = '\u06D3';
+ t['\uFBB1'] = '\u06D3';
+ t['\uFBD3'] = '\u06AD';
+ t['\uFBD4'] = '\u06AD';
+ t['\uFBD5'] = '\u06AD';
+ t['\uFBD6'] = '\u06AD';
+ t['\uFBD7'] = '\u06C7';
+ t['\uFBD8'] = '\u06C7';
+ t['\uFBD9'] = '\u06C6';
+ t['\uFBDA'] = '\u06C6';
+ t['\uFBDB'] = '\u06C8';
+ t['\uFBDC'] = '\u06C8';
+ t['\uFBDD'] = '\u0677';
+ t['\uFBDE'] = '\u06CB';
+ t['\uFBDF'] = '\u06CB';
+ t['\uFBE0'] = '\u06C5';
+ t['\uFBE1'] = '\u06C5';
+ t['\uFBE2'] = '\u06C9';
+ t['\uFBE3'] = '\u06C9';
+ t['\uFBE4'] = '\u06D0';
+ t['\uFBE5'] = '\u06D0';
+ t['\uFBE6'] = '\u06D0';
+ t['\uFBE7'] = '\u06D0';
+ t['\uFBE8'] = '\u0649';
+ t['\uFBE9'] = '\u0649';
+ t['\uFBEA'] = '\u0626\u0627';
+ t['\uFBEB'] = '\u0626\u0627';
+ t['\uFBEC'] = '\u0626\u06D5';
+ t['\uFBED'] = '\u0626\u06D5';
+ t['\uFBEE'] = '\u0626\u0648';
+ t['\uFBEF'] = '\u0626\u0648';
+ t['\uFBF0'] = '\u0626\u06C7';
+ t['\uFBF1'] = '\u0626\u06C7';
+ t['\uFBF2'] = '\u0626\u06C6';
+ t['\uFBF3'] = '\u0626\u06C6';
+ t['\uFBF4'] = '\u0626\u06C8';
+ t['\uFBF5'] = '\u0626\u06C8';
+ t['\uFBF6'] = '\u0626\u06D0';
+ t['\uFBF7'] = '\u0626\u06D0';
+ t['\uFBF8'] = '\u0626\u06D0';
+ t['\uFBF9'] = '\u0626\u0649';
+ t['\uFBFA'] = '\u0626\u0649';
+ t['\uFBFB'] = '\u0626\u0649';
+ t['\uFBFC'] = '\u06CC';
+ t['\uFBFD'] = '\u06CC';
+ t['\uFBFE'] = '\u06CC';
+ t['\uFBFF'] = '\u06CC';
+ t['\uFC00'] = '\u0626\u062C';
+ t['\uFC01'] = '\u0626\u062D';
+ t['\uFC02'] = '\u0626\u0645';
+ t['\uFC03'] = '\u0626\u0649';
+ t['\uFC04'] = '\u0626\u064A';
+ t['\uFC05'] = '\u0628\u062C';
+ t['\uFC06'] = '\u0628\u062D';
+ t['\uFC07'] = '\u0628\u062E';
+ t['\uFC08'] = '\u0628\u0645';
+ t['\uFC09'] = '\u0628\u0649';
+ t['\uFC0A'] = '\u0628\u064A';
+ t['\uFC0B'] = '\u062A\u062C';
+ t['\uFC0C'] = '\u062A\u062D';
+ t['\uFC0D'] = '\u062A\u062E';
+ t['\uFC0E'] = '\u062A\u0645';
+ t['\uFC0F'] = '\u062A\u0649';
+ t['\uFC10'] = '\u062A\u064A';
+ t['\uFC11'] = '\u062B\u062C';
+ t['\uFC12'] = '\u062B\u0645';
+ t['\uFC13'] = '\u062B\u0649';
+ t['\uFC14'] = '\u062B\u064A';
+ t['\uFC15'] = '\u062C\u062D';
+ t['\uFC16'] = '\u062C\u0645';
+ t['\uFC17'] = '\u062D\u062C';
+ t['\uFC18'] = '\u062D\u0645';
+ t['\uFC19'] = '\u062E\u062C';
+ t['\uFC1A'] = '\u062E\u062D';
+ t['\uFC1B'] = '\u062E\u0645';
+ t['\uFC1C'] = '\u0633\u062C';
+ t['\uFC1D'] = '\u0633\u062D';
+ t['\uFC1E'] = '\u0633\u062E';
+ t['\uFC1F'] = '\u0633\u0645';
+ t['\uFC20'] = '\u0635\u062D';
+ t['\uFC21'] = '\u0635\u0645';
+ t['\uFC22'] = '\u0636\u062C';
+ t['\uFC23'] = '\u0636\u062D';
+ t['\uFC24'] = '\u0636\u062E';
+ t['\uFC25'] = '\u0636\u0645';
+ t['\uFC26'] = '\u0637\u062D';
+ t['\uFC27'] = '\u0637\u0645';
+ t['\uFC28'] = '\u0638\u0645';
+ t['\uFC29'] = '\u0639\u062C';
+ t['\uFC2A'] = '\u0639\u0645';
+ t['\uFC2B'] = '\u063A\u062C';
+ t['\uFC2C'] = '\u063A\u0645';
+ t['\uFC2D'] = '\u0641\u062C';
+ t['\uFC2E'] = '\u0641\u062D';
+ t['\uFC2F'] = '\u0641\u062E';
+ t['\uFC30'] = '\u0641\u0645';
+ t['\uFC31'] = '\u0641\u0649';
+ t['\uFC32'] = '\u0641\u064A';
+ t['\uFC33'] = '\u0642\u062D';
+ t['\uFC34'] = '\u0642\u0645';
+ t['\uFC35'] = '\u0642\u0649';
+ t['\uFC36'] = '\u0642\u064A';
+ t['\uFC37'] = '\u0643\u0627';
+ t['\uFC38'] = '\u0643\u062C';
+ t['\uFC39'] = '\u0643\u062D';
+ t['\uFC3A'] = '\u0643\u062E';
+ t['\uFC3B'] = '\u0643\u0644';
+ t['\uFC3C'] = '\u0643\u0645';
+ t['\uFC3D'] = '\u0643\u0649';
+ t['\uFC3E'] = '\u0643\u064A';
+ t['\uFC3F'] = '\u0644\u062C';
+ t['\uFC40'] = '\u0644\u062D';
+ t['\uFC41'] = '\u0644\u062E';
+ t['\uFC42'] = '\u0644\u0645';
+ t['\uFC43'] = '\u0644\u0649';
+ t['\uFC44'] = '\u0644\u064A';
+ t['\uFC45'] = '\u0645\u062C';
+ t['\uFC46'] = '\u0645\u062D';
+ t['\uFC47'] = '\u0645\u062E';
+ t['\uFC48'] = '\u0645\u0645';
+ t['\uFC49'] = '\u0645\u0649';
+ t['\uFC4A'] = '\u0645\u064A';
+ t['\uFC4B'] = '\u0646\u062C';
+ t['\uFC4C'] = '\u0646\u062D';
+ t['\uFC4D'] = '\u0646\u062E';
+ t['\uFC4E'] = '\u0646\u0645';
+ t['\uFC4F'] = '\u0646\u0649';
+ t['\uFC50'] = '\u0646\u064A';
+ t['\uFC51'] = '\u0647\u062C';
+ t['\uFC52'] = '\u0647\u0645';
+ t['\uFC53'] = '\u0647\u0649';
+ t['\uFC54'] = '\u0647\u064A';
+ t['\uFC55'] = '\u064A\u062C';
+ t['\uFC56'] = '\u064A\u062D';
+ t['\uFC57'] = '\u064A\u062E';
+ t['\uFC58'] = '\u064A\u0645';
+ t['\uFC59'] = '\u064A\u0649';
+ t['\uFC5A'] = '\u064A\u064A';
+ t['\uFC5B'] = '\u0630\u0670';
+ t['\uFC5C'] = '\u0631\u0670';
+ t['\uFC5D'] = '\u0649\u0670';
+ t['\uFC5E'] = '\u0020\u064C\u0651';
+ t['\uFC5F'] = '\u0020\u064D\u0651';
+ t['\uFC60'] = '\u0020\u064E\u0651';
+ t['\uFC61'] = '\u0020\u064F\u0651';
+ t['\uFC62'] = '\u0020\u0650\u0651';
+ t['\uFC63'] = '\u0020\u0651\u0670';
+ t['\uFC64'] = '\u0626\u0631';
+ t['\uFC65'] = '\u0626\u0632';
+ t['\uFC66'] = '\u0626\u0645';
+ t['\uFC67'] = '\u0626\u0646';
+ t['\uFC68'] = '\u0626\u0649';
+ t['\uFC69'] = '\u0626\u064A';
+ t['\uFC6A'] = '\u0628\u0631';
+ t['\uFC6B'] = '\u0628\u0632';
+ t['\uFC6C'] = '\u0628\u0645';
+ t['\uFC6D'] = '\u0628\u0646';
+ t['\uFC6E'] = '\u0628\u0649';
+ t['\uFC6F'] = '\u0628\u064A';
+ t['\uFC70'] = '\u062A\u0631';
+ t['\uFC71'] = '\u062A\u0632';
+ t['\uFC72'] = '\u062A\u0645';
+ t['\uFC73'] = '\u062A\u0646';
+ t['\uFC74'] = '\u062A\u0649';
+ t['\uFC75'] = '\u062A\u064A';
+ t['\uFC76'] = '\u062B\u0631';
+ t['\uFC77'] = '\u062B\u0632';
+ t['\uFC78'] = '\u062B\u0645';
+ t['\uFC79'] = '\u062B\u0646';
+ t['\uFC7A'] = '\u062B\u0649';
+ t['\uFC7B'] = '\u062B\u064A';
+ t['\uFC7C'] = '\u0641\u0649';
+ t['\uFC7D'] = '\u0641\u064A';
+ t['\uFC7E'] = '\u0642\u0649';
+ t['\uFC7F'] = '\u0642\u064A';
+ t['\uFC80'] = '\u0643\u0627';
+ t['\uFC81'] = '\u0643\u0644';
+ t['\uFC82'] = '\u0643\u0645';
+ t['\uFC83'] = '\u0643\u0649';
+ t['\uFC84'] = '\u0643\u064A';
+ t['\uFC85'] = '\u0644\u0645';
+ t['\uFC86'] = '\u0644\u0649';
+ t['\uFC87'] = '\u0644\u064A';
+ t['\uFC88'] = '\u0645\u0627';
+ t['\uFC89'] = '\u0645\u0645';
+ t['\uFC8A'] = '\u0646\u0631';
+ t['\uFC8B'] = '\u0646\u0632';
+ t['\uFC8C'] = '\u0646\u0645';
+ t['\uFC8D'] = '\u0646\u0646';
+ t['\uFC8E'] = '\u0646\u0649';
+ t['\uFC8F'] = '\u0646\u064A';
+ t['\uFC90'] = '\u0649\u0670';
+ t['\uFC91'] = '\u064A\u0631';
+ t['\uFC92'] = '\u064A\u0632';
+ t['\uFC93'] = '\u064A\u0645';
+ t['\uFC94'] = '\u064A\u0646';
+ t['\uFC95'] = '\u064A\u0649';
+ t['\uFC96'] = '\u064A\u064A';
+ t['\uFC97'] = '\u0626\u062C';
+ t['\uFC98'] = '\u0626\u062D';
+ t['\uFC99'] = '\u0626\u062E';
+ t['\uFC9A'] = '\u0626\u0645';
+ t['\uFC9B'] = '\u0626\u0647';
+ t['\uFC9C'] = '\u0628\u062C';
+ t['\uFC9D'] = '\u0628\u062D';
+ t['\uFC9E'] = '\u0628\u062E';
+ t['\uFC9F'] = '\u0628\u0645';
+ t['\uFCA0'] = '\u0628\u0647';
+ t['\uFCA1'] = '\u062A\u062C';
+ t['\uFCA2'] = '\u062A\u062D';
+ t['\uFCA3'] = '\u062A\u062E';
+ t['\uFCA4'] = '\u062A\u0645';
+ t['\uFCA5'] = '\u062A\u0647';
+ t['\uFCA6'] = '\u062B\u0645';
+ t['\uFCA7'] = '\u062C\u062D';
+ t['\uFCA8'] = '\u062C\u0645';
+ t['\uFCA9'] = '\u062D\u062C';
+ t['\uFCAA'] = '\u062D\u0645';
+ t['\uFCAB'] = '\u062E\u062C';
+ t['\uFCAC'] = '\u062E\u0645';
+ t['\uFCAD'] = '\u0633\u062C';
+ t['\uFCAE'] = '\u0633\u062D';
+ t['\uFCAF'] = '\u0633\u062E';
+ t['\uFCB0'] = '\u0633\u0645';
+ t['\uFCB1'] = '\u0635\u062D';
+ t['\uFCB2'] = '\u0635\u062E';
+ t['\uFCB3'] = '\u0635\u0645';
+ t['\uFCB4'] = '\u0636\u062C';
+ t['\uFCB5'] = '\u0636\u062D';
+ t['\uFCB6'] = '\u0636\u062E';
+ t['\uFCB7'] = '\u0636\u0645';
+ t['\uFCB8'] = '\u0637\u062D';
+ t['\uFCB9'] = '\u0638\u0645';
+ t['\uFCBA'] = '\u0639\u062C';
+ t['\uFCBB'] = '\u0639\u0645';
+ t['\uFCBC'] = '\u063A\u062C';
+ t['\uFCBD'] = '\u063A\u0645';
+ t['\uFCBE'] = '\u0641\u062C';
+ t['\uFCBF'] = '\u0641\u062D';
+ t['\uFCC0'] = '\u0641\u062E';
+ t['\uFCC1'] = '\u0641\u0645';
+ t['\uFCC2'] = '\u0642\u062D';
+ t['\uFCC3'] = '\u0642\u0645';
+ t['\uFCC4'] = '\u0643\u062C';
+ t['\uFCC5'] = '\u0643\u062D';
+ t['\uFCC6'] = '\u0643\u062E';
+ t['\uFCC7'] = '\u0643\u0644';
+ t['\uFCC8'] = '\u0643\u0645';
+ t['\uFCC9'] = '\u0644\u062C';
+ t['\uFCCA'] = '\u0644\u062D';
+ t['\uFCCB'] = '\u0644\u062E';
+ t['\uFCCC'] = '\u0644\u0645';
+ t['\uFCCD'] = '\u0644\u0647';
+ t['\uFCCE'] = '\u0645\u062C';
+ t['\uFCCF'] = '\u0645\u062D';
+ t['\uFCD0'] = '\u0645\u062E';
+ t['\uFCD1'] = '\u0645\u0645';
+ t['\uFCD2'] = '\u0646\u062C';
+ t['\uFCD3'] = '\u0646\u062D';
+ t['\uFCD4'] = '\u0646\u062E';
+ t['\uFCD5'] = '\u0646\u0645';
+ t['\uFCD6'] = '\u0646\u0647';
+ t['\uFCD7'] = '\u0647\u062C';
+ t['\uFCD8'] = '\u0647\u0645';
+ t['\uFCD9'] = '\u0647\u0670';
+ t['\uFCDA'] = '\u064A\u062C';
+ t['\uFCDB'] = '\u064A\u062D';
+ t['\uFCDC'] = '\u064A\u062E';
+ t['\uFCDD'] = '\u064A\u0645';
+ t['\uFCDE'] = '\u064A\u0647';
+ t['\uFCDF'] = '\u0626\u0645';
+ t['\uFCE0'] = '\u0626\u0647';
+ t['\uFCE1'] = '\u0628\u0645';
+ t['\uFCE2'] = '\u0628\u0647';
+ t['\uFCE3'] = '\u062A\u0645';
+ t['\uFCE4'] = '\u062A\u0647';
+ t['\uFCE5'] = '\u062B\u0645';
+ t['\uFCE6'] = '\u062B\u0647';
+ t['\uFCE7'] = '\u0633\u0645';
+ t['\uFCE8'] = '\u0633\u0647';
+ t['\uFCE9'] = '\u0634\u0645';
+ t['\uFCEA'] = '\u0634\u0647';
+ t['\uFCEB'] = '\u0643\u0644';
+ t['\uFCEC'] = '\u0643\u0645';
+ t['\uFCED'] = '\u0644\u0645';
+ t['\uFCEE'] = '\u0646\u0645';
+ t['\uFCEF'] = '\u0646\u0647';
+ t['\uFCF0'] = '\u064A\u0645';
+ t['\uFCF1'] = '\u064A\u0647';
+ t['\uFCF2'] = '\u0640\u064E\u0651';
+ t['\uFCF3'] = '\u0640\u064F\u0651';
+ t['\uFCF4'] = '\u0640\u0650\u0651';
+ t['\uFCF5'] = '\u0637\u0649';
+ t['\uFCF6'] = '\u0637\u064A';
+ t['\uFCF7'] = '\u0639\u0649';
+ t['\uFCF8'] = '\u0639\u064A';
+ t['\uFCF9'] = '\u063A\u0649';
+ t['\uFCFA'] = '\u063A\u064A';
+ t['\uFCFB'] = '\u0633\u0649';
+ t['\uFCFC'] = '\u0633\u064A';
+ t['\uFCFD'] = '\u0634\u0649';
+ t['\uFCFE'] = '\u0634\u064A';
+ t['\uFCFF'] = '\u062D\u0649';
+ t['\uFD00'] = '\u062D\u064A';
+ t['\uFD01'] = '\u062C\u0649';
+ t['\uFD02'] = '\u062C\u064A';
+ t['\uFD03'] = '\u062E\u0649';
+ t['\uFD04'] = '\u062E\u064A';
+ t['\uFD05'] = '\u0635\u0649';
+ t['\uFD06'] = '\u0635\u064A';
+ t['\uFD07'] = '\u0636\u0649';
+ t['\uFD08'] = '\u0636\u064A';
+ t['\uFD09'] = '\u0634\u062C';
+ t['\uFD0A'] = '\u0634\u062D';
+ t['\uFD0B'] = '\u0634\u062E';
+ t['\uFD0C'] = '\u0634\u0645';
+ t['\uFD0D'] = '\u0634\u0631';
+ t['\uFD0E'] = '\u0633\u0631';
+ t['\uFD0F'] = '\u0635\u0631';
+ t['\uFD10'] = '\u0636\u0631';
+ t['\uFD11'] = '\u0637\u0649';
+ t['\uFD12'] = '\u0637\u064A';
+ t['\uFD13'] = '\u0639\u0649';
+ t['\uFD14'] = '\u0639\u064A';
+ t['\uFD15'] = '\u063A\u0649';
+ t['\uFD16'] = '\u063A\u064A';
+ t['\uFD17'] = '\u0633\u0649';
+ t['\uFD18'] = '\u0633\u064A';
+ t['\uFD19'] = '\u0634\u0649';
+ t['\uFD1A'] = '\u0634\u064A';
+ t['\uFD1B'] = '\u062D\u0649';
+ t['\uFD1C'] = '\u062D\u064A';
+ t['\uFD1D'] = '\u062C\u0649';
+ t['\uFD1E'] = '\u062C\u064A';
+ t['\uFD1F'] = '\u062E\u0649';
+ t['\uFD20'] = '\u062E\u064A';
+ t['\uFD21'] = '\u0635\u0649';
+ t['\uFD22'] = '\u0635\u064A';
+ t['\uFD23'] = '\u0636\u0649';
+ t['\uFD24'] = '\u0636\u064A';
+ t['\uFD25'] = '\u0634\u062C';
+ t['\uFD26'] = '\u0634\u062D';
+ t['\uFD27'] = '\u0634\u062E';
+ t['\uFD28'] = '\u0634\u0645';
+ t['\uFD29'] = '\u0634\u0631';
+ t['\uFD2A'] = '\u0633\u0631';
+ t['\uFD2B'] = '\u0635\u0631';
+ t['\uFD2C'] = '\u0636\u0631';
+ t['\uFD2D'] = '\u0634\u062C';
+ t['\uFD2E'] = '\u0634\u062D';
+ t['\uFD2F'] = '\u0634\u062E';
+ t['\uFD30'] = '\u0634\u0645';
+ t['\uFD31'] = '\u0633\u0647';
+ t['\uFD32'] = '\u0634\u0647';
+ t['\uFD33'] = '\u0637\u0645';
+ t['\uFD34'] = '\u0633\u062C';
+ t['\uFD35'] = '\u0633\u062D';
+ t['\uFD36'] = '\u0633\u062E';
+ t['\uFD37'] = '\u0634\u062C';
+ t['\uFD38'] = '\u0634\u062D';
+ t['\uFD39'] = '\u0634\u062E';
+ t['\uFD3A'] = '\u0637\u0645';
+ t['\uFD3B'] = '\u0638\u0645';
+ t['\uFD3C'] = '\u0627\u064B';
+ t['\uFD3D'] = '\u0627\u064B';
+ t['\uFD50'] = '\u062A\u062C\u0645';
+ t['\uFD51'] = '\u062A\u062D\u062C';
+ t['\uFD52'] = '\u062A\u062D\u062C';
+ t['\uFD53'] = '\u062A\u062D\u0645';
+ t['\uFD54'] = '\u062A\u062E\u0645';
+ t['\uFD55'] = '\u062A\u0645\u062C';
+ t['\uFD56'] = '\u062A\u0645\u062D';
+ t['\uFD57'] = '\u062A\u0645\u062E';
+ t['\uFD58'] = '\u062C\u0645\u062D';
+ t['\uFD59'] = '\u062C\u0645\u062D';
+ t['\uFD5A'] = '\u062D\u0645\u064A';
+ t['\uFD5B'] = '\u062D\u0645\u0649';
+ t['\uFD5C'] = '\u0633\u062D\u062C';
+ t['\uFD5D'] = '\u0633\u062C\u062D';
+ t['\uFD5E'] = '\u0633\u062C\u0649';
+ t['\uFD5F'] = '\u0633\u0645\u062D';
+ t['\uFD60'] = '\u0633\u0645\u062D';
+ t['\uFD61'] = '\u0633\u0645\u062C';
+ t['\uFD62'] = '\u0633\u0645\u0645';
+ t['\uFD63'] = '\u0633\u0645\u0645';
+ t['\uFD64'] = '\u0635\u062D\u062D';
+ t['\uFD65'] = '\u0635\u062D\u062D';
+ t['\uFD66'] = '\u0635\u0645\u0645';
+ t['\uFD67'] = '\u0634\u062D\u0645';
+ t['\uFD68'] = '\u0634\u062D\u0645';
+ t['\uFD69'] = '\u0634\u062C\u064A';
+ t['\uFD6A'] = '\u0634\u0645\u062E';
+ t['\uFD6B'] = '\u0634\u0645\u062E';
+ t['\uFD6C'] = '\u0634\u0645\u0645';
+ t['\uFD6D'] = '\u0634\u0645\u0645';
+ t['\uFD6E'] = '\u0636\u062D\u0649';
+ t['\uFD6F'] = '\u0636\u062E\u0645';
+ t['\uFD70'] = '\u0636\u062E\u0645';
+ t['\uFD71'] = '\u0637\u0645\u062D';
+ t['\uFD72'] = '\u0637\u0645\u062D';
+ t['\uFD73'] = '\u0637\u0645\u0645';
+ t['\uFD74'] = '\u0637\u0645\u064A';
+ t['\uFD75'] = '\u0639\u062C\u0645';
+ t['\uFD76'] = '\u0639\u0645\u0645';
+ t['\uFD77'] = '\u0639\u0645\u0645';
+ t['\uFD78'] = '\u0639\u0645\u0649';
+ t['\uFD79'] = '\u063A\u0645\u0645';
+ t['\uFD7A'] = '\u063A\u0645\u064A';
+ t['\uFD7B'] = '\u063A\u0645\u0649';
+ t['\uFD7C'] = '\u0641\u062E\u0645';
+ t['\uFD7D'] = '\u0641\u062E\u0645';
+ t['\uFD7E'] = '\u0642\u0645\u062D';
+ t['\uFD7F'] = '\u0642\u0645\u0645';
+ t['\uFD80'] = '\u0644\u062D\u0645';
+ t['\uFD81'] = '\u0644\u062D\u064A';
+ t['\uFD82'] = '\u0644\u062D\u0649';
+ t['\uFD83'] = '\u0644\u062C\u062C';
+ t['\uFD84'] = '\u0644\u062C\u062C';
+ t['\uFD85'] = '\u0644\u062E\u0645';
+ t['\uFD86'] = '\u0644\u062E\u0645';
+ t['\uFD87'] = '\u0644\u0645\u062D';
+ t['\uFD88'] = '\u0644\u0645\u062D';
+ t['\uFD89'] = '\u0645\u062D\u062C';
+ t['\uFD8A'] = '\u0645\u062D\u0645';
+ t['\uFD8B'] = '\u0645\u062D\u064A';
+ t['\uFD8C'] = '\u0645\u062C\u062D';
+ t['\uFD8D'] = '\u0645\u062C\u0645';
+ t['\uFD8E'] = '\u0645\u062E\u062C';
+ t['\uFD8F'] = '\u0645\u062E\u0645';
+ t['\uFD92'] = '\u0645\u062C\u062E';
+ t['\uFD93'] = '\u0647\u0645\u062C';
+ t['\uFD94'] = '\u0647\u0645\u0645';
+ t['\uFD95'] = '\u0646\u062D\u0645';
+ t['\uFD96'] = '\u0646\u062D\u0649';
+ t['\uFD97'] = '\u0646\u062C\u0645';
+ t['\uFD98'] = '\u0646\u062C\u0645';
+ t['\uFD99'] = '\u0646\u062C\u0649';
+ t['\uFD9A'] = '\u0646\u0645\u064A';
+ t['\uFD9B'] = '\u0646\u0645\u0649';
+ t['\uFD9C'] = '\u064A\u0645\u0645';
+ t['\uFD9D'] = '\u064A\u0645\u0645';
+ t['\uFD9E'] = '\u0628\u062E\u064A';
+ t['\uFD9F'] = '\u062A\u062C\u064A';
+ t['\uFDA0'] = '\u062A\u062C\u0649';
+ t['\uFDA1'] = '\u062A\u062E\u064A';
+ t['\uFDA2'] = '\u062A\u062E\u0649';
+ t['\uFDA3'] = '\u062A\u0645\u064A';
+ t['\uFDA4'] = '\u062A\u0645\u0649';
+ t['\uFDA5'] = '\u062C\u0645\u064A';
+ t['\uFDA6'] = '\u062C\u062D\u0649';
+ t['\uFDA7'] = '\u062C\u0645\u0649';
+ t['\uFDA8'] = '\u0633\u062E\u0649';
+ t['\uFDA9'] = '\u0635\u062D\u064A';
+ t['\uFDAA'] = '\u0634\u062D\u064A';
+ t['\uFDAB'] = '\u0636\u062D\u064A';
+ t['\uFDAC'] = '\u0644\u062C\u064A';
+ t['\uFDAD'] = '\u0644\u0645\u064A';
+ t['\uFDAE'] = '\u064A\u062D\u064A';
+ t['\uFDAF'] = '\u064A\u062C\u064A';
+ t['\uFDB0'] = '\u064A\u0645\u064A';
+ t['\uFDB1'] = '\u0645\u0645\u064A';
+ t['\uFDB2'] = '\u0642\u0645\u064A';
+ t['\uFDB3'] = '\u0646\u062D\u064A';
+ t['\uFDB4'] = '\u0642\u0645\u062D';
+ t['\uFDB5'] = '\u0644\u062D\u0645';
+ t['\uFDB6'] = '\u0639\u0645\u064A';
+ t['\uFDB7'] = '\u0643\u0645\u064A';
+ t['\uFDB8'] = '\u0646\u062C\u062D';
+ t['\uFDB9'] = '\u0645\u062E\u064A';
+ t['\uFDBA'] = '\u0644\u062C\u0645';
+ t['\uFDBB'] = '\u0643\u0645\u0645';
+ t['\uFDBC'] = '\u0644\u062C\u0645';
+ t['\uFDBD'] = '\u0646\u062C\u062D';
+ t['\uFDBE'] = '\u062C\u062D\u064A';
+ t['\uFDBF'] = '\u062D\u062C\u064A';
+ t['\uFDC0'] = '\u0645\u062C\u064A';
+ t['\uFDC1'] = '\u0641\u0645\u064A';
+ t['\uFDC2'] = '\u0628\u062D\u064A';
+ t['\uFDC3'] = '\u0643\u0645\u0645';
+ t['\uFDC4'] = '\u0639\u062C\u0645';
+ t['\uFDC5'] = '\u0635\u0645\u0645';
+ t['\uFDC6'] = '\u0633\u062E\u064A';
+ t['\uFDC7'] = '\u0646\u062C\u064A';
+ t['\uFE49'] = '\u203E';
+ t['\uFE4A'] = '\u203E';
+ t['\uFE4B'] = '\u203E';
+ t['\uFE4C'] = '\u203E';
+ t['\uFE4D'] = '\u005F';
+ t['\uFE4E'] = '\u005F';
+ t['\uFE4F'] = '\u005F';
+ t['\uFE80'] = '\u0621';
+ t['\uFE81'] = '\u0622';
+ t['\uFE82'] = '\u0622';
+ t['\uFE83'] = '\u0623';
+ t['\uFE84'] = '\u0623';
+ t['\uFE85'] = '\u0624';
+ t['\uFE86'] = '\u0624';
+ t['\uFE87'] = '\u0625';
+ t['\uFE88'] = '\u0625';
+ t['\uFE89'] = '\u0626';
+ t['\uFE8A'] = '\u0626';
+ t['\uFE8B'] = '\u0626';
+ t['\uFE8C'] = '\u0626';
+ t['\uFE8D'] = '\u0627';
+ t['\uFE8E'] = '\u0627';
+ t['\uFE8F'] = '\u0628';
+ t['\uFE90'] = '\u0628';
+ t['\uFE91'] = '\u0628';
+ t['\uFE92'] = '\u0628';
+ t['\uFE93'] = '\u0629';
+ t['\uFE94'] = '\u0629';
+ t['\uFE95'] = '\u062A';
+ t['\uFE96'] = '\u062A';
+ t['\uFE97'] = '\u062A';
+ t['\uFE98'] = '\u062A';
+ t['\uFE99'] = '\u062B';
+ t['\uFE9A'] = '\u062B';
+ t['\uFE9B'] = '\u062B';
+ t['\uFE9C'] = '\u062B';
+ t['\uFE9D'] = '\u062C';
+ t['\uFE9E'] = '\u062C';
+ t['\uFE9F'] = '\u062C';
+ t['\uFEA0'] = '\u062C';
+ t['\uFEA1'] = '\u062D';
+ t['\uFEA2'] = '\u062D';
+ t['\uFEA3'] = '\u062D';
+ t['\uFEA4'] = '\u062D';
+ t['\uFEA5'] = '\u062E';
+ t['\uFEA6'] = '\u062E';
+ t['\uFEA7'] = '\u062E';
+ t['\uFEA8'] = '\u062E';
+ t['\uFEA9'] = '\u062F';
+ t['\uFEAA'] = '\u062F';
+ t['\uFEAB'] = '\u0630';
+ t['\uFEAC'] = '\u0630';
+ t['\uFEAD'] = '\u0631';
+ t['\uFEAE'] = '\u0631';
+ t['\uFEAF'] = '\u0632';
+ t['\uFEB0'] = '\u0632';
+ t['\uFEB1'] = '\u0633';
+ t['\uFEB2'] = '\u0633';
+ t['\uFEB3'] = '\u0633';
+ t['\uFEB4'] = '\u0633';
+ t['\uFEB5'] = '\u0634';
+ t['\uFEB6'] = '\u0634';
+ t['\uFEB7'] = '\u0634';
+ t['\uFEB8'] = '\u0634';
+ t['\uFEB9'] = '\u0635';
+ t['\uFEBA'] = '\u0635';
+ t['\uFEBB'] = '\u0635';
+ t['\uFEBC'] = '\u0635';
+ t['\uFEBD'] = '\u0636';
+ t['\uFEBE'] = '\u0636';
+ t['\uFEBF'] = '\u0636';
+ t['\uFEC0'] = '\u0636';
+ t['\uFEC1'] = '\u0637';
+ t['\uFEC2'] = '\u0637';
+ t['\uFEC3'] = '\u0637';
+ t['\uFEC4'] = '\u0637';
+ t['\uFEC5'] = '\u0638';
+ t['\uFEC6'] = '\u0638';
+ t['\uFEC7'] = '\u0638';
+ t['\uFEC8'] = '\u0638';
+ t['\uFEC9'] = '\u0639';
+ t['\uFECA'] = '\u0639';
+ t['\uFECB'] = '\u0639';
+ t['\uFECC'] = '\u0639';
+ t['\uFECD'] = '\u063A';
+ t['\uFECE'] = '\u063A';
+ t['\uFECF'] = '\u063A';
+ t['\uFED0'] = '\u063A';
+ t['\uFED1'] = '\u0641';
+ t['\uFED2'] = '\u0641';
+ t['\uFED3'] = '\u0641';
+ t['\uFED4'] = '\u0641';
+ t['\uFED5'] = '\u0642';
+ t['\uFED6'] = '\u0642';
+ t['\uFED7'] = '\u0642';
+ t['\uFED8'] = '\u0642';
+ t['\uFED9'] = '\u0643';
+ t['\uFEDA'] = '\u0643';
+ t['\uFEDB'] = '\u0643';
+ t['\uFEDC'] = '\u0643';
+ t['\uFEDD'] = '\u0644';
+ t['\uFEDE'] = '\u0644';
+ t['\uFEDF'] = '\u0644';
+ t['\uFEE0'] = '\u0644';
+ t['\uFEE1'] = '\u0645';
+ t['\uFEE2'] = '\u0645';
+ t['\uFEE3'] = '\u0645';
+ t['\uFEE4'] = '\u0645';
+ t['\uFEE5'] = '\u0646';
+ t['\uFEE6'] = '\u0646';
+ t['\uFEE7'] = '\u0646';
+ t['\uFEE8'] = '\u0646';
+ t['\uFEE9'] = '\u0647';
+ t['\uFEEA'] = '\u0647';
+ t['\uFEEB'] = '\u0647';
+ t['\uFEEC'] = '\u0647';
+ t['\uFEED'] = '\u0648';
+ t['\uFEEE'] = '\u0648';
+ t['\uFEEF'] = '\u0649';
+ t['\uFEF0'] = '\u0649';
+ t['\uFEF1'] = '\u064A';
+ t['\uFEF2'] = '\u064A';
+ t['\uFEF3'] = '\u064A';
+ t['\uFEF4'] = '\u064A';
+ t['\uFEF5'] = '\u0644\u0622';
+ t['\uFEF6'] = '\u0644\u0622';
+ t['\uFEF7'] = '\u0644\u0623';
+ t['\uFEF8'] = '\u0644\u0623';
+ t['\uFEF9'] = '\u0644\u0625';
+ t['\uFEFA'] = '\u0644\u0625';
+ t['\uFEFB'] = '\u0644\u0627';
+ t['\uFEFC'] = '\u0644\u0627';
+ });
+ function reverseIfRtl(chars) {
+ var charsLength = chars.length;
+ //reverse an arabic ligature
+ if (charsLength <= 1 || !isRTLRangeFor(chars.charCodeAt(0))) {
+ return chars;
+ }
+ var s = '';
+ for (var ii = charsLength - 1; ii >= 0; ii--) {
+ s += chars[ii];
+ }
+ return s;
+ }
+ exports.mapSpecialUnicodeValues = mapSpecialUnicodeValues;
+ exports.reverseIfRtl = reverseIfRtl;
+ exports.getUnicodeRangeFor = getUnicodeRangeFor;
+ exports.getNormalizedUnicodes = getNormalizedUnicodes;
+ exports.getUnicodeForGlyph = getUnicodeForGlyph;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreStream = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreJbig2, root.pdfjsCoreJpg, root.pdfjsCoreJpx);
+ }(this, function (exports, sharedUtil, corePrimitives, coreJbig2, coreJpg, coreJpx) {
+ var Util = sharedUtil.Util;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isInt = sharedUtil.isInt;
+ var isArray = sharedUtil.isArray;
+ var createObjectURL = sharedUtil.createObjectURL;
+ var shadow = sharedUtil.shadow;
+ var warn = sharedUtil.warn;
+ var isSpace = sharedUtil.isSpace;
+ var Dict = corePrimitives.Dict;
+ var isDict = corePrimitives.isDict;
+ var isStream = corePrimitives.isStream;
+ var Jbig2Image = coreJbig2.Jbig2Image;
+ var JpegImage = coreJpg.JpegImage;
+ var JpxImage = coreJpx.JpxImage;
+ var Stream = function StreamClosure() {
+ function Stream(arrayBuffer, start, length, dict) {
+ this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer);
+ this.start = start || 0;
+ this.pos = this.start;
+ this.end = start + length || this.bytes.length;
+ this.dict = dict;
+ }
+ // required methods for a stream. if a particular stream does not
+ // implement these, an error should be thrown
+ Stream.prototype = {
+ get length() {
+ return this.end - this.start;
+ },
+ get isEmpty() {
+ return this.length === 0;
+ },
+ getByte: function Stream_getByte() {
+ if (this.pos >= this.end) {
+ return -1;
+ }
+ return this.bytes[this.pos++];
+ },
+ getUint16: function Stream_getUint16() {
+ var b0 = this.getByte();
+ var b1 = this.getByte();
+ if (b0 === -1 || b1 === -1) {
+ return -1;
+ }
+ return (b0 << 8) + b1;
+ },
+ getInt32: function Stream_getInt32() {
+ var b0 = this.getByte();
+ var b1 = this.getByte();
+ var b2 = this.getByte();
+ var b3 = this.getByte();
+ return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
+ },
+ // returns subarray of original buffer
+ // should only be read
+ getBytes: function Stream_getBytes(length) {
+ var bytes = this.bytes;
+ var pos = this.pos;
+ var strEnd = this.end;
+ if (!length) {
+ return bytes.subarray(pos, strEnd);
+ }
+ var end = pos + length;
+ if (end > strEnd) {
+ end = strEnd;
+ }
+ this.pos = end;
+ return bytes.subarray(pos, end);
+ },
+ peekByte: function Stream_peekByte() {
+ var peekedByte = this.getByte();
+ this.pos--;
+ return peekedByte;
+ },
+ peekBytes: function Stream_peekBytes(length) {
+ var bytes = this.getBytes(length);
+ this.pos -= bytes.length;
+ return bytes;
+ },
+ skip: function Stream_skip(n) {
+ if (!n) {
+ n = 1;
+ }
+ this.pos += n;
+ },
+ reset: function Stream_reset() {
+ this.pos = this.start;
+ },
+ moveStart: function Stream_moveStart() {
+ this.start = this.pos;
+ },
+ makeSubStream: function Stream_makeSubStream(start, length, dict) {
+ return new Stream(this.bytes.buffer, start, length, dict);
+ },
+ isStream: true
+ };
+ return Stream;
+ }();
+ var StringStream = function StringStreamClosure() {
+ function StringStream(str) {
+ var length = str.length;
+ var bytes = new Uint8Array(length);
+ for (var n = 0; n < length; ++n) {
+ bytes[n] = str.charCodeAt(n);
+ }
+ Stream.call(this, bytes);
+ }
+ StringStream.prototype = Stream.prototype;
+ return StringStream;
+ }();
+ // super class for the decoding streams
+ var DecodeStream = function DecodeStreamClosure() {
+ // Lots of DecodeStreams are created whose buffers are never used. For these
+ // we share a single empty buffer. This is (a) space-efficient and (b) avoids
+ // having special cases that would be required if we used |null| for an empty
+ // buffer.
+ var emptyBuffer = new Uint8Array(0);
+ function DecodeStream(maybeMinBufferLength) {
+ this.pos = 0;
+ this.bufferLength = 0;
+ this.eof = false;
+ this.buffer = emptyBuffer;
+ this.minBufferLength = 512;
+ if (maybeMinBufferLength) {
+ // Compute the first power of two that is as big as maybeMinBufferLength.
+ while (this.minBufferLength < maybeMinBufferLength) {
+ this.minBufferLength *= 2;
+ }
+ }
+ }
+ DecodeStream.prototype = {
+ get isEmpty() {
+ while (!this.eof && this.bufferLength === 0) {
+ this.readBlock();
+ }
+ return this.bufferLength === 0;
+ },
+ ensureBuffer: function DecodeStream_ensureBuffer(requested) {
+ var buffer = this.buffer;
+ if (requested <= buffer.byteLength) {
+ return buffer;
+ }
+ var size = this.minBufferLength;
+ while (size < requested) {
+ size *= 2;
+ }
+ var buffer2 = new Uint8Array(size);
+ buffer2.set(buffer);
+ return this.buffer = buffer2;
+ },
+ getByte: function DecodeStream_getByte() {
+ var pos = this.pos;
+ while (this.bufferLength <= pos) {
+ if (this.eof) {
+ return -1;
+ }
+ this.readBlock();
+ }
+ return this.buffer[this.pos++];
+ },
+ getUint16: function DecodeStream_getUint16() {
+ var b0 = this.getByte();
+ var b1 = this.getByte();
+ if (b0 === -1 || b1 === -1) {
+ return -1;
+ }
+ return (b0 << 8) + b1;
+ },
+ getInt32: function DecodeStream_getInt32() {
+ var b0 = this.getByte();
+ var b1 = this.getByte();
+ var b2 = this.getByte();
+ var b3 = this.getByte();
+ return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
+ },
+ getBytes: function DecodeStream_getBytes(length) {
+ var end, pos = this.pos;
+ if (length) {
+ this.ensureBuffer(pos + length);
+ end = pos + length;
+ while (!this.eof && this.bufferLength < end) {
+ this.readBlock();
+ }
+ var bufEnd = this.bufferLength;
+ if (end > bufEnd) {
+ end = bufEnd;
+ }
+ } else {
+ while (!this.eof) {
+ this.readBlock();
+ }
+ end = this.bufferLength;
+ }
+ this.pos = end;
+ return this.buffer.subarray(pos, end);
+ },
+ peekByte: function DecodeStream_peekByte() {
+ var peekedByte = this.getByte();
+ this.pos--;
+ return peekedByte;
+ },
+ peekBytes: function DecodeStream_peekBytes(length) {
+ var bytes = this.getBytes(length);
+ this.pos -= bytes.length;
+ return bytes;
+ },
+ makeSubStream: function DecodeStream_makeSubStream(start, length, dict) {
+ var end = start + length;
+ while (this.bufferLength <= end && !this.eof) {
+ this.readBlock();
+ }
+ return new Stream(this.buffer, start, length, dict);
+ },
+ skip: function DecodeStream_skip(n) {
+ if (!n) {
+ n = 1;
+ }
+ this.pos += n;
+ },
+ reset: function DecodeStream_reset() {
+ this.pos = 0;
+ },
+ getBaseStreams: function DecodeStream_getBaseStreams() {
+ if (this.str && this.str.getBaseStreams) {
+ return this.str.getBaseStreams();
+ }
+ return [];
+ }
+ };
+ return DecodeStream;
+ }();
+ var StreamsSequenceStream = function StreamsSequenceStreamClosure() {
+ function StreamsSequenceStream(streams) {
+ this.streams = streams;
+ DecodeStream.call(this, /* maybeLength = */
+ null);
+ }
+ StreamsSequenceStream.prototype = Object.create(DecodeStream.prototype);
+ StreamsSequenceStream.prototype.readBlock = function streamSequenceStreamReadBlock() {
+ var streams = this.streams;
+ if (streams.length === 0) {
+ this.eof = true;
+ return;
+ }
+ var stream = streams.shift();
+ var chunk = stream.getBytes();
+ var bufferLength = this.bufferLength;
+ var newLength = bufferLength + chunk.length;
+ var buffer = this.ensureBuffer(newLength);
+ buffer.set(chunk, bufferLength);
+ this.bufferLength = newLength;
+ };
+ StreamsSequenceStream.prototype.getBaseStreams = function StreamsSequenceStream_getBaseStreams() {
+ var baseStreams = [];
+ for (var i = 0, ii = this.streams.length; i < ii; i++) {
+ var stream = this.streams[i];
+ if (stream.getBaseStreams) {
+ Util.appendToArray(baseStreams, stream.getBaseStreams());
+ }
+ }
+ return baseStreams;
+ };
+ return StreamsSequenceStream;
+ }();
+ var FlateStream = function FlateStreamClosure() {
+ var codeLenCodeMap = new Int32Array([
+ 16,
+ 17,
+ 18,
+ 0,
+ 8,
+ 7,
+ 9,
+ 6,
+ 10,
+ 5,
+ 11,
+ 4,
+ 12,
+ 3,
+ 13,
+ 2,
+ 14,
+ 1,
+ 15
+ ]);
+ var lengthDecode = new Int32Array([
+ 0x00003,
+ 0x00004,
+ 0x00005,
+ 0x00006,
+ 0x00007,
+ 0x00008,
+ 0x00009,
+ 0x0000a,
+ 0x1000b,
+ 0x1000d,
+ 0x1000f,
+ 0x10011,
+ 0x20013,
+ 0x20017,
+ 0x2001b,
+ 0x2001f,
+ 0x30023,
+ 0x3002b,
+ 0x30033,
+ 0x3003b,
+ 0x40043,
+ 0x40053,
+ 0x40063,
+ 0x40073,
+ 0x50083,
+ 0x500a3,
+ 0x500c3,
+ 0x500e3,
+ 0x00102,
+ 0x00102,
+ 0x00102
+ ]);
+ var distDecode = new Int32Array([
+ 0x00001,
+ 0x00002,
+ 0x00003,
+ 0x00004,
+ 0x10005,
+ 0x10007,
+ 0x20009,
+ 0x2000d,
+ 0x30011,
+ 0x30019,
+ 0x40021,
+ 0x40031,
+ 0x50041,
+ 0x50061,
+ 0x60081,
+ 0x600c1,
+ 0x70101,
+ 0x70181,
+ 0x80201,
+ 0x80301,
+ 0x90401,
+ 0x90601,
+ 0xa0801,
+ 0xa0c01,
+ 0xb1001,
+ 0xb1801,
+ 0xc2001,
+ 0xc3001,
+ 0xd4001,
+ 0xd6001
+ ]);
+ var fixedLitCodeTab = [
+ new Int32Array([
+ 0x70100,
+ 0x80050,
+ 0x80010,
+ 0x80118,
+ 0x70110,
+ 0x80070,
+ 0x80030,
+ 0x900c0,
+ 0x70108,
+ 0x80060,
+ 0x80020,
+ 0x900a0,
+ 0x80000,
+ 0x80080,
+ 0x80040,
+ 0x900e0,
+ 0x70104,
+ 0x80058,
+ 0x80018,
+ 0x90090,
+ 0x70114,
+ 0x80078,
+ 0x80038,
+ 0x900d0,
+ 0x7010c,
+ 0x80068,
+ 0x80028,
+ 0x900b0,
+ 0x80008,
+ 0x80088,
+ 0x80048,
+ 0x900f0,
+ 0x70102,
+ 0x80054,
+ 0x80014,
+ 0x8011c,
+ 0x70112,
+ 0x80074,
+ 0x80034,
+ 0x900c8,
+ 0x7010a,
+ 0x80064,
+ 0x80024,
+ 0x900a8,
+ 0x80004,
+ 0x80084,
+ 0x80044,
+ 0x900e8,
+ 0x70106,
+ 0x8005c,
+ 0x8001c,
+ 0x90098,
+ 0x70116,
+ 0x8007c,
+ 0x8003c,
+ 0x900d8,
+ 0x7010e,
+ 0x8006c,
+ 0x8002c,
+ 0x900b8,
+ 0x8000c,
+ 0x8008c,
+ 0x8004c,
+ 0x900f8,
+ 0x70101,
+ 0x80052,
+ 0x80012,
+ 0x8011a,
+ 0x70111,
+ 0x80072,
+ 0x80032,
+ 0x900c4,
+ 0x70109,
+ 0x80062,
+ 0x80022,
+ 0x900a4,
+ 0x80002,
+ 0x80082,
+ 0x80042,
+ 0x900e4,
+ 0x70105,
+ 0x8005a,
+ 0x8001a,
+ 0x90094,
+ 0x70115,
+ 0x8007a,
+ 0x8003a,
+ 0x900d4,
+ 0x7010d,
+ 0x8006a,
+ 0x8002a,
+ 0x900b4,
+ 0x8000a,
+ 0x8008a,
+ 0x8004a,
+ 0x900f4,
+ 0x70103,
+ 0x80056,
+ 0x80016,
+ 0x8011e,
+ 0x70113,
+ 0x80076,
+ 0x80036,
+ 0x900cc,
+ 0x7010b,
+ 0x80066,
+ 0x80026,
+ 0x900ac,
+ 0x80006,
+ 0x80086,
+ 0x80046,
+ 0x900ec,
+ 0x70107,
+ 0x8005e,
+ 0x8001e,
+ 0x9009c,
+ 0x70117,
+ 0x8007e,
+ 0x8003e,
+ 0x900dc,
+ 0x7010f,
+ 0x8006e,
+ 0x8002e,
+ 0x900bc,
+ 0x8000e,
+ 0x8008e,
+ 0x8004e,
+ 0x900fc,
+ 0x70100,
+ 0x80051,
+ 0x80011,
+ 0x80119,
+ 0x70110,
+ 0x80071,
+ 0x80031,
+ 0x900c2,
+ 0x70108,
+ 0x80061,
+ 0x80021,
+ 0x900a2,
+ 0x80001,
+ 0x80081,
+ 0x80041,
+ 0x900e2,
+ 0x70104,
+ 0x80059,
+ 0x80019,
+ 0x90092,
+ 0x70114,
+ 0x80079,
+ 0x80039,
+ 0x900d2,
+ 0x7010c,
+ 0x80069,
+ 0x80029,
+ 0x900b2,
+ 0x80009,
+ 0x80089,
+ 0x80049,
+ 0x900f2,
+ 0x70102,
+ 0x80055,
+ 0x80015,
+ 0x8011d,
+ 0x70112,
+ 0x80075,
+ 0x80035,
+ 0x900ca,
+ 0x7010a,
+ 0x80065,
+ 0x80025,
+ 0x900aa,
+ 0x80005,
+ 0x80085,
+ 0x80045,
+ 0x900ea,
+ 0x70106,
+ 0x8005d,
+ 0x8001d,
+ 0x9009a,
+ 0x70116,
+ 0x8007d,
+ 0x8003d,
+ 0x900da,
+ 0x7010e,
+ 0x8006d,
+ 0x8002d,
+ 0x900ba,
+ 0x8000d,
+ 0x8008d,
+ 0x8004d,
+ 0x900fa,
+ 0x70101,
+ 0x80053,
+ 0x80013,
+ 0x8011b,
+ 0x70111,
+ 0x80073,
+ 0x80033,
+ 0x900c6,
+ 0x70109,
+ 0x80063,
+ 0x80023,
+ 0x900a6,
+ 0x80003,
+ 0x80083,
+ 0x80043,
+ 0x900e6,
+ 0x70105,
+ 0x8005b,
+ 0x8001b,
+ 0x90096,
+ 0x70115,
+ 0x8007b,
+ 0x8003b,
+ 0x900d6,
+ 0x7010d,
+ 0x8006b,
+ 0x8002b,
+ 0x900b6,
+ 0x8000b,
+ 0x8008b,
+ 0x8004b,
+ 0x900f6,
+ 0x70103,
+ 0x80057,
+ 0x80017,
+ 0x8011f,
+ 0x70113,
+ 0x80077,
+ 0x80037,
+ 0x900ce,
+ 0x7010b,
+ 0x80067,
+ 0x80027,
+ 0x900ae,
+ 0x80007,
+ 0x80087,
+ 0x80047,
+ 0x900ee,
+ 0x70107,
+ 0x8005f,
+ 0x8001f,
+ 0x9009e,
+ 0x70117,
+ 0x8007f,
+ 0x8003f,
+ 0x900de,
+ 0x7010f,
+ 0x8006f,
+ 0x8002f,
+ 0x900be,
+ 0x8000f,
+ 0x8008f,
+ 0x8004f,
+ 0x900fe,
+ 0x70100,
+ 0x80050,
+ 0x80010,
+ 0x80118,
+ 0x70110,
+ 0x80070,
+ 0x80030,
+ 0x900c1,
+ 0x70108,
+ 0x80060,
+ 0x80020,
+ 0x900a1,
+ 0x80000,
+ 0x80080,
+ 0x80040,
+ 0x900e1,
+ 0x70104,
+ 0x80058,
+ 0x80018,
+ 0x90091,
+ 0x70114,
+ 0x80078,
+ 0x80038,
+ 0x900d1,
+ 0x7010c,
+ 0x80068,
+ 0x80028,
+ 0x900b1,
+ 0x80008,
+ 0x80088,
+ 0x80048,
+ 0x900f1,
+ 0x70102,
+ 0x80054,
+ 0x80014,
+ 0x8011c,
+ 0x70112,
+ 0x80074,
+ 0x80034,
+ 0x900c9,
+ 0x7010a,
+ 0x80064,
+ 0x80024,
+ 0x900a9,
+ 0x80004,
+ 0x80084,
+ 0x80044,
+ 0x900e9,
+ 0x70106,
+ 0x8005c,
+ 0x8001c,
+ 0x90099,
+ 0x70116,
+ 0x8007c,
+ 0x8003c,
+ 0x900d9,
+ 0x7010e,
+ 0x8006c,
+ 0x8002c,
+ 0x900b9,
+ 0x8000c,
+ 0x8008c,
+ 0x8004c,
+ 0x900f9,
+ 0x70101,
+ 0x80052,
+ 0x80012,
+ 0x8011a,
+ 0x70111,
+ 0x80072,
+ 0x80032,
+ 0x900c5,
+ 0x70109,
+ 0x80062,
+ 0x80022,
+ 0x900a5,
+ 0x80002,
+ 0x80082,
+ 0x80042,
+ 0x900e5,
+ 0x70105,
+ 0x8005a,
+ 0x8001a,
+ 0x90095,
+ 0x70115,
+ 0x8007a,
+ 0x8003a,
+ 0x900d5,
+ 0x7010d,
+ 0x8006a,
+ 0x8002a,
+ 0x900b5,
+ 0x8000a,
+ 0x8008a,
+ 0x8004a,
+ 0x900f5,
+ 0x70103,
+ 0x80056,
+ 0x80016,
+ 0x8011e,
+ 0x70113,
+ 0x80076,
+ 0x80036,
+ 0x900cd,
+ 0x7010b,
+ 0x80066,
+ 0x80026,
+ 0x900ad,
+ 0x80006,
+ 0x80086,
+ 0x80046,
+ 0x900ed,
+ 0x70107,
+ 0x8005e,
+ 0x8001e,
+ 0x9009d,
+ 0x70117,
+ 0x8007e,
+ 0x8003e,
+ 0x900dd,
+ 0x7010f,
+ 0x8006e,
+ 0x8002e,
+ 0x900bd,
+ 0x8000e,
+ 0x8008e,
+ 0x8004e,
+ 0x900fd,
+ 0x70100,
+ 0x80051,
+ 0x80011,
+ 0x80119,
+ 0x70110,
+ 0x80071,
+ 0x80031,
+ 0x900c3,
+ 0x70108,
+ 0x80061,
+ 0x80021,
+ 0x900a3,
+ 0x80001,
+ 0x80081,
+ 0x80041,
+ 0x900e3,
+ 0x70104,
+ 0x80059,
+ 0x80019,
+ 0x90093,
+ 0x70114,
+ 0x80079,
+ 0x80039,
+ 0x900d3,
+ 0x7010c,
+ 0x80069,
+ 0x80029,
+ 0x900b3,
+ 0x80009,
+ 0x80089,
+ 0x80049,
+ 0x900f3,
+ 0x70102,
+ 0x80055,
+ 0x80015,
+ 0x8011d,
+ 0x70112,
+ 0x80075,
+ 0x80035,
+ 0x900cb,
+ 0x7010a,
+ 0x80065,
+ 0x80025,
+ 0x900ab,
+ 0x80005,
+ 0x80085,
+ 0x80045,
+ 0x900eb,
+ 0x70106,
+ 0x8005d,
+ 0x8001d,
+ 0x9009b,
+ 0x70116,
+ 0x8007d,
+ 0x8003d,
+ 0x900db,
+ 0x7010e,
+ 0x8006d,
+ 0x8002d,
+ 0x900bb,
+ 0x8000d,
+ 0x8008d,
+ 0x8004d,
+ 0x900fb,
+ 0x70101,
+ 0x80053,
+ 0x80013,
+ 0x8011b,
+ 0x70111,
+ 0x80073,
+ 0x80033,
+ 0x900c7,
+ 0x70109,
+ 0x80063,
+ 0x80023,
+ 0x900a7,
+ 0x80003,
+ 0x80083,
+ 0x80043,
+ 0x900e7,
+ 0x70105,
+ 0x8005b,
+ 0x8001b,
+ 0x90097,
+ 0x70115,
+ 0x8007b,
+ 0x8003b,
+ 0x900d7,
+ 0x7010d,
+ 0x8006b,
+ 0x8002b,
+ 0x900b7,
+ 0x8000b,
+ 0x8008b,
+ 0x8004b,
+ 0x900f7,
+ 0x70103,
+ 0x80057,
+ 0x80017,
+ 0x8011f,
+ 0x70113,
+ 0x80077,
+ 0x80037,
+ 0x900cf,
+ 0x7010b,
+ 0x80067,
+ 0x80027,
+ 0x900af,
+ 0x80007,
+ 0x80087,
+ 0x80047,
+ 0x900ef,
+ 0x70107,
+ 0x8005f,
+ 0x8001f,
+ 0x9009f,
+ 0x70117,
+ 0x8007f,
+ 0x8003f,
+ 0x900df,
+ 0x7010f,
+ 0x8006f,
+ 0x8002f,
+ 0x900bf,
+ 0x8000f,
+ 0x8008f,
+ 0x8004f,
+ 0x900ff
+ ]),
+ 9
+ ];
+ var fixedDistCodeTab = [
+ new Int32Array([
+ 0x50000,
+ 0x50010,
+ 0x50008,
+ 0x50018,
+ 0x50004,
+ 0x50014,
+ 0x5000c,
+ 0x5001c,
+ 0x50002,
+ 0x50012,
+ 0x5000a,
+ 0x5001a,
+ 0x50006,
+ 0x50016,
+ 0x5000e,
+ 0x00000,
+ 0x50001,
+ 0x50011,
+ 0x50009,
+ 0x50019,
+ 0x50005,
+ 0x50015,
+ 0x5000d,
+ 0x5001d,
+ 0x50003,
+ 0x50013,
+ 0x5000b,
+ 0x5001b,
+ 0x50007,
+ 0x50017,
+ 0x5000f,
+ 0x00000
+ ]),
+ 5
+ ];
+ function FlateStream(str, maybeLength) {
+ this.str = str;
+ this.dict = str.dict;
+ var cmf = str.getByte();
+ var flg = str.getByte();
+ if (cmf === -1 || flg === -1) {
+ error('Invalid header in flate stream: ' + cmf + ', ' + flg);
+ }
+ if ((cmf & 0x0f) !== 0x08) {
+ error('Unknown compression method in flate stream: ' + cmf + ', ' + flg);
+ }
+ if (((cmf << 8) + flg) % 31 !== 0) {
+ error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg);
+ }
+ if (flg & 0x20) {
+ error('FDICT bit set in flate stream: ' + cmf + ', ' + flg);
+ }
+ this.codeSize = 0;
+ this.codeBuf = 0;
+ DecodeStream.call(this, maybeLength);
+ }
+ FlateStream.prototype = Object.create(DecodeStream.prototype);
+ FlateStream.prototype.getBits = function FlateStream_getBits(bits) {
+ var str = this.str;
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var b;
+ while (codeSize < bits) {
+ if ((b = str.getByte()) === -1) {
+ error('Bad encoding in flate stream');
+ }
+ codeBuf |= b << codeSize;
+ codeSize += 8;
+ }
+ b = codeBuf & (1 << bits) - 1;
+ this.codeBuf = codeBuf >> bits;
+ this.codeSize = codeSize -= bits;
+ return b;
+ };
+ FlateStream.prototype.getCode = function FlateStream_getCode(table) {
+ var str = this.str;
+ var codes = table[0];
+ var maxLen = table[1];
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var b;
+ while (codeSize < maxLen) {
+ if ((b = str.getByte()) === -1) {
+ // premature end of stream. code might however still be valid.
+ // codeSize < codeLen check below guards against incomplete codeVal.
+ break;
+ }
+ codeBuf |= b << codeSize;
+ codeSize += 8;
+ }
+ var code = codes[codeBuf & (1 << maxLen) - 1];
+ var codeLen = code >> 16;
+ var codeVal = code & 0xffff;
+ if (codeLen < 1 || codeSize < codeLen) {
+ error('Bad encoding in flate stream');
+ }
+ this.codeBuf = codeBuf >> codeLen;
+ this.codeSize = codeSize - codeLen;
+ return codeVal;
+ };
+ FlateStream.prototype.generateHuffmanTable = function flateStreamGenerateHuffmanTable(lengths) {
+ var n = lengths.length;
+ // find max code length
+ var maxLen = 0;
+ var i;
+ for (i = 0; i < n; ++i) {
+ if (lengths[i] > maxLen) {
+ maxLen = lengths[i];
+ }
+ }
+ // build the table
+ var size = 1 << maxLen;
+ var codes = new Int32Array(size);
+ for (var len = 1, code = 0, skip = 2; len <= maxLen; ++len, code <<= 1, skip <<= 1) {
+ for (var val = 0; val < n; ++val) {
+ if (lengths[val] === len) {
+ // bit-reverse the code
+ var code2 = 0;
+ var t = code;
+ for (i = 0; i < len; ++i) {
+ code2 = code2 << 1 | t & 1;
+ t >>= 1;
+ }
+ // fill the table entries
+ for (i = code2; i < size; i += skip) {
+ codes[i] = len << 16 | val;
+ }
+ ++code;
+ }
+ }
+ }
+ return [
+ codes,
+ maxLen
+ ];
+ };
+ FlateStream.prototype.readBlock = function FlateStream_readBlock() {
+ var buffer, len;
+ var str = this.str;
+ // read block header
+ var hdr = this.getBits(3);
+ if (hdr & 1) {
+ this.eof = true;
+ }
+ hdr >>= 1;
+ if (hdr === 0) {
+ // uncompressed block
+ var b;
+ if ((b = str.getByte()) === -1) {
+ error('Bad block header in flate stream');
+ }
+ var blockLen = b;
+ if ((b = str.getByte()) === -1) {
+ error('Bad block header in flate stream');
+ }
+ blockLen |= b << 8;
+ if ((b = str.getByte()) === -1) {
+ error('Bad block header in flate stream');
+ }
+ var check = b;
+ if ((b = str.getByte()) === -1) {
+ error('Bad block header in flate stream');
+ }
+ check |= b << 8;
+ if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) {
+ // Ignoring error for bad "empty" block (see issue 1277)
+ error('Bad uncompressed block length in flate stream');
+ }
+ this.codeBuf = 0;
+ this.codeSize = 0;
+ var bufferLength = this.bufferLength;
+ buffer = this.ensureBuffer(bufferLength + blockLen);
+ var end = bufferLength + blockLen;
+ this.bufferLength = end;
+ if (blockLen === 0) {
+ if (str.peekByte() === -1) {
+ this.eof = true;
+ }
+ } else {
+ for (var n = bufferLength; n < end; ++n) {
+ if ((b = str.getByte()) === -1) {
+ this.eof = true;
+ break;
+ }
+ buffer[n] = b;
+ }
+ }
+ return;
+ }
+ var litCodeTable;
+ var distCodeTable;
+ if (hdr === 1) {
+ // compressed block, fixed codes
+ litCodeTable = fixedLitCodeTab;
+ distCodeTable = fixedDistCodeTab;
+ } else if (hdr === 2) {
+ // compressed block, dynamic codes
+ var numLitCodes = this.getBits(5) + 257;
+ var numDistCodes = this.getBits(5) + 1;
+ var numCodeLenCodes = this.getBits(4) + 4;
+ // build the code lengths code table
+ var codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length);
+ var i;
+ for (i = 0; i < numCodeLenCodes; ++i) {
+ codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3);
+ }
+ var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);
+ // build the literal and distance code tables
+ len = 0;
+ i = 0;
+ var codes = numLitCodes + numDistCodes;
+ var codeLengths = new Uint8Array(codes);
+ var bitsLength, bitsOffset, what;
+ while (i < codes) {
+ var code = this.getCode(codeLenCodeTab);
+ if (code === 16) {
+ bitsLength = 2;
+ bitsOffset = 3;
+ what = len;
+ } else if (code === 17) {
+ bitsLength = 3;
+ bitsOffset = 3;
+ what = len = 0;
+ } else if (code === 18) {
+ bitsLength = 7;
+ bitsOffset = 11;
+ what = len = 0;
+ } else {
+ codeLengths[i++] = len = code;
+ continue;
+ }
+ var repeatLength = this.getBits(bitsLength) + bitsOffset;
+ while (repeatLength-- > 0) {
+ codeLengths[i++] = what;
+ }
+ }
+ litCodeTable = this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes));
+ distCodeTable = this.generateHuffmanTable(codeLengths.subarray(numLitCodes, codes));
+ } else {
+ error('Unknown block type in flate stream');
+ }
+ buffer = this.buffer;
+ var limit = buffer ? buffer.length : 0;
+ var pos = this.bufferLength;
+ while (true) {
+ var code1 = this.getCode(litCodeTable);
+ if (code1 < 256) {
+ if (pos + 1 >= limit) {
+ buffer = this.ensureBuffer(pos + 1);
+ limit = buffer.length;
+ }
+ buffer[pos++] = code1;
+ continue;
+ }
+ if (code1 === 256) {
+ this.bufferLength = pos;
+ return;
+ }
+ code1 -= 257;
+ code1 = lengthDecode[code1];
+ var code2 = code1 >> 16;
+ if (code2 > 0) {
+ code2 = this.getBits(code2);
+ }
+ len = (code1 & 0xffff) + code2;
+ code1 = this.getCode(distCodeTable);
+ code1 = distDecode[code1];
+ code2 = code1 >> 16;
+ if (code2 > 0) {
+ code2 = this.getBits(code2);
+ }
+ var dist = (code1 & 0xffff) + code2;
+ if (pos + len >= limit) {
+ buffer = this.ensureBuffer(pos + len);
+ limit = buffer.length;
+ }
+ for (var k = 0; k < len; ++k, ++pos) {
+ buffer[pos] = buffer[pos - dist];
+ }
+ }
+ };
+ return FlateStream;
+ }();
+ var PredictorStream = function PredictorStreamClosure() {
+ function PredictorStream(str, maybeLength, params) {
+ if (!isDict(params)) {
+ return str;
+ }
+ // no prediction
+ var predictor = this.predictor = params.get('Predictor') || 1;
+ if (predictor <= 1) {
+ return str;
+ }
+ // no prediction
+ if (predictor !== 2 && (predictor < 10 || predictor > 15)) {
+ error('Unsupported predictor: ' + predictor);
+ }
+ if (predictor === 2) {
+ this.readBlock = this.readBlockTiff;
+ } else {
+ this.readBlock = this.readBlockPng;
+ }
+ this.str = str;
+ this.dict = str.dict;
+ var colors = this.colors = params.get('Colors') || 1;
+ var bits = this.bits = params.get('BitsPerComponent') || 8;
+ var columns = this.columns = params.get('Columns') || 1;
+ this.pixBytes = colors * bits + 7 >> 3;
+ this.rowBytes = columns * colors * bits + 7 >> 3;
+ DecodeStream.call(this, maybeLength);
+ return this;
+ }
+ PredictorStream.prototype = Object.create(DecodeStream.prototype);
+ PredictorStream.prototype.readBlockTiff = function predictorStreamReadBlockTiff() {
+ var rowBytes = this.rowBytes;
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + rowBytes);
+ var bits = this.bits;
+ var colors = this.colors;
+ var rawBytes = this.str.getBytes(rowBytes);
+ this.eof = !rawBytes.length;
+ if (this.eof) {
+ return;
+ }
+ var inbuf = 0, outbuf = 0;
+ var inbits = 0, outbits = 0;
+ var pos = bufferLength;
+ var i;
+ if (bits === 1) {
+ for (i = 0; i < rowBytes; ++i) {
+ var c = rawBytes[i];
+ inbuf = inbuf << 8 | c;
+ // bitwise addition is exclusive or
+ // first shift inbuf and then add
+ buffer[pos++] = (c ^ inbuf >> colors) & 0xFF;
+ // truncate inbuf (assumes colors < 16)
+ inbuf &= 0xFFFF;
+ }
+ } else if (bits === 8) {
+ for (i = 0; i < colors; ++i) {
+ buffer[pos++] = rawBytes[i];
+ }
+ for (; i < rowBytes; ++i) {
+ buffer[pos] = buffer[pos - colors] + rawBytes[i];
+ pos++;
+ }
+ } else {
+ var compArray = new Uint8Array(colors + 1);
+ var bitMask = (1 << bits) - 1;
+ var j = 0, k = bufferLength;
+ var columns = this.columns;
+ for (i = 0; i < columns; ++i) {
+ for (var kk = 0; kk < colors; ++kk) {
+ if (inbits < bits) {
+ inbuf = inbuf << 8 | rawBytes[j++] & 0xFF;
+ inbits += 8;
+ }
+ compArray[kk] = compArray[kk] + (inbuf >> inbits - bits) & bitMask;
+ inbits -= bits;
+ outbuf = outbuf << bits | compArray[kk];
+ outbits += bits;
+ if (outbits >= 8) {
+ buffer[k++] = outbuf >> outbits - 8 & 0xFF;
+ outbits -= 8;
+ }
+ }
+ }
+ if (outbits > 0) {
+ buffer[k++] = (outbuf << 8 - outbits) + (inbuf & (1 << 8 - outbits) - 1);
+ }
+ }
+ this.bufferLength += rowBytes;
+ };
+ PredictorStream.prototype.readBlockPng = function predictorStreamReadBlockPng() {
+ var rowBytes = this.rowBytes;
+ var pixBytes = this.pixBytes;
+ var predictor = this.str.getByte();
+ var rawBytes = this.str.getBytes(rowBytes);
+ this.eof = !rawBytes.length;
+ if (this.eof) {
+ return;
+ }
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + rowBytes);
+ var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
+ if (prevRow.length === 0) {
+ prevRow = new Uint8Array(rowBytes);
+ }
+ var i, j = bufferLength, up, c;
+ switch (predictor) {
+ case 0:
+ for (i = 0; i < rowBytes; ++i) {
+ buffer[j++] = rawBytes[i];
+ }
+ break;
+ case 1:
+ for (i = 0; i < pixBytes; ++i) {
+ buffer[j++] = rawBytes[i];
+ }
+ for (; i < rowBytes; ++i) {
+ buffer[j] = buffer[j - pixBytes] + rawBytes[i] & 0xFF;
+ j++;
+ }
+ break;
+ case 2:
+ for (i = 0; i < rowBytes; ++i) {
+ buffer[j++] = prevRow[i] + rawBytes[i] & 0xFF;
+ }
+ break;
+ case 3:
+ for (i = 0; i < pixBytes; ++i) {
+ buffer[j++] = (prevRow[i] >> 1) + rawBytes[i];
+ }
+ for (; i < rowBytes; ++i) {
+ buffer[j] = (prevRow[i] + buffer[j - pixBytes] >> 1) + rawBytes[i] & 0xFF;
+ j++;
+ }
+ break;
+ case 4:
+ // we need to save the up left pixels values. the simplest way
+ // is to create a new buffer
+ for (i = 0; i < pixBytes; ++i) {
+ up = prevRow[i];
+ c = rawBytes[i];
+ buffer[j++] = up + c;
+ }
+ for (; i < rowBytes; ++i) {
+ up = prevRow[i];
+ var upLeft = prevRow[i - pixBytes];
+ var left = buffer[j - pixBytes];
+ var p = left + up - upLeft;
+ var pa = p - left;
+ if (pa < 0) {
+ pa = -pa;
+ }
+ var pb = p - up;
+ if (pb < 0) {
+ pb = -pb;
+ }
+ var pc = p - upLeft;
+ if (pc < 0) {
+ pc = -pc;
+ }
+ c = rawBytes[i];
+ if (pa <= pb && pa <= pc) {
+ buffer[j++] = left + c;
+ } else if (pb <= pc) {
+ buffer[j++] = up + c;
+ } else {
+ buffer[j++] = upLeft + c;
+ }
+ }
+ break;
+ default:
+ error('Unsupported predictor: ' + predictor);
+ }
+ this.bufferLength += rowBytes;
+ };
+ return PredictorStream;
+ }();
+ /**
+ * Depending on the type of JPEG a JpegStream is handled in different ways. For
+ * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image
+ * data is stored and then loaded by the browser. For unsupported JPEG's we use
+ * a library to decode these images and the stream behaves like all the other
+ * DecodeStreams.
+ */
+ var JpegStream = function JpegStreamClosure() {
+ function JpegStream(stream, maybeLength, dict, params) {
+ // Some images may contain 'junk' before the SOI (start-of-image) marker.
+ // Note: this seems to mainly affect inline images.
+ var ch;
+ while ((ch = stream.getByte()) !== -1) {
+ if (ch === 0xFF) {
+ // Find the first byte of the SOI marker (0xFFD8).
+ stream.skip(-1);
+ // Reset the stream position to the SOI.
+ break;
+ }
+ }
+ this.stream = stream;
+ this.maybeLength = maybeLength;
+ this.dict = dict;
+ this.params = params;
+ DecodeStream.call(this, maybeLength);
+ }
+ JpegStream.prototype = Object.create(DecodeStream.prototype);
+ Object.defineProperty(JpegStream.prototype, 'bytes', {
+ get: function JpegStream_bytes() {
+ // If this.maybeLength is null, we'll get the entire stream.
+ return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength));
+ },
+ configurable: true
+ });
+ JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) {
+ if (this.bufferLength) {
+ return;
+ }
+ var jpegImage = new JpegImage();
+ // Checking if values need to be transformed before conversion.
+ var decodeArr = this.dict.getArray('Decode', 'D');
+ if (this.forceRGB && isArray(decodeArr)) {
+ var bitsPerComponent = this.dict.get('BitsPerComponent') || 8;
+ var decodeArrLength = decodeArr.length;
+ var transform = new Int32Array(decodeArrLength);
+ var transformNeeded = false;
+ var maxValue = (1 << bitsPerComponent) - 1;
+ for (var i = 0; i < decodeArrLength; i += 2) {
+ transform[i] = (decodeArr[i + 1] - decodeArr[i]) * 256 | 0;
+ transform[i + 1] = decodeArr[i] * maxValue | 0;
+ if (transform[i] !== 256 || transform[i + 1] !== 0) {
+ transformNeeded = true;
+ }
+ }
+ if (transformNeeded) {
+ jpegImage.decodeTransform = transform;
+ }
+ }
+ // Fetching the 'ColorTransform' entry, if it exists.
+ if (isDict(this.params)) {
+ var colorTransform = this.params.get('ColorTransform');
+ if (isInt(colorTransform)) {
+ jpegImage.colorTransform = colorTransform;
+ }
+ }
+ jpegImage.parse(this.bytes);
+ var data = jpegImage.getData(this.drawWidth, this.drawHeight, this.forceRGB);
+ this.buffer = data;
+ this.bufferLength = data.length;
+ this.eof = true;
+ };
+ JpegStream.prototype.getBytes = function JpegStream_getBytes(length) {
+ this.ensureBuffer();
+ return this.buffer;
+ };
+ JpegStream.prototype.getIR = function JpegStream_getIR(forceDataSchema) {
+ return createObjectURL(this.bytes, 'image/jpeg', forceDataSchema);
+ };
+ return JpegStream;
+ }();
+ /**
+ * For JPEG 2000's we use a library to decode these images and
+ * the stream behaves like all the other DecodeStreams.
+ */
+ var JpxStream = function JpxStreamClosure() {
+ function JpxStream(stream, maybeLength, dict, params) {
+ this.stream = stream;
+ this.maybeLength = maybeLength;
+ this.dict = dict;
+ this.params = params;
+ DecodeStream.call(this, maybeLength);
+ }
+ JpxStream.prototype = Object.create(DecodeStream.prototype);
+ Object.defineProperty(JpxStream.prototype, 'bytes', {
+ get: function JpxStream_bytes() {
+ // If this.maybeLength is null, we'll get the entire stream.
+ return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength));
+ },
+ configurable: true
+ });
+ JpxStream.prototype.ensureBuffer = function JpxStream_ensureBuffer(req) {
+ if (this.bufferLength) {
+ return;
+ }
+ var jpxImage = new JpxImage();
+ jpxImage.parse(this.bytes);
+ var width = jpxImage.width;
+ var height = jpxImage.height;
+ var componentsCount = jpxImage.componentsCount;
+ var tileCount = jpxImage.tiles.length;
+ if (tileCount === 1) {
+ this.buffer = jpxImage.tiles[0].items;
+ } else {
+ var data = new Uint8Array(width * height * componentsCount);
+ for (var k = 0; k < tileCount; k++) {
+ var tileComponents = jpxImage.tiles[k];
+ var tileWidth = tileComponents.width;
+ var tileHeight = tileComponents.height;
+ var tileLeft = tileComponents.left;
+ var tileTop = tileComponents.top;
+ var src = tileComponents.items;
+ var srcPosition = 0;
+ var dataPosition = (width * tileTop + tileLeft) * componentsCount;
+ var imgRowSize = width * componentsCount;
+ var tileRowSize = tileWidth * componentsCount;
+ for (var j = 0; j < tileHeight; j++) {
+ var rowBytes = src.subarray(srcPosition, srcPosition + tileRowSize);
+ data.set(rowBytes, dataPosition);
+ srcPosition += tileRowSize;
+ dataPosition += imgRowSize;
+ }
+ }
+ this.buffer = data;
+ }
+ this.bufferLength = this.buffer.length;
+ this.eof = true;
+ };
+ return JpxStream;
+ }();
+ /**
+ * For JBIG2's we use a library to decode these images and
+ * the stream behaves like all the other DecodeStreams.
+ */
+ var Jbig2Stream = function Jbig2StreamClosure() {
+ function Jbig2Stream(stream, maybeLength, dict, params) {
+ this.stream = stream;
+ this.maybeLength = maybeLength;
+ this.dict = dict;
+ this.params = params;
+ DecodeStream.call(this, maybeLength);
+ }
+ Jbig2Stream.prototype = Object.create(DecodeStream.prototype);
+ Object.defineProperty(Jbig2Stream.prototype, 'bytes', {
+ get: function Jbig2Stream_bytes() {
+ // If this.maybeLength is null, we'll get the entire stream.
+ return shadow(this, 'bytes', this.stream.getBytes(this.maybeLength));
+ },
+ configurable: true
+ });
+ Jbig2Stream.prototype.ensureBuffer = function Jbig2Stream_ensureBuffer(req) {
+ if (this.bufferLength) {
+ return;
+ }
+ var jbig2Image = new Jbig2Image();
+ var chunks = [];
+ if (isDict(this.params)) {
+ var globalsStream = this.params.get('JBIG2Globals');
+ if (isStream(globalsStream)) {
+ var globals = globalsStream.getBytes();
+ chunks.push({
+ data: globals,
+ start: 0,
+ end: globals.length
+ });
+ }
+ }
+ chunks.push({
+ data: this.bytes,
+ start: 0,
+ end: this.bytes.length
+ });
+ var data = jbig2Image.parseChunks(chunks);
+ var dataLength = data.length;
+ // JBIG2 had black as 1 and white as 0, inverting the colors
+ for (var i = 0; i < dataLength; i++) {
+ data[i] ^= 0xFF;
+ }
+ this.buffer = data;
+ this.bufferLength = dataLength;
+ this.eof = true;
+ };
+ return Jbig2Stream;
+ }();
+ var DecryptStream = function DecryptStreamClosure() {
+ function DecryptStream(str, maybeLength, decrypt) {
+ this.str = str;
+ this.dict = str.dict;
+ this.decrypt = decrypt;
+ this.nextChunk = null;
+ this.initialized = false;
+ DecodeStream.call(this, maybeLength);
+ }
+ var chunkSize = 512;
+ DecryptStream.prototype = Object.create(DecodeStream.prototype);
+ DecryptStream.prototype.readBlock = function DecryptStream_readBlock() {
+ var chunk;
+ if (this.initialized) {
+ chunk = this.nextChunk;
+ } else {
+ chunk = this.str.getBytes(chunkSize);
+ this.initialized = true;
+ }
+ if (!chunk || chunk.length === 0) {
+ this.eof = true;
+ return;
+ }
+ this.nextChunk = this.str.getBytes(chunkSize);
+ var hasMoreData = this.nextChunk && this.nextChunk.length > 0;
+ var decrypt = this.decrypt;
+ chunk = decrypt(chunk, !hasMoreData);
+ var bufferLength = this.bufferLength;
+ var i, n = chunk.length;
+ var buffer = this.ensureBuffer(bufferLength + n);
+ for (i = 0; i < n; i++) {
+ buffer[bufferLength++] = chunk[i];
+ }
+ this.bufferLength = bufferLength;
+ };
+ return DecryptStream;
+ }();
+ var Ascii85Stream = function Ascii85StreamClosure() {
+ function Ascii85Stream(str, maybeLength) {
+ this.str = str;
+ this.dict = str.dict;
+ this.input = new Uint8Array(5);
+ // Most streams increase in size when decoded, but Ascii85 streams
+ // typically shrink by ~20%.
+ if (maybeLength) {
+ maybeLength = 0.8 * maybeLength;
+ }
+ DecodeStream.call(this, maybeLength);
+ }
+ Ascii85Stream.prototype = Object.create(DecodeStream.prototype);
+ Ascii85Stream.prototype.readBlock = function Ascii85Stream_readBlock() {
+ var TILDA_CHAR = 0x7E;
+ // '~'
+ var Z_LOWER_CHAR = 0x7A;
+ // 'z'
+ var EOF = -1;
+ var str = this.str;
+ var c = str.getByte();
+ while (isSpace(c)) {
+ c = str.getByte();
+ }
+ if (c === EOF || c === TILDA_CHAR) {
+ this.eof = true;
+ return;
+ }
+ var bufferLength = this.bufferLength, buffer;
+ var i;
+ // special code for z
+ if (c === Z_LOWER_CHAR) {
+ buffer = this.ensureBuffer(bufferLength + 4);
+ for (i = 0; i < 4; ++i) {
+ buffer[bufferLength + i] = 0;
+ }
+ this.bufferLength += 4;
+ } else {
+ var input = this.input;
+ input[0] = c;
+ for (i = 1; i < 5; ++i) {
+ c = str.getByte();
+ while (isSpace(c)) {
+ c = str.getByte();
+ }
+ input[i] = c;
+ if (c === EOF || c === TILDA_CHAR) {
+ break;
+ }
+ }
+ buffer = this.ensureBuffer(bufferLength + i - 1);
+ this.bufferLength += i - 1;
+ // partial ending;
+ if (i < 5) {
+ for (; i < 5; ++i) {
+ input[i] = 0x21 + 84;
+ }
+ this.eof = true;
+ }
+ var t = 0;
+ for (i = 0; i < 5; ++i) {
+ t = t * 85 + (input[i] - 0x21);
+ }
+ for (i = 3; i >= 0; --i) {
+ buffer[bufferLength + i] = t & 0xFF;
+ t >>= 8;
+ }
+ }
+ };
+ return Ascii85Stream;
+ }();
+ var AsciiHexStream = function AsciiHexStreamClosure() {
+ function AsciiHexStream(str, maybeLength) {
+ this.str = str;
+ this.dict = str.dict;
+ this.firstDigit = -1;
+ // Most streams increase in size when decoded, but AsciiHex streams shrink
+ // by 50%.
+ if (maybeLength) {
+ maybeLength = 0.5 * maybeLength;
+ }
+ DecodeStream.call(this, maybeLength);
+ }
+ AsciiHexStream.prototype = Object.create(DecodeStream.prototype);
+ AsciiHexStream.prototype.readBlock = function AsciiHexStream_readBlock() {
+ var UPSTREAM_BLOCK_SIZE = 8000;
+ var bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE);
+ if (!bytes.length) {
+ this.eof = true;
+ return;
+ }
+ var maxDecodeLength = bytes.length + 1 >> 1;
+ var buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength);
+ var bufferLength = this.bufferLength;
+ var firstDigit = this.firstDigit;
+ for (var i = 0, ii = bytes.length; i < ii; i++) {
+ var ch = bytes[i], digit;
+ if (ch >= 0x30 && ch <= 0x39) {
+ // '0'-'9'
+ digit = ch & 0x0F;
+ } else if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
+ // 'A'-'Z', 'a'-'z'
+ digit = (ch & 0x0F) + 9;
+ } else if (ch === 0x3E) {
+ // '>'
+ this.eof = true;
+ break;
+ } else {
+ // probably whitespace
+ continue;
+ }
+ // ignoring
+ if (firstDigit < 0) {
+ firstDigit = digit;
+ } else {
+ buffer[bufferLength++] = firstDigit << 4 | digit;
+ firstDigit = -1;
+ }
+ }
+ if (firstDigit >= 0 && this.eof) {
+ // incomplete byte
+ buffer[bufferLength++] = firstDigit << 4;
+ firstDigit = -1;
+ }
+ this.firstDigit = firstDigit;
+ this.bufferLength = bufferLength;
+ };
+ return AsciiHexStream;
+ }();
+ var RunLengthStream = function RunLengthStreamClosure() {
+ function RunLengthStream(str, maybeLength) {
+ this.str = str;
+ this.dict = str.dict;
+ DecodeStream.call(this, maybeLength);
+ }
+ RunLengthStream.prototype = Object.create(DecodeStream.prototype);
+ RunLengthStream.prototype.readBlock = function RunLengthStream_readBlock() {
+ // The repeatHeader has following format. The first byte defines type of run
+ // and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes
+ // (in addition to the second byte from the header), n = 129 through 255 -
+ // duplicate the second byte from the header (257 - n) times, n = 128 - end.
+ var repeatHeader = this.str.getBytes(2);
+ if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) {
+ this.eof = true;
+ return;
+ }
+ var buffer;
+ var bufferLength = this.bufferLength;
+ var n = repeatHeader[0];
+ if (n < 128) {
+ // copy n bytes
+ buffer = this.ensureBuffer(bufferLength + n + 1);
+ buffer[bufferLength++] = repeatHeader[1];
+ if (n > 0) {
+ var source = this.str.getBytes(n);
+ buffer.set(source, bufferLength);
+ bufferLength += n;
+ }
+ } else {
+ n = 257 - n;
+ var b = repeatHeader[1];
+ buffer = this.ensureBuffer(bufferLength + n + 1);
+ for (var i = 0; i < n; i++) {
+ buffer[bufferLength++] = b;
+ }
+ }
+ this.bufferLength = bufferLength;
+ };
+ return RunLengthStream;
+ }();
+ var CCITTFaxStream = function CCITTFaxStreamClosure() {
+ var ccittEOL = -2;
+ var ccittEOF = -1;
+ var twoDimPass = 0;
+ var twoDimHoriz = 1;
+ var twoDimVert0 = 2;
+ var twoDimVertR1 = 3;
+ var twoDimVertL1 = 4;
+ var twoDimVertR2 = 5;
+ var twoDimVertL2 = 6;
+ var twoDimVertR3 = 7;
+ var twoDimVertL3 = 8;
+ var twoDimTable = [
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 000000x
+ [
+ 7,
+ twoDimVertL3
+ ],
+ // 0000010
+ [
+ 7,
+ twoDimVertR3
+ ],
+ // 0000011
+ [
+ 6,
+ twoDimVertL2
+ ],
+ [
+ 6,
+ twoDimVertL2
+ ],
+ // 000010x
+ [
+ 6,
+ twoDimVertR2
+ ],
+ [
+ 6,
+ twoDimVertR2
+ ],
+ // 000011x
+ [
+ 4,
+ twoDimPass
+ ],
+ [
+ 4,
+ twoDimPass
+ ],
+ // 0001xxx
+ [
+ 4,
+ twoDimPass
+ ],
+ [
+ 4,
+ twoDimPass
+ ],
+ [
+ 4,
+ twoDimPass
+ ],
+ [
+ 4,
+ twoDimPass
+ ],
+ [
+ 4,
+ twoDimPass
+ ],
+ [
+ 4,
+ twoDimPass
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ // 001xxxx
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimHoriz
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ // 010xxxx
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertL1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ // 011xxxx
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 3,
+ twoDimVertR1
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ // 1xxxxxx
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ],
+ [
+ 1,
+ twoDimVert0
+ ]
+ ];
+ var whiteTable1 = [
+ [
+ -1,
+ -1
+ ],
+ // 00000
+ [
+ 12,
+ ccittEOL
+ ],
+ // 00001
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 0001x
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 001xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 010xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 011xx
+ [
+ 11,
+ 1792
+ ],
+ [
+ 11,
+ 1792
+ ],
+ // 1000x
+ [
+ 12,
+ 1984
+ ],
+ // 10010
+ [
+ 12,
+ 2048
+ ],
+ // 10011
+ [
+ 12,
+ 2112
+ ],
+ // 10100
+ [
+ 12,
+ 2176
+ ],
+ // 10101
+ [
+ 12,
+ 2240
+ ],
+ // 10110
+ [
+ 12,
+ 2304
+ ],
+ // 10111
+ [
+ 11,
+ 1856
+ ],
+ [
+ 11,
+ 1856
+ ],
+ // 1100x
+ [
+ 11,
+ 1920
+ ],
+ [
+ 11,
+ 1920
+ ],
+ // 1101x
+ [
+ 12,
+ 2368
+ ],
+ // 11100
+ [
+ 12,
+ 2432
+ ],
+ // 11101
+ [
+ 12,
+ 2496
+ ],
+ // 11110
+ [
+ 12,
+ 2560
+ ]
+ ];
+ // 11111
+ var whiteTable2 = [
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 0000000xx
+ [
+ 8,
+ 29
+ ],
+ [
+ 8,
+ 29
+ ],
+ // 00000010x
+ [
+ 8,
+ 30
+ ],
+ [
+ 8,
+ 30
+ ],
+ // 00000011x
+ [
+ 8,
+ 45
+ ],
+ [
+ 8,
+ 45
+ ],
+ // 00000100x
+ [
+ 8,
+ 46
+ ],
+ [
+ 8,
+ 46
+ ],
+ // 00000101x
+ [
+ 7,
+ 22
+ ],
+ [
+ 7,
+ 22
+ ],
+ [
+ 7,
+ 22
+ ],
+ [
+ 7,
+ 22
+ ],
+ // 0000011xx
+ [
+ 7,
+ 23
+ ],
+ [
+ 7,
+ 23
+ ],
+ [
+ 7,
+ 23
+ ],
+ [
+ 7,
+ 23
+ ],
+ // 0000100xx
+ [
+ 8,
+ 47
+ ],
+ [
+ 8,
+ 47
+ ],
+ // 00001010x
+ [
+ 8,
+ 48
+ ],
+ [
+ 8,
+ 48
+ ],
+ // 00001011x
+ [
+ 6,
+ 13
+ ],
+ [
+ 6,
+ 13
+ ],
+ [
+ 6,
+ 13
+ ],
+ [
+ 6,
+ 13
+ ],
+ // 000011xxx
+ [
+ 6,
+ 13
+ ],
+ [
+ 6,
+ 13
+ ],
+ [
+ 6,
+ 13
+ ],
+ [
+ 6,
+ 13
+ ],
+ [
+ 7,
+ 20
+ ],
+ [
+ 7,
+ 20
+ ],
+ [
+ 7,
+ 20
+ ],
+ [
+ 7,
+ 20
+ ],
+ // 0001000xx
+ [
+ 8,
+ 33
+ ],
+ [
+ 8,
+ 33
+ ],
+ // 00010010x
+ [
+ 8,
+ 34
+ ],
+ [
+ 8,
+ 34
+ ],
+ // 00010011x
+ [
+ 8,
+ 35
+ ],
+ [
+ 8,
+ 35
+ ],
+ // 00010100x
+ [
+ 8,
+ 36
+ ],
+ [
+ 8,
+ 36
+ ],
+ // 00010101x
+ [
+ 8,
+ 37
+ ],
+ [
+ 8,
+ 37
+ ],
+ // 00010110x
+ [
+ 8,
+ 38
+ ],
+ [
+ 8,
+ 38
+ ],
+ // 00010111x
+ [
+ 7,
+ 19
+ ],
+ [
+ 7,
+ 19
+ ],
+ [
+ 7,
+ 19
+ ],
+ [
+ 7,
+ 19
+ ],
+ // 0001100xx
+ [
+ 8,
+ 31
+ ],
+ [
+ 8,
+ 31
+ ],
+ // 00011010x
+ [
+ 8,
+ 32
+ ],
+ [
+ 8,
+ 32
+ ],
+ // 00011011x
+ [
+ 6,
+ 1
+ ],
+ [
+ 6,
+ 1
+ ],
+ [
+ 6,
+ 1
+ ],
+ [
+ 6,
+ 1
+ ],
+ // 000111xxx
+ [
+ 6,
+ 1
+ ],
+ [
+ 6,
+ 1
+ ],
+ [
+ 6,
+ 1
+ ],
+ [
+ 6,
+ 1
+ ],
+ [
+ 6,
+ 12
+ ],
+ [
+ 6,
+ 12
+ ],
+ [
+ 6,
+ 12
+ ],
+ [
+ 6,
+ 12
+ ],
+ // 001000xxx
+ [
+ 6,
+ 12
+ ],
+ [
+ 6,
+ 12
+ ],
+ [
+ 6,
+ 12
+ ],
+ [
+ 6,
+ 12
+ ],
+ [
+ 8,
+ 53
+ ],
+ [
+ 8,
+ 53
+ ],
+ // 00100100x
+ [
+ 8,
+ 54
+ ],
+ [
+ 8,
+ 54
+ ],
+ // 00100101x
+ [
+ 7,
+ 26
+ ],
+ [
+ 7,
+ 26
+ ],
+ [
+ 7,
+ 26
+ ],
+ [
+ 7,
+ 26
+ ],
+ // 0010011xx
+ [
+ 8,
+ 39
+ ],
+ [
+ 8,
+ 39
+ ],
+ // 00101000x
+ [
+ 8,
+ 40
+ ],
+ [
+ 8,
+ 40
+ ],
+ // 00101001x
+ [
+ 8,
+ 41
+ ],
+ [
+ 8,
+ 41
+ ],
+ // 00101010x
+ [
+ 8,
+ 42
+ ],
+ [
+ 8,
+ 42
+ ],
+ // 00101011x
+ [
+ 8,
+ 43
+ ],
+ [
+ 8,
+ 43
+ ],
+ // 00101100x
+ [
+ 8,
+ 44
+ ],
+ [
+ 8,
+ 44
+ ],
+ // 00101101x
+ [
+ 7,
+ 21
+ ],
+ [
+ 7,
+ 21
+ ],
+ [
+ 7,
+ 21
+ ],
+ [
+ 7,
+ 21
+ ],
+ // 0010111xx
+ [
+ 7,
+ 28
+ ],
+ [
+ 7,
+ 28
+ ],
+ [
+ 7,
+ 28
+ ],
+ [
+ 7,
+ 28
+ ],
+ // 0011000xx
+ [
+ 8,
+ 61
+ ],
+ [
+ 8,
+ 61
+ ],
+ // 00110010x
+ [
+ 8,
+ 62
+ ],
+ [
+ 8,
+ 62
+ ],
+ // 00110011x
+ [
+ 8,
+ 63
+ ],
+ [
+ 8,
+ 63
+ ],
+ // 00110100x
+ [
+ 8,
+ 0
+ ],
+ [
+ 8,
+ 0
+ ],
+ // 00110101x
+ [
+ 8,
+ 320
+ ],
+ [
+ 8,
+ 320
+ ],
+ // 00110110x
+ [
+ 8,
+ 384
+ ],
+ [
+ 8,
+ 384
+ ],
+ // 00110111x
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ // 00111xxxx
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 10
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ // 01000xxxx
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 5,
+ 11
+ ],
+ [
+ 7,
+ 27
+ ],
+ [
+ 7,
+ 27
+ ],
+ [
+ 7,
+ 27
+ ],
+ [
+ 7,
+ 27
+ ],
+ // 0100100xx
+ [
+ 8,
+ 59
+ ],
+ [
+ 8,
+ 59
+ ],
+ // 01001010x
+ [
+ 8,
+ 60
+ ],
+ [
+ 8,
+ 60
+ ],
+ // 01001011x
+ [
+ 9,
+ 1472
+ ],
+ // 010011000
+ [
+ 9,
+ 1536
+ ],
+ // 010011001
+ [
+ 9,
+ 1600
+ ],
+ // 010011010
+ [
+ 9,
+ 1728
+ ],
+ // 010011011
+ [
+ 7,
+ 18
+ ],
+ [
+ 7,
+ 18
+ ],
+ [
+ 7,
+ 18
+ ],
+ [
+ 7,
+ 18
+ ],
+ // 0100111xx
+ [
+ 7,
+ 24
+ ],
+ [
+ 7,
+ 24
+ ],
+ [
+ 7,
+ 24
+ ],
+ [
+ 7,
+ 24
+ ],
+ // 0101000xx
+ [
+ 8,
+ 49
+ ],
+ [
+ 8,
+ 49
+ ],
+ // 01010010x
+ [
+ 8,
+ 50
+ ],
+ [
+ 8,
+ 50
+ ],
+ // 01010011x
+ [
+ 8,
+ 51
+ ],
+ [
+ 8,
+ 51
+ ],
+ // 01010100x
+ [
+ 8,
+ 52
+ ],
+ [
+ 8,
+ 52
+ ],
+ // 01010101x
+ [
+ 7,
+ 25
+ ],
+ [
+ 7,
+ 25
+ ],
+ [
+ 7,
+ 25
+ ],
+ [
+ 7,
+ 25
+ ],
+ // 0101011xx
+ [
+ 8,
+ 55
+ ],
+ [
+ 8,
+ 55
+ ],
+ // 01011000x
+ [
+ 8,
+ 56
+ ],
+ [
+ 8,
+ 56
+ ],
+ // 01011001x
+ [
+ 8,
+ 57
+ ],
+ [
+ 8,
+ 57
+ ],
+ // 01011010x
+ [
+ 8,
+ 58
+ ],
+ [
+ 8,
+ 58
+ ],
+ // 01011011x
+ [
+ 6,
+ 192
+ ],
+ [
+ 6,
+ 192
+ ],
+ [
+ 6,
+ 192
+ ],
+ [
+ 6,
+ 192
+ ],
+ // 010111xxx
+ [
+ 6,
+ 192
+ ],
+ [
+ 6,
+ 192
+ ],
+ [
+ 6,
+ 192
+ ],
+ [
+ 6,
+ 192
+ ],
+ [
+ 6,
+ 1664
+ ],
+ [
+ 6,
+ 1664
+ ],
+ [
+ 6,
+ 1664
+ ],
+ [
+ 6,
+ 1664
+ ],
+ // 011000xxx
+ [
+ 6,
+ 1664
+ ],
+ [
+ 6,
+ 1664
+ ],
+ [
+ 6,
+ 1664
+ ],
+ [
+ 6,
+ 1664
+ ],
+ [
+ 8,
+ 448
+ ],
+ [
+ 8,
+ 448
+ ],
+ // 01100100x
+ [
+ 8,
+ 512
+ ],
+ [
+ 8,
+ 512
+ ],
+ // 01100101x
+ [
+ 9,
+ 704
+ ],
+ // 011001100
+ [
+ 9,
+ 768
+ ],
+ // 011001101
+ [
+ 8,
+ 640
+ ],
+ [
+ 8,
+ 640
+ ],
+ // 01100111x
+ [
+ 8,
+ 576
+ ],
+ [
+ 8,
+ 576
+ ],
+ // 01101000x
+ [
+ 9,
+ 832
+ ],
+ // 011010010
+ [
+ 9,
+ 896
+ ],
+ // 011010011
+ [
+ 9,
+ 960
+ ],
+ // 011010100
+ [
+ 9,
+ 1024
+ ],
+ // 011010101
+ [
+ 9,
+ 1088
+ ],
+ // 011010110
+ [
+ 9,
+ 1152
+ ],
+ // 011010111
+ [
+ 9,
+ 1216
+ ],
+ // 011011000
+ [
+ 9,
+ 1280
+ ],
+ // 011011001
+ [
+ 9,
+ 1344
+ ],
+ // 011011010
+ [
+ 9,
+ 1408
+ ],
+ // 011011011
+ [
+ 7,
+ 256
+ ],
+ [
+ 7,
+ 256
+ ],
+ [
+ 7,
+ 256
+ ],
+ [
+ 7,
+ 256
+ ],
+ // 0110111xx
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ // 0111xxxxx
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 2
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ // 1000xxxxx
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 4,
+ 3
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ // 10010xxxx
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 128
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ // 10011xxxx
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 8
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ // 10100xxxx
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 5,
+ 9
+ ],
+ [
+ 6,
+ 16
+ ],
+ [
+ 6,
+ 16
+ ],
+ [
+ 6,
+ 16
+ ],
+ [
+ 6,
+ 16
+ ],
+ // 101010xxx
+ [
+ 6,
+ 16
+ ],
+ [
+ 6,
+ 16
+ ],
+ [
+ 6,
+ 16
+ ],
+ [
+ 6,
+ 16
+ ],
+ [
+ 6,
+ 17
+ ],
+ [
+ 6,
+ 17
+ ],
+ [
+ 6,
+ 17
+ ],
+ [
+ 6,
+ 17
+ ],
+ // 101011xxx
+ [
+ 6,
+ 17
+ ],
+ [
+ 6,
+ 17
+ ],
+ [
+ 6,
+ 17
+ ],
+ [
+ 6,
+ 17
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ // 1011xxxxx
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 4
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ // 1100xxxxx
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 6,
+ 14
+ ],
+ [
+ 6,
+ 14
+ ],
+ [
+ 6,
+ 14
+ ],
+ [
+ 6,
+ 14
+ ],
+ // 110100xxx
+ [
+ 6,
+ 14
+ ],
+ [
+ 6,
+ 14
+ ],
+ [
+ 6,
+ 14
+ ],
+ [
+ 6,
+ 14
+ ],
+ [
+ 6,
+ 15
+ ],
+ [
+ 6,
+ 15
+ ],
+ [
+ 6,
+ 15
+ ],
+ [
+ 6,
+ 15
+ ],
+ // 110101xxx
+ [
+ 6,
+ 15
+ ],
+ [
+ 6,
+ 15
+ ],
+ [
+ 6,
+ 15
+ ],
+ [
+ 6,
+ 15
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ // 11011xxxx
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 5,
+ 64
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ // 1110xxxxx
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ // 1111xxxxx
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ],
+ [
+ 4,
+ 7
+ ]
+ ];
+ var blackTable1 = [
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 000000000000x
+ [
+ 12,
+ ccittEOL
+ ],
+ [
+ 12,
+ ccittEOL
+ ],
+ // 000000000001x
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 00000000001xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 00000000010xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 00000000011xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 00000000100xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 00000000101xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 00000000110xx
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 00000000111xx
+ [
+ 11,
+ 1792
+ ],
+ [
+ 11,
+ 1792
+ ],
+ [
+ 11,
+ 1792
+ ],
+ [
+ 11,
+ 1792
+ ],
+ // 00000001000xx
+ [
+ 12,
+ 1984
+ ],
+ [
+ 12,
+ 1984
+ ],
+ // 000000010010x
+ [
+ 12,
+ 2048
+ ],
+ [
+ 12,
+ 2048
+ ],
+ // 000000010011x
+ [
+ 12,
+ 2112
+ ],
+ [
+ 12,
+ 2112
+ ],
+ // 000000010100x
+ [
+ 12,
+ 2176
+ ],
+ [
+ 12,
+ 2176
+ ],
+ // 000000010101x
+ [
+ 12,
+ 2240
+ ],
+ [
+ 12,
+ 2240
+ ],
+ // 000000010110x
+ [
+ 12,
+ 2304
+ ],
+ [
+ 12,
+ 2304
+ ],
+ // 000000010111x
+ [
+ 11,
+ 1856
+ ],
+ [
+ 11,
+ 1856
+ ],
+ [
+ 11,
+ 1856
+ ],
+ [
+ 11,
+ 1856
+ ],
+ // 00000001100xx
+ [
+ 11,
+ 1920
+ ],
+ [
+ 11,
+ 1920
+ ],
+ [
+ 11,
+ 1920
+ ],
+ [
+ 11,
+ 1920
+ ],
+ // 00000001101xx
+ [
+ 12,
+ 2368
+ ],
+ [
+ 12,
+ 2368
+ ],
+ // 000000011100x
+ [
+ 12,
+ 2432
+ ],
+ [
+ 12,
+ 2432
+ ],
+ // 000000011101x
+ [
+ 12,
+ 2496
+ ],
+ [
+ 12,
+ 2496
+ ],
+ // 000000011110x
+ [
+ 12,
+ 2560
+ ],
+ [
+ 12,
+ 2560
+ ],
+ // 000000011111x
+ [
+ 10,
+ 18
+ ],
+ [
+ 10,
+ 18
+ ],
+ [
+ 10,
+ 18
+ ],
+ [
+ 10,
+ 18
+ ],
+ // 0000001000xxx
+ [
+ 10,
+ 18
+ ],
+ [
+ 10,
+ 18
+ ],
+ [
+ 10,
+ 18
+ ],
+ [
+ 10,
+ 18
+ ],
+ [
+ 12,
+ 52
+ ],
+ [
+ 12,
+ 52
+ ],
+ // 000000100100x
+ [
+ 13,
+ 640
+ ],
+ // 0000001001010
+ [
+ 13,
+ 704
+ ],
+ // 0000001001011
+ [
+ 13,
+ 768
+ ],
+ // 0000001001100
+ [
+ 13,
+ 832
+ ],
+ // 0000001001101
+ [
+ 12,
+ 55
+ ],
+ [
+ 12,
+ 55
+ ],
+ // 000000100111x
+ [
+ 12,
+ 56
+ ],
+ [
+ 12,
+ 56
+ ],
+ // 000000101000x
+ [
+ 13,
+ 1280
+ ],
+ // 0000001010010
+ [
+ 13,
+ 1344
+ ],
+ // 0000001010011
+ [
+ 13,
+ 1408
+ ],
+ // 0000001010100
+ [
+ 13,
+ 1472
+ ],
+ // 0000001010101
+ [
+ 12,
+ 59
+ ],
+ [
+ 12,
+ 59
+ ],
+ // 000000101011x
+ [
+ 12,
+ 60
+ ],
+ [
+ 12,
+ 60
+ ],
+ // 000000101100x
+ [
+ 13,
+ 1536
+ ],
+ // 0000001011010
+ [
+ 13,
+ 1600
+ ],
+ // 0000001011011
+ [
+ 11,
+ 24
+ ],
+ [
+ 11,
+ 24
+ ],
+ [
+ 11,
+ 24
+ ],
+ [
+ 11,
+ 24
+ ],
+ // 00000010111xx
+ [
+ 11,
+ 25
+ ],
+ [
+ 11,
+ 25
+ ],
+ [
+ 11,
+ 25
+ ],
+ [
+ 11,
+ 25
+ ],
+ // 00000011000xx
+ [
+ 13,
+ 1664
+ ],
+ // 0000001100100
+ [
+ 13,
+ 1728
+ ],
+ // 0000001100101
+ [
+ 12,
+ 320
+ ],
+ [
+ 12,
+ 320
+ ],
+ // 000000110011x
+ [
+ 12,
+ 384
+ ],
+ [
+ 12,
+ 384
+ ],
+ // 000000110100x
+ [
+ 12,
+ 448
+ ],
+ [
+ 12,
+ 448
+ ],
+ // 000000110101x
+ [
+ 13,
+ 512
+ ],
+ // 0000001101100
+ [
+ 13,
+ 576
+ ],
+ // 0000001101101
+ [
+ 12,
+ 53
+ ],
+ [
+ 12,
+ 53
+ ],
+ // 000000110111x
+ [
+ 12,
+ 54
+ ],
+ [
+ 12,
+ 54
+ ],
+ // 000000111000x
+ [
+ 13,
+ 896
+ ],
+ // 0000001110010
+ [
+ 13,
+ 960
+ ],
+ // 0000001110011
+ [
+ 13,
+ 1024
+ ],
+ // 0000001110100
+ [
+ 13,
+ 1088
+ ],
+ // 0000001110101
+ [
+ 13,
+ 1152
+ ],
+ // 0000001110110
+ [
+ 13,
+ 1216
+ ],
+ // 0000001110111
+ [
+ 10,
+ 64
+ ],
+ [
+ 10,
+ 64
+ ],
+ [
+ 10,
+ 64
+ ],
+ [
+ 10,
+ 64
+ ],
+ // 0000001111xxx
+ [
+ 10,
+ 64
+ ],
+ [
+ 10,
+ 64
+ ],
+ [
+ 10,
+ 64
+ ],
+ [
+ 10,
+ 64
+ ]
+ ];
+ var blackTable2 = [
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ // 00000100xxxx
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 8,
+ 13
+ ],
+ [
+ 11,
+ 23
+ ],
+ [
+ 11,
+ 23
+ ],
+ // 00000101000x
+ [
+ 12,
+ 50
+ ],
+ // 000001010010
+ [
+ 12,
+ 51
+ ],
+ // 000001010011
+ [
+ 12,
+ 44
+ ],
+ // 000001010100
+ [
+ 12,
+ 45
+ ],
+ // 000001010101
+ [
+ 12,
+ 46
+ ],
+ // 000001010110
+ [
+ 12,
+ 47
+ ],
+ // 000001010111
+ [
+ 12,
+ 57
+ ],
+ // 000001011000
+ [
+ 12,
+ 58
+ ],
+ // 000001011001
+ [
+ 12,
+ 61
+ ],
+ // 000001011010
+ [
+ 12,
+ 256
+ ],
+ // 000001011011
+ [
+ 10,
+ 16
+ ],
+ [
+ 10,
+ 16
+ ],
+ [
+ 10,
+ 16
+ ],
+ [
+ 10,
+ 16
+ ],
+ // 0000010111xx
+ [
+ 10,
+ 17
+ ],
+ [
+ 10,
+ 17
+ ],
+ [
+ 10,
+ 17
+ ],
+ [
+ 10,
+ 17
+ ],
+ // 0000011000xx
+ [
+ 12,
+ 48
+ ],
+ // 000001100100
+ [
+ 12,
+ 49
+ ],
+ // 000001100101
+ [
+ 12,
+ 62
+ ],
+ // 000001100110
+ [
+ 12,
+ 63
+ ],
+ // 000001100111
+ [
+ 12,
+ 30
+ ],
+ // 000001101000
+ [
+ 12,
+ 31
+ ],
+ // 000001101001
+ [
+ 12,
+ 32
+ ],
+ // 000001101010
+ [
+ 12,
+ 33
+ ],
+ // 000001101011
+ [
+ 12,
+ 40
+ ],
+ // 000001101100
+ [
+ 12,
+ 41
+ ],
+ // 000001101101
+ [
+ 11,
+ 22
+ ],
+ [
+ 11,
+ 22
+ ],
+ // 00000110111x
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ // 00000111xxxx
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 8,
+ 14
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ // 0000100xxxxx
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 10
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ // 0000101xxxxx
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 7,
+ 11
+ ],
+ [
+ 9,
+ 15
+ ],
+ [
+ 9,
+ 15
+ ],
+ [
+ 9,
+ 15
+ ],
+ [
+ 9,
+ 15
+ ],
+ // 000011000xxx
+ [
+ 9,
+ 15
+ ],
+ [
+ 9,
+ 15
+ ],
+ [
+ 9,
+ 15
+ ],
+ [
+ 9,
+ 15
+ ],
+ [
+ 12,
+ 128
+ ],
+ // 000011001000
+ [
+ 12,
+ 192
+ ],
+ // 000011001001
+ [
+ 12,
+ 26
+ ],
+ // 000011001010
+ [
+ 12,
+ 27
+ ],
+ // 000011001011
+ [
+ 12,
+ 28
+ ],
+ // 000011001100
+ [
+ 12,
+ 29
+ ],
+ // 000011001101
+ [
+ 11,
+ 19
+ ],
+ [
+ 11,
+ 19
+ ],
+ // 00001100111x
+ [
+ 11,
+ 20
+ ],
+ [
+ 11,
+ 20
+ ],
+ // 00001101000x
+ [
+ 12,
+ 34
+ ],
+ // 000011010010
+ [
+ 12,
+ 35
+ ],
+ // 000011010011
+ [
+ 12,
+ 36
+ ],
+ // 000011010100
+ [
+ 12,
+ 37
+ ],
+ // 000011010101
+ [
+ 12,
+ 38
+ ],
+ // 000011010110
+ [
+ 12,
+ 39
+ ],
+ // 000011010111
+ [
+ 11,
+ 21
+ ],
+ [
+ 11,
+ 21
+ ],
+ // 00001101100x
+ [
+ 12,
+ 42
+ ],
+ // 000011011010
+ [
+ 12,
+ 43
+ ],
+ // 000011011011
+ [
+ 10,
+ 0
+ ],
+ [
+ 10,
+ 0
+ ],
+ [
+ 10,
+ 0
+ ],
+ [
+ 10,
+ 0
+ ],
+ // 0000110111xx
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ // 0000111xxxxx
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ],
+ [
+ 7,
+ 12
+ ]
+ ];
+ var blackTable3 = [
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ [
+ -1,
+ -1
+ ],
+ // 0000xx
+ [
+ 6,
+ 9
+ ],
+ // 000100
+ [
+ 6,
+ 8
+ ],
+ // 000101
+ [
+ 5,
+ 7
+ ],
+ [
+ 5,
+ 7
+ ],
+ // 00011x
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ [
+ 4,
+ 6
+ ],
+ // 0010xx
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ [
+ 4,
+ 5
+ ],
+ // 0011xx
+ [
+ 3,
+ 1
+ ],
+ [
+ 3,
+ 1
+ ],
+ [
+ 3,
+ 1
+ ],
+ [
+ 3,
+ 1
+ ],
+ // 010xxx
+ [
+ 3,
+ 1
+ ],
+ [
+ 3,
+ 1
+ ],
+ [
+ 3,
+ 1
+ ],
+ [
+ 3,
+ 1
+ ],
+ [
+ 3,
+ 4
+ ],
+ [
+ 3,
+ 4
+ ],
+ [
+ 3,
+ 4
+ ],
+ [
+ 3,
+ 4
+ ],
+ // 011xxx
+ [
+ 3,
+ 4
+ ],
+ [
+ 3,
+ 4
+ ],
+ [
+ 3,
+ 4
+ ],
+ [
+ 3,
+ 4
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ // 10xxxx
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 3
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ // 11xxxx
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ],
+ [
+ 2,
+ 2
+ ]
+ ];
+ function CCITTFaxStream(str, maybeLength, params) {
+ this.str = str;
+ this.dict = str.dict;
+ params = params || Dict.empty;
+ this.encoding = params.get('K') || 0;
+ this.eoline = params.get('EndOfLine') || false;
+ this.byteAlign = params.get('EncodedByteAlign') || false;
+ this.columns = params.get('Columns') || 1728;
+ this.rows = params.get('Rows') || 0;
+ var eoblock = params.get('EndOfBlock');
+ if (eoblock === null || eoblock === undefined) {
+ eoblock = true;
+ }
+ this.eoblock = eoblock;
+ this.black = params.get('BlackIs1') || false;
+ this.codingLine = new Uint32Array(this.columns + 1);
+ this.refLine = new Uint32Array(this.columns + 2);
+ this.codingLine[0] = this.columns;
+ this.codingPos = 0;
+ this.row = 0;
+ this.nextLine2D = this.encoding < 0;
+ this.inputBits = 0;
+ this.inputBuf = 0;
+ this.outputBits = 0;
+ var code1;
+ while ((code1 = this.lookBits(12)) === 0) {
+ this.eatBits(1);
+ }
+ if (code1 === 1) {
+ this.eatBits(12);
+ }
+ if (this.encoding > 0) {
+ this.nextLine2D = !this.lookBits(1);
+ this.eatBits(1);
+ }
+ DecodeStream.call(this, maybeLength);
+ }
+ CCITTFaxStream.prototype = Object.create(DecodeStream.prototype);
+ CCITTFaxStream.prototype.readBlock = function CCITTFaxStream_readBlock() {
+ while (!this.eof) {
+ var c = this.lookChar();
+ this.ensureBuffer(this.bufferLength + 1);
+ this.buffer[this.bufferLength++] = c;
+ }
+ };
+ CCITTFaxStream.prototype.addPixels = function ccittFaxStreamAddPixels(a1, blackPixels) {
+ var codingLine = this.codingLine;
+ var codingPos = this.codingPos;
+ if (a1 > codingLine[codingPos]) {
+ if (a1 > this.columns) {
+ info('row is wrong length');
+ this.err = true;
+ a1 = this.columns;
+ }
+ if (codingPos & 1 ^ blackPixels) {
+ ++codingPos;
+ }
+ codingLine[codingPos] = a1;
+ }
+ this.codingPos = codingPos;
+ };
+ CCITTFaxStream.prototype.addPixelsNeg = function ccittFaxStreamAddPixelsNeg(a1, blackPixels) {
+ var codingLine = this.codingLine;
+ var codingPos = this.codingPos;
+ if (a1 > codingLine[codingPos]) {
+ if (a1 > this.columns) {
+ info('row is wrong length');
+ this.err = true;
+ a1 = this.columns;
+ }
+ if (codingPos & 1 ^ blackPixels) {
+ ++codingPos;
+ }
+ codingLine[codingPos] = a1;
+ } else if (a1 < codingLine[codingPos]) {
+ if (a1 < 0) {
+ info('invalid code');
+ this.err = true;
+ a1 = 0;
+ }
+ while (codingPos > 0 && a1 < codingLine[codingPos - 1]) {
+ --codingPos;
+ }
+ codingLine[codingPos] = a1;
+ }
+ this.codingPos = codingPos;
+ };
+ CCITTFaxStream.prototype.lookChar = function CCITTFaxStream_lookChar() {
+ var refLine = this.refLine;
+ var codingLine = this.codingLine;
+ var columns = this.columns;
+ var refPos, blackPixels, bits, i;
+ if (this.outputBits === 0) {
+ if (this.eof) {
+ return null;
+ }
+ this.err = false;
+ var code1, code2, code3;
+ if (this.nextLine2D) {
+ for (i = 0; codingLine[i] < columns; ++i) {
+ refLine[i] = codingLine[i];
+ }
+ refLine[i++] = columns;
+ refLine[i] = columns;
+ codingLine[0] = 0;
+ this.codingPos = 0;
+ refPos = 0;
+ blackPixels = 0;
+ while (codingLine[this.codingPos] < columns) {
+ code1 = this.getTwoDimCode();
+ switch (code1) {
+ case twoDimPass:
+ this.addPixels(refLine[refPos + 1], blackPixels);
+ if (refLine[refPos + 1] < columns) {
+ refPos += 2;
+ }
+ break;
+ case twoDimHoriz:
+ code1 = code2 = 0;
+ if (blackPixels) {
+ do {
+ code1 += code3 = this.getBlackCode();
+ } while (code3 >= 64);
+ do {
+ code2 += code3 = this.getWhiteCode();
+ } while (code3 >= 64);
+ } else {
+ do {
+ code1 += code3 = this.getWhiteCode();
+ } while (code3 >= 64);
+ do {
+ code2 += code3 = this.getBlackCode();
+ } while (code3 >= 64);
+ }
+ this.addPixels(codingLine[this.codingPos] + code1, blackPixels);
+ if (codingLine[this.codingPos] < columns) {
+ this.addPixels(codingLine[this.codingPos] + code2, blackPixels ^ 1);
+ }
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ break;
+ case twoDimVertR3:
+ this.addPixels(refLine[refPos] + 3, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case twoDimVertR2:
+ this.addPixels(refLine[refPos] + 2, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case twoDimVertR1:
+ this.addPixels(refLine[refPos] + 1, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case twoDimVert0:
+ this.addPixels(refLine[refPos], blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ ++refPos;
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case twoDimVertL3:
+ this.addPixelsNeg(refLine[refPos] - 3, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ if (refPos > 0) {
+ --refPos;
+ } else {
+ ++refPos;
+ }
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case twoDimVertL2:
+ this.addPixelsNeg(refLine[refPos] - 2, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ if (refPos > 0) {
+ --refPos;
+ } else {
+ ++refPos;
+ }
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case twoDimVertL1:
+ this.addPixelsNeg(refLine[refPos] - 1, blackPixels);
+ blackPixels ^= 1;
+ if (codingLine[this.codingPos] < columns) {
+ if (refPos > 0) {
+ --refPos;
+ } else {
+ ++refPos;
+ }
+ while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
+ refPos += 2;
+ }
+ }
+ break;
+ case ccittEOF:
+ this.addPixels(columns, 0);
+ this.eof = true;
+ break;
+ default:
+ info('bad 2d code');
+ this.addPixels(columns, 0);
+ this.err = true;
+ }
+ }
+ } else {
+ codingLine[0] = 0;
+ this.codingPos = 0;
+ blackPixels = 0;
+ while (codingLine[this.codingPos] < columns) {
+ code1 = 0;
+ if (blackPixels) {
+ do {
+ code1 += code3 = this.getBlackCode();
+ } while (code3 >= 64);
+ } else {
+ do {
+ code1 += code3 = this.getWhiteCode();
+ } while (code3 >= 64);
+ }
+ this.addPixels(codingLine[this.codingPos] + code1, blackPixels);
+ blackPixels ^= 1;
+ }
+ }
+ var gotEOL = false;
+ if (this.byteAlign) {
+ this.inputBits &= ~7;
+ }
+ if (!this.eoblock && this.row === this.rows - 1) {
+ this.eof = true;
+ } else {
+ code1 = this.lookBits(12);
+ if (this.eoline) {
+ while (code1 !== ccittEOF && code1 !== 1) {
+ this.eatBits(1);
+ code1 = this.lookBits(12);
+ }
+ } else {
+ while (code1 === 0) {
+ this.eatBits(1);
+ code1 = this.lookBits(12);
+ }
+ }
+ if (code1 === 1) {
+ this.eatBits(12);
+ gotEOL = true;
+ } else if (code1 === ccittEOF) {
+ this.eof = true;
+ }
+ }
+ if (!this.eof && this.encoding > 0) {
+ this.nextLine2D = !this.lookBits(1);
+ this.eatBits(1);
+ }
+ if (this.eoblock && gotEOL && this.byteAlign) {
+ code1 = this.lookBits(12);
+ if (code1 === 1) {
+ this.eatBits(12);
+ if (this.encoding > 0) {
+ this.lookBits(1);
+ this.eatBits(1);
+ }
+ if (this.encoding >= 0) {
+ for (i = 0; i < 4; ++i) {
+ code1 = this.lookBits(12);
+ if (code1 !== 1) {
+ info('bad rtc code: ' + code1);
+ }
+ this.eatBits(12);
+ if (this.encoding > 0) {
+ this.lookBits(1);
+ this.eatBits(1);
+ }
+ }
+ }
+ this.eof = true;
+ }
+ } else if (this.err && this.eoline) {
+ while (true) {
+ code1 = this.lookBits(13);
+ if (code1 === ccittEOF) {
+ this.eof = true;
+ return null;
+ }
+ if (code1 >> 1 === 1) {
+ break;
+ }
+ this.eatBits(1);
+ }
+ this.eatBits(12);
+ if (this.encoding > 0) {
+ this.eatBits(1);
+ this.nextLine2D = !(code1 & 1);
+ }
+ }
+ if (codingLine[0] > 0) {
+ this.outputBits = codingLine[this.codingPos = 0];
+ } else {
+ this.outputBits = codingLine[this.codingPos = 1];
+ }
+ this.row++;
+ }
+ var c;
+ if (this.outputBits >= 8) {
+ c = this.codingPos & 1 ? 0 : 0xFF;
+ this.outputBits -= 8;
+ if (this.outputBits === 0 && codingLine[this.codingPos] < columns) {
+ this.codingPos++;
+ this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
+ }
+ } else {
+ bits = 8;
+ c = 0;
+ do {
+ if (this.outputBits > bits) {
+ c <<= bits;
+ if (!(this.codingPos & 1)) {
+ c |= 0xFF >> 8 - bits;
+ }
+ this.outputBits -= bits;
+ bits = 0;
+ } else {
+ c <<= this.outputBits;
+ if (!(this.codingPos & 1)) {
+ c |= 0xFF >> 8 - this.outputBits;
+ }
+ bits -= this.outputBits;
+ this.outputBits = 0;
+ if (codingLine[this.codingPos] < columns) {
+ this.codingPos++;
+ this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
+ } else if (bits > 0) {
+ c <<= bits;
+ bits = 0;
+ }
+ }
+ } while (bits);
+ }
+ if (this.black) {
+ c ^= 0xFF;
+ }
+ return c;
+ };
+ // This functions returns the code found from the table.
+ // The start and end parameters set the boundaries for searching the table.
+ // The limit parameter is optional. Function returns an array with three
+ // values. The first array element indicates whether a valid code is being
+ // returned. The second array element is the actual code. The third array
+ // element indicates whether EOF was reached.
+ CCITTFaxStream.prototype.findTableCode = function ccittFaxStreamFindTableCode(start, end, table, limit) {
+ var limitValue = limit || 0;
+ for (var i = start; i <= end; ++i) {
+ var code = this.lookBits(i);
+ if (code === ccittEOF) {
+ return [
+ true,
+ 1,
+ false
+ ];
+ }
+ if (i < end) {
+ code <<= end - i;
+ }
+ if (!limitValue || code >= limitValue) {
+ var p = table[code - limitValue];
+ if (p[0] === i) {
+ this.eatBits(i);
+ return [
+ true,
+ p[1],
+ true
+ ];
+ }
+ }
+ }
+ return [
+ false,
+ 0,
+ false
+ ];
+ };
+ CCITTFaxStream.prototype.getTwoDimCode = function ccittFaxStreamGetTwoDimCode() {
+ var code = 0;
+ var p;
+ if (this.eoblock) {
+ code = this.lookBits(7);
+ p = twoDimTable[code];
+ if (p && p[0] > 0) {
+ this.eatBits(p[0]);
+ return p[1];
+ }
+ } else {
+ var result = this.findTableCode(1, 7, twoDimTable);
+ if (result[0] && result[2]) {
+ return result[1];
+ }
+ }
+ info('Bad two dim code');
+ return ccittEOF;
+ };
+ CCITTFaxStream.prototype.getWhiteCode = function ccittFaxStreamGetWhiteCode() {
+ var code = 0;
+ var p;
+ if (this.eoblock) {
+ code = this.lookBits(12);
+ if (code === ccittEOF) {
+ return 1;
+ }
+ if (code >> 5 === 0) {
+ p = whiteTable1[code];
+ } else {
+ p = whiteTable2[code >> 3];
+ }
+ if (p[0] > 0) {
+ this.eatBits(p[0]);
+ return p[1];
+ }
+ } else {
+ var result = this.findTableCode(1, 9, whiteTable2);
+ if (result[0]) {
+ return result[1];
+ }
+ result = this.findTableCode(11, 12, whiteTable1);
+ if (result[0]) {
+ return result[1];
+ }
+ }
+ info('bad white code');
+ this.eatBits(1);
+ return 1;
+ };
+ CCITTFaxStream.prototype.getBlackCode = function ccittFaxStreamGetBlackCode() {
+ var code, p;
+ if (this.eoblock) {
+ code = this.lookBits(13);
+ if (code === ccittEOF) {
+ return 1;
+ }
+ if (code >> 7 === 0) {
+ p = blackTable1[code];
+ } else if (code >> 9 === 0 && code >> 7 !== 0) {
+ p = blackTable2[(code >> 1) - 64];
+ } else {
+ p = blackTable3[code >> 7];
+ }
+ if (p[0] > 0) {
+ this.eatBits(p[0]);
+ return p[1];
+ }
+ } else {
+ var result = this.findTableCode(2, 6, blackTable3);
+ if (result[0]) {
+ return result[1];
+ }
+ result = this.findTableCode(7, 12, blackTable2, 64);
+ if (result[0]) {
+ return result[1];
+ }
+ result = this.findTableCode(10, 13, blackTable1);
+ if (result[0]) {
+ return result[1];
+ }
+ }
+ info('bad black code');
+ this.eatBits(1);
+ return 1;
+ };
+ CCITTFaxStream.prototype.lookBits = function CCITTFaxStream_lookBits(n) {
+ var c;
+ while (this.inputBits < n) {
+ if ((c = this.str.getByte()) === -1) {
+ if (this.inputBits === 0) {
+ return ccittEOF;
+ }
+ return this.inputBuf << n - this.inputBits & 0xFFFF >> 16 - n;
+ }
+ this.inputBuf = this.inputBuf << 8 | c;
+ this.inputBits += 8;
+ }
+ return this.inputBuf >> this.inputBits - n & 0xFFFF >> 16 - n;
+ };
+ CCITTFaxStream.prototype.eatBits = function CCITTFaxStream_eatBits(n) {
+ if ((this.inputBits -= n) < 0) {
+ this.inputBits = 0;
+ }
+ };
+ return CCITTFaxStream;
+ }();
+ var LZWStream = function LZWStreamClosure() {
+ function LZWStream(str, maybeLength, earlyChange) {
+ this.str = str;
+ this.dict = str.dict;
+ this.cachedData = 0;
+ this.bitsCached = 0;
+ var maxLzwDictionarySize = 4096;
+ var lzwState = {
+ earlyChange: earlyChange,
+ codeLength: 9,
+ nextCode: 258,
+ dictionaryValues: new Uint8Array(maxLzwDictionarySize),
+ dictionaryLengths: new Uint16Array(maxLzwDictionarySize),
+ dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize),
+ currentSequence: new Uint8Array(maxLzwDictionarySize),
+ currentSequenceLength: 0
+ };
+ for (var i = 0; i < 256; ++i) {
+ lzwState.dictionaryValues[i] = i;
+ lzwState.dictionaryLengths[i] = 1;
+ }
+ this.lzwState = lzwState;
+ DecodeStream.call(this, maybeLength);
+ }
+ LZWStream.prototype = Object.create(DecodeStream.prototype);
+ LZWStream.prototype.readBits = function LZWStream_readBits(n) {
+ var bitsCached = this.bitsCached;
+ var cachedData = this.cachedData;
+ while (bitsCached < n) {
+ var c = this.str.getByte();
+ if (c === -1) {
+ this.eof = true;
+ return null;
+ }
+ cachedData = cachedData << 8 | c;
+ bitsCached += 8;
+ }
+ this.bitsCached = bitsCached -= n;
+ this.cachedData = cachedData;
+ this.lastCode = null;
+ return cachedData >>> bitsCached & (1 << n) - 1;
+ };
+ LZWStream.prototype.readBlock = function LZWStream_readBlock() {
+ var blockSize = 512;
+ var estimatedDecodedSize = blockSize * 2, decodedSizeDelta = blockSize;
+ var i, j, q;
+ var lzwState = this.lzwState;
+ if (!lzwState) {
+ return;
+ }
+ // eof was found
+ var earlyChange = lzwState.earlyChange;
+ var nextCode = lzwState.nextCode;
+ var dictionaryValues = lzwState.dictionaryValues;
+ var dictionaryLengths = lzwState.dictionaryLengths;
+ var dictionaryPrevCodes = lzwState.dictionaryPrevCodes;
+ var codeLength = lzwState.codeLength;
+ var prevCode = lzwState.prevCode;
+ var currentSequence = lzwState.currentSequence;
+ var currentSequenceLength = lzwState.currentSequenceLength;
+ var decodedLength = 0;
+ var currentBufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
+ for (i = 0; i < blockSize; i++) {
+ var code = this.readBits(codeLength);
+ var hasPrev = currentSequenceLength > 0;
+ if (code < 256) {
+ currentSequence[0] = code;
+ currentSequenceLength = 1;
+ } else if (code >= 258) {
+ if (code < nextCode) {
+ currentSequenceLength = dictionaryLengths[code];
+ for (j = currentSequenceLength - 1, q = code; j >= 0; j--) {
+ currentSequence[j] = dictionaryValues[q];
+ q = dictionaryPrevCodes[q];
+ }
+ } else {
+ currentSequence[currentSequenceLength++] = currentSequence[0];
+ }
+ } else if (code === 256) {
+ codeLength = 9;
+ nextCode = 258;
+ currentSequenceLength = 0;
+ continue;
+ } else {
+ this.eof = true;
+ delete this.lzwState;
+ break;
+ }
+ if (hasPrev) {
+ dictionaryPrevCodes[nextCode] = prevCode;
+ dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1;
+ dictionaryValues[nextCode] = currentSequence[0];
+ nextCode++;
+ codeLength = nextCode + earlyChange & nextCode + earlyChange - 1 ? codeLength : Math.min(Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, 12) | 0;
+ }
+ prevCode = code;
+ decodedLength += currentSequenceLength;
+ if (estimatedDecodedSize < decodedLength) {
+ do {
+ estimatedDecodedSize += decodedSizeDelta;
+ } while (estimatedDecodedSize < decodedLength);
+ buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
+ }
+ for (j = 0; j < currentSequenceLength; j++) {
+ buffer[currentBufferLength++] = currentSequence[j];
+ }
+ }
+ lzwState.nextCode = nextCode;
+ lzwState.codeLength = codeLength;
+ lzwState.prevCode = prevCode;
+ lzwState.currentSequenceLength = currentSequenceLength;
+ this.bufferLength = currentBufferLength;
+ };
+ return LZWStream;
+ }();
+ var NullStream = function NullStreamClosure() {
+ function NullStream() {
+ Stream.call(this, new Uint8Array(0));
+ }
+ NullStream.prototype = Stream.prototype;
+ return NullStream;
+ }();
+ exports.Ascii85Stream = Ascii85Stream;
+ exports.AsciiHexStream = AsciiHexStream;
+ exports.CCITTFaxStream = CCITTFaxStream;
+ exports.DecryptStream = DecryptStream;
+ exports.DecodeStream = DecodeStream;
+ exports.FlateStream = FlateStream;
+ exports.Jbig2Stream = Jbig2Stream;
+ exports.JpegStream = JpegStream;
+ exports.JpxStream = JpxStream;
+ exports.NullStream = NullStream;
+ exports.PredictorStream = PredictorStream;
+ exports.RunLengthStream = RunLengthStream;
+ exports.Stream = Stream;
+ exports.StreamsSequenceStream = StreamsSequenceStream;
+ exports.StringStream = StringStream;
+ exports.LZWStream = LZWStream;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreCrypto = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream);
+ }(this, function (exports, sharedUtil, corePrimitives, coreStream) {
+ var PasswordException = sharedUtil.PasswordException;
+ var PasswordResponses = sharedUtil.PasswordResponses;
+ var bytesToString = sharedUtil.bytesToString;
+ var warn = sharedUtil.warn;
+ var error = sharedUtil.error;
+ var assert = sharedUtil.assert;
+ var isInt = sharedUtil.isInt;
+ var stringToBytes = sharedUtil.stringToBytes;
+ var utf8StringToString = sharedUtil.utf8StringToString;
+ var Name = corePrimitives.Name;
+ var isName = corePrimitives.isName;
+ var isDict = corePrimitives.isDict;
+ var DecryptStream = coreStream.DecryptStream;
+ var ARCFourCipher = function ARCFourCipherClosure() {
+ function ARCFourCipher(key) {
+ this.a = 0;
+ this.b = 0;
+ var s = new Uint8Array(256);
+ var i, j = 0, tmp, keyLength = key.length;
+ for (i = 0; i < 256; ++i) {
+ s[i] = i;
+ }
+ for (i = 0; i < 256; ++i) {
+ tmp = s[i];
+ j = j + tmp + key[i % keyLength] & 0xFF;
+ s[i] = s[j];
+ s[j] = tmp;
+ }
+ this.s = s;
+ }
+ ARCFourCipher.prototype = {
+ encryptBlock: function ARCFourCipher_encryptBlock(data) {
+ var i, n = data.length, tmp, tmp2;
+ var a = this.a, b = this.b, s = this.s;
+ var output = new Uint8Array(n);
+ for (i = 0; i < n; ++i) {
+ a = a + 1 & 0xFF;
+ tmp = s[a];
+ b = b + tmp & 0xFF;
+ tmp2 = s[b];
+ s[a] = tmp2;
+ s[b] = tmp;
+ output[i] = data[i] ^ s[tmp + tmp2 & 0xFF];
+ }
+ this.a = a;
+ this.b = b;
+ return output;
+ }
+ };
+ ARCFourCipher.prototype.decryptBlock = ARCFourCipher.prototype.encryptBlock;
+ return ARCFourCipher;
+ }();
+ var calculateMD5 = function calculateMD5Closure() {
+ var r = new Uint8Array([
+ 7,
+ 12,
+ 17,
+ 22,
+ 7,
+ 12,
+ 17,
+ 22,
+ 7,
+ 12,
+ 17,
+ 22,
+ 7,
+ 12,
+ 17,
+ 22,
+ 5,
+ 9,
+ 14,
+ 20,
+ 5,
+ 9,
+ 14,
+ 20,
+ 5,
+ 9,
+ 14,
+ 20,
+ 5,
+ 9,
+ 14,
+ 20,
+ 4,
+ 11,
+ 16,
+ 23,
+ 4,
+ 11,
+ 16,
+ 23,
+ 4,
+ 11,
+ 16,
+ 23,
+ 4,
+ 11,
+ 16,
+ 23,
+ 6,
+ 10,
+ 15,
+ 21,
+ 6,
+ 10,
+ 15,
+ 21,
+ 6,
+ 10,
+ 15,
+ 21,
+ 6,
+ 10,
+ 15,
+ 21
+ ]);
+ var k = new Int32Array([
+ -680876936,
+ -389564586,
+ 606105819,
+ -1044525330,
+ -176418897,
+ 1200080426,
+ -1473231341,
+ -45705983,
+ 1770035416,
+ -1958414417,
+ -42063,
+ -1990404162,
+ 1804603682,
+ -40341101,
+ -1502002290,
+ 1236535329,
+ -165796510,
+ -1069501632,
+ 643717713,
+ -373897302,
+ -701558691,
+ 38016083,
+ -660478335,
+ -405537848,
+ 568446438,
+ -1019803690,
+ -187363961,
+ 1163531501,
+ -1444681467,
+ -51403784,
+ 1735328473,
+ -1926607734,
+ -378558,
+ -2022574463,
+ 1839030562,
+ -35309556,
+ -1530992060,
+ 1272893353,
+ -155497632,
+ -1094730640,
+ 681279174,
+ -358537222,
+ -722521979,
+ 76029189,
+ -640364487,
+ -421815835,
+ 530742520,
+ -995338651,
+ -198630844,
+ 1126891415,
+ -1416354905,
+ -57434055,
+ 1700485571,
+ -1894986606,
+ -1051523,
+ -2054922799,
+ 1873313359,
+ -30611744,
+ -1560198380,
+ 1309151649,
+ -145523070,
+ -1120210379,
+ 718787259,
+ -343485551
+ ]);
+ function hash(data, offset, length) {
+ var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
+ // pre-processing
+ var paddedLength = length + 72 & ~63;
+ // data + 9 extra bytes
+ var padded = new Uint8Array(paddedLength);
+ var i, j, n;
+ for (i = 0; i < length; ++i) {
+ padded[i] = data[offset++];
+ }
+ padded[i++] = 0x80;
+ n = paddedLength - 8;
+ while (i < n) {
+ padded[i++] = 0;
+ }
+ padded[i++] = length << 3 & 0xFF;
+ padded[i++] = length >> 5 & 0xFF;
+ padded[i++] = length >> 13 & 0xFF;
+ padded[i++] = length >> 21 & 0xFF;
+ padded[i++] = length >>> 29 & 0xFF;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ var w = new Int32Array(16);
+ for (i = 0; i < paddedLength;) {
+ for (j = 0; j < 16; ++j, i += 4) {
+ w[j] = padded[i] | padded[i + 1] << 8 | padded[i + 2] << 16 | padded[i + 3] << 24;
+ }
+ var a = h0, b = h1, c = h2, d = h3, f, g;
+ for (j = 0; j < 64; ++j) {
+ if (j < 16) {
+ f = b & c | ~b & d;
+ g = j;
+ } else if (j < 32) {
+ f = d & b | ~d & c;
+ g = 5 * j + 1 & 15;
+ } else if (j < 48) {
+ f = b ^ c ^ d;
+ g = 3 * j + 5 & 15;
+ } else {
+ f = c ^ (b | ~d);
+ g = 7 * j & 15;
+ }
+ var tmp = d, rotateArg = a + f + k[j] + w[g] | 0, rotate = r[j];
+ d = c;
+ c = b;
+ b = b + (rotateArg << rotate | rotateArg >>> 32 - rotate) | 0;
+ a = tmp;
+ }
+ h0 = h0 + a | 0;
+ h1 = h1 + b | 0;
+ h2 = h2 + c | 0;
+ h3 = h3 + d | 0;
+ }
+ return new Uint8Array([
+ h0 & 0xFF,
+ h0 >> 8 & 0xFF,
+ h0 >> 16 & 0xFF,
+ h0 >>> 24 & 0xFF,
+ h1 & 0xFF,
+ h1 >> 8 & 0xFF,
+ h1 >> 16 & 0xFF,
+ h1 >>> 24 & 0xFF,
+ h2 & 0xFF,
+ h2 >> 8 & 0xFF,
+ h2 >> 16 & 0xFF,
+ h2 >>> 24 & 0xFF,
+ h3 & 0xFF,
+ h3 >> 8 & 0xFF,
+ h3 >> 16 & 0xFF,
+ h3 >>> 24 & 0xFF
+ ]);
+ }
+ return hash;
+ }();
+ var Word64 = function Word64Closure() {
+ function Word64(highInteger, lowInteger) {
+ this.high = highInteger | 0;
+ this.low = lowInteger | 0;
+ }
+ Word64.prototype = {
+ and: function Word64_and(word) {
+ this.high &= word.high;
+ this.low &= word.low;
+ },
+ xor: function Word64_xor(word) {
+ this.high ^= word.high;
+ this.low ^= word.low;
+ },
+ or: function Word64_or(word) {
+ this.high |= word.high;
+ this.low |= word.low;
+ },
+ shiftRight: function Word64_shiftRight(places) {
+ if (places >= 32) {
+ this.low = this.high >>> places - 32 | 0;
+ this.high = 0;
+ } else {
+ this.low = this.low >>> places | this.high << 32 - places;
+ this.high = this.high >>> places | 0;
+ }
+ },
+ shiftLeft: function Word64_shiftLeft(places) {
+ if (places >= 32) {
+ this.high = this.low << places - 32;
+ this.low = 0;
+ } else {
+ this.high = this.high << places | this.low >>> 32 - places;
+ this.low = this.low << places;
+ }
+ },
+ rotateRight: function Word64_rotateRight(places) {
+ var low, high;
+ if (places & 32) {
+ high = this.low;
+ low = this.high;
+ } else {
+ low = this.low;
+ high = this.high;
+ }
+ places &= 31;
+ this.low = low >>> places | high << 32 - places;
+ this.high = high >>> places | low << 32 - places;
+ },
+ not: function Word64_not() {
+ this.high = ~this.high;
+ this.low = ~this.low;
+ },
+ add: function Word64_add(word) {
+ var lowAdd = (this.low >>> 0) + (word.low >>> 0);
+ var highAdd = (this.high >>> 0) + (word.high >>> 0);
+ if (lowAdd > 0xFFFFFFFF) {
+ highAdd += 1;
+ }
+ this.low = lowAdd | 0;
+ this.high = highAdd | 0;
+ },
+ copyTo: function Word64_copyTo(bytes, offset) {
+ bytes[offset] = this.high >>> 24 & 0xFF;
+ bytes[offset + 1] = this.high >> 16 & 0xFF;
+ bytes[offset + 2] = this.high >> 8 & 0xFF;
+ bytes[offset + 3] = this.high & 0xFF;
+ bytes[offset + 4] = this.low >>> 24 & 0xFF;
+ bytes[offset + 5] = this.low >> 16 & 0xFF;
+ bytes[offset + 6] = this.low >> 8 & 0xFF;
+ bytes[offset + 7] = this.low & 0xFF;
+ },
+ assign: function Word64_assign(word) {
+ this.high = word.high;
+ this.low = word.low;
+ }
+ };
+ return Word64;
+ }();
+ var calculateSHA256 = function calculateSHA256Closure() {
+ function rotr(x, n) {
+ return x >>> n | x << 32 - n;
+ }
+ function ch(x, y, z) {
+ return x & y ^ ~x & z;
+ }
+ function maj(x, y, z) {
+ return x & y ^ x & z ^ y & z;
+ }
+ function sigma(x) {
+ return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
+ }
+ function sigmaPrime(x) {
+ return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
+ }
+ function littleSigma(x) {
+ return rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
+ }
+ function littleSigmaPrime(x) {
+ return rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10;
+ }
+ var k = [
+ 0x428a2f98,
+ 0x71374491,
+ 0xb5c0fbcf,
+ 0xe9b5dba5,
+ 0x3956c25b,
+ 0x59f111f1,
+ 0x923f82a4,
+ 0xab1c5ed5,
+ 0xd807aa98,
+ 0x12835b01,
+ 0x243185be,
+ 0x550c7dc3,
+ 0x72be5d74,
+ 0x80deb1fe,
+ 0x9bdc06a7,
+ 0xc19bf174,
+ 0xe49b69c1,
+ 0xefbe4786,
+ 0x0fc19dc6,
+ 0x240ca1cc,
+ 0x2de92c6f,
+ 0x4a7484aa,
+ 0x5cb0a9dc,
+ 0x76f988da,
+ 0x983e5152,
+ 0xa831c66d,
+ 0xb00327c8,
+ 0xbf597fc7,
+ 0xc6e00bf3,
+ 0xd5a79147,
+ 0x06ca6351,
+ 0x14292967,
+ 0x27b70a85,
+ 0x2e1b2138,
+ 0x4d2c6dfc,
+ 0x53380d13,
+ 0x650a7354,
+ 0x766a0abb,
+ 0x81c2c92e,
+ 0x92722c85,
+ 0xa2bfe8a1,
+ 0xa81a664b,
+ 0xc24b8b70,
+ 0xc76c51a3,
+ 0xd192e819,
+ 0xd6990624,
+ 0xf40e3585,
+ 0x106aa070,
+ 0x19a4c116,
+ 0x1e376c08,
+ 0x2748774c,
+ 0x34b0bcb5,
+ 0x391c0cb3,
+ 0x4ed8aa4a,
+ 0x5b9cca4f,
+ 0x682e6ff3,
+ 0x748f82ee,
+ 0x78a5636f,
+ 0x84c87814,
+ 0x8cc70208,
+ 0x90befffa,
+ 0xa4506ceb,
+ 0xbef9a3f7,
+ 0xc67178f2
+ ];
+ function hash(data, offset, length) {
+ // initial hash values
+ var h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a, h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19;
+ // pre-processing
+ var paddedLength = Math.ceil((length + 9) / 64) * 64;
+ var padded = new Uint8Array(paddedLength);
+ var i, j, n;
+ for (i = 0; i < length; ++i) {
+ padded[i] = data[offset++];
+ }
+ padded[i++] = 0x80;
+ n = paddedLength - 8;
+ while (i < n) {
+ padded[i++] = 0;
+ }
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = length >>> 29 & 0xFF;
+ padded[i++] = length >> 21 & 0xFF;
+ padded[i++] = length >> 13 & 0xFF;
+ padded[i++] = length >> 5 & 0xFF;
+ padded[i++] = length << 3 & 0xFF;
+ var w = new Uint32Array(64);
+ // for each 512 bit block
+ for (i = 0; i < paddedLength;) {
+ for (j = 0; j < 16; ++j) {
+ w[j] = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
+ i += 4;
+ }
+ for (j = 16; j < 64; ++j) {
+ w[j] = littleSigmaPrime(w[j - 2]) + w[j - 7] + littleSigma(w[j - 15]) + w[j - 16] | 0;
+ }
+ var a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7, t1, t2;
+ for (j = 0; j < 64; ++j) {
+ t1 = h + sigmaPrime(e) + ch(e, f, g) + k[j] + w[j];
+ t2 = sigma(a) + maj(a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1 | 0;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2 | 0;
+ }
+ h0 = h0 + a | 0;
+ h1 = h1 + b | 0;
+ h2 = h2 + c | 0;
+ h3 = h3 + d | 0;
+ h4 = h4 + e | 0;
+ h5 = h5 + f | 0;
+ h6 = h6 + g | 0;
+ h7 = h7 + h | 0;
+ }
+ return new Uint8Array([
+ h0 >> 24 & 0xFF,
+ h0 >> 16 & 0xFF,
+ h0 >> 8 & 0xFF,
+ h0 & 0xFF,
+ h1 >> 24 & 0xFF,
+ h1 >> 16 & 0xFF,
+ h1 >> 8 & 0xFF,
+ h1 & 0xFF,
+ h2 >> 24 & 0xFF,
+ h2 >> 16 & 0xFF,
+ h2 >> 8 & 0xFF,
+ h2 & 0xFF,
+ h3 >> 24 & 0xFF,
+ h3 >> 16 & 0xFF,
+ h3 >> 8 & 0xFF,
+ h3 & 0xFF,
+ h4 >> 24 & 0xFF,
+ h4 >> 16 & 0xFF,
+ h4 >> 8 & 0xFF,
+ h4 & 0xFF,
+ h5 >> 24 & 0xFF,
+ h5 >> 16 & 0xFF,
+ h5 >> 8 & 0xFF,
+ h5 & 0xFF,
+ h6 >> 24 & 0xFF,
+ h6 >> 16 & 0xFF,
+ h6 >> 8 & 0xFF,
+ h6 & 0xFF,
+ h7 >> 24 & 0xFF,
+ h7 >> 16 & 0xFF,
+ h7 >> 8 & 0xFF,
+ h7 & 0xFF
+ ]);
+ }
+ return hash;
+ }();
+ var calculateSHA512 = function calculateSHA512Closure() {
+ function ch(result, x, y, z, tmp) {
+ result.assign(x);
+ result.and(y);
+ tmp.assign(x);
+ tmp.not();
+ tmp.and(z);
+ result.xor(tmp);
+ }
+ function maj(result, x, y, z, tmp) {
+ result.assign(x);
+ result.and(y);
+ tmp.assign(x);
+ tmp.and(z);
+ result.xor(tmp);
+ tmp.assign(y);
+ tmp.and(z);
+ result.xor(tmp);
+ }
+ function sigma(result, x, tmp) {
+ result.assign(x);
+ result.rotateRight(28);
+ tmp.assign(x);
+ tmp.rotateRight(34);
+ result.xor(tmp);
+ tmp.assign(x);
+ tmp.rotateRight(39);
+ result.xor(tmp);
+ }
+ function sigmaPrime(result, x, tmp) {
+ result.assign(x);
+ result.rotateRight(14);
+ tmp.assign(x);
+ tmp.rotateRight(18);
+ result.xor(tmp);
+ tmp.assign(x);
+ tmp.rotateRight(41);
+ result.xor(tmp);
+ }
+ function littleSigma(result, x, tmp) {
+ result.assign(x);
+ result.rotateRight(1);
+ tmp.assign(x);
+ tmp.rotateRight(8);
+ result.xor(tmp);
+ tmp.assign(x);
+ tmp.shiftRight(7);
+ result.xor(tmp);
+ }
+ function littleSigmaPrime(result, x, tmp) {
+ result.assign(x);
+ result.rotateRight(19);
+ tmp.assign(x);
+ tmp.rotateRight(61);
+ result.xor(tmp);
+ tmp.assign(x);
+ tmp.shiftRight(6);
+ result.xor(tmp);
+ }
+ var k = [
+ new Word64(0x428a2f98, 0xd728ae22),
+ new Word64(0x71374491, 0x23ef65cd),
+ new Word64(0xb5c0fbcf, 0xec4d3b2f),
+ new Word64(0xe9b5dba5, 0x8189dbbc),
+ new Word64(0x3956c25b, 0xf348b538),
+ new Word64(0x59f111f1, 0xb605d019),
+ new Word64(0x923f82a4, 0xaf194f9b),
+ new Word64(0xab1c5ed5, 0xda6d8118),
+ new Word64(0xd807aa98, 0xa3030242),
+ new Word64(0x12835b01, 0x45706fbe),
+ new Word64(0x243185be, 0x4ee4b28c),
+ new Word64(0x550c7dc3, 0xd5ffb4e2),
+ new Word64(0x72be5d74, 0xf27b896f),
+ new Word64(0x80deb1fe, 0x3b1696b1),
+ new Word64(0x9bdc06a7, 0x25c71235),
+ new Word64(0xc19bf174, 0xcf692694),
+ new Word64(0xe49b69c1, 0x9ef14ad2),
+ new Word64(0xefbe4786, 0x384f25e3),
+ new Word64(0x0fc19dc6, 0x8b8cd5b5),
+ new Word64(0x240ca1cc, 0x77ac9c65),
+ new Word64(0x2de92c6f, 0x592b0275),
+ new Word64(0x4a7484aa, 0x6ea6e483),
+ new Word64(0x5cb0a9dc, 0xbd41fbd4),
+ new Word64(0x76f988da, 0x831153b5),
+ new Word64(0x983e5152, 0xee66dfab),
+ new Word64(0xa831c66d, 0x2db43210),
+ new Word64(0xb00327c8, 0x98fb213f),
+ new Word64(0xbf597fc7, 0xbeef0ee4),
+ new Word64(0xc6e00bf3, 0x3da88fc2),
+ new Word64(0xd5a79147, 0x930aa725),
+ new Word64(0x06ca6351, 0xe003826f),
+ new Word64(0x14292967, 0x0a0e6e70),
+ new Word64(0x27b70a85, 0x46d22ffc),
+ new Word64(0x2e1b2138, 0x5c26c926),
+ new Word64(0x4d2c6dfc, 0x5ac42aed),
+ new Word64(0x53380d13, 0x9d95b3df),
+ new Word64(0x650a7354, 0x8baf63de),
+ new Word64(0x766a0abb, 0x3c77b2a8),
+ new Word64(0x81c2c92e, 0x47edaee6),
+ new Word64(0x92722c85, 0x1482353b),
+ new Word64(0xa2bfe8a1, 0x4cf10364),
+ new Word64(0xa81a664b, 0xbc423001),
+ new Word64(0xc24b8b70, 0xd0f89791),
+ new Word64(0xc76c51a3, 0x0654be30),
+ new Word64(0xd192e819, 0xd6ef5218),
+ new Word64(0xd6990624, 0x5565a910),
+ new Word64(0xf40e3585, 0x5771202a),
+ new Word64(0x106aa070, 0x32bbd1b8),
+ new Word64(0x19a4c116, 0xb8d2d0c8),
+ new Word64(0x1e376c08, 0x5141ab53),
+ new Word64(0x2748774c, 0xdf8eeb99),
+ new Word64(0x34b0bcb5, 0xe19b48a8),
+ new Word64(0x391c0cb3, 0xc5c95a63),
+ new Word64(0x4ed8aa4a, 0xe3418acb),
+ new Word64(0x5b9cca4f, 0x7763e373),
+ new Word64(0x682e6ff3, 0xd6b2b8a3),
+ new Word64(0x748f82ee, 0x5defb2fc),
+ new Word64(0x78a5636f, 0x43172f60),
+ new Word64(0x84c87814, 0xa1f0ab72),
+ new Word64(0x8cc70208, 0x1a6439ec),
+ new Word64(0x90befffa, 0x23631e28),
+ new Word64(0xa4506ceb, 0xde82bde9),
+ new Word64(0xbef9a3f7, 0xb2c67915),
+ new Word64(0xc67178f2, 0xe372532b),
+ new Word64(0xca273ece, 0xea26619c),
+ new Word64(0xd186b8c7, 0x21c0c207),
+ new Word64(0xeada7dd6, 0xcde0eb1e),
+ new Word64(0xf57d4f7f, 0xee6ed178),
+ new Word64(0x06f067aa, 0x72176fba),
+ new Word64(0x0a637dc5, 0xa2c898a6),
+ new Word64(0x113f9804, 0xbef90dae),
+ new Word64(0x1b710b35, 0x131c471b),
+ new Word64(0x28db77f5, 0x23047d84),
+ new Word64(0x32caab7b, 0x40c72493),
+ new Word64(0x3c9ebe0a, 0x15c9bebc),
+ new Word64(0x431d67c4, 0x9c100d4c),
+ new Word64(0x4cc5d4be, 0xcb3e42b6),
+ new Word64(0x597f299c, 0xfc657e2a),
+ new Word64(0x5fcb6fab, 0x3ad6faec),
+ new Word64(0x6c44198c, 0x4a475817)
+ ];
+ function hash(data, offset, length, mode384) {
+ mode384 = !!mode384;
+ // initial hash values
+ var h0, h1, h2, h3, h4, h5, h6, h7;
+ if (!mode384) {
+ h0 = new Word64(0x6a09e667, 0xf3bcc908);
+ h1 = new Word64(0xbb67ae85, 0x84caa73b);
+ h2 = new Word64(0x3c6ef372, 0xfe94f82b);
+ h3 = new Word64(0xa54ff53a, 0x5f1d36f1);
+ h4 = new Word64(0x510e527f, 0xade682d1);
+ h5 = new Word64(0x9b05688c, 0x2b3e6c1f);
+ h6 = new Word64(0x1f83d9ab, 0xfb41bd6b);
+ h7 = new Word64(0x5be0cd19, 0x137e2179);
+ } else {
+ // SHA384 is exactly the same
+ // except with different starting values and a trimmed result
+ h0 = new Word64(0xcbbb9d5d, 0xc1059ed8);
+ h1 = new Word64(0x629a292a, 0x367cd507);
+ h2 = new Word64(0x9159015a, 0x3070dd17);
+ h3 = new Word64(0x152fecd8, 0xf70e5939);
+ h4 = new Word64(0x67332667, 0xffc00b31);
+ h5 = new Word64(0x8eb44a87, 0x68581511);
+ h6 = new Word64(0xdb0c2e0d, 0x64f98fa7);
+ h7 = new Word64(0x47b5481d, 0xbefa4fa4);
+ }
+ // pre-processing
+ var paddedLength = Math.ceil((length + 17) / 128) * 128;
+ var padded = new Uint8Array(paddedLength);
+ var i, j, n;
+ for (i = 0; i < length; ++i) {
+ padded[i] = data[offset++];
+ }
+ padded[i++] = 0x80;
+ n = paddedLength - 16;
+ while (i < n) {
+ padded[i++] = 0;
+ }
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = 0;
+ padded[i++] = length >>> 29 & 0xFF;
+ padded[i++] = length >> 21 & 0xFF;
+ padded[i++] = length >> 13 & 0xFF;
+ padded[i++] = length >> 5 & 0xFF;
+ padded[i++] = length << 3 & 0xFF;
+ var w = new Array(80);
+ for (i = 0; i < 80; i++) {
+ w[i] = new Word64(0, 0);
+ }
+ var a = new Word64(0, 0), b = new Word64(0, 0), c = new Word64(0, 0);
+ var d = new Word64(0, 0), e = new Word64(0, 0), f = new Word64(0, 0);
+ var g = new Word64(0, 0), h = new Word64(0, 0);
+ var t1 = new Word64(0, 0), t2 = new Word64(0, 0);
+ var tmp1 = new Word64(0, 0), tmp2 = new Word64(0, 0), tmp3;
+ // for each 1024 bit block
+ for (i = 0; i < paddedLength;) {
+ for (j = 0; j < 16; ++j) {
+ w[j].high = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
+ w[j].low = padded[i + 4] << 24 | padded[i + 5] << 16 | padded[i + 6] << 8 | padded[i + 7];
+ i += 8;
+ }
+ for (j = 16; j < 80; ++j) {
+ tmp3 = w[j];
+ littleSigmaPrime(tmp3, w[j - 2], tmp2);
+ tmp3.add(w[j - 7]);
+ littleSigma(tmp1, w[j - 15], tmp2);
+ tmp3.add(tmp1);
+ tmp3.add(w[j - 16]);
+ }
+ a.assign(h0);
+ b.assign(h1);
+ c.assign(h2);
+ d.assign(h3);
+ e.assign(h4);
+ f.assign(h5);
+ g.assign(h6);
+ h.assign(h7);
+ for (j = 0; j < 80; ++j) {
+ t1.assign(h);
+ sigmaPrime(tmp1, e, tmp2);
+ t1.add(tmp1);
+ ch(tmp1, e, f, g, tmp2);
+ t1.add(tmp1);
+ t1.add(k[j]);
+ t1.add(w[j]);
+ sigma(t2, a, tmp2);
+ maj(tmp1, a, b, c, tmp2);
+ t2.add(tmp1);
+ tmp3 = h;
+ h = g;
+ g = f;
+ f = e;
+ d.add(t1);
+ e = d;
+ d = c;
+ c = b;
+ b = a;
+ tmp3.assign(t1);
+ tmp3.add(t2);
+ a = tmp3;
+ }
+ h0.add(a);
+ h1.add(b);
+ h2.add(c);
+ h3.add(d);
+ h4.add(e);
+ h5.add(f);
+ h6.add(g);
+ h7.add(h);
+ }
+ var result;
+ if (!mode384) {
+ result = new Uint8Array(64);
+ h0.copyTo(result, 0);
+ h1.copyTo(result, 8);
+ h2.copyTo(result, 16);
+ h3.copyTo(result, 24);
+ h4.copyTo(result, 32);
+ h5.copyTo(result, 40);
+ h6.copyTo(result, 48);
+ h7.copyTo(result, 56);
+ } else {
+ result = new Uint8Array(48);
+ h0.copyTo(result, 0);
+ h1.copyTo(result, 8);
+ h2.copyTo(result, 16);
+ h3.copyTo(result, 24);
+ h4.copyTo(result, 32);
+ h5.copyTo(result, 40);
+ }
+ return result;
+ }
+ return hash;
+ }();
+ var calculateSHA384 = function calculateSHA384Closure() {
+ function hash(data, offset, length) {
+ return calculateSHA512(data, offset, length, true);
+ }
+ return hash;
+ }();
+ var NullCipher = function NullCipherClosure() {
+ function NullCipher() {
+ }
+ NullCipher.prototype = {
+ decryptBlock: function NullCipher_decryptBlock(data) {
+ return data;
+ }
+ };
+ return NullCipher;
+ }();
+ var AES128Cipher = function AES128CipherClosure() {
+ var rcon = new Uint8Array([
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d
+ ]);
+ var s = new Uint8Array([
+ 0x63,
+ 0x7c,
+ 0x77,
+ 0x7b,
+ 0xf2,
+ 0x6b,
+ 0x6f,
+ 0xc5,
+ 0x30,
+ 0x01,
+ 0x67,
+ 0x2b,
+ 0xfe,
+ 0xd7,
+ 0xab,
+ 0x76,
+ 0xca,
+ 0x82,
+ 0xc9,
+ 0x7d,
+ 0xfa,
+ 0x59,
+ 0x47,
+ 0xf0,
+ 0xad,
+ 0xd4,
+ 0xa2,
+ 0xaf,
+ 0x9c,
+ 0xa4,
+ 0x72,
+ 0xc0,
+ 0xb7,
+ 0xfd,
+ 0x93,
+ 0x26,
+ 0x36,
+ 0x3f,
+ 0xf7,
+ 0xcc,
+ 0x34,
+ 0xa5,
+ 0xe5,
+ 0xf1,
+ 0x71,
+ 0xd8,
+ 0x31,
+ 0x15,
+ 0x04,
+ 0xc7,
+ 0x23,
+ 0xc3,
+ 0x18,
+ 0x96,
+ 0x05,
+ 0x9a,
+ 0x07,
+ 0x12,
+ 0x80,
+ 0xe2,
+ 0xeb,
+ 0x27,
+ 0xb2,
+ 0x75,
+ 0x09,
+ 0x83,
+ 0x2c,
+ 0x1a,
+ 0x1b,
+ 0x6e,
+ 0x5a,
+ 0xa0,
+ 0x52,
+ 0x3b,
+ 0xd6,
+ 0xb3,
+ 0x29,
+ 0xe3,
+ 0x2f,
+ 0x84,
+ 0x53,
+ 0xd1,
+ 0x00,
+ 0xed,
+ 0x20,
+ 0xfc,
+ 0xb1,
+ 0x5b,
+ 0x6a,
+ 0xcb,
+ 0xbe,
+ 0x39,
+ 0x4a,
+ 0x4c,
+ 0x58,
+ 0xcf,
+ 0xd0,
+ 0xef,
+ 0xaa,
+ 0xfb,
+ 0x43,
+ 0x4d,
+ 0x33,
+ 0x85,
+ 0x45,
+ 0xf9,
+ 0x02,
+ 0x7f,
+ 0x50,
+ 0x3c,
+ 0x9f,
+ 0xa8,
+ 0x51,
+ 0xa3,
+ 0x40,
+ 0x8f,
+ 0x92,
+ 0x9d,
+ 0x38,
+ 0xf5,
+ 0xbc,
+ 0xb6,
+ 0xda,
+ 0x21,
+ 0x10,
+ 0xff,
+ 0xf3,
+ 0xd2,
+ 0xcd,
+ 0x0c,
+ 0x13,
+ 0xec,
+ 0x5f,
+ 0x97,
+ 0x44,
+ 0x17,
+ 0xc4,
+ 0xa7,
+ 0x7e,
+ 0x3d,
+ 0x64,
+ 0x5d,
+ 0x19,
+ 0x73,
+ 0x60,
+ 0x81,
+ 0x4f,
+ 0xdc,
+ 0x22,
+ 0x2a,
+ 0x90,
+ 0x88,
+ 0x46,
+ 0xee,
+ 0xb8,
+ 0x14,
+ 0xde,
+ 0x5e,
+ 0x0b,
+ 0xdb,
+ 0xe0,
+ 0x32,
+ 0x3a,
+ 0x0a,
+ 0x49,
+ 0x06,
+ 0x24,
+ 0x5c,
+ 0xc2,
+ 0xd3,
+ 0xac,
+ 0x62,
+ 0x91,
+ 0x95,
+ 0xe4,
+ 0x79,
+ 0xe7,
+ 0xc8,
+ 0x37,
+ 0x6d,
+ 0x8d,
+ 0xd5,
+ 0x4e,
+ 0xa9,
+ 0x6c,
+ 0x56,
+ 0xf4,
+ 0xea,
+ 0x65,
+ 0x7a,
+ 0xae,
+ 0x08,
+ 0xba,
+ 0x78,
+ 0x25,
+ 0x2e,
+ 0x1c,
+ 0xa6,
+ 0xb4,
+ 0xc6,
+ 0xe8,
+ 0xdd,
+ 0x74,
+ 0x1f,
+ 0x4b,
+ 0xbd,
+ 0x8b,
+ 0x8a,
+ 0x70,
+ 0x3e,
+ 0xb5,
+ 0x66,
+ 0x48,
+ 0x03,
+ 0xf6,
+ 0x0e,
+ 0x61,
+ 0x35,
+ 0x57,
+ 0xb9,
+ 0x86,
+ 0xc1,
+ 0x1d,
+ 0x9e,
+ 0xe1,
+ 0xf8,
+ 0x98,
+ 0x11,
+ 0x69,
+ 0xd9,
+ 0x8e,
+ 0x94,
+ 0x9b,
+ 0x1e,
+ 0x87,
+ 0xe9,
+ 0xce,
+ 0x55,
+ 0x28,
+ 0xdf,
+ 0x8c,
+ 0xa1,
+ 0x89,
+ 0x0d,
+ 0xbf,
+ 0xe6,
+ 0x42,
+ 0x68,
+ 0x41,
+ 0x99,
+ 0x2d,
+ 0x0f,
+ 0xb0,
+ 0x54,
+ 0xbb,
+ 0x16
+ ]);
+ var inv_s = new Uint8Array([
+ 0x52,
+ 0x09,
+ 0x6a,
+ 0xd5,
+ 0x30,
+ 0x36,
+ 0xa5,
+ 0x38,
+ 0xbf,
+ 0x40,
+ 0xa3,
+ 0x9e,
+ 0x81,
+ 0xf3,
+ 0xd7,
+ 0xfb,
+ 0x7c,
+ 0xe3,
+ 0x39,
+ 0x82,
+ 0x9b,
+ 0x2f,
+ 0xff,
+ 0x87,
+ 0x34,
+ 0x8e,
+ 0x43,
+ 0x44,
+ 0xc4,
+ 0xde,
+ 0xe9,
+ 0xcb,
+ 0x54,
+ 0x7b,
+ 0x94,
+ 0x32,
+ 0xa6,
+ 0xc2,
+ 0x23,
+ 0x3d,
+ 0xee,
+ 0x4c,
+ 0x95,
+ 0x0b,
+ 0x42,
+ 0xfa,
+ 0xc3,
+ 0x4e,
+ 0x08,
+ 0x2e,
+ 0xa1,
+ 0x66,
+ 0x28,
+ 0xd9,
+ 0x24,
+ 0xb2,
+ 0x76,
+ 0x5b,
+ 0xa2,
+ 0x49,
+ 0x6d,
+ 0x8b,
+ 0xd1,
+ 0x25,
+ 0x72,
+ 0xf8,
+ 0xf6,
+ 0x64,
+ 0x86,
+ 0x68,
+ 0x98,
+ 0x16,
+ 0xd4,
+ 0xa4,
+ 0x5c,
+ 0xcc,
+ 0x5d,
+ 0x65,
+ 0xb6,
+ 0x92,
+ 0x6c,
+ 0x70,
+ 0x48,
+ 0x50,
+ 0xfd,
+ 0xed,
+ 0xb9,
+ 0xda,
+ 0x5e,
+ 0x15,
+ 0x46,
+ 0x57,
+ 0xa7,
+ 0x8d,
+ 0x9d,
+ 0x84,
+ 0x90,
+ 0xd8,
+ 0xab,
+ 0x00,
+ 0x8c,
+ 0xbc,
+ 0xd3,
+ 0x0a,
+ 0xf7,
+ 0xe4,
+ 0x58,
+ 0x05,
+ 0xb8,
+ 0xb3,
+ 0x45,
+ 0x06,
+ 0xd0,
+ 0x2c,
+ 0x1e,
+ 0x8f,
+ 0xca,
+ 0x3f,
+ 0x0f,
+ 0x02,
+ 0xc1,
+ 0xaf,
+ 0xbd,
+ 0x03,
+ 0x01,
+ 0x13,
+ 0x8a,
+ 0x6b,
+ 0x3a,
+ 0x91,
+ 0x11,
+ 0x41,
+ 0x4f,
+ 0x67,
+ 0xdc,
+ 0xea,
+ 0x97,
+ 0xf2,
+ 0xcf,
+ 0xce,
+ 0xf0,
+ 0xb4,
+ 0xe6,
+ 0x73,
+ 0x96,
+ 0xac,
+ 0x74,
+ 0x22,
+ 0xe7,
+ 0xad,
+ 0x35,
+ 0x85,
+ 0xe2,
+ 0xf9,
+ 0x37,
+ 0xe8,
+ 0x1c,
+ 0x75,
+ 0xdf,
+ 0x6e,
+ 0x47,
+ 0xf1,
+ 0x1a,
+ 0x71,
+ 0x1d,
+ 0x29,
+ 0xc5,
+ 0x89,
+ 0x6f,
+ 0xb7,
+ 0x62,
+ 0x0e,
+ 0xaa,
+ 0x18,
+ 0xbe,
+ 0x1b,
+ 0xfc,
+ 0x56,
+ 0x3e,
+ 0x4b,
+ 0xc6,
+ 0xd2,
+ 0x79,
+ 0x20,
+ 0x9a,
+ 0xdb,
+ 0xc0,
+ 0xfe,
+ 0x78,
+ 0xcd,
+ 0x5a,
+ 0xf4,
+ 0x1f,
+ 0xdd,
+ 0xa8,
+ 0x33,
+ 0x88,
+ 0x07,
+ 0xc7,
+ 0x31,
+ 0xb1,
+ 0x12,
+ 0x10,
+ 0x59,
+ 0x27,
+ 0x80,
+ 0xec,
+ 0x5f,
+ 0x60,
+ 0x51,
+ 0x7f,
+ 0xa9,
+ 0x19,
+ 0xb5,
+ 0x4a,
+ 0x0d,
+ 0x2d,
+ 0xe5,
+ 0x7a,
+ 0x9f,
+ 0x93,
+ 0xc9,
+ 0x9c,
+ 0xef,
+ 0xa0,
+ 0xe0,
+ 0x3b,
+ 0x4d,
+ 0xae,
+ 0x2a,
+ 0xf5,
+ 0xb0,
+ 0xc8,
+ 0xeb,
+ 0xbb,
+ 0x3c,
+ 0x83,
+ 0x53,
+ 0x99,
+ 0x61,
+ 0x17,
+ 0x2b,
+ 0x04,
+ 0x7e,
+ 0xba,
+ 0x77,
+ 0xd6,
+ 0x26,
+ 0xe1,
+ 0x69,
+ 0x14,
+ 0x63,
+ 0x55,
+ 0x21,
+ 0x0c,
+ 0x7d
+ ]);
+ var mixCol = new Uint8Array(256);
+ for (var i = 0; i < 256; i++) {
+ if (i < 128) {
+ mixCol[i] = i << 1;
+ } else {
+ mixCol[i] = i << 1 ^ 0x1b;
+ }
+ }
+ var mix = new Uint32Array([
+ 0x00000000,
+ 0x0e090d0b,
+ 0x1c121a16,
+ 0x121b171d,
+ 0x3824342c,
+ 0x362d3927,
+ 0x24362e3a,
+ 0x2a3f2331,
+ 0x70486858,
+ 0x7e416553,
+ 0x6c5a724e,
+ 0x62537f45,
+ 0x486c5c74,
+ 0x4665517f,
+ 0x547e4662,
+ 0x5a774b69,
+ 0xe090d0b0,
+ 0xee99ddbb,
+ 0xfc82caa6,
+ 0xf28bc7ad,
+ 0xd8b4e49c,
+ 0xd6bde997,
+ 0xc4a6fe8a,
+ 0xcaaff381,
+ 0x90d8b8e8,
+ 0x9ed1b5e3,
+ 0x8ccaa2fe,
+ 0x82c3aff5,
+ 0xa8fc8cc4,
+ 0xa6f581cf,
+ 0xb4ee96d2,
+ 0xbae79bd9,
+ 0xdb3bbb7b,
+ 0xd532b670,
+ 0xc729a16d,
+ 0xc920ac66,
+ 0xe31f8f57,
+ 0xed16825c,
+ 0xff0d9541,
+ 0xf104984a,
+ 0xab73d323,
+ 0xa57ade28,
+ 0xb761c935,
+ 0xb968c43e,
+ 0x9357e70f,
+ 0x9d5eea04,
+ 0x8f45fd19,
+ 0x814cf012,
+ 0x3bab6bcb,
+ 0x35a266c0,
+ 0x27b971dd,
+ 0x29b07cd6,
+ 0x038f5fe7,
+ 0x0d8652ec,
+ 0x1f9d45f1,
+ 0x119448fa,
+ 0x4be30393,
+ 0x45ea0e98,
+ 0x57f11985,
+ 0x59f8148e,
+ 0x73c737bf,
+ 0x7dce3ab4,
+ 0x6fd52da9,
+ 0x61dc20a2,
+ 0xad766df6,
+ 0xa37f60fd,
+ 0xb16477e0,
+ 0xbf6d7aeb,
+ 0x955259da,
+ 0x9b5b54d1,
+ 0x894043cc,
+ 0x87494ec7,
+ 0xdd3e05ae,
+ 0xd33708a5,
+ 0xc12c1fb8,
+ 0xcf2512b3,
+ 0xe51a3182,
+ 0xeb133c89,
+ 0xf9082b94,
+ 0xf701269f,
+ 0x4de6bd46,
+ 0x43efb04d,
+ 0x51f4a750,
+ 0x5ffdaa5b,
+ 0x75c2896a,
+ 0x7bcb8461,
+ 0x69d0937c,
+ 0x67d99e77,
+ 0x3daed51e,
+ 0x33a7d815,
+ 0x21bccf08,
+ 0x2fb5c203,
+ 0x058ae132,
+ 0x0b83ec39,
+ 0x1998fb24,
+ 0x1791f62f,
+ 0x764dd68d,
+ 0x7844db86,
+ 0x6a5fcc9b,
+ 0x6456c190,
+ 0x4e69e2a1,
+ 0x4060efaa,
+ 0x527bf8b7,
+ 0x5c72f5bc,
+ 0x0605bed5,
+ 0x080cb3de,
+ 0x1a17a4c3,
+ 0x141ea9c8,
+ 0x3e218af9,
+ 0x302887f2,
+ 0x223390ef,
+ 0x2c3a9de4,
+ 0x96dd063d,
+ 0x98d40b36,
+ 0x8acf1c2b,
+ 0x84c61120,
+ 0xaef93211,
+ 0xa0f03f1a,
+ 0xb2eb2807,
+ 0xbce2250c,
+ 0xe6956e65,
+ 0xe89c636e,
+ 0xfa877473,
+ 0xf48e7978,
+ 0xdeb15a49,
+ 0xd0b85742,
+ 0xc2a3405f,
+ 0xccaa4d54,
+ 0x41ecdaf7,
+ 0x4fe5d7fc,
+ 0x5dfec0e1,
+ 0x53f7cdea,
+ 0x79c8eedb,
+ 0x77c1e3d0,
+ 0x65daf4cd,
+ 0x6bd3f9c6,
+ 0x31a4b2af,
+ 0x3fadbfa4,
+ 0x2db6a8b9,
+ 0x23bfa5b2,
+ 0x09808683,
+ 0x07898b88,
+ 0x15929c95,
+ 0x1b9b919e,
+ 0xa17c0a47,
+ 0xaf75074c,
+ 0xbd6e1051,
+ 0xb3671d5a,
+ 0x99583e6b,
+ 0x97513360,
+ 0x854a247d,
+ 0x8b432976,
+ 0xd134621f,
+ 0xdf3d6f14,
+ 0xcd267809,
+ 0xc32f7502,
+ 0xe9105633,
+ 0xe7195b38,
+ 0xf5024c25,
+ 0xfb0b412e,
+ 0x9ad7618c,
+ 0x94de6c87,
+ 0x86c57b9a,
+ 0x88cc7691,
+ 0xa2f355a0,
+ 0xacfa58ab,
+ 0xbee14fb6,
+ 0xb0e842bd,
+ 0xea9f09d4,
+ 0xe49604df,
+ 0xf68d13c2,
+ 0xf8841ec9,
+ 0xd2bb3df8,
+ 0xdcb230f3,
+ 0xcea927ee,
+ 0xc0a02ae5,
+ 0x7a47b13c,
+ 0x744ebc37,
+ 0x6655ab2a,
+ 0x685ca621,
+ 0x42638510,
+ 0x4c6a881b,
+ 0x5e719f06,
+ 0x5078920d,
+ 0x0a0fd964,
+ 0x0406d46f,
+ 0x161dc372,
+ 0x1814ce79,
+ 0x322bed48,
+ 0x3c22e043,
+ 0x2e39f75e,
+ 0x2030fa55,
+ 0xec9ab701,
+ 0xe293ba0a,
+ 0xf088ad17,
+ 0xfe81a01c,
+ 0xd4be832d,
+ 0xdab78e26,
+ 0xc8ac993b,
+ 0xc6a59430,
+ 0x9cd2df59,
+ 0x92dbd252,
+ 0x80c0c54f,
+ 0x8ec9c844,
+ 0xa4f6eb75,
+ 0xaaffe67e,
+ 0xb8e4f163,
+ 0xb6edfc68,
+ 0x0c0a67b1,
+ 0x02036aba,
+ 0x10187da7,
+ 0x1e1170ac,
+ 0x342e539d,
+ 0x3a275e96,
+ 0x283c498b,
+ 0x26354480,
+ 0x7c420fe9,
+ 0x724b02e2,
+ 0x605015ff,
+ 0x6e5918f4,
+ 0x44663bc5,
+ 0x4a6f36ce,
+ 0x587421d3,
+ 0x567d2cd8,
+ 0x37a10c7a,
+ 0x39a80171,
+ 0x2bb3166c,
+ 0x25ba1b67,
+ 0x0f853856,
+ 0x018c355d,
+ 0x13972240,
+ 0x1d9e2f4b,
+ 0x47e96422,
+ 0x49e06929,
+ 0x5bfb7e34,
+ 0x55f2733f,
+ 0x7fcd500e,
+ 0x71c45d05,
+ 0x63df4a18,
+ 0x6dd64713,
+ 0xd731dcca,
+ 0xd938d1c1,
+ 0xcb23c6dc,
+ 0xc52acbd7,
+ 0xef15e8e6,
+ 0xe11ce5ed,
+ 0xf307f2f0,
+ 0xfd0efffb,
+ 0xa779b492,
+ 0xa970b999,
+ 0xbb6bae84,
+ 0xb562a38f,
+ 0x9f5d80be,
+ 0x91548db5,
+ 0x834f9aa8,
+ 0x8d4697a3
+ ]);
+ function expandKey128(cipherKey) {
+ var b = 176, result = new Uint8Array(b);
+ result.set(cipherKey);
+ for (var j = 16, i = 1; j < b; ++i) {
+ // RotWord
+ var t1 = result[j - 3], t2 = result[j - 2], t3 = result[j - 1], t4 = result[j - 4];
+ // SubWord
+ t1 = s[t1];
+ t2 = s[t2];
+ t3 = s[t3];
+ t4 = s[t4];
+ // Rcon
+ t1 = t1 ^ rcon[i];
+ for (var n = 0; n < 4; ++n) {
+ result[j] = t1 ^= result[j - 16];
+ j++;
+ result[j] = t2 ^= result[j - 16];
+ j++;
+ result[j] = t3 ^= result[j - 16];
+ j++;
+ result[j] = t4 ^= result[j - 16];
+ j++;
+ }
+ }
+ return result;
+ }
+ function decrypt128(input, key) {
+ var state = new Uint8Array(16);
+ state.set(input);
+ var i, j, k;
+ var t, u, v;
+ // AddRoundKey
+ for (j = 0, k = 160; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ for (i = 9; i >= 1; --i) {
+ // InvShiftRows
+ t = state[13];
+ state[13] = state[9];
+ state[9] = state[5];
+ state[5] = state[1];
+ state[1] = t;
+ t = state[14];
+ u = state[10];
+ state[14] = state[6];
+ state[10] = state[2];
+ state[6] = t;
+ state[2] = u;
+ t = state[15];
+ u = state[11];
+ v = state[7];
+ state[15] = state[3];
+ state[11] = t;
+ state[7] = u;
+ state[3] = v;
+ // InvSubBytes
+ for (j = 0; j < 16; ++j) {
+ state[j] = inv_s[state[j]];
+ }
+ // AddRoundKey
+ for (j = 0, k = i * 16; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ // InvMixColumns
+ for (j = 0; j < 16; j += 4) {
+ var s0 = mix[state[j]], s1 = mix[state[j + 1]], s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
+ t = s0 ^ s1 >>> 8 ^ s1 << 24 ^ s2 >>> 16 ^ s2 << 16 ^ s3 >>> 24 ^ s3 << 8;
+ state[j] = t >>> 24 & 0xFF;
+ state[j + 1] = t >> 16 & 0xFF;
+ state[j + 2] = t >> 8 & 0xFF;
+ state[j + 3] = t & 0xFF;
+ }
+ }
+ // InvShiftRows
+ t = state[13];
+ state[13] = state[9];
+ state[9] = state[5];
+ state[5] = state[1];
+ state[1] = t;
+ t = state[14];
+ u = state[10];
+ state[14] = state[6];
+ state[10] = state[2];
+ state[6] = t;
+ state[2] = u;
+ t = state[15];
+ u = state[11];
+ v = state[7];
+ state[15] = state[3];
+ state[11] = t;
+ state[7] = u;
+ state[3] = v;
+ for (j = 0; j < 16; ++j) {
+ // InvSubBytes
+ state[j] = inv_s[state[j]];
+ // AddRoundKey
+ state[j] ^= key[j];
+ }
+ return state;
+ }
+ function encrypt128(input, key) {
+ var t, u, v, k;
+ var state = new Uint8Array(16);
+ state.set(input);
+ for (j = 0; j < 16; ++j) {
+ // AddRoundKey
+ state[j] ^= key[j];
+ }
+ for (i = 1; i < 10; i++) {
+ //SubBytes
+ for (j = 0; j < 16; ++j) {
+ state[j] = s[state[j]];
+ }
+ //ShiftRows
+ v = state[1];
+ state[1] = state[5];
+ state[5] = state[9];
+ state[9] = state[13];
+ state[13] = v;
+ v = state[2];
+ u = state[6];
+ state[2] = state[10];
+ state[6] = state[14];
+ state[10] = v;
+ state[14] = u;
+ v = state[3];
+ u = state[7];
+ t = state[11];
+ state[3] = state[15];
+ state[7] = v;
+ state[11] = u;
+ state[15] = t;
+ //MixColumns
+ for (var j = 0; j < 16; j += 4) {
+ var s0 = state[j + 0], s1 = state[j + 1];
+ var s2 = state[j + 2], s3 = state[j + 3];
+ t = s0 ^ s1 ^ s2 ^ s3;
+ state[j + 0] ^= t ^ mixCol[s0 ^ s1];
+ state[j + 1] ^= t ^ mixCol[s1 ^ s2];
+ state[j + 2] ^= t ^ mixCol[s2 ^ s3];
+ state[j + 3] ^= t ^ mixCol[s3 ^ s0];
+ }
+ //AddRoundKey
+ for (j = 0, k = i * 16; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ }
+ //SubBytes
+ for (j = 0; j < 16; ++j) {
+ state[j] = s[state[j]];
+ }
+ //ShiftRows
+ v = state[1];
+ state[1] = state[5];
+ state[5] = state[9];
+ state[9] = state[13];
+ state[13] = v;
+ v = state[2];
+ u = state[6];
+ state[2] = state[10];
+ state[6] = state[14];
+ state[10] = v;
+ state[14] = u;
+ v = state[3];
+ u = state[7];
+ t = state[11];
+ state[3] = state[15];
+ state[7] = v;
+ state[11] = u;
+ state[15] = t;
+ //AddRoundKey
+ for (j = 0, k = 160; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ return state;
+ }
+ function AES128Cipher(key) {
+ this.key = expandKey128(key);
+ this.buffer = new Uint8Array(16);
+ this.bufferPosition = 0;
+ }
+ function decryptBlock2(data, finalize) {
+ var i, j, ii, sourceLength = data.length, buffer = this.buffer, bufferLength = this.bufferPosition, result = [], iv = this.iv;
+ for (i = 0; i < sourceLength; ++i) {
+ buffer[bufferLength] = data[i];
+ ++bufferLength;
+ if (bufferLength < 16) {
+ continue;
+ }
+ // buffer is full, decrypting
+ var plain = decrypt128(buffer, this.key);
+ // xor-ing the IV vector to get plain text
+ for (j = 0; j < 16; ++j) {
+ plain[j] ^= iv[j];
+ }
+ iv = buffer;
+ result.push(plain);
+ buffer = new Uint8Array(16);
+ bufferLength = 0;
+ }
+ // saving incomplete buffer
+ this.buffer = buffer;
+ this.bufferLength = bufferLength;
+ this.iv = iv;
+ if (result.length === 0) {
+ return new Uint8Array([]);
+ }
+ // combining plain text blocks into one
+ var outputLength = 16 * result.length;
+ if (finalize) {
+ // undo a padding that is described in RFC 2898
+ var lastBlock = result[result.length - 1];
+ var psLen = lastBlock[15];
+ if (psLen <= 16) {
+ for (i = 15, ii = 16 - psLen; i >= ii; --i) {
+ if (lastBlock[i] !== psLen) {
+ // Invalid padding, assume that the block has no padding.
+ psLen = 0;
+ break;
+ }
+ }
+ outputLength -= psLen;
+ result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
+ }
+ }
+ var output = new Uint8Array(outputLength);
+ for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
+ output.set(result[i], j);
+ }
+ return output;
+ }
+ AES128Cipher.prototype = {
+ decryptBlock: function AES128Cipher_decryptBlock(data, finalize) {
+ var i, sourceLength = data.length;
+ var buffer = this.buffer, bufferLength = this.bufferPosition;
+ // waiting for IV values -- they are at the start of the stream
+ for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
+ buffer[bufferLength] = data[i];
+ }
+ if (bufferLength < 16) {
+ // need more data
+ this.bufferLength = bufferLength;
+ return new Uint8Array([]);
+ }
+ this.iv = buffer;
+ this.buffer = new Uint8Array(16);
+ this.bufferLength = 0;
+ // starting decryption
+ this.decryptBlock = decryptBlock2;
+ return this.decryptBlock(data.subarray(16), finalize);
+ },
+ encrypt: function AES128Cipher_encrypt(data, iv) {
+ var i, j, ii, sourceLength = data.length, buffer = this.buffer, bufferLength = this.bufferPosition, result = [];
+ if (!iv) {
+ iv = new Uint8Array(16);
+ }
+ for (i = 0; i < sourceLength; ++i) {
+ buffer[bufferLength] = data[i];
+ ++bufferLength;
+ if (bufferLength < 16) {
+ continue;
+ }
+ for (j = 0; j < 16; ++j) {
+ buffer[j] ^= iv[j];
+ }
+ // buffer is full, encrypting
+ var cipher = encrypt128(buffer, this.key);
+ iv = cipher;
+ result.push(cipher);
+ buffer = new Uint8Array(16);
+ bufferLength = 0;
+ }
+ // saving incomplete buffer
+ this.buffer = buffer;
+ this.bufferLength = bufferLength;
+ this.iv = iv;
+ if (result.length === 0) {
+ return new Uint8Array([]);
+ }
+ // combining plain text blocks into one
+ var outputLength = 16 * result.length;
+ var output = new Uint8Array(outputLength);
+ for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
+ output.set(result[i], j);
+ }
+ return output;
+ }
+ };
+ return AES128Cipher;
+ }();
+ var AES256Cipher = function AES256CipherClosure() {
+ var rcon = new Uint8Array([
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d,
+ 0x01,
+ 0x02,
+ 0x04,
+ 0x08,
+ 0x10,
+ 0x20,
+ 0x40,
+ 0x80,
+ 0x1b,
+ 0x36,
+ 0x6c,
+ 0xd8,
+ 0xab,
+ 0x4d,
+ 0x9a,
+ 0x2f,
+ 0x5e,
+ 0xbc,
+ 0x63,
+ 0xc6,
+ 0x97,
+ 0x35,
+ 0x6a,
+ 0xd4,
+ 0xb3,
+ 0x7d,
+ 0xfa,
+ 0xef,
+ 0xc5,
+ 0x91,
+ 0x39,
+ 0x72,
+ 0xe4,
+ 0xd3,
+ 0xbd,
+ 0x61,
+ 0xc2,
+ 0x9f,
+ 0x25,
+ 0x4a,
+ 0x94,
+ 0x33,
+ 0x66,
+ 0xcc,
+ 0x83,
+ 0x1d,
+ 0x3a,
+ 0x74,
+ 0xe8,
+ 0xcb,
+ 0x8d
+ ]);
+ var s = new Uint8Array([
+ 0x63,
+ 0x7c,
+ 0x77,
+ 0x7b,
+ 0xf2,
+ 0x6b,
+ 0x6f,
+ 0xc5,
+ 0x30,
+ 0x01,
+ 0x67,
+ 0x2b,
+ 0xfe,
+ 0xd7,
+ 0xab,
+ 0x76,
+ 0xca,
+ 0x82,
+ 0xc9,
+ 0x7d,
+ 0xfa,
+ 0x59,
+ 0x47,
+ 0xf0,
+ 0xad,
+ 0xd4,
+ 0xa2,
+ 0xaf,
+ 0x9c,
+ 0xa4,
+ 0x72,
+ 0xc0,
+ 0xb7,
+ 0xfd,
+ 0x93,
+ 0x26,
+ 0x36,
+ 0x3f,
+ 0xf7,
+ 0xcc,
+ 0x34,
+ 0xa5,
+ 0xe5,
+ 0xf1,
+ 0x71,
+ 0xd8,
+ 0x31,
+ 0x15,
+ 0x04,
+ 0xc7,
+ 0x23,
+ 0xc3,
+ 0x18,
+ 0x96,
+ 0x05,
+ 0x9a,
+ 0x07,
+ 0x12,
+ 0x80,
+ 0xe2,
+ 0xeb,
+ 0x27,
+ 0xb2,
+ 0x75,
+ 0x09,
+ 0x83,
+ 0x2c,
+ 0x1a,
+ 0x1b,
+ 0x6e,
+ 0x5a,
+ 0xa0,
+ 0x52,
+ 0x3b,
+ 0xd6,
+ 0xb3,
+ 0x29,
+ 0xe3,
+ 0x2f,
+ 0x84,
+ 0x53,
+ 0xd1,
+ 0x00,
+ 0xed,
+ 0x20,
+ 0xfc,
+ 0xb1,
+ 0x5b,
+ 0x6a,
+ 0xcb,
+ 0xbe,
+ 0x39,
+ 0x4a,
+ 0x4c,
+ 0x58,
+ 0xcf,
+ 0xd0,
+ 0xef,
+ 0xaa,
+ 0xfb,
+ 0x43,
+ 0x4d,
+ 0x33,
+ 0x85,
+ 0x45,
+ 0xf9,
+ 0x02,
+ 0x7f,
+ 0x50,
+ 0x3c,
+ 0x9f,
+ 0xa8,
+ 0x51,
+ 0xa3,
+ 0x40,
+ 0x8f,
+ 0x92,
+ 0x9d,
+ 0x38,
+ 0xf5,
+ 0xbc,
+ 0xb6,
+ 0xda,
+ 0x21,
+ 0x10,
+ 0xff,
+ 0xf3,
+ 0xd2,
+ 0xcd,
+ 0x0c,
+ 0x13,
+ 0xec,
+ 0x5f,
+ 0x97,
+ 0x44,
+ 0x17,
+ 0xc4,
+ 0xa7,
+ 0x7e,
+ 0x3d,
+ 0x64,
+ 0x5d,
+ 0x19,
+ 0x73,
+ 0x60,
+ 0x81,
+ 0x4f,
+ 0xdc,
+ 0x22,
+ 0x2a,
+ 0x90,
+ 0x88,
+ 0x46,
+ 0xee,
+ 0xb8,
+ 0x14,
+ 0xde,
+ 0x5e,
+ 0x0b,
+ 0xdb,
+ 0xe0,
+ 0x32,
+ 0x3a,
+ 0x0a,
+ 0x49,
+ 0x06,
+ 0x24,
+ 0x5c,
+ 0xc2,
+ 0xd3,
+ 0xac,
+ 0x62,
+ 0x91,
+ 0x95,
+ 0xe4,
+ 0x79,
+ 0xe7,
+ 0xc8,
+ 0x37,
+ 0x6d,
+ 0x8d,
+ 0xd5,
+ 0x4e,
+ 0xa9,
+ 0x6c,
+ 0x56,
+ 0xf4,
+ 0xea,
+ 0x65,
+ 0x7a,
+ 0xae,
+ 0x08,
+ 0xba,
+ 0x78,
+ 0x25,
+ 0x2e,
+ 0x1c,
+ 0xa6,
+ 0xb4,
+ 0xc6,
+ 0xe8,
+ 0xdd,
+ 0x74,
+ 0x1f,
+ 0x4b,
+ 0xbd,
+ 0x8b,
+ 0x8a,
+ 0x70,
+ 0x3e,
+ 0xb5,
+ 0x66,
+ 0x48,
+ 0x03,
+ 0xf6,
+ 0x0e,
+ 0x61,
+ 0x35,
+ 0x57,
+ 0xb9,
+ 0x86,
+ 0xc1,
+ 0x1d,
+ 0x9e,
+ 0xe1,
+ 0xf8,
+ 0x98,
+ 0x11,
+ 0x69,
+ 0xd9,
+ 0x8e,
+ 0x94,
+ 0x9b,
+ 0x1e,
+ 0x87,
+ 0xe9,
+ 0xce,
+ 0x55,
+ 0x28,
+ 0xdf,
+ 0x8c,
+ 0xa1,
+ 0x89,
+ 0x0d,
+ 0xbf,
+ 0xe6,
+ 0x42,
+ 0x68,
+ 0x41,
+ 0x99,
+ 0x2d,
+ 0x0f,
+ 0xb0,
+ 0x54,
+ 0xbb,
+ 0x16
+ ]);
+ var inv_s = new Uint8Array([
+ 0x52,
+ 0x09,
+ 0x6a,
+ 0xd5,
+ 0x30,
+ 0x36,
+ 0xa5,
+ 0x38,
+ 0xbf,
+ 0x40,
+ 0xa3,
+ 0x9e,
+ 0x81,
+ 0xf3,
+ 0xd7,
+ 0xfb,
+ 0x7c,
+ 0xe3,
+ 0x39,
+ 0x82,
+ 0x9b,
+ 0x2f,
+ 0xff,
+ 0x87,
+ 0x34,
+ 0x8e,
+ 0x43,
+ 0x44,
+ 0xc4,
+ 0xde,
+ 0xe9,
+ 0xcb,
+ 0x54,
+ 0x7b,
+ 0x94,
+ 0x32,
+ 0xa6,
+ 0xc2,
+ 0x23,
+ 0x3d,
+ 0xee,
+ 0x4c,
+ 0x95,
+ 0x0b,
+ 0x42,
+ 0xfa,
+ 0xc3,
+ 0x4e,
+ 0x08,
+ 0x2e,
+ 0xa1,
+ 0x66,
+ 0x28,
+ 0xd9,
+ 0x24,
+ 0xb2,
+ 0x76,
+ 0x5b,
+ 0xa2,
+ 0x49,
+ 0x6d,
+ 0x8b,
+ 0xd1,
+ 0x25,
+ 0x72,
+ 0xf8,
+ 0xf6,
+ 0x64,
+ 0x86,
+ 0x68,
+ 0x98,
+ 0x16,
+ 0xd4,
+ 0xa4,
+ 0x5c,
+ 0xcc,
+ 0x5d,
+ 0x65,
+ 0xb6,
+ 0x92,
+ 0x6c,
+ 0x70,
+ 0x48,
+ 0x50,
+ 0xfd,
+ 0xed,
+ 0xb9,
+ 0xda,
+ 0x5e,
+ 0x15,
+ 0x46,
+ 0x57,
+ 0xa7,
+ 0x8d,
+ 0x9d,
+ 0x84,
+ 0x90,
+ 0xd8,
+ 0xab,
+ 0x00,
+ 0x8c,
+ 0xbc,
+ 0xd3,
+ 0x0a,
+ 0xf7,
+ 0xe4,
+ 0x58,
+ 0x05,
+ 0xb8,
+ 0xb3,
+ 0x45,
+ 0x06,
+ 0xd0,
+ 0x2c,
+ 0x1e,
+ 0x8f,
+ 0xca,
+ 0x3f,
+ 0x0f,
+ 0x02,
+ 0xc1,
+ 0xaf,
+ 0xbd,
+ 0x03,
+ 0x01,
+ 0x13,
+ 0x8a,
+ 0x6b,
+ 0x3a,
+ 0x91,
+ 0x11,
+ 0x41,
+ 0x4f,
+ 0x67,
+ 0xdc,
+ 0xea,
+ 0x97,
+ 0xf2,
+ 0xcf,
+ 0xce,
+ 0xf0,
+ 0xb4,
+ 0xe6,
+ 0x73,
+ 0x96,
+ 0xac,
+ 0x74,
+ 0x22,
+ 0xe7,
+ 0xad,
+ 0x35,
+ 0x85,
+ 0xe2,
+ 0xf9,
+ 0x37,
+ 0xe8,
+ 0x1c,
+ 0x75,
+ 0xdf,
+ 0x6e,
+ 0x47,
+ 0xf1,
+ 0x1a,
+ 0x71,
+ 0x1d,
+ 0x29,
+ 0xc5,
+ 0x89,
+ 0x6f,
+ 0xb7,
+ 0x62,
+ 0x0e,
+ 0xaa,
+ 0x18,
+ 0xbe,
+ 0x1b,
+ 0xfc,
+ 0x56,
+ 0x3e,
+ 0x4b,
+ 0xc6,
+ 0xd2,
+ 0x79,
+ 0x20,
+ 0x9a,
+ 0xdb,
+ 0xc0,
+ 0xfe,
+ 0x78,
+ 0xcd,
+ 0x5a,
+ 0xf4,
+ 0x1f,
+ 0xdd,
+ 0xa8,
+ 0x33,
+ 0x88,
+ 0x07,
+ 0xc7,
+ 0x31,
+ 0xb1,
+ 0x12,
+ 0x10,
+ 0x59,
+ 0x27,
+ 0x80,
+ 0xec,
+ 0x5f,
+ 0x60,
+ 0x51,
+ 0x7f,
+ 0xa9,
+ 0x19,
+ 0xb5,
+ 0x4a,
+ 0x0d,
+ 0x2d,
+ 0xe5,
+ 0x7a,
+ 0x9f,
+ 0x93,
+ 0xc9,
+ 0x9c,
+ 0xef,
+ 0xa0,
+ 0xe0,
+ 0x3b,
+ 0x4d,
+ 0xae,
+ 0x2a,
+ 0xf5,
+ 0xb0,
+ 0xc8,
+ 0xeb,
+ 0xbb,
+ 0x3c,
+ 0x83,
+ 0x53,
+ 0x99,
+ 0x61,
+ 0x17,
+ 0x2b,
+ 0x04,
+ 0x7e,
+ 0xba,
+ 0x77,
+ 0xd6,
+ 0x26,
+ 0xe1,
+ 0x69,
+ 0x14,
+ 0x63,
+ 0x55,
+ 0x21,
+ 0x0c,
+ 0x7d
+ ]);
+ var mixCol = new Uint8Array(256);
+ for (var i = 0; i < 256; i++) {
+ if (i < 128) {
+ mixCol[i] = i << 1;
+ } else {
+ mixCol[i] = i << 1 ^ 0x1b;
+ }
+ }
+ var mix = new Uint32Array([
+ 0x00000000,
+ 0x0e090d0b,
+ 0x1c121a16,
+ 0x121b171d,
+ 0x3824342c,
+ 0x362d3927,
+ 0x24362e3a,
+ 0x2a3f2331,
+ 0x70486858,
+ 0x7e416553,
+ 0x6c5a724e,
+ 0x62537f45,
+ 0x486c5c74,
+ 0x4665517f,
+ 0x547e4662,
+ 0x5a774b69,
+ 0xe090d0b0,
+ 0xee99ddbb,
+ 0xfc82caa6,
+ 0xf28bc7ad,
+ 0xd8b4e49c,
+ 0xd6bde997,
+ 0xc4a6fe8a,
+ 0xcaaff381,
+ 0x90d8b8e8,
+ 0x9ed1b5e3,
+ 0x8ccaa2fe,
+ 0x82c3aff5,
+ 0xa8fc8cc4,
+ 0xa6f581cf,
+ 0xb4ee96d2,
+ 0xbae79bd9,
+ 0xdb3bbb7b,
+ 0xd532b670,
+ 0xc729a16d,
+ 0xc920ac66,
+ 0xe31f8f57,
+ 0xed16825c,
+ 0xff0d9541,
+ 0xf104984a,
+ 0xab73d323,
+ 0xa57ade28,
+ 0xb761c935,
+ 0xb968c43e,
+ 0x9357e70f,
+ 0x9d5eea04,
+ 0x8f45fd19,
+ 0x814cf012,
+ 0x3bab6bcb,
+ 0x35a266c0,
+ 0x27b971dd,
+ 0x29b07cd6,
+ 0x038f5fe7,
+ 0x0d8652ec,
+ 0x1f9d45f1,
+ 0x119448fa,
+ 0x4be30393,
+ 0x45ea0e98,
+ 0x57f11985,
+ 0x59f8148e,
+ 0x73c737bf,
+ 0x7dce3ab4,
+ 0x6fd52da9,
+ 0x61dc20a2,
+ 0xad766df6,
+ 0xa37f60fd,
+ 0xb16477e0,
+ 0xbf6d7aeb,
+ 0x955259da,
+ 0x9b5b54d1,
+ 0x894043cc,
+ 0x87494ec7,
+ 0xdd3e05ae,
+ 0xd33708a5,
+ 0xc12c1fb8,
+ 0xcf2512b3,
+ 0xe51a3182,
+ 0xeb133c89,
+ 0xf9082b94,
+ 0xf701269f,
+ 0x4de6bd46,
+ 0x43efb04d,
+ 0x51f4a750,
+ 0x5ffdaa5b,
+ 0x75c2896a,
+ 0x7bcb8461,
+ 0x69d0937c,
+ 0x67d99e77,
+ 0x3daed51e,
+ 0x33a7d815,
+ 0x21bccf08,
+ 0x2fb5c203,
+ 0x058ae132,
+ 0x0b83ec39,
+ 0x1998fb24,
+ 0x1791f62f,
+ 0x764dd68d,
+ 0x7844db86,
+ 0x6a5fcc9b,
+ 0x6456c190,
+ 0x4e69e2a1,
+ 0x4060efaa,
+ 0x527bf8b7,
+ 0x5c72f5bc,
+ 0x0605bed5,
+ 0x080cb3de,
+ 0x1a17a4c3,
+ 0x141ea9c8,
+ 0x3e218af9,
+ 0x302887f2,
+ 0x223390ef,
+ 0x2c3a9de4,
+ 0x96dd063d,
+ 0x98d40b36,
+ 0x8acf1c2b,
+ 0x84c61120,
+ 0xaef93211,
+ 0xa0f03f1a,
+ 0xb2eb2807,
+ 0xbce2250c,
+ 0xe6956e65,
+ 0xe89c636e,
+ 0xfa877473,
+ 0xf48e7978,
+ 0xdeb15a49,
+ 0xd0b85742,
+ 0xc2a3405f,
+ 0xccaa4d54,
+ 0x41ecdaf7,
+ 0x4fe5d7fc,
+ 0x5dfec0e1,
+ 0x53f7cdea,
+ 0x79c8eedb,
+ 0x77c1e3d0,
+ 0x65daf4cd,
+ 0x6bd3f9c6,
+ 0x31a4b2af,
+ 0x3fadbfa4,
+ 0x2db6a8b9,
+ 0x23bfa5b2,
+ 0x09808683,
+ 0x07898b88,
+ 0x15929c95,
+ 0x1b9b919e,
+ 0xa17c0a47,
+ 0xaf75074c,
+ 0xbd6e1051,
+ 0xb3671d5a,
+ 0x99583e6b,
+ 0x97513360,
+ 0x854a247d,
+ 0x8b432976,
+ 0xd134621f,
+ 0xdf3d6f14,
+ 0xcd267809,
+ 0xc32f7502,
+ 0xe9105633,
+ 0xe7195b38,
+ 0xf5024c25,
+ 0xfb0b412e,
+ 0x9ad7618c,
+ 0x94de6c87,
+ 0x86c57b9a,
+ 0x88cc7691,
+ 0xa2f355a0,
+ 0xacfa58ab,
+ 0xbee14fb6,
+ 0xb0e842bd,
+ 0xea9f09d4,
+ 0xe49604df,
+ 0xf68d13c2,
+ 0xf8841ec9,
+ 0xd2bb3df8,
+ 0xdcb230f3,
+ 0xcea927ee,
+ 0xc0a02ae5,
+ 0x7a47b13c,
+ 0x744ebc37,
+ 0x6655ab2a,
+ 0x685ca621,
+ 0x42638510,
+ 0x4c6a881b,
+ 0x5e719f06,
+ 0x5078920d,
+ 0x0a0fd964,
+ 0x0406d46f,
+ 0x161dc372,
+ 0x1814ce79,
+ 0x322bed48,
+ 0x3c22e043,
+ 0x2e39f75e,
+ 0x2030fa55,
+ 0xec9ab701,
+ 0xe293ba0a,
+ 0xf088ad17,
+ 0xfe81a01c,
+ 0xd4be832d,
+ 0xdab78e26,
+ 0xc8ac993b,
+ 0xc6a59430,
+ 0x9cd2df59,
+ 0x92dbd252,
+ 0x80c0c54f,
+ 0x8ec9c844,
+ 0xa4f6eb75,
+ 0xaaffe67e,
+ 0xb8e4f163,
+ 0xb6edfc68,
+ 0x0c0a67b1,
+ 0x02036aba,
+ 0x10187da7,
+ 0x1e1170ac,
+ 0x342e539d,
+ 0x3a275e96,
+ 0x283c498b,
+ 0x26354480,
+ 0x7c420fe9,
+ 0x724b02e2,
+ 0x605015ff,
+ 0x6e5918f4,
+ 0x44663bc5,
+ 0x4a6f36ce,
+ 0x587421d3,
+ 0x567d2cd8,
+ 0x37a10c7a,
+ 0x39a80171,
+ 0x2bb3166c,
+ 0x25ba1b67,
+ 0x0f853856,
+ 0x018c355d,
+ 0x13972240,
+ 0x1d9e2f4b,
+ 0x47e96422,
+ 0x49e06929,
+ 0x5bfb7e34,
+ 0x55f2733f,
+ 0x7fcd500e,
+ 0x71c45d05,
+ 0x63df4a18,
+ 0x6dd64713,
+ 0xd731dcca,
+ 0xd938d1c1,
+ 0xcb23c6dc,
+ 0xc52acbd7,
+ 0xef15e8e6,
+ 0xe11ce5ed,
+ 0xf307f2f0,
+ 0xfd0efffb,
+ 0xa779b492,
+ 0xa970b999,
+ 0xbb6bae84,
+ 0xb562a38f,
+ 0x9f5d80be,
+ 0x91548db5,
+ 0x834f9aa8,
+ 0x8d4697a3
+ ]);
+ function expandKey256(cipherKey) {
+ var b = 240, result = new Uint8Array(b);
+ var r = 1;
+ result.set(cipherKey);
+ for (var j = 32, i = 1; j < b; ++i) {
+ if (j % 32 === 16) {
+ t1 = s[t1];
+ t2 = s[t2];
+ t3 = s[t3];
+ t4 = s[t4];
+ } else if (j % 32 === 0) {
+ // RotWord
+ var t1 = result[j - 3], t2 = result[j - 2], t3 = result[j - 1], t4 = result[j - 4];
+ // SubWord
+ t1 = s[t1];
+ t2 = s[t2];
+ t3 = s[t3];
+ t4 = s[t4];
+ // Rcon
+ t1 = t1 ^ r;
+ if ((r <<= 1) >= 256) {
+ r = (r ^ 0x1b) & 0xFF;
+ }
+ }
+ for (var n = 0; n < 4; ++n) {
+ result[j] = t1 ^= result[j - 32];
+ j++;
+ result[j] = t2 ^= result[j - 32];
+ j++;
+ result[j] = t3 ^= result[j - 32];
+ j++;
+ result[j] = t4 ^= result[j - 32];
+ j++;
+ }
+ }
+ return result;
+ }
+ function decrypt256(input, key) {
+ var state = new Uint8Array(16);
+ state.set(input);
+ var i, j, k;
+ var t, u, v;
+ // AddRoundKey
+ for (j = 0, k = 224; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ for (i = 13; i >= 1; --i) {
+ // InvShiftRows
+ t = state[13];
+ state[13] = state[9];
+ state[9] = state[5];
+ state[5] = state[1];
+ state[1] = t;
+ t = state[14];
+ u = state[10];
+ state[14] = state[6];
+ state[10] = state[2];
+ state[6] = t;
+ state[2] = u;
+ t = state[15];
+ u = state[11];
+ v = state[7];
+ state[15] = state[3];
+ state[11] = t;
+ state[7] = u;
+ state[3] = v;
+ // InvSubBytes
+ for (j = 0; j < 16; ++j) {
+ state[j] = inv_s[state[j]];
+ }
+ // AddRoundKey
+ for (j = 0, k = i * 16; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ // InvMixColumns
+ for (j = 0; j < 16; j += 4) {
+ var s0 = mix[state[j]], s1 = mix[state[j + 1]], s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
+ t = s0 ^ s1 >>> 8 ^ s1 << 24 ^ s2 >>> 16 ^ s2 << 16 ^ s3 >>> 24 ^ s3 << 8;
+ state[j] = t >>> 24 & 0xFF;
+ state[j + 1] = t >> 16 & 0xFF;
+ state[j + 2] = t >> 8 & 0xFF;
+ state[j + 3] = t & 0xFF;
+ }
+ }
+ // InvShiftRows
+ t = state[13];
+ state[13] = state[9];
+ state[9] = state[5];
+ state[5] = state[1];
+ state[1] = t;
+ t = state[14];
+ u = state[10];
+ state[14] = state[6];
+ state[10] = state[2];
+ state[6] = t;
+ state[2] = u;
+ t = state[15];
+ u = state[11];
+ v = state[7];
+ state[15] = state[3];
+ state[11] = t;
+ state[7] = u;
+ state[3] = v;
+ for (j = 0; j < 16; ++j) {
+ // InvSubBytes
+ state[j] = inv_s[state[j]];
+ // AddRoundKey
+ state[j] ^= key[j];
+ }
+ return state;
+ }
+ function encrypt256(input, key) {
+ var t, u, v, k;
+ var state = new Uint8Array(16);
+ state.set(input);
+ for (j = 0; j < 16; ++j) {
+ // AddRoundKey
+ state[j] ^= key[j];
+ }
+ for (i = 1; i < 14; i++) {
+ //SubBytes
+ for (j = 0; j < 16; ++j) {
+ state[j] = s[state[j]];
+ }
+ //ShiftRows
+ v = state[1];
+ state[1] = state[5];
+ state[5] = state[9];
+ state[9] = state[13];
+ state[13] = v;
+ v = state[2];
+ u = state[6];
+ state[2] = state[10];
+ state[6] = state[14];
+ state[10] = v;
+ state[14] = u;
+ v = state[3];
+ u = state[7];
+ t = state[11];
+ state[3] = state[15];
+ state[7] = v;
+ state[11] = u;
+ state[15] = t;
+ //MixColumns
+ for (var j = 0; j < 16; j += 4) {
+ var s0 = state[j + 0], s1 = state[j + 1];
+ var s2 = state[j + 2], s3 = state[j + 3];
+ t = s0 ^ s1 ^ s2 ^ s3;
+ state[j + 0] ^= t ^ mixCol[s0 ^ s1];
+ state[j + 1] ^= t ^ mixCol[s1 ^ s2];
+ state[j + 2] ^= t ^ mixCol[s2 ^ s3];
+ state[j + 3] ^= t ^ mixCol[s3 ^ s0];
+ }
+ //AddRoundKey
+ for (j = 0, k = i * 16; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ }
+ //SubBytes
+ for (j = 0; j < 16; ++j) {
+ state[j] = s[state[j]];
+ }
+ //ShiftRows
+ v = state[1];
+ state[1] = state[5];
+ state[5] = state[9];
+ state[9] = state[13];
+ state[13] = v;
+ v = state[2];
+ u = state[6];
+ state[2] = state[10];
+ state[6] = state[14];
+ state[10] = v;
+ state[14] = u;
+ v = state[3];
+ u = state[7];
+ t = state[11];
+ state[3] = state[15];
+ state[7] = v;
+ state[11] = u;
+ state[15] = t;
+ //AddRoundKey
+ for (j = 0, k = 224; j < 16; ++j, ++k) {
+ state[j] ^= key[k];
+ }
+ return state;
+ }
+ function AES256Cipher(key) {
+ this.key = expandKey256(key);
+ this.buffer = new Uint8Array(16);
+ this.bufferPosition = 0;
+ }
+ function decryptBlock2(data, finalize) {
+ var i, j, ii, sourceLength = data.length, buffer = this.buffer, bufferLength = this.bufferPosition, result = [], iv = this.iv;
+ for (i = 0; i < sourceLength; ++i) {
+ buffer[bufferLength] = data[i];
+ ++bufferLength;
+ if (bufferLength < 16) {
+ continue;
+ }
+ // buffer is full, decrypting
+ var plain = decrypt256(buffer, this.key);
+ // xor-ing the IV vector to get plain text
+ for (j = 0; j < 16; ++j) {
+ plain[j] ^= iv[j];
+ }
+ iv = buffer;
+ result.push(plain);
+ buffer = new Uint8Array(16);
+ bufferLength = 0;
+ }
+ // saving incomplete buffer
+ this.buffer = buffer;
+ this.bufferLength = bufferLength;
+ this.iv = iv;
+ if (result.length === 0) {
+ return new Uint8Array([]);
+ }
+ // combining plain text blocks into one
+ var outputLength = 16 * result.length;
+ if (finalize) {
+ // undo a padding that is described in RFC 2898
+ var lastBlock = result[result.length - 1];
+ var psLen = lastBlock[15];
+ if (psLen <= 16) {
+ for (i = 15, ii = 16 - psLen; i >= ii; --i) {
+ if (lastBlock[i] !== psLen) {
+ // Invalid padding, assume that the block has no padding.
+ psLen = 0;
+ break;
+ }
+ }
+ outputLength -= psLen;
+ result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
+ }
+ }
+ var output = new Uint8Array(outputLength);
+ for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
+ output.set(result[i], j);
+ }
+ return output;
+ }
+ AES256Cipher.prototype = {
+ decryptBlock: function AES256Cipher_decryptBlock(data, finalize, iv) {
+ var i, sourceLength = data.length;
+ var buffer = this.buffer, bufferLength = this.bufferPosition;
+ // if not supplied an IV wait for IV values
+ // they are at the start of the stream
+ if (iv) {
+ this.iv = iv;
+ } else {
+ for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
+ buffer[bufferLength] = data[i];
+ }
+ if (bufferLength < 16) {
+ //need more data
+ this.bufferLength = bufferLength;
+ return new Uint8Array([]);
+ }
+ this.iv = buffer;
+ data = data.subarray(16);
+ }
+ this.buffer = new Uint8Array(16);
+ this.bufferLength = 0;
+ // starting decryption
+ this.decryptBlock = decryptBlock2;
+ return this.decryptBlock(data, finalize);
+ },
+ encrypt: function AES256Cipher_encrypt(data, iv) {
+ var i, j, ii, sourceLength = data.length, buffer = this.buffer, bufferLength = this.bufferPosition, result = [];
+ if (!iv) {
+ iv = new Uint8Array(16);
+ }
+ for (i = 0; i < sourceLength; ++i) {
+ buffer[bufferLength] = data[i];
+ ++bufferLength;
+ if (bufferLength < 16) {
+ continue;
+ }
+ for (j = 0; j < 16; ++j) {
+ buffer[j] ^= iv[j];
+ }
+ // buffer is full, encrypting
+ var cipher = encrypt256(buffer, this.key);
+ this.iv = cipher;
+ result.push(cipher);
+ buffer = new Uint8Array(16);
+ bufferLength = 0;
+ }
+ // saving incomplete buffer
+ this.buffer = buffer;
+ this.bufferLength = bufferLength;
+ this.iv = iv;
+ if (result.length === 0) {
+ return new Uint8Array([]);
+ }
+ // combining plain text blocks into one
+ var outputLength = 16 * result.length;
+ var output = new Uint8Array(outputLength);
+ for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
+ output.set(result[i], j);
+ }
+ return output;
+ }
+ };
+ return AES256Cipher;
+ }();
+ var PDF17 = function PDF17Closure() {
+ function compareByteArrays(array1, array2) {
+ if (array1.length !== array2.length) {
+ return false;
+ }
+ for (var i = 0; i < array1.length; i++) {
+ if (array1[i] !== array2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ function PDF17() {
+ }
+ PDF17.prototype = {
+ checkOwnerPassword: function PDF17_checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
+ var hashData = new Uint8Array(password.length + 56);
+ hashData.set(password, 0);
+ hashData.set(ownerValidationSalt, password.length);
+ hashData.set(userBytes, password.length + ownerValidationSalt.length);
+ var result = calculateSHA256(hashData, 0, hashData.length);
+ return compareByteArrays(result, ownerPassword);
+ },
+ checkUserPassword: function PDF17_checkUserPassword(password, userValidationSalt, userPassword) {
+ var hashData = new Uint8Array(password.length + 8);
+ hashData.set(password, 0);
+ hashData.set(userValidationSalt, password.length);
+ var result = calculateSHA256(hashData, 0, hashData.length);
+ return compareByteArrays(result, userPassword);
+ },
+ getOwnerKey: function PDF17_getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
+ var hashData = new Uint8Array(password.length + 56);
+ hashData.set(password, 0);
+ hashData.set(ownerKeySalt, password.length);
+ hashData.set(userBytes, password.length + ownerKeySalt.length);
+ var key = calculateSHA256(hashData, 0, hashData.length);
+ var cipher = new AES256Cipher(key);
+ return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
+ },
+ getUserKey: function PDF17_getUserKey(password, userKeySalt, userEncryption) {
+ var hashData = new Uint8Array(password.length + 8);
+ hashData.set(password, 0);
+ hashData.set(userKeySalt, password.length);
+ //key is the decryption key for the UE string
+ var key = calculateSHA256(hashData, 0, hashData.length);
+ var cipher = new AES256Cipher(key);
+ return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
+ }
+ };
+ return PDF17;
+ }();
+ var PDF20 = function PDF20Closure() {
+ function concatArrays(array1, array2) {
+ var t = new Uint8Array(array1.length + array2.length);
+ t.set(array1, 0);
+ t.set(array2, array1.length);
+ return t;
+ }
+ function calculatePDF20Hash(password, input, userBytes) {
+ //This refers to Algorithm 2.B as defined in ISO 32000-2
+ var k = calculateSHA256(input, 0, input.length).subarray(0, 32);
+ var e = [0];
+ var i = 0;
+ while (i < 64 || e[e.length - 1] > i - 32) {
+ var arrayLength = password.length + k.length + userBytes.length;
+ var k1 = new Uint8Array(arrayLength * 64);
+ var array = concatArrays(password, k);
+ array = concatArrays(array, userBytes);
+ for (var j = 0, pos = 0; j < 64; j++, pos += arrayLength) {
+ k1.set(array, pos);
+ }
+ //AES128 CBC NO PADDING with
+ //first 16 bytes of k as the key and the second 16 as the iv.
+ var cipher = new AES128Cipher(k.subarray(0, 16));
+ e = cipher.encrypt(k1, k.subarray(16, 32));
+ //Now we have to take the first 16 bytes of an unsigned
+ //big endian integer... and compute the remainder
+ //modulo 3.... That is a fairly large number and
+ //JavaScript isn't going to handle that well...
+ //So we're using a trick that allows us to perform
+ //modulo math byte by byte
+ var remainder = 0;
+ for (var z = 0; z < 16; z++) {
+ remainder *= 256 % 3;
+ remainder %= 3;
+ remainder += (e[z] >>> 0) % 3;
+ remainder %= 3;
+ }
+ if (remainder === 0) {
+ k = calculateSHA256(e, 0, e.length);
+ } else if (remainder === 1) {
+ k = calculateSHA384(e, 0, e.length);
+ } else if (remainder === 2) {
+ k = calculateSHA512(e, 0, e.length);
+ }
+ i++;
+ }
+ return k.subarray(0, 32);
+ }
+ function PDF20() {
+ }
+ function compareByteArrays(array1, array2) {
+ if (array1.length !== array2.length) {
+ return false;
+ }
+ for (var i = 0; i < array1.length; i++) {
+ if (array1[i] !== array2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ PDF20.prototype = {
+ hash: function PDF20_hash(password, concatBytes, userBytes) {
+ return calculatePDF20Hash(password, concatBytes, userBytes);
+ },
+ checkOwnerPassword: function PDF20_checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
+ var hashData = new Uint8Array(password.length + 56);
+ hashData.set(password, 0);
+ hashData.set(ownerValidationSalt, password.length);
+ hashData.set(userBytes, password.length + ownerValidationSalt.length);
+ var result = calculatePDF20Hash(password, hashData, userBytes);
+ return compareByteArrays(result, ownerPassword);
+ },
+ checkUserPassword: function PDF20_checkUserPassword(password, userValidationSalt, userPassword) {
+ var hashData = new Uint8Array(password.length + 8);
+ hashData.set(password, 0);
+ hashData.set(userValidationSalt, password.length);
+ var result = calculatePDF20Hash(password, hashData, []);
+ return compareByteArrays(result, userPassword);
+ },
+ getOwnerKey: function PDF20_getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
+ var hashData = new Uint8Array(password.length + 56);
+ hashData.set(password, 0);
+ hashData.set(ownerKeySalt, password.length);
+ hashData.set(userBytes, password.length + ownerKeySalt.length);
+ var key = calculatePDF20Hash(password, hashData, userBytes);
+ var cipher = new AES256Cipher(key);
+ return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
+ },
+ getUserKey: function PDF20_getUserKey(password, userKeySalt, userEncryption) {
+ var hashData = new Uint8Array(password.length + 8);
+ hashData.set(password, 0);
+ hashData.set(userKeySalt, password.length);
+ //key is the decryption key for the UE string
+ var key = calculatePDF20Hash(password, hashData, []);
+ var cipher = new AES256Cipher(key);
+ return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
+ }
+ };
+ return PDF20;
+ }();
+ var CipherTransform = function CipherTransformClosure() {
+ function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
+ this.stringCipherConstructor = stringCipherConstructor;
+ this.streamCipherConstructor = streamCipherConstructor;
+ }
+ CipherTransform.prototype = {
+ createStream: function CipherTransform_createStream(stream, length) {
+ var cipher = new this.streamCipherConstructor();
+ return new DecryptStream(stream, length, function cipherTransformDecryptStream(data, finalize) {
+ return cipher.decryptBlock(data, finalize);
+ });
+ },
+ decryptString: function CipherTransform_decryptString(s) {
+ var cipher = new this.stringCipherConstructor();
+ var data = stringToBytes(s);
+ data = cipher.decryptBlock(data, true);
+ return bytesToString(data);
+ }
+ };
+ return CipherTransform;
+ }();
+ var CipherTransformFactory = function CipherTransformFactoryClosure() {
+ var defaultPasswordBytes = new Uint8Array([
+ 0x28,
+ 0xBF,
+ 0x4E,
+ 0x5E,
+ 0x4E,
+ 0x75,
+ 0x8A,
+ 0x41,
+ 0x64,
+ 0x00,
+ 0x4E,
+ 0x56,
+ 0xFF,
+ 0xFA,
+ 0x01,
+ 0x08,
+ 0x2E,
+ 0x2E,
+ 0x00,
+ 0xB6,
+ 0xD0,
+ 0x68,
+ 0x3E,
+ 0x80,
+ 0x2F,
+ 0x0C,
+ 0xA9,
+ 0xFE,
+ 0x64,
+ 0x53,
+ 0x69,
+ 0x7A
+ ]);
+ function createEncryptionKey20(revision, password, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms) {
+ if (password) {
+ var passwordLength = Math.min(127, password.length);
+ password = password.subarray(0, passwordLength);
+ } else {
+ password = [];
+ }
+ var pdfAlgorithm;
+ if (revision === 6) {
+ pdfAlgorithm = new PDF20();
+ } else {
+ pdfAlgorithm = new PDF17();
+ }
+ if (pdfAlgorithm.checkUserPassword(password, userValidationSalt, userPassword)) {
+ return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption);
+ } else if (password.length && pdfAlgorithm.checkOwnerPassword(password, ownerValidationSalt, uBytes, ownerPassword)) {
+ return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes, ownerEncryption);
+ }
+ return null;
+ }
+ function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata) {
+ var hashDataSize = 40 + ownerPassword.length + fileId.length;
+ var hashData = new Uint8Array(hashDataSize), i = 0, j, n;
+ if (password) {
+ n = Math.min(32, password.length);
+ for (; i < n; ++i) {
+ hashData[i] = password[i];
+ }
+ }
+ j = 0;
+ while (i < 32) {
+ hashData[i++] = defaultPasswordBytes[j++];
+ }
+ // as now the padded password in the hashData[0..i]
+ for (j = 0, n = ownerPassword.length; j < n; ++j) {
+ hashData[i++] = ownerPassword[j];
+ }
+ hashData[i++] = flags & 0xFF;
+ hashData[i++] = flags >> 8 & 0xFF;
+ hashData[i++] = flags >> 16 & 0xFF;
+ hashData[i++] = flags >>> 24 & 0xFF;
+ for (j = 0, n = fileId.length; j < n; ++j) {
+ hashData[i++] = fileId[j];
+ }
+ if (revision >= 4 && !encryptMetadata) {
+ hashData[i++] = 0xFF;
+ hashData[i++] = 0xFF;
+ hashData[i++] = 0xFF;
+ hashData[i++] = 0xFF;
+ }
+ var hash = calculateMD5(hashData, 0, i);
+ var keyLengthInBytes = keyLength >> 3;
+ if (revision >= 3) {
+ for (j = 0; j < 50; ++j) {
+ hash = calculateMD5(hash, 0, keyLengthInBytes);
+ }
+ }
+ var encryptionKey = hash.subarray(0, keyLengthInBytes);
+ var cipher, checkData;
+ if (revision >= 3) {
+ for (i = 0; i < 32; ++i) {
+ hashData[i] = defaultPasswordBytes[i];
+ }
+ for (j = 0, n = fileId.length; j < n; ++j) {
+ hashData[i++] = fileId[j];
+ }
+ cipher = new ARCFourCipher(encryptionKey);
+ checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
+ n = encryptionKey.length;
+ var derivedKey = new Uint8Array(n), k;
+ for (j = 1; j <= 19; ++j) {
+ for (k = 0; k < n; ++k) {
+ derivedKey[k] = encryptionKey[k] ^ j;
+ }
+ cipher = new ARCFourCipher(derivedKey);
+ checkData = cipher.encryptBlock(checkData);
+ }
+ for (j = 0, n = checkData.length; j < n; ++j) {
+ if (userPassword[j] !== checkData[j]) {
+ return null;
+ }
+ }
+ } else {
+ cipher = new ARCFourCipher(encryptionKey);
+ checkData = cipher.encryptBlock(defaultPasswordBytes);
+ for (j = 0, n = checkData.length; j < n; ++j) {
+ if (userPassword[j] !== checkData[j]) {
+ return null;
+ }
+ }
+ }
+ return encryptionKey;
+ }
+ function decodeUserPassword(password, ownerPassword, revision, keyLength) {
+ var hashData = new Uint8Array(32), i = 0, j, n;
+ n = Math.min(32, password.length);
+ for (; i < n; ++i) {
+ hashData[i] = password[i];
+ }
+ j = 0;
+ while (i < 32) {
+ hashData[i++] = defaultPasswordBytes[j++];
+ }
+ var hash = calculateMD5(hashData, 0, i);
+ var keyLengthInBytes = keyLength >> 3;
+ if (revision >= 3) {
+ for (j = 0; j < 50; ++j) {
+ hash = calculateMD5(hash, 0, hash.length);
+ }
+ }
+ var cipher, userPassword;
+ if (revision >= 3) {
+ userPassword = ownerPassword;
+ var derivedKey = new Uint8Array(keyLengthInBytes), k;
+ for (j = 19; j >= 0; j--) {
+ for (k = 0; k < keyLengthInBytes; ++k) {
+ derivedKey[k] = hash[k] ^ j;
+ }
+ cipher = new ARCFourCipher(derivedKey);
+ userPassword = cipher.encryptBlock(userPassword);
+ }
+ } else {
+ cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
+ userPassword = cipher.encryptBlock(ownerPassword);
+ }
+ return userPassword;
+ }
+ var identityName = Name.get('Identity');
+ function CipherTransformFactory(dict, fileId, password) {
+ var filter = dict.get('Filter');
+ if (!isName(filter, 'Standard')) {
+ error('unknown encryption method');
+ }
+ this.dict = dict;
+ var algorithm = dict.get('V');
+ if (!isInt(algorithm) || algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5) {
+ error('unsupported encryption algorithm');
+ }
+ this.algorithm = algorithm;
+ var keyLength = dict.get('Length');
+ if (!keyLength) {
+ // Spec asks to rely on encryption dictionary's Length entry, however
+ // some PDFs don't have it. Trying to recover.
+ if (algorithm <= 3) {
+ // For 1 and 2 it's fixed to 40-bit, for 3 40-bit is a minimal value.
+ keyLength = 40;
+ } else {
+ // Trying to find default handler -- it usually has Length.
+ var cfDict = dict.get('CF');
+ var streamCryptoName = dict.get('StmF');
+ if (isDict(cfDict) && isName(streamCryptoName)) {
+ cfDict.suppressEncryption = true;
+ // See comment below.
+ var handlerDict = cfDict.get(streamCryptoName.name);
+ keyLength = handlerDict && handlerDict.get('Length') || 128;
+ if (keyLength < 40) {
+ // Sometimes it's incorrect value of bits, generators specify bytes.
+ keyLength <<= 3;
+ }
+ }
+ }
+ }
+ if (!isInt(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
+ error('invalid key length');
+ }
+ // prepare keys
+ var ownerPassword = stringToBytes(dict.get('O')).subarray(0, 32);
+ var userPassword = stringToBytes(dict.get('U')).subarray(0, 32);
+ var flags = dict.get('P');
+ var revision = dict.get('R');
+ // meaningful when V is 4 or 5
+ var encryptMetadata = (algorithm === 4 || algorithm === 5) && dict.get('EncryptMetadata') !== false;
+ this.encryptMetadata = encryptMetadata;
+ var fileIdBytes = stringToBytes(fileId);
+ var passwordBytes;
+ if (password) {
+ if (revision === 6) {
+ try {
+ password = utf8StringToString(password);
+ } catch (ex) {
+ warn('CipherTransformFactory: ' + 'Unable to convert UTF8 encoded password.');
+ }
+ }
+ passwordBytes = stringToBytes(password);
+ }
+ var encryptionKey;
+ if (algorithm !== 5) {
+ encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
+ } else {
+ var ownerValidationSalt = stringToBytes(dict.get('O')).subarray(32, 40);
+ var ownerKeySalt = stringToBytes(dict.get('O')).subarray(40, 48);
+ var uBytes = stringToBytes(dict.get('U')).subarray(0, 48);
+ var userValidationSalt = stringToBytes(dict.get('U')).subarray(32, 40);
+ var userKeySalt = stringToBytes(dict.get('U')).subarray(40, 48);
+ var ownerEncryption = stringToBytes(dict.get('OE'));
+ var userEncryption = stringToBytes(dict.get('UE'));
+ var perms = stringToBytes(dict.get('Perms'));
+ encryptionKey = createEncryptionKey20(revision, passwordBytes, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms);
+ }
+ if (!encryptionKey && !password) {
+ throw new PasswordException('No password given', PasswordResponses.NEED_PASSWORD);
+ } else if (!encryptionKey && password) {
+ // Attempting use the password as an owner password
+ var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength);
+ encryptionKey = prepareKeyData(fileIdBytes, decodedPassword, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
+ }
+ if (!encryptionKey) {
+ throw new PasswordException('Incorrect Password', PasswordResponses.INCORRECT_PASSWORD);
+ }
+ this.encryptionKey = encryptionKey;
+ if (algorithm >= 4) {
+ var cf = dict.get('CF');
+ if (isDict(cf)) {
+ // The 'CF' dictionary itself should not be encrypted, and by setting
+ // `suppressEncryption` we can prevent an infinite loop inside of
+ // `XRef_fetchUncompressed` if the dictionary contains indirect objects
+ // (fixes issue7665.pdf).
+ cf.suppressEncryption = true;
+ }
+ this.cf = cf;
+ this.stmf = dict.get('StmF') || identityName;
+ this.strf = dict.get('StrF') || identityName;
+ this.eff = dict.get('EFF') || this.stmf;
+ }
+ }
+ function buildObjectKey(num, gen, encryptionKey, isAes) {
+ var key = new Uint8Array(encryptionKey.length + 9), i, n;
+ for (i = 0, n = encryptionKey.length; i < n; ++i) {
+ key[i] = encryptionKey[i];
+ }
+ key[i++] = num & 0xFF;
+ key[i++] = num >> 8 & 0xFF;
+ key[i++] = num >> 16 & 0xFF;
+ key[i++] = gen & 0xFF;
+ key[i++] = gen >> 8 & 0xFF;
+ if (isAes) {
+ key[i++] = 0x73;
+ key[i++] = 0x41;
+ key[i++] = 0x6C;
+ key[i++] = 0x54;
+ }
+ var hash = calculateMD5(key, 0, i);
+ return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
+ }
+ function buildCipherConstructor(cf, name, num, gen, key) {
+ assert(isName(name), 'Invalid crypt filter name.');
+ var cryptFilter = cf.get(name.name);
+ var cfm;
+ if (cryptFilter !== null && cryptFilter !== undefined) {
+ cfm = cryptFilter.get('CFM');
+ }
+ if (!cfm || cfm.name === 'None') {
+ return function cipherTransformFactoryBuildCipherConstructorNone() {
+ return new NullCipher();
+ };
+ }
+ if ('V2' === cfm.name) {
+ return function cipherTransformFactoryBuildCipherConstructorV2() {
+ return new ARCFourCipher(buildObjectKey(num, gen, key, false));
+ };
+ }
+ if ('AESV2' === cfm.name) {
+ return function cipherTransformFactoryBuildCipherConstructorAESV2() {
+ return new AES128Cipher(buildObjectKey(num, gen, key, true));
+ };
+ }
+ if ('AESV3' === cfm.name) {
+ return function cipherTransformFactoryBuildCipherConstructorAESV3() {
+ return new AES256Cipher(key);
+ };
+ }
+ error('Unknown crypto method');
+ }
+ CipherTransformFactory.prototype = {
+ createCipherTransform: function CipherTransformFactory_createCipherTransform(num, gen) {
+ if (this.algorithm === 4 || this.algorithm === 5) {
+ return new CipherTransform(buildCipherConstructor(this.cf, this.stmf, num, gen, this.encryptionKey), buildCipherConstructor(this.cf, this.strf, num, gen, this.encryptionKey));
+ }
+ // algorithms 1 and 2
+ var key = buildObjectKey(num, gen, this.encryptionKey, false);
+ var cipherConstructor = function buildCipherCipherConstructor() {
+ return new ARCFourCipher(key);
+ };
+ return new CipherTransform(cipherConstructor, cipherConstructor);
+ }
+ };
+ return CipherTransformFactory;
+ }();
+ exports.AES128Cipher = AES128Cipher;
+ exports.AES256Cipher = AES256Cipher;
+ exports.ARCFourCipher = ARCFourCipher;
+ exports.CipherTransformFactory = CipherTransformFactory;
+ exports.PDF17 = PDF17;
+ exports.PDF20 = PDF20;
+ exports.calculateMD5 = calculateMD5;
+ exports.calculateSHA256 = calculateSHA256;
+ exports.calculateSHA384 = calculateSHA384;
+ exports.calculateSHA512 = calculateSHA512;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreFontRenderer = {}, root.pdfjsSharedUtil, root.pdfjsCoreStream, root.pdfjsCoreGlyphList, root.pdfjsCoreEncodings, root.pdfjsCoreCFFParser);
+ }(this, function (exports, sharedUtil, coreStream, coreGlyphList, coreEncodings, coreCFFParser) {
+ var Util = sharedUtil.Util;
+ var bytesToString = sharedUtil.bytesToString;
+ var error = sharedUtil.error;
+ var Stream = coreStream.Stream;
+ var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
+ var StandardEncoding = coreEncodings.StandardEncoding;
+ var CFFParser = coreCFFParser.CFFParser;
+ var FontRendererFactory = function FontRendererFactoryClosure() {
+ function getLong(data, offset) {
+ return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
+ }
+ function getUshort(data, offset) {
+ return data[offset] << 8 | data[offset + 1];
+ }
+ function parseCmap(data, start, end) {
+ var offset = getUshort(data, start + 2) === 1 ? getLong(data, start + 8) : getLong(data, start + 16);
+ var format = getUshort(data, start + offset);
+ var length, ranges, p, i;
+ if (format === 4) {
+ length = getUshort(data, start + offset + 2);
+ var segCount = getUshort(data, start + offset + 6) >> 1;
+ p = start + offset + 14;
+ ranges = [];
+ for (i = 0; i < segCount; i++, p += 2) {
+ ranges[i] = { end: getUshort(data, p) };
+ }
+ p += 2;
+ for (i = 0; i < segCount; i++, p += 2) {
+ ranges[i].start = getUshort(data, p);
+ }
+ for (i = 0; i < segCount; i++, p += 2) {
+ ranges[i].idDelta = getUshort(data, p);
+ }
+ for (i = 0; i < segCount; i++, p += 2) {
+ var idOffset = getUshort(data, p);
+ if (idOffset === 0) {
+ continue;
+ }
+ ranges[i].ids = [];
+ for (var j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) {
+ ranges[i].ids[j] = getUshort(data, p + idOffset);
+ idOffset += 2;
+ }
+ }
+ return ranges;
+ } else if (format === 12) {
+ length = getLong(data, start + offset + 4);
+ var groups = getLong(data, start + offset + 12);
+ p = start + offset + 16;
+ ranges = [];
+ for (i = 0; i < groups; i++) {
+ ranges.push({
+ start: getLong(data, p),
+ end: getLong(data, p + 4),
+ idDelta: getLong(data, p + 8) - getLong(data, p)
+ });
+ p += 12;
+ }
+ return ranges;
+ }
+ error('not supported cmap: ' + format);
+ }
+ function parseCff(data, start, end, seacAnalysisEnabled) {
+ var properties = {};
+ var parser = new CFFParser(new Stream(data, start, end - start), properties, seacAnalysisEnabled);
+ var cff = parser.parse();
+ return {
+ glyphs: cff.charStrings.objects,
+ subrs: cff.topDict.privateDict && cff.topDict.privateDict.subrsIndex && cff.topDict.privateDict.subrsIndex.objects,
+ gsubrs: cff.globalSubrIndex && cff.globalSubrIndex.objects
+ };
+ }
+ function parseGlyfTable(glyf, loca, isGlyphLocationsLong) {
+ var itemSize, itemDecode;
+ if (isGlyphLocationsLong) {
+ itemSize = 4;
+ itemDecode = function fontItemDecodeLong(data, offset) {
+ return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
+ };
+ } else {
+ itemSize = 2;
+ itemDecode = function fontItemDecode(data, offset) {
+ return data[offset] << 9 | data[offset + 1] << 1;
+ };
+ }
+ var glyphs = [];
+ var startOffset = itemDecode(loca, 0);
+ for (var j = itemSize; j < loca.length; j += itemSize) {
+ var endOffset = itemDecode(loca, j);
+ glyphs.push(glyf.subarray(startOffset, endOffset));
+ startOffset = endOffset;
+ }
+ return glyphs;
+ }
+ function lookupCmap(ranges, unicode) {
+ var code = unicode.charCodeAt(0), gid = 0;
+ var l = 0, r = ranges.length - 1;
+ while (l < r) {
+ var c = l + r + 1 >> 1;
+ if (code < ranges[c].start) {
+ r = c - 1;
+ } else {
+ l = c;
+ }
+ }
+ if (ranges[l].start <= code && code <= ranges[l].end) {
+ gid = ranges[l].idDelta + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code) & 0xFFFF;
+ }
+ return {
+ charCode: code,
+ glyphId: gid
+ };
+ }
+ function compileGlyf(code, cmds, font) {
+ function moveTo(x, y) {
+ cmds.push({
+ cmd: 'moveTo',
+ args: [
+ x,
+ y
+ ]
+ });
+ }
+ function lineTo(x, y) {
+ cmds.push({
+ cmd: 'lineTo',
+ args: [
+ x,
+ y
+ ]
+ });
+ }
+ function quadraticCurveTo(xa, ya, x, y) {
+ cmds.push({
+ cmd: 'quadraticCurveTo',
+ args: [
+ xa,
+ ya,
+ x,
+ y
+ ]
+ });
+ }
+ var i = 0;
+ var numberOfContours = (code[i] << 24 | code[i + 1] << 16) >> 16;
+ var flags;
+ var x = 0, y = 0;
+ i += 10;
+ if (numberOfContours < 0) {
+ // composite glyph
+ do {
+ flags = code[i] << 8 | code[i + 1];
+ var glyphIndex = code[i + 2] << 8 | code[i + 3];
+ i += 4;
+ var arg1, arg2;
+ if (flags & 0x01) {
+ arg1 = (code[i] << 24 | code[i + 1] << 16) >> 16;
+ arg2 = (code[i + 2] << 24 | code[i + 3] << 16) >> 16;
+ i += 4;
+ } else {
+ arg1 = code[i++];
+ arg2 = code[i++];
+ }
+ if (flags & 0x02) {
+ x = arg1;
+ y = arg2;
+ } else {
+ x = 0;
+ y = 0;
+ }
+ // TODO "they are points" ?
+ var scaleX = 1, scaleY = 1, scale01 = 0, scale10 = 0;
+ if (flags & 0x08) {
+ scaleX = scaleY = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
+ i += 2;
+ } else if (flags & 0x40) {
+ scaleX = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
+ scaleY = (code[i + 2] << 24 | code[i + 3] << 16) / 1073741824;
+ i += 4;
+ } else if (flags & 0x80) {
+ scaleX = (code[i] << 24 | code[i + 1] << 16) / 1073741824;
+ scale01 = (code[i + 2] << 24 | code[i + 3] << 16) / 1073741824;
+ scale10 = (code[i + 4] << 24 | code[i + 5] << 16) / 1073741824;
+ scaleY = (code[i + 6] << 24 | code[i + 7] << 16) / 1073741824;
+ i += 8;
+ }
+ var subglyph = font.glyphs[glyphIndex];
+ if (subglyph) {
+ cmds.push({ cmd: 'save' });
+ cmds.push({
+ cmd: 'transform',
+ args: [
+ scaleX,
+ scale01,
+ scale10,
+ scaleY,
+ x,
+ y
+ ]
+ });
+ compileGlyf(subglyph, cmds, font);
+ cmds.push({ cmd: 'restore' });
+ }
+ } while (flags & 0x20);
+ } else {
+ // simple glyph
+ var endPtsOfContours = [];
+ var j, jj;
+ for (j = 0; j < numberOfContours; j++) {
+ endPtsOfContours.push(code[i] << 8 | code[i + 1]);
+ i += 2;
+ }
+ var instructionLength = code[i] << 8 | code[i + 1];
+ i += 2 + instructionLength;
+ // skipping the instructions
+ var numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1;
+ var points = [];
+ while (points.length < numberOfPoints) {
+ flags = code[i++];
+ var repeat = 1;
+ if (flags & 0x08) {
+ repeat += code[i++];
+ }
+ while (repeat-- > 0) {
+ points.push({ flags: flags });
+ }
+ }
+ for (j = 0; j < numberOfPoints; j++) {
+ switch (points[j].flags & 0x12) {
+ case 0x00:
+ x += (code[i] << 24 | code[i + 1] << 16) >> 16;
+ i += 2;
+ break;
+ case 0x02:
+ x -= code[i++];
+ break;
+ case 0x12:
+ x += code[i++];
+ break;
+ }
+ points[j].x = x;
+ }
+ for (j = 0; j < numberOfPoints; j++) {
+ switch (points[j].flags & 0x24) {
+ case 0x00:
+ y += (code[i] << 24 | code[i + 1] << 16) >> 16;
+ i += 2;
+ break;
+ case 0x04:
+ y -= code[i++];
+ break;
+ case 0x24:
+ y += code[i++];
+ break;
+ }
+ points[j].y = y;
+ }
+ var startPoint = 0;
+ for (i = 0; i < numberOfContours; i++) {
+ var endPoint = endPtsOfContours[i];
+ // contours might have implicit points, which is located in the middle
+ // between two neighboring off-curve points
+ var contour = points.slice(startPoint, endPoint + 1);
+ if (contour[0].flags & 1) {
+ contour.push(contour[0]);
+ } else // using start point at the contour end
+ if (contour[contour.length - 1].flags & 1) {
+ // first is off-curve point, trying to use one from the end
+ contour.unshift(contour[contour.length - 1]);
+ } else {
+ // start and end are off-curve points, creating implicit one
+ var p = {
+ flags: 1,
+ x: (contour[0].x + contour[contour.length - 1].x) / 2,
+ y: (contour[0].y + contour[contour.length - 1].y) / 2
+ };
+ contour.unshift(p);
+ contour.push(p);
+ }
+ moveTo(contour[0].x, contour[0].y);
+ for (j = 1, jj = contour.length; j < jj; j++) {
+ if (contour[j].flags & 1) {
+ lineTo(contour[j].x, contour[j].y);
+ } else if (contour[j + 1].flags & 1) {
+ quadraticCurveTo(contour[j].x, contour[j].y, contour[j + 1].x, contour[j + 1].y);
+ j++;
+ } else {
+ quadraticCurveTo(contour[j].x, contour[j].y, (contour[j].x + contour[j + 1].x) / 2, (contour[j].y + contour[j + 1].y) / 2);
+ }
+ }
+ startPoint = endPoint + 1;
+ }
+ }
+ }
+ function compileCharString(code, cmds, font) {
+ var stack = [];
+ var x = 0, y = 0;
+ var stems = 0;
+ function moveTo(x, y) {
+ cmds.push({
+ cmd: 'moveTo',
+ args: [
+ x,
+ y
+ ]
+ });
+ }
+ function lineTo(x, y) {
+ cmds.push({
+ cmd: 'lineTo',
+ args: [
+ x,
+ y
+ ]
+ });
+ }
+ function bezierCurveTo(x1, y1, x2, y2, x, y) {
+ cmds.push({
+ cmd: 'bezierCurveTo',
+ args: [
+ x1,
+ y1,
+ x2,
+ y2,
+ x,
+ y
+ ]
+ });
+ }
+ function parse(code) {
+ var i = 0;
+ while (i < code.length) {
+ var stackClean = false;
+ var v = code[i++];
+ var xa, xb, ya, yb, y1, y2, y3, n, subrCode;
+ switch (v) {
+ case 1:
+ // hstem
+ stems += stack.length >> 1;
+ stackClean = true;
+ break;
+ case 3:
+ // vstem
+ stems += stack.length >> 1;
+ stackClean = true;
+ break;
+ case 4:
+ // vmoveto
+ y += stack.pop();
+ moveTo(x, y);
+ stackClean = true;
+ break;
+ case 5:
+ // rlineto
+ while (stack.length > 0) {
+ x += stack.shift();
+ y += stack.shift();
+ lineTo(x, y);
+ }
+ break;
+ case 6:
+ // hlineto
+ while (stack.length > 0) {
+ x += stack.shift();
+ lineTo(x, y);
+ if (stack.length === 0) {
+ break;
+ }
+ y += stack.shift();
+ lineTo(x, y);
+ }
+ break;
+ case 7:
+ // vlineto
+ while (stack.length > 0) {
+ y += stack.shift();
+ lineTo(x, y);
+ if (stack.length === 0) {
+ break;
+ }
+ x += stack.shift();
+ lineTo(x, y);
+ }
+ break;
+ case 8:
+ // rrcurveto
+ while (stack.length > 0) {
+ xa = x + stack.shift();
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + stack.shift();
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ }
+ break;
+ case 10:
+ // callsubr
+ n = stack.pop() + font.subrsBias;
+ subrCode = font.subrs[n];
+ if (subrCode) {
+ parse(subrCode);
+ }
+ break;
+ case 11:
+ // return
+ return;
+ case 12:
+ v = code[i++];
+ switch (v) {
+ case 34:
+ // flex
+ xa = x + stack.shift();
+ xb = xa + stack.shift();
+ y1 = y + stack.shift();
+ x = xb + stack.shift();
+ bezierCurveTo(xa, y, xb, y1, x, y1);
+ xa = x + stack.shift();
+ xb = xa + stack.shift();
+ x = xb + stack.shift();
+ bezierCurveTo(xa, y1, xb, y, x, y);
+ break;
+ case 35:
+ // flex
+ xa = x + stack.shift();
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + stack.shift();
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ xa = x + stack.shift();
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + stack.shift();
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ stack.pop();
+ // fd
+ break;
+ case 36:
+ // hflex1
+ xa = x + stack.shift();
+ y1 = y + stack.shift();
+ xb = xa + stack.shift();
+ y2 = y1 + stack.shift();
+ x = xb + stack.shift();
+ bezierCurveTo(xa, y1, xb, y2, x, y2);
+ xa = x + stack.shift();
+ xb = xa + stack.shift();
+ y3 = y2 + stack.shift();
+ x = xb + stack.shift();
+ bezierCurveTo(xa, y2, xb, y3, x, y);
+ break;
+ case 37:
+ // flex1
+ var x0 = x, y0 = y;
+ xa = x + stack.shift();
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + stack.shift();
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ xa = x + stack.shift();
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb;
+ y = yb;
+ if (Math.abs(x - x0) > Math.abs(y - y0)) {
+ x += stack.shift();
+ } else {
+ y += stack.shift();
+ }
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ break;
+ default:
+ error('unknown operator: 12 ' + v);
+ }
+ break;
+ case 14:
+ // endchar
+ if (stack.length >= 4) {
+ var achar = stack.pop();
+ var bchar = stack.pop();
+ y = stack.pop();
+ x = stack.pop();
+ cmds.push({ cmd: 'save' });
+ cmds.push({
+ cmd: 'translate',
+ args: [
+ x,
+ y
+ ]
+ });
+ var cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]));
+ compileCharString(font.glyphs[cmap.glyphId], cmds, font);
+ cmds.push({ cmd: 'restore' });
+ cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]]));
+ compileCharString(font.glyphs[cmap.glyphId], cmds, font);
+ }
+ return;
+ case 18:
+ // hstemhm
+ stems += stack.length >> 1;
+ stackClean = true;
+ break;
+ case 19:
+ // hintmask
+ stems += stack.length >> 1;
+ i += stems + 7 >> 3;
+ stackClean = true;
+ break;
+ case 20:
+ // cntrmask
+ stems += stack.length >> 1;
+ i += stems + 7 >> 3;
+ stackClean = true;
+ break;
+ case 21:
+ // rmoveto
+ y += stack.pop();
+ x += stack.pop();
+ moveTo(x, y);
+ stackClean = true;
+ break;
+ case 22:
+ // hmoveto
+ x += stack.pop();
+ moveTo(x, y);
+ stackClean = true;
+ break;
+ case 23:
+ // vstemhm
+ stems += stack.length >> 1;
+ stackClean = true;
+ break;
+ case 24:
+ // rcurveline
+ while (stack.length > 2) {
+ xa = x + stack.shift();
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + stack.shift();
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ }
+ x += stack.shift();
+ y += stack.shift();
+ lineTo(x, y);
+ break;
+ case 25:
+ // rlinecurve
+ while (stack.length > 6) {
+ x += stack.shift();
+ y += stack.shift();
+ lineTo(x, y);
+ }
+ xa = x + stack.shift();
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + stack.shift();
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ break;
+ case 26:
+ // vvcurveto
+ if (stack.length % 2) {
+ x += stack.shift();
+ }
+ while (stack.length > 0) {
+ xa = x;
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb;
+ y = yb + stack.shift();
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ }
+ break;
+ case 27:
+ // hhcurveto
+ if (stack.length % 2) {
+ y += stack.shift();
+ }
+ while (stack.length > 0) {
+ xa = x + stack.shift();
+ ya = y;
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb;
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ }
+ break;
+ case 28:
+ stack.push((code[i] << 24 | code[i + 1] << 16) >> 16);
+ i += 2;
+ break;
+ case 29:
+ // callgsubr
+ n = stack.pop() + font.gsubrsBias;
+ subrCode = font.gsubrs[n];
+ if (subrCode) {
+ parse(subrCode);
+ }
+ break;
+ case 30:
+ // vhcurveto
+ while (stack.length > 0) {
+ xa = x;
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + (stack.length === 1 ? stack.shift() : 0);
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ if (stack.length === 0) {
+ break;
+ }
+ xa = x + stack.shift();
+ ya = y;
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ y = yb + stack.shift();
+ x = xb + (stack.length === 1 ? stack.shift() : 0);
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ }
+ break;
+ case 31:
+ // hvcurveto
+ while (stack.length > 0) {
+ xa = x + stack.shift();
+ ya = y;
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ y = yb + stack.shift();
+ x = xb + (stack.length === 1 ? stack.shift() : 0);
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ if (stack.length === 0) {
+ break;
+ }
+ xa = x;
+ ya = y + stack.shift();
+ xb = xa + stack.shift();
+ yb = ya + stack.shift();
+ x = xb + stack.shift();
+ y = yb + (stack.length === 1 ? stack.shift() : 0);
+ bezierCurveTo(xa, ya, xb, yb, x, y);
+ }
+ break;
+ default:
+ if (v < 32) {
+ error('unknown operator: ' + v);
+ }
+ if (v < 247) {
+ stack.push(v - 139);
+ } else if (v < 251) {
+ stack.push((v - 247) * 256 + code[i++] + 108);
+ } else if (v < 255) {
+ stack.push(-(v - 251) * 256 - code[i++] - 108);
+ } else {
+ stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536);
+ i += 4;
+ }
+ break;
+ }
+ if (stackClean) {
+ stack.length = 0;
+ }
+ }
+ }
+ parse(code);
+ }
+ var noop = '';
+ function CompiledFont(fontMatrix) {
+ this.compiledGlyphs = Object.create(null);
+ this.compiledCharCodeToGlyphId = Object.create(null);
+ this.fontMatrix = fontMatrix;
+ }
+ CompiledFont.prototype = {
+ getPathJs: function (unicode) {
+ var cmap = lookupCmap(this.cmap, unicode);
+ var fn = this.compiledGlyphs[cmap.glyphId];
+ if (!fn) {
+ fn = this.compileGlyph(this.glyphs[cmap.glyphId]);
+ this.compiledGlyphs[cmap.glyphId] = fn;
+ }
+ if (this.compiledCharCodeToGlyphId[cmap.charCode] === undefined) {
+ this.compiledCharCodeToGlyphId[cmap.charCode] = cmap.glyphId;
+ }
+ return fn;
+ },
+ compileGlyph: function (code) {
+ if (!code || code.length === 0 || code[0] === 14) {
+ return noop;
+ }
+ var cmds = [];
+ cmds.push({ cmd: 'save' });
+ cmds.push({
+ cmd: 'transform',
+ args: this.fontMatrix.slice()
+ });
+ cmds.push({
+ cmd: 'scale',
+ args: [
+ 'size',
+ '-size'
+ ]
+ });
+ this.compileGlyphImpl(code, cmds);
+ cmds.push({ cmd: 'restore' });
+ return cmds;
+ },
+ compileGlyphImpl: function () {
+ error('Children classes should implement this.');
+ },
+ hasBuiltPath: function (unicode) {
+ var cmap = lookupCmap(this.cmap, unicode);
+ return this.compiledGlyphs[cmap.glyphId] !== undefined && this.compiledCharCodeToGlyphId[cmap.charCode] !== undefined;
+ }
+ };
+ function TrueTypeCompiled(glyphs, cmap, fontMatrix) {
+ fontMatrix = fontMatrix || [
+ 0.000488,
+ 0,
+ 0,
+ 0.000488,
+ 0,
+ 0
+ ];
+ CompiledFont.call(this, fontMatrix);
+ this.glyphs = glyphs;
+ this.cmap = cmap;
+ }
+ Util.inherit(TrueTypeCompiled, CompiledFont, {
+ compileGlyphImpl: function (code, cmds) {
+ compileGlyf(code, cmds, this);
+ }
+ });
+ function Type2Compiled(cffInfo, cmap, fontMatrix, glyphNameMap) {
+ fontMatrix = fontMatrix || [
+ 0.001,
+ 0,
+ 0,
+ 0.001,
+ 0,
+ 0
+ ];
+ CompiledFont.call(this, fontMatrix);
+ this.glyphs = cffInfo.glyphs;
+ this.gsubrs = cffInfo.gsubrs || [];
+ this.subrs = cffInfo.subrs || [];
+ this.cmap = cmap;
+ this.glyphNameMap = glyphNameMap || getGlyphsUnicode();
+ this.gsubrsBias = this.gsubrs.length < 1240 ? 107 : this.gsubrs.length < 33900 ? 1131 : 32768;
+ this.subrsBias = this.subrs.length < 1240 ? 107 : this.subrs.length < 33900 ? 1131 : 32768;
+ }
+ Util.inherit(Type2Compiled, CompiledFont, {
+ compileGlyphImpl: function (code, cmds) {
+ compileCharString(code, cmds, this);
+ }
+ });
+ return {
+ create: function FontRendererFactory_create(font, seacAnalysisEnabled) {
+ var data = new Uint8Array(font.data);
+ var cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
+ var numTables = getUshort(data, 4);
+ for (var i = 0, p = 12; i < numTables; i++, p += 16) {
+ var tag = bytesToString(data.subarray(p, p + 4));
+ var offset = getLong(data, p + 8);
+ var length = getLong(data, p + 12);
+ switch (tag) {
+ case 'cmap':
+ cmap = parseCmap(data, offset, offset + length);
+ break;
+ case 'glyf':
+ glyf = data.subarray(offset, offset + length);
+ break;
+ case 'loca':
+ loca = data.subarray(offset, offset + length);
+ break;
+ case 'head':
+ unitsPerEm = getUshort(data, offset + 18);
+ indexToLocFormat = getUshort(data, offset + 50);
+ break;
+ case 'CFF ':
+ cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
+ break;
+ }
+ }
+ if (glyf) {
+ var fontMatrix = !unitsPerEm ? font.fontMatrix : [
+ 1 / unitsPerEm,
+ 0,
+ 0,
+ 1 / unitsPerEm,
+ 0,
+ 0
+ ];
+ return new TrueTypeCompiled(parseGlyfTable(glyf, loca, indexToLocFormat), cmap, fontMatrix);
+ } else {
+ return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
+ }
+ }
+ };
+ }();
+ exports.FontRendererFactory = FontRendererFactory;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreParser = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream);
+ }(this, function (exports, sharedUtil, corePrimitives, coreStream) {
+ var MissingDataException = sharedUtil.MissingDataException;
+ var StreamType = sharedUtil.StreamType;
+ var assert = sharedUtil.assert;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var isInt = sharedUtil.isInt;
+ var isNum = sharedUtil.isNum;
+ var isString = sharedUtil.isString;
+ var warn = sharedUtil.warn;
+ var Cmd = corePrimitives.Cmd;
+ var Dict = corePrimitives.Dict;
+ var Name = corePrimitives.Name;
+ var Ref = corePrimitives.Ref;
+ var isCmd = corePrimitives.isCmd;
+ var isDict = corePrimitives.isDict;
+ var isName = corePrimitives.isName;
+ var Ascii85Stream = coreStream.Ascii85Stream;
+ var AsciiHexStream = coreStream.AsciiHexStream;
+ var CCITTFaxStream = coreStream.CCITTFaxStream;
+ var FlateStream = coreStream.FlateStream;
+ var Jbig2Stream = coreStream.Jbig2Stream;
+ var JpegStream = coreStream.JpegStream;
+ var JpxStream = coreStream.JpxStream;
+ var LZWStream = coreStream.LZWStream;
+ var NullStream = coreStream.NullStream;
+ var PredictorStream = coreStream.PredictorStream;
+ var RunLengthStream = coreStream.RunLengthStream;
+ var EOF = {};
+ function isEOF(v) {
+ return v === EOF;
+ }
+ var MAX_LENGTH_TO_CACHE = 1000;
+ var Parser = function ParserClosure() {
+ function Parser(lexer, allowStreams, xref, recoveryMode) {
+ this.lexer = lexer;
+ this.allowStreams = allowStreams;
+ this.xref = xref;
+ this.recoveryMode = recoveryMode || false;
+ this.imageCache = Object.create(null);
+ this.refill();
+ }
+ Parser.prototype = {
+ refill: function Parser_refill() {
+ this.buf1 = this.lexer.getObj();
+ this.buf2 = this.lexer.getObj();
+ },
+ shift: function Parser_shift() {
+ if (isCmd(this.buf2, 'ID')) {
+ this.buf1 = this.buf2;
+ this.buf2 = null;
+ } else {
+ this.buf1 = this.buf2;
+ this.buf2 = this.lexer.getObj();
+ }
+ },
+ tryShift: function Parser_tryShift() {
+ try {
+ this.shift();
+ return true;
+ } catch (e) {
+ if (e instanceof MissingDataException) {
+ throw e;
+ }
+ // Upon failure, the caller should reset this.lexer.pos to a known good
+ // state and call this.shift() twice to reset the buffers.
+ return false;
+ }
+ },
+ getObj: function Parser_getObj(cipherTransform) {
+ var buf1 = this.buf1;
+ this.shift();
+ if (buf1 instanceof Cmd) {
+ switch (buf1.cmd) {
+ case 'BI':
+ // inline image
+ return this.makeInlineImage(cipherTransform);
+ case '[':
+ // array
+ var array = [];
+ while (!isCmd(this.buf1, ']') && !isEOF(this.buf1)) {
+ array.push(this.getObj(cipherTransform));
+ }
+ if (isEOF(this.buf1)) {
+ if (!this.recoveryMode) {
+ error('End of file inside array');
+ }
+ return array;
+ }
+ this.shift();
+ return array;
+ case '<<':
+ // dictionary or stream
+ var dict = new Dict(this.xref);
+ while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) {
+ if (!isName(this.buf1)) {
+ info('Malformed dictionary: key must be a name object');
+ this.shift();
+ continue;
+ }
+ var key = this.buf1.name;
+ this.shift();
+ if (isEOF(this.buf1)) {
+ break;
+ }
+ dict.set(key, this.getObj(cipherTransform));
+ }
+ if (isEOF(this.buf1)) {
+ if (!this.recoveryMode) {
+ error('End of file inside dictionary');
+ }
+ return dict;
+ }
+ // Stream objects are not allowed inside content streams or
+ // object streams.
+ if (isCmd(this.buf2, 'stream')) {
+ return this.allowStreams ? this.makeStream(dict, cipherTransform) : dict;
+ }
+ this.shift();
+ return dict;
+ default:
+ // simple object
+ return buf1;
+ }
+ }
+ if (isInt(buf1)) {
+ // indirect reference or integer
+ var num = buf1;
+ if (isInt(this.buf1) && isCmd(this.buf2, 'R')) {
+ var ref = new Ref(num, this.buf1);
+ this.shift();
+ this.shift();
+ return ref;
+ }
+ return num;
+ }
+ if (isString(buf1)) {
+ // string
+ var str = buf1;
+ if (cipherTransform) {
+ str = cipherTransform.decryptString(str);
+ }
+ return str;
+ }
+ // simple object
+ return buf1;
+ },
+ /**
+ * Find the end of the stream by searching for the /EI\s/.
+ * @returns {number} The inline stream length.
+ */
+ findDefaultInlineStreamEnd: function Parser_findDefaultInlineStreamEnd(stream) {
+ var E = 0x45, I = 0x49, SPACE = 0x20, LF = 0xA, CR = 0xD;
+ var startPos = stream.pos, state = 0, ch, i, n, followingBytes;
+ while ((ch = stream.getByte()) !== -1) {
+ if (state === 0) {
+ state = ch === E ? 1 : 0;
+ } else if (state === 1) {
+ state = ch === I ? 2 : 0;
+ } else {
+ assert(state === 2);
+ if (ch === SPACE || ch === LF || ch === CR) {
+ // Let's check the next five bytes are ASCII... just be sure.
+ n = 5;
+ followingBytes = stream.peekBytes(n);
+ for (i = 0; i < n; i++) {
+ ch = followingBytes[i];
+ if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7F)) {
+ // Not a LF, CR, SPACE or any visible ASCII character, i.e.
+ // it's binary stuff. Resetting the state.
+ state = 0;
+ break;
+ }
+ }
+ if (state === 2) {
+ break;
+ }
+ } else
+ // Finished!
+ {
+ state = 0;
+ }
+ }
+ }
+ return stream.pos - 4 - startPos;
+ },
+ /**
+ * Find the EOI (end-of-image) marker 0xFFD9 of the stream.
+ * @returns {number} The inline stream length.
+ */
+ findDCTDecodeInlineStreamEnd: function Parser_findDCTDecodeInlineStreamEnd(stream) {
+ var startPos = stream.pos, foundEOI = false, b, markerLength, length;
+ while ((b = stream.getByte()) !== -1) {
+ if (b !== 0xFF) {
+ // Not a valid marker.
+ continue;
+ }
+ switch (stream.getByte()) {
+ case 0x00:
+ // Byte stuffing.
+ // 0xFF00 appears to be a very common byte sequence in JPEG images.
+ break;
+ case 0xFF:
+ // Fill byte.
+ // Avoid skipping a valid marker, resetting the stream position.
+ stream.skip(-1);
+ break;
+ case 0xD9:
+ // EOI
+ foundEOI = true;
+ break;
+ case 0xC0:
+ // SOF0
+ case 0xC1:
+ // SOF1
+ case 0xC2:
+ // SOF2
+ case 0xC3:
+ // SOF3
+ case 0xC5:
+ // SOF5
+ case 0xC6:
+ // SOF6
+ case 0xC7:
+ // SOF7
+ case 0xC9:
+ // SOF9
+ case 0xCA:
+ // SOF10
+ case 0xCB:
+ // SOF11
+ case 0xCD:
+ // SOF13
+ case 0xCE:
+ // SOF14
+ case 0xCF:
+ // SOF15
+ case 0xC4:
+ // DHT
+ case 0xCC:
+ // DAC
+ case 0xDA:
+ // SOS
+ case 0xDB:
+ // DQT
+ case 0xDC:
+ // DNL
+ case 0xDD:
+ // DRI
+ case 0xDE:
+ // DHP
+ case 0xDF:
+ // EXP
+ case 0xE0:
+ // APP0
+ case 0xE1:
+ // APP1
+ case 0xE2:
+ // APP2
+ case 0xE3:
+ // APP3
+ case 0xE4:
+ // APP4
+ case 0xE5:
+ // APP5
+ case 0xE6:
+ // APP6
+ case 0xE7:
+ // APP7
+ case 0xE8:
+ // APP8
+ case 0xE9:
+ // APP9
+ case 0xEA:
+ // APP10
+ case 0xEB:
+ // APP11
+ case 0xEC:
+ // APP12
+ case 0xED:
+ // APP13
+ case 0xEE:
+ // APP14
+ case 0xEF:
+ // APP15
+ case 0xFE:
+ // COM
+ // The marker should be followed by the length of the segment.
+ markerLength = stream.getUint16();
+ if (markerLength > 2) {
+ // |markerLength| contains the byte length of the marker segment,
+ // including its own length (2 bytes) and excluding the marker.
+ stream.skip(markerLength - 2);
+ } else
+ // Jump to the next marker.
+ {
+ // The marker length is invalid, resetting the stream position.
+ stream.skip(-2);
+ }
+ break;
+ }
+ if (foundEOI) {
+ break;
+ }
+ }
+ length = stream.pos - startPos;
+ if (b === -1) {
+ warn('Inline DCTDecode image stream: ' + 'EOI marker not found, searching for /EI/ instead.');
+ stream.skip(-length);
+ // Reset the stream position.
+ return this.findDefaultInlineStreamEnd(stream);
+ }
+ this.inlineStreamSkipEI(stream);
+ return length;
+ },
+ /**
+ * Find the EOD (end-of-data) marker '~>' (i.e. TILDE + GT) of the stream.
+ * @returns {number} The inline stream length.
+ */
+ findASCII85DecodeInlineStreamEnd: function Parser_findASCII85DecodeInlineStreamEnd(stream) {
+ var TILDE = 0x7E, GT = 0x3E;
+ var startPos = stream.pos, ch, length;
+ while ((ch = stream.getByte()) !== -1) {
+ if (ch === TILDE && stream.peekByte() === GT) {
+ stream.skip();
+ break;
+ }
+ }
+ length = stream.pos - startPos;
+ if (ch === -1) {
+ warn('Inline ASCII85Decode image stream: ' + 'EOD marker not found, searching for /EI/ instead.');
+ stream.skip(-length);
+ // Reset the stream position.
+ return this.findDefaultInlineStreamEnd(stream);
+ }
+ this.inlineStreamSkipEI(stream);
+ return length;
+ },
+ /**
+ * Find the EOD (end-of-data) marker '>' (i.e. GT) of the stream.
+ * @returns {number} The inline stream length.
+ */
+ findASCIIHexDecodeInlineStreamEnd: function Parser_findASCIIHexDecodeInlineStreamEnd(stream) {
+ var GT = 0x3E;
+ var startPos = stream.pos, ch, length;
+ while ((ch = stream.getByte()) !== -1) {
+ if (ch === GT) {
+ break;
+ }
+ }
+ length = stream.pos - startPos;
+ if (ch === -1) {
+ warn('Inline ASCIIHexDecode image stream: ' + 'EOD marker not found, searching for /EI/ instead.');
+ stream.skip(-length);
+ // Reset the stream position.
+ return this.findDefaultInlineStreamEnd(stream);
+ }
+ this.inlineStreamSkipEI(stream);
+ return length;
+ },
+ /**
+ * Skip over the /EI/ for streams where we search for an EOD marker.
+ */
+ inlineStreamSkipEI: function Parser_inlineStreamSkipEI(stream) {
+ var E = 0x45, I = 0x49;
+ var state = 0, ch;
+ while ((ch = stream.getByte()) !== -1) {
+ if (state === 0) {
+ state = ch === E ? 1 : 0;
+ } else if (state === 1) {
+ state = ch === I ? 2 : 0;
+ } else if (state === 2) {
+ break;
+ }
+ }
+ },
+ makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
+ var lexer = this.lexer;
+ var stream = lexer.stream;
+ // Parse dictionary.
+ var dict = new Dict(this.xref);
+ while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
+ if (!isName(this.buf1)) {
+ error('Dictionary key must be a name object');
+ }
+ var key = this.buf1.name;
+ this.shift();
+ if (isEOF(this.buf1)) {
+ break;
+ }
+ dict.set(key, this.getObj(cipherTransform));
+ }
+ // Extract the name of the first (i.e. the current) image filter.
+ var filter = dict.get('Filter', 'F'), filterName;
+ if (isName(filter)) {
+ filterName = filter.name;
+ } else if (isArray(filter) && isName(filter[0])) {
+ filterName = filter[0].name;
+ }
+ // Parse image stream.
+ var startPos = stream.pos, length, i, ii;
+ if (filterName === 'DCTDecode' || filterName === 'DCT') {
+ length = this.findDCTDecodeInlineStreamEnd(stream);
+ } else if (filterName === 'ASCII85Decide' || filterName === 'A85') {
+ length = this.findASCII85DecodeInlineStreamEnd(stream);
+ } else if (filterName === 'ASCIIHexDecode' || filterName === 'AHx') {
+ length = this.findASCIIHexDecodeInlineStreamEnd(stream);
+ } else {
+ length = this.findDefaultInlineStreamEnd(stream);
+ }
+ var imageStream = stream.makeSubStream(startPos, length, dict);
+ // Cache all images below the MAX_LENGTH_TO_CACHE threshold by their
+ // adler32 checksum.
+ var adler32;
+ if (length < MAX_LENGTH_TO_CACHE) {
+ var imageBytes = imageStream.getBytes();
+ imageStream.reset();
+ var a = 1;
+ var b = 0;
+ for (i = 0, ii = imageBytes.length; i < ii; ++i) {
+ // No modulo required in the loop if imageBytes.length < 5552.
+ a += imageBytes[i] & 0xff;
+ b += a;
+ }
+ adler32 = b % 65521 << 16 | a % 65521;
+ if (this.imageCache.adler32 === adler32) {
+ this.buf2 = Cmd.get('EI');
+ this.shift();
+ this.imageCache[adler32].reset();
+ return this.imageCache[adler32];
+ }
+ }
+ if (cipherTransform) {
+ imageStream = cipherTransform.createStream(imageStream, length);
+ }
+ imageStream = this.filter(imageStream, dict, length);
+ imageStream.dict = dict;
+ if (adler32 !== undefined) {
+ imageStream.cacheKey = 'inline_' + length + '_' + adler32;
+ this.imageCache[adler32] = imageStream;
+ }
+ this.buf2 = Cmd.get('EI');
+ this.shift();
+ return imageStream;
+ },
+ makeStream: function Parser_makeStream(dict, cipherTransform) {
+ var lexer = this.lexer;
+ var stream = lexer.stream;
+ // get stream start position
+ lexer.skipToNextLine();
+ var pos = stream.pos - 1;
+ // get length
+ var length = dict.get('Length');
+ if (!isInt(length)) {
+ info('Bad ' + length + ' attribute in stream');
+ length = 0;
+ }
+ // skip over the stream data
+ stream.pos = pos + length;
+ lexer.nextChar();
+ // Shift '>>' and check whether the new object marks the end of the stream
+ if (this.tryShift() && isCmd(this.buf2, 'endstream')) {
+ this.shift();
+ } else
+ // 'stream'
+ {
+ // bad stream length, scanning for endstream
+ stream.pos = pos;
+ var SCAN_BLOCK_SIZE = 2048;
+ var ENDSTREAM_SIGNATURE_LENGTH = 9;
+ var ENDSTREAM_SIGNATURE = [
+ 0x65,
+ 0x6E,
+ 0x64,
+ 0x73,
+ 0x74,
+ 0x72,
+ 0x65,
+ 0x61,
+ 0x6D
+ ];
+ var skipped = 0, found = false, i, j;
+ while (stream.pos < stream.end) {
+ var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE);
+ var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH;
+ if (scanLength <= 0) {
+ break;
+ }
+ found = false;
+ i = 0;
+ while (i < scanLength) {
+ j = 0;
+ while (j < ENDSTREAM_SIGNATURE_LENGTH && scanBytes[i + j] === ENDSTREAM_SIGNATURE[j]) {
+ j++;
+ }
+ if (j >= ENDSTREAM_SIGNATURE_LENGTH) {
+ found = true;
+ break;
+ }
+ i++;
+ }
+ if (found) {
+ skipped += i;
+ stream.pos += i;
+ break;
+ }
+ skipped += scanLength;
+ stream.pos += scanLength;
+ }
+ if (!found) {
+ error('Missing endstream');
+ }
+ length = skipped;
+ lexer.nextChar();
+ this.shift();
+ this.shift();
+ }
+ this.shift();
+ // 'endstream'
+ stream = stream.makeSubStream(pos, length, dict);
+ if (cipherTransform) {
+ stream = cipherTransform.createStream(stream, length);
+ }
+ stream = this.filter(stream, dict, length);
+ stream.dict = dict;
+ return stream;
+ },
+ filter: function Parser_filter(stream, dict, length) {
+ var filter = dict.get('Filter', 'F');
+ var params = dict.get('DecodeParms', 'DP');
+ if (isName(filter)) {
+ if (isArray(params)) {
+ params = params[0];
+ }
+ return this.makeFilter(stream, filter.name, length, params);
+ }
+ var maybeLength = length;
+ if (isArray(filter)) {
+ var filterArray = filter;
+ var paramsArray = params;
+ for (var i = 0, ii = filterArray.length; i < ii; ++i) {
+ filter = filterArray[i];
+ if (!isName(filter)) {
+ error('Bad filter name: ' + filter);
+ }
+ params = null;
+ if (isArray(paramsArray) && i in paramsArray) {
+ params = paramsArray[i];
+ }
+ stream = this.makeFilter(stream, filter.name, maybeLength, params);
+ // after the first stream the length variable is invalid
+ maybeLength = null;
+ }
+ }
+ return stream;
+ },
+ makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
+ // Since the 'Length' entry in the stream dictionary can be completely
+ // wrong, e.g. zero for non-empty streams, only skip parsing the stream
+ // when we can be absolutely certain that it actually is empty.
+ if (maybeLength === 0) {
+ warn('Empty "' + name + '" stream.');
+ return new NullStream(stream);
+ }
+ try {
+ if (params && this.xref) {
+ params = this.xref.fetchIfRef(params);
+ }
+ var xrefStreamStats = this.xref.stats.streamTypes;
+ if (name === 'FlateDecode' || name === 'Fl') {
+ xrefStreamStats[StreamType.FLATE] = true;
+ if (params) {
+ return new PredictorStream(new FlateStream(stream, maybeLength), maybeLength, params);
+ }
+ return new FlateStream(stream, maybeLength);
+ }
+ if (name === 'LZWDecode' || name === 'LZW') {
+ xrefStreamStats[StreamType.LZW] = true;
+ var earlyChange = 1;
+ if (params) {
+ if (params.has('EarlyChange')) {
+ earlyChange = params.get('EarlyChange');
+ }
+ return new PredictorStream(new LZWStream(stream, maybeLength, earlyChange), maybeLength, params);
+ }
+ return new LZWStream(stream, maybeLength, earlyChange);
+ }
+ if (name === 'DCTDecode' || name === 'DCT') {
+ xrefStreamStats[StreamType.DCT] = true;
+ return new JpegStream(stream, maybeLength, stream.dict, params);
+ }
+ if (name === 'JPXDecode' || name === 'JPX') {
+ xrefStreamStats[StreamType.JPX] = true;
+ return new JpxStream(stream, maybeLength, stream.dict, params);
+ }
+ if (name === 'ASCII85Decode' || name === 'A85') {
+ xrefStreamStats[StreamType.A85] = true;
+ return new Ascii85Stream(stream, maybeLength);
+ }
+ if (name === 'ASCIIHexDecode' || name === 'AHx') {
+ xrefStreamStats[StreamType.AHX] = true;
+ return new AsciiHexStream(stream, maybeLength);
+ }
+ if (name === 'CCITTFaxDecode' || name === 'CCF') {
+ xrefStreamStats[StreamType.CCF] = true;
+ return new CCITTFaxStream(stream, maybeLength, params);
+ }
+ if (name === 'RunLengthDecode' || name === 'RL') {
+ xrefStreamStats[StreamType.RL] = true;
+ return new RunLengthStream(stream, maybeLength);
+ }
+ if (name === 'JBIG2Decode') {
+ xrefStreamStats[StreamType.JBIG] = true;
+ return new Jbig2Stream(stream, maybeLength, stream.dict, params);
+ }
+ warn('filter "' + name + '" not supported yet');
+ return stream;
+ } catch (ex) {
+ if (ex instanceof MissingDataException) {
+ throw ex;
+ }
+ warn('Invalid stream: \"' + ex + '\"');
+ return new NullStream(stream);
+ }
+ }
+ };
+ return Parser;
+ }();
+ var Lexer = function LexerClosure() {
+ function Lexer(stream, knownCommands) {
+ this.stream = stream;
+ this.nextChar();
+ // While lexing, we build up many strings one char at a time. Using += for
+ // this can result in lots of garbage 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 = [];
+ // The PDFs might have "glued" commands with other commands, operands or
+ // literals, e.g. "q1". The knownCommands is a dictionary of the valid
+ // commands and their prefixes. The prefixes are built the following way:
+ // if there a command that is a prefix of the other valid command or
+ // literal (e.g. 'f' and 'false') the following prefixes must be included,
+ // 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no
+ // other commands or literals as a prefix. The knowCommands is optional.
+ this.knownCommands = knownCommands;
+ }
+ // A '1' in this array means the character is white space. A '1' or
+ // '2' means the character ends a name or command.
+ var specialChars = [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ // 0x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // 1x
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 0,
+ 0,
+ 2,
+ 2,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ // 2x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 0,
+ 2,
+ 0,
+ // 3x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // 4x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 0,
+ 2,
+ 0,
+ 0,
+ // 5x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // 6x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 2,
+ 0,
+ 2,
+ 0,
+ 0,
+ // 7x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // 8x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // 9x
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // ax
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // bx
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // cx
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // dx
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ // ex
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ];
+ // fx
+ function toHexDigit(ch) {
+ if (ch >= 0x30 && ch <= 0x39) {
+ // '0'-'9'
+ return ch & 0x0F;
+ }
+ if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
+ // 'A'-'F', 'a'-'f'
+ return (ch & 0x0F) + 9;
+ }
+ return -1;
+ }
+ Lexer.prototype = {
+ nextChar: function Lexer_nextChar() {
+ return this.currentChar = this.stream.getByte();
+ },
+ peekChar: function Lexer_peekChar() {
+ return this.stream.peekByte();
+ },
+ getNumber: function Lexer_getNumber() {
+ var ch = this.currentChar;
+ var eNotation = false;
+ var divideBy = 0;
+ // different from 0 if it's a floating point value
+ var sign = 1;
+ if (ch === 0x2D) {
+ // '-'
+ sign = -1;
+ ch = this.nextChar();
+ if (ch === 0x2D) {
+ // '-'
+ // Ignore double negative (this is consistent with Adobe Reader).
+ ch = this.nextChar();
+ }
+ } else if (ch === 0x2B) {
+ // '+'
+ ch = this.nextChar();
+ }
+ if (ch === 0x2E) {
+ // '.'
+ divideBy = 10;
+ ch = this.nextChar();
+ }
+ if (ch < 0x30 || ch > 0x39) {
+ // '0' - '9'
+ error('Invalid number: ' + String.fromCharCode(ch));
+ return 0;
+ }
+ var baseValue = ch - 0x30;
+ // '0'
+ var powerValue = 0;
+ var powerValueSign = 1;
+ while ((ch = this.nextChar()) >= 0) {
+ if (0x30 <= ch && ch <= 0x39) {
+ // '0' - '9'
+ var currentDigit = ch - 0x30;
+ // '0'
+ if (eNotation) {
+ // We are after an 'e' or 'E'
+ powerValue = powerValue * 10 + currentDigit;
+ } else {
+ if (divideBy !== 0) {
+ // We are after a point
+ divideBy *= 10;
+ }
+ baseValue = baseValue * 10 + currentDigit;
+ }
+ } else if (ch === 0x2E) {
+ // '.'
+ if (divideBy === 0) {
+ divideBy = 1;
+ } else {
+ // A number can have only one '.'
+ break;
+ }
+ } else if (ch === 0x2D) {
+ // '-'
+ // ignore minus signs in the middle of numbers to match
+ // Adobe's behavior
+ warn('Badly formatted number');
+ } else if (ch === 0x45 || ch === 0x65) {
+ // 'E', 'e'
+ // 'E' can be either a scientific notation or the beginning of a new
+ // operator
+ ch = this.peekChar();
+ if (ch === 0x2B || ch === 0x2D) {
+ // '+', '-'
+ powerValueSign = ch === 0x2D ? -1 : 1;
+ this.nextChar();
+ } else // Consume the sign character
+ if (ch < 0x30 || ch > 0x39) {
+ // '0' - '9'
+ // The 'E' must be the beginning of a new operator
+ break;
+ }
+ eNotation = true;
+ } else {
+ // the last character doesn't belong to us
+ break;
+ }
+ }
+ if (divideBy !== 0) {
+ baseValue /= divideBy;
+ }
+ if (eNotation) {
+ baseValue *= Math.pow(10, powerValueSign * powerValue);
+ }
+ return sign * baseValue;
+ },
+ getString: function Lexer_getString() {
+ var numParen = 1;
+ var done = false;
+ var strBuf = this.strBuf;
+ strBuf.length = 0;
+ var ch = this.nextChar();
+ while (true) {
+ var charBuffered = false;
+ switch (ch | 0) {
+ case -1:
+ warn('Unterminated string');
+ done = true;
+ break;
+ case 0x28:
+ // '('
+ ++numParen;
+ strBuf.push('(');
+ break;
+ case 0x29:
+ // ')'
+ if (--numParen === 0) {
+ this.nextChar();
+ // consume strings ')'
+ done = true;
+ } else {
+ strBuf.push(')');
+ }
+ break;
+ case 0x5C:
+ // '\\'
+ ch = this.nextChar();
+ switch (ch) {
+ case -1:
+ warn('Unterminated string');
+ done = true;
+ break;
+ case 0x6E:
+ // 'n'
+ strBuf.push('\n');
+ break;
+ case 0x72:
+ // 'r'
+ strBuf.push('\r');
+ break;
+ case 0x74:
+ // 't'
+ strBuf.push('\t');
+ break;
+ case 0x62:
+ // 'b'
+ strBuf.push('\b');
+ break;
+ case 0x66:
+ // 'f'
+ strBuf.push('\f');
+ break;
+ case 0x5C:
+ // '\'
+ case 0x28:
+ // '('
+ case 0x29:
+ // ')'
+ strBuf.push(String.fromCharCode(ch));
+ break;
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ // '0'-'3'
+ case 0x34:
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ // '4'-'7'
+ var x = ch & 0x0F;
+ ch = this.nextChar();
+ charBuffered = true;
+ if (ch >= 0x30 && ch <= 0x37) {
+ // '0'-'7'
+ x = (x << 3) + (ch & 0x0F);
+ ch = this.nextChar();
+ if (ch >= 0x30 && ch <= 0x37) {
+ // '0'-'7'
+ charBuffered = false;
+ x = (x << 3) + (ch & 0x0F);
+ }
+ }
+ strBuf.push(String.fromCharCode(x));
+ break;
+ case 0x0D:
+ // CR
+ if (this.peekChar() === 0x0A) {
+ // LF
+ this.nextChar();
+ }
+ break;
+ case 0x0A:
+ // LF
+ break;
+ default:
+ strBuf.push(String.fromCharCode(ch));
+ break;
+ }
+ break;
+ default:
+ strBuf.push(String.fromCharCode(ch));
+ break;
+ }
+ if (done) {
+ break;
+ }
+ if (!charBuffered) {
+ ch = this.nextChar();
+ }
+ }
+ return strBuf.join('');
+ },
+ getName: function Lexer_getName() {
+ var ch, previousCh;
+ var strBuf = this.strBuf;
+ strBuf.length = 0;
+ while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
+ if (ch === 0x23) {
+ // '#'
+ ch = this.nextChar();
+ if (specialChars[ch]) {
+ warn('Lexer_getName: ' + 'NUMBER SIGN (#) should be followed by a hexadecimal number.');
+ strBuf.push('#');
+ break;
+ }
+ var x = toHexDigit(ch);
+ if (x !== -1) {
+ previousCh = ch;
+ ch = this.nextChar();
+ var x2 = toHexDigit(ch);
+ if (x2 === -1) {
+ warn('Lexer_getName: Illegal digit (' + String.fromCharCode(ch) + ') in hexadecimal number.');
+ strBuf.push('#', String.fromCharCode(previousCh));
+ if (specialChars[ch]) {
+ break;
+ }
+ strBuf.push(String.fromCharCode(ch));
+ continue;
+ }
+ strBuf.push(String.fromCharCode(x << 4 | x2));
+ } else {
+ strBuf.push('#', String.fromCharCode(ch));
+ }
+ } else {
+ strBuf.push(String.fromCharCode(ch));
+ }
+ }
+ if (strBuf.length > 127) {
+ warn('name token is longer than allowed by the spec: ' + strBuf.length);
+ }
+ return Name.get(strBuf.join(''));
+ },
+ getHexString: function Lexer_getHexString() {
+ var strBuf = this.strBuf;
+ strBuf.length = 0;
+ var ch = this.currentChar;
+ var isFirstHex = true;
+ var firstDigit;
+ var secondDigit;
+ while (true) {
+ if (ch < 0) {
+ warn('Unterminated hex string');
+ break;
+ } else if (ch === 0x3E) {
+ // '>'
+ this.nextChar();
+ break;
+ } else if (specialChars[ch] === 1) {
+ ch = this.nextChar();
+ continue;
+ } else {
+ if (isFirstHex) {
+ firstDigit = toHexDigit(ch);
+ if (firstDigit === -1) {
+ warn('Ignoring invalid character "' + ch + '" in hex string');
+ ch = this.nextChar();
+ continue;
+ }
+ } else {
+ secondDigit = toHexDigit(ch);
+ if (secondDigit === -1) {
+ warn('Ignoring invalid character "' + ch + '" in hex string');
+ ch = this.nextChar();
+ continue;
+ }
+ strBuf.push(String.fromCharCode(firstDigit << 4 | secondDigit));
+ }
+ isFirstHex = !isFirstHex;
+ ch = this.nextChar();
+ }
+ }
+ return strBuf.join('');
+ },
+ getObj: function Lexer_getObj() {
+ // skip whitespace and comments
+ var comment = false;
+ var ch = this.currentChar;
+ while (true) {
+ if (ch < 0) {
+ return EOF;
+ }
+ if (comment) {
+ if (ch === 0x0A || ch === 0x0D) {
+ // LF, CR
+ comment = false;
+ }
+ } else if (ch === 0x25) {
+ // '%'
+ comment = true;
+ } else if (specialChars[ch] !== 1) {
+ break;
+ }
+ ch = this.nextChar();
+ }
+ // start reading token
+ switch (ch | 0) {
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ // '0'-'4'
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ case 0x38:
+ case 0x39:
+ // '5'-'9'
+ case 0x2B:
+ case 0x2D:
+ case 0x2E:
+ // '+', '-', '.'
+ return this.getNumber();
+ case 0x28:
+ // '('
+ return this.getString();
+ case 0x2F:
+ // '/'
+ return this.getName();
+ // array punctuation
+ case 0x5B:
+ // '['
+ this.nextChar();
+ return Cmd.get('[');
+ case 0x5D:
+ // ']'
+ this.nextChar();
+ return Cmd.get(']');
+ // hex string or dict punctuation
+ case 0x3C:
+ // '<'
+ ch = this.nextChar();
+ if (ch === 0x3C) {
+ // dict punctuation
+ this.nextChar();
+ return Cmd.get('<<');
+ }
+ return this.getHexString();
+ // dict punctuation
+ case 0x3E:
+ // '>'
+ ch = this.nextChar();
+ if (ch === 0x3E) {
+ this.nextChar();
+ return Cmd.get('>>');
+ }
+ return Cmd.get('>');
+ case 0x7B:
+ // '{'
+ this.nextChar();
+ return Cmd.get('{');
+ case 0x7D:
+ // '}'
+ this.nextChar();
+ return Cmd.get('}');
+ case 0x29:
+ // ')'
+ error('Illegal character: ' + ch);
+ break;
+ }
+ // command
+ var str = String.fromCharCode(ch);
+ var knownCommands = this.knownCommands;
+ var knownCommandFound = knownCommands && knownCommands[str] !== undefined;
+ while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
+ // stop if known command is found and next character does not make
+ // the str a command
+ var possibleCommand = str + String.fromCharCode(ch);
+ if (knownCommandFound && knownCommands[possibleCommand] === undefined) {
+ break;
+ }
+ if (str.length === 128) {
+ error('Command token too long: ' + str.length);
+ }
+ str = possibleCommand;
+ knownCommandFound = knownCommands && knownCommands[str] !== undefined;
+ }
+ if (str === 'true') {
+ return true;
+ }
+ if (str === 'false') {
+ return false;
+ }
+ if (str === 'null') {
+ return null;
+ }
+ return Cmd.get(str);
+ },
+ skipToNextLine: function Lexer_skipToNextLine() {
+ var ch = this.currentChar;
+ while (ch >= 0) {
+ if (ch === 0x0D) {
+ // CR
+ ch = this.nextChar();
+ if (ch === 0x0A) {
+ // LF
+ this.nextChar();
+ }
+ break;
+ } else if (ch === 0x0A) {
+ // LF
+ this.nextChar();
+ break;
+ }
+ ch = this.nextChar();
+ }
+ }
+ };
+ return Lexer;
+ }();
+ var Linearization = {
+ create: function LinearizationCreate(stream) {
+ function getInt(name, allowZeroValue) {
+ var obj = linDict.get(name);
+ if (isInt(obj) && (allowZeroValue ? obj >= 0 : obj > 0)) {
+ return obj;
+ }
+ throw new Error('The "' + name + '" parameter in the linearization ' + 'dictionary is invalid.');
+ }
+ function getHints() {
+ var hints = linDict.get('H'), hintsLength, item;
+ if (isArray(hints) && ((hintsLength = hints.length) === 2 || hintsLength === 4)) {
+ for (var index = 0; index < hintsLength; index++) {
+ if (!(isInt(item = hints[index]) && item > 0)) {
+ throw new Error('Hint (' + index + ') in the linearization dictionary is invalid.');
+ }
+ }
+ return hints;
+ }
+ throw new Error('Hint array in the linearization dictionary is invalid.');
+ }
+ var parser = new Parser(new Lexer(stream), false, null);
+ var obj1 = parser.getObj();
+ var obj2 = parser.getObj();
+ var obj3 = parser.getObj();
+ var linDict = parser.getObj();
+ var obj, length;
+ if (!(isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') && isDict(linDict) && isNum(obj = linDict.get('Linearized')) && obj > 0)) {
+ return null;
+ } else // No valid linearization dictionary found.
+ if ((length = getInt('L')) !== stream.length) {
+ throw new Error('The "L" parameter in the linearization dictionary ' + 'does not equal the stream length.');
+ }
+ return {
+ length: length,
+ hints: getHints(),
+ objectNumberFirst: getInt('O'),
+ endFirst: getInt('E'),
+ numPages: getInt('N'),
+ mainXRefEntriesOffset: getInt('T'),
+ pageFirst: linDict.has('P') ? getInt('P', true) : 0
+ };
+ }
+ };
+ exports.EOF = EOF;
+ exports.Lexer = Lexer;
+ exports.Linearization = Linearization;
+ exports.Parser = Parser;
+ exports.isEOF = isEOF;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreType1Parser = {}, root.pdfjsSharedUtil, root.pdfjsCoreStream, root.pdfjsCoreEncodings);
+ }(this, function (exports, sharedUtil, coreStream, coreEncodings) {
+ var warn = sharedUtil.warn;
+ var isSpace = sharedUtil.isSpace;
+ var Stream = coreStream.Stream;
+ var getEncoding = coreEncodings.getEncoding;
+ // Hinting is currently disabled due to unknown problems on windows
+ // in tracemonkey and various other pdfs with type1 fonts.
+ var HINTING_ENABLED = false;
+ /*
+ * CharStrings are encoded following the the CharString Encoding sequence
+ * describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
+ * The value in a byte indicates a command, a number, or subsequent bytes
+ * that are to be interpreted in a special way.
+ *
+ * CharString Number Encoding:
+ * A CharString byte containing the values from 32 through 255 inclusive
+ * indicate an integer. These values are decoded in four ranges.
+ *
+ * 1. A CharString byte containing a value, v, between 32 and 246 inclusive,
+ * indicate the integer v - 139. Thus, the integer values from -107 through
+ * 107 inclusive may be encoded in single byte.
+ *
+ * 2. A CharString byte containing a value, v, between 247 and 250 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * [(v - 247) x 256] + w + 108
+ *
+ * 3. A CharString byte containing a value, v, between 251 and 254 inclusive,
+ * indicates an integer involving the next byte, w, according to the formula:
+ * -[(v - 251) * 256] - w - 108
+ *
+ * 4. A CharString containing the value 255 indicates that the next 4 bytes
+ * are a two complement signed integer. The first of these bytes contains the
+ * highest order bits, the second byte contains the next higher order bits
+ * and the fourth byte contain the lowest order bits.
+ *
+ *
+ * CharString Command Encoding:
+ * CharStrings commands are encoded in 1 or 2 bytes.
+ *
+ * Single byte commands are encoded in 1 byte that contains a value between
+ * 0 and 31 inclusive.
+ * If a command byte contains the value 12, then the value in the next byte
+ * indicates a command. This "escape" mechanism allows many extra commands
+ * to be encoded and this encoding technique helps to minimize the length of
+ * the charStrings.
+ */
+ var Type1CharString = function Type1CharStringClosure() {
+ var COMMAND_MAP = {
+ 'hstem': [1],
+ 'vstem': [3],
+ 'vmoveto': [4],
+ 'rlineto': [5],
+ 'hlineto': [6],
+ 'vlineto': [7],
+ 'rrcurveto': [8],
+ 'callsubr': [10],
+ 'flex': [
+ 12,
+ 35
+ ],
+ 'drop': [
+ 12,
+ 18
+ ],
+ 'endchar': [14],
+ 'rmoveto': [21],
+ 'hmoveto': [22],
+ 'vhcurveto': [30],
+ 'hvcurveto': [31]
+ };
+ function Type1CharString() {
+ this.width = 0;
+ this.lsb = 0;
+ this.flexing = false;
+ this.output = [];
+ this.stack = [];
+ }
+ Type1CharString.prototype = {
+ convert: function Type1CharString_convert(encoded, subrs, seacAnalysisEnabled) {
+ var count = encoded.length;
+ var error = false;
+ var wx, sbx, subrNumber;
+ for (var i = 0; i < count; i++) {
+ var value = encoded[i];
+ if (value < 32) {
+ if (value === 12) {
+ value = (value << 8) + encoded[++i];
+ }
+ switch (value) {
+ case 1:
+ // hstem
+ if (!HINTING_ENABLED) {
+ this.stack = [];
+ break;
+ }
+ error = this.executeCommand(2, COMMAND_MAP.hstem);
+ break;
+ case 3:
+ // vstem
+ if (!HINTING_ENABLED) {
+ this.stack = [];
+ break;
+ }
+ error = this.executeCommand(2, COMMAND_MAP.vstem);
+ break;
+ case 4:
+ // vmoveto
+ if (this.flexing) {
+ if (this.stack.length < 1) {
+ error = true;
+ break;
+ }
+ // Add the dx for flex and but also swap the values so they are
+ // the right order.
+ var dy = this.stack.pop();
+ this.stack.push(0, dy);
+ break;
+ }
+ error = this.executeCommand(1, COMMAND_MAP.vmoveto);
+ break;
+ case 5:
+ // rlineto
+ error = this.executeCommand(2, COMMAND_MAP.rlineto);
+ break;
+ case 6:
+ // hlineto
+ error = this.executeCommand(1, COMMAND_MAP.hlineto);
+ break;
+ case 7:
+ // vlineto
+ error = this.executeCommand(1, COMMAND_MAP.vlineto);
+ break;
+ case 8:
+ // rrcurveto
+ error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
+ break;
+ case 9:
+ // closepath
+ // closepath is a Type1 command that does not take argument and is
+ // useless in Type2 and it can simply be ignored.
+ this.stack = [];
+ break;
+ case 10:
+ // callsubr
+ if (this.stack.length < 1) {
+ error = true;
+ break;
+ }
+ subrNumber = this.stack.pop();
+ error = this.convert(subrs[subrNumber], subrs, seacAnalysisEnabled);
+ break;
+ case 11:
+ // return
+ return error;
+ case 13:
+ // hsbw
+ if (this.stack.length < 2) {
+ error = true;
+ break;
+ }
+ // To convert to type2 we have to move the width value to the
+ // first part of the charstring and then use hmoveto with lsb.
+ wx = this.stack.pop();
+ sbx = this.stack.pop();
+ this.lsb = sbx;
+ this.width = wx;
+ this.stack.push(wx, sbx);
+ error = this.executeCommand(2, COMMAND_MAP.hmoveto);
+ break;
+ case 14:
+ // endchar
+ this.output.push(COMMAND_MAP.endchar[0]);
+ break;
+ case 21:
+ // rmoveto
+ if (this.flexing) {
+ break;
+ }
+ error = this.executeCommand(2, COMMAND_MAP.rmoveto);
+ break;
+ case 22:
+ // hmoveto
+ if (this.flexing) {
+ // Add the dy for flex.
+ this.stack.push(0);
+ break;
+ }
+ error = this.executeCommand(1, COMMAND_MAP.hmoveto);
+ break;
+ case 30:
+ // vhcurveto
+ error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
+ break;
+ case 31:
+ // hvcurveto
+ error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
+ break;
+ case (12 << 8) + 0:
+ // dotsection
+ // dotsection is a Type1 command to specify some hinting feature
+ // for dots that do not take a parameter and it can safely be
+ // ignored for Type2.
+ this.stack = [];
+ break;
+ case (12 << 8) + 1:
+ // vstem3
+ if (!HINTING_ENABLED) {
+ this.stack = [];
+ break;
+ }
+ // [vh]stem3 are Type1 only and Type2 supports [vh]stem with
+ // multiple parameters, so instead of returning [vh]stem3 take a
+ // shortcut and return [vhstem] instead.
+ error = this.executeCommand(2, COMMAND_MAP.vstem);
+ break;
+ case (12 << 8) + 2:
+ // hstem3
+ if (!HINTING_ENABLED) {
+ this.stack = [];
+ break;
+ }
+ // See vstem3.
+ error = this.executeCommand(2, COMMAND_MAP.hstem);
+ break;
+ case (12 << 8) + 6:
+ // seac
+ // seac is like type 2's special endchar but it doesn't use the
+ // first argument asb, so remove it.
+ if (seacAnalysisEnabled) {
+ this.seac = this.stack.splice(-4, 4);
+ error = this.executeCommand(0, COMMAND_MAP.endchar);
+ } else {
+ error = this.executeCommand(4, COMMAND_MAP.endchar);
+ }
+ break;
+ case (12 << 8) + 7:
+ // sbw
+ if (this.stack.length < 4) {
+ error = true;
+ break;
+ }
+ // To convert to type2 we have to move the width value to the
+ // first part of the charstring and then use rmoveto with
+ // (dx, dy). The height argument will not be used for vmtx and
+ // vhea tables reconstruction -- ignoring it.
+ var wy = this.stack.pop();
+ wx = this.stack.pop();
+ var sby = this.stack.pop();
+ sbx = this.stack.pop();
+ this.lsb = sbx;
+ this.width = wx;
+ this.stack.push(wx, sbx, sby);
+ error = this.executeCommand(3, COMMAND_MAP.rmoveto);
+ break;
+ case (12 << 8) + 12:
+ // div
+ if (this.stack.length < 2) {
+ error = true;
+ break;
+ }
+ var num2 = this.stack.pop();
+ var num1 = this.stack.pop();
+ this.stack.push(num1 / num2);
+ break;
+ case (12 << 8) + 16:
+ // callothersubr
+ if (this.stack.length < 2) {
+ error = true;
+ break;
+ }
+ subrNumber = this.stack.pop();
+ var numArgs = this.stack.pop();
+ if (subrNumber === 0 && numArgs === 3) {
+ var flexArgs = this.stack.splice(this.stack.length - 17, 17);
+ this.stack.push(flexArgs[2] + flexArgs[0], // bcp1x + rpx
+ flexArgs[3] + flexArgs[1], // bcp1y + rpy
+ flexArgs[4], // bcp2x
+ flexArgs[5], // bcp2y
+ flexArgs[6], // p2x
+ flexArgs[7], // p2y
+ flexArgs[8], // bcp3x
+ flexArgs[9], // bcp3y
+ flexArgs[10], // bcp4x
+ flexArgs[11], // bcp4y
+ flexArgs[12], // p3x
+ flexArgs[13], // p3y
+ flexArgs[14]);
+ // flexDepth
+ // 15 = finalx unused by flex
+ // 16 = finaly unused by flex
+ error = this.executeCommand(13, COMMAND_MAP.flex, true);
+ this.flexing = false;
+ this.stack.push(flexArgs[15], flexArgs[16]);
+ } else if (subrNumber === 1 && numArgs === 0) {
+ this.flexing = true;
+ }
+ break;
+ case (12 << 8) + 17:
+ // pop
+ // Ignore this since it is only used with othersubr.
+ break;
+ case (12 << 8) + 33:
+ // setcurrentpoint
+ // Ignore for now.
+ this.stack = [];
+ break;
+ default:
+ warn('Unknown type 1 charstring command of "' + value + '"');
+ break;
+ }
+ if (error) {
+ break;
+ }
+ continue;
+ } else if (value <= 246) {
+ value = value - 139;
+ } else if (value <= 250) {
+ value = (value - 247) * 256 + encoded[++i] + 108;
+ } else if (value <= 254) {
+ value = -((value - 251) * 256) - encoded[++i] - 108;
+ } else {
+ value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 | (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
+ }
+ this.stack.push(value);
+ }
+ return error;
+ },
+ executeCommand: function (howManyArgs, command, keepStack) {
+ var stackLength = this.stack.length;
+ if (howManyArgs > stackLength) {
+ return true;
+ }
+ var start = stackLength - howManyArgs;
+ for (var i = start; i < stackLength; i++) {
+ var value = this.stack[i];
+ if (value === (value | 0)) {
+ // int
+ this.output.push(28, value >> 8 & 0xff, value & 0xff);
+ } else {
+ // fixed point
+ value = 65536 * value | 0;
+ this.output.push(255, value >> 24 & 0xFF, value >> 16 & 0xFF, value >> 8 & 0xFF, value & 0xFF);
+ }
+ }
+ this.output.push.apply(this.output, command);
+ if (keepStack) {
+ this.stack.splice(start, howManyArgs);
+ } else {
+ this.stack.length = 0;
+ }
+ return false;
+ }
+ };
+ return Type1CharString;
+ }();
+ /*
+ * Type1Parser encapsulate the needed code for parsing a Type1 font
+ * program. Some of its logic depends on the Type2 charstrings
+ * structure.
+ * Note: this doesn't really parse the font since that would require evaluation
+ * of PostScript, but it is possible in most cases to extract what we need
+ * without a full parse.
+ */
+ var Type1Parser = function Type1ParserClosure() {
+ /*
+ * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
+ * of Plaintext Bytes. The function took a key as a parameter which can be
+ * for decrypting the eexec block of for decoding charStrings.
+ */
+ var EEXEC_ENCRYPT_KEY = 55665;
+ var CHAR_STRS_ENCRYPT_KEY = 4330;
+ function isHexDigit(code) {
+ return code >= 48 && code <= 57 || // '0'-'9'
+ code >= 65 && code <= 70 || // 'A'-'F'
+ code >= 97 && code <= 102;
+ }
+ // 'a'-'f'
+ function decrypt(data, key, discardNumber) {
+ if (discardNumber >= data.length) {
+ return new Uint8Array(0);
+ }
+ var r = key | 0, c1 = 52845, c2 = 22719, i, j;
+ for (i = 0; i < discardNumber; i++) {
+ r = (data[i] + r) * c1 + c2 & (1 << 16) - 1;
+ }
+ var count = data.length - discardNumber;
+ var decrypted = new Uint8Array(count);
+ for (i = discardNumber, j = 0; j < count; i++, j++) {
+ var value = data[i];
+ decrypted[j] = value ^ r >> 8;
+ r = (value + r) * c1 + c2 & (1 << 16) - 1;
+ }
+ return decrypted;
+ }
+ function decryptAscii(data, key, discardNumber) {
+ var r = key | 0, c1 = 52845, c2 = 22719;
+ var count = data.length, maybeLength = count >>> 1;
+ var decrypted = new Uint8Array(maybeLength);
+ var i, j;
+ for (i = 0, j = 0; i < count; i++) {
+ var digit1 = data[i];
+ if (!isHexDigit(digit1)) {
+ continue;
+ }
+ i++;
+ var digit2;
+ while (i < count && !isHexDigit(digit2 = data[i])) {
+ i++;
+ }
+ if (i < count) {
+ var value = parseInt(String.fromCharCode(digit1, digit2), 16);
+ decrypted[j++] = value ^ r >> 8;
+ r = (value + r) * c1 + c2 & (1 << 16) - 1;
+ }
+ }
+ return Array.prototype.slice.call(decrypted, discardNumber, j);
+ }
+ function isSpecial(c) {
+ return c === 0x2F || // '/'
+ c === 0x5B || c === 0x5D || // '[', ']'
+ c === 0x7B || c === 0x7D || // '{', '}'
+ c === 0x28 || c === 0x29;
+ }
+ // '(', ')'
+ function Type1Parser(stream, encrypted, seacAnalysisEnabled) {
+ if (encrypted) {
+ var data = stream.getBytes();
+ var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) && isHexDigit(data[2]) && isHexDigit(data[3]));
+ stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
+ }
+ this.seacAnalysisEnabled = !!seacAnalysisEnabled;
+ this.stream = stream;
+ this.nextChar();
+ }
+ Type1Parser.prototype = {
+ readNumberArray: function Type1Parser_readNumberArray() {
+ this.getToken();
+ // read '[' or '{' (arrays can start with either)
+ var array = [];
+ while (true) {
+ var token = this.getToken();
+ if (token === null || token === ']' || token === '}') {
+ break;
+ }
+ array.push(parseFloat(token || 0));
+ }
+ return array;
+ },
+ readNumber: function Type1Parser_readNumber() {
+ var token = this.getToken();
+ return parseFloat(token || 0);
+ },
+ readInt: function Type1Parser_readInt() {
+ // Use '| 0' to prevent setting a double into length such as the double
+ // does not flow into the loop variable.
+ var token = this.getToken();
+ return parseInt(token || 0, 10) | 0;
+ },
+ readBoolean: function Type1Parser_readBoolean() {
+ var token = this.getToken();
+ // Use 1 and 0 since that's what type2 charstrings use.
+ return token === 'true' ? 1 : 0;
+ },
+ nextChar: function Type1_nextChar() {
+ return this.currentChar = this.stream.getByte();
+ },
+ getToken: function Type1Parser_getToken() {
+ // Eat whitespace and comments.
+ var comment = false;
+ var ch = this.currentChar;
+ while (true) {
+ if (ch === -1) {
+ return null;
+ }
+ if (comment) {
+ if (ch === 0x0A || ch === 0x0D) {
+ comment = false;
+ }
+ } else if (ch === 0x25) {
+ // '%'
+ comment = true;
+ } else if (!isSpace(ch)) {
+ break;
+ }
+ ch = this.nextChar();
+ }
+ if (isSpecial(ch)) {
+ this.nextChar();
+ return String.fromCharCode(ch);
+ }
+ var token = '';
+ do {
+ token += String.fromCharCode(ch);
+ ch = this.nextChar();
+ } while (ch >= 0 && !isSpace(ch) && !isSpecial(ch));
+ return token;
+ },
+ /*
+ * Returns an object containing a Subrs array and a CharStrings
+ * array extracted from and eexec encrypted block of data
+ */
+ extractFontProgram: function Type1Parser_extractFontProgram() {
+ var stream = this.stream;
+ var subrs = [], charstrings = [];
+ var privateData = Object.create(null);
+ privateData['lenIV'] = 4;
+ var program = {
+ subrs: [],
+ charstrings: [],
+ properties: { 'privateData': privateData }
+ };
+ var token, length, data, lenIV, encoded;
+ while ((token = this.getToken()) !== null) {
+ if (token !== '/') {
+ continue;
+ }
+ token = this.getToken();
+ switch (token) {
+ case 'CharStrings':
+ // The number immediately following CharStrings must be greater or
+ // equal to the number of CharStrings.
+ this.getToken();
+ this.getToken();
+ // read in 'dict'
+ this.getToken();
+ // read in 'dup'
+ this.getToken();
+ // read in 'begin'
+ while (true) {
+ token = this.getToken();
+ if (token === null || token === 'end') {
+ break;
+ }
+ if (token !== '/') {
+ continue;
+ }
+ var glyph = this.getToken();
+ length = this.readInt();
+ this.getToken();
+ // read in 'RD' or '-|'
+ data = stream.makeSubStream(stream.pos, length);
+ lenIV = program.properties.privateData['lenIV'];
+ encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
+ // Skip past the required space and binary data.
+ stream.skip(length);
+ this.nextChar();
+ token = this.getToken();
+ // read in 'ND' or '|-'
+ if (token === 'noaccess') {
+ this.getToken();
+ }
+ // read in 'def'
+ charstrings.push({
+ glyph: glyph,
+ encoded: encoded
+ });
+ }
+ break;
+ case 'Subrs':
+ var num = this.readInt();
+ this.getToken();
+ // read in 'array'
+ while ((token = this.getToken()) === 'dup') {
+ var index = this.readInt();
+ length = this.readInt();
+ this.getToken();
+ // read in 'RD' or '-|'
+ data = stream.makeSubStream(stream.pos, length);
+ lenIV = program.properties.privateData['lenIV'];
+ encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
+ // Skip past the required space and binary data.
+ stream.skip(length);
+ this.nextChar();
+ token = this.getToken();
+ // read in 'NP' or '|'
+ if (token === 'noaccess') {
+ this.getToken();
+ }
+ // read in 'put'
+ subrs[index] = encoded;
+ }
+ break;
+ case 'BlueValues':
+ case 'OtherBlues':
+ case 'FamilyBlues':
+ case 'FamilyOtherBlues':
+ var blueArray = this.readNumberArray();
+ // *Blue* values may contain invalid data: disables reading of
+ // those values when hinting is disabled.
+ if (blueArray.length > 0 && blueArray.length % 2 === 0 && HINTING_ENABLED) {
+ program.properties.privateData[token] = blueArray;
+ }
+ break;
+ case 'StemSnapH':
+ case 'StemSnapV':
+ program.properties.privateData[token] = this.readNumberArray();
+ break;
+ case 'StdHW':
+ case 'StdVW':
+ program.properties.privateData[token] = this.readNumberArray()[0];
+ break;
+ case 'BlueShift':
+ case 'lenIV':
+ case 'BlueFuzz':
+ case 'BlueScale':
+ case 'LanguageGroup':
+ case 'ExpansionFactor':
+ program.properties.privateData[token] = this.readNumber();
+ break;
+ case 'ForceBold':
+ program.properties.privateData[token] = this.readBoolean();
+ break;
+ }
+ }
+ for (var i = 0; i < charstrings.length; i++) {
+ glyph = charstrings[i].glyph;
+ encoded = charstrings[i].encoded;
+ var charString = new Type1CharString();
+ var error = charString.convert(encoded, subrs, this.seacAnalysisEnabled);
+ var output = charString.output;
+ if (error) {
+ // It seems when FreeType encounters an error while evaluating a glyph
+ // that it completely ignores the glyph so we'll mimic that behaviour
+ // here and put an endchar to make the validator happy.
+ output = [14];
+ }
+ program.charstrings.push({
+ glyphName: glyph,
+ charstring: output,
+ width: charString.width,
+ lsb: charString.lsb,
+ seac: charString.seac
+ });
+ }
+ return program;
+ },
+ extractFontHeader: function Type1Parser_extractFontHeader(properties) {
+ var token;
+ while ((token = this.getToken()) !== null) {
+ if (token !== '/') {
+ continue;
+ }
+ token = this.getToken();
+ switch (token) {
+ case 'FontMatrix':
+ var matrix = this.readNumberArray();
+ properties.fontMatrix = matrix;
+ break;
+ case 'Encoding':
+ var encodingArg = this.getToken();
+ var encoding;
+ if (!/^\d+$/.test(encodingArg)) {
+ // encoding name is specified
+ encoding = getEncoding(encodingArg);
+ } else {
+ encoding = [];
+ var size = parseInt(encodingArg, 10) | 0;
+ this.getToken();
+ // read in 'array'
+ for (var j = 0; j < size; j++) {
+ token = this.getToken();
+ // skipping till first dup or def (e.g. ignoring for statement)
+ while (token !== 'dup' && token !== 'def') {
+ token = this.getToken();
+ if (token === null) {
+ return;
+ }
+ }
+ // invalid header
+ if (token === 'def') {
+ break;
+ }
+ // read all array data
+ var index = this.readInt();
+ this.getToken();
+ // read in '/'
+ var glyph = this.getToken();
+ encoding[index] = glyph;
+ this.getToken();
+ }
+ }
+ // read the in 'put'
+ properties.builtInEncoding = encoding;
+ break;
+ case 'FontBBox':
+ var fontBBox = this.readNumberArray();
+ // adjusting ascent/descent
+ properties.ascent = fontBBox[3];
+ properties.descent = fontBBox[1];
+ properties.ascentScaled = true;
+ break;
+ }
+ }
+ }
+ };
+ return Type1Parser;
+ }();
+ exports.Type1Parser = Type1Parser;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreCMap = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser);
+ }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser) {
+ var Util = sharedUtil.Util;
+ var assert = sharedUtil.assert;
+ var warn = sharedUtil.warn;
+ var error = sharedUtil.error;
+ var isInt = sharedUtil.isInt;
+ var isString = sharedUtil.isString;
+ var MissingDataException = sharedUtil.MissingDataException;
+ var isName = corePrimitives.isName;
+ var isCmd = corePrimitives.isCmd;
+ var isStream = corePrimitives.isStream;
+ var StringStream = coreStream.StringStream;
+ var Lexer = coreParser.Lexer;
+ var isEOF = coreParser.isEOF;
+ var BUILT_IN_CMAPS = [
+ // << Start unicode maps.
+ 'Adobe-GB1-UCS2',
+ 'Adobe-CNS1-UCS2',
+ 'Adobe-Japan1-UCS2',
+ 'Adobe-Korea1-UCS2',
+ // >> End unicode maps.
+ '78-EUC-H',
+ '78-EUC-V',
+ '78-H',
+ '78-RKSJ-H',
+ '78-RKSJ-V',
+ '78-V',
+ '78ms-RKSJ-H',
+ '78ms-RKSJ-V',
+ '83pv-RKSJ-H',
+ '90ms-RKSJ-H',
+ '90ms-RKSJ-V',
+ '90msp-RKSJ-H',
+ '90msp-RKSJ-V',
+ '90pv-RKSJ-H',
+ '90pv-RKSJ-V',
+ 'Add-H',
+ 'Add-RKSJ-H',
+ 'Add-RKSJ-V',
+ 'Add-V',
+ 'Adobe-CNS1-0',
+ 'Adobe-CNS1-1',
+ 'Adobe-CNS1-2',
+ 'Adobe-CNS1-3',
+ 'Adobe-CNS1-4',
+ 'Adobe-CNS1-5',
+ 'Adobe-CNS1-6',
+ 'Adobe-GB1-0',
+ 'Adobe-GB1-1',
+ 'Adobe-GB1-2',
+ 'Adobe-GB1-3',
+ 'Adobe-GB1-4',
+ 'Adobe-GB1-5',
+ 'Adobe-Japan1-0',
+ 'Adobe-Japan1-1',
+ 'Adobe-Japan1-2',
+ 'Adobe-Japan1-3',
+ 'Adobe-Japan1-4',
+ 'Adobe-Japan1-5',
+ 'Adobe-Japan1-6',
+ 'Adobe-Korea1-0',
+ 'Adobe-Korea1-1',
+ 'Adobe-Korea1-2',
+ 'B5-H',
+ 'B5-V',
+ 'B5pc-H',
+ 'B5pc-V',
+ 'CNS-EUC-H',
+ 'CNS-EUC-V',
+ 'CNS1-H',
+ 'CNS1-V',
+ 'CNS2-H',
+ 'CNS2-V',
+ 'ETHK-B5-H',
+ 'ETHK-B5-V',
+ 'ETen-B5-H',
+ 'ETen-B5-V',
+ 'ETenms-B5-H',
+ 'ETenms-B5-V',
+ 'EUC-H',
+ 'EUC-V',
+ 'Ext-H',
+ 'Ext-RKSJ-H',
+ 'Ext-RKSJ-V',
+ 'Ext-V',
+ 'GB-EUC-H',
+ 'GB-EUC-V',
+ 'GB-H',
+ 'GB-V',
+ 'GBK-EUC-H',
+ 'GBK-EUC-V',
+ 'GBK2K-H',
+ 'GBK2K-V',
+ 'GBKp-EUC-H',
+ 'GBKp-EUC-V',
+ 'GBT-EUC-H',
+ 'GBT-EUC-V',
+ 'GBT-H',
+ 'GBT-V',
+ 'GBTpc-EUC-H',
+ 'GBTpc-EUC-V',
+ 'GBpc-EUC-H',
+ 'GBpc-EUC-V',
+ 'H',
+ 'HKdla-B5-H',
+ 'HKdla-B5-V',
+ 'HKdlb-B5-H',
+ 'HKdlb-B5-V',
+ 'HKgccs-B5-H',
+ 'HKgccs-B5-V',
+ 'HKm314-B5-H',
+ 'HKm314-B5-V',
+ 'HKm471-B5-H',
+ 'HKm471-B5-V',
+ 'HKscs-B5-H',
+ 'HKscs-B5-V',
+ 'Hankaku',
+ 'Hiragana',
+ 'KSC-EUC-H',
+ 'KSC-EUC-V',
+ 'KSC-H',
+ 'KSC-Johab-H',
+ 'KSC-Johab-V',
+ 'KSC-V',
+ 'KSCms-UHC-H',
+ 'KSCms-UHC-HW-H',
+ 'KSCms-UHC-HW-V',
+ 'KSCms-UHC-V',
+ 'KSCpc-EUC-H',
+ 'KSCpc-EUC-V',
+ 'Katakana',
+ 'NWP-H',
+ 'NWP-V',
+ 'RKSJ-H',
+ 'RKSJ-V',
+ 'Roman',
+ 'UniCNS-UCS2-H',
+ 'UniCNS-UCS2-V',
+ 'UniCNS-UTF16-H',
+ 'UniCNS-UTF16-V',
+ 'UniCNS-UTF32-H',
+ 'UniCNS-UTF32-V',
+ 'UniCNS-UTF8-H',
+ 'UniCNS-UTF8-V',
+ 'UniGB-UCS2-H',
+ 'UniGB-UCS2-V',
+ 'UniGB-UTF16-H',
+ 'UniGB-UTF16-V',
+ 'UniGB-UTF32-H',
+ 'UniGB-UTF32-V',
+ 'UniGB-UTF8-H',
+ 'UniGB-UTF8-V',
+ 'UniJIS-UCS2-H',
+ 'UniJIS-UCS2-HW-H',
+ 'UniJIS-UCS2-HW-V',
+ 'UniJIS-UCS2-V',
+ 'UniJIS-UTF16-H',
+ 'UniJIS-UTF16-V',
+ 'UniJIS-UTF32-H',
+ 'UniJIS-UTF32-V',
+ 'UniJIS-UTF8-H',
+ 'UniJIS-UTF8-V',
+ 'UniJIS2004-UTF16-H',
+ 'UniJIS2004-UTF16-V',
+ 'UniJIS2004-UTF32-H',
+ 'UniJIS2004-UTF32-V',
+ 'UniJIS2004-UTF8-H',
+ 'UniJIS2004-UTF8-V',
+ 'UniJISPro-UCS2-HW-V',
+ 'UniJISPro-UCS2-V',
+ 'UniJISPro-UTF8-V',
+ 'UniJISX0213-UTF32-H',
+ 'UniJISX0213-UTF32-V',
+ 'UniJISX02132004-UTF32-H',
+ 'UniJISX02132004-UTF32-V',
+ 'UniKS-UCS2-H',
+ 'UniKS-UCS2-V',
+ 'UniKS-UTF16-H',
+ 'UniKS-UTF16-V',
+ 'UniKS-UTF32-H',
+ 'UniKS-UTF32-V',
+ 'UniKS-UTF8-H',
+ 'UniKS-UTF8-V',
+ 'V',
+ 'WP-Symbol'
+ ];
+ // CMap, not to be confused with TrueType's cmap.
+ var CMap = function CMapClosure() {
+ function CMap(builtInCMap) {
+ // Codespace ranges are stored as follows:
+ // [[1BytePairs], [2BytePairs], [3BytePairs], [4BytePairs]]
+ // where nBytePairs are ranges e.g. [low1, high1, low2, high2, ...]
+ this.codespaceRanges = [
+ [],
+ [],
+ [],
+ []
+ ];
+ this.numCodespaceRanges = 0;
+ // Map entries have one of two forms.
+ // - cid chars are 16-bit unsigned integers, stored as integers.
+ // - bf chars are variable-length byte sequences, stored as strings, with
+ // one byte per character.
+ this._map = [];
+ this.name = '';
+ this.vertical = false;
+ this.useCMap = null;
+ this.builtInCMap = builtInCMap;
+ }
+ CMap.prototype = {
+ addCodespaceRange: function (n, low, high) {
+ this.codespaceRanges[n - 1].push(low, high);
+ this.numCodespaceRanges++;
+ },
+ mapCidRange: function (low, high, dstLow) {
+ while (low <= high) {
+ this._map[low++] = dstLow++;
+ }
+ },
+ mapBfRange: function (low, high, dstLow) {
+ var lastByte = dstLow.length - 1;
+ while (low <= high) {
+ this._map[low++] = dstLow;
+ // Only the last byte has to be incremented.
+ dstLow = dstLow.substr(0, lastByte) + String.fromCharCode(dstLow.charCodeAt(lastByte) + 1);
+ }
+ },
+ mapBfRangeToArray: function (low, high, array) {
+ var i = 0, ii = array.length;
+ while (low <= high && i < ii) {
+ this._map[low] = array[i++];
+ ++low;
+ }
+ },
+ // This is used for both bf and cid chars.
+ mapOne: function (src, dst) {
+ this._map[src] = dst;
+ },
+ lookup: function (code) {
+ return this._map[code];
+ },
+ contains: function (code) {
+ return this._map[code] !== undefined;
+ },
+ forEach: function (callback) {
+ // Most maps have fewer than 65536 entries, and for those we use normal
+ // array iteration. But really sparse tables are possible -- e.g. with
+ // indices in the *billions*. For such tables we use for..in, which isn't
+ // ideal because it stringifies the indices for all present elements, but
+ // it does avoid iterating over every undefined entry.
+ var map = this._map;
+ var length = map.length;
+ var i;
+ if (length <= 0x10000) {
+ for (i = 0; i < length; i++) {
+ if (map[i] !== undefined) {
+ callback(i, map[i]);
+ }
+ }
+ } else {
+ for (i in this._map) {
+ callback(i, map[i]);
+ }
+ }
+ },
+ charCodeOf: function (value) {
+ return this._map.indexOf(value);
+ },
+ getMap: function () {
+ return this._map;
+ },
+ readCharCode: function (str, offset, out) {
+ var c = 0;
+ var codespaceRanges = this.codespaceRanges;
+ var codespaceRangesLen = this.codespaceRanges.length;
+ // 9.7.6.2 CMap Mapping
+ // The code length is at most 4.
+ for (var n = 0; n < codespaceRangesLen; n++) {
+ c = (c << 8 | str.charCodeAt(offset + n)) >>> 0;
+ // Check each codespace range to see if it falls within.
+ var codespaceRange = codespaceRanges[n];
+ for (var k = 0, kk = codespaceRange.length; k < kk;) {
+ var low = codespaceRange[k++];
+ var high = codespaceRange[k++];
+ if (c >= low && c <= high) {
+ out.charcode = c;
+ out.length = n + 1;
+ return;
+ }
+ }
+ }
+ out.charcode = 0;
+ out.length = 1;
+ },
+ get length() {
+ return this._map.length;
+ },
+ get isIdentityCMap() {
+ if (!(this.name === 'Identity-H' || this.name === 'Identity-V')) {
+ return false;
+ }
+ if (this._map.length !== 0x10000) {
+ return false;
+ }
+ for (var i = 0; i < 0x10000; i++) {
+ if (this._map[i] !== i) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+ return CMap;
+ }();
+ // A special case of CMap, where the _map array implicitly has a length of
+ // 65536 and each element is equal to its index.
+ var IdentityCMap = function IdentityCMapClosure() {
+ function IdentityCMap(vertical, n) {
+ CMap.call(this);
+ this.vertical = vertical;
+ this.addCodespaceRange(n, 0, 0xffff);
+ }
+ Util.inherit(IdentityCMap, CMap, {});
+ IdentityCMap.prototype = {
+ addCodespaceRange: CMap.prototype.addCodespaceRange,
+ mapCidRange: function (low, high, dstLow) {
+ error('should not call mapCidRange');
+ },
+ mapBfRange: function (low, high, dstLow) {
+ error('should not call mapBfRange');
+ },
+ mapBfRangeToArray: function (low, high, array) {
+ error('should not call mapBfRangeToArray');
+ },
+ mapOne: function (src, dst) {
+ error('should not call mapCidOne');
+ },
+ lookup: function (code) {
+ return isInt(code) && code <= 0xffff ? code : undefined;
+ },
+ contains: function (code) {
+ return isInt(code) && code <= 0xffff;
+ },
+ forEach: function (callback) {
+ for (var i = 0; i <= 0xffff; i++) {
+ callback(i, i);
+ }
+ },
+ charCodeOf: function (value) {
+ return isInt(value) && value <= 0xffff ? value : -1;
+ },
+ getMap: function () {
+ // Sometimes identity maps must be instantiated, but it's rare.
+ var map = new Array(0x10000);
+ for (var i = 0; i <= 0xffff; i++) {
+ map[i] = i;
+ }
+ return map;
+ },
+ readCharCode: CMap.prototype.readCharCode,
+ get length() {
+ return 0x10000;
+ },
+ get isIdentityCMap() {
+ error('should not access .isIdentityCMap');
+ }
+ };
+ return IdentityCMap;
+ }();
+ var BinaryCMapReader = function BinaryCMapReaderClosure() {
+ function fetchBinaryData(url) {
+ return new Promise(function (resolve, reject) {
+ var request = new XMLHttpRequest();
+ request.open('GET', url, true);
+ request.responseType = 'arraybuffer';
+ request.onreadystatechange = function () {
+ if (request.readyState === XMLHttpRequest.DONE) {
+ if (!request.response || request.status !== 200 && request.status !== 0) {
+ reject(new Error('Unable to get binary cMap at: ' + url));
+ } else {
+ resolve(new Uint8Array(request.response));
+ }
+ }
+ };
+ request.send(null);
+ });
+ }
+ function hexToInt(a, size) {
+ var n = 0;
+ for (var i = 0; i <= size; i++) {
+ n = n << 8 | a[i];
+ }
+ return n >>> 0;
+ }
+ function hexToStr(a, size) {
+ // This code is hot. Special-case some common values to avoid creating an
+ // object with subarray().
+ if (size === 1) {
+ return String.fromCharCode(a[0], a[1]);
+ }
+ if (size === 3) {
+ return String.fromCharCode(a[0], a[1], a[2], a[3]);
+ }
+ return String.fromCharCode.apply(null, a.subarray(0, size + 1));
+ }
+ function addHex(a, b, size) {
+ var c = 0;
+ for (var i = size; i >= 0; i--) {
+ c += a[i] + b[i];
+ a[i] = c & 255;
+ c >>= 8;
+ }
+ }
+ function incHex(a, size) {
+ var c = 1;
+ for (var i = size; i >= 0 && c > 0; i--) {
+ c += a[i];
+ a[i] = c & 255;
+ c >>= 8;
+ }
+ }
+ var MAX_NUM_SIZE = 16;
+ var MAX_ENCODED_NUM_SIZE = 19;
+ // ceil(MAX_NUM_SIZE * 7 / 8)
+ function BinaryCMapStream(data) {
+ this.buffer = data;
+ this.pos = 0;
+ this.end = data.length;
+ this.tmpBuf = new Uint8Array(MAX_ENCODED_NUM_SIZE);
+ }
+ BinaryCMapStream.prototype = {
+ readByte: function () {
+ if (this.pos >= this.end) {
+ return -1;
+ }
+ return this.buffer[this.pos++];
+ },
+ readNumber: function () {
+ var n = 0;
+ var last;
+ do {
+ var b = this.readByte();
+ if (b < 0) {
+ error('unexpected EOF in bcmap');
+ }
+ last = !(b & 0x80);
+ n = n << 7 | b & 0x7F;
+ } while (!last);
+ return n;
+ },
+ readSigned: function () {
+ var n = this.readNumber();
+ return n & 1 ? ~(n >>> 1) : n >>> 1;
+ },
+ readHex: function (num, size) {
+ num.set(this.buffer.subarray(this.pos, this.pos + size + 1));
+ this.pos += size + 1;
+ },
+ readHexNumber: function (num, size) {
+ var last;
+ var stack = this.tmpBuf, sp = 0;
+ do {
+ var b = this.readByte();
+ if (b < 0) {
+ error('unexpected EOF in bcmap');
+ }
+ last = !(b & 0x80);
+ stack[sp++] = b & 0x7F;
+ } while (!last);
+ var i = size, buffer = 0, bufferSize = 0;
+ while (i >= 0) {
+ while (bufferSize < 8 && stack.length > 0) {
+ buffer = stack[--sp] << bufferSize | buffer;
+ bufferSize += 7;
+ }
+ num[i] = buffer & 255;
+ i--;
+ buffer >>= 8;
+ bufferSize -= 8;
+ }
+ },
+ readHexSigned: function (num, size) {
+ this.readHexNumber(num, size);
+ var sign = num[size] & 1 ? 255 : 0;
+ var c = 0;
+ for (var i = 0; i <= size; i++) {
+ c = (c & 1) << 8 | num[i];
+ num[i] = c >> 1 ^ sign;
+ }
+ },
+ readString: function () {
+ var len = this.readNumber();
+ var s = '';
+ for (var i = 0; i < len; i++) {
+ s += String.fromCharCode(this.readNumber());
+ }
+ return s;
+ }
+ };
+ function processBinaryCMap(url, cMap, extend) {
+ return fetchBinaryData(url).then(function (data) {
+ var stream = new BinaryCMapStream(data);
+ var header = stream.readByte();
+ cMap.vertical = !!(header & 1);
+ var useCMap = null;
+ var start = new Uint8Array(MAX_NUM_SIZE);
+ var end = new Uint8Array(MAX_NUM_SIZE);
+ var char = new Uint8Array(MAX_NUM_SIZE);
+ var charCode = new Uint8Array(MAX_NUM_SIZE);
+ var tmp = new Uint8Array(MAX_NUM_SIZE);
+ var code;
+ var b;
+ while ((b = stream.readByte()) >= 0) {
+ var type = b >> 5;
+ if (type === 7) {
+ // metadata, e.g. comment or usecmap
+ switch (b & 0x1F) {
+ case 0:
+ stream.readString();
+ // skipping comment
+ break;
+ case 1:
+ useCMap = stream.readString();
+ break;
+ }
+ continue;
+ }
+ var sequence = !!(b & 0x10);
+ var dataSize = b & 15;
+ assert(dataSize + 1 <= MAX_NUM_SIZE);
+ var ucs2DataSize = 1;
+ var subitemsCount = stream.readNumber();
+ var i;
+ switch (type) {
+ case 0:
+ // codespacerange
+ stream.readHex(start, dataSize);
+ stream.readHexNumber(end, dataSize);
+ addHex(end, start, dataSize);
+ cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
+ for (i = 1; i < subitemsCount; i++) {
+ incHex(end, dataSize);
+ stream.readHexNumber(start, dataSize);
+ addHex(start, end, dataSize);
+ stream.readHexNumber(end, dataSize);
+ addHex(end, start, dataSize);
+ cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
+ }
+ break;
+ case 1:
+ // notdefrange
+ stream.readHex(start, dataSize);
+ stream.readHexNumber(end, dataSize);
+ addHex(end, start, dataSize);
+ code = stream.readNumber();
+ // undefined range, skipping
+ for (i = 1; i < subitemsCount; i++) {
+ incHex(end, dataSize);
+ stream.readHexNumber(start, dataSize);
+ addHex(start, end, dataSize);
+ stream.readHexNumber(end, dataSize);
+ addHex(end, start, dataSize);
+ code = stream.readNumber();
+ }
+ // nop
+ break;
+ case 2:
+ // cidchar
+ stream.readHex(char, dataSize);
+ code = stream.readNumber();
+ cMap.mapOne(hexToInt(char, dataSize), code);
+ for (i = 1; i < subitemsCount; i++) {
+ incHex(char, dataSize);
+ if (!sequence) {
+ stream.readHexNumber(tmp, dataSize);
+ addHex(char, tmp, dataSize);
+ }
+ code = stream.readSigned() + (code + 1);
+ cMap.mapOne(hexToInt(char, dataSize), code);
+ }
+ break;
+ case 3:
+ // cidrange
+ stream.readHex(start, dataSize);
+ stream.readHexNumber(end, dataSize);
+ addHex(end, start, dataSize);
+ code = stream.readNumber();
+ cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
+ for (i = 1; i < subitemsCount; i++) {
+ incHex(end, dataSize);
+ if (!sequence) {
+ stream.readHexNumber(start, dataSize);
+ addHex(start, end, dataSize);
+ } else {
+ start.set(end);
+ }
+ stream.readHexNumber(end, dataSize);
+ addHex(end, start, dataSize);
+ code = stream.readNumber();
+ cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
+ }
+ break;
+ case 4:
+ // bfchar
+ stream.readHex(char, ucs2DataSize);
+ stream.readHex(charCode, dataSize);
+ cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
+ for (i = 1; i < subitemsCount; i++) {
+ incHex(char, ucs2DataSize);
+ if (!sequence) {
+ stream.readHexNumber(tmp, ucs2DataSize);
+ addHex(char, tmp, ucs2DataSize);
+ }
+ incHex(charCode, dataSize);
+ stream.readHexSigned(tmp, dataSize);
+ addHex(charCode, tmp, dataSize);
+ cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
+ }
+ break;
+ case 5:
+ // bfrange
+ stream.readHex(start, ucs2DataSize);
+ stream.readHexNumber(end, ucs2DataSize);
+ addHex(end, start, ucs2DataSize);
+ stream.readHex(charCode, dataSize);
+ cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
+ for (i = 1; i < subitemsCount; i++) {
+ incHex(end, ucs2DataSize);
+ if (!sequence) {
+ stream.readHexNumber(start, ucs2DataSize);
+ addHex(start, end, ucs2DataSize);
+ } else {
+ start.set(end);
+ }
+ stream.readHexNumber(end, ucs2DataSize);
+ addHex(end, start, ucs2DataSize);
+ stream.readHex(charCode, dataSize);
+ cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
+ }
+ break;
+ default:
+ error('Unknown type: ' + type);
+ break;
+ }
+ }
+ if (useCMap) {
+ return extend(useCMap);
+ }
+ return cMap;
+ });
+ }
+ function BinaryCMapReader() {
+ }
+ BinaryCMapReader.prototype = { read: processBinaryCMap };
+ return BinaryCMapReader;
+ }();
+ var CMapFactory = function CMapFactoryClosure() {
+ function strToInt(str) {
+ var a = 0;
+ for (var i = 0; i < str.length; i++) {
+ a = a << 8 | str.charCodeAt(i);
+ }
+ return a >>> 0;
+ }
+ function expectString(obj) {
+ if (!isString(obj)) {
+ error('Malformed CMap: expected string.');
+ }
+ }
+ function expectInt(obj) {
+ if (!isInt(obj)) {
+ error('Malformed CMap: expected int.');
+ }
+ }
+ function parseBfChar(cMap, lexer) {
+ while (true) {
+ var obj = lexer.getObj();
+ if (isEOF(obj)) {
+ break;
+ }
+ if (isCmd(obj, 'endbfchar')) {
+ return;
+ }
+ expectString(obj);
+ var src = strToInt(obj);
+ obj = lexer.getObj();
+ // TODO are /dstName used?
+ expectString(obj);
+ var dst = obj;
+ cMap.mapOne(src, dst);
+ }
+ }
+ function parseBfRange(cMap, lexer) {
+ while (true) {
+ var obj = lexer.getObj();
+ if (isEOF(obj)) {
+ break;
+ }
+ if (isCmd(obj, 'endbfrange')) {
+ return;
+ }
+ expectString(obj);
+ var low = strToInt(obj);
+ obj = lexer.getObj();
+ expectString(obj);
+ var high = strToInt(obj);
+ obj = lexer.getObj();
+ if (isInt(obj) || isString(obj)) {
+ var dstLow = isInt(obj) ? String.fromCharCode(obj) : obj;
+ cMap.mapBfRange(low, high, dstLow);
+ } else if (isCmd(obj, '[')) {
+ obj = lexer.getObj();
+ var array = [];
+ while (!isCmd(obj, ']') && !isEOF(obj)) {
+ array.push(obj);
+ obj = lexer.getObj();
+ }
+ cMap.mapBfRangeToArray(low, high, array);
+ } else {
+ break;
+ }
+ }
+ error('Invalid bf range.');
+ }
+ function parseCidChar(cMap, lexer) {
+ while (true) {
+ var obj = lexer.getObj();
+ if (isEOF(obj)) {
+ break;
+ }
+ if (isCmd(obj, 'endcidchar')) {
+ return;
+ }
+ expectString(obj);
+ var src = strToInt(obj);
+ obj = lexer.getObj();
+ expectInt(obj);
+ var dst = obj;
+ cMap.mapOne(src, dst);
+ }
+ }
+ function parseCidRange(cMap, lexer) {
+ while (true) {
+ var obj = lexer.getObj();
+ if (isEOF(obj)) {
+ break;
+ }
+ if (isCmd(obj, 'endcidrange')) {
+ return;
+ }
+ expectString(obj);
+ var low = strToInt(obj);
+ obj = lexer.getObj();
+ expectString(obj);
+ var high = strToInt(obj);
+ obj = lexer.getObj();
+ expectInt(obj);
+ var dstLow = obj;
+ cMap.mapCidRange(low, high, dstLow);
+ }
+ }
+ function parseCodespaceRange(cMap, lexer) {
+ while (true) {
+ var obj = lexer.getObj();
+ if (isEOF(obj)) {
+ break;
+ }
+ if (isCmd(obj, 'endcodespacerange')) {
+ return;
+ }
+ if (!isString(obj)) {
+ break;
+ }
+ var low = strToInt(obj);
+ obj = lexer.getObj();
+ if (!isString(obj)) {
+ break;
+ }
+ var high = strToInt(obj);
+ cMap.addCodespaceRange(obj.length, low, high);
+ }
+ error('Invalid codespace range.');
+ }
+ function parseWMode(cMap, lexer) {
+ var obj = lexer.getObj();
+ if (isInt(obj)) {
+ cMap.vertical = !!obj;
+ }
+ }
+ function parseCMapName(cMap, lexer) {
+ var obj = lexer.getObj();
+ if (isName(obj) && isString(obj.name)) {
+ cMap.name = obj.name;
+ }
+ }
+ function parseCMap(cMap, lexer, builtInCMapParams, useCMap) {
+ var previous;
+ var embededUseCMap;
+ objLoop:
+ while (true) {
+ try {
+ var obj = lexer.getObj();
+ if (isEOF(obj)) {
+ break;
+ } else if (isName(obj)) {
+ if (obj.name === 'WMode') {
+ parseWMode(cMap, lexer);
+ } else if (obj.name === 'CMapName') {
+ parseCMapName(cMap, lexer);
+ }
+ previous = obj;
+ } else if (isCmd(obj)) {
+ switch (obj.cmd) {
+ case 'endcmap':
+ break objLoop;
+ case 'usecmap':
+ if (isName(previous)) {
+ embededUseCMap = previous.name;
+ }
+ break;
+ case 'begincodespacerange':
+ parseCodespaceRange(cMap, lexer);
+ break;
+ case 'beginbfchar':
+ parseBfChar(cMap, lexer);
+ break;
+ case 'begincidchar':
+ parseCidChar(cMap, lexer);
+ break;
+ case 'beginbfrange':
+ parseBfRange(cMap, lexer);
+ break;
+ case 'begincidrange':
+ parseCidRange(cMap, lexer);
+ break;
+ }
+ }
+ } catch (ex) {
+ if (ex instanceof MissingDataException) {
+ throw ex;
+ }
+ warn('Invalid cMap data: ' + ex);
+ continue;
+ }
+ }
+ if (!useCMap && embededUseCMap) {
+ // Load the usecmap definition from the file only if there wasn't one
+ // specified.
+ useCMap = embededUseCMap;
+ }
+ if (useCMap) {
+ return extendCMap(cMap, builtInCMapParams, useCMap);
+ }
+ return Promise.resolve(cMap);
+ }
+ function extendCMap(cMap, builtInCMapParams, useCMap) {
+ return createBuiltInCMap(useCMap, builtInCMapParams).then(function (newCMap) {
+ cMap.useCMap = newCMap;
+ // If there aren't any code space ranges defined clone all the parent ones
+ // into this cMap.
+ if (cMap.numCodespaceRanges === 0) {
+ var useCodespaceRanges = cMap.useCMap.codespaceRanges;
+ for (var i = 0; i < useCodespaceRanges.length; i++) {
+ cMap.codespaceRanges[i] = useCodespaceRanges[i].slice();
+ }
+ cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges;
+ }
+ // Merge the map into the current one, making sure not to override
+ // any previously defined entries.
+ cMap.useCMap.forEach(function (key, value) {
+ if (!cMap.contains(key)) {
+ cMap.mapOne(key, cMap.useCMap.lookup(key));
+ }
+ });
+ return cMap;
+ });
+ }
+ function parseBinaryCMap(name, builtInCMapParams) {
+ var url = builtInCMapParams.url + name + '.bcmap';
+ var cMap = new CMap(true);
+ return new BinaryCMapReader().read(url, cMap, function (useCMap) {
+ return extendCMap(cMap, builtInCMapParams, useCMap);
+ });
+ }
+ function createBuiltInCMap(name, builtInCMapParams) {
+ if (name === 'Identity-H') {
+ return Promise.resolve(new IdentityCMap(false, 2));
+ } else if (name === 'Identity-V') {
+ return Promise.resolve(new IdentityCMap(true, 2));
+ }
+ if (BUILT_IN_CMAPS.indexOf(name) === -1) {
+ return Promise.reject(new Error('Unknown cMap name: ' + name));
+ }
+ assert(builtInCMapParams, 'built-in cMap parameters are not provided');
+ if (builtInCMapParams.packed) {
+ return parseBinaryCMap(name, builtInCMapParams);
+ }
+ return new Promise(function (resolve, reject) {
+ var url = builtInCMapParams.url + name;
+ var request = new XMLHttpRequest();
+ request.onreadystatechange = function () {
+ if (request.readyState === XMLHttpRequest.DONE) {
+ if (request.status === 200 || request.status === 0) {
+ var cMap = new CMap(true);
+ var lexer = new Lexer(new StringStream(request.responseText));
+ parseCMap(cMap, lexer, builtInCMapParams, null).then(function (parsedCMap) {
+ resolve(parsedCMap);
+ });
+ } else {
+ reject(new Error('Unable to get cMap at: ' + url));
+ }
+ }
+ };
+ request.open('GET', url, true);
+ request.send(null);
+ });
+ }
+ return {
+ create: function (encoding, builtInCMapParams, useCMap) {
+ if (isName(encoding)) {
+ return createBuiltInCMap(encoding.name, builtInCMapParams);
+ } else if (isStream(encoding)) {
+ var cMap = new CMap();
+ var lexer = new Lexer(encoding);
+ return parseCMap(cMap, lexer, builtInCMapParams, useCMap).then(function (parsedCMap) {
+ if (parsedCMap.isIdentityCMap) {
+ return createBuiltInCMap(parsedCMap.name, builtInCMapParams);
+ }
+ return parsedCMap;
+ });
+ }
+ return Promise.reject(new Error('Encoding required.'));
+ }
+ };
+ }();
+ exports.CMap = CMap;
+ exports.CMapFactory = CMapFactory;
+ exports.IdentityCMap = IdentityCMap;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreFonts = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreGlyphList, root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings, root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode, root.pdfjsCoreType1Parser, root.pdfjsCoreCFFParser);
+ }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreGlyphList, coreFontRenderer, coreEncodings, coreStandardFonts, coreUnicode, coreType1Parser, coreCFFParser) {
+ var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
+ var FontType = sharedUtil.FontType;
+ var assert = sharedUtil.assert;
+ var bytesToString = sharedUtil.bytesToString;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var isInt = sharedUtil.isInt;
+ var isNum = sharedUtil.isNum;
+ var readUint32 = sharedUtil.readUint32;
+ var shadow = sharedUtil.shadow;
+ var string32 = sharedUtil.string32;
+ var warn = sharedUtil.warn;
+ var MissingDataException = sharedUtil.MissingDataException;
+ var isSpace = sharedUtil.isSpace;
+ var Stream = coreStream.Stream;
+ var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
+ var getDingbatsGlyphsUnicode = coreGlyphList.getDingbatsGlyphsUnicode;
+ var FontRendererFactory = coreFontRenderer.FontRendererFactory;
+ var StandardEncoding = coreEncodings.StandardEncoding;
+ var MacRomanEncoding = coreEncodings.MacRomanEncoding;
+ var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
+ var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
+ var getEncoding = coreEncodings.getEncoding;
+ var getStdFontMap = coreStandardFonts.getStdFontMap;
+ var getNonStdFontMap = coreStandardFonts.getNonStdFontMap;
+ var getGlyphMapForStandardFonts = coreStandardFonts.getGlyphMapForStandardFonts;
+ var getSupplementalGlyphMapForArialBlack = coreStandardFonts.getSupplementalGlyphMapForArialBlack;
+ var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor;
+ var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues;
+ var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
+ var Type1Parser = coreType1Parser.Type1Parser;
+ var CFFStandardStrings = coreCFFParser.CFFStandardStrings;
+ var CFFParser = coreCFFParser.CFFParser;
+ var CFFCompiler = coreCFFParser.CFFCompiler;
+ var CFF = coreCFFParser.CFF;
+ var CFFHeader = coreCFFParser.CFFHeader;
+ var CFFTopDict = coreCFFParser.CFFTopDict;
+ var CFFPrivateDict = coreCFFParser.CFFPrivateDict;
+ var CFFStrings = coreCFFParser.CFFStrings;
+ var CFFIndex = coreCFFParser.CFFIndex;
+ var CFFCharset = coreCFFParser.CFFCharset;
+ // Unicode Private Use Area
+ var PRIVATE_USE_OFFSET_START = 0xE000;
+ var PRIVATE_USE_OFFSET_END = 0xF8FF;
+ var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
+ // PDF Glyph Space Units are one Thousandth of a TextSpace Unit
+ // except for Type 3 fonts
+ var PDF_GLYPH_SPACE_UNITS = 1000;
+ // Accented charactars are not displayed properly on Windows, using this flag
+ // to control analysis of seac charstrings.
+ var SEAC_ANALYSIS_ENABLED = false;
+ var FontFlags = {
+ FixedPitch: 1,
+ Serif: 2,
+ Symbolic: 4,
+ Script: 8,
+ Nonsymbolic: 32,
+ Italic: 64,
+ AllCap: 65536,
+ SmallCap: 131072,
+ ForceBold: 262144
+ };
+ var MacStandardGlyphOrdering = [
+ '.notdef',
+ '.null',
+ 'nonmarkingreturn',
+ 'space',
+ 'exclam',
+ 'quotedbl',
+ 'numbersign',
+ 'dollar',
+ 'percent',
+ 'ampersand',
+ 'quotesingle',
+ 'parenleft',
+ 'parenright',
+ 'asterisk',
+ 'plus',
+ 'comma',
+ 'hyphen',
+ 'period',
+ 'slash',
+ 'zero',
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five',
+ 'six',
+ 'seven',
+ 'eight',
+ 'nine',
+ 'colon',
+ 'semicolon',
+ 'less',
+ 'equal',
+ 'greater',
+ 'question',
+ 'at',
+ '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',
+ 'bracketleft',
+ 'backslash',
+ 'bracketright',
+ 'asciicircum',
+ 'underscore',
+ 'grave',
+ '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',
+ 'braceleft',
+ 'bar',
+ 'braceright',
+ 'asciitilde',
+ 'Adieresis',
+ 'Aring',
+ 'Ccedilla',
+ 'Eacute',
+ 'Ntilde',
+ 'Odieresis',
+ 'Udieresis',
+ 'aacute',
+ 'agrave',
+ 'acircumflex',
+ 'adieresis',
+ 'atilde',
+ 'aring',
+ 'ccedilla',
+ 'eacute',
+ 'egrave',
+ 'ecircumflex',
+ 'edieresis',
+ 'iacute',
+ 'igrave',
+ 'icircumflex',
+ 'idieresis',
+ 'ntilde',
+ 'oacute',
+ 'ograve',
+ 'ocircumflex',
+ 'odieresis',
+ 'otilde',
+ 'uacute',
+ 'ugrave',
+ 'ucircumflex',
+ 'udieresis',
+ 'dagger',
+ 'degree',
+ 'cent',
+ 'sterling',
+ 'section',
+ 'bullet',
+ 'paragraph',
+ 'germandbls',
+ 'registered',
+ 'copyright',
+ 'trademark',
+ 'acute',
+ 'dieresis',
+ 'notequal',
+ 'AE',
+ 'Oslash',
+ 'infinity',
+ 'plusminus',
+ 'lessequal',
+ 'greaterequal',
+ 'yen',
+ 'mu',
+ 'partialdiff',
+ 'summation',
+ 'product',
+ 'pi',
+ 'integral',
+ 'ordfeminine',
+ 'ordmasculine',
+ 'Omega',
+ 'ae',
+ 'oslash',
+ 'questiondown',
+ 'exclamdown',
+ 'logicalnot',
+ 'radical',
+ 'florin',
+ 'approxequal',
+ 'Delta',
+ 'guillemotleft',
+ 'guillemotright',
+ 'ellipsis',
+ 'nonbreakingspace',
+ 'Agrave',
+ 'Atilde',
+ 'Otilde',
+ 'OE',
+ 'oe',
+ 'endash',
+ 'emdash',
+ 'quotedblleft',
+ 'quotedblright',
+ 'quoteleft',
+ 'quoteright',
+ 'divide',
+ 'lozenge',
+ 'ydieresis',
+ 'Ydieresis',
+ 'fraction',
+ 'currency',
+ 'guilsinglleft',
+ 'guilsinglright',
+ 'fi',
+ 'fl',
+ 'daggerdbl',
+ 'periodcentered',
+ 'quotesinglbase',
+ 'quotedblbase',
+ 'perthousand',
+ 'Acircumflex',
+ 'Ecircumflex',
+ 'Aacute',
+ 'Edieresis',
+ 'Egrave',
+ 'Iacute',
+ 'Icircumflex',
+ 'Idieresis',
+ 'Igrave',
+ 'Oacute',
+ 'Ocircumflex',
+ 'apple',
+ 'Ograve',
+ 'Uacute',
+ 'Ucircumflex',
+ 'Ugrave',
+ 'dotlessi',
+ 'circumflex',
+ 'tilde',
+ 'macron',
+ 'breve',
+ 'dotaccent',
+ 'ring',
+ 'cedilla',
+ 'hungarumlaut',
+ 'ogonek',
+ 'caron',
+ 'Lslash',
+ 'lslash',
+ 'Scaron',
+ 'scaron',
+ 'Zcaron',
+ 'zcaron',
+ 'brokenbar',
+ 'Eth',
+ 'eth',
+ 'Yacute',
+ 'yacute',
+ 'Thorn',
+ 'thorn',
+ 'minus',
+ 'multiply',
+ 'onesuperior',
+ 'twosuperior',
+ 'threesuperior',
+ 'onehalf',
+ 'onequarter',
+ 'threequarters',
+ 'franc',
+ 'Gbreve',
+ 'gbreve',
+ 'Idotaccent',
+ 'Scedilla',
+ 'scedilla',
+ 'Cacute',
+ 'cacute',
+ 'Ccaron',
+ 'ccaron',
+ 'dcroat'
+ ];
+ function adjustWidths(properties) {
+ if (!properties.fontMatrix) {
+ return;
+ }
+ if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
+ return;
+ }
+ // adjusting width to fontMatrix scale
+ var scale = 0.001 / properties.fontMatrix[0];
+ var glyphsWidths = properties.widths;
+ for (var glyph in glyphsWidths) {
+ glyphsWidths[glyph] *= scale;
+ }
+ properties.defaultWidth *= scale;
+ }
+ function adjustToUnicode(properties, builtInEncoding) {
+ if (properties.hasIncludedToUnicodeMap) {
+ return;
+ }
+ // The font dictionary has a `ToUnicode` entry.
+ if (properties.hasEncoding) {
+ return;
+ }
+ // The font dictionary has an `Encoding` entry.
+ if (builtInEncoding === properties.defaultEncoding) {
+ return;
+ }
+ // No point in trying to adjust `toUnicode` if the encodings match.
+ if (properties.toUnicode instanceof IdentityToUnicodeMap) {
+ return;
+ }
+ var toUnicode = [], glyphsUnicodeMap = getGlyphsUnicode();
+ for (var charCode in builtInEncoding) {
+ var glyphName = builtInEncoding[charCode];
+ var unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
+ if (unicode !== -1) {
+ toUnicode[charCode] = String.fromCharCode(unicode);
+ }
+ }
+ properties.toUnicode.amend(toUnicode);
+ }
+ function getFontType(type, subtype) {
+ switch (type) {
+ case 'Type1':
+ return subtype === 'Type1C' ? FontType.TYPE1C : FontType.TYPE1;
+ case 'CIDFontType0':
+ return subtype === 'CIDFontType0C' ? FontType.CIDFONTTYPE0C : FontType.CIDFONTTYPE0;
+ case 'OpenType':
+ return FontType.OPENTYPE;
+ case 'TrueType':
+ return FontType.TRUETYPE;
+ case 'CIDFontType2':
+ return FontType.CIDFONTTYPE2;
+ case 'MMType1':
+ return FontType.MMTYPE1;
+ case 'Type0':
+ return FontType.TYPE0;
+ default:
+ return FontType.UNKNOWN;
+ }
+ }
+ // Some bad PDF generators, e.g. Scribus PDF, include glyph names
+ // in a 'uniXXXX' format -- attempting to recover proper ones.
+ function recoverGlyphName(name, glyphsUnicodeMap) {
+ if (glyphsUnicodeMap[name] !== undefined) {
+ return name;
+ }
+ // The glyph name is non-standard, trying to recover.
+ var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap);
+ if (unicode !== -1) {
+ for (var key in glyphsUnicodeMap) {
+ if (glyphsUnicodeMap[key] === unicode) {
+ return key;
+ }
+ }
+ }
+ info('Unable to recover a standard glyph name for: ' + name);
+ return name;
+ }
+ var Glyph = function GlyphClosure() {
+ function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont) {
+ this.fontChar = fontChar;
+ this.unicode = unicode;
+ this.accent = accent;
+ this.width = width;
+ this.vmetric = vmetric;
+ this.operatorListId = operatorListId;
+ this.isSpace = isSpace;
+ this.isInFont = isInFont;
+ }
+ Glyph.prototype.matchesForCache = function (fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont) {
+ return this.fontChar === fontChar && this.unicode === unicode && this.accent === accent && this.width === width && this.vmetric === vmetric && this.operatorListId === operatorListId && this.isSpace === isSpace && this.isInFont === isInFont;
+ };
+ return Glyph;
+ }();
+ var ToUnicodeMap = function ToUnicodeMapClosure() {
+ function ToUnicodeMap(cmap) {
+ // The elements of this._map can be integers or strings, depending on how
+ // |cmap| was created.
+ this._map = cmap;
+ }
+ ToUnicodeMap.prototype = {
+ get length() {
+ return this._map.length;
+ },
+ forEach: function (callback) {
+ for (var charCode in this._map) {
+ callback(charCode, this._map[charCode].charCodeAt(0));
+ }
+ },
+ has: function (i) {
+ return this._map[i] !== undefined;
+ },
+ get: function (i) {
+ return this._map[i];
+ },
+ charCodeOf: function (v) {
+ return this._map.indexOf(v);
+ },
+ amend: function (map) {
+ for (var charCode in map) {
+ this._map[charCode] = map[charCode];
+ }
+ }
+ };
+ return ToUnicodeMap;
+ }();
+ var IdentityToUnicodeMap = function IdentityToUnicodeMapClosure() {
+ function IdentityToUnicodeMap(firstChar, lastChar) {
+ this.firstChar = firstChar;
+ this.lastChar = lastChar;
+ }
+ IdentityToUnicodeMap.prototype = {
+ get length() {
+ return this.lastChar + 1 - this.firstChar;
+ },
+ forEach: function (callback) {
+ for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
+ callback(i, i);
+ }
+ },
+ has: function (i) {
+ return this.firstChar <= i && i <= this.lastChar;
+ },
+ get: function (i) {
+ if (this.firstChar <= i && i <= this.lastChar) {
+ return String.fromCharCode(i);
+ }
+ return undefined;
+ },
+ charCodeOf: function (v) {
+ return isInt(v) && v >= this.firstChar && v <= this.lastChar ? v : -1;
+ },
+ amend: function (map) {
+ error('Should not call amend()');
+ }
+ };
+ return IdentityToUnicodeMap;
+ }();
+ var OpenTypeFileBuilder = function OpenTypeFileBuilderClosure() {
+ function writeInt16(dest, offset, num) {
+ dest[offset] = num >> 8 & 0xFF;
+ dest[offset + 1] = num & 0xFF;
+ }
+ function writeInt32(dest, offset, num) {
+ dest[offset] = num >> 24 & 0xFF;
+ dest[offset + 1] = num >> 16 & 0xFF;
+ dest[offset + 2] = num >> 8 & 0xFF;
+ dest[offset + 3] = num & 0xFF;
+ }
+ function writeData(dest, offset, data) {
+ var i, ii;
+ if (data instanceof Uint8Array) {
+ dest.set(data, offset);
+ } else if (typeof data === 'string') {
+ for (i = 0, ii = data.length; i < ii; i++) {
+ dest[offset++] = data.charCodeAt(i) & 0xFF;
+ }
+ } else {
+ // treating everything else as array
+ for (i = 0, ii = data.length; i < ii; i++) {
+ dest[offset++] = data[i] & 0xFF;
+ }
+ }
+ }
+ function OpenTypeFileBuilder(sfnt) {
+ this.sfnt = sfnt;
+ this.tables = Object.create(null);
+ }
+ OpenTypeFileBuilder.getSearchParams = function OpenTypeFileBuilder_getSearchParams(entriesCount, entrySize) {
+ var maxPower2 = 1, log2 = 0;
+ while ((maxPower2 ^ entriesCount) > maxPower2) {
+ maxPower2 <<= 1;
+ log2++;
+ }
+ var searchRange = maxPower2 * entrySize;
+ return {
+ range: searchRange,
+ entry: log2,
+ rangeShift: entrySize * entriesCount - searchRange
+ };
+ };
+ var OTF_HEADER_SIZE = 12;
+ var OTF_TABLE_ENTRY_SIZE = 16;
+ OpenTypeFileBuilder.prototype = {
+ toArray: function OpenTypeFileBuilder_toArray() {
+ var sfnt = this.sfnt;
+ // Tables needs to be written by ascendant alphabetic order
+ var tables = this.tables;
+ var tablesNames = Object.keys(tables);
+ tablesNames.sort();
+ var numTables = tablesNames.length;
+ var i, j, jj, table, tableName;
+ // layout the tables data
+ var offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE;
+ var tableOffsets = [offset];
+ for (i = 0; i < numTables; i++) {
+ table = tables[tablesNames[i]];
+ var paddedLength = (table.length + 3 & ~3) >>> 0;
+ offset += paddedLength;
+ tableOffsets.push(offset);
+ }
+ var file = new Uint8Array(offset);
+ // write the table data first (mostly for checksum)
+ for (i = 0; i < numTables; i++) {
+ table = tables[tablesNames[i]];
+ writeData(file, tableOffsets[i], table);
+ }
+ // sfnt version (4 bytes)
+ if (sfnt === 'true') {
+ // Windows hates the Mac TrueType sfnt version number
+ sfnt = string32(0x00010000);
+ }
+ file[0] = sfnt.charCodeAt(0) & 0xFF;
+ file[1] = sfnt.charCodeAt(1) & 0xFF;
+ file[2] = sfnt.charCodeAt(2) & 0xFF;
+ file[3] = sfnt.charCodeAt(3) & 0xFF;
+ // numTables (2 bytes)
+ writeInt16(file, 4, numTables);
+ var searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16);
+ // searchRange (2 bytes)
+ writeInt16(file, 6, searchParams.range);
+ // entrySelector (2 bytes)
+ writeInt16(file, 8, searchParams.entry);
+ // rangeShift (2 bytes)
+ writeInt16(file, 10, searchParams.rangeShift);
+ offset = OTF_HEADER_SIZE;
+ // writing table entries
+ for (i = 0; i < numTables; i++) {
+ tableName = tablesNames[i];
+ file[offset] = tableName.charCodeAt(0) & 0xFF;
+ file[offset + 1] = tableName.charCodeAt(1) & 0xFF;
+ file[offset + 2] = tableName.charCodeAt(2) & 0xFF;
+ file[offset + 3] = tableName.charCodeAt(3) & 0xFF;
+ // checksum
+ var checksum = 0;
+ for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) {
+ var quad = readUint32(file, j);
+ checksum = checksum + quad >>> 0;
+ }
+ writeInt32(file, offset + 4, checksum);
+ // offset
+ writeInt32(file, offset + 8, tableOffsets[i]);
+ // length
+ writeInt32(file, offset + 12, tables[tableName].length);
+ offset += OTF_TABLE_ENTRY_SIZE;
+ }
+ return file;
+ },
+ addTable: function OpenTypeFileBuilder_addTable(tag, data) {
+ if (tag in this.tables) {
+ throw new Error('Table ' + tag + ' already exists');
+ }
+ this.tables[tag] = data;
+ }
+ };
+ return OpenTypeFileBuilder;
+ }();
+ // Problematic Unicode characters in the fonts that needs to be moved to avoid
+ // issues when they are painted on the canvas, e.g. complex-script shaping or
+ // control/whitespace characters. The ranges are listed in pairs: the first item
+ // is a code of the first problematic code, the second one is the next
+ // non-problematic code. The ranges must be in sorted order.
+ var ProblematicCharRanges = new Int32Array([
+ // Control characters.
+ 0x0000,
+ 0x0020,
+ 0x007F,
+ 0x00A1,
+ 0x00AD,
+ 0x00AE,
+ // Chars that is used in complex-script shaping.
+ 0x0600,
+ 0x0780,
+ 0x08A0,
+ 0x10A0,
+ 0x1780,
+ 0x1800,
+ 0x1C00,
+ 0x1C50,
+ // General punctuation chars.
+ 0x2000,
+ 0x2010,
+ 0x2011,
+ 0x2012,
+ 0x2028,
+ 0x2030,
+ 0x205F,
+ 0x2070,
+ 0x25CC,
+ 0x25CD,
+ 0x3000,
+ 0x3001,
+ // Chars that is used in complex-script shaping.
+ 0xAA60,
+ 0xAA80,
+ // Specials Unicode block.
+ 0xFFF0,
+ 0x10000
+ ]);
+ /**
+ * 'Font' is the class the outside world should use, it encapsulate all the font
+ * decoding logics whatever type it is (assuming the font type is supported).
+ *
+ * For example to read a Type1 font and to attach it to the document:
+ * var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
+ * type1Font.bind();
+ */
+ var Font = function FontClosure() {
+ function Font(name, file, properties) {
+ var charCode, glyphName, unicode;
+ this.name = name;
+ this.loadedName = properties.loadedName;
+ this.isType3Font = properties.isType3Font;
+ this.sizes = [];
+ this.missingFile = false;
+ this.glyphCache = Object.create(null);
+ var names = name.split('+');
+ names = names.length > 1 ? names[1] : names[0];
+ names = names.split(/[-,_]/g)[0];
+ this.isSerifFont = !!(properties.flags & FontFlags.Serif);
+ this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
+ this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
+ var type = properties.type;
+ var subtype = properties.subtype;
+ this.type = type;
+ this.fallbackName = this.isMonospace ? 'monospace' : this.isSerifFont ? 'serif' : 'sans-serif';
+ this.differences = properties.differences;
+ this.widths = properties.widths;
+ this.defaultWidth = properties.defaultWidth;
+ this.composite = properties.composite;
+ this.wideChars = properties.wideChars;
+ this.cMap = properties.cMap;
+ this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
+ this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
+ this.fontMatrix = properties.fontMatrix;
+ this.bbox = properties.bbox;
+ this.toUnicode = properties.toUnicode;
+ this.toFontChar = [];
+ if (properties.type === 'Type3') {
+ for (charCode = 0; charCode < 256; charCode++) {
+ this.toFontChar[charCode] = this.differences[charCode] || properties.defaultEncoding[charCode];
+ }
+ this.fontType = FontType.TYPE3;
+ return;
+ }
+ this.cidEncoding = properties.cidEncoding;
+ this.vertical = properties.vertical;
+ if (this.vertical) {
+ this.vmetrics = properties.vmetrics;
+ this.defaultVMetrics = properties.defaultVMetrics;
+ }
+ var glyphsUnicodeMap;
+ if (!file || file.isEmpty) {
+ if (file) {
+ // Some bad PDF generators will include empty font files,
+ // attempting to recover by assuming that no file exists.
+ warn('Font file is empty in "' + name + '" (' + this.loadedName + ')');
+ }
+ this.missingFile = true;
+ // The file data is not specified. Trying to fix the font name
+ // to be used with the canvas.font.
+ var fontName = name.replace(/[,_]/g, '-');
+ var stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap();
+ var isStandardFont = !!stdFontMap[fontName] || !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
+ fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
+ this.bold = fontName.search(/bold/gi) !== -1;
+ this.italic = fontName.search(/oblique/gi) !== -1 || fontName.search(/italic/gi) !== -1;
+ // Use 'name' instead of 'fontName' here because the original
+ // name ArialBlack for example will be replaced by Helvetica.
+ this.black = name.search(/Black/g) !== -1;
+ // if at least one width is present, remeasure all chars when exists
+ this.remeasure = Object.keys(this.widths).length > 0;
+ if (isStandardFont && type === 'CIDFontType2' && properties.cidEncoding.indexOf('Identity-') === 0) {
+ var GlyphMapForStandardFonts = getGlyphMapForStandardFonts();
+ // Standard fonts might be embedded as CID font without glyph mapping.
+ // Building one based on GlyphMapForStandardFonts.
+ var map = [];
+ for (charCode in GlyphMapForStandardFonts) {
+ map[+charCode] = GlyphMapForStandardFonts[charCode];
+ }
+ if (/ArialBlack/i.test(name)) {
+ var SupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack();
+ for (charCode in SupplementalGlyphMapForArialBlack) {
+ map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
+ }
+ }
+ var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
+ if (!isIdentityUnicode) {
+ this.toUnicode.forEach(function (charCode, unicodeCharCode) {
+ map[+charCode] = unicodeCharCode;
+ });
+ }
+ this.toFontChar = map;
+ this.toUnicode = new ToUnicodeMap(map);
+ } else if (/Symbol/i.test(fontName)) {
+ this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(), properties.differences);
+ } else if (/Dingbats/i.test(fontName)) {
+ if (/Wingdings/i.test(name)) {
+ warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
+ }
+ this.toFontChar = buildToFontChar(ZapfDingbatsEncoding, getDingbatsGlyphsUnicode(), properties.differences);
+ } else if (isStandardFont) {
+ this.toFontChar = buildToFontChar(properties.defaultEncoding, getGlyphsUnicode(), properties.differences);
+ } else {
+ glyphsUnicodeMap = getGlyphsUnicode();
+ this.toUnicode.forEach(function (charCode, unicodeCharCode) {
+ if (!this.composite) {
+ glyphName = properties.differences[charCode] || properties.defaultEncoding[charCode];
+ unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
+ if (unicode !== -1) {
+ unicodeCharCode = unicode;
+ }
+ }
+ this.toFontChar[charCode] = unicodeCharCode;
+ }.bind(this));
+ }
+ this.loadedName = fontName.split('-')[0];
+ this.loading = false;
+ this.fontType = getFontType(type, subtype);
+ return;
+ }
+ // Some fonts might use wrong font types for Type1C or CIDFontType0C
+ if (subtype === 'Type1C') {
+ if (type !== 'Type1' && type !== 'MMType1') {
+ // Some TrueType fonts by mistake claim Type1C
+ if (isTrueTypeFile(file)) {
+ subtype = 'TrueType';
+ } else {
+ type = 'Type1';
+ }
+ } else if (isOpenTypeFile(file)) {
+ // Sometimes the type/subtype can be a complete lie (see issue7598.pdf).
+ type = subtype = 'OpenType';
+ }
+ }
+ if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
+ type = 'CIDFontType0';
+ }
+ if (subtype === 'OpenType') {
+ type = 'OpenType';
+ }
+ // Some CIDFontType0C fonts by mistake claim CIDFontType0.
+ if (type === 'CIDFontType0') {
+ if (isType1File(file)) {
+ subtype = 'CIDFontType0';
+ } else if (isOpenTypeFile(file)) {
+ // Sometimes the type/subtype can be a complete lie (see issue6782.pdf).
+ type = subtype = 'OpenType';
+ } else {
+ subtype = 'CIDFontType0C';
+ }
+ }
+ var data;
+ switch (type) {
+ case 'MMType1':
+ info('MMType1 font (' + name + '), falling back to Type1.');
+ case 'Type1':
+ case 'CIDFontType0':
+ this.mimetype = 'font/opentype';
+ var cff = subtype === 'Type1C' || subtype === 'CIDFontType0C' ? new CFFFont(file, properties) : new Type1Font(name, file, properties);
+ adjustWidths(properties);
+ // Wrap the CFF data inside an OTF font file
+ data = this.convert(name, cff, properties);
+ break;
+ case 'OpenType':
+ case 'TrueType':
+ case 'CIDFontType2':
+ this.mimetype = 'font/opentype';
+ // Repair the TrueType file. It is can be damaged in the point of
+ // view of the sanitizer
+ data = this.checkAndRepair(name, file, properties);
+ if (this.isOpenType) {
+ adjustWidths(properties);
+ type = 'OpenType';
+ }
+ break;
+ default:
+ error('Font ' + type + ' is not supported');
+ break;
+ }
+ this.data = data;
+ this.fontType = getFontType(type, subtype);
+ // Transfer some properties again that could change during font conversion
+ this.fontMatrix = properties.fontMatrix;
+ this.widths = properties.widths;
+ this.defaultWidth = properties.defaultWidth;
+ this.toUnicode = properties.toUnicode;
+ this.encoding = properties.baseEncoding;
+ this.seacMap = properties.seacMap;
+ this.loading = true;
+ }
+ Font.getFontID = function () {
+ var ID = 1;
+ return function Font_getFontID() {
+ return String(ID++);
+ };
+ }();
+ function int16(b0, b1) {
+ return (b0 << 8) + b1;
+ }
+ function signedInt16(b0, b1) {
+ var value = (b0 << 8) + b1;
+ return value & 1 << 15 ? value - 0x10000 : value;
+ }
+ function int32(b0, b1, b2, b3) {
+ return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
+ }
+ function string16(value) {
+ return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
+ }
+ function safeString16(value) {
+ // clamp value to the 16-bit int range
+ value = value > 0x7FFF ? 0x7FFF : value < -0x8000 ? -0x8000 : value;
+ return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
+ }
+ function isTrueTypeFile(file) {
+ var header = file.peekBytes(4);
+ return readUint32(header, 0) === 0x00010000;
+ }
+ function isOpenTypeFile(file) {
+ var header = file.peekBytes(4);
+ return bytesToString(header) === 'OTTO';
+ }
+ function isType1File(file) {
+ var header = file.peekBytes(2);
+ // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21).
+ if (header[0] === 0x25 && header[1] === 0x21) {
+ return true;
+ }
+ // ... obviously some fonts violate that part of the specification,
+ // please refer to the comment in |Type1Font| below.
+ if (header[0] === 0x80 && header[1] === 0x01) {
+ // pfb file header.
+ return true;
+ }
+ return false;
+ }
+ function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
+ var toFontChar = [], unicode;
+ for (var i = 0, ii = encoding.length; i < ii; i++) {
+ unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
+ if (unicode !== -1) {
+ toFontChar[i] = unicode;
+ }
+ }
+ for (var charCode in differences) {
+ unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
+ if (unicode !== -1) {
+ toFontChar[+charCode] = unicode;
+ }
+ }
+ return toFontChar;
+ }
+ /**
+ * Helper function for |adjustMapping|.
+ * @return {boolean}
+ */
+ function isProblematicUnicodeLocation(code) {
+ // Using binary search to find a range start.
+ var i = 0, j = ProblematicCharRanges.length - 1;
+ while (i < j) {
+ var c = i + j + 1 >> 1;
+ if (code < ProblematicCharRanges[c]) {
+ j = c - 1;
+ } else {
+ i = c;
+ }
+ }
+ // Even index means code in problematic range.
+ return !(i & 1);
+ }
+ /**
+ * Rebuilds the char code to glyph ID map by trying to replace the char codes
+ * with their unicode value. It also moves char codes that are in known
+ * problematic locations.
+ * @return {Object} Two properties:
+ * 'toFontChar' - maps original char codes(the value that will be read
+ * from commands such as show text) to the char codes that will be used in the
+ * font that we build
+ * 'charCodeToGlyphId' - maps the new font char codes to glyph ids
+ */
+ function adjustMapping(charCodeToGlyphId, properties) {
+ var toUnicode = properties.toUnicode;
+ var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
+ var isIdentityUnicode = properties.toUnicode instanceof IdentityToUnicodeMap;
+ var newMap = Object.create(null);
+ var toFontChar = [];
+ var usedFontCharCodes = [];
+ var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
+ for (var originalCharCode in charCodeToGlyphId) {
+ originalCharCode |= 0;
+ var glyphId = charCodeToGlyphId[originalCharCode];
+ var fontCharCode = originalCharCode;
+ // First try to map the value to a unicode position if a non identity map
+ // was created.
+ if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
+ var unicode = toUnicode.get(fontCharCode);
+ // TODO: Try to map ligatures to the correct spot.
+ if (unicode.length === 1) {
+ fontCharCode = unicode.charCodeAt(0);
+ }
+ }
+ // Try to move control characters, special characters and already mapped
+ // characters to the private use area since they will not be drawn by
+ // canvas if left in their current position. Also, move characters if the
+ // font was symbolic and there is only an identity unicode map since the
+ // characters probably aren't in the correct position (fixes an issue
+ // with firefox and thuluthfont).
+ if ((usedFontCharCodes[fontCharCode] !== undefined || isProblematicUnicodeLocation(fontCharCode) || isSymbolic && isIdentityUnicode) && nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) {
+ // Room left.
+ // Loop to try and find a free spot in the private use area.
+ do {
+ fontCharCode = nextAvailableFontCharCode++;
+ if (SKIP_PRIVATE_USE_RANGE_F000_TO_F01F && fontCharCode === 0xF000) {
+ fontCharCode = 0xF020;
+ nextAvailableFontCharCode = fontCharCode + 1;
+ }
+ } while (usedFontCharCodes[fontCharCode] !== undefined && nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END);
+ }
+ newMap[fontCharCode] = glyphId;
+ toFontChar[originalCharCode] = fontCharCode;
+ usedFontCharCodes[fontCharCode] = true;
+ }
+ return {
+ toFontChar: toFontChar,
+ charCodeToGlyphId: newMap,
+ nextAvailableFontCharCode: nextAvailableFontCharCode
+ };
+ }
+ function getRanges(glyphs, numGlyphs) {
+ // Array.sort() sorts by characters, not numerically, so convert to an
+ // array of characters.
+ var codes = [];
+ for (var charCode in glyphs) {
+ // Remove an invalid glyph ID mappings to make OTS happy.
+ if (glyphs[charCode] >= numGlyphs) {
+ continue;
+ }
+ codes.push({
+ fontCharCode: charCode | 0,
+ glyphId: glyphs[charCode]
+ });
+ }
+ codes.sort(function fontGetRangesSort(a, b) {
+ return a.fontCharCode - b.fontCharCode;
+ });
+ // Split the sorted codes into ranges.
+ var ranges = [];
+ var length = codes.length;
+ for (var n = 0; n < length;) {
+ var start = codes[n].fontCharCode;
+ var codeIndices = [codes[n].glyphId];
+ ++n;
+ var end = start;
+ while (n < length && end + 1 === codes[n].fontCharCode) {
+ codeIndices.push(codes[n].glyphId);
+ ++end;
+ ++n;
+ if (end === 0xFFFF) {
+ break;
+ }
+ }
+ ranges.push([
+ start,
+ end,
+ codeIndices
+ ]);
+ }
+ return ranges;
+ }
+ function createCmapTable(glyphs, numGlyphs) {
+ var ranges = getRanges(glyphs, numGlyphs);
+ var numTables = ranges[ranges.length - 1][1] > 0xFFFF ? 2 : 1;
+ var cmap = '\x00\x00' + // version
+ string16(numTables) + // numTables
+ '\x00\x03' + // platformID
+ '\x00\x01' + // encodingID
+ string32(4 + numTables * 8);
+ // start of the table record
+ var i, ii, j, jj;
+ for (i = ranges.length - 1; i >= 0; --i) {
+ if (ranges[i][0] <= 0xFFFF) {
+ break;
+ }
+ }
+ var bmpLength = i + 1;
+ if (ranges[i][0] < 0xFFFF && ranges[i][1] === 0xFFFF) {
+ ranges[i][1] = 0xFFFE;
+ }
+ var trailingRangesCount = ranges[i][1] < 0xFFFF ? 1 : 0;
+ var segCount = bmpLength + trailingRangesCount;
+ var searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
+ // Fill up the 4 parallel arrays describing the segments.
+ var startCount = '';
+ var endCount = '';
+ var idDeltas = '';
+ var idRangeOffsets = '';
+ var glyphsIds = '';
+ var bias = 0;
+ var range, start, end, codes;
+ for (i = 0, ii = bmpLength; i < ii; i++) {
+ range = ranges[i];
+ start = range[0];
+ end = range[1];
+ startCount += string16(start);
+ endCount += string16(end);
+ codes = range[2];
+ var contiguous = true;
+ for (j = 1, jj = codes.length; j < jj; ++j) {
+ if (codes[j] !== codes[j - 1] + 1) {
+ contiguous = false;
+ break;
+ }
+ }
+ if (!contiguous) {
+ var offset = (segCount - i) * 2 + bias * 2;
+ bias += end - start + 1;
+ idDeltas += string16(0);
+ idRangeOffsets += string16(offset);
+ for (j = 0, jj = codes.length; j < jj; ++j) {
+ glyphsIds += string16(codes[j]);
+ }
+ } else {
+ var startCode = codes[0];
+ idDeltas += string16(startCode - start & 0xFFFF);
+ idRangeOffsets += string16(0);
+ }
+ }
+ if (trailingRangesCount > 0) {
+ endCount += '\xFF\xFF';
+ startCount += '\xFF\xFF';
+ idDeltas += '\x00\x01';
+ idRangeOffsets += '\x00\x00';
+ }
+ var format314 = '\x00\x00' + // language
+ string16(2 * segCount) + string16(searchParams.range) + string16(searchParams.entry) + string16(searchParams.rangeShift) + endCount + '\x00\x00' + startCount + idDeltas + idRangeOffsets + glyphsIds;
+ var format31012 = '';
+ var header31012 = '';
+ if (numTables > 1) {
+ cmap += '\x00\x03' + // platformID
+ '\x00\x0A' + // encodingID
+ string32(4 + numTables * 8 + 4 + format314.length);
+ // start of the table record
+ format31012 = '';
+ for (i = 0, ii = ranges.length; i < ii; i++) {
+ range = ranges[i];
+ start = range[0];
+ codes = range[2];
+ var code = codes[0];
+ for (j = 1, jj = codes.length; j < jj; ++j) {
+ if (codes[j] !== codes[j - 1] + 1) {
+ end = range[0] + j - 1;
+ format31012 += string32(start) + // startCharCode
+ string32(end) + // endCharCode
+ string32(code);
+ // startGlyphID
+ start = end + 1;
+ code = codes[j];
+ }
+ }
+ format31012 += string32(start) + // startCharCode
+ string32(range[1]) + // endCharCode
+ string32(code);
+ }
+ // startGlyphID
+ header31012 = '\x00\x0C' + // format
+ '\x00\x00' + // reserved
+ string32(format31012.length + 16) + // length
+ '\x00\x00\x00\x00' + // language
+ string32(format31012.length / 12);
+ }
+ // nGroups
+ return cmap + '\x00\x04' + // format
+ string16(format314.length + 4) + // length
+ format314 + header31012 + format31012;
+ }
+ function validateOS2Table(os2) {
+ var stream = new Stream(os2.data);
+ var version = stream.getUint16();
+ // TODO verify all OS/2 tables fields, but currently we validate only those
+ // that give us issues
+ stream.getBytes(60);
+ // skipping type, misc sizes, panose, unicode ranges
+ var selection = stream.getUint16();
+ if (version < 4 && selection & 0x0300) {
+ return false;
+ }
+ var firstChar = stream.getUint16();
+ var lastChar = stream.getUint16();
+ if (firstChar > lastChar) {
+ return false;
+ }
+ stream.getBytes(6);
+ // skipping sTypoAscender/Descender/LineGap
+ var usWinAscent = stream.getUint16();
+ if (usWinAscent === 0) {
+ // makes font unreadable by windows
+ return false;
+ }
+ // OS/2 appears to be valid, resetting some fields
+ os2.data[8] = os2.data[9] = 0;
+ // IE rejects fonts if fsType != 0
+ return true;
+ }
+ function createOS2Table(properties, charstrings, override) {
+ override = override || {
+ unitsPerEm: 0,
+ yMax: 0,
+ yMin: 0,
+ ascent: 0,
+ descent: 0
+ };
+ var ulUnicodeRange1 = 0;
+ var ulUnicodeRange2 = 0;
+ var ulUnicodeRange3 = 0;
+ var ulUnicodeRange4 = 0;
+ var firstCharIndex = null;
+ var lastCharIndex = 0;
+ if (charstrings) {
+ for (var code in charstrings) {
+ code |= 0;
+ if (firstCharIndex > code || !firstCharIndex) {
+ firstCharIndex = code;
+ }
+ if (lastCharIndex < code) {
+ lastCharIndex = code;
+ }
+ var position = getUnicodeRangeFor(code);
+ if (position < 32) {
+ ulUnicodeRange1 |= 1 << position;
+ } else if (position < 64) {
+ ulUnicodeRange2 |= 1 << position - 32;
+ } else if (position < 96) {
+ ulUnicodeRange3 |= 1 << position - 64;
+ } else if (position < 123) {
+ ulUnicodeRange4 |= 1 << position - 96;
+ } else {
+ error('Unicode ranges Bits > 123 are reserved for internal usage');
+ }
+ }
+ } else {
+ // TODO
+ firstCharIndex = 0;
+ lastCharIndex = 255;
+ }
+ var bbox = properties.bbox || [
+ 0,
+ 0,
+ 0,
+ 0
+ ];
+ var unitsPerEm = override.unitsPerEm || 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
+ // if the font units differ to the PDF glyph space units
+ // then scale up the values
+ var scale = properties.ascentScaled ? 1.0 : unitsPerEm / PDF_GLYPH_SPACE_UNITS;
+ var typoAscent = override.ascent || Math.round(scale * (properties.ascent || bbox[3]));
+ var typoDescent = override.descent || Math.round(scale * (properties.descent || bbox[1]));
+ if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
+ typoDescent = -typoDescent;
+ }
+ // fixing incorrect descent
+ var winAscent = override.yMax || typoAscent;
+ var winDescent = -override.yMin || -typoDescent;
+ return '\x00\x03' + // version
+ '\x02\x24' + // xAvgCharWidth
+ '\x01\xF4' + // usWeightClass
+ '\x00\x05' + // usWidthClass
+ '\x00\x00' + // fstype (0 to let the font loads via font-face on IE)
+ '\x02\x8A' + // ySubscriptXSize
+ '\x02\xBB' + // ySubscriptYSize
+ '\x00\x00' + // ySubscriptXOffset
+ '\x00\x8C' + // ySubscriptYOffset
+ '\x02\x8A' + // ySuperScriptXSize
+ '\x02\xBB' + // ySuperScriptYSize
+ '\x00\x00' + // ySuperScriptXOffset
+ '\x01\xDF' + // ySuperScriptYOffset
+ '\x00\x31' + // yStrikeOutSize
+ '\x01\x02' + // yStrikeOutPosition
+ '\x00\x00' + // sFamilyClass
+ '\x00\x00\x06' + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + '\x00\x00\x00\x00\x00\x00' + // Panose
+ string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31)
+ string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63)
+ string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95)
+ string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127)
+ '\x2A\x32\x31\x2A' + // achVendID
+ string16(properties.italicAngle ? 1 : 0) + // fsSelection
+ string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex
+ string16(lastCharIndex || properties.lastChar) + // usLastCharIndex
+ string16(typoAscent) + // sTypoAscender
+ string16(typoDescent) + // sTypoDescender
+ '\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
+ string16(winAscent) + // usWinAscent
+ string16(winDescent) + // usWinDescent
+ '\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
+ '\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
+ string16(properties.xHeight) + // sxHeight
+ string16(properties.capHeight) + // sCapHeight
+ string16(0) + // usDefaultChar
+ string16(firstCharIndex || properties.firstChar) + // usBreakChar
+ '\x00\x03';
+ }
+ // usMaxContext
+ function createPostTable(properties) {
+ var angle = Math.floor(properties.italicAngle * Math.pow(2, 16));
+ return '\x00\x03\x00\x00' + // Version number
+ string32(angle) + // italicAngle
+ '\x00\x00' + // underlinePosition
+ '\x00\x00' + // underlineThickness
+ string32(properties.fixedPitch) + // isFixedPitch
+ '\x00\x00\x00\x00' + // minMemType42
+ '\x00\x00\x00\x00' + // maxMemType42
+ '\x00\x00\x00\x00' + // minMemType1
+ '\x00\x00\x00\x00';
+ }
+ // maxMemType1
+ function createNameTable(name, proto) {
+ if (!proto) {
+ proto = [
+ [],
+ []
+ ];
+ }
+ // no strings and unicode strings
+ var strings = [
+ proto[0][0] || 'Original licence',
+ // 0.Copyright
+ proto[0][1] || name,
+ // 1.Font family
+ proto[0][2] || 'Unknown',
+ // 2.Font subfamily (font weight)
+ proto[0][3] || 'uniqueID',
+ // 3.Unique ID
+ proto[0][4] || name,
+ // 4.Full font name
+ proto[0][5] || 'Version 0.11',
+ // 5.Version
+ proto[0][6] || '',
+ // 6.Postscript name
+ proto[0][7] || 'Unknown',
+ // 7.Trademark
+ proto[0][8] || 'Unknown',
+ // 8.Manufacturer
+ proto[0][9] || 'Unknown'
+ ];
+ // 9.Designer
+ // Mac want 1-byte per character strings while Windows want
+ // 2-bytes per character, so duplicate the names table
+ var stringsUnicode = [];
+ var i, ii, j, jj, str;
+ for (i = 0, ii = strings.length; i < ii; i++) {
+ str = proto[1][i] || strings[i];
+ var strBufUnicode = [];
+ for (j = 0, jj = str.length; j < jj; j++) {
+ strBufUnicode.push(string16(str.charCodeAt(j)));
+ }
+ stringsUnicode.push(strBufUnicode.join(''));
+ }
+ var names = [
+ strings,
+ stringsUnicode
+ ];
+ var platforms = [
+ '\x00\x01',
+ '\x00\x03'
+ ];
+ var encodings = [
+ '\x00\x00',
+ '\x00\x01'
+ ];
+ var languages = [
+ '\x00\x00',
+ '\x04\x09'
+ ];
+ var namesRecordCount = strings.length * platforms.length;
+ var nameTable = '\x00\x00' + // format
+ string16(namesRecordCount) + // Number of names Record
+ string16(namesRecordCount * 12 + 6);
+ // Storage
+ // Build the name records field
+ var strOffset = 0;
+ for (i = 0, ii = platforms.length; i < ii; i++) {
+ var strs = names[i];
+ for (j = 0, jj = strs.length; j < jj; j++) {
+ str = strs[j];
+ var nameRecord = platforms[i] + // platform ID
+ encodings[i] + // encoding ID
+ languages[i] + // language ID
+ string16(j) + // name ID
+ string16(str.length) + string16(strOffset);
+ nameTable += nameRecord;
+ strOffset += str.length;
+ }
+ }
+ nameTable += strings.join('') + stringsUnicode.join('');
+ return nameTable;
+ }
+ Font.prototype = {
+ name: null,
+ font: null,
+ mimetype: null,
+ encoding: null,
+ get renderer() {
+ var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
+ return shadow(this, 'renderer', renderer);
+ },
+ exportData: function Font_exportData() {
+ // TODO remove enumerating of the properties, e.g. hardcode exact names.
+ var data = {};
+ for (var i in this) {
+ if (this.hasOwnProperty(i)) {
+ data[i] = this[i];
+ }
+ }
+ return data;
+ },
+ checkAndRepair: function Font_checkAndRepair(name, font, properties) {
+ function readTableEntry(file) {
+ var tag = bytesToString(file.getBytes(4));
+ var checksum = file.getInt32() >>> 0;
+ var offset = file.getInt32() >>> 0;
+ var length = file.getInt32() >>> 0;
+ // Read the table associated data
+ var previousPosition = file.pos;
+ file.pos = file.start ? file.start : 0;
+ file.skip(offset);
+ var data = file.getBytes(length);
+ file.pos = previousPosition;
+ if (tag === 'head') {
+ // clearing checksum adjustment
+ data[8] = data[9] = data[10] = data[11] = 0;
+ data[17] |= 0x20;
+ }
+ //Set font optimized for cleartype flag
+ return {
+ tag: tag,
+ checksum: checksum,
+ length: length,
+ offset: offset,
+ data: data
+ };
+ }
+ function readOpenTypeHeader(ttf) {
+ return {
+ version: bytesToString(ttf.getBytes(4)),
+ numTables: ttf.getUint16(),
+ searchRange: ttf.getUint16(),
+ entrySelector: ttf.getUint16(),
+ rangeShift: ttf.getUint16()
+ };
+ }
+ /**
+ * Read the appropriate subtable from the cmap according to 9.6.6.4 from
+ * PDF spec
+ */
+ function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
+ if (!cmap) {
+ warn('No cmap table available.');
+ return {
+ platformId: -1,
+ encodingId: -1,
+ mappings: [],
+ hasShortCmap: false
+ };
+ }
+ var segment;
+ var start = (font.start ? font.start : 0) + cmap.offset;
+ font.pos = start;
+ var version = font.getUint16();
+ var numTables = font.getUint16();
+ var potentialTable;
+ var canBreak = false;
+ // There's an order of preference in terms of which cmap subtable to
+ // use:
+ // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table
+ // - symbolic fonts the preference is a 3,0 table then a 1,0 table
+ // The following takes advantage of the fact that the tables are sorted
+ // to work.
+ for (var i = 0; i < numTables; i++) {
+ var platformId = font.getUint16();
+ var encodingId = font.getUint16();
+ var offset = font.getInt32() >>> 0;
+ var useTable = false;
+ if (platformId === 0 && encodingId === 0) {
+ useTable = true;
+ } else // Continue the loop since there still may be a higher priority
+ // table.
+ if (platformId === 1 && encodingId === 0) {
+ useTable = true;
+ } else // Continue the loop since there still may be a higher priority
+ // table.
+ if (platformId === 3 && encodingId === 1 && (!isSymbolicFont && hasEncoding || !potentialTable)) {
+ useTable = true;
+ if (!isSymbolicFont) {
+ canBreak = true;
+ }
+ } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
+ useTable = true;
+ canBreak = true;
+ }
+ if (useTable) {
+ potentialTable = {
+ platformId: platformId,
+ encodingId: encodingId,
+ offset: offset
+ };
+ }
+ if (canBreak) {
+ break;
+ }
+ }
+ if (potentialTable) {
+ font.pos = start + potentialTable.offset;
+ }
+ if (!potentialTable || font.peekByte() === -1) {
+ warn('Could not find a preferred cmap table.');
+ return {
+ platformId: -1,
+ encodingId: -1,
+ mappings: [],
+ hasShortCmap: false
+ };
+ }
+ var format = font.getUint16();
+ var length = font.getUint16();
+ var language = font.getUint16();
+ var hasShortCmap = false;
+ var mappings = [];
+ var j, glyphId;
+ // TODO(mack): refactor this cmap subtable reading logic out
+ if (format === 0) {
+ for (j = 0; j < 256; j++) {
+ var index = font.getByte();
+ if (!index) {
+ continue;
+ }
+ mappings.push({
+ charCode: j,
+ glyphId: index
+ });
+ }
+ hasShortCmap = true;
+ } else if (format === 4) {
+ // re-creating the table in format 4 since the encoding
+ // might be changed
+ var segCount = font.getUint16() >> 1;
+ font.getBytes(6);
+ // skipping range fields
+ var segIndex, segments = [];
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments.push({ end: font.getUint16() });
+ }
+ font.getUint16();
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments[segIndex].start = font.getUint16();
+ }
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments[segIndex].delta = font.getUint16();
+ }
+ var offsetsCount = 0;
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segment = segments[segIndex];
+ var rangeOffset = font.getUint16();
+ if (!rangeOffset) {
+ segment.offsetIndex = -1;
+ continue;
+ }
+ var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
+ segment.offsetIndex = offsetIndex;
+ offsetsCount = Math.max(offsetsCount, offsetIndex + segment.end - segment.start + 1);
+ }
+ var offsets = [];
+ for (j = 0; j < offsetsCount; j++) {
+ offsets.push(font.getUint16());
+ }
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segment = segments[segIndex];
+ start = segment.start;
+ var end = segment.end;
+ var delta = segment.delta;
+ offsetIndex = segment.offsetIndex;
+ for (j = start; j <= end; j++) {
+ if (j === 0xFFFF) {
+ continue;
+ }
+ glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start];
+ glyphId = glyphId + delta & 0xFFFF;
+ if (glyphId === 0) {
+ continue;
+ }
+ mappings.push({
+ charCode: j,
+ glyphId: glyphId
+ });
+ }
+ }
+ } else if (format === 6) {
+ // Format 6 is a 2-bytes dense mapping, which means the font data
+ // lives glue together even if they are pretty far in the unicode
+ // table. (This looks weird, so I can have missed something), this
+ // works on Linux but seems to fails on Mac so let's rewrite the
+ // cmap table to a 3-1-4 style
+ var firstCode = font.getUint16();
+ var entryCount = font.getUint16();
+ for (j = 0; j < entryCount; j++) {
+ glyphId = font.getUint16();
+ var charCode = firstCode + j;
+ mappings.push({
+ charCode: charCode,
+ glyphId: glyphId
+ });
+ }
+ } else {
+ warn('cmap table has unsupported format: ' + format);
+ return {
+ platformId: -1,
+ encodingId: -1,
+ mappings: [],
+ hasShortCmap: false
+ };
+ }
+ // removing duplicate entries
+ mappings.sort(function (a, b) {
+ return a.charCode - b.charCode;
+ });
+ for (i = 1; i < mappings.length; i++) {
+ if (mappings[i - 1].charCode === mappings[i].charCode) {
+ mappings.splice(i, 1);
+ i--;
+ }
+ }
+ return {
+ platformId: potentialTable.platformId,
+ encodingId: potentialTable.encodingId,
+ mappings: mappings,
+ hasShortCmap: hasShortCmap
+ };
+ }
+ function sanitizeMetrics(font, header, metrics, numGlyphs) {
+ if (!header) {
+ if (metrics) {
+ metrics.data = null;
+ }
+ return;
+ }
+ font.pos = (font.start ? font.start : 0) + header.offset;
+ font.pos += header.length - 2;
+ var numOfMetrics = font.getUint16();
+ if (numOfMetrics > numGlyphs) {
+ info('The numOfMetrics (' + numOfMetrics + ') should not be ' + 'greater than the numGlyphs (' + numGlyphs + ')');
+ // Reduce numOfMetrics if it is greater than numGlyphs
+ numOfMetrics = numGlyphs;
+ header.data[34] = (numOfMetrics & 0xff00) >> 8;
+ header.data[35] = numOfMetrics & 0x00ff;
+ }
+ var numOfSidebearings = numGlyphs - numOfMetrics;
+ var numMissing = numOfSidebearings - (metrics.length - numOfMetrics * 4 >> 1);
+ if (numMissing > 0) {
+ // For each missing glyph, we set both the width and lsb to 0 (zero).
+ // Since we need to add two properties for each glyph, this explains
+ // the use of |numMissing * 2| when initializing the typed array.
+ var entries = new Uint8Array(metrics.length + numMissing * 2);
+ entries.set(metrics.data);
+ metrics.data = entries;
+ }
+ }
+ function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart, hintsValid) {
+ if (sourceEnd - sourceStart <= 12) {
+ // glyph with data less than 12 is invalid one
+ return 0;
+ }
+ var glyf = source.subarray(sourceStart, sourceEnd);
+ var contoursCount = glyf[0] << 8 | glyf[1];
+ if (contoursCount & 0x8000) {
+ // complex glyph, writing as is
+ dest.set(glyf, destStart);
+ return glyf.length;
+ }
+ var i, j = 10, flagsCount = 0;
+ for (i = 0; i < contoursCount; i++) {
+ var endPoint = glyf[j] << 8 | glyf[j + 1];
+ flagsCount = endPoint + 1;
+ j += 2;
+ }
+ // skipping instructions
+ var instructionsStart = j;
+ var instructionsLength = glyf[j] << 8 | glyf[j + 1];
+ j += 2 + instructionsLength;
+ var instructionsEnd = j;
+ // validating flags
+ var coordinatesLength = 0;
+ for (i = 0; i < flagsCount; i++) {
+ var flag = glyf[j++];
+ if (flag & 0xC0) {
+ // reserved flags must be zero, cleaning up
+ glyf[j - 1] = flag & 0x3F;
+ }
+ var xyLength = (flag & 2 ? 1 : flag & 16 ? 0 : 2) + (flag & 4 ? 1 : flag & 32 ? 0 : 2);
+ coordinatesLength += xyLength;
+ if (flag & 8) {
+ var repeat = glyf[j++];
+ i += repeat;
+ coordinatesLength += repeat * xyLength;
+ }
+ }
+ // glyph without coordinates will be rejected
+ if (coordinatesLength === 0) {
+ return 0;
+ }
+ var glyphDataLength = j + coordinatesLength;
+ if (glyphDataLength > glyf.length) {
+ // not enough data for coordinates
+ return 0;
+ }
+ if (!hintsValid && instructionsLength > 0) {
+ dest.set(glyf.subarray(0, instructionsStart), destStart);
+ dest.set([
+ 0,
+ 0
+ ], destStart + instructionsStart);
+ dest.set(glyf.subarray(instructionsEnd, glyphDataLength), destStart + instructionsStart + 2);
+ glyphDataLength -= instructionsLength;
+ if (glyf.length - glyphDataLength > 3) {
+ glyphDataLength = glyphDataLength + 3 & ~3;
+ }
+ return glyphDataLength;
+ }
+ if (glyf.length - glyphDataLength > 3) {
+ // truncating and aligning to 4 bytes the long glyph data
+ glyphDataLength = glyphDataLength + 3 & ~3;
+ dest.set(glyf.subarray(0, glyphDataLength), destStart);
+ return glyphDataLength;
+ }
+ // glyph data is fine
+ dest.set(glyf, destStart);
+ return glyf.length;
+ }
+ function sanitizeHead(head, numGlyphs, locaLength) {
+ var data = head.data;
+ // Validate version:
+ // Should always be 0x00010000
+ var version = int32(data[0], data[1], data[2], data[3]);
+ if (version >> 16 !== 1) {
+ info('Attempting to fix invalid version in head table: ' + version);
+ data[0] = 0;
+ data[1] = 1;
+ data[2] = 0;
+ data[3] = 0;
+ }
+ var indexToLocFormat = int16(data[50], data[51]);
+ if (indexToLocFormat < 0 || indexToLocFormat > 1) {
+ info('Attempting to fix invalid indexToLocFormat in head table: ' + indexToLocFormat);
+ // The value of indexToLocFormat should be 0 if the loca table
+ // consists of short offsets, and should be 1 if the loca table
+ // consists of long offsets.
+ //
+ // The number of entries in the loca table should be numGlyphs + 1.
+ //
+ // Using this information, we can work backwards to deduce if the
+ // size of each offset in the loca table, and thus figure out the
+ // appropriate value for indexToLocFormat.
+ var numGlyphsPlusOne = numGlyphs + 1;
+ if (locaLength === numGlyphsPlusOne << 1) {
+ // 0x0000 indicates the loca table consists of short offsets
+ data[50] = 0;
+ data[51] = 0;
+ } else if (locaLength === numGlyphsPlusOne << 2) {
+ // 0x0001 indicates the loca table consists of long offsets
+ data[50] = 0;
+ data[51] = 1;
+ } else {
+ warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
+ }
+ }
+ }
+ function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry) {
+ var itemSize, itemDecode, itemEncode;
+ if (isGlyphLocationsLong) {
+ itemSize = 4;
+ itemDecode = function fontItemDecodeLong(data, offset) {
+ return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
+ };
+ itemEncode = function fontItemEncodeLong(data, offset, value) {
+ data[offset] = value >>> 24 & 0xFF;
+ data[offset + 1] = value >> 16 & 0xFF;
+ data[offset + 2] = value >> 8 & 0xFF;
+ data[offset + 3] = value & 0xFF;
+ };
+ } else {
+ itemSize = 2;
+ itemDecode = function fontItemDecode(data, offset) {
+ return data[offset] << 9 | data[offset + 1] << 1;
+ };
+ itemEncode = function fontItemEncode(data, offset, value) {
+ data[offset] = value >> 9 & 0xFF;
+ data[offset + 1] = value >> 1 & 0xFF;
+ };
+ }
+ var locaData = loca.data;
+ var locaDataSize = itemSize * (1 + numGlyphs);
+ // is loca.data too short or long?
+ if (locaData.length !== locaDataSize) {
+ locaData = new Uint8Array(locaDataSize);
+ locaData.set(loca.data.subarray(0, locaDataSize));
+ loca.data = locaData;
+ }
+ // removing the invalid glyphs
+ var oldGlyfData = glyf.data;
+ var oldGlyfDataLength = oldGlyfData.length;
+ var newGlyfData = new Uint8Array(oldGlyfDataLength);
+ var startOffset = itemDecode(locaData, 0);
+ var writeOffset = 0;
+ var missingGlyphData = Object.create(null);
+ itemEncode(locaData, 0, writeOffset);
+ var i, j;
+ for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
+ var endOffset = itemDecode(locaData, j);
+ if (endOffset > oldGlyfDataLength && (oldGlyfDataLength + 3 & ~3) === endOffset) {
+ // Aspose breaks fonts by aligning the glyphs to the qword, but not
+ // the glyf table size, which makes last glyph out of range.
+ endOffset = oldGlyfDataLength;
+ }
+ if (endOffset > oldGlyfDataLength) {
+ // glyph end offset points outside glyf data, rejecting the glyph
+ itemEncode(locaData, j, writeOffset);
+ startOffset = endOffset;
+ continue;
+ }
+ if (startOffset === endOffset) {
+ missingGlyphData[i] = true;
+ }
+ var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset, newGlyfData, writeOffset, hintsValid);
+ writeOffset += newLength;
+ itemEncode(locaData, j, writeOffset);
+ startOffset = endOffset;
+ }
+ if (writeOffset === 0) {
+ // glyf table cannot be empty -- redoing the glyf and loca tables
+ // to have single glyph with one point
+ var simpleGlyph = new Uint8Array([
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 49,
+ 0
+ ]);
+ for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
+ itemEncode(locaData, j, simpleGlyph.length);
+ }
+ glyf.data = simpleGlyph;
+ return missingGlyphData;
+ }
+ if (dupFirstEntry) {
+ var firstEntryLength = itemDecode(locaData, itemSize);
+ if (newGlyfData.length > firstEntryLength + writeOffset) {
+ glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
+ } else {
+ glyf.data = new Uint8Array(firstEntryLength + writeOffset);
+ glyf.data.set(newGlyfData.subarray(0, writeOffset));
+ }
+ glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);
+ itemEncode(loca.data, locaData.length - itemSize, writeOffset + firstEntryLength);
+ } else {
+ glyf.data = newGlyfData.subarray(0, writeOffset);
+ }
+ return missingGlyphData;
+ }
+ function readPostScriptTable(post, properties, maxpNumGlyphs) {
+ var start = (font.start ? font.start : 0) + post.offset;
+ font.pos = start;
+ var length = post.length, end = start + length;
+ var version = font.getInt32();
+ // skip rest to the tables
+ font.getBytes(28);
+ var glyphNames;
+ var valid = true;
+ var i;
+ switch (version) {
+ case 0x00010000:
+ glyphNames = MacStandardGlyphOrdering;
+ break;
+ case 0x00020000:
+ var numGlyphs = font.getUint16();
+ if (numGlyphs !== maxpNumGlyphs) {
+ valid = false;
+ break;
+ }
+ var glyphNameIndexes = [];
+ for (i = 0; i < numGlyphs; ++i) {
+ var index = font.getUint16();
+ if (index >= 32768) {
+ valid = false;
+ break;
+ }
+ glyphNameIndexes.push(index);
+ }
+ if (!valid) {
+ break;
+ }
+ var customNames = [];
+ var strBuf = [];
+ while (font.pos < end) {
+ var stringLength = font.getByte();
+ strBuf.length = stringLength;
+ for (i = 0; i < stringLength; ++i) {
+ strBuf[i] = String.fromCharCode(font.getByte());
+ }
+ customNames.push(strBuf.join(''));
+ }
+ glyphNames = [];
+ for (i = 0; i < numGlyphs; ++i) {
+ var j = glyphNameIndexes[i];
+ if (j < 258) {
+ glyphNames.push(MacStandardGlyphOrdering[j]);
+ continue;
+ }
+ glyphNames.push(customNames[j - 258]);
+ }
+ break;
+ case 0x00030000:
+ break;
+ default:
+ warn('Unknown/unsupported post table version ' + version);
+ valid = false;
+ if (properties.defaultEncoding) {
+ glyphNames = properties.defaultEncoding;
+ }
+ break;
+ }
+ properties.glyphNames = glyphNames;
+ return valid;
+ }
+ function readNameTable(nameTable) {
+ var start = (font.start ? font.start : 0) + nameTable.offset;
+ font.pos = start;
+ var names = [
+ [],
+ []
+ ];
+ var length = nameTable.length, end = start + length;
+ var format = font.getUint16();
+ var FORMAT_0_HEADER_LENGTH = 6;
+ if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {
+ // unsupported name table format or table "too" small
+ return names;
+ }
+ var numRecords = font.getUint16();
+ var stringsStart = font.getUint16();
+ var records = [];
+ var NAME_RECORD_LENGTH = 12;
+ var i, ii;
+ for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) {
+ var r = {
+ platform: font.getUint16(),
+ encoding: font.getUint16(),
+ language: font.getUint16(),
+ name: font.getUint16(),
+ length: font.getUint16(),
+ offset: font.getUint16()
+ };
+ // using only Macintosh and Windows platform/encoding names
+ if (r.platform === 1 && r.encoding === 0 && r.language === 0 || r.platform === 3 && r.encoding === 1 && r.language === 0x409) {
+ records.push(r);
+ }
+ }
+ for (i = 0, ii = records.length; i < ii; i++) {
+ var record = records[i];
+ if (record.length <= 0) {
+ continue;
+ }
+ // Nothing to process, ignoring.
+ var pos = start + stringsStart + record.offset;
+ if (pos + record.length > end) {
+ continue;
+ }
+ // outside of name table, ignoring
+ font.pos = pos;
+ var nameIndex = record.name;
+ if (record.encoding) {
+ // unicode
+ var str = '';
+ for (var j = 0, jj = record.length; j < jj; j += 2) {
+ str += String.fromCharCode(font.getUint16());
+ }
+ names[1][nameIndex] = str;
+ } else {
+ names[0][nameIndex] = bytesToString(font.getBytes(record.length));
+ }
+ }
+ return names;
+ }
+ var TTOpsStackDeltas = [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ -2,
+ -2,
+ -2,
+ -2,
+ 0,
+ 0,
+ -2,
+ -5,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ 0,
+ 0,
+ -1,
+ 0,
+ -1,
+ -1,
+ -1,
+ -1,
+ 1,
+ -1,
+ -999,
+ 0,
+ 1,
+ 0,
+ -1,
+ -2,
+ 0,
+ -1,
+ -2,
+ -1,
+ -1,
+ 0,
+ -1,
+ -1,
+ 0,
+ 0,
+ -999,
+ -999,
+ -1,
+ -1,
+ -1,
+ -1,
+ -2,
+ -999,
+ -2,
+ -2,
+ -999,
+ 0,
+ -2,
+ -2,
+ 0,
+ 0,
+ -2,
+ 0,
+ -2,
+ 0,
+ 0,
+ 0,
+ -2,
+ -1,
+ -1,
+ 1,
+ 1,
+ 0,
+ 0,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ 0,
+ 0,
+ -1,
+ 0,
+ -1,
+ -1,
+ 0,
+ -999,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ -2,
+ -999,
+ -999,
+ -999,
+ -999,
+ -999,
+ -1,
+ -1,
+ -2,
+ -2,
+ 0,
+ 0,
+ 0,
+ 0,
+ -1,
+ -1,
+ -999,
+ -2,
+ -2,
+ 0,
+ 0,
+ -1,
+ -2,
+ -2,
+ 0,
+ 0,
+ 0,
+ -1,
+ -1,
+ -1,
+ -2
+ ];
+ // 0xC0-DF == -1 and 0xE0-FF == -2
+ function sanitizeTTProgram(table, ttContext) {
+ var data = table.data;
+ var i = 0, j, n, b, funcId, pc, lastEndf = 0, lastDeff = 0;
+ var stack = [];
+ var callstack = [];
+ var functionsCalled = [];
+ var tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions;
+ var inFDEF = false, ifLevel = 0, inELSE = 0;
+ for (var ii = data.length; i < ii;) {
+ var op = data[i++];
+ // The TrueType instruction set docs can be found at
+ // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html
+ if (op === 0x40) {
+ // NPUSHB - pushes n bytes
+ n = data[i++];
+ if (inFDEF || inELSE) {
+ i += n;
+ } else {
+ for (j = 0; j < n; j++) {
+ stack.push(data[i++]);
+ }
+ }
+ } else if (op === 0x41) {
+ // NPUSHW - pushes n words
+ n = data[i++];
+ if (inFDEF || inELSE) {
+ i += n * 2;
+ } else {
+ for (j = 0; j < n; j++) {
+ b = data[i++];
+ stack.push(b << 8 | data[i++]);
+ }
+ }
+ } else if ((op & 0xF8) === 0xB0) {
+ // PUSHB - pushes bytes
+ n = op - 0xB0 + 1;
+ if (inFDEF || inELSE) {
+ i += n;
+ } else {
+ for (j = 0; j < n; j++) {
+ stack.push(data[i++]);
+ }
+ }
+ } else if ((op & 0xF8) === 0xB8) {
+ // PUSHW - pushes words
+ n = op - 0xB8 + 1;
+ if (inFDEF || inELSE) {
+ i += n * 2;
+ } else {
+ for (j = 0; j < n; j++) {
+ b = data[i++];
+ stack.push(b << 8 | data[i++]);
+ }
+ }
+ } else if (op === 0x2B && !tooComplexToFollowFunctions) {
+ // CALL
+ if (!inFDEF && !inELSE) {
+ // collecting inforamtion about which functions are used
+ funcId = stack[stack.length - 1];
+ ttContext.functionsUsed[funcId] = true;
+ if (funcId in ttContext.functionsStackDeltas) {
+ stack.length += ttContext.functionsStackDeltas[funcId];
+ } else if (funcId in ttContext.functionsDefined && functionsCalled.indexOf(funcId) < 0) {
+ callstack.push({
+ data: data,
+ i: i,
+ stackTop: stack.length - 1
+ });
+ functionsCalled.push(funcId);
+ pc = ttContext.functionsDefined[funcId];
+ if (!pc) {
+ warn('TT: CALL non-existent function');
+ ttContext.hintsValid = false;
+ return;
+ }
+ data = pc.data;
+ i = pc.i;
+ }
+ }
+ } else if (op === 0x2C && !tooComplexToFollowFunctions) {
+ // FDEF
+ if (inFDEF || inELSE) {
+ warn('TT: nested FDEFs not allowed');
+ tooComplexToFollowFunctions = true;
+ }
+ inFDEF = true;
+ // collecting inforamtion about which functions are defined
+ lastDeff = i;
+ funcId = stack.pop();
+ ttContext.functionsDefined[funcId] = {
+ data: data,
+ i: i
+ };
+ } else if (op === 0x2D) {
+ // ENDF - end of function
+ if (inFDEF) {
+ inFDEF = false;
+ lastEndf = i;
+ } else {
+ pc = callstack.pop();
+ if (!pc) {
+ warn('TT: ENDF bad stack');
+ ttContext.hintsValid = false;
+ return;
+ }
+ funcId = functionsCalled.pop();
+ data = pc.data;
+ i = pc.i;
+ ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop;
+ }
+ } else if (op === 0x89) {
+ // IDEF - instruction definition
+ if (inFDEF || inELSE) {
+ warn('TT: nested IDEFs not allowed');
+ tooComplexToFollowFunctions = true;
+ }
+ inFDEF = true;
+ // recording it as a function to track ENDF
+ lastDeff = i;
+ } else if (op === 0x58) {
+ // IF
+ ++ifLevel;
+ } else if (op === 0x1B) {
+ // ELSE
+ inELSE = ifLevel;
+ } else if (op === 0x59) {
+ // EIF
+ if (inELSE === ifLevel) {
+ inELSE = 0;
+ }
+ --ifLevel;
+ } else if (op === 0x1C) {
+ // JMPR
+ if (!inFDEF && !inELSE) {
+ var offset = stack[stack.length - 1];
+ // only jumping forward to prevent infinite loop
+ if (offset > 0) {
+ i += offset - 1;
+ }
+ }
+ }
+ // Adjusting stack not extactly, but just enough to get function id
+ if (!inFDEF && !inELSE) {
+ var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] : op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
+ if (op >= 0x71 && op <= 0x75) {
+ n = stack.pop();
+ if (n === n) {
+ stackDelta = -n * 2;
+ }
+ }
+ while (stackDelta < 0 && stack.length > 0) {
+ stack.pop();
+ stackDelta++;
+ }
+ while (stackDelta > 0) {
+ stack.push(NaN);
+ // pushing any number into stack
+ stackDelta--;
+ }
+ }
+ }
+ ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
+ var content = [data];
+ if (i > data.length) {
+ content.push(new Uint8Array(i - data.length));
+ }
+ if (lastDeff > lastEndf) {
+ warn('TT: complementing a missing function tail');
+ // new function definition started, but not finished
+ // complete function by [CLEAR, ENDF]
+ content.push(new Uint8Array([
+ 0x22,
+ 0x2D
+ ]));
+ }
+ foldTTTable(table, content);
+ }
+ function checkInvalidFunctions(ttContext, maxFunctionDefs) {
+ if (ttContext.tooComplexToFollowFunctions) {
+ return;
+ }
+ if (ttContext.functionsDefined.length > maxFunctionDefs) {
+ warn('TT: more functions defined than expected');
+ ttContext.hintsValid = false;
+ return;
+ }
+ for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
+ if (j > maxFunctionDefs) {
+ warn('TT: invalid function id: ' + j);
+ ttContext.hintsValid = false;
+ return;
+ }
+ if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
+ warn('TT: undefined function: ' + j);
+ ttContext.hintsValid = false;
+ return;
+ }
+ }
+ }
+ function foldTTTable(table, content) {
+ if (content.length > 1) {
+ // concatenating the content items
+ var newLength = 0;
+ var j, jj;
+ for (j = 0, jj = content.length; j < jj; j++) {
+ newLength += content[j].length;
+ }
+ newLength = newLength + 3 & ~3;
+ var result = new Uint8Array(newLength);
+ var pos = 0;
+ for (j = 0, jj = content.length; j < jj; j++) {
+ result.set(content[j], pos);
+ pos += content[j].length;
+ }
+ table.data = result;
+ table.length = newLength;
+ }
+ }
+ function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) {
+ var ttContext = {
+ functionsDefined: [],
+ functionsUsed: [],
+ functionsStackDeltas: [],
+ tooComplexToFollowFunctions: false,
+ hintsValid: true
+ };
+ if (fpgm) {
+ sanitizeTTProgram(fpgm, ttContext);
+ }
+ if (prep) {
+ sanitizeTTProgram(prep, ttContext);
+ }
+ if (fpgm) {
+ checkInvalidFunctions(ttContext, maxFunctionDefs);
+ }
+ if (cvt && cvt.length & 1) {
+ var cvtData = new Uint8Array(cvt.length + 1);
+ cvtData.set(cvt.data);
+ cvt.data = cvtData;
+ }
+ return ttContext.hintsValid;
+ }
+ // The following steps modify the original font data, making copy
+ font = new Stream(new Uint8Array(font.getBytes()));
+ var VALID_TABLES = [
+ 'OS/2',
+ 'cmap',
+ 'head',
+ 'hhea',
+ 'hmtx',
+ 'maxp',
+ 'name',
+ 'post',
+ 'loca',
+ 'glyf',
+ 'fpgm',
+ 'prep',
+ 'cvt ',
+ 'CFF '
+ ];
+ var header = readOpenTypeHeader(font);
+ var numTables = header.numTables;
+ var cff, cffFile;
+ var tables = Object.create(null);
+ tables['OS/2'] = null;
+ tables['cmap'] = null;
+ tables['head'] = null;
+ tables['hhea'] = null;
+ tables['hmtx'] = null;
+ tables['maxp'] = null;
+ tables['name'] = null;
+ tables['post'] = null;
+ var table;
+ for (var i = 0; i < numTables; i++) {
+ table = readTableEntry(font);
+ if (VALID_TABLES.indexOf(table.tag) < 0) {
+ continue;
+ }
+ // skipping table if it's not a required or optional table
+ if (table.length === 0) {
+ continue;
+ }
+ // skipping empty tables
+ tables[table.tag] = table;
+ }
+ var isTrueType = !tables['CFF '];
+ if (!isTrueType) {
+ // OpenType font
+ if (header.version === 'OTTO' && properties.type !== 'CIDFontType2' || !tables['head'] || !tables['hhea'] || !tables['maxp'] || !tables['post']) {
+ // no major tables: throwing everything at CFFFont
+ cffFile = new Stream(tables['CFF '].data);
+ cff = new CFFFont(cffFile, properties);
+ adjustWidths(properties);
+ return this.convert(name, cff, properties);
+ }
+ delete tables['glyf'];
+ delete tables['loca'];
+ delete tables['fpgm'];
+ delete tables['prep'];
+ delete tables['cvt '];
+ this.isOpenType = true;
+ } else {
+ if (!tables['loca']) {
+ error('Required "loca" table is not found');
+ }
+ if (!tables['glyf']) {
+ warn('Required "glyf" table is not found -- trying to recover.');
+ // Note: We use `sanitizeGlyphLocations` to add dummy glyf data below.
+ tables['glyf'] = {
+ tag: 'glyf',
+ data: new Uint8Array(0)
+ };
+ }
+ this.isOpenType = false;
+ }
+ if (!tables['maxp']) {
+ error('Required "maxp" table is not found');
+ }
+ font.pos = (font.start || 0) + tables['maxp'].offset;
+ var version = font.getInt32();
+ var numGlyphs = font.getUint16();
+ var maxFunctionDefs = 0;
+ if (version >= 0x00010000 && tables['maxp'].length >= 22) {
+ // maxZones can be invalid
+ font.pos += 8;
+ var maxZones = font.getUint16();
+ if (maxZones > 2) {
+ // reset to 2 if font has invalid maxZones
+ tables['maxp'].data[14] = 0;
+ tables['maxp'].data[15] = 2;
+ }
+ font.pos += 4;
+ maxFunctionDefs = font.getUint16();
+ }
+ var dupFirstEntry = false;
+ if (properties.type === 'CIDFontType2' && properties.toUnicode && properties.toUnicode.get(0) > '\u0000') {
+ // oracle's defect (see 3427), duplicating first entry
+ dupFirstEntry = true;
+ numGlyphs++;
+ tables['maxp'].data[4] = numGlyphs >> 8;
+ tables['maxp'].data[5] = numGlyphs & 255;
+ }
+ var hintsValid = sanitizeTTPrograms(tables['fpgm'], tables['prep'], tables['cvt '], maxFunctionDefs);
+ if (!hintsValid) {
+ delete tables['fpgm'];
+ delete tables['prep'];
+ delete tables['cvt '];
+ }
+ // Ensure the hmtx table contains the advance width and
+ // sidebearings information for numGlyphs in the maxp table
+ sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphs);
+ if (!tables['head']) {
+ error('Required "head" table is not found');
+ }
+ sanitizeHead(tables['head'], numGlyphs, isTrueType ? tables['loca'].length : 0);
+ var missingGlyphs = Object.create(null);
+ if (isTrueType) {
+ var isGlyphLocationsLong = int16(tables['head'].data[50], tables['head'].data[51]);
+ missingGlyphs = sanitizeGlyphLocations(tables['loca'], tables['glyf'], numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry);
+ }
+ if (!tables['hhea']) {
+ error('Required "hhea" table is not found');
+ }
+ // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
+ // Sometimes it's 0. That needs to be fixed
+ if (tables['hhea'].data[10] === 0 && tables['hhea'].data[11] === 0) {
+ tables['hhea'].data[10] = 0xFF;
+ tables['hhea'].data[11] = 0xFF;
+ }
+ // Extract some more font properties from the OpenType head and
+ // hhea tables; yMin and descent value are always negative.
+ var metricsOverride = {
+ unitsPerEm: int16(tables['head'].data[18], tables['head'].data[19]),
+ yMax: int16(tables['head'].data[42], tables['head'].data[43]),
+ yMin: signedInt16(tables['head'].data[38], tables['head'].data[39]),
+ ascent: int16(tables['hhea'].data[4], tables['hhea'].data[5]),
+ descent: signedInt16(tables['hhea'].data[6], tables['hhea'].data[7])
+ };
+ // PDF FontDescriptor metrics lie -- using data from actual font.
+ this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
+ this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
+ // The 'post' table has glyphs names.
+ if (tables['post']) {
+ var valid = readPostScriptTable(tables['post'], properties, numGlyphs);
+ if (!valid) {
+ tables['post'] = null;
+ }
+ }
+ var charCodeToGlyphId = [], charCode;
+ var toUnicode = properties.toUnicode, widths = properties.widths;
+ var skipToUnicode = toUnicode instanceof IdentityToUnicodeMap || toUnicode.length === 0x10000;
+ // Helper function to try to skip mapping of empty glyphs.
+ // Note: In some cases, just relying on the glyph data doesn't work,
+ // hence we also use a few heuristics to fix various PDF files.
+ function hasGlyph(glyphId, charCode, widthCode) {
+ if (!missingGlyphs[glyphId]) {
+ return true;
+ }
+ if (!skipToUnicode && charCode >= 0 && toUnicode.has(charCode)) {
+ return true;
+ }
+ if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
+ return true;
+ }
+ return false;
+ }
+ if (properties.type === 'CIDFontType2') {
+ var cidToGidMap = properties.cidToGidMap || [];
+ var isCidToGidMapEmpty = cidToGidMap.length === 0;
+ properties.cMap.forEach(function (charCode, cid) {
+ assert(cid <= 0xffff, 'Max size of CID is 65,535');
+ var glyphId = -1;
+ if (isCidToGidMapEmpty) {
+ glyphId = cid;
+ } else if (cidToGidMap[cid] !== undefined) {
+ glyphId = cidToGidMap[cid];
+ }
+ if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId, charCode, cid)) {
+ charCodeToGlyphId[charCode] = glyphId;
+ }
+ });
+ if (dupFirstEntry && (isCidToGidMapEmpty || !charCodeToGlyphId[0])) {
+ // We don't duplicate the first entry in the `charCodeToGlyphId` map
+ // if the font has a `CIDToGIDMap` which has already mapped the first
+ // entry to a non-zero `glyphId` (fixes issue7544.pdf).
+ charCodeToGlyphId[0] = numGlyphs - 1;
+ }
+ } else {
+ // Most of the following logic in this code branch is based on the
+ // 9.6.6.4 of the PDF spec.
+ var cmapTable = readCmapTable(tables['cmap'], font, this.isSymbolicFont, properties.hasEncoding);
+ var cmapPlatformId = cmapTable.platformId;
+ var cmapEncodingId = cmapTable.encodingId;
+ var cmapMappings = cmapTable.mappings;
+ var cmapMappingsLength = cmapMappings.length;
+ // The spec seems to imply that if the font is symbolic the encoding
+ // should be ignored, this doesn't appear to work for 'preistabelle.pdf'
+ // where the the font is symbolic and it has an encoding.
+ if (properties.hasEncoding && (cmapPlatformId === 3 && cmapEncodingId === 1 || cmapPlatformId === 1 && cmapEncodingId === 0) || cmapPlatformId === -1 && cmapEncodingId === -1 && // Temporary hack
+ !!getEncoding(properties.baseEncodingName)) {
+ // Temporary hack
+ // When no preferred cmap table was found and |baseEncodingName| is
+ // one of the predefined encodings, we seem to obtain a better
+ // |charCodeToGlyphId| map from the code below (fixes bug 1057544).
+ // TODO: Note that this is a hack which should be removed as soon as
+ // we have proper support for more exotic cmap tables.
+ var baseEncoding = [];
+ if (properties.baseEncodingName === 'MacRomanEncoding' || properties.baseEncodingName === 'WinAnsiEncoding') {
+ baseEncoding = getEncoding(properties.baseEncodingName);
+ }
+ var glyphsUnicodeMap = getGlyphsUnicode();
+ for (charCode = 0; charCode < 256; charCode++) {
+ var glyphName, standardGlyphName;
+ if (this.differences && charCode in this.differences) {
+ glyphName = this.differences[charCode];
+ } else if (charCode in baseEncoding && baseEncoding[charCode] !== '') {
+ glyphName = baseEncoding[charCode];
+ } else {
+ glyphName = StandardEncoding[charCode];
+ }
+ if (!glyphName) {
+ continue;
+ }
+ // Ensure that non-standard glyph names are resolved to valid ones.
+ standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
+ var unicodeOrCharCode, isUnicode = false;
+ if (cmapPlatformId === 3 && cmapEncodingId === 1) {
+ unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
+ isUnicode = true;
+ } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
+ // TODO: the encoding needs to be updated with mac os table.
+ unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
+ }
+ var found = false;
+ for (i = 0; i < cmapMappingsLength; ++i) {
+ if (cmapMappings[i].charCode !== unicodeOrCharCode) {
+ continue;
+ }
+ var code = isUnicode ? charCode : unicodeOrCharCode;
+ if (hasGlyph(cmapMappings[i].glyphId, code, -1)) {
+ charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
+ found = true;
+ break;
+ }
+ }
+ if (!found && properties.glyphNames) {
+ // Try to map using the post table.
+ var glyphId = properties.glyphNames.indexOf(glyphName);
+ // The post table ought to use the same kind of glyph names as the
+ // `differences` array, but check the standard ones as a fallback.
+ if (glyphId === -1 && standardGlyphName !== glyphName) {
+ glyphId = properties.glyphNames.indexOf(standardGlyphName);
+ }
+ if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
+ charCodeToGlyphId[charCode] = glyphId;
+ found = true;
+ }
+ }
+ if (!found) {
+ charCodeToGlyphId[charCode] = 0;
+ }
+ }
+ } else // notdef
+ if (cmapPlatformId === 0 && cmapEncodingId === 0) {
+ // Default Unicode semantics, use the charcodes as is.
+ for (i = 0; i < cmapMappingsLength; ++i) {
+ charCodeToGlyphId[cmapMappings[i].charCode] = cmapMappings[i].glyphId;
+ }
+ } else {
+ // For (3, 0) cmap tables:
+ // The charcode key being stored in charCodeToGlyphId is the lower
+ // byte of the two-byte charcodes of the cmap table since according to
+ // the spec: 'each byte from the string shall be prepended with the
+ // high byte of the range [of charcodes in the cmap table], to form
+ // a two-byte character, which shall be used to select the
+ // associated glyph description from the subtable'.
+ //
+ // For (1, 0) cmap tables:
+ // 'single bytes from the string shall be used to look up the
+ // associated glyph descriptions from the subtable'. This means
+ // charcodes in the cmap will be single bytes, so no-op since
+ // glyph.charCode & 0xFF === glyph.charCode
+ for (i = 0; i < cmapMappingsLength; ++i) {
+ charCode = cmapMappings[i].charCode & 0xFF;
+ charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
+ }
+ }
+ }
+ if (charCodeToGlyphId.length === 0) {
+ // defines at least one glyph
+ charCodeToGlyphId[0] = 0;
+ }
+ // Converting glyphs and ids into font's cmap table
+ var newMapping = adjustMapping(charCodeToGlyphId, properties);
+ this.toFontChar = newMapping.toFontChar;
+ tables['cmap'] = {
+ tag: 'cmap',
+ data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs)
+ };
+ if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
+ tables['OS/2'] = {
+ tag: 'OS/2',
+ data: createOS2Table(properties, newMapping.charCodeToGlyphId, metricsOverride)
+ };
+ }
+ // Rewrite the 'post' table if needed
+ if (!tables['post']) {
+ tables['post'] = {
+ tag: 'post',
+ data: createPostTable(properties)
+ };
+ }
+ if (!isTrueType) {
+ try {
+ // Trying to repair CFF file
+ cffFile = new Stream(tables['CFF '].data);
+ var parser = new CFFParser(cffFile, properties, SEAC_ANALYSIS_ENABLED);
+ cff = parser.parse();
+ var compiler = new CFFCompiler(cff);
+ tables['CFF '].data = compiler.compile();
+ } catch (e) {
+ warn('Failed to compile font ' + properties.loadedName);
+ }
+ }
+ // Re-creating 'name' table
+ if (!tables['name']) {
+ tables['name'] = {
+ tag: 'name',
+ data: createNameTable(this.name)
+ };
+ } else {
+ // ... using existing 'name' table as prototype
+ var namePrototype = readNameTable(tables['name']);
+ tables['name'].data = createNameTable(name, namePrototype);
+ }
+ var builder = new OpenTypeFileBuilder(header.version);
+ for (var tableTag in tables) {
+ builder.addTable(tableTag, tables[tableTag].data);
+ }
+ return builder.toArray();
+ },
+ convert: function Font_convert(fontName, font, properties) {
+ // TODO: Check the charstring widths to determine this.
+ properties.fixedPitch = false;
+ if (properties.builtInEncoding) {
+ // For Type1 fonts that do not include either `ToUnicode` or `Encoding`
+ // data, attempt to use the `builtInEncoding` to improve text selection.
+ adjustToUnicode(properties, properties.builtInEncoding);
+ }
+ var mapping = font.getGlyphMapping(properties);
+ var newMapping = adjustMapping(mapping, properties);
+ this.toFontChar = newMapping.toFontChar;
+ var numGlyphs = font.numGlyphs;
+ function getCharCodes(charCodeToGlyphId, glyphId) {
+ var charCodes = null;
+ for (var charCode in charCodeToGlyphId) {
+ if (glyphId === charCodeToGlyphId[charCode]) {
+ if (!charCodes) {
+ charCodes = [];
+ }
+ charCodes.push(charCode | 0);
+ }
+ }
+ return charCodes;
+ }
+ function createCharCode(charCodeToGlyphId, glyphId) {
+ for (var charCode in charCodeToGlyphId) {
+ if (glyphId === charCodeToGlyphId[charCode]) {
+ return charCode | 0;
+ }
+ }
+ newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] = glyphId;
+ return newMapping.nextAvailableFontCharCode++;
+ }
+ var seacs = font.seacs;
+ if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) {
+ var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
+ var charset = font.getCharset();
+ var seacMap = Object.create(null);
+ for (var glyphId in seacs) {
+ glyphId |= 0;
+ var seac = seacs[glyphId];
+ var baseGlyphName = StandardEncoding[seac[2]];
+ var accentGlyphName = StandardEncoding[seac[3]];
+ var baseGlyphId = charset.indexOf(baseGlyphName);
+ var accentGlyphId = charset.indexOf(accentGlyphName);
+ if (baseGlyphId < 0 || accentGlyphId < 0) {
+ continue;
+ }
+ var accentOffset = {
+ x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
+ y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
+ };
+ var charCodes = getCharCodes(mapping, glyphId);
+ if (!charCodes) {
+ // There's no point in mapping it if the char code was never mapped
+ // to begin with.
+ continue;
+ }
+ for (var i = 0, ii = charCodes.length; i < ii; i++) {
+ var charCode = charCodes[i];
+ // Find a fontCharCode that maps to the base and accent glyphs.
+ // If one doesn't exists, create it.
+ var charCodeToGlyphId = newMapping.charCodeToGlyphId;
+ var baseFontCharCode = createCharCode(charCodeToGlyphId, baseGlyphId);
+ var accentFontCharCode = createCharCode(charCodeToGlyphId, accentGlyphId);
+ seacMap[charCode] = {
+ baseFontCharCode: baseFontCharCode,
+ accentFontCharCode: accentFontCharCode,
+ accentOffset: accentOffset
+ };
+ }
+ }
+ properties.seacMap = seacMap;
+ }
+ var unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
+ var builder = new OpenTypeFileBuilder('\x4F\x54\x54\x4F');
+ // PostScript Font Program
+ builder.addTable('CFF ', font.data);
+ // OS/2 and Windows Specific metrics
+ builder.addTable('OS/2', createOS2Table(properties, newMapping.charCodeToGlyphId));
+ // Character to glyphs mapping
+ builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId, numGlyphs));
+ // Font header
+ builder.addTable('head', '\x00\x01\x00\x00' + // Version number
+ '\x00\x00\x10\x00' + // fontRevision
+ '\x00\x00\x00\x00' + // checksumAdjustement
+ '\x5F\x0F\x3C\xF5' + // magicNumber
+ '\x00\x00' + // Flags
+ safeString16(unitsPerEm) + // unitsPerEM
+ '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date
+ '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date
+ '\x00\x00' + // xMin
+ safeString16(properties.descent) + // yMin
+ '\x0F\xFF' + // xMax
+ safeString16(properties.ascent) + // yMax
+ string16(properties.italicAngle ? 2 : 0) + // macStyle
+ '\x00\x11' + // lowestRecPPEM
+ '\x00\x00' + // fontDirectionHint
+ '\x00\x00' + // indexToLocFormat
+ '\x00\x00');
+ // glyphDataFormat
+ // Horizontal header
+ builder.addTable('hhea', '\x00\x01\x00\x00' + // Version number
+ safeString16(properties.ascent) + // Typographic Ascent
+ safeString16(properties.descent) + // Typographic Descent
+ '\x00\x00' + // Line Gap
+ '\xFF\xFF' + // advanceWidthMax
+ '\x00\x00' + // minLeftSidebearing
+ '\x00\x00' + // minRightSidebearing
+ '\x00\x00' + // xMaxExtent
+ safeString16(properties.capHeight) + // caretSlopeRise
+ safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun
+ '\x00\x00' + // caretOffset
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // -reserved-
+ '\x00\x00' + // metricDataFormat
+ string16(numGlyphs));
+ // Number of HMetrics
+ // Horizontal metrics
+ builder.addTable('hmtx', function fontFieldsHmtx() {
+ var charstrings = font.charstrings;
+ var cffWidths = font.cff ? font.cff.widths : null;
+ var hmtx = '\x00\x00\x00\x00';
+ // Fake .notdef
+ for (var i = 1, ii = numGlyphs; i < ii; i++) {
+ var width = 0;
+ if (charstrings) {
+ var charstring = charstrings[i - 1];
+ width = 'width' in charstring ? charstring.width : 0;
+ } else if (cffWidths) {
+ width = Math.ceil(cffWidths[i] || 0);
+ }
+ hmtx += string16(width) + string16(0);
+ }
+ return hmtx;
+ }());
+ // Maximum profile
+ builder.addTable('maxp', '\x00\x00\x50\x00' + // Version number
+ string16(numGlyphs));
+ // Num of glyphs
+ // Naming tables
+ builder.addTable('name', createNameTable(fontName));
+ // PostScript information
+ builder.addTable('post', createPostTable(properties));
+ return builder.toArray();
+ },
+ get spaceWidth() {
+ if ('_shadowWidth' in this) {
+ return this._shadowWidth;
+ }
+ // trying to estimate space character width
+ var possibleSpaceReplacements = [
+ 'space',
+ 'minus',
+ 'one',
+ 'i',
+ 'I'
+ ];
+ var width;
+ for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) {
+ var glyphName = possibleSpaceReplacements[i];
+ // if possible, getting width by glyph name
+ if (glyphName in this.widths) {
+ width = this.widths[glyphName];
+ break;
+ }
+ var glyphsUnicodeMap = getGlyphsUnicode();
+ var glyphUnicode = glyphsUnicodeMap[glyphName];
+ // finding the charcode via unicodeToCID map
+ var charcode = 0;
+ if (this.composite) {
+ if (this.cMap.contains(glyphUnicode)) {
+ charcode = this.cMap.lookup(glyphUnicode);
+ }
+ }
+ // ... via toUnicode map
+ if (!charcode && this.toUnicode) {
+ charcode = this.toUnicode.charCodeOf(glyphUnicode);
+ }
+ // setting it to unicode if negative or undefined
+ if (charcode <= 0) {
+ charcode = glyphUnicode;
+ }
+ // trying to get width via charcode
+ width = this.widths[charcode];
+ if (width) {
+ break;
+ }
+ }
+ // the non-zero width found
+ width = width || this.defaultWidth;
+ // Do not shadow the property here. See discussion:
+ // https://github.com/mozilla/pdf.js/pull/2127#discussion_r1662280
+ this._shadowWidth = width;
+ return width;
+ },
+ charToGlyph: function Font_charToGlyph(charcode, isSpace) {
+ var fontCharCode, width, operatorListId;
+ var widthCode = charcode;
+ if (this.cMap && this.cMap.contains(charcode)) {
+ widthCode = this.cMap.lookup(charcode);
+ }
+ width = this.widths[widthCode];
+ width = isNum(width) ? width : this.defaultWidth;
+ var vmetric = this.vmetrics && this.vmetrics[widthCode];
+ var unicode = this.toUnicode.get(charcode) || charcode;
+ if (typeof unicode === 'number') {
+ unicode = String.fromCharCode(unicode);
+ }
+ var isInFont = charcode in this.toFontChar;
+ // First try the toFontChar map, if it's not there then try falling
+ // back to the char code.
+ fontCharCode = this.toFontChar[charcode] || charcode;
+ if (this.missingFile) {
+ fontCharCode = mapSpecialUnicodeValues(fontCharCode);
+ }
+ if (this.isType3Font) {
+ // Font char code in this case is actually a glyph name.
+ operatorListId = fontCharCode;
+ }
+ var accent = null;
+ if (this.seacMap && this.seacMap[charcode]) {
+ isInFont = true;
+ var seac = this.seacMap[charcode];
+ fontCharCode = seac.baseFontCharCode;
+ accent = {
+ fontChar: String.fromCharCode(seac.accentFontCharCode),
+ offset: seac.accentOffset
+ };
+ }
+ var fontChar = String.fromCharCode(fontCharCode);
+ var glyph = this.glyphCache[charcode];
+ if (!glyph || !glyph.matchesForCache(fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont)) {
+ glyph = new Glyph(fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont);
+ this.glyphCache[charcode] = glyph;
+ }
+ return glyph;
+ },
+ charsToGlyphs: function Font_charsToGlyphs(chars) {
+ var charsCache = this.charsCache;
+ var glyphs, glyph, charcode;
+ // if we translated this string before, just grab it from the cache
+ if (charsCache) {
+ glyphs = charsCache[chars];
+ if (glyphs) {
+ return glyphs;
+ }
+ }
+ // lazily create the translation cache
+ if (!charsCache) {
+ charsCache = this.charsCache = Object.create(null);
+ }
+ glyphs = [];
+ var charsCacheKey = chars;
+ var i = 0, ii;
+ if (this.cMap) {
+ // composite fonts have multi-byte strings convert the string from
+ // single-byte to multi-byte
+ var c = Object.create(null);
+ while (i < chars.length) {
+ this.cMap.readCharCode(chars, i, c);
+ charcode = c.charcode;
+ var length = c.length;
+ i += length;
+ // Space is char with code 0x20 and length 1 in multiple-byte codes.
+ var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20;
+ glyph = this.charToGlyph(charcode, isSpace);
+ glyphs.push(glyph);
+ }
+ } else {
+ for (i = 0, ii = chars.length; i < ii; ++i) {
+ charcode = chars.charCodeAt(i);
+ glyph = this.charToGlyph(charcode, charcode === 0x20);
+ glyphs.push(glyph);
+ }
+ }
+ // Enter the translated string into the cache
+ return charsCache[charsCacheKey] = glyphs;
+ }
+ };
+ return Font;
+ }();
+ var ErrorFont = function ErrorFontClosure() {
+ function ErrorFont(error) {
+ this.error = error;
+ this.loadedName = 'g_font_error';
+ this.loading = false;
+ }
+ ErrorFont.prototype = {
+ charsToGlyphs: function ErrorFont_charsToGlyphs() {
+ return [];
+ },
+ exportData: function ErrorFont_exportData() {
+ return { error: this.error };
+ }
+ };
+ return ErrorFont;
+ }();
+ /**
+ * Shared logic for building a char code to glyph id mapping for Type1 and
+ * simple CFF fonts. See section 9.6.6.2 of the spec.
+ * @param {Object} properties Font properties object.
+ * @param {Object} builtInEncoding The encoding contained within the actual font
+ * data.
+ * @param {Array} glyphNames Array of glyph names where the index is the
+ * glyph ID.
+ * @returns {Object} A char code to glyph ID map.
+ */
+ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) {
+ var charCodeToGlyphId = Object.create(null);
+ var glyphId, charCode, baseEncoding;
+ if (properties.baseEncodingName) {
+ // If a valid base encoding name was used, the mapping is initialized with
+ // that.
+ baseEncoding = getEncoding(properties.baseEncodingName);
+ for (charCode = 0; charCode < baseEncoding.length; charCode++) {
+ glyphId = glyphNames.indexOf(baseEncoding[charCode]);
+ if (glyphId >= 0) {
+ charCodeToGlyphId[charCode] = glyphId;
+ } else {
+ charCodeToGlyphId[charCode] = 0;
+ }
+ }
+ } else // notdef
+ if (!!(properties.flags & FontFlags.Symbolic)) {
+ // For a symbolic font the encoding should be the fonts built-in
+ // encoding.
+ for (charCode in builtInEncoding) {
+ charCodeToGlyphId[charCode] = builtInEncoding[charCode];
+ }
+ } else {
+ // For non-symbolic fonts that don't have a base encoding the standard
+ // encoding should be used.
+ baseEncoding = StandardEncoding;
+ for (charCode = 0; charCode < baseEncoding.length; charCode++) {
+ glyphId = glyphNames.indexOf(baseEncoding[charCode]);
+ if (glyphId >= 0) {
+ charCodeToGlyphId[charCode] = glyphId;
+ } else {
+ charCodeToGlyphId[charCode] = 0;
+ }
+ }
+ }
+ // notdef
+ // Lastly, merge in the differences.
+ var differences = properties.differences, glyphsUnicodeMap;
+ if (differences) {
+ for (charCode in differences) {
+ var glyphName = differences[charCode];
+ glyphId = glyphNames.indexOf(glyphName);
+ if (glyphId === -1) {
+ if (!glyphsUnicodeMap) {
+ glyphsUnicodeMap = getGlyphsUnicode();
+ }
+ var standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
+ if (standardGlyphName !== glyphName) {
+ glyphId = glyphNames.indexOf(standardGlyphName);
+ }
+ }
+ if (glyphId >= 0) {
+ charCodeToGlyphId[charCode] = glyphId;
+ } else {
+ charCodeToGlyphId[charCode] = 0;
+ }
+ }
+ }
+ // notdef
+ return charCodeToGlyphId;
+ }
+ // Type1Font is also a CIDFontType0.
+ var Type1Font = function Type1FontClosure() {
+ function findBlock(streamBytes, signature, startIndex) {
+ var streamBytesLength = streamBytes.length;
+ var signatureLength = signature.length;
+ var scanLength = streamBytesLength - signatureLength;
+ var i = startIndex, j, found = false;
+ while (i < scanLength) {
+ j = 0;
+ while (j < signatureLength && streamBytes[i + j] === signature[j]) {
+ j++;
+ }
+ if (j >= signatureLength) {
+ // `signature` found, skip over whitespace.
+ i += j;
+ while (i < streamBytesLength && isSpace(streamBytes[i])) {
+ i++;
+ }
+ found = true;
+ break;
+ }
+ i++;
+ }
+ return {
+ found: found,
+ length: i
+ };
+ }
+ function getHeaderBlock(stream, suggestedLength) {
+ var EEXEC_SIGNATURE = [
+ 0x65,
+ 0x65,
+ 0x78,
+ 0x65,
+ 0x63
+ ];
+ var streamStartPos = stream.pos;
+ // Save the initial stream position.
+ var headerBytes, headerBytesLength, block;
+ try {
+ headerBytes = stream.getBytes(suggestedLength);
+ headerBytesLength = headerBytes.length;
+ } catch (ex) {
+ if (ex instanceof MissingDataException) {
+ throw ex;
+ }
+ }
+ // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array
+ // cannot hold the result of `getBytes`, and fallback to simply checking
+ // the entire stream (fixes issue3928.pdf).
+ if (headerBytesLength === suggestedLength) {
+ // Most of the time `suggestedLength` is correct, so to speed things up we
+ // initially only check the last few bytes to see if the header was found.
+ // Otherwise we (potentially) check the entire stream to prevent errors in
+ // `Type1Parser` (fixes issue5686.pdf).
+ block = findBlock(headerBytes, EEXEC_SIGNATURE, suggestedLength - 2 * EEXEC_SIGNATURE.length);
+ if (block.found && block.length === suggestedLength) {
+ return {
+ stream: new Stream(headerBytes),
+ length: suggestedLength
+ };
+ }
+ }
+ warn('Invalid "Length1" property in Type1 font -- trying to recover.');
+ stream.pos = streamStartPos;
+ // Reset the stream position.
+ var SCAN_BLOCK_LENGTH = 2048;
+ var actualLength;
+ while (true) {
+ var scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
+ block = findBlock(scanBytes, EEXEC_SIGNATURE, 0);
+ if (block.length === 0) {
+ break;
+ }
+ stream.pos += block.length;
+ // Update the stream position.
+ if (block.found) {
+ actualLength = stream.pos - streamStartPos;
+ break;
+ }
+ }
+ stream.pos = streamStartPos;
+ // Reset the stream position.
+ if (actualLength) {
+ return {
+ stream: new Stream(stream.getBytes(actualLength)),
+ length: actualLength
+ };
+ }
+ warn('Unable to recover "Length1" property in Type1 font -- using as is.');
+ return {
+ stream: new Stream(stream.getBytes(suggestedLength)),
+ length: suggestedLength
+ };
+ }
+ function getEexecBlock(stream, suggestedLength) {
+ // We should ideally parse the eexec block to ensure that `suggestedLength`
+ // is correct, so we don't truncate the block data if it's too small.
+ // However, this would also require checking if the fixed-content portion
+ // exists (using the 'Length3' property), and ensuring that it's valid.
+ //
+ // Given that `suggestedLength` almost always is correct, all the validation
+ // would require a great deal of unnecessary parsing for most fonts.
+ // To save time, we always fetch the entire stream instead, which also avoid
+ // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`).
+ //
+ // NOTE: This means that the function can include the fixed-content portion
+ // in the returned eexec block. In practice this does *not* seem to matter,
+ // since `Type1Parser_extractFontProgram` will skip over any non-commands.
+ var eexecBytes = stream.getBytes();
+ return {
+ stream: new Stream(eexecBytes),
+ length: eexecBytes.length
+ };
+ }
+ function Type1Font(name, file, properties) {
+ // Some bad generators embed pfb file as is, we have to strip 6-byte header.
+ // Also, length1 and length2 might be off by 6 bytes as well.
+ // http://www.math.ubc.ca/~cass/piscript/type1.pdf
+ var PFB_HEADER_SIZE = 6;
+ var headerBlockLength = properties.length1;
+ var eexecBlockLength = properties.length2;
+ var pfbHeader = file.peekBytes(PFB_HEADER_SIZE);
+ var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
+ if (pfbHeaderPresent) {
+ file.skip(PFB_HEADER_SIZE);
+ headerBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
+ }
+ // Get the data block containing glyphs and subrs information
+ var headerBlock = getHeaderBlock(file, headerBlockLength);
+ headerBlockLength = headerBlock.length;
+ var headerBlockParser = new Type1Parser(headerBlock.stream, false, SEAC_ANALYSIS_ENABLED);
+ headerBlockParser.extractFontHeader(properties);
+ if (pfbHeaderPresent) {
+ pfbHeader = file.getBytes(PFB_HEADER_SIZE);
+ eexecBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
+ }
+ // Decrypt the data blocks and retrieve it's content
+ var eexecBlock = getEexecBlock(file, eexecBlockLength);
+ eexecBlockLength = eexecBlock.length;
+ var eexecBlockParser = new Type1Parser(eexecBlock.stream, true, SEAC_ANALYSIS_ENABLED);
+ var data = eexecBlockParser.extractFontProgram();
+ for (var info in data.properties) {
+ properties[info] = data.properties[info];
+ }
+ var charstrings = data.charstrings;
+ var type2Charstrings = this.getType2Charstrings(charstrings);
+ var subrs = this.getType2Subrs(data.subrs);
+ this.charstrings = charstrings;
+ this.data = this.wrap(name, type2Charstrings, this.charstrings, subrs, properties);
+ this.seacs = this.getSeacs(data.charstrings);
+ }
+ Type1Font.prototype = {
+ get numGlyphs() {
+ return this.charstrings.length + 1;
+ },
+ getCharset: function Type1Font_getCharset() {
+ var charset = ['.notdef'];
+ var charstrings = this.charstrings;
+ for (var glyphId = 0; glyphId < charstrings.length; glyphId++) {
+ charset.push(charstrings[glyphId].glyphName);
+ }
+ return charset;
+ },
+ getGlyphMapping: function Type1Font_getGlyphMapping(properties) {
+ var charstrings = this.charstrings;
+ var glyphNames = ['.notdef'], glyphId;
+ for (glyphId = 0; glyphId < charstrings.length; glyphId++) {
+ glyphNames.push(charstrings[glyphId].glyphName);
+ }
+ var encoding = properties.builtInEncoding;
+ if (encoding) {
+ var builtInEncoding = Object.create(null);
+ for (var charCode in encoding) {
+ glyphId = glyphNames.indexOf(encoding[charCode]);
+ if (glyphId >= 0) {
+ builtInEncoding[charCode] = glyphId;
+ }
+ }
+ }
+ return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
+ },
+ getSeacs: function Type1Font_getSeacs(charstrings) {
+ var i, ii;
+ var seacMap = [];
+ for (i = 0, ii = charstrings.length; i < ii; i++) {
+ var charstring = charstrings[i];
+ if (charstring.seac) {
+ // Offset by 1 for .notdef
+ seacMap[i + 1] = charstring.seac;
+ }
+ }
+ return seacMap;
+ },
+ getType2Charstrings: function Type1Font_getType2Charstrings(type1Charstrings) {
+ var type2Charstrings = [];
+ for (var i = 0, ii = type1Charstrings.length; i < ii; i++) {
+ type2Charstrings.push(type1Charstrings[i].charstring);
+ }
+ return type2Charstrings;
+ },
+ getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) {
+ var bias = 0;
+ var count = type1Subrs.length;
+ if (count < 1133) {
+ bias = 107;
+ } else if (count < 33769) {
+ bias = 1131;
+ } else {
+ bias = 32768;
+ }
+ // Add a bunch of empty subrs to deal with the Type2 bias
+ var type2Subrs = [];
+ var i;
+ for (i = 0; i < bias; i++) {
+ type2Subrs.push([0x0B]);
+ }
+ for (i = 0; i < count; i++) {
+ type2Subrs.push(type1Subrs[i]);
+ }
+ return type2Subrs;
+ },
+ wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
+ var cff = new CFF();
+ cff.header = new CFFHeader(1, 0, 4, 4);
+ cff.names = [name];
+ var topDict = new CFFTopDict();
+ // CFF strings IDs 0...390 are predefined names, so refering
+ // to entries in our own String INDEX starts at SID 391.
+ topDict.setByName('version', 391);
+ topDict.setByName('Notice', 392);
+ topDict.setByName('FullName', 393);
+ topDict.setByName('FamilyName', 394);
+ topDict.setByName('Weight', 395);
+ topDict.setByName('Encoding', null);
+ // placeholder
+ topDict.setByName('FontMatrix', properties.fontMatrix);
+ topDict.setByName('FontBBox', properties.bbox);
+ topDict.setByName('charset', null);
+ // placeholder
+ topDict.setByName('CharStrings', null);
+ // placeholder
+ topDict.setByName('Private', null);
+ // placeholder
+ cff.topDict = topDict;
+ var strings = new CFFStrings();
+ strings.add('Version 0.11');
+ // Version
+ strings.add('See original notice');
+ // Notice
+ strings.add(name);
+ // FullName
+ strings.add(name);
+ // FamilyName
+ strings.add('Medium');
+ // Weight
+ cff.strings = strings;
+ cff.globalSubrIndex = new CFFIndex();
+ var count = glyphs.length;
+ var charsetArray = [0];
+ var i, ii;
+ for (i = 0; i < count; i++) {
+ var index = CFFStandardStrings.indexOf(charstrings[i].glyphName);
+ // TODO: Insert the string and correctly map it. Previously it was
+ // thought mapping names that aren't in the standard strings to .notdef
+ // was fine, however in issue818 when mapping them all to .notdef the
+ // adieresis glyph no longer worked.
+ if (index === -1) {
+ index = 0;
+ }
+ charsetArray.push(index >> 8 & 0xff, index & 0xff);
+ }
+ cff.charset = new CFFCharset(false, 0, [], charsetArray);
+ var charStringsIndex = new CFFIndex();
+ charStringsIndex.add([
+ 0x8B,
+ 0x0E
+ ]);
+ // .notdef
+ for (i = 0; i < count; i++) {
+ var glyph = glyphs[i];
+ // If the CharString outline is empty, replace it with .notdef to
+ // prevent OTS from rejecting the font (fixes bug1252420.pdf).
+ if (glyph.length === 0) {
+ charStringsIndex.add([
+ 0x8B,
+ 0x0E
+ ]);
+ // .notdef
+ continue;
+ }
+ charStringsIndex.add(glyph);
+ }
+ cff.charStrings = charStringsIndex;
+ var privateDict = new CFFPrivateDict();
+ privateDict.setByName('Subrs', null);
+ // placeholder
+ var fields = [
+ 'BlueValues',
+ 'OtherBlues',
+ 'FamilyBlues',
+ 'FamilyOtherBlues',
+ 'StemSnapH',
+ 'StemSnapV',
+ 'BlueShift',
+ 'BlueFuzz',
+ 'BlueScale',
+ 'LanguageGroup',
+ 'ExpansionFactor',
+ 'ForceBold',
+ 'StdHW',
+ 'StdVW'
+ ];
+ for (i = 0, ii = fields.length; i < ii; i++) {
+ var field = fields[i];
+ if (!(field in properties.privateData)) {
+ continue;
+ }
+ var value = properties.privateData[field];
+ if (isArray(value)) {
+ // All of the private dictionary array data in CFF must be stored as
+ // "delta-encoded" numbers.
+ for (var j = value.length - 1; j > 0; j--) {
+ value[j] -= value[j - 1];
+ }
+ }
+ // ... difference from previous value
+ privateDict.setByName(field, value);
+ }
+ cff.topDict.privateDict = privateDict;
+ var subrIndex = new CFFIndex();
+ for (i = 0, ii = subrs.length; i < ii; i++) {
+ subrIndex.add(subrs[i]);
+ }
+ privateDict.subrsIndex = subrIndex;
+ var compiler = new CFFCompiler(cff);
+ return compiler.compile();
+ }
+ };
+ return Type1Font;
+ }();
+ var CFFFont = function CFFFontClosure() {
+ function CFFFont(file, properties) {
+ this.properties = properties;
+ var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
+ this.cff = parser.parse();
+ var compiler = new CFFCompiler(this.cff);
+ this.seacs = this.cff.seacs;
+ try {
+ this.data = compiler.compile();
+ } catch (e) {
+ warn('Failed to compile font ' + properties.loadedName);
+ // There may have just been an issue with the compiler, set the data
+ // anyway and hope the font loaded.
+ this.data = file;
+ }
+ }
+ CFFFont.prototype = {
+ get numGlyphs() {
+ return this.cff.charStrings.count;
+ },
+ getCharset: function CFFFont_getCharset() {
+ return this.cff.charset.charset;
+ },
+ getGlyphMapping: function CFFFont_getGlyphMapping() {
+ var cff = this.cff;
+ var properties = this.properties;
+ var charsets = cff.charset.charset;
+ var charCodeToGlyphId;
+ var glyphId;
+ if (properties.composite) {
+ charCodeToGlyphId = Object.create(null);
+ if (cff.isCIDFont) {
+ // If the font is actually a CID font then we should use the charset
+ // to map CIDs to GIDs.
+ for (glyphId = 0; glyphId < charsets.length; glyphId++) {
+ var cid = charsets[glyphId];
+ var charCode = properties.cMap.charCodeOf(cid);
+ charCodeToGlyphId[charCode] = glyphId;
+ }
+ } else {
+ // If it is NOT actually a CID font then CIDs should be mapped
+ // directly to GIDs.
+ for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) {
+ charCodeToGlyphId[glyphId] = glyphId;
+ }
+ }
+ return charCodeToGlyphId;
+ }
+ var encoding = cff.encoding ? cff.encoding.encoding : null;
+ charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
+ return charCodeToGlyphId;
+ }
+ };
+ return CFFFont;
+ }();
+ // Workaround for seac on Windows.
+ (function checkSeacSupport() {
+ if (typeof navigator !== 'undefined' && /Windows/.test(navigator.userAgent)) {
+ SEAC_ANALYSIS_ENABLED = true;
+ }
+ }());
+ // Workaround for Private Use Area characters in Chrome on Windows
+ // http://code.google.com/p/chromium/issues/detail?id=122465
+ // https://github.com/mozilla/pdf.js/issues/1689
+ (function checkChromeWindows() {
+ if (typeof navigator !== 'undefined' && /Windows.*Chrome/.test(navigator.userAgent)) {
+ SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = true;
+ }
+ }());
+ exports.ErrorFont = ErrorFont;
+ exports.Font = Font;
+ exports.FontFlags = FontFlags;
+ exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
+ exports.ToUnicodeMap = ToUnicodeMap;
+ exports.getFontType = getFontType;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCorePsParser = {}, root.pdfjsSharedUtil, root.pdfjsCoreParser);
+ }(this, function (exports, sharedUtil, coreParser) {
+ var error = sharedUtil.error;
+ var isSpace = sharedUtil.isSpace;
+ var EOF = coreParser.EOF;
+ var PostScriptParser = function PostScriptParserClosure() {
+ function PostScriptParser(lexer) {
+ this.lexer = lexer;
+ this.operators = [];
+ this.token = null;
+ this.prev = null;
+ }
+ PostScriptParser.prototype = {
+ nextToken: function PostScriptParser_nextToken() {
+ this.prev = this.token;
+ this.token = this.lexer.getToken();
+ },
+ accept: function PostScriptParser_accept(type) {
+ if (this.token.type === type) {
+ this.nextToken();
+ return true;
+ }
+ return false;
+ },
+ expect: function PostScriptParser_expect(type) {
+ if (this.accept(type)) {
+ return true;
+ }
+ error('Unexpected symbol: found ' + this.token.type + ' expected ' + type + '.');
+ },
+ parse: function PostScriptParser_parse() {
+ this.nextToken();
+ this.expect(PostScriptTokenTypes.LBRACE);
+ this.parseBlock();
+ this.expect(PostScriptTokenTypes.RBRACE);
+ return this.operators;
+ },
+ parseBlock: function PostScriptParser_parseBlock() {
+ while (true) {
+ if (this.accept(PostScriptTokenTypes.NUMBER)) {
+ this.operators.push(this.prev.value);
+ } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
+ this.operators.push(this.prev.value);
+ } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+ this.parseCondition();
+ } else {
+ return;
+ }
+ }
+ },
+ parseCondition: function PostScriptParser_parseCondition() {
+ // Add two place holders that will be updated later
+ var conditionLocation = this.operators.length;
+ this.operators.push(null, null);
+ this.parseBlock();
+ this.expect(PostScriptTokenTypes.RBRACE);
+ if (this.accept(PostScriptTokenTypes.IF)) {
+ // The true block is right after the 'if' so it just falls through on
+ // true else it jumps and skips the true block.
+ this.operators[conditionLocation] = this.operators.length;
+ this.operators[conditionLocation + 1] = 'jz';
+ } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
+ var jumpLocation = this.operators.length;
+ this.operators.push(null, null);
+ var endOfTrue = this.operators.length;
+ this.parseBlock();
+ this.expect(PostScriptTokenTypes.RBRACE);
+ this.expect(PostScriptTokenTypes.IFELSE);
+ // The jump is added at the end of the true block to skip the false
+ // block.
+ this.operators[jumpLocation] = this.operators.length;
+ this.operators[jumpLocation + 1] = 'j';
+ this.operators[conditionLocation] = endOfTrue;
+ this.operators[conditionLocation + 1] = 'jz';
+ } else {
+ error('PS Function: error parsing conditional.');
+ }
+ }
+ };
+ return PostScriptParser;
+ }();
+ var PostScriptTokenTypes = {
+ LBRACE: 0,
+ RBRACE: 1,
+ NUMBER: 2,
+ OPERATOR: 3,
+ IF: 4,
+ IFELSE: 5
+ };
+ var PostScriptToken = function PostScriptTokenClosure() {
+ function PostScriptToken(type, value) {
+ this.type = type;
+ this.value = value;
+ }
+ var opCache = Object.create(null);
+ PostScriptToken.getOperator = function PostScriptToken_getOperator(op) {
+ var opValue = opCache[op];
+ if (opValue) {
+ return opValue;
+ }
+ return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
+ };
+ PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE, '{');
+ PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE, '}');
+ PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
+ PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE, 'IFELSE');
+ return PostScriptToken;
+ }();
+ var PostScriptLexer = function PostScriptLexerClosure() {
+ function PostScriptLexer(stream) {
+ this.stream = stream;
+ this.nextChar();
+ this.strBuf = [];
+ }
+ PostScriptLexer.prototype = {
+ nextChar: function PostScriptLexer_nextChar() {
+ return this.currentChar = this.stream.getByte();
+ },
+ getToken: function PostScriptLexer_getToken() {
+ var comment = false;
+ var ch = this.currentChar;
+ // skip comments
+ while (true) {
+ if (ch < 0) {
+ return EOF;
+ }
+ if (comment) {
+ if (ch === 0x0A || ch === 0x0D) {
+ comment = false;
+ }
+ } else if (ch === 0x25) {
+ // '%'
+ comment = true;
+ } else if (!isSpace(ch)) {
+ break;
+ }
+ ch = this.nextChar();
+ }
+ switch (ch | 0) {
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ // '0'-'4'
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ case 0x38:
+ case 0x39:
+ // '5'-'9'
+ case 0x2B:
+ case 0x2D:
+ case 0x2E:
+ // '+', '-', '.'
+ return new PostScriptToken(PostScriptTokenTypes.NUMBER, this.getNumber());
+ case 0x7B:
+ // '{'
+ this.nextChar();
+ return PostScriptToken.LBRACE;
+ case 0x7D:
+ // '}'
+ this.nextChar();
+ return PostScriptToken.RBRACE;
+ }
+ // operator
+ var strBuf = this.strBuf;
+ strBuf.length = 0;
+ strBuf[0] = String.fromCharCode(ch);
+ while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z'
+ (ch >= 0x41 && ch <= 0x5A || ch >= 0x61 && ch <= 0x7A)) {
+ strBuf.push(String.fromCharCode(ch));
+ }
+ var str = strBuf.join('');
+ switch (str.toLowerCase()) {
+ case 'if':
+ return PostScriptToken.IF;
+ case 'ifelse':
+ return PostScriptToken.IFELSE;
+ default:
+ return PostScriptToken.getOperator(str);
+ }
+ },
+ getNumber: function PostScriptLexer_getNumber() {
+ var ch = this.currentChar;
+ var strBuf = this.strBuf;
+ strBuf.length = 0;
+ strBuf[0] = String.fromCharCode(ch);
+ while ((ch = this.nextChar()) >= 0) {
+ if (ch >= 0x30 && ch <= 0x39 || // '0'-'9'
+ ch === 0x2D || ch === 0x2E) {
+ // '-', '.'
+ strBuf.push(String.fromCharCode(ch));
+ } else {
+ break;
+ }
+ }
+ var value = parseFloat(strBuf.join(''));
+ if (isNaN(value)) {
+ error('Invalid floating point number: ' + value);
+ }
+ return value;
+ }
+ };
+ return PostScriptLexer;
+ }();
+ exports.PostScriptLexer = PostScriptLexer;
+ exports.PostScriptParser = PostScriptParser;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreFunction = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCorePsParser);
+ }(this, function (exports, sharedUtil, corePrimitives, corePsParser) {
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var isBool = sharedUtil.isBool;
+ var isDict = corePrimitives.isDict;
+ var isStream = corePrimitives.isStream;
+ var PostScriptLexer = corePsParser.PostScriptLexer;
+ var PostScriptParser = corePsParser.PostScriptParser;
+ var PDFFunction = function PDFFunctionClosure() {
+ var CONSTRUCT_SAMPLED = 0;
+ var CONSTRUCT_INTERPOLATED = 2;
+ var CONSTRUCT_STICHED = 3;
+ var CONSTRUCT_POSTSCRIPT = 4;
+ return {
+ getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, str) {
+ var i, ii;
+ var length = 1;
+ for (i = 0, ii = size.length; i < ii; i++) {
+ length *= size[i];
+ }
+ length *= outputSize;
+ var array = new Array(length);
+ var codeSize = 0;
+ var codeBuf = 0;
+ // 32 is a valid bps so shifting won't work
+ var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1);
+ var strBytes = str.getBytes((length * bps + 7) / 8);
+ var strIdx = 0;
+ for (i = 0; i < length; i++) {
+ while (codeSize < bps) {
+ codeBuf <<= 8;
+ codeBuf |= strBytes[strIdx++];
+ codeSize += 8;
+ }
+ codeSize -= bps;
+ array[i] = (codeBuf >> codeSize) * sampleMul;
+ codeBuf &= (1 << codeSize) - 1;
+ }
+ return array;
+ },
+ getIR: function PDFFunction_getIR(xref, fn) {
+ var dict = fn.dict;
+ if (!dict) {
+ dict = fn;
+ }
+ var types = [
+ this.constructSampled,
+ null,
+ this.constructInterpolated,
+ this.constructStiched,
+ this.constructPostScript
+ ];
+ var typeNum = dict.get('FunctionType');
+ var typeFn = types[typeNum];
+ if (!typeFn) {
+ error('Unknown type of function');
+ }
+ return typeFn.call(this, fn, dict, xref);
+ },
+ fromIR: function PDFFunction_fromIR(IR) {
+ var type = IR[0];
+ switch (type) {
+ case CONSTRUCT_SAMPLED:
+ return this.constructSampledFromIR(IR);
+ case CONSTRUCT_INTERPOLATED:
+ return this.constructInterpolatedFromIR(IR);
+ case CONSTRUCT_STICHED:
+ return this.constructStichedFromIR(IR);
+ //case CONSTRUCT_POSTSCRIPT:
+ default:
+ return this.constructPostScriptFromIR(IR);
+ }
+ },
+ parse: function PDFFunction_parse(xref, fn) {
+ var IR = this.getIR(xref, fn);
+ return this.fromIR(IR);
+ },
+ parseArray: function PDFFunction_parseArray(xref, fnObj) {
+ if (!isArray(fnObj)) {
+ // not an array -- parsing as regular function
+ return this.parse(xref, fnObj);
+ }
+ var fnArray = [];
+ for (var j = 0, jj = fnObj.length; j < jj; j++) {
+ var obj = xref.fetchIfRef(fnObj[j]);
+ fnArray.push(PDFFunction.parse(xref, obj));
+ }
+ return function (src, srcOffset, dest, destOffset) {
+ for (var i = 0, ii = fnArray.length; i < ii; i++) {
+ fnArray[i](src, srcOffset, dest, destOffset + i);
+ }
+ };
+ },
+ constructSampled: function PDFFunction_constructSampled(str, dict) {
+ function toMultiArray(arr) {
+ var inputLength = arr.length;
+ var out = [];
+ var index = 0;
+ for (var i = 0; i < inputLength; i += 2) {
+ out[index] = [
+ arr[i],
+ arr[i + 1]
+ ];
+ ++index;
+ }
+ return out;
+ }
+ var domain = dict.getArray('Domain');
+ var range = dict.getArray('Range');
+ if (!domain || !range) {
+ error('No domain or range');
+ }
+ var inputSize = domain.length / 2;
+ var outputSize = range.length / 2;
+ domain = toMultiArray(domain);
+ range = toMultiArray(range);
+ var size = dict.get('Size');
+ var bps = dict.get('BitsPerSample');
+ var order = dict.get('Order') || 1;
+ if (order !== 1) {
+ // No description how cubic spline interpolation works in PDF32000:2008
+ // As in poppler, ignoring order, linear interpolation may work as good
+ info('No support for cubic spline interpolation: ' + order);
+ }
+ var encode = dict.getArray('Encode');
+ if (!encode) {
+ encode = [];
+ for (var i = 0; i < inputSize; ++i) {
+ encode.push(0);
+ encode.push(size[i] - 1);
+ }
+ }
+ encode = toMultiArray(encode);
+ var decode = dict.getArray('Decode');
+ if (!decode) {
+ decode = range;
+ } else {
+ decode = toMultiArray(decode);
+ }
+ var samples = this.getSampleArray(size, outputSize, bps, str);
+ return [
+ CONSTRUCT_SAMPLED,
+ inputSize,
+ domain,
+ encode,
+ decode,
+ samples,
+ size,
+ outputSize,
+ Math.pow(2, bps) - 1,
+ range
+ ];
+ },
+ constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) {
+ // See chapter 3, page 109 of the PDF reference
+ function interpolate(x, xmin, xmax, ymin, ymax) {
+ return ymin + (x - xmin) * ((ymax - ymin) / (xmax - xmin));
+ }
+ return function constructSampledFromIRResult(src, srcOffset, dest, destOffset) {
+ // See chapter 3, page 110 of the PDF reference.
+ var m = IR[1];
+ var domain = IR[2];
+ var encode = IR[3];
+ var decode = IR[4];
+ var samples = IR[5];
+ var size = IR[6];
+ var n = IR[7];
+ //var mask = IR[8];
+ var range = IR[9];
+ // Building the cube vertices: its part and sample index
+ // http://rjwagner49.com/Mathematics/Interpolation.pdf
+ var cubeVertices = 1 << m;
+ var cubeN = new Float64Array(cubeVertices);
+ var cubeVertex = new Uint32Array(cubeVertices);
+ var i, j;
+ for (j = 0; j < cubeVertices; j++) {
+ cubeN[j] = 1;
+ }
+ var k = n, pos = 1;
+ // Map x_i to y_j for 0 <= i < m using the sampled function.
+ for (i = 0; i < m; ++i) {
+ // x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
+ var domain_2i = domain[i][0];
+ var domain_2i_1 = domain[i][1];
+ var xi = Math.min(Math.max(src[srcOffset + i], domain_2i), domain_2i_1);
+ // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1,
+ // Encode_2i, Encode_2i+1)
+ var e = interpolate(xi, domain_2i, domain_2i_1, encode[i][0], encode[i][1]);
+ // e_i' = min(max(e_i, 0), Size_i - 1)
+ var size_i = size[i];
+ e = Math.min(Math.max(e, 0), size_i - 1);
+ // Adjusting the cube: N and vertex sample index
+ var e0 = e < size_i - 1 ? Math.floor(e) : e - 1;
+ // e1 = e0 + 1;
+ var n0 = e0 + 1 - e;
+ // (e1 - e) / (e1 - e0);
+ var n1 = e - e0;
+ // (e - e0) / (e1 - e0);
+ var offset0 = e0 * k;
+ var offset1 = offset0 + k;
+ // e1 * k
+ for (j = 0; j < cubeVertices; j++) {
+ if (j & pos) {
+ cubeN[j] *= n1;
+ cubeVertex[j] += offset1;
+ } else {
+ cubeN[j] *= n0;
+ cubeVertex[j] += offset0;
+ }
+ }
+ k *= size_i;
+ pos <<= 1;
+ }
+ for (j = 0; j < n; ++j) {
+ // Sum all cube vertices' samples portions
+ var rj = 0;
+ for (i = 0; i < cubeVertices; i++) {
+ rj += samples[cubeVertex[i] + j] * cubeN[i];
+ }
+ // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
+ // Decode_2j, Decode_2j+1)
+ rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
+ // y_j = min(max(r_j, range_2j), range_2j+1)
+ dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
+ }
+ };
+ },
+ constructInterpolated: function PDFFunction_constructInterpolated(str, dict) {
+ var c0 = dict.getArray('C0') || [0];
+ var c1 = dict.getArray('C1') || [1];
+ var n = dict.get('N');
+ if (!isArray(c0) || !isArray(c1)) {
+ error('Illegal dictionary for interpolated function');
+ }
+ var length = c0.length;
+ var diff = [];
+ for (var i = 0; i < length; ++i) {
+ diff.push(c1[i] - c0[i]);
+ }
+ return [
+ CONSTRUCT_INTERPOLATED,
+ c0,
+ diff,
+ n
+ ];
+ },
+ constructInterpolatedFromIR: function PDFFunction_constructInterpolatedFromIR(IR) {
+ var c0 = IR[1];
+ var diff = IR[2];
+ var n = IR[3];
+ var length = diff.length;
+ return function constructInterpolatedFromIRResult(src, srcOffset, dest, destOffset) {
+ var x = n === 1 ? src[srcOffset] : Math.pow(src[srcOffset], n);
+ for (var j = 0; j < length; ++j) {
+ dest[destOffset + j] = c0[j] + x * diff[j];
+ }
+ };
+ },
+ constructStiched: function PDFFunction_constructStiched(fn, dict, xref) {
+ var domain = dict.getArray('Domain');
+ if (!domain) {
+ error('No domain');
+ }
+ var inputSize = domain.length / 2;
+ if (inputSize !== 1) {
+ error('Bad domain for stiched function');
+ }
+ var fnRefs = dict.get('Functions');
+ var fns = [];
+ for (var i = 0, ii = fnRefs.length; i < ii; ++i) {
+ fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
+ }
+ var bounds = dict.getArray('Bounds');
+ var encode = dict.getArray('Encode');
+ return [
+ CONSTRUCT_STICHED,
+ domain,
+ bounds,
+ encode,
+ fns
+ ];
+ },
+ constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) {
+ var domain = IR[1];
+ var bounds = IR[2];
+ var encode = IR[3];
+ var fnsIR = IR[4];
+ var fns = [];
+ var tmpBuf = new Float32Array(1);
+ for (var i = 0, ii = fnsIR.length; i < ii; i++) {
+ fns.push(PDFFunction.fromIR(fnsIR[i]));
+ }
+ return function constructStichedFromIRResult(src, srcOffset, dest, destOffset) {
+ var clip = function constructStichedFromIRClip(v, min, max) {
+ if (v > max) {
+ v = max;
+ } else if (v < min) {
+ v = min;
+ }
+ return v;
+ };
+ // clip to domain
+ var v = clip(src[srcOffset], domain[0], domain[1]);
+ // calculate which bound the value is in
+ for (var i = 0, ii = bounds.length; i < ii; ++i) {
+ if (v < bounds[i]) {
+ break;
+ }
+ }
+ // encode value into domain of function
+ var dmin = domain[0];
+ if (i > 0) {
+ dmin = bounds[i - 1];
+ }
+ var dmax = domain[1];
+ if (i < bounds.length) {
+ dmax = bounds[i];
+ }
+ var rmin = encode[2 * i];
+ var rmax = encode[2 * i + 1];
+ // Prevent the value from becoming NaN as a result
+ // of division by zero (fixes issue6113.pdf).
+ tmpBuf[0] = dmin === dmax ? rmin : rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
+ // call the appropriate function
+ fns[i](tmpBuf, 0, dest, destOffset);
+ };
+ },
+ constructPostScript: function PDFFunction_constructPostScript(fn, dict, xref) {
+ var domain = dict.getArray('Domain');
+ var range = dict.getArray('Range');
+ if (!domain) {
+ error('No domain.');
+ }
+ if (!range) {
+ error('No range.');
+ }
+ var lexer = new PostScriptLexer(fn);
+ var parser = new PostScriptParser(lexer);
+ var code = parser.parse();
+ return [
+ CONSTRUCT_POSTSCRIPT,
+ domain,
+ range,
+ code
+ ];
+ },
+ constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR(IR) {
+ var domain = IR[1];
+ var range = IR[2];
+ var code = IR[3];
+ var compiled = new PostScriptCompiler().compile(code, domain, range);
+ if (compiled) {
+ // Compiled function consists of simple expressions such as addition,
+ // subtraction, Math.max, and also contains 'var' and 'return'
+ // statements. See the generation in the PostScriptCompiler below.
+ return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled);
+ }
+ info('Unable to compile PS function');
+ var numOutputs = range.length >> 1;
+ var numInputs = domain.length >> 1;
+ var evaluator = new PostScriptEvaluator(code);
+ // Cache the values for a big speed up, the cache size is limited though
+ // since the number of possible values can be huge from a PS function.
+ var cache = Object.create(null);
+ // The MAX_CACHE_SIZE is set to ~4x the maximum number of distinct values
+ // seen in our tests.
+ var MAX_CACHE_SIZE = 2048 * 4;
+ var cache_available = MAX_CACHE_SIZE;
+ var tmpBuf = new Float32Array(numInputs);
+ return function constructPostScriptFromIRResult(src, srcOffset, dest, destOffset) {
+ var i, value;
+ var key = '';
+ var input = tmpBuf;
+ for (i = 0; i < numInputs; i++) {
+ value = src[srcOffset + i];
+ input[i] = value;
+ key += value + '_';
+ }
+ var cachedValue = cache[key];
+ if (cachedValue !== undefined) {
+ dest.set(cachedValue, destOffset);
+ return;
+ }
+ var output = new Float32Array(numOutputs);
+ var stack = evaluator.execute(input);
+ var stackIndex = stack.length - numOutputs;
+ for (i = 0; i < numOutputs; i++) {
+ value = stack[stackIndex + i];
+ var bound = range[i * 2];
+ if (value < bound) {
+ value = bound;
+ } else {
+ bound = range[i * 2 + 1];
+ if (value > bound) {
+ value = bound;
+ }
+ }
+ output[i] = value;
+ }
+ if (cache_available > 0) {
+ cache_available--;
+ cache[key] = output;
+ }
+ dest.set(output, destOffset);
+ };
+ }
+ };
+ }();
+ function isPDFFunction(v) {
+ var fnDict;
+ if (typeof v !== 'object') {
+ return false;
+ } else if (isDict(v)) {
+ fnDict = v;
+ } else if (isStream(v)) {
+ fnDict = v.dict;
+ } else {
+ return false;
+ }
+ return fnDict.has('FunctionType');
+ }
+ var PostScriptStack = function PostScriptStackClosure() {
+ var MAX_STACK_SIZE = 100;
+ function PostScriptStack(initialStack) {
+ this.stack = !initialStack ? [] : Array.prototype.slice.call(initialStack, 0);
+ }
+ PostScriptStack.prototype = {
+ push: function PostScriptStack_push(value) {
+ if (this.stack.length >= MAX_STACK_SIZE) {
+ error('PostScript function stack overflow.');
+ }
+ this.stack.push(value);
+ },
+ pop: function PostScriptStack_pop() {
+ if (this.stack.length <= 0) {
+ error('PostScript function stack underflow.');
+ }
+ return this.stack.pop();
+ },
+ copy: function PostScriptStack_copy(n) {
+ if (this.stack.length + n >= MAX_STACK_SIZE) {
+ error('PostScript function stack overflow.');
+ }
+ var stack = this.stack;
+ for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
+ stack.push(stack[i]);
+ }
+ },
+ index: function PostScriptStack_index(n) {
+ this.push(this.stack[this.stack.length - n - 1]);
+ },
+ // rotate the last n stack elements p times
+ roll: function PostScriptStack_roll(n, p) {
+ var stack = this.stack;
+ var l = stack.length - n;
+ var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t;
+ for (i = l, j = r; i < j; i++, j--) {
+ t = stack[i];
+ stack[i] = stack[j];
+ stack[j] = t;
+ }
+ for (i = l, j = c - 1; i < j; i++, j--) {
+ t = stack[i];
+ stack[i] = stack[j];
+ stack[j] = t;
+ }
+ for (i = c, j = r; i < j; i++, j--) {
+ t = stack[i];
+ stack[i] = stack[j];
+ stack[j] = t;
+ }
+ }
+ };
+ return PostScriptStack;
+ }();
+ var PostScriptEvaluator = function PostScriptEvaluatorClosure() {
+ function PostScriptEvaluator(operators) {
+ this.operators = operators;
+ }
+ PostScriptEvaluator.prototype = {
+ execute: function PostScriptEvaluator_execute(initialStack) {
+ var stack = new PostScriptStack(initialStack);
+ var counter = 0;
+ var operators = this.operators;
+ var length = operators.length;
+ var operator, a, b;
+ while (counter < length) {
+ operator = operators[counter++];
+ if (typeof operator === 'number') {
+ // Operator is really an operand and should be pushed to the stack.
+ stack.push(operator);
+ continue;
+ }
+ switch (operator) {
+ // non standard ps operators
+ case 'jz':
+ // jump if false
+ b = stack.pop();
+ a = stack.pop();
+ if (!a) {
+ counter = b;
+ }
+ break;
+ case 'j':
+ // jump
+ a = stack.pop();
+ counter = a;
+ break;
+ // all ps operators in alphabetical order (excluding if/ifelse)
+ case 'abs':
+ a = stack.pop();
+ stack.push(Math.abs(a));
+ break;
+ case 'add':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a + b);
+ break;
+ case 'and':
+ b = stack.pop();
+ a = stack.pop();
+ if (isBool(a) && isBool(b)) {
+ stack.push(a && b);
+ } else {
+ stack.push(a & b);
+ }
+ break;
+ case 'atan':
+ a = stack.pop();
+ stack.push(Math.atan(a));
+ break;
+ case 'bitshift':
+ b = stack.pop();
+ a = stack.pop();
+ if (a > 0) {
+ stack.push(a << b);
+ } else {
+ stack.push(a >> b);
+ }
+ break;
+ case 'ceiling':
+ a = stack.pop();
+ stack.push(Math.ceil(a));
+ break;
+ case 'copy':
+ a = stack.pop();
+ stack.copy(a);
+ break;
+ case 'cos':
+ a = stack.pop();
+ stack.push(Math.cos(a));
+ break;
+ case 'cvi':
+ a = stack.pop() | 0;
+ stack.push(a);
+ break;
+ case 'cvr':
+ // noop
+ break;
+ case 'div':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a / b);
+ break;
+ case 'dup':
+ stack.copy(1);
+ break;
+ case 'eq':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a === b);
+ break;
+ case 'exch':
+ stack.roll(2, 1);
+ break;
+ case 'exp':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(Math.pow(a, b));
+ break;
+ case 'false':
+ stack.push(false);
+ break;
+ case 'floor':
+ a = stack.pop();
+ stack.push(Math.floor(a));
+ break;
+ case 'ge':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a >= b);
+ break;
+ case 'gt':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a > b);
+ break;
+ case 'idiv':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a / b | 0);
+ break;
+ case 'index':
+ a = stack.pop();
+ stack.index(a);
+ break;
+ case 'le':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a <= b);
+ break;
+ case 'ln':
+ a = stack.pop();
+ stack.push(Math.log(a));
+ break;
+ case 'log':
+ a = stack.pop();
+ stack.push(Math.log(a) / Math.LN10);
+ break;
+ case 'lt':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a < b);
+ break;
+ case 'mod':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a % b);
+ break;
+ case 'mul':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a * b);
+ break;
+ case 'ne':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a !== b);
+ break;
+ case 'neg':
+ a = stack.pop();
+ stack.push(-a);
+ break;
+ case 'not':
+ a = stack.pop();
+ if (isBool(a)) {
+ stack.push(!a);
+ } else {
+ stack.push(~a);
+ }
+ break;
+ case 'or':
+ b = stack.pop();
+ a = stack.pop();
+ if (isBool(a) && isBool(b)) {
+ stack.push(a || b);
+ } else {
+ stack.push(a | b);
+ }
+ break;
+ case 'pop':
+ stack.pop();
+ break;
+ case 'roll':
+ b = stack.pop();
+ a = stack.pop();
+ stack.roll(a, b);
+ break;
+ case 'round':
+ a = stack.pop();
+ stack.push(Math.round(a));
+ break;
+ case 'sin':
+ a = stack.pop();
+ stack.push(Math.sin(a));
+ break;
+ case 'sqrt':
+ a = stack.pop();
+ stack.push(Math.sqrt(a));
+ break;
+ case 'sub':
+ b = stack.pop();
+ a = stack.pop();
+ stack.push(a - b);
+ break;
+ case 'true':
+ stack.push(true);
+ break;
+ case 'truncate':
+ a = stack.pop();
+ a = a < 0 ? Math.ceil(a) : Math.floor(a);
+ stack.push(a);
+ break;
+ case 'xor':
+ b = stack.pop();
+ a = stack.pop();
+ if (isBool(a) && isBool(b)) {
+ stack.push(a !== b);
+ } else {
+ stack.push(a ^ b);
+ }
+ break;
+ default:
+ error('Unknown operator ' + operator);
+ break;
+ }
+ }
+ return stack.stack;
+ }
+ };
+ return PostScriptEvaluator;
+ }();
+ // Most of the PDFs functions consist of simple operations such as:
+ // roll, exch, sub, cvr, pop, index, dup, mul, if, gt, add.
+ //
+ // We can compile most of such programs, and at the same moment, we can
+ // optimize some expressions using basic math properties. Keeping track of
+ // min/max values will allow us to avoid extra Math.min/Math.max calls.
+ var PostScriptCompiler = function PostScriptCompilerClosure() {
+ function AstNode(type) {
+ this.type = type;
+ }
+ AstNode.prototype.visit = function (visitor) {
+ throw new Error('abstract method');
+ };
+ function AstArgument(index, min, max) {
+ AstNode.call(this, 'args');
+ this.index = index;
+ this.min = min;
+ this.max = max;
+ }
+ AstArgument.prototype = Object.create(AstNode.prototype);
+ AstArgument.prototype.visit = function (visitor) {
+ visitor.visitArgument(this);
+ };
+ function AstLiteral(number) {
+ AstNode.call(this, 'literal');
+ this.number = number;
+ this.min = number;
+ this.max = number;
+ }
+ AstLiteral.prototype = Object.create(AstNode.prototype);
+ AstLiteral.prototype.visit = function (visitor) {
+ visitor.visitLiteral(this);
+ };
+ function AstBinaryOperation(op, arg1, arg2, min, max) {
+ AstNode.call(this, 'binary');
+ this.op = op;
+ this.arg1 = arg1;
+ this.arg2 = arg2;
+ this.min = min;
+ this.max = max;
+ }
+ AstBinaryOperation.prototype = Object.create(AstNode.prototype);
+ AstBinaryOperation.prototype.visit = function (visitor) {
+ visitor.visitBinaryOperation(this);
+ };
+ function AstMin(arg, max) {
+ AstNode.call(this, 'max');
+ this.arg = arg;
+ this.min = arg.min;
+ this.max = max;
+ }
+ AstMin.prototype = Object.create(AstNode.prototype);
+ AstMin.prototype.visit = function (visitor) {
+ visitor.visitMin(this);
+ };
+ function AstVariable(index, min, max) {
+ AstNode.call(this, 'var');
+ this.index = index;
+ this.min = min;
+ this.max = max;
+ }
+ AstVariable.prototype = Object.create(AstNode.prototype);
+ AstVariable.prototype.visit = function (visitor) {
+ visitor.visitVariable(this);
+ };
+ function AstVariableDefinition(variable, arg) {
+ AstNode.call(this, 'definition');
+ this.variable = variable;
+ this.arg = arg;
+ }
+ AstVariableDefinition.prototype = Object.create(AstNode.prototype);
+ AstVariableDefinition.prototype.visit = function (visitor) {
+ visitor.visitVariableDefinition(this);
+ };
+ function ExpressionBuilderVisitor() {
+ this.parts = [];
+ }
+ ExpressionBuilderVisitor.prototype = {
+ visitArgument: function (arg) {
+ this.parts.push('Math.max(', arg.min, ', Math.min(', arg.max, ', src[srcOffset + ', arg.index, ']))');
+ },
+ visitVariable: function (variable) {
+ this.parts.push('v', variable.index);
+ },
+ visitLiteral: function (literal) {
+ this.parts.push(literal.number);
+ },
+ visitBinaryOperation: function (operation) {
+ this.parts.push('(');
+ operation.arg1.visit(this);
+ this.parts.push(' ', operation.op, ' ');
+ operation.arg2.visit(this);
+ this.parts.push(')');
+ },
+ visitVariableDefinition: function (definition) {
+ this.parts.push('var ');
+ definition.variable.visit(this);
+ this.parts.push(' = ');
+ definition.arg.visit(this);
+ this.parts.push(';');
+ },
+ visitMin: function (max) {
+ this.parts.push('Math.min(');
+ max.arg.visit(this);
+ this.parts.push(', ', max.max, ')');
+ },
+ toString: function () {
+ return this.parts.join('');
+ }
+ };
+ function buildAddOperation(num1, num2) {
+ if (num2.type === 'literal' && num2.number === 0) {
+ // optimization: second operand is 0
+ return num1;
+ }
+ if (num1.type === 'literal' && num1.number === 0) {
+ // optimization: first operand is 0
+ return num2;
+ }
+ if (num2.type === 'literal' && num1.type === 'literal') {
+ // optimization: operands operand are literals
+ return new AstLiteral(num1.number + num2.number);
+ }
+ return new AstBinaryOperation('+', num1, num2, num1.min + num2.min, num1.max + num2.max);
+ }
+ function buildMulOperation(num1, num2) {
+ if (num2.type === 'literal') {
+ // optimization: second operands is a literal...
+ if (num2.number === 0) {
+ return new AstLiteral(0);
+ } else // and it's 0
+ if (num2.number === 1) {
+ return num1;
+ } else // and it's 1
+ if (num1.type === 'literal') {
+ // ... and first operands is a literal too
+ return new AstLiteral(num1.number * num2.number);
+ }
+ }
+ if (num1.type === 'literal') {
+ // optimization: first operands is a literal...
+ if (num1.number === 0) {
+ return new AstLiteral(0);
+ } else // and it's 0
+ if (num1.number === 1) {
+ return num2;
+ }
+ }
+ // and it's 1
+ var min = Math.min(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
+ var max = Math.max(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
+ return new AstBinaryOperation('*', num1, num2, min, max);
+ }
+ function buildSubOperation(num1, num2) {
+ if (num2.type === 'literal') {
+ // optimization: second operands is a literal...
+ if (num2.number === 0) {
+ return num1;
+ } else // ... and it's 0
+ if (num1.type === 'literal') {
+ // ... and first operands is a literal too
+ return new AstLiteral(num1.number - num2.number);
+ }
+ }
+ if (num2.type === 'binary' && num2.op === '-' && num1.type === 'literal' && num1.number === 1 && num2.arg1.type === 'literal' && num2.arg1.number === 1) {
+ // optimization for case: 1 - (1 - x)
+ return num2.arg2;
+ }
+ return new AstBinaryOperation('-', num1, num2, num1.min - num2.max, num1.max - num2.min);
+ }
+ function buildMinOperation(num1, max) {
+ if (num1.min >= max) {
+ // optimization: num1 min value is not less than required max
+ return new AstLiteral(max);
+ } else // just returning max
+ if (num1.max <= max) {
+ // optimization: num1 max value is not greater than required max
+ return num1;
+ }
+ // just returning an argument
+ return new AstMin(num1, max);
+ }
+ function PostScriptCompiler() {
+ }
+ PostScriptCompiler.prototype = {
+ compile: function PostScriptCompiler_compile(code, domain, range) {
+ var stack = [];
+ var i, ii;
+ var instructions = [];
+ var inputSize = domain.length >> 1, outputSize = range.length >> 1;
+ var lastRegister = 0;
+ var n, j;
+ var num1, num2, ast1, ast2, tmpVar, item;
+ for (i = 0; i < inputSize; i++) {
+ stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1]));
+ }
+ for (i = 0, ii = code.length; i < ii; i++) {
+ item = code[i];
+ if (typeof item === 'number') {
+ stack.push(new AstLiteral(item));
+ continue;
+ }
+ switch (item) {
+ case 'add':
+ if (stack.length < 2) {
+ return null;
+ }
+ num2 = stack.pop();
+ num1 = stack.pop();
+ stack.push(buildAddOperation(num1, num2));
+ break;
+ case 'cvr':
+ if (stack.length < 1) {
+ return null;
+ }
+ break;
+ case 'mul':
+ if (stack.length < 2) {
+ return null;
+ }
+ num2 = stack.pop();
+ num1 = stack.pop();
+ stack.push(buildMulOperation(num1, num2));
+ break;
+ case 'sub':
+ if (stack.length < 2) {
+ return null;
+ }
+ num2 = stack.pop();
+ num1 = stack.pop();
+ stack.push(buildSubOperation(num1, num2));
+ break;
+ case 'exch':
+ if (stack.length < 2) {
+ return null;
+ }
+ ast1 = stack.pop();
+ ast2 = stack.pop();
+ stack.push(ast1, ast2);
+ break;
+ case 'pop':
+ if (stack.length < 1) {
+ return null;
+ }
+ stack.pop();
+ break;
+ case 'index':
+ if (stack.length < 1) {
+ return null;
+ }
+ num1 = stack.pop();
+ if (num1.type !== 'literal') {
+ return null;
+ }
+ n = num1.number;
+ if (n < 0 || (n | 0) !== n || stack.length < n) {
+ return null;
+ }
+ ast1 = stack[stack.length - n - 1];
+ if (ast1.type === 'literal' || ast1.type === 'var') {
+ stack.push(ast1);
+ break;
+ }
+ tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
+ stack[stack.length - n - 1] = tmpVar;
+ stack.push(tmpVar);
+ instructions.push(new AstVariableDefinition(tmpVar, ast1));
+ break;
+ case 'dup':
+ if (stack.length < 1) {
+ return null;
+ }
+ if (typeof code[i + 1] === 'number' && code[i + 2] === 'gt' && code[i + 3] === i + 7 && code[i + 4] === 'jz' && code[i + 5] === 'pop' && code[i + 6] === code[i + 1]) {
+ // special case of the commands sequence for the min operation
+ num1 = stack.pop();
+ stack.push(buildMinOperation(num1, code[i + 1]));
+ i += 6;
+ break;
+ }
+ ast1 = stack[stack.length - 1];
+ if (ast1.type === 'literal' || ast1.type === 'var') {
+ // we don't have to save into intermediate variable a literal or
+ // variable.
+ stack.push(ast1);
+ break;
+ }
+ tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
+ stack[stack.length - 1] = tmpVar;
+ stack.push(tmpVar);
+ instructions.push(new AstVariableDefinition(tmpVar, ast1));
+ break;
+ case 'roll':
+ if (stack.length < 2) {
+ return null;
+ }
+ num2 = stack.pop();
+ num1 = stack.pop();
+ if (num2.type !== 'literal' || num1.type !== 'literal') {
+ // both roll operands must be numbers
+ return null;
+ }
+ j = num2.number;
+ n = num1.number;
+ if (n <= 0 || (n | 0) !== n || (j | 0) !== j || stack.length < n) {
+ // ... and integers
+ return null;
+ }
+ j = (j % n + n) % n;
+ if (j === 0) {
+ break;
+ }
+ // just skipping -- there are nothing to rotate
+ Array.prototype.push.apply(stack, stack.splice(stack.length - n, n - j));
+ break;
+ default:
+ return null;
+ }
+ }
+ // unsupported operator
+ if (stack.length !== outputSize) {
+ return null;
+ }
+ var result = [];
+ instructions.forEach(function (instruction) {
+ var statementBuilder = new ExpressionBuilderVisitor();
+ instruction.visit(statementBuilder);
+ result.push(statementBuilder.toString());
+ });
+ stack.forEach(function (expr, i) {
+ var statementBuilder = new ExpressionBuilderVisitor();
+ expr.visit(statementBuilder);
+ var min = range[i * 2], max = range[i * 2 + 1];
+ var out = [statementBuilder.toString()];
+ if (min > expr.min) {
+ out.unshift('Math.max(', min, ', ');
+ out.push(')');
+ }
+ if (max < expr.max) {
+ out.unshift('Math.min(', max, ', ');
+ out.push(')');
+ }
+ out.unshift('dest[destOffset + ', i, '] = ');
+ out.push(';');
+ result.push(out.join(''));
+ });
+ return result.join('\n');
+ }
+ };
+ return PostScriptCompiler;
+ }();
+ exports.isPDFFunction = isPDFFunction;
+ exports.PDFFunction = PDFFunction;
+ exports.PostScriptEvaluator = PostScriptEvaluator;
+ exports.PostScriptCompiler = PostScriptCompiler;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreColorSpace = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreFunction);
+ }(this, function (exports, sharedUtil, corePrimitives, coreFunction) {
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var isString = sharedUtil.isString;
+ var shadow = sharedUtil.shadow;
+ var warn = sharedUtil.warn;
+ var isDict = corePrimitives.isDict;
+ var isName = corePrimitives.isName;
+ var isStream = corePrimitives.isStream;
+ var PDFFunction = coreFunction.PDFFunction;
+ var ColorSpace = function ColorSpaceClosure() {
+ /**
+ * Resizes an RGB image with 3 components.
+ * @param {TypedArray} src - The source buffer.
+ * @param {Number} bpc - Number of bits per component.
+ * @param {Number} w1 - Original width.
+ * @param {Number} h1 - Original height.
+ * @param {Number} w2 - New width.
+ * @param {Number} h2 - New height.
+ * @param {Number} alpha01 - Size reserved for the alpha channel.
+ * @param {TypedArray} dest - The destination buffer.
+ */
+ function resizeRgbImage(src, bpc, w1, h1, w2, h2, alpha01, dest) {
+ var COMPONENTS = 3;
+ alpha01 = alpha01 !== 1 ? 0 : alpha01;
+ var xRatio = w1 / w2;
+ var yRatio = h1 / h2;
+ var i, j, py, newIndex = 0, oldIndex;
+ var xScaled = new Uint16Array(w2);
+ var w1Scanline = w1 * COMPONENTS;
+ for (i = 0; i < w2; i++) {
+ xScaled[i] = Math.floor(i * xRatio) * COMPONENTS;
+ }
+ for (i = 0; i < h2; i++) {
+ py = Math.floor(i * yRatio) * w1Scanline;
+ for (j = 0; j < w2; j++) {
+ oldIndex = py + xScaled[j];
+ dest[newIndex++] = src[oldIndex++];
+ dest[newIndex++] = src[oldIndex++];
+ dest[newIndex++] = src[oldIndex++];
+ newIndex += alpha01;
+ }
+ }
+ }
+ // Constructor should define this.numComps, this.defaultColor, this.name
+ function ColorSpace() {
+ error('should not call ColorSpace constructor');
+ }
+ ColorSpace.prototype = {
+ /**
+ * Converts the color value to the RGB color. The color components are
+ * located in the src array starting from the srcOffset. Returns the array
+ * of the rgb components, each value ranging from [0,255].
+ */
+ getRgb: function ColorSpace_getRgb(src, srcOffset) {
+ var rgb = new Uint8Array(3);
+ this.getRgbItem(src, srcOffset, rgb, 0);
+ return rgb;
+ },
+ /**
+ * Converts the color value to the RGB color, similar to the getRgb method.
+ * The result placed into the dest array starting from the destOffset.
+ */
+ getRgbItem: function ColorSpace_getRgbItem(src, srcOffset, dest, destOffset) {
+ error('Should not call ColorSpace.getRgbItem');
+ },
+ /**
+ * Converts the specified number of the color values to the RGB colors.
+ * The colors are located in the src array starting from the srcOffset.
+ * The result is placed into the dest array starting from the destOffset.
+ * The src array items shall be in [0,2^bits) range, the dest array items
+ * will be in [0,255] range. alpha01 indicates how many alpha components
+ * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA
+ * array).
+ */
+ getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ error('Should not call ColorSpace.getRgbBuffer');
+ },
+ /**
+ * Determines the number of bytes required to store the result of the
+ * conversion done by the getRgbBuffer method. As in getRgbBuffer,
+ * |alpha01| is either 0 (RGB output) or 1 (RGBA output).
+ */
+ getOutputLength: function ColorSpace_getOutputLength(inputLength, alpha01) {
+ error('Should not call ColorSpace.getOutputLength');
+ },
+ /**
+ * Returns true if source data will be equal the result/output data.
+ */
+ isPassthrough: function ColorSpace_isPassthrough(bits) {
+ return false;
+ },
+ /**
+ * Fills in the RGB colors in the destination buffer. alpha01 indicates
+ * how many alpha components there are in the dest array; it will be either
+ * 0 (RGB array) or 1 (RGBA array).
+ */
+ fillRgb: function ColorSpace_fillRgb(dest, originalWidth, originalHeight, width, height, actualHeight, bpc, comps, alpha01) {
+ var count = originalWidth * originalHeight;
+ var rgbBuf = null;
+ var numComponentColors = 1 << bpc;
+ var needsResizing = originalHeight !== height || originalWidth !== width;
+ var i, ii;
+ if (this.isPassthrough(bpc)) {
+ rgbBuf = comps;
+ } else if (this.numComps === 1 && count > numComponentColors && this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') {
+ // Optimization: create a color map when there is just one component and
+ // we are converting more colors than the size of the color map. We
+ // don't build the map if the colorspace is gray or rgb since those
+ // methods are faster than building a map. This mainly offers big speed
+ // ups for indexed and alternate colorspaces.
+ //
+ // TODO it may be worth while to cache the color map. While running
+ // testing I never hit a cache so I will leave that out for now (perhaps
+ // we are reparsing colorspaces too much?).
+ var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : new Uint16Array(numComponentColors);
+ var key;
+ for (i = 0; i < numComponentColors; i++) {
+ allColors[i] = i;
+ }
+ var colorMap = new Uint8Array(numComponentColors * 3);
+ this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, /* alpha01 = */
+ 0);
+ var destPos, rgbPos;
+ if (!needsResizing) {
+ // Fill in the RGB values directly into |dest|.
+ destPos = 0;
+ for (i = 0; i < count; ++i) {
+ key = comps[i] * 3;
+ dest[destPos++] = colorMap[key];
+ dest[destPos++] = colorMap[key + 1];
+ dest[destPos++] = colorMap[key + 2];
+ destPos += alpha01;
+ }
+ } else {
+ rgbBuf = new Uint8Array(count * 3);
+ rgbPos = 0;
+ for (i = 0; i < count; ++i) {
+ key = comps[i] * 3;
+ rgbBuf[rgbPos++] = colorMap[key];
+ rgbBuf[rgbPos++] = colorMap[key + 1];
+ rgbBuf[rgbPos++] = colorMap[key + 2];
+ }
+ }
+ } else {
+ if (!needsResizing) {
+ // Fill in the RGB values directly into |dest|.
+ this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, alpha01);
+ } else {
+ rgbBuf = new Uint8Array(count * 3);
+ this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, /* alpha01 = */
+ 0);
+ }
+ }
+ if (rgbBuf) {
+ if (needsResizing) {
+ resizeRgbImage(rgbBuf, bpc, originalWidth, originalHeight, width, height, alpha01, dest);
+ } else {
+ rgbPos = 0;
+ destPos = 0;
+ for (i = 0, ii = width * actualHeight; i < ii; i++) {
+ dest[destPos++] = rgbBuf[rgbPos++];
+ dest[destPos++] = rgbBuf[rgbPos++];
+ dest[destPos++] = rgbBuf[rgbPos++];
+ destPos += alpha01;
+ }
+ }
+ }
+ },
+ /**
+ * True if the colorspace has components in the default range of [0, 1].
+ * This should be true for all colorspaces except for lab color spaces
+ * which are [0,100], [-128, 127], [-128, 127].
+ */
+ usesZeroToOneRange: true
+ };
+ ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
+ var IR = ColorSpace.parseToIR(cs, xref, res);
+ if (IR instanceof AlternateCS) {
+ return IR;
+ }
+ return ColorSpace.fromIR(IR);
+ };
+ ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
+ var name = isArray(IR) ? IR[0] : IR;
+ var whitePoint, blackPoint, gamma;
+ switch (name) {
+ case 'DeviceGrayCS':
+ return this.singletons.gray;
+ case 'DeviceRgbCS':
+ return this.singletons.rgb;
+ case 'DeviceCmykCS':
+ return this.singletons.cmyk;
+ case 'CalGrayCS':
+ whitePoint = IR[1];
+ blackPoint = IR[2];
+ gamma = IR[3];
+ return new CalGrayCS(whitePoint, blackPoint, gamma);
+ case 'CalRGBCS':
+ whitePoint = IR[1];
+ blackPoint = IR[2];
+ gamma = IR[3];
+ var matrix = IR[4];
+ return new CalRGBCS(whitePoint, blackPoint, gamma, matrix);
+ case 'PatternCS':
+ var basePatternCS = IR[1];
+ if (basePatternCS) {
+ basePatternCS = ColorSpace.fromIR(basePatternCS);
+ }
+ return new PatternCS(basePatternCS);
+ case 'IndexedCS':
+ var baseIndexedCS = IR[1];
+ var hiVal = IR[2];
+ var lookup = IR[3];
+ return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
+ case 'AlternateCS':
+ var numComps = IR[1];
+ var alt = IR[2];
+ var tintFnIR = IR[3];
+ return new AlternateCS(numComps, ColorSpace.fromIR(alt), PDFFunction.fromIR(tintFnIR));
+ case 'LabCS':
+ whitePoint = IR[1];
+ blackPoint = IR[2];
+ var range = IR[3];
+ return new LabCS(whitePoint, blackPoint, range);
+ default:
+ error('Unknown name ' + name);
+ }
+ return null;
+ };
+ ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) {
+ if (isName(cs)) {
+ var colorSpaces = res.get('ColorSpace');
+ if (isDict(colorSpaces)) {
+ var refcs = colorSpaces.get(cs.name);
+ if (refcs) {
+ cs = refcs;
+ }
+ }
+ }
+ cs = xref.fetchIfRef(cs);
+ var mode;
+ if (isName(cs)) {
+ mode = cs.name;
+ this.mode = mode;
+ switch (mode) {
+ case 'DeviceGray':
+ case 'G':
+ return 'DeviceGrayCS';
+ case 'DeviceRGB':
+ case 'RGB':
+ return 'DeviceRgbCS';
+ case 'DeviceCMYK':
+ case 'CMYK':
+ return 'DeviceCmykCS';
+ case 'Pattern':
+ return [
+ 'PatternCS',
+ null
+ ];
+ default:
+ error('unrecognized colorspace ' + mode);
+ }
+ } else if (isArray(cs)) {
+ mode = xref.fetchIfRef(cs[0]).name;
+ this.mode = mode;
+ var numComps, params, alt, whitePoint, blackPoint, gamma;
+ switch (mode) {
+ case 'DeviceGray':
+ case 'G':
+ return 'DeviceGrayCS';
+ case 'DeviceRGB':
+ case 'RGB':
+ return 'DeviceRgbCS';
+ case 'DeviceCMYK':
+ case 'CMYK':
+ return 'DeviceCmykCS';
+ case 'CalGray':
+ params = xref.fetchIfRef(cs[1]);
+ whitePoint = params.getArray('WhitePoint');
+ blackPoint = params.getArray('BlackPoint');
+ gamma = params.get('Gamma');
+ return [
+ 'CalGrayCS',
+ whitePoint,
+ blackPoint,
+ gamma
+ ];
+ case 'CalRGB':
+ params = xref.fetchIfRef(cs[1]);
+ whitePoint = params.getArray('WhitePoint');
+ blackPoint = params.getArray('BlackPoint');
+ gamma = params.getArray('Gamma');
+ var matrix = params.getArray('Matrix');
+ return [
+ 'CalRGBCS',
+ whitePoint,
+ blackPoint,
+ gamma,
+ matrix
+ ];
+ case 'ICCBased':
+ var stream = xref.fetchIfRef(cs[1]);
+ var dict = stream.dict;
+ numComps = dict.get('N');
+ alt = dict.get('Alternate');
+ if (alt) {
+ var altIR = ColorSpace.parseToIR(alt, xref, res);
+ // Parse the /Alternate CS to ensure that the number of components
+ // are correct, and also (indirectly) that it is not a PatternCS.
+ var altCS = ColorSpace.fromIR(altIR);
+ if (altCS.numComps === numComps) {
+ return altIR;
+ }
+ warn('ICCBased color space: Ignoring incorrect /Alternate entry.');
+ }
+ if (numComps === 1) {
+ return 'DeviceGrayCS';
+ } else if (numComps === 3) {
+ return 'DeviceRgbCS';
+ } else if (numComps === 4) {
+ return 'DeviceCmykCS';
+ }
+ break;
+ case 'Pattern':
+ var basePatternCS = cs[1] || null;
+ if (basePatternCS) {
+ basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
+ }
+ return [
+ 'PatternCS',
+ basePatternCS
+ ];
+ case 'Indexed':
+ case 'I':
+ var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
+ var hiVal = xref.fetchIfRef(cs[2]) + 1;
+ var lookup = xref.fetchIfRef(cs[3]);
+ if (isStream(lookup)) {
+ lookup = lookup.getBytes();
+ }
+ return [
+ 'IndexedCS',
+ baseIndexedCS,
+ hiVal,
+ lookup
+ ];
+ case 'Separation':
+ case 'DeviceN':
+ var name = xref.fetchIfRef(cs[1]);
+ numComps = 1;
+ if (isName(name)) {
+ numComps = 1;
+ } else if (isArray(name)) {
+ numComps = name.length;
+ }
+ alt = ColorSpace.parseToIR(cs[2], xref, res);
+ var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
+ return [
+ 'AlternateCS',
+ numComps,
+ alt,
+ tintFnIR
+ ];
+ case 'Lab':
+ params = xref.fetchIfRef(cs[1]);
+ whitePoint = params.getArray('WhitePoint');
+ blackPoint = params.getArray('BlackPoint');
+ var range = params.getArray('Range');
+ return [
+ 'LabCS',
+ whitePoint,
+ blackPoint,
+ range
+ ];
+ default:
+ error('unimplemented color space object "' + mode + '"');
+ }
+ } else {
+ error('unrecognized color space object: "' + cs + '"');
+ }
+ return null;
+ };
+ /**
+ * Checks if a decode map matches the default decode map for a color space.
+ * This handles the general decode maps where there are two values per
+ * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color.
+ * This does not handle Lab, Indexed, or Pattern decode maps since they are
+ * slightly different.
+ * @param {Array} decode Decode map (usually from an image).
+ * @param {Number} n Number of components the color space has.
+ */
+ ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
+ if (!isArray(decode)) {
+ return true;
+ }
+ if (n * 2 !== decode.length) {
+ warn('The decode map is not the correct length');
+ return true;
+ }
+ for (var i = 0, ii = decode.length; i < ii; i += 2) {
+ if (decode[i] !== 0 || decode[i + 1] !== 1) {
+ return false;
+ }
+ }
+ return true;
+ };
+ ColorSpace.singletons = {
+ get gray() {
+ return shadow(this, 'gray', new DeviceGrayCS());
+ },
+ get rgb() {
+ return shadow(this, 'rgb', new DeviceRgbCS());
+ },
+ get cmyk() {
+ return shadow(this, 'cmyk', new DeviceCmykCS());
+ }
+ };
+ return ColorSpace;
+ }();
+ /**
+ * Alternate color space handles both Separation and DeviceN color spaces. A
+ * Separation color space is actually just a DeviceN with one color component.
+ * Both color spaces use a tinting function to convert colors to a base color
+ * space.
+ */
+ var AlternateCS = function AlternateCSClosure() {
+ function AlternateCS(numComps, base, tintFn) {
+ this.name = 'Alternate';
+ this.numComps = numComps;
+ this.defaultColor = new Float32Array(numComps);
+ for (var i = 0; i < numComps; ++i) {
+ this.defaultColor[i] = 1;
+ }
+ this.base = base;
+ this.tintFn = tintFn;
+ this.tmpBuf = new Float32Array(base.numComps);
+ }
+ AlternateCS.prototype = {
+ getRgb: ColorSpace.prototype.getRgb,
+ getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ var tmpBuf = this.tmpBuf;
+ this.tintFn(src, srcOffset, tmpBuf, 0);
+ this.base.getRgbItem(tmpBuf, 0, dest, destOffset);
+ },
+ getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ var tintFn = this.tintFn;
+ var base = this.base;
+ var scale = 1 / ((1 << bits) - 1);
+ var baseNumComps = base.numComps;
+ var usesZeroToOneRange = base.usesZeroToOneRange;
+ var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0;
+ var pos = isPassthrough ? destOffset : 0;
+ var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count);
+ var numComps = this.numComps;
+ var scaled = new Float32Array(numComps);
+ var tinted = new Float32Array(baseNumComps);
+ var i, j;
+ if (usesZeroToOneRange) {
+ for (i = 0; i < count; i++) {
+ for (j = 0; j < numComps; j++) {
+ scaled[j] = src[srcOffset++] * scale;
+ }
+ tintFn(scaled, 0, tinted, 0);
+ for (j = 0; j < baseNumComps; j++) {
+ baseBuf[pos++] = tinted[j] * 255;
+ }
+ }
+ } else {
+ for (i = 0; i < count; i++) {
+ for (j = 0; j < numComps; j++) {
+ scaled[j] = src[srcOffset++] * scale;
+ }
+ tintFn(scaled, 0, tinted, 0);
+ base.getRgbItem(tinted, 0, baseBuf, pos);
+ pos += baseNumComps;
+ }
+ }
+ if (!isPassthrough) {
+ base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
+ }
+ },
+ getOutputLength: function AlternateCS_getOutputLength(inputLength, alpha01) {
+ return this.base.getOutputLength(inputLength * this.base.numComps / this.numComps, alpha01);
+ },
+ isPassthrough: ColorSpace.prototype.isPassthrough,
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ },
+ usesZeroToOneRange: true
+ };
+ return AlternateCS;
+ }();
+ var PatternCS = function PatternCSClosure() {
+ function PatternCS(baseCS) {
+ this.name = 'Pattern';
+ this.base = baseCS;
+ }
+ PatternCS.prototype = {};
+ return PatternCS;
+ }();
+ var IndexedCS = function IndexedCSClosure() {
+ function IndexedCS(base, highVal, lookup) {
+ this.name = 'Indexed';
+ this.numComps = 1;
+ this.defaultColor = new Uint8Array([0]);
+ this.base = base;
+ this.highVal = highVal;
+ var baseNumComps = base.numComps;
+ var length = baseNumComps * highVal;
+ var lookupArray;
+ if (isStream(lookup)) {
+ lookupArray = new Uint8Array(length);
+ var bytes = lookup.getBytes(length);
+ lookupArray.set(bytes);
+ } else if (isString(lookup)) {
+ lookupArray = new Uint8Array(length);
+ for (var i = 0; i < length; ++i) {
+ lookupArray[i] = lookup.charCodeAt(i);
+ }
+ } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
+ lookupArray = lookup;
+ } else {
+ error('Unrecognized lookup table: ' + lookup);
+ }
+ this.lookup = lookupArray;
+ }
+ IndexedCS.prototype = {
+ getRgb: ColorSpace.prototype.getRgb,
+ getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ var numComps = this.base.numComps;
+ var start = src[srcOffset] * numComps;
+ this.base.getRgbItem(this.lookup, start, dest, destOffset);
+ },
+ getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ var base = this.base;
+ var numComps = base.numComps;
+ var outputDelta = base.getOutputLength(numComps, alpha01);
+ var lookup = this.lookup;
+ for (var i = 0; i < count; ++i) {
+ var lookupPos = src[srcOffset++] * numComps;
+ base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01);
+ destOffset += outputDelta;
+ }
+ },
+ getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) {
+ return this.base.getOutputLength(inputLength * this.base.numComps, alpha01);
+ },
+ isPassthrough: ColorSpace.prototype.isPassthrough,
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) {
+ // indexed color maps shouldn't be changed
+ return true;
+ },
+ usesZeroToOneRange: true
+ };
+ return IndexedCS;
+ }();
+ var DeviceGrayCS = function DeviceGrayCSClosure() {
+ function DeviceGrayCS() {
+ this.name = 'DeviceGray';
+ this.numComps = 1;
+ this.defaultColor = new Float32Array([0]);
+ }
+ DeviceGrayCS.prototype = {
+ getRgb: ColorSpace.prototype.getRgb,
+ getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ var c = src[srcOffset] * 255 | 0;
+ c = c < 0 ? 0 : c > 255 ? 255 : c;
+ dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
+ },
+ getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ var scale = 255 / ((1 << bits) - 1);
+ var j = srcOffset, q = destOffset;
+ for (var i = 0; i < count; ++i) {
+ var c = scale * src[j++] | 0;
+ dest[q++] = c;
+ dest[q++] = c;
+ dest[q++] = c;
+ q += alpha01;
+ }
+ },
+ getOutputLength: function DeviceGrayCS_getOutputLength(inputLength, alpha01) {
+ return inputLength * (3 + alpha01);
+ },
+ isPassthrough: ColorSpace.prototype.isPassthrough,
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ },
+ usesZeroToOneRange: true
+ };
+ return DeviceGrayCS;
+ }();
+ var DeviceRgbCS = function DeviceRgbCSClosure() {
+ function DeviceRgbCS() {
+ this.name = 'DeviceRGB';
+ this.numComps = 3;
+ this.defaultColor = new Float32Array([
+ 0,
+ 0,
+ 0
+ ]);
+ }
+ DeviceRgbCS.prototype = {
+ getRgb: ColorSpace.prototype.getRgb,
+ getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ var r = src[srcOffset] * 255 | 0;
+ var g = src[srcOffset + 1] * 255 | 0;
+ var b = src[srcOffset + 2] * 255 | 0;
+ dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r;
+ dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g;
+ dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b;
+ },
+ getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ if (bits === 8 && alpha01 === 0) {
+ dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset);
+ return;
+ }
+ var scale = 255 / ((1 << bits) - 1);
+ var j = srcOffset, q = destOffset;
+ for (var i = 0; i < count; ++i) {
+ dest[q++] = scale * src[j++] | 0;
+ dest[q++] = scale * src[j++] | 0;
+ dest[q++] = scale * src[j++] | 0;
+ q += alpha01;
+ }
+ },
+ getOutputLength: function DeviceRgbCS_getOutputLength(inputLength, alpha01) {
+ return inputLength * (3 + alpha01) / 3 | 0;
+ },
+ isPassthrough: function DeviceRgbCS_isPassthrough(bits) {
+ return bits === 8;
+ },
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ },
+ usesZeroToOneRange: true
+ };
+ return DeviceRgbCS;
+ }();
+ var DeviceCmykCS = function DeviceCmykCSClosure() {
+ // The coefficients below was found using numerical analysis: the method of
+ // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors,
+ // where color_value is the tabular value from the table of sampled RGB colors
+ // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding
+ // CMYK color conversion using the estimation below:
+ // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255
+ function convertToRgb(src, srcOffset, srcScale, dest, destOffset) {
+ var c = src[srcOffset + 0] * srcScale;
+ var m = src[srcOffset + 1] * srcScale;
+ var y = src[srcOffset + 2] * srcScale;
+ var k = src[srcOffset + 3] * srcScale;
+ var r = c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k + -285.2331026137004) + m * (1.7149763477362134 * m - 5.6096736904047315 * y + -17.873870861415444 * k - 5.497006427196366) + y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) + k * (-21.86122147463605 * k - 189.48180835922747) + 255 | 0;
+ var g = c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k + -79.2970844816548) + m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + k * (-20.737325471181034 * k - 187.80453709719578) + 255 | 0;
+ var b = c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k + -14.183576799673286) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) + y * (0.03296041114873217 * y + 115.60384449646641 * k + -193.58209356861505) + k * (-22.33816807309886 * k - 180.12613974708367) + 255 | 0;
+ dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
+ dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
+ dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
+ }
+ function DeviceCmykCS() {
+ this.name = 'DeviceCMYK';
+ this.numComps = 4;
+ this.defaultColor = new Float32Array([
+ 0,
+ 0,
+ 0,
+ 1
+ ]);
+ }
+ DeviceCmykCS.prototype = {
+ getRgb: ColorSpace.prototype.getRgb,
+ getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ convertToRgb(src, srcOffset, 1, dest, destOffset);
+ },
+ getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ var scale = 1 / ((1 << bits) - 1);
+ for (var i = 0; i < count; i++) {
+ convertToRgb(src, srcOffset, scale, dest, destOffset);
+ srcOffset += 4;
+ destOffset += 3 + alpha01;
+ }
+ },
+ getOutputLength: function DeviceCmykCS_getOutputLength(inputLength, alpha01) {
+ return inputLength / 4 * (3 + alpha01) | 0;
+ },
+ isPassthrough: ColorSpace.prototype.isPassthrough,
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ },
+ usesZeroToOneRange: true
+ };
+ return DeviceCmykCS;
+ }();
+ //
+ // CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245
+ //
+ var CalGrayCS = function CalGrayCSClosure() {
+ function CalGrayCS(whitePoint, blackPoint, gamma) {
+ this.name = 'CalGray';
+ this.numComps = 1;
+ this.defaultColor = new Float32Array([0]);
+ if (!whitePoint) {
+ error('WhitePoint missing - required for color space CalGray');
+ }
+ blackPoint = blackPoint || [
+ 0,
+ 0,
+ 0
+ ];
+ gamma = gamma || 1;
+ // Translate arguments to spec variables.
+ this.XW = whitePoint[0];
+ this.YW = whitePoint[1];
+ this.ZW = whitePoint[2];
+ this.XB = blackPoint[0];
+ this.YB = blackPoint[1];
+ this.ZB = blackPoint[2];
+ this.G = gamma;
+ // Validate variables as per spec.
+ if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
+ error('Invalid WhitePoint components for ' + this.name + ', no fallback available');
+ }
+ if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
+ info('Invalid BlackPoint for ' + this.name + ', falling back to default');
+ this.XB = this.YB = this.ZB = 0;
+ }
+ if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
+ warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + ', ZB: ' + this.ZB + ', only default values are supported.');
+ }
+ if (this.G < 1) {
+ info('Invalid Gamma: ' + this.G + ' for ' + this.name + ', falling back to default');
+ this.G = 1;
+ }
+ }
+ function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
+ // A represents a gray component of a calibrated gray space.
+ // A <---> AG in the spec
+ var A = src[srcOffset] * scale;
+ var AG = Math.pow(A, cs.G);
+ // Computes L as per spec. ( = cs.YW * AG )
+ // Except if other than default BlackPoint values are used.
+ var L = cs.YW * AG;
+ // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
+ // Convert values to rgb range [0, 255].
+ var val = Math.max(295.8 * Math.pow(L, 0.333333333333333333) - 40.8, 0) | 0;
+ dest[destOffset] = val;
+ dest[destOffset + 1] = val;
+ dest[destOffset + 2] = val;
+ }
+ CalGrayCS.prototype = {
+ getRgb: ColorSpace.prototype.getRgb,
+ getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ convertToRgb(this, src, srcOffset, dest, destOffset, 1);
+ },
+ getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ var scale = 1 / ((1 << bits) - 1);
+ for (var i = 0; i < count; ++i) {
+ convertToRgb(this, src, srcOffset, dest, destOffset, scale);
+ srcOffset += 1;
+ destOffset += 3 + alpha01;
+ }
+ },
+ getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) {
+ return inputLength * (3 + alpha01);
+ },
+ isPassthrough: ColorSpace.prototype.isPassthrough,
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ },
+ usesZeroToOneRange: true
+ };
+ return CalGrayCS;
+ }();
+ //
+ // CalRGBCS: Based on "PDF Reference, Sixth Ed", p.247
+ //
+ var CalRGBCS = function CalRGBCSClosure() {
+ // See http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html for these
+ // matrices.
+ var BRADFORD_SCALE_MATRIX = new Float32Array([
+ 0.8951,
+ 0.2664,
+ -0.1614,
+ -0.7502,
+ 1.7135,
+ 0.0367,
+ 0.0389,
+ -0.0685,
+ 1.0296
+ ]);
+ var BRADFORD_SCALE_INVERSE_MATRIX = new Float32Array([
+ 0.9869929,
+ -0.1470543,
+ 0.1599627,
+ 0.4323053,
+ 0.5183603,
+ 0.0492912,
+ -0.0085287,
+ 0.0400428,
+ 0.9684867
+ ]);
+ // See http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html.
+ var SRGB_D65_XYZ_TO_RGB_MATRIX = new Float32Array([
+ 3.2404542,
+ -1.5371385,
+ -0.4985314,
+ -0.9692660,
+ 1.8760108,
+ 0.0415560,
+ 0.0556434,
+ -0.2040259,
+ 1.0572252
+ ]);
+ var FLAT_WHITEPOINT_MATRIX = new Float32Array([
+ 1,
+ 1,
+ 1
+ ]);
+ var tempNormalizeMatrix = new Float32Array(3);
+ var tempConvertMatrix1 = new Float32Array(3);
+ var tempConvertMatrix2 = new Float32Array(3);
+ var DECODE_L_CONSTANT = Math.pow((8 + 16) / 116, 3) / 8.0;
+ function CalRGBCS(whitePoint, blackPoint, gamma, matrix) {
+ this.name = 'CalRGB';
+ this.numComps = 3;
+ this.defaultColor = new Float32Array(3);
+ if (!whitePoint) {
+ error('WhitePoint missing - required for color space CalRGB');
+ }
+ blackPoint = blackPoint || new Float32Array(3);
+ gamma = gamma || new Float32Array([
+ 1,
+ 1,
+ 1
+ ]);
+ matrix = matrix || new Float32Array([
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1
+ ]);
+ // Translate arguments to spec variables.
+ var XW = whitePoint[0];
+ var YW = whitePoint[1];
+ var ZW = whitePoint[2];
+ this.whitePoint = whitePoint;
+ var XB = blackPoint[0];
+ var YB = blackPoint[1];
+ var ZB = blackPoint[2];
+ this.blackPoint = blackPoint;
+ this.GR = gamma[0];
+ this.GG = gamma[1];
+ this.GB = gamma[2];
+ this.MXA = matrix[0];
+ this.MYA = matrix[1];
+ this.MZA = matrix[2];
+ this.MXB = matrix[3];
+ this.MYB = matrix[4];
+ this.MZB = matrix[5];
+ this.MXC = matrix[6];
+ this.MYC = matrix[7];
+ this.MZC = matrix[8];
+ // Validate variables as per spec.
+ if (XW < 0 || ZW < 0 || YW !== 1) {
+ error('Invalid WhitePoint components for ' + this.name + ', no fallback available');
+ }
+ if (XB < 0 || YB < 0 || ZB < 0) {
+ info('Invalid BlackPoint for ' + this.name + ' [' + XB + ', ' + YB + ', ' + ZB + '], falling back to default');
+ this.blackPoint = new Float32Array(3);
+ }
+ if (this.GR < 0 || this.GG < 0 || this.GB < 0) {
+ info('Invalid Gamma [' + this.GR + ', ' + this.GG + ', ' + this.GB + '] for ' + this.name + ', falling back to default');
+ this.GR = this.GG = this.GB = 1;
+ }
+ if (this.MXA < 0 || this.MYA < 0 || this.MZA < 0 || this.MXB < 0 || this.MYB < 0 || this.MZB < 0 || this.MXC < 0 || this.MYC < 0 || this.MZC < 0) {
+ info('Invalid Matrix for ' + this.name + ' [' + this.MXA + ', ' + this.MYA + ', ' + this.MZA + this.MXB + ', ' + this.MYB + ', ' + this.MZB + this.MXC + ', ' + this.MYC + ', ' + this.MZC + '], falling back to default');
+ this.MXA = this.MYB = this.MZC = 1;
+ this.MXB = this.MYA = this.MZA = this.MXC = this.MYC = this.MZB = 0;
+ }
+ }
+ function matrixProduct(a, b, result) {
+ result[0] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+ result[1] = a[3] * b[0] + a[4] * b[1] + a[5] * b[2];
+ result[2] = a[6] * b[0] + a[7] * b[1] + a[8] * b[2];
+ }
+ function convertToFlat(sourceWhitePoint, LMS, result) {
+ result[0] = LMS[0] * 1 / sourceWhitePoint[0];
+ result[1] = LMS[1] * 1 / sourceWhitePoint[1];
+ result[2] = LMS[2] * 1 / sourceWhitePoint[2];
+ }
+ function convertToD65(sourceWhitePoint, LMS, result) {
+ var D65X = 0.95047;
+ var D65Y = 1;
+ var D65Z = 1.08883;
+ result[0] = LMS[0] * D65X / sourceWhitePoint[0];
+ result[1] = LMS[1] * D65Y / sourceWhitePoint[1];
+ result[2] = LMS[2] * D65Z / sourceWhitePoint[2];
+ }
+ function sRGBTransferFunction(color) {
+ // See http://en.wikipedia.org/wiki/SRGB.
+ if (color <= 0.0031308) {
+ return adjustToRange(0, 1, 12.92 * color);
+ }
+ return adjustToRange(0, 1, (1 + 0.055) * Math.pow(color, 1 / 2.4) - 0.055);
+ }
+ function adjustToRange(min, max, value) {
+ return Math.max(min, Math.min(max, value));
+ }
+ function decodeL(L) {
+ if (L < 0) {
+ return -decodeL(-L);
+ }
+ if (L > 8.0) {
+ return Math.pow((L + 16) / 116, 3);
+ }
+ return L * DECODE_L_CONSTANT;
+ }
+ function compensateBlackPoint(sourceBlackPoint, XYZ_Flat, result) {
+ // In case the blackPoint is already the default blackPoint then there is
+ // no need to do compensation.
+ if (sourceBlackPoint[0] === 0 && sourceBlackPoint[1] === 0 && sourceBlackPoint[2] === 0) {
+ result[0] = XYZ_Flat[0];
+ result[1] = XYZ_Flat[1];
+ result[2] = XYZ_Flat[2];
+ return;
+ }
+ // For the blackPoint calculation details, please see
+ // http://www.adobe.com/content/dam/Adobe/en/devnet/photoshop/sdk/
+ // AdobeBPC.pdf.
+ // The destination blackPoint is the default blackPoint [0, 0, 0].
+ var zeroDecodeL = decodeL(0);
+ var X_DST = zeroDecodeL;
+ var X_SRC = decodeL(sourceBlackPoint[0]);
+ var Y_DST = zeroDecodeL;
+ var Y_SRC = decodeL(sourceBlackPoint[1]);
+ var Z_DST = zeroDecodeL;
+ var Z_SRC = decodeL(sourceBlackPoint[2]);
+ var X_Scale = (1 - X_DST) / (1 - X_SRC);
+ var X_Offset = 1 - X_Scale;
+ var Y_Scale = (1 - Y_DST) / (1 - Y_SRC);
+ var Y_Offset = 1 - Y_Scale;
+ var Z_Scale = (1 - Z_DST) / (1 - Z_SRC);
+ var Z_Offset = 1 - Z_Scale;
+ result[0] = XYZ_Flat[0] * X_Scale + X_Offset;
+ result[1] = XYZ_Flat[1] * Y_Scale + Y_Offset;
+ result[2] = XYZ_Flat[2] * Z_Scale + Z_Offset;
+ }
+ function normalizeWhitePointToFlat(sourceWhitePoint, XYZ_In, result) {
+ // In case the whitePoint is already flat then there is no need to do
+ // normalization.
+ if (sourceWhitePoint[0] === 1 && sourceWhitePoint[2] === 1) {
+ result[0] = XYZ_In[0];
+ result[1] = XYZ_In[1];
+ result[2] = XYZ_In[2];
+ return;
+ }
+ var LMS = result;
+ matrixProduct(BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
+ var LMS_Flat = tempNormalizeMatrix;
+ convertToFlat(sourceWhitePoint, LMS, LMS_Flat);
+ matrixProduct(BRADFORD_SCALE_INVERSE_MATRIX, LMS_Flat, result);
+ }
+ function normalizeWhitePointToD65(sourceWhitePoint, XYZ_In, result) {
+ var LMS = result;
+ matrixProduct(BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
+ var LMS_D65 = tempNormalizeMatrix;
+ convertToD65(sourceWhitePoint, LMS, LMS_D65);
+ matrixProduct(BRADFORD_SCALE_INVERSE_MATRIX, LMS_D65, result);
+ }
+ function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
+ // A, B and C represent a red, green and blue components of a calibrated
+ // rgb space.
+ var A = adjustToRange(0, 1, src[srcOffset] * scale);
+ var B = adjustToRange(0, 1, src[srcOffset + 1] * scale);
+ var C = adjustToRange(0, 1, src[srcOffset + 2] * scale);
+ // A <---> AGR in the spec
+ // B <---> BGG in the spec
+ // C <---> CGB in the spec
+ var AGR = Math.pow(A, cs.GR);
+ var BGG = Math.pow(B, cs.GG);
+ var CGB = Math.pow(C, cs.GB);
+ // Computes intermediate variables L, M, N as per spec.
+ // To decode X, Y, Z values map L, M, N directly to them.
+ var X = cs.MXA * AGR + cs.MXB * BGG + cs.MXC * CGB;
+ var Y = cs.MYA * AGR + cs.MYB * BGG + cs.MYC * CGB;
+ var Z = cs.MZA * AGR + cs.MZB * BGG + cs.MZC * CGB;
+ // The following calculations are based on this document:
+ // http://www.adobe.com/content/dam/Adobe/en/devnet/photoshop/sdk/
+ // AdobeBPC.pdf.
+ var XYZ = tempConvertMatrix1;
+ XYZ[0] = X;
+ XYZ[1] = Y;
+ XYZ[2] = Z;
+ var XYZ_Flat = tempConvertMatrix2;
+ normalizeWhitePointToFlat(cs.whitePoint, XYZ, XYZ_Flat);
+ var XYZ_Black = tempConvertMatrix1;
+ compensateBlackPoint(cs.blackPoint, XYZ_Flat, XYZ_Black);
+ var XYZ_D65 = tempConvertMatrix2;
+ normalizeWhitePointToD65(FLAT_WHITEPOINT_MATRIX, XYZ_Black, XYZ_D65);
+ var SRGB = tempConvertMatrix1;
+ matrixProduct(SRGB_D65_XYZ_TO_RGB_MATRIX, XYZ_D65, SRGB);
+ var sR = sRGBTransferFunction(SRGB[0]);
+ var sG = sRGBTransferFunction(SRGB[1]);
+ var sB = sRGBTransferFunction(SRGB[2]);
+ // Convert the values to rgb range [0, 255].
+ dest[destOffset] = Math.round(sR * 255);
+ dest[destOffset + 1] = Math.round(sG * 255);
+ dest[destOffset + 2] = Math.round(sB * 255);
+ }
+ CalRGBCS.prototype = {
+ getRgb: function CalRGBCS_getRgb(src, srcOffset) {
+ var rgb = new Uint8Array(3);
+ this.getRgbItem(src, srcOffset, rgb, 0);
+ return rgb;
+ },
+ getRgbItem: function CalRGBCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ convertToRgb(this, src, srcOffset, dest, destOffset, 1);
+ },
+ getRgbBuffer: function CalRGBCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ var scale = 1 / ((1 << bits) - 1);
+ for (var i = 0; i < count; ++i) {
+ convertToRgb(this, src, srcOffset, dest, destOffset, scale);
+ srcOffset += 3;
+ destOffset += 3 + alpha01;
+ }
+ },
+ getOutputLength: function CalRGBCS_getOutputLength(inputLength, alpha01) {
+ return inputLength * (3 + alpha01) / 3 | 0;
+ },
+ isPassthrough: ColorSpace.prototype.isPassthrough,
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function CalRGBCS_isDefaultDecode(decodeMap) {
+ return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+ },
+ usesZeroToOneRange: true
+ };
+ return CalRGBCS;
+ }();
+ //
+ // LabCS: Based on "PDF Reference, Sixth Ed", p.250
+ //
+ var LabCS = function LabCSClosure() {
+ function LabCS(whitePoint, blackPoint, range) {
+ this.name = 'Lab';
+ this.numComps = 3;
+ this.defaultColor = new Float32Array([
+ 0,
+ 0,
+ 0
+ ]);
+ if (!whitePoint) {
+ error('WhitePoint missing - required for color space Lab');
+ }
+ blackPoint = blackPoint || [
+ 0,
+ 0,
+ 0
+ ];
+ range = range || [
+ -100,
+ 100,
+ -100,
+ 100
+ ];
+ // Translate args to spec variables
+ this.XW = whitePoint[0];
+ this.YW = whitePoint[1];
+ this.ZW = whitePoint[2];
+ this.amin = range[0];
+ this.amax = range[1];
+ this.bmin = range[2];
+ this.bmax = range[3];
+ // These are here just for completeness - the spec doesn't offer any
+ // formulas that use BlackPoint in Lab
+ this.XB = blackPoint[0];
+ this.YB = blackPoint[1];
+ this.ZB = blackPoint[2];
+ // Validate vars as per spec
+ if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
+ error('Invalid WhitePoint components, no fallback available');
+ }
+ if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
+ info('Invalid BlackPoint, falling back to default');
+ this.XB = this.YB = this.ZB = 0;
+ }
+ if (this.amin > this.amax || this.bmin > this.bmax) {
+ info('Invalid Range, falling back to defaults');
+ this.amin = -100;
+ this.amax = 100;
+ this.bmin = -100;
+ this.bmax = 100;
+ }
+ }
+ // Function g(x) from spec
+ function fn_g(x) {
+ if (x >= 6 / 29) {
+ return x * x * x;
+ } else {
+ return 108 / 841 * (x - 4 / 29);
+ }
+ }
+ function decode(value, high1, low2, high2) {
+ return low2 + value * (high2 - low2) / high1;
+ }
+ // If decoding is needed maxVal should be 2^bits per component - 1.
+ function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) {
+ // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax]
+ // not the usual [0, 1]. If a command like setFillColor is used the src
+ // values will already be within the correct range. However, if we are
+ // converting an image we have to map the values to the correct range given
+ // above.
+ // Ls,as,bs <---> L*,a*,b* in the spec
+ var Ls = src[srcOffset];
+ var as = src[srcOffset + 1];
+ var bs = src[srcOffset + 2];
+ if (maxVal !== false) {
+ Ls = decode(Ls, maxVal, 0, 100);
+ as = decode(as, maxVal, cs.amin, cs.amax);
+ bs = decode(bs, maxVal, cs.bmin, cs.bmax);
+ }
+ // Adjust limits of 'as' and 'bs'
+ as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as;
+ bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs;
+ // Computes intermediate variables X,Y,Z as per spec
+ var M = (Ls + 16) / 116;
+ var L = M + as / 500;
+ var N = M - bs / 200;
+ var X = cs.XW * fn_g(L);
+ var Y = cs.YW * fn_g(M);
+ var Z = cs.ZW * fn_g(N);
+ var r, g, b;
+ // Using different conversions for D50 and D65 white points,
+ // per http://www.color.org/srgb.pdf
+ if (cs.ZW < 1) {
+ // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249)
+ r = X * 3.1339 + Y * -1.6170 + Z * -0.4906;
+ g = X * -0.9785 + Y * 1.9160 + Z * 0.0333;
+ b = X * 0.0720 + Y * -0.2290 + Z * 1.4057;
+ } else {
+ // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888)
+ r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
+ g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
+ b = X * 0.0557 + Y * -0.2040 + Z * 1.0570;
+ }
+ // clamp color values to [0,1] range then convert to [0,255] range.
+ dest[destOffset] = r <= 0 ? 0 : r >= 1 ? 255 : Math.sqrt(r) * 255 | 0;
+ dest[destOffset + 1] = g <= 0 ? 0 : g >= 1 ? 255 : Math.sqrt(g) * 255 | 0;
+ dest[destOffset + 2] = b <= 0 ? 0 : b >= 1 ? 255 : Math.sqrt(b) * 255 | 0;
+ }
+ LabCS.prototype = {
+ getRgb: ColorSpace.prototype.getRgb,
+ getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) {
+ convertToRgb(this, src, srcOffset, false, dest, destOffset);
+ },
+ getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
+ var maxVal = (1 << bits) - 1;
+ for (var i = 0; i < count; i++) {
+ convertToRgb(this, src, srcOffset, maxVal, dest, destOffset);
+ srcOffset += 3;
+ destOffset += 3 + alpha01;
+ }
+ },
+ getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) {
+ return inputLength * (3 + alpha01) / 3 | 0;
+ },
+ isPassthrough: ColorSpace.prototype.isPassthrough,
+ fillRgb: ColorSpace.prototype.fillRgb,
+ isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) {
+ // XXX: Decoding is handled with the lab conversion because of the strange
+ // ranges that are used.
+ return true;
+ },
+ usesZeroToOneRange: false
+ };
+ return LabCS;
+ }();
+ exports.ColorSpace = ColorSpace;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreImage = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreColorSpace, root.pdfjsCoreStream, root.pdfjsCoreJpx);
+ }(this, function (exports, sharedUtil, corePrimitives, coreColorSpace, coreStream, coreJpx) {
+ var ImageKind = sharedUtil.ImageKind;
+ var assert = sharedUtil.assert;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var warn = sharedUtil.warn;
+ var Name = corePrimitives.Name;
+ var isStream = corePrimitives.isStream;
+ var ColorSpace = coreColorSpace.ColorSpace;
+ var DecodeStream = coreStream.DecodeStream;
+ var JpegStream = coreStream.JpegStream;
+ var JpxImage = coreJpx.JpxImage;
+ var PDFImage = function PDFImageClosure() {
+ /**
+ * Decodes the image using native decoder if possible. Resolves the promise
+ * when the image data is ready.
+ */
+ function handleImageData(image, nativeDecoder) {
+ if (nativeDecoder && nativeDecoder.canDecode(image)) {
+ return nativeDecoder.decode(image);
+ } else {
+ return Promise.resolve(image);
+ }
+ }
+ /**
+ * Decode and clamp a value. The formula is different from the spec because we
+ * don't decode to float range [0,1], we decode it in the [0,max] range.
+ */
+ function decodeAndClamp(value, addend, coefficient, max) {
+ value = addend + value * coefficient;
+ // Clamp the value to the range
+ return value < 0 ? 0 : value > max ? max : value;
+ }
+ /**
+ * Resizes an image mask with 1 component.
+ * @param {TypedArray} src - The source buffer.
+ * @param {Number} bpc - Number of bits per component.
+ * @param {Number} w1 - Original width.
+ * @param {Number} h1 - Original height.
+ * @param {Number} w2 - New width.
+ * @param {Number} h2 - New height.
+ * @returns {TypedArray} The resized image mask buffer.
+ */
+ function resizeImageMask(src, bpc, w1, h1, w2, h2) {
+ var length = w2 * h2;
+ var dest = bpc <= 8 ? new Uint8Array(length) : bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
+ var xRatio = w1 / w2;
+ var yRatio = h1 / h2;
+ var i, j, py, newIndex = 0, oldIndex;
+ var xScaled = new Uint16Array(w2);
+ var w1Scanline = w1;
+ for (i = 0; i < w2; i++) {
+ xScaled[i] = Math.floor(i * xRatio);
+ }
+ for (i = 0; i < h2; i++) {
+ py = Math.floor(i * yRatio) * w1Scanline;
+ for (j = 0; j < w2; j++) {
+ oldIndex = py + xScaled[j];
+ dest[newIndex++] = src[oldIndex];
+ }
+ }
+ return dest;
+ }
+ function PDFImage(xref, res, image, inline, smask, mask, isMask) {
+ this.image = image;
+ var dict = image.dict;
+ if (dict.has('Filter')) {
+ var filter = dict.get('Filter').name;
+ if (filter === 'JPXDecode') {
+ var jpxImage = new JpxImage();
+ jpxImage.parseImageProperties(image.stream);
+ image.stream.reset();
+ image.bitsPerComponent = jpxImage.bitsPerComponent;
+ image.numComps = jpxImage.componentsCount;
+ } else if (filter === 'JBIG2Decode') {
+ image.bitsPerComponent = 1;
+ image.numComps = 1;
+ }
+ }
+ // TODO cache rendered images?
+ this.width = dict.get('Width', 'W');
+ this.height = dict.get('Height', 'H');
+ if (this.width < 1 || this.height < 1) {
+ error('Invalid image width: ' + this.width + ' or height: ' + this.height);
+ }
+ this.interpolate = dict.get('Interpolate', 'I') || false;
+ this.imageMask = dict.get('ImageMask', 'IM') || false;
+ this.matte = dict.get('Matte') || false;
+ var bitsPerComponent = image.bitsPerComponent;
+ if (!bitsPerComponent) {
+ bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
+ if (!bitsPerComponent) {
+ if (this.imageMask) {
+ bitsPerComponent = 1;
+ } else {
+ error('Bits per component missing in image: ' + this.imageMask);
+ }
+ }
+ }
+ this.bpc = bitsPerComponent;
+ if (!this.imageMask) {
+ var colorSpace = dict.get('ColorSpace', 'CS');
+ if (!colorSpace) {
+ info('JPX images (which do not require color spaces)');
+ switch (image.numComps) {
+ case 1:
+ colorSpace = Name.get('DeviceGray');
+ break;
+ case 3:
+ colorSpace = Name.get('DeviceRGB');
+ break;
+ case 4:
+ colorSpace = Name.get('DeviceCMYK');
+ break;
+ default:
+ error('JPX images with ' + this.numComps + ' color components not supported.');
+ }
+ }
+ this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
+ this.numComps = this.colorSpace.numComps;
+ }
+ this.decode = dict.getArray('Decode', 'D');
+ this.needsDecode = false;
+ if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode) || isMask && !ColorSpace.isDefaultDecode(this.decode, 1))) {
+ this.needsDecode = true;
+ // Do some preprocessing to avoid more math.
+ var max = (1 << bitsPerComponent) - 1;
+ this.decodeCoefficients = [];
+ this.decodeAddends = [];
+ for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
+ var dmin = this.decode[i];
+ var dmax = this.decode[i + 1];
+ this.decodeCoefficients[j] = dmax - dmin;
+ this.decodeAddends[j] = max * dmin;
+ }
+ }
+ if (smask) {
+ this.smask = new PDFImage(xref, res, smask, false);
+ } else if (mask) {
+ if (isStream(mask)) {
+ var maskDict = mask.dict, imageMask = maskDict.get('ImageMask', 'IM');
+ if (!imageMask) {
+ warn('Ignoring /Mask in image without /ImageMask.');
+ } else {
+ this.mask = new PDFImage(xref, res, mask, false, null, null, true);
+ }
+ } else {
+ // Color key mask (just an array).
+ this.mask = mask;
+ }
+ }
+ }
+ /**
+ * Handles processing of image data and returns the Promise that is resolved
+ * with a PDFImage when the image is ready to be used.
+ */
+ PDFImage.buildImage = function PDFImage_buildImage(handler, xref, res, image, inline, nativeDecoder) {
+ var imagePromise = handleImageData(image, nativeDecoder);
+ var smaskPromise;
+ var maskPromise;
+ var smask = image.dict.get('SMask');
+ var mask = image.dict.get('Mask');
+ if (smask) {
+ smaskPromise = handleImageData(smask, nativeDecoder);
+ maskPromise = Promise.resolve(null);
+ } else {
+ smaskPromise = Promise.resolve(null);
+ if (mask) {
+ if (isStream(mask)) {
+ maskPromise = handleImageData(mask, nativeDecoder);
+ } else if (isArray(mask)) {
+ maskPromise = Promise.resolve(mask);
+ } else {
+ warn('Unsupported mask format.');
+ maskPromise = Promise.resolve(null);
+ }
+ } else {
+ maskPromise = Promise.resolve(null);
+ }
+ }
+ return Promise.all([
+ imagePromise,
+ smaskPromise,
+ maskPromise
+ ]).then(function (results) {
+ var imageData = results[0];
+ var smaskData = results[1];
+ var maskData = results[2];
+ return new PDFImage(xref, res, imageData, inline, smaskData, maskData);
+ });
+ };
+ PDFImage.createMask = function PDFImage_createMask(imgArray, width, height, imageIsFromDecodeStream, inverseDecode) {
+ // |imgArray| might not contain full data for every pixel of the mask, so
+ // we need to distinguish between |computedLength| and |actualLength|.
+ // In particular, if inverseDecode is true, then the array we return must
+ // have a length of |computedLength|.
+ var computedLength = (width + 7 >> 3) * height;
+ var actualLength = imgArray.byteLength;
+ var haveFullData = computedLength === actualLength;
+ var data, i;
+ if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
+ // imgArray came from a DecodeStream and its data is in an appropriate
+ // form, so we can just transfer it.
+ data = imgArray;
+ } else if (!inverseDecode) {
+ data = new Uint8Array(actualLength);
+ data.set(imgArray);
+ } else {
+ data = new Uint8Array(computedLength);
+ data.set(imgArray);
+ for (i = actualLength; i < computedLength; i++) {
+ data[i] = 0xff;
+ }
+ }
+ // If necessary, invert the original mask data (but not any extra we might
+ // have added above). It's safe to modify the array -- whether it's the
+ // original or a copy, we're about to transfer it anyway, so nothing else
+ // in this thread can be relying on its contents.
+ if (inverseDecode) {
+ for (i = 0; i < actualLength; i++) {
+ data[i] = ~data[i];
+ }
+ }
+ return {
+ data: data,
+ width: width,
+ height: height
+ };
+ };
+ PDFImage.prototype = {
+ get drawWidth() {
+ return Math.max(this.width, this.smask && this.smask.width || 0, this.mask && this.mask.width || 0);
+ },
+ get drawHeight() {
+ return Math.max(this.height, this.smask && this.smask.height || 0, this.mask && this.mask.height || 0);
+ },
+ decodeBuffer: function PDFImage_decodeBuffer(buffer) {
+ var bpc = this.bpc;
+ var numComps = this.numComps;
+ var decodeAddends = this.decodeAddends;
+ var decodeCoefficients = this.decodeCoefficients;
+ var max = (1 << bpc) - 1;
+ var i, ii;
+ if (bpc === 1) {
+ // If the buffer needed decode that means it just needs to be inverted.
+ for (i = 0, ii = buffer.length; i < ii; i++) {
+ buffer[i] = +!buffer[i];
+ }
+ return;
+ }
+ var index = 0;
+ for (i = 0, ii = this.width * this.height; i < ii; i++) {
+ for (var j = 0; j < numComps; j++) {
+ buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], decodeCoefficients[j], max);
+ index++;
+ }
+ }
+ },
+ getComponents: function PDFImage_getComponents(buffer) {
+ var bpc = this.bpc;
+ // This image doesn't require any extra work.
+ if (bpc === 8) {
+ return buffer;
+ }
+ var width = this.width;
+ var height = this.height;
+ var numComps = this.numComps;
+ var length = width * height * numComps;
+ var bufferPos = 0;
+ var output = bpc <= 8 ? new Uint8Array(length) : bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
+ var rowComps = width * numComps;
+ var max = (1 << bpc) - 1;
+ var i = 0, ii, buf;
+ if (bpc === 1) {
+ // Optimization for reading 1 bpc images.
+ var mask, loop1End, loop2End;
+ for (var j = 0; j < height; j++) {
+ loop1End = i + (rowComps & ~7);
+ loop2End = i + rowComps;
+ // unroll loop for all full bytes
+ while (i < loop1End) {
+ buf = buffer[bufferPos++];
+ output[i] = buf >> 7 & 1;
+ output[i + 1] = buf >> 6 & 1;
+ output[i + 2] = buf >> 5 & 1;
+ output[i + 3] = buf >> 4 & 1;
+ output[i + 4] = buf >> 3 & 1;
+ output[i + 5] = buf >> 2 & 1;
+ output[i + 6] = buf >> 1 & 1;
+ output[i + 7] = buf & 1;
+ i += 8;
+ }
+ // handle remaining bits
+ if (i < loop2End) {
+ buf = buffer[bufferPos++];
+ mask = 128;
+ while (i < loop2End) {
+ output[i++] = +!!(buf & mask);
+ mask >>= 1;
+ }
+ }
+ }
+ } else {
+ // The general case that handles all other bpc values.
+ var bits = 0;
+ buf = 0;
+ for (i = 0, ii = length; i < ii; ++i) {
+ if (i % rowComps === 0) {
+ buf = 0;
+ bits = 0;
+ }
+ while (bits < bpc) {
+ buf = buf << 8 | buffer[bufferPos++];
+ bits += 8;
+ }
+ var remainingBits = bits - bpc;
+ var value = buf >> remainingBits;
+ output[i] = value < 0 ? 0 : value > max ? max : value;
+ buf = buf & (1 << remainingBits) - 1;
+ bits = remainingBits;
+ }
+ }
+ return output;
+ },
+ fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height, actualHeight, image) {
+ var smask = this.smask;
+ var mask = this.mask;
+ var alphaBuf, sw, sh, i, ii, j;
+ if (smask) {
+ sw = smask.width;
+ sh = smask.height;
+ alphaBuf = new Uint8Array(sw * sh);
+ smask.fillGrayBuffer(alphaBuf);
+ if (sw !== width || sh !== height) {
+ alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
+ }
+ } else if (mask) {
+ if (mask instanceof PDFImage) {
+ sw = mask.width;
+ sh = mask.height;
+ alphaBuf = new Uint8Array(sw * sh);
+ mask.numComps = 1;
+ mask.fillGrayBuffer(alphaBuf);
+ // Need to invert values in rgbaBuf
+ for (i = 0, ii = sw * sh; i < ii; ++i) {
+ alphaBuf[i] = 255 - alphaBuf[i];
+ }
+ if (sw !== width || sh !== height) {
+ alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height);
+ }
+ } else if (isArray(mask)) {
+ // Color key mask: if any of the components are outside the range
+ // then they should be painted.
+ alphaBuf = new Uint8Array(width * height);
+ var numComps = this.numComps;
+ for (i = 0, ii = width * height; i < ii; ++i) {
+ var opacity = 0;
+ var imageOffset = i * numComps;
+ for (j = 0; j < numComps; ++j) {
+ var color = image[imageOffset + j];
+ var maskOffset = j * 2;
+ if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
+ opacity = 255;
+ break;
+ }
+ }
+ alphaBuf[i] = opacity;
+ }
+ } else {
+ error('Unknown mask format.');
+ }
+ }
+ if (alphaBuf) {
+ for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
+ rgbaBuf[j] = alphaBuf[i];
+ }
+ } else {
+ // No mask.
+ for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
+ rgbaBuf[j] = 255;
+ }
+ }
+ },
+ undoPreblend: function PDFImage_undoPreblend(buffer, width, height) {
+ var matte = this.smask && this.smask.matte;
+ if (!matte) {
+ return;
+ }
+ var matteRgb = this.colorSpace.getRgb(matte, 0);
+ var matteR = matteRgb[0];
+ var matteG = matteRgb[1];
+ var matteB = matteRgb[2];
+ var length = width * height * 4;
+ var r, g, b;
+ for (var i = 0; i < length; i += 4) {
+ var alpha = buffer[i + 3];
+ if (alpha === 0) {
+ // according formula we have to get Infinity in all components
+ // making it white (typical paper color) should be okay
+ buffer[i] = 255;
+ buffer[i + 1] = 255;
+ buffer[i + 2] = 255;
+ continue;
+ }
+ var k = 255 / alpha;
+ r = (buffer[i] - matteR) * k + matteR;
+ g = (buffer[i + 1] - matteG) * k + matteG;
+ b = (buffer[i + 2] - matteB) * k + matteB;
+ buffer[i] = r <= 0 ? 0 : r >= 255 ? 255 : r | 0;
+ buffer[i + 1] = g <= 0 ? 0 : g >= 255 ? 255 : g | 0;
+ buffer[i + 2] = b <= 0 ? 0 : b >= 255 ? 255 : b | 0;
+ }
+ },
+ createImageData: function PDFImage_createImageData(forceRGBA) {
+ var drawWidth = this.drawWidth;
+ var drawHeight = this.drawHeight;
+ var imgData = {
+ // other fields are filled in below
+ width: drawWidth,
+ height: drawHeight
+ };
+ var numComps = this.numComps;
+ var originalWidth = this.width;
+ var originalHeight = this.height;
+ var bpc = this.bpc;
+ // Rows start at byte boundary.
+ var rowBytes = originalWidth * numComps * bpc + 7 >> 3;
+ var imgArray;
+ if (!forceRGBA) {
+ // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image
+ // without any complications, we pass a same-sized copy to the main
+ // thread rather than expanding by 32x to RGBA form. This saves *lots*
+ // of memory for many scanned documents. It's also much faster.
+ //
+ // Similarly, if it is a 24-bit-per pixel RGB image without any
+ // complications, we avoid expanding by 1.333x to RGBA form.
+ var kind;
+ if (this.colorSpace.name === 'DeviceGray' && bpc === 1) {
+ kind = ImageKind.GRAYSCALE_1BPP;
+ } else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8 && !this.needsDecode) {
+ kind = ImageKind.RGB_24BPP;
+ }
+ if (kind && !this.smask && !this.mask && drawWidth === originalWidth && drawHeight === originalHeight) {
+ imgData.kind = kind;
+ imgArray = this.getImageBytes(originalHeight * rowBytes);
+ // If imgArray came from a DecodeStream, we're safe to transfer it
+ // (and thus detach its underlying buffer) because it will constitute
+ // the entire DecodeStream's data. But if it came from a Stream, we
+ // need to copy it because it'll only be a portion of the Stream's
+ // data, and the rest will be read later on.
+ if (this.image instanceof DecodeStream) {
+ imgData.data = imgArray;
+ } else {
+ var newArray = new Uint8Array(imgArray.length);
+ newArray.set(imgArray);
+ imgData.data = newArray;
+ }
+ if (this.needsDecode) {
+ // Invert the buffer (which must be grayscale if we reached here).
+ assert(kind === ImageKind.GRAYSCALE_1BPP);
+ var buffer = imgData.data;
+ for (var i = 0, ii = buffer.length; i < ii; i++) {
+ buffer[i] ^= 0xff;
+ }
+ }
+ return imgData;
+ }
+ if (this.image instanceof JpegStream && !this.smask && !this.mask && (this.colorSpace.name === 'DeviceGray' || this.colorSpace.name === 'DeviceRGB' || this.colorSpace.name === 'DeviceCMYK')) {
+ imgData.kind = ImageKind.RGB_24BPP;
+ imgData.data = this.getImageBytes(originalHeight * rowBytes, drawWidth, drawHeight, true);
+ return imgData;
+ }
+ }
+ imgArray = this.getImageBytes(originalHeight * rowBytes);
+ // imgArray can be incomplete (e.g. after CCITT fax encoding).
+ var actualHeight = 0 | imgArray.length / rowBytes * drawHeight / originalHeight;
+ var comps = this.getComponents(imgArray);
+ // If opacity data is present, use RGBA_32BPP form. Otherwise, use the
+ // more compact RGB_24BPP form if allowable.
+ var alpha01, maybeUndoPreblend;
+ if (!forceRGBA && !this.smask && !this.mask) {
+ imgData.kind = ImageKind.RGB_24BPP;
+ imgData.data = new Uint8Array(drawWidth * drawHeight * 3);
+ alpha01 = 0;
+ maybeUndoPreblend = false;
+ } else {
+ imgData.kind = ImageKind.RGBA_32BPP;
+ imgData.data = new Uint8Array(drawWidth * drawHeight * 4);
+ alpha01 = 1;
+ maybeUndoPreblend = true;
+ // Color key masking (opacity) must be performed before decoding.
+ this.fillOpacity(imgData.data, drawWidth, drawHeight, actualHeight, comps);
+ }
+ if (this.needsDecode) {
+ this.decodeBuffer(comps);
+ }
+ this.colorSpace.fillRgb(imgData.data, originalWidth, originalHeight, drawWidth, drawHeight, actualHeight, bpc, comps, alpha01);
+ if (maybeUndoPreblend) {
+ this.undoPreblend(imgData.data, drawWidth, actualHeight);
+ }
+ return imgData;
+ },
+ fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
+ var numComps = this.numComps;
+ if (numComps !== 1) {
+ error('Reading gray scale from a color image: ' + numComps);
+ }
+ var width = this.width;
+ var height = this.height;
+ var bpc = this.bpc;
+ // rows start at byte boundary
+ var rowBytes = width * numComps * bpc + 7 >> 3;
+ var imgArray = this.getImageBytes(height * rowBytes);
+ var comps = this.getComponents(imgArray);
+ var i, length;
+ if (bpc === 1) {
+ // inline decoding (= inversion) for 1 bpc images
+ length = width * height;
+ if (this.needsDecode) {
+ // invert and scale to {0, 255}
+ for (i = 0; i < length; ++i) {
+ buffer[i] = comps[i] - 1 & 255;
+ }
+ } else {
+ // scale to {0, 255}
+ for (i = 0; i < length; ++i) {
+ buffer[i] = -comps[i] & 255;
+ }
+ }
+ return;
+ }
+ if (this.needsDecode) {
+ this.decodeBuffer(comps);
+ }
+ length = width * height;
+ // we aren't using a colorspace so we need to scale the value
+ var scale = 255 / ((1 << bpc) - 1);
+ for (i = 0; i < length; ++i) {
+ buffer[i] = scale * comps[i] | 0;
+ }
+ },
+ getImageBytes: function PDFImage_getImageBytes(length, drawWidth, drawHeight, forceRGB) {
+ this.image.reset();
+ this.image.drawWidth = drawWidth || this.width;
+ this.image.drawHeight = drawHeight || this.height;
+ this.image.forceRGB = !!forceRGB;
+ return this.image.getBytes(length);
+ }
+ };
+ return PDFImage;
+ }();
+ exports.PDFImage = PDFImage;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreObj = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreCrypto, root.pdfjsCoreParser, root.pdfjsCoreChunkedStream, root.pdfjsCoreColorSpace);
+ }(this, function (exports, sharedUtil, corePrimitives, coreCrypto, coreParser, coreChunkedStream, coreColorSpace) {
+ var InvalidPDFException = sharedUtil.InvalidPDFException;
+ var MissingDataException = sharedUtil.MissingDataException;
+ var XRefParseException = sharedUtil.XRefParseException;
+ var assert = sharedUtil.assert;
+ var bytesToString = sharedUtil.bytesToString;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var isBool = sharedUtil.isBool;
+ var isInt = sharedUtil.isInt;
+ var isString = sharedUtil.isString;
+ var shadow = sharedUtil.shadow;
+ var stringToPDFString = sharedUtil.stringToPDFString;
+ var stringToUTF8String = sharedUtil.stringToUTF8String;
+ var warn = sharedUtil.warn;
+ var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
+ var Util = sharedUtil.Util;
+ var Ref = corePrimitives.Ref;
+ var RefSet = corePrimitives.RefSet;
+ var RefSetCache = corePrimitives.RefSetCache;
+ var isName = corePrimitives.isName;
+ var isCmd = corePrimitives.isCmd;
+ var isDict = corePrimitives.isDict;
+ var isRef = corePrimitives.isRef;
+ var isRefsEqual = corePrimitives.isRefsEqual;
+ var isStream = corePrimitives.isStream;
+ var CipherTransformFactory = coreCrypto.CipherTransformFactory;
+ var Lexer = coreParser.Lexer;
+ var Parser = coreParser.Parser;
+ var ChunkedStream = coreChunkedStream.ChunkedStream;
+ var ColorSpace = coreColorSpace.ColorSpace;
+ var Catalog = function CatalogClosure() {
+ function Catalog(pdfManager, xref, pageFactory) {
+ this.pdfManager = pdfManager;
+ this.xref = xref;
+ this.catDict = xref.getCatalogObj();
+ this.fontCache = new RefSetCache();
+ assert(isDict(this.catDict), 'catalog object is not a dictionary');
+ // TODO refactor to move getPage() to the PDFDocument.
+ this.pageFactory = pageFactory;
+ this.pagePromises = [];
+ }
+ Catalog.prototype = {
+ get metadata() {
+ var streamRef = this.catDict.getRaw('Metadata');
+ if (!isRef(streamRef)) {
+ return shadow(this, 'metadata', null);
+ }
+ var encryptMetadata = !this.xref.encrypt ? false : this.xref.encrypt.encryptMetadata;
+ var stream = this.xref.fetch(streamRef, !encryptMetadata);
+ var metadata;
+ if (stream && isDict(stream.dict)) {
+ var type = stream.dict.get('Type');
+ var subtype = stream.dict.get('Subtype');
+ if (isName(type, 'Metadata') && isName(subtype, 'XML')) {
+ // XXX: This should examine the charset the XML document defines,
+ // however since there are currently no real means to decode
+ // arbitrary charsets, let's just hope that the author of the PDF
+ // was reasonable enough to stick with the XML default charset,
+ // which is UTF-8.
+ try {
+ metadata = stringToUTF8String(bytesToString(stream.getBytes()));
+ } catch (e) {
+ info('Skipping invalid metadata.');
+ }
+ }
+ }
+ return shadow(this, 'metadata', metadata);
+ },
+ get toplevelPagesDict() {
+ var pagesObj = this.catDict.get('Pages');
+ assert(isDict(pagesObj), 'invalid top-level pages dictionary');
+ // shadow the prototype getter
+ return shadow(this, 'toplevelPagesDict', pagesObj);
+ },
+ get documentOutline() {
+ var obj = null;
+ try {
+ obj = this.readDocumentOutline();
+ } catch (ex) {
+ if (ex instanceof MissingDataException) {
+ throw ex;
+ }
+ warn('Unable to read document outline');
+ }
+ return shadow(this, 'documentOutline', obj);
+ },
+ readDocumentOutline: function Catalog_readDocumentOutline() {
+ var obj = this.catDict.get('Outlines');
+ if (!isDict(obj)) {
+ return null;
+ }
+ obj = obj.getRaw('First');
+ if (!isRef(obj)) {
+ return null;
+ }
+ var root = { items: [] };
+ var queue = [{
+ obj: obj,
+ parent: root
+ }];
+ // To avoid recursion, keep track of the already processed items.
+ var processed = new RefSet();
+ processed.put(obj);
+ var xref = this.xref, blackColor = new Uint8Array(3);
+ while (queue.length > 0) {
+ var i = queue.shift();
+ var outlineDict = xref.fetchIfRef(i.obj);
+ if (outlineDict === null) {
+ continue;
+ }
+ assert(outlineDict.has('Title'), 'Invalid outline item');
+ var data = {
+ url: null,
+ dest: null
+ };
+ Catalog.parseDestDictionary({
+ destDict: outlineDict,
+ resultObj: data,
+ docBaseUrl: this.pdfManager.docBaseUrl
+ });
+ var title = outlineDict.get('Title');
+ var flags = outlineDict.get('F') || 0;
+ var color = outlineDict.getArray('C'), rgbColor = blackColor;
+ // We only need to parse the color when it's valid, and non-default.
+ if (isArray(color) && color.length === 3 && (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) {
+ rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
+ }
+ var outlineItem = {
+ dest: data.dest,
+ url: data.url,
+ unsafeUrl: data.unsafeUrl,
+ newWindow: data.newWindow,
+ title: stringToPDFString(title),
+ color: rgbColor,
+ count: outlineDict.get('Count'),
+ bold: !!(flags & 2),
+ italic: !!(flags & 1),
+ items: []
+ };
+ i.parent.items.push(outlineItem);
+ obj = outlineDict.getRaw('First');
+ if (isRef(obj) && !processed.has(obj)) {
+ queue.push({
+ obj: obj,
+ parent: outlineItem
+ });
+ processed.put(obj);
+ }
+ obj = outlineDict.getRaw('Next');
+ if (isRef(obj) && !processed.has(obj)) {
+ queue.push({
+ obj: obj,
+ parent: i.parent
+ });
+ processed.put(obj);
+ }
+ }
+ return root.items.length > 0 ? root.items : null;
+ },
+ get numPages() {
+ var obj = this.toplevelPagesDict.get('Count');
+ assert(isInt(obj), 'page count in top level pages object is not an integer');
+ // shadow the prototype getter
+ return shadow(this, 'num', obj);
+ },
+ get destinations() {
+ function fetchDestination(dest) {
+ return isDict(dest) ? dest.get('D') : dest;
+ }
+ var xref = this.xref;
+ var dests = {}, nameTreeRef, nameDictionaryRef;
+ var obj = this.catDict.get('Names');
+ if (obj && obj.has('Dests')) {
+ nameTreeRef = obj.getRaw('Dests');
+ } else if (this.catDict.has('Dests')) {
+ nameDictionaryRef = this.catDict.get('Dests');
+ }
+ if (nameDictionaryRef) {
+ // reading simple destination dictionary
+ obj = nameDictionaryRef;
+ obj.forEach(function catalogForEach(key, value) {
+ if (!value) {
+ return;
+ }
+ dests[key] = fetchDestination(value);
+ });
+ }
+ if (nameTreeRef) {
+ var nameTree = new NameTree(nameTreeRef, xref);
+ var names = nameTree.getAll();
+ for (var name in names) {
+ dests[name] = fetchDestination(names[name]);
+ }
+ }
+ return shadow(this, 'destinations', dests);
+ },
+ getDestination: function Catalog_getDestination(destinationId) {
+ function fetchDestination(dest) {
+ return isDict(dest) ? dest.get('D') : dest;
+ }
+ var xref = this.xref;
+ var dest = null, nameTreeRef, nameDictionaryRef;
+ var obj = this.catDict.get('Names');
+ if (obj && obj.has('Dests')) {
+ nameTreeRef = obj.getRaw('Dests');
+ } else if (this.catDict.has('Dests')) {
+ nameDictionaryRef = this.catDict.get('Dests');
+ }
+ if (nameDictionaryRef) {
+ // Simple destination dictionary.
+ var value = nameDictionaryRef.get(destinationId);
+ if (value) {
+ dest = fetchDestination(value);
+ }
+ }
+ if (nameTreeRef) {
+ var nameTree = new NameTree(nameTreeRef, xref);
+ dest = fetchDestination(nameTree.get(destinationId));
+ }
+ return dest;
+ },
+ get pageLabels() {
+ var obj = null;
+ try {
+ obj = this.readPageLabels();
+ } catch (ex) {
+ if (ex instanceof MissingDataException) {
+ throw ex;
+ }
+ warn('Unable to read page labels.');
+ }
+ return shadow(this, 'pageLabels', obj);
+ },
+ readPageLabels: function Catalog_readPageLabels() {
+ var obj = this.catDict.getRaw('PageLabels');
+ if (!obj) {
+ return null;
+ }
+ var pageLabels = new Array(this.numPages);
+ var style = null;
+ var prefix = '';
+ var numberTree = new NumberTree(obj, this.xref);
+ var nums = numberTree.getAll();
+ var currentLabel = '', currentIndex = 1;
+ for (var i = 0, ii = this.numPages; i < ii; i++) {
+ if (i in nums) {
+ var labelDict = nums[i];
+ assert(isDict(labelDict), 'The PageLabel is not a dictionary.');
+ var type = labelDict.get('Type');
+ assert(!type || isName(type, 'PageLabel'), 'Invalid type in PageLabel dictionary.');
+ var s = labelDict.get('S');
+ assert(!s || isName(s), 'Invalid style in PageLabel dictionary.');
+ style = s ? s.name : null;
+ var p = labelDict.get('P');
+ assert(!p || isString(p), 'Invalid prefix in PageLabel dictionary.');
+ prefix = p ? stringToPDFString(p) : '';
+ var st = labelDict.get('St');
+ assert(!st || isInt(st) && st >= 1, 'Invalid start in PageLabel dictionary.');
+ currentIndex = st || 1;
+ }
+ switch (style) {
+ case 'D':
+ currentLabel = currentIndex;
+ break;
+ case 'R':
+ case 'r':
+ currentLabel = Util.toRoman(currentIndex, style === 'r');
+ break;
+ case 'A':
+ case 'a':
+ var LIMIT = 26;
+ // Use only the characters A--Z, or a--z.
+ var A_UPPER_CASE = 0x41, A_LOWER_CASE = 0x61;
+ var baseCharCode = style === 'a' ? A_LOWER_CASE : A_UPPER_CASE;
+ var letterIndex = currentIndex - 1;
+ var character = String.fromCharCode(baseCharCode + letterIndex % LIMIT);
+ var charBuf = [];
+ for (var j = 0, jj = letterIndex / LIMIT | 0; j <= jj; j++) {
+ charBuf.push(character);
+ }
+ currentLabel = charBuf.join('');
+ break;
+ default:
+ assert(!style, 'Invalid style "' + style + '" in PageLabel dictionary.');
+ }
+ pageLabels[i] = prefix + currentLabel;
+ currentLabel = '';
+ currentIndex++;
+ }
+ return pageLabels;
+ },
+ get attachments() {
+ var xref = this.xref;
+ var attachments = null, nameTreeRef;
+ var obj = this.catDict.get('Names');
+ if (obj) {
+ nameTreeRef = obj.getRaw('EmbeddedFiles');
+ }
+ if (nameTreeRef) {
+ var nameTree = new NameTree(nameTreeRef, xref);
+ var names = nameTree.getAll();
+ for (var name in names) {
+ var fs = new FileSpec(names[name], xref);
+ if (!attachments) {
+ attachments = Object.create(null);
+ }
+ attachments[stringToPDFString(name)] = fs.serializable;
+ }
+ }
+ return shadow(this, 'attachments', attachments);
+ },
+ get javaScript() {
+ var xref = this.xref;
+ var obj = this.catDict.get('Names');
+ var javaScript = [];
+ function appendIfJavaScriptDict(jsDict) {
+ var type = jsDict.get('S');
+ if (!isName(type, 'JavaScript')) {
+ return;
+ }
+ var js = jsDict.get('JS');
+ if (isStream(js)) {
+ js = bytesToString(js.getBytes());
+ } else if (!isString(js)) {
+ return;
+ }
+ javaScript.push(stringToPDFString(js));
+ }
+ if (obj && obj.has('JavaScript')) {
+ var nameTree = new NameTree(obj.getRaw('JavaScript'), xref);
+ var names = nameTree.getAll();
+ for (var name in names) {
+ // We don't really use the JavaScript right now. This code is
+ // defensive so we don't cause errors on document load.
+ var jsDict = names[name];
+ if (isDict(jsDict)) {
+ appendIfJavaScriptDict(jsDict);
+ }
+ }
+ }
+ // Append OpenAction actions to javaScript array
+ var openactionDict = this.catDict.get('OpenAction');
+ if (isDict(openactionDict, 'Action')) {
+ var actionType = openactionDict.get('S');
+ if (isName(actionType, 'Named')) {
+ // The named Print action is not a part of the PDF 1.7 specification,
+ // but is supported by many PDF readers/writers (including Adobe's).
+ var action = openactionDict.get('N');
+ if (isName(action, 'Print')) {
+ javaScript.push('print({});');
+ }
+ } else {
+ appendIfJavaScriptDict(openactionDict);
+ }
+ }
+ return shadow(this, 'javaScript', javaScript);
+ },
+ cleanup: function Catalog_cleanup() {
+ var promises = [];
+ this.fontCache.forEach(function (promise) {
+ promises.push(promise);
+ });
+ return Promise.all(promises).then(function (translatedFonts) {
+ for (var i = 0, ii = translatedFonts.length; i < ii; i++) {
+ var font = translatedFonts[i].dict;
+ delete font.translated;
+ }
+ this.fontCache.clear();
+ }.bind(this));
+ },
+ getPage: function Catalog_getPage(pageIndex) {
+ if (!(pageIndex in this.pagePromises)) {
+ this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then(function (a) {
+ var dict = a[0];
+ var ref = a[1];
+ return this.pageFactory.createPage(pageIndex, dict, ref, this.fontCache);
+ }.bind(this));
+ }
+ return this.pagePromises[pageIndex];
+ },
+ getPageDict: function Catalog_getPageDict(pageIndex) {
+ var capability = createPromiseCapability();
+ var nodesToVisit = [this.catDict.getRaw('Pages')];
+ var currentPageIndex = 0;
+ var xref = this.xref;
+ var checkAllKids = false;
+ function next() {
+ while (nodesToVisit.length) {
+ var currentNode = nodesToVisit.pop();
+ if (isRef(currentNode)) {
+ xref.fetchAsync(currentNode).then(function (obj) {
+ if (isDict(obj, 'Page') || isDict(obj) && !obj.has('Kids')) {
+ if (pageIndex === currentPageIndex) {
+ capability.resolve([
+ obj,
+ currentNode
+ ]);
+ } else {
+ currentPageIndex++;
+ next();
+ }
+ return;
+ }
+ nodesToVisit.push(obj);
+ next();
+ }, capability.reject);
+ return;
+ }
+ // Must be a child page dictionary.
+ assert(isDict(currentNode), 'page dictionary kid reference points to wrong type of object');
+ var count = currentNode.get('Count');
+ // If the current node doesn't have any children, avoid getting stuck
+ // in an empty node further down in the tree (see issue5644.pdf).
+ if (count === 0) {
+ checkAllKids = true;
+ }
+ // Skip nodes where the page can't be.
+ if (currentPageIndex + count <= pageIndex) {
+ currentPageIndex += count;
+ continue;
+ }
+ var kids = currentNode.get('Kids');
+ assert(isArray(kids), 'page dictionary kids object is not an array');
+ if (!checkAllKids && count === kids.length) {
+ // Nodes that don't have the page have been skipped and this is the
+ // bottom of the tree which means the page requested must be a
+ // descendant of this pages node. Ideally we would just resolve the
+ // promise with the page ref here, but there is the case where more
+ // pages nodes could link to single a page (see issue 3666 pdf). To
+ // handle this push it back on the queue so if it is a pages node it
+ // will be descended into.
+ nodesToVisit = [kids[pageIndex - currentPageIndex]];
+ currentPageIndex = pageIndex;
+ continue;
+ } else {
+ for (var last = kids.length - 1; last >= 0; last--) {
+ nodesToVisit.push(kids[last]);
+ }
+ }
+ }
+ capability.reject('Page index ' + pageIndex + ' not found.');
+ }
+ next();
+ return capability.promise;
+ },
+ getPageIndex: function Catalog_getPageIndex(pageRef) {
+ // The page tree nodes have the count of all the leaves below them. To get
+ // how many pages are before we just have to walk up the tree and keep
+ // adding the count of siblings to the left of the node.
+ var xref = this.xref;
+ function pagesBeforeRef(kidRef) {
+ var total = 0;
+ var parentRef;
+ return xref.fetchAsync(kidRef).then(function (node) {
+ if (isRefsEqual(kidRef, pageRef) && !isDict(node, 'Page') && !(isDict(node) && !node.has('Type') && node.has('Contents'))) {
+ throw new Error('The reference does not point to a /Page Dict.');
+ }
+ if (!node) {
+ return null;
+ }
+ assert(isDict(node), 'node must be a Dict.');
+ parentRef = node.getRaw('Parent');
+ return node.getAsync('Parent');
+ }).then(function (parent) {
+ if (!parent) {
+ return null;
+ }
+ assert(isDict(parent), 'parent must be a Dict.');
+ return parent.getAsync('Kids');
+ }).then(function (kids) {
+ if (!kids) {
+ return null;
+ }
+ var kidPromises = [];
+ var found = false;
+ for (var i = 0; i < kids.length; i++) {
+ var kid = kids[i];
+ assert(isRef(kid), 'kid must be a Ref.');
+ if (kid.num === kidRef.num) {
+ found = true;
+ break;
+ }
+ kidPromises.push(xref.fetchAsync(kid).then(function (kid) {
+ if (kid.has('Count')) {
+ var count = kid.get('Count');
+ total += count;
+ } else {
+ // page leaf node
+ total++;
+ }
+ }));
+ }
+ if (!found) {
+ error('kid ref not found in parents kids');
+ }
+ return Promise.all(kidPromises).then(function () {
+ return [
+ total,
+ parentRef
+ ];
+ });
+ });
+ }
+ var total = 0;
+ function next(ref) {
+ return pagesBeforeRef(ref).then(function (args) {
+ if (!args) {
+ return total;
+ }
+ var count = args[0];
+ var parentRef = args[1];
+ total += count;
+ return next(parentRef);
+ });
+ }
+ return next(pageRef);
+ }
+ };
+ /**
+ * Helper function used to parse the contents of destination dictionaries.
+ * @param {Dict} destDict - The dictionary containing the destination.
+ * @param {Object} resultObj - The object where the parsed destination
+ * properties will be placed.
+ * @param {string} docBaseUrl - (optional) The document base URL that is used
+ * when attempting to recover valid absolute URLs from relative ones.
+ */
+ Catalog.parseDestDictionary = function Catalog_parseDestDictionary(params) {
+ // Lets URLs beginning with 'www.' default to using the 'http://' protocol.
+ function addDefaultProtocolToUrl(url) {
+ if (url.indexOf('www.') === 0) {
+ return 'http://' + url;
+ }
+ return url;
+ }
+ // According to ISO 32000-1:2008, section 12.6.4.7, URIs should be encoded
+ // in 7-bit ASCII. Some bad PDFs use UTF-8 encoding, see Bugzilla 1122280.
+ function tryConvertUrlEncoding(url) {
+ try {
+ return stringToUTF8String(url);
+ } catch (e) {
+ return url;
+ }
+ }
+ var destDict = params.destDict;
+ if (!isDict(destDict)) {
+ warn('Catalog_parseDestDictionary: "destDict" must be a dictionary.');
+ return;
+ }
+ var resultObj = params.resultObj;
+ if (typeof resultObj !== 'object') {
+ warn('Catalog_parseDestDictionary: "resultObj" must be an object.');
+ return;
+ }
+ var docBaseUrl = params.docBaseUrl || null;
+ var action = destDict.get('A'), url, dest;
+ if (isDict(action)) {
+ var linkType = action.get('S').name;
+ switch (linkType) {
+ case 'URI':
+ url = action.get('URI');
+ if (isName(url)) {
+ // Some bad PDFs do not put parentheses around relative URLs.
+ url = '/' + url.name;
+ } else if (isString(url)) {
+ url = addDefaultProtocolToUrl(url);
+ }
+ // TODO: pdf spec mentions urls can be relative to a Base
+ // entry in the dictionary.
+ break;
+ case 'GoTo':
+ dest = action.get('D');
+ break;
+ case 'Launch':
+ // We neither want, nor can, support arbitrary 'Launch' actions.
+ // However, in practice they are mostly used for linking to other PDF
+ // files, which we thus attempt to support (utilizing `docBaseUrl`).
+ case 'GoToR':
+ var urlDict = action.get('F');
+ if (isDict(urlDict)) {
+ // We assume that we found a FileSpec dictionary
+ // and fetch the URL without checking any further.
+ url = urlDict.get('F') || null;
+ } else if (isString(urlDict)) {
+ url = urlDict;
+ }
+ // NOTE: the destination is relative to the *remote* document.
+ var remoteDest = action.get('D');
+ if (remoteDest) {
+ if (isName(remoteDest)) {
+ remoteDest = remoteDest.name;
+ }
+ if (isString(url)) {
+ var baseUrl = url.split('#')[0];
+ if (isString(remoteDest)) {
+ // In practice, a named destination may contain only a number.
+ // If that happens, use the '#nameddest=' form to avoid the link
+ // redirecting to a page, instead of the correct destination.
+ url = baseUrl + '#' + (/^\d+$/.test(remoteDest) ? 'nameddest=' : '') + remoteDest;
+ } else if (isArray(remoteDest)) {
+ url = baseUrl + '#' + JSON.stringify(remoteDest);
+ }
+ }
+ }
+ // The 'NewWindow' property, equal to `LinkTarget.BLANK`.
+ var newWindow = action.get('NewWindow');
+ if (isBool(newWindow)) {
+ resultObj.newWindow = newWindow;
+ }
+ break;
+ case 'Named':
+ var namedAction = action.get('N');
+ if (isName(namedAction)) {
+ resultObj.action = namedAction.name;
+ }
+ break;
+ case 'JavaScript':
+ var jsAction = action.get('JS'), js;
+ if (isStream(jsAction)) {
+ js = bytesToString(jsAction.getBytes());
+ } else if (isString(jsAction)) {
+ js = jsAction;
+ }
+ if (js) {
+ // Attempt to recover valid URLs from 'JS' entries with certain
+ // white-listed formats, e.g.
+ // - window.open('http://example.com')
+ // - app.launchURL('http://example.com', true)
+ var URL_OPEN_METHODS = [
+ 'app.launchURL',
+ 'window.open'
+ ];
+ var regex = new RegExp('^(?:' + URL_OPEN_METHODS.join('|') + ')' + '\\((?:\'|\")(\\S+)(?:\'|\")(?:,|\\))');
+ var jsUrl = regex.exec(stringToPDFString(js), 'i');
+ if (jsUrl && jsUrl[1]) {
+ url = jsUrl[1];
+ break;
+ }
+ }
+ default:
+ warn('Catalog_parseDestDictionary: Unrecognized link type "' + linkType + '".');
+ break;
+ }
+ } else if (destDict.has('Dest')) {
+ // Simple destination link.
+ dest = destDict.get('Dest');
+ }
+ if (isString(url)) {
+ url = tryConvertUrlEncoding(url);
+ var absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl);
+ if (absoluteUrl) {
+ resultObj.url = absoluteUrl.href;
+ }
+ resultObj.unsafeUrl = url;
+ }
+ if (dest) {
+ if (isName(dest)) {
+ dest = dest.name;
+ }
+ if (isString(dest) || isArray(dest)) {
+ resultObj.dest = dest;
+ }
+ }
+ };
+ return Catalog;
+ }();
+ var XRef = function XRefClosure() {
+ function XRef(stream, password) {
+ this.stream = stream;
+ this.entries = [];
+ this.xrefstms = Object.create(null);
+ // prepare the XRef cache
+ this.cache = [];
+ this.password = password;
+ this.stats = {
+ streamTypes: [],
+ fontTypes: []
+ };
+ }
+ XRef.prototype = {
+ setStartXRef: function XRef_setStartXRef(startXRef) {
+ // Store the starting positions of xref tables as we process them
+ // so we can recover from missing data errors
+ this.startXRefQueue = [startXRef];
+ },
+ parse: function XRef_parse(recoveryMode) {
+ var trailerDict;
+ if (!recoveryMode) {
+ trailerDict = this.readXRef();
+ } else {
+ warn('Indexing all PDF objects');
+ trailerDict = this.indexObjects();
+ }
+ trailerDict.assignXref(this);
+ this.trailer = trailerDict;
+ var encrypt = trailerDict.get('Encrypt');
+ if (encrypt) {
+ var ids = trailerDict.get('ID');
+ var fileId = ids && ids.length ? ids[0] : '';
+ // The 'Encrypt' dictionary itself should not be encrypted, and by
+ // setting `suppressEncryption` we can prevent an infinite loop inside
+ // of `XRef_fetchUncompressed` if the dictionary contains indirect
+ // objects (fixes issue7665.pdf).
+ encrypt.suppressEncryption = true;
+ this.encrypt = new CipherTransformFactory(encrypt, fileId, this.password);
+ }
+ // get the root dictionary (catalog) object
+ if (!(this.root = trailerDict.get('Root'))) {
+ error('Invalid root reference');
+ }
+ },
+ processXRefTable: function XRef_processXRefTable(parser) {
+ if (!('tableState' in this)) {
+ // Stores state of the table as we process it so we can resume
+ // from middle of table in case of missing data error
+ this.tableState = {
+ entryNum: 0,
+ streamPos: parser.lexer.stream.pos,
+ parserBuf1: parser.buf1,
+ parserBuf2: parser.buf2
+ };
+ }
+ var obj = this.readXRefTable(parser);
+ // Sanity check
+ if (!isCmd(obj, 'trailer')) {
+ error('Invalid XRef table: could not find trailer dictionary');
+ }
+ // Read trailer dictionary, e.g.
+ // trailer
+ // << /Size 22
+ // /Root 20R
+ // /Info 10R
+ // /ID [ <81b14aafa313db63dbd6f981e49f94f4> ]
+ // >>
+ // The parser goes through the entire stream << ... >> and provides
+ // a getter interface for the key-value table
+ var dict = parser.getObj();
+ // The pdflib PDF generator can generate a nested trailer dictionary
+ if (!isDict(dict) && dict.dict) {
+ dict = dict.dict;
+ }
+ if (!isDict(dict)) {
+ error('Invalid XRef table: could not parse trailer dictionary');
+ }
+ delete this.tableState;
+ return dict;
+ },
+ readXRefTable: function XRef_readXRefTable(parser) {
+ // Example of cross-reference table:
+ // xref
+ // 0 1 <-- subsection header (first obj #, obj count)
+ // 0000000000 65535 f <-- actual object (offset, generation #, f/n)
+ // 23 2 <-- subsection header ... and so on ...
+ // 0000025518 00002 n
+ // 0000025635 00000 n
+ // trailer
+ // ...
+ var stream = parser.lexer.stream;
+ var tableState = this.tableState;
+ stream.pos = tableState.streamPos;
+ parser.buf1 = tableState.parserBuf1;
+ parser.buf2 = tableState.parserBuf2;
+ // Outer loop is over subsection headers
+ var obj;
+ while (true) {
+ if (!('firstEntryNum' in tableState) || !('entryCount' in tableState)) {
+ if (isCmd(obj = parser.getObj(), 'trailer')) {
+ break;
+ }
+ tableState.firstEntryNum = obj;
+ tableState.entryCount = parser.getObj();
+ }
+ var first = tableState.firstEntryNum;
+ var count = tableState.entryCount;
+ if (!isInt(first) || !isInt(count)) {
+ error('Invalid XRef table: wrong types in subsection header');
+ }
+ // Inner loop is over objects themselves
+ for (var i = tableState.entryNum; i < count; i++) {
+ tableState.streamPos = stream.pos;
+ tableState.entryNum = i;
+ tableState.parserBuf1 = parser.buf1;
+ tableState.parserBuf2 = parser.buf2;
+ var entry = {};
+ entry.offset = parser.getObj();
+ entry.gen = parser.getObj();
+ var type = parser.getObj();
+ if (isCmd(type, 'f')) {
+ entry.free = true;
+ } else if (isCmd(type, 'n')) {
+ entry.uncompressed = true;
+ }
+ // Validate entry obj
+ if (!isInt(entry.offset) || !isInt(entry.gen) || !(entry.free || entry.uncompressed)) {
+ error('Invalid entry in XRef subsection: ' + first + ', ' + count);
+ }
+ // The first xref table entry, i.e. obj 0, should be free. Attempting
+ // to adjust an incorrect first obj # (fixes issue 3248 and 7229).
+ if (i === 0 && entry.free && first === 1) {
+ first = 0;
+ }
+ if (!this.entries[i + first]) {
+ this.entries[i + first] = entry;
+ }
+ }
+ tableState.entryNum = 0;
+ tableState.streamPos = stream.pos;
+ tableState.parserBuf1 = parser.buf1;
+ tableState.parserBuf2 = parser.buf2;
+ delete tableState.firstEntryNum;
+ delete tableState.entryCount;
+ }
+ // Sanity check: as per spec, first object must be free
+ if (this.entries[0] && !this.entries[0].free) {
+ error('Invalid XRef table: unexpected first object');
+ }
+ return obj;
+ },
+ processXRefStream: function XRef_processXRefStream(stream) {
+ if (!('streamState' in this)) {
+ // Stores state of the stream as we process it so we can resume
+ // from middle of stream in case of missing data error
+ var streamParameters = stream.dict;
+ var byteWidths = streamParameters.get('W');
+ var range = streamParameters.get('Index');
+ if (!range) {
+ range = [
+ 0,
+ streamParameters.get('Size')
+ ];
+ }
+ this.streamState = {
+ entryRanges: range,
+ byteWidths: byteWidths,
+ entryNum: 0,
+ streamPos: stream.pos
+ };
+ }
+ this.readXRefStream(stream);
+ delete this.streamState;
+ return stream.dict;
+ },
+ readXRefStream: function XRef_readXRefStream(stream) {
+ var i, j;
+ var streamState = this.streamState;
+ stream.pos = streamState.streamPos;
+ var byteWidths = streamState.byteWidths;
+ var typeFieldWidth = byteWidths[0];
+ var offsetFieldWidth = byteWidths[1];
+ var generationFieldWidth = byteWidths[2];
+ var entryRanges = streamState.entryRanges;
+ while (entryRanges.length > 0) {
+ var first = entryRanges[0];
+ var n = entryRanges[1];
+ if (!isInt(first) || !isInt(n)) {
+ error('Invalid XRef range fields: ' + first + ', ' + n);
+ }
+ if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) || !isInt(generationFieldWidth)) {
+ error('Invalid XRef entry fields length: ' + first + ', ' + n);
+ }
+ for (i = streamState.entryNum; i < n; ++i) {
+ streamState.entryNum = i;
+ streamState.streamPos = stream.pos;
+ var type = 0, offset = 0, generation = 0;
+ for (j = 0; j < typeFieldWidth; ++j) {
+ type = type << 8 | stream.getByte();
+ }
+ // if type field is absent, its default value is 1
+ if (typeFieldWidth === 0) {
+ type = 1;
+ }
+ for (j = 0; j < offsetFieldWidth; ++j) {
+ offset = offset << 8 | stream.getByte();
+ }
+ for (j = 0; j < generationFieldWidth; ++j) {
+ generation = generation << 8 | stream.getByte();
+ }
+ var entry = {};
+ entry.offset = offset;
+ entry.gen = generation;
+ switch (type) {
+ case 0:
+ entry.free = true;
+ break;
+ case 1:
+ entry.uncompressed = true;
+ break;
+ case 2:
+ break;
+ default:
+ error('Invalid XRef entry type: ' + type);
+ }
+ if (!this.entries[first + i]) {
+ this.entries[first + i] = entry;
+ }
+ }
+ streamState.entryNum = 0;
+ streamState.streamPos = stream.pos;
+ entryRanges.splice(0, 2);
+ }
+ },
+ indexObjects: function XRef_indexObjects() {
+ // Simple scan through the PDF content to find objects,
+ // trailers and XRef streams.
+ var TAB = 0x9, LF = 0xA, CR = 0xD, SPACE = 0x20;
+ var PERCENT = 0x25, LT = 0x3C;
+ function readToken(data, offset) {
+ var token = '', ch = data[offset];
+ while (ch !== LF && ch !== CR && ch !== LT) {
+ if (++offset >= data.length) {
+ break;
+ }
+ token += String.fromCharCode(ch);
+ ch = data[offset];
+ }
+ return token;
+ }
+ function skipUntil(data, offset, what) {
+ var length = what.length, dataLength = data.length;
+ var skipped = 0;
+ // finding byte sequence
+ while (offset < dataLength) {
+ var i = 0;
+ while (i < length && data[offset + i] === what[i]) {
+ ++i;
+ }
+ if (i >= length) {
+ break;
+ }
+ // sequence found
+ offset++;
+ skipped++;
+ }
+ return skipped;
+ }
+ var objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
+ var trailerBytes = new Uint8Array([
+ 116,
+ 114,
+ 97,
+ 105,
+ 108,
+ 101,
+ 114
+ ]);
+ var startxrefBytes = new Uint8Array([
+ 115,
+ 116,
+ 97,
+ 114,
+ 116,
+ 120,
+ 114,
+ 101,
+ 102
+ ]);
+ var endobjBytes = new Uint8Array([
+ 101,
+ 110,
+ 100,
+ 111,
+ 98,
+ 106
+ ]);
+ var xrefBytes = new Uint8Array([
+ 47,
+ 88,
+ 82,
+ 101,
+ 102
+ ]);
+ // Clear out any existing entries, since they may be bogus.
+ this.entries.length = 0;
+ var stream = this.stream;
+ stream.pos = 0;
+ var buffer = stream.getBytes();
+ var position = stream.start, length = buffer.length;
+ var trailers = [], xrefStms = [];
+ while (position < length) {
+ var ch = buffer[position];
+ if (ch === TAB || ch === LF || ch === CR || ch === SPACE) {
+ ++position;
+ continue;
+ }
+ if (ch === PERCENT) {
+ // %-comment
+ do {
+ ++position;
+ if (position >= length) {
+ break;
+ }
+ ch = buffer[position];
+ } while (ch !== LF && ch !== CR);
+ continue;
+ }
+ var token = readToken(buffer, position);
+ var m;
+ if (token.indexOf('xref') === 0 && (token.length === 4 || /\s/.test(token[4]))) {
+ position += skipUntil(buffer, position, trailerBytes);
+ trailers.push(position);
+ position += skipUntil(buffer, position, startxrefBytes);
+ } else if (m = objRegExp.exec(token)) {
+ if (typeof this.entries[m[1]] === 'undefined') {
+ this.entries[m[1]] = {
+ offset: position - stream.start,
+ gen: m[2] | 0,
+ uncompressed: true
+ };
+ }
+ var contentLength = skipUntil(buffer, position, endobjBytes) + 7;
+ var content = buffer.subarray(position, position + contentLength);
+ // checking XRef stream suspect
+ // (it shall have '/XRef' and next char is not a letter)
+ var xrefTagOffset = skipUntil(content, 0, xrefBytes);
+ if (xrefTagOffset < contentLength && content[xrefTagOffset + 5] < 64) {
+ xrefStms.push(position - stream.start);
+ this.xrefstms[position - stream.start] = 1;
+ }
+ // Avoid recursion
+ position += contentLength;
+ } else if (token.indexOf('trailer') === 0 && (token.length === 7 || /\s/.test(token[7]))) {
+ trailers.push(position);
+ position += skipUntil(buffer, position, startxrefBytes);
+ } else {
+ position += token.length + 1;
+ }
+ }
+ // reading XRef streams
+ var i, ii;
+ for (i = 0, ii = xrefStms.length; i < ii; ++i) {
+ this.startXRefQueue.push(xrefStms[i]);
+ this.readXRef(/* recoveryMode */
+ true);
+ }
+ // finding main trailer
+ var dict;
+ for (i = 0, ii = trailers.length; i < ii; ++i) {
+ stream.pos = trailers[i];
+ var parser = new Parser(new Lexer(stream), /* allowStreams = */
+ true, /* xref = */
+ this, /* recoveryMode = */
+ true);
+ var obj = parser.getObj();
+ if (!isCmd(obj, 'trailer')) {
+ continue;
+ }
+ // read the trailer dictionary
+ dict = parser.getObj();
+ if (!isDict(dict)) {
+ continue;
+ }
+ // taking the first one with 'ID'
+ if (dict.has('ID')) {
+ return dict;
+ }
+ }
+ // no tailer with 'ID', taking last one (if exists)
+ if (dict) {
+ return dict;
+ }
+ // nothing helps
+ // calling error() would reject worker with an UnknownErrorException.
+ throw new InvalidPDFException('Invalid PDF structure');
+ },
+ readXRef: function XRef_readXRef(recoveryMode) {
+ var stream = this.stream;
+ try {
+ while (this.startXRefQueue.length) {
+ var startXRef = this.startXRefQueue[0];
+ stream.pos = startXRef + stream.start;
+ var parser = new Parser(new Lexer(stream), true, this);
+ var obj = parser.getObj();
+ var dict;
+ // Get dictionary
+ if (isCmd(obj, 'xref')) {
+ // Parse end-of-file XRef
+ dict = this.processXRefTable(parser);
+ if (!this.topDict) {
+ this.topDict = dict;
+ }
+ // Recursively get other XRefs 'XRefStm', if any
+ obj = dict.get('XRefStm');
+ if (isInt(obj)) {
+ var pos = obj;
+ // ignore previously loaded xref streams
+ // (possible infinite recursion)
+ if (!(pos in this.xrefstms)) {
+ this.xrefstms[pos] = 1;
+ this.startXRefQueue.push(pos);
+ }
+ }
+ } else if (isInt(obj)) {
+ // Parse in-stream XRef
+ if (!isInt(parser.getObj()) || !isCmd(parser.getObj(), 'obj') || !isStream(obj = parser.getObj())) {
+ error('Invalid XRef stream');
+ }
+ dict = this.processXRefStream(obj);
+ if (!this.topDict) {
+ this.topDict = dict;
+ }
+ if (!dict) {
+ error('Failed to read XRef stream');
+ }
+ } else {
+ error('Invalid XRef stream header');
+ }
+ // Recursively get previous dictionary, if any
+ obj = dict.get('Prev');
+ if (isInt(obj)) {
+ this.startXRefQueue.push(obj);
+ } else if (isRef(obj)) {
+ // The spec says Prev must not be a reference, i.e. "/Prev NNN"
+ // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R"
+ this.startXRefQueue.push(obj.num);
+ }
+ this.startXRefQueue.shift();
+ }
+ return this.topDict;
+ } catch (e) {
+ if (e instanceof MissingDataException) {
+ throw e;
+ }
+ info('(while reading XRef): ' + e);
+ }
+ if (recoveryMode) {
+ return;
+ }
+ throw new XRefParseException();
+ },
+ getEntry: function XRef_getEntry(i) {
+ var xrefEntry = this.entries[i];
+ if (xrefEntry && !xrefEntry.free && xrefEntry.offset) {
+ return xrefEntry;
+ }
+ return null;
+ },
+ fetchIfRef: function XRef_fetchIfRef(obj, suppressEncryption) {
+ if (!isRef(obj)) {
+ return obj;
+ }
+ return this.fetch(obj, suppressEncryption);
+ },
+ fetch: function XRef_fetch(ref, suppressEncryption) {
+ assert(isRef(ref), 'ref object is not a reference');
+ var num = ref.num;
+ if (num in this.cache) {
+ var cacheEntry = this.cache[num];
+ return cacheEntry;
+ }
+ var xrefEntry = this.getEntry(num);
+ // the referenced entry can be free
+ if (xrefEntry === null) {
+ return this.cache[num] = null;
+ }
+ if (xrefEntry.uncompressed) {
+ xrefEntry = this.fetchUncompressed(ref, xrefEntry, suppressEncryption);
+ } else {
+ xrefEntry = this.fetchCompressed(xrefEntry, suppressEncryption);
+ }
+ if (isDict(xrefEntry)) {
+ xrefEntry.objId = ref.toString();
+ } else if (isStream(xrefEntry)) {
+ xrefEntry.dict.objId = ref.toString();
+ }
+ return xrefEntry;
+ },
+ fetchUncompressed: function XRef_fetchUncompressed(ref, xrefEntry, suppressEncryption) {
+ var gen = ref.gen;
+ var num = ref.num;
+ if (xrefEntry.gen !== gen) {
+ error('inconsistent generation in XRef');
+ }
+ var stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start);
+ var parser = new Parser(new Lexer(stream), true, this);
+ var obj1 = parser.getObj();
+ var obj2 = parser.getObj();
+ var obj3 = parser.getObj();
+ if (!isInt(obj1) || parseInt(obj1, 10) !== num || !isInt(obj2) || parseInt(obj2, 10) !== gen || !isCmd(obj3)) {
+ error('bad XRef entry');
+ }
+ if (!isCmd(obj3, 'obj')) {
+ // some bad PDFs use "obj1234" and really mean 1234
+ if (obj3.cmd.indexOf('obj') === 0) {
+ num = parseInt(obj3.cmd.substring(3), 10);
+ if (!isNaN(num)) {
+ return num;
+ }
+ }
+ error('bad XRef entry');
+ }
+ if (this.encrypt && !suppressEncryption) {
+ xrefEntry = parser.getObj(this.encrypt.createCipherTransform(num, gen));
+ } else {
+ xrefEntry = parser.getObj();
+ }
+ if (!isStream(xrefEntry)) {
+ this.cache[num] = xrefEntry;
+ }
+ return xrefEntry;
+ },
+ fetchCompressed: function XRef_fetchCompressed(xrefEntry, suppressEncryption) {
+ var tableOffset = xrefEntry.offset;
+ var stream = this.fetch(new Ref(tableOffset, 0));
+ if (!isStream(stream)) {
+ error('bad ObjStm stream');
+ }
+ var first = stream.dict.get('First');
+ var n = stream.dict.get('N');
+ if (!isInt(first) || !isInt(n)) {
+ error('invalid first and n parameters for ObjStm stream');
+ }
+ var parser = new Parser(new Lexer(stream), false, this);
+ parser.allowStreams = true;
+ var i, entries = [], num, nums = [];
+ // read the object numbers to populate cache
+ for (i = 0; i < n; ++i) {
+ num = parser.getObj();
+ if (!isInt(num)) {
+ error('invalid object number in the ObjStm stream: ' + num);
+ }
+ nums.push(num);
+ var offset = parser.getObj();
+ if (!isInt(offset)) {
+ error('invalid object offset in the ObjStm stream: ' + offset);
+ }
+ }
+ // read stream objects for cache
+ for (i = 0; i < n; ++i) {
+ entries.push(parser.getObj());
+ // The ObjStm should not contain 'endobj'. If it's present, skip over it
+ // to support corrupt PDFs (fixes issue 5241, bug 898610, bug 1037816).
+ if (isCmd(parser.buf1, 'endobj')) {
+ parser.shift();
+ }
+ num = nums[i];
+ var entry = this.entries[num];
+ if (entry && entry.offset === tableOffset && entry.gen === i) {
+ this.cache[num] = entries[i];
+ }
+ }
+ xrefEntry = entries[xrefEntry.gen];
+ if (xrefEntry === undefined) {
+ error('bad XRef entry for compressed object');
+ }
+ return xrefEntry;
+ },
+ fetchIfRefAsync: function XRef_fetchIfRefAsync(obj, suppressEncryption) {
+ if (!isRef(obj)) {
+ return Promise.resolve(obj);
+ }
+ return this.fetchAsync(obj, suppressEncryption);
+ },
+ fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) {
+ var streamManager = this.stream.manager;
+ var xref = this;
+ return new Promise(function tryFetch(resolve, reject) {
+ try {
+ resolve(xref.fetch(ref, suppressEncryption));
+ } catch (e) {
+ if (e instanceof MissingDataException) {
+ streamManager.requestRange(e.begin, e.end).then(function () {
+ tryFetch(resolve, reject);
+ }, reject);
+ return;
+ }
+ reject(e);
+ }
+ });
+ },
+ getCatalogObj: function XRef_getCatalogObj() {
+ return this.root;
+ }
+ };
+ return XRef;
+ }();
+ /**
+ * A NameTree/NumberTree is like a Dict but has some advantageous properties,
+ * see the specification (7.9.6 and 7.9.7) for additional details.
+ * TODO: implement all the Dict functions and make this more efficient.
+ */
+ var NameOrNumberTree = function NameOrNumberTreeClosure() {
+ function NameOrNumberTree(root, xref) {
+ throw new Error('Cannot initialize NameOrNumberTree.');
+ }
+ NameOrNumberTree.prototype = {
+ getAll: function NameOrNumberTree_getAll() {
+ var dict = Object.create(null);
+ if (!this.root) {
+ return dict;
+ }
+ var xref = this.xref;
+ // Reading Name/Number tree.
+ var processed = new RefSet();
+ processed.put(this.root);
+ var queue = [this.root];
+ while (queue.length > 0) {
+ var i, n;
+ var obj = xref.fetchIfRef(queue.shift());
+ if (!isDict(obj)) {
+ continue;
+ }
+ if (obj.has('Kids')) {
+ var kids = obj.get('Kids');
+ for (i = 0, n = kids.length; i < n; i++) {
+ var kid = kids[i];
+ assert(!processed.has(kid), 'Duplicate entry in "' + this._type + '" tree.');
+ queue.push(kid);
+ processed.put(kid);
+ }
+ continue;
+ }
+ var entries = obj.get(this._type);
+ if (isArray(entries)) {
+ for (i = 0, n = entries.length; i < n; i += 2) {
+ dict[xref.fetchIfRef(entries[i])] = xref.fetchIfRef(entries[i + 1]);
+ }
+ }
+ }
+ return dict;
+ },
+ get: function NameOrNumberTree_get(key) {
+ if (!this.root) {
+ return null;
+ }
+ var xref = this.xref;
+ var kidsOrEntries = xref.fetchIfRef(this.root);
+ var loopCount = 0;
+ var MAX_LEVELS = 10;
+ var l, r, m;
+ // Perform a binary search to quickly find the entry that
+ // contains the key we are looking for.
+ while (kidsOrEntries.has('Kids')) {
+ if (++loopCount > MAX_LEVELS) {
+ warn('Search depth limit reached for "' + this._type + '" tree.');
+ return null;
+ }
+ var kids = kidsOrEntries.get('Kids');
+ if (!isArray(kids)) {
+ return null;
+ }
+ l = 0;
+ r = kids.length - 1;
+ while (l <= r) {
+ m = l + r >> 1;
+ var kid = xref.fetchIfRef(kids[m]);
+ var limits = kid.get('Limits');
+ if (key < xref.fetchIfRef(limits[0])) {
+ r = m - 1;
+ } else if (key > xref.fetchIfRef(limits[1])) {
+ l = m + 1;
+ } else {
+ kidsOrEntries = xref.fetchIfRef(kids[m]);
+ break;
+ }
+ }
+ if (l > r) {
+ return null;
+ }
+ }
+ // If we get here, then we have found the right entry. Now go through the
+ // entries in the dictionary until we find the key we're looking for.
+ var entries = kidsOrEntries.get(this._type);
+ if (isArray(entries)) {
+ // Perform a binary search to reduce the lookup time.
+ l = 0;
+ r = entries.length - 2;
+ while (l <= r) {
+ // Check only even indices (0, 2, 4, ...) because the
+ // odd indices contain the actual data.
+ m = l + r & ~1;
+ var currentKey = xref.fetchIfRef(entries[m]);
+ if (key < currentKey) {
+ r = m - 2;
+ } else if (key > currentKey) {
+ l = m + 2;
+ } else {
+ return xref.fetchIfRef(entries[m + 1]);
+ }
+ }
+ }
+ return null;
+ }
+ };
+ return NameOrNumberTree;
+ }();
+ var NameTree = function NameTreeClosure() {
+ function NameTree(root, xref) {
+ this.root = root;
+ this.xref = xref;
+ this._type = 'Names';
+ }
+ Util.inherit(NameTree, NameOrNumberTree, {});
+ return NameTree;
+ }();
+ var NumberTree = function NumberTreeClosure() {
+ function NumberTree(root, xref) {
+ this.root = root;
+ this.xref = xref;
+ this._type = 'Nums';
+ }
+ Util.inherit(NumberTree, NameOrNumberTree, {});
+ return NumberTree;
+ }();
+ /**
+ * "A PDF file can refer to the contents of another file by using a File
+ * Specification (PDF 1.1)", see the spec (7.11) for more details.
+ * NOTE: Only embedded files are supported (as part of the attachments support)
+ * TODO: support the 'URL' file system (with caching if !/V), portable
+ * collections attributes and related files (/RF)
+ */
+ var FileSpec = function FileSpecClosure() {
+ function FileSpec(root, xref) {
+ if (!root || !isDict(root)) {
+ return;
+ }
+ this.xref = xref;
+ this.root = root;
+ if (root.has('FS')) {
+ this.fs = root.get('FS');
+ }
+ this.description = root.has('Desc') ? stringToPDFString(root.get('Desc')) : '';
+ if (root.has('RF')) {
+ warn('Related file specifications are not supported');
+ }
+ this.contentAvailable = true;
+ if (!root.has('EF')) {
+ this.contentAvailable = false;
+ warn('Non-embedded file specifications are not supported');
+ }
+ }
+ function pickPlatformItem(dict) {
+ // Look for the filename in this order:
+ // UF, F, Unix, Mac, DOS
+ if (dict.has('UF')) {
+ return dict.get('UF');
+ } else if (dict.has('F')) {
+ return dict.get('F');
+ } else if (dict.has('Unix')) {
+ return dict.get('Unix');
+ } else if (dict.has('Mac')) {
+ return dict.get('Mac');
+ } else if (dict.has('DOS')) {
+ return dict.get('DOS');
+ } else {
+ return null;
+ }
+ }
+ FileSpec.prototype = {
+ get filename() {
+ if (!this._filename && this.root) {
+ var filename = pickPlatformItem(this.root) || 'unnamed';
+ this._filename = stringToPDFString(filename).replace(/\\\\/g, '\\').replace(/\\\//g, '/').replace(/\\/g, '/');
+ }
+ return this._filename;
+ },
+ get content() {
+ if (!this.contentAvailable) {
+ return null;
+ }
+ if (!this.contentRef && this.root) {
+ this.contentRef = pickPlatformItem(this.root.get('EF'));
+ }
+ var content = null;
+ if (this.contentRef) {
+ var xref = this.xref;
+ var fileObj = xref.fetchIfRef(this.contentRef);
+ if (fileObj && isStream(fileObj)) {
+ content = fileObj.getBytes();
+ } else {
+ warn('Embedded file specification points to non-existing/invalid ' + 'content');
+ }
+ } else {
+ warn('Embedded file specification does not have a content');
+ }
+ return content;
+ },
+ get serializable() {
+ return {
+ filename: this.filename,
+ content: this.content
+ };
+ }
+ };
+ return FileSpec;
+ }();
+ /**
+ * A helper for loading missing data in object graphs. It traverses the graph
+ * depth first and queues up any objects that have missing data. Once it has
+ * has traversed as many objects that are available it attempts to bundle the
+ * missing data requests and then resume from the nodes that weren't ready.
+ *
+ * NOTE: It provides protection from circular references by keeping track of
+ * of loaded references. However, you must be careful not to load any graphs
+ * that have references to the catalog or other pages since that will cause the
+ * entire PDF document object graph to be traversed.
+ */
+ var ObjectLoader = function () {
+ function mayHaveChildren(value) {
+ return isRef(value) || isDict(value) || isArray(value) || isStream(value);
+ }
+ function addChildren(node, nodesToVisit) {
+ var value;
+ if (isDict(node) || isStream(node)) {
+ var map;
+ if (isDict(node)) {
+ map = node.map;
+ } else {
+ map = node.dict.map;
+ }
+ for (var key in map) {
+ value = map[key];
+ if (mayHaveChildren(value)) {
+ nodesToVisit.push(value);
+ }
+ }
+ } else if (isArray(node)) {
+ for (var i = 0, ii = node.length; i < ii; i++) {
+ value = node[i];
+ if (mayHaveChildren(value)) {
+ nodesToVisit.push(value);
+ }
+ }
+ }
+ }
+ function ObjectLoader(obj, keys, xref) {
+ this.obj = obj;
+ this.keys = keys;
+ this.xref = xref;
+ this.refSet = null;
+ this.capability = null;
+ }
+ ObjectLoader.prototype = {
+ load: function ObjectLoader_load() {
+ var keys = this.keys;
+ this.capability = createPromiseCapability();
+ // Don't walk the graph if all the data is already loaded.
+ if (!(this.xref.stream instanceof ChunkedStream) || this.xref.stream.getMissingChunks().length === 0) {
+ this.capability.resolve();
+ return this.capability.promise;
+ }
+ this.refSet = new RefSet();
+ // Setup the initial nodes to visit.
+ var nodesToVisit = [];
+ for (var i = 0; i < keys.length; i++) {
+ nodesToVisit.push(this.obj[keys[i]]);
+ }
+ this._walk(nodesToVisit);
+ return this.capability.promise;
+ },
+ _walk: function ObjectLoader_walk(nodesToVisit) {
+ var nodesToRevisit = [];
+ var pendingRequests = [];
+ // DFS walk of the object graph.
+ while (nodesToVisit.length) {
+ var currentNode = nodesToVisit.pop();
+ // Only references or chunked streams can cause missing data exceptions.
+ if (isRef(currentNode)) {
+ // Skip nodes that have already been visited.
+ if (this.refSet.has(currentNode)) {
+ continue;
+ }
+ try {
+ var ref = currentNode;
+ this.refSet.put(ref);
+ currentNode = this.xref.fetch(currentNode);
+ } catch (e) {
+ if (!(e instanceof MissingDataException)) {
+ throw e;
+ }
+ nodesToRevisit.push(currentNode);
+ pendingRequests.push({
+ begin: e.begin,
+ end: e.end
+ });
+ }
+ }
+ if (currentNode && currentNode.getBaseStreams) {
+ var baseStreams = currentNode.getBaseStreams();
+ var foundMissingData = false;
+ for (var i = 0; i < baseStreams.length; i++) {
+ var stream = baseStreams[i];
+ if (stream.getMissingChunks && stream.getMissingChunks().length) {
+ foundMissingData = true;
+ pendingRequests.push({
+ begin: stream.start,
+ end: stream.end
+ });
+ }
+ }
+ if (foundMissingData) {
+ nodesToRevisit.push(currentNode);
+ }
+ }
+ addChildren(currentNode, nodesToVisit);
+ }
+ if (pendingRequests.length) {
+ this.xref.stream.manager.requestRanges(pendingRequests).then(function pendingRequestCallback() {
+ nodesToVisit = nodesToRevisit;
+ for (var i = 0; i < nodesToRevisit.length; i++) {
+ var node = nodesToRevisit[i];
+ // Remove any reference nodes from the currrent refset so they
+ // aren't skipped when we revist them.
+ if (isRef(node)) {
+ this.refSet.remove(node);
+ }
+ }
+ this._walk(nodesToVisit);
+ }.bind(this), this.capability.reject);
+ return;
+ }
+ // Everything is loaded.
+ this.refSet = null;
+ this.capability.resolve();
+ }
+ };
+ return ObjectLoader;
+ }();
+ exports.Catalog = Catalog;
+ exports.ObjectLoader = ObjectLoader;
+ exports.XRef = XRef;
+ exports.FileSpec = FileSpec;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCorePattern = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreFunction, root.pdfjsCoreColorSpace);
+ }(this, function (exports, sharedUtil, corePrimitives, coreFunction, coreColorSpace) {
+ var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
+ var MissingDataException = sharedUtil.MissingDataException;
+ var Util = sharedUtil.Util;
+ var assert = sharedUtil.assert;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var warn = sharedUtil.warn;
+ var isStream = corePrimitives.isStream;
+ var PDFFunction = coreFunction.PDFFunction;
+ var ColorSpace = coreColorSpace.ColorSpace;
+ var ShadingType = {
+ FUNCTION_BASED: 1,
+ AXIAL: 2,
+ RADIAL: 3,
+ FREE_FORM_MESH: 4,
+ LATTICE_FORM_MESH: 5,
+ COONS_PATCH_MESH: 6,
+ TENSOR_PATCH_MESH: 7
+ };
+ var Pattern = function PatternClosure() {
+ // Constructor should define this.getPattern
+ function Pattern() {
+ error('should not call Pattern constructor');
+ }
+ Pattern.prototype = {
+ // Input: current Canvas context
+ // Output: the appropriate fillStyle or strokeStyle
+ getPattern: function Pattern_getPattern(ctx) {
+ error('Should not call Pattern.getStyle: ' + ctx);
+ }
+ };
+ Pattern.parseShading = function Pattern_parseShading(shading, matrix, xref, res, handler) {
+ var dict = isStream(shading) ? shading.dict : shading;
+ var type = dict.get('ShadingType');
+ try {
+ switch (type) {
+ case ShadingType.AXIAL:
+ case ShadingType.RADIAL:
+ // Both radial and axial shadings are handled by RadialAxial shading.
+ return new Shadings.RadialAxial(dict, matrix, xref, res);
+ case ShadingType.FREE_FORM_MESH:
+ case ShadingType.LATTICE_FORM_MESH:
+ case ShadingType.COONS_PATCH_MESH:
+ case ShadingType.TENSOR_PATCH_MESH:
+ return new Shadings.Mesh(shading, matrix, xref, res);
+ default:
+ throw new Error('Unsupported ShadingType: ' + type);
+ }
+ } catch (ex) {
+ if (ex instanceof MissingDataException) {
+ throw ex;
+ }
+ handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.shadingPattern });
+ warn(ex);
+ return new Shadings.Dummy();
+ }
+ };
+ return Pattern;
+ }();
+ var Shadings = {};
+ // A small number to offset the first/last color stops so we can insert ones to
+ // support extend. Number.MIN_VALUE is too small and breaks the extend.
+ Shadings.SMALL_NUMBER = 1e-6;
+ // Radial and axial shading have very similar implementations
+ // If needed, the implementations can be broken into two classes
+ Shadings.RadialAxial = function RadialAxialClosure() {
+ function RadialAxial(dict, matrix, xref, res) {
+ this.matrix = matrix;
+ this.coordsArr = dict.getArray('Coords');
+ this.shadingType = dict.get('ShadingType');
+ this.type = 'Pattern';
+ var cs = dict.get('ColorSpace', 'CS');
+ cs = ColorSpace.parse(cs, xref, res);
+ this.cs = cs;
+ var t0 = 0.0, t1 = 1.0;
+ if (dict.has('Domain')) {
+ var domainArr = dict.getArray('Domain');
+ t0 = domainArr[0];
+ t1 = domainArr[1];
+ }
+ var extendStart = false, extendEnd = false;
+ if (dict.has('Extend')) {
+ var extendArr = dict.getArray('Extend');
+ extendStart = extendArr[0];
+ extendEnd = extendArr[1];
+ }
+ if (this.shadingType === ShadingType.RADIAL && (!extendStart || !extendEnd)) {
+ // Radial gradient only currently works if either circle is fully within
+ // the other circle.
+ var x1 = this.coordsArr[0];
+ var y1 = this.coordsArr[1];
+ var r1 = this.coordsArr[2];
+ var x2 = this.coordsArr[3];
+ var y2 = this.coordsArr[4];
+ var r2 = this.coordsArr[5];
+ var distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
+ if (r1 <= r2 + distance && r2 <= r1 + distance) {
+ warn('Unsupported radial gradient.');
+ }
+ }
+ this.extendStart = extendStart;
+ this.extendEnd = extendEnd;
+ var fnObj = dict.get('Function');
+ var fn = PDFFunction.parseArray(xref, fnObj);
+ // 10 samples seems good enough for now, but probably won't work
+ // if there are sharp color changes. Ideally, we would implement
+ // the spec faithfully and add lossless optimizations.
+ var diff = t1 - t0;
+ var step = diff / 10;
+ var colorStops = this.colorStops = [];
+ // Protect against bad domains so we don't end up in an infinte loop below.
+ if (t0 >= t1 || step <= 0) {
+ // Acrobat doesn't seem to handle these cases so we'll ignore for
+ // now.
+ info('Bad shading domain.');
+ return;
+ }
+ var color = new Float32Array(cs.numComps), ratio = new Float32Array(1);
+ var rgbColor;
+ for (var i = t0; i <= t1; i += step) {
+ ratio[0] = i;
+ fn(ratio, 0, color, 0);
+ rgbColor = cs.getRgb(color, 0);
+ var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
+ colorStops.push([
+ (i - t0) / diff,
+ cssColor
+ ]);
+ }
+ var background = 'transparent';
+ if (dict.has('Background')) {
+ rgbColor = cs.getRgb(dict.get('Background'), 0);
+ background = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
+ }
+ if (!extendStart) {
+ // Insert a color stop at the front and offset the first real color stop
+ // so it doesn't conflict with the one we insert.
+ colorStops.unshift([
+ 0,
+ background
+ ]);
+ colorStops[1][0] += Shadings.SMALL_NUMBER;
+ }
+ if (!extendEnd) {
+ // Same idea as above in extendStart but for the end.
+ colorStops[colorStops.length - 1][0] -= Shadings.SMALL_NUMBER;
+ colorStops.push([
+ 1,
+ background
+ ]);
+ }
+ this.colorStops = colorStops;
+ }
+ RadialAxial.prototype = {
+ getIR: function RadialAxial_getIR() {
+ var coordsArr = this.coordsArr;
+ var shadingType = this.shadingType;
+ var type, p0, p1, r0, r1;
+ if (shadingType === ShadingType.AXIAL) {
+ p0 = [
+ coordsArr[0],
+ coordsArr[1]
+ ];
+ p1 = [
+ coordsArr[2],
+ coordsArr[3]
+ ];
+ r0 = null;
+ r1 = null;
+ type = 'axial';
+ } else if (shadingType === ShadingType.RADIAL) {
+ p0 = [
+ coordsArr[0],
+ coordsArr[1]
+ ];
+ p1 = [
+ coordsArr[3],
+ coordsArr[4]
+ ];
+ r0 = coordsArr[2];
+ r1 = coordsArr[5];
+ type = 'radial';
+ } else {
+ error('getPattern type unknown: ' + shadingType);
+ }
+ var matrix = this.matrix;
+ if (matrix) {
+ p0 = Util.applyTransform(p0, matrix);
+ p1 = Util.applyTransform(p1, matrix);
+ if (shadingType === ShadingType.RADIAL) {
+ var scale = Util.singularValueDecompose2dScale(matrix);
+ r0 *= scale[0];
+ r1 *= scale[1];
+ }
+ }
+ return [
+ 'RadialAxial',
+ type,
+ this.colorStops,
+ p0,
+ p1,
+ r0,
+ r1
+ ];
+ }
+ };
+ return RadialAxial;
+ }();
+ // All mesh shading. For now, they will be presented as set of the triangles
+ // to be drawn on the canvas and rgb color for each vertex.
+ Shadings.Mesh = function MeshClosure() {
+ function MeshStreamReader(stream, context) {
+ this.stream = stream;
+ this.context = context;
+ this.buffer = 0;
+ this.bufferLength = 0;
+ var numComps = context.numComps;
+ this.tmpCompsBuf = new Float32Array(numComps);
+ var csNumComps = context.colorSpace.numComps;
+ this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : this.tmpCompsBuf;
+ }
+ MeshStreamReader.prototype = {
+ get hasData() {
+ if (this.stream.end) {
+ return this.stream.pos < this.stream.end;
+ }
+ if (this.bufferLength > 0) {
+ return true;
+ }
+ var nextByte = this.stream.getByte();
+ if (nextByte < 0) {
+ return false;
+ }
+ this.buffer = nextByte;
+ this.bufferLength = 8;
+ return true;
+ },
+ readBits: function MeshStreamReader_readBits(n) {
+ var buffer = this.buffer;
+ var bufferLength = this.bufferLength;
+ if (n === 32) {
+ if (bufferLength === 0) {
+ return (this.stream.getByte() << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte()) >>> 0;
+ }
+ buffer = buffer << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte();
+ var nextByte = this.stream.getByte();
+ this.buffer = nextByte & (1 << bufferLength) - 1;
+ return (buffer << 8 - bufferLength | (nextByte & 0xFF) >> bufferLength) >>> 0;
+ }
+ if (n === 8 && bufferLength === 0) {
+ return this.stream.getByte();
+ }
+ while (bufferLength < n) {
+ buffer = buffer << 8 | this.stream.getByte();
+ bufferLength += 8;
+ }
+ bufferLength -= n;
+ this.bufferLength = bufferLength;
+ this.buffer = buffer & (1 << bufferLength) - 1;
+ return buffer >> bufferLength;
+ },
+ align: function MeshStreamReader_align() {
+ this.buffer = 0;
+ this.bufferLength = 0;
+ },
+ readFlag: function MeshStreamReader_readFlag() {
+ return this.readBits(this.context.bitsPerFlag);
+ },
+ readCoordinate: function MeshStreamReader_readCoordinate() {
+ var bitsPerCoordinate = this.context.bitsPerCoordinate;
+ var xi = this.readBits(bitsPerCoordinate);
+ var yi = this.readBits(bitsPerCoordinate);
+ var decode = this.context.decode;
+ var scale = bitsPerCoordinate < 32 ? 1 / ((1 << bitsPerCoordinate) - 1) : 2.3283064365386963e-10;
+ // 2 ^ -32
+ return [
+ xi * scale * (decode[1] - decode[0]) + decode[0],
+ yi * scale * (decode[3] - decode[2]) + decode[2]
+ ];
+ },
+ readComponents: function MeshStreamReader_readComponents() {
+ var numComps = this.context.numComps;
+ var bitsPerComponent = this.context.bitsPerComponent;
+ var scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10;
+ // 2 ^ -32
+ var decode = this.context.decode;
+ var components = this.tmpCompsBuf;
+ for (var i = 0, j = 4; i < numComps; i++, j += 2) {
+ var ci = this.readBits(bitsPerComponent);
+ components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j];
+ }
+ var color = this.tmpCsCompsBuf;
+ if (this.context.colorFn) {
+ this.context.colorFn(components, 0, color, 0);
+ }
+ return this.context.colorSpace.getRgb(color, 0);
+ }
+ };
+ function decodeType4Shading(mesh, reader) {
+ var coords = mesh.coords;
+ var colors = mesh.colors;
+ var operators = [];
+ var ps = [];
+ // not maintaining cs since that will match ps
+ var verticesLeft = 0;
+ // assuming we have all data to start a new triangle
+ while (reader.hasData) {
+ var f = reader.readFlag();
+ var coord = reader.readCoordinate();
+ var color = reader.readComponents();
+ if (verticesLeft === 0) {
+ // ignoring flags if we started a triangle
+ assert(0 <= f && f <= 2, 'Unknown type4 flag');
+ switch (f) {
+ case 0:
+ verticesLeft = 3;
+ break;
+ case 1:
+ ps.push(ps[ps.length - 2], ps[ps.length - 1]);
+ verticesLeft = 1;
+ break;
+ case 2:
+ ps.push(ps[ps.length - 3], ps[ps.length - 1]);
+ verticesLeft = 1;
+ break;
+ }
+ operators.push(f);
+ }
+ ps.push(coords.length);
+ coords.push(coord);
+ colors.push(color);
+ verticesLeft--;
+ reader.align();
+ }
+ mesh.figures.push({
+ type: 'triangles',
+ coords: new Int32Array(ps),
+ colors: new Int32Array(ps)
+ });
+ }
+ function decodeType5Shading(mesh, reader, verticesPerRow) {
+ var coords = mesh.coords;
+ var colors = mesh.colors;
+ var ps = [];
+ // not maintaining cs since that will match ps
+ while (reader.hasData) {
+ var coord = reader.readCoordinate();
+ var color = reader.readComponents();
+ ps.push(coords.length);
+ coords.push(coord);
+ colors.push(color);
+ }
+ mesh.figures.push({
+ type: 'lattice',
+ coords: new Int32Array(ps),
+ colors: new Int32Array(ps),
+ verticesPerRow: verticesPerRow
+ });
+ }
+ var MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3;
+ var MAX_SPLIT_PATCH_CHUNKS_AMOUNT = 20;
+ var TRIANGLE_DENSITY = 20;
+ // count of triangles per entire mesh bounds
+ var getB = function getBClosure() {
+ function buildB(count) {
+ var lut = [];
+ for (var i = 0; i <= count; i++) {
+ var t = i / count, t_ = 1 - t;
+ lut.push(new Float32Array([
+ t_ * t_ * t_,
+ 3 * t * t_ * t_,
+ 3 * t * t * t_,
+ t * t * t
+ ]));
+ }
+ return lut;
+ }
+ var cache = [];
+ return function getB(count) {
+ if (!cache[count]) {
+ cache[count] = buildB(count);
+ }
+ return cache[count];
+ };
+ }();
+ function buildFigureFromPatch(mesh, index) {
+ var figure = mesh.figures[index];
+ assert(figure.type === 'patch', 'Unexpected patch mesh figure');
+ var coords = mesh.coords, colors = mesh.colors;
+ var pi = figure.coords;
+ var ci = figure.colors;
+ var figureMinX = Math.min(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
+ var figureMinY = Math.min(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
+ var figureMaxX = Math.max(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
+ var figureMaxY = Math.max(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
+ var splitXBy = Math.ceil((figureMaxX - figureMinX) * TRIANGLE_DENSITY / (mesh.bounds[2] - mesh.bounds[0]));
+ splitXBy = Math.max(MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy));
+ var splitYBy = Math.ceil((figureMaxY - figureMinY) * TRIANGLE_DENSITY / (mesh.bounds[3] - mesh.bounds[1]));
+ splitYBy = Math.max(MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy));
+ var verticesPerRow = splitXBy + 1;
+ var figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow);
+ var figureColors = new Int32Array((splitYBy + 1) * verticesPerRow);
+ var k = 0;
+ var cl = new Uint8Array(3), cr = new Uint8Array(3);
+ var c0 = colors[ci[0]], c1 = colors[ci[1]], c2 = colors[ci[2]], c3 = colors[ci[3]];
+ var bRow = getB(splitYBy), bCol = getB(splitXBy);
+ for (var row = 0; row <= splitYBy; row++) {
+ cl[0] = (c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy | 0;
+ cl[1] = (c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy | 0;
+ cl[2] = (c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy | 0;
+ cr[0] = (c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy | 0;
+ cr[1] = (c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy | 0;
+ cr[2] = (c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy | 0;
+ for (var col = 0; col <= splitXBy; col++, k++) {
+ if ((row === 0 || row === splitYBy) && (col === 0 || col === splitXBy)) {
+ continue;
+ }
+ var x = 0, y = 0;
+ var q = 0;
+ for (var i = 0; i <= 3; i++) {
+ for (var j = 0; j <= 3; j++, q++) {
+ var m = bRow[row][i] * bCol[col][j];
+ x += coords[pi[q]][0] * m;
+ y += coords[pi[q]][1] * m;
+ }
+ }
+ figureCoords[k] = coords.length;
+ coords.push([
+ x,
+ y
+ ]);
+ figureColors[k] = colors.length;
+ var newColor = new Uint8Array(3);
+ newColor[0] = (cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy | 0;
+ newColor[1] = (cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy | 0;
+ newColor[2] = (cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy | 0;
+ colors.push(newColor);
+ }
+ }
+ figureCoords[0] = pi[0];
+ figureColors[0] = ci[0];
+ figureCoords[splitXBy] = pi[3];
+ figureColors[splitXBy] = ci[1];
+ figureCoords[verticesPerRow * splitYBy] = pi[12];
+ figureColors[verticesPerRow * splitYBy] = ci[2];
+ figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15];
+ figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3];
+ mesh.figures[index] = {
+ type: 'lattice',
+ coords: figureCoords,
+ colors: figureColors,
+ verticesPerRow: verticesPerRow
+ };
+ }
+ function decodeType6Shading(mesh, reader) {
+ // A special case of Type 7. The p11, p12, p21, p22 automatically filled
+ var coords = mesh.coords;
+ var colors = mesh.colors;
+ var ps = new Int32Array(16);
+ // p00, p10, ..., p30, p01, ..., p33
+ var cs = new Int32Array(4);
+ // c00, c30, c03, c33
+ while (reader.hasData) {
+ var f = reader.readFlag();
+ assert(0 <= f && f <= 3, 'Unknown type6 flag');
+ var i, ii;
+ var pi = coords.length;
+ for (i = 0, ii = f !== 0 ? 8 : 12; i < ii; i++) {
+ coords.push(reader.readCoordinate());
+ }
+ var ci = colors.length;
+ for (i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
+ colors.push(reader.readComponents());
+ }
+ var tmp1, tmp2, tmp3, tmp4;
+ switch (f) {
+ case 0:
+ ps[12] = pi + 3;
+ ps[13] = pi + 4;
+ ps[14] = pi + 5;
+ ps[15] = pi + 6;
+ ps[8] = pi + 2;
+ /* values for 5, 6, 9, 10 are */
+ ps[11] = pi + 7;
+ ps[4] = pi + 1;
+ /* calculated below */
+ ps[7] = pi + 8;
+ ps[0] = pi;
+ ps[1] = pi + 11;
+ ps[2] = pi + 10;
+ ps[3] = pi + 9;
+ cs[2] = ci + 1;
+ cs[3] = ci + 2;
+ cs[0] = ci;
+ cs[1] = ci + 3;
+ break;
+ case 1:
+ tmp1 = ps[12];
+ tmp2 = ps[13];
+ tmp3 = ps[14];
+ tmp4 = ps[15];
+ ps[12] = tmp4;
+ ps[13] = pi + 0;
+ ps[14] = pi + 1;
+ ps[15] = pi + 2;
+ ps[8] = tmp3;
+ /* values for 5, 6, 9, 10 are */
+ ps[11] = pi + 3;
+ ps[4] = tmp2;
+ /* calculated below */
+ ps[7] = pi + 4;
+ ps[0] = tmp1;
+ ps[1] = pi + 7;
+ ps[2] = pi + 6;
+ ps[3] = pi + 5;
+ tmp1 = cs[2];
+ tmp2 = cs[3];
+ cs[2] = tmp2;
+ cs[3] = ci;
+ cs[0] = tmp1;
+ cs[1] = ci + 1;
+ break;
+ case 2:
+ tmp1 = ps[15];
+ tmp2 = ps[11];
+ ps[12] = ps[3];
+ ps[13] = pi + 0;
+ ps[14] = pi + 1;
+ ps[15] = pi + 2;
+ ps[8] = ps[7];
+ /* values for 5, 6, 9, 10 are */
+ ps[11] = pi + 3;
+ ps[4] = tmp2;
+ /* calculated below */
+ ps[7] = pi + 4;
+ ps[0] = tmp1;
+ ps[1] = pi + 7;
+ ps[2] = pi + 6;
+ ps[3] = pi + 5;
+ tmp1 = cs[3];
+ cs[2] = cs[1];
+ cs[3] = ci;
+ cs[0] = tmp1;
+ cs[1] = ci + 1;
+ break;
+ case 3:
+ ps[12] = ps[0];
+ ps[13] = pi + 0;
+ ps[14] = pi + 1;
+ ps[15] = pi + 2;
+ ps[8] = ps[1];
+ /* values for 5, 6, 9, 10 are */
+ ps[11] = pi + 3;
+ ps[4] = ps[2];
+ /* calculated below */
+ ps[7] = pi + 4;
+ ps[0] = ps[3];
+ ps[1] = pi + 7;
+ ps[2] = pi + 6;
+ ps[3] = pi + 5;
+ cs[2] = cs[0];
+ cs[3] = ci;
+ cs[0] = cs[1];
+ cs[1] = ci + 1;
+ break;
+ }
+ // set p11, p12, p21, p22
+ ps[5] = coords.length;
+ coords.push([
+ (-4 * coords[ps[0]][0] - coords[ps[15]][0] + 6 * (coords[ps[4]][0] + coords[ps[1]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[13]][0] + coords[ps[7]][0])) / 9,
+ (-4 * coords[ps[0]][1] - coords[ps[15]][1] + 6 * (coords[ps[4]][1] + coords[ps[1]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[13]][1] + coords[ps[7]][1])) / 9
+ ]);
+ ps[6] = coords.length;
+ coords.push([
+ (-4 * coords[ps[3]][0] - coords[ps[12]][0] + 6 * (coords[ps[2]][0] + coords[ps[7]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[4]][0] + coords[ps[14]][0])) / 9,
+ (-4 * coords[ps[3]][1] - coords[ps[12]][1] + 6 * (coords[ps[2]][1] + coords[ps[7]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[4]][1] + coords[ps[14]][1])) / 9
+ ]);
+ ps[9] = coords.length;
+ coords.push([
+ (-4 * coords[ps[12]][0] - coords[ps[3]][0] + 6 * (coords[ps[8]][0] + coords[ps[13]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[11]][0] + coords[ps[1]][0])) / 9,
+ (-4 * coords[ps[12]][1] - coords[ps[3]][1] + 6 * (coords[ps[8]][1] + coords[ps[13]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[11]][1] + coords[ps[1]][1])) / 9
+ ]);
+ ps[10] = coords.length;
+ coords.push([
+ (-4 * coords[ps[15]][0] - coords[ps[0]][0] + 6 * (coords[ps[11]][0] + coords[ps[14]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[2]][0] + coords[ps[8]][0])) / 9,
+ (-4 * coords[ps[15]][1] - coords[ps[0]][1] + 6 * (coords[ps[11]][1] + coords[ps[14]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[2]][1] + coords[ps[8]][1])) / 9
+ ]);
+ mesh.figures.push({
+ type: 'patch',
+ coords: new Int32Array(ps),
+ // making copies of ps and cs
+ colors: new Int32Array(cs)
+ });
+ }
+ }
+ function decodeType7Shading(mesh, reader) {
+ var coords = mesh.coords;
+ var colors = mesh.colors;
+ var ps = new Int32Array(16);
+ // p00, p10, ..., p30, p01, ..., p33
+ var cs = new Int32Array(4);
+ // c00, c30, c03, c33
+ while (reader.hasData) {
+ var f = reader.readFlag();
+ assert(0 <= f && f <= 3, 'Unknown type7 flag');
+ var i, ii;
+ var pi = coords.length;
+ for (i = 0, ii = f !== 0 ? 12 : 16; i < ii; i++) {
+ coords.push(reader.readCoordinate());
+ }
+ var ci = colors.length;
+ for (i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
+ colors.push(reader.readComponents());
+ }
+ var tmp1, tmp2, tmp3, tmp4;
+ switch (f) {
+ case 0:
+ ps[12] = pi + 3;
+ ps[13] = pi + 4;
+ ps[14] = pi + 5;
+ ps[15] = pi + 6;
+ ps[8] = pi + 2;
+ ps[9] = pi + 13;
+ ps[10] = pi + 14;
+ ps[11] = pi + 7;
+ ps[4] = pi + 1;
+ ps[5] = pi + 12;
+ ps[6] = pi + 15;
+ ps[7] = pi + 8;
+ ps[0] = pi;
+ ps[1] = pi + 11;
+ ps[2] = pi + 10;
+ ps[3] = pi + 9;
+ cs[2] = ci + 1;
+ cs[3] = ci + 2;
+ cs[0] = ci;
+ cs[1] = ci + 3;
+ break;
+ case 1:
+ tmp1 = ps[12];
+ tmp2 = ps[13];
+ tmp3 = ps[14];
+ tmp4 = ps[15];
+ ps[12] = tmp4;
+ ps[13] = pi + 0;
+ ps[14] = pi + 1;
+ ps[15] = pi + 2;
+ ps[8] = tmp3;
+ ps[9] = pi + 9;
+ ps[10] = pi + 10;
+ ps[11] = pi + 3;
+ ps[4] = tmp2;
+ ps[5] = pi + 8;
+ ps[6] = pi + 11;
+ ps[7] = pi + 4;
+ ps[0] = tmp1;
+ ps[1] = pi + 7;
+ ps[2] = pi + 6;
+ ps[3] = pi + 5;
+ tmp1 = cs[2];
+ tmp2 = cs[3];
+ cs[2] = tmp2;
+ cs[3] = ci;
+ cs[0] = tmp1;
+ cs[1] = ci + 1;
+ break;
+ case 2:
+ tmp1 = ps[15];
+ tmp2 = ps[11];
+ ps[12] = ps[3];
+ ps[13] = pi + 0;
+ ps[14] = pi + 1;
+ ps[15] = pi + 2;
+ ps[8] = ps[7];
+ ps[9] = pi + 9;
+ ps[10] = pi + 10;
+ ps[11] = pi + 3;
+ ps[4] = tmp2;
+ ps[5] = pi + 8;
+ ps[6] = pi + 11;
+ ps[7] = pi + 4;
+ ps[0] = tmp1;
+ ps[1] = pi + 7;
+ ps[2] = pi + 6;
+ ps[3] = pi + 5;
+ tmp1 = cs[3];
+ cs[2] = cs[1];
+ cs[3] = ci;
+ cs[0] = tmp1;
+ cs[1] = ci + 1;
+ break;
+ case 3:
+ ps[12] = ps[0];
+ ps[13] = pi + 0;
+ ps[14] = pi + 1;
+ ps[15] = pi + 2;
+ ps[8] = ps[1];
+ ps[9] = pi + 9;
+ ps[10] = pi + 10;
+ ps[11] = pi + 3;
+ ps[4] = ps[2];
+ ps[5] = pi + 8;
+ ps[6] = pi + 11;
+ ps[7] = pi + 4;
+ ps[0] = ps[3];
+ ps[1] = pi + 7;
+ ps[2] = pi + 6;
+ ps[3] = pi + 5;
+ cs[2] = cs[0];
+ cs[3] = ci;
+ cs[0] = cs[1];
+ cs[1] = ci + 1;
+ break;
+ }
+ mesh.figures.push({
+ type: 'patch',
+ coords: new Int32Array(ps),
+ // making copies of ps and cs
+ colors: new Int32Array(cs)
+ });
+ }
+ }
+ function updateBounds(mesh) {
+ var minX = mesh.coords[0][0], minY = mesh.coords[0][1], maxX = minX, maxY = minY;
+ for (var i = 1, ii = mesh.coords.length; i < ii; i++) {
+ var x = mesh.coords[i][0], y = mesh.coords[i][1];
+ minX = minX > x ? x : minX;
+ minY = minY > y ? y : minY;
+ maxX = maxX < x ? x : maxX;
+ maxY = maxY < y ? y : maxY;
+ }
+ mesh.bounds = [
+ minX,
+ minY,
+ maxX,
+ maxY
+ ];
+ }
+ function packData(mesh) {
+ var i, ii, j, jj;
+ var coords = mesh.coords;
+ var coordsPacked = new Float32Array(coords.length * 2);
+ for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
+ var xy = coords[i];
+ coordsPacked[j++] = xy[0];
+ coordsPacked[j++] = xy[1];
+ }
+ mesh.coords = coordsPacked;
+ var colors = mesh.colors;
+ var colorsPacked = new Uint8Array(colors.length * 3);
+ for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
+ var c = colors[i];
+ colorsPacked[j++] = c[0];
+ colorsPacked[j++] = c[1];
+ colorsPacked[j++] = c[2];
+ }
+ mesh.colors = colorsPacked;
+ var figures = mesh.figures;
+ for (i = 0, ii = figures.length; i < ii; i++) {
+ var figure = figures[i], ps = figure.coords, cs = figure.colors;
+ for (j = 0, jj = ps.length; j < jj; j++) {
+ ps[j] *= 2;
+ cs[j] *= 3;
+ }
+ }
+ }
+ function Mesh(stream, matrix, xref, res) {
+ assert(isStream(stream), 'Mesh data is not a stream');
+ var dict = stream.dict;
+ this.matrix = matrix;
+ this.shadingType = dict.get('ShadingType');
+ this.type = 'Pattern';
+ this.bbox = dict.getArray('BBox');
+ var cs = dict.get('ColorSpace', 'CS');
+ cs = ColorSpace.parse(cs, xref, res);
+ this.cs = cs;
+ this.background = dict.has('Background') ? cs.getRgb(dict.get('Background'), 0) : null;
+ var fnObj = dict.get('Function');
+ var fn = fnObj ? PDFFunction.parseArray(xref, fnObj) : null;
+ this.coords = [];
+ this.colors = [];
+ this.figures = [];
+ var decodeContext = {
+ bitsPerCoordinate: dict.get('BitsPerCoordinate'),
+ bitsPerComponent: dict.get('BitsPerComponent'),
+ bitsPerFlag: dict.get('BitsPerFlag'),
+ decode: dict.getArray('Decode'),
+ colorFn: fn,
+ colorSpace: cs,
+ numComps: fn ? 1 : cs.numComps
+ };
+ var reader = new MeshStreamReader(stream, decodeContext);
+ var patchMesh = false;
+ switch (this.shadingType) {
+ case ShadingType.FREE_FORM_MESH:
+ decodeType4Shading(this, reader);
+ break;
+ case ShadingType.LATTICE_FORM_MESH:
+ var verticesPerRow = dict.get('VerticesPerRow') | 0;
+ assert(verticesPerRow >= 2, 'Invalid VerticesPerRow');
+ decodeType5Shading(this, reader, verticesPerRow);
+ break;
+ case ShadingType.COONS_PATCH_MESH:
+ decodeType6Shading(this, reader);
+ patchMesh = true;
+ break;
+ case ShadingType.TENSOR_PATCH_MESH:
+ decodeType7Shading(this, reader);
+ patchMesh = true;
+ break;
+ default:
+ error('Unsupported mesh type.');
+ break;
+ }
+ if (patchMesh) {
+ // dirty bounds calculation for determining, how dense shall be triangles
+ updateBounds(this);
+ for (var i = 0, ii = this.figures.length; i < ii; i++) {
+ buildFigureFromPatch(this, i);
+ }
+ }
+ // calculate bounds
+ updateBounds(this);
+ packData(this);
+ }
+ Mesh.prototype = {
+ getIR: function Mesh_getIR() {
+ return [
+ 'Mesh',
+ this.shadingType,
+ this.coords,
+ this.colors,
+ this.figures,
+ this.bounds,
+ this.matrix,
+ this.bbox,
+ this.background
+ ];
+ }
+ };
+ return Mesh;
+ }();
+ Shadings.Dummy = function DummyClosure() {
+ function Dummy() {
+ this.type = 'Pattern';
+ }
+ Dummy.prototype = {
+ getIR: function Dummy_getIR() {
+ return ['Dummy'];
+ }
+ };
+ return Dummy;
+ }();
+ function getTilingPatternIR(operatorList, dict, args) {
+ var matrix = dict.getArray('Matrix');
+ var bbox = dict.getArray('BBox');
+ var xstep = dict.get('XStep');
+ var ystep = dict.get('YStep');
+ var paintType = dict.get('PaintType');
+ var tilingType = dict.get('TilingType');
+ return [
+ 'TilingPattern',
+ args,
+ operatorList,
+ matrix,
+ bbox,
+ xstep,
+ ystep,
+ paintType,
+ tilingType
+ ];
+ }
+ exports.Pattern = Pattern;
+ exports.getTilingPatternIR = getTilingPatternIR;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreEvaluator = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser, root.pdfjsCoreImage, root.pdfjsCoreColorSpace, root.pdfjsCoreMurmurHash3, root.pdfjsCoreFonts, root.pdfjsCoreFunction, root.pdfjsCorePattern, root.pdfjsCoreCMap, root.pdfjsCoreMetrics, root.pdfjsCoreBidi, root.pdfjsCoreEncodings, root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode, root.pdfjsCoreGlyphList);
+ }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser, coreImage, coreColorSpace, coreMurmurHash3, coreFonts, coreFunction, corePattern, coreCMap, coreMetrics, coreBidi, coreEncodings, coreStandardFonts, coreUnicode, coreGlyphList) {
+ var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
+ var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
+ var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
+ var ImageKind = sharedUtil.ImageKind;
+ var OPS = sharedUtil.OPS;
+ var TextRenderingMode = sharedUtil.TextRenderingMode;
+ var Util = sharedUtil.Util;
+ var assert = sharedUtil.assert;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var isNum = sharedUtil.isNum;
+ var isString = sharedUtil.isString;
+ var getLookupTableFactory = sharedUtil.getLookupTableFactory;
+ var warn = sharedUtil.warn;
+ var Dict = corePrimitives.Dict;
+ var Name = corePrimitives.Name;
+ var isCmd = corePrimitives.isCmd;
+ var isDict = corePrimitives.isDict;
+ var isName = corePrimitives.isName;
+ var isRef = corePrimitives.isRef;
+ var isStream = corePrimitives.isStream;
+ var DecodeStream = coreStream.DecodeStream;
+ var JpegStream = coreStream.JpegStream;
+ var Stream = coreStream.Stream;
+ var Lexer = coreParser.Lexer;
+ var Parser = coreParser.Parser;
+ var isEOF = coreParser.isEOF;
+ var PDFImage = coreImage.PDFImage;
+ var ColorSpace = coreColorSpace.ColorSpace;
+ var MurmurHash3_64 = coreMurmurHash3.MurmurHash3_64;
+ var ErrorFont = coreFonts.ErrorFont;
+ var FontFlags = coreFonts.FontFlags;
+ var Font = coreFonts.Font;
+ var IdentityToUnicodeMap = coreFonts.IdentityToUnicodeMap;
+ var ToUnicodeMap = coreFonts.ToUnicodeMap;
+ var getFontType = coreFonts.getFontType;
+ var isPDFFunction = coreFunction.isPDFFunction;
+ var PDFFunction = coreFunction.PDFFunction;
+ var Pattern = corePattern.Pattern;
+ var getTilingPatternIR = corePattern.getTilingPatternIR;
+ var CMapFactory = coreCMap.CMapFactory;
+ var IdentityCMap = coreCMap.IdentityCMap;
+ var getMetrics = coreMetrics.getMetrics;
+ var bidi = coreBidi.bidi;
+ var WinAnsiEncoding = coreEncodings.WinAnsiEncoding;
+ var StandardEncoding = coreEncodings.StandardEncoding;
+ var MacRomanEncoding = coreEncodings.MacRomanEncoding;
+ var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
+ var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
+ var getEncoding = coreEncodings.getEncoding;
+ var getStdFontMap = coreStandardFonts.getStdFontMap;
+ var getSerifFonts = coreStandardFonts.getSerifFonts;
+ var getSymbolsFonts = coreStandardFonts.getSymbolsFonts;
+ var getNormalizedUnicodes = coreUnicode.getNormalizedUnicodes;
+ var reverseIfRtl = coreUnicode.reverseIfRtl;
+ var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
+ var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
+ var PartialEvaluator = function PartialEvaluatorClosure() {
+ var DefaultPartialEvaluatorOptions = {
+ forceDataSchema: false,
+ maxImageSize: -1,
+ disableFontFace: false,
+ cMapOptions: {
+ url: null,
+ packed: false
+ }
+ };
+ function NativeImageDecoder(xref, resources, handler, forceDataSchema) {
+ this.xref = xref;
+ this.resources = resources;
+ this.handler = handler;
+ this.forceDataSchema = forceDataSchema;
+ }
+ NativeImageDecoder.prototype = {
+ canDecode: function (image) {
+ return image instanceof JpegStream && NativeImageDecoder.isDecodable(image, this.xref, this.resources);
+ },
+ decode: function (image) {
+ // For natively supported JPEGs send them to the main thread for decoding.
+ var dict = image.dict;
+ var colorSpace = dict.get('ColorSpace', 'CS');
+ colorSpace = ColorSpace.parse(colorSpace, this.xref, this.resources);
+ var numComps = colorSpace.numComps;
+ var decodePromise = this.handler.sendWithPromise('JpegDecode', [
+ image.getIR(this.forceDataSchema),
+ numComps
+ ]);
+ return decodePromise.then(function (message) {
+ var data = message.data;
+ return new Stream(data, 0, data.length, image.dict);
+ });
+ }
+ };
+ /**
+ * Checks if the image can be decoded and displayed by the browser without any
+ * further processing such as color space conversions.
+ */
+ NativeImageDecoder.isSupported = function NativeImageDecoder_isSupported(image, xref, res) {
+ var dict = image.dict;
+ if (dict.has('DecodeParms') || dict.has('DP')) {
+ return false;
+ }
+ var cs = ColorSpace.parse(dict.get('ColorSpace', 'CS'), xref, res);
+ return (cs.name === 'DeviceGray' || cs.name === 'DeviceRGB') && cs.isDefaultDecode(dict.getArray('Decode', 'D'));
+ };
+ /**
+ * Checks if the image can be decoded by the browser.
+ */
+ NativeImageDecoder.isDecodable = function NativeImageDecoder_isDecodable(image, xref, res) {
+ var dict = image.dict;
+ if (dict.has('DecodeParms') || dict.has('DP')) {
+ return false;
+ }
+ var cs = ColorSpace.parse(dict.get('ColorSpace', 'CS'), xref, res);
+ return (cs.numComps === 1 || cs.numComps === 3) && cs.isDefaultDecode(dict.getArray('Decode', 'D'));
+ };
+ function PartialEvaluator(pdfManager, xref, handler, pageIndex, uniquePrefix, idCounters, fontCache, options) {
+ this.pdfManager = pdfManager;
+ this.xref = xref;
+ this.handler = handler;
+ this.pageIndex = pageIndex;
+ this.uniquePrefix = uniquePrefix;
+ this.idCounters = idCounters;
+ this.fontCache = fontCache;
+ this.options = options || DefaultPartialEvaluatorOptions;
+ }
+ // Trying to minimize Date.now() usage and check every 100 time
+ var TIME_SLOT_DURATION_MS = 20;
+ var CHECK_TIME_EVERY = 100;
+ function TimeSlotManager() {
+ this.reset();
+ }
+ TimeSlotManager.prototype = {
+ check: function TimeSlotManager_check() {
+ if (++this.checked < CHECK_TIME_EVERY) {
+ return false;
+ }
+ this.checked = 0;
+ return this.endTime <= Date.now();
+ },
+ reset: function TimeSlotManager_reset() {
+ this.endTime = Date.now() + TIME_SLOT_DURATION_MS;
+ this.checked = 0;
+ }
+ };
+ var deferred = Promise.resolve();
+ var TILING_PATTERN = 1, SHADING_PATTERN = 2;
+ PartialEvaluator.prototype = {
+ hasBlendModes: function PartialEvaluator_hasBlendModes(resources) {
+ if (!isDict(resources)) {
+ return false;
+ }
+ var processed = Object.create(null);
+ if (resources.objId) {
+ processed[resources.objId] = true;
+ }
+ var nodes = [resources], xref = this.xref;
+ while (nodes.length) {
+ var key, i, ii;
+ var node = nodes.shift();
+ // First check the current resources for blend modes.
+ var graphicStates = node.get('ExtGState');
+ if (isDict(graphicStates)) {
+ var graphicStatesKeys = graphicStates.getKeys();
+ for (i = 0, ii = graphicStatesKeys.length; i < ii; i++) {
+ key = graphicStatesKeys[i];
+ var graphicState = graphicStates.get(key);
+ var bm = graphicState.get('BM');
+ if (isName(bm) && bm.name !== 'Normal') {
+ return true;
+ }
+ }
+ }
+ // Descend into the XObjects to look for more resources and blend modes.
+ var xObjects = node.get('XObject');
+ if (!isDict(xObjects)) {
+ continue;
+ }
+ var xObjectsKeys = xObjects.getKeys();
+ for (i = 0, ii = xObjectsKeys.length; i < ii; i++) {
+ key = xObjectsKeys[i];
+ var xObject = xObjects.getRaw(key);
+ if (isRef(xObject)) {
+ if (processed[xObject.toString()]) {
+ // The XObject has already been processed, and by avoiding a
+ // redundant `xref.fetch` we can *significantly* reduce the load
+ // time for badly generated PDF files (fixes issue6961.pdf).
+ continue;
+ }
+ xObject = xref.fetch(xObject);
+ }
+ if (!isStream(xObject)) {
+ continue;
+ }
+ if (xObject.dict.objId) {
+ if (processed[xObject.dict.objId]) {
+ // stream has objId and is processed already
+ continue;
+ }
+ processed[xObject.dict.objId] = true;
+ }
+ var xResources = xObject.dict.get('Resources');
+ // Checking objId to detect an infinite loop.
+ if (isDict(xResources) && (!xResources.objId || !processed[xResources.objId])) {
+ nodes.push(xResources);
+ if (xResources.objId) {
+ processed[xResources.objId] = true;
+ }
+ }
+ }
+ }
+ return false;
+ },
+ buildFormXObject: function PartialEvaluator_buildFormXObject(resources, xobj, smask, operatorList, task, initialState) {
+ var matrix = xobj.dict.getArray('Matrix');
+ var bbox = xobj.dict.getArray('BBox');
+ var group = xobj.dict.get('Group');
+ if (group) {
+ var groupOptions = {
+ matrix: matrix,
+ bbox: bbox,
+ smask: smask,
+ isolated: false,
+ knockout: false
+ };
+ var groupSubtype = group.get('S');
+ var colorSpace;
+ if (isName(groupSubtype, 'Transparency')) {
+ groupOptions.isolated = group.get('I') || false;
+ groupOptions.knockout = group.get('K') || false;
+ colorSpace = group.has('CS') ? ColorSpace.parse(group.get('CS'), this.xref, resources) : null;
+ }
+ if (smask && smask.backdrop) {
+ colorSpace = colorSpace || ColorSpace.singletons.rgb;
+ smask.backdrop = colorSpace.getRgb(smask.backdrop, 0);
+ }
+ operatorList.addOp(OPS.beginGroup, [groupOptions]);
+ }
+ operatorList.addOp(OPS.paintFormXObjectBegin, [
+ matrix,
+ bbox
+ ]);
+ return this.getOperatorList(xobj, task, xobj.dict.get('Resources') || resources, operatorList, initialState).then(function () {
+ operatorList.addOp(OPS.paintFormXObjectEnd, []);
+ if (group) {
+ operatorList.addOp(OPS.endGroup, [groupOptions]);
+ }
+ });
+ },
+ buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(resources, image, inline, operatorList, cacheKey, imageCache) {
+ var self = this;
+ var dict = image.dict;
+ var w = dict.get('Width', 'W');
+ var h = dict.get('Height', 'H');
+ if (!(w && isNum(w)) || !(h && isNum(h))) {
+ warn('Image dimensions are missing, or not numbers.');
+ return;
+ }
+ var maxImageSize = this.options.maxImageSize;
+ if (maxImageSize !== -1 && w * h > maxImageSize) {
+ warn('Image exceeded maximum allowed size and was removed.');
+ return;
+ }
+ var imageMask = dict.get('ImageMask', 'IM') || false;
+ var imgData, args;
+ if (imageMask) {
+ // This depends on a tmpCanvas being filled with the
+ // current fillStyle, such that processing the pixel
+ // data can't be done here. Instead of creating a
+ // complete PDFImage, only read the information needed
+ // for later.
+ var width = dict.get('Width', 'W');
+ var height = dict.get('Height', 'H');
+ var bitStrideLength = width + 7 >> 3;
+ var imgArray = image.getBytes(bitStrideLength * height);
+ var decode = dict.getArray('Decode', 'D');
+ var inverseDecode = !!decode && decode[0] > 0;
+ imgData = PDFImage.createMask(imgArray, width, height, image instanceof DecodeStream, inverseDecode);
+ imgData.cached = true;
+ args = [imgData];
+ operatorList.addOp(OPS.paintImageMaskXObject, args);
+ if (cacheKey) {
+ imageCache[cacheKey] = {
+ fn: OPS.paintImageMaskXObject,
+ args: args
+ };
+ }
+ return;
+ }
+ var softMask = dict.get('SMask', 'SM') || false;
+ var mask = dict.get('Mask') || false;
+ var SMALL_IMAGE_DIMENSIONS = 200;
+ // Inlining small images into the queue as RGB data
+ if (inline && !softMask && !mask && !(image instanceof JpegStream) && w + h < SMALL_IMAGE_DIMENSIONS) {
+ var imageObj = new PDFImage(this.xref, resources, image, inline, null, null);
+ // We force the use of RGBA_32BPP images here, because we can't handle
+ // any other kind.
+ imgData = imageObj.createImageData(/* forceRGBA = */
+ true);
+ operatorList.addOp(OPS.paintInlineImageXObject, [imgData]);
+ return;
+ }
+ // If there is no imageMask, create the PDFImage and a lot
+ // of image processing can be done here.
+ var uniquePrefix = this.uniquePrefix || '';
+ var objId = 'img_' + uniquePrefix + ++this.idCounters.obj;
+ operatorList.addDependency(objId);
+ args = [
+ objId,
+ w,
+ h
+ ];
+ if (!softMask && !mask && image instanceof JpegStream && NativeImageDecoder.isSupported(image, this.xref, resources)) {
+ // These JPEGs don't need any more processing so we can just send it.
+ operatorList.addOp(OPS.paintJpegXObject, args);
+ this.handler.send('obj', [
+ objId,
+ this.pageIndex,
+ 'JpegStream',
+ image.getIR(this.options.forceDataSchema)
+ ]);
+ return;
+ }
+ // Creates native image decoder only if a JPEG image or mask is present.
+ var nativeImageDecoder = null;
+ if (image instanceof JpegStream || mask instanceof JpegStream || softMask instanceof JpegStream) {
+ nativeImageDecoder = new NativeImageDecoder(self.xref, resources, self.handler, self.options.forceDataSchema);
+ }
+ PDFImage.buildImage(self.handler, self.xref, resources, image, inline, nativeImageDecoder).then(function (imageObj) {
+ var imgData = imageObj.createImageData(/* forceRGBA = */
+ false);
+ self.handler.send('obj', [
+ objId,
+ self.pageIndex,
+ 'Image',
+ imgData
+ ], [imgData.data.buffer]);
+ }).then(undefined, function (reason) {
+ warn('Unable to decode image: ' + reason);
+ self.handler.send('obj', [
+ objId,
+ self.pageIndex,
+ 'Image',
+ null
+ ]);
+ });
+ operatorList.addOp(OPS.paintImageXObject, args);
+ if (cacheKey) {
+ imageCache[cacheKey] = {
+ fn: OPS.paintImageXObject,
+ args: args
+ };
+ }
+ },
+ handleSMask: function PartialEvaluator_handleSmask(smask, resources, operatorList, task, stateManager) {
+ var smaskContent = smask.get('G');
+ var smaskOptions = {
+ subtype: smask.get('S').name,
+ backdrop: smask.get('BC')
+ };
+ // The SMask might have a alpha/luminosity value transfer function --
+ // we will build a map of integer values in range 0..255 to be fast.
+ var transferObj = smask.get('TR');
+ if (isPDFFunction(transferObj)) {
+ var transferFn = PDFFunction.parse(this.xref, transferObj);
+ var transferMap = new Uint8Array(256);
+ var tmp = new Float32Array(1);
+ for (var i = 0; i < 256; i++) {
+ tmp[0] = i / 255;
+ transferFn(tmp, 0, tmp, 0);
+ transferMap[i] = tmp[0] * 255 | 0;
+ }
+ smaskOptions.transferMap = transferMap;
+ }
+ return this.buildFormXObject(resources, smaskContent, smaskOptions, operatorList, task, stateManager.state.clone());
+ },
+ handleTilingType: function PartialEvaluator_handleTilingType(fn, args, resources, pattern, patternDict, operatorList, task) {
+ // Create an IR of the pattern code.
+ var tilingOpList = new OperatorList();
+ // Merge the available resources, to prevent issues when the patternDict
+ // is missing some /Resources entries (fixes issue6541.pdf).
+ var resourcesArray = [
+ patternDict.get('Resources'),
+ resources
+ ];
+ var patternResources = Dict.merge(this.xref, resourcesArray);
+ return this.getOperatorList(pattern, task, patternResources, tilingOpList).then(function () {
+ // Add the dependencies to the parent operator list so they are
+ // resolved before sub operator list is executed synchronously.
+ operatorList.addDependencies(tilingOpList.dependencies);
+ operatorList.addOp(fn, getTilingPatternIR({
+ fnArray: tilingOpList.fnArray,
+ argsArray: tilingOpList.argsArray
+ }, patternDict, args));
+ });
+ },
+ handleSetFont: function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef, operatorList, task, state) {
+ // TODO(mack): Not needed?
+ var fontName;
+ if (fontArgs) {
+ fontArgs = fontArgs.slice();
+ fontName = fontArgs[0].name;
+ }
+ var self = this;
+ return this.loadFont(fontName, fontRef, this.xref, resources).then(function (translated) {
+ if (!translated.font.isType3Font) {
+ return translated;
+ }
+ return translated.loadType3Data(self, resources, operatorList, task).then(function () {
+ return translated;
+ }, function (reason) {
+ // Error in the font data -- sending unsupported feature notification.
+ self.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
+ return new TranslatedFont('g_font_error', new ErrorFont('Type3 font load error: ' + reason), translated.font);
+ });
+ }).then(function (translated) {
+ state.font = translated.font;
+ translated.send(self.handler);
+ return translated.loadedName;
+ });
+ },
+ handleText: function PartialEvaluator_handleText(chars, state) {
+ var font = state.font;
+ var glyphs = font.charsToGlyphs(chars);
+ var isAddToPathSet = !!(state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
+ if (font.data && (isAddToPathSet || this.options.disableFontFace)) {
+ var buildPath = function (fontChar) {
+ if (!font.renderer.hasBuiltPath(fontChar)) {
+ var path = font.renderer.getPathJs(fontChar);
+ this.handler.send('commonobj', [
+ font.loadedName + '_path_' + fontChar,
+ 'FontPath',
+ path
+ ]);
+ }
+ }.bind(this);
+ for (var i = 0, ii = glyphs.length; i < ii; i++) {
+ var glyph = glyphs[i];
+ buildPath(glyph.fontChar);
+ // If the glyph has an accent we need to build a path for its
+ // fontChar too, otherwise CanvasGraphics_paintChar will fail.
+ var accent = glyph.accent;
+ if (accent && accent.fontChar) {
+ buildPath(accent.fontChar);
+ }
+ }
+ }
+ return glyphs;
+ },
+ setGState: function PartialEvaluator_setGState(resources, gState, operatorList, task, xref, stateManager) {
+ // This array holds the converted/processed state data.
+ var gStateObj = [];
+ var gStateKeys = gState.getKeys();
+ var self = this;
+ var promise = Promise.resolve();
+ for (var i = 0, ii = gStateKeys.length; i < ii; i++) {
+ var key = gStateKeys[i];
+ var value = gState.get(key);
+ switch (key) {
+ case 'Type':
+ break;
+ case 'LW':
+ case 'LC':
+ case 'LJ':
+ case 'ML':
+ case 'D':
+ case 'RI':
+ case 'FL':
+ case 'CA':
+ case 'ca':
+ gStateObj.push([
+ key,
+ value
+ ]);
+ break;
+ case 'Font':
+ promise = promise.then(function () {
+ return self.handleSetFont(resources, null, value[0], operatorList, task, stateManager.state).then(function (loadedName) {
+ operatorList.addDependency(loadedName);
+ gStateObj.push([
+ key,
+ [
+ loadedName,
+ value[1]
+ ]
+ ]);
+ });
+ });
+ break;
+ case 'BM':
+ gStateObj.push([
+ key,
+ value
+ ]);
+ break;
+ case 'SMask':
+ if (isName(value, 'None')) {
+ gStateObj.push([
+ key,
+ false
+ ]);
+ break;
+ }
+ if (isDict(value)) {
+ promise = promise.then(function (dict) {
+ return self.handleSMask(dict, resources, operatorList, task, stateManager);
+ }.bind(this, value));
+ gStateObj.push([
+ key,
+ true
+ ]);
+ } else {
+ warn('Unsupported SMask type');
+ }
+ break;
+ // Only generate info log messages for the following since
+ // they are unlikely to have a big impact on the rendering.
+ case 'OP':
+ case 'op':
+ case 'OPM':
+ case 'BG':
+ case 'BG2':
+ case 'UCR':
+ case 'UCR2':
+ case 'TR':
+ case 'TR2':
+ case 'HT':
+ case 'SM':
+ case 'SA':
+ case 'AIS':
+ case 'TK':
+ // TODO implement these operators.
+ info('graphic state operator ' + key);
+ break;
+ default:
+ info('Unknown graphic state operator ' + key);
+ break;
+ }
+ }
+ return promise.then(function () {
+ if (gStateObj.length > 0) {
+ operatorList.addOp(OPS.setGState, [gStateObj]);
+ }
+ });
+ },
+ loadFont: function PartialEvaluator_loadFont(fontName, font, xref, resources) {
+ function errorFont() {
+ return Promise.resolve(new TranslatedFont('g_font_error', new ErrorFont('Font ' + fontName + ' is not available'), font));
+ }
+ var fontRef;
+ if (font) {
+ // Loading by ref.
+ assert(isRef(font));
+ fontRef = font;
+ } else {
+ // Loading by name.
+ var fontRes = resources.get('Font');
+ if (fontRes) {
+ fontRef = fontRes.getRaw(fontName);
+ } else {
+ warn('fontRes not available');
+ return errorFont();
+ }
+ }
+ if (!fontRef) {
+ warn('fontRef not available');
+ return errorFont();
+ }
+ if (this.fontCache.has(fontRef)) {
+ return this.fontCache.get(fontRef);
+ }
+ font = xref.fetchIfRef(fontRef);
+ if (!isDict(font)) {
+ return errorFont();
+ }
+ // We are holding `font.translated` references just for `fontRef`s that
+ // are not actually `Ref`s, but rather `Dict`s. See explanation below.
+ if (font.translated) {
+ return font.translated;
+ }
+ var fontCapability = createPromiseCapability();
+ var preEvaluatedFont = this.preEvaluateFont(font, xref);
+ var descriptor = preEvaluatedFont.descriptor;
+ var fontRefIsRef = isRef(fontRef), fontID;
+ if (fontRefIsRef) {
+ fontID = fontRef.toString();
+ }
+ if (isDict(descriptor)) {
+ if (!descriptor.fontAliases) {
+ descriptor.fontAliases = Object.create(null);
+ }
+ var fontAliases = descriptor.fontAliases;
+ var hash = preEvaluatedFont.hash;
+ if (fontAliases[hash]) {
+ var aliasFontRef = fontAliases[hash].aliasRef;
+ if (fontRefIsRef && aliasFontRef && this.fontCache.has(aliasFontRef)) {
+ this.fontCache.putAlias(fontRef, aliasFontRef);
+ return this.fontCache.get(fontRef);
+ }
+ } else {
+ fontAliases[hash] = { fontID: Font.getFontID() };
+ }
+ if (fontRefIsRef) {
+ fontAliases[hash].aliasRef = fontRef;
+ }
+ fontID = fontAliases[hash].fontID;
+ }
+ // Workaround for bad PDF generators that reference fonts incorrectly,
+ // where `fontRef` is a `Dict` rather than a `Ref` (fixes bug946506.pdf).
+ // In this case we should not put the font into `this.fontCache` (which is
+ // a `RefSetCache`), since it's not meaningful to use a `Dict` as a key.
+ //
+ // However, if we don't cache the font it's not possible to remove it
+ // when `cleanup` is triggered from the API, which causes issues on
+ // subsequent rendering operations (see issue7403.pdf).
+ // A simple workaround would be to just not hold `font.translated`
+ // references in this case, but this would force us to unnecessarily load
+ // the same fonts over and over.
+ //
+ // Instead, we cheat a bit by attempting to use a modified `fontID` as a
+ // key in `this.fontCache`, to allow the font to be cached.
+ // NOTE: This works because `RefSetCache` calls `toString()` on provided
+ // keys. Also, since `fontRef` is used when getting cached fonts,
+ // we'll not accidentally match fonts cached with the `fontID`.
+ if (fontRefIsRef) {
+ this.fontCache.put(fontRef, fontCapability.promise);
+ } else {
+ if (!fontID) {
+ fontID = (this.uniquePrefix || 'F_') + ++this.idCounters.obj;
+ }
+ this.fontCache.put('id_' + fontID, fontCapability.promise);
+ }
+ assert(fontID, 'The "fontID" must be defined.');
+ // Keep track of each font we translated so the caller can
+ // load them asynchronously before calling display on a page.
+ font.loadedName = 'g_' + this.pdfManager.docId + '_f' + fontID;
+ font.translated = fontCapability.promise;
+ // TODO move promises into translate font
+ var translatedPromise;
+ try {
+ translatedPromise = this.translateFont(preEvaluatedFont, xref);
+ } catch (e) {
+ translatedPromise = Promise.reject(e);
+ }
+ var self = this;
+ translatedPromise.then(function (translatedFont) {
+ if (translatedFont.fontType !== undefined) {
+ var xrefFontStats = xref.stats.fontTypes;
+ xrefFontStats[translatedFont.fontType] = true;
+ }
+ fontCapability.resolve(new TranslatedFont(font.loadedName, translatedFont, font));
+ }, function (reason) {
+ // TODO fontCapability.reject?
+ // Error in the font data -- sending unsupported feature notification.
+ self.handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.font });
+ try {
+ // error, but it's still nice to have font type reported
+ var descriptor = preEvaluatedFont.descriptor;
+ var fontFile3 = descriptor && descriptor.get('FontFile3');
+ var subtype = fontFile3 && fontFile3.get('Subtype');
+ var fontType = getFontType(preEvaluatedFont.type, subtype && subtype.name);
+ var xrefFontStats = xref.stats.fontTypes;
+ xrefFontStats[fontType] = true;
+ } catch (ex) {
+ }
+ fontCapability.resolve(new TranslatedFont(font.loadedName, new ErrorFont(reason instanceof Error ? reason.message : reason), font));
+ });
+ return fontCapability.promise;
+ },
+ buildPath: function PartialEvaluator_buildPath(operatorList, fn, args) {
+ var lastIndex = operatorList.length - 1;
+ if (!args) {
+ args = [];
+ }
+ if (lastIndex < 0 || operatorList.fnArray[lastIndex] !== OPS.constructPath) {
+ operatorList.addOp(OPS.constructPath, [
+ [fn],
+ args
+ ]);
+ } else {
+ var opArgs = operatorList.argsArray[lastIndex];
+ opArgs[0].push(fn);
+ Array.prototype.push.apply(opArgs[1], args);
+ }
+ },
+ handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args, cs, patterns, resources, task, xref) {
+ // compile tiling patterns
+ var patternName = args[args.length - 1];
+ // SCN/scn applies patterns along with normal colors
+ var pattern;
+ if (isName(patternName) && (pattern = patterns.get(patternName.name))) {
+ var dict = isStream(pattern) ? pattern.dict : pattern;
+ var typeNum = dict.get('PatternType');
+ if (typeNum === TILING_PATTERN) {
+ var color = cs.base ? cs.base.getRgb(args, 0) : null;
+ return this.handleTilingType(fn, color, resources, pattern, dict, operatorList, task);
+ } else if (typeNum === SHADING_PATTERN) {
+ var shading = dict.get('Shading');
+ var matrix = dict.getArray('Matrix');
+ pattern = Pattern.parseShading(shading, matrix, xref, resources, this.handler);
+ operatorList.addOp(fn, pattern.getIR());
+ return Promise.resolve();
+ } else {
+ return Promise.reject('Unknown PatternType: ' + typeNum);
+ }
+ }
+ // TODO shall we fail here?
+ operatorList.addOp(fn, args);
+ return Promise.resolve();
+ },
+ getOperatorList: function PartialEvaluator_getOperatorList(stream, task, resources, operatorList, initialState) {
+ var self = this;
+ var xref = this.xref;
+ var imageCache = Object.create(null);
+ assert(operatorList);
+ resources = resources || Dict.empty;
+ var xobjs = resources.get('XObject') || Dict.empty;
+ var patterns = resources.get('Pattern') || Dict.empty;
+ var stateManager = new StateManager(initialState || new EvalState());
+ var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
+ var timeSlotManager = new TimeSlotManager();
+ return new Promise(function promiseBody(resolve, reject) {
+ var next = function (promise) {
+ promise.then(function () {
+ try {
+ promiseBody(resolve, reject);
+ } catch (ex) {
+ reject(ex);
+ }
+ }, reject);
+ };
+ task.ensureNotTerminated();
+ timeSlotManager.reset();
+ var stop, operation = {}, i, ii, cs;
+ while (!(stop = timeSlotManager.check())) {
+ // The arguments parsed by read() are used beyond this loop, so we
+ // cannot reuse the same array on each iteration. Therefore we pass
+ // in |null| as the initial value (see the comment on
+ // EvaluatorPreprocessor_read() for why).
+ operation.args = null;
+ if (!preprocessor.read(operation)) {
+ break;
+ }
+ var args = operation.args;
+ var fn = operation.fn;
+ switch (fn | 0) {
+ case OPS.paintXObject:
+ if (args[0].code) {
+ break;
+ }
+ // eagerly compile XForm objects
+ var name = args[0].name;
+ if (!name) {
+ warn('XObject must be referred to by name.');
+ continue;
+ }
+ if (imageCache[name] !== undefined) {
+ operatorList.addOp(imageCache[name].fn, imageCache[name].args);
+ args = null;
+ continue;
+ }
+ var xobj = xobjs.get(name);
+ if (xobj) {
+ assert(isStream(xobj), 'XObject should be a stream');
+ var type = xobj.dict.get('Subtype');
+ assert(isName(type), 'XObject should have a Name subtype');
+ if (type.name === 'Form') {
+ stateManager.save();
+ next(self.buildFormXObject(resources, xobj, null, operatorList, task, stateManager.state.clone()).then(function () {
+ stateManager.restore();
+ }));
+ return;
+ } else if (type.name === 'Image') {
+ self.buildPaintImageXObject(resources, xobj, false, operatorList, name, imageCache);
+ args = null;
+ continue;
+ } else if (type.name === 'PS') {
+ // PostScript XObjects are unused when viewing documents.
+ // See section 4.7.1 of Adobe's PDF reference.
+ info('Ignored XObject subtype PS');
+ continue;
+ } else {
+ error('Unhandled XObject subtype ' + type.name);
+ }
+ }
+ break;
+ case OPS.setFont:
+ var fontSize = args[1];
+ // eagerly collect all fonts
+ next(self.handleSetFont(resources, args, null, operatorList, task, stateManager.state).then(function (loadedName) {
+ operatorList.addDependency(loadedName);
+ operatorList.addOp(OPS.setFont, [
+ loadedName,
+ fontSize
+ ]);
+ }));
+ return;
+ case OPS.endInlineImage:
+ var cacheKey = args[0].cacheKey;
+ if (cacheKey) {
+ var cacheEntry = imageCache[cacheKey];
+ if (cacheEntry !== undefined) {
+ operatorList.addOp(cacheEntry.fn, cacheEntry.args);
+ args = null;
+ continue;
+ }
+ }
+ self.buildPaintImageXObject(resources, args[0], true, operatorList, cacheKey, imageCache);
+ args = null;
+ continue;
+ case OPS.showText:
+ args[0] = self.handleText(args[0], stateManager.state);
+ break;
+ case OPS.showSpacedText:
+ var arr = args[0];
+ var combinedGlyphs = [];
+ var arrLength = arr.length;
+ var state = stateManager.state;
+ for (i = 0; i < arrLength; ++i) {
+ var arrItem = arr[i];
+ if (isString(arrItem)) {
+ Array.prototype.push.apply(combinedGlyphs, self.handleText(arrItem, state));
+ } else if (isNum(arrItem)) {
+ combinedGlyphs.push(arrItem);
+ }
+ }
+ args[0] = combinedGlyphs;
+ fn = OPS.showText;
+ break;
+ case OPS.nextLineShowText:
+ operatorList.addOp(OPS.nextLine);
+ args[0] = self.handleText(args[0], stateManager.state);
+ fn = OPS.showText;
+ break;
+ case OPS.nextLineSetSpacingShowText:
+ operatorList.addOp(OPS.nextLine);
+ operatorList.addOp(OPS.setWordSpacing, [args.shift()]);
+ operatorList.addOp(OPS.setCharSpacing, [args.shift()]);
+ args[0] = self.handleText(args[0], stateManager.state);
+ fn = OPS.showText;
+ break;
+ case OPS.setTextRenderingMode:
+ stateManager.state.textRenderingMode = args[0];
+ break;
+ case OPS.setFillColorSpace:
+ stateManager.state.fillColorSpace = ColorSpace.parse(args[0], xref, resources);
+ continue;
+ case OPS.setStrokeColorSpace:
+ stateManager.state.strokeColorSpace = ColorSpace.parse(args[0], xref, resources);
+ continue;
+ case OPS.setFillColor:
+ cs = stateManager.state.fillColorSpace;
+ args = cs.getRgb(args, 0);
+ fn = OPS.setFillRGBColor;
+ break;
+ case OPS.setStrokeColor:
+ cs = stateManager.state.strokeColorSpace;
+ args = cs.getRgb(args, 0);
+ fn = OPS.setStrokeRGBColor;
+ break;
+ case OPS.setFillGray:
+ stateManager.state.fillColorSpace = ColorSpace.singletons.gray;
+ args = ColorSpace.singletons.gray.getRgb(args, 0);
+ fn = OPS.setFillRGBColor;
+ break;
+ case OPS.setStrokeGray:
+ stateManager.state.strokeColorSpace = ColorSpace.singletons.gray;
+ args = ColorSpace.singletons.gray.getRgb(args, 0);
+ fn = OPS.setStrokeRGBColor;
+ break;
+ case OPS.setFillCMYKColor:
+ stateManager.state.fillColorSpace = ColorSpace.singletons.cmyk;
+ args = ColorSpace.singletons.cmyk.getRgb(args, 0);
+ fn = OPS.setFillRGBColor;
+ break;
+ case OPS.setStrokeCMYKColor:
+ stateManager.state.strokeColorSpace = ColorSpace.singletons.cmyk;
+ args = ColorSpace.singletons.cmyk.getRgb(args, 0);
+ fn = OPS.setStrokeRGBColor;
+ break;
+ case OPS.setFillRGBColor:
+ stateManager.state.fillColorSpace = ColorSpace.singletons.rgb;
+ args = ColorSpace.singletons.rgb.getRgb(args, 0);
+ break;
+ case OPS.setStrokeRGBColor:
+ stateManager.state.strokeColorSpace = ColorSpace.singletons.rgb;
+ args = ColorSpace.singletons.rgb.getRgb(args, 0);
+ break;
+ case OPS.setFillColorN:
+ cs = stateManager.state.fillColorSpace;
+ if (cs.name === 'Pattern') {
+ next(self.handleColorN(operatorList, OPS.setFillColorN, args, cs, patterns, resources, task, xref));
+ return;
+ }
+ args = cs.getRgb(args, 0);
+ fn = OPS.setFillRGBColor;
+ break;
+ case OPS.setStrokeColorN:
+ cs = stateManager.state.strokeColorSpace;
+ if (cs.name === 'Pattern') {
+ next(self.handleColorN(operatorList, OPS.setStrokeColorN, args, cs, patterns, resources, task, xref));
+ return;
+ }
+ args = cs.getRgb(args, 0);
+ fn = OPS.setStrokeRGBColor;
+ break;
+ case OPS.shadingFill:
+ var shadingRes = resources.get('Shading');
+ if (!shadingRes) {
+ error('No shading resource found');
+ }
+ var shading = shadingRes.get(args[0].name);
+ if (!shading) {
+ error('No shading object found');
+ }
+ var shadingFill = Pattern.parseShading(shading, null, xref, resources, self.handler);
+ var patternIR = shadingFill.getIR();
+ args = [patternIR];
+ fn = OPS.shadingFill;
+ break;
+ case OPS.setGState:
+ var dictName = args[0];
+ var extGState = resources.get('ExtGState');
+ if (!isDict(extGState) || !extGState.has(dictName.name)) {
+ break;
+ }
+ var gState = extGState.get(dictName.name);
+ next(self.setGState(resources, gState, operatorList, task, xref, stateManager));
+ return;
+ case OPS.moveTo:
+ case OPS.lineTo:
+ case OPS.curveTo:
+ case OPS.curveTo2:
+ case OPS.curveTo3:
+ case OPS.closePath:
+ self.buildPath(operatorList, fn, args);
+ continue;
+ case OPS.rectangle:
+ self.buildPath(operatorList, fn, args);
+ continue;
+ case OPS.markPoint:
+ case OPS.markPointProps:
+ case OPS.beginMarkedContent:
+ case OPS.beginMarkedContentProps:
+ case OPS.endMarkedContent:
+ case OPS.beginCompat:
+ case OPS.endCompat:
+ // Ignore operators where the corresponding handlers are known to
+ // be no-op in CanvasGraphics (display/canvas.js). This prevents
+ // serialization errors and is also a bit more efficient.
+ // We could also try to serialize all objects in a general way,
+ // e.g. as done in https://github.com/mozilla/pdf.js/pull/6266,
+ // but doing so is meaningless without knowing the semantics.
+ continue;
+ default:
+ // Note: Ignore the operator if it has `Dict` arguments, since
+ // those are non-serializable, otherwise postMessage will throw
+ // "An object could not be cloned.".
+ if (args !== null) {
+ for (i = 0, ii = args.length; i < ii; i++) {
+ if (args[i] instanceof Dict) {
+ break;
+ }
+ }
+ if (i < ii) {
+ warn('getOperatorList - ignoring operator: ' + fn);
+ continue;
+ }
+ }
+ }
+ operatorList.addOp(fn, args);
+ }
+ if (stop) {
+ next(deferred);
+ return;
+ }
+ // Some PDFs don't close all restores inside object/form.
+ // Closing those for them.
+ for (i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
+ operatorList.addOp(OPS.restore, []);
+ }
+ resolve();
+ });
+ },
+ getTextContent: function PartialEvaluator_getTextContent(stream, task, resources, stateManager, normalizeWhitespace, combineTextItems) {
+ stateManager = stateManager || new StateManager(new TextState());
+ var WhitespaceRegexp = /\s/g;
+ var textContent = {
+ items: [],
+ styles: Object.create(null)
+ };
+ var textContentItem = {
+ initialized: false,
+ str: [],
+ width: 0,
+ height: 0,
+ vertical: false,
+ lastAdvanceWidth: 0,
+ lastAdvanceHeight: 0,
+ textAdvanceScale: 0,
+ spaceWidth: 0,
+ fakeSpaceMin: Infinity,
+ fakeMultiSpaceMin: Infinity,
+ fakeMultiSpaceMax: -0,
+ textRunBreakAllowed: false,
+ transform: null,
+ fontName: null
+ };
+ var SPACE_FACTOR = 0.3;
+ var MULTI_SPACE_FACTOR = 1.5;
+ var MULTI_SPACE_FACTOR_MAX = 4;
+ var self = this;
+ var xref = this.xref;
+ resources = xref.fetchIfRef(resources) || Dict.empty;
+ // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
+ var xobjs = null;
+ var xobjsCache = Object.create(null);
+ var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
+ var textState;
+ function ensureTextContentItem() {
+ if (textContentItem.initialized) {
+ return textContentItem;
+ }
+ var font = textState.font;
+ if (!(font.loadedName in textContent.styles)) {
+ textContent.styles[font.loadedName] = {
+ fontFamily: font.fallbackName,
+ ascent: font.ascent,
+ descent: font.descent,
+ vertical: font.vertical
+ };
+ }
+ textContentItem.fontName = font.loadedName;
+ // 9.4.4 Text Space Details
+ var tsm = [
+ textState.fontSize * textState.textHScale,
+ 0,
+ 0,
+ textState.fontSize,
+ 0,
+ textState.textRise
+ ];
+ if (font.isType3Font && textState.fontMatrix !== FONT_IDENTITY_MATRIX && textState.fontSize === 1) {
+ var glyphHeight = font.bbox[3] - font.bbox[1];
+ if (glyphHeight > 0) {
+ glyphHeight = glyphHeight * textState.fontMatrix[3];
+ tsm[3] *= glyphHeight;
+ }
+ }
+ var trm = Util.transform(textState.ctm, Util.transform(textState.textMatrix, tsm));
+ textContentItem.transform = trm;
+ if (!font.vertical) {
+ textContentItem.width = 0;
+ textContentItem.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]);
+ textContentItem.vertical = false;
+ } else {
+ textContentItem.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]);
+ textContentItem.height = 0;
+ textContentItem.vertical = true;
+ }
+ var a = textState.textLineMatrix[0];
+ var b = textState.textLineMatrix[1];
+ var scaleLineX = Math.sqrt(a * a + b * b);
+ a = textState.ctm[0];
+ b = textState.ctm[1];
+ var scaleCtmX = Math.sqrt(a * a + b * b);
+ textContentItem.textAdvanceScale = scaleCtmX * scaleLineX;
+ textContentItem.lastAdvanceWidth = 0;
+ textContentItem.lastAdvanceHeight = 0;
+ var spaceWidth = font.spaceWidth / 1000 * textState.fontSize;
+ if (spaceWidth) {
+ textContentItem.spaceWidth = spaceWidth;
+ textContentItem.fakeSpaceMin = spaceWidth * SPACE_FACTOR;
+ textContentItem.fakeMultiSpaceMin = spaceWidth * MULTI_SPACE_FACTOR;
+ textContentItem.fakeMultiSpaceMax = spaceWidth * MULTI_SPACE_FACTOR_MAX;
+ // It's okay for monospace fonts to fake as much space as needed.
+ textContentItem.textRunBreakAllowed = !font.isMonospace;
+ } else {
+ textContentItem.spaceWidth = 0;
+ textContentItem.fakeSpaceMin = Infinity;
+ textContentItem.fakeMultiSpaceMin = Infinity;
+ textContentItem.fakeMultiSpaceMax = 0;
+ textContentItem.textRunBreakAllowed = false;
+ }
+ textContentItem.initialized = true;
+ return textContentItem;
+ }
+ function replaceWhitespace(str) {
+ // Replaces all whitespaces with standard spaces (0x20), to avoid
+ // alignment issues between the textLayer and the canvas if the text
+ // contains e.g. tabs (fixes issue6612.pdf).
+ var i = 0, ii = str.length, code;
+ while (i < ii && (code = str.charCodeAt(i)) >= 0x20 && code <= 0x7F) {
+ i++;
+ }
+ return i < ii ? str.replace(WhitespaceRegexp, ' ') : str;
+ }
+ function runBidiTransform(textChunk) {
+ var str = textChunk.str.join('');
+ var bidiResult = bidi(str, -1, textChunk.vertical);
+ return {
+ str: normalizeWhitespace ? replaceWhitespace(bidiResult.str) : bidiResult.str,
+ dir: bidiResult.dir,
+ width: textChunk.width,
+ height: textChunk.height,
+ transform: textChunk.transform,
+ fontName: textChunk.fontName
+ };
+ }
+ function handleSetFont(fontName, fontRef) {
+ return self.loadFont(fontName, fontRef, xref, resources).then(function (translated) {
+ textState.font = translated.font;
+ textState.fontMatrix = translated.font.fontMatrix || FONT_IDENTITY_MATRIX;
+ });
+ }
+ function buildTextContentItem(chars) {
+ var font = textState.font;
+ var textChunk = ensureTextContentItem();
+ var width = 0;
+ var height = 0;
+ var glyphs = font.charsToGlyphs(chars);
+ var defaultVMetrics = font.defaultVMetrics;
+ for (var i = 0; i < glyphs.length; i++) {
+ var glyph = glyphs[i];
+ var vMetricX = null;
+ var vMetricY = null;
+ var glyphWidth = null;
+ if (font.vertical) {
+ if (glyph.vmetric) {
+ glyphWidth = glyph.vmetric[0];
+ vMetricX = glyph.vmetric[1];
+ vMetricY = glyph.vmetric[2];
+ } else {
+ glyphWidth = glyph.width;
+ vMetricX = glyph.width * 0.5;
+ vMetricY = defaultVMetrics[2];
+ }
+ } else {
+ glyphWidth = glyph.width;
+ }
+ var glyphUnicode = glyph.unicode;
+ var NormalizedUnicodes = getNormalizedUnicodes();
+ if (NormalizedUnicodes[glyphUnicode] !== undefined) {
+ glyphUnicode = NormalizedUnicodes[glyphUnicode];
+ }
+ glyphUnicode = reverseIfRtl(glyphUnicode);
+ // The following will calculate the x and y of the individual glyphs.
+ // if (font.vertical) {
+ // tsm[4] -= vMetricX * Math.abs(textState.fontSize) *
+ // textState.fontMatrix[0];
+ // tsm[5] -= vMetricY * textState.fontSize *
+ // textState.fontMatrix[0];
+ // }
+ // var trm = Util.transform(textState.textMatrix, tsm);
+ // var pt = Util.applyTransform([trm[4], trm[5]], textState.ctm);
+ // var x = pt[0];
+ // var y = pt[1];
+ var charSpacing = textState.charSpacing;
+ if (glyph.isSpace) {
+ var wordSpacing = textState.wordSpacing;
+ charSpacing += wordSpacing;
+ if (wordSpacing > 0) {
+ addFakeSpaces(wordSpacing, textChunk.str);
+ }
+ }
+ var tx = 0;
+ var ty = 0;
+ if (!font.vertical) {
+ var w0 = glyphWidth * textState.fontMatrix[0];
+ tx = (w0 * textState.fontSize + charSpacing) * textState.textHScale;
+ width += tx;
+ } else {
+ var w1 = glyphWidth * textState.fontMatrix[0];
+ ty = w1 * textState.fontSize + charSpacing;
+ height += ty;
+ }
+ textState.translateTextMatrix(tx, ty);
+ textChunk.str.push(glyphUnicode);
+ }
+ if (!font.vertical) {
+ textChunk.lastAdvanceWidth = width;
+ textChunk.width += width * textChunk.textAdvanceScale;
+ } else {
+ textChunk.lastAdvanceHeight = height;
+ textChunk.height += Math.abs(height * textChunk.textAdvanceScale);
+ }
+ return textChunk;
+ }
+ function addFakeSpaces(width, strBuf) {
+ if (width < textContentItem.fakeSpaceMin) {
+ return;
+ }
+ if (width < textContentItem.fakeMultiSpaceMin) {
+ strBuf.push(' ');
+ return;
+ }
+ var fakeSpaces = Math.round(width / textContentItem.spaceWidth);
+ while (fakeSpaces-- > 0) {
+ strBuf.push(' ');
+ }
+ }
+ function flushTextContentItem() {
+ if (!textContentItem.initialized) {
+ return;
+ }
+ textContent.items.push(runBidiTransform(textContentItem));
+ textContentItem.initialized = false;
+ textContentItem.str.length = 0;
+ }
+ var timeSlotManager = new TimeSlotManager();
+ return new Promise(function promiseBody(resolve, reject) {
+ var next = function (promise) {
+ promise.then(function () {
+ try {
+ promiseBody(resolve, reject);
+ } catch (ex) {
+ reject(ex);
+ }
+ }, reject);
+ };
+ task.ensureNotTerminated();
+ timeSlotManager.reset();
+ var stop, operation = {}, args = [];
+ while (!(stop = timeSlotManager.check())) {
+ // The arguments parsed by read() are not used beyond this loop, so
+ // we can reuse the same array on every iteration, thus avoiding
+ // unnecessary allocations.
+ args.length = 0;
+ operation.args = args;
+ if (!preprocessor.read(operation)) {
+ break;
+ }
+ textState = stateManager.state;
+ var fn = operation.fn;
+ args = operation.args;
+ var advance, diff;
+ switch (fn | 0) {
+ case OPS.setFont:
+ // Optimization to ignore multiple identical Tf commands.
+ var fontNameArg = args[0].name, fontSizeArg = args[1];
+ if (textState.font && fontNameArg === textState.fontName && fontSizeArg === textState.fontSize) {
+ break;
+ }
+ flushTextContentItem();
+ textState.fontName = fontNameArg;
+ textState.fontSize = fontSizeArg;
+ next(handleSetFont(fontNameArg, null));
+ return;
+ case OPS.setTextRise:
+ flushTextContentItem();
+ textState.textRise = args[0];
+ break;
+ case OPS.setHScale:
+ flushTextContentItem();
+ textState.textHScale = args[0] / 100;
+ break;
+ case OPS.setLeading:
+ flushTextContentItem();
+ textState.leading = args[0];
+ break;
+ case OPS.moveText:
+ // Optimization to treat same line movement as advance
+ var isSameTextLine = !textState.font ? false : (textState.font.vertical ? args[0] : args[1]) === 0;
+ advance = args[0] - args[1];
+ if (combineTextItems && isSameTextLine && textContentItem.initialized && advance > 0 && advance <= textContentItem.fakeMultiSpaceMax) {
+ textState.translateTextLineMatrix(args[0], args[1]);
+ textContentItem.width += args[0] - textContentItem.lastAdvanceWidth;
+ textContentItem.height += args[1] - textContentItem.lastAdvanceHeight;
+ diff = args[0] - textContentItem.lastAdvanceWidth - (args[1] - textContentItem.lastAdvanceHeight);
+ addFakeSpaces(diff, textContentItem.str);
+ break;
+ }
+ flushTextContentItem();
+ textState.translateTextLineMatrix(args[0], args[1]);
+ textState.textMatrix = textState.textLineMatrix.slice();
+ break;
+ case OPS.setLeadingMoveText:
+ flushTextContentItem();
+ textState.leading = -args[1];
+ textState.translateTextLineMatrix(args[0], args[1]);
+ textState.textMatrix = textState.textLineMatrix.slice();
+ break;
+ case OPS.nextLine:
+ flushTextContentItem();
+ textState.carriageReturn();
+ break;
+ case OPS.setTextMatrix:
+ // Optimization to treat same line movement as advance.
+ advance = textState.calcTextLineMatrixAdvance(args[0], args[1], args[2], args[3], args[4], args[5]);
+ if (combineTextItems && advance !== null && textContentItem.initialized && advance.value > 0 && advance.value <= textContentItem.fakeMultiSpaceMax) {
+ textState.translateTextLineMatrix(advance.width, advance.height);
+ textContentItem.width += advance.width - textContentItem.lastAdvanceWidth;
+ textContentItem.height += advance.height - textContentItem.lastAdvanceHeight;
+ diff = advance.width - textContentItem.lastAdvanceWidth - (advance.height - textContentItem.lastAdvanceHeight);
+ addFakeSpaces(diff, textContentItem.str);
+ break;
+ }
+ flushTextContentItem();
+ textState.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
+ textState.setTextLineMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
+ break;
+ case OPS.setCharSpacing:
+ textState.charSpacing = args[0];
+ break;
+ case OPS.setWordSpacing:
+ textState.wordSpacing = args[0];
+ break;
+ case OPS.beginText:
+ flushTextContentItem();
+ textState.textMatrix = IDENTITY_MATRIX.slice();
+ textState.textLineMatrix = IDENTITY_MATRIX.slice();
+ break;
+ case OPS.showSpacedText:
+ var items = args[0];
+ var offset;
+ for (var j = 0, jj = items.length; j < jj; j++) {
+ if (typeof items[j] === 'string') {
+ buildTextContentItem(items[j]);
+ } else if (isNum(items[j])) {
+ ensureTextContentItem();
+ // PDF Specification 5.3.2 states:
+ // The number is expressed in thousandths of a unit of text
+ // space.
+ // This amount is subtracted from the current horizontal or
+ // vertical coordinate, depending on the writing mode.
+ // In the default coordinate system, a positive adjustment
+ // has the effect of moving the next glyph painted either to
+ // the left or down by the given amount.
+ advance = items[j] * textState.fontSize / 1000;
+ var breakTextRun = false;
+ if (textState.font.vertical) {
+ offset = advance * (textState.textHScale * textState.textMatrix[2] + textState.textMatrix[3]);
+ textState.translateTextMatrix(0, advance);
+ breakTextRun = textContentItem.textRunBreakAllowed && advance > textContentItem.fakeMultiSpaceMax;
+ if (!breakTextRun) {
+ // Value needs to be added to height to paint down.
+ textContentItem.height += offset;
+ }
+ } else {
+ advance = -advance;
+ offset = advance * (textState.textHScale * textState.textMatrix[0] + textState.textMatrix[1]);
+ textState.translateTextMatrix(advance, 0);
+ breakTextRun = textContentItem.textRunBreakAllowed && advance > textContentItem.fakeMultiSpaceMax;
+ if (!breakTextRun) {
+ // Value needs to be subtracted from width to paint left.
+ textContentItem.width += offset;
+ }
+ }
+ if (breakTextRun) {
+ flushTextContentItem();
+ } else if (advance > 0) {
+ addFakeSpaces(advance, textContentItem.str);
+ }
+ }
+ }
+ break;
+ case OPS.showText:
+ buildTextContentItem(args[0]);
+ break;
+ case OPS.nextLineShowText:
+ flushTextContentItem();
+ textState.carriageReturn();
+ buildTextContentItem(args[0]);
+ break;
+ case OPS.nextLineSetSpacingShowText:
+ flushTextContentItem();
+ textState.wordSpacing = args[0];
+ textState.charSpacing = args[1];
+ textState.carriageReturn();
+ buildTextContentItem(args[2]);
+ break;
+ case OPS.paintXObject:
+ flushTextContentItem();
+ if (args[0].code) {
+ break;
+ }
+ if (!xobjs) {
+ xobjs = resources.get('XObject') || Dict.empty;
+ }
+ var name = args[0].name;
+ if (xobjsCache.key === name) {
+ if (xobjsCache.texts) {
+ Util.appendToArray(textContent.items, xobjsCache.texts.items);
+ Util.extendObj(textContent.styles, xobjsCache.texts.styles);
+ }
+ break;
+ }
+ var xobj = xobjs.get(name);
+ if (!xobj) {
+ break;
+ }
+ assert(isStream(xobj), 'XObject should be a stream');
+ var type = xobj.dict.get('Subtype');
+ assert(isName(type), 'XObject should have a Name subtype');
+ if ('Form' !== type.name) {
+ xobjsCache.key = name;
+ xobjsCache.texts = null;
+ break;
+ }
+ stateManager.save();
+ var matrix = xobj.dict.getArray('Matrix');
+ if (isArray(matrix) && matrix.length === 6) {
+ stateManager.transform(matrix);
+ }
+ next(self.getTextContent(xobj, task, xobj.dict.get('Resources') || resources, stateManager, normalizeWhitespace, combineTextItems).then(function (formTextContent) {
+ Util.appendToArray(textContent.items, formTextContent.items);
+ Util.extendObj(textContent.styles, formTextContent.styles);
+ stateManager.restore();
+ xobjsCache.key = name;
+ xobjsCache.texts = formTextContent;
+ }));
+ return;
+ case OPS.setGState:
+ flushTextContentItem();
+ var dictName = args[0];
+ var extGState = resources.get('ExtGState');
+ if (!isDict(extGState) || !isName(dictName)) {
+ break;
+ }
+ var gState = extGState.get(dictName.name);
+ if (!isDict(gState)) {
+ break;
+ }
+ var gStateFont = gState.get('Font');
+ if (gStateFont) {
+ textState.fontName = null;
+ textState.fontSize = gStateFont[1];
+ next(handleSetFont(null, gStateFont[0]));
+ return;
+ }
+ break;
+ }
+ }
+ // switch
+ // while
+ if (stop) {
+ next(deferred);
+ return;
+ }
+ flushTextContentItem();
+ resolve(textContent);
+ });
+ },
+ extractDataStructures: function PartialEvaluator_extractDataStructures(dict, baseDict, xref, properties) {
+ // 9.10.2
+ var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
+ var toUnicodePromise = toUnicode ? this.readToUnicode(toUnicode) : Promise.resolve(undefined);
+ if (properties.composite) {
+ // CIDSystemInfo helps to match CID to glyphs
+ var cidSystemInfo = dict.get('CIDSystemInfo');
+ if (isDict(cidSystemInfo)) {
+ properties.cidSystemInfo = {
+ registry: cidSystemInfo.get('Registry'),
+ ordering: cidSystemInfo.get('Ordering'),
+ supplement: cidSystemInfo.get('Supplement')
+ };
+ }
+ var cidToGidMap = dict.get('CIDToGIDMap');
+ if (isStream(cidToGidMap)) {
+ properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
+ }
+ }
+ // Based on 9.6.6 of the spec the encoding can come from multiple places
+ // and depends on the font type. The base encoding and differences are
+ // read here, but the encoding that is actually used is chosen during
+ // glyph mapping in the font.
+ // TODO: Loading the built in encoding in the font would allow the
+ // differences to be merged in here not require us to hold on to it.
+ var differences = [];
+ var baseEncodingName = null;
+ var encoding;
+ if (dict.has('Encoding')) {
+ encoding = dict.get('Encoding');
+ if (isDict(encoding)) {
+ baseEncodingName = encoding.get('BaseEncoding');
+ baseEncodingName = isName(baseEncodingName) ? baseEncodingName.name : null;
+ // Load the differences between the base and original
+ if (encoding.has('Differences')) {
+ var diffEncoding = encoding.get('Differences');
+ var index = 0;
+ for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
+ var data = xref.fetchIfRef(diffEncoding[j]);
+ if (isNum(data)) {
+ index = data;
+ } else if (isName(data)) {
+ differences[index++] = data.name;
+ } else {
+ error('Invalid entry in \'Differences\' array: ' + data);
+ }
+ }
+ }
+ } else if (isName(encoding)) {
+ baseEncodingName = encoding.name;
+ } else {
+ error('Encoding is not a Name nor a Dict');
+ }
+ // According to table 114 if the encoding is a named encoding it must be
+ // one of these predefined encodings.
+ if (baseEncodingName !== 'MacRomanEncoding' && baseEncodingName !== 'MacExpertEncoding' && baseEncodingName !== 'WinAnsiEncoding') {
+ baseEncodingName = null;
+ }
+ }
+ if (baseEncodingName) {
+ properties.defaultEncoding = getEncoding(baseEncodingName).slice();
+ } else {
+ encoding = properties.type === 'TrueType' ? WinAnsiEncoding : StandardEncoding;
+ // The Symbolic attribute can be misused for regular fonts
+ // Heuristic: we have to check if the font is a standard one also
+ if (!!(properties.flags & FontFlags.Symbolic)) {
+ encoding = MacRomanEncoding;
+ if (!properties.file) {
+ if (/Symbol/i.test(properties.name)) {
+ encoding = SymbolSetEncoding;
+ } else if (/Dingbats/i.test(properties.name)) {
+ encoding = ZapfDingbatsEncoding;
+ }
+ }
+ }
+ properties.defaultEncoding = encoding;
+ }
+ properties.differences = differences;
+ properties.baseEncodingName = baseEncodingName;
+ properties.hasEncoding = !!baseEncodingName || differences.length > 0;
+ properties.dict = dict;
+ return toUnicodePromise.then(function (toUnicode) {
+ properties.toUnicode = toUnicode;
+ return this.buildToUnicode(properties);
+ }.bind(this)).then(function (toUnicode) {
+ properties.toUnicode = toUnicode;
+ return properties;
+ });
+ },
+ /**
+ * Builds a char code to unicode map based on section 9.10 of the spec.
+ * @param {Object} properties Font properties object.
+ * @return {Promise} A Promise that is resolved with a
+ * {ToUnicodeMap|IdentityToUnicodeMap} object.
+ */
+ buildToUnicode: function PartialEvaluator_buildToUnicode(properties) {
+ properties.hasIncludedToUnicodeMap = !!properties.toUnicode && properties.toUnicode.length > 0;
+ // Section 9.10.2 Mapping Character Codes to Unicode Values
+ if (properties.hasIncludedToUnicodeMap) {
+ return Promise.resolve(properties.toUnicode);
+ }
+ // According to the spec if the font is a simple font we should only map
+ // to unicode if the base encoding is MacRoman, MacExpert, or WinAnsi or
+ // the differences array only contains adobe standard or symbol set names,
+ // in pratice it seems better to always try to create a toUnicode
+ // map based of the default encoding.
+ var toUnicode, charcode, glyphName;
+ if (!properties.composite)
+ /* is simple font */
+ {
+ toUnicode = [];
+ var encoding = properties.defaultEncoding.slice();
+ var baseEncodingName = properties.baseEncodingName;
+ // Merge in the differences array.
+ var differences = properties.differences;
+ for (charcode in differences) {
+ glyphName = differences[charcode];
+ if (glyphName === '.notdef') {
+ // Skip .notdef to prevent rendering errors, e.g. boxes appearing
+ // where there should be spaces (fixes issue5256.pdf).
+ continue;
+ }
+ encoding[charcode] = glyphName;
+ }
+ var glyphsUnicodeMap = getGlyphsUnicode();
+ for (charcode in encoding) {
+ // a) Map the character code to a character name.
+ glyphName = encoding[charcode];
+ // b) Look up the character name in the Adobe Glyph List (see the
+ // Bibliography) to obtain the corresponding Unicode value.
+ if (glyphName === '') {
+ continue;
+ } else if (glyphsUnicodeMap[glyphName] === undefined) {
+ // (undocumented) c) Few heuristics to recognize unknown glyphs
+ // NOTE: Adobe Reader does not do this step, but OSX Preview does
+ var code = 0;
+ switch (glyphName[0]) {
+ case 'G':
+ // Gxx glyph
+ if (glyphName.length === 3) {
+ code = parseInt(glyphName.substr(1), 16);
+ }
+ break;
+ case 'g':
+ // g00xx glyph
+ if (glyphName.length === 5) {
+ code = parseInt(glyphName.substr(1), 16);
+ }
+ break;
+ case 'C':
+ // Cddd glyph
+ case 'c':
+ // cddd glyph
+ if (glyphName.length >= 3) {
+ code = +glyphName.substr(1);
+ }
+ break;
+ default:
+ // 'uniXXXX'/'uXXXX{XX}' glyphs
+ var unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
+ if (unicode !== -1) {
+ code = unicode;
+ }
+ }
+ if (code) {
+ // If |baseEncodingName| is one the predefined encodings,
+ // and |code| equals |charcode|, using the glyph defined in the
+ // baseEncoding seems to yield a better |toUnicode| mapping
+ // (fixes issue 5070).
+ if (baseEncodingName && code === +charcode) {
+ var baseEncoding = getEncoding(baseEncodingName);
+ if (baseEncoding && (glyphName = baseEncoding[charcode])) {
+ toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]);
+ continue;
+ }
+ }
+ toUnicode[charcode] = String.fromCharCode(code);
+ }
+ continue;
+ }
+ toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]);
+ }
+ return Promise.resolve(new ToUnicodeMap(toUnicode));
+ }
+ // If the font is a composite font that uses one of the predefined CMaps
+ // listed in Table 118 (except Identity–H and Identity–V) or whose
+ // descendant CIDFont uses the Adobe-GB1, Adobe-CNS1, Adobe-Japan1, or
+ // Adobe-Korea1 character collection:
+ if (properties.composite && (properties.cMap.builtInCMap && !(properties.cMap instanceof IdentityCMap) || properties.cidSystemInfo.registry === 'Adobe' && (properties.cidSystemInfo.ordering === 'GB1' || properties.cidSystemInfo.ordering === 'CNS1' || properties.cidSystemInfo.ordering === 'Japan1' || properties.cidSystemInfo.ordering === 'Korea1'))) {
+ // Then:
+ // a) Map the character code to a character identifier (CID) according
+ // to the font’s CMap.
+ // b) Obtain the registry and ordering of the character collection used
+ // by the font’s CMap (for example, Adobe and Japan1) from its
+ // CIDSystemInfo dictionary.
+ var registry = properties.cidSystemInfo.registry;
+ var ordering = properties.cidSystemInfo.ordering;
+ // c) Construct a second CMap name by concatenating the registry and
+ // ordering obtained in step (b) in the format registry–ordering–UCS2
+ // (for example, Adobe–Japan1–UCS2).
+ var ucs2CMapName = Name.get(registry + '-' + ordering + '-UCS2');
+ // d) Obtain the CMap with the name constructed in step (c) (available
+ // from the ASN Web site; see the Bibliography).
+ return CMapFactory.create(ucs2CMapName, this.options.cMapOptions, null).then(function (ucs2CMap) {
+ var cMap = properties.cMap;
+ toUnicode = [];
+ cMap.forEach(function (charcode, cid) {
+ assert(cid <= 0xffff, 'Max size of CID is 65,535');
+ // e) Map the CID obtained in step (a) according to the CMap
+ // obtained in step (d), producing a Unicode value.
+ var ucs2 = ucs2CMap.lookup(cid);
+ if (ucs2) {
+ toUnicode[charcode] = String.fromCharCode((ucs2.charCodeAt(0) << 8) + ucs2.charCodeAt(1));
+ }
+ });
+ return new ToUnicodeMap(toUnicode);
+ });
+ }
+ // The viewer's choice, just use an identity map.
+ return Promise.resolve(new IdentityToUnicodeMap(properties.firstChar, properties.lastChar));
+ },
+ readToUnicode: function PartialEvaluator_readToUnicode(toUnicode) {
+ var cmapObj = toUnicode;
+ if (isName(cmapObj)) {
+ return CMapFactory.create(cmapObj, this.options.cMapOptions, null).then(function (cmap) {
+ if (cmap instanceof IdentityCMap) {
+ return new IdentityToUnicodeMap(0, 0xFFFF);
+ }
+ return new ToUnicodeMap(cmap.getMap());
+ });
+ } else if (isStream(cmapObj)) {
+ return CMapFactory.create(cmapObj, this.options.cMapOptions, null).then(function (cmap) {
+ if (cmap instanceof IdentityCMap) {
+ return new IdentityToUnicodeMap(0, 0xFFFF);
+ }
+ var map = new Array(cmap.length);
+ // Convert UTF-16BE
+ // NOTE: cmap can be a sparse array, so use forEach instead of for(;;)
+ // to iterate over all keys.
+ cmap.forEach(function (charCode, token) {
+ var str = [];
+ for (var k = 0; k < token.length; k += 2) {
+ var w1 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
+ if ((w1 & 0xF800) !== 0xD800) {
+ // w1 < 0xD800 || w1 > 0xDFFF
+ str.push(w1);
+ continue;
+ }
+ k += 2;
+ var w2 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
+ str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
+ }
+ map[charCode] = String.fromCharCode.apply(String, str);
+ });
+ return new ToUnicodeMap(map);
+ });
+ }
+ return Promise.resolve(null);
+ },
+ readCidToGidMap: function PartialEvaluator_readCidToGidMap(cidToGidStream) {
+ // Extract the encoding from the CIDToGIDMap
+ var glyphsData = cidToGidStream.getBytes();
+ // Set encoding 0 to later verify the font has an encoding
+ var result = [];
+ for (var j = 0, jj = glyphsData.length; j < jj; j++) {
+ var glyphID = glyphsData[j++] << 8 | glyphsData[j];
+ if (glyphID === 0) {
+ continue;
+ }
+ var code = j >> 1;
+ result[code] = glyphID;
+ }
+ return result;
+ },
+ extractWidths: function PartialEvaluator_extractWidths(dict, xref, descriptor, properties) {
+ var glyphsWidths = [];
+ var defaultWidth = 0;
+ var glyphsVMetrics = [];
+ var defaultVMetrics;
+ var i, ii, j, jj, start, code, widths;
+ if (properties.composite) {
+ defaultWidth = dict.get('DW') || 1000;
+ widths = dict.get('W');
+ if (widths) {
+ for (i = 0, ii = widths.length; i < ii; i++) {
+ start = widths[i++];
+ code = xref.fetchIfRef(widths[i]);
+ if (isArray(code)) {
+ for (j = 0, jj = code.length; j < jj; j++) {
+ glyphsWidths[start++] = code[j];
+ }
+ } else {
+ var width = widths[++i];
+ for (j = start; j <= code; j++) {
+ glyphsWidths[j] = width;
+ }
+ }
+ }
+ }
+ if (properties.vertical) {
+ var vmetrics = dict.get('DW2') || [
+ 880,
+ -1000
+ ];
+ defaultVMetrics = [
+ vmetrics[1],
+ defaultWidth * 0.5,
+ vmetrics[0]
+ ];
+ vmetrics = dict.get('W2');
+ if (vmetrics) {
+ for (i = 0, ii = vmetrics.length; i < ii; i++) {
+ start = vmetrics[i++];
+ code = xref.fetchIfRef(vmetrics[i]);
+ if (isArray(code)) {
+ for (j = 0, jj = code.length; j < jj; j++) {
+ glyphsVMetrics[start++] = [
+ code[j++],
+ code[j++],
+ code[j]
+ ];
+ }
+ } else {
+ var vmetric = [
+ vmetrics[++i],
+ vmetrics[++i],
+ vmetrics[++i]
+ ];
+ for (j = start; j <= code; j++) {
+ glyphsVMetrics[j] = vmetric;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ var firstChar = properties.firstChar;
+ widths = dict.get('Widths');
+ if (widths) {
+ j = firstChar;
+ for (i = 0, ii = widths.length; i < ii; i++) {
+ glyphsWidths[j++] = widths[i];
+ }
+ defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
+ } else {
+ // Trying get the BaseFont metrics (see comment above).
+ var baseFontName = dict.get('BaseFont');
+ if (isName(baseFontName)) {
+ var metrics = this.getBaseFontMetrics(baseFontName.name);
+ glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties);
+ defaultWidth = metrics.defaultWidth;
+ }
+ }
+ }
+ // Heuristic: detection of monospace font by checking all non-zero widths
+ var isMonospace = true;
+ var firstWidth = defaultWidth;
+ for (var glyph in glyphsWidths) {
+ var glyphWidth = glyphsWidths[glyph];
+ if (!glyphWidth) {
+ continue;
+ }
+ if (!firstWidth) {
+ firstWidth = glyphWidth;
+ continue;
+ }
+ if (firstWidth !== glyphWidth) {
+ isMonospace = false;
+ break;
+ }
+ }
+ if (isMonospace) {
+ properties.flags |= FontFlags.FixedPitch;
+ }
+ properties.defaultWidth = defaultWidth;
+ properties.widths = glyphsWidths;
+ properties.defaultVMetrics = defaultVMetrics;
+ properties.vmetrics = glyphsVMetrics;
+ },
+ isSerifFont: function PartialEvaluator_isSerifFont(baseFontName) {
+ // Simulating descriptor flags attribute
+ var fontNameWoStyle = baseFontName.split('-')[0];
+ return fontNameWoStyle in getSerifFonts() || fontNameWoStyle.search(/serif/gi) !== -1;
+ },
+ getBaseFontMetrics: function PartialEvaluator_getBaseFontMetrics(name) {
+ var defaultWidth = 0;
+ var widths = [];
+ var monospace = false;
+ var stdFontMap = getStdFontMap();
+ var lookupName = stdFontMap[name] || name;
+ var Metrics = getMetrics();
+ if (!(lookupName in Metrics)) {
+ // Use default fonts for looking up font metrics if the passed
+ // font is not a base font
+ if (this.isSerifFont(name)) {
+ lookupName = 'Times-Roman';
+ } else {
+ lookupName = 'Helvetica';
+ }
+ }
+ var glyphWidths = Metrics[lookupName];
+ if (isNum(glyphWidths)) {
+ defaultWidth = glyphWidths;
+ monospace = true;
+ } else {
+ widths = glyphWidths();
+ }
+ // expand lazy widths array
+ return {
+ defaultWidth: defaultWidth,
+ monospace: monospace,
+ widths: widths
+ };
+ },
+ buildCharCodeToWidth: function PartialEvaluator_bulildCharCodeToWidth(widthsByGlyphName, properties) {
+ var widths = Object.create(null);
+ var differences = properties.differences;
+ var encoding = properties.defaultEncoding;
+ for (var charCode = 0; charCode < 256; charCode++) {
+ if (charCode in differences && widthsByGlyphName[differences[charCode]]) {
+ widths[charCode] = widthsByGlyphName[differences[charCode]];
+ continue;
+ }
+ if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) {
+ widths[charCode] = widthsByGlyphName[encoding[charCode]];
+ continue;
+ }
+ }
+ return widths;
+ },
+ preEvaluateFont: function PartialEvaluator_preEvaluateFont(dict, xref) {
+ var baseDict = dict;
+ var type = dict.get('Subtype');
+ assert(isName(type), 'invalid font Subtype');
+ var composite = false;
+ var uint8array;
+ if (type.name === 'Type0') {
+ // If font is a composite
+ // - get the descendant font
+ // - set the type according to the descendant font
+ // - get the FontDescriptor from the descendant font
+ var df = dict.get('DescendantFonts');
+ if (!df) {
+ error('Descendant fonts are not specified');
+ }
+ dict = isArray(df) ? xref.fetchIfRef(df[0]) : df;
+ type = dict.get('Subtype');
+ assert(isName(type), 'invalid font Subtype');
+ composite = true;
+ }
+ var descriptor = dict.get('FontDescriptor');
+ if (descriptor) {
+ var hash = new MurmurHash3_64();
+ var encoding = baseDict.getRaw('Encoding');
+ if (isName(encoding)) {
+ hash.update(encoding.name);
+ } else if (isRef(encoding)) {
+ hash.update(encoding.toString());
+ } else if (isDict(encoding)) {
+ var keys = encoding.getKeys();
+ for (var i = 0, ii = keys.length; i < ii; i++) {
+ var entry = encoding.getRaw(keys[i]);
+ if (isName(entry)) {
+ hash.update(entry.name);
+ } else if (isRef(entry)) {
+ hash.update(entry.toString());
+ } else if (isArray(entry)) {
+ // 'Differences' entry.
+ // Ideally we should check the contents of the array, but to avoid
+ // parsing it here and then again in |extractDataStructures|,
+ // we only use the array length for now (fixes bug1157493.pdf).
+ hash.update(entry.length.toString());
+ }
+ }
+ }
+ var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
+ if (isStream(toUnicode)) {
+ var stream = toUnicode.str || toUnicode;
+ uint8array = stream.buffer ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : new Uint8Array(stream.bytes.buffer, stream.start, stream.end - stream.start);
+ hash.update(uint8array);
+ } else if (isName(toUnicode)) {
+ hash.update(toUnicode.name);
+ }
+ var widths = dict.get('Widths') || baseDict.get('Widths');
+ if (widths) {
+ uint8array = new Uint8Array(new Uint32Array(widths).buffer);
+ hash.update(uint8array);
+ }
+ }
+ return {
+ descriptor: descriptor,
+ dict: dict,
+ baseDict: baseDict,
+ composite: composite,
+ type: type.name,
+ hash: hash ? hash.hexdigest() : ''
+ };
+ },
+ translateFont: function PartialEvaluator_translateFont(preEvaluatedFont, xref) {
+ var baseDict = preEvaluatedFont.baseDict;
+ var dict = preEvaluatedFont.dict;
+ var composite = preEvaluatedFont.composite;
+ var descriptor = preEvaluatedFont.descriptor;
+ var type = preEvaluatedFont.type;
+ var maxCharIndex = composite ? 0xFFFF : 0xFF;
+ var cMapOptions = this.options.cMapOptions;
+ var properties;
+ if (!descriptor) {
+ if (type === 'Type3') {
+ // FontDescriptor is only required for Type3 fonts when the document
+ // is a tagged pdf. Create a barbebones one to get by.
+ descriptor = new Dict(null);
+ descriptor.set('FontName', Name.get(type));
+ descriptor.set('FontBBox', dict.getArray('FontBBox'));
+ } else {
+ // Before PDF 1.5 if the font was one of the base 14 fonts, having a
+ // FontDescriptor was not required.
+ // This case is here for compatibility.
+ var baseFontName = dict.get('BaseFont');
+ if (!isName(baseFontName)) {
+ error('Base font is not specified');
+ }
+ // Using base font name as a font name.
+ baseFontName = baseFontName.name.replace(/[,_]/g, '-');
+ var metrics = this.getBaseFontMetrics(baseFontName);
+ // Simulating descriptor flags attribute
+ var fontNameWoStyle = baseFontName.split('-')[0];
+ var flags = (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | (metrics.monospace ? FontFlags.FixedPitch : 0) | (getSymbolsFonts()[fontNameWoStyle] ? FontFlags.Symbolic : FontFlags.Nonsymbolic);
+ properties = {
+ type: type,
+ name: baseFontName,
+ widths: metrics.widths,
+ defaultWidth: metrics.defaultWidth,
+ flags: flags,
+ firstChar: 0,
+ lastChar: maxCharIndex
+ };
+ return this.extractDataStructures(dict, dict, xref, properties).then(function (properties) {
+ properties.widths = this.buildCharCodeToWidth(metrics.widths, properties);
+ return new Font(baseFontName, null, properties);
+ }.bind(this));
+ }
+ }
+ // According to the spec if 'FontDescriptor' is declared, 'FirstChar',
+ // 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
+ // to ignore this rule when a variant of a standard font is used.
+ // TODO Fill the width array depending on which of the base font this is
+ // a variant.
+ var firstChar = dict.get('FirstChar') || 0;
+ var lastChar = dict.get('LastChar') || maxCharIndex;
+ var fontName = descriptor.get('FontName');
+ var baseFont = dict.get('BaseFont');
+ // Some bad PDFs have a string as the font name.
+ if (isString(fontName)) {
+ fontName = Name.get(fontName);
+ }
+ if (isString(baseFont)) {
+ baseFont = Name.get(baseFont);
+ }
+ if (type !== 'Type3') {
+ var fontNameStr = fontName && fontName.name;
+ var baseFontStr = baseFont && baseFont.name;
+ if (fontNameStr !== baseFontStr) {
+ info('The FontDescriptor\'s FontName is "' + fontNameStr + '" but should be the same as the Font\'s BaseFont "' + baseFontStr + '"');
+ // Workaround for cases where e.g. fontNameStr = 'Arial' and
+ // baseFontStr = 'Arial,Bold' (needed when no font file is embedded).
+ if (fontNameStr && baseFontStr && baseFontStr.indexOf(fontNameStr) === 0) {
+ fontName = baseFont;
+ }
+ }
+ }
+ fontName = fontName || baseFont;
+ assert(isName(fontName), 'invalid font name');
+ var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
+ if (fontFile) {
+ if (fontFile.dict) {
+ var subtype = fontFile.dict.get('Subtype');
+ if (subtype) {
+ subtype = subtype.name;
+ }
+ var length1 = fontFile.dict.get('Length1');
+ var length2 = fontFile.dict.get('Length2');
+ var length3 = fontFile.dict.get('Length3');
+ }
+ }
+ properties = {
+ type: type,
+ name: fontName.name,
+ subtype: subtype,
+ file: fontFile,
+ length1: length1,
+ length2: length2,
+ length3: length3,
+ loadedName: baseDict.loadedName,
+ composite: composite,
+ wideChars: composite,
+ fixedPitch: false,
+ fontMatrix: dict.getArray('FontMatrix') || FONT_IDENTITY_MATRIX,
+ firstChar: firstChar || 0,
+ lastChar: lastChar || maxCharIndex,
+ bbox: descriptor.getArray('FontBBox'),
+ ascent: descriptor.get('Ascent'),
+ descent: descriptor.get('Descent'),
+ xHeight: descriptor.get('XHeight'),
+ capHeight: descriptor.get('CapHeight'),
+ flags: descriptor.get('Flags'),
+ italicAngle: descriptor.get('ItalicAngle'),
+ coded: false
+ };
+ var cMapPromise;
+ if (composite) {
+ var cidEncoding = baseDict.get('Encoding');
+ if (isName(cidEncoding)) {
+ properties.cidEncoding = cidEncoding.name;
+ }
+ cMapPromise = CMapFactory.create(cidEncoding, cMapOptions, null).then(function (cMap) {
+ properties.cMap = cMap;
+ properties.vertical = properties.cMap.vertical;
+ });
+ } else {
+ cMapPromise = Promise.resolve(undefined);
+ }
+ return cMapPromise.then(function () {
+ return this.extractDataStructures(dict, baseDict, xref, properties);
+ }.bind(this)).then(function (properties) {
+ this.extractWidths(dict, xref, descriptor, properties);
+ if (type === 'Type3') {
+ properties.isType3Font = true;
+ }
+ return new Font(fontName.name, fontFile, properties);
+ }.bind(this));
+ }
+ };
+ return PartialEvaluator;
+ }();
+ var TranslatedFont = function TranslatedFontClosure() {
+ function TranslatedFont(loadedName, font, dict) {
+ this.loadedName = loadedName;
+ this.font = font;
+ this.dict = dict;
+ this.type3Loaded = null;
+ this.sent = false;
+ }
+ TranslatedFont.prototype = {
+ send: function (handler) {
+ if (this.sent) {
+ return;
+ }
+ var fontData = this.font.exportData();
+ handler.send('commonobj', [
+ this.loadedName,
+ 'Font',
+ fontData
+ ]);
+ this.sent = true;
+ },
+ loadType3Data: function (evaluator, resources, parentOperatorList, task) {
+ assert(this.font.isType3Font);
+ if (this.type3Loaded) {
+ return this.type3Loaded;
+ }
+ var translatedFont = this.font;
+ var loadCharProcsPromise = Promise.resolve();
+ var charProcs = this.dict.get('CharProcs');
+ var fontResources = this.dict.get('Resources') || resources;
+ var charProcKeys = charProcs.getKeys();
+ var charProcOperatorList = Object.create(null);
+ for (var i = 0, n = charProcKeys.length; i < n; ++i) {
+ loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
+ var glyphStream = charProcs.get(key);
+ var operatorList = new OperatorList();
+ return evaluator.getOperatorList(glyphStream, task, fontResources, operatorList).then(function () {
+ charProcOperatorList[key] = operatorList.getIR();
+ // Add the dependencies to the parent operator list so they are
+ // resolved before sub operator list is executed synchronously.
+ parentOperatorList.addDependencies(operatorList.dependencies);
+ }, function (reason) {
+ warn('Type3 font resource \"' + key + '\" is not available');
+ var operatorList = new OperatorList();
+ charProcOperatorList[key] = operatorList.getIR();
+ });
+ }.bind(this, charProcKeys[i]));
+ }
+ this.type3Loaded = loadCharProcsPromise.then(function () {
+ translatedFont.charProcOperatorList = charProcOperatorList;
+ });
+ return this.type3Loaded;
+ }
+ };
+ return TranslatedFont;
+ }();
+ var OperatorList = function OperatorListClosure() {
+ var CHUNK_SIZE = 1000;
+ var CHUNK_SIZE_ABOUT = CHUNK_SIZE - 5;
+ // close to chunk size
+ function getTransfers(queue) {
+ var transfers = [];
+ var fnArray = queue.fnArray, argsArray = queue.argsArray;
+ for (var i = 0, ii = queue.length; i < ii; i++) {
+ switch (fnArray[i]) {
+ case OPS.paintInlineImageXObject:
+ case OPS.paintInlineImageXObjectGroup:
+ case OPS.paintImageMaskXObject:
+ var arg = argsArray[i][0];
+ // first param in imgData
+ if (!arg.cached) {
+ transfers.push(arg.data.buffer);
+ }
+ break;
+ }
+ }
+ return transfers;
+ }
+ function OperatorList(intent, messageHandler, pageIndex) {
+ this.messageHandler = messageHandler;
+ this.fnArray = [];
+ this.argsArray = [];
+ this.dependencies = Object.create(null);
+ this._totalLength = 0;
+ this.pageIndex = pageIndex;
+ this.intent = intent;
+ }
+ OperatorList.prototype = {
+ get length() {
+ return this.argsArray.length;
+ },
+ /**
+ * @returns {number} The total length of the entire operator list,
+ * since `this.length === 0` after flushing.
+ */
+ get totalLength() {
+ return this._totalLength + this.length;
+ },
+ addOp: function (fn, args) {
+ this.fnArray.push(fn);
+ this.argsArray.push(args);
+ if (this.messageHandler) {
+ if (this.fnArray.length >= CHUNK_SIZE) {
+ this.flush();
+ } else if (this.fnArray.length >= CHUNK_SIZE_ABOUT && (fn === OPS.restore || fn === OPS.endText)) {
+ // heuristic to flush on boundary of restore or endText
+ this.flush();
+ }
+ }
+ },
+ addDependency: function (dependency) {
+ if (dependency in this.dependencies) {
+ return;
+ }
+ this.dependencies[dependency] = true;
+ this.addOp(OPS.dependency, [dependency]);
+ },
+ addDependencies: function (dependencies) {
+ for (var key in dependencies) {
+ this.addDependency(key);
+ }
+ },
+ addOpList: function (opList) {
+ Util.extendObj(this.dependencies, opList.dependencies);
+ for (var i = 0, ii = opList.length; i < ii; i++) {
+ this.addOp(opList.fnArray[i], opList.argsArray[i]);
+ }
+ },
+ getIR: function () {
+ return {
+ fnArray: this.fnArray,
+ argsArray: this.argsArray,
+ length: this.length
+ };
+ },
+ flush: function (lastChunk) {
+ if (this.intent !== 'oplist') {
+ new QueueOptimizer().optimize(this);
+ }
+ var transfers = getTransfers(this);
+ var length = this.length;
+ this._totalLength += length;
+ this.messageHandler.send('RenderPageChunk', {
+ operatorList: {
+ fnArray: this.fnArray,
+ argsArray: this.argsArray,
+ lastChunk: lastChunk,
+ length: length
+ },
+ pageIndex: this.pageIndex,
+ intent: this.intent
+ }, transfers);
+ this.dependencies = Object.create(null);
+ this.fnArray.length = 0;
+ this.argsArray.length = 0;
+ }
+ };
+ return OperatorList;
+ }();
+ var StateManager = function StateManagerClosure() {
+ function StateManager(initialState) {
+ this.state = initialState;
+ this.stateStack = [];
+ }
+ StateManager.prototype = {
+ save: function () {
+ var old = this.state;
+ this.stateStack.push(this.state);
+ this.state = old.clone();
+ },
+ restore: function () {
+ var prev = this.stateStack.pop();
+ if (prev) {
+ this.state = prev;
+ }
+ },
+ transform: function (args) {
+ this.state.ctm = Util.transform(this.state.ctm, args);
+ }
+ };
+ return StateManager;
+ }();
+ var TextState = function TextStateClosure() {
+ function TextState() {
+ this.ctm = new Float32Array(IDENTITY_MATRIX);
+ this.fontName = null;
+ this.fontSize = 0;
+ this.font = null;
+ this.fontMatrix = FONT_IDENTITY_MATRIX;
+ this.textMatrix = IDENTITY_MATRIX.slice();
+ this.textLineMatrix = IDENTITY_MATRIX.slice();
+ this.charSpacing = 0;
+ this.wordSpacing = 0;
+ this.leading = 0;
+ this.textHScale = 1;
+ this.textRise = 0;
+ }
+ TextState.prototype = {
+ setTextMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
+ var m = this.textMatrix;
+ m[0] = a;
+ m[1] = b;
+ m[2] = c;
+ m[3] = d;
+ m[4] = e;
+ m[5] = f;
+ },
+ setTextLineMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
+ var m = this.textLineMatrix;
+ m[0] = a;
+ m[1] = b;
+ m[2] = c;
+ m[3] = d;
+ m[4] = e;
+ m[5] = f;
+ },
+ translateTextMatrix: function TextState_translateTextMatrix(x, y) {
+ var m = this.textMatrix;
+ m[4] = m[0] * x + m[2] * y + m[4];
+ m[5] = m[1] * x + m[3] * y + m[5];
+ },
+ translateTextLineMatrix: function TextState_translateTextMatrix(x, y) {
+ var m = this.textLineMatrix;
+ m[4] = m[0] * x + m[2] * y + m[4];
+ m[5] = m[1] * x + m[3] * y + m[5];
+ },
+ calcTextLineMatrixAdvance: function TextState_calcTextLineMatrixAdvance(a, b, c, d, e, f) {
+ var font = this.font;
+ if (!font) {
+ return null;
+ }
+ var m = this.textLineMatrix;
+ if (!(a === m[0] && b === m[1] && c === m[2] && d === m[3])) {
+ return null;
+ }
+ var txDiff = e - m[4], tyDiff = f - m[5];
+ if (font.vertical && txDiff !== 0 || !font.vertical && tyDiff !== 0) {
+ return null;
+ }
+ var tx, ty, denominator = a * d - b * c;
+ if (font.vertical) {
+ tx = -tyDiff * c / denominator;
+ ty = tyDiff * a / denominator;
+ } else {
+ tx = txDiff * d / denominator;
+ ty = -txDiff * b / denominator;
+ }
+ return {
+ width: tx,
+ height: ty,
+ value: font.vertical ? ty : tx
+ };
+ },
+ calcRenderMatrix: function TextState_calcRendeMatrix(ctm) {
+ // 9.4.4 Text Space Details
+ var tsm = [
+ this.fontSize * this.textHScale,
+ 0,
+ 0,
+ this.fontSize,
+ 0,
+ this.textRise
+ ];
+ return Util.transform(ctm, Util.transform(this.textMatrix, tsm));
+ },
+ carriageReturn: function TextState_carriageReturn() {
+ this.translateTextLineMatrix(0, -this.leading);
+ this.textMatrix = this.textLineMatrix.slice();
+ },
+ clone: function TextState_clone() {
+ var clone = Object.create(this);
+ clone.textMatrix = this.textMatrix.slice();
+ clone.textLineMatrix = this.textLineMatrix.slice();
+ clone.fontMatrix = this.fontMatrix.slice();
+ return clone;
+ }
+ };
+ return TextState;
+ }();
+ var EvalState = function EvalStateClosure() {
+ function EvalState() {
+ this.ctm = new Float32Array(IDENTITY_MATRIX);
+ this.font = null;
+ this.textRenderingMode = TextRenderingMode.FILL;
+ this.fillColorSpace = ColorSpace.singletons.gray;
+ this.strokeColorSpace = ColorSpace.singletons.gray;
+ }
+ EvalState.prototype = {
+ clone: function CanvasExtraState_clone() {
+ return Object.create(this);
+ }
+ };
+ return EvalState;
+ }();
+ var EvaluatorPreprocessor = function EvaluatorPreprocessorClosure() {
+ // Specifies properties for each command
+ //
+ // If variableArgs === true: [0, `numArgs`] expected
+ // If variableArgs === false: exactly `numArgs` expected
+ var getOPMap = getLookupTableFactory(function (t) {
+ // Graphic state
+ t['w'] = {
+ id: OPS.setLineWidth,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['J'] = {
+ id: OPS.setLineCap,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['j'] = {
+ id: OPS.setLineJoin,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['M'] = {
+ id: OPS.setMiterLimit,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['d'] = {
+ id: OPS.setDash,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['ri'] = {
+ id: OPS.setRenderingIntent,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['i'] = {
+ id: OPS.setFlatness,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['gs'] = {
+ id: OPS.setGState,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['q'] = {
+ id: OPS.save,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['Q'] = {
+ id: OPS.restore,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['cm'] = {
+ id: OPS.transform,
+ numArgs: 6,
+ variableArgs: false
+ };
+ // Path
+ t['m'] = {
+ id: OPS.moveTo,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['l'] = {
+ id: OPS.lineTo,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['c'] = {
+ id: OPS.curveTo,
+ numArgs: 6,
+ variableArgs: false
+ };
+ t['v'] = {
+ id: OPS.curveTo2,
+ numArgs: 4,
+ variableArgs: false
+ };
+ t['y'] = {
+ id: OPS.curveTo3,
+ numArgs: 4,
+ variableArgs: false
+ };
+ t['h'] = {
+ id: OPS.closePath,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['re'] = {
+ id: OPS.rectangle,
+ numArgs: 4,
+ variableArgs: false
+ };
+ t['S'] = {
+ id: OPS.stroke,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['s'] = {
+ id: OPS.closeStroke,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['f'] = {
+ id: OPS.fill,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['F'] = {
+ id: OPS.fill,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['f*'] = {
+ id: OPS.eoFill,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['B'] = {
+ id: OPS.fillStroke,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['B*'] = {
+ id: OPS.eoFillStroke,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['b'] = {
+ id: OPS.closeFillStroke,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['b*'] = {
+ id: OPS.closeEOFillStroke,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['n'] = {
+ id: OPS.endPath,
+ numArgs: 0,
+ variableArgs: false
+ };
+ // Clipping
+ t['W'] = {
+ id: OPS.clip,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['W*'] = {
+ id: OPS.eoClip,
+ numArgs: 0,
+ variableArgs: false
+ };
+ // Text
+ t['BT'] = {
+ id: OPS.beginText,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['ET'] = {
+ id: OPS.endText,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['Tc'] = {
+ id: OPS.setCharSpacing,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['Tw'] = {
+ id: OPS.setWordSpacing,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['Tz'] = {
+ id: OPS.setHScale,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['TL'] = {
+ id: OPS.setLeading,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['Tf'] = {
+ id: OPS.setFont,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['Tr'] = {
+ id: OPS.setTextRenderingMode,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['Ts'] = {
+ id: OPS.setTextRise,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['Td'] = {
+ id: OPS.moveText,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['TD'] = {
+ id: OPS.setLeadingMoveText,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['Tm'] = {
+ id: OPS.setTextMatrix,
+ numArgs: 6,
+ variableArgs: false
+ };
+ t['T*'] = {
+ id: OPS.nextLine,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['Tj'] = {
+ id: OPS.showText,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['TJ'] = {
+ id: OPS.showSpacedText,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['\''] = {
+ id: OPS.nextLineShowText,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['"'] = {
+ id: OPS.nextLineSetSpacingShowText,
+ numArgs: 3,
+ variableArgs: false
+ };
+ // Type3 fonts
+ t['d0'] = {
+ id: OPS.setCharWidth,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['d1'] = {
+ id: OPS.setCharWidthAndBounds,
+ numArgs: 6,
+ variableArgs: false
+ };
+ // Color
+ t['CS'] = {
+ id: OPS.setStrokeColorSpace,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['cs'] = {
+ id: OPS.setFillColorSpace,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['SC'] = {
+ id: OPS.setStrokeColor,
+ numArgs: 4,
+ variableArgs: true
+ };
+ t['SCN'] = {
+ id: OPS.setStrokeColorN,
+ numArgs: 33,
+ variableArgs: true
+ };
+ t['sc'] = {
+ id: OPS.setFillColor,
+ numArgs: 4,
+ variableArgs: true
+ };
+ t['scn'] = {
+ id: OPS.setFillColorN,
+ numArgs: 33,
+ variableArgs: true
+ };
+ t['G'] = {
+ id: OPS.setStrokeGray,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['g'] = {
+ id: OPS.setFillGray,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['RG'] = {
+ id: OPS.setStrokeRGBColor,
+ numArgs: 3,
+ variableArgs: false
+ };
+ t['rg'] = {
+ id: OPS.setFillRGBColor,
+ numArgs: 3,
+ variableArgs: false
+ };
+ t['K'] = {
+ id: OPS.setStrokeCMYKColor,
+ numArgs: 4,
+ variableArgs: false
+ };
+ t['k'] = {
+ id: OPS.setFillCMYKColor,
+ numArgs: 4,
+ variableArgs: false
+ };
+ // Shading
+ t['sh'] = {
+ id: OPS.shadingFill,
+ numArgs: 1,
+ variableArgs: false
+ };
+ // Images
+ t['BI'] = {
+ id: OPS.beginInlineImage,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['ID'] = {
+ id: OPS.beginImageData,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['EI'] = {
+ id: OPS.endInlineImage,
+ numArgs: 1,
+ variableArgs: false
+ };
+ // XObjects
+ t['Do'] = {
+ id: OPS.paintXObject,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['MP'] = {
+ id: OPS.markPoint,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['DP'] = {
+ id: OPS.markPointProps,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['BMC'] = {
+ id: OPS.beginMarkedContent,
+ numArgs: 1,
+ variableArgs: false
+ };
+ t['BDC'] = {
+ id: OPS.beginMarkedContentProps,
+ numArgs: 2,
+ variableArgs: false
+ };
+ t['EMC'] = {
+ id: OPS.endMarkedContent,
+ numArgs: 0,
+ variableArgs: false
+ };
+ // Compatibility
+ t['BX'] = {
+ id: OPS.beginCompat,
+ numArgs: 0,
+ variableArgs: false
+ };
+ t['EX'] = {
+ id: OPS.endCompat,
+ numArgs: 0,
+ variableArgs: false
+ };
+ // (reserved partial commands for the lexer)
+ t['BM'] = null;
+ t['BD'] = null;
+ t['true'] = null;
+ t['fa'] = null;
+ t['fal'] = null;
+ t['fals'] = null;
+ t['false'] = null;
+ t['nu'] = null;
+ t['nul'] = null;
+ t['null'] = null;
+ });
+ function EvaluatorPreprocessor(stream, xref, stateManager) {
+ this.opMap = getOPMap();
+ // TODO(mduan): pass array of knownCommands rather than this.opMap
+ // dictionary
+ this.parser = new Parser(new Lexer(stream, this.opMap), false, xref);
+ this.stateManager = stateManager;
+ this.nonProcessedArgs = [];
+ }
+ EvaluatorPreprocessor.prototype = {
+ get savedStatesDepth() {
+ return this.stateManager.stateStack.length;
+ },
+ // |operation| is an object with two fields:
+ //
+ // - |fn| is an out param.
+ //
+ // - |args| is an inout param. On entry, it should have one of two values.
+ //
+ // - An empty array. This indicates that the caller is providing the
+ // array in which the args will be stored in. The caller should use
+ // this value if it can reuse a single array for each call to read().
+ //
+ // - |null|. This indicates that the caller needs this function to create
+ // the array in which any args are stored in. If there are zero args,
+ // this function will leave |operation.args| as |null| (thus avoiding
+ // allocations that would occur if we used an empty array to represent
+ // zero arguments). Otherwise, it will replace |null| with a new array
+ // containing the arguments. The caller should use this value if it
+ // cannot reuse an array for each call to read().
+ //
+ // These two modes are present because this function is very hot and so
+ // avoiding allocations where possible is worthwhile.
+ //
+ read: function EvaluatorPreprocessor_read(operation) {
+ var args = operation.args;
+ while (true) {
+ var obj = this.parser.getObj();
+ if (isCmd(obj)) {
+ var cmd = obj.cmd;
+ // Check that the command is valid
+ var opSpec = this.opMap[cmd];
+ if (!opSpec) {
+ warn('Unknown command "' + cmd + '"');
+ continue;
+ }
+ var fn = opSpec.id;
+ var numArgs = opSpec.numArgs;
+ var argsLength = args !== null ? args.length : 0;
+ if (!opSpec.variableArgs) {
+ // Postscript commands can be nested, e.g. /F2 /GS2 gs 5.711 Tf
+ if (argsLength !== numArgs) {
+ var nonProcessedArgs = this.nonProcessedArgs;
+ while (argsLength > numArgs) {
+ nonProcessedArgs.push(args.shift());
+ argsLength--;
+ }
+ while (argsLength < numArgs && nonProcessedArgs.length !== 0) {
+ if (!args) {
+ args = [];
+ }
+ args.unshift(nonProcessedArgs.pop());
+ argsLength++;
+ }
+ }
+ if (argsLength < numArgs) {
+ // If we receive too few args, it's not possible to possible
+ // to execute the command, so skip the command
+ info('Command ' + fn + ': because expected ' + numArgs + ' args, but received ' + argsLength + ' args; skipping');
+ args = null;
+ continue;
+ }
+ } else if (argsLength > numArgs) {
+ info('Command ' + fn + ': expected [0,' + numArgs + '] args, but received ' + argsLength + ' args');
+ }
+ // TODO figure out how to type-check vararg functions
+ this.preprocessCommand(fn, args);
+ operation.fn = fn;
+ operation.args = args;
+ return true;
+ } else {
+ if (isEOF(obj)) {
+ return false;
+ }
+ // no more commands
+ // argument
+ if (obj !== null) {
+ if (!args) {
+ args = [];
+ }
+ args.push(obj);
+ assert(args.length <= 33, 'Too many arguments');
+ }
+ }
+ }
+ },
+ preprocessCommand: function EvaluatorPreprocessor_preprocessCommand(fn, args) {
+ switch (fn | 0) {
+ case OPS.save:
+ this.stateManager.save();
+ break;
+ case OPS.restore:
+ this.stateManager.restore();
+ break;
+ case OPS.transform:
+ this.stateManager.transform(args);
+ break;
+ }
+ }
+ };
+ return EvaluatorPreprocessor;
+ }();
+ var QueueOptimizer = function QueueOptimizerClosure() {
+ function addState(parentState, pattern, fn) {
+ var state = parentState;
+ for (var i = 0, ii = pattern.length - 1; i < ii; i++) {
+ var item = pattern[i];
+ state = state[item] || (state[item] = []);
+ }
+ state[pattern[pattern.length - 1]] = fn;
+ }
+ function handlePaintSolidColorImageMask(iFirstSave, count, fnArray, argsArray) {
+ // Handles special case of mainly LaTeX documents which use image masks to
+ // draw lines with the current fill style.
+ // 'count' groups of (save, transform, paintImageMaskXObject, restore)+
+ // have been found at iFirstSave.
+ var iFirstPIMXO = iFirstSave + 2;
+ for (var i = 0; i < count; i++) {
+ var arg = argsArray[iFirstPIMXO + 4 * i];
+ var imageMask = arg.length === 1 && arg[0];
+ if (imageMask && imageMask.width === 1 && imageMask.height === 1 && (!imageMask.data.length || imageMask.data.length === 1 && imageMask.data[0] === 0)) {
+ fnArray[iFirstPIMXO + 4 * i] = OPS.paintSolidColorImageMask;
+ continue;
+ }
+ break;
+ }
+ return count - i;
+ }
+ var InitialState = [];
+ // This replaces (save, transform, paintInlineImageXObject, restore)+
+ // sequences with one |paintInlineImageXObjectGroup| operation.
+ addState(InitialState, [
+ OPS.save,
+ OPS.transform,
+ OPS.paintInlineImageXObject,
+ OPS.restore
+ ], function foundInlineImageGroup(context) {
+ var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
+ var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
+ var MAX_WIDTH = 1000;
+ var IMAGE_PADDING = 1;
+ var fnArray = context.fnArray, argsArray = context.argsArray;
+ var curr = context.iCurr;
+ var iFirstSave = curr - 3;
+ var iFirstTransform = curr - 2;
+ var iFirstPIIXO = curr - 1;
+ // Look for the quartets.
+ var i = iFirstSave + 4;
+ var ii = fnArray.length;
+ while (i + 3 < ii) {
+ if (fnArray[i] !== OPS.save || fnArray[i + 1] !== OPS.transform || fnArray[i + 2] !== OPS.paintInlineImageXObject || fnArray[i + 3] !== OPS.restore) {
+ break;
+ }
+ // ops don't match
+ i += 4;
+ }
+ // At this point, i is the index of the first op past the last valid
+ // quartet.
+ var count = Math.min((i - iFirstSave) / 4, MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
+ if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
+ return i;
+ }
+ // assuming that heights of those image is too small (~1 pixel)
+ // packing as much as possible by lines
+ var maxX = 0;
+ var map = [], maxLineHeight = 0;
+ var currentX = IMAGE_PADDING, currentY = IMAGE_PADDING;
+ var q;
+ for (q = 0; q < count; q++) {
+ var transform = argsArray[iFirstTransform + (q << 2)];
+ var img = argsArray[iFirstPIIXO + (q << 2)][0];
+ if (currentX + img.width > MAX_WIDTH) {
+ // starting new line
+ maxX = Math.max(maxX, currentX);
+ currentY += maxLineHeight + 2 * IMAGE_PADDING;
+ currentX = 0;
+ maxLineHeight = 0;
+ }
+ map.push({
+ transform: transform,
+ x: currentX,
+ y: currentY,
+ w: img.width,
+ h: img.height
+ });
+ currentX += img.width + 2 * IMAGE_PADDING;
+ maxLineHeight = Math.max(maxLineHeight, img.height);
+ }
+ var imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
+ var imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
+ var imgData = new Uint8Array(imgWidth * imgHeight * 4);
+ var imgRowSize = imgWidth << 2;
+ for (q = 0; q < count; q++) {
+ var data = argsArray[iFirstPIIXO + (q << 2)][0].data;
+ // Copy image by lines and extends pixels into padding.
+ var rowSize = map[q].w << 2;
+ var dataOffset = 0;
+ var offset = map[q].x + map[q].y * imgWidth << 2;
+ imgData.set(data.subarray(0, rowSize), offset - imgRowSize);
+ for (var k = 0, kk = map[q].h; k < kk; k++) {
+ imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset);
+ dataOffset += rowSize;
+ offset += imgRowSize;
+ }
+ imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset);
+ while (offset >= 0) {
+ data[offset - 4] = data[offset];
+ data[offset - 3] = data[offset + 1];
+ data[offset - 2] = data[offset + 2];
+ data[offset - 1] = data[offset + 3];
+ data[offset + rowSize] = data[offset + rowSize - 4];
+ data[offset + rowSize + 1] = data[offset + rowSize - 3];
+ data[offset + rowSize + 2] = data[offset + rowSize - 2];
+ data[offset + rowSize + 3] = data[offset + rowSize - 1];
+ offset -= imgRowSize;
+ }
+ }
+ // Replace queue items.
+ fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
+ argsArray.splice(iFirstSave, count * 4, [
+ {
+ width: imgWidth,
+ height: imgHeight,
+ kind: ImageKind.RGBA_32BPP,
+ data: imgData
+ },
+ map
+ ]);
+ return iFirstSave + 1;
+ });
+ // This replaces (save, transform, paintImageMaskXObject, restore)+
+ // sequences with one |paintImageMaskXObjectGroup| or one
+ // |paintImageMaskXObjectRepeat| operation.
+ addState(InitialState, [
+ OPS.save,
+ OPS.transform,
+ OPS.paintImageMaskXObject,
+ OPS.restore
+ ], function foundImageMaskGroup(context) {
+ var MIN_IMAGES_IN_MASKS_BLOCK = 10;
+ var MAX_IMAGES_IN_MASKS_BLOCK = 100;
+ var MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000;
+ var fnArray = context.fnArray, argsArray = context.argsArray;
+ var curr = context.iCurr;
+ var iFirstSave = curr - 3;
+ var iFirstTransform = curr - 2;
+ var iFirstPIMXO = curr - 1;
+ // Look for the quartets.
+ var i = iFirstSave + 4;
+ var ii = fnArray.length;
+ while (i + 3 < ii) {
+ if (fnArray[i] !== OPS.save || fnArray[i + 1] !== OPS.transform || fnArray[i + 2] !== OPS.paintImageMaskXObject || fnArray[i + 3] !== OPS.restore) {
+ break;
+ }
+ // ops don't match
+ i += 4;
+ }
+ // At this point, i is the index of the first op past the last valid
+ // quartet.
+ var count = (i - iFirstSave) / 4;
+ count = handlePaintSolidColorImageMask(iFirstSave, count, fnArray, argsArray);
+ if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
+ return i;
+ }
+ var q;
+ var isSameImage = false;
+ var iTransform, transformArgs;
+ var firstPIMXOArg0 = argsArray[iFirstPIMXO][0];
+ if (argsArray[iFirstTransform][1] === 0 && argsArray[iFirstTransform][2] === 0) {
+ isSameImage = true;
+ var firstTransformArg0 = argsArray[iFirstTransform][0];
+ var firstTransformArg3 = argsArray[iFirstTransform][3];
+ iTransform = iFirstTransform + 4;
+ var iPIMXO = iFirstPIMXO + 4;
+ for (q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) {
+ transformArgs = argsArray[iTransform];
+ if (argsArray[iPIMXO][0] !== firstPIMXOArg0 || transformArgs[0] !== firstTransformArg0 || transformArgs[1] !== 0 || transformArgs[2] !== 0 || transformArgs[3] !== firstTransformArg3) {
+ if (q < MIN_IMAGES_IN_MASKS_BLOCK) {
+ isSameImage = false;
+ } else {
+ count = q;
+ }
+ break;
+ }
+ }
+ }
+ // different image or transform
+ if (isSameImage) {
+ count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK);
+ var positions = new Float32Array(count * 2);
+ iTransform = iFirstTransform;
+ for (q = 0; q < count; q++, iTransform += 4) {
+ transformArgs = argsArray[iTransform];
+ positions[q << 1] = transformArgs[4];
+ positions[(q << 1) + 1] = transformArgs[5];
+ }
+ // Replace queue items.
+ fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat);
+ argsArray.splice(iFirstSave, count * 4, [
+ firstPIMXOArg0,
+ firstTransformArg0,
+ firstTransformArg3,
+ positions
+ ]);
+ } else {
+ count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK);
+ var images = [];
+ for (q = 0; q < count; q++) {
+ transformArgs = argsArray[iFirstTransform + (q << 2)];
+ var maskParams = argsArray[iFirstPIMXO + (q << 2)][0];
+ images.push({
+ data: maskParams.data,
+ width: maskParams.width,
+ height: maskParams.height,
+ transform: transformArgs
+ });
+ }
+ // Replace queue items.
+ fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup);
+ argsArray.splice(iFirstSave, count * 4, [images]);
+ }
+ return iFirstSave + 1;
+ });
+ // This replaces (save, transform, paintImageXObject, restore)+ sequences
+ // with one paintImageXObjectRepeat operation, if the |transform| and
+ // |paintImageXObjectRepeat| ops are appropriate.
+ addState(InitialState, [
+ OPS.save,
+ OPS.transform,
+ OPS.paintImageXObject,
+ OPS.restore
+ ], function (context) {
+ var MIN_IMAGES_IN_BLOCK = 3;
+ var MAX_IMAGES_IN_BLOCK = 1000;
+ var fnArray = context.fnArray, argsArray = context.argsArray;
+ var curr = context.iCurr;
+ var iFirstSave = curr - 3;
+ var iFirstTransform = curr - 2;
+ var iFirstPIXO = curr - 1;
+ var iFirstRestore = curr;
+ if (argsArray[iFirstTransform][1] !== 0 || argsArray[iFirstTransform][2] !== 0) {
+ return iFirstRestore + 1;
+ }
+ // transform has the wrong form
+ // Look for the quartets.
+ var firstPIXOArg0 = argsArray[iFirstPIXO][0];
+ var firstTransformArg0 = argsArray[iFirstTransform][0];
+ var firstTransformArg3 = argsArray[iFirstTransform][3];
+ var i = iFirstSave + 4;
+ var ii = fnArray.length;
+ while (i + 3 < ii) {
+ if (fnArray[i] !== OPS.save || fnArray[i + 1] !== OPS.transform || fnArray[i + 2] !== OPS.paintImageXObject || fnArray[i + 3] !== OPS.restore) {
+ break;
+ }
+ // ops don't match
+ if (argsArray[i + 1][0] !== firstTransformArg0 || argsArray[i + 1][1] !== 0 || argsArray[i + 1][2] !== 0 || argsArray[i + 1][3] !== firstTransformArg3) {
+ break;
+ }
+ // transforms don't match
+ if (argsArray[i + 2][0] !== firstPIXOArg0) {
+ break;
+ }
+ // images don't match
+ i += 4;
+ }
+ // At this point, i is the index of the first op past the last valid
+ // quartet.
+ var count = Math.min((i - iFirstSave) / 4, MAX_IMAGES_IN_BLOCK);
+ if (count < MIN_IMAGES_IN_BLOCK) {
+ return i;
+ }
+ // Extract the (x,y) positions from all of the matching transforms.
+ var positions = new Float32Array(count * 2);
+ var iTransform = iFirstTransform;
+ for (var q = 0; q < count; q++, iTransform += 4) {
+ var transformArgs = argsArray[iTransform];
+ positions[q << 1] = transformArgs[4];
+ positions[(q << 1) + 1] = transformArgs[5];
+ }
+ // Replace queue items.
+ var args = [
+ firstPIXOArg0,
+ firstTransformArg0,
+ firstTransformArg3,
+ positions
+ ];
+ fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat);
+ argsArray.splice(iFirstSave, count * 4, args);
+ return iFirstSave + 1;
+ });
+ // This replaces (beginText, setFont, setTextMatrix, showText, endText)+
+ // sequences with (beginText, setFont, (setTextMatrix, showText)+, endText)+
+ // sequences, if the font for each one is the same.
+ addState(InitialState, [
+ OPS.beginText,
+ OPS.setFont,
+ OPS.setTextMatrix,
+ OPS.showText,
+ OPS.endText
+ ], function (context) {
+ var MIN_CHARS_IN_BLOCK = 3;
+ var MAX_CHARS_IN_BLOCK = 1000;
+ var fnArray = context.fnArray, argsArray = context.argsArray;
+ var curr = context.iCurr;
+ var iFirstBeginText = curr - 4;
+ var iFirstSetFont = curr - 3;
+ var iFirstSetTextMatrix = curr - 2;
+ var iFirstShowText = curr - 1;
+ var iFirstEndText = curr;
+ // Look for the quintets.
+ var firstSetFontArg0 = argsArray[iFirstSetFont][0];
+ var firstSetFontArg1 = argsArray[iFirstSetFont][1];
+ var i = iFirstBeginText + 5;
+ var ii = fnArray.length;
+ while (i + 4 < ii) {
+ if (fnArray[i] !== OPS.beginText || fnArray[i + 1] !== OPS.setFont || fnArray[i + 2] !== OPS.setTextMatrix || fnArray[i + 3] !== OPS.showText || fnArray[i + 4] !== OPS.endText) {
+ break;
+ }
+ // ops don't match
+ if (argsArray[i + 1][0] !== firstSetFontArg0 || argsArray[i + 1][1] !== firstSetFontArg1) {
+ break;
+ }
+ // fonts don't match
+ i += 5;
+ }
+ // At this point, i is the index of the first op past the last valid
+ // quintet.
+ var count = Math.min((i - iFirstBeginText) / 5, MAX_CHARS_IN_BLOCK);
+ if (count < MIN_CHARS_IN_BLOCK) {
+ return i;
+ }
+ // If the preceding quintet is (<something>, setFont, setTextMatrix,
+ // showText, endText), include that as well. (E.g. <something> might be
+ // |dependency|.)
+ var iFirst = iFirstBeginText;
+ if (iFirstBeginText >= 4 && fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) {
+ count++;
+ iFirst -= 5;
+ }
+ // Remove (endText, beginText, setFont) trios.
+ var iEndText = iFirst + 4;
+ for (var q = 1; q < count; q++) {
+ fnArray.splice(iEndText, 3);
+ argsArray.splice(iEndText, 3);
+ iEndText += 2;
+ }
+ return iEndText + 1;
+ });
+ function QueueOptimizer() {
+ }
+ QueueOptimizer.prototype = {
+ optimize: function QueueOptimizer_optimize(queue) {
+ var fnArray = queue.fnArray, argsArray = queue.argsArray;
+ var context = {
+ iCurr: 0,
+ fnArray: fnArray,
+ argsArray: argsArray
+ };
+ var state;
+ var i = 0, ii = fnArray.length;
+ while (i < ii) {
+ state = (state || InitialState)[fnArray[i]];
+ if (typeof state === 'function') {
+ // we found some handler
+ context.iCurr = i;
+ // state() returns the index of the first non-matching op (if we
+ // didn't match) or the first op past the modified ops (if we did
+ // match and replace).
+ i = state(context);
+ state = undefined;
+ // reset the state machine
+ ii = context.fnArray.length;
+ } else {
+ i++;
+ }
+ }
+ }
+ };
+ return QueueOptimizer;
+ }();
+ exports.OperatorList = OperatorList;
+ exports.PartialEvaluator = PartialEvaluator;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreAnnotation = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreColorSpace, root.pdfjsCoreObj, root.pdfjsCoreEvaluator);
+ }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreColorSpace, coreObj, coreEvaluator) {
+ var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
+ var AnnotationFieldFlag = sharedUtil.AnnotationFieldFlag;
+ var AnnotationFlag = sharedUtil.AnnotationFlag;
+ var AnnotationType = sharedUtil.AnnotationType;
+ var OPS = sharedUtil.OPS;
+ var Util = sharedUtil.Util;
+ var isString = sharedUtil.isString;
+ var isArray = sharedUtil.isArray;
+ var isInt = sharedUtil.isInt;
+ var stringToBytes = sharedUtil.stringToBytes;
+ var stringToPDFString = sharedUtil.stringToPDFString;
+ var warn = sharedUtil.warn;
+ var Dict = corePrimitives.Dict;
+ var isDict = corePrimitives.isDict;
+ var isName = corePrimitives.isName;
+ var isRef = corePrimitives.isRef;
+ var Stream = coreStream.Stream;
+ var ColorSpace = coreColorSpace.ColorSpace;
+ var Catalog = coreObj.Catalog;
+ var ObjectLoader = coreObj.ObjectLoader;
+ var FileSpec = coreObj.FileSpec;
+ var OperatorList = coreEvaluator.OperatorList;
+ /**
+ * @class
+ * @alias AnnotationFactory
+ */
+ function AnnotationFactory() {
+ }
+ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */
+ {
+ /**
+ * @param {XRef} xref
+ * @param {Object} ref
+ * @param {PDFManager} pdfManager
+ * @param {string} uniquePrefix
+ * @param {Object} idCounters
+ * @returns {Annotation}
+ */
+ create: function AnnotationFactory_create(xref, ref, pdfManager, uniquePrefix, idCounters) {
+ var dict = xref.fetchIfRef(ref);
+ if (!isDict(dict)) {
+ return;
+ }
+ var id = isRef(ref) ? ref.toString() : 'annot_' + (uniquePrefix || '') + ++idCounters.obj;
+ // Determine the annotation's subtype.
+ var subtype = dict.get('Subtype');
+ subtype = isName(subtype) ? subtype.name : null;
+ // Return the right annotation object based on the subtype and field type.
+ var parameters = {
+ xref: xref,
+ dict: dict,
+ ref: isRef(ref) ? ref : null,
+ subtype: subtype,
+ id: id,
+ pdfManager: pdfManager
+ };
+ switch (subtype) {
+ case 'Link':
+ return new LinkAnnotation(parameters);
+ case 'Text':
+ return new TextAnnotation(parameters);
+ case 'Widget':
+ var fieldType = Util.getInheritableProperty(dict, 'FT');
+ fieldType = isName(fieldType) ? fieldType.name : null;
+ switch (fieldType) {
+ case 'Tx':
+ return new TextWidgetAnnotation(parameters);
+ case 'Ch':
+ return new ChoiceWidgetAnnotation(parameters);
+ }
+ warn('Unimplemented widget field type "' + fieldType + '", ' + 'falling back to base field type.');
+ return new WidgetAnnotation(parameters);
+ case 'Popup':
+ return new PopupAnnotation(parameters);
+ case 'Highlight':
+ return new HighlightAnnotation(parameters);
+ case 'Underline':
+ return new UnderlineAnnotation(parameters);
+ case 'Squiggly':
+ return new SquigglyAnnotation(parameters);
+ case 'StrikeOut':
+ return new StrikeOutAnnotation(parameters);
+ case 'FileAttachment':
+ return new FileAttachmentAnnotation(parameters);
+ default:
+ if (!subtype) {
+ warn('Annotation is missing the required /Subtype.');
+ } else {
+ warn('Unimplemented annotation type "' + subtype + '", ' + 'falling back to base annotation.');
+ }
+ return new Annotation(parameters);
+ }
+ }
+ };
+ var Annotation = function AnnotationClosure() {
+ // 12.5.5: Algorithm: Appearance streams
+ function getTransformMatrix(rect, bbox, matrix) {
+ var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
+ var minX = bounds[0];
+ var minY = bounds[1];
+ var maxX = bounds[2];
+ var maxY = bounds[3];
+ if (minX === maxX || minY === maxY) {
+ // From real-life file, bbox was [0, 0, 0, 0]. In this case,
+ // just apply the transform for rect
+ return [
+ 1,
+ 0,
+ 0,
+ 1,
+ rect[0],
+ rect[1]
+ ];
+ }
+ var xRatio = (rect[2] - rect[0]) / (maxX - minX);
+ var yRatio = (rect[3] - rect[1]) / (maxY - minY);
+ return [
+ xRatio,
+ 0,
+ 0,
+ yRatio,
+ rect[0] - minX * xRatio,
+ rect[1] - minY * yRatio
+ ];
+ }
+ function getDefaultAppearance(dict) {
+ var appearanceState = dict.get('AP');
+ if (!isDict(appearanceState)) {
+ return;
+ }
+ var appearance;
+ var appearances = appearanceState.get('N');
+ if (isDict(appearances)) {
+ var as = dict.get('AS');
+ if (as && appearances.has(as.name)) {
+ appearance = appearances.get(as.name);
+ }
+ } else {
+ appearance = appearances;
+ }
+ return appearance;
+ }
+ function Annotation(params) {
+ var dict = params.dict;
+ this.setFlags(dict.get('F'));
+ this.setRectangle(dict.getArray('Rect'));
+ this.setColor(dict.getArray('C'));
+ this.setBorderStyle(dict);
+ this.appearance = getDefaultAppearance(dict);
+ // Expose public properties using a data object.
+ this.data = {};
+ this.data.id = params.id;
+ this.data.subtype = params.subtype;
+ this.data.annotationFlags = this.flags;
+ this.data.rect = this.rectangle;
+ this.data.color = this.color;
+ this.data.borderStyle = this.borderStyle;
+ this.data.hasAppearance = !!this.appearance;
+ }
+ Annotation.prototype = {
+ /**
+ * @private
+ */
+ _hasFlag: function Annotation_hasFlag(flags, flag) {
+ return !!(flags & flag);
+ },
+ /**
+ * @private
+ */
+ _isViewable: function Annotation_isViewable(flags) {
+ return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, AnnotationFlag.HIDDEN) && !this._hasFlag(flags, AnnotationFlag.NOVIEW);
+ },
+ /**
+ * @private
+ */
+ _isPrintable: function AnnotationFlag_isPrintable(flags) {
+ return this._hasFlag(flags, AnnotationFlag.PRINT) && !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, AnnotationFlag.HIDDEN);
+ },
+ /**
+ * @return {boolean}
+ */
+ get viewable() {
+ if (this.flags === 0) {
+ return true;
+ }
+ return this._isViewable(this.flags);
+ },
+ /**
+ * @return {boolean}
+ */
+ get printable() {
+ if (this.flags === 0) {
+ return false;
+ }
+ return this._isPrintable(this.flags);
+ },
+ /**
+ * Set the flags.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {number} flags - Unsigned 32-bit integer specifying annotation
+ * characteristics
+ * @see {@link shared/util.js}
+ */
+ setFlags: function Annotation_setFlags(flags) {
+ this.flags = isInt(flags) && flags > 0 ? flags : 0;
+ },
+ /**
+ * Check if a provided flag is set.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {number} flag - Hexadecimal representation for an annotation
+ * characteristic
+ * @return {boolean}
+ * @see {@link shared/util.js}
+ */
+ hasFlag: function Annotation_hasFlag(flag) {
+ return this._hasFlag(this.flags, flag);
+ },
+ /**
+ * Set the rectangle.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {Array} rectangle - The rectangle array with exactly four entries
+ */
+ setRectangle: function Annotation_setRectangle(rectangle) {
+ if (isArray(rectangle) && rectangle.length === 4) {
+ this.rectangle = Util.normalizeRect(rectangle);
+ } else {
+ this.rectangle = [
+ 0,
+ 0,
+ 0,
+ 0
+ ];
+ }
+ },
+ /**
+ * Set the color and take care of color space conversion.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {Array} color - The color array containing either 0
+ * (transparent), 1 (grayscale), 3 (RGB) or
+ * 4 (CMYK) elements
+ */
+ setColor: function Annotation_setColor(color) {
+ var rgbColor = new Uint8Array(3);
+ // Black in RGB color space (default)
+ if (!isArray(color)) {
+ this.color = rgbColor;
+ return;
+ }
+ switch (color.length) {
+ case 0:
+ // Transparent, which we indicate with a null value
+ this.color = null;
+ break;
+ case 1:
+ // Convert grayscale to RGB
+ ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
+ this.color = rgbColor;
+ break;
+ case 3:
+ // Convert RGB percentages to RGB
+ ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
+ this.color = rgbColor;
+ break;
+ case 4:
+ // Convert CMYK to RGB
+ ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
+ this.color = rgbColor;
+ break;
+ default:
+ this.color = rgbColor;
+ break;
+ }
+ },
+ /**
+ * Set the border style (as AnnotationBorderStyle object).
+ *
+ * @public
+ * @memberof Annotation
+ * @param {Dict} borderStyle - The border style dictionary
+ */
+ setBorderStyle: function Annotation_setBorderStyle(borderStyle) {
+ this.borderStyle = new AnnotationBorderStyle();
+ if (!isDict(borderStyle)) {
+ return;
+ }
+ if (borderStyle.has('BS')) {
+ var dict = borderStyle.get('BS');
+ var dictType = dict.get('Type');
+ if (!dictType || isName(dictType, 'Border')) {
+ this.borderStyle.setWidth(dict.get('W'));
+ this.borderStyle.setStyle(dict.get('S'));
+ this.borderStyle.setDashArray(dict.getArray('D'));
+ }
+ } else if (borderStyle.has('Border')) {
+ var array = borderStyle.getArray('Border');
+ if (isArray(array) && array.length >= 3) {
+ this.borderStyle.setHorizontalCornerRadius(array[0]);
+ this.borderStyle.setVerticalCornerRadius(array[1]);
+ this.borderStyle.setWidth(array[2]);
+ if (array.length === 4) {
+ // Dash array available
+ this.borderStyle.setDashArray(array[3]);
+ }
+ }
+ } else {
+ // There are no border entries in the dictionary. According to the
+ // specification, we should draw a solid border of width 1 in that
+ // case, but Adobe Reader did not implement that part of the
+ // specification and instead draws no border at all, so we do the same.
+ // See also https://github.com/mozilla/pdf.js/issues/6179.
+ this.borderStyle.setWidth(0);
+ }
+ },
+ /**
+ * Prepare the annotation for working with a popup in the display layer.
+ *
+ * @private
+ * @memberof Annotation
+ * @param {Dict} dict - The annotation's data dictionary
+ */
+ _preparePopup: function Annotation_preparePopup(dict) {
+ if (!dict.has('C')) {
+ // Fall back to the default background color.
+ this.data.color = null;
+ }
+ this.data.hasPopup = dict.has('Popup');
+ this.data.title = stringToPDFString(dict.get('T') || '');
+ this.data.contents = stringToPDFString(dict.get('Contents') || '');
+ },
+ loadResources: function Annotation_loadResources(keys) {
+ return new Promise(function (resolve, reject) {
+ this.appearance.dict.getAsync('Resources').then(function (resources) {
+ if (!resources) {
+ resolve();
+ return;
+ }
+ var objectLoader = new ObjectLoader(resources.map, keys, resources.xref);
+ objectLoader.load().then(function () {
+ resolve(resources);
+ }, reject);
+ }, reject);
+ }.bind(this));
+ },
+ getOperatorList: function Annotation_getOperatorList(evaluator, task, renderForms) {
+ if (!this.appearance) {
+ return Promise.resolve(new OperatorList());
+ }
+ var data = this.data;
+ var appearanceDict = this.appearance.dict;
+ var resourcesPromise = this.loadResources([
+ 'ExtGState',
+ 'ColorSpace',
+ 'Pattern',
+ 'Shading',
+ 'XObject',
+ 'Font'
+ ]);
+ // ProcSet
+ // Properties
+ var bbox = appearanceDict.getArray('BBox') || [
+ 0,
+ 0,
+ 1,
+ 1
+ ];
+ var matrix = appearanceDict.getArray('Matrix') || [
+ 1,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0
+ ];
+ var transform = getTransformMatrix(data.rect, bbox, matrix);
+ var self = this;
+ return resourcesPromise.then(function (resources) {
+ var opList = new OperatorList();
+ opList.addOp(OPS.beginAnnotation, [
+ data.rect,
+ transform,
+ matrix
+ ]);
+ return evaluator.getOperatorList(self.appearance, task, resources, opList).then(function () {
+ opList.addOp(OPS.endAnnotation, []);
+ self.appearance.reset();
+ return opList;
+ });
+ });
+ }
+ };
+ Annotation.appendToOperatorList = function Annotation_appendToOperatorList(annotations, opList, partialEvaluator, task, intent, renderForms) {
+ var annotationPromises = [];
+ for (var i = 0, n = annotations.length; i < n; ++i) {
+ if (intent === 'display' && annotations[i].viewable || intent === 'print' && annotations[i].printable) {
+ annotationPromises.push(annotations[i].getOperatorList(partialEvaluator, task, renderForms));
+ }
+ }
+ return Promise.all(annotationPromises).then(function (operatorLists) {
+ opList.addOp(OPS.beginAnnotations, []);
+ for (var i = 0, n = operatorLists.length; i < n; ++i) {
+ opList.addOpList(operatorLists[i]);
+ }
+ opList.addOp(OPS.endAnnotations, []);
+ });
+ };
+ return Annotation;
+ }();
+ /**
+ * Contains all data regarding an annotation's border style.
+ *
+ * @class
+ */
+ var AnnotationBorderStyle = function AnnotationBorderStyleClosure() {
+ /**
+ * @constructor
+ * @private
+ */
+ function AnnotationBorderStyle() {
+ this.width = 1;
+ this.style = AnnotationBorderStyleType.SOLID;
+ this.dashArray = [3];
+ this.horizontalCornerRadius = 0;
+ this.verticalCornerRadius = 0;
+ }
+ AnnotationBorderStyle.prototype = {
+ /**
+ * Set the width.
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {integer} width - The width
+ */
+ setWidth: function AnnotationBorderStyle_setWidth(width) {
+ if (width === (width | 0)) {
+ this.width = width;
+ }
+ },
+ /**
+ * Set the style.
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {Object} style - The style object
+ * @see {@link shared/util.js}
+ */
+ setStyle: function AnnotationBorderStyle_setStyle(style) {
+ if (!style) {
+ return;
+ }
+ switch (style.name) {
+ case 'S':
+ this.style = AnnotationBorderStyleType.SOLID;
+ break;
+ case 'D':
+ this.style = AnnotationBorderStyleType.DASHED;
+ break;
+ case 'B':
+ this.style = AnnotationBorderStyleType.BEVELED;
+ break;
+ case 'I':
+ this.style = AnnotationBorderStyleType.INSET;
+ break;
+ case 'U':
+ this.style = AnnotationBorderStyleType.UNDERLINE;
+ break;
+ default:
+ break;
+ }
+ },
+ /**
+ * Set the dash array.
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {Array} dashArray - The dash array with at least one element
+ */
+ setDashArray: function AnnotationBorderStyle_setDashArray(dashArray) {
+ // We validate the dash array, but we do not use it because CSS does not
+ // allow us to change spacing of dashes. For more information, visit
+ // http://www.w3.org/TR/css3-background/#the-border-style.
+ if (isArray(dashArray) && dashArray.length > 0) {
+ // According to the PDF specification: the elements in a dashArray
+ // shall be numbers that are nonnegative and not all equal to zero.
+ var isValid = true;
+ var allZeros = true;
+ for (var i = 0, len = dashArray.length; i < len; i++) {
+ var element = dashArray[i];
+ var validNumber = +element >= 0;
+ if (!validNumber) {
+ isValid = false;
+ break;
+ } else if (element > 0) {
+ allZeros = false;
+ }
+ }
+ if (isValid && !allZeros) {
+ this.dashArray = dashArray;
+ } else {
+ this.width = 0;
+ }
+ } else // Adobe behavior when the array is invalid.
+ if (dashArray) {
+ this.width = 0;
+ }
+ },
+ // Adobe behavior when the array is invalid.
+ /**
+ * Set the horizontal corner radius (from a Border dictionary).
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {integer} radius - The horizontal corner radius
+ */
+ setHorizontalCornerRadius: function AnnotationBorderStyle_setHorizontalCornerRadius(radius) {
+ if (radius === (radius | 0)) {
+ this.horizontalCornerRadius = radius;
+ }
+ },
+ /**
+ * Set the vertical corner radius (from a Border dictionary).
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {integer} radius - The vertical corner radius
+ */
+ setVerticalCornerRadius: function AnnotationBorderStyle_setVerticalCornerRadius(radius) {
+ if (radius === (radius | 0)) {
+ this.verticalCornerRadius = radius;
+ }
+ }
+ };
+ return AnnotationBorderStyle;
+ }();
+ var WidgetAnnotation = function WidgetAnnotationClosure() {
+ function WidgetAnnotation(params) {
+ Annotation.call(this, params);
+ var dict = params.dict;
+ var data = this.data;
+ data.annotationType = AnnotationType.WIDGET;
+ data.fieldName = this._constructFieldName(dict);
+ data.fieldValue = Util.getInheritableProperty(dict, 'V', /* getArray = */
+ true);
+ data.alternativeText = stringToPDFString(dict.get('TU') || '');
+ data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
+ var fieldType = Util.getInheritableProperty(dict, 'FT');
+ data.fieldType = isName(fieldType) ? fieldType.name : null;
+ this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
+ data.fieldFlags = Util.getInheritableProperty(dict, 'Ff');
+ if (!isInt(data.fieldFlags) || data.fieldFlags < 0) {
+ data.fieldFlags = 0;
+ }
+ data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
+ // Hide signatures because we cannot validate them.
+ if (data.fieldType === 'Sig') {
+ this.setFlags(AnnotationFlag.HIDDEN);
+ }
+ }
+ Util.inherit(WidgetAnnotation, Annotation, {
+ /**
+ * Construct the (fully qualified) field name from the (partial) field
+ * names of the field and its ancestors.
+ *
+ * @private
+ * @memberof WidgetAnnotation
+ * @param {Dict} dict - Complete widget annotation dictionary
+ * @return {string}
+ */
+ _constructFieldName: function WidgetAnnotation_constructFieldName(dict) {
+ // Both the `Parent` and `T` fields are optional. While at least one of
+ // them should be provided, bad PDF generators may fail to do so.
+ if (!dict.has('T') && !dict.has('Parent')) {
+ warn('Unknown field name, falling back to empty field name.');
+ return '';
+ }
+ // If no parent exists, the partial and fully qualified names are equal.
+ if (!dict.has('Parent')) {
+ return stringToPDFString(dict.get('T'));
+ }
+ // Form the fully qualified field name by appending the partial name to
+ // the parent's fully qualified name, separated by a period.
+ var fieldName = [];
+ if (dict.has('T')) {
+ fieldName.unshift(stringToPDFString(dict.get('T')));
+ }
+ var loopDict = dict;
+ while (loopDict.has('Parent')) {
+ loopDict = loopDict.get('Parent');
+ if (loopDict.has('T')) {
+ fieldName.unshift(stringToPDFString(loopDict.get('T')));
+ }
+ }
+ return fieldName.join('.');
+ },
+ /**
+ * Check if a provided field flag is set.
+ *
+ * @public
+ * @memberof WidgetAnnotation
+ * @param {number} flag - Hexadecimal representation for an annotation
+ * field characteristic
+ * @return {boolean}
+ * @see {@link shared/util.js}
+ */
+ hasFieldFlag: function WidgetAnnotation_hasFieldFlag(flag) {
+ return !!(this.data.fieldFlags & flag);
+ }
+ });
+ return WidgetAnnotation;
+ }();
+ var TextWidgetAnnotation = function TextWidgetAnnotationClosure() {
+ function TextWidgetAnnotation(params) {
+ WidgetAnnotation.call(this, params);
+ // The field value is always a string.
+ this.data.fieldValue = stringToPDFString(this.data.fieldValue || '');
+ // Determine the alignment of text in the field.
+ var alignment = Util.getInheritableProperty(params.dict, 'Q');
+ if (!isInt(alignment) || alignment < 0 || alignment > 2) {
+ alignment = null;
+ }
+ this.data.textAlignment = alignment;
+ // Determine the maximum length of text in the field.
+ var maximumLength = Util.getInheritableProperty(params.dict, 'MaxLen');
+ if (!isInt(maximumLength) || maximumLength < 0) {
+ maximumLength = null;
+ }
+ this.data.maxLen = maximumLength;
+ // Process field flags for the display layer.
+ this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
+ this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) && !this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) && !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) && !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && this.data.maxLen !== null;
+ }
+ Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
+ getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
+ var operatorList = new OperatorList();
+ // Do not render form elements on the canvas when interactive forms are
+ // enabled. The display layer is responsible for rendering them instead.
+ if (renderForms) {
+ return Promise.resolve(operatorList);
+ }
+ if (this.appearance) {
+ return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
+ }
+ // Even if there is an appearance stream, ignore it. This is the
+ // behaviour used by Adobe Reader.
+ if (!this.data.defaultAppearance) {
+ return Promise.resolve(operatorList);
+ }
+ var stream = new Stream(stringToBytes(this.data.defaultAppearance));
+ return evaluator.getOperatorList(stream, task, this.fieldResources, operatorList).then(function () {
+ return operatorList;
+ });
+ }
+ });
+ return TextWidgetAnnotation;
+ }();
+ var ChoiceWidgetAnnotation = function ChoiceWidgetAnnotationClosure() {
+ function ChoiceWidgetAnnotation(params) {
+ WidgetAnnotation.call(this, params);
+ // Determine the options. The options array may consist of strings or
+ // arrays. If the array consists of arrays, then the first element of
+ // each array is the export value and the second element of each array is
+ // the display value. If the array consists of strings, then these
+ // represent both the export and display value. In this case, we convert
+ // it to an array of arrays as well for convenience in the display layer.
+ this.data.options = [];
+ var options = params.dict.getArray('Opt');
+ if (isArray(options)) {
+ for (var i = 0, ii = options.length; i < ii; i++) {
+ var option = options[i];
+ this.data.options[i] = {
+ exportValue: isArray(option) ? option[0] : option,
+ displayValue: isArray(option) ? option[1] : option
+ };
+ }
+ }
+ // Determine the field value. In this case, it may be a string or an
+ // array of strings. For convenience in the display layer, convert the
+ // string to an array of one string as well.
+ if (!isArray(this.data.fieldValue)) {
+ this.data.fieldValue = [this.data.fieldValue];
+ }
+ // Process field flags for the display layer.
+ this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
+ this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
+ }
+ Util.inherit(ChoiceWidgetAnnotation, WidgetAnnotation, {
+ getOperatorList: function ChoiceWidgetAnnotation_getOperatorList(evaluator, task, renderForms) {
+ var operatorList = new OperatorList();
+ // Do not render form elements on the canvas when interactive forms are
+ // enabled. The display layer is responsible for rendering them instead.
+ if (renderForms) {
+ return Promise.resolve(operatorList);
+ }
+ return Annotation.prototype.getOperatorList.call(this, evaluator, task, renderForms);
+ }
+ });
+ return ChoiceWidgetAnnotation;
+ }();
+ var TextAnnotation = function TextAnnotationClosure() {
+ var DEFAULT_ICON_SIZE = 22;
+ // px
+ function TextAnnotation(parameters) {
+ Annotation.call(this, parameters);
+ this.data.annotationType = AnnotationType.TEXT;
+ if (this.data.hasAppearance) {
+ this.data.name = 'NoIcon';
+ } else {
+ this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
+ this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
+ this.data.name = parameters.dict.has('Name') ? parameters.dict.get('Name').name : 'Note';
+ }
+ this._preparePopup(parameters.dict);
+ }
+ Util.inherit(TextAnnotation, Annotation, {});
+ return TextAnnotation;
+ }();
+ var LinkAnnotation = function LinkAnnotationClosure() {
+ function LinkAnnotation(params) {
+ Annotation.call(this, params);
+ var data = this.data;
+ data.annotationType = AnnotationType.LINK;
+ Catalog.parseDestDictionary({
+ destDict: params.dict,
+ resultObj: data,
+ docBaseUrl: params.pdfManager.docBaseUrl
+ });
+ }
+ Util.inherit(LinkAnnotation, Annotation, {});
+ return LinkAnnotation;
+ }();
+ var PopupAnnotation = function PopupAnnotationClosure() {
+ function PopupAnnotation(parameters) {
+ Annotation.call(this, parameters);
+ this.data.annotationType = AnnotationType.POPUP;
+ var dict = parameters.dict;
+ var parentItem = dict.get('Parent');
+ if (!parentItem) {
+ warn('Popup annotation has a missing or invalid parent annotation.');
+ return;
+ }
+ this.data.parentId = dict.getRaw('Parent').toString();
+ this.data.title = stringToPDFString(parentItem.get('T') || '');
+ this.data.contents = stringToPDFString(parentItem.get('Contents') || '');
+ if (!parentItem.has('C')) {
+ // Fall back to the default background color.
+ this.data.color = null;
+ } else {
+ this.setColor(parentItem.getArray('C'));
+ this.data.color = this.color;
+ }
+ // If the Popup annotation is not viewable, but the parent annotation is,
+ // that is most likely a bug. Fallback to inherit the flags from the parent
+ // annotation (this is consistent with the behaviour in Adobe Reader).
+ if (!this.viewable) {
+ var parentFlags = parentItem.get('F');
+ if (this._isViewable(parentFlags)) {
+ this.setFlags(parentFlags);
+ }
+ }
+ }
+ Util.inherit(PopupAnnotation, Annotation, {});
+ return PopupAnnotation;
+ }();
+ var HighlightAnnotation = function HighlightAnnotationClosure() {
+ function HighlightAnnotation(parameters) {
+ Annotation.call(this, parameters);
+ this.data.annotationType = AnnotationType.HIGHLIGHT;
+ this._preparePopup(parameters.dict);
+ // PDF viewers completely ignore any border styles.
+ this.data.borderStyle.setWidth(0);
+ }
+ Util.inherit(HighlightAnnotation, Annotation, {});
+ return HighlightAnnotation;
+ }();
+ var UnderlineAnnotation = function UnderlineAnnotationClosure() {
+ function UnderlineAnnotation(parameters) {
+ Annotation.call(this, parameters);
+ this.data.annotationType = AnnotationType.UNDERLINE;
+ this._preparePopup(parameters.dict);
+ // PDF viewers completely ignore any border styles.
+ this.data.borderStyle.setWidth(0);
+ }
+ Util.inherit(UnderlineAnnotation, Annotation, {});
+ return UnderlineAnnotation;
+ }();
+ var SquigglyAnnotation = function SquigglyAnnotationClosure() {
+ function SquigglyAnnotation(parameters) {
+ Annotation.call(this, parameters);
+ this.data.annotationType = AnnotationType.SQUIGGLY;
+ this._preparePopup(parameters.dict);
+ // PDF viewers completely ignore any border styles.
+ this.data.borderStyle.setWidth(0);
+ }
+ Util.inherit(SquigglyAnnotation, Annotation, {});
+ return SquigglyAnnotation;
+ }();
+ var StrikeOutAnnotation = function StrikeOutAnnotationClosure() {
+ function StrikeOutAnnotation(parameters) {
+ Annotation.call(this, parameters);
+ this.data.annotationType = AnnotationType.STRIKEOUT;
+ this._preparePopup(parameters.dict);
+ // PDF viewers completely ignore any border styles.
+ this.data.borderStyle.setWidth(0);
+ }
+ Util.inherit(StrikeOutAnnotation, Annotation, {});
+ return StrikeOutAnnotation;
+ }();
+ var FileAttachmentAnnotation = function FileAttachmentAnnotationClosure() {
+ function FileAttachmentAnnotation(parameters) {
+ Annotation.call(this, parameters);
+ var file = new FileSpec(parameters.dict.get('FS'), parameters.xref);
+ this.data.annotationType = AnnotationType.FILEATTACHMENT;
+ this.data.file = file.serializable;
+ this._preparePopup(parameters.dict);
+ }
+ Util.inherit(FileAttachmentAnnotation, Annotation, {});
+ return FileAttachmentAnnotation;
+ }();
+ exports.Annotation = Annotation;
+ exports.AnnotationBorderStyle = AnnotationBorderStyle;
+ exports.AnnotationFactory = AnnotationFactory;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreDocument = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreObj, root.pdfjsCoreParser, root.pdfjsCoreCrypto, root.pdfjsCoreEvaluator, root.pdfjsCoreAnnotation);
+ }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreObj, coreParser, coreCrypto, coreEvaluator, coreAnnotation) {
+ var MissingDataException = sharedUtil.MissingDataException;
+ var Util = sharedUtil.Util;
+ var assert = sharedUtil.assert;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var isArray = sharedUtil.isArray;
+ var isArrayBuffer = sharedUtil.isArrayBuffer;
+ var isString = sharedUtil.isString;
+ var shadow = sharedUtil.shadow;
+ var stringToBytes = sharedUtil.stringToBytes;
+ var stringToPDFString = sharedUtil.stringToPDFString;
+ var warn = sharedUtil.warn;
+ var isSpace = sharedUtil.isSpace;
+ var Dict = corePrimitives.Dict;
+ var isDict = corePrimitives.isDict;
+ var isName = corePrimitives.isName;
+ var isStream = corePrimitives.isStream;
+ var NullStream = coreStream.NullStream;
+ var Stream = coreStream.Stream;
+ var StreamsSequenceStream = coreStream.StreamsSequenceStream;
+ var Catalog = coreObj.Catalog;
+ var ObjectLoader = coreObj.ObjectLoader;
+ var XRef = coreObj.XRef;
+ var Linearization = coreParser.Linearization;
+ var calculateMD5 = coreCrypto.calculateMD5;
+ var OperatorList = coreEvaluator.OperatorList;
+ var PartialEvaluator = coreEvaluator.PartialEvaluator;
+ var Annotation = coreAnnotation.Annotation;
+ var AnnotationFactory = coreAnnotation.AnnotationFactory;
+ var Page = function PageClosure() {
+ var LETTER_SIZE_MEDIABOX = [
+ 0,
+ 0,
+ 612,
+ 792
+ ];
+ function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) {
+ this.pdfManager = pdfManager;
+ this.pageIndex = pageIndex;
+ this.pageDict = pageDict;
+ this.xref = xref;
+ this.ref = ref;
+ this.fontCache = fontCache;
+ this.uniquePrefix = 'p' + this.pageIndex + '_';
+ this.idCounters = { obj: 0 };
+ this.evaluatorOptions = pdfManager.evaluatorOptions;
+ this.resourcesPromise = null;
+ }
+ Page.prototype = {
+ getPageProp: function Page_getPageProp(key) {
+ return this.pageDict.get(key);
+ },
+ getInheritedPageProp: function Page_getInheritedPageProp(key) {
+ var dict = this.pageDict, valueArray = null, loopCount = 0;
+ var MAX_LOOP_COUNT = 100;
+ // Always walk up the entire parent chain, to be able to find
+ // e.g. \Resources placed on multiple levels of the tree.
+ while (dict) {
+ var value = dict.get(key);
+ if (value) {
+ if (!valueArray) {
+ valueArray = [];
+ }
+ valueArray.push(value);
+ }
+ if (++loopCount > MAX_LOOP_COUNT) {
+ warn('Page_getInheritedPageProp: maximum loop count exceeded.');
+ break;
+ }
+ dict = dict.get('Parent');
+ }
+ if (!valueArray) {
+ return Dict.empty;
+ }
+ if (valueArray.length === 1 || !isDict(valueArray[0]) || loopCount > MAX_LOOP_COUNT) {
+ return valueArray[0];
+ }
+ return Dict.merge(this.xref, valueArray);
+ },
+ get content() {
+ return this.getPageProp('Contents');
+ },
+ get resources() {
+ // For robustness: The spec states that a \Resources entry has to be
+ // present, but can be empty. Some document omit it still, in this case
+ // we return an empty dictionary.
+ return shadow(this, 'resources', this.getInheritedPageProp('Resources'));
+ },
+ get mediaBox() {
+ var obj = this.getInheritedPageProp('MediaBox');
+ // Reset invalid media box to letter size.
+ if (!isArray(obj) || obj.length !== 4) {
+ obj = LETTER_SIZE_MEDIABOX;
+ }
+ return shadow(this, 'mediaBox', obj);
+ },
+ get view() {
+ var mediaBox = this.mediaBox;
+ var cropBox = this.getInheritedPageProp('CropBox');
+ if (!isArray(cropBox) || cropBox.length !== 4) {
+ return shadow(this, 'view', mediaBox);
+ }
+ // From the spec, 6th ed., p.963:
+ // "The crop, bleed, trim, and art boxes should not ordinarily
+ // extend beyond the boundaries of the media box. If they do, they are
+ // effectively reduced to their intersection with the media box."
+ cropBox = Util.intersect(cropBox, mediaBox);
+ if (!cropBox) {
+ return shadow(this, 'view', mediaBox);
+ }
+ return shadow(this, 'view', cropBox);
+ },
+ get rotate() {
+ var rotate = this.getInheritedPageProp('Rotate') || 0;
+ // Normalize rotation so it's a multiple of 90 and between 0 and 270
+ if (rotate % 90 !== 0) {
+ rotate = 0;
+ } else if (rotate >= 360) {
+ rotate = rotate % 360;
+ } else if (rotate < 0) {
+ // The spec doesn't cover negatives, assume its counterclockwise
+ // rotation. The following is the other implementation of modulo.
+ rotate = (rotate % 360 + 360) % 360;
+ }
+ return shadow(this, 'rotate', rotate);
+ },
+ getContentStream: function Page_getContentStream() {
+ var content = this.content;
+ var stream;
+ if (isArray(content)) {
+ // fetching items
+ var xref = this.xref;
+ var i, n = content.length;
+ var streams = [];
+ for (i = 0; i < n; ++i) {
+ streams.push(xref.fetchIfRef(content[i]));
+ }
+ stream = new StreamsSequenceStream(streams);
+ } else if (isStream(content)) {
+ stream = content;
+ } else {
+ // replacing non-existent page content with empty one
+ stream = new NullStream();
+ }
+ return stream;
+ },
+ loadResources: function Page_loadResources(keys) {
+ if (!this.resourcesPromise) {
+ // TODO: add async getInheritedPageProp and remove this.
+ this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
+ }
+ return this.resourcesPromise.then(function resourceSuccess() {
+ var objectLoader = new ObjectLoader(this.resources.map, keys, this.xref);
+ return objectLoader.load();
+ }.bind(this));
+ },
+ getOperatorList: function Page_getOperatorList(handler, task, intent, renderInteractiveForms) {
+ var self = this;
+ var pdfManager = this.pdfManager;
+ var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', []);
+ var resourcesPromise = this.loadResources([
+ 'ExtGState',
+ 'ColorSpace',
+ 'Pattern',
+ 'Shading',
+ 'XObject',
+ 'Font'
+ ]);
+ // ProcSet
+ // Properties
+ var partialEvaluator = new PartialEvaluator(pdfManager, this.xref, handler, this.pageIndex, this.uniquePrefix, this.idCounters, this.fontCache, this.evaluatorOptions);
+ var dataPromises = Promise.all([
+ contentStreamPromise,
+ resourcesPromise
+ ]);
+ var pageListPromise = dataPromises.then(function (data) {
+ var contentStream = data[0];
+ var opList = new OperatorList(intent, handler, self.pageIndex);
+ handler.send('StartRenderPage', {
+ transparency: partialEvaluator.hasBlendModes(self.resources),
+ pageIndex: self.pageIndex,
+ intent: intent
+ });
+ return partialEvaluator.getOperatorList(contentStream, task, self.resources, opList).then(function () {
+ return opList;
+ });
+ });
+ var annotationsPromise = pdfManager.ensure(this, 'annotations');
+ return Promise.all([
+ pageListPromise,
+ annotationsPromise
+ ]).then(function (datas) {
+ var pageOpList = datas[0];
+ var annotations = datas[1];
+ if (annotations.length === 0) {
+ pageOpList.flush(true);
+ return pageOpList;
+ }
+ var annotationsReadyPromise = Annotation.appendToOperatorList(annotations, pageOpList, partialEvaluator, task, intent, renderInteractiveForms);
+ return annotationsReadyPromise.then(function () {
+ pageOpList.flush(true);
+ return pageOpList;
+ });
+ });
+ },
+ extractTextContent: function Page_extractTextContent(task, normalizeWhitespace, combineTextItems) {
+ var handler = {
+ on: function nullHandlerOn() {
+ },
+ send: function nullHandlerSend() {
+ }
+ };
+ var self = this;
+ var pdfManager = this.pdfManager;
+ var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', []);
+ var resourcesPromise = this.loadResources([
+ 'ExtGState',
+ 'XObject',
+ 'Font'
+ ]);
+ var dataPromises = Promise.all([
+ contentStreamPromise,
+ resourcesPromise
+ ]);
+ return dataPromises.then(function (data) {
+ var contentStream = data[0];
+ var partialEvaluator = new PartialEvaluator(pdfManager, self.xref, handler, self.pageIndex, self.uniquePrefix, self.idCounters, self.fontCache, self.evaluatorOptions);
+ return partialEvaluator.getTextContent(contentStream, task, self.resources, /* stateManager = */
+ null, normalizeWhitespace, combineTextItems);
+ });
+ },
+ getAnnotationsData: function Page_getAnnotationsData(intent) {
+ var annotations = this.annotations;
+ var annotationsData = [];
+ for (var i = 0, n = annotations.length; i < n; ++i) {
+ if (intent) {
+ if (!(intent === 'display' && annotations[i].viewable) && !(intent === 'print' && annotations[i].printable)) {
+ continue;
+ }
+ }
+ annotationsData.push(annotations[i].data);
+ }
+ return annotationsData;
+ },
+ get annotations() {
+ var annotations = [];
+ var annotationRefs = this.getInheritedPageProp('Annots') || [];
+ var annotationFactory = new AnnotationFactory();
+ for (var i = 0, n = annotationRefs.length; i < n; ++i) {
+ var annotationRef = annotationRefs[i];
+ var annotation = annotationFactory.create(this.xref, annotationRef, this.pdfManager, this.uniquePrefix, this.idCounters);
+ if (annotation) {
+ annotations.push(annotation);
+ }
+ }
+ return shadow(this, 'annotations', annotations);
+ }
+ };
+ return Page;
+ }();
+ /**
+ * The `PDFDocument` holds all the data of the PDF file. Compared to the
+ * `PDFDoc`, this one doesn't have any job management code.
+ * Right now there exists one PDFDocument on the main thread + one object
+ * for each worker. If there is no worker support enabled, there are two
+ * `PDFDocument` objects on the main thread created.
+ */
+ var PDFDocument = function PDFDocumentClosure() {
+ var FINGERPRINT_FIRST_BYTES = 1024;
+ var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' + '\x00\x00\x00\x00\x00\x00\x00\x00\x00';
+ function PDFDocument(pdfManager, arg, password) {
+ if (isStream(arg)) {
+ init.call(this, pdfManager, arg, password);
+ } else if (isArrayBuffer(arg)) {
+ init.call(this, pdfManager, new Stream(arg), password);
+ } else {
+ error('PDFDocument: Unknown argument type');
+ }
+ }
+ function init(pdfManager, stream, password) {
+ assert(stream.length > 0, 'stream must have data');
+ this.pdfManager = pdfManager;
+ this.stream = stream;
+ var xref = new XRef(this.stream, password, pdfManager);
+ this.xref = xref;
+ }
+ function find(stream, needle, limit, backwards) {
+ var pos = stream.pos;
+ var end = stream.end;
+ var strBuf = [];
+ if (pos + limit > end) {
+ limit = end - pos;
+ }
+ for (var n = 0; n < limit; ++n) {
+ strBuf.push(String.fromCharCode(stream.getByte()));
+ }
+ var str = strBuf.join('');
+ stream.pos = pos;
+ var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
+ if (index === -1) {
+ return false;
+ }
+ /* not found */
+ stream.pos += index;
+ return true;
+ }
+ /* found */
+ var DocumentInfoValidators = {
+ get entries() {
+ // Lazily build this since all the validation functions below are not
+ // defined until after this file loads.
+ return shadow(this, 'entries', {
+ Title: isString,
+ Author: isString,
+ Subject: isString,
+ Keywords: isString,
+ Creator: isString,
+ Producer: isString,
+ CreationDate: isString,
+ ModDate: isString,
+ Trapped: isName
+ });
+ }
+ };
+ PDFDocument.prototype = {
+ parse: function PDFDocument_parse(recoveryMode) {
+ this.setup(recoveryMode);
+ var version = this.catalog.catDict.get('Version');
+ if (isName(version)) {
+ this.pdfFormatVersion = version.name;
+ }
+ try {
+ // checking if AcroForm is present
+ this.acroForm = this.catalog.catDict.get('AcroForm');
+ if (this.acroForm) {
+ this.xfa = this.acroForm.get('XFA');
+ var fields = this.acroForm.get('Fields');
+ if ((!fields || !isArray(fields) || fields.length === 0) && !this.xfa) {
+ // no fields and no XFA -- not a form (?)
+ this.acroForm = null;
+ }
+ }
+ } catch (ex) {
+ info('Something wrong with AcroForm entry');
+ this.acroForm = null;
+ }
+ },
+ get linearization() {
+ var linearization = null;
+ if (this.stream.length) {
+ try {
+ linearization = Linearization.create(this.stream);
+ } catch (err) {
+ if (err instanceof MissingDataException) {
+ throw err;
+ }
+ info(err);
+ }
+ }
+ // shadow the prototype getter with a data property
+ return shadow(this, 'linearization', linearization);
+ },
+ get startXRef() {
+ var stream = this.stream;
+ var startXRef = 0;
+ var linearization = this.linearization;
+ if (linearization) {
+ // Find end of first obj.
+ stream.reset();
+ if (find(stream, 'endobj', 1024)) {
+ startXRef = stream.pos + 6;
+ }
+ } else {
+ // Find startxref by jumping backward from the end of the file.
+ var step = 1024;
+ var found = false, pos = stream.end;
+ while (!found && pos > 0) {
+ pos -= step - 'startxref'.length;
+ if (pos < 0) {
+ pos = 0;
+ }
+ stream.pos = pos;
+ found = find(stream, 'startxref', step, true);
+ }
+ if (found) {
+ stream.skip(9);
+ var ch;
+ do {
+ ch = stream.getByte();
+ } while (isSpace(ch));
+ var str = '';
+ while (ch >= 0x20 && ch <= 0x39) {
+ // < '9'
+ str += String.fromCharCode(ch);
+ ch = stream.getByte();
+ }
+ startXRef = parseInt(str, 10);
+ if (isNaN(startXRef)) {
+ startXRef = 0;
+ }
+ }
+ }
+ // shadow the prototype getter with a data property
+ return shadow(this, 'startXRef', startXRef);
+ },
+ get mainXRefEntriesOffset() {
+ var mainXRefEntriesOffset = 0;
+ var linearization = this.linearization;
+ if (linearization) {
+ mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
+ }
+ // shadow the prototype getter with a data property
+ return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
+ },
+ // Find the header, remove leading garbage and setup the stream
+ // starting from the header.
+ checkHeader: function PDFDocument_checkHeader() {
+ var stream = this.stream;
+ stream.reset();
+ if (find(stream, '%PDF-', 1024)) {
+ // Found the header, trim off any garbage before it.
+ stream.moveStart();
+ // Reading file format version
+ var MAX_VERSION_LENGTH = 12;
+ var version = '', ch;
+ while ((ch = stream.getByte()) > 0x20) {
+ // SPACE
+ if (version.length >= MAX_VERSION_LENGTH) {
+ break;
+ }
+ version += String.fromCharCode(ch);
+ }
+ if (!this.pdfFormatVersion) {
+ // removing "%PDF-"-prefix
+ this.pdfFormatVersion = version.substring(5);
+ }
+ return;
+ }
+ },
+ // May not be a PDF file, continue anyway.
+ parseStartXRef: function PDFDocument_parseStartXRef() {
+ var startXRef = this.startXRef;
+ this.xref.setStartXRef(startXRef);
+ },
+ setup: function PDFDocument_setup(recoveryMode) {
+ this.xref.parse(recoveryMode);
+ var self = this;
+ var pageFactory = {
+ createPage: function (pageIndex, dict, ref, fontCache) {
+ return new Page(self.pdfManager, self.xref, pageIndex, dict, ref, fontCache);
+ }
+ };
+ this.catalog = new Catalog(this.pdfManager, this.xref, pageFactory);
+ },
+ get numPages() {
+ var linearization = this.linearization;
+ var num = linearization ? linearization.numPages : this.catalog.numPages;
+ // shadow the prototype getter
+ return shadow(this, 'numPages', num);
+ },
+ get documentInfo() {
+ var docInfo = {
+ PDFFormatVersion: this.pdfFormatVersion,
+ IsAcroFormPresent: !!this.acroForm,
+ IsXFAPresent: !!this.xfa
+ };
+ var infoDict;
+ try {
+ infoDict = this.xref.trailer.get('Info');
+ } catch (err) {
+ info('The document information dictionary is invalid.');
+ }
+ if (infoDict) {
+ var validEntries = DocumentInfoValidators.entries;
+ // Only fill the document info with valid entries from the spec.
+ for (var key in validEntries) {
+ if (infoDict.has(key)) {
+ var value = infoDict.get(key);
+ // Make sure the value conforms to the spec.
+ if (validEntries[key](value)) {
+ docInfo[key] = typeof value !== 'string' ? value : stringToPDFString(value);
+ } else {
+ info('Bad value in document info for "' + key + '"');
+ }
+ }
+ }
+ }
+ return shadow(this, 'documentInfo', docInfo);
+ },
+ get fingerprint() {
+ var xref = this.xref, hash, fileID = '';
+ var idArray = xref.trailer.get('ID');
+ if (idArray && isArray(idArray) && idArray[0] && isString(idArray[0]) && idArray[0] !== EMPTY_FINGERPRINT) {
+ hash = stringToBytes(idArray[0]);
+ } else {
+ if (this.stream.ensureRange) {
+ this.stream.ensureRange(0, Math.min(FINGERPRINT_FIRST_BYTES, this.stream.end));
+ }
+ hash = calculateMD5(this.stream.bytes.subarray(0, FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES);
+ }
+ for (var i = 0, n = hash.length; i < n; i++) {
+ var hex = hash[i].toString(16);
+ fileID += hex.length === 1 ? '0' + hex : hex;
+ }
+ return shadow(this, 'fingerprint', fileID);
+ },
+ getPage: function PDFDocument_getPage(pageIndex) {
+ return this.catalog.getPage(pageIndex);
+ },
+ cleanup: function PDFDocument_cleanup() {
+ return this.catalog.cleanup();
+ }
+ };
+ return PDFDocument;
+ }();
+ exports.Page = Page;
+ exports.PDFDocument = PDFDocument;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCorePdfManager = {}, root.pdfjsSharedUtil, root.pdfjsCoreStream, root.pdfjsCoreChunkedStream, root.pdfjsCoreDocument);
+ }(this, function (exports, sharedUtil, coreStream, coreChunkedStream, coreDocument) {
+ var warn = sharedUtil.warn;
+ var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
+ var shadow = sharedUtil.shadow;
+ var NotImplementedException = sharedUtil.NotImplementedException;
+ var MissingDataException = sharedUtil.MissingDataException;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var Util = sharedUtil.Util;
+ var Stream = coreStream.Stream;
+ var ChunkedStreamManager = coreChunkedStream.ChunkedStreamManager;
+ var PDFDocument = coreDocument.PDFDocument;
+ var BasePdfManager = function BasePdfManagerClosure() {
+ function BasePdfManager() {
+ throw new Error('Cannot initialize BaseManagerManager');
+ }
+ BasePdfManager.prototype = {
+ get docId() {
+ return this._docId;
+ },
+ get docBaseUrl() {
+ var docBaseUrl = null;
+ if (this._docBaseUrl) {
+ var absoluteUrl = createValidAbsoluteUrl(this._docBaseUrl);
+ if (absoluteUrl) {
+ docBaseUrl = absoluteUrl.href;
+ } else {
+ warn('Invalid absolute docBaseUrl: "' + this._docBaseUrl + '".');
+ }
+ }
+ return shadow(this, 'docBaseUrl', docBaseUrl);
+ },
+ onLoadedStream: function BasePdfManager_onLoadedStream() {
+ throw new NotImplementedException();
+ },
+ ensureDoc: function BasePdfManager_ensureDoc(prop, args) {
+ return this.ensure(this.pdfDocument, prop, args);
+ },
+ ensureXRef: function BasePdfManager_ensureXRef(prop, args) {
+ return this.ensure(this.pdfDocument.xref, prop, args);
+ },
+ ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) {
+ return this.ensure(this.pdfDocument.catalog, prop, args);
+ },
+ getPage: function BasePdfManager_getPage(pageIndex) {
+ return this.pdfDocument.getPage(pageIndex);
+ },
+ cleanup: function BasePdfManager_cleanup() {
+ return this.pdfDocument.cleanup();
+ },
+ ensure: function BasePdfManager_ensure(obj, prop, args) {
+ return new NotImplementedException();
+ },
+ requestRange: function BasePdfManager_requestRange(begin, end) {
+ return new NotImplementedException();
+ },
+ requestLoadedStream: function BasePdfManager_requestLoadedStream() {
+ return new NotImplementedException();
+ },
+ sendProgressiveData: function BasePdfManager_sendProgressiveData(chunk) {
+ return new NotImplementedException();
+ },
+ updatePassword: function BasePdfManager_updatePassword(password) {
+ this.pdfDocument.xref.password = this.password = password;
+ if (this._passwordChangedCapability) {
+ this._passwordChangedCapability.resolve();
+ }
+ },
+ passwordChanged: function BasePdfManager_passwordChanged() {
+ this._passwordChangedCapability = createPromiseCapability();
+ return this._passwordChangedCapability.promise;
+ },
+ terminate: function BasePdfManager_terminate() {
+ return new NotImplementedException();
+ }
+ };
+ return BasePdfManager;
+ }();
+ var LocalPdfManager = function LocalPdfManagerClosure() {
+ function LocalPdfManager(docId, data, password, evaluatorOptions, docBaseUrl) {
+ this._docId = docId;
+ this._docBaseUrl = docBaseUrl;
+ this.evaluatorOptions = evaluatorOptions;
+ var stream = new Stream(data);
+ this.pdfDocument = new PDFDocument(this, stream, password);
+ this._loadedStreamCapability = createPromiseCapability();
+ this._loadedStreamCapability.resolve(stream);
+ }
+ Util.inherit(LocalPdfManager, BasePdfManager, {
+ ensure: function LocalPdfManager_ensure(obj, prop, args) {
+ return new Promise(function (resolve, reject) {
+ try {
+ var value = obj[prop];
+ var result;
+ if (typeof value === 'function') {
+ result = value.apply(obj, args);
+ } else {
+ result = value;
+ }
+ resolve(result);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ },
+ requestRange: function LocalPdfManager_requestRange(begin, end) {
+ return Promise.resolve();
+ },
+ requestLoadedStream: function LocalPdfManager_requestLoadedStream() {
+ return;
+ },
+ onLoadedStream: function LocalPdfManager_onLoadedStream() {
+ return this._loadedStreamCapability.promise;
+ },
+ terminate: function LocalPdfManager_terminate() {
+ return;
+ }
+ });
+ return LocalPdfManager;
+ }();
+ var NetworkPdfManager = function NetworkPdfManagerClosure() {
+ function NetworkPdfManager(docId, pdfNetworkStream, args, evaluatorOptions, docBaseUrl) {
+ this._docId = docId;
+ this._docBaseUrl = docBaseUrl;
+ this.msgHandler = args.msgHandler;
+ this.evaluatorOptions = evaluatorOptions;
+ var params = {
+ msgHandler: args.msgHandler,
+ url: args.url,
+ length: args.length,
+ disableAutoFetch: args.disableAutoFetch,
+ rangeChunkSize: args.rangeChunkSize
+ };
+ this.streamManager = new ChunkedStreamManager(pdfNetworkStream, params);
+ this.pdfDocument = new PDFDocument(this, this.streamManager.getStream(), args.password);
+ }
+ Util.inherit(NetworkPdfManager, BasePdfManager, {
+ ensure: function NetworkPdfManager_ensure(obj, prop, args) {
+ var pdfManager = this;
+ return new Promise(function (resolve, reject) {
+ function ensureHelper() {
+ try {
+ var result;
+ var value = obj[prop];
+ if (typeof value === 'function') {
+ result = value.apply(obj, args);
+ } else {
+ result = value;
+ }
+ resolve(result);
+ } catch (e) {
+ if (!(e instanceof MissingDataException)) {
+ reject(e);
+ return;
+ }
+ pdfManager.streamManager.requestRange(e.begin, e.end).then(ensureHelper, reject);
+ }
+ }
+ ensureHelper();
+ });
+ },
+ requestRange: function NetworkPdfManager_requestRange(begin, end) {
+ return this.streamManager.requestRange(begin, end);
+ },
+ requestLoadedStream: function NetworkPdfManager_requestLoadedStream() {
+ this.streamManager.requestAllChunks();
+ },
+ sendProgressiveData: function NetworkPdfManager_sendProgressiveData(chunk) {
+ this.streamManager.onReceiveData({ chunk: chunk });
+ },
+ onLoadedStream: function NetworkPdfManager_onLoadedStream() {
+ return this.streamManager.onLoadedStream();
+ },
+ terminate: function NetworkPdfManager_terminate() {
+ this.streamManager.abort();
+ }
+ });
+ return NetworkPdfManager;
+ }();
+ exports.LocalPdfManager = LocalPdfManager;
+ exports.NetworkPdfManager = NetworkPdfManager;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsCoreWorker = {}, root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCorePdfManager);
+ }(this, function (exports, sharedUtil, corePrimitives, corePdfManager) {
+ var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
+ var InvalidPDFException = sharedUtil.InvalidPDFException;
+ var MessageHandler = sharedUtil.MessageHandler;
+ var MissingPDFException = sharedUtil.MissingPDFException;
+ var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
+ var PasswordException = sharedUtil.PasswordException;
+ var PasswordResponses = sharedUtil.PasswordResponses;
+ var UnknownErrorException = sharedUtil.UnknownErrorException;
+ var XRefParseException = sharedUtil.XRefParseException;
+ var arrayByteLength = sharedUtil.arrayByteLength;
+ var arraysToBytes = sharedUtil.arraysToBytes;
+ var assert = sharedUtil.assert;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var error = sharedUtil.error;
+ var info = sharedUtil.info;
+ var warn = sharedUtil.warn;
+ var setVerbosityLevel = sharedUtil.setVerbosityLevel;
+ var Ref = corePrimitives.Ref;
+ var LocalPdfManager = corePdfManager.LocalPdfManager;
+ var NetworkPdfManager = corePdfManager.NetworkPdfManager;
+ var globalScope = sharedUtil.globalScope;
+ var WorkerTask = function WorkerTaskClosure() {
+ function WorkerTask(name) {
+ this.name = name;
+ this.terminated = false;
+ this._capability = createPromiseCapability();
+ }
+ WorkerTask.prototype = {
+ get finished() {
+ return this._capability.promise;
+ },
+ finish: function () {
+ this._capability.resolve();
+ },
+ terminate: function () {
+ this.terminated = true;
+ },
+ ensureNotTerminated: function () {
+ if (this.terminated) {
+ throw new Error('Worker task was terminated');
+ }
+ }
+ };
+ return WorkerTask;
+ }();
+ /** @implements {IPDFStream} */
+ var PDFWorkerStream = function PDFWorkerStreamClosure() {
+ function PDFWorkerStream(params, msgHandler) {
+ this._queuedChunks = [];
+ var initialData = params.initialData;
+ if (initialData && initialData.length > 0) {
+ this._queuedChunks.push(initialData);
+ }
+ this._msgHandler = msgHandler;
+ this._isRangeSupported = !params.disableRange;
+ this._isStreamingSupported = !params.disableStream;
+ this._contentLength = params.length;
+ this._fullRequestReader = null;
+ this._rangeReaders = [];
+ msgHandler.on('OnDataRange', this._onReceiveData.bind(this));
+ msgHandler.on('OnDataProgress', this._onProgress.bind(this));
+ }
+ PDFWorkerStream.prototype = {
+ _onReceiveData: function PDFWorkerStream_onReceiveData(args) {
+ if (args.begin === undefined) {
+ if (this._fullRequestReader) {
+ this._fullRequestReader._enqueue(args.chunk);
+ } else {
+ this._queuedChunks.push(args.chunk);
+ }
+ } else {
+ var found = this._rangeReaders.some(function (rangeReader) {
+ if (rangeReader._begin !== args.begin) {
+ return false;
+ }
+ rangeReader._enqueue(args.chunk);
+ return true;
+ });
+ assert(found);
+ }
+ },
+ _onProgress: function PDFWorkerStream_onProgress(evt) {
+ if (this._rangeReaders.length > 0) {
+ // Reporting to first range reader.
+ var firstReader = this._rangeReaders[0];
+ if (firstReader.onProgress) {
+ firstReader.onProgress({ loaded: evt.loaded });
+ }
+ }
+ },
+ _removeRangeReader: function PDFWorkerStream_removeRangeReader(reader) {
+ var i = this._rangeReaders.indexOf(reader);
+ if (i >= 0) {
+ this._rangeReaders.splice(i, 1);
+ }
+ },
+ getFullReader: function PDFWorkerStream_getFullReader() {
+ assert(!this._fullRequestReader);
+ var queuedChunks = this._queuedChunks;
+ this._queuedChunks = null;
+ return new PDFWorkerStreamReader(this, queuedChunks);
+ },
+ getRangeReader: function PDFWorkerStream_getRangeReader(begin, end) {
+ var reader = new PDFWorkerStreamRangeReader(this, begin, end);
+ this._msgHandler.send('RequestDataRange', {
+ begin: begin,
+ end: end
+ });
+ this._rangeReaders.push(reader);
+ return reader;
+ },
+ cancelAllRequests: function PDFWorkerStream_cancelAllRequests(reason) {
+ if (this._fullRequestReader) {
+ this._fullRequestReader.cancel(reason);
+ }
+ var readers = this._rangeReaders.slice(0);
+ readers.forEach(function (rangeReader) {
+ rangeReader.cancel(reason);
+ });
+ }
+ };
+ /** @implements {IPDFStreamReader} */
+ function PDFWorkerStreamReader(stream, queuedChunks) {
+ this._stream = stream;
+ this._done = false;
+ this._queuedChunks = queuedChunks || [];
+ this._requests = [];
+ this._headersReady = Promise.resolve();
+ stream._fullRequestReader = this;
+ this.onProgress = null;
+ }
+ // not used
+ PDFWorkerStreamReader.prototype = {
+ _enqueue: function PDFWorkerStreamReader_enqueue(chunk) {
+ if (this._done) {
+ return;
+ }
+ // ignore new data
+ if (this._requests.length > 0) {
+ var requestCapability = this._requests.shift();
+ requestCapability.resolve({
+ value: chunk,
+ done: false
+ });
+ return;
+ }
+ this._queuedChunks.push(chunk);
+ },
+ get headersReady() {
+ return this._headersReady;
+ },
+ get isRangeSupported() {
+ return this._stream._isRangeSupported;
+ },
+ get isStreamingSupported() {
+ return this._stream._isStreamingSupported;
+ },
+ get contentLength() {
+ return this._stream._contentLength;
+ },
+ read: function PDFWorkerStreamReader_read() {
+ if (this._queuedChunks.length > 0) {
+ var chunk = this._queuedChunks.shift();
+ return Promise.resolve({
+ value: chunk,
+ done: false
+ });
+ }
+ if (this._done) {
+ return Promise.resolve({
+ value: undefined,
+ done: true
+ });
+ }
+ var requestCapability = createPromiseCapability();
+ this._requests.push(requestCapability);
+ return requestCapability.promise;
+ },
+ cancel: function PDFWorkerStreamReader_cancel(reason) {
+ this._done = true;
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.resolve({
+ value: undefined,
+ done: true
+ });
+ });
+ this._requests = [];
+ }
+ };
+ /** @implements {IPDFStreamRangeReader} */
+ function PDFWorkerStreamRangeReader(stream, begin, end) {
+ this._stream = stream;
+ this._begin = begin;
+ this._end = end;
+ this._queuedChunk = null;
+ this._requests = [];
+ this._done = false;
+ this.onProgress = null;
+ }
+ PDFWorkerStreamRangeReader.prototype = {
+ _enqueue: function PDFWorkerStreamRangeReader_enqueue(chunk) {
+ if (this._done) {
+ return;
+ }
+ // ignore new data
+ if (this._requests.length === 0) {
+ this._queuedChunk = chunk;
+ } else {
+ var requestsCapability = this._requests.shift();
+ requestsCapability.resolve({
+ value: chunk,
+ done: false
+ });
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.resolve({
+ value: undefined,
+ done: true
+ });
+ });
+ this._requests = [];
+ }
+ this._done = true;
+ this._stream._removeRangeReader(this);
+ },
+ get isStreamingSupported() {
+ return false;
+ },
+ read: function PDFWorkerStreamRangeReader_read() {
+ if (this._queuedChunk) {
+ return Promise.resolve({
+ value: this._queuedChunk,
+ done: false
+ });
+ }
+ if (this._done) {
+ return Promise.resolve({
+ value: undefined,
+ done: true
+ });
+ }
+ var requestCapability = createPromiseCapability();
+ this._requests.push(requestCapability);
+ return requestCapability.promise;
+ },
+ cancel: function PDFWorkerStreamRangeReader_cancel(reason) {
+ this._done = true;
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.resolve({
+ value: undefined,
+ done: true
+ });
+ });
+ this._requests = [];
+ this._stream._removeRangeReader(this);
+ }
+ };
+ return PDFWorkerStream;
+ }();
+ /** @type IPDFStream */
+ var PDFNetworkStream;
+ /**
+ * Sets PDFNetworkStream class to be used as alternative PDF data transport.
+ * @param {IPDFStream} cls - the PDF data transport.
+ */
+ function setPDFNetworkStreamClass(cls) {
+ PDFNetworkStream = cls;
+ }
+ var WorkerMessageHandler = {
+ setup: function wphSetup(handler, port) {
+ var testMessageProcessed = false;
+ handler.on('test', function wphSetupTest(data) {
+ if (testMessageProcessed) {
+ return;
+ }
+ // we already processed 'test' message once
+ testMessageProcessed = true;
+ // check if Uint8Array can be sent to worker
+ if (!(data instanceof Uint8Array)) {
+ handler.send('test', 'main', false);
+ return;
+ }
+ // making sure postMessage transfers are working
+ var supportTransfers = data[0] === 255;
+ handler.postMessageTransfers = supportTransfers;
+ // check if the response property is supported by xhr
+ var xhr = new XMLHttpRequest();
+ var responseExists = 'response' in xhr;
+ // check if the property is actually implemented
+ try {
+ var dummy = xhr.responseType;
+ } catch (e) {
+ responseExists = false;
+ }
+ if (!responseExists) {
+ handler.send('test', false);
+ return;
+ }
+ handler.send('test', {
+ supportTypedArray: true,
+ supportTransfers: supportTransfers
+ });
+ });
+ handler.on('configure', function wphConfigure(data) {
+ setVerbosityLevel(data.verbosity);
+ });
+ handler.on('GetDocRequest', function wphSetupDoc(data) {
+ return WorkerMessageHandler.createDocumentHandler(data, port);
+ });
+ },
+ createDocumentHandler: function wphCreateDocumentHandler(docParams, port) {
+ // This context is actually holds references on pdfManager and handler,
+ // until the latter is destroyed.
+ var pdfManager;
+ var terminated = false;
+ var cancelXHRs = null;
+ var WorkerTasks = [];
+ var docId = docParams.docId;
+ var docBaseUrl = docParams.docBaseUrl;
+ var workerHandlerName = docParams.docId + '_worker';
+ var handler = new MessageHandler(workerHandlerName, docId, port);
+ // Ensure that postMessage transfers are correctly enabled/disabled,
+ // to prevent "DataCloneError" in older versions of IE (see issue 6957).
+ handler.postMessageTransfers = docParams.postMessageTransfers;
+ function ensureNotTerminated() {
+ if (terminated) {
+ throw new Error('Worker was terminated');
+ }
+ }
+ function startWorkerTask(task) {
+ WorkerTasks.push(task);
+ }
+ function finishWorkerTask(task) {
+ task.finish();
+ var i = WorkerTasks.indexOf(task);
+ WorkerTasks.splice(i, 1);
+ }
+ function loadDocument(recoveryMode) {
+ var loadDocumentCapability = createPromiseCapability();
+ var parseSuccess = function parseSuccess() {
+ var numPagesPromise = pdfManager.ensureDoc('numPages');
+ var fingerprintPromise = pdfManager.ensureDoc('fingerprint');
+ var encryptedPromise = pdfManager.ensureXRef('encrypt');
+ Promise.all([
+ numPagesPromise,
+ fingerprintPromise,
+ encryptedPromise
+ ]).then(function onDocReady(results) {
+ var doc = {
+ numPages: results[0],
+ fingerprint: results[1],
+ encrypted: !!results[2]
+ };
+ loadDocumentCapability.resolve(doc);
+ }, parseFailure);
+ };
+ var parseFailure = function parseFailure(e) {
+ loadDocumentCapability.reject(e);
+ };
+ pdfManager.ensureDoc('checkHeader', []).then(function () {
+ pdfManager.ensureDoc('parseStartXRef', []).then(function () {
+ pdfManager.ensureDoc('parse', [recoveryMode]).then(parseSuccess, parseFailure);
+ }, parseFailure);
+ }, parseFailure);
+ return loadDocumentCapability.promise;
+ }
+ function getPdfManager(data, evaluatorOptions) {
+ var pdfManagerCapability = createPromiseCapability();
+ var pdfManager;
+ var source = data.source;
+ if (source.data) {
+ try {
+ pdfManager = new LocalPdfManager(docId, source.data, source.password, evaluatorOptions, docBaseUrl);
+ pdfManagerCapability.resolve(pdfManager);
+ } catch (ex) {
+ pdfManagerCapability.reject(ex);
+ }
+ return pdfManagerCapability.promise;
+ }
+ var pdfStream;
+ try {
+ if (source.chunkedViewerLoading) {
+ pdfStream = new PDFWorkerStream(source, handler);
+ } else {
+ assert(PDFNetworkStream, 'pdfjs/core/network module is not loaded');
+ pdfStream = new PDFNetworkStream(data);
+ }
+ } catch (ex) {
+ pdfManagerCapability.reject(ex);
+ return pdfManagerCapability.promise;
+ }
+ var fullRequest = pdfStream.getFullReader();
+ fullRequest.headersReady.then(function () {
+ if (!fullRequest.isStreamingSupported || !fullRequest.isRangeSupported) {
+ // If stream or range are disabled, it's our only way to report
+ // loading progress.
+ fullRequest.onProgress = function (evt) {
+ handler.send('DocProgress', {
+ loaded: evt.loaded,
+ total: evt.total
+ });
+ };
+ }
+ if (!fullRequest.isRangeSupported) {
+ return;
+ }
+ // We don't need auto-fetch when streaming is enabled.
+ var disableAutoFetch = source.disableAutoFetch || fullRequest.isStreamingSupported;
+ pdfManager = new NetworkPdfManager(docId, pdfStream, {
+ msgHandler: handler,
+ url: source.url,
+ password: source.password,
+ length: fullRequest.contentLength,
+ disableAutoFetch: disableAutoFetch,
+ rangeChunkSize: source.rangeChunkSize
+ }, evaluatorOptions, docBaseUrl);
+ pdfManagerCapability.resolve(pdfManager);
+ cancelXHRs = null;
+ }).catch(function (reason) {
+ pdfManagerCapability.reject(reason);
+ cancelXHRs = null;
+ });
+ var cachedChunks = [], loaded = 0;
+ var flushChunks = function () {
+ var pdfFile = arraysToBytes(cachedChunks);
+ if (source.length && pdfFile.length !== source.length) {
+ warn('reported HTTP length is different from actual');
+ }
+ // the data is array, instantiating directly from it
+ try {
+ pdfManager = new LocalPdfManager(docId, pdfFile, source.password, evaluatorOptions, docBaseUrl);
+ pdfManagerCapability.resolve(pdfManager);
+ } catch (ex) {
+ pdfManagerCapability.reject(ex);
+ }
+ cachedChunks = [];
+ };
+ var readPromise = new Promise(function (resolve, reject) {
+ var readChunk = function (chunk) {
+ try {
+ ensureNotTerminated();
+ if (chunk.done) {
+ if (!pdfManager) {
+ flushChunks();
+ }
+ cancelXHRs = null;
+ return;
+ }
+ var data = chunk.value;
+ loaded += arrayByteLength(data);
+ if (!fullRequest.isStreamingSupported) {
+ handler.send('DocProgress', {
+ loaded: loaded,
+ total: Math.max(loaded, fullRequest.contentLength || 0)
+ });
+ }
+ if (pdfManager) {
+ pdfManager.sendProgressiveData(data);
+ } else {
+ cachedChunks.push(data);
+ }
+ fullRequest.read().then(readChunk, reject);
+ } catch (e) {
+ reject(e);
+ }
+ };
+ fullRequest.read().then(readChunk, reject);
+ });
+ readPromise.catch(function (e) {
+ pdfManagerCapability.reject(e);
+ cancelXHRs = null;
+ });
+ cancelXHRs = function () {
+ pdfStream.cancelAllRequests('abort');
+ };
+ return pdfManagerCapability.promise;
+ }
+ var setupDoc = function (data) {
+ var onSuccess = function (doc) {
+ ensureNotTerminated();
+ handler.send('GetDoc', { pdfInfo: doc });
+ };
+ var onFailure = function (e) {
+ if (e instanceof PasswordException) {
+ if (e.code === PasswordResponses.NEED_PASSWORD) {
+ handler.send('NeedPassword', e);
+ } else if (e.code === PasswordResponses.INCORRECT_PASSWORD) {
+ handler.send('IncorrectPassword', e);
+ }
+ } else if (e instanceof InvalidPDFException) {
+ handler.send('InvalidPDF', e);
+ } else if (e instanceof MissingPDFException) {
+ handler.send('MissingPDF', e);
+ } else if (e instanceof UnexpectedResponseException) {
+ handler.send('UnexpectedResponse', e);
+ } else {
+ handler.send('UnknownError', new UnknownErrorException(e.message, e.toString()));
+ }
+ };
+ ensureNotTerminated();
+ var cMapOptions = {
+ url: data.cMapUrl === undefined ? null : data.cMapUrl,
+ packed: data.cMapPacked === true
+ };
+ var evaluatorOptions = {
+ forceDataSchema: data.disableCreateObjectURL,
+ maxImageSize: data.maxImageSize === undefined ? -1 : data.maxImageSize,
+ disableFontFace: data.disableFontFace,
+ cMapOptions: cMapOptions
+ };
+ getPdfManager(data, evaluatorOptions).then(function (newPdfManager) {
+ if (terminated) {
+ // We were in a process of setting up the manager, but it got
+ // terminated in the middle.
+ newPdfManager.terminate();
+ throw new Error('Worker was terminated');
+ }
+ pdfManager = newPdfManager;
+ handler.send('PDFManagerReady', null);
+ pdfManager.onLoadedStream().then(function (stream) {
+ handler.send('DataLoaded', { length: stream.bytes.byteLength });
+ });
+ }).then(function pdfManagerReady() {
+ ensureNotTerminated();
+ loadDocument(false).then(onSuccess, function loadFailure(ex) {
+ ensureNotTerminated();
+ // Try again with recoveryMode == true
+ if (!(ex instanceof XRefParseException)) {
+ if (ex instanceof PasswordException) {
+ // after password exception prepare to receive a new password
+ // to repeat loading
+ pdfManager.passwordChanged().then(pdfManagerReady);
+ }
+ onFailure(ex);
+ return;
+ }
+ pdfManager.requestLoadedStream();
+ pdfManager.onLoadedStream().then(function () {
+ ensureNotTerminated();
+ loadDocument(true).then(onSuccess, onFailure);
+ });
+ }, onFailure);
+ }, onFailure);
+ };
+ handler.on('GetPage', function wphSetupGetPage(data) {
+ return pdfManager.getPage(data.pageIndex).then(function (page) {
+ var rotatePromise = pdfManager.ensure(page, 'rotate');
+ var refPromise = pdfManager.ensure(page, 'ref');
+ var viewPromise = pdfManager.ensure(page, 'view');
+ return Promise.all([
+ rotatePromise,
+ refPromise,
+ viewPromise
+ ]).then(function (results) {
+ return {
+ rotate: results[0],
+ ref: results[1],
+ view: results[2]
+ };
+ });
+ });
+ });
+ handler.on('GetPageIndex', function wphSetupGetPageIndex(data) {
+ var ref = new Ref(data.ref.num, data.ref.gen);
+ var catalog = pdfManager.pdfDocument.catalog;
+ return catalog.getPageIndex(ref);
+ });
+ handler.on('GetDestinations', function wphSetupGetDestinations(data) {
+ return pdfManager.ensureCatalog('destinations');
+ });
+ handler.on('GetDestination', function wphSetupGetDestination(data) {
+ return pdfManager.ensureCatalog('getDestination', [data.id]);
+ });
+ handler.on('GetPageLabels', function wphSetupGetPageLabels(data) {
+ return pdfManager.ensureCatalog('pageLabels');
+ });
+ handler.on('GetAttachments', function wphSetupGetAttachments(data) {
+ return pdfManager.ensureCatalog('attachments');
+ });
+ handler.on('GetJavaScript', function wphSetupGetJavaScript(data) {
+ return pdfManager.ensureCatalog('javaScript');
+ });
+ handler.on('GetOutline', function wphSetupGetOutline(data) {
+ return pdfManager.ensureCatalog('documentOutline');
+ });
+ handler.on('GetMetadata', function wphSetupGetMetadata(data) {
+ return Promise.all([
+ pdfManager.ensureDoc('documentInfo'),
+ pdfManager.ensureCatalog('metadata')
+ ]);
+ });
+ handler.on('GetData', function wphSetupGetData(data) {
+ pdfManager.requestLoadedStream();
+ return pdfManager.onLoadedStream().then(function (stream) {
+ return stream.bytes;
+ });
+ });
+ handler.on('GetStats', function wphSetupGetStats(data) {
+ return pdfManager.pdfDocument.xref.stats;
+ });
+ handler.on('UpdatePassword', function wphSetupUpdatePassword(data) {
+ pdfManager.updatePassword(data);
+ });
+ handler.on('GetAnnotations', function wphSetupGetAnnotations(data) {
+ return pdfManager.getPage(data.pageIndex).then(function (page) {
+ return pdfManager.ensure(page, 'getAnnotationsData', [data.intent]);
+ });
+ });
+ handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
+ var pageIndex = data.pageIndex;
+ pdfManager.getPage(pageIndex).then(function (page) {
+ var task = new WorkerTask('RenderPageRequest: page ' + pageIndex);
+ startWorkerTask(task);
+ var pageNum = pageIndex + 1;
+ var start = Date.now();
+ // Pre compile the pdf page and fetch the fonts/images.
+ page.getOperatorList(handler, task, data.intent, data.renderInteractiveForms).then(function (operatorList) {
+ finishWorkerTask(task);
+ info('page=' + pageNum + ' - getOperatorList: time=' + (Date.now() - start) + 'ms, len=' + operatorList.totalLength);
+ }, function (e) {
+ finishWorkerTask(task);
+ if (task.terminated) {
+ return;
+ }
+ // ignoring errors from the terminated thread
+ // For compatibility with older behavior, generating unknown
+ // unsupported feature notification on errors.
+ handler.send('UnsupportedFeature', { featureId: UNSUPPORTED_FEATURES.unknown });
+ var minimumStackMessage = 'worker.js: while trying to getPage() and getOperatorList()';
+ var wrappedException;
+ // Turn the error into an obj that can be serialized
+ if (typeof e === 'string') {
+ wrappedException = {
+ message: e,
+ stack: minimumStackMessage
+ };
+ } else if (typeof e === 'object') {
+ wrappedException = {
+ message: e.message || e.toString(),
+ stack: e.stack || minimumStackMessage
+ };
+ } else {
+ wrappedException = {
+ message: 'Unknown exception type: ' + typeof e,
+ stack: minimumStackMessage
+ };
+ }
+ handler.send('PageError', {
+ pageNum: pageNum,
+ error: wrappedException,
+ intent: data.intent
+ });
+ });
+ });
+ }, this);
+ handler.on('GetTextContent', function wphExtractText(data) {
+ var pageIndex = data.pageIndex;
+ var normalizeWhitespace = data.normalizeWhitespace;
+ var combineTextItems = data.combineTextItems;
+ return pdfManager.getPage(pageIndex).then(function (page) {
+ var task = new WorkerTask('GetTextContent: page ' + pageIndex);
+ startWorkerTask(task);
+ var pageNum = pageIndex + 1;
+ var start = Date.now();
+ return page.extractTextContent(task, normalizeWhitespace, combineTextItems).then(function (textContent) {
+ finishWorkerTask(task);
+ info('text indexing: page=' + pageNum + ' - time=' + (Date.now() - start) + 'ms');
+ return textContent;
+ }, function (reason) {
+ finishWorkerTask(task);
+ if (task.terminated) {
+ return;
+ }
+ // ignoring errors from the terminated thread
+ throw reason;
+ });
+ });
+ });
+ handler.on('Cleanup', function wphCleanup(data) {
+ return pdfManager.cleanup();
+ });
+ handler.on('Terminate', function wphTerminate(data) {
+ terminated = true;
+ if (pdfManager) {
+ pdfManager.terminate();
+ pdfManager = null;
+ }
+ if (cancelXHRs) {
+ cancelXHRs();
+ }
+ var waitOn = [];
+ WorkerTasks.forEach(function (task) {
+ waitOn.push(task.finished);
+ task.terminate();
+ });
+ return Promise.all(waitOn).then(function () {
+ // Notice that even if we destroying handler, resolved response promise
+ // must be sent back.
+ handler.destroy();
+ handler = null;
+ });
+ });
+ handler.on('Ready', function wphReady(data) {
+ setupDoc(docParams);
+ docParams = null;
+ });
+ // we don't need docParams anymore -- saving memory.
+ return workerHandlerName;
+ }
+ };
+ function initializeWorker() {
+ var handler = new MessageHandler('worker', 'main', self);
+ WorkerMessageHandler.setup(handler, self);
+ handler.send('ready', null);
+ }
+ // Worker thread (and not node.js)?
+ if (typeof window === 'undefined' && !(typeof module !== 'undefined' && module.require)) {
+ initializeWorker();
+ }
+ exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
+ exports.WorkerTask = WorkerTask;
+ exports.WorkerMessageHandler = WorkerMessageHandler;
+ }));
+ }.call(pdfjsLibs));
+ exports.WorkerMessageHandler = pdfjsLibs.pdfjsCoreWorker.WorkerMessageHandler;
+})); \ No newline at end of file
diff --git a/browser/extensions/pdfjs/content/network.js b/browser/extensions/pdfjs/content/network.js
new file mode 100644
index 000000000..68c8dd19f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/network.js
@@ -0,0 +1,614 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+
+// NOTE: Be careful what goes in this file, as it is also used from the context
+// of the addon. So using warn/error in here will break the addon.
+
+'use strict';
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define('pdfjs/core/network', ['exports', 'pdfjs/shared/util',
+ 'pdfjs/core/worker'], factory);
+ } else if (typeof exports !== 'undefined') {
+ factory(exports, require('../shared/util.js'), require('./worker.js'));
+ } else {
+ factory((root.pdfjsCoreNetwork = {}), root.pdfjsSharedUtil,
+ root.pdfjsCoreWorker);
+ }
+}(this, function (exports, sharedUtil, coreWorker) {
+if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
+ throw new Error('Module "pdfjs/core/network" shall not ' +
+ 'be used with FIREFOX or MOZCENTRAL build.');
+}
+
+ var OK_RESPONSE = 200;
+ var PARTIAL_CONTENT_RESPONSE = 206;
+
+ function NetworkManager(url, args) {
+ this.url = url;
+ args = args || {};
+ this.isHttp = /^https?:/i.test(url);
+ this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
+ this.withCredentials = args.withCredentials || false;
+ this.getXhr = args.getXhr ||
+ function NetworkManager_getXhr() {
+ return new XMLHttpRequest();
+ };
+
+ this.currXhrId = 0;
+ this.pendingRequests = Object.create(null);
+ this.loadedRequests = Object.create(null);
+ }
+
+ function getArrayBuffer(xhr) {
+ var data = xhr.response;
+ if (typeof data !== 'string') {
+ return data;
+ }
+ var length = data.length;
+ var array = new Uint8Array(length);
+ for (var i = 0; i < length; i++) {
+ array[i] = data.charCodeAt(i) & 0xFF;
+ }
+ return array.buffer;
+ }
+
+ var supportsMozChunked =
+ typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME') ? false :
+ (function supportsMozChunkedClosure() {
+ try {
+ var x = new XMLHttpRequest();
+ // Firefox 37- required .open() to be called before setting responseType.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=707484
+ // Even though the URL is not visited, .open() could fail if the URL is
+ // blocked, e.g. via the connect-src CSP directive or the NoScript addon.
+ // When this error occurs, this feature detection method will mistakenly
+ // report that moz-chunked-arraybuffer is not supported in Firefox 37-.
+ x.open('GET', 'https://example.com');
+ x.responseType = 'moz-chunked-arraybuffer';
+ return x.responseType === 'moz-chunked-arraybuffer';
+ } catch (e) {
+ return false;
+ }
+ })();
+
+ NetworkManager.prototype = {
+ requestRange: function NetworkManager_requestRange(begin, end, listeners) {
+ var args = {
+ begin: begin,
+ end: end
+ };
+ for (var prop in listeners) {
+ args[prop] = listeners[prop];
+ }
+ return this.request(args);
+ },
+
+ requestFull: function NetworkManager_requestFull(listeners) {
+ return this.request(listeners);
+ },
+
+ request: function NetworkManager_request(args) {
+ var xhr = this.getXhr();
+ var xhrId = this.currXhrId++;
+ var pendingRequest = this.pendingRequests[xhrId] = {
+ xhr: xhr
+ };
+
+ xhr.open('GET', this.url);
+ xhr.withCredentials = this.withCredentials;
+ for (var property in this.httpHeaders) {
+ var value = this.httpHeaders[property];
+ if (typeof value === 'undefined') {
+ continue;
+ }
+ xhr.setRequestHeader(property, value);
+ }
+ if (this.isHttp && 'begin' in args && 'end' in args) {
+ var rangeStr = args.begin + '-' + (args.end - 1);
+ xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
+ pendingRequest.expectedStatus = 206;
+ } else {
+ pendingRequest.expectedStatus = 200;
+ }
+
+ var useMozChunkedLoading = supportsMozChunked && !!args.onProgressiveData;
+ if (useMozChunkedLoading) {
+ xhr.responseType = 'moz-chunked-arraybuffer';
+ pendingRequest.onProgressiveData = args.onProgressiveData;
+ pendingRequest.mozChunked = true;
+ } else {
+ xhr.responseType = 'arraybuffer';
+ }
+
+ if (args.onError) {
+ xhr.onerror = function(evt) {
+ args.onError(xhr.status);
+ };
+ }
+ xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
+ xhr.onprogress = this.onProgress.bind(this, xhrId);
+
+ pendingRequest.onHeadersReceived = args.onHeadersReceived;
+ pendingRequest.onDone = args.onDone;
+ pendingRequest.onError = args.onError;
+ pendingRequest.onProgress = args.onProgress;
+
+ xhr.send(null);
+
+ return xhrId;
+ },
+
+ onProgress: function NetworkManager_onProgress(xhrId, evt) {
+ var pendingRequest = this.pendingRequests[xhrId];
+ if (!pendingRequest) {
+ // Maybe abortRequest was called...
+ return;
+ }
+
+ if (pendingRequest.mozChunked) {
+ var chunk = getArrayBuffer(pendingRequest.xhr);
+ pendingRequest.onProgressiveData(chunk);
+ }
+
+ var onProgress = pendingRequest.onProgress;
+ if (onProgress) {
+ onProgress(evt);
+ }
+ },
+
+ onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
+ var pendingRequest = this.pendingRequests[xhrId];
+ if (!pendingRequest) {
+ // Maybe abortRequest was called...
+ return;
+ }
+
+ var xhr = pendingRequest.xhr;
+ if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
+ pendingRequest.onHeadersReceived();
+ delete pendingRequest.onHeadersReceived;
+ }
+
+ if (xhr.readyState !== 4) {
+ return;
+ }
+
+ if (!(xhrId in this.pendingRequests)) {
+ // The XHR request might have been aborted in onHeadersReceived()
+ // callback, in which case we should abort request
+ return;
+ }
+
+ delete this.pendingRequests[xhrId];
+
+ // success status == 0 can be on ftp, file and other protocols
+ if (xhr.status === 0 && this.isHttp) {
+ if (pendingRequest.onError) {
+ pendingRequest.onError(xhr.status);
+ }
+ return;
+ }
+ var xhrStatus = xhr.status || OK_RESPONSE;
+
+ // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
+ // "A server MAY ignore the Range header". This means it's possible to
+ // get a 200 rather than a 206 response from a range request.
+ var ok_response_on_range_request =
+ xhrStatus === OK_RESPONSE &&
+ pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
+
+ if (!ok_response_on_range_request &&
+ xhrStatus !== pendingRequest.expectedStatus) {
+ if (pendingRequest.onError) {
+ pendingRequest.onError(xhr.status);
+ }
+ return;
+ }
+
+ this.loadedRequests[xhrId] = true;
+
+ var chunk = getArrayBuffer(xhr);
+ if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
+ var rangeHeader = xhr.getResponseHeader('Content-Range');
+ var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
+ var begin = parseInt(matches[1], 10);
+ pendingRequest.onDone({
+ begin: begin,
+ chunk: chunk
+ });
+ } else if (pendingRequest.onProgressiveData) {
+ pendingRequest.onDone(null);
+ } else if (chunk) {
+ pendingRequest.onDone({
+ begin: 0,
+ chunk: chunk
+ });
+ } else if (pendingRequest.onError) {
+ pendingRequest.onError(xhr.status);
+ }
+ },
+
+ hasPendingRequests: function NetworkManager_hasPendingRequests() {
+ for (var xhrId in this.pendingRequests) {
+ return true;
+ }
+ return false;
+ },
+
+ getRequestXhr: function NetworkManager_getXhr(xhrId) {
+ return this.pendingRequests[xhrId].xhr;
+ },
+
+ isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
+ return !!(this.pendingRequests[xhrId].onProgressiveData);
+ },
+
+ isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
+ return xhrId in this.pendingRequests;
+ },
+
+ isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
+ return xhrId in this.loadedRequests;
+ },
+
+ abortAllRequests: function NetworkManager_abortAllRequests() {
+ for (var xhrId in this.pendingRequests) {
+ this.abortRequest(xhrId | 0);
+ }
+ },
+
+ abortRequest: function NetworkManager_abortRequest(xhrId) {
+ var xhr = this.pendingRequests[xhrId].xhr;
+ delete this.pendingRequests[xhrId];
+ xhr.abort();
+ }
+ };
+
+ var assert = sharedUtil.assert;
+ var createPromiseCapability = sharedUtil.createPromiseCapability;
+ var isInt = sharedUtil.isInt;
+ var MissingPDFException = sharedUtil.MissingPDFException;
+ var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
+
+ /** @implements {IPDFStream} */
+ function PDFNetworkStream(options) {
+ this._options = options;
+ var source = options.source;
+ this._manager = new NetworkManager(source.url, {
+ httpHeaders: source.httpHeaders,
+ withCredentials: source.withCredentials
+ });
+ this._rangeChunkSize = source.rangeChunkSize;
+ this._fullRequestReader = null;
+ this._rangeRequestReaders = [];
+ }
+
+ PDFNetworkStream.prototype = {
+ _onRangeRequestReaderClosed:
+ function PDFNetworkStream_onRangeRequestReaderClosed(reader) {
+ var i = this._rangeRequestReaders.indexOf(reader);
+ if (i >= 0) {
+ this._rangeRequestReaders.splice(i, 1);
+ }
+ },
+
+ getFullReader: function PDFNetworkStream_getFullReader() {
+ assert(!this._fullRequestReader);
+ this._fullRequestReader =
+ new PDFNetworkStreamFullRequestReader(this._manager, this._options);
+ return this._fullRequestReader;
+ },
+
+ getRangeReader: function PDFNetworkStream_getRangeReader(begin, end) {
+ var reader = new PDFNetworkStreamRangeRequestReader(this._manager,
+ begin, end);
+ reader.onClosed = this._onRangeRequestReaderClosed.bind(this);
+ this._rangeRequestReaders.push(reader);
+ return reader;
+ },
+
+ cancelAllRequests: function PDFNetworkStream_cancelAllRequests(reason) {
+ if (this._fullRequestReader) {
+ this._fullRequestReader.cancel(reason);
+ }
+ var readers = this._rangeRequestReaders.slice(0);
+ readers.forEach(function (reader) {
+ reader.cancel(reason);
+ });
+ }
+ };
+
+ /** @implements {IPDFStreamReader} */
+ function PDFNetworkStreamFullRequestReader(manager, options) {
+ this._manager = manager;
+
+ var source = options.source;
+ var args = {
+ onHeadersReceived: this._onHeadersReceived.bind(this),
+ onProgressiveData: source.disableStream ? null :
+ this._onProgressiveData.bind(this),
+ onDone: this._onDone.bind(this),
+ onError: this._onError.bind(this),
+ onProgress: this._onProgress.bind(this)
+ };
+ this._url = source.url;
+ this._fullRequestId = manager.requestFull(args);
+ this._headersReceivedCapability = createPromiseCapability();
+ this._disableRange = options.disableRange || false;
+ this._contentLength = source.length; // optional
+ this._rangeChunkSize = source.rangeChunkSize;
+ if (!this._rangeChunkSize && !this._disableRange) {
+ this._disableRange = true;
+ }
+
+ this._isStreamingSupported = false;
+ this._isRangeSupported = false;
+
+ this._cachedChunks = [];
+ this._requests = [];
+ this._done = false;
+ this._storedError = undefined;
+
+ this.onProgress = null;
+ }
+
+ PDFNetworkStreamFullRequestReader.prototype = {
+ _validateRangeRequestCapabilities: function
+ PDFNetworkStreamFullRequestReader_validateRangeRequestCapabilities() {
+
+ if (this._disableRange) {
+ return false;
+ }
+
+ var networkManager = this._manager;
+ var fullRequestXhrId = this._fullRequestId;
+ var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
+ if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
+ return false;
+ }
+
+ var contentEncoding =
+ fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity';
+ if (contentEncoding !== 'identity') {
+ return false;
+ }
+
+ var length = fullRequestXhr.getResponseHeader('Content-Length');
+ length = parseInt(length, 10);
+ if (!isInt(length)) {
+ return false;
+ }
+
+ this._contentLength = length; // setting right content length
+
+ if (length <= 2 * this._rangeChunkSize) {
+ // The file size is smaller than the size of two chunks, so it does
+ // not make any sense to abort the request and retry with a range
+ // request.
+ return false;
+ }
+
+ return true;
+ },
+
+ _onHeadersReceived:
+ function PDFNetworkStreamFullRequestReader_onHeadersReceived() {
+
+ if (this._validateRangeRequestCapabilities()) {
+ this._isRangeSupported = true;
+ }
+
+ var networkManager = this._manager;
+ var fullRequestXhrId = this._fullRequestId;
+ if (networkManager.isStreamingRequest(fullRequestXhrId)) {
+ // We can continue fetching when progressive loading is enabled,
+ // and we don't need the autoFetch feature.
+ this._isStreamingSupported = true;
+ } else if (this._isRangeSupported) {
+ // NOTE: by cancelling the full request, and then issuing range
+ // requests, there will be an issue for sites where you can only
+ // request the pdf once. However, if this is the case, then the
+ // server should not be returning that it can support range
+ // requests.
+ networkManager.abortRequest(fullRequestXhrId);
+ }
+
+ this._headersReceivedCapability.resolve();
+ },
+
+ _onProgressiveData:
+ function PDFNetworkStreamFullRequestReader_onProgressiveData(chunk) {
+ if (this._requests.length > 0) {
+ var requestCapability = this._requests.shift();
+ requestCapability.resolve({value: chunk, done: false});
+ } else {
+ this._cachedChunks.push(chunk);
+ }
+ },
+
+ _onDone: function PDFNetworkStreamFullRequestReader_onDone(args) {
+ if (args) {
+ this._onProgressiveData(args.chunk);
+ }
+ this._done = true;
+ if (this._cachedChunks.length > 0) {
+ return;
+ }
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.resolve({value: undefined, done: true});
+ });
+ this._requests = [];
+ },
+
+ _onError: function PDFNetworkStreamFullRequestReader_onError(status) {
+ var url = this._url;
+ var exception;
+ if (status === 404 || status === 0 && /^file:/.test(url)) {
+ exception = new MissingPDFException('Missing PDF "' + url + '".');
+ } else {
+ exception = new UnexpectedResponseException(
+ 'Unexpected server response (' + status +
+ ') while retrieving PDF "' + url + '".', status);
+ }
+ this._storedError = exception;
+ this._headersReceivedCapability.reject(exception);
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.reject(exception);
+ });
+ this._requests = [];
+ this._cachedChunks = [];
+ },
+
+ _onProgress: function PDFNetworkStreamFullRequestReader_onProgress(data) {
+ if (this.onProgress) {
+ this.onProgress({
+ loaded: data.loaded,
+ total: data.lengthComputable ? data.total : this._contentLength
+ });
+ }
+ },
+
+ get isRangeSupported() {
+ return this._isRangeSupported;
+ },
+
+ get isStreamingSupported() {
+ return this._isStreamingSupported;
+ },
+
+ get contentLength() {
+ return this._contentLength;
+ },
+
+ get headersReady() {
+ return this._headersReceivedCapability.promise;
+ },
+
+ read: function PDFNetworkStreamFullRequestReader_read() {
+ if (this._storedError) {
+ return Promise.reject(this._storedError);
+ }
+ if (this._cachedChunks.length > 0) {
+ var chunk = this._cachedChunks.shift();
+ return Promise.resolve(chunk);
+ }
+ if (this._done) {
+ return Promise.resolve({value: undefined, done: true});
+ }
+ var requestCapability = createPromiseCapability();
+ this._requests.push(requestCapability);
+ return requestCapability.promise;
+ },
+
+ cancel: function PDFNetworkStreamFullRequestReader_cancel(reason) {
+ this._done = true;
+ this._headersReceivedCapability.reject(reason);
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.resolve({value: undefined, done: true});
+ });
+ this._requests = [];
+ if (this._manager.isPendingRequest(this._fullRequestId)) {
+ this._manager.abortRequest(this._fullRequestId);
+ }
+ this._fullRequestReader = null;
+ }
+ };
+
+ /** @implements {IPDFStreamRangeReader} */
+ function PDFNetworkStreamRangeRequestReader(manager, begin, end) {
+ this._manager = manager;
+ var args = {
+ onDone: this._onDone.bind(this),
+ onProgress: this._onProgress.bind(this)
+ };
+ this._requestId = manager.requestRange(begin, end, args);
+ this._requests = [];
+ this._queuedChunk = null;
+ this._done = false;
+
+ this.onProgress = null;
+ this.onClosed = null;
+ }
+
+ PDFNetworkStreamRangeRequestReader.prototype = {
+ _close: function PDFNetworkStreamRangeRequestReader_close() {
+ if (this.onClosed) {
+ this.onClosed(this);
+ }
+ },
+
+ _onDone: function PDFNetworkStreamRangeRequestReader_onDone(data) {
+ var chunk = data.chunk;
+ if (this._requests.length > 0) {
+ var requestCapability = this._requests.shift();
+ requestCapability.resolve({value: chunk, done: false});
+ } else {
+ this._queuedChunk = chunk;
+ }
+ this._done = true;
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.resolve({value: undefined, done: true});
+ });
+ this._requests = [];
+ this._close();
+ },
+
+ _onProgress: function PDFNetworkStreamRangeRequestReader_onProgress(evt) {
+ if (!this.isStreamingSupported && this.onProgress) {
+ this.onProgress({
+ loaded: evt.loaded
+ });
+ }
+ },
+
+ get isStreamingSupported() {
+ return false; // TODO allow progressive range bytes loading
+ },
+
+ read: function PDFNetworkStreamRangeRequestReader_read() {
+ if (this._queuedChunk !== null) {
+ var chunk = this._queuedChunk;
+ this._queuedChunk = null;
+ return Promise.resolve({value: chunk, done: false});
+ }
+ if (this._done) {
+ return Promise.resolve({value: undefined, done: true});
+ }
+ var requestCapability = createPromiseCapability();
+ this._requests.push(requestCapability);
+ return requestCapability.promise;
+ },
+
+ cancel: function PDFNetworkStreamRangeRequestReader_cancel(reason) {
+ this._done = true;
+ this._requests.forEach(function (requestCapability) {
+ requestCapability.resolve({value: undefined, done: true});
+ });
+ this._requests = [];
+ if (this._manager.isPendingRequest(this._requestId)) {
+ this._manager.abortRequest(this._requestId);
+ }
+ this._close();
+ }
+ };
+
+ coreWorker.setPDFNetworkStreamClass(PDFNetworkStream);
+
+ exports.PDFNetworkStream = PDFNetworkStream;
+ exports.NetworkManager = NetworkManager;
+}));
+
diff --git a/browser/extensions/pdfjs/content/pdfjschildbootstrap.js b/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
new file mode 100644
index 000000000..1f44b9c2e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
@@ -0,0 +1,36 @@
+/* Copyright 2014 Mozilla Foundation
+*
+* 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.
+*/
+/* jshint esnext:true */
+/* globals Components, PdfjsContentUtils, PdfJs, Services */
+
+'use strict';
+
+/*
+ * pdfjschildbootstrap.js loads into the content process to take care of
+ * initializing our built-in version of pdfjs when running remote.
+ */
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+Components.utils.import('resource://pdf.js/PdfJs.jsm');
+Components.utils.import('resource://pdf.js/PdfjsContentUtils.jsm');
+
+// init content utils shim pdfjs will use to access privileged apis.
+PdfjsContentUtils.init();
+
+if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+ // register various pdfjs factories that hook us into content loading.
+ PdfJs.updateRegistration();
+}
+
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78-EUC-H.bcmap
new file mode 100644
index 000000000..2655fc70a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78-EUC-V.bcmap
new file mode 100644
index 000000000..f1ed85382
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78-H.bcmap
new file mode 100644
index 000000000..39e89d333
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-H.bcmap
new file mode 100644
index 000000000..e4167cb51
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-V.bcmap
new file mode 100644
index 000000000..50b1646e9
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78-RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78-V.bcmap
new file mode 100644
index 000000000..d7af99b5e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-H.bcmap
new file mode 100644
index 000000000..37077d01e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-V.bcmap
new file mode 100644
index 000000000..acf23231a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/78ms-RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/83pv-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/83pv-RKSJ-H.bcmap
new file mode 100644
index 000000000..2359bc529
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/83pv-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-H.bcmap
new file mode 100644
index 000000000..af8293829
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-V.bcmap
new file mode 100644
index 000000000..780549de1
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/90ms-RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-H.bcmap
new file mode 100644
index 000000000..bfd3119c6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-V.bcmap
new file mode 100644
index 000000000..25ef14ab4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/90msp-RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-H.bcmap
new file mode 100644
index 000000000..02f713bb8
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-V.bcmap
new file mode 100644
index 000000000..d08e0cc5d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/90pv-RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Add-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Add-H.bcmap
new file mode 100644
index 000000000..59442acaf
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Add-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-H.bcmap
new file mode 100644
index 000000000..a3065e441
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-V.bcmap
new file mode 100644
index 000000000..040014cfc
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Add-RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Add-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Add-V.bcmap
new file mode 100644
index 000000000..2f816d320
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Add-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-0.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-0.bcmap
new file mode 100644
index 000000000..88ec04af4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-0.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-1.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-1.bcmap
new file mode 100644
index 000000000..03a501477
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-1.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-2.bcmap
new file mode 100644
index 000000000..2aa95141f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-3.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-3.bcmap
new file mode 100644
index 000000000..86d8b8c79
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-3.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-4.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-4.bcmap
new file mode 100644
index 000000000..f50fc6c14
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-4.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-5.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-5.bcmap
new file mode 100644
index 000000000..6caf4a831
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-5.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-6.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-6.bcmap
new file mode 100644
index 000000000..b77fb0705
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-6.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-UCS2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-UCS2.bcmap
new file mode 100644
index 000000000..69d79a2c2
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-CNS1-UCS2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-0.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-0.bcmap
new file mode 100644
index 000000000..36101083f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-0.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-1.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-1.bcmap
new file mode 100644
index 000000000..707bb1065
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-1.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-2.bcmap
new file mode 100644
index 000000000..f7648cc3f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-3.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-3.bcmap
new file mode 100644
index 000000000..852145890
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-3.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-4.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-4.bcmap
new file mode 100644
index 000000000..e40c63ab1
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-4.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-5.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-5.bcmap
new file mode 100644
index 000000000..d7623b500
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-5.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-UCS2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-UCS2.bcmap
new file mode 100644
index 000000000..758652593
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-GB1-UCS2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-0.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-0.bcmap
new file mode 100644
index 000000000..f0e94ec19
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-0.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-1.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-1.bcmap
new file mode 100644
index 000000000..dad42c5ad
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-1.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-2.bcmap
new file mode 100644
index 000000000..090819a06
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-3.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-3.bcmap
new file mode 100644
index 000000000..087dfc155
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-3.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-4.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-4.bcmap
new file mode 100644
index 000000000..46aa9bffe
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-4.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-5.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-5.bcmap
new file mode 100644
index 000000000..5b4b65cc6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-5.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-6.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-6.bcmap
new file mode 100644
index 000000000..e77d699ab
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-6.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-UCS2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-UCS2.bcmap
new file mode 100644
index 000000000..128a14107
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Japan1-UCS2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-0.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-0.bcmap
new file mode 100644
index 000000000..cef1a9985
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-0.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-1.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-1.bcmap
new file mode 100644
index 000000000..11ffa36df
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-1.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-2.bcmap
new file mode 100644
index 000000000..3172308c7
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-UCS2.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-UCS2.bcmap
new file mode 100644
index 000000000..f3371c0cb
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Adobe-Korea1-UCS2.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/B5-H.bcmap
new file mode 100644
index 000000000..beb4d2281
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/B5-V.bcmap
new file mode 100644
index 000000000..2d4f87d50
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/B5pc-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/B5pc-H.bcmap
new file mode 100644
index 000000000..ce0013167
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/B5pc-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/B5pc-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/B5pc-V.bcmap
new file mode 100644
index 000000000..73b99ff2f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/B5pc-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-H.bcmap
new file mode 100644
index 000000000..61d1d0cb0
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-V.bcmap
new file mode 100644
index 000000000..1a393a51e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/CNS-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/CNS1-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/CNS1-H.bcmap
new file mode 100644
index 000000000..f738e218a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/CNS1-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/CNS1-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/CNS1-V.bcmap
new file mode 100644
index 000000000..9c3169f0d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/CNS1-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/CNS2-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/CNS2-H.bcmap
new file mode 100644
index 000000000..c89b3527f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/CNS2-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/CNS2-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/CNS2-V.bcmap
new file mode 100644
index 000000000..7588cec83
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/CNS2-V.bcmap
@@ -0,0 +1,3 @@
+RCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSECNS2-H \ No newline at end of file
diff --git a/browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-H.bcmap
new file mode 100644
index 000000000..cb29415de
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-V.bcmap
new file mode 100644
index 000000000..f09aec631
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/ETHK-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/ETen-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/ETen-B5-H.bcmap
new file mode 100644
index 000000000..c2d77462d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/ETen-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/ETen-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/ETen-B5-V.bcmap
new file mode 100644
index 000000000..89bff159e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/ETen-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-H.bcmap
new file mode 100644
index 000000000..a7d69db5e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-H.bcmap
@@ -0,0 +1,3 @@
+RCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSE ETen-B5-H` ^ \ No newline at end of file
diff --git a/browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-V.bcmap
new file mode 100644
index 000000000..adc5d618d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/ETenms-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/EUC-H.bcmap
new file mode 100644
index 000000000..e92ea5b3b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/EUC-V.bcmap
new file mode 100644
index 000000000..7a7c18322
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Ext-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Ext-H.bcmap
new file mode 100644
index 000000000..3b5cde44d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Ext-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-H.bcmap
new file mode 100644
index 000000000..ea4d2d97b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-V.bcmap
new file mode 100644
index 000000000..3457c2770
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Ext-RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Ext-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Ext-V.bcmap
new file mode 100644
index 000000000..4999ca404
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Ext-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GB-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GB-EUC-H.bcmap
new file mode 100644
index 000000000..e39908b98
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GB-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GB-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GB-EUC-V.bcmap
new file mode 100644
index 000000000..d5be5446a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GB-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GB-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GB-H.bcmap
new file mode 100644
index 000000000..39189c54e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GB-H.bcmap
@@ -0,0 +1,4 @@
+RCopyright 1990-2009 Adobe Systems Incorporated.
+All rights reserved.
+See ./LICENSE!!]aX!!]`21> p z$]"Rd-U7* 4%+ Z {/%<9Kb1]." `],"]
+"]h"]F"]$"]"]`"]>"]"]z"]X"]6"]"]r"]P"]."] "]j"]H"]&"]"]b"]@"]"]|"]Z"]8"]"]t"]R"]0"]"]l"]J"]("]"]d"]B"] "X~']W"]5"]"]q"]O"]-"] "]i"]G"]%"]"]a"]?"]"]{"]Y"]7"]"]s"]Q"]/"] "]k"]I"]'"]"]c"]A"]"]}"]["]9 \ No newline at end of file
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GB-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GB-V.bcmap
new file mode 100644
index 000000000..310834512
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GB-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-H.bcmap
new file mode 100644
index 000000000..05fff7e82
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-V.bcmap
new file mode 100644
index 000000000..0cdf6bed6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBK-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBK2K-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBK2K-H.bcmap
new file mode 100644
index 000000000..46f6ba596
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBK2K-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBK2K-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBK2K-V.bcmap
new file mode 100644
index 000000000..d9a947984
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBK2K-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-H.bcmap
new file mode 100644
index 000000000..5cb0af687
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-V.bcmap
new file mode 100644
index 000000000..bca93b8ef
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBKp-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-H.bcmap
new file mode 100644
index 000000000..4b4e2d322
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-V.bcmap
new file mode 100644
index 000000000..38f706699
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBT-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBT-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBT-H.bcmap
new file mode 100644
index 000000000..8437ac337
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBT-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBT-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBT-V.bcmap
new file mode 100644
index 000000000..697ab4a8e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBT-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-H.bcmap
new file mode 100644
index 000000000..f6e50e893
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-V.bcmap
new file mode 100644
index 000000000..6c0d71a2d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBTpc-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-H.bcmap
new file mode 100644
index 000000000..c9edf67cf
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-V.bcmap
new file mode 100644
index 000000000..31450c97f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/GBpc-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/H.bcmap
new file mode 100644
index 000000000..7b24ea462
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-H.bcmap
new file mode 100644
index 000000000..7d30c0500
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-V.bcmap
new file mode 100644
index 000000000..78946940d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKdla-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-H.bcmap
new file mode 100644
index 000000000..d829a2310
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-V.bcmap
new file mode 100644
index 000000000..2b572b50a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKdlb-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-H.bcmap
new file mode 100644
index 000000000..971a4f23f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-V.bcmap
new file mode 100644
index 000000000..d353ca256
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKgccs-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-H.bcmap
new file mode 100644
index 000000000..576dc0111
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-V.bcmap
new file mode 100644
index 000000000..0e96d0e22
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKm314-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-H.bcmap
new file mode 100644
index 000000000..11d170c75
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-V.bcmap
new file mode 100644
index 000000000..54959bf9e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKm471-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-H.bcmap
new file mode 100644
index 000000000..6ef7857ad
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-V.bcmap
new file mode 100644
index 000000000..1fb2fa2a2
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/HKscs-B5-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Hankaku.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Hankaku.bcmap
new file mode 100644
index 000000000..4b8ec7fce
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Hankaku.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Hiragana.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Hiragana.bcmap
new file mode 100644
index 000000000..17e983e77
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Hiragana.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-H.bcmap
new file mode 100644
index 000000000..a45c65f00
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-V.bcmap
new file mode 100644
index 000000000..0e7b21f0a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSC-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSC-H.bcmap
new file mode 100644
index 000000000..b9b22b678
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-H.bcmap
new file mode 100644
index 000000000..2531ffcf4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-V.bcmap
new file mode 100644
index 000000000..367ceb226
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSC-Johab-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSC-V.bcmap
new file mode 100644
index 000000000..6ae2f0b6b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-H.bcmap
new file mode 100644
index 000000000..a8d4240e6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-H.bcmap
new file mode 100644
index 000000000..8b4ae18fd
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-V.bcmap
new file mode 100644
index 000000000..b655dbcfb
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-HW-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-V.bcmap
new file mode 100644
index 000000000..21f97f65b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSCms-UHC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-H.bcmap
new file mode 100644
index 000000000..e06f361eb
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-V.bcmap
new file mode 100644
index 000000000..f3c9113fc
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/KSCpc-EUC-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Katakana.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Katakana.bcmap
new file mode 100644
index 000000000..524303c4f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Katakana.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/LICENSE b/browser/extensions/pdfjs/content/web/cmaps/LICENSE
new file mode 100644
index 000000000..b1ad168ad
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/LICENSE
@@ -0,0 +1,36 @@
+%%Copyright: -----------------------------------------------------------
+%%Copyright: Copyright 1990-2009 Adobe Systems Incorporated.
+%%Copyright: All rights reserved.
+%%Copyright:
+%%Copyright: Redistribution and use in source and binary forms, with or
+%%Copyright: without modification, are permitted provided that the
+%%Copyright: following conditions are met:
+%%Copyright:
+%%Copyright: Redistributions of source code must retain the above
+%%Copyright: copyright notice, this list of conditions and the following
+%%Copyright: disclaimer.
+%%Copyright:
+%%Copyright: Redistributions in binary form must reproduce the above
+%%Copyright: copyright notice, this list of conditions and the following
+%%Copyright: disclaimer in the documentation and/or other materials
+%%Copyright: provided with the distribution.
+%%Copyright:
+%%Copyright: Neither the name of Adobe Systems Incorporated nor the names
+%%Copyright: of its contributors may be used to endorse or promote
+%%Copyright: products derived from this software without specific prior
+%%Copyright: written permission.
+%%Copyright:
+%%Copyright: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+%%Copyright: CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+%%Copyright: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+%%Copyright: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+%%Copyright: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+%%Copyright: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+%%Copyright: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+%%Copyright: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%%Copyright: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+%%Copyright: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+%%Copyright: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+%%Copyright: OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+%%Copyright: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+%%Copyright: -----------------------------------------------------------
diff --git a/browser/extensions/pdfjs/content/web/cmaps/NWP-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/NWP-H.bcmap
new file mode 100644
index 000000000..afc5e4b05
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/NWP-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/NWP-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/NWP-V.bcmap
new file mode 100644
index 000000000..bb5785e32
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/NWP-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/RKSJ-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/RKSJ-H.bcmap
new file mode 100644
index 000000000..fb8d298e9
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/RKSJ-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/RKSJ-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/RKSJ-V.bcmap
new file mode 100644
index 000000000..a2555a6c0
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/RKSJ-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/Roman.bcmap b/browser/extensions/pdfjs/content/web/cmaps/Roman.bcmap
new file mode 100644
index 000000000..f896dcf1c
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/Roman.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-H.bcmap
new file mode 100644
index 000000000..d5db27c5c
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-V.bcmap
new file mode 100644
index 000000000..1dc9b7a21
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UCS2-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-H.bcmap
new file mode 100644
index 000000000..961afefb6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-V.bcmap
new file mode 100644
index 000000000..df0cffe86
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF16-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-H.bcmap
new file mode 100644
index 000000000..1ab18a143
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-V.bcmap
new file mode 100644
index 000000000..ad14662e2
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF32-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-H.bcmap
new file mode 100644
index 000000000..83c6bd7c4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-V.bcmap
new file mode 100644
index 000000000..22a27e4dd
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniCNS-UTF8-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-H.bcmap
new file mode 100644
index 000000000..5bd6228ce
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-V.bcmap
new file mode 100644
index 000000000..53c534b7f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UCS2-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-H.bcmap
new file mode 100644
index 000000000..b95045b40
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-V.bcmap
new file mode 100644
index 000000000..51f023e0d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF16-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-H.bcmap
new file mode 100644
index 000000000..f0dbd14f3
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-V.bcmap
new file mode 100644
index 000000000..ce9c30a98
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF32-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-H.bcmap
new file mode 100644
index 000000000..982ca462b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-V.bcmap
new file mode 100644
index 000000000..f78020dd4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniGB-UTF8-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-H.bcmap
new file mode 100644
index 000000000..7daf56afa
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-H.bcmap
new file mode 100644
index 000000000..ac9975c58
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-V.bcmap
new file mode 100644
index 000000000..3da0a1c62
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-V.bcmap
new file mode 100644
index 000000000..c50b9ddfd
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UCS2-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-H.bcmap
new file mode 100644
index 000000000..676134463
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-V.bcmap
new file mode 100644
index 000000000..70bf90c0e
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF16-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-H.bcmap
new file mode 100644
index 000000000..7a83d53ae
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-V.bcmap
new file mode 100644
index 000000000..7a8713539
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF32-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-H.bcmap
new file mode 100644
index 000000000..9f0334cac
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-V.bcmap
new file mode 100644
index 000000000..808a94f0f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS-UTF8-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-H.bcmap
new file mode 100644
index 000000000..d768bf811
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-V.bcmap
new file mode 100644
index 000000000..3d5bf6fb4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF16-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-H.bcmap
new file mode 100644
index 000000000..09eee10d4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-V.bcmap
new file mode 100644
index 000000000..6c5460013
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF32-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-H.bcmap
new file mode 100644
index 000000000..1b1a64f50
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-V.bcmap
new file mode 100644
index 000000000..994aa9ef9
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJIS2004-UTF8-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-HW-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-HW-V.bcmap
new file mode 100644
index 000000000..643f921b6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-HW-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-V.bcmap
new file mode 100644
index 000000000..c148f67f5
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UCS2-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UTF8-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UTF8-V.bcmap
new file mode 100644
index 000000000..1849d809a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJISPro-UTF8-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-H.bcmap
new file mode 100644
index 000000000..a83a677c5
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-V.bcmap
new file mode 100644
index 000000000..f527248ad
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJISX0213-UTF32-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-H.bcmap
new file mode 100644
index 000000000..e1a988dc9
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-V.bcmap
new file mode 100644
index 000000000..47e054a96
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-H.bcmap
new file mode 100644
index 000000000..b5b94852a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-V.bcmap
new file mode 100644
index 000000000..026adcaad
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UCS2-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-H.bcmap
new file mode 100644
index 000000000..fd4e66e81
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-V.bcmap
new file mode 100644
index 000000000..075efb705
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF16-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-H.bcmap
new file mode 100644
index 000000000..769d2142c
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-V.bcmap
new file mode 100644
index 000000000..bdab208b6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF32-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-H.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-H.bcmap
new file mode 100644
index 000000000..6ff8674af
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-H.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-V.bcmap
new file mode 100644
index 000000000..8dfa76a58
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/UniKS-UTF8-V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/V.bcmap b/browser/extensions/pdfjs/content/web/cmaps/V.bcmap
new file mode 100644
index 000000000..fdec99066
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/V.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/cmaps/WP-Symbol.bcmap b/browser/extensions/pdfjs/content/web/cmaps/WP-Symbol.bcmap
new file mode 100644
index 000000000..46729bbf3
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/cmaps/WP-Symbol.bcmap
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/debugger.js b/browser/extensions/pdfjs/content/web/debugger.js
new file mode 100644
index 000000000..8a049f4c6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -0,0 +1,616 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+
+'use strict';
+
+var FontInspector = (function FontInspectorClosure() {
+ var fonts;
+ var active = false;
+ var fontAttribute = 'data-font-name';
+ function removeSelection() {
+ var divs = document.querySelectorAll('div[' + fontAttribute + ']');
+ for (var i = 0, ii = divs.length; i < ii; ++i) {
+ var div = divs[i];
+ div.className = '';
+ }
+ }
+ function resetSelection() {
+ var divs = document.querySelectorAll('div[' + fontAttribute + ']');
+ for (var i = 0, ii = divs.length; i < ii; ++i) {
+ var div = divs[i];
+ div.className = 'debuggerHideText';
+ }
+ }
+ function selectFont(fontName, show) {
+ var divs = document.querySelectorAll('div[' + fontAttribute + '=' +
+ fontName + ']');
+ for (var i = 0, ii = divs.length; i < ii; ++i) {
+ var div = divs[i];
+ div.className = show ? 'debuggerShowText' : 'debuggerHideText';
+ }
+ }
+ function textLayerClick(e) {
+ if (!e.target.dataset.fontName ||
+ e.target.tagName.toUpperCase() !== 'DIV') {
+ return;
+ }
+ var fontName = e.target.dataset.fontName;
+ var selects = document.getElementsByTagName('input');
+ for (var i = 0; i < selects.length; ++i) {
+ var select = selects[i];
+ if (select.dataset.fontName !== fontName) {
+ continue;
+ }
+ select.checked = !select.checked;
+ selectFont(fontName, select.checked);
+ select.scrollIntoView();
+ }
+ }
+ return {
+ // Properties/functions needed by PDFBug.
+ id: 'FontInspector',
+ name: 'Font Inspector',
+ panel: null,
+ manager: null,
+ init: function init(pdfjsLib) {
+ var panel = this.panel;
+ panel.setAttribute('style', 'padding: 5px;');
+ var tmp = document.createElement('button');
+ tmp.addEventListener('click', resetSelection);
+ tmp.textContent = 'Refresh';
+ panel.appendChild(tmp);
+
+ fonts = document.createElement('div');
+ panel.appendChild(fonts);
+ },
+ cleanup: function cleanup() {
+ fonts.textContent = '';
+ },
+ enabled: false,
+ get active() {
+ return active;
+ },
+ set active(value) {
+ active = value;
+ if (active) {
+ document.body.addEventListener('click', textLayerClick, true);
+ resetSelection();
+ } else {
+ document.body.removeEventListener('click', textLayerClick, true);
+ removeSelection();
+ }
+ },
+ // FontInspector specific functions.
+ fontAdded: function fontAdded(fontObj, url) {
+ function properties(obj, list) {
+ var moreInfo = document.createElement('table');
+ for (var i = 0; i < list.length; i++) {
+ var tr = document.createElement('tr');
+ var td1 = document.createElement('td');
+ td1.textContent = list[i];
+ tr.appendChild(td1);
+ var td2 = document.createElement('td');
+ td2.textContent = obj[list[i]].toString();
+ tr.appendChild(td2);
+ moreInfo.appendChild(tr);
+ }
+ return moreInfo;
+ }
+ var moreInfo = properties(fontObj, ['name', 'type']);
+ var fontName = fontObj.loadedName;
+ var font = document.createElement('div');
+ var name = document.createElement('span');
+ name.textContent = fontName;
+ var download = document.createElement('a');
+ if (url) {
+ url = /url\(['"]?([^\)"']+)/.exec(url);
+ download.href = url[1];
+ } else if (fontObj.data) {
+ url = URL.createObjectURL(new Blob([fontObj.data], {
+ type: fontObj.mimeType
+ }));
+ download.href = url;
+ }
+ download.textContent = 'Download';
+ var logIt = document.createElement('a');
+ logIt.href = '';
+ logIt.textContent = 'Log';
+ logIt.addEventListener('click', function(event) {
+ event.preventDefault();
+ console.log(fontObj);
+ });
+ var select = document.createElement('input');
+ select.setAttribute('type', 'checkbox');
+ select.dataset.fontName = fontName;
+ select.addEventListener('click', (function(select, fontName) {
+ return (function() {
+ selectFont(fontName, select.checked);
+ });
+ })(select, fontName));
+ font.appendChild(select);
+ font.appendChild(name);
+ font.appendChild(document.createTextNode(' '));
+ font.appendChild(download);
+ font.appendChild(document.createTextNode(' '));
+ font.appendChild(logIt);
+ font.appendChild(moreInfo);
+ fonts.appendChild(font);
+ // Somewhat of a hack, should probably add a hook for when the text layer
+ // is done rendering.
+ setTimeout(function() {
+ if (this.active) {
+ resetSelection();
+ }
+ }.bind(this), 2000);
+ }
+ };
+})();
+
+var opMap;
+
+// Manages all the page steppers.
+var StepperManager = (function StepperManagerClosure() {
+ var steppers = [];
+ var stepperDiv = null;
+ var stepperControls = null;
+ var stepperChooser = null;
+ var breakPoints = Object.create(null);
+ return {
+ // Properties/functions needed by PDFBug.
+ id: 'Stepper',
+ name: 'Stepper',
+ panel: null,
+ manager: null,
+ init: function init(pdfjsLib) {
+ var self = this;
+ this.panel.setAttribute('style', 'padding: 5px;');
+ stepperControls = document.createElement('div');
+ stepperChooser = document.createElement('select');
+ stepperChooser.addEventListener('change', function(event) {
+ self.selectStepper(this.value);
+ });
+ stepperControls.appendChild(stepperChooser);
+ stepperDiv = document.createElement('div');
+ this.panel.appendChild(stepperControls);
+ this.panel.appendChild(stepperDiv);
+ if (sessionStorage.getItem('pdfjsBreakPoints')) {
+ breakPoints = JSON.parse(sessionStorage.getItem('pdfjsBreakPoints'));
+ }
+
+ opMap = Object.create(null);
+ for (var key in pdfjsLib.OPS) {
+ opMap[pdfjsLib.OPS[key]] = key;
+ }
+ },
+ cleanup: function cleanup() {
+ stepperChooser.textContent = '';
+ stepperDiv.textContent = '';
+ steppers = [];
+ },
+ enabled: false,
+ active: false,
+ // Stepper specific functions.
+ create: function create(pageIndex) {
+ var debug = document.createElement('div');
+ debug.id = 'stepper' + pageIndex;
+ debug.setAttribute('hidden', true);
+ debug.className = 'stepper';
+ stepperDiv.appendChild(debug);
+ var b = document.createElement('option');
+ b.textContent = 'Page ' + (pageIndex + 1);
+ b.value = pageIndex;
+ stepperChooser.appendChild(b);
+ var initBreakPoints = breakPoints[pageIndex] || [];
+ var stepper = new Stepper(debug, pageIndex, initBreakPoints);
+ steppers.push(stepper);
+ if (steppers.length === 1) {
+ this.selectStepper(pageIndex, false);
+ }
+ return stepper;
+ },
+ selectStepper: function selectStepper(pageIndex, selectPanel) {
+ var i;
+ pageIndex = pageIndex | 0;
+ if (selectPanel) {
+ this.manager.selectPanel(this);
+ }
+ for (i = 0; i < steppers.length; ++i) {
+ var stepper = steppers[i];
+ if (stepper.pageIndex === pageIndex) {
+ stepper.panel.removeAttribute('hidden');
+ } else {
+ stepper.panel.setAttribute('hidden', true);
+ }
+ }
+ var options = stepperChooser.options;
+ for (i = 0; i < options.length; ++i) {
+ var option = options[i];
+ option.selected = (option.value | 0) === pageIndex;
+ }
+ },
+ saveBreakPoints: function saveBreakPoints(pageIndex, bps) {
+ breakPoints[pageIndex] = bps;
+ sessionStorage.setItem('pdfjsBreakPoints', JSON.stringify(breakPoints));
+ }
+ };
+})();
+
+// The stepper for each page's IRQueue.
+var Stepper = (function StepperClosure() {
+ // Shorter way to create element and optionally set textContent.
+ function c(tag, textContent) {
+ var d = document.createElement(tag);
+ if (textContent) {
+ d.textContent = textContent;
+ }
+ return d;
+ }
+
+ function simplifyArgs(args) {
+ if (typeof args === 'string') {
+ var MAX_STRING_LENGTH = 75;
+ return args.length <= MAX_STRING_LENGTH ? args :
+ args.substr(0, MAX_STRING_LENGTH) + '...';
+ }
+ if (typeof args !== 'object' || args === null) {
+ return args;
+ }
+ if ('length' in args) { // array
+ var simpleArgs = [], i, ii;
+ var MAX_ITEMS = 10;
+ for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
+ simpleArgs.push(simplifyArgs(args[i]));
+ }
+ if (i < args.length) {
+ simpleArgs.push('...');
+ }
+ return simpleArgs;
+ }
+ var simpleObj = {};
+ for (var key in args) {
+ simpleObj[key] = simplifyArgs(args[key]);
+ }
+ return simpleObj;
+ }
+
+ function Stepper(panel, pageIndex, initialBreakPoints) {
+ this.panel = panel;
+ this.breakPoint = 0;
+ this.nextBreakPoint = null;
+ this.pageIndex = pageIndex;
+ this.breakPoints = initialBreakPoints;
+ this.currentIdx = -1;
+ this.operatorListIdx = 0;
+ }
+ Stepper.prototype = {
+ init: function init(operatorList) {
+ var panel = this.panel;
+ var content = c('div', 'c=continue, s=step');
+ var table = c('table');
+ content.appendChild(table);
+ table.cellSpacing = 0;
+ var headerRow = c('tr');
+ table.appendChild(headerRow);
+ headerRow.appendChild(c('th', 'Break'));
+ headerRow.appendChild(c('th', 'Idx'));
+ headerRow.appendChild(c('th', 'fn'));
+ headerRow.appendChild(c('th', 'args'));
+ panel.appendChild(content);
+ this.table = table;
+ this.updateOperatorList(operatorList);
+ },
+ updateOperatorList: function updateOperatorList(operatorList) {
+ var self = this;
+
+ function cboxOnClick() {
+ var x = +this.dataset.idx;
+ if (this.checked) {
+ self.breakPoints.push(x);
+ } else {
+ self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
+ }
+ StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
+ }
+
+ var MAX_OPERATORS_COUNT = 15000;
+ if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
+ return;
+ }
+
+ var chunk = document.createDocumentFragment();
+ var operatorsToDisplay = Math.min(MAX_OPERATORS_COUNT,
+ operatorList.fnArray.length);
+ for (var i = this.operatorListIdx; i < operatorsToDisplay; i++) {
+ var line = c('tr');
+ line.className = 'line';
+ line.dataset.idx = i;
+ chunk.appendChild(line);
+ var checked = this.breakPoints.indexOf(i) !== -1;
+ var args = operatorList.argsArray[i] || [];
+
+ var breakCell = c('td');
+ var cbox = c('input');
+ cbox.type = 'checkbox';
+ cbox.className = 'points';
+ cbox.checked = checked;
+ cbox.dataset.idx = i;
+ cbox.onclick = cboxOnClick;
+
+ breakCell.appendChild(cbox);
+ line.appendChild(breakCell);
+ line.appendChild(c('td', i.toString()));
+ var fn = opMap[operatorList.fnArray[i]];
+ var decArgs = args;
+ if (fn === 'showText') {
+ var glyphs = args[0];
+ var newArgs = [];
+ var str = [];
+ for (var j = 0; j < glyphs.length; j++) {
+ var glyph = glyphs[j];
+ if (typeof glyph === 'object' && glyph !== null) {
+ str.push(glyph.fontChar);
+ } else {
+ if (str.length > 0) {
+ newArgs.push(str.join(''));
+ str = [];
+ }
+ newArgs.push(glyph); // null or number
+ }
+ }
+ if (str.length > 0) {
+ newArgs.push(str.join(''));
+ }
+ decArgs = [newArgs];
+ }
+ line.appendChild(c('td', fn));
+ line.appendChild(c('td', JSON.stringify(simplifyArgs(decArgs))));
+ }
+ if (operatorsToDisplay < operatorList.fnArray.length) {
+ line = c('tr');
+ var lastCell = c('td', '...');
+ lastCell.colspan = 4;
+ chunk.appendChild(lastCell);
+ }
+ this.operatorListIdx = operatorList.fnArray.length;
+ this.table.appendChild(chunk);
+ },
+ getNextBreakPoint: function getNextBreakPoint() {
+ this.breakPoints.sort(function(a, b) { return a - b; });
+ for (var i = 0; i < this.breakPoints.length; i++) {
+ if (this.breakPoints[i] > this.currentIdx) {
+ return this.breakPoints[i];
+ }
+ }
+ return null;
+ },
+ breakIt: function breakIt(idx, callback) {
+ StepperManager.selectStepper(this.pageIndex, true);
+ var self = this;
+ var dom = document;
+ self.currentIdx = idx;
+ var listener = function(e) {
+ switch (e.keyCode) {
+ case 83: // step
+ dom.removeEventListener('keydown', listener, false);
+ self.nextBreakPoint = self.currentIdx + 1;
+ self.goTo(-1);
+ callback();
+ break;
+ case 67: // continue
+ dom.removeEventListener('keydown', listener, false);
+ var breakPoint = self.getNextBreakPoint();
+ self.nextBreakPoint = breakPoint;
+ self.goTo(-1);
+ callback();
+ break;
+ }
+ };
+ dom.addEventListener('keydown', listener, false);
+ self.goTo(idx);
+ },
+ goTo: function goTo(idx) {
+ var allRows = this.panel.getElementsByClassName('line');
+ for (var x = 0, xx = allRows.length; x < xx; ++x) {
+ var row = allRows[x];
+ if ((row.dataset.idx | 0) === idx) {
+ row.style.backgroundColor = 'rgb(251,250,207)';
+ row.scrollIntoView();
+ } else {
+ row.style.backgroundColor = null;
+ }
+ }
+ }
+ };
+ return Stepper;
+})();
+
+var Stats = (function Stats() {
+ var stats = [];
+ function clear(node) {
+ while (node.hasChildNodes()) {
+ node.removeChild(node.lastChild);
+ }
+ }
+ function getStatIndex(pageNumber) {
+ for (var i = 0, ii = stats.length; i < ii; ++i) {
+ if (stats[i].pageNumber === pageNumber) {
+ return i;
+ }
+ }
+ return false;
+ }
+ return {
+ // Properties/functions needed by PDFBug.
+ id: 'Stats',
+ name: 'Stats',
+ panel: null,
+ manager: null,
+ init: function init(pdfjsLib) {
+ this.panel.setAttribute('style', 'padding: 5px;');
+ pdfjsLib.PDFJS.enableStats = true;
+ },
+ enabled: false,
+ active: false,
+ // Stats specific functions.
+ add: function(pageNumber, stat) {
+ if (!stat) {
+ return;
+ }
+ var statsIndex = getStatIndex(pageNumber);
+ if (statsIndex !== false) {
+ var b = stats[statsIndex];
+ this.panel.removeChild(b.div);
+ stats.splice(statsIndex, 1);
+ }
+ var wrapper = document.createElement('div');
+ wrapper.className = 'stats';
+ var title = document.createElement('div');
+ title.className = 'title';
+ title.textContent = 'Page: ' + pageNumber;
+ var statsDiv = document.createElement('div');
+ statsDiv.textContent = stat.toString();
+ wrapper.appendChild(title);
+ wrapper.appendChild(statsDiv);
+ stats.push({ pageNumber: pageNumber, div: wrapper });
+ stats.sort(function(a, b) { return a.pageNumber - b.pageNumber; });
+ clear(this.panel);
+ for (var i = 0, ii = stats.length; i < ii; ++i) {
+ this.panel.appendChild(stats[i].div);
+ }
+ },
+ cleanup: function () {
+ stats = [];
+ clear(this.panel);
+ }
+ };
+})();
+
+// Manages all the debugging tools.
+var PDFBug = (function PDFBugClosure() {
+ var panelWidth = 300;
+ var buttons = [];
+ var activePanel = null;
+
+ return {
+ tools: [
+ FontInspector,
+ StepperManager,
+ Stats
+ ],
+ enable: function(ids) {
+ var all = false, tools = this.tools;
+ if (ids.length === 1 && ids[0] === 'all') {
+ all = true;
+ }
+ for (var i = 0; i < tools.length; ++i) {
+ var tool = tools[i];
+ if (all || ids.indexOf(tool.id) !== -1) {
+ tool.enabled = true;
+ }
+ }
+ if (!all) {
+ // Sort the tools by the order they are enabled.
+ tools.sort(function(a, b) {
+ var indexA = ids.indexOf(a.id);
+ indexA = indexA < 0 ? tools.length : indexA;
+ var indexB = ids.indexOf(b.id);
+ indexB = indexB < 0 ? tools.length : indexB;
+ return indexA - indexB;
+ });
+ }
+ },
+ init: function init(pdfjsLib, container) {
+ /*
+ * Basic Layout:
+ * PDFBug
+ * Controls
+ * Panels
+ * Panel
+ * Panel
+ * ...
+ */
+ var ui = document.createElement('div');
+ ui.id = 'PDFBug';
+
+ var controls = document.createElement('div');
+ controls.setAttribute('class', 'controls');
+ ui.appendChild(controls);
+
+ var panels = document.createElement('div');
+ panels.setAttribute('class', 'panels');
+ ui.appendChild(panels);
+
+ container.appendChild(ui);
+ container.style.right = panelWidth + 'px';
+
+ // Initialize all the debugging tools.
+ var tools = this.tools;
+ var self = this;
+ for (var i = 0; i < tools.length; ++i) {
+ var tool = tools[i];
+ var panel = document.createElement('div');
+ var panelButton = document.createElement('button');
+ panelButton.textContent = tool.name;
+ panelButton.addEventListener('click', (function(selected) {
+ return function(event) {
+ event.preventDefault();
+ self.selectPanel(selected);
+ };
+ })(i));
+ controls.appendChild(panelButton);
+ panels.appendChild(panel);
+ tool.panel = panel;
+ tool.manager = this;
+ if (tool.enabled) {
+ tool.init(pdfjsLib);
+ } else {
+ panel.textContent = tool.name + ' is disabled. To enable add ' +
+ ' "' + tool.id + '" to the pdfBug parameter ' +
+ 'and refresh (separate multiple by commas).';
+ }
+ buttons.push(panelButton);
+ }
+ this.selectPanel(0);
+ },
+ cleanup: function cleanup() {
+ for (var i = 0, ii = this.tools.length; i < ii; i++) {
+ if (this.tools[i].enabled) {
+ this.tools[i].cleanup();
+ }
+ }
+ },
+ selectPanel: function selectPanel(index) {
+ if (typeof index !== 'number') {
+ index = this.tools.indexOf(index);
+ }
+ if (index === activePanel) {
+ return;
+ }
+ activePanel = index;
+ var tools = this.tools;
+ for (var j = 0; j < tools.length; ++j) {
+ if (j === index) {
+ buttons[j].setAttribute('class', 'active');
+ tools[j].active = true;
+ tools[j].panel.removeAttribute('hidden');
+ } else {
+ buttons[j].setAttribute('class', '');
+ tools[j].active = false;
+ tools[j].panel.setAttribute('hidden', 'true');
+ }
+ }
+ }
+ };
+})();
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-check.svg b/browser/extensions/pdfjs/content/web/images/annotation-check.svg
new file mode 100644
index 000000000..71cd16df5
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-check.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="40"
+ height="40"
+ viewBox="0 0 40 40">
+ <path
+ d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
+ id="path4"
+ style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-comment.svg b/browser/extensions/pdfjs/content/web/images/annotation-comment.svg
new file mode 100644
index 000000000..86f1f1724
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-comment.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ height="40"
+ width="40"
+ viewBox="0 0 40 40">
+ <rect
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ width="33.76017"
+ height="33.76017"
+ x="3.119915"
+ y="3.119915" />
+ <path
+ d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.93031836;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-help.svg b/browser/extensions/pdfjs/content/web/images/annotation-help.svg
new file mode 100644
index 000000000..00938fefe
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-help.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="40"
+ height="40"
+ viewBox="0 0 40 40">
+ <g
+ transform="translate(0,-60)"
+ id="layer1">
+ <rect
+ width="36.460953"
+ height="34.805603"
+ x="1.7695236"
+ y="62.597198"
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.30826771;stroke-opacity:1" />
+ <g
+ transform="matrix(0.88763677,0,0,0.88763677,2.2472646,8.9890584)">
+ <path
+ d="M 20,64.526342 C 11.454135,64.526342 4.5263421,71.454135 4.5263421,80 4.5263421,88.545865 11.454135,95.473658 20,95.473658 28.545865,95.473658 35.473658,88.545865 35.473658,80 35.473658,71.454135 28.545865,64.526342 20,64.526342 z m -0.408738,9.488564 c 3.527079,0 6.393832,2.84061 6.393832,6.335441 0,3.494831 -2.866753,6.335441 -6.393832,6.335441 -3.527079,0 -6.393832,-2.84061 -6.393832,-6.335441 0,-3.494831 2.866753,-6.335441 6.393832,-6.335441 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.02768445;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 7.2335209,71.819938 4.9702591,4.161823 c -1.679956,2.581606 -1.443939,6.069592 0.159325,8.677725 l -5.1263071,3.424463 c 0.67516,1.231452 3.0166401,3.547686 4.2331971,4.194757 l 3.907728,-4.567277 c 2.541952,1.45975 5.730694,1.392161 8.438683,-0.12614 l 3.469517,6.108336 c 1.129779,-0.44367 4.742234,-3.449633 5.416358,-5.003859 l -5.46204,-4.415541 c 1.44319,-2.424098 1.651175,-5.267515 0.557303,-7.748623 l 5.903195,-3.833951 C 33.14257,71.704996 30.616217,69.018606 29.02952,67.99296 l -4.118813,4.981678 C 22.411934,71.205099 18.900853,70.937534 16.041319,72.32916 l -3.595408,-5.322091 c -1.345962,0.579488 -4.1293881,2.921233 -5.2123901,4.812869 z m 8.1010311,3.426672 c 2.75284,-2.446266 6.769149,-2.144694 9.048998,0.420874 2.279848,2.56557 2.113919,6.596919 -0.638924,9.043185 -2.752841,2.446267 -6.775754,2.13726 -9.055604,-0.428308 -2.279851,-2.565568 -2.107313,-6.589485 0.64553,-9.035751 z"
+ style="fill:#000000;fill-opacity:1;stroke:none" />
+ </g>
+ </g>
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-insert.svg b/browser/extensions/pdfjs/content/web/images/annotation-insert.svg
new file mode 100644
index 000000000..519ef6826
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-insert.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="64"
+ height="64"
+ viewBox="0 0 64 64">
+ <path
+ d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
+ style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-key.svg b/browser/extensions/pdfjs/content/web/images/annotation-key.svg
new file mode 100644
index 000000000..8d09d5378
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-key.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="64"
+ height="64"
+ viewBox="0 0 64 64">
+ <path
+ d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
+ id="path604"
+ style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-newparagraph.svg b/browser/extensions/pdfjs/content/web/images/annotation-newparagraph.svg
new file mode 100644
index 000000000..38d2497da
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-newparagraph.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="64"
+ height="64"
+ viewBox="0 0 64 64">
+ <path
+ d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
+ id="path2985"
+ style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-noicon.svg b/browser/extensions/pdfjs/content/web/images/annotation-noicon.svg
new file mode 100644
index 000000000..c07d10808
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-noicon.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="40"
+ height="40"
+ viewBox="0 0 40 40">
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-note.svg b/browser/extensions/pdfjs/content/web/images/annotation-note.svg
new file mode 100644
index 000000000..70173651c
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-note.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="40"
+ height="40"
+ viewBox="0 0 40 40">
+ <rect
+ width="36.075428"
+ height="31.096582"
+ x="1.962286"
+ y="4.4517088"
+ id="rect4"
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
+ <rect
+ width="27.96859"
+ height="1.5012145"
+ x="6.0157046"
+ y="10.285"
+ id="rect6"
+ style="fill:#000000;fill-opacity:1;stroke:none" />
+ <rect
+ width="27.96859"
+ height="0.85783684"
+ x="6.0157056"
+ y="23.21689"
+ id="rect8"
+ style="fill:#000000;fill-opacity:1;stroke:none" />
+ <rect
+ width="27.96859"
+ height="0.85783684"
+ x="5.8130345"
+ y="28.964394"
+ id="rect10"
+ style="fill:#000000;fill-opacity:1;stroke:none" />
+ <rect
+ width="27.96859"
+ height="0.85783684"
+ x="6.0157046"
+ y="17.426493"
+ id="rect12"
+ style="fill:#000000;fill-opacity:1;stroke:none" />
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/annotation-paragraph.svg b/browser/extensions/pdfjs/content/web/images/annotation-paragraph.svg
new file mode 100644
index 000000000..6ae5212b7
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/annotation-paragraph.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="40"
+ height="40"
+ viewBox="0 0 40 40">
+ <rect
+ width="33.76017"
+ height="33.76017"
+ x="3.119915"
+ y="3.119915"
+ style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
+ style="font-size:29.42051125px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.07795751;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
+</svg>
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl.png b/browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl.png
new file mode 100644
index 000000000..bef02743f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl@2x.png
new file mode 100644
index 000000000..1da6dc949
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-next-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-next.png b/browser/extensions/pdfjs/content/web/images/findbarButton-next.png
new file mode 100644
index 000000000..de1d0fc90
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-next.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-next@2x.png b/browser/extensions/pdfjs/content/web/images/findbarButton-next@2x.png
new file mode 100644
index 000000000..0250307c0
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-next@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl.png b/browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl.png
new file mode 100644
index 000000000..de1d0fc90
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl@2x.png
new file mode 100644
index 000000000..0250307c0
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-previous-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-previous.png b/browser/extensions/pdfjs/content/web/images/findbarButton-previous.png
new file mode 100644
index 000000000..bef02743f
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-previous.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/findbarButton-previous@2x.png b/browser/extensions/pdfjs/content/web/images/findbarButton-previous@2x.png
new file mode 100644
index 000000000..1da6dc949
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/findbarButton-previous@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/grab.cur b/browser/extensions/pdfjs/content/web/images/grab.cur
new file mode 100644
index 000000000..db7ad5aed
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/grab.cur
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/grabbing.cur b/browser/extensions/pdfjs/content/web/images/grabbing.cur
new file mode 100644
index 000000000..e0dfd04e4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/grabbing.cur
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/loading-icon.gif b/browser/extensions/pdfjs/content/web/images/loading-icon.gif
new file mode 100644
index 000000000..1c72ebb55
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/loading-icon.gif
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/loading-small.png b/browser/extensions/pdfjs/content/web/images/loading-small.png
new file mode 100644
index 000000000..8831a8058
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/loading-small.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/loading-small@2x.png b/browser/extensions/pdfjs/content/web/images/loading-small@2x.png
new file mode 100644
index 000000000..b25b4452a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/loading-small@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties.png
new file mode 100644
index 000000000..40925e25a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties@2x.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties@2x.png
new file mode 100644
index 000000000..adb240eaa
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage.png
new file mode 100644
index 000000000..e68846aa5
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage@2x.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage@2x.png
new file mode 100644
index 000000000..3ad8af517
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-firstPage@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool.png
new file mode 100644
index 000000000..cb85a841b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool@2x.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool@2x.png
new file mode 100644
index 000000000..5c13f77ff
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage.png
new file mode 100644
index 000000000..be763e0c4
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage@2x.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage@2x.png
new file mode 100644
index 000000000..8570984f2
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-lastPage@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw.png
new file mode 100644
index 000000000..675d6da2c
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw@2x.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw@2x.png
new file mode 100644
index 000000000..b9e743122
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw.png
new file mode 100644
index 000000000..e1c759888
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw@2x.png b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw@2x.png
new file mode 100644
index 000000000..cb257b41c
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-rotateCw@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/shadow.png b/browser/extensions/pdfjs/content/web/images/shadow.png
new file mode 100644
index 000000000..31d3bdb14
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/shadow.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/texture.png b/browser/extensions/pdfjs/content/web/images/texture.png
new file mode 100644
index 000000000..12bae83a9
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/texture.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark.png
new file mode 100644
index 000000000..a187be6c9
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark@2x.png
new file mode 100644
index 000000000..4efbaa675
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-bookmark@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-download.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-download.png
new file mode 100644
index 000000000..eaab35f09
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-download.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-download@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-download@2x.png
new file mode 100644
index 000000000..896face45
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-download@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows.png
new file mode 100644
index 000000000..e50ca4eee
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows@2x.png
new file mode 100644
index 000000000..f7570bc0d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-menuArrows@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-openFile.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-openFile.png
new file mode 100644
index 000000000..b5cf1bd06
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-openFile.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-openFile@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-openFile@2x.png
new file mode 100644
index 000000000..91ab76593
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-openFile@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl.png
new file mode 100644
index 000000000..1957f79ab
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl@2x.png
new file mode 100644
index 000000000..16ebcb8ef
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown.png
new file mode 100644
index 000000000..8219ecf83
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown@2x.png
new file mode 100644
index 000000000..758c01d83
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageDown@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl.png
new file mode 100644
index 000000000..98e7ce481
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl@2x.png
new file mode 100644
index 000000000..a01b02380
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp.png
new file mode 100644
index 000000000..fb9daa337
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp@2x.png
new file mode 100644
index 000000000..a5cfd755b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-pageUp@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode.png
new file mode 100644
index 000000000..3ac21244d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode@2x.png
new file mode 100644
index 000000000..cada9e791
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-presentationMode@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-print.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-print.png
new file mode 100644
index 000000000..51275e54b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-print.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-print@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-print@2x.png
new file mode 100644
index 000000000..53d18daf7
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-print@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-search.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-search.png
new file mode 100644
index 000000000..f9b75579b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-search.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-search@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-search@2x.png
new file mode 100644
index 000000000..456b13324
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-search@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl.png
new file mode 100644
index 000000000..843709527
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png
new file mode 100644
index 000000000..9d9bfa4f6
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle.png
new file mode 100644
index 000000000..1f90f83da
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle@2x.png
new file mode 100644
index 000000000..b066fe5cb
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl.png
new file mode 100644
index 000000000..6f85ec061
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl@2x.png
new file mode 100644
index 000000000..291e00679
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle.png
new file mode 100644
index 000000000..025dc9040
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle@2x.png
new file mode 100644
index 000000000..7f834df94
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-sidebarToggle@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments.png
new file mode 100644
index 000000000..fcd0b268a
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments@2x.png
new file mode 100644
index 000000000..4a5e2b8a3
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewAttachments@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl.png
new file mode 100644
index 000000000..aaa943021
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl@2x.png
new file mode 100644
index 000000000..3410f70df
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline.png
new file mode 100644
index 000000000..976365a50
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline@2x.png
new file mode 100644
index 000000000..b6a197fdf
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewOutline@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail.png
new file mode 100644
index 000000000..584ba5588
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail@2x.png
new file mode 100644
index 000000000..a0208b413
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-viewThumbnail@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn.png
new file mode 100644
index 000000000..513d081bc
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn@2x.png
new file mode 100644
index 000000000..d5d49d5ff
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomIn@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut.png
new file mode 100644
index 000000000..156c26b94
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut@2x.png b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut@2x.png
new file mode 100644
index 000000000..959e1919d
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/toolbarButton-zoomOut@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl.png b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl.png
new file mode 100644
index 000000000..0496b3577
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl@2x.png b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl@2x.png
new file mode 100644
index 000000000..6ad9ebcdf
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/treeitem-collapsed.png b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed.png
new file mode 100644
index 000000000..06d4d3769
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/treeitem-collapsed@2x.png b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed@2x.png
new file mode 100644
index 000000000..eec1e58c1
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/treeitem-collapsed@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/treeitem-expanded.png b/browser/extensions/pdfjs/content/web/images/treeitem-expanded.png
new file mode 100644
index 000000000..c8d557351
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/treeitem-expanded.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/images/treeitem-expanded@2x.png b/browser/extensions/pdfjs/content/web/images/treeitem-expanded@2x.png
new file mode 100644
index 000000000..3b3b6103b
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/images/treeitem-expanded@2x.png
Binary files differ
diff --git a/browser/extensions/pdfjs/content/web/l10n.js b/browser/extensions/pdfjs/content/web/l10n.js
new file mode 100644
index 000000000..2e0684e06
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/l10n.js
@@ -0,0 +1,151 @@
+
+'use strict';
+
+// Small subset of the webL10n API by Fabien Cazenave for pdf.js extension.
+(function(window) {
+ var gLanguage = '';
+ var gExternalLocalizerServices = null;
+
+ // fetch an l10n objects
+ function getL10nData(key) {
+ var response = gExternalLocalizerServices.getStrings(key);
+ var data = JSON.parse(response);
+ if (!data) {
+ console.warn('[l10n] #' + key + ' missing for [' + gLanguage + ']');
+ }
+ return data;
+ }
+
+ // replace {{arguments}} with their values
+ function substArguments(text, args) {
+ if (!args) {
+ return text;
+ }
+ return text.replace(/\{\{\s*(\w+)\s*\}\}/g, function(all, name) {
+ return (name in args ? args[name] : '{{' + name + '}}');
+ });
+ }
+
+ // translate a string
+ function translateString(key, args, fallback) {
+ var i = key.lastIndexOf('.');
+ var name, property;
+ if (i >= 0) {
+ name = key.substring(0, i);
+ property = key.substring(i + 1);
+ } else {
+ name = key;
+ property = 'textContent';
+ }
+ var data = getL10nData(name);
+ var value = (data && data[property]) || fallback;
+ if (!value) {
+ return '{{' + key + '}}';
+ }
+ return substArguments(value, args);
+ }
+
+ // translate an HTML element
+ function translateElement(element) {
+ if (!element || !element.dataset) {
+ return;
+ }
+
+ // get the related l10n object
+ var key = element.dataset.l10nId;
+ var data = getL10nData(key);
+ if (!data) {
+ return;
+ }
+
+ // get arguments (if any)
+ // TODO: more flexible parser?
+ var args;
+ if (element.dataset.l10nArgs) {
+ try {
+ args = JSON.parse(element.dataset.l10nArgs);
+ } catch (e) {
+ console.warn('[l10n] could not parse arguments for #' + key + '');
+ }
+ }
+
+ // translate element
+ // TODO: security check?
+ for (var k in data) {
+ element[k] = substArguments(data[k], args);
+ }
+ }
+
+
+ // translate an HTML subtree
+ function translateFragment(element) {
+ element = element || document.querySelector('html');
+
+ // 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++) {
+ translateElement(children[i]);
+ }
+
+ // translate element itself if necessary
+ if (element.dataset.l10nId) {
+ translateElement(element);
+ }
+ }
+
+ function translateDocument() {
+ gLanguage = gExternalLocalizerServices.getLocale();
+
+ translateFragment();
+
+ // fire a 'localized' DOM event
+ var evtObject = document.createEvent('Event');
+ evtObject.initEvent('localized', false, false);
+ evtObject.language = gLanguage;
+ window.dispatchEvent(evtObject);
+ }
+
+ window.addEventListener('DOMContentLoaded', function() {
+ if (gExternalLocalizerServices) {
+ translateDocument();
+ }
+ // ... else see setExternalLocalizerServices below
+ });
+
+ // Public API
+ document.mozL10n = {
+ // get a localized string
+ get: translateString,
+
+ // get the document language
+ getLanguage: function() {
+ return gLanguage;
+ },
+
+ // get the direction (ltr|rtl) of the current language
+ getDirection: function() {
+ // http://www.w3.org/International/questions/qa-scripts
+ // Arabic, Hebrew, Farsi, Pashto, Urdu
+ var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
+
+ // use the short language code for "full" codes like 'ar-sa' (issue 5440)
+ var shortCode = gLanguage.split('-')[0];
+
+ return (rtlList.indexOf(shortCode) >= 0) ? 'rtl' : 'ltr';
+ },
+
+ setExternalLocalizerServices: function (externalLocalizerServices) {
+ gExternalLocalizerServices = externalLocalizerServices;
+
+ // ... in case if we missed DOMContentLoaded above.
+ if (window.document.readyState === 'interactive' ||
+ window.document.readyState === 'complete') {
+ translateDocument();
+ }
+ },
+
+ // translate an element or document fragment
+ translate: translateFragment
+ };
+})(this);
diff --git a/browser/extensions/pdfjs/content/web/viewer.css b/browser/extensions/pdfjs/content/web/viewer.css
new file mode 100644
index 000000000..74b6fab86
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -0,0 +1,2034 @@
+/* Copyright 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+.textLayer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+ opacity: 0.2;
+ line-height: 1.0;
+}
+
+.textLayer > div {
+ color: transparent;
+ position: absolute;
+ white-space: pre;
+ cursor: text;
+ -moz-transform-origin: 0% 0%;
+ transform-origin: 0% 0%;
+}
+
+.textLayer .highlight {
+ margin: -1px;
+ padding: 1px;
+
+ background-color: rgb(180, 0, 170);
+ border-radius: 4px;
+}
+
+.textLayer .highlight.begin {
+ border-radius: 4px 0px 0px 4px;
+}
+
+.textLayer .highlight.end {
+ border-radius: 0px 4px 4px 0px;
+}
+
+.textLayer .highlight.middle {
+ border-radius: 0px;
+}
+
+.textLayer .highlight.selected {
+ background-color: rgb(0, 100, 0);
+}
+
+.textLayer ::selection { background: rgb(0,0,255); }
+.textLayer ::-moz-selection { background: rgb(0,0,255); }
+
+.textLayer .endOfContent {
+ display: block;
+ position: absolute;
+ left: 0px;
+ top: 100%;
+ right: 0px;
+ bottom: 0px;
+ z-index: -1;
+ cursor: default;
+ -moz-user-select: none;
+}
+
+.textLayer .endOfContent.active {
+ top: 0px;
+}
+
+
+.annotationLayer section {
+ position: absolute;
+}
+
+.annotationLayer .linkAnnotation > a {
+ position: absolute;
+ font-size: 1em;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.annotationLayer .linkAnnotation > a:hover {
+ opacity: 0.2;
+ background: #ff0;
+ box-shadow: 0px 2px 10px #ff0;
+}
+
+.annotationLayer .textAnnotation img {
+ position: absolute;
+ cursor: pointer;
+}
+
+.annotationLayer .textWidgetAnnotation input,
+.annotationLayer .textWidgetAnnotation textarea,
+.annotationLayer .choiceWidgetAnnotation select {
+ background-color: rgba(0, 54, 255, 0.13);
+ border: 1px solid transparent;
+ box-sizing: border-box;
+ font-size: 9px;
+ height: 100%;
+ padding: 0 3px;
+ vertical-align: top;
+ width: 100%;
+}
+
+.annotationLayer .textWidgetAnnotation textarea {
+ font: message-box;
+ font-size: 9px;
+ resize: none;
+}
+
+.annotationLayer .textWidgetAnnotation input[disabled],
+.annotationLayer .textWidgetAnnotation textarea[disabled],
+.annotationLayer .choiceWidgetAnnotation select[disabled] {
+ background: none;
+ border: 1px solid transparent;
+ cursor: not-allowed;
+}
+
+.annotationLayer .textWidgetAnnotation input:hover,
+.annotationLayer .textWidgetAnnotation textarea:hover,
+.annotationLayer .choiceWidgetAnnotation select:hover {
+ border: 1px solid #000;
+}
+
+.annotationLayer .textWidgetAnnotation input:focus,
+.annotationLayer .textWidgetAnnotation textarea:focus,
+.annotationLayer .choiceWidgetAnnotation select:focus {
+ background: none;
+ border: 1px solid transparent;
+}
+
+.annotationLayer .textWidgetAnnotation input.comb {
+ font-family: monospace;
+ padding-left: 2px;
+ padding-right: 0;
+}
+
+.annotationLayer .textWidgetAnnotation input.comb:focus {
+ /*
+ * Letter spacing is placed on the right side of each character. Hence, the
+ * letter spacing of the last character may be placed outside the visible
+ * area, causing horizontal scrolling. We avoid this by extending the width
+ * when the element has focus and revert this when it loses focus.
+ */
+ width: 115%;
+}
+
+.annotationLayer .popupWrapper {
+ position: absolute;
+ width: 20em;
+}
+
+.annotationLayer .popup {
+ position: absolute;
+ z-index: 200;
+ max-width: 20em;
+ background-color: #FFFF99;
+ box-shadow: 0px 2px 5px #333;
+ border-radius: 2px;
+ padding: 0.6em;
+ margin-left: 5px;
+ cursor: pointer;
+ word-wrap: break-word;
+}
+
+.annotationLayer .popup h1 {
+ font-size: 1em;
+ border-bottom: 1px solid #000000;
+ padding-bottom: 0.2em;
+}
+
+.annotationLayer .popup p {
+ padding-top: 0.2em;
+}
+
+.annotationLayer .highlightAnnotation,
+.annotationLayer .underlineAnnotation,
+.annotationLayer .squigglyAnnotation,
+.annotationLayer .strikeoutAnnotation,
+.annotationLayer .fileAttachmentAnnotation {
+ cursor: pointer;
+}
+
+.pdfViewer .canvasWrapper {
+ overflow: hidden;
+}
+
+.pdfViewer .page {
+ direction: ltr;
+ width: 816px;
+ height: 1056px;
+ margin: 1px auto -8px auto;
+ position: relative;
+ overflow: visible;
+ border: 9px solid transparent;
+ background-clip: content-box;
+ border-image: url(images/shadow.png) 9 9 repeat;
+ background-color: white;
+}
+
+.pdfViewer.removePageBorders .page {
+ margin: 0px auto 10px auto;
+ border: none;
+}
+
+.pdfViewer.singlePageView {
+ display: inline-block;
+}
+
+.pdfViewer.singlePageView .page {
+ margin: 0;
+ border: none;
+}
+
+.pdfViewer .page canvas {
+ margin: 0;
+ display: block;
+}
+
+.pdfViewer .page .loadingIcon {
+ position: absolute;
+ display: block;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ background: url('images/loading-icon.gif') center no-repeat;
+}
+
+.pdfPresentationMode:-moz-full-screen .pdfViewer .page {
+ margin-bottom: 100%;
+ border: 0;
+}
+
+.pdfPresentationMode:fullscreen .pdfViewer .page {
+ margin-bottom: 100%;
+ border: 0;
+}
+
+* {
+ padding: 0;
+ margin: 0;
+}
+
+html {
+ height: 100%;
+ width: 100%;
+ /* Font size is needed to make the activity bar the correct size. */
+ font-size: 10px;
+}
+
+body {
+ height: 100%;
+ width: 100%;
+ background-color: #404040;
+ background-image: url(images/texture.png);
+}
+
+body,
+input,
+button,
+select {
+ font: message-box;
+ outline: none;
+}
+
+.hidden {
+ display: none !important;
+}
+[hidden] {
+ display: none !important;
+}
+
+#viewerContainer.pdfPresentationMode:-moz-full-screen {
+ top: 0px;
+ border-top: 2px solid transparent;
+ background-color: #000;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ cursor: none;
+ -moz-user-select: none;
+}
+
+#viewerContainer.pdfPresentationMode:fullscreen {
+ top: 0px;
+ border-top: 2px solid transparent;
+ background-color: #000;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ cursor: none;
+ -moz-user-select: none;
+}
+
+.pdfPresentationMode:-moz-full-screen a:not(.internalLink) {
+ display: none;
+}
+
+.pdfPresentationMode:fullscreen a:not(.internalLink) {
+ display: none;
+}
+
+.pdfPresentationMode:-moz-full-screen .textLayer > div {
+ cursor: none;
+}
+
+.pdfPresentationMode:fullscreen .textLayer > div {
+ cursor: none;
+}
+
+.pdfPresentationMode.pdfPresentationModeControls > *,
+.pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
+ cursor: default;
+}
+
+#outerContainer {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
+#sidebarContainer {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 200px;
+ visibility: hidden;
+ transition-duration: 200ms;
+ transition-timing-function: ease;
+
+}
+html[dir='ltr'] #sidebarContainer {
+ transition-property: left;
+ left: -200px;
+}
+html[dir='rtl'] #sidebarContainer {
+ transition-property: right;
+ right: -200px;
+}
+
+#outerContainer.sidebarMoving > #sidebarContainer,
+#outerContainer.sidebarOpen > #sidebarContainer {
+ visibility: visible;
+}
+html[dir='ltr'] #outerContainer.sidebarOpen > #sidebarContainer {
+ left: 0px;
+}
+html[dir='rtl'] #outerContainer.sidebarOpen > #sidebarContainer {
+ right: 0px;
+}
+
+#mainContainer {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ min-width: 320px;
+ transition-duration: 200ms;
+ transition-timing-function: ease;
+}
+html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
+ transition-property: left;
+ left: 200px;
+}
+html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
+ transition-property: right;
+ right: 200px;
+}
+
+#sidebarContent {
+ top: 32px;
+ bottom: 0;
+ overflow: auto;
+ position: absolute;
+ width: 200px;
+ background-color: hsla(0,0%,0%,.1);
+}
+html[dir='ltr'] #sidebarContent {
+ left: 0;
+ box-shadow: inset -1px 0 0 hsla(0,0%,0%,.25);
+}
+html[dir='rtl'] #sidebarContent {
+ right: 0;
+ box-shadow: inset 1px 0 0 hsla(0,0%,0%,.25);
+}
+
+#viewerContainer {
+ overflow: auto;
+ position: absolute;
+ top: 32px;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ outline: none;
+}
+html[dir='ltr'] #viewerContainer {
+ box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
+}
+html[dir='rtl'] #viewerContainer {
+ box-shadow: inset -1px 0 0 hsla(0,0%,100%,.05);
+}
+
+.toolbar {
+ position: relative;
+ left: 0;
+ right: 0;
+ z-index: 9999;
+ cursor: default;
+}
+
+#toolbarContainer {
+ width: 100%;
+}
+
+#toolbarSidebar {
+ width: 200px;
+ height: 32px;
+ background-color: #424242; /* fallback */
+ background-image: url(images/texture.png),
+ linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
+}
+html[dir='ltr'] #toolbarSidebar {
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25),
+ inset 0 -1px 0 hsla(0,0%,100%,.05),
+ 0 1px 0 hsla(0,0%,0%,.15),
+ 0 0 1px hsla(0,0%,0%,.1);
+}
+html[dir='rtl'] #toolbarSidebar {
+ box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.25),
+ inset 0 1px 0 hsla(0,0%,100%,.05),
+ 0 1px 0 hsla(0,0%,0%,.15),
+ 0 0 1px hsla(0,0%,0%,.1);
+}
+
+#toolbarContainer, .findbar, .secondaryToolbar {
+ position: relative;
+ height: 32px;
+ background-color: #474747; /* fallback */
+ background-image: url(images/texture.png),
+ linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
+}
+html[dir='ltr'] #toolbarContainer, .findbar, .secondaryToolbar {
+ box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
+ inset 0 1px 1px hsla(0,0%,0%,.15),
+ inset 0 -1px 0 hsla(0,0%,100%,.05),
+ 0 1px 0 hsla(0,0%,0%,.15),
+ 0 1px 1px hsla(0,0%,0%,.1);
+}
+html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
+ box-shadow: inset -1px 0 0 hsla(0,0%,100%,.08),
+ inset 0 1px 1px hsla(0,0%,0%,.15),
+ inset 0 -1px 0 hsla(0,0%,100%,.05),
+ 0 1px 0 hsla(0,0%,0%,.15),
+ 0 1px 1px hsla(0,0%,0%,.1);
+}
+
+#toolbarViewer {
+ height: 32px;
+}
+
+#loadingBar {
+ position: relative;
+ width: 100%;
+ height: 4px;
+ background-color: #333;
+ border-bottom: 1px solid #333;
+}
+
+#loadingBar .progress {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 0%;
+ height: 100%;
+ background-color: #ddd;
+ overflow: hidden;
+ transition: width 200ms;
+}
+
+@keyframes progressIndeterminate {
+ 0% { left: -142px; }
+ 100% { left: 0; }
+}
+
+#loadingBar .progress.indeterminate {
+ background-color: #999;
+ transition: none;
+}
+
+#loadingBar .progress.indeterminate .glimmer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: calc(100% + 150px);
+
+ background: repeating-linear-gradient(135deg,
+ #bbb 0, #999 5px,
+ #999 45px, #ddd 55px,
+ #ddd 95px, #bbb 100px);
+
+ animation: progressIndeterminate 950ms linear infinite;
+}
+
+.findbar, .secondaryToolbar {
+ top: 32px;
+ position: absolute;
+ z-index: 10000;
+ height: 32px;
+
+ min-width: 16px;
+ padding: 0px 6px 0px 6px;
+ margin: 4px 2px 4px 2px;
+ color: hsl(0,0%,85%);
+ font-size: 12px;
+ line-height: 14px;
+ text-align: left;
+ cursor: default;
+}
+
+html[dir='ltr'] .findbar {
+ left: 68px;
+}
+
+html[dir='rtl'] .findbar {
+ right: 68px;
+}
+
+.findbar label {
+ -moz-user-select: none;
+}
+
+#findInput[data-status="pending"] {
+ background-image: url(images/loading-small.png);
+ background-repeat: no-repeat;
+ background-position: right;
+}
+html[dir='rtl'] #findInput[data-status="pending"] {
+ background-position: left;
+}
+
+.secondaryToolbar {
+ padding: 6px;
+ height: auto;
+ z-index: 30000;
+}
+html[dir='ltr'] .secondaryToolbar {
+ right: 4px;
+}
+html[dir='rtl'] .secondaryToolbar {
+ left: 4px;
+}
+
+#secondaryToolbarButtonContainer {
+ max-width: 200px;
+ max-height: 400px;
+ overflow-y: auto;
+ margin-bottom: -4px;
+}
+
+.doorHanger,
+.doorHangerRight {
+ border: 1px solid hsla(0,0%,0%,.5);
+ border-radius: 2px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+}
+.doorHanger:after, .doorHanger:before,
+.doorHangerRight:after, .doorHangerRight:before {
+ bottom: 100%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+}
+.doorHanger:after,
+.doorHangerRight:after {
+ border-bottom-color: hsla(0,0%,32%,.99);
+ border-width: 8px;
+}
+.doorHanger:before,
+.doorHangerRight:before {
+ border-bottom-color: hsla(0,0%,0%,.5);
+ border-width: 9px;
+}
+
+html[dir='ltr'] .doorHanger:after,
+html[dir='rtl'] .doorHangerRight:after {
+ left: 13px;
+ margin-left: -8px;
+}
+
+html[dir='ltr'] .doorHanger:before,
+html[dir='rtl'] .doorHangerRight:before {
+ left: 13px;
+ margin-left: -9px;
+}
+
+html[dir='rtl'] .doorHanger:after,
+html[dir='ltr'] .doorHangerRight:after {
+ right: 13px;
+ margin-right: -8px;
+}
+
+html[dir='rtl'] .doorHanger:before,
+html[dir='ltr'] .doorHangerRight:before {
+ right: 13px;
+ margin-right: -9px;
+}
+
+#findResultsCount {
+ background-color: hsl(0, 0%, 85%);
+ color: hsl(0, 0%, 32%);
+ text-align: center;
+ padding: 3px 4px;
+}
+
+#findMsg {
+ font-style: italic;
+ color: #A6B7D0;
+}
+
+#findInput.notFound {
+ background-color: rgb(255, 102, 102);
+}
+
+#toolbarViewerMiddle {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+html[dir='ltr'] #toolbarViewerLeft,
+html[dir='rtl'] #toolbarViewerRight {
+ float: left;
+}
+html[dir='ltr'] #toolbarViewerRight,
+html[dir='rtl'] #toolbarViewerLeft {
+ float: right;
+}
+html[dir='ltr'] #toolbarViewerLeft > *,
+html[dir='ltr'] #toolbarViewerMiddle > *,
+html[dir='ltr'] #toolbarViewerRight > *,
+html[dir='ltr'] .findbar > * {
+ position: relative;
+ float: left;
+}
+html[dir='rtl'] #toolbarViewerLeft > *,
+html[dir='rtl'] #toolbarViewerMiddle > *,
+html[dir='rtl'] #toolbarViewerRight > *,
+html[dir='rtl'] .findbar > * {
+ position: relative;
+ float: right;
+}
+
+html[dir='ltr'] .splitToolbarButton {
+ margin: 3px 2px 4px 0;
+ display: inline-block;
+}
+html[dir='rtl'] .splitToolbarButton {
+ margin: 3px 0 4px 2px;
+ display: inline-block;
+}
+html[dir='ltr'] .splitToolbarButton > .toolbarButton {
+ border-radius: 0;
+ float: left;
+}
+html[dir='rtl'] .splitToolbarButton > .toolbarButton {
+ border-radius: 0;
+ float: right;
+}
+
+.toolbarButton,
+.secondaryToolbarButton,
+.overlayButton {
+ border: 0 none;
+ background: none;
+ width: 32px;
+ height: 25px;
+}
+
+.toolbarButton > span {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+}
+
+.toolbarButton[disabled],
+.secondaryToolbarButton[disabled],
+.overlayButton[disabled] {
+ opacity: .5;
+}
+
+.toolbarButton.group {
+ margin-right: 0;
+}
+
+.splitToolbarButton.toggled .toolbarButton {
+ margin: 0;
+}
+
+.splitToolbarButton:hover > .toolbarButton,
+.splitToolbarButton:focus > .toolbarButton,
+.splitToolbarButton.toggled > .toolbarButton,
+.toolbarButton.textButton {
+ background-color: hsla(0,0%,0%,.12);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ background-clip: padding-box;
+ border: 1px solid hsla(0,0%,0%,.35);
+ border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
+ 0 0 1px hsla(0,0%,100%,.15) inset,
+ 0 1px 0 hsla(0,0%,100%,.05);
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+ transition-timing-function: ease;
+
+}
+.splitToolbarButton > .toolbarButton:hover,
+.splitToolbarButton > .toolbarButton:focus,
+.dropdownToolbarButton:hover,
+.overlayButton:hover,
+.overlayButton:focus,
+.toolbarButton.textButton:hover,
+.toolbarButton.textButton:focus {
+ background-color: hsla(0,0%,0%,.2);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
+ 0 0 1px hsla(0,0%,100%,.15) inset,
+ 0 0 1px hsla(0,0%,0%,.05);
+ z-index: 199;
+}
+.splitToolbarButton > .toolbarButton {
+ position: relative;
+}
+html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child,
+html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child {
+ position: relative;
+ margin: 0;
+ margin-right: -1px;
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+ border-right-color: transparent;
+}
+html[dir='ltr'] .splitToolbarButton > .toolbarButton:last-child,
+html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child {
+ position: relative;
+ margin: 0;
+ margin-left: -1px;
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+ border-left-color: transparent;
+}
+.splitToolbarButtonSeparator {
+ padding: 8px 0;
+ width: 1px;
+ background-color: hsla(0,0%,0%,.5);
+ z-index: 99;
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
+ display: inline-block;
+ margin: 5px 0;
+}
+html[dir='ltr'] .splitToolbarButtonSeparator {
+ float: left;
+}
+html[dir='rtl'] .splitToolbarButtonSeparator {
+ float: right;
+}
+.splitToolbarButton:hover > .splitToolbarButtonSeparator,
+.splitToolbarButton.toggled > .splitToolbarButtonSeparator {
+ padding: 12px 0;
+ margin: 1px 0;
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
+ transition-property: padding;
+ transition-duration: 10ms;
+ transition-timing-function: ease;
+}
+
+.toolbarButton,
+.dropdownToolbarButton,
+.secondaryToolbarButton,
+.overlayButton {
+ min-width: 16px;
+ padding: 2px 6px 0;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ color: hsla(0,0%,100%,.8);
+ font-size: 12px;
+ line-height: 14px;
+ -moz-user-select: none;
+ /* Opera does not support user-select, use <... unselectable="on"> instead */
+ cursor: default;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+ transition-timing-function: ease;
+}
+
+html[dir='ltr'] .toolbarButton,
+html[dir='ltr'] .overlayButton,
+html[dir='ltr'] .dropdownToolbarButton {
+ margin: 3px 2px 4px 0;
+}
+html[dir='rtl'] .toolbarButton,
+html[dir='rtl'] .overlayButton,
+html[dir='rtl'] .dropdownToolbarButton {
+ margin: 3px 0 4px 2px;
+}
+
+.toolbarButton:hover,
+.toolbarButton:focus,
+.dropdownToolbarButton,
+.overlayButton,
+.secondaryToolbarButton:hover,
+.secondaryToolbarButton:focus {
+ background-color: hsla(0,0%,0%,.12);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ background-clip: padding-box;
+ border: 1px solid hsla(0,0%,0%,.35);
+ border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
+ 0 0 1px hsla(0,0%,100%,.15) inset,
+ 0 1px 0 hsla(0,0%,100%,.05);
+}
+
+.toolbarButton:hover:active,
+.overlayButton:hover:active,
+.dropdownToolbarButton:hover:active,
+.secondaryToolbarButton:hover:active {
+ background-color: hsla(0,0%,0%,.2);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45);
+ box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
+ 0 0 1px hsla(0,0%,0%,.2) inset,
+ 0 1px 0 hsla(0,0%,100%,.05);
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 10ms;
+ transition-timing-function: linear;
+}
+
+.toolbarButton.toggled,
+.splitToolbarButton.toggled > .toolbarButton.toggled,
+.secondaryToolbarButton.toggled {
+ background-color: hsla(0,0%,0%,.3);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.45) hsla(0,0%,0%,.5);
+ box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
+ 0 0 1px hsla(0,0%,0%,.2) inset,
+ 0 1px 0 hsla(0,0%,100%,.05);
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 10ms;
+ transition-timing-function: linear;
+}
+
+.toolbarButton.toggled:hover:active,
+.splitToolbarButton.toggled > .toolbarButton.toggled:hover:active,
+.secondaryToolbarButton.toggled:hover:active {
+ background-color: hsla(0,0%,0%,.4);
+ border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55);
+ box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset,
+ 0 0 1px hsla(0,0%,0%,.3) inset,
+ 0 1px 0 hsla(0,0%,100%,.05);
+}
+
+.dropdownToolbarButton {
+ width: 120px;
+ max-width: 120px;
+ padding: 0;
+ overflow: hidden;
+ background: url(images/toolbarButton-menuArrows.png) no-repeat;
+}
+html[dir='ltr'] .dropdownToolbarButton {
+ background-position: 95%;
+}
+html[dir='rtl'] .dropdownToolbarButton {
+ background-position: 5%;
+}
+
+.dropdownToolbarButton > select {
+ min-width: 140px;
+ font-size: 12px;
+ color: hsl(0,0%,95%);
+ margin: 0;
+ padding: 3px 2px 2px;
+ border: none;
+ background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */
+}
+
+.dropdownToolbarButton > select > option {
+ background: hsl(0,0%,24%);
+}
+
+#customScaleOption {
+ display: none;
+}
+
+#pageWidthOption {
+ border-bottom: 1px rgba(255, 255, 255, .5) solid;
+}
+
+html[dir='ltr'] .splitToolbarButton:first-child,
+html[dir='ltr'] .toolbarButton:first-child,
+html[dir='rtl'] .splitToolbarButton:last-child,
+html[dir='rtl'] .toolbarButton:last-child {
+ margin-left: 4px;
+}
+html[dir='ltr'] .splitToolbarButton:last-child,
+html[dir='ltr'] .toolbarButton:last-child,
+html[dir='rtl'] .splitToolbarButton:first-child,
+html[dir='rtl'] .toolbarButton:first-child {
+ margin-right: 4px;
+}
+
+.toolbarButtonSpacer {
+ width: 30px;
+ display: inline-block;
+ height: 1px;
+}
+
+.toolbarButtonFlexibleSpacer {
+ -moz-box-flex: 1;
+ min-width: 30px;
+}
+
+html[dir='ltr'] #findPrevious {
+ margin-left: 3px;
+}
+html[dir='ltr'] #findNext {
+ margin-right: 3px;
+}
+
+html[dir='rtl'] #findPrevious {
+ margin-right: 3px;
+}
+html[dir='rtl'] #findNext {
+ margin-left: 3px;
+}
+
+.toolbarButton::before,
+.secondaryToolbarButton::before {
+ /* All matching images have a size of 16x16
+ * All relevant containers have a size of 32x25 */
+ position: absolute;
+ display: inline-block;
+ top: 4px;
+ left: 7px;
+}
+
+html[dir="ltr"] .secondaryToolbarButton::before {
+ left: 4px;
+}
+html[dir="rtl"] .secondaryToolbarButton::before {
+ right: 4px;
+}
+
+html[dir='ltr'] .toolbarButton#sidebarToggle::before {
+ content: url(images/toolbarButton-sidebarToggle.png);
+}
+html[dir='rtl'] .toolbarButton#sidebarToggle::before {
+ content: url(images/toolbarButton-sidebarToggle-rtl.png);
+}
+
+html[dir='ltr'] .toolbarButton#secondaryToolbarToggle::before {
+ content: url(images/toolbarButton-secondaryToolbarToggle.png);
+}
+html[dir='rtl'] .toolbarButton#secondaryToolbarToggle::before {
+ content: url(images/toolbarButton-secondaryToolbarToggle-rtl.png);
+}
+
+html[dir='ltr'] .toolbarButton.findPrevious::before {
+ content: url(images/findbarButton-previous.png);
+}
+html[dir='rtl'] .toolbarButton.findPrevious::before {
+ content: url(images/findbarButton-previous-rtl.png);
+}
+
+html[dir='ltr'] .toolbarButton.findNext::before {
+ content: url(images/findbarButton-next.png);
+}
+html[dir='rtl'] .toolbarButton.findNext::before {
+ content: url(images/findbarButton-next-rtl.png);
+}
+
+html[dir='ltr'] .toolbarButton.pageUp::before {
+ content: url(images/toolbarButton-pageUp.png);
+}
+html[dir='rtl'] .toolbarButton.pageUp::before {
+ content: url(images/toolbarButton-pageUp-rtl.png);
+}
+
+html[dir='ltr'] .toolbarButton.pageDown::before {
+ content: url(images/toolbarButton-pageDown.png);
+}
+html[dir='rtl'] .toolbarButton.pageDown::before {
+ content: url(images/toolbarButton-pageDown-rtl.png);
+}
+
+.toolbarButton.zoomOut::before {
+ content: url(images/toolbarButton-zoomOut.png);
+}
+
+.toolbarButton.zoomIn::before {
+ content: url(images/toolbarButton-zoomIn.png);
+}
+
+.toolbarButton.presentationMode::before,
+.secondaryToolbarButton.presentationMode::before {
+ content: url(images/toolbarButton-presentationMode.png);
+}
+
+.toolbarButton.print::before,
+.secondaryToolbarButton.print::before {
+ content: url(images/toolbarButton-print.png);
+}
+
+.toolbarButton.openFile::before,
+.secondaryToolbarButton.openFile::before {
+ content: url(images/toolbarButton-openFile.png);
+}
+
+.toolbarButton.download::before,
+.secondaryToolbarButton.download::before {
+ content: url(images/toolbarButton-download.png);
+}
+
+.toolbarButton.bookmark,
+.secondaryToolbarButton.bookmark {
+ box-sizing: border-box;
+ outline: none;
+ padding-top: 4px;
+ text-decoration: none;
+}
+.secondaryToolbarButton.bookmark {
+ padding-top: 5px;
+}
+
+.bookmark[href='#'] {
+ opacity: .5;
+ pointer-events: none;
+}
+
+.toolbarButton.bookmark::before,
+.secondaryToolbarButton.bookmark::before {
+ content: url(images/toolbarButton-bookmark.png);
+}
+
+#viewThumbnail.toolbarButton::before {
+ content: url(images/toolbarButton-viewThumbnail.png);
+}
+
+html[dir="ltr"] #viewOutline.toolbarButton::before {
+ content: url(images/toolbarButton-viewOutline.png);
+}
+html[dir="rtl"] #viewOutline.toolbarButton::before {
+ content: url(images/toolbarButton-viewOutline-rtl.png);
+}
+
+#viewAttachments.toolbarButton::before {
+ content: url(images/toolbarButton-viewAttachments.png);
+}
+
+#viewFind.toolbarButton::before {
+ content: url(images/toolbarButton-search.png);
+}
+
+.secondaryToolbarButton {
+ position: relative;
+ margin: 0 0 4px 0;
+ padding: 3px 0 1px 0;
+ height: auto;
+ min-height: 25px;
+ width: auto;
+ min-width: 100%;
+ white-space: normal;
+}
+html[dir="ltr"] .secondaryToolbarButton {
+ padding-left: 24px;
+ text-align: left;
+}
+html[dir="rtl"] .secondaryToolbarButton {
+ padding-right: 24px;
+ text-align: right;
+}
+html[dir="ltr"] .secondaryToolbarButton.bookmark {
+ padding-left: 27px;
+}
+html[dir="rtl"] .secondaryToolbarButton.bookmark {
+ padding-right: 27px;
+}
+
+html[dir="ltr"] .secondaryToolbarButton > span {
+ padding-right: 4px;
+}
+html[dir="rtl"] .secondaryToolbarButton > span {
+ padding-left: 4px;
+}
+
+.secondaryToolbarButton.firstPage::before {
+ content: url(images/secondaryToolbarButton-firstPage.png);
+}
+
+.secondaryToolbarButton.lastPage::before {
+ content: url(images/secondaryToolbarButton-lastPage.png);
+}
+
+.secondaryToolbarButton.rotateCcw::before {
+ content: url(images/secondaryToolbarButton-rotateCcw.png);
+}
+
+.secondaryToolbarButton.rotateCw::before {
+ content: url(images/secondaryToolbarButton-rotateCw.png);
+}
+
+.secondaryToolbarButton.handTool::before {
+ content: url(images/secondaryToolbarButton-handTool.png);
+}
+
+.secondaryToolbarButton.documentProperties::before {
+ content: url(images/secondaryToolbarButton-documentProperties.png);
+}
+
+.verticalToolbarSeparator {
+ display: block;
+ padding: 8px 0;
+ margin: 8px 4px;
+ width: 1px;
+ background-color: hsla(0,0%,0%,.5);
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
+}
+html[dir='ltr'] .verticalToolbarSeparator {
+ margin-left: 2px;
+}
+html[dir='rtl'] .verticalToolbarSeparator {
+ margin-right: 2px;
+}
+
+.horizontalToolbarSeparator {
+ display: block;
+ margin: 0 0 4px 0;
+ height: 1px;
+ width: 100%;
+ background-color: hsla(0,0%,0%,.5);
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
+}
+
+.toolbarField {
+ padding: 3px 6px;
+ margin: 4px 0 4px 0;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ background-color: hsla(0,0%,100%,.09);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ background-clip: padding-box;
+ border: 1px solid hsla(0,0%,0%,.35);
+ border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
+ box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset,
+ 0 1px 0 hsla(0,0%,100%,.05);
+ color: hsl(0,0%,95%);
+ font-size: 12px;
+ line-height: 14px;
+ outline-style: none;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+ transition-timing-function: ease;
+}
+
+.toolbarField[type=checkbox] {
+ display: inline-block;
+ margin: 8px 0px;
+}
+
+.toolbarField.pageNumber {
+ -moz-appearance: textfield; /* hides the spinner in moz */
+ min-width: 16px;
+ text-align: right;
+ width: 40px;
+}
+
+.toolbarField.pageNumber.visiblePageIsLoading {
+ background-image: url(images/loading-small.png);
+ background-repeat: no-repeat;
+ background-position: 1px;
+}
+
+.toolbarField:hover {
+ background-color: hsla(0,0%,100%,.11);
+ border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45);
+}
+
+.toolbarField:focus {
+ background-color: hsla(0,0%,100%,.15);
+ border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9);
+}
+
+.toolbarLabel {
+ min-width: 16px;
+ padding: 3px 6px 3px 2px;
+ margin: 4px 2px 4px 0;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ color: hsl(0,0%,85%);
+ font-size: 12px;
+ line-height: 14px;
+ text-align: left;
+ -moz-user-select: none;
+ cursor: default;
+}
+
+#thumbnailView {
+ position: absolute;
+ width: 120px;
+ top: 0;
+ bottom: 0;
+ padding: 10px 40px 0;
+ overflow: auto;
+}
+
+.thumbnail {
+ float: left;
+ margin-bottom: 5px;
+}
+
+#thumbnailView > a:last-of-type > .thumbnail {
+ margin-bottom: 10px;
+}
+
+#thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) {
+ margin-bottom: 9px;
+}
+
+.thumbnail:not([data-loaded]) {
+ border: 1px dashed rgba(255, 255, 255, 0.5);
+ margin: -1px -1px 4px -1px;
+}
+
+.thumbnailImage {
+ border: 1px solid transparent;
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
+ opacity: 0.8;
+ z-index: 99;
+ background-color: white;
+ background-clip: content-box;
+}
+
+.thumbnailSelectionRing {
+ border-radius: 2px;
+ padding: 7px;
+}
+
+a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
+.thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
+ opacity: .9;
+}
+
+a:focus > .thumbnail > .thumbnailSelectionRing,
+.thumbnail:hover > .thumbnailSelectionRing {
+ background-color: hsla(0,0%,100%,.15);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ background-clip: padding-box;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
+ 0 0 1px hsla(0,0%,100%,.2) inset,
+ 0 0 1px hsla(0,0%,0%,.2);
+ color: hsla(0,0%,100%,.9);
+}
+
+.thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage {
+ box-shadow: 0 0 0 1px hsla(0,0%,0%,.5);
+ opacity: 1;
+}
+
+.thumbnail.selected > .thumbnailSelectionRing {
+ background-color: hsla(0,0%,100%,.3);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ background-clip: padding-box;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
+ 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 0 1px hsla(0,0%,0%,.2);
+ color: hsla(0,0%,100%,1);
+}
+
+#outlineView,
+#attachmentsView {
+ position: absolute;
+ width: 192px;
+ top: 0;
+ bottom: 0;
+ overflow: auto;
+ -moz-user-select: none;
+}
+
+#outlineView {
+ padding: 4px 4px 0;
+}
+#attachmentsView {
+ padding: 3px 4px 0;
+}
+
+html[dir='ltr'] .outlineWithDeepNesting > .outlineItem,
+html[dir='ltr'] .outlineItem > .outlineItems {
+ margin-left: 20px;
+}
+
+html[dir='rtl'] .outlineWithDeepNesting > .outlineItem,
+html[dir='rtl'] .outlineItem > .outlineItems {
+ margin-right: 20px;
+}
+
+.outlineItem > a,
+.attachmentsItem > button {
+ text-decoration: none;
+ display: inline-block;
+ min-width: 95%;
+ min-width: calc(100% - 4px); /* Subtract the right padding (left, in RTL mode)
+ of the container. */
+ height: auto;
+ margin-bottom: 1px;
+ border-radius: 2px;
+ color: hsla(0,0%,100%,.8);
+ font-size: 13px;
+ line-height: 15px;
+ -moz-user-select: none;
+ white-space: normal;
+}
+
+.attachmentsItem > button {
+ border: 0 none;
+ background: none;
+ cursor: pointer;
+ width: 100%;
+}
+
+html[dir='ltr'] .outlineItem > a {
+ padding: 2px 0 5px 4px;
+}
+html[dir='ltr'] .attachmentsItem > button {
+ padding: 2px 0 3px 7px;
+ text-align: left;
+}
+
+html[dir='rtl'] .outlineItem > a {
+ padding: 2px 4px 5px 0;
+}
+html[dir='rtl'] .attachmentsItem > button {
+ padding: 2px 7px 3px 0;
+ text-align: right;
+}
+
+.outlineItemToggler {
+ position: relative;
+ height: 0;
+ width: 0;
+ color: hsla(0,0%,100%,.5);
+}
+.outlineItemToggler::before {
+ content: url(images/treeitem-expanded.png);
+ display: inline-block;
+ position: absolute;
+}
+html[dir='ltr'] .outlineItemToggler.outlineItemsHidden::before {
+ content: url(images/treeitem-collapsed.png);
+}
+html[dir='rtl'] .outlineItemToggler.outlineItemsHidden::before {
+ content: url(images/treeitem-collapsed-rtl.png);
+}
+.outlineItemToggler.outlineItemsHidden ~ .outlineItems {
+ display: none;
+}
+html[dir='ltr'] .outlineItemToggler {
+ float: left;
+}
+html[dir='rtl'] .outlineItemToggler {
+ float: right;
+}
+html[dir='ltr'] .outlineItemToggler::before {
+ right: 4px;
+}
+html[dir='rtl'] .outlineItemToggler::before {
+ left: 4px;
+}
+
+.outlineItemToggler:hover,
+.outlineItemToggler:hover + a,
+.outlineItemToggler:hover ~ .outlineItems,
+.outlineItem > a:hover,
+.attachmentsItem > button:hover {
+ background-color: hsla(0,0%,100%,.02);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ background-clip: padding-box;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
+ 0 0 1px hsla(0,0%,100%,.2) inset,
+ 0 0 1px hsla(0,0%,0%,.2);
+ border-radius: 2px;
+ color: hsla(0,0%,100%,.9);
+}
+
+.outlineItem.selected {
+ background-color: hsla(0,0%,100%,.08);
+ background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+ background-clip: padding-box;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
+ 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 0 1px hsla(0,0%,0%,.2);
+ color: hsla(0,0%,100%,1);
+}
+
+.noResults {
+ font-size: 12px;
+ color: hsla(0,0%,100%,.8);
+ font-style: italic;
+ cursor: default;
+}
+
+/* TODO: file FF bug to support ::-moz-selection:window-inactive
+ so we can override the opaque grey background when the window is inactive;
+ see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
+::selection { background: rgba(0,0,255,0.3); }
+::-moz-selection { background: rgba(0,0,255,0.3); }
+
+#errorWrapper {
+ background: none repeat scroll 0 0 #FF5555;
+ color: white;
+ left: 0;
+ position: absolute;
+ right: 0;
+ z-index: 1000;
+ padding: 3px;
+ font-size: 0.8em;
+}
+.loadingInProgress #errorWrapper {
+ top: 37px;
+}
+
+#errorMessageLeft {
+ float: left;
+}
+
+#errorMessageRight {
+ float: right;
+}
+
+#errorMoreInfo {
+ background-color: #FFFFFF;
+ color: black;
+ padding: 3px;
+ margin: 3px;
+ width: 98%;
+}
+
+.overlayButton {
+ width: auto;
+ margin: 3px 4px 2px 4px !important;
+ padding: 2px 6px 3px 6px;
+}
+
+#overlayContainer {
+ display: table;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: hsla(0,0%,0%,.2);
+ z-index: 40000;
+}
+#overlayContainer > * {
+ overflow: auto;
+}
+
+#overlayContainer > .container {
+ display: table-cell;
+ vertical-align: middle;
+ text-align: center;
+}
+
+#overlayContainer > .container > .dialog {
+ display: inline-block;
+ padding: 15px;
+ border-spacing: 4px;
+ color: hsl(0,0%,85%);
+ font-size: 12px;
+ line-height: 14px;
+ background-color: #474747; /* fallback */
+ background-image: url(images/texture.png),
+ linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
+ box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
+ inset 0 1px 1px hsla(0,0%,0%,.15),
+ inset 0 -1px 0 hsla(0,0%,100%,.05),
+ 0 1px 0 hsla(0,0%,0%,.15),
+ 0 1px 1px hsla(0,0%,0%,.1);
+ border: 1px solid hsla(0,0%,0%,.5);
+ border-radius: 4px;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
+}
+
+.dialog > .row {
+ display: table-row;
+}
+
+.dialog > .row > * {
+ display: table-cell;
+}
+
+.dialog .toolbarField {
+ margin: 5px 0;
+}
+
+.dialog .separator {
+ display: block;
+ margin: 4px 0 4px 0;
+ height: 1px;
+ width: 100%;
+ background-color: hsla(0,0%,0%,.5);
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
+}
+
+.dialog .buttonRow {
+ text-align: center;
+ vertical-align: middle;
+}
+
+.dialog :link {
+ color: white;
+}
+
+#passwordOverlay > .dialog {
+ text-align: center;
+}
+#passwordOverlay .toolbarField {
+ width: 200px;
+}
+
+#documentPropertiesOverlay > .dialog {
+ text-align: left;
+}
+#documentPropertiesOverlay .row > * {
+ min-width: 100px;
+}
+html[dir='ltr'] #documentPropertiesOverlay .row > * {
+ text-align: left;
+}
+html[dir='rtl'] #documentPropertiesOverlay .row > * {
+ text-align: right;
+}
+#documentPropertiesOverlay .row > span {
+ width: 125px;
+ word-wrap: break-word;
+}
+#documentPropertiesOverlay .row > p {
+ max-width: 225px;
+ word-wrap: break-word;
+}
+#documentPropertiesOverlay .buttonRow {
+ margin-top: 10px;
+}
+
+.clearBoth {
+ clear: both;
+}
+
+.fileInput {
+ background: white;
+ color: black;
+ margin-top: 5px;
+ visibility: hidden;
+ position: fixed;
+ right: 0;
+ top: 0;
+}
+
+#PDFBug {
+ background: none repeat scroll 0 0 white;
+ border: 1px solid #666666;
+ position: fixed;
+ top: 32px;
+ right: 0;
+ bottom: 0;
+ font-size: 10px;
+ padding: 0;
+ width: 300px;
+}
+#PDFBug .controls {
+ background:#EEEEEE;
+ border-bottom: 1px solid #666666;
+ padding: 3px;
+}
+#PDFBug .panels {
+ bottom: 0;
+ left: 0;
+ overflow: auto;
+ position: absolute;
+ right: 0;
+ top: 27px;
+}
+#PDFBug button.active {
+ font-weight: bold;
+}
+.debuggerShowText {
+ background: none repeat scroll 0 0 yellow;
+ color: blue;
+}
+.debuggerHideText:hover {
+ background: none repeat scroll 0 0 yellow;
+}
+#PDFBug .stats {
+ font-family: courier;
+ font-size: 10px;
+ white-space: pre;
+}
+#PDFBug .stats .title {
+ font-weight: bold;
+}
+#PDFBug table {
+ font-size: 10px;
+}
+
+#viewer.textLayer-visible .textLayer {
+ opacity: 1.0;
+}
+
+#viewer.textLayer-visible .canvasWrapper {
+ background-color: rgb(128,255,128);
+}
+
+#viewer.textLayer-visible .canvasWrapper canvas {
+ mix-blend-mode: screen;
+}
+
+#viewer.textLayer-visible .textLayer > div {
+ background-color: rgba(255, 255, 0, 0.1);
+ color: black;
+ border: solid 1px rgba(255, 0, 0, 0.5);
+ box-sizing: border-box;
+}
+
+#viewer.textLayer-hover .textLayer > div:hover {
+ background-color: white;
+ color: black;
+}
+
+#viewer.textLayer-shadow .textLayer > div {
+ background-color: rgba(255,255,255, .6);
+ color: black;
+}
+
+.grab-to-pan-grab {
+ cursor: url("images/grab.cur"), move !important;
+ cursor: grab !important;
+}
+.grab-to-pan-grab *:not(input):not(textarea):not(button):not(select):not(:link) {
+ cursor: inherit !important;
+}
+.grab-to-pan-grab:active,
+.grab-to-pan-grabbing {
+ cursor: url("images/grabbing.cur"), move !important;
+ cursor: grabbing !important;
+
+ position: fixed;
+ background: transparent;
+ display: block;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+ z-index: 50000; /* should be higher than anything else in PDF.js! */
+}
+
+@page {
+ margin: 0;
+}
+
+#printContainer {
+ display: none;
+}
+
+@media screen and (min-resolution: 2dppx) {
+ /* Rules for Retina screens */
+ .toolbarButton::before {
+ transform: scale(0.5);
+ top: -5px;
+ }
+
+ .secondaryToolbarButton::before {
+ transform: scale(0.5);
+ top: -4px;
+ }
+
+ html[dir='ltr'] .toolbarButton::before,
+ html[dir='rtl'] .toolbarButton::before {
+ left: -1px;
+ }
+
+ html[dir='ltr'] .secondaryToolbarButton::before {
+ left: -2px;
+ }
+ html[dir='rtl'] .secondaryToolbarButton::before {
+ left: 186px;
+ }
+
+ .toolbarField.pageNumber.visiblePageIsLoading,
+ #findInput[data-status="pending"] {
+ background-image: url(images/loading-small@2x.png);
+ background-size: 16px 17px;
+ }
+
+ .dropdownToolbarButton {
+ background: url(images/toolbarButton-menuArrows@2x.png) no-repeat;
+ background-size: 7px 16px;
+ }
+
+ html[dir='ltr'] .toolbarButton#sidebarToggle::before {
+ content: url(images/toolbarButton-sidebarToggle@2x.png);
+ }
+ html[dir='rtl'] .toolbarButton#sidebarToggle::before {
+ content: url(images/toolbarButton-sidebarToggle-rtl@2x.png);
+ }
+
+ html[dir='ltr'] .toolbarButton#secondaryToolbarToggle::before {
+ content: url(images/toolbarButton-secondaryToolbarToggle@2x.png);
+ }
+ html[dir='rtl'] .toolbarButton#secondaryToolbarToggle::before {
+ content: url(images/toolbarButton-secondaryToolbarToggle-rtl@2x.png);
+ }
+
+ html[dir='ltr'] .toolbarButton.findPrevious::before {
+ content: url(images/findbarButton-previous@2x.png);
+ }
+ html[dir='rtl'] .toolbarButton.findPrevious::before {
+ content: url(images/findbarButton-previous-rtl@2x.png);
+ }
+
+ html[dir='ltr'] .toolbarButton.findNext::before {
+ content: url(images/findbarButton-next@2x.png);
+ }
+ html[dir='rtl'] .toolbarButton.findNext::before {
+ content: url(images/findbarButton-next-rtl@2x.png);
+ }
+
+ html[dir='ltr'] .toolbarButton.pageUp::before {
+ content: url(images/toolbarButton-pageUp@2x.png);
+ }
+ html[dir='rtl'] .toolbarButton.pageUp::before {
+ content: url(images/toolbarButton-pageUp-rtl@2x.png);
+ }
+
+ html[dir='ltr'] .toolbarButton.pageDown::before {
+ content: url(images/toolbarButton-pageDown@2x.png);
+ }
+ html[dir='rtl'] .toolbarButton.pageDown::before {
+ content: url(images/toolbarButton-pageDown-rtl@2x.png);
+ }
+
+ .toolbarButton.zoomIn::before {
+ content: url(images/toolbarButton-zoomIn@2x.png);
+ }
+
+ .toolbarButton.zoomOut::before {
+ content: url(images/toolbarButton-zoomOut@2x.png);
+ }
+
+ .toolbarButton.presentationMode::before,
+ .secondaryToolbarButton.presentationMode::before {
+ content: url(images/toolbarButton-presentationMode@2x.png);
+ }
+
+ .toolbarButton.print::before,
+ .secondaryToolbarButton.print::before {
+ content: url(images/toolbarButton-print@2x.png);
+ }
+
+ .toolbarButton.openFile::before,
+ .secondaryToolbarButton.openFile::before {
+ content: url(images/toolbarButton-openFile@2x.png);
+ }
+
+ .toolbarButton.download::before,
+ .secondaryToolbarButton.download::before {
+ content: url(images/toolbarButton-download@2x.png);
+ }
+
+ .toolbarButton.bookmark::before,
+ .secondaryToolbarButton.bookmark::before {
+ content: url(images/toolbarButton-bookmark@2x.png);
+ }
+
+ #viewThumbnail.toolbarButton::before {
+ content: url(images/toolbarButton-viewThumbnail@2x.png);
+ }
+
+ html[dir="ltr"] #viewOutline.toolbarButton::before {
+ content: url(images/toolbarButton-viewOutline@2x.png);
+ }
+ html[dir="rtl"] #viewOutline.toolbarButton::before {
+ content: url(images/toolbarButton-viewOutline-rtl@2x.png);
+ }
+
+ #viewAttachments.toolbarButton::before {
+ content: url(images/toolbarButton-viewAttachments@2x.png);
+ }
+
+ #viewFind.toolbarButton::before {
+ content: url(images/toolbarButton-search@2x.png);
+ }
+
+ .secondaryToolbarButton.firstPage::before {
+ content: url(images/secondaryToolbarButton-firstPage@2x.png);
+ }
+
+ .secondaryToolbarButton.lastPage::before {
+ content: url(images/secondaryToolbarButton-lastPage@2x.png);
+ }
+
+ .secondaryToolbarButton.rotateCcw::before {
+ content: url(images/secondaryToolbarButton-rotateCcw@2x.png);
+ }
+
+ .secondaryToolbarButton.rotateCw::before {
+ content: url(images/secondaryToolbarButton-rotateCw@2x.png);
+ }
+
+ .secondaryToolbarButton.handTool::before {
+ content: url(images/secondaryToolbarButton-handTool@2x.png);
+ }
+
+ .secondaryToolbarButton.documentProperties::before {
+ content: url(images/secondaryToolbarButton-documentProperties@2x.png);
+ }
+
+ .outlineItemToggler::before {
+ transform: scale(0.5);
+ top: -1px;
+ content: url(images/treeitem-expanded@2x.png);
+ }
+ html[dir='ltr'] .outlineItemToggler.outlineItemsHidden::before {
+ content: url(images/treeitem-collapsed@2x.png);
+ }
+ html[dir='rtl'] .outlineItemToggler.outlineItemsHidden::before {
+ content: url(images/treeitem-collapsed-rtl@2x.png);
+ }
+ html[dir='ltr'] .outlineItemToggler::before {
+ right: 0;
+ }
+ html[dir='rtl'] .outlineItemToggler::before {
+ left: 0;
+ }
+}
+
+@media print {
+ /* General rules for printing. */
+ body {
+ background: transparent none;
+ }
+
+ /* Rules for browsers that don't support mozPrintCallback. */
+ #sidebarContainer, #secondaryToolbar, .toolbar, #loadingBox, #errorWrapper, .textLayer {
+ display: none;
+ }
+ #viewerContainer {
+ overflow: visible;
+ }
+
+ #mainContainer, #viewerContainer, .page, .page canvas {
+ position: static;
+ padding: 0;
+ margin: 0;
+ }
+
+ .page {
+ float: left;
+ display: none;
+ border: none;
+ box-shadow: none;
+ background-clip: content-box;
+ background-color: white;
+ }
+
+ .page[data-loaded] {
+ display: block;
+ }
+
+ .fileInput {
+ display: none;
+ }
+
+ /* Rules for browsers that support PDF.js printing */
+ body[data-pdfjsprinting] #outerContainer {
+ display: none;
+ }
+ body[data-pdfjsprinting] #printContainer {
+ display: block;
+ }
+ #printContainer {
+ height: 100%;
+ }
+ /* wrapper around (scaled) print canvas elements */
+ #printContainer > div {
+ position: relative;
+ top: 0;
+ left: 0;
+ width: 1px;
+ height: 1px;
+ overflow: visible;
+ page-break-after: always;
+ page-break-inside: avoid;
+ }
+ #printContainer canvas,
+ #printContainer img {
+ display: block;
+ }
+}
+
+.visibleLargeView,
+.visibleMediumView,
+.visibleSmallView {
+ display: none;
+}
+
+@media all and (max-width: 1040px) {
+ #outerContainer.sidebarMoving #toolbarViewerMiddle,
+ #outerContainer.sidebarOpen #toolbarViewerMiddle {
+ display: table;
+ margin: auto;
+ left: auto;
+ position: inherit;
+ transform: none;
+ }
+}
+
+@media all and (max-width: 980px) {
+ .sidebarMoving .hiddenLargeView,
+ .sidebarOpen .hiddenLargeView {
+ display: none;
+ }
+ .sidebarMoving .visibleLargeView,
+ .sidebarOpen .visibleLargeView {
+ display: inherit;
+ }
+}
+
+@media all and (max-width: 900px) {
+ #toolbarViewerMiddle {
+ display: table;
+ margin: auto;
+ left: auto;
+ position: inherit;
+ transform: none;
+ }
+ .sidebarMoving .hiddenMediumView,
+ .sidebarOpen .hiddenMediumView {
+ display: none;
+ }
+ .sidebarMoving .visibleMediumView,
+ .sidebarOpen .visibleMediumView {
+ display: inherit;
+ }
+}
+
+@media all and (max-width: 840px) {
+ #sidebarContainer {
+ top: 32px;
+ z-index: 100;
+ }
+ .loadingInProgress #sidebarContainer {
+ top: 37px;
+ }
+ #sidebarContent {
+ top: 32px;
+ background-color: hsla(0,0%,0%,.7);
+ }
+
+ html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
+ left: 0px;
+ }
+ html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
+ right: 0px;
+ }
+
+ #outerContainer .hiddenLargeView,
+ #outerContainer .hiddenMediumView {
+ display: inherit;
+ }
+ #outerContainer .visibleLargeView,
+ #outerContainer .visibleMediumView {
+ display: none;
+ }
+}
+
+@media all and (max-width: 770px) {
+ #outerContainer .hiddenLargeView {
+ display: none;
+ }
+ #outerContainer .visibleLargeView {
+ display: inherit;
+ }
+}
+
+@media all and (max-width: 700px) {
+ #outerContainer .hiddenMediumView {
+ display: none;
+ }
+ #outerContainer .visibleMediumView {
+ display: inherit;
+ }
+}
+
+@media all and (max-width: 640px) {
+ .hiddenSmallView {
+ display: none;
+ }
+ .visibleSmallView {
+ display: inherit;
+ }
+ .toolbarButtonSpacer {
+ width: 0;
+ }
+}
+
+@media all and (max-width: 535px) {
+ #scaleSelectContainer {
+ display: none;
+ }
+}
diff --git a/browser/extensions/pdfjs/content/web/viewer.html b/browser/extensions/pdfjs/content/web/viewer.html
new file mode 100644
index 000000000..3a06aa7b1
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -0,0 +1,337 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Mozilla Foundation
+
+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.
+
+Adobe CMap resources are covered by their own copyright but the same license:
+
+ Copyright 1990-2015 Adobe Systems Incorporated.
+
+See https://github.com/adobe-type-tools/cmap-resources
+-->
+<html dir="ltr" mozdisallowselectionprint moznomarginboxes>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+ <title>PDF.js viewer</title>
+
+<!-- This snippet is used in the Firefox extension (included from viewer.html) -->
+<base href="resource://pdf.js/web/">
+<script src="l10n.js"></script>
+<script src="../build/pdf.js"></script>
+
+
+ <link rel="stylesheet" href="viewer.css">
+
+
+
+
+
+
+ <script src="viewer.js"></script>
+
+ </head>
+
+ <body tabindex="1" class="loadingInProgress">
+ <div id="outerContainer">
+
+ <div id="sidebarContainer">
+ <div id="toolbarSidebar">
+ <div class="splitToolbarButton toggled">
+ <button id="viewThumbnail" class="toolbarButton group toggled" title="Show Thumbnails" tabindex="2" data-l10n-id="thumbs">
+ <span data-l10n-id="thumbs_label">Thumbnails</span>
+ </button>
+ <button id="viewOutline" class="toolbarButton group" title="Show Document Outline (double-click to expand/collapse all items)" tabindex="3" data-l10n-id="document_outline">
+ <span data-l10n-id="document_outline_label">Document Outline</span>
+ </button>
+ <button id="viewAttachments" class="toolbarButton group" title="Show Attachments" tabindex="4" data-l10n-id="attachments">
+ <span data-l10n-id="attachments_label">Attachments</span>
+ </button>
+ </div>
+ </div>
+ <div id="sidebarContent">
+ <div id="thumbnailView">
+ </div>
+ <div id="outlineView" class="hidden">
+ </div>
+ <div id="attachmentsView" class="hidden">
+ </div>
+ </div>
+ </div> <!-- sidebarContainer -->
+
+ <div id="mainContainer">
+ <div class="findbar hidden doorHanger hiddenSmallView" id="findbar">
+ <label for="findInput" class="toolbarLabel" data-l10n-id="find_label">Find:</label>
+ <input id="findInput" class="toolbarField" tabindex="91">
+ <div class="splitToolbarButton">
+ <button class="toolbarButton findPrevious" title="" id="findPrevious" tabindex="92" data-l10n-id="find_previous">
+ <span data-l10n-id="find_previous_label">Previous</span>
+ </button>
+ <div class="splitToolbarButtonSeparator"></div>
+ <button class="toolbarButton findNext" title="" id="findNext" tabindex="93" data-l10n-id="find_next">
+ <span data-l10n-id="find_next_label">Next</span>
+ </button>
+ </div>
+ <input type="checkbox" id="findHighlightAll" class="toolbarField" tabindex="94">
+ <label for="findHighlightAll" class="toolbarLabel" data-l10n-id="find_highlight">Highlight all</label>
+ <input type="checkbox" id="findMatchCase" class="toolbarField" tabindex="95">
+ <label for="findMatchCase" class="toolbarLabel" data-l10n-id="find_match_case_label">Match case</label>
+ <span id="findResultsCount" class="toolbarLabel hidden"></span>
+ <span id="findMsg" class="toolbarLabel"></span>
+ </div> <!-- findbar -->
+
+ <div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
+ <div id="secondaryToolbarButtonContainer">
+ <button id="secondaryPresentationMode" class="secondaryToolbarButton presentationMode visibleLargeView" title="Switch to Presentation Mode" tabindex="51" data-l10n-id="presentation_mode">
+ <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
+ </button>
+
+ <button id="secondaryOpenFile" class="secondaryToolbarButton openFile visibleLargeView" title="Open File" tabindex="52" data-l10n-id="open_file">
+ <span data-l10n-id="open_file_label">Open</span>
+ </button>
+
+ <button id="secondaryPrint" class="secondaryToolbarButton print visibleMediumView" title="Print" tabindex="53" data-l10n-id="print">
+ <span data-l10n-id="print_label">Print</span>
+ </button>
+
+ <button id="secondaryDownload" class="secondaryToolbarButton download visibleMediumView" title="Download" tabindex="54" data-l10n-id="download">
+ <span data-l10n-id="download_label">Download</span>
+ </button>
+
+ <a href="#" id="secondaryViewBookmark" class="secondaryToolbarButton bookmark visibleSmallView" title="Current view (copy or open in new window)" tabindex="55" data-l10n-id="bookmark">
+ <span data-l10n-id="bookmark_label">Current View</span>
+ </a>
+
+ <div class="horizontalToolbarSeparator visibleLargeView"></div>
+
+ <button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="56" data-l10n-id="first_page">
+ <span data-l10n-id="first_page_label">Go to First Page</span>
+ </button>
+ <button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="57" data-l10n-id="last_page">
+ <span data-l10n-id="last_page_label">Go to Last Page</span>
+ </button>
+
+ <div class="horizontalToolbarSeparator"></div>
+
+ <button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="58" data-l10n-id="page_rotate_cw">
+ <span data-l10n-id="page_rotate_cw_label">Rotate Clockwise</span>
+ </button>
+ <button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="59" data-l10n-id="page_rotate_ccw">
+ <span data-l10n-id="page_rotate_ccw_label">Rotate Counterclockwise</span>
+ </button>
+
+ <div class="horizontalToolbarSeparator"></div>
+
+ <button id="toggleHandTool" class="secondaryToolbarButton handTool" title="Enable hand tool" tabindex="60" data-l10n-id="hand_tool_enable">
+ <span data-l10n-id="hand_tool_enable_label">Enable hand tool</span>
+ </button>
+
+ <div class="horizontalToolbarSeparator"></div>
+
+ <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="61" data-l10n-id="document_properties">
+ <span data-l10n-id="document_properties_label">Document Properties…</span>
+ </button>
+ </div>
+ </div> <!-- secondaryToolbar -->
+
+ <div class="toolbar">
+ <div id="toolbarContainer">
+ <div id="toolbarViewer">
+ <div id="toolbarViewerLeft">
+ <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="11" data-l10n-id="toggle_sidebar">
+ <span data-l10n-id="toggle_sidebar_label">Toggle Sidebar</span>
+ </button>
+ <div class="toolbarButtonSpacer"></div>
+ <button id="viewFind" class="toolbarButton group hiddenSmallView" title="Find in Document" tabindex="12" data-l10n-id="findbar">
+ <span data-l10n-id="findbar_label">Find</span>
+ </button>
+ <div class="splitToolbarButton">
+ <button class="toolbarButton pageUp" title="Previous Page" id="previous" tabindex="13" data-l10n-id="previous">
+ <span data-l10n-id="previous_label">Previous</span>
+ </button>
+ <div class="splitToolbarButtonSeparator"></div>
+ <button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="14" data-l10n-id="next">
+ <span data-l10n-id="next_label">Next</span>
+ </button>
+ </div>
+ <input type="number" id="pageNumber" class="toolbarField pageNumber" title="Page" value="1" size="4" min="1" tabindex="15" data-l10n-id="page">
+ <span id="numPages" class="toolbarLabel"></span>
+ </div>
+ <div id="toolbarViewerRight">
+ <button id="presentationMode" class="toolbarButton presentationMode hiddenLargeView" title="Switch to Presentation Mode" tabindex="31" data-l10n-id="presentation_mode">
+ <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
+ </button>
+
+ <button id="openFile" class="toolbarButton openFile hiddenLargeView" title="Open File" tabindex="32" data-l10n-id="open_file">
+ <span data-l10n-id="open_file_label">Open</span>
+ </button>
+
+ <button id="print" class="toolbarButton print hiddenMediumView" title="Print" tabindex="33" data-l10n-id="print">
+ <span data-l10n-id="print_label">Print</span>
+ </button>
+
+ <button id="download" class="toolbarButton download hiddenMediumView" title="Download" tabindex="34" data-l10n-id="download">
+ <span data-l10n-id="download_label">Download</span>
+ </button>
+ <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="35" data-l10n-id="bookmark">
+ <span data-l10n-id="bookmark_label">Current View</span>
+ </a>
+
+ <div class="verticalToolbarSeparator hiddenSmallView"></div>
+
+ <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="36" data-l10n-id="tools">
+ <span data-l10n-id="tools_label">Tools</span>
+ </button>
+ </div>
+ <div id="toolbarViewerMiddle">
+ <div class="splitToolbarButton">
+ <button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="21" data-l10n-id="zoom_out">
+ <span data-l10n-id="zoom_out_label">Zoom Out</span>
+ </button>
+ <div class="splitToolbarButtonSeparator"></div>
+ <button id="zoomIn" class="toolbarButton zoomIn" title="Zoom In" tabindex="22" data-l10n-id="zoom_in">
+ <span data-l10n-id="zoom_in_label">Zoom In</span>
+ </button>
+ </div>
+ <span id="scaleSelectContainer" class="dropdownToolbarButton">
+ <select id="scaleSelect" title="Zoom" tabindex="23" data-l10n-id="zoom">
+ <option id="pageAutoOption" title="" value="auto" selected="selected" data-l10n-id="page_scale_auto">Automatic Zoom</option>
+ <option id="pageActualOption" title="" value="page-actual" data-l10n-id="page_scale_actual">Actual Size</option>
+ <option id="pageFitOption" title="" value="page-fit" data-l10n-id="page_scale_fit">Fit Page</option>
+ <option id="pageWidthOption" title="" value="page-width" data-l10n-id="page_scale_width">Full Width</option>
+ <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true"></option>
+ <option title="" value="0.5" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 50 }'>50%</option>
+ <option title="" value="0.75" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 75 }'>75%</option>
+ <option title="" value="1" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 100 }'>100%</option>
+ <option title="" value="1.25" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 125 }'>125%</option>
+ <option title="" value="1.5" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 150 }'>150%</option>
+ <option title="" value="2" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 200 }'>200%</option>
+ <option title="" value="3" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 300 }'>300%</option>
+ <option title="" value="4" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 400 }'>400%</option>
+ </select>
+ </span>
+ </div>
+ </div>
+ <div id="loadingBar">
+ <div class="progress">
+ <div class="glimmer">
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <menu type="context" id="viewerContextMenu">
+ <menuitem id="contextFirstPage" label="First Page"
+ data-l10n-id="first_page"></menuitem>
+ <menuitem id="contextLastPage" label="Last Page"
+ data-l10n-id="last_page"></menuitem>
+ <menuitem id="contextPageRotateCw" label="Rotate Clockwise"
+ data-l10n-id="page_rotate_cw"></menuitem>
+ <menuitem id="contextPageRotateCcw" label="Rotate Counter-Clockwise"
+ data-l10n-id="page_rotate_ccw"></menuitem>
+ </menu>
+
+ <div id="viewerContainer" tabindex="0">
+ <div id="viewer" class="pdfViewer"></div>
+ </div>
+
+ <div id="errorWrapper" hidden='true'>
+ <div id="errorMessageLeft">
+ <span id="errorMessage"></span>
+ <button id="errorShowMore" data-l10n-id="error_more_info">
+ More Information
+ </button>
+ <button id="errorShowLess" data-l10n-id="error_less_info" hidden='true'>
+ Less Information
+ </button>
+ </div>
+ <div id="errorMessageRight">
+ <button id="errorClose" data-l10n-id="error_close">
+ Close
+ </button>
+ </div>
+ <div class="clearBoth"></div>
+ <textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
+ </div>
+ </div> <!-- mainContainer -->
+
+ <div id="overlayContainer" class="hidden">
+ <div id="passwordOverlay" class="container hidden">
+ <div class="dialog">
+ <div class="row">
+ <p id="passwordText" data-l10n-id="password_label">Enter the password to open this PDF file:</p>
+ </div>
+ <div class="row">
+ <!-- The type="password" attribute is set via script, to prevent warnings in Firefox for all http:// documents. -->
+ <input id="password" class="toolbarField">
+ </div>
+ <div class="buttonRow">
+ <button id="passwordCancel" class="overlayButton"><span data-l10n-id="password_cancel">Cancel</span></button>
+ <button id="passwordSubmit" class="overlayButton"><span data-l10n-id="password_ok">OK</span></button>
+ </div>
+ </div>
+ </div>
+ <div id="documentPropertiesOverlay" class="container hidden">
+ <div class="dialog">
+ <div class="row">
+ <span data-l10n-id="document_properties_file_name">File name:</span> <p id="fileNameField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_file_size">File size:</span> <p id="fileSizeField">-</p>
+ </div>
+ <div class="separator"></div>
+ <div class="row">
+ <span data-l10n-id="document_properties_title">Title:</span> <p id="titleField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_author">Author:</span> <p id="authorField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_subject">Subject:</span> <p id="subjectField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_keywords">Keywords:</span> <p id="keywordsField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_creation_date">Creation Date:</span> <p id="creationDateField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_modification_date">Modification Date:</span> <p id="modificationDateField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_creator">Creator:</span> <p id="creatorField">-</p>
+ </div>
+ <div class="separator"></div>
+ <div class="row">
+ <span data-l10n-id="document_properties_producer">PDF Producer:</span> <p id="producerField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_version">PDF Version:</span> <p id="versionField">-</p>
+ </div>
+ <div class="row">
+ <span data-l10n-id="document_properties_page_count">Page Count:</span> <p id="pageCountField">-</p>
+ </div>
+ <div class="buttonRow">
+ <button id="documentPropertiesClose" class="overlayButton"><span data-l10n-id="document_properties_close">Close</span></button>
+ </div>
+ </div>
+ </div>
+ </div> <!-- overlayContainer -->
+
+ </div> <!-- outerContainer -->
+ <div id="printContainer"></div>
+ </body>
+</html>
+
diff --git a/browser/extensions/pdfjs/content/web/viewer.js b/browser/extensions/pdfjs/content/web/viewer.js
new file mode 100644
index 000000000..35fef2cd0
--- /dev/null
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -0,0 +1,8671 @@
+/* Copyright 2016 Mozilla Foundation
+ *
+ * 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.
+ */
+'use strict';
+var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
+;
+var pdfjsWebLibs;
+{
+ pdfjsWebLibs = { pdfjsWebPDFJS: window.pdfjsDistBuildPdf };
+ (function () {
+ (function (root, factory) {
+ factory(root.pdfjsWebGrabToPan = {});
+ }(this, function (exports) {
+ /**
+ * Construct a GrabToPan instance for a given HTML element.
+ * @param options.element {Element}
+ * @param options.ignoreTarget {function} optional. See `ignoreTarget(node)`
+ * @param options.onActiveChanged {function(boolean)} optional. Called
+ * when grab-to-pan is (de)activated. The first argument is a boolean that
+ * shows whether grab-to-pan is activated.
+ */
+ function GrabToPan(options) {
+ this.element = options.element;
+ this.document = options.element.ownerDocument;
+ if (typeof options.ignoreTarget === 'function') {
+ this.ignoreTarget = options.ignoreTarget;
+ }
+ this.onActiveChanged = options.onActiveChanged;
+ // Bind the contexts to ensure that `this` always points to
+ // the GrabToPan instance.
+ this.activate = this.activate.bind(this);
+ this.deactivate = this.deactivate.bind(this);
+ this.toggle = this.toggle.bind(this);
+ this._onmousedown = this._onmousedown.bind(this);
+ this._onmousemove = this._onmousemove.bind(this);
+ this._endPan = this._endPan.bind(this);
+ // This overlay will be inserted in the document when the mouse moves during
+ // a grab operation, to ensure that the cursor has the desired appearance.
+ var overlay = this.overlay = document.createElement('div');
+ overlay.className = 'grab-to-pan-grabbing';
+ }
+ GrabToPan.prototype = {
+ /**
+ * Class name of element which can be grabbed
+ */
+ CSS_CLASS_GRAB: 'grab-to-pan-grab',
+ /**
+ * Bind a mousedown event to the element to enable grab-detection.
+ */
+ activate: function GrabToPan_activate() {
+ if (!this.active) {
+ this.active = true;
+ this.element.addEventListener('mousedown', this._onmousedown, true);
+ this.element.classList.add(this.CSS_CLASS_GRAB);
+ if (this.onActiveChanged) {
+ this.onActiveChanged(true);
+ }
+ }
+ },
+ /**
+ * Removes all events. Any pending pan session is immediately stopped.
+ */
+ deactivate: function GrabToPan_deactivate() {
+ if (this.active) {
+ this.active = false;
+ this.element.removeEventListener('mousedown', this._onmousedown, true);
+ this._endPan();
+ this.element.classList.remove(this.CSS_CLASS_GRAB);
+ if (this.onActiveChanged) {
+ this.onActiveChanged(false);
+ }
+ }
+ },
+ toggle: function GrabToPan_toggle() {
+ if (this.active) {
+ this.deactivate();
+ } else {
+ this.activate();
+ }
+ },
+ /**
+ * Whether to not pan if the target element is clicked.
+ * Override this method to change the default behaviour.
+ *
+ * @param node {Element} The target of the event
+ * @return {boolean} Whether to not react to the click event.
+ */
+ ignoreTarget: function GrabToPan_ignoreTarget(node) {
+ // Use matchesSelector to check whether the clicked element
+ // is (a child of) an input element / link
+ return node[matchesSelector]('a[href], a[href] *, input, textarea, button, button *, select, option');
+ },
+ /**
+ * @private
+ */
+ _onmousedown: function GrabToPan__onmousedown(event) {
+ if (event.button !== 0 || this.ignoreTarget(event.target)) {
+ return;
+ }
+ if (event.originalTarget) {
+ try {
+ event.originalTarget.tagName;
+ } catch (e) {
+ // Mozilla-specific: element is a scrollbar (XUL element)
+ return;
+ }
+ }
+ this.scrollLeftStart = this.element.scrollLeft;
+ this.scrollTopStart = this.element.scrollTop;
+ this.clientXStart = event.clientX;
+ this.clientYStart = event.clientY;
+ this.document.addEventListener('mousemove', this._onmousemove, true);
+ this.document.addEventListener('mouseup', this._endPan, true);
+ // When a scroll event occurs before a mousemove, assume that the user
+ // dragged a scrollbar (necessary for Opera Presto, Safari and IE)
+ // (not needed for Chrome/Firefox)
+ this.element.addEventListener('scroll', this._endPan, true);
+ event.preventDefault();
+ event.stopPropagation();
+ var focusedElement = document.activeElement;
+ if (focusedElement && !focusedElement.contains(event.target)) {
+ focusedElement.blur();
+ }
+ },
+ /**
+ * @private
+ */
+ _onmousemove: function GrabToPan__onmousemove(event) {
+ this.element.removeEventListener('scroll', this._endPan, true);
+ if (isLeftMouseReleased(event)) {
+ this._endPan();
+ return;
+ }
+ var xDiff = event.clientX - this.clientXStart;
+ var yDiff = event.clientY - this.clientYStart;
+ var scrollTop = this.scrollTopStart - yDiff;
+ var scrollLeft = this.scrollLeftStart - xDiff;
+ if (this.element.scrollTo) {
+ this.element.scrollTo({
+ top: scrollTop,
+ left: scrollLeft,
+ behavior: 'instant'
+ });
+ } else {
+ this.element.scrollTop = scrollTop;
+ this.element.scrollLeft = scrollLeft;
+ }
+ if (!this.overlay.parentNode) {
+ document.body.appendChild(this.overlay);
+ }
+ },
+ /**
+ * @private
+ */
+ _endPan: function GrabToPan__endPan() {
+ this.element.removeEventListener('scroll', this._endPan, true);
+ this.document.removeEventListener('mousemove', this._onmousemove, true);
+ this.document.removeEventListener('mouseup', this._endPan, true);
+ if (this.overlay.parentNode) {
+ this.overlay.parentNode.removeChild(this.overlay);
+ }
+ }
+ };
+ // Get the correct (vendor-prefixed) name of the matches method.
+ var matchesSelector;
+ [
+ 'webkitM',
+ 'mozM',
+ 'msM',
+ 'oM',
+ 'm'
+ ].some(function (prefix) {
+ var name = prefix + 'atches';
+ if (name in document.documentElement) {
+ matchesSelector = name;
+ }
+ name += 'Selector';
+ if (name in document.documentElement) {
+ matchesSelector = name;
+ }
+ return matchesSelector;
+ });
+ // If found, then truthy, and [].some() ends.
+ // Browser sniffing because it's impossible to feature-detect
+ // whether event.which for onmousemove is reliable
+ var isNotIEorIsIE10plus = !document.documentMode || document.documentMode > 9;
+ var chrome = window.chrome;
+ var isChrome15OrOpera15plus = chrome && (chrome.webstore || chrome.app);
+ // ^ Chrome 15+ ^ Opera 15+
+ var isSafari6plus = /Apple/.test(navigator.vendor) && /Version\/([6-9]\d*|[1-5]\d+)/.test(navigator.userAgent);
+ /**
+ * Whether the left mouse is not pressed.
+ * @param event {MouseEvent}
+ * @return {boolean} True if the left mouse button is not pressed.
+ * False if unsure or if the left mouse button is pressed.
+ */
+ function isLeftMouseReleased(event) {
+ if ('buttons' in event && isNotIEorIsIE10plus) {
+ // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-buttons
+ // Firefox 15+
+ // Internet Explorer 10+
+ return !(event.buttons & 1);
+ }
+ if (isChrome15OrOpera15plus || isSafari6plus) {
+ // Chrome 14+
+ // Opera 15+
+ // Safari 6.0+
+ return event.which === 0;
+ }
+ }
+ exports.GrabToPan = GrabToPan;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebOverlayManager = {});
+ }(this, function (exports) {
+ var OverlayManager = {
+ overlays: {},
+ active: null,
+ /**
+ * @param {string} name The name of the overlay that is registered.
+ * @param {HTMLDivElement} element The overlay's DOM element.
+ * @param {function} callerCloseMethod (optional) The method that, if present,
+ * will call OverlayManager.close from the Object
+ * registering the overlay. Access to this method is
+ * necessary in order to run cleanup code when e.g.
+ * the overlay is force closed. The default is null.
+ * @param {boolean} canForceClose (optional) Indicates if opening the overlay
+ * will close an active overlay. The default is false.
+ * @returns {Promise} A promise that is resolved when the overlay has been
+ * registered.
+ */
+ register: function overlayManagerRegister(name, element, callerCloseMethod, canForceClose) {
+ return new Promise(function (resolve) {
+ var container;
+ if (!name || !element || !(container = element.parentNode)) {
+ throw new Error('Not enough parameters.');
+ } else if (this.overlays[name]) {
+ throw new Error('The overlay is already registered.');
+ }
+ this.overlays[name] = {
+ element: element,
+ container: container,
+ callerCloseMethod: callerCloseMethod || null,
+ canForceClose: canForceClose || false
+ };
+ resolve();
+ }.bind(this));
+ },
+ /**
+ * @param {string} name The name of the overlay that is unregistered.
+ * @returns {Promise} A promise that is resolved when the overlay has been
+ * unregistered.
+ */
+ unregister: function overlayManagerUnregister(name) {
+ return new Promise(function (resolve) {
+ if (!this.overlays[name]) {
+ throw new Error('The overlay does not exist.');
+ } else if (this.active === name) {
+ throw new Error('The overlay cannot be removed while it is active.');
+ }
+ delete this.overlays[name];
+ resolve();
+ }.bind(this));
+ },
+ /**
+ * @param {string} name The name of the overlay that should be opened.
+ * @returns {Promise} A promise that is resolved when the overlay has been
+ * opened.
+ */
+ open: function overlayManagerOpen(name) {
+ return new Promise(function (resolve) {
+ if (!this.overlays[name]) {
+ throw new Error('The overlay does not exist.');
+ } else if (this.active) {
+ if (this.overlays[name].canForceClose) {
+ this._closeThroughCaller();
+ } else if (this.active === name) {
+ throw new Error('The overlay is already active.');
+ } else {
+ throw new Error('Another overlay is currently active.');
+ }
+ }
+ this.active = name;
+ this.overlays[this.active].element.classList.remove('hidden');
+ this.overlays[this.active].container.classList.remove('hidden');
+ window.addEventListener('keydown', this._keyDown);
+ resolve();
+ }.bind(this));
+ },
+ /**
+ * @param {string} name The name of the overlay that should be closed.
+ * @returns {Promise} A promise that is resolved when the overlay has been
+ * closed.
+ */
+ close: function overlayManagerClose(name) {
+ return new Promise(function (resolve) {
+ if (!this.overlays[name]) {
+ throw new Error('The overlay does not exist.');
+ } else if (!this.active) {
+ throw new Error('The overlay is currently not active.');
+ } else if (this.active !== name) {
+ throw new Error('Another overlay is currently active.');
+ }
+ this.overlays[this.active].container.classList.add('hidden');
+ this.overlays[this.active].element.classList.add('hidden');
+ this.active = null;
+ window.removeEventListener('keydown', this._keyDown);
+ resolve();
+ }.bind(this));
+ },
+ /**
+ * @private
+ */
+ _keyDown: function overlayManager_keyDown(evt) {
+ var self = OverlayManager;
+ if (self.active && evt.keyCode === 27) {
+ // Esc key.
+ self._closeThroughCaller();
+ evt.preventDefault();
+ }
+ },
+ /**
+ * @private
+ */
+ _closeThroughCaller: function overlayManager_closeThroughCaller() {
+ if (this.overlays[this.active].callerCloseMethod) {
+ this.overlays[this.active].callerCloseMethod();
+ }
+ if (this.active) {
+ this.close(this.active);
+ }
+ }
+ };
+ exports.OverlayManager = OverlayManager;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFRenderingQueue = {});
+ }(this, function (exports) {
+ var CLEANUP_TIMEOUT = 30000;
+ var RenderingStates = {
+ INITIAL: 0,
+ RUNNING: 1,
+ PAUSED: 2,
+ FINISHED: 3
+ };
+ /**
+ * Controls rendering of the views for pages and thumbnails.
+ * @class
+ */
+ var PDFRenderingQueue = function PDFRenderingQueueClosure() {
+ /**
+ * @constructs
+ */
+ function PDFRenderingQueue() {
+ this.pdfViewer = null;
+ this.pdfThumbnailViewer = null;
+ this.onIdle = null;
+ this.highestPriorityPage = null;
+ this.idleTimeout = null;
+ this.printing = false;
+ this.isThumbnailViewEnabled = false;
+ }
+ PDFRenderingQueue.prototype = /** @lends PDFRenderingQueue.prototype */
+ {
+ /**
+ * @param {PDFViewer} pdfViewer
+ */
+ setViewer: function PDFRenderingQueue_setViewer(pdfViewer) {
+ this.pdfViewer = pdfViewer;
+ },
+ /**
+ * @param {PDFThumbnailViewer} pdfThumbnailViewer
+ */
+ setThumbnailViewer: function PDFRenderingQueue_setThumbnailViewer(pdfThumbnailViewer) {
+ this.pdfThumbnailViewer = pdfThumbnailViewer;
+ },
+ /**
+ * @param {IRenderableView} view
+ * @returns {boolean}
+ */
+ isHighestPriority: function PDFRenderingQueue_isHighestPriority(view) {
+ return this.highestPriorityPage === view.renderingId;
+ },
+ renderHighestPriority: function PDFRenderingQueue_renderHighestPriority(currentlyVisiblePages) {
+ if (this.idleTimeout) {
+ clearTimeout(this.idleTimeout);
+ this.idleTimeout = null;
+ }
+ // Pages have a higher priority than thumbnails, so check them first.
+ if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
+ return;
+ }
+ // No pages needed rendering so check thumbnails.
+ if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
+ if (this.pdfThumbnailViewer.forceRendering()) {
+ return;
+ }
+ }
+ if (this.printing) {
+ // If printing is currently ongoing do not reschedule cleanup.
+ return;
+ }
+ if (this.onIdle) {
+ this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
+ }
+ },
+ getHighestPriority: function PDFRenderingQueue_getHighestPriority(visible, views, scrolledDown) {
+ // The state has changed figure out which page has the highest priority to
+ // render next (if any).
+ // Priority:
+ // 1 visible pages
+ // 2 if last scrolled down page after the visible pages
+ // 2 if last scrolled up page before the visible pages
+ var visibleViews = visible.views;
+ var numVisible = visibleViews.length;
+ if (numVisible === 0) {
+ return false;
+ }
+ for (var i = 0; i < numVisible; ++i) {
+ var view = visibleViews[i].view;
+ if (!this.isViewFinished(view)) {
+ return view;
+ }
+ }
+ // All the visible views have rendered, try to render next/previous pages.
+ if (scrolledDown) {
+ var nextPageIndex = visible.last.id;
+ // ID's start at 1 so no need to add 1.
+ if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) {
+ return views[nextPageIndex];
+ }
+ } else {
+ var previousPageIndex = visible.first.id - 2;
+ if (views[previousPageIndex] && !this.isViewFinished(views[previousPageIndex])) {
+ return views[previousPageIndex];
+ }
+ }
+ // Everything that needs to be rendered has been.
+ return null;
+ },
+ /**
+ * @param {IRenderableView} view
+ * @returns {boolean}
+ */
+ isViewFinished: function PDFRenderingQueue_isViewFinished(view) {
+ return view.renderingState === RenderingStates.FINISHED;
+ },
+ /**
+ * Render a page or thumbnail view. This calls the appropriate function
+ * based on the views state. If the view is already rendered it will return
+ * false.
+ * @param {IRenderableView} view
+ */
+ renderView: function PDFRenderingQueue_renderView(view) {
+ var state = view.renderingState;
+ switch (state) {
+ case RenderingStates.FINISHED:
+ return false;
+ case RenderingStates.PAUSED:
+ this.highestPriorityPage = view.renderingId;
+ view.resume();
+ break;
+ case RenderingStates.RUNNING:
+ this.highestPriorityPage = view.renderingId;
+ break;
+ case RenderingStates.INITIAL:
+ this.highestPriorityPage = view.renderingId;
+ var continueRendering = function () {
+ this.renderHighestPriority();
+ }.bind(this);
+ view.draw().then(continueRendering, continueRendering);
+ break;
+ }
+ return true;
+ }
+ };
+ return PDFRenderingQueue;
+ }();
+ exports.RenderingStates = RenderingStates;
+ exports.PDFRenderingQueue = PDFRenderingQueue;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPreferences = {});
+ }(this, function (exports) {
+ var defaultPreferences;
+ defaultPreferences = Promise.resolve({
+ "showPreviousViewOnLoad": true,
+ "defaultZoomValue": "",
+ "sidebarViewOnLoad": 0,
+ "enableHandToolOnLoad": false,
+ "enableWebGL": false,
+ "pdfBugEnabled": false,
+ "disableRange": false,
+ "disableStream": false,
+ "disableAutoFetch": false,
+ "disableFontFace": false,
+ "disableTextLayer": false,
+ "useOnlyCssZoom": false,
+ "externalLinkTarget": 0,
+ "enhanceTextSelection": false,
+ "renderInteractiveForms": false,
+ "disablePageLabels": false
+ });
+ function cloneObj(obj) {
+ var result = {};
+ for (var i in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, i)) {
+ result[i] = obj[i];
+ }
+ }
+ return result;
+ }
+ /**
+ * Preferences - Utility for storing persistent settings.
+ * Used for settings that should be applied to all opened documents,
+ * or every time the viewer is loaded.
+ */
+ var Preferences = {
+ prefs: null,
+ isInitializedPromiseResolved: false,
+ initializedPromise: null,
+ /**
+ * Initialize and fetch the current preference values from storage.
+ * @return {Promise} A promise that is resolved when the preferences
+ * have been initialized.
+ */
+ initialize: function preferencesInitialize() {
+ return this.initializedPromise = defaultPreferences.then(function (defaults) {
+ Object.defineProperty(this, 'defaults', {
+ value: Object.freeze(defaults),
+ writable: false,
+ enumerable: true,
+ configurable: false
+ });
+ this.prefs = cloneObj(defaults);
+ return this._readFromStorage(defaults);
+ }.bind(this)).then(function (prefObj) {
+ this.isInitializedPromiseResolved = true;
+ if (prefObj) {
+ this.prefs = prefObj;
+ }
+ }.bind(this));
+ },
+ /**
+ * Stub function for writing preferences to storage.
+ * NOTE: This should be overridden by a build-specific function defined below.
+ * @param {Object} prefObj The preferences that should be written to storage.
+ * @return {Promise} A promise that is resolved when the preference values
+ * have been written.
+ */
+ _writeToStorage: function preferences_writeToStorage(prefObj) {
+ return Promise.resolve();
+ },
+ /**
+ * Stub function for reading preferences from storage.
+ * NOTE: This should be overridden by a build-specific function defined below.
+ * @param {Object} prefObj The preferences that should be read from storage.
+ * @return {Promise} A promise that is resolved with an {Object} containing
+ * the preferences that have been read.
+ */
+ _readFromStorage: function preferences_readFromStorage(prefObj) {
+ return Promise.resolve();
+ },
+ /**
+ * Reset the preferences to their default values and update storage.
+ * @return {Promise} A promise that is resolved when the preference values
+ * have been reset.
+ */
+ reset: function preferencesReset() {
+ return this.initializedPromise.then(function () {
+ this.prefs = cloneObj(this.defaults);
+ return this._writeToStorage(this.defaults);
+ }.bind(this));
+ },
+ /**
+ * Replace the current preference values with the ones from storage.
+ * @return {Promise} A promise that is resolved when the preference values
+ * have been updated.
+ */
+ reload: function preferencesReload() {
+ return this.initializedPromise.then(function () {
+ this._readFromStorage(this.defaults).then(function (prefObj) {
+ if (prefObj) {
+ this.prefs = prefObj;
+ }
+ }.bind(this));
+ }.bind(this));
+ },
+ /**
+ * Set the value of a preference.
+ * @param {string} name The name of the preference that should be changed.
+ * @param {boolean|number|string} value The new value of the preference.
+ * @return {Promise} A promise that is resolved when the value has been set,
+ * provided that the preference exists and the types match.
+ */
+ set: function preferencesSet(name, value) {
+ return this.initializedPromise.then(function () {
+ if (this.defaults[name] === undefined) {
+ throw new Error('preferencesSet: \'' + name + '\' is undefined.');
+ } else if (value === undefined) {
+ throw new Error('preferencesSet: no value is specified.');
+ }
+ var valueType = typeof value;
+ var defaultType = typeof this.defaults[name];
+ if (valueType !== defaultType) {
+ if (valueType === 'number' && defaultType === 'string') {
+ value = value.toString();
+ } else {
+ throw new Error('Preferences_set: \'' + value + '\' is a \"' + valueType + '\", expected \"' + defaultType + '\".');
+ }
+ } else {
+ if (valueType === 'number' && (value | 0) !== value) {
+ throw new Error('Preferences_set: \'' + value + '\' must be an \"integer\".');
+ }
+ }
+ this.prefs[name] = value;
+ return this._writeToStorage(this.prefs);
+ }.bind(this));
+ },
+ /**
+ * Get the value of a preference.
+ * @param {string} name The name of the preference whose value is requested.
+ * @return {Promise} A promise that is resolved with a {boolean|number|string}
+ * containing the value of the preference.
+ */
+ get: function preferencesGet(name) {
+ return this.initializedPromise.then(function () {
+ var defaultValue = this.defaults[name];
+ if (defaultValue === undefined) {
+ throw new Error('preferencesGet: \'' + name + '\' is undefined.');
+ } else {
+ var prefValue = this.prefs[name];
+ if (prefValue !== undefined) {
+ return prefValue;
+ }
+ }
+ return defaultValue;
+ }.bind(this));
+ }
+ };
+ exports.Preferences = Preferences;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebViewHistory = {});
+ }(this, function (exports) {
+ var DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
+ /**
+ * View History - This is a utility for saving various view parameters for
+ * recently opened files.
+ *
+ * The way that the view parameters are stored depends on how PDF.js is built,
+ * for 'gulp <flag>' the following cases exist:
+ * - FIREFOX or MOZCENTRAL - uses sessionStorage.
+ * - GENERIC or CHROME - uses localStorage, if it is available.
+ */
+ var ViewHistory = function ViewHistoryClosure() {
+ function ViewHistory(fingerprint, cacheSize) {
+ this.fingerprint = fingerprint;
+ this.cacheSize = cacheSize || DEFAULT_VIEW_HISTORY_CACHE_SIZE;
+ this.isInitializedPromiseResolved = false;
+ this.initializedPromise = this._readFromStorage().then(function (databaseStr) {
+ this.isInitializedPromiseResolved = true;
+ var database = JSON.parse(databaseStr || '{}');
+ if (!('files' in database)) {
+ database.files = [];
+ }
+ if (database.files.length >= this.cacheSize) {
+ database.files.shift();
+ }
+ var index;
+ for (var i = 0, length = database.files.length; i < length; i++) {
+ var branch = database.files[i];
+ if (branch.fingerprint === this.fingerprint) {
+ index = i;
+ break;
+ }
+ }
+ if (typeof index !== 'number') {
+ index = database.files.push({ fingerprint: this.fingerprint }) - 1;
+ }
+ this.file = database.files[index];
+ this.database = database;
+ }.bind(this));
+ }
+ ViewHistory.prototype = {
+ _writeToStorage: function ViewHistory_writeToStorage() {
+ return new Promise(function (resolve) {
+ var databaseStr = JSON.stringify(this.database);
+ sessionStorage.setItem('pdfjsHistory', databaseStr);
+ resolve();
+ }.bind(this));
+ },
+ _readFromStorage: function ViewHistory_readFromStorage() {
+ return new Promise(function (resolve) {
+ resolve(sessionStorage.getItem('pdfjsHistory'));
+ });
+ },
+ set: function ViewHistory_set(name, val) {
+ if (!this.isInitializedPromiseResolved) {
+ return;
+ }
+ this.file[name] = val;
+ return this._writeToStorage();
+ },
+ setMultiple: function ViewHistory_setMultiple(properties) {
+ if (!this.isInitializedPromiseResolved) {
+ return;
+ }
+ for (var name in properties) {
+ this.file[name] = properties[name];
+ }
+ return this._writeToStorage();
+ },
+ get: function ViewHistory_get(name, defaultValue) {
+ if (!this.isInitializedPromiseResolved) {
+ return defaultValue;
+ }
+ return this.file[name] || defaultValue;
+ }
+ };
+ return ViewHistory;
+ }();
+ exports.ViewHistory = ViewHistory;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebDownloadManager = {}, root.pdfjsWebPDFJS);
+ }(this, function (exports, pdfjsLib) {
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebHandTool = {}, root.pdfjsWebGrabToPan, root.pdfjsWebPreferences);
+ }(this, function (exports, grabToPan, preferences) {
+ var GrabToPan = grabToPan.GrabToPan;
+ var Preferences = preferences.Preferences;
+ /**
+ * @typedef {Object} HandToolOptions
+ * @property {HTMLDivElement} container - The document container.
+ * @property {EventBus} eventBus - The application event bus.
+ */
+ /**
+ * @class
+ */
+ var HandTool = function HandToolClosure() {
+ /**
+ * @constructs HandTool
+ * @param {HandToolOptions} options
+ */
+ function HandTool(options) {
+ this.container = options.container;
+ this.eventBus = options.eventBus;
+ this.wasActive = false;
+ this.handTool = new GrabToPan({
+ element: this.container,
+ onActiveChanged: function (isActive) {
+ this.eventBus.dispatch('handtoolchanged', { isActive: isActive });
+ }.bind(this)
+ });
+ this.eventBus.on('togglehandtool', this.toggle.bind(this));
+ this.eventBus.on('localized', function (e) {
+ Preferences.get('enableHandToolOnLoad').then(function resolved(value) {
+ if (value) {
+ this.handTool.activate();
+ }
+ }.bind(this), function rejected(reason) {
+ });
+ }.bind(this));
+ this.eventBus.on('presentationmodechanged', function (e) {
+ if (e.switchInProgress) {
+ return;
+ }
+ if (e.active) {
+ this.enterPresentationMode();
+ } else {
+ this.exitPresentationMode();
+ }
+ }.bind(this));
+ }
+ HandTool.prototype = {
+ /**
+ * @return {boolean}
+ */
+ get isActive() {
+ return !!this.handTool.active;
+ },
+ toggle: function HandTool_toggle() {
+ this.handTool.toggle();
+ },
+ enterPresentationMode: function HandTool_enterPresentationMode() {
+ if (this.isActive) {
+ this.wasActive = true;
+ this.handTool.deactivate();
+ }
+ },
+ exitPresentationMode: function HandTool_exitPresentationMode() {
+ if (this.wasActive) {
+ this.wasActive = false;
+ this.handTool.activate();
+ }
+ }
+ };
+ return HandTool;
+ }();
+ exports.HandTool = HandTool;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFAttachmentViewer = {}, root.pdfjsWebPDFJS);
+ }(this, function (exports, pdfjsLib) {
+ /**
+ * @typedef {Object} PDFAttachmentViewerOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {EventBus} eventBus - The application event bus.
+ * @property {DownloadManager} downloadManager - The download manager.
+ */
+ /**
+ * @typedef {Object} PDFAttachmentViewerRenderParameters
+ * @property {Array|null} attachments - An array of attachment objects.
+ */
+ /**
+ * @class
+ */
+ var PDFAttachmentViewer = function PDFAttachmentViewerClosure() {
+ /**
+ * @constructs PDFAttachmentViewer
+ * @param {PDFAttachmentViewerOptions} options
+ */
+ function PDFAttachmentViewer(options) {
+ this.attachments = null;
+ this.container = options.container;
+ this.eventBus = options.eventBus;
+ this.downloadManager = options.downloadManager;
+ }
+ PDFAttachmentViewer.prototype = {
+ reset: function PDFAttachmentViewer_reset() {
+ this.attachments = null;
+ var container = this.container;
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+ },
+ /**
+ * @private
+ */
+ _dispatchEvent: function PDFAttachmentViewer_dispatchEvent(attachmentsCount) {
+ this.eventBus.dispatch('attachmentsloaded', {
+ source: this,
+ attachmentsCount: attachmentsCount
+ });
+ },
+ /**
+ * @private
+ */
+ _bindLink: function PDFAttachmentViewer_bindLink(button, content, filename) {
+ button.onclick = function downloadFile(e) {
+ this.downloadManager.downloadData(content, filename, '');
+ return false;
+ }.bind(this);
+ },
+ /**
+ * @param {PDFAttachmentViewerRenderParameters} params
+ */
+ render: function PDFAttachmentViewer_render(params) {
+ var attachments = params && params.attachments || null;
+ var attachmentsCount = 0;
+ if (this.attachments) {
+ this.reset();
+ }
+ this.attachments = attachments;
+ if (!attachments) {
+ this._dispatchEvent(attachmentsCount);
+ return;
+ }
+ var names = Object.keys(attachments).sort(function (a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ });
+ attachmentsCount = names.length;
+ for (var i = 0; i < attachmentsCount; i++) {
+ var item = attachments[names[i]];
+ var filename = pdfjsLib.getFilenameFromUrl(item.filename);
+ var div = document.createElement('div');
+ div.className = 'attachmentsItem';
+ var button = document.createElement('button');
+ this._bindLink(button, item.content, filename);
+ button.textContent = pdfjsLib.removeNullCharacters(filename);
+ div.appendChild(button);
+ this.container.appendChild(div);
+ }
+ this._dispatchEvent(attachmentsCount);
+ }
+ };
+ return PDFAttachmentViewer;
+ }();
+ exports.PDFAttachmentViewer = PDFAttachmentViewer;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFOutlineViewer = {}, root.pdfjsWebPDFJS);
+ }(this, function (exports, pdfjsLib) {
+ var PDFJS = pdfjsLib.PDFJS;
+ var DEFAULT_TITLE = '\u2013';
+ /**
+ * @typedef {Object} PDFOutlineViewerOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {EventBus} eventBus - The application event bus.
+ */
+ /**
+ * @typedef {Object} PDFOutlineViewerRenderParameters
+ * @property {Array|null} outline - An array of outline objects.
+ */
+ /**
+ * @class
+ */
+ var PDFOutlineViewer = function PDFOutlineViewerClosure() {
+ /**
+ * @constructs PDFOutlineViewer
+ * @param {PDFOutlineViewerOptions} options
+ */
+ function PDFOutlineViewer(options) {
+ this.outline = null;
+ this.lastToggleIsShow = true;
+ this.container = options.container;
+ this.linkService = options.linkService;
+ this.eventBus = options.eventBus;
+ }
+ PDFOutlineViewer.prototype = {
+ reset: function PDFOutlineViewer_reset() {
+ this.outline = null;
+ this.lastToggleIsShow = true;
+ var container = this.container;
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+ },
+ /**
+ * @private
+ */
+ _dispatchEvent: function PDFOutlineViewer_dispatchEvent(outlineCount) {
+ this.eventBus.dispatch('outlineloaded', {
+ source: this,
+ outlineCount: outlineCount
+ });
+ },
+ /**
+ * @private
+ */
+ _bindLink: function PDFOutlineViewer_bindLink(element, item) {
+ if (item.url) {
+ pdfjsLib.addLinkAttributes(element, {
+ url: item.url,
+ target: item.newWindow ? PDFJS.LinkTarget.BLANK : undefined
+ });
+ return;
+ }
+ var self = this, destination = item.dest;
+ element.href = self.linkService.getDestinationHash(destination);
+ element.onclick = function () {
+ if (destination) {
+ self.linkService.navigateTo(destination);
+ }
+ return false;
+ };
+ },
+ /**
+ * @private
+ */
+ _setStyles: function PDFOutlineViewer_setStyles(element, item) {
+ var styleStr = '';
+ if (item.bold) {
+ styleStr += 'font-weight: bold;';
+ }
+ if (item.italic) {
+ styleStr += 'font-style: italic;';
+ }
+ if (styleStr) {
+ element.setAttribute('style', styleStr);
+ }
+ },
+ /**
+ * Prepend a button before an outline item which allows the user to toggle
+ * the visibility of all outline items at that level.
+ *
+ * @private
+ */
+ _addToggleButton: function PDFOutlineViewer_addToggleButton(div) {
+ var toggler = document.createElement('div');
+ toggler.className = 'outlineItemToggler';
+ toggler.onclick = function (event) {
+ event.stopPropagation();
+ toggler.classList.toggle('outlineItemsHidden');
+ if (event.shiftKey) {
+ var shouldShowAll = !toggler.classList.contains('outlineItemsHidden');
+ this._toggleOutlineItem(div, shouldShowAll);
+ }
+ }.bind(this);
+ div.insertBefore(toggler, div.firstChild);
+ },
+ /**
+ * Toggle the visibility of the subtree of an outline item.
+ *
+ * @param {Element} root - the root of the outline (sub)tree.
+ * @param {boolean} show - whether to show the outline (sub)tree. If false,
+ * the outline subtree rooted at |root| will be collapsed.
+ *
+ * @private
+ */
+ _toggleOutlineItem: function PDFOutlineViewer_toggleOutlineItem(root, show) {
+ this.lastToggleIsShow = show;
+ var togglers = root.querySelectorAll('.outlineItemToggler');
+ for (var i = 0, ii = togglers.length; i < ii; ++i) {
+ togglers[i].classList[show ? 'remove' : 'add']('outlineItemsHidden');
+ }
+ },
+ /**
+ * Collapse or expand all subtrees of the outline.
+ */
+ toggleOutlineTree: function PDFOutlineViewer_toggleOutlineTree() {
+ if (!this.outline) {
+ return;
+ }
+ this._toggleOutlineItem(this.container, !this.lastToggleIsShow);
+ },
+ /**
+ * @param {PDFOutlineViewerRenderParameters} params
+ */
+ render: function PDFOutlineViewer_render(params) {
+ var outline = params && params.outline || null;
+ var outlineCount = 0;
+ if (this.outline) {
+ this.reset();
+ }
+ this.outline = outline;
+ if (!outline) {
+ this._dispatchEvent(outlineCount);
+ return;
+ }
+ var fragment = document.createDocumentFragment();
+ var queue = [{
+ parent: fragment,
+ items: this.outline
+ }];
+ var hasAnyNesting = false;
+ while (queue.length > 0) {
+ var levelData = queue.shift();
+ for (var i = 0, len = levelData.items.length; i < len; i++) {
+ var item = levelData.items[i];
+ var div = document.createElement('div');
+ div.className = 'outlineItem';
+ var element = document.createElement('a');
+ this._bindLink(element, item);
+ this._setStyles(element, item);
+ element.textContent = pdfjsLib.removeNullCharacters(item.title) || DEFAULT_TITLE;
+ div.appendChild(element);
+ if (item.items.length > 0) {
+ hasAnyNesting = true;
+ this._addToggleButton(div);
+ var itemsDiv = document.createElement('div');
+ itemsDiv.className = 'outlineItems';
+ div.appendChild(itemsDiv);
+ queue.push({
+ parent: itemsDiv,
+ items: item.items
+ });
+ }
+ levelData.parent.appendChild(div);
+ outlineCount++;
+ }
+ }
+ if (hasAnyNesting) {
+ this.container.classList.add('outlineWithDeepNesting');
+ }
+ this.container.appendChild(fragment);
+ this._dispatchEvent(outlineCount);
+ }
+ };
+ return PDFOutlineViewer;
+ }();
+ exports.PDFOutlineViewer = PDFOutlineViewer;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFSidebar = {}, root.pdfjsWebPDFRenderingQueue);
+ }(this, function (exports, pdfRenderingQueue) {
+ var RenderingStates = pdfRenderingQueue.RenderingStates;
+ var SidebarView = {
+ NONE: 0,
+ THUMBS: 1,
+ OUTLINE: 2,
+ ATTACHMENTS: 3
+ };
+ /**
+ * @typedef {Object} PDFSidebarOptions
+ * @property {PDFViewer} pdfViewer - The document viewer.
+ * @property {PDFThumbnailViewer} pdfThumbnailViewer - The thumbnail viewer.
+ * @property {PDFOutlineViewer} pdfOutlineViewer - The outline viewer.
+ * @property {HTMLDivElement} mainContainer - The main container
+ * (in which the viewer element is placed).
+ * @property {HTMLDivElement} outerContainer - The outer container
+ * (encasing both the viewer and sidebar elements).
+ * @property {EventBus} eventBus - The application event bus.
+ * @property {HTMLButtonElement} toggleButton - The button used for
+ * opening/closing the sidebar.
+ * @property {HTMLButtonElement} thumbnailButton - The button used to show
+ * the thumbnail view.
+ * @property {HTMLButtonElement} outlineButton - The button used to show
+ * the outline view.
+ * @property {HTMLButtonElement} attachmentsButton - The button used to show
+ * the attachments view.
+ * @property {HTMLDivElement} thumbnailView - The container in which
+ * the thumbnails are placed.
+ * @property {HTMLDivElement} outlineView - The container in which
+ * the outline is placed.
+ * @property {HTMLDivElement} attachmentsView - The container in which
+ * the attachments are placed.
+ */
+ /**
+ * @class
+ */
+ var PDFSidebar = function PDFSidebarClosure() {
+ /**
+ * @constructs PDFSidebar
+ * @param {PDFSidebarOptions} options
+ */
+ function PDFSidebar(options) {
+ this.isOpen = false;
+ this.active = SidebarView.THUMBS;
+ this.isInitialViewSet = false;
+ /**
+ * Callback used when the sidebar has been opened/closed, to ensure that
+ * the viewers (PDFViewer/PDFThumbnailViewer) are updated correctly.
+ */
+ this.onToggled = null;
+ this.pdfViewer = options.pdfViewer;
+ this.pdfThumbnailViewer = options.pdfThumbnailViewer;
+ this.pdfOutlineViewer = options.pdfOutlineViewer;
+ this.mainContainer = options.mainContainer;
+ this.outerContainer = options.outerContainer;
+ this.eventBus = options.eventBus;
+ this.toggleButton = options.toggleButton;
+ this.thumbnailButton = options.thumbnailButton;
+ this.outlineButton = options.outlineButton;
+ this.attachmentsButton = options.attachmentsButton;
+ this.thumbnailView = options.thumbnailView;
+ this.outlineView = options.outlineView;
+ this.attachmentsView = options.attachmentsView;
+ this._addEventListeners();
+ }
+ PDFSidebar.prototype = {
+ reset: function PDFSidebar_reset() {
+ this.isInitialViewSet = false;
+ this.close();
+ this.switchView(SidebarView.THUMBS);
+ this.outlineButton.disabled = false;
+ this.attachmentsButton.disabled = false;
+ },
+ /**
+ * @returns {number} One of the values in {SidebarView}.
+ */
+ get visibleView() {
+ return this.isOpen ? this.active : SidebarView.NONE;
+ },
+ get isThumbnailViewVisible() {
+ return this.isOpen && this.active === SidebarView.THUMBS;
+ },
+ get isOutlineViewVisible() {
+ return this.isOpen && this.active === SidebarView.OUTLINE;
+ },
+ get isAttachmentsViewVisible() {
+ return this.isOpen && this.active === SidebarView.ATTACHMENTS;
+ },
+ /**
+ * @param {number} view - The sidebar view that should become visible,
+ * must be one of the values in {SidebarView}.
+ */
+ setInitialView: function PDFSidebar_setInitialView(view) {
+ if (this.isInitialViewSet) {
+ return;
+ }
+ this.isInitialViewSet = true;
+ if (this.isOpen && view === SidebarView.NONE) {
+ this._dispatchEvent();
+ // If the user has already manually opened the sidebar,
+ // immediately closing it would be bad UX.
+ return;
+ }
+ var isViewPreserved = view === this.visibleView;
+ this.switchView(view, /* forceOpen */
+ true);
+ if (isViewPreserved) {
+ // Prevent dispatching two back-to-back `sidebarviewchanged` events,
+ // since `this.switchView` dispatched the event if the view changed.
+ this._dispatchEvent();
+ }
+ },
+ /**
+ * @param {number} view - The sidebar view that should be switched to,
+ * must be one of the values in {SidebarView}.
+ * @param {boolean} forceOpen - (optional) Ensure that the sidebar is open.
+ * The default value is false.
+ */
+ switchView: function PDFSidebar_switchView(view, forceOpen) {
+ if (view === SidebarView.NONE) {
+ this.close();
+ return;
+ }
+ var isViewChanged = view !== this.active;
+ var shouldForceRendering = false;
+ switch (view) {
+ case SidebarView.THUMBS:
+ this.thumbnailButton.classList.add('toggled');
+ this.outlineButton.classList.remove('toggled');
+ this.attachmentsButton.classList.remove('toggled');
+ this.thumbnailView.classList.remove('hidden');
+ this.outlineView.classList.add('hidden');
+ this.attachmentsView.classList.add('hidden');
+ if (this.isOpen && isViewChanged) {
+ this._updateThumbnailViewer();
+ shouldForceRendering = true;
+ }
+ break;
+ case SidebarView.OUTLINE:
+ if (this.outlineButton.disabled) {
+ return;
+ }
+ this.thumbnailButton.classList.remove('toggled');
+ this.outlineButton.classList.add('toggled');
+ this.attachmentsButton.classList.remove('toggled');
+ this.thumbnailView.classList.add('hidden');
+ this.outlineView.classList.remove('hidden');
+ this.attachmentsView.classList.add('hidden');
+ break;
+ case SidebarView.ATTACHMENTS:
+ if (this.attachmentsButton.disabled) {
+ return;
+ }
+ this.thumbnailButton.classList.remove('toggled');
+ this.outlineButton.classList.remove('toggled');
+ this.attachmentsButton.classList.add('toggled');
+ this.thumbnailView.classList.add('hidden');
+ this.outlineView.classList.add('hidden');
+ this.attachmentsView.classList.remove('hidden');
+ break;
+ default:
+ console.error('PDFSidebar_switchView: "' + view + '" is an unsupported value.');
+ return;
+ }
+ // Update the active view *after* it has been validated above,
+ // in order to prevent setting it to an invalid state.
+ this.active = view | 0;
+ if (forceOpen && !this.isOpen) {
+ this.open();
+ // NOTE: `this.open` will trigger rendering, and dispatch the event.
+ return;
+ }
+ if (shouldForceRendering) {
+ this._forceRendering();
+ }
+ if (isViewChanged) {
+ this._dispatchEvent();
+ }
+ },
+ open: function PDFSidebar_open() {
+ if (this.isOpen) {
+ return;
+ }
+ this.isOpen = true;
+ this.toggleButton.classList.add('toggled');
+ this.outerContainer.classList.add('sidebarMoving');
+ this.outerContainer.classList.add('sidebarOpen');
+ if (this.active === SidebarView.THUMBS) {
+ this._updateThumbnailViewer();
+ }
+ this._forceRendering();
+ this._dispatchEvent();
+ },
+ close: function PDFSidebar_close() {
+ if (!this.isOpen) {
+ return;
+ }
+ this.isOpen = false;
+ this.toggleButton.classList.remove('toggled');
+ this.outerContainer.classList.add('sidebarMoving');
+ this.outerContainer.classList.remove('sidebarOpen');
+ this._forceRendering();
+ this._dispatchEvent();
+ },
+ toggle: function PDFSidebar_toggle() {
+ if (this.isOpen) {
+ this.close();
+ } else {
+ this.open();
+ }
+ },
+ /**
+ * @private
+ */
+ _dispatchEvent: function PDFSidebar_dispatchEvent() {
+ this.eventBus.dispatch('sidebarviewchanged', {
+ source: this,
+ view: this.visibleView
+ });
+ },
+ /**
+ * @private
+ */
+ _forceRendering: function PDFSidebar_forceRendering() {
+ if (this.onToggled) {
+ this.onToggled();
+ } else {
+ // Fallback
+ this.pdfViewer.forceRendering();
+ this.pdfThumbnailViewer.forceRendering();
+ }
+ },
+ /**
+ * @private
+ */
+ _updateThumbnailViewer: function PDFSidebar_updateThumbnailViewer() {
+ var pdfViewer = this.pdfViewer;
+ var thumbnailViewer = this.pdfThumbnailViewer;
+ // Use the rendered pages to set the corresponding thumbnail images.
+ var pagesCount = pdfViewer.pagesCount;
+ for (var pageIndex = 0; pageIndex < pagesCount; pageIndex++) {
+ var pageView = pdfViewer.getPageView(pageIndex);
+ if (pageView && pageView.renderingState === RenderingStates.FINISHED) {
+ var thumbnailView = thumbnailViewer.getThumbnail(pageIndex);
+ thumbnailView.setImage(pageView);
+ }
+ }
+ thumbnailViewer.scrollThumbnailIntoView(pdfViewer.currentPageNumber);
+ },
+ /**
+ * @private
+ */
+ _addEventListeners: function PDFSidebar_addEventListeners() {
+ var self = this;
+ self.mainContainer.addEventListener('transitionend', function (evt) {
+ if (evt.target === /* mainContainer */
+ this) {
+ self.outerContainer.classList.remove('sidebarMoving');
+ }
+ });
+ // Buttons for switching views.
+ self.thumbnailButton.addEventListener('click', function () {
+ self.switchView(SidebarView.THUMBS);
+ });
+ self.outlineButton.addEventListener('click', function () {
+ self.switchView(SidebarView.OUTLINE);
+ });
+ self.outlineButton.addEventListener('dblclick', function () {
+ self.pdfOutlineViewer.toggleOutlineTree();
+ });
+ self.attachmentsButton.addEventListener('click', function () {
+ self.switchView(SidebarView.ATTACHMENTS);
+ });
+ // Disable/enable views.
+ self.eventBus.on('outlineloaded', function (e) {
+ var outlineCount = e.outlineCount;
+ self.outlineButton.disabled = !outlineCount;
+ if (!outlineCount && self.active === SidebarView.OUTLINE) {
+ self.switchView(SidebarView.THUMBS);
+ }
+ });
+ self.eventBus.on('attachmentsloaded', function (e) {
+ var attachmentsCount = e.attachmentsCount;
+ self.attachmentsButton.disabled = !attachmentsCount;
+ if (!attachmentsCount && self.active === SidebarView.ATTACHMENTS) {
+ self.switchView(SidebarView.THUMBS);
+ }
+ });
+ // Update the thumbnailViewer, if visible, when exiting presentation mode.
+ self.eventBus.on('presentationmodechanged', function (e) {
+ if (!e.active && !e.switchInProgress && self.isThumbnailViewVisible) {
+ self._updateThumbnailViewer();
+ }
+ });
+ }
+ };
+ return PDFSidebar;
+ }();
+ exports.SidebarView = SidebarView;
+ exports.PDFSidebar = PDFSidebar;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebUIUtils = {}, root.pdfjsWebPDFJS);
+ }(this, function (exports, pdfjsLib) {
+ var CSS_UNITS = 96.0 / 72.0;
+ var DEFAULT_SCALE_VALUE = 'auto';
+ var DEFAULT_SCALE = 1.0;
+ var UNKNOWN_SCALE = 0;
+ var MAX_AUTO_SCALE = 1.25;
+ var SCROLLBAR_PADDING = 40;
+ var VERTICAL_PADDING = 5;
+ var mozL10n = document.mozL10n || document.webL10n;
+ var PDFJS = pdfjsLib.PDFJS;
+ /**
+ * Disables fullscreen support, and by extension Presentation Mode,
+ * in browsers which support the fullscreen API.
+ * @var {boolean}
+ */
+ PDFJS.disableFullscreen = PDFJS.disableFullscreen === undefined ? false : PDFJS.disableFullscreen;
+ /**
+ * Enables CSS only zooming.
+ * @var {boolean}
+ */
+ PDFJS.useOnlyCssZoom = PDFJS.useOnlyCssZoom === undefined ? false : PDFJS.useOnlyCssZoom;
+ /**
+ * The maximum supported canvas size in total pixels e.g. width * height.
+ * The default value is 4096 * 4096. Use -1 for no limit.
+ * @var {number}
+ */
+ PDFJS.maxCanvasPixels = PDFJS.maxCanvasPixels === undefined ? 16777216 : PDFJS.maxCanvasPixels;
+ /**
+ * Disables saving of the last position of the viewed PDF.
+ * @var {boolean}
+ */
+ PDFJS.disableHistory = PDFJS.disableHistory === undefined ? false : PDFJS.disableHistory;
+ /**
+ * Disables creation of the text layer that used for text selection and search.
+ * @var {boolean}
+ */
+ PDFJS.disableTextLayer = PDFJS.disableTextLayer === undefined ? false : PDFJS.disableTextLayer;
+ /**
+ * Disables maintaining the current position in the document when zooming.
+ */
+ PDFJS.ignoreCurrentPositionOnZoom = PDFJS.ignoreCurrentPositionOnZoom === undefined ? false : PDFJS.ignoreCurrentPositionOnZoom;
+ /**
+ * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
+ * @return {Object} The object with horizontal (sx) and vertical (sy)
+ scales. The scaled property is set to false if scaling is
+ not required, true otherwise.
+ */
+ function getOutputScale(ctx) {
+ var devicePixelRatio = window.devicePixelRatio || 1;
+ var backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
+ var pixelRatio = devicePixelRatio / backingStoreRatio;
+ return {
+ sx: pixelRatio,
+ sy: pixelRatio,
+ scaled: pixelRatio !== 1
+ };
+ }
+ /**
+ * Scrolls specified element into view of its parent.
+ * @param {Object} element - The element to be visible.
+ * @param {Object} spot - An object with optional top and left properties,
+ * specifying the offset from the top left edge.
+ * @param {boolean} skipOverflowHiddenElements - Ignore elements that have
+ * the CSS rule `overflow: hidden;` set. The default is false.
+ */
+ function scrollIntoView(element, spot, skipOverflowHiddenElements) {
+ // Assuming offsetParent is available (it's not available when viewer is in
+ // hidden iframe or object). We have to scroll: if the offsetParent is not set
+ // producing the error. See also animationStartedClosure.
+ var parent = element.offsetParent;
+ if (!parent) {
+ console.error('offsetParent is not set -- cannot scroll');
+ return;
+ }
+ var checkOverflow = skipOverflowHiddenElements || false;
+ var offsetY = element.offsetTop + element.clientTop;
+ var offsetX = element.offsetLeft + element.clientLeft;
+ while (parent.clientHeight === parent.scrollHeight || checkOverflow && getComputedStyle(parent).overflow === 'hidden') {
+ if (parent.dataset._scaleY) {
+ offsetY /= parent.dataset._scaleY;
+ offsetX /= parent.dataset._scaleX;
+ }
+ offsetY += parent.offsetTop;
+ offsetX += parent.offsetLeft;
+ parent = parent.offsetParent;
+ if (!parent) {
+ return;
+ }
+ }
+ // no need to scroll
+ if (spot) {
+ if (spot.top !== undefined) {
+ offsetY += spot.top;
+ }
+ if (spot.left !== undefined) {
+ offsetX += spot.left;
+ parent.scrollLeft = offsetX;
+ }
+ }
+ parent.scrollTop = offsetY;
+ }
+ /**
+ * Helper function to start monitoring the scroll event and converting them into
+ * PDF.js friendly one: with scroll debounce and scroll direction.
+ */
+ function watchScroll(viewAreaElement, callback) {
+ var debounceScroll = function debounceScroll(evt) {
+ if (rAF) {
+ return;
+ }
+ // schedule an invocation of scroll for next animation frame.
+ rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
+ rAF = null;
+ var currentY = viewAreaElement.scrollTop;
+ var lastY = state.lastY;
+ if (currentY !== lastY) {
+ state.down = currentY > lastY;
+ }
+ state.lastY = currentY;
+ callback(state);
+ });
+ };
+ var state = {
+ down: true,
+ lastY: viewAreaElement.scrollTop,
+ _eventHandler: debounceScroll
+ };
+ var rAF = null;
+ viewAreaElement.addEventListener('scroll', debounceScroll, true);
+ return state;
+ }
+ /**
+ * Helper function to parse query string (e.g. ?param1=value&parm2=...).
+ */
+ function parseQueryString(query) {
+ var parts = query.split('&');
+ var params = {};
+ for (var i = 0, ii = parts.length; i < ii; ++i) {
+ var param = parts[i].split('=');
+ var key = param[0].toLowerCase();
+ var value = param.length > 1 ? param[1] : null;
+ params[decodeURIComponent(key)] = decodeURIComponent(value);
+ }
+ return params;
+ }
+ /**
+ * Use binary search to find the index of the first item in a given array which
+ * passes a given condition. The items are expected to be sorted in the sense
+ * that if the condition is true for one item in the array, then it is also true
+ * for all following items.
+ *
+ * @returns {Number} Index of the first array element to pass the test,
+ * or |items.length| if no such element exists.
+ */
+ function binarySearchFirstItem(items, condition) {
+ var minIndex = 0;
+ var maxIndex = items.length - 1;
+ if (items.length === 0 || !condition(items[maxIndex])) {
+ return items.length;
+ }
+ if (condition(items[minIndex])) {
+ return minIndex;
+ }
+ while (minIndex < maxIndex) {
+ var currentIndex = minIndex + maxIndex >> 1;
+ var currentItem = items[currentIndex];
+ if (condition(currentItem)) {
+ maxIndex = currentIndex;
+ } else {
+ minIndex = currentIndex + 1;
+ }
+ }
+ return minIndex;
+ }
+ /* === maxIndex */
+ /**
+ * Approximates float number as a fraction using Farey sequence (max order
+ * of 8).
+ * @param {number} x - Positive float number.
+ * @returns {Array} Estimated fraction: the first array item is a numerator,
+ * the second one is a denominator.
+ */
+ function approximateFraction(x) {
+ // Fast paths for int numbers or their inversions.
+ if (Math.floor(x) === x) {
+ return [
+ x,
+ 1
+ ];
+ }
+ var xinv = 1 / x;
+ var limit = 8;
+ if (xinv > limit) {
+ return [
+ 1,
+ limit
+ ];
+ } else if (Math.floor(xinv) === xinv) {
+ return [
+ 1,
+ xinv
+ ];
+ }
+ var x_ = x > 1 ? xinv : x;
+ // a/b and c/d are neighbours in Farey sequence.
+ var a = 0, b = 1, c = 1, d = 1;
+ // Limiting search to order 8.
+ while (true) {
+ // Generating next term in sequence (order of q).
+ var p = a + c, q = b + d;
+ if (q > limit) {
+ break;
+ }
+ if (x_ <= p / q) {
+ c = p;
+ d = q;
+ } else {
+ a = p;
+ b = q;
+ }
+ }
+ // Select closest of the neighbours to x.
+ if (x_ - a / b < c / d - x_) {
+ return x_ === x ? [
+ a,
+ b
+ ] : [
+ b,
+ a
+ ];
+ } else {
+ return x_ === x ? [
+ c,
+ d
+ ] : [
+ d,
+ c
+ ];
+ }
+ }
+ function roundToDivide(x, div) {
+ var r = x % div;
+ return r === 0 ? x : Math.round(x - r + div);
+ }
+ /**
+ * Generic helper to find out what elements are visible within a scroll pane.
+ */
+ function getVisibleElements(scrollEl, views, sortByVisibility) {
+ var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
+ var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
+ function isElementBottomBelowViewTop(view) {
+ var element = view.div;
+ var elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
+ return elementBottom > top;
+ }
+ var visible = [], view, element;
+ var currentHeight, viewHeight, hiddenHeight, percentHeight;
+ var currentWidth, viewWidth;
+ var firstVisibleElementInd = views.length === 0 ? 0 : binarySearchFirstItem(views, isElementBottomBelowViewTop);
+ for (var i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
+ view = views[i];
+ element = view.div;
+ currentHeight = element.offsetTop + element.clientTop;
+ viewHeight = element.clientHeight;
+ if (currentHeight > bottom) {
+ break;
+ }
+ currentWidth = element.offsetLeft + element.clientLeft;
+ viewWidth = element.clientWidth;
+ if (currentWidth + viewWidth < left || currentWidth > right) {
+ continue;
+ }
+ hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, currentHeight + viewHeight - bottom);
+ percentHeight = (viewHeight - hiddenHeight) * 100 / viewHeight | 0;
+ visible.push({
+ id: view.id,
+ x: currentWidth,
+ y: currentHeight,
+ view: view,
+ percent: percentHeight
+ });
+ }
+ var first = visible[0];
+ var last = visible[visible.length - 1];
+ if (sortByVisibility) {
+ visible.sort(function (a, b) {
+ var pc = a.percent - b.percent;
+ if (Math.abs(pc) > 0.001) {
+ return -pc;
+ }
+ return a.id - b.id;
+ });
+ }
+ // ensure stability
+ return {
+ first: first,
+ last: last,
+ views: visible
+ };
+ }
+ /**
+ * Event handler to suppress context menu.
+ */
+ function noContextMenuHandler(e) {
+ e.preventDefault();
+ }
+ /**
+ * Returns the filename or guessed filename from the url (see issue 3455).
+ * url {String} The original PDF location.
+ * @return {String} Guessed PDF file name.
+ */
+ function getPDFFileNameFromURL(url) {
+ var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
+ // SCHEME HOST 1.PATH 2.QUERY 3.REF
+ // Pattern to get last matching NAME.pdf
+ var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
+ var splitURI = reURI.exec(url);
+ var suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]);
+ if (suggestedFilename) {
+ suggestedFilename = suggestedFilename[0];
+ if (suggestedFilename.indexOf('%') !== -1) {
+ // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
+ try {
+ suggestedFilename = reFilename.exec(decodeURIComponent(suggestedFilename))[0];
+ } catch (e) {
+ }
+ }
+ }
+ return suggestedFilename || 'document.pdf';
+ }
+ function normalizeWheelEventDelta(evt) {
+ var delta = Math.sqrt(evt.deltaX * evt.deltaX + evt.deltaY * evt.deltaY);
+ var angle = Math.atan2(evt.deltaY, evt.deltaX);
+ if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
+ // All that is left-up oriented has to change the sign.
+ delta = -delta;
+ }
+ var MOUSE_DOM_DELTA_PIXEL_MODE = 0;
+ var MOUSE_DOM_DELTA_LINE_MODE = 1;
+ var MOUSE_PIXELS_PER_LINE = 30;
+ var MOUSE_LINES_PER_PAGE = 30;
+ // Converts delta to per-page units
+ if (evt.deltaMode === MOUSE_DOM_DELTA_PIXEL_MODE) {
+ delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
+ } else if (evt.deltaMode === MOUSE_DOM_DELTA_LINE_MODE) {
+ delta /= MOUSE_LINES_PER_PAGE;
+ }
+ return delta;
+ }
+ /**
+ * Simple event bus for an application. Listeners are attached using the
+ * `on` and `off` methods. To raise an event, the `dispatch` method shall be
+ * used.
+ */
+ var EventBus = function EventBusClosure() {
+ function EventBus() {
+ this._listeners = Object.create(null);
+ }
+ EventBus.prototype = {
+ on: function EventBus_on(eventName, listener) {
+ var eventListeners = this._listeners[eventName];
+ if (!eventListeners) {
+ eventListeners = [];
+ this._listeners[eventName] = eventListeners;
+ }
+ eventListeners.push(listener);
+ },
+ off: function EventBus_on(eventName, listener) {
+ var eventListeners = this._listeners[eventName];
+ var i;
+ if (!eventListeners || (i = eventListeners.indexOf(listener)) < 0) {
+ return;
+ }
+ eventListeners.splice(i, 1);
+ },
+ dispatch: function EventBus_dispath(eventName) {
+ var eventListeners = this._listeners[eventName];
+ if (!eventListeners || eventListeners.length === 0) {
+ return;
+ }
+ // Passing all arguments after the eventName to the listeners.
+ var args = Array.prototype.slice.call(arguments, 1);
+ // Making copy of the listeners array in case if it will be modified
+ // during dispatch.
+ eventListeners.slice(0).forEach(function (listener) {
+ listener.apply(null, args);
+ });
+ }
+ };
+ return EventBus;
+ }();
+ var ProgressBar = function ProgressBarClosure() {
+ function clamp(v, min, max) {
+ return Math.min(Math.max(v, min), max);
+ }
+ function ProgressBar(id, opts) {
+ this.visible = true;
+ // Fetch the sub-elements for later.
+ this.div = document.querySelector(id + ' .progress');
+ // Get the loading bar element, so it can be resized to fit the viewer.
+ this.bar = this.div.parentNode;
+ // Get options, with sensible defaults.
+ this.height = opts.height || 100;
+ this.width = opts.width || 100;
+ this.units = opts.units || '%';
+ // Initialize heights.
+ this.div.style.height = this.height + this.units;
+ this.percent = 0;
+ }
+ ProgressBar.prototype = {
+ updateBar: function ProgressBar_updateBar() {
+ if (this._indeterminate) {
+ this.div.classList.add('indeterminate');
+ this.div.style.width = this.width + this.units;
+ return;
+ }
+ this.div.classList.remove('indeterminate');
+ var progressSize = this.width * this._percent / 100;
+ this.div.style.width = progressSize + this.units;
+ },
+ get percent() {
+ return this._percent;
+ },
+ set percent(val) {
+ this._indeterminate = isNaN(val);
+ this._percent = clamp(val, 0, 100);
+ this.updateBar();
+ },
+ setWidth: function ProgressBar_setWidth(viewer) {
+ if (viewer) {
+ var container = viewer.parentNode;
+ var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
+ if (scrollbarWidth > 0) {
+ this.bar.setAttribute('style', 'width: calc(100% - ' + scrollbarWidth + 'px);');
+ }
+ }
+ },
+ hide: function ProgressBar_hide() {
+ if (!this.visible) {
+ return;
+ }
+ this.visible = false;
+ this.bar.classList.add('hidden');
+ document.body.classList.remove('loadingInProgress');
+ },
+ show: function ProgressBar_show() {
+ if (this.visible) {
+ return;
+ }
+ this.visible = true;
+ document.body.classList.add('loadingInProgress');
+ this.bar.classList.remove('hidden');
+ }
+ };
+ return ProgressBar;
+ }();
+ exports.CSS_UNITS = CSS_UNITS;
+ exports.DEFAULT_SCALE_VALUE = DEFAULT_SCALE_VALUE;
+ exports.DEFAULT_SCALE = DEFAULT_SCALE;
+ exports.UNKNOWN_SCALE = UNKNOWN_SCALE;
+ exports.MAX_AUTO_SCALE = MAX_AUTO_SCALE;
+ exports.SCROLLBAR_PADDING = SCROLLBAR_PADDING;
+ exports.VERTICAL_PADDING = VERTICAL_PADDING;
+ exports.mozL10n = mozL10n;
+ exports.EventBus = EventBus;
+ exports.ProgressBar = ProgressBar;
+ exports.getPDFFileNameFromURL = getPDFFileNameFromURL;
+ exports.noContextMenuHandler = noContextMenuHandler;
+ exports.parseQueryString = parseQueryString;
+ exports.getVisibleElements = getVisibleElements;
+ exports.roundToDivide = roundToDivide;
+ exports.approximateFraction = approximateFraction;
+ exports.getOutputScale = getOutputScale;
+ exports.scrollIntoView = scrollIntoView;
+ exports.watchScroll = watchScroll;
+ exports.binarySearchFirstItem = binarySearchFirstItem;
+ exports.normalizeWheelEventDelta = normalizeWheelEventDelta;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebDOMEvents = {}, root.pdfjsWebUIUtils);
+ }(this, function (exports, uiUtils) {
+ var EventBus = uiUtils.EventBus;
+ // Attaching to the application event bus to dispatch events to the DOM for
+ // backwards viewer API compatibility.
+ function attachDOMEventsToEventBus(eventBus) {
+ eventBus.on('documentload', function () {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('documentload', true, true, {});
+ window.dispatchEvent(event);
+ });
+ eventBus.on('pagerendered', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagerendered', true, true, {
+ pageNumber: e.pageNumber,
+ cssTransform: e.cssTransform
+ });
+ e.source.div.dispatchEvent(event);
+ });
+ eventBus.on('textlayerrendered', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('textlayerrendered', true, true, { pageNumber: e.pageNumber });
+ e.source.textLayerDiv.dispatchEvent(event);
+ });
+ eventBus.on('pagechange', function (e) {
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('pagechange', true, true, window, 0);
+ event.pageNumber = e.pageNumber;
+ e.source.container.dispatchEvent(event);
+ });
+ eventBus.on('pagesinit', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagesinit', true, true, null);
+ e.source.container.dispatchEvent(event);
+ });
+ eventBus.on('pagesloaded', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagesloaded', true, true, { pagesCount: e.pagesCount });
+ e.source.container.dispatchEvent(event);
+ });
+ eventBus.on('scalechange', function (e) {
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('scalechange', true, true, window, 0);
+ event.scale = e.scale;
+ event.presetValue = e.presetValue;
+ e.source.container.dispatchEvent(event);
+ });
+ eventBus.on('updateviewarea', function (e) {
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('updateviewarea', true, true, window, 0);
+ event.location = e.location;
+ e.source.container.dispatchEvent(event);
+ });
+ eventBus.on('find', function (e) {
+ if (e.source === window) {
+ return;
+ }
+ // event comes from FirefoxCom, no need to replicate
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('find' + e.type, true, true, {
+ query: e.query,
+ phraseSearch: e.phraseSearch,
+ caseSensitive: e.caseSensitive,
+ highlightAll: e.highlightAll,
+ findPrevious: e.findPrevious
+ });
+ window.dispatchEvent(event);
+ });
+ eventBus.on('attachmentsloaded', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('attachmentsloaded', true, true, { attachmentsCount: e.attachmentsCount });
+ e.source.container.dispatchEvent(event);
+ });
+ eventBus.on('sidebarviewchanged', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('sidebarviewchanged', true, true, { view: e.view });
+ e.source.outerContainer.dispatchEvent(event);
+ });
+ eventBus.on('pagemode', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagemode', true, true, { mode: e.mode });
+ e.source.pdfViewer.container.dispatchEvent(event);
+ });
+ eventBus.on('namedaction', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('namedaction', true, true, { action: e.action });
+ e.source.pdfViewer.container.dispatchEvent(event);
+ });
+ eventBus.on('presentationmodechanged', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('presentationmodechanged', true, true, {
+ active: e.active,
+ switchInProgress: e.switchInProgress
+ });
+ window.dispatchEvent(event);
+ });
+ eventBus.on('outlineloaded', function (e) {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('outlineloaded', true, true, { outlineCount: e.outlineCount });
+ e.source.container.dispatchEvent(event);
+ });
+ }
+ var globalEventBus = null;
+ function getGlobalEventBus() {
+ if (globalEventBus) {
+ return globalEventBus;
+ }
+ globalEventBus = new EventBus();
+ attachDOMEventsToEventBus(globalEventBus);
+ return globalEventBus;
+ }
+ exports.attachDOMEventsToEventBus = attachDOMEventsToEventBus;
+ exports.getGlobalEventBus = getGlobalEventBus;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPasswordPrompt = {}, root.pdfjsWebUIUtils, root.pdfjsWebOverlayManager, root.pdfjsWebPDFJS);
+ }(this, function (exports, uiUtils, overlayManager, pdfjsLib) {
+ var mozL10n = uiUtils.mozL10n;
+ var OverlayManager = overlayManager.OverlayManager;
+ /**
+ * @typedef {Object} PasswordPromptOptions
+ * @property {string} overlayName - Name of the overlay for the overlay manager.
+ * @property {HTMLDivElement} container - Div container for the overlay.
+ * @property {HTMLParagraphElement} label - Label containing instructions for
+ * entering the password.
+ * @property {HTMLInputElement} input - Input field for entering the password.
+ * @property {HTMLButtonElement} submitButton - Button for submitting the
+ * password.
+ * @property {HTMLButtonElement} cancelButton - Button for cancelling password
+ * entry.
+ */
+ /**
+ * @class
+ */
+ var PasswordPrompt = function PasswordPromptClosure() {
+ /**
+ * @constructs PasswordPrompt
+ * @param {PasswordPromptOptions} options
+ */
+ function PasswordPrompt(options) {
+ this.overlayName = options.overlayName;
+ this.container = options.container;
+ this.label = options.label;
+ this.input = options.input;
+ this.submitButton = options.submitButton;
+ this.cancelButton = options.cancelButton;
+ this.updateCallback = null;
+ this.reason = null;
+ // Attach the event listeners.
+ this.submitButton.addEventListener('click', this.verify.bind(this));
+ this.cancelButton.addEventListener('click', this.close.bind(this));
+ this.input.addEventListener('keydown', function (e) {
+ if (e.keyCode === 13) {
+ // Enter key
+ this.verify();
+ }
+ }.bind(this));
+ OverlayManager.register(this.overlayName, this.container, this.close.bind(this), true);
+ }
+ PasswordPrompt.prototype = {
+ open: function PasswordPrompt_open() {
+ OverlayManager.open(this.overlayName).then(function () {
+ this.input.type = 'password';
+ this.input.focus();
+ var promptString = mozL10n.get('password_label', null, 'Enter the password to open this PDF file.');
+ if (this.reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
+ promptString = mozL10n.get('password_invalid', null, 'Invalid password. Please try again.');
+ }
+ this.label.textContent = promptString;
+ }.bind(this));
+ },
+ close: function PasswordPrompt_close() {
+ OverlayManager.close(this.overlayName).then(function () {
+ this.input.value = '';
+ this.input.type = '';
+ }.bind(this));
+ },
+ verify: function PasswordPrompt_verify() {
+ var password = this.input.value;
+ if (password && password.length > 0) {
+ this.close();
+ return this.updateCallback(password);
+ }
+ },
+ setUpdateCallback: function PasswordPrompt_setUpdateCallback(updateCallback, reason) {
+ this.updateCallback = updateCallback;
+ this.reason = reason;
+ }
+ };
+ return PasswordPrompt;
+ }();
+ exports.PasswordPrompt = PasswordPrompt;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFDocumentProperties = {}, root.pdfjsWebUIUtils, root.pdfjsWebOverlayManager);
+ }(this, function (exports, uiUtils, overlayManager) {
+ var getPDFFileNameFromURL = uiUtils.getPDFFileNameFromURL;
+ var mozL10n = uiUtils.mozL10n;
+ var OverlayManager = overlayManager.OverlayManager;
+ /**
+ * @typedef {Object} PDFDocumentPropertiesOptions
+ * @property {string} overlayName - Name/identifier for the overlay.
+ * @property {Object} fields - Names and elements of the overlay's fields.
+ * @property {HTMLButtonElement} closeButton - Button for closing the overlay.
+ */
+ /**
+ * @class
+ */
+ var PDFDocumentProperties = function PDFDocumentPropertiesClosure() {
+ /**
+ * @constructs PDFDocumentProperties
+ * @param {PDFDocumentPropertiesOptions} options
+ */
+ function PDFDocumentProperties(options) {
+ this.fields = options.fields;
+ this.overlayName = options.overlayName;
+ this.container = options.container;
+ this.rawFileSize = 0;
+ this.url = null;
+ this.pdfDocument = null;
+ // Bind the event listener for the Close button.
+ if (options.closeButton) {
+ options.closeButton.addEventListener('click', this.close.bind(this));
+ }
+ this.dataAvailablePromise = new Promise(function (resolve) {
+ this.resolveDataAvailable = resolve;
+ }.bind(this));
+ OverlayManager.register(this.overlayName, this.container, this.close.bind(this));
+ }
+ PDFDocumentProperties.prototype = {
+ /**
+ * Open the document properties overlay.
+ */
+ open: function PDFDocumentProperties_open() {
+ Promise.all([
+ OverlayManager.open(this.overlayName),
+ this.dataAvailablePromise
+ ]).then(function () {
+ this._getProperties();
+ }.bind(this));
+ },
+ /**
+ * Close the document properties overlay.
+ */
+ close: function PDFDocumentProperties_close() {
+ OverlayManager.close(this.overlayName);
+ },
+ /**
+ * Set the file size of the PDF document. This method is used to
+ * update the file size in the document properties overlay once it
+ * is known so we do not have to wait until the entire file is loaded.
+ *
+ * @param {number} fileSize - The file size of the PDF document.
+ */
+ setFileSize: function PDFDocumentProperties_setFileSize(fileSize) {
+ if (fileSize > 0) {
+ this.rawFileSize = fileSize;
+ }
+ },
+ /**
+ * Set a reference to the PDF document and the URL in order
+ * to populate the overlay fields with the document properties.
+ * Note that the overlay will contain no information if this method
+ * is not called.
+ *
+ * @param {Object} pdfDocument - A reference to the PDF document.
+ * @param {string} url - The URL of the document.
+ */
+ setDocumentAndUrl: function PDFDocumentProperties_setDocumentAndUrl(pdfDocument, url) {
+ this.pdfDocument = pdfDocument;
+ this.url = url;
+ this.resolveDataAvailable();
+ },
+ /**
+ * @private
+ */
+ _getProperties: function PDFDocumentProperties_getProperties() {
+ if (!OverlayManager.active) {
+ // If the dialog was closed before dataAvailablePromise was resolved,
+ // don't bother updating the properties.
+ return;
+ }
+ // Get the file size (if it hasn't already been set).
+ this.pdfDocument.getDownloadInfo().then(function (data) {
+ if (data.length === this.rawFileSize) {
+ return;
+ }
+ this.setFileSize(data.length);
+ this._updateUI(this.fields['fileSize'], this._parseFileSize());
+ }.bind(this));
+ // Get the document properties.
+ this.pdfDocument.getMetadata().then(function (data) {
+ var content = {
+ 'fileName': getPDFFileNameFromURL(this.url),
+ 'fileSize': this._parseFileSize(),
+ 'title': data.info.Title,
+ 'author': data.info.Author,
+ 'subject': data.info.Subject,
+ 'keywords': data.info.Keywords,
+ 'creationDate': this._parseDate(data.info.CreationDate),
+ 'modificationDate': this._parseDate(data.info.ModDate),
+ 'creator': data.info.Creator,
+ 'producer': data.info.Producer,
+ 'version': data.info.PDFFormatVersion,
+ 'pageCount': this.pdfDocument.numPages
+ };
+ // Show the properties in the dialog.
+ for (var identifier in content) {
+ this._updateUI(this.fields[identifier], content[identifier]);
+ }
+ }.bind(this));
+ },
+ /**
+ * @private
+ */
+ _updateUI: function PDFDocumentProperties_updateUI(field, content) {
+ if (field && content !== undefined && content !== '') {
+ field.textContent = content;
+ }
+ },
+ /**
+ * @private
+ */
+ _parseFileSize: function PDFDocumentProperties_parseFileSize() {
+ var fileSize = this.rawFileSize, kb = fileSize / 1024;
+ if (!kb) {
+ return;
+ } else if (kb < 1024) {
+ return mozL10n.get('document_properties_kb', {
+ size_kb: (+kb.toPrecision(3)).toLocaleString(),
+ size_b: fileSize.toLocaleString()
+ }, '{{size_kb}} KB ({{size_b}} bytes)');
+ } else {
+ return mozL10n.get('document_properties_mb', {
+ size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
+ size_b: fileSize.toLocaleString()
+ }, '{{size_mb}} MB ({{size_b}} bytes)');
+ }
+ },
+ /**
+ * @private
+ */
+ _parseDate: function PDFDocumentProperties_parseDate(inputDate) {
+ // This is implemented according to the PDF specification, but note that
+ // Adobe Reader doesn't handle changing the date to universal time
+ // and doesn't use the user's time zone (they're effectively ignoring
+ // the HH' and mm' parts of the date string).
+ var dateToParse = inputDate;
+ if (dateToParse === undefined) {
+ return '';
+ }
+ // Remove the D: prefix if it is available.
+ if (dateToParse.substring(0, 2) === 'D:') {
+ dateToParse = dateToParse.substring(2);
+ }
+ // Get all elements from the PDF date string.
+ // JavaScript's Date object expects the month to be between
+ // 0 and 11 instead of 1 and 12, so we're correcting for this.
+ var year = parseInt(dateToParse.substring(0, 4), 10);
+ var month = parseInt(dateToParse.substring(4, 6), 10) - 1;
+ var day = parseInt(dateToParse.substring(6, 8), 10);
+ var hours = parseInt(dateToParse.substring(8, 10), 10);
+ var minutes = parseInt(dateToParse.substring(10, 12), 10);
+ var seconds = parseInt(dateToParse.substring(12, 14), 10);
+ var utRel = dateToParse.substring(14, 15);
+ var offsetHours = parseInt(dateToParse.substring(15, 17), 10);
+ var offsetMinutes = parseInt(dateToParse.substring(18, 20), 10);
+ // As per spec, utRel = 'Z' means equal to universal time.
+ // The other cases ('-' and '+') have to be handled here.
+ if (utRel === '-') {
+ hours += offsetHours;
+ minutes += offsetMinutes;
+ } else if (utRel === '+') {
+ hours -= offsetHours;
+ minutes -= offsetMinutes;
+ }
+ // Return the new date format from the user's locale.
+ var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
+ var dateString = date.toLocaleDateString();
+ var timeString = date.toLocaleTimeString();
+ return mozL10n.get('document_properties_date_string', {
+ date: dateString,
+ time: timeString
+ }, '{{date}}, {{time}}');
+ }
+ };
+ return PDFDocumentProperties;
+ }();
+ exports.PDFDocumentProperties = PDFDocumentProperties;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFFindController = {}, root.pdfjsWebUIUtils);
+ }(this, function (exports, uiUtils) {
+ var scrollIntoView = uiUtils.scrollIntoView;
+ var FindStates = {
+ FIND_FOUND: 0,
+ FIND_NOTFOUND: 1,
+ FIND_WRAPPED: 2,
+ FIND_PENDING: 3
+ };
+ var FIND_SCROLL_OFFSET_TOP = -50;
+ var FIND_SCROLL_OFFSET_LEFT = -400;
+ var CHARACTERS_TO_NORMALIZE = {
+ '\u2018': '\'',
+ // Left single quotation mark
+ '\u2019': '\'',
+ // Right single quotation mark
+ '\u201A': '\'',
+ // Single low-9 quotation mark
+ '\u201B': '\'',
+ // Single high-reversed-9 quotation mark
+ '\u201C': '"',
+ // Left double quotation mark
+ '\u201D': '"',
+ // Right double quotation mark
+ '\u201E': '"',
+ // Double low-9 quotation mark
+ '\u201F': '"',
+ // Double high-reversed-9 quotation mark
+ '\u00BC': '1/4',
+ // Vulgar fraction one quarter
+ '\u00BD': '1/2',
+ // Vulgar fraction one half
+ '\u00BE': '3/4'
+ };
+ // Vulgar fraction three quarters
+ /**
+ * Provides "search" or "find" functionality for the PDF.
+ * This object actually performs the search for a given string.
+ */
+ var PDFFindController = function PDFFindControllerClosure() {
+ function PDFFindController(options) {
+ this.pdfViewer = options.pdfViewer || null;
+ this.onUpdateResultsCount = null;
+ this.onUpdateState = null;
+ this.reset();
+ // Compile the regular expression for text normalization once.
+ var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
+ this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
+ }
+ PDFFindController.prototype = {
+ reset: function PDFFindController_reset() {
+ this.startedTextExtraction = false;
+ this.extractTextPromises = [];
+ this.pendingFindMatches = Object.create(null);
+ this.active = false;
+ // If active, find results will be highlighted.
+ this.pageContents = [];
+ // Stores the text for each page.
+ this.pageMatches = [];
+ this.pageMatchesLength = null;
+ this.matchCount = 0;
+ this.selected = {
+ // Currently selected match.
+ pageIdx: -1,
+ matchIdx: -1
+ };
+ this.offset = {
+ // Where the find algorithm currently is in the document.
+ pageIdx: null,
+ matchIdx: null
+ };
+ this.pagesToSearch = null;
+ this.resumePageIdx = null;
+ this.state = null;
+ this.dirtyMatch = false;
+ this.findTimeout = null;
+ this.firstPagePromise = new Promise(function (resolve) {
+ this.resolveFirstPage = resolve;
+ }.bind(this));
+ },
+ normalize: function PDFFindController_normalize(text) {
+ return text.replace(this.normalizationRegex, function (ch) {
+ return CHARACTERS_TO_NORMALIZE[ch];
+ });
+ },
+ // Helper for multiple search - fills matchesWithLength array
+ // and takes into account cases when one search term
+ // include another search term (for example, "tamed tame" or "this is").
+ // Looking for intersecting terms in the 'matches' and
+ // leave elements with a longer match-length.
+ _prepareMatches: function PDFFindController_prepareMatches(matchesWithLength, matches, matchesLength) {
+ function isSubTerm(matchesWithLength, currentIndex) {
+ var currentElem, prevElem, nextElem;
+ currentElem = matchesWithLength[currentIndex];
+ nextElem = matchesWithLength[currentIndex + 1];
+ // checking for cases like "TAMEd TAME"
+ if (currentIndex < matchesWithLength.length - 1 && currentElem.match === nextElem.match) {
+ currentElem.skipped = true;
+ return true;
+ }
+ // checking for cases like "thIS IS"
+ for (var i = currentIndex - 1; i >= 0; i--) {
+ prevElem = matchesWithLength[i];
+ if (prevElem.skipped) {
+ continue;
+ }
+ if (prevElem.match + prevElem.matchLength < currentElem.match) {
+ break;
+ }
+ if (prevElem.match + prevElem.matchLength >= currentElem.match + currentElem.matchLength) {
+ currentElem.skipped = true;
+ return true;
+ }
+ }
+ return false;
+ }
+ var i, len;
+ // Sorting array of objects { match: <match>, matchLength: <matchLength> }
+ // in increasing index first and then the lengths.
+ matchesWithLength.sort(function (a, b) {
+ return a.match === b.match ? a.matchLength - b.matchLength : a.match - b.match;
+ });
+ for (i = 0, len = matchesWithLength.length; i < len; i++) {
+ if (isSubTerm(matchesWithLength, i)) {
+ continue;
+ }
+ matches.push(matchesWithLength[i].match);
+ matchesLength.push(matchesWithLength[i].matchLength);
+ }
+ },
+ calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch(query, pageIndex, pageContent) {
+ var matches = [];
+ var queryLen = query.length;
+ var matchIdx = -queryLen;
+ while (true) {
+ matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
+ if (matchIdx === -1) {
+ break;
+ }
+ matches.push(matchIdx);
+ }
+ this.pageMatches[pageIndex] = matches;
+ },
+ calcFindWordMatch: function PDFFindController_calcFindWordMatch(query, pageIndex, pageContent) {
+ var matchesWithLength = [];
+ // Divide the query into pieces and search for text on each piece.
+ var queryArray = query.match(/\S+/g);
+ var subquery, subqueryLen, matchIdx;
+ for (var i = 0, len = queryArray.length; i < len; i++) {
+ subquery = queryArray[i];
+ subqueryLen = subquery.length;
+ matchIdx = -subqueryLen;
+ while (true) {
+ matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen);
+ if (matchIdx === -1) {
+ break;
+ }
+ // Other searches do not, so we store the length.
+ matchesWithLength.push({
+ match: matchIdx,
+ matchLength: subqueryLen,
+ skipped: false
+ });
+ }
+ }
+ // Prepare arrays for store the matches.
+ if (!this.pageMatchesLength) {
+ this.pageMatchesLength = [];
+ }
+ this.pageMatchesLength[pageIndex] = [];
+ this.pageMatches[pageIndex] = [];
+ // Sort matchesWithLength, clean up intersecting terms
+ // and put the result into the two arrays.
+ this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], this.pageMatchesLength[pageIndex]);
+ },
+ calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) {
+ var pageContent = this.normalize(this.pageContents[pageIndex]);
+ var query = this.normalize(this.state.query);
+ var caseSensitive = this.state.caseSensitive;
+ var phraseSearch = this.state.phraseSearch;
+ var queryLen = query.length;
+ if (queryLen === 0) {
+ // Do nothing: the matches should be wiped out already.
+ return;
+ }
+ if (!caseSensitive) {
+ pageContent = pageContent.toLowerCase();
+ query = query.toLowerCase();
+ }
+ if (phraseSearch) {
+ this.calcFindPhraseMatch(query, pageIndex, pageContent);
+ } else {
+ this.calcFindWordMatch(query, pageIndex, pageContent);
+ }
+ this.updatePage(pageIndex);
+ if (this.resumePageIdx === pageIndex) {
+ this.resumePageIdx = null;
+ this.nextPageMatch();
+ }
+ // Update the matches count
+ if (this.pageMatches[pageIndex].length > 0) {
+ this.matchCount += this.pageMatches[pageIndex].length;
+ this.updateUIResultsCount();
+ }
+ },
+ extractText: function PDFFindController_extractText() {
+ if (this.startedTextExtraction) {
+ return;
+ }
+ this.startedTextExtraction = true;
+ this.pageContents = [];
+ var extractTextPromisesResolves = [];
+ var numPages = this.pdfViewer.pagesCount;
+ for (var i = 0; i < numPages; i++) {
+ this.extractTextPromises.push(new Promise(function (resolve) {
+ extractTextPromisesResolves.push(resolve);
+ }));
+ }
+ var self = this;
+ function extractPageText(pageIndex) {
+ self.pdfViewer.getPageTextContent(pageIndex).then(function textContentResolved(textContent) {
+ var textItems = textContent.items;
+ var str = [];
+ for (var i = 0, len = textItems.length; i < len; i++) {
+ str.push(textItems[i].str);
+ }
+ // Store the pageContent as a string.
+ self.pageContents.push(str.join(''));
+ extractTextPromisesResolves[pageIndex](pageIndex);
+ if (pageIndex + 1 < self.pdfViewer.pagesCount) {
+ extractPageText(pageIndex + 1);
+ }
+ });
+ }
+ extractPageText(0);
+ },
+ executeCommand: function PDFFindController_executeCommand(cmd, state) {
+ if (this.state === null || cmd !== 'findagain') {
+ this.dirtyMatch = true;
+ }
+ this.state = state;
+ this.updateUIState(FindStates.FIND_PENDING);
+ this.firstPagePromise.then(function () {
+ this.extractText();
+ clearTimeout(this.findTimeout);
+ if (cmd === 'find') {
+ // Only trigger the find action after 250ms of silence.
+ this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
+ } else {
+ this.nextMatch();
+ }
+ }.bind(this));
+ },
+ updatePage: function PDFFindController_updatePage(index) {
+ if (this.selected.pageIdx === index) {
+ // If the page is selected, scroll the page into view, which triggers
+ // rendering the page, which adds the textLayer. Once the textLayer is
+ // build, it will scroll onto the selected match.
+ this.pdfViewer.currentPageNumber = index + 1;
+ }
+ var page = this.pdfViewer.getPageView(index);
+ if (page.textLayer) {
+ page.textLayer.updateMatches();
+ }
+ },
+ nextMatch: function PDFFindController_nextMatch() {
+ var previous = this.state.findPrevious;
+ var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
+ var numPages = this.pdfViewer.pagesCount;
+ this.active = true;
+ if (this.dirtyMatch) {
+ // Need to recalculate the matches, reset everything.
+ this.dirtyMatch = false;
+ this.selected.pageIdx = this.selected.matchIdx = -1;
+ this.offset.pageIdx = currentPageIndex;
+ this.offset.matchIdx = null;
+ this.hadMatch = false;
+ this.resumePageIdx = null;
+ this.pageMatches = [];
+ this.matchCount = 0;
+ this.pageMatchesLength = null;
+ var self = this;
+ for (var i = 0; i < numPages; i++) {
+ // Wipe out any previous highlighted matches.
+ this.updatePage(i);
+ // As soon as the text is extracted start finding the matches.
+ if (!(i in this.pendingFindMatches)) {
+ this.pendingFindMatches[i] = true;
+ this.extractTextPromises[i].then(function (pageIdx) {
+ delete self.pendingFindMatches[pageIdx];
+ self.calcFindMatch(pageIdx);
+ });
+ }
+ }
+ }
+ // If there's no query there's no point in searching.
+ if (this.state.query === '') {
+ this.updateUIState(FindStates.FIND_FOUND);
+ return;
+ }
+ // If we're waiting on a page, we return since we can't do anything else.
+ if (this.resumePageIdx) {
+ return;
+ }
+ var offset = this.offset;
+ // Keep track of how many pages we should maximally iterate through.
+ this.pagesToSearch = numPages;
+ // If there's already a matchIdx that means we are iterating through a
+ // page's matches.
+ if (offset.matchIdx !== null) {
+ var numPageMatches = this.pageMatches[offset.pageIdx].length;
+ if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
+ // The simple case; we just have advance the matchIdx to select
+ // the next match on the page.
+ this.hadMatch = true;
+ offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
+ this.updateMatch(true);
+ return;
+ }
+ // We went beyond the current page's matches, so we advance to
+ // the next page.
+ this.advanceOffsetPage(previous);
+ }
+ // Start searching through the page.
+ this.nextPageMatch();
+ },
+ matchesReady: function PDFFindController_matchesReady(matches) {
+ var offset = this.offset;
+ var numMatches = matches.length;
+ var previous = this.state.findPrevious;
+ if (numMatches) {
+ // There were matches for the page, so initialize the matchIdx.
+ this.hadMatch = true;
+ offset.matchIdx = previous ? numMatches - 1 : 0;
+ this.updateMatch(true);
+ return true;
+ } else {
+ // No matches, so attempt to search the next page.
+ this.advanceOffsetPage(previous);
+ if (offset.wrapped) {
+ offset.matchIdx = null;
+ if (this.pagesToSearch < 0) {
+ // No point in wrapping again, there were no matches.
+ this.updateMatch(false);
+ // while matches were not found, searching for a page
+ // with matches should nevertheless halt.
+ return true;
+ }
+ }
+ // Matches were not found (and searching is not done).
+ return false;
+ }
+ },
+ /**
+ * The method is called back from the text layer when match presentation
+ * is updated.
+ * @param {number} pageIndex - page index.
+ * @param {number} index - match index.
+ * @param {Array} elements - text layer div elements array.
+ * @param {number} beginIdx - start index of the div array for the match.
+ */
+ updateMatchPosition: function PDFFindController_updateMatchPosition(pageIndex, index, elements, beginIdx) {
+ if (this.selected.matchIdx === index && this.selected.pageIdx === pageIndex) {
+ var spot = {
+ top: FIND_SCROLL_OFFSET_TOP,
+ left: FIND_SCROLL_OFFSET_LEFT
+ };
+ scrollIntoView(elements[beginIdx], spot, /* skipOverflowHiddenElements = */
+ true);
+ }
+ },
+ nextPageMatch: function PDFFindController_nextPageMatch() {
+ if (this.resumePageIdx !== null) {
+ console.error('There can only be one pending page.');
+ }
+ do {
+ var pageIdx = this.offset.pageIdx;
+ var matches = this.pageMatches[pageIdx];
+ if (!matches) {
+ // The matches don't exist yet for processing by "matchesReady",
+ // so set a resume point for when they do exist.
+ this.resumePageIdx = pageIdx;
+ break;
+ }
+ } while (!this.matchesReady(matches));
+ },
+ advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) {
+ var offset = this.offset;
+ var numPages = this.extractTextPromises.length;
+ offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
+ offset.matchIdx = null;
+ this.pagesToSearch--;
+ if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
+ offset.pageIdx = previous ? numPages - 1 : 0;
+ offset.wrapped = true;
+ }
+ },
+ updateMatch: function PDFFindController_updateMatch(found) {
+ var state = FindStates.FIND_NOTFOUND;
+ var wrapped = this.offset.wrapped;
+ this.offset.wrapped = false;
+ if (found) {
+ var previousPage = this.selected.pageIdx;
+ this.selected.pageIdx = this.offset.pageIdx;
+ this.selected.matchIdx = this.offset.matchIdx;
+ state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND;
+ // Update the currently selected page to wipe out any selected matches.
+ if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
+ this.updatePage(previousPage);
+ }
+ }
+ this.updateUIState(state, this.state.findPrevious);
+ if (this.selected.pageIdx !== -1) {
+ this.updatePage(this.selected.pageIdx);
+ }
+ },
+ updateUIResultsCount: function PDFFindController_updateUIResultsCount() {
+ if (this.onUpdateResultsCount) {
+ this.onUpdateResultsCount(this.matchCount);
+ }
+ },
+ updateUIState: function PDFFindController_updateUIState(state, previous) {
+ if (this.onUpdateState) {
+ this.onUpdateState(state, previous, this.matchCount);
+ }
+ }
+ };
+ return PDFFindController;
+ }();
+ exports.FindStates = FindStates;
+ exports.PDFFindController = PDFFindController;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFPresentationMode = {}, root.pdfjsWebUIUtils);
+ }(this, function (exports, uiUtils) {
+ var normalizeWheelEventDelta = uiUtils.normalizeWheelEventDelta;
+ var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1500;
+ // in ms
+ var DELAY_BEFORE_HIDING_CONTROLS = 3000;
+ // in ms
+ var ACTIVE_SELECTOR = 'pdfPresentationMode';
+ var CONTROLS_SELECTOR = 'pdfPresentationModeControls';
+ /**
+ * @typedef {Object} PDFPresentationModeOptions
+ * @property {HTMLDivElement} container - The container for the viewer element.
+ * @property {HTMLDivElement} viewer - (optional) The viewer element.
+ * @property {PDFViewer} pdfViewer - The document viewer.
+ * @property {EventBus} eventBus - The application event bus.
+ * @property {Array} contextMenuItems - (optional) The menuitems that are added
+ * to the context menu in Presentation Mode.
+ */
+ /**
+ * @class
+ */
+ var PDFPresentationMode = function PDFPresentationModeClosure() {
+ /**
+ * @constructs PDFPresentationMode
+ * @param {PDFPresentationModeOptions} options
+ */
+ function PDFPresentationMode(options) {
+ this.container = options.container;
+ this.viewer = options.viewer || options.container.firstElementChild;
+ this.pdfViewer = options.pdfViewer;
+ this.eventBus = options.eventBus;
+ var contextMenuItems = options.contextMenuItems || null;
+ this.active = false;
+ this.args = null;
+ this.contextMenuOpen = false;
+ this.mouseScrollTimeStamp = 0;
+ this.mouseScrollDelta = 0;
+ this.touchSwipeState = null;
+ if (contextMenuItems) {
+ contextMenuItems.contextFirstPage.addEventListener('click', function PDFPresentationMode_contextFirstPageClick(e) {
+ this.contextMenuOpen = false;
+ this.eventBus.dispatch('firstpage');
+ }.bind(this));
+ contextMenuItems.contextLastPage.addEventListener('click', function PDFPresentationMode_contextLastPageClick(e) {
+ this.contextMenuOpen = false;
+ this.eventBus.dispatch('lastpage');
+ }.bind(this));
+ contextMenuItems.contextPageRotateCw.addEventListener('click', function PDFPresentationMode_contextPageRotateCwClick(e) {
+ this.contextMenuOpen = false;
+ this.eventBus.dispatch('rotatecw');
+ }.bind(this));
+ contextMenuItems.contextPageRotateCcw.addEventListener('click', function PDFPresentationMode_contextPageRotateCcwClick(e) {
+ this.contextMenuOpen = false;
+ this.eventBus.dispatch('rotateccw');
+ }.bind(this));
+ }
+ }
+ PDFPresentationMode.prototype = {
+ /**
+ * Request the browser to enter fullscreen mode.
+ * @returns {boolean} Indicating if the request was successful.
+ */
+ request: function PDFPresentationMode_request() {
+ if (this.switchInProgress || this.active || !this.viewer.hasChildNodes()) {
+ return false;
+ }
+ this._addFullscreenChangeListeners();
+ this._setSwitchInProgress();
+ this._notifyStateChange();
+ if (this.container.requestFullscreen) {
+ this.container.requestFullscreen();
+ } else if (this.container.mozRequestFullScreen) {
+ this.container.mozRequestFullScreen();
+ } else if (this.container.webkitRequestFullscreen) {
+ this.container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ } else if (this.container.msRequestFullscreen) {
+ this.container.msRequestFullscreen();
+ } else {
+ return false;
+ }
+ this.args = {
+ page: this.pdfViewer.currentPageNumber,
+ previousScale: this.pdfViewer.currentScaleValue
+ };
+ return true;
+ },
+ /**
+ * @private
+ */
+ _mouseWheel: function PDFPresentationMode_mouseWheel(evt) {
+ if (!this.active) {
+ return;
+ }
+ evt.preventDefault();
+ var delta = normalizeWheelEventDelta(evt);
+ var MOUSE_SCROLL_COOLDOWN_TIME = 50;
+ var PAGE_SWITCH_THRESHOLD = 0.1;
+ var currentTime = new Date().getTime();
+ var storedTime = this.mouseScrollTimeStamp;
+ // If we've already switched page, avoid accidentally switching again.
+ if (currentTime > storedTime && currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
+ return;
+ }
+ // If the scroll direction changed, reset the accumulated scroll delta.
+ if (this.mouseScrollDelta > 0 && delta < 0 || this.mouseScrollDelta < 0 && delta > 0) {
+ this._resetMouseScrollState();
+ }
+ this.mouseScrollDelta += delta;
+ if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) {
+ var totalDelta = this.mouseScrollDelta;
+ this._resetMouseScrollState();
+ var success = totalDelta > 0 ? this._goToPreviousPage() : this._goToNextPage();
+ if (success) {
+ this.mouseScrollTimeStamp = currentTime;
+ }
+ }
+ },
+ get isFullscreen() {
+ return !!(document.fullscreenElement || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement);
+ },
+ /**
+ * @private
+ */
+ _goToPreviousPage: function PDFPresentationMode_goToPreviousPage() {
+ var page = this.pdfViewer.currentPageNumber;
+ // If we're at the first page, we don't need to do anything.
+ if (page <= 1) {
+ return false;
+ }
+ this.pdfViewer.currentPageNumber = page - 1;
+ return true;
+ },
+ /**
+ * @private
+ */
+ _goToNextPage: function PDFPresentationMode_goToNextPage() {
+ var page = this.pdfViewer.currentPageNumber;
+ // If we're at the last page, we don't need to do anything.
+ if (page >= this.pdfViewer.pagesCount) {
+ return false;
+ }
+ this.pdfViewer.currentPageNumber = page + 1;
+ return true;
+ },
+ /**
+ * @private
+ */
+ _notifyStateChange: function PDFPresentationMode_notifyStateChange() {
+ this.eventBus.dispatch('presentationmodechanged', {
+ source: this,
+ active: this.active,
+ switchInProgress: !!this.switchInProgress
+ });
+ },
+ /**
+ * Used to initialize a timeout when requesting Presentation Mode,
+ * i.e. when the browser is requested to enter fullscreen mode.
+ * This timeout is used to prevent the current page from being scrolled
+ * partially, or completely, out of view when entering Presentation Mode.
+ * NOTE: This issue seems limited to certain zoom levels (e.g. page-width).
+ * @private
+ */
+ _setSwitchInProgress: function PDFPresentationMode_setSwitchInProgress() {
+ if (this.switchInProgress) {
+ clearTimeout(this.switchInProgress);
+ }
+ this.switchInProgress = setTimeout(function switchInProgressTimeout() {
+ this._removeFullscreenChangeListeners();
+ delete this.switchInProgress;
+ this._notifyStateChange();
+ }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
+ },
+ /**
+ * @private
+ */
+ _resetSwitchInProgress: function PDFPresentationMode_resetSwitchInProgress() {
+ if (this.switchInProgress) {
+ clearTimeout(this.switchInProgress);
+ delete this.switchInProgress;
+ }
+ },
+ /**
+ * @private
+ */
+ _enter: function PDFPresentationMode_enter() {
+ this.active = true;
+ this._resetSwitchInProgress();
+ this._notifyStateChange();
+ this.container.classList.add(ACTIVE_SELECTOR);
+ // Ensure that the correct page is scrolled into view when entering
+ // Presentation Mode, by waiting until fullscreen mode in enabled.
+ setTimeout(function enterPresentationModeTimeout() {
+ this.pdfViewer.currentPageNumber = this.args.page;
+ this.pdfViewer.currentScaleValue = 'page-fit';
+ }.bind(this), 0);
+ this._addWindowListeners();
+ this._showControls();
+ this.contextMenuOpen = false;
+ this.container.setAttribute('contextmenu', 'viewerContextMenu');
+ // Text selection is disabled in Presentation Mode, thus it's not possible
+ // for the user to deselect text that is selected (e.g. with "Select all")
+ // when entering Presentation Mode, hence we remove any active selection.
+ window.getSelection().removeAllRanges();
+ },
+ /**
+ * @private
+ */
+ _exit: function PDFPresentationMode_exit() {
+ var page = this.pdfViewer.currentPageNumber;
+ this.container.classList.remove(ACTIVE_SELECTOR);
+ // Ensure that the correct page is scrolled into view when exiting
+ // Presentation Mode, by waiting until fullscreen mode is disabled.
+ setTimeout(function exitPresentationModeTimeout() {
+ this.active = false;
+ this._removeFullscreenChangeListeners();
+ this._notifyStateChange();
+ this.pdfViewer.currentScaleValue = this.args.previousScale;
+ this.pdfViewer.currentPageNumber = page;
+ this.args = null;
+ }.bind(this), 0);
+ this._removeWindowListeners();
+ this._hideControls();
+ this._resetMouseScrollState();
+ this.container.removeAttribute('contextmenu');
+ this.contextMenuOpen = false;
+ },
+ /**
+ * @private
+ */
+ _mouseDown: function PDFPresentationMode_mouseDown(evt) {
+ if (this.contextMenuOpen) {
+ this.contextMenuOpen = false;
+ evt.preventDefault();
+ return;
+ }
+ if (evt.button === 0) {
+ // Enable clicking of links in presentation mode. Please note:
+ // Only links pointing to destinations in the current PDF document work.
+ var isInternalLink = evt.target.href && evt.target.classList.contains('internalLink');
+ if (!isInternalLink) {
+ // Unless an internal link was clicked, advance one page.
+ evt.preventDefault();
+ this.pdfViewer.currentPageNumber += evt.shiftKey ? -1 : 1;
+ }
+ }
+ },
+ /**
+ * @private
+ */
+ _contextMenu: function PDFPresentationMode_contextMenu() {
+ this.contextMenuOpen = true;
+ },
+ /**
+ * @private
+ */
+ _showControls: function PDFPresentationMode_showControls() {
+ if (this.controlsTimeout) {
+ clearTimeout(this.controlsTimeout);
+ } else {
+ this.container.classList.add(CONTROLS_SELECTOR);
+ }
+ this.controlsTimeout = setTimeout(function showControlsTimeout() {
+ this.container.classList.remove(CONTROLS_SELECTOR);
+ delete this.controlsTimeout;
+ }.bind(this), DELAY_BEFORE_HIDING_CONTROLS);
+ },
+ /**
+ * @private
+ */
+ _hideControls: function PDFPresentationMode_hideControls() {
+ if (!this.controlsTimeout) {
+ return;
+ }
+ clearTimeout(this.controlsTimeout);
+ this.container.classList.remove(CONTROLS_SELECTOR);
+ delete this.controlsTimeout;
+ },
+ /**
+ * Resets the properties used for tracking mouse scrolling events.
+ * @private
+ */
+ _resetMouseScrollState: function PDFPresentationMode_resetMouseScrollState() {
+ this.mouseScrollTimeStamp = 0;
+ this.mouseScrollDelta = 0;
+ },
+ /**
+ * @private
+ */
+ _touchSwipe: function PDFPresentationMode_touchSwipe(evt) {
+ if (!this.active) {
+ return;
+ }
+ // Must move at least these many CSS pixels for it to count as a swipe
+ var SWIPE_MIN_DISTANCE_THRESHOLD = 50;
+ // The swipe angle is allowed to deviate from the x or y axis by this much
+ // before it is not considered a swipe in that direction any more.
+ var SWIPE_ANGLE_THRESHOLD = Math.PI / 6;
+ if (evt.touches.length > 1) {
+ // Multiple touch points detected, cancel the swipe.
+ this.touchSwipeState = null;
+ return;
+ }
+ switch (evt.type) {
+ case 'touchstart':
+ this.touchSwipeState = {
+ startX: evt.touches[0].pageX,
+ startY: evt.touches[0].pageY,
+ endX: evt.touches[0].pageX,
+ endY: evt.touches[0].pageY
+ };
+ break;
+ case 'touchmove':
+ if (this.touchSwipeState === null) {
+ return;
+ }
+ this.touchSwipeState.endX = evt.touches[0].pageX;
+ this.touchSwipeState.endY = evt.touches[0].pageY;
+ // Do a preventDefault to avoid the swipe from triggering browser
+ // gestures (Chrome in particular has some sort of swipe gesture in
+ // fullscreen mode).
+ evt.preventDefault();
+ break;
+ case 'touchend':
+ if (this.touchSwipeState === null) {
+ return;
+ }
+ var delta = 0;
+ var dx = this.touchSwipeState.endX - this.touchSwipeState.startX;
+ var dy = this.touchSwipeState.endY - this.touchSwipeState.startY;
+ var absAngle = Math.abs(Math.atan2(dy, dx));
+ if (Math.abs(dx) > SWIPE_MIN_DISTANCE_THRESHOLD && (absAngle <= SWIPE_ANGLE_THRESHOLD || absAngle >= Math.PI - SWIPE_ANGLE_THRESHOLD)) {
+ // horizontal swipe
+ delta = dx;
+ } else if (Math.abs(dy) > SWIPE_MIN_DISTANCE_THRESHOLD && Math.abs(absAngle - Math.PI / 2) <= SWIPE_ANGLE_THRESHOLD) {
+ // vertical swipe
+ delta = dy;
+ }
+ if (delta > 0) {
+ this._goToPreviousPage();
+ } else if (delta < 0) {
+ this._goToNextPage();
+ }
+ break;
+ }
+ },
+ /**
+ * @private
+ */
+ _addWindowListeners: function PDFPresentationMode_addWindowListeners() {
+ this.showControlsBind = this._showControls.bind(this);
+ this.mouseDownBind = this._mouseDown.bind(this);
+ this.mouseWheelBind = this._mouseWheel.bind(this);
+ this.resetMouseScrollStateBind = this._resetMouseScrollState.bind(this);
+ this.contextMenuBind = this._contextMenu.bind(this);
+ this.touchSwipeBind = this._touchSwipe.bind(this);
+ window.addEventListener('mousemove', this.showControlsBind);
+ window.addEventListener('mousedown', this.mouseDownBind);
+ window.addEventListener('wheel', this.mouseWheelBind);
+ window.addEventListener('keydown', this.resetMouseScrollStateBind);
+ window.addEventListener('contextmenu', this.contextMenuBind);
+ window.addEventListener('touchstart', this.touchSwipeBind);
+ window.addEventListener('touchmove', this.touchSwipeBind);
+ window.addEventListener('touchend', this.touchSwipeBind);
+ },
+ /**
+ * @private
+ */
+ _removeWindowListeners: function PDFPresentationMode_removeWindowListeners() {
+ window.removeEventListener('mousemove', this.showControlsBind);
+ window.removeEventListener('mousedown', this.mouseDownBind);
+ window.removeEventListener('wheel', this.mouseWheelBind);
+ window.removeEventListener('keydown', this.resetMouseScrollStateBind);
+ window.removeEventListener('contextmenu', this.contextMenuBind);
+ window.removeEventListener('touchstart', this.touchSwipeBind);
+ window.removeEventListener('touchmove', this.touchSwipeBind);
+ window.removeEventListener('touchend', this.touchSwipeBind);
+ delete this.showControlsBind;
+ delete this.mouseDownBind;
+ delete this.mouseWheelBind;
+ delete this.resetMouseScrollStateBind;
+ delete this.contextMenuBind;
+ delete this.touchSwipeBind;
+ },
+ /**
+ * @private
+ */
+ _fullscreenChange: function PDFPresentationMode_fullscreenChange() {
+ if (this.isFullscreen) {
+ this._enter();
+ } else {
+ this._exit();
+ }
+ },
+ /**
+ * @private
+ */
+ _addFullscreenChangeListeners: function PDFPresentationMode_addFullscreenChangeListeners() {
+ this.fullscreenChangeBind = this._fullscreenChange.bind(this);
+ window.addEventListener('fullscreenchange', this.fullscreenChangeBind);
+ window.addEventListener('mozfullscreenchange', this.fullscreenChangeBind);
+ },
+ /**
+ * @private
+ */
+ _removeFullscreenChangeListeners: function PDFPresentationMode_removeFullscreenChangeListeners() {
+ window.removeEventListener('fullscreenchange', this.fullscreenChangeBind);
+ window.removeEventListener('mozfullscreenchange', this.fullscreenChangeBind);
+ delete this.fullscreenChangeBind;
+ }
+ };
+ return PDFPresentationMode;
+ }();
+ exports.PDFPresentationMode = PDFPresentationMode;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFThumbnailView = {}, root.pdfjsWebUIUtils, root.pdfjsWebPDFRenderingQueue);
+ }(this, function (exports, uiUtils, pdfRenderingQueue) {
+ var mozL10n = uiUtils.mozL10n;
+ var getOutputScale = uiUtils.getOutputScale;
+ var RenderingStates = pdfRenderingQueue.RenderingStates;
+ var THUMBNAIL_WIDTH = 98;
+ // px
+ var THUMBNAIL_CANVAS_BORDER_WIDTH = 1;
+ // px
+ /**
+ * @typedef {Object} PDFThumbnailViewOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {number} id - The thumbnail's unique ID (normally its number).
+ * @property {PageViewport} defaultViewport - The page viewport.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @property {boolean} disableCanvasToImageConversion - (optional) Don't convert
+ * the canvas thumbnails to images. This prevents `toDataURL` calls,
+ * but increases the overall memory usage. The default value is false.
+ */
+ /**
+ * @class
+ * @implements {IRenderableView}
+ */
+ var PDFThumbnailView = function PDFThumbnailViewClosure() {
+ function getTempCanvas(width, height) {
+ var tempCanvas = PDFThumbnailView.tempImageCache;
+ if (!tempCanvas) {
+ tempCanvas = document.createElement('canvas');
+ PDFThumbnailView.tempImageCache = tempCanvas;
+ }
+ tempCanvas.width = width;
+ tempCanvas.height = height;
+ tempCanvas.mozOpaque = true;
+ var ctx = tempCanvas.getContext('2d', { alpha: false });
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, width, height);
+ ctx.restore();
+ return tempCanvas;
+ }
+ /**
+ * @constructs PDFThumbnailView
+ * @param {PDFThumbnailViewOptions} options
+ */
+ function PDFThumbnailView(options) {
+ var container = options.container;
+ var id = options.id;
+ var defaultViewport = options.defaultViewport;
+ var linkService = options.linkService;
+ var renderingQueue = options.renderingQueue;
+ var disableCanvasToImageConversion = options.disableCanvasToImageConversion || false;
+ this.id = id;
+ this.renderingId = 'thumbnail' + id;
+ this.pageLabel = null;
+ this.pdfPage = null;
+ this.rotation = 0;
+ this.viewport = defaultViewport;
+ this.pdfPageRotate = defaultViewport.rotation;
+ this.linkService = linkService;
+ this.renderingQueue = renderingQueue;
+ this.renderTask = null;
+ this.renderingState = RenderingStates.INITIAL;
+ this.resume = null;
+ this.disableCanvasToImageConversion = disableCanvasToImageConversion;
+ this.pageWidth = this.viewport.width;
+ this.pageHeight = this.viewport.height;
+ this.pageRatio = this.pageWidth / this.pageHeight;
+ this.canvasWidth = THUMBNAIL_WIDTH;
+ this.canvasHeight = this.canvasWidth / this.pageRatio | 0;
+ this.scale = this.canvasWidth / this.pageWidth;
+ var anchor = document.createElement('a');
+ anchor.href = linkService.getAnchorUrl('#page=' + id);
+ anchor.title = mozL10n.get('thumb_page_title', { page: id }, 'Page {{page}}');
+ anchor.onclick = function stopNavigation() {
+ linkService.page = id;
+ return false;
+ };
+ this.anchor = anchor;
+ var div = document.createElement('div');
+ div.id = 'thumbnailContainer' + id;
+ div.className = 'thumbnail';
+ this.div = div;
+ if (id === 1) {
+ // Highlight the thumbnail of the first page when no page number is
+ // specified (or exists in cache) when the document is loaded.
+ div.classList.add('selected');
+ }
+ var ring = document.createElement('div');
+ ring.className = 'thumbnailSelectionRing';
+ var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
+ ring.style.width = this.canvasWidth + borderAdjustment + 'px';
+ ring.style.height = this.canvasHeight + borderAdjustment + 'px';
+ this.ring = ring;
+ div.appendChild(ring);
+ anchor.appendChild(div);
+ container.appendChild(anchor);
+ }
+ PDFThumbnailView.prototype = {
+ setPdfPage: function PDFThumbnailView_setPdfPage(pdfPage) {
+ this.pdfPage = pdfPage;
+ this.pdfPageRotate = pdfPage.rotate;
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = pdfPage.getViewport(1, totalRotation);
+ this.reset();
+ },
+ reset: function PDFThumbnailView_reset() {
+ this.cancelRendering();
+ this.pageWidth = this.viewport.width;
+ this.pageHeight = this.viewport.height;
+ this.pageRatio = this.pageWidth / this.pageHeight;
+ this.canvasHeight = this.canvasWidth / this.pageRatio | 0;
+ this.scale = this.canvasWidth / this.pageWidth;
+ this.div.removeAttribute('data-loaded');
+ var ring = this.ring;
+ var childNodes = ring.childNodes;
+ for (var i = childNodes.length - 1; i >= 0; i--) {
+ ring.removeChild(childNodes[i]);
+ }
+ var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
+ ring.style.width = this.canvasWidth + borderAdjustment + 'px';
+ ring.style.height = this.canvasHeight + borderAdjustment + 'px';
+ if (this.canvas) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ this.canvas.width = 0;
+ this.canvas.height = 0;
+ delete this.canvas;
+ }
+ if (this.image) {
+ this.image.removeAttribute('src');
+ delete this.image;
+ }
+ },
+ update: function PDFThumbnailView_update(rotation) {
+ if (typeof rotation !== 'undefined') {
+ this.rotation = rotation;
+ }
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = this.viewport.clone({
+ scale: 1,
+ rotation: totalRotation
+ });
+ this.reset();
+ },
+ cancelRendering: function PDFThumbnailView_cancelRendering() {
+ if (this.renderTask) {
+ this.renderTask.cancel();
+ this.renderTask = null;
+ }
+ this.renderingState = RenderingStates.INITIAL;
+ this.resume = null;
+ },
+ /**
+ * @private
+ */
+ _getPageDrawContext: function PDFThumbnailView_getPageDrawContext(noCtxScale) {
+ var canvas = document.createElement('canvas');
+ // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`,
+ // until rendering/image conversion is complete, to avoid display issues.
+ this.canvas = canvas;
+ canvas.mozOpaque = true;
+ var ctx = canvas.getContext('2d', { alpha: false });
+ var outputScale = getOutputScale(ctx);
+ canvas.width = this.canvasWidth * outputScale.sx | 0;
+ canvas.height = this.canvasHeight * outputScale.sy | 0;
+ canvas.style.width = this.canvasWidth + 'px';
+ canvas.style.height = this.canvasHeight + 'px';
+ if (!noCtxScale && outputScale.scaled) {
+ ctx.scale(outputScale.sx, outputScale.sy);
+ }
+ return ctx;
+ },
+ /**
+ * @private
+ */
+ _convertCanvasToImage: function PDFThumbnailView_convertCanvasToImage() {
+ if (!this.canvas) {
+ return;
+ }
+ if (this.renderingState !== RenderingStates.FINISHED) {
+ return;
+ }
+ var id = this.renderingId;
+ var className = 'thumbnailImage';
+ var ariaLabel = mozL10n.get('thumb_page_canvas', { page: this.pageId }, 'Thumbnail of Page {{page}}');
+ if (this.disableCanvasToImageConversion) {
+ this.canvas.id = id;
+ this.canvas.className = className;
+ this.canvas.setAttribute('aria-label', ariaLabel);
+ this.div.setAttribute('data-loaded', true);
+ this.ring.appendChild(this.canvas);
+ return;
+ }
+ var image = document.createElement('img');
+ image.id = id;
+ image.className = className;
+ image.setAttribute('aria-label', ariaLabel);
+ image.style.width = this.canvasWidth + 'px';
+ image.style.height = this.canvasHeight + 'px';
+ image.src = this.canvas.toDataURL();
+ this.image = image;
+ this.div.setAttribute('data-loaded', true);
+ this.ring.appendChild(image);
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ this.canvas.width = 0;
+ this.canvas.height = 0;
+ delete this.canvas;
+ },
+ draw: function PDFThumbnailView_draw() {
+ if (this.renderingState !== RenderingStates.INITIAL) {
+ console.error('Must be in new state before drawing');
+ return Promise.resolve(undefined);
+ }
+ this.renderingState = RenderingStates.RUNNING;
+ var resolveRenderPromise, rejectRenderPromise;
+ var promise = new Promise(function (resolve, reject) {
+ resolveRenderPromise = resolve;
+ rejectRenderPromise = reject;
+ });
+ var self = this;
+ function thumbnailDrawCallback(error) {
+ // The renderTask may have been replaced by a new one, so only remove
+ // the reference to the renderTask if it matches the one that is
+ // triggering this callback.
+ if (renderTask === self.renderTask) {
+ self.renderTask = null;
+ }
+ if (error === 'cancelled') {
+ rejectRenderPromise(error);
+ return;
+ }
+ self.renderingState = RenderingStates.FINISHED;
+ self._convertCanvasToImage();
+ if (!error) {
+ resolveRenderPromise(undefined);
+ } else {
+ rejectRenderPromise(error);
+ }
+ }
+ var ctx = this._getPageDrawContext();
+ var drawViewport = this.viewport.clone({ scale: this.scale });
+ var renderContinueCallback = function renderContinueCallback(cont) {
+ if (!self.renderingQueue.isHighestPriority(self)) {
+ self.renderingState = RenderingStates.PAUSED;
+ self.resume = function resumeCallback() {
+ self.renderingState = RenderingStates.RUNNING;
+ cont();
+ };
+ return;
+ }
+ cont();
+ };
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: drawViewport
+ };
+ var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+ renderTask.onContinue = renderContinueCallback;
+ renderTask.promise.then(function pdfPageRenderCallback() {
+ thumbnailDrawCallback(null);
+ }, function pdfPageRenderError(error) {
+ thumbnailDrawCallback(error);
+ });
+ return promise;
+ },
+ setImage: function PDFThumbnailView_setImage(pageView) {
+ if (this.renderingState !== RenderingStates.INITIAL) {
+ return;
+ }
+ var img = pageView.canvas;
+ if (!img) {
+ return;
+ }
+ if (!this.pdfPage) {
+ this.setPdfPage(pageView.pdfPage);
+ }
+ this.renderingState = RenderingStates.FINISHED;
+ var ctx = this._getPageDrawContext(true);
+ var canvas = ctx.canvas;
+ if (img.width <= 2 * canvas.width) {
+ ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
+ this._convertCanvasToImage();
+ return;
+ }
+ // drawImage does an awful job of rescaling the image, doing it gradually.
+ var MAX_NUM_SCALING_STEPS = 3;
+ var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
+ var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
+ var reducedImage = getTempCanvas(reducedWidth, reducedHeight);
+ var reducedImageCtx = reducedImage.getContext('2d');
+ while (reducedWidth > img.width || reducedHeight > img.height) {
+ reducedWidth >>= 1;
+ reducedHeight >>= 1;
+ }
+ reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, reducedWidth, reducedHeight);
+ while (reducedWidth > 2 * canvas.width) {
+ reducedImageCtx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, reducedWidth >> 1, reducedHeight >> 1);
+ reducedWidth >>= 1;
+ reducedHeight >>= 1;
+ }
+ ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, canvas.width, canvas.height);
+ this._convertCanvasToImage();
+ },
+ get pageId() {
+ return this.pageLabel !== null ? this.pageLabel : this.id;
+ },
+ /**
+ * @param {string|null} label
+ */
+ setPageLabel: function PDFThumbnailView_setPageLabel(label) {
+ this.pageLabel = typeof label === 'string' ? label : null;
+ this.anchor.title = mozL10n.get('thumb_page_title', { page: this.pageId }, 'Page {{page}}');
+ if (this.renderingState !== RenderingStates.FINISHED) {
+ return;
+ }
+ var ariaLabel = mozL10n.get('thumb_page_canvas', { page: this.pageId }, 'Thumbnail of Page {{page}}');
+ if (this.image) {
+ this.image.setAttribute('aria-label', ariaLabel);
+ } else if (this.disableCanvasToImageConversion && this.canvas) {
+ this.canvas.setAttribute('aria-label', ariaLabel);
+ }
+ }
+ };
+ return PDFThumbnailView;
+ }();
+ PDFThumbnailView.tempImageCache = null;
+ exports.PDFThumbnailView = PDFThumbnailView;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebSecondaryToolbar = {}, root.pdfjsWebUIUtils);
+ }(this, function (exports, uiUtils) {
+ var SCROLLBAR_PADDING = uiUtils.SCROLLBAR_PADDING;
+ var mozL10n = uiUtils.mozL10n;
+ /**
+ * @typedef {Object} SecondaryToolbarOptions
+ * @property {HTMLDivElement} toolbar - Container for the secondary toolbar.
+ * @property {HTMLButtonElement} toggleButton - Button to toggle the visibility
+ * of the secondary toolbar.
+ * @property {HTMLDivElement} toolbarButtonContainer - Container where all the
+ * toolbar buttons are placed. The maximum height of the toolbar is controlled
+ * dynamically by adjusting the 'max-height' CSS property of this DOM element.
+ * @property {HTMLButtonElement} presentationModeButton - Button for entering
+ * presentation mode.
+ * @property {HTMLButtonElement} openFileButton - Button to open a file.
+ * @property {HTMLButtonElement} printButton - Button to print the document.
+ * @property {HTMLButtonElement} downloadButton - Button to download the
+ * document.
+ * @property {HTMLLinkElement} viewBookmarkButton - Button to obtain a bookmark
+ * link to the current location in the document.
+ * @property {HTMLButtonElement} firstPageButton - Button to go to the first
+ * page in the document.
+ * @property {HTMLButtonElement} lastPageButton - Button to go to the last page
+ * in the document.
+ * @property {HTMLButtonElement} pageRotateCwButton - Button to rotate the pages
+ * clockwise.
+ * @property {HTMLButtonElement} pageRotateCcwButton - Button to rotate the
+ * pages counterclockwise.
+ * @property {HTMLButtonElement} toggleHandToolButton - Button to toggle the
+ * hand tool.
+ * @property {HTMLButtonElement} documentPropertiesButton - Button for opening
+ * the document properties dialog.
+ */
+ /**
+ * @class
+ */
+ var SecondaryToolbar = function SecondaryToolbarClosure() {
+ /**
+ * @constructs SecondaryToolbar
+ * @param {SecondaryToolbarOptions} options
+ * @param {HTMLDivElement} mainContainer
+ * @param {EventBus} eventBus
+ */
+ function SecondaryToolbar(options, mainContainer, eventBus) {
+ this.toolbar = options.toolbar;
+ this.toggleButton = options.toggleButton;
+ this.toolbarButtonContainer = options.toolbarButtonContainer;
+ this.buttons = [
+ {
+ element: options.presentationModeButton,
+ eventName: 'presentationmode',
+ close: true
+ },
+ {
+ element: options.openFileButton,
+ eventName: 'openfile',
+ close: true
+ },
+ {
+ element: options.printButton,
+ eventName: 'print',
+ close: true
+ },
+ {
+ element: options.downloadButton,
+ eventName: 'download',
+ close: true
+ },
+ {
+ element: options.viewBookmarkButton,
+ eventName: null,
+ close: true
+ },
+ {
+ element: options.firstPageButton,
+ eventName: 'firstpage',
+ close: true
+ },
+ {
+ element: options.lastPageButton,
+ eventName: 'lastpage',
+ close: true
+ },
+ {
+ element: options.pageRotateCwButton,
+ eventName: 'rotatecw',
+ close: false
+ },
+ {
+ element: options.pageRotateCcwButton,
+ eventName: 'rotateccw',
+ close: false
+ },
+ {
+ element: options.toggleHandToolButton,
+ eventName: 'togglehandtool',
+ close: true
+ },
+ {
+ element: options.documentPropertiesButton,
+ eventName: 'documentproperties',
+ close: true
+ }
+ ];
+ this.mainContainer = mainContainer;
+ this.eventBus = eventBus;
+ this.opened = false;
+ this.containerHeight = null;
+ this.previousContainerHeight = null;
+ // Bind the event listeners for click and hand tool actions.
+ this._bindClickListeners();
+ this._bindHandToolListener(options.toggleHandToolButton);
+ // Bind the event listener for adjusting the 'max-height' of the toolbar.
+ this.eventBus.on('resize', this._setMaxHeight.bind(this));
+ }
+ SecondaryToolbar.prototype = {
+ /**
+ * @return {boolean}
+ */
+ get isOpen() {
+ return this.opened;
+ },
+ _bindClickListeners: function SecondaryToolbar_bindClickListeners() {
+ // Button to toggle the visibility of the secondary toolbar.
+ this.toggleButton.addEventListener('click', this.toggle.bind(this));
+ // All items within the secondary toolbar.
+ for (var button in this.buttons) {
+ var element = this.buttons[button].element;
+ var eventName = this.buttons[button].eventName;
+ var close = this.buttons[button].close;
+ element.addEventListener('click', function (eventName, close) {
+ if (eventName !== null) {
+ this.eventBus.dispatch(eventName, { source: this });
+ }
+ if (close) {
+ this.close();
+ }
+ }.bind(this, eventName, close));
+ }
+ },
+ _bindHandToolListener: function SecondaryToolbar_bindHandToolListener(toggleHandToolButton) {
+ var isHandToolActive = false;
+ this.eventBus.on('handtoolchanged', function (e) {
+ if (isHandToolActive === e.isActive) {
+ return;
+ }
+ isHandToolActive = e.isActive;
+ if (isHandToolActive) {
+ toggleHandToolButton.title = mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool');
+ toggleHandToolButton.firstElementChild.textContent = mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool');
+ } else {
+ toggleHandToolButton.title = mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool');
+ toggleHandToolButton.firstElementChild.textContent = mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool');
+ }
+ }.bind(this));
+ },
+ open: function SecondaryToolbar_open() {
+ if (this.opened) {
+ return;
+ }
+ this.opened = true;
+ this._setMaxHeight();
+ this.toggleButton.classList.add('toggled');
+ this.toolbar.classList.remove('hidden');
+ },
+ close: function SecondaryToolbar_close() {
+ if (!this.opened) {
+ return;
+ }
+ this.opened = false;
+ this.toolbar.classList.add('hidden');
+ this.toggleButton.classList.remove('toggled');
+ },
+ toggle: function SecondaryToolbar_toggle() {
+ if (this.opened) {
+ this.close();
+ } else {
+ this.open();
+ }
+ },
+ /**
+ * @private
+ */
+ _setMaxHeight: function SecondaryToolbar_setMaxHeight() {
+ if (!this.opened) {
+ return;
+ }
+ // Only adjust the 'max-height' if the toolbar is visible.
+ this.containerHeight = this.mainContainer.clientHeight;
+ if (this.containerHeight === this.previousContainerHeight) {
+ return;
+ }
+ this.toolbarButtonContainer.setAttribute('style', 'max-height: ' + (this.containerHeight - SCROLLBAR_PADDING) + 'px;');
+ this.previousContainerHeight = this.containerHeight;
+ }
+ };
+ return SecondaryToolbar;
+ }();
+ exports.SecondaryToolbar = SecondaryToolbar;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFFindBar = {}, root.pdfjsWebUIUtils, root.pdfjsWebPDFFindController);
+ }(this, function (exports, uiUtils, pdfFindController) {
+ var mozL10n = uiUtils.mozL10n;
+ var FindStates = pdfFindController.FindStates;
+ /**
+ * Creates a "search bar" given a set of DOM elements that act as controls
+ * for searching or for setting search preferences in the UI. This object
+ * also sets up the appropriate events for the controls. Actual searching
+ * is done by PDFFindController.
+ */
+ var PDFFindBar = function PDFFindBarClosure() {
+ function PDFFindBar(options) {
+ this.opened = false;
+ this.bar = options.bar || null;
+ this.toggleButton = options.toggleButton || null;
+ this.findField = options.findField || null;
+ this.highlightAll = options.highlightAllCheckbox || null;
+ this.caseSensitive = options.caseSensitiveCheckbox || null;
+ this.findMsg = options.findMsg || null;
+ this.findResultsCount = options.findResultsCount || null;
+ this.findStatusIcon = options.findStatusIcon || null;
+ this.findPreviousButton = options.findPreviousButton || null;
+ this.findNextButton = options.findNextButton || null;
+ this.findController = options.findController || null;
+ this.eventBus = options.eventBus;
+ if (this.findController === null) {
+ throw new Error('PDFFindBar cannot be used without a ' + 'PDFFindController instance.');
+ }
+ // Add event listeners to the DOM elements.
+ var self = this;
+ this.toggleButton.addEventListener('click', function () {
+ self.toggle();
+ });
+ this.findField.addEventListener('input', function () {
+ self.dispatchEvent('');
+ });
+ this.bar.addEventListener('keydown', function (evt) {
+ switch (evt.keyCode) {
+ case 13:
+ // Enter
+ if (evt.target === self.findField) {
+ self.dispatchEvent('again', evt.shiftKey);
+ }
+ break;
+ case 27:
+ // Escape
+ self.close();
+ break;
+ }
+ });
+ this.findPreviousButton.addEventListener('click', function () {
+ self.dispatchEvent('again', true);
+ });
+ this.findNextButton.addEventListener('click', function () {
+ self.dispatchEvent('again', false);
+ });
+ this.highlightAll.addEventListener('click', function () {
+ self.dispatchEvent('highlightallchange');
+ });
+ this.caseSensitive.addEventListener('click', function () {
+ self.dispatchEvent('casesensitivitychange');
+ });
+ }
+ PDFFindBar.prototype = {
+ reset: function PDFFindBar_reset() {
+ this.updateUIState();
+ },
+ dispatchEvent: function PDFFindBar_dispatchEvent(type, findPrev) {
+ this.eventBus.dispatch('find', {
+ source: this,
+ type: type,
+ query: this.findField.value,
+ caseSensitive: this.caseSensitive.checked,
+ phraseSearch: true,
+ highlightAll: this.highlightAll.checked,
+ findPrevious: findPrev
+ });
+ },
+ updateUIState: function PDFFindBar_updateUIState(state, previous, matchCount) {
+ var notFound = false;
+ var findMsg = '';
+ var status = '';
+ switch (state) {
+ case FindStates.FIND_FOUND:
+ break;
+ case FindStates.FIND_PENDING:
+ status = 'pending';
+ break;
+ case FindStates.FIND_NOTFOUND:
+ findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
+ notFound = true;
+ break;
+ case FindStates.FIND_WRAPPED:
+ if (previous) {
+ findMsg = mozL10n.get('find_reached_top', null, 'Reached top of document, continued from bottom');
+ } else {
+ findMsg = mozL10n.get('find_reached_bottom', null, 'Reached end of document, continued from top');
+ }
+ break;
+ }
+ if (notFound) {
+ this.findField.classList.add('notFound');
+ } else {
+ this.findField.classList.remove('notFound');
+ }
+ this.findField.setAttribute('data-status', status);
+ this.findMsg.textContent = findMsg;
+ this.updateResultsCount(matchCount);
+ },
+ updateResultsCount: function (matchCount) {
+ if (!this.findResultsCount) {
+ return;
+ }
+ // no UI control is provided
+ // If there are no matches, hide the counter
+ if (!matchCount) {
+ this.findResultsCount.classList.add('hidden');
+ return;
+ }
+ // Create the match counter
+ this.findResultsCount.textContent = matchCount.toLocaleString();
+ // Show the counter
+ this.findResultsCount.classList.remove('hidden');
+ },
+ open: function PDFFindBar_open() {
+ if (!this.opened) {
+ this.opened = true;
+ this.toggleButton.classList.add('toggled');
+ this.bar.classList.remove('hidden');
+ }
+ this.findField.select();
+ this.findField.focus();
+ },
+ close: function PDFFindBar_close() {
+ if (!this.opened) {
+ return;
+ }
+ this.opened = false;
+ this.toggleButton.classList.remove('toggled');
+ this.bar.classList.add('hidden');
+ this.findController.active = false;
+ },
+ toggle: function PDFFindBar_toggle() {
+ if (this.opened) {
+ this.close();
+ } else {
+ this.open();
+ }
+ }
+ };
+ return PDFFindBar;
+ }();
+ exports.PDFFindBar = PDFFindBar;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFHistory = {}, root.pdfjsWebDOMEvents);
+ }(this, function (exports, domEvents) {
+ function PDFHistory(options) {
+ this.linkService = options.linkService;
+ this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
+ this.initialized = false;
+ this.initialDestination = null;
+ this.initialBookmark = null;
+ }
+ PDFHistory.prototype = {
+ /**
+ * @param {string} fingerprint
+ */
+ initialize: function pdfHistoryInitialize(fingerprint) {
+ this.initialized = true;
+ this.reInitialized = false;
+ this.allowHashChange = true;
+ this.historyUnlocked = true;
+ this.isViewerInPresentationMode = false;
+ this.previousHash = window.location.hash.substring(1);
+ this.currentBookmark = '';
+ this.currentPage = 0;
+ this.updatePreviousBookmark = false;
+ this.previousBookmark = '';
+ this.previousPage = 0;
+ this.nextHashParam = '';
+ this.fingerprint = fingerprint;
+ this.currentUid = this.uid = 0;
+ this.current = {};
+ var state = window.history.state;
+ if (this._isStateObjectDefined(state)) {
+ // This corresponds to navigating back to the document
+ // from another page in the browser history.
+ if (state.target.dest) {
+ this.initialDestination = state.target.dest;
+ } else {
+ this.initialBookmark = state.target.hash;
+ }
+ this.currentUid = state.uid;
+ this.uid = state.uid + 1;
+ this.current = state.target;
+ } else {
+ // This corresponds to the loading of a new document.
+ if (state && state.fingerprint && this.fingerprint !== state.fingerprint) {
+ // Reinitialize the browsing history when a new document
+ // is opened in the web viewer.
+ this.reInitialized = true;
+ }
+ this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
+ }
+ var self = this;
+ window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
+ if (!self.historyUnlocked) {
+ return;
+ }
+ if (evt.state) {
+ // Move back/forward in the history.
+ self._goTo(evt.state);
+ return;
+ }
+ // If the state is not set, then the user tried to navigate to a
+ // different hash by manually editing the URL and pressing Enter, or by
+ // clicking on an in-page link (e.g. the "current view" link).
+ // Save the current view state to the browser history.
+ // Note: In Firefox, history.null could also be null after an in-page
+ // navigation to the same URL, and without dispatching the popstate
+ // event: https://bugzilla.mozilla.org/show_bug.cgi?id=1183881
+ if (self.uid === 0) {
+ // Replace the previous state if it was not explicitly set.
+ var previousParams = self.previousHash && self.currentBookmark && self.previousHash !== self.currentBookmark ? {
+ hash: self.currentBookmark,
+ page: self.currentPage
+ } : { page: 1 };
+ replacePreviousHistoryState(previousParams, function () {
+ updateHistoryWithCurrentHash();
+ });
+ } else {
+ updateHistoryWithCurrentHash();
+ }
+ }, false);
+ function updateHistoryWithCurrentHash() {
+ self.previousHash = window.location.hash.slice(1);
+ self._pushToHistory({ hash: self.previousHash }, false, true);
+ self._updatePreviousBookmark();
+ }
+ function replacePreviousHistoryState(params, callback) {
+ // To modify the previous history entry, the following happens:
+ // 1. history.back()
+ // 2. _pushToHistory, which calls history.replaceState( ... )
+ // 3. history.forward()
+ // Because a navigation via the history API does not immediately update
+ // the history state, the popstate event is used for synchronization.
+ self.historyUnlocked = false;
+ // Suppress the hashchange event to avoid side effects caused by
+ // navigating back and forward.
+ self.allowHashChange = false;
+ window.addEventListener('popstate', rewriteHistoryAfterBack);
+ history.back();
+ function rewriteHistoryAfterBack() {
+ window.removeEventListener('popstate', rewriteHistoryAfterBack);
+ window.addEventListener('popstate', rewriteHistoryAfterForward);
+ self._pushToHistory(params, false, true);
+ history.forward();
+ }
+ function rewriteHistoryAfterForward() {
+ window.removeEventListener('popstate', rewriteHistoryAfterForward);
+ self.allowHashChange = true;
+ self.historyUnlocked = true;
+ callback();
+ }
+ }
+ function pdfHistoryBeforeUnload() {
+ var previousParams = self._getPreviousParams(null, true);
+ if (previousParams) {
+ var replacePrevious = !self.current.dest && self.current.hash !== self.previousHash;
+ self._pushToHistory(previousParams, false, replacePrevious);
+ self._updatePreviousBookmark();
+ }
+ // Remove the event listener when navigating away from the document,
+ // since 'beforeunload' prevents Firefox from caching the document.
+ window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+ }
+ window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+ window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
+ // If the entire viewer (including the PDF file) is cached in
+ // the browser, we need to reattach the 'beforeunload' event listener
+ // since the 'DOMContentLoaded' event is not fired on 'pageshow'.
+ window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+ }, false);
+ self.eventBus.on('presentationmodechanged', function (e) {
+ self.isViewerInPresentationMode = e.active;
+ });
+ },
+ clearHistoryState: function pdfHistory_clearHistoryState() {
+ this._pushOrReplaceState(null, true);
+ },
+ _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
+ return state && state.uid >= 0 && state.fingerprint && this.fingerprint === state.fingerprint && state.target && state.target.hash ? true : false;
+ },
+ _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj, replace) {
+ if (replace) {
+ window.history.replaceState(stateObj, '');
+ } else {
+ window.history.pushState(stateObj, '');
+ }
+ },
+ get isHashChangeUnlocked() {
+ if (!this.initialized) {
+ return true;
+ }
+ return this.allowHashChange;
+ },
+ _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
+ if (this.updatePreviousBookmark && this.currentBookmark && this.currentPage) {
+ this.previousBookmark = this.currentBookmark;
+ this.previousPage = this.currentPage;
+ this.updatePreviousBookmark = false;
+ }
+ },
+ updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark, pageNum) {
+ if (this.initialized) {
+ this.currentBookmark = bookmark.substring(1);
+ this.currentPage = pageNum | 0;
+ this._updatePreviousBookmark();
+ }
+ },
+ updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
+ if (this.initialized) {
+ this.nextHashParam = param;
+ }
+ },
+ push: function pdfHistoryPush(params, isInitialBookmark) {
+ if (!(this.initialized && this.historyUnlocked)) {
+ return;
+ }
+ if (params.dest && !params.hash) {
+ params.hash = this.current.hash && this.current.dest && this.current.dest === params.dest ? this.current.hash : this.linkService.getDestinationHash(params.dest).split('#')[1];
+ }
+ if (params.page) {
+ params.page |= 0;
+ }
+ if (isInitialBookmark) {
+ var target = window.history.state.target;
+ if (!target) {
+ // Invoked when the user specifies an initial bookmark,
+ // thus setting initialBookmark, when the document is loaded.
+ this._pushToHistory(params, false);
+ this.previousHash = window.location.hash.substring(1);
+ }
+ this.updatePreviousBookmark = this.nextHashParam ? false : true;
+ if (target) {
+ // If the current document is reloaded,
+ // avoid creating duplicate entries in the history.
+ this._updatePreviousBookmark();
+ }
+ return;
+ }
+ if (this.nextHashParam) {
+ if (this.nextHashParam === params.hash) {
+ this.nextHashParam = null;
+ this.updatePreviousBookmark = true;
+ return;
+ } else {
+ this.nextHashParam = null;
+ }
+ }
+ if (params.hash) {
+ if (this.current.hash) {
+ if (this.current.hash !== params.hash) {
+ this._pushToHistory(params, true);
+ } else {
+ if (!this.current.page && params.page) {
+ this._pushToHistory(params, false, true);
+ }
+ this.updatePreviousBookmark = true;
+ }
+ } else {
+ this._pushToHistory(params, true);
+ }
+ } else if (this.current.page && params.page && this.current.page !== params.page) {
+ this._pushToHistory(params, true);
+ }
+ },
+ _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage, beforeUnload) {
+ if (!(this.currentBookmark && this.currentPage)) {
+ return null;
+ } else if (this.updatePreviousBookmark) {
+ this.updatePreviousBookmark = false;
+ }
+ if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
+ // Prevent the history from getting stuck in the current state,
+ // effectively preventing the user from going back/forward in
+ // the history.
+ //
+ // This happens if the current position in the document didn't change
+ // when the history was previously updated. The reasons for this are
+ // either:
+ // 1. The current zoom value is such that the document does not need to,
+ // or cannot, be scrolled to display the destination.
+ // 2. The previous destination is broken, and doesn't actally point to a
+ // position within the document.
+ // (This is either due to a bad PDF generator, or the user making a
+ // mistake when entering a destination in the hash parameters.)
+ return null;
+ }
+ if (!this.current.dest && !onlyCheckPage || beforeUnload) {
+ if (this.previousBookmark === this.currentBookmark) {
+ return null;
+ }
+ } else if (this.current.page || onlyCheckPage) {
+ if (this.previousPage === this.currentPage) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ var params = {
+ hash: this.currentBookmark,
+ page: this.currentPage
+ };
+ if (this.isViewerInPresentationMode) {
+ params.hash = null;
+ }
+ return params;
+ },
+ _stateObj: function pdfHistory_stateObj(params) {
+ return {
+ fingerprint: this.fingerprint,
+ uid: this.uid,
+ target: params
+ };
+ },
+ _pushToHistory: function pdfHistory_pushToHistory(params, addPrevious, overwrite) {
+ if (!this.initialized) {
+ return;
+ }
+ if (!params.hash && params.page) {
+ params.hash = 'page=' + params.page;
+ }
+ if (addPrevious && !overwrite) {
+ var previousParams = this._getPreviousParams();
+ if (previousParams) {
+ var replacePrevious = !this.current.dest && this.current.hash !== this.previousHash;
+ this._pushToHistory(previousParams, false, replacePrevious);
+ }
+ }
+ this._pushOrReplaceState(this._stateObj(params), overwrite || this.uid === 0);
+ this.currentUid = this.uid++;
+ this.current = params;
+ this.updatePreviousBookmark = true;
+ },
+ _goTo: function pdfHistory_goTo(state) {
+ if (!(this.initialized && this.historyUnlocked && this._isStateObjectDefined(state))) {
+ return;
+ }
+ if (!this.reInitialized && state.uid < this.currentUid) {
+ var previousParams = this._getPreviousParams(true);
+ if (previousParams) {
+ this._pushToHistory(this.current, false);
+ this._pushToHistory(previousParams, false);
+ this.currentUid = state.uid;
+ window.history.back();
+ return;
+ }
+ }
+ this.historyUnlocked = false;
+ if (state.target.dest) {
+ this.linkService.navigateTo(state.target.dest);
+ } else {
+ this.linkService.setHash(state.target.hash);
+ }
+ this.currentUid = state.uid;
+ if (state.uid > this.uid) {
+ this.uid = state.uid;
+ }
+ this.current = state.target;
+ this.updatePreviousBookmark = true;
+ var currentHash = window.location.hash.substring(1);
+ if (this.previousHash !== currentHash) {
+ this.allowHashChange = false;
+ }
+ this.previousHash = currentHash;
+ this.historyUnlocked = true;
+ },
+ back: function pdfHistoryBack() {
+ this.go(-1);
+ },
+ forward: function pdfHistoryForward() {
+ this.go(1);
+ },
+ go: function pdfHistoryGo(direction) {
+ if (this.initialized && this.historyUnlocked) {
+ var state = window.history.state;
+ if (direction === -1 && state && state.uid > 0) {
+ window.history.back();
+ } else if (direction === 1 && state && state.uid < this.uid - 1) {
+ window.history.forward();
+ }
+ }
+ }
+ };
+ exports.PDFHistory = PDFHistory;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFLinkService = {}, root.pdfjsWebUIUtils, root.pdfjsWebDOMEvents);
+ }(this, function (exports, uiUtils, domEvents) {
+ var parseQueryString = uiUtils.parseQueryString;
+ var PageNumberRegExp = /^\d+$/;
+ function isPageNumber(str) {
+ return PageNumberRegExp.test(str);
+ }
+ /**
+ * @typedef {Object} PDFLinkServiceOptions
+ * @property {EventBus} eventBus - The application event bus.
+ */
+ /**
+ * Performs navigation functions inside PDF, such as opening specified page,
+ * or destination.
+ * @class
+ * @implements {IPDFLinkService}
+ */
+ var PDFLinkService = function PDFLinkServiceClosure() {
+ /**
+ * @constructs PDFLinkService
+ * @param {PDFLinkServiceOptions} options
+ */
+ function PDFLinkService(options) {
+ options = options || {};
+ this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
+ this.baseUrl = null;
+ this.pdfDocument = null;
+ this.pdfViewer = null;
+ this.pdfHistory = null;
+ this._pagesRefCache = null;
+ }
+ PDFLinkService.prototype = {
+ setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
+ this.baseUrl = baseUrl;
+ this.pdfDocument = pdfDocument;
+ this._pagesRefCache = Object.create(null);
+ },
+ setViewer: function PDFLinkService_setViewer(pdfViewer) {
+ this.pdfViewer = pdfViewer;
+ },
+ setHistory: function PDFLinkService_setHistory(pdfHistory) {
+ this.pdfHistory = pdfHistory;
+ },
+ /**
+ * @returns {number}
+ */
+ get pagesCount() {
+ return this.pdfDocument ? this.pdfDocument.numPages : 0;
+ },
+ /**
+ * @returns {number}
+ */
+ get page() {
+ return this.pdfViewer.currentPageNumber;
+ },
+ /**
+ * @param {number} value
+ */
+ set page(value) {
+ this.pdfViewer.currentPageNumber = value;
+ },
+ /**
+ * @param dest - The PDF destination object.
+ */
+ navigateTo: function PDFLinkService_navigateTo(dest) {
+ var destString = '';
+ var self = this;
+ var goToDestination = function (destRef) {
+ // dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
+ var pageNumber;
+ if (destRef instanceof Object) {
+ pageNumber = self._cachedPageNumber(destRef);
+ } else if ((destRef | 0) === destRef) {
+ // Integer
+ pageNumber = destRef + 1;
+ } else {
+ console.error('PDFLinkService_navigateTo: "' + destRef + '" is not a valid destination reference.');
+ return;
+ }
+ if (pageNumber) {
+ if (pageNumber < 1 || pageNumber > self.pagesCount) {
+ console.error('PDFLinkService_navigateTo: "' + pageNumber + '" is a non-existent page number.');
+ return;
+ }
+ self.pdfViewer.scrollPageIntoView({
+ pageNumber: pageNumber,
+ destArray: dest
+ });
+ if (self.pdfHistory) {
+ // Update the browsing history.
+ self.pdfHistory.push({
+ dest: dest,
+ hash: destString,
+ page: pageNumber
+ });
+ }
+ } else {
+ self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
+ self.cachePageRef(pageIndex + 1, destRef);
+ goToDestination(destRef);
+ }).catch(function () {
+ console.error('PDFLinkService_navigateTo: "' + destRef + '" is not a valid page reference.');
+ return;
+ });
+ }
+ };
+ var destinationPromise;
+ if (typeof dest === 'string') {
+ destString = dest;
+ destinationPromise = this.pdfDocument.getDestination(dest);
+ } else {
+ destinationPromise = Promise.resolve(dest);
+ }
+ destinationPromise.then(function (destination) {
+ dest = destination;
+ if (!(destination instanceof Array)) {
+ console.error('PDFLinkService_navigateTo: "' + destination + '" is not a valid destination array.');
+ return;
+ }
+ goToDestination(destination[0]);
+ });
+ },
+ /**
+ * @param dest - The PDF destination object.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
+ if (typeof dest === 'string') {
+ // In practice, a named destination may contain only a number.
+ // If that happens, use the '#nameddest=' form to avoid the link
+ // redirecting to a page, instead of the correct destination.
+ return this.getAnchorUrl('#' + (isPageNumber(dest) ? 'nameddest=' : '') + escape(dest));
+ }
+ if (dest instanceof Array) {
+ var str = JSON.stringify(dest);
+ return this.getAnchorUrl('#' + escape(str));
+ }
+ return this.getAnchorUrl('');
+ },
+ /**
+ * Prefix the full url on anchor links to make sure that links are resolved
+ * relative to the current URL instead of the one defined in <base href>.
+ * @param {String} anchor The anchor hash, including the #.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
+ return (this.baseUrl || '') + anchor;
+ },
+ /**
+ * @param {string} hash
+ */
+ setHash: function PDFLinkService_setHash(hash) {
+ var pageNumber, dest;
+ if (hash.indexOf('=') >= 0) {
+ var params = parseQueryString(hash);
+ if ('search' in params) {
+ this.eventBus.dispatch('findfromurlhash', {
+ source: this,
+ query: params['search'].replace(/"/g, ''),
+ phraseSearch: params['phrase'] === 'true'
+ });
+ }
+ // borrowing syntax from "Parameters for Opening PDF Files"
+ if ('nameddest' in params) {
+ if (this.pdfHistory) {
+ this.pdfHistory.updateNextHashParam(params.nameddest);
+ }
+ this.navigateTo(params.nameddest);
+ return;
+ }
+ if ('page' in params) {
+ pageNumber = params.page | 0 || 1;
+ }
+ if ('zoom' in params) {
+ // Build the destination array.
+ var zoomArgs = params.zoom.split(',');
+ // scale,left,top
+ var zoomArg = zoomArgs[0];
+ var zoomArgNumber = parseFloat(zoomArg);
+ if (zoomArg.indexOf('Fit') === -1) {
+ // If the zoomArg is a number, it has to get divided by 100. If it's
+ // a string, it should stay as it is.
+ dest = [
+ null,
+ { name: 'XYZ' },
+ zoomArgs.length > 1 ? zoomArgs[1] | 0 : null,
+ zoomArgs.length > 2 ? zoomArgs[2] | 0 : null,
+ zoomArgNumber ? zoomArgNumber / 100 : zoomArg
+ ];
+ } else {
+ if (zoomArg === 'Fit' || zoomArg === 'FitB') {
+ dest = [
+ null,
+ { name: zoomArg }
+ ];
+ } else if (zoomArg === 'FitH' || zoomArg === 'FitBH' || (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
+ dest = [
+ null,
+ { name: zoomArg },
+ zoomArgs.length > 1 ? zoomArgs[1] | 0 : null
+ ];
+ } else if (zoomArg === 'FitR') {
+ if (zoomArgs.length !== 5) {
+ console.error('PDFLinkService_setHash: ' + 'Not enough parameters for \'FitR\'.');
+ } else {
+ dest = [
+ null,
+ { name: zoomArg },
+ zoomArgs[1] | 0,
+ zoomArgs[2] | 0,
+ zoomArgs[3] | 0,
+ zoomArgs[4] | 0
+ ];
+ }
+ } else {
+ console.error('PDFLinkService_setHash: \'' + zoomArg + '\' is not a valid zoom value.');
+ }
+ }
+ }
+ if (dest) {
+ this.pdfViewer.scrollPageIntoView({
+ pageNumber: pageNumber || this.page,
+ destArray: dest,
+ allowNegativeOffset: true
+ });
+ } else if (pageNumber) {
+ this.page = pageNumber;
+ }
+ // simple page
+ if ('pagemode' in params) {
+ this.eventBus.dispatch('pagemode', {
+ source: this,
+ mode: params.pagemode
+ });
+ }
+ } else {
+ dest = unescape(hash);
+ try {
+ dest = JSON.parse(dest);
+ if (!(dest instanceof Array)) {
+ // Avoid incorrectly rejecting a valid named destination, such as
+ // e.g. "4.3" or "true", because `JSON.parse` converted its type.
+ dest = dest.toString();
+ }
+ } catch (ex) {
+ }
+ if (typeof dest === 'string' || isValidExplicitDestination(dest)) {
+ if (this.pdfHistory) {
+ this.pdfHistory.updateNextHashParam(dest);
+ }
+ this.navigateTo(dest);
+ return;
+ }
+ console.error('PDFLinkService_setHash: \'' + unescape(hash) + '\' is not a valid destination.');
+ }
+ },
+ /**
+ * @param {string} action
+ */
+ executeNamedAction: function PDFLinkService_executeNamedAction(action) {
+ // See PDF reference, table 8.45 - Named action
+ switch (action) {
+ case 'GoBack':
+ if (this.pdfHistory) {
+ this.pdfHistory.back();
+ }
+ break;
+ case 'GoForward':
+ if (this.pdfHistory) {
+ this.pdfHistory.forward();
+ }
+ break;
+ case 'NextPage':
+ if (this.page < this.pagesCount) {
+ this.page++;
+ }
+ break;
+ case 'PrevPage':
+ if (this.page > 1) {
+ this.page--;
+ }
+ break;
+ case 'LastPage':
+ this.page = this.pagesCount;
+ break;
+ case 'FirstPage':
+ this.page = 1;
+ break;
+ default:
+ break;
+ }
+ // No action according to spec
+ this.eventBus.dispatch('namedaction', {
+ source: this,
+ action: action
+ });
+ },
+ /**
+ * @param {number} pageNum - page number.
+ * @param {Object} pageRef - reference to the page.
+ */
+ cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
+ var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+ this._pagesRefCache[refStr] = pageNum;
+ },
+ _cachedPageNumber: function PDFLinkService_cachedPageNumber(pageRef) {
+ var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+ return this._pagesRefCache && this._pagesRefCache[refStr] || null;
+ }
+ };
+ function isValidExplicitDestination(dest) {
+ if (!(dest instanceof Array)) {
+ return false;
+ }
+ var destLength = dest.length, allowNull = true;
+ if (destLength < 2) {
+ return false;
+ }
+ var page = dest[0];
+ if (!(typeof page === 'object' && typeof page.num === 'number' && (page.num | 0) === page.num && typeof page.gen === 'number' && (page.gen | 0) === page.gen) && !(typeof page === 'number' && (page | 0) === page && page >= 0)) {
+ return false;
+ }
+ var zoom = dest[1];
+ if (!(typeof zoom === 'object' && typeof zoom.name === 'string')) {
+ return false;
+ }
+ switch (zoom.name) {
+ case 'XYZ':
+ if (destLength !== 5) {
+ return false;
+ }
+ break;
+ case 'Fit':
+ case 'FitB':
+ return destLength === 2;
+ case 'FitH':
+ case 'FitBH':
+ case 'FitV':
+ case 'FitBV':
+ if (destLength !== 3) {
+ return false;
+ }
+ break;
+ case 'FitR':
+ if (destLength !== 6) {
+ return false;
+ }
+ allowNull = false;
+ break;
+ default:
+ return false;
+ }
+ for (var i = 2; i < destLength; i++) {
+ var param = dest[i];
+ if (!(typeof param === 'number' || allowNull && param === null)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return PDFLinkService;
+ }();
+ var SimpleLinkService = function SimpleLinkServiceClosure() {
+ function SimpleLinkService() {
+ }
+ SimpleLinkService.prototype = {
+ /**
+ * @returns {number}
+ */
+ get page() {
+ return 0;
+ },
+ /**
+ * @param {number} value
+ */
+ set page(value) {
+ },
+ /**
+ * @param dest - The PDF destination object.
+ */
+ navigateTo: function (dest) {
+ },
+ /**
+ * @param dest - The PDF destination object.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getDestinationHash: function (dest) {
+ return '#';
+ },
+ /**
+ * @param hash - The PDF parameters/hash.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getAnchorUrl: function (hash) {
+ return '#';
+ },
+ /**
+ * @param {string} hash
+ */
+ setHash: function (hash) {
+ },
+ /**
+ * @param {string} action
+ */
+ executeNamedAction: function (action) {
+ },
+ /**
+ * @param {number} pageNum - page number.
+ * @param {Object} pageRef - reference to the page.
+ */
+ cachePageRef: function (pageNum, pageRef) {
+ }
+ };
+ return SimpleLinkService;
+ }();
+ exports.PDFLinkService = PDFLinkService;
+ exports.SimpleLinkService = SimpleLinkService;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFPageView = {}, root.pdfjsWebUIUtils, root.pdfjsWebPDFRenderingQueue, root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS);
+ }(this, function (exports, uiUtils, pdfRenderingQueue, domEvents, pdfjsLib) {
+ var CSS_UNITS = uiUtils.CSS_UNITS;
+ var DEFAULT_SCALE = uiUtils.DEFAULT_SCALE;
+ var getOutputScale = uiUtils.getOutputScale;
+ var approximateFraction = uiUtils.approximateFraction;
+ var roundToDivide = uiUtils.roundToDivide;
+ var RenderingStates = pdfRenderingQueue.RenderingStates;
+ var TEXT_LAYER_RENDER_DELAY = 200;
+ // ms
+ /**
+ * @typedef {Object} PDFPageViewOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {EventBus} eventBus - The application event bus.
+ * @property {number} id - The page unique ID (normally its number).
+ * @property {number} scale - The page scale display.
+ * @property {PageViewport} defaultViewport - The page viewport.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @property {IPDFTextLayerFactory} textLayerFactory
+ * @property {IPDFAnnotationLayerFactory} annotationLayerFactory
+ * @property {boolean} enhanceTextSelection - Turns on the text selection
+ * enhancement. The default is `false`.
+ * @property {boolean} renderInteractiveForms - Turns on rendering of
+ * interactive form elements. The default is `false`.
+ */
+ /**
+ * @class
+ * @implements {IRenderableView}
+ */
+ var PDFPageView = function PDFPageViewClosure() {
+ /**
+ * @constructs PDFPageView
+ * @param {PDFPageViewOptions} options
+ */
+ function PDFPageView(options) {
+ var container = options.container;
+ var id = options.id;
+ var scale = options.scale;
+ var defaultViewport = options.defaultViewport;
+ var renderingQueue = options.renderingQueue;
+ var textLayerFactory = options.textLayerFactory;
+ var annotationLayerFactory = options.annotationLayerFactory;
+ var enhanceTextSelection = options.enhanceTextSelection || false;
+ var renderInteractiveForms = options.renderInteractiveForms || false;
+ this.id = id;
+ this.renderingId = 'page' + id;
+ this.pageLabel = null;
+ this.rotation = 0;
+ this.scale = scale || DEFAULT_SCALE;
+ this.viewport = defaultViewport;
+ this.pdfPageRotate = defaultViewport.rotation;
+ this.hasRestrictedScaling = false;
+ this.enhanceTextSelection = enhanceTextSelection;
+ this.renderInteractiveForms = renderInteractiveForms;
+ this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
+ this.renderingQueue = renderingQueue;
+ this.textLayerFactory = textLayerFactory;
+ this.annotationLayerFactory = annotationLayerFactory;
+ this.renderTask = null;
+ this.renderingState = RenderingStates.INITIAL;
+ this.resume = null;
+ this.onBeforeDraw = null;
+ this.onAfterDraw = null;
+ this.textLayer = null;
+ this.zoomLayer = null;
+ this.annotationLayer = null;
+ var div = document.createElement('div');
+ div.id = 'pageContainer' + this.id;
+ div.className = 'page';
+ div.style.width = Math.floor(this.viewport.width) + 'px';
+ div.style.height = Math.floor(this.viewport.height) + 'px';
+ div.setAttribute('data-page-number', this.id);
+ this.div = div;
+ container.appendChild(div);
+ }
+ PDFPageView.prototype = {
+ setPdfPage: function PDFPageView_setPdfPage(pdfPage) {
+ this.pdfPage = pdfPage;
+ this.pdfPageRotate = pdfPage.rotate;
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
+ this.stats = pdfPage.stats;
+ this.reset();
+ },
+ destroy: function PDFPageView_destroy() {
+ this.zoomLayer = null;
+ this.reset();
+ if (this.pdfPage) {
+ this.pdfPage.cleanup();
+ }
+ },
+ reset: function PDFPageView_reset(keepZoomLayer, keepAnnotations) {
+ this.cancelRendering();
+ var div = this.div;
+ div.style.width = Math.floor(this.viewport.width) + 'px';
+ div.style.height = Math.floor(this.viewport.height) + 'px';
+ var childNodes = div.childNodes;
+ var currentZoomLayerNode = keepZoomLayer && this.zoomLayer || null;
+ var currentAnnotationNode = keepAnnotations && this.annotationLayer && this.annotationLayer.div || null;
+ for (var i = childNodes.length - 1; i >= 0; i--) {
+ var node = childNodes[i];
+ if (currentZoomLayerNode === node || currentAnnotationNode === node) {
+ continue;
+ }
+ div.removeChild(node);
+ }
+ div.removeAttribute('data-loaded');
+ if (currentAnnotationNode) {
+ // Hide annotationLayer until all elements are resized
+ // so they are not displayed on the already-resized page
+ this.annotationLayer.hide();
+ } else {
+ this.annotationLayer = null;
+ }
+ if (this.canvas && !currentZoomLayerNode) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ this.canvas.width = 0;
+ this.canvas.height = 0;
+ delete this.canvas;
+ }
+ this.loadingIconDiv = document.createElement('div');
+ this.loadingIconDiv.className = 'loadingIcon';
+ div.appendChild(this.loadingIconDiv);
+ },
+ update: function PDFPageView_update(scale, rotation) {
+ this.scale = scale || this.scale;
+ if (typeof rotation !== 'undefined') {
+ this.rotation = rotation;
+ }
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = this.viewport.clone({
+ scale: this.scale * CSS_UNITS,
+ rotation: totalRotation
+ });
+ var isScalingRestricted = false;
+ if (this.canvas && pdfjsLib.PDFJS.maxCanvasPixels > 0) {
+ var outputScale = this.outputScale;
+ if ((Math.floor(this.viewport.width) * outputScale.sx | 0) * (Math.floor(this.viewport.height) * outputScale.sy | 0) > pdfjsLib.PDFJS.maxCanvasPixels) {
+ isScalingRestricted = true;
+ }
+ }
+ if (this.canvas) {
+ if (pdfjsLib.PDFJS.useOnlyCssZoom || this.hasRestrictedScaling && isScalingRestricted) {
+ this.cssTransform(this.canvas, true);
+ this.eventBus.dispatch('pagerendered', {
+ source: this,
+ pageNumber: this.id,
+ cssTransform: true
+ });
+ return;
+ }
+ if (!this.zoomLayer) {
+ this.zoomLayer = this.canvas.parentNode;
+ this.zoomLayer.style.position = 'absolute';
+ }
+ }
+ if (this.zoomLayer) {
+ this.cssTransform(this.zoomLayer.firstChild);
+ }
+ this.reset(/* keepZoomLayer = */
+ true, /* keepAnnotations = */
+ true);
+ },
+ cancelRendering: function PDFPageView_cancelRendering() {
+ if (this.renderTask) {
+ this.renderTask.cancel();
+ this.renderTask = null;
+ }
+ this.renderingState = RenderingStates.INITIAL;
+ this.resume = null;
+ if (this.textLayer) {
+ this.textLayer.cancel();
+ this.textLayer = null;
+ }
+ },
+ /**
+ * Called when moved in the parent's container.
+ */
+ updatePosition: function PDFPageView_updatePosition() {
+ if (this.textLayer) {
+ this.textLayer.render(TEXT_LAYER_RENDER_DELAY);
+ }
+ },
+ cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) {
+ var CustomStyle = pdfjsLib.CustomStyle;
+ // Scale canvas, canvas wrapper, and page container.
+ var width = this.viewport.width;
+ var height = this.viewport.height;
+ var div = this.div;
+ canvas.style.width = canvas.parentNode.style.width = div.style.width = Math.floor(width) + 'px';
+ canvas.style.height = canvas.parentNode.style.height = div.style.height = Math.floor(height) + 'px';
+ // The canvas may have been originally rotated, rotate relative to that.
+ var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
+ var absRotation = Math.abs(relativeRotation);
+ var scaleX = 1, scaleY = 1;
+ if (absRotation === 90 || absRotation === 270) {
+ // Scale x and y because of the rotation.
+ scaleX = height / width;
+ scaleY = width / height;
+ }
+ var cssTransform = 'rotate(' + relativeRotation + 'deg) ' + 'scale(' + scaleX + ',' + scaleY + ')';
+ CustomStyle.setProp('transform', canvas, cssTransform);
+ if (this.textLayer) {
+ // Rotating the text layer is more complicated since the divs inside the
+ // the text layer are rotated.
+ // TODO: This could probably be simplified by drawing the text layer in
+ // one orientation then rotating overall.
+ var textLayerViewport = this.textLayer.viewport;
+ var textRelativeRotation = this.viewport.rotation - textLayerViewport.rotation;
+ var textAbsRotation = Math.abs(textRelativeRotation);
+ var scale = width / textLayerViewport.width;
+ if (textAbsRotation === 90 || textAbsRotation === 270) {
+ scale = width / textLayerViewport.height;
+ }
+ var textLayerDiv = this.textLayer.textLayerDiv;
+ var transX, transY;
+ switch (textAbsRotation) {
+ case 0:
+ transX = transY = 0;
+ break;
+ case 90:
+ transX = 0;
+ transY = '-' + textLayerDiv.style.height;
+ break;
+ case 180:
+ transX = '-' + textLayerDiv.style.width;
+ transY = '-' + textLayerDiv.style.height;
+ break;
+ case 270:
+ transX = '-' + textLayerDiv.style.width;
+ transY = 0;
+ break;
+ default:
+ console.error('Bad rotation value.');
+ break;
+ }
+ CustomStyle.setProp('transform', textLayerDiv, 'rotate(' + textAbsRotation + 'deg) ' + 'scale(' + scale + ', ' + scale + ') ' + 'translate(' + transX + ', ' + transY + ')');
+ CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
+ }
+ if (redrawAnnotations && this.annotationLayer) {
+ this.annotationLayer.render(this.viewport, 'display');
+ }
+ },
+ get width() {
+ return this.viewport.width;
+ },
+ get height() {
+ return this.viewport.height;
+ },
+ getPagePoint: function PDFPageView_getPagePoint(x, y) {
+ return this.viewport.convertToPdfPoint(x, y);
+ },
+ draw: function PDFPageView_draw() {
+ if (this.renderingState !== RenderingStates.INITIAL) {
+ console.error('Must be in new state before drawing');
+ this.reset();
+ }
+ // Ensure that we reset all state to prevent issues.
+ this.renderingState = RenderingStates.RUNNING;
+ var pdfPage = this.pdfPage;
+ var viewport = this.viewport;
+ var div = this.div;
+ // Wrap the canvas so if it has a css transform for highdpi the overflow
+ // will be hidden in FF.
+ var canvasWrapper = document.createElement('div');
+ canvasWrapper.style.width = div.style.width;
+ canvasWrapper.style.height = div.style.height;
+ canvasWrapper.classList.add('canvasWrapper');
+ var canvas = document.createElement('canvas');
+ canvas.id = 'page' + this.id;
+ // Keep the canvas hidden until the first draw callback, or until drawing
+ // is complete when `!this.renderingQueue`, to prevent black flickering.
+ canvas.setAttribute('hidden', 'hidden');
+ var isCanvasHidden = true;
+ canvasWrapper.appendChild(canvas);
+ if (this.annotationLayer && this.annotationLayer.div) {
+ // annotationLayer needs to stay on top
+ div.insertBefore(canvasWrapper, this.annotationLayer.div);
+ } else {
+ div.appendChild(canvasWrapper);
+ }
+ this.canvas = canvas;
+ canvas.mozOpaque = true;
+ var ctx = canvas.getContext('2d', { alpha: false });
+ var outputScale = getOutputScale(ctx);
+ this.outputScale = outputScale;
+ if (pdfjsLib.PDFJS.useOnlyCssZoom) {
+ var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
+ // Use a scale that will make the canvas be the original intended size
+ // of the page.
+ outputScale.sx *= actualSizeViewport.width / viewport.width;
+ outputScale.sy *= actualSizeViewport.height / viewport.height;
+ outputScale.scaled = true;
+ }
+ if (pdfjsLib.PDFJS.maxCanvasPixels > 0) {
+ var pixelsInViewport = viewport.width * viewport.height;
+ var maxScale = Math.sqrt(pdfjsLib.PDFJS.maxCanvasPixels / pixelsInViewport);
+ if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
+ outputScale.sx = maxScale;
+ outputScale.sy = maxScale;
+ outputScale.scaled = true;
+ this.hasRestrictedScaling = true;
+ } else {
+ this.hasRestrictedScaling = false;
+ }
+ }
+ var sfx = approximateFraction(outputScale.sx);
+ var sfy = approximateFraction(outputScale.sy);
+ canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
+ canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
+ canvas.style.width = roundToDivide(viewport.width, sfx[1]) + 'px';
+ canvas.style.height = roundToDivide(viewport.height, sfy[1]) + 'px';
+ // Add the viewport so it's known what it was originally drawn with.
+ canvas._viewport = viewport;
+ var textLayerDiv = null;
+ var textLayer = null;
+ if (this.textLayerFactory) {
+ textLayerDiv = document.createElement('div');
+ textLayerDiv.className = 'textLayer';
+ textLayerDiv.style.width = canvasWrapper.style.width;
+ textLayerDiv.style.height = canvasWrapper.style.height;
+ if (this.annotationLayer && this.annotationLayer.div) {
+ // annotationLayer needs to stay on top
+ div.insertBefore(textLayerDiv, this.annotationLayer.div);
+ } else {
+ div.appendChild(textLayerDiv);
+ }
+ textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv, this.id - 1, this.viewport, this.enhanceTextSelection);
+ }
+ this.textLayer = textLayer;
+ var resolveRenderPromise, rejectRenderPromise;
+ var promise = new Promise(function (resolve, reject) {
+ resolveRenderPromise = resolve;
+ rejectRenderPromise = reject;
+ });
+ // Rendering area
+ var self = this;
+ function pageViewDrawCallback(error) {
+ // The renderTask may have been replaced by a new one, so only remove
+ // the reference to the renderTask if it matches the one that is
+ // triggering this callback.
+ if (renderTask === self.renderTask) {
+ self.renderTask = null;
+ }
+ if (error === 'cancelled') {
+ rejectRenderPromise(error);
+ return;
+ }
+ self.renderingState = RenderingStates.FINISHED;
+ if (isCanvasHidden) {
+ self.canvas.removeAttribute('hidden');
+ isCanvasHidden = false;
+ }
+ if (self.loadingIconDiv) {
+ div.removeChild(self.loadingIconDiv);
+ delete self.loadingIconDiv;
+ }
+ if (self.zoomLayer) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ var zoomLayerCanvas = self.zoomLayer.firstChild;
+ zoomLayerCanvas.width = 0;
+ zoomLayerCanvas.height = 0;
+ if (div.contains(self.zoomLayer)) {
+ // Prevent "Node was not found" errors if the `zoomLayer` was
+ // already removed. This may occur intermittently if the scale
+ // changes many times in very quick succession.
+ div.removeChild(self.zoomLayer);
+ }
+ self.zoomLayer = null;
+ }
+ self.error = error;
+ self.stats = pdfPage.stats;
+ if (self.onAfterDraw) {
+ self.onAfterDraw();
+ }
+ self.eventBus.dispatch('pagerendered', {
+ source: self,
+ pageNumber: self.id,
+ cssTransform: false
+ });
+ if (!error) {
+ resolveRenderPromise(undefined);
+ } else {
+ rejectRenderPromise(error);
+ }
+ }
+ var renderContinueCallback = null;
+ if (this.renderingQueue) {
+ renderContinueCallback = function renderContinueCallback(cont) {
+ if (!self.renderingQueue.isHighestPriority(self)) {
+ self.renderingState = RenderingStates.PAUSED;
+ self.resume = function resumeCallback() {
+ self.renderingState = RenderingStates.RUNNING;
+ cont();
+ };
+ return;
+ }
+ if (isCanvasHidden) {
+ self.canvas.removeAttribute('hidden');
+ isCanvasHidden = false;
+ }
+ cont();
+ };
+ }
+ var transform = !outputScale.scaled ? null : [
+ outputScale.sx,
+ 0,
+ 0,
+ outputScale.sy,
+ 0,
+ 0
+ ];
+ var renderContext = {
+ canvasContext: ctx,
+ transform: transform,
+ viewport: this.viewport,
+ renderInteractiveForms: this.renderInteractiveForms
+ };
+ // intent: 'default', // === 'display'
+ var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+ renderTask.onContinue = renderContinueCallback;
+ this.renderTask.promise.then(function pdfPageRenderCallback() {
+ pageViewDrawCallback(null);
+ if (textLayer) {
+ self.pdfPage.getTextContent({ normalizeWhitespace: true }).then(function textContentResolved(textContent) {
+ textLayer.setTextContent(textContent);
+ textLayer.render(TEXT_LAYER_RENDER_DELAY);
+ });
+ }
+ }, function pdfPageRenderError(error) {
+ pageViewDrawCallback(error);
+ });
+ if (this.annotationLayerFactory) {
+ if (!this.annotationLayer) {
+ this.annotationLayer = this.annotationLayerFactory.createAnnotationLayerBuilder(div, this.pdfPage, this.renderInteractiveForms);
+ }
+ this.annotationLayer.render(this.viewport, 'display');
+ }
+ div.setAttribute('data-loaded', true);
+ if (self.onBeforeDraw) {
+ self.onBeforeDraw();
+ }
+ return promise;
+ },
+ /**
+ * @param {string|null} label
+ */
+ setPageLabel: function PDFView_setPageLabel(label) {
+ this.pageLabel = typeof label === 'string' ? label : null;
+ if (this.pageLabel !== null) {
+ this.div.setAttribute('data-page-label', this.pageLabel);
+ } else {
+ this.div.removeAttribute('data-page-label');
+ }
+ }
+ };
+ return PDFPageView;
+ }();
+ exports.PDFPageView = PDFPageView;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFThumbnailViewer = {}, root.pdfjsWebUIUtils, root.pdfjsWebPDFThumbnailView);
+ }(this, function (exports, uiUtils, pdfThumbnailView) {
+ var watchScroll = uiUtils.watchScroll;
+ var getVisibleElements = uiUtils.getVisibleElements;
+ var scrollIntoView = uiUtils.scrollIntoView;
+ var PDFThumbnailView = pdfThumbnailView.PDFThumbnailView;
+ var THUMBNAIL_SCROLL_MARGIN = -19;
+ /**
+ * @typedef {Object} PDFThumbnailViewerOptions
+ * @property {HTMLDivElement} container - The container for the thumbnail
+ * elements.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ */
+ /**
+ * Simple viewer control to display thumbnails for pages.
+ * @class
+ * @implements {IRenderableView}
+ */
+ var PDFThumbnailViewer = function PDFThumbnailViewerClosure() {
+ /**
+ * @constructs PDFThumbnailViewer
+ * @param {PDFThumbnailViewerOptions} options
+ */
+ function PDFThumbnailViewer(options) {
+ this.container = options.container;
+ this.renderingQueue = options.renderingQueue;
+ this.linkService = options.linkService;
+ this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
+ this._resetView();
+ }
+ PDFThumbnailViewer.prototype = {
+ /**
+ * @private
+ */
+ _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() {
+ this.renderingQueue.renderHighestPriority();
+ },
+ getThumbnail: function PDFThumbnailViewer_getThumbnail(index) {
+ return this.thumbnails[index];
+ },
+ /**
+ * @private
+ */
+ _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() {
+ return getVisibleElements(this.container, this.thumbnails);
+ },
+ scrollThumbnailIntoView: function PDFThumbnailViewer_scrollThumbnailIntoView(page) {
+ var selected = document.querySelector('.thumbnail.selected');
+ if (selected) {
+ selected.classList.remove('selected');
+ }
+ var thumbnail = document.getElementById('thumbnailContainer' + page);
+ if (thumbnail) {
+ thumbnail.classList.add('selected');
+ }
+ var visibleThumbs = this._getVisibleThumbs();
+ var numVisibleThumbs = visibleThumbs.views.length;
+ // If the thumbnail isn't currently visible, scroll it into view.
+ if (numVisibleThumbs > 0) {
+ var first = visibleThumbs.first.id;
+ // Account for only one thumbnail being visible.
+ var last = numVisibleThumbs > 1 ? visibleThumbs.last.id : first;
+ if (page <= first || page >= last) {
+ scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
+ }
+ }
+ },
+ get pagesRotation() {
+ return this._pagesRotation;
+ },
+ set pagesRotation(rotation) {
+ this._pagesRotation = rotation;
+ for (var i = 0, l = this.thumbnails.length; i < l; i++) {
+ var thumb = this.thumbnails[i];
+ thumb.update(rotation);
+ }
+ },
+ cleanup: function PDFThumbnailViewer_cleanup() {
+ var tempCanvas = PDFThumbnailView.tempImageCache;
+ if (tempCanvas) {
+ // Zeroing the width and height causes Firefox to release graphics
+ // resources immediately, which can greatly reduce memory consumption.
+ tempCanvas.width = 0;
+ tempCanvas.height = 0;
+ }
+ PDFThumbnailView.tempImageCache = null;
+ },
+ /**
+ * @private
+ */
+ _resetView: function PDFThumbnailViewer_resetView() {
+ this.thumbnails = [];
+ this._pageLabels = null;
+ this._pagesRotation = 0;
+ this._pagesRequests = [];
+ // Remove the thumbnails from the DOM.
+ this.container.textContent = '';
+ },
+ setDocument: function PDFThumbnailViewer_setDocument(pdfDocument) {
+ if (this.pdfDocument) {
+ this._cancelRendering();
+ this._resetView();
+ }
+ this.pdfDocument = pdfDocument;
+ if (!pdfDocument) {
+ return Promise.resolve();
+ }
+ return pdfDocument.getPage(1).then(function (firstPage) {
+ var pagesCount = pdfDocument.numPages;
+ var viewport = firstPage.getViewport(1.0);
+ for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+ var thumbnail = new PDFThumbnailView({
+ container: this.container,
+ id: pageNum,
+ defaultViewport: viewport.clone(),
+ linkService: this.linkService,
+ renderingQueue: this.renderingQueue,
+ disableCanvasToImageConversion: false
+ });
+ this.thumbnails.push(thumbnail);
+ }
+ }.bind(this));
+ },
+ /**
+ * @private
+ */
+ _cancelRendering: function PDFThumbnailViewer_cancelRendering() {
+ for (var i = 0, ii = this.thumbnails.length; i < ii; i++) {
+ if (this.thumbnails[i]) {
+ this.thumbnails[i].cancelRendering();
+ }
+ }
+ },
+ /**
+ * @param {Array|null} labels
+ */
+ setPageLabels: function PDFThumbnailViewer_setPageLabels(labels) {
+ if (!this.pdfDocument) {
+ return;
+ }
+ if (!labels) {
+ this._pageLabels = null;
+ } else if (!(labels instanceof Array && this.pdfDocument.numPages === labels.length)) {
+ this._pageLabels = null;
+ console.error('PDFThumbnailViewer_setPageLabels: Invalid page labels.');
+ } else {
+ this._pageLabels = labels;
+ }
+ // Update all the `PDFThumbnailView` instances.
+ for (var i = 0, ii = this.thumbnails.length; i < ii; i++) {
+ var thumbnailView = this.thumbnails[i];
+ var label = this._pageLabels && this._pageLabels[i];
+ thumbnailView.setPageLabel(label);
+ }
+ },
+ /**
+ * @param {PDFThumbnailView} thumbView
+ * @returns {PDFPage}
+ * @private
+ */
+ _ensurePdfPageLoaded: function PDFThumbnailViewer_ensurePdfPageLoaded(thumbView) {
+ if (thumbView.pdfPage) {
+ return Promise.resolve(thumbView.pdfPage);
+ }
+ var pageNumber = thumbView.id;
+ if (this._pagesRequests[pageNumber]) {
+ return this._pagesRequests[pageNumber];
+ }
+ var promise = this.pdfDocument.getPage(pageNumber).then(function (pdfPage) {
+ thumbView.setPdfPage(pdfPage);
+ this._pagesRequests[pageNumber] = null;
+ return pdfPage;
+ }.bind(this));
+ this._pagesRequests[pageNumber] = promise;
+ return promise;
+ },
+ forceRendering: function () {
+ var visibleThumbs = this._getVisibleThumbs();
+ var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, this.thumbnails, this.scroll.down);
+ if (thumbView) {
+ this._ensurePdfPageLoaded(thumbView).then(function () {
+ this.renderingQueue.renderView(thumbView);
+ }.bind(this));
+ return true;
+ }
+ return false;
+ }
+ };
+ return PDFThumbnailViewer;
+ }();
+ exports.PDFThumbnailViewer = PDFThumbnailViewer;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebTextLayerBuilder = {}, root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS);
+ }(this, function (exports, domEvents, pdfjsLib) {
+ var EXPAND_DIVS_TIMEOUT = 300;
+ // ms
+ /**
+ * @typedef {Object} TextLayerBuilderOptions
+ * @property {HTMLDivElement} textLayerDiv - The text layer container.
+ * @property {EventBus} eventBus - The application event bus.
+ * @property {number} pageIndex - The page index.
+ * @property {PageViewport} viewport - The viewport of the text layer.
+ * @property {PDFFindController} findController
+ * @property {boolean} enhanceTextSelection - Option to turn on improved
+ * text selection.
+ */
+ /**
+ * TextLayerBuilder provides text-selection functionality for the PDF.
+ * It does this by creating overlay divs over the PDF text. These divs
+ * contain text that matches the PDF text they are overlaying. This object
+ * also provides a way to highlight text that is being searched for.
+ * @class
+ */
+ var TextLayerBuilder = function TextLayerBuilderClosure() {
+ function TextLayerBuilder(options) {
+ this.textLayerDiv = options.textLayerDiv;
+ this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
+ this.textContent = null;
+ this.renderingDone = false;
+ this.pageIdx = options.pageIndex;
+ this.pageNumber = this.pageIdx + 1;
+ this.matches = [];
+ this.viewport = options.viewport;
+ this.textDivs = [];
+ this.findController = options.findController || null;
+ this.textLayerRenderTask = null;
+ this.enhanceTextSelection = options.enhanceTextSelection;
+ this._bindMouse();
+ }
+ TextLayerBuilder.prototype = {
+ /**
+ * @private
+ */
+ _finishRendering: function TextLayerBuilder_finishRendering() {
+ this.renderingDone = true;
+ if (!this.enhanceTextSelection) {
+ var endOfContent = document.createElement('div');
+ endOfContent.className = 'endOfContent';
+ this.textLayerDiv.appendChild(endOfContent);
+ }
+ this.eventBus.dispatch('textlayerrendered', {
+ source: this,
+ pageNumber: this.pageNumber,
+ numTextDivs: this.textDivs.length
+ });
+ },
+ /**
+ * Renders the text layer.
+ * @param {number} timeout (optional) if specified, the rendering waits
+ * for specified amount of ms.
+ */
+ render: function TextLayerBuilder_render(timeout) {
+ if (!this.textContent || this.renderingDone) {
+ return;
+ }
+ this.cancel();
+ this.textDivs = [];
+ var textLayerFrag = document.createDocumentFragment();
+ this.textLayerRenderTask = pdfjsLib.renderTextLayer({
+ textContent: this.textContent,
+ container: textLayerFrag,
+ viewport: this.viewport,
+ textDivs: this.textDivs,
+ timeout: timeout,
+ enhanceTextSelection: this.enhanceTextSelection
+ });
+ this.textLayerRenderTask.promise.then(function () {
+ this.textLayerDiv.appendChild(textLayerFrag);
+ this._finishRendering();
+ this.updateMatches();
+ }.bind(this), function (reason) {
+ });
+ },
+ /**
+ * Cancels rendering of the text layer.
+ */
+ cancel: function TextLayerBuilder_cancel() {
+ if (this.textLayerRenderTask) {
+ this.textLayerRenderTask.cancel();
+ this.textLayerRenderTask = null;
+ }
+ },
+ setTextContent: function TextLayerBuilder_setTextContent(textContent) {
+ this.cancel();
+ this.textContent = textContent;
+ },
+ convertMatches: function TextLayerBuilder_convertMatches(matches, matchesLength) {
+ var i = 0;
+ var iIndex = 0;
+ var bidiTexts = this.textContent.items;
+ var end = bidiTexts.length - 1;
+ var queryLen = this.findController === null ? 0 : this.findController.state.query.length;
+ var ret = [];
+ if (!matches) {
+ return ret;
+ }
+ for (var m = 0, len = matches.length; m < len; m++) {
+ // Calculate the start position.
+ var matchIdx = matches[m];
+ // Loop over the divIdxs.
+ while (i !== end && matchIdx >= iIndex + bidiTexts[i].str.length) {
+ iIndex += bidiTexts[i].str.length;
+ i++;
+ }
+ if (i === bidiTexts.length) {
+ console.error('Could not find a matching mapping');
+ }
+ var match = {
+ begin: {
+ divIdx: i,
+ offset: matchIdx - iIndex
+ }
+ };
+ // Calculate the end position.
+ if (matchesLength) {
+ // multiterm search
+ matchIdx += matchesLength[m];
+ } else {
+ // phrase search
+ matchIdx += queryLen;
+ }
+ // Somewhat the same array as above, but use > instead of >= to get
+ // the end position right.
+ while (i !== end && matchIdx > iIndex + bidiTexts[i].str.length) {
+ iIndex += bidiTexts[i].str.length;
+ i++;
+ }
+ match.end = {
+ divIdx: i,
+ offset: matchIdx - iIndex
+ };
+ ret.push(match);
+ }
+ return ret;
+ },
+ renderMatches: function TextLayerBuilder_renderMatches(matches) {
+ // Early exit if there is nothing to render.
+ if (matches.length === 0) {
+ return;
+ }
+ var bidiTexts = this.textContent.items;
+ var textDivs = this.textDivs;
+ var prevEnd = null;
+ var pageIdx = this.pageIdx;
+ var isSelectedPage = this.findController === null ? false : pageIdx === this.findController.selected.pageIdx;
+ var selectedMatchIdx = this.findController === null ? -1 : this.findController.selected.matchIdx;
+ var highlightAll = this.findController === null ? false : this.findController.state.highlightAll;
+ var infinity = {
+ divIdx: -1,
+ offset: undefined
+ };
+ function beginText(begin, className) {
+ var divIdx = begin.divIdx;
+ textDivs[divIdx].textContent = '';
+ appendTextToDiv(divIdx, 0, begin.offset, className);
+ }
+ function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
+ var div = textDivs[divIdx];
+ var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
+ var node = document.createTextNode(content);
+ if (className) {
+ var span = document.createElement('span');
+ span.className = className;
+ span.appendChild(node);
+ div.appendChild(span);
+ return;
+ }
+ div.appendChild(node);
+ }
+ var i0 = selectedMatchIdx, i1 = i0 + 1;
+ if (highlightAll) {
+ i0 = 0;
+ i1 = matches.length;
+ } else if (!isSelectedPage) {
+ // Not highlighting all and this isn't the selected page, so do nothing.
+ return;
+ }
+ for (var i = i0; i < i1; i++) {
+ var match = matches[i];
+ var begin = match.begin;
+ var end = match.end;
+ var isSelected = isSelectedPage && i === selectedMatchIdx;
+ var highlightSuffix = isSelected ? ' selected' : '';
+ if (this.findController) {
+ this.findController.updateMatchPosition(pageIdx, i, textDivs, begin.divIdx);
+ }
+ // Match inside new div.
+ if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
+ // If there was a previous div, then add the text at the end.
+ if (prevEnd !== null) {
+ appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
+ }
+ // Clear the divs and set the content until the starting point.
+ beginText(begin);
+ } else {
+ appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
+ }
+ if (begin.divIdx === end.divIdx) {
+ appendTextToDiv(begin.divIdx, begin.offset, end.offset, 'highlight' + highlightSuffix);
+ } else {
+ appendTextToDiv(begin.divIdx, begin.offset, infinity.offset, 'highlight begin' + highlightSuffix);
+ for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
+ textDivs[n0].className = 'highlight middle' + highlightSuffix;
+ }
+ beginText(end, 'highlight end' + highlightSuffix);
+ }
+ prevEnd = end;
+ }
+ if (prevEnd) {
+ appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
+ }
+ },
+ updateMatches: function TextLayerBuilder_updateMatches() {
+ // Only show matches when all rendering is done.
+ if (!this.renderingDone) {
+ return;
+ }
+ // Clear all matches.
+ var matches = this.matches;
+ var textDivs = this.textDivs;
+ var bidiTexts = this.textContent.items;
+ var clearedUntilDivIdx = -1;
+ // Clear all current matches.
+ for (var i = 0, len = matches.length; i < len; i++) {
+ var match = matches[i];
+ var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
+ for (var n = begin, end = match.end.divIdx; n <= end; n++) {
+ var div = textDivs[n];
+ div.textContent = bidiTexts[n].str;
+ div.className = '';
+ }
+ clearedUntilDivIdx = match.end.divIdx + 1;
+ }
+ if (this.findController === null || !this.findController.active) {
+ return;
+ }
+ // Convert the matches on the page controller into the match format
+ // used for the textLayer.
+ var pageMatches, pageMatchesLength;
+ if (this.findController !== null) {
+ pageMatches = this.findController.pageMatches[this.pageIdx] || null;
+ pageMatchesLength = this.findController.pageMatchesLength ? this.findController.pageMatchesLength[this.pageIdx] || null : null;
+ }
+ this.matches = this.convertMatches(pageMatches, pageMatchesLength);
+ this.renderMatches(this.matches);
+ },
+ /**
+ * Fixes text selection: adds additional div where mouse was clicked.
+ * This reduces flickering of the content if mouse slowly dragged down/up.
+ * @private
+ */
+ _bindMouse: function TextLayerBuilder_bindMouse() {
+ var div = this.textLayerDiv;
+ var self = this;
+ var expandDivsTimer = null;
+ div.addEventListener('mousedown', function (e) {
+ if (self.enhanceTextSelection && self.textLayerRenderTask) {
+ self.textLayerRenderTask.expandTextDivs(true);
+ return;
+ }
+ var end = div.querySelector('.endOfContent');
+ if (!end) {
+ return;
+ }
+ end.classList.add('active');
+ });
+ div.addEventListener('mouseup', function (e) {
+ if (self.enhanceTextSelection && self.textLayerRenderTask) {
+ self.textLayerRenderTask.expandTextDivs(false);
+ return;
+ }
+ var end = div.querySelector('.endOfContent');
+ if (!end) {
+ return;
+ }
+ end.classList.remove('active');
+ });
+ }
+ };
+ return TextLayerBuilder;
+ }();
+ /**
+ * @constructor
+ * @implements IPDFTextLayerFactory
+ */
+ function DefaultTextLayerFactory() {
+ }
+ DefaultTextLayerFactory.prototype = {
+ /**
+ * @param {HTMLDivElement} textLayerDiv
+ * @param {number} pageIndex
+ * @param {PageViewport} viewport
+ * @param {boolean} enhanceTextSelection
+ * @returns {TextLayerBuilder}
+ */
+ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport, enhanceTextSelection) {
+ return new TextLayerBuilder({
+ textLayerDiv: textLayerDiv,
+ pageIndex: pageIndex,
+ viewport: viewport,
+ enhanceTextSelection: enhanceTextSelection
+ });
+ }
+ };
+ exports.TextLayerBuilder = TextLayerBuilder;
+ exports.DefaultTextLayerFactory = DefaultTextLayerFactory;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebAnnotationLayerBuilder = {}, root.pdfjsWebUIUtils, root.pdfjsWebPDFLinkService, root.pdfjsWebPDFJS);
+ }(this, function (exports, uiUtils, pdfLinkService, pdfjsLib) {
+ var mozL10n = uiUtils.mozL10n;
+ var SimpleLinkService = pdfLinkService.SimpleLinkService;
+ /**
+ * @typedef {Object} AnnotationLayerBuilderOptions
+ * @property {HTMLDivElement} pageDiv
+ * @property {PDFPage} pdfPage
+ * @property {boolean} renderInteractiveForms
+ * @property {IPDFLinkService} linkService
+ * @property {DownloadManager} downloadManager
+ */
+ /**
+ * @class
+ */
+ var AnnotationLayerBuilder = function AnnotationLayerBuilderClosure() {
+ /**
+ * @param {AnnotationLayerBuilderOptions} options
+ * @constructs AnnotationLayerBuilder
+ */
+ function AnnotationLayerBuilder(options) {
+ this.pageDiv = options.pageDiv;
+ this.pdfPage = options.pdfPage;
+ this.renderInteractiveForms = options.renderInteractiveForms;
+ this.linkService = options.linkService;
+ this.downloadManager = options.downloadManager;
+ this.div = null;
+ }
+ AnnotationLayerBuilder.prototype = /** @lends AnnotationLayerBuilder.prototype */
+ {
+ /**
+ * @param {PageViewport} viewport
+ * @param {string} intent (default value is 'display')
+ */
+ render: function AnnotationLayerBuilder_render(viewport, intent) {
+ var self = this;
+ var parameters = { intent: intent === undefined ? 'display' : intent };
+ this.pdfPage.getAnnotations(parameters).then(function (annotations) {
+ viewport = viewport.clone({ dontFlip: true });
+ parameters = {
+ viewport: viewport,
+ div: self.div,
+ annotations: annotations,
+ page: self.pdfPage,
+ renderInteractiveForms: self.renderInteractiveForms,
+ linkService: self.linkService,
+ downloadManager: self.downloadManager
+ };
+ if (self.div) {
+ // If an annotationLayer already exists, refresh its children's
+ // transformation matrices.
+ pdfjsLib.AnnotationLayer.update(parameters);
+ } else {
+ // Create an annotation layer div and render the annotations
+ // if there is at least one annotation.
+ if (annotations.length === 0) {
+ return;
+ }
+ self.div = document.createElement('div');
+ self.div.className = 'annotationLayer';
+ self.pageDiv.appendChild(self.div);
+ parameters.div = self.div;
+ pdfjsLib.AnnotationLayer.render(parameters);
+ if (typeof mozL10n !== 'undefined') {
+ mozL10n.translate(self.div);
+ }
+ }
+ });
+ },
+ hide: function AnnotationLayerBuilder_hide() {
+ if (!this.div) {
+ return;
+ }
+ this.div.setAttribute('hidden', 'true');
+ }
+ };
+ return AnnotationLayerBuilder;
+ }();
+ /**
+ * @constructor
+ * @implements IPDFAnnotationLayerFactory
+ */
+ function DefaultAnnotationLayerFactory() {
+ }
+ DefaultAnnotationLayerFactory.prototype = {
+ /**
+ * @param {HTMLDivElement} pageDiv
+ * @param {PDFPage} pdfPage
+ * @param {boolean} renderInteractiveForms
+ * @returns {AnnotationLayerBuilder}
+ */
+ createAnnotationLayerBuilder: function (pageDiv, pdfPage, renderInteractiveForms) {
+ return new AnnotationLayerBuilder({
+ pageDiv: pageDiv,
+ pdfPage: pdfPage,
+ renderInteractiveForms: renderInteractiveForms,
+ linkService: new SimpleLinkService()
+ });
+ }
+ };
+ exports.AnnotationLayerBuilder = AnnotationLayerBuilder;
+ exports.DefaultAnnotationLayerFactory = DefaultAnnotationLayerFactory;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebPDFViewer = {}, root.pdfjsWebUIUtils, root.pdfjsWebPDFPageView, root.pdfjsWebPDFRenderingQueue, root.pdfjsWebTextLayerBuilder, root.pdfjsWebAnnotationLayerBuilder, root.pdfjsWebPDFLinkService, root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS);
+ }(this, function (exports, uiUtils, pdfPageView, pdfRenderingQueue, textLayerBuilder, annotationLayerBuilder, pdfLinkService, domEvents, pdfjsLib) {
+ var UNKNOWN_SCALE = uiUtils.UNKNOWN_SCALE;
+ var SCROLLBAR_PADDING = uiUtils.SCROLLBAR_PADDING;
+ var VERTICAL_PADDING = uiUtils.VERTICAL_PADDING;
+ var MAX_AUTO_SCALE = uiUtils.MAX_AUTO_SCALE;
+ var CSS_UNITS = uiUtils.CSS_UNITS;
+ var DEFAULT_SCALE = uiUtils.DEFAULT_SCALE;
+ var DEFAULT_SCALE_VALUE = uiUtils.DEFAULT_SCALE_VALUE;
+ var scrollIntoView = uiUtils.scrollIntoView;
+ var watchScroll = uiUtils.watchScroll;
+ var getVisibleElements = uiUtils.getVisibleElements;
+ var PDFPageView = pdfPageView.PDFPageView;
+ var RenderingStates = pdfRenderingQueue.RenderingStates;
+ var PDFRenderingQueue = pdfRenderingQueue.PDFRenderingQueue;
+ var TextLayerBuilder = textLayerBuilder.TextLayerBuilder;
+ var AnnotationLayerBuilder = annotationLayerBuilder.AnnotationLayerBuilder;
+ var SimpleLinkService = pdfLinkService.SimpleLinkService;
+ var PresentationModeState = {
+ UNKNOWN: 0,
+ NORMAL: 1,
+ CHANGING: 2,
+ FULLSCREEN: 3
+ };
+ var DEFAULT_CACHE_SIZE = 10;
+ /**
+ * @typedef {Object} PDFViewerOptions
+ * @property {HTMLDivElement} container - The container for the viewer element.
+ * @property {HTMLDivElement} viewer - (optional) The viewer element.
+ * @property {EventBus} eventBus - The application event bus.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {DownloadManager} downloadManager - (optional) The download
+ * manager component.
+ * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
+ * queue object.
+ * @property {boolean} removePageBorders - (optional) Removes the border shadow
+ * around the pages. The default is false.
+ * @property {boolean} enhanceTextSelection - (optional) Enables the improved
+ * text selection behaviour. The default is `false`.
+ * @property {boolean} renderInteractiveForms - (optional) Enables rendering of
+ * interactive form elements. The default is `false`.
+ */
+ /**
+ * Simple viewer control to display PDF content/pages.
+ * @class
+ * @implements {IRenderableView}
+ */
+ var PDFViewer = function pdfViewer() {
+ function PDFPageViewBuffer(size) {
+ var data = [];
+ this.push = function cachePush(view) {
+ var i = data.indexOf(view);
+ if (i >= 0) {
+ data.splice(i, 1);
+ }
+ data.push(view);
+ if (data.length > size) {
+ data.shift().destroy();
+ }
+ };
+ this.resize = function (newSize) {
+ size = newSize;
+ while (data.length > size) {
+ data.shift().destroy();
+ }
+ };
+ }
+ function isSameScale(oldScale, newScale) {
+ if (newScale === oldScale) {
+ return true;
+ }
+ if (Math.abs(newScale - oldScale) < 1e-15) {
+ // Prevent unnecessary re-rendering of all pages when the scale
+ // changes only because of limited numerical precision.
+ return true;
+ }
+ return false;
+ }
+ /**
+ * @constructs PDFViewer
+ * @param {PDFViewerOptions} options
+ */
+ function PDFViewer(options) {
+ this.container = options.container;
+ this.viewer = options.viewer || options.container.firstElementChild;
+ this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
+ this.linkService = options.linkService || new SimpleLinkService();
+ this.downloadManager = options.downloadManager || null;
+ this.removePageBorders = options.removePageBorders || false;
+ this.enhanceTextSelection = options.enhanceTextSelection || false;
+ this.renderInteractiveForms = options.renderInteractiveForms || false;
+ this.defaultRenderingQueue = !options.renderingQueue;
+ if (this.defaultRenderingQueue) {
+ // Custom rendering queue is not specified, using default one
+ this.renderingQueue = new PDFRenderingQueue();
+ this.renderingQueue.setViewer(this);
+ } else {
+ this.renderingQueue = options.renderingQueue;
+ }
+ this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
+ this.presentationModeState = PresentationModeState.UNKNOWN;
+ this._resetView();
+ if (this.removePageBorders) {
+ this.viewer.classList.add('removePageBorders');
+ }
+ }
+ PDFViewer.prototype = /** @lends PDFViewer.prototype */
+ {
+ get pagesCount() {
+ return this._pages.length;
+ },
+ getPageView: function (index) {
+ return this._pages[index];
+ },
+ /**
+ * @returns {boolean} true if all {PDFPageView} objects are initialized.
+ */
+ get pageViewsReady() {
+ return this._pageViewsReady;
+ },
+ /**
+ * @returns {number}
+ */
+ get currentPageNumber() {
+ return this._currentPageNumber;
+ },
+ /**
+ * @param {number} val - The page number.
+ */
+ set currentPageNumber(val) {
+ if ((val | 0) !== val) {
+ // Ensure that `val` is an integer.
+ throw new Error('Invalid page number.');
+ }
+ if (!this.pdfDocument) {
+ this._currentPageNumber = val;
+ return;
+ }
+ // The intent can be to just reset a scroll position and/or scale.
+ this._setCurrentPageNumber(val, /* resetCurrentPageView = */
+ true);
+ },
+ /**
+ * @private
+ */
+ _setCurrentPageNumber: function PDFViewer_setCurrentPageNumber(val, resetCurrentPageView) {
+ if (this._currentPageNumber === val) {
+ if (resetCurrentPageView) {
+ this._resetCurrentPageView();
+ }
+ return;
+ }
+ if (!(0 < val && val <= this.pagesCount)) {
+ console.error('PDFViewer_setCurrentPageNumber: "' + val + '" is out of bounds.');
+ return;
+ }
+ var arg = {
+ source: this,
+ pageNumber: val,
+ pageLabel: this._pageLabels && this._pageLabels[val - 1]
+ };
+ this._currentPageNumber = val;
+ this.eventBus.dispatch('pagechanging', arg);
+ this.eventBus.dispatch('pagechange', arg);
+ if (resetCurrentPageView) {
+ this._resetCurrentPageView();
+ }
+ },
+ /**
+ * @returns {string|null} Returns the current page label,
+ * or `null` if no page labels exist.
+ */
+ get currentPageLabel() {
+ return this._pageLabels && this._pageLabels[this._currentPageNumber - 1];
+ },
+ /**
+ * @param {string} val - The page label.
+ */
+ set currentPageLabel(val) {
+ var pageNumber = val | 0;
+ // Fallback page number.
+ if (this._pageLabels) {
+ var i = this._pageLabels.indexOf(val);
+ if (i >= 0) {
+ pageNumber = i + 1;
+ }
+ }
+ this.currentPageNumber = pageNumber;
+ },
+ /**
+ * @returns {number}
+ */
+ get currentScale() {
+ return this._currentScale !== UNKNOWN_SCALE ? this._currentScale : DEFAULT_SCALE;
+ },
+ /**
+ * @param {number} val - Scale of the pages in percents.
+ */
+ set currentScale(val) {
+ if (isNaN(val)) {
+ throw new Error('Invalid numeric scale');
+ }
+ if (!this.pdfDocument) {
+ this._currentScale = val;
+ this._currentScaleValue = val !== UNKNOWN_SCALE ? val.toString() : null;
+ return;
+ }
+ this._setScale(val, false);
+ },
+ /**
+ * @returns {string}
+ */
+ get currentScaleValue() {
+ return this._currentScaleValue;
+ },
+ /**
+ * @param val - The scale of the pages (in percent or predefined value).
+ */
+ set currentScaleValue(val) {
+ if (!this.pdfDocument) {
+ this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
+ this._currentScaleValue = val.toString();
+ return;
+ }
+ this._setScale(val, false);
+ },
+ /**
+ * @returns {number}
+ */
+ get pagesRotation() {
+ return this._pagesRotation;
+ },
+ /**
+ * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
+ */
+ set pagesRotation(rotation) {
+ if (!(typeof rotation === 'number' && rotation % 90 === 0)) {
+ throw new Error('Invalid pages rotation angle.');
+ }
+ this._pagesRotation = rotation;
+ if (!this.pdfDocument) {
+ return;
+ }
+ for (var i = 0, l = this._pages.length; i < l; i++) {
+ var pageView = this._pages[i];
+ pageView.update(pageView.scale, rotation);
+ }
+ this._setScale(this._currentScaleValue, true);
+ if (this.defaultRenderingQueue) {
+ this.update();
+ }
+ },
+ /**
+ * @param pdfDocument {PDFDocument}
+ */
+ setDocument: function (pdfDocument) {
+ if (this.pdfDocument) {
+ this._cancelRendering();
+ this._resetView();
+ }
+ this.pdfDocument = pdfDocument;
+ if (!pdfDocument) {
+ return;
+ }
+ var pagesCount = pdfDocument.numPages;
+ var self = this;
+ var resolvePagesPromise;
+ var pagesPromise = new Promise(function (resolve) {
+ resolvePagesPromise = resolve;
+ });
+ this.pagesPromise = pagesPromise;
+ pagesPromise.then(function () {
+ self._pageViewsReady = true;
+ self.eventBus.dispatch('pagesloaded', {
+ source: self,
+ pagesCount: pagesCount
+ });
+ });
+ var isOnePageRenderedResolved = false;
+ var resolveOnePageRendered = null;
+ var onePageRendered = new Promise(function (resolve) {
+ resolveOnePageRendered = resolve;
+ });
+ this.onePageRendered = onePageRendered;
+ var bindOnAfterAndBeforeDraw = function (pageView) {
+ pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() {
+ // Add the page to the buffer at the start of drawing. That way it can
+ // be evicted from the buffer and destroyed even if we pause its
+ // rendering.
+ self._buffer.push(this);
+ };
+ pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
+ if (!isOnePageRenderedResolved) {
+ isOnePageRenderedResolved = true;
+ resolveOnePageRendered();
+ }
+ };
+ };
+ var firstPagePromise = pdfDocument.getPage(1);
+ this.firstPagePromise = firstPagePromise;
+ // Fetch a single page so we can get a viewport that will be the default
+ // viewport for all pages
+ return firstPagePromise.then(function (pdfPage) {
+ var scale = this.currentScale;
+ var viewport = pdfPage.getViewport(scale * CSS_UNITS);
+ for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+ var textLayerFactory = null;
+ if (!pdfjsLib.PDFJS.disableTextLayer) {
+ textLayerFactory = this;
+ }
+ var pageView = new PDFPageView({
+ container: this.viewer,
+ eventBus: this.eventBus,
+ id: pageNum,
+ scale: scale,
+ defaultViewport: viewport.clone(),
+ renderingQueue: this.renderingQueue,
+ textLayerFactory: textLayerFactory,
+ annotationLayerFactory: this,
+ enhanceTextSelection: this.enhanceTextSelection,
+ renderInteractiveForms: this.renderInteractiveForms
+ });
+ bindOnAfterAndBeforeDraw(pageView);
+ this._pages.push(pageView);
+ }
+ var linkService = this.linkService;
+ // Fetch all the pages since the viewport is needed before printing
+ // starts to create the correct size canvas. Wait until one page is
+ // rendered so we don't tie up too many resources early on.
+ onePageRendered.then(function () {
+ if (!pdfjsLib.PDFJS.disableAutoFetch) {
+ var getPagesLeft = pagesCount;
+ for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+ pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
+ var pageView = self._pages[pageNum - 1];
+ if (!pageView.pdfPage) {
+ pageView.setPdfPage(pdfPage);
+ }
+ linkService.cachePageRef(pageNum, pdfPage.ref);
+ getPagesLeft--;
+ if (!getPagesLeft) {
+ resolvePagesPromise();
+ }
+ }.bind(null, pageNum));
+ }
+ } else {
+ // XXX: Printing is semi-broken with auto fetch disabled.
+ resolvePagesPromise();
+ }
+ });
+ self.eventBus.dispatch('pagesinit', { source: self });
+ if (this.defaultRenderingQueue) {
+ this.update();
+ }
+ if (this.findController) {
+ this.findController.resolveFirstPage();
+ }
+ }.bind(this));
+ },
+ /**
+ * @param {Array|null} labels
+ */
+ setPageLabels: function PDFViewer_setPageLabels(labels) {
+ if (!this.pdfDocument) {
+ return;
+ }
+ if (!labels) {
+ this._pageLabels = null;
+ } else if (!(labels instanceof Array && this.pdfDocument.numPages === labels.length)) {
+ this._pageLabels = null;
+ console.error('PDFViewer_setPageLabels: Invalid page labels.');
+ } else {
+ this._pageLabels = labels;
+ }
+ // Update all the `PDFPageView` instances.
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ var pageView = this._pages[i];
+ var label = this._pageLabels && this._pageLabels[i];
+ pageView.setPageLabel(label);
+ }
+ },
+ _resetView: function () {
+ this._pages = [];
+ this._currentPageNumber = 1;
+ this._currentScale = UNKNOWN_SCALE;
+ this._currentScaleValue = null;
+ this._pageLabels = null;
+ this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
+ this._location = null;
+ this._pagesRotation = 0;
+ this._pagesRequests = [];
+ this._pageViewsReady = false;
+ // Remove the pages from the DOM.
+ this.viewer.textContent = '';
+ },
+ _scrollUpdate: function PDFViewer_scrollUpdate() {
+ if (this.pagesCount === 0) {
+ return;
+ }
+ this.update();
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ this._pages[i].updatePosition();
+ }
+ },
+ _setScaleDispatchEvent: function pdfViewer_setScaleDispatchEvent(newScale, newValue, preset) {
+ var arg = {
+ source: this,
+ scale: newScale,
+ presetValue: preset ? newValue : undefined
+ };
+ this.eventBus.dispatch('scalechanging', arg);
+ this.eventBus.dispatch('scalechange', arg);
+ },
+ _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(newScale, newValue, noScroll, preset) {
+ this._currentScaleValue = newValue.toString();
+ if (isSameScale(this._currentScale, newScale)) {
+ if (preset) {
+ this._setScaleDispatchEvent(newScale, newValue, true);
+ }
+ return;
+ }
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ this._pages[i].update(newScale);
+ }
+ this._currentScale = newScale;
+ if (!noScroll) {
+ var page = this._currentPageNumber, dest;
+ if (this._location && !pdfjsLib.PDFJS.ignoreCurrentPositionOnZoom && !(this.isInPresentationMode || this.isChangingPresentationMode)) {
+ page = this._location.pageNumber;
+ dest = [
+ null,
+ { name: 'XYZ' },
+ this._location.left,
+ this._location.top,
+ null
+ ];
+ }
+ this.scrollPageIntoView({
+ pageNumber: page,
+ destArray: dest,
+ allowNegativeOffset: true
+ });
+ }
+ this._setScaleDispatchEvent(newScale, newValue, preset);
+ if (this.defaultRenderingQueue) {
+ this.update();
+ }
+ },
+ _setScale: function PDFViewer_setScale(value, noScroll) {
+ var scale = parseFloat(value);
+ if (scale > 0) {
+ this._setScaleUpdatePages(scale, value, noScroll, false);
+ } else {
+ var currentPage = this._pages[this._currentPageNumber - 1];
+ if (!currentPage) {
+ return;
+ }
+ var hPadding = this.isInPresentationMode || this.removePageBorders ? 0 : SCROLLBAR_PADDING;
+ var vPadding = this.isInPresentationMode || this.removePageBorders ? 0 : VERTICAL_PADDING;
+ var pageWidthScale = (this.container.clientWidth - hPadding) / currentPage.width * currentPage.scale;
+ var pageHeightScale = (this.container.clientHeight - vPadding) / currentPage.height * currentPage.scale;
+ switch (value) {
+ case 'page-actual':
+ scale = 1;
+ break;
+ case 'page-width':
+ scale = pageWidthScale;
+ break;
+ case 'page-height':
+ scale = pageHeightScale;
+ break;
+ case 'page-fit':
+ scale = Math.min(pageWidthScale, pageHeightScale);
+ break;
+ case 'auto':
+ var isLandscape = currentPage.width > currentPage.height;
+ // For pages in landscape mode, fit the page height to the viewer
+ // *unless* the page would thus become too wide to fit horizontally.
+ var horizontalScale = isLandscape ? Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
+ scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
+ break;
+ default:
+ console.error('PDFViewer_setScale: "' + value + '" is an unknown zoom value.');
+ return;
+ }
+ this._setScaleUpdatePages(scale, value, noScroll, true);
+ }
+ },
+ /**
+ * Refreshes page view: scrolls to the current page and updates the scale.
+ * @private
+ */
+ _resetCurrentPageView: function () {
+ if (this.isInPresentationMode) {
+ // Fixes the case when PDF has different page sizes.
+ this._setScale(this._currentScaleValue, true);
+ }
+ var pageView = this._pages[this._currentPageNumber - 1];
+ scrollIntoView(pageView.div);
+ },
+ /**
+ * @typedef ScrollPageIntoViewParameters
+ * @property {number} pageNumber - The page number.
+ * @property {Array} destArray - (optional) The original PDF destination
+ * array, in the format: <page-ref> </XYZ|/FitXXX> <args..>
+ * @property {boolean} allowNegativeOffset - (optional) Allow negative page
+ * offsets. The default value is `false`.
+ */
+ /**
+ * Scrolls page into view.
+ * @param {ScrollPageIntoViewParameters} params
+ */
+ scrollPageIntoView: function PDFViewer_scrollPageIntoView(params) {
+ if (!this.pdfDocument) {
+ return;
+ }
+ var pageNumber = params.pageNumber || 0;
+ var dest = params.destArray || null;
+ var allowNegativeOffset = params.allowNegativeOffset || false;
+ if (this.isInPresentationMode || !dest) {
+ this._setCurrentPageNumber(pageNumber, /* resetCurrentPageView */
+ true);
+ return;
+ }
+ var pageView = this._pages[pageNumber - 1];
+ if (!pageView) {
+ console.error('PDFViewer_scrollPageIntoView: ' + 'Invalid "pageNumber" parameter.');
+ return;
+ }
+ var x = 0, y = 0;
+ var width = 0, height = 0, widthScale, heightScale;
+ var changeOrientation = pageView.rotation % 180 === 0 ? false : true;
+ var pageWidth = (changeOrientation ? pageView.height : pageView.width) / pageView.scale / CSS_UNITS;
+ var pageHeight = (changeOrientation ? pageView.width : pageView.height) / pageView.scale / CSS_UNITS;
+ var scale = 0;
+ switch (dest[1].name) {
+ case 'XYZ':
+ x = dest[2];
+ y = dest[3];
+ scale = dest[4];
+ // If x and/or y coordinates are not supplied, default to
+ // _top_ left of the page (not the obvious bottom left,
+ // since aligning the bottom of the intended page with the
+ // top of the window is rarely helpful).
+ x = x !== null ? x : 0;
+ y = y !== null ? y : pageHeight;
+ break;
+ case 'Fit':
+ case 'FitB':
+ scale = 'page-fit';
+ break;
+ case 'FitH':
+ case 'FitBH':
+ y = dest[2];
+ scale = 'page-width';
+ // According to the PDF spec, section 12.3.2.2, a `null` value in the
+ // parameter should maintain the position relative to the new page.
+ if (y === null && this._location) {
+ x = this._location.left;
+ y = this._location.top;
+ }
+ break;
+ case 'FitV':
+ case 'FitBV':
+ x = dest[2];
+ width = pageWidth;
+ height = pageHeight;
+ scale = 'page-height';
+ break;
+ case 'FitR':
+ x = dest[2];
+ y = dest[3];
+ width = dest[4] - x;
+ height = dest[5] - y;
+ var hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
+ var vPadding = this.removePageBorders ? 0 : VERTICAL_PADDING;
+ widthScale = (this.container.clientWidth - hPadding) / width / CSS_UNITS;
+ heightScale = (this.container.clientHeight - vPadding) / height / CSS_UNITS;
+ scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
+ break;
+ default:
+ console.error('PDFViewer_scrollPageIntoView: \'' + dest[1].name + '\' is not a valid destination type.');
+ return;
+ }
+ if (scale && scale !== this._currentScale) {
+ this.currentScaleValue = scale;
+ } else if (this._currentScale === UNKNOWN_SCALE) {
+ this.currentScaleValue = DEFAULT_SCALE_VALUE;
+ }
+ if (scale === 'page-fit' && !dest[4]) {
+ scrollIntoView(pageView.div);
+ return;
+ }
+ var boundingRect = [
+ pageView.viewport.convertToViewportPoint(x, y),
+ pageView.viewport.convertToViewportPoint(x + width, y + height)
+ ];
+ var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
+ var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
+ if (!allowNegativeOffset) {
+ // Some bad PDF generators will create destinations with e.g. top values
+ // that exceeds the page height. Ensure that offsets are not negative,
+ // to prevent a previous page from becoming visible (fixes bug 874482).
+ left = Math.max(left, 0);
+ top = Math.max(top, 0);
+ }
+ scrollIntoView(pageView.div, {
+ left: left,
+ top: top
+ });
+ },
+ _updateLocation: function (firstPage) {
+ var currentScale = this._currentScale;
+ var currentScaleValue = this._currentScaleValue;
+ var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? Math.round(currentScale * 10000) / 100 : currentScaleValue;
+ var pageNumber = firstPage.id;
+ var pdfOpenParams = '#page=' + pageNumber;
+ pdfOpenParams += '&zoom=' + normalizedScaleValue;
+ var currentPageView = this._pages[pageNumber - 1];
+ var container = this.container;
+ var topLeft = currentPageView.getPagePoint(container.scrollLeft - firstPage.x, container.scrollTop - firstPage.y);
+ var intLeft = Math.round(topLeft[0]);
+ var intTop = Math.round(topLeft[1]);
+ pdfOpenParams += ',' + intLeft + ',' + intTop;
+ this._location = {
+ pageNumber: pageNumber,
+ scale: normalizedScaleValue,
+ top: intTop,
+ left: intLeft,
+ pdfOpenParams: pdfOpenParams
+ };
+ },
+ update: function PDFViewer_update() {
+ var visible = this._getVisiblePages();
+ var visiblePages = visible.views;
+ if (visiblePages.length === 0) {
+ return;
+ }
+ var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * visiblePages.length + 1);
+ this._buffer.resize(suggestedCacheSize);
+ this.renderingQueue.renderHighestPriority(visible);
+ var currentId = this._currentPageNumber;
+ var firstPage = visible.first;
+ for (var i = 0, ii = visiblePages.length, stillFullyVisible = false; i < ii; ++i) {
+ var page = visiblePages[i];
+ if (page.percent < 100) {
+ break;
+ }
+ if (page.id === currentId) {
+ stillFullyVisible = true;
+ break;
+ }
+ }
+ if (!stillFullyVisible) {
+ currentId = visiblePages[0].id;
+ }
+ if (!this.isInPresentationMode) {
+ this._setCurrentPageNumber(currentId);
+ }
+ this._updateLocation(firstPage);
+ this.eventBus.dispatch('updateviewarea', {
+ source: this,
+ location: this._location
+ });
+ },
+ containsElement: function (element) {
+ return this.container.contains(element);
+ },
+ focus: function () {
+ this.container.focus();
+ },
+ get isInPresentationMode() {
+ return this.presentationModeState === PresentationModeState.FULLSCREEN;
+ },
+ get isChangingPresentationMode() {
+ return this.presentationModeState === PresentationModeState.CHANGING;
+ },
+ get isHorizontalScrollbarEnabled() {
+ return this.isInPresentationMode ? false : this.container.scrollWidth > this.container.clientWidth;
+ },
+ _getVisiblePages: function () {
+ if (!this.isInPresentationMode) {
+ return getVisibleElements(this.container, this._pages, true);
+ } else {
+ // The algorithm in getVisibleElements doesn't work in all browsers and
+ // configurations when presentation mode is active.
+ var visible = [];
+ var currentPage = this._pages[this._currentPageNumber - 1];
+ visible.push({
+ id: currentPage.id,
+ view: currentPage
+ });
+ return {
+ first: currentPage,
+ last: currentPage,
+ views: visible
+ };
+ }
+ },
+ cleanup: function () {
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ if (this._pages[i] && this._pages[i].renderingState !== RenderingStates.FINISHED) {
+ this._pages[i].reset();
+ }
+ }
+ },
+ /**
+ * @private
+ */
+ _cancelRendering: function PDFViewer_cancelRendering() {
+ for (var i = 0, ii = this._pages.length; i < ii; i++) {
+ if (this._pages[i]) {
+ this._pages[i].cancelRendering();
+ }
+ }
+ },
+ /**
+ * @param {PDFPageView} pageView
+ * @returns {PDFPage}
+ * @private
+ */
+ _ensurePdfPageLoaded: function (pageView) {
+ if (pageView.pdfPage) {
+ return Promise.resolve(pageView.pdfPage);
+ }
+ var pageNumber = pageView.id;
+ if (this._pagesRequests[pageNumber]) {
+ return this._pagesRequests[pageNumber];
+ }
+ var promise = this.pdfDocument.getPage(pageNumber).then(function (pdfPage) {
+ pageView.setPdfPage(pdfPage);
+ this._pagesRequests[pageNumber] = null;
+ return pdfPage;
+ }.bind(this));
+ this._pagesRequests[pageNumber] = promise;
+ return promise;
+ },
+ forceRendering: function (currentlyVisiblePages) {
+ var visiblePages = currentlyVisiblePages || this._getVisiblePages();
+ var pageView = this.renderingQueue.getHighestPriority(visiblePages, this._pages, this.scroll.down);
+ if (pageView) {
+ this._ensurePdfPageLoaded(pageView).then(function () {
+ this.renderingQueue.renderView(pageView);
+ }.bind(this));
+ return true;
+ }
+ return false;
+ },
+ getPageTextContent: function (pageIndex) {
+ return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
+ return page.getTextContent({ normalizeWhitespace: true });
+ });
+ },
+ /**
+ * @param {HTMLDivElement} textLayerDiv
+ * @param {number} pageIndex
+ * @param {PageViewport} viewport
+ * @returns {TextLayerBuilder}
+ */
+ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport, enhanceTextSelection) {
+ return new TextLayerBuilder({
+ textLayerDiv: textLayerDiv,
+ eventBus: this.eventBus,
+ pageIndex: pageIndex,
+ viewport: viewport,
+ findController: this.isInPresentationMode ? null : this.findController,
+ enhanceTextSelection: this.isInPresentationMode ? false : enhanceTextSelection
+ });
+ },
+ /**
+ * @param {HTMLDivElement} pageDiv
+ * @param {PDFPage} pdfPage
+ * @param {boolean} renderInteractiveForms
+ * @returns {AnnotationLayerBuilder}
+ */
+ createAnnotationLayerBuilder: function (pageDiv, pdfPage, renderInteractiveForms) {
+ return new AnnotationLayerBuilder({
+ pageDiv: pageDiv,
+ pdfPage: pdfPage,
+ renderInteractiveForms: renderInteractiveForms,
+ linkService: this.linkService,
+ downloadManager: this.downloadManager
+ });
+ },
+ setFindController: function (findController) {
+ this.findController = findController;
+ },
+ /**
+ * Returns sizes of the pages.
+ * @returns {Array} Array of objects with width/height fields.
+ */
+ getPagesOverview: function () {
+ return this._pages.map(function (pageView) {
+ var viewport = pageView.pdfPage.getViewport(1);
+ return {
+ width: viewport.width,
+ height: viewport.height
+ };
+ });
+ }
+ };
+ return PDFViewer;
+ }();
+ exports.PresentationModeState = PresentationModeState;
+ exports.PDFViewer = PDFViewer;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebApp = {}, root.pdfjsWebUIUtils, root.pdfjsWebDownloadManager, root.pdfjsWebPDFHistory, root.pdfjsWebPreferences, root.pdfjsWebPDFSidebar, root.pdfjsWebViewHistory, root.pdfjsWebPDFThumbnailViewer, root.pdfjsWebSecondaryToolbar, root.pdfjsWebPasswordPrompt, root.pdfjsWebPDFPresentationMode, root.pdfjsWebPDFDocumentProperties, root.pdfjsWebHandTool, root.pdfjsWebPDFViewer, root.pdfjsWebPDFRenderingQueue, root.pdfjsWebPDFLinkService, root.pdfjsWebPDFOutlineViewer, root.pdfjsWebOverlayManager, root.pdfjsWebPDFAttachmentViewer, root.pdfjsWebPDFFindController, root.pdfjsWebPDFFindBar, root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS);
+ }(this, function (exports, uiUtilsLib, downloadManagerLib, pdfHistoryLib, preferencesLib, pdfSidebarLib, viewHistoryLib, pdfThumbnailViewerLib, secondaryToolbarLib, passwordPromptLib, pdfPresentationModeLib, pdfDocumentPropertiesLib, handToolLib, pdfViewerLib, pdfRenderingQueueLib, pdfLinkServiceLib, pdfOutlineViewerLib, overlayManagerLib, pdfAttachmentViewerLib, pdfFindControllerLib, pdfFindBarLib, domEventsLib, pdfjsLib) {
+ var UNKNOWN_SCALE = uiUtilsLib.UNKNOWN_SCALE;
+ var DEFAULT_SCALE_VALUE = uiUtilsLib.DEFAULT_SCALE_VALUE;
+ var ProgressBar = uiUtilsLib.ProgressBar;
+ var getPDFFileNameFromURL = uiUtilsLib.getPDFFileNameFromURL;
+ var noContextMenuHandler = uiUtilsLib.noContextMenuHandler;
+ var mozL10n = uiUtilsLib.mozL10n;
+ var parseQueryString = uiUtilsLib.parseQueryString;
+ var PDFHistory = pdfHistoryLib.PDFHistory;
+ var Preferences = preferencesLib.Preferences;
+ var SidebarView = pdfSidebarLib.SidebarView;
+ var PDFSidebar = pdfSidebarLib.PDFSidebar;
+ var ViewHistory = viewHistoryLib.ViewHistory;
+ var PDFThumbnailViewer = pdfThumbnailViewerLib.PDFThumbnailViewer;
+ var SecondaryToolbar = secondaryToolbarLib.SecondaryToolbar;
+ var PasswordPrompt = passwordPromptLib.PasswordPrompt;
+ var PDFPresentationMode = pdfPresentationModeLib.PDFPresentationMode;
+ var PDFDocumentProperties = pdfDocumentPropertiesLib.PDFDocumentProperties;
+ var HandTool = handToolLib.HandTool;
+ var PresentationModeState = pdfViewerLib.PresentationModeState;
+ var PDFViewer = pdfViewerLib.PDFViewer;
+ var RenderingStates = pdfRenderingQueueLib.RenderingStates;
+ var PDFRenderingQueue = pdfRenderingQueueLib.PDFRenderingQueue;
+ var PDFLinkService = pdfLinkServiceLib.PDFLinkService;
+ var PDFOutlineViewer = pdfOutlineViewerLib.PDFOutlineViewer;
+ var OverlayManager = overlayManagerLib.OverlayManager;
+ var PDFAttachmentViewer = pdfAttachmentViewerLib.PDFAttachmentViewer;
+ var PDFFindController = pdfFindControllerLib.PDFFindController;
+ var PDFFindBar = pdfFindBarLib.PDFFindBar;
+ var getGlobalEventBus = domEventsLib.getGlobalEventBus;
+ var normalizeWheelEventDelta = uiUtilsLib.normalizeWheelEventDelta;
+ var DEFAULT_SCALE_DELTA = 1.1;
+ var MIN_SCALE = 0.25;
+ var MAX_SCALE = 10.0;
+ var SCALE_SELECT_CONTAINER_PADDING = 8;
+ var SCALE_SELECT_PADDING = 22;
+ var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
+ var DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000;
+ function configure(PDFJS) {
+ PDFJS.imageResourcesPath = './images/';
+ PDFJS.workerSrc = '../build/pdf.worker.js';
+ PDFJS.cMapUrl = '../web/cmaps/';
+ PDFJS.cMapPacked = true;
+ }
+ var DefaultExernalServices = {
+ updateFindControlState: function (data) {
+ },
+ initPassiveLoading: function (callbacks) {
+ },
+ fallback: function (data, callback) {
+ },
+ reportTelemetry: function (data) {
+ },
+ createDownloadManager: function () {
+ return new downloadManagerLib.DownloadManager();
+ },
+ supportsIntegratedFind: false,
+ supportsDocumentFonts: true,
+ supportsDocumentColors: true,
+ supportedMouseWheelZoomModifierKeys: {
+ ctrlKey: true,
+ metaKey: true
+ }
+ };
+ var PDFViewerApplication = {
+ initialBookmark: document.location.hash.substring(1),
+ initialDestination: null,
+ initialized: false,
+ fellback: false,
+ appConfig: null,
+ pdfDocument: null,
+ pdfLoadingTask: null,
+ printService: null,
+ /** @type {PDFViewer} */
+ pdfViewer: null,
+ /** @type {PDFThumbnailViewer} */
+ pdfThumbnailViewer: null,
+ /** @type {PDFRenderingQueue} */
+ pdfRenderingQueue: null,
+ /** @type {PDFPresentationMode} */
+ pdfPresentationMode: null,
+ /** @type {PDFDocumentProperties} */
+ pdfDocumentProperties: null,
+ /** @type {PDFLinkService} */
+ pdfLinkService: null,
+ /** @type {PDFHistory} */
+ pdfHistory: null,
+ /** @type {PDFSidebar} */
+ pdfSidebar: null,
+ /** @type {PDFOutlineViewer} */
+ pdfOutlineViewer: null,
+ /** @type {PDFAttachmentViewer} */
+ pdfAttachmentViewer: null,
+ /** @type {ViewHistory} */
+ store: null,
+ /** @type {DownloadManager} */
+ downloadManager: null,
+ /** @type {EventBus} */
+ eventBus: null,
+ pageRotation: 0,
+ isInitialViewSet: false,
+ animationStartedPromise: null,
+ preferenceSidebarViewOnLoad: SidebarView.NONE,
+ preferencePdfBugEnabled: false,
+ preferenceShowPreviousViewOnLoad: true,
+ preferenceDefaultZoomValue: '',
+ preferenceDisablePageLabels: false,
+ isViewerEmbedded: window.parent !== window,
+ url: '',
+ baseUrl: '',
+ externalServices: DefaultExernalServices,
+ hasPageLabels: false,
+ // called once when the document is loaded
+ initialize: function pdfViewInitialize(appConfig) {
+ configure(pdfjsLib.PDFJS);
+ this.appConfig = appConfig;
+ var eventBus = appConfig.eventBus || getGlobalEventBus();
+ this.eventBus = eventBus;
+ this.bindEvents();
+ var pdfRenderingQueue = new PDFRenderingQueue();
+ pdfRenderingQueue.onIdle = this.cleanup.bind(this);
+ this.pdfRenderingQueue = pdfRenderingQueue;
+ var pdfLinkService = new PDFLinkService({ eventBus: eventBus });
+ this.pdfLinkService = pdfLinkService;
+ var downloadManager = this.externalServices.createDownloadManager();
+ this.downloadManager = downloadManager;
+ var container = appConfig.mainContainer;
+ var viewer = appConfig.viewerContainer;
+ this.pdfViewer = new PDFViewer({
+ container: container,
+ viewer: viewer,
+ eventBus: eventBus,
+ renderingQueue: pdfRenderingQueue,
+ linkService: pdfLinkService,
+ downloadManager: downloadManager,
+ enhanceTextSelection: false,
+ renderInteractiveForms: false
+ });
+ pdfRenderingQueue.setViewer(this.pdfViewer);
+ pdfLinkService.setViewer(this.pdfViewer);
+ var thumbnailContainer = appConfig.sidebar.thumbnailView;
+ this.pdfThumbnailViewer = new PDFThumbnailViewer({
+ container: thumbnailContainer,
+ renderingQueue: pdfRenderingQueue,
+ linkService: pdfLinkService
+ });
+ pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
+ Preferences.initialize();
+ this.preferences = Preferences;
+ this.pdfHistory = new PDFHistory({
+ linkService: pdfLinkService,
+ eventBus: this.eventBus
+ });
+ pdfLinkService.setHistory(this.pdfHistory);
+ this.findController = new PDFFindController({ pdfViewer: this.pdfViewer });
+ this.findController.onUpdateResultsCount = function (matchCount) {
+ if (this.supportsIntegratedFind) {
+ return;
+ }
+ this.findBar.updateResultsCount(matchCount);
+ }.bind(this);
+ this.findController.onUpdateState = function (state, previous, matchCount) {
+ if (this.supportsIntegratedFind) {
+ this.externalServices.updateFindControlState({
+ result: state,
+ findPrevious: previous
+ });
+ } else {
+ this.findBar.updateUIState(state, previous, matchCount);
+ }
+ }.bind(this);
+ this.pdfViewer.setFindController(this.findController);
+ // FIXME better PDFFindBar constructor parameters
+ var findBarConfig = Object.create(appConfig.findBar);
+ findBarConfig.findController = this.findController;
+ findBarConfig.eventBus = this.eventBus;
+ this.findBar = new PDFFindBar(findBarConfig);
+ this.overlayManager = OverlayManager;
+ this.handTool = new HandTool({
+ container: container,
+ eventBus: this.eventBus
+ });
+ this.pdfDocumentProperties = new PDFDocumentProperties(appConfig.documentProperties);
+ this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, container, eventBus);
+ if (this.supportsFullscreen) {
+ this.pdfPresentationMode = new PDFPresentationMode({
+ container: container,
+ viewer: viewer,
+ pdfViewer: this.pdfViewer,
+ eventBus: this.eventBus,
+ contextMenuItems: appConfig.fullscreen
+ });
+ }
+ this.passwordPrompt = new PasswordPrompt(appConfig.passwordOverlay);
+ this.pdfOutlineViewer = new PDFOutlineViewer({
+ container: appConfig.sidebar.outlineView,
+ eventBus: this.eventBus,
+ linkService: pdfLinkService
+ });
+ this.pdfAttachmentViewer = new PDFAttachmentViewer({
+ container: appConfig.sidebar.attachmentsView,
+ eventBus: this.eventBus,
+ downloadManager: downloadManager
+ });
+ // FIXME better PDFSidebar constructor parameters
+ var sidebarConfig = Object.create(appConfig.sidebar);
+ sidebarConfig.pdfViewer = this.pdfViewer;
+ sidebarConfig.pdfThumbnailViewer = this.pdfThumbnailViewer;
+ sidebarConfig.pdfOutlineViewer = this.pdfOutlineViewer;
+ sidebarConfig.eventBus = this.eventBus;
+ this.pdfSidebar = new PDFSidebar(sidebarConfig);
+ this.pdfSidebar.onToggled = this.forceRendering.bind(this);
+ var self = this;
+ var PDFJS = pdfjsLib.PDFJS;
+ var initializedPromise = Promise.all([
+ Preferences.get('enableWebGL').then(function resolved(value) {
+ PDFJS.disableWebGL = !value;
+ }),
+ Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
+ self.preferenceSidebarViewOnLoad = value;
+ }),
+ Preferences.get('pdfBugEnabled').then(function resolved(value) {
+ self.preferencePdfBugEnabled = value;
+ }),
+ Preferences.get('showPreviousViewOnLoad').then(function resolved(value) {
+ self.preferenceShowPreviousViewOnLoad = value;
+ }),
+ Preferences.get('defaultZoomValue').then(function resolved(value) {
+ self.preferenceDefaultZoomValue = value;
+ }),
+ Preferences.get('enhanceTextSelection').then(function resolved(value) {
+ // TODO: Move the initialization and fetching of `Preferences` to occur
+ // before the various viewer components are initialized.
+ //
+ // This was attempted in: https://github.com/mozilla/pdf.js/pull/7586,
+ // but it had to be backed out since it violated implicit assumptions
+ // about some viewer components being synchronously available.
+ //
+ // NOTE: This hack works since the `enhanceTextSelection` option is not
+ // needed until `PDFViewer.setDocument` has been called.
+ self.pdfViewer.enhanceTextSelection = value;
+ }),
+ Preferences.get('disableTextLayer').then(function resolved(value) {
+ if (PDFJS.disableTextLayer === true) {
+ return;
+ }
+ PDFJS.disableTextLayer = value;
+ }),
+ Preferences.get('disableRange').then(function resolved(value) {
+ if (PDFJS.disableRange === true) {
+ return;
+ }
+ PDFJS.disableRange = value;
+ }),
+ Preferences.get('disableStream').then(function resolved(value) {
+ if (PDFJS.disableStream === true) {
+ return;
+ }
+ PDFJS.disableStream = value;
+ }),
+ Preferences.get('disableAutoFetch').then(function resolved(value) {
+ PDFJS.disableAutoFetch = value;
+ }),
+ Preferences.get('disableFontFace').then(function resolved(value) {
+ if (PDFJS.disableFontFace === true) {
+ return;
+ }
+ PDFJS.disableFontFace = value;
+ }),
+ Preferences.get('useOnlyCssZoom').then(function resolved(value) {
+ PDFJS.useOnlyCssZoom = value;
+ }),
+ Preferences.get('externalLinkTarget').then(function resolved(value) {
+ if (PDFJS.isExternalLinkTargetSet()) {
+ return;
+ }
+ PDFJS.externalLinkTarget = value;
+ }),
+ Preferences.get('renderInteractiveForms').then(function resolved(value) {
+ // TODO: Like the `enhanceTextSelection` preference, move the
+ // initialization and fetching of `Preferences` to occur
+ // before the various viewer components are initialized.
+ self.pdfViewer.renderInteractiveForms = value;
+ }),
+ Preferences.get('disablePageLabels').then(function resolved(value) {
+ self.preferenceDisablePageLabels = value;
+ })
+ ]).catch(function (reason) {
+ });
+ return initializedPromise.then(function () {
+ if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
+ // Prevent external links from "replacing" the viewer,
+ // when it's embedded in e.g. an iframe or an object.
+ PDFJS.externalLinkTarget = PDFJS.LinkTarget.TOP;
+ }
+ self.initialized = true;
+ });
+ },
+ run: function pdfViewRun(config) {
+ this.initialize(config).then(webViewerInitialized);
+ },
+ zoomIn: function pdfViewZoomIn(ticks) {
+ var newScale = this.pdfViewer.currentScale;
+ do {
+ newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
+ newScale = Math.ceil(newScale * 10) / 10;
+ newScale = Math.min(MAX_SCALE, newScale);
+ } while (--ticks > 0 && newScale < MAX_SCALE);
+ this.pdfViewer.currentScaleValue = newScale;
+ },
+ zoomOut: function pdfViewZoomOut(ticks) {
+ var newScale = this.pdfViewer.currentScale;
+ do {
+ newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
+ newScale = Math.floor(newScale * 10) / 10;
+ newScale = Math.max(MIN_SCALE, newScale);
+ } while (--ticks > 0 && newScale > MIN_SCALE);
+ this.pdfViewer.currentScaleValue = newScale;
+ },
+ get pagesCount() {
+ return this.pdfDocument ? this.pdfDocument.numPages : 0;
+ },
+ set page(val) {
+ this.pdfViewer.currentPageNumber = val;
+ },
+ get page() {
+ return this.pdfViewer.currentPageNumber;
+ },
+ get printing() {
+ return !!this.printService;
+ },
+ get supportsPrinting() {
+ return PDFPrintServiceFactory.instance.supportsPrinting;
+ },
+ get supportsFullscreen() {
+ var support;
+ support = document.fullscreenEnabled === true || document.mozFullScreenEnabled === true;
+ if (support && pdfjsLib.PDFJS.disableFullscreen === true) {
+ support = false;
+ }
+ return pdfjsLib.shadow(this, 'supportsFullscreen', support);
+ },
+ get supportsIntegratedFind() {
+ return this.externalServices.supportsIntegratedFind;
+ },
+ get supportsDocumentFonts() {
+ return this.externalServices.supportsDocumentFonts;
+ },
+ get supportsDocumentColors() {
+ return this.externalServices.supportsDocumentColors;
+ },
+ get loadingBar() {
+ var bar = new ProgressBar('#loadingBar', {});
+ return pdfjsLib.shadow(this, 'loadingBar', bar);
+ },
+ get supportedMouseWheelZoomModifierKeys() {
+ return this.externalServices.supportedMouseWheelZoomModifierKeys;
+ },
+ initPassiveLoading: function pdfViewInitPassiveLoading() {
+ this.externalServices.initPassiveLoading({
+ onOpenWithTransport: function (url, length, transport) {
+ PDFViewerApplication.open(url, { range: transport });
+ if (length) {
+ PDFViewerApplication.pdfDocumentProperties.setFileSize(length);
+ }
+ },
+ onOpenWithData: function (data) {
+ PDFViewerApplication.open(data);
+ },
+ onOpenWithURL: function (url, length, originalURL) {
+ var file = url, args = null;
+ if (length !== undefined) {
+ args = { length: length };
+ }
+ if (originalURL !== undefined) {
+ file = {
+ file: url,
+ originalURL: originalURL
+ };
+ }
+ PDFViewerApplication.open(file, args);
+ },
+ onError: function (e) {
+ PDFViewerApplication.error(mozL10n.get('loading_error', null, 'An error occurred while loading the PDF.'), e);
+ },
+ onProgress: function (loaded, total) {
+ PDFViewerApplication.progress(loaded / total);
+ }
+ });
+ },
+ setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
+ this.url = url;
+ this.baseUrl = url.split('#')[0];
+ try {
+ this.setTitle(decodeURIComponent(pdfjsLib.getFilenameFromUrl(url)) || url);
+ } catch (e) {
+ // decodeURIComponent may throw URIError,
+ // fall back to using the unprocessed url in that case
+ this.setTitle(url);
+ }
+ },
+ setTitle: function pdfViewSetTitle(title) {
+ if (this.isViewerEmbedded) {
+ // Embedded PDF viewers should not be changing their parent page's title.
+ return;
+ }
+ document.title = title;
+ },
+ /**
+ * Closes opened PDF document.
+ * @returns {Promise} - Returns the promise, which is resolved when all
+ * destruction is completed.
+ */
+ close: function pdfViewClose() {
+ var errorWrapper = this.appConfig.errorWrapper.container;
+ errorWrapper.setAttribute('hidden', 'true');
+ if (!this.pdfLoadingTask) {
+ return Promise.resolve();
+ }
+ var promise = this.pdfLoadingTask.destroy();
+ this.pdfLoadingTask = null;
+ if (this.pdfDocument) {
+ this.pdfDocument = null;
+ this.pdfThumbnailViewer.setDocument(null);
+ this.pdfViewer.setDocument(null);
+ this.pdfLinkService.setDocument(null, null);
+ }
+ this.store = null;
+ this.isInitialViewSet = false;
+ this.hasPageLabels = false;
+ this.pdfSidebar.reset();
+ this.pdfOutlineViewer.reset();
+ this.pdfAttachmentViewer.reset();
+ this.findController.reset();
+ this.findBar.reset();
+ if (typeof PDFBug !== 'undefined') {
+ PDFBug.cleanup();
+ }
+ return promise;
+ },
+ /**
+ * Opens PDF document specified by URL or array with additional arguments.
+ * @param {string|TypedArray|ArrayBuffer} file - PDF location or binary data.
+ * @param {Object} args - (optional) Additional arguments for the getDocument
+ * call, e.g. HTTP headers ('httpHeaders') or
+ * alternative data transport ('range').
+ * @returns {Promise} - Returns the promise, which is resolved when document
+ * is opened.
+ */
+ open: function pdfViewOpen(file, args) {
+ if (this.pdfLoadingTask) {
+ // We need to destroy already opened document.
+ return this.close().then(function () {
+ // Reload the preferences if a document was previously opened.
+ Preferences.reload();
+ // ... and repeat the open() call.
+ return this.open(file, args);
+ }.bind(this));
+ }
+ var parameters = Object.create(null), scale;
+ if (typeof file === 'string') {
+ // URL
+ this.setTitleUsingUrl(file);
+ parameters.url = file;
+ } else if (file && 'byteLength' in file) {
+ // ArrayBuffer
+ parameters.data = file;
+ } else if (file.url && file.originalUrl) {
+ this.setTitleUsingUrl(file.originalUrl);
+ parameters.url = file.url;
+ }
+ parameters.docBaseUrl = this.baseUrl;
+ if (args) {
+ for (var prop in args) {
+ parameters[prop] = args[prop];
+ }
+ if (args.scale) {
+ scale = args.scale;
+ }
+ if (args.length) {
+ this.pdfDocumentProperties.setFileSize(args.length);
+ }
+ }
+ var self = this;
+ self.downloadComplete = false;
+ var loadingTask = pdfjsLib.getDocument(parameters);
+ this.pdfLoadingTask = loadingTask;
+ loadingTask.onPassword = function passwordNeeded(updateCallback, reason) {
+ self.passwordPrompt.setUpdateCallback(updateCallback, reason);
+ self.passwordPrompt.open();
+ };
+ loadingTask.onProgress = function getDocumentProgress(progressData) {
+ self.progress(progressData.loaded / progressData.total);
+ };
+ // Listen for unsupported features to trigger the fallback UI.
+ loadingTask.onUnsupportedFeature = this.fallback.bind(this);
+ return loadingTask.promise.then(function getDocumentCallback(pdfDocument) {
+ self.load(pdfDocument, scale);
+ }, function getDocumentError(exception) {
+ var message = exception && exception.message;
+ var loadingErrorMessage = mozL10n.get('loading_error', null, 'An error occurred while loading the PDF.');
+ if (exception instanceof pdfjsLib.InvalidPDFException) {
+ // change error message also for other builds
+ loadingErrorMessage = mozL10n.get('invalid_file_error', null, 'Invalid or corrupted PDF file.');
+ } else if (exception instanceof pdfjsLib.MissingPDFException) {
+ // special message for missing PDF's
+ loadingErrorMessage = mozL10n.get('missing_file_error', null, 'Missing PDF file.');
+ } else if (exception instanceof pdfjsLib.UnexpectedResponseException) {
+ loadingErrorMessage = mozL10n.get('unexpected_response_error', null, 'Unexpected server response.');
+ }
+ var moreInfo = { message: message };
+ self.error(loadingErrorMessage, moreInfo);
+ throw new Error(loadingErrorMessage);
+ });
+ },
+ download: function pdfViewDownload() {
+ function downloadByUrl() {
+ downloadManager.downloadUrl(url, filename);
+ }
+ var url = this.baseUrl;
+ var filename = getPDFFileNameFromURL(url);
+ var downloadManager = this.downloadManager;
+ downloadManager.onerror = function (err) {
+ // This error won't really be helpful because it's likely the
+ // fallback won't work either (or is already open).
+ PDFViewerApplication.error('PDF failed to download.');
+ };
+ if (!this.pdfDocument) {
+ // the PDF is not ready yet
+ downloadByUrl();
+ return;
+ }
+ if (!this.downloadComplete) {
+ // the PDF is still downloading
+ downloadByUrl();
+ return;
+ }
+ this.pdfDocument.getData().then(function getDataSuccess(data) {
+ var blob = pdfjsLib.createBlob(data, 'application/pdf');
+ downloadManager.download(blob, url, filename);
+ }, downloadByUrl).then(null, downloadByUrl);
+ },
+ fallback: function pdfViewFallback(featureId) {
+ // Only trigger the fallback once so we don't spam the user with messages
+ // for one PDF.
+ if (this.fellback) {
+ return;
+ }
+ this.fellback = true;
+ this.externalServices.fallback({
+ featureId: featureId,
+ url: this.baseUrl
+ }, function response(download) {
+ if (!download) {
+ return;
+ }
+ PDFViewerApplication.download();
+ });
+ },
+ /**
+ * Show the error box.
+ * @param {String} message A message that is human readable.
+ * @param {Object} moreInfo (optional) Further information about the error
+ * that is more technical. Should have a 'message'
+ * and optionally a 'stack' property.
+ */
+ error: function pdfViewError(message, moreInfo) {
+ var moreInfoText = mozL10n.get('error_version_info', {
+ version: pdfjsLib.version || '?',
+ build: pdfjsLib.build || '?'
+ }, 'PDF.js v{{version}} (build: {{build}})') + '\n';
+ if (moreInfo) {
+ moreInfoText += mozL10n.get('error_message', { message: moreInfo.message }, 'Message: {{message}}');
+ if (moreInfo.stack) {
+ moreInfoText += '\n' + mozL10n.get('error_stack', { stack: moreInfo.stack }, 'Stack: {{stack}}');
+ } else {
+ if (moreInfo.filename) {
+ moreInfoText += '\n' + mozL10n.get('error_file', { file: moreInfo.filename }, 'File: {{file}}');
+ }
+ if (moreInfo.lineNumber) {
+ moreInfoText += '\n' + mozL10n.get('error_line', { line: moreInfo.lineNumber }, 'Line: {{line}}');
+ }
+ }
+ }
+ console.error(message + '\n' + moreInfoText);
+ this.fallback();
+ },
+ progress: function pdfViewProgress(level) {
+ var percent = Math.round(level * 100);
+ // When we transition from full request to range requests, it's possible
+ // that we discard some of the loaded data. This can cause the loading
+ // bar to move backwards. So prevent this by only updating the bar if it
+ // increases.
+ if (percent > this.loadingBar.percent || isNaN(percent)) {
+ this.loadingBar.percent = percent;
+ // When disableAutoFetch is enabled, it's not uncommon for the entire file
+ // to never be fetched (depends on e.g. the file structure). In this case
+ // the loading bar will not be completely filled, nor will it be hidden.
+ // To prevent displaying a partially filled loading bar permanently, we
+ // hide it when no data has been loaded during a certain amount of time.
+ if (pdfjsLib.PDFJS.disableAutoFetch && percent) {
+ if (this.disableAutoFetchLoadingBarTimeout) {
+ clearTimeout(this.disableAutoFetchLoadingBarTimeout);
+ this.disableAutoFetchLoadingBarTimeout = null;
+ }
+ this.loadingBar.show();
+ this.disableAutoFetchLoadingBarTimeout = setTimeout(function () {
+ this.loadingBar.hide();
+ this.disableAutoFetchLoadingBarTimeout = null;
+ }.bind(this), DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
+ }
+ }
+ },
+ load: function pdfViewLoad(pdfDocument, scale) {
+ var self = this;
+ scale = scale || UNKNOWN_SCALE;
+ this.pdfDocument = pdfDocument;
+ this.pdfDocumentProperties.setDocumentAndUrl(pdfDocument, this.url);
+ var downloadedPromise = pdfDocument.getDownloadInfo().then(function () {
+ self.downloadComplete = true;
+ self.loadingBar.hide();
+ });
+ this._updateUIToolbar({ resetNumPages: true });
+ var id = this.documentFingerprint = pdfDocument.fingerprint;
+ var store = this.store = new ViewHistory(id);
+ var baseDocumentUrl;
+ baseDocumentUrl = this.baseUrl;
+ this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
+ var pdfViewer = this.pdfViewer;
+ pdfViewer.currentScale = scale;
+ pdfViewer.setDocument(pdfDocument);
+ var firstPagePromise = pdfViewer.firstPagePromise;
+ var pagesPromise = pdfViewer.pagesPromise;
+ var onePageRendered = pdfViewer.onePageRendered;
+ this.pageRotation = 0;
+ var pdfThumbnailViewer = this.pdfThumbnailViewer;
+ pdfThumbnailViewer.setDocument(pdfDocument);
+ firstPagePromise.then(function (pdfPage) {
+ downloadedPromise.then(function () {
+ self.eventBus.dispatch('documentload', { source: self });
+ });
+ self.loadingBar.setWidth(self.appConfig.viewerContainer);
+ if (!pdfjsLib.PDFJS.disableHistory && !self.isViewerEmbedded) {
+ // The browsing history is only enabled when the viewer is standalone,
+ // i.e. not when it is embedded in a web page.
+ if (!self.preferenceShowPreviousViewOnLoad) {
+ self.pdfHistory.clearHistoryState();
+ }
+ self.pdfHistory.initialize(self.documentFingerprint);
+ if (self.pdfHistory.initialDestination) {
+ self.initialDestination = self.pdfHistory.initialDestination;
+ } else if (self.pdfHistory.initialBookmark) {
+ self.initialBookmark = self.pdfHistory.initialBookmark;
+ }
+ }
+ var initialParams = {
+ destination: self.initialDestination,
+ bookmark: self.initialBookmark,
+ hash: null
+ };
+ store.initializedPromise.then(function resolved() {
+ var storedHash = null, sidebarView = null;
+ if (self.preferenceShowPreviousViewOnLoad && store.get('exists', false)) {
+ var pageNum = store.get('page', '1');
+ var zoom = self.preferenceDefaultZoomValue || store.get('zoom', DEFAULT_SCALE_VALUE);
+ var left = store.get('scrollLeft', '0');
+ var top = store.get('scrollTop', '0');
+ storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' + left + ',' + top;
+ sidebarView = store.get('sidebarView', SidebarView.NONE);
+ } else if (self.preferenceDefaultZoomValue) {
+ storedHash = 'page=1&zoom=' + self.preferenceDefaultZoomValue;
+ }
+ self.setInitialView(storedHash, {
+ scale: scale,
+ sidebarView: sidebarView
+ });
+ initialParams.hash = storedHash;
+ // Make all navigation keys work on document load,
+ // unless the viewer is embedded in a web page.
+ if (!self.isViewerEmbedded) {
+ self.pdfViewer.focus();
+ }
+ }, function rejected(reason) {
+ console.error(reason);
+ self.setInitialView(null, { scale: scale });
+ });
+ // For documents with different page sizes,
+ // ensure that the correct location becomes visible on load.
+ pagesPromise.then(function resolved() {
+ if (!initialParams.destination && !initialParams.bookmark && !initialParams.hash) {
+ return;
+ }
+ if (self.hasEqualPageSizes) {
+ return;
+ }
+ self.initialDestination = initialParams.destination;
+ self.initialBookmark = initialParams.bookmark;
+ self.pdfViewer.currentScaleValue = self.pdfViewer.currentScaleValue;
+ self.setInitialView(initialParams.hash);
+ });
+ });
+ pdfDocument.getPageLabels().then(function (labels) {
+ if (!labels || self.preferenceDisablePageLabels) {
+ return;
+ }
+ var i = 0, numLabels = labels.length;
+ if (numLabels !== self.pagesCount) {
+ console.error('The number of Page Labels does not match ' + 'the number of pages in the document.');
+ return;
+ }
+ // Ignore page labels that correspond to standard page numbering.
+ while (i < numLabels && labels[i] === (i + 1).toString()) {
+ i++;
+ }
+ if (i === numLabels) {
+ return;
+ }
+ pdfViewer.setPageLabels(labels);
+ pdfThumbnailViewer.setPageLabels(labels);
+ self.hasPageLabels = true;
+ self._updateUIToolbar({ resetNumPages: true });
+ });
+ pagesPromise.then(function () {
+ if (self.supportsPrinting) {
+ pdfDocument.getJavaScript().then(function (javaScript) {
+ if (javaScript.length) {
+ console.warn('Warning: JavaScript is not supported');
+ self.fallback(pdfjsLib.UNSUPPORTED_FEATURES.javaScript);
+ }
+ // Hack to support auto printing.
+ var regex = /\bprint\s*\(/;
+ for (var i = 0, ii = javaScript.length; i < ii; i++) {
+ var js = javaScript[i];
+ if (js && regex.test(js)) {
+ setTimeout(function () {
+ window.print();
+ });
+ return;
+ }
+ }
+ });
+ }
+ });
+ Promise.all([
+ onePageRendered,
+ this.animationStartedPromise
+ ]).then(function () {
+ pdfDocument.getOutline().then(function (outline) {
+ self.pdfOutlineViewer.render({ outline: outline });
+ });
+ pdfDocument.getAttachments().then(function (attachments) {
+ self.pdfAttachmentViewer.render({ attachments: attachments });
+ });
+ });
+ pdfDocument.getMetadata().then(function (data) {
+ var info = data.info, metadata = data.metadata;
+ self.documentInfo = info;
+ self.metadata = metadata;
+ // Provides some basic debug information
+ console.log('PDF ' + pdfDocument.fingerprint + ' [' + info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() + ' / ' + (info.Creator || '-').trim() + ']' + ' (PDF.js: ' + (pdfjsLib.version || '-') + (!pdfjsLib.PDFJS.disableWebGL ? ' [WebGL]' : '') + ')');
+ var pdfTitle;
+ if (metadata && metadata.has('dc:title')) {
+ var title = metadata.get('dc:title');
+ // Ghostscript sometimes return 'Untitled', sets the title to 'Untitled'
+ if (title !== 'Untitled') {
+ pdfTitle = title;
+ }
+ }
+ if (!pdfTitle && info && info['Title']) {
+ pdfTitle = info['Title'];
+ }
+ if (pdfTitle) {
+ self.setTitle(pdfTitle + ' - ' + document.title);
+ }
+ if (info.IsAcroFormPresent) {
+ console.warn('Warning: AcroForm/XFA is not supported');
+ self.fallback(pdfjsLib.UNSUPPORTED_FEATURES.forms);
+ }
+ var versionId = String(info.PDFFormatVersion).slice(-1) | 0;
+ var generatorId = 0;
+ var KNOWN_GENERATORS = [
+ 'acrobat distiller',
+ 'acrobat pdfwriter',
+ 'adobe livecycle',
+ 'adobe pdf library',
+ 'adobe photoshop',
+ 'ghostscript',
+ 'tcpdf',
+ 'cairo',
+ 'dvipdfm',
+ 'dvips',
+ 'pdftex',
+ 'pdfkit',
+ 'itext',
+ 'prince',
+ 'quarkxpress',
+ 'mac os x',
+ 'microsoft',
+ 'openoffice',
+ 'oracle',
+ 'luradocument',
+ 'pdf-xchange',
+ 'antenna house',
+ 'aspose.cells',
+ 'fpdf'
+ ];
+ if (info.Producer) {
+ KNOWN_GENERATORS.some(function (generator, s, i) {
+ if (generator.indexOf(s) < 0) {
+ return false;
+ }
+ generatorId = i + 1;
+ return true;
+ }.bind(null, info.Producer.toLowerCase()));
+ }
+ var formType = !info.IsAcroFormPresent ? null : info.IsXFAPresent ? 'xfa' : 'acroform';
+ self.externalServices.reportTelemetry({
+ type: 'documentInfo',
+ version: versionId,
+ generator: generatorId,
+ formType: formType
+ });
+ });
+ },
+ setInitialView: function pdfViewSetInitialView(storedHash, options) {
+ var scale = options && options.scale;
+ var sidebarView = options && options.sidebarView;
+ this.isInitialViewSet = true;
+ this.pdfSidebar.setInitialView(this.preferenceSidebarViewOnLoad || sidebarView | 0);
+ if (this.initialDestination) {
+ this.pdfLinkService.navigateTo(this.initialDestination);
+ this.initialDestination = null;
+ } else if (this.initialBookmark) {
+ this.pdfLinkService.setHash(this.initialBookmark);
+ this.pdfHistory.push({ hash: this.initialBookmark }, true);
+ this.initialBookmark = null;
+ } else if (storedHash) {
+ this.pdfLinkService.setHash(storedHash);
+ } else if (scale) {
+ this.pdfViewer.currentScaleValue = scale;
+ this.page = 1;
+ }
+ if (!this.pdfViewer.currentScaleValue) {
+ // Scale was not initialized: invalid bookmark or scale was not specified.
+ // Setting the default one.
+ this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
+ }
+ },
+ cleanup: function pdfViewCleanup() {
+ if (!this.pdfDocument) {
+ return;
+ }
+ // run cleanup when document is loaded
+ this.pdfViewer.cleanup();
+ this.pdfThumbnailViewer.cleanup();
+ this.pdfDocument.cleanup();
+ },
+ forceRendering: function pdfViewForceRendering() {
+ this.pdfRenderingQueue.printing = this.printing;
+ this.pdfRenderingQueue.isThumbnailViewEnabled = this.pdfSidebar.isThumbnailViewVisible;
+ this.pdfRenderingQueue.renderHighestPriority();
+ },
+ beforePrint: function pdfViewSetupBeforePrint() {
+ if (this.printService) {
+ // There is no way to suppress beforePrint/afterPrint events,
+ // but PDFPrintService may generate double events -- this will ignore
+ // the second event that will be coming from native window.print().
+ return;
+ }
+ if (!this.supportsPrinting) {
+ var printMessage = mozL10n.get('printing_not_supported', null, 'Warning: Printing is not fully supported by this browser.');
+ this.error(printMessage);
+ return;
+ }
+ // The beforePrint is a sync method and we need to know layout before
+ // returning from this method. Ensure that we can get sizes of the pages.
+ if (!this.pdfViewer.pageViewsReady) {
+ var notReadyMessage = mozL10n.get('printing_not_ready', null, 'Warning: The PDF is not fully loaded for printing.');
+ window.alert(notReadyMessage);
+ return;
+ }
+ var pagesOverview = this.pdfViewer.getPagesOverview();
+ var printContainer = this.appConfig.printContainer;
+ var printService = PDFPrintServiceFactory.instance.createPrintService(this.pdfDocument, pagesOverview, printContainer);
+ this.printService = printService;
+ this.forceRendering();
+ printService.layout();
+ this.externalServices.reportTelemetry({ type: 'print' });
+ },
+ // Whether all pages of the PDF have the same width and height.
+ get hasEqualPageSizes() {
+ var firstPage = this.pdfViewer.getPageView(0);
+ for (var i = 1, ii = this.pagesCount; i < ii; ++i) {
+ var pageView = this.pdfViewer.getPageView(i);
+ if (pageView.width !== firstPage.width || pageView.height !== firstPage.height) {
+ return false;
+ }
+ }
+ return true;
+ },
+ afterPrint: function pdfViewSetupAfterPrint() {
+ if (this.printService) {
+ this.printService.destroy();
+ this.printService = null;
+ }
+ this.forceRendering();
+ },
+ rotatePages: function pdfViewRotatePages(delta) {
+ var pageNumber = this.page;
+ this.pageRotation = (this.pageRotation + 360 + delta) % 360;
+ this.pdfViewer.pagesRotation = this.pageRotation;
+ this.pdfThumbnailViewer.pagesRotation = this.pageRotation;
+ this.forceRendering();
+ this.pdfViewer.currentPageNumber = pageNumber;
+ },
+ requestPresentationMode: function pdfViewRequestPresentationMode() {
+ if (!this.pdfPresentationMode) {
+ return;
+ }
+ this.pdfPresentationMode.request();
+ },
+ /**
+ * @typedef UpdateUIToolbarParameters
+ * @property {number} pageNumber
+ * @property {string} pageLabel
+ * @property {string} scaleValue
+ * @property {number} scale
+ * @property {boolean} resetNumPages
+ */
+ /**
+ * @param {Object} UpdateUIToolbarParameters
+ * @private
+ */
+ _updateUIToolbar: function (params) {
+ function selectScaleOption(value, scale) {
+ var options = toolbarConfig.scaleSelect.options;
+ var predefinedValueFound = false;
+ for (var i = 0, ii = options.length; i < ii; i++) {
+ var option = options[i];
+ if (option.value !== value) {
+ option.selected = false;
+ continue;
+ }
+ option.selected = true;
+ predefinedValueFound = true;
+ }
+ if (!predefinedValueFound) {
+ var customScale = Math.round(scale * 10000) / 100;
+ toolbarConfig.customScaleOption.textContent = mozL10n.get('page_scale_percent', { scale: customScale }, '{{scale}}%');
+ toolbarConfig.customScaleOption.selected = true;
+ }
+ }
+ var pageNumber = params.pageNumber || this.pdfViewer.currentPageNumber;
+ var scaleValue = (params.scaleValue || params.scale || this.pdfViewer.currentScaleValue || DEFAULT_SCALE_VALUE).toString();
+ var scale = params.scale || this.pdfViewer.currentScale;
+ var resetNumPages = params.resetNumPages || false;
+ var toolbarConfig = this.appConfig.toolbar;
+ var pagesCount = this.pagesCount;
+ if (resetNumPages) {
+ if (this.hasPageLabels) {
+ toolbarConfig.pageNumber.type = 'text';
+ } else {
+ toolbarConfig.pageNumber.type = 'number';
+ toolbarConfig.numPages.textContent = mozL10n.get('of_pages', { pagesCount: pagesCount }, 'of {{pagesCount}}');
+ }
+ toolbarConfig.pageNumber.max = pagesCount;
+ }
+ if (this.hasPageLabels) {
+ toolbarConfig.pageNumber.value = params.pageLabel || this.pdfViewer.currentPageLabel;
+ toolbarConfig.numPages.textContent = mozL10n.get('page_of_pages', {
+ pageNumber: pageNumber,
+ pagesCount: pagesCount
+ }, '({{pageNumber}} of {{pagesCount}})');
+ } else {
+ toolbarConfig.pageNumber.value = pageNumber;
+ }
+ toolbarConfig.previous.disabled = pageNumber <= 1;
+ toolbarConfig.next.disabled = pageNumber >= pagesCount;
+ toolbarConfig.firstPage.disabled = pageNumber <= 1;
+ toolbarConfig.lastPage.disabled = pageNumber >= pagesCount;
+ toolbarConfig.zoomOut.disabled = scale <= MIN_SCALE;
+ toolbarConfig.zoomIn.disabled = scale >= MAX_SCALE;
+ selectScaleOption(scaleValue, scale);
+ },
+ bindEvents: function pdfViewBindEvents() {
+ var eventBus = this.eventBus;
+ eventBus.on('resize', webViewerResize);
+ eventBus.on('localized', webViewerLocalized);
+ eventBus.on('hashchange', webViewerHashchange);
+ eventBus.on('beforeprint', this.beforePrint.bind(this));
+ eventBus.on('afterprint', this.afterPrint.bind(this));
+ eventBus.on('pagerendered', webViewerPageRendered);
+ eventBus.on('textlayerrendered', webViewerTextLayerRendered);
+ eventBus.on('updateviewarea', webViewerUpdateViewarea);
+ eventBus.on('pagechanging', webViewerPageChanging);
+ eventBus.on('scalechanging', webViewerScaleChanging);
+ eventBus.on('sidebarviewchanged', webViewerSidebarViewChanged);
+ eventBus.on('pagemode', webViewerPageMode);
+ eventBus.on('namedaction', webViewerNamedAction);
+ eventBus.on('presentationmodechanged', webViewerPresentationModeChanged);
+ eventBus.on('presentationmode', webViewerPresentationMode);
+ eventBus.on('openfile', webViewerOpenFile);
+ eventBus.on('print', webViewerPrint);
+ eventBus.on('download', webViewerDownload);
+ eventBus.on('firstpage', webViewerFirstPage);
+ eventBus.on('lastpage', webViewerLastPage);
+ eventBus.on('rotatecw', webViewerRotateCw);
+ eventBus.on('rotateccw', webViewerRotateCcw);
+ eventBus.on('documentproperties', webViewerDocumentProperties);
+ eventBus.on('find', webViewerFind);
+ eventBus.on('findfromurlhash', webViewerFindFromUrlHash);
+ }
+ };
+ var validateFileURL;
+ function loadAndEnablePDFBug(enabledTabs) {
+ return new Promise(function (resolve, reject) {
+ var appConfig = PDFViewerApplication.appConfig;
+ var script = document.createElement('script');
+ script.src = appConfig.debuggerScriptPath;
+ script.onload = function () {
+ PDFBug.enable(enabledTabs);
+ PDFBug.init(pdfjsLib, appConfig.mainContainer);
+ resolve();
+ };
+ script.onerror = function () {
+ reject(new Error('Cannot load debugger at ' + script.src));
+ };
+ (document.getElementsByTagName('head')[0] || document.body).appendChild(script);
+ });
+ }
+ function webViewerInitialized() {
+ var file;
+ file = window.location.href.split('#')[0];
+ var waitForBeforeOpening = [];
+ var appConfig = PDFViewerApplication.appConfig;
+ appConfig.toolbar.openFile.setAttribute('hidden', 'true');
+ appConfig.secondaryToolbar.openFileButton.setAttribute('hidden', 'true');
+ var PDFJS = pdfjsLib.PDFJS;
+ if (PDFViewerApplication.preferencePdfBugEnabled) {
+ // Special debugging flags in the hash section of the URL.
+ var hash = document.location.hash.substring(1);
+ var hashParams = parseQueryString(hash);
+ if ('disableworker' in hashParams) {
+ PDFJS.disableWorker = hashParams['disableworker'] === 'true';
+ }
+ if ('disablerange' in hashParams) {
+ PDFJS.disableRange = hashParams['disablerange'] === 'true';
+ }
+ if ('disablestream' in hashParams) {
+ PDFJS.disableStream = hashParams['disablestream'] === 'true';
+ }
+ if ('disableautofetch' in hashParams) {
+ PDFJS.disableAutoFetch = hashParams['disableautofetch'] === 'true';
+ }
+ if ('disablefontface' in hashParams) {
+ PDFJS.disableFontFace = hashParams['disablefontface'] === 'true';
+ }
+ if ('disablehistory' in hashParams) {
+ PDFJS.disableHistory = hashParams['disablehistory'] === 'true';
+ }
+ if ('webgl' in hashParams) {
+ PDFJS.disableWebGL = hashParams['webgl'] !== 'true';
+ }
+ if ('useonlycsszoom' in hashParams) {
+ PDFJS.useOnlyCssZoom = hashParams['useonlycsszoom'] === 'true';
+ }
+ if ('verbosity' in hashParams) {
+ PDFJS.verbosity = hashParams['verbosity'] | 0;
+ }
+ if ('ignorecurrentpositiononzoom' in hashParams) {
+ PDFJS.ignoreCurrentPositionOnZoom = hashParams['ignorecurrentpositiononzoom'] === 'true';
+ }
+ if ('textlayer' in hashParams) {
+ switch (hashParams['textlayer']) {
+ case 'off':
+ PDFJS.disableTextLayer = true;
+ break;
+ case 'visible':
+ case 'shadow':
+ case 'hover':
+ var viewer = appConfig.viewerContainer;
+ viewer.classList.add('textLayer-' + hashParams['textlayer']);
+ break;
+ }
+ }
+ if ('pdfbug' in hashParams) {
+ PDFJS.pdfBug = true;
+ var pdfBug = hashParams['pdfbug'];
+ var enabled = pdfBug.split(',');
+ waitForBeforeOpening.push(loadAndEnablePDFBug(enabled));
+ }
+ }
+ if (!PDFViewerApplication.supportsDocumentFonts) {
+ PDFJS.disableFontFace = true;
+ console.warn(mozL10n.get('web_fonts_disabled', null, 'Web fonts are disabled: unable to use embedded PDF fonts.'));
+ }
+ if (!PDFViewerApplication.supportsPrinting) {
+ appConfig.toolbar.print.classList.add('hidden');
+ appConfig.secondaryToolbar.printButton.classList.add('hidden');
+ }
+ if (!PDFViewerApplication.supportsFullscreen) {
+ appConfig.toolbar.presentationModeButton.classList.add('hidden');
+ appConfig.secondaryToolbar.presentationModeButton.classList.add('hidden');
+ }
+ if (PDFViewerApplication.supportsIntegratedFind) {
+ appConfig.toolbar.viewFind.classList.add('hidden');
+ }
+ // Suppress context menus for some controls
+ appConfig.toolbar.scaleSelect.oncontextmenu = noContextMenuHandler;
+ appConfig.sidebar.mainContainer.addEventListener('transitionend', function (e) {
+ if (e.target === /* mainContainer */
+ this) {
+ PDFViewerApplication.eventBus.dispatch('resize');
+ }
+ }, true);
+ appConfig.sidebar.toggleButton.addEventListener('click', function () {
+ PDFViewerApplication.pdfSidebar.toggle();
+ });
+ appConfig.toolbar.previous.addEventListener('click', function () {
+ PDFViewerApplication.page--;
+ });
+ appConfig.toolbar.next.addEventListener('click', function () {
+ PDFViewerApplication.page++;
+ });
+ appConfig.toolbar.zoomIn.addEventListener('click', function () {
+ PDFViewerApplication.zoomIn();
+ });
+ appConfig.toolbar.zoomOut.addEventListener('click', function () {
+ PDFViewerApplication.zoomOut();
+ });
+ appConfig.toolbar.pageNumber.addEventListener('click', function () {
+ this.select();
+ });
+ appConfig.toolbar.pageNumber.addEventListener('change', function () {
+ var pdfViewer = PDFViewerApplication.pdfViewer;
+ pdfViewer.currentPageLabel = this.value;
+ // Ensure that the page number input displays the correct value, even if the
+ // value entered by the user was invalid (e.g. a floating point number).
+ if (this.value !== pdfViewer.currentPageNumber.toString() && this.value !== pdfViewer.currentPageLabel) {
+ PDFViewerApplication._updateUIToolbar({});
+ }
+ });
+ appConfig.toolbar.scaleSelect.addEventListener('change', function () {
+ if (this.value === 'custom') {
+ return;
+ }
+ PDFViewerApplication.pdfViewer.currentScaleValue = this.value;
+ });
+ appConfig.toolbar.presentationModeButton.addEventListener('click', function (e) {
+ PDFViewerApplication.eventBus.dispatch('presentationmode');
+ });
+ appConfig.toolbar.openFile.addEventListener('click', function (e) {
+ PDFViewerApplication.eventBus.dispatch('openfile');
+ });
+ appConfig.toolbar.print.addEventListener('click', function (e) {
+ PDFViewerApplication.eventBus.dispatch('print');
+ });
+ appConfig.toolbar.download.addEventListener('click', function (e) {
+ PDFViewerApplication.eventBus.dispatch('download');
+ });
+ Promise.all(waitForBeforeOpening).then(function () {
+ webViewerOpenFileViaURL(file);
+ }).catch(function (reason) {
+ PDFViewerApplication.error(mozL10n.get('loading_error', null, 'An error occurred while opening.'), reason);
+ });
+ }
+ var webViewerOpenFileViaURL;
+ webViewerOpenFileViaURL = function webViewerOpenFileViaURL(file) {
+ PDFViewerApplication.setTitleUsingUrl(file);
+ PDFViewerApplication.initPassiveLoading();
+ };
+ function webViewerPageRendered(e) {
+ var pageNumber = e.pageNumber;
+ var pageIndex = pageNumber - 1;
+ var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
+ // If the page is still visible when it has finished rendering,
+ // ensure that the page number input loading indicator is hidden.
+ if (pageNumber === PDFViewerApplication.page) {
+ var pageNumberInput = PDFViewerApplication.appConfig.toolbar.pageNumber;
+ pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
+ }
+ // Prevent errors in the edge-case where the PDF document is removed *before*
+ // the 'pagerendered' event handler is invoked.
+ if (!pageView) {
+ return;
+ }
+ // Use the rendered page to set the corresponding thumbnail image.
+ if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) {
+ var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.getThumbnail(pageIndex);
+ thumbnailView.setImage(pageView);
+ }
+ if (pdfjsLib.PDFJS.pdfBug && Stats.enabled && pageView.stats) {
+ Stats.add(pageNumber, pageView.stats);
+ }
+ if (pageView.error) {
+ PDFViewerApplication.error(mozL10n.get('rendering_error', null, 'An error occurred while rendering the page.'), pageView.error);
+ }
+ PDFViewerApplication.externalServices.reportTelemetry({ type: 'pageInfo' });
+ // It is a good time to report stream and font types.
+ PDFViewerApplication.pdfDocument.getStats().then(function (stats) {
+ PDFViewerApplication.externalServices.reportTelemetry({
+ type: 'documentStats',
+ stats: stats
+ });
+ });
+ }
+ function webViewerTextLayerRendered(e) {
+ if (e.numTextDivs > 0 && !PDFViewerApplication.supportsDocumentColors) {
+ console.error(mozL10n.get('document_colors_not_allowed', null, 'PDF documents are not allowed to use their own colors: ' + '\'Allow pages to choose their own colors\' ' + 'is deactivated in the browser.'));
+ PDFViewerApplication.fallback();
+ }
+ }
+ function webViewerPageMode(e) {
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ // Handle the 'pagemode' hash parameter, see also `PDFLinkService_setHash`.
+ var mode = e.mode, view;
+ switch (mode) {
+ case 'thumbs':
+ view = SidebarView.THUMBS;
+ break;
+ case 'bookmarks':
+ case 'outline':
+ view = SidebarView.OUTLINE;
+ break;
+ case 'attachments':
+ view = SidebarView.ATTACHMENTS;
+ break;
+ case 'none':
+ view = SidebarView.NONE;
+ break;
+ default:
+ console.error('Invalid "pagemode" hash parameter: ' + mode);
+ return;
+ }
+ PDFViewerApplication.pdfSidebar.switchView(view, /* forceOpen = */
+ true);
+ }
+ function webViewerNamedAction(e) {
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ // Processing couple of named actions that might be useful.
+ // See also PDFLinkService.executeNamedAction
+ var action = e.action;
+ switch (action) {
+ case 'GoToPage':
+ PDFViewerApplication.appConfig.toolbar.pageNumber.select();
+ break;
+ case 'Find':
+ if (!PDFViewerApplication.supportsIntegratedFind) {
+ PDFViewerApplication.findBar.toggle();
+ }
+ break;
+ }
+ }
+ function webViewerPresentationModeChanged(e) {
+ var active = e.active;
+ var switchInProgress = e.switchInProgress;
+ PDFViewerApplication.pdfViewer.presentationModeState = switchInProgress ? PresentationModeState.CHANGING : active ? PresentationModeState.FULLSCREEN : PresentationModeState.NORMAL;
+ }
+ function webViewerSidebarViewChanged(e) {
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ PDFViewerApplication.pdfRenderingQueue.isThumbnailViewEnabled = PDFViewerApplication.pdfSidebar.isThumbnailViewVisible;
+ var store = PDFViewerApplication.store;
+ if (!store || !PDFViewerApplication.isInitialViewSet) {
+ // Only update the storage when the document has been loaded *and* rendered.
+ return;
+ }
+ store.initializedPromise.then(function () {
+ store.set('sidebarView', e.view).catch(function () {
+ });
+ });
+ }
+ function webViewerUpdateViewarea(e) {
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ var location = e.location, store = PDFViewerApplication.store;
+ if (store) {
+ store.initializedPromise.then(function () {
+ store.setMultiple({
+ 'exists': true,
+ 'page': location.pageNumber,
+ 'zoom': location.scale,
+ 'scrollLeft': location.left,
+ 'scrollTop': location.top
+ }).catch(function () {
+ });
+ });
+ }
+ var href = PDFViewerApplication.pdfLinkService.getAnchorUrl(location.pdfOpenParams);
+ PDFViewerApplication.appConfig.toolbar.viewBookmark.href = href;
+ PDFViewerApplication.appConfig.secondaryToolbar.viewBookmarkButton.href = href;
+ // Update the current bookmark in the browsing history.
+ PDFViewerApplication.pdfHistory.updateCurrentBookmark(location.pdfOpenParams, location.pageNumber);
+ // Show/hide the loading indicator in the page number input element.
+ var pageNumberInput = PDFViewerApplication.appConfig.toolbar.pageNumber;
+ var currentPage = PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1);
+ if (currentPage.renderingState === RenderingStates.FINISHED) {
+ pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
+ } else {
+ pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR);
+ }
+ }
+ window.addEventListener('resize', function webViewerResize(evt) {
+ if (!PDFViewerApplication.eventBus) {
+ return;
+ }
+ PDFViewerApplication.eventBus.dispatch('resize');
+ });
+ function webViewerResize() {
+ if (PDFViewerApplication.initialized) {
+ var currentScaleValue = PDFViewerApplication.pdfViewer.currentScaleValue;
+ if (currentScaleValue === 'auto' || currentScaleValue === 'page-fit' || currentScaleValue === 'page-width') {
+ // Note: the scale is constant for 'page-actual'.
+ PDFViewerApplication.pdfViewer.currentScaleValue = currentScaleValue;
+ } else if (!currentScaleValue) {
+ // Normally this shouldn't happen, but if the scale wasn't initialized
+ // we set it to the default value in order to prevent any issues.
+ // (E.g. the document being rendered with the wrong scale on load.)
+ PDFViewerApplication.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
+ }
+ PDFViewerApplication.pdfViewer.update();
+ }
+ }
+ window.addEventListener('hashchange', function webViewerHashchange(evt) {
+ var hash = document.location.hash.substring(1);
+ PDFViewerApplication.eventBus.dispatch('hashchange', { hash: hash });
+ });
+ function webViewerHashchange(e) {
+ if (PDFViewerApplication.pdfHistory.isHashChangeUnlocked) {
+ var hash = e.hash;
+ if (!hash) {
+ return;
+ }
+ if (!PDFViewerApplication.isInitialViewSet) {
+ PDFViewerApplication.initialBookmark = hash;
+ } else {
+ PDFViewerApplication.pdfLinkService.setHash(hash);
+ }
+ }
+ }
+ var webViewerFileInputChange;
+ window.addEventListener('localized', function localized(evt) {
+ PDFViewerApplication.eventBus.dispatch('localized');
+ });
+ function webViewerLocalized() {
+ document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
+ PDFViewerApplication.animationStartedPromise.then(function () {
+ // Adjust the width of the zoom box to fit the content.
+ // Note: If the window is narrow enough that the zoom box is not visible,
+ // we temporarily show it to be able to adjust its width.
+ var container = PDFViewerApplication.appConfig.toolbar.scaleSelectContainer;
+ if (container.clientWidth === 0) {
+ container.setAttribute('style', 'display: inherit;');
+ }
+ if (container.clientWidth > 0) {
+ var select = PDFViewerApplication.appConfig.toolbar.scaleSelect;
+ select.setAttribute('style', 'min-width: inherit;');
+ var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING;
+ select.setAttribute('style', 'min-width: ' + (width + SCALE_SELECT_PADDING) + 'px;');
+ container.setAttribute('style', 'min-width: ' + width + 'px; ' + 'max-width: ' + width + 'px;');
+ }
+ });
+ }
+ function webViewerPresentationMode() {
+ PDFViewerApplication.requestPresentationMode();
+ }
+ function webViewerOpenFile() {
+ var openFileInputName = PDFViewerApplication.appConfig.openFileInputName;
+ document.getElementById(openFileInputName).click();
+ }
+ function webViewerPrint() {
+ window.print();
+ }
+ function webViewerDownload() {
+ PDFViewerApplication.download();
+ }
+ function webViewerFirstPage() {
+ if (PDFViewerApplication.pdfDocument) {
+ PDFViewerApplication.page = 1;
+ }
+ }
+ function webViewerLastPage() {
+ if (PDFViewerApplication.pdfDocument) {
+ PDFViewerApplication.page = PDFViewerApplication.pagesCount;
+ }
+ }
+ function webViewerRotateCw() {
+ PDFViewerApplication.rotatePages(90);
+ }
+ function webViewerRotateCcw() {
+ PDFViewerApplication.rotatePages(-90);
+ }
+ function webViewerDocumentProperties() {
+ PDFViewerApplication.pdfDocumentProperties.open();
+ }
+ function webViewerFind(e) {
+ PDFViewerApplication.findController.executeCommand('find' + e.type, {
+ query: e.query,
+ phraseSearch: e.phraseSearch,
+ caseSensitive: e.caseSensitive,
+ highlightAll: e.highlightAll,
+ findPrevious: e.findPrevious
+ });
+ }
+ function webViewerFindFromUrlHash(e) {
+ PDFViewerApplication.findController.executeCommand('find', {
+ query: e.query,
+ phraseSearch: e.phraseSearch,
+ caseSensitive: false,
+ highlightAll: true,
+ findPrevious: false
+ });
+ }
+ function webViewerScaleChanging(e) {
+ PDFViewerApplication._updateUIToolbar({
+ scaleValue: e.presetValue,
+ scale: e.scale
+ });
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ PDFViewerApplication.pdfViewer.update();
+ }
+ function webViewerPageChanging(e) {
+ var page = e.pageNumber;
+ PDFViewerApplication._updateUIToolbar({
+ pageNumber: page,
+ pageLabel: e.pageLabel
+ });
+ if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) {
+ PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
+ }
+ // we need to update stats
+ if (pdfjsLib.PDFJS.pdfBug && Stats.enabled) {
+ var pageView = PDFViewerApplication.pdfViewer.getPageView(page - 1);
+ if (pageView.stats) {
+ Stats.add(page, pageView.stats);
+ }
+ }
+ }
+ var zoomDisabled = false, zoomDisabledTimeout;
+ function handleMouseWheel(evt) {
+ var pdfViewer = PDFViewerApplication.pdfViewer;
+ if (!pdfViewer || pdfViewer.isInPresentationMode) {
+ return;
+ }
+ if (evt.ctrlKey || evt.metaKey) {
+ var support = PDFViewerApplication.supportedMouseWheelZoomModifierKeys;
+ if (evt.ctrlKey && !support.ctrlKey || evt.metaKey && !support.metaKey) {
+ return;
+ }
+ // Only zoom the pages, not the entire viewer.
+ evt.preventDefault();
+ // NOTE: this check must be placed *after* preventDefault.
+ if (zoomDisabled) {
+ return;
+ }
+ var previousScale = pdfViewer.currentScale;
+ var delta = normalizeWheelEventDelta(evt);
+ var MOUSE_WHEEL_DELTA_PER_PAGE_SCALE = 3.0;
+ var ticks = delta * MOUSE_WHEEL_DELTA_PER_PAGE_SCALE;
+ if (ticks < 0) {
+ PDFViewerApplication.zoomOut(-ticks);
+ } else {
+ PDFViewerApplication.zoomIn(ticks);
+ }
+ var currentScale = pdfViewer.currentScale;
+ if (previousScale !== currentScale) {
+ // After scaling the page via zoomIn/zoomOut, the position of the upper-
+ // left corner is restored. When the mouse wheel is used, the position
+ // under the cursor should be restored instead.
+ var scaleCorrectionFactor = currentScale / previousScale - 1;
+ var rect = pdfViewer.container.getBoundingClientRect();
+ var dx = evt.clientX - rect.left;
+ var dy = evt.clientY - rect.top;
+ pdfViewer.container.scrollLeft += dx * scaleCorrectionFactor;
+ pdfViewer.container.scrollTop += dy * scaleCorrectionFactor;
+ }
+ } else {
+ zoomDisabled = true;
+ clearTimeout(zoomDisabledTimeout);
+ zoomDisabledTimeout = setTimeout(function () {
+ zoomDisabled = false;
+ }, 1000);
+ }
+ }
+ window.addEventListener('wheel', handleMouseWheel);
+ window.addEventListener('click', function click(evt) {
+ if (!PDFViewerApplication.secondaryToolbar.isOpen) {
+ return;
+ }
+ var appConfig = PDFViewerApplication.appConfig;
+ if (PDFViewerApplication.pdfViewer.containsElement(evt.target) || appConfig.toolbar.container.contains(evt.target) && evt.target !== appConfig.secondaryToolbar.toggleButton) {
+ PDFViewerApplication.secondaryToolbar.close();
+ }
+ }, true);
+ window.addEventListener('keydown', function keydown(evt) {
+ if (OverlayManager.active) {
+ return;
+ }
+ var handled = false;
+ var cmd = (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0);
+ var pdfViewer = PDFViewerApplication.pdfViewer;
+ var isViewerInPresentationMode = pdfViewer && pdfViewer.isInPresentationMode;
+ // First, handle the key bindings that are independent whether an input
+ // control is selected or not.
+ if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
+ // either CTRL or META key with optional SHIFT.
+ switch (evt.keyCode) {
+ case 70:
+ // f
+ if (!PDFViewerApplication.supportsIntegratedFind) {
+ PDFViewerApplication.findBar.open();
+ handled = true;
+ }
+ break;
+ case 71:
+ // g
+ if (!PDFViewerApplication.supportsIntegratedFind) {
+ var findState = PDFViewerApplication.findController.state;
+ if (findState) {
+ PDFViewerApplication.findController.executeCommand('findagain', {
+ query: findState.query,
+ phraseSearch: findState.phraseSearch,
+ caseSensitive: findState.caseSensitive,
+ highlightAll: findState.highlightAll,
+ findPrevious: cmd === 5 || cmd === 12
+ });
+ }
+ handled = true;
+ }
+ break;
+ case 61:
+ // FF/Mac '='
+ case 107:
+ // FF '+' and '='
+ case 187:
+ // Chrome '+'
+ case 171:
+ // FF with German keyboard
+ if (!isViewerInPresentationMode) {
+ PDFViewerApplication.zoomIn();
+ }
+ handled = true;
+ break;
+ case 173:
+ // FF/Mac '-'
+ case 109:
+ // FF '-'
+ case 189:
+ // Chrome '-'
+ if (!isViewerInPresentationMode) {
+ PDFViewerApplication.zoomOut();
+ }
+ handled = true;
+ break;
+ case 48:
+ // '0'
+ case 96:
+ // '0' on Numpad of Swedish keyboard
+ if (!isViewerInPresentationMode) {
+ // keeping it unhandled (to restore page zoom to 100%)
+ setTimeout(function () {
+ // ... and resetting the scale after browser adjusts its scale
+ pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
+ });
+ handled = false;
+ }
+ break;
+ }
+ }
+ // CTRL+ALT or Option+Command
+ if (cmd === 3 || cmd === 10) {
+ switch (evt.keyCode) {
+ case 80:
+ // p
+ PDFViewerApplication.requestPresentationMode();
+ handled = true;
+ break;
+ case 71:
+ // g
+ // focuses input#pageNumber field
+ PDFViewerApplication.appConfig.toolbar.pageNumber.select();
+ handled = true;
+ break;
+ }
+ }
+ if (handled) {
+ evt.preventDefault();
+ return;
+ }
+ // Some shortcuts should not get handled if a control/input element
+ // is selected.
+ var curElement = document.activeElement || document.querySelector(':focus');
+ var curElementTagName = curElement && curElement.tagName.toUpperCase();
+ if (curElementTagName === 'INPUT' || curElementTagName === 'TEXTAREA' || curElementTagName === 'SELECT') {
+ // Make sure that the secondary toolbar is closed when Escape is pressed.
+ if (evt.keyCode !== 27) {
+ // 'Esc'
+ return;
+ }
+ }
+ var ensureViewerFocused = false;
+ if (cmd === 0) {
+ // no control key pressed at all.
+ switch (evt.keyCode) {
+ case 38:
+ // up arrow
+ case 33:
+ // pg up
+ case 8:
+ // backspace
+ if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
+ break;
+ }
+ /* in presentation mode */
+ case 37:
+ // left arrow
+ // horizontal scrolling using arrow keys
+ if (pdfViewer.isHorizontalScrollbarEnabled) {
+ break;
+ }
+ case 75:
+ // 'k'
+ case 80:
+ // 'p'
+ if (PDFViewerApplication.page > 1) {
+ PDFViewerApplication.page--;
+ }
+ handled = true;
+ break;
+ case 27:
+ // esc key
+ if (PDFViewerApplication.secondaryToolbar.isOpen) {
+ PDFViewerApplication.secondaryToolbar.close();
+ handled = true;
+ }
+ if (!PDFViewerApplication.supportsIntegratedFind && PDFViewerApplication.findBar.opened) {
+ PDFViewerApplication.findBar.close();
+ handled = true;
+ }
+ break;
+ case 40:
+ // down arrow
+ case 34:
+ // pg down
+ case 32:
+ // spacebar
+ if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
+ break;
+ }
+ case 39:
+ // right arrow
+ // horizontal scrolling using arrow keys
+ if (pdfViewer.isHorizontalScrollbarEnabled) {
+ break;
+ }
+ case 74:
+ // 'j'
+ case 78:
+ // 'n'
+ if (PDFViewerApplication.page < PDFViewerApplication.pagesCount) {
+ PDFViewerApplication.page++;
+ }
+ handled = true;
+ break;
+ case 36:
+ // home
+ if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
+ PDFViewerApplication.page = 1;
+ handled = true;
+ ensureViewerFocused = true;
+ }
+ break;
+ case 35:
+ // end
+ if (isViewerInPresentationMode || PDFViewerApplication.page < PDFViewerApplication.pagesCount) {
+ PDFViewerApplication.page = PDFViewerApplication.pagesCount;
+ handled = true;
+ ensureViewerFocused = true;
+ }
+ break;
+ case 72:
+ // 'h'
+ if (!isViewerInPresentationMode) {
+ PDFViewerApplication.handTool.toggle();
+ }
+ break;
+ case 82:
+ // 'r'
+ PDFViewerApplication.rotatePages(90);
+ break;
+ }
+ }
+ if (cmd === 4) {
+ // shift-key
+ switch (evt.keyCode) {
+ case 32:
+ // spacebar
+ if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
+ break;
+ }
+ if (PDFViewerApplication.page > 1) {
+ PDFViewerApplication.page--;
+ }
+ handled = true;
+ break;
+ case 82:
+ // 'r'
+ PDFViewerApplication.rotatePages(-90);
+ break;
+ }
+ }
+ if (!handled && !isViewerInPresentationMode) {
+ // 33=Page Up 34=Page Down 35=End 36=Home
+ // 37=Left 38=Up 39=Right 40=Down
+ // 32=Spacebar
+ if (evt.keyCode >= 33 && evt.keyCode <= 40 || evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
+ ensureViewerFocused = true;
+ }
+ }
+ if (cmd === 2) {
+ // alt-key
+ switch (evt.keyCode) {
+ case 37:
+ // left arrow
+ if (isViewerInPresentationMode) {
+ PDFViewerApplication.pdfHistory.back();
+ handled = true;
+ }
+ break;
+ case 39:
+ // right arrow
+ if (isViewerInPresentationMode) {
+ PDFViewerApplication.pdfHistory.forward();
+ handled = true;
+ }
+ break;
+ }
+ }
+ if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
+ // The page container is not focused, but a page navigation key has been
+ // pressed. Change the focus to the viewer container to make sure that
+ // navigation by keyboard works as expected.
+ pdfViewer.focus();
+ }
+ if (handled) {
+ evt.preventDefault();
+ }
+ });
+ window.addEventListener('beforeprint', function beforePrint(evt) {
+ PDFViewerApplication.eventBus.dispatch('beforeprint');
+ });
+ window.addEventListener('afterprint', function afterPrint(evt) {
+ PDFViewerApplication.eventBus.dispatch('afterprint');
+ });
+ (function animationStartedClosure() {
+ // The offsetParent is not set until the pdf.js iframe or object is visible.
+ // Waiting for first animation.
+ PDFViewerApplication.animationStartedPromise = new Promise(function (resolve) {
+ window.requestAnimationFrame(resolve);
+ });
+ }());
+ /* Abstract factory for the print service. */
+ var PDFPrintServiceFactory = {
+ instance: {
+ supportsPrinting: false,
+ createPrintService: function () {
+ throw new Error('Not implemented: createPrintService');
+ }
+ }
+ };
+ exports.PDFViewerApplication = PDFViewerApplication;
+ exports.DefaultExernalServices = DefaultExernalServices;
+ exports.PDFPrintServiceFactory = PDFPrintServiceFactory;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebFirefoxPrintService = {}, root.pdfjsWebUIUtils, root.pdfjsWebApp, root.pdfjsWebPDFJS);
+ }(this, function (exports, uiUtils, app, pdfjsLib) {
+ var CSS_UNITS = uiUtils.CSS_UNITS;
+ var PDFPrintServiceFactory = app.PDFPrintServiceFactory;
+ // Creates a placeholder with div and canvas with right size for the page.
+ function composePage(pdfDocument, pageNumber, size, printContainer) {
+ var canvas = document.createElement('canvas');
+ // The size of the canvas in pixels for printing.
+ var PRINT_RESOLUTION = 150;
+ var PRINT_UNITS = PRINT_RESOLUTION / 72.0;
+ canvas.width = Math.floor(size.width * PRINT_UNITS);
+ canvas.height = Math.floor(size.height * PRINT_UNITS);
+ // The physical size of the canvas as specified by the PDF document.
+ canvas.style.width = Math.floor(size.width * CSS_UNITS) + 'px';
+ canvas.style.height = Math.floor(size.height * CSS_UNITS) + 'px';
+ var canvasWrapper = document.createElement('div');
+ canvasWrapper.appendChild(canvas);
+ printContainer.appendChild(canvasWrapper);
+ canvas.mozPrintCallback = function (obj) {
+ // Printing/rendering the page.
+ var ctx = obj.context;
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.restore();
+ pdfDocument.getPage(pageNumber).then(function (pdfPage) {
+ var renderContext = {
+ canvasContext: ctx,
+ transform: [
+ PRINT_UNITS,
+ 0,
+ 0,
+ PRINT_UNITS,
+ 0,
+ 0
+ ],
+ viewport: pdfPage.getViewport(1),
+ intent: 'print'
+ };
+ return pdfPage.render(renderContext).promise;
+ }).then(function () {
+ // Tell the printEngine that rendering this canvas/page has finished.
+ obj.done();
+ }, function (error) {
+ console.error(error);
+ // Tell the printEngine that rendering this canvas/page has failed.
+ // This will make the print process stop.
+ if ('abort' in obj) {
+ obj.abort();
+ } else {
+ obj.done();
+ }
+ });
+ };
+ }
+ function FirefoxPrintService(pdfDocument, pagesOverview, printContainer) {
+ this.pdfDocument = pdfDocument;
+ this.pagesOverview = pagesOverview;
+ this.printContainer = printContainer;
+ }
+ FirefoxPrintService.prototype = {
+ layout: function () {
+ var pdfDocument = this.pdfDocument;
+ var printContainer = this.printContainer;
+ var body = document.querySelector('body');
+ body.setAttribute('data-pdfjsprinting', true);
+ for (var i = 0, ii = this.pagesOverview.length; i < ii; ++i) {
+ composePage(pdfDocument, i + 1, this.pagesOverview[i], printContainer);
+ }
+ },
+ destroy: function () {
+ this.printContainer.textContent = '';
+ }
+ };
+ PDFPrintServiceFactory.instance = {
+ get supportsPrinting() {
+ var canvas = document.createElement('canvas');
+ var value = 'mozPrintCallback' in canvas;
+ return pdfjsLib.shadow(this, 'supportsPrinting', value);
+ },
+ createPrintService: function (pdfDocument, pagesOverview, printContainer) {
+ return new FirefoxPrintService(pdfDocument, pagesOverview, printContainer);
+ }
+ };
+ exports.FirefoxPrintService = FirefoxPrintService;
+ }));
+ (function (root, factory) {
+ factory(root.pdfjsWebFirefoxCom = {}, root.pdfjsWebPreferences, root.pdfjsWebApp, root.pdfjsWebPDFJS);
+ }(this, function (exports, preferences, app, pdfjsLib) {
+ var Preferences = preferences.Preferences;
+ var PDFViewerApplication = app.PDFViewerApplication;
+ var FirefoxCom = function FirefoxComClosure() {
+ return {
+ /**
+ * Creates an event that the extension is listening for and will
+ * synchronously respond to.
+ * NOTE: It is reccomended to use request() instead since one day we may not
+ * be able to synchronously reply.
+ * @param {String} action The action to trigger.
+ * @param {String} data Optional data to send.
+ * @return {*} The response.
+ */
+ requestSync: function (action, data) {
+ var request = document.createTextNode('');
+ document.documentElement.appendChild(request);
+ var sender = document.createEvent('CustomEvent');
+ sender.initCustomEvent('pdf.js.message', true, false, {
+ action: action,
+ data: data,
+ sync: true
+ });
+ request.dispatchEvent(sender);
+ var response = sender.detail.response;
+ document.documentElement.removeChild(request);
+ return response;
+ },
+ /**
+ * Creates an event that the extension is listening for and will
+ * asynchronously respond by calling the callback.
+ * @param {String} action The action to trigger.
+ * @param {String} data Optional data to send.
+ * @param {Function} callback Optional response callback that will be called
+ * with one data argument.
+ */
+ request: function (action, data, callback) {
+ var request = document.createTextNode('');
+ if (callback) {
+ document.addEventListener('pdf.js.response', function listener(event) {
+ var node = event.target;
+ var response = event.detail.response;
+ document.documentElement.removeChild(node);
+ document.removeEventListener('pdf.js.response', listener, false);
+ return callback(response);
+ }, false);
+ }
+ document.documentElement.appendChild(request);
+ var sender = document.createEvent('CustomEvent');
+ sender.initCustomEvent('pdf.js.message', true, false, {
+ action: action,
+ data: data,
+ sync: false,
+ responseExpected: !!callback
+ });
+ return request.dispatchEvent(sender);
+ }
+ };
+ }();
+ var DownloadManager = function DownloadManagerClosure() {
+ function DownloadManager() {
+ }
+ DownloadManager.prototype = {
+ downloadUrl: function DownloadManager_downloadUrl(url, filename) {
+ FirefoxCom.request('download', {
+ originalUrl: url,
+ filename: filename
+ });
+ },
+ downloadData: function DownloadManager_downloadData(data, filename, contentType) {
+ var blobUrl = pdfjsLib.createObjectURL(data, contentType, false);
+ FirefoxCom.request('download', {
+ blobUrl: blobUrl,
+ originalUrl: blobUrl,
+ filename: filename,
+ isAttachment: true
+ });
+ },
+ download: function DownloadManager_download(blob, url, filename) {
+ var blobUrl = window.URL.createObjectURL(blob);
+ FirefoxCom.request('download', {
+ blobUrl: blobUrl,
+ originalUrl: url,
+ filename: filename
+ }, function response(err) {
+ if (err && this.onerror) {
+ this.onerror(err);
+ }
+ window.URL.revokeObjectURL(blobUrl);
+ }.bind(this));
+ }
+ };
+ return DownloadManager;
+ }();
+ Preferences._writeToStorage = function (prefObj) {
+ return new Promise(function (resolve) {
+ FirefoxCom.request('setPreferences', prefObj, resolve);
+ });
+ };
+ Preferences._readFromStorage = function (prefObj) {
+ return new Promise(function (resolve) {
+ FirefoxCom.request('getPreferences', prefObj, function (prefStr) {
+ var readPrefs = JSON.parse(prefStr);
+ resolve(readPrefs);
+ });
+ });
+ };
+ (function listenFindEvents() {
+ var events = [
+ 'find',
+ 'findagain',
+ 'findhighlightallchange',
+ 'findcasesensitivitychange'
+ ];
+ var handleEvent = function (evt) {
+ if (!PDFViewerApplication.initialized) {
+ return;
+ }
+ PDFViewerApplication.eventBus.dispatch('find', {
+ source: window,
+ type: evt.type.substring('find'.length),
+ query: evt.detail.query,
+ phraseSearch: true,
+ caseSensitive: !!evt.detail.caseSensitive,
+ highlightAll: !!evt.detail.highlightAll,
+ findPrevious: !!evt.detail.findPrevious
+ });
+ }.bind(this);
+ for (var i = 0, len = events.length; i < len; i++) {
+ window.addEventListener(events[i], handleEvent);
+ }
+ }());
+ function FirefoxComDataRangeTransport(length, initialData) {
+ pdfjsLib.PDFDataRangeTransport.call(this, length, initialData);
+ }
+ FirefoxComDataRangeTransport.prototype = Object.create(pdfjsLib.PDFDataRangeTransport.prototype);
+ FirefoxComDataRangeTransport.prototype.requestDataRange = function FirefoxComDataRangeTransport_requestDataRange(begin, end) {
+ FirefoxCom.request('requestDataRange', {
+ begin: begin,
+ end: end
+ });
+ };
+ FirefoxComDataRangeTransport.prototype.abort = function FirefoxComDataRangeTransport_abort() {
+ // Sync call to ensure abort is really started.
+ FirefoxCom.requestSync('abortLoading', null);
+ };
+ PDFViewerApplication.externalServices = {
+ updateFindControlState: function (data) {
+ FirefoxCom.request('updateFindControlState', data);
+ },
+ initPassiveLoading: function (callbacks) {
+ var pdfDataRangeTransport;
+ window.addEventListener('message', function windowMessage(e) {
+ if (e.source !== null) {
+ // The message MUST originate from Chrome code.
+ console.warn('Rejected untrusted message from ' + e.origin);
+ return;
+ }
+ var args = e.data;
+ if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) {
+ return;
+ }
+ switch (args.pdfjsLoadAction) {
+ case 'supportsRangedLoading':
+ pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data);
+ callbacks.onOpenWithTransport(args.pdfUrl, args.length, pdfDataRangeTransport);
+ break;
+ case 'range':
+ pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
+ break;
+ case 'rangeProgress':
+ pdfDataRangeTransport.onDataProgress(args.loaded);
+ break;
+ case 'progressiveRead':
+ pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
+ break;
+ case 'progress':
+ callbacks.onProgress(args.loaded, args.total);
+ break;
+ case 'complete':
+ if (!args.data) {
+ callbacks.onError(args.errorCode);
+ break;
+ }
+ callbacks.onOpenWithData(args.data);
+ break;
+ }
+ });
+ FirefoxCom.requestSync('initPassiveLoading', null);
+ },
+ fallback: function (data, callback) {
+ FirefoxCom.request('fallback', data, callback);
+ },
+ reportTelemetry: function (data) {
+ FirefoxCom.request('reportTelemetry', JSON.stringify(data));
+ },
+ createDownloadManager: function () {
+ return new DownloadManager();
+ },
+ get supportsIntegratedFind() {
+ var support = FirefoxCom.requestSync('supportsIntegratedFind');
+ return pdfjsLib.shadow(this, 'supportsIntegratedFind', support);
+ },
+ get supportsDocumentFonts() {
+ var support = FirefoxCom.requestSync('supportsDocumentFonts');
+ return pdfjsLib.shadow(this, 'supportsDocumentFonts', support);
+ },
+ get supportsDocumentColors() {
+ var support = FirefoxCom.requestSync('supportsDocumentColors');
+ return pdfjsLib.shadow(this, 'supportsDocumentColors', support);
+ },
+ get supportedMouseWheelZoomModifierKeys() {
+ var support = FirefoxCom.requestSync('supportedMouseWheelZoomModifierKeys');
+ return pdfjsLib.shadow(this, 'supportedMouseWheelZoomModifierKeys', support);
+ }
+ };
+ //// l10n.js for Firefox extension expects services to be set.
+ document.mozL10n.setExternalLocalizerServices({
+ getLocale: function () {
+ return FirefoxCom.requestSync('getLocale', null);
+ },
+ getStrings: function (key) {
+ return FirefoxCom.requestSync('getStrings', key);
+ }
+ });
+ exports.DownloadManager = DownloadManager;
+ exports.FirefoxCom = FirefoxCom;
+ }));
+ }.call(pdfjsWebLibs));
+}
+{
+ // FIXME the l10n.js file in the Firefox extension needs global FirefoxCom.
+ window.FirefoxCom = pdfjsWebLibs.pdfjsWebFirefoxCom.FirefoxCom;
+}
+function getViewerConfiguration() {
+ return {
+ appContainer: document.body,
+ mainContainer: document.getElementById('viewerContainer'),
+ viewerContainer: document.getElementById('viewer'),
+ eventBus: null,
+ // using global event bus with DOM events
+ toolbar: {
+ container: document.getElementById('toolbarViewer'),
+ numPages: document.getElementById('numPages'),
+ pageNumber: document.getElementById('pageNumber'),
+ scaleSelectContainer: document.getElementById('scaleSelectContainer'),
+ scaleSelect: document.getElementById('scaleSelect'),
+ customScaleOption: document.getElementById('customScaleOption'),
+ previous: document.getElementById('previous'),
+ next: document.getElementById('next'),
+ firstPage: document.getElementById('firstPage'),
+ lastPage: document.getElementById('lastPage'),
+ zoomIn: document.getElementById('zoomIn'),
+ zoomOut: document.getElementById('zoomOut'),
+ viewFind: document.getElementById('viewFind'),
+ openFile: document.getElementById('openFile'),
+ print: document.getElementById('print'),
+ presentationModeButton: document.getElementById('presentationMode'),
+ download: document.getElementById('download'),
+ viewBookmark: document.getElementById('viewBookmark')
+ },
+ secondaryToolbar: {
+ toolbar: document.getElementById('secondaryToolbar'),
+ toggleButton: document.getElementById('secondaryToolbarToggle'),
+ toolbarButtonContainer: document.getElementById('secondaryToolbarButtonContainer'),
+ presentationModeButton: document.getElementById('secondaryPresentationMode'),
+ openFileButton: document.getElementById('secondaryOpenFile'),
+ printButton: document.getElementById('secondaryPrint'),
+ downloadButton: document.getElementById('secondaryDownload'),
+ viewBookmarkButton: document.getElementById('secondaryViewBookmark'),
+ firstPageButton: document.getElementById('firstPage'),
+ lastPageButton: document.getElementById('lastPage'),
+ pageRotateCwButton: document.getElementById('pageRotateCw'),
+ pageRotateCcwButton: document.getElementById('pageRotateCcw'),
+ toggleHandToolButton: document.getElementById('toggleHandTool'),
+ documentPropertiesButton: document.getElementById('documentProperties')
+ },
+ fullscreen: {
+ contextFirstPage: document.getElementById('contextFirstPage'),
+ contextLastPage: document.getElementById('contextLastPage'),
+ contextPageRotateCw: document.getElementById('contextPageRotateCw'),
+ contextPageRotateCcw: document.getElementById('contextPageRotateCcw')
+ },
+ sidebar: {
+ // Divs (and sidebar button)
+ mainContainer: document.getElementById('mainContainer'),
+ outerContainer: document.getElementById('outerContainer'),
+ toggleButton: document.getElementById('sidebarToggle'),
+ // Buttons
+ thumbnailButton: document.getElementById('viewThumbnail'),
+ outlineButton: document.getElementById('viewOutline'),
+ attachmentsButton: document.getElementById('viewAttachments'),
+ // Views
+ thumbnailView: document.getElementById('thumbnailView'),
+ outlineView: document.getElementById('outlineView'),
+ attachmentsView: document.getElementById('attachmentsView')
+ },
+ findBar: {
+ bar: document.getElementById('findbar'),
+ toggleButton: document.getElementById('viewFind'),
+ findField: document.getElementById('findInput'),
+ highlightAllCheckbox: document.getElementById('findHighlightAll'),
+ caseSensitiveCheckbox: document.getElementById('findMatchCase'),
+ findMsg: document.getElementById('findMsg'),
+ findResultsCount: document.getElementById('findResultsCount'),
+ findStatusIcon: document.getElementById('findStatusIcon'),
+ findPreviousButton: document.getElementById('findPrevious'),
+ findNextButton: document.getElementById('findNext')
+ },
+ passwordOverlay: {
+ overlayName: 'passwordOverlay',
+ container: document.getElementById('passwordOverlay'),
+ label: document.getElementById('passwordText'),
+ input: document.getElementById('password'),
+ submitButton: document.getElementById('passwordSubmit'),
+ cancelButton: document.getElementById('passwordCancel')
+ },
+ documentProperties: {
+ overlayName: 'documentPropertiesOverlay',
+ container: document.getElementById('documentPropertiesOverlay'),
+ closeButton: document.getElementById('documentPropertiesClose'),
+ fields: {
+ 'fileName': document.getElementById('fileNameField'),
+ 'fileSize': document.getElementById('fileSizeField'),
+ 'title': document.getElementById('titleField'),
+ 'author': document.getElementById('authorField'),
+ 'subject': document.getElementById('subjectField'),
+ 'keywords': document.getElementById('keywordsField'),
+ 'creationDate': document.getElementById('creationDateField'),
+ 'modificationDate': document.getElementById('modificationDateField'),
+ 'creator': document.getElementById('creatorField'),
+ 'producer': document.getElementById('producerField'),
+ 'version': document.getElementById('versionField'),
+ 'pageCount': document.getElementById('pageCountField')
+ }
+ },
+ errorWrapper: {
+ container: document.getElementById('errorWrapper'),
+ errorMessage: document.getElementById('errorMessage'),
+ closeButton: document.getElementById('errorClose'),
+ errorMoreInfo: document.getElementById('errorMoreInfo'),
+ moreInfoButton: document.getElementById('errorShowMore'),
+ lessInfoButton: document.getElementById('errorShowLess')
+ },
+ printContainer: document.getElementById('printContainer'),
+ openFileInputName: 'fileInput',
+ debuggerScriptPath: './debugger.js'
+ };
+}
+function webViewerLoad() {
+ var config = getViewerConfiguration();
+ window.PDFViewerApplication = pdfjsWebLibs.pdfjsWebApp.PDFViewerApplication;
+ pdfjsWebLibs.pdfjsWebApp.PDFViewerApplication.run(config);
+}
+document.addEventListener('DOMContentLoaded', webViewerLoad, true); \ No newline at end of file
diff --git a/browser/extensions/pdfjs/jar.mn b/browser/extensions/pdfjs/jar.mn
new file mode 100644
index 000000000..ca1148155
--- /dev/null
+++ b/browser/extensions/pdfjs/jar.mn
@@ -0,0 +1,3 @@
+pdfjs.jar:
+% resource pdf.js %content/
+ content/ (content/*)
diff --git a/browser/extensions/pdfjs/moz.build b/browser/extensions/pdfjs/moz.build
new file mode 100644
index 000000000..a1d2634da
--- /dev/null
+++ b/browser/extensions/pdfjs/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/extensions/pdfjs/test/.eslintrc.js b/browser/extensions/pdfjs/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/extensions/pdfjs/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/extensions/pdfjs/test/browser.ini b/browser/extensions/pdfjs/test/browser.ini
new file mode 100644
index 000000000..fb4aa9afc
--- /dev/null
+++ b/browser/extensions/pdfjs/test/browser.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ file_pdfjs_test.pdf
+ head.js
+
+[browser_pdfjs_main.js]
+[browser_pdfjs_navigation.js]
+[browser_pdfjs_savedialog.js]
+[browser_pdfjs_views.js]
+[browser_pdfjs_zoom.js]
diff --git a/browser/extensions/pdfjs/test/browser_pdfjs_main.js b/browser/extensions/pdfjs/test/browser_pdfjs_main.js
new file mode 100644
index 000000000..9660e92c1
--- /dev/null
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_main.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
+const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
+
+add_task(function* test() {
+ let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
+
+ // Make sure pdf.js is the default handler.
+ is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
+ is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
+
+ info('Pref action: ' + handlerInfo.preferredAction);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser: gBrowser, url: "about:blank" },
+ function* (newTabBrowser) {
+ yield waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf");
+
+ ok(gBrowser.isFindBarInitialized(), "Browser FindBar initialized!");
+
+ yield ContentTask.spawn(newTabBrowser, null, function* () {
+ //
+ // Overall sanity tests
+ //
+ Assert.ok(content.document.querySelector('div#viewer'), "document content has viewer UI");
+ Assert.ok('PDFJS' in content.wrappedJSObject, "window content has PDFJS object");
+
+ //
+ // Sidebar: open
+ //
+ var sidebar = content.document.querySelector('button#sidebarToggle'),
+ outerContainer = content.document.querySelector('div#outerContainer');
+
+ sidebar.click();
+ Assert.ok(outerContainer.classList.contains('sidebarOpen'), "sidebar opens on click");
+
+ //
+ // Sidebar: close
+ //
+ sidebar.click();
+ Assert.ok(!outerContainer.classList.contains('sidebarOpen'), "sidebar closes on click");
+
+ //
+ // Page change from prev/next buttons
+ //
+ var prevPage = content.document.querySelector('button#previous'),
+ nextPage = content.document.querySelector('button#next');
+
+ var pgNumber = content.document.querySelector('input#pageNumber').value;
+ Assert.equal(parseInt(pgNumber, 10), 1, "initial page is 1");
+
+ //
+ // Bookmark button
+ //
+ var viewBookmark = content.document.querySelector('a#viewBookmark');
+ viewBookmark.click();
+
+ Assert.ok(viewBookmark.href.length > 0, "viewBookmark button has href");
+
+ var viewer = content.wrappedJSObject.PDFViewerApplication;
+ yield viewer.close();
+ });
+ });
+});
diff --git a/browser/extensions/pdfjs/test/browser_pdfjs_navigation.js b/browser/extensions/pdfjs/test/browser_pdfjs_navigation.js
new file mode 100644
index 000000000..b01a87ec8
--- /dev/null
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_navigation.js
@@ -0,0 +1,283 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+Components.utils.import("resource://gre/modules/Promise.jsm", this);
+
+const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
+const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
+
+const PDF_OUTLINE_ITEMS = 17;
+const TESTS = [
+ {
+ action: {
+ selector: "button#next",
+ event: "click"
+ },
+ expectedPage: 2,
+ message: "navigated to next page using NEXT button"
+
+ },
+ {
+ action: {
+ selector: "button#previous",
+ event: "click"
+ },
+ expectedPage: 1,
+ message: "navigated to previous page using PREV button"
+ },
+ {
+ action: {
+ selector: "button#next",
+ event: "click"
+ },
+ expectedPage: 2,
+ message: "navigated to next page using NEXT button"
+ },
+ {
+ action: {
+ selector: "input#pageNumber",
+ value: 1,
+ event: "change"
+ },
+ expectedPage: 1,
+ message: "navigated to first page using pagenumber"
+ },
+ {
+ action: {
+ selector: "#thumbnailView a:nth-child(4)",
+ event: "click"
+ },
+ expectedPage: 4,
+ message: "navigated to 4th page using thumbnail view"
+ },
+ {
+ action: {
+ selector: "#thumbnailView a:nth-child(2)",
+ event: "click"
+ },
+ expectedPage: 2,
+ message: "navigated to 2nd page using thumbnail view"
+ },
+ {
+ action: {
+ selector: "#viewer",
+ event: "keydown",
+ keyCode: 36
+ },
+ expectedPage: 1,
+ message: "navigated to 1st page using 'home' key"
+ },
+ {
+ action: {
+ selector: "#viewer",
+ event: "keydown",
+ keyCode: 34
+ },
+ expectedPage: 2,
+ message: "navigated to 2nd page using 'Page Down' key"
+ },
+ {
+ action: {
+ selector: "#viewer",
+ event: "keydown",
+ keyCode: 33
+ },
+ expectedPage: 1,
+ message: "navigated to 1st page using 'Page Up' key"
+ },
+ {
+ action: {
+ selector: "#viewer",
+ event: "keydown",
+ keyCode: 39
+ },
+ expectedPage: 2,
+ message: "navigated to 2nd page using 'right' key"
+ },
+ {
+ action: {
+ selector: "#viewer",
+ event: "keydown",
+ keyCode: 37
+ },
+ expectedPage: 1,
+ message: "navigated to 1st page using 'left' key"
+ },
+ {
+ action: {
+ selector: "#viewer",
+ event: "keydown",
+ keyCode: 35
+ },
+ expectedPage: 5,
+ message: "navigated to last page using 'home' key"
+ },
+ {
+ action: {
+ selector: ".outlineItem:nth-child(1) a",
+ event: "click"
+ },
+ expectedPage: 1,
+ message: "navigated to 1st page using outline view"
+ },
+ {
+ action: {
+ selector: ".outlineItem:nth-child(" + PDF_OUTLINE_ITEMS + ") a",
+ event: "click"
+ },
+ expectedPage: 4,
+ message: "navigated to 4th page using outline view"
+ },
+ {
+ action: {
+ selector: "input#pageNumber",
+ value: 5,
+ event: "change"
+ },
+ expectedPage: 5,
+ message: "navigated to 5th page using pagenumber"
+ }
+];
+
+add_task(function* test() {
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
+
+ // Make sure pdf.js is the default handler.
+ is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
+ is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
+
+ info('Pref action: ' + handlerInfo.preferredAction);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (newTabBrowser) {
+ yield waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf");
+
+ yield ContentTask.spawn(newTabBrowser, null, function* () {
+ // Check if PDF is opened with internal viewer
+ Assert.ok(content.document.querySelector("div#viewer"), "document content has viewer UI");
+ Assert.ok("PDFJS" in content.wrappedJSObject, "window content has PDFJS object");
+ });
+
+ yield ContentTask.spawn(newTabBrowser, null, contentSetUp);
+
+ yield Task.spawn(runTests(newTabBrowser));
+
+ yield ContentTask.spawn(newTabBrowser, null, function*() {
+ let pageNumber = content.document.querySelector('input#pageNumber');
+ Assert.equal(pageNumber.value, pageNumber.max, "Document is left on the last page");
+ });
+ });
+});
+
+function* contentSetUp() {
+ /**
+ * Outline Items gets appended to the document later on we have to
+ * wait for them before we start to navigate though document
+ *
+ * @param document
+ * @returns {deferred.promise|*}
+ */
+ function waitForOutlineItems(document) {
+ return new Promise((resolve, reject) => {
+ document.addEventListener("outlineloaded", function outlineLoaded(evt) {
+ document.removeEventListener("outlineloaded", outlineLoaded);
+ var outlineCount = evt.detail.outlineCount;
+
+ if (document.querySelectorAll(".outlineItem").length === outlineCount) {
+ resolve();
+ } else {
+ reject();
+ }
+ });
+ });
+ }
+
+ /**
+ * The key navigation has to happen in page-fit, otherwise it won't scroll
+ * through a complete page
+ *
+ * @param document
+ * @returns {deferred.promise|*}
+ */
+ function setZoomToPageFit(document) {
+ return new Promise((resolve) => {
+ document.addEventListener("pagerendered", function onZoom(e) {
+ document.removeEventListener("pagerendered", onZoom);
+ document.querySelector("#viewer").click();
+ resolve();
+ });
+
+ var select = document.querySelector("select#scaleSelect");
+ select.selectedIndex = 2;
+ select.dispatchEvent(new Event("change"));
+ });
+ }
+
+ yield waitForOutlineItems(content.document);
+ yield setZoomToPageFit(content.document);
+}
+
+/**
+ * As the page changes asynchronously, we have to wait for the event after
+ * we trigger the action so we will be at the expected page number after each action
+ *
+ * @param document
+ * @param window
+ * @param test
+ * @param callback
+ */
+function* runTests(browser) {
+ yield ContentTask.spawn(browser, TESTS, function* (TESTS) {
+ let window = content;
+ let document = window.document;
+
+ for (let test of TESTS) {
+ let deferred = {};
+ deferred.promise = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+
+ let pageNumber = document.querySelector('input#pageNumber');
+
+ // Add an event-listener to wait for page to change, afterwards resolve the promise
+ let timeout = window.setTimeout(() => deferred.reject(), 5000);
+ window.addEventListener('pagechange', function pageChange() {
+ if (pageNumber.value == test.expectedPage) {
+ window.removeEventListener('pagechange', pageChange);
+ window.clearTimeout(timeout);
+ deferred.resolve(+pageNumber.value);
+ }
+ });
+
+ // Get the element and trigger the action for changing the page
+ var el = document.querySelector(test.action.selector);
+ Assert.ok(el, "Element '" + test.action.selector + "' has been found");
+
+ // The value option is for input case
+ if (test.action.value)
+ el.value = test.action.value;
+
+ // Dispatch the event for changing the page
+ if (test.action.event == "keydown") {
+ var ev = document.createEvent("KeyboardEvent");
+ ev.initKeyEvent("keydown", true, true, null, false, false, false, false,
+ test.action.keyCode, 0);
+ el.dispatchEvent(ev);
+ }
+ else {
+ var ev = new Event(test.action.event);
+ }
+ el.dispatchEvent(ev);
+
+ let pgNumber = yield deferred.promise;
+ Assert.equal(pgNumber, test.expectedPage, test.message);
+ }
+
+ var viewer = content.wrappedJSObject.PDFViewerApplication;
+ yield viewer.close();
+ });
+}
diff --git a/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js b/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js
new file mode 100644
index 000000000..a6564e591
--- /dev/null
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_savedialog.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
+const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
+
+function test() {
+ var oldAction = changeMimeHandler(Ci.nsIHandlerInfo.useSystemDefault, true);
+ var tab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf");
+ //
+ // Test: "Open with" dialog comes up when pdf.js is not selected as the default
+ // handler.
+ //
+ addWindowListener('chrome://mozapps/content/downloads/unknownContentType.xul', finish);
+
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ changeMimeHandler(oldAction[0], oldAction[1]);
+ gBrowser.removeTab(tab);
+ });
+}
+
+function changeMimeHandler(preferredAction, alwaysAskBeforeHandling) {
+ let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
+ var oldAction = [handlerInfo.preferredAction, handlerInfo.alwaysAskBeforeHandling];
+
+ // Change and save mime handler settings
+ handlerInfo.alwaysAskBeforeHandling = alwaysAskBeforeHandling;
+ handlerInfo.preferredAction = preferredAction;
+ handlerService.store(handlerInfo);
+
+ Services.obs.notifyObservers(null, 'pdfjs:handlerChanged', null);
+
+ // Refresh data
+ handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
+
+ //
+ // Test: Mime handler was updated
+ //
+ is(handlerInfo.alwaysAskBeforeHandling, alwaysAskBeforeHandling, 'always-ask prompt change successful');
+ is(handlerInfo.preferredAction, preferredAction, 'mime handler change successful');
+
+ return oldAction;
+}
+
+function addWindowListener(aURL, aCallback) {
+ Services.wm.addListener({
+ onOpenWindow: function(aXULWindow) {
+ info("window opened, waiting for focus");
+ Services.wm.removeListener(this);
+
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ waitForFocus(function() {
+ is(domwindow.document.location.href, aURL, "should have seen the right window open");
+ domwindow.close();
+ aCallback();
+ }, domwindow);
+ },
+ onCloseWindow: function(aXULWindow) { },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+ });
+}
diff --git a/browser/extensions/pdfjs/test/browser_pdfjs_views.js b/browser/extensions/pdfjs/test/browser_pdfjs_views.js
new file mode 100644
index 000000000..d14503e41
--- /dev/null
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_views.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
+const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
+
+add_task(function* test() {
+ let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
+
+ // Make sure pdf.js is the default handler.
+ is(handlerInfo.alwaysAskBeforeHandling, false, 'pdf handler defaults to always-ask is false');
+ is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally, 'pdf handler defaults to internal');
+
+ info('Pref action: ' + handlerInfo.preferredAction);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (browser) {
+ // check that PDF is opened with internal viewer
+ yield waitForPdfJS(browser, TESTROOT + "file_pdfjs_test.pdf");
+
+ yield ContentTask.spawn(browser, null, function* () {
+ Assert.ok(content.document.querySelector("div#viewer"), "document content has viewer UI");
+ Assert.ok("PDFJS" in content.wrappedJSObject, "window content has PDFJS object");
+
+ //open sidebar
+ var sidebar = content.document.querySelector('button#sidebarToggle');
+ var outerContainer = content.document.querySelector('div#outerContainer');
+
+ sidebar.click();
+ Assert.ok(outerContainer.classList.contains("sidebarOpen"), "sidebar opens on click");
+
+ // check that thumbnail view is open
+ var thumbnailView = content.document.querySelector('div#thumbnailView');
+ var outlineView = content.document.querySelector('div#outlineView');
+
+ Assert.equal(thumbnailView.getAttribute("class"), null,
+ "Initial view is thumbnail view");
+ Assert.equal(outlineView.getAttribute("class"), "hidden",
+ "Outline view is hidden initially");
+
+ //switch to outline view
+ var viewOutlineButton = content.document.querySelector('button#viewOutline');
+ viewOutlineButton.click();
+
+ Assert.equal(thumbnailView.getAttribute("class"), "hidden",
+ "Thumbnail view is hidden when outline is selected");
+ Assert.equal(outlineView.getAttribute("class"), "",
+ "Outline view is visible when selected");
+
+ //switch back to thumbnail view
+ var viewThumbnailButton = content.document.querySelector('button#viewThumbnail');
+ viewThumbnailButton.click();
+
+ Assert.equal(thumbnailView.getAttribute("class"), "",
+ "Thumbnail view is visible when selected");
+ Assert.equal(outlineView.getAttribute("class"), "hidden",
+ "Outline view is hidden when thumbnail is selected");
+
+ sidebar.click();
+
+ var viewer = content.wrappedJSObject.PDFViewerApplication;
+ yield viewer.close();
+ });
+ });
+});
diff --git a/browser/extensions/pdfjs/test/browser_pdfjs_zoom.js b/browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
new file mode 100644
index 000000000..f2b73fd99
--- /dev/null
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_zoom.js
@@ -0,0 +1,154 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+Components.utils.import("resource://gre/modules/Promise.jsm", this);
+
+const RELATIVE_DIR = "browser/extensions/pdfjs/test/";
+const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
+
+const TESTS = [
+ {
+ action: {
+ selector: "button#zoomIn",
+ event: "click"
+ },
+ expectedZoom: 1, // 1 - zoom in
+ message: "Zoomed in using the '+' (zoom in) button"
+ },
+
+ {
+ action: {
+ selector: "button#zoomOut",
+ event: "click"
+ },
+ expectedZoom: -1, // -1 - zoom out
+ message: "Zoomed out using the '-' (zoom out) button"
+ },
+
+ {
+ action: {
+ keyboard: true,
+ keyCode: 61,
+ event: "+"
+ },
+ expectedZoom: 1, // 1 - zoom in
+ message: "Zoomed in using the CTRL++ keys"
+ },
+
+ {
+ action: {
+ keyboard: true,
+ keyCode: 109,
+ event: "-"
+ },
+ expectedZoom: -1, // -1 - zoom out
+ message: "Zoomed out using the CTRL+- keys"
+ },
+
+ {
+ action: {
+ selector: "select#scaleSelect",
+ index: 5,
+ event: "change"
+ },
+ expectedZoom: -1, // -1 - zoom out
+ message: "Zoomed using the zoom picker"
+ }
+];
+
+add_task(function* test() {
+ let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"]
+ .getService(Ci.nsIHandlerService);
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let handlerInfo = mimeService.getFromTypeAndExtension('application/pdf', 'pdf');
+
+ // Make sure pdf.js is the default handler.
+ is(handlerInfo.alwaysAskBeforeHandling, false,
+ 'pdf handler defaults to always-ask is false');
+ is(handlerInfo.preferredAction, Ci.nsIHandlerInfo.handleInternally,
+ 'pdf handler defaults to internal');
+
+ info('Pref action: ' + handlerInfo.preferredAction);
+
+ yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" },
+ function* (newTabBrowser) {
+ yield waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf" + "#zoom=100");
+
+ yield ContentTask.spawn(newTabBrowser, TESTS, function* (TESTS) {
+ let document = content.document;
+
+ function waitForRender() {
+ return new Promise((resolve) => {
+ document.addEventListener("pagerendered", function onPageRendered(e) {
+ if(e.detail.pageNumber !== 1) {
+ return;
+ }
+
+ document.removeEventListener("pagerendered", onPageRendered, true);
+ resolve();
+ }, true);
+ });
+ }
+
+ // check that PDF is opened with internal viewer
+ Assert.ok(content.document.querySelector("div#viewer"), "document content has viewer UI");
+ Assert.ok("PDFJS" in content.wrappedJSObject, "window content has PDFJS object");
+
+ let initialWidth, previousWidth;
+ initialWidth = previousWidth =
+ parseInt(content.document.querySelector("div#pageContainer1").style.width);
+
+ for (let test of TESTS) {
+ // We zoom using an UI element
+ var ev;
+ if (test.action.selector) {
+ // Get the element and trigger the action for changing the zoom
+ var el = document.querySelector(test.action.selector);
+ Assert.ok(el, "Element '" + test.action.selector + "' has been found");
+
+ if (test.action.index){
+ el.selectedIndex = test.action.index;
+ }
+
+ // Dispatch the event for changing the zoom
+ ev = new Event(test.action.event);
+ }
+ // We zoom using keyboard
+ else {
+ // Simulate key press
+ ev = new content.KeyboardEvent("keydown",
+ { key: test.action.event,
+ keyCode: test.action.keyCode,
+ ctrlKey: true });
+ el = content;
+ }
+
+ el.dispatchEvent(ev);
+ yield waitForRender();
+
+ var pageZoomScale = content.document.querySelector('select#scaleSelect');
+
+ // The zoom value displayed in the zoom select
+ var zoomValue = pageZoomScale.options[pageZoomScale.selectedIndex].innerHTML;
+
+ let pageContainer = content.document.querySelector('div#pageContainer1');
+ let actualWidth = parseInt(pageContainer.style.width);
+
+ // the actual zoom of the PDF document
+ let computedZoomValue = parseInt(((actualWidth/initialWidth).toFixed(2))*100) + "%";
+ Assert.equal(computedZoomValue, zoomValue, "Content has correct zoom");
+
+ // Check that document zooms in the expected way (in/out)
+ let zoom = (actualWidth - previousWidth) * test.expectedZoom;
+ Assert.ok(zoom > 0, test.message);
+
+ previousWidth = actualWidth;
+ }
+
+ var viewer = content.wrappedJSObject.PDFViewerApplication;
+ yield viewer.close();
+ });
+ });
+});
diff --git a/browser/extensions/pdfjs/test/file_pdfjs_test.pdf b/browser/extensions/pdfjs/test/file_pdfjs_test.pdf
new file mode 100644
index 000000000..7ad87e3c2
--- /dev/null
+++ b/browser/extensions/pdfjs/test/file_pdfjs_test.pdf
Binary files differ
diff --git a/browser/extensions/pdfjs/test/head.js b/browser/extensions/pdfjs/test/head.js
new file mode 100644
index 000000000..d980bceb1
--- /dev/null
+++ b/browser/extensions/pdfjs/test/head.js
@@ -0,0 +1,15 @@
+function waitForPdfJS(browser, url) {
+ // Runs tests after all 'load' event handlers have fired off
+ return ContentTask.spawn(browser, url, function* (url) {
+ yield new Promise((resolve) => {
+ // NB: Add the listener to the global object so that we receive the
+ // event fired from the new window.
+ addEventListener("documentload", function listener() {
+ removeEventListener("documentload", listener, false);
+ resolve();
+ }, false, true);
+
+ content.location = url;
+ });
+ });
+}
diff --git a/browser/extensions/pocket/bootstrap.js b/browser/extensions/pocket/bootstrap.js
new file mode 100644
index 000000000..c470eb8d3
--- /dev/null
+++ b/browser/extensions/pocket/bootstrap.js
@@ -0,0 +1,511 @@
+/* -*- 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 {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+ "resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
+ "chrome://pocket/content/Pocket.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutPocket",
+ "chrome://pocket/content/AboutPocket.jsm");
+XPCOMUtils.defineLazyGetter(this, "gPocketBundle", function() {
+ return Services.strings.createBundle("chrome://pocket/locale/pocket.properties");
+});
+XPCOMUtils.defineLazyGetter(this, "gPocketStyleURI", function() {
+ return Services.io.newURI("chrome://pocket/skin/pocket.css", null, null);
+});
+
+// Due to bug 1051238 frame scripts are cached forever, so we can't update them
+// as a restartless add-on. The Math.random() is the work around for this.
+const PROCESS_SCRIPT = "chrome://pocket/content/pocket-content-process.js?" + Math.random();
+
+const PREF_BRANCH = "extensions.pocket.";
+const PREFS = {
+ enabled: true, // bug 1229937, figure out ui tour support
+ api: "api.getpocket.com",
+ site: "getpocket.com",
+ oAuthConsumerKey: "40249-e88c401e1b1f2242d9e441c4"
+};
+
+function setDefaultPrefs() {
+ let branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
+ for (let [key, val] of Object.entries(PREFS)) {
+ // If someone beat us to setting a default, don't overwrite it. This can
+ // happen if distribution.ini sets the default first.
+ if (branch.getPrefType(key) != branch.PREF_INVALID)
+ continue;
+ switch (typeof val) {
+ case "boolean":
+ branch.setBoolPref(key, val);
+ break;
+ case "number":
+ branch.setIntPref(key, val);
+ break;
+ case "string":
+ branch.setCharPref(key, val);
+ break;
+ }
+ }
+}
+
+function createElementWithAttrs(document, type, attrs) {
+ let element = document.createElement(type);
+ Object.keys(attrs).forEach(function (attr) {
+ element.setAttribute(attr, attrs[attr]);
+ })
+ return element;
+}
+
+function CreatePocketWidget(reason) {
+ let id = "pocket-button"
+ let widget = CustomizableUI.getWidget(id);
+ // The widget is only null if we've created then destroyed the widget.
+ // Once we've actually called createWidget the provider will be set to
+ // PROVIDER_API.
+ if (widget && widget.provider == CustomizableUI.PROVIDER_API)
+ return;
+ // if upgrading from builtin version and the button was placed in ui,
+ // seenWidget will not be null
+ let seenWidget = CustomizableUI.getPlacementOfWidget("pocket-button", false, true);
+ let pocketButton = {
+ id: "pocket-button",
+ defaultArea: CustomizableUI.AREA_NAVBAR,
+ introducedInVersion: "pref",
+ type: "view",
+ tabSpecific: true,
+ viewId: "PanelUI-pocketView",
+ label: gPocketBundle.GetStringFromName("pocket-button.label"),
+ tooltiptext: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
+ // Use forwarding functions here to avoid loading Pocket.jsm on startup:
+ onViewShowing: function() {
+ return Pocket.onPanelViewShowing.apply(this, arguments);
+ },
+ onViewHiding: function() {
+ return Pocket.onPanelViewHiding.apply(this, arguments);
+ },
+ onBeforeCreated: function(doc) {
+ // Bug 1223127,CUI should make this easier to do.
+ if (doc.getElementById("PanelUI-pocketView"))
+ return;
+ let view = doc.createElement("panelview");
+ view.id = "PanelUI-pocketView";
+ let panel = doc.createElement("vbox");
+ panel.setAttribute("class", "panel-subview-body");
+ view.appendChild(panel);
+ doc.getElementById("PanelUI-multiView").appendChild(view);
+ }
+ };
+
+ CustomizableUI.createWidget(pocketButton);
+ CustomizableUI.addListener(pocketButton);
+ // placed is null if location is palette
+ let placed = CustomizableUI.getPlacementOfWidget("pocket-button");
+
+ // a first time install will always have placed the button somewhere, and will
+ // not have a placement prior to creating the widget. Thus, !seenWidget &&
+ // placed.
+ if (reason == ADDON_ENABLE && !seenWidget && placed) {
+ // initially place the button after the bookmarks button if it is in the UI
+ let widgets = CustomizableUI.getWidgetIdsInArea(CustomizableUI.AREA_NAVBAR);
+ let bmbtn = widgets.indexOf("bookmarks-menu-button");
+ if (bmbtn > -1) {
+ CustomizableUI.moveWidgetWithinArea("pocket-button", bmbtn + 1);
+ }
+ }
+
+ // Uninstall the Pocket social provider if it exists, but only if we haven't
+ // already uninstalled it in this manner. That way the user can reinstall
+ // it if they prefer it without its being uninstalled every time they start
+ // the browser.
+ let SocialService;
+ try {
+ // For Firefox 51+
+ SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+ } catch (e) {
+ SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+ }
+
+ let origin = "https://getpocket.com";
+ SocialService.getProvider(origin, provider => {
+ if (provider) {
+ let pref = "social.backup.getpocket-com";
+ if (!Services.prefs.prefHasUserValue(pref)) {
+ let str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ str.data = JSON.stringify(provider.manifest);
+ Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str);
+ SocialService.uninstallProvider(origin, () => {});
+ }
+ }
+ });
+
+}
+
+// PocketContextMenu
+// When the context menu is opened check if we need to build and enable pocket UI.
+var PocketContextMenu = {
+ init: function() {
+ Services.obs.addObserver(this, "on-build-contextmenu", false);
+ },
+ shutdown: function() {
+ Services.obs.removeObserver(this, "on-build-contextmenu");
+ // loop through windows and remove context menus
+ // iterate through all windows and add pocket to them
+ for (let win of CustomizableUI.windows) {
+ let document = win.document;
+ for (let id of ["context-pocket", "context-savelinktopocket"]) {
+ let element = document.getElementById(id);
+ if (element)
+ element.remove();
+ }
+ }
+ },
+ observe: function(aSubject, aTopic, aData) {
+ let subject = aSubject.wrappedJSObject;
+ let document = subject.menu.ownerDocument;
+ let pocketEnabled = CustomizableUI.getPlacementOfWidget("pocket-button");
+
+ let showSaveCurrentPageToPocket = !(subject.onTextInput || subject.onLink ||
+ subject.isContentSelected || subject.onImage ||
+ subject.onCanvas || subject.onVideo || subject.onAudio);
+ let targetUrl = subject.onLink ? subject.linkUrl : subject.pageUrl;
+ let targetURI = Services.io.newURI(targetUrl, null, null);
+ let canPocket = pocketEnabled && (targetURI.schemeIs("http") || targetURI.schemeIs("https") ||
+ (targetURI.schemeIs("about") && ReaderMode.getOriginalUrl(targetUrl)));
+
+ let showSaveLinkToPocket = canPocket && !showSaveCurrentPageToPocket && subject.onLink;
+
+ // create menu entries if necessary
+ let menu = document.getElementById("context-pocket");
+ if (!menu) {
+ menu = createElementWithAttrs(document, "menuitem", {
+ "id": "context-pocket",
+ "label": gPocketBundle.GetStringFromName("saveToPocketCmd.label"),
+ "accesskey": gPocketBundle.GetStringFromName("saveToPocketCmd.accesskey"),
+ "oncommand": "Pocket.savePage(gContextMenu.browser, gContextMenu.browser.currentURI.spec, gContextMenu.browser.contentTitle);"
+ });
+ let sibling = document.getElementById("context-savepage");
+ if (sibling.nextSibling) {
+ sibling.parentNode.insertBefore(menu, sibling.nextSibling);
+ } else {
+ sibling.parentNode.appendChild(menu);
+ }
+ }
+ menu.hidden = !(canPocket && showSaveCurrentPageToPocket);
+
+ menu = document.getElementById("context-savelinktopocket");
+ if (!menu) {
+ menu = createElementWithAttrs(document, "menuitem", {
+ "id": "context-savelinktopocket",
+ "label": gPocketBundle.GetStringFromName("saveLinkToPocketCmd.label"),
+ "accesskey": gPocketBundle.GetStringFromName("saveLinkToPocketCmd.accesskey"),
+ "oncommand": "Pocket.savePage(gContextMenu.browser, gContextMenu.linkURL);"
+ });
+ let sibling = document.getElementById("context-savelink");
+ if (sibling.nextSibling) {
+ sibling.parentNode.insertBefore(menu, sibling.nextSibling);
+ } else {
+ sibling.parentNode.appendChild(menu);
+ }
+ }
+ menu.hidden = !showSaveLinkToPocket;
+ }
+}
+
+// PocketReader
+// Listen for reader mode setup and add our button to the reader toolbar
+var PocketReader = {
+ _hidden: true,
+ get hidden() {
+ return this._hidden;
+ },
+ set hidden(hide) {
+ hide = !!hide;
+ if (hide === this._hidden)
+ return;
+ this._hidden = hide;
+ this.update();
+ },
+ startup: function() {
+ // Setup the listeners, update will be called when the widget is added,
+ // no need to do that now.
+ let mm = Services.mm;
+ mm.addMessageListener("Reader:OnSetup", this);
+ mm.addMessageListener("Reader:Clicked-pocket-button", this);
+ },
+ shutdown: function() {
+ let mm = Services.mm;
+ mm.removeMessageListener("Reader:OnSetup", this);
+ mm.removeMessageListener("Reader:Clicked-pocket-button", this);
+ this.hidden = true;
+ },
+ update: function() {
+ if (this.hidden) {
+ Services.mm.broadcastAsyncMessage("Reader:RemoveButton", { id: "pocket-button" });
+ } else {
+ Services.mm.broadcastAsyncMessage("Reader:AddButton",
+ { id: "pocket-button",
+ title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
+ image: "chrome://pocket/content/panels/img/pocket.svg#pocket-mark" });
+ }
+ },
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Reader:OnSetup": {
+ // Tell the reader about our button.
+ if (this.hidden)
+ break;
+ message.target.messageManager.
+ sendAsyncMessage("Reader:AddButton", { id: "pocket-button",
+ title: gPocketBundle.GetStringFromName("pocket-button.tooltiptext"),
+ image: "chrome://pocket/content/panels/img/pocket.svg#pocket-mark"});
+ break;
+ }
+ case "Reader:Clicked-pocket-button": {
+ let doc = message.target.ownerDocument;
+ let pocketWidget = doc.getElementById("pocket-button");
+ let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
+ if (placement) {
+ if (placement.area == CustomizableUI.AREA_PANEL) {
+ doc.defaultView.PanelUI.show().then(function() {
+ // The DOM node might not exist yet if the panel wasn't opened before.
+ pocketWidget = doc.getElementById("pocket-button");
+ pocketWidget.doCommand();
+ });
+ } else {
+ pocketWidget.doCommand();
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+
+function pktUIGetter(prop, window) {
+ return {
+ get: function() {
+ // delete any getters for properties loaded from main.js so we only load main.js once
+ delete window.pktUI;
+ delete window.pktApi;
+ delete window.pktUIMessaging;
+ Services.scriptloader.loadSubScript("chrome://pocket/content/main.js", window);
+ return window[prop];
+ },
+ configurable: true,
+ enumerable: true
+ };
+}
+
+var PocketOverlay = {
+ startup: function(reason) {
+ let styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"]
+ .getService(Ci.nsIStyleSheetService);
+ this._sheetType = styleSheetService.AUTHOR_SHEET;
+ this._cachedSheet = styleSheetService.preloadSheet(gPocketStyleURI,
+ this._sheetType);
+ Services.ppmm.loadProcessScript(PROCESS_SCRIPT, true);
+ PocketReader.startup();
+ CustomizableUI.addListener(this);
+ CreatePocketWidget(reason);
+ PocketContextMenu.init();
+
+ for (let win of CustomizableUI.windows) {
+ this.onWindowOpened(win);
+ }
+ },
+ shutdown: function(reason) {
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.broadcastAsyncMessage("PocketShuttingDown");
+ // Although the ppmm loads the scripts into the chrome process as well,
+ // we need to manually unregister here anyway to ensure these aren't part
+ // of the chrome process and avoid errors.
+ AboutPocket.aboutSaved.unregister();
+ AboutPocket.aboutSignup.unregister();
+
+ CustomizableUI.removeListener(this);
+ for (let window of CustomizableUI.windows) {
+ for (let id of ["panelMenu_pocket", "menu_pocket", "BMB_pocket",
+ "panelMenu_pocketSeparator", "menu_pocketSeparator",
+ "BMB_pocketSeparator"]) {
+ let element = window.document.getElementById(id);
+ if (element)
+ element.remove();
+ }
+ this.removeStyles(window);
+ // remove script getters/objects
+ delete window.Pocket;
+ delete window.pktApi;
+ delete window.pktUI;
+ delete window.pktUIMessaging;
+ }
+ CustomizableUI.destroyWidget("pocket-button");
+ PocketContextMenu.shutdown();
+ PocketReader.shutdown();
+ },
+ onWindowOpened: function(window) {
+ if (window.hasOwnProperty("pktUI"))
+ return;
+ this.setWindowScripts(window);
+ this.addStyles(window);
+ this.updateWindow(window);
+ },
+ setWindowScripts: function(window) {
+ XPCOMUtils.defineLazyModuleGetter(window, "Pocket",
+ "chrome://pocket/content/Pocket.jsm");
+ // Can't use XPCOMUtils for these because the scripts try to define the variables
+ // on window, and so the defineProperty inside defineLazyGetter fails.
+ Object.defineProperty(window, "pktApi", pktUIGetter("pktApi", window));
+ Object.defineProperty(window, "pktUI", pktUIGetter("pktUI", window));
+ Object.defineProperty(window, "pktUIMessaging", pktUIGetter("pktUIMessaging", window));
+ },
+ // called for each window as it is opened
+ updateWindow: function(window) {
+ // insert our three menu items
+ let document = window.document;
+ let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
+
+ // add to bookmarksMenu
+ let sib = document.getElementById("menu_bookmarkThisPage");
+ if (sib && !document.getElementById("menu_pocket")) {
+ let menu = createElementWithAttrs(document, "menuitem", {
+ "id": "menu_pocket",
+ "label": gPocketBundle.GetStringFromName("pocketMenuitem.label"),
+ "class": "menuitem-iconic", // OSX only
+ "oncommand": "openUILink(Pocket.listURL, event);",
+ "hidden": hidden
+ });
+ let sep = createElementWithAttrs(document, "menuseparator", {
+ "id": "menu_pocketSeparator",
+ "hidden": hidden
+ });
+ sib.parentNode.insertBefore(menu, sib);
+ sib.parentNode.insertBefore(sep, sib);
+ }
+
+ // add to bookmarks-menu-button
+ sib = document.getElementById("BMB_bookmarksToolbar");
+ if (sib && !document.getElementById("BMB_pocket")) {
+ let menu = createElementWithAttrs(document, "menuitem", {
+ "id": "BMB_pocket",
+ "label": gPocketBundle.GetStringFromName("pocketMenuitem.label"),
+ "class": "menuitem-iconic bookmark-item subviewbutton",
+ "oncommand": "openUILink(Pocket.listURL, event);",
+ "hidden": hidden
+ });
+ let sep = createElementWithAttrs(document, "menuseparator", {
+ "id": "BMB_pocketSeparator",
+ "hidden": hidden
+ });
+ sib.parentNode.insertBefore(menu, sib);
+ sib.parentNode.insertBefore(sep, sib);
+ }
+
+ // add to PanelUI-bookmarks
+ sib = document.getElementById("panelMenuBookmarkThisPage");
+ if (sib && !document.getElementById("panelMenu_pocket")) {
+ let menu = createElementWithAttrs(document, "toolbarbutton", {
+ "id": "panelMenu_pocket",
+ "label": gPocketBundle.GetStringFromName("pocketMenuitem.label"),
+ "class": "subviewbutton cui-withicon",
+ "oncommand": "openUILink(Pocket.listURL, event);",
+ "hidden": hidden
+ });
+ let sep = createElementWithAttrs(document, "toolbarseparator", {
+ "id": "panelMenu_pocketSeparator",
+ "hidden": hidden
+ });
+ // nextSibling is no-id toolbarseparator
+ // insert separator first then button
+ sib = sib.nextSibling;
+ sib.parentNode.insertBefore(sep, sib);
+ sib.parentNode.insertBefore(menu, sib);
+ }
+ },
+ onWidgetAfterDOMChange: function(aWidgetNode) {
+ if (aWidgetNode.id != "pocket-button") {
+ return;
+ }
+ let doc = aWidgetNode.ownerDocument;
+ let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
+ for (let prefix of ["panelMenu_", "menu_", "BMB_"]) {
+ let element = doc.getElementById(prefix + "pocket");
+ if (element) {
+ element.hidden = hidden;
+ doc.getElementById(prefix + "pocketSeparator").hidden = hidden;
+ }
+ }
+ // enable or disable reader button
+ PocketReader.hidden = hidden;
+ },
+
+ addStyles: function(win) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ utils.addSheet(this._cachedSheet, this._sheetType);
+ },
+
+ removeStyles: function(win) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ utils.removeSheet(gPocketStyleURI, this._sheetType);
+ }
+
+}
+
+// use enabled pref as a way for tests (e.g. test_contextmenu.html) to disable
+// the addon when running.
+function prefObserver(aSubject, aTopic, aData) {
+ let enabled = Services.prefs.getBoolPref("extensions.pocket.enabled");
+ if (enabled)
+ PocketOverlay.startup(ADDON_ENABLE);
+ else
+ PocketOverlay.shutdown(ADDON_DISABLE);
+}
+
+function startup(data, reason) {
+ AddonManager.getAddonByID("isreaditlater@ideashower.com", addon => {
+ if (addon && addon.isActive)
+ return;
+ setDefaultPrefs();
+ // migrate enabled pref
+ if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
+ Services.prefs.setBoolPref("extensions.pocket.enabled", Services.prefs.getBoolPref("browser.pocket.enabled"));
+ Services.prefs.clearUserPref("browser.pocket.enabled");
+ }
+ // watch pref change and enable/disable if necessary
+ Services.prefs.addObserver("extensions.pocket.enabled", prefObserver, false);
+ if (!Services.prefs.getBoolPref("extensions.pocket.enabled"))
+ return;
+ PocketOverlay.startup(reason);
+ });
+}
+
+function shutdown(data, reason) {
+ // For speed sake, we should only do a shutdown if we're being disabled.
+ // On an app shutdown, just let it fade away...
+ if (reason != APP_SHUTDOWN) {
+ Services.prefs.removeObserver("extensions.pocket.enabled", prefObserver);
+ PocketOverlay.shutdown(reason);
+ }
+}
+
+function install() {
+}
+
+function uninstall() {
+}
diff --git a/browser/extensions/pocket/content/AboutPocket.jsm b/browser/extensions/pocket/content/AboutPocket.jsm
new file mode 100644
index 000000000..c7f57aa87
--- /dev/null
+++ b/browser/extensions/pocket/content/AboutPocket.jsm
@@ -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";
+
+const { interfaces: Ci, results: Cr, manager: Cm, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
+const PREF_LOG_LEVEL = "loop.debug.loglevel";
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
+ let consoleOptions = {
+ maxLogLevelPref: PREF_LOG_LEVEL,
+ prefix: "Loop"
+ };
+ return new ConsoleAPI(consoleOptions);
+});
+
+
+function AboutPage(chromeURL, aboutHost, classID, description, uriFlags) {
+ this.chromeURL = chromeURL;
+ this.aboutHost = aboutHost;
+ this.classID = Components.ID(classID);
+ this.description = description;
+ this.uriFlags = uriFlags;
+}
+
+AboutPage.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+ getURIFlags: function(aURI) { // eslint-disable-line no-unused-vars
+ return this.uriFlags;
+ },
+
+ newChannel: function(aURI, aLoadInfo) {
+ let newURI = Services.io.newURI(this.chromeURL, null, null);
+ let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
+ aLoadInfo);
+ channel.originalURI = aURI;
+
+ if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
+ let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(aURI);
+ channel.owner = principal;
+ }
+ return channel;
+ },
+
+ createInstance: function(outer, iid) {
+ if (outer !== null) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(iid);
+ },
+
+ register: function() {
+ Cm.QueryInterface(Ci.nsIComponentRegistrar).registerFactory(
+ this.classID, this.description,
+ "@mozilla.org/network/protocol/about;1?what=" + this.aboutHost, this);
+ },
+
+ unregister: function() {
+ Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(
+ this.classID, this);
+ }
+};
+
+/* exported AboutPocket */
+var AboutPocket = {};
+
+XPCOMUtils.defineLazyGetter(AboutPocket, "aboutSaved", () =>
+ new AboutPage("chrome://pocket/content/panels/saved.html",
+ "pocket-saved",
+ "{3e759f54-37af-7843-9824-f71b5993ceed}",
+ "About Pocket Saved",
+ Ci.nsIAboutModule.ALLOW_SCRIPT |
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)
+);
+
+XPCOMUtils.defineLazyGetter(AboutPocket, "aboutSignup", () =>
+ new AboutPage("chrome://pocket/content/panels/signup.html",
+ "pocket-signup",
+ "{8548329d-00c4-234e-8f17-75026db3b56e}",
+ "About Pocket Signup",
+ Ci.nsIAboutModule.ALLOW_SCRIPT |
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)
+);
+
+this.EXPORTED_SYMBOLS = ["AboutPocket"];
diff --git a/browser/extensions/pocket/content/Pocket.jsm b/browser/extensions/pocket/content/Pocket.jsm
new file mode 100644
index 000000000..54f9cdf11
--- /dev/null
+++ b/browser/extensions/pocket/content/Pocket.jsm
@@ -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";
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = ["Pocket"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+ "resource://gre/modules/ReaderMode.jsm");
+
+var Pocket = {
+ get site() { return Services.prefs.getCharPref("extensions.pocket.site"); },
+ get listURL() { return "https://" + Pocket.site + "/?src=ff_ext"; },
+
+ /**
+ * Functions related to the Pocket panel UI.
+ */
+ onPanelViewShowing(event) {
+ let document = event.target.ownerDocument;
+ let window = document.defaultView;
+ let iframe = window.pktUI.getPanelFrame();
+
+ let urlToSave = Pocket._urlToSave;
+ let titleToSave = Pocket._titleToSave;
+ Pocket._urlToSave = null;
+ Pocket._titleToSave = null;
+ // ViewShowing fires immediately before it creates the contents,
+ // in lieu of an AfterViewShowing event, just spin the event loop.
+ window.setTimeout(function() {
+ if (urlToSave) {
+ window.pktUI.tryToSaveUrl(urlToSave, titleToSave);
+ } else {
+ window.pktUI.tryToSaveCurrentPage();
+ }
+
+ // pocketPanelDidHide in main.js set iframe to about:blank when it was
+ // hidden, make sure we're loading the save panel.
+ if (iframe.contentDocument &&
+ iframe.contentDocument.readyState == "complete" &&
+ iframe.contentDocument.documentURI != "about:blank") {
+ window.pktUI.pocketPanelDidShow();
+ } else {
+ // iframe didn't load yet. This seems to always be the case when in
+ // the toolbar panel, but never the case for a subview.
+ // XXX this only being fired when it's a _capturing_ listener!
+ iframe.addEventListener("load", Pocket.onFrameLoaded, true);
+ }
+ }, 0);
+ },
+
+ onFrameLoaded(event) {
+ let document = event.currentTarget.ownerDocument;
+ let window = document.defaultView;
+ let iframe = window.pktUI.getPanelFrame();
+
+ iframe.removeEventListener("load", Pocket.onFrameLoaded, true);
+ window.pktUI.pocketPanelDidShow();
+ },
+
+ onPanelViewHiding(event) {
+ let window = event.target.ownerGlobal;
+ window.pktUI.pocketPanelDidHide(event);
+ },
+
+ _urlToSave: null,
+ _titleToSave: null,
+ savePage(browser, url, title) {
+ let document = browser.ownerDocument;
+ let pocketWidget = document.getElementById("pocket-button");
+ let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
+ if (!placement)
+ return;
+
+ this._urlToSave = url;
+ this._titleToSave = title;
+ if (placement.area == CustomizableUI.AREA_PANEL) {
+ let win = document.defaultView;
+ win.PanelUI.show().then(function() {
+ pocketWidget = document.getElementById("pocket-button");
+ pocketWidget.doCommand();
+ });
+ } else {
+ pocketWidget.doCommand();
+ }
+ },
+};
diff --git a/browser/extensions/pocket/content/main.js b/browser/extensions/pocket/content/main.js
new file mode 100644
index 000000000..3c1c5785e
--- /dev/null
+++ b/browser/extensions/pocket/content/main.js
@@ -0,0 +1,737 @@
+/*
+ * LICENSE
+ *
+ * POCKET MARKS
+ *
+ * Notwithstanding the permitted uses of the Software (as defined below) pursuant to the license set forth below, "Pocket," "Read It Later" and the Pocket icon and logos (collectively, the “Pocket Marks”) are registered and common law trademarks of Read It Later, Inc. This means that, while you have considerable freedom to redistribute and modify the Software, there are tight restrictions on your ability to use the Pocket Marks. This license does not grant you any rights to use the Pocket Marks except as they are embodied in the Software.
+ *
+ * ---
+ *
+ * SOFTWARE
+ *
+ * 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.
+ */
+
+/*
+ * Pocket UI module
+ *
+ * Handles interactions with Pocket buttons, panels and menus.
+ *
+ */
+
+// TODO : Get the toolbar icons from Firefox's build (Nikki needs to give us a red saved icon)
+// TODO : [needs clarificaiton from Fx] Firefox's plan was to hide Pocket from context menus until the user logs in. Now that it's an extension I'm wondering if we still need to do this.
+// TODO : [needs clarificaiton from Fx] Reader mode (might be a something they need to do since it's in html, need to investigate their code)
+// TODO : [needs clarificaiton from Fx] Move prefs within pktApi.s to sqlite or a local file so it's not editable (and is safer)
+// TODO : [nice to have] - Immediately save, buffer the actions in a local queue and send (so it works offline, works like our native extensions)
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+ "resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "pktApi",
+ "chrome://pocket/content/pktApi.jsm");
+
+var pktUI = (function() {
+
+ // -- Initialization (on startup and new windows) -- //
+ var _currentPanelDidShow;
+ var _currentPanelDidHide;
+
+ // Init panel id at 0. The first actual panel id will have the number 1 so
+ // in case at some point any panel has the id 0 we know there is something
+ // wrong
+ var _panelId = 0;
+
+ var overflowMenuWidth = 230;
+ var overflowMenuHeight = 475;
+ var savePanelWidth = 350;
+ var savePanelHeights = {collapsed: 153, expanded: 272};
+
+ // -- Event Handling -- //
+
+ /**
+ * Event handler when Pocket toolbar button is pressed
+ */
+
+ function pocketPanelDidShow(event) {
+ if (_currentPanelDidShow) {
+ _currentPanelDidShow(event);
+ }
+
+ }
+
+ function pocketPanelDidHide(event) {
+ if (_currentPanelDidHide) {
+ _currentPanelDidHide(event);
+ }
+
+ // clear the panel
+ getPanelFrame().setAttribute('src', 'about:blank');
+ }
+
+
+ // -- Communication to API -- //
+
+ /**
+ * Either save or attempt to log the user in
+ */
+ function tryToSaveCurrentPage() {
+ tryToSaveUrl(getCurrentUrl(), getCurrentTitle());
+ }
+
+ function tryToSaveUrl(url, title) {
+
+ // If the user is logged in, go ahead and save the current page
+ if (pktApi.isUserLoggedIn()) {
+ saveAndShowConfirmation(url, title);
+ return;
+ }
+
+ // If the user is not logged in, show the logged-out state to prompt them to authenticate
+ showSignUp();
+ }
+
+
+ // -- Panel UI -- //
+
+ /**
+ * Show the sign-up panel
+ */
+ function showSignUp() {
+ // AB test: Direct logged-out users to tab vs panel
+ if (pktApi.getSignupPanelTabTestVariant() == 'v2')
+ {
+ let site = Services.prefs.getCharPref("extensions.pocket.site");
+ openTabWithUrl('https://' + site + '/firefox_learnmore?s=ffi&t=autoredirect&tv=page_learnmore&src=ff_ext', true);
+
+ // force the panel closed before it opens
+ getPanel().hidePopup();
+
+ return;
+ }
+
+ // Control: Show panel as normal
+ getFirefoxAccountSignedInUser(function(userdata)
+ {
+ var fxasignedin = (typeof userdata == 'object' && userdata !== null) ? '1' : '0';
+ var startheight = 490;
+ var inOverflowMenu = isInOverflowMenu();
+ var controlvariant = pktApi.getSignupPanelTabTestVariant() == 'control';
+
+ if (inOverflowMenu)
+ {
+ startheight = overflowMenuHeight;
+ }
+ else
+ {
+ startheight = 460;
+ if (fxasignedin == '1')
+ {
+ startheight = 406;
+ }
+ }
+ if (!controlvariant) {
+ startheight = 427;
+ }
+ var variant;
+ if (inOverflowMenu)
+ {
+ variant = 'overflow';
+ }
+ else
+ {
+ variant = 'storyboard_lm';
+ }
+
+ showPanel("about:pocket-signup?pockethost="
+ + Services.prefs.getCharPref("extensions.pocket.site")
+ + "&fxasignedin="
+ + fxasignedin
+ + "&variant="
+ + variant
+ + '&controlvariant='
+ + controlvariant
+ + '&inoverflowmenu='
+ + inOverflowMenu
+ + "&locale="
+ + getUILocale(), {
+ onShow: function() {
+ },
+ onHide: panelDidHide,
+ width: inOverflowMenu ? overflowMenuWidth : 300,
+ height: startheight
+ });
+ });
+ }
+
+ /**
+ * Show the logged-out state / sign-up panel
+ */
+ function saveAndShowConfirmation(url, title) {
+
+ // Validate input parameter
+ if (typeof url !== 'undefined' && url.startsWith("about:reader?url=")) {
+ url = ReaderMode.getOriginalUrl(url);
+ }
+
+ var isValidURL = (typeof url !== 'undefined' && (url.startsWith("http") || url.startsWith('https')));
+
+ var inOverflowMenu = isInOverflowMenu();
+ var startheight = pktApi.isPremiumUser() && isValidURL ? savePanelHeights.expanded : savePanelHeights.collapsed;
+ if (inOverflowMenu) {
+ startheight = overflowMenuHeight;
+ }
+
+ var panelId = showPanel("about:pocket-saved?pockethost=" + Services.prefs.getCharPref("extensions.pocket.site") + "&premiumStatus=" + (pktApi.isPremiumUser() ? '1' : '0') + '&inoverflowmenu='+inOverflowMenu + "&locale=" + getUILocale(), {
+ onShow: function() {
+ var saveLinkMessageId = 'saveLink';
+
+ // Send error message for invalid url
+ if (!isValidURL) {
+ // TODO: Pass key for localized error in error object
+ let error = {
+ message: 'Only links can be saved',
+ localizedKey: "onlylinkssaved"
+ };
+ pktUIMessaging.sendErrorMessageToPanel(panelId, saveLinkMessageId, error);
+ return;
+ }
+
+ // Check online state
+ if (!navigator.onLine) {
+ // TODO: Pass key for localized error in error object
+ let error = {
+ message: 'You must be connected to the Internet in order to save to Pocket. Please connect to the Internet and try again.'
+ };
+ pktUIMessaging.sendErrorMessageToPanel(panelId, saveLinkMessageId, error);
+ return;
+ }
+
+ // Add url
+ var options = {
+ success: function(data, request) {
+ var item = data.item;
+ var successResponse = {
+ status: "success",
+ item: item
+ };
+ pktUIMessaging.sendMessageToPanel(panelId, saveLinkMessageId, successResponse);
+ },
+ error: function(error, request) {
+ // If user is not authorized show singup page
+ if (request.status === 401) {
+ showSignUp();
+ return;
+ }
+
+ // If there is no error message in the error use a
+ // complete catch-all
+ var errorMessage = error.message || "There was an error when trying to save to Pocket.";
+ var panelError = { message: errorMessage}
+
+ // Send error message to panel
+ pktUIMessaging.sendErrorMessageToPanel(panelId, saveLinkMessageId, panelError);
+ }
+ }
+
+ // Add title if given
+ if (typeof title !== "undefined") {
+ options.title = title;
+ }
+
+ // Send the link
+ pktApi.addLink(url, options);
+ },
+ onHide: panelDidHide,
+ width: inOverflowMenu ? overflowMenuWidth : savePanelWidth,
+ height: startheight
+ });
+ }
+
+ /**
+ * Open a generic panel
+ */
+ function showPanel(url, options) {
+
+ // Add new panel id
+ _panelId += 1;
+ url += ("&panelId=" + _panelId);
+
+ // We don't have to hide and show the panel again if it's already shown
+ // as if the user tries to click again on the toolbar button the overlay
+ // will close instead of the button will be clicked
+ var iframe = getPanelFrame();
+
+ // Register event handlers
+ registerEventMessages();
+
+ // Load the iframe
+ iframe.setAttribute('src', url);
+
+ // Uncomment to leave panel open -- for debugging
+ // panel.setAttribute('noautohide', true);
+ // panel.setAttribute('consumeoutsideclicks', false);
+ //
+
+ // For some reason setting onpopupshown and onpopuphidden on the panel directly didn't work, so
+ // do it this hacky way for now
+ _currentPanelDidShow = options.onShow;
+ _currentPanelDidHide = options.onHide;
+
+ resizePanel({
+ width: options.width,
+ height: options.height
+ });
+ return _panelId;
+ }
+
+ /**
+ * Resize the panel
+ * options = {
+ * width: ,
+ * height: ,
+ * animate [default false]
+ * }
+ */
+ function resizePanel(options) {
+ var iframe = getPanelFrame();
+ var subview = getSubview();
+
+ if (subview) {
+ // Use the subview's size
+ iframe.style.width = "100%";
+ iframe.style.height = subview.parentNode.clientHeight + "px";
+ } else {
+ // Set an explicit size, panel will adapt.
+ iframe.style.width = options.width + "px";
+ iframe.style.height = options.height + "px";
+ }
+ }
+
+ /**
+ * Called when the signup and saved panel was hidden
+ */
+ function panelDidHide() {
+ // clear the onShow and onHide values
+ _currentPanelDidShow = null;
+ _currentPanelDidHide = null;
+ }
+
+ /**
+ * Register all of the messages needed for the panels
+ */
+ function registerEventMessages() {
+ var iframe = getPanelFrame();
+
+ // Only register the messages once
+ var didInitAttributeKey = 'did_init';
+ var didInitMessageListener = iframe.getAttribute(didInitAttributeKey);
+ if (typeof didInitMessageListener !== "undefined" && didInitMessageListener == 1) {
+ return;
+ }
+ iframe.setAttribute(didInitAttributeKey, 1);
+
+ // When the panel is displayed it generated an event called
+ // "show": we will listen for that event and when it happens,
+ // send our own "show" event to the panel's script, so the
+ // script can prepare the panel for display.
+ var _showMessageId = "show";
+ pktUIMessaging.addMessageListener(iframe, _showMessageId, function(panelId, data) {
+ // Let panel know that it is ready
+ pktUIMessaging.sendMessageToPanel(panelId, _showMessageId);
+ });
+
+ // Open a new tab with a given url and activate if
+ var _openTabWithUrlMessageId = "openTabWithUrl";
+ pktUIMessaging.addMessageListener(iframe, _openTabWithUrlMessageId, function(panelId, data, contentPrincipal) {
+ try {
+ urlSecurityCheck(data.url, contentPrincipal, Services.scriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ } catch (ex) {
+ return;
+ }
+
+ // Check if the tab should become active after opening
+ var activate = true;
+ if (typeof data.activate !== "undefined") {
+ activate = data.activate;
+ }
+
+ var url = data.url;
+ openTabWithUrl(url, activate);
+ pktUIMessaging.sendResponseMessageToPanel(panelId, _openTabWithUrlMessageId, url);
+ });
+
+ // Close the panel
+ var _closeMessageId = "close";
+ pktUIMessaging.addMessageListener(iframe, _closeMessageId, function(panelId, data) {
+ getPanel().hidePopup();
+ });
+
+ // Send the current url to the panel
+ var _getCurrentURLMessageId = "getCurrentURL";
+ pktUIMessaging.addMessageListener(iframe, _getCurrentURLMessageId, function(panelId, data) {
+ pktUIMessaging.sendResponseMessageToPanel(panelId, _getCurrentURLMessageId, getCurrentUrl());
+ });
+
+ var _resizePanelMessageId = "resizePanel";
+ pktUIMessaging.addMessageListener(iframe, _resizePanelMessageId, function(panelId, data) {
+ resizePanel(data);
+ });
+
+ // Callback post initialization to tell background script that panel is "ready" for communication.
+ pktUIMessaging.addMessageListener(iframe, "listenerReady", function(panelId, data) {
+
+ });
+
+ pktUIMessaging.addMessageListener(iframe, "collapseSavePanel", function(panelId, data) {
+ if (!pktApi.isPremiumUser() && !isInOverflowMenu())
+ resizePanel({width:savePanelWidth, height:savePanelHeights.collapsed});
+ });
+
+ pktUIMessaging.addMessageListener(iframe, "expandSavePanel", function(panelId, data) {
+ if (!isInOverflowMenu())
+ resizePanel({width:savePanelWidth, height:savePanelHeights.expanded});
+ });
+
+ // Ask for recently accessed/used tags for auto complete
+ var _getTagsMessageId = "getTags";
+ pktUIMessaging.addMessageListener(iframe, _getTagsMessageId, function(panelId, data) {
+ pktApi.getTags(function(tags, usedTags) {
+ pktUIMessaging.sendResponseMessageToPanel(panelId, _getTagsMessageId, {
+ tags: tags,
+ usedTags: usedTags
+ });
+ });
+ });
+
+ // Ask for suggested tags based on passed url
+ var _getSuggestedTagsMessageId = "getSuggestedTags";
+ pktUIMessaging.addMessageListener(iframe, _getSuggestedTagsMessageId, function(panelId, data) {
+ pktApi.getSuggestedTagsForURL(data.url, {
+ success: function(data, response) {
+ var suggestedTags = data.suggested_tags;
+ var successResponse = {
+ status: "success",
+ value: {
+ suggestedTags: suggestedTags
+ }
+ }
+ pktUIMessaging.sendResponseMessageToPanel(panelId, _getSuggestedTagsMessageId, successResponse);
+ },
+ error: function(error, response) {
+ pktUIMessaging.sendErrorResponseMessageToPanel(panelId, _getSuggestedTagsMessageId, error);
+ }
+ })
+ });
+
+ // Pass url and array list of tags, add to existing save item accordingly
+ var _addTagsMessageId = "addTags";
+ pktUIMessaging.addMessageListener(iframe, _addTagsMessageId, function(panelId, data) {
+ pktApi.addTagsToURL(data.url, data.tags, {
+ success: function(data, response) {
+ var successResponse = {status: "success"};
+ pktUIMessaging.sendResponseMessageToPanel(panelId, _addTagsMessageId, successResponse);
+ },
+ error: function(error, response) {
+ pktUIMessaging.sendErrorResponseMessageToPanel(panelId, _addTagsMessageId, error);
+ }
+ });
+ });
+
+ // Based on clicking "remove page" CTA, and passed unique item id, remove the item
+ var _deleteItemMessageId = "deleteItem";
+ pktUIMessaging.addMessageListener(iframe, _deleteItemMessageId, function(panelId, data) {
+ pktApi.deleteItem(data.itemId, {
+ success: function(data, response) {
+ var successResponse = {status: "success"};
+ pktUIMessaging.sendResponseMessageToPanel(panelId, _deleteItemMessageId, successResponse);
+ },
+ error: function(error, response) {
+ pktUIMessaging.sendErrorResponseMessageToPanel(panelId, _deleteItemMessageId, error);
+ }
+ })
+ });
+
+ var _initL10NMessageId = "initL10N";
+ pktUIMessaging.addMessageListener(iframe, _initL10NMessageId, function(panelId, data) {
+ var strings = {};
+ var bundle = Services.strings.createBundle("chrome://pocket/locale/pocket.properties");
+ var e = bundle.getSimpleEnumeration();
+ while (e.hasMoreElements()) {
+ var str = e.getNext().QueryInterface(Components.interfaces.nsIPropertyElement);
+ if (str.key in data) {
+ strings[str.key] = bundle.formatStringFromName(str.key, data[str.key], data[str.key].length);
+ } else {
+ strings[str.key] = str.value;
+ }
+ }
+ pktUIMessaging.sendResponseMessageToPanel(panelId, _initL10NMessageId, { strings: strings });
+ });
+
+ }
+
+ // -- Browser Navigation -- //
+
+ /**
+ * Open a new tab with a given url and notify the iframe panel that it was opened
+ */
+
+ function openTabWithUrl(url) {
+ let recentWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (!recentWindow) {
+ Cu.reportError("Pocket: No open browser windows to openTabWithUrl");
+ return;
+ }
+
+ // If the user is in permanent private browsing than this is not an issue,
+ // since the current window will always share the same cookie jar as the other
+ // windows.
+ if (!PrivateBrowsingUtils.isWindowPrivate(recentWindow) ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ recentWindow.openUILinkIn(url, "tab");
+ return;
+ }
+
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (!PrivateBrowsingUtils.isWindowPrivate(win)) {
+ win.openUILinkIn(url, "tab");
+ return;
+ }
+ }
+
+ // If there were no non-private windows opened already.
+ recentWindow.openUILinkIn(url, "window");
+ }
+
+
+ // -- Helper Functions -- //
+
+ function getCurrentUrl() {
+ return getBrowser().currentURI.spec;
+ }
+
+ function getCurrentTitle() {
+ return getBrowser().contentTitle;
+ }
+
+ function getPanel() {
+ var frame = getPanelFrame();
+ var panel = frame;
+ while (panel && panel.localName != "panel") {
+ panel = panel.parentNode;
+ }
+ return panel;
+ }
+
+ function getPanelFrame() {
+ var frame = document.getElementById('pocket-panel-iframe');
+ if (!frame) {
+ var frameParent = document.getElementById("PanelUI-pocketView").firstChild;
+ frame = document.createElement("iframe");
+ frame.id = 'pocket-panel-iframe';
+ frame.setAttribute("type", "content");
+ frameParent.appendChild(frame);
+ }
+ return frame;
+ }
+
+ function getSubview() {
+ var view = document.getElementById("PanelUI-pocketView");
+ if (view && view.getAttribute("current") == "true")
+ return view;
+ return null;
+ }
+
+ function isInOverflowMenu() {
+ var subview = getSubview();
+ return !!subview;
+ }
+
+ function getFirefoxAccountSignedInUser(callback) {
+ fxAccounts.getSignedInUser().then(userData => {
+ callback(userData);
+ }).then(null, error => {
+ callback();
+ });
+ }
+
+ function getUILocale() {
+ var locale = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry).
+ getSelectedLocale("browser");
+ return locale;
+ }
+
+ /**
+ * Public functions
+ */
+ return {
+ getPanelFrame: getPanelFrame,
+
+ openTabWithUrl: openTabWithUrl,
+
+ pocketPanelDidShow: pocketPanelDidShow,
+ pocketPanelDidHide: pocketPanelDidHide,
+
+ tryToSaveUrl: tryToSaveUrl,
+ tryToSaveCurrentPage: tryToSaveCurrentPage
+ };
+}());
+
+// -- Communication to Background -- //
+// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
+var pktUIMessaging = (function() {
+
+ /**
+ * Prefix message id for message listening
+ */
+ function prefixedMessageId(messageId) {
+ return 'PKT_' + messageId;
+ }
+
+ /**
+ * Register a listener and callback for a specific messageId
+ */
+ function addMessageListener(iframe, messageId, callback) {
+ iframe.addEventListener(prefixedMessageId(messageId), function(e) {
+ var nodePrincipal = e.target.nodePrincipal;
+ // ignore to ensure we do not pick up other events in the browser
+ if (!nodePrincipal || !nodePrincipal.URI || !nodePrincipal.URI.spec.startsWith("about:pocket")) {
+ return;
+ }
+
+ // Pass in information to callback
+ var payload = JSON.parse(e.target.getAttribute("payload"))[0];
+ var panelId = payload.panelId;
+ var data = payload.data;
+ callback(panelId, data, nodePrincipal);
+
+ // Cleanup the element
+ e.target.parentNode.removeChild(e.target);
+
+ }, false, true);
+ }
+
+ /**
+ * Send a message to the panel's iframe
+ */
+ function sendMessageToPanel(panelId, messageId, payload) {
+
+ if (!isPanelIdValid(panelId)) { return; }
+
+ var panelFrame = pktUI.getPanelFrame();
+ if (!isPocketPanelFrameValid(panelFrame)) { return; }
+
+ var doc = panelFrame.contentWindow.document;
+ var documentElement = doc.documentElement;
+
+ // Send message to panel
+ var panelMessageId = prefixedMessageId(panelId + '_' + messageId);
+
+ var AnswerEvt = doc.createElement("PKTMessage");
+ AnswerEvt.setAttribute("payload", JSON.stringify([payload]));
+ documentElement.appendChild(AnswerEvt);
+
+ var event = doc.createEvent("HTMLEvents");
+ event.initEvent(panelMessageId, true, false);
+ AnswerEvt.dispatchEvent(event);
+ }
+
+ function sendResponseMessageToPanel(panelId, messageId, payload) {
+ var responseMessageId = messageId + "Response";
+ sendMessageToPanel(panelId, responseMessageId, payload);
+ }
+
+ /**
+ * Helper function to package an error object and send it to the panel
+ * iframe as a message response
+ */
+ function sendErrorMessageToPanel(panelId, messageId, error) {
+ var errorResponse = {status: "error", error: error};
+ sendMessageToPanel(panelId, messageId, errorResponse);
+ }
+
+ function sendErrorResponseMessageToPanel(panelId, messageId, error) {
+ var errorResponse = {status: "error", error: error};
+ sendResponseMessageToPanel(panelId, messageId, errorResponse);
+ }
+
+ /**
+ * Validation
+ */
+
+ function isPanelIdValid(panelId) {
+ // First check if panelId has a valid value > 0. We set the panelId to
+ // 0 to start. But if for some reason the message is attempted to be
+ // sent before the panel has a panelId, then it's going to send out
+ // a message with panelId 0, which is never going to be heard. If this
+ // happens, it means some race condition occurred where the panel was
+ // trying to communicate before it should.
+ if (panelId === 0) {
+ console.warn("Tried to send message to panel with id 0.")
+ return false;
+ }
+
+ return true
+ }
+
+ function isPocketPanelFrameValid(panelFrame) {
+ // Check if panel is available if not throw a warning and bailout.
+ // We likely try to send to a panel that is not visible anymore
+ if (typeof panelFrame === "undefined") {
+ console.warn("Pocket panel frame is undefined");
+ return false;
+ }
+
+ var contentWindow = panelFrame.contentWindow;
+ if (typeof contentWindow == "undefined") {
+ console.warn("Pocket panel frame content window is undefined");
+ return false;
+ }
+
+ var doc = contentWindow.document;
+ if (typeof doc === "undefined") {
+ console.warn("Pocket panel frame content window document is undefined");
+ return false;
+ }
+
+ var documentElement = doc.documentElement;
+ if (typeof documentElement === "undefined") {
+ console.warn("Pocket panel frame content window document document element is undefined");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Public
+ */
+ return {
+ addMessageListener: addMessageListener,
+ sendMessageToPanel: sendMessageToPanel,
+ sendResponseMessageToPanel: sendResponseMessageToPanel,
+ sendErrorMessageToPanel: sendErrorMessageToPanel,
+ sendErrorResponseMessageToPanel: sendErrorResponseMessageToPanel
+ }
+}());
diff --git a/browser/extensions/pocket/content/panels/css/firasans.css b/browser/extensions/pocket/content/panels/css/firasans.css
new file mode 100644
index 000000000..5915345d6
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/css/firasans.css
@@ -0,0 +1,6 @@
+@font-face {
+ font-family: 'FiraSans';
+ src: url('../fonts/FiraSans-Regular.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+} \ No newline at end of file
diff --git a/browser/extensions/pocket/content/panels/css/normalize.css b/browser/extensions/pocket/content/panels/css/normalize.css
new file mode 100644
index 000000000..b7b4b746e
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/css/normalize.css
@@ -0,0 +1,424 @@
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+ overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit; /* 1 */
+ font: inherit; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+ overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+ line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ box-sizing: content-box;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+ font-weight: bold;
+}
+
+/* Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+td,
+th {
+ padding: 0;
+}
+
+/* Normalization for FF panel defauts
+ ========================================================================== */
+html {
+ outline: none;
+ padding: 0;
+}
+
+a {
+ color: #0095dd;
+ margin: 0;
+ outline: none;
+ padding: 0;
+ text-decoration: none;
+}
+
+a:hover,
+a:active {
+ color: #008acb;
+ text-decoration: underline;
+}
+
+a:active {
+ color: #006b9d;
+}
diff --git a/browser/extensions/pocket/content/panels/css/saved.css b/browser/extensions/pocket/content/panels/css/saved.css
new file mode 100644
index 000000000..d3f88d04c
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/css/saved.css
@@ -0,0 +1,825 @@
+/* saved.css
+ *
+ * Description:
+ * With base elements out of the way, this sets all custom styling for the page saved dialog.
+ *
+ * Contents:
+ * Global
+ * Loading spinner
+ * Core detail
+ * Tag entry
+ * Recent/suggested tags
+ * Premium upsell
+ * Token input/autocomplete
+ * Overflow mode
+ * Language overrides
+ */
+
+/*=Global
+--------------------------------------------------------------------------------------- */
+.pkt_ext_containersaved {
+ background-color: #fbfbfb;
+ border-radius: 4px;
+ display: block;
+ font-size: 16px;
+ font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ padding: 0;
+ position: relative;
+ text-align: center;
+}
+.pkt_ext_cf:after {
+ content: " ";
+ display:table;
+ clear:both;
+}
+.pkt_ext_containersaved .pkt_ext_tag_detail,
+.pkt_ext_containersaved .pkt_ext_recenttag_detail,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail {
+ margin: 0 auto;
+ padding: 0.25em 1em;
+ position: relative;
+ width: auto;
+}
+
+/*=Loading spinner
+--------------------------------------------------------------------------------------- */
+@keyframes pkt_ext_spin {
+ to {
+ transform: rotate(1turn);
+ }
+}
+.pkt_ext_containersaved {
+ font-size: 16px;
+}
+.pkt_ext_containersaved .pkt_ext_loadingspinner {
+ position: relative;
+ display: inline-block;
+ height: 2.5em;
+ left: 50%;
+ margin: 2em 0 0 -1.25em;
+ font-size: 10px;
+ text-indent: 999em;
+ position: absolute;
+ top: 4em;
+ overflow: hidden;
+ width: 2.5em;
+ animation: pkt_ext_spin 0.7s infinite steps(8);
+}
+.pkt_ext_containersaved .pkt_ext_loadingspinner:before,
+.pkt_ext_containersaved .pkt_ext_loadingspinner:after,
+.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before,
+.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 1.125em;
+ width: 0.25em;
+ height: 0.75em;
+ border-radius: .2em;
+ background: #eee;
+ box-shadow: 0 1.75em #eee;
+ transform-origin: 50% 1.25em;
+}
+.pkt_ext_containersaved .pkt_ext_loadingspinner:before {
+ background: #555;
+}
+.pkt_ext_containersaved .pkt_ext_loadingspinner:after {
+ transform: rotate(-45deg);
+ background: #777;
+}
+.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before {
+ transform: rotate(-90deg);
+ background: #999;
+}
+.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after {
+ transform: rotate(-135deg);
+ background: #bbb;
+}
+
+/*=Core detail
+--------------------------------------------------------------------------------------- */
+.pkt_ext_containersaved .pkt_ext_initload {
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+.pkt_ext_containersaved .pkt_ext_detail {
+ max-height: 0;
+ opacity: 0;
+ position: relative;
+ z-index: 10;
+}
+.pkt_ext_container_detailactive .pkt_ext_initload {
+ opacity: 0;
+}
+.pkt_ext_container_detailactive .pkt_ext_initload .pkt_ext_loadingspinner,
+.pkt_ext_container_finalstate .pkt_ext_initload .pkt_ext_loadingspinner {
+ animation: none;
+}
+.pkt_ext_container_detailactive .pkt_ext_detail {
+ max-height: 20em;
+ opacity: 1;
+}
+.pkt_ext_container_finalstate .pkt_ext_edit_msg,
+.pkt_ext_container_finalstate .pkt_ext_tag_detail,
+.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail,
+.pkt_ext_container_finalstate .pkt_ext_item_actions {
+ opacity: 0;
+ transition: opacity 0.2s ease-out;
+}
+.pkt_ext_container_finalerrorstate .pkt_ext_edit_msg,
+.pkt_ext_container_finalerrorstate .pkt_ext_tag_detail,
+.pkt_ext_container_finalerrorstate .pkt_ext_suggestedtag_detail,
+.pkt_ext_container_finalerrorstate .pkt_ext_item_actions {
+ display: none;
+ transition: none;
+}
+.pkt_ext_containersaved h2 {
+ background: transparent;
+ border: none;
+ color: #333;
+ display: block;
+ float: none;
+ font-size: 18px;
+ font-weight: normal;
+ letter-spacing: normal;
+ line-height: 1;
+ margin: 19px 0 4px;
+ padding: 0;
+ position: relative;
+ text-align: left;
+ text-transform: none;
+}
+@keyframes fade_in_out {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+.pkt_ext_container_finalstate h2 {
+ animation: fade_in_out 0.4s ease-out;
+}
+.pkt_ext_container_finalerrorstate h2 {
+ animation: none;
+ color: #d74345;
+}
+.pkt_ext_containersaved .pkt_ext_errordetail {
+ display: none;
+ font-size: 12px;
+ font-weight: normal;
+ left: 6.4em;
+ max-width: 21em;
+ opacity: 0;
+ position: absolute;
+ top: 2.7em;
+ text-align: left;
+ visibility: hidden;
+}
+.pkt_ext_container_finalerrorstate .pkt_ext_errordetail {
+ display: block;
+ opacity: 1;
+ visibility: visible;
+}
+.pkt_ext_containersaved .pkt_ext_logo {
+ background: url(../img/pocketlogosolo@1x.png) center center no-repeat;
+ display: block;
+ float: left;
+ height: 40px;
+ padding: 1.25em 1em;
+ position: relative;
+ width: 44px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersaved .pkt_ext_logo {
+ background-image: url(../img/pocketlogosolo@2x.png);
+ background-size: 44px 40px;
+ }
+}
+.pkt_ext_container_finalerrorstate .pkt_ext_logo {
+ background-image: url(../img/pocketerror@1x.png);
+ height: 44px;
+ width: 44px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_container_finalerrorstate .pkt_ext_logo {
+ background-image: url(../img/pocketerror@2x.png);
+ background-size: 44px 44px;
+ }
+}
+.pkt_ext_containersaved .pkt_ext_topdetail {
+ float: left;
+}
+.pkt_ext_containersaved .pkt_ext_edit_msg {
+ box-sizing: border-box;
+ display: none;
+ font-size: 0.75em;
+ left: auto;
+ padding: 0 1.4em;
+ position: absolute;
+ text-align: left;
+ top: 8.7em;
+ width: 100%;
+}
+.pkt_ext_containersaved .pkt_ext_edit_msg_error {
+ color: #d74345;
+}
+.pkt_ext_containersaved .pkt_ext_edit_msg_active {
+ display: block;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions {
+ background: transparent;
+ float: none;
+ height: auto;
+ margin-bottom: 1em;
+ margin-top: 0;
+ width: auto;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions_disabled {
+ opacity: 0.5;
+}
+.pkt_ext_container_finalstate .pkt_ext_item_actions_disabled {
+ opacity: 0;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions ul {
+ background: none;
+ display: block;
+ float: none;
+ font-size: 16px;
+ height: auto;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions li {
+ box-sizing: border-box;
+ background: none;
+ border: 0;
+ float: left;
+ list-style: none;
+ line-height: 0.8;
+ height: auto;
+ padding-right: 0.4em;
+ width: auto;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions li:before {
+ content: none;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_actions_separator {
+ border-left: 2px solid #777;
+ height: 0.75em;
+ margin-top: 0.3em;
+ padding: 0;
+ width: 10px;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions a {
+ background: transparent;
+ color: #0095dd;
+ display: block;
+ font-feature-settings: normal;
+ font-size: 12px;
+ font-weight: normal;
+ letter-spacing: normal;
+ line-height: inherit;
+ height: auto;
+ margin: 0;
+ padding: 0.5em;
+ float: left;
+ text-align: left;
+ text-decoration: none;
+ text-transform: none;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions a:hover {
+ color: #008acb;
+ text-decoration: underline;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions a:before,
+.pkt_ext_containersaved .pkt_ext_item_actions a:after {
+ background: transparent;
+ display: none;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions_disabled a {
+ cursor: default;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_openpocket {
+ float: right;
+ padding-right: 0.7em;
+ text-align: right;
+}
+.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_removeitem {
+ padding-left: 0;
+}
+.pkt_ext_containersaved .pkt_ext_close {
+ background: url(../img/tag_close@1x.png) center center no-repeat;
+ color: #333;
+ display: block;
+ font-size: 0.8em;
+ height: 10px;
+ right: 0.5em;
+ overflow: hidden;
+ position: absolute;
+ text-align: center;
+ text-indent: -9999px;
+ top: -1em;
+ width: 10px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersaved .pkt_ext_close {
+ background-image: url(../img/tag_close@2x.png);
+ background-size: 8px 8px;
+ }
+}
+.pkt_ext_containersaved .pkt_ext_close:hover {
+ color: #000;
+ text-decoration: none;
+}
+
+/*=Tag entry
+--------------------------------------------------------------------------------------- */
+.pkt_ext_containersaved .pkt_ext_tag_detail {
+ border: 1px solid #c1c1c1;
+ border-radius: 2px;
+ font-size: 16px;
+ clear: both;
+ margin: 1.25em 1em;
+ padding: 0;
+ display: flex;
+}
+.pkt_ext_containersaved .pkt_ext_tag_error {
+ border: none;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper {
+ box-sizing: border-box;
+ flex: 1;
+ background-color: #fff;
+ border-right: 1px solid #c3c3c3;
+ color: #333;
+ display: block;
+ float: none;
+ font-size: 0.875em;
+ list-style: none;
+ margin: 0;
+ overflow: hidden;
+ padding: 0.25em 0.5em;
+ width: 14em;
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+}
+.pkt_ext_containersaved .pkt_ext_tag_error .pkt_ext_tag_input_wrapper {
+ border: 1px solid #d74345;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list {
+ display: block;
+ left: 0;
+ height: 1.7em;
+ overflow: hidden;
+ position: relative;
+ width: 60em;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list,
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li {
+ font-size: 14px;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li {
+ height: auto;
+ width: auto;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li:before {
+ content: none;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input {
+ border: 0;
+ box-shadow: none;
+ background-color: #fff;
+ color: #333;
+ font-size: 14px;
+ float: left;
+ line-height: normal;
+ height: auto;
+ min-height: 0;
+ min-width: 5em;
+ padding: 3px 2px 1px;
+ text-transform: none;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input::placeholder {
+ color: #a9a9a9;
+ letter-spacing: normal;
+ text-transform: none;
+}
+.pkt_ext_containersaved .input_disabled {
+ cursor: default;
+ opacity: 0.5;
+}
+.pkt_ext_containersaved .pkt_ext_btn {
+ box-sizing: border-box;
+ color: #333;
+ float: none;
+ font-size: 0.875em;
+ font-size: 14px;
+ letter-spacing: normal;
+ height: 2.2em;
+ min-width: 4em;
+ padding: 0.5em 0;
+ text-decoration: none;
+ text-transform: none;
+ width: auto;
+}
+.pkt_ext_containersaved .pkt_ext_btn:hover {
+ background-color: #ebebeb;
+}
+.pkt_ext_containersaved .pkt_ext_btn:active {
+ background-color: #dadada;
+}
+.pkt_ext_containersaved .pkt_ext_btn_disabled,
+.pkt_ext_containersaved .pkt_ext_btn_disabled:hover,
+.pkt_ext_containersaved .pkt_ext_btn_disabled:active {
+ background-color: transparent;
+ cursor: default;
+ opacity: 0.4;
+}
+.pkt_ext_containersaved .pkt_ext_tag_error .pkt_ext_btn {
+ border: 1px solid #c3c3c3;
+ border-width: 1px 1px 1px 0;
+ height: 2.35em;
+}
+.pkt_ext_containersaved .autocomplete-suggestions {
+ margin-top: 2.2em;
+}
+
+/*=Recent/suggested tags
+--------------------------------------------------------------------------------------- */
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detailshown {
+ border-top: 1px solid #c1c1c1;
+ bottom: 0;
+ box-sizing: border-box;
+ background: #ebebeb;
+ clear: both;
+ left: 0;
+ opacity: 0;
+ min-height: 110px;
+ position: fixed;
+ visibility: hidden;
+ width: 100%;
+}
+.pkt_ext_container_detailactive .pkt_ext_suggestedtag_detail,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detailshown {
+ opacity: 1;
+ visibility: visible;
+}
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detailshown {
+ padding: 4px 0;
+}
+.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail {
+ opacity: 0;
+ visibility: hidden;
+}
+.pkt_ext_containersaved
+.pkt_ext_containersaved .pkt_ext_recenttag_detail h4,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail h4 {
+ color: #333;
+ font-size: 0.8125em;
+ font-size: 13px;
+ font-weight: normal;
+ font-style: normal;
+ letter-spacing: normal;
+ margin: 0.5em 0;
+ text-align: left;
+ text-transform: none;
+}
+.pkt_ext_containersaved .pkt_ext_recenttag_detail .pkt_ext_loadingspinner,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .pkt_ext_loadingspinner {
+ display: none;
+ position: absolute;
+}
+.pkt_ext_containersaved .pkt_ext_recenttag_detail_loading .pkt_ext_loadingspinner,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_loading .pkt_ext_loadingspinner {
+ display: block;
+ font-size: 6px;
+ left: 48%;
+}
+.pkt_ext_containersaved .pkt_ext_recenttag_detail ul,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail ul {
+ display: block;
+ margin: 0;
+ height: 2em;
+ overflow: hidden;
+ padding: 2px 0 0 0;
+}
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail ul {
+ height: auto;
+ margin: 0;
+ max-height: 4em;
+ padding-top: 6px;
+}
+.pkt_ext_containersaved .pkt_ext_recenttag_detail li,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li {
+ background: none;
+ float: left;
+ height: inherit;
+ line-height: 1.5;
+ list-style: none;
+ margin-bottom: 0.5em;
+ width: inherit;
+}
+.pkt_ext_containersaved .pkt_ext_recenttag_detail li:before,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li:before {
+ content: none;
+}
+.pkt_ext_containersaved .pkt_ext_recenttag_detail .recenttag_msg,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .suggestedtag_msg {
+ color: #333;
+ font-size: 0.8125em;
+ line-height: 1.2;
+ left: auto;
+ position: absolute;
+ text-align: left;
+ top: 2em;
+}
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .suggestedtag_msg {
+ margin-right: 1.3em;
+}
+.pkt_ext_containersaved .token_tag {
+ border-radius: 4px;
+ background: #f7f7f7;
+ border: 1px solid #c3c3c3;
+ color: #333;
+ font-size: 0.875em;
+ font-size: 14px;
+ font-weight: normal;
+ letter-spacing: normal;
+ margin-right: 0.5em;
+ padding: 0.125em 0.625em;
+ text-decoration: none;
+ text-transform: none;
+}
+.pkt_ext_containersaved .token_tag:hover {
+ background-color: #008acb;
+ border-color: #008acb;
+ color: #fff;
+ text-decoration: none;
+}
+.pkt_ext_containersaved .token_tag:before,
+.pkt_ext_containersaved .token_tag:after {
+ content: none;
+}
+.pkt_ext_containersaved .token_tag:hover span {
+ background-image: url(../img/tag_closeactive@1x.png);
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersaved .token_tag:hover span {
+ background-image: url(../img/tag_closeactive@2x.png);
+ background-size: 8px 8px;
+ }
+}
+.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag,
+.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag:hover,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag,
+.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag:hover {
+ background-color: #f7f7f7;
+ cursor: default;
+ opacity: 0.5;
+}
+.pkt_ext_containersaved .token_tag_inactive {
+ display: none;
+}
+
+/*=Premium upsell
+--------------------------------------------------------------------------------------- */
+.pkt_ext_detail .pkt_ext_premupsell {
+ background-color: #50bbb6;
+ display: block;
+ padding: 1.5em 0;
+ text-align: center;
+}
+.pkt_ext_premupsell h4 {
+ color: #fff;
+ font-size: 1em;
+ margin-bottom: 1em;
+}
+.pkt_ext_premupsell a {
+ color: #28605d;
+ border-bottom: 1px solid #47a7a3;
+ font-weight: normal;
+}
+.pkt_ext_premupsell a:hover {
+ color: #14302f;
+}
+
+/*=Token input/autocomplete
+--------------------------------------------------------------------------------------- */
+.token-input-dropdown-tag {
+ border-radius: 4px;
+ box-sizing: border-box;
+ background: #fff;
+ border: 1px solid #cdcdcd;
+ margin-top: 0.5em;
+ left: 0 !important;
+ overflow-y: auto;
+ top: 1.9em !important;
+ z-index: 9000;
+}
+.token-input-dropdown-tag ul {
+ height: inherit;
+ max-height: 115px;
+ margin: 0;
+ overflow: auto;
+ padding: 0.5em 0;
+}
+.token-input-dropdown-tag ul li {
+ background: none;
+ color: #333;
+ font-weight: normal;
+ font-size: 1em;
+ float: none;
+ height: inherit;
+ letter-spacing: normal;
+ list-style: none;
+ padding: 0.75em;
+ text-align: left;
+ text-transform: none;
+ width: inherit;
+}
+.token-input-dropdown-tag ul li:before {
+ content: none;
+}
+.token-input-dropdown ul li.token-input-selected-dropdown-item {
+ background-color: #008acb;
+ color: #fff;
+}
+.token-input-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+.token-input-list li {
+ text-align: left;
+ list-style: none;
+}
+.token-input-list li input {
+ border: 0;
+ background-color: white;
+}
+.pkt_ext_containersaved .token-input-token {
+ background: none;
+ border-radius: 4px;
+ border: 1px solid #c3c3c3;
+ overflow: hidden;
+ margin: 0;
+ padding: 0 8px;
+ background-color: #f7f7f7;
+ color: #000;
+ font-weight: normal;
+ cursor: default;
+ line-height: 1.5;
+ display: block;
+ width: auto;
+ margin: 0 0.2em;
+ float: left;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled {
+ position: relative;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled input {
+ opacity: 0.5;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-list {
+ opacity: 0.5;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .pkt_ext_tag_input_blocker {
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ z-index: 5;
+}
+.pkt_ext_containersaved .token-input-token p {
+ display: inline-block;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: inherit;
+ letter-spacing: normal;
+ padding: 0;
+ margin: 0;
+ text-transform: none;
+ vertical-align: top;
+ width: auto;
+}
+.pkt_ext_containersaved .token-input-token p:before {
+ content: none;
+ width: 0;
+}
+.pkt_ext_containersaved .token-input-token span {
+ background: url(../img/tag_close@1x.png) center center no-repeat;
+ cursor: pointer;
+ display: inline-block;
+ height: 8px;
+ margin: 0 0 0 8px;
+ overflow: hidden;
+ width: 8px;
+ text-indent: -99px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersaved .token-input-token span {
+ background-image: url(../img/tag_close@2x.png);
+ background-size: 8px 8px;
+ }
+}
+.pkt_ext_containersaved .token-input-selected-token {
+ background-color: #008acb;
+ border-color: #008acb;
+ color: #fff;
+}
+.pkt_ext_containersaved .token-input-selected-token span {
+ background-image: url(../img/tag_closeactive@1x.png);
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersaved .token-input-selected-token span {
+ background-image: url(../img/tag_closeactive@2x.png);
+ background-size: 8px 8px;
+ }
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token {
+ background-color: #f7f7f7;
+}
+.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token span {
+ color: #bbb;
+}
+
+/*=Overflow mode
+--------------------------------------------------------------------------------------- */
+.pkt_ext_saved_overflow .pkt_ext_logo {
+ float: none;
+ margin: 0.5em auto 0;
+}
+.pkt_ext_saved_overflow .pkt_ext_initload {
+ top: -8px;
+}
+.pkt_ext_saved_overflow .pkt_ext_loadingspinner {
+ top: 10em;
+}
+.pkt_ext_saved_overflow .pkt_ext_topdetail {
+ float: none;
+ margin: 0 auto;
+ padding: 0 1em;
+}
+.pkt_ext_saved_overflow h2 {
+ margin-bottom: 0.5em;
+ margin-top: 0;
+ text-align: center;
+}
+.pkt_ext_saved_overflow .pkt_ext_item_actions ul {
+ display: inline-block;
+ width: auto;
+}
+.pkt_ext_saved_overflow .pkt_ext_item_actions li {
+ float: none;
+ padding-left: 1em;
+ padding-right: 1em;
+ text-align: center;
+}
+.pkt_ext_saved_overflow .pkt_ext_item_actions .pkt_ext_removeitem,
+.pkt_ext_saved_overflow .pkt_ext_item_actions .pkt_ext_openpocket {
+ float: none;
+ text-align: center;
+ padding-left: 0;
+ padding-right: 0;
+}
+.pkt_ext_saved_overflow .pkt_ext_item_actions .pkt_ext_actions_separator {
+ display: none;
+}
+.pkt_ext_saved_overflow .pkt_ext_tag_detail {
+ margin-top: 0;
+}
+.pkt_ext_saved_overflow .pkt_ext_suggestedtag_detail,
+.pkt_ext_saved_overflow .pkt_ext_suggestedtag_detailshown {
+ top: 14.75em;
+}
+.pkt_ext_saved_overflow .pkt_ext_edit_msg {
+ top: 16em;
+}
+.pkt_ext_container_finalerrorstate.pkt_ext_saved_overflow .pkt_ext_errordetail {
+ box-sizing: border-box;
+ left: 0;
+ padding-left: 1em;
+ padding-right: 1em;
+ text-align: center;
+ top: 8.3em;
+ width: 100%;
+}
+
+/*=Language overrides
+--------------------------------------------------------------------------------------- */
+.pkt_ext_saved_es .pkt_ext_btn {
+ min-width: 5em;
+}
+.pkt_ext_saved_de .pkt_ext_btn,
+.pkt_ext_saved_ru .pkt_ext_btn {
+ min-width: 6em;
+}
diff --git a/browser/extensions/pocket/content/panels/css/signup.css b/browser/extensions/pocket/content/panels/css/signup.css
new file mode 100644
index 000000000..5c428a29b
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/css/signup.css
@@ -0,0 +1,424 @@
+/* signup.css
+ *
+ * Description:
+ * With base elements out of the way, this sets all custom styling for the extension.
+ *
+ * Contents:
+ * Global
+ * Core detail
+ * Core detail - storyboard
+ * Buttons
+ * Overflow mode
+ * Language overrides
+ */
+
+/*=Global
+--------------------------------------------------------------------------------------- */
+.pkt_ext_containersignup {
+ background-color: #ebebeb;
+ color: #333;
+ display: block;
+ font-size: 16px;
+ font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ text-align: center;
+}
+.pkt_ext_containersignup_inactive {
+ animation: pkt_ext_hide 0.3s ease-out;
+ opacity: 0;
+ visibility: hidden;
+}
+.pkt_ext_cf:after {
+ content: " ";
+ display: table;
+ clear: both;
+}
+@keyframes pkt_ext_hide {
+ 0% {
+ opacity: 1;
+ visibility: visible;
+ }
+ 99% {
+ opacity: 0;
+ visibility: visible;
+ }
+ 100% {
+ opacity: 0;
+ visibility: hidden;
+ }
+}
+
+/*=Core detail
+--------------------------------------------------------------------------------------- */
+.pkt_ext_containersignup p {
+ font-size: 14px;
+ color: #333;
+ font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 1.3;
+ margin: 0 auto 1.5em;
+ max-width: 260px;
+}
+.pkt_ext_containersignup a {
+ color: #4c8fd0;
+}
+.pkt_ext_containersignup a:hover {
+ color: #3076b9;
+}
+.pkt_ext_containersignup .pkt_ext_introdetail {
+ background-color: #fbfbfb;
+ border: 1px solid #c1c1c1;
+ border-width: 0 0 1px;
+}
+.pkt_ext_containersignup .pkt_ext_logo {
+ background: url(../img/pocketlogo@1x.png) center bottom no-repeat;
+ display: block;
+ height: 32px;
+ margin: 0 auto 15px;
+ padding-top: 25px;
+ position: relative;
+ text-indent: -9999px;
+ width: 123px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersignup .pkt_ext_logo {
+ background-image: url(../img/pocketlogo@2x.png);
+ background-size: 123px 32px;
+ }
+}
+.pkt_ext_containersignup .pkt_ext_introimg {
+ background: url(../img/pocketsignup_hero@1x.png) center center no-repeat;
+ display: block;
+ height: 125px;
+ margin: 0 auto;
+ position: relative;
+ text-indent: -9999px;
+ width: 255px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersignup .pkt_ext_introimg {
+ background-image: url(../img/pocketsignup_hero@2x.png);
+ background-size: 255px 125px;
+ }
+}
+.pkt_ext_containersignup .pkt_ext_tagline {
+ margin-bottom: 0.5em;
+}
+.pkt_ext_containersignup .pkt_ext_learnmore {
+ font-size: 12px;
+}
+.pkt_ext_containersignup .pkt_ext_learnmoreinactive {
+ visibility: hidden;
+}
+.pkt_ext_signupdetail h4 {
+ font-size: 12px;
+ font-weight: normal;
+}
+.pkt_ext_signupdetail .btn-container {
+ position: relative;
+ margin-bottom: 0.8em;
+}
+.pkt_ext_containersignup .ff_signuphelp {
+ background: url(../img/signup_help@1x.png) center center no-repeat;
+ display: block;
+ height: 18px;
+ margin-top: -9px;
+ right: -15px;
+ position: absolute;
+ text-indent: -9999px;
+ width: 18px;
+ top: 50%;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersignup .ff_signuphelp {
+ background-image: url(../img/signup_help@2x.png);
+ background-size: 18px 18px;
+ }
+}
+.pkt_ext_containersignup .alreadyhave {
+ font-size: 12px;
+ max-width: 320px;
+ margin-top: 15px;
+}
+.pkt_ext_containersignup .tryitnowspace {
+ margin-top: 22px;
+}
+.pkt_ext_signupdetail p.pkt_ext_tos {
+ color: #777;
+ font-size: 10px;
+ line-height: 1.5;
+ margin-top: 17px;
+ padding-top: 0;
+ max-width: 190px;
+}
+
+/*=Core detail - storyboard
+--------------------------------------------------------------------------------------- */
+.pkt_ext_introstory {
+ align-items: center;
+ display: flex;
+ padding: 20px;
+}
+.pkt_ext_introstory:after {
+ clear: both;
+ content: "";
+ display: table;
+}
+.pkt_ext_introstory p {
+ margin-bottom: 0;
+ text-align: left;
+}
+.pkt_ext_introstoryone {
+ padding: 20px 18px 15px 20px;
+}
+.pkt_ext_introstorytwo {
+ padding: 3px 0 0 20px;
+}
+.pkt_ext_introstorytwo .pkt_ext_tagline {
+ margin-bottom: 1.5em;
+}
+.pkt_ext_introstory_text {
+ flex: 1;
+}
+.pkt_ext_introstoryone_img,
+.pkt_ext_introstorytwo_img {
+ display: block;
+ overflow: hidden;
+ position: relative;
+ text-indent: -999px;
+}
+.pkt_ext_introstoryone_img {
+ background: url(../img/pocketsignup_button@1x.png) center right no-repeat;
+ height: 82px;
+ padding: 0 0 0 0.7em;
+ width: 82px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_introstoryone_img {
+ background-image: url(../img/pocketsignup_button@2x.png);
+ background-size: 82px 82px;
+ }
+}
+.pkt_ext_introstorytwo_img {
+ background: url(../img/pocketsignup_devices@1x.png) bottom right no-repeat;
+ height: 110px;
+ padding: 1em 0 0 0.7em;
+ width: 124px;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_introstorytwo_img {
+ background-image: url(../img/pocketsignup_devices@2x.png);
+ background-size: 124px 110px;
+ }
+}
+.pkt_ext_introstorydivider {
+ border-top: 1px solid #c1c1c1;
+ height: 1px;
+ margin: 0 auto;
+ width: 125px;
+}
+
+/*=Buttons
+--------------------------------------------------------------------------------------- */
+.pkt_ext_containersignup .btn {
+ background-color: #0096dd;
+ border: 1px solid #0095dd;
+ border-radius: 2px;
+ color: #fff;
+ display: inline-block;
+ font-family: "FiraSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 1;
+ margin: 0;
+ padding: 11px 45px;
+ text-align: center;
+ text-decoration: none;
+ text-shadow: 0 -1px 0 rgba(142,4,17,0.5);
+ transition: background-color 0.1s linear;
+ width: auto;
+}
+.pkt_ext_containersignup .btn-secondary {
+ background-color: #fbfbfb;
+ border-color: #c1c1c1;
+ color: #444;
+ text-shadow: 0 1px 0 rgba(255,255,255,0.5);
+}
+.pkt_ext_containersignup .btn-small {
+ padding: 6px 20px;
+}
+.pkt_ext_containersignup .btn-mini {
+ font-size: 14px;
+ padding: 5px 15px 4px;
+}
+.pkt_ext_containersignup .btn:hover {
+ background-color: #008acb;
+ color: #fff;
+ text-decoration: none;
+}
+.pkt_ext_containersignup .btn-secondary:hover,
+.pkt_ext_containersignup .btn-important:hover {
+ background-color: #f6f6f6;
+ color: #222;
+}
+.pkt_ext_containersignup .btn-disabled {
+ background-image: none;
+ color: #ccc;
+ color: rgba(255,255,255,0.6);
+ cursor: default;
+ opacity: 0.9;
+}
+.pkt_ext_containersignup .signup-btn-firefox,
+.pkt_ext_containersignup .signup-btn-tryitnow,
+.pkt_ext_containersignup .signup-btn-email,
+.pkt_ext_containersignup .signupinterim-btn-login,
+.pkt_ext_containersignup .signupinterim-btn-signup,
+.pkt_ext_containersignup .forgot-btn-submit,
+.pkt_ext_containersignup .forgotreset-btn-change {
+ min-width: 12.125em;
+ padding: 0.8em 1.1875em;
+ box-sizing: content-box;
+}
+.pkt_ext_containersignup .signup-btn-email {
+ position: relative;
+ z-index: 10;
+}
+.pkt_ext_containersignup .signup-btn-tryitnow,
+.pkt_ext_containersignup .signup-btn-firefox {
+ min-width: 14.5em;
+ position: relative;
+ padding: 0;
+}
+.pkt_ext_containersignup .signup-btn-tryitnow{
+ margin-top: 25px;
+}
+.pkt_ext_containersignup .signup-btn-firefox .logo {
+ background: url(../img/signup_firefoxlogo@1x.png) center center no-repeat;
+ height: 2.6em;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ width: 22px;
+ position: absolute;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_containersignup .signup-btn-firefox .logo {
+ background-image: url(../img/signup_firefoxlogo@2x.png);
+ background-size: 22px 22px;
+ }
+}
+.pkt_ext_containersignup .forgotreset-btn-change {
+ margin-bottom: 2em;
+}
+.pkt_ext_containersignup .signup-btn-tryitnow .text,
+.pkt_ext_containersignup .signup-btn-firefox .text {
+ display: inline-block;
+ padding: 0.8em 1.625em;
+ position: relative;
+ text-shadow: none;
+ white-space: nowrap;
+}
+.pkt_ext_containersignup .signup-btn-tryitnow .text,
+.pkt_ext_containersignup .signup-btn-firefox .text {
+ color: #fff;
+}
+.pkt_ext_containersignup .btn-disabled .text {
+ color: #ccc;
+ color: rgba(255,255,255,0.6);
+}
+
+/*=Overflow mode
+--------------------------------------------------------------------------------------- */
+.pkt_ext_signup_overflow .pkt_ext_tagline {
+ margin-bottom: 1em;
+ padding: 0 1em;
+}
+.pkt_ext_signup_overflow .pkt_ext_introimg {
+ background-size: 200px 98px;
+ height: 98px;
+ width: 200px;
+}
+.pkt_ext_signup_overflow .signup-btn-firefox,
+.pkt_ext_containersignup .signup-btn-tryitnow,
+.pkt_ext_signup_overflow .signup-btn-email {
+ font-size: 14px;
+ min-width: 12.6em;
+ padding-left: 0.75em;
+ padding-right: 0.75em;
+}
+.pkt_ext_signup_overflow .signup-btn-tryitnow .text,
+.pkt_ext_signup_overflow .signup-btn-firefox .text {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+/*=Language overrides
+--------------------------------------------------------------------------------------- */
+.pkt_ext_signup_de .pkt_ext_introstoryone_img {
+ margin-right: -5px;
+ padding-left: 0;
+}
+.pkt_ext_signup_de .pkt_ext_introstorytwo .pkt_ext_tagline,
+.pkt_ext_signup_es .pkt_ext_introstorytwo .pkt_ext_tagline,
+.pkt_ext_signup_ja .pkt_ext_introstorytwo .pkt_ext_tagline,
+.pkt_ext_signup_ru .pkt_ext_introstorytwo .pkt_ext_tagline {
+ margin-bottom: 0.5em;
+}
+.pkt_ext_signup_de .signup-btn-firefox .text,
+.pkt_ext_signup_de .signup-btn-tryitnow .text,
+.pkt_ext_signup_de .signup-btn-email,
+.pkt_ext_signup_es .pkt_ext_signupdetail_hero .signup-btn-firefox .text,
+.pkt_ext_signup_es .pkt_ext_signupdetail_hero .signup-btn-email,
+.pkt_ext_signup_ja .signup-btn-firefox .text,
+.pkt_ext_signup_ja .signup-btn-tryitnow .text,
+.pkt_ext_signup_ja .signup-btn-email,
+.pkt_ext_signup_ru .signup-btn-firefox .text,
+.pkt_ext_signup_ru .signup-btn-tryitnow .text,
+.pkt_ext_signup_ru .signup-btn-email {
+ font-size: 15px;
+}
+.pkt_ext_signup_ja .signup-btn-firefox .text,
+.pkt_ext_signup_ja .signup-btn-tryitnow .text,
+.pkt_ext_signup_ru .signup-btn-firefox .text,
+.pkt_ext_signup_ru .signup-btn-tryitnow .text {
+ left: 15px;
+}
+.pkt_ext_signup_de .signup-btn-firefox .logo,
+.pkt_ext_signup_es .pkt_ext_signupdetail_hero .signup-btn-firefox .logo,
+.pkt_ext_signup_ja .signup-btn-firefox .logo,
+.pkt_ext_signup_ru .signup-btn-firefox .logo {
+ height: 2.4em;
+}
+@media (min-resolution: 1.1dppx) {
+ .pkt_ext_signup_de .signup-btn-firefox .logo,
+ .pkt_ext_signup_es .pkt_ext_signupdetail_hero .signup-btn-firefox .logo,
+ .pkt_ext_signup_ja .signup-btn-firefox .logo,
+ .pkt_ext_signup_ru .signup-btn-firefox .logo {
+ height: 2.5em;
+ }
+}
+.pkt_ext_signup_de .signup-btn-email,
+.pkt_ext_signup_es .pkt_ext_signupdetail_hero .signup-btn-email,
+.pkt_ext_signup_ja .signup-btn-email,
+.pkt_ext_signup_ru .signup-btn-email {
+ min-width: 13em;
+ padding: 0.8533em 1.2667em;
+}
+.pkt_ext_signup_de .pkt_ext_logo,
+.pkt_ext_signup_es .pkt_ext_logo,
+.pkt_ext_signup_ru .pkt_ext_logo {
+ padding-top: 15px;
+}
+.pkt_ext_signup_de .pkt_ext_introdetailhero .pkt_ext_tagline,
+.pkt_ext_signup_es .pkt_ext_introdetailhero .pkt_ext_tagline,
+.pkt_ext_signup_ja .pkt_ext_introdetailhero .pkt_ext_tagline,
+.pkt_ext_signup_ru .pkt_ext_introdetailhero .pkt_ext_tagline {
+ font-size: 13px;
+}
+.pkt_ext_signup_overflow.pkt_ext_signup_de .signup-btn-firefox .logo,
+.pkt_ext_signup_overflow.pkt_ext_signup_es .signup-btn-firefox .logo,
+.pkt_ext_signup_overflow.pkt_ext_signup_ja .signup-btn-firefox .logo,
+.pkt_ext_signup_overflow.pkt_ext_signup_ru .signup-btn-firefox .logo {
+ display: none;
+}
diff --git a/browser/extensions/pocket/content/panels/fonts/FiraSans-Regular.woff b/browser/extensions/pocket/content/panels/fonts/FiraSans-Regular.woff
new file mode 100644
index 000000000..f466cdda9
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/fonts/FiraSans-Regular.woff
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocket.svg b/browser/extensions/pocket/content/panels/img/pocket.svg
new file mode 100644
index 000000000..d93fd6a15
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocket.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill: #808080;
+ }
+ use[id$="-added"] {
+ fill: #ee4056;
+ }
+ </style>
+ <defs>
+ <path id="pocket-mark-shape" d="M21.901,4.204C21.642,3.484,20.956,3,20.196,3h-0.01h-1.721H3.814C3.067,3,2.385,3.474,2.119,4.179 C2.04,4.388,2,4.606,2,4.828v6.082l0.069,1.21c0.29,2.751,1.707,5.155,3.899,6.832c0.039,0.03,0.079,0.06,0.119,0.089l0.025,0.018 c1.175,0.866,2.491,1.452,3.91,1.741C10.677,20.932,11.347,21,12.013,21c0.615,0,1.232-0.057,1.839-0.171 c0.073-0.014,0.145-0.028,0.219-0.044c0.02-0.004,0.042-0.012,0.064-0.023c1.359-0.299,2.621-0.87,3.753-1.704l0.025-0.018 c0.04-0.029,0.08-0.059,0.119-0.089c2.192-1.677,3.609-4.08,3.898-6.832L22,10.91V4.828C22,4.618,21.975,4.409,21.901,4.204z M17.667,10.539l-4.704,4.547c-0.266,0.256-0.608,0.385-0.949,0.385c-0.342,0-0.684-0.129-0.949-0.385l-4.705-4.547 c-0.547-0.528-0.565-1.403-0.04-1.954c0.524-0.551,1.392-0.569,1.939-0.041l3.756,3.63l3.755-3.63 c0.547-0.528,1.415-0.51,1.939,0.04C18.231,9.136,18.213,10.011,17.667,10.539z"/>
+ </defs>
+ <use id="pocket-mark" xlink:href="#pocket-mark-shape"/>
+ <use id="pocket-mark-added" xlink:href="#pocket-mark-shape"/>
+</svg>
diff --git a/browser/extensions/pocket/content/panels/img/pocketerror@1x.png b/browser/extensions/pocket/content/panels/img/pocketerror@1x.png
new file mode 100644
index 000000000..e2b4d04de
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketerror@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketerror@2x.png b/browser/extensions/pocket/content/panels/img/pocketerror@2x.png
new file mode 100644
index 000000000..d501503b0
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketerror@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketlogo@1x.png b/browser/extensions/pocket/content/panels/img/pocketlogo@1x.png
new file mode 100644
index 000000000..62b3db310
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketlogo@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketlogo@2x.png b/browser/extensions/pocket/content/panels/img/pocketlogo@2x.png
new file mode 100644
index 000000000..b0e80bff3
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketlogo@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketlogosolo@1x.png b/browser/extensions/pocket/content/panels/img/pocketlogosolo@1x.png
new file mode 100644
index 000000000..77dc16f8c
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketlogosolo@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketlogosolo@2x.png b/browser/extensions/pocket/content/panels/img/pocketlogosolo@2x.png
new file mode 100644
index 000000000..c467c5a29
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketlogosolo@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketmenuitem16.png b/browser/extensions/pocket/content/panels/img/pocketmenuitem16.png
new file mode 100644
index 000000000..b52db6abf
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketmenuitem16.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketmenuitem16@2x.png b/browser/extensions/pocket/content/panels/img/pocketmenuitem16@2x.png
new file mode 100644
index 000000000..69aa55b03
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketmenuitem16@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketsignup_button@1x.png b/browser/extensions/pocket/content/panels/img/pocketsignup_button@1x.png
new file mode 100644
index 000000000..12326fae3
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketsignup_button@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketsignup_button@2x.png b/browser/extensions/pocket/content/panels/img/pocketsignup_button@2x.png
new file mode 100644
index 000000000..5bdebc5e9
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketsignup_button@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketsignup_devices@1x.png b/browser/extensions/pocket/content/panels/img/pocketsignup_devices@1x.png
new file mode 100644
index 000000000..c4a7ad677
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketsignup_devices@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketsignup_devices@2x.png b/browser/extensions/pocket/content/panels/img/pocketsignup_devices@2x.png
new file mode 100644
index 000000000..157304c3e
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketsignup_devices@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketsignup_hero@1x.png b/browser/extensions/pocket/content/panels/img/pocketsignup_hero@1x.png
new file mode 100644
index 000000000..80c5bd486
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketsignup_hero@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/pocketsignup_hero@2x.png b/browser/extensions/pocket/content/panels/img/pocketsignup_hero@2x.png
new file mode 100644
index 000000000..36d0add61
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/pocketsignup_hero@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/signup_firefoxlogo@1x.png b/browser/extensions/pocket/content/panels/img/signup_firefoxlogo@1x.png
new file mode 100644
index 000000000..52cbe052c
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/signup_firefoxlogo@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/signup_firefoxlogo@2x.png b/browser/extensions/pocket/content/panels/img/signup_firefoxlogo@2x.png
new file mode 100644
index 000000000..cd218805e
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/signup_firefoxlogo@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/signup_help@1x.png b/browser/extensions/pocket/content/panels/img/signup_help@1x.png
new file mode 100644
index 000000000..5019025c0
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/signup_help@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/signup_help@2x.png b/browser/extensions/pocket/content/panels/img/signup_help@2x.png
new file mode 100644
index 000000000..6714bb3bc
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/signup_help@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/signup_or@1x.png b/browser/extensions/pocket/content/panels/img/signup_or@1x.png
new file mode 100644
index 000000000..318cea0f6
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/signup_or@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/signup_or@2x.png b/browser/extensions/pocket/content/panels/img/signup_or@2x.png
new file mode 100644
index 000000000..837f1814a
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/signup_or@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/tag_close@1x.png b/browser/extensions/pocket/content/panels/img/tag_close@1x.png
new file mode 100644
index 000000000..2dd02ba02
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/tag_close@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/tag_close@2x.png b/browser/extensions/pocket/content/panels/img/tag_close@2x.png
new file mode 100644
index 000000000..8bd0eec57
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/tag_close@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/tag_closeactive@1x.png b/browser/extensions/pocket/content/panels/img/tag_closeactive@1x.png
new file mode 100644
index 000000000..ad4239232
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/tag_closeactive@1x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/img/tag_closeactive@2x.png b/browser/extensions/pocket/content/panels/img/tag_closeactive@2x.png
new file mode 100644
index 000000000..80c35e3aa
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/img/tag_closeactive@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/content/panels/js/messages.js b/browser/extensions/pocket/content/panels/js/messages.js
new file mode 100644
index 000000000..ae08c3e73
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/js/messages.js
@@ -0,0 +1,78 @@
+// Documentation of methods used here are at:
+// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
+
+var pktPanelMessaging = (function() {
+
+ function panelIdFromURL(url) {
+ var panelId = url.match(/panelId=([\w|\d|\.]*)&?/);
+ if (panelId && panelId.length > 1) {
+ return panelId[1];
+ }
+
+ return 0;
+ }
+
+ function prefixedMessageId(messageId) {
+ return 'PKT_' + messageId;
+ }
+
+ function panelPrefixedMessageId(panelId, messageId) {
+ return prefixedMessageId(panelId + '_' + messageId);
+ }
+
+ function addMessageListener(panelId, messageId, callback) {
+ document.addEventListener(panelPrefixedMessageId(panelId, messageId), function(e) {
+
+ callback(JSON.parse(e.target.getAttribute("payload"))[0]);
+
+ // TODO: Figure out why e.target.parentNode is null
+ // e.target.parentNode.removeChild(e.target);
+
+ }, false);
+
+ }
+
+ function removeMessageListener(panelId, messageId, callback) {
+ document.removeEventListener(panelPrefixedMessageId(panelId, messageId), callback);
+ }
+
+ function sendMessage(panelId, messageId, payload, callback) {
+ // Payload needs to be an object in format:
+ // { panelId: panelId, data: {} }
+ var messagePayload = {
+ panelId: panelId,
+ data: (payload || {})
+ };
+
+ // Create a callback to listen for a response
+ if (callback) {
+ var messageResponseId = messageId + "Response";
+ var responseListener = function(responsePayload) {
+ callback(responsePayload);
+ removeMessageListener(panelId, messageResponseId, responseListener);
+ }
+
+ addMessageListener(panelId, messageResponseId, responseListener);
+ }
+
+ // Send message
+ var element = document.createElement("PKTMessageFromPanelElement");
+ element.setAttribute("payload", JSON.stringify([messagePayload]));
+ document.documentElement.appendChild(element);
+
+ var evt = document.createEvent("Events");
+ evt.initEvent(prefixedMessageId(messageId), true, false);
+ element.dispatchEvent(evt);
+ }
+
+
+ /**
+ * Public functions
+ */
+ return {
+ panelIdFromURL: panelIdFromURL,
+ addMessageListener : addMessageListener,
+ removeMessageListener : removeMessageListener,
+ sendMessage: sendMessage
+ };
+}());
diff --git a/browser/extensions/pocket/content/panels/js/saved.js b/browser/extensions/pocket/content/panels/js/saved.js
new file mode 100644
index 000000000..3abc8889a
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/js/saved.js
@@ -0,0 +1,608 @@
+/*
+PKT_SAVED_OVERLAY is the view itself and contains all of the methods to manipute the overlay and messaging.
+It does not contain any logic for saving or communication with the extension or server.
+*/
+var PKT_SAVED_OVERLAY = function (options)
+{
+ var myself = this;
+ this.inited = false;
+ this.active = false;
+ this.wrapper = null;
+ this.pockethost = "getpocket.com";
+ this.savedItemId = 0;
+ this.savedUrl = '';
+ this.premiumStatus = false;
+ this.preventCloseTimerCancel = false;
+ this.closeValid = true;
+ this.mouseInside = false;
+ this.autocloseTimer = null;
+ this.inoverflowmenu = false;
+ this.dictJSON = {};
+ this.autocloseTiming = 3500;
+ this.autocloseTimingFinalState = 2000;
+ this.mouseInside = false;
+ this.userTags = [];
+ this.cxt_suggested_available = 0;
+ this.cxt_entered = 0;
+ this.cxt_suggested = 0;
+ this.cxt_removed = 0;
+ this.justaddedsuggested = false;
+ this.fillTagContainer = function(tags, container, tagclass) {
+ container.children().remove();
+ for (var i = 0; i < tags.length; i++) {
+ var newtag = $('<li><a href="#" class="token_tag"></a></li>');
+ newtag.find('a').text(tags[i]);
+ newtag.addClass(tagclass);
+ container.append(newtag);
+ this.cxt_suggested_available++;
+ }
+ };
+ this.fillUserTags = function() {
+ thePKT_SAVED.sendMessage("getTags", {}, function(resp)
+ {
+ if (typeof resp == 'object' && typeof resp.tags == 'object')
+ {
+ myself.userTags = resp.tags;
+ }
+ });
+ };
+ this.fillSuggestedTags = function()
+ {
+ if (!$('.pkt_ext_suggestedtag_detail').length)
+ {
+ myself.suggestedTagsLoaded = true;
+ myself.startCloseTimer();
+ return;
+ }
+
+ thePKT_SAVED.sendMessage("getSuggestedTags",
+ {
+ url: myself.savedUrl
+ }, function(resp)
+ {
+ $('.pkt_ext_suggestedtag_detail').removeClass('pkt_ext_suggestedtag_detail_loading');
+ if (resp.status == 'success')
+ {
+ var newtags = [];
+ for (var i = 0; i < resp.value.suggestedTags.length; i++)
+ {
+ newtags.push(resp.value.suggestedTags[i].tag);
+ }
+ myself.suggestedTagsLoaded = true;
+ if (!myself.mouseInside) {
+ myself.startCloseTimer();
+ }
+ myself.fillTagContainer(newtags, $('.pkt_ext_suggestedtag_detail ul'), 'token_suggestedtag');
+ }
+ else if (resp.status == 'error') {
+ var msg = $('<p class="suggestedtag_msg">');
+ msg.text(resp.error.message);
+ $('.pkt_ext_suggestedtag_detail').append(msg);
+ this.suggestedTagsLoaded = true;
+ if (!myself.mouseInside) {
+ myself.startCloseTimer();
+ }
+ }
+ });
+ }
+ this.initAutoCloseEvents = function() {
+ this.wrapper.on('mouseenter', function() {
+ myself.mouseInside = true;
+ myself.stopCloseTimer();
+ });
+ this.wrapper.on('mouseleave', function() {
+ myself.mouseInside = false;
+ myself.startCloseTimer();
+ });
+ this.wrapper.on('click', function(e) {
+ myself.closeValid = false;
+ });
+ };
+ this.startCloseTimer = function(manualtime)
+ {
+ var settime = manualtime ? manualtime : myself.autocloseTiming;
+ if (typeof myself.autocloseTimer == 'number')
+ {
+ clearTimeout(myself.autocloseTimer);
+ }
+ myself.autocloseTimer = setTimeout(function()
+ {
+ if (myself.closeValid || myself.preventCloseTimerCancel)
+ {
+ myself.preventCloseTimerCancel = false;
+ myself.closePopup();
+ }
+ }, settime);
+ };
+ this.stopCloseTimer = function()
+ {
+ if (myself.preventCloseTimerCancel)
+ {
+ return;
+ }
+ clearTimeout(myself.autocloseTimer);
+ };
+ this.closePopup = function() {
+ myself.stopCloseTimer();
+ thePKT_SAVED.sendMessage("close");
+ };
+ this.checkValidTagSubmit = function() {
+ var inputlength = $.trim($('.pkt_ext_tag_input_wrapper').find('.token-input-input-token').children('input').val()).length;
+ if ($('.pkt_ext_containersaved').find('.token-input-token').length || (inputlength > 0 && inputlength < 26))
+ {
+ $('.pkt_ext_containersaved').find('.pkt_ext_btn').removeClass('pkt_ext_btn_disabled');
+ }
+ else
+ {
+ $('.pkt_ext_containersaved').find('.pkt_ext_btn').addClass('pkt_ext_btn_disabled');
+ }
+ myself.updateSlidingTagList();
+ };
+ this.updateSlidingTagList = function() {
+ var inputleft = $('.token-input-input-token input').position().left;
+ var listleft = $('.token-input-list').position().left;
+ var listleftmanual = parseInt($('.token-input-list').css('left'));
+ var listleftnatural = listleft - listleftmanual;
+ var leftwidth = $('.pkt_ext_tag_input_wrapper').outerWidth();
+
+ if ((inputleft + listleft + 20) > leftwidth)
+ {
+ $('.token-input-list').css('left', Math.min(((inputleft + listleftnatural - leftwidth + 20)*-1), 0) + 'px');
+ }
+ else
+ {
+ $('.token-input-list').css('left', '0');
+ }
+ };
+ this.checkPlaceholderStatus = function() {
+ if (this.wrapper.find('.pkt_ext_tag_input_wrapper').find('.token-input-token').length)
+ {
+ this.wrapper.find('.token-input-input-token input').attr('placeholder', '');
+ }
+ else
+ {
+ this.wrapper.find('.token-input-input-token input').attr('placeholder', $('.pkt_ext_tag_input').attr('placeholder')).css('width', '200px');
+ }
+ };
+ this.initTagInput = function() {
+ var inputwrapper = $('.pkt_ext_tag_input_wrapper');
+ inputwrapper.find('.pkt_ext_tag_input').tokenInput([], {
+ searchDelay: 200,
+ minChars: 1,
+ animateDropdown: false,
+ noResultsHideDropdown: true,
+ scrollKeyboard: true,
+ emptyInputLength: 200,
+ search_function: function(term, cb) {
+ var returnlist = [];
+ if (term.length) {
+ var limit = 15;
+ var r = new RegExp('^' + term);
+ for (var i = 0; i < myself.userTags.length; i++) {
+ if (r.test(myself.userTags[i]) && limit > 0) {
+ returnlist.push({name:myself.userTags[i]});
+ limit--;
+ }
+ }
+ }
+ if (!$('.token-input-dropdown-tag').data('init')) {
+ $('.token-input-dropdown-tag').css('width', inputwrapper.outerWidth()).data('init');
+ inputwrapper.append($('.token-input-dropdown-tag'));
+ }
+ cb(returnlist);
+ },
+ textToData: function(text) {
+ if ($.trim(text).length > 25 || !$.trim(text).length) {
+ if (text.length > 25) {
+ myself.showTagsError(myself.dictJSON.maxtaglength);
+ changestamp = Date.now();
+ setTimeout(function() {
+ $('.token-input-input-token input').val(text).focus();
+ }, 10);
+ }
+ return null;
+ }
+ myself.hideTagsError();
+ return {name:myself.sanitizeText(text.toLowerCase())};
+ },
+ onReady: function() {
+ $('.token-input-dropdown').addClass('token-input-dropdown-tag');
+ inputwrapper.find('.token-input-input-token input').attr('placeholder', $('.tag-input').attr('placeholder')).css('width', '200px');
+ if ($('.pkt_ext_suggestedtag_detail').length) {
+ myself.wrapper.find('.pkt_ext_suggestedtag_detail').on('click', '.token_tag', function(e) {
+ e.preventDefault();
+ var tag = $(e.target);
+ if ($(this).parents('.pkt_ext_suggestedtag_detail_disabled').length) {
+ return;
+ }
+ myself.justaddedsuggested = true;
+ inputwrapper.find('.pkt_ext_tag_input').tokenInput('add', {id:inputwrapper.find('.token-input-token').length, name:tag.text()});
+ tag.addClass('token-suggestedtag-inactive');
+ $('.token-input-input-token input').focus();
+ });
+ }
+ $('.token-input-list').on('keydown', 'input', function(e) {
+ if (e.which == 37) {
+ myself.updateSlidingTagList();
+ }
+ }).on('keypress', 'input', function(e) {
+ if (e.which == 13) {
+ if (typeof changestamp == 'undefined' || (Date.now() - changestamp > 250)) {
+ e.preventDefault();
+ myself.wrapper.find('.pkt_ext_btn').trigger('click');
+ }
+ }
+ }).on('keyup', 'input', function(e) {
+ myself.checkValidTagSubmit();
+ });
+ myself.checkPlaceholderStatus();
+ },
+ onAdd: function() {
+ myself.checkValidTagSubmit();
+ changestamp = Date.now();
+ myself.hideInactiveTags();
+ myself.checkPlaceholderStatus();
+ },
+ onDelete: function() {
+ myself.checkValidTagSubmit();
+ changestamp = Date.now();
+ myself.showActiveTags();
+ myself.checkPlaceholderStatus();
+ },
+ onShowDropdown: function() {
+ thePKT_SAVED.sendMessage("expandSavePanel");
+ },
+ onHideDropdown: function() {
+ thePKT_SAVED.sendMessage("collapseSavePanel");
+ }
+ });
+ $('body').on('keydown', function(e) {
+ var key = e.keyCode || e.which;
+ if (key == 8) {
+ var selected = $('.token-input-selected-token');
+ if (selected.length) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ inputwrapper.find('.pkt_ext_tag_input').tokenInput('remove', {name:selected.find('p').text()});
+ }
+ }
+ else if ($(e.target).parent().hasClass('token-input-input-token')) {
+ e.stopImmediatePropagation();
+ }
+ });
+ };
+ this.disableInput = function() {
+ this.wrapper.find('.pkt_ext_item_actions').addClass('pkt_ext_item_actions_disabled');
+ this.wrapper.find('.pkt_ext_btn').addClass('pkt_ext_btn_disabled');
+ this.wrapper.find('.pkt_ext_tag_input_wrapper').addClass('pkt_ext_tag_input_wrapper_disabled');
+ if (this.wrapper.find('.pkt_ext_suggestedtag_detail').length) {
+ this.wrapper.find('.pkt_ext_suggestedtag_detail').addClass('pkt_ext_suggestedtag_detail_disabled');
+ }
+ };
+ this.enableInput = function() {
+ this.wrapper.find('.pkt_ext_item_actions').removeClass('pkt_ext_item_actions_disabled');
+ this.checkValidTagSubmit();
+ this.wrapper.find('.pkt_ext_tag_input_wrapper').removeClass('pkt_ext_tag_input_wrapper_disabled');
+ if (this.wrapper.find('.pkt_ext_suggestedtag_detail').length) {
+ this.wrapper.find('.pkt_ext_suggestedtag_detail').removeClass('pkt_ext_suggestedtag_detail_disabled');
+ }
+ };
+ this.initAddTagInput = function() {
+ $('.pkt_ext_btn').click(function(e) {
+ e.preventDefault();
+ if ($(this).hasClass('pkt_ext_btn_disabled') || $('.pkt_ext_edit_msg_active').filter('.pkt_ext_edit_msg_error').length)
+ {
+ return;
+ }
+ myself.disableInput();
+ $('.pkt_ext_containersaved').find('.pkt_ext_detail h2').text(myself.dictJSON.processingtags);
+ var originaltags = [];
+ $('.token-input-token').each(function()
+ {
+ var text = $.trim($(this).find('p').text());
+ if (text.length)
+ {
+ originaltags.push(text);
+ }
+ });
+
+ thePKT_SAVED.sendMessage("addTags",
+ {
+ url: myself.savedUrl,
+ tags: originaltags
+ }, function(resp)
+ {
+ if (resp.status == 'success')
+ {
+ myself.showStateFinalMsg(myself.dictJSON.tagssaved);
+ }
+ else if (resp.status == 'error')
+ {
+ $('.pkt_ext_edit_msg').addClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text(resp.error.message);
+ }
+ });
+ });
+ };
+ this.initRemovePageInput = function() {
+ $('.pkt_ext_removeitem').click(function(e) {
+ if ($(this).parents('.pkt_ext_item_actions_disabled').length) {
+ e.preventDefault();
+ return;
+ }
+ if ($(this).hasClass('pkt_ext_removeitem')) {
+ e.preventDefault();
+ myself.disableInput();
+ $('.pkt_ext_containersaved').find('.pkt_ext_detail h2').text(myself.dictJSON.processingremove);
+
+ thePKT_SAVED.sendMessage("deleteItem",
+ {
+ itemId: myself.savedItemId
+ }, function(resp) {
+ if (resp.status == 'success') {
+ myself.showStateFinalMsg(myself.dictJSON.pageremoved);
+ }
+ else if (resp.status == 'error') {
+ $('.pkt_ext_edit_msg').addClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text(resp.error.message);
+ }
+ });
+ }
+ });
+ };
+ this.initOpenListInput = function() {
+ $('.pkt_ext_openpocket').click(function(e)
+ {
+ e.preventDefault();
+ thePKT_SAVED.sendMessage("openTabWithUrl",
+ {
+ url: $(this).attr('href'),
+ activate: true
+ });
+ myself.closePopup();
+ });
+ };
+ this.showTagsError = function(msg) {
+ $('.pkt_ext_edit_msg').addClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text(msg);
+ $('.pkt_ext_tag_detail').addClass('pkt_ext_tag_error');
+ };
+ this.hideTagsError = function(msg) {
+ $('.pkt_ext_edit_msg').removeClass('pkt_ext_edit_msg_error pkt_ext_edit_msg_active').text('');
+ $('.pkt_ext_tag_detail').removeClass('pkt_ext_tag_error');
+ };
+ this.showActiveTags = function() {
+ if (!$('.pkt_ext_suggestedtag_detail').length) {
+ return;
+ }
+ var activetokenstext = '|';
+ $('.token-input-token').each(function(index, element) {
+ activetokenstext += $(element).find('p').text() + '|';
+ });
+
+ var inactivetags = $('.pkt_ext_suggestedtag_detail').find('.token_tag_inactive');
+ inactivetags.each(function(index, element) {
+ if (activetokenstext.indexOf('|' + $(element).text() + '|') == -1) {
+ $(element).removeClass('token_tag_inactive');
+ }
+ });
+ };
+ this.hideInactiveTags = function() {
+ if (!$('.pkt_ext_suggestedtag_detail').length) {
+ return;
+ }
+ var activetokenstext = '|';
+ $('.token-input-token').each(function(index, element) {
+ activetokenstext += $(element).find('p').text() + '|';
+ });
+ var activesuggestedtags = $('.token_tag').not('.token_tag_inactive');
+ activesuggestedtags.each(function(index, element) {
+ if (activetokenstext.indexOf('|' + $(element).text() + '|') > -1) {
+ $(element).addClass('token_tag_inactive');
+ }
+ });
+ };
+ this.showStateSaved = function(initobj) {
+ this.wrapper.find('.pkt_ext_detail h2').text(this.dictJSON.pagesaved);
+ this.wrapper.find('.pkt_ext_btn').addClass('pkt_ext_btn_disabled');
+ if (typeof initobj.item == 'object')
+ {
+ this.savedItemId = initobj.item.item_id;
+ this.savedUrl = initobj.item.given_url;
+ }
+ $('.pkt_ext_containersaved').addClass('pkt_ext_container_detailactive').removeClass('pkt_ext_container_finalstate');
+
+ myself.fillUserTags();
+ if (myself.suggestedTagsLoaded) {
+ myself.startCloseTimer();
+ }
+ else {
+ myself.fillSuggestedTags();
+ }
+ };
+ this.sanitizeText = function(s) {
+ var sanitizeMap = {
+ "&": "&amp;",
+ "<": "&lt;",
+ ">": "&gt;",
+ '"': '&quot;',
+ "'": '&#39;'
+ };
+ if (typeof s !== 'string')
+ {
+ return '';
+ }
+ return String(s).replace(/[&<>"']/g, function (str) {
+ return sanitizeMap[str];
+ });
+ };
+ this.showStateFinalMsg = function(msg) {
+ this.wrapper.find('.pkt_ext_tag_detail').one('webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd', function(e)
+ {
+ $(this).off('webkitTransitionEnd transitionend msTransitionEnd oTransitionEnd');
+ myself.preventCloseTimerCancel = true;
+ myself.startCloseTimer(myself.autocloseTimingFinalState);
+ myself.wrapper.find('.pkt_ext_detail h2').text(msg);
+ });
+ this.wrapper.addClass('pkt_ext_container_finalstate');
+ };
+ this.showStateError = function(headline, detail) {
+ this.wrapper.find('.pkt_ext_detail h2').text(headline);
+ this.wrapper.find('.pkt_ext_detail h3').text(detail);
+ this.wrapper.addClass('pkt_ext_container_detailactive pkt_ext_container_finalstate pkt_ext_container_finalerrorstate');
+ this.preventCloseTimerCancel = true;
+ this.startCloseTimer(myself.autocloseTimingFinalState);
+ }
+ this.getTranslations = function()
+ {
+ this.dictJSON = window.pocketStrings;
+ };
+};
+
+PKT_SAVED_OVERLAY.prototype = {
+ create : function()
+ {
+ if (this.active)
+ {
+ return;
+ }
+ this.active = true;
+
+ // set translations
+ this.getTranslations();
+
+ // set host
+ this.dictJSON.pockethost = this.pockethost;
+
+ // extra modifier class for collapsed state
+ if (this.inoverflowmenu)
+ {
+ $('body').addClass('pkt_ext_saved_overflow');
+ }
+
+ // extra modifier class for language
+ if (this.locale)
+ {
+ $('body').addClass('pkt_ext_saved_' + this.locale);
+ }
+
+ // Create actual content
+ $('body').append(Handlebars.templates.saved_shell(this.dictJSON));
+
+ // Add in premium content (if applicable based on premium status)
+ this.createPremiumFunctionality();
+
+ // Initialize functionality for overlay
+ this.wrapper = $('.pkt_ext_containersaved');
+ this.initTagInput();
+ this.initAddTagInput();
+ this.initRemovePageInput();
+ this.initOpenListInput();
+ this.initAutoCloseEvents();
+ },
+ createPremiumFunctionality: function()
+ {
+ if (this.premiumStatus && !$('.pkt_ext_suggestedtag_detail').length)
+ {
+ $('body').append(Handlebars.templates.saved_premiumshell(this.dictJSON));
+ $('.pkt_ext_initload').append(Handlebars.templates.saved_premiumextras(this.dictJSON));
+ }
+ }
+};
+
+
+// Layer between Bookmarklet and Extensions
+var PKT_SAVED = function () {};
+
+PKT_SAVED.prototype = {
+ init: function () {
+ if (this.inited) {
+ return;
+ }
+ this.panelId = pktPanelMessaging.panelIdFromURL(window.location.href);
+ this.overlay = new PKT_SAVED_OVERLAY();
+
+ this.inited = true;
+ },
+
+ addMessageListener: function(messageId, callback) {
+ pktPanelMessaging.addMessageListener(this.panelId, messageId, callback);
+ },
+
+ sendMessage: function(messageId, payload, callback) {
+ pktPanelMessaging.sendMessage(this.panelId, messageId, payload, callback);
+ },
+
+ create: function() {
+ var myself = this;
+ var url = window.location.href.match(/premiumStatus=([\w|\d|\.]*)&?/);
+ if (url && url.length > 1)
+ {
+ myself.overlay.premiumStatus = (url[1] == '1');
+ }
+ var host = window.location.href.match(/pockethost=([\w|\.]*)&?/);
+ if (host && host.length > 1)
+ {
+ myself.overlay.pockethost = host[1];
+ }
+ var inoverflowmenu = window.location.href.match(/inoverflowmenu=([\w|\.]*)&?/);
+ if (inoverflowmenu && inoverflowmenu.length > 1)
+ {
+ myself.overlay.inoverflowmenu = (inoverflowmenu[1] == 'true');
+ }
+ var locale = window.location.href.match(/locale=([\w|\.]*)&?/);
+ if (locale && locale.length > 1)
+ {
+ myself.overlay.locale = locale[1].toLowerCase();
+ }
+
+ myself.overlay.create();
+
+ // tell back end we're ready
+ thePKT_SAVED.sendMessage("show");
+
+ // wait confirmation of save before flipping to final saved state
+ thePKT_SAVED.addMessageListener("saveLink", function(resp)
+ {
+ if (resp.status == 'error') {
+ if (typeof resp.error == 'object')
+ {
+ if (resp.error.localizedKey)
+ {
+ myself.overlay.showStateError(myself.overlay.dictJSON.pagenotsaved, myself.overlay.dictJSON[resp.error.localizedKey]);
+ }
+ else
+ {
+ myself.overlay.showStateError(myself.overlay.dictJSON.pagenotsaved, resp.error.message);
+ }
+ }
+ else
+ {
+ myself.overlay.showStateError(myself.overlay.dictJSON.pagenotsaved, myself.overlay.dictJSON.errorgeneric);
+ }
+ return;
+ }
+
+ myself.overlay.showStateSaved(resp);
+ });
+
+ }
+}
+
+$(function()
+{
+ if (!window.thePKT_SAVED) {
+ var thePKT_SAVED = new PKT_SAVED();
+ window.thePKT_SAVED = thePKT_SAVED;
+ thePKT_SAVED.init();
+ }
+
+ var pocketHost = thePKT_SAVED.overlay.pockethost;
+ // send an async message to get string data
+ thePKT_SAVED.sendMessage("initL10N", {
+ tos: [
+ 'https://'+ pocketHost +'/tos?s=ffi&t=tos&tv=panel_tryit',
+ 'https://'+ pocketHost +'/privacy?s=ffi&t=privacypolicy&tv=panel_tryit'
+ ]
+ }, function(resp) {
+ window.pocketStrings = resp.strings;
+ window.thePKT_SAVED.create();
+ });
+});
diff --git a/browser/extensions/pocket/content/panels/js/signup.js b/browser/extensions/pocket/content/panels/js/signup.js
new file mode 100644
index 000000000..af55cc2a7
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/js/signup.js
@@ -0,0 +1,193 @@
+/*
+PKT_SIGNUP_OVERLAY is the view itself and contains all of the methods to manipute the overlay and messaging.
+It does not contain any logic for saving or communication with the extension or server.
+*/
+var PKT_SIGNUP_OVERLAY = function (options)
+{
+ var myself = this;
+ this.inited = false;
+ this.active = false;
+ this.delayedStateSaved = false;
+ this.wrapper = null;
+ this.variant = window.___PKT__SIGNUP_VARIANT;
+ this.tagline = window.___PKT__SIGNUP_TAGLINE || '';
+ this.preventCloseTimerCancel = false;
+ this.translations = {};
+ this.closeValid = true;
+ this.mouseInside = false;
+ this.autocloseTimer = null;
+ this.variant = "";
+ this.inoverflowmenu = false;
+ this.controlvariant;
+ this.pockethost = "getpocket.com";
+ this.fxasignedin = false;
+ this.dictJSON = {};
+ this.initCloseTabEvents = function() {
+ $('.btn,.pkt_ext_learnmore,.alreadyhave > a').click(function(e)
+ {
+ e.preventDefault();
+ thePKT_SIGNUP.sendMessage("openTabWithUrl",
+ {
+ url: $(this).attr('href'),
+ activate: true
+ });
+ myself.closePopup();
+ });
+ };
+ this.closePopup = function() {
+ thePKT_SIGNUP.sendMessage("close");
+ };
+ this.sanitizeText = function(s) {
+ var sanitizeMap = {
+ "&": "&amp;",
+ "<": "&lt;",
+ ">": "&gt;",
+ '"': '&quot;',
+ "'": '&#39;'
+ };
+ if (typeof s !== 'string')
+ {
+ return '';
+ }
+ return String(s).replace(/[&<>"']/g, function (str) {
+ return sanitizeMap[str];
+ });
+ };
+ this.getTranslations = function()
+ {
+ this.dictJSON = window.pocketStrings;
+ };
+
+};
+
+PKT_SIGNUP_OVERLAY.prototype = {
+ create : function()
+ {
+ var controlvariant = window.location.href.match(/controlvariant=([\w|\.]*)&?/);
+ if (controlvariant && controlvariant.length > 1)
+ {
+ this.controlvariant = controlvariant[1];
+ }
+ var variant = window.location.href.match(/variant=([\w|\.]*)&?/);
+ if (variant && variant.length > 1)
+ {
+ this.variant = variant[1];
+ }
+ var fxasignedin = window.location.href.match(/fxasignedin=([\w|\d|\.]*)&?/);
+ if (fxasignedin && fxasignedin.length > 1)
+ {
+ this.fxasignedin = (fxasignedin[1] == '1');
+ }
+ var host = window.location.href.match(/pockethost=([\w|\.]*)&?/);
+ if (host && host.length > 1)
+ {
+ this.pockethost = host[1];
+ }
+ var inoverflowmenu = window.location.href.match(/inoverflowmenu=([\w|\.]*)&?/);
+ if (inoverflowmenu && inoverflowmenu.length > 1)
+ {
+ this.inoverflowmenu = (inoverflowmenu[1] == 'true');
+ }
+ var locale = window.location.href.match(/locale=([\w|\.]*)&?/);
+ if (locale && locale.length > 1)
+ {
+ this.locale = locale[1].toLowerCase();
+ }
+
+ if (this.active)
+ {
+ return;
+ }
+ this.active = true;
+
+ // set translations
+ this.getTranslations();
+ this.dictJSON.fxasignedin = this.fxasignedin ? 1 : 0;
+ this.dictJSON.controlvariant = this.controlvariant == 'true' ? 1 : 0;
+ this.dictJSON.variant = (this.variant ? this.variant : 'undefined');
+ this.dictJSON.variant += this.fxasignedin ? '_fxa' : '_nonfxa';
+ this.dictJSON.pockethost = this.pockethost;
+ this.dictJSON.showlearnmore = true;
+
+ // extra modifier class for collapsed state
+ if (this.inoverflowmenu)
+ {
+ $('body').addClass('pkt_ext_signup_overflow');
+ }
+
+ // extra modifier class for language
+ if (this.locale)
+ {
+ $('body').addClass('pkt_ext_signup_' + this.locale);
+ }
+
+ // Create actual content
+ if (this.variant == 'overflow')
+ {
+ $('body').append(Handlebars.templates.signup_shell(this.dictJSON));
+ }
+ else
+ {
+ $('body').append(Handlebars.templates.signupstoryboard_shell(this.dictJSON));
+ }
+
+
+ // tell background we're ready
+ thePKT_SIGNUP.sendMessage("show");
+
+ // close events
+ this.initCloseTabEvents();
+ }
+};
+
+
+// Layer between Bookmarklet and Extensions
+var PKT_SIGNUP = function () {};
+
+PKT_SIGNUP.prototype = {
+ init: function () {
+ if (this.inited) {
+ return;
+ }
+ this.panelId = pktPanelMessaging.panelIdFromURL(window.location.href);
+ this.overlay = new PKT_SIGNUP_OVERLAY();
+
+ this.inited = true;
+ },
+
+ addMessageListener: function(messageId, callback) {
+ pktPanelMessaging.addMessageListener(this.panelId, messageId, callback);
+ },
+
+ sendMessage: function(messageId, payload, callback) {
+ pktPanelMessaging.sendMessage(this.panelId, messageId, payload, callback);
+ },
+
+ create: function() {
+ this.overlay.create();
+
+ // tell back end we're ready
+ thePKT_SIGNUP.sendMessage("show");
+ }
+}
+
+$(function()
+{
+ if (!window.thePKT_SIGNUP) {
+ var thePKT_SIGNUP = new PKT_SIGNUP();
+ window.thePKT_SIGNUP = thePKT_SIGNUP;
+ thePKT_SIGNUP.init();
+ }
+
+ var pocketHost = thePKT_SIGNUP.overlay.pockethost;
+ // send an async message to get string data
+ thePKT_SIGNUP.sendMessage("initL10N", {
+ tos: [
+ 'https://'+ pocketHost +'/tos?s=ffi&t=tos&tv=panel_tryit',
+ 'https://'+ pocketHost +'/privacy?s=ffi&t=privacypolicy&tv=panel_tryit'
+ ]
+ }, function(resp) {
+ window.pocketStrings = resp.strings;
+ window.thePKT_SIGNUP.create();
+ });
+});
diff --git a/browser/extensions/pocket/content/panels/js/tmpl.js b/browser/extensions/pocket/content/panels/js/tmpl.js
new file mode 100644
index 000000000..a03ffda70
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/js/tmpl.js
@@ -0,0 +1,242 @@
+(function() {
+ var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
+templates['saved_premiumextras'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
+ return "<div class=\"pkt_ext_suggestedtag_detailshown\">\r\n</div> ";
+ },"useData":true});
+templates['saved_premiumshell'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return "<div class=\"pkt_ext_suggestedtag_detail pkt_ext_suggestedtag_detail_loading\">\n <h4>"
+ + escapeExpression(((helper = (helper = helpers.suggestedtags || (depth0 != null ? depth0.suggestedtags : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"suggestedtags","hash":{},"data":data}) : helper)))
+ + "</h4>\n <div class=\"pkt_ext_loadingspinner\"><div></div></div>\n <ul class=\"pkt_ext_cf\">\n </ul>\n</div>";
+},"useData":true});
+templates['saved_shell'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return "<div class=\"pkt_ext_initload\">\n <div class=\"pkt_ext_logo\"></div> \n <div class=\"pkt_ext_topdetail\">\n <h2>"
+ + escapeExpression(((helper = (helper = helpers.saving || (depth0 != null ? depth0.saving : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"saving","hash":{},"data":data}) : helper)))
+ + "</h2>\n </div> \n <div class=\"pkt_ext_loadingspinner\"><div></div></div>\n</div> \n<div class=\"pkt_ext_detail\"> \n <div class=\"pkt_ext_logo\"></div>\n <div class=\"pkt_ext_topdetail\">\n <h2>"
+ + escapeExpression(((helper = (helper = helpers.pagesaved || (depth0 != null ? depth0.pagesaved : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pagesaved","hash":{},"data":data}) : helper)))
+ + "</h2>\n <h3 class=\"pkt_ext_errordetail\"></h3>\n <nav class=\"pkt_ext_item_actions pkt_ext_cf\">\n <ul>\n <li><a class=\"pkt_ext_removeitem\" href=\"#\">"
+ + escapeExpression(((helper = (helper = helpers.removepage || (depth0 != null ? depth0.removepage : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"removepage","hash":{},"data":data}) : helper)))
+ + "</a></li>\n <li class=\"pkt_ext_actions_separator\"></li> \n <li><a class=\"pkt_ext_openpocket\" href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/a?src=ff_ext_saved\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.viewlist || (depth0 != null ? depth0.viewlist : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"viewlist","hash":{},"data":data}) : helper)))
+ + "</a></li>\n </ul>\n </nav> \n </div>\n <div class=\"pkt_ext_tag_detail pkt_ext_cf\">\n <div class=\"pkt_ext_tag_input_wrapper\">\n <div class=\"pkt_ext_tag_input_blocker\"></div>\n <input class=\"pkt_ext_tag_input\" type=\"text\" placeholder=\""
+ + escapeExpression(((helper = (helper = helpers.addtags || (depth0 != null ? depth0.addtags : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"addtags","hash":{},"data":data}) : helper)))
+ + "\">\n </div>\n <a href=\"#\" class=\"pkt_ext_btn pkt_ext_btn_disabled\">"
+ + escapeExpression(((helper = (helper = helpers.save || (depth0 != null ? depth0.save : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"save","hash":{},"data":data}) : helper)))
+ + "</a>\n </div>\n <p class=\"pkt_ext_edit_msg\"></p>\n</div>";
+},"useData":true});
+templates['signup_shell'] = template({"1":function(depth0,helpers,partials,data) {
+ var stack1, buffer = "";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.controlvariant : depth0), {"name":"if","hash":{},"fn":this.program(2, data),"inverse":this.program(4, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ return buffer;
+},"2":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <p class=\"pkt_ext_learnmorecontainer\"><a class=\"pkt_ext_learnmore\" href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/firefox_learnmore?s=ffi&t=learnmore&tv=panel_control&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.learnmore || (depth0 != null ? depth0.learnmore : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"learnmore","hash":{},"data":data}) : helper)))
+ + "</a></p>\n";
+},"4":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <p class=\"pkt_ext_learnmorecontainer\"><a class=\"pkt_ext_learnmore\" href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/firefox_learnmore?s=ffi&t=learnmore&tv=panel_tryit&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.learnmore || (depth0 != null ? depth0.learnmore : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"learnmore","hash":{},"data":data}) : helper)))
+ + "</a></p>\n";
+},"6":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <p class=\"pkt_ext_learnmorecontainer\"><a class=\"pkt_ext_learnmore pkt_ext_learnmoreinactive\" href=\"#\">"
+ + escapeExpression(((helper = (helper = helpers.learnmore || (depth0 != null ? depth0.learnmore : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"learnmore","hash":{},"data":data}) : helper)))
+ + "</a></p>\n";
+},"8":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <h4>"
+ + escapeExpression(((helper = (helper = helpers.signuptosave || (depth0 != null ? depth0.signuptosave : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signuptosave","hash":{},"data":data}) : helper)))
+ + "</h4>\n <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/ff_signup?s=ffi&t=signupff&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\" class=\"btn signup-btn-firefox\"><span class=\"logo\"></span><span class=\"text\">"
+ + escapeExpression(((helper = (helper = helpers.signinfirefox || (depth0 != null ? depth0.signinfirefox : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signinfirefox","hash":{},"data":data}) : helper)))
+ + "</span></a></p>\n <p class=\"alreadyhave\">"
+ + escapeExpression(((helper = (helper = helpers.alreadyhaveacct || (depth0 != null ? depth0.alreadyhaveacct : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"alreadyhaveacct","hash":{},"data":data}) : helper)))
+ + " <a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/login?ep=3&src=extension&s=ffi&t=login&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.loginnow || (depth0 != null ? depth0.loginnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"loginnow","hash":{},"data":data}) : helper)))
+ + "</a>.</p>\n";
+},"10":function(depth0,helpers,partials,data) {
+ var stack1, buffer = "";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.controlvariant : depth0), {"name":"if","hash":{},"fn":this.program(11, data),"inverse":this.program(13, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ return buffer;
+},"11":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <h4>"
+ + escapeExpression(((helper = (helper = helpers.signuptosave || (depth0 != null ? depth0.signuptosave : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signuptosave","hash":{},"data":data}) : helper)))
+ + "</h4>\n <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/ff_signup?s=ffi&tv=panel_control&t=signupff&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\" class=\"btn signup-btn-firefox\"><span class=\"logo\"></span><span class=\"text\">"
+ + escapeExpression(((helper = (helper = helpers.signupfirefox || (depth0 != null ? depth0.signupfirefox : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signupfirefox","hash":{},"data":data}) : helper)))
+ + "</span></a></p>\n <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/signup?force=email&tv=panel_control&src=extension&s=ffi&t=signupemail&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\" class=\"btn btn-secondary signup-btn-email signup-btn-initstate\">"
+ + escapeExpression(((helper = (helper = helpers.signupemail || (depth0 != null ? depth0.signupemail : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signupemail","hash":{},"data":data}) : helper)))
+ + "</a></p>\n <p class=\"alreadyhave\">"
+ + escapeExpression(((helper = (helper = helpers.alreadyhaveacct || (depth0 != null ? depth0.alreadyhaveacct : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"alreadyhaveacct","hash":{},"data":data}) : helper)))
+ + " <a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/login?ep=3&tv=panel_control&src=extension&s=ffi&t=login&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.loginnow || (depth0 != null ? depth0.loginnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"loginnow","hash":{},"data":data}) : helper)))
+ + "</a>.</p>\n";
+},"13":function(depth0,helpers,partials,data) {
+ var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = " <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/firefox_tryitnow?s=ffi&tv=panel_tryit&t=tryitnow\" target=\"_blank\" class=\"btn signup-btn-tryitnow\"><span class=\"text\">"
+ + escapeExpression(((helper = (helper = helpers.tryitnow || (depth0 != null ? depth0.tryitnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"tryitnow","hash":{},"data":data}) : helper)))
+ + "</span></a></p>\n <p class=\"alreadyhave tryitnowspace\">"
+ + escapeExpression(((helper = (helper = helpers.alreadyhaveacct || (depth0 != null ? depth0.alreadyhaveacct : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"alreadyhaveacct","hash":{},"data":data}) : helper)))
+ + " <a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/login?ep=3&s=ffi&tv=panel_tryit&src=extension&t=login&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.loginnow || (depth0 != null ? depth0.loginnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"loginnow","hash":{},"data":data}) : helper)))
+ + "</a>.</p>\n <p class=\"pkt_ext_tos\">";
+ stack1 = ((helper = (helper = helpers.tos || (depth0 != null ? depth0.tos : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"tos","hash":{},"data":data}) : helper));
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "</p>\n";
+},"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
+ var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = "<div class=\"pkt_ext_introdetail pkt_ext_introdetailhero\">\n <h2 class=\"pkt_ext_logo\">Pocket</h2>\n <p class=\"pkt_ext_tagline\">"
+ + escapeExpression(((helper = (helper = helpers.tagline || (depth0 != null ? depth0.tagline : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"tagline","hash":{},"data":data}) : helper)))
+ + "</p>\n";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.showlearnmore : depth0), {"name":"if","hash":{},"fn":this.program(1, data),"inverse":this.program(6, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ buffer += " <div class=\"pkt_ext_introimg\"></div>\n</div>\n<div class=\"pkt_ext_signupdetail pkt_ext_signupdetail_hero\">\n";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.fxasignedin : depth0), {"name":"if","hash":{},"fn":this.program(8, data),"inverse":this.program(10, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "</div>\n";
+},"useData":true});
+templates['signupstoryboard_shell'] = template({"1":function(depth0,helpers,partials,data) {
+ var stack1, buffer = "";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.controlvariant : depth0), {"name":"if","hash":{},"fn":this.program(2, data),"inverse":this.program(4, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ return buffer;
+},"2":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <p><a class=\"pkt_ext_learnmore\" href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/firefox_learnmore?s=ffi&t=learnmore&tv=panel_control&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.learnmore || (depth0 != null ? depth0.learnmore : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"learnmore","hash":{},"data":data}) : helper)))
+ + "</a></p>\n";
+},"4":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <p><a class=\"pkt_ext_learnmore\" href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/firefox_learnmore?s=ffi&t=learnmore&tv=panel_tryit&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.learnmore || (depth0 != null ? depth0.learnmore : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"learnmore","hash":{},"data":data}) : helper)))
+ + "</a></p>\n";
+},"6":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <p><a class=\"pkt_ext_learnmore pkt_ext_learnmoreinactive\" href=\"#\">"
+ + escapeExpression(((helper = (helper = helpers.learnmore || (depth0 != null ? depth0.learnmore : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"learnmore","hash":{},"data":data}) : helper)))
+ + "</a></p>\n";
+},"8":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <h4>"
+ + escapeExpression(((helper = (helper = helpers.signuptosave || (depth0 != null ? depth0.signuptosave : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signuptosave","hash":{},"data":data}) : helper)))
+ + "</h4>\n <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/ff_signup?s=ffi&t=signupff&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\" class=\"btn signup-btn-firefox\"><span class=\"logo\"></span><span class=\"text\">"
+ + escapeExpression(((helper = (helper = helpers.signinfirefox || (depth0 != null ? depth0.signinfirefox : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signinfirefox","hash":{},"data":data}) : helper)))
+ + "</span></a></p>\n <p class=\"alreadyhave\">"
+ + escapeExpression(((helper = (helper = helpers.alreadyhaveacct || (depth0 != null ? depth0.alreadyhaveacct : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"alreadyhaveacct","hash":{},"data":data}) : helper)))
+ + " <a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/login?ep=3&src=extension&s=ffi&t=login&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.loginnow || (depth0 != null ? depth0.loginnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"loginnow","hash":{},"data":data}) : helper)))
+ + "</a>.</p>\n";
+},"10":function(depth0,helpers,partials,data) {
+ var stack1, buffer = "";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.controlvariant : depth0), {"name":"if","hash":{},"fn":this.program(11, data),"inverse":this.program(13, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ return buffer;
+},"11":function(depth0,helpers,partials,data) {
+ var helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;
+ return " <h4>"
+ + escapeExpression(((helper = (helper = helpers.signuptosave || (depth0 != null ? depth0.signuptosave : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signuptosave","hash":{},"data":data}) : helper)))
+ + "</h4>\n <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/ff_signup?s=ffi&tv=panel_control&t=signupff&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\" class=\"btn signup-btn-firefox\"><span class=\"logo\"></span><span class=\"text\">"
+ + escapeExpression(((helper = (helper = helpers.signupfirefox || (depth0 != null ? depth0.signupfirefox : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signupfirefox","hash":{},"data":data}) : helper)))
+ + "</span></a></p>\n <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/signup?force=email&tv=panel_control&src=extension&s=ffi&t=signupemail&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\" class=\"btn btn-secondary signup-btn-email signup-btn-initstate\">"
+ + escapeExpression(((helper = (helper = helpers.signupemail || (depth0 != null ? depth0.signupemail : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"signupemail","hash":{},"data":data}) : helper)))
+ + "</a></p>\n <p class=\"alreadyhave\">"
+ + escapeExpression(((helper = (helper = helpers.alreadyhaveacct || (depth0 != null ? depth0.alreadyhaveacct : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"alreadyhaveacct","hash":{},"data":data}) : helper)))
+ + " <a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/login?ep=3&tv=panel_control&src=extension&s=ffi&t=login&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.loginnow || (depth0 != null ? depth0.loginnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"loginnow","hash":{},"data":data}) : helper)))
+ + "</a>.</p>\n";
+},"13":function(depth0,helpers,partials,data) {
+ var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = " <p class=\"btn-container\"><a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/firefox_tryitnow?s=ffi&tv=panel_tryit&t=tryitnow\" target=\"_blank\" class=\"btn signup-btn-tryitnow\"><span class=\"text\">"
+ + escapeExpression(((helper = (helper = helpers.tryitnow || (depth0 != null ? depth0.tryitnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"tryitnow","hash":{},"data":data}) : helper)))
+ + "</span></a></p>\n <p class=\"alreadyhave tryitnowspace\">"
+ + escapeExpression(((helper = (helper = helpers.alreadyhaveacct || (depth0 != null ? depth0.alreadyhaveacct : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"alreadyhaveacct","hash":{},"data":data}) : helper)))
+ + " <a href=\"https://"
+ + escapeExpression(((helper = (helper = helpers.pockethost || (depth0 != null ? depth0.pockethost : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"pockethost","hash":{},"data":data}) : helper)))
+ + "/login?ep=3&s=ffi&tv=panel_tryit&src=extension&t=login&v="
+ + escapeExpression(((helper = (helper = helpers.variant || (depth0 != null ? depth0.variant : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"variant","hash":{},"data":data}) : helper)))
+ + "\" target=\"_blank\">"
+ + escapeExpression(((helper = (helper = helpers.loginnow || (depth0 != null ? depth0.loginnow : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"loginnow","hash":{},"data":data}) : helper)))
+ + "</a>.</p>\n <p class=\"pkt_ext_tos\">";
+ stack1 = ((helper = (helper = helpers.tos || (depth0 != null ? depth0.tos : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"tos","hash":{},"data":data}) : helper));
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "</p>\n";
+},"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
+ var stack1, helper, functionType="function", helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, buffer = "<div class=\"pkt_ext_introdetail pkt_ext_introdetailstoryboard\">\n <div class=\"pkt_ext_introstory pkt_ext_introstoryone\">\n <div class=\"pkt_ext_introstory_text\">\n <p class=\"pkt_ext_tagline\">"
+ + escapeExpression(((helper = (helper = helpers.taglinestory_one || (depth0 != null ? depth0.taglinestory_one : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"taglinestory_one","hash":{},"data":data}) : helper)))
+ + "</p>\n </div>\n <div class=\"pkt_ext_introstoryone_img\"></div>\n </div>\n <div class=\"pkt_ext_introstorydivider\"></div>\n <div class=\"pkt_ext_introstory pkt_ext_introstorytwo\">\n <div class=\"pkt_ext_introstory_text\">\n <p class=\"pkt_ext_tagline\">"
+ + escapeExpression(((helper = (helper = helpers.taglinestory_two || (depth0 != null ? depth0.taglinestory_two : depth0)) != null ? helper : helperMissing),(typeof helper === functionType ? helper.call(depth0, {"name":"taglinestory_two","hash":{},"data":data}) : helper)))
+ + "</p>\n";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.showlearnmore : depth0), {"name":"if","hash":{},"fn":this.program(1, data),"inverse":this.program(6, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ buffer += " </div>\n <div class=\"pkt_ext_introstorytwo_img\"></div>\n </div>\n</div>\n<div class=\"pkt_ext_signupdetail\">\n";
+ stack1 = helpers['if'].call(depth0, (depth0 != null ? depth0.fxasignedin : depth0), {"name":"if","hash":{},"fn":this.program(8, data),"inverse":this.program(10, data),"data":data});
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "\n</div>\n";
+},"useData":true});
+})();
diff --git a/browser/extensions/pocket/content/panels/js/vendor/handlebars.runtime.js b/browser/extensions/pocket/content/panels/js/vendor/handlebars.runtime.js
new file mode 100644
index 000000000..c8bb1c452
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/js/vendor/handlebars.runtime.js
@@ -0,0 +1,660 @@
+/*
+
+ handlebars v2.0.0
+
+Copyright (C) 2011-2014 by Yehuda Katz
+
+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.
+
+@license
+*/
+/* exported Handlebars */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([], factory);
+ } else if (typeof exports === 'object') {
+ module.exports = factory();
+ } else {
+ root.Handlebars = root.Handlebars || factory();
+ }
+}(this, function () {
+// handlebars/safe-string.js
+var __module3__ = (function() {
+ "use strict";
+ var __exports__;
+ // Build out our basic SafeString type
+ function SafeString(string) {
+ this.string = string;
+ }
+
+ SafeString.prototype.toString = function() {
+ return "" + this.string;
+ };
+
+ __exports__ = SafeString;
+ return __exports__;
+})();
+
+// handlebars/utils.js
+var __module2__ = (function(__dependency1__) {
+ "use strict";
+ var __exports__ = {};
+ /*jshint -W004 */
+ var SafeString = __dependency1__;
+
+ var escape = {
+ "&": "&amp;",
+ "<": "&lt;",
+ ">": "&gt;",
+ '"': "&quot;",
+ "'": "&#x27;",
+ "`": "&#x60;"
+ };
+
+ var badChars = /[&<>"'`]/g;
+ var possible = /[&<>"'`]/;
+
+ function escapeChar(chr) {
+ return escape[chr];
+ }
+
+ function extend(obj /* , ...source */) {
+ for (var i = 1; i < arguments.length; i++) {
+ for (var key in arguments[i]) {
+ if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
+ obj[key] = arguments[i][key];
+ }
+ }
+ }
+
+ return obj;
+ }
+
+ __exports__.extend = extend;var toString = Object.prototype.toString;
+ __exports__.toString = toString;
+ // Sourced from lodash
+ // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
+ var isFunction = function(value) {
+ return typeof value === 'function';
+ };
+ // fallback for older versions of Chrome and Safari
+ /* istanbul ignore next */
+ if (isFunction(/x/)) {
+ isFunction = function(value) {
+ return typeof value === 'function' && toString.call(value) === '[object Function]';
+ };
+ }
+ var isFunction;
+ __exports__.isFunction = isFunction;
+ /* istanbul ignore next */
+ var isArray = Array.isArray || function(value) {
+ return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
+ };
+ __exports__.isArray = isArray;
+
+ function escapeExpression(string) {
+ // don't escape SafeStrings, since they're already safe
+ if (string instanceof SafeString) {
+ return string.toString();
+ } else if (string == null) {
+ return "";
+ } else if (!string) {
+ return string + '';
+ }
+
+ // Force a string conversion as this will be done by the append regardless and
+ // the regex test will do this transparently behind the scenes, causing issues if
+ // an object's to string has escaped characters in it.
+ string = "" + string;
+
+ if(!possible.test(string)) { return string; }
+ return string.replace(badChars, escapeChar);
+ }
+
+ __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
+ if (!value && value !== 0) {
+ return true;
+ } else if (isArray(value) && value.length === 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ __exports__.isEmpty = isEmpty;function appendContextPath(contextPath, id) {
+ return (contextPath ? contextPath + '.' : '') + id;
+ }
+
+ __exports__.appendContextPath = appendContextPath;
+ return __exports__;
+})(__module3__);
+
+// handlebars/exception.js
+var __module4__ = (function() {
+ "use strict";
+ var __exports__;
+
+ var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
+
+ function Exception(message, node) {
+ var line;
+ if (node && node.firstLine) {
+ line = node.firstLine;
+
+ message += ' - ' + line + ':' + node.firstColumn;
+ }
+
+ var tmp = Error.prototype.constructor.call(this, message);
+
+ // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
+ for (var idx = 0; idx < errorProps.length; idx++) {
+ this[errorProps[idx]] = tmp[errorProps[idx]];
+ }
+
+ if (line) {
+ this.lineNumber = line;
+ this.column = node.firstColumn;
+ }
+ }
+
+ Exception.prototype = new Error();
+
+ __exports__ = Exception;
+ return __exports__;
+})();
+
+// handlebars/base.js
+var __module1__ = (function(__dependency1__, __dependency2__) {
+ "use strict";
+ var __exports__ = {};
+ var Utils = __dependency1__;
+ var Exception = __dependency2__;
+
+ var VERSION = "2.0.0";
+ __exports__.VERSION = VERSION;var COMPILER_REVISION = 6;
+ __exports__.COMPILER_REVISION = COMPILER_REVISION;
+ var REVISION_CHANGES = {
+ 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
+ 2: '== 1.0.0-rc.3',
+ 3: '== 1.0.0-rc.4',
+ 4: '== 1.x.x',
+ 5: '== 2.0.0-alpha.x',
+ 6: '>= 2.0.0-beta.1'
+ };
+ __exports__.REVISION_CHANGES = REVISION_CHANGES;
+ var isArray = Utils.isArray,
+ isFunction = Utils.isFunction,
+ toString = Utils.toString,
+ objectType = '[object Object]';
+
+ function HandlebarsEnvironment(helpers, partials) {
+ this.helpers = helpers || {};
+ this.partials = partials || {};
+
+ registerDefaultHelpers(this);
+ }
+
+ __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
+ constructor: HandlebarsEnvironment,
+
+ logger: logger,
+ log: log,
+
+ registerHelper: function(name, fn) {
+ if (toString.call(name) === objectType) {
+ if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
+ Utils.extend(this.helpers, name);
+ } else {
+ this.helpers[name] = fn;
+ }
+ },
+ unregisterHelper: function(name) {
+ delete this.helpers[name];
+ },
+
+ registerPartial: function(name, partial) {
+ if (toString.call(name) === objectType) {
+ Utils.extend(this.partials, name);
+ } else {
+ this.partials[name] = partial;
+ }
+ },
+ unregisterPartial: function(name) {
+ delete this.partials[name];
+ }
+ };
+
+ function registerDefaultHelpers(instance) {
+ instance.registerHelper('helperMissing', function(/* [args, ]options */) {
+ if(arguments.length === 1) {
+ // A missing field in a {{foo}} constuct.
+ return undefined;
+ } else {
+ // Someone is actually trying to call something, blow up.
+ throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
+ }
+ });
+
+ instance.registerHelper('blockHelperMissing', function(context, options) {
+ var inverse = options.inverse,
+ fn = options.fn;
+
+ if(context === true) {
+ return fn(this);
+ } else if(context === false || context == null) {
+ return inverse(this);
+ } else if (isArray(context)) {
+ if(context.length > 0) {
+ if (options.ids) {
+ options.ids = [options.name];
+ }
+
+ return instance.helpers.each(context, options);
+ } else {
+ return inverse(this);
+ }
+ } else {
+ if (options.data && options.ids) {
+ var data = createFrame(options.data);
+ data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
+ options = {data: data};
+ }
+
+ return fn(context, options);
+ }
+ });
+
+ instance.registerHelper('each', function(context, options) {
+ if (!options) {
+ throw new Exception('Must pass iterator to #each');
+ }
+
+ var fn = options.fn, inverse = options.inverse;
+ var i = 0, ret = "", data;
+
+ var contextPath;
+ if (options.data && options.ids) {
+ contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
+ }
+
+ if (isFunction(context)) { context = context.call(this); }
+
+ if (options.data) {
+ data = createFrame(options.data);
+ }
+
+ if(context && typeof context === 'object') {
+ if (isArray(context)) {
+ for(var j = context.length; i<j; i++) {
+ if (data) {
+ data.index = i;
+ data.first = (i === 0);
+ data.last = (i === (context.length-1));
+
+ if (contextPath) {
+ data.contextPath = contextPath + i;
+ }
+ }
+ ret = ret + fn(context[i], { data: data });
+ }
+ } else {
+ for(var key in context) {
+ if(context.hasOwnProperty(key)) {
+ if(data) {
+ data.key = key;
+ data.index = i;
+ data.first = (i === 0);
+
+ if (contextPath) {
+ data.contextPath = contextPath + key;
+ }
+ }
+ ret = ret + fn(context[key], {data: data});
+ i++;
+ }
+ }
+ }
+ }
+
+ if(i === 0){
+ ret = inverse(this);
+ }
+
+ return ret;
+ });
+
+ instance.registerHelper('if', function(conditional, options) {
+ if (isFunction(conditional)) { conditional = conditional.call(this); }
+
+ // Default behavior is to render the positive path if the value is truthy and not empty.
+ // The `includeZero` option may be set to treat the condtional as purely not empty based on the
+ // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
+ if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
+ return options.inverse(this);
+ } else {
+ return options.fn(this);
+ }
+ });
+
+ instance.registerHelper('unless', function(conditional, options) {
+ return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
+ });
+
+ instance.registerHelper('with', function(context, options) {
+ if (isFunction(context)) { context = context.call(this); }
+
+ var fn = options.fn;
+
+ if (!Utils.isEmpty(context)) {
+ if (options.data && options.ids) {
+ var data = createFrame(options.data);
+ data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
+ options = {data:data};
+ }
+
+ return fn(context, options);
+ } else {
+ return options.inverse(this);
+ }
+ });
+
+ instance.registerHelper('log', function(message, options) {
+ var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
+ instance.log(level, message);
+ });
+
+ instance.registerHelper('lookup', function(obj, field) {
+ return obj && obj[field];
+ });
+ }
+
+ var logger = {
+ methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
+
+ // State enum
+ DEBUG: 0,
+ INFO: 1,
+ WARN: 2,
+ ERROR: 3,
+ level: 3,
+
+ // can be overridden in the host environment
+ log: function(level, message) {
+ if (logger.level <= level) {
+ var method = logger.methodMap[level];
+ if (typeof console !== 'undefined' && console[method]) {
+ console[method].call(console, message);
+ }
+ }
+ }
+ };
+ __exports__.logger = logger;
+ var log = logger.log;
+ __exports__.log = log;
+ var createFrame = function(object) {
+ var frame = Utils.extend({}, object);
+ frame._parent = object;
+ return frame;
+ };
+ __exports__.createFrame = createFrame;
+ return __exports__;
+})(__module2__, __module4__);
+
+// handlebars/runtime.js
+var __module5__ = (function(__dependency1__, __dependency2__, __dependency3__) {
+ "use strict";
+ var __exports__ = {};
+ var Utils = __dependency1__;
+ var Exception = __dependency2__;
+ var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
+ var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
+ var createFrame = __dependency3__.createFrame;
+
+ function checkRevision(compilerInfo) {
+ var compilerRevision = compilerInfo && compilerInfo[0] || 1,
+ currentRevision = COMPILER_REVISION;
+
+ if (compilerRevision !== currentRevision) {
+ if (compilerRevision < currentRevision) {
+ var runtimeVersions = REVISION_CHANGES[currentRevision],
+ compilerVersions = REVISION_CHANGES[compilerRevision];
+ throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
+ "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
+ } else {
+ // Use the embedded version info since the runtime doesn't know about this revision yet
+ throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
+ "Please update your runtime to a newer version ("+compilerInfo[1]+").");
+ }
+ }
+ }
+
+ __exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
+
+ function template(templateSpec, env) {
+ /* istanbul ignore next */
+ if (!env) {
+ throw new Exception("No environment passed to template");
+ }
+ if (!templateSpec || !templateSpec.main) {
+ throw new Exception('Unknown template object: ' + typeof templateSpec);
+ }
+
+ // Note: Using env.VM references rather than local var references throughout this section to allow
+ // for external users to override these as psuedo-supported APIs.
+ env.VM.checkRevision(templateSpec.compiler);
+
+ var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data, depths) {
+ if (hash) {
+ context = Utils.extend({}, context, hash);
+ }
+
+ var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data, depths);
+
+ if (result == null && env.compile) {
+ var options = { helpers: helpers, partials: partials, data: data, depths: depths };
+ partials[name] = env.compile(partial, { data: data !== undefined, compat: templateSpec.compat }, env);
+ result = partials[name](context, options);
+ }
+ if (result != null) {
+ if (indent) {
+ var lines = result.split('\n');
+ for (var i = 0, l = lines.length; i < l; i++) {
+ if (!lines[i] && i + 1 === l) {
+ break;
+ }
+
+ lines[i] = indent + lines[i];
+ }
+ result = lines.join('\n');
+ }
+ return result;
+ } else {
+ throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
+ }
+ };
+
+ // Just add water
+ var container = {
+ lookup: function(depths, name) {
+ var len = depths.length;
+ for (var i = 0; i < len; i++) {
+ if (depths[i] && depths[i][name] != null) {
+ return depths[i][name];
+ }
+ }
+ },
+ lambda: function(current, context) {
+ return typeof current === 'function' ? current.call(context) : current;
+ },
+
+ escapeExpression: Utils.escapeExpression,
+ invokePartial: invokePartialWrapper,
+
+ fn: function(i) {
+ return templateSpec[i];
+ },
+
+ programs: [],
+ program: function(i, data, depths) {
+ var programWrapper = this.programs[i],
+ fn = this.fn(i);
+ if (data || depths) {
+ programWrapper = program(this, i, fn, data, depths);
+ } else if (!programWrapper) {
+ programWrapper = this.programs[i] = program(this, i, fn);
+ }
+ return programWrapper;
+ },
+
+ data: function(data, depth) {
+ while (data && depth--) {
+ data = data._parent;
+ }
+ return data;
+ },
+ merge: function(param, common) {
+ var ret = param || common;
+
+ if (param && common && (param !== common)) {
+ ret = Utils.extend({}, common, param);
+ }
+
+ return ret;
+ },
+
+ noop: env.VM.noop,
+ compilerInfo: templateSpec.compiler
+ };
+
+ var ret = function(context, options) {
+ options = options || {};
+ var data = options.data;
+
+ ret._setup(options);
+ if (!options.partial && templateSpec.useData) {
+ data = initData(context, data);
+ }
+ var depths;
+ if (templateSpec.useDepths) {
+ depths = options.depths ? [context].concat(options.depths) : [context];
+ }
+
+ return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
+ };
+ ret.isTop = true;
+
+ ret._setup = function(options) {
+ if (!options.partial) {
+ container.helpers = container.merge(options.helpers, env.helpers);
+
+ if (templateSpec.usePartial) {
+ container.partials = container.merge(options.partials, env.partials);
+ }
+ } else {
+ container.helpers = options.helpers;
+ container.partials = options.partials;
+ }
+ };
+
+ ret._child = function(i, data, depths) {
+ if (templateSpec.useDepths && !depths) {
+ throw new Exception('must pass parent depths');
+ }
+
+ return program(container, i, templateSpec[i], data, depths);
+ };
+ return ret;
+ }
+
+ __exports__.template = template;function program(container, i, fn, data, depths) {
+ var prog = function(context, options) {
+ options = options || {};
+
+ return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
+ };
+ prog.program = i;
+ prog.depth = depths ? depths.length : 0;
+ return prog;
+ }
+
+ __exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data, depths) {
+ var options = { partial: true, helpers: helpers, partials: partials, data: data, depths: depths };
+
+ if(partial === undefined) {
+ throw new Exception("The partial " + name + " could not be found");
+ } else if(partial instanceof Function) {
+ return partial(context, options);
+ }
+ }
+
+ __exports__.invokePartial = invokePartial;function noop() { return ""; }
+
+ __exports__.noop = noop;function initData(context, data) {
+ if (!data || !('root' in data)) {
+ data = data ? createFrame(data) : {};
+ data.root = context;
+ }
+ return data;
+ }
+ return __exports__;
+})(__module2__, __module4__, __module1__);
+
+// handlebars.runtime.js
+var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
+ "use strict";
+ var __exports__;
+ /*globals Handlebars: true */
+ var base = __dependency1__;
+
+ // Each of these augment the Handlebars object. No need to setup here.
+ // (This is done to easily share code between commonjs and browse envs)
+ var SafeString = __dependency2__;
+ var Exception = __dependency3__;
+ var Utils = __dependency4__;
+ var runtime = __dependency5__;
+
+ // For compatibility and usage outside of module systems, make the Handlebars object a namespace
+ var create = function() {
+ var hb = new base.HandlebarsEnvironment();
+
+ Utils.extend(hb, base);
+ hb.SafeString = SafeString;
+ hb.Exception = Exception;
+ hb.Utils = Utils;
+ hb.escapeExpression = Utils.escapeExpression;
+
+ hb.VM = runtime;
+ hb.template = function(spec) {
+ return runtime.template(spec, hb);
+ };
+
+ return hb;
+ };
+
+ var Handlebars = create();
+ Handlebars.create = create;
+
+ Handlebars['default'] = Handlebars;
+
+ __exports__ = Handlebars;
+ return __exports__;
+})(__module1__, __module3__, __module4__, __module2__, __module5__);
+
+ return __module0__;
+}));
diff --git a/browser/extensions/pocket/content/panels/js/vendor/jquery-2.1.1.min.js b/browser/extensions/pocket/content/panels/js/vendor/jquery-2.1.1.min.js
new file mode 100644
index 000000000..9ed2acc66
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/js/vendor/jquery-2.1.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)
+},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ib={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp("^("+Q+")(.*)$","i"),Bb=new RegExp("^([+-])=("+Q+")","i"),Cb={position:"absolute",visibility:"hidden",display:"block"},Db={letterSpacing:"0",fontWeight:"400"},Eb=["Webkit","O","Moz","ms"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?"border":"content"),d,f)+"px"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",tb(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fb(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fb(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),"normal"===e&&b in Db&&(e=Db[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?zb.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=yb(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xb,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(n.cssHooks[a+b].set=Gb)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}n.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Kb.prototype.init,n.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pb=/queueHooks$/,Qb=[Vb],Rb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Ob.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=n.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||tb(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?tb(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ub(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return n.map(k,Ub,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xb,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xb(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),n.each({slideDown:Tb("show"),slideUp:Tb("hide"),slideToggle:Tb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Lb=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Lb=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Mb||(Mb=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Mb),Mb=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Yb,Zb,$b=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))
+},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$b[b]||n.find.attr;$b[b]=function(a,b,d){var e,f;return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=n.now(),dc=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*([^\r\n]*)$/gm,jc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,kc=/^(?:GET|HEAD)$/,lc=/^\/\//,mc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,nc={},oc={},pc="*/".concat("*");try{fc=location.href}catch(qc){fc=l.createElement("a"),fc.href="",fc=fc.href}ec=mc.exec(fc.toLowerCase())||[];function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function tc(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fc,type:"GET",isLocal:jc.test(ec[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":pc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?tc(tc(a,n.ajaxSettings),b):tc(n.ajaxSettings,a)},ajaxPrefilter:rc(nc),ajaxTransport:rc(oc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=ic.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||fc)+"").replace(gc,"").replace(lc,ec[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=mc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===ec[1]&&h[2]===ec[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(ec[3]||("http:"===ec[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),sc(nc,k,b,v),2===t)return v;i=k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!kc.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=hc.test(d)?d.replace(hc,"$1_="+cc++):d+(dc.test(d)?"&":"?")+"_="+cc++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+pc+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=sc(oc,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=uc(k,v,f)),u=vc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var wc=/%20/g,xc=/\[\]$/,yc=/\r?\n/g,zc=/^(?:submit|button|image|reset|file)$/i,Ac=/^(?:input|select|textarea|keygen)/i;function Bc(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||xc.test(a)?d(a,e):Bc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Bc(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Bc(c,a[c],b,e);return d.join("&").replace(wc,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Ac.test(this.nodeName)&&!zc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(yc,"\r\n")}}):{name:b.name,value:c.replace(yc,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Cc=0,Dc={},Ec={0:200,1223:204},Fc=n.ajaxSettings.xhr();a.ActiveXObject&&n(a).on("unload",function(){for(var a in Dc)Dc[a]()}),k.cors=!!Fc&&"withCredentials"in Fc,k.ajax=Fc=!!Fc,n.ajaxTransport(function(a){var b;return k.cors||Fc&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Cc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Dc[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Ec[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Dc[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Gc=[],Hc=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Gc.pop()||n.expando+"_"+cc++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Hc.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Hc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Hc,"$1"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Gc.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Ic=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Ic)return Ic.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Jc=a.document.documentElement;function Kc(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Kc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Jc;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Jc})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Kc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=yb(k.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Lc=a.jQuery,Mc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Mc),b&&a.jQuery===n&&(a.jQuery=Lc),n},typeof b===U&&(a.jQuery=a.$=n),n}); \ No newline at end of file
diff --git a/browser/extensions/pocket/content/panels/js/vendor/jquery.tokeninput.min.js b/browser/extensions/pocket/content/panels/js/vendor/jquery.tokeninput.min.js
new file mode 100644
index 000000000..40d323df0
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/js/vendor/jquery.tokeninput.min.js
@@ -0,0 +1,954 @@
+/*
+ * jQuery Plugin: Tokenizing Autocomplete Text Entry
+ * Version 1.6.0
+ *
+ * Copyright (c) 2009 James Smith (http://loopj.com)
+ * Licensed jointly under the GPL and MIT licenses,
+ * choose which one suits your project best!
+ *
+ * Licensed under MIT
+ * With modifications
+ *
+ */
+
+(function ($) {
+// Default settings
+var DEFAULT_SETTINGS = {
+ // Search settings
+ method: "GET",
+ contentType: "json",
+ queryParam: "q",
+ searchDelay: 300,
+ minChars: 1,
+ propertyToSearch: "name",
+ jsonContainer: null,
+ scrollKeyboard: false,
+
+ // Display settings
+ hintText: null,
+ noResultsText: null,
+ noResultsHideDropdown: false,
+ searchingText: null,
+ deleteText: "&times;",
+ animateDropdown: true,
+ emptyInputLength: null,
+
+ // Tokenization settings
+ tokenLimit: null,
+ tokenDelimiter: ",",
+ preventDuplicates: false,
+
+ // Output settings
+ tokenValue: "id",
+
+ // Prepopulation settings
+ prePopulate: null,
+ processPrePopulate: false,
+
+ // Manipulation settings
+ idPrefix: "token-input-",
+
+ // Formatters
+ resultsFormatter: function(item) {
+ let listItem = document.createElement("li");
+ listItem.textContent = item[this.propertyToSearch];
+ return listItem.outerHTML;
+ },
+ tokenFormatter: function(item) {
+ let listItem = document.createElement("li");
+ let p = document.createElement("p");
+ p.textContent = item[this.propertyToSearch];
+ listItem.appendChild(p);
+ return listItem.outerHTML;
+ },
+
+ // Validations
+ validateItem: null,
+
+ // Force selections only on mouse click
+ noHoverSelect: false,
+
+ // Callbacks
+ onResult: null,
+ onAdd: null,
+ onDelete: null,
+ onReady: null
+};
+
+// Default classes to use when theming
+var DEFAULT_CLASSES = {
+ tokenList: "token-input-list",
+ token: "token-input-token",
+ tokenDelete: "token-input-delete-token",
+ selectedToken: "token-input-selected-token",
+ highlightedToken: "token-input-highlighted-token",
+ dropdown: "token-input-dropdown",
+ dropdownItem: "token-input-dropdown-item",
+ dropdownItem2: "token-input-dropdown-item2",
+ selectedDropdownItem: "token-input-selected-dropdown-item",
+ inputToken: "token-input-input-token"
+};
+
+// Input box position "enum"
+var POSITION = {
+ BEFORE: 0,
+ AFTER: 1,
+ END: 2
+};
+
+// Keys "enum"
+var KEY = {
+ BACKSPACE: 8,
+ TAB: 9,
+ ENTER: 13,
+ ESCAPE: 27,
+ SPACE: 32,
+ PAGE_UP: 33,
+ PAGE_DOWN: 34,
+ END: 35,
+ HOME: 36,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ NUMPAD_ENTER: 108,
+ COMMA: 188
+};
+
+// Additional public (exposed) methods
+var methods = {
+ init: function(url_or_data_or_function, options) {
+ var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
+
+ return this.each(function () {
+ $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
+ });
+ },
+ clear: function() {
+ this.data("tokenInputObject").clear();
+ return this;
+ },
+ add: function(item) {
+ this.data("tokenInputObject").add(item);
+ return this;
+ },
+ remove: function(item) {
+ this.data("tokenInputObject").remove(item);
+ return this;
+ },
+ get: function() {
+ return this.data("tokenInputObject").getTokens();
+ }
+}
+
+// Expose the .tokenInput function to jQuery as a plugin
+$.fn.tokenInput = function (method) {
+ // Method calling and initialization logic
+ if(methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else {
+ return methods.init.apply(this, arguments);
+ }
+};
+
+// TokenList class for each input
+$.TokenList = function (input, url_or_data, settings) {
+ //
+ // Initialization
+ //
+
+ // Configure the data source
+ if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
+ // Set the url to query against
+ settings.url = url_or_data;
+
+ // If the URL is a function, evaluate it here to do our initalization work
+ var url = computeURL();
+
+ // Make a smart guess about cross-domain if it wasn't explicitly specified
+ if(settings.crossDomain === undefined) {
+ if(url.indexOf("://") === -1) {
+ settings.crossDomain = false;
+ } else {
+ settings.crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
+ }
+ }
+ } else if(typeof(url_or_data) === "object") {
+ // Set the local data to search through
+ settings.local_data = url_or_data;
+ }
+
+ // Build class names
+ if(settings.classes) {
+ // Use custom class names
+ settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
+ } else if(settings.theme) {
+ // Use theme-suffixed default class names
+ settings.classes = {};
+ $.each(DEFAULT_CLASSES, function(key, value) {
+ settings.classes[key] = value + "-" + settings.theme;
+ });
+ } else {
+ settings.classes = DEFAULT_CLASSES;
+ }
+
+
+ // Save the tokens
+ var saved_tokens = [];
+
+ // Keep track of the number of tokens in the list
+ var token_count = 0;
+
+ // Basic cache to save on db hits
+ var cache = new $.TokenList.Cache();
+
+ // Keep track of the timeout, old vals
+ var timeout;
+ var input_val;
+
+ function tokenize(){
+ var item = $(selected_dropdown_item).data("tokeninput");
+ if(!item && settings.textToData){
+ item = settings.textToData(input_box.val());
+ }
+
+ if(item) {
+ add_token(item);
+ hidden_input.change();
+ return false;
+ }
+ }
+
+ // Create a new text input an attach keyup events
+ var input_box = $("<input type=\"text\" autocomplete=\"off\">")
+ .css({
+ outline: "none"
+ })
+ .attr("id", settings.idPrefix + input.id)
+ .focus(function () {
+ if (settings.minChars == 0) {
+ setTimeout(function(){do_search();}, 5);
+ }
+ if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
+ show_dropdown_hint();
+ }
+ })
+ .blur(function () {
+ tokenize();
+ hide_dropdown();
+ $(this).val("");
+ })
+ .bind("keyup keydown blur update", resize_input)
+ .keydown(function (event) {
+ var previous_token;
+ var next_token;
+
+ switch(event.keyCode) {
+ case KEY.LEFT:
+ case KEY.RIGHT:
+ case KEY.UP:
+ case KEY.DOWN:
+ if(!$(this).val()) {
+ previous_token = input_token.prev();
+ next_token = input_token.next();
+
+ if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
+ // Check if there is a previous/next token and it is selected
+ if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
+ deselect_token($(selected_token), POSITION.BEFORE);
+ } else {
+ deselect_token($(selected_token), POSITION.AFTER);
+ }
+ } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
+ // We are moving left, select the previous token if it exists
+ select_token($(previous_token.get(0)));
+ } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
+ // We are moving right, select the next token if it exists
+ select_token($(next_token.get(0)));
+ }
+ } else {
+ if (event.keyCode === KEY.UP || event.keyCode === KEY.DOWN) {
+ var dropdown_item = null;
+ if(!selected_dropdown_item && (event.keyCode === KEY.DOWN)) {
+ dropdown_item = $('.token-input-dropdown li').first();
+ }
+ else if(event.keyCode === KEY.DOWN) {
+ dropdown_item = $(selected_dropdown_item).next();
+ } else {
+ dropdown_item = $(selected_dropdown_item).prev();
+ }
+
+ if(dropdown_item.length) {
+ select_dropdown_item(dropdown_item,true);
+ }
+ else if (!(event.keyCode === KEY.DOWN) && $(selected_dropdown_item).length) {
+ deselect_dropdown_item($(selected_dropdown_item));
+ }
+ return false;
+ }
+ }
+ break;
+
+ case KEY.BACKSPACE:
+ previous_token = input_token.prev();
+
+ if(!$(this).val().length) {
+ if(selected_token) {
+ delete_token($(selected_token));
+ hidden_input.change();
+ } else if(previous_token.length) {
+ select_token($(previous_token.get(0)));
+ }
+
+ return false;
+ } else if($(this).val().length === 1) {
+ hide_dropdown();
+ } else {
+ // set a timeout just long enough to let this function finish.
+ setTimeout(function(){do_search();}, 5);
+ }
+ break;
+
+ case KEY.TAB:
+ case KEY.ENTER:
+ case KEY.NUMPAD_ENTER:
+ case KEY.COMMA:
+ if (event.keyCode != KEY.ENTER && event.keyCode != KEY.NUMPAD_ENTER)
+ {
+ event.preventDefault();
+ }
+ tokenize();
+ break;
+
+ case KEY.ESCAPE:
+ hide_dropdown();
+ return true;
+
+ default:
+ if(String.fromCharCode(event.which)) {
+ // set a timeout just long enough to let this function finish.
+ setTimeout(function(){do_search();}, 5);
+ }
+ break;
+ }
+ });
+
+ // Keep a reference to the original input box
+ var hidden_input = $(input)
+ .hide()
+ .val("")
+ .focus(function () {
+ input_box.focus();
+ })
+ .blur(function () {
+ input_box.blur();
+ });
+
+ // Keep a reference to the selected token and dropdown item
+ var selected_token = null;
+ var selected_token_index = 0;
+ var selected_dropdown_item = null;
+
+ // The list to store the token items in
+ var token_list = $("<ul />")
+ .addClass(settings.classes.tokenList)
+ .click(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
+ toggle_select_token(li);
+ } else {
+ // Deselect selected token
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.END);
+ }
+
+ // Focus input box
+ input_box.focus();
+ }
+ })
+ .mouseover(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && selected_token !== this) {
+ li.addClass(settings.classes.highlightedToken);
+ }
+ })
+ .mouseout(function (event) {
+ var li = $(event.target).closest("li");
+ if(li && selected_token !== this) {
+ li.removeClass(settings.classes.highlightedToken);
+ }
+ })
+ .insertBefore(hidden_input);
+
+ // The token holding the input box
+ var input_token = $("<li />")
+ .addClass(settings.classes.inputToken)
+ .appendTo(token_list)
+ .append(input_box);
+
+ // The list to store the dropdown items in
+ var dropdown = $("<div>")
+ .addClass(settings.classes.dropdown)
+ .appendTo("body")
+ .hide();
+
+ // Magic element to help us resize the text input
+ var input_resizer = $("<tester/>")
+ .insertAfter(input_box)
+ .css({
+ position: "absolute",
+ top: -9999,
+ left: -9999,
+ width: "auto",
+ fontSize: input_box.css("fontSize"),
+ fontFamily: input_box.css("fontFamily"),
+ fontWeight: input_box.css("fontWeight"),
+ letterSpacing: input_box.css("letterSpacing"),
+ whiteSpace: "nowrap"
+ });
+
+ // Pre-populate list if items exist
+ hidden_input.val("");
+ var li_data = settings.prePopulate || hidden_input.data("pre");
+ if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
+ li_data = settings.onResult.call(hidden_input, li_data);
+ }
+ if(li_data && li_data.length) {
+ $.each(li_data, function (index, value) {
+ insert_token(value);
+ checkTokenLimit();
+ });
+ }
+
+ // Initialization is done
+ if($.isFunction(settings.onReady)) {
+ settings.onReady.call();
+ if (settings.minChars == 0)
+ {
+ setTimeout(function(){do_search();}, 5);
+ }
+ }
+
+ //
+ // Public functions
+ //
+
+ this.clear = function() {
+ token_list.children("li").each(function() {
+ if ($(this).children("input").length === 0) {
+ delete_token($(this));
+ }
+ });
+ }
+
+ this.add = function(item) {
+ add_token(item);
+ }
+
+ this.remove = function(item) {
+ token_list.children("li").each(function() {
+ if ($(this).children("input").length === 0) {
+ var currToken = $(this).data("tokeninput");
+ var match = true;
+ for (var prop in item) {
+ if (item[prop] !== currToken[prop]) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ delete_token($(this));
+ }
+ }
+ });
+ }
+
+ this.getTokens = function() {
+ return saved_tokens;
+ }
+
+ //
+ // Private functions
+ //
+
+ function checkTokenLimit() {
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
+ input_box.hide();
+ hide_dropdown();
+ return;
+ }
+ }
+
+ function resize_input() {
+ if(input_val === (input_val = input_box.val())) {return;}
+
+ // Enter new content into resizer and resize input accordingly
+ var escaped = input_val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ input_resizer.html(escaped);
+ var minwidth = 30;
+ if (settings.emptyInputLength && token_list.children().length < 2) {
+ minwidth = settings.emptyInputLength;
+ }
+ input_box.width(input_resizer.width() + minwidth);
+ }
+
+ function is_printable_character(keycode) {
+ return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
+ (keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
+ (keycode >= 186 && keycode <= 192) || // ; = , - . / ^
+ (keycode >= 219 && keycode <= 222)); // ( \ ) '
+ }
+
+ // Inner function to a token to the list
+ function insert_token(item) {
+ var this_token = settings.tokenFormatter(item);
+ this_token = $(this_token)
+ .addClass(settings.classes.token)
+ .insertBefore(input_token);
+
+ // The 'delete token' button
+ $("<span>" + settings.deleteText + "</span>")
+ .addClass(settings.classes.tokenDelete)
+ .appendTo(this_token)
+ .click(function () {
+ delete_token($(this).parent());
+ hidden_input.change();
+ return false;
+ });
+
+ // Store data on the token
+ var token_data = {"id": item.id};
+ token_data[settings.propertyToSearch] = item[settings.propertyToSearch];
+ token_data.item = item;
+ $.data(this_token.get(0), "tokeninput", item);
+
+ // Save this token for duplicate checking
+ saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
+ selected_token_index++;
+
+ // Update the hidden input
+ update_hidden_input(saved_tokens, hidden_input);
+
+ token_count += 1;
+
+ // Check the token limit
+ if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
+ input_box.hide();
+ hide_dropdown();
+ }
+
+ return this_token;
+ }
+
+ // Add a token to the token list based on user input
+ function add_token (item) {
+ if(!item) return;
+
+ // Check for item validation
+ if ($.isFunction(settings.validateItem) && !settings.validateItem(item)) {
+ return false;
+ }
+
+ var callback = settings.onAdd;
+
+ // See if the token already exists and select it if we don't want duplicates
+ if(token_count > 0 && settings.preventDuplicates) {
+ var found_existing_token = null;
+ token_list.children().each(function () {
+ var existing_token = $(this);
+ var existing_data = $.data(existing_token.get(0), "tokeninput");
+ if(existing_data && existing_data.id === item.id) {
+ found_existing_token = existing_token;
+ return false;
+ }
+ });
+
+ if(found_existing_token) {
+ select_token(found_existing_token);
+ input_token.insertAfter(found_existing_token);
+ input_box.focus();
+ return;
+ }
+ }
+
+ // Insert the new tokens
+ if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
+ insert_token(item);
+ checkTokenLimit();
+ }
+
+ // Clear input box
+ input_box.val("");
+
+ // Don't show the help dropdown, they've got the idea
+ hide_dropdown();
+
+ // Execute the onAdd callback if defined
+ if($.isFunction(callback)) {
+ callback.call(hidden_input,item);
+ }
+ }
+
+ // Select a token in the token list
+ function select_token (token) {
+ token.addClass(settings.classes.selectedToken);
+ selected_token = token.get(0);
+
+ // Hide input box
+ input_box.val("");
+
+ // Hide dropdown if it is visible (eg if we clicked to select token)
+ hide_dropdown();
+ }
+
+ // Deselect a token in the token list
+ function deselect_token (token, position) {
+ token.removeClass(settings.classes.selectedToken);
+ selected_token = null;
+
+ if(position === POSITION.BEFORE) {
+ input_token.insertBefore(token);
+ selected_token_index--;
+ } else if(position === POSITION.AFTER) {
+ input_token.insertAfter(token);
+ selected_token_index++;
+ } else {
+ input_token.appendTo(token_list);
+ selected_token_index = token_count;
+ }
+
+ // Show the input box and give it focus again
+ input_box.focus();
+ }
+
+ // Toggle selection of a token in the token list
+ function toggle_select_token(token) {
+ var previous_selected_token = selected_token;
+
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.END);
+ }
+
+ if(previous_selected_token === token.get(0)) {
+ deselect_token(token, POSITION.END);
+ } else {
+ select_token(token);
+ }
+ }
+
+ // Delete a token from the token list
+ function delete_token (token) {
+ // Remove the id from the saved list
+ var token_data = $.data(token.get(0), "tokeninput");
+ var callback = settings.onDelete;
+
+ var index = token.prevAll().length;
+ if(index > selected_token_index) index--;
+
+ // Delete the token
+ token.remove();
+ selected_token = null;
+
+ // Show the input box and give it focus again
+ input_box.focus();
+
+ // Remove this token from the saved list
+ saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
+ if(index < selected_token_index) selected_token_index--;
+
+ // Update the hidden input
+ update_hidden_input(saved_tokens, hidden_input);
+
+ token_count -= 1;
+
+ if(settings.tokenLimit !== null) {
+ input_box
+ .show()
+ .val("")
+ .focus();
+ }
+
+ // Execute the onDelete callback if defined
+ if($.isFunction(callback)) {
+ callback.call(hidden_input,token_data);
+ }
+ }
+
+ // Update the hidden input box value
+ function update_hidden_input(saved_tokens, hidden_input) {
+ var token_values = $.map(saved_tokens, function (el) {
+ return el[settings.tokenValue];
+ });
+ hidden_input.val(token_values.join(settings.tokenDelimiter));
+
+ }
+
+ // Hide and clear the results dropdown
+ function hide_dropdown () {
+ dropdown.hide().empty();
+ selected_dropdown_item = null;
+ if (settings.onHideDropdown)
+ settings.onHideDropdown();
+ }
+
+ function show_dropdown() {
+ dropdown
+ .css({
+ position: "absolute",
+ top: $(token_list).offset().top + $(token_list).outerHeight(),
+ left: $(token_list).offset().left,
+ zindex: 999
+ })
+ .show();
+ if (settings.onShowDropdown)
+ settings.onShowDropdown();
+ }
+
+ function show_dropdown_searching () {
+ if(settings.searchingText) {
+ dropdown.html("<p>"+settings.searchingText+"</p>");
+ show_dropdown();
+ }
+ }
+
+ function show_dropdown_hint () {
+ if(settings.hintText) {
+ dropdown.html("<p>"+settings.hintText+"</p>");
+ show_dropdown();
+ }
+ }
+
+ // Highlight the query part of the search term
+ function highlight_term(value, term) {
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
+ }
+
+ function find_value_and_highlight_term(template, value, term) {
+ return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
+ }
+
+ // Populate the results dropdown with some results
+ function populate_dropdown (query, results) {
+ if(results && results.length) {
+ dropdown.empty();
+ var dropdown_ul = $("<ul>")
+ .appendTo(dropdown)
+ .mouseover(function (event) {
+ select_dropdown_item($(event.target).closest("li"));
+ })
+ .mousedown(function (event) {
+ add_token($(event.target).closest("li").data("tokeninput"));
+ hidden_input.change();
+ return false;
+ })
+ .hide();
+ if (settings.noHoverSelect) {
+ dropdown_ul.off('mouseover');
+ dropdown_ul.on('mouseover',function (event) {
+ $(this).find("li").removeClass(settings.classes.selectedDropdownItem);
+ $(event.target).closest("li").addClass(settings.classes.selectedDropdownItem);
+ });
+ }
+
+ $.each(results, function(index, value) {
+ var this_li = settings.resultsFormatter(value);
+
+ // this_li = find_value_and_highlight_term(this_li ,value[settings.propertyToSearch], query);
+
+ this_li = $(this_li).appendTo(dropdown_ul);
+
+ if(index % 2) {
+ this_li.addClass(settings.classes.dropdownItem);
+ } else {
+ this_li.addClass(settings.classes.dropdownItem2);
+ }
+
+ // if(index === 0) {
+ // select_dropdown_item(this_li);
+ // }
+
+ $.data(this_li.get(0), "tokeninput", value);
+ });
+
+ show_dropdown();
+
+ if(settings.animateDropdown) {
+ dropdown_ul.slideDown("fast");
+ } else {
+ dropdown_ul.show();
+ }
+ } else {
+ if(settings.noResultsText) {
+ dropdown.html("<p>"+settings.noResultsText+"</p>");
+ show_dropdown();
+ }
+ if (settings.noResultsHideDropdown) {
+ hide_dropdown();
+ }
+ }
+ }
+
+ // Highlight an item in the results dropdown
+ function select_dropdown_item (item,withkeyboard) {
+ if(item) {
+ if(selected_dropdown_item) {
+ deselect_dropdown_item($(selected_dropdown_item));
+ }
+ if (settings.scrollKeyboard && withkeyboard) {
+ var list = $('.token-input-dropdown-tag ul');
+ var listheight = list.height();
+ var itemheight = item.outerHeight();
+ var itemtop = item.position().top;
+ if (itemtop > listheight) {
+ var listscroll = list.scrollTop();
+ list.scrollTop(listscroll + itemheight);
+ }
+ else if (itemtop < 0) {
+ var listscroll = list.scrollTop();
+ list.scrollTop(listscroll - itemheight);
+ }
+
+ }
+ item.addClass(settings.classes.selectedDropdownItem);
+ selected_dropdown_item = item.get(0);
+ }
+ }
+
+ // Remove highlighting from an item in the results dropdown
+ function deselect_dropdown_item (item) {
+ item.removeClass(settings.classes.selectedDropdownItem);
+ selected_dropdown_item = null;
+ }
+
+ // Do a search and show the "searching" dropdown if the input is longer
+ // than settings.minChars
+ function do_search() {
+ var query = input_box.val().toLowerCase();
+ if(query && query.length || settings.minChars == 0) {
+ if(selected_token) {
+ deselect_token($(selected_token), POSITION.AFTER);
+ }
+
+ if(query.length >= settings.minChars) {
+ show_dropdown_searching();
+ clearTimeout(timeout);
+
+ timeout = setTimeout(function(){
+ run_search(query);
+ }, settings.searchDelay);
+ } else {
+ hide_dropdown();
+ }
+ }
+ }
+
+ // Do the actual search
+ function run_search(query) {
+ var cache_key = query + computeURL();
+ var cached_results = cache.get(cache_key);
+ if(cached_results) {
+ populate_dropdown(query, cached_results);
+ } else {
+ // Are we doing an ajax search or local data search?
+ if(settings.url) {
+ var url = computeURL();
+ // Extract exisiting get params
+ var ajax_params = {};
+ ajax_params.data = {};
+ if(url.indexOf("?") > -1) {
+ var parts = url.split("?");
+ ajax_params.url = parts[0];
+
+ var param_array = parts[1].split("&");
+ $.each(param_array, function (index, value) {
+ var kv = value.split("=");
+ ajax_params.data[kv[0]] = kv[1];
+ });
+ } else {
+ ajax_params.url = url;
+ }
+
+ // Prepare the request
+ ajax_params.data[settings.queryParam] = query;
+ ajax_params.type = settings.method;
+ ajax_params.dataType = settings.contentType;
+ if(settings.crossDomain) {
+ ajax_params.dataType = "jsonp";
+ }
+
+ // Attach the success callback
+ ajax_params.success = function(results) {
+ if($.isFunction(settings.onResult)) {
+ results = settings.onResult.call(hidden_input, results);
+ }
+ cache.add(cache_key, settings.jsonContainer ? results[settings.jsonContainer] : results);
+
+ // only populate the dropdown if the results are associated with the active search query
+ if(input_box.val().toLowerCase() === query) {
+ populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
+ }
+ };
+
+ // Make the request
+ $.ajax(ajax_params);
+ } else if(settings.search_function){
+ settings.search_function(query, function(results){
+ cache.add(cache_key, results);
+ populate_dropdown(query, results);
+ });
+ } else if(settings.local_data) {
+ // Do the search through local data
+ var results = $.grep(settings.local_data, function (row) {
+ return row[settings.propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
+ });
+
+ if($.isFunction(settings.onResult)) {
+ results = settings.onResult.call(hidden_input, results);
+ }
+ cache.add(cache_key, results);
+ populate_dropdown(query, results);
+ }
+ }
+ }
+
+ // compute the dynamic URL
+ function computeURL() {
+ var url = settings.url;
+ if(typeof settings.url == 'function') {
+ url = settings.url.call();
+ }
+ return url;
+ }
+};
+
+// Really basic cache for the results
+$.TokenList.Cache = function (options) {
+ var settings = $.extend({
+ max_size: 500
+ }, options);
+
+ var data = {};
+ var size = 0;
+
+ var flush = function () {
+ data = {};
+ size = 0;
+ };
+
+ this.add = function (query, results) {
+ if(size > settings.max_size) {
+ flush();
+ }
+
+ if(!data[query]) {
+ size += 1;
+ }
+
+ data[query] = results;
+ };
+
+ this.get = function (query) {
+ return data[query];
+ };
+};
+}(jQuery));
diff --git a/browser/extensions/pocket/content/panels/license.txt b/browser/extensions/pocket/content/panels/license.txt
new file mode 100644
index 000000000..7f3f806ba
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/license.txt
@@ -0,0 +1,35 @@
+
+Unless where otherwise noted, the following license applies to the files
+within this directory and descendents of this directory.
+
+POCKET MARKS
+
+Notwithstanding the permitted uses of the Software (as defined below) pursuant
+to the license set forth below, "Pocket," "Read It Later" and the Pocket icon
+and logos (collectively, the “Pocket Marks”) are registered and common law
+trademarks of Read It Later, Inc. This means that, while you have considerable
+freedom to redistribute and modify the Software, there are tight restrictions
+on your ability to use the Pocket Marks. This license does not grant you any
+rights to use the Pocket Marks except as they are embodied in the Software.
+
+---
+
+SOFTWARE
+
+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/browser/extensions/pocket/content/panels/saved.html b/browser/extensions/pocket/content/panels/saved.html
new file mode 100644
index 000000000..93477d8ee
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/saved.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <base href="chrome://pocket/content/panels/">
+ <title>Pocket: Page Saved</title>
+ <link rel="stylesheet" href="css/normalize.css">
+ <link rel="stylesheet" href="css/firasans.css">
+ <link rel="stylesheet" href="css/saved.css">
+ </head>
+ <body class="pkt_ext_containersaved" aria-live="polite">
+ <script type="text/javascript" src="js/vendor/jquery-2.1.1.min.js"></script>
+ <script type="text/javascript" src="js/vendor/handlebars.runtime.js"></script>
+ <script type="text/javascript" src="js/vendor/jquery.tokeninput.min.js"></script>
+ <script type="text/javascript" src="js/tmpl.js"></script>
+ <script type="text/javascript" src="js/messages.js"></script>
+ <script type="text/javascript" src="js/saved.js"></script>
+ </body>
+</html>
diff --git a/browser/extensions/pocket/content/panels/signup.html b/browser/extensions/pocket/content/panels/signup.html
new file mode 100644
index 000000000..bcdf66ed4
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/signup.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <base href="chrome://pocket/content/panels/">
+ <title>Pocket: Sign Up</title>
+ <link rel="stylesheet" href="css/normalize.css">
+ <link rel="stylesheet" href="css/firasans.css">
+ <link rel="stylesheet" href="css/signup.css">
+ </head>
+ <body class="pkt_ext_containersignup" aria-live="polite">
+ <script type="text/javascript" src="js/vendor/jquery-2.1.1.min.js"></script>
+ <script type="text/javascript" src="js/vendor/handlebars.runtime.js"></script>
+ <script type="text/javascript" src="js/tmpl.js"></script>
+ <script type="text/javascript" src="js/messages.js"></script>
+ <script type="text/javascript" src="js/signup.js"></script>
+ </body>
+</html>
diff --git a/browser/extensions/pocket/content/panels/tmpl/saved_premiumextras.handlebars b/browser/extensions/pocket/content/panels/tmpl/saved_premiumextras.handlebars
new file mode 100644
index 000000000..b224450ec
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/tmpl/saved_premiumextras.handlebars
@@ -0,0 +1,2 @@
+<div class="pkt_ext_suggestedtag_detailshown">
+</div> \ No newline at end of file
diff --git a/browser/extensions/pocket/content/panels/tmpl/saved_premiumshell.handlebars b/browser/extensions/pocket/content/panels/tmpl/saved_premiumshell.handlebars
new file mode 100644
index 000000000..5ef042636
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/tmpl/saved_premiumshell.handlebars
@@ -0,0 +1,6 @@
+<div class="pkt_ext_suggestedtag_detail pkt_ext_suggestedtag_detail_loading">
+ <h4>{{suggestedtags}}</h4>
+ <div class="pkt_ext_loadingspinner"><div></div></div>
+ <ul class="pkt_ext_cf">
+ </ul>
+</div> \ No newline at end of file
diff --git a/browser/extensions/pocket/content/panels/tmpl/saved_shell.handlebars b/browser/extensions/pocket/content/panels/tmpl/saved_shell.handlebars
new file mode 100644
index 000000000..89b7500cd
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/tmpl/saved_shell.handlebars
@@ -0,0 +1,29 @@
+<div class="pkt_ext_initload">
+ <div class="pkt_ext_logo"></div>
+ <div class="pkt_ext_topdetail">
+ <h2>{{saving}}</h2>
+ </div>
+ <div class="pkt_ext_loadingspinner"><div></div></div>
+</div>
+<div class="pkt_ext_detail">
+ <div class="pkt_ext_logo"></div>
+ <div class="pkt_ext_topdetail">
+ <h2>{{pagesaved}}</h2>
+ <h3 class="pkt_ext_errordetail"></h3>
+ <nav class="pkt_ext_item_actions pkt_ext_cf">
+ <ul>
+ <li><a class="pkt_ext_removeitem" href="#">{{removepage}}</a></li>
+ <li class="pkt_ext_actions_separator"></li>
+ <li><a class="pkt_ext_openpocket" href="https://{{pockethost}}/a?src=ff_ext_saved" target="_blank">{{viewlist}}</a></li>
+ </ul>
+ </nav>
+ </div>
+ <div class="pkt_ext_tag_detail pkt_ext_cf">
+ <div class="pkt_ext_tag_input_wrapper">
+ <div class="pkt_ext_tag_input_blocker"></div>
+ <input class="pkt_ext_tag_input" type="text" placeholder="{{addtags}}">
+ </div>
+ <a href="#" class="pkt_ext_btn pkt_ext_btn_disabled">{{save}}</a>
+ </div>
+ <p class="pkt_ext_edit_msg"></p>
+</div> \ No newline at end of file
diff --git a/browser/extensions/pocket/content/panels/tmpl/signup_shell.handlebars b/browser/extensions/pocket/content/panels/tmpl/signup_shell.handlebars
new file mode 100644
index 000000000..8dddaef60
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/tmpl/signup_shell.handlebars
@@ -0,0 +1,32 @@
+<div class="pkt_ext_introdetail pkt_ext_introdetailhero">
+ <h2 class="pkt_ext_logo">Pocket</h2>
+ <p class="pkt_ext_tagline">{{tagline}}</p>
+ {{#if showlearnmore}}
+ {{#if controlvariant}}
+ <p class="pkt_ext_learnmorecontainer"><a class="pkt_ext_learnmore" href="https://{{pockethost}}/firefox_learnmore?s=ffi&t=learnmore&tv=panel_control&v={{variant}}" target="_blank">{{learnmore}}</a></p>
+ {{else}}
+ <p class="pkt_ext_learnmorecontainer"><a class="pkt_ext_learnmore" href="https://{{pockethost}}/firefox_learnmore?s=ffi&t=learnmore&tv=panel_tryit&v={{variant}}" target="_blank">{{learnmore}}</a></p>
+ {{/if}}
+ {{else}}
+ <p class="pkt_ext_learnmorecontainer"><a class="pkt_ext_learnmore pkt_ext_learnmoreinactive" href="#">{{learnmore}}</a></p>
+ {{/if}}
+ <div class="pkt_ext_introimg"></div>
+</div>
+<div class="pkt_ext_signupdetail pkt_ext_signupdetail_hero">
+ {{#if fxasignedin}}
+ <h4>{{signuptosave}}</h4>
+ <p class="btn-container"><a href="https://{{pockethost}}/ff_signup?s=ffi&t=signupff&v={{variant}}" target="_blank" class="btn signup-btn-firefox"><span class="logo"></span><span class="text">{{signinfirefox}}</span></a></p>
+ <p class="alreadyhave">{{alreadyhaveacct}} <a href="https://{{pockethost}}/login?ep=3&src=extension&s=ffi&t=login&v={{variant}}" target="_blank">{{loginnow}}</a>.</p>
+ {{else}}
+ {{#if controlvariant}}
+ <h4>{{signuptosave}}</h4>
+ <p class="btn-container"><a href="https://{{pockethost}}/ff_signup?s=ffi&tv=panel_control&t=signupff&v={{variant}}" target="_blank" class="btn signup-btn-firefox"><span class="logo"></span><span class="text">{{signupfirefox}}</span></a></p>
+ <p class="btn-container"><a href="https://{{pockethost}}/signup?force=email&tv=panel_control&src=extension&s=ffi&t=signupemail&v={{variant}}" target="_blank" class="btn btn-secondary signup-btn-email signup-btn-initstate">{{signupemail}}</a></p>
+ <p class="alreadyhave">{{alreadyhaveacct}} <a href="https://{{pockethost}}/login?ep=3&tv=panel_control&src=extension&s=ffi&t=login&v={{variant}}" target="_blank">{{loginnow}}</a>.</p>
+ {{else}}
+ <p class="btn-container"><a href="https://{{pockethost}}/firefox_tryitnow?s=ffi&tv=panel_tryit&t=tryitnow" target="_blank" class="btn signup-btn-tryitnow"><span class="text">{{tryitnow}}</span></a></p>
+ <p class="alreadyhave tryitnowspace">{{alreadyhaveacct}} <a href="https://{{pockethost}}/login?ep=3&s=ffi&tv=panel_tryit&src=extension&t=login&v={{variant}}" target="_blank">{{loginnow}}</a>.</p>
+ <p class="pkt_ext_tos">{{{tos}}}</p>
+ {{/if}}
+ {{/if}}
+</div>
diff --git a/browser/extensions/pocket/content/panels/tmpl/signupstoryboard_shell.handlebars b/browser/extensions/pocket/content/panels/tmpl/signupstoryboard_shell.handlebars
new file mode 100644
index 000000000..8a181e23d
--- /dev/null
+++ b/browser/extensions/pocket/content/panels/tmpl/signupstoryboard_shell.handlebars
@@ -0,0 +1,43 @@
+<div class="pkt_ext_introdetail pkt_ext_introdetailstoryboard">
+ <div class="pkt_ext_introstory pkt_ext_introstoryone">
+ <div class="pkt_ext_introstory_text">
+ <p class="pkt_ext_tagline">{{taglinestory_one}}</p>
+ </div>
+ <div class="pkt_ext_introstoryone_img"></div>
+ </div>
+ <div class="pkt_ext_introstorydivider"></div>
+ <div class="pkt_ext_introstory pkt_ext_introstorytwo">
+ <div class="pkt_ext_introstory_text">
+ <p class="pkt_ext_tagline">{{taglinestory_two}}</p>
+ {{#if showlearnmore}}
+ {{#if controlvariant}}
+ <p><a class="pkt_ext_learnmore" href="https://{{pockethost}}/firefox_learnmore?s=ffi&t=learnmore&tv=panel_control&v={{variant}}" target="_blank">{{learnmore}}</a></p>
+ {{else}}
+ <p><a class="pkt_ext_learnmore" href="https://{{pockethost}}/firefox_learnmore?s=ffi&t=learnmore&tv=panel_tryit&v={{variant}}" target="_blank">{{learnmore}}</a></p>
+ {{/if}}
+ {{else}}
+ <p><a class="pkt_ext_learnmore pkt_ext_learnmoreinactive" href="#">{{learnmore}}</a></p>
+ {{/if}}
+ </div>
+ <div class="pkt_ext_introstorytwo_img"></div>
+ </div>
+</div>
+<div class="pkt_ext_signupdetail">
+ {{#if fxasignedin}}
+ <h4>{{signuptosave}}</h4>
+ <p class="btn-container"><a href="https://{{pockethost}}/ff_signup?s=ffi&t=signupff&v={{variant}}" target="_blank" class="btn signup-btn-firefox"><span class="logo"></span><span class="text">{{signinfirefox}}</span></a></p>
+ <p class="alreadyhave">{{alreadyhaveacct}} <a href="https://{{pockethost}}/login?ep=3&src=extension&s=ffi&t=login&v={{variant}}" target="_blank">{{loginnow}}</a>.</p>
+ {{else}}
+ {{#if controlvariant}}
+ <h4>{{signuptosave}}</h4>
+ <p class="btn-container"><a href="https://{{pockethost}}/ff_signup?s=ffi&tv=panel_control&t=signupff&v={{variant}}" target="_blank" class="btn signup-btn-firefox"><span class="logo"></span><span class="text">{{signupfirefox}}</span></a></p>
+ <p class="btn-container"><a href="https://{{pockethost}}/signup?force=email&tv=panel_control&src=extension&s=ffi&t=signupemail&v={{variant}}" target="_blank" class="btn btn-secondary signup-btn-email signup-btn-initstate">{{signupemail}}</a></p>
+ <p class="alreadyhave">{{alreadyhaveacct}} <a href="https://{{pockethost}}/login?ep=3&tv=panel_control&src=extension&s=ffi&t=login&v={{variant}}" target="_blank">{{loginnow}}</a>.</p>
+ {{else}}
+ <p class="btn-container"><a href="https://{{pockethost}}/firefox_tryitnow?s=ffi&tv=panel_tryit&t=tryitnow" target="_blank" class="btn signup-btn-tryitnow"><span class="text">{{tryitnow}}</span></a></p>
+ <p class="alreadyhave tryitnowspace">{{alreadyhaveacct}} <a href="https://{{pockethost}}/login?ep=3&s=ffi&tv=panel_tryit&src=extension&t=login&v={{variant}}" target="_blank">{{loginnow}}</a>.</p>
+ <p class="pkt_ext_tos">{{{tos}}}</p>
+ {{/if}}
+ {{/if}}
+
+</div>
diff --git a/browser/extensions/pocket/content/pktApi.jsm b/browser/extensions/pocket/content/pktApi.jsm
new file mode 100644
index 000000000..63b6d415c
--- /dev/null
+++ b/browser/extensions/pocket/content/pktApi.jsm
@@ -0,0 +1,657 @@
+/*
+ * LICENSE
+ *
+ * POCKET MARKS
+ *
+ * Notwithstanding the permitted uses of the Software (as defined below) pursuant to the license set forth below, "Pocket," "Read It Later" and the Pocket icon and logos (collectively, the “Pocket Marks”) are registered and common law trademarks of Read It Later, Inc. This means that, while you have considerable freedom to redistribute and modify the Software, there are tight restrictions on your ability to use the Pocket Marks. This license does not grant you any rights to use the Pocket Marks except as they are embodied in the Software.
+ *
+ * ---
+ *
+ * SOFTWARE
+ *
+ * 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.
+ */
+
+/*
+ * Pocket API module
+ *
+ * Public API Documentation: http://getpocket.com/developer/
+ *
+ *
+ * Definition of keys stored in preferences to preserve user state:
+ * premium_status: Current premium status for logged in user if available
+ * Can be 0 for no premium and 1 for premium
+ * latestSince: Last timestamp a save happened
+ * tags: All tags for logged in user
+ * usedTags: All used tags from within the extension sorted by recency
+ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm} = Components;
+this.EXPORTED_SYMBOLS = ["pktApi"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+
+var pktApi = (function() {
+
+ /**
+ * Configuration
+ */
+
+ // Base url for all api calls
+ var pocketAPIhost = Services.prefs.getCharPref("extensions.pocket.api"); // api.getpocket.com
+ var pocketSiteHost = Services.prefs.getCharPref("extensions.pocket.site"); // getpocket.com
+ var baseAPIUrl = "https://" + pocketAPIhost + "/v3";
+
+
+ /**
+ * Auth keys for the API requests
+ */
+ var oAuthConsumerKey = Services.prefs.getCharPref("extensions.pocket.oAuthConsumerKey");
+
+ /**
+ *
+ */
+ var prefBranch = Services.prefs.getBranch("extensions.pocket.settings.");
+
+ /**
+ * Helper
+ */
+
+ var extend = function(out) {
+ out = out || {};
+
+ for (var i = 1; i < arguments.length; i++) {
+ if (!arguments[i])
+ continue;
+
+ for (var key in arguments[i]) {
+ if (arguments[i].hasOwnProperty(key))
+ out[key] = arguments[i][key];
+ }
+ }
+ return out;
+ }
+
+ var parseJSON = function(jsonString) {
+ try {
+ var o = JSON.parse(jsonString);
+
+ // Handle non-exception-throwing cases:
+ // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking,
+ // but... JSON.parse(null) returns 'null', and typeof null === "object",
+ // so we must check for that, too.
+ if (o && typeof o === "object" && o !== null) {
+ return o;
+ }
+ }
+ catch (e) { }
+
+ return undefined;
+ };
+
+ /**
+ * Settings
+ */
+
+ /**
+ * Wrapper for different plattforms to get settings for a given key
+ * @param {string} key A string containing the name of the key you want to
+ * retrieve the value of
+ * @return {string} String containing the value of the key. If the key
+ * does not exist, null is returned
+ */
+ function getSetting(key) {
+ // TODO : Move this to sqlite or a local file so it's not editable (and is safer)
+ // https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage
+
+ if (!prefBranch.prefHasUserValue(key))
+ return undefined;
+
+ return prefBranch.getComplexValue(key, Components.interfaces.nsISupportsString).data;
+ }
+
+ /**
+ * Wrapper for different plattforms to set a value for a given key in settings
+ * @param {string} key A string containing the name of the key you want
+ * to create/update.
+ * @param {string} value String containing the value you want to give
+ * the key you are creating/updating.
+ */
+ function setSetting(key, value) {
+ // TODO : Move this to sqlite or a local file so it's not editable (and is safer)
+ // https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage
+
+ if (!value)
+ prefBranch.clearUserPref(key);
+ else
+ {
+ // We use complexValue as tags can have utf-8 characters in them
+ var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
+ str.data = value;
+ prefBranch.setComplexValue(key, Components.interfaces.nsISupportsString, str);
+ }
+ }
+
+ /**
+ * Auth
+ */
+
+ /*
+ * All cookies from the Pocket domain
+ * The return format: { cookieName:cookieValue, cookieName:cookieValue, ... }
+ */
+ function getCookiesFromPocket() {
+
+ var cookieManager = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+ var pocketCookies = cookieManager.getCookiesFromHost(pocketSiteHost, {});
+ var cookies = {};
+ while (pocketCookies.hasMoreElements()) {
+ var cookie = pocketCookies.getNext().QueryInterface(Ci.nsICookie2);
+ cookies[cookie.name] = cookie.value;
+ }
+ return cookies;
+ }
+
+ /**
+ * Returns access token or undefined if no logged in user was found
+ * @return {string | undefined} Access token for logged in user user
+ */
+ function getAccessToken() {
+ var pocketCookies = getCookiesFromPocket();
+
+ // If no cookie was found just return undefined
+ if (typeof pocketCookies['ftv1'] === "undefined") {
+ return undefined;
+ }
+
+ // Check if a new user logged in in the meantime and clearUserData if so
+ var sessionId = pocketCookies['fsv1'];
+ var lastSessionId = getSetting('fsv1');
+ if (sessionId !== lastSessionId) {
+ clearUserData();
+ setSetting("fsv1", sessionId);
+ }
+
+ // Return access token
+ return pocketCookies['ftv1'];
+ }
+
+ /**
+ * Get the current premium status of the user
+ * @return {number | undefined} Premium status of user
+ */
+ function getPremiumStatus() {
+ var premiumStatus = getSetting("premium_status");
+ if (typeof premiumStatus === "undefined") {
+ // Premium status is not in settings try get it from cookie
+ var pocketCookies = getCookiesFromPocket();
+ premiumStatus = pocketCookies['ps'];
+ }
+ return premiumStatus;
+ }
+
+ /**
+ * Helper method to check if a user is premium or not
+ * @return {Boolean} Boolean if user is premium or not
+ */
+ function isPremiumUser() {
+ return getPremiumStatus() == 1;
+ }
+
+
+ /**
+ * Returns users logged in status
+ * @return {Boolean} Users logged in status
+ */
+ function isUserLoggedIn() {
+ return (typeof getAccessToken() !== "undefined");
+ }
+
+ /**
+ * API
+ */
+
+ /**
+ * Helper function for executing api requests. It mainly configures the
+ * ajax call with default values like type, headers or dataType for an api call.
+ * This function is for internal usage only.
+ * @param {Object} options
+ * Possible keys:
+ * - {string} path: This should be the Pocket API
+ * endpoint to call. For example providing the path
+ * "/get" would result in a call to getpocket.com/v3/get
+ * - {Object|undefined} data: Gets passed on to the jQuery ajax
+ * call as data parameter
+ * - {function(Object data, XMLHttpRequest xhr) | undefined} success:
+ * A function to be called if the request succeeds.
+ * - {function(Error errorThrown, XMLHttpRequest xhr) | undefined} error:
+ * A function to be called if the request fails.
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ *
+ */
+ function apiRequest(options) {
+ if ((typeof options === "undefined") || (typeof options.path === "undefined")) {
+ return false;
+ }
+
+ var url = baseAPIUrl + options.path;
+ var data = options.data || {};
+ data.locale_lang = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry).
+ getSelectedLocale("browser");
+ data.consumer_key = oAuthConsumerKey;
+
+ var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
+ request.open("POST", url, true);
+ request.onreadystatechange = function(e) {
+ if (request.readyState == 4) {
+ if (request.status === 200) {
+ // There could still be an error if the response is no valid json
+ // or does not have status = 1
+ var response = parseJSON(request.response);
+ if (options.success && response && response.status == 1) {
+ options.success(response, request);
+ return;
+ }
+ }
+
+ // Handle error case
+ if (options.error) {
+ // In case the user did revoke the access token or it's not
+ // valid anymore clear the user data
+ if (request.status === 401) {
+ clearUserData();
+ }
+
+ // Handle error message
+ var errorMessage;
+ if (request.status !== 200) {
+ errorMessage = request.getResponseHeader("X-Error") || request.statusText;
+ errorMessage = JSON.parse('"' + errorMessage + '"');
+ }
+ var error = {message: errorMessage};
+ options.error(error, request);
+ }
+ }
+ };
+
+ // Set headers
+ request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
+ request.setRequestHeader('X-Accept', ' application/json');
+
+ // Serialize and Fire off the request
+ var str = [];
+ for (var p in data) {
+ if (data.hasOwnProperty(p)) {
+ str.push(encodeURIComponent(p) + "=" + encodeURIComponent(data[p]));
+ }
+ }
+
+ request.send(str.join("&"));
+
+ return true;
+ }
+
+ /**
+ * Cleans all settings for the previously logged in user
+ */
+ function clearUserData() {
+ // Clear stored information
+ setSetting("premium_status", undefined);
+ setSetting("latestSince", undefined);
+ setSetting("tags", undefined);
+ setSetting("usedTags", undefined);
+
+ setSetting("fsv1", undefined);
+ }
+
+ /**
+ * Add a new link to Pocket
+ * @param {string} url URL of the link
+ * @param {Object | undefined} options Can provide a string-based title, a
+ * `success` callback and an `error` callback.
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function addLink(url, options) {
+
+ var since = getSetting('latestSince');
+ var accessToken = getAccessToken();
+
+ var sendData = {
+ access_token: accessToken,
+ url: url,
+ since: since ? since : 0
+ };
+
+ if (options.title) {
+ sendData.title = options.title;
+ }
+
+ return apiRequest({
+ path: "/firefox/save",
+ data: sendData,
+ success: function(data) {
+
+ // Update premium status, tags and since
+ var tags = data.tags;
+ if ((typeof tags !== "undefined") && Array.isArray(tags)) {
+ // If a tagslist is in the response replace the tags
+ setSetting('tags', JSON.stringify(data.tags));
+ }
+
+ // Update premium status
+ var premiumStatus = data.premium_status;
+ if (typeof premiumStatus !== "undefined") {
+ // If a premium_status is in the response replace the premium_status
+ setSetting("premium_status", premiumStatus);
+ }
+
+ // Save since value for further requests
+ setSetting('latestSince', data.since);
+
+ if (options.success) {
+ options.success.apply(options, Array.apply(null, arguments));
+ }
+ },
+ error: options.error
+ });
+ }
+
+ /**
+ * Delete an item identified by item id from the users list
+ * @param {string} itemId The id from the item we want to remove
+ * @param {Object | undefined} options Can provide an actionInfo object with
+ * further data to send to the API. Can
+ * have success and error callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function deleteItem(itemId, options) {
+ var action = {
+ action: "delete",
+ item_id: itemId
+ };
+ return sendAction(action, options);
+ }
+
+ /**
+ * General function to send all kinds of actions like adding of links or
+ * removing of items via the API
+ * @param {Object} action Action object
+ * @param {Object | undefined} options Can provide an actionInfo object
+ * with further data to send to the
+ * API. Can have success and error
+ * callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function sendAction(action, options) {
+ // Options can have an 'actionInfo' object. This actionInfo object gets
+ // passed through to the action object that will be send to the API endpoint
+ if (typeof options.actionInfo !== 'undefined') {
+ action = extend(action, options.actionInfo);
+ }
+ return sendActions([action], options);
+ }
+
+ /**
+ * General function to send all kinds of actions like adding of links or
+ * removing of items via the API
+ * @param {Array} actions Array of action objects
+ * @param {Object | undefined} options Can have success and error callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function sendActions(actions, options) {
+ return apiRequest({
+ path: "/send",
+ data: {
+ access_token: getAccessToken(),
+ actions: JSON.stringify(actions)
+ },
+ success: options.success,
+ error: options.error
+ });
+ }
+
+ /**
+ * Handling Tags
+ */
+
+ /**
+ * Add tags to the item identified by the url. Also updates the used tags
+ * list
+ * @param {string} itemId The item identifier by item id
+ * @param {Array} tags Tags adding to the item
+ * @param {Object | undefined} options Can provide an actionInfo object with
+ * further data to send to the API. Can
+ * have success and error callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function addTagsToItem(itemId, tags, options) {
+ return addTags({item_id: itemId}, tags, options);
+ }
+
+ /**
+ * Add tags to the item identified by the url. Also updates the used tags
+ * list
+ * @param {string} url The item identifier by url
+ * @param {Array} tags Tags adding to the item
+ * @param {Object} options Can provide an actionInfo object with further
+ * data to send to the API. Can have success and error
+ * callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function addTagsToURL(url, tags, options) {
+ return addTags({url: url}, tags, options);
+ }
+
+ /**
+ * Helper function to execute the add tags api call. Will be used from addTagsToURL
+ * and addTagsToItem but not exposed outside
+ * @param {string} actionPart Specific action part to add to action
+ * @param {Array} tags Tags adding to the item
+ * @param {Object | undefined} options Can provide an actionInfo object with
+ * further data to send to the API. Can
+ * have success and error callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function addTags(actionPart, tags, options) {
+ // Tags add action
+ var action = {
+ action: "tags_add",
+ tags: tags
+ };
+ action = extend(action, actionPart);
+
+ // Backup the success callback as we need it later
+ var finalSuccessCallback = options.success;
+
+ // Switch the success callback
+ options.success = function(data) {
+
+ // Update used tags
+ var usedTagsJSON = getSetting("usedTags");
+ var usedTags = usedTagsJSON ? JSON.parse(usedTagsJSON) : {};
+
+ // Check for each tag if it's already in the used tags
+ for (var i = 0; i < tags.length; i++) {
+ var tagToSave = tags[i].trim();
+ var newUsedTagObject = {
+ "tag": tagToSave,
+ "timestamp": new Date().getTime()
+ };
+ usedTags[tagToSave] = newUsedTagObject;
+ }
+ setSetting("usedTags", JSON.stringify(usedTags));
+
+ // Let the callback know that we are finished
+ if (finalSuccessCallback) {
+ finalSuccessCallback(data);
+ }
+ };
+
+ // Execute the action
+ return sendAction(action, options);
+ }
+
+ /**
+ * Get all cached tags and used tags within the callback
+ * @param {function(Array, Array, Boolean)} callback
+ * Function with tags and used tags as parameter.
+ */
+ function getTags(callback) {
+
+ var tagsFromSettings = function() {
+ var tagsJSON = getSetting("tags");
+ if (typeof tagsJSON !== "undefined") {
+ return JSON.parse(tagsJSON)
+ }
+ return [];
+ }
+
+ var sortedUsedTagsFromSettings = function() {
+ // Get and Sort used tags
+ var usedTags = [];
+
+ var usedTagsJSON = getSetting("usedTags");
+ if (typeof usedTagsJSON !== "undefined") {
+ var usedTagsObject = JSON.parse(usedTagsJSON);
+ var usedTagsObjectArray = [];
+ for (var tagKey in usedTagsObject) {
+ usedTagsObjectArray.push(usedTagsObject[tagKey]);
+ }
+
+ // Sort usedTagsObjectArray based on timestamp
+ usedTagsObjectArray.sort(function(usedTagA, usedTagB) {
+ var a = usedTagA.timestamp;
+ var b = usedTagB.timestamp;
+ return a - b;
+ });
+
+ // Get all keys tags
+ for (var j = 0; j < usedTagsObjectArray.length; j++) {
+ usedTags.push(usedTagsObjectArray[j].tag);
+ }
+
+ // Reverse to set the last recent used tags to the front
+ usedTags.reverse();
+ }
+
+ return usedTags;
+ }
+
+ if (callback) {
+ var tags = tagsFromSettings();
+ var usedTags = sortedUsedTagsFromSettings();
+ callback(tags, usedTags);
+ }
+ }
+
+ /**
+ * Fetch suggested tags for a given item id
+ * @param {string} itemId Item id of
+ * @param {Object | undefined} options Can provide an actionInfo object
+ * with further data to send to the API.
+ * Can have success and error callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function getSuggestedTagsForItem(itemId, options) {
+ return getSuggestedTags({item_id: itemId}, options);
+ }
+
+ /**
+ * Fetch suggested tags for a given URL
+ * @param {string} url (required) The item identifier by url
+ * @param {Object} options Can provide an actionInfo object with further
+ * data to send to the API. Can have success and error
+ * callbacks
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function getSuggestedTagsForURL(url, options) {
+ return getSuggestedTags({url: url}, options);
+ }
+
+ /**
+ * Helper function to get suggested tags
+ * @return {Boolean} Returns Boolean whether the api call started sucessfully
+ */
+ function getSuggestedTags(data, options) {
+
+ data = data || {};
+ options = options || {};
+
+ data.access_token = getAccessToken();
+
+ return apiRequest({
+ path: "/getSuggestedTags",
+ data: data,
+ success: options.success,
+ error: options.error
+ });
+ }
+
+ /**
+ * Helper function to get current signup AB group the user is in
+ */
+ function getSignupPanelTabTestVariant() {
+ return getMultipleTestOption('panelSignUp', {control: 1, v1: 8, v2: 1 })
+ }
+
+ function getMultipleTestOption(testName, testOptions) {
+ // Get the test from preferences if we've already assigned the user to a test
+ var settingName = 'test.' + testName;
+ var assignedValue = getSetting(settingName);
+ var valArray = [];
+
+ // If not assigned yet, pick and store a value
+ if (!assignedValue)
+ {
+ // Get a weighted array of test variants from the testOptions object
+ Object.keys(testOptions).forEach(function(key) {
+ for (var i = 0; i < testOptions[key]; i++) {
+ valArray.push(key);
+ }
+ });
+
+ // Get a random test variant and set the user to it
+ assignedValue = valArray[Math.floor(Math.random() * valArray.length)];
+ setSetting(settingName, assignedValue);
+ }
+
+ return assignedValue;
+
+ }
+
+ /**
+ * Public functions
+ */
+ return {
+ isUserLoggedIn : isUserLoggedIn,
+ clearUserData: clearUserData,
+ addLink: addLink,
+ deleteItem: deleteItem,
+ addTagsToItem: addTagsToItem,
+ addTagsToURL: addTagsToURL,
+ getTags: getTags,
+ isPremiumUser: isPremiumUser,
+ getSuggestedTagsForItem: getSuggestedTagsForItem,
+ getSuggestedTagsForURL: getSuggestedTagsForURL,
+ getSignupPanelTabTestVariant: getSignupPanelTabTestVariant,
+ };
+}());
diff --git a/browser/extensions/pocket/content/pocket-content-process.js b/browser/extensions/pocket/content/pocket-content-process.js
new file mode 100644
index 000000000..9b158a0ed
--- /dev/null
+++ b/browser/extensions/pocket/content/pocket-content-process.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";
+
+// This file is loaded as a process script, it will be loaded in the parent
+// process as well as all content processes.
+
+const { utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("chrome://pocket/content/AboutPocket.jsm");
+
+function AboutPocketChildListener() {
+}
+AboutPocketChildListener.prototype = {
+ onStartup: function onStartup() {
+
+ // Only do this in content processes since, as the broadcaster of this
+ // message, the parent process doesn't also receive it. We handlers
+ // the shutting down separately.
+ if (Services.appinfo.processType ==
+ Services.appinfo.PROCESS_TYPE_CONTENT) {
+
+ Services.cpmm.addMessageListener("PocketShuttingDown", this, true);
+ }
+
+ AboutPocket.aboutSaved.register();
+ AboutPocket.aboutSignup.register();
+ },
+
+ onShutdown: function onShutdown() {
+ AboutPocket.aboutSignup.unregister();
+ AboutPocket.aboutSaved.unregister();
+
+ Services.cpmm.removeMessageListener("PocketShuttingDown", this);
+ Cu.unload("chrome://pocket/content/AboutPocket.jsm");
+ },
+
+ receiveMessage: function receiveMessage(message) {
+ switch (message.name) {
+ case "PocketShuttingDown":
+ this.onShutdown();
+ break;
+ default:
+ break;
+ }
+
+ return;
+ }
+};
+
+const listener = new AboutPocketChildListener();
+listener.onStartup();
diff --git a/browser/extensions/pocket/install.rdf.in b/browser/extensions/pocket/install.rdf.in
new file mode 100644
index 000000000..babccfa5a
--- /dev/null
+++ b/browser/extensions/pocket/install.rdf.in
@@ -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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>firefox@getpocket.com</em:id>
+ <em:version>1.0.5</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Target Application this theme can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Pocket</em:name>
+ <em:description>When you find something you want to view later, put it in Pocket.</em:description>
+ </Description>
+</RDF>
diff --git a/browser/extensions/pocket/jar.mn b/browser/extensions/pocket/jar.mn
new file mode 100644
index 000000000..5fab627fe
--- /dev/null
+++ b/browser/extensions/pocket/jar.mn
@@ -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/.
+
+[features/firefox@getpocket.com] chrome.jar:
+% content pocket %content/ contentaccessible=yes
+% skin pocket classic/1.0 %skin/linux/
+% skin pocket classic/1.0 %skin/osx/ os=Darwin
+% skin pocket classic/1.0 %skin/windows/ os=WINNT
+% skin pocket-shared classic/1.0 %skin/shared/
+ content/ (content/*)
+ skin/ (skin/*)
+# windows overrides
+% override chrome://pocket/skin/menuPanel.png chrome://pocket/skin/menuPanel-aero.png os=WINNT osversion=6
+% override chrome://pocket/skin/menuPanel.png chrome://pocket/skin/menuPanel-aero.png os=WINNT osversion=6.1
+% override chrome://pocket/skin/menuPanel@2x.png chrome://pocket/skin/menuPanel-aero@2x.png os=WINNT osversion=6
+% override chrome://pocket/skin/menuPanel@2x.png chrome://pocket/skin/menuPanel-aero@2x.png os=WINNT osversion=6.1
+% override chrome://pocket/skin/Toolbar@2x.png chrome://pocket/skin/Toolbar-aero@2x.png os=WINNT osversion=6
+% override chrome://pocket/skin/Toolbar@2x.png chrome://pocket/skin/Toolbar-aero@2x.png os=WINNT osversion=6.1
+% override chrome://pocket/skin/Toolbar@2x.png chrome://pocket/skin/Toolbar-win8@2x.png os=WINNT osversion=6.2
+% override chrome://pocket/skin/Toolbar@2x.png chrome://pocket/skin/Toolbar-win8@2x.png os=WINNT osversion=6.3
+% override chrome://pocket/skin/Toolbar.png chrome://pocket/skin/Toolbar-XP.png os=WINNT osversion<6
+% override chrome://pocket/skin/Toolbar.png chrome://pocket/skin/Toolbar-aero.png os=WINNT osversion=6
+% override chrome://pocket/skin/Toolbar.png chrome://pocket/skin/Toolbar-aero.png os=WINNT osversion=6.1
+% override chrome://pocket/skin/Toolbar.png chrome://pocket/skin/Toolbar-win8.png os=WINNT osversion=6.2
+% override chrome://pocket/skin/Toolbar.png chrome://pocket/skin/Toolbar-win8.png os=WINNT osversion=6.3
+# osx overrides
+% override chrome://pocket/skin/Toolbar.png chrome://pocket/skin/Toolbar-yosemite.png os=Darwin osversion>=10.10
+% override chrome://pocket/skin/Toolbar@2x.png chrome://pocket/skin/Toolbar-yosemite@2x.png os=Darwin osversion>=10.10
+% override chrome://pocket/skin/menuPanel.png chrome://pocket/skin/menuPanel-yosemite.png os=Darwin osversion>=10.10
+% override chrome://pocket/skin/menuPanel@2x.png chrome://pocket/skin/menuPanel-yosemite@2x.png os=Darwin osversion>=10.10
+
diff --git a/browser/extensions/pocket/locale/ast/pocket.properties b/browser/extensions/pocket/locale/ast/pocket.properties
new file mode 100644
index 000000000..c32e53b54
--- /dev/null
+++ b/browser/extensions/pocket/locale/ast/pocket.properties
@@ -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/.
+
+addtags = Amestar etiquetes
+alreadyhaveacct = ¿Yá yes un usuariu de Pocket?
+continueff = Siguir con Firefox
+errorgeneric = Hebo un fallu tentando de guardar en Pocket.
+learnmore = Depriendi más
+loginnow = Aniciar sesión
+maxtaglength = Les etiquetes lléndense a 25 caráuteres
+mustbeconnected = Has tar coneutáu a internet pa guardar en Pocket. Comprueba la to conexón y volvi tentalo, por favor.
+onlylinkssaved = Namái puen guardase enllaces
+pagenotsaved = Páxina non guardada
+pageremoved = Páxina desaniciada
+pagesaved = Guardóse en Pocket
+processingremove = Desaniciando páxina…
+processingtags = Amestando etiquetes…
+removepage = Desanicia páxina
+save = Guardar
+saving = Guardando…
+signupemail = Rexistrase con corréu
+signuptosave = Rexístrate en Pocket. Ye de baldre.
+suggestedtags = Etiquetes suxeríes
+tagline = Guardar artículos y vídeos dende Firefox pa ver en Pocket o en cualquier preséu, en cualquier momentu.
+taglinestory_one = Fai clic nel botón de Pocket pa guardar cualquier artículu, videu o páxina dende Firefox.
+taglinestory_two = Ver en Pocker o en cualquier preséu, en cualquier momentu.
+tagssaved = Etiquetes amestaes
+tos = Sigiuiendo, tas acordies colos <a href="%1$S" target="_blank">Términos de Serviciu</a> y la <a href="%2$S" target="_blank">Política de privacidá</a> de Pocket
+tryitnow = Pruébalu agora
+signinfirefox = Anicia sesión con Firefox
+signupfirefox = Rexístrate con Firefox
+viewlist = Ver llista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Guardar en Pocket
+saveToPocketCmd.label = Guardar páxina en Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Guardar enllaz en Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Ver la llista de Pocket
diff --git a/browser/extensions/pocket/locale/az/pocket.properties b/browser/extensions/pocket/locale/az/pocket.properties
new file mode 100644
index 000000000..a228ca026
--- /dev/null
+++ b/browser/extensions/pocket/locale/az/pocket.properties
@@ -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/.
+
+addtags = Etiket əlavə et
+alreadyhaveacct = Artıq Pocket istifadəçisisiniz?
+continueff = Firefox ilə davam et
+errorgeneric = Pocket-ə saxlarkən xəta baş verdi.
+learnmore = Ətraflı Öyrən
+loginnow = Daxil ol
+maxtaglength = Etiketlər 25 simvol ilə limitlidir
+mustbeconnected = Pocket-ə saxlamaq üçün internetə qoşulu olmalısınız. Lütfən internetə qoşulu olduğunuza əmin olub təkrar yoxlayın.
+onlylinkssaved = Ancaq keçidlər saxlana bilər
+pagenotsaved = Səhifə saxlanmadı
+pageremoved = Səhifə silindi
+pagesaved = Pocket-ə saxlandı
+processingremove = Səhifə silinir…
+processingtags = Etiketlər əlavə edilir…
+removepage = Səhifəni sil
+save = Saxla
+saving = Saxlanır…
+signupemail = E-poçt ilə qeyd ol
+signuptosave = Pocket üçün qeyd ol. Bu pulsuzdur.
+suggestedtags = Məsləhərli etiketlər
+tagline = Firefoxdan məqalə və videoları Pocket-ə saxlayın, istədiyiniz vaxt, istədiyiniz yerdə baxın.
+taglinestory_one = Firefoxda hər hansı bir məqalə, video və ya səhifəni saxlamaq üçün Pocket Düyməsinə klikləyin.
+taglinestory_two = İstənilən cihazda, istənilən vaxt Pocket-də görün.
+tagssaved = Etiketlər əlavə edildi
+tos = Davam etməklə, Pocket-in <a href="%1$S" target="_blank">İstifadə Şərtləri</a> və <a href="%2$S" target="_blank">Məxfilik Siyasəti</a> ilə razılaşmış olursunuz
+tryitnow = İndi Yoxlayın
+signinfirefox = Firefox ilə daxil ol
+signupfirefox = Firefox ilə qeyd ol
+viewlist = Siyahını gör
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket-ə Saxla
+saveToPocketCmd.label = Səhifəni Pocket-ə Saxla
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Keçidi Pocket-ə Saxla
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket Siyahısını Gör
diff --git a/browser/extensions/pocket/locale/bg/pocket.properties b/browser/extensions/pocket/locale/bg/pocket.properties
new file mode 100644
index 000000000..ba86dd7f6
--- /dev/null
+++ b/browser/extensions/pocket/locale/bg/pocket.properties
@@ -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/.
+
+addtags = Добавяне на етикети
+alreadyhaveacct = Вече сте потребител на Pocket?
+continueff = Продължаване с Firefox
+errorgeneric = Получи се грешка при опит за запис в Pocket.
+learnmore = Научете повече
+loginnow = Вписване
+maxtaglength = Етикетите могат да са до 25 знака
+mustbeconnected = Трябва да сте свързан към Интернет, за да запазвате в Pocket. Моля, проверете свързаността си с Интернет и пробвайте отново.
+onlylinkssaved = Могат да бъдат запазвани само връзки
+pagenotsaved = Страницата не е запазена
+pageremoved = Страницата е премахната
+pagesaved = Запазена в Pocket
+processingremove = Премахване на страница…
+processingtags = Добавяне на етикети…
+removepage = Премахване на страница
+save = Запазване
+saving = Запазване…
+signupemail = Регистриране с мейл
+signuptosave = Регистрирайте се в Pocket. Безплатно е.
+suggestedtags = Предложени етикети
+tagline = Запазвайте статии и видеота от Firefox и можете да ги преглеждате в Pocket на всяко устройство по всяко време.
+taglinestory_one = Щракнете на бутона на Pocket за запазване на статия, видео или страница от Firefox.
+taglinestory_two = Преглеждайте в Pocket на всяко устройство и по всяко време.
+tagssaved = Етикетите са добавени
+tos = Продължавайки, вие се съгласявате с <a href="%1$S" target="_blank">Условията за ползване</a> и <a href="%2$S" target="_blank">Политиката за поверителност</a> на Pocket
+tryitnow = Опитайте сега
+signinfirefox = Вписване с Firefox
+signupfirefox = Регистриране с Firefox
+viewlist = Преглед на списъка
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Запазване в Pocket
+saveToPocketCmd.label = Запазване на страницата в Pocket
+saveToPocketCmd.accesskey = с
+saveLinkToPocketCmd.label = Запазване на връзката в Pocket
+saveLinkToPocketCmd.accesskey = в
+pocketMenuitem.label = Преглед списъка на Pocket
diff --git a/browser/extensions/pocket/locale/bn-BD/pocket.properties b/browser/extensions/pocket/locale/bn-BD/pocket.properties
new file mode 100644
index 000000000..7a43edad1
--- /dev/null
+++ b/browser/extensions/pocket/locale/bn-BD/pocket.properties
@@ -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/.
+
+addtags = ট্যাগ যোগ করুন
+alreadyhaveacct = আপনি Pocket ব্যবহার করছেন?
+continueff = Firefox ব্যবহার চালিয়ে যান
+errorgeneric = Pocket এ সংরক্ষণ করতে ত্রুটি ঘটেছে।
+learnmore = আরও জানুন
+loginnow = লগ ইন
+maxtaglength = ট্যাগ ২৫ অক্ষরের মধ্যে সীমাবদ্ধ
+mustbeconnected = Pocket এ কোন কিছু সংরক্ষণ করে রাখতে চাইলে, ইন্টারনেটে সংযুক্ত থাকতে হবে। ইন্টারনেট সংযোগ পরীক্ষা করুন এবং আবার চেষ্টা করুন।
+onlylinkssaved = শুধু লিঙ্ক সংরক্ষণ করা যাবে
+pagenotsaved = পাতা সংরক্ষণ করা হয়নি
+pageremoved = পাতা অপসারণ করা হয়েছে
+pagesaved = Pocket এ সংরক্ষিত হয়েছে
+processingremove = পাতা অপসারিত হচ্ছে…
+processingtags = ট্যাগ যুক্ত করা হচ্ছে…
+removepage = পেজ মুছে ফেলুন
+save = সংরক্ষণ
+saving = সংরক্ষণ করা হচ্ছে...
+signupemail = ইমেইল দিয়ে সাইন আপ করুন
+signuptosave = Pocket সাইন আপ করুন। এটি মুফত।
+suggestedtags = প্রস্তাবিত ট্যাগ
+tagline = Pocket এর মাধ্যমে যেকোন সময়, যেকোন ডিভাইসে নিবন্ধ এবং ভিডিও দেখতে Firefox থেকে সেগুলো সংরক্ষণ করুন।
+taglinestory_one = Firefox থেকে আর্টিকেল, ভিডিও বা পৃষ্ঠা সংরক্ষণ করার জন্য Pocket বাটন ক্লিক করুন।
+taglinestory_two = যেকোন সময়ে, যেকোন স্থানে Pocket এ দেখুন।
+tagssaved = ট্যাগ যোগ করা হয়েছে
+tos = এটি অব্যহত রেখে, আপনি Pocket এর <a href="%1$S" target="_blank">সেবার শর্তাবলী</a> এবং <a href="%2$S" target="_blank">গোপনীয়তা নীতিমালায়</a> সম্মত হবেন।
+tryitnow = এখনই ব্যবহার করুন
+signinfirefox = ফায়ারফক্স দিয়ে সাইন ইন করুন
+signupfirefox = ফায়ারফক্স দিয়ে সাইন আপ করুন
+viewlist = তালিকা দেখুন
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket এ সংরক্ষণ করুন
+saveToPocketCmd.label = Pocket এ পাতাটি সংরক্ষণ করুন k
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Pocket এ লিঙ্কটি সংরক্ষণ করুন o
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket তালিকা দেখুন
diff --git a/browser/extensions/pocket/locale/cs/pocket.properties b/browser/extensions/pocket/locale/cs/pocket.properties
new file mode 100644
index 000000000..619b0a01a
--- /dev/null
+++ b/browser/extensions/pocket/locale/cs/pocket.properties
@@ -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/.
+
+addtags = Přidat štítky
+alreadyhaveacct = Jste již uživatel služby Pocket?
+continueff = Pokračovat pomocí Firefoxu
+errorgeneric = Při pokusu o uložení do služby Pocket došlo k chybě.
+learnmore = Zjistit více
+loginnow = Přihlásit se
+maxtaglength = Štítky jsou omezeny na 25 znaků
+mustbeconnected = Abyste mohli ukládat do služby Pocket, musíte být připojeni k internetu. Zkontrolujte prosím své připojení a zkuste to znovu.
+onlylinkssaved = Pouze odkazy mohou být uloženy
+pagenotsaved = Stránka nebyla uložena
+pageremoved = Stránka byla odstraněna
+pagesaved = Uloženo do služby Pocket
+processingremove = Odstraňování stránky…
+processingtags = Přidávání štítků…
+removepage = Odstranit stránku
+save = Uložit
+saving = Ukládání…
+signupemail = Registrace e-mailem
+signuptosave = Registrujte se do služby Pocket. Je to zdarma.
+suggestedtags = Doporučené štítky
+tagline = Ukládejte si články a videa z Firefoxu pro zobrazení ve službě Pocket kdykoliv a na jakémkoli zařízení.
+taglinestory_one = Klepněte na tlačítko služby Pocket pro uložení jakéhokoliv článku, videa nebo stránky přímo z Firefoxu.
+taglinestory_two = Zobrazení ve službě Pocket kdykoliv a na jakémkoliv zařízení.
+tagssaved = Štítky přidány
+tos = Pokračování souhlasíte s <a href="%1$S" target="_blank">Podmínkami služby</a> Pocket a <a href="%2$S" target="_blank">Zásadami ochrany osobních údajů</a>
+tryitnow = Vyzkoušejte nyní
+signinfirefox = Přihlášení ve Firefoxu
+signupfirefox = Registrace ve Firefoxu
+viewlist = Zobrazit seznam
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Uloží do služby Pocket
+saveToPocketCmd.label = Uložit stránku do služby Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Uložit odkaz do služby Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Zobrazit seznam služby Pocket
diff --git a/browser/extensions/pocket/locale/da/pocket.properties b/browser/extensions/pocket/locale/da/pocket.properties
new file mode 100644
index 000000000..5e5438359
--- /dev/null
+++ b/browser/extensions/pocket/locale/da/pocket.properties
@@ -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/.
+
+addtags = Tilføj tags
+alreadyhaveacct = Er du allerede Pocket-bruger?
+continueff = Fortsæt med Firefox
+errorgeneric = Der opstod en fejl ved forsøg på at gemme til Pocket.
+learnmore = Læs mere
+loginnow = Log ind
+maxtaglength = Tags er begrænset til 25 tegn
+mustbeconnected = Du skal have forbindelse til internettet for at kunne gemme til Pocket. Kontroller din internetforbindelse og prøv igen.
+onlylinkssaved = Kun links kan gemmes
+pagenotsaved = Siden blev ikke gemt
+pageremoved = Siden er fjernet
+pagesaved = Gemt til Pocket
+processingremove = Fjerner side…
+processingtags = Tilføjer tags…
+removepage = Fjern side
+save = Gem
+saving = Gemmer…
+signupemail = Log ind med mailadresse
+signuptosave = Meld dig til Pocket. Det er gratis.
+suggestedtags = Foreslåede tags
+tagline = Gemmer artikler og videoer fra Firefox i Pocket, så du senere kan se dem hvor og hvornår, du har lyst.
+taglinestory_one = Klik på knappen Pocket for at gemme en artikel, video eller webside fra Firefox.
+taglinestory_two = Se i Pocket hvor og hvornår, du har lyst.
+tagssaved = Tags tilføjet
+tos = Fortsætter du, accepterer du Pockets <a href="%1$S" target="_blank">tjenestevilkår</a> og <a href="%2$S" target="_blank">privatlivspolitik</a>
+tryitnow = Prøv det nu
+signinfirefox = Log ind med Firefox
+signupfirefox = Meld dig til med Firefox
+viewlist = Vis liste
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Gem til Pocket
+saveToPocketCmd.label = Gem siden til Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Gem link til Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Vis Pocket-liste
diff --git a/browser/extensions/pocket/locale/de/pocket.properties b/browser/extensions/pocket/locale/de/pocket.properties
new file mode 100644
index 000000000..f02f5da05
--- /dev/null
+++ b/browser/extensions/pocket/locale/de/pocket.properties
@@ -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/.
+
+addtags = Tags hinzufügen
+alreadyhaveacct = Sind Sie bereits Pocket-Nutzer?
+continueff = Mit Firefox fortfahren
+errorgeneric = Beim Speichern des Links bei Pocket ist ein Fehler aufgetreten.
+learnmore = Mehr erfahren
+loginnow = Anmelden
+maxtaglength = Tags dürfen höchsten 25 Zeichen lang sein.
+mustbeconnected = Bitte überprüfen Sie, ob Sie mit dem Internet verbunden sind.
+onlylinkssaved = Es können nur Links gespeichert werden
+pagenotsaved = Seite nicht gespeichert
+pageremoved = Seite entfernt
+pagesaved = Bei Pocket gespeichert
+processingremove = Seite wird entfernt…
+processingtags = Tags werden hinzugefügt…
+removepage = Seite entfernen
+save = Speichern
+saving = Speichern…
+signupemail = Mit E-Mail registrieren
+signuptosave = Registrieren Sie sich bei Pocket. Das ist kostenlos.
+suggestedtags = Vorgeschlagene Tags
+tagline = Speichern Sie Artikel und Videos aus Firefox bei Pocket, um sie jederzeit und auf jedem Gerät ansehen zu können.
+taglinestory_one = Klicken Sie auf die Pocket-Schaltfläche, um beliebige Artikel, Videos und Seiten aus Firefox zu speichern.
+taglinestory_two = Lesen Sie diese mit Pocket, jederzeit und auf jedem Gerät.
+tagssaved = Tags hinzugefügt
+tos = Indem Sie fortfahren, akzeptieren Sie die <a href="%1$S" target="_blank">Nutzungsbedingungen</a> und die <a href="%2$S" target="_blank">Datenschutzerklärung</a> von Pocket.
+tryitnow = Jetzt ausprobieren
+signinfirefox = Mit Firefox anmelden
+signupfirefox = Mit Firefox registrieren
+viewlist = Liste anzeigen
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Bei Pocket speichern
+saveToPocketCmd.label = Seite bei Pocket speichern
+saveToPocketCmd.accesskey = b
+saveLinkToPocketCmd.label = Link bei Pocket speichern
+saveLinkToPocketCmd.accesskey = c
+pocketMenuitem.label = Pocket-Liste anzeigen
diff --git a/browser/extensions/pocket/locale/dsb/pocket.properties b/browser/extensions/pocket/locale/dsb/pocket.properties
new file mode 100644
index 000000000..a878de329
--- /dev/null
+++ b/browser/extensions/pocket/locale/dsb/pocket.properties
@@ -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/.
+
+addtags = Wobznamjenja pśidaś
+alreadyhaveacct = Sćo južo wužywaŕ Pocket?
+continueff = Z Firefox pókšacowaś
+errorgeneric = Pśi składowanju do Pocket jo zmólka nastała.
+learnmore = Dalšne informacije
+loginnow = Pśizjawiś
+maxtaglength = Wobznamjenja su na 25 znamuškow wobgranicowane
+mustbeconnected = Musyśo z internetom zwězany byś, aby do Pocket składował. Pšosym pśeglědajśo swój zwisk a wopytajśo hyšći raz.
+onlylinkssaved = Jano wótkaze daju se składowaś
+pagenotsaved = Bok njejo se składł
+pageremoved = Bok jo se wótwónoźeł
+pagesaved = Do Pocket skłaźony
+processingremove = Bok se wótwónoźujo…
+processingtags = Wobznamjenja se pśidawaju…
+removepage = Bok wótwónoźeś
+save = Składowaś
+saving = Składujo se…
+signupemail = Registrěrujśo se z mejlku
+signuptosave = Registrěrujśo se za Pocket. Jo dermo.
+suggestedtags = Naraźone wobznamjenja
+tagline = Składujśo nastawki a wideo z Firefox, aby se je kuždy cas w Pocket na kuždem rěźe woglědał.
+taglinestory_one = Klikniśo na tłocašk Pocket, aby nastawk, wideo abo bok z Firefox składował.
+taglinestory_two = Se w Pocket na kuždem rěźee kuždy cas woglědaś.
+tagssaved = Wobznamjenja su se pśidali
+tos = Gaž pókšacujośo, zwólijośo do <a href="%1$S" target="_blank">wužywarskich wuměnjenjow</a> a <a href="%2$S" target="_blank">pšawidłow priwatnosći</a> Pocket
+tryitnow = Wopytajśo to něnto
+signinfirefox = Z Firefox pśizjawiś
+signupfirefox = Z Firefox registrěrowaś
+viewlist = Lisćinu pokazaś
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Do Pocket składowaś
+saveToPocketCmd.label = Bok do Pocket składowaś
+saveToPocketCmd.accesskey = b
+saveLinkToPocketCmd.label = Wótkaz do Pocket składowaś
+saveLinkToPocketCmd.accesskey = w
+pocketMenuitem.label = Lisćinu Pocket pokazaś
diff --git a/browser/extensions/pocket/locale/en-GB/pocket.properties b/browser/extensions/pocket/locale/en-GB/pocket.properties
new file mode 100644
index 000000000..bddd825c5
--- /dev/null
+++ b/browser/extensions/pocket/locale/en-GB/pocket.properties
@@ -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/.
+
+addtags = Add Tags
+alreadyhaveacct = Already a Pocket user?
+continueff = Continue with Firefox
+errorgeneric = There was an error when trying to save to Pocket.
+learnmore = Learn More
+loginnow = Log in
+maxtaglength = Tags are limited to 25 characters
+mustbeconnected = You must be connected to the Internet in order to save to Pocket. Please check your connection and try again.
+onlylinkssaved = Only links can be saved
+pagenotsaved = Page Not Saved
+pageremoved = Page Removed
+pagesaved = Saved to Pocket
+processingremove = Removing Page…
+processingtags = Adding tags…
+removepage = Remove Page
+save = Save
+saving = Saving…
+signupemail = Sign up with email
+signuptosave = Sign up for Pocket. It’s free.
+suggestedtags = Suggested Tags
+tagline = Save articles and videos from Firefox to view in Pocket on any device, any time.
+taglinestory_one = Click the Pocket Button to save any article, video or page from Firefox.
+taglinestory_two = View in Pocket on any device, any time.
+tagssaved = Tags Added
+tos = By continuing, you agree to Pocket’s <a href="%1$S" target="_blank">Terms of Service</a> and <a href="%2$S" target="_blank">Privacy Policy</a>
+tryitnow = Try It Now
+signinfirefox = Sign in with Firefox
+signupfirefox = Sign up with Firefox
+viewlist = View List
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Save to Pocket
+saveToPocketCmd.label = Save Page to Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Save Link to Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = View Pocket List
diff --git a/browser/extensions/pocket/locale/en-US/pocket.properties b/browser/extensions/pocket/locale/en-US/pocket.properties
new file mode 100644
index 000000000..dee2681dc
--- /dev/null
+++ b/browser/extensions/pocket/locale/en-US/pocket.properties
@@ -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/.
+
+addtags = Add Tags
+alreadyhaveacct = Already a Pocket user?
+continueff = Continue with Firefox
+errorgeneric = There was an error when trying to save to Pocket.
+learnmore = Learn More
+loginnow = Log in
+maxtaglength = Tags are limited to 25 characters
+mustbeconnected = You must be connected to the Internet in order to save to Pocket. Please check your connection and try again.
+onlylinkssaved = Only links can be saved
+pagenotsaved = Page Not Saved
+pageremoved = Page Removed
+pagesaved = Saved to Pocket
+processingremove = Removing Page…
+processingtags = Adding tags…
+removepage = Remove Page
+save = Save
+saving = Saving…
+signupemail = Sign up with email
+signuptosave = Sign up for Pocket. It’s free.
+suggestedtags = Suggested Tags
+tagline = Save articles and videos from Firefox to view in Pocket on any device, any time.
+taglinestory_one = Click the Pocket Button to save any article, video or page from Firefox.
+taglinestory_two = View in Pocket on any device, any time.
+tagssaved = Tags Added
+tos = By continuing, you agree to Pocket's <a href="%1$S" target="_blank">Terms of Service</a> and <a href="%2$S" target="_blank">Privacy Policy</a>
+tryitnow = Try It Now
+signinfirefox = Sign in with Firefox
+signupfirefox = Sign up with Firefox
+viewlist = View List
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Save to Pocket
+saveToPocketCmd.label = Save Page to Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Save Link to Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = View Pocket List
diff --git a/browser/extensions/pocket/locale/es-AR/pocket.properties b/browser/extensions/pocket/locale/es-AR/pocket.properties
new file mode 100644
index 000000000..2782d3ccf
--- /dev/null
+++ b/browser/extensions/pocket/locale/es-AR/pocket.properties
@@ -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/.
+
+addtags = Agregar etiquetas
+alreadyhaveacct = ¿Ya es un usuario de Pocket?
+continueff = Continuar con Firefox
+errorgeneric = Hubo un error al tratar de guardar en Pocket.
+learnmore = Conocer más
+loginnow = Ingresar
+maxtaglength = Las etiquetas están limitadas a 25 caracteres
+mustbeconnected = Debe estar conectado a Internet para poder guardar en Pocket. Verifique la conexión e intente nuevamente.
+onlylinkssaved = Solamente pueden guardarle enlaces
+pagenotsaved = Página no guardada
+pageremoved = Página eliminada
+pagesaved = Guardado en Pocket
+processingremove = Eliminando página…
+processingtags = Agregando etiquetas…
+removepage = Eliminar página
+save = Guardar
+saving = Guardando…
+signupemail = Ingresar con correo electrónico
+signuptosave = Registrarse en Pocket. En grátis.
+suggestedtags = Etiquetas sugeridas
+tagline = Guardar artículos y videos desde Firefox para ver en Pocket en cualquier dispositivo en cualquier momento.
+taglinestory_one = Clic en el botón Pocket para guardar cualquier artículo, video o página desde Firefox.
+taglinestory_two = Ver en Pocket en cualquier dispositivo en cualquier momento.
+tagssaved = Etiquetas agregadas
+tos = Al continuar acepta los <a href="%1$S" target="_blank">términos de servicio</a> y la <a href="%2$S" target="_blank">política de privacidad</a> de Pocket
+tryitnow = Probalo ahora
+signinfirefox = Ingresar con Firefox
+signupfirefox = Registrarse con Firefox
+viewlist = Ver lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Guardar en Pocket
+saveToPocketCmd.label = Guardar página en Pocket
+saveToPocketCmd.accesskey = G
+saveLinkToPocketCmd.label = Guardar enlace en Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Ver lista de Pocket
diff --git a/browser/extensions/pocket/locale/es-CL/pocket.properties b/browser/extensions/pocket/locale/es-CL/pocket.properties
new file mode 100644
index 000000000..7a4e6872b
--- /dev/null
+++ b/browser/extensions/pocket/locale/es-CL/pocket.properties
@@ -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/.
+
+addtags = Añadir etiquetas
+alreadyhaveacct = ¿Ya eres usuario de Pocket?
+continueff = Continuar con Firefox
+errorgeneric = Hubo un error al intentar guardarla en Pocket.
+learnmore = Aprender más
+loginnow = Conectarse
+maxtaglength = Las etiquetas están limitadas a 25 caracteres
+mustbeconnected = Debes estar conectado a Internet para guardar en Pocket. Por favor, revisa tu conexión y vuelve a intentarlo.
+onlylinkssaved = Solo se pueden guardar enlaces
+pagenotsaved = Página no guardada
+pageremoved = Página eliminada
+pagesaved = Guardada en Pocket
+processingremove = Eliminando página…
+processingtags = Añadiendo etiquetas…
+removepage = Eliminar página
+save = Guardar
+saving = Guardando…
+signupemail = Registrarse usando un email
+signuptosave = Registrarse en Pocket. Es gratis.
+suggestedtags = Etiquetas sugeridas
+tagline = Guarda artículos y videos desde Firefox para verlos en Pocket en cualquier dispositivo y momento.
+taglinestory_one = Aprieta el botón Pocket para guardar cualquier artículo, video o página de Firefox.
+taglinestory_two = Mírala en Pocket en cualquier dispositivo y momento
+tagssaved = Etiquetas añadidas
+tos = Al continuar, aceptas los <a href="%1$S" target="_blank">Términos del servicio</a> y la <a href="%2$S" target="_blank">Política de privacidad</a> de Pocket.
+tryitnow = Probarlo ahora
+signinfirefox = Conectarse con Firefox
+signupfirefox = Registrarse con Firefox
+viewlist = Ver lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Guardar en Pocket
+saveToPocketCmd.label = Guardar página en Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Guardar enlace en Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Ver lista de Pocket
diff --git a/browser/extensions/pocket/locale/es-ES/pocket.properties b/browser/extensions/pocket/locale/es-ES/pocket.properties
new file mode 100644
index 000000000..f10a20525
--- /dev/null
+++ b/browser/extensions/pocket/locale/es-ES/pocket.properties
@@ -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/.
+
+addtags = Añadir etiquetas
+alreadyhaveacct = ¿Ya es usuario de Pocket?
+continueff = Continuar con Firefox
+errorgeneric = Ha sucedido un error al intentar guardar en Pocket.
+learnmore = Más información
+loginnow = Iniciar sesión
+maxtaglength = Las etiquetas están limitadas a 25 caracteres
+mustbeconnected = Debe estar conectado a Internet para poder guardar en Pocket. Compruebe su conexión y vuelva a intentarlo.
+onlylinkssaved = Solo se pueden guardar enlaces
+pagenotsaved = Página no guardada
+pageremoved = Página eliminada
+pagesaved = Guardado en Pocket
+processingremove = Eliminando página…
+processingtags = Añadiendo etiquetas…
+removepage = Eliminar página
+save = Guardar
+saving = Guardando…
+signupemail = Regístrese con su dirección de correo
+signuptosave = Regístrese en Pocket. Es gratis.
+suggestedtags = Etiquetas sugeridas
+tagline = Guarde artículos y vídeos desde Firefox para verlos en Pocket en cualquier dispositivo, en cualquier momento.
+taglinestory_one = Pulse el botón Pocket para guardar cualquier artículo, vídeo o página desde Firefox.
+taglinestory_two = Véalo en Pocket en cualquier dispositivo, en cualquier momento.
+tagssaved = Etiquetas añadidas
+tos = Al continuar, aceptas los <a href="%1$S" target="_blank">Términos del servicio</a> y la <a href="%2$S" target="_blank">Política de privacidad</a> de Pocket
+tryitnow = Pruébalo ahora
+signinfirefox = Iniciar sesión con Firefox
+signupfirefox = Registrarse con Firefox
+viewlist = Ver lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Guardar en Pocket
+saveToPocketCmd.label = Guardar página en Pocket
+saveToPocketCmd.accesskey = G
+saveLinkToPocketCmd.label = Guardar enlace en Pocket
+saveLinkToPocketCmd.accesskey = P
+pocketMenuitem.label = Ver la lista de Pocket
diff --git a/browser/extensions/pocket/locale/es-MX/pocket.properties b/browser/extensions/pocket/locale/es-MX/pocket.properties
new file mode 100644
index 000000000..4d2bb9c4f
--- /dev/null
+++ b/browser/extensions/pocket/locale/es-MX/pocket.properties
@@ -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/.
+
+addtags = Agregar Etiquetas
+alreadyhaveacct = ¿Ya eres usuario de Pocket?
+continueff = Continúa con Firefox
+errorgeneric = Hubo un error cuando se intentaba guardar en Pocket.
+learnmore = Aprende más
+loginnow = Ingresar
+maxtaglength = Las etiquetas están limitadas a 25 caracteres
+mustbeconnected = Debes estar conectado a Internet para guardar en Pocket. Por favor, revisa tu conexión e intenta de nuevo.
+onlylinkssaved = Sólo los enlaces pueden guardarse
+pagenotsaved = Página no guardada
+pageremoved = Página eliminada
+pagesaved = Guardado en Pocket
+processingremove = Eliminando página…
+processingtags = Agregando etiquetas…
+removepage = Eliminar página
+save = Guardar
+saving = Guardando…
+signupemail = Regístrate con un correo electrónico
+signuptosave = Regístrate en Pocket. Es gratis.
+suggestedtags = Etiquetas sugeridas
+tagline = Guardar artículos y videos desde Firefox para ver en Pocket o en cualquier dispositivo, en cualquier momento.
+taglinestory_one = Haz clic en el botón de Pocket para guardar cualquier artículo, video o página desde Firefox.
+taglinestory_two = Ver en Pocker o en cualquier dispositivo, en cualquier momento.
+tagssaved = Etiquetas agregadas
+tos = Al continuar, aceptas los <a href="%1$S" target="_blank">Términos del servicio</a> y la <a href="%2$S" target="_blank">Política de privacidad</a> de Pocket
+tryitnow = Pruébalo ahora
+signinfirefox = Ingresa con Firefox
+signupfirefox = Regístrate con Firefox
+viewlist = Ver lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Guardar en Pocket
+saveToPocketCmd.label = Guardar página en Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Guardar enlace en Pocket
+saveLinkToPocketCmd.accesskey = n
+pocketMenuitem.label = Ver la lista de Pocket
diff --git a/browser/extensions/pocket/locale/et/pocket.properties b/browser/extensions/pocket/locale/et/pocket.properties
new file mode 100644
index 000000000..5f2d68f9c
--- /dev/null
+++ b/browser/extensions/pocket/locale/et/pocket.properties
@@ -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/.
+
+addtags = Lisa silte
+alreadyhaveacct = Kas oled juba Pocketi kasutaja?
+continueff = Jätka Firefoxiga
+errorgeneric = Pocketisse salvestamisel esines viga.
+learnmore = Rohkem teavet
+loginnow = Logi sisse
+maxtaglength = Siltide pikkus võib olla kuni 25 tähemärki
+mustbeconnected = Pocketisse salvestamiseks on vajalik töötav internetiühendus. Palun kontrolli oma ühendust ja proovi uuesti.
+onlylinkssaved = Salvestada saab ainult linke
+pagenotsaved = Lehte ei salvestatud
+pageremoved = Leht eemaldati
+pagesaved = Pocketisse salvestatud
+processingremove = Lehe eemaldamine…
+processingtags = Siltide lisamine…
+removepage = Eemalda leht
+save = Salvesta
+saving = Salvestamine…
+signupemail = Registreeru e-posti teel
+signuptosave = Liitu Pocketiga. See on tasuta.
+suggestedtags = Soovitatud sildid
+tagline = Salvesta Firefoxist artikleid ja videoid, et vaadata neid Pocketist kõigil seadmeil just siis, kui ise soovid.
+taglinestory_one = Artikli, video või lehe salvestamiseks klõpsa Pocketi nupul.
+taglinestory_two = Vaata Pocketist kõigil seadmeil just siis, kui ise soovid.
+tagssaved = Sildid on lisatud
+tos = Jätkates nõustud Pocket'i <a href="%1$S" target="_blank">kasutustingimuste</a> ja <a href="%2$S" target="_blank">privaatsuspoliitikaga</a>.
+tryitnow = Proovi kohe
+signinfirefox = Logi sisse Firefoxiga
+signupfirefox = Registreeru Firefoxiga
+viewlist = Vaata nimekirja
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Salvesta Pocketisse
+saveToPocketCmd.label = Salvesta leht Pocketisse
+saveToPocketCmd.accesskey = l
+saveLinkToPocketCmd.label = Salvesta link Pocketisse
+saveLinkToPocketCmd.accesskey = i
+pocketMenuitem.label = Vaata Pocketi nimekirja
diff --git a/browser/extensions/pocket/locale/fi/pocket.properties b/browser/extensions/pocket/locale/fi/pocket.properties
new file mode 100644
index 000000000..3cd47e891
--- /dev/null
+++ b/browser/extensions/pocket/locale/fi/pocket.properties
@@ -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/.
+
+addtags = Lisää tunnisteita
+alreadyhaveacct = Oletko jo Pocket-palvelun käyttäjä?
+continueff = Jatka Firefoxin parissa
+errorgeneric = Tapahtui virhe tallennettaessa Pocket-palveluun.
+learnmore = Lue lisää
+loginnow = Kirjaudu sisään
+maxtaglength = Tunnisteet voivat olla enintään 25 merkkiä pitkiä
+mustbeconnected = Tarvitset aktiivisen Internet-yhteyden talllentaaksesi sivuja Pocket-palveluun. Tarkista Internet-yhteytesi ja yritä uudestaan.
+onlylinkssaved = Vain linkkejä voidaan tallentaa
+pagenotsaved = Sivua ei ole tallennettu
+pageremoved = Sivu poistettiin
+pagesaved = Tallennettiin Pocket-palveluun
+processingremove = Poistetaan sivu…
+processingtags = Lisätään tunnisteet…
+removepage = Poista sivu
+save = Tallenna
+saving = Tallennetaan…
+signupemail = Rekisteröidy sähköpostiosoitteella
+signuptosave = Rekisteröidy Pocket-palveluun. Se on ilmaista.
+suggestedtags = Ehdotetut tunnisteet
+tagline = Tallenna artikkelit ja videot Firefoxista Pocket-palveluun katseltaviksi millä tahansa laitteella, koska tahansa.
+taglinestory_one = Napsauta Pocket-painiketta tallentaaksesi artikkelin, videon tai sivun Firefoxissa.
+taglinestory_two = Katsele Pocket-palvelussa millä tahansa laitteella, koska tahansa.
+tagssaved = Tunnisteet lisättiin
+tos = Jatkamalla hyväksyt Pocketin <a href="%1$S" target="_blank">käyttöehdot</a> ja <a href="%2$S" target="_blank">tietosuojakäytännön</a>
+tryitnow = Kokeile nyt
+signinfirefox = Kirjaudu sisään Firefox-tilillä
+signupfirefox = Rekisteröidy Firefox-tilillä
+viewlist = Näytä lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Tallenna Pocket-palveluun
+saveToPocketCmd.label = Tallenna sivu Pocket-palveluun
+saveToPocketCmd.accesskey = c
+saveLinkToPocketCmd.label = Tallenna linkki Pocket-palveluun
+saveLinkToPocketCmd.accesskey = k
+pocketMenuitem.label = Näytä Pocket-palvelun lista
diff --git a/browser/extensions/pocket/locale/fr/pocket.properties b/browser/extensions/pocket/locale/fr/pocket.properties
new file mode 100644
index 000000000..cb9b0ca8f
--- /dev/null
+++ b/browser/extensions/pocket/locale/fr/pocket.properties
@@ -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/.
+
+addtags = Ajouter des étiquettes
+alreadyhaveacct = Vous utilisez déjà Pocket ?
+continueff = Continuer avec Firefox
+errorgeneric = Une erreur s’est produite lors de l’enregistrement dans Pocket.
+learnmore = En savoir plus
+loginnow = Connectez-vous
+maxtaglength = Les étiquettes sont limitées à 25 caractères
+mustbeconnected = Vous devez être connecté à Internet pour enregistrer des liens dans Pocket. Veuillez vérifier votre connexion puis réessayer.
+onlylinkssaved = Seuls les liens peuvent être enregistrés
+pagenotsaved = Page non enregistrée
+pageremoved = Page supprimée
+pagesaved = Page enregistrée dans Pocket
+processingremove = Suppression de la page…
+processingtags = Ajout des étiquettes…
+removepage = Supprimer la page
+save = Enregistrer
+saving = Enregistrement…
+signupemail = S’inscrire avec une adresse électronique
+signuptosave = Inscrivez-vous à Pocket, c’est gratuit.
+suggestedtags = Étiquettes suggérées
+tagline = Enregistrez des articles et des vidéos depuis Firefox pour les visualiser dans Pocket sur n’importe quel appareil, à tout moment.
+taglinestory_one = Cliquez sur le bouton Pocket pour enregistrer depuis Firefox n’importe quel article, vidéo ou page.
+taglinestory_two = Affichez vos pages dans Pocket sur n’importe quel appareil, à tout moment.
+tagssaved = Étiquettes ajoutées
+tos = En continuant, vous acceptez les <a href="%1$S" target="_blank">conditions d’utilisation</a> et la <a href="%2$S" target="_blank">politique de confidentialité</a> de Pocket
+tryitnow = Essayer
+signinfirefox = Connexion via Firefox
+signupfirefox = S’inscrire avec Firefox
+viewlist = Afficher la liste
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Enregistrer dans Pocket
+saveToPocketCmd.label = Enregistrer la page dans Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Enregistrer le lien dans Pocket
+saveLinkToPocketCmd.accesskey = k
+pocketMenuitem.label = Afficher la liste Pocket
diff --git a/browser/extensions/pocket/locale/fy-NL/pocket.properties b/browser/extensions/pocket/locale/fy-NL/pocket.properties
new file mode 100644
index 000000000..5b41c652f
--- /dev/null
+++ b/browser/extensions/pocket/locale/fy-NL/pocket.properties
@@ -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/.
+
+addtags = Labels tafoegje
+alreadyhaveacct = Al in Pocket-brûker?
+continueff = Trochgean mei Firefox
+errorgeneric = Der is in flater bard by it bewarjen nei Pocket.
+learnmore = Mear ynfo
+loginnow = Meld jo oan
+maxtaglength = Labels binne beheint ta 25 tekens
+mustbeconnected = Jo moatte mei it ynternet ferbûn wêze om nei Pocket bewarje te kinnen. Kontrolearje jo ferbining en probearje it opnij.
+onlylinkssaved = Allinnich keppelingen kinne bewarre wurde
+pagenotsaved = Side net bewarre
+pageremoved = Side fuortsmiten
+pagesaved = Bewarre nei Pocket
+processingremove = Side fuortsmite…
+processingtags = Labels tafoegje…
+removepage = Side fuortsmite
+save = Bewarje
+saving = Bewarje…
+signupemail = Registrearje mei e-mailadres
+signuptosave = Registrearje foar Pocket. It is fergees.
+suggestedtags = Foarstelde labels
+tagline = Bewarje artikelen en fideo’s fan Firefox út foar werjaan yn Pocket op ferskate apparaten, wannear dan ek.
+taglinestory_one = Klik op de Pocket-knop om artikelen, fideo’s of siden fan Firefox út te bewarjen.
+taglinestory_two = Besjoch se op ferskate apparaten, wannear dan ek.
+tagssaved = Labels tafoege
+tos = Troch fierder te gean, geane jo akkoard mei de <a href="%1$S" target="_blank">Tsjinstbetingsten</a> en it <a href="%2$S" target="_blank">Privacybelied</a> fan Pocket
+tryitnow = No probearje
+signinfirefox = Oanmelde mei Firefox
+signupfirefox = Registrearje mei Firefox
+viewlist = List werjaan
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Bewarje nei Pocket
+saveToPocketCmd.label = Side bewarje nei Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Keppeling bewarje nei Pocket
+saveLinkToPocketCmd.accesskey = e
+pocketMenuitem.label = Pocket-list werjaan
diff --git a/browser/extensions/pocket/locale/gu-IN/pocket.properties b/browser/extensions/pocket/locale/gu-IN/pocket.properties
new file mode 100644
index 000000000..2261ff5b4
--- /dev/null
+++ b/browser/extensions/pocket/locale/gu-IN/pocket.properties
@@ -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/.
+
+addtags = ટૅગ્સ ઉમેરો
+alreadyhaveacct = પહેલેથી જ એક પોકેટ વપરાશકર્તા છો?
+continueff = ફાયરફોક્સ સાથે ચાલુ રાખો
+errorgeneric = પોકેટ સાચવી રાખવા માટે પ્રયાસ હતો ત્યારે એક ભૂલ હતી.
+learnmore = વધુ શીખો
+loginnow = પ્રવેશ કરો
+maxtaglength = ટૅગ્સ 25 અક્ષરો સુધી મર્યાદિત છે
+mustbeconnected = તમે પોકેટ પર સેવ કરવા માટે ઇન્ટરનેટ સાથે જોડાયેલ હોવા જ જોઈએ. કૃપા કરીને તમારા જોડાણ તપાસ કરો અને ફરીથી પ્રયત્ન કરો.
+onlylinkssaved = માત્ર લિંક્સ સાચવી શકાય છે
+pagenotsaved = પૃષ્ઠ સાચવેલા નથી
+pageremoved = પૃષ્ઠ દૂર
+pagesaved = પોકેટ પર સાચવ્યું
+processingremove = પૃષ્ઠ દૂર કરી રહ્યા છીએ…
+processingtags = ટૅગ્સ ઉમેરી રહ્યું છે…
+removepage = પૃષ્ઠ દૂર
+save = સાચવો
+saving = સાચવી રહ્યું છે…
+signupemail = ઇમેઇલ સાથે સાઇનઅપ
+signuptosave = પોકેટ માટે સાઇન અપ કરો. તે મફત છે.
+suggestedtags = સૂચવેલ ટૅગ્સ
+tagline = કોઈપણ ઉપકરણ, કોઈ પણ સમય પર પોકેટ માં જોવા માટે ફાયરફોક્સ ના લેખો અને વીડિયો સાચવો.
+taglinestory_one = ફાયરફોક્સ એક લેખ, વિડિઓ અથવા પાનું સેવ કરવા પોકેટ બટન પર ક્લિક કરો.
+taglinestory_two = કોઈપણ ઉપકરણ, કોઈ પણ સમય પર પોકેટ માં જુઓ.
+tagssaved = ટૅગ્સ ઉમેર્યું
+tos = ચાલુ કરવાથી, તમે પોકેટ માટેની <a href="%1$S" target="_blank">સેવાની શરતો</a> અને <a href="%2$S" target="_blank">ગોપનીયતા નીતિ</a>સંમત થશો
+tryitnow = અત્યારે પ્રયાસ કરો
+signinfirefox = ફાયરફોક્સ સાથે ચાલુ રાખો
+signupfirefox = ફાયરફોક્સ સાથે સાઇન અપ કરો
+viewlist = યાદી જુઓ
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = પોકેટ
+pocket-button.tooltiptext = પોકેટ પર સાચવો
+saveToPocketCmd.label = પોકેટ પર પૃષ્ઠ સાચવો
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = પોકેટ પર લિંક સાચવો
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = જુઓ પોકેટની યાદી
diff --git a/browser/extensions/pocket/locale/hr/pocket.properties b/browser/extensions/pocket/locale/hr/pocket.properties
new file mode 100644
index 000000000..3b723499f
--- /dev/null
+++ b/browser/extensions/pocket/locale/hr/pocket.properties
@@ -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/.
+
+addtags = Dodaj oznake
+alreadyhaveacct = Postojeći ste Pocket korisnik?
+continueff = Nastavite s Firefoxom
+errorgeneric = Došlo je do greške pri snimanju u Pocket.
+learnmore = Saznajte više
+loginnow = Prijava
+maxtaglength = Oznake su ograničene na 25 znakova
+mustbeconnected = Morate biti povezani na Internet da bi ste mogli snimiti u Pocket. Molimo vas da provjerite vašu vezu i pokušate ponovno.
+onlylinkssaved = Mogu se spremiti samo poveznice
+pagenotsaved = Stranica nije spremljena
+pageremoved = Stranica uklonjena
+pagesaved = Spremljeno u Pocket
+processingremove = Uklanjanje stranice…
+processingtags = Dodavanje oznaka…
+removepage = Ukloni stranicu
+save = Spremi
+saving = Spremanje…
+signupemail = Registracija s e-poštom
+signuptosave = Registrirajte se na Pocket. Besplatno je.
+suggestedtags = Predložene oznake
+tagline = Spremite članke, video snimke iz Firefoxa za prikaz u Pocketu, na bilo kojem uređaju, bilo kada.
+taglinestory_one = Kliknite na Pocket tipku da biste snimili bilo koji članak, video ili stranicu iz Firefoxa.
+taglinestory_two = Pregledajte u Pocketu na bilo kojem uređaju, bilo kada.
+tagssaved = Oznake dodane
+tos = Nastavljajući, prihvaćate Pocket <a href="%1$S" target="_blank">Uvjete pružanja usluge</a> i <a href="%2$S" target="_blank">Izjavu o privatnosti</a>
+tryitnow = Isprobajte odmah
+signinfirefox = Prijava s Firefoxom
+signupfirefox = Registracija s Firefoxom
+viewlist = Prikaži popis
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Spremi u Pocket
+saveToPocketCmd.label = Spremi stranicu u Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Spremi poveznicu u Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Prikaži Pocket popis
diff --git a/browser/extensions/pocket/locale/hsb/pocket.properties b/browser/extensions/pocket/locale/hsb/pocket.properties
new file mode 100644
index 000000000..a5f5583e7
--- /dev/null
+++ b/browser/extensions/pocket/locale/hsb/pocket.properties
@@ -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/.
+
+addtags = Znački přidać
+alreadyhaveacct = Sće hižo wužiwar Pocket?
+continueff = Z Firefox pokročować
+errorgeneric = Při składowanju do Pocket je zmylk wustupił.
+learnmore = Dalše informacije
+loginnow = Přizjewić
+maxtaglength = Znački su na 25 znamješkow wobmjezowane
+mustbeconnected = Dyrbiće z internetom zwjazany być, zo byšće do Pocket składował. Prošu přepruwujće swój zwisk a spytajće hišće raz.
+onlylinkssaved = Jenož wotkazy dadźa so składować
+pagenotsaved = Strona njeje so składowała
+pageremoved = Strona je so wotstroniła
+pagesaved = Do Pocket składowany
+processingremove = Strona so wotstronja…
+processingtags = Znački so přidawaja…
+removepage = Stronu wotstronić
+save = Składować
+saving = Składuje so…
+signupemail = Registrujće so z e-mejlku
+signuptosave = Registrujće so za Pocket. Je darmo.
+suggestedtags = Namjetowane znački
+tagline = Składujće nastawki a wideja z Firefox, zo byšće sej je kóždy čas w Pocket na kóždym graće wobhladał.
+taglinestory_one = Klikńće na tłóčatko Pocket, zo byšće nastawk, widejo abo stronu z Firefox składował.
+taglinestory_two = Sej w Pocket na kóždym graće kóždy čas wobhladać.
+tagssaved = Znački su so přidali
+tos = Hdyž pokročujeće, zwoliće do <a href="%1$S" target="_blank">wužiwarskich wuměnjenjow</a> a <a href="%2$S" target="_blank">prawidłow priwatnosće</a> Pocket
+tryitnow = Spytajće to nětko
+signinfirefox = Z Firefox přizjewić
+signupfirefox = Z Firefox registrować
+viewlist = Lisćinu pokazać
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Do Pocket składować
+saveToPocketCmd.label = Stronu do Pocket składować
+saveToPocketCmd.accesskey = d
+saveLinkToPocketCmd.label = Wotkaz do Pocket składować
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Lisćinu Pocket pokazać
diff --git a/browser/extensions/pocket/locale/hu/pocket.properties b/browser/extensions/pocket/locale/hu/pocket.properties
new file mode 100644
index 000000000..767638e82
--- /dev/null
+++ b/browser/extensions/pocket/locale/hu/pocket.properties
@@ -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/.
+
+addtags = Címkék hozzáadása
+alreadyhaveacct = Már Pocket felhasználó?
+continueff = Folytatás a Firefoxszal
+errorgeneric = Hiba történt a Pocketre mentés közben.
+learnmore = Tudjon meg többet
+loginnow = Bejelentkezés
+maxtaglength = A címkék legfeljebb 25 karakter hosszúak lehetnek
+mustbeconnected = Csatlakoznia kell az internethez a Pocketre mentéshez. Ellenőrizze a kapcsolatot, és próbálja újra.
+onlylinkssaved = Csak hivatkozások menthetők
+pagenotsaved = Az oldal nem lett mentve
+pageremoved = Oldal eltávolítva
+pagesaved = Mentve a Pocketbe
+processingremove = Oldal eltávolítása…
+processingtags = Címkék hozzáadása…
+removepage = Oldal eltávolítása
+save = Mentés
+saving = Mentés…
+signupemail = Regisztráció e-maillel
+signuptosave = Regisztráljon ingyenesen a Pocketre.
+suggestedtags = Javasolt címkék
+tagline = Mentsen cikkeket és videókat a Firefoxból a Pocketen való megtekintéshez bármely eszközön, bármikor.
+taglinestory_one = Kattintson a Pocket gombra bármely cikk, videó vagy oldal mentéséhez a Firefoxból.
+taglinestory_two = Nézze meg a Pocketen bármely eszközön, bármikor.
+tagssaved = Címkék hozzáadva
+tos = A folytatással elfogadja a Pocket <a href="%1$S" target="_blank">Szolgáltatási feltételeit</a> és az <a href="%2$S" target="_blank">Adatvédelmi nyilatkozatot</a>
+tryitnow = Próbálja ki most
+signinfirefox = Bejelentkezés a Firefoxszal
+signupfirefox = Regisztráció a Firefoxszal
+viewlist = Lista megjelenítése
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Mentés a Pocketbe
+saveToPocketCmd.label = Oldal mentése a Pocketbe
+saveToPocketCmd.accesskey = c
+saveLinkToPocketCmd.label = Hivatkozás mentése a Pocketbe
+saveLinkToPocketCmd.accesskey = H
+pocketMenuitem.label = Pocket lista megjelenítése
diff --git a/browser/extensions/pocket/locale/it/pocket.properties b/browser/extensions/pocket/locale/it/pocket.properties
new file mode 100644
index 000000000..2105011eb
--- /dev/null
+++ b/browser/extensions/pocket/locale/it/pocket.properties
@@ -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/.
+
+addtags = Aggiungi etichette
+alreadyhaveacct = Hai già un account registrato su Pocket?
+continueff = Prosegui con Firefox
+errorgeneric = Si è verificato un errore durante il salvataggio in Pocket.
+learnmore = Ulteriori informazioni
+loginnow = Accedi
+maxtaglength = La lunghezza massima per le etichette è di 25 caratteri
+mustbeconnected = È necessario essere connessi a Internet per salvare in Pocket. Verificare la connessione e riprovare.
+onlylinkssaved = È possibile salvare solo link
+pagenotsaved = Pagina non salvata
+pageremoved = Pagina rimossa
+pagesaved = Salvata in Pocket
+processingremove = Rimozione pagina…
+processingtags = Salvataggio etichette…
+removepage = Rimuovi pagina
+save = Salva
+saving = Salvataggio…
+signupemail = Accedi con email
+signuptosave = Registrati su Pocket. È gratis.
+suggestedtags = Etichette suggerite
+tagline = Salva articoli e video da Firefox per visualizzarli in Pocket da qualunque dispositivo e in qualunque momento.
+taglinestory_one = Fai clic sul pulsante Pocket per salvare qualunque articolo, video o pagina da Firefox.
+taglinestory_two = Visualizza in Pocket da qualunque dispositivo e in qualunque momento.
+tagssaved = Aggiunte etichette
+tos = Proseguendo si accettano i <a href="%1$S" target="_blank">termini di servizio</a> e l’<a href="%2$S" target="_blank">informativa sulla privacy</a>
+tryitnow = Provalo subito
+signinfirefox = Accedi con Firefox
+signupfirefox = Registrati con Firefox
+viewlist = Visualizza elenco
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Salva in Pocket
+saveToPocketCmd.label = Salva pagina in Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Salva link in Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Visualizza elenco Pocket
diff --git a/browser/extensions/pocket/locale/ja/pocket.properties b/browser/extensions/pocket/locale/ja/pocket.properties
new file mode 100644
index 000000000..1aef6bba1
--- /dev/null
+++ b/browser/extensions/pocket/locale/ja/pocket.properties
@@ -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/.
+
+addtags = タグを追加
+alreadyhaveacct = Pocket に登録済みですか?
+continueff = Firefox で続行
+errorgeneric = Pocket への保存中にエラーがありました。
+learnmore = 詳細
+loginnow = ログイン
+maxtaglength = タグは 25 文字までです
+mustbeconnected = Pocket に保存するには、インターネット接続が必要です。接続状況を確認してから、試してみたください。
+onlylinkssaved = リンクのみ保存しました
+pagenotsaved = ページを保存しませんでした
+pageremoved = ページを削除しました
+pagesaved = Pocket に保存しました
+processingremove = ページを削除しています...
+processingtags = タグを追加しています...
+removepage = ページを削除
+save = 保存
+saving = 保存しています...
+signupemail = メールアドレスで新規登録
+signuptosave = Pocket に新規登録します。無料です。
+suggestedtags = 提案タグ
+tagline = Firefox で記事や動画を保存すると、いつでもどこでも Pocket で閲覧できます。
+taglinestory_one = Firefox で Pocket ボタンをクリックすると、様々な記事や動画やページを保存できます。
+taglinestory_two = Pocket でいつでもどこでも閲覧できます。
+tagssaved = タグを追加しました
+tos = 続けることで、Pocket の <a href="%1$S" target="_blank">利用規約</a> と <a href="%2$S" target="_blank">プライバシーポリシー</a> に同意したことになります
+tryitnow = 今すぐ試す
+signinfirefox = Firefox でログイン
+signupfirefox = Firefox で新規登録
+viewlist = リストを表示
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket に保存
+saveToPocketCmd.label = ページを Pocket に保存
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = リンクを Pocket に保存
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket のリストを表示
diff --git a/browser/extensions/pocket/locale/jar.mn b/browser/extensions/pocket/locale/jar.mn
new file mode 100644
index 000000000..f29ca7367
--- /dev/null
+++ b/browser/extensions/pocket/locale/jar.mn
@@ -0,0 +1,33 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# These are used for the big if statement, as the preprocessor can't handle
+# dashes.
+#define bn_BD bn-BD
+#define en_GB en-GB
+#define en_US en-US
+#define es_AR es-AR
+#define es_CL es-CL
+#define es_ES es-ES
+#define es_MX es-MX
+#define fy_NL fy-NL
+#define gu_IN gu-IN
+#define nn_NO nn-NO
+#define pt_BR pt-BR
+#define pt_PT pt-PT
+#define sv_SE sv-SE
+#define zh_CN zh-CN
+#define zh_TW zh-TW
+
+[features/firefox@getpocket.com] @AB_CD@.jar:
+% locale pocket @AB_CD@ %locale/@AB_CD@/
+ # For locales we support, include the file from the locale's directory in the
+ # source tree.
+ # For other locales (and en-US) fallback to the en-US directory.
+#if AB_CD == ast || AB_CD == az || AB_CD == bg || AB_CD == bn_BD || AB_CD == cs || AB_CD == da || AB_CD == de || AB_CD == dsb || AB_CD == en_GB || AB_CD == en_US || AB_CD == es_AR || AB_CD == es_CL || AB_CD == es_ES || AB_CD == es_MX || AB_CD == et || AB_CD == fi || AB_CD == fr || AB_CD == fy_NL || AB_CD == gu_IN || AB_CD == hr || AB_CD == hsb || AB_CD == hu || AB_CD == it || AB_CD == ja || AB_CD == ka || AB_CD == kab || AB_CD == lt || AB_CD == lv || AB_CD == mr || AB_CD == ms || AB_CD == nl || AB_CD == nn_NO || AB_CD == or || AB_CD == pl || AB_CD == pt_BR || AB_CD == pt_PT || AB_CD == rm || AB_CD == ro || AB_CD == ru || AB_CD == sk || AB_CD == sl || AB_CD == sq || AB_CD == sr || AB_CD == sv_SE || AB_CD == te || AB_CD == th || AB_CD == tr || AB_CD == uk || AB_CD == zh_CN || AB_CD == zh_TW
+ locale/@AB_CD@/ (@AB_CD@/*)
+#else
+ locale/@AB_CD@/ (en-US/*)
+#endif
diff --git a/browser/extensions/pocket/locale/ka/pocket.properties b/browser/extensions/pocket/locale/ka/pocket.properties
new file mode 100644
index 000000000..266ded044
--- /dev/null
+++ b/browser/extensions/pocket/locale/ka/pocket.properties
@@ -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/.
+
+addtags = იარლიყების დამატება
+alreadyhaveacct = უკვე იყენებთ Pocket-ს?
+continueff = Firefox-ით გაგრძელება
+errorgeneric = Pocket-ში შენახვისას დაფიქსირდა შეცდომა.
+learnmore = დაწვრილებით
+loginnow = შესვლა
+maxtaglength = იარლიყები შეზღუდულია 25 ასომდე
+mustbeconnected = Pocket-ში შესანახად საჭიროა ინტერნეთთან კავშირი. გთხოვთ შეამოწმეთ თქვენი კავშირი და ხელახლა ცადეთ.
+onlylinkssaved = შესაძლებელია მხოლოდ ბმულების შენახვა
+pagenotsaved = გვერდი არ შეინახა
+pageremoved = გვერდი წაიშალა
+pagesaved = შეინახა Pocket-ში
+processingremove = იშლება გვერდი…
+processingtags = ემატება იარლიყები…
+removepage = გვერდის წაშლა
+save = შენახვა
+saving = ინახება…
+signupemail = რეგისტრაცია ელ-ფოსტით
+signuptosave = დარეგისტრირდით Pocket-ზე. ეს უფასოა.
+suggestedtags = შემოთავაზებული იარლიყები
+tagline = შეინახეთ სტატიები და ვიდეობეი Firefox-იდან მათ Pocket-ში სანახავად ნებისმიერ მოწყობილობაზე, ნებისმიერ დროს.
+taglinestory_one = Firefox-იდან ნებისმიერი სტატიის, ვიდეოს ან გვერდის შესანახად დააწკაპეთ Pocket-ის ღილაკს.
+taglinestory_two = დაათვალიერეთ Pocket-ში ნებისმიერ მოწყობილობაზე, ნებისმიერ დროს.
+tagssaved = იარლიყები დაემატა
+tos = გაგრძელების შემთხვევაში თქვენ ეთანხმებით Pocket-ის <a href="%1$S" target="_blank">მომსახურების პირობებს</a> და <a href="%2$S" target="_blank">პრივატულობის პოლიტიკას</a>
+tryitnow = სცადეთ ახლავე
+signinfirefox = შესვლა Firefox-ით
+signupfirefox = რეგისრაცია Firefox-ით
+viewlist = სიის ნახვა
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket-ში შენახვა
+saveToPocketCmd.label = გვერდის შენახვა Pocket-ში
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = ბმულის შენახვა Pocket-ში
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket სიის ნახვა
diff --git a/browser/extensions/pocket/locale/kab/pocket.properties b/browser/extensions/pocket/locale/kab/pocket.properties
new file mode 100644
index 000000000..3f4cc642a
--- /dev/null
+++ b/browser/extensions/pocket/locale/kab/pocket.properties
@@ -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/.
+
+addtags = Rnu tibzimin
+alreadyhaveacct = Aseqdac yakan n Pocket?
+continueff = Kemmel s Firefox
+errorgeneric = Teḍra-d tuccḍa deg aɛraḍ n usekles ɣer Pocket.
+learnmore = Issin ugar
+loginnow = Kcem
+maxtaglength = Tibzimin ɣur-sent talast n 25 n isekkilen
+mustbeconnected = Yessefk ad tiliḍ teqqneḍ ɣer Internet akken ad tizmireḍ ad teskelseḍ ɣer Pocket. Ma ulac aɣilif, senqed tuqqna yinek sakin ɛreḍ tikelt nniḍen.
+onlylinkssaved = Al iseɣwan i yezmren ad ttwakelsen
+pagenotsaved = Asebter ur yettwakles ara
+pageremoved = Asebter yettwakkes
+pagesaved = Yettwakles ɣer Pocket
+processingremove = Tukksa n isebtar…
+processingtags = Timerna n tebzimin…
+removepage = Kkes asebter
+save = Sekles
+saving = Asekles…
+signupemail = Jerred s yimayl
+signuptosave = Jerred ɣer Pocket. Baṭel.
+suggestedtags = Tibzimin yettwasumren
+tagline = Sekles imagraden akked tvidyutin si Firefox akken ad twaliḍ di Pocket ɣef yal ibenk, melmi tebɣiḍ.
+taglinestory_one = Sit ɣef tqeffalt Pocket akken ad teskelseḍ yal amagrad, tavidyut neɣ asebter si Firefox.
+taglinestory_two = Sken di Pocket ɣef yal ibenk yellan, melmi tebɣiḍ.
+tagssaved = Tibzimin yettwarnan
+tos = Ma tkemleḍ, ad tqebleḍ <a href="%1$S" target="_blank">tiwtilin n useqdec</a> akked <a href="%2$S" target="_blank">tsertit tabaḍnit</a> n Pocket
+tryitnow = Ɛreḍ-it tura
+signinfirefox = Kcem s Firefox
+signupfirefox = Jerred s Firefox
+viewlist = Sken tabdart
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Sekles ɣer Pocket
+saveToPocketCmd.label = Sekles asebter ɣer Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Sekles aseɣwen ɣer Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Sken tabdart n Pocket
diff --git a/browser/extensions/pocket/locale/lt/pocket.properties b/browser/extensions/pocket/locale/lt/pocket.properties
new file mode 100644
index 000000000..f3e8df077
--- /dev/null
+++ b/browser/extensions/pocket/locale/lt/pocket.properties
@@ -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/.
+
+addtags = Pridėkite gairių
+alreadyhaveacct = Jau naudojatės „Pocket“?
+continueff = Tęsti su „Firefox“
+errorgeneric = Bandant išsaugoti į „Pocket“ įvyko klaida.
+learnmore = Sužinokite daugiau
+loginnow = Prisijungti
+maxtaglength = Gaires gali sudaryti iki 25 simbolių
+mustbeconnected = Norėdami saugoti į „Pocket“, turite būti prisijungę prie interneto. Prašome patikrinti savo ryšį ir bandyti vėl.
+onlylinkssaved = Išsaugoti galima tik nuorodas
+pagenotsaved = Tinklalapis neišsaugotas
+pageremoved = Tinklalapis pašalintas
+pagesaved = Išsaugota į „Pocket“
+processingremove = Šalinamas tinklalapis…
+processingtags = Pridedamos gairės…
+removepage = Pašalinti tinklalapį
+save = Išsaugoti
+saving = Išsaugoma…
+signupemail = Prisijungti su el. paštu
+signuptosave = Pradėkite naudotis „Pocket“. Tai nemokama.
+suggestedtags = Siūlomos gairės
+tagline = Išsaugokite straipsnius bei vaizdo įrašus iš „Firefox“ norėdami juos peržiūrėti bet kokiame įrenginyje su „Pocket“, bet kuriuo metu.
+taglinestory_one = Spustelėkite „Pocket“ mygtuką norėdami išsaugoti bet kokį straipsnį, vaizdo įrašą ar tinklalapį iš „Firefox“.
+taglinestory_two = Peržiūrėkite bet kokiame įrenginyje su „Pocket“, bet kuriuo metu.
+tagssaved = Gairės pridėtos
+tos = Tęsdami sutinkate su „Pocket“ <a href="%1$S" target="_blank">paslaugos teikimo sąlygomis</a> bei <a href="%2$S" target="_blank">privatumo nuostatais</a>
+tryitnow = Išbandykite dabar
+signinfirefox = Prisijungti su „Firefox“
+signupfirefox = Prisijungti su „Firefox“
+viewlist = Peržiūrėti sąrašą
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Išsaugoti į „Pocket“
+saveToPocketCmd.label = Išsaugoti tinklalapį į „Pocket“
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Išsaugoti saitą į „Pocket“
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Peržiūrėti „Pocket“ sąrašą
diff --git a/browser/extensions/pocket/locale/lv/pocket.properties b/browser/extensions/pocket/locale/lv/pocket.properties
new file mode 100644
index 000000000..40e3bb367
--- /dev/null
+++ b/browser/extensions/pocket/locale/lv/pocket.properties
@@ -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/.
+
+addtags = Pievienot birkas
+alreadyhaveacct = Jau lietojat Pocket?
+continueff = Turpināt ar Firefox
+errorgeneric = Kļūda saglabājot Pocket.
+learnmore = Uzzināt vairāk
+loginnow = Pieslēgties
+maxtaglength = Birkas nevar būt garākas par 25 simboliem
+mustbeconnected = Lai saglabātu Pocket, jābūt savienojumam ar internetu. Lūdzu pārbaudiet savienojumu un mēģiniet vēlreiz.
+onlylinkssaved = Saglabāt var tikai saites
+pagenotsaved = Lapa nav saglabāta
+pageremoved = Lapa ir aizvākta
+pagesaved = Saglabāt Pocket
+processingremove = Aizvāc lapu…
+processingtags = Pievieno birkas…
+removepage = Izņemt lapu
+save = Saglabāt
+saving = Saglabā…
+signupemail = Pierakstīties ar epastu
+signuptosave = Pierakstīties Pocket. Tas ir bez maksas.
+suggestedtags = Ieteiktās birkas
+tagline = Saglabājiet Firefox rakstu vai video, lai skatītos to ar Pocket jebkurā ierīcē un jebkurā laikā.
+taglinestory_one = Klikšķiniet uz Pocket pogas, lai saglabātu Firefox rakstus, video vai lapas.
+taglinestory_two = Skatiet ar Pocket jebkurā ierīcē un jebkurā laikā.
+tagssaved = Birkas pievienotas
+tos = Turpinot, tu piekrīti Pocket <a href="%1$S" target="_blank">Noteikumiem</a> un <a href="%2$S" target="_blank">Privātuma politikai</a>
+tryitnow = Izmēģini tagad
+signinfirefox = Pieslēgties ar Firefox
+signupfirefox = Pierakstīties ar Firefox
+viewlist = Skatījumu saraksts
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Saglabāt Pocket
+saveToPocketCmd.label = Saglabāt lapu Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Saglabāt saiti Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Aplūkot Pocket sarakstu
diff --git a/browser/extensions/pocket/locale/moz.build b/browser/extensions/pocket/locale/moz.build
new file mode 100644
index 000000000..aac3a838c
--- /dev/null
+++ b/browser/extensions/pocket/locale/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/extensions/pocket/locale/mr/pocket.properties b/browser/extensions/pocket/locale/mr/pocket.properties
new file mode 100644
index 000000000..af330ec1e
--- /dev/null
+++ b/browser/extensions/pocket/locale/mr/pocket.properties
@@ -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/.
+
+addtags = टॅग जोडा
+alreadyhaveacct = आधीपासून Pocket वापरताय?
+continueff = Firefox सोबत पुढे चला
+errorgeneric = Pocket मध्ये जतन करताना त्रुटी आली.
+learnmore = अधिक जाणून घ्या
+loginnow = लॉग इन
+maxtaglength = टॅग्ज साठी 25 वर्णांची मर्यादा आहे
+mustbeconnected = Pocket मध्ये साठविण्यासाठी आपले इंटरनेट चालू असणे आवश्यक आहे. कृपया आपली जोडणी तपासा आणि पुन्हा प्रयत्न करा.
+onlylinkssaved = फक्त दुवे जतन केले जाऊ शकतात
+pagenotsaved = पृष्ठ जतन झाले नाही
+pageremoved = पृष्ठ काढले गेले
+pagesaved = Pocket मध्ये जतन झाले
+processingremove = पृष्ठ काढून टाकत आहे...
+processingtags = टॅग्ज जोडत आहे…
+removepage = पृष्ठ काढून टाका
+save = जतन करा
+saving = जतन करत आहे...
+signupemail = ईमेलसह साईन अप करा
+signuptosave = Pocket साठी साईन अप करा. हे मोफत आहे.
+suggestedtags = सूचविलेले टॅग्स
+tagline = Firefox मधील नोंदी आणि व्हिडीओ कुठल्याही साधनावर केंव्हाही Pocket मध्ये पाहण्यासाठी साठवा.
+taglinestory_one = Firefox वरील कोणताही लेख, व्हिडिओ किंवा पृष्ठ जतन करण्यासाठी Pocket बटणावर क्लिक करा.
+taglinestory_two = कधीही कुठल्याही साधनावर Pocket मध्ये पाहा.
+tagssaved = टॅग्स जोडले
+tos = सुरु ठेवुन, आपण Pocketच्या <a href="%1$S" target="_blank">सेवेच्या अटी</a> आणि <a href="%2$S" target="_blank">गोपनीयता धोरणांशी</a> सहमत आहात
+tryitnow = आत्ताच वापरुन पाहा
+signinfirefox = Firefox सह साइन इन करा
+signupfirefox = Firefox सह साईन अप करा
+viewlist = यादी पहा
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket मध्ये जतन करा
+saveToPocketCmd.label = पृष्ठ Pocket मध्ये जतन करा
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = दुवा Pocket मध्ये संकलित करा
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = पॉकेट सूची पहा
diff --git a/browser/extensions/pocket/locale/ms/pocket.properties b/browser/extensions/pocket/locale/ms/pocket.properties
new file mode 100644
index 000000000..67a935be8
--- /dev/null
+++ b/browser/extensions/pocket/locale/ms/pocket.properties
@@ -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/.
+
+addtags = Tambah Tag
+alreadyhaveacct = Sudah menjadi pengguna Poket?
+continueff = Teruskan dengan Firefox
+errorgeneric = Ada ralat semasa cuba menyimpan ke Pocket.
+learnmore = Ketahui Selanjutnya
+loginnow = Log masuk
+maxtaglength = Tag dihadkan hanya 25 aksara
+mustbeconnected = Anda mesti ada sambungan Internet untuk menyimpan ke Pocket. Sila periksa sambungan anda dan cuba lagi.
+onlylinkssaved = Hanya pautan boleh disimpan
+pagenotsaved = Halaman Tidak Disimpan
+pageremoved = Halaman Dialih keluar
+pagesaved = Disimpan ke Pocket
+processingremove = Sedang mengalih keluar Halaman…
+processingtags = Sedang menambah tag…
+removepage = Alih keluar Halaman
+save = Simpan
+saving = Sedang menyimpan…
+signupemail = Daftar dengan e-mel
+signuptosave = Daftar masuk ke Pocket. Percuma.
+suggestedtags = Tag Disyorkan
+tagline = Simpan artikel dan video dari Firefox untuk dilihat dalam Pocket pada apa jua peranti pada bila-bila masa.
+taglinestory_one = Klik butang Pocket untuk menyimpan apa jua artikel, video atau halaman daripada Firefox.
+taglinestory_two = Papar dalam Pocket dalam mana-mana peranti, bila-bila masa saja.
+tagssaved = Tag Ditambah
+tos = Dengan meneruskan, anda setuju dengan <a href="%1$S" target="_blank">Terma Perkhidmatan</a> Pocket dan <a href="%2$S" target="_blank">Polisi Privasi</a>
+tryitnow = Cubanya Sekarang
+signinfirefox = Daftar masuk Firefox
+signupfirefox = Daftar dengan Firefox
+viewlist = Senarai Paparan
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Simpan ke Pocket
+saveToPocketCmd.label = Simpan Halaman ke Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Simpan Pautan ke Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Papar Senarai Pocket
diff --git a/browser/extensions/pocket/locale/nl/pocket.properties b/browser/extensions/pocket/locale/nl/pocket.properties
new file mode 100644
index 000000000..3abe14491
--- /dev/null
+++ b/browser/extensions/pocket/locale/nl/pocket.properties
@@ -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/.
+
+addtags = Labels toevoegen
+alreadyhaveacct = Al een Pocket-gebruiker?
+continueff = Doorgaan met Firefox
+errorgeneric = Er is een fout opgetreden bij het opslaan naar Pocket.
+learnmore = Meer info
+loginnow = Meld u aan
+maxtaglength = Labels zijn beperkt tot 25 tekens
+mustbeconnected = U moet met het internet zijn verbonden om naar Pocket te kunnen opslaan. Controleer uw verbinding en probeer het opnieuw.
+onlylinkssaved = Alleen koppelingen kunnen worden opgeslagen
+pagenotsaved = Pagina niet opgeslagen
+pageremoved = Pagina verwijderd
+pagesaved = Opgeslagen naar Pocket
+processingremove = Pagina verwijderen…
+processingtags = Labels toevoegen…
+removepage = Pagina verwijderen
+save = Opslaan
+saving = Opslaan…
+signupemail = Registreren met e-mailadres
+signuptosave = Registreer voor Pocket. Het is gratis.
+suggestedtags = Voorgestelde labels
+tagline = Sla artikelen en video’s vanuit Firefox op voor weergeven in Pocket op diverse apparaten, wanneer dan ook.
+taglinestory_one = Klik op de Pocket-knop om artikelen, video’s of pagina’s vanuit Firefox op te slaan.
+taglinestory_two = Bekijk ze op diverse apparaten, wanneer dan ook.
+tagssaved = Labels toegevoegd
+tos = Door verder te gaan, gaat u akkoord met de <a href="%1$S" target="_blank">Servicevoorwaarden</a> en het <a href="%2$S" target="_blank">Privacybeleid</a> van Pocket
+tryitnow = Nu proberen
+signinfirefox = Aanmelden met Firefox
+signupfirefox = Registreren met Firefox
+viewlist = Lijst weergeven
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Opslaan naar Pocket
+saveToPocketCmd.label = Pagina opslaan naar Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Koppeling opslaan naar Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket-lijst weergeven
diff --git a/browser/extensions/pocket/locale/nn-NO/pocket.properties b/browser/extensions/pocket/locale/nn-NO/pocket.properties
new file mode 100644
index 000000000..3f3dc971e
--- /dev/null
+++ b/browser/extensions/pocket/locale/nn-NO/pocket.properties
@@ -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/.
+
+addtags = Legg til merkelapp-stikkord
+alreadyhaveacct = Allereie ein Pocket-brukar?
+continueff = Hald fram med Firefox
+errorgeneric = Eit problem oppstod ved lagring til Pocket.
+learnmore = Les meir
+loginnow = Logg inn
+maxtaglength = Merkelapp-stikkord er avgrensa til 25 teikn
+mustbeconnected = Du må vera kopla til nettet for å lagra til Pocket. Kontroller tilkoplinga og prøv igjen.
+onlylinkssaved = Berre lenker kan lagrast
+pagenotsaved = Sida ikkje lagra
+pageremoved = Sida fjerna
+pagesaved = Lagrar til Pocket
+processingremove = Fjernar sida …
+processingtags = Legg til merkelapp-stikkord…
+removepage = Fjern sida
+save = Lagra
+saving = Lagrar …
+signupemail = Logg inn med e-postadresse
+signuptosave = Registrer deg på Pocket. Det er gratis.
+suggestedtags = Føreslåtte merkelapp-stikkord
+tagline = Lagra artiklar og videoar frå Firefox for å visa dei i Pocket på kva som helst eining, når som helst.
+taglinestory_one = Trykk på Pocket-knappen for å lagra kva som helst artikkel, video eller side frå Firefox.
+taglinestory_two = Vis i Pocket, på kva som helst eining, når som helst.
+tagssaved = Merkelapp-stikkord lagt til
+tos = Ved å fortsetta godtek du Pocket sine <a href="%1$S" target="_blank">tenestevilkår</a> og <a href="%2$S" target="_blank">personvernpraksis</a>
+tryitnow = Prøv no
+signinfirefox = Logg inn med Firefox
+signupfirefox = Registrer deg med Firefox
+viewlist = Vis liste
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Lagra til Pocket
+saveToPocketCmd.label = Lagra sida i Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Lagra lenke til Pocket
+saveLinkToPocketCmd.accesskey = l
+pocketMenuitem.label = Vis Pocket-liste
diff --git a/browser/extensions/pocket/locale/or/pocket.properties b/browser/extensions/pocket/locale/or/pocket.properties
new file mode 100644
index 000000000..d2616e484
--- /dev/null
+++ b/browser/extensions/pocket/locale/or/pocket.properties
@@ -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/.
+
+addtags = ଟ୍ୟାଗ ଯୋଡ଼ନ୍ତୁ
+alreadyhaveacct = ଆଗରୁ Pocket ବ୍ୟବହାର କରୁଛନ୍ତି?
+continueff = Firefox ଦେଇ ଆଗକୁ ଯିବେ
+errorgeneric = Pocketରେ ସାଇତିବା ବେଳେ ଅସୁବିଧାଟିଏ ହେଲା ।
+learnmore = ଅଧିକ ଶିଖନ୍ତୁ
+loginnow = ଲଗ ଇନ
+maxtaglength = ଟ୍ୟାଗ 25 ଟି ଅକ୍ଷରରେ ସୀମିତ
+mustbeconnected = Pocketରେ ସାଇତିବା ପାଇଁ ଆପଣ ଇଣ୍ଟରନେଟ ସହ ସଂଯୁକ୍ତ ହୋଇଥିବା ଲୋଡ଼ା । ଦୟାକରି ନିଜ ସଂଯୋଗ ପରଖି ଆଉଥରେ ଚେଷ୍ଟାକରନ୍ତୁ ।
+onlylinkssaved = କେବଳ ଲିଙ୍କ ସାଇତାଯାଇପାରିବ
+pagenotsaved = ପୃଷ୍ଠା ସାଇତା ଯାଇନାହିଁ
+pageremoved = ପୃଷ୍ଠାଟି ହଟାଗଲା
+pagesaved = Pocketରେ ସାଇତାଗଲା
+processingremove = ପୃଷ୍ଠା ହଟାଯାଉଛି…
+processingtags = ଟ୍ୟାଗ ଯୋଡ଼ାଯାଉଛି…
+removepage = ପୃଷ୍ଠା ହଟାନ୍ତୁ
+save = ସାଇତିବେ
+saving = ସାଇତୁଛି…
+signupemail = ଇମେଲରେ ସାଇନ ଅପ
+signuptosave = Pocket ପାଇଁ ସାଇନ ଅପ । ଏହା ମାଗଣା ।
+suggestedtags = ପ୍ରସ୍ତାବିତ ଟ୍ୟାଗ
+tagline = ଯେତେବେଳେ ଲୋଡ଼ା କୌଣସି ଏକ ଡିଭାଇସରୁ Pocketରେ ଦେଖିବା ପାଇଁ Firefoxରୁ ପ୍ରସଙ୍ଗ ଓ ଭିଡ଼ିଓ ସାଇତିପାରିବେ ।
+taglinestory_one = କୌଣସି ପ୍ରସଙ୍ଗ, ଭିଡ଼ିଓ ବା ପୃଷ୍ଠା ସାଇତିବା ପାଇଁ Pocket Button ଟିପନ୍ତୁ ।
+taglinestory_two = ଯେତେବେଳେ ଲୋଡ଼ା ସବୁ ଡିଭାଇସରୁ Pocketରେ ଦେଖନ୍ତୁ ।
+tagssaved = ଟ୍ୟାଗ ଯୋଡ଼ାଗଲା
+tos = ଆଗକୁ ବଢ଼ିବା ଯୋଗୁ ଆପଣ Pocketର <a href="%1$S" target="_blank">ନୀତି ନିୟମ</a> ଓ <a href="%2$S" target="_blank">ଗୋପନୀୟତା ନୀତିବଳୀ</a> ମାନୁଛନ୍ତି ବୋଲି ଜାଣିରଖିବେ
+tryitnow = ଏବେ ଏହା ଚେଷ୍ଟାକରନ୍ତୁ
+signinfirefox = Firefoxରେ ସାଇନ ଇନ କରନ୍ତୁ
+signupfirefox = Firefoxରେ ସାଇନ ଅପ କରନ୍ତୁ
+viewlist = ତାଲିକା ଦେଖନ୍ତୁ
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocketରେ ସାଇତନ୍ତୁ
+saveToPocketCmd.label = Pocketରେ ପୃଷ୍ଠା ସାଇତନ୍ତୁ
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Pocketରେ ଲିଙ୍କ ସାଇତନ୍ତୁ
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket ତାଲିକା ଦେଖନ୍ତୁ
diff --git a/browser/extensions/pocket/locale/pl/pocket.properties b/browser/extensions/pocket/locale/pl/pocket.properties
new file mode 100644
index 000000000..07b5866f1
--- /dev/null
+++ b/browser/extensions/pocket/locale/pl/pocket.properties
@@ -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/.
+
+taglinestory_one=Kliknij przycisk Pocket, aby wysłać dowolny artykuł, film lub stronę z Firefoksa.
+taglinestory_two=Czytaj z Pocket o dowolnej porze na dowolnym urządzeniu.
+learnmore=Więcej informacji
+
+signuptosave=Utwórz konto w Pocket. Jest darmowe.
+signupfirefox=Utwórz konto z Firefoksem
+signupemail=Utwórz konto z adresem e-mail
+alreadyhaveacct=Masz już konto Pocket?
+loginnow=Zaloguj się
+
+tos=Kontynuując, wyrażasz zgodę na <a href="%1$S" target="_blank">warunki korzystania z usługi</a> i <a href="%2$S" target="_blank">politykę prywatności</a>
+tryitnow=Wypróbuj teraz
+
+continueff=Kontynuuj z kontem Firefoksa
+signinfirefox=Zaloguj się z Firefoksem
+viewlist=Otwórz w Pocket
+
+removepage=Usuń stronę
+processingremove=Usuwanie strony…
+pageremoved=Usunięto stronę
+
+save=Wyślij
+saving=Wysyłanie…
+pagesaved=Wysłano do Pocket
+
+addtags=Etykiety
+processingtags=Wysyłanie etykiet…
+tagssaved=Wysłano etykiety
+maxtaglength=Etykiety są ograniczone do 25 znaków
+suggestedtags=Sugerowane etykiety
+tagline=Wysyłaj artykuły i filmy z Firefoksa do Pocket, aby wyświetlić je o dowolnej porze na dowolnym urządzeniu.
+
+errorgeneric=Wystąpił błąd podczas wysyłania do Pocket.
+mustbeconnected=Połączenie z Internetem jest konieczne do przesyłania do Pocket. Proszę sprawdzić połączenie i spróbować ponownie.
+onlylinkssaved=Tylko odnośniki mogą być przesyłane
+pagenotsaved=Nie przesłano strony
+
+pocket-button.label=Pocket
+pocket-button.tooltiptext=Wyślij do Pocket
+saveToPocketCmd.label=Wyślij stronę do Pocket
+saveToPocketCmd.accesskey=s
+saveLinkToPocketCmd.label=Wyślij odnośnik do Pocket
+saveLinkToPocketCmd.accesskey=o
+pocketMenuitem.label=Wysłane do Pocket
diff --git a/browser/extensions/pocket/locale/pt-BR/pocket.properties b/browser/extensions/pocket/locale/pt-BR/pocket.properties
new file mode 100644
index 000000000..a637aea29
--- /dev/null
+++ b/browser/extensions/pocket/locale/pt-BR/pocket.properties
@@ -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/.
+
+addtags = Adicionar etiquetas
+alreadyhaveacct = Já é um usuário do Pocket?
+continueff = Continuar com o Firefox
+errorgeneric = Houve um erro ao tentar salvar no Pocket.
+learnmore = Saber mais
+loginnow = Entrar
+maxtaglength = As etiquetas estão limitadas a 25 caracteres
+mustbeconnected = Você deve estar conectado à Internet para salvar no Pocket. Verifique a sua conexão e tente novamente.
+onlylinkssaved = Somente links podem ser salvos
+pagenotsaved = Página não salva
+pageremoved = Página removida
+pagesaved = Salva no Pocket
+processingremove = Removendo página…
+processingtags = Adicionando etiquetas…
+removepage = Remover página
+save = Salvar
+saving = Salvando…
+signupemail = Registrar com e-mail
+signuptosave = Registre-se no Pocket. É gratuito.
+suggestedtags = Etiquetas sugeridas
+tagline = Salve os artigos e vídeos do Firefox no Pocket para vê-los mais tarde e em qualquer local.
+taglinestory_one = Clique no botão Pocket para salvar um artigo, vídeo ou página do Firefox.
+taglinestory_two = Ver no Pocket em qualquer dispositivo, a qualquer hora.
+tagssaved = Etiquetas adicionadas
+tos = Continuando, você concorda com os <a href="%1$S" target="_blank">Termos de serviço</a> e <a href="%2$S" target="_blank">Política de privacidade</a> do Pocket
+tryitnow = Experimente-o agora
+signinfirefox = Entrar com o Firefox
+signupfirefox = Cadastre-se com o Firefox
+viewlist = Ver lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Salvar no Pocket
+saveToPocketCmd.label = Salvar página no Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Salvar link no Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Ver lista do Pocket
diff --git a/browser/extensions/pocket/locale/pt-PT/pocket.properties b/browser/extensions/pocket/locale/pt-PT/pocket.properties
new file mode 100644
index 000000000..90eafe77f
--- /dev/null
+++ b/browser/extensions/pocket/locale/pt-PT/pocket.properties
@@ -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/.
+
+addtags = Adicionar etiquetas
+alreadyhaveacct = Já é um utilizador do Pocket?
+continueff = Continuar com o Firefox
+errorgeneric = Ocorreu um erro ao tentar guardar no Pocket.
+learnmore = Saber mais
+loginnow = Iniciar sessão
+maxtaglength = As etiquetas estão limitadas a 25 caracteres
+mustbeconnected = É necessária uma ligação à Internet para poder guardar no Pocket. Por favor, verifique a sua ligação à Internet e tente novamente.
+onlylinkssaved = Só podem ser guardadas ligações
+pagenotsaved = Página não guardada
+pageremoved = Página removida
+pagesaved = Guardado no Pocket
+processingremove = A remover página…
+processingtags = A adicionar etiquetas…
+removepage = Remover página
+save = Guardar
+saving = A guardar…
+signupemail = Registar com email
+signuptosave = Registe-se no Pocket. É gratuito.
+suggestedtags = Etiquetas sugeridas
+tagline = Guardar artigos e vídeos do Firefox para os ver no Pocket em qualquer dispositivo, em qualquer altura.
+taglinestory_one = Clique no botão Pocket para guardar qualquer artigo, vídeo ou página a partir Firefox.
+taglinestory_two = Ver no Pocket em qualquer dispositivo, a qualquer altura.
+tagssaved = Etiquetas adicionadas
+tos = Ao continuar, concorda com os <a href="%1$S" target="_blank">termos do serviço</a> e <a href="%2$S" target="_blank">política de privacidade</a> do Pocket
+tryitnow = Experimente-o agora
+signinfirefox = Iniciar sessão com Firefox
+signupfirefox = Registar com Firefox
+viewlist = Ver lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Guardar no Pocket
+saveToPocketCmd.label = Guardar página no Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Guardar ligação no Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Ver lista do Pocket
diff --git a/browser/extensions/pocket/locale/rm/pocket.properties b/browser/extensions/pocket/locale/rm/pocket.properties
new file mode 100644
index 000000000..803277b21
--- /dev/null
+++ b/browser/extensions/pocket/locale/rm/pocket.properties
@@ -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/.
+
+addtags = Agiuntar tags
+alreadyhaveacct = Es ti gia in utilisader da Pocket?
+continueff = Cuntinuar cun Firefox
+errorgeneric = Ina errur è succedida durant empruvar da memorisar en Pocket.
+learnmore = Ulteriuras infurmaziuns
+loginnow = S'annunziar
+maxtaglength = Tags èn limitads a 25 caracters
+mustbeconnected = Ti stos esser connectà cun l'internet per pudair memorisar en Pocket. Controllescha p.pl. tia connexiun ed emprova anc ina giada.
+onlylinkssaved = Mo colliaziuns pon vegnir memorisadas
+pagenotsaved = Betg memorisà la pagina
+pageremoved = Allontanà la pagina
+pagesaved = Memorisà en Pocket
+processingremove = Allontanar la pagina…
+processingtags = Agiuntar tags…
+removepage = Allontanar la pagina
+save = Memorisar
+saving = Memorisar…
+signupemail = Sa registrar cun l'adressa dad e-mail
+signuptosave = Ta registrescha tar Pocket. Gratuit.
+suggestedtags = Tags proponids
+tagline = Memorisescha artitgels e videos ord Firefox per als vesair en Pocket, sin mintga apparat, da tut temp.
+taglinestory_one = Clicca sin il buttun da Pocket per memorisar directamain ord Firefox tge artitgel, video u pagina ch'i saja.
+taglinestory_two = Vesair en Pocket sin mintga apparat, da tut temp.
+tagssaved = Tags agiuntads
+tos = Cun cuntinuar accepteschas ti il <a href="%1$S" target="_blank">Contract da licenza</a> e las <a href="%2$S" target="_blank">Directivas per la protecziun da datas</a> da Pocket
+tryitnow = Emprova ussa
+signinfirefox = S'annunziar cun Firefox
+signupfirefox = Sa registrar cun Firefox
+viewlist = Mussar la glista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Memorisar en Pocket
+saveToPocketCmd.label = Memorisar la pagina en Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Memorisar la colliaziun en Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Mussar la glista da Pocket
diff --git a/browser/extensions/pocket/locale/ro/pocket.properties b/browser/extensions/pocket/locale/ro/pocket.properties
new file mode 100644
index 000000000..eedb756f9
--- /dev/null
+++ b/browser/extensions/pocket/locale/ro/pocket.properties
@@ -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/.
+
+addtags = Adaugă etichete
+alreadyhaveacct = Ești deja un utilizator Pocket?
+continueff = Continuă cu Firefox
+errorgeneric = A apărut o eroare la încercarea de salvare în Pocket.
+learnmore = Află mai multe
+loginnow = Autentificare
+maxtaglength = Etichetele sunt limitate la 25 de caractere
+mustbeconnected = Trebuie să fii conectat la internet pentru a salva în Pocket. Te rugăm să verifici conexiunea și să încerci din nou.
+onlylinkssaved = Doar linkurile pot fi salvate
+pagenotsaved = Pagină nesalvată
+pageremoved = Pagină eliminată
+pagesaved = Salvat în Pocket
+processingremove = Se elimină pagina…
+processingtags = Se adaugă etichete…
+removepage = Elimină pagina
+save = Salvează
+saving = Se salvează...
+signupemail = Înregistrare cu e-mail
+signuptosave = Înregistrează-te pentru Pocket. Este gratuit.
+suggestedtags = Etichete sugerate
+tagline = Salvează articole și videoclipuri din Firefox pentru a le vedea în Pocket de pe orice dispozitiv, oricând.
+taglinestory_one = Clic pe butonul Pocket pentru a salva orice articol, videoclip sau pagină din Firefox.
+taglinestory_two = Vezi în Pocket de pe orice dispozitiv, oricând.
+tagssaved = Etichete adăugate
+tos = Continuând, ești de acord cu <a href="%1$S" target="_blank">termenii de utilizare a serviciului</a> și <a href="%2$S" target="_blank">politica de confidențialitate</a> a Pocket
+tryitnow = Încearcă acum
+signinfirefox = Autentificare în Firefox
+signupfirefox = Înregistrare în Firefox
+viewlist = Vezi lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Salvează în Pocket
+saveToPocketCmd.label = Salvează pagina în Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Salvează linkul în Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Vezi lista Pocket
diff --git a/browser/extensions/pocket/locale/ru/pocket.properties b/browser/extensions/pocket/locale/ru/pocket.properties
new file mode 100644
index 000000000..0da716e8a
--- /dev/null
+++ b/browser/extensions/pocket/locale/ru/pocket.properties
@@ -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/.
+
+addtags = Добавить теги
+alreadyhaveacct = Уже используете Pocket?
+continueff = Продолжить через Firefox
+errorgeneric = При попытке сохранить в Pocket произошла ошибка.
+learnmore = Узнайте больше
+loginnow = Войдите
+maxtaglength = Длина тега не должна превышать 25 символов
+mustbeconnected = Чтобы сохранять в Pocket, вы должны быть подключены к Интернету. Пожалуйста, проверьте ваше соединение и попробуйте снова.
+onlylinkssaved = Можно сохранять только ссылки
+pagenotsaved = Страница не сохранена
+pageremoved = Страница удалена
+pagesaved = Сохранено в Pocket
+processingremove = Удаление страницы…
+processingtags = Добавление тегов…
+removepage = Удалить страницу
+save = Сохранить
+saving = Сохранение…
+signupemail = Регистрация по эл. почте
+signuptosave = Зарегистрируйтесь в Pocket. Это бесплатно.
+suggestedtags = Рекомендуемые теги
+tagline = Сохраняйте статьи и видео из Firefox для просмотра в Pocket на любом устройстве, в любой момент.
+taglinestory_one = Щёлкните по кнопке Pocket, чтобы сохранить любую статью, видео или страницу из Firefox.
+taglinestory_two = Просматривайте их в Pocket на любом устройстве, в любой момент.
+tagssaved = Теги добавлены
+tos = Продолжая, вы принимаете <a href="%1$S" target="_blank">Условия службы</a> и <a href="%2$S" target="_blank">Политику приватности</a> Pocket
+tryitnow = Попробовать сейчас
+signinfirefox = Войти через Firefox
+signupfirefox = Регистрация через Firefox
+viewlist = Просмотреть список
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Сохранить в Pocket
+saveToPocketCmd.label = Сохранить страницу в Pocket
+saveToPocketCmd.accesskey = х
+saveLinkToPocketCmd.label = Сохранить ссылку в Pocket
+saveLinkToPocketCmd.accesskey = о
+pocketMenuitem.label = Показать список Pocket
diff --git a/browser/extensions/pocket/locale/sk/pocket.properties b/browser/extensions/pocket/locale/sk/pocket.properties
new file mode 100644
index 000000000..57327365c
--- /dev/null
+++ b/browser/extensions/pocket/locale/sk/pocket.properties
@@ -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/.
+
+addtags = Pridať značky
+alreadyhaveacct = Už ste používateľom služby Pocket?
+continueff = Pokračovať s Firefoxom
+errorgeneric = Počas ukladania údajov do služby Pocket sa vyskytla chyba.
+learnmore = Ďalšie informácie
+loginnow = Prihlásiť sa
+maxtaglength = Značky môžu obsahovať najviac 25 znakov
+mustbeconnected = Ak chcete ukladať údaje do služby Pocket, musíte byť pripojený k sieti Internet. Skontrolujte svoje pripojenie a skúste to znova.
+onlylinkssaved = Uložené môžu byť len odkazy
+pagenotsaved = Stránka nebola uložená
+pageremoved = Stránka bola odstránená
+pagesaved = Uložená do služby Pocket
+processingremove = Stránka sa odstraňuje…
+processingtags = Pridávajú sa značky…
+removepage = Odstrániť stránku
+save = Uložiť
+saving = Ukladá sa…
+signupemail = Zaregistrovať sa pomocou e-mailu
+signuptosave = Zaregistrujte sa v službe Pocket. Je zadarmo.
+suggestedtags = Navrhované značky
+tagline = Ukladajte si články a videá z Firefoxu a majte ich dostupné kdekoľvek a na akomkoľvek zariadení pomocou služby Pocket.
+taglinestory_one = Kliknutím na tlačidlo Pocket vo Firefoxe uložíte akýkoľvek článok, video alebo stránku.
+taglinestory_two = Tieto sú potom so službou Pocket dostupné kdekoľvek a na akomkoľvek zariadení.
+tagssaved = Značky boli pridané
+tos = Pokračovaním vyjadrujete súhlas s <a href="%1$S" target="_blank">podmienkami používania</a> služby Pocket a so <a href="%2$S" target="_blank">zásadami ochrany osobných údajov</a>
+tryitnow = Vyskúšajte to hneď teraz
+signinfirefox = Prihlásiť sa pomocou Firefoxu
+signupfirefox = Zaregistrovať sa pomocou Firefoxu
+viewlist = Zobraziť zoznam
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Služba Pocket
+pocket-button.tooltiptext = Uložiť do služby Pocket
+saveToPocketCmd.label = Uložiť stránku do služby Pocket
+saveToPocketCmd.accesskey = P
+saveLinkToPocketCmd.label = Uložiť odkaz do služby Pocket
+saveLinkToPocketCmd.accesskey = d
+pocketMenuitem.label = Zobraziť zoznam služby Pocket
diff --git a/browser/extensions/pocket/locale/sl/pocket.properties b/browser/extensions/pocket/locale/sl/pocket.properties
new file mode 100644
index 000000000..e0451d72b
--- /dev/null
+++ b/browser/extensions/pocket/locale/sl/pocket.properties
@@ -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/.
+
+addtags = Dodaj oznake
+alreadyhaveacct = Že uporabljate Pocket?
+continueff = Nadaljuj s Firefoxom
+errorgeneric = Med shranjevanjem na Pocket je prišlo do napake.
+learnmore = Več o tem
+loginnow = Prijava
+maxtaglength = Oznake so omejene na 25 znakov
+mustbeconnected = Za shranjevanje na Pocket morate biti povezani na internet. Preverite povezavo in poskusite znova.
+onlylinkssaved = Shranite lahko samo povezave
+pagenotsaved = Stran ni bila shranjena
+pageremoved = Stran odstranjena
+pagesaved = Shranjeno na Pocket
+processingremove = Odstranjevanje strani …
+processingtags = Dodajanje oznak …
+removepage = Odstrani stran
+save = Shrani
+saving = Shranjevanje …
+signupemail = Registrirajte se z e-pošto
+signuptosave = Brezplačno se registrirajte na Pocketu.
+suggestedtags = Predlagane oznake
+tagline = Shranite članke in videe v Firefoxu in si jih oglejte na Pocketu iz katere koli naprave.
+taglinestory_one = Kliknite gumb Pocket v Firefoxu in shranite članek, video ali stran.
+taglinestory_two = Oglejte si v Pocketu na kateri koli napravi.
+tagssaved = Oznake dodane
+tos = Če nadaljujete, sprejemate <a href="%1$S" target="_blank">Pogoje uporabe</a> in <a href="%2$S" target="_blank">Politiko zasebnosti</a> storitve Pocket
+tryitnow = Preizkusite ga zdaj
+signinfirefox = Prijavite se s Firefoxom
+signupfirefox = Registrirajte se s Firefoxom
+viewlist = Ogled seznama
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Shrani v Pocket
+saveToPocketCmd.label = Shrani stran v Pocket
+saveToPocketCmd.accesskey = r
+saveLinkToPocketCmd.label = Shrani povezavo v Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pokaži seznam Pocket
diff --git a/browser/extensions/pocket/locale/sq/pocket.properties b/browser/extensions/pocket/locale/sq/pocket.properties
new file mode 100644
index 000000000..b8b17ad22
--- /dev/null
+++ b/browser/extensions/pocket/locale/sq/pocket.properties
@@ -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/.
+
+addtags = Shtoni Etiketa
+alreadyhaveacct = Jeni tashmë përdorues Pocket-i?
+continueff = Vazhdoni me Firefox-in
+errorgeneric = Pati një gabim teksa përpiqej të ruante te Pocket.
+learnmore = Mësoni më tepër
+loginnow = Hyni
+maxtaglength = Etiketat kufizohen deri në 25 shenja
+mustbeconnected = Që të ruani në Pocket, duhet të jeni i lidhur në Internet. Ju lutemi, kontrolloni lidhjen tuaj dhe riprovoni.
+onlylinkssaved = Mund të ruhen vetëm lidhje
+pagenotsaved = Faqja S’u Ruajt
+pageremoved = Faqja u Hoq
+pagesaved = U ruajt te Pocket
+processingremove = Po hiqet Faqja…
+processingtags = Po shtohen etiketa…
+removepage = Hiqe Faqen
+save = Ruaje
+saving = Po ruhet…
+signupemail = Regjistrohuni me email
+signuptosave = Regjistrohuni në Pocket. Është falas.
+suggestedtags = Etiketa të Këshilluara
+tagline = Ruani që nga Firefoxc-i artikuj dhe video për t’i parë në Pocket në çfarëdo pajisje, kurdo.
+taglinestory_one = Klikoni butonin Pocket që të ruani që nga Firefox-i çfarëdo artikulli, video ose faqe.
+taglinestory_two = Shihini në Pocket, në çfarëdo pajisje, kurdo.
+tagssaved = Etiketat u Shtuan
+tos = Duke vazhduar, pajtoheni me <a href="%1$S" target="_blank">Kushtet e Shërbimit</a> dhe <a href="%2$S" target="_blank">Rregullat e Privatësisë</a> për Pocket-in
+tryitnow = Provojeni Që Tani
+signinfirefox = Hyni me Firefox
+signupfirefox = Regjistrohuni me Firefox
+viewlist = Shihni Listën
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Ruajeni te Pocket
+saveToPocketCmd.label = Ruajeni Faqen te Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Ruajeni lidhjen te Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Shihni Listën te Pocket
diff --git a/browser/extensions/pocket/locale/sr/pocket.properties b/browser/extensions/pocket/locale/sr/pocket.properties
new file mode 100644
index 000000000..c3dbf2ece
--- /dev/null
+++ b/browser/extensions/pocket/locale/sr/pocket.properties
@@ -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/.
+
+addtags = Додај ознаку
+alreadyhaveacct = Већ сте Pocket корисник?
+continueff = Настави са Firefox-ом
+errorgeneric = Десила се грешка при покушају снимања у Pocket.
+learnmore = Сазнајте више
+loginnow = Пријава
+maxtaglength = Ознаке су ограничене на 25 карактера
+mustbeconnected = Морате бити повезани на интернет да бисте снимили у Pocket. Проверити везу и покушајте поново.
+onlylinkssaved = Само се везе могу снимити
+pagenotsaved = Страница није снимљена
+pageremoved = Страница уклоњена
+pagesaved = Снимљено у Pocket
+processingremove = Уклањам страницу…
+processingtags = Додајем ознаку…
+removepage = Уклони страницу
+save = Сними
+saving = Снимам…
+signupemail = Региструј се са е-поштом
+signuptosave = Региструјте се да користите Pocket. Бесплатно је.
+suggestedtags = Предложене ознаке
+tagline = Снимите чланке и видео снимке из Firefox-а да бисте их погледали у Pocket-у на било ком уређају било када.
+taglinestory_one = Кликните на Pocket дугме да бисте снимили чланак, видео или страницу из Firefox-а.
+taglinestory_two = Погледајте садржај у Pocket-у на било ком уређају било када.
+tagssaved = Ознаке додате
+tos = Настављањем прихватате <a href="%1$S" target="_blank">услове коришћења</a> и <a href="%2$S" target="_blank">полису приватности</a> Pocket-а
+tryitnow = Покушајте сада
+signinfirefox = Пријави се
+signupfirefox = Региструј се
+viewlist = Прикажи листу
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Сними у Pocket
+saveToPocketCmd.label = Сними страницу у Pocket
+saveToPocketCmd.accesskey = С
+saveLinkToPocketCmd.label = Сними везу у Pocket
+saveLinkToPocketCmd.accesskey = в
+pocketMenuitem.label = Прикажи Pocket листу
diff --git a/browser/extensions/pocket/locale/sv-SE/pocket.properties b/browser/extensions/pocket/locale/sv-SE/pocket.properties
new file mode 100644
index 000000000..e01ac7373
--- /dev/null
+++ b/browser/extensions/pocket/locale/sv-SE/pocket.properties
@@ -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/.
+
+addtags = Lägg till etiketter
+alreadyhaveacct = Redan en Pocket användare?
+continueff = Fortsätt med Firefox\u0020
+errorgeneric = Ett fel upptäcktes då du försökte spara till Pocket.
+learnmore = Läs mer
+loginnow = Logga in
+maxtaglength = Etiketter kan max vara 25 tecken
+mustbeconnected = Du måste vara ansluten till internet för att kunna spara till Pocket. Kontrollera din anslutning och försök igen.
+onlylinkssaved = Bara länkar kan sparas\u0020
+pagenotsaved = Sidan sparades inte\u0020
+pageremoved = Sidan borttagen
+pagesaved = Spara till Pocket\u0020
+processingremove = Tar bort sida…
+processingtags = Lägger till etiketter…
+removepage = Ta bort sida
+save = Spara
+saving = Sparar…
+signupemail = Registrera dig med din E-postadress
+signuptosave = Registrera dig för Pocket. Det är gratis.
+suggestedtags = Föreslagna etiketter
+tagline = Spara artiklar och videor från Firefox för att visa i Pocket på vilken enhet som helst, när som helst.
+taglinestory_one = Klicka på Pocket knappen för att spara vilken artikel, video eller sida som helst från Firefox.\u0020
+taglinestory_two = Visa i Pocket på vilken enhet som helst, när som helst.\u0020
+tagssaved = Etiketter Tillagda
+tos = Genom att fortsätta godkänner du Pocket's <a href="%1$S" target="_blank">användarvillkor</a> och <a href="%2$S" target="_blank">sekretesspolicy</a>
+tryitnow = Prova nu
+signinfirefox = Logga in med Firefox\u0020
+signupfirefox = Registrera dig med Firefox\u0020
+viewlist = Visa lista
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Spara till Pocket
+saveToPocketCmd.label = Spara sida till Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Spara Länk till Pocket
+saveLinkToPocketCmd.accesskey = l
+pocketMenuitem.label = Visa Pocket Lista\u0020
diff --git a/browser/extensions/pocket/locale/te/pocket.properties b/browser/extensions/pocket/locale/te/pocket.properties
new file mode 100644
index 000000000..2e9552793
--- /dev/null
+++ b/browser/extensions/pocket/locale/te/pocket.properties
@@ -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/.
+
+addtags = టాగ్‌లను జోడించు
+alreadyhaveacct = ఇప్పటికే ఒక పాకెట్ యూజర్?
+continueff = ఫైర్ఫాక్స్ తో కొనసాగించుము
+errorgeneric = పాకెట్ కు సేవ్ చేయడానికి ప్రయత్నిస్తున్నప్పుడు లోపం ఉంది.
+learnmore = మరింత తెలుసుకోండి
+loginnow = లాగ్ ఇన్
+maxtaglength = టాగ్లు 25 అక్షరాలకు పరిమితం చేయబడ్డాయి
+mustbeconnected = మీరు పాకెట్ కు సేవ్ చేయడానికి ఇంటర్నెట్ కనెక్ట్ చేయక తప్పదు. మీ కనెక్షన్ను తనిఖీ చేసి, మళ్ళీ ప్రయత్నించండి.
+onlylinkssaved = కేవలం లింకులు సేవ్ చేయవచ్చు
+pagenotsaved = పేజీ సేవ్ చేయబడలేదు
+pageremoved = పేజీ తీసివేయబడెను
+pagesaved = పాకెట్ కు సేవ్ చేయబడింది
+processingremove = పేజీని తొలగించు…
+processingtags = టాగ్లు జోడిస్తోంది...
+removepage = పేజీని తొలగించు
+save = సేవ్ చేయి
+saving = సేవ్ చేస్తోంది...
+signupemail = ఇమెయిల్ తో సైన్అప్ అవ్వండ్
+signuptosave = పాకెట్ కోసం సైన్ అప్ చేయండి. ఇది ఉచితం.
+suggestedtags = సూచించిన టాగ్లు
+tagline = ఏ పరికరం, ఏ సమయం లో పాకెట్ వీక్షించడానికి Firefox నుండి వ్యాసాలు మరియు వీడియోలను సేవ్ చేయవచ్చు.
+taglinestory_one = ఫైర్ఫాక్సు నుండి ఒక వ్యాసం, వీడియో లేదా పేజీ సేవ్ పాకెట్ బటన్ క్లిక్ చేయండి.
+taglinestory_two = ఏ పరికరంలో అయినా, ఏ సమయంలో అయినా పాకెట్ లో చూడండి.
+tagssaved = టాగ్లు చేర్చబడింది
+tos = కొనసాగించడం ద్వారా, మీరు పాకెట్ యొక్క <a href="%1$S" target="_blank"> సేవా నిబంధనలు</a> మరియు <a href="%2$S" target="_blank"> గోప్యతా విధానము</a> ను అంగీకరిచబడుతారు
+tryitnow = దీన్ని ఇప్పుడు ప్రయత్నించండి
+signinfirefox = ఫైర్ఫాక్సుకు ప్రవేశించండి
+signupfirefox = ఫైర్ఫాక్సుకు ప్రవేశించండి
+viewlist = జాబితాను చూడండి
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = పాకెట్
+pocket-button.tooltiptext = పాకెట్ కు సేవ్ చేయండి
+saveToPocketCmd.label = పాకెట్ కు సేవ్ చేయండి
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = పాకెట్ కు లింక్ ను సేవ్ చేయండి
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = పాకెట్ జాబితా చూడండి
diff --git a/browser/extensions/pocket/locale/th/pocket.properties b/browser/extensions/pocket/locale/th/pocket.properties
new file mode 100644
index 000000000..6cdf1c9ec
--- /dev/null
+++ b/browser/extensions/pocket/locale/th/pocket.properties
@@ -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/.
+
+addtags = เพิ่มป้ายกำกับ
+alreadyhaveacct = เป็นผู้ใช้ Pocket อยู่แล้ว?
+continueff = ดำเนินการต่อด้วย Firefox
+errorgeneric = เกิดข้อผิดพลาดระหว่างการบันทึกไปยัง Pocket
+learnmore = เรียนรู้เพิ่มเติม
+loginnow = เข้าสู่ระบบ
+maxtaglength = ป้ายกำกับถูกจำกัดไว้ที่ 25 ตัวอักษร
+mustbeconnected = คุณต้องเชื่อมต่อกับอินเทอร์เน็ตก่อนที่จะบันทึก Pocket โปรดตรวจสอบการเชื่อมต่อของคุณและลองอีกครั้ง
+onlylinkssaved = ลิงก์เท่านั้นที่สามารถถูกบันทึกได้
+pagenotsaved = หน้าไม่ถูกบันทึก
+pageremoved = ลบหน้าแล้ว
+pagesaved = บันทึกไปยัง Pocket
+processingremove = กำลังลบหน้า…
+processingtags = กำลังเพิ่มป้ายกำกับ…
+removepage = ลบหน้า
+save = บันทึก
+saving = กำลังบันทึก…
+signupemail = ลงทะเบียนด้วยอีเมล
+signuptosave = ไม่มีค่าใช้จ่ายในการลงทะเบียน Pocket
+suggestedtags = ป้ายกำกับที่ถูกแนะนำ
+tagline = บันทึกบทความและวิดีโอจาก Firefox เพื่อดูใน Pocket บนอุปกรณ์ต่าง ๆ เวลาไหนก็ได้
+taglinestory_one = คลิกปุ่ม Pocket เพื่อบันทึกบทความ วิดีโอ หรือหน้าจาก Firefox
+taglinestory_two = ดูใน Pocket บนอุปกรณ์ต่าง ๆ เวลาไหนก็ได้
+tagssaved = ป้ายกำกับถูกเพิ่มแล้ว
+tos = หากตกลง หมายความว่า คุณยอมรับ<a href="%1$S" target="_blank">เงื่อนไขการให้บริการ</a> และ<a href="%2$S" target="_blank">นโยบายความเป็นส่วนตัว</a>ของ Pocket
+tryitnow = ลองเลย
+signinfirefox = ลงชื่อเข้าด้วย Firefox
+signupfirefox = ลงทะเบียนกับ Firefox
+viewlist = ดูรายการ
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = บันทึกไปยัง Pocket
+saveToPocketCmd.label = บันทึกหน้าไปยัง Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = บันทึกลิงก์ไปยัง Pocket
+saveLinkToPocketCmd.accesskey = ป
+pocketMenuitem.label = ดูรายการ Pocket
diff --git a/browser/extensions/pocket/locale/tr/pocket.properties b/browser/extensions/pocket/locale/tr/pocket.properties
new file mode 100644
index 000000000..ea1970a7d
--- /dev/null
+++ b/browser/extensions/pocket/locale/tr/pocket.properties
@@ -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/.
+
+addtags = Etiket ekle
+alreadyhaveacct = Zaten Pocket kullanıcısı mısınız?
+continueff = Firefox ile devam et
+errorgeneric = Pocket’a kaydetmeye çalışırken bir hata oluştu.
+learnmore = Daha fazla bilgi al
+loginnow = Giriş yapın
+maxtaglength = Etiketler en fazla 25 karakter olabilir
+mustbeconnected = Pocket’a kaydetmek için internete bağlı olmalısınız. Lütfen bağlantınızı kontrol edip yeniden deneyin.
+onlylinkssaved = Yalnızca bağlantılar kaydedilebilir
+pagenotsaved = Sayfa kaydedilmedi
+pageremoved = Sayfa silindi
+pagesaved = Pocket’a kaydedildi
+processingremove = Sayfa siliniyor…
+processingtags = Etiketler ekleniyor…
+removepage = Sayfayı sil
+save = Kaydet
+saving = Kaydediliyor…
+signupemail = E-postayla kaydol
+signuptosave = Pocket’a kaydolun. Ücretsiz!
+suggestedtags = Önerilen etiketler
+tagline = İstediğiniz cihazda, istediğiniz zaman görmek istediğiniz yazı ve videoları Firefox’tan Pocket’a kaydedin.
+taglinestory_one = Firefox’ta istediğiniz yazıyı, videoyu veya sayfayı kaydetmek için Pocket düğmesine tıklayın.
+taglinestory_two = İstediğiniz cihazda, istediğiniz zaman Pocket’tan bakın.
+tagssaved = Etiketler eklendi
+tos = Devam ederseniz Pocket'ın <a href="%1$S" target="_blank">Kullanım Koşullarını</a> ve <a href="%2$S" target="_blank">Gizlilik İlkelerini</a> kabul etmiş sayılırsınız
+tryitnow = Hemen deneyin
+signinfirefox = Firefox ile giriş yap
+signupfirefox = Firefox ile kaydol
+viewlist = Listeyi göster
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Pocket’a kaydet
+saveToPocketCmd.label = Sayfayı Pocket’a kaydet
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = Bağlantıyı Pocket’a kaydet
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = Pocket listesini göster
diff --git a/browser/extensions/pocket/locale/uk/pocket.properties b/browser/extensions/pocket/locale/uk/pocket.properties
new file mode 100644
index 000000000..d15049b2c
--- /dev/null
+++ b/browser/extensions/pocket/locale/uk/pocket.properties
@@ -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/.
+
+addtags = Додати мітки
+alreadyhaveacct = Вже використовуєте Pocket?
+continueff = Продовжити з Firefox
+errorgeneric = При спробі збереження в Pocket сталася помилка.
+learnmore = Докладніше
+loginnow = Увійти
+maxtaglength = Мітки мають обмеження до 25 символів
+mustbeconnected = Для можливості збереження в Pocket ви повинні бути підключені до Інтернету. Перевірте своє з'єднання і спробуйте знову.
+onlylinkssaved = Можна зберігати лише посилання
+pagenotsaved = Сторінку не збережено
+pageremoved = Сторінку вилучено
+pagesaved = Збережено в Pocket
+processingremove = Вилучення сторінки…
+processingtags = Додавання міток…
+removepage = Вилучити сторінку
+save = Зберегти
+saving = Збереження…
+signupemail = Реєстрація за адресою електронної пошти
+signuptosave = Зареєструйтеся в Pocket. Це безплатно.
+suggestedtags = Пропоновані мітки
+tagline = Зберігайте статті та відео з Firefox, щоб переглядати їх в Pocket на будь-якому пристрої та в будь-який час.
+taglinestory_one = Натисніть кнопку Pocket для збереження будь-якої статті, відео чи сторінки з Firefox.
+taglinestory_two = Переглядайте в Pocket на будь-якому пристрої та в будь-який час.
+tagssaved = Мітки додано
+tos = Продовжуючи, ви погоджуєтесь з <a href="%1$S" target="_blank">Умовами використання</a> і <a href="%2$S" target="_blank">Політикою приватності</a>
+tryitnow = Спробувати зараз
+signinfirefox = Увійти через Firefox
+signupfirefox = Реєстрація через Firefox
+viewlist = Перегляд списку
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = Зберегти в Pocket
+saveToPocketCmd.label = Зберегти сторінку в Pocket
+saveToPocketCmd.accesskey = З
+saveLinkToPocketCmd.label = Зберегти посилання в Pocket
+saveLinkToPocketCmd.accesskey = п
+pocketMenuitem.label = Перегляд списку Pocket
diff --git a/browser/extensions/pocket/locale/zh-CN/pocket.properties b/browser/extensions/pocket/locale/zh-CN/pocket.properties
new file mode 100644
index 000000000..e913887b8
--- /dev/null
+++ b/browser/extensions/pocket/locale/zh-CN/pocket.properties
@@ -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/.
+
+addtags = 添加标签
+alreadyhaveacct = 已有 Pocket 账号?
+continueff = 使用 Firefox 继续
+errorgeneric = 尝试保存到 Pocket 时出错。
+learnmore = 详细了解
+loginnow = 登录
+maxtaglength = 标签不能超过 25 个字符
+mustbeconnected = 您必须已连接互联网才能保存到 Pocket。请检查您的连接,然后再试。
+onlylinkssaved = 只有链接能被保存
+pagenotsaved = 页面未保存
+pageremoved = 页面已移除
+pagesaved = 已保存到 Pocket
+processingremove = 正在移除页面…
+processingtags = 正在添加标签…
+removepage = 移除页面
+save = 保存
+saving = 正在保存…
+signupemail = 通过电子邮件注册
+signuptosave = 免费注册 Pocket。
+suggestedtags = 推荐标签
+tagline = 在 Firefox 上保存文章和视频,以供在任何时间、任何设备上用 Pocket 访问。
+taglinestory_one = 点击 Pocket 按钮保存 Firefox 上的任何文章、视频或页面。
+taglinestory_two = 在任何时间、任何设备上的 Pocket 中查看。
+tagssaved = 标签已添加
+tos = 继续则表示您同意 Pocket 的<a href="%1$S" target="_blank">服务条款</a>和<a href="%2$S" target="_blank">隐私政策</a>
+tryitnow = 立即尝试
+signinfirefox = 使用 Firefox 登录
+signupfirefox = 使用 Firefox 注册
+viewlist = 查看列表
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = 保存到 Pocket
+saveToPocketCmd.label = 保存页面到 Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = 保存链接到 Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = 查看 Pocket 列表
diff --git a/browser/extensions/pocket/locale/zh-TW/pocket.properties b/browser/extensions/pocket/locale/zh-TW/pocket.properties
new file mode 100644
index 000000000..02cf5223b
--- /dev/null
+++ b/browser/extensions/pocket/locale/zh-TW/pocket.properties
@@ -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/.
+
+addtags = 新增標籤
+alreadyhaveacct = 已經是 Pocket 使用者了嗎?
+continueff = 使用 Firefox 繼續
+errorgeneric = 嘗試儲存至 Pocket 時發生錯誤。
+learnmore = 更多資訊
+loginnow = 登入
+maxtaglength = 標籤僅能有 25 字元
+mustbeconnected = 您必須連線至網際網路才能儲存至 Pocket。請檢查您的連線狀態後再試一次。
+onlylinkssaved = 僅能儲存鏈結
+pagenotsaved = 未儲存頁面
+pageremoved = 已移除頁面
+pagesaved = 已儲存至 Pocket
+processingremove = 正在移除頁面…
+processingtags = 正在新增標籤…
+removepage = 移除頁面
+save = 儲存
+saving = 儲存中…
+signupemail = 使用電子郵件地址註冊
+signuptosave = 免費註冊 Pocket 帳號。
+suggestedtags = 建議的標籤
+tagline = 隨時隨地在任何裝置上的 Firefox 來儲存文章與影片,稍後再用 Pocket 開啟。
+taglinestory_one = 在 Firefox 中點擊 Pocket 按鈕來儲存任何文章、影片或網頁。
+taglinestory_two = 隨時隨地在任何裝置上用 Pocket 檢視。
+tagssaved = 已新增標籤
+tos = 繼續使用就代表您同意 Pocket 的 <a href="%1$S" target="_blank">服務條款</a> 及 <a href="%2$S" target="_blank">隱私權保護政策</a>
+tryitnow = 立刻試試
+signinfirefox = 使用 Firefox 登入
+signupfirefox = 使用 Firefox 註冊
+viewlist = 檢視清單
+
+# LOCALIZATION NOTE(pocket-button.label, pocket-button.tooltiptext, saveToPocketCmd.label, saveLinkToPocketCmd.label, pocketMenuitem.label):
+# "Pocket" is a brand name.
+pocket-button.label = Pocket
+pocket-button.tooltiptext = 儲存至 Pocket
+saveToPocketCmd.label = 將頁面儲存至 Pocket
+saveToPocketCmd.accesskey = k
+saveLinkToPocketCmd.label = 將鏈結儲存至 Pocket
+saveLinkToPocketCmd.accesskey = o
+pocketMenuitem.label = 檢視 Pocket 清單
diff --git a/browser/extensions/pocket/moz.build b/browser/extensions/pocket/moz.build
new file mode 100644
index 000000000..495e48a62
--- /dev/null
+++ b/browser/extensions/pocket/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+DIRS += ['locale']
+
+FINAL_TARGET_FILES.features['firefox@getpocket.com'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['firefox@getpocket.com'] += [
+ 'install.rdf.in'
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/extensions/pocket/skin/linux/Toolbar-inverted.png b/browser/extensions/pocket/skin/linux/Toolbar-inverted.png
new file mode 100644
index 000000000..68f5125ea
--- /dev/null
+++ b/browser/extensions/pocket/skin/linux/Toolbar-inverted.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/linux/Toolbar-inverted@2x.png b/browser/extensions/pocket/skin/linux/Toolbar-inverted@2x.png
new file mode 100644
index 000000000..89056295c
--- /dev/null
+++ b/browser/extensions/pocket/skin/linux/Toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/linux/Toolbar.png b/browser/extensions/pocket/skin/linux/Toolbar.png
new file mode 100644
index 000000000..be9b0e6d2
--- /dev/null
+++ b/browser/extensions/pocket/skin/linux/Toolbar.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/linux/Toolbar@2x.png b/browser/extensions/pocket/skin/linux/Toolbar@2x.png
new file mode 100644
index 000000000..5dc4e754a
--- /dev/null
+++ b/browser/extensions/pocket/skin/linux/Toolbar@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/linux/menuPanel.png b/browser/extensions/pocket/skin/linux/menuPanel.png
new file mode 100644
index 000000000..55bc46cc9
--- /dev/null
+++ b/browser/extensions/pocket/skin/linux/menuPanel.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/linux/menuPanel@2x.png b/browser/extensions/pocket/skin/linux/menuPanel@2x.png
new file mode 100644
index 000000000..ad13ca4ce
--- /dev/null
+++ b/browser/extensions/pocket/skin/linux/menuPanel@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/linux/pocket.css b/browser/extensions/pocket/skin/linux/pocket.css
new file mode 100644
index 000000000..156964923
--- /dev/null
+++ b/browser/extensions/pocket/skin/linux/pocket.css
@@ -0,0 +1,10 @@
+@import url("chrome://pocket-shared/skin/pocket.css");
+
+#nav-bar #pocket-button > .toolbarbutton-icon {
+ padding: 2px 6px;
+}
+
+:-moz-any(#TabsToolbar, .widget-overflow-list) #pocket-button > .toolbarbutton-icon {
+ max-width: 18px;
+ padding: 0;
+} \ No newline at end of file
diff --git a/browser/extensions/pocket/skin/osx/Toolbar-inverted.png b/browser/extensions/pocket/skin/osx/Toolbar-inverted.png
new file mode 100644
index 000000000..0f4d57b4e
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/Toolbar-inverted.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/Toolbar-inverted@2x.png b/browser/extensions/pocket/skin/osx/Toolbar-inverted@2x.png
new file mode 100644
index 000000000..03c3b42d5
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/Toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/Toolbar-yosemite.png b/browser/extensions/pocket/skin/osx/Toolbar-yosemite.png
new file mode 100644
index 000000000..28602b186
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/Toolbar-yosemite.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/Toolbar-yosemite@2x.png b/browser/extensions/pocket/skin/osx/Toolbar-yosemite@2x.png
new file mode 100644
index 000000000..53040cb46
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/Toolbar-yosemite@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/Toolbar.png b/browser/extensions/pocket/skin/osx/Toolbar.png
new file mode 100644
index 000000000..1a8d7a51c
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/Toolbar.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/Toolbar@2x.png b/browser/extensions/pocket/skin/osx/Toolbar@2x.png
new file mode 100644
index 000000000..91bf3ef6f
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/Toolbar@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/menuPanel-yosemite.png b/browser/extensions/pocket/skin/osx/menuPanel-yosemite.png
new file mode 100644
index 000000000..30b475cc0
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/menuPanel-yosemite.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/menuPanel-yosemite@2x.png b/browser/extensions/pocket/skin/osx/menuPanel-yosemite@2x.png
new file mode 100644
index 000000000..389d96ec7
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/menuPanel-yosemite@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/menuPanel.png b/browser/extensions/pocket/skin/osx/menuPanel.png
new file mode 100644
index 000000000..d2bf61888
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/menuPanel.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/menuPanel@2x.png b/browser/extensions/pocket/skin/osx/menuPanel@2x.png
new file mode 100644
index 000000000..f58afbc4d
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/menuPanel@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/osx/pocket.css b/browser/extensions/pocket/skin/osx/pocket.css
new file mode 100644
index 000000000..534a881a5
--- /dev/null
+++ b/browser/extensions/pocket/skin/osx/pocket.css
@@ -0,0 +1,42 @@
+@import url("chrome://pocket-shared/skin/pocket.css");
+
+#pocket-button[cui-areatype="toolbar"] > .toolbarbutton-icon {
+ max-width: 18px;
+ margin: 0;
+}
+
+#pocket-button[cui-areatype="toolbar"][open] {
+ -moz-image-region: rect(36px, 18px, 54px, 0);
+}
+
+@media (min-resolution: 2dppx) {
+ #panelMenu_pocket,
+ #menu_pocket,
+ #BMB_pocket {
+ list-style-image: url("chrome://pocket/content/panels/img/pocketmenuitem16@2x.png");
+ }
+
+ #panelMenu_pocket > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+@media not all and (min-resolution: 1.1dppx) {
+ #pocket-button:hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"]) {
+ -moz-image-region: rect(18px, 18px, 36px, 0);
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ #pocket-button[cui-areatype="toolbar"][open] {
+ -moz-image-region: rect(72px, 36px, 108px, 0);
+ }
+
+ #pocket-button:hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"]) {
+ -moz-image-region: rect(36px, 36px, 72px, 0);
+ }
+}
+
+#PanelUI-pocketView[mainview=true] > .panel-subview-body > #pocket-panel-iframe {
+ border-radius: var(--arrowpanel-border-radius);
+}
diff --git a/browser/extensions/pocket/skin/shared/pocket.css b/browser/extensions/pocket/skin/shared/pocket.css
new file mode 100644
index 000000000..ec46e1890
--- /dev/null
+++ b/browser/extensions/pocket/skin/shared/pocket.css
@@ -0,0 +1,79 @@
+/* Bug 1164419 - increase Pocket panel size to accomidate wider Russian text. */
+panelmultiview[mainViewId=PanelUI-pocketView] > .panel-viewcontainer > .panel-viewstack > .panel-mainview:not([panelid="PanelUI-popup"]) {
+ max-width: 33em; /* standaloneSubviewWidth + 3 */
+}
+
+.cui-widget-panel[viewId="PanelUI-pocketView"] > .panel-arrowcontainer > .panel-arrowcontent {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+#PanelUI-pocketView > .panel-subview-body,
+#PanelUI-pocketView {
+ overflow: visible;
+}
+
+#pocket-button {
+ list-style-image: url("chrome://pocket/skin/Toolbar.png");
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+toolbar[brighttext] #pocket-button {
+ list-style-image: url(chrome://pocket/skin/Toolbar-inverted.png);
+}
+
+@media not all and (min-resolution: 1.1dppx) {
+ #pocket-button[cui-areatype="menu-panel"],
+ toolbarpaletteitem[place="palette"] > #pocket-button {
+ list-style-image: url(chrome://pocket/skin/menuPanel.png);
+ -moz-image-region: rect(0, 32px, 32px, 0);
+ }
+
+ #pocket-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
+ -moz-image-region: rect(32px, 32px, 64px, 0);
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ #pocket-button[cui-areatype="menu-panel"],
+ toolbarpaletteitem[place="palette"] > #pocket-button {
+ list-style-image: url(chrome://pocket/skin/menuPanel@2x.png);
+ -moz-image-region: rect(0px, 64px, 64px, 0);
+ }
+
+ #pocket-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
+ -moz-image-region: rect(64px, 64px, 128px, 0);
+ }
+}
+
+#pocket-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+#pocket-button[cui-areatype="toolbar"][open] {
+ -moz-image-region: rect(18px, 18px, 36px, 0);
+}
+
+@media (min-resolution: 1.1dppx) {
+ #pocket-button {
+ list-style-image: url("chrome://pocket/skin/Toolbar@2x.png");
+ }
+
+ toolbar[brighttext] #pocket-button {
+ list-style-image: url("chrome://pocket/skin/Toolbar-inverted@2x.png");
+ }
+
+ #pocket-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 36px, 36px, 0px);
+ }
+
+ #pocket-button[cui-areatype="toolbar"][open] {
+ -moz-image-region: rect(36px, 36px, 72px, 0px);
+ }
+}
+
+#panelMenu_pocket,
+#menu_pocket,
+#BMB_pocket {
+ list-style-image: url("chrome://pocket/content/panels/img/pocketmenuitem16.png");
+}
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-XP.png b/browser/extensions/pocket/skin/windows/Toolbar-XP.png
new file mode 100644
index 000000000..9220a207f
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-XP.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-aero.png b/browser/extensions/pocket/skin/windows/Toolbar-aero.png
new file mode 100644
index 000000000..7b373acb9
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-aero.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-aero@2x.png b/browser/extensions/pocket/skin/windows/Toolbar-aero@2x.png
new file mode 100644
index 000000000..6e83dcc60
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-aero@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-inverted.png b/browser/extensions/pocket/skin/windows/Toolbar-inverted.png
new file mode 100644
index 000000000..70e95f464
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-inverted.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-inverted@2x.png b/browser/extensions/pocket/skin/windows/Toolbar-inverted@2x.png
new file mode 100644
index 000000000..ba0b7e2af
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-lunaSilver.png b/browser/extensions/pocket/skin/windows/Toolbar-lunaSilver.png
new file mode 100644
index 000000000..5afe7ca44
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-lunaSilver.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-win8.png b/browser/extensions/pocket/skin/windows/Toolbar-win8.png
new file mode 100644
index 000000000..208dc03ae
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-win8.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar-win8@2x.png b/browser/extensions/pocket/skin/windows/Toolbar-win8@2x.png
new file mode 100644
index 000000000..3704f0885
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar-win8@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar.png b/browser/extensions/pocket/skin/windows/Toolbar.png
new file mode 100644
index 000000000..4801961f8
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/Toolbar@2x.png b/browser/extensions/pocket/skin/windows/Toolbar@2x.png
new file mode 100644
index 000000000..f3a1cc738
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/Toolbar@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/menuPanel-aero.png b/browser/extensions/pocket/skin/windows/menuPanel-aero.png
new file mode 100644
index 000000000..459e82634
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/menuPanel-aero.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/menuPanel-aero@2x.png b/browser/extensions/pocket/skin/windows/menuPanel-aero@2x.png
new file mode 100644
index 000000000..eef525741
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/menuPanel-aero@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/menuPanel.png b/browser/extensions/pocket/skin/windows/menuPanel.png
new file mode 100644
index 000000000..55bc46cc9
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/menuPanel.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/menuPanel@2x.png b/browser/extensions/pocket/skin/windows/menuPanel@2x.png
new file mode 100644
index 000000000..ad13ca4ce
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/menuPanel@2x.png
Binary files differ
diff --git a/browser/extensions/pocket/skin/windows/pocket.css b/browser/extensions/pocket/skin/windows/pocket.css
new file mode 100644
index 000000000..011b821a9
--- /dev/null
+++ b/browser/extensions/pocket/skin/windows/pocket.css
@@ -0,0 +1,16 @@
+@import url("chrome://pocket-shared/skin/pocket.css");
+
+#nav-bar #pocket-button > .toolbarbutton-icon {
+ padding: calc(var(--toolbarbutton-vertical-inner-padding)) 6px;
+}
+
+:-moz-any(#TabsToolbar, .widget-overflow-list) #pocket-button > .toolbarbutton-icon {
+ max-width: 18px;
+ padding: 0;
+}
+
+@media (-moz-windows-theme: luna-silver) and (max-resolution: 1dppx) {
+ #pocket-button {
+ list-style-image: url(Toolbar-lunaSilver.png);
+ }
+}
diff --git a/browser/extensions/pocket/test/.eslintrc.js b/browser/extensions/pocket/test/.eslintrc.js
new file mode 100644
index 000000000..c764b133d
--- /dev/null
+++ b/browser/extensions/pocket/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/extensions/pocket/test/browser.ini b/browser/extensions/pocket/test/browser.ini
new file mode 100644
index 000000000..3e0be8736
--- /dev/null
+++ b/browser/extensions/pocket/test/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ head.js
+ test.html
+
+[browser_pocket_ui_check.js]
diff --git a/browser/extensions/pocket/test/browser_pocket_ui_check.js b/browser/extensions/pocket/test/browser_pocket_ui_check.js
new file mode 100644
index 000000000..12aeaffd6
--- /dev/null
+++ b/browser/extensions/pocket/test/browser_pocket_ui_check.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function checkWindowProperties(expectPresent, l) {
+ for (let name of l) {
+ is(!!window.hasOwnProperty(name), expectPresent, "property " + name + (expectPresent ? " is" : " is not") + " present");
+ }
+}
+function checkElements(expectPresent, l) {
+ for (let id of l) {
+ is(!!document.getElementById(id), expectPresent, "element " + id + (expectPresent ? " is" : " is not") + " present");
+ }
+}
+
+add_task(function* test_setup() {
+ let clearValue = Services.prefs.prefHasUserValue("extensions.pocket.enabled");
+ let enabledOnStartup = Services.prefs.getBoolPref("extensions.pocket.enabled");
+ registerCleanupFunction(() => {
+ if (clearValue) {
+ Services.prefs.clearUserPref("extensions.pocket.enabled");
+ } else {
+ Services.prefs.setBoolPref("extensions.pocket.enabled", enabledOnStartup);
+ }
+ });
+});
+
+add_task(function*() {
+ yield promisePocketEnabled();
+
+ checkWindowProperties(true, ["Pocket", "pktUI", "pktUIMessaging"]);
+ checkElements(true, ["pocket-button", "panelMenu_pocket", "menu_pocket", "BMB_pocket",
+ "panelMenu_pocketSeparator", "menu_pocketSeparator",
+ "BMB_pocketSeparator"]);
+
+ // check context menu exists
+ info("checking content context menu");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/browser/browser/extensions/pocket/test/test.html");
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ let popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ yield BrowserTestUtils.synthesizeMouseAtCenter("body", {
+ type: "contextmenu",
+ button: 2
+ }, tab.linkedBrowser);
+ yield popupShown;
+
+ checkElements(true, ["context-pocket", "context-savelinktopocket"]);
+
+ contextMenu.hidePopup();
+ yield popupHidden;
+ yield BrowserTestUtils.removeTab(tab);
+
+ yield promisePocketDisabled();
+
+ checkWindowProperties(false, ["Pocket", "pktUI", "pktUIMessaging"]);
+ checkElements(false, ["pocket-button", "panelMenu_pocket", "menu_pocket", "BMB_pocket",
+ "panelMenu_pocketSeparator", "menu_pocketSeparator",
+ "BMB_pocketSeparator", "context-pocket", "context-savelinktopocket"]);
+
+ yield promisePocketReset();
+});
diff --git a/browser/extensions/pocket/test/head.js b/browser/extensions/pocket/test/head.js
new file mode 100644
index 000000000..e044a42c7
--- /dev/null
+++ b/browser/extensions/pocket/test/head.js
@@ -0,0 +1,67 @@
+// Currently Pocket is disabled in tests. We want these tests to work under
+// either case that Pocket is disabled or enabled on startup of the browser,
+// and that at the end we're reset to the correct state.
+let enabledOnStartup = false;
+
+// PocketEnabled/Disabled promises return true if it was already
+// Enabled/Disabled, and false if it need to Enable/Disable.
+function promisePocketEnabled() {
+ if (Services.prefs.getPrefType("extensions.pocket.enabled") != Services.prefs.PREF_INVALID &&
+ Services.prefs.getBoolPref("extensions.pocket.enabled")) {
+ info( "pocket was already enabled, assuming enabled by default for tests");
+ enabledOnStartup = true;
+ return Promise.resolve(true);
+ }
+ info( "pocket is not enabled");
+ return new Promise((resolve, reject) => {
+ let listener = {
+ onWidgetAfterCreation(widgetid) {
+ if (widgetid == "pocket-button") {
+ info("pocket-button created");
+ CustomizableUI.removeListener(listener);
+ resolve(false);
+ }
+ }
+ }
+ CustomizableUI.addListener(listener);
+ Services.prefs.setBoolPref("extensions.pocket.enabled", true);
+ });
+}
+
+function promisePocketDisabled() {
+ if (Services.prefs.getPrefType("extensions.pocket.enabled") == Services.prefs.PREF_INVALID ||
+ !Services.prefs.getBoolPref("extensions.pocket.enabled")) {
+ info("pocket-button already disabled");
+ return Promise.resolve(true);
+ }
+ return new Promise((resolve, reject) => {
+ let listener = {
+ onWidgetDestroyed: function(widgetid) {
+ if (widgetid == "pocket-button") {
+ CustomizableUI.removeListener(listener);
+ info( "pocket-button destroyed");
+ // wait for a full unload of pocket
+ BrowserTestUtils.waitForCondition(() => {
+ return !window.hasOwnProperty("pktUI");
+ }, "pocket properties removed from window").then(() => {
+ resolve(false);
+ })
+ }
+ }
+ }
+ CustomizableUI.addListener(listener);
+ info("reset pocket enabled pref");
+ // testing/profiles/prefs_general.js uses user_pref to disable pocket, set
+ // back to false.
+ Services.prefs.setBoolPref("extensions.pocket.enabled", false);
+ });
+}
+
+function promisePocketReset() {
+ if (enabledOnStartup) {
+ info("reset is enabling pocket addon");
+ return promisePocketEnabled();
+ }
+ info("reset is disabling pocket addon");
+ return promisePocketDisabled();
+}
diff --git a/browser/extensions/pocket/test/test.html b/browser/extensions/pocket/test/test.html
new file mode 100644
index 000000000..aa08cd566
--- /dev/null
+++ b/browser/extensions/pocket/test/test.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+ <title>Page Title</title>
+ <meta charset="utf-8" />
+</head>
+
+<body>
+</body>
+</html>
diff --git a/browser/extensions/webcompat/bootstrap.js b/browser/extensions/webcompat/bootstrap.js
new file mode 100644
index 000000000..19141a0f5
--- /dev/null
+++ b/browser/extensions/webcompat/bootstrap.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";
+function startup() {}
+function shutdown() {}
+function install() {}
+function uninstall() {}
diff --git a/browser/extensions/webcompat/install.rdf.in b/browser/extensions/webcompat/install.rdf.in
new file mode 100644
index 000000000..2c4406f25
--- /dev/null
+++ b/browser/extensions/webcompat/install.rdf.in
@@ -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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>webcompat@mozilla.org</em:id>
+ <em:version>1.0</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Target Application this extension can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Web Compat</em:name>
+ <em:description>Urgent post-release fixes for web compatibility.</em:description>
+ </Description>
+</RDF>
diff --git a/browser/extensions/webcompat/moz.build b/browser/extensions/webcompat/moz.build
new file mode 100644
index 000000000..2423511b4
--- /dev/null
+++ b/browser/extensions/webcompat/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['webcompat@mozilla.org'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['webcompat@mozilla.org'] += [
+ 'install.rdf.in'
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
diff --git a/browser/extensions/webcompat/test/browser/.eslintrc.js b/browser/extensions/webcompat/test/browser/.eslintrc.js
new file mode 100644
index 000000000..7c8021192
--- /dev/null
+++ b/browser/extensions/webcompat/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/extensions/webcompat/test/browser/browser.ini b/browser/extensions/webcompat/test/browser/browser.ini
new file mode 100644
index 000000000..500224636
--- /dev/null
+++ b/browser/extensions/webcompat/test/browser/browser.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[browser_check_installed.js]
diff --git a/browser/extensions/webcompat/test/browser/browser_check_installed.js b/browser/extensions/webcompat/test/browser/browser_check_installed.js
new file mode 100644
index 000000000..b77458054
--- /dev/null
+++ b/browser/extensions/webcompat/test/browser/browser_check_installed.js
@@ -0,0 +1,13 @@
+"use strict";
+
+add_task(function* test_enabled() {
+ let addon = yield new Promise(
+ resolve => AddonManager.getAddonByID("webcompat@mozilla.org", resolve)
+ );
+ isnot(addon, null, "Check addon exists");
+ is(addon.version, "1.0", "Check version");
+ is(addon.name, "Web Compat", "Check name");
+ ok(addon.isCompatible, "Check application compatibility");
+ ok(!addon.appDisabled, "Check not app disabled");
+ ok(addon.isActive, "Check addon is active");
+});
diff --git a/browser/fonts/EmojiOneMozilla.ttf b/browser/fonts/EmojiOneMozilla.ttf
new file mode 100644
index 000000000..50356509d
--- /dev/null
+++ b/browser/fonts/EmojiOneMozilla.ttf
Binary files differ
diff --git a/browser/fonts/README.txt b/browser/fonts/README.txt
new file mode 100644
index 000000000..188ea3fff
--- /dev/null
+++ b/browser/fonts/README.txt
@@ -0,0 +1,9 @@
+EmojiOne Mozilla
+================
+
+The upstream repository of EmojiOne Mozilla can be found at
+
+ https://github.com/mozilla/emojione-colr
+
+Please refer commit history for the current version of the font.
+This file purposely omit the version, so there is no need to update it here.
diff --git a/browser/fonts/moz.build b/browser/fonts/moz.build
new file mode 100644
index 000000000..b1a43e528
--- /dev/null
+++ b/browser/fonts/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['OS_ARCH'] in ('WINNT', 'Linux'):
+ DIST_SUBDIR = ''
+ FINAL_TARGET_FILES.fonts += [
+ 'EmojiOneMozilla.ttf'
+ ]
diff --git a/browser/installer/Makefile.in b/browser/installer/Makefile.in
new file mode 100644
index 000000000..55df797ef
--- /dev/null
+++ b/browser/installer/Makefile.in
@@ -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/.
+
+STANDALONE_MAKEFILE := 1
+DIST_SUBDIR := browser
+
+include $(topsrcdir)/config/rules.mk
+
+MOZ_PKG_REMOVALS = $(srcdir)/removed-files.in
+
+MOZ_PKG_MANIFEST = $(srcdir)/package-manifest.in
+MOZ_PKG_DUPEFLAGS = -f $(srcdir)/allowed-dupes.mn
+
+# Some files have been already bundled with xulrunner
+ifndef MOZ_MULET
+MOZ_PKG_FATAL_WARNINGS = 1
+else
+DEFINES += -DMOZ_MULET
+endif
+
+# When packaging an artifact build not all xpt files expected by the
+# packager will be present.
+ifdef MOZ_ARTIFACT_BUILDS
+MOZ_PKG_FATAL_WARNINGS =
+endif
+
+DEFINES += -DMOZ_APP_NAME=$(MOZ_APP_NAME) -DPREF_DIR=$(PREF_DIR)
+
+ifdef MOZ_DEBUG
+DEFINES += -DMOZ_DEBUG=1
+endif
+
+ifneq (,$(filter gtk%,$(MOZ_WIDGET_TOOLKIT)))
+DEFINES += -DMOZ_GTK=1
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk3)
+DEFINES += -DMOZ_GTK3=1
+endif
+endif
+
+ifdef MOZ_SYSTEM_NSPR
+DEFINES += -DMOZ_SYSTEM_NSPR=1
+endif
+
+ifdef MOZ_SYSTEM_NSS
+DEFINES += -DMOZ_SYSTEM_NSS=1
+endif
+
+ifdef NSS_DISABLE_DBM
+DEFINES += -DNSS_DISABLE_DBM=1
+endif
+
+ifdef MOZ_ARTIFACT_BUILDS
+DEFINES += -DMOZ_ARTIFACT_BUILDS=1
+endif
+
+DEFINES += -DJAREXT=
+
+ifdef MOZ_ANGLE_RENDERER
+DEFINES += -DMOZ_ANGLE_RENDERER=$(MOZ_ANGLE_RENDERER)
+ifdef MOZ_D3DCOMPILER_VISTA_DLL
+DEFINES += -DMOZ_D3DCOMPILER_VISTA_DLL=$(MOZ_D3DCOMPILER_VISTA_DLL)
+endif
+ifdef MOZ_D3DCOMPILER_XP_DLL
+DEFINES += -DMOZ_D3DCOMPILER_XP_DLL=$(MOZ_D3DCOMPILER_XP_DLL)
+endif
+endif
+
+DEFINES += -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME)
+
+# Set MSVC dlls version to package, if any.
+ifdef MOZ_NO_DEBUG_RTL
+ifdef WIN32_REDIST_DIR
+DEFINES += -DMOZ_PACKAGE_MSVC_DLLS=1
+DEFINES += -DMSVC_C_RUNTIME_DLL=$(MSVC_C_RUNTIME_DLL)
+DEFINES += -DMSVC_CXX_RUNTIME_DLL=$(MSVC_CXX_RUNTIME_DLL)
+endif
+ifdef WIN_UCRT_REDIST_DIR
+DEFINES += -DMOZ_PACKAGE_WIN_UCRT_DLLS=1
+endif
+endif
+
+ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
+DEFINES += -DMOZ_SHARED_MOZGLUE=1
+endif
+
+ifdef NECKO_WIFI
+DEFINES += -DNECKO_WIFI
+endif
+
+ifdef MAKENSISU
+DEFINES += -DHAVE_MAKENSISU=1
+endif
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+MOZ_PKG_MAC_DSSTORE=branding/dsstore
+MOZ_PKG_MAC_BACKGROUND=branding/background.png
+MOZ_PKG_MAC_ICON=branding/disk.icns
+MOZ_PKG_MAC_EXTRA=--symlink '/Applications:/ '
+endif
+
+INSTALL_SDK = 1
+
+include $(topsrcdir)/toolkit/mozapps/installer/signing.mk
+include $(topsrcdir)/toolkit/mozapps/installer/packager.mk
+
+ifeq (bundle, $(MOZ_FS_LAYOUT))
+BINPATH = $(_BINPATH)
+DEFINES += -DAPPNAME=$(_APPNAME)
+else
+# Every other platform just winds up in dist/bin
+BINPATH = bin
+endif
+DEFINES += -DBINPATH=$(BINPATH)
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+RESPATH = $(_APPNAME)/Contents/Resources
+else
+RESPATH = $(BINPATH)
+endif
+DEFINES += -DRESPATH=$(RESPATH)
+
+LPROJ_ROOT = $(firstword $(subst -, ,$(AB_CD)))
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+ifeq (zh-TW,$(AB_CD))
+LPROJ_ROOT := $(subst -,_,$(AB_CD))
+endif
+endif
+DEFINES += -DLPROJ_ROOT=$(LPROJ_ROOT)
+
+DEFINES += -DMOZ_ICU_VERSION=$(MOZ_ICU_VERSION)
+ifdef MOZ_SYSTEM_ICU
+DEFINES += -DMOZ_SYSTEM_ICU
+endif
+ifdef MOZ_ICU_DATA_ARCHIVE
+DEFINES += -DMOZ_ICU_DATA_ARCHIVE
+endif
+DEFINES += -DMOZ_ICU_DBG_SUFFIX=$(MOZ_ICU_DBG_SUFFIX)
+DEFINES += -DICU_DATA_FILE=$(ICU_DATA_FILE)
+ifdef CLANG_CXX
+DEFINES += -DCLANG_CXX
+endif
+ifdef CLANG_CL
+DEFINES += -DCLANG_CL
+endif
+ifeq (x86,$(CPU_ARCH))
+ifdef _MSC_VER
+ifndef CLANG_CL
+DEFINES += -DWOW_HELPER
+endif
+endif
+endif
+
+
+# Builds using the hybrid FasterMake/RecursiveMake backend will
+# fail to produce a langpack. See bug 1255096.
+libs::
+ifeq (,$(filter FasterMake+RecursiveMake,$(BUILD_BACKENDS)))
+ $(MAKE) -C $(DEPTH)/browser/locales langpack
+endif
+
+ifeq (WINNT,$(OS_ARCH))
+PKGCOMP_FIND_OPTS =
+else
+PKGCOMP_FIND_OPTS = -L
+endif
+ifeq (Darwin, $(OS_ARCH))
+FINDPATH = $(_APPNAME)/Contents/MacOS
+else
+FINDPATH=bin
+endif
+
+package-compare::
+ cd $(DIST); find $(PKGCOMP_FIND_OPTS) $(FINDPATH) -type f | sort > bin-list.txt
+ $(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) $(MOZ_PKG_MANIFEST)) | grep '^$(BINPATH)' | sed -e 's/^\///' | sort > $(DIST)/pack-list.txt
+ -diff -u $(DIST)/pack-list.txt $(DIST)/bin-list.txt
+ rm -f $(DIST)/pack-list.txt $(DIST)/bin-list.txt
+
+installer::
+ifdef INSTALLER_DIR
+ $(MAKE) -C $(INSTALLER_DIR)
+endif
+
+ifdef ENABLE_MARIONETTE
+DEFINES += -DENABLE_MARIONETTE=1
+endif
diff --git a/browser/installer/allowed-dupes.mn b/browser/installer/allowed-dupes.mn
new file mode 100644
index 000000000..2ea55aff4
--- /dev/null
+++ b/browser/installer/allowed-dupes.mn
@@ -0,0 +1,228 @@
+# Known duplicate files
+# This file is ideally removed, but some existing files will be grandfathered in
+# See bug 1303184
+#
+# PLEASE DO NOT ADD MORE EXCEPTIONS TO THIS LIST
+#
+
+# updater on osx is bug 1311194
+LaunchServices/org.mozilla.updater
+updater.app/Contents/MacOS/org.mozilla.updater
+updater.app/Contents/PkgInfo
+browser/chrome.manifest
+# browser branding / themes is bug 1313106
+browser/chrome/browser/content/branding/icon128.png
+browser/chrome/browser/content/branding/icon16.png
+browser/chrome/browser/content/branding/icon32.png
+browser/chrome/browser/content/branding/icon48.png
+browser/chrome/browser/content/browser/defaultthemes/5.footer.png
+browser/chrome/browser/content/browser/defaultthemes/5.header.png
+browser/chrome/browser/content/browser/extension.svg
+browser/chrome/browser/content/browser/places/bookmarkProperties.xul
+browser/chrome/browser/content/browser/places/bookmarkProperties2.xul
+browser/chrome/browser/skin/classic/browser/addons/addon-install-confirm.svg
+browser/chrome/browser/skin/classic/browser/connection-secure.svg
+browser/chrome/browser/skin/classic/browser/controlcenter/warning-gray.svg
+browser/chrome/browser/skin/classic/browser/newtab/close.png
+browser/chrome/browser/skin/classic/browser/theme-switcher-icon.png
+# devtools reduction is bug 1311178
+browser/chrome/devtools/content/dom/content/dom-view.css
+browser/chrome/devtools/content/dom/dom.html
+browser/chrome/devtools/content/dom/main.js
+browser/chrome/devtools/content/framework/toolbox-options.js
+browser/chrome/devtools/content/inspector/fonts/fonts.js
+browser/chrome/devtools/content/inspector/inspector.xhtml
+browser/chrome/devtools/content/memory/initializer.js
+browser/chrome/devtools/content/projecteditor/lib/helpers/readdir.js
+browser/chrome/devtools/content/shared/frame-script-utils.js
+browser/chrome/devtools/content/shared/theme-switching.js
+browser/chrome/devtools/modules/devtools/client/dom/content/dom-view.css
+browser/chrome/devtools/modules/devtools/client/dom/dom.html
+browser/chrome/devtools/modules/devtools/client/dom/main.js
+browser/chrome/devtools/modules/devtools/client/framework/toolbox-options.js
+browser/chrome/devtools/modules/devtools/client/inspector/fonts/fonts.js
+browser/chrome/devtools/modules/devtools/client/inspector/inspector.xhtml
+browser/chrome/devtools/modules/devtools/client/jsonview/css/controls.png
+browser/chrome/devtools/modules/devtools/client/jsonview/css/controls@2x.png
+browser/chrome/devtools/modules/devtools/client/memory/initializer.js
+browser/chrome/devtools/modules/devtools/client/projecteditor/lib/helpers/readdir.js
+browser/chrome/devtools/modules/devtools/client/shared/frame-script-utils.js
+browser/chrome/devtools/modules/devtools/client/shared/theme-switching.js
+browser/chrome/devtools/modules/devtools/client/themes/common.css
+browser/chrome/devtools/modules/devtools/client/themes/toolbars.css
+browser/chrome/devtools/modules/devtools/client/themes/variables.css
+browser/chrome/devtools/skin/common.css
+browser/chrome/devtools/skin/toolbars.css
+browser/chrome/devtools/skin/images/command-scratchpad.svg
+browser/chrome/devtools/skin/images/controls.png
+browser/chrome/devtools/skin/images/controls@2x.png
+browser/chrome/devtools/skin/images/debugger-blackbox.svg
+browser/chrome/devtools/skin/images/debugger-prettyprint.svg
+browser/chrome/devtools/skin/images/filetypes/store.svg
+browser/chrome/devtools/skin/images/itemToggle.svg
+browser/chrome/devtools/skin/images/security-state-broken.svg
+browser/chrome/devtools/skin/images/security-state-local.svg
+browser/chrome/devtools/skin/images/security-state-secure.svg
+browser/chrome/devtools/skin/images/tabs-icon.svg
+browser/chrome/devtools/skin/images/tool-scratchpad.svg
+browser/chrome/devtools/skin/images/tool-storage.svg
+browser/chrome/devtools/skin/images/tool-styleeditor.svg
+browser/chrome/devtools/skin/promisedebugger.css
+browser/chrome/devtools/skin/variables.css
+modules/devtools/Console.jsm
+modules/devtools/Loader.jsm
+modules/devtools/Simulator.jsm
+modules/devtools/shared/Console.jsm
+modules/devtools/shared/Loader.jsm
+modules/devtools/shared/apps/Simulator.jsm
+browser/modules/devtools/client/framework/gDevTools.jsm
+browser/modules/devtools/gDevTools.jsm
+browser/chrome/icons/default/default16.png
+browser/chrome/icons/default/default32.png
+browser/chrome/icons/default/default48.png
+browser/chrome/pdfjs/content/web/images/findbarButton-next-rtl.png
+browser/chrome/pdfjs/content/web/images/findbarButton-next-rtl@2x.png
+browser/chrome/pdfjs/content/web/images/findbarButton-next.png
+browser/chrome/pdfjs/content/web/images/findbarButton-next@2x.png
+browser/chrome/pdfjs/content/web/images/findbarButton-previous-rtl.png
+browser/chrome/pdfjs/content/web/images/findbarButton-previous-rtl@2x.png
+browser/chrome/pdfjs/content/web/images/findbarButton-previous.png
+browser/chrome/pdfjs/content/web/images/findbarButton-previous@2x.png
+browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/icon.png
+browser/features/firefox@getpocket.com/chrome/skin/linux/menuPanel.png
+browser/features/firefox@getpocket.com/chrome/skin/linux/menuPanel@2x.png
+browser/features/firefox@getpocket.com/chrome/skin/windows/menuPanel.png
+browser/features/firefox@getpocket.com/chrome/skin/windows/menuPanel@2x.png
+# flyweb reduction is bug 1313107
+browser/features/flyweb@mozilla.org/chrome/skin/linux/flyweb.css
+browser/features/flyweb@mozilla.org/chrome/skin/linux/icon-16.png
+browser/features/flyweb@mozilla.org/chrome/skin/linux/icon-32-anchored.png
+browser/features/flyweb@mozilla.org/chrome/skin/linux/icon-32.png
+browser/features/flyweb@mozilla.org/chrome/skin/linux/icon-64-anchored.png
+browser/features/flyweb@mozilla.org/chrome/skin/linux/icon-64.png
+browser/features/flyweb@mozilla.org/chrome/skin/osx/flyweb.css
+browser/features/flyweb@mozilla.org/chrome/skin/osx/icon-32-anchored.png
+browser/features/flyweb@mozilla.org/chrome/skin/osx/icon-64-anchored.png
+browser/features/flyweb@mozilla.org/chrome/skin/windows/flyweb.css
+browser/features/flyweb@mozilla.org/chrome/skin/windows/icon-16.png
+browser/features/flyweb@mozilla.org/chrome/skin/windows/icon-32-anchored.png
+browser/features/flyweb@mozilla.org/chrome/skin/windows/icon-32.png
+browser/features/flyweb@mozilla.org/chrome/skin/windows/icon-64-anchored.png
+browser/features/flyweb@mozilla.org/chrome/skin/windows/icon-64.png
+browser/icons/mozicon128.png
+chrome.manifest
+chrome/en-US/locale/en-US/browser/overrides/AccessFu.properties
+chrome/en-US/locale/en-US/browser/overrides/about.dtd
+chrome/en-US/locale/en-US/browser/overrides/aboutAbout.dtd
+chrome/en-US/locale/en-US/browser/overrides/aboutReader.properties
+chrome/en-US/locale/en-US/browser/overrides/aboutRights.dtd
+chrome/en-US/locale/en-US/browser/overrides/charsetMenu.properties
+chrome/en-US/locale/en-US/browser/overrides/commonDialogs.properties
+chrome/en-US/locale/en-US/browser/overrides/crashreporter/crashes.dtd
+chrome/en-US/locale/en-US/browser/overrides/crashreporter/crashes.properties
+chrome/en-US/locale/en-US/browser/overrides/dom/dom.properties
+chrome/en-US/locale/en-US/browser/overrides/global.dtd
+chrome/en-US/locale/en-US/browser/overrides/global/aboutSupport.dtd
+chrome/en-US/locale/en-US/browser/overrides/global/aboutSupport.properties
+chrome/en-US/locale/en-US/browser/overrides/global/aboutTelemetry.dtd
+chrome/en-US/locale/en-US/browser/overrides/global/aboutTelemetry.properties
+chrome/en-US/locale/en-US/browser/overrides/global/aboutWebrtc.properties
+chrome/en-US/locale/en-US/browser/overrides/global/mozilla.dtd
+chrome/en-US/locale/en-US/browser/overrides/intl.css
+chrome/en-US/locale/en-US/browser/overrides/intl.properties
+chrome/en-US/locale/en-US/browser/overrides/passwordmgr.properties
+chrome/en-US/locale/en-US/browser/overrides/plugins.properties
+chrome/en-US/locale/en-US/browser/overrides/plugins/pluginproblem.dtd
+chrome/en-US/locale/en-US/browser/overrides/search/search.properties
+chrome/en-US/locale/en-US/global-platform/mac/intl.properties
+chrome/en-US/locale/en-US/global-platform/unix/accessible.properties
+chrome/en-US/locale/en-US/global-platform/unix/intl.properties
+chrome/en-US/locale/en-US/global-platform/unix/platformKeys.properties
+chrome/en-US/locale/en-US/global-platform/win/accessible.properties
+chrome/en-US/locale/en-US/global-platform/win/intl.properties
+chrome/en-US/locale/en-US/global-platform/win/platformKeys.properties
+chrome/en-US/locale/en-US/global/AccessFu.properties
+chrome/en-US/locale/en-US/global/about.dtd
+chrome/en-US/locale/en-US/global/aboutAbout.dtd
+chrome/en-US/locale/en-US/global/aboutReader.properties
+chrome/en-US/locale/en-US/global/aboutRights.dtd
+chrome/en-US/locale/en-US/global/aboutSupport.dtd
+chrome/en-US/locale/en-US/global/aboutSupport.properties
+chrome/en-US/locale/en-US/global/aboutTelemetry.dtd
+chrome/en-US/locale/en-US/global/aboutTelemetry.properties
+chrome/en-US/locale/en-US/global/aboutWebrtc.properties
+chrome/en-US/locale/en-US/global/charsetMenu.properties
+chrome/en-US/locale/en-US/global/commonDialogs.properties
+chrome/en-US/locale/en-US/global/crashes.dtd
+chrome/en-US/locale/en-US/global/crashes.properties
+chrome/en-US/locale/en-US/global/dom/dom.properties
+chrome/en-US/locale/en-US/global/global.dtd
+chrome/en-US/locale/en-US/global/intl.css
+chrome/en-US/locale/en-US/global/intl.properties
+chrome/en-US/locale/en-US/global/mozilla.dtd
+chrome/en-US/locale/en-US/global/plugins.properties
+chrome/en-US/locale/en-US/global/search/search.properties
+chrome/en-US/locale/en-US/passwordmgr/passwordmgr.properties
+chrome/en-US/locale/en-US/pluginproblem/pluginproblem.dtd
+chrome/toolkit/skin/classic/global/autocomplete.css
+chrome/toolkit/skin/classic/global/button.css
+chrome/toolkit/skin/classic/global/checkbox.css
+chrome/toolkit/skin/classic/global/dialog.css
+chrome/toolkit/skin/classic/global/dropmarker.css
+chrome/toolkit/skin/classic/global/global.css
+chrome/toolkit/skin/classic/global/groupbox.css
+chrome/toolkit/skin/classic/global/icons/close-XPVista7.png
+chrome/toolkit/skin/classic/global/icons/tabprompts-bgtexture.png
+chrome/toolkit/skin/classic/global/listbox.css
+chrome/toolkit/skin/classic/global/media/clicktoplay-bgtexture.png
+chrome/toolkit/skin/classic/global/menu.css
+chrome/toolkit/skin/classic/global/menulist.css
+chrome/toolkit/skin/classic/global/numberbox.css
+chrome/toolkit/skin/classic/global/popup.css
+chrome/toolkit/skin/classic/global/preferences.css
+chrome/toolkit/skin/classic/global/progressmeter.css
+chrome/toolkit/skin/classic/global/radio.css
+chrome/toolkit/skin/classic/global/resizer.css
+chrome/toolkit/skin/classic/global/richlistbox.css
+chrome/toolkit/skin/classic/global/scale.css
+chrome/toolkit/skin/classic/global/scrollbars.css
+chrome/toolkit/skin/classic/global/scrollbox.css
+chrome/toolkit/skin/classic/global/spinbuttons.css
+chrome/toolkit/skin/classic/global/splitter.css
+chrome/toolkit/skin/classic/global/tabbox.css
+chrome/toolkit/skin/classic/global/textbox.css
+chrome/toolkit/skin/classic/global/toolbar.css
+chrome/toolkit/skin/classic/global/toolbarbutton.css
+chrome/toolkit/skin/classic/global/tree.css
+chrome/toolkit/skin/classic/global/wizard.css
+chrome/toolkit/skin/classic/mozapps/downloads/buttons.png
+chrome/toolkit/skin/classic/mozapps/downloads/downloadButtons-XP.png
+chrome/toolkit/skin/classic/mozapps/downloads/downloadButtons.png
+chrome/toolkit/skin/classic/mozapps/extensions/category-dictionaries.png
+chrome/toolkit/skin/classic/mozapps/extensions/category-experiments.png
+chrome/toolkit/skin/classic/mozapps/extensions/dictionaryGeneric.png
+chrome/toolkit/skin/classic/mozapps/extensions/experimentGeneric.png
+chrome/toolkit/skin/classic/mozapps/update/buttons.png
+chrome/toolkit/skin/classic/mozapps/update/downloadButtons-XP.png
+chrome/toolkit/skin/classic/mozapps/update/downloadButtons.png
+components/FxAccountsPush.js
+crashreporter.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
+crashreporter.app/Contents/Resources/English.lproj/MainMenuRTL.nib/classes.nib
+# firefox/firefox-bin is bug 658850
+@MOZ_APP_NAME@
+@MOZ_APP_NAME@-bin
+modules/FxAccountsPush.js
+modules/commonjs/index.js
+modules/commonjs/sdk/ui/button/view/events.js
+modules/commonjs/sdk/ui/state/events.js
+plugin-container.app/Contents/PkgInfo
+res/table-remove-column-active.gif
+res/table-remove-column-hover.gif
+res/table-remove-column.gif
+res/table-remove-row-active.gif
+res/table-remove-row-hover.gif
+res/table-remove-row.gif
+# Aurora branding
+browser/chrome/browser/content/browser/defaultthemes/devedition.icon.png
+browser/chrome/browser/content/branding/icon64.png
+browser/chrome/devtools/content/framework/dev-edition-promo/dev-edition-logo.png
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
new file mode 100644
index 000000000..6e5001714
--- /dev/null
+++ b/browser/installer/package-manifest.in
@@ -0,0 +1,830 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+; Package file for the Firefox build.
+;
+; Packaging manifest is used to copy files from dist/bin
+; to the staging directory.
+; Some other files are built in the staging directory directly,
+; so they will be implicitly packaged too.
+;
+; File format:
+;
+; [] designates a toplevel component. Example: [xpcom]
+; - in front of a file specifies it to be removed from the destination
+; * wildcard support to recursively copy the entire directory
+; ; file comment
+;
+
+; Due to Apple Mac OS X packaging requirements, files that are in the same
+; directory on other platforms must be located in different directories on
+; Mac OS X. The following defines allow specifying the Mac OS X bundle
+; location which also work on other platforms.
+;
+; @BINPATH@
+; Equals Contents/MacOS/ on Mac OS X and is the path to the main binary on other
+; platforms.
+;
+; @RESPATH@
+; Equals Contents/Resources/ on Mac OS X and is equivalent to @BINPATH@ on other
+; platforms.
+
+#filter substitution
+
+#ifdef XP_MACOSX
+; Mac bundle stuff
+@APPNAME@/Contents/Info.plist
+@APPNAME@/Contents/Library/LaunchServices
+@APPNAME@/Contents/PkgInfo
+@RESPATH@/firefox.icns
+@RESPATH@/document.icns
+@RESPATH@/@LPROJ_ROOT@.lproj/*
+#endif
+
+[@AB_CD@]
+@RESPATH@/browser/chrome/@AB_CD@@JAREXT@
+@RESPATH@/browser/chrome/@AB_CD@.manifest
+@RESPATH@/chrome/@AB_CD@@JAREXT@
+@RESPATH@/chrome/@AB_CD@.manifest
+@RESPATH@/dictionaries/*
+#if defined(XP_WIN) || defined(XP_LINUX)
+@RESPATH@/fonts/*
+#endif
+@RESPATH@/hyphenation/*
+@RESPATH@/browser/@PREF_DIR@/firefox-l10n.js
+#ifdef HAVE_MAKENSISU
+@BINPATH@/uninstall/helper.exe
+#endif
+#ifdef MOZ_UPDATER
+@RESPATH@/update.locale
+@RESPATH@/updater.ini
+#endif
+
+[xpcom]
+@RESPATH@/dependentlibs.list
+#ifdef MOZ_SHARED_MOZGLUE
+@BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
+#endif
+#ifndef MOZ_STATIC_JS
+@BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
+#endif
+#ifdef MOZ_DMD
+@BINPATH@/@DLL_PREFIX@dmd@DLL_SUFFIX@
+#endif
+#ifndef MOZ_SYSTEM_NSPR
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@plds4@DLL_SUFFIX@
+#endif
+#endif
+#ifdef XP_MACOSX
+@BINPATH@/XUL
+#else
+@BINPATH@/@DLL_PREFIX@xul@DLL_SUFFIX@
+#endif
+#ifdef XP_MACOSX
+@BINPATH@/@MOZ_CHILD_PROCESS_NAME@.app/
+@BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@
+#else
+@BINPATH@/@MOZ_CHILD_PROCESS_NAME@
+#endif
+#ifdef XP_WIN32
+@BINPATH@/plugin-hang-ui@BIN_SUFFIX@
+#if MOZ_PACKAGE_MSVC_DLLS
+@BINPATH@/@MSVC_C_RUNTIME_DLL@
+@BINPATH@/@MSVC_CXX_RUNTIME_DLL@
+#endif
+#if MOZ_PACKAGE_WIN_UCRT_DLLS
+@BINPATH@/api-ms-win-*.dll
+@BINPATH@/ucrtbase.dll
+#endif
+#endif
+#ifdef MOZ_ICU_DATA_ARCHIVE
+@RESPATH@/@ICU_DATA_FILE@
+#endif
+#ifdef MOZ_GTK3
+@BINPATH@/@DLL_PREFIX@mozgtk@DLL_SUFFIX@
+@BINPATH@/gtk2/@DLL_PREFIX@mozgtk@DLL_SUFFIX@
+#endif
+
+[browser]
+; [Base Browser Files]
+#ifndef XP_UNIX
+@BINPATH@/@MOZ_APP_NAME@.exe
+@BINPATH@/firefox.VisualElementsManifest.xml
+@BINPATH@/browser/VisualElements/VisualElements_150.png
+@BINPATH@/browser/VisualElements/VisualElements_70.png
+#else
+@BINPATH@/@MOZ_APP_NAME@-bin
+@BINPATH@/@MOZ_APP_NAME@
+#endif
+@RESPATH@/application.ini
+#ifdef MOZ_UPDATER
+@RESPATH@/update-settings.ini
+#endif
+@RESPATH@/platform.ini
+#ifndef MOZ_SYSTEM_SQLITE
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@
+#endif
+#endif
+@BINPATH@/@DLL_PREFIX@lgpllibs@DLL_SUFFIX@
+#ifdef MOZ_FFVPX
+@BINPATH@/@DLL_PREFIX@mozavutil@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@mozavcodec@DLL_SUFFIX@
+#endif
+@RESPATH@/browser/blocklist.xml
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+@RESPATH@/run-mozilla.sh
+#endif
+#endif
+#ifdef XP_WIN
+#ifdef _AMD64_
+@BINPATH@/@DLL_PREFIX@qipcap64@DLL_SUFFIX@
+#else
+@BINPATH@/@DLL_PREFIX@qipcap@DLL_SUFFIX@
+#endif
+#endif
+
+; [Components]
+#ifdef MOZ_ARTIFACT_BUILDS
+@RESPATH@/components/prebuilt-interfaces.manifest
+@RESPATH@/components/interfaces.xpt
+@RESPATH@/browser/components/prebuilt-interfaces.manifest
+@RESPATH@/browser/components/interfaces.xpt
+#endif
+@RESPATH@/components/alerts.xpt
+#ifdef ACCESSIBILITY
+#ifdef XP_WIN32
+@BINPATH@/Accessible.tlb
+@BINPATH@/AccessibleMarshal.dll
+@BINPATH@/IA2Marshal.dll
+#endif
+@RESPATH@/components/accessibility.xpt
+#endif
+@RESPATH@/components/appshell.xpt
+@RESPATH@/components/appstartup.xpt
+@RESPATH@/components/autocomplete.xpt
+@RESPATH@/components/autoconfig.xpt
+@RESPATH@/components/browser-element.xpt
+@RESPATH@/browser/components/browsercompsbase.xpt
+@RESPATH@/browser/components/browser-feeds.xpt
+@RESPATH@/components/caps.xpt
+@RESPATH@/components/chrome.xpt
+@RESPATH@/components/commandhandler.xpt
+@RESPATH@/components/commandlines.xpt
+@RESPATH@/components/composer.xpt
+@RESPATH@/components/content_events.xpt
+@RESPATH@/components/content_html.xpt
+@RESPATH@/components/content_geckomediaplugins.xpt
+#ifdef MOZ_WEBRTC
+@RESPATH@/components/content_webrtc.xpt
+#endif
+@RESPATH@/components/content_xslt.xpt
+@RESPATH@/components/cookie.xpt
+@RESPATH@/components/directory.xpt
+@RESPATH@/components/docshell.xpt
+@RESPATH@/components/dom.xpt
+@RESPATH@/components/dom_apps.xpt
+@RESPATH@/components/dom_base.xpt
+@RESPATH@/components/dom_system.xpt
+@RESPATH@/components/dom_canvas.xpt
+@RESPATH@/components/dom_core.xpt
+@RESPATH@/components/dom_css.xpt
+@RESPATH@/components/dom_events.xpt
+@RESPATH@/components/dom_geolocation.xpt
+@RESPATH@/components/dom_media.xpt
+@RESPATH@/components/dom_network.xpt
+@RESPATH@/components/dom_notification.xpt
+@RESPATH@/components/dom_html.xpt
+@RESPATH@/components/dom_offline.xpt
+@RESPATH@/components/dom_json.xpt
+@RESPATH@/components/dom_power.xpt
+@RESPATH@/components/dom_push.xpt
+@RESPATH@/components/dom_quota.xpt
+@RESPATH@/components/dom_range.xpt
+@RESPATH@/components/dom_security.xpt
+@RESPATH@/components/dom_settings.xpt
+@RESPATH@/components/dom_permissionsettings.xpt
+@RESPATH@/components/dom_sidebar.xpt
+@RESPATH@/components/dom_storage.xpt
+@RESPATH@/components/dom_stylesheets.xpt
+@RESPATH@/components/dom_traversal.xpt
+#ifdef MOZ_WEBSPEECH
+@RESPATH@/components/dom_webspeechrecognition.xpt
+#endif
+@RESPATH@/components/dom_workers.xpt
+@RESPATH@/components/dom_xbl.xpt
+@RESPATH@/components/dom_xhr.xpt
+@RESPATH@/components/dom_xpath.xpt
+@RESPATH@/components/dom_xul.xpt
+@RESPATH@/components/dom_presentation.xpt
+@RESPATH@/components/downloads.xpt
+@RESPATH@/components/editor.xpt
+@RESPATH@/components/embed_base.xpt
+@RESPATH@/components/extensions.xpt
+@RESPATH@/components/exthandler.xpt
+@RESPATH@/components/exthelper.xpt
+@RESPATH@/components/fastfind.xpt
+@RESPATH@/components/feeds.xpt
+#ifdef MOZ_GTK
+@RESPATH@/components/filepicker.xpt
+#endif
+@RESPATH@/components/find.xpt
+@RESPATH@/components/gfx.xpt
+@RESPATH@/components/html5.xpt
+@RESPATH@/components/htmlparser.xpt
+@RESPATH@/components/identity.xpt
+@RESPATH@/components/imglib2.xpt
+@RESPATH@/components/inspector.xpt
+@RESPATH@/components/intl.xpt
+@RESPATH@/components/jar.xpt
+@RESPATH@/components/jsdebugger.xpt
+@RESPATH@/components/jsdownloads.xpt
+@RESPATH@/browser/components/jsinspector.xpt
+@RESPATH@/components/layout_base.xpt
+#ifdef NS_PRINTING
+@RESPATH@/components/layout_printing.xpt
+#endif
+@RESPATH@/components/layout_xul_tree.xpt
+@RESPATH@/components/layout_xul.xpt
+@RESPATH@/components/locale.xpt
+@RESPATH@/components/lwbrk.xpt
+#ifdef MOZ_ENABLE_PROFILER_SPS
+@RESPATH@/components/memory_profiler.xpt
+#endif
+@RESPATH@/browser/components/migration.xpt
+@RESPATH@/components/mimetype.xpt
+@RESPATH@/components/mozfind.xpt
+#ifdef ENABLE_INTL_API
+@RESPATH@/components/mozintl.xpt
+#endif
+@RESPATH@/components/necko_about.xpt
+@RESPATH@/components/necko_cache.xpt
+@RESPATH@/components/necko_cache2.xpt
+@RESPATH@/components/necko_cookie.xpt
+@RESPATH@/components/necko_dns.xpt
+@RESPATH@/components/necko_file.xpt
+@RESPATH@/components/necko_ftp.xpt
+@RESPATH@/components/necko_http.xpt
+@RESPATH@/components/necko_mdns.xpt
+@RESPATH@/components/necko_res.xpt
+@RESPATH@/components/necko_socket.xpt
+@RESPATH@/components/necko_strconv.xpt
+@RESPATH@/components/necko_viewsource.xpt
+@RESPATH@/components/necko_websocket.xpt
+#ifdef NECKO_WIFI
+@RESPATH@/components/necko_wifi.xpt
+#endif
+@RESPATH@/components/necko_wyciwyg.xpt
+@RESPATH@/components/necko.xpt
+@RESPATH@/components/loginmgr.xpt
+@RESPATH@/components/parentalcontrols.xpt
+#ifdef MOZ_WEBRTC
+@RESPATH@/components/peerconnection.xpt
+#endif
+@RESPATH@/components/places.xpt
+@RESPATH@/components/plugin.xpt
+@RESPATH@/components/pref.xpt
+@RESPATH@/components/prefetch.xpt
+#ifdef MOZ_ENABLE_PROFILER_SPS
+@RESPATH@/components/profiler.xpt
+#endif
+@RESPATH@/components/rdf.xpt
+@RESPATH@/components/satchel.xpt
+@RESPATH@/components/saxparser.xpt
+@RESPATH@/browser/components/sessionstore.xpt
+@RESPATH@/components/services-crypto-component.xpt
+@RESPATH@/components/captivedetect.xpt
+@RESPATH@/browser/components/shellservice.xpt
+@RESPATH@/components/shistory.xpt
+@RESPATH@/components/spellchecker.xpt
+@RESPATH@/components/storage.xpt
+@RESPATH@/components/toolkit_asyncshutdown.xpt
+@RESPATH@/components/toolkit_filewatcher.xpt
+@RESPATH@/components/toolkit_finalizationwitness.xpt
+@RESPATH@/components/toolkit_formautofill.xpt
+@RESPATH@/components/toolkit_osfile.xpt
+@RESPATH@/components/toolkit_securityreporter.xpt
+@RESPATH@/components/toolkit_perfmonitoring.xpt
+@RESPATH@/components/toolkit_xulstore.xpt
+@RESPATH@/components/toolkitprofile.xpt
+#ifdef MOZ_ENABLE_XREMOTE
+@RESPATH@/components/toolkitremote.xpt
+#endif
+@RESPATH@/components/txtsvc.xpt
+@RESPATH@/components/txmgr.xpt
+@RESPATH@/components/uconv.xpt
+@RESPATH@/components/unicharutil.xpt
+@RESPATH@/components/update.xpt
+@RESPATH@/components/uriloader.xpt
+@RESPATH@/components/urlformatter.xpt
+@RESPATH@/components/webBrowser_core.xpt
+@RESPATH@/components/webbrowserpersist.xpt
+@RESPATH@/components/widget.xpt
+#ifdef XP_MACOSX
+@RESPATH@/components/widget_cocoa.xpt
+#endif
+@RESPATH@/components/windowds.xpt
+@RESPATH@/components/windowwatcher.xpt
+@RESPATH@/components/xpcom_base.xpt
+@RESPATH@/components/xpcom_system.xpt
+@RESPATH@/components/xpcom_components.xpt
+@RESPATH@/components/xpcom_ds.xpt
+@RESPATH@/components/xpcom_io.xpt
+@RESPATH@/components/xpcom_threads.xpt
+@RESPATH@/components/xpcom_xpti.xpt
+@RESPATH@/components/xpconnect.xpt
+@RESPATH@/components/xulapp.xpt
+@RESPATH@/components/xul.xpt
+@RESPATH@/components/xultmpl.xpt
+@RESPATH@/components/zipwriter.xpt
+@RESPATH@/components/telemetry.xpt
+
+; JavaScript components
+@RESPATH@/components/ConsoleAPI.manifest
+@RESPATH@/components/ConsoleAPIStorage.js
+@RESPATH@/components/BrowserElementParent.manifest
+@RESPATH@/components/BrowserElementParent.js
+@RESPATH@/components/BrowserElementProxy.manifest
+@RESPATH@/components/BrowserElementProxy.js
+@RESPATH@/components/FeedProcessor.manifest
+@RESPATH@/components/FeedProcessor.js
+@RESPATH@/components/WellKnownOpportunisticUtils.js
+@RESPATH@/components/WellKnownOpportunisticUtils.manifest
+#ifndef XP_MACOSX
+; OSX uses native platform impl. Windows, Linux, and Android uses fallback JS impl.
+@BINPATH@/components/nsDNSServiceDiscovery.manifest
+@BINPATH@/components/nsDNSServiceDiscovery.js
+#endif
+@RESPATH@/browser/components/BrowserFeeds.manifest
+@RESPATH@/browser/components/FeedConverter.js
+@RESPATH@/browser/components/FeedWriter.js
+@RESPATH@/browser/components/WebContentConverter.js
+@RESPATH@/browser/components/BrowserComponents.manifest
+@RESPATH@/browser/components/nsBrowserContentHandler.js
+@RESPATH@/browser/components/nsBrowserGlue.js
+@RESPATH@/browser/components/nsSetDefaultBrowser.manifest
+@RESPATH@/browser/components/nsSetDefaultBrowser.js
+@RESPATH@/browser/components/devtools-startup.manifest
+@RESPATH@/browser/components/devtools-startup.js
+@RESPATH@/browser/components/webideCli.js
+@RESPATH@/browser/components/webideComponents.manifest
+@RESPATH@/browser/components/Experiments.manifest
+@RESPATH@/browser/components/ExperimentsService.js
+@RESPATH@/browser/components/browser-newtab.xpt
+@RESPATH@/browser/components/aboutNewTabService.js
+@RESPATH@/browser/components/NewTabComponents.manifest
+@RESPATH@/components/Downloads.manifest
+@RESPATH@/components/DownloadLegacy.js
+@RESPATH@/components/BrowserPageThumbs.manifest
+@RESPATH@/components/crashmonitor.manifest
+@RESPATH@/components/nsCrashMonitor.js
+@RESPATH@/components/SiteSpecificUserAgent.js
+@RESPATH@/components/SiteSpecificUserAgent.manifest
+@RESPATH@/components/toolkitsearch.manifest
+@RESPATH@/components/nsSearchService.js
+@RESPATH@/components/nsSearchSuggestions.js
+@RESPATH@/components/nsSidebar.js
+@RESPATH@/components/passwordmgr.manifest
+@RESPATH@/components/nsLoginInfo.js
+@RESPATH@/components/nsLoginManager.js
+@RESPATH@/components/nsLoginManagerPrompter.js
+@RESPATH@/components/storage-json.js
+@RESPATH@/components/crypto-SDR.js
+@RESPATH@/components/TooltipTextProvider.js
+@RESPATH@/components/TooltipTextProvider.manifest
+@RESPATH@/components/webvtt.xpt
+@RESPATH@/components/WebVTT.manifest
+@RESPATH@/components/WebVTTParserWrapper.js
+#ifdef MOZ_GTK
+@RESPATH@/components/nsFilePicker.manifest
+@RESPATH@/components/nsFilePicker.js
+#endif
+@RESPATH@/components/nsHelperAppDlg.manifest
+@RESPATH@/components/nsHelperAppDlg.js
+@RESPATH@/components/NetworkGeolocationProvider.manifest
+@RESPATH@/components/NetworkGeolocationProvider.js
+@RESPATH@/components/extensions.manifest
+@RESPATH@/components/EditorUtils.manifest
+@RESPATH@/components/EditorUtils.js
+@RESPATH@/components/addonManager.js
+@RESPATH@/components/amContentHandler.js
+@RESPATH@/components/amInstallTrigger.js
+@RESPATH@/components/amWebAPI.js
+@RESPATH@/components/amWebInstallListener.js
+@RESPATH@/components/nsBlocklistService.js
+@RESPATH@/components/nsBlocklistServiceContent.js
+#ifdef MOZ_UPDATER
+@RESPATH@/components/nsUpdateService.manifest
+@RESPATH@/components/nsUpdateService.js
+@RESPATH@/components/nsUpdateServiceStub.js
+#endif
+@RESPATH@/components/nsUpdateTimerManager.manifest
+@RESPATH@/components/nsUpdateTimerManager.js
+@RESPATH@/components/addoncompat.manifest
+@RESPATH@/components/multiprocessShims.js
+@RESPATH@/components/defaultShims.js
+@RESPATH@/components/utils.manifest
+@RESPATH@/components/simpleServices.js
+@RESPATH@/components/pluginGlue.manifest
+@RESPATH@/components/ProcessSingleton.manifest
+@RESPATH@/components/MainProcessSingleton.js
+@RESPATH@/components/ContentProcessSingleton.js
+@RESPATH@/browser/components/nsSessionStore.manifest
+@RESPATH@/browser/components/nsSessionStartup.js
+@RESPATH@/browser/components/nsSessionStore.js
+@RESPATH@/components/nsURLFormatter.manifest
+@RESPATH@/components/nsURLFormatter.js
+@RESPATH@/components/txEXSLTRegExFunctions.manifest
+@RESPATH@/components/txEXSLTRegExFunctions.js
+@RESPATH@/components/toolkitplaces.manifest
+@RESPATH@/components/nsLivemarkService.js
+@RESPATH@/components/nsTaggingService.js
+@RESPATH@/components/UnifiedComplete.js
+@RESPATH@/components/nsPlacesExpiration.js
+@RESPATH@/components/PageIconProtocolHandler.js
+@RESPATH@/components/PlacesCategoriesStarter.js
+@RESPATH@/components/ColorAnalyzer.js
+@RESPATH@/components/PageThumbsProtocol.js
+@RESPATH@/components/mozProtocolHandler.js
+@RESPATH@/components/mozProtocolHandler.manifest
+@RESPATH@/components/nsDefaultCLH.manifest
+@RESPATH@/components/nsDefaultCLH.js
+@RESPATH@/components/nsContentPrefService.manifest
+@RESPATH@/components/nsContentPrefService.js
+@RESPATH@/components/nsContentDispatchChooser.manifest
+@RESPATH@/components/nsContentDispatchChooser.js
+@RESPATH@/components/nsHandlerService.manifest
+@RESPATH@/components/nsHandlerService.js
+@RESPATH@/components/nsWebHandlerApp.manifest
+@RESPATH@/components/nsWebHandlerApp.js
+@RESPATH@/components/satchel.manifest
+@RESPATH@/components/nsFormAutoComplete.js
+@RESPATH@/components/nsFormHistory.js
+@RESPATH@/components/FormHistoryStartup.js
+@RESPATH@/components/nsInputListAutoComplete.js
+@RESPATH@/components/formautofill.manifest
+@RESPATH@/components/FormAutofillContentService.js
+@RESPATH@/components/FormAutofillStartup.js
+@RESPATH@/components/CSSUnprefixingService.js
+@RESPATH@/components/CSSUnprefixingService.manifest
+@RESPATH@/components/contentAreaDropListener.manifest
+@RESPATH@/components/contentAreaDropListener.js
+@RESPATH@/browser/components/BrowserProfileMigrators.manifest
+@RESPATH@/browser/components/ProfileMigrator.js
+@RESPATH@/browser/components/ChromeProfileMigrator.js
+@RESPATH@/browser/components/FirefoxProfileMigrator.js
+#ifdef XP_WIN
+@RESPATH@/browser/components/360seProfileMigrator.js
+@RESPATH@/browser/components/EdgeProfileMigrator.js
+@RESPATH@/browser/components/IEProfileMigrator.js
+#endif
+#ifdef XP_MACOSX
+@RESPATH@/browser/components/SafariProfileMigrator.js
+#endif
+@RESPATH@/components/nsINIProcessor.manifest
+@RESPATH@/components/nsINIProcessor.js
+@RESPATH@/components/nsPrompter.manifest
+@RESPATH@/components/nsPrompter.js
+#ifdef MOZ_SERVICES_HEALTHREPORT
+@RESPATH@/browser/components/SelfSupportService.manifest
+@RESPATH@/browser/components/SelfSupportService.js
+#endif
+@RESPATH@/components/SyncComponents.manifest
+@RESPATH@/components/Weave.js
+@RESPATH@/components/FxAccountsComponents.manifest
+@RESPATH@/components/FxAccountsPush.js
+@RESPATH@/components/CaptivePortalDetectComponents.manifest
+@RESPATH@/components/captivedetect.js
+@RESPATH@/components/servicesComponents.manifest
+@RESPATH@/components/cryptoComponents.manifest
+@RESPATH@/components/TelemetryStartup.js
+@RESPATH@/components/TelemetryStartup.manifest
+@RESPATH@/components/XULStore.js
+@RESPATH@/components/XULStore.manifest
+@RESPATH@/components/messageWakeupService.js
+@RESPATH@/components/messageWakeupService.manifest
+@RESPATH@/components/SettingsManager.js
+@RESPATH@/components/SettingsManager.manifest
+@RESPATH@/components/AppsService.js
+@RESPATH@/components/AppsService.manifest
+@RESPATH@/components/recording-cmdline.js
+@RESPATH@/components/recording-cmdline.manifest
+@RESPATH@/components/htmlMenuBuilder.js
+@RESPATH@/components/htmlMenuBuilder.manifest
+
+@RESPATH@/components/PermissionSettings.js
+@RESPATH@/components/PermissionSettings.manifest
+@RESPATH@/components/NotificationStorage.js
+@RESPATH@/components/NotificationStorage.manifest
+@RESPATH@/components/Push.js
+@RESPATH@/components/Push.manifest
+@RESPATH@/components/PushComponents.js
+
+@RESPATH@/components/remotebrowserutils.manifest
+@RESPATH@/components/RemoteWebNavigation.js
+
+@RESPATH@/components/SlowScriptDebug.manifest
+@RESPATH@/components/SlowScriptDebug.js
+
+#ifdef MOZ_WEBRTC
+@RESPATH@/components/PeerConnection.js
+@RESPATH@/components/PeerConnection.manifest
+#endif
+
+@RESPATH@/chrome/marionette@JAREXT@
+@RESPATH@/chrome/marionette.manifest
+@RESPATH@/components/marionette.manifest
+@RESPATH@/components/marionette.js
+
+#ifdef MOZ_WEBSPEECH
+@RESPATH@/components/dom_webspeechsynth.xpt
+#endif
+
+@RESPATH@/components/nsAsyncShutdown.manifest
+@RESPATH@/components/nsAsyncShutdown.js
+
+@RESPATH@/components/PresentationDeviceInfoManager.manifest
+@RESPATH@/components/PresentationDeviceInfoManager.js
+@RESPATH@/components/BuiltinProviders.manifest
+@RESPATH@/components/PresentationControlService.js
+@RESPATH@/components/PresentationDataChannelSessionTransport.js
+@RESPATH@/components/PresentationDataChannelSessionTransport.manifest
+
+; InputMethod API
+@RESPATH@/components/MozKeyboard.js
+@RESPATH@/components/InputMethod.manifest
+
+#if defined(ENABLE_TESTS) && defined(MOZ_DEBUG)
+@RESPATH@/components/TestInterfaceJS.js
+@RESPATH@/components/TestInterfaceJS.manifest
+@RESPATH@/components/TestInterfaceJSMaplike.js
+#endif
+
+; [Extensions]
+@RESPATH@/components/extensions-toolkit.manifest
+@RESPATH@/browser/components/extensions-browser.manifest
+
+; Modules
+@RESPATH@/browser/modules/*
+@RESPATH@/modules/*
+
+; Safe Browsing
+@RESPATH@/components/nsURLClassifier.manifest
+@RESPATH@/components/nsUrlClassifierHashCompleter.js
+@RESPATH@/components/nsUrlClassifierListManager.js
+@RESPATH@/components/nsUrlClassifierLib.js
+@RESPATH@/components/url-classifier.xpt
+
+; Private Browsing
+@RESPATH@/components/privatebrowsing.xpt
+@RESPATH@/components/PrivateBrowsing.manifest
+@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
+
+; Security Reports
+@RESPATH@/components/SecurityReporter.manifest
+@RESPATH@/components/SecurityReporter.js
+
+; ANGLE GLES-on-D3D rendering library
+#ifdef MOZ_ANGLE_RENDERER
+@BINPATH@/libEGL.dll
+@BINPATH@/libGLESv2.dll
+
+#ifdef MOZ_D3DCOMPILER_VISTA_DLL
+@BINPATH@/@MOZ_D3DCOMPILER_VISTA_DLL@
+#endif
+
+#ifdef MOZ_D3DCOMPILER_XP_DLL
+@BINPATH@/@MOZ_D3DCOMPILER_XP_DLL@
+#endif
+#endif # MOZ_ANGLE_RENDERER
+
+; [Browser Chrome Files]
+@RESPATH@/browser/chrome.manifest
+@RESPATH@/browser/chrome/browser@JAREXT@
+@RESPATH@/browser/chrome/browser.manifest
+@RESPATH@/browser/chrome/pdfjs.manifest
+@RESPATH@/browser/chrome/pdfjs/*
+@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.manifest
+@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/icon.png
+@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
+@RESPATH@/chrome/toolkit@JAREXT@
+@RESPATH@/chrome/toolkit.manifest
+@RESPATH@/chrome/recording.manifest
+@RESPATH@/chrome/recording/*
+#ifdef MOZ_GTK
+@RESPATH@/browser/chrome/icons/default/default16.png
+@RESPATH@/browser/chrome/icons/default/default32.png
+@RESPATH@/browser/chrome/icons/default/default48.png
+#endif
+@RESPATH@/browser/features/*
+
+; [Webide Files]
+@RESPATH@/browser/chrome/webide@JAREXT@
+@RESPATH@/browser/chrome/webide.manifest
+@RESPATH@/browser/@PREF_DIR@/webide-prefs.js
+
+; DevTools
+@RESPATH@/browser/chrome/devtools@JAREXT@
+@RESPATH@/browser/chrome/devtools.manifest
+@RESPATH@/browser/@PREF_DIR@/devtools.js
+
+; shell icons
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+; shell icons
+@RESPATH@/browser/icons/*.png
+#ifdef MOZ_UPDATER
+; updater icon
+@RESPATH@/icons/updater.png
+#endif
+#endif
+#endif
+
+; [Default Preferences]
+; All the pref files must be part of base to prevent migration bugs
+@RESPATH@/browser/@PREF_DIR@/firefox.js
+@RESPATH@/browser/@PREF_DIR@/firefox-branding.js
+@RESPATH@/greprefs.js
+@RESPATH@/defaults/autoconfig/prefcalls.js
+@RESPATH@/browser/defaults/permissions
+
+; Warning: changing the path to channel-prefs.js can cause bugs (Bug 756325)
+; Technically this is an app pref file, but we are keeping it in the original
+; gre location for now.
+@RESPATH@/defaults/pref/channel-prefs.js
+
+; Services (gre) prefs
+@RESPATH@/defaults/pref/services-sync.js
+
+; [Layout Engine Resources]
+; Style Sheets, Graphics and other Resources used by the layout engine.
+@RESPATH@/res/EditorOverride.css
+@RESPATH@/res/contenteditable.css
+@RESPATH@/res/designmode.css
+@RESPATH@/res/ImageDocument.css
+@RESPATH@/res/TopLevelImageDocument.css
+@RESPATH@/res/TopLevelVideoDocument.css
+@RESPATH@/res/table-add-column-after-active.gif
+@RESPATH@/res/table-add-column-after-hover.gif
+@RESPATH@/res/table-add-column-after.gif
+@RESPATH@/res/table-add-column-before-active.gif
+@RESPATH@/res/table-add-column-before-hover.gif
+@RESPATH@/res/table-add-column-before.gif
+@RESPATH@/res/table-add-row-after-active.gif
+@RESPATH@/res/table-add-row-after-hover.gif
+@RESPATH@/res/table-add-row-after.gif
+@RESPATH@/res/table-add-row-before-active.gif
+@RESPATH@/res/table-add-row-before-hover.gif
+@RESPATH@/res/table-add-row-before.gif
+@RESPATH@/res/table-remove-column-active.gif
+@RESPATH@/res/table-remove-column-hover.gif
+@RESPATH@/res/table-remove-column.gif
+@RESPATH@/res/table-remove-row-active.gif
+@RESPATH@/res/table-remove-row-hover.gif
+@RESPATH@/res/table-remove-row.gif
+@RESPATH@/res/grabber.gif
+#ifdef XP_MACOSX
+@RESPATH@/res/cursors/*
+#endif
+@RESPATH@/res/fonts/*
+@RESPATH@/res/dtd/*
+@RESPATH@/res/html/*
+#if defined(XP_MACOSX)
+; For SafariProfileMigrator.js.
+@RESPATH@/res/langGroups.properties
+#endif
+@RESPATH@/res/language.properties
+@RESPATH@/res/entityTables/*
+#ifdef XP_MACOSX
+@RESPATH@/res/MainMenu.nib/
+#endif
+
+; svg
+@RESPATH@/res/svg.css
+@RESPATH@/components/dom_svg.xpt
+@RESPATH@/components/dom_smil.xpt
+
+; [Personal Security Manager]
+;
+; NSS libraries are signed in the staging directory,
+; meaning their .chk files are created there directly.
+;
+#ifndef MOZ_SYSTEM_NSS
+#if defined(XP_LINUX) && !defined(ANDROID)
+@BINPATH@/@DLL_PREFIX@freeblpriv3@DLL_SUFFIX@
+#else
+@BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@
+#endif
+@BINPATH@/@DLL_PREFIX@nss3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@nssckbi@DLL_SUFFIX@
+#ifndef NSS_DISABLE_DBM
+@BINPATH@/@DLL_PREFIX@nssdbm3@DLL_SUFFIX@
+#endif
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@nssutil3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@smime3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@ssl3@DLL_SUFFIX@
+#endif
+@BINPATH@/@DLL_PREFIX@softokn3@DLL_SUFFIX@
+#endif
+@RESPATH@/chrome/pippki@JAREXT@
+@RESPATH@/chrome/pippki.manifest
+@RESPATH@/components/pipnss.xpt
+@RESPATH@/components/pippki.xpt
+
+; For process sandboxing
+#if defined(MOZ_SANDBOX)
+#if defined(XP_WIN)
+#if defined(WOW_HELPER)
+@BINPATH@/wow_helper.exe
+#endif
+#endif
+#endif
+
+#if defined(MOZ_SANDBOX)
+#if defined(XP_LINUX)
+@BINPATH@/@DLL_PREFIX@mozsandbox@DLL_SUFFIX@
+#endif
+#endif
+
+; for Solaris SPARC
+#ifdef SOLARIS
+bin/libfreebl_32fpu_3.so
+bin/libfreebl_32int_3.so
+bin/libfreebl_32int64_3.so
+#endif
+
+; [Updater]
+;
+#ifdef MOZ_UPDATER
+#ifdef XP_MACOSX
+@BINPATH@/updater.app/
+#else
+@BINPATH@/updater@BIN_SUFFIX@
+#endif
+#endif
+
+; [MaintenanceService]
+;
+#ifdef MOZ_MAINTENANCE_SERVICE
+@BINPATH@/maintenanceservice.exe
+@BINPATH@/maintenanceservice_installer.exe
+#endif
+
+; [Crash Reporter]
+;
+#ifdef MOZ_CRASHREPORTER
+@RESPATH@/components/CrashService.manifest
+@RESPATH@/components/CrashService.js
+@RESPATH@/components/toolkit_crashservice.xpt
+#ifdef XP_MACOSX
+@BINPATH@/crashreporter.app/
+#else
+@BINPATH@/crashreporter@BIN_SUFFIX@
+@BINPATH@/minidump-analyzer@BIN_SUFFIX@
+@RESPATH@/crashreporter.ini
+#ifdef XP_UNIX
+@RESPATH@/Throbber-small.gif
+#endif
+#endif
+@RESPATH@/browser/crashreporter-override.ini
+#ifdef MOZ_CRASHREPORTER_INJECTOR
+@BINPATH@/breakpadinjector.dll
+#endif
+#endif
+
+@RESPATH@/components/dom_audiochannel.xpt
+
+; Shutdown Terminator
+@RESPATH@/components/nsTerminatorTelemetry.js
+@RESPATH@/components/terminator.manifest
+
+#if defined(CLANG_CXX)
+#if defined(MOZ_ASAN) || defined(MOZ_TSAN)
+@BINPATH@/llvm-symbolizer
+#endif
+#endif
+
+#if defined(MOZ_ASAN) && defined(CLANG_CL)
+@BINPATH@/clang_rt.asan_dynamic-*.dll
+#endif
+
+
+; media
+@RESPATH@/gmp-clearkey/0.1/@DLL_PREFIX@clearkey@DLL_SUFFIX@
+@RESPATH@/gmp-clearkey/0.1/clearkey.info
+
+; gfx
+#ifdef XP_WIN
+@RESPATH@/components/GfxSanityTest.manifest
+@RESPATH@/components/SanityTest.js
+#endif
+
+#ifdef MOZ_MULET
+#include ../../b2g/installer/package-manifest.in
+#endif
diff --git a/browser/installer/removed-files.in b/browser/installer/removed-files.in
new file mode 100644
index 000000000..8801d1cb0
--- /dev/null
+++ b/browser/installer/removed-files.in
@@ -0,0 +1,116 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# The removed-files.in file specifies files and directories to be removed during
+# an application update that are not automatically removed by the application
+# update process. The application update process handles the vast majority of
+# file and directory removals automatically so this file should not be used in
+# the vast majority of cases.
+
+# When to use removed-files.in file to remove files and directories:
+# * Files and directories located in the installation's "distribution/" and
+# "extensions/" directories that were added before Firefox 27. Files and
+# directories located in these directories were not included in the
+# application update file removals for a complete update prior to Firefox 27.
+# * Empty directories that were accidentally added to the installation
+# directory.
+# * Third party files and directories that were added to the installation
+# directory. Under normal circumstances this should only be done after release
+# drivers have approved the removal of these third party files.
+
+# If you are not sure whether a file or directory should be removed using the
+# removed-files.in file please contact one of the developers that work on
+# application update.
+
+# Note: the "distribution/" and "browser/extensions/" directories should never
+# be removed recursively since these directories are used by Partner builds and
+# custom installations.
+
+# To specify a file to be removed add the path to the file.
+# * If the file doesn't exist the update will succeed.
+# * If the file exists and can't be removed (e.g. the file is locked) the
+# update will fail.
+#
+# Example: path/to/file
+
+# To specify a directory to be removed only if it is empty add the path to the
+# directory with a trailing forward slash.
+# * If the directory doesn't exist the update will succeed.
+# * If the directory can't be removed (e.g. the directory is locked, contains
+# files, etc.) the update will succeed.
+#
+# Example: path/to/dir/
+
+# To specify a directory that should be recursively removed add the path to the
+# directory with a trailing forward slash and "*".
+# * If the directory doesn't exist the update will succeed.
+# * If all of the files the directory contains can be removed but the directory
+# or a subdirectory can't be removed (e.g. the directory is locked) the update
+# will succeed.
+# * If a file within the directory can't be removed the update will fail.
+#
+# Example: path/to/dir/*
+
+# Due to Apple Mac OS X packaging requirements files that are in the same
+# directory on other platforms must be located in different directories on
+# Mac OS X. The following defines allow specifying the Mac OS X bundle
+# location which also work on other platforms.
+#
+# @DIR_MACOS@
+# Equals Contents/MacOS/ on Mac OS X and is an empty string on other platforms.
+#
+# @DIR_RESOURCES@
+# Equals Contents/Resources/ on Mac OS X and is an empty string on other
+# platforms.
+
+# Common File Removals
+# This is located under the "distribution/" directory and it was added before
+# Firefox 27
+@DIR_MACOS@distribution/extensions/testpilot@labs.mozilla.com.xpi
+
+# Mac OS X v2 signing removals
+#ifdef XP_MACOSX
+ @DIR_MACOS@active-update.xml
+ @DIR_MACOS@update-settings.ini
+ @DIR_MACOS@updates.xml
+ @DIR_MACOS@defaults/*
+ @DIR_MACOS@updates/*
+#endif
+
+# Common Directory removals
+@DIR_MACOS@chrome/
+#ifdef XP_UNIX
+ #ifndef XP_MACOSX
+ chrome/icons/
+ chrome/icons/default/
+ #endif
+#endif
+@DIR_MACOS@chrome/overlayinfo/
+@DIR_MACOS@components/
+@DIR_MACOS@defaults/autoconfig/
+@DIR_MACOS@defaults/profile/
+@DIR_MACOS@defaults/profile/chrome/
+@DIR_MACOS@defaults/profile/US/*
+@DIR_MACOS@defaults/profile/extensions/
+@DIR_MACOS@defaults/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/*
+@DIR_MACOS@distribution/
+@DIR_MACOS@distribution/extensions/
+@DIR_MACOS@extensions/
+@DIR_MACOS@extensions/inspector@mozilla.org/*
+@DIR_MACOS@extensions/reporter@mozilla.org/*
+@DIR_MACOS@extensions/talkback@mozilla.org/*
+@DIR_MACOS@extensions/testpilot@labs.mozilla.com/*
+@DIR_MACOS@extensions/{641d8d09-7dda-4850-8228-ac0ab65e2ac9}/*
+@DIR_MACOS@extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/*
+@DIR_MACOS@greprefs/
+@DIR_MACOS@jssubloader/
+@DIR_MACOS@modules/
+#ifdef XP_MACOSX
+ @DIR_MACOS@plugins/Default Plugin.plugin/*
+ @DIR_MACOS@plugins/JavaEmbeddingPlugin.bundle/*
+ @DIR_MACOS@plugins/MRJPlugin.plugin/*
+ Contents/Plug-Ins/PrintPDE.plugin/*
+#endif
+@DIR_MACOS@searchplugins/*
+@DIR_MACOS@webapprt/components/
diff --git a/browser/installer/windows/Makefile.in b/browser/installer/windows/Makefile.in
new file mode 100644
index 000000000..fa6fd5ba9
--- /dev/null
+++ b/browser/installer/windows/Makefile.in
@@ -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/.
+
+include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
+
+CONFIG_DIR = instgen
+SFX_MODULE = $(topsrcdir)/other-licenses/7zstub/firefox/7zSD.sfx
+
+INSTALLER_FILES = \
+ app.tag \
+ nsis/installer.nsi \
+ nsis/uninstaller.nsi \
+ nsis/stub.nsi \
+ nsis/shared.nsh \
+ stub.tag \
+ $(NULL)
+
+ifdef MOZ_MAINTENANCE_SERVICE
+INSTALLER_FILES += \
+ nsis/maintenanceservice_installer.nsi \
+ $(NULL)
+endif
+
+BRANDING_FILES = \
+ branding.nsi \
+ appname.bmp \
+ bgintro.bmp \
+ clock.bmp \
+ particles.bmp \
+ pencil.bmp \
+ pencil-rtl.bmp \
+ wizHeader.bmp \
+ wizHeaderRTL.bmp \
+ wizWatermark.bmp \
+ $(NULL)
+
+include $(topsrcdir)/config/config.mk
+
+ifdef LOCALE_MERGEDIR
+PPL_LOCALE_ARGS = \
+ --l10n-dir=$(LOCALE_MERGEDIR)/browser/installer \
+ --l10n-dir=$(call EXPAND_LOCALE_SRCDIR,browser/locales)/installer \
+ --l10n-dir=$(topsrcdir)/browser/locales/en-US/installer \
+ $(NULL)
+else
+PPL_LOCALE_ARGS=$(call EXPAND_LOCALE_SRCDIR,browser/locales)/installer
+endif
+
+OVERRIDE_DEFAULT_GOAL := installer
+installer::
+ $(MAKE) -C .. installer-stage
+ $(MAKE) $(CONFIG_DIR)/setup.exe
+
+# For building the uninstaller during the application build so it can be
+# included for mar file generation.
+uninstaller::
+ $(RM) -r $(CONFIG_DIR)
+ $(MKDIR) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR)
+ $(call py_action,preprocessor,-Fsubstitution $(DEFINES) $(ACDEFINES) \
+ $(srcdir)/nsis/defines.nsi.in -o $(CONFIG_DIR)/defines.nsi)
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
+ --preprocess-locale $(topsrcdir) \
+ $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR)
+
+# For building the maintenanceservice installer
+ifdef MOZ_MAINTENANCE_SERVICE
+maintenanceservice_installer::
+ $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR)
+ $(call py_action,preprocessor,-Fsubstitution $(DEFINES) $(ACDEFINES) \
+ $(srcdir)/nsis/defines.nsi.in -o $(CONFIG_DIR)/defines.nsi)
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
+ --preprocess-locale $(topsrcdir) \
+ $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR)
+endif
+
+$(CONFIG_DIR)/setup.exe::
+ $(RM) -r $(CONFIG_DIR)
+ $(MKDIR) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR)
+ $(call py_action,preprocessor,-Fsubstitution $(DEFINES) $(ACDEFINES) \
+ $(srcdir)/nsis/defines.nsi.in -o $(CONFIG_DIR)/defines.nsi)
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
+ --preprocess-locale $(topsrcdir) \
+ $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR)
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
+ --preprocess-single-file $(topsrcdir) \
+ $(PPL_LOCALE_ARGS) $(CONFIG_DIR) \
+ nsisstrings.properties nsisstrings.nlf
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
+ --convert-utf8-utf16le \
+ $(srcdir)/nsis/oneoff_en-US.nsh $(CONFIG_DIR)/oneoff_en-US.nsh
+
+GARBARGE_DIRS += instgen
+
+include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/makensis.mk
diff --git a/browser/installer/windows/app.tag b/browser/installer/windows/app.tag
new file mode 100644
index 000000000..479d9f714
--- /dev/null
+++ b/browser/installer/windows/app.tag
@@ -0,0 +1,4 @@
+;!@Install@!UTF-8!
+Title="Mozilla Firefox"
+RunProgram="setup.exe"
+;!@InstallEnd@! \ No newline at end of file
diff --git a/browser/installer/windows/moz.build b/browser/installer/windows/moz.build
new file mode 100644
index 000000000..b953b57a0
--- /dev/null
+++ b/browser/installer/windows/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['APP_VERSION'] = CONFIG['FIREFOX_VERSION']
+
+DEFINES['MOZ_APP_NAME'] = CONFIG['MOZ_APP_NAME']
+DEFINES['MOZ_APP_DISPLAYNAME'] = CONFIG['MOZ_APP_DISPLAYNAME']
+DEFINES['MOZILLA_VERSION'] = CONFIG['MOZILLA_VERSION']
diff --git a/browser/installer/windows/nsis/defines.nsi.in b/browser/installer/windows/nsis/defines.nsi.in
new file mode 100644
index 000000000..ffb23ff1c
--- /dev/null
+++ b/browser/installer/windows/nsis/defines.nsi.in
@@ -0,0 +1,110 @@
+#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/.
+
+# Defining FunnelcakeVersion will append the value of StubURLVersionAppend to
+# StubURLVersion, append the value of URLManualDownloadAppend to
+# URLManualDownload, and append the value of URLStubDownloadAppend to
+# URLStubDownload. The value of FunnelcakeVersion should not be defined when it
+# is not used and when it is defined its value should never be empty.
+# !define FunnelcakeVersion "999"
+
+!ifdef FunnelcakeVersion
+!define URLManualDownloadAppend "&f=${FunnelcakeVersion}"
+!define URLStubDownloadAppend "-f${FunnelcakeVersion}"
+!define StubURLVersionAppend "-${FunnelcakeVersion}"
+!else
+!define URLManualDownloadAppend ""
+!define URLStubDownloadAppend ""
+!define StubURLVersionAppend ""
+!endif
+
+# These defines should match application.ini settings
+!define AppName "Firefox"
+!define AppVersion "@APP_VERSION@"
+!define GREVersion @MOZILLA_VERSION@
+!define AB_CD "@AB_CD@"
+
+!define FileMainEXE "@MOZ_APP_NAME@.exe"
+!define WindowClass "FirefoxMessageWindow"
+!define DDEApplication "Firefox"
+!define AppRegName "Firefox"
+
+!ifndef DEV_EDITION
+!define BrandShortName "@MOZ_APP_DISPLAYNAME@"
+!endif
+!define BrandFullName "${BrandFullNameInternal}"
+
+!define CERTIFICATE_NAME "Mozilla Corporation"
+!define CERTIFICATE_ISSUER "DigiCert SHA2 Assured ID Code Signing CA"
+; Changing the name or issuer requires us to have both the old and the new
+; in the registry at the same time, temporarily.
+!define CERTIFICATE_NAME_PREVIOUS "Mozilla Corporation"
+!define CERTIFICATE_ISSUER_PREVIOUS "DigiCert Assured ID Code Signing CA-1"
+
+# LSP_CATEGORIES is the permitted LSP categories for the application. Each LSP
+# category value is ANDed together to set multiple permitted categories.
+# See http://msdn.microsoft.com/en-us/library/ms742253%28VS.85%29.aspx
+# The value below removes all LSP categories previously set.
+!define LSP_CATEGORIES "0x00000000"
+
+!if "@MOZ_UPDATE_CHANNEL@" == ""
+!define UpdateChannel "Unknown"
+!else
+!define UpdateChannel "@MOZ_UPDATE_CHANNEL@"
+!endif
+
+# Due to official and beta using the same branding this is needed to
+# differentiante between the url used by the stub for downloading.
+!if "@MOZ_UPDATE_CHANNEL@" == "beta"
+!define BETA_UPDATE_CHANNEL
+!endif
+
+!define BaseURLStubPing "http://download-stats.mozilla.org/stub"
+
+# ARCH is used when it is necessary to differentiate the x64 registry keys from
+# the x86 registry keys (e.g. the uninstall registry key).
+#ifdef HAVE_64BIT_BUILD
+!define HAVE_64BIT_BUILD
+!define ARCH "x64"
+!define MinSupportedVer "Microsoft Windows 7 x64"
+#else
+!define ARCH "x86"
+!define MinSupportedVer "Microsoft Windows XP SP2"
+#endif
+
+!define MinSupportedCPU "SSE2"
+
+#ifdef MOZ_MAINTENANCE_SERVICE
+!define MOZ_MAINTENANCE_SERVICE
+#endif
+
+# File details shared by both the installer and uninstaller
+VIProductVersion "1.0.0.0"
+VIAddVersionKey "ProductName" "${BrandShortName}"
+VIAddVersionKey "CompanyName" "${CompanyName}"
+#ifdef MOZ_OFFICIAL_BRANDING
+VIAddVersionKey "LegalTrademarks" "${BrandShortName} is a Trademark of The Mozilla Foundation."
+#endif
+VIAddVersionKey "LegalCopyright" "${CompanyName}"
+VIAddVersionKey "FileVersion" "${AppVersion}"
+VIAddVersionKey "ProductVersion" "${AppVersion}"
+# Comments is not used but left below commented out for future reference
+# VIAddVersionKey "Comments" "Comments"
+
+# It isn't possible to get the size of the installation prior to downloading
+# so the stub installer uses an estimate. The size is derived from the size of
+# the complete installer, the size of the extracted complete installer, and at
+# least 15 MB additional for working room.
+!define APPROXIMATE_REQUIRED_SPACE_MB "145"
+
+# Control positions in Dialog Units so they are placed correctly with
+# non-default DPI settings
+!define OPTIONS_ITEM_EDGE_DU 90u
+!define OPTIONS_ITEM_WIDTH_DU 356u
+!define OPTIONS_SUBITEM_EDGE_DU 119u
+!define OPTIONS_SUBITEM_WIDTH_DU 327u
+!define INSTALL_BLURB_TOP_DU 78u
+!define APPNAME_BMP_EDGE_DU 19u
+!define APPNAME_BMP_TOP_DU 12u
diff --git a/browser/installer/windows/nsis/installer.nsi b/browser/installer/windows/nsis/installer.nsi
new file mode 100755
index 000000000..1f20c01eb
--- /dev/null
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -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/.
+
+# Required Plugins:
+# AppAssocReg http://nsis.sourceforge.net/Application_Association_Registration_plug-in
+# ApplicationID http://nsis.sourceforge.net/ApplicationID_plug-in
+# CityHash http://dxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash
+# ShellLink http://nsis.sourceforge.net/ShellLink_plug-in
+# UAC http://nsis.sourceforge.net/UAC_plug-in
+# ServicesHelper Mozilla specific plugin that is located in /other-licenses/nsis
+
+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
+!verbose 3
+
+; 7-Zip provides better compression than the lzma from NSIS so we add the files
+; uncompressed and use 7-Zip to create a SFX archive of it
+SetDatablockOptimize on
+SetCompress off
+CRCCheck on
+
+RequestExecutionLevel user
+
+; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
+; be removed after we require NSIS 3.0a2 or greater.
+!ifdef NSIS_PACKEDVERSION
+ Unicode true
+ ManifestSupportedOS all
+ ManifestDPIAware true
+!endif
+
+!addplugindir ./
+
+Var TmpVal
+Var InstallType
+Var AddStartMenuSC
+Var AddTaskbarSC
+Var AddQuickLaunchSC
+Var AddDesktopSC
+Var InstallMaintenanceService
+Var PageName
+Var PreventRebootRequired
+
+; By defining NO_STARTMENU_DIR an installer that doesn't provide an option for
+; an application's Start Menu PROGRAMS directory and doesn't define the
+; StartMenuDir variable can use the common InstallOnInitCommon macro.
+!define NO_STARTMENU_DIR
+
+; On Vista and above attempt to elevate Standard Users in addition to users that
+; are a member of the Administrators group.
+!define NONADMIN_ELEVATE
+
+!define AbortSurveyURL "http://www.kampyle.com/feedback_form/ff-feedback-form.php?site_code=8166124&form_id=12116&url="
+
+; Other included files may depend upon these includes!
+; The following includes are provided by NSIS.
+!include FileFunc.nsh
+!include LogicLib.nsh
+!include MUI.nsh
+!include WinMessages.nsh
+!include WinVer.nsh
+!include WordFunc.nsh
+
+!insertmacro GetOptions
+!insertmacro GetParameters
+!insertmacro GetSize
+!insertmacro StrFilter
+!insertmacro WordFind
+!insertmacro WordReplace
+
+; The following includes are custom.
+!include branding.nsi
+!include defines.nsi
+!include common.nsh
+!include locales.nsi
+
+VIAddVersionKey "FileDescription" "${BrandShortName} Installer"
+VIAddVersionKey "OriginalFilename" "setup.exe"
+
+; Must be inserted before other macros that use logging
+!insertmacro _LoggingCommon
+
+!insertmacro AddDisabledDDEHandlerValues
+!insertmacro ChangeMUIHeaderImage
+!insertmacro CheckForFilesInUse
+!insertmacro CleanUpdateDirectories
+!insertmacro CopyFilesFromDir
+!insertmacro CreateRegKey
+!insertmacro GetLongPath
+!insertmacro GetPathFromString
+!insertmacro GetParent
+!insertmacro InitHashAppModelId
+!insertmacro IsHandlerForInstallDir
+!insertmacro IsPinnedToTaskBar
+!insertmacro IsUserAdmin
+!insertmacro LogDesktopShortcut
+!insertmacro LogQuickLaunchShortcut
+!insertmacro LogStartMenuShortcut
+!insertmacro ManualCloseAppPrompt
+!insertmacro PinnedToStartMenuLnkCount
+!insertmacro RegCleanAppHandler
+!insertmacro RegCleanMain
+!insertmacro RegCleanUninstall
+!insertmacro RemovePrecompleteEntries
+!insertmacro SetAppLSPCategories
+!insertmacro SetBrandNameVars
+!insertmacro UpdateShortcutAppModelIDs
+!insertmacro UnloadUAC
+!insertmacro WriteRegStr2
+!insertmacro WriteRegDWORD2
+
+!include shared.nsh
+
+; Helper macros for ui callbacks. Insert these after shared.nsh
+!insertmacro CheckCustomCommon
+!insertmacro InstallEndCleanupCommon
+!insertmacro InstallOnInitCommon
+!insertmacro InstallStartCleanupCommon
+!insertmacro LeaveDirectoryCommon
+!insertmacro LeaveOptionsCommon
+!insertmacro OnEndCommon
+!insertmacro PreDirectoryCommon
+
+Name "${BrandFullName}"
+OutFile "setup.exe"
+!ifdef HAVE_64BIT_BUILD
+ InstallDir "$PROGRAMFILES64\${BrandFullName}\"
+!else
+ InstallDir "$PROGRAMFILES32\${BrandFullName}\"
+!endif
+ShowInstDetails nevershow
+
+################################################################################
+# Modern User Interface - MUI
+
+!define MOZ_MUI_CUSTOM_ABORT
+!define MUI_CUSTOMFUNCTION_ABORT "CustomAbort"
+!define MUI_ICON setup.ico
+!define MUI_UNICON setup.ico
+!define MUI_WELCOMEPAGE_TITLE_3LINES
+!define MUI_HEADERIMAGE
+!define MUI_HEADERIMAGE_RIGHT
+!define MUI_WELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
+
+; Use a right to left header image when the language is right to left
+!ifdef ${AB_CD}_rtl
+!define MUI_HEADERIMAGE_BITMAP_RTL wizHeaderRTL.bmp
+!else
+!define MUI_HEADERIMAGE_BITMAP wizHeader.bmp
+!endif
+
+/**
+ * Installation Pages
+ */
+; Welcome Page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE preWelcome
+!insertmacro MUI_PAGE_WELCOME
+
+; Custom Options Page
+Page custom preOptions leaveOptions
+
+; Select Install Directory Page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE preDirectory
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE leaveDirectory
+!define MUI_DIRECTORYPAGE_VERIFYONLEAVE
+!insertmacro MUI_PAGE_DIRECTORY
+
+; Custom Components Page
+!ifdef MOZ_MAINTENANCE_SERVICE
+Page custom preComponents leaveComponents
+!endif
+
+; Custom Shortcuts Page
+Page custom preShortcuts leaveShortcuts
+
+; Custom Summary Page
+Page custom preSummary leaveSummary
+
+; Install Files Page
+!insertmacro MUI_PAGE_INSTFILES
+
+; Finish Page
+!define MUI_FINISHPAGE_TITLE_3LINES
+!define MUI_FINISHPAGE_RUN
+!define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
+!define MUI_FINISHPAGE_RUN_TEXT $(LAUNCH_TEXT)
+!define MUI_PAGE_CUSTOMFUNCTION_PRE preFinish
+!insertmacro MUI_PAGE_FINISH
+
+; Use the default dialog for IDD_VERIFY for a simple Banner
+ChangeUI IDD_VERIFY "${NSISDIR}\Contrib\UIs\default.exe"
+
+################################################################################
+# Install Sections
+
+; Cleanup operations to perform at the start of the installation.
+Section "-InstallStartCleanup"
+ SetDetailsPrint both
+ DetailPrint $(STATUS_CLEANUP)
+ SetDetailsPrint none
+
+ SetOutPath "$INSTDIR"
+ ${StartInstallLog} "${BrandFullName}" "${AB_CD}" "${AppVersion}" "${GREVersion}"
+
+ StrCpy $R9 "true"
+ StrCpy $PreventRebootRequired "false"
+ ${GetParameters} $R8
+ ${GetOptions} "$R8" "/INI=" $R7
+ ${Unless} ${Errors}
+ ; The configuration file must also exist
+ ${If} ${FileExists} "$R7"
+ ReadINIStr $R9 $R7 "Install" "RemoveDistributionDir"
+ ReadINIStr $R8 $R7 "Install" "PreventRebootRequired"
+ ${If} $R8 == "true"
+ StrCpy $PreventRebootRequired "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ ; Remove directories and files we always control before parsing the uninstall
+ ; log so empty directories can be removed.
+ ${If} ${FileExists} "$INSTDIR\updates"
+ RmDir /r "$INSTDIR\updates"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\updated"
+ RmDir /r "$INSTDIR\updated"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\shortcuts"
+ RmDir /r "$INSTDIR\defaults\shortcuts"
+ ${EndIf}
+ ; Only remove the distribution directory if it exists and if the installer
+ ; isn't launched with an ini file that has RemoveDistributionDir=false in the
+ ; install section.
+ ${If} ${FileExists} "$INSTDIR\distribution"
+ ${AndIf} $R9 != "false"
+ RmDir /r "$INSTDIR\distribution"
+ ${EndIf}
+
+ ; Delete the app exe if present to prevent launching the app while we are
+ ; installing.
+ ClearErrors
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ${If} ${Errors}
+ ; If the user closed the application it can take several seconds for it to
+ ; shut down completely. If the application is being used by another user we
+ ; can rename the file and then delete is when the system is restarted.
+ Sleep 5000
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ClearErrors
+ ${EndIf}
+
+ ; setup the application model id registration value
+ ${InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
+
+ ; Remove the updates directory for Vista and above
+ ${CleanUpdateDirectories} "Mozilla\Firefox" "Mozilla\updates"
+
+ ${RemoveDeprecatedFiles}
+ ${RemovePrecompleteEntries} "false"
+
+ ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
+ Delete "$INSTDIR\defaults\pref\channel-prefs.js"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\pref"
+ RmDir "$INSTDIR\defaults\pref"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults"
+ RmDir "$INSTDIR\defaults"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\uninstall"
+ ; Remove the uninstall directory that we control
+ RmDir /r "$INSTDIR\uninstall"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\update-settings.ini"
+ Delete "$INSTDIR\update-settings.ini"
+ ${EndIf}
+
+ ; Explictly remove empty webapprt dir in case it exists (bug 757978).
+ RmDir "$INSTDIR\webapprt\components"
+ RmDir "$INSTDIR\webapprt"
+
+ ${InstallStartCleanupCommon}
+SectionEnd
+
+Section "-Application" APP_IDX
+ ${StartUninstallLog}
+
+ SetDetailsPrint both
+ DetailPrint $(STATUS_INSTALL_APP)
+ SetDetailsPrint none
+
+ ${LogHeader} "Installing Main Files"
+ ${CopyFilesFromDir} "$EXEDIR\core" "$INSTDIR" \
+ "$(ERROR_CREATE_DIRECTORY_PREFIX)" \
+ "$(ERROR_CREATE_DIRECTORY_SUFFIX)"
+
+ ; Register DLLs
+ ; XXXrstrong - AccessibleMarshal.dll can be used by multiple applications but
+ ; is only registered for the last application installed. When the last
+ ; application installed is uninstalled AccessibleMarshal.dll will no longer be
+ ; registered. bug 338878
+ ${LogHeader} "DLL Registration"
+ ClearErrors
+ ${RegisterDLL} "$INSTDIR\AccessibleMarshal.dll"
+ ${If} ${Errors}
+ ${LogMsg} "** ERROR Registering: $INSTDIR\AccessibleMarshal.dll **"
+ ${Else}
+ ${LogUninstall} "DLLReg: \AccessibleMarshal.dll"
+ ${LogMsg} "Registered: $INSTDIR\AccessibleMarshal.dll"
+ ${EndIf}
+
+ ClearErrors
+
+ ; Default for creating Start Menu shortcut
+ ; (1 = create, 0 = don't create)
+ ${If} $AddStartMenuSC == ""
+ StrCpy $AddStartMenuSC "1"
+ ${EndIf}
+
+ ; Default for creating Quick Launch shortcut (1 = create, 0 = don't create)
+ ${If} $AddQuickLaunchSC == ""
+ ; Don't install the quick launch shortcut on Windows 7
+ ${If} ${AtLeastWin7}
+ StrCpy $AddQuickLaunchSC "0"
+ ${Else}
+ StrCpy $AddQuickLaunchSC "1"
+ ${EndIf}
+ ${EndIf}
+
+ ; Default for creating Desktop shortcut (1 = create, 0 = don't create)
+ ${If} $AddDesktopSC == ""
+ StrCpy $AddDesktopSC "1"
+ ${EndIf}
+
+ ${LogHeader} "Adding Registry Entries"
+ SetShellVarContext current ; Set SHCTX to HKCU
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ StrCpy $TmpVal "HKCU" ; used primarily for logging
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to HKLM
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $TmpVal "HKLM" ; used primarily for logging
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+
+ ReadRegStr $0 HKLM "Software\mozilla.org\Mozilla" "CurrentVersion"
+ ${If} "$0" != "${GREVersion}"
+ WriteRegStr HKLM "Software\mozilla.org\Mozilla" "CurrentVersion" "${GREVersion}"
+ ${EndIf}
+ ${EndIf}
+
+ ${RemoveDeprecatedKeys}
+
+ ; The previous installer adds several regsitry values to both HKLM and HKCU.
+ ; We now try to add to HKLM and if that fails to HKCU
+
+ ; The order that reg keys and values are added is important if you use the
+ ; uninstall log to remove them on uninstall. When using the uninstall log you
+ ; MUST add children first so they will be removed first on uninstall so they
+ ; will be empty when the key is deleted. This allows the uninstaller to
+ ; specify that only empty keys will be deleted.
+ ${SetAppKeys}
+
+ ${FixClassKeys}
+
+ ; Uninstall keys can only exist under HKLM on some versions of windows. Since
+ ; it doesn't cause problems always add them.
+ ${SetUninstallKeys}
+
+ ; On install always add the FirefoxHTML and FirefoxURL keys.
+ ; An empty string is used for the 5th param because FirefoxHTML is not a
+ ; protocol handler.
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+ StrCpy $2 "$\"$8$\" -osint -url $\"%1$\""
+
+ ; In Win8, the delegate execute handler picks up the value in FirefoxURL and
+ ; FirefoxHTML to launch the desktop browser when it needs to.
+ ${AddDisabledDDEHandlerValues} "FirefoxHTML" "$2" "$8,1" \
+ "${AppRegName} Document" ""
+ ${AddDisabledDDEHandlerValues} "FirefoxURL" "$2" "$8,1" "${AppRegName} URL" \
+ "true"
+
+ ; For pre win8, the following keys should only be set if we can write to HKLM.
+ ; For post win8, the keys below get set in both HKLM and HKCU.
+ ${If} $TmpVal == "HKLM"
+ ; Set the Start Menu Internet and Vista Registered App HKLM registry keys.
+ ${SetStartMenuInternet} "HKLM"
+ ${FixShellIconHandler} "HKLM"
+
+ ; If we are writing to HKLM and create either the desktop or start menu
+ ; shortcuts set IconsVisible to 1 otherwise to 0.
+ ; Taskbar shortcuts imply having a start menu shortcut.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9\InstallInfo"
+ ${If} $AddDesktopSC == 1
+ ${OrIf} $AddStartMenuSC == 1
+ ${OrIf} $AddTaskbarSC == 1
+ WriteRegDWORD HKLM "$0" "IconsVisible" 1
+ ${Else}
+ WriteRegDWORD HKLM "$0" "IconsVisible" 0
+ ${EndIf}
+ ${EndIf}
+
+ ${If} ${AtLeastWin8}
+ ; Set the Start Menu Internet and Vista Registered App HKCU registry keys.
+ ${SetStartMenuInternet} "HKCU"
+ ${FixShellIconHandler} "HKCU"
+
+ ; If we create either the desktop or start menu shortcuts, then
+ ; set IconsVisible to 1 otherwise to 0.
+ ; Taskbar shortcuts imply having a start menu shortcut.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9\InstallInfo"
+ ${If} $AddDesktopSC == 1
+ ${OrIf} $AddStartMenuSC == 1
+ ${OrIf} $AddTaskbarSC == 1
+ WriteRegDWORD HKCU "$0" "IconsVisible" 1
+ ${Else}
+ WriteRegDWORD HKCU "$0" "IconsVisible" 0
+ ${EndIf}
+ ${EndIf}
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+ ; If the maintenance service page was displayed then a value was already
+ ; explicitly selected for installing the maintenance service and
+ ; and so InstallMaintenanceService will already be 0 or 1.
+ ; If the maintenance service page was not displayed then
+ ; InstallMaintenanceService will be equal to "".
+ ${If} $InstallMaintenanceService == ""
+ Call IsUserAdmin
+ Pop $R0
+ ${If} $R0 == "true"
+ ; Only proceed if we have HKLM write access
+ ${AndIf} $TmpVal == "HKLM"
+ ; On Windows < XP SP3 we do not install the maintenance service.
+ ${If} ${IsWinXP}
+ ${AndIf} ${AtMostServicePack} 2
+ StrCpy $InstallMaintenanceService "0"
+ ${Else}
+ ; The user is an admin, so we should default to installing the service.
+ StrCpy $InstallMaintenanceService "1"
+ ${EndIf}
+ ${Else}
+ ; The user is not admin, so we can't install the service.
+ StrCpy $InstallMaintenanceService "0"
+ ${EndIf}
+ ${EndIf}
+
+ ${If} $InstallMaintenanceService == "1"
+ ; The user wants to install the maintenance service, so execute
+ ; the pre-packaged maintenance service installer.
+ ; This option can only be turned on if the user is an admin so there
+ ; is no need to use ExecShell w/ verb runas to enforce elevated.
+ nsExec::Exec "$\"$INSTDIR\maintenanceservice_installer.exe$\""
+ ${EndIf}
+!endif
+
+ ; These need special handling on uninstall since they may be overwritten by
+ ; an install into a different location.
+ StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\App Paths\${FileMainEXE}"
+ ${WriteRegStr2} $TmpVal "$0" "" "$INSTDIR\${FileMainEXE}" 0
+ ${WriteRegStr2} $TmpVal "$0" "Path" "$INSTDIR" 0
+
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\$R9"
+ ${CreateRegKey} "$TmpVal" "$0" 0
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\plugin-container.exe"
+ ${CreateRegKey} "$TmpVal" "$0" 0
+
+ ${If} $TmpVal == "HKLM"
+ ; Set the permitted LSP Categories for WinVista and above
+ ${SetAppLSPCategories} ${LSP_CATEGORIES}
+ ${EndIf}
+
+ ; Create shortcuts
+ ${LogHeader} "Adding Shortcuts"
+
+ ; Remove the start menu shortcuts and directory if the SMPROGRAMS section
+ ; exists in the shortcuts_log.ini and the SMPROGRAMS. The installer's shortcut
+ ; creation code will create the shortcut in the root of the Start Menu
+ ; Programs directory.
+ ${RemoveStartMenuDir}
+
+ ; Always add the application's shortcuts to the shortcuts log ini file. The
+ ; DeleteShortcuts macro will do the right thing on uninstall if the
+ ; shortcuts don't exist.
+ ${LogStartMenuShortcut} "${BrandFullName}.lnk"
+ ${LogQuickLaunchShortcut} "${BrandFullName}.lnk"
+ ${LogDesktopShortcut} "${BrandFullName}.lnk"
+
+ ; Best effort to update the Win7 taskbar and start menu shortcut app model
+ ; id's. The possible contexts are current user / system and the user that
+ ; elevated the installer.
+ Call FixShortcutAppModelIDs
+ ; If the current context is all also perform Win7 taskbar and start menu link
+ ; maintenance for the current user context.
+ ${If} $TmpVal == "HKLM"
+ SetShellVarContext current ; Set SHCTX to HKCU
+ Call FixShortcutAppModelIDs
+ SetShellVarContext all ; Set SHCTX to HKLM
+ ${EndIf}
+
+ ; If running elevated also perform Win7 taskbar and start menu link
+ ; maintenance for the unelevated user context in case that is different than
+ ; the current user.
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${Unless} ${Errors}
+ GetFunctionAddress $0 FixShortcutAppModelIDs
+ UAC::ExecCodeSegment $0
+ ${EndUnless}
+
+ ; UAC only allows elevating to an Admin account so there is no need to add
+ ; the Start Menu or Desktop shortcuts from the original unelevated process
+ ; since this will either add it for the user if unelevated or All Users if
+ ; elevated.
+ ${If} $AddStartMenuSC == 1
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${LogMsg} "Added Shortcut: $SMPROGRAMS\${BrandFullName}.lnk"
+ ${Else}
+ ${LogMsg} "** ERROR Adding Shortcut: $SMPROGRAMS\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+
+ ; Update lastwritetime of the Start Menu shortcut to clear the tile cache.
+ ${If} ${AtLeastWin8}
+ ${AndIf} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ FileOpen $0 "$SMPROGRAMS\${BrandFullName}.lnk" a
+ FileClose $0
+ ${EndIf}
+
+ ${If} $AddDesktopSC == 1
+ CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$DESKTOP\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$DESKTOP\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${LogMsg} "Added Shortcut: $DESKTOP\${BrandFullName}.lnk"
+ ${Else}
+ ${LogMsg} "** ERROR Adding Shortcut: $DESKTOP\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+
+ ; If elevated the Quick Launch shortcut must be added from the unelevated
+ ; original process.
+ ${If} $AddQuickLaunchSC == 1
+ ${Unless} ${AtLeastWin7}
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors}
+ Call AddQuickLaunchShortcut
+ ${LogMsg} "Added Shortcut: $QUICKLAUNCH\${BrandFullName}.lnk"
+ ${Else}
+ ; It is not possible to add a log entry from the unelevated process so
+ ; add the log entry without the path since there is no simple way to
+ ; know the correct full path.
+ ${LogMsg} "Added Quick Launch Shortcut: ${BrandFullName}.lnk"
+ GetFunctionAddress $0 AddQuickLaunchShortcut
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+ ${If} $TmpVal == "HKLM"
+ ; Add the registry keys for allowed certificates.
+ ${AddMaintCertKeys}
+ ${EndIf}
+!endif
+SectionEnd
+
+; Cleanup operations to perform at the end of the installation.
+Section "-InstallEndCleanup"
+ SetDetailsPrint both
+ DetailPrint "$(STATUS_CLEANUP)"
+ SetDetailsPrint none
+
+ ${Unless} ${Silent}
+ ClearErrors
+ ${MUI_INSTALLOPTIONS_READ} $0 "summary.ini" "Field 4" "State"
+ ${If} "$0" == "1"
+ ; NB: this code is duplicated in stub.nsi. Please keep in sync.
+ ; For data migration in the app, we want to know what the default browser
+ ; value was before we changed it. To do so, we read it here and store it
+ ; in our own registry key.
+ StrCpy $0 ""
+ ${If} ${AtLeastWinVista}
+ AppAssocReg::QueryCurrentDefault "http" "protocol" "effective"
+ Pop $1
+ ; If the method hasn't failed, $1 will contain the progid. Check:
+ ${If} "$1" != "method failed"
+ ${AndIf} "$1" != "method not available"
+ ; Read the actual command from the progid
+ ReadRegStr $0 HKCR "$1\shell\open\command" ""
+ ${EndIf}
+ ${EndIf}
+ ; If using the App Association Registry didn't happen or failed, fall back
+ ; to the effective http default:
+ ${If} "$0" == ""
+ ReadRegStr $0 HKCR "http\shell\open\command" ""
+ ${EndIf}
+ ; If we have something other than empty string now, write the value.
+ ${If} "$0" != ""
+ ClearErrors
+ WriteRegStr HKCU "Software\Mozilla\Firefox" "OldDefaultBrowserCommand" "$0"
+ ${EndIf}
+
+ ${LogHeader} "Setting as the default browser"
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors}
+ Call SetAsDefaultAppUserHKCU
+ ${Else}
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ ${ElseIfNot} ${Errors}
+ ${LogHeader} "Writing default-browser opt-out"
+ ClearErrors
+ WriteRegStr HKCU "Software\Mozilla\Firefox" "DefaultBrowserOptOut" "True"
+ ${If} ${Errors}
+ ${LogMsg} "Error writing default-browser opt-out"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ ; Adds a pinned Task Bar shortcut (see MigrateTaskBarShortcut for details).
+ ${MigrateTaskBarShortcut}
+
+ ; Add the Firewall entries during install
+ Call AddFirewallEntries
+
+ ; Refresh desktop icons
+ System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_DWORDFLUSH}, i 0, i 0)"
+
+ ${InstallEndCleanupCommon}
+
+ ${If} $PreventRebootRequired == "true"
+ SetRebootFlag false
+ ${EndIf}
+
+ ${If} ${RebootFlag}
+ ; Admin is required to delete files on reboot so only add the moz-delete if
+ ; the user is an admin. After calling UAC::IsAdmin $0 will equal 1 if the
+ ; user is an admin.
+ UAC::IsAdmin
+ ${If} "$0" == "1"
+ ; When a reboot is required give SHChangeNotify time to finish the
+ ; refreshing the icons so the OS doesn't display the icons from helper.exe
+ Sleep 10000
+ ${LogHeader} "Reboot Required To Finish Installation"
+ ; ${FileMainEXE}.moz-upgrade should never exist but just in case...
+ ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-upgrade"
+ Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-upgrade"
+ ${EndUnless}
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ ClearErrors
+ Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-delete"
+ ${Unless} ${Errors}
+ Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
+ ${EndUnless}
+ ${EndIf}
+
+ ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ CopyFiles /SILENT "$INSTDIR\uninstall\helper.exe" "$INSTDIR"
+ FileOpen $0 "$INSTDIR\${FileMainEXE}" w
+ FileWrite $0 "Will be deleted on restart"
+ Rename /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-upgrade" "$INSTDIR\${FileMainEXE}"
+ FileClose $0
+ Delete "$INSTDIR\${FileMainEXE}"
+ Rename "$INSTDIR\helper.exe" "$INSTDIR\${FileMainEXE}"
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+SectionEnd
+
+################################################################################
+# Install Abort Survey Functions
+
+Function CustomAbort
+ ${If} "${AB_CD}" == "en-US"
+ ${AndIf} "$PageName" != ""
+ ${AndIf} ${FileExists} "$EXEDIR\core\distribution\distribution.ini"
+ ReadINIStr $0 "$EXEDIR\core\distribution\distribution.ini" "Global" "about"
+ ClearErrors
+ ${WordFind} "$0" "Funnelcake" "E#" $1
+ ${Unless} ${Errors}
+ ; Yes = fill out the survey and exit, No = don't fill out survey and exit,
+ ; Cancel = don't exit.
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION \
+ "Would you like to tell us why you are canceling this installation?" \
+ IDYes +1 IDNO CustomAbort_finish
+ ${If} "$PageName" == "Welcome"
+ GetFunctionAddress $0 AbortSurveyWelcome
+ ${ElseIf} "$PageName" == "Options"
+ GetFunctionAddress $0 AbortSurveyOptions
+ ${ElseIf} "$PageName" == "Directory"
+ GetFunctionAddress $0 AbortSurveyDirectory
+ ${ElseIf} "$PageName" == "Shortcuts"
+ GetFunctionAddress $0 AbortSurveyShortcuts
+ ${ElseIf} "$PageName" == "Summary"
+ GetFunctionAddress $0 AbortSurveySummary
+ ${EndIf}
+ ClearErrors
+ ${GetParameters} $1
+ ${GetOptions} "$1" "/UAC:" $2
+ ${If} ${Errors}
+ Call $0
+ ${Else}
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+
+ CustomAbort_finish:
+ Return
+ ${EndUnless}
+ ${EndIf}
+
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(MOZ_MUI_TEXT_ABORTWARNING)" \
+ IDYES +1 IDNO +2
+ Return
+ Abort
+FunctionEnd
+
+Function AbortSurveyWelcome
+ ExecShell "open" "${AbortSurveyURL}step1"
+FunctionEnd
+
+Function AbortSurveyOptions
+ ExecShell "open" "${AbortSurveyURL}step2"
+FunctionEnd
+
+Function AbortSurveyDirectory
+ ExecShell "open" "${AbortSurveyURL}step3"
+FunctionEnd
+
+Function AbortSurveyShortcuts
+ ExecShell "open" "${AbortSurveyURL}step4"
+FunctionEnd
+
+Function AbortSurveySummary
+ ExecShell "open" "${AbortSurveyURL}step5"
+FunctionEnd
+
+################################################################################
+# Helper Functions
+
+Function AddQuickLaunchShortcut
+ CreateShortCut "$QUICKLAUNCH\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$QUICKLAUNCH\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${EndIf}
+FunctionEnd
+
+Function CheckExistingInstall
+ ; If there is a pending file copy from a previous upgrade don't allow
+ ; installing until after the system has rebooted.
+ IfFileExists "$INSTDIR\${FileMainEXE}.moz-upgrade" +1 +4
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(WARN_RESTART_REQUIRED_UPGRADE)" IDNO +2
+ Reboot
+ Quit
+
+ ; If there is a pending file deletion from a previous uninstall don't allow
+ ; installing until after the system has rebooted.
+ IfFileExists "$INSTDIR\${FileMainEXE}.moz-delete" +1 +4
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(WARN_RESTART_REQUIRED_UNINSTALL)" IDNO +2
+ Reboot
+ Quit
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ ; Disable the next, cancel, and back buttons
+ GetDlgItem $0 $HWNDPARENT 1 ; Next button
+ EnableWindow $0 0
+ GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+ EnableWindow $0 0
+ GetDlgItem $0 $HWNDPARENT 3 ; Back button
+ EnableWindow $0 0
+
+ Banner::show /NOUNLOAD "$(BANNER_CHECK_EXISTING)"
+
+ ${If} "$TmpVal" == "FoundMessageWindow"
+ Sleep 5000
+ ${EndIf}
+
+ ${PushFilesToCheck}
+
+ ; Store the return value in $TmpVal so it is less likely to be accidentally
+ ; overwritten elsewhere.
+ ${CheckForFilesInUse} $TmpVal
+
+ Banner::destroy
+
+ ; Enable the next, cancel, and back buttons
+ GetDlgItem $0 $HWNDPARENT 1 ; Next button
+ EnableWindow $0 1
+ GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+ EnableWindow $0 1
+ GetDlgItem $0 $HWNDPARENT 3 ; Back button
+ EnableWindow $0 1
+
+ ${If} "$TmpVal" == "true"
+ StrCpy $TmpVal "FoundMessageWindow"
+ ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_INSTALL)"
+ StrCpy $TmpVal "true"
+ ${EndIf}
+ ${EndIf}
+FunctionEnd
+
+Function LaunchApp
+!ifndef DEV_EDITION
+ ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_LAUNCH)"
+!endif
+
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $1
+ ${If} ${Errors}
+ Exec "$\"$INSTDIR\${FileMainEXE}$\""
+ ${Else}
+ GetFunctionAddress $0 LaunchAppFromElevatedProcess
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+FunctionEnd
+
+Function LaunchAppFromElevatedProcess
+ ; Find the installation directory when launching using GetFunctionAddress
+ ; from an elevated installer since $INSTDIR will not be set in this installer
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $1
+ ; Set our current working directory to the application's install directory
+ ; otherwise the 7-Zip temp directory will be in use and won't be deleted.
+ SetOutPath "$1"
+ Exec "$\"$0$\""
+FunctionEnd
+
+################################################################################
+# Language
+
+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
+!verbose push
+!verbose 3
+!include "overrideLocale.nsh"
+!include "customLocale.nsh"
+!verbose pop
+
+; Set this after the locale files to override it if it is in the locale
+; using " " for BrandingText will hide the "Nullsoft Install System..." branding
+BrandingText " "
+
+################################################################################
+# Page pre, show, and leave functions
+
+Function preWelcome
+ StrCpy $PageName "Welcome"
+ ${If} ${FileExists} "$EXEDIR\core\distribution\modern-wizard.bmp"
+ Delete "$PLUGINSDIR\modern-wizard.bmp"
+ CopyFiles /SILENT "$EXEDIR\core\distribution\modern-wizard.bmp" "$PLUGINSDIR\modern-wizard.bmp"
+ ${EndIf}
+FunctionEnd
+
+Function preOptions
+ StrCpy $PageName "Options"
+ ${If} ${FileExists} "$EXEDIR\core\distribution\modern-header.bmp"
+ ${AndIf} $hHeaderBitmap == ""
+ Delete "$PLUGINSDIR\modern-header.bmp"
+ CopyFiles /SILENT "$EXEDIR\core\distribution\modern-header.bmp" "$PLUGINSDIR\modern-header.bmp"
+ ${ChangeMUIHeaderImage} "$PLUGINSDIR\modern-header.bmp"
+ ${EndIf}
+ !insertmacro MUI_HEADER_TEXT "$(OPTIONS_PAGE_TITLE)" "$(OPTIONS_PAGE_SUBTITLE)"
+ !insertmacro MUI_INSTALLOPTIONS_DISPLAY "options.ini"
+FunctionEnd
+
+Function leaveOptions
+ ${MUI_INSTALLOPTIONS_READ} $0 "options.ini" "Settings" "State"
+ ${If} $0 != 0
+ Abort
+ ${EndIf}
+ ${MUI_INSTALLOPTIONS_READ} $R0 "options.ini" "Field 2" "State"
+ StrCmp $R0 "1" +1 +2
+ StrCpy $InstallType ${INSTALLTYPE_BASIC}
+ ${MUI_INSTALLOPTIONS_READ} $R0 "options.ini" "Field 3" "State"
+ StrCmp $R0 "1" +1 +2
+ StrCpy $InstallType ${INSTALLTYPE_CUSTOM}
+
+ ${LeaveOptionsCommon}
+
+ ${If} $InstallType == ${INSTALLTYPE_BASIC}
+ Call CheckExistingInstall
+ ${EndIf}
+FunctionEnd
+
+Function preDirectory
+ StrCpy $PageName "Directory"
+ ${PreDirectoryCommon}
+FunctionEnd
+
+Function leaveDirectory
+ ${If} $InstallType == ${INSTALLTYPE_BASIC}
+ Call CheckExistingInstall
+ ${EndIf}
+ ${LeaveDirectoryCommon} "$(WARN_DISK_SPACE)" "$(WARN_WRITE_ACCESS)"
+FunctionEnd
+
+Function preShortcuts
+ StrCpy $PageName "Shortcuts"
+ ${CheckCustomCommon}
+ !insertmacro MUI_HEADER_TEXT "$(SHORTCUTS_PAGE_TITLE)" "$(SHORTCUTS_PAGE_SUBTITLE)"
+ !insertmacro MUI_INSTALLOPTIONS_DISPLAY "shortcuts.ini"
+FunctionEnd
+
+Function leaveShortcuts
+ ${MUI_INSTALLOPTIONS_READ} $0 "shortcuts.ini" "Settings" "State"
+ ${If} $0 != 0
+ Abort
+ ${EndIf}
+ ${MUI_INSTALLOPTIONS_READ} $AddDesktopSC "shortcuts.ini" "Field 2" "State"
+ ${MUI_INSTALLOPTIONS_READ} $AddStartMenuSC "shortcuts.ini" "Field 3" "State"
+
+ ; Don't install the quick launch shortcut on Windows 7
+ ${Unless} ${AtLeastWin7}
+ ${MUI_INSTALLOPTIONS_READ} $AddQuickLaunchSC "shortcuts.ini" "Field 4" "State"
+ ${EndUnless}
+
+ ${If} $InstallType == ${INSTALLTYPE_CUSTOM}
+ Call CheckExistingInstall
+ ${EndIf}
+FunctionEnd
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+Function preComponents
+ ; If the service already exists, don't show this page
+ ServicesHelper::IsInstalled "MozillaMaintenance"
+ Pop $R9
+ ${If} $R9 == 1
+ ; The service already exists so don't show this page.
+ Abort
+ ${EndIf}
+
+ ; On Windows < XP SP3 we do not install the maintenance service.
+ ${If} ${IsWinXP}
+ ${AndIf} ${AtMostServicePack} 2
+ Abort
+ ${EndIf}
+
+ ; Don't show the custom components page if the
+ ; user is not an admin
+ Call IsUserAdmin
+ Pop $R9
+ ${If} $R9 != "true"
+ Abort
+ ${EndIf}
+
+ ; Only show the maintenance service page if we have write access to HKLM
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" \
+ "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ ClearErrors
+ Abort
+ ${Else}
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ ${EndIf}
+
+ StrCpy $PageName "Components"
+ ${CheckCustomCommon}
+ !insertmacro MUI_HEADER_TEXT "$(COMPONENTS_PAGE_TITLE)" "$(COMPONENTS_PAGE_SUBTITLE)"
+ !insertmacro MUI_INSTALLOPTIONS_DISPLAY "components.ini"
+FunctionEnd
+
+Function leaveComponents
+ ${MUI_INSTALLOPTIONS_READ} $0 "components.ini" "Settings" "State"
+ ${If} $0 != 0
+ Abort
+ ${EndIf}
+ ${MUI_INSTALLOPTIONS_READ} $InstallMaintenanceService "components.ini" "Field 2" "State"
+ ${If} $InstallType == ${INSTALLTYPE_CUSTOM}
+ Call CheckExistingInstall
+ ${EndIf}
+FunctionEnd
+!endif
+
+Function preSummary
+ StrCpy $PageName "Summary"
+ ; Setup the summary.ini file for the Custom Summary Page
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NumFields "3"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Text "$(SUMMARY_INSTALLED_TO)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Bottom "15"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Type "text"
+ ; The contents of this control must be set as follows in the pre function
+ ; ${MUI_INSTALLOPTIONS_READ} $1 "summary.ini" "Field 2" "HWND"
+ ; SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" state ""
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Top "17"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Bottom "30"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" flags "READONLY"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Type "label"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Top "130"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Bottom "150"
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Text "$(SUMMARY_UPGRADE_CLICK)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NextButtonText "$(UPGRADE_BUTTON)"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Text "$(SUMMARY_INSTALL_CLICK)"
+ DeleteINIStr "$PLUGINSDIR\summary.ini" "Settings" NextButtonText
+ ${EndIf}
+
+
+ ; Remove the "Field 4" ini section in case the user hits back and changes the
+ ; installation directory which could change whether the make default checkbox
+ ; should be displayed.
+ DeleteINISec "$PLUGINSDIR\summary.ini" "Field 4"
+
+ ; Check if it is possible to write to HKLM
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${Unless} ${Errors}
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ ; Check if Firefox is the http handler for this user.
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${IsHandlerForInstallDir} "http" $R9
+ ${If} $TmpVal == "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users
+ ${EndIf}
+ ; If Firefox isn't the http handler for this user show the option to set
+ ; Firefox as the default browser.
+ ${If} "$R9" != "true"
+ ${AndIf} ${AtMostWin2008R2}
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NumFields "4"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Text "$(SUMMARY_TAKE_DEFAULTS)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" State "1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Top "32"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Bottom "53"
+ ${EndIf}
+ ${EndUnless}
+
+ ${If} "$TmpVal" == "true"
+ ; If there is already a Type entry in the "Field 4" section with a value of
+ ; checkbox then the set as the default browser checkbox is displayed and
+ ; this text must be moved below it.
+ ReadINIStr $0 "$PLUGINSDIR\summary.ini" "Field 4" "Type"
+ ${If} "$0" == "checkbox"
+ StrCpy $0 "5"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Top "53"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Bottom "68"
+ ${Else}
+ StrCpy $0 "4"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Top "35"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Bottom "50"
+ ${EndIf}
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NumFields "$0"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Type "label"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Text "$(SUMMARY_REBOOT_REQUIRED_INSTALL)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Right "-1"
+ ${EndIf}
+
+ !insertmacro MUI_HEADER_TEXT "$(SUMMARY_PAGE_TITLE)" "$(SUMMARY_PAGE_SUBTITLE)"
+
+ ; The Summary custom page has a textbox that will automatically receive
+ ; focus. This sets the focus to the Install button instead.
+ !insertmacro MUI_INSTALLOPTIONS_INITDIALOG "summary.ini"
+ GetDlgItem $0 $HWNDPARENT 1
+ System::Call "user32::SetFocus(i r0, i 0x0007, i,i)i"
+ ${MUI_INSTALLOPTIONS_READ} $1 "summary.ini" "Field 2" "HWND"
+ SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ !insertmacro MUI_INSTALLOPTIONS_SHOW
+FunctionEnd
+
+Function leaveSummary
+ ; Try to delete the app executable and if we can't delete it try to find the
+ ; app's message window and prompt the user to close the app. This allows
+ ; running an instance that is located in another directory. If for whatever
+ ; reason there is no message window we will just rename the app's files and
+ ; then remove them on restart.
+ ClearErrors
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ${If} ${Errors}
+ ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_INSTALL)"
+ ${EndIf}
+FunctionEnd
+
+; When we add an optional action to the finish page the cancel button is
+; enabled. This disables it and leaves the finish button as the only choice.
+Function preFinish
+ StrCpy $PageName ""
+ ${EndInstallLog} "${BrandFullName}"
+ !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "settings" "cancelenabled" "0"
+FunctionEnd
+
+################################################################################
+# Initialization Functions
+
+Function .onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ StrCpy $PageName ""
+ StrCpy $LANGUAGE 0
+ ${SetBrandNameVars} "$EXEDIR\core\distribution\setup.ini"
+
+ ; Don't install on systems that don't support SSE2. The parameter value of
+ ; 10 is for PF_XMMI64_INSTRUCTIONS_AVAILABLE which will check whether the
+ ; SSE2 instruction set is available.
+ System::Call "kernel32::IsProcessorFeaturePresent(i 10)i .R7"
+
+!ifdef HAVE_64BIT_BUILD
+ ; Restrict x64 builds from being installed on x86 and pre Win7
+ ${Unless} ${RunningX64}
+ ${OrUnless} ${AtLeastWin7}
+ ${If} "$R7" == "0"
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
+ ${Else}
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_MSG)"
+ ${EndIf}
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$R7" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndUnless}
+
+ SetRegView 64
+!else
+ StrCpy $R8 "0"
+ ${If} ${AtMostWin2000}
+ StrCpy $R8 "1"
+ ${EndIf}
+
+ ${If} ${IsWinXP}
+ ${AndIf} ${AtMostServicePack} 1
+ StrCpy $R8 "1"
+ ${EndIf}
+
+ ${If} $R8 == "1"
+ ; XXX-rstrong - some systems failed the AtLeastWin2000 test that we
+ ; used to use for an unknown reason and likely fail the AtMostWin2000
+ ; and possibly the IsWinXP test as well. To work around this also
+ ; check if the Windows NT registry Key exists and if it does if the
+ ; first char in CurrentVersion is equal to 3 (Windows NT 3.5 and
+ ; 3.5.1), 4 (Windows NT 4), or 5 (Windows 2000 and Windows XP).
+ StrCpy $R8 ""
+ ClearErrors
+ ReadRegStr $R8 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" "CurrentVersion"
+ StrCpy $R8 "$R8" 1
+ ${If} ${Errors}
+ ${OrIf} "$R8" == "3"
+ ${OrIf} "$R8" == "4"
+ ${OrIf} "$R8" == "5"
+ ${If} "$R7" == "0"
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
+ ${Else}
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_MSG)"
+ ${EndIf}
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$R7" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndIf}
+ ${EndUnless}
+!endif
+
+ ${If} "$R7" == "0"
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_CPU_MSG)" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndIf}
+
+ ${InstallOnInitCommon} "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
+
+; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
+; removed after we require NSIS 3.0a2 or greater.
+!ifndef NSIS_PACKEDVERSION
+ ${If} ${AtLeastWinVista}
+ System::Call 'user32::SetProcessDPIAware()'
+ ${EndIf}
+!endif
+
+ !insertmacro InitInstallOptionsFile "options.ini"
+ !insertmacro InitInstallOptionsFile "shortcuts.ini"
+ !insertmacro InitInstallOptionsFile "components.ini"
+ !insertmacro InitInstallOptionsFile "summary.ini"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Settings" NumFields "5"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Text "$(OPTIONS_SUMMARY)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Top "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Bottom "10"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Type "RadioButton"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Text "$(OPTION_STANDARD_RADIO)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Top "25"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Bottom "35"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" State "1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Flags "GROUP"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Type "RadioButton"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Text "$(OPTION_CUSTOM_RADIO)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Top "55"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Bottom "65"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" State "0"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Type "label"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Text "$(OPTION_STANDARD_DESC)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Left "15"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Top "37"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Bottom "57"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Type "label"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Text "$(OPTION_CUSTOM_DESC)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Left "15"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Top "67"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Bottom "87"
+
+ ; Setup the shortcuts.ini file for the Custom Shortcuts Page
+ ; Don't offer to install the quick launch shortcut on Windows 7
+ ${If} ${AtLeastWin7}
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "3"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "4"
+ ${EndIf}
+
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Text "$(CREATE_ICONS_DESC)"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Bottom "15"
+
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Text "$(ICONS_DESKTOP)"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Top "20"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Bottom "30"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" State "1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Flags "GROUP"
+
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Text "$(ICONS_STARTMENU)"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Top "40"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Bottom "50"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" State "1"
+
+ ; Don't offer to install the quick launch shortcut on Windows 7
+ ${Unless} ${AtLeastWin7}
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Text "$(ICONS_QUICKLAUNCH)"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Left "0"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Right "-1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Top "60"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" Bottom "70"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 4" State "1"
+ ${EndUnless}
+
+ ; Setup the components.ini file for the Components Page
+ WriteINIStr "$PLUGINSDIR\components.ini" "Settings" NumFields "2"
+
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Text "$(OPTIONAL_COMPONENTS_DESC)"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 1" Bottom "25"
+
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Text "$(MAINTENANCE_SERVICE_CHECKBOX_DESC)"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Top "27"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Bottom "37"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" State "1"
+ WriteINIStr "$PLUGINSDIR\components.ini" "Field 2" Flags "GROUP"
+
+ ; There must always be a core directory.
+ ${GetSize} "$EXEDIR\core\" "/S=0K" $R5 $R7 $R8
+ SectionSetSize ${APP_IDX} $R5
+
+ ; Initialize $hHeaderBitmap to prevent redundant changing of the bitmap if
+ ; the user clicks the back button
+ StrCpy $hHeaderBitmap ""
+FunctionEnd
+
+Function .onGUIEnd
+ ${OnEndCommon}
+FunctionEnd
diff --git a/browser/installer/windows/nsis/maintenanceservice_installer.nsi b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
new file mode 100644
index 000000000..ef30c1360
--- /dev/null
+++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
@@ -0,0 +1,335 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
+!verbose 3
+
+; 7-Zip provides better compression than the lzma from NSIS so we add the files
+; uncompressed and use 7-Zip to create a SFX archive of it
+SetDatablockOptimize on
+SetCompress off
+CRCCheck on
+
+RequestExecutionLevel admin
+
+; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
+; be removed after we require NSIS 3.0a2 or greater.
+!ifdef NSIS_PACKEDVERSION
+ Unicode true
+ ManifestSupportedOS all
+ ManifestDPIAware true
+!endif
+
+!addplugindir ./
+
+; Variables
+Var TempMaintServiceName
+Var BrandFullNameDA
+Var BrandFullName
+
+; Other included files may depend upon these includes!
+; The following includes are provided by NSIS.
+!include FileFunc.nsh
+!include LogicLib.nsh
+!include MUI.nsh
+!include WinMessages.nsh
+!include WinVer.nsh
+!include WordFunc.nsh
+
+!insertmacro GetOptions
+!insertmacro GetParameters
+!insertmacro GetSize
+
+; The test slaves use this fallback key to run tests.
+; And anyone that wants to run tests themselves should already have
+; this installed.
+!define FallbackKey \
+ "SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4"
+
+!define CompanyName "Mozilla Corporation"
+!define BrandFullNameInternal ""
+
+; The following includes are custom.
+!include defines.nsi
+; We keep defines.nsi defined so that we get other things like
+; the version number, but we redefine BrandFullName
+!define MaintFullName "Mozilla Maintenance Service"
+!undef BrandFullName
+!define BrandFullName "${MaintFullName}"
+
+!include common.nsh
+!include locales.nsi
+
+VIAddVersionKey "FileDescription" "${MaintFullName} Installer"
+VIAddVersionKey "OriginalFilename" "maintenanceservice_installer.exe"
+
+Name "${MaintFullName}"
+OutFile "maintenanceservice_installer.exe"
+
+; Get installation folder from registry if available
+InstallDirRegKey HKLM "Software\Mozilla\MaintenanceService" ""
+
+SetOverwrite on
+
+; serviceinstall.cpp also uses this key, in case the path is changed, update
+; there too.
+!define MaintUninstallKey \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
+
+; Always install into the 32-bit location even if we have a 64-bit build.
+; This is because we use only 1 service for all Firefox channels.
+; Allow either x86 and x64 builds to exist at this location, depending on
+; what is the latest build.
+InstallDir "$PROGRAMFILES32\${MaintFullName}\"
+ShowUnInstDetails nevershow
+
+################################################################################
+# Modern User Interface - MUI
+
+!define MUI_ICON setup.ico
+!define MUI_UNICON setup.ico
+!define MUI_WELCOMEPAGE_TITLE_3LINES
+!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
+
+;Interface Settings
+!define MUI_ABORTWARNING
+
+; Uninstaller Pages
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+
+################################################################################
+# Language
+
+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
+!verbose push
+!verbose 3
+!include "overrideLocale.nsh"
+!include "customLocale.nsh"
+!verbose pop
+
+; Set this after the locale files to override it if it is in the locale
+; using " " for BrandingText will hide the "Nullsoft Install System..." branding
+BrandingText " "
+
+Function .onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ SetSilent silent
+
+ ; On Windows 2000 we do not install the maintenance service.
+ ; We won't run this installer from the parent installer, but just in case
+ ; someone tries to execute it on Windows 2000...
+ ${Unless} ${AtLeastWinXP}
+ Abort
+ ${EndUnless}
+FunctionEnd
+
+Function un.onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
+; removed after we require NSIS 3.0a2 or greater.
+!ifndef NSIS_PACKEDVERSION
+ ${If} ${AtLeastWinVista}
+ System::Call 'user32::SetProcessDPIAware()'
+ ${EndIf}
+!endif
+
+ StrCpy $BrandFullNameDA "${MaintFullName}"
+ StrCpy $BrandFullName "${MaintFullName}"
+FunctionEnd
+
+Section "MaintenanceService"
+ AllowSkipFiles off
+
+ CreateDirectory $INSTDIR
+ SetOutPath $INSTDIR
+
+ ; If the service already exists, then it will be stopped when upgrading it
+ ; via the maintenanceservice_tmp.exe command executed below.
+ ; The maintenanceservice_tmp.exe command will rename the file to
+ ; maintenanceservice.exe if maintenanceservice_tmp.exe is newer.
+ ; If the service does not exist yet, we install it and drop the file on
+ ; disk as maintenanceservice.exe directly.
+ StrCpy $TempMaintServiceName "maintenanceservice.exe"
+ IfFileExists "$INSTDIR\maintenanceservice.exe" 0 skipAlreadyExists
+ StrCpy $TempMaintServiceName "maintenanceservice_tmp.exe"
+ skipAlreadyExists:
+
+ ; We always write out a copy and then decide whether to install it or
+ ; not via calling its 'install' cmdline which works by version comparison.
+ CopyFiles "$EXEDIR\maintenanceservice.exe" "$INSTDIR\$TempMaintServiceName"
+
+ ; The updater.ini file is only used when performing an install or upgrade,
+ ; and only if that install or upgrade is successful. If an old updater.ini
+ ; happened to be copied into the maintenance service installation directory
+ ; but the service was not newer, the updater.ini file would be unused.
+ ; It is used to fill the description of the service on success.
+ CopyFiles "$EXEDIR\updater.ini" "$INSTDIR\updater.ini"
+
+ ; Install the application maintenance service.
+ ; If a service already exists, the command line parameter will stop the
+ ; service and only install itself if it is newer than the already installed
+ ; service. If successful it will remove the old maintenanceservice.exe
+ ; and replace it with maintenanceservice_tmp.exe.
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/Upgrade" $0
+ ${If} ${Errors}
+ ExecWait '"$INSTDIR\$TempMaintServiceName" install'
+ ${Else}
+ ; The upgrade cmdline is the same as install except
+ ; It will fail if the service isn't already installed.
+ ExecWait '"$INSTDIR\$TempMaintServiceName" upgrade'
+ ${EndIf}
+
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+ WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}"
+ WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \
+ '"$INSTDIR\uninstall.exe"'
+ WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" \
+ "$INSTDIR\Uninstall.exe,0"
+ WriteRegStr HKLM "${MaintUninstallKey}" "DisplayVersion" "${AppVersion}"
+ WriteRegStr HKLM "${MaintUninstallKey}" "Publisher" "Mozilla"
+ WriteRegStr HKLM "${MaintUninstallKey}" "Comments" "${BrandFullName}"
+ WriteRegDWORD HKLM "${MaintUninstallKey}" "NoModify" 1
+ ${GetSize} "$INSTDIR" "/S=0K" $R2 $R3 $R4
+ WriteRegDWORD HKLM "${MaintUninstallKey}" "EstimatedSize" $R2
+
+ ; Write out that a maintenance service was attempted.
+ ; We do this because on upgrades we will check this value and we only
+ ; want to install once on the first upgrade to maintenance service.
+ ; Also write out that we are currently installed, preferences will check
+ ; this value to determine if we should show the service update pref.
+ ; Since the Maintenance service can be installed either x86 or x64,
+ ; always use the 64-bit registry for checking if an attempt was made.
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+ WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1
+ WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Installed" 1
+ DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "FFPrefetchDisabled"
+
+ ; Included here for debug purposes only.
+ ; These keys are used to bypass the installation dir is a valid installation
+ ; check from the service so that tests can be run.
+ ; WriteRegStr HKLM "${FallbackKey}\0" "name" "Mozilla Corporation"
+ ; WriteRegStr HKLM "${FallbackKey}\0" "issuer" "DigiCert SHA2 Assured ID Code Signing CA"
+ ${If} ${RunningX64}
+ SetRegView lastused
+ ${EndIf}
+SectionEnd
+
+; By renaming before deleting we improve things slightly in case
+; there is a file in use error. In this case a new install can happen.
+Function un.RenameDelete
+ Pop $9
+ ; If the .moz-delete file already exists previously, delete it
+ ; If it doesn't exist, the call is ignored.
+ ; We don't need to pass /REBOOTOK here since it was already marked that way
+ ; if it exists.
+ Delete "$9.moz-delete"
+ Rename "$9" "$9.moz-delete"
+ ${If} ${Errors}
+ Delete /REBOOTOK "$9"
+ ${Else}
+ Delete /REBOOTOK "$9.moz-delete"
+ ${EndIf}
+ ClearErrors
+FunctionEnd
+
+Section "Uninstall"
+ ; Delete the service so that no updates will be attempted
+ ExecWait '"$INSTDIR\maintenanceservice.exe" uninstall'
+
+ Push "$INSTDIR\updater.ini"
+ Call un.RenameDelete
+ Push "$INSTDIR\maintenanceservice.exe"
+ Call un.RenameDelete
+ Push "$INSTDIR\maintenanceservice_tmp.exe"
+ Call un.RenameDelete
+ Push "$INSTDIR\maintenanceservice.old"
+ Call un.RenameDelete
+ Push "$INSTDIR\Uninstall.exe"
+ Call un.RenameDelete
+ Push "$INSTDIR\update\updater.ini"
+ Call un.RenameDelete
+ Push "$INSTDIR\update\updater.exe"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-1.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-2.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-3.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-4.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-5.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-6.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-7.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-8.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-9.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-10.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-install.log"
+ Call un.RenameDelete
+ Push "$INSTDIR\logs\maintenanceservice-uninstall.log"
+ Call un.RenameDelete
+ SetShellVarContext all
+ Push "$APPDATA\Mozilla\logs\maintenanceservice.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-1.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-2.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-3.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-4.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-5.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-6.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-7.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-8.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-9.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-10.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-install.log"
+ Call un.RenameDelete
+ Push "$APPDATA\Mozilla\logs\maintenanceservice-uninstall.log"
+ Call un.RenameDelete
+ RMDir /REBOOTOK "$APPDATA\Mozilla\logs"
+ RMDir /REBOOTOK "$APPDATA\Mozilla"
+ RMDir /REBOOTOK "$INSTDIR\logs"
+ RMDir /REBOOTOK "$INSTDIR\update"
+ RMDir /REBOOTOK "$INSTDIR"
+
+ DeleteRegKey HKLM "${MaintUninstallKey}"
+
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+ DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "Installed"
+ DeleteRegValue HKLM "Software\Mozilla\MaintenanceService" "FFPrefetchDisabled"
+ DeleteRegKey HKLM "${FallbackKey}\"
+ ${If} ${RunningX64}
+ SetRegView lastused
+ ${EndIf}
+SectionEnd
diff --git a/browser/installer/windows/nsis/oneoff_en-US.nsh b/browser/installer/windows/nsis/oneoff_en-US.nsh
new file mode 100644
index 000000000..62d95ade9
--- /dev/null
+++ b/browser/installer/windows/nsis/oneoff_en-US.nsh
@@ -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/.
+
+; Custom strings for en-US. This is not in the locale directory so these strings
+; aren't translated.
+!define INDENT " "
+!define INTRO_BLURB "Thanks for choosing $BrandFullName. We’re not just designed to be different, we’re different by design."
+!define INSTALL_BLURB1 "You're about to enjoy the very latest in speed, flexibility and security so you're always in control."
+!define INSTALL_BLURB2 "And you're joining a global community of users, contributors and developers working to make the best browser in the world."
+!define INSTALL_BLURB3 "You even get a haiku:$\n${INDENT}Proudly non-profit$\n${INDENT}Free to innovate for you$\n${INDENT}And a better Web"
+!undef INDENT
diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh
new file mode 100755
index 000000000..8d7eea618
--- /dev/null
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -0,0 +1,1410 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+!macro PostUpdate
+ ; PostUpdate is called from both session 0 and from the user session
+ ; for service updates, make sure that we only register with the user session
+ ; Otherwise ApplicationID::Set can fail intermittently with a file in use error.
+ System::Call "kernel32::GetCurrentProcessId() i.r0"
+ System::Call "kernel32::ProcessIdToSessionId(i $0, *i ${NSIS_MAX_STRLEN} r9)"
+
+ ; Determine if we're the protected UserChoice default or not. If so fix the
+ ; start menu tile. In case there are 2 Firefox installations, we only do
+ ; this if the application being updated is the default.
+ ReadRegStr $0 HKCU "Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice" "ProgId"
+ ${If} $0 == "FirefoxURL"
+ ${AndIf} $9 != 0 ; We're not running in session 0
+ ReadRegStr $0 HKCU "Software\Classes\FirefoxURL\shell\open\command" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${EndIf}
+ ${EndIf}
+
+ ${CreateShortcutsLog}
+
+ ; Remove registry entries for non-existent apps and for apps that point to our
+ ; install location in the Software\Mozilla key and uninstall registry entries
+ ; that point to our install location for both HKCU and HKLM.
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+
+ ; setup the application model id registration value
+ ${InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
+
+ ; Win7 taskbar and start menu link maintenance
+ Call FixShortcutAppModelIDs
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ StrCpy $TmpVal "HKCU" ; used primarily for logging
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $TmpVal "HKLM" ; used primarily for logging
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+ ${FixShellIconHandler} "HKLM"
+ ${SetAppLSPCategories} ${LSP_CATEGORIES}
+
+ ; Win7 taskbar and start menu link maintenance
+ Call FixShortcutAppModelIDs
+
+ ; Add the Firewall entries after an update
+ Call AddFirewallEntries
+
+ ; Only update the Clients\StartMenuInternet registry key values in HKLM if
+ ; they don't exist or this installation is the same as the one set in those
+ ; keys.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $1
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$1\DefaultIcon" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${EndIf}
+ ${If} "$0" == "$INSTDIR"
+ ${SetStartMenuInternet} "HKLM"
+ ${EndIf}
+
+ ; Only update the Clients\StartMenuInternet registry key values in HKCU if
+ ; they don't exist or this installation is the same as the one set in those
+ ; keys. This is only done in Windows 8 to avoid a UAC prompt.
+ ${If} ${AtLeastWin8}
+ ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$1\DefaultIcon" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${EndIf}
+ ${If} "$0" == "$INSTDIR"
+ ${SetStartMenuInternet} "HKCU"
+ ${EndIf}
+ ${EndIf}
+
+ ReadRegStr $0 HKLM "Software\mozilla.org\Mozilla" "CurrentVersion"
+ ${If} "$0" != "${GREVersion}"
+ WriteRegStr HKLM "Software\mozilla.org\Mozilla" "CurrentVersion" "${GREVersion}"
+ ${EndIf}
+ ${EndIf}
+
+ ; Migrate the application's Start Menu directory to a single shortcut in the
+ ; root of the Start Menu Programs directory.
+ ${MigrateStartMenuShortcut}
+
+ ; Update lastwritetime of the Start Menu shortcut to clear the tile cache.
+ ${If} ${AtLeastWin8}
+ ${AndIf} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ FileOpen $0 "$SMPROGRAMS\${BrandFullName}.lnk" a
+ FileClose $0
+ ${EndIf}
+
+ ; Adds a pinned Task Bar shortcut (see MigrateTaskBarShortcut for details).
+ ${MigrateTaskBarShortcut}
+
+ ${RemoveDeprecatedKeys}
+
+ ${SetAppKeys}
+ ${FixClassKeys}
+ ${SetUninstallKeys}
+
+ ; Remove files that may be left behind by the application in the
+ ; VirtualStore directory.
+ ${CleanVirtualStore}
+
+ ${RemoveDeprecatedFiles}
+
+ ; Fix the distribution.ini file if applicable
+ ${FixDistributionsINI}
+
+ RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+ Call IsUserAdmin
+ Pop $R0
+ ${If} $R0 == "true"
+ ; Only proceed if we have HKLM write access
+ ${AndIf} $TmpVal == "HKLM"
+ ; On Windows 2000 we do not install the maintenance service.
+ ${AndIf} ${AtLeastWinXP}
+ ; We check to see if the maintenance service install was already attempted.
+ ; Since the Maintenance service can be installed either x86 or x64,
+ ; always use the 64-bit registry for checking if an attempt was made.
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+ ReadRegDWORD $5 HKLM "Software\Mozilla\MaintenanceService" "Attempted"
+ ClearErrors
+ ${If} ${RunningX64}
+ SetRegView lastused
+ ${EndIf}
+
+ ; Add the registry keys for allowed certificates.
+ ${AddMaintCertKeys}
+
+ ; If the maintenance service is already installed, do nothing.
+ ; The maintenance service will launch:
+ ; maintenanceservice_installer.exe /Upgrade to upgrade the maintenance
+ ; service if necessary. If the update was done from updater.exe without
+ ; the service (i.e. service is failing), updater.exe will do the update of
+ ; the service. The reasons we do not do it here is because we don't want
+ ; to have to prompt for limited user accounts when the service isn't used
+ ; and we currently call the PostUpdate twice, once for the user and once
+ ; for the SYSTEM account. Also, this would stop the maintenance service
+ ; and we need a return result back to the service when run that way.
+ ${If} $5 == ""
+ ; An install of maintenance service was never attempted.
+ ; We know we are an Admin and that we have write access into HKLM
+ ; based on the above checks, so attempt to just run the EXE.
+ ; In the worst case, in case there is some edge case with the
+ ; IsAdmin check and the permissions check, the maintenance service
+ ; will just fail to be attempted to be installed.
+ nsExec::Exec "$\"$INSTDIR\maintenanceservice_installer.exe$\""
+ ${EndIf}
+ ${EndIf}
+!endif
+!macroend
+!define PostUpdate "!insertmacro PostUpdate"
+
+!macro SetAsDefaultAppGlobal
+ ${RemoveDeprecatedKeys} ; Does not use SHCTX
+
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ ${SetHandlers} ; Uses SHCTX
+ ${SetStartMenuInternet} "HKLM"
+ ${FixShellIconHandler} "HKLM"
+ ${ShowShortcuts}
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ WriteRegStr HKLM "Software\Clients\StartMenuInternet" "" "$R9"
+!macroend
+!define SetAsDefaultAppGlobal "!insertmacro SetAsDefaultAppGlobal"
+
+; Removes shortcuts for this installation. This should also remove the
+; application from Open With for the file types the application handles
+; (bug 370480).
+!macro HideShortcuts
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $0
+ StrCpy $R1 "Software\Clients\StartMenuInternet\$0\InstallInfo"
+ WriteRegDWORD HKLM "$R1" "IconsVisible" 0
+ ${If} ${AtLeastWin8}
+ WriteRegDWORD HKCU "$R1" "IconsVisible" 0
+ ${EndIf}
+
+ SetShellVarContext all ; Set $DESKTOP to All Users
+ ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ SetShellVarContext current ; Set $DESKTOP to the current user's desktop
+ ${EndUnless}
+
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::GetShortCutArgs "$DESKTOP\${BrandFullName}.lnk"
+ Pop $0
+ ${If} "$0" == ""
+ ShellLink::GetShortCutTarget "$DESKTOP\${BrandFullName}.lnk"
+ Pop $0
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR\${FileMainEXE}"
+ Delete "$DESKTOP\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ SetShellVarContext all ; Set $SMPROGRAMS to All Users
+ ${Unless} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ SetShellVarContext current ; Set $SMPROGRAMS to the current user's Start
+ ; Menu Programs directory
+ ${EndUnless}
+
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::GetShortCutArgs "$SMPROGRAMS\${BrandFullName}.lnk"
+ Pop $0
+ ${If} "$0" == ""
+ ShellLink::GetShortCutTarget "$SMPROGRAMS\${BrandFullName}.lnk"
+ Pop $0
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR\${FileMainEXE}"
+ Delete "$SMPROGRAMS\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ShellLink::GetShortCutArgs "$QUICKLAUNCH\${BrandFullName}.lnk"
+ Pop $0
+ ${If} "$0" == ""
+ ShellLink::GetShortCutTarget "$QUICKLAUNCH\${BrandFullName}.lnk"
+ Pop $0
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR\${FileMainEXE}"
+ Delete "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define HideShortcuts "!insertmacro HideShortcuts"
+
+; Adds shortcuts for this installation. This should also add the application
+; to Open With for the file types the application handles (bug 370480).
+!macro ShowShortcuts
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $0
+ StrCpy $R1 "Software\Clients\StartMenuInternet\$0\InstallInfo"
+ WriteRegDWORD HKLM "$R1" "IconsVisible" 1
+ ${If} ${AtLeastWin8}
+ WriteRegDWORD HKCU "$R1" "IconsVisible" 1
+ ${EndIf}
+
+ SetShellVarContext all ; Set $DESKTOP to All Users
+ ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$DESKTOP\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${Else}
+ SetShellVarContext current ; Set $DESKTOP to the current user's desktop
+ ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$DESKTOP\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$DESKTOP\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndUnless}
+
+ SetShellVarContext all ; Set $SMPROGRAMS to All Users
+ ${Unless} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${Else}
+ SetShellVarContext current ; Set $SMPROGRAMS to the current user's Start
+ ; Menu Programs directory
+ ${Unless} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndUnless}
+
+ ; Windows 7 doesn't use the QuickLaunch directory
+ ${Unless} ${AtLeastWin7}
+ ${AndUnless} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ CreateShortCut "$QUICKLAUNCH\${BrandFullName}.lnk" \
+ "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$QUICKLAUNCH\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${EndIf}
+ ${EndUnless}
+!macroend
+!define ShowShortcuts "!insertmacro ShowShortcuts"
+
+!macro AddAssociationIfNoneExist FILE_TYPE
+ ClearErrors
+ EnumRegKey $7 HKCR "${FILE_TYPE}" 0
+ ${If} ${Errors}
+ WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}" "" "FirefoxHTML"
+ ${EndIf}
+ WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}\OpenWithProgids" "FirefoxHTML" ""
+!macroend
+!define AddAssociationIfNoneExist "!insertmacro AddAssociationIfNoneExist"
+
+; Adds the protocol and file handler registry entries for making Firefox the
+; default handler (uses SHCTX).
+!macro SetHandlers
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+
+ StrCpy $0 "SOFTWARE\Classes"
+ StrCpy $2 "$\"$8$\" -osint -url $\"%1$\""
+
+ ; Associate the file handlers with FirefoxHTML
+ ReadRegStr $6 SHCTX "$0\.htm" ""
+ ${If} "$6" != "FirefoxHTML"
+ WriteRegStr SHCTX "$0\.htm" "" "FirefoxHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.html" ""
+ ${If} "$6" != "FirefoxHTML"
+ WriteRegStr SHCTX "$0\.html" "" "FirefoxHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.shtml" ""
+ ${If} "$6" != "FirefoxHTML"
+ WriteRegStr SHCTX "$0\.shtml" "" "FirefoxHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.xht" ""
+ ${If} "$6" != "FirefoxHTML"
+ WriteRegStr SHCTX "$0\.xht" "" "FirefoxHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.xhtml" ""
+ ${If} "$6" != "FirefoxHTML"
+ WriteRegStr SHCTX "$0\.xhtml" "" "FirefoxHTML"
+ ${EndIf}
+
+ ${AddAssociationIfNoneExist} ".pdf"
+ ${AddAssociationIfNoneExist} ".oga"
+ ${AddAssociationIfNoneExist} ".ogg"
+ ${AddAssociationIfNoneExist} ".ogv"
+ ${AddAssociationIfNoneExist} ".pdf"
+ ${AddAssociationIfNoneExist} ".webm"
+
+ ; An empty string is used for the 5th param because FirefoxHTML is not a
+ ; protocol handler
+ ${AddDisabledDDEHandlerValues} "FirefoxHTML" "$2" "$8,1" \
+ "${AppRegName} HTML Document" ""
+
+ ${AddDisabledDDEHandlerValues} "FirefoxURL" "$2" "$8,1" "${AppRegName} URL" \
+ "true"
+ ; An empty string is used for the 4th & 5th params because the following
+ ; protocol handlers already have a display name and the additional keys
+ ; required for a protocol handler.
+ ${AddDisabledDDEHandlerValues} "ftp" "$2" "$8,1" "" ""
+ ${AddDisabledDDEHandlerValues} "http" "$2" "$8,1" "" ""
+ ${AddDisabledDDEHandlerValues} "https" "$2" "$8,1" "" ""
+!macroend
+!define SetHandlers "!insertmacro SetHandlers"
+
+; Adds the HKLM\Software\Clients\StartMenuInternet\FIREFOX.EXE registry
+; entries (does not use SHCTX).
+;
+; The values for StartMenuInternet are only valid under HKLM and there can only
+; be one installation registerred under StartMenuInternet per application since
+; the key name is derived from the main application executable.
+; http://support.microsoft.com/kb/297878
+;
+; In Windows 8 this changes slightly, you can store StartMenuInternet entries in
+; HKCU. The icon in start menu for StartMenuInternet is deprecated as of Win7,
+; but the subkeys are what's important. Control panel default programs looks
+; for them only in HKLM pre win8.
+;
+; Note: we might be able to get away with using the full path to the
+; application executable for the key name in order to support multiple
+; installations.
+!macro SetStartMenuInternet RegKey
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+ ${GetLongPath} "$INSTDIR\uninstall\helper.exe" $7
+
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9"
+
+ WriteRegStr ${RegKey} "$0" "" "${BrandFullName}"
+
+ WriteRegStr ${RegKey} "$0\DefaultIcon" "" "$8,0"
+
+ ; The Reinstall Command is defined at
+ ; http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_adv/registeringapps.asp
+ WriteRegStr ${RegKey} "$0\InstallInfo" "HideIconsCommand" "$\"$7$\" /HideShortcuts"
+ WriteRegStr ${RegKey} "$0\InstallInfo" "ShowIconsCommand" "$\"$7$\" /ShowShortcuts"
+ WriteRegStr ${RegKey} "$0\InstallInfo" "ReinstallCommand" "$\"$7$\" /SetAsDefaultAppGlobal"
+
+ ClearErrors
+ ReadRegDWORD $1 ${RegKey} "$0\InstallInfo" "IconsVisible"
+ ; If the IconsVisible name value pair doesn't exist add it otherwise the
+ ; application won't be displayed in Set Program Access and Defaults.
+ ${If} ${Errors}
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ WriteRegDWORD ${RegKey} "$0\InstallInfo" "IconsVisible" 1
+ ${Else}
+ WriteRegDWORD ${RegKey} "$0\InstallInfo" "IconsVisible" 0
+ ${EndIf}
+ ${EndIf}
+
+ WriteRegStr ${RegKey} "$0\shell\open\command" "" "$\"$8$\""
+
+ WriteRegStr ${RegKey} "$0\shell\properties" "" "$(CONTEXT_OPTIONS)"
+ WriteRegStr ${RegKey} "$0\shell\properties\command" "" "$\"$8$\" -preferences"
+
+ WriteRegStr ${RegKey} "$0\shell\safemode" "" "$(CONTEXT_SAFE_MODE)"
+ WriteRegStr ${RegKey} "$0\shell\safemode\command" "" "$\"$8$\" -safe-mode"
+
+ ; Vista Capabilities registry keys
+ WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationDescription" "$(REG_APP_DESC)"
+ WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationIcon" "$8,0"
+ WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationName" "${BrandShortName}"
+
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".htm" "FirefoxHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".html" "FirefoxHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".shtml" "FirefoxHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xht" "FirefoxHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xhtml" "FirefoxHTML"
+
+ WriteRegStr ${RegKey} "$0\Capabilities\StartMenu" "StartMenuInternet" "$R9"
+
+ WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "ftp" "FirefoxURL"
+ WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "http" "FirefoxURL"
+ WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "https" "FirefoxURL"
+
+ ; Vista Registered Application
+ WriteRegStr ${RegKey} "Software\RegisteredApplications" "${AppRegName}" "$0\Capabilities"
+!macroend
+!define SetStartMenuInternet "!insertmacro SetStartMenuInternet"
+
+; The IconHandler reference for FirefoxHTML can end up in an inconsistent state
+; due to changes not being detected by the IconHandler for side by side
+; installs (see bug 268512). The symptoms can be either an incorrect icon or no
+; icon being displayed for files associated with Firefox (does not use SHCTX).
+!macro FixShellIconHandler RegKey
+ ClearErrors
+ ReadRegStr $1 ${RegKey} "Software\Classes\FirefoxHTML\ShellEx\IconHandler" ""
+ ${Unless} ${Errors}
+ ReadRegStr $1 ${RegKey} "Software\Classes\FirefoxHTML\DefaultIcon" ""
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $2
+ ${If} "$1" != "$2,1"
+ WriteRegStr ${RegKey} "Software\Classes\FirefoxHTML\DefaultIcon" "" "$2,1"
+ ${EndIf}
+ ${EndUnless}
+!macroend
+!define FixShellIconHandler "!insertmacro FixShellIconHandler"
+
+; Add Software\Mozilla\ registry entries (uses SHCTX).
+!macro SetAppKeys
+ ; Check if this is an ESR release and if so add registry values so it is
+ ; possible to determine that this is an ESR install (bug 726781).
+ ClearErrors
+ ${WordFind} "${UpdateChannel}" "esr" "E#" $3
+ ${If} ${Errors}
+ StrCpy $3 ""
+ ${Else}
+ StrCpy $3 " ESR"
+ ${EndIf}
+
+ ${GetLongPath} "$INSTDIR" $8
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})\Main"
+ ${WriteRegStr2} $TmpVal "$0" "Install Directory" "$8" 0
+ ${WriteRegStr2} $TmpVal "$0" "PathToExe" "$8\${FileMainEXE}" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})\Uninstall"
+ ${WriteRegStr2} $TmpVal "$0" "Description" "${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})"
+ ${WriteRegStr2} $TmpVal "$0" "" "${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+ ${If} "$3" == ""
+ DeleteRegValue SHCTX "$0" "ESR"
+ ${Else}
+ ${WriteRegDWORD2} $TmpVal "$0" "ESR" 1 0
+ ${EndIf}
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal} ${AppVersion}$3\bin"
+ ${WriteRegStr2} $TmpVal "$0" "PathToExe" "$8\${FileMainEXE}" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal} ${AppVersion}$3\extensions"
+ ${WriteRegStr2} $TmpVal "$0" "Components" "$8\components" 0
+ ${WriteRegStr2} $TmpVal "$0" "Plugins" "$8\plugins" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal} ${AppVersion}$3"
+ ${WriteRegStr2} $TmpVal "$0" "GeckoVer" "${GREVersion}" 0
+ ${If} "$3" == ""
+ DeleteRegValue SHCTX "$0" "ESR"
+ ${Else}
+ ${WriteRegDWORD2} $TmpVal "$0" "ESR" 1 0
+ ${EndIf}
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}$3"
+ ${WriteRegStr2} $TmpVal "$0" "" "${GREVersion}" 0
+ ${WriteRegStr2} $TmpVal "$0" "CurrentVersion" "${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+!macroend
+!define SetAppKeys "!insertmacro SetAppKeys"
+
+; Add uninstall registry entries. This macro tests for write access to determine
+; if the uninstall keys should be added to HKLM or HKCU.
+!macro SetUninstallKeys
+ ; Check if this is an ESR release and if so add registry values so it is
+ ; possible to determine that this is an ESR install (bug 726781).
+ ClearErrors
+ ${WordFind} "${UpdateChannel}" "esr" "E#" $3
+ ${If} ${Errors}
+ StrCpy $3 ""
+ ${Else}
+ StrCpy $3 " ESR"
+ ${EndIf}
+
+ StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})"
+
+ StrCpy $2 ""
+ ClearErrors
+ WriteRegStr HKLM "$0" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ ; If the uninstall keys already exist in HKLM don't create them in HKCU
+ ClearErrors
+ ReadRegStr $2 "HKLM" $0 "DisplayName"
+ ${If} $2 == ""
+ ; Otherwise we don't have any keys for this product in HKLM so proceeed
+ ; to create them in HKCU. Better handling for this will be done in:
+ ; Bug 711044 - Better handling for 2 uninstall icons
+ StrCpy $1 "HKCU"
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+ ${EndIf}
+ ClearErrors
+ ${Else}
+ StrCpy $1 "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ DeleteRegValue HKLM "$0" "${BrandShortName}InstallerTest"
+ ${EndIf}
+
+ ${If} $2 == ""
+ ${GetLongPath} "$INSTDIR" $8
+
+ ; Write the uninstall registry keys
+ ${WriteRegStr2} $1 "$0" "Comments" "${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+ ${WriteRegStr2} $1 "$0" "DisplayIcon" "$8\${FileMainEXE},0" 0
+ ${WriteRegStr2} $1 "$0" "DisplayName" "${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+ ${WriteRegStr2} $1 "$0" "DisplayVersion" "${AppVersion}" 0
+ ${WriteRegStr2} $1 "$0" "HelpLink" "${HelpLink}" 0
+ ${WriteRegStr2} $1 "$0" "InstallLocation" "$8" 0
+ ${WriteRegStr2} $1 "$0" "Publisher" "Mozilla" 0
+ ${WriteRegStr2} $1 "$0" "UninstallString" "$\"$8\uninstall\helper.exe$\"" 0
+ DeleteRegValue SHCTX "$0" "URLInfoAbout"
+; Don't add URLUpdateInfo which is the release notes url except for the release
+; and esr channels since nightly, aurora, and beta do not have release notes.
+; Note: URLUpdateInfo is only defined in the official branding.nsi.
+!ifdef URLUpdateInfo
+!ifndef BETA_UPDATE_CHANNEL
+ ${WriteRegStr2} $1 "$0" "URLUpdateInfo" "${URLUpdateInfo}" 0
+!endif
+!endif
+ ${WriteRegStr2} $1 "$0" "URLInfoAbout" "${URLInfoAbout}" 0
+ ${WriteRegDWORD2} $1 "$0" "NoModify" 1 0
+ ${WriteRegDWORD2} $1 "$0" "NoRepair" 1 0
+
+ ${GetSize} "$8" "/S=0K" $R2 $R3 $R4
+ ${WriteRegDWORD2} $1 "$0" "EstimatedSize" $R2 0
+
+ ${If} "$TmpVal" == "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ ${Else}
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define SetUninstallKeys "!insertmacro SetUninstallKeys"
+
+; Due to a bug when associating some file handlers, only SHCTX was checked for
+; some file types such as ".pdf". SHCTX is set to HKCU or HKLM depending on
+; whether the installer has write access to HKLM. The bug would happen when
+; HCKU was checked and didn't exist since programs aren't required to set the
+; HKCU Software\Classes keys when associating handlers. The fix uses the merged
+; view in HKCR to check for existance of an existing association. This macro
+; cleans affected installations by removing the HKLM and HKCU value if it is set
+; to FirefoxHTML when there is a value for PersistentHandler or by removing the
+; HKCU value when the HKLM value has a value other than an empty string.
+!macro FixBadFileAssociation FILE_TYPE
+ ; Only delete the default value in case the key has values for OpenWithList,
+ ; OpenWithProgids, PersistentHandler, etc.
+ ReadRegStr $0 HKCU "Software\Classes\${FILE_TYPE}" ""
+ ReadRegStr $1 HKLM "Software\Classes\${FILE_TYPE}" ""
+ ReadRegStr $2 HKCR "${FILE_TYPE}\PersistentHandler" ""
+ ${If} "$2" != ""
+ ; Since there is a persistent handler remove FirefoxHTML as the default
+ ; value from both HKCU and HKLM if it set to FirefoxHTML.
+ ${If} "$0" == "FirefoxHTML"
+ DeleteRegValue HKCU "Software\Classes\${FILE_TYPE}" ""
+ ${EndIf}
+ ${If} "$1" == "FirefoxHTML"
+ DeleteRegValue HKLM "Software\Classes\${FILE_TYPE}" ""
+ ${EndIf}
+ ${ElseIf} "$0" == "FirefoxHTML"
+ ; Since KHCU is set to FirefoxHTML remove FirefoxHTML as the default value
+ ; from HKCU if HKLM is set to a value other than an empty string.
+ ${If} "$1" != ""
+ DeleteRegValue HKCU "Software\Classes\${FILE_TYPE}" ""
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define FixBadFileAssociation "!insertmacro FixBadFileAssociation"
+
+; Add app specific handler registry entries under Software\Classes if they
+; don't exist (does not use SHCTX).
+!macro FixClassKeys
+ StrCpy $1 "SOFTWARE\Classes"
+
+ ; File handler keys and name value pairs that may need to be created during
+ ; install or upgrade.
+ ReadRegStr $0 HKCR ".shtml" "Content Type"
+ ${If} "$0" == ""
+ StrCpy $0 "$1\.shtml"
+ ${WriteRegStr2} $TmpVal "$1\.shtml" "" "shtmlfile" 0
+ ${WriteRegStr2} $TmpVal "$1\.shtml" "Content Type" "text/html" 0
+ ${WriteRegStr2} $TmpVal "$1\.shtml" "PerceivedType" "text" 0
+ ${EndIf}
+
+ ReadRegStr $0 HKCR ".xht" "Content Type"
+ ${If} "$0" == ""
+ ${WriteRegStr2} $TmpVal "$1\.xht" "" "xhtfile" 0
+ ${WriteRegStr2} $TmpVal "$1\.xht" "Content Type" "application/xhtml+xml" 0
+ ${EndIf}
+
+ ReadRegStr $0 HKCR ".xhtml" "Content Type"
+ ${If} "$0" == ""
+ ${WriteRegStr2} $TmpVal "$1\.xhtml" "" "xhtmlfile" 0
+ ${WriteRegStr2} $TmpVal "$1\.xhtml" "Content Type" "application/xhtml+xml" 0
+ ${EndIf}
+
+ ; Remove possibly badly associated file types
+ ${FixBadFileAssociation} ".pdf"
+ ${FixBadFileAssociation} ".oga"
+ ${FixBadFileAssociation} ".ogg"
+ ${FixBadFileAssociation} ".ogv"
+ ${FixBadFileAssociation} ".pdf"
+ ${FixBadFileAssociation} ".webm"
+!macroend
+!define FixClassKeys "!insertmacro FixClassKeys"
+
+; Updates protocol handlers if their registry open command value is for this
+; install location (uses SHCTX).
+!macro UpdateProtocolHandlers
+ ; Store the command to open the app with an url in a register for easy access.
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+ StrCpy $2 "$\"$8$\" -osint -url $\"%1$\""
+
+ ; Only set the file and protocol handlers if the existing one under HKCR is
+ ; for this install location.
+
+ ${IsHandlerForInstallDir} "FirefoxHTML" $R9
+ ${If} "$R9" == "true"
+ ; An empty string is used for the 5th param because FirefoxHTML is not a
+ ; protocol handler.
+ ${AddDisabledDDEHandlerValues} "FirefoxHTML" "$2" "$8,1" \
+ "${AppRegName} HTML Document" ""
+ ${EndIf}
+
+ ${IsHandlerForInstallDir} "FirefoxURL" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "FirefoxURL" "$2" "$8,1" \
+ "${AppRegName} URL" "true"
+ ${EndIf}
+
+ ; An empty string is used for the 4th & 5th params because the following
+ ; protocol handlers already have a display name and the additional keys
+ ; required for a protocol handler.
+ ${IsHandlerForInstallDir} "ftp" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "ftp" "$2" "$8,1" "" ""
+ ${EndIf}
+
+ ${IsHandlerForInstallDir} "http" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "http" "$2" "$8,1" "" ""
+ ${EndIf}
+
+ ${IsHandlerForInstallDir} "https" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "https" "$2" "$8,1" "" ""
+ ${EndIf}
+!macroend
+!define UpdateProtocolHandlers "!insertmacro UpdateProtocolHandlers"
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+; Adds maintenance service certificate keys for the install dir.
+; For the cert to work, it must also be signed by a trusted cert for the user.
+!macro AddMaintCertKeys
+ Push $R0
+ ; Allow main Mozilla cert information for updates
+ ; This call will push the needed key on the stack
+ ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
+ Pop $R0
+ ${If} $R0 != ""
+ ; More than one certificate can be specified in a different subfolder
+ ; for example: $R0\1, but each individual binary can be signed
+ ; with at most one certificate. A fallback certificate can only be used
+ ; if the binary is replaced with a different certificate.
+ ; We always use the 64bit registry for certs.
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+
+ ; PrefetchProcessName was originally used to experiment with deleting
+ ; Windows prefetch as a speed optimization. It is no longer used though.
+ DeleteRegValue HKLM "$R0" "prefetchProcessName"
+
+ ; Setting the Attempted value will ensure that a new Maintenance Service
+ ; install will never be attempted again after this from updates. The value
+ ; is used only to see if updates should attempt new service installs.
+ WriteRegDWORD HKLM "Software\Mozilla\MaintenanceService" "Attempted" 1
+
+ ; These values associate the allowed certificates for the current
+ ; installation.
+ WriteRegStr HKLM "$R0\0" "name" "${CERTIFICATE_NAME}"
+ WriteRegStr HKLM "$R0\0" "issuer" "${CERTIFICATE_ISSUER}"
+ ; These values associate the allowed certificates for the previous
+ ; installation, so that we can update from it cleanly using the
+ ; old updater.exe (which will still have this signature).
+ WriteRegStr HKLM "$R0\1" "name" "${CERTIFICATE_NAME_PREVIOUS}"
+ WriteRegStr HKLM "$R0\1" "issuer" "${CERTIFICATE_ISSUER_PREVIOUS}"
+ ${If} ${RunningX64}
+ SetRegView lastused
+ ${EndIf}
+ ClearErrors
+ ${EndIf}
+ ; Restore the previously used value back
+ Pop $R0
+!macroend
+!define AddMaintCertKeys "!insertmacro AddMaintCertKeys"
+!endif
+
+; Removes various registry entries for reasons noted below (does not use SHCTX).
+!macro RemoveDeprecatedKeys
+ StrCpy $0 "SOFTWARE\Classes"
+ ; Remove support for launching gopher urls from the shell during install or
+ ; update if the DefaultIcon is from firefox.exe.
+ ${RegCleanAppHandler} "gopher"
+
+ ; Remove support for launching chrome urls from the shell during install or
+ ; update if the DefaultIcon is from firefox.exe (Bug 301073).
+ ${RegCleanAppHandler} "chrome"
+
+ ; Remove protocol handler registry keys added by the MS shim
+ DeleteRegKey HKLM "Software\Classes\Firefox.URL"
+ DeleteRegKey HKCU "Software\Classes\Firefox.URL"
+
+ ; Delete gopher from Capabilities\URLAssociations if it is present.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9"
+ ClearErrors
+ ReadRegStr $2 HKLM "$0\Capabilities\URLAssociations" "gopher"
+ ${Unless} ${Errors}
+ DeleteRegValue HKLM "$0\Capabilities\URLAssociations" "gopher"
+ ${EndUnless}
+
+ ; Delete gopher from the user's UrlAssociations if it points to FirefoxURL.
+ StrCpy $0 "Software\Microsoft\Windows\Shell\Associations\UrlAssociations\gopher"
+ ReadRegStr $2 HKCU "$0\UserChoice" "Progid"
+ ${If} "$2" == "FirefoxURL"
+ DeleteRegKey HKCU "$0"
+ ${EndIf}
+!macroend
+!define RemoveDeprecatedKeys "!insertmacro RemoveDeprecatedKeys"
+
+; Removes various directories and files for reasons noted below.
+!macro RemoveDeprecatedFiles
+ ; Remove talkback if it is present (remove after bug 386760 is fixed)
+ ${If} ${FileExists} "$INSTDIR\extensions\talkback@mozilla.org"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\talkback@mozilla.org"
+ ${EndIf}
+
+ ; Remove the Java Console extension (bug 1165156)
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0031-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0031-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0034-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0034-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0039-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0039-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0045-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0045-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0017-0000-0000-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0017-0000-0000-ABCDEFFEDCBA}"
+ ${EndIf}
+!macroend
+!define RemoveDeprecatedFiles "!insertmacro RemoveDeprecatedFiles"
+
+; Converts specific partner distribution.ini from ansi to utf-8 (bug 882989)
+!macro FixDistributionsINI
+ StrCpy $1 "$INSTDIR\distribution\distribution.ini"
+ StrCpy $2 "$INSTDIR\distribution\utf8fix"
+ StrCpy $0 "0" ; Default to not attempting to fix
+
+ ; Check if the distribution.ini settings are for a partner build that needs
+ ; to have its distribution.ini converted from ansi to utf-8.
+ ${If} ${FileExists} "$1"
+ ${Unless} ${FileExists} "$2"
+ ReadINIStr $3 "$1" "Preferences" "app.distributor"
+ ${If} "$3" == "yahoo"
+ ReadINIStr $3 "$1" "Preferences" "app.distributor.channel"
+ ${If} "$3" == "de"
+ ${OrIf} "$3" == "es"
+ ${OrIf} "$3" == "e1"
+ ${OrIf} "$3" == "mx"
+ StrCpy $0 "1"
+ ${EndIf}
+ ${EndIf}
+ ; Create the utf8fix so this only runs once
+ FileOpen $3 "$2" w
+ FileClose $3
+ ${EndUnless}
+ ${EndIf}
+
+ ${If} "$0" == "1"
+ StrCpy $0 "0"
+ ClearErrors
+ ReadINIStr $3 "$1" "Global" "version"
+ ${Unless} ${Errors}
+ StrCpy $4 "$3" 2
+ ${If} "$4" == "1."
+ StrCpy $4 "$3" "" 2 ; Everything after "1."
+ ${If} $4 < 23
+ StrCpy $0 "1"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+
+ ${If} "$0" == "1"
+ ClearErrors
+ FileOpen $3 "$1" r
+ ${If} ${Errors}
+ FileClose $3
+ ${Else}
+ StrCpy $2 "$INSTDIR\distribution\distribution.new"
+ ClearErrors
+ FileOpen $4 "$2" w
+ ${If} ${Errors}
+ FileClose $3
+ FileClose $4
+ ${Else}
+ StrCpy $0 "0" ; Default to not replacing the original distribution.ini
+ ${Do}
+ FileReadByte $3 $5
+ ${If} $5 == ""
+ ${Break}
+ ${EndIf}
+ ${If} $5 == 233 ; ansi é
+ StrCpy $0 "1"
+ FileWriteByte $4 195
+ FileWriteByte $4 169
+ ${ElseIf} $5 == 241 ; ansi ñ
+ StrCpy $0 "1"
+ FileWriteByte $4 195
+ FileWriteByte $4 177
+ ${ElseIf} $5 == 252 ; ansi ü
+ StrCpy $0 "1"
+ FileWriteByte $4 195
+ FileWriteByte $4 188
+ ${ElseIf} $5 < 128
+ FileWriteByte $4 $5
+ ${EndIf}
+ ${Loop}
+ FileClose $3
+ FileClose $4
+ ${If} "$0" == "1"
+ ClearErrors
+ Rename "$1" "$1.bak"
+ ${Unless} ${Errors}
+ Rename "$2" "$1"
+ Delete "$1.bak"
+ ${EndUnless}
+ ${Else}
+ Delete "$2"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define FixDistributionsINI "!insertmacro FixDistributionsINI"
+
+; Adds a pinned shortcut to Task Bar on update for Windows 7 and above if this
+; macro has never been called before and the application is default (see
+; PinToTaskBar for more details).
+; Since defaults handling is handled by Windows in Win8 and later, we always
+; attempt to pin a taskbar on that OS. If Windows sets the defaults at
+; installation time, then we don't get the opportunity to run this code at
+; that time.
+!macro MigrateTaskBarShortcut
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ClearErrors
+ ReadINIStr $1 "$0" "TASKBAR" "Migrated"
+ ${If} ${Errors}
+ ClearErrors
+ WriteIniStr "$0" "TASKBAR" "Migrated" "true"
+ ${If} ${AtLeastWin7}
+ ; If we didn't run the stub installer, AddTaskbarSC will be empty.
+ ; We determine whether to pin based on whether we're the default
+ ; browser, or if we're on win8 or later, we always pin.
+ ${If} $AddTaskbarSC == ""
+ ; No need to check the default on Win8 and later
+ ${If} ${AtMostWin2008R2}
+ ; Check if the Firefox is the http handler for this user
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${IsHandlerForInstallDir} "http" $R9
+ ${If} $TmpVal == "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users
+ ${EndIf}
+ ${EndIf}
+ ${If} "$R9" == "true"
+ ${OrIf} ${AtLeastWin8}
+ ${PinToTaskBar}
+ ${EndIf}
+ ${ElseIf} $AddTaskbarSC == "1"
+ ${PinToTaskBar}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define MigrateTaskBarShortcut "!insertmacro MigrateTaskBarShortcut"
+
+; Adds a pinned Task Bar shortcut on Windows 7 if there isn't one for the main
+; application executable already. Existing pinned shortcuts for the same
+; application model ID must be removed first to prevent breaking the pinned
+; item's lists but multiple installations with the same application model ID is
+; an edgecase. If removing existing pinned shortcuts with the same application
+; model ID removes a pinned pinned Start Menu shortcut this will also add a
+; pinned Start Menu shortcut.
+!macro PinToTaskBar
+ ${If} ${AtLeastWin7}
+ StrCpy $8 "false" ; Whether a shortcut had to be created
+ ${IsPinnedToTaskBar} "$INSTDIR\${FileMainEXE}" $R9
+ ${If} "$R9" == "false"
+ ; Find an existing Start Menu shortcut or create one to use for pinning
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ClearErrors
+ ReadINIStr $1 "$0" "STARTMENU" "Shortcut0"
+ ${Unless} ${Errors}
+ SetShellVarContext all ; Set SHCTX to all users
+ ${Unless} ${FileExists} "$SMPROGRAMS\$1"
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${Unless} ${FileExists} "$SMPROGRAMS\$1"
+ StrCpy $8 "true"
+ CreateShortCut "$SMPROGRAMS\$1" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\$1"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\$1" \
+ "$INSTDIR"
+ ${If} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\$1" "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndUnless}
+
+ ${If} ${FileExists} "$SMPROGRAMS\$1"
+ ; Count of Start Menu pinned shortcuts before unpinning.
+ ${PinnedToStartMenuLnkCount} $R9
+
+ ; Having multiple shortcuts pointing to different installations with
+ ; the same AppUserModelID (e.g. side by side installations of the
+ ; same version) will make the TaskBar shortcut's lists into an bad
+ ; state where the lists are not shown. To prevent this first
+ ; uninstall the pinned item.
+ ApplicationID::UninstallPinnedItem "$SMPROGRAMS\$1"
+
+ ; Count of Start Menu pinned shortcuts after unpinning.
+ ${PinnedToStartMenuLnkCount} $R8
+
+ ; If there is a change in the number of Start Menu pinned shortcuts
+ ; assume that unpinning unpinned a side by side installation from
+ ; the Start Menu and pin this installation to the Start Menu.
+ ${Unless} $R8 == $R9
+ ; Pin the shortcut to the Start Menu. 5381 is the shell32.dll
+ ; resource id for the "Pin to Start Menu" string.
+ InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5381"
+ ${EndUnless}
+
+ ; Pin the shortcut to the TaskBar. 5386 is the shell32.dll resource
+ ; id for the "Pin to Taskbar" string.
+ InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5386"
+
+ ; Delete the shortcut if it was created
+ ${If} "$8" == "true"
+ Delete "$SMPROGRAMS\$1"
+ ${EndIf}
+ ${EndIf}
+
+ ${If} $TmpVal == "HKCU"
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to all users
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define PinToTaskBar "!insertmacro PinToTaskBar"
+
+; Adds a shortcut to the root of the Start Menu Programs directory if the
+; application's Start Menu Programs directory exists with a shortcut pointing to
+; this installation directory. This will also remove the old shortcuts and the
+; application's Start Menu Programs directory by calling the RemoveStartMenuDir
+; macro.
+!macro MigrateStartMenuShortcut
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ClearErrors
+ ReadINIStr $5 "$0" "SMPROGRAMS" "RelativePathToDir"
+ ${Unless} ${Errors}
+ ClearErrors
+ ReadINIStr $1 "$0" "STARTMENU" "Shortcut0"
+ ${If} ${Errors}
+ ; The STARTMENU ini section doesn't exist.
+ ${LogStartMenuShortcut} "${BrandFullName}.lnk"
+ ${GetLongPath} "$SMPROGRAMS" $2
+ ${GetLongPath} "$2\$5" $1
+ ${If} "$1" != ""
+ ClearErrors
+ ReadINIStr $3 "$0" "SMPROGRAMS" "Shortcut0"
+ ${Unless} ${Errors}
+ ${If} ${FileExists} "$1\$3"
+ ShellLink::GetShortCutTarget "$1\$3"
+ Pop $4
+ ${If} "$INSTDIR\${FileMainEXE}" == "$4"
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+ ; Remove the application's Start Menu Programs directory, shortcuts, and
+ ; ini section.
+ ${RemoveStartMenuDir}
+ ${EndUnless}
+ ${EndIf}
+!macroend
+!define MigrateStartMenuShortcut "!insertmacro MigrateStartMenuShortcut"
+
+; Removes the application's start menu directory along with its shortcuts if
+; they exist and if they exist creates a start menu shortcut in the root of the
+; start menu directory (bug 598779). If the application's start menu directory
+; is not empty after removing the shortucts the directory will not be removed
+; since these additional items were not created by the installer (uses SHCTX).
+!macro RemoveStartMenuDir
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ; Delete Start Menu Programs shortcuts, directory if it is empty, and
+ ; parent directories if they are empty up to but not including the start
+ ; menu directory.
+ ${GetLongPath} "$SMPROGRAMS" $1
+ ClearErrors
+ ReadINIStr $2 "$0" "SMPROGRAMS" "RelativePathToDir"
+ ${Unless} ${Errors}
+ ${GetLongPath} "$1\$2" $2
+ ${If} "$2" != ""
+ ; Delete shortucts in the Start Menu Programs directory.
+ StrCpy $3 0
+ ${Do}
+ ClearErrors
+ ReadINIStr $4 "$0" "SMPROGRAMS" "Shortcut$3"
+ ; Stop if there are no more entries
+ ${If} ${Errors}
+ ${ExitDo}
+ ${EndIf}
+ ${If} ${FileExists} "$2\$4"
+ ShellLink::GetShortCutTarget "$2\$4"
+ Pop $5
+ ${If} "$INSTDIR\${FileMainEXE}" == "$5"
+ Delete "$2\$4"
+ ${EndIf}
+ ${EndIf}
+ IntOp $3 $3 + 1 ; Increment the counter
+ ${Loop}
+ ; Delete Start Menu Programs directory and parent directories
+ ${Do}
+ ; Stop if the current directory is the start menu directory
+ ${If} "$1" == "$2"
+ ${ExitDo}
+ ${EndIf}
+ ClearErrors
+ RmDir "$2"
+ ; Stop if removing the directory failed
+ ${If} ${Errors}
+ ${ExitDo}
+ ${EndIf}
+ ${GetParent} "$2" $2
+ ${Loop}
+ ${EndIf}
+ DeleteINISec "$0" "SMPROGRAMS"
+ ${EndUnless}
+ ${EndIf}
+!macroend
+!define RemoveStartMenuDir "!insertmacro RemoveStartMenuDir"
+
+; Creates the shortcuts log ini file with the appropriate entries if it doesn't
+; already exist.
+!macro CreateShortcutsLog
+ ${GetShortcutsLogPath} $0
+ ${Unless} ${FileExists} "$0"
+ ${LogStartMenuShortcut} "${BrandFullName}.lnk"
+ ${LogQuickLaunchShortcut} "${BrandFullName}.lnk"
+ ${LogDesktopShortcut} "${BrandFullName}.lnk"
+ ${EndUnless}
+!macroend
+!define CreateShortcutsLog "!insertmacro CreateShortcutsLog"
+
+; The files to check if they are in use during (un)install so the restart is
+; required message is displayed. All files must be located in the $INSTDIR
+; directory.
+!macro PushFilesToCheck
+ ; The first string to be pushed onto the stack MUST be "end" to indicate
+ ; that there are no more files to check in $INSTDIR and the last string
+ ; should be ${FileMainEXE} so if it is in use the CheckForFilesInUse macro
+ ; returns after the first check.
+ Push "end"
+ Push "AccessibleMarshal.dll"
+ Push "IA2Marshal.dll"
+ Push "freebl3.dll"
+ Push "nssckbi.dll"
+ Push "nspr4.dll"
+ Push "nssdbm3.dll"
+ Push "mozsqlite3.dll"
+ Push "xpcom.dll"
+ Push "crashreporter.exe"
+ Push "minidump-analyzer.exe"
+ Push "updater.exe"
+ Push "${FileMainEXE}"
+!macroend
+!define PushFilesToCheck "!insertmacro PushFilesToCheck"
+
+
+; Pushes the string "true" to the top of the stack if the Firewall service is
+; running and pushes the string "false" to the top of the stack if it isn't.
+!define SC_MANAGER_ALL_ACCESS 0x3F
+!define SERVICE_QUERY_CONFIG 0x0001
+!define SERVICE_QUERY_STATUS 0x0004
+!define SERVICE_RUNNING 0x4
+
+!macro IsFirewallSvcRunning
+ Push $R9
+ Push $R8
+ Push $R7
+ Push $R6
+ Push "false"
+
+ System::Call 'advapi32::OpenSCManagerW(n, n, i ${SC_MANAGER_ALL_ACCESS}) i.R6'
+ ${If} $R6 != 0
+ ; MpsSvc is the Firewall service on Windows Vista and above.
+ ; When opening the service with SERVICE_QUERY_CONFIG the return value will
+ ; be 0 if the service is not installed.
+ System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_CONFIG}) i.R7'
+ ${If} $R7 != 0
+ System::Call 'advapi32::CloseServiceHandle(i R7) n'
+ ; Open the service with SERVICE_QUERY_CONFIG so its status can be queried.
+ System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_STATUS}) i.R7'
+ ${Else}
+ ; SharedAccess is the Firewall service on Windows XP.
+ ; When opening the service with SERVICE_QUERY_CONFIG the return value will
+ ; be 0 if the service is not installed.
+ System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_CONFIG}) i.R7'
+ ${If} $R7 != 0
+ System::Call 'advapi32::CloseServiceHandle(i R7) n'
+ ; Open the service with SERVICE_QUERY_CONFIG so its status can be
+ ; queried.
+ System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_STATUS}) i.R7'
+ ${EndIf}
+ ${EndIf}
+ ; Did the calls to OpenServiceW succeed?
+ ${If} $R7 != 0
+ System::Call '*(i,i,i,i,i,i,i) i.R9'
+ ; Query the current status of the service.
+ System::Call 'advapi32::QueryServiceStatus(i R7, i $R9) i'
+ System::Call '*$R9(i, i.R8)'
+ System::Free $R9
+ System::Call 'advapi32::CloseServiceHandle(i R7) n'
+ IntFmt $R8 "0x%X" $R8
+ ${If} $R8 == ${SERVICE_RUNNING}
+ Pop $R9
+ Push "true"
+ ${EndIf}
+ ${EndIf}
+ System::Call 'advapi32::CloseServiceHandle(i R6) n'
+ ${EndIf}
+
+ Exch 1
+ Pop $R6
+ Exch 1
+ Pop $R7
+ Exch 1
+ Pop $R8
+ Exch 1
+ Pop $R9
+!macroend
+!define IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
+!define un.IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
+
+; Sets this installation as the default browser by setting the registry keys
+; under HKEY_CURRENT_USER via registry calls and using the AppAssocReg NSIS
+; plugin for Vista and above. This is a function instead of a macro so it is
+; easily called from an elevated instance of the binary. Since this can be
+; called by an elevated instance logging is not performed in this function.
+Function SetAsDefaultAppUserHKCU
+ ; Only set as the user's StartMenuInternet browser if the StartMenuInternet
+ ; registry keys are for this install.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ ClearErrors
+ ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${If} ${Errors}
+ ${OrIf} ${AtMostWin2008R2}
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${EndIf}
+ ${Unless} ${Errors}
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR"
+ WriteRegStr HKCU "Software\Clients\StartMenuInternet" "" "$R9"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+
+ ${If} ${AtLeastWin8}
+ ${SetStartMenuInternet} "HKCU"
+ ${FixShellIconHandler} "HKCU"
+ ${FixClassKeys} ; Does not use SHCTX
+ ${EndIf}
+
+ ${SetHandlers}
+
+ ${If} ${AtLeastWinVista}
+ ; Only register as the handler on Vista and above if the app registry name
+ ; exists under the RegisteredApplications registry key. The protocol and
+ ; file handlers set previously at the user level will associate this install
+ ; as the default browser.
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\RegisteredApplications" "${AppRegName}"
+ ${Unless} ${Errors}
+ ; This is all protected by a user choice hash in Windows 8 so it won't
+ ; help, but it also won't hurt.
+ AppAssocReg::SetAppAsDefaultAll "${AppRegName}"
+ ${EndUnless}
+ ${EndIf}
+ ${RemoveDeprecatedKeys}
+ ${MigrateTaskBarShortcut}
+FunctionEnd
+
+; Helper for updating the shortcut application model IDs.
+Function FixShortcutAppModelIDs
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ${UpdateShortcutAppModelIDs} "$INSTDIR\${FileMainEXE}" "$AppUserModelID" $0
+ ${EndIf}
+FunctionEnd
+
+; Helper for adding Firewall exceptions during install and after app update.
+Function AddFirewallEntries
+ ${IsFirewallSvcRunning}
+ Pop $0
+ ${If} "$0" == "true"
+ liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
+ ${EndIf}
+FunctionEnd
+
+; The !ifdef NO_LOG prevents warnings when compiling the installer.nsi due to
+; this function only being used by the uninstaller.nsi.
+!ifdef NO_LOG
+
+Function SetAsDefaultAppUser
+ ; On Win8, we want to avoid having a UAC prompt since we'll already have
+ ; another action for control panel default browser selection popping up
+ ; to the user. Win8 is the first OS where the start menu keys can be
+ ; added into HKCU. The call to SetAsDefaultAppUserHKCU will have already
+ ; set the HKCU keys for SetStartMenuInternet.
+ ${If} ${AtLeastWin8}
+ ; Check if this is running in an elevated process
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors} ; Not elevated
+ Call SetAsDefaultAppUserHKCU
+ ${Else} ; Elevated - execute the function in the unelevated process
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ Return ; Nothing more needs to be done
+ ${EndIf}
+
+ ; Before Win8, it is only possible to set this installation of the application
+ ; as the StartMenuInternet handler if it was added to the HKLM
+ ; StartMenuInternet registry keys.
+ ; http://support.microsoft.com/kb/297878
+
+ ; Check if this install location registered as the StartMenuInternet client
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ ClearErrors
+ ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${If} ${Errors}
+ ${OrIf} ${AtMostWin2008R2}
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${EndIf}
+
+ ${Unless} ${Errors}
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR"
+ ; Check if this is running in an elevated process
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors} ; Not elevated
+ Call SetAsDefaultAppUserHKCU
+ ${Else} ; Elevated - execute the function in the unelevated process
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ Return ; Nothing more needs to be done
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ ; The code after ElevateUAC won't be executed on Vista and above when the
+ ; user:
+ ; a) is a member of the administrators group (e.g. elevation is required)
+ ; b) is not a member of the administrators group and chooses to elevate
+ ${ElevateUAC}
+
+ ${SetStartMenuInternet} "HKLM"
+
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+
+ ${FixClassKeys} ; Does not use SHCTX
+ ${FixShellIconHandler} "HKLM"
+ ${RemoveDeprecatedKeys} ; Does not use SHCTX
+
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors}
+ Call SetAsDefaultAppUserHKCU
+ ${Else}
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+FunctionEnd
+!define SetAsDefaultAppUser "Call SetAsDefaultAppUser"
+
+!endif ; NO_LOG
diff --git a/browser/installer/windows/nsis/stub.nsi b/browser/installer/windows/nsis/stub.nsi
new file mode 100644
index 000000000..176f641b3
--- /dev/null
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -0,0 +1,2093 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Required Plugins:
+# AppAssocReg
+# CertCheck
+# InetBgDL
+# ShellLink
+# UAC
+
+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
+!verbose 3
+
+SetDatablockOptimize on
+SetCompress off
+CRCCheck on
+
+RequestExecutionLevel user
+
+; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
+; be removed after we require NSIS 3.0a2 or greater.
+!ifdef NSIS_PACKEDVERSION
+ Unicode true
+ ManifestSupportedOS all
+ ManifestDPIAware true
+!endif
+
+!addplugindir ./
+
+Var Dialog
+Var Progressbar
+Var ProgressbarMarqueeIntervalMS
+Var LabelDownloading
+Var LabelInstalling
+Var LabelFreeSpace
+Var CheckboxSetAsDefault
+Var CheckboxShortcutOnBar ; Used for Quicklaunch or Taskbar as appropriate
+Var CheckboxShortcutInStartMenu
+Var CheckboxShortcutOnDesktop
+Var CheckboxSendPing
+Var CheckboxInstallMaintSvc
+Var DirRequest
+Var ButtonBrowse
+Var LabelBlurb1
+Var LabelBlurb2
+Var LabelBlurb3
+Var BitmapBlurb1
+Var BitmapBlurb2
+Var BitmapBlurb3
+Var HwndBitmapBlurb1
+Var HwndBitmapBlurb2
+Var HWndBitmapBlurb3
+
+Var FontNormal
+Var FontItalic
+Var FontBlurb
+
+Var WasOptionsButtonClicked
+Var CanWriteToInstallDir
+Var HasRequiredSpaceAvailable
+Var IsDownloadFinished
+Var DownloadSizeBytes
+Var HalfOfDownload
+Var DownloadReset
+Var ExistingTopDir
+Var SpaceAvailableBytes
+Var InitialInstallDir
+Var HandleDownload
+Var CanSetAsDefault
+Var InstallCounterStep
+Var InstallStepSize
+Var InstallTotalSteps
+Var ProgressCompleted
+Var ProgressTotal
+Var TmpVal
+
+Var ExitCode
+Var FirefoxLaunchCode
+
+; The first three tick counts are for the start of a phase and equate equate to
+; the display of individual installer pages.
+Var StartIntroPhaseTickCount
+Var StartOptionsPhaseTickCount
+Var StartDownloadPhaseTickCount
+; Since the Intro and Options pages can be displayed multiple times the total
+; seconds spent on each of these pages is reported.
+Var IntroPhaseSeconds
+Var OptionsPhaseSeconds
+; The tick count for the last download.
+Var StartLastDownloadTickCount
+; The number of seconds from the start of the download phase until the first
+; bytes are received. This is only recorded for first request so it is possible
+; to determine connection issues for the first request.
+Var DownloadFirstTransferSeconds
+; The last four tick counts are for the end of a phase in the installation page.
+Var EndDownloadPhaseTickCount
+Var EndPreInstallPhaseTickCount
+Var EndInstallPhaseTickCount
+Var EndFinishPhaseTickCount
+
+Var InitialInstallRequirementsCode
+Var ExistingProfile
+Var ExistingVersion
+Var ExistingBuildID
+Var DownloadedBytes
+Var DownloadRetryCount
+Var OpenedDownloadPage
+Var DownloadServerIP
+Var PostSigningData
+
+Var ControlHeightPX
+Var ControlRightPX
+
+; Uncomment the following to prevent pinging the metrics server when testing
+; the stub installer
+;!define STUB_DEBUG
+
+!define StubURLVersion "v7"
+
+; Successful install exit code
+!define ERR_SUCCESS 0
+
+/**
+ * The following errors prefixed with ERR_DOWNLOAD apply to the download phase.
+ */
+; The download was cancelled by the user
+!define ERR_DOWNLOAD_CANCEL 10
+
+; Too many attempts to download. The maximum attempts is defined in
+; DownloadMaxRetries.
+!define ERR_DOWNLOAD_TOO_MANY_RETRIES 11
+
+/**
+ * The following errors prefixed with ERR_PREINSTALL apply to the pre-install
+ * check phase.
+ */
+; Unable to acquire a file handle to the downloaded file
+!define ERR_PREINSTALL_INVALID_HANDLE 20
+
+; The downloaded file's certificate is not trusted by the certificate store.
+!define ERR_PREINSTALL_CERT_UNTRUSTED 21
+
+; The downloaded file's certificate attribute values were incorrect.
+!define ERR_PREINSTALL_CERT_ATTRIBUTES 22
+
+; The downloaded file's certificate is not trusted by the certificate store and
+; certificate attribute values were incorrect.
+!define ERR_PREINSTALL_CERT_UNTRUSTED_AND_ATTRIBUTES 23
+
+/**
+ * The following errors prefixed with ERR_INSTALL apply to the install phase.
+ */
+; The installation timed out. The installation timeout is defined by the number
+; of progress steps defined in InstallTotalSteps and the install timer
+; interval defined in InstallIntervalMS
+!define ERR_INSTALL_TIMEOUT 30
+
+; Maximum times to retry the download before displaying an error
+!define DownloadMaxRetries 9
+
+; Minimum size expected to download in bytes
+!define DownloadMinSizeBytes 15728640 ; 15 MB
+
+; Maximum size expected to download in bytes
+!define DownloadMaxSizeBytes 73400320 ; 70 MB
+
+; Interval before retrying to download. 3 seconds is used along with 10
+; attempted downloads (the first attempt along with 9 retries) to give a
+; minimum of 30 seconds or retrying before giving up.
+!define DownloadRetryIntervalMS 3000
+
+; Interval for the download timer
+!define DownloadIntervalMS 200
+
+; Interval for the install timer
+!define InstallIntervalMS 100
+
+; The first step for the install progress bar. By starting with a large step
+; immediate feedback is given to the user.
+!define InstallProgressFirstStep 20
+
+; The finish step size to quickly increment the progress bar after the
+; installation has finished.
+!define InstallProgressFinishStep 40
+
+; Number of steps for the install progress.
+; This might not be enough when installing on a slow network drive so it will
+; fallback to downloading the full installer if it reaches this number. The size
+; of the install progress step is increased when the full installer finishes
+; instead of waiting.
+
+; Approximately 150 seconds with a 100 millisecond timer and a first step of 20
+; as defined by InstallProgressFirstStep.
+!define /math InstallCleanTotalSteps ${InstallProgressFirstStep} + 1500
+
+; Approximately 165 seconds (minus 0.2 seconds for each file that is removed)
+; with a 100 millisecond timer and a first step of 20 as defined by
+; InstallProgressFirstStep .
+!define /math InstallPaveOverTotalSteps ${InstallProgressFirstStep} + 1800
+
+; On Vista and above attempt to elevate Standard Users in addition to users that
+; are a member of the Administrators group.
+!define NONADMIN_ELEVATE
+
+!define CONFIG_INI "config.ini"
+
+!ifndef FILE_SHARE_READ
+ !define FILE_SHARE_READ 1
+!endif
+!ifndef GENERIC_READ
+ !define GENERIC_READ 0x80000000
+!endif
+!ifndef OPEN_EXISTING
+ !define OPEN_EXISTING 3
+!endif
+!ifndef INVALID_HANDLE_VALUE
+ !define INVALID_HANDLE_VALUE -1
+!endif
+
+!include "nsDialogs.nsh"
+!include "LogicLib.nsh"
+!include "FileFunc.nsh"
+!include "TextFunc.nsh"
+!include "WinVer.nsh"
+!include "WordFunc.nsh"
+
+!insertmacro GetParameters
+!insertmacro GetOptions
+!insertmacro LineFind
+!insertmacro StrFilter
+
+!include "locales.nsi"
+!include "branding.nsi"
+
+!include "defines.nsi"
+
+; Must be included after defines.nsi
+!include "locale-fonts.nsh"
+
+; The OFFICIAL define is a workaround to support different urls for Release and
+; Beta since they share the same branding when building with other branches that
+; set the update channel to beta.
+!ifdef OFFICIAL
+!ifdef BETA_UPDATE_CHANNEL
+!undef URLStubDownload
+!define URLStubDownload "http://download.mozilla.org/?os=win&lang=${AB_CD}&product=firefox-beta-latest"
+!undef URLManualDownload
+!define URLManualDownload "https://www.mozilla.org/${AB_CD}/firefox/installer-help/?channel=beta&installer_lang=${AB_CD}"
+!undef Channel
+!define Channel "beta"
+!endif
+!endif
+
+!include "common.nsh"
+
+!insertmacro ElevateUAC
+!insertmacro GetLongPath
+!insertmacro GetPathFromString
+!insertmacro GetParent
+!insertmacro GetSingleInstallPath
+!insertmacro GetTextWidthHeight
+!insertmacro IsUserAdmin
+!insertmacro RemovePrecompleteEntries
+!insertmacro SetBrandNameVars
+!insertmacro ITBL3Create
+!insertmacro UnloadUAC
+
+VIAddVersionKey "FileDescription" "${BrandShortName} Stub Installer"
+VIAddVersionKey "OriginalFilename" "setup-stub.exe"
+
+Name "$BrandFullName"
+OutFile "setup-stub.exe"
+icon "setup.ico"
+XPStyle on
+BrandingText " "
+ChangeUI all "nsisui.exe"
+!ifdef HAVE_64BIT_BUILD
+ InstallDir "$PROGRAMFILES64\${BrandFullName}\"
+!else
+ InstallDir "$PROGRAMFILES32\${BrandFullName}\"
+!endif
+
+!ifdef ${AB_CD}_rtl
+ LoadLanguageFile "locale-rtl.nlf"
+!else
+ LoadLanguageFile "locale.nlf"
+!endif
+
+!include "nsisstrings.nlf"
+
+!if "${AB_CD}" == "en-US"
+ ; Custom strings for en-US. This is done here so they aren't translated.
+ !include oneoff_en-US.nsh
+!else
+ !define INTRO_BLURB "$(INTRO_BLURB1)"
+ !define INSTALL_BLURB1 "$(INSTALL_BLURB1)"
+ !define INSTALL_BLURB2 "$(INSTALL_BLURB2)"
+ !define INSTALL_BLURB3 "$(INSTALL_BLURB3)"
+!endif
+
+Caption "$(WIN_CAPTION)"
+
+Page custom createDummy ; Needed to enable the Intro page's back button
+Page custom createIntro leaveIntro ; Introduction page
+Page custom createOptions leaveOptions ; Options page
+Page custom createInstall ; Download / Installation page
+
+Function .onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ StrCpy $LANGUAGE 0
+ ; This macro is used to set the brand name variables but the ini file method
+ ; isn't supported for the stub installer.
+ ${SetBrandNameVars} "$PLUGINSDIR\ignored.ini"
+
+ ; Don't install on systems that don't support SSE2. The parameter value of
+ ; 10 is for PF_XMMI64_INSTRUCTIONS_AVAILABLE which will check whether the
+ ; SSE2 instruction set is available.
+ System::Call "kernel32::IsProcessorFeaturePresent(i 10)i .R7"
+
+!ifdef HAVE_64BIT_BUILD
+ ; Restrict x64 builds from being installed on x86 and pre Win7
+ ${Unless} ${RunningX64}
+ ${OrUnless} ${AtLeastWin7}
+ ${If} "$R7" == "0"
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
+ ${Else}
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_MSG)"
+ ${EndIf}
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$R7" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndUnless}
+
+ SetRegView 64
+!else
+ StrCpy $R8 "0"
+ ${If} ${AtMostWin2000}
+ StrCpy $R8 "1"
+ ${EndIf}
+
+ ${If} ${IsWinXP}
+ ${AndIf} ${AtMostServicePack} 1
+ StrCpy $R8 "1"
+ ${EndIf}
+
+ ${If} $R8 == "1"
+ ; XXX-rstrong - some systems failed the AtLeastWin2000 test that we
+ ; used to use for an unknown reason and likely fail the AtMostWin2000
+ ; and possibly the IsWinXP test as well. To work around this also
+ ; check if the Windows NT registry Key exists and if it does if the
+ ; first char in CurrentVersion is equal to 3 (Windows NT 3.5 and
+ ; 3.5.1), 4 (Windows NT 4), or 5 (Windows 2000 and Windows XP).
+ StrCpy $R8 ""
+ ClearErrors
+ ReadRegStr $R8 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" "CurrentVersion"
+ StrCpy $R8 "$R8" 1
+ ${If} ${Errors}
+ ${OrIf} "$R8" == "3"
+ ${OrIf} "$R8" == "4"
+ ${OrIf} "$R8" == "5"
+ ${If} "$R7" == "0"
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
+ ${Else}
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_MSG)"
+ ${EndIf}
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$R7" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndIf}
+ ${EndUnless}
+!endif
+
+ ${If} "$R7" == "0"
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_CPU_MSG)" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndIf}
+
+ ; Require elevation if the user can elevate
+ ${ElevateUAC}
+
+; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
+; removed after we require NSIS 3.0a2 or greater.
+!ifndef NSIS_PACKEDVERSION
+ ${If} ${AtLeastWinVista}
+ System::Call 'user32::SetProcessDPIAware()'
+ ${EndIf}
+!endif
+
+ SetShellVarContext all ; Set SHCTX to HKLM
+ ${GetSingleInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $R9
+
+ ${If} "$R9" == "false"
+ SetShellVarContext current ; Set SHCTX to HKCU
+ ${GetSingleInstallPath} "Software\Mozilla\${BrandFullNameInternal}" $R9
+
+ ${If} ${RunningX64}
+ ; In HKCU there is no WOW64 redirection, which means we may have gotten
+ ; the path to a 32-bit install even though we're 64-bit, or vice-versa.
+ ; In that case, just use the default path instead of offering an upgrade.
+ ; But only do that override if the existing install is in Program Files,
+ ; because that's the only place we can be sure is specific
+ ; to either 32 or 64 bit applications.
+ ; The WordFind syntax below searches for the first occurence of the
+ ; "delimiter" (the Program Files path) in the install path and returns
+ ; anything that appears before that. If nothing appears before that,
+ ; then the install is under Program Files (32 or 64).
+!ifdef HAVE_64BIT_BUILD
+ ${WordFind} $R9 $PROGRAMFILES32 "+1{" $0
+!else
+ ${WordFind} $R9 $PROGRAMFILES64 "+1{" $0
+!endif
+ ${If} $0 == ""
+ StrCpy $R9 "false"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ ${If} "$R9" != "false"
+ StrCpy $INSTDIR "$R9"
+ ${EndIf}
+
+ ; Used to determine if the default installation directory was used.
+ StrCpy $InitialInstallDir "$INSTDIR"
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" \
+ "Write Test"
+
+ ; Only display set as default when there is write access to HKLM and on Win7
+ ; and below.
+ ${If} ${Errors}
+ ${OrIf} ${AtLeastWin8}
+ StrCpy $CanSetAsDefault "false"
+ StrCpy $CheckboxSetAsDefault "0"
+ ${Else}
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $CanSetAsDefault "true"
+ ${EndIf}
+
+ ; The interval in MS used for the progress bars set as marquee.
+ ${If} ${AtLeastWinVista}
+ StrCpy $ProgressbarMarqueeIntervalMS "10"
+ ${Else}
+ StrCpy $ProgressbarMarqueeIntervalMS "50"
+ ${EndIf}
+
+ ; Initialize the majority of variables except those that need to be reset
+ ; when a page is displayed.
+ StrCpy $IntroPhaseSeconds "0"
+ StrCpy $OptionsPhaseSeconds "0"
+ StrCpy $EndPreInstallPhaseTickCount "0"
+ StrCpy $EndInstallPhaseTickCount "0"
+ StrCpy $InitialInstallRequirementsCode ""
+ StrCpy $IsDownloadFinished ""
+ StrCpy $FirefoxLaunchCode "0"
+ StrCpy $CheckboxShortcutOnBar "1"
+ StrCpy $CheckboxShortcutInStartMenu "1"
+ StrCpy $CheckboxShortcutOnDesktop "1"
+ StrCpy $CheckboxSendPing "1"
+!ifdef MOZ_MAINTENANCE_SERVICE
+ StrCpy $CheckboxInstallMaintSvc "1"
+!else
+ StrCpy $CheckboxInstallMaintSvc "0"
+!endif
+ StrCpy $WasOptionsButtonClicked "0"
+
+ StrCpy $0 ""
+!ifdef FONT_FILE1
+ ${If} ${FileExists} "$FONTS\${FONT_FILE1}"
+ StrCpy $0 "${FONT_NAME1}"
+ ${EndIf}
+!endif
+
+!ifdef FONT_FILE2
+ ${If} $0 == ""
+ ${AndIf} ${FileExists} "$FONTS\${FONT_FILE2}"
+ StrCpy $0 "${FONT_NAME2}"
+ ${EndIf}
+!endif
+
+ ${If} $0 == ""
+ StrCpy $0 "$(^Font)"
+ ${EndIf}
+
+ CreateFont $FontBlurb "$0" "12" "500"
+ CreateFont $FontNormal "$0" "11" "500"
+ CreateFont $FontItalic "$0" "11" "500" /ITALIC
+
+ InitPluginsDir
+ File /oname=$PLUGINSDIR\bgintro.bmp "bgintro.bmp"
+ File /oname=$PLUGINSDIR\appname.bmp "appname.bmp"
+ File /oname=$PLUGINSDIR\clock.bmp "clock.bmp"
+ File /oname=$PLUGINSDIR\particles.bmp "particles.bmp"
+!ifdef ${AB_CD}_rtl
+ ; The horizontally flipped pencil looks better in RTL
+ File /oname=$PLUGINSDIR\pencil.bmp "pencil-rtl.bmp"
+!else
+ File /oname=$PLUGINSDIR\pencil.bmp "pencil.bmp"
+!endif
+FunctionEnd
+
+; .onGUIInit isn't needed except for RTL locales
+!ifdef ${AB_CD}_rtl
+Function .onGUIInit
+ ; Since NSIS RTL support doesn't mirror progress bars use Windows mirroring.
+ ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LAYOUTRTL}
+ ${RemoveExStyle} $HWNDPARENT ${WS_EX_RTLREADING}
+ ${RemoveExStyle} $HWNDPARENT ${WS_EX_RIGHT}
+ ${NSD_AddExStyle} $HWNDPARENT ${WS_EX_LEFT}|${WS_EX_LTRREADING}
+FunctionEnd
+!endif
+
+Function .onGUIEnd
+ Delete "$PLUGINSDIR\_temp"
+ Delete "$PLUGINSDIR\download.exe"
+ Delete "$PLUGINSDIR\${CONFIG_INI}"
+
+ ${UnloadUAC}
+FunctionEnd
+
+Function .onUserAbort
+ ${NSD_KillTimer} StartDownload
+ ${NSD_KillTimer} OnDownload
+ ${NSD_KillTimer} CheckInstall
+ ${NSD_KillTimer} FinishInstall
+ ${NSD_KillTimer} FinishProgressBar
+ ${NSD_KillTimer} DisplayDownloadError
+
+ ${If} "$IsDownloadFinished" != ""
+ Call DisplayDownloadError
+ ; Aborting the abort will allow SendPing which is called by
+ ; DisplayDownloadError to hide the installer window and close the installer
+ ; after it sends the metrics ping.
+ Abort
+ ${EndIf}
+FunctionEnd
+
+Function SendPing
+ HideWindow
+ ; Try to send a ping if a download was attempted
+ ${If} $CheckboxSendPing == 1
+ ${AndIf} $IsDownloadFinished != ""
+ ; Get the tick count for the completion of all phases.
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $EndFinishPhaseTickCount
+
+ ; When the value of $IsDownloadFinished is false the download was started
+ ; but didn't finish. In this case the tick count stored in
+ ; $EndFinishPhaseTickCount is used to determine how long the download was
+ ; in progress.
+ ${If} "$IsDownloadFinished" == "false"
+ ${OrIf} "$EndDownloadPhaseTickCount" == ""
+ StrCpy $EndDownloadPhaseTickCount "$EndFinishPhaseTickCount"
+ ; Cancel the download in progress
+ InetBgDL::Get /RESET /END
+ ${EndIf}
+
+
+ ; When $DownloadFirstTransferSeconds equals an empty string the download
+ ; never successfully started so set the value to 0. It will be possible to
+ ; determine that the download didn't successfully start from the seconds for
+ ; the last download.
+ ${If} "$DownloadFirstTransferSeconds" == ""
+ StrCpy $DownloadFirstTransferSeconds "0"
+ ${EndIf}
+
+ ; When $StartLastDownloadTickCount equals an empty string the download never
+ ; successfully started so set the value to $EndDownloadPhaseTickCount to
+ ; compute the correct value.
+ ${If} $StartLastDownloadTickCount == ""
+ ; This could happen if the download never successfully starts
+ StrCpy $StartLastDownloadTickCount "$EndDownloadPhaseTickCount"
+ ${EndIf}
+
+ ; When $EndPreInstallPhaseTickCount equals 0 the installation phase was
+ ; never completed so set its value to $EndFinishPhaseTickCount to compute
+ ; the correct value.
+ ${If} "$EndPreInstallPhaseTickCount" == "0"
+ StrCpy $EndPreInstallPhaseTickCount "$EndFinishPhaseTickCount"
+ ${EndIf}
+
+ ; When $EndInstallPhaseTickCount equals 0 the installation phase was never
+ ; completed so set its value to $EndFinishPhaseTickCount to compute the
+ ; correct value.
+ ${If} "$EndInstallPhaseTickCount" == "0"
+ StrCpy $EndInstallPhaseTickCount "$EndFinishPhaseTickCount"
+ ${EndIf}
+
+ ; Get the seconds elapsed from the start of the download phase to the end of
+ ; the download phase.
+ ${GetSecondsElapsed} "$StartDownloadPhaseTickCount" "$EndDownloadPhaseTickCount" $0
+
+ ; Get the seconds elapsed from the start of the last download to the end of
+ ; the last download.
+ ${GetSecondsElapsed} "$StartLastDownloadTickCount" "$EndDownloadPhaseTickCount" $1
+
+ ; Get the seconds elapsed from the end of the download phase to the
+ ; completion of the pre-installation check phase.
+ ${GetSecondsElapsed} "$EndDownloadPhaseTickCount" "$EndPreInstallPhaseTickCount" $2
+
+ ; Get the seconds elapsed from the end of the pre-installation check phase
+ ; to the completion of the installation phase.
+ ${GetSecondsElapsed} "$EndPreInstallPhaseTickCount" "$EndInstallPhaseTickCount" $3
+
+ ; Get the seconds elapsed from the end of the installation phase to the
+ ; completion of all phases.
+ ${GetSecondsElapsed} "$EndInstallPhaseTickCount" "$EndFinishPhaseTickCount" $4
+
+!ifdef HAVE_64BIT_BUILD
+ StrCpy $R0 "1"
+!else
+ StrCpy $R0 "0"
+!endif
+
+ ${If} ${RunningX64}
+ StrCpy $R1 "1"
+ ${Else}
+ StrCpy $R1 "0"
+ ${EndIf}
+
+ ; Though these values are sometimes incorrect due to bug 444664 it happens
+ ; so rarely it isn't worth working around it by reading the registry values.
+ ${WinVerGetMajor} $5
+ ${WinVerGetMinor} $6
+ ${WinVerGetBuild} $7
+ ${WinVerGetServicePackLevel} $8
+ ${If} ${IsServerOS}
+ StrCpy $9 "1"
+ ${Else}
+ StrCpy $9 "0"
+ ${EndIf}
+
+ ${If} "$ExitCode" == "${ERR_SUCCESS}"
+ ReadINIStr $R5 "$INSTDIR\application.ini" "App" "Version"
+ ReadINIStr $R6 "$INSTDIR\application.ini" "App" "BuildID"
+ ${Else}
+ StrCpy $R5 "0"
+ StrCpy $R6 "0"
+ ${EndIf}
+
+ ; Whether installed into the default installation directory
+ ${GetLongPath} "$INSTDIR" $R7
+ ${GetLongPath} "$InitialInstallDir" $R8
+ ${If} "$R7" == "$R8"
+ StrCpy $R7 "1"
+ ${Else}
+ StrCpy $R7 "0"
+ ${EndIf}
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" \
+ "Write Test"
+ ${If} ${Errors}
+ StrCpy $R8 "0"
+ ${Else}
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $R8 "1"
+ ${EndIf}
+
+ ${If} "$DownloadServerIP" == ""
+ StrCpy $DownloadServerIP "Unknown"
+ ${EndIf}
+
+ StrCpy $R2 ""
+ SetShellVarContext current ; Set SHCTX to the current user
+ ReadRegStr $R2 HKCU "Software\Classes\http\shell\open\command" ""
+ ${If} $R2 != ""
+ ${GetPathFromString} "$R2" $R2
+ ${GetParent} "$R2" $R3
+ ${GetLongPath} "$R3" $R3
+ ${If} $R3 == $INSTDIR
+ StrCpy $R2 "1" ; This Firefox install is set as default.
+ ${Else}
+ StrCpy $R2 "$R2" "" -11 # length of firefox.exe
+ ${If} "$R2" == "${FileMainEXE}"
+ StrCpy $R2 "2" ; Another Firefox install is set as default.
+ ${Else}
+ StrCpy $R2 "0"
+ ${EndIf}
+ ${EndIf}
+ ${Else}
+ StrCpy $R2 "0" ; Firefox is not set as default.
+ ${EndIf}
+
+ ${If} "$R2" == "0"
+ ${AndIf} ${AtLeastWinVista}
+ ; Check to see if this install location is currently set as the default
+ ; browser by Default Programs which is only available on Vista and above.
+ ClearErrors
+ ReadRegStr $R3 HKLM "Software\RegisteredApplications" "${AppRegName}"
+ ${Unless} ${Errors}
+ AppAssocReg::QueryAppIsDefaultAll "${AppRegName}" "effective"
+ Pop $R3
+ ${If} $R3 == "1"
+ StrCpy $R3 ""
+ ReadRegStr $R2 HKLM "Software\Classes\http\shell\open\command" ""
+ ${If} $R2 != ""
+ ${GetPathFromString} "$R2" $R2
+ ${GetParent} "$R2" $R3
+ ${GetLongPath} "$R3" $R3
+ ${If} $R3 == $INSTDIR
+ StrCpy $R2 "1" ; This Firefox install is set as default.
+ ${Else}
+ StrCpy $R2 "$R2" "" -11 # length of firefox.exe
+ ${If} "$R2" == "${FileMainEXE}"
+ StrCpy $R2 "2" ; Another Firefox install is set as default.
+ ${Else}
+ StrCpy $R2 "0"
+ ${EndIf}
+ ${EndIf}
+ ${Else}
+ StrCpy $R2 "0" ; Firefox is not set as default.
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+
+ ${If} $CanSetAsDefault == "true"
+ ${If} $CheckboxSetAsDefault == "1"
+ StrCpy $R3 "2"
+ ${Else}
+ StrCpy $R3 "3"
+ ${EndIf}
+ ${Else}
+ ${If} ${AtLeastWin8}
+ StrCpy $R3 "1"
+ ${Else}
+ StrCpy $R3 "0"
+ ${EndIf}
+ ${EndIf}
+
+!ifdef STUB_DEBUG
+ MessageBox MB_OK "${BaseURLStubPing} \
+ $\nStub URL Version = ${StubURLVersion}${StubURLVersionAppend} \
+ $\nBuild Channel = ${Channel} \
+ $\nUpdate Channel = ${UpdateChannel} \
+ $\nLocale = ${AB_CD} \
+ $\nFirefox x64 = $R0 \
+ $\nRunning x64 Windows = $R1 \
+ $\nMajor = $5 \
+ $\nMinor = $6 \
+ $\nBuild = $7 \
+ $\nServicePack = $8 \
+ $\nIsServer = $9 \
+ $\nExit Code = $ExitCode \
+ $\nFirefox Launch Code = $FirefoxLaunchCode \
+ $\nDownload Retry Count = $DownloadRetryCount \
+ $\nDownloaded Bytes = $DownloadedBytes \
+ $\nDownload Size Bytes = $DownloadSizeBytes \
+ $\nIntroduction Phase Seconds = $IntroPhaseSeconds \
+ $\nOptions Phase Seconds = $OptionsPhaseSeconds \
+ $\nDownload Phase Seconds = $0 \
+ $\nLast Download Seconds = $1 \
+ $\nDownload First Transfer Seconds = $DownloadFirstTransferSeconds \
+ $\nPreinstall Phase Seconds = $2 \
+ $\nInstall Phase Seconds = $3 \
+ $\nFinish Phase Seconds = $4 \
+ $\nInitial Install Requirements Code = $InitialInstallRequirementsCode \
+ $\nOpened Download Page = $OpenedDownloadPage \
+ $\nExisting Profile = $ExistingProfile \
+ $\nExisting Version = $ExistingVersion \
+ $\nExisting Build ID = $ExistingBuildID \
+ $\nNew Version = $R5 \
+ $\nNew Build ID = $R6 \
+ $\nDefault Install Dir = $R7 \
+ $\nHas Admin = $R8 \
+ $\nDefault Status = $R2 \
+ $\nSet As Sefault Status = $R3 \
+ $\nDownload Server IP = $DownloadServerIP \
+ $\nPost-Signing Data = $PostSigningData"
+ ; The following will exit the installer
+ SetAutoClose true
+ StrCpy $R9 "2"
+ Call RelativeGotoPage
+!else
+ ${NSD_CreateTimer} OnPing ${DownloadIntervalMS}
+ InetBgDL::Get "${BaseURLStubPing}/${StubURLVersion}${StubURLVersionAppend}/${Channel}/${UpdateChannel}/${AB_CD}/$R0/$R1/$5/$6/$7/$8/$9/$ExitCode/$FirefoxLaunchCode/$DownloadRetryCount/$DownloadedBytes/$DownloadSizeBytes/$IntroPhaseSeconds/$OptionsPhaseSeconds/$0/$1/$DownloadFirstTransferSeconds/$2/$3/$4/$InitialInstallRequirementsCode/$OpenedDownloadPage/$ExistingProfile/$ExistingVersion/$ExistingBuildID/$R5/$R6/$R7/$R8/$R2/$R3/$DownloadServerIP/$PostSigningData" \
+ "$PLUGINSDIR\_temp" /END
+!endif
+ ${Else}
+ ${If} "$IsDownloadFinished" == "false"
+ ; Cancel the download in progress
+ InetBgDL::Get /RESET /END
+ ${EndIf}
+ ; The following will exit the installer
+ SetAutoClose true
+ StrCpy $R9 "2"
+ Call RelativeGotoPage
+ ${EndIf}
+FunctionEnd
+
+Function createDummy
+FunctionEnd
+
+Function createIntro
+ nsDialogs::Create /NOUNLOAD 1018
+ Pop $Dialog
+
+ GetFunctionAddress $0 OnBack
+ nsDialogs::OnBack /NOUNLOAD $0
+
+!ifdef ${AB_CD}_rtl
+ ; For RTL align the text with the top of the F in the Firefox bitmap
+ StrCpy $0 "${INTRO_BLURB_RTL_TOP_DU}"
+!else
+ ; For LTR align the text with the top of the x in the Firefox bitmap
+ StrCpy $0 "${INTRO_BLURB_LTR_TOP_DU}"
+!endif
+ ${NSD_CreateLabel} ${INTRO_BLURB_EDGE_DU} $0 ${INTRO_BLURB_WIDTH_DU} 76u "${INTRO_BLURB}"
+ Pop $0
+ SendMessage $0 ${WM_SETFONT} $FontBlurb 0
+ SetCtlColors $0 ${INTRO_BLURB_TEXT_COLOR} transparent
+
+ SetCtlColors $HWNDPARENT ${FOOTER_CONTROL_TEXT_COLOR_NORMAL} ${FOOTER_BKGRD_COLOR}
+ GetDlgItem $0 $HWNDPARENT 10 ; Default browser checkbox
+ ${If} "$CanSetAsDefault" == "true"
+ ; The uxtheme must be disabled on checkboxes in order to override the
+ ; system font color.
+ System::Call 'uxtheme::SetWindowTheme(i $0 , w " ", w " ")'
+ SendMessage $0 ${WM_SETFONT} $FontNormal 0
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(MAKE_DEFAULT)"
+ SendMessage $0 ${BM_SETCHECK} 1 0
+ SetCtlColors $0 ${FOOTER_CONTROL_TEXT_COLOR_NORMAL} ${FOOTER_BKGRD_COLOR}
+ ${Else}
+ ShowWindow $0 ${SW_HIDE}
+ ${EndIf}
+ GetDlgItem $0 $HWNDPARENT 11
+ ShowWindow $0 ${SW_HIDE}
+
+ ${NSD_CreateBitmap} ${APPNAME_BMP_EDGE_DU} ${APPNAME_BMP_TOP_DU} \
+ ${APPNAME_BMP_WIDTH_DU} ${APPNAME_BMP_HEIGHT_DU} ""
+ Pop $2
+ ${SetStretchedTransparentImage} $2 $PLUGINSDIR\appname.bmp $0
+
+ ${NSD_CreateBitmap} 0 0 100% 100% ""
+ Pop $2
+ ${NSD_SetStretchedImage} $2 $PLUGINSDIR\bgintro.bmp $1
+
+ GetDlgItem $0 $HWNDPARENT 1 ; Install button
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(UPGRADE_BUTTON)"
+ ${Else}
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(INSTALL_BUTTON)"
+ ${EndIf}
+ ${NSD_SetFocus} $0
+
+ GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(CANCEL_BUTTON)"
+
+ GetDlgItem $0 $HWNDPARENT 3 ; Back button used for Options
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(OPTIONS_BUTTON)"
+
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $StartIntroPhaseTickCount
+
+ LockWindow off
+ nsDialogs::Show
+
+ ${NSD_FreeImage} $0
+ ${NSD_FreeImage} $1
+FunctionEnd
+
+Function leaveIntro
+ LockWindow on
+
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $0
+ ${GetSecondsElapsed} "$StartIntroPhaseTickCount" "$0" $IntroPhaseSeconds
+ ; It is possible for this value to be 0 if the user clicks fast enough so
+ ; increment the value by 1 if it is 0.
+ ${If} $IntroPhaseSeconds == 0
+ IntOp $IntroPhaseSeconds $IntroPhaseSeconds + 1
+ ${EndIf}
+
+ SetShellVarContext all ; Set SHCTX to All Users
+ ; If the user doesn't have write access to the installation directory set
+ ; the installation directory to a subdirectory of the All Users application
+ ; directory and if the user can't write to that location set the installation
+ ; directory to a subdirectory of the users local application directory
+ ; (e.g. non-roaming).
+ Call CanWrite
+ ${If} "$CanWriteToInstallDir" == "false"
+ StrCpy $INSTDIR "$APPDATA\${BrandFullName}\"
+ Call CanWrite
+ ${If} "$CanWriteToInstallDir" == "false"
+ ; This should never happen but just in case.
+ StrCpy $CanWriteToInstallDir "false"
+ ${Else}
+ StrCpy $INSTDIR "$LOCALAPPDATA\${BrandFullName}\"
+ Call CanWrite
+ ${EndIf}
+ ${EndIf}
+
+ Call CheckSpace
+
+ ${If} ${FileExists} "$INSTDIR"
+ ; Always display the long path if the path exists.
+ ${GetLongPath} "$INSTDIR" $INSTDIR
+ ${EndIf}
+
+FunctionEnd
+
+Function createOptions
+ ; Check whether the install requirements are satisfied using the default
+ ; values for metrics.
+ ${If} "$InitialInstallRequirementsCode" == ""
+ ${If} "$CanWriteToInstallDir" != "true"
+ ${AndIf} "$HasRequiredSpaceAvailable" != "true"
+ StrCpy $InitialInstallRequirementsCode "1"
+ ${ElseIf} "$CanWriteToInstallDir" != "true"
+ StrCpy $InitialInstallRequirementsCode "2"
+ ${ElseIf} "$HasRequiredSpaceAvailable" != "true"
+ StrCpy $InitialInstallRequirementsCode "3"
+ ${Else}
+ StrCpy $InitialInstallRequirementsCode "0"
+ ${EndIf}
+ ${EndIf}
+
+ ; Skip the options page unless the Options button was clicked as long as the
+ ; installation directory can be written to and there is the minimum required
+ ; space available.
+ ${If} "$WasOptionsButtonClicked" != "1"
+ ${If} "$CanWriteToInstallDir" == "true"
+ ${AndIf} "$HasRequiredSpaceAvailable" == "true"
+ Abort ; Skip the options page
+ ${EndIf}
+ ${EndIf}
+
+ StrCpy $ExistingTopDir ""
+
+ nsDialogs::Create /NOUNLOAD 1018
+ Pop $Dialog
+ ; Since the text color for controls is set in this Dialog the foreground and
+ ; background colors of the Dialog must also be hardcoded.
+ SetCtlColors $Dialog ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+
+ ${NSD_CreateLabel} ${OPTIONS_ITEM_EDGE_DU} 18u ${OPTIONS_ITEM_WIDTH_DU} \
+ 12u "$(CREATE_SHORTCUTS)"
+ Pop $0
+ SetCtlColors $0 ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $0 ${WM_SETFONT} $FontNormal 0
+
+ ${If} ${AtLeastWin7}
+ StrCpy $0 "$(ADD_SC_TASKBAR)"
+ ${Else}
+ StrCpy $0 "$(ADD_SC_QUICKLAUNCHBAR)"
+ ${EndIf}
+ ${NSD_CreateCheckbox} ${OPTIONS_SUBITEM_EDGE_DU} 38u \
+ ${OPTIONS_SUBITEM_WIDTH_DU} 12u "$0"
+ Pop $CheckboxShortcutOnBar
+ ; The uxtheme must be disabled on checkboxes in order to override the system
+ ; font color.
+ System::Call 'uxtheme::SetWindowTheme(i $CheckboxShortcutOnBar, w " ", w " ")'
+ SetCtlColors $CheckboxShortcutOnBar ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $CheckboxShortcutOnBar ${WM_SETFONT} $FontNormal 0
+ ${NSD_Check} $CheckboxShortcutOnBar
+
+ ${NSD_CreateCheckbox} ${OPTIONS_SUBITEM_EDGE_DU} 54u ${OPTIONS_SUBITEM_WIDTH_DU} \
+ 12u "$(ADD_CheckboxShortcutInStartMenu)"
+ Pop $CheckboxShortcutInStartMenu
+ ; The uxtheme must be disabled on checkboxes in order to override the system
+ ; font color.
+ System::Call 'uxtheme::SetWindowTheme(i $CheckboxShortcutInStartMenu, w " ", w " ")'
+ SetCtlColors $CheckboxShortcutInStartMenu ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $CheckboxShortcutInStartMenu ${WM_SETFONT} $FontNormal 0
+ ${NSD_Check} $CheckboxShortcutInStartMenu
+
+ ${NSD_CreateCheckbox} ${OPTIONS_SUBITEM_EDGE_DU} 70u ${OPTIONS_SUBITEM_WIDTH_DU} \
+ 12u "$(ADD_CheckboxShortcutOnDesktop)"
+ Pop $CheckboxShortcutOnDesktop
+ ; The uxtheme must be disabled on checkboxes in order to override the system
+ ; font color.
+ System::Call 'uxtheme::SetWindowTheme(i $CheckboxShortcutOnDesktop, w " ", w " ")'
+ SetCtlColors $CheckboxShortcutOnDesktop ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $CheckboxShortcutOnDesktop ${WM_SETFONT} $FontNormal 0
+ ${NSD_Check} $CheckboxShortcutOnDesktop
+
+ ${NSD_CreateLabel} ${OPTIONS_ITEM_EDGE_DU} 100u ${OPTIONS_ITEM_WIDTH_DU} \
+ 12u "$(DEST_FOLDER)"
+ Pop $0
+ SetCtlColors $0 ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $0 ${WM_SETFONT} $FontNormal 0
+
+ ${NSD_CreateDirRequest} ${OPTIONS_SUBITEM_EDGE_DU} 116u 159u 14u "$INSTDIR"
+ Pop $DirRequest
+ SetCtlColors $DirRequest ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $DirRequest ${WM_SETFONT} $FontNormal 0
+ System::Call shlwapi::SHAutoComplete(i $DirRequest, i ${SHACF_FILESYSTEM})
+ ${NSD_OnChange} $DirRequest OnChange_DirRequest
+
+!ifdef ${AB_CD}_rtl
+ ; Remove the RTL styling from the directory request text box
+ ${RemoveStyle} $DirRequest ${SS_RIGHT}
+ ${RemoveExStyle} $DirRequest ${WS_EX_RIGHT}
+ ${RemoveExStyle} $DirRequest ${WS_EX_RTLREADING}
+ ${NSD_AddStyle} $DirRequest ${SS_LEFT}
+ ${NSD_AddExStyle} $DirRequest ${WS_EX_LTRREADING}|${WS_EX_LEFT}
+!endif
+
+ ${NSD_CreateBrowseButton} 280u 116u 50u 14u "$(BROWSE_BUTTON)"
+ Pop $ButtonBrowse
+ SetCtlColors $ButtonBrowse "" ${COMMON_BKGRD_COLOR}
+ ${NSD_OnClick} $ButtonBrowse OnClick_ButtonBrowse
+
+ ; Get the number of pixels from the left of the Dialog to the right side of
+ ; the "Space Required:" and "Space Available:" labels prior to setting RTL so
+ ; the correct position of the controls can be set by NSIS for RTL locales.
+
+ ; Get the width and height of both labels and use the tallest for the height
+ ; and the widest to calculate where to place the labels after these labels.
+ ${GetTextExtent} "$(SPACE_REQUIRED)" $FontItalic $0 $1
+ ${GetTextExtent} "$(SPACE_AVAILABLE)" $FontItalic $2 $3
+ ${If} $1 > $3
+ StrCpy $ControlHeightPX "$1"
+ ${Else}
+ StrCpy $ControlHeightPX "$3"
+ ${EndIf}
+
+ IntOp $0 $0 + 8 ; Add padding to the control's width
+ ; Make both controls the same width as the widest control
+ ${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 134u $0 $ControlHeightPX "$(SPACE_REQUIRED)"
+ Pop $5
+ SetCtlColors $5 ${COMMON_TEXT_COLOR_FADED} ${COMMON_BKGRD_COLOR}
+ SendMessage $5 ${WM_SETFONT} $FontItalic 0
+
+ IntOp $2 $2 + 8 ; Add padding to the control's width
+ ${NSD_CreateLabelCenter} ${OPTIONS_SUBITEM_EDGE_DU} 145u $2 $ControlHeightPX "$(SPACE_AVAILABLE)"
+ Pop $6
+ SetCtlColors $6 ${COMMON_TEXT_COLOR_FADED} ${COMMON_BKGRD_COLOR}
+ SendMessage $6 ${WM_SETFONT} $FontItalic 0
+
+ ; Use the widest label for aligning the labels next to them
+ ${If} $0 > $2
+ StrCpy $6 "$5"
+ ${EndIf}
+ FindWindow $1 "#32770" "" $HWNDPARENT
+ ${GetDlgItemEndPX} $6 $ControlRightPX
+
+ IntOp $ControlRightPX $ControlRightPX + 6
+
+ ${NSD_CreateLabel} $ControlRightPX 134u 100% $ControlHeightPX \
+ "${APPROXIMATE_REQUIRED_SPACE_MB} $(MEGA)$(BYTE)"
+ Pop $7
+ SetCtlColors $7 ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $7 ${WM_SETFONT} $FontNormal 0
+
+ ; Create the free space label with an empty string and update it by calling
+ ; UpdateFreeSpaceLabel
+ ${NSD_CreateLabel} $ControlRightPX 145u 100% $ControlHeightPX " "
+ Pop $LabelFreeSpace
+ SetCtlColors $LabelFreeSpace ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $LabelFreeSpace ${WM_SETFONT} $FontNormal 0
+
+ Call UpdateFreeSpaceLabel
+
+ ${NSD_CreateCheckbox} ${OPTIONS_ITEM_EDGE_DU} 168u ${OPTIONS_SUBITEM_WIDTH_DU} \
+ 12u "$(SEND_PING)"
+ Pop $CheckboxSendPing
+ ; The uxtheme must be disabled on checkboxes in order to override the system
+ ; font color.
+ System::Call 'uxtheme::SetWindowTheme(i $CheckboxSendPing, w " ", w " ")'
+ SetCtlColors $CheckboxSendPing ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $CheckboxSendPing ${WM_SETFONT} $FontNormal 0
+ ${NSD_Check} $CheckboxSendPing
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+ ; We can only install the maintenance service if the user is an admin.
+ Call IsUserAdmin
+ Pop $0
+
+ ; Only show the maintenance service checkbox if we're on XP SP3 or higher;
+ ; we don't ever want to install it on XP without at least SP3 installed.
+ ${If} $0 == "true"
+ ${AndIf} ${IsWinXP}
+ ${AndIf} ${AtMostServicePack} 2
+ StrCpy $0 "false"
+ ${EndIf}
+
+ ; Only show the maintenance service checkbox if we have write access to HKLM
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" \
+ "Write Test"
+ ${If} ${Errors}
+ ${OrIf} $0 != "true"
+ StrCpy $CheckboxInstallMaintSvc "0"
+ ${Else}
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ ; Read the registry instead of using ServicesHelper::IsInstalled so the
+ ; plugin isn't included in the stub installer to lessen its size.
+ ClearErrors
+ ReadRegStr $0 HKLM "SYSTEM\CurrentControlSet\services\MozillaMaintenance" "ImagePath"
+ ${If} ${Errors}
+ ${NSD_CreateCheckbox} ${OPTIONS_ITEM_EDGE_DU} 184u ${OPTIONS_ITEM_WIDTH_DU} \
+ 12u "$(INSTALL_MAINT_SERVICE)"
+ Pop $CheckboxInstallMaintSvc
+ System::Call 'uxtheme::SetWindowTheme(i $CheckboxInstallMaintSvc, w " ", w " ")'
+ SetCtlColors $CheckboxInstallMaintSvc ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+ SendMessage $CheckboxInstallMaintSvc ${WM_SETFONT} $FontNormal 0
+ ${NSD_Check} $CheckboxInstallMaintSvc
+ ${EndIf}
+ ${EndIf}
+!endif
+
+ GetDlgItem $0 $HWNDPARENT 1 ; Install button
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(UPGRADE_BUTTON)"
+ ${Else}
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(INSTALL_BUTTON)"
+ ${EndIf}
+ ${NSD_SetFocus} $0
+
+ GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(CANCEL_BUTTON)"
+
+ GetDlgItem $0 $HWNDPARENT 3 ; Back button used for Options
+ EnableWindow $0 0
+ ShowWindow $0 ${SW_HIDE}
+
+ ; If the option button was not clicked display the reason for what needs to be
+ ; resolved to continue the installation.
+ ${If} "$WasOptionsButtonClicked" != "1"
+ ${If} "$CanWriteToInstallDir" == "false"
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_WRITE_ACCESS)"
+ ${ElseIf} "$HasRequiredSpaceAvailable" == "false"
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_DISK_SPACE)"
+ ${EndIf}
+ ${EndIf}
+
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $StartOptionsPhaseTickCount
+
+ LockWindow off
+ nsDialogs::Show
+FunctionEnd
+
+Function leaveOptions
+ LockWindow on
+
+ ${GetRoot} "$INSTDIR" $0
+ ${GetLongPath} "$INSTDIR" $INSTDIR
+ ${GetLongPath} "$0" $0
+ ${If} "$INSTDIR" == "$0"
+ LockWindow off
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_ROOT_INSTALL)"
+ Abort ; Stay on the page
+ ${EndIf}
+
+ Call CanWrite
+ ${If} "$CanWriteToInstallDir" == "false"
+ LockWindow off
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_WRITE_ACCESS)"
+ Abort ; Stay on the page
+ ${EndIf}
+
+ Call CheckSpace
+ ${If} "$HasRequiredSpaceAvailable" == "false"
+ LockWindow off
+ MessageBox MB_OK|MB_ICONEXCLAMATION "$(WARN_DISK_SPACE)"
+ Abort ; Stay on the page
+ ${EndIf}
+
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $0
+ ${GetSecondsElapsed} "$StartOptionsPhaseTickCount" "$0" $OptionsPhaseSeconds
+ ; It is possible for this value to be 0 if the user clicks fast enough so
+ ; increment the value by 1 if it is 0.
+ ${If} $OptionsPhaseSeconds == 0
+ IntOp $OptionsPhaseSeconds $OptionsPhaseSeconds + 1
+ ${EndIf}
+
+ ${NSD_GetState} $CheckboxShortcutOnBar $CheckboxShortcutOnBar
+ ${NSD_GetState} $CheckboxShortcutInStartMenu $CheckboxShortcutInStartMenu
+ ${NSD_GetState} $CheckboxShortcutOnDesktop $CheckboxShortcutOnDesktop
+ ${NSD_GetState} $CheckboxSendPing $CheckboxSendPing
+!ifdef MOZ_MAINTENANCE_SERVICE
+ ${NSD_GetState} $CheckboxInstallMaintSvc $CheckboxInstallMaintSvc
+!endif
+
+FunctionEnd
+
+Function createInstall
+ nsDialogs::Create /NOUNLOAD 1018
+ Pop $Dialog
+ ; Since the text color for controls is set in this Dialog the foreground and
+ ; background colors of the Dialog must also be hardcoded.
+ SetCtlColors $Dialog ${COMMON_TEXT_COLOR_NORMAL} ${COMMON_BKGRD_COLOR}
+
+ ${NSD_CreateLabel} 0 0 49u 64u ""
+ Pop $0
+ ${GetDlgItemWidthHeight} $0 $1 $2
+ System::Call 'user32::DestroyWindow(i r0)'
+
+ ${NSD_CreateLabel} 0 0 11u 16u ""
+ Pop $0
+ ${GetDlgItemWidthHeight} $0 $3 $4
+ System::Call 'user32::DestroyWindow(i r0)'
+
+ FindWindow $7 "#32770" "" $HWNDPARENT
+ ${GetDlgItemWidthHeight} $7 $8 $9
+
+ ; Allow a maximum text width of half of the Dialog's width
+ IntOp $R0 $8 / 2
+
+ ${GetTextWidthHeight} "${INSTALL_BLURB1}" $FontBlurb $R0 $5 $6
+ IntOp $R1 $1 + $3
+ IntOp $R1 $R1 + $5
+ IntOp $R1 $8 - $R1
+ IntOp $R1 $R1 / 2
+ ${NSD_CreateBitmap} $R1 ${INSTALL_BLURB_TOP_DU} 49u 64u ""
+ Pop $BitmapBlurb1
+ ${SetStretchedTransparentImage} $BitmapBlurb1 $PLUGINSDIR\clock.bmp $HwndBitmapBlurb1
+ IntOp $R1 $R1 + $1
+ IntOp $R1 $R1 + $3
+ ${NSD_CreateLabel} $R1 ${INSTALL_BLURB_TOP_DU} $5 $6 "${INSTALL_BLURB1}"
+ Pop $LabelBlurb1
+ SendMessage $LabelBlurb1 ${WM_SETFONT} $FontBlurb 0
+ SetCtlColors $LabelBlurb1 ${INSTALL_BLURB_TEXT_COLOR} transparent
+
+ ${GetTextWidthHeight} "${INSTALL_BLURB2}" $FontBlurb $R0 $5 $6
+ IntOp $R1 $1 + $3
+ IntOp $R1 $R1 + $5
+ IntOp $R1 $8 - $R1
+ IntOp $R1 $R1 / 2
+ ${NSD_CreateBitmap} $R1 ${INSTALL_BLURB_TOP_DU} 49u 64u ""
+ Pop $BitmapBlurb2
+ ${SetStretchedTransparentImage} $BitmapBlurb2 $PLUGINSDIR\particles.bmp $HwndBitmapBlurb2
+ IntOp $R1 $R1 + $1
+ IntOp $R1 $R1 + $3
+ ${NSD_CreateLabel} $R1 ${INSTALL_BLURB_TOP_DU} $5 $6 "${INSTALL_BLURB2}"
+ Pop $LabelBlurb2
+ SendMessage $LabelBlurb2 ${WM_SETFONT} $FontBlurb 0
+ SetCtlColors $LabelBlurb2 ${INSTALL_BLURB_TEXT_COLOR} transparent
+ ShowWindow $BitmapBlurb2 ${SW_HIDE}
+ ShowWindow $LabelBlurb2 ${SW_HIDE}
+
+ ${GetTextWidthHeight} "${INSTALL_BLURB3}" $FontBlurb $R0 $5 $6
+ IntOp $R1 $1 + $3
+ IntOp $R1 $R1 + $5
+ IntOp $R1 $8 - $R1
+ IntOp $R1 $R1 / 2
+ ${NSD_CreateBitmap} $R1 ${INSTALL_BLURB_TOP_DU} 49u 64u ""
+ Pop $BitmapBlurb3
+ ${SetStretchedTransparentImage} $BitmapBlurb3 $PLUGINSDIR\pencil.bmp $HWndBitmapBlurb3
+ IntOp $R1 $R1 + $1
+ IntOp $R1 $R1 + $3
+ ${NSD_CreateLabel} $R1 ${INSTALL_BLURB_TOP_DU} $5 $6 "${INSTALL_BLURB3}"
+ Pop $LabelBlurb3
+ SendMessage $LabelBlurb3 ${WM_SETFONT} $FontBlurb 0
+ SetCtlColors $LabelBlurb3 ${INSTALL_BLURB_TEXT_COLOR} transparent
+ ShowWindow $BitmapBlurb3 ${SW_HIDE}
+ ShowWindow $LabelBlurb3 ${SW_HIDE}
+
+ ${NSD_CreateProgressBar} 103u 166u 241u 9u ""
+ Pop $Progressbar
+ ${NSD_AddStyle} $Progressbar ${PBS_MARQUEE}
+ SendMessage $Progressbar ${PBM_SETMARQUEE} 1 \
+ $ProgressbarMarqueeIntervalMS ; start=1|stop=0 interval(ms)=+N
+
+ ${NSD_CreateLabelCenter} 103u 180u 241u 20u "$(DOWNLOADING_LABEL)"
+ Pop $LabelDownloading
+ SendMessage $LabelDownloading ${WM_SETFONT} $FontNormal 0
+ SetCtlColors $LabelDownloading ${INSTALL_PROGRESS_TEXT_COLOR_NORMAL} transparent
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ ${NSD_CreateLabelCenter} 103u 180u 241u 20u "$(UPGRADING_LABEL)"
+ ${Else}
+ ${NSD_CreateLabelCenter} 103u 180u 241u 20u "$(INSTALLING_LABEL)"
+ ${EndIf}
+ Pop $LabelInstalling
+ SendMessage $LabelInstalling ${WM_SETFONT} $FontNormal 0
+ SetCtlColors $LabelInstalling ${INSTALL_PROGRESS_TEXT_COLOR_NORMAL} transparent
+ ShowWindow $LabelInstalling ${SW_HIDE}
+
+ ${NSD_CreateBitmap} ${APPNAME_BMP_EDGE_DU} ${APPNAME_BMP_TOP_DU} \
+ ${APPNAME_BMP_WIDTH_DU} ${APPNAME_BMP_HEIGHT_DU} ""
+ Pop $2
+ ${SetStretchedTransparentImage} $2 $PLUGINSDIR\appname.bmp $0
+
+ GetDlgItem $0 $HWNDPARENT 1 ; Install button
+ EnableWindow $0 0
+ ShowWindow $0 ${SW_HIDE}
+
+ GetDlgItem $0 $HWNDPARENT 3 ; Back button used for Options
+ EnableWindow $0 0
+ ShowWindow $0 ${SW_HIDE}
+
+ GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(CANCEL_BUTTON)"
+ ; Focus the Cancel button otherwise it isn't possible to tab to it since it is
+ ; the only control that can be tabbed to.
+ ${NSD_SetFocus} $0
+ ; Kill the Cancel button's focus so pressing enter won't cancel the install.
+ SendMessage $0 ${WM_KILLFOCUS} 0 0
+
+ ${If} "$CanSetAsDefault" == "true"
+ GetDlgItem $0 $HWNDPARENT 10 ; Default browser checkbox
+ SendMessage $0 ${BM_GETCHECK} 0 0 $CheckboxSetAsDefault
+ EnableWindow $0 0
+ ShowWindow $0 ${SW_HIDE}
+ ${EndIf}
+
+ GetDlgItem $0 $HWNDPARENT 11
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(ONE_MOMENT_UPGRADE)"
+ ${Else}
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(ONE_MOMENT_INSTALL)"
+ ${EndIf}
+ SendMessage $0 ${WM_SETFONT} $FontNormal 0
+ SetCtlColors $0 ${FOOTER_CONTROL_TEXT_COLOR_FADED} ${FOOTER_BKGRD_COLOR}
+ ShowWindow $0 ${SW_SHOW}
+
+ ; Set $DownloadReset to true so the first download tick count is measured.
+ StrCpy $DownloadReset "true"
+ StrCpy $IsDownloadFinished "false"
+ StrCpy $DownloadRetryCount "0"
+ StrCpy $DownloadedBytes "0"
+ StrCpy $StartLastDownloadTickCount ""
+ StrCpy $EndDownloadPhaseTickCount ""
+ StrCpy $DownloadFirstTransferSeconds ""
+ StrCpy $ExitCode "${ERR_DOWNLOAD_CANCEL}"
+ StrCpy $OpenedDownloadPage "0"
+
+ ClearErrors
+ ReadINIStr $ExistingVersion "$INSTDIR\application.ini" "App" "Version"
+ ${If} ${Errors}
+ StrCpy $ExistingVersion "0"
+ ${EndIf}
+
+ ClearErrors
+ ReadINIStr $ExistingBuildID "$INSTDIR\application.ini" "App" "BuildID"
+ ${If} ${Errors}
+ StrCpy $ExistingBuildID "0"
+ ${EndIf}
+
+ ${If} ${FileExists} "$LOCALAPPDATA\Mozilla\Firefox"
+ StrCpy $ExistingProfile "1"
+ ${Else}
+ StrCpy $ExistingProfile "0"
+ ${EndIf}
+
+ StrCpy $DownloadServerIP ""
+
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $StartDownloadPhaseTickCount
+
+ ${If} ${FileExists} "$INSTDIR\uninstall\uninstall.log"
+ StrCpy $InstallTotalSteps ${InstallPaveOverTotalSteps}
+ ${Else}
+ StrCpy $InstallTotalSteps ${InstallCleanTotalSteps}
+ ${EndIf}
+
+ ${ITBL3Create}
+ ${ITBL3SetProgressState} "${TBPF_INDETERMINATE}"
+
+ ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+
+ LockWindow off
+ nsDialogs::Show
+
+ ${NSD_FreeImage} $0
+ ${NSD_FreeImage} $HwndBitmapBlurb1
+ ${NSD_FreeImage} $HwndBitmapBlurb2
+ ${NSD_FreeImage} $HWndBitmapBlurb3
+FunctionEnd
+
+Function StartDownload
+ ${NSD_KillTimer} StartDownload
+ InetBgDL::Get "${URLStubDownload}${URLStubDownloadAppend}" "$PLUGINSDIR\download.exe" \
+ /CONNECTTIMEOUT 120 /RECEIVETIMEOUT 120 /END
+ StrCpy $4 ""
+ ${NSD_CreateTimer} OnDownload ${DownloadIntervalMS}
+ ${If} ${FileExists} "$INSTDIR\${TO_BE_DELETED}"
+ RmDir /r "$INSTDIR\${TO_BE_DELETED}"
+ ${EndIf}
+FunctionEnd
+
+Function SetProgressBars
+ SendMessage $Progressbar ${PBM_SETPOS} $ProgressCompleted 0
+ ${ITBL3SetProgressValue} "$ProgressCompleted" "$ProgressTotal"
+FunctionEnd
+
+Function RemoveFileProgressCallback
+ IntOp $InstallCounterStep $InstallCounterStep + 2
+ System::Int64Op $ProgressCompleted + $InstallStepSize
+ Pop $ProgressCompleted
+ Call SetProgressBars
+ System::Int64Op $ProgressCompleted + $InstallStepSize
+ Pop $ProgressCompleted
+ Call SetProgressBars
+FunctionEnd
+
+Function OnDownload
+ InetBgDL::GetStats
+ # $0 = HTTP status code, 0=Completed
+ # $1 = Completed files
+ # $2 = Remaining files
+ # $3 = Number of downloaded bytes for the current file
+ # $4 = Size of current file (Empty string if the size is unknown)
+ # /RESET must be used if status $0 > 299 (e.g. failure)
+ # When status is $0 =< 299 it is handled by InetBgDL
+ StrCpy $DownloadServerIP "$5"
+ ${If} $0 > 299
+ ${NSD_KillTimer} OnDownload
+ IntOp $DownloadRetryCount $DownloadRetryCount + 1
+ ${If} "$DownloadReset" != "true"
+ StrCpy $DownloadedBytes "0"
+ ${NSD_AddStyle} $Progressbar ${PBS_MARQUEE}
+ SendMessage $Progressbar ${PBM_SETMARQUEE} 1 \
+ $ProgressbarMarqueeIntervalMS ; start=1|stop=0 interval(ms)=+N
+ ${ITBL3SetProgressState} "${TBPF_INDETERMINATE}"
+ ${EndIf}
+ InetBgDL::Get /RESET /END
+ StrCpy $DownloadSizeBytes ""
+ StrCpy $DownloadReset "true"
+
+ ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+ StrCpy $ExitCode "${ERR_DOWNLOAD_TOO_MANY_RETRIES}"
+ ; Use a timer so the UI has a chance to update
+ ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+ ${Else}
+ ${NSD_CreateTimer} StartDownload ${DownloadRetryIntervalMS}
+ ${EndIf}
+ Return
+ ${EndIf}
+
+ ${If} "$DownloadReset" == "true"
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $StartLastDownloadTickCount
+ StrCpy $DownloadReset "false"
+ ; The seconds elapsed from the start of the download phase until the first
+ ; bytes are received are only recorded for the first request so it is
+ ; possible to determine connection issues for the first request.
+ ${If} "$DownloadFirstTransferSeconds" == ""
+ ; Get the seconds elapsed from the start of the download phase until the
+ ; first bytes are received.
+ ${GetSecondsElapsed} "$StartDownloadPhaseTickCount" "$StartLastDownloadTickCount" $DownloadFirstTransferSeconds
+ ${EndIf}
+ ${EndIf}
+
+ ${If} "$DownloadSizeBytes" == ""
+ ${AndIf} "$4" != ""
+ ; Handle the case where the size of the file to be downloaded is less than
+ ; the minimum expected size or greater than the maximum expected size at the
+ ; beginning of the download.
+ ${If} $4 < ${DownloadMinSizeBytes}
+ ${OrIf} $4 > ${DownloadMaxSizeBytes}
+ ${NSD_KillTimer} OnDownload
+ InetBgDL::Get /RESET /END
+ StrCpy $DownloadReset "true"
+
+ ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+ ; Use a timer so the UI has a chance to update
+ ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+ ${Else}
+ ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+ ${EndIf}
+ Return
+ ${EndIf}
+
+ StrCpy $DownloadSizeBytes "$4"
+ System::Int64Op $4 / 2
+ Pop $HalfOfDownload
+ System::Int64Op $HalfOfDownload / $InstallTotalSteps
+ Pop $InstallStepSize
+ SendMessage $Progressbar ${PBM_SETMARQUEE} 0 0 ; start=1|stop=0 interval(ms)=+N
+ ${RemoveStyle} $Progressbar ${PBS_MARQUEE}
+ System::Int64Op $HalfOfDownload + $DownloadSizeBytes
+ Pop $ProgressTotal
+ StrCpy $ProgressCompleted 0
+ SendMessage $Progressbar ${PBM_SETRANGE32} $ProgressCompleted $ProgressTotal
+ ${EndIf}
+
+ ; Don't update the status until after the download starts
+ ${If} $2 != 0
+ ${AndIf} "$4" == ""
+ Return
+ ${EndIf}
+
+ ; Handle the case where the downloaded size is greater than the maximum
+ ; expected size during the download.
+ ${If} $DownloadedBytes > ${DownloadMaxSizeBytes}
+ InetBgDL::Get /RESET /END
+ StrCpy $DownloadReset "true"
+
+ ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+ ; Use a timer so the UI has a chance to update
+ ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+ ${Else}
+ ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+ ${EndIf}
+ Return
+ ${EndIf}
+
+ ${If} $IsDownloadFinished != "true"
+ ${If} $2 == 0
+ ${NSD_KillTimer} OnDownload
+ StrCpy $IsDownloadFinished "true"
+ ; The first step of the install progress bar is determined by the
+ ; InstallProgressFirstStep define and provides the user with immediate
+ ; feedback.
+ StrCpy $InstallCounterStep "${InstallProgressFirstStep}"
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $EndDownloadPhaseTickCount
+
+ StrCpy $DownloadedBytes "$DownloadSizeBytes"
+
+ ; When a download has finished handle the case where the downloaded size
+ ; is less than the minimum expected size or greater than the maximum
+ ; expected size during the download.
+ ${If} $DownloadedBytes < ${DownloadMinSizeBytes}
+ ${OrIf} $DownloadedBytes > ${DownloadMaxSizeBytes}
+ InetBgDL::Get /RESET /END
+ StrCpy $DownloadReset "true"
+
+ ${If} $DownloadRetryCount >= ${DownloadMaxRetries}
+ ; Use a timer so the UI has a chance to update
+ ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+ ${Else}
+ ${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
+ ${EndIf}
+ Return
+ ${EndIf}
+
+ LockWindow on
+ ; Update the progress bars first in the UI change so they take affect
+ ; before other UI changes.
+ StrCpy $ProgressCompleted "$DownloadSizeBytes"
+ Call SetProgressBars
+ System::Int64Op $InstallStepSize * ${InstallProgressFirstStep}
+ Pop $R9
+ System::Int64Op $ProgressCompleted + $R9
+ Pop $ProgressCompleted
+ Call SetProgressBars
+ ShowWindow $LabelDownloading ${SW_HIDE}
+ ShowWindow $LabelInstalling ${SW_SHOW}
+ ShowWindow $LabelBlurb2 ${SW_HIDE}
+ ShowWindow $BitmapBlurb2 ${SW_HIDE}
+ ShowWindow $LabelBlurb3 ${SW_SHOW}
+ ShowWindow $BitmapBlurb3 ${SW_SHOW}
+ ; Disable the Cancel button during the install
+ GetDlgItem $5 $HWNDPARENT 2
+ EnableWindow $5 0
+ LockWindow off
+
+ ; Open a handle to prevent modification of the full installer
+ StrCpy $R9 "${INVALID_HANDLE_VALUE}"
+ System::Call 'kernel32::CreateFileW(w "$PLUGINSDIR\download.exe", \
+ i ${GENERIC_READ}, \
+ i ${FILE_SHARE_READ}, i 0, \
+ i ${OPEN_EXISTING}, i 0, i 0) i .R9'
+ StrCpy $HandleDownload "$R9"
+
+ ${If} $HandleDownload == ${INVALID_HANDLE_VALUE}
+ StrCpy $ExitCode "${ERR_PREINSTALL_INVALID_HANDLE}"
+ StrCpy $0 "0"
+ StrCpy $1 "0"
+ ${Else}
+ CertCheck::VerifyCertTrust "$PLUGINSDIR\download.exe"
+ Pop $0
+ CertCheck::VerifyCertNameIssuer "$PLUGINSDIR\download.exe" \
+ "${CertNameDownload}" "${CertIssuerDownload}"
+ Pop $1
+ ${If} $0 == 0
+ ${AndIf} $1 == 0
+ StrCpy $ExitCode "${ERR_PREINSTALL_CERT_UNTRUSTED_AND_ATTRIBUTES}"
+ ${ElseIf} $0 == 0
+ StrCpy $ExitCode "${ERR_PREINSTALL_CERT_UNTRUSTED}"
+ ${ElseIf} $1 == 0
+ StrCpy $ExitCode "${ERR_PREINSTALL_CERT_ATTRIBUTES}"
+ ${EndIf}
+ ${EndIf}
+
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $EndPreInstallPhaseTickCount
+
+ ${If} $0 == 0
+ ${OrIf} $1 == 0
+ ; Use a timer so the UI has a chance to update
+ ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+ Return
+ ${EndIf}
+
+ ; Instead of extracting the files we use the downloaded installer to
+ ; install in case it needs to perform operations that the stub doesn't
+ ; know about.
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "InstallDirectoryPath" "$INSTDIR"
+ ; Don't create the QuickLaunch or Taskbar shortcut from the launched installer
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "QuickLaunchShortcut" "false"
+
+ ; Either avoid or force adding a taskbar pin based on the checkbox value:
+ ${If} $CheckboxShortcutOnBar == 0
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "TaskbarShortcut" "false"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "TaskbarShortcut" "true"
+ ${EndIf}
+
+ ${If} $CheckboxShortcutOnDesktop == 1
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "DesktopShortcut" "true"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "DesktopShortcut" "false"
+ ${EndIf}
+
+ ${If} $CheckboxShortcutInStartMenu == 1
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "StartMenuShortcuts" "true"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "StartMenuShortcuts" "false"
+ ${EndIf}
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+ ${If} $CheckboxInstallMaintSvc == 1
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "MaintenanceService" "true"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "MaintenanceService" "false"
+ ${EndIf}
+!else
+ WriteINIStr "$PLUGINSDIR\${CONFIG_INI}" "Install" "MaintenanceService" "false"
+!endif
+
+ ; Delete the taskbar shortcut history to ensure we do the right thing based on
+ ; the config file above.
+ ${GetShortcutsLogPath} $0
+ Delete "$0"
+
+ GetFunctionAddress $0 RemoveFileProgressCallback
+ ${RemovePrecompleteEntries} $0
+
+ ; Delete the install.log and let the full installer create it. When the
+ ; installer closes it we can detect that it has completed.
+ Delete "$INSTDIR\install.log"
+
+ ; Delete firefox.exe.moz-upgrade and firefox.exe.moz-delete if it exists
+ ; since it being present will require an OS restart for the full
+ ; installer.
+ Delete "$INSTDIR\${FileMainEXE}.moz-upgrade"
+ Delete "$INSTDIR\${FileMainEXE}.moz-delete"
+
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $EndPreInstallPhaseTickCount
+
+ Exec "$\"$PLUGINSDIR\download.exe$\" /INI=$PLUGINSDIR\${CONFIG_INI}"
+ ${NSD_CreateTimer} CheckInstall ${InstallIntervalMS}
+ ${Else}
+ ${If} $HalfOfDownload != "true"
+ ${AndIf} $3 > $HalfOfDownload
+ StrCpy $HalfOfDownload "true"
+ LockWindow on
+ ShowWindow $LabelBlurb1 ${SW_HIDE}
+ ShowWindow $BitmapBlurb1 ${SW_HIDE}
+ ShowWindow $LabelBlurb2 ${SW_SHOW}
+ ShowWindow $BitmapBlurb2 ${SW_SHOW}
+ LockWindow off
+ ${EndIf}
+ StrCpy $DownloadedBytes "$3"
+ StrCpy $ProgressCompleted "$DownloadedBytes"
+ Call SetProgressBars
+ ${EndIf}
+ ${EndIf}
+FunctionEnd
+
+Function OnPing
+ InetBgDL::GetStats
+ # $0 = HTTP status code, 0=Completed
+ # $1 = Completed files
+ # $2 = Remaining files
+ # $3 = Number of downloaded bytes for the current file
+ # $4 = Size of current file (Empty string if the size is unknown)
+ # /RESET must be used if status $0 > 299 (e.g. failure)
+ # When status is $0 =< 299 it is handled by InetBgDL
+ ${If} $2 == 0
+ ${OrIf} $0 > 299
+ ${NSD_KillTimer} OnPing
+ ${If} $0 > 299
+ InetBgDL::Get /RESET /END
+ ${EndIf}
+ ; The following will exit the installer
+ SetAutoClose true
+ StrCpy $R9 "2"
+ Call RelativeGotoPage
+ ${EndIf}
+FunctionEnd
+
+Function CheckInstall
+ IntOp $InstallCounterStep $InstallCounterStep + 1
+ ${If} $InstallCounterStep >= $InstallTotalSteps
+ ${NSD_KillTimer} CheckInstall
+ ; Close the handle that prevents modification of the full installer
+ System::Call 'kernel32::CloseHandle(i $HandleDownload)'
+ StrCpy $ExitCode "${ERR_INSTALL_TIMEOUT}"
+ ; Use a timer so the UI has a chance to update
+ ${NSD_CreateTimer} DisplayDownloadError ${InstallIntervalMS}
+ Return
+ ${EndIf}
+
+ System::Int64Op $ProgressCompleted + $InstallStepSize
+ Pop $ProgressCompleted
+ Call SetProgressBars
+
+ ${If} ${FileExists} "$INSTDIR\install.log"
+ Delete "$INSTDIR\install.tmp"
+ CopyFiles /SILENT "$INSTDIR\install.log" "$INSTDIR\install.tmp"
+
+ ; The unfocus and refocus that happens approximately here is caused by the
+ ; installer calling SHChangeNotify to refresh the shortcut icons.
+
+ ; When the full installer completes the installation the install.log will no
+ ; longer be in use.
+ ClearErrors
+ Delete "$INSTDIR\install.log"
+ ${Unless} ${Errors}
+ ${NSD_KillTimer} CheckInstall
+ ; Close the handle that prevents modification of the full installer
+ System::Call 'kernel32::CloseHandle(i $HandleDownload)'
+ Rename "$INSTDIR\install.tmp" "$INSTDIR\install.log"
+ Delete "$PLUGINSDIR\download.exe"
+ Delete "$PLUGINSDIR\${CONFIG_INI}"
+ System::Call "kernel32::GetTickCount()l .s"
+ Pop $EndInstallPhaseTickCount
+ System::Int64Op $InstallStepSize * ${InstallProgressFinishStep}
+ Pop $InstallStepSize
+ ${NSD_CreateTimer} FinishInstall ${InstallIntervalMS}
+ ${EndUnless}
+ ${EndIf}
+FunctionEnd
+
+Function FinishInstall
+ ; The full installer has completed but the progress bar still needs to finish
+ ; so increase the size of the step.
+ IntOp $InstallCounterStep $InstallCounterStep + ${InstallProgressFinishStep}
+ ${If} $InstallTotalSteps < $InstallCounterStep
+ StrCpy $InstallCounterStep "$InstallTotalSteps"
+ ${EndIf}
+
+ ${If} $InstallTotalSteps != $InstallCounterStep
+ System::Int64Op $ProgressCompleted + $InstallStepSize
+ Pop $ProgressCompleted
+ Call SetProgressBars
+ Return
+ ${EndIf}
+
+ ${NSD_KillTimer} FinishInstall
+
+ StrCpy $ProgressCompleted "$ProgressTotal"
+ Call SetProgressBars
+
+ ${If} "$CheckboxSetAsDefault" == "1"
+ ; NB: this code is duplicated in installer.nsi. Please keep in sync.
+ ; For data migration in the app, we want to know what the default browser
+ ; value was before we changed it. To do so, we read it here and store it
+ ; in our own registry key.
+ StrCpy $0 ""
+ ${If} ${AtLeastWinVista}
+ AppAssocReg::QueryCurrentDefault "http" "protocol" "effective"
+ Pop $1
+ ; If the method hasn't failed, $1 will contain the progid. Check:
+ ${If} "$1" != "method failed"
+ ${AndIf} "$1" != "method not available"
+ ; Read the actual command from the progid
+ ReadRegStr $0 HKCR "$1\shell\open\command" ""
+ ${EndIf}
+ ${EndIf}
+ ; If using the App Association Registry didn't happen or failed, fall back
+ ; to the effective http default:
+ ${If} "$0" == ""
+ ReadRegStr $0 HKCR "http\shell\open\command" ""
+ ${EndIf}
+ ; If we have something other than empty string now, write the value.
+ ${If} "$0" != ""
+ ClearErrors
+ WriteRegStr HKCU "Software\Mozilla\Firefox" "OldDefaultBrowserCommand" "$0"
+ ${EndIf}
+
+ ${GetParameters} $0
+ ClearErrors
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors} ; Not elevated
+ Call ExecSetAsDefaultAppUser
+ ${Else} ; Elevated - execute the function in the unelevated process
+ GetFunctionAddress $0 ExecSetAsDefaultAppUser
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ ${EndIf}
+
+ ${If} $CheckboxShortcutOnBar == 1
+ ${If} ${AtMostWinVista}
+ ClearErrors
+ ${GetParameters} $0
+ ClearErrors
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors}
+ Call AddQuickLaunchShortcut
+ ${Else}
+ GetFunctionAddress $0 AddQuickLaunchShortcut
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-upgrade"
+ Delete "$INSTDIR\${FileMainEXE}"
+ Rename "$INSTDIR\${FileMainEXE}.moz-upgrade" "$INSTDIR\${FileMainEXE}"
+ ${EndIf}
+
+ StrCpy $ExitCode "${ERR_SUCCESS}"
+
+ StrCpy $InstallCounterStep 0
+ ${NSD_CreateTimer} FinishProgressBar ${InstallIntervalMS}
+FunctionEnd
+
+Function FinishProgressBar
+ IntOp $InstallCounterStep $InstallCounterStep + 1
+
+ ${If} $InstallCounterStep < 10
+ Return
+ ${EndIf}
+
+ ${NSD_KillTimer} FinishProgressBar
+
+ Call CopyPostSigningData
+ Call LaunchApp
+ Call SendPing
+FunctionEnd
+
+Function OnBack
+ StrCpy $WasOptionsButtonClicked "1"
+ StrCpy $R9 "1" ; Goto the next page
+ Call RelativeGotoPage
+ ; The call to Abort prevents NSIS from trying to move to the previous or the
+ ; next page.
+ Abort
+FunctionEnd
+
+Function RelativeGotoPage
+ IntCmp $R9 0 0 Move Move
+ StrCmp $R9 "X" 0 Move
+ StrCpy $R9 "120"
+
+ Move:
+ SendMessage $HWNDPARENT "0x408" "$R9" ""
+FunctionEnd
+
+Function UpdateFreeSpaceLabel
+ ; Only update when $ExistingTopDir isn't set
+ ${If} "$ExistingTopDir" != ""
+ StrLen $5 "$ExistingTopDir"
+ StrLen $6 "$INSTDIR"
+ ${If} $5 <= $6
+ StrCpy $7 "$INSTDIR" $5
+ ${If} "$7" == "$ExistingTopDir"
+ Return
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ Call CheckSpace
+
+ StrCpy $0 "$SpaceAvailableBytes"
+
+ StrCpy $1 "$(BYTE)"
+
+ ${If} $0 > 1024
+ ${OrIf} $0 < 0
+ System::Int64Op $0 / 1024
+ Pop $0
+ StrCpy $1 "$(KILO)$(BYTE)"
+ ${If} $0 > 1024
+ ${OrIf} $0 < 0
+ System::Int64Op $0 / 1024
+ Pop $0
+ StrCpy $1 "$(MEGA)$(BYTE)"
+ ${If} $0 > 1024
+ ${OrIf} $0 < 0
+ System::Int64Op $0 / 1024
+ Pop $0
+ StrCpy $1 "$(GIGA)$(BYTE)"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ SendMessage $LabelFreeSpace ${WM_SETTEXT} 0 "STR:$0 $1"
+FunctionEnd
+
+Function OnChange_DirRequest
+ Pop $0
+ System::Call 'user32::GetWindowTextW(i $DirRequest, w .r0, i ${NSIS_MAX_STRLEN})'
+ StrCpy $1 "$0" 1 ; the first character
+ ${If} "$1" == "$\""
+ StrCpy $1 "$0" "" -1 ; the last character
+ ${If} "$1" == "$\""
+ StrCpy $0 "$0" "" 1 ; all but the first character
+ StrCpy $0 "$0" -1 ; all but the last character
+ ${EndIf}
+ ${EndIf}
+
+ StrCpy $INSTDIR "$0"
+ Call UpdateFreeSpaceLabel
+
+ GetDlgItem $0 $HWNDPARENT 1 ; Install button
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(UPGRADE_BUTTON)"
+ ${Else}
+ SendMessage $0 ${WM_SETTEXT} 0 "STR:$(INSTALL_BUTTON)"
+ ${EndIf}
+FunctionEnd
+
+Function OnClick_ButtonBrowse
+ StrCpy $0 "$INSTDIR"
+ nsDialogs::SelectFolderDialog /NOUNLOAD "$(SELECT_FOLDER_TEXT)" $0
+ Pop $0
+ ${If} $0 == "error" ; returns 'error' if 'cancel' was pressed?
+ Return
+ ${EndIf}
+
+ ${If} $0 != ""
+ StrCpy $INSTDIR "$0"
+ System::Call 'user32::SetWindowTextW(i $DirRequest, w "$INSTDIR")'
+ ${EndIf}
+FunctionEnd
+
+Function CheckSpace
+ ${If} "$ExistingTopDir" != ""
+ StrLen $0 "$ExistingTopDir"
+ StrLen $1 "$INSTDIR"
+ ${If} $0 <= $1
+ StrCpy $2 "$INSTDIR" $3
+ ${If} "$2" == "$ExistingTopDir"
+ Return
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ StrCpy $ExistingTopDir "$INSTDIR"
+ ${DoUntil} ${FileExists} "$ExistingTopDir"
+ ${GetParent} "$ExistingTopDir" $ExistingTopDir
+ ${If} "$ExistingTopDir" == ""
+ StrCpy $SpaceAvailableBytes "0"
+ StrCpy $HasRequiredSpaceAvailable "false"
+ Return
+ ${EndIf}
+ ${Loop}
+
+ ${GetLongPath} "$ExistingTopDir" $ExistingTopDir
+
+ ; GetDiskFreeSpaceExW requires a backslash.
+ StrCpy $0 "$ExistingTopDir" "" -1 ; the last character
+ ${If} "$0" != "\"
+ StrCpy $0 "\"
+ ${Else}
+ StrCpy $0 ""
+ ${EndIf}
+
+ System::Call 'kernel32::GetDiskFreeSpaceExW(w, *l, *l, *l) i("$ExistingTopDir$0", .r1, .r2, .r3) .'
+ StrCpy $SpaceAvailableBytes "$1"
+
+ System::Int64Op $SpaceAvailableBytes / 1048576
+ Pop $1
+ System::Int64Op $1 > ${APPROXIMATE_REQUIRED_SPACE_MB}
+ Pop $1
+ ${If} $1 == 1
+ StrCpy $HasRequiredSpaceAvailable "true"
+ ${Else}
+ StrCpy $HasRequiredSpaceAvailable "false"
+ ${EndIf}
+FunctionEnd
+
+Function CanWrite
+ StrCpy $CanWriteToInstallDir "false"
+
+ StrCpy $0 "$INSTDIR"
+ ; Use the existing directory when it exists
+ ${Unless} ${FileExists} "$INSTDIR"
+ ; Get the topmost directory that exists for new installs
+ ${DoUntil} ${FileExists} "$0"
+ ${GetParent} "$0" $0
+ ${If} "$0" == ""
+ Return
+ ${EndIf}
+ ${Loop}
+ ${EndUnless}
+
+ GetTempFileName $2 "$0"
+ Delete $2
+ CreateDirectory "$2"
+ ${If} ${FileExists} "$2"
+ ${If} ${FileExists} "$INSTDIR"
+ GetTempFileName $3 "$INSTDIR"
+ ${Else}
+ GetTempFileName $3 "$2"
+ ${EndIf}
+ ${If} ${FileExists} "$3"
+ Delete "$3"
+ StrCpy $CanWriteToInstallDir "true"
+ ${EndIf}
+ RmDir "$2"
+ ${EndIf}
+FunctionEnd
+
+Function AddQuickLaunchShortcut
+ CreateShortCut "$QUICKLAUNCH\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$QUICKLAUNCH\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${EndIf}
+FunctionEnd
+
+Function ExecSetAsDefaultAppUser
+ ; Using the helper.exe lessens the stub installer size.
+ ; This could ask for elevatation when the user doesn't install as admin.
+ Exec "$\"$INSTDIR\uninstall\helper.exe$\" /SetAsDefaultAppUser"
+FunctionEnd
+
+Function LaunchApp
+!ifndef DEV_EDITION
+ FindWindow $0 "${WindowClass}"
+ ${If} $0 <> 0 ; integer comparison
+ StrCpy $FirefoxLaunchCode "1"
+ MessageBox MB_OK|MB_ICONQUESTION "$(WARN_MANUALLY_CLOSE_APP_LAUNCH)"
+ Return
+ ${EndIf}
+!endif
+
+ StrCpy $FirefoxLaunchCode "2"
+
+ ; Set the current working directory to the installation directory
+ SetOutPath "$INSTDIR"
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $1
+ ${If} ${Errors}
+ Exec "$\"$INSTDIR\${FileMainEXE}$\""
+ ${Else}
+ GetFunctionAddress $0 LaunchAppFromElevatedProcess
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+FunctionEnd
+
+Function LaunchAppFromElevatedProcess
+ ; Find the installation directory when launching using GetFunctionAddress
+ ; from an elevated installer since $INSTDIR will not be set in this installer
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${GetPathFromString} "$0" $0
+ ; Set the current working directory to the installation directory
+ ${GetParent} "$0" $1
+ SetOutPath "$1"
+ Exec "$\"$0$\""
+FunctionEnd
+
+Function CopyPostSigningData
+ ${LineRead} "$EXEDIR\postSigningData" "1" $PostSigningData
+ ${If} ${Errors}
+ ClearErrors
+ StrCpy $PostSigningData "0"
+ ${Else}
+ CreateDirectory "$LOCALAPPDATA\Mozilla\Firefox"
+ CopyFiles /SILENT "$EXEDIR\postSigningData" "$LOCALAPPDATA\Mozilla\Firefox"
+ ${Endif}
+FunctionEnd
+
+Function DisplayDownloadError
+ ${NSD_KillTimer} DisplayDownloadError
+ ; To better display the error state on the taskbar set the progress completed
+ ; value to the total value.
+ ${ITBL3SetProgressValue} "100" "100"
+ ${ITBL3SetProgressState} "${TBPF_ERROR}"
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$(ERROR_DOWNLOAD)" IDCANCEL +2 IDOK +1
+ StrCpy $OpenedDownloadPage "1" ; Already initialized to 0
+
+ ${If} "$OpenedDownloadPage" == "1"
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $1
+ ${If} ${Errors}
+ Call OpenManualDownloadURL
+ ${Else}
+ GetFunctionAddress $0 OpenManualDownloadURL
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ ${EndIf}
+
+ Call SendPing
+FunctionEnd
+
+Function OpenManualDownloadURL
+ ExecShell "open" "${URLManualDownload}${URLManualDownloadAppend}"
+FunctionEnd
+
+Section
+SectionEnd
diff --git a/browser/installer/windows/nsis/uninstaller.nsi b/browser/installer/windows/nsis/uninstaller.nsi
new file mode 100755
index 000000000..c97728b47
--- /dev/null
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -0,0 +1,627 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Required Plugins:
+# AppAssocReg http://nsis.sourceforge.net/Application_Association_Registration_plug-in
+# CityHash http://dxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash
+# ShellLink http://nsis.sourceforge.net/ShellLink_plug-in
+# UAC http://nsis.sourceforge.net/UAC_plug-in
+
+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
+!verbose 3
+
+; 7-Zip provides better compression than the lzma from NSIS so we add the files
+; uncompressed and use 7-Zip to create a SFX archive of it
+SetDatablockOptimize on
+SetCompress off
+CRCCheck on
+
+RequestExecutionLevel user
+
+; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
+; be removed after we require NSIS 3.0a2 or greater.
+!ifdef NSIS_PACKEDVERSION
+ Unicode true
+ ManifestSupportedOS all
+ ManifestDPIAware true
+!endif
+
+!addplugindir ./
+
+; On Vista and above attempt to elevate Standard Users in addition to users that
+; are a member of the Administrators group.
+!define NONADMIN_ELEVATE
+
+; prevents compiling of the reg write logging.
+!define NO_LOG
+
+!define MaintUninstallKey \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
+
+Var TmpVal
+Var MaintCertKey
+
+; Other included files may depend upon these includes!
+; The following includes are provided by NSIS.
+!include FileFunc.nsh
+!include LogicLib.nsh
+!include MUI.nsh
+!include WinMessages.nsh
+!include WinVer.nsh
+!include WordFunc.nsh
+
+!insertmacro GetSize
+!insertmacro StrFilter
+!insertmacro WordReplace
+
+!insertmacro un.GetParent
+
+; The following includes are custom.
+!include branding.nsi
+!include defines.nsi
+!include common.nsh
+!include locales.nsi
+
+; This is named BrandShortName helper because we use this for software update
+; post update cleanup.
+VIAddVersionKey "FileDescription" "${BrandShortName} Helper"
+VIAddVersionKey "OriginalFilename" "helper.exe"
+
+!insertmacro AddDisabledDDEHandlerValues
+!insertmacro CleanVirtualStore
+!insertmacro ElevateUAC
+!insertmacro GetLongPath
+!insertmacro GetPathFromString
+!insertmacro InitHashAppModelId
+!insertmacro IsHandlerForInstallDir
+!insertmacro IsPinnedToTaskBar
+!insertmacro IsUserAdmin
+!insertmacro LogDesktopShortcut
+!insertmacro LogQuickLaunchShortcut
+!insertmacro LogStartMenuShortcut
+!insertmacro PinnedToStartMenuLnkCount
+!insertmacro RegCleanAppHandler
+!insertmacro RegCleanMain
+!insertmacro RegCleanUninstall
+!insertmacro SetAppLSPCategories
+!insertmacro SetBrandNameVars
+!insertmacro UpdateShortcutAppModelIDs
+!insertmacro UnloadUAC
+!insertmacro WriteRegDWORD2
+!insertmacro WriteRegStr2
+
+!insertmacro un.ChangeMUIHeaderImage
+!insertmacro un.CheckForFilesInUse
+!insertmacro un.CleanUpdateDirectories
+!insertmacro un.CleanVirtualStore
+!insertmacro un.DeleteShortcuts
+!insertmacro un.GetLongPath
+!insertmacro un.GetSecondInstallPath
+!insertmacro un.InitHashAppModelId
+!insertmacro un.ManualCloseAppPrompt
+!insertmacro un.RegCleanAppHandler
+!insertmacro un.RegCleanFileHandler
+!insertmacro un.RegCleanMain
+!insertmacro un.RegCleanUninstall
+!insertmacro un.RegCleanProtocolHandler
+!insertmacro un.RemoveQuotesFromPath
+!insertmacro un.RemovePrecompleteEntries
+!insertmacro un.SetAppLSPCategories
+!insertmacro un.SetBrandNameVars
+
+!include shared.nsh
+
+; Helper macros for ui callbacks. Insert these after shared.nsh
+!insertmacro OnEndCommon
+!insertmacro UninstallOnInitCommon
+
+!insertmacro un.OnEndCommon
+!insertmacro un.UninstallUnOnInitCommon
+
+Name "${BrandFullName}"
+OutFile "helper.exe"
+!ifdef HAVE_64BIT_BUILD
+ InstallDir "$PROGRAMFILES64\${BrandFullName}\"
+!else
+ InstallDir "$PROGRAMFILES32\${BrandFullName}\"
+!endif
+ShowUnInstDetails nevershow
+
+################################################################################
+# Modern User Interface - MUI
+
+!define MUI_ABORTWARNING
+!define MUI_ICON setup.ico
+!define MUI_UNICON setup.ico
+!define MUI_WELCOMEPAGE_TITLE_3LINES
+!define MUI_HEADERIMAGE
+!define MUI_HEADERIMAGE_RIGHT
+!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
+
+; Use a right to left header image when the language is right to left
+!ifdef ${AB_CD}_rtl
+!define MUI_HEADERIMAGE_BITMAP_RTL wizHeaderRTL.bmp
+!else
+!define MUI_HEADERIMAGE_BITMAP wizHeader.bmp
+!endif
+
+/**
+ * Uninstall Pages
+ */
+; Welcome Page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE un.preWelcome
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.leaveWelcome
+!insertmacro MUI_UNPAGE_WELCOME
+
+; Custom Uninstall Confirm Page
+UninstPage custom un.preConfirm
+
+; Remove Files Page
+!insertmacro MUI_UNPAGE_INSTFILES
+
+; Finish Page
+
+!insertmacro MUI_UNPAGE_FINISH
+
+; Use the default dialog for IDD_VERIFY for a simple Banner
+ChangeUI IDD_VERIFY "${NSISDIR}\Contrib\UIs\default.exe"
+
+################################################################################
+# Helper Functions
+
+; This function is used to uninstall the maintenance service if the
+; application currently being uninstalled is the last application to use the
+; maintenance service.
+Function un.UninstallServiceIfNotUsed
+ ; $0 will store if a subkey exists
+ ; $1 will store the first subkey if it exists or an empty string if it doesn't
+ ; Backup the old values
+ Push $0
+ Push $1
+
+ ; The maintenance service always uses the 64-bit registry on x64 systems
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+
+ ; Figure out the number of subkeys
+ StrCpy $0 0
+ ${Do}
+ EnumRegKey $1 HKLM "Software\Mozilla\MaintenanceService" $0
+ ${If} "$1" == ""
+ ${ExitDo}
+ ${EndIf}
+ IntOp $0 $0 + 1
+ ${Loop}
+
+ ; Restore back the registry view
+ ${If} ${RunningX64}
+ SetRegView lastUsed
+ ${EndIf}
+ ${If} $0 == 0
+ ; Get the path of the maintenance service uninstaller
+ ReadRegStr $1 HKLM ${MaintUninstallKey} "UninstallString"
+
+ ; If the uninstall string does not exist, skip executing it
+ StrCmp $1 "" doneUninstall
+
+ ; $1 is already a quoted string pointing to the install path
+ ; so we're already protected against paths with spaces
+ nsExec::Exec "$1 /S"
+doneUninstall:
+ ${EndIf}
+
+ ; Restore the old value of $1 and $0
+ Pop $1
+ Pop $0
+FunctionEnd
+
+################################################################################
+# Install Sections
+; Empty section required for the installer to compile as an uninstaller
+Section ""
+SectionEnd
+
+################################################################################
+# Uninstall Sections
+
+Section "Uninstall"
+ SetDetailsPrint textonly
+ DetailPrint $(STATUS_UNINSTALL_MAIN)
+ SetDetailsPrint none
+
+ ; Delete the app exe to prevent launching the app while we are uninstalling.
+ ClearErrors
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ${If} ${Errors}
+ ; If the user closed the application it can take several seconds for it to
+ ; shut down completely. If the application is being used by another user we
+ ; can still delete the files when the system is restarted.
+ Sleep 5000
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ClearErrors
+ ${EndIf}
+
+ ; setup the application model id registration value
+ ${un.InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
+
+ SetShellVarContext current ; Set SHCTX to HKCU
+ ${un.RegCleanMain} "Software\Mozilla"
+ ${un.RegCleanUninstall}
+ ${un.DeleteShortcuts}
+
+ ; Unregister resources associated with Win7 taskbar jump lists.
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::UninstallJumpLists "$AppUserModelID"
+ ${EndIf}
+
+ ; Remove the updates directory for Vista and above
+ ${un.CleanUpdateDirectories} "Mozilla\Firefox" "Mozilla\updates"
+
+ ; Remove any app model id's stored in the registry for this install path
+ DeleteRegValue HKCU "Software\Mozilla\${AppName}\TaskBarIDs" "$INSTDIR"
+ DeleteRegValue HKLM "Software\Mozilla\${AppName}\TaskBarIDs" "$INSTDIR"
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ StrCpy $TmpVal "HKCU" ; used primarily for logging
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to HKLM
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $TmpVal "HKLM" ; used primarily for logging
+ ${un.RegCleanMain} "Software\Mozilla"
+ ${un.RegCleanUninstall}
+ ${un.DeleteShortcuts}
+ ${un.SetAppLSPCategories}
+ ${EndIf}
+
+ ${un.RegCleanAppHandler} "FirefoxURL"
+ ${un.RegCleanAppHandler} "FirefoxHTML"
+ ${un.RegCleanProtocolHandler} "ftp"
+ ${un.RegCleanProtocolHandler} "http"
+ ${un.RegCleanProtocolHandler} "https"
+
+ ClearErrors
+ ReadRegStr $R9 HKCR "FirefoxHTML" ""
+ ; Don't clean up the file handlers if the FirefoxHTML key still exists since
+ ; there should be a second installation that may be the default file handler
+ ${If} ${Errors}
+ ${un.RegCleanFileHandler} ".htm" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".html" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".shtml" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".xht" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".xhtml" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".oga" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".ogg" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".ogv" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".pdf" "FirefoxHTML"
+ ${un.RegCleanFileHandler} ".webm" "FirefoxHTML"
+ ${EndIf}
+
+ SetShellVarContext all ; Set SHCTX to HKLM
+ ${un.GetSecondInstallPath} "Software\Mozilla" $R9
+ ${If} $R9 == "false"
+ SetShellVarContext current ; Set SHCTX to HKCU
+ ${un.GetSecondInstallPath} "Software\Mozilla" $R9
+ ${EndIf}
+
+ StrCpy $0 "Software\Clients\StartMenuInternet\${FileMainEXE}\shell\open\command"
+ ReadRegStr $R1 HKLM "$0" ""
+ ${un.RemoveQuotesFromPath} "$R1" $R1
+ ${un.GetParent} "$R1" $R1
+
+ ; Only remove the StartMenuInternet key if it refers to this install location.
+ ; The StartMenuInternet registry key is independent of the default browser
+ ; settings. The XPInstall base un-installer always removes this key if it is
+ ; uninstalling the default browser and it will always replace the keys when
+ ; installing even if there is another install of Firefox that is set as the
+ ; default browser. Now the key is always updated on install but it is only
+ ; removed if it refers to this install location.
+ ${If} "$INSTDIR" == "$R1"
+ DeleteRegKey HKLM "Software\Clients\StartMenuInternet\${FileMainEXE}"
+ DeleteRegValue HKLM "Software\RegisteredApplications" "${AppRegName}"
+ ${EndIf}
+
+ ReadRegStr $R1 HKCU "$0" ""
+ ${un.RemoveQuotesFromPath} "$R1" $R1
+ ${un.GetParent} "$R1" $R1
+
+ ; Only remove the StartMenuInternet key if it refers to this install location.
+ ; The StartMenuInternet registry key is independent of the default browser
+ ; settings. The XPInstall base un-installer always removes this key if it is
+ ; uninstalling the default browser and it will always replace the keys when
+ ; installing even if there is another install of Firefox that is set as the
+ ; default browser. Now the key is always updated on install but it is only
+ ; removed if it refers to this install location.
+ ${If} "$INSTDIR" == "$R1"
+ DeleteRegKey HKCU "Software\Clients\StartMenuInternet\${FileMainEXE}"
+ DeleteRegValue HKCU "Software\RegisteredApplications" "${AppRegName}"
+ ${EndIf}
+
+ StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\App Paths\${FileMainEXE}"
+ ${If} $R9 == "false"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\${FileMainEXE}"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\plugin-container.exe"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ StrCpy $0 "Software\Classes\MIME\Database\Content Type\application/x-xpinstall;app=firefox"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ ${Else}
+ ReadRegStr $R1 HKLM "$0" ""
+ ${un.RemoveQuotesFromPath} "$R1" $R1
+ ${un.GetParent} "$R1" $R1
+ ${If} "$INSTDIR" == "$R1"
+ WriteRegStr HKLM "$0" "" "$R9"
+ ${un.GetParent} "$R9" $R1
+ WriteRegStr HKLM "$0" "Path" "$R1"
+ ${EndIf}
+ ${EndIf}
+
+ ; Remove directories and files we always control before parsing the uninstall
+ ; log so empty directories can be removed.
+ ${If} ${FileExists} "$INSTDIR\updates"
+ RmDir /r /REBOOTOK "$INSTDIR\updates"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\updated"
+ RmDir /r /REBOOTOK "$INSTDIR\updated"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\shortcuts"
+ RmDir /r /REBOOTOK "$INSTDIR\defaults\shortcuts"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\distribution"
+ RmDir /r /REBOOTOK "$INSTDIR\distribution"
+ ${EndIf}
+
+ ; Remove files that may be left behind by the application in the
+ ; VirtualStore directory.
+ ${un.CleanVirtualStore}
+
+ ; Only unregister the dll if the registration points to this installation
+ ReadRegStr $R1 HKCR "CLSID\{0D68D6D0-D93D-4D08-A30D-F00DD1F45B24}\InProcServer32" ""
+ ${If} "$INSTDIR\AccessibleMarshal.dll" == "$R1"
+ ${UnregisterDLL} "$INSTDIR\AccessibleMarshal.dll"
+ ${EndIf}
+
+ ${un.RemovePrecompleteEntries} "false"
+
+ ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
+ Delete /REBOOTOK "$INSTDIR\defaults\pref\channel-prefs.js"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\pref"
+ RmDir /REBOOTOK "$INSTDIR\defaults\pref"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults"
+ RmDir /REBOOTOK "$INSTDIR\defaults"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\uninstall"
+ ; Remove the uninstall directory that we control
+ RmDir /r /REBOOTOK "$INSTDIR\uninstall"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\install.log"
+ Delete /REBOOTOK "$INSTDIR\install.log"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\update-settings.ini"
+ Delete /REBOOTOK "$INSTDIR\update-settings.ini"
+ ${EndIf}
+
+ ; Explicitly remove empty webapprt dir in case it exists (bug 757978).
+ RmDir "$INSTDIR\webapprt\components"
+ RmDir "$INSTDIR\webapprt"
+
+ ; Remove the installation directory if it is empty
+ RmDir "$INSTDIR"
+
+ ; If firefox.exe was successfully deleted yet we still need to restart to
+ ; remove other files create a dummy firefox.exe.moz-delete to prevent the
+ ; installer from allowing an install without restart when it is required
+ ; to complete an uninstall.
+ ${If} ${RebootFlag}
+ ; Admin is required to delete files on reboot so only add the moz-delete if
+ ; the user is an admin. After calling UAC::IsAdmin $0 will equal 1 if the
+ ; user is an admin.
+ UAC::IsAdmin
+ ${If} "$0" == "1"
+ ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-delete"
+ FileOpen $0 "$INSTDIR\${FileMainEXE}.moz-delete" w
+ FileWrite $0 "Will be deleted on restart"
+ Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
+ FileClose $0
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+
+ ; Refresh desktop icons otherwise the start menu internet item won't be
+ ; removed and other ugly things will happen like recreation of the app's
+ ; clients registry key by the OS under some conditions.
+ System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i 0, i 0, i 0)"
+
+ ; Users who uninstall then reinstall expecting Firefox to use a clean profile
+ ; may be surprised during first-run. This key is checked during startup of Firefox and
+ ; subsequently deleted after checking. If the value is found during startup
+ ; the browser will offer to Reset Firefox. We use the UpdateChannel to match
+ ; uninstalls of Firefox-release with reinstalls of Firefox-release, for example.
+ WriteRegStr HKCU "Software\Mozilla\Firefox" "Uninstalled-${UpdateChannel}" "True"
+
+!ifdef MOZ_MAINTENANCE_SERVICE
+ ; Get the path the allowed cert is at and remove it
+ ; Keep this block of code last since it modfies the reg view
+ ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
+ Pop $MaintCertKey
+ ${If} $MaintCertKey != ""
+ ; Always use the 64bit registry for certs on 64bit systems.
+ ${If} ${RunningX64}
+ SetRegView 64
+ ${EndIf}
+ DeleteRegKey HKLM "$MaintCertKey"
+ ${If} ${RunningX64}
+ SetRegView lastused
+ ${EndIf}
+ ${EndIf}
+ Call un.UninstallServiceIfNotUsed
+!endif
+
+ ${un.IsFirewallSvcRunning}
+ Pop $0
+ ${If} "$0" == "true"
+ liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
+ ${EndIf}
+SectionEnd
+
+################################################################################
+# Language
+
+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
+!verbose push
+!verbose 3
+!include "overrideLocale.nsh"
+!include "customLocale.nsh"
+!verbose pop
+
+; Set this after the locale files to override it if it is in the locale. Using
+; " " for BrandingText will hide the "Nullsoft Install System..." branding.
+BrandingText " "
+
+################################################################################
+# Page pre, show, and leave functions
+
+Function un.preWelcome
+ ${If} ${FileExists} "$INSTDIR\distribution\modern-wizard.bmp"
+ Delete "$PLUGINSDIR\modern-wizard.bmp"
+ CopyFiles /SILENT "$INSTDIR\distribution\modern-wizard.bmp" "$PLUGINSDIR\modern-wizard.bmp"
+ ${EndIf}
+FunctionEnd
+
+Function un.leaveWelcome
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ Banner::show /NOUNLOAD "$(BANNER_CHECK_EXISTING)"
+
+ ; If the message window has been found previously give the app an additional
+ ; five seconds to close.
+ ${If} "$TmpVal" == "FoundMessageWindow"
+ Sleep 5000
+ ${EndIf}
+
+ ${PushFilesToCheck}
+
+ ${un.CheckForFilesInUse} $TmpVal
+
+ Banner::destroy
+
+ ; If there are files in use $TmpVal will be "true"
+ ${If} "$TmpVal" == "true"
+ ; If the message window is found the call to ManualCloseAppPrompt will
+ ; abort leaving the value of $TmpVal set to "FoundMessageWindow".
+ StrCpy $TmpVal "FoundMessageWindow"
+ ${un.ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_UNINSTALL)"
+ ; If the message window is not found set $TmpVal to "true" so the restart
+ ; required message is displayed.
+ StrCpy $TmpVal "true"
+ ${EndIf}
+ ${EndIf}
+FunctionEnd
+
+Function un.preConfirm
+ ${If} ${FileExists} "$INSTDIR\distribution\modern-header.bmp"
+ ${AndIf} $hHeaderBitmap == ""
+ Delete "$PLUGINSDIR\modern-header.bmp"
+ CopyFiles /SILENT "$INSTDIR\distribution\modern-header.bmp" "$PLUGINSDIR\modern-header.bmp"
+ ${un.ChangeMUIHeaderImage} "$PLUGINSDIR\modern-header.bmp"
+ ${EndIf}
+
+ ; Setup the unconfirm.ini file for the Custom Uninstall Confirm Page
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Settings" NumFields "3"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Text "$(UN_CONFIRM_UNINSTALLED_FROM)"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Bottom "15"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Type "text"
+ ; The contents of this control must be set as follows in the pre function
+ ; ${MUI_INSTALLOPTIONS_READ} $1 "unconfirm.ini" "Field 2" "HWND"
+ ; SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" State ""
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Top "17"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Bottom "30"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" flags "READONLY"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Type "label"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Text "$(UN_CONFIRM_CLICK)"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Top "130"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Bottom "150"
+
+ ${If} "$TmpVal" == "true"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Type "label"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Text "$(SUMMARY_REBOOT_REQUIRED_UNINSTALL)"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Top "35"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Bottom "45"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Settings" NumFields "4"
+ ${EndIf}
+
+ !insertmacro MUI_HEADER_TEXT "$(UN_CONFIRM_PAGE_TITLE)" "$(UN_CONFIRM_PAGE_SUBTITLE)"
+ ; The Summary custom page has a textbox that will automatically receive
+ ; focus. This sets the focus to the Install button instead.
+ !insertmacro MUI_INSTALLOPTIONS_INITDIALOG "unconfirm.ini"
+ GetDlgItem $0 $HWNDPARENT 1
+ System::Call "user32::SetFocus(i r0, i 0x0007, i,i)i"
+ ${MUI_INSTALLOPTIONS_READ} $1 "unconfirm.ini" "Field 2" "HWND"
+ SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ !insertmacro MUI_INSTALLOPTIONS_SHOW
+FunctionEnd
+
+################################################################################
+# Initialization Functions
+
+Function .onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ ; We need this set up for most of the helper.exe operations.
+ ${UninstallOnInitCommon}
+FunctionEnd
+
+Function un.onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ StrCpy $LANGUAGE 0
+
+ ${un.UninstallUnOnInitCommon}
+
+; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
+; removed after we require NSIS 3.0a2 or greater.
+!ifndef NSIS_PACKEDVERSION
+ ${If} ${AtLeastWinVista}
+ System::Call 'user32::SetProcessDPIAware()'
+ ${EndIf}
+!endif
+
+ !insertmacro InitInstallOptionsFile "unconfirm.ini"
+FunctionEnd
+
+Function .onGUIEnd
+ ${OnEndCommon}
+FunctionEnd
+
+Function un.onGUIEnd
+ ${un.OnEndCommon}
+FunctionEnd
diff --git a/browser/installer/windows/nsis/updater_append.ini b/browser/installer/windows/nsis/updater_append.ini
new file mode 100644
index 000000000..af7742c12
--- /dev/null
+++ b/browser/installer/windows/nsis/updater_append.ini
@@ -0,0 +1,12 @@
+
+; IMPORTANT: This file should always start with a newline in case a locale
+; provided updater.ini does not end with a newline.
+; Application to launch after an update has been successfully applied. This
+; must be in the same directory or a sub-directory of the directory of the
+; application executable that initiated the software update.
+[PostUpdateWin]
+; ExeRelPath is the path to the PostUpdateWin executable relative to the
+; application executable.
+ExeRelPath=uninstall\helper.exe
+; ExeArg is the argument to pass to the PostUpdateWin exe
+ExeArg=/PostUpdate
diff --git a/browser/installer/windows/stub.tag b/browser/installer/windows/stub.tag
new file mode 100644
index 000000000..f32bef36e
--- /dev/null
+++ b/browser/installer/windows/stub.tag
@@ -0,0 +1,4 @@
+;!@Install@!UTF-8!
+Title="Mozilla Firefox"
+RunProgram="setup-stub.exe"
+;!@InstallEnd@! \ No newline at end of file
diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in
new file mode 100644
index 000000000..6b0455bf0
--- /dev/null
+++ b/browser/locales/Makefile.in
@@ -0,0 +1,200 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/config.mk
+
+ifdef LOCALE_MERGEDIR
+vpath crashreporter%.ini $(LOCALE_MERGEDIR)/browser/crashreporter
+endif
+vpath crashreporter%.ini $(LOCALE_SRCDIR)/crashreporter
+ifdef LOCALE_MERGEDIR
+vpath crashreporter%.ini @srcdir@/en-US/crashreporter
+endif
+
+
+SUBMAKEFILES += \
+ $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/Makefile \
+ $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales/Makefile \
+ $(NULL)
+
+# This makefile uses variable overrides from the libs-% target to
+# build non-default locales to non-default dist/ locations. Be aware!
+
+PWD := $(CURDIR)
+
+# These are defaulted to be compatible with the files the wget-en-US target
+# pulls. You may override them if you provide your own files. You _must_
+# override them when MOZ_PKG_PRETTYNAMES is defined - the defaults will not
+# work in that case.
+ZIP_IN ?= $(ABS_DIST)/$(PACKAGE)
+WIN32_INSTALLER_IN ?= $(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
+RETRIEVE_WINDOWS_INSTALLER = 1
+
+MOZ_LANGPACK_EID=langpack-$(AB_CD)@firefox.mozilla.org
+
+L10N_PREF_JS_EXPORTS = $(call MERGE_FILE,firefox-l10n.js)
+L10N_PREF_JS_EXPORTS_PATH = $(FINAL_TARGET)/$(PREF_DIR)
+L10N_PREF_JS_EXPORTS_FLAGS = $(PREF_PPFLAGS) --silence-missing-directive-warnings
+PP_TARGETS += L10N_PREF_JS_EXPORTS
+
+ifneq (,$(filter cocoa,$(MOZ_WIDGET_TOOLKIT)))
+MOZ_PKG_MAC_DSSTORE=$(ABS_DIST)/branding/dsstore
+MOZ_PKG_MAC_BACKGROUND=$(ABS_DIST)/branding/background.png
+MOZ_PKG_MAC_ICON=$(ABS_DIST)/branding/disk.icns
+MOZ_PKG_MAC_EXTRA=--symlink '/Applications:/ '
+endif
+
+ifeq (WINNT,$(OS_ARCH))
+UNINSTALLER_PACKAGE_HOOK = $(RM) -r $(STAGEDIST)/uninstall; \
+ $(NSINSTALL) -D $(STAGEDIST)/uninstall; \
+ cp ../installer/windows/l10ngen/helper.exe $(STAGEDIST)/uninstall; \
+ $(RM) $(ABS_DIST)/l10n-stage/setup.exe; \
+ cp ../installer/windows/l10ngen/setup.exe $(ABS_DIST)/l10n-stage; \
+ $(NULL)
+
+STUB_HOOK = $(NSINSTALL) -D '$(ABS_DIST)/$(PKG_INST_PATH)'; \
+ $(RM) '$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
+ cp ../installer/windows/l10ngen/stub.exe '$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
+ chmod 0755 '$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe'; \
+ $(NULL)
+endif
+
+SEARCHPLUGINS_FILENAMES := $(shell $(call py_action,output_searchplugins_list,$(srcdir)/search/list.json $(AB_CD)))
+SEARCHPLUGINS_PATH := .deps/generated_$(AB_CD)
+SEARCHPLUGINS_TARGET := libs searchplugins
+SEARCHPLUGINS := $(foreach plugin,$(addsuffix .xml,$(SEARCHPLUGINS_FILENAMES)),$(or $(wildcard $(srcdir)/searchplugins/$(plugin)),$(warning Missing searchplugin: $(plugin))))
+# Some locale-specific search plugins may have preprocessor directives, but the
+# default en-US ones do not.
+SEARCHPLUGINS_FLAGS := --silence-missing-directive-warnings
+PP_TARGETS += SEARCHPLUGINS
+
+list-json = $(SEARCHPLUGINS_PATH)/list.json
+GARBAGE += $(list-json)
+
+libs:: searchplugins
+
+# Required for l10n.mk - defines a list of app sub dirs that should
+# be included in langpack xpis.
+DIST_SUBDIRS = $(DIST_SUBDIR)
+
+include $(topsrcdir)/config/rules.mk
+
+include $(topsrcdir)/toolkit/locales/l10n.mk
+
+$(list-json): $(call mkdir_deps,$(SEARCHPLUGINS_PATH)) $(if $(IS_LANGUAGE_REPACK),FORCE)
+ $(call py_action,generate_searchjson,$(srcdir)/search/list.json $(AB_CD) $(list-json))
+searchplugins:: $(list-json)
+
+$(STAGEDIST): $(DIST)/branding
+
+$(DIST)/branding:
+ $(NSINSTALL) -D $@
+
+DEFINES += -DBOOKMARKS_INCLUDE_DIR=$(dir $(call MERGE_FILE,profile/bookmarks.inc))
+
+libs-%:
+ $(NSINSTALL) -D $(DIST)/install
+ @$(MAKE) -C ../../toolkit/locales libs-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
+ @$(MAKE) -C ../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
+ @$(MAKE) -C ../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
+ @$(MAKE) -C ../extensions/pocket/locale AB_CD=$* XPI_NAME=locale-$*
+ @$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
+ @$(MAKE) -C ../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$* XPI_ROOT_APPID='$(XPI_ROOT_APPID)'
+ @$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
+ @$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
+ @$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
+
+repackage-win32-installer: WIN32_INSTALLER_OUT=$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
+repackage-win32-installer: $(call ESCAPE_WILDCARD,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
+ @echo 'Repackaging $(WIN32_INSTALLER_IN) into $(WIN32_INSTALLER_OUT).'
+ $(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY) export
+ $(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
+ $(MAKE) repackage-zip \
+ AB_CD=$(AB_CD) \
+ MOZ_PKG_FORMAT=SFX7Z \
+ ZIP_IN='$(WIN32_INSTALLER_IN)' \
+ ZIP_OUT='$(WIN32_INSTALLER_OUT)' \
+ SFX_HEADER='$(PWD)/../installer/windows/l10ngen/7zSD.sfx \
+ $(topsrcdir)/browser/installer/windows/app.tag'
+
+ifeq (WINNT,$(OS_ARCH))
+repackage-win32-installer-%: $(STAGEDIST)
+ @$(MAKE) repackage-win32-installer AB_CD=$* WIN32_INSTALLER_IN='$(WIN32_INSTALLER_IN)'
+
+repackage-zip-%: repackage-win32-installer-%
+else
+repackage-win32-installer-%: ;
+endif
+
+
+clobber-zip:
+ $(RM) $(STAGEDIST)/chrome/$(AB_CD).jar \
+ $(STAGEDIST)/chrome/$(AB_CD).manifest \
+ $(STAGEDIST)/$(PREF_DIR)/firefox-l10n.js
+ $(RM) -rf $(STAGEDIST)/dictionaries \
+ $(STAGEDIST)/hyphenation \
+ $(STAGEDIST)/defaults/profile \
+ $(STAGEDIST)/chrome/$(AB_CD)
+
+
+langpack: langpack-$(AB_CD)
+
+# This is a generic target that will make a langpack, repack ZIP (+tarball)
+# builds, and repack an installer if applicable. It is called from the
+# tinderbox scripts. Alter it with caution.
+
+installers-%: clobber-% langpack-% repackage-win32-installer-% repackage-zip-%
+ @echo 'repackaging done'
+
+ifdef MOZ_UPDATER
+# Note that we want updater.ini to be in the top directory, not the browser/
+# subdirectory, because that's where the updater is installed and runs.
+libs:: $(call MERGE_FILE,updater/updater.ini) $(call mkdir_deps,$(DIST)/bin)
+ifeq ($(OS_ARCH),WINNT)
+ cat $< $(srcdir)/../installer/windows/nsis/updater_append.ini | \
+ sed -e 's/^InfoText=/Info=/' -e 's/^TitleText=/Title=/' | \
+ sed -e 's/%MOZ_APP_DISPLAYNAME%/$(MOZ_APP_DISPLAYNAME)/' > \
+ $(FINAL_TARGET)/../updater.ini
+else
+ cat $< | \
+ sed -e 's/^InfoText=/Info=/' -e 's/^TitleText=/Title=/' | \
+ sed -e 's/%MOZ_APP_DISPLAYNAME%/$(MOZ_APP_DISPLAYNAME)/' > \
+ $(FINAL_TARGET)/../updater.ini
+endif
+endif
+
+ifdef MOZ_CRASHREPORTER
+libs:: crashreporter-override.ini
+ $(SYSINSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)
+endif
+
+ident:
+ @printf 'fx_revision '
+ @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py \
+ $(STAGEDIST)/application.ini App SourceStamp
+ @printf 'buildid '
+ @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py \
+ $(STAGEDIST)/application.ini App BuildID
+
+merge-%:
+ifdef LOCALE_MERGEDIR
+ $(RM) -rf $(LOCALE_MERGEDIR)
+ $(topsrcdir)/mach compare-locales --merge-dir $(LOCALE_MERGEDIR) $*
+endif
+ @echo
+
+# test target, depends on make package
+# try to repack x-test, with just toolkit/defines.inc being there
+l10n-check:: INNER_UNMAKE_PACKAGE=true
+l10n-check::
+ $(RM) -rf x-test
+ $(NSINSTALL) -D x-test/toolkit
+ echo '#define MOZ_LANG_TITLE Just testing' > x-test/toolkit/defines.inc
+ @# ZIP_IN='$(ZIP_IN)' will pass down the *current* value of ZIP_IN, based
+ @# on MOZ_SIMPLE_PACKAGE_NAME not being reset, overwriting the value it
+ @# would get with MOZ_SIMPLE_PACKAGE_NAME reset.
+ $(MAKE) installers-x-test L10NBASEDIR='$(PWD)' LOCALE_MERGEDIR='$(PWD)/mergedir' ZIP_IN='$(ZIP_IN)' MOZ_SIMPLE_PACKAGE_NAME= UNIVERSAL_BINARY=
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/unpack.py $(DIST)/l10n-stage/$(MOZ_PKG_DIR)$(_RESPATH)
+ cd $(DIST)/l10n-stage && test $$(cat $(MOZ_PKG_DIR)$(_RESPATH)/update.locale) = x-test
diff --git a/browser/locales/all-locales b/browser/locales/all-locales
new file mode 100644
index 000000000..dba2d5336
--- /dev/null
+++ b/browser/locales/all-locales
@@ -0,0 +1,97 @@
+ach
+af
+an
+ar
+as
+ast
+az
+bg
+bn-BD
+bn-IN
+br
+bs
+ca
+cak
+cs
+cy
+da
+de
+dsb
+el
+en-GB
+en-ZA
+eo
+es-AR
+es-CL
+es-ES
+es-MX
+et
+eu
+fa
+ff
+fi
+fr
+fy-NL
+ga-IE
+gd
+gl
+gn
+gu-IN
+he
+hi-IN
+hr
+hsb
+hu
+hy-AM
+id
+is
+it
+ja
+ja-JP-mac
+ka
+kab
+kk
+km
+kn
+ko
+lij
+lt
+ltg
+lv
+mai
+mk
+ml
+mr
+ms
+my
+nb-NO
+ne-NP
+nl
+nn-NO
+or
+pa-IN
+pl
+pt-BR
+pt-PT
+rm
+ro
+ru
+si
+sk
+sl
+son
+sq
+sr
+sv-SE
+ta
+te
+th
+tl
+tr
+uk
+ur
+uz
+vi
+xh
+zh-CN
+zh-TW
diff --git a/browser/locales/en-US/chrome/browser-region/region.properties b/browser/locales/en-US/chrome/browser-region/region.properties
new file mode 100644
index 000000000..e078ed528
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser-region/region.properties
@@ -0,0 +1,41 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Default search engine
+browser.search.defaultenginename=Google
+
+# Search engine order (order displayed in the search bar dropdown)s
+browser.search.order.1=Google
+browser.search.order.2=Yahoo
+browser.search.order.3=Bing
+
+# This is the default set of web based feed handlers shown in the reader
+# selection UI
+browser.contentHandlers.types.0.title=My Yahoo!
+browser.contentHandlers.types.0.uri=https://add.my.yahoo.com/rss?url=%s
+
+# increment this number when anything gets changed in the list below. This will
+# cause Firefox to re-read these prefs and inject any new handlers into the
+# profile database. Note that "new" is defined as "has a different URL"; this
+# means that it's not possible to update the name of existing handler, so
+# don't make any spelling errors here.
+gecko.handlerService.defaultHandlersVersion=4
+
+# The default set of protocol handlers for webcal:
+gecko.handlerService.schemes.webcal.0.name=30 Boxes
+gecko.handlerService.schemes.webcal.0.uriTemplate=https://30boxes.com/external/widget?refer=ff&url=%s
+
+# The default set of protocol handlers for mailto:
+gecko.handlerService.schemes.mailto.0.name=Yahoo! Mail
+gecko.handlerService.schemes.mailto.0.uriTemplate=https://compose.mail.yahoo.com/?To=%s
+gecko.handlerService.schemes.mailto.1.name=Gmail
+gecko.handlerService.schemes.mailto.1.uriTemplate=https://mail.google.com/mail/?extsrc=mailto&url=%s
+
+# The default set of protocol handlers for irc:
+gecko.handlerService.schemes.irc.0.name=Mibbit
+gecko.handlerService.schemes.irc.0.uriTemplate=https://www.mibbit.com/?url=%s
+
+# The default set of protocol handlers for ircs:
+gecko.handlerService.schemes.ircs.0.name=Mibbit
+gecko.handlerService.schemes.ircs.0.uriTemplate=https://www.mibbit.com/?url=%s
diff --git a/browser/locales/en-US/chrome/browser/aboutAccounts.dtd b/browser/locales/en-US/chrome/browser/aboutAccounts.dtd
new file mode 100644
index 000000000..358722156
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutAccounts.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 aboutAccounts.welcome "Welcome to &syncBrand.shortName.label;">
+<!ENTITY aboutAccounts.connected "Account connected">
+
+<!ENTITY aboutAccountsConfig.description "Sign in to sync your tabs, bookmarks, passwords &amp; more.">
+<!ENTITY aboutAccountsConfig.startButton.label "Get started">
+<!ENTITY aboutAccountsConfig.useOldSync.label "Using an older version of Sync?">
+<!ENTITY aboutAccountsConfig.syncPreferences.label "Sync preferences">
+<!ENTITY aboutAccounts.noConnection.title "No connection">
+<!ENTITY aboutAccounts.noConnection.description "You must be connected to the Internet to sign in.">
+<!ENTITY aboutAccounts.noConnection.retry "Try again">
+<!ENTITY aboutAccounts.badConfig.title "Bad configuration">
+<!ENTITY aboutAccounts.badConfig.description "Unable to determine your Firefox Account server configuration. Please try again later.">
diff --git a/browser/locales/en-US/chrome/browser/aboutDialog.dtd b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
new file mode 100644
index 000000000..e0c34692e
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -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/. -->
+<!ENTITY aboutDialog.title "About &brandFullName;">
+
+<!-- LOCALIZATION NOTE (update.checkForUpdatesButton.*, update.updateButton.*):
+# Only one button is present at a time.
+# The button when displayed is located directly under the Firefox version in
+# the about dialog (see bug 596813 for screenshots).
+-->
+<!ENTITY update.checkForUpdatesButton.label "Check for updates">
+<!ENTITY update.checkForUpdatesButton.accesskey "C">
+<!ENTITY update.updateButton.label2 "Restart &brandShortName; to Update">
+<!ENTITY update.updateButton.accesskey "R">
+
+
+<!-- LOCALIZATION NOTE (warningDesc.version): This is a warning about the experimental nature of Nightly and Aurora builds. It is only shown in those versions. -->
+<!ENTITY warningDesc.version "&brandShortName; is experimental and may be unstable.">
+<!-- LOCALIZATION NOTE (warningDesc.telemetryDesc): This is a notification that Nightly/Aurora builds automatically send Telemetry data back to Mozilla. It is only shown in those versions. "It" refers to brandShortName. -->
+<!ENTITY warningDesc.telemetryDesc "It automatically sends information about performance, hardware, usage and customizations back to &vendorShortName; to help make &brandShortName; better.">
+
+<!-- LOCALIZATION NOTE (community.exp.*) This paragraph is shown in "experimental" builds, i.e. Nightly and Aurora builds, instead of the other "community.*" strings below. -->
+<!ENTITY community.exp.start "">
+<!-- LOCALIZATION NOTE (community.exp.mozillaLink): This is a link title that links to http://www.mozilla.org/. -->
+<!ENTITY community.exp.mozillaLink "&vendorShortName;">
+<!ENTITY community.exp.middle " is a ">
+<!-- LOCALIZATION NOTE (community.exp.creditslink): This is a link title that links to about:credits. -->
+<!ENTITY community.exp.creditsLink "global community">
+<!ENTITY community.exp.end " working together to keep the Web open, public and accessible to all.">
+
+<!ENTITY community.start2 "&brandShortName; is designed by ">
+<!-- LOCALIZATION NOTE (community.mozillaLink): This is a link title that links to http://www.mozilla.org/. -->
+<!ENTITY community.mozillaLink "&vendorShortName;">
+<!ENTITY community.middle2 ", a ">
+<!-- LOCALIZATION NOTE (community.creditsLink): This is a link title that links to about:credits. -->
+<!ENTITY community.creditsLink "global community">
+<!ENTITY community.end3 " working together to keep the Web open, public and accessible to all.">
+
+<!ENTITY helpus.start "Want to help? ">
+<!-- LOCALIZATION NOTE (helpus.donateLink): This is a link title that links to https://sendto.mozilla.org/page/contribute/Give-Now?source=mozillaorg_default_footer&ref=firefox_about&utm_campaign=firefox_about&utm_source=firefox&utm_medium=referral&utm_content=20140929_FireFoxAbout. -->
+<!ENTITY helpus.donateLink "Make a donation">
+<!ENTITY helpus.middle " or ">
+<!-- LOCALIZATION NOTE (helpus.getInvolvedLink): This is a link title that links to http://www.mozilla.org/contribute/. -->
+<!ENTITY helpus.getInvolvedLink "get involved!">
+<!ENTITY helpus.end "">
+
+<!ENTITY releaseNotes.link "What’s new">
+
+<!-- LOCALIZATION NOTE (bottomLinks.license): This is a link title that links to about:license. -->
+<!ENTITY bottomLinks.license "Licensing Information">
+
+<!-- LOCALIZATION NOTE (bottomLinks.rights): This is a link title that links to about:rights. -->
+<!ENTITY bottomLinks.rights "End-User Rights">
+
+<!-- LOCALIZATION NOTE (bottomLinks.privacy): This is a link title that links to https://www.mozilla.org/legal/privacy/. -->
+<!ENTITY bottomLinks.privacy "Privacy Policy">
+
+<!-- LOCALIZATION NOTE (update.checkingForUpdates): try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.checkingForUpdates "Checking for updates…">
+<!-- LOCALIZATION NOTE (update.noUpdatesFound): try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.noUpdatesFound "&brandShortName; is up to date">
+<!-- LOCALIZATION NOTE (update.adminDisabled): try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.adminDisabled "Updates disabled by your system administrator">
+<!-- LOCALIZATION NOTE (update.otherInstanceHandlingUpdates): try to make the localized text short -->
+<!ENTITY update.otherInstanceHandlingUpdates "&brandShortName; is being updated by another instance">
+<!ENTITY update.restarting "Restarting…">
+
+<!-- LOCALIZATION NOTE (update.failed.start,update.failed.linkText,update.failed.end):
+ update.failed.start, update.failed.linkText, and update.failed.end all go into
+ one line with linkText being wrapped in an anchor that links to a site to download
+ the latest version of Firefox (e.g. http://www.firefox.com). As this is all in
+ one line, try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.failed.start "Update failed. ">
+<!ENTITY update.failed.linkText "Download the latest version">
+<!ENTITY update.failed.end "">
+
+<!-- LOCALIZATION NOTE (update.manual.start,update.manual.end): update.manual.start and update.manual.end
+ all go into one line and have an anchor in between with text that is the same as the link to a site
+ to download the latest version of Firefox (e.g. http://www.firefox.com). As this is all in one line,
+ try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.manual.start "Updates available at ">
+<!ENTITY update.manual.end "">
+
+<!-- LOCALIZATION NOTE (update.unsupported.start,update.unsupported.linkText,update.unsupported.end):
+ update.unsupported.start, update.unsupported.linkText, and
+ update.unsupported.end all go into one line with linkText being wrapped in
+ an anchor that links to a site to provide additional information regarding
+ why the system is no longer supported. As this is all in one line, try to
+ make the localized text short (see bug 843497 for screenshots). -->
+<!ENTITY update.unsupported.start "You can not perform further updates on this system. ">
+<!ENTITY update.unsupported.linkText "Learn more">
+<!ENTITY update.unsupported.end "">
+
+<!-- LOCALIZATION NOTE (update.downloading.start,update.downloading.end): update.downloading.start and
+ update.downloading.end all go into one line, with the amount downloaded inserted in between. As this
+ is all in one line, try to make the localized text short (see bug 596813 for screenshots). The — is
+ the "em dash" (long dash).
+ example: Downloading update — 111 KB of 13 MB -->
+<!ENTITY update.downloading.start "Downloading update — ">
+<!ENTITY update.downloading.end "">
+
+<!ENTITY update.applying "Applying update…">
+
+<!-- LOCALIZATION NOTE (channel.description.start,channel.description.end): channel.description.start and
+ channel.description.end create one sentence, with the current channel label inserted in between.
+ example: You are currently on the _Stable_ update channel. -->
+<!ENTITY channel.description.start "You are currently on the ">
+<!ENTITY channel.description.end " update channel. ">
diff --git a/browser/locales/en-US/chrome/browser/aboutHealthReport.dtd b/browser/locales/en-US/chrome/browser/aboutHealthReport.dtd
new file mode 100644
index 000000000..0cc20e38e
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutHealthReport.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/. -->
+
+<!-- LOCALIZATION NOTE (abouthealth.pagetitle): Firefox Health Report is a proper noun in en-US, please keep this in mind. -->
+<!ENTITY abouthealth.pagetitle "&brandShortName; Health Report">
diff --git a/browser/locales/en-US/chrome/browser/aboutHome.dtd b/browser/locales/en-US/chrome/browser/aboutHome.dtd
new file mode 100644
index 000000000..7e3b57a79
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutHome.dtd
@@ -0,0 +1,40 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+%syncBrandDTD;
+
+<!-- These strings are used in the about:home page -->
+
+<!ENTITY abouthome.pageTitle "&brandFullName; Start Page">
+
+<!-- LOCALIZATION NOTE (abouthome.defaultSnippet1.v1):
+ text in <a/> will be linked to the Firefox features page on mozilla.com
+-->
+<!ENTITY abouthome.defaultSnippet1.v1 "Thanks for choosing Firefox! To get the most out of your browser, learn more about the <a>latest features</a>.">
+<!-- LOCALIZATION NOTE (abouthome.defaultSnippet2.v1):
+ text in <a/> will be linked to the featured add-ons on addons.mozilla.org
+-->
+<!ENTITY abouthome.defaultSnippet2.v1 "It’s easy to customize your Firefox exactly the way you want it. <a>Choose from thousands of add-ons</a>.">
+<!-- LOCALIZATION NOTE (abouthome.rightsSnippet): text in <a/> will be linked to about:rights -->
+<!ENTITY abouthome.rightsSnippet "&brandFullName; is free and open source software from the non-profit Mozilla Foundation. <a>Know your rights…</a>">
+
+<!ENTITY abouthome.bookmarksButton.label "Bookmarks">
+<!ENTITY abouthome.historyButton.label "History">
+<!-- LOCALIZATION NOTE (abouthome.preferencesButtonWin.label): The label for the
+ preferences/options item on about:home on Windows -->
+<!ENTITY abouthome.preferencesButtonWin.label "Options">
+<!-- LOCALIZATION NOTE (abouthome.preferencesButtonUnix.label): The label for the
+ preferences/options item on about:home on Linux and OS X -->
+<!ENTITY abouthome.preferencesButtonUnix.label "Preferences">
+<!ENTITY abouthome.addonsButton.label "Add-ons">
+<!ENTITY abouthome.downloadsButton.label "Downloads">
+<!ENTITY abouthome.syncButton.label "&syncBrand.shortName.label;">
+
+<!-- LOCALIZATION NOTE (abouthome.aboutMozilla.label): The (invisible) label for
+ the mozilla wordmark in the top-right corner that links to Mozilla's main
+ about page. -->
+<!ENTITY abouthome.aboutMozilla.label "About Mozilla">
diff --git a/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd b/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd
new file mode 100644
index 000000000..381da356a
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.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 aboutPrivateBrowsing.notPrivate "You are currently not in a private window.">
+<!ENTITY privatebrowsingpage.openPrivateWindow.label "Open a Private Window">
+<!ENTITY privatebrowsingpage.openPrivateWindow.accesskey "P">
+
+<!ENTITY privateBrowsing.title "Private Browsing">
+<!ENTITY privateBrowsing.title.tracking "Private Browsing with Tracking Protection">
+<!ENTITY aboutPrivateBrowsing.info.notsaved.before "When you browse in a Private Window, Firefox ">
+<!ENTITY aboutPrivateBrowsing.info.notsaved.emphasize "does not save">
+<!ENTITY aboutPrivateBrowsing.info.notsaved.after ":">
+<!ENTITY aboutPrivateBrowsing.info.visited "visited pages">
+<!ENTITY aboutPrivateBrowsing.info.searches "searches">
+<!ENTITY aboutPrivateBrowsing.info.cookies "cookies">
+<!ENTITY aboutPrivateBrowsing.info.temporaryFiles "temporary files">
+<!ENTITY aboutPrivateBrowsing.info.saved.before "Firefox ">
+<!ENTITY aboutPrivateBrowsing.info.saved.emphasize "will save">
+<!ENTITY aboutPrivateBrowsing.info.saved.after2 " your:">
+<!ENTITY aboutPrivateBrowsing.info.downloads "downloads">
+<!ENTITY aboutPrivateBrowsing.info.bookmarks "bookmarks">
+<!ENTITY aboutPrivateBrowsing.note.before "Private Browsing ">
+<!ENTITY aboutPrivateBrowsing.note.emphasize "doesn’t make you anonymous">
+<!ENTITY aboutPrivateBrowsing.note.after " on the Internet. Your employer or Internet service provider can still know what page you visit.">
+<!ENTITY aboutPrivateBrowsing.learnMore2 "Learn more about">
+<!ENTITY aboutPrivateBrowsing.learnMore2.title "Private Browsing">
+
+<!ENTITY trackingProtection.title "Tracking Protection">
+<!ENTITY trackingProtection.description2 "Some websites use trackers that can monitor your activity across the Internet. With Tracking Protection Firefox will block many trackers that can collect information about your browsing behavior.">
+<!ENTITY trackingProtection.startTour1 "See how it works">
diff --git a/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.properties b/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.properties
new file mode 100644
index 000000000..f01e40dd7
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.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/.
+
+title.head=Private Browsing
+title.normal=Open a private window?
diff --git a/browser/locales/en-US/chrome/browser/aboutRobots.dtd b/browser/locales/en-US/chrome/browser/aboutRobots.dtd
new file mode 100644
index 000000000..23447add1
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutRobots.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/. -->
+
+<!-- These strings are used in the about:robots page, which ties in with the
+ robots theme used in the Firefox 3 Beta 2/3 first run pages.
+ They're just meant to be fun and whimsical, with references to some geeky
+ but well-known robots in movies and books. Be creative with translations! -->
+
+<!-- Nonsense line from the movie "The Day The Earth Stood Still". No translation needed. -->
+<!ENTITY robots.pagetitle "Gort! Klaatu barada nikto!">
+<!-- Movie: Logan's Run... Box (cybog): "Welcome Humans! I am ready for you." -->
+<!ENTITY robots.errorTitleText "Welcome Humans!">
+<!-- Movie: The Day The Earth Stood Still. Spoken by Klaatu. -->
+<!ENTITY robots.errorShortDescText "We have come to visit you in peace and with goodwill!">
+<!-- Various books by Isaac Asimov. http://en.wikipedia.org/wiki/Three_Laws_of_Robotics -->
+<!ENTITY robots.errorLongDesc1 "Robots may not injure a human being or, through inaction, allow a human being to come to harm.">
+<!-- Movie: Blade Runner. Batty: "I've seen things you people wouldn’t believe..." -->
+<!ENTITY robots.errorLongDesc2 "Robots have seen things you people wouldn’t believe.">
+<!-- Book: Hitchhiker’s Guide To The Galaxy. What the Sirius Cybernetics Corporation calls robots. -->
+<!ENTITY robots.errorLongDesc3 "Robots are Your Plastic Pal Who’s Fun To Be With.">
+<!-- TV: Futurama. Bender's first line is "Bite my shiny metal ass." -->
+<!ENTITY robots.errorLongDesc4 "Robots have shiny metal posteriors which should not be bitten.">
+<!-- TV: Battlestar Galactica (2004 series). From the opening text. -->
+<!ENTITY robots.errorTrailerDescText "And they have a plan.">
+<!-- TV: Battlestar Galactica (2004 series). Common expletive referring to Cylons. -->
+<!ENTITY robots.imgtitle "Frakkin' Toasters">
+<!-- Book: Hitchhiker's Guide To The Galaxy. Arthur presses a button and it warns him. -->
+<!ENTITY robots.dontpress "Please do not press this button again.">
diff --git a/browser/locales/en-US/chrome/browser/aboutSearchReset.dtd b/browser/locales/en-US/chrome/browser/aboutSearchReset.dtd
new file mode 100644
index 000000000..65ae1599f
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutSearchReset.dtd
@@ -0,0 +1,30 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY searchreset.tabtitle "Restore Search Settings">
+
+<!ENTITY searchreset.pageTitle "Restore your search settings?">
+
+<!ENTITY searchreset.pageInfo1 "Your search settings might be out-of-date. &brandShortName; can help you restore the default search settings.">
+
+
+<!-- LOCALIZATION NOTE (searchreset.selector.label): this string is
+followed by a dropdown of all the built-in search engines. -->
+<!ENTITY searchreset.selector.label "This will set your default search engine to">
+
+<!-- LOCALIZATION NOTE (searchreset.beforelink.pageInfo,
+searchreset.afterlink.pageInfo): these two string are used respectively
+before and after the "Settings page" link (searchreset.link.pageInfo2).
+Localizers can use one of them, or both, to better adapt this sentence to
+their language. -->
+<!ENTITY searchreset.beforelink.pageInfo2 "You can change these settings at any time from the ">
+<!ENTITY searchreset.afterlink.pageInfo2 ".">
+
+<!ENTITY searchreset.link.pageInfo2 "Settings page">
+
+<!ENTITY searchreset.noChangeButton "Don’t Change">
+<!ENTITY searchreset.noChangeButton.access "D">
+
+<!ENTITY searchreset.changeEngineButton "Change Search Engine">
+<!ENTITY searchreset.changeEngineButton.access "C">
diff --git a/browser/locales/en-US/chrome/browser/aboutSessionRestore.dtd b/browser/locales/en-US/chrome/browser/aboutSessionRestore.dtd
new file mode 100644
index 000000000..cd1f3c426
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutSessionRestore.dtd
@@ -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/. -->
+
+<!ENTITY restorepage.tabtitle "Restore Session">
+
+<!-- LOCALIZATION NOTE: The title is intended to be apologetic and disarming, expressing dismay
+ and regret that we are unable to restore the session for the user -->
+<!ENTITY restorepage.errorTitle "Well, this is embarrassing.">
+<!ENTITY restorepage.problemDesc "&brandShortName; is having trouble recovering your windows and tabs. This is usually caused by a recently opened web page.">
+<!ENTITY restorepage.tryThis "You can try:">
+<!ENTITY restorepage.restoreSome "Removing one or more tabs that you think may be causing the problem">
+<!ENTITY restorepage.startNew "Starting an entirely new browsing session">
+
+<!ENTITY restorepage.tryagainButton "Restore">
+<!ENTITY restorepage.restore.access "R">
+<!ENTITY restorepage.closeButton "Close">
+<!ENTITY restorepage.close.access "C">
+
+<!ENTITY restorepage.restoreHeader "Restore">
+<!ENTITY restorepage.listHeader "Windows and Tabs">
+<!-- LOCALIZATION NOTE: &#37;S will be replaced with a number. -->
+<!ENTITY restorepage.windowLabel "Window &#37;S">
+
+
+<!-- LOCALIZATION NOTE: The following 'welcomeback2' strings are for about:welcomeback,
+ not for about:sessionstore -->
+
+<!ENTITY welcomeback2.restoreButton "Let’s go!">
+<!ENTITY welcomeback2.restoreButton.access "L">
+
+<!ENTITY welcomeback2.tabtitle "Success!">
+
+<!ENTITY welcomeback2.pageTitle "Success!">
+<!ENTITY welcomeback2.pageInfo1 "&brandShortName; is ready to go.">
+
+<!ENTITY welcomeback2.label.restoreAll "Restore all Windows and Tabs">
+<!ENTITY welcomeback2.label.restoreSome "Restore only the ones you want">
+
+
+<!-- LOCALIZATION NOTE (welcomeback2.beforelink.pageInfo2,
+welcomeback2.afterlink.pageInfo2): these two string are used respectively
+before and after the the "learn more" link (welcomeback2.link.pageInfo2).
+Localizers can use one of them, or both, to better adapt this sentence to
+their language.
+-->
+<!ENTITY welcomeback2.beforelink.pageInfo2 "Your add-ons and customizations have been removed and your browser settings have been restored to their defaults. If this didn’t fix your issue, ">
+<!ENTITY welcomeback2.afterlink.pageInfo2 "">
+
+<!ENTITY welcomeback2.link.pageInfo2 "learn more about what you can do.">
+
diff --git a/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd b/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd
new file mode 100644
index 000000000..fad491bbd
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd
@@ -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/. -->
+
+<!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/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
new file mode 100644
index 000000000..e624de155
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd
@@ -0,0 +1,30 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY tabCrashed.closeTab "Close This Tab">
+<!ENTITY tabCrashed.restoreTab "Restore This Tab">
+<!ENTITY tabCrashed.restoreAll "Restore All Crashed Tabs">
+
+<!-- LOCALIZATION NOTE (tabCrashed.header2): "Gah" is an English slang word
+ used to express surprise or frustration (or both at the same time). We
+ are using it to communicate in an informal way that it is both
+ frustrating that your tab crashed and a surprise that we didn't want to
+ happen. If you have a similar word or short phrase that is not profane or
+ vulgar, use it. If not, feel free to skip the word in your
+ translation. -->
+<!ENTITY tabCrashed.header2 "Gah. Your tab just crashed.">
+<!ENTITY tabCrashed.offerHelp "We can help you!">
+<!ENTITY tabCrashed.single.offerHelpMessage "Choose &tabCrashed.restoreTab; to reload page content.">
+<!ENTITY tabCrashed.multiple.offerHelpMessage "Choose &tabCrashed.restoreTab; or &tabCrashed.restoreAll; to reload page content.">
+<!ENTITY tabCrashed.requestHelp "Will you help us?">
+<!ENTITY tabCrashed.requestHelpMessage "Crash reports help us diagnose problems and make &brandShortName; better.">
+<!ENTITY tabCrashed.requestReport "Report this tab">
+<!ENTITY tabCrashed.sendReport2 "Send a crash report for the tab you are viewing">
+<!ENTITY tabCrashed.commentPlaceholder2 "Optional comments (comments are publicly visible)">
+<!ENTITY tabCrashed.includeURL2 "Include page URL with this crash report">
+<!ENTITY tabCrashed.emailPlaceholder "Enter your email address here">
+<!ENTITY tabCrashed.emailMe "Email me when more information is available">
+<!ENTITY tabCrashed.reportSent "Crash report already submitted; thank you for helping make &brandShortName; better!">
+<!ENTITY tabCrashed.requestAutoSubmit2 "Report background tabs">
+<!ENTITY tabCrashed.autoSubmit "Update preferences to automatically submit backlogged crash reports (and get fewer messages like this from us in the future)"> \ No newline at end of file
diff --git a/browser/locales/en-US/chrome/browser/accounts.properties b/browser/locales/en-US/chrome/browser/accounts.properties
new file mode 100644
index 000000000..237556132
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/accounts.properties
@@ -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/.
+
+# autoDisconnectDescription is shown in an info bar when we detect an old
+# Sync is being used.
+autoDisconnectDescription = We’ve rebuilt Sync to make it easier for everyone.
+
+# autoDisconnectSignIn.label and .accessKey are for buttons when we auto-disconnect
+autoDisconnectSignIn.label = Sign in to Sync
+autoDisconnectSignIn.accessKey = S
+
+# LOCALIZATION NOTE (reconnectDescription) - %S = Email address of user's Firefox Account
+reconnectDescription = Reconnect %S
+
+# LOCALIZATION NOTE (verifyDescription) - %S = Email address of user's Firefox Account
+verifyDescription = Verify %S
+
+# These strings are shown in a desktop notification after the
+# user requests we resend a verification email.
+verificationSentTitle = Verification Sent
+# LOCALIZATION NOTE (verificationSentBody) - %S = Email address of user's Firefox Account
+verificationSentBody = A verification link has been sent to %S.
+verificationNotSentTitle = Unable to Send Verification
+verificationNotSentBody = We are unable to send a verification mail at this time, please try again later.
+
+# LOCALIZATION NOTE (syncStartNotification.title, syncStartNotification.body)
+# These strings are used in a notification shown after Sync is connected.
+syncStartNotification.title = Sync enabled
+# %S is brandShortName
+syncStartNotification.body2 = %S will begin syncing momentarily.
+
+# LOCALIZATION NOTE (deviceDisconnectedNotification.title, deviceDisconnectedNotification.body)
+# These strings are used in a notification shown after Sync was disconnected remotely.
+deviceDisconnectedNotification.title = Sync disconnected
+deviceDisconnectedNotification.body = This computer has been successfully disconnected from Firefox Sync.
+
+# LOCALIZATION NOTE (sendTabToAllDevices.menuitem)
+# Displayed in the Send Tabs context menu when right clicking a tab, a page or a link.
+sendTabToAllDevices.menuitem = All Devices
+
+# LOCALIZATION NOTE (tabArrivingNotification.title, tabArrivingNotificationWithDevice.title,
+# tabsArrivingNotification.title, unnamedTabsArrivingNotification2.body,
+# unnamedTabsArrivingNotificationMultiple2.body, unnamedTabsArrivingNotificationNoDevice.body)
+# These strings are used in a notification shown when we're opening tab(s) another device sent us to display.
+
+# LOCALIZATION NOTE (tabArrivingNotification.title, tabArrivingNotificationWithDevice.title)
+# The body for these is the URL of the tab recieved
+tabArrivingNotification.title = Tab received
+# LOCALIZATION NOTE (tabArrivingNotificationWithDevice.title) %S is the device name
+tabArrivingNotificationWithDevice.title = Tab from %S
+
+tabsArrivingNotification.title = Multiple tabs received
+# LOCALIZATION NOTE (unnamedTabsArrivingNotification2.body):
+# Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of tabs received and #2 is the device name.
+unnamedTabsArrivingNotification2.body = #1 tab has arrived from #2;#1 tabs have arrived from #2
+# LOCALIZATION NOTE (unnamedTabsArrivingNotificationMultiple2.body):
+# Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of tabs received.
+unnamedTabsArrivingNotificationMultiple2.body = #1 tab has arrived from your connected devices;#1 tabs have arrived from your connected devices
+
+# LOCALIZATION NOTE (unnamedTabsArrivingNotificationNoDevice.body):
+# Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of tabs received
+# This version is used when we don't know any device names.
+unnamedTabsArrivingNotificationNoDevice.body = #1 tab has arrived;#1 tabs have arrived
diff --git a/browser/locales/en-US/chrome/browser/baseMenuOverlay.dtd b/browser/locales/en-US/chrome/browser/baseMenuOverlay.dtd
new file mode 100644
index 000000000..de92ad3aa
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/baseMenuOverlay.dtd
@@ -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/. -->
+
+<!ENTITY minimizeWindow.key "m">
+<!ENTITY minimizeWindow.label "Minimize">
+<!ENTITY bringAllToFront.label "Bring All to Front">
+<!ENTITY zoomWindow.label "Zoom">
+<!ENTITY windowMenu.label "Window">
+
+<!ENTITY helpMenu.label "Help">
+<!ENTITY helpMenu.accesskey "H">
+<!-- LOCALIZATION NOTE some localizations of Windows (ex:french, german) use "?"
+ for the help button in the menubar but Gnome does not. -->
+<!ENTITY helpMenuWin.label "Help">
+<!ENTITY helpMenuWin.accesskey "H">
+<!ENTITY aboutProduct2.label "About &brandShorterName;">
+<!ENTITY aboutProduct2.accesskey "A">
+<!ENTITY productHelp2.label "&brandShorterName; Help">
+<!ENTITY productHelp2.accesskey "H">
+<!ENTITY helpMac.commandkey "?">
+
+<!ENTITY helpKeyboardShortcuts.label "Keyboard Shortcuts">
+<!ENTITY helpKeyboardShortcuts.accesskey "K">
+
+<!ENTITY helpSafeMode.label "Restart with Add-ons Disabled…">
+<!ENTITY helpSafeMode.accesskey "R">
+<!ENTITY helpSafeMode.stop.label "Restart with Add-ons Enabled">
+<!ENTITY helpSafeMode.stop.accesskey "R">
+
+<!ENTITY healthReport2.label "&brandShorterName; Health Report">
+<!ENTITY healthReport2.accesskey "e">
+
+<!ENTITY helpTroubleshootingInfo.label "Troubleshooting Information">
+<!ENTITY helpTroubleshootingInfo.accesskey "T">
+
+<!ENTITY helpFeedbackPage.label "Submit Feedback…">
+<!ENTITY helpFeedbackPage.accesskey "S">
+
+<!ENTITY helpShowTour2.label "&brandShorterName; Tour">
+<!ENTITY helpShowTour2.accesskey "o">
+
+<!ENTITY preferencesCmdMac.label "Preferences…">
+<!ENTITY preferencesCmdMac.commandkey ",">
+
+<!ENTITY servicesMenuMac.label "Services">
+
+<!ENTITY hideThisAppCmdMac2.label "Hide &brandShorterName;">
+<!ENTITY hideThisAppCmdMac2.commandkey "H">
+
+<!ENTITY hideOtherAppsCmdMac.label "Hide Others">
+<!ENTITY hideOtherAppsCmdMac.commandkey "H">
+
+<!ENTITY showAllAppsCmdMac.label "Show All">
diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd
new file mode 100644
index 000000000..99e55c7e4
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -0,0 +1,884 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the browser main menu items -->
+<!-- LOCALIZATION NOTE : FILE Do not translate commandkeys -->
+
+<!-- LOCALIZATION NOTE (mainWindow.title): DONT_TRANSLATE -->
+<!ENTITY mainWindow.title "&brandFullName;">
+<!-- LOCALIZATION NOTE (mainWindow.titlemodifier) : DONT_TRANSLATE -->
+<!ENTITY mainWindow.titlemodifier "&brandFullName;">
+<!-- LOCALIZATION NOTE (mainWindow.titlemodifiermenuseparator): DONT_TRANSLATE -->
+<!ENTITY mainWindow.titlemodifiermenuseparator " - ">
+<!-- LOCALIZATION NOTE (mainWindow.titlePrivateBrowsingSuffix): This will be appended to the window's title
+ inside the private browsing mode -->
+<!ENTITY mainWindow.titlePrivateBrowsingSuffix "(Private Browsing)">
+
+<!ENTITY appmenu.tooltip "Open menu">
+<!ENTITY navbarOverflow.label "More tools…">
+
+<!-- Tab context menu -->
+<!ENTITY reloadTab.label "Reload Tab">
+<!ENTITY reloadTab.accesskey "R">
+<!ENTITY reloadAllTabs.label "Reload All Tabs">
+<!ENTITY reloadAllTabs.accesskey "A">
+<!-- LOCALIZATION NOTE (closeTabsToTheEnd.label): This should indicate the
+direction in which tabs are closed, i.e. locales that use RTL mode should say
+left instead of right. -->
+<!ENTITY closeTabsToTheEnd.label "Close Tabs to the Right">
+<!ENTITY closeTabsToTheEnd.accesskey "i">
+<!ENTITY closeOtherTabs.label "Close Other Tabs">
+<!ENTITY closeOtherTabs.accesskey "o">
+
+<!-- LOCALIZATION NOTE (pinTab.label, unpinTab.label): "Pin" is being
+used as a metaphor for expressing the fact that these tabs are "pinned" to the
+left edge of the tabstrip. Really we just want the string to express the idea
+that this is a lightweight and reversible action that keeps your tab where you
+can reach it easily. -->
+<!ENTITY pinTab.label "Pin Tab">
+<!ENTITY pinTab.accesskey "P">
+<!ENTITY unpinTab.label "Unpin Tab">
+<!ENTITY unpinTab.accesskey "b">
+<!ENTITY sendTabToDevice.label "Send Tab to Device">
+<!ENTITY sendTabToDevice.accesskey "D">
+<!ENTITY sendPageToDevice.label "Send Page to Device">
+<!ENTITY sendPageToDevice.accesskey "D">
+<!ENTITY sendLinkToDevice.label "Send Link to Device">
+<!ENTITY sendLinkToDevice.accesskey "D">
+<!ENTITY moveToNewWindow.label "Move to New Window">
+<!ENTITY moveToNewWindow.accesskey "W">
+<!ENTITY bookmarkAllTabs.label "Bookmark All Tabs…">
+<!ENTITY bookmarkAllTabs.accesskey "T">
+<!ENTITY undoCloseTab.label "Undo Close Tab">
+<!ENTITY undoCloseTab.accesskey "U">
+<!ENTITY closeTab.label "Close Tab">
+<!ENTITY closeTab.accesskey "c">
+
+<!ENTITY listAllTabs.label "List all tabs">
+
+<!ENTITY tabCmd.label "New Tab">
+<!ENTITY tabCmd.accesskey "T">
+<!ENTITY tabCmd.commandkey "t">
+<!-- LOCALIZATION NOTE (openLocationCmd.label): "Open Location" is only
+displayed on OS X, and only on windows that aren't main browser windows, or
+when there are no windows but Firefox is still running. -->
+<!ENTITY openLocationCmd.label "Open Location…">
+<!ENTITY openFileCmd.label "Open File…">
+<!ENTITY openFileCmd.accesskey "O">
+<!ENTITY openFileCmd.commandkey "o">
+<!ENTITY printSetupCmd.label "Page Setup…">
+<!ENTITY printSetupCmd.accesskey "u">
+<!ENTITY printPreviewCmd.label "Print Preview">
+<!ENTITY printPreviewCmd.accesskey "v">
+<!ENTITY printCmd.label "Print…">
+<!ENTITY printCmd.accesskey "P">
+<!ENTITY printCmd.commandkey "p">
+
+<!ENTITY goOfflineCmd.label "Work Offline">
+<!ENTITY goOfflineCmd.accesskey "k">
+
+<!ENTITY menubarCmd.label "Menu Bar">
+<!ENTITY menubarCmd.accesskey "M">
+<!ENTITY navbarCmd.label "Navigation Toolbar">
+<!ENTITY personalbarCmd.label "Bookmarks Toolbar">
+<!ENTITY personalbarCmd.accesskey "B">
+<!ENTITY bookmarksToolbarItem.label "Bookmarks Toolbar Items">
+
+<!ENTITY toolbarContextMenu.reloadAllTabs.label "Reload All Tabs">
+<!ENTITY toolbarContextMenu.reloadAllTabs.accesskey "A">
+<!ENTITY toolbarContextMenu.bookmarkAllTabs.label "Bookmark All Tabs…">
+<!ENTITY toolbarContextMenu.bookmarkAllTabs.accesskey "T">
+<!ENTITY toolbarContextMenu.undoCloseTab.label "Undo Close Tab">
+<!ENTITY toolbarContextMenu.undoCloseTab.accesskey "U">
+
+<!ENTITY pageSourceCmd.label "Page Source">
+<!ENTITY pageSourceCmd.accesskey "o">
+<!ENTITY pageSourceCmd.commandkey "u">
+<!ENTITY pageInfoCmd.label "Page Info">
+<!ENTITY pageInfoCmd.accesskey "I">
+<!ENTITY pageInfoCmd.commandkey "i">
+<!ENTITY mirrorTabCmd.label "Mirror Tab">
+<!ENTITY mirrorTabCmd.accesskey "m">
+<!-- LOCALIZATION NOTE (enterFullScreenCmd.label, exitFullScreenCmd.label):
+These should match what Safari and other Apple applications use on OS X Lion. -->
+<!ENTITY enterFullScreenCmd.label "Enter Full Screen">
+<!ENTITY enterFullScreenCmd.accesskey "F">
+<!ENTITY exitFullScreenCmd.label "Exit Full Screen">
+<!ENTITY exitFullScreenCmd.accesskey "F">
+<!ENTITY fullScreenCmd.label "Full Screen">
+<!ENTITY fullScreenCmd.accesskey "F">
+<!ENTITY fullScreenCmd.macCommandKey "f">
+<!ENTITY showAllTabsCmd.label "Show All Tabs">
+<!ENTITY showAllTabsCmd.accesskey "A">
+<!ENTITY toggleReaderMode.key "R">
+
+<!ENTITY fxaSignIn.label "Sign in to &syncBrand.shortName.label;">
+<!ENTITY fxaSignedIn.tooltip "Open &syncBrand.shortName.label; preferences">
+<!ENTITY fxaSignInError.label "Reconnect to &syncBrand.shortName.label;">
+<!ENTITY fxaUnverified.label "Verify Your Account">
+
+
+<!ENTITY fullScreenMinimize.tooltip "Minimize">
+<!ENTITY fullScreenRestore.tooltip "Restore">
+<!ENTITY fullScreenClose.tooltip "Close">
+<!ENTITY fullScreenAutohide.label "Hide Toolbars">
+<!ENTITY fullScreenAutohide.accesskey "H">
+<!ENTITY fullScreenExit.label "Exit Full Screen Mode">
+<!ENTITY fullScreenExit.accesskey "F">
+
+<!-- LOCALIZATION NOTE (fullscreenWarning.beforeDomain.label,
+ fullscreenWarning.afterDomain.label): these two strings are used
+ respectively before and after the domain requiring fullscreen.
+ Localizers can use one of them, or both, to better adapt this
+ sentence to their language. -->
+<!ENTITY fullscreenWarning.beforeDomain.label "">
+<!ENTITY fullscreenWarning.afterDomain.label "is now full screen">
+<!ENTITY fullscreenWarning.generic.label "This document is now full screen">
+
+<!-- LOCALIZATION NOTE (exitDOMFullscreen.button,
+ exitDOMFullscreenMac.button): the "escape" button on PC keyboards
+ is uppercase, while on Mac keyboards it is lowercase -->
+<!ENTITY exitDOMFullscreen.button "Exit Full Screen (Esc)">
+<!ENTITY exitDOMFullscreenMac.button "Exit Full Screen (esc)">
+<!ENTITY leaveDOMFullScreen.label "Exit Full Screen">
+<!ENTITY leaveDOMFullScreen.accesskey "u">
+
+<!-- LOCALIZATION NOTE (pointerlockWarning.beforeDomain.label,
+ pointerlockWarning.afterDomain.label): these two strings are used
+ respectively before and after the domain requiring pointerlock.
+ Localizers can use one of them, or both, to better adapt this
+ sentence to their language. -->
+<!ENTITY pointerlockWarning.beforeDomain.label "">
+<!ENTITY pointerlockWarning.afterDomain.label "has control of your pointer. Press Esc to take back control.">
+<!ENTITY pointerlockWarning.generic.label "This document has control of your pointer. Press Esc to take back control.">
+
+<!ENTITY closeWindow.label "Close Window">
+<!ENTITY closeWindow.accesskey "d">
+
+<!ENTITY bookmarksMenu.label "Bookmarks">
+<!ENTITY bookmarksMenu.accesskey "B">
+<!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
+<!ENTITY editThisBookmarkCmd.label "Edit This Bookmark">
+<!ENTITY bookmarkThisPageCmd.commandkey "d">
+<!-- LOCALIZATION NOTE (findShareServices.label):
+ - Use the unicode ellipsis char, \u2026,
+ - or use "..." if \u2026 doesn't suit traditions in your locale. -->
+<!ENTITY findShareServices.label "Find more Share services…">
+<!ENTITY sharePageCmd.label "Share This Page">
+<!ENTITY sharePageCmd.commandkey "S">
+<!ENTITY sharePageCmd.accesskey "s">
+<!-- LOCALIZATION NOTE (shareLink.accesskey): must be different than the following share access keys -->
+<!ENTITY shareLink.label "Share This Link">
+<!ENTITY shareLink.accesskey "h">
+<!ENTITY shareImage.label "Share This Image">
+<!ENTITY shareImage.accesskey "r">
+<!ENTITY shareSelect.label "Share Selection">
+<!ENTITY shareSelect.accesskey "r">
+<!ENTITY shareVideo.label "Share This Video">
+<!ENTITY shareVideo.accesskey "r">
+<!ENTITY feedsMenu2.label "Subscribe to This Page">
+<!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
+<!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
+<!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">
+<!ENTITY showAllBookmarks2.label "Show All Bookmarks">
+<!ENTITY recentBookmarks.label "Recently Bookmarked">
+<!ENTITY otherBookmarksCmd.label "Other Bookmarks">
+<!ENTITY bookmarksToolbarChevron.tooltip "Show more bookmarks">
+<!ENTITY showRecentlyBookmarked.label "Show Recently Bookmarked">
+<!ENTITY showRecentlyBookmarked.accesskey "h">
+<!ENTITY hideRecentlyBookmarked.label "Hide Recently Bookmarked">
+<!ENTITY hideRecentlyBookmarked.accesskey "H">
+
+<!ENTITY backCmd.label "Back">
+<!ENTITY backButton.tooltip "Go back one page">
+<!ENTITY forwardCmd.label "Forward">
+<!ENTITY forwardButton.tooltip "Go forward one page">
+<!ENTITY backForwardButtonMenu.tooltip "Right-click or pull down to show history">
+<!ENTITY backForwardButtonMenuMac.tooltip "Pull down to show history">
+<!ENTITY reloadCmd.label "Reload">
+<!ENTITY reloadButton.tooltip "Reload current page">
+<!ENTITY stopCmd.label "Stop">
+<!ENTITY stopCmd.macCommandKey ".">
+<!ENTITY stopButton.tooltip "Stop loading this page">
+<!ENTITY goEndCap.tooltip "Go to the address in the Location Bar">
+<!ENTITY printButton.label "Print">
+<!ENTITY printButton.tooltip "Print this page">
+
+<!ENTITY urlbar.viewSiteInfo.label "View site information">
+
+<!ENTITY urlbar.defaultNotificationAnchor.tooltip "Open message panel">
+<!ENTITY urlbar.geolocationNotificationAnchor.tooltip "Open location request panel">
+<!ENTITY urlbar.addonsNotificationAnchor.tooltip "Open add-on installation message panel">
+<!ENTITY urlbar.indexedDBNotificationAnchor.tooltip "Open offline storage message panel">
+<!ENTITY urlbar.passwordNotificationAnchor.tooltip "Open save password message panel">
+<!ENTITY urlbar.pluginsNotificationAnchor.tooltip "Manage plug-in use">
+<!ENTITY urlbar.webNotificationAnchor.tooltip "Change whether you can receive notifications from the site">
+
+<!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.tooltip "Manage sharing your camera and/or microphone with the site">
+<!ENTITY urlbar.webRTCShareMicrophoneNotificationAnchor.tooltip "Manage sharing your microphone with the site">
+<!ENTITY urlbar.webRTCShareScreenNotificationAnchor.tooltip "Manage sharing your windows or screen with the site">
+
+<!ENTITY urlbar.servicesNotificationAnchor.tooltip "Open install message panel">
+<!ENTITY urlbar.translateNotificationAnchor.tooltip "Translate this page">
+<!ENTITY urlbar.translatedNotificationAnchor.tooltip "Manage page translation">
+<!ENTITY urlbar.emeNotificationAnchor.tooltip "Manage use of DRM software">
+
+<!ENTITY urlbar.cameraBlocked.tooltip "You have blocked your camera for this website.">
+<!ENTITY urlbar.microphoneBlocked.tooltip "You have blocked your microphone for this website.">
+<!ENTITY urlbar.screenBlocked.tooltip "You have blocked this website from sharing your screen.">
+<!ENTITY urlbar.geolocationBlocked.tooltip "You have blocked location information for this website.">
+<!ENTITY urlbar.indexedDBBlocked.tooltip "You have blocked data storage for this website.">
+<!ENTITY urlbar.webNotificationsBlocked.tooltip "You have blocked notifications for this website.">
+
+<!ENTITY urlbar.openHistoryPopup.tooltip "Show history">
+
+<!ENTITY urlbar.zoomReset.tooltip "Reset zoom level">
+
+<!ENTITY searchItem.title "Search">
+
+<!-- Toolbar items -->
+<!ENTITY homeButton.label "Home">
+
+<!ENTITY bookmarksButton.label "Bookmarks">
+<!ENTITY bookmarksCmd.commandkey "b">
+
+<!ENTITY bookmarksMenuButton.label "Bookmarks">
+<!ENTITY bookmarksMenuButton.other.label "Other Bookmarks">
+<!ENTITY viewBookmarksSidebar2.label "View Bookmarks Sidebar">
+<!ENTITY viewBookmarksToolbar.label "View Bookmarks Toolbar">
+
+<!ENTITY containersMenu.label "Containers">
+
+<!-- LOCALIZATION NOTE (bookmarksSidebarGtkCmd.commandkey): This command
+ - key should not contain the letters A-F, since these are reserved
+ - shortcut keys on Linux. -->
+<!ENTITY bookmarksGtkCmd.commandkey "o">
+<!ENTITY bookmarksWinCmd.commandkey "i">
+
+<!ENTITY historyButton.label "History">
+<!ENTITY historySidebarCmd.commandKey "h">
+
+<!ENTITY toolsMenu.label "Tools">
+<!ENTITY toolsMenu.accesskey "T">
+
+<!ENTITY keywordfield.label "Add a Keyword for this Search…">
+<!ENTITY keywordfield.accesskey "K">
+
+<!ENTITY downloads.label "Downloads">
+<!ENTITY downloads.accesskey "D">
+<!ENTITY downloads.commandkey "j">
+<!ENTITY downloadsUnix.commandkey "y">
+<!ENTITY addons.label "Add-ons">
+<!ENTITY addons.accesskey "A">
+<!ENTITY addons.commandkey "A">
+
+<!ENTITY webDeveloperMenu.label "Web Developer">
+<!ENTITY webDeveloperMenu.accesskey "W">
+
+<!ENTITY inspectContextMenu.label "Inspect Element">
+<!ENTITY inspectContextMenu.accesskey "Q">
+
+<!ENTITY fileMenu.label "File">
+<!ENTITY fileMenu.accesskey "F">
+<!ENTITY newUserContext.label "New Container Tab">
+<!ENTITY newUserContext.accesskey "B">
+<!ENTITY newNavigatorCmd.label "New Window">
+<!ENTITY newNavigatorCmd.key "N">
+<!ENTITY newNavigatorCmd.accesskey "N">
+<!ENTITY newPrivateWindow.label "New Private Window">
+<!ENTITY newPrivateWindow.accesskey "W">
+<!ENTITY newNonRemoteWindow.label "New Non-e10s Window">
+
+<!ENTITY editMenu.label "Edit">
+<!ENTITY editMenu.accesskey "E">
+<!ENTITY undoCmd.label "Undo">
+<!ENTITY undoCmd.key "Z">
+<!ENTITY undoCmd.accesskey "U">
+<!ENTITY redoCmd.label "Redo">
+<!ENTITY redoCmd.key "Y">
+<!ENTITY redoCmd.accesskey "R">
+<!ENTITY cutCmd.label "Cut">
+<!ENTITY cutCmd.key "X">
+<!ENTITY cutCmd.accesskey "t">
+<!ENTITY copyCmd.label "Copy">
+<!ENTITY copyCmd.key "C">
+<!ENTITY copyCmd.accesskey "C">
+<!ENTITY pasteCmd.label "Paste">
+<!ENTITY pasteCmd.key "V">
+<!ENTITY pasteCmd.accesskey "P">
+<!ENTITY deleteCmd.label "Delete">
+<!ENTITY deleteCmd.key "D">
+<!ENTITY deleteCmd.accesskey "D">
+<!ENTITY selectAllCmd.label "Select All">
+<!ENTITY selectAllCmd.key "A">
+<!ENTITY selectAllCmd.accesskey "A">
+<!ENTITY preferencesCmd2.label "Options">
+<!ENTITY preferencesCmd2.accesskey "O">
+<!ENTITY preferencesCmdUnix.label "Preferences">
+<!ENTITY preferencesCmdUnix.accesskey "n">
+
+<!ENTITY clearRecentHistory.label "Clear Recent History…">
+
+<!ENTITY privateBrowsingCmd.commandkey "P">
+
+<!ENTITY viewMenu.label "View">
+<!ENTITY viewMenu.accesskey "V">
+<!ENTITY viewToolbarsMenu.label "Toolbars">
+<!ENTITY viewToolbarsMenu.accesskey "T">
+<!ENTITY viewSidebarMenu.label "Sidebar">
+<!ENTITY viewSidebarMenu.accesskey "e">
+<!ENTITY viewCustomizeToolbar.label "Customize…">
+<!ENTITY viewCustomizeToolbar.accesskey "C">
+
+<!ENTITY historyMenu.label "History">
+<!ENTITY historyMenu.accesskey "s">
+<!ENTITY historyUndoMenu.label "Recently Closed Tabs">
+<!-- LOCALIZATION NOTE (historyUndoWindowMenu): see bug 394759 -->
+<!ENTITY historyUndoWindowMenu.label "Recently Closed Windows">
+<!ENTITY historyRestoreLastSession.label "Restore Previous Session">
+
+<!ENTITY showAllHistoryCmd2.label "Show All History">
+<!ENTITY showAllHistoryCmd.commandkey "H">
+
+<!ENTITY appMenuCustomize.label "Customize">
+<!ENTITY appMenuCustomize.tooltip "Customize the Menu and Toolbars">
+<!ENTITY appMenuCustomizeExit.label "Exit Customize">
+<!ENTITY appMenuCustomizeExit.tooltip "Finish Customizing">
+<!ENTITY appMenuHistory.label "History">
+<!ENTITY appMenuHistory.showAll.label "Show All History">
+<!ENTITY appMenuHistory.clearRecent.label "Clear Recent History…">
+<!ENTITY appMenuHistory.restoreSession.label "Restore Previous Session">
+<!ENTITY appMenuHistory.viewSidebar.label "View History Sidebar">
+<!ENTITY appMenuHelp.tooltip "Open Help Menu">
+
+<!ENTITY appMenuRemoteTabs.label "Synced Tabs">
+<!-- LOCALIZATION NOTE (appMenuRemoteTabs.notabs.label): This is shown beneath
+ the name of a device when that device has no open tabs -->
+<!ENTITY appMenuRemoteTabs.notabs.label "No open tabs">
+<!-- LOCALIZATION NOTE (appMenuRemoteTabs.tabsnotsyncing.label): This is shown
+ when Sync is configured but syncing tabs is disabled. -->
+<!ENTITY appMenuRemoteTabs.tabsnotsyncing.label "Turn on tab syncing to view a list of tabs from your other devices.">
+<!-- LOCALIZATION NOTE (appMenuRemoteTabs.noclients.label): This is shown
+ when Sync is configured but this appears to be the only device attached to
+ the account. We also show links to download Firefox for android/ios. -->
+<!ENTITY appMenuRemoteTabs.noclients.title "No synced tabs… yet!">
+<!ENTITY appMenuRemoteTabs.noclients.subtitle "Want to see your tabs from other devices here?">
+<!ENTITY appMenuRemoteTabs.openprefs.label "Sync Preferences">
+<!ENTITY appMenuRemoteTabs.notsignedin.label "Sign in to view a list of tabs from your other devices.">
+<!ENTITY appMenuRemoteTabs.signin.label "Sign in to Sync">
+<!ENTITY appMenuRemoteTabs.sidebar.label "View Synced Tabs Sidebar">
+
+<!ENTITY customizeMenu.addToToolbar.label "Add to Toolbar">
+<!ENTITY customizeMenu.addToToolbar.accesskey "A">
+<!ENTITY customizeMenu.addToPanel.label "Add to Menu">
+<!ENTITY customizeMenu.addToPanel.accesskey "M">
+<!ENTITY customizeMenu.moveToToolbar.label "Move to Toolbar">
+<!ENTITY customizeMenu.moveToToolbar.accesskey "o">
+<!-- LOCALIZATION NOTE (customizeMenu.moveToPanel.accesskey) can appear on the
+ same context menu as menubarCmd and personalbarCmd, so they should have
+ different access keys. customizeMenu.moveToToolbar and
+ customizeMenu.moveToPanel are mutually exclusive, so can share access
+ keys. -->
+<!ENTITY customizeMenu.moveToPanel.label "Move to Menu">
+<!ENTITY customizeMenu.moveToPanel.accesskey "o">
+<!ENTITY customizeMenu.removeFromToolbar.label "Remove from Toolbar">
+<!ENTITY customizeMenu.removeFromToolbar.accesskey "R">
+<!ENTITY customizeMenu.removeFromMenu.label "Remove from Menu">
+<!ENTITY customizeMenu.removeFromMenu.accesskey "R">
+<!ENTITY customizeMenu.addMoreItems.label "Add More Items…">
+<!ENTITY customizeMenu.addMoreItems.accesskey "A">
+
+<!ENTITY openCmd.commandkey "l">
+<!ENTITY urlbar.placeholder2 "Search or enter address">
+<!ENTITY urlbar.accesskey "d">
+<!-- LOCALIZATION NOTE (urlbar.extension.label): Used to indicate that a selected autocomplete entry is provided by an extension. -->
+<!ENTITY urlbar.extension.label "Extension:">
+<!ENTITY urlbar.switchToTab.label "Switch to tab:">
+
+<!ENTITY urlbar.searchSuggestionsNotification.question "Would you like to improve your search experience with suggestions?">
+<!ENTITY urlbar.searchSuggestionsNotification.learnMore "Learn more…">
+<!ENTITY urlbar.searchSuggestionsNotification.learnMore.accesskey "l">
+<!ENTITY urlbar.searchSuggestionsNotification.disable "No">
+<!ENTITY urlbar.searchSuggestionsNotification.disable.accesskey "n">
+<!ENTITY urlbar.searchSuggestionsNotification.enable "Yes">
+<!ENTITY urlbar.searchSuggestionsNotification.enable.accesskey "y">
+
+<!--
+ Comment duplicated from browser-sets.inc:
+
+ Search Command Key Logic works like this:
+
+ Unix: Ctrl+J (0.8, 0.9 support)
+ Ctrl+K (cross platform binding)
+ Mac: Cmd+K (cross platform binding)
+ Cmd+Opt+F (platform convention)
+ Win: Ctrl+K (cross platform binding)
+ Ctrl+E (IE compat)
+
+ We support Ctrl+K on all platforms now and advertise it in the menu since it is
+ our standard - it is a "safe" choice since it is near no harmful keys like "W" as
+ "E" is. People mourning the loss of Ctrl+K for emacs compat can switch their GTK
+ system setting to use emacs emulation, and we should respect it. Focus-Search-Box
+ is a fundamental keybinding and we are maintaining a XP binding so that it is easy
+ for people to switch to Linux.
+
+ -->
+<!ENTITY searchFocus.commandkey "k">
+<!ENTITY searchFocus.commandkey2 "e">
+<!ENTITY searchFocusUnix.commandkey "j">
+
+<!-- LOCALIZATION NOTE (contentSearchInput.label):
+ This is set as the aria-label attribute for the search input box in the
+ in-content search UI, to be used by screen readers. -->
+<!ENTITY contentSearchInput.label "Search query">
+<!ENTITY contentSearchSubmit.tooltip "Submit search">
+
+<!-- LOCALIZATION NOTE (searchFor.label, searchWith.label):
+ These two strings are used to build the header above the list of one-click
+ search providers: "Search for <used typed keywords> with:" -->
+<!ENTITY searchFor.label "Search for ">
+<!ENTITY searchWith.label " with:">
+
+<!-- LOCALIZATION NOTE (search.label, searchAfter.label):
+ This string is used to build the header above the list of one-click search
+ providers when a one off engine has been selected. The searchAfter text is
+ intentionally left empty for en-US and can be used by other localizations to
+ display a string after the search engine name. This string will be displayed
+ as: "Search <selected engine name><searchAfter.label text>" -->
+<!ENTITY search.label "Search ">
+<!ENTITY searchAfter.label "">
+
+<!-- LOCALIZATION NOTE (searchWithHeader.label):
+ The wording of this string should be as close as possible to
+ searchFor.label and searchWith.label. This string will be used instead of
+ them when the user has not typed any keyword. -->
+<!ENTITY searchWithHeader.label "Search with:">
+<!-- LOCALIZATION NOTE (changeSearchSettings.button):
+ This string won't wrap, so if the translated string is longer,
+ consider translating it as if it said only "Search Settings". -->
+<!ENTITY changeSearchSettings.button "Change Search Settings">
+<!ENTITY changeSearchSettings.tooltip "Change search settings">
+
+<!ENTITY searchInNewTab.label "Search in New Tab">
+<!ENTITY searchInNewTab.accesskey "T">
+<!ENTITY searchSetAsDefault.label "Set As Default Search Engine">
+<!ENTITY searchSetAsDefault.accesskey "D">
+
+<!ENTITY openLinkCmdInTab.label "Open Link in New Tab">
+<!ENTITY openLinkCmdInTab.accesskey "T">
+<!ENTITY openLinkCmd.label "Open Link in New Window">
+<!ENTITY openLinkCmd.accesskey "W">
+<!ENTITY openLinkInPrivateWindowCmd.label "Open Link in New Private Window">
+<!ENTITY openLinkInPrivateWindowCmd.accesskey "P">
+<!ENTITY openLinkCmdInCurrent.label "Open Link">
+<!ENTITY openLinkCmdInCurrent.accesskey "O">
+<!ENTITY openFrameCmdInTab.label "Open Frame in New Tab">
+<!ENTITY openFrameCmdInTab.accesskey "T">
+<!ENTITY openFrameCmd.label "Open Frame in New Window">
+<!ENTITY openFrameCmd.accesskey "W">
+<!ENTITY openLinkCmdInContainerTab.label "Open Link in New Container Tab">
+<!ENTITY openLinkCmdInContainerTab.accesskey "z">
+<!ENTITY showOnlyThisFrameCmd.label "Show Only This Frame">
+<!ENTITY showOnlyThisFrameCmd.accesskey "S">
+<!ENTITY reloadCmd.commandkey "r">
+<!ENTITY reloadFrameCmd.label "Reload Frame">
+<!ENTITY reloadFrameCmd.accesskey "R">
+<!ENTITY viewPartialSourceForSelectionCmd.label "View Selection Source">
+<!ENTITY viewPartialSourceForMathMLCmd.label "View MathML Source">
+<!-- LOCALIZATION NOTE (viewPartialSourceCmd.accesskey): This accesskey is used for both
+ viewPartialSourceForSelectionCmd.label and viewPartialSourceForMathMLCmd.label -->
+<!ENTITY viewPartialSourceCmd.accesskey "e">
+<!ENTITY viewPageSourceCmd.label "View Page Source">
+<!ENTITY viewPageSourceCmd.accesskey "V">
+<!ENTITY viewFrameSourceCmd.label "View Frame Source">
+<!ENTITY viewFrameSourceCmd.accesskey "V">
+<!ENTITY viewPageInfoCmd.label "View Page Info">
+<!ENTITY viewPageInfoCmd.accesskey "I">
+<!ENTITY viewFrameInfoCmd.label "View Frame Info">
+<!ENTITY viewFrameInfoCmd.accesskey "I">
+<!ENTITY reloadImageCmd.label "Reload Image">
+<!ENTITY reloadImageCmd.accesskey "R">
+<!ENTITY viewImageCmd.label "View Image">
+<!ENTITY viewImageCmd.accesskey "I">
+<!ENTITY viewImageInfoCmd.label "View Image Info">
+<!ENTITY viewImageInfoCmd.accesskey "f">
+<!ENTITY viewImageDescCmd.label "View Description">
+<!ENTITY viewImageDescCmd.accesskey "D">
+<!ENTITY viewVideoCmd.label "View Video">
+<!ENTITY viewVideoCmd.accesskey "I">
+<!ENTITY viewBGImageCmd.label "View Background Image">
+<!ENTITY viewBGImageCmd.accesskey "w">
+<!ENTITY setDesktopBackgroundCmd.label "Set As Desktop Background…">
+<!ENTITY setDesktopBackgroundCmd.accesskey "S">
+<!ENTITY bookmarkPageCmd2.label "Bookmark This Page">
+<!ENTITY bookmarkThisLinkCmd.label "Bookmark This Link">
+<!ENTITY bookmarkThisLinkCmd.accesskey "L">
+<!ENTITY bookmarkThisFrameCmd.label "Bookmark This Frame">
+<!ENTITY bookmarkThisFrameCmd.accesskey "m">
+<!ENTITY emailPageCmd.label "Email Link…">
+<!ENTITY emailPageCmd.accesskey "E">
+<!ENTITY savePageCmd.label "Save Page As…">
+<!ENTITY savePageCmd.accesskey "A">
+<!-- alternate for content area context menu -->
+<!ENTITY savePageCmd.accesskey2 "P">
+<!ENTITY savePageCmd.commandkey "s">
+<!ENTITY saveFrameCmd.label "Save Frame As…">
+<!ENTITY saveFrameCmd.accesskey "F">
+<!ENTITY printFrameCmd.label "Print Frame…">
+<!ENTITY printFrameCmd.accesskey "P">
+<!ENTITY saveLinkCmd.label "Save Link As…">
+<!ENTITY saveLinkCmd.accesskey "k">
+<!ENTITY saveImageCmd.label "Save Image As…">
+<!ENTITY saveImageCmd.accesskey "v">
+<!ENTITY saveVideoCmd.label "Save Video As…">
+<!ENTITY saveVideoCmd.accesskey "v">
+<!ENTITY saveAudioCmd.label "Save Audio As…">
+<!ENTITY saveAudioCmd.accesskey "v">
+<!ENTITY emailImageCmd.label "Email Image…">
+<!ENTITY emailImageCmd.accesskey "g">
+<!ENTITY emailVideoCmd.label "Email Video…">
+<!ENTITY emailVideoCmd.accesskey "a">
+<!ENTITY castVideoCmd.label "Send Video To Device">
+<!ENTITY castVideoCmd.accesskey "e">
+<!ENTITY emailAudioCmd.label "Email Audio…">
+<!ENTITY emailAudioCmd.accesskey "a">
+<!ENTITY playPluginCmd.label "Activate this plugin">
+<!ENTITY playPluginCmd.accesskey "c">
+<!ENTITY hidePluginCmd.label "Hide this plugin">
+<!ENTITY hidePluginCmd.accesskey "H">
+<!ENTITY copyLinkCmd.label "Copy Link Location">
+<!ENTITY copyLinkCmd.accesskey "a">
+<!ENTITY copyImageCmd.label "Copy Image Location">
+<!ENTITY copyImageCmd.accesskey "o">
+<!ENTITY copyImageContentsCmd.label "Copy Image">
+<!ENTITY copyImageContentsCmd.accesskey "y">
+<!ENTITY copyVideoURLCmd.label "Copy Video Location">
+<!ENTITY copyVideoURLCmd.accesskey "o">
+<!ENTITY copyAudioURLCmd.label "Copy Audio Location">
+<!ENTITY copyAudioURLCmd.accesskey "o">
+<!ENTITY copyEmailCmd.label "Copy Email Address">
+<!ENTITY copyEmailCmd.accesskey "E">
+<!ENTITY thisFrameMenu.label "This Frame">
+<!ENTITY thisFrameMenu.accesskey "h">
+
+<!-- Media (video/audio) controls -->
+<!-- LOCALIZATION NOTE: The access keys for "Play" and
+"Pause" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY mediaPlay.label "Play">
+<!ENTITY mediaPlay.accesskey "P">
+<!ENTITY mediaPause.label "Pause">
+<!ENTITY mediaPause.accesskey "P">
+<!-- LOCALIZATION NOTE: The access keys for "Mute" and
+"Unmute" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY mediaMute.label "Mute">
+<!ENTITY mediaMute.accesskey "M">
+<!ENTITY mediaUnmute.label "Unmute">
+<!ENTITY mediaUnmute.accesskey "m">
+<!ENTITY mediaPlaybackRate2.label "Play Speed">
+<!ENTITY mediaPlaybackRate2.accesskey "d">
+<!ENTITY mediaPlaybackRate050x2.label "Slow (0.5×)">
+<!ENTITY mediaPlaybackRate050x2.accesskey "S">
+<!ENTITY mediaPlaybackRate100x2.label "Normal">
+<!ENTITY mediaPlaybackRate100x2.accesskey "N">
+<!ENTITY mediaPlaybackRate125x2.label "Fast (1.25×)">
+<!ENTITY mediaPlaybackRate125x2.accesskey "F">
+<!ENTITY mediaPlaybackRate150x2.label "Faster (1.5×)">
+<!ENTITY mediaPlaybackRate150x2.accesskey "a">
+<!-- LOCALIZATION NOTE: "Ludicrous" is a reference to the
+movie "Space Balls" and is meant to say that this speed is very
+fast. -->
+<!ENTITY mediaPlaybackRate200x2.label "Ludicrous (2×)">
+<!ENTITY mediaPlaybackRate200x2.accesskey "L">
+<!ENTITY mediaLoop.label "Loop">
+<!ENTITY mediaLoop.accesskey "L">
+<!-- LOCALIZATION NOTE: The access keys for "Show Controls" and
+"Hide Controls" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY mediaShowControls.label "Show Controls">
+<!ENTITY mediaShowControls.accesskey "C">
+<!ENTITY mediaHideControls.label "Hide Controls">
+<!ENTITY mediaHideControls.accesskey "C">
+<!ENTITY videoFullScreen.label "Full Screen">
+<!ENTITY videoFullScreen.accesskey "F">
+<!ENTITY videoSaveImage.label "Save Snapshot As…">
+<!ENTITY videoSaveImage.accesskey "S">
+<!-- LOCALIZATION NOTE: The access keys for "Show Statistics" and
+"Hide Statistics" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY videoShowStats.label "Show Statistics">
+<!ENTITY videoShowStats.accesskey "t">
+<!ENTITY videoHideStats.label "Hide Statistics">
+<!ENTITY videoHideStats.accesskey "t">
+
+<!-- LOCALIZATION NOTE :
+fullZoomEnlargeCmd.commandkey3, fullZoomReduceCmd.commandkey2 and
+fullZoomResetCmd.commandkey2 are alternative acceleration keys for zoom.
+If shift key is needed with your locale popular keyboard for them,
+you can use these alternative items. Otherwise, their values should be empty. -->
+
+<!ENTITY fullZoomEnlargeCmd.label "Zoom In">
+<!ENTITY fullZoomEnlargeCmd.accesskey "I">
+<!ENTITY fullZoomEnlargeCmd.commandkey "+">
+<!ENTITY fullZoomEnlargeCmd.commandkey2 "="> <!-- + is above this key on many keyboards -->
+<!ENTITY fullZoomEnlargeCmd.commandkey3 "">
+
+<!ENTITY fullZoomReduceCmd.label "Zoom Out">
+<!ENTITY fullZoomReduceCmd.accesskey "O">
+<!ENTITY fullZoomReduceCmd.commandkey "-">
+<!ENTITY fullZoomReduceCmd.commandkey2 "">
+
+<!ENTITY fullZoomResetCmd.label "Reset">
+<!ENTITY fullZoomResetCmd.accesskey "R">
+<!ENTITY fullZoomResetCmd.commandkey "0">
+<!ENTITY fullZoomResetCmd.commandkey2 "">
+
+<!ENTITY fullZoomToggleCmd.label "Zoom Text Only">
+<!ENTITY fullZoomToggleCmd.accesskey "T">
+<!ENTITY fullZoom.label "Zoom">
+<!ENTITY fullZoom.accesskey "Z">
+
+<!ENTITY sidebarCloseButton.tooltip "Close sidebar">
+
+<!ENTITY quitApplicationCmdWin2.label "Exit">
+<!ENTITY quitApplicationCmdWin2.accesskey "x">
+<!ENTITY quitApplicationCmdWin2.tooltip "Exit &brandShorterName;">
+<!ENTITY goBackCmd.commandKey "[">
+<!ENTITY goForwardCmd.commandKey "]">
+<!ENTITY quitApplicationCmd.label "Quit">
+<!ENTITY quitApplicationCmd.accesskey "Q">
+<!ENTITY quitApplicationCmdMac2.label "Quit &brandShorterName;">
+<!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX builds. -->
+<!ENTITY quitApplicationCmdUnix.key "Q">
+
+<!ENTITY closeCmd.label "Close">
+<!ENTITY closeCmd.key "W">
+<!ENTITY closeCmd.accesskey "C">
+
+<!ENTITY toggleMuteCmd.key "M">
+
+<!ENTITY pageStyleMenu.label "Page Style">
+<!ENTITY pageStyleMenu.accesskey "y">
+<!ENTITY pageStyleNoStyle.label "No Style">
+<!ENTITY pageStyleNoStyle.accesskey "n">
+<!ENTITY pageStylePersistentOnly.label "Basic Page Style">
+<!ENTITY pageStylePersistentOnly.accesskey "b">
+
+<!ENTITY pageReportIcon.tooltip "Change pop-up blocking settings for this website">
+
+<!ENTITY allowPopups.accesskey "p">
+<!-- On Windows we use the term "Options" to describe settings, but
+ on Linux and Mac OS X we use "Preferences" - carry that distinction
+ over into this string, which is used in the "popup blocked" info bar . -->
+<!ENTITY editPopupSettingsUnix.label "Edit Pop-up Blocker Preferences…">
+<!ENTITY editPopupSettings.label "Edit Pop-up Blocker Options…">
+<!ENTITY editPopupSettings.accesskey "E">
+<!ENTITY dontShowMessage.accesskey "D">
+
+<!ENTITY bidiSwitchPageDirectionItem.label "Switch Page Direction">
+<!ENTITY bidiSwitchPageDirectionItem.accesskey "D">
+<!ENTITY bidiSwitchTextDirectionItem.label "Switch Text Direction">
+<!ENTITY bidiSwitchTextDirectionItem.accesskey "w">
+<!ENTITY bidiSwitchTextDirectionItem.commandkey "X">
+
+<!ENTITY findOnCmd.label "Find in This Page…">
+<!ENTITY findOnCmd.accesskey "F">
+<!ENTITY findOnCmd.commandkey "f">
+<!ENTITY findAgainCmd.label "Find Again">
+<!ENTITY findAgainCmd.accesskey "g">
+<!ENTITY findAgainCmd.commandkey "g">
+<!ENTITY findAgainCmd.commandkey2 "VK_F3">
+<!ENTITY findSelectionCmd.commandkey "e">
+
+<!ENTITY spellAddDictionaries.label "Add Dictionaries…">
+<!ENTITY spellAddDictionaries.accesskey "A">
+
+<!ENTITY editBookmark.done.label "Done">
+<!ENTITY editBookmark.removeBookmark.accessKey "R">
+
+<!ENTITY identity.connectionSecure "Secure Connection">
+<!ENTITY identity.connectionNotSecure "Connection is Not Secure">
+<!ENTITY identity.connectionFile "This page is stored on your computer.">
+<!ENTITY identity.connectionVerified2 "You are securely connected to this site, owned by:">
+<!ENTITY identity.connectionInternal "This is a secure &brandShortName; page.">
+<!ENTITY identity.insecureLoginForms2 "Logins entered on this page could be compromised.">
+
+<!-- Strings for connection state warnings. -->
+<!ENTITY identity.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
+<!ENTITY identity.passiveLoaded "Parts of this page are not secure (such as images).">
+<!ENTITY identity.activeLoaded "You have disabled protection on this page.">
+<!ENTITY identity.weakEncryption "This page uses weak encryption.">
+
+<!-- Strings for connection state warnings in the subview. -->
+<!ENTITY identity.description.insecure "Your connection to this site is not private. Information you submit could be viewed by others (like passwords, messages, credit cards, etc.).">
+<!ENTITY identity.description.insecureLoginForms "The login information you enter on this page is not secure and could be compromised.">
+<!ENTITY identity.description.weakCipher "Your connection to this website uses weak encryption and is not private.">
+<!ENTITY identity.description.weakCipher2 "Other people can view your information or modify the website’s behavior.">
+<!ENTITY identity.description.activeBlocked "&brandShortName; has blocked parts of this page that are not secure.">
+<!ENTITY identity.description.passiveLoaded "Your connection is not private and information you share with the site could be viewed by others.">
+<!ENTITY identity.description.passiveLoaded2 "This website contains content that is not secure (such as images).">
+<!ENTITY identity.description.passiveLoaded3 "Although &brandShortName; has blocked some content, there is still content on the page that is not secure (such as images).">
+<!ENTITY identity.description.activeLoaded "This website contains content that is not secure (such as scripts) and your connection to it is not private.">
+<!ENTITY identity.description.activeLoaded2 "Information you share with this site could be viewed by others (like passwords, messages, credit cards, etc.).">
+
+<!ENTITY identity.enableMixedContentBlocking.label "Enable protection">
+<!ENTITY identity.enableMixedContentBlocking.accesskey "E">
+<!ENTITY identity.disableMixedContentBlocking.label "Disable protection for now">
+<!ENTITY identity.disableMixedContentBlocking.accesskey "D">
+<!ENTITY identity.learnMore "Learn More">
+
+<!ENTITY identity.removeCertException.label "Remove Exception">
+<!ENTITY identity.removeCertException.accesskey "R">
+
+<!ENTITY identity.moreInfoLinkText2 "More Information">
+
+<!ENTITY identity.permissions "Permissions">
+<!ENTITY identity.permissionsEmpty "You have not granted this site any special permissions.">
+<!ENTITY identity.permissionsReloadHint "You may need to reload the page for changes to apply.">
+
+<!-- Name for the tabs toolbar as spoken by screen readers.
+ The word "toolbar" is appended automatically and should not be contained below! -->
+<!ENTITY tabsToolbar.label "Browser tabs">
+
+<!-- LOCALIZATION NOTE (syncTabsMenu3.label): This appears in the history menu -->
+<!ENTITY syncTabsMenu3.label "Synced Tabs">
+
+<!ENTITY syncedTabs.sidebar.label "Synced Tabs">
+<!ENTITY syncedTabs.sidebar.noclients.label "Sign in to Firefox from your other devices to view their tabs here.">
+<!ENTITY syncedTabs.sidebar.noclients.title "No synced tabs… yet!">
+<!ENTITY syncedTabs.sidebar.noclients.subtitle "Want to see your tabs from other devices here?">
+<!ENTITY syncedTabs.sidebar.notsignedin.label "Sign in to view a list of tabs from your other devices.">
+<!ENTITY syncedTabs.sidebar.notabs.label "No open tabs">
+<!ENTITY syncedTabs.sidebar.openprefs.label "Open &syncBrand.shortName.label; Preferences">
+<!-- LOCALIZATION NOTE (syncedTabs.sidebar.tabsnotsyncing.label): This is shown
+ when Sync is configured but syncing tabs is disabled. -->
+<!ENTITY syncedTabs.sidebar.tabsnotsyncing.label "Turn on tab syncing to view a list of tabs from your other devices.">
+
+<!ENTITY syncedTabs.context.open.label "Open">
+<!ENTITY syncedTabs.context.open.accesskey "O">
+<!ENTITY syncedTabs.context.openInNewTab.label "Open in a New Tab">
+<!ENTITY syncedTabs.context.openInNewTab.accesskey "w">
+<!ENTITY syncedTabs.context.openInNewWindow.label "Open in a New Window">
+<!ENTITY syncedTabs.context.openInNewWindow.accesskey "N">
+<!ENTITY syncedTabs.context.openInNewPrivateWindow.label "Open in a New Private Window">
+<!ENTITY syncedTabs.context.openInNewPrivateWindow.accesskey "P">
+<!ENTITY syncedTabs.context.bookmarkSingleTab.label "Bookmark This Tab…">
+<!ENTITY syncedTabs.context.bookmarkSingleTab.accesskey "B">
+<!ENTITY syncedTabs.context.copy.label "Copy">
+<!ENTITY syncedTabs.context.copy.accesskey "C">
+
+
+<!ENTITY syncBrand.shortName.label "Sync">
+
+<!ENTITY syncSignIn.label "Sign In To &syncBrand.shortName.label;…">
+<!ENTITY syncSignIn.accesskey "Y">
+<!ENTITY syncSyncNowItem.label "Sync Now">
+<!ENTITY syncSyncNowItem.accesskey "S">
+<!ENTITY syncReAuthItem.label "Reconnect to &syncBrand.shortName.label;…">
+<!ENTITY syncReAuthItem.accesskey "R">
+<!ENTITY syncToolbarButton.label "Sync">
+
+<!ENTITY social.addons.label "Manage Services…">
+
+<!ENTITY social.directory.label "Activations Directory">
+<!ENTITY social.directory.text "You can activate Share services from the directory.">
+<!ENTITY social.directory.button "Take me there!">
+<!ENTITY social.directory.introText "Click on a service to add it to &brandShortName;.">
+<!ENTITY social.directory.viewmore.text "View More">
+
+<!ENTITY customizeMode.menuAndToolbars.header2 "Additional Tools and Features">
+<!ENTITY customizeMode.menuAndToolbars.empty "Want more tools?">
+<!ENTITY customizeMode.menuAndToolbars.emptyLink "Choose from thousands of add-ons">
+<!ENTITY customizeMode.restoreDefaults "Restore Defaults">
+<!ENTITY customizeMode.toolbars "Show / Hide Toolbars">
+<!ENTITY customizeMode.titlebar "Title Bar">
+<!ENTITY customizeMode.lwthemes "Themes">
+<!ENTITY customizeMode.lwthemes.myThemes "My Themes">
+<!ENTITY customizeMode.lwthemes.recommended "Recommended">
+<!ENTITY customizeMode.lwthemes.menuManage "Manage">
+<!ENTITY customizeMode.lwthemes.menuManage.accessKey "M">
+<!ENTITY customizeMode.lwthemes.menuGetMore "Get More Themes">
+<!ENTITY customizeMode.lwthemes.menuGetMore.accessKey "G">
+
+<!ENTITY getUserMedia.selectCamera.label "Camera to share:">
+<!ENTITY getUserMedia.selectCamera.accesskey "C">
+<!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">
+<!ENTITY getUserMedia.selectMicrophone.accesskey "M">
+<!ENTITY getUserMedia.audioCapture.label "Audio from the tab will be shared.">
+<!ENTITY getUserMedia.allWindowsShared.message "All visible windows on your screen will be shared.">
+
+<!ENTITY trackingProtection.title "Tracking Protection">
+<!ENTITY trackingProtection.detectedBlocked3 "&brandShortName; is blocking parts of the page that may track your browsing.">
+<!ENTITY trackingProtection.detectedNotBlocked3 "This site includes elements that may track your browsing. You have disabled protection.">
+<!ENTITY trackingProtection.notDetected3 "No tracking elements detected on this page.">
+<!-- LOCALIZATION NOTE (trackingProtection.unblock.label, trackingProtection.unblock.accesskey):
+ The associated button with this label and accesskey is only shown when opening the control
+ center while looking at a site with trackers in NON-private browsing mode. -->
+<!ENTITY trackingProtection.unblock.label "Disable protection for this site">
+<!ENTITY trackingProtection.unblock.accesskey "D">
+<!-- LOCALIZATION NOTE (trackingProtection.unblockPrivate.label, trackingProtection.unblockPrivate.accesskey):
+ The associated button with this label and accesskey is only shown when opening the control
+ center while looking at a site with trackers in PRIVATE browsing mode. -->
+<!ENTITY trackingProtection.unblockPrivate.label "Disable protection for this session">
+<!ENTITY trackingProtection.unblockPrivate.accesskey "D">
+<!ENTITY trackingProtection.block2.label "Enable protection">
+<!ENTITY trackingProtection.block2.accesskey "E">
+
+<!ENTITY trackingContentBlocked.message "Tracking">
+<!ENTITY trackingContentBlocked.moreinfo "Parts of the page that track your online activity have been blocked.">
+<!ENTITY trackingContentBlocked.learnMore "Learn More">
+<!ENTITY trackingContentBlocked.options "Options">
+<!ENTITY trackingContentBlocked.unblock2.label "Disable protection for this site">
+<!ENTITY trackingContentBlocked.unblock2.accesskey "D">
+<!ENTITY trackingContentBlocked.block.label "Enable protection">
+<!ENTITY trackingContentBlocked.block.accesskey "E">
+<!ENTITY trackingContentBlocked.disabled.message "Tracking protection is disabled">
+
+<!ENTITY pluginNotification.showAll.label "Show All">
+<!ENTITY pluginNotification.showAll.accesskey "S">
+
+<!-- LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.properties -->
+<!ENTITY pluginActivateNow.label "Allow Now">
+<!ENTITY pluginActivateAlways.label "Allow and Remember">
+<!ENTITY pluginBlockNow.label "Block Plugin">
+
+<!-- LOCALIZATION NOTE: (pluginNotification.width): This is used to determine the
+ width of the plugin popup notification that can appear if a plugin has been
+ blocked on a page. Should be wide enough to fit the pluginActivateNow.label
+ and pluginActivateAlways.label strings above on a single line. This must be
+ a CSS length value. -->
+<!ENTITY pluginNotification.width "28em">
+
+<!ENTITY uiTour.infoPanel.close "Close">
+
+<!ENTITY appMenuSidebars.label "Sidebars">
+
+<!-- LOCALIZATION NOTE: (panicButton.view.mainTimeframeDesc, panicButton.view.5min, panicButton.view.2hr, panicButton.view.day):
+ The .mainTimeframeDesc string combined with any of the 3 others is meant to form a complete sentence, e.g. "Forget the last: Five minutes".
+ Please ensure that this remains the case in the translation. -->
+<!ENTITY panicButton.view.mainTimeframeDesc "Forget the last:">
+<!ENTITY panicButton.view.5min "Five minutes">
+<!ENTITY panicButton.view.2hr "Two hours">
+<!ENTITY panicButton.view.day "24 hours">
+
+<!-- LOCALIZATION NOTE: (panicButton.view.mainLabel, panicButton.view.deleteCookies, panicButton.view.deleteHistory, panicButton.view.deleteTabsAndWindows, panicButton.view.openNewWindow):
+ The .mainActionDesc string combined with any of the 4 others is meant to form a complete sentence, e.g. "Proceeding will: Delete Recent Cookies".
+ Note also that the deleteCookies, deleteHistory and deleteTabsAndWindows strings include <html:strong> tags for emphasis on the words "Cookies", "History", "Tabs" and "Windows".
+ The translation should do the same. -->
+<!ENTITY panicButton.view.mainActionDesc "Proceeding will:">
+<!ENTITY panicButton.view.deleteCookies "Delete Recent <html:strong>Cookies</html:strong>">
+<!ENTITY panicButton.view.deleteHistory "Delete Recent <html:strong>History</html:strong>">
+<!ENTITY panicButton.view.deleteTabsAndWindows "Close all <html:strong>Tabs</html:strong> and <html:strong>Windows</html:strong>">
+<!ENTITY panicButton.view.openNewWindow "Open a new clean Window">
+
+<!ENTITY panicButton.view.undoWarning "This action cannot be undone.">
+<!ENTITY panicButton.view.forgetButton "Forget!">
+
+<!ENTITY panicButton.thankyou.msg1 "Your recent history is cleared.">
+<!ENTITY panicButton.thankyou.msg2 "Safe browsing!">
+<!ENTITY panicButton.thankyou.buttonlabel "Thanks!">
+
+<!ENTITY emeLearnMoreContextMenu.label "Learn more about DRM…">
+<!ENTITY emeLearnMoreContextMenu.accesskey "D">
diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties
new file mode 100644
index 000000000..21e794f08
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -0,0 +1,763 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+nv_timeout=Timed Out
+openFile=Open File
+
+droponhometitle=Set Home Page
+droponhomemsg=Do you want this document to be your new home page?
+droponhomemsgMultiple=Do you want these documents to be your new home pages?
+
+# context menu strings
+
+# LOCALIZATION NOTE (contextMenuSearch): %1$S is the search engine,
+# %2$S is the selection string.
+contextMenuSearch=Search %1$S for “%2$S”
+contextMenuSearch.accesskey=S
+
+# bookmark dialog strings
+
+bookmarkAllTabsDefault=[Folder Name]
+
+xpinstallPromptMessage=%S prevented this site from asking you to install software on your computer.
+xpinstallPromptAllowButton=Allow
+# Accessibility Note:
+# Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
+# See http://www.mozilla.org/access/keyboard/accesskey for details
+xpinstallPromptAllowButton.accesskey=A
+xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
+xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
+xpinstallDisabledButton=Enable
+xpinstallDisabledButton.accesskey=n
+
+# LOCALIZATION NOTE (addonDownloadingAndVerifying):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
+addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying #1 add-ons…
+addonDownloadVerifying=Verifying
+
+addonInstall.unsigned=(Unverified)
+addonInstall.cancelButton.label=Cancel
+addonInstall.cancelButton.accesskey=C
+addonInstall.acceptButton.label=Install
+addonInstall.acceptButton.accesskey=I
+
+# LOCALIZATION NOTE (addonConfirmInstallMessage,addonConfirmInstallUnsigned):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName
+# #2 is the number of add-ons being installed
+addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
+addonConfirmInstallUnsigned.message=Caution: This site would like to install an unverified add-on in #1. Proceed at your own risk.;Caution: This site would like to install #2 unverified add-ons in #1. Proceed at your own risk.
+
+# LOCALIZATION NOTE (addonConfirmInstallSomeUnsigned.message):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName
+# #2 is the total number of add-ons being installed (at least 2)
+addonConfirmInstallSomeUnsigned.message=;Caution: This site would like to install #2 add-ons in #1, some of which are unverified. Proceed at your own risk.
+
+addonwatch.slow=%1$S might be making %2$S run slowly
+addonwatch.disable.label=Disable %S
+addonwatch.ignoreSession.label=Ignore for now
+addonwatch.ignoreSession.accesskey=I
+addonwatch.ignorePerm.label=Ignore permanently
+addonwatch.ignorePerm.accesskey=p
+addonwatch.restart.message=To disable %1$S you must restart %2$S
+addonwatch.restart.label=Restart %S
+addonwatch.restart.accesskey=R
+
+# LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 first add-on's name, #2 number of add-ons, #3 application name
+addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
+addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
+addonInstallRestartButton=Restart Now
+addonInstallRestartButton.accesskey=R
+
+# LOCALIZATION NOTE (addonInstallError-1, addonInstallError-2, addonInstallError-3, addonInstallError-4, addonInstallError-5, addonLocalInstallError-1, addonLocalInstallError-2, addonLocalInstallError-3, addonLocalInstallError-4, addonLocalInstallError-5):
+# %1$S is the application name, %2$S is the add-on name
+addonInstallError-1=The add-on could not be downloaded because of a connection failure.
+addonInstallError-2=The add-on could not be installed because it does not match the add-on %1$S expected.
+addonInstallError-3=The add-on downloaded from this site could not be installed because it appears to be corrupt.
+addonInstallError-4=%2$S could not be installed because %1$S cannot modify the needed file.
+addonInstallError-5=%1$S has prevented this site from installing an unverified add-on.
+addonLocalInstallError-1=This add-on could not be installed because of a filesystem error.
+addonLocalInstallError-2=This add-on could not be installed because it does not match the add-on %1$S expected.
+addonLocalInstallError-3=This add-on could not be installed because it appears to be corrupt.
+addonLocalInstallError-4=%2$S could not be installed because %1$S cannot modify the needed file.
+addonLocalInstallError-5=This add-on could not be installed because it has not been verified.
+
+# LOCALIZATION NOTE (addonInstallErrorIncompatible):
+# %1$S is the application name, %2$S is the application version, %3$S is the add-on name
+addonInstallErrorIncompatible=%3$S could not be installed because it is not compatible with %1$S %2$S.
+
+# LOCALIZATION NOTE (addonInstallErrorBlocklisted): %S is add-on name
+addonInstallErrorBlocklisted=%S could not be installed because it has a high risk of causing stability or security problems.
+
+unsignedAddonsDisabled.message=One or more installed add-ons cannot be verified and have been disabled.
+unsignedAddonsDisabled.learnMore.label=Learn More
+unsignedAddonsDisabled.learnMore.accesskey=L
+
+# LOCALIZATION NOTE (deveditionTheme.name): This should be nearly the brand name for aurora.
+# See browser/branding/aurora/locales/*/brand.properties
+deveditionTheme.name=Developer Edition
+
+# LOCALIZATION NOTE (lwthemeInstallRequest.message): %S will be replaced with
+# the host name of the site.
+lwthemeInstallRequest.message=This site (%S) attempted to install a theme.
+lwthemeInstallRequest.allowButton=Allow
+lwthemeInstallRequest.allowButton.accesskey=a
+
+lwthemePostInstallNotification.message=A new theme has been installed.
+lwthemePostInstallNotification.undoButton=Undo
+lwthemePostInstallNotification.undoButton.accesskey=U
+lwthemePostInstallNotification.manageButton=Manage Themes…
+lwthemePostInstallNotification.manageButton.accesskey=M
+
+# LOCALIZATION NOTE (lwthemeNeedsRestart.message):
+# %S will be replaced with the new theme name.
+lwthemeNeedsRestart.message=%S will be installed after you restart.
+lwthemeNeedsRestart.button=Restart Now
+lwthemeNeedsRestart.accesskey=R
+
+# LOCALIZATION NOTE (popupWarning.message): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName and #2 is the number of pop-ups blocked.
+popupWarning.message=#1 prevented this site from opening a pop-up window.;#1 prevented this site from opening #2 pop-up windows.
+popupWarningButton=Options
+popupWarningButton.accesskey=O
+popupWarningButtonUnix=Preferences
+popupWarningButtonUnix.accesskey=P
+popupAllow=Allow pop-ups for %S
+popupBlock=Block pop-ups for %S
+popupWarningDontShowFromMessage=Don’t show this message when pop-ups are blocked
+popupWarningDontShowFromLocationbar=Don’t show info bar when pop-ups are blocked
+popupShowPopupPrefix=Show ‘%S’
+
+# Bad Content Blocker Doorhanger Notification
+# %S is brandShortName
+badContentBlocked.blocked.message=%S is blocking content on this page.
+badContentBlocked.notblocked.message=%S is not blocking any content on this page.
+
+crashedpluginsMessage.title=The %S plugin has crashed.
+crashedpluginsMessage.reloadButton.label=Reload page
+crashedpluginsMessage.reloadButton.accesskey=R
+crashedpluginsMessage.submitButton.label=Submit a crash report
+crashedpluginsMessage.submitButton.accesskey=S
+crashedpluginsMessage.learnMore=Learn More…
+
+# Keyword fixup messages
+# LOCALIZATION NOTE (keywordURIFixup.message): Used when the user tries to visit
+# a local host page, by the time the DNS request recognizes it, we have already
+# loaded a search page for the given word. An infobar then asks to the user
+# whether he rather wanted to visit the host. %S is the recognized host.
+keywordURIFixup.message=Did you mean to go to %S?
+keywordURIFixup.goTo=Yes, take me to %S
+keywordURIFixup.goTo.accesskey=Y
+keywordURIFixup.dismiss=No thanks
+keywordURIFixup.dismiss.accesskey=N
+
+## Plugin doorhanger strings
+# LOCALIZATION NOTE (pluginActivateNew.message): Used for newly-installed
+# plugins which are not known to be unsafe. %1$S is the plugin name and %2$S
+# is the site domain.
+pluginActivateNew.message=Allow %2$S to run “%1$S”?
+pluginActivateMultiple.message=Allow %S to run plugins?
+pluginActivate.learnMore=Learn More…
+# LOCALIZATION NOTE (pluginActivateOutdated.message, pluginActivateOutdated.label):
+# These strings are used when an unsafe plugin has an update available.
+# %1$S is the plugin name, %2$S is the domain, and %3$S is brandShortName.
+pluginActivateOutdated.message=%3$S has prevented the outdated plugin “%1$S” from running on %2$S.
+pluginActivateOutdated.label=Outdated plugin
+pluginActivate.updateLabel=Update now…
+# LOCALIZATION NOTE (pluginActivateVulnerable.message, pluginActivateVulnerable.label):
+# These strings are used when an unsafe plugin has no update available.
+# %1$S is the plugin name, %2$S is the domain, and %3$S is brandShortName.
+pluginActivateVulnerable.message=%3$S has prevented the unsafe plugin “%1$S” from running on %2$S.
+pluginActivateVulnerable.label=Vulnerable plugin!
+pluginActivate.riskLabel=What’s the risk?
+# LOCALIZATION NOTE (pluginActivateBlocked.message): %1$S is the plugin name, %2$S is brandShortName
+pluginActivateBlocked.message=%2$S has blocked “%1$S” for your protection.
+pluginActivateBlocked.label=Blocked for your protection
+pluginActivateDisabled.message=“%S” is disabled.
+pluginActivateDisabled.label=Disabled
+pluginActivateDisabled.manage=Manage plugins…
+pluginEnabled.message=“%S” is enabled on %S.
+pluginEnabledOutdated.message=Outdated plugin “%S” is enabled on %S.
+pluginEnabledVulnerable.message=Insecure plugin “%S” is enabled on %S.
+pluginInfo.unknownPlugin=Unknown
+
+# LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.dtd
+# LOCALIZATION NOTE (pluginActivateNow.label): This button will enable the
+# plugin in the current session for an short time (about an hour), auto-renewed
+# if the site keeps using the plugin.
+pluginActivateNow.label=Allow Now
+pluginActivateNow.accesskey=N
+# LOCALIZATION NOTE (pluginActivateAlways.label): This button will enable the
+# plugin for a long while (90 days), auto-renewed if the site keeps using the
+# plugin.
+pluginActivateAlways.label=Allow and Remember
+pluginActivateAlways.accesskey=R
+pluginBlockNow.label=Block Plugin
+pluginBlockNow.accesskey=B
+pluginContinue.label=Continue Allowing
+pluginContinue.accesskey=C
+
+# in-page UI
+PluginClickToActivate=Activate %S.
+PluginVulnerableUpdatable=This plugin is vulnerable and should be updated.
+PluginVulnerableNoUpdate=This plugin has security vulnerabilities.
+
+# infobar UI
+pluginContinueBlocking.label=Continue Blocking
+pluginContinueBlocking.accesskey=B
+# LOCALIZATION NOTE (pluginActivateTrigger): Use the unicode ellipsis char, \u2026,
+# or use "..." if \u2026 doesn't suit traditions in your locale.
+pluginActivateTrigger.label=Allow…
+pluginActivateTrigger.accesskey=A
+
+# Sanitize
+# LOCALIZATION NOTE (sanitizeDialog2.everything.title): When "Time range to
+# clear" is set to "Everything", the Clear Recent History dialog's title is
+# changed to this. See UI mockup and comment 11 at bug 480169 -->
+sanitizeDialog2.everything.title=Clear All History
+sanitizeButtonOK=Clear Now
+# LOCALIZATION NOTE (sanitizeButtonClearing): The label for the default
+# button between the user clicking it and the window closing. Indicates the
+# items are being cleared.
+sanitizeButtonClearing=Clearing
+
+# LOCALIZATION NOTE (sanitizeEverythingWarning2): Warning that appears when
+# "Time range to clear" is set to "Everything" in Clear Recent History dialog,
+# provided that the user has not modified the default set of history items to clear.
+sanitizeEverythingWarning2=All history will be cleared.
+# LOCALIZATION NOTE (sanitizeSelectedWarning): Warning that appears when
+# "Time range to clear" is set to "Everything" in Clear Recent History dialog,
+# provided that the user has modified the default set of history items to clear.
+sanitizeSelectedWarning=All selected items will be cleared.
+
+# LOCALIZATION NOTE (downloadAndInstallButton.label): %S is replaced by the
+# version of the update: "Update to 28.0".
+update.downloadAndInstallButton.label=Update to %S
+update.downloadAndInstallButton.accesskey=U
+
+menuOpenAllInTabs.label=Open All in Tabs
+
+# History menu
+menuRestoreAllTabs.label=Restore All Tabs
+# LOCALIZATION NOTE (menuRestoreAllTabsSubview.label): like menuRestoreAllTabs.label,
+# but used in the history subview in the panel UI, so needs to mention these are *closed* tabs.
+menuRestoreAllTabsSubview.label=Restore Closed Tabs
+# LOCALIZATION NOTE (menuRestoreAllWindows, menuUndoCloseWindowLabel, menuUndoCloseWindowSingleTabLabel):
+# see bug 394759
+menuRestoreAllWindows.label=Restore All Windows
+# LOCALIZATION NOTE (menuRestoreAllWindowsSubview.label): like menuRestoreAllWindows.label,
+# but used in the history subview in the panel UI, so needs to mention these are *closed* windows.
+menuRestoreAllWindowsSubview.label=Restore Closed Windows
+# LOCALIZATION NOTE (menuUndoCloseWindowLabel): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 Window Title, #2 Number of tabs
+menuUndoCloseWindowLabel=#1 (and #2 other tab);#1 (and #2 other tabs)
+menuUndoCloseWindowSingleTabLabel=#1
+
+# Unified Back-/Forward Popup
+tabHistory.current=Stay on this page
+tabHistory.goBack=Go back to this page
+tabHistory.goForward=Go forward to this page
+
+# URL Bar
+pasteAndGo.label=Paste & Go
+# LOCALIZATION NOTE(urlbar-zoom-button.label): %S is the current zoom level,
+# %% will be displayed as a single % character (% is commonly used to define
+# format specifiers, so it needs to be escaped).
+urlbar-zoom-button.label = %S%%
+
+# Block autorefresh
+refreshBlocked.goButton=Allow
+refreshBlocked.goButton.accesskey=A
+refreshBlocked.refreshLabel=%S prevented this page from automatically reloading.
+refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page.
+
+# General bookmarks button
+# LOCALIZATION NOTE (bookmarksMenuButton.tooltip):
+# %S is the keyboard shortcut for "Show All Bookmarks"
+bookmarksMenuButton.tooltip=Show your bookmarks (%S)
+# Star button
+starButtonOn.tooltip2=Edit this bookmark (%S)
+starButtonOff.tooltip2=Bookmark this page (%S)
+starButtonOverflowed.label=Bookmark This Page
+starButtonOverflowedStarred.label=Edit This Bookmark
+
+# Downloads button tooltip
+# LOCALIZATION NOTE (downloads.tooltip):
+# %S is the keyboard shortcut for "Downloads"
+downloads.tooltip=Display the progress of ongoing downloads (%S)
+
+# Print button tooltip on OS X
+# LOCALIZATION NOTE (printButton.tooltip):
+# Use the unicode ellipsis char, \u2026,
+# or use "..." if \u2026 doesn't suit traditions in your locale.
+# %S is the keyboard shortcut for "Print"
+printButton.tooltip=Print this page… (%S)
+
+# New Window button tooltip
+# LOCALIZATION NOTE (newWindowButton.tooltip):
+# %S is the keyboard shortcut for "New Window"
+newWindowButton.tooltip=Open a new window (%S)
+
+# New Tab button tooltip
+# LOCALIZATION NOTE (newTabButton.tooltip):
+# %S is the keyboard shortcut for "New Tab"
+newTabButton.tooltip=Open a new tab (%S)
+
+# Offline web applications
+offlineApps.available=This website (%S) is asking to store data on your computer for offline use.
+offlineApps.allow=Allow
+offlineApps.allowAccessKey=A
+offlineApps.never=Never for This Site
+offlineApps.neverAccessKey=e
+offlineApps.notNow=Not Now
+offlineApps.notNowAccessKey=N
+
+offlineApps.usage=This website (%S) is now storing more than %SMB of data on your computer for offline use.
+offlineApps.manageUsage=Show settings
+offlineApps.manageUsageAccessKey=S
+
+identity.identified.verifier=Verified by: %S
+identity.identified.verified_by_you=You have added a security exception for this site.
+identity.identified.state_and_country=%S, %S
+
+identity.icon.tooltip=Show site information
+
+trackingProtection.intro.title=How Tracking Protection works
+# LOCALIZATION NOTE (trackingProtection.intro.description2):
+# %S is brandShortName. This string should match the one from Step 1 of the tour
+# when it starts from the button shown when a new private window is opened.
+trackingProtection.intro.description2=When you see the shield, %S is blocking some parts of the page that could track your browsing activity.
+# LOCALIZATION NOTE (trackingProtection.intro.step1of3): Indicates that the intro panel is step one of three in a tour.
+trackingProtection.intro.step1of3=1 of 3
+trackingProtection.intro.nextButton.label=Next
+
+trackingProtection.icon.activeTooltip=Tracking attempts blocked
+trackingProtection.icon.disabledTooltip=Tracking content detected
+
+# Edit Bookmark UI
+editBookmarkPanel.pageBookmarkedTitle=Page Bookmarked
+editBookmarkPanel.pageBookmarkedDescription=%S will always remember this page for you.
+editBookmarkPanel.bookmarkedRemovedTitle=Bookmark Removed
+editBookmarkPanel.editBookmarkTitle=Edit This Bookmark
+
+# LOCALIZATION NOTE (editBookmark.removeBookmarks.label): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# Replacement for #1 is the number of bookmarks to be removed.
+# If this causes problems with localization you can also do "Remove Bookmarks (#1)"
+# instead of "Remove #1 Bookmarks".
+editBookmark.removeBookmarks.label=Remove Bookmark;Remove #1 Bookmarks
+
+# Post Update Notifications
+pu.notifyButton.label=Details…
+pu.notifyButton.accesskey=D
+# LOCALIZATION NOTE %S will be replaced by the short name of the application.
+puNotifyText=%S has been updated
+puAlertTitle=%S Updated
+puAlertText=Click here for details
+
+# Geolocation UI
+
+# LOCALIZATION NOTE (geolocation.shareLocation geolocation.alwaysShareLocation geolocation.neverShareLocation):
+# If you're having trouble with the word Share, please use Allow and Block in your language.
+geolocation.shareLocation=Share Location
+geolocation.shareLocation.accesskey=a
+geolocation.alwaysShareLocation=Always Share Location
+geolocation.alwaysShareLocation.accesskey=A
+geolocation.neverShareLocation=Never Share Location
+geolocation.neverShareLocation.accesskey=N
+geolocation.shareWithSite2=Would you like to share your location with this site?
+geolocation.shareWithFile2=Would you like to share your location with this file?
+
+webNotifications.receiveForSession=Receive for this session
+webNotifications.receiveForSession.accesskey=s
+webNotifications.alwaysReceive=Always Receive Notifications
+webNotifications.alwaysReceive.accesskey=A
+webNotifications.neverShow=Always Block Notifications
+webNotifications.neverShow.accesskey=N
+webNotifications.receiveFromSite=Would you like to receive notifications from this site?
+# LOCALIZATION NOTE (webNotifications.upgradeTitle): When using native notifications on OS X, the title may be truncated around 32 characters.
+webNotifications.upgradeTitle=Upgraded notifications
+# LOCALIZATION NOTE (webNotifications.upgradeBody): When using native notifications on OS X, the body may be truncated around 100 characters in some views.
+webNotifications.upgradeBody=You can now receive notifications from sites that are not currently loaded. Click to learn more.
+
+# Phishing/Malware Notification Bar.
+# LOCALIZATION NOTE (notADeceptiveSite, notAnAttack)
+# The two button strings will never be shown at the same time, so
+# it's okay for them to have the same access key
+safebrowsing.getMeOutOfHereButton.label=Get me out of here!
+safebrowsing.getMeOutOfHereButton.accessKey=G
+safebrowsing.deceptiveSite=Deceptive Site!
+safebrowsing.notADeceptiveSiteButton.label=This isn’t a deceptive site…
+safebrowsing.notADeceptiveSiteButton.accessKey=D
+safebrowsing.reportedAttackSite=Reported Attack Site!
+safebrowsing.notAnAttackButton.label=This isn’t an attack site…
+safebrowsing.notAnAttackButton.accessKey=A
+safebrowsing.reportedUnwantedSite=Reported Unwanted Software Site!
+
+# Ctrl-Tab
+# LOCALIZATION NOTE (ctrlTab.listAllTabs.label): #1 represents the number
+# of tabs in the current browser window. It will always be 2 at least.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+ctrlTab.listAllTabs.label=;List All #1 Tabs
+
+# LOCALIZATION NOTE (addKeywordTitleAutoFill): %S will be replaced by the page's title
+# Used as the bookmark name when saving a keyword for a search field.
+addKeywordTitleAutoFill=Search %S
+
+extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name=Default
+extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description=The default theme.
+
+# safeModeRestart
+safeModeRestartPromptTitle=Restart with Add-ons Disabled
+safeModeRestartPromptMessage=Are you sure you want to disable all add-ons and restart?
+safeModeRestartButton=Restart
+
+# LOCALIZATION NOTE (browser.menu.showCharacterEncoding): Set to the string
+# "true" (spelled and capitalized exactly that way) to show the "Text
+# Encoding" menu in the main Firefox button on Windows. Any other value will
+# hide it. Regardless of the value of this setting, the "Text Encoding"
+# menu will always be accessible via the "Web Developer" menu.
+# This is not a string to translate; it just controls whether the menu shows
+# up in the Firefox button. If users frequently use the "Text Encoding"
+# menu, set this to "true". Otherwise, you can leave it as "false".
+browser.menu.showCharacterEncoding=false
+
+# Mozilla data reporting notification (Telemetry, Firefox Health Report, etc)
+dataReportingNotification.message = %1$S automatically sends some data to %2$S so that we can improve your experience.
+dataReportingNotification.button.label = Choose What I Share
+dataReportingNotification.button.accessKey = C
+
+# Process hang reporter
+processHang.label = A web page is slowing down your browser. What would you like to do?
+processHang.button_stop.label = Stop It
+processHang.button_stop.accessKey = S
+processHang.button_wait.label = Wait
+processHang.button_wait.accessKey = W
+processHang.button_debug.label = Debug Script
+processHang.button_debug.accessKey = D
+
+# LOCALIZATION NOTE (fullscreenButton.tooltip): %S is the keyboard shortcut for full screen
+fullscreenButton.tooltip=Display the window in full screen (%S)
+
+service.toolbarbutton.label=Services
+service.toolbarbutton.tooltiptext=Services
+
+# LOCALIZATION NOTE (social.install.description): %1$S is the hostname of the social provider, %2$S is brandShortName (e.g. Firefox)
+service.install.description=Would you like to enable services from %1$S to display in your %2$S toolbar and sidebar?
+service.install.ok.label=Enable Services
+service.install.ok.accesskey=E
+
+# LOCALIZATION NOTE (social.markpageMenu.label): %S is the name of the social provider
+social.markpageMenu.label=Save Page to %S
+# LOCALIZATION NOTE (social.marklinkMenu.label): %S is the name of the social provider
+social.marklinkMenu.label=Save Link to %S
+
+# LOCALIZATION NOTE (social.error.message): %1$S is brandShortName (e.g. Firefox), %2$S is the name of the social provider
+social.error.message=%1$S is unable to connect with %2$S right now.
+social.error.tryAgain.label=Try Again
+social.error.tryAgain.accesskey=T
+social.error.closeSidebar.label=Close This Sidebar
+social.error.closeSidebar.accesskey=C
+
+# LOCALIZATION NOTE: %1$S is the label for the toolbar button, %2$S is the associated badge numbering that the social provider may provide.
+social.aria.toolbarButtonBadgeText=%1$S (%2$S)
+
+# LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message,
+# getUserMedia.shareScreen.message, getUserMedia.shareCameraAndMicrophone.message,
+# getUserMedia.shareScreenAndMicrophone.message, getUserMedia.shareCameraAndAudioCapture.message,
+# getUserMedia.shareAudioCapture.message, getUserMedia.shareScreenAndAudioCapture.message):
+# %S is the website origin (e.g. www.mozilla.org)
+getUserMedia.shareCamera.message = Would you like to share your camera with %S?
+getUserMedia.shareMicrophone.message = Would you like to share your microphone with %S?
+getUserMedia.shareScreen.message = Would you like to share your screen with %S?
+getUserMedia.shareCameraAndMicrophone.message = Would you like to share your camera and microphone with %S?
+getUserMedia.shareCameraAndAudioCapture.message = Would you like to share your camera and this tab’s audio with %S?
+getUserMedia.shareScreenAndMicrophone.message = Would you like to share your microphone and screen with %S?
+getUserMedia.shareScreenAndAudioCapture.message = Would you like to share this tab’s audio and your screen with %S?
+getUserMedia.shareAudioCapture.message = Would you like to share this tab’s audio with %S?
+# LOCALIZATION NOTE (getUserMedia.shareScreenWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
+# %S will be the 'learn more' link
+getUserMedia.shareScreenWarning.message = Only share screens with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %S
+# LOCALIZATION NOTE (getUserMedia.shareFirefoxWarning.message): NB: inserted via innerHTML, so please don't use <, > or & in this string.
+# %1$S is brandShortName (eg. Firefox)
+# %2$S will be the 'learn more' link
+getUserMedia.shareFirefoxWarning.message = Only share %1$S with sites you trust. Sharing can allow deceptive sites to browse as you and steal your private data. %2$S
+# LOCALIZATION NOTE(getUserMedia.shareScreen.learnMoreLabel): NB: inserted via innerHTML, so please don't use <, > or & in this string.
+getUserMedia.shareScreen.learnMoreLabel = Learn More
+getUserMedia.selectWindow.label=Window to share:
+getUserMedia.selectWindow.accesskey=W
+getUserMedia.selectScreen.label=Screen to share:
+getUserMedia.selectScreen.accesskey=S
+getUserMedia.selectApplication.label=Application to share:
+getUserMedia.selectApplication.accesskey=A
+getUserMedia.noApplication.label = No Application
+getUserMedia.noScreen.label = No Screen
+getUserMedia.noWindow.label = No Window
+getUserMedia.shareEntireScreen.label = Entire screen
+# LOCALIZATION NOTE (getUserMedia.shareMonitor.label):
+# %S is screen number (digits 1, 2, etc)
+# Example: Screen 1, Screen 2,..
+getUserMedia.shareMonitor.label = Screen %S
+# LOCALIZATION NOTE (getUserMedia.shareApplicationWindowCount.label):
+# Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# Replacement for #1 is the name of the application.
+# Replacement for #2 is the number of windows currently displayed by the application.
+getUserMedia.shareApplicationWindowCount.label=#1 (#2 window);#1 (#2 windows)
+# LOCALIZATION NOTE (getUserMedia.shareSelectedDevices.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# The number of devices can be either one or two.
+getUserMedia.shareSelectedDevices.label = Share Selected Device;Share Selected Devices
+getUserMedia.shareSelectedDevices.accesskey = S
+getUserMedia.shareScreen.label = Share Screen
+getUserMedia.shareApplication.label = Share Selected Application
+getUserMedia.shareWindow.label = Share Selected Window
+getUserMedia.shareSelectedItems.label = Share Selected Items
+getUserMedia.always.label = Always Share
+getUserMedia.always.accesskey = A
+getUserMedia.denyRequest.label = Don’t Share
+getUserMedia.denyRequest.accesskey = D
+getUserMedia.never.label = Never Share
+getUserMedia.never.accesskey = N
+
+getUserMedia.sharingMenu.label = Tabs sharing devices
+getUserMedia.sharingMenu.accesskey = d
+# LOCALIZATION NOTE (getUserMedia.sharingMenuCamera
+# getUserMedia.sharingMenuMicrophone,
+# getUserMedia.sharingMenuAudioCapture,
+# getUserMedia.sharingMenuApplication,
+# getUserMedia.sharingMenuScreen,
+# getUserMedia.sharingMenuWindow,
+# getUserMedia.sharingMenuBrowser,
+# getUserMedia.sharingMenuCameraMicrophone,
+# getUserMedia.sharingMenuCameraMicrophoneApplication,
+# getUserMedia.sharingMenuCameraMicrophoneScreen,
+# getUserMedia.sharingMenuCameraMicrophoneWindow,
+# getUserMedia.sharingMenuCameraMicrophoneBrowser,
+# getUserMedia.sharingMenuCameraAudioCapture,
+# getUserMedia.sharingMenuCameraAudioCaptureApplication,
+# getUserMedia.sharingMenuCameraAudioCaptureScreen,
+# getUserMedia.sharingMenuCameraAudioCaptureWindow,
+# getUserMedia.sharingMenuCameraAudioCaptureBrowser,
+# getUserMedia.sharingMenuCameraApplication,
+# getUserMedia.sharingMenuCameraScreen,
+# getUserMedia.sharingMenuCameraWindow,
+# getUserMedia.sharingMenuCameraBrowser,
+# getUserMedia.sharingMenuMicrophoneApplication,
+# getUserMedia.sharingMenuMicrophoneScreen,
+# getUserMedia.sharingMenuMicrophoneWindow,
+# getUserMedia.sharingMenuMicrophoneBrowser,
+# getUserMedia.sharingMenuAudioCaptureApplication,
+# getUserMedia.sharingMenuAudioCaptureScreen,
+# getUserMedia.sharingMenuAudioCaptureWindow,
+# getUserMedia.sharingMenuAudioCaptureBrowser):
+# %S is the website origin (e.g. www.mozilla.org)
+getUserMedia.sharingMenuCamera = %S (camera)
+getUserMedia.sharingMenuMicrophone = %S (microphone)
+getUserMedia.sharingMenuAudioCapture = %S (tab audio)
+getUserMedia.sharingMenuApplication = %S (application)
+getUserMedia.sharingMenuScreen = %S (screen)
+getUserMedia.sharingMenuWindow = %S (window)
+getUserMedia.sharingMenuBrowser = %S (tab)
+getUserMedia.sharingMenuCameraMicrophone = %S (camera and microphone)
+getUserMedia.sharingMenuCameraMicrophoneApplication = %S (camera, microphone and application)
+getUserMedia.sharingMenuCameraMicrophoneScreen = %S (camera, microphone and screen)
+getUserMedia.sharingMenuCameraMicrophoneWindow = %S (camera, microphone and window)
+getUserMedia.sharingMenuCameraMicrophoneBrowser = %S (camera, microphone and tab)
+getUserMedia.sharingMenuCameraAudioCapture = %S (camera and tab audio)
+getUserMedia.sharingMenuCameraAudioCaptureApplication = %S (camera, tab audio and application)
+getUserMedia.sharingMenuCameraAudioCaptureScreen = %S (camera, tab audio and screen)
+getUserMedia.sharingMenuCameraAudioCaptureWindow = %S (camera, tab audio and window)
+getUserMedia.sharingMenuCameraAudioCaptureBrowser = %S (camera, tab audio and tab)
+getUserMedia.sharingMenuCameraApplication = %S (camera and application)
+getUserMedia.sharingMenuCameraScreen = %S (camera and screen)
+getUserMedia.sharingMenuCameraWindow = %S (camera and window)
+getUserMedia.sharingMenuCameraBrowser = %S (camera and tab)
+getUserMedia.sharingMenuMicrophoneApplication = %S (microphone and application)
+getUserMedia.sharingMenuMicrophoneScreen = %S (microphone and screen)
+getUserMedia.sharingMenuMicrophoneWindow = %S (microphone and window)
+getUserMedia.sharingMenuMicrophoneBrowser = %S (microphone and tab)
+getUserMedia.sharingMenuAudioCaptureApplication = %S (tab audio and application)
+getUserMedia.sharingMenuAudioCaptureScreen = %S (tab audio and screen)
+getUserMedia.sharingMenuAudioCaptureWindow = %S (tab audio and window)
+getUserMedia.sharingMenuAudioCaptureBrowser = %S (tab audio and tab)
+# LOCALIZATION NOTE(getUserMedia.sharingMenuUnknownHost): this is used for the website
+# origin for the sharing menu if no readable origin could be deduced from the URL.
+getUserMedia.sharingMenuUnknownHost = Unknown origin
+
+# LOCALIZATION NOTE(emeNotifications.drmContentPlaying.message2): %S is brandShortName.
+emeNotifications.drmContentPlaying.message2 = Some audio or video on this site uses DRM software, which may limit what %S can let you do with it.
+emeNotifications.drmContentPlaying.button.label = Configure…
+emeNotifications.drmContentPlaying.button.accesskey = C
+
+# LOCALIZATION NOTE(emeNotifications.drmContentDisabled.message): NB: inserted via innerHTML, so please don't use <, > or & in this string. %S will be the 'learn more' link
+emeNotifications.drmContentDisabled.message = You must enable DRM to play some audio or video on this page. %S
+emeNotifications.drmContentDisabled.button.label = Enable DRM
+emeNotifications.drmContentDisabled.button.accesskey = E
+# LOCALIZATION NOTE(emeNotifications.drmContentDisabled.learnMoreLabel): NB: inserted via innerHTML, so please don't use <, > or & in this string.
+emeNotifications.drmContentDisabled.learnMoreLabel = Learn More
+
+# LOCALIZATION NOTE(emeNotifications.drmContentCDMInstalling.message): NB: inserted via innerHTML, so please don't use <, > or & in this string. %S is brandShortName
+emeNotifications.drmContentCDMInstalling.message = %S is installing components needed to play the audio or video on this page. Please try again later.
+
+emeNotifications.unknownDRMSoftware = Unknown
+
+# LOCALIZATION NOTE - %S is brandShortName
+slowStartup.message = %S seems slow… to… start.
+slowStartup.helpButton.label = Learn How to Speed It Up
+slowStartup.helpButton.accesskey = L
+slowStartup.disableNotificationButton.label = Don’t Tell Me Again
+slowStartup.disableNotificationButton.accesskey = A
+
+# LOCALIZATION NOTE - %S is brandShortName
+flashHang.message = %S changed some Adobe Flash settings to improve performance.
+flashHang.helpButton.label = Learn More…
+flashHang.helpButton.accesskey = L
+
+# LOCALIZATION NOTE(customizeTips.tip0): %1$S will be replaced with the text defined
+# in customizeTips.tip0.hint, %2$S will be replaced with brandShortName, %3$S will
+# be replaced with a hyperlink containing the text defined in customizeTips.tip0.learnMore.
+customizeTips.tip0 = %1$S: You can customize %2$S to work the way you do. Simply drag any of the above to the menu or toolbar. %3$S about customizing %2$S.
+customizeTips.tip0.hint = Hint
+customizeTips.tip0.learnMore = Learn more
+
+# LOCALIZATION NOTE (customizeMode.tabTitle): %S is brandShortName
+customizeMode.tabTitle = Customize %S
+
+# LOCALIZATION NOTE(appmenu.*.description, appmenu.*.label): these are used for
+# the appmenu labels and buttons that appear when an update is staged for
+# installation or a background update has failed and a manual download is required.
+# %S is brandShortName
+appmenu.restartNeeded.description = Restart %S to apply updates
+appmenu.updateFailed.description = Background update failed, please download update
+appmenu.restartBrowserButton.label = Restart %S
+appmenu.downloadUpdateButton.label = Download Update
+
+# LOCALIZATION NOTE : FILE Reader View is a feature name and therefore typically used as a proper noun.
+
+readingList.promo.firstUse.readerView.title = Reader View
+readingList.promo.firstUse.readerView.body = Remove clutter so you can focus exactly on what you want to read.
+
+# LOCALIZATION NOTE (appMenuRemoteTabs.mobilePromo.text2):
+# %1$S will be replaced with a link, the text of which is
+# appMenuRemoteTabs.mobilePromo.android and the link will be to
+# https://www.mozilla.org/firefox/android/.
+# %2$S will be replaced with a link, the text of which is
+# appMenuRemoteTabs.mobilePromo.ios
+# and the link will be to https://www.mozilla.org/firefox/ios/.
+appMenuRemoteTabs.mobilePromo.text2 = Download %1$S or %2$S and connect them to your Firefox Account.
+appMenuRemoteTabs.mobilePromo.android = Firefox for Android
+appMenuRemoteTabs.mobilePromo.ios = Firefox for iOS
+
+# LOCALIZATION NOTE (e10s.accessibilityNotice.mainMessage,
+# e10s.accessibilityNotice.enableAndRestart.label,
+# e10s.accessibilityNotice.enableAndRestart.accesskey):
+# These strings are related to the messages we display to offer e10s (Multi-process) to users
+# on the pre-release channels. They won't be used in release but they will likely be used in
+# beta starting from version 41, so it's still useful to have these strings properly localized.
+# %S is brandShortName
+e10s.accessibilityNotice.mainMessage2 = Accessibility support is partially disabled due to compatibility issues with new %S features.
+e10s.accessibilityNotice.acceptButton.label = OK
+e10s.accessibilityNotice.acceptButton.accesskey = O
+e10s.accessibilityNotice.enableAndRestart.label = Enable (Requires Restart)
+e10s.accessibilityNotice.enableAndRestart.accesskey = E
+
+# LOCALIZATION NOTE (userContextPersonal.label,
+# userContextWork.label,
+# userContextShopping.label,
+# userContextBanking.label,
+# userContextNone.label):
+# These strings specify the four predefined contexts included in support of the
+# Contextual Identity / Containers project. Each context is meant to represent
+# the context that the user is in when interacting with the site. Different
+# contexts will store cookies and other information from those sites in
+# different, isolated locations. You can enable the feature by typing
+# about:config in the URL bar and changing privacy.userContext.enabled to true.
+# Once enabled, you can open a new tab in a specific context by clicking
+# File > New Container Tab > (1 of 4 contexts). Once opened, you will see these
+# strings on the right-hand side of the URL bar.
+userContextPersonal.label = Personal
+userContextWork.label = Work
+userContextBanking.label = Banking
+userContextShopping.label = Shopping
+userContextNone.label = No Container
+
+userContextPersonal.accesskey = P
+userContextWork.accesskey = W
+userContextBanking.accesskey = B
+userContextShopping.accesskey = S
+userContextNone.accesskey = N
+
+userContext.aboutPage.label = Manage containers
+userContext.aboutPage.accesskey = O
+
+userContextOpenLink.label = Open Link in New %S Tab
+
+muteTab.label = Mute Tab
+muteTab.accesskey = M
+unmuteTab.label = Unmute Tab
+unmuteTab.accesskey = M
+
+# LOCALIZATION NOTE (weakCryptoOverriding.message): %S is brandShortName
+weakCryptoOverriding.message = %S recommends that you don’t enter your password, credit card and other personal information on this website.
+revokeOverride.label = Don’t Trust This Website
+revokeOverride.accesskey = D
+
+# LOCALIZATION NOTE (certErrorDetails*.label): These are text strings that
+# appear in the about:certerror page, so that the user can copy and send them to
+# the server administrators for troubleshooting.
+certErrorDetailsHSTS.label = HTTP Strict Transport Security: %S
+certErrorDetailsKeyPinning.label = HTTP Public Key Pinning: %S
+certErrorDetailsCertChain.label = Certificate chain:
+
+# LOCALIZATION NOTE (pendingCrashReports2.label): Semi-colon list of plural forms
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of pending crash reports
+pendingCrashReports2.label = You have an unsent crash report;You have #1 unsent crash reports
+pendingCrashReports.viewAll = View
+pendingCrashReports.send = Send
+pendingCrashReports.alwaysSend = Always Send
+
+decoder.noCodecs.button = Learn how
+decoder.noCodecs.accesskey = L
+decoder.noCodecs.message = To play video, you may need to install Microsoft’s Media Feature Pack.
+decoder.noCodecsVista.message = To play video, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
+decoder.noCodecsXP.message = To play video, you may need to enable Adobe’s Primetime Content Decryption Module.
+decoder.noCodecsLinux.message = To play video, you may need to install the required video codecs.
+decoder.noHWAcceleration.message = To improve video quality, you may need to install Microsoft’s Media Feature Pack.
+decoder.noHWAccelerationVista.message = To improve video quality, you may need to install Microsoft’s Platform Update Supplement for Windows Vista.
+decoder.noPulseAudio.message = To play audio, you may need to install the required PulseAudio software.
+decoder.unsupportedLibavcodec.message = libavcodec may be vulnerable or is not supported, and should be updated to play video.
+
+# LOCALIZATION NOTE (captivePortal.infoMessage,
+# captivePortal.infoMessage2):
+# Shown in a notification bar when we detect a captive portal is blocking network access
+# and requires the user to log in before browsing. %1$S is replaced with brandShortName.
+captivePortal.infoMessage = This network may require you to login to use the internet. %1$S has opened the login page for you.
+captivePortal.infoMessage2 = This network may require you to login to use the internet.
+# LOCALIZATION NOTE (captivePortal.showLoginPage):
+# The label for a button shown in the info bar in all tabs except the login page tab.
+# The button shows the portal login page tab when clicked.
+captivePortal.showLoginPage = Show Login Page
+
+permissions.remove.tooltip = Clear this permission and ask again
+
+# LOCALIZATION NOTE (aboutDialog.architecture.*):
+# The sixtyFourBit and thirtyTwoBit strings describe the architecture of the
+# current Firefox build: 32-bit or 64-bit. These strings are used in parentheses
+# between the Firefox version and the "What's new" link in the About dialog,
+# e.g.: "48.0.2 (32-bit) <What's new>" or "51.0a1 (2016-09-05) (64-bit)".
+aboutDialog.architecture.sixtyFourBit = 64-bit
+aboutDialog.architecture.thirtyTwoBit = 32-bit
diff --git a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
new file mode 100644
index 000000000..a467aef69
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -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/.
+
+history-panelmenu.label = History
+# LOCALIZATION NOTE(history-panelmenu.tooltiptext2): %S is the keyboard shortcut
+history-panelmenu.tooltiptext2 = Show your history (%S)
+
+remotetabs-panelmenu.label = Synced Tabs
+remotetabs-panelmenu.tooltiptext2 = Show tabs from other devices
+
+privatebrowsing-button.label = New Private Window
+# LOCALIZATION NOTE(privatebrowsing-button.tooltiptext): %S is the keyboard shortcut
+privatebrowsing-button.tooltiptext = Open a new Private Browsing window (%S)
+
+save-page-button.label = Save Page
+# LOCALIZATION NOTE(save-page-button.tooltiptext3): %S is the keyboard shortcut
+save-page-button.tooltiptext3 = Save this page (%S)
+
+find-button.label = Find
+# LOCALIZATION NOTE(find-button.tooltiptext3): %S is the keyboard shortcut.
+find-button.tooltiptext3 = Find in this page (%S)
+
+open-file-button.label = Open File
+# LOCALIZATION NOTE (open-file-button.tooltiptext3): %S is the keyboard shortcut.
+open-file-button.tooltiptext3 = Open a file (%S)
+
+developer-button.label = Developer
+# LOCALIZATION NOTE(developer-button.tooltiptext): %S is the keyboard shortcut
+developer-button.tooltiptext2 = Open Web developer tools (%S)
+
+sidebar-button.label = Sidebars
+sidebar-button.tooltiptext2 = Show sidebars
+
+add-ons-button.label = Add-ons
+# LOCALIZATION NOTE(add-ons-button.tooltiptext3): %S is the keyboard shortcut
+add-ons-button.tooltiptext3 = Manage your add-ons (%S)
+
+preferences-button.label = Preferences
+preferences-button.tooltiptext2 = Open preferences
+preferences-button.tooltiptext.withshortcut = Open preferences (%S)
+# LOCALIZATION NOTE (preferences-button.labelWin): Windows-only label for Options
+preferences-button.labelWin = Options
+# LOCALIZATION NOTE (preferences-button.tooltipWin): Windows-only tooltip for Options
+preferences-button.tooltipWin2 = Open options
+
+zoom-controls.label = Zoom Controls
+zoom-controls.tooltiptext2 = Zoom controls
+
+zoom-out-button.label = Zoom out
+# LOCALIZATION NOTE(zoom-out-button.tooltiptext2): %S is the keyboard shortcut.
+zoom-out-button.tooltiptext2 = Zoom out (%S)
+
+# LOCALIZATION NOTE(zoom-reset-button.label): %S is the current zoom level,
+# %% will be displayed as a single % character (% is commonly used to define
+# format specifiers, so it needs to be escaped).
+zoom-reset-button.label = %S%%
+# LOCALIZATION NOTE(zoom-reset-button.tooltiptext2): %S is the keyboard shortcut.
+zoom-reset-button.tooltiptext2 = Reset zoom level (%S)
+
+zoom-in-button.label = Zoom in
+# LOCALIZATION NOTE(zoom-in-button.tooltiptext2): %S is the keyboard shortcut.
+zoom-in-button.tooltiptext2 = Zoom in (%S)
+
+edit-controls.label = Edit Controls
+edit-controls.tooltiptext2 = Edit controls
+
+cut-button.label = Cut
+# LOCALIZATION NOTE(cut-button.tooltiptext2): %S is the keyboard shortcut.
+cut-button.tooltiptext2 = Cut (%S)
+
+copy-button.label = Copy
+# LOCALIZATION NOTE(copy-button.tooltiptext2): %S is the keyboard shortcut.
+copy-button.tooltiptext2 = Copy (%S)
+
+paste-button.label = Paste
+# LOCALIZATION NOTE(paste-button.tooltiptext2): %S is the keyboard shortcut.
+paste-button.tooltiptext2 = Paste (%S)
+
+feed-button.label = Subscribe
+feed-button.tooltiptext2 = Subscribe to this page
+
+containers-panelmenu.label = Open Container Tab
+containers-panelmenu.tooltiptext = Open Container Tab
+
+# LOCALIZATION NOTE (characterencoding-button2.label): The \u00ad text at the beginning
+# of the string is used to disable auto hyphenation on the button text when it is displayed
+# in the menu panel.
+characterencoding-button2.label = \u00adText Encoding
+characterencoding-button2.tooltiptext = Show text encoding options
+
+email-link-button.label = Email Link
+email-link-button.tooltiptext3 = Email a link to this page
+
+# LOCALIZATION NOTE(quit-button.tooltiptext.linux2): %1$S is the brand name (e.g. Firefox),
+# %2$S is the keyboard shortcut
+quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
+# LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
+# %2$S is the keyboard shortcut
+quit-button.tooltiptext.mac = Quit %1$S (%2$S)
+
+social-share-button.label = Share This Page
+social-share-button.tooltiptext = Share this page
+
+panic-button.label = Forget
+panic-button.tooltiptext = Forget about some browsing history
+
+# LOCALIZATION NOTE(devtools-webide-button.label, devtools-webide-button.tooltiptext):
+# widget is only visible after WebIDE has been started once (Tools > Web Developers > WebIDE)
+# %S is the keyboard shortcut
+devtools-webide-button2.label = WebIDE
+devtools-webide-button2.tooltiptext = Open WebIDE (%S)
+
+e10s-button.label = New Non-e10s Window
+e10s-button.tooltiptext = Open a new Non-e10s Window
diff --git a/browser/locales/en-US/chrome/browser/downloads/downloads.dtd b/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
new file mode 100644
index 000000000..1217f43e4
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
@@ -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/. -->
+
+<!-- LOCALIZATION NOTE (downloads.title):
+ Used by screen readers to describe the Downloads Panel.
+ -->
+<!ENTITY downloads.title "Downloads">
+
+<!-- LOCALIZATION NOTE (downloadDetails.width):
+ Width of details for a Downloads Panel item (which directly influences the
+ width of the Downloads Panel) expressed using a CSS unit. The longest
+ labels that should fit in the item width are usually those of in-progress
+ downloads and those of blocked downloads.
+
+ A good rule of thumb is to try to determine the longest string possible
+ that an in-progress download could display, and use that value in ch
+ units.
+
+ For example, in English, a long string would be:
+
+ 59 minutes, 59 seconds remaining - 1022 of 1023 KB
+
+ That's 50 characters, so we set the width at 50ch.
+ -->
+<!ENTITY downloadDetails.width "50ch">
+
+<!-- LOCALIZATION NOTE (downloadsSummary.minWidth2):
+ Minimum width for the main description of the downloads summary,
+ which is displayed at the bottom of the Downloads Panel if the
+ number of downloads exceeds the limit that the panel can display.
+
+ A good rule of thumb here is to look at the otherDownloads2 string
+ in downloads.properties, and make a reasonable estimate of its
+ maximum length. For English, this seems like a reasonable limit:
+
+ + 999 other downloads
+
+ that's 21 characters, so we set the minimum width to 21ch.
+ -->
+<!ENTITY downloadsSummary.minWidth2 "21ch">
+
+<!ENTITY cmd.pause.label "Pause">
+<!ENTITY cmd.pause.accesskey "P">
+<!ENTITY cmd.resume.label "Resume">
+<!ENTITY cmd.resume.accesskey "R">
+<!ENTITY cmd.cancel.label "Cancel">
+<!ENTITY cmd.cancel.accesskey "C">
+<!-- LOCALIZATION NOTE (cmd.show.label, cmd.show.accesskey, cmd.showMac.label,
+ cmd.showMac.accesskey):
+ The show and showMac commands are never shown together, thus they can share
+ the same access key (though the two access keys can also be different).
+ -->
+<!ENTITY cmd.show.label "Open Containing Folder">
+<!ENTITY cmd.show.accesskey "F">
+<!ENTITY cmd.showMac.label "Show In Finder">
+<!ENTITY cmd.showMac.accesskey "F">
+<!ENTITY cmd.retry.label "Retry">
+<!ENTITY cmd.goToDownloadPage.label "Go To Download Page">
+<!ENTITY cmd.goToDownloadPage.accesskey "G">
+<!ENTITY cmd.copyDownloadLink.label "Copy Download Link">
+<!ENTITY cmd.copyDownloadLink.accesskey "L">
+<!ENTITY cmd.removeFromHistory.label "Remove From History">
+<!ENTITY cmd.removeFromHistory.accesskey "e">
+<!ENTITY cmd.clearList2.label "Clear Preview Panel">
+<!ENTITY cmd.clearList2.accesskey "a">
+<!ENTITY cmd.clearDownloads.label "Clear Downloads">
+<!ENTITY cmd.clearDownloads.accesskey "D">
+<!-- LOCALIZATION NOTE (cmd.unblock2.label):
+ This command is shown in the context menu when downloads are blocked.
+ -->
+<!ENTITY cmd.unblock2.label "Allow Download">
+<!ENTITY cmd.unblock2.accesskey "o">
+<!-- LOCALIZATION NOTE (cmd.removeFile.label):
+ This is the tooltip of the action button shown when malware is blocked.
+ -->
+<!ENTITY cmd.removeFile.label "Remove File">
+<!-- LOCALIZATION NOTE (cmd.chooseUnblock.tooltip):
+ This is the tooltip of the action button shown when potentially unwanted
+ downloads are blocked. This opens a dialog where the user can choose
+ whether to unblock or remove the download. Removing is the default option.
+ -->
+<!ENTITY cmd.chooseUnblock.label "Remove File or Allow Download">
+<!-- LOCALIZATION NOTE (cmd.chooseOpen.tooltip):
+ This is the tooltip of the action button shown when uncommon downloads are
+ blocked.This opens a dialog where the user can choose whether to open the
+ file or remove the download. Opening is the default option.
+ -->
+<!ENTITY cmd.chooseOpen.label "Open or Remove File">
+
+<!-- LOCALIZATION NOTE (blocked.label):
+ Shown as a tag before the file name for some types of blocked downloads.
+ Note: This string doesn't exist in the UI yet. See bug 1053890.
+ -->
+<!ENTITY blocked.label "BLOCKED">
+
+<!-- LOCALIZATION NOTE (learnMore.label):
+ Shown as a text link for some types of blocked downloads, for example
+ malware, when there is an associated explanation page on the Mozilla site.
+ Note: This string doesn't exist in the UI yet. See bug 1053890.
+ -->
+<!ENTITY learnMore.label "Learn More">
+
+<!-- LOCALIZATION NOTE (downloadsHistory.label, downloadsHistory.accesskey):
+ This string is shown at the bottom of the Downloads Panel when all the
+ downloads fit in the available space, or when there are no downloads in
+ the panel at all.
+ -->
+<!ENTITY downloadsHistory.label "Show All Downloads">
+<!ENTITY downloadsHistory.accesskey "S">
+
+<!ENTITY openDownloadsFolder.label "Open Downloads Folder">
+
+<!ENTITY clearDownloadsButton.label "Clear Downloads">
+<!ENTITY clearDownloadsButton.tooltip "Clears completed, canceled and failed downloads">
+
+<!-- LOCALIZATION NOTE (downloadsListEmpty.label):
+ This string is shown when there are no items in the Downloads view, when it
+ is displayed inside a browser tab.
+ -->
+<!ENTITY downloadsListEmpty.label "There are no downloads.">
+
+<!-- LOCALIZATION NOTE (downloadsPanelEmpty.label):
+ This string is shown when there are no items in the Downloads Panel.
+ -->
+<!ENTITY downloadsPanelEmpty.label "No downloads for this session.">
+
+<!-- LOCALIZATION NOTE (downloadsListNoMatch.label):
+ This string is shown when some search terms are specified, but there are no
+ results in the Downloads view.
+ -->
+<!ENTITY downloadsListNoMatch.label "Could not find any matching downloads.">
diff --git a/browser/locales/en-US/chrome/browser/downloads/downloads.properties b/browser/locales/en-US/chrome/browser/downloads/downloads.properties
new file mode 100644
index 000000000..30885e006
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.properties
@@ -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/.
+
+# LOCALIZATION NOTE (stateStarting):
+# Indicates that the download is starting.
+stateStarting=Starting…
+# LOCALIZATION NOTE (stateScanning):
+# Indicates that an external program is scanning the download for viruses.
+stateScanning=Scanning for viruses…
+# LOCALIZATION NOTE (stateFailed):
+# Indicates that the download failed because of an error.
+stateFailed=Failed
+# LOCALIZATION NOTE (statePaused):
+# Indicates that the download was paused by the user.
+statePaused=Paused
+# LOCALIZATION NOTE (stateCanceled):
+# Indicates that the download was canceled by the user.
+stateCanceled=Canceled
+# LOCALIZATION NOTE (stateBlockedParentalControls):
+# Indicates that the download was blocked by the Parental Controls feature of
+# Windows. "Parental Controls" should be consistently named and capitalized
+# with the display of this feature in Windows. The following article can
+# provide a reference for the translation of "Parental Controls" in various
+# languages:
+# http://windows.microsoft.com/en-US/windows-vista/Set-up-Parental-Controls
+stateBlockedParentalControls=Blocked by Parental Controls
+# LOCALIZATION NOTE (stateBlockedPolicy):
+# Indicates that the download was blocked on Windows because of the "Launching
+# applications and unsafe files" setting of the "security zone" associated with
+# the target site. "Security zone" should be consistently named and capitalized
+# with the display of this feature in Windows. The following article can
+# provide a reference for the translation of "security zone" in various
+# languages:
+# http://support.microsoft.com/kb/174360
+stateBlockedPolicy=Blocked by your security zone policy
+# LOCALIZATION NOTE (stateDirty):
+# Indicates that the download was blocked after scanning.
+stateDirty=Blocked: May contain a virus or spyware
+
+# LOCALIZATION NOTE (blockedMalware, blockedPotentiallyUnwanted,
+# blockedUncommon2):
+# These strings are shown in the panel for some types of blocked downloads, and
+# are immediately followed by the "Learn More" link, thus they must end with a
+# period. You may need to adjust "downloadDetails.width" in "downloads.dtd" if
+# this turns out to be longer than the other existing status strings.
+# Note: These strings don't exist in the UI yet. See bug 1053890.
+blockedMalware=This file contains a virus or malware.
+blockedPotentiallyUnwanted=This file may harm your computer.
+blockedUncommon2=This file is not commonly downloaded.
+
+# LOCALIZATION NOTE (unblockHeaderUnblock, unblockHeaderOpen,
+# unblockTypeMalware, unblockTypePotentiallyUnwanted2,
+# unblockTypeUncommon2, unblockTip2, unblockButtonOpen,
+# unblockButtonUnblock, unblockButtonConfirmBlock):
+# These strings are displayed in the dialog shown when the user asks a blocked
+# download to be unblocked. The severity of the threat is expressed in
+# descending order by the unblockType strings, it is higher for files detected
+# as malware and lower for uncommon downloads.
+unblockHeaderUnblock=Are you sure you want to allow this download?
+unblockHeaderOpen=Are you sure you want to open this file?
+unblockTypeMalware=This file contains a virus or other malware that will harm your computer.
+unblockTypePotentiallyUnwanted2=This file is disguised as a helpful download, but it can make unexpected changes to your programs and settings.
+unblockTypeUncommon2=This file is not commonly downloaded and may not be safe to open. It may contain a virus or make unexpected changes to your programs and settings.
+unblockTip2=You can search for an alternate download source or try again later.
+unblockButtonOpen=Open
+unblockButtonUnblock=Allow download
+unblockButtonConfirmBlock=Remove file
+
+# LOCALIZATION NOTE (sizeWithUnits):
+# %1$S is replaced with the size number, and %2$S with the measurement unit.
+sizeWithUnits=%1$S %2$S
+sizeUnknown=Unknown size
+
+# LOCALIZATION NOTE (shortTimeLeftSeconds, shortTimeLeftMinutes,
+# shortTimeLeftHours, shortTimeLeftDays):
+# These values are displayed in the downloads indicator in the main browser
+# window, where space is available for three characters maximum. %1$S is
+# replaced with the time left for the given measurement unit. Even for days,
+# the value is never longer than two digits.
+shortTimeLeftSeconds=%1$Ss
+shortTimeLeftMinutes=%1$Sm
+shortTimeLeftHours=%1$Sh
+shortTimeLeftDays=%1$Sd
+
+# LOCALIZATION NOTE (statusSeparator, statusSeparatorBeforeNumber):
+# These strings define templates for the separation of different elements in the
+# status line of a download item. As a separator, by default we use the Unicode
+# character U+2014 'EM DASH' (long dash). Examples of status lines include
+# "Canceled - 222.net", "1.1 MB - website2.com", or "Paused - 1.1 MB". Note
+# that we use a wider space after the separator when it is followed by a number,
+# just to avoid visually confusing it with with a minus sign with some fonts.
+# If you use a different separator, this might not be necessary. However, there
+# is usually no need to change the separator or the order of the substitutions,
+# even for right-to-left languages, unless the defaults are not suitable.
+statusSeparator=%1$S \u2014 %2$S
+statusSeparatorBeforeNumber=%1$S \u2014 %2$S
+
+fileExecutableSecurityWarning=“%S” is an executable file. Executable files may contain viruses or other malicious code that could harm your computer. Use caution when opening this file. Are you sure you want to launch “%S”?
+fileExecutableSecurityWarningTitle=Open Executable File?
+fileExecutableSecurityWarningDontAsk=Don’t ask me this again
+
+# LOCALIZATION NOTE (otherDownloads2):
+# This is displayed in an item at the bottom of the Downloads Panel when
+# there are more downloads than can fit in the list in the panel. Use a
+# semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/Localization_and_Plurals
+otherDownloads2=+ %1$S other download; + %1$S other downloads
diff --git a/browser/locales/en-US/chrome/browser/engineManager.properties b/browser/locales/en-US/chrome/browser/engineManager.properties
new file mode 100644
index 000000000..afe48f125
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/engineManager.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/.
+
+duplicateTitle=Duplicate Keyword
+duplicateEngineMsg=You have chosen a keyword that is currently in use by “%S”. Please select another.
+duplicateBookmarkMsg=You have chosen a keyword that is currently in use by a bookmark. Please select another.
diff --git a/browser/locales/en-US/chrome/browser/feeds/subscribe.dtd b/browser/locales/en-US/chrome/browser/feeds/subscribe.dtd
new file mode 100644
index 000000000..9e124196d
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/feeds/subscribe.dtd
@@ -0,0 +1,10 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY feedPage.title
+ "Viewing Feed">
+<!ENTITY feedSubscribeNow
+ "Subscribe Now">
+<!ENTITY feedLiveBookmarks
+ "Live Bookmarks">
diff --git a/browser/locales/en-US/chrome/browser/feeds/subscribe.properties b/browser/locales/en-US/chrome/browser/feeds/subscribe.properties
new file mode 100644
index 000000000..14354843c
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/feeds/subscribe.properties
@@ -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/.
+
+linkTitleTextFormat=Go to %S
+addHandler=Add “%S” (%S) as a Feed Reader?
+addHandlerAddButton=Add Feed Reader
+addHandlerAddButtonAccesskey=A
+handlerRegistered=“%S” is already registered as a Feed Reader
+liveBookmarks=Live Bookmarks
+subscribeNow=Subscribe Now
+chooseApplicationMenuItem=Choose Application…
+chooseApplicationDialogTitle=Choose Application
+alwaysUse=Always use %S to subscribe to feeds
+mediaLabel=Media files
+
+# LOCALIZATION NOTE: The next string is for the size of the enclosed media.
+# e.g. enclosureSizeText : "50.23 MB"
+# %1$S = size (in bytes or megabytes, ...)
+# %2$S = unit of measure (bytes, KB, MB, ...)
+enclosureSizeText=%1$S %2$S
+
+bytes=bytes
+kilobyte=KB
+megabyte=MB
+gigabyte=GB
+
+# LOCALIZATION NOTE: The next three strings explains to the user what they're
+# doing.
+# e.g. alwaysUseForVideoPodcasts : "Always use Miro to subscribe to video podcasts."
+# %S = application to use (Miro, iTunes, ...)
+alwaysUseForFeeds=Always use %S to subscribe to feeds.
+alwaysUseForAudioPodcasts=Always use %S to subscribe to podcasts.
+alwaysUseForVideoPodcasts=Always use %S to subscribe to video podcasts.
+
+subscribeFeedUsing=Subscribe to this feed using
+subscribeAudioPodcastUsing=Subscribe to this podcast using
+subscribeVideoPodcastUsing=Subscribe to this video podcast using
+
+feedSubscriptionFeed1=This is a “feed” of frequently changing content on this site.
+feedSubscriptionAudioPodcast1=This is a “podcast” of frequently changing content on this site.
+feedSubscriptionVideoPodcast1=This is a “video podcast” of frequently changing content on this site.
+
+feedSubscriptionFeed2=You can subscribe to this feed to receive updates when this content changes.
+feedSubscriptionAudioPodcast2=You can subscribe to this podcast to receive updates when this content changes.
+feedSubscriptionVideoPodcast2=You can subscribe to this video podcast to receive updates when this content changes.
+
+# Protocol Handling
+# "Add %appName (%appDomain) as an application for %protocolType links?"
+addProtocolHandler=Add %S (%S) as an application for %S links?
+addProtocolHandlerAddButton=Add Application
+addProtocolHandlerAddButtonAccesskey=A \ No newline at end of file
diff --git a/browser/locales/en-US/chrome/browser/lightweightThemes.properties b/browser/locales/en-US/chrome/browser/lightweightThemes.properties
new file mode 100644
index 000000000..7228c5b0d
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/lightweightThemes.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/.
+
+lightweightThemes.recommended-1.name=A Web Browser Renaissance
+lightweightThemes.recommended-1.description=A Web Browser Renaissance is (C) Sean.Martell. Available under CC-BY-SA. No warranty.
+
+lightweightThemes.recommended-2.name=Space Fantasy
+lightweightThemes.recommended-2.description=Space Fantasy is (C) fx5800p. Available under CC-BY-SA. No warranty.
+
+lightweightThemes.recommended-3.name=Linen Light
+lightweightThemes.recommended-3.description=Linen Light is (C) DVemer. Available under CC-BY-SA. No warranty.
+
+lightweightThemes.recommended-4.name=Pastel Gradient
+lightweightThemes.recommended-4.description=Pastel Gradient is (C) darrinhenein. Available under CC-BY. No warranty.
+
+lightweightThemes.recommended-5.name=Carbon Light
+lightweightThemes.recommended-5.description=Carbon Light is (C) Jaxivo. Available under CC-BY-SA. No warranty.
diff --git a/browser/locales/en-US/chrome/browser/migration/migration.dtd b/browser/locales/en-US/chrome/browser/migration/migration.dtd
new file mode 100644
index 000000000..df6938c51
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/migration/migration.dtd
@@ -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/. -->
+
+
+<!ENTITY migrationWizard.title "Import Wizard">
+
+<!ENTITY importFrom.label "Import Options, Bookmarks, History, Passwords and other data from:">
+<!ENTITY importFromUnix.label "Import Preferences, Bookmarks, History, Passwords and other data from:">
+<!ENTITY importFromBookmarks.label "Import Bookmarks from:">
+
+<!ENTITY importFromIE.label "Microsoft Internet Explorer">
+<!ENTITY importFromIE.accesskey "M">
+<!ENTITY importFromEdge.label "Microsoft Edge">
+<!ENTITY importFromEdge.accesskey "E">
+<!ENTITY importFromNothing.label "Don’t import anything">
+<!ENTITY importFromNothing.accesskey "D">
+<!ENTITY importFromSafari.label "Safari">
+<!ENTITY importFromSafari.accesskey "S">
+<!ENTITY importFromCanary.label "Chrome Canary">
+<!ENTITY importFromCanary.accesskey "n">
+<!ENTITY importFromChrome.label "Chrome">
+<!ENTITY importFromChrome.accesskey "C">
+<!ENTITY importFromChromium.label "Chromium">
+<!ENTITY importFromChromium.accesskey "u">
+<!ENTITY importFromFirefox.label "Firefox">
+<!ENTITY importFromFirefox.accesskey "X">
+<!ENTITY importFrom360se.label "360 Secure Browser">
+<!ENTITY importFrom360se.accesskey "3">
+
+<!ENTITY noMigrationSources.label "No programs that contain bookmarks, history or password data could be found.">
+
+<!ENTITY importSource.title "Import Settings and Data">
+<!ENTITY importItems.title "Items to Import">
+<!ENTITY importItems.label "Select which items to import:">
+
+<!ENTITY migrating.title "Importing…">
+<!ENTITY migrating.label "The following items are currently being imported…">
+
+<!ENTITY selectProfile.title "Select Profile">
+<!ENTITY selectProfile.label "The following profiles are available to import from:">
+
+<!ENTITY done.title "Import Complete">
+<!ENTITY done.label "The following items were successfully imported:">
+
+<!ENTITY closeSourceBrowser.label "Please ensure the selected browser is closed before continuing.">
diff --git a/browser/locales/en-US/chrome/browser/migration/migration.properties b/browser/locales/en-US/chrome/browser/migration/migration.properties
new file mode 100644
index 000000000..65e39c78e
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/migration/migration.properties
@@ -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/.
+
+profileName_format=%S %S
+
+# Browser Specific
+sourceNameIE=Internet Explorer
+sourceNameEdge=Microsoft Edge
+sourceNameSafari=Safari
+sourceNameCanary=Google Chrome Canary
+sourceNameChrome=Google Chrome
+sourceNameChromium=Chromium
+sourceNameFirefox=Mozilla Firefox
+sourceName360se=360 Secure Browser
+
+importedBookmarksFolder=From %S
+
+importedSafariReadingList=Reading List (From Safari)
+importedEdgeReadingList=Reading List (From Edge)
+
+# Import Sources
+# Note: When adding an import source for profile reset, add the string name to
+# resetProfile.js if it should be listed in the reset dialog.
+1_ie=Internet Options
+1_edge=Settings
+1_safari=Preferences
+1_chrome=Preferences
+1_360se=Preferences
+
+2_ie=Cookies
+2_edge=Cookies
+2_safari=Cookies
+2_chrome=Cookies
+2_firefox=Cookies
+2_360se=Cookies
+
+4_ie=Browsing History
+4_edge=Browsing History
+4_safari=Browsing History
+4_chrome=Browsing History
+4_firefox_history_and_bookmarks=Browsing History and Bookmarks
+4_360se=Browsing History
+
+8_ie=Saved Form History
+8_edge=Saved Form History
+8_safari=Saved Form History
+8_chrome=Saved Form History
+8_firefox=Saved Form History
+8_360se=Saved Form History
+
+16_ie=Saved Passwords
+16_edge=Saved Passwords
+16_safari=Saved Passwords
+16_chrome=Saved Passwords
+16_firefox=Saved Passwords
+16_360se=Saved Passwords
+
+32_ie=Favorites
+32_edge=Favorites
+32_safari=Bookmarks
+32_chrome=Bookmarks
+32_360se=Bookmarks
+
+64_ie=Other Data
+64_edge=Other Data
+64_safari=Other Data
+64_chrome=Other Data
+64_firefox_other=Other Data
+64_360se=Other Data
+
+128_firefox=Windows and Tabs
+
+# Automigration undo notification.
+automigration.undo.message = We automatically imported your data from %S. Would you like to keep it?
+automigration.undo.unknownBrowserMessage = We automatically imported your data from another browser. Would you like to keep it?
+automigration.undo.keep.label = Keep
+automigration.undo.keep.accesskey = K
+automigration.undo.dontkeep.label = Don’t Keep
+automigration.undo.dontkeep.accesskey = D
diff --git a/browser/locales/en-US/chrome/browser/newTab.dtd b/browser/locales/en-US/chrome/browser/newTab.dtd
new file mode 100644
index 000000000..392aeb957
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/newTab.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/. -->
+
+<!-- These strings are used in the about:newtab page -->
+<!ENTITY newtab.pageTitle "New Tab">
+<!ENTITY newtab.customize.classic "Show your top sites">
+<!ENTITY newtab.customize.cog.enhanced "Include suggested sites">
+<!ENTITY newtab.customize.cog.title2 "NEW TAB CONTROLS">
+<!ENTITY newtab.customize.cog.learn "Learn about New Tab">
+<!ENTITY newtab.customize.title "Customize your New Tab page">
+<!ENTITY newtab.customize.suggested "Show suggested and your top sites">
+<!ENTITY newtab.customize.topsites "Show your top sites">
+<!ENTITY newtab.customize.blank2 "Show blank page">
+<!ENTITY newtab.undo.removedLabel "Thumbnail removed.">
+<!ENTITY newtab.undo.undoButton "Undo.">
+<!ENTITY newtab.undo.restoreButton "Restore All.">
+<!ENTITY newtab.undo.closeTooltip "Hide">
diff --git a/browser/locales/en-US/chrome/browser/newTab.properties b/browser/locales/en-US/chrome/browser/newTab.properties
new file mode 100644
index 000000000..7b3fe248e
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/newTab.properties
@@ -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/.
+
+newtab.pin=Pin this site at its current position
+newtab.unpin=Unpin this site
+newtab.block=Remove this site
+# LOCALIZATION NOTE(newtab.sponsored.button): This text appears for sponsored
+# and enhanced tiles on the same line as the tile's title, so prefer short
+# strings to avoid overlap. This string should be uppercase.
+newtab.sponsored.button=SPONSORED
+# LOCALIZATION NOTE(newtab.suggested.button): This text appears for sponsored
+# and suggested tiles on the same line as the tile's title, so prefer short
+# strings to avoid overlap. This string should be uppercase.
+newtab.suggested.tag=SUGGESTED
+# LOCALIZATION NOTE(newtab.suggested.button): %1$S will be replaced inline by
+# one of the user's top 100 sites that triggered this suggested tile.
+# This text appears for suggested tiles under the tile's title, so prefer short
+# strings to avoid truncating important text.
+newtab.suggested.button=Suggested for %1$S visitors
+# LOCALIZATION NOTE(newtab.sponsored.explain): %1$S will be replaced inline by
+# the (X) block icon. %2$S will be replaced by an active link using string
+# newtab.learn.link as text.
+newtab.sponsored.explain=This tile is being shown to you on behalf of a Mozilla partner. You can remove it at any time by clicking the %1$S button. %2$S
+# LOCALIZATION NOTE(newtab.sponsored.explain2): %1$S will be replaced inline by
+# the (X) block icon. %2$S will be replaced by an active link using string
+# newtab.learn.link as text.
+newtab.sponsored.explain2=This site is suggested to you on behalf of a Mozilla partner. You can remove it at any time by clicking the %1$S button. %2$S
+# LOCALIZATION NOTE(newtab.suggested.explain): %1$S will be replaced inline by
+# the (X) block icon. %2$S will be replaced by an active link using string
+# newtab.learn.link as text.
+newtab.suggested.explain=This site is suggested to you by Mozilla. You can remove it at any time by clicking the %1$S button. %2$S
+# LOCALIZATION NOTE(newtab.enhanced.explain): %1$S will be replaced inline by
+# the gear icon used to customize the new tab window. %2$S will be replaced by
+# an active link using string newtab.learn.link as text.
+newtab.enhanced.explain=A Mozilla partner has visually enhanced this tile, replacing the screenshot. You can turn off enhanced tiles by clicking the %1$S button for your preferences. %2$S
+newtab.intro1.paragraph1=Now when you open New Tab, you’ll also see sites we think might be interesting to you. Some may be suggested by Mozilla or sponsored by one of our partners.
+# LOCALIZATION NOTE(newtab.intro1.paragraph2): %1$S will be replaced inline by
+# an active link using string newtab.privacy.link as text. %2$S will be replaced
+# inline by the gear icon used to customize the new tab window.
+newtab.intro1.paragraph2=In order to provide this service, some data is automatically sent back to us in accordance with our %1$S. You can turn this off by unchecking the option under the gear icon (%2$S).
+newtab.learn.link=Learn more…
+newtab.privacy.link=Privacy Notice
+newtab.learn.link2=More about New Tab
+newtab.intro.header.update=New Tab got an update!
+newtab.intro.gotit=Got it!
diff --git a/browser/locales/en-US/chrome/browser/pageInfo.dtd b/browser/locales/en-US/chrome/browser/pageInfo.dtd
new file mode 100644
index 000000000..1e861c59c
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/pageInfo.dtd
@@ -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/. -->
+
+<!ENTITY pageInfoWindow.width "600">
+<!ENTITY pageInfoWindow.height "550">
+
+<!ENTITY copy.key "C">
+<!ENTITY copy.label "Copy">
+<!ENTITY copy.accesskey "C">
+<!ENTITY selectall.key "A">
+<!ENTITY selectall.label "Select All">
+<!ENTITY selectall.accesskey "A">
+<!ENTITY closeWindow.key "w">
+
+<!ENTITY generalTab "General">
+<!ENTITY generalTab.accesskey "G">
+<!ENTITY generalTitle "Title:">
+<!ENTITY generalURL "Address:">
+<!ENTITY generalType "Type:">
+<!ENTITY generalMode "Render Mode:">
+<!ENTITY generalSize "Size:">
+<!ENTITY generalReferrer "Referring URL:">
+<!ENTITY generalSource "Cache Source:">
+<!ENTITY generalModified "Modified:">
+<!ENTITY generalEncoding2 "Text Encoding:">
+<!ENTITY generalMetaName "Name">
+<!ENTITY generalMetaContent "Content">
+
+<!ENTITY mediaTab "Media">
+<!ENTITY mediaTab.accesskey "M">
+<!ENTITY mediaLocation "Location:">
+<!ENTITY mediaText "Associated Text:">
+<!ENTITY mediaAltHeader "Alternate Text">
+<!ENTITY mediaAddress "Address">
+<!ENTITY mediaType "Type">
+<!ENTITY mediaSize "Size">
+<!ENTITY mediaCount "Count">
+<!ENTITY mediaDimension "Dimensions:">
+<!ENTITY mediaLongdesc "Long Description:">
+<!ENTITY mediaBlockImage.accesskey "B">
+<!ENTITY mediaSaveAs "Save As…">
+<!ENTITY mediaSaveAs.accesskey "A">
+<!ENTITY mediaSaveAs2.accesskey "e">
+<!ENTITY mediaPreview "Media Preview:">
+
+<!ENTITY feedTab "Feeds">
+<!ENTITY feedTab.accesskey "F">
+<!ENTITY feedSubscribe "Subscribe">
+<!ENTITY feedSubscribe.accesskey "u">
+
+<!ENTITY permTab "Permissions">
+<!ENTITY permTab.accesskey "P">
+<!ENTITY permUseDefault "Use Default">
+<!ENTITY permAskAlways "Always ask">
+<!ENTITY permAllow "Allow">
+<!ENTITY permAllowSession "Allow for Session">
+<!ENTITY permBlock "Block">
+<!ENTITY permissionsFor "Permissions for:">
+<!ENTITY permPlugins "Activate Plugins">
+
+<!ENTITY permClearStorage "Clear Storage">
+<!ENTITY permClearStorage.accesskey "C">
+
+<!ENTITY securityTab "Security">
+<!ENTITY securityTab.accesskey "S">
+<!ENTITY securityView.certView "View Certificate">
+<!ENTITY securityView.accesskey "V">
+<!ENTITY securityView.unknown "Unknown">
+
+
+<!ENTITY securityView.identity.header "Website Identity">
+<!ENTITY securityView.identity.owner "Owner:">
+<!ENTITY securityView.identity.domain "Website:">
+<!ENTITY securityView.identity.verifier "Verified by:">
+
+<!ENTITY securityView.privacy.header "Privacy &amp; History">
+<!ENTITY securityView.privacy.history "Have I visited this website prior to today?">
+<!ENTITY securityView.privacy.cookies "Is this website storing information (cookies) on my computer?">
+<!ENTITY securityView.privacy.viewCookies "View Cookies">
+<!ENTITY securityView.privacy.viewCookies.accessKey "k">
+<!ENTITY securityView.privacy.passwords "Have I saved any passwords for this website?">
+<!ENTITY securityView.privacy.viewPasswords "View Saved Passwords">
+<!ENTITY securityView.privacy.viewPasswords.accessKey "w">
+
+<!ENTITY securityView.technical.header "Technical Details">
+
+<!ENTITY helpButton.label "Help">
diff --git a/browser/locales/en-US/chrome/browser/pageInfo.properties b/browser/locales/en-US/chrome/browser/pageInfo.properties
new file mode 100644
index 000000000..5cb678f4d
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/pageInfo.properties
@@ -0,0 +1,56 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+pageInfo.page.title=Page Info - %S
+pageInfo.frame.title=Frame Info - %S
+
+noPageTitle=Untitled Page:
+unknown=Unknown
+notset=Not specified
+yes=Yes
+no=No
+
+mediaImg=Image
+mediaVideo=Video
+mediaAudio=Audio
+mediaBGImg=Background
+mediaBorderImg=Border
+mediaListImg=Bullet
+mediaCursor=Cursor
+mediaObject=Object
+mediaEmbed=Embed
+mediaLink=Icon
+mediaInput=Input
+mediaFileSize=%S KB
+mediaSize=%Spx \u00D7 %Spx
+mediaSelectFolder=Select a Folder to Save the Images
+mediaBlockImage=Block Images from %S
+mediaUnknownNotCached=Unknown (not cached)
+mediaImageType=%S Image
+mediaAnimatedImageType=%S Image (animated, %S frames)
+mediaDimensions=%Spx \u00D7 %Spx
+mediaDimensionsScaled=%Spx \u00D7 %Spx (scaled to %Spx \u00D7 %Spx)
+
+generalQuirksMode=Quirks mode
+generalStrictMode=Standards compliance mode
+generalSize=%S KB (%S bytes)
+generalMetaTag=Meta (1 tag)
+generalMetaTags=Meta (%S tags)
+
+feedRss=RSS
+feedAtom=Atom
+feedXML=XML
+
+securityNoOwner=This website does not supply ownership information.
+securityOneVisit=Yes, once
+securityNVisits=Yes, %S times
+
+# LOCALIZATION NOTE: The next string is for the disk usage of the
+# database
+# e.g. indexedDBUsage : "50.23 MB"
+# %1$S = size (in bytes or megabytes, ...)
+# %2$S = unit of measure (bytes, KB, MB, ...)
+indexedDBUsage=This website is using %1$S %2$S
+
+permissions.useDefault=Use Default
diff --git a/browser/locales/en-US/chrome/browser/places/bookmarkProperties.properties b/browser/locales/en-US/chrome/browser/places/bookmarkProperties.properties
new file mode 100644
index 000000000..9f818f45b
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/places/bookmarkProperties.properties
@@ -0,0 +1,19 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+dialogAcceptLabelAddItem=Add
+dialogAcceptLabelSaveItem=Save
+dialogAcceptLabelAddLivemark=Subscribe
+dialogAcceptLabelAddMulti=Add Bookmarks
+dialogAcceptLabelEdit=Save
+dialogTitleAddBookmark=New Bookmark
+dialogTitleAddLivemark=Subscribe with Live Bookmark
+dialogTitleAddFolder=New Folder
+dialogTitleAddMulti=New Bookmarks
+dialogTitleEdit=Properties for “%S”
+
+bookmarkAllTabsDefault=[Folder Name]
+newFolderDefault=New Folder
+newBookmarkDefault=New Bookmark
+newLivemarkDefault=New Live Bookmark
diff --git a/browser/locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd b/browser/locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd
new file mode 100644
index 000000000..d78c355c2
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd
@@ -0,0 +1,28 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY editBookmarkOverlay.name.label "Name:">
+<!ENTITY editBookmarkOverlay.name.accesskey "N">
+<!ENTITY editBookmarkOverlay.location.label "Location:">
+<!ENTITY editBookmarkOverlay.location.accesskey "L">
+<!ENTITY editBookmarkOverlay.feedLocation.label "Feed Location:">
+<!ENTITY editBookmarkOverlay.feedLocation.accesskey "F">
+<!ENTITY editBookmarkOverlay.siteLocation.label "Site Location:">
+<!ENTITY editBookmarkOverlay.siteLocation.accesskey "S">
+<!ENTITY editBookmarkOverlay.folder.label "Folder:">
+<!ENTITY editBookmarkOverlay.foldersExpanderDown.tooltip "Show all the bookmarks folders">
+<!ENTITY editBookmarkOverlay.expanderUp.tooltip "Hide">
+<!ENTITY editBookmarkOverlay.tags.label "Tags:">
+<!ENTITY editBookmarkOverlay.tags.accesskey "T">
+<!ENTITY editBookmarkOverlay.tagsEmptyDesc.label "Separate tags with commas">
+<!ENTITY editBookmarkOverlay.description.label "Description:">
+<!ENTITY editBookmarkOverlay.description.accesskey "D">
+<!ENTITY editBookmarkOverlay.keyword.label "Keyword:">
+<!ENTITY editBookmarkOverlay.keyword.accesskey "K">
+<!ENTITY editBookmarkOverlay.tagsExpanderDown.tooltip "Show all tags">
+<!ENTITY editBookmarkOverlay.loadInSidebar.label "Load this bookmark in the sidebar">
+<!ENTITY editBookmarkOverlay.loadInSidebar.accesskey "h">
+<!ENTITY editBookmarkOverlay.choose.label "Choose…">
+<!ENTITY editBookmarkOverlay.newFolderButton.label "New Folder">
+<!ENTITY editBookmarkOverlay.newFolderButton.accesskey "o">
diff --git a/browser/locales/en-US/chrome/browser/places/moveBookmarks.dtd b/browser/locales/en-US/chrome/browser/places/moveBookmarks.dtd
new file mode 100644
index 000000000..9f28b8098
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/places/moveBookmarks.dtd
@@ -0,0 +1,9 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY window.title "Choose Folder">
+<!ENTITY window.style "width: 36em; height: 18em;">
+<!ENTITY moveTo.label "Move to:">
+<!ENTITY newFolderButton.label "New Folder">
+<!ENTITY newFolderButton.accesskey "N">
diff --git a/browser/locales/en-US/chrome/browser/places/places.dtd b/browser/locales/en-US/chrome/browser/places/places.dtd
new file mode 100644
index 000000000..6b2d10b1a
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/places/places.dtd
@@ -0,0 +1,124 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- LOCALIZATION NOTE (places.library.title): use "Library", "Archive" or "Repository" -->
+<!ENTITY places.library.title "Library">
+<!ENTITY places.library.width "700">
+<!ENTITY places.library.height "500">
+<!ENTITY organize.label "Organize">
+<!ENTITY organize.accesskey "O">
+<!ENTITY organize.tooltip "Organize your bookmarks">
+
+<!ENTITY file.close.label "Close">
+<!ENTITY file.close.accesskey "C">
+<!ENTITY cmd.close.key "w">
+<!ENTITY views.label "Views">
+<!ENTITY views.accesskey "V">
+<!ENTITY views.tooltip "Change your view">
+<!ENTITY view.columns.label "Show Columns">
+<!ENTITY view.columns.accesskey "C">
+<!ENTITY view.sort.label "Sort">
+<!ENTITY view.sort.accesskey "S">
+<!ENTITY view.unsorted.label "Unsorted">
+<!ENTITY view.unsorted.accesskey "U">
+<!ENTITY view.sortAscending.label "A > Z Sort Order">
+<!ENTITY view.sortAscending.accesskey "A">
+<!ENTITY view.sortDescending.label "Z > A Sort Order">
+<!ENTITY view.sortDescending.accesskey "Z">
+
+<!ENTITY importBookmarksFromHTML.label "Import Bookmarks from HTML…">
+<!ENTITY importBookmarksFromHTML.accesskey "I">
+<!ENTITY exportBookmarksToHTML.label "Export Bookmarks to HTML…">
+<!ENTITY exportBookmarksToHTML.accesskey "E">
+<!ENTITY importOtherBrowser.label "Import Data from Another Browser…">
+<!ENTITY importOtherBrowser.accesskey "A">
+
+<!ENTITY cmd.backup.label "Backup…">
+<!ENTITY cmd.backup.accesskey "B">
+<!ENTITY cmd.restore2.label "Restore">
+<!ENTITY cmd.restore2.accesskey "R">
+<!ENTITY cmd.restoreFromFile.label "Choose File…">
+<!ENTITY cmd.restoreFromFile.accesskey "C">
+
+<!ENTITY cmd.bookmarkLink.label "Bookmark This Page…">
+<!ENTITY cmd.bookmarkLink.accesskey "B">
+<!ENTITY cmd.delete.label "Delete This Page">
+<!ENTITY cmd.delete.accesskey "D">
+<!ENTITY cmd.deleteDomainData.label "Forget About This Site">
+<!ENTITY cmd.deleteDomainData.accesskey "F">
+
+<!ENTITY cmd.open.label "Open">
+<!ENTITY cmd.open.accesskey "O">
+<!ENTITY cmd.open_window.label "Open in a New Window">
+<!ENTITY cmd.open_window.accesskey "N">
+<!ENTITY cmd.open_private_window.label "Open in a New Private Window">
+<!ENTITY cmd.open_private_window.accesskey "P">
+<!ENTITY cmd.open_tab.label "Open in a New Tab">
+<!ENTITY cmd.open_tab.accesskey "w">
+<!ENTITY cmd.open_all_in_tabs.label "Open All in Tabs">
+<!ENTITY cmd.open_all_in_tabs.accesskey "O">
+
+<!ENTITY cmd.properties.label "Properties">
+<!ENTITY cmd.properties.accesskey "i">
+
+<!ENTITY cmd.sortby_name.label "Sort By Name">
+<!ENTITY cmd.sortby_name.accesskey "S">
+<!ENTITY cmd.context_sortby_name.accesskey "r">
+
+<!ENTITY cmd.new_bookmark.label "New Bookmark…">
+<!ENTITY cmd.new_bookmark.accesskey "B">
+<!ENTITY cmd.new_folder.label "New Folder…">
+<!ENTITY cmd.new_folder.accesskey "o">
+<!ENTITY cmd.context_new_folder.accesskey "F">
+<!ENTITY cmd.new_separator.label "New Separator">
+<!ENTITY cmd.new_separator.accesskey "S">
+
+<!ENTITY cmd.reloadLivebookmark.label "Reload Live Bookmark">
+<!ENTITY cmd.reloadLivebookmark.accesskey "R">
+
+<!ENTITY cmd.moveBookmarks.label "Move…">
+<!ENTITY cmd.moveBookmarks.accesskey "M">
+
+<!ENTITY col.name.label "Name">
+<!ENTITY col.tags.label "Tags">
+<!ENTITY col.url.label "Location">
+<!ENTITY col.mostrecentvisit.label "Most Recent Visit">
+<!ENTITY col.visitcount.label "Visit Count">
+<!ENTITY col.description.label "Description">
+<!ENTITY col.dateadded.label "Added">
+<!ENTITY col.lastmodified.label "Last Modified">
+
+<!ENTITY search.label "Search:">
+<!ENTITY search.accesskey "S">
+
+<!ENTITY cmd.find.key "f">
+
+<!ENTITY maintenance.label "Import and Backup">
+<!ENTITY maintenance.accesskey "I">
+<!ENTITY maintenance.tooltip "Import and backup your bookmarks">
+
+<!ENTITY backButton.tooltip "Go back">
+
+<!ENTITY forwardButton.tooltip "Go forward">
+
+<!ENTITY detailsPane.more.label "More">
+<!ENTITY detailsPane.more.accesskey "e">
+<!ENTITY detailsPane.less.label "Less">
+<!ENTITY detailsPane.less.accesskey "e">
+<!ENTITY detailsPane.selectAnItemText.description "Select an item to view and edit its properties">
+
+<!ENTITY find.label "Search:">
+<!ENTITY find.accesskey "S">
+<!ENTITY view.label "View">
+<!ENTITY view.accesskey "w">
+<!ENTITY byDate.label "By Date">
+<!ENTITY byDate.accesskey "D">
+<!ENTITY bySite.label "By Site">
+<!ENTITY bySite.accesskey "S">
+<!ENTITY byMostVisited.label "By Most Visited">
+<!ENTITY byMostVisited.accesskey "V">
+<!ENTITY byLastVisited.label "By Last Visited">
+<!ENTITY byLastVisited.accesskey "L">
+<!ENTITY byDayAndSite.label "By Date and Site">
+<!ENTITY byDayAndSite.accesskey "t">
diff --git a/browser/locales/en-US/chrome/browser/places/places.properties b/browser/locales/en-US/chrome/browser/places/places.properties
new file mode 100644
index 000000000..f83b095c7
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/places/places.properties
@@ -0,0 +1,92 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+load-js-data-url-error=For security reasons, javascript or data urls cannot be loaded from the history window or sidebar.
+noTitle=(no title)
+
+bookmarksMenuEmptyFolder=(Empty)
+
+bookmarksBackupTitle=Bookmarks backup filename
+
+bookmarksRestoreAlertTitle=Revert Bookmarks
+bookmarksRestoreAlert=This will replace all of your current bookmarks with the backup. Are you sure?
+bookmarksRestoreTitle=Select a bookmarks backup
+bookmarksRestoreFilterName=JSON
+
+bookmarksRestoreFormatError=Unsupported file type.
+bookmarksRestoreParseError=Unable to process the backup file.
+
+bookmarksLivemarkLoading=Live Bookmark loading…
+bookmarksLivemarkFailed=Live Bookmark feed failed to load.
+
+menuOpenLivemarkOrigin.label=Open “%S”
+
+sortByName=Sort ‘%S’ by Name
+sortByNameGeneric=Sort by Name
+# LOCALIZATION NOTE (view.sortBy.1.name.label): sortBy properties are versioned.
+# When any of these changes, all of the properties must be bumped, and the
+# change must be annotated here. Both label and accesskey must be updated.
+# - version 1: changed view.sortBy.1.date.
+view.sortBy.1.name.label=Sort by Name
+view.sortBy.1.name.accesskey=N
+view.sortBy.1.url.label=Sort by Location
+view.sortBy.1.url.accesskey=L
+view.sortBy.1.date.label=Sort by Most Recent Visit
+view.sortBy.1.date.accesskey=V
+view.sortBy.1.visitCount.label=Sort by Visit Count
+view.sortBy.1.visitCount.accesskey=C
+view.sortBy.1.description.label=Sort by Description
+view.sortBy.1.description.accesskey=D
+view.sortBy.1.dateAdded.label=Sort by Added
+view.sortBy.1.dateAdded.accesskey=e
+view.sortBy.1.lastModified.label=Sort by Last Modified
+view.sortBy.1.lastModified.accesskey=M
+view.sortBy.1.tags.label=Sort by Tags
+view.sortBy.1.tags.accesskey=T
+
+searchBookmarks=Search Bookmarks
+searchHistory=Search History
+searchDownloads=Search Downloads
+
+tabs.openWarningTitle=Confirm open
+tabs.openWarningMultipleBranded=You are about to open %S tabs. This might slow down %S while the pages are loading. Are you sure you want to continue?
+tabs.openButtonMultiple=Open tabs
+tabs.openWarningPromptMeBranded=Warn me when opening multiple tabs might slow down %S
+
+SelectImport=Import Bookmarks File
+EnterExport=Export Bookmarks File
+
+detailsPane.noItems=No items
+# LOCALIZATION NOTE (detailsPane.itemsCountLabel): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of items
+# example: 111 items
+detailsPane.itemsCountLabel=One item;#1 items
+
+mostVisitedTitle=Most Visited
+recentTagsTitle=Recent Tags
+
+OrganizerQueryHistory=History
+OrganizerQueryDownloads=Downloads
+OrganizerQueryAllBookmarks=All Bookmarks
+OrganizerQueryTags=Tags
+
+# LOCALIZATION NOTE (tagResultLabel, bookmarkResultLabel, switchtabResultLabel,
+# keywordResultLabel, searchengineResultLabel)
+# Noun used to describe the location bar autocomplete result type
+# to users with screen readers
+# See createResultLabel() in urlbarBindings.xml
+tagResultLabel=Tag
+bookmarkResultLabel=Bookmark
+switchtabResultLabel=Tab
+keywordResultLabel=Keyword
+searchengineResultLabel=Search
+
+
+# LOCALIZATION NOTE (lockPrompt.text)
+# %S will be replaced with the application name.
+lockPrompt.title=Browser Startup Error
+lockPrompt.text=The bookmarks and history system will not be functional because one of %S’s files is in use by another application. Some security software can cause this problem.
+lockPromptInfoButton.label=Learn More
+lockPromptInfoButton.accessKey=L
diff --git a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
new file mode 100644
index 000000000..124c00d84
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -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/. -->
+
+<!-- Note: each tab panel must contain unique accesskeys -->
+
+<!ENTITY generalTab.label "General">
+
+<!ENTITY accessibility.label "Accessibility">
+
+<!ENTITY useCursorNavigation.label "Always use the cursor keys to navigate within pages">
+<!ENTITY useCursorNavigation.accesskey "c">
+<!ENTITY searchStartTyping.label "Search for text when I start typing">
+<!ENTITY searchStartTyping.accesskey "x">
+<!ENTITY blockAutoRefresh.label "Warn me when websites try to redirect or reload the page">
+<!ENTITY blockAutoRefresh.accesskey "b">
+<!ENTITY useOnScreenKeyboard.label "Show a touch keyboard when necessary">
+<!ENTITY useOnScreenKeyboard.accesskey "k">
+
+<!ENTITY browsing.label "Browsing">
+
+<!ENTITY useAutoScroll.label "Use autoscrolling">
+<!ENTITY useAutoScroll.accesskey "a">
+<!ENTITY useSmoothScrolling.label "Use smooth scrolling">
+<!ENTITY useSmoothScrolling.accesskey "m">
+<!ENTITY allowHWAccel.label "Use hardware acceleration when available">
+<!ENTITY allowHWAccel.accesskey "r">
+<!ENTITY checkSpelling.label "Check my spelling as I type">
+<!ENTITY checkSpelling.accesskey "t">
+
+<!ENTITY dataChoicesTab.label "Data Choices">
+
+<!ENTITY healthReportDesc.label "Helps you understand your browser performance and shares data with &vendorShortName; about your browser health">
+<!ENTITY enableHealthReport.label "Enable &brandShortName; Health Report">
+<!ENTITY enableHealthReport.accesskey "R">
+<!ENTITY healthReportLearnMore.label "Learn More">
+
+<!ENTITY telemetryDesc.label "Shares performance, usage, hardware and customization data about your browser with &vendorShortName; to help us make &brandShortName; better">
+<!ENTITY enableTelemetryData.label "Share additional data (i.e., Telemetry)">
+<!ENTITY enableTelemetryData.accesskey "T">
+<!ENTITY telemetryLearnMore.label "Learn More">
+
+<!ENTITY crashReporterDesc2.label "Crash reports help &vendorShortName; fix problems and make your browser more stable and secure">
+<!ENTITY alwaysSubmitCrashReports.label "Allow &brandShortName; to send backlogged crash reports on your behalf">
+<!ENTITY alwaysSubmitCrashReports.accesskey "c">
+<!ENTITY crashReporterLearnMore.label "Learn More">
+
+<!ENTITY networkTab.label "Network">
+
+<!ENTITY connection.label "Connection">
+
+<!ENTITY connectionDesc.label "Configure how &brandShortName; connects to the Internet">
+<!ENTITY connectionSettings.label "Settings…">
+<!ENTITY connectionSettings.accesskey "e">
+
+<!ENTITY httpCache.label "Cached Web Content">
+
+<!ENTITY offlineStorage2.label "Offline Web Content and User Data">
+
+<!-- LOCALIZATION NOTE:
+ The entities limitCacheSizeBefore.label and limitCacheSizeAfter.label appear on a single
+ line in preferences as follows:
+
+ &limitCacheSizeBefore.label [textbox for cache size in MB] &limitCacheSizeAfter.label;
+-->
+<!ENTITY limitCacheSizeBefore.label "Limit cache to">
+<!ENTITY limitCacheSizeBefore.accesskey "L">
+<!ENTITY limitCacheSizeAfter.label "MB of space">
+<!ENTITY clearCacheNow.label "Clear Now">
+<!ENTITY clearCacheNow.accesskey "C">
+<!ENTITY clearOfflineAppCacheNow.label "Clear Now">
+<!ENTITY clearOfflineAppCacheNow.accesskey "N">
+<!ENTITY overrideSmartCacheSize.label "Override automatic cache management">
+<!ENTITY overrideSmartCacheSize.accesskey "O">
+
+<!ENTITY updateTab.label "Update">
+
+<!ENTITY updateApp.label "&brandShortName; updates:">
+<!ENTITY updateAuto1.label "Automatically install updates (recommended: improved security)">
+<!ENTITY updateAuto1.accesskey "A">
+<!ENTITY updateCheck.label "Check for updates, but let me choose whether to install them">
+<!ENTITY updateCheck.accesskey "C">
+<!ENTITY updateManual.label "Never check for updates (not recommended: security risk)">
+<!ENTITY updateManual.accesskey "N">
+
+<!ENTITY updateHistory.label "Show Update History">
+<!ENTITY updateHistory.accesskey "p">
+
+<!ENTITY useService.label "Use a background service to install updates">
+<!ENTITY useService.accesskey "b">
+
+<!ENTITY updateOthers.label "Automatically update:">
+<!ENTITY enableSearchUpdate.label "Search Engines">
+<!ENTITY enableSearchUpdate.accesskey "E">
+
+<!ENTITY offlineNotify.label "Tell me when a website asks to store data for offline use">
+<!ENTITY offlineNotify.accesskey "T">
+<!ENTITY offlineNotifyExceptions.label "Exceptions…">
+<!ENTITY offlineNotifyExceptions.accesskey "x">
+
+<!ENTITY offlineAppsList2.label "The following websites are allowed to store data for offline use:">
+<!ENTITY offlineAppsList.height "7em">
+<!ENTITY offlineAppsListRemove.label "Remove…">
+<!ENTITY offlineAppsListRemove.accesskey "R">
+<!ENTITY offlineAppRemove.confirm "Remove offline data">
+
+<!ENTITY certificateTab.label "Certificates">
+<!ENTITY certSelection.label "Requests">
+<!ENTITY certSelection.description "When a server requests my personal certificate:">
+<!ENTITY certs.auto "Select one automatically">
+<!ENTITY certs.auto.accesskey "S">
+<!ENTITY certs.ask "Ask me every time">
+<!ENTITY certs.ask.accesskey "A">
+<!ENTITY enableOCSP.label "Query OCSP responder servers to confirm the current validity of certificates">
+<!ENTITY enableOCSP.accesskey "Q">
+<!ENTITY viewCerts.label "View Certificates">
+<!ENTITY viewCerts.accesskey "C">
+<!ENTITY viewSecurityDevices.label "Security Devices">
+<!ENTITY viewSecurityDevices.accesskey "D">
diff --git a/browser/locales/en-US/chrome/browser/preferences/applicationManager.dtd b/browser/locales/en-US/chrome/browser/preferences/applicationManager.dtd
new file mode 100644
index 000000000..d2c76e63d
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/applicationManager.dtd
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY appManager.title "Application details">
+<!ENTITY appManager.style "width: 30em; min-height: 20em;">
+<!ENTITY remove.label "Remove">
+<!ENTITY remove.accesskey "R">
diff --git a/browser/locales/en-US/chrome/browser/preferences/applicationManager.properties b/browser/locales/en-US/chrome/browser/preferences/applicationManager.properties
new file mode 100644
index 000000000..335363a66
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/applicationManager.properties
@@ -0,0 +1,14 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE
+# in descriptionApplications, %S will be replaced by one of the 3 following strings
+descriptionApplications=The following applications can be used to handle %S.
+
+handleProtocol=%S links
+handleWebFeeds=Web Feeds
+handleFile=%S content
+
+descriptionWebApp=This web application is hosted at:
+descriptionLocalApp=This application is located at:
diff --git a/browser/locales/en-US/chrome/browser/preferences/applications.dtd b/browser/locales/en-US/chrome/browser/preferences/applications.dtd
new file mode 100644
index 000000000..ea89c3124
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/applications.dtd
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY typeColumn.label "Content Type">
+<!ENTITY typeColumn.accesskey "T">
+
+<!ENTITY actionColumn2.label "Action">
+<!ENTITY actionColumn2.accesskey "A">
+
+<!ENTITY focusSearch1.key "f">
+<!ENTITY focusSearch2.key "k">
+
+<!ENTITY filter.emptytext "Search">
diff --git a/browser/locales/en-US/chrome/browser/preferences/blocklists.dtd b/browser/locales/en-US/chrome/browser/preferences/blocklists.dtd
new file mode 100644
index 000000000..31a2986fe
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/blocklists.dtd
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY window.title "Block Lists">
+<!ENTITY window.width "55em">
+
+<!ENTITY treehead.list.label "List">
+<!ENTITY windowClose.key "w">
+
+<!ENTITY button.cancel.label "Cancel">
+<!ENTITY button.cancel.accesskey "C">
+<!ENTITY button.ok.label "Save Changes">
+<!ENTITY button.ok.accesskey "S">
diff --git a/browser/locales/en-US/chrome/browser/preferences/colors.dtd b/browser/locales/en-US/chrome/browser/preferences/colors.dtd
new file mode 100644
index 000000000..7456f48b5
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/colors.dtd
@@ -0,0 +1,30 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY colorsDialog.title "Colors">
+<!ENTITY window.width "38em">
+<!ENTITY window.macWidth "41em">
+
+<!ENTITY overridePageColors.label "Override the colors specified by the page with my selections above:">
+<!ENTITY overridePageColors.accesskey "O">
+
+<!ENTITY overridePageColors.always.label "Always">
+<!ENTITY overridePageColors.auto.label "Only with High Contrast themes">
+<!ENTITY overridePageColors.never.label "Never">
+
+<!ENTITY color "Text and Background">
+<!ENTITY textColor.label "Text:">
+<!ENTITY textColor.accesskey "T">
+<!ENTITY backgroundColor.label "Background:">
+<!ENTITY backgroundColor.accesskey "B">
+<!ENTITY useSystemColors.label "Use system colors">
+<!ENTITY useSystemColors.accesskey "s">
+
+<!ENTITY underlineLinks.label "Underline links">
+<!ENTITY underlineLinks.accesskey "U">
+<!ENTITY links "Link Colors">
+<!ENTITY linkColor.label "Unvisited Links:">
+<!ENTITY linkColor.accesskey "L">
+<!ENTITY visitedLinkColor.label "Visited Links:">
+<!ENTITY visitedLinkColor.accesskey "V">
diff --git a/browser/locales/en-US/chrome/browser/preferences/connection.dtd b/browser/locales/en-US/chrome/browser/preferences/connection.dtd
new file mode 100644
index 000000000..850964a0f
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/connection.dtd
@@ -0,0 +1,49 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!ENTITY connectionsDialog.title "Connection Settings">
+<!ENTITY window.width2 "49em">
+<!ENTITY window.macWidth2 "44em">
+
+<!ENTITY proxyTitle.label "Configure Proxies to Access the Internet">
+<!ENTITY noProxyTypeRadio.label "No proxy">
+<!ENTITY noProxyTypeRadio.accesskey "y">
+<!ENTITY systemTypeRadio.label "Use system proxy settings">
+<!ENTITY systemTypeRadio.accesskey "u">
+<!ENTITY WPADTypeRadio.label "Auto-detect proxy settings for this network">
+<!ENTITY WPADTypeRadio.accesskey "w">
+<!ENTITY manualTypeRadio.label "Manual proxy configuration:">
+<!ENTITY manualTypeRadio.accesskey "m">
+<!ENTITY autoTypeRadio.label "Automatic proxy configuration URL:">
+<!ENTITY autoTypeRadio.accesskey "A">
+<!ENTITY reload.label "Reload">
+<!ENTITY reload.accesskey "e">
+<!ENTITY ftp.label "FTP Proxy:">
+<!ENTITY ftp.accesskey "F">
+<!ENTITY http.label "HTTP Proxy:">
+<!ENTITY http.accesskey "x">
+<!ENTITY ssl.label "SSL Proxy:">
+<!ENTITY ssl.accesskey "L">
+<!ENTITY socks.label "SOCKS Host:">
+<!ENTITY socks.accesskey "C">
+<!ENTITY socks4.label "SOCKS v4">
+<!ENTITY socks4.accesskey "K">
+<!ENTITY socks5.label "SOCKS v5">
+<!ENTITY socks5.accesskey "v">
+<!ENTITY socksRemoteDNS.label2 "Proxy DNS when using SOCKS v5">
+<!ENTITY socksRemoteDNS.accesskey "d">
+<!ENTITY port.label "Port:">
+<!ENTITY HTTPport.accesskey "P">
+<!ENTITY SSLport.accesskey "o">
+<!ENTITY FTPport.accesskey "r">
+<!ENTITY SOCKSport.accesskey "t">
+<!ENTITY noproxy.label "No Proxy for:">
+<!ENTITY noproxy.accesskey "n">
+<!ENTITY noproxyExplain.label "Example: .mozilla.org, .net.nz, 192.168.1.0/24">
+<!ENTITY shareproxy.label "Use this proxy server for all protocols">
+<!ENTITY shareproxy.accesskey "s">
+<!ENTITY autologinproxy.label "Do not prompt for authentication if password is saved">
+<!ENTITY autologinproxy.accesskey "i">
+<!ENTITY autologinproxy.tooltip "This option silently authenticates you to proxies when you have saved credentials for them. You will be prompted if authentication fails.">
diff --git a/browser/locales/en-US/chrome/browser/preferences/containers.dtd b/browser/locales/en-US/chrome/browser/preferences/containers.dtd
new file mode 100644
index 000000000..58eca1ace
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/containers.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 label.label "Name">
+<!ENTITY addButton.label "Add New Container">
+<!ENTITY addButton.accesskey "A">
+<!-- &#171; is &laquo; however it's not defined in XML -->
+<!ENTITY backLink.label "&#171; Go Back to Privacy">
+
+<!ENTITY window.title "Add New Container">
+<!ENTITY window.width "45em">
+
+<!ENTITY name.label "Name:">
+<!ENTITY name.accesskey "N">
+<!ENTITY icon.label "Icon:">
+<!ENTITY icon.accesskey "I">
+<!ENTITY color.label "Color:">
+<!ENTITY color.accesskey "o">
+<!ENTITY windowClose.key "w">
+
+<!ENTITY button.ok.label "Done">
+<!ENTITY button.ok.accesskey "D">
+
diff --git a/browser/locales/en-US/chrome/browser/preferences/containers.properties b/browser/locales/en-US/chrome/browser/preferences/containers.properties
new file mode 100644
index 000000000..9866a0659
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/containers.properties
@@ -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/.
+
+containers.removeButton = Remove
+containers.preferencesButton = Preferences
+containers.colorHeading = Color:
+containers.labelMinWidth = 4rem
+containers.nameLabel = Name:
+containers.namePlaceholder = Enter a container name
+containers.submitButton = Done
+containers.iconHeading = Icon:
+containers.updateContainerTitle = %S Container Preferences
+
+containers.blue.label = Blue
+containers.turquoise.label = Turquoise
+containers.green.label = Green
+containers.yellow.label = Yellow
+containers.orange.label = Orange
+containers.red.label = Red
+containers.pink.label = Pink
+containers.purple.label = Purple
+
+containers.fingerprint.label = Fingerprint
+containers.briefcase.label = Briefcase
+# LOCALIZATION NOTE (containers.dollar.label)
+# String represents a money sign but currently uses a dollar sign so don't change to local currency
+# See Bug 1291672
+containers.dollar.label = Dollar sign
+containers.cart.label = Shopping cart
+containers.circle.label = Dot
diff --git a/browser/locales/en-US/chrome/browser/preferences/content.dtd b/browser/locales/en-US/chrome/browser/preferences/content.dtd
new file mode 100644
index 000000000..5d58ffa37
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/content.dtd
@@ -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/. -->
+
+<!ENTITY popups.label "Pop-ups">
+
+<!ENTITY blockPopups.label "Block pop-up windows">
+<!ENTITY blockPopups.accesskey "B">
+
+<!ENTITY notificationsPolicy.label "Notifications">
+<!ENTITY notificationsPolicyLearnMore.label "Learn more">
+<!ENTITY notificationsPolicyDesc3.label "Choose which sites are allowed to send you notifications">
+<!ENTITY notificationsPolicyButton.accesskey "h">
+<!ENTITY notificationsPolicyButton.label "Choose…">
+<!ENTITY notificationsDoNotDisturb.label "Do not disturb me">
+<!ENTITY notificationsDoNotDisturb.accesskey "n">
+<!ENTITY notificationsDoNotDisturbDetails.value "No notification will be shown until you restart &brandShortName;">
+
+<!ENTITY popupExceptions.label "Exceptions…">
+<!ENTITY popupExceptions.accesskey "E">
+
+<!ENTITY fontsAndColors.label "Fonts &amp; Colors">
+
+<!ENTITY defaultFont.label "Default font:">
+<!ENTITY defaultFont.accesskey "D">
+<!ENTITY defaultSize.label "Size:">
+<!ENTITY defaultSize.accesskey "S">
+
+<!ENTITY advancedFonts.label "Advanced…">
+<!ENTITY advancedFonts.accesskey "A">
+
+<!ENTITY colors.label "Colors…">
+<!ENTITY colors.accesskey "C">
+
+
+<!ENTITY languages.label "Languages">
+<!ENTITY chooseLanguage.label "Choose your preferred language for displaying pages">
+<!ENTITY chooseButton.label "Choose…">
+<!ENTITY chooseButton.accesskey "o">
+
+<!ENTITY translateWebPages.label "Translate web content">
+<!ENTITY translateWebPages.accesskey "T">
+<!ENTITY translateExceptions.label "Exceptions…">
+<!ENTITY translateExceptions.accesskey "x">
+
+<!-- LOCALIZATION NOTE (translation.options.attribution.beforeLogo,
+ - translation.options.attribution.afterLogo):
+ - These 2 strings are displayed before and after a 'Microsoft Translator'
+ - logo.
+ - The translations for these strings should match the translations in
+ - browser/translation.dtd
+ -->
+<!ENTITY translation.options.attribution.beforeLogo "Translations by">
+<!ENTITY translation.options.attribution.afterLogo "">
+
+<!ENTITY drmContent.label "DRM content">
+
+<!ENTITY playDRMContent.label "Play DRM content">
+<!ENTITY playDRMContent.accesskey "P">
+<!ENTITY playDRMContent.learnMore.label "Learn more">
diff --git a/browser/locales/en-US/chrome/browser/preferences/cookies.dtd b/browser/locales/en-US/chrome/browser/preferences/cookies.dtd
new file mode 100644
index 000000000..9dfafffa5
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/cookies.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 window.width "36em">
+
+<!ENTITY cookiesonsystem.label "The following cookies are stored on your computer:">
+<!ENTITY cookiename.label "Cookie Name">
+<!ENTITY cookiedomain.label "Site">
+<!-- LOCALIZATION NOTE (button.removeSelectedCookies.accesskey):
+ The label associated with this accesskey can be found in
+ preferences.properties as removeSelectedCookies.
+-->
+<!ENTITY button.removeSelectedCookies.accesskey "R">
+<!ENTITY button.removeAllCookies.label "Remove All">
+<!ENTITY button.removeAllCookies.accesskey "A">
+
+<!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 props.container.label "Container:">
+
+<!ENTITY window.title "Cookies">
+<!ENTITY windowClose.key "w">
+<!ENTITY focusSearch1.key "f">
+<!ENTITY focusSearch2.key "k">
+
+<!ENTITY filter.label "Search:">
+<!ENTITY filter.accesskey "S">
+
+<!ENTITY button.close.label "Close">
+<!ENTITY button.close.accesskey "C">
diff --git a/browser/locales/en-US/chrome/browser/preferences/donottrack.dtd b/browser/locales/en-US/chrome/browser/preferences/donottrack.dtd
new file mode 100644
index 000000000..781fb53cd
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/donottrack.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 window.title "Do Not Track">
+<!ENTITY window.width "50em">
+<!ENTITY window.height "10em">
+
+<!ENTITY doNotTrackCheckbox2.label "Always apply Do Not Track">
+<!ENTITY doNotTrackCheckbox2.accesskey "A">
+
+<!ENTITY doNotTrackTPInfo.description "&brandShortName; will send a signal that you don’t want to be tracked whenever Tracking Protection is on.">
+<!ENTITY doNotTrackLearnMore.label "Learn More">
diff --git a/browser/locales/en-US/chrome/browser/preferences/fonts.dtd b/browser/locales/en-US/chrome/browser/preferences/fonts.dtd
new file mode 100644
index 000000000..98d697d35
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/fonts.dtd
@@ -0,0 +1,107 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY fontsDialog.title "Fonts">
+
+<!ENTITY language.label "Fonts for:">
+<!ENTITY language.accesskey "F">
+
+<!ENTITY size.label "Size:">
+<!ENTITY sizeProportional.accesskey "z">
+<!ENTITY sizeMonospace.accesskey "e">
+
+<!ENTITY proportional.label "Proportional:">
+<!ENTITY proportional.accesskey "P">
+
+<!ENTITY serif.label "Serif:">
+<!ENTITY serif.accesskey "S">
+<!ENTITY sans-serif.label "Sans-serif:">
+<!ENTITY sans-serif.accesskey "n">
+<!ENTITY monospace.label "Monospace:">
+<!ENTITY monospace.accesskey "M">
+
+<!-- LOCALIZATION NOTE (font.langGroup.latin) :
+ Translate "Latin" as the name of Latin (Roman) script, not as the name of the Latin language. -->
+<!ENTITY font.langGroup.latin "Latin">
+<!ENTITY font.langGroup.japanese "Japanese">
+<!ENTITY font.langGroup.trad-chinese "Traditional Chinese (Taiwan)">
+<!ENTITY font.langGroup.simpl-chinese "Simplified Chinese">
+<!ENTITY font.langGroup.trad-chinese-hk "Traditional Chinese (Hong Kong)">
+<!ENTITY font.langGroup.korean "Korean">
+<!ENTITY font.langGroup.cyrillic "Cyrillic">
+<!ENTITY font.langGroup.el "Greek">
+<!ENTITY font.langGroup.other "Other Writing Systems">
+<!ENTITY font.langGroup.thai "Thai">
+<!ENTITY font.langGroup.hebrew "Hebrew">
+<!ENTITY font.langGroup.arabic "Arabic">
+<!ENTITY font.langGroup.devanagari "Devanagari">
+<!ENTITY font.langGroup.tamil "Tamil">
+<!ENTITY font.langGroup.armenian "Armenian">
+<!ENTITY font.langGroup.bengali "Bengali">
+<!ENTITY font.langGroup.canadian "Unified Canadian Syllabary">
+<!ENTITY font.langGroup.ethiopic "Ethiopic">
+<!ENTITY font.langGroup.georgian "Georgian">
+<!ENTITY font.langGroup.gujarati "Gujarati">
+<!ENTITY font.langGroup.gurmukhi "Gurmukhi">
+<!ENTITY font.langGroup.khmer "Khmer">
+<!ENTITY font.langGroup.malayalam "Malayalam">
+<!ENTITY font.langGroup.math "Mathematics">
+<!ENTITY font.langGroup.odia "Odia">
+<!ENTITY font.langGroup.telugu "Telugu">
+<!ENTITY font.langGroup.kannada "Kannada">
+<!ENTITY font.langGroup.sinhala "Sinhala">
+<!ENTITY font.langGroup.tibetan "Tibetan">
+<!-- Minimum font size -->
+<!ENTITY minSize.label "Minimum font size:">
+<!ENTITY minSize.accesskey "o">
+<!ENTITY minSize.none "None">
+
+<!-- default font type -->
+<!ENTITY useDefaultFontSerif.label "Serif">
+<!ENTITY useDefaultFontSansSerif.label "Sans Serif">
+
+<!ENTITY allowPagesToUse.label "Allow pages to choose their own fonts, instead of my selections above">
+<!ENTITY allowPagesToUse.accesskey "A">
+
+<!ENTITY languages.customize.Fallback2.grouplabel "Text Encoding for Legacy Content">
+<!ENTITY languages.customize.Fallback2.label "Fallback Text Encoding:">
+<!ENTITY languages.customize.Fallback2.accesskey "T">
+<!ENTITY languages.customize.Fallback2.desc "This text encoding is used for legacy content that fails to declare its encoding.">
+
+<!ENTITY languages.customize.Fallback.auto "Default for Current Locale">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.arabic):
+ Translate "Arabic" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.arabic "Arabic">
+<!ENTITY languages.customize.Fallback.baltic "Baltic">
+<!ENTITY languages.customize.Fallback.ceiso "Central European, ISO">
+<!ENTITY languages.customize.Fallback.cewindows "Central European, Microsoft">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.simplified):
+ Translate "Chinese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.simplified "Chinese, Simplified">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.traditional):
+ Translate "Chinese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.traditional "Chinese, Traditional">
+<!ENTITY languages.customize.Fallback.cyrillic "Cyrillic">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.greek):
+ Translate "Greek" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.greek "Greek">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.hebrew):
+ Translate "Hebrew" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.hebrew "Hebrew">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.japanese):
+ Translate "Japanese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.japanese "Japanese">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.korean):
+ Translate "Korean" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.korean "Korean">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.thai):
+ Translate "Thai" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.thai "Thai">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.turkish):
+ Translate "Turkish" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.turkish "Turkish">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.vietnamese):
+ Translate "Vietnamese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.vietnamese "Vietnamese">
+<!ENTITY languages.customize.Fallback.other "Other (incl. Western European)">
diff --git a/browser/locales/en-US/chrome/browser/preferences/languages.dtd b/browser/locales/en-US/chrome/browser/preferences/languages.dtd
new file mode 100644
index 000000000..ba5b11eb5
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/languages.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 window.width "30em">
+
+<!ENTITY languages.customize.Header "Languages">
+<!ENTITY languages.customize.description "Web pages are sometimes offered in more than one language. Choose languages for displaying these web pages, in order of preference:">
+<!ENTITY languages.customize.moveUp.label "Move Up">
+<!ENTITY languages.customize.moveUp.accesskey "U">
+<!ENTITY languages.customize.moveDown.label "Move Down">
+<!ENTITY languages.customize.moveDown.accesskey "D">
+<!ENTITY languages.customize.deleteButton.label "Remove">
+<!ENTITY languages.customize.deleteButton.accesskey "R">
+<!ENTITY languages.customize.selectLanguage.label "Select a language to add…">
+<!ENTITY languages.customize.addButton.label "Add">
+<!ENTITY languages.customize.addButton.accesskey "A">
+
diff --git a/browser/locales/en-US/chrome/browser/preferences/main.dtd b/browser/locales/en-US/chrome/browser/preferences/main.dtd
new file mode 100644
index 000000000..6b56f3400
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/main.dtd
@@ -0,0 +1,45 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY startup.label "Startup">
+
+<!ENTITY startupPage.label "When &brandShortName; starts:">
+<!ENTITY startupPage.accesskey "s">
+<!ENTITY startupHomePage.label "Show my home page">
+<!ENTITY startupBlankPage.label "Show a blank page">
+<!ENTITY startupLastSession.label "Show my windows and tabs from last time">
+
+<!ENTITY homepage.label "Home Page:">
+<!ENTITY homepage.accesskey "P">
+<!ENTITY useCurrentPage.label "Use Current Page">
+<!ENTITY useCurrentPage.accesskey "C">
+<!ENTITY useMultiple.label "Use Current Pages">
+<!ENTITY chooseBookmark.label "Use Bookmark…">
+<!ENTITY chooseBookmark.accesskey "B">
+<!ENTITY restoreDefault.label "Restore to Default">
+<!ENTITY restoreDefault.accesskey "R">
+
+<!ENTITY downloads.label "Downloads">
+
+<!ENTITY saveTo.label "Save files to">
+<!ENTITY saveTo.accesskey "v">
+<!ENTITY chooseFolderWin.label "Browse…">
+<!ENTITY chooseFolderWin.accesskey "o">
+<!ENTITY chooseFolderMac.label "Choose…">
+<!ENTITY chooseFolderMac.accesskey "e">
+<!ENTITY alwaysAsk.label "Always ask me where to save files">
+<!ENTITY alwaysAsk.accesskey "A">
+
+<!ENTITY alwaysCheckDefault2.label "Always check if &brandShortName; is your default browser">
+<!ENTITY alwaysCheckDefault2.accesskey "y">
+<!ENTITY setAsMyDefaultBrowser2.label "Make Default">
+<!ENTITY setAsMyDefaultBrowser2.accesskey "D">
+<!ENTITY isDefault.label "&brandShortName; is currently your default browser">
+<!ENTITY isNotDefault.label "&brandShortName; is not your default browser">
+
+<!ENTITY separateProfileMode.label "Allow &brandShortName; and Firefox to run at the same time">
+<!ENTITY useFirefoxSync.label "Tip: This uses separate profiles. Use Sync to share data between them.">
+<!ENTITY getStarted.label "Start using Sync…">
+
+<!ENTITY e10sEnabled.label "Enable multi-process &brandShortName;">
diff --git a/browser/locales/en-US/chrome/browser/preferences/permissions.dtd b/browser/locales/en-US/chrome/browser/preferences/permissions.dtd
new file mode 100644
index 000000000..e61228b76
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/permissions.dtd
@@ -0,0 +1,28 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY window.title "Exceptions">
+<!ENTITY window.width "45em">
+
+<!ENTITY treehead.sitename.label "Site">
+<!ENTITY treehead.status.label "Status">
+<!ENTITY removepermission.label "Remove Site">
+<!ENTITY removepermission.accesskey "R">
+<!ENTITY removeallpermissions.label "Remove All Sites">
+<!ENTITY removeallpermissions.accesskey "e">
+<!ENTITY address.label "Address of website:">
+<!ENTITY address.accesskey "d">
+<!ENTITY block.label "Block">
+<!ENTITY block.accesskey "B">
+<!ENTITY session.label "Allow for Session">
+<!ENTITY session.accesskey "S">
+<!ENTITY allow.label "Allow">
+<!ENTITY allow.accesskey "A">
+<!ENTITY windowClose.key "w">
+
+<!ENTITY button.cancel.label "Cancel">
+<!ENTITY button.cancel.accesskey "C">
+<!ENTITY button.ok.label "Save Changes">
+<!ENTITY button.ok.accesskey "S">
+
diff --git a/browser/locales/en-US/chrome/browser/preferences/preferences.dtd b/browser/locales/en-US/chrome/browser/preferences/preferences.dtd
new file mode 100644
index 000000000..380da7178
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.dtd
@@ -0,0 +1,29 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!ENTITY prefWindow.titleWin "Options">
+<!ENTITY prefWindow.title "Preferences">
+<!-- LOCALIZATION NOTE (prefWindow.titleGNOME): This is not used for in-content preferences -->
+<!ENTITY prefWindow.titleGNOME "&brandShortName; Preferences">
+<!-- When making changes to prefWindow.styleWin test both Windows Classic and
+ Luna since widget heights are different based on the OS theme -->
+<!ENTITY prefWinMinSize.styleWin2 "width: 42em; min-height: 37.5em;">
+<!ENTITY prefWinMinSize.styleMac "width: 47em; min-height: 40em;">
+<!ENTITY prefWinMinSize.styleGNOME "width: 45.5em; min-height: 40.5em;">
+
+<!ENTITY paneGeneral.title "General">
+<!ENTITY paneTabs.title "Tabs">
+<!ENTITY paneSearch.title "Search">
+<!ENTITY paneContent.title "Content">
+<!ENTITY paneApplications.title "Applications">
+<!ENTITY panePrivacy.title "Privacy">
+<!ENTITY paneContainers.title "Container Tabs">
+<!ENTITY paneSecurity.title "Security">
+<!ENTITY paneAdvanced.title "Advanced">
+
+<!-- LOCALIZATION NOTE (paneSync.title): This should match syncBrand.shortName.label in ../syncBrand.dtd -->
+<!ENTITY paneSync.title "Sync">
+
+<!ENTITY helpButton.label "Help">
diff --git a/browser/locales/en-US/chrome/browser/preferences/preferences.properties b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
new file mode 100644
index 000000000..bf9ac602b
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -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/.
+
+#### Security
+
+# LOCALIZATION NOTE: phishBefore uses %S to represent the name of the provider
+# whose privacy policy must be accepted (for enabling
+# check-every-page-as-I-load-it phishing protection).
+phishBeforeText=Selecting this option will send the address of web pages you are viewing to %S. To continue, please review and accept the following terms of service.
+
+#### Fonts
+
+labelDefaultFont=Default (%S)
+
+veryLargeMinimumFontTitle=Large minimum font size
+veryLargeMinimumFontWarning=You have selected a very large minimum font size (more than 24 pixels). This may make it difficult or impossible to use some important configuration pages like this one.
+acceptVeryLargeMinimumFont=Keep my changes anyway
+
+#### Permissions Manager
+
+trackingprotectionpermissionstext=You have disabled Tracking Protection on these sites.
+trackingprotectionpermissionstitle=Exceptions - Tracking Protection
+cookiepermissionstext=You can specify which websites are always or never allowed to use cookies. Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow.
+cookiepermissionstitle=Exceptions - Cookies
+addonspermissionstext=You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow.
+addons_permissions_title=Allowed Sites - Add-ons Installation
+popuppermissionstext=You can specify which websites are allowed to open pop-up windows. Type the exact address of the site you want to allow and then click Allow.
+popuppermissionstitle=Allowed Sites - Pop-ups
+notificationspermissionstext4=Control which websites are always or never allowed to send you notifications. If you remove a site, it will need to request permission again.
+notificationspermissionstitle=Notification Permissions
+invalidURI=Please enter a valid hostname
+invalidURITitle=Invalid Hostname Entered
+savedLoginsExceptions_title=Exceptions - Saved Logins
+savedLoginsExceptions_desc=Logins for the following sites will not be saved:
+
+#### Block List Manager
+
+blockliststext=You can choose which list Firefox will use to block Web elements that may track your browsing activity.
+blockliststitle=Block Lists
+# LOCALIZATION NOTE (mozNameTemplate): This template constructs the name of the
+# block list in the block lists dialog. It combines the list name and
+# description.
+# e.g. mozNameTemplate : "Standard (Recommended). This list does a pretty good job."
+# %1$S = list name (fooName), %2$S = list descriptive text (fooDesc)
+mozNameTemplate=%1$S %2$S
+# LOCALIZATION NOTE (mozstdName, etc.): These labels appear in the tracking
+# protection block lists dialog, mozNameTemplate is used to create the final
+# string. Note that in the future these two strings (name, desc) could be
+# displayed on two different lines.
+mozstdName=Disconnect.me basic protection (Recommended).
+mozstdDesc=Allows some trackers so websites function properly.
+mozfullName=Disconnect.me strict protection.
+mozfullDesc=Blocks known trackers. Some sites may not function properly.
+# LOCALIZATION NOTE (blocklistChangeRequiresRestart): %S = brandShortName
+blocklistChangeRequiresRestart=%S must restart to change block lists.
+
+#### Master Password
+
+pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
+pw_change_failed_title=Password Change Failed
+
+#### Fonts
+
+# LOCALIZATION NOTE: Next two strings are for language name representations with
+# and without the region.
+# e.g. languageRegionCodeFormat : "French/Canada [fr-ca]" languageCodeFormat : "French [fr]"
+# %1$S = language name, %2$S = region name, %3$S = language-region code
+languageRegionCodeFormat=%1$S/%2$S [%3$S]
+# %1$S = language name, %2$S = language-region code
+languageCodeFormat=%1$S [%2$S]
+
+#### Downloads
+
+desktopFolderName=Desktop
+downloadsFolderName=Downloads
+chooseDownloadFolderTitle=Choose Download Folder:
+
+#### Applications
+
+fileEnding=%S file
+saveFile=Save File
+
+# LOCALIZATION NOTE (useApp, useDefault): %S = Application name
+useApp=Use %S
+useDefault=Use %S (default)
+
+useOtherApp=Use other…
+fpTitleChooseApp=Select Helper Application
+manageApp=Application Details…
+webFeed=Web Feed
+videoPodcastFeed=Video Podcast
+audioPodcastFeed=Podcast
+alwaysAsk=Always ask
+portableDocumentFormat=Portable Document Format (PDF)
+
+# LOCALIZATION NOTE (usePluginIn):
+# %1$S = plugin name (for example "QuickTime Plugin-in 7.2")
+# %2$S = brandShortName from brand.properties (for example "Minefield")
+usePluginIn=Use %S (in %S)
+
+# LOCALIZATION NOTE (previewInApp, addLiveBookmarksInApp): %S = brandShortName
+previewInApp=Preview in %S
+addLiveBookmarksInApp=Add Live Bookmarks in %S
+
+# LOCALIZATION NOTE (typeDescriptionWithType):
+# %1$S = type description (for example "Portable Document Format")
+# %2$S = type (for example "application/pdf")
+typeDescriptionWithType=%S (%S)
+
+
+#### Cookie Viewer
+
+hostColon=Host:
+domainColon=Domain:
+forSecureOnly=Encrypted connections only
+forAnyConnection=Any type of connection
+expireAtEndOfSession=At end of session
+can=Allow
+canAccessFirstParty=Allow first party only
+canSession=Allow for Session
+cannot=Block
+noCookieSelected=<no cookie selected>
+cookiesAll=The following cookies are stored on your computer:
+cookiesFiltered=The following cookies match your search:
+# LOCALIZATION NOTE (removeSelectedCookies):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# If you need to display the number of selected elements in your language,
+# you can use #1 in your localization as a placeholder for the number.
+# For example this is the English string with numbers:
+# removeSelectedCookied=Remove #1 Selected;Remove #1 Selected
+removeSelectedCookies=Remove Selected;Remove Selected
+defaultUserContextLabel=None
+
+#### Offline apps
+offlineAppsList.height=7em
+offlineAppRemoveTitle=Remove offline website data
+offlineAppRemovePrompt=After removing this data, %S will not be available offline. Are you sure you want to remove this offline website?
+offlineAppRemoveConfirm=Remove offline data
+
+# LOCALIZATION NOTE: The next string is for the disk usage of the
+# offline application
+# e.g. offlineAppUsage : "50.23 MB"
+# %1$S = size (in bytes or megabytes, ...)
+# %2$S = unit of measure (bytes, KB, MB, ...)
+offlineAppUsage=%1$S %2$S
+
+offlinepermissionstext=The following websites are not allowed to store data for offline use:
+offlinepermissionstitle=Offline Data
+
+####Preferences::Advanced::Network
+#LOCALIZATION NOTE: The next string is for the disk usage of the web content cache.
+# e.g., "Your web content cache is currently using 200 MB"
+# %1$S = size
+# %2$S = unit (MB, KB, etc.)
+actualDiskCacheSize=Your web content cache is currently using %1$S %2$S of disk space
+actualDiskCacheSizeCalculated=Calculating web content cache size…
+
+####Preferences::Advanced::Network
+#LOCALIZATION NOTE: The next string is for the disk usage of the application cache.
+# e.g., "Your application cache is currently using 200 MB"
+# %1$S = size
+# %2$S = unit (MB, KB, etc.)
+actualAppCacheSize=Your application cache is currently using %1$S %2$S of disk space
+
+syncUnlink.title=Do you want to unlink your device?
+syncUnlink.label=This device will no longer be associated with your Sync account. All of your personal data, both on this device and in your Sync account, will remain intact.
+syncUnlinkConfirm.label=Unlink
+
+# LOCALIZATION NOTE (featureEnableRequiresRestart, featureDisableRequiresRestart, restartTitle): %S = brandShortName
+featureEnableRequiresRestart=%S must restart to enable this feature.
+featureDisableRequiresRestart=%S must restart to disable this feature.
+shouldRestartTitle=Restart %S
+okToRestartButton=Restart %S now
+revertNoRestartButton=Revert
+
+restartNow=Restart Now
+restartLater=Restart Later
+
+disableContainersAlertTitle=Close All Container Tabs?
+
+# LOCALIZATION NOTE (disableContainersMsg): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #S is the number of container tabs
+disableContainersMsg=If you disable Container Tabs now, #S container tab will be closed. Are you sure you want to disable Container Tabs?;If you disable Container Tabs now, #S container tabs will be closed. Are you sure you want to disable Container Tabs?
+
+# LOCALIZATION NOTE (disableContainersOkButton): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #S is the number of container tabs
+disableContainersOkButton=Close #S Container Tab;Close #S Container Tabs
+
+disableContainersButton2=Keep enabled
diff --git a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
new file mode 100644
index 000000000..4c38c111a
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -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/. -->
+
+<!ENTITY trackingProtectionHeader.label "Use Tracking Protection">
+<!ENTITY trackingProtectionAlways.label "Always">
+<!ENTITY trackingProtectionAlways.accesskey "y">
+<!ENTITY trackingProtectionPrivate.label "Only in private windows">
+<!ENTITY trackingProtectionPrivate.accesskey "l">
+<!ENTITY trackingProtectionNever.label "Never">
+<!ENTITY trackingProtectionNever.accesskey "n">
+<!ENTITY trackingProtectionLearnMore.label "Learn more">
+<!ENTITY trackingProtectionExceptions.label "Exceptions…">
+<!ENTITY trackingProtectionExceptions.accesskey "x">
+
+<!ENTITY tracking.label "Tracking">
+<!ENTITY trackingProtectionPBM5.label "Use Tracking Protection in Private Windows">
+<!ENTITY trackingProtectionPBM5.accesskey "v">
+<!ENTITY trackingProtectionPBMLearnMore.label "Learn more">
+<!ENTITY changeBlockList.label "Change Block List">
+<!ENTITY changeBlockList.accesskey "C">
+
+<!-- LOCALIZATION NOTE (doNotTrack.pre.label): include a trailing space as needed -->
+<!-- LOCALIZATION NOTE (doNotTrack.post.label): include a starting space as needed -->
+<!ENTITY doNotTrack.pre.label "You can also ">
+<!ENTITY doNotTrack.settings.label "manage your Do Not Track settings">
+<!ENTITY doNotTrack.post.label ".">
+
+<!ENTITY history.label "History">
+
+<!ENTITY locationBar.label "Location Bar">
+
+<!ENTITY locbar.suggest.label "When using the location bar, suggest:">
+<!ENTITY locbar.history.label "History">
+<!ENTITY locbar.history.accesskey "H">
+<!ENTITY locbar.bookmarks.label "Bookmarks">
+<!ENTITY locbar.bookmarks.accesskey "k">
+<!ENTITY locbar.openpage.label "Open tabs">
+<!ENTITY locbar.openpage.accesskey "O">
+<!ENTITY locbar.searches.label "Related searches from the default search engine">
+<!ENTITY locbar.searches.accesskey "d">
+
+<!ENTITY suggestionSettings.label "Change preferences for search engine suggestions…">
+<!ENTITY suggestionSettings.accesskey "g">
+
+<!ENTITY acceptCookies.label "Accept cookies from sites">
+<!ENTITY acceptCookies.accesskey "A">
+
+<!ENTITY acceptThirdParty.pre.label "Accept third-party cookies:">
+<!ENTITY acceptThirdParty.pre.accesskey "y">
+<!ENTITY acceptThirdParty.always.label "Always">
+<!ENTITY acceptThirdParty.never.label "Never">
+<!ENTITY acceptThirdParty.visited.label "From visited">
+
+<!ENTITY keepUntil.label "Keep until:">
+<!ENTITY keepUntil.accesskey "u">
+
+<!ENTITY expire.label "they expire">
+<!ENTITY close.label "I close &brandShortName;">
+
+<!ENTITY cookieExceptions.label "Exceptions…">
+<!ENTITY cookieExceptions.accesskey "E">
+
+<!ENTITY showCookies.label "Show Cookies…">
+<!ENTITY showCookies.accesskey "S">
+
+<!ENTITY historyHeader.pre.label "&brandShortName; will:">
+<!ENTITY historyHeader.pre.accesskey "w">
+<!ENTITY historyHeader.remember.label "Remember history">
+<!ENTITY historyHeader.dontremember.label "Never remember history">
+<!ENTITY historyHeader.custom.label "Use custom settings for history">
+<!ENTITY historyHeader.post.label "">
+
+<!ENTITY rememberDescription.label "&brandShortName; will remember your browsing, download, form and search history, and keep cookies from websites you visit.">
+
+<!-- LOCALIZATION NOTE (rememberActions.pre.label): include a trailing space as needed -->
+<!-- LOCALIZATION NOTE (rememberActions.middle.label): include a starting and trailing space as needed -->
+<!-- LOCALIZATION NOTE (rememberActions.post.label): include a starting space as needed -->
+<!ENTITY rememberActions.pre.label "You may want to ">
+<!ENTITY rememberActions.clearHistory.label "clear your recent history">
+<!ENTITY rememberActions.middle.label ", or ">
+<!ENTITY rememberActions.removeCookies.label "remove individual cookies">
+<!ENTITY rememberActions.post.label ".">
+
+<!ENTITY dontrememberDescription.label "&brandShortName; will use the same settings as private browsing, and will not remember any history as you browse the Web.">
+
+<!-- LOCALIZATION NOTE (dontrememberActions.pre.label): include a trailing space as needed -->
+<!-- LOCALIZATION NOTE (dontrememberActions.post.label): include a starting space as needed -->
+<!ENTITY dontrememberActions.pre.label "You may also want to ">
+<!ENTITY dontrememberActions.clearHistory.label "clear all current history">
+<!ENTITY dontrememberActions.post.label ".">
+
+<!ENTITY privateBrowsingPermanent2.label "Always use private browsing mode">
+<!ENTITY privateBrowsingPermanent2.accesskey "p">
+
+<!ENTITY rememberHistory2.label "Remember my browsing and download history">
+<!ENTITY rememberHistory2.accesskey "b">
+
+<!ENTITY rememberSearchForm.label "Remember search and form history">
+<!ENTITY rememberSearchForm.accesskey "f">
+
+<!ENTITY clearOnClose.label "Clear history when &brandShortName; closes">
+<!ENTITY clearOnClose.accesskey "r">
+
+<!ENTITY clearOnCloseSettings.label "Settings…">
+<!ENTITY clearOnCloseSettings.accesskey "t">
+
+<!ENTITY browserContainersHeader.label "Container Tabs">
+<!ENTITY browserContainersLearnMore.label "Learn more">
+<!ENTITY browserContainersEnabled.label "Enable Container Tabs">
+<!ENTITY browserContainersEnabled.accesskey "n">
+<!ENTITY browserContainersSettings.label "Settings…">
+<!ENTITY browserContainersSettings.accesskey "i">
diff --git a/browser/locales/en-US/chrome/browser/preferences/search.dtd b/browser/locales/en-US/chrome/browser/preferences/search.dtd
new file mode 100644
index 000000000..dbc2d7134
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/search.dtd
@@ -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/. -->
+
+<!ENTITY defaultSearchEngine.label "Default Search Engine">
+
+<!ENTITY chooseYourDefaultSearchEngine.label "Choose your default search engine. &brandShortName; uses it in the location bar, search bar, and start page.">
+
+<!ENTITY provideSearchSuggestions.label "Provide search suggestions">
+<!ENTITY provideSearchSuggestions.accesskey "s">
+
+<!ENTITY showURLBarSuggestions.label "Show search suggestions in location bar results">
+<!ENTITY showURLBarSuggestions.accesskey "l">
+<!ENTITY urlBarSuggestionsPermanentPB.label "Search suggestions will not be shown in location bar results because you have configured &brandShortName; to never remember history.">
+
+<!ENTITY redirectWindowsSearch.label "Use this search engine for searches from Windows">
+<!ENTITY redirectWindowsSearch.accesskey "W">
+
+<!ENTITY oneClickSearchEngines.label "One-click search engines">
+
+<!ENTITY chooseWhichOneToDisplay.label "The search bar lets you search alternate engines directly. Choose which ones to display.">
+
+<!ENTITY engineNameColumn.label "Search Engine">
+<!ENTITY engineKeywordColumn.label "Keyword">
+
+<!ENTITY restoreDefaultSearchEngines.label "Restore Default Search Engines">
+<!ENTITY restoreDefaultSearchEngines.accesskey "d">
+
+<!ENTITY removeEngine.label "Remove">
+<!ENTITY removeEngine.accesskey "r">
+
+<!ENTITY addMoreSearchEngines.label "Add more search engines…">
diff --git a/browser/locales/en-US/chrome/browser/preferences/security.dtd b/browser/locales/en-US/chrome/browser/preferences/security.dtd
new file mode 100644
index 000000000..ca9420401
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/security.dtd
@@ -0,0 +1,40 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY general.label "General">
+
+<!ENTITY warnAddonInstall.label "Warn me when sites try to install add-ons">
+<!ENTITY warnAddonInstall.accesskey "W">
+
+<!-- LOCALIZATION NOTE (enableSafeBrowsing.label, blockDownloads.label, blockUncommonUnwanted.label):
+ It is important that wording follows the guidelines outlined on this page:
+ https://developers.google.com/safe-browsing/developers_guide_v2#AcceptableUsage
+-->
+<!ENTITY enableSafeBrowsing.label "Block dangerous and deceptive content">
+<!ENTITY enableSafeBrowsing.accesskey "B">
+
+<!ENTITY blockDownloads.label "Block dangerous downloads">
+<!ENTITY blockDownloads.accesskey "D">
+
+<!ENTITY blockUncommonUnwanted.label "Warn me about unwanted and uncommon software">
+<!ENTITY blockUncommonUnwanted.accesskey "C">
+
+<!ENTITY addonExceptions.label "Exceptions…">
+<!ENTITY addonExceptions.accesskey "E">
+
+
+<!ENTITY logins.label "Logins">
+
+<!ENTITY rememberLogins.label "Remember logins for sites">
+<!ENTITY rememberLogins.accesskey "R">
+<!ENTITY passwordExceptions.label "Exceptions…">
+<!ENTITY passwordExceptions.accesskey "x">
+
+<!ENTITY useMasterPassword.label "Use a master password">
+<!ENTITY useMasterPassword.accesskey "U">
+<!ENTITY changeMasterPassword.label "Change Master Password…">
+<!ENTITY changeMasterPassword.accesskey "M">
+
+<!ENTITY savedLogins.label "Saved Logins…">
+<!ENTITY savedLogins.accesskey "L">
diff --git a/browser/locales/en-US/chrome/browser/preferences/selectBookmark.dtd b/browser/locales/en-US/chrome/browser/preferences/selectBookmark.dtd
new file mode 100644
index 000000000..a6082862a
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/selectBookmark.dtd
@@ -0,0 +1,9 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY selectBookmark.title
+ "Set Home Page">
+<!ENTITY selectBookmark.label
+ "Choose a Bookmark to be your Home Page. If you choose a folder, the Bookmarks in that folder will be opened in Tabs.">
+
diff --git a/browser/locales/en-US/chrome/browser/preferences/sync.dtd b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
new file mode 100644
index 000000000..78cadd74c
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -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/. -->
+
+<!-- The page shown when not logged in... -->
+<!ENTITY setupButton.label "Set Up &syncBrand.fullName.label;">
+<!ENTITY setupButton.accesskey "S">
+<!ENTITY weaveDesc.label "&syncBrand.fullName.label; lets you access your history, bookmarks, passwords and open tabs across all your devices.">
+
+<!-- The page shown when logged in... -->
+
+<!-- Login error feedback -->
+<!ENTITY updatePass.label "Update">
+<!ENTITY resetPass.label "Reset">
+
+<!-- Manage Account -->
+<!ENTITY manageAccount.label "Manage Account">
+<!ENTITY manageAccount.accesskey "n">
+<!ENTITY changePassword2.label "Change Password…">
+<!ENTITY myRecoveryKey.label "My Recovery Key">
+<!ENTITY resetSync2.label "Reset Sync…">
+
+<!ENTITY pairDevice.label "Pair a Device">
+
+<!ENTITY syncMy.label "Sync My">
+<!ENTITY engine.bookmarks.label "Bookmarks">
+<!ENTITY engine.bookmarks.accesskey "m">
+<!ENTITY engine.tabs.label "Tabs">
+<!ENTITY engine.tabs.accesskey "T">
+<!ENTITY engine.history.label "History">
+<!ENTITY engine.history.accesskey "r">
+<!ENTITY engine.passwords.label "Passwords">
+<!ENTITY engine.passwords.accesskey "P">
+<!ENTITY engine.prefs.label "Preferences">
+<!ENTITY engine.prefs.accesskey "S">
+<!ENTITY engine.addons.label "Add-ons">
+<!ENTITY engine.addons.accesskey "A">
+
+<!-- Device Settings -->
+<!ENTITY syncDeviceName.label "Device Name:">
+<!ENTITY fxaSyncDeviceName.label "Device Name">
+<!ENTITY changeSyncDeviceName.label "Change Device Name…">
+<!ENTITY changeSyncDeviceName.accesskey "h">
+<!ENTITY cancelChangeSyncDeviceName.label "Cancel">
+<!ENTITY cancelChangeSyncDeviceName.accesskey "n">
+<!ENTITY saveChangeSyncDeviceName.label "Save">
+<!ENTITY saveChangeSyncDeviceName.accesskey "v">
+<!ENTITY unlinkDevice.label "Unlink This Device">
+
+<!-- Footer stuff -->
+<!ENTITY prefs.tosLink.label "Terms of Service">
+<!ENTITY prefs.ppLink.label "Privacy Policy">
+
+<!-- Firefox Accounts stuff -->
+<!ENTITY fxaPrivacyNotice.link.label "Privacy Notice">
+<!ENTITY determiningAcctStatus.label "Determining your account status…">
+
+<!-- LOCALIZATION NOTE (signedInUnverified.beforename.label,
+signedInUnverified.aftername.label): these two string are used respectively
+before and after the account email address. Localizers can use one of them, or
+both, to better adapt this sentence to their language.
+-->
+<!ENTITY signedInUnverified.beforename.label "">
+<!ENTITY signedInUnverified.aftername.label "is not verified.">
+
+<!-- LOCALIZATION NOTE (signedInLoginFailure.beforename.label,
+signedInLoginFailure.aftername.label): these two string are used respectively
+before and after the account email address. Localizers can use one of them, or
+both, to better adapt this sentence to their language.
+-->
+<!ENTITY signedInLoginFailure.beforename.label "Please sign in to reconnect">
+<!ENTITY signedInLoginFailure.aftername.label "">
+
+<!ENTITY notSignedIn.label "You are not signed in.">
+<!ENTITY signIn.label "Sign in">
+<!ENTITY signIn.accesskey "g">
+<!ENTITY profilePicture.tooltip "Change profile picture">
+<!ENTITY verifiedManage.label "Manage Account">
+<!ENTITY verifiedManage.accesskey "o">
+<!ENTITY disconnect.label "Disconnect…">
+<!ENTITY disconnect.accesskey "D">
+<!ENTITY verify.label "Verify Email">
+<!ENTITY verify.accesskey "V">
+<!ENTITY forget.label "Forget this Email">
+<!ENTITY forget.accesskey "F">
+
+<!ENTITY welcome.description "Access your tabs, bookmarks, passwords and more wherever you use &brandShortName;.">
+<!ENTITY welcome.signIn.label "Sign In">
+<!ENTITY welcome.createAccount.label "Create Account">
+
+<!ENTITY welcome.useOldSync.label "Using an older version of Sync?">
+
+<!ENTITY signedOut.caption "Take your Web with you">
+<!ENTITY signedOut.description "Synchronize your bookmarks, history, tabs, passwords, add-ons, and preferences across all your devices.">
+<!ENTITY signedOut.accountBox.title "Connect with a &syncBrand.fxAccount.label;">
+<!ENTITY signedOut.accountBox.create "Create Account">
+<!ENTITY signedOut.accountBox.create.accesskey "C">
+<!ENTITY signedOut.accountBox.signin "Sign In">
+<!ENTITY signedOut.accountBox.signin.accesskey "I">
+
+<!ENTITY signedIn.engines.label "Sync across all devices">
+
+<!-- LOCALIZATION NOTE (mobilePromo3.*): the following strings will be used to
+ create a single sentence with active links.
+ The resulting sentence in English is: "Download Firefox for
+ Android or iOS to sync with your mobile device." -->
+
+<!ENTITY mobilePromo3.start "Download Firefox for ">
+<!-- LOCALIZATION NOTE (mobilePromo3.androidLink): This is a link title that links to https://www.mozilla.org/firefox/android/ -->
+<!ENTITY mobilePromo3.androidLink "Android">
+
+<!-- LOCALIZATION NOTE (mobilePromo3.iOSBefore): This is text displayed between mobilePromo3.androidLink and mobilePromo3.iosLink -->
+<!ENTITY mobilePromo3.iOSBefore " or ">
+<!-- LOCALIZATION NOTE (mobilePromo3.iOSLink): This is a link title that links to https://www.mozilla.org/firefox/ios/ -->
+<!ENTITY mobilePromo3.iOSLink "iOS">
+
+<!ENTITY mobilePromo3.end " to sync with your mobile device.">
diff --git a/browser/locales/en-US/chrome/browser/preferences/tabs.dtd b/browser/locales/en-US/chrome/browser/preferences/tabs.dtd
new file mode 100644
index 000000000..5df3e330d
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/tabs.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/. -->
+
+<!ENTITY ctrlTabRecentlyUsedOrder.label "Ctrl+Tab cycles through tabs in recently used order">
+<!ENTITY ctrlTabRecentlyUsedOrder.accesskey "T">
+
+<!ENTITY newWindowsAsTabs.label "Open new windows in a new tab instead">
+<!ENTITY newWindowsAsTabs.accesskey "w">
+
+<!ENTITY warnCloseMultipleTabs.label "Warn me when closing multiple tabs">
+<!ENTITY warnCloseMultipleTabs.accesskey "m">
+
+<!ENTITY warnOpenManyTabs.label "Warn me when opening multiple tabs might slow down &brandShortName;">
+<!ENTITY warnOpenManyTabs.accesskey "d">
+
+<!ENTITY switchToNewTabs.label "When I open a link in a new tab, switch to it immediately">
+<!ENTITY switchToNewTabs.accesskey "h">
+
+<!ENTITY showTabsInTaskbar.label "Show tab previews in the Windows taskbar">
+<!ENTITY showTabsInTaskbar.accesskey "k">
+<!ENTITY tabsGroup.label "Tabs">
diff --git a/browser/locales/en-US/chrome/browser/preferences/translation.dtd b/browser/locales/en-US/chrome/browser/preferences/translation.dtd
new file mode 100644
index 000000000..fc5fb2b7b
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/preferences/translation.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 window.title "Exceptions - Translation">
+<!ENTITY window.width "36em">
+<!ENTITY windowClose.key "w">
+
+<!ENTITY noTranslationForLanguages.label "Translation will not be offered for the following languages:">
+<!ENTITY treehead.languageName.label "Languages">
+<!ENTITY removeLanguage.label "Remove Language">
+<!ENTITY removeLanguage.accesskey "R">
+<!ENTITY removeAllLanguages.label "Remove All Languages">
+<!ENTITY removeAllLanguages.accesskey "e">
+
+<!ENTITY noTranslationForSites.label "Translation will not be offered for the following sites:">
+<!ENTITY treehead.siteName.label "Sites">
+<!ENTITY removeSite.label "Remove Site">
+<!ENTITY removeSite.accesskey "S">
+<!ENTITY removeAllSites.label "Remove All Sites">
+<!ENTITY removeAllSites.accesskey "i">
+
+<!ENTITY button.close.label "Close">
+<!ENTITY button.close.accesskey "C">
diff --git a/browser/locales/en-US/chrome/browser/quitDialog.properties b/browser/locales/en-US/chrome/browser/quitDialog.properties
new file mode 100644
index 000000000..35cdaa0d7
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/quitDialog.properties
@@ -0,0 +1,13 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+quitDialogTitle=Quit %S
+
+quitTitle=&Quit
+cancelTitle=&Cancel
+saveTitle=&Save and Quit
+neverAsk2=&Do not ask next time
+message=Do you want %S to save your tabs and windows for the next time it starts?
+messageNoWindows=Do you want %S to save your tabs for the next time it starts?
+messagePrivate=You’re in private browsing mode. Quitting %S now will discard all your open tabs and windows.
diff --git a/browser/locales/en-US/chrome/browser/safeMode.dtd b/browser/locales/en-US/chrome/browser/safeMode.dtd
new file mode 100644
index 000000000..ab23731c1
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/safeMode.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 safeModeDialog.title "&brandShortName; Safe Mode">
+<!ENTITY window.maxWidth "400">
+
+<!ENTITY startSafeMode.label "Start in Safe Mode">
+<!ENTITY refreshProfile.label "Refresh &brandShortName;">
+
+<!ENTITY safeModeDescription3.label "Safe Mode is a special mode of &brandShortName; that can be used to troubleshoot issues.">
+<!ENTITY safeModeDescription4.label "Your add-ons and custom settings will be temporarily disabled.">
+
+<!ENTITY refreshProfileInstead.label "You can also skip troubleshooting and try refreshing &brandShortName;.">
+
+<!-- LOCALIZATION NOTE (autoSafeModeDescription3.label): Shown on the safe mode dialog after multiple startup crashes. See also chrome/global/resetProfile.dtd -->
+<!ENTITY autoSafeModeDescription3.label "&brandShortName; closed unexpectedly while starting. This might be caused by add-ons or other problems. You can try to resolve the problem by troubleshooting in Safe Mode.">
diff --git a/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd b/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
new file mode 100644
index 000000000..33d2202b3
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
@@ -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/. -->
+
+<!ENTITY safeb.palm.accept.label "Get me out of here!">
+<!ENTITY safeb.palm.decline.label "Ignore this warning">
+<!-- Localization note (safeb.palm.notdeceptive.label) - Label of the Help menu
+ item. Either this or reportDeceptiveSiteMenu.label from report-phishing.dtd is
+ shown. -->
+<!ENTITY safeb.palm.notdeceptive.label "This isn’t a deceptive site…">
+<!-- Localization note (safeb.palm.notdeceptive.accesskey) - Because
+ safeb.palm.notdeceptive.label and reportDeceptiveSiteMenu.title from
+ report-phishing.dtd are never shown at the same time, the same accesskey can
+ be used for them. -->
+<!ENTITY safeb.palm.notdeceptive.accesskey "d">
+<!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
+
+<!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
+<!-- Localization note (safeb.blocked.malwarePage.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
+<!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
+
+<!ENTITY safeb.blocked.unwantedPage.title "Reported Unwanted Software Page!">
+<!-- Localization note (safeb.blocked.unwantedPage.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.unwantedPage.shortDesc "This web page at <span id='unwanted_sitename'/> has been reported to contain unwanted software and has been blocked based on your security preferences.">
+<!ENTITY safeb.blocked.unwantedPage.longDesc "<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>">
+
+<!ENTITY safeb.blocked.phishingPage.title2 "Deceptive Site!">
+<!-- Localization note (safeb.blocked.phishingPage.shortDesc2) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.phishingPage.shortDesc2 "This web page at <span id='phishing_sitename'/> has been reported as a deceptive site and has been blocked based on your security preferences.">
+<!ENTITY safeb.blocked.phishingPage.longDesc2 "<p>Deceptive sites are designed to trick you into doing something dangerous, like installing software, or revealing your personal information, like passwords, phone numbers or credit cards.</p><p>Entering any information on this web page may result in identity theft or other fraud.</p>">
+
diff --git a/browser/locales/en-US/chrome/browser/safebrowsing/report-phishing.dtd b/browser/locales/en-US/chrome/browser/safebrowsing/report-phishing.dtd
new file mode 100644
index 000000000..7444bcbf3
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/safebrowsing/report-phishing.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/. -->
+
+<!-- Localization note (reportDeceptiveSiteMenu.title) - Label of the Help menu
+ item. Either this or safeb.palm.notdeceptive.label from
+ phishing-afterload-warning-message.dtd is shown. -->
+<!ENTITY reportDeceptiveSiteMenu.title "Report deceptive site…">
+<!-- Localization note (reportDeceptiveSiteMenu.accesskey) - Because
+ safeb.palm.notdeceptive.label from phishing-afterload-warning-message.dtd and
+ reportDeceptiveSiteMenu.title are never shown at the same time, the same
+ accesskey can be used for them. -->
+<!ENTITY reportDeceptiveSiteMenu.accesskey "D">
diff --git a/browser/locales/en-US/chrome/browser/sanitize.dtd b/browser/locales/en-US/chrome/browser/sanitize.dtd
new file mode 100644
index 000000000..ab06ece0e
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/sanitize.dtd
@@ -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/. -->
+
+<!ENTITY sanitizePrefs2.title "Settings for Clearing History">
+<!-- LOCALIZATION NOTE (sanitizePrefs2.modal.width): width of the Clear History on Shutdown dialog.
+ Should be large enough to contain the item* strings on a single line.
+ The column width should be set at half of the dialog width. -->
+<!ENTITY sanitizePrefs2.modal.width "34em">
+<!ENTITY sanitizePrefs2.column.width "17em">
+<!-- LOCALIZATION NOTE (sanitizePrefs2.inContent.dialog.width): width of the
+ Clear History on Shutdown subdialog in the in-content preferences.
+ Should be large enough to contain the item* strings on a single line.
+ The column width adjusts the width of the first column in the dialog.
+ You can set the column width to a value that makes the dialog look visually balanced,
+ or at half of the dialog width if unsure. -->
+<!ENTITY sanitizePrefs2.inContent.dialog.width "34em">
+<!ENTITY sanitizePrefs2.inContent.column.width "24em">
+
+<!ENTITY sanitizeDialog2.title "Clear Recent History">
+<!-- LOCALIZATION NOTE (sanitizeDialog2.width): width of the Clear Recent History dialog -->
+<!ENTITY sanitizeDialog2.width "34em">
+
+<!ENTITY clearDataSettings2.label "When I quit &brandShortName;, it should automatically clear all:">
+
+<!-- XXX rearrange entities to match physical layout when l10n isn't an issue -->
+<!-- LOCALIZATION NOTE (clearTimeDuration.*): "Time range to clear" dropdown.
+ See UI mockup at bug 480169 -->
+<!ENTITY clearTimeDuration.label "Time range to clear: ">
+<!ENTITY clearTimeDuration.accesskey "T">
+<!ENTITY clearTimeDuration.lastHour "Last Hour">
+<!ENTITY clearTimeDuration.last2Hours "Last Two Hours">
+<!ENTITY clearTimeDuration.last4Hours "Last Four Hours">
+<!ENTITY clearTimeDuration.today "Today">
+<!ENTITY clearTimeDuration.everything "Everything">
+<!-- Localization note (clearTimeDuration.suffix) - trailing entity for languages
+that require it. -->
+<!ENTITY clearTimeDuration.suffix "">
+<!ENTITY clearTimeDuration.dateColumn "Visit Date">
+<!ENTITY clearTimeDuration.nameColumn "Name">
+
+<!-- LOCALIZATION NOTE (detailsProgressiveDisclosure.*): Labels and accesskeys
+ of the "Details" progressive disclosure button. See UI mockup at bug
+ 480169 -->
+<!ENTITY detailsProgressiveDisclosure.label "Details">
+<!ENTITY detailsProgressiveDisclosure.accesskey "e">
+
+<!ENTITY historySection.label "History">
+<!ENTITY dataSection.label "Data">
+
+<!ENTITY itemHistoryAndDownloads.label "Browsing &amp; Download History">
+<!ENTITY itemHistoryAndDownloads.accesskey "B">
+<!ENTITY itemFormSearchHistory.label "Form &amp; Search History">
+<!ENTITY itemFormSearchHistory.accesskey "F">
+<!ENTITY itemCookies.label "Cookies">
+<!ENTITY itemCookies.accesskey "C">
+<!ENTITY itemCache.label "Cache">
+<!ENTITY itemCache.accesskey "A">
+<!ENTITY itemOfflineApps.label "Offline Website Data">
+<!ENTITY itemOfflineApps.accesskey "O">
+<!ENTITY itemActiveLogins.label "Active Logins">
+<!ENTITY itemActiveLogins.accesskey "L">
+<!ENTITY itemSitePreferences.label "Site Preferences">
+<!ENTITY itemSitePreferences.accesskey "S">
+
+<!-- LOCALIZATION NOTE (sanitizeEverythingUndoWarning): Second warning paragraph
+ that appears when "Time range to clear" is set to "Everything". See UI
+ mockup at bug 480169 -->
+<!ENTITY sanitizeEverythingUndoWarning "This action cannot be undone.">
diff --git a/browser/locales/en-US/chrome/browser/search.properties b/browser/locales/en-US/chrome/browser/search.properties
new file mode 100644
index 000000000..83e8b690c
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/search.properties
@@ -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/.
+
+searchtip=Search using %S
+
+# LOCALIZATION NOTE (searchPlaceholder): this is shown in the searchbox when
+# the user hasn't typed anything yet.
+searchPlaceholder=Search
+
+# LOCALIZATION NOTE (searchHeader): this is displayed at the top of the panel
+# showing search suggestions.
+# %S is replaced with the name of the current default search engine.
+searchHeader=%S Search
+
+# LOCALIZATION NOTE (cmd_pasteAndSearch): "Search" is a verb, this is the
+# search bar equivalent to the url bar's "Paste & Go"
+cmd_pasteAndSearch=Paste & Search
+
+cmd_clearHistory=Clear Search History
+cmd_clearHistory_accesskey=H
+
+cmd_showSuggestions=Show Suggestions
+cmd_showSuggestions_accesskey=S
+
+# LOCALIZATION NOTE (cmd_addFoundEngine): %S is replaced by the name of
+# a search engine offered by a web page. Each engine is displayed as a
+# menuitem at the bottom of the search panel.
+cmd_addFoundEngine=Add “%S”
+# LOCALIZATION NOTE (cmd_addFoundEngineMenu): When more than 5 engines
+# are offered by a web page, instead of listing all of them in the
+# search panel using the cmd_addFoundEngine string, they will be
+# grouped in a submenu using cmd_addFoundEngineMenu as a label.
+cmd_addFoundEngineMenu=Add search engine
+
+# LOCALIZATION NOTE (searchForSomethingWith):
+# This string is used to build the header above the list of one-click
+# search providers: "Search for <user-typed string> with:"
+# NB: please leave the <span> and its class exactly as it is in English.
+searchForSomethingWith=Search for <span class='contentSearchSearchWithHeaderSearchText'></span> with:
+
+# LOCALIZATION NOTE (searchWithHeader):
+# The wording of this string should be as close as possible to
+# searchForSomethingWith. This string will be used when the user
+# has not typed anything.
+searchWithHeader=Search with:
+
+# LOCALIZATION NOTE (searchSettings):
+# This is the label for the button that opens Search preferences.
+searchSettings=Change Search Settings
diff --git a/browser/locales/en-US/chrome/browser/searchbar.dtd b/browser/locales/en-US/chrome/browser/searchbar.dtd
new file mode 100644
index 000000000..e5d18e4e8
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/searchbar.dtd
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY cmd_engineManager.label "Manage Search Engines…">
+<!ENTITY searchEndCap.label "Search">
diff --git a/browser/locales/en-US/chrome/browser/setDesktopBackground.dtd b/browser/locales/en-US/chrome/browser/setDesktopBackground.dtd
new file mode 100644
index 000000000..71aa4b649
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/setDesktopBackground.dtd
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY position.label "Position:">
+<!ENTITY tile.label "Tile">
+<!ENTITY center.label "Center">
+<!ENTITY stretch.label "Stretch">
+<!ENTITY fill.label "Fill">
+<!ENTITY fit.label "Fit">
+<!ENTITY preview.label "Preview">
+<!ENTITY color.label "Color:">
+<!ENTITY setDesktopBackground.title "Set Desktop Background">
+<!ENTITY openDesktopPrefs.label "Open Desktop Preferences">
+<!ENTITY closeWindow.key "w">
diff --git a/browser/locales/en-US/chrome/browser/shellservice.properties b/browser/locales/en-US/chrome/browser/shellservice.properties
new file mode 100644
index 000000000..e3dc14b7e
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/shellservice.properties
@@ -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/.
+
+optionsLabel=%S &Options
+safeModeLabel=%S &Safe Mode
+
+# LOCALIZATION NOTE (setDefaultBrowserMessage2, setDefaultBrowserConfirm.label):
+# %S will be replaced by brandShortName
+setDefaultBrowserMessage2 = Get the most out of %S by setting it as your default browser
+setDefaultBrowserConfirm.label = Use %S as my default browser
+setDefaultBrowserConfirm.accesskey = U
+setDefaultBrowserOptions.label = Options
+setDefaultBrowserOptions.accesskey = O
+setDefaultBrowserNotNow.label = Not now
+setDefaultBrowserNotNow.accesskey = N
+setDefaultBrowserNever.label = Don’t ask me again
+setDefaultBrowserNever.accesskey = D
+
+# LOCALIZATION NOTE (setDefaultBrowserTitle, setDefaultBrowserMessage, setDefaultBrowserDontAsk, setDefaultBrowserAlertConfirm.label, setDefaultBrowserAlertNotNow.label):
+# These strings are used as an alternative to the ones above, in a modal dialog.
+# %S will be replaced by brandShortName
+setDefaultBrowserTitle=Default Browser
+setDefaultBrowserMessage=%S is not currently set as your default browser. Would you like to make it your default browser?
+setDefaultBrowserDontAsk=Always perform this check when starting %S.
+setDefaultBrowserAlertConfirm.label=Use %S as my default browser
+setDefaultBrowserAlertNotNow.label=Not now
+
+desktopBackgroundLeafNameWin=Desktop Background.bmp
+DesktopBackgroundDownloading=Saving Picture…
+DesktopBackgroundSet=Set Desktop Background
diff --git a/browser/locales/en-US/chrome/browser/sitePermissions.properties b/browser/locales/en-US/chrome/browser/sitePermissions.properties
new file mode 100644
index 000000000..8126995f2
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/sitePermissions.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/.
+
+allow = Allow
+allowForSession = Allow for Session
+allowTemporarily = Allow Temporarily
+block = Block
+alwaysAsk = Always Ask
+
+permission.cookie.label = Set Cookies
+permission.desktop-notification2.label = Receive Notifications
+permission.image.label = Load Images
+permission.camera.label = Use the Camera
+permission.microphone.label = Use the Microphone
+permission.screen.label = Share the Screen
+permission.install.label = Install Add-ons
+permission.popup.label = Open Pop-up Windows
+permission.geo.label = Access Your Location
+permission.indexedDB.label = Maintain Offline Storage
diff --git a/browser/locales/en-US/chrome/browser/syncBrand.dtd b/browser/locales/en-US/chrome/browser/syncBrand.dtd
new file mode 100644
index 000000000..0c9e9201a
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncBrand.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 syncBrand.shortName.label "Sync">
+<!ENTITY syncBrand.fullName.label "Firefox Sync">
+<!ENTITY syncBrand.fxAccount.label "Firefox Account">
diff --git a/browser/locales/en-US/chrome/browser/syncCustomize.dtd b/browser/locales/en-US/chrome/browser/syncCustomize.dtd
new file mode 100644
index 000000000..3375c48ce
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncCustomize.dtd
@@ -0,0 +1,27 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY syncCustomize.dialog.title "Sync Selection">
+<!ENTITY syncCustomize.acceptButton.label "Start">
+
+<!ENTITY syncCustomize.title "What would you like to sync?">
+<!ENTITY syncCustomize.description "You can change this selection in Options.">
+<!ENTITY syncCustomizeUnix.description "You can change this selection in Preferences.">
+
+<!--
+ These engine names are the same as in browser/preferences/sync.dtd except
+ for the last two that are marked as being specific to Desktop browsers.
+-->
+<!ENTITY engine.bookmarks.label "Bookmarks">
+<!ENTITY engine.bookmarks.accesskey "m">
+<!ENTITY engine.history.label "History">
+<!ENTITY engine.history.accesskey "r">
+<!ENTITY engine.tabs.label "Tabs">
+<!ENTITY engine.tabs.accesskey "T">
+<!ENTITY engine.passwords.label "Passwords">
+<!ENTITY engine.passwords.accesskey "P">
+<!ENTITY engine.addons.label "Desktop Add-ons">
+<!ENTITY engine.addons.accesskey "A">
+<!ENTITY engine.prefs.label "Desktop Preferences">
+<!ENTITY engine.prefs.accesskey "S">
diff --git a/browser/locales/en-US/chrome/browser/syncGenericChange.properties b/browser/locales/en-US/chrome/browser/syncGenericChange.properties
new file mode 100644
index 000000000..aea86ad0e
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncGenericChange.properties
@@ -0,0 +1,37 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#LOCALIZATION NOTE (change.password.title): This (and associated change.password/passphrase) are used when the user elects to change their password.
+change.password.title = Change your Password
+change.password.acceptButton = Change Password
+change.password.status.active = Changing your password…
+change.password.status.success = Your password has been changed.
+change.password.status.error = There was an error changing your password.
+
+change.password3.introText = Your password must be at least 8 characters long. It cannot be the same as either your user name or your Recovery Key.
+change.password.warningText = Note: All of your other devices will be unable to connect to your account once you change this password.
+
+change.recoverykey.title = My Recovery Key
+change.recoverykey.acceptButton = Change Recovery Key
+change.recoverykey.label = Changing Recovery Key and uploading local data, please wait…
+change.recoverykey.error = There was an error while changing your Recovery Key!
+change.recoverykey.success = Your Recovery Key was successfully changed!
+
+change.synckey.introText2 = To ensure your total privacy, all of your data is encrypted prior to being uploaded. The key to decrypt your data is not uploaded.
+# LOCALIZATION NOTE (change.recoverykey.warningText) "Sync" should match &syncBrand.shortName.label; from syncBrand.dtd
+change.recoverykey.warningText = Note: Changing this will erase all data stored on the Sync server and upload new data secured by this Recovery Key. Your other devices will not sync until the new Recovery Key is entered for that device.
+
+new.recoverykey.label = Your Recovery Key
+
+# LOCALIZATION NOTE (new.password.title): This (and associated new.password/passphrase) are used on a second computer when it detects that your password or passphrase has been changed on a different device.
+new.password.title = Update Password
+new.password.introText = Your password was rejected by the server, please update your password.
+new.password.label = Enter your new password
+new.password.confirm = Confirm your new password
+new.password.acceptButton = Update Password
+new.password.status.incorrect = Password incorrect, please try again.
+
+new.recoverykey.title = Update Recovery Key
+new.recoverykey.introText = Your Recovery Key was changed using another device, please enter your updated Recovery Key.
+new.recoverykey.acceptButton = Update Recovery Key
diff --git a/browser/locales/en-US/chrome/browser/syncKey.dtd b/browser/locales/en-US/chrome/browser/syncKey.dtd
new file mode 100644
index 000000000..2ff001842
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncKey.dtd
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY syncKey.page.title "Your &syncBrand.fullName.label; Key">
+<!ENTITY syncKey.page.description2 "This key is used to decode the data in your &syncBrand.fullName.label; account. You will need to enter the key each time you configure &syncBrand.fullName.label; on a new device.">
+<!ENTITY syncKey.keepItSecret.heading "Keep it secret">
+<!ENTITY syncKey.keepItSecret.description "Your &syncBrand.fullName.label; account is encrypted to protect your privacy. Without this key, it would take years for anyone to decode your personal information. You are the only person who holds this key. This means you’re the only one who can access your &syncBrand.fullName.label; data.">
+<!ENTITY syncKey.keepItSafe.heading "Keep it safe">
+<!ENTITY syncKey.keepItSafe1.description "Do not lose this key.">
+<!ENTITY syncKey.keepItSafe2.description " We don’t keep a copy of your key (that wouldn’t be keeping it secret!) so ">
+<!ENTITY syncKey.keepItSafe3.description "we can’t help you recover it">
+<!ENTITY syncKey.keepItSafe4a.description " if it’s lost. You’ll need to use this key any time you connect a new device to &syncBrand.fullName.label;.">
+<!ENTITY syncKey.findOutMore1.label "Find out more about &syncBrand.fullName.label; and your privacy at ">
+<!ENTITY syncKey.findOutMore2.label ".">
+<!ENTITY syncKey.footer1.label "&syncBrand.fullName.label; Terms of Service are available at ">
+<!ENTITY syncKey.footer2.label ". The Privacy Policy is available at ">
+<!ENTITY syncKey.footer3.label ".">
diff --git a/browser/locales/en-US/chrome/browser/syncQuota.dtd b/browser/locales/en-US/chrome/browser/syncQuota.dtd
new file mode 100644
index 000000000..71174f087
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncQuota.dtd
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY quota.dialogTitle.label "Server Quota">
+<!ENTITY quota.retrievingInfo.label "Retrieving quota information…">
+<!ENTITY quota.typeColumn.label "Type">
+<!ENTITY quota.sizeColumn.label "Size">
diff --git a/browser/locales/en-US/chrome/browser/syncQuota.properties b/browser/locales/en-US/chrome/browser/syncQuota.properties
new file mode 100644
index 000000000..099090ec9
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncQuota.properties
@@ -0,0 +1,42 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+collection.addons.label = Add-ons
+collection.bookmarks.label = Bookmarks
+collection.history.label = History
+collection.passwords.label = Passwords
+collection.prefs.label = Preferences
+collection.tabs.label = Tabs
+
+# LOCALIZATION NOTE (quota.usageNoQuota.label): %1$S and %2$S are numeric value
+# and unit (as defined in the download manager) of the amount of space occupied
+# on the server
+quota.usageNoQuota.label = You are currently using %1$S %2$S.
+# LOCALIZATION NOTE (quota.usagePercentage.label):
+# %1$S is the percentage of space used,
+# %2$S and %3$S numeric value and unit (as defined in the download manager)
+# of the amount of space used,
+# %3$S and %4$S numeric value and unit (as defined in the download manager)
+# of the total space available.
+quota.usagePercentage.label = You are using %1$S%% (%2$S %3$S) of your allowed %4$S %5$S.
+quota.usageError.label = Could not retrieve quota information.
+quota.retrieving.label = Retrieving…
+# LOCALIZATION NOTE (quota.sizeValueUnit.label): %1$S is the amount of space
+# occupied by the engine, %2$K the corresponding unit (e.g. kB) as defined in
+# the download manager.
+quota.sizeValueUnit.label = %1$S %2$S
+quota.remove.label = Remove
+quota.treeCaption.label = Uncheck items to stop syncing them and free up space on the server.
+# LOCALIZATION NOTE (quota.removal.label): %S is a list of engines that will be
+# disabled and whose data will be removed once the user confirms.
+quota.removal.label = Firefox 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/browser/locales/en-US/chrome/browser/syncSetup.dtd b/browser/locales/en-US/chrome/browser/syncSetup.dtd
new file mode 100644
index 000000000..1fd46942d
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncSetup.dtd
@@ -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/. -->
+
+<!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: Mozilla &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">
+
+<!-- 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/browser/locales/en-US/chrome/browser/syncSetup.properties b/browser/locales/en-US/chrome/browser/syncSetup.properties
new file mode 100644
index 000000000..9d388af86
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncSetup.properties
@@ -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/.
+
+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 add-on;#1 add-ons
+
+save.recoverykey.title = Save Recovery Key
+save.recoverykey.defaultfilename = Firefox Recovery Key.html
+
+newAccount.action.label = Firefox 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 = Firefox Sync will now merge all this device’s browser data into your Sync account.
+wipeClient.change2.label = Firefox Sync will now replace all of the browser data on this device with the data in your Sync account.
+wipeRemote.change2.label = Firefox 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
+
+# Firefox Accounts based setup.
+continue.label = Continue
+
+# LOCALIZATION NOTE (disconnect.label, disconnect.verify.title, disconnect.verify.bodyHeading, disconnect.verify.bodyText):
+# These strings are used in the confirmation dialog shown when the user hits the disconnect button
+# LOCALIZATION NOTE (disconnect.label): This is the label for the disconnect button
+disconnect.label = Disconnect
+disconnect.verify.title = Disconnect
+disconnect.verify.bodyHeading = Disconnect from Sync?
+disconnect.verify.bodyText = Your browsing data will remain on this computer, but it will no longer sync with your account.
+
+relinkVerify.title = Merge Warning
+relinkVerify.heading = Are you sure you want to sign in to Sync?
+# LOCALIZATION NOTE (relinkVerify.description): Email address of a user previously signed into sync.
+relinkVerify.description = A different user was previously signed in to Sync on this computer. Signing in will merge this browser’s bookmarks, passwords and other settings with %S
diff --git a/browser/locales/en-US/chrome/browser/tabbrowser.properties b/browser/locales/en-US/chrome/browser/tabbrowser.properties
new file mode 100644
index 000000000..8e833c435
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/tabbrowser.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: the following strings can be used in the tab title or
+# location bar to represent various states as a web page loads:
+# tabs.connecting = Firefox is sending a HTTP connection request
+# tabs.encryptingConnection = Firefox is sending a HTTPS connection request
+# tabs.searching = Firefox is searching for something (Awesomebar or Web search)
+# tabs.loading = Firefox is loading the web page
+# tabs.waiting = Firefox is waiting for a web resource to load
+# tabs.downloading = Firefox is downloading a file for a helper application (PDF)
+tabs.connecting=Connecting…
+tabs.encryptingConnection=Securing connection…
+tabs.searching=Searching…
+tabs.loading=Loading…
+tabs.waiting=Waiting…
+tabs.downloading=Downloading…
+
+tabs.restoreLastTabs=Restore Tabs From Last Time
+tabs.emptyTabTitle=New Tab
+tabs.closeTab=Close Tab
+tabs.close=Close
+tabs.closeWarningTitle=Confirm close
+# LOCALIZATION NOTE (tabs.closeWarningMultiple):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# The singular form is not considered since this string is used only for
+# multiple tabs.
+tabs.closeWarningMultiple=;You are about to close #1 tabs. Are you sure you want to continue?
+tabs.closeButtonMultiple=Close tabs
+tabs.closeWarningPromptMe=Warn me when I attempt to close multiple tabs
+
+tabs.closeTab.tooltip=Close tab
+# LOCALIZATION NOTE (tabs.closeSelectedTab.tooltip):
+# %S is the keyboard shortcut for closing the current tab
+tabs.closeSelectedTab.tooltip=Close tab (%S)
+# LOCALIZATION NOTE (tabs.muteAudio.tooltip):
+# %S is the keyboard shortcut for "Mute tab"
+tabs.muteAudio.tooltip=Mute tab (%S)
+# LOCALIZATION NOTE (tabs.unmuteAudio.tooltip):
+# %S is the keyboard shortcut for "Unmute tab"
+tabs.unmuteAudio.tooltip=Unmute tab (%S)
+tabs.muteAudio.background.tooltip=Mute tab
+tabs.unmuteAudio.background.tooltip=Unmute tab
+
+tabs.unblockAudio.tooltip=Play tab
+
+# LOCALIZATION NOTE (tabs.allowTabFocusByPromptForSite):
+# %S is the hostname of the site where dialogs are allowed to switch tabs
+tabs.allowTabFocusByPromptForSite=Allow dialogs from %S to take you to their tab
diff --git a/browser/locales/en-US/chrome/browser/taskbar.properties b/browser/locales/en-US/chrome/browser/taskbar.properties
new file mode 100644
index 000000000..987d5ccf8
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/taskbar.properties
@@ -0,0 +1,12 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+taskbar.tasks.newTab.label=Open new tab
+taskbar.tasks.newTab.description=Open a new browser tab.
+taskbar.tasks.newWindow.label=Open new window
+taskbar.tasks.newWindow.description=Open a new browser window.
+taskbar.tasks.newPrivateWindow.label=New private window
+taskbar.tasks.newPrivateWindow.description=Open a new window in private browsing mode.
+taskbar.frequent.label=Frequent
+taskbar.recent.label=Recent
diff --git a/browser/locales/en-US/chrome/browser/translation.dtd b/browser/locales/en-US/chrome/browser/translation.dtd
new file mode 100644
index 000000000..ca8bb9d51
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/translation.dtd
@@ -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/. -->
+
+<!-- LOCALIZATION NOTE (translation.thisPageIsIn.label,
+ - translation.translateThisPage.label):
+ - These 2 strings are used to construct a sentence that contains a dropdown
+ - showing the detected language of the current web page.
+ - In en-US it looks like this:
+ - This page is in [detected language] Translate this page?
+ - "detected language" here is a language name coming from the
+ - global/languageNames.properties file; for some locales it may not be in
+ - the correct grammar case to keep the same structure of the original
+ - sentence. -->
+<!ENTITY translation.thisPageIsIn.label "This page is in">
+<!ENTITY translation.translateThisPage.label "Translate this page?">
+<!ENTITY translation.translate.button "Translate">
+<!ENTITY translation.notNow.button "Not Now">
+
+<!ENTITY translation.translatingContent.label "Translating page content…">
+
+<!-- LOCALIZATION NOTE (translation.translatedFrom.label,
+ - translation.translatedTo.label,
+ - translation.translatedToSuffix.label):
+ - These 3 strings are used to construct a sentence that contains 2 dropdowns
+ - showing the source and target language of a translated web page.
+ - In en-US it looks like this:
+ - This page has been translated from [from language] to [to language]
+ - "from language" and "to language" here are language names coming from the
+ - global/languageNames.properties file; for some locales they may not be in
+ - the correct grammar case to keep the same structure of the original
+ - sentence.
+ -
+ - translation.translatedToSuffix.label (empty in en-US) is for locales that
+ - need to display some text after the second drop down for the sentence to
+ - be grammatically correct. -->
+<!ENTITY translation.translatedFrom.label "This page has been translated from">
+<!ENTITY translation.translatedTo.label "to">
+<!ENTITY translation.translatedToSuffix.label "">
+
+<!ENTITY translation.showOriginal.button "Show Original">
+<!ENTITY translation.showTranslation.button "Show Translation">
+
+<!ENTITY translation.errorTranslating.label "There has been an error translating this page.">
+<!ENTITY translation.tryAgain.button "Try Again">
+
+<!ENTITY translation.serviceUnavailable.label "Translation is not available at the moment. Please try again later.">
+
+<!ENTITY translation.options.menu "Options">
+<!-- LOCALIZATION NOTE (translation.options.neverForSite.accesskey,
+ - translation.options.preferences.accesskey):
+ - The accesskey values used here should not clash with the value used for
+ - translation.options.neverForLanguage.accesskey in translation.properties
+ -->
+<!ENTITY translation.options.neverForSite.label "Never translate this site">
+<!ENTITY translation.options.neverForSite.accesskey "e">
+<!ENTITY translation.options.preferences.label "Translation preferences">
+<!ENTITY translation.options.preferences.accesskey "T">
+
+<!-- LOCALIZATION NOTE (translation.options.attribution.beforeLogo,
+ - translation.options.attribution.afterLogo):
+ - These 2 strings are displayed before and after a 'Microsoft Translator'
+ - logo.
+ -->
+<!ENTITY translation.options.attribution.beforeLogo "Translations by">
+<!ENTITY translation.options.attribution.afterLogo "">
+
+<!-- LOCALIZATION NOTE (translation.options.attribution.poweredByYandex,
+ translation.options.attribution.beforeLogo,
+ - translation.options.attribution.afterLogo):
+ - translation.options.attribution.poweredByYandex is displayed instead of
+ - the other two strings when yandex translation engine is preferred by the
+ - user.
+ -->
+<!ENTITY translation.options.attribution.yandexTranslate "Powered by Yandex.Translate">
diff --git a/browser/locales/en-US/chrome/browser/translation.properties b/browser/locales/en-US/chrome/browser/translation.properties
new file mode 100644
index 000000000..e62edbd0a
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/translation.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/.
+
+# LOCALIZATION NOTE (translation.options.neverForLanguage.label):
+# %S is a language name coming from the global/languageNames.properties file.
+translation.options.neverForLanguage.label=Never translate %S
+
+# LOCALIZATION NOTE (translation.options.neverForLanguage.accesskey):
+# The accesskey value used here should not clash with the values used for
+# translation.options.*.accesskey in translation.dtd
+translation.options.neverForLanguage.accesskey=N
diff --git a/browser/locales/en-US/chrome/browser/webrtcIndicator.properties b/browser/locales/en-US/chrome/browser/webrtcIndicator.properties
new file mode 100644
index 000000000..71d6f2ed9
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/webrtcIndicator.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 : FILE This file contains the webrtc global indicator strings
+
+# LOCALIZATION NOTE (webrtcIndicator.windowtitle): %S is the brand name (e.g. Firefox).
+# This string is used so that the window has a title in tools that enumerate/look for window
+# titles. It is not normally visible anywhere.
+webrtcIndicator.windowtitle = %S - Sharing Indicator
+
+webrtcIndicator.sharingCameraAndMicrophone.tooltip = Your camera and microphone are being shared. Click to control sharing.
+webrtcIndicator.sharingCamera.tooltip = Your camera is being shared. Click to control sharing.
+webrtcIndicator.sharingMicrophone.tooltip = Your microphone is being shared. Click to control sharing.
+webrtcIndicator.sharingApplication.tooltip = An application is being shared. Click to control sharing.
+webrtcIndicator.sharingScreen.tooltip = Your screen is being shared. Click to control sharing.
+webrtcIndicator.sharingWindow.tooltip = A window is being shared. Click to control sharing.
+webrtcIndicator.sharingBrowser.tooltip = A tab is being shared. Click to control sharing.
+
+
+# LOCALIZATION NOTE : The following strings are only used on Mac for
+# menus attached to icons near the clock on the mac menubar.
+
+# LOCALIZATION NOTE (webrtcIndicator.sharing*With.menuitem):
+# %S is the title of the tab using the share.
+webrtcIndicator.sharingCameraWith.menuitem = Sharing Camera with “%S”
+webrtcIndicator.sharingMicrophoneWith.menuitem = Sharing Microphone with “%S”
+webrtcIndicator.sharingApplicationWith.menuitem = Sharing an Application with “%S”
+webrtcIndicator.sharingScreenWith.menuitem = Sharing Screen with “%S”
+webrtcIndicator.sharingWindowWith.menuitem = Sharing a Window with “%S”
+webrtcIndicator.sharingBrowserWith.menuitem = Sharing a Tab with “%S”
+webrtcIndicator.controlSharing.menuitem = Control Sharing
+# LOCALIZATION NOTE (webrtcIndicator.sharingCameraWithNTabs.menuitem):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+webrtcIndicator.sharingCameraWithNTabs.menuitem = Sharing Camera with #1 tab;Sharing Camera with #1 tabs
+# LOCALIZATION NOTE (webrtcIndicator.sharingMicrophoneWithNTabs.menuitem):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+webrtcIndicator.sharingMicrophoneWithNTabs.menuitem = Sharing Microphone with #1 tab;Sharing Microphone with #1 tabs
+# LOCALIZATION NOTE (webrtcIndicator.sharingApplicationWithNTabs.menuitem):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+webrtcIndicator.sharingApplicationWithNTabs.menuitem = Sharing an Application with #1 tab;Sharing Applications with #1 tabs
+# LOCALIZATION NOTE (webrtcIndicator.sharingScreenWithNTabs.menuitem):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+webrtcIndicator.sharingScreenWithNTabs.menuitem = Sharing Screen with #1 tab;Sharing Screen with #1 tabs
+# LOCALIZATION NOTE (webrtcIndicator.sharingWindowWithNTabs.menuitem):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+webrtcIndicator.sharingWindowWithNTabs.menuitem = Sharing a Window with #1 tab;Sharing Windows with #1 tabs
+# LOCALIZATION NOTE (webrtcIndicator.sharingBrowserWithNTabs.menuitem):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# This message is shown when the contents of a tab is shared during a WebRTC
+# session, which currently is only possible with Loop/Hello.
+webrtcIndicator.sharingBrowserWithNTabs.menuitem = Sharing a Tab with #1 tab;Sharing Tabs with #1 tabs
+# LOCALIZATION NOTE (webrtcIndicator.controlSharingOn.menuitem):
+# %S is the title of the tab using the share.
+webrtcIndicator.controlSharingOn.menuitem = Control Sharing on “%S”
diff --git a/browser/locales/en-US/chrome/overrides/appstrings.properties b/browser/locales/en-US/chrome/overrides/appstrings.properties
new file mode 100644
index 000000000..02682c89c
--- /dev/null
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -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/.
+
+malformedURI=The URL is not valid and cannot be loaded.
+fileNotFound=Firefox can’t find the file at %S.
+fileAccessDenied=The file at %S is not readable.
+dnsNotFound=Firefox can’t find the server at %S.
+unknownProtocolFound=Firefox doesn’t know how to open this address, because one of the following protocols (%S) isn’t associated with any program or is not allowed in this context.
+connectionFailure=Firefox can’t establish a connection to the server at %S.
+netInterrupt=The connection to %S was interrupted while the page was loading.
+netTimeout=The server at %S is taking too long to respond.
+redirectLoop=Firefox has detected that the server is redirecting the request for this address in a way that will never complete.
+## LOCALIZATION NOTE (confirmRepostPrompt): In this item, don’t translate "%S"
+confirmRepostPrompt=To display this page, %S must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
+resendButton.label=Resend
+unknownSocketType=Firefox doesn’t know how to communicate with the server.
+netReset=The connection to the server was reset while the page was loading.
+notCached=This document is no longer available.
+netOffline=Firefox is currently in offline mode and can’t browse the Web.
+isprinting=The document cannot change while Printing or in Print Preview.
+deniedPortAccess=This address uses a network port which is normally used for purposes other than Web browsing. Firefox has canceled the request for your protection.
+proxyResolveFailure=Firefox is configured to use a proxy server that can’t be found.
+proxyConnectFailure=Firefox is configured to use a proxy server that is refusing connections.
+contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.
+unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
+externalProtocolTitle=External Protocol Request
+externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
+#LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
+externalProtocolUnknown=<Unknown>
+externalProtocolChkMsg=Remember my choice for all links of this type.
+externalProtocolLaunchBtn=Launch application
+malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
+unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
+deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences.
+cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
+corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired.
+remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
+## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
+sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
+## LOCALIZATION NOTE (weakCryptoUsed) - Do not translate "%S".
+weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
+inadequateSecurityError=The website tried to negotiate an inadequate level of security.
diff --git a/browser/locales/en-US/chrome/overrides/netError.dtd b/browser/locales/en-US/chrome/overrides/netError.dtd
new file mode 100644
index 000000000..30dd2346a
--- /dev/null
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -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/. -->
+
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+
+<!ENTITY loadError.label "Problem loading page">
+<!ENTITY retry.label "Try Again">
+<!ENTITY returnToPreviousPage.label "Go Back">
+<!ENTITY advanced.label "Advanced">
+
+<!-- Specific error messages -->
+
+<!ENTITY connectionFailure.title "Unable to connect">
+<!ENTITY connectionFailure.longDesc "&sharedLongDesc;">
+
+<!ENTITY deniedPortAccess.title "This address is restricted">
+<!ENTITY deniedPortAccess.longDesc "">
+
+<!ENTITY dnsNotFound.title "Server not found">
+<!ENTITY dnsNotFound.longDesc "
+<ul>
+ <li>Check the address for typing errors such as
+ <strong>ww</strong>.example.com instead of
+ <strong>www</strong>.example.com</li>
+ <li>If you are unable to load any pages, check your computer’s network
+ connection.</li>
+ <li>If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.</li>
+</ul>
+">
+
+<!ENTITY fileNotFound.title "File not found">
+<!ENTITY fileNotFound.longDesc "
+<ul>
+ <li>Check the file name for capitalization or other typing errors.</li>
+ <li>Check to see if the file was moved, renamed or deleted.</li>
+</ul>
+">
+
+<!ENTITY fileAccessDenied.title "Access to the file was denied">
+<!ENTITY fileAccessDenied.longDesc "
+<ul>
+ <li>It may have been removed, moved, or file permissions may be preventing access.</li>
+</ul>
+">
+
+<!ENTITY generic.title "Oops.">
+<!ENTITY generic.longDesc "
+<p>&brandShortName; can’t load this page for some reason.</p>
+">
+
+<!ENTITY captivePortal.title "Login to network">
+<!ENTITY captivePortal.longDesc "
+<p>This network may require you to login to access the internet.</p>
+">
+
+<!ENTITY openPortalLoginPage.label "Open Login Page">
+
+<!ENTITY malformedURI.title "The address isn’t valid">
+<!ENTITY malformedURI.longDesc "
+<ul>
+ <li>Web addresses are usually written like
+ <strong>http://www.example.com/</strong></li>
+ <li>Make sure that you’re using forward slashes (i.e.
+ <strong>/</strong>).</li>
+</ul>
+">
+
+<!ENTITY netInterrupt.title "The connection was interrupted">
+<!ENTITY netInterrupt.longDesc "&sharedLongDesc;">
+
+<!ENTITY notCached.title "Document Expired">
+<!ENTITY notCached.longDesc "<p>The requested document is not available in &brandShortName;’s cache.</p><ul><li>As a security precaution, &brandShortName; does not automatically re-request sensitive documents.</li><li>Click Try Again to re-request the document from the website.</li></ul>">
+
+<!ENTITY netOffline.title "Offline mode">
+<!ENTITY netOffline.longDesc2 "
+<ul>
+ <li>Press &quot;Try Again&quot; to switch to online mode and reload the page.</li>
+</ul>
+">
+
+<!ENTITY contentEncodingError.title "Content Encoding Error">
+<!ENTITY contentEncodingError.longDesc "
+<ul>
+ <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
+<!ENTITY unsafeContentType.title "Unsafe File Type">
+<!ENTITY unsafeContentType.longDesc "
+<ul>
+ <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
+<!ENTITY netReset.title "The connection was reset">
+<!ENTITY netReset.longDesc "&sharedLongDesc;">
+
+<!ENTITY netTimeout.title "The connection has timed out">
+<!ENTITY netTimeout.longDesc "&sharedLongDesc;">
+
+<!ENTITY unknownProtocolFound.title "The address wasn’t understood">
+<!ENTITY unknownProtocolFound.longDesc "
+<ul>
+ <li>You might need to install other software to open this address.</li>
+</ul>
+">
+
+<!ENTITY proxyConnectFailure.title "The proxy server is refusing connections">
+<!ENTITY proxyConnectFailure.longDesc "
+<ul>
+ <li>Check the proxy settings to make sure that they are correct.</li>
+ <li>Contact your network administrator to make sure the proxy server is
+ working.</li>
+</ul>
+">
+
+<!ENTITY proxyResolveFailure.title "Unable to find the proxy server">
+<!ENTITY proxyResolveFailure.longDesc "
+<ul>
+ <li>Check the proxy settings to make sure that they are correct.</li>
+ <li>Check to make sure your computer has a working network connection.</li>
+ <li>If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.</li>
+</ul>
+">
+
+<!ENTITY redirectLoop.title "The page isn’t redirecting properly">
+<!ENTITY redirectLoop.longDesc "
+<ul>
+ <li>This problem can sometimes be caused by disabling or refusing to accept
+ cookies.</li>
+</ul>
+">
+
+<!ENTITY unknownSocketType.title "Unexpected response from server">
+<!ENTITY unknownSocketType.longDesc "
+<ul>
+ <li>Check to make sure your system has the Personal Security Manager
+ installed.</li>
+ <li>This might be due to a non-standard configuration on the server.</li>
+</ul>
+">
+
+<!ENTITY nssFailure2.title "Secure Connection Failed">
+<!ENTITY nssFailure2.longDesc2 "
+<ul>
+ <li>The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.</li>
+ <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
+<!ENTITY certerror.longpagetitle1 "Your connection is not secure">
+<!-- Localization note (certerror.introPara) - The text content of the span tag
+will be replaced at runtime with the name of the server to which the user
+was trying to connect. -->
+<!ENTITY certerror.introPara "The owner of <span class='hostname'/> has configured their website improperly. To protect your information from being stolen, &brandShortName; has not connected to this website.">
+
+<!ENTITY sharedLongDesc "
+<ul>
+ <li>The site could be temporarily unavailable or too busy. Try again in a few
+ moments.</li>
+ <li>If you are unable to load any pages, check your computer’s network
+ connection.</li>
+ <li>If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.</li>
+</ul>
+">
+
+<!ENTITY cspBlocked.title "Blocked by Content Security Policy">
+<!ENTITY cspBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
+
+<!ENTITY corruptedContentErrorv2.title "Corrupted Content Error">
+<!ENTITY corruptedContentErrorv2.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
+
+
+<!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">
+
+<!ENTITY errorReporting.automatic2 "Report errors like this to help Mozilla identify and block malicious sites">
+<!ENTITY errorReporting.learnMore "Learn more…">
+
+<!ENTITY remoteXUL.title "Remote XUL">
+<!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
+
+<!ENTITY sslv3Used.title "Unable to Connect Securely">
+<!-- LOCALIZATION NOTE (sslv3Used.longDesc2) - Do not translate
+ "SSL_ERROR_UNSUPPORTED_VERSION". -->
+<!ENTITY sslv3Used.longDesc2 "Advanced info: SSL_ERROR_UNSUPPORTED_VERSION">
+
+<!ENTITY weakCryptoUsed.title "Your connection is not secure">
+<!-- LOCALIZATION NOTE (weakCryptoUsed.longDesc2) - Do not translate
+ "SSL_ERROR_NO_CYPHER_OVERLAP". -->
+<!ENTITY weakCryptoUsed.longDesc2 "Advanced info: SSL_ERROR_NO_CYPHER_OVERLAP">
+<!ENTITY weakCryptoAdvanced.title "Advanced">
+<!ENTITY weakCryptoAdvanced.longDesc "<span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe.">
+<!ENTITY weakCryptoAdvanced.override "(Not secure) Try loading <span class='hostname'></span> using outdated security">
+
+<!-- LOCALIZATION NOTE (certerror.wrongSystemTime) - The <span id='..' /> tags will be injected with actual values,
+ please leave them unchanged. -->
+<!ENTITY certerror.wrongSystemTime "<p>A secure connection to <span id='wrongSystemTime_URL'/> isn’t possible because your clock appears to show the wrong time.</p> <p>Your computer thinks it is <span id='wrongSystemTime_systemDate'/>, when it should be <span id='wrongSystemTime_actualDate'/>. To fix this problem, change your date and time settings to match the correct time.</p>">
+
+<!ENTITY certerror.pagetitle1 "Insecure Connection">
+<!ENTITY certerror.whatShouldIDo.badStsCertExplanation "This site uses HTTP
+Strict Transport Security (HSTS) to specify that &brandShortName; may only connect
+to it securely. As a result, it is not possible to add an exception for this
+certificate.">
+<!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
+
+<!ENTITY inadequateSecurityError.title "Your connection is not secure">
+<!-- LOCALIZATION NOTE (inadequateSecurityError.longDesc) - Do not translate
+ "NS_ERROR_NET_INADEQUATE_SECURITY". -->
+<!ENTITY inadequateSecurityError.longDesc "<p><span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe. The website administrator will need to fix the server first before you can visit the site.</p><p>Error code: NS_ERROR_NET_INADEQUATE_SECURITY</p>">
+
+<!ENTITY prefReset.longDesc "It looks like your network security settings might be causing this. Do you want the default settings to be restored?">
+<!ENTITY prefReset.label "Restore default settings">
diff --git a/browser/locales/en-US/chrome/overrides/settingsChange.dtd b/browser/locales/en-US/chrome/overrides/settingsChange.dtd
new file mode 100644
index 000000000..1357b92fa
--- /dev/null
+++ b/browser/locales/en-US/chrome/overrides/settingsChange.dtd
@@ -0,0 +1,7 @@
+<!-- -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY settingsChangePreferences.label "Settings can be changed using the Applications tab in &brandShortName;'s Preferences.">
+<!ENTITY settingsChangeOptions.label "Settings can be changed using the Applications tab in &brandShortName;'s Options.">
diff --git a/browser/locales/en-US/crashreporter/crashreporter-override.ini b/browser/locales/en-US/crashreporter/crashreporter-override.ini
new file mode 100644
index 000000000..f14b1c4f0
--- /dev/null
+++ b/browser/locales/en-US/crashreporter/crashreporter-override.ini
@@ -0,0 +1,9 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file is in the UTF-8 encoding
+[Strings]
+# LOCALIZATION NOTE (CrashReporterProductErrorText2): The %s is replaced with a string containing detailed information.
+CrashReporterProductErrorText2=Firefox had a problem and crashed. We’ll try to restore your tabs and windows when it restarts.\n\nUnfortunately the crash reporter is unable to submit a crash report.\n\nDetails: %s
+CrashReporterDescriptionText2=Firefox had a problem and crashed. We’ll try to restore your tabs and windows when it restarts.\n\nTo help us diagnose and fix the problem, you can send us a crash report.
diff --git a/browser/locales/en-US/defines.inc b/browser/locales/en-US/defines.inc
new file mode 100644
index 000000000..fb7fe4c93
--- /dev/null
+++ b/browser/locales/en-US/defines.inc
@@ -0,0 +1,12 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#filter emptyLines
+
+#define MOZ_LANGPACK_CREATOR mozilla.org
+
+# If non-English locales wish to credit multiple contributors, uncomment this
+# variable definition and use the format specified.
+# #define MOZ_LANGPACK_CONTRIBUTORS <em:contributor>Joe Solon</em:contributor> <em:contributor>Suzy Solon</em:contributor>
+
+#unfilter emptyLines
diff --git a/browser/locales/en-US/firefox-l10n.js b/browser/locales/en-US/firefox-l10n.js
new file mode 100644
index 000000000..c1e5510f3
--- /dev/null
+++ b/browser/locales/en-US/firefox-l10n.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/.
+
+#filter substitution
+
+# LOCALIZATION NOTE: this preference is set to true for en-US specifically,
+# locales without this line have the setting set to false by default.
+pref("browser.search.geoSpecificDefaults", true);
+
+pref("general.useragent.locale", "@AB_CD@");
diff --git a/browser/locales/en-US/installer/custom.properties b/browser/locales/en-US/installer/custom.properties
new file mode 100644
index 000000000..4abb1a550
--- /dev/null
+++ b/browser/locales/en-US/installer/custom.properties
@@ -0,0 +1,85 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+REG_APP_DESC=$BrandShortName delivers safe, easy web browsing. A familiar user interface, enhanced security features including protection from online identity theft, and integrated search let you get the most out of the web.
+CONTEXT_OPTIONS=$BrandShortName &Options
+CONTEXT_SAFE_MODE=$BrandShortName &Safe Mode
+OPTIONS_PAGE_TITLE=Setup Type
+OPTIONS_PAGE_SUBTITLE=Choose setup options
+SHORTCUTS_PAGE_TITLE=Set Up Shortcuts
+SHORTCUTS_PAGE_SUBTITLE=Create Program Icons
+COMPONENTS_PAGE_TITLE=Set Up Optional Components
+COMPONENTS_PAGE_SUBTITLE=Optional Recommended Components
+OPTIONAL_COMPONENTS_DESC=The Maintenance Service will allow you to update $BrandShortName silently in the background.
+MAINTENANCE_SERVICE_CHECKBOX_DESC=Install &Maintenance Service
+SUMMARY_PAGE_TITLE=Summary
+SUMMARY_PAGE_SUBTITLE=Ready to start installing $BrandShortName
+SUMMARY_INSTALLED_TO=$BrandShortName will be installed to the following location:
+SUMMARY_REBOOT_REQUIRED_INSTALL=A restart of your computer may be required to complete the installation.
+SUMMARY_REBOOT_REQUIRED_UNINSTALL=A restart of your computer may be required to complete the uninstall.
+SUMMARY_TAKE_DEFAULTS=U&se $BrandShortName as my default web browser
+SUMMARY_INSTALL_CLICK=Click Install to continue.
+SUMMARY_UPGRADE_CLICK=Click Upgrade to continue.
+SURVEY_TEXT=&Tell us what you thought of $BrandShortName
+LAUNCH_TEXT=&Launch $BrandShortName now
+CREATE_ICONS_DESC=Create icons for $BrandShortName:
+ICONS_DESKTOP=On my &Desktop
+ICONS_STARTMENU=In my &Start Menu Programs folder
+ICONS_QUICKLAUNCH=In my &Quick Launch bar
+WARN_MANUALLY_CLOSE_APP_INSTALL=$BrandShortName must be closed to proceed with the installation.\n\nPlease close $BrandShortName to continue.
+WARN_MANUALLY_CLOSE_APP_UNINSTALL=$BrandShortName must be closed to proceed with the uninstall.\n\nPlease close $BrandShortName to continue.
+WARN_MANUALLY_CLOSE_APP_LAUNCH=$BrandShortName is already running.\n\nPlease close $BrandShortName prior to launching the version you have just installed.
+WARN_WRITE_ACCESS=You don't have access to write to the installation directory.\n\nClick OK to select a different directory.
+WARN_DISK_SPACE=You don't have sufficient disk space to install to this location.\n\nClick OK to select a different location.
+WARN_MIN_SUPPORTED_OSVER_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer. Please click the OK button for additional information.
+WARN_MIN_SUPPORTED_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires a processor with ${MinSupportedCPU} support. Please click the OK button for additional information.
+WARN_MIN_SUPPORTED_OSVER_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer and a processor with ${MinSupportedCPU} support. Please click the OK button for additional information.
+WARN_RESTART_REQUIRED_UNINSTALL=Your computer must be restarted to complete a previous uninstall of $BrandShortName. Do you want to reboot now?
+WARN_RESTART_REQUIRED_UPGRADE=Your computer must be restarted to complete a previous upgrade of $BrandShortName. Do you want to reboot now?
+ERROR_CREATE_DIRECTORY_PREFIX=Error creating directory:
+ERROR_CREATE_DIRECTORY_SUFFIX=Click Cancel to stop the installation or\nRetry to try again.
+
+UN_CONFIRM_PAGE_TITLE=Uninstall $BrandFullName
+UN_CONFIRM_PAGE_SUBTITLE=Remove $BrandFullName from your computer.
+UN_CONFIRM_UNINSTALLED_FROM=$BrandShortName will be uninstalled from the following location:
+UN_CONFIRM_CLICK=Click Uninstall to continue.
+
+BANNER_CHECK_EXISTING=Checking existing installation…
+
+STATUS_INSTALL_APP=Installing $BrandShortName…
+STATUS_INSTALL_LANG=Installing Language Files (${AB_CD})…
+STATUS_UNINSTALL_MAIN=Uninstalling $BrandShortName…
+STATUS_CLEANUP=A Little Housekeeping…
+
+# _DESC strings support approximately 65 characters per line.
+# One line
+OPTIONS_SUMMARY=Choose the type of setup you prefer, then click Next.
+# One line
+OPTION_STANDARD_DESC=$BrandShortName will be installed with the most common options.
+OPTION_STANDARD_RADIO=&Standard
+# Two lines
+OPTION_CUSTOM_DESC=You may choose individual options to be installed. Recommended for experienced users.
+OPTION_CUSTOM_RADIO=&Custom
+
+# LOCALIZATION NOTE:
+# The following text replaces the Install button text on the summary page.
+# Verify that the access key for InstallBtn (in override.properties) and
+# UPGRADE_BUTTON is not already used by SUMMARY_TAKE_DEFAULTS.
+UPGRADE_BUTTON=&Upgrade
diff --git a/browser/locales/en-US/installer/mui.properties b/browser/locales/en-US/installer/mui.properties
new file mode 100644
index 000000000..c786dbba2
--- /dev/null
+++ b/browser/locales/en-US/installer/mui.properties
@@ -0,0 +1,61 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# To make the l10n tinderboxen see changes to this file you can change a value
+# name by adding - to the end of the name followed by chars (e.g. Branding-2).
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+MUI_TEXT_WELCOME_INFO_TITLE=Welcome to the $BrandFullNameDA Setup Wizard
+MUI_TEXT_WELCOME_INFO_TEXT=This wizard will guide you through the installation of $BrandFullNameDA.\n\nIt is recommended that you close all other applications before starting Setup. This will make it possible to update relevant system files without having to reboot your computer.\n\n$_CLICK
+MUI_TEXT_COMPONENTS_TITLE=Choose Components
+MUI_TEXT_COMPONENTS_SUBTITLE=Choose which features of $BrandFullNameDA you want to install.
+MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE=Description
+MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO=Position your mouse over a component to see its description.
+MUI_TEXT_DIRECTORY_TITLE=Choose Install Location
+MUI_TEXT_DIRECTORY_SUBTITLE=Choose the folder in which to install $BrandFullNameDA.
+MUI_TEXT_INSTALLING_TITLE=Installing
+MUI_TEXT_INSTALLING_SUBTITLE=Please wait while $BrandFullNameDA is being installed.
+MUI_TEXT_FINISH_TITLE=Installation Complete
+MUI_TEXT_FINISH_SUBTITLE=Setup was completed successfully.
+MUI_TEXT_ABORT_TITLE=Installation Aborted
+MUI_TEXT_ABORT_SUBTITLE=Setup was not completed successfully.
+MUI_BUTTONTEXT_FINISH=&Finish
+MUI_TEXT_FINISH_INFO_TITLE=Completing the $BrandFullNameDA Setup Wizard
+MUI_TEXT_FINISH_INFO_TEXT=$BrandFullNameDA has been installed on your computer.\n\nClick Finish to close this wizard.
+MUI_TEXT_FINISH_INFO_REBOOT=Your computer must be restarted in order to complete the installation of $BrandFullNameDA. Do you want to reboot now?
+MUI_TEXT_FINISH_REBOOTNOW=Reboot now
+MUI_TEXT_FINISH_REBOOTLATER=I want to manually reboot later
+MUI_TEXT_STARTMENU_TITLE=Choose Start Menu Folder
+MUI_TEXT_STARTMENU_SUBTITLE=Choose a Start Menu folder for the $BrandFullNameDA shortcuts.
+MUI_INNERTEXT_STARTMENU_TOP=Select the Start Menu folder in which you would like to create the program's shortcuts. You can also enter a name to create a new folder.
+MUI_TEXT_ABORTWARNING=Are you sure you want to quit $BrandFullName Setup?
+MUI_UNTEXT_WELCOME_INFO_TITLE=Welcome to the $BrandFullNameDA Uninstall Wizard
+MUI_UNTEXT_WELCOME_INFO_TEXT=This wizard will guide you through the uninstallation of $BrandFullNameDA.\n\nBefore starting the uninstallation, make sure $BrandFullNameDA is not running.\n\n$_CLICK
+MUI_UNTEXT_CONFIRM_TITLE=Uninstall $BrandFullNameDA
+MUI_UNTEXT_CONFIRM_SUBTITLE=Remove $BrandFullNameDA from your computer.
+MUI_UNTEXT_UNINSTALLING_TITLE=Uninstalling
+MUI_UNTEXT_UNINSTALLING_SUBTITLE=Please wait while $BrandFullNameDA is being uninstalled.
+MUI_UNTEXT_FINISH_TITLE=Uninstallation Complete
+MUI_UNTEXT_FINISH_SUBTITLE=Uninstall was completed successfully.
+MUI_UNTEXT_ABORT_TITLE=Uninstallation Aborted
+MUI_UNTEXT_ABORT_SUBTITLE=Uninstall was not completed successfully.
+MUI_UNTEXT_FINISH_INFO_TITLE=Completing the $BrandFullNameDA Uninstall Wizard
+MUI_UNTEXT_FINISH_INFO_TEXT=$BrandFullNameDA has been uninstalled from your computer.\n\nClick Finish to close this wizard.
+MUI_UNTEXT_FINISH_INFO_REBOOT=Your computer must be restarted in order to complete the uninstallation of $BrandFullNameDA. Do you want to reboot now?
+MUI_UNTEXT_ABORTWARNING=Are you sure you want to quit $BrandFullName Uninstall?
diff --git a/browser/locales/en-US/installer/nsisstrings.properties b/browser/locales/en-US/installer/nsisstrings.properties
new file mode 100644
index 000000000..389405696
--- /dev/null
+++ b/browser/locales/en-US/installer/nsisstrings.properties
@@ -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/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+WIN_CAPTION=$BrandShortName Setup
+
+INTRO_BLURB1=Thanks for choosing $BrandFullName, the browser that chooses you above everything else.
+INSTALL_BLURB1=You're about to enjoy the very latest in speed, flexibility and security so you're always in control.
+INSTALL_BLURB2=That's because $BrandShortName is made by a non-profit to make browsing and the Web better for you.
+INSTALL_BLURB3=You're also joining a global community of users, contributors and developers working to make the best browser in the world.
+
+WARN_MIN_SUPPORTED_OSVER_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer. Please click the OK button for additional information.
+WARN_MIN_SUPPORTED_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires a processor with ${MinSupportedCPU} support. Please click the OK button for additional information.
+WARN_MIN_SUPPORTED_OSVER_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer and a processor with ${MinSupportedCPU} support. Please click the OK button for additional information.
+WARN_WRITE_ACCESS=You don't have access to write to the installation directory.\n\nClick OK to select a different directory.
+WARN_DISK_SPACE=You don't have sufficient disk space to install to this location.\n\nClick OK to select a different location.
+WARN_ROOT_INSTALL=Unable to install to the root of your disk.\n\nClick OK to select a different location.
+WARN_MANUALLY_CLOSE_APP_LAUNCH=$BrandShortName is already running.\n\nPlease close $BrandShortName prior to launching the version you have just installed.
+
+ERROR_DOWNLOAD=Your download was interrupted.\n\nPlease click the OK button to continue.
+
+INSTALL_BUTTON=&Install
+UPGRADE_BUTTON=&Upgrade
+CANCEL_BUTTON=Cancel
+OPTIONS_BUTTON=&Options
+
+MAKE_DEFAULT=&Make $BrandShortName my default browser
+CREATE_SHORTCUTS=Create Shortcuts for $BrandShortName:
+ADD_SC_TASKBAR=On my &Task bar
+ADD_SC_QUICKLAUNCHBAR=On my &Quick Launch bar
+ADD_CheckboxShortcutInStartMenu=In my &Start Menu Programs Folder
+ADD_CheckboxShortcutOnDesktop=On my &Desktop
+SPACE_REQUIRED=Space Required:
+SPACE_AVAILABLE=Space Available:
+ONE_MOMENT_INSTALL=One moment, $BrandShortName will launch as soon as the install is complete…
+ONE_MOMENT_UPGRADE=One moment, $BrandShortName will launch as soon as the upgrade is complete…
+INSTALL_MAINT_SERVICE=&Install the $BrandShortName background update service
+SEND_PING=S&end information about this installation to Mozilla
+BROWSE_BUTTON=B&rowse…
+DEST_FOLDER=Destination Folder
+
+DOWNLOADING_LABEL=Downloading $BrandShortName…
+INSTALLING_LABEL=Installing $BrandShortName…
+UPGRADING_LABEL=Upgrading $BrandShortName…
+
+SELECT_FOLDER_TEXT=Select the folder to install $BrandShortName in.
+
+BYTE=B
+KILO=K
+MEGA=M
+GIGA=G
diff --git a/browser/locales/en-US/installer/override.properties b/browser/locales/en-US/installer/override.properties
new file mode 100644
index 000000000..288f67480
--- /dev/null
+++ b/browser/locales/en-US/installer/override.properties
@@ -0,0 +1,86 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+# Strings that require a space at the end should be enclosed with double
+# quotes and the double quotes will be removed. To add quotes to the beginning
+# and end of a strong enclose the add and additional double quote to the
+# beginning and end of the string (e.g. ""This will include quotes"").
+
+SetupCaption=$BrandFullName Setup
+UninstallCaption=$BrandFullName Uninstall
+BackBtn=< &Back
+NextBtn=&Next >
+AcceptBtn=I &accept the terms in the License Agreement
+DontAcceptBtn=I &do not accept the terms in the License Agreement
+InstallBtn=&Install
+UninstallBtn=&Uninstall
+CancelBtn=Cancel
+CloseBtn=&Close
+BrowseBtn=B&rowse…
+ShowDetailsBtn=Show &details
+ClickNext=Click Next to continue.
+ClickInstall=Click Install to start the installation.
+ClickUninstall=Click Uninstall to start the uninstallation.
+Completed=Completed
+LicenseTextRB=Please review the license agreement before installing $BrandFullNameDA. If you accept all terms of the agreement, select the first option below. $_CLICK
+ComponentsText=Check the components you want to install and uncheck the components you don't want to install. $_CLICK
+ComponentsSubText2_NoInstTypes=Select components to install:
+DirText=Setup will install $BrandFullNameDA in the following folder. To install in a different folder, click Browse and select another folder. $_CLICK
+DirSubText=Destination Folder
+DirBrowseText=Select the folder to install $BrandFullNameDA in:
+SpaceAvailable="Space available: "
+SpaceRequired="Space required: "
+UninstallingText=$BrandFullNameDA will be uninstalled from the following folder. $_CLICK
+UninstallingSubText=Uninstalling from:
+FileError=Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Abort to stop the installation,\r\nRetry to try again, or\r\nIgnore to skip this file.
+FileError_NoIgnore=Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Retry to try again, or\r\nCancel to stop the installation.
+CantWrite="Can't write: "
+CopyFailed=Copy failed
+CopyTo="Copy to "
+Registering="Registering: "
+Unregistering="Unregistering: "
+SymbolNotFound="Could not find symbol: "
+CouldNotLoad="Could not load: "
+CreateFolder="Create folder: "
+CreateShortcut="Create shortcut: "
+CreatedUninstaller="Created uninstaller: "
+Delete="Delete file: "
+DeleteOnReboot="Delete on reboot: "
+ErrorCreatingShortcut="Error creating shortcut: "
+ErrorCreating="Error creating: "
+ErrorDecompressing=Error decompressing data! Corrupted installer?
+ErrorRegistering=Error registering DLL
+ExecShell="ExecShell: "
+Exec="Execute: "
+Extract="Extract: "
+ErrorWriting="Extract: error writing to file "
+InvalidOpcode=Installer corrupted: invalid opcode
+NoOLE="No OLE for: "
+OutputFolder="Output folder: "
+RemoveFolder="Remove folder: "
+RenameOnReboot="Rename on reboot: "
+Rename="Rename: "
+Skipped="Skipped: "
+CopyDetails=Copy Details To Clipboard
+LogInstall=Log install process
+Byte=B
+Kilo=K
+Mega=M
+Giga=G
diff --git a/browser/locales/en-US/pdfviewer/chrome.properties b/browser/locales/en-US/pdfviewer/chrome.properties
new file mode 100644
index 000000000..e42ac790c
--- /dev/null
+++ b/browser/locales/en-US/pdfviewer/chrome.properties
@@ -0,0 +1,19 @@
+# Copyright 2012 Mozilla Foundation
+#
+# 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.
+
+# Chrome notification bar messages and buttons
+unsupported_feature=This PDF document might not be displayed correctly.
+unsupported_feature_forms=This PDF document contains forms. The filling of form fields is not supported.
+open_with_different_viewer=Open With Different Viewer
+open_with_different_viewer.accessKey=o
diff --git a/browser/locales/en-US/pdfviewer/viewer.properties b/browser/locales/en-US/pdfviewer/viewer.properties
new file mode 100644
index 000000000..43ce1f317
--- /dev/null
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -0,0 +1,182 @@
+# Copyright 2012 Mozilla Foundation
+#
+# 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.
+
+# Main toolbar buttons (tooltips and alt text for images)
+previous.title=Previous Page
+previous_label=Previous
+next.title=Next Page
+next_label=Next
+
+# LOCALIZATION NOTE (page.title): The tooltip for the pageNumber input.
+page.title=Page
+# LOCALIZATION NOTE (of_pages): "{{pagesCount}}" will be replaced by a number
+# representing the total number of pages in the document.
+of_pages=of {{pagesCount}}
+# LOCALIZATION NOTE (page_of_pages): "{{pageNumber}}" and "{{pagesCount}}"
+# will be replaced by a number representing the currently visible page,
+# respectively a number representing the total number of pages in the document.
+page_of_pages=({{pageNumber}} of {{pagesCount}})
+
+zoom_out.title=Zoom Out
+zoom_out_label=Zoom Out
+zoom_in.title=Zoom In
+zoom_in_label=Zoom In
+zoom.title=Zoom
+presentation_mode.title=Switch to Presentation Mode
+presentation_mode_label=Presentation Mode
+open_file.title=Open File
+open_file_label=Open
+print.title=Print
+print_label=Print
+download.title=Download
+download_label=Download
+bookmark.title=Current view (copy or open in new window)
+bookmark_label=Current View
+
+# Secondary toolbar and context menu
+tools.title=Tools
+tools_label=Tools
+first_page.title=Go to First Page
+first_page.label=Go to First Page
+first_page_label=Go to First Page
+last_page.title=Go to Last Page
+last_page.label=Go to Last Page
+last_page_label=Go to Last Page
+page_rotate_cw.title=Rotate Clockwise
+page_rotate_cw.label=Rotate Clockwise
+page_rotate_cw_label=Rotate Clockwise
+page_rotate_ccw.title=Rotate Counterclockwise
+page_rotate_ccw.label=Rotate Counterclockwise
+page_rotate_ccw_label=Rotate Counterclockwise
+
+hand_tool_enable.title=Enable hand tool
+hand_tool_enable_label=Enable hand tool
+hand_tool_disable.title=Disable hand tool
+hand_tool_disable_label=Disable hand tool
+
+# Document properties dialog box
+document_properties.title=Document Properties…
+document_properties_label=Document Properties…
+document_properties_file_name=File name:
+document_properties_file_size=File size:
+# LOCALIZATION NOTE (document_properties_kb): "{{size_kb}}" and "{{size_b}}"
+# will be replaced by the PDF file size in kilobytes, respectively in bytes.
+document_properties_kb={{size_kb}} KB ({{size_b}} bytes)
+# LOCALIZATION NOTE (document_properties_mb): "{{size_mb}}" and "{{size_b}}"
+# will be replaced by the PDF file size in megabytes, respectively in bytes.
+document_properties_mb={{size_mb}} MB ({{size_b}} bytes)
+document_properties_title=Title:
+document_properties_author=Author:
+document_properties_subject=Subject:
+document_properties_keywords=Keywords:
+document_properties_creation_date=Creation Date:
+document_properties_modification_date=Modification Date:
+# LOCALIZATION NOTE (document_properties_date_string): "{{date}}" and "{{time}}"
+# will be replaced by the creation/modification date, and time, of the PDF file.
+document_properties_date_string={{date}}, {{time}}
+document_properties_creator=Creator:
+document_properties_producer=PDF Producer:
+document_properties_version=PDF Version:
+document_properties_page_count=Page Count:
+document_properties_close=Close
+
+print_progress_message=Preparing document for printing…
+# LOCALIZATION NOTE (print_progress_percent): "{{progress}}" will be replaced by
+# a numerical per cent value.
+print_progress_percent={{progress}}%
+print_progress_close=Cancel
+
+# Tooltips and alt text for side panel toolbar buttons
+# (the _label strings are alt text for the buttons, the .title strings are
+# tooltips)
+toggle_sidebar.title=Toggle Sidebar
+toggle_sidebar_label=Toggle Sidebar
+document_outline.title=Show Document Outline (double-click to expand/collapse all items)
+document_outline_label=Document Outline
+attachments.title=Show Attachments
+attachments_label=Attachments
+thumbs.title=Show Thumbnails
+thumbs_label=Thumbnails
+findbar.title=Find in Document
+findbar_label=Find
+
+# Thumbnails panel item (tooltip and alt text for images)
+# LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page
+# number.
+thumb_page_title=Page {{page}}
+# LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page
+# number.
+thumb_page_canvas=Thumbnail of Page {{page}}
+
+# Find panel button title and messages
+find_label=Find:
+find_previous.title=Find the previous occurrence of the phrase
+find_previous_label=Previous
+find_next.title=Find the next occurrence of the phrase
+find_next_label=Next
+find_highlight=Highlight all
+find_match_case_label=Match case
+find_reached_top=Reached top of document, continued from bottom
+find_reached_bottom=Reached end of document, continued from top
+find_not_found=Phrase not found
+
+# Error panel labels
+error_more_info=More Information
+error_less_info=Less Information
+error_close=Close
+# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be
+# replaced by the PDF.JS version and build ID.
+error_version_info=PDF.js v{{version}} (build: {{build}})
+# LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an
+# english string describing the error.
+error_message=Message: {{message}}
+# LOCALIZATION NOTE (error_stack): "{{stack}}" will be replaced with a stack
+# trace.
+error_stack=Stack: {{stack}}
+# LOCALIZATION NOTE (error_file): "{{file}}" will be replaced with a filename
+error_file=File: {{file}}
+# LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number
+error_line=Line: {{line}}
+rendering_error=An error occurred while rendering the page.
+
+# Predefined zoom values
+page_scale_width=Page Width
+page_scale_fit=Page Fit
+page_scale_auto=Automatic Zoom
+page_scale_actual=Actual Size
+# LOCALIZATION NOTE (page_scale_percent): "{{scale}}" will be replaced by a
+# numerical scale value.
+page_scale_percent={{scale}}%
+
+# Loading indicator messages
+loading_error_indicator=Error
+loading_error=An error occurred while loading the PDF.
+invalid_file_error=Invalid or corrupted PDF file.
+missing_file_error=Missing PDF file.
+unexpected_response_error=Unexpected server response.
+
+# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
+# "{{type}}" will be replaced with an annotation type from a list defined in
+# the PDF spec (32000-1:2008 Table 169 – Annotation types).
+# Some common types are e.g.: "Check", "Text", "Comment", "Note"
+text_annotation_type.alt=[{{type}} Annotation]
+password_label=Enter the password to open this PDF file.
+password_invalid=Invalid password. Please try again.
+password_ok=OK
+password_cancel=Cancel
+
+printing_not_supported=Warning: Printing is not fully supported by this browser.
+printing_not_ready=Warning: The PDF is not fully loaded for printing.
+web_fonts_disabled=Web fonts are disabled: unable to use embedded PDF fonts.
+document_colors_not_allowed=PDF documents are not allowed to use their own colors: “Allow pages to choose their own colors” is deactivated in the browser.
diff --git a/browser/locales/en-US/profile/bookmarks.inc b/browser/locales/en-US/profile/bookmarks.inc
new file mode 100644
index 000000000..3ef47799a
--- /dev/null
+++ b/browser/locales/en-US/profile/bookmarks.inc
@@ -0,0 +1,72 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#filter emptyLines
+
+# LOCALIZATION NOTE: The 'en-US' strings in the URLs will be replaced with
+# your locale code, and link to your translated pages as soon as they're
+# live.
+
+#define bookmarks_title Bookmarks
+#define bookmarks_heading Bookmarks
+
+#define bookmarks_toolbarfolder Bookmarks Toolbar Folder
+#define bookmarks_toolbarfolder_description Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar
+
+# LOCALIZATION NOTE (getting_started):
+# link title for https://www.mozilla.org/en-US/firefox/central/
+#define getting_started Getting Started
+
+# LOCALIZATION NOTE (firefox_heading):
+# Firefox links folder name
+#define firefox_heading Mozilla Firefox
+
+# LOCALIZATION NOTE (firefox_help):
+# link title for https://www.mozilla.org/en-US/firefox/help/
+#define firefox_help Help and Tutorials
+
+# LOCALIZATION NOTE (firefox_customize):
+# link title for https://www.mozilla.org/en-US/firefox/customize/
+#define firefox_customize Customize Firefox
+
+# LOCALIZATION NOTE (firefox_community):
+# link title for https://www.mozilla.org/en-US/contribute/
+#define firefox_community Get Involved
+
+# LOCALIZATION NOTE (firefox_about):
+# link title for https://www.mozilla.org/en-US/about/
+#define firefox_about About Us
+
+# LOCALIZATION NOTE (nightly_heading):
+# Firefox Nightly links folder name
+#define nightly_heading Firefox Nightly Resources
+
+# LOCALIZATION NOTE (nightly_blog):
+# Nightly builds only, link title for https://blog.nightly.mozilla.org/
+#define nightly_blog Firefox Nightly blog
+
+# LOCALIZATION NOTE (bugzilla):
+# Nightly builds only, link title for https://bugzilla.mozilla.org/
+#define bugzilla Mozilla Bug Tracker
+
+# LOCALIZATION NOTE (mdn):
+# Nightly builds only, link title for https://developer.mozilla.org/
+#define mdn Mozilla Developer Network
+
+# LOCALIZATION NOTE (nightly_tester_tools):
+# Nightly builds only, link title for https://addons.mozilla.org/en-US/firefox/addon/nightly-tester-tools/
+#define nightly_tester_tools Nightly Tester Tools
+
+# LOCALIZATION NOTE (crashes):
+# Nightly builds only, link title for about:crashes
+#define crashes All your crashes
+
+# LOCALIZATION NOTE (irc):
+# Nightly builds only, link title for ircs://irc.mozilla.org/nightly
+#define irc Discuss Nightly on IRC
+
+# LOCALIZATION NOTE (planet):
+# Nightly builds only, link title for https://planet.mozilla.org/
+#define planet Planet Mozilla
+
+#unfilter emptyLines
diff --git a/browser/locales/en-US/updater/updater.ini b/browser/locales/en-US/updater/updater.ini
new file mode 100644
index 000000000..6bc731f16
--- /dev/null
+++ b/browser/locales/en-US/updater/updater.ini
@@ -0,0 +1,10 @@
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+; This file is in the UTF-8 encoding
+; All strings must be less than 600 chars.
+[Strings]
+TitleText=%MOZ_APP_DISPLAYNAME% Update
+InfoText=%MOZ_APP_DISPLAYNAME% is installing your updates and will start in a few moments…
+MozillaMaintenanceDescription=The Mozilla Maintenance Service ensures that you have the latest and most secure version of Mozilla Firefox on your computer. Keeping Firefox up to date is very important for your online security, and Mozilla strongly recommends that you keep this service enabled.
diff --git a/browser/locales/filter.py b/browser/locales/filter.py
new file mode 100755
index 000000000..52e596d96
--- /dev/null
+++ b/browser/locales/filter.py
@@ -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/.
+
+def test(mod, path, entity = None):
+ import re
+ # ignore anything but Firefox
+ if mod not in ("netwerk", "dom", "toolkit", "security/manager",
+ "devtools/client", "devtools/shared",
+ "browser",
+ "extensions/spellcheck",
+ "other-licenses/branding/firefox",
+ "browser/branding/official",
+ "services/sync"):
+ return "ignore"
+ if mod not in ("browser", "extensions/spellcheck"):
+ # we only have exceptions for browser and extensions/spellcheck
+ return "error"
+ if not entity:
+ # the only files to ignore are spell checkers
+ if mod == "extensions/spellcheck":
+ return "ignore"
+ return "error"
+ if mod == "extensions/spellcheck":
+ # l10n ships en-US dictionary or something, do compare
+ return "error"
+ if path == "defines.inc":
+ return "ignore" if entity == "MOZ_LANGPACK_CONTRIBUTORS" else "error"
+
+ if mod == "browser" and path == "chrome/browser-region/region.properties":
+ # only region.properties exceptions remain, compare all others
+ return ("ignore"
+ if (re.match(r"browser\.search\.order\.[1-9]", entity) or
+ re.match(r"browser\.contentHandlers\.types\.[0-5]", entity) or
+ re.match(r"gecko\.handlerService\.schemes\.", entity) or
+ re.match(r"gecko\.handlerService\.defaultHandlersVersion", entity))
+ else "error")
+ return "error"
diff --git a/browser/locales/generic/profile/bookmarks.html.in b/browser/locales/generic/profile/bookmarks.html.in
new file mode 100644
index 000000000..5b7bdad69
--- /dev/null
+++ b/browser/locales/generic/profile/bookmarks.html.in
@@ -0,0 +1,56 @@
+#filter substitution
+#include @BOOKMARKS_INCLUDE_DIR@/bookmarks.inc
+#define ja_jp_mac ja-JP-mac
+#if AB_CD == ja_jp_mac
+#define AB_CD ja
+#endif
+
+#define mozilla_icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gwMDAsTBZbkNwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABNElEQVQ4y8WSsU0DURBE3yyWIaAJaqAAN4DPSL6AlIACKIEOyJEgRsIgOOkiInJqgAKowNg7BHdn7MOksNl+zZ//dvbDf5cAiklp22BdVtXdeTEpDYDB9m1VzU6OJuVp2NdEQCaI96fH2YHG4+mDduKYNMYINTcjcGbXzQVDEAphG0k48zUsajIbnAiMIXThpW8EICE0RAK4dvoKg9NIcTiQ589otyHOZLnwqK5nLwBFUZ4igc3iM0d1ff8CMC6mZ6Ihiaqq3gi1aUAnArD00SW1fq5OLBg0ymYmSZsR2/t4e/rGyCLW0sbp3oq+yTYqVgytQWui2FS7XYF7GFprY921T4CNQt8zr47dNzCkIX7y/jBtH+v+RGMQrc828W8pApnZbmEVQp/Ae7BlOy2ttib81/UFc+WRWEbjckIAAAAASUVORK5CYII=
+
+#define nightly_icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA01JREFUeNpkk01oXFUUx//vzp33ZiaTzkwnyUwmsbbNRpp0GhuVKiVgQIUiLmoX7lyIG10I6qIUrG6KGyuYZbMwiATqxipSMVhQYwWhiwSJwUxKkzbz8ZJ5H/M+7/vunUIE7Vnc1f3/zjn/c46wsCbjIOI4Rlfew+Zf6/h9+RYYYzj70hymZp7GULUCSin+H8IBwLYsfLOwiOfffndK1c2LmhO84jl+2e5ZCTN6Xer0fpp5dmq+frxy5zGAY9n49IOLuHDls8uqE3y07YV0ywwQGA6oZsJRFRT0FnK2Hpw8PTl/7tXZDw8ApF/29YUvufjqxxqLPrnLQBUiQRUI4EcQHQ+EgwbjENl8Pv3H9W/f//m7W9cOAKkXzr+J4pm5umy6S/uEkiAEOlzscWHG5jTbhcsrDJiHsN1CIScJTlc5JaSlldEna9uk8fcGHNO6tOMGqfssQjNFoHOxyAXU9xF5HiRXw2AqhuUEsLs6giCkjbXG549a+O3mMhQneLnthOh0NBiaAdozkTEdpC0Hnm2hKAKR3ESJRigNFZBHCF2zn+p7SMIwRM9kRcI/D5g2xAcd1PQeMtw8xkGuafIRGRgZKfHfCbKlIu5t3IVASYYDDtMzL85il2fLuh6qIkFkWtjrARY3zjENJLwCKgqwOjLcnW30Qh/HnpmGYTJhT1bSdPL0NBpr+6rbs8qhIiPivQtRhDDJQuCViI4BxdJRrx/jBopob25BDmIMnzgZjFTK+2R4tAo4+q8iM9Ba3wBTuxirHELeM5AxFO6FCsnSsHNnFfL9XUiFIo7ypNmB9D3eQkT665nS5Xm72Q5r3KCo04be2MIRKULOUlEkPnJpAbn8AI7XJyEUy/CBhIrJlUdT6D/rP3x1u1yQFjP5bFIbH4HdbHG2j/GJMfiqzneCwVI0oFrD8OgTiKLgl5Ubi0v/uYV3ZqcrE2ff+Do2/LlUxAi/HKQZH9eegupzpxDztL4gIfTU9d3VG+ev/nh7s6/797wiz1K3VpYuDU3MvFWunXg9ly2WTNVNDZaPgIU08V3NMNXVZfmfP68lvt187Br78d5r52hoKaUkiWspQqqHx4+OCQLhFu3KAXNbCYQWzR5SBCr5X3x/M+lrHgowAMP2wWqd+FzxAAAAAElFTkSuQmCC
+
+#define firefox_icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAz5JREFUeNpcU0toXFUY/s49596588ikySSmM8rk0UkJEirVTVEjYk1NUdRFFgouXZQI2k2hUCjdiYIuCi4Kuig0DbqQYgU3Yi0iFRuRCBMwaZvMZDTNY95z3/fM8b8jldpzOeeee/i/737n+/+f4ZExf730rkiaZ9NJMx/nmk5HypMqqLr+ZuD4F67N5ZYejmfRsnvhyKsLhSs3zP70SubgYKHSdFENu+gXAprQIGnvBi5ez0m1b6u7M399+PTx+UvtCKtFi2F6195qXK2byir88esy5ksfYND5DhuhRCtUKBNB0Q7w5Z8ee2GgWdjOn250fn5v5j8F5dOHlZD0kWRgAwp3hrKY0S9BJWKIcQ5PShhNG4+1bHx+IocT+RXcrRSDMfFNRvvt3Oyp/icIrRhUl9hoO4kt3F55Bxf3LkLV20jWOohbDjzXxyc/lMH0cRSyCd1xq+dFhu986t7T0CUwIzB8EkW3O5ibhj44ib7NJhiBKRoB/aS4ZWHjbxtjGQYP7nGRTjim1SaQqYiAVAREpHG8vzWJn+6PQ6CF0HPR7nQQczpYeEWHyWMUkwU8MSECGwgJxAWp9xWKNzkuH3oet+JDlIE9Momedh1G4CN0bfhVG9nsS0BQQthJx0V7j3elq7ihqV5ezQTwZmodP9ZGkU/bOPr4Nr53xtDYsaD7Dg4nNqFaH5OCYSSdGhf7daNmuv6wEKqXk/yExKGBHSznFsH6FFpKQ/JAA0ulUejWLt44lkHXsqDaX6C6Nl4Rnq8vK8s/KThDMlJhkI91YOO+iR3XwEelCdTCBLSgijNzd6DLNfhlHfZ6FtWiWBQpq/V2LdSrrNPljBQkoOjuCiPxAMutOIbkPqYSEqeequGZaTLzdhZhS0PlnlB9bvhtr5CuHj3y2Qj8hVQ/kEopxONEEsO/0yATDYXYAdWrE69JEElXq7Hyk0tro+xBU1yZmr4xrIcvptIRCZlJJAYBmRGRkD08aivA7VDvWPqt564Xn41w/AHB1/u7l1/uGxGU8mNcgodUNDIq0JDeLnpzc1uo1Qr76rWbqyf/142PjsXJqdkmN2YDnQ9oGkJNU+t57q7qUv4y9/t64+HYfwQYAN7OczrzUDvGAAAAAElFTkSuQmCC
+
+#define bugzilla_icon data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnYCL7J0pY9wwETY9kYEM9AAAAAEBAYAhVVVUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1bjTOS1Vb/wAA2/8JEqT/Q0g8kQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJnMqoiJtJ/0haST/NEkp/yQnW/8AAPD8AAHY/RsgP/JZUI+GKitih4ObUEAAAAAAAAAAAAAAAAAAAAAAKnouwip5Lv9ooW//a6Jz/y9GIv8JBXb/AADO/gEBvf8AAK7/FRJp/z9BfP8iLGj1AAAAAAAAAAAAAAAAL4YzwiB0I/+UwJ3/bK+C/02eZ/9CTjz/DBa1/wABxf8BAOj/AACp/w8Oc/sJCv//EhCN9AAAAAAAAAAANJA6iC2MMv93tYL/TZ5U/3mzjP9NoWr/NEss/wYIU/8CBOn/ARCX/wwNqP0TD6X/Cgyex5qacDAAAAAAAAAAADKUN/ZirWj/d76R/0SgXf9Ln1P/eLSM/1mda/8rOkb/CQiD/wMQvf8UEnT/MTAt4P//MwUhZyN8AAAAAAAAAAAznDf5UqlZ/228jP9NqnD/Qp9c/0yiVP+Dv5b/VaVw/0VxXf9PZXD/S3pQ/y54Nf8jcCf/I2wn/wAAAAA0ozjIM6E4/zOeOP+Uz6z/XLR+/06scv9BoV3/TqZX/4XBmP9XqHP/hr6Z/yp+Lf8leSr1JXUqbQAAAAA3rTz7Nqo7/zWmOqM3oz7rUK1W/43Mpf9etYD/T61z/0KjXf9Rqln/msup/46/lf8pgy7/JFg6sAAAAAAAAAAAOK8+8jqvOiMAAAAAAAAAADSlOv85pT//kM6o/2K5hP9Ysnv/YLJ2/ziXPv8piS3/V6Ri/yZQQ9wAAAAAAAAAAAAAAAAAAAAAAAAAADetP0E2qzz/OKg98UWsS/+e1K3/pNe4/4XDjv8ojy3/T7df/5fIqv8sjTH/K4kw/yqFLv8AAAAAAAAAAAAAAAA4sT3xN7A8+QAAAAA4qz3yNag6/zSlOf80oTn/csJ+/6/jwv9fjHj/MmRMdQAAAAAAAAAAAAAAAAAAAAAAAAAAOrdA/zm0QHQAAAAAAAAAADasO/k2qTvuRX5lpjqGT/gznDr/O3FXigAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADq9QiMAAAAAAAAAAAAAAAA4sj7/Nq09s0uOaSI1qTplM6U68wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOrc//zu0P0EAAAAAOK4+UjWsPPgAAAAAAAAAAAAAAAAAAAAA/48AAP8HAAD4AQAA8AAAAOAAAADAAQAAwAMAAMAAAACAAwAAAAMAAHADAADwAAAA5AcAAO4HAAD+bwAA/u8AAA==
+
+#define mdn_icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAACYklEQVQ4y3WSTYjNYRTGf+f9/+/NuDN3GKMxt1mYpKR8RT6zEk1iyUo2IysSNspCxEIZRSN07YiUJSWkaUIZizELMzIZlO9h3MzXvf/341jcmTGa69RZvKfnPOc5z3mhQlRd651Ry17tqQQl5uR9ONHC3EtdiPPxWKZ24bhJvQV0OlBHhiV7pTs3WpUdEmeLrnWJAhhOtJA5/3RjqZg8Lrnwy4bQT/DvTb73Qt3N9ymATNvTU15Mvx8vfvTWDrmgT0y+dzuAZM527lM0Xx6jFKvnEKIYvIIqVeMFRCfEiDBWMw9UQUBUT8fBWvmrU9HEUpUWDq2cT23a0P6kQEqEwTFLQCBJpm9mYrV+6lW0ngNr62jb0kw6KvMeXJohZYSLzz9xvOMDJG6aPUKMtZPDObq+iXPbmoG/ooJC0QVaVy2g5AI3Bkq8/jYCkQEBo9ah1hESS1N1Gu9DxXNFRji0oYm+/Stoaa4tK0kcRq1HE4fxHqOKEf4bk16ubphd9iKxGKxFrcOWLB1vvuOCzmg0Irig1MxKMfBznPbOd2DLCmJNHKgSIdzr/kjXhwKbFtVPNQ+NJiw7+YDh0YTFuVr6CwLpaNJDjCYOtR61DldyHL7975c9dqeH4ZESscC7r79BFBI7kY5YrZsCC8qL/kFyR+6yY0UjS3M1PHz5ich71IOIQGSn0cskwdTeryTyfV9+jGzOP3rTQAiIHUNQEBkCeUbkcqguB2JEMGqt4sJlg8lGIsv0+p5dON8YaVgn3veJ85/F61YNtn5uXf3OCF0TQVacP0PiXMVzpfbemlnbna+I/QPSFTsRLd+hHAAAAABJRU5ErkJggg==
+
+#define addon_icon data:image/vndmicrosofticon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAKoSURBVDiNjZNLaJRnFIaf75vvy2UGTWr8NYkxl0IFUYkNXSSZQNWFWlxIEVy1FLLo0i4adddVXRnS4kJwYxci2GaRTWlSFGKFGRXNmFBF62VMO4TENBdjMv9k5p//Oy4SLzgqHs7yPc95eTlHiQjvqniPbVYRzuBoR+FQDEvId4neIPNCY945DayNVff/8NUvn9XXNJMv5hhKnTvYP3ymHmj/IMBSbmHjjP8fycxv+MEzGqtatUI1vK7R7wM4J32XUv3Li/l5imGBkbtXlsMw/PF1jXozg3iP3aM0Z8XRBFDr1fl7Og9EnTguXx30p/6fjAIozb/i6C4BdB2z6QOfH27ZUFNL0QUELs/tycs4J2zdGEerCADTM1P8df3PxyUZiKPF8zyGHpwGFLC6QCCRuYA4QQQ6mw4hjhYT77GN2qiBWFnV5sXc0xMAgfKx5foVYKURB2vLPbxoM8auODE6ws/fH+77dHtjhxp5OHzywpWf/IIsRk25Rq0CRFY4UVNNndkmqZFRf3YuWaYUS0aE3RP5MXX3/iCOgu3Y1WYfL90sdSDgxRoYTY5mZ2anjwC/AqEBdM4tMJ4dwYs1YiOGSBkYrVHqlQNxoC34OV8BdxK9gQ+guo7bwdYdrXu92nU6nR7Pzc/PSXxfazTj30IpRaWpIqbX4UQoBDkWJlRwLzVeLBTyGs2UcUX5duzvsfPqNpvDovQhnBIdELGaWFk1hclYmLx2fxmFbNm5qXJNvdi2DQ1WAsO1P/5petshyf6v25gJ0nxUUUeiP531s/kvAcor7MDOL2pjz3JzVKpqUoMTpb8QsSqdeTT98fomjyDv8LP5aKI3uAgQP2orCkGAVRVMZxbQhnQJIAzkm3s3Js6GSfkEQEd4+bpa8+TW71P1ANqoB65I93OA2DgV+Z+omgAAAABJRU5ErkJggg==
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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 NETSCAPE-Bookmark-file-1>
+<meta charset="UTF-8">
+<title>@bookmarks_title@</title>
+<h1>@bookmarks_heading@</h1>
+
+<dl><p>
+ <dt><h3 personal_toolbar_folder="true">@bookmarks_toolbarfolder@</h3></dt>
+ <dd>@bookmarks_toolbarfolder_description@
+#ifndef NIGHTLY_BUILD
+ <dl>
+ <p><dt><a href="https://www.mozilla.org/@AB_CD@/firefox/central/" icon="@firefox_icon@">@getting_started@</a></dt>
+ </dl>
+ <p><dt><h3>@firefox_heading@</h3></dt>
+ <dl><p>
+ <dt><a href="https://www.mozilla.org/@AB_CD@/firefox/help/" icon="@mozilla_icon@">@firefox_help@</a>
+ <dt><a href="https://www.mozilla.org/@AB_CD@/firefox/customize/" icon="@mozilla_icon@">@firefox_customize@</a>
+ <dt><a href="https://www.mozilla.org/@AB_CD@/contribute/" icon="@mozilla_icon@">@firefox_community@</a>
+ <dt><a href="https://www.mozilla.org/@AB_CD@/about/" icon="@mozilla_icon@">@firefox_about@</a>
+ </dl>
+#else
+ <dl>
+ <p><dt><a href="https://www.mozilla.org/@AB_CD@/contribute/" icon="@mozilla_icon@">@firefox_community@</a>
+ </dl>
+ <p><dt><h3>@nightly_heading@</h3></dt>
+ <dl><p>
+ <dt><a href="https://blog.nightly.mozilla.org/" icon="@nightly_icon@">@nightly_blog@</a>
+ <dt><a href="https://bugzilla.mozilla.org/" icon="@bugzilla_icon@" shortcuturl="bz">@bugzilla@</a>
+ <dt><a href="https://developer.mozilla.org/" icon="@mdn_icon@" shortcuturl="mdn">@mdn@</a>
+ <dt><a href="https://addons.mozilla.org/@AB_CD@/firefox/addon/nightly-tester-tools/" icon="@addon_icon@">@nightly_tester_tools@</a>
+ <dt><a href="about:crashes" icon="@mozilla_icon@">@crashes@</a>
+ <dt><a href="https://mibbit.com/?server=irc.mozilla.org&channel=%23nightly" icon="@mozilla_icon@">@irc@</a>
+ <dt><a href="https://planet.mozilla.org/" icon="@mozilla_icon@">@planet@</a>
+ </dl>
+#endif
+</dl>
diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn
new file mode 100644
index 000000000..eff09189b
--- /dev/null
+++ b/browser/locales/jar.mn
@@ -0,0 +1,120 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+@AB_CD@.jar:
+% locale browser @AB_CD@ %locale/browser/
+* locale/browser/bookmarks.html (generic/profile/bookmarks.html.in)
+ locale/browser/aboutAccounts.dtd (%chrome/browser/aboutAccounts.dtd)
+ locale/browser/aboutDialog.dtd (%chrome/browser/aboutDialog.dtd)
+ locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)
+ locale/browser/aboutPrivateBrowsing.properties (%chrome/browser/aboutPrivateBrowsing.properties)
+ locale/browser/aboutRobots.dtd (%chrome/browser/aboutRobots.dtd)
+ locale/browser/aboutHome.dtd (%chrome/browser/aboutHome.dtd)
+ locale/browser/accounts.properties (%chrome/browser/accounts.properties)
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ locale/browser/aboutHealthReport.dtd (%chrome/browser/aboutHealthReport.dtd)
+#endif
+ locale/browser/aboutSearchReset.dtd (%chrome/browser/aboutSearchReset.dtd)
+ locale/browser/aboutSessionRestore.dtd (%chrome/browser/aboutSessionRestore.dtd)
+ locale/browser/aboutTabCrashed.dtd (%chrome/browser/aboutTabCrashed.dtd)
+ locale/browser/syncCustomize.dtd (%chrome/browser/syncCustomize.dtd)
+ locale/browser/aboutSyncTabs.dtd (%chrome/browser/aboutSyncTabs.dtd)
+ locale/browser/browser.dtd (%chrome/browser/browser.dtd)
+ locale/browser/baseMenuOverlay.dtd (%chrome/browser/baseMenuOverlay.dtd)
+ locale/browser/browser.properties (%chrome/browser/browser.properties)
+ locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
+ locale/browser/lightweightThemes.properties (%chrome/browser/lightweightThemes.properties)
+ locale/browser/newTab.dtd (%chrome/browser/newTab.dtd)
+ locale/browser/newTab.properties (%chrome/browser/newTab.properties)
+ locale/browser/pageInfo.dtd (%chrome/browser/pageInfo.dtd)
+ locale/browser/pageInfo.properties (%chrome/browser/pageInfo.properties)
+ locale/browser/quitDialog.properties (%chrome/browser/quitDialog.properties)
+ locale/browser/safeMode.dtd (%chrome/browser/safeMode.dtd)
+ locale/browser/sanitize.dtd (%chrome/browser/sanitize.dtd)
+ locale/browser/search.properties (%chrome/browser/search.properties)
+ locale/browser/searchbar.dtd (%chrome/browser/searchbar.dtd)
+ locale/browser/sitePermissions.properties (%chrome/browser/sitePermissions.properties)
+ locale/browser/engineManager.properties (%chrome/browser/engineManager.properties)
+ locale/browser/setDesktopBackground.dtd (%chrome/browser/setDesktopBackground.dtd)
+ locale/browser/shellservice.properties (%chrome/browser/shellservice.properties)
+ locale/browser/tabbrowser.properties (%chrome/browser/tabbrowser.properties)
+ locale/browser/taskbar.properties (%chrome/browser/taskbar.properties)
+ locale/browser/translation.dtd (%chrome/browser/translation.dtd)
+ locale/browser/translation.properties (%chrome/browser/translation.properties)
+ locale/browser/webrtcIndicator.properties (%chrome/browser/webrtcIndicator.properties)
+ locale/browser/downloads/downloads.dtd (%chrome/browser/downloads/downloads.dtd)
+ locale/browser/downloads/downloads.properties (%chrome/browser/downloads/downloads.properties)
+ locale/browser/places/places.dtd (%chrome/browser/places/places.dtd)
+ locale/browser/places/places.properties (%chrome/browser/places/places.properties)
+ locale/browser/places/editBookmarkOverlay.dtd (%chrome/browser/places/editBookmarkOverlay.dtd)
+ locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
+ locale/browser/preferences/selectBookmark.dtd (%chrome/browser/preferences/selectBookmark.dtd)
+ locale/browser/places/moveBookmarks.dtd (%chrome/browser/places/moveBookmarks.dtd)
+ locale/browser/safebrowsing/phishing-afterload-warning-message.dtd (%chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd)
+ locale/browser/safebrowsing/report-phishing.dtd (%chrome/browser/safebrowsing/report-phishing.dtd)
+ locale/browser/feeds/subscribe.dtd (%chrome/browser/feeds/subscribe.dtd)
+ locale/browser/feeds/subscribe.properties (%chrome/browser/feeds/subscribe.properties)
+ locale/browser/migration/migration.dtd (%chrome/browser/migration/migration.dtd)
+ locale/browser/migration/migration.properties (%chrome/browser/migration/migration.properties)
+ locale/browser/preferences/advanced.dtd (%chrome/browser/preferences/advanced.dtd)
+ locale/browser/preferences/applicationManager.dtd (%chrome/browser/preferences/applicationManager.dtd)
+ locale/browser/preferences/applicationManager.properties (%chrome/browser/preferences/applicationManager.properties)
+ locale/browser/preferences/blocklists.dtd (%chrome/browser/preferences/blocklists.dtd)
+ locale/browser/preferences/colors.dtd (%chrome/browser/preferences/colors.dtd)
+ locale/browser/preferences/cookies.dtd (%chrome/browser/preferences/cookies.dtd)
+ locale/browser/preferences/content.dtd (%chrome/browser/preferences/content.dtd)
+ locale/browser/preferences/connection.dtd (%chrome/browser/preferences/connection.dtd)
+ locale/browser/preferences/donottrack.dtd (%chrome/browser/preferences/donottrack.dtd)
+ locale/browser/preferences/applications.dtd (%chrome/browser/preferences/applications.dtd)
+ locale/browser/preferences/fonts.dtd (%chrome/browser/preferences/fonts.dtd)
+ locale/browser/preferences/main.dtd (%chrome/browser/preferences/main.dtd)
+ locale/browser/preferences/languages.dtd (%chrome/browser/preferences/languages.dtd)
+ locale/browser/preferences/permissions.dtd (%chrome/browser/preferences/permissions.dtd)
+ locale/browser/preferences/preferences.dtd (%chrome/browser/preferences/preferences.dtd)
+ locale/browser/preferences/preferences.properties (%chrome/browser/preferences/preferences.properties)
+ locale/browser/preferences/containers.properties (%chrome/browser/preferences/containers.properties)
+ locale/browser/preferences/privacy.dtd (%chrome/browser/preferences/privacy.dtd)
+ locale/browser/preferences/security.dtd (%chrome/browser/preferences/security.dtd)
+ locale/browser/preferences/containers.dtd (%chrome/browser/preferences/containers.dtd)
+ locale/browser/preferences/sync.dtd (%chrome/browser/preferences/sync.dtd)
+ locale/browser/preferences/tabs.dtd (%chrome/browser/preferences/tabs.dtd)
+ locale/browser/preferences/search.dtd (%chrome/browser/preferences/search.dtd)
+ locale/browser/preferences/translation.dtd (%chrome/browser/preferences/translation.dtd)
+ locale/browser/syncBrand.dtd (%chrome/browser/syncBrand.dtd)
+ locale/browser/syncSetup.dtd (%chrome/browser/syncSetup.dtd)
+ locale/browser/syncSetup.properties (%chrome/browser/syncSetup.properties)
+ locale/browser/syncGenericChange.properties (%chrome/browser/syncGenericChange.properties)
+ locale/browser/syncKey.dtd (%chrome/browser/syncKey.dtd)
+ locale/browser/syncQuota.dtd (%chrome/browser/syncQuota.dtd)
+ locale/browser/syncQuota.properties (%chrome/browser/syncQuota.properties)
+% resource search-plugins chrome://browser/locale/searchplugins/
+#if BUILD_FASTER
+ locale/browser/searchplugins/ (searchplugins/*.xml)
+ locale/browser/searchplugins/list.json (search/list.json)
+#else
+ locale/browser/searchplugins/ (.deps/generated_@AB_CD@/*.xml)
+ locale/browser/searchplugins/list.json (.deps/generated_@AB_CD@/list.json)
+#endif
+ locale/browser/searchplugins/images/yandex-en.ico (searchplugins/images/yandex-en.ico)
+% locale browser-region @AB_CD@ %locale/browser-region/
+ locale/browser-region/region.properties (%chrome/browser-region/region.properties)
+# the following files are browser-specific overrides
+ locale/browser/netError.dtd (%chrome/overrides/netError.dtd)
+ locale/browser/appstrings.properties (%chrome/overrides/appstrings.properties)
+ locale/browser/downloads/settingsChange.dtd (%chrome/overrides/settingsChange.dtd)
+% override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
+% override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
+% override chrome://mozapps/locale/downloads/settingsChange.dtd chrome://browser/locale/downloads/settingsChange.dtd
+% locale pdf.js @AB_CD@ %locale/pdfviewer/
+ locale/pdfviewer/viewer.properties (%pdfviewer/viewer.properties)
+ locale/pdfviewer/chrome.properties (%pdfviewer/chrome.properties)
+
+#ifdef XPI_NAME
+# Bug 1240628, restructure how l10n repacks work with feature addons
+# This is hacky, but ensures the chrome.manifest chain is complete
+[.] chrome.jar:
+% manifest features/chrome.manifest
+#endif
diff --git a/browser/locales/l10n.ini b/browser/locales/l10n.ini
new file mode 100644
index 000000000..2e387e97a
--- /dev/null
+++ b/browser/locales/l10n.ini
@@ -0,0 +1,22 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[general]
+depth = ../..
+all = browser/locales/all-locales
+
+[compare]
+dirs = browser
+ other-licenses/branding/firefox
+ browser/branding/official
+ devtools/client
+
+[includes]
+# non-central apps might want to use %(topsrcdir)s here, or other vars
+# RFE: that needs to be supported by compare-locales, too, though
+toolkit = toolkit/locales/l10n.ini
+services_sync = services/sync/locales/l10n.ini
+
+[extras]
+dirs = extensions/spellcheck
diff --git a/browser/locales/moz.build b/browser/locales/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/locales/search/list.json b/browser/locales/search/list.json
new file mode 100644
index 000000000..5003126fc
--- /dev/null
+++ b/browser/locales/search/list.json
@@ -0,0 +1,842 @@
+{
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazondotcom", "bing", "ddg", "twitter", "wikipedia"
+ ]
+ },
+ "locales": {
+ "en-US": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazondotcom", "bing", "ddg", "twitter", "wikipedia"
+ ]
+ },
+ "experimental-hidden": {
+ "visibleDefaultEngines": [
+ "yahoo-en-CA", "yandex-en", "google-2018"
+ ]
+ }
+ },
+ "ach": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia"
+ ]
+ }
+ },
+ "af": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "wikipedia-af"
+ ]
+ }
+ },
+ "an": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-es", "bing", "wikipedia-an", "ddg", "twitter"
+ ]
+ }
+ },
+ "ar": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "wikipedia-ar"
+ ]
+ }
+ },
+ "as": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "amazondotcom", "ddg", "wikipedia-as"
+ ]
+ }
+ },
+ "ast": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-es", "bing", "diccionariu-alla", "ddg", "wikipedia-ast"
+ ]
+ }
+ },
+ "az": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "amazondotcom", "azerdict", "bing", "ddg", "wikipedia-az", "yandex-az"
+ ]
+ }
+ },
+ "bg": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "diribg", "amazondotcom", "ddg", "portalbgdict", "wikipedia-bg"
+ ]
+ }
+ },
+ "bn-BD": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "ddg", "wikipedia-bn"
+ ]
+ }
+ },
+ "bn-IN": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "amazondotcom", "bing", "ddg", "rediff", "wikipedia-bn"
+ ]
+ }
+ },
+ "br": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-france", "amazon-france", "ddg", "freelang", "klask", "wikipedia-br"
+ ]
+ }
+ },
+ "bs": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "ddg", "olx", "twitter", "wikipedia-bs"
+ ]
+ }
+ },
+ "ca": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "bing", "diec2", "ddg", "twitter", "wikipedia-ca"
+ ]
+ }
+ },
+ "cak": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-espanol", "bing", "amazondotcom", "ddg", "wikipedia-es"
+ ]
+ }
+ },
+ "cs": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "seznam-cz", "ddg", "heureka-cz", "mapy-cz", "wikipedia-cz"
+ ]
+ }
+ },
+ "cy": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-en-GB", "amazon-en-GB", "ddg", "palasprint", "termau", "wikipedia-cy"
+ ]
+ }
+ },
+ "da": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "bing", "amazon-en-GB", "ddg", "wikipedia-da"
+ ]
+ }
+ },
+ "de": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-de", "amazondotcom-de", "bing", "ddg", "leo_ende_de", "wikipedia-de"
+ ]
+ }
+ },
+ "dsb": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-de", "bing", "amazondotcom-de", "ddg", "leo_ende_de", "wikipedia-dsb"
+ ]
+ }
+ },
+ "el": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazon-en-GB", "bing", "ddg", "wikipedia-el"
+ ]
+ }
+ },
+ "en-GB": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-en-GB", "bing", "amazon-en-GB", "chambers-en-GB", "ddg", "twitter", "wikipedia"
+ ]
+ },
+ "experimental-hidden": {
+ "visibleDefaultEngines": [
+ "yandex-en"
+ ]
+ }
+ },
+ "en-ZA": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "bing", "amazondotcom", "ddg", "twitter", "wikipedia"
+ ]
+ }
+ },
+ "eo": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "reta-vortaro", "wikipedia-eo"
+ ]
+ }
+ },
+ "es-AR": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-ar", "amazondotcom", "drae", "ddg", "mercadolibre-ar", "wikipedia-es"
+ ]
+ }
+ },
+ "es-CL": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-cl", "bing", "drae", "ddg", "mercadolibre-cl", "wikipedia-es"
+ ]
+ }
+ },
+ "es-ES": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-es", "bing", "drae", "ddg", "twitter", "wikipedia-es"
+ ]
+ }
+ },
+ "es-MX": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-mx", "bing", "ddg", "mercadolibre-mx", "wikipedia-es"
+ ]
+ }
+ },
+ "et": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "neti-ee", "ddg", "osta-ee", "wikipedia-et", "eki-ee"
+ ]
+ }
+ },
+ "eu": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazon-en-GB", "ddg", "elebila", "wikipedia-eu"
+ ]
+ }
+ },
+ "fa": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazondotcom", "bing", "ddg", "wikipedia-fa"
+ ]
+ }
+ },
+ "ff": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-france", "bing", "amazon-france", "ddg", "cnrtl-tlfi-fr", "wikipedia-fr"
+ ]
+ }
+ },
+ "fi": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-fi", "bing", "bookplus-fi", "ddg", "wikipedia-fi"
+ ]
+ }
+ },
+ "fr": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-france", "bing", "amazon-france", "ddg", "cnrtl-tlfi-fr", "wikipedia-fr"
+ ]
+ }
+ },
+ "fy-NL": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-fy-NL", "bing", "bolcom-fy-NL", "ddg", "marktplaats-fy-NL", "wikipedia-fy-NL"
+ ]
+ }
+ },
+ "ga-IE": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-en-GB", "amazon-en-GB", "ddg", "tearma", "twitter", "wikipedia-ga-IE"
+ ]
+ }
+ },
+ "gd": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-en-GB", "faclair-beag", "amazon-en-GB", "bbc-alba", "ddg", "wikipedia-gd"
+ ]
+ }
+ },
+ "gl": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-es", "amazon-en-GB", "ddg", "wikipedia-gl"
+ ]
+ }
+ },
+ "gn": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-es", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-gn"
+ ]
+ }
+ },
+ "gu-IN": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "bing", "ddg", "gujaratilexicon", "wikipedia-gu"
+ ]
+ }
+ },
+ "he": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "ddg", "wikipedia-he", "morfix-dic"
+ ]
+ }
+ },
+ "hi-IN": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "bing", "ddg", "wikipedia-hi"
+ ]
+ }
+ },
+ "hr": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazon-en-GB", "bing", "ddg", "eudict", "twitter", "wikipedia-hr"
+ ]
+ }
+ },
+ "hsb": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-de", "bing", "amazondotcom-de", "ddg", "leo_ende_de", "wikipedia-hsb"
+ ]
+ }
+ },
+ "hu": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "ddg", "sztaki-en-hu", "vatera", "wikipedia-hu"
+ ]
+ }
+ },
+ "hy-AM": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazondotcom", "ddg", "list-am", "wikipedia-hy"
+ ]
+ }
+ },
+ "id": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-id", "ddg", "wikipedia-id"
+ ]
+ }
+ },
+ "is": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "leit-is", "wikipedia-is"
+ ]
+ }
+ },
+ "it": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-it", "bing", "amazon-it", "ddg", "hoepli", "wikipedia-it"
+ ]
+ }
+ },
+ "ja-JP-mac": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-jp", "bing", "amazon-jp", "rakuten", "yahoo-jp-auctions", "oshiete-goo", "twitter-ja", "wikipedia-ja", "ddg"
+ ]
+ }
+ },
+ "ja": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-jp", "bing", "amazon-jp", "rakuten", "yahoo-jp-auctions", "oshiete-goo", "twitter-ja", "wikipedia-ja", "ddg"
+ ]
+ }
+ },
+ "ka": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-ka"
+ ]
+ }
+ },
+ "kab": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-france", "bing", "ddg", "wikipedia-kab"
+ ]
+ }
+ },
+ "kk": {
+ "default": {
+ "visibleDefaultEngines": [
+ "yandex-kk", "google", "ddg", "flip", "kaz-kk", "twitter", "wikipedia-kk"
+ ]
+ },
+ "KZ": {
+ "visibleDefaultEngines": [
+ "yandex-kk", "google-nocodes", "ddg", "flip", "kaz-kk", "twitter", "wikipedia-kk"
+ ]
+ },
+ "BY": {
+ "visibleDefaultEngines": [
+ "yandex-kk", "google-nocodes", "ddg", "flip", "kaz-kk", "twitter", "wikipedia-kk"
+ ]
+ },
+ "RU": {
+ "visibleDefaultEngines": [
+ "yandex-kk", "google-nocodes", "ddg", "flip", "kaz-kk", "twitter", "wikipedia-kk"
+ ]
+ },
+ "TR": {
+ "visibleDefaultEngines": [
+ "yandex-kk", "google-nocodes", "ddg", "flip", "kaz-kk", "twitter", "wikipedia-kk"
+ ]
+ },
+ "UA": {
+ "visibleDefaultEngines": [
+ "yandex-kk", "google-nocodes", "ddg", "flip", "kaz-kk", "twitter", "wikipedia-kk"
+ ]
+ }
+ },
+ "km": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-km"
+ ]
+ }
+ },
+ "kn": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "bing", "amazondotcom", "ddg", "kannadastore", "wikipedia-kn"
+ ]
+ }
+ },
+ "ko": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "ddg", "naver-kr", "danawa-kr", "daum-kr", "wikipedia-kr"
+ ]
+ }
+ },
+ "lij": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-it", "bing", "amazon-it", "ddg", "paroledigenova-lij", "wikipedia-lij"
+ ]
+ }
+ },
+ "lt": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "wikipedia-lt", "bing", "amazondotcom", "ddg", "twitter"
+ ]
+ }
+ },
+ "ltg": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "dict-enlv", "ddg", "salidzinilv", "sslv", "wikipedia-lv"
+ ]
+ }
+ },
+ "lv": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "dict-enlv", "ddg", "salidzinilv", "sslv", "wikipedia-lv"
+ ]
+ }
+ },
+ "mai": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "bing", "ddg", "twitter", "wikipedia-hi"
+ ]
+ }
+ },
+ "mk": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "wikipedia-mk"
+ ]
+ }
+ },
+ "ml": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "webdunia", "bing", "ddg", "rediff", "wikipedia", "wikipedia-ml"
+ ]
+ }
+ },
+ "mr": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "amazondotcom", "ddg", "rediff", "wikipedia-mr"
+ ]
+ }
+ },
+ "ms": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-ms"
+ ]
+ }
+ },
+ "my": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-my"
+ ]
+ }
+ },
+ "nb-NO": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-NO", "amazon-en-GB", "bing", "ddg", "gulesider-NO", "bok-NO", "qxl-NO", "wikipedia-NO"
+ ]
+ }
+ },
+ "ne-NP": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "ddg", "twitter", "wikipedia-ne"
+ ]
+ }
+ },
+ "nl": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "bing", "bolcom-nl", "ddg", "marktplaats-nl", "wikipedia-nl"
+ ]
+ }
+ },
+ "nn-NO": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "bing", "amazon-en-GB", "ddg", "gulesider-NO", "bok-NO", "qxl-NO", "wikipedia-NN"
+ ]
+ }
+ },
+ "or": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "bing", "amazondotcom", "ddg", "wikipedia-or"
+ ]
+ }
+ },
+ "pa-IN": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "bing", "ddg", "wikipedia-pa"
+ ]
+ }
+ },
+ "pl": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "allegro-pl", "ddg", "pwn-pl", "wikipedia-pl", "wolnelektury-pl"
+ ]
+ }
+ },
+ "pt-BR": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-br", "bing", "buscape", "ddg", "mercadolivre", "twitter", "wikipedia-pt"
+ ]
+ }
+ },
+ "pt-PT": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "amazon-en-GB", "ddg", "priberam", "sapo", "wikipedia-pt"
+ ]
+ }
+ },
+ "rm": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-ch", "bing", "ddg", "leo_ende_de-rm", "pledarigrond", "wikipedia-rm"
+ ]
+ }
+ },
+ "ro": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "wikipediaro"
+ ]
+ }
+ },
+ "ru": {
+ "default": {
+ "visibleDefaultEngines": [
+ "yandex-ru", "google", "ddg", "ozonru", "priceru", "wikipedia-ru", "mailru"
+ ]
+ },
+ "RU": {
+ "visibleDefaultEngines": [
+ "yandex-ru", "google-nocodes", "ddg", "ozonru", "priceru", "wikipedia-ru", "mailru"
+ ]
+ },
+ "BY": {
+ "visibleDefaultEngines": [
+ "yandex-ru", "google-nocodes", "ddg", "ozonru", "priceru", "wikipedia-ru", "mailru"
+ ]
+ },
+ "KZ": {
+ "visibleDefaultEngines": [
+ "yandex-ru", "google-nocodes", "ddg", "ozonru", "priceru", "wikipedia-ru", "mailru"
+ ]
+ },
+ "TR": {
+ "visibleDefaultEngines": [
+ "yandex-ru", "google-nocodes", "ddg", "ozonru", "priceru", "wikipedia-ru", "mailru"
+ ]
+ },
+ "UA": {
+ "visibleDefaultEngines": [
+ "yandex-ru", "google-nocodes", "ddg", "ozonru", "priceru", "wikipedia-ru", "mailru"
+ ]
+ }
+ },
+ "si": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazondotcom", "ddg", "wikipedia-si"
+ ]
+ }
+ },
+ "sk": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "azet-sk", "atlas-sk", "ddg", "dunaj-sk", "slovnik-sk", "wikipedia-sk", "zoznam-sk"
+ ]
+ }
+ },
+ "sl": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "ceneji", "ddg", "najdi-si", "odpiralni", "twitter", "wikipedia-sl"
+ ]
+ }
+ },
+ "son": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-france", "bing", "amazon-france", "ddg", "cnrtl-tlfi-fr", "wikipedia-fr"
+ ]
+ }
+ },
+ "sq": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazon-en-GB", "ddg", "wikipedia-sq"
+ ]
+ }
+ },
+ "sr": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "amazon-en-GB", "bing", "ddg", "wikipedia-sr", "pogodak"
+ ]
+ }
+ },
+ "sv-SE": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-sv-SE", "bing", "allaannonser-sv-SE", "ddg", "prisjakt-sv-SE", "tyda-sv-SE", "wikipedia-sv-SE"
+ ]
+ }
+ },
+ "ta": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "ddg", "wikipedia-ta"
+ ]
+ }
+ },
+ "te": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "amazondotcom", "ddg", "wikipedia-te", "wiktionary-te"
+ ]
+ }
+ },
+ "th": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "amazondotcom", "bing", "ddg", "longdo", "wikipedia-th"
+ ]
+ }
+ },
+ "tl": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-tl", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-tl"
+ ]
+ }
+ },
+ "tr": {
+ "default": {
+ "visibleDefaultEngines": [
+ "yandex-tr", "google", "ddg", "twitter", "wikipedia-tr"
+ ]
+ },
+ "TR": {
+ "visibleDefaultEngines": [
+ "yandex-tr", "google-nocodes", "ddg", "twitter", "wikipedia-tr"
+ ]
+ },
+ "BY": {
+ "visibleDefaultEngines": [
+ "yandex-tr", "google-nocodes", "ddg", "twitter", "wikipedia-tr"
+ ]
+ },
+ "KZ": {
+ "visibleDefaultEngines": [
+ "yandex-tr", "google-nocodes", "ddg", "twitter", "wikipedia-tr"
+ ]
+ },
+ "RU": {
+ "visibleDefaultEngines": [
+ "yandex-tr", "google-nocodes", "ddg", "twitter", "wikipedia-tr"
+ ]
+ },
+ "UA": {
+ "visibleDefaultEngines": [
+ "yandex-tr", "google-nocodes", "ddg", "twitter", "wikipedia-tr"
+ ]
+ }
+ },
+ "uk": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yandex-uk", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
+ ]
+ },
+ "UA": {
+ "visibleDefaultEngines": [
+ "google-nocodes", "yandex-uk", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
+ ]
+ },
+ "TR": {
+ "visibleDefaultEngines": [
+ "google-nocodes", "yandex-uk", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
+ ]
+ },
+ "BY": {
+ "visibleDefaultEngines": [
+ "google-nocodes", "yandex-uk", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
+ ]
+ },
+ "KZ": {
+ "visibleDefaultEngines": [
+ "google-nocodes", "yandex-uk", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
+ ]
+ },
+ "RU": {
+ "visibleDefaultEngines": [
+ "google-nocodes", "yandex-uk", "meta-ua", "ddg", "wikipedia-uk", "metamarket"
+ ]
+ }
+ },
+ "ur": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo-in", "bing", "amazon-in", "ddg", "twitter", "wikipedia-ur"
+ ]
+ }
+ },
+ "uz": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-uz"
+ ]
+ }
+ },
+ "vi": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "ddg", "wikipedia-vi", "zing-mp3"
+ ]
+ }
+ },
+ "wo": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "yahoo", "bing", "amazondotcom", "ddg", "wikipedia-wo"
+ ]
+ }
+ },
+ "xh": {
+ "default": {
+ "visibleDefaultEngines": [
+ "google", "bing", "ddg", "wikipedia"
+ ]
+ }
+ },
+ "zh-CN": {
+ "default": {
+ "visibleDefaultEngines": [
+ "baidu", "google", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
+ ]
+ },
+ "CN": {
+ "visibleDefaultEngines": [
+ "baidu", "google-nocodes", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
+ ]
+ },
+ "KZ": {
+ "visibleDefaultEngines": [
+ "baidu", "google-nocodes", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
+ ]
+ },
+ "BY": {
+ "visibleDefaultEngines": [
+ "baidu", "google-nocodes", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
+ ]
+ },
+ "RU": {
+ "visibleDefaultEngines": [
+ "baidu", "google-nocodes", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
+ ]
+ },
+ "TR": {
+ "visibleDefaultEngines": [
+ "baidu", "google-nocodes", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
+ ]
+ },
+ "UA": {
+ "visibleDefaultEngines": [
+ "baidu", "google-nocodes", "bing", "ddg", "wikipedia-zh-CN", "amazondotcn"
+ ]
+ }
+ },
+ "zh-TW": {
+ "default": {
+ "visibleDefaultEngines": [
+ "yahoo-zh-TW", "google", "ddg", "findbook-zh-TW", "wikipedia-zh-TW", "yahoo-zh-TW-HK", "yahoo-bid-zh-TW", "yahoo-answer-zh-TW"
+ ]
+ }
+ }
+ }
+}
diff --git a/browser/locales/searchplugins/allaannonser-sv-SE.xml b/browser/locales/searchplugins/allaannonser-sv-SE.xml
new file mode 100644
index 000000000..ed933e60f
--- /dev/null
+++ b/browser/locales/searchplugins/allaannonser-sv-SE.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Allaannonser</ShortName>
+<Description>Allaannonser</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMA/wD/AP83WBt9AAAACXBIWXMAAAsTAAALEwEAmpwYAAACPElEQVR42nVSS0iUYRQ995tfZ8YZlXF8gqUWUlJgkwUV0cKwRe3ctzDa9Ni1DqKNWtSih1QGtqkWgRa50VW1EMLUENNBMXXKsRpH/Wd0nv//nRZjmUJndeEeOOeee4QkdoCElWAsDHeJFPh3LI2tMR1DZJLD98Wcp8Mp5gKchTx5HW6/lB2ApyzHkpwCU6t4fR7LXyT2HeKAMgCANrQF5WDTFTndAZX3RyETx/AD+ToAZbCuBY1tqDoKpfBrAuM9MjuAsSe0UnLqBjxlIKmDb9jpZWeh/nCTiRX+i3RcjzzinVK2u/T8O5IKABMRWGnub5Vj1+D2bbsx3yuNbWy6DGqEh3M+Lf3iDG8VazPE/0Cn47xXw+4A03HF1KqY31gRkMJqBvv0xMttCX9+xum3ku/l3rNIrnBt3kBmHXYWvj0QQTKKbGKbpUQEIgDgrYKdkUzcgDIgCtl1ABK4iGxSR2ekwA/DxfiSHLqw+Ts7AxEqh4GCUriKEf5EKy3KQLBPR6cBQjlhp8RbqRpa4SlH6D2cRSjarcRwsa5FNpYQ7AXA6hPKXaJ+jKrFIWWlUNuMfC8WP8rPMVYcFk85SOrIFLv2satBRya1bZHUs4P2+HNqW2tbx8LsOc67lTo0RBK53PR0P2/7+LBejz7VG8s6saITUZ0y9VQvuwPscOuRx9QWyc0ugTYnX8lQO8wQimvp9kNEkqswF+D28chVaboER95W+TZTX5vDTD9mB2HOgRpFu1DTjPpzUn7wL+c37UxbeDY2F2wAAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="http://www.allaannonser.se/hitlist.php" resultdomain="allaannonser.se">
+ <Param name="sourceid" value="Mozilla-search"/>
+ <Param name="keyword" value="{searchTerms}"/>
+ <Param name="order" value="date"/>
+ <Param name="desc" value="1"/>
+</Url>
+<SearchForm>http://www.allaannonser.se</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/allegro-pl.xml b/browser/locales/searchplugins/allegro-pl.xml
new file mode 100644
index 000000000..06c063cc8
--- /dev/null
+++ b/browser/locales/searchplugins/allegro-pl.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Allegro</ShortName>
+<Description>Wyszukiwanie w aukcjach Allegro</Description>
+<InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAFr/OgBa/zkAWv84AFr/OABa/zgAWv85AFr/PABa/wn///8A////AP///wD///8A////AABa/xYAWv+4AFr//wBa//8AWv//AFr//wBa//8AWv//AFr//wBa//8AWv+H////AP///wD///8A////AP///wAAWv/wAFr//wBa//8AWv//AFr//wBa//8AWv//AFr//wBa//8AWv//AFr/gv///wD///8A////AP///wAAWv9MAFr//wBa//8AWv//AFr/ewBa/zUAWv82AFr/NABa/2YAWv//AFr//wBa/4H///8A////AP///wD///8AAFr/gwBa//8AWv//AFr/if///wD///8A////AP///wAAWv8yAFr//wBa//8AWv+A////AP///wD///8A////AABa/4MAWv//AFr//wBa/8L///8A////AP///wD///8AAFr/MQBa//8AWv//AFr/gP///wD///8A////AP///wAAWv8oAFr//wBa//8AWv//AFr/xABa/34AWv9/AFr/fQBa/5sAWv//AFr//wBa/4D///8A////AP///wD///8A////AABa/5QAWv//AFr//wBa//8AWv//AFr//wBa//8AWv//AFr//wBa//8AWv+A////AP///wD///8A////AP///wD///8AAFr/NwBa/7UAWv//AFr//wBa//8AWv//AFr//wBa//8AWv//AFr/gf///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AABa/2IAWv//AFr//wBa/4L///8A////AP///wD///8A////AP///wAAWv/RAFr/ywBa/0b///8A////AABa/woAWv/mAFr//wBa//8AWv85////AP///wD///8A////AP///wD///8AAFr//wBa//8AWv//AFr//wBa//8AWv//AFr//wBa//8AWv/v////AP///wD///8A////AP///wD///8A////AABa/5gAWv//AFr//wBa//8AWv//AFr//wBa//8AWv//AFr/J////wD///8A////AP///wD///8A////AP///wD///8AAFr/FgBa/14AWv+CAFr/gQBa/4IAWv9eAFr/BP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAP//AADwAwAA4AMAAOPjAADD4wAAw+MAAOHDAADgAwAA+AMAAP/jAADzxwAA8AcAAPAPAAD+PwAA//8AAA==</Image>
+ <SearchForm>http://allegro.pl</SearchForm>
+ <Url method="GET"
+ template="http://allegro.pl/listing/listing.php"
+ type="text/html">
+ <Param name="string" value="{searchTerms}" />
+ <Param name="sourceid" value="Mozilla-search" />
+ </Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazon-en-GB.xml b/browser/locales/searchplugins/amazon-en-GB.xml
new file mode 100644
index 000000000..bb385d072
--- /dev/null
+++ b/browser/locales/searchplugins/amazon-en-GB.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.co.uk</ShortName>
+<Description>Amazon.co.uk Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.co.uk/exec/obidos/external-search/" resultdomain="amazon.co.uk">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="ie" value="{inputEncoding}"/>
+ <Param name="mode" value="blended"/>
+ <Param name="tag" value="firefox-uk-21"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>https://www.amazon.co.uk/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazon-france.xml b/browser/locales/searchplugins/amazon-france.xml
new file mode 100644
index 000000000..98ec6caa7
--- /dev/null
+++ b/browser/locales/searchplugins/amazon-france.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.fr</ShortName>
+<Description>Recherche Amazon.fr</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.fr/exec/obidos/external-search/" resultdomain="amazon.fr">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="ie" value="{inputEncoding}"/>
+ <Param name="mode" value="blended"/>
+ <Param name="tag" value="firefox-fr-21"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>https://www.amazon.fr/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazon-in.xml b/browser/locales/searchplugins/amazon-in.xml
new file mode 100644
index 000000000..b4a656fb0
--- /dev/null
+++ b/browser/locales/searchplugins/amazon-in.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.in</ShortName>
+<Description>Amazon.in Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.in/exec/obidos/external-search/" resultdomain="amazon.in">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="ie" value="{inputEncoding}"/>
+ <Param name="mode" value="blended"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>https://www.amazon.in/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazon-it.xml b/browser/locales/searchplugins/amazon-it.xml
new file mode 100644
index 000000000..c1b321b1d
--- /dev/null
+++ b/browser/locales/searchplugins/amazon-it.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.it</ShortName>
+<Description>Ricerca Amazon.it</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.it/exec/obidos/external-search/" resultdomain="amazon.it">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="ie" value="{inputEncoding}"/>
+ <Param name="mode" value="blended"/>
+ <Param name="tag" value="firefoxit-21"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>https://www.amazon.it/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazon-jp.xml b/browser/locales/searchplugins/amazon-jp.xml
new file mode 100644
index 000000000..96620c30e
--- /dev/null
+++ b/browser/locales/searchplugins/amazon-jp.xml
@@ -0,0 +1,30 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.co.jp</ShortName>
+<Description>Amazon.co.jp Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.co.jp/exec/obidos/external-search/" resultdomain="amazon.co.jp">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="mode" value="blended"/>
+ <!--
+ <Param name="mode" value="books-jp"/>
+ <Param name="mode" value="books-us"/>
+ -->
+ <Param name="tag" value="mozillajapan-fx-22"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+ <!--
+ <Param name="sz" value="25"/>
+ <Param name="rank" value="+salesrank"/>
+ <Param name="rank" value="+pricerank"/>
+ <Param name="rank" value="+inverse-pricerank"/>
+ <Param name="rank" value="+daterank"/>
+ <Param name="rank" value="+titlerank"/>
+ <Param name="rank" value="-titlerank"/>
+ -->
+</Url>
+<SearchForm>https://www.amazon.co.jp/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazondotcn.xml b/browser/locales/searchplugins/amazondotcn.xml
new file mode 100644
index 000000000..460d41ca0
--- /dev/null
+++ b/browser/locales/searchplugins/amazondotcn.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>亚马逊</ShortName>
+<Description>亚马逊搜索</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.cn/mn/searchApp" resultdomain="amazon.cn">
+ <Param name="keywords" value="{searchTerms}"/>
+ <Param name="ix" value="sunray"/>
+ <Param name="pageletid" value="headsearch"/>
+ <Param name="searchType" value=""/>
+ <Param name="Go.x" value="0"/>
+ <Param name="Go.y" value="0"/>
+ <Param name="bestSaleNum" value="0"/>
+</Url>
+<SearchForm>https://www.amazon.cn/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazondotcom-de.xml b/browser/locales/searchplugins/amazondotcom-de.xml
new file mode 100644
index 000000000..3f8868d04
--- /dev/null
+++ b/browser/locales/searchplugins/amazondotcom-de.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.de</ShortName>
+<Description>Amazon.de Suche</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="https://www.amazon.de/exec/obidos/external-search/" resultdomain="amazon.de">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="ie" value="{inputEncoding}"/>
+ <Param name="mode" value="blended"/>
+ <Param name="tag" value="firefox-de-21"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>https://www.amazon.de/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/amazondotcom.xml b/browser/locales/searchplugins/amazondotcom.xml
new file mode 100644
index 000000000..8a1d6f247
--- /dev/null
+++ b/browser/locales/searchplugins/amazondotcom.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.com</ShortName>
+<Description>Amazon.com Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://completion.amazon.com/search/complete?q={searchTerms}&amp;search-alias=aps&amp;mkt=1"/>
+<Url type="text/html" method="GET" template="https://www.amazon.com/exec/obidos/external-search/" rel="searchform">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="ie" value="{inputEncoding}"/>
+ <Param name="mode" value="blended"/>
+ <Param name="tag" value="mozilla-20"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/atlas-sk.xml b/browser/locales/searchplugins/atlas-sk.xml
new file mode 100644
index 000000000..c1df83e3e
--- /dev/null
+++ b/browser/locales/searchplugins/atlas-sk.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Atlas</ShortName>
+<Description>Internetovy portal - Atlas.sk</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAALESURBVHjaYvz//z8DMvj7nuHXY4YfLxiA4hziDJzyDIyCyPIAAcSCYP55w/Bi7deHW188e/H5B/tfRg5mVnZuAWEJVXteWR8GVjGIKoAAYoTa8P3mz0tVj27f+8rrIazmxSkgx8TM+v/fr6/vH768vZ+L7aOKZTI7vw5QIUAAAW3+///7ve8H3W+sCHh+59zf/+jgz5//j64fO7e16fPrG0AuQAAxAI35e7n4xVr3L68f/8cNnt27fOXQit+/vgMEEOP/z5d+HMn+r97Eqehw/9Wf5Ue+/vrDyMLCxMLIyMTMKMzDbKPBqi7FBHTMz+/f2Dg4AAKIheH9JUYuTTYZK6DQ07d/Zu36+O0HIzs7MzMTEysrMycH26Zzv0t9OW3Umdk5uYBqAAKI5e+394y8mkysbECOhjRLTzTX9x9/mZiY/v//t+/0g1MvJO+/5t989pe5CicrM8jPAAHExMDE85+ZCxJkInwsqrJ8Pxk43nxl+vybjY2V9d+fn3/+Mjx88+/nb2h0AQQQCxOPIsvvuxDOhpPfGtd8fvWJgY0V6KL/zMyKzBwM//7++/2PCR67AAHExCigwcDMzvDn8/ffDGtOfHvwhoH970c7yftxhi9Nuc/+/vjk9z/mv38Z4dELEEAsDJxijJL2DEws//8w/GdgYmX+//c/44GTDw7/+/Lr509maTEmBsa///4zwKwACCBg0mD8zyPz689v5v8/HDUZTt399+OPMLO8x39GBjZGRqDCn7///vqDSEEAAcQCTBk/vn9/9+79t29fjcV/ZZp9P3Pv7/effxkY/nNyccnJyvAL8EkJMLGxQF0FEEDA+GFgZWVhYWUFJoGvX75JsT4zFXzz7t2nX7//crNwG4rquTiYcnKywm0ACCBGePL+8/fvn99/fv768+Pn71+//gDjAehadnY2QX4eNjZmuAaAAAMA9HBNfsA2E5MAAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="http://www.atlas.sk/search.php" resultdomain="atlas.sk">
+ <Param name="phrase" value="{searchTerms}"/>
+ <Param name="sourceid" value="firefox"/>
+</Url>
+<SearchForm>http://www.atlas.sk/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/azerdict.xml b/browser/locales/searchplugins/azerdict.xml
new file mode 100644
index 000000000..ca4a39bfd
--- /dev/null
+++ b/browser/locales/searchplugins/azerdict.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Azerdict</ShortName>
+<Description>Azərbaycanın Online Lüğəti</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAADYAQAA2AEAAAAAAAAAAAAAF+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/GuLx8yHU4+Ih1OPiIdTj4iHU4+Ih1OPiIdTj4hjm9vgX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/H9rp6FdYWPFXV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9WWVnnGuLx9Bfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xzg7+xVWVnmV1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/VVtb4Bnk9fUX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/Guj4+hzf7+8c3+/vHN/v7xzf7+8c3+/vHN/v7xjp+v0X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+n6/T+Mk79KdnnCSnZ5wkp2ecJKdnnCSnZ5wkp2ecI5m6TCF+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/yPP3dpXV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/x3d7O0X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/N52lwUSBhsBEgYbARIGGwESBhsBEgYbARIGGwDejqsQX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+n6/iHS4d4c4fDsF+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/HN/u7y21wMtBiI6/UGZny1dYWO5XV1f/VlhY6Bno+PcX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8Y6Pn9UmJj0VdXV/5XV1f/V1dX/1dXV/9XV1f/V1dX/lJmZ8sX6fr9F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/G9/v8lZWV/VXV1f/V1hY8FFnaMxCh47ALrfBzBzf7vAX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8f2ejpI8/d4Bfp+v4X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAACwAwAAsAMAAAAAAAAAAAAAF+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/GOj5/CXI19csucXNLLnFzSy5xc0sucXNLLnFzSy5xc0sucXNLLnFzSy5xc0sucXNLLnFzSy5xc0lzNnbF+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xjm9vZSYWLQV1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9ObnDIF+n6/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/NaStwVdXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/8nwc7XF+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8zqLHCV1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/iPO3N8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xjo+ftNb3HEV1dX/ldXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/U/jZTBF+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8a4/L0HN/v7xzf7+8c3+/vHN/v7xzf7+8c3+/vHN/v7xzf7+8c3+/vHN/v7xzf7+8c3+/vGOf3+hfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8Y6fr8I8/f3CXH1NYlx9TWJcfU1iXH1NYlx9TWJcfU1iXH1NYlx9TWJcfU1iXH1NYlx9TWJcfU1iLS4d8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/GOf391FhZM1XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/0pydcQX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/80p7HAV1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/yfG09oX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/zOoscJXV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f+I87d4Bfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/GOj5+01wc8NXV1f9V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX+0CJj8IX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xrj8vQc3+/vHN/v7xzf7+8c4O/uIdTj4iHU4+Ih1OPiIdTj4iHU4+Ih1OPiIdTj4iHU4+IZ5vb4F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/GOf4+iXI1dU4oam+Mq21wBjn+fkX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/x7Y5+UxrbfFRoCEvlNhY89XV1f0V1dX/1dXV/9XV1f/UWdpwxfq+/4X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xrk9fUpvsvRPZGXwE5qbcdWWVnnV1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/Htrq3Rfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/y+0wM1UX2DVV1dX+1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1ZYWOgZ6Pj2F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8f2efpV1dX+VdXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/tTYWLSKMLO0hfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/yy4w89XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV1f/V1dX/1dXV/9WWVnoT2ttyT6TmcEqwMzTGeT09xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/GuLz9VVaWt9XV1f/V1dX/1dXV/9XV1f3U2Fi0kZ+g8IzrbXJINjm6Rfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/HN/v7zuZocE6mqLCJ8XS2Bnn+PsX6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/xfq+/8X6vv/F+r7/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://api.azerdict.com/english/autocomplete">
+ <Param name="action" value="opensearch" />
+ <Param name="query" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="http://azerdict.com/english/" resultdomain="azerdict.com">
+ <Param name="word" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://azerdict.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/azet-sk.xml b/browser/locales/searchplugins/azet-sk.xml
new file mode 100644
index 000000000..cfd702196
--- /dev/null
+++ b/browser/locales/searchplugins/azet-sk.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Azet</ShortName>
+<Description>Azet - portal, kde je vzdy najviac ludi</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANinbf/GdRz/xnUc/8qDNv/Yp23/68+u/9inbf/Yp23/2Kdt/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMqDNv/GdRz/xnUc/8Z1HP/Kgzb/xnUc/8Z1HP/GdRz/xnUc/8qDNv8AAAAAAAAAAAAAAAAAAAAAAAAAAOvPrv/GdRz/xnUc/8qDNv8AAAAAAAAAAAAAAADYp23/xnUc/8Z1HP/Kgzb/AAAAAAAAAAAAAAAAAAAAAAAAAADrz67/xnUc/8Z1HP/Yp23/AAAAAAAAAAAAAAAA68+u/8Z1HP/GdRz/yoM2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMqDNv/GdRz/xnUc/9inbf/rz67/68+u/+vPrv/GdRz/xnUc/8qDNv8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2Kdt/8qDNv/GdRz/xnUc/8Z1HP/GdRz/xnUc/8Z1HP/Kgzb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADrz67/2Kdt/8Z1HP/GdRz/yoM2//qu2f/uR5n/7keZ/+5Hmf/uR5n/AAAAAMqDNv/Kgzb/yoM2/wAAAAAAAAAAAAAAAOvPrv/GdRz/xnUc/8qDNv/uR5n/7keZ/+5Hmf/uR5n/7keZ/wAAAADYp23/xnUc/8Z1HP/Kgzb/68+u/+vPrv/Kgzb/xnUc/8Z1HP/Yp23/7keZ/+5Hmf/uR5n/7keZ//qu2f8AAAAAAAAAANinbf/GdRz/xnUc/8Z1HP/GdRz/xnUc/8Z1HP/Yp23/AAAAAAAAAAD6rtn/AAAAAOvPrv/Kgzb/vXsA/9inbf8AAAAAAAAAAOvPrv/rz67/68+u/+vPrv8AAAAAAAAAAAAAAAAAAAAAAAAAAOvPrv/Bfwz/vXsA/717AP/Yp23/AAAAAFnCg/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKgzb/vXsA/717AP/Kgzb/AAAAAFnCg/9ZwoP/WcKD/wAAAAATof//E6H//wAAAAAAAAAAAAAAAAAAAAAAAAAA68+u/8F/DP/Bfwz/68+u/wAAAABZwoP/WcKD/1nCg/8AAAAAE6H//xOh//8Tof//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZwoP/WcKD/1nCg/9ZwoP/AAAAABOh//8Tof//E6H//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFnCg/9ZwoP/AAAAAAAAAAATof//E6H//xOh//8AAAAAAAAAAP4AAAD8AAAA+HAAAPhwAAD8AAAA/gAAAP/gAAAEcAAABAAAAAYBAAChhwAAwX8AAMInAADCIwAA/CMAAP5jAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wAAAAABgIAAAv///wDMZgAFtGQUM7VnEkW1ZxJFs2YSRrNnE0OzZxYvs2YaCv///wD///8Av2AgCLhmFBm1ahUYtWoVGK1mFBmuaBcW1YAABv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8As2YSRrVnE7m0ZxTyvm0V/75tFf+/bRX/u2sU/7NnE+6zZxO9s2YTa79AAAS0ZxNqs2cU2LNnFMy0ZxTLs2cT17RnE665XRcL////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/gAAC////ALNoFFuzZxT2wG0V/7VnFP+zZxP9tGcT+7NmE/m0ZxP7tGcU/7trFP+3aRT/s2cTubNnFNC+bRX/uWoU/7lqFP/CbxX/s2YTr////wD/AAAB////AP///wD///8A////AP///wD///8A////AP///wD///8AqlUAA////wC0aBEstGYT7bpqFP+0ZxP6tGYT/rNmFP+4aRT/wm8V/71sFf+9bBX/vmwV/7dpFP+5ahT/tGcU/7RnE/y0ZxP8s2cT/bZoFP+zZxN5////AICAAAL///8A////AP///wD///8A////AP///wD///8A////AP///wCAgAAC////ALRnE3fBbhX/tGcT+rRnFP+0ZhP+t2kU/7RnFMu0ZxNqs2cSVLVoElO1ZhR/s2YT4bdpFP+0ZhP+tGcU/7NmFP+zZxT/uGkU/7NoE3b///8A/4AAAv///wD///8A////AP///wD///8A////AP///wD///8A/wAAAf///wCqVQADs2YTorxrFP+zZxP9s2cT/bpqFP+zZhTbuGoVJP///wD///8A////AP///wCyZRIrs2cU2LlqFP+zZxP9s2YU/7NnFP+4aRT/tGcTd////wCqVQAD////AP///wD///8A////AP///wD///8A////AP///wD//wAB////ALZtJAezZhOvu2sU/7NnE/20ZxP7wG4V/7NnFLP///8AqnEcCapVAAaZZgAFqlUAA////wCzZhNstmgU/7RnFP+0ZhP+s2cU/7hpFP+0ZxN3////AP//AAH///8A////AP///wD///8A////AP///wD///8A////AP8AAAH///8A////ALNnFJe+bRX/s2YT/LRnE/y7axT/s2cT07tmEQ////8A////AICAAALVgAAG////ALNmEyizZxTzt2kU/7NnE/2zZxT/uGkU/7RnE3f///8A//8AAf///wD///8A////AP///wD///8A////AP///wD///8A////AKpVAAP///8AtGcUZr5sFf+0ZxP7tGYT/rRmE/64aRT/tGcTq7RkFDOxYhQN////AP///wD///8As2YaCrRmE++4aRT/s2cT/bNnFP+4aRT/tGcTd////wD//wAB////AP///wD///8A////AP///wD///8A////AP///wD///8AgIAAAv///wCuaw0TtGcUzMFuFf+zZxT/tGcT+7RmE/y7axT/s2cU9rNnE720ZxScs2YTh7NlFFuzZxI5s2cT7rhpFP+zZxP9s2cU/7hpFP+0ZxN3////AP//AAH///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AgIAAAv///wC3aRYutGcUv7VnFP++bRX/u2sU/7VoFP+1ZxT/uWoU/79tFf+/bRX/s2cT/bRnFOezZhP8tGcU/7RmE/6zZxT/uGkU/7RnE3f///8A//8AAf///wD///8A////AAAAAAH/AP8BgACAAoAAgAKAAIACgACAAv8AAAH///8AgIAAAv///wCqgAAGsmgTYLRnE66zZhTRs2cU67RnFP/AbhX/vWwV/7prFP+6ahT/uGkU/7RnFP+zZxT/s2YU/7NnFP+4aRT/tGcTd////wD//wAB////AP///wCAAIACAAAAAf///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALhjDhKzZhQys2cVSrRlFFi1ZhR1tGcUmbNmFNG3aBT/tGcU/7NnFP+zZhT/s2cU/7hpFP+0ZxN3////AP//AAH///8AgACAAv///wD///8AiTtiDYgzZg+SN20OkjdtDogzZg+LLnQL////AJlmAAW7ZhEPu2YRD7RpDxGvYBAQtm0kB////wD///8A////AP///wD/AAABtm0SDrJnFH6zZhT/tGcU/7RmE/6zZxT/uGkU/7JnE3f///8A//8AAYAAgAL///8AizdpLo01bZSMNWu6jTZrvow2bL2MNWu8jDVrxo02a40AAP8Cs2cAJbRnFLC1ZhTAtGcTvLRnE7yzZhPKsmUVSf///wC5dBcLv4AABKpVAAP///8Av2oVDLRmE/C4aRT/s2cT/bNnFP+4aRT/tGcTd////wD//wAB////AI84aimMNWvYmTp1/5I4cP+SOHD/kjhw/5E3b/+XOnT/jDZr1XYAiQ3JeQATtGYW38JvFf+5ahT/uWoU/8ZxFv+0ZxOu////AP///wD///8A////AP///wCzZhJGtGcT+rZoFP+zZxP9s2cU/7dpFP+zZhN2////AP//AAH///8AjDVsh5g6dP+MNWz5jTZr/Iw1bP2MNWz9jDZs+402a/yNNWz4jTlqOv///wCzZheevWwV/7RnFPizZhP8s2YT/LdoFP+zZhOdsmUTNbFpFi63aRYutGYUS7NnE9O4aRT/tGYT/rRmE/6zZxT/tmgU/7RoEkf///8AgIAAAv///wCMNWyVlzp0/4w1a/qNNmz/jDZs/402a/yMNmz/kjdw/5M4cP+NOGpg////ALBmHC2zZxTivWwV/7NmE/y0ZhP8tGcU/7trFP+0ZhP+s2cU67RnE/C4aRT/uWkU/7RmE/6zZxP9tGcT+r1sFf+0ZxPSuGMOEv///wCAgAAC////AIs2a3WXOXT/jDVs/Yw2bPuMNmv8kjhw/5A3bv+NNmvKhy50QoAAgAaqcRwJ////ALZoE0K0ZhPgvWwV/7prFP+0ZhP+tGYT+rNnE/20ZxT/tGcU/7RnE/u0ZxP6tGYT/rhpFP+/bRX/tGcT5LZoFjv///8AgIAAAv///wD///8AkjFtFY02a7WVOXL/lTlz/5U5cv+MNmvcijNtaf///wDmmQAKtGgKhLRnFLexYxUk////ALFpFCeyaBaVsmgV2LZoFP/BbhX/vm0V/75sFP++bBT/vm0U/8FuFf+5ahT/s2cT4rRnFKa0ZBQz////AP8AAAH///8A////AP8A/wH///8AkjdtDow0a12NNWuGjDdsZnAAjxD///8AtGgNYrNnFt28axX/v20V/7VmFNG2ZhJG////AP///wDZQAAUsmUVSbJqGGq0ZhWEs2cihrJoJoexaCeJsWcrcrJnGk+0ZBMp////AP///wCAgAAC////AP///wD///8A////AIAAgAL///8A////AP///wD///8AumwANLNnF8e3aRX/u2sU/7NmE/y0ZhP6wG4V/7NnFcb/AAABQrVaHwDJaxP///8A////AP///wD///8A////AP///wD///8A////AP///wD/AAABgIAAAv///wD///8A////AP///wD///8A////AIAAgAKZM2YF////ALFmG0GzZhXyvWwU/7RnFP+zZhP8tGYU/rRnFP+zaBX/uV0ATQDXeRM6tF3bObRc1T20XZ9As1tXAAD/ASSC8z8khPBVJYLyYCyD7mlxcaoJ////AAAA/wH///8A////AP///wD///8A////AP///wD///8AAAAAAf///wD///8As2YUor1sFf+zZxP7tGYT/rRnFP+zZhP8vW0V/7NnFKT///8ANbZeiz2/Y/85uF//Psho/zm2WsAkbf8HJILzviWJ/f8lhvj/JYv//ySE81X///8AAKr/A////wD///8A////AP///wD///8A////AP///wD/AAAB////AP///wC0ZxS3uGkU/7NmE/yzZxT/s2YT/LdpFP+zZxXyyEUAJQDFZTA7tFz7ObZe/zi1Xfo6umH/ObVaryuA/wYjg/O/JYv//yOE8/gkh/r/I4Py0iCA7xD///8AAID/Av///wD///8A////AP///wD///8A////AP///wD///8A////ALRnE4jBbhX/tGcU/7NnE/21ZxT/v20V/7RmEYT///8AO7Rduzu+Yv84tV38OLRd/Tq8Yv84tVqyK4D/BiOC874liv//JIPz+yOD8/0liv//JIPzgP///wAA//8B////AP///wD///8A////AP///wD///8A////AICAAAL///8As2YRHrRmE8W2aBT/uGkU/7ZoFP+zaBa4/wAAAiu3YFg6tl7/OLZe/zi1Xf44tF39Orxi/zi2WbErgP8GI4LzviWL//8jg/P8I4Py/iSF9/8kg/LtJIbzKv///wAAgP8C////AP///wD///8A////AP///wD///8A////AP8AAAH///8AsWIUDbVnEmG1ZxODtGcTXJ+AIAj///8AObNdsjzBZP84tV37OLVd/zi0Xf06vGL/ObVZtCRt/wcjgvO+JYr//yOD8/wjg/P/I4Pz/SWJ/v8jhPN8////AAAA/wH///8A////AP///wD///8A////AP///wD///8A////AP8AAAH///8A////AP///wD///8A////AP///wA5tV2wPMBj/zi1Xfo4tV3/OLRd/Dq8Yv84tVqfAED/BCOD88Iliv//JIPz+yOD8/8jg/P9JIf6/ySD8o7///8A////AAAA/wH///8A////AP///wD///8A////AP///wD///8A////AP8AAAGqVQADqlUAA/+AAAKAv0AE////ADm1Xm87v2P/Obhf/zi1Xfs5uV//O71i/za1ZFn///8AJIXxlCaO//8jg/P8I4Pz/COD8v8liv//IoPyYf///wAAgIAC////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAH///8AM7NmCjm2XY84tV33PMBj/zi1XvA5tFyCJLaSB////wAkie0cI4PzvSWL//8lif7/JYj8/yOD9KEagOYK////AAD//wH///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAD/AAH///8A////ADm2XjE5tVxIObVbLf///wD///8AAKqqA////wAiiO4PIoL0WiSE9Hgig/FKK4D/Bv///wAA//8B////AP///wD///8A////AP/kAwH/+AAB/9AAAv+gAAL/oAAC/0A8Av9AQgL/YDIC/6AOAv+gAAL/0AAC4CgAAs//AAKwIHgCQAAiAoAAPgKAEAACgBAAAoAIAAWAhAALQQMAN7wAf8/IAABf2AQAX9gAAC/4CAAv6AAAF/QQABf78AAb/BAQF//QEBf/7Ggv</Image>
+<Url type="text/html" method="GET" template="http://www.azet.sk/katalog/vyhladavanie/firmy/" resultdomain="azet.sk">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="k" value=""/>
+</Url>
+<SearchForm>http://www.azet.sk/katalog/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/baidu.xml b/browser/locales/searchplugins/baidu.xml
new file mode 100644
index 000000000..8b823e37a
--- /dev/null
+++ b/browser/locales/searchplugins/baidu.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>百度</ShortName>
+<Description>百度网页搜索</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64, AAABAAIAEBAAAAEACABoBQAAJgAAACAgAAABACAAqBAAAI4FAAAoAAAAEAAAACAAAAABAAgAAAAAAEABAAAAAAAAAAAAAAABAAAAAAAAAAAAAP///wDhMikA9b67AOI3LgDzr6wA8ZyYAO2EfwD//fwA6WxlAOZUTAD3yMUA9bu5AP3u7QDrdm8A6GVeAPfFwwD4z80A6WdgAPrg3gDsfXcA+dTSAP3x8ADsenUA8qaiAPzp6ADujokA+t3cAORFPQDyqKQA4jkxAPnW1ADnWVEA51tUAPvk4wD++PcA7H96APS3swD74uEA4z42AOVPRwDpaWMA9sC+APzs6wDnXVYA6GJbAP3z8gDxoZ0A8qOfAO6LhgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIGBQAFBQUABQUFAAUFBQAFBQUABQUFAAUFBQAFBQYAFwUFAAUFBQAFBQUABQUFAAUFBQAFBQUABQUFAAUFBQAFBQIAAQEBAAEBAQABAQEAAQEBAAEBAQABAQEAAQIFAAUFAQABAQEAJTVLAAICAgICAgICAgICAgICAgICAQEBAQEBAQEBAQEBAQECAgEBLgkCKDEaIAIPLgEBAgIBARgCAgICAgICAi8BAQICAQEvAgICAgICAgIwAQECAgEBFigCAgICAgItLgEBAgIBAQErKAICAgIsIwEBAQICAQglHyYnAgIoDSkEKgECAgEYAh4WHyAhIiMCAiQBAgIBGgICGwEBAQEBHAIdAQICARECEhMUFRYXGBkLAQECAgEBDQEOAg8FAgIQAQEBAgIBAQEBCQIKCwICDAEBAQICAQEBAQMEBQEGBwgBAQECAgEBAQEBAQEBAQEBAQEBAgICAgICAgICAgICAgICAgIAAABpAAAAaQAAAGkAAABpAAAAaQAAAGkAAABpAAAAaQAAAGkAAABpAAAAaQAAAGkAAABpAAAAaQAAAGkAAABpKAAAACAAAABAAAAAAQAgAAAAAACAEAAAAAAAAAAAAAAAAAAAAAAAAOEyKUjhMinn4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKefhMilI4TIp5OEyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKeThMin/4TIp//fFw/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////fFw//hMin/4TIp/+EyKf/hMin////////////////////////////97u3/8Z+a/+lpY//nW1T/6nFq/+6JhP/zran/98rI//rg3v/629n/+MzK//CXk//oZV7/5U9H/+EyKf/iNy7/6m5o//nU0v///////////////////////////+EyKf/hMin/4TIp/+EyKf//////////////////////++Lh/+RFPf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TQs//jPzf//////////////////////4TIp/+EyKf/hMin/4TIp///////////////////////pbGX/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/51lR///////////////////////hMin/4TIp/+EyKf/hMin/////////////////++fm/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/++fm/////////////////+EyKf/hMin/4TIp/+EyKf/////////////////2wL7/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/3yMX/////////////////4TIp/+EyKf/hMin/4TIp//////////////////W+u//hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp//jPzf/////////////////hMin/4TIp/+EyKf/hMin/////////////////++fm/+E0LP/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/jQzv//vj3/////////////////+EyKf/hMin/4TIp/+EyKf//////////////////////7YJ8/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp//Otqf//////////////////////4TIp/+EyKf/hMin/4TIp///////////////////////+9fX/5lRM/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/xn5r////////////////////////////hMin/4TIp/+EyKf/hMin////////////////////////////97u3/6GVe/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hNCz/9LKu/////////////////////////////////+EyKf/hMin/4TIp/+EyKf/////////////////////////////////++vr/63hy/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TQs//W5tv/51tT/8qOf//CXk//51NL/////////////////4TIp/+EyKf/hMin/4TIp/////////////////////////////////////////fz/63Zv/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/0sq7/9Lez/+E0LP/hMin/4TIp/+EyKf/1ubb////////////hMin/4TIp/+EyKf/hMin/////////////////9bm2/+lpY//oYlv/86+s///////++Pf/5lZP/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/7ouG///////lT0f/4TIp/+EyKf/hMin/4TIp/+ZRSv///////////+EyKf/hMin/4TIp/+EyKf////////////W+u//hMin/4TIp/+EyKf/hMin/8qai///////74uH/4z42/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+hiW//++vr//////+I5Mf/hMin/4TIp/+EyKf/hMin/4TIp////////////4TIp/+EyKf/hMin/4TIp////////////511W/+EyKf/hMin/4TIp/+EyKf/lSkL////////////2w8D/4jcu/+EyKf/hMin/4TIp/+EyKf/kSED//Ozr////////////4jcu/+EyKf/hMin/4TIp/+EyKf/hMin//vX1///////hMin/4TIp/+EyKf/hMin////////////iNy7/4TIp/+EyKf/hMin/4TIp/+EyKf/++Pf////////////4z83/6GBZ/+EyKf/jQzv/7H96//zs6//////////////////lTUX/4TIp/+EyKf/hMin/4TIp/+RIQP///////////+EyKf/hMin/4TIp/+EyKf///////////+I8M//hMin/4TIp/+EyKf/hMin/4TIp//749////////////////////////////////////////////////////////////+6Lhv/hMin/4TIp/+EyKf/hMin/8aGd////////////4TIp/+EyKf/hMin/4TIp////////////6m5o/+EyKf/hMin/4TIp/+EyKf/mVk///////////////////////////////////////////////fz/9sPA//fKyP///fz//vj3/+t2b//iPDP/5EhA/++VkP/////////////////hMin/4TIp/+EyKf/hMin////////////3ysj/4TIp/+EyKf/hMin/4TIp//bDwP//////8Z+a/+I8M//jQzv/9bu5/////////////fHw/+ZUTP/hMin/4TIp/+ZWT//86ej//////////////////////////////////////+EyKf/hMin/4TIp/+EyKf/////////////////yqKT/5EU9/+dZUf/1vrv///////bDwP/hMin/4TIp/+EyKf/hNCz/++Tj///////xnJj/4TIp/+EyKf/hMin/4TIp/+puaP//////////////////////////////////////4TIp/+EyKf/hMin/4TIp////////////////////////////////////////////6nFq/+EyKf/hMin/4TIp/+EyKf/vlZD//////+t2b//hMin/4TIp/+EyKf/hMin/4TIp//rb2f/////////////////////////////////hMin/4TIp/+EyKf/hMin////////////////////////////////////////////lTUX/4TIp/+EyKf/hMin/4TIp/+pxav//////6nFq/+EyKf/hMin/4TIp/+EyKf/hMin/862p/////////////////////////////////+EyKf/hMin/4TIp/+EyKf///////////////////////////////////////////+RIQP/hMin/4TIp/+EyKf/hMin/6m5o///////ypqL/4TIp/+EyKf/hMin/4TIp/+EyKf/1ubb/////////////////////////////////4TIp/+EyKf/hMin/4TIp////////////////////////////////////////////6nFq/+EyKf/hMin/4TIp/+EyKf/vlZD///////719f/lSkL/4TIp/+EyKf/hMin/5EhA//719f/////////////////////////////////hMin/4TIp/+EyKf/hMin////////////////////////////////////////////3ysj/4TIp/+EyKf/hMin/4jcu//zp6P////////////nY1//kRT3/4TIp/+I8M//4z83//////////////////////////////////////+EyKf/hMin/4TIp/+EyKf/////////////////////////////////////////////////zq6f/5EU9/+VNRf/3yMX///////////////////////3z8v/1u7n//fHw////////////////////////////////////////////4TIp/+EyKf/hMin/4TIp//fKyP////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////fKyP/hMin/4TIp/+EyKefhMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMinn4TIpP+EyKcnhMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIp/+EyKf/hMin/4TIpyeEyKT8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://www.baidu.com/su">
+ <Param name="wd" value="{searchTerms}"/>
+ <Param name="tn" value="monline_dg"/>
+ <Param name="ie" value="utf-8"/>
+ <Param name="action" value="opensearch"/>
+</Url>
+<Url type="text/html" method="GET" template="https://www.baidu.com/baidu" resultdomain="baidu.com">
+ <Param name="wd" value="{searchTerms}"/>
+ <Param name="tn" value="monline_dg"/>
+ <Param name="ie" value="utf-8"/>
+</Url>
+<SearchForm>https://www.baidu.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/bbc-alba.xml b/browser/locales/searchplugins/bbc-alba.xml
new file mode 100644
index 000000000..a755623a8
--- /dev/null
+++ b/browser/locales/searchplugins/bbc-alba.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>BBC ┐ BBC Alba</ShortName>
+<Description>Lorg BBC ┐ BBC Alba</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBACAAEAAQCwAAAAJgAAACAgEAABAAQA6AIAANYAAAAoAAAAEAAAACAAAAABAAEAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjDAAAUpAAAGMQAABSkAAAYwwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2F%2F8AAP%2F%2FAAD%2F%2FwAA%2F%2F8AAP%2F%2FAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAA%2F%2F8AAP%2F%2FAAD%2F%2FwAA%2F%2F8AACgAAAAgAAAAQAAAAAEABAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD%2F%2FwD%2FAAAA%2FwD%2FAP%2F%2FAAD%2F%2F%2F8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2F%2FgAAAD%2F%2BAAAAHj%2FgAAAAPAHgAAA8AeAAAeHAHAAAADwB4AAAPAHgAAIcAAAAAAA%2BIgAAAD4iAAAD3AAAAAAAPAPAAAA8A8AAAiAAAAAAAD3iAAAAPeIAAAAiHeAAAAAiHAAAACIcAAAAAeIcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FAEAQBwBAEAcAQBAHAEAQBwBAEAcAQBAHAEAQBwBAEAcAQBAH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fw%3D%3D</Image>
+<Url type="application/x-suggestions+json" template="http://search.bbc.co.uk/suggest">
+ <Param name="format" value="opensearch"/>
+ <Param name="q" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" template="http://search.bbc.co.uk/search" resultdomain="bbc.co.uk">
+ <Param name="opensearch" value="all-1"/>
+ <Param name="q" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.bbc.co.uk/alba/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/bing.xml b/browser/locales/searchplugins/bing.xml
new file mode 100644
index 000000000..3f7d9d918
--- /dev/null
+++ b/browser/locales/searchplugins/bing.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Bing</ShortName>
+ <Description>Bing. Search by Microsoft.</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAICAAAAEACACoCAAAJgAAABAQAAABAAgAaAUAAM4IAAAoAAAAIAAAAEAAAAABAAgAAAAAAIAEAAAAAAAAAAAAAAABAAAAAAAAhIQMAI+PIwCXlzEAn59BAKioUwCurl8AtbVuAL6+fwDJyZMA0tKlANjYsgDe3r4A5eXMAOzs2QDy8uUA+fnzAP///wAAGi8AAC1QAAA/cAAAUZAAAGOwAAB2zwAAiPAAEZj/ADGm/wBRs/8AccH/AJHP/wCx3f8A0ev/AP///wAAAAAAACwvAABLUAAAaHAAAIaQAAClsAAAw88AAOHwABHv/wAx8f8AUfP/AHH1/wCR9/8Asfn/ANH7/wD///8AAAAAAAAvIQAAUDcAAHBMAACQYwAAsHkAAM+PAADwpgAR/7QAMf++AFH/yABx/9MAkf/cALH/5QDR//AA////AAAAAAAALw4AAFAYAABwIgAAkCwAALA2AADPQAAA8EoAEf9bADH/cQBR/4cAcf+dAJH/sgCx/8kA0f/fAP///wAAAAAAAi8AAARQAAAGcAAACJAAAAqwAAALzwAADvAAACD/EgA9/zEAW/9RAHn/cQCY/5EAtf+xANT/0QD///8AAAAAABQvAAAiUAAAMHAAAD2QAABMsAAAWc8AAGfwAAB4/xEAiv8xAJz/UQCu/3EAwP+RANL/sQDk/9EA////AAAAAAAmLwAAQFAAAFpwAAB0kAAAjrAAAKnPAADC8AAA0f8RANj/MQDe/1EA4/9xAOn/kQDv/7EA9v/RAP///wAAAAAALyYAAFBBAABwWwAAkHQAALCOAADPqQAA8MMAAP/SEQD/2DEA/91RAP/kcQD/6pEA//CxAP/20QD///8AAAAAAC8UAABQIgAAcDAAAJA+AACwTQAAz1sAAPBpAAD/eREA/4oxAP+dUQD/r3EA/8GRAP/SsQD/5dEA////AAAAAAAvAwAAUAQAAHAGAACQCQAAsAoAAM8MAADwDgAA/yASAP8+MQD/XFEA/3pxAP+XkQD/trEA/9TRAP///wAAAAAALwAOAFAAFwBwACEAkAArALAANgDPAEAA8ABJAP8RWgD/MXAA/1GGAP9xnAD/kbIA/7HIAP/R3wD///8AAAAAAC8AIABQADYAcABMAJAAYgCwAHgAzwCOAPAApAD/EbMA/zG+AP9RxwD/cdEA/5HcAP+x5QD/0fAA////AAAAAAAsAC8ASwBQAGkAcACHAJAApQCwAMQAzwDhAPAA8BH/APIx/wD0Uf8A9nH/APeR/wD5sf8A+9H/AP///wAAAAAAGwAvAC0AUAA/AHAAUgCQAGMAsAB2AM8AiADwAJkR/wCmMf8AtFH/AMJx/wDPkf8A3LH/AOvR/wD///8AAAAAAAgALwAOAFAAFQBwABsAkAAhALAAJgDPACwA8AA+Ef8AWDH/AHFR/wCMcf8AppH/AL+x/wDa0f8A////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBggCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAsQEA0HAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwkPEBAQEBALBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMEBAQEBAQEBAPCQMAAAAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAQEBAQEBAQDQgBAAAAAAAAAAAAAAAAAAAAAAABDRAQEBAQEBAQEBAQEA0GAQAAAAAAAAAAAAAAAAAAAAENEBAQDgcLEBAQEBAQEBALBAAAAAAAAAAAAAAAAAAAAQ0QEBANAQEHDRAQEBAQEBAOCAAAAAAAAAAAAAAAAAABDRAQEA4BAAACCA4QEBAQEBANAQAAAAAAAAAAAAAAAAENEBAQDgEAAAAABAsQEBAQEA0BAAAAAAAAAAAAAAAAAQ0QEBAOAQAAAAABBw4QEBAQDQEAAAAAAAAAAAAAAAABDRAQEA4BAAABBw0QEBAQEBANAQAAAAAAAAAAAAAAAAENEBAQDgEAAAcQEBAQEBAQEA0BAAAAAAAAAAAAAAAAAQ0QEBAOAQABCxAQEBAQEBANBwAAAAAAAAAAAAAAAAABDRAQEA4BAAQPEBAQEA0IBAEAAAAAAAAAAAAAAAAAAAENEBAQDgEACRAQDQkGAQAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAOAQILCgYCAAAAAAAAAAAAAAAAAAAAAAAAAAABDRAQEA4BAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENEBAQDgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRAQEA4BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENEBAQDgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ0QEBAOAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRAQEA0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENEA0IBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQcFAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAQAAAAIAAAAAEACAAAAAAAQAEAAAAAAAAAAAAAAAEAAAAAAACEhAAAjIwAAJKSFgCZmSgAqqpRALKyYgC+vnsAxsaKAM3NmQDU1KgA4ODBAOjozwDw8OEA+fnyAP///wD///8AAAAAAAAaLwAALVAAAD9wAABRkAAAY7AAAHbPAACI8AARmP8AMab/AFGz/wBxwf8Akc//ALHd/wDR6/8A////AAAAAAAALC8AAEtQAABocAAAhpAAAKWwAADDzwAA4fAAEe//ADHx/wBR8/8AcfX/AJH3/wCx+f8A0fv/AP///wAAAAAAAC8hAABQNwAAcEwAAJBjAACweQAAz48AAPCmABH/tAAx/74AUf/IAHH/0wCR/9wAsf/lANH/8AD///8AAAAAAAAvDgAAUBgAAHAiAACQLAAAsDYAAM9AAADwSgAR/1sAMf9xAFH/hwBx/50Akf+yALH/yQDR/98A////AAAAAAACLwAABFAAAAZwAAAIkAAACrAAAAvPAAAO8AAAIP8SAD3/MQBb/1EAef9xAJj/kQC1/7EA1P/RAP///wAAAAAAFC8AACJQAAAwcAAAPZAAAEywAABZzwAAZ/AAAHj/EQCK/zEAnP9RAK7/cQDA/5EA0v+xAOT/0QD///8AAAAAACYvAABAUAAAWnAAAHSQAACOsAAAqc8AAMLwAADR/xEA2P8xAN7/UQDj/3EA6f+RAO//sQD2/9EA////AAAAAAAvJgAAUEEAAHBbAACQdAAAsI4AAM+pAADwwwAA/9IRAP/YMQD/3VEA/+RxAP/qkQD/8LEA//bRAP///wAAAAAALxQAAFAiAABwMAAAkD4AALBNAADPWwAA8GkAAP95EQD/ijEA/51RAP+vcQD/wZEA/9KxAP/l0QD///8AAAAAAC8DAABQBAAAcAYAAJAJAACwCgAAzwwAAPAOAAD/IBIA/z4xAP9cUQD/enEA/5eRAP+2sQD/1NEA////AAAAAAAvAA4AUAAXAHAAIQCQACsAsAA2AM8AQADwAEkA/xFaAP8xcAD/UYYA/3GcAP+RsgD/scgA/9HfAP///wAAAAAALwAgAFAANgBwAEwAkABiALAAeADPAI4A8ACkAP8RswD/Mb4A/1HHAP9x0QD/kdwA/7HlAP/R8AD///8AAAAAACwALwBLAFAAaQBwAIcAkAClALAAxADPAOEA8ADwEf8A8jH/APRR/wD2cf8A95H/APmx/wD70f8A////AAAAAAAbAC8ALQBQAD8AcABSAJAAYwCwAHYAzwCIAPAAmRH/AKYx/wC0Uf8AwnH/AM+R/wDcsf8A69H/AP///wAAAAAACAAvAA4AUAAVAHAAGwCQACEAsAAmAM8ALADwAD4R/wBYMf8AcVH/AIxx/wCmkf8Av7H/ANrR/wD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMAAAAAAAAAAAAAAAACBgwMBwIAAAAAAAAAAAAABg4ODg4MBgIAAAAAAAAAAAYODQoODg4KBAAAAAAAAAAGDgwDBQsODg4FAAAAAAAABg4MAgADCg4OBgAAAAAAAAYODAICCg4ODgYAAAAAAAAGDgwCBg4OCwcCAAAAAAAABg4MAwkIBAIAAAAAAAAAAAYODAICAAAAAAAAAAAAAAAGDgwCAAAAAAAAAAAAAAAABg4MAgAAAAAAAAAAAAAAAAYMCAEAAAAAAAAAAAAAAAACAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</Image>
+ <Url type="application/x-suggestions+json" template="https://www.bing.com/osjson.aspx">
+ <Param name="query" value="{searchTerms}"/>
+ <Param name="form" value="OSDJAS"/>
+ <Param name="language" value="{moz:locale}"/>
+ </Url>
+ <Url type="text/html" method="GET" template="https://www.bing.com/search" rel="searchform">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="pc" value="MOZI"/>
+ <MozParam name="form" condition="purpose" purpose="contextmenu" value="MOZCON"/>
+ <MozParam name="form" condition="purpose" purpose="searchbar" value="MOZSBR"/>
+ <MozParam name="form" condition="purpose" purpose="homepage" value="MOZSPG"/>
+ <MozParam name="form" condition="purpose" purpose="keyword" value="MOZLBR"/>
+ <MozParam name="form" condition="purpose" purpose="newtab" value="MOZTSB"/>
+ </Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/bok-NO.xml b/browser/locales/searchplugins/bok-NO.xml
new file mode 100644
index 000000000..6c5093fff
--- /dev/null
+++ b/browser/locales/searchplugins/bok-NO.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Ordbok</ShortName>
+<Description>Norske ordbøker.</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB2UlEQVQ4ja3TzWuSAQDH8edvyqioiIg61FBIRqNDjBi1YIeYk8VeFJJWTy+jIJq5ww5L7ZArNsuY0xE2e2G6YTC1NnGlOdPHR30eH19W8O2wQ8dq7vC7fk7fn1BVNVStuatVVQ2hpjXRGtu7Wk1rIqhtAOqeARW1yeqXEs/efOPhTBLRFcMVSKFqLbTGNtXFp8hTJipOE6rvFtqnOTRF/gMsxQrYX6xz40kc06Mo3TdDnBkKMBNMUlp6iXRVhzRymLJ4AmWig7qzk9Y7cQeQKnXmQlkmZzcQ3QkGHav03A6jHwlwRfSTHuum1L8PyXqUyt2TqA49Dfc5fvouohVTCKlsFe/b70y/TiO645jtK/SOv+esdZEui4/ENQPygI7S9WOU7+wATc95fvl7aKT8CNGExMTzJL3jYYyWAPrheYyWBQyjQYxDr0gMnEY26SiYD/Kj/wAF8yFk23Hq0520ovcRVpJFDMPznDL76LIFmfQm+LCWZyGS5Z4rwuaUDSU8i7L2kbLXQcHaQb5PR75vP2rEg5ArqlwYCzFoXyadq/49nkIe+fFlpNEjKJtxhK9bCg88n8kUav/egFxEcV5C3VhG2JJqrGeU/46onolRyyX2oMS2z9TunX8DKb6IjiC3ONQAAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="http://www.nob-ordbok.uio.no/perl/ordbok.cgi" resultdomain="uio.no">
+ <Param name="OPP" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>http://www.nob-ordbok.uio.no/</SearchForm>
+</SearchPlugin>
+
diff --git a/browser/locales/searchplugins/bolcom-fy-NL.xml b/browser/locales/searchplugins/bolcom-fy-NL.xml
new file mode 100644
index 000000000..e730fcff5
--- /dev/null
+++ b/browser/locales/searchplugins/bolcom-fy-NL.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>bol.com</ShortName>
+<Description>Sykje by bol.com</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAAAABMLAAATCwAAAAEAAAAAAAAAAAAA////ANJtQADot6AA3JJvAPXc0ADkpYYA7sq5ANiAVgD77egAzmMxAO3CqwDmr5IA4Jp8ANR4SgDcimMA+eTeAPHSwwD99/UA0Gk5APPWygD34NYA1nxQAOKggADos5gA7MaxAOCYdQD98+8A6r2oANRzRgDcjGkA2oheAP37+QDOZzUA5rWcAPvx6gD56OAA0nFCANZ4TgD12swA7si1AOq7pADkp4oA89TGAOCefQDiooQA0G09ANZ+VADYimAA3pZzAOrBrADtvacA//39AP359wDOZTMA/fXxANBnNwD78+wA0Gs7ANR1SAD34tgA9d7SANiCWAD12MoA3IxlANyOZwDuxrMA3ph3AOq/qgDquaIA6LWeAOazmgDvyLcA78q1AO/IswDswa0A4Jx7AOKkhgDOZTEA0Gc1APvv6ADQazkA+ejeANJxRADUdkoA1HhMANZ6TgDWflAA89jKANZ8UgDWflIA89bIANyMYwDuxrUA6LWcAOKgggDms5gA4qKCAOaxkgDkp4gA5q2SAOzBqwDrwKoA1HZIANR3SgDgnn4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQESP0IoPSABAQEBAQEBATljLgoKCk47AyABAQEBARBVCkxFAz42CgpcNwEBASMOCkwtJh5EMAoKCkEgAQFpCgpfTAoKCgoKCgoKCwFQOAoKOiclCgoKCgoKClk0SQoKCgoPBVMKCgoKCgo2JF4KNl5dZBoMAmUHKk8KCltHCkYfIUMFViIdNgZINgorSQoDPmAKOhkxEwoKMhY2UglPWlsYYh0cFzwvIV5oVjQBDQpNZhcpBAoISlgsCjMBAVBnCmEUVwoKCgoKCkAgAQEBFVQKVgoKCgoKCh8jAQEBAQEjX1EKCgoKJV41AQEBAQEBAQEbEURLJzUBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</Image>
+<SearchForm>http://www.bol.com/</SearchForm>
+<Url type="text/html" method="GET" template="http://www.bol.com/nl/s/algemeen/zoekresultaten/Ntt/{searchTerms}/Ntk/media_all/Nty/1/suggestedFor/{searchTerms}/N/0/Ne/0/search/true/searchType/qck/index.html" resultdomain="bol.com">
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/bolcom-nl.xml b/browser/locales/searchplugins/bolcom-nl.xml
new file mode 100644
index 000000000..f3c4e9d81
--- /dev/null
+++ b/browser/locales/searchplugins/bolcom-nl.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>bol.com</ShortName>
+<Description>Zoeken bij bol.com</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAAAABMLAAATCwAAAAEAAAAAAAAAAAAA////ANJtQADot6AA3JJvAPXc0ADkpYYA7sq5ANiAVgD77egAzmMxAO3CqwDmr5IA4Jp8ANR4SgDcimMA+eTeAPHSwwD99/UA0Gk5APPWygD34NYA1nxQAOKggADos5gA7MaxAOCYdQD98+8A6r2oANRzRgDcjGkA2oheAP37+QDOZzUA5rWcAPvx6gD56OAA0nFCANZ4TgD12swA7si1AOq7pADkp4oA89TGAOCefQDiooQA0G09ANZ+VADYimAA3pZzAOrBrADtvacA//39AP359wDOZTMA/fXxANBnNwD78+wA0Gs7ANR1SAD34tgA9d7SANiCWAD12MoA3IxlANyOZwDuxrMA3ph3AOq/qgDquaIA6LWeAOazmgDvyLcA78q1AO/IswDswa0A4Jx7AOKkhgDOZTEA0Gc1APvv6ADQazkA+ejeANJxRADUdkoA1HhMANZ6TgDWflAA89jKANZ8UgDWflIA89bIANyMYwDuxrUA6LWcAOKgggDms5gA4qKCAOaxkgDkp4gA5q2SAOzBqwDrwKoA1HZIANR3SgDgnn4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQESP0IoPSABAQEBAQEBATljLgoKCk47AyABAQEBARBVCkxFAz42CgpcNwEBASMOCkwtJh5EMAoKCkEgAQFpCgpfTAoKCgoKCgoKCwFQOAoKOiclCgoKCgoKClk0SQoKCgoPBVMKCgoKCgo2JF4KNl5dZBoMAmUHKk8KCltHCkYfIUMFViIdNgZINgorSQoDPmAKOhkxEwoKMhY2UglPWlsYYh0cFzwvIV5oVjQBDQpNZhcpBAoISlgsCjMBAVBnCmEUVwoKCgoKCkAgAQEBFVQKVgoKCgoKCh8jAQEBAQEjX1EKCgoKJV41AQEBAQEBAQEbEURLJzUBAQEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</Image>
+<SearchForm>http://www.bol.com/</SearchForm>
+<Url type="text/html" method="GET" template="http://www.bol.com/nl/s/algemeen/zoekresultaten/Ntt/{searchTerms}/Ntk/media_all/Nty/1/suggestedFor/{searchTerms}/N/0/Ne/0/search/true/searchType/qck/index.html" resultdomain="bol.com">
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/bookplus-fi.xml b/browser/locales/searchplugins/bookplus-fi.xml
new file mode 100644
index 000000000..55d36354a
--- /dev/null
+++ b/browser/locales/searchplugins/bookplus-fi.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Bookplus.fi</ShortName>
+<Description>Pikahaku nettikirjakauppa Bookplus.fi:hin</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQYKieTp1k8w2bonzNGmC/jJlfv4wX3fzLltxzCxacXkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQncc8SpK3y0eNsf5HjrL/SI+z/0eOsf9Fiq3/QYKj/zp1kv8yZH3+LVpxyy5ddzwAAAAAAAAAAAAAAABZrt08UqbP3lKlzv9VqtT/WLHd/1q14v9btuT/WrXi/1eu2v9QoMj/RYqt/zhwjP8wX3feM2Z7PAAAAAAAAAAAXLnoy1+GqP9mAAD/ZgAA/2YNEf9mAAD/ZqLL/2XK/f9mAAD/ZDNA/1iw3P9KlLn/OnWS/zJkfcsAAAAAZ8z/eWTG+P5kkbb/ZgAA/2ZRZf9mMT3/Zg4R/2YOEf9mvu7/ZgAA/2Y2Q/9ixPX/WbLe/0mStv86dJD+Nm2IeXDP/8xtzv//Z5a7/2YAAP9mbIf/Zsz//2ZtiP9mAAD/Zpa7/2YAAP9mNkT/Zcr9/2HC8v9UqdP/RIiq/zp1k8x50v/zdNH//2qXu/9mAAD/Zm2I/2bM//9mRFX/ZgAA/2ax3f9mAAD/Zhsi/2ZtiP9koMj/W7bk/0yYvv9AgaDzgNT//nnS//9smLv/ZgAA/2YbIv9mGyL/ZgAA/2ZtiP9mzP//ZgAA/2YAAP9mAAD/ZgAA/2JYbv9Rosv/RIiq/oXW//5+1P//bZi7/2YAAP9mNkT/ZkRV/2YAAP9mepn/Zsz//2YAAP9mNkT/Zr7u/2YpM/9mAAD/VZ3E/0iOsv6L2f/zhdb//3GZu/9mAAD/Zm2I/2bM//9mKTP/Zhsi/2bM//9mAAD/ZjZE/2bM//9mbYj/ZgAA/1uAn/9Lmb7zk9r/zI7Z//94m7v/ZgAA/2dtiP9mzP//Zhsi/2YAAP9mzP//ZgAA/2Y2RP9nvu7/ZjZE/2YAAP9cnMP/UqXOzJvd/3mZ3P/+gp+7/2YAAP9nGyL/Zhsi/2YAAP9mX3f/Zsz//2YAAP9mAAD/ZgAA/2YAAP9lUGT/Xrzr/lqz33kAAAAAot//y5fP7v96c4j/cXGI/2tuiP9ppMz/Z8z//2fM//9nepn/aG2I/2puiP9riqr/as3//2TG+MsAAAAAAAAAAKXh/zym4f/en9///5Lb//+E1v//e9P//3fS//910f//dtH//3jS//950v//d9L//3DP/95qzP88AAAAAAAAAAAAAAAApeH/PKnj/8ul4P/+nd7//5Xc//+Q2v//jdn//4vY//+J2P//hNX//n7U/8t30P88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAquP/eaji/8yk4P/zod///p3e//6b3v/zk9r/zIvW/3kAAAAAAAAAAAAAAAAAAAAA8A8AAMADAACAAQAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAEAAIABAADAAwAA8A8AAA==</Image>
+<Url type="text/html" method="GET" template="http://www.bookplus.fi/search.php" resultdomain="bookplus.fi">
+ <Param name="Textfield" value="{searchTerms}"/>
+ <Param name="mode" value="book"/>
+</Url>
+<SearchForm>http://www.bookplus.fi/search.php</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/buscape.xml b/browser/locales/searchplugins/buscape.xml
new file mode 100644
index 000000000..9f5f57fd8
--- /dev/null
+++ b/browser/locales/searchplugins/buscape.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>BuscaPé</ShortName>
+<Description>Comparação de produtos e pesquisa de preços.</Description>
+<InputEncoding>iso-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,R0lGODlhEAAQANUoALu7u/zW1/WFiPm1tnd3d/itryIiIvFSVlVVVd3d3e7u7vimqKqqqvm3uf3i4//6+v3p6u44Pfzb3PJobPBGS/ekpveane86P/FcYPWChe9BRvFZXfFVWfR2evaOkURERBEREYiIiO4zOMzMzDMzM2ZmZpmZmQAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACgALAAAAAAQABAAAAaPQBQgRDShUCZiaHQ8Ik4f0MkIAn1OoCbKYECFpolTCXVNNKGKZwJwCqFI2SP7dDIwvCcSqd00tRliKCVtIQBaBCcjCicIb3FaZCcAiG5QkEdSdGMjJwQoDxsDl6MVIiITDigBqwMFBQsCsRqmIhcWtLi5uBIBrwIdGBS6B6KjHrTEo0cQESIHDcpNGRzQkEEAOw==</Image>
+<Url type="text/html" method="GET" template="http://busca.buscape.com.br/cprocura" resultdomain="buscape.com.br">
+ <Param name="produto" value="{searchTerms}"/>
+ <Param name="site_origem" value="11642"/>
+</Url>
+<SearchForm>http://www.buscape.com.br/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/ceneji.xml b/browser/locales/searchplugins/ceneji.xml
new file mode 100644
index 000000000..69d1c0636
--- /dev/null
+++ b/browser/locales/searchplugins/ceneji.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Ceneje.si</ShortName>
+<Description>Iskalnik Ceneje.si</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image height="16" width="16" type="image/x-icon">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAzUlEQVQ4EWNgGGjACHLA39uz1R7vqLjJz83EwMnByMDEyoTTXWyhL8B6YAqY/j3bJfX3Qu1NmAAh+tdqif8oakACGIIoKlA5DyYL/wdhmCgLjEEsDfImMkDlIctA2f8+30NRw8nOyADCMIAiCRNEpv/ssPqL7EUmNiYGEIYBlr+//sHYWGn0UEdXxPLtO34D0DWg81k+foUHKLocUXxwaCD7EZuuP0Bvfge6FGSZjBQrA7K3wKGBLIDNAGQxUtQi68NgE3I1hgZsAlQxBAAKUEIHR/DoRAAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="http://www.ceneje.si/search_new.aspx" resultdomain="ceneje.si">
+ <Param name="q" value="{searchTerms}" />
+ <Param name="FF-SearchBox" value="1" />
+</Url>
+<SearchForm>http://www.ceneje.si</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/chambers-en-GB.xml b/browser/locales/searchplugins/chambers-en-GB.xml
new file mode 100644
index 000000000..accc693f0
--- /dev/null
+++ b/browser/locales/searchplugins/chambers-en-GB.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Chambers (UK)</ShortName>
+<Description>Chambers 21st Century Dictionary Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAARVBMVEV8grz///8cJoz8AgS8CiQ8QpxMUqQsMpRMDly8wtw8EmSMksS8RmT8iozsBgzM0uR8DkScosxscrSkLlzUGizs7vQsEmwP6BukAAAAYUlEQVR4XpXMNxLEIBBE0ekxgLzf+x9VmEKFlO3P+gVN/CmDI9pbWJCyBzqAHF2uggFR2XwFAph/IcTRh77CKhJBRQv4F3hgSGAVeAO6UeRUnQvwAEySy5ByR/ORW/+FthvNOwN3ZVtbCQAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="http://www.chambers.co.uk/search.php" resultdomain="chambers.co.uk">
+ <Param name="query" value="{searchTerms}"/>
+ <Param name="title" value="21st"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>http://www.chambers.co.uk/search.php</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/cnrtl-tlfi-fr.xml b/browser/locales/searchplugins/cnrtl-tlfi-fr.xml
new file mode 100644
index 000000000..e78af42e2
--- /dev/null
+++ b/browser/locales/searchplugins/cnrtl-tlfi-fr.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Portail Lexical - CNRTL</ShortName>
+<Description>Centre National de Ressources Textuelles et Lexicales</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAAAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgbUWgbUWfa0SZZECVXzyUXTuUXTuUXTuUXTuUXTuUXTuUXTuUXTuUXTuXYT6eakOgbUWfbESXYD2PVTiMUjiLUTeMUTeMUTeMUTeMUTeMUTeMUTeMUTeLUTePVTebZkGgbUWcaUPAmoTawbTZwbXZwbXZwbXZwbXZwbXZwbXZwbXZwbXZwbXPuKyaaE6aZUCgbUW8kXP639D94dL+4tP+4tP+4tP+4tP+4tP+4tP+4tP+4tP+4tPt08WhcFWbZ0KgbUXXs5z+49T+49T+5df+5tn+5tn+5tn+5tn+5tn+5tn+5tn+5tn74tSvgWGfbESgbUXat6D+49Ty1MS0h2qtflutf1utf1utf1utf1utf1utf1utf1utf1qjcUqgbUWgbUXat6H53tC3i3SXYT6gbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUXat6H12syrfmeXYT6gbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWhbkbat6H02suoe2SSWjmcZ0KdakOdakOdakOdakOdakOdakOdakOdakOea0SgbUWhbkbat6H12syrgW6JTC+OVDWQVzeQVzeQVzeQVzeQVzeQVzeQVzeQVzeUXTudaUOhbkbbuKH74NLXvrGhdmSZaVSaalWaalWaalWaalWaalV3TFNgOUtgOUt9STmaZUChbkbUsZn+5NX739Du08boz8Hoz8Hoz8HozsHozsHozsGFesdCQsxBQchtRVOaZkGgbUWzhmT85dj+5NX+49X+49X+49X+49X+49T+49T+49SPg9BDRNJCQ85yTFidaUOgbUWgbUW8knPnyrft0L/t0L/t0L/t0L/s0L/s0L/s0L+fjLpqYLtoXrqFXV6gbUWgbUWgbkagbUWgbUWhbkahbkahbkahbkagbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUWgbkagbUWgbUWhbkahbkahbkahbkagbUWgbUWgbUWgbUWgbUWgbUWgbUWgbUUAAH9fAADP/gAAz/4AAPr/AADC/gAAwP4AAE5eAAD//wAA//8AAMb+AAD//wAAwP4AAE5eAAD//wAA//8AAMz+</Image>
+<SearchForm>http://www.cnrtl.fr/lexicographie/</SearchForm>
+<Url type="text/html" method="GET" template="http://www.cnrtl.fr/lexicographie/{searchTerms}" resultdomain="cnrtl.fr">
+</Url>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.cnrtl.fr/utilities/OPEN">
+ <Param name="query" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/danawa-kr.xml b/browser/locales/searchplugins/danawa-kr.xml
new file mode 100644
index 000000000..7354434b1
--- /dev/null
+++ b/browser/locales/searchplugins/danawa-kr.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>다나와</ShortName>
+<Description>다나와 쇼핑 검색</Description>
+<InputEncoding>EUC-KR</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAI6crmyOnK/4jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv+I6crmyOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/GKMg/xCgGf8QoBn/EKAZ/xCgGf8Uoh3/HKQk/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/G6Qj/3fJfP//////////////////////yurM/xSiHf8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/xWiHv/C58T/////////////////+v36/0GzSP8cpCT/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/xijIf9fv2X//////////////////f79/2vEcP8YoyH/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/HqUm/xaiH/9avWD/9/z3////////////7/nw/1y+Yv8YoyH/I6cr/yOnK/8jpyv/IKYo/xylJf8YoyH/E6Ec/0CzR/+w4LP////////////0+/T/mdad/yapL/8ZoyL/I6cr/yOnK/8jpyv/I6cr/zewP/9jwWn/lNWY/9Xu1v/m9ef/yenL/57Yof9gwGb/HKUl/xijIf8gpij/I6cr/yOnK/8jpyv/I6cr/yOnK/8gpij/HKUl/xijIf8ToRz/EqEb/xSiHf8YoyD/HaUl/yKnKv8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6crmyOnK/4jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv+I6crmwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAI6crCiOnK54jpyv3I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv3I6crniOnKwojpyueI6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6crniOnK/cjpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv3I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/9GtUz////////////////////////////////////////////H6cn/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/3jJff//////////////////////////////////////+f35/0a1TP8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/vuXB//////////////////////////////////////+DzYf/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/z+yRv/+//7/////////////////////////////////tOG3/ySnLP8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/tuK4/////////////////////////////////8zrzv8sqjP/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/2vEcP/////////////////////////////////P7ND/MKw3/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/9Tulr/9/z4////////////////////////////t+K5/yyqM/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/ZcFq//b79v//////////////////////+f35/4XOiv8kpyz/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/Lqs2/6Lapf/+//7//////////////////////8Dmwv9GtU3/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/Ma04/4XOiv/u+O7//////////////////////8boyP9evmT/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/80rjz/a8Rx/7Lgtf/3/Pj/////////////////3/Lg/5jWnP9Itk//I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/9FtEv/bcVy/5PUl/+95L//4fPi///////0+/T/3/Lg/8Pnxv+h2aT/dMd5/0GzSP8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/cjpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv3I6crniOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK54jpysKI6crniOnK/cjpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/8jpyv/I6cr/yOnK/cjpyueI6crCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="text/html" method="GET" template="http://search.danawa.com/dsearch.php" resultdomain="danawa.com">
+ <Param name="k1" value="{searchTerms}"/>
+ <Param name="from" value="firefox"/>
+</Url>
+<SearchForm>http://search.danawa.com</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/daum-kr.xml b/browser/locales/searchplugins/daum-kr.xml
new file mode 100644
index 000000000..ea4b1e5a0
--- /dev/null
+++ b/browser/locales/searchplugins/daum-kr.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>다음</ShortName>
+<Description>다음 검색</Description>
+<InputEncoding>utf-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G/wC6Y/8AumP/ALpj/wC09P8AtPT/ALT0/zo8+/86PPv/Ojz7//htBv/6iyr/+osq//qLKv/6iyr/+osq//qLKv8A1p//ANaf/wDWn/8A2v//ANr//wDa//9fYfz/X2H8/zo8+//4bQb/+osq//qLKv/6iyr/+osq//qLKv/6iyr/ANaf/wDWn/8A1p//ANr//wDa//8A2v//X2H8/19h/P86PPv/+G0G//qLKv/6iyr/+osq//qLKv/6iyr/+osq/wDWn/8A1p//ANaf/wDa//8A2v//ANr//19h/P9fYfz/Ojz7//htBv/6iyr/+osq//qLKv/6iyr/+osq//qLKv8A1p//ANaf/wDWn/8A2v//ANr//wDa//9fYfz/X2H8/zo8+//4bQb/+osq//qLKv/6iyr/+osq//qLKv/6iyr/ANaf/wDWn/8A1p//ANr//wDa//8A2v//X2H8/19h/P86PPv/+G0G//qLKv/6iyr/+osq//qLKv/6iyr/+osq/wDWn/8A1p//ANaf/wDa//8A2v//ANr//19h/P9fYfz/Ojz7//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv8AumP/ALpj/wC6Y/8AtPT/ALT0/wC09P86PPv/Ojz7/zo8+/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//AAD//wAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA//8AAP//AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv8AumP/ALpj/wC6Y/8AumP/ALpj/wC6Y/8AtPT/ALT0/wC09P8AtPT/ALT0/wC09P86PPv/Ojz7/zo8+/86PPv/Ojz7/zo8+//4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G/wC6Y/8AumP/ALpj/wC6Y/8AumP/ALpj/wC09P8AtPT/ALT0/wC09P8AtPT/ALT0/zo8+/86PPv/Ojz7/zo8+/86PPv/Ojz7//htBv/4bQb/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/ANaf/wDWn/8A1p//ANaf/wDWn/8A1p//ANr//wDa//8A2v//ANr//wDa//8A2v//X2H8/19h/P9fYfz/X2H8/zo8+/86PPv/+G0G//htBv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv8A1p//ANaf/wDWn/8A1p//ANaf/wDWn/8A2v//ANr//wDa//8A2v//ANr//wDa//9fYfz/X2H8/19h/P9fYfz/Ojz7/zo8+//4bQb/+G0G//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq/wDWn/8A1p//ANaf/wDWn/8A1p//ANaf/wDa//8A2v//ANr//wDa//8A2v//ANr//19h/P9fYfz/X2H8/19h/P86PPv/Ojz7//htBv/4bQb/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/ANaf/wDWn/8A1p//ANaf/wDWn/8A1p//ANr//wDa//8A2v//ANr//wDa//8A2v//X2H8/19h/P9fYfz/X2H8/zo8+/86PPv/+G0G//htBv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv8A1p//ANaf/wDWn/8A1p//ANaf/wDWn/8A2v//ANr//wDa//8A2v//ANr//wDa//9fYfz/X2H8/19h/P9fYfz/Ojz7/zo8+//4bQb/+G0G//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq/wDWn/8A1p//ANaf/wDWn/8A1p//ANaf/wDa//8A2v//ANr//wDa//8A2v//ANr//19h/P9fYfz/X2H8/19h/P86PPv/Ojz7//htBv/4bQb/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/ANaf/wDWn/8A1p//ANaf/wDWn/8A1p//ANr//wDa//8A2v//ANr//wDa//8A2v//X2H8/19h/P9fYfz/X2H8/zo8+/86PPv/+G0G//htBv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv8A1p//ANaf/wDWn/8A1p//ANaf/wDWn/8A2v//ANr//wDa//8A2v//ANr//wDa//9fYfz/X2H8/19h/P9fYfz/Ojz7/zo8+//4bQb/+G0G//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq/wDWn/8A1p//ANaf/wDWn/8A1p//ANaf/wDa//8A2v//ANr//wDa//8A2v//ANr//19h/P9fYfz/X2H8/19h/P86PPv/Ojz7//htBv/4bQb/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/ANaf/wDWn/8A1p//ANaf/wDWn/8A1p//ANr//wDa//8A2v//ANr//wDa//8A2v//X2H8/19h/P9fYfz/X2H8/zo8+/86PPv/+G0G//htBv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv8A1p//ANaf/wDWn/8A1p//ANaf/wDWn/8A2v//ANr//wDa//8A2v//ANr//wDa//9fYfz/X2H8/19h/P9fYfz/Ojz7/zo8+//4bQb/+G0G//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq//qLKv/6iyr/+osq/wDWn/8A1p//ANaf/wDWn/8A1p//ANaf/wDa//8A2v//ANr//wDa//8A2v//ANr//19h/P9fYfz/X2H8/19h/P86PPv/Ojz7//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/ALpj/wC6Y/8AumP/ALpj/wC6Y/8AumP/ALT0/wC09P8AtPT/ALT0/wC09P8AtPT/Ojz7/zo8+/86PPv/Ojz7/zo8+/86PPv/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv/4bQb/+G0G//htBv8AumP/ALpj/wC6Y/8AumP/ALpj/wC6Y/8AtPT/ALT0/wC09P8AtPT/ALT0/wC09P86PPv/Ojz7/zo8+/86PPv/Ojz7/zo8+/////8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://sug.search.daum.net/search_nsuggest">
+ <Param name="mod" value="fxjson" />
+ <Param name="code" value="utf_in_out" />
+ <Param name="q" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="http://search.daum.net/search" resultdomain="daum.net">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="w" value="tot"/>
+ <Param name="nil_ch" value="ffsr"/>
+</Url>
+<SearchForm>http://search.daum.net</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/ddg.xml b/browser/locales/searchplugins/ddg.xml
new file mode 100644
index 000000000..f076ec869
--- /dev/null
+++ b/browser/locales/searchplugins/ddg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>DuckDuckGo</ShortName>
+ <Description>Search DuckDuckGo</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image height="16" width="16">data:image/icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA11RgALs6oACbQ9wAj0v8AI9L/ACfQ9wAu0agANdUYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzzN4CNdL/oK/z//////////////////////+jsPv/BDXX/wAz0t4AAAAAAAAAAAAAAAAAAAAAAAAAAAAyzvNSduD//////8jK/v+P+Lf/IbQL/17RPP+J3Y//wOKX//////9YeuX/ADLO8wAAAAAAAAAAAAAAAAAw091piOX/8/X9/1Fx5P9xhu//WOWZ/0W9Lv9Lwjn/J8BB/xyDAP9bdfL/9fP//2mI5v8AMNPdAAAAAAc610YRQ9f//////0Zr4P8AGdD/sb32////////////wrv//wAh1/8MPab/ACPc/05r4///////EkPX/wc610YANtWkrr/y/6S48P8AJ9L/AB3R/+/w/v///////////3+D7f8AQeL/AYTw/wFr5/8AMNb/p7Tv/6698v8AM9WkADLW//////8yXt//AC3V/wAw1/////////////z///8A0P7/AKb1/wWI7P8AuPf/AJ3w/zZW3P//////ADHV/wAx2P//////AzrZ/wAu1/84ZOL////////////e////AND//wC1+f8Atff/AZbv/wY62f8ELNf//////wAw1/8AMtn//////wAw2f8ALNn/kKrz////+//cwbH////////////R////Rcb8/wDO/f8A/P//AHzo//////8AMNj/ADXa//////8vXuL/ACna/4yq9///79T/jUkg/9i+r///////r2Q0/7Cozv8BKdr/AirY/zdZ4P//////ADTa/wI72tOuv/T/prr0/wAl2v+JqPb//7yW/+bUxv/9+/n////u//W+n/+Op/L/ADPd/wAv2v+ru/T/r7/0/wI72tMLQd1DEEjg//////9Cbef/ADng///////////////////////R3///AC3g/wAy3v9SeOn//////xFI4P8LQd1DAAAAAAM64PNmiuz/9/j//2mN7f/m7P3///////////9Cb+n/ACXd/wAt3v9rju3//////2iL7P8DOuDzAAAAAAAAAAAAAAAAAT3g/0p16f//////3OT8/3OS7v8AKt3/ACPc/zhn5/+xw/b//////0956v8CPeD/AAAAAAAAAAAAAAAAAAAAAAAAAAAEPODzBUDh/5uz8//7/f7/////////////////prz0/wtF4v8FQeDzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtF5kYDQOOkADrj/wA44v8AOeP/ADzk/wVB46QPReZGAAAAAAAAAAAAAAAAAAAAAPAPAADgBwAAwAMAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAADAAwAA4AcAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChIzyAnRNFwJ0TQryND0d8nRNH/J0TR/ydE0f8nRNH/I0PR3ydE0K8nRNFwKEjPIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChE00AlRdK/J0XS/ydF0v8nRdL/XXPd/11z3f94i+P/k6Lp/5Oi6f9rf+D/NVDV/ydF0v8nRdL/JUXSvyhE00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAzxAnRNOvJ0XT/ydF0/8lRdK/KEXSYOvu+6/+/v6//v7+v/39/c////////////7+/r/J0fOAKEXSYCVF0r8nRdP/J0XT/ydE068gQM8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlRdUwJ0bT7ydG0/8nRtHPKETTQAAAAADHx8dA2vHhn5TYpN/o9+z/////////////////8PL83ydG0o8lRdUwAAAAAChE00AnRtHPJ0bT/ydG0+8lRdUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKEXVYCdG1P8nRtT/KEbTgAAAAAAmRtZQI0PU38jIyP/F6s//Rrtk/0a7ZP9/yIr/c796/4vLkv+JpNf/M3Kq/zyWh/8zeKTfJkbWUAAAAAAoRtOAJ0bU/ydG1P8oRdVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVF1TAnR9X/J0fV/yhF1WAgQM8QJ0fTrydH1f9CW8//2tra/6Pdsv9Gu2T/Rrtk/0WzWv9Gu2T/Rrtk/0a7ZP9Gu2T/Rrtk/z6egP8nR9X/J0fTryBAzxAoRdVgJ0fV/ydH1f8lRdUwAAAAAAAAAAAAAAAAAAAAAAAAAAAgQM8QJ0fV7ydH1f8oSNVgIEDPECdH1c8nR9X/J0fV/1xwyf/t7e3/o92y/0a7ZP9Gu2T/Ra5U/0a7ZP9Gu2T/Rrtk/0a7ZP9Gu2T/Pp6A/ydH1f8nR9X/J0fVzyBAzxAoSNVgJ0fV/ydH1e8gQM8QAAAAAAAAAAAAAAAAAAAAACdH1q8nR9b/KEjVgCBQzxAnR9bPJ0fW/ydH1v8nR9b/gIzB//r6+v+j3bL/Rrtk/13Ed/+i26//ruG7/z6egf8+noH/Rrtk/0a7ZP86kI//J0fW/ydH1v8nR9b/J0fWzyBQzxAoSNWAJ0fW/ydH1q8AAAAAAAAAAAAAAAAoSNdAJkjW/yZH1s8AAAAAJEfWryZI1v8mSNb/JkjW/yZI1v+jqsT//////+j37P/R7tj////////////W3ff/JkjW/yZI1v8uZbr/PJeI/zJzrP8mSNb/JkjW/yZI1v8mSNb/JEfWrwAAAAAmR9bPJkjW/yhI10AAAAAAAAAAACVI1r8mSNf/KEjXQCZJ1lAmSNf/JkjX/yZI1/8mSNf/JkjX/9HR0f///////////////////////////5Ok6/8mSNf/JkjX/yZI1/8mSNf/JkjX/yZI1/8mSNf/JkjX/yZI1/8mSNf/JknWUChI10AmSNf/JUjWvwAAAAAoSNcgJknY/yZH2M8AAAAAI0nY3yZJ2P8mSdj/JknY/yZJ2P9KZM//39/f////////////////////////////XHfi/yZJ2P8mSdj/JknY/yZJ2P8mSdj/JknY/yZJ2P8mSdj/JknY/yZJ2P8jSdjfAAAAACZH2M8mSdj/KEjXICdJ2HAmSdj/JUjXYCVK2jAmSdj/JknY/yZJ2P8mSdj/JknY/2V4yf/t7e3///////////////////////////9cd+L/HXTj/xSf7/8Nwfj/CdL8/wnS/P8J0vz/ELDz/xt85v8mSdj/JknY/yZJ2P8lStowJUjXYCZJ2P8nSdhwJErZryZK2f8oSNcgJUnajyZK2f8mStn/JkrZ/yZK2f8mStn/iJPA////////////////////////////0ff+/xjV/P8J0vz/Drn1/xiO6/8Yjuv/GI7r/xCw8/8Lyvr/CdL8/xmF6P8mStn/JkrZ/yVJ2o8oSNcgJkrZ/yRK2a8jStrfI0rZ3wAAAAAlSdq/Jkra/yZK2v8mStr/Jkra/yZK2v+xtsf///////////////////////////8o2Pz/CdL8/wvK+v8mStr/Jkra/yZK2v8mStr/Jkra/yZK2v8iW97/Jkra/yZK2v8mStr/JUnavwAAAAAjStnfI0ra3yZK2v8lSdq/AAAAACZH2O8mStr/Jkra/yZK2v8mStr/L1HY/9HR0f///////////////////////////yjY/P8J0vz/CdL8/xCw9P8QsPT/ELD0/xSf7/8ddeX/Jkra/yZK2v8mStr/Jkra/yZK2v8mR9jvAAAAACVJ2r8mStr/Jkvb/yVJ2r8AAAAAJkvb/yZL2/8mS9v/Jkvb/yZL2/9KZtL/4+Pj////////////////////////////4Pn//0fd/f8J0vz/CdL8/wnS/P8J0vz/CdL8/wnS/P8Lyvr/Fpfu/yJc3/8mS9v/Jkvb/yZL2/8AAAAAJUnavyZL2/8mS9z/JUncvwAAAAAmS9z/Jkvc/yZL3P8mS9z/Jkvc/26AyP/x8fH//////////////////////////////////////9H3/v/C9P7/o+7+/2fa+/8Oufb/CdL8/wnS/P8J0vz/CdL8/xiP7P8mS9z/Jkvc/wAAAAAlSdy/Jkvc/yZM3P8lTNy/AAAAACZJ2e8mTNz/Jkzc/yZM3P8mTNz/iJTB////////////qnth/5VaOf/x6eX///////////////////////Hp5f/x6eX/ydL2/yZM3P8kVN7/G37o/xKo8v8QsfT/HXbm/yZM3P8mSdnvAAAAACVM3L8mTNz/I0vc3yZJ2u8AAAAAJUzevyZM3f8mTN3/Jkzd/yZM3f+fqc3///////////+VWjn/v5yI/+re1///////////////////////jk8s/7iRe//J0vb/Jkzd/yZM3f8mTN3/Jkzd/yZM3f8mTN3/Jkzd/yVM3r8AAAAAI0vc3yNL3N8kTd2vJk3d/yhQ3yAlTd2PJk3d/yZN3f8mTd3/Jk3d/6St0v////////////Hp5f/q3tf///////////////////////////+xhm7/49PK/6Cx8P8mTd3/Jk3d/yZN3f8mTd3/Jk3d/yZN3f8mTd3/JU3djyhQ3yAmTd3/JE3drydN33AmTd7/J03fcCVK3zAmTd7/Jk3e/yZN3v8mTd7/pK7S///////Sp5r/////////////////////////////////////////////////T27k/yZN3v8mTd7/Jk3e/yZN3v8mTd7/Jk3e/yZN3v8lSt8wJ03fcCZN3v8nTd9wKFDfICZO3/8mTt3PAAAAACVN3r8mTt//Jk7f/yZO3/+EltX//////+fRyv/SqaD/59LO///////////////////////at63/vIBy/7Glxf8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8mTt//JU3evwAAAAAmTt3PJk7f/yhQ3yAAAAAAJE/dryZO3/8oUN9AKFDfQCZO3/8mTt//Jk7f/zhb2v/o6/T/////////////////////////////////////////////////XHrn/yZO3/8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8oUN9AKFDfQCZO3/8kT92vAAAAAAAAAAAoUN9AJk7g/yZO4M8AAAAAJk/hnyZO4P8mTuD/Jk7g/05v5v/k6fv//////////////////////////////////////3eR7P8mTuD/Jk7g/yZO4P8mTuD/Jk7g/yZO4P8mTuD/Jk/hnwAAAAAmTuDPJk7g/yhQ30AAAAAAAAAAAAAAAAAjT+GfJU/h/yVO4Y8gUN8QIk7gzyVP4f8lT+H/SWnW/0lp1v+bq+H/8fHx/////////////////6Cy8v9OcOb/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yJO4M8gUN8QJU7hjyVP4f8jT+GfAAAAAAAAAAAAAAAAAAAAACBQ3xAlTOHvJU/h/yVQ4mAgUN8QIk7hzyVP4f+ktOv///////////////////////H0/f9phur/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yVP4f8iTuHPIFDfECVQ4mAlT+H/JUzh7yBQ3xAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ3zAlUOLvJVDi/yVQ4mAgUN8QI1Din4mb2//J0/j/ydP4/6299P93ku3/M1vk/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/I1DinyBQ3xAlUOJgJVDi/yVQ4u8lUN8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAlUOLvJVDi/yVQ4o8AAAAAJFDjQCVQ4r8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDivyRQ40AAAAAAJVDijyVQ4v8lUOLvJVDkMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAjUeTfJVHj/yNR5N8kUONAAAAAACVQ5DAmUuOAJVHivyNR5N8lUeP/JVHj/yNR5N8lUeK/JlLjgCVQ5DAAAAAAJFDjQCNR5N8lUeP/I1Hk3yVQ5DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBQ3xAjUuSfJVHk/yVR5P8jUeTfJFLkcChQ5yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoUOcgJFLkcCNR5N8lUeT/JVHk/yNS5J8gUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkUONAI1LknyVS5P8lUuT/JVLk/yVS5O8lUeS/JVHkvyVR5L8lUeS/JVLk7yVS5P8lUuT/JVLk/yRS468kUONAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFDfECVS5GAjUuWfIlPlzyVS5f8lUuX/JVLl/yVS5f8iU+XPI1LlnyVS5GAgUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/ggBB/wgAEP4AAAB8AAAAPAAAADiAAAEYAAAAEQAAAIAAAAAAAAAAAgAAAEIAAABCAAAAQgAAAEIAAABCAAAAQAAAAAAAAAABAAAAiAAAABiAAAEcAAAAPAAAAD4AAAB/CAAQ/4IAQf/AfgP/8AAP//wAP/</Image>
+ <Url type="text/html" method="get" template="https://duckduckgo.com/" rel="searchform">
+ <Param name="q" value="{searchTerms}"/>
+ <MozParam name="t" condition="purpose" purpose="contextmenu" value="ffcm"/>
+ <MozParam name="t" condition="purpose" purpose="keyword" value="ffab"/>
+ <MozParam name="t" condition="purpose" purpose="searchbar" value="ffsb"/>
+ <MozParam name="t" condition="purpose" purpose="homepage" value="ffhp"/>
+ <MozParam name="t" condition="purpose" purpose="newtab" value="ffnt"/>
+ </Url>
+ <Url type="application/x-suggestions+json" template="https://ac.duckduckgo.com/ac/">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="type" value="list"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/browser/locales/searchplugins/diccionariu-alla.xml b/browser/locales/searchplugins/diccionariu-alla.xml
new file mode 100644
index 000000000..007038a3b
--- /dev/null
+++ b/browser/locales/searchplugins/diccionariu-alla.xml
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Diccionariu ALLA</ShortName>
+<Description>Diccionariu de la Academia de la Llingua Asturiana</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO719xO73vJspdXypanU76C63fNk8vP0DQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2vD4H6XT78eLyO39weL1/3G86/+v2PD/tNzy/32/6fyh0e/C1O73FwAAAAAAAAAAAAAAAAAAAAAAAAAAxub2TKPR7PXJ5fb/W7Hs/6HS7/+YzO3/isju/4/J7f95v+3/3vH6/47K8PK13vU6AAAAAAAAAAAAAAAA2O/3KJPL6vlotun/pdPq/53P7f8/pOz/f8Lu/1iv7P+ExOz/ZLbv/47I6/9nt+v/m87u8M7q9hgAAAAAAAAAAKHS79fi8vr/isbr/37C7/98we3/arfs/12y6/+DxO//Zrft/3G/7/91v+//rdfv/8/o9v+g0e7FAAAAAN7x+St0vOj/fMDs/2C07P+Bwuz/d7/t/5rQ7/+bz/D/kMnu/6jV7/98we//eL7s/6DS8P9cs+7/isfs/Nfx+Biz2fGPweL1/4jH7P9/w+3/bLjs/6fV7/9Gpun/h8bt/3W97v9Cpez/qdTu/2K28P88pO7/q9fw/8bk9/+23fVums7wt7jd8v+Nxun/WK/r/4nH7v+DxfH/d73t/83l8//B4vT/hMXu/57P7/9Uruz/gcTv/5/R8P91vOr/pdTyoJnN77d0ver/n9Du/4DD7f9hs+j/ls3w/4HD7v/N5vT/yOT0/3K+8f+Ryu7/gcTu/0yt7/+Qy+//tdvw/6fV8p+v1u6T2O33/5rP7f9Dper/XrXv/5nN7f9Ip+r/b7vw/4PE7/9Cpe3/rNXv/2K28f+DxO7/jcnv/7Lb9P+73/Zjzer2M37A6P9mtu3/kczv/2+97v92vu7/rdbu/4rH7/+d0O7/lczu/3e/7/99we7/bLrw/3e/7v9+wOr89vf3DAAAAACd0O7b0ur1/57R7v+Awuz/f8Lu/16z7v+Hx+//YLPr/1mw7v98wO//g8Tw/4jF7f/W7fn/odPyvAAAAAAAAAAAyer4MprP7vdYr+r/lsvt/2+77/+Ixu3/Sqvv/4XF7v9HqOz/oNLx/6bT7v9otun/lM3w8uT3+xQAAAAAAAAAAAAAAAC+4vVMl87s9+Py+P92u+r/lczs/5TK7P+Vy+3/q9Xu/1iu6v/N5/T/lszv8NDp9i8AAAAAAAAAAAAAAAAAAAAAAAAAANbt9x+g0e/Le77o/rzf8/+j1PL/eMDt/73f9v+Lx+z7pNPxvdrz+hcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOr3+RW63vRsrtj0na7X8prC4/Zh9v3+CwAAAAAAAAAAAAAAAAAAAAAAAAAA/n8AAPAPAADgBwAAwAMAAIABAACAAQAAAAEAAAAAAAAAAAAAAAEAAIABAACAAQAAwAMAAOAHAADwDwAA/n8AAA==</Image>
+<Url type="text/html" method="POST" template="http://www.academiadelallingua.com/diccionariu/index.php" resultdomain="academiadelallingua.com">
+ <Param name="pallabra" value="{searchTerms}" />
+</Url>
+<SearchForm>http://search.ebay.es/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/dict-enlv.xml b/browser/locales/searchplugins/dict-enlv.xml
new file mode 100644
index 000000000..72bf2e1e9
--- /dev/null
+++ b/browser/locales/searchplugins/dict-enlv.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Dict.lv En-Lv</ShortName>
+<Description>Angļu - latviešu vārdnīca</Description>
+<Image height="16" width="16" type="image/x-icon">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAABF0lEQVQ4EY1TuxHCMAw1nwUYgCaMEAqOmhXICEBHCSOEkg5YgRFIzR13yQo0DJCWkvcSO45sE9CdTrak96TIykAFZL7ZRuPpTL3yxzsQFq6huNnLCMcziA6wEbS8n45XG7annj3KE8AkyaG0ExCUMqO%2B9UNO7WPlJ5TAlfZ5pouA4AS6hqboaOGh%2F3UAfKOG8rtmwIoxtNA2heUs2Fkj3iugEkFn6AVqwDuNWMLyZRoRBABzcGx1j0okoGTw07IDxoW4Q6wqtcAm2ezATwImsG0h7ne3g24HjHFxvom3TC4BJxzjmwVJ6565zC6BGZy7ebwXgdkobw9QjU9lnpEV%2BazciQQE3id4BEisBEQEUfgneoOtQ0p9ALb%2FSSokPYL8AAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="get" template="http://dict.lv/enlv/{searchTerms}" resultdomain="dict.lv" />
+<InputEncoding>UTF-8</InputEncoding>
+<SearchForm>http://dict.lv</SearchForm>
+</SearchPlugin>
+
+
+
diff --git a/browser/locales/searchplugins/diec2.xml b/browser/locales/searchplugins/diec2.xml
new file mode 100644
index 000000000..afba6bb67
--- /dev/null
+++ b/browser/locales/searchplugins/diec2.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>DIEC2</ShortName>
+<Description>Diccionari de l'Institut d'Estudis Catalans</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAFfKj%2FFAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167%2B3t%2B9f7vOec5%2FzOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP%2FwBr28AAgBw1S4kEsfh%2F4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv%2BCpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH%2BOD%2BQ5%2Bbk4eZm52zv9MWi%2FmvwbyI%2BIfHf%2FryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3%2FldM9sJoFoK0Hr5i3k4%2FEAenqFQyDwdHAoLC%2B0lYqG9MOOLPv8z4W%2Fgi372%2FEAe%2Ftt68ABxmkCZrcCjg%2F1xYW52rlKO58sEQjFu9%2Bcj%2FseFf%2F2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R%2BW%2FQmTdw0ArIZPwE62B7XLbMB%2B7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv%2FmPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5%2BASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1%2BTSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q%2B0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw%2BS3FDrFiOJMCaIkUqSUEko1ZT%2FlBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC%2FpdLoJ3YMeRZfQl9Jr6Afp5%2BmD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA%2BYb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV%2Bjvl%2F9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1%2BrTfaetq%2B2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z%2Bo%2B02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y%2FDMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS%2BKc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw%2BlXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r%2B00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle%2B70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l%2Bs7pAz7GPgKfep%2BHvqa%2BIt89viN%2B1n6Zfgf8nvs7%2Bsv9j%2Fi%2F4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww%2BFUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX%2BX0UKSoyqi7qUbRTdHF09yzWrORZ%2B2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY%2BybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP%2BWDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D%2BmiGT0Z1xjMJT1IreZEZkrkj801WRNberM%2FZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c%2FPbFWyFTNGjtFKuUA4WTC%2BoK3hbGFt4uEi9SFrUM99m%2Fur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl%2FVfPV5bdra3kq3yu3rSOuk626s91m%2Fr0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e%2B2Sba1r%2Fdd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q%2F5n7duEd3T8Wej3ulewf2Re%2FranRvbNyvv7%2ByCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9%2BmfHvjUOihzsPcw83fmX%2B39QjrSHkr0jq%2Fdawto22gPaG97%2BiMo50dXh1Hvrf%2Ffu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1%2F3yfPe549d8Lxw9CL3Ytslt0utPa49R35w%2FeFIr1tv62X3y%2B1XPK509E3rO9Hv03%2F6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r%2Fy%2B2v3qB%2FoP6n%2B0%2FrFlwG3g%2BGDAYM%2FDWQ%2FvDgmHnv6U%2F9OH4dJHzEfVI0YjjY%2BdHx8bDRq98mTOk%2BGnsqcTz8p%2BVv9563Or59%2F94vtLz1j82PAL%2BYvPv655qfNy76uprzrHI8cfvM55PfGm%2FK3O233vuO%2B638e9H5ko%2FED%2BUPPR%2BmPHp9BP9z7nfP78L%2FeE8%2Fsl0p8zAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn%2FAACA6QAAdTAAAOpgAAA6mAAAF2%2BSX8VGAAADAFBMVEX%2F%2F%2F%2F%2F%2F8z%2F%2F5n%2F%2F2b%2F%2FzP%2F%2FwD%2FzP%2F%2FzMz%2FzJn%2FzGb%2FzDP%2FzAD%2Fmf%2F%2Fmcz%2FmZn%2FmWb%2FmTP%2FmQD%2FZv%2F%2FZsz%2FZpn%2FZmb%2FZjP%2FZgD%2FM%2F%2F%2FM8z%2FM5n%2FM2b%2FMzP%2FMwD%2FAP%2F%2FAMz%2FAJn%2FAGb%2FADP%2FAADM%2F%2F%2FM%2F8zM%2F5nM%2F2bM%2FzPM%2FwDMzP%2FMzMzMzJnMzGbMzDPMzADMmf%2FMmczMmZnMmWbMmTPMmQDMZv%2FMZszMZpnMZmbMZjPMZgDMM%2F%2FMM8zMM5nMM2bMMzPMMwDMAP%2FMAMzMAJnMAGbMADPMAACZ%2F%2F%2BZ%2F8yZ%2F5mZ%2F2aZ%2FzOZ%2FwCZzP%2BZzMyZzJmZzGaZzDOZzACZmf%2BZmcyZmZmZmWaZmTOZmQCZZv%2BZZsyZZpmZZmaZZjOZZgCZM%2F%2BZM8yZM5mZM2aZMzOZMwCZAP%2BZAMyZAJmZAGaZADOZAABm%2F%2F9m%2F8xm%2F5lm%2F2Zm%2FzNm%2FwBmzP9mzMxmzJlmzGZmzDNmzABmmf9mmcxmmZlmmWZmmTNmmQBmZv9mZsxmZplmZmZmZjNmZgBmM%2F9mM8xmM5lmM2ZmMzNmMwBmAP9mAMxmAJlmAGZmADNmAAAz%2F%2F8z%2F8wz%2F5kz%2F2Yz%2FzMz%2FwAzzP8zzMwzzJkzzGYzzDMzzAAzmf8zmcwzmZkzmWYzmTMzmQAzZv8zZswzZpkzZmYzZjMzZgAzM%2F8zM8wzM5kzM2YzMzMzMwAzAP8zAMwzAJkzAGYzADMzAAAA%2F%2F8A%2F8wA%2F5kA%2F2YA%2FzMA%2FwAAzP8AzMwAzJkAzGYAzDMAzAAAmf8AmcwAmZkAmWYAmTMAmQAAZv8AZswAZpkAZmYAZjMAZgAAM%2F8AM8wAM5kAM2YAMzMAMwAAAP8AAMwAAJkAAGYAADMAAAD6Fxj8FRb7FBX5EhT8BQf7BQf5Bwj9Cgv8DBD7DQ%2F5DxH6EhX9ExT6ExX8FBb18ez84N3%2B6%2Br5Cgn4Dgz7Dw%2F2Ew%2F5EhH5ExH2FRH3FRL9ExP5FBT9FhX8FRX6FRX4FxX8Fhb7Fxb2Gxj5HRr2HRn3HRv%2F%2F%2F8AAAD5ITrYAAAA%2F3RSTlP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwBm%2FIpZAAAA7ElEQVR42mL4d4PhHwMAAAD%2F%2F2K4cYOFSRAAAAD%2F%2F2L494zhLRPDV4a%2FAAAAAP%2F%2FYrzBwMDAwPDrGQMTw98vDEwM3xkZAAAAAP%2F%2FYvzHdIuL9SOrIhMDw39R8f%2B8DAz%2FGH78ZvjxnoHhHwMEAFiWQx0AQSgAgOeT5IjMSOb%2Fv8Zut7ARwcL124%2BQW%2BRWJbdcfMLSlyFMx3RJknoaQvd22P35qZBjFQBBKArDvxe3ahIi8P3fKZwFJZzjhmAN3qXDNxx%2Bhr0KFDgF6ErXoytbAg%2BUx1zLDFFNcDNkMW0HAeJtwjtDXk3z4Ebit28Ab35LCW4dGm8AAAAASUVORK5CYII%3D</Image>
+<Url type="text/html" method="GET" template="http://dlc.iec.cat/results.asp" resultdomain="iec.cat">
+ <Param name="txtEntrada" value="{searchTerms}"/>
+ <Param name="OperEntrada" value="0"/>
+</Url>
+<SearchForm>http://dlc.iec.cat</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/diribg.xml b/browser/locales/searchplugins/diribg.xml
new file mode 100644
index 000000000..81df35a6f
--- /dev/null
+++ b/browser/locales/searchplugins/diribg.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Дири.бг</ShortName>
+<Description>Търсачка Дири на dir.bg</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAQAUAAAAAAAAAAAAAAAEAAAABAAD///8A7NLCAM+OZgDFd0cAyoNXAOK8pACySgoA+/TxANCPZwC0TAsAwm45AOzTwwDPiFkAuVENAMJmKgDpyLMAynxJAO7UwwDnwKYAvlYPAMp1OwD79PAA0opZAO/VxADXjlwAxFsRAPDWwwD46uEA8NbEAMlfEwDWh00A8tfFAM5kFgDRbiUA2opQAPPZxQDZfDUA02kYAPns4gDWciYA9NrGAOy3jQDZbhoA56VwAO6/mgD228YA/fbxAOB8KgDecxwA7LCAAPHCnAD33McA+ubVAOWALADjeB4A6ppWAOeJOgD43scA/O7jAO+scgDleh8A7aNkAOybVwD53sgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDAwQFAAMGAQAAAAAHCAkJCQkJCQoKCQsAAAAADA0ODwAADxANDQ0RAAAAEhMUFQAAAAAAFhMTFwAAABgZGgAAAAAAABsZGRwAAAAdHQAAAAAAAAAAHh0fAAAAICEAAAAAAAAAACIgIwAAACQlJgAAAAAAAAAnJSgAAAApKisAAAAAAAAsKiotAAAALi8wMS4AAAAyMDAwMwAAAAA0NTY1Nzc4NjU1NjkAAAAAADo7PDw8PD00Pjw/AAAAAAAAAAA5OQAAAD48PwAAAAAAAAAAAAAAAAA+PD8AAAAAAAAAAAAAAAAAPjw/AAAAAAAAAAAAAAAAAD48PwAAAAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8=</Image>
+<Url type="text/html" method="GET" template="http://www.diri.bg/search.php" resultdomain="diri.bg">
+ <Param name="came" value="s" />
+ <Param name="u" value="1" />
+ <Param name="browser" value="ff" />
+ <Param name="textfield" value="{searchTerms}" />
+</Url>
+<SearchForm>http://diri.bg/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/drae.xml b/browser/locales/searchplugins/drae.xml
new file mode 100644
index 000000000..cdc7f3c0b
--- /dev/null
+++ b/browser/locales/searchplugins/drae.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Diccionario RAE</ShortName>
+ <Description>Real Academia Española. Diccionario Usual.</Description>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA////AP///wDP8eMAw83LAKyLlgCshYMAqpKMSIc/QsyeYl2PmVZPPZlpbgB1enQApru/AL/V2wD///8A////AP///wD///8Az/HjAMHJxgCzn6oAmlZUSIk5NrWjgoOhoWRfwH0jINKAKiefi315GMDc4gDL6fEA////AP///wD///8A////AOj//wDCvrkAjEdOTYkvMv2bYWCYoH+CP59wcUSrgX2Ahiwk/5xBRMGTkpIAfYuJAP///wD///8A////AP///wCOqqMAnpCMOZRFQ9WRW164p4KAfpRmYXaGNTWNnnNxa5dZWbKLIij/gVhVbzI0LQD///8A////AP///wD///8AZ0xMH3hGRoiiZ2eNoGNkrJdjYbqeb22Jk01Lo5xlY66bZGWinGBfx7KNjaF+bm1B////AP///wD///8A////ALB7fE+bVlqzhUJCgZRfYIOfZmipjlhXep58d0WbY2XDoXp6cJZWVG+EMDPTkTU6iP///wD///8A////AP///wClYGJjn2BmtotNS7SCRECnooeJTax9fIibV1OeoHl0ZZlobYKVT1Fvjk9Qs38vM4b///8A////AP///wD///8AS0g/CYA+NoqeR0XfmGhno66ZmG6woKFxiFZXsKmHhGujcXCNjT0+6pNUU7ikgnk4////AP///wD///8A////AKPHvwCdoZ8mhykw3Y9MTbW3o6BlmndyT3hRTlq1nJmJmm1xkZ5TVtWAQD+Td2BXAP///wD///8A////AP///wDg2+YAu6qtJIA5MraJOjXTkmBfY4lRVIiVV1eds5uYL49XV52FJCb3q25qY6V3eAH///8A////AP///wD///8AwtzIAKSjmiiDR0x7ml5fzX4iJuiXVFZynGtvaYUeIuGQPDzem1has4JWVEOeYWQA////AP///wD///8A////AM7/6wCgwLcAjFNRK8XEwwCffXyKhigozZRDSa11MSu9oWBfQJJ+fEBle3YAg316AP///wD///8A////AP///wDAz8cAuL65AMCyqwCpiIQLl2RkbY0+P9GHHBn9gkxKfIx3cQByfHQAYIiAAF+DewD///8A////AP///wD///8AmnN0AJ57egCikI4AlGRkrZE/Q+qWTEivi0BArJU2Pv6VbmuKi3p1AJF3cwCQd3MA////AP///wD///8A////AKmcngCpnJ4App6iALeemiqndW1jiU9Pm55ueZefXFlkv7OxItr6/wDT5u0A0+btAP///wD///8A////AP///wCxq7AAsauwAK6stADBrKcAsZWHAJyAg0ewpqVAtIN3ALmrqgDH9f8AxOTsAMTk7AD///8A////APw/AAD4DwAA8A8AAOAHAADAAwAAwAMAAMADAADAAwAA4AcAAOADAADgBwAA9A8AAPg/AAD4HwAA+B8AAP5/AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wDO8OQAy+HSALmopQDC2vIAs6GoAJ9gXgClcGgAro2IAMTc2SeHRkqQjU5TiaiOkC6MRTxF08K4I2hWTQBTFR0Az5SYAGRdWQB+Zl0A0fP2AL/c3gC/3N4A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AM7w5ADL4dIAuailAMLa8gCzoagAn2BeAKd1bQCxkI0AqIKCi3EIBv+HQ0TinG5qg4g1LLm9hnyAZ1ZMAFMXHwDRlpsAZl9cAH5lXADR8/YAv9zeAL/c3gD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AzvDkAMvh0gC5qKUAwtnyALKhpwCgYWAAr4iBD5NUUG2JLibvk1RWxaRybZyTQj7RkE5O1H8MCv9mDQu+eTc+V8qanC5lYVoFfmVdANDz9gC/3N4Av9zeAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDO8OQAyuDQALutqwDG5P0AtKOpA5xcX0eYUE2eexES0Z12dpbI6e4RrJGPepZMSrqvko1UmG9wn5A4MOuRMirkkkNB2Gw+Om6ahHoA0P//ANb3+QDX+PoA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AM7w5ADM5dYAtZuWAMHP5gC2qK85jTs1x4MoK/GUQ0HRmGhmYaeUlzOSZnJelmdlQ65wYmmwiY1un398hYgnFtp2Dw//m09TtLKHhWS0x8wEjaKjAIqhogD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A0/frANP36gCugHUAlWJtAZVaX5p6AAD+mGFlm6J0dYaYUEqgn1ZWlZldZICOUFByll1dcJpcX6CWU0qipYCBfZRQT8SAGxfnoVBVqZNtaQYAAQAAAAAAAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDQ9egAzevfCqh9eUF+JiOrfBwZ/513fJyXWGGZlltbnKyJf5aQcWwjhGJhUaFWT6xWVFcAooOKVZpiWaWfdnSPlkhHtHMICvmAFiHxkGBdlVJIQh1cU0oA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAHBgBfYWIeuIOLxpNJQtaMSkvQs724VpJVT6aPTU/Nu7e3X5JxdWBzLy2ilk1FwXszLZKea3J2qIyGdpVPR8N/HibFiEpTwoo4MtCXUlXgvaGeZ5GEeAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAGFTTjOhamWgnpGRLKVwcnKYVFWuhTk+36aAgI2phH9/o3Boo6Fuco6lcHJRfQUA/4g/Otqsi4dlpXt+loo0O9uje3aYn3FzoLORlIC0rrBDKScmHv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wAuNzYAqnFumWkYGcJyODpZoVRXoZ1OUrKaXmCrnHV3kJVYVsWPQTO8rY6GYbCDh2qVTkqhjUpD4qR+hZGrjpBrjURBz5ZaVai1mJNloXB1hppoZLOtkItm////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJh6eR6ea3OSj0BBupxQTYWhbWlxll5bl49KT7KjcXKFlmlroXoaIfmRam1ehnx7MbCAel6bam+2nmputKp8dlucZF2ihjg41JFFRsuLNTbvjkZD0o5NVWn///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Au5OQQZ5gY5aRTk2gt4mJa3FBPz9vLTOmsI2Lfa+ZlVybYWmXnVBNoZuIhD+WoJ0ApFpXjpNWUdaPX1+1nXqAa6Jzc1+tfnhDgTM2a3oyOXOQOTm5fBkeiv///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCYU1NXjkVMxJRbZrOSYGivhD8/spA3NMqWZ2mBgU1Pe6+RkGS3mpNVoFNPqJxGQLCQR0OdsIqAWJVcYYSOdHucoHJ6bZtYVDyXXVptj1NfZ4Y7Od5uFB6a////AP///wD///8A////AP///wD///8A////AP///wD///8A////AK5ycV+cW1+gmGBfhY9VVZ+XXVe4jTMr6IFWV3ZzU1p1t5efVp9zb4eba2qyllhVw5hkZH6wj4k3oXRwdp1tZ2CcXmOjhyov345CQ7OfZGOPj1pXyH9JS33///8A////AP///wD///8A////AP///wD///8A////AP///wD///8ASkdGFKyIhHeYPj6ofh0a2aSCf5OISEO9pnVtorSPi3+oiopss5+dXKiXoH+YYmqrmllQnJ18fX2rmJJ2t5CJU4M3Pc2TVVbLm11YnIxHR7GmfHmQr3htU////wD///8A////AP///wD///8A////AP///wD///8A////AP///wAfKCIAgGpbFJA/O6qCGBj/om1qp5ZkZ5+MU1SysqOeWqiLjJTI4+ARq4B/h41BSNmZZ1+jl2JguqJ7hnKph4hzj0hGyaF4cYyMNjXUhiAl+ZhqZpOcfnMN////AP///wD///8A////AP///wD///8A////AP///wD///8A////AFJtXABab10CmW1qjKZdYrmLNTLJmFpgiIpESsOujo2HsI2Ne6R6doy0k4RMNTIzFnNnbEi1i4e3s5GMZJ1jZqGTXmGnpoCFdIk3Pem1bHCxRyEnSgAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A8P//AOz//wylrKooh1Zla30OEv9/KiLjlmZrhayGh4W2iX5WqYJ8WJNSVIhkJihskktGgr6spVqvoJ9/iDw+05NeYJ+nfoOGiCor4rV7d7JfQjg/DRMKAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDI1eAA09zoCItzdSN0MzGklkpO64c9NMKqeXGIgn56FoZWWIyzioxtc1lZK6Jub5a/kohiq5SQY7S2r0WRYGVmhTY0zYk9N89zBQbtnWFhiLeIghu/gX4A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AK6HiQCmfYI1p3NwZptoWGaWWFSxjzs5vZFLSruLYV5mjkxMhYs6PLKCVFSYjllgn5JMUZWMP0OMuKGaEKeIhEl9FxryhC0w1ZBNUZ+eWFdzrn+BUKpzdwX///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AyujVAMXk0gu1komOdERFUppsaVKKNDLsgS4264UoJe2IMDizizw+c6BkYZGZcG20hDEyq5BJUaqVSk6Jijw8wZJNSNeaYnCdllxblb91bapoTEc0BQ0IAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDQ/+cA4v//AIyWlw1uPkl/oV5lgZhpZ4+lhISkjkhHyHcLEv+HLC7DpGtvDbWenwyNQEGZhCUq93EAAP+ENDfwmV1bn6FeX3OucnGhWk1KUgkCAwAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AM383wDk//kAdISFAGhgYQCsd3GBnWZncMbi6hayv78giDUxwIQpJPifc3BlpXaATIowO7OFKSX1qmxivplaVn2RRkuXgSklrJSMjUVFX2MAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Azv/iAOX//AB0hYYAYlJVALeZjAKlcWoIxMnEAMbX0AKOREPHdwkH9IczNdyVSlDWdAAA/3suLtx5e3cPkkM+AKNcYSGhnJUwd5KMBEVYWgAAAAAAAAAAAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDP/OEA4//3AIKVkwBza2sAwKufAMC6tQCvl4YAk05PVYg9Od2WUlR8iDk51YozMLGaTUKqdSAl5VdIQA5LPTYAp46PALi4swCOm5cATlRVAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AMixuADIsbgAybK5AMmzugDGr7gAwb69DJpuZWSRXGR0qYmFYpRTVpePT0rAjj4+t4wtLrKIKCnXmFBShZmekx9JW1sAHSAgAC8/PgAZJCYAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8Aa05YAGpNVwBtUVsAa1BaAG1RXgCfgIJvkVNWt5NXWKyfYmSejTU3vo07N8eUTVC2iDAvwZhZW52JLzvetGln0lxFPyoQAAAAJQsIAC4SDQA9HhUAORsTAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wAAAAAAAAAAAAAAAAAAAAAACQ8MAJBpYo2NNTLojkBEypRDQ9B3BgL5k0tDp5xzcYaCLTHnjkZTu5BLTraWZWC3wJuYVc2mogDIoZ0AyKGdAMihnQDIoZ0A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAAAAAAAAAAAAAAAAAAGBQQAr6erI7yak2qtj4xpsJGQg5E5PK2niotkm2txa4koL7GfYGCcpWdkeqp/e2nE4Ocbz/j/AMzv/ADM7/wAzO/8AMzv/AD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAAAAAAAAAAAAAAAAAAUAAAC0usQA0MnFAMWijwKXgn8yhlZTcqBsb6yleoCbpI6WcLSXkz+3eGoMqHFqAMTf4gDR9P8Azez2AM3s9gDN7PYAzez2AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wAAAAAAAAAAAAAAAAAAAAAABQEAALK1vgDJu7cA1KqXAHhhYABcbnAAh1NWlLCMjm686/oAxL+2ALV3aQCmbmkAw93hAM/y/gDM6vQAzOr0AMzq9ADM6vQA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AAAAAAAAAAAAAAAAAAAAAAAFAQAAsrW+AMm8twDSqpcAa1JRAISuugDFs7VJq6aYMb7i4gDBt7EAtHZnAKZuagDD3eEAz/L+AMzq9ADM6vQAzOr0AMzq9AD///8A////AP///wD///8A////AP/4H///+B///+AB//+AAf//gAB//wAAf/wAQD/8AAA//AAAH/wAAB/4AAAf+ACAH/gAAB/4AAAf+AAAH/wAAB/8AAA//AAAP/wAAD/8AAAf/AAAP/4AAH//gAD//5AI///wD///wAf//8AD///AA///wAP///AP///+f////n//</Image>
+ <Url type="text/html" method="get" template="http://dle.rae.es/" resultdomain="dle.rae.es"
+ rel="searchform">
+ <Param name="w" value="{searchTerms}"/>
+ </Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/dunaj-sk.xml b/browser/locales/searchplugins/dunaj-sk.xml
new file mode 100644
index 000000000..84ac4b406
--- /dev/null
+++ b/browser/locales/searchplugins/dunaj-sk.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Dunaj</ShortName>
+<Description>Dunaj.sk - zelena vasim nakupom</Description>
+<InputEncoding>WINDOWS-1250</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACOUlEQVQ4jb2ST0iTcRzGn/e3952b29vLfJtuvrp0zaEzjXA2qCHV0MBTlJ4iCIqKDp28dOpS0C0i+nOIOnQKolAqEKJ/BhmWEuuQdggbvmtm5u/9ve/e7XXv1sUshMAKeu6f5/v9wAP8Y7i/BYONkb3Hjx09zP8BIwCwAZQB+Jy14XPqItXWTSeSqZPb4okBAPymeN9EonfwCYDqdSt09w2+MIu27OLL0wLPO1+O3jkAwPydgmPlXQBAMBhsy9FiFzNMlySK0VL29Q4AJgCQXyASCkdTkdbWvpWC1dSHYwOU5V0cR8CMPBGVzquBQKj9xyWEQuFkQyxxWawiWYMydfuu3iFFUTYbRVMzGdP9Ld03i9ay0OAT7oNmznyYGjur6/QTgAofjcVSy1J0mC99PsKWlnJN8dRIak9SfjT68MZXVb3klmWlQtWL9H36Xq5AP651JaruPBX0VaffTozf1qrqLhzc3y+ndsZhFO36aFvHvsAGWdEWMk8lSawFIK0t4MKdySsFQTpkZafizU0tPZYYujbQv5uIXg+oxlCyy3A4CB4/uHv6+aux62AsD6/XA13/AgCcLMuKsqVn+Jtpd4R9lfOMLs5Q3Qps9PubRY/XkZ3PaQW2NFkw6azPF2wXBCJMv5seyecXVODnlMXI1sRQ2V13wuN2GrbJJt1Obs7hICWqGW6bIzWSKLpg0tE3489uAdBXFdYo1TQ2R+KhpkiXYZVqCEjFsvK5+bnZ9LyaGQew/un+t3wHzZ7XlmdRXP0AAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="http://www.dunaj.sk/default.asp" resultdomain="dunaj.sk">
+ <Param name="bShowGoodsList" value="True"/>
+ <Param name="bShowSearchForm" value="True"/>
+ <Param name="sSearchBasic" value="{searchTerms}"/>
+ <Param name="nDepartmentID" value="10000"/>
+ <Param name="sourceid" value="firefox"/>
+</Url>
+<SearchForm>http://www.dunaj.sk/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/eki-ee.xml b/browser/locales/searchplugins/eki-ee.xml
new file mode 100644
index 000000000..3cce716fb
--- /dev/null
+++ b/browser/locales/searchplugins/eki-ee.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Õigekeelsussõnaraamat</ShortName>
+<Description>EKI.ee Eesti õigekeelsussõnaraamat ÕS 2013</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAhUML/4A7AP+AOwD/gDsA/4A7AP+AOwD/tY1q///////ezb7/gT0D/4A7AP+AOwD/gDsA/4A7AP+AOwD/klck/4A7AP+AOwD/gDsA/49TH//Pror/z6uC/+DJr///////1sCt/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4pLFf+AOwD/gDsA/4A7AP+gbUH/vIQ//6ZcAP/Gllz//////+HRxP+hbkP/hUMK/4A7AP+AOwD/g0AG/4hIEf+SWCX/yqeC/8qngv/Kp4L/z62H/6xnEf+mXAD/xpZc///////m0rn/tXku/7GEWv+AOwD/jlEd/8qidP/DlFr/yJ1o/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/8aWXP//////4sqt/6ZcAP+/j1b/v5Zs/8OZbP+qZA3/plwA/61pFf/PpXX/z6d3/6ZdAv/Bjk//yJpi/8iaYv/cvpv//////+jVvv+4fjb/uH42/7R2Kf+mXAD/plwA/6ZcAP+taRX/vYJA/9Svhf+zdCb/zJ5r/7p8N/+6fDf/0qt////////bvZj/wIxL/8CMS//SrX//snIj/7mAOP/Qp3j/0qx//6t7UP+ziWT/1bCH/7yAPv+6fDf/vIE//+TJpv/26dX/5c6y/9KsfP+8hUD/v4pJ/8GNTv/SroD/woxQ/8eWX/+AOwD/iUkT/8qcav+6fTn/vIA+/8eVXf/nyJX/5MGF//jt1v/25sP/6dS1/6lhCf/ElFj/yZ1m/8mbY//NpHH/oWw//66BWf/Il2H/4sWe/+fLof/nyp//4rt7/+TBhv/47dr/9+fH//Lix//Im2H/wY5N/6ZdAv+mXAD/rWkV/8GJTP/Ah0n/v4VF/+nOof/huXb/7NSr//Dcuv/x3r7/5sSM//Xny//25L//9+jI//fq0f+5gDn/qmQN/7FxIv/mzq7/5s6x/+PFnv/kwIX/58eR/+vPov/huXf/4bh0/+G4dP/26M//9ufI//bmxP/36Mr/1bGI/8udaf/RqHr/9uS///XlyP/huHX/4bh0/+jIlP/47tv/9OTG/+K7e//huHT/79m2/+TBhf/x3rz/9ujJ/8qaZf+6fDj/v4dI//HevP/qzZ3/6cyb/+3Vrv/x38H/+OvP//fqzv/kv4P/4bh0/+3Wr//huHT/79ew//fpy//r1rX/1q+B/7+HSP/huHT/4bh0/+vQo//huHT/6MiU//jrz//36s7/5L+D/+G4dP/t1q//4bh0/+/XsP/36cr/58aQ/+C/kv+/h0j/4rt6/+G4dP/rz6L/4bh0/+jIlP/468//9+rO/+S/g//huHT/7dav/+G4dP/v17D/9+nK/+fGkP/gv5L/w45T/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAll0s/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/jVAb/+vh2P/////////////////Os53/hkQM/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gT0D/7GGYv+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+EQQj/18Kw/////////////////6+EX/+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/lVwr/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+KShT/vpt8/9rFsf/dybb/3cm2/97Luf/07uj/////////////////rYFb/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+VXCv/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/7WNa//iyq//wY1N/8GNTf/BjU3/w5BS/+vbx/////////////////+tgVv/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/5VcK/+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/wJ+C/9Ovgv+mXAD/plwA/6ZcAP+oYQj/48yw/////////////////62BW/+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP+AOwD/lVwr/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/4A7AP/An4L/0at8/6ZcAP+mXAD/plwA/6hhCP/jzLD/////////////////2se2/8anjf+/nH//lVsq/4A7AP+AOwD/gDsA/4A7AP+AOwD/gDsA/41PG/+RViP/kVYj/5FWI/+kckj/w6OH/8Ojh//Do4f/w6OH/8Ojh//Do4f/xqeM/+PTw/+7gz7/plwA/6ZcAP+mXAD/qGEI/+PMsP/////////////////WtYz/wIxM/8WWXP/Qs5X/iEcQ/4A7AP+AOwD/gDsA/41PGv/Rt53/4cy1/+HNtf/hzbX/4c21/+bVwv/Rq3z/0at8/9GrfP/Rq3z/0at8/9GrfP/Qqnv/wo9R/6hhB/+mXAD/plwA/6ZcAP+oYQj/48yw/////////////////8WWW/+mXAD/q2YQ/+DHqf+NTxr/gDsA/4A7AP+AOwD/roJb/9Cpef+nXgP/plwA/6ZcAP+mXAD/tHcr/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6hhCP/jzLD/////////////////xZZb/6ZcAP+mXQL/0q1//7iQbf+oeVH/qHlR/6p8VP/RtJj/uX42/6ZcAP+mXAD/plwA/6ZcAP+0dyv/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/qGEI/+PMsP/////////////////Fllv/plwA/6ZcAP+rZQ7/x5he/9Wyh//Vsof/0q1//72HRP+mXAD/plwA/6ZcAP+mXAD/plwA/7R3K//kzrP/5M6z/9/Fpf+4fjb/plwA/6ZcAP+oYAf/rmsY/69tG/+vbRv/r20b/69tG/+xcSL/5tG4/////////////////8WWW/+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/tHcr/7p8N/+6fDf/zaBu/9m6lP+oYQj/plwA/8+od//gxab/4cep/+HHqf/hx6n/4cep/+LJrP/17eT/////////////////3sOh/8uhbP/LoWz/y6Fs/8uhbP/LoWz/uoA5/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+0dyv/unw3/7p8N//Il2D/3cCe/6liCv+vbBr/38Sk/7yAPf+6fDf/unw3/7p8N/+6fDf/vIA9/+nWwf////////////n07f/bvZn/2ryX/9q8l//avJf/2ryX/9zAnf/o1b7/tXkt/6ZcAP+mXAD/tnkt/+DGp//jzbH/482x/+jVvv/AiEr/wIhK/82hb//gxqf/snIj/8GOT//auZX/unw3/7p8N/+6fDf/unw3/7p8N/+8gD3/6dbB////////////8OPU/6deBP+mXAD/plwA/6ZcAP+mXAD/plwA/9/Dov/ImmH/plwA/6hhCP/iyq3/w45S/7p8N/+6fDf/xZJZ/9e8of/XvKH/2sKq/+LRv//kz7T/2rmU/8OPVP+6fDf/unw3/7p8N/+6fDf/unw3/8WSWf/x5Nb//v79//7+/f/w5NX/qWIJ/6dfBf+nXwX/p14E/6ZcAP+mXAD/3sOi/8iaYf+mXAD/r2wa/+nWwP+6fDf/unw3/7p8N//Fkln/gDsA/4A7AP+AOwD/k1gm/9u9nf+6fDf/unw3/7p8N/+6fDf/unw3/7p8N//FkVf/7t7H/+rPof/qz6H/79u5//z69f/9+vP//frz//368//v4c//tnow/6ZcAP/TsIP/2bqU/72GQv/Ek1b/7uHR/8udav/LnWr/y51q/9Sug/+AOwD/gDsA/4A7AP+TWCb/272d/7p8N/+6fDf/unw3/7p8N/+6fDf/unw3/86jc//u2bn/4bh0/+G4dP/oypf/+/Xq//foyf/36Mj/9+jI//rx3v/Xtoz/plwA/7JzJP/cwJz/6NW+/+nXwf/u4M//6tjD/+rYw//q2MP/7d/N/4A7AP+AOwD/gDsA/5NYJv/bvZ3/unw3/7p8N/+9gkD/v4VF/7+FRf+/hkb/1a+G/+7Xtf/huHT/4bh0/+jKl//69Oj/9uS///bkvv/25L7/+OvQ/9y+mf+mXAD/plwA/6ZdAv+nXgP/p14E/6deBP+nXgT/p14E/6deBP+2ei7/gDsA/4A7AP+AOwD/m2U3/9y/oP+6fDf/zJ9t/+fSuv/r2cX/69nF/+vZxP/t28H/58eR/+G4dP/huHT/6MqX//v16//46s7/9uXA//bkvv/469D/4cen/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/6ZcAP+mXAD/plwA/7R3K//Cnn7/wp5+/8Ohgf/aw6z/yppm/7+GR//x4s7/5MCF/+K8fP/ivHz/4rx8/+K7ef/huHT/4bh0/+G4dP/oypj/+fLl//PjyP/5793/9uXB//blwv/58eP/7d3I/+nWv//p1r//0Kl4/6ljC/+mXAD/plwA/6ZcAP+mXAD/tHcr/8iXYf/Il2H/yJdh/8SPVP+6fTj/w45S//Lk0f/huHT/4bh0/+G4dP/huXb/58eT/+nMm//pzJv/6cyb//LhxP/x38D/4bl3//TlzP/36cn/9uS+//fnxf/47NL/+OzS//js0v/37+D/wY5P/6ZcAP+mXAD/plwA/6ZcAP+0dyv/unw3/7p8N/+6fDf/unw3/7p8N//Dj1T/8ePN/+G4dP/huHT/471///Pkyv/37Nr/9+zZ//fs2f/269n/8d7A/+TAhf/huHT/8+PJ//fqzf/25L7/9uS+//bkvv/25L7/9uS+//fs1f/QqXn/r20b/69tG/+vbRv/r20b/7yGQf/VsIb/1bCG/9Wwhv/VsIb/1bCG/9/Cof/s1bD/4bh0/+G4dP/s06n/9OXN/+K6eP/huHT/4bh0/+G4dP/huHT/4bh0/+G4dP/z48n/9+rN//bkvv/25L7/9uS+//bkvv/25L7/+O7Y/+fUvP/XtY3/17WN/9e1jf/XtY3/4suu//fr1v/369b/+O/f//Xp1//t1rH/6s2f/+K8fP/huHT/4bh0/+/Ztf/y4cX/471//+O9f//huHX/4bh0/+G4dP/huHT/4bh0//Pjyf/58N3/9+vS//fr0//47NP/9ubD//bkvv/37Nb/2beS/7+FRf+/hUX/v4VF/7+FRf/LnGn/9uS+//bkvv/468//8d/B/+G5dv/huHT/4bh0/+G4dP/huHT/79m1//r16//26dP/9ujR/+vRpf/huXf/4bh0/+G4dP/huHT/8+PJ/+7Wr//oyZf/6MqY/+/at//57df/9uS+//fs1f/Wson/unw3/7p8N/+6fDf/unw3/8WSWf/25MD/9uTA//js0v/x3r7/4bl2/+G4dP/huHT/4bh0/+G4dP/v2bX/+/Tl//fnx//36Mn/+O3Z/+bEjP/huHT/4bh0/+G4dP/z48n/6MiU/+G4dP/huHT/5cKI//ju2v/25L7/9+zW/9m3kf++hEP/u387/7p8N/+6fDf/xZJZ//vz4//78+P/+O7b/+nKmP/hunj/58eS/+rPof/qz6L/6s+i//Tlzf/68uH/9uS+//bkwP/4793/58eS/+G4dP/huHT/4bh0//Pjyf/oyJT/4bh0/+G4dP/lwYb/+O3Z//bkvv/57tn/8eXV/+fRtv/auJP/v4ZG/7p8N//Fkln/6MiU/+jIlP/lw4r/4bl2/+nMnP/z48j/8Ny7//Dcu//w3Lv/9+za//ry4f/25L7/9uTA//jv3f/nx5L/4bh0/+G4dP/huHT/8+PJ/+jIlP/huHT/4bh0/+XBhv/47dn/9uS+//nu2P/w3Lv/5sSN/+3Xs//RqHn/unw3/8WSWf/huHT/4bh0/+G4dP/huHT/79i0/+jIlf/huHT/4bh0/+G4dP/v2bX/+vLh//bkvv/25MD/+O/d/+fHkv/huHT/4bh0/+G4dP/z48n/6MiU/+G4dP/huHT/5cGG//jt2f/25L7/+O7X/+3Vrf/huHT/6c2e/9axhv+6fDf/xZJZ/+G4dP/huHT/4bh0/+G4dP/u2LP/58eS/+G4dP/huHT/4bh0/+/Ztf/68uH/9uS+//bkwP/4793/58eS/+G4dP/huHT/4bh0//Pjyf/oyJT/4bh0/+G4dP/lwYb/+O3Z//bkvv/47tf/7dWt/+G4dP/pzZ7/1rGG/7p8N//Fkln/4bh0/+G4dP/huHT/4bh0/+7Ys//nx5L/4bh0/+G4dP/huHT/79m1//ry4f/25L7/9uTA//jv3f/nx5L/4bh0/+G4dP/huHT/8+PJ/+jIlP/huHT/4bh0/+XBhv/47dn/9uS+//ju1//t1a3/4bh0/+nNnv/WsYb/unw3/8WSWf/mxIz/4bh0/+G4dP/huHT/7tiz/+fHkv/huHT/4bh0/+G4dP/v2bX/+vLh//bkvv/25MD/+O/d/+fHkv/huHT/4bh0/+G4dP/z48n/6MiU/+G4dP/huHT/5cGG//jt2f/25L7/+O7X/+3Vrf/huHT/6c2e/9axhv+6fTr/1K6E/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.eki.ee/dict/soovita.cgi">
+ <Param name="D" value="qs"/>
+ <Param name="Q" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="http://www.eki.ee/dict/qs/index.cgi" resultdomain="eki.ee">
+ <Param name="F" value="M"/>
+ <Param name="Q" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.eki.ee/dict/qs/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/elebila.xml b/browser/locales/searchplugins/elebila.xml
new file mode 100644
index 000000000..e75d951dd
--- /dev/null
+++ b/browser/locales/searchplugins/elebila.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Elebila</ShortName>
+ <Description>Euskarazko bilatzailea.</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image height="16" width="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AezWtAHs1rQR7Na0BezWtAHs1rQB6NKwAhTq4O6tN46ajSdqdezWtCHs1rQB7Na0AezWtAHs1rQD///8A////AHs1rQB7Na0CeTSrAHo1rAB7Na0AeTSqAKpM4dqoTODTlEHJl3w1rgl6NawAeTSrAHs1rAB7Na0A////AP///wB6NKwAezWtD4Y6uT+AOLMzejSrAXo0qwCrTuPsgzm1UXcypwB4NKoAfzexJoY7uj99Nq8hejWsAP///wD///8AfzeyLKxO5Oa2Tu/9skzr9ZpE0LN3M6gCqk3i6IU6uFh3M6kAjD3AZ7RR7f+0TO73sEzp9Io8vmv///8A////AJlDz7WjTte4UUt7Rk9Re3h1OaUrejOsAKpN4umFOrhVdzOoAa1M5vuFSbRnSFBzbltJiGF7Nq0X////AP///wCaRM+1oEjWwmxfoN9wbqb/dD6kWnszrQSqTeLohTq4VXczqQGsTOTxiEe6p21upP9vYKLiejaqLP///wD///8AmEPNsrJQ6+KkRNuypUTbrrdS8P+EOrdFqEzg34U6uFV3M6gBq03j86pL4sikQtutq0nhx61O5dj///8A////AJZCzKmpTOHOfzexNYU7uEq0U+3/hDm3RqhM4N+FOrhWdzOoAapN4vOWQ8yDfjawNJRDyIiuT+fd////AP///wB8Nq4YoEjXt6tN492qTeLYkD/Ee3czqACqTeHnhTq4WHgzqQCFOrhHqUzh0atN492mSt3KhDq4RP///wD///8AejWsAHgzqQB5NKsAeDSqAHgzqQCBOLMmsFDo8YU6uFt6NKsAeTSrAHgzqQB5NKsAeDOpAHk0qwD///8A////AHs1rQB7Na0AezWtAHs1rQB5NKoAnEXRuqtN4+x+N7EyejSsAHs1rQB7Na0AezWtAHs1rQB7Na0A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//AAD//wAAzh8AAN4fAADCYwAAgEEAAIIBAACAAQAAgAEAAIABAACCQQAA/H8AAPx/AAD//wAA//8AAP//AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AezWtAHs1rQB7Na0EezWtCHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AeDOpBIs9v4+mSt3vp0ve76lM4fiRQMageTSqAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAP///wD///8A////AP///wB7Na0AezWtAHs1rQh7Na0SezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHg0qgCRQMagvlb4/79X+v+6VfT/vVf4/6VK3O13M6kRezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0A////AP///wD///8A////AHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AdzOpEahM4O7CWf3/l0PNwXw1rkx/N7JZfjawPXs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQD///8A////AP///wD///8AezWtAHs1rQB7Na0AezWtAHo0rAB6NKwAejSsAHs1rQB7Na0AezWtAHs1rQB3M6kRp0ve7rtV9f9/N7JbeTOqAHo0rAB6NKwAezWtAHs1rQB7Na0AejSsAHo0rAB6NKwAezWtAHs1rQB7Na0AezWtAP///wD///8A////AP///wB7Na0AezWtAHgzqgB5NKwnfzeyWX83slZ8Nq9RdjOoFHczqQB6NawAezWtAHczqRGnS97uulT0/383slV6NKwAezWtAHs1rQB7Na0AeTSrAHczqRB+N7FRfzeyVn43sVl4M6opdzOpAnk0qwB7Na0A////AP///wD///8A////AHo0rAB4M6kEij2+jqxN4/W5VPP/uVPy/7NR6/+kStvskUDGrX42sCh6NawAdzOpEadL3u66VfT/fzeyVXo0rAB7Na0AezWtAHg0qgCAOLNVoUjY4bhT8f+5U/L/t1Lw/6lM4feYRM7Igzm2V3o0rAD///8A////AP///wD///8AeTSrHZA/xaC/V/n/v1b6/7dS8f+5U/P/ulP0/75W+P/EWv//jj/DnngzqQB3M6kRp0ve7rpV9P9/N7JVejSsAHs1rQB5NKsAgTi0WbRR7P7CWP3/uFLy/7hT8v+5U/P/vFX2/8Vb//+lStzkdzOpDf///wD///8A////AP///wCLPb+OtFLt/71U+P+XU8a8gj6zVHk1qUZ2NaVBhjm6bZQ/ya5+N7ApejWsAHczqRGnS97uulX0/383slV6NKwAezWtAHczqQegR9fXwlj9/6VS2eKGSLNtfDSuRnY1pUR+N69QlT7LrYU6uFx6NKwA////AP///wD///8A////AJFAxqq7VfX/qEjh8HZFoBRLSW8ANFlcfTViXqBDRWkeajaYAHs0rQB7Na0AdzOpEadL3u66VfT/fzeyVXo0rAB7Na0AdzOpEahM4O67VPX/gT6yW11NgQA1T1pMN2VhrTlQXkxfOIoAdzWpAHw1rgD///8A////AP///wD///8AkEDFqrpV9P+pSeHuXzKMBCddTIREln7/SJuF/y91W9E8QV8HeTWrAHw1rgB3M6kRp0ve7rpV9P9/N7JVejSsAHs1rQB3M6kRp0ve7rtU9f96NKxKL0hSLjqKbv5JnIb/O4lv/itMTDxrN5oAfjSwAP///wD///8A////AP///wCQQMWquVTz/65P5vmPPcOrgk614YFguP+BX7n/gFi1/ok8vJh9NbA3ejWsAHgzqRGnS97uulX0/383slV6NKwAezWtAHczqRGmS97uvFX2/5VAysKGRLnCf162/4Ffuf9/X7b/h0S6woI2tVJ6NKwJ////AP///wD///8A////AJBAxaq2U/D/vlf5/8JZ/v/FVv//xVP//8VT///GVf//wFf6/65O5v59Nq84dzOoB6dL3u66VfT/fzeyVXo0rAB7Na0AdzOpEaZK3e6/V/r/wVn8/8RY/v/FU///xVP//8ZT///DV/3/uFTy/4o9von///8A////AP///wD///8AkEDFqrhU8v+zUez5kEDFrpBAxamQQMWqkEDFqY9Bw66yU+n5ulX0/4A3slB2M6gDp0ve7rpV9P9/N7JVejSsAHs1rQB3M6kRpkve7r5W+P+YRM7Fjz/DpZBAxaqQQMWqjT/CpaNM2OHCWf3/kUDGqv///wD///8A////AP///wCRQMaqu1X1/6tN4+5zMaQSdTOnAHYzqAB2MqkAazWVEqFP0+68Vff/gDeyUHYzqAOnS97uulX0/383slV6NKwAezWtAHczqRGoS9/uvVb3/383slZzMaQAdjOoAHYzqQBuMpwAhkWxq8NZ//+RQMaq////AP///wD///8A////AIs9v460Ue3/v1f5/5hEzrx+N7FXfjexUn83sleXQ828vVf3/7RR7f99Nq86dzOoBqdL3u66VfT/fzeyVXo0rAB7Na0AdzOpB6BH1tfDWf7/p0vf4oI5tm1+NrFQfjawT4s9v5GyUev5wFj7/4s9v47///8A////AP///wD///8AeTSrHZA/xaC+Vvj/wln9/7tV9f+6VfT/u1X1/8JZ/f+8VfX/kD/FoHgzqQB4M6kQp0ve7rpV9P9/N7JVejSsAHs1rQB5NKsAgTi0WbNR7P7DWf7/vVb3/7pV8/+6VfP/wFj7/8FY+/+iSNjceTSrHf///wD///8A////AP///wB6NKwAeDOqBog8vIagR9bdqEvf8qdL3u6oS9/ymETOx4M5tmp4M6oGezWtAHczqRGnS97uulX0/383slV6NKwAezWtAHs1rQB4NKoAgTi0VplEzsmoS9/yp0ve7qhM3/KgR9bciT2+j3k0qx16NawA////AP///wD///8A////AHs1rQB7Na0AeDOqAHczqQt3M6kSdzOpEXczqRJ3M6kGeTSrAHs1rQB7Na0AdjKoEaZK3e66VfT/fzeyVXo0rAB7Na0AezWtAHs1rQB5NKsAdzOpBnczqRJ3M6kRdzOpEnczqQt4M6oAezWtAHs1rQD///8A////AP///wD///8AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rR9+N7FlrU7l87lU8/9/N7JVejSsAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAP///wD///8A////AP///wB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB3M6kJn0fW2r5X+f/BWPv/ulX0/4A3slV6NKwAezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0A////AP///wD///8A////AHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHk0qgCSQMekqUzh+KhL3/OdRtPWezWtHns1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQB7Na0AezWtAHs1rQD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//////////////////////////8/wP//P8B///+A////h///g4fA/gCHgHwAhwA8AIYAfCOGEfwBhgD8AIYAPAAGADwABgA8OAYePAAGADwAhwA+AIeAf4OHwf//B////gf///8H//////////////////////////////////</Image>
+ <Url type="text/html" template="http://elebila.elhuyar.eus/search/" resultdomain="elhuyar.eus">
+ <Param name="opensearch" value="on" />
+ <Param name="lang" value="eu" />
+ <Param name="bilatu" value="{searchTerms}" />
+ </Url>
+ <SearchForm>http://elebila.elhuyar.eus</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/eudict.xml b/browser/locales/searchplugins/eudict.xml
new file mode 100644
index 000000000..f760fa3a4
--- /dev/null
+++ b/browser/locales/searchplugins/eudict.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>EUdict Eng->Cro</ShortName>
+<Description>EUdict - englesko-hrvatski rječnik</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/gif;base64,R0lGODlhEAAQAKIAAH6CuJufvmJwrtze4vb4%2BLwxMcZmZtG7uyH5BAAAAAAALAAAAAAQABAAAANvSHTMs0dBZWoNpMpiyLhBMBycMhTYUT0KiS3oYnVZXEczQBXRnRkBhoLDIp4MrGHhQUI2LQ8DRxpTzZhUYNIjOAwALJbIAyiPMAOBYBBQfwPmUDrIFoQABwBG35bACXAAdmlseBFmeXBeOml2C2sJADs%3D</Image>
+<Url type="text/html" method="GET" template="http://www.eudict.com/indexHr.php" resultdomain="eudict.com">
+ <Param name="lang" value="engcro"/>
+ <Param name="word" value="{searchTerms}"/>
+ <Param name="client" value="firefox"/>
+</Url>
+<SearchForm>http://www.eudict.com/indexHr.php</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/faclair-beag.xml b/browser/locales/searchplugins/faclair-beag.xml
new file mode 100644
index 000000000..d13b97535
--- /dev/null
+++ b/browser/locales/searchplugins/faclair-beag.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Am Faclair Beag</ShortName>
+<Description>Lorg Am Faclair Beag</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,%2F9j%2F4AAQSkZJRgABAQAAAQABAAD%2F%2FgA8Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2NjIpLCBxdWFsaXR5ID0gMTAwCv%2FbAEMAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf%2FbAEMBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf%2FAABEIABAAEAMBIgACEQEDEQH%2FxAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv%2FxAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5%2Bjp6vHy8%2FT19vf4%2Bfr%2FxAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv%2FxAC1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4%2BTl5ufo6ery8%2FT19vf4%2Bfr%2F2gAMAwEAAhEDEQA%2FAPyc8bfsbaf8dPAPxs%2BKP%2FBQHwJ%2B3T8ff%2BCl37W%2F7Knhz9t34WftIaHpy6X8BfD%2FAIi8e3Fpp3wz%2BEPi67m0Kx%2BHng7wd4V8BSaB4o%2BJnijxVrHgX4U%2FBf4W%2BG774W%2BBdN%2BH8fw90CbxX8TfAH4QfFX9ivwtqH7ZH7G3hT9pv9o39o%2F4A3Op6y%2F7W37PvgTx1H%2BxN%2By1d2NnqeleMrpPiHaeGrg%2FtM6pp%2BgSa3ovjCHWF8Lfs2SaHfa1Y%2BJtP%2FaA%2BF%2BqXsOrdt%2B0l%2B0L8ff2SPAOmfsW%2Ft8%2FF%2F8AaZ%2FaN%2BOnwITQvD7fsJfEnxb4u8N%2FsifAUaLZaRqnw%2FtfjFqOna7Za7%2B1TJY%2BG20DXvAvh%2FwLeaX8GofDt54a1rwx8ZPiN4N1Cbw%2Fefavgz9sy4%2BLPwo%2BD%2FwU%2FwCCe%2F7SP7Yv7Tf7cP7aX7IOsfsXePP2BU8L6n4K%2FZ3%2BEHxK%2BIVx%2Fa3xW%2BMPgrT0v%2FC%2F7Pvw8%2BH%2FAIJ%2BHh8VeEPh54d%2BGPgnw74N%2BG%2FgPSrr4yfE3x%2F4HtdB8Y2%2FiYA%2F%2F9k%3D</Image>
+<Url type="text/html" method="GET" template="http://www.faclair.com/" resultdomain="faclair.com">
+<Param name="txtSearch" value="{searchTerms}"/>
+</Url>
+</SearchPlugin> \ No newline at end of file
diff --git a/browser/locales/searchplugins/findbook-zh-TW.xml b/browser/locales/searchplugins/findbook-zh-TW.xml
new file mode 100644
index 000000000..17821a445
--- /dev/null
+++ b/browser/locales/searchplugins/findbook-zh-TW.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Findbook</ShortName>
+<Description>Findbook 書籍搜尋</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAArHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/7a2tv8AAAAArHEA/LZ3AP/LhgT/1YwF/9SMBP/UjAT/1IwE/9SMBP/UjAT/1IwE/9SMBf/UjAX/1IwF/6xxAP+2trb/ypMqDqxxAP+2eAD/y4YC/9OMBf/QgQD/9uvV////////////uXAA/9WMBP/UjAX/1IwF/9SMBf+scQD/tra2/61zAuKscQD/tngA/8uGAv/UjAX/z4EA//Xmyv///////////7pwAP/VjAT/1IwF/9SMBf/UjAX/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgT/1IwF/8+BAP/15sr///////////+6cAD/1YwE/9SMBf/UjAX/1IwF/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YC/9OMBf/QgQD/9ebK////////////tGQA/9GCAP/PgAD/0YQA/9SMBf+scQD/tra2/6xxAP+scQD/tngA/8uGBP/UjAX/z4EA//Xmyv/////////////////////////////////QggD/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgL/1IwF/9CBAP/15sr///////////+5hCL/yJg5/8iYO//BjjD/04kA/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YE/9SMBf/OgQD/9ebK////////////um0A/9aKAP/UiQD/1YoA/9SLA/+scQD/tra2/6xxAP+scQD/tngA/8uGBP/UjAX/0IEA//Xmyf///////////7pwAP/VjAT/1IwF/9SLA//Tiwb/rHEA/7a2tv+scQD/rHEA/7Z4AP/LhgL/1IwE/859AP/15sr///////////+1ZwD/04YA/9GEAP/RgAD/zKVa/6xxAP+2trb/rHEA/6xxAP+2eAD/y4YB/9WMBf////////////////////////////////////////////////+scQD/tra2/6xxAP+scQD/tXUA/8uDAP/SkRj/yJc5/8iXOf/Ilzn/yJc5/8iWN//Ilzn/x5c5/8iXOf/Ilzn/rHEA/7a2tv+scQD/rHEA/7B2CP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/61xAP+2trb/rHEA/6xyAfvp+///6PD//+z2///w+///9f////v//////////////////////////////////////////////6xxAP8AAAAArncM8axxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/6xxAP+scQD/rHEA/691B/mtcwPugAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA%3D%3D</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://findbook.tw/search/suggest?q={searchTerms}&amp;utm_source=ff-bundled&amp;utm_medium=mozsearch&amp;utm_campaign=search" />
+<Url type="text/html" method="GET" template="http://findbook.tw/search" resultdomain="findbook.tw">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="utm_source" value="ff-bundled"/>
+ <Param name="utm_medium" value="mozsearch"/>
+ <Param name="utm_campaign" value="search"/>
+</Url>
+<SearchForm>http://findbook.tw/search</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/flip.xml b/browser/locales/searchplugins/flip.xml
new file mode 100644
index 000000000..b8c0cdb0c
--- /dev/null
+++ b/browser/locales/searchplugins/flip.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>flip.kz</ShortName>
+<LongName>flip.kz көмегімен іздестіру</LongName>
+<Description>Қазақстандық интернет-дүкенде іздеу</Description>
+<InputEncoding>utf-8</InputEncoding>
+<Image height="16" width="16" type="image/png">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVHjaYkxY%2BpiNgYGhDogTgFiagTjwFIgXAHETC5BoBOIKBtIAyKJqIGZmAhLJDOSDZJALRNFFO30lGMR4WbDqKNv0guH1lz8wrigTugIHVW6cmrEBDJVcrKhmrr7wEYX%2F7dc%2F%2FAagg23XPpPmAnQwP0oGzr724idD977XqAaAAuTS0x8M7779BQvICrAyhBrwYzXs5ec%2FDKvPQ7wkxMXMoCfNwcBy4sE3hh%2B%2F%2F%2BN0Qd22l3D2h%2B9%2F4WyQhSC9LPg0g0Df%2Fjc45UB6mRgoBBQbgBEL5598R%2FE3IcDIXXLl%2F4B6ASDAAE8uSkx9GD%2FfAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" template="http://www.flip.kz/ajax/search_keyword.php">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="type" value="os"/>
+</Url>
+<Url type="text/html" method="get" template="http://www.flip.kz/search" resultdomain="flip.kz">
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://flip.kz/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/freelang.xml b/browser/locales/searchplugins/freelang.xml
new file mode 100644
index 000000000..017dfeb0b
--- /dev/null
+++ b/browser/locales/searchplugins/freelang.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Freelang (br)</ShortName>
+<Description>Geriadur Freelang</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAD5+vYAQxGlAHp1DQDMzJoAnp4+AIWEDADc3b0AgH8CAJmZNADS06gAe3oAAJ6ePwCCgQUAfn0AALGxZAB/fQAAgIAAAIGAAABcP14AtrZvAIKDAACGhQsAuLhyAIqKFgDS0acAx8eSAP7+/gB7dg0AeHETALOzaACtrFsAc2ofAMLCiABPJ4MAi4oXAHh4AAB5eAAA+vv3AIGAAgB6eAAAubp2AFw/YABMIYwA9PTqAHx7AACNjBoAmJgyAH9+AABgRlUAgIEAAOjo0wCBgQAApKRKAIGAAwD7+/gAurp3AIKEAAC3t28Ag4QAAPT06wCXljAAzc2cAEUWnAB0ax4AqalVALu8egCjokgAjo4eAKurWACIhxEAhYQJALCwYwDq6tcA4+PKAJ+fQQCgn0EA+fn3AG5gLgB/fgIAyMiTAHp5AAD///8AiosPALe3cQCQkCIAfXwAAEMRpAB/fwAAgH8AAIF/AACBggAAi4sYAIKCAACPjiAA4eLGAJ2ePQChoUUA0NGkAHZtGwDJypcAh4YOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAERERERERERERLwc8LBEREREREREREREREFhYWzdVERFYJg0RERERERERES8ZNFgRWEVHClgQERERERFYYExQEVkRLWM1LxFYERFYL1NRVBERWBFYYU9DJCwsJEIaURURERFZWCdbCQAySSVRURMvERFZVUBeK0gDKEQWHmQvEREREUtRBgguBB0YXw0RWREREVg5OyNYIFFdCgsOVRERERERWzIsEQU2PS9YRkpXWBEREQ9BIhERDBcRERFSWBERERERVQtcFBERWBEzEhwRERERERFYMCpaEVkRTjEQWRERERERET8+Hyk4TT8hXBEREREREVgROgJWGwFiOlgREQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</Image>
+<Url type="text/html" method="POST" template="http://www.freelang.com/enligne/breton.php?lg=fr" resultdomain="freelang.com">
+ <Param name="dico" value="fr_bre_fra"/>
+ <Param name="mot1" value="{searchTerms}"/>
+ <Param name="mot2" value=""/>
+ <Param name="entier" value="on"/>
+</Url>
+
+<SearchForm>http://www.freelang.com/enligne/breton.php</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/google-2018.xml b/browser/locales/searchplugins/google-2018.xml
new file mode 100644
index 000000000..27227fa3b
--- /dev/null
+++ b/browser/locales/searchplugins/google-2018.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Google</ShortName>
+<Description>Google Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///zD9/f2W/f392P39/fn9/f35/f391/39/ZT+/v4uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Cf39/Zn///////////////////////////////////////////39/ZX///8IAAAAAAAAAAAAAAAA/v7+Cf39/cH/////+v35/7TZp/92ul3/WKs6/1iqOv9yuFn/rNWd//j79v///////f39v////wgAAAAAAAAAAP39/Zn/////7PXp/3G3WP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP+Or1j//vDo///////9/f2VAAAAAP///zD/////+vz5/3G3V/9TqDT/WKo6/6LQkf/U6cz/1urO/6rUm/+Zo0r/8IZB//adZ////v7///////7+/i79/f2Y/////4nWzf9Lqkj/Vqo4/9Xqzv///////////////////////ebY//SHRv/0hUL//NjD///////9/f2U/f392v////8sxPH/Ebzt/43RsP/////////////////////////////////4roL/9IVC//i1jf///////f391/39/fr/////Cr37/wW8+/+16/7/////////////////9IVC//SFQv/0hUL/9IVC//SFQv/3pnX///////39/fn9/f36/////wu++/8FvPv/tuz+//////////////////SFQv/0hUL/9IVC//SFQv/0hUL/96p7///////9/f35/f392/////81yfz/CrL5/2uk9v///////////////////////////////////////////////////////f392P39/Zn/////ks/7/zdS7P84Rur/0NT6///////////////////////9/f////////////////////////39/Zb+/v4y//////n5/v9WYu3/NUPq/ztJ6/+VnPT/z9L6/9HU+v+WnfT/Ul7t/+Hj/P////////////////////8wAAAAAP39/Z3/////6Or9/1hj7v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9sdvD////////////9/f2YAAAAAAAAAAD///8K/f39w//////5+f7/paz2/11p7v88Suv/Okfq/1pm7v+iqfX/+fn+///////9/f3B/v7+CQAAAAAAAAAAAAAAAP///wr9/f2d///////////////////////////////////////////9/f2Z/v7+CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f2Z/f392/39/fr9/f36/f392v39/Zj///8wAAAAAAAAAAAAAAAAAAAAAPAPAADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/g3+/v5X/f39mf39/cj9/f3q/f39+f39/fn9/f3q/f39yP39/Zn+/v5W////DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f2c/f399f/////////////////////////////////////////////////////9/f31/f39mv7+/iMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/gn9/f2K/f39+////////////////////////////////////////////////////////////////////////////f39+v39/Yf///8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4k/f390v////////////////////////////////////////////////////////////////////////////////////////////////39/dD///8iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////MP39/er//////////////////////////+r05v+v16H/gsBs/2WxSf9Wqjj/Vqk3/2OwRv99vWX/pdKV/97u2P////////////////////////////39/ej+/v4vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f3q/////////////////////+v15/+Pxnv/VKk2/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/36+Z//d7tf///////////////////////39/ej///8iAAAAAAAAAAAAAAAAAAAAAAAAAAD///8K/f390//////////////////////E4bn/XKw+/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1apN/+x0pv///////////////////////39/dD///8IAAAAAAAAAAAAAAAAAAAAAP39/Yv/////////////////////sdij/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/YKU1/8qOPv/5wZ////////////////////////39/YcAAAAAAAAAAAAAAAD+/v4l/f39+////////////////8Lgt/9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9utlT/n86N/7faqv+426v/pdKV/3u8ZP9UqDX/U6g0/3egN//jiUH/9IVC//SFQv/82MP//////////////////f39+v7+/iMAAAAAAAAAAP39/Z3////////////////q9Ob/W6w+/1OoNP9TqDT/U6g0/1OoNP9nskz/zOXC/////////////////////////////////+Dv2v+osWP/8YVC//SFQv/0hUL/9IVC//WQVP/++fb//////////////////f39mgAAAAD+/v4O/f399v///////////////4LHj/9TqDT/U6g0/1OoNP9TqDT/dblc//L58P/////////////////////////////////////////////8+v/3p3f/9IVC//SFQv/0hUL/9IVC//rIqf/////////////////9/f31////DP7+/ln////////////////f9v7/Cbz2/zOwhv9TqDT/U6g0/2KwRv/v9+z///////////////////////////////////////////////////////738//1kFT/9IVC//SFQv/0hUL/9plg///////////////////////+/v5W/f39nP///////////////4jf/f8FvPv/Bbz7/yG1s/9QqDz/vN2w//////////////////////////////////////////////////////////////////rHqP/0hUL/9IVC//SFQv/0hUL//vDn//////////////////39/Zn9/f3L////////////////R878/wW8+/8FvPv/Bbz7/y7C5P/7/fr//////////////////////////////////////////////////////////////////ere//SFQv/0hUL/9IVC//SFQv/718H//////////////////f39yP39/ez///////////////8cwvv/Bbz7/wW8+/8FvPv/WNL8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//rIqv/////////////////9/f3q/f39+v///////////////we9+/8FvPv/Bbz7/wW8+/993P3///////////////////////////////////////SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/+cGf//////////////////39/fn9/f36////////////////B737/wW8+/8FvPv/Bbz7/33c/f//////////////////////////////////////9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/6xaX//////////////////f39+f39/e3///////////////8cwvv/Bbz7/wW8+/8FvPv/WdP8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//vVv//////////////////9/f3q/f39y////////////////0bN/P8FvPv/Bbz7/wW8+/8hrvn/+/v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////39/cj9/f2c////////////////ht/9/wW8+/8FvPv/FZP1/zRJ6/+zuPf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////f39mf7+/lr////////////////d9v7/B7n7/yB38f81Q+r/NUPq/0hV7P/u8P3////////////////////////////////////////////////////////////////////////////////////////////////////////////+/v5X////D/39/ff///////////////9tkPT/NUPq/zVD6v81Q+r/NUPq/2Fs7//y8v7////////////////////////////////////////////09f7//////////////////////////////////////////////////f399f7+/g0AAAAA/f39n////////////////+Tm/P89Suv/NUPq/zVD6v81Q+r/NUPq/1Bc7f/IzPn/////////////////////////////////x8v5/0xY7P+MlPP////////////////////////////////////////////9/f2cAAAAAAAAAAD+/v4n/f39/P///////////////7W69/81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9ZZe7/k5v0/6609/+vtff/lJv0/1pm7v81Q+r/NUPq/zVD6v+GjvL//v7//////////////////////////////f39+/7+/iQAAAAAAAAAAAAAAAD9/f2N/////////////////////6Cn9f81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v+BivL////////////////////////////9/f2KAAAAAAAAAAAAAAAAAAAAAP7+/gv9/f3V/////////////////////7W69/8+S+v/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/P0zr/7q/+P///////////////////////f390v7+/gkAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3r/////////////////////+Xn/P94gfH/NkTq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NkTq/3Z/8f/l5/z///////////////////////39/er+/v4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f3r///////////////////////////k5vz/nqX1/2p08P9IVez/OEbq/zdF6v9GU+z/aHLv/5qh9f/i5Pz////////////////////////////9/f3q////MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3V/////////////////////////////////////////////////////////////////////////////////////////////////f390v7+/iQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wr9/f2N/f39/P///////////////////////////////////////////////////////////////////////////f39+/39/Yv+/v4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4n/f39n/39/ff//////////////////////////////////////////////////////f399v39/Z3+/v4lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Dv7+/lr9/f2c/f39y/39/e39/f36/f39+v39/ez9/f3L/f39nP7+/ln+/v4OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AAAAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8AAP//wAP/</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
+<Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="ie" value="utf-8"/>
+ <Param name="oe" value="utf-8"/>
+ <MozParam name="client" condition="purpose" purpose="keyword" value="firefox-b-1-ab"/>
+ <MozParam name="client" condition="purpose" purpose="searchbar" value="firefox-b-1"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/google-nocodes.xml b/browser/locales/searchplugins/google-nocodes.xml
new file mode 100644
index 000000000..f7583b99d
--- /dev/null
+++ b/browser/locales/searchplugins/google-nocodes.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Google</ShortName>
+<Description>Google Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///zD9/f2W/f392P39/fn9/f35/f391/39/ZT+/v4uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Cf39/Zn///////////////////////////////////////////39/ZX///8IAAAAAAAAAAAAAAAA/v7+Cf39/cH/////+v35/7TZp/92ul3/WKs6/1iqOv9yuFn/rNWd//j79v///////f39v////wgAAAAAAAAAAP39/Zn/////7PXp/3G3WP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP+Or1j//vDo///////9/f2VAAAAAP///zD/////+vz5/3G3V/9TqDT/WKo6/6LQkf/U6cz/1urO/6rUm/+Zo0r/8IZB//adZ////v7///////7+/i79/f2Y/////4nWzf9Lqkj/Vqo4/9Xqzv///////////////////////ebY//SHRv/0hUL//NjD///////9/f2U/f392v////8sxPH/Ebzt/43RsP/////////////////////////////////4roL/9IVC//i1jf///////f391/39/fr/////Cr37/wW8+/+16/7/////////////////9IVC//SFQv/0hUL/9IVC//SFQv/3pnX///////39/fn9/f36/////wu++/8FvPv/tuz+//////////////////SFQv/0hUL/9IVC//SFQv/0hUL/96p7///////9/f35/f392/////81yfz/CrL5/2uk9v///////////////////////////////////////////////////////f392P39/Zn/////ks/7/zdS7P84Rur/0NT6///////////////////////9/f////////////////////////39/Zb+/v4y//////n5/v9WYu3/NUPq/ztJ6/+VnPT/z9L6/9HU+v+WnfT/Ul7t/+Hj/P////////////////////8wAAAAAP39/Z3/////6Or9/1hj7v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9sdvD////////////9/f2YAAAAAAAAAAD///8K/f39w//////5+f7/paz2/11p7v88Suv/Okfq/1pm7v+iqfX/+fn+///////9/f3B/v7+CQAAAAAAAAAAAAAAAP///wr9/f2d///////////////////////////////////////////9/f2Z/v7+CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f2Z/f392/39/fr9/f36/f392v39/Zj///8wAAAAAAAAAAAAAAAAAAAAAPAPAADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/g3+/v5X/f39mf39/cj9/f3q/f39+f39/fn9/f3q/f39yP39/Zn+/v5W////DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f2c/f399f/////////////////////////////////////////////////////9/f31/f39mv7+/iMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/gn9/f2K/f39+////////////////////////////////////////////////////////////////////////////f39+v39/Yf///8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4k/f390v////////////////////////////////////////////////////////////////////////////////////////////////39/dD///8iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////MP39/er//////////////////////////+r05v+v16H/gsBs/2WxSf9Wqjj/Vqk3/2OwRv99vWX/pdKV/97u2P////////////////////////////39/ej+/v4vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f3q/////////////////////+v15/+Pxnv/VKk2/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/36+Z//d7tf///////////////////////39/ej///8iAAAAAAAAAAAAAAAAAAAAAAAAAAD///8K/f390//////////////////////E4bn/XKw+/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1apN/+x0pv///////////////////////39/dD///8IAAAAAAAAAAAAAAAAAAAAAP39/Yv/////////////////////sdij/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/YKU1/8qOPv/5wZ////////////////////////39/YcAAAAAAAAAAAAAAAD+/v4l/f39+////////////////8Lgt/9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9utlT/n86N/7faqv+426v/pdKV/3u8ZP9UqDX/U6g0/3egN//jiUH/9IVC//SFQv/82MP//////////////////f39+v7+/iMAAAAAAAAAAP39/Z3////////////////q9Ob/W6w+/1OoNP9TqDT/U6g0/1OoNP9nskz/zOXC/////////////////////////////////+Dv2v+osWP/8YVC//SFQv/0hUL/9IVC//WQVP/++fb//////////////////f39mgAAAAD+/v4O/f399v///////////////4LHj/9TqDT/U6g0/1OoNP9TqDT/dblc//L58P/////////////////////////////////////////////8+v/3p3f/9IVC//SFQv/0hUL/9IVC//rIqf/////////////////9/f31////DP7+/ln////////////////f9v7/Cbz2/zOwhv9TqDT/U6g0/2KwRv/v9+z///////////////////////////////////////////////////////738//1kFT/9IVC//SFQv/0hUL/9plg///////////////////////+/v5W/f39nP///////////////4jf/f8FvPv/Bbz7/yG1s/9QqDz/vN2w//////////////////////////////////////////////////////////////////rHqP/0hUL/9IVC//SFQv/0hUL//vDn//////////////////39/Zn9/f3L////////////////R878/wW8+/8FvPv/Bbz7/y7C5P/7/fr//////////////////////////////////////////////////////////////////ere//SFQv/0hUL/9IVC//SFQv/718H//////////////////f39yP39/ez///////////////8cwvv/Bbz7/wW8+/8FvPv/WNL8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//rIqv/////////////////9/f3q/f39+v///////////////we9+/8FvPv/Bbz7/wW8+/993P3///////////////////////////////////////SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/+cGf//////////////////39/fn9/f36////////////////B737/wW8+/8FvPv/Bbz7/33c/f//////////////////////////////////////9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/6xaX//////////////////f39+f39/e3///////////////8cwvv/Bbz7/wW8+/8FvPv/WdP8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//vVv//////////////////9/f3q/f39y////////////////0bN/P8FvPv/Bbz7/wW8+/8hrvn/+/v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////39/cj9/f2c////////////////ht/9/wW8+/8FvPv/FZP1/zRJ6/+zuPf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////f39mf7+/lr////////////////d9v7/B7n7/yB38f81Q+r/NUPq/0hV7P/u8P3////////////////////////////////////////////////////////////////////////////////////////////////////////////+/v5X////D/39/ff///////////////9tkPT/NUPq/zVD6v81Q+r/NUPq/2Fs7//y8v7////////////////////////////////////////////09f7//////////////////////////////////////////////////f399f7+/g0AAAAA/f39n////////////////+Tm/P89Suv/NUPq/zVD6v81Q+r/NUPq/1Bc7f/IzPn/////////////////////////////////x8v5/0xY7P+MlPP////////////////////////////////////////////9/f2cAAAAAAAAAAD+/v4n/f39/P///////////////7W69/81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9ZZe7/k5v0/6609/+vtff/lJv0/1pm7v81Q+r/NUPq/zVD6v+GjvL//v7//////////////////////////////f39+/7+/iQAAAAAAAAAAAAAAAD9/f2N/////////////////////6Cn9f81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v+BivL////////////////////////////9/f2KAAAAAAAAAAAAAAAAAAAAAP7+/gv9/f3V/////////////////////7W69/8+S+v/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/P0zr/7q/+P///////////////////////f390v7+/gkAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3r/////////////////////+Xn/P94gfH/NkTq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NkTq/3Z/8f/l5/z///////////////////////39/er+/v4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f3r///////////////////////////k5vz/nqX1/2p08P9IVez/OEbq/zdF6v9GU+z/aHLv/5qh9f/i5Pz////////////////////////////9/f3q////MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3V/////////////////////////////////////////////////////////////////////////////////////////////////f390v7+/iQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wr9/f2N/f39/P///////////////////////////////////////////////////////////////////////////f39+/39/Yv+/v4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4n/f39n/39/ff//////////////////////////////////////////////////////f399v39/Z3+/v4lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Dv7+/lr9/f2c/f39y/39/e39/f36/f39+v39/ez9/f3L/f39nP7+/ln+/v4OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AAAAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8AAP//wAP/</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
+<Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="ie" value="utf-8"/>
+ <Param name="oe" value="utf-8"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/google.xml b/browser/locales/searchplugins/google.xml
new file mode 100644
index 000000000..0642d56f9
--- /dev/null
+++ b/browser/locales/searchplugins/google.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Google</ShortName>
+<Description>Google Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///zD9/f2W/f392P39/fn9/f35/f391/39/ZT+/v4uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Cf39/Zn///////////////////////////////////////////39/ZX///8IAAAAAAAAAAAAAAAA/v7+Cf39/cH/////+v35/7TZp/92ul3/WKs6/1iqOv9yuFn/rNWd//j79v///////f39v////wgAAAAAAAAAAP39/Zn/////7PXp/3G3WP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP+Or1j//vDo///////9/f2VAAAAAP///zD/////+vz5/3G3V/9TqDT/WKo6/6LQkf/U6cz/1urO/6rUm/+Zo0r/8IZB//adZ////v7///////7+/i79/f2Y/////4nWzf9Lqkj/Vqo4/9Xqzv///////////////////////ebY//SHRv/0hUL//NjD///////9/f2U/f392v////8sxPH/Ebzt/43RsP/////////////////////////////////4roL/9IVC//i1jf///////f391/39/fr/////Cr37/wW8+/+16/7/////////////////9IVC//SFQv/0hUL/9IVC//SFQv/3pnX///////39/fn9/f36/////wu++/8FvPv/tuz+//////////////////SFQv/0hUL/9IVC//SFQv/0hUL/96p7///////9/f35/f392/////81yfz/CrL5/2uk9v///////////////////////////////////////////////////////f392P39/Zn/////ks/7/zdS7P84Rur/0NT6///////////////////////9/f////////////////////////39/Zb+/v4y//////n5/v9WYu3/NUPq/ztJ6/+VnPT/z9L6/9HU+v+WnfT/Ul7t/+Hj/P////////////////////8wAAAAAP39/Z3/////6Or9/1hj7v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9sdvD////////////9/f2YAAAAAAAAAAD///8K/f39w//////5+f7/paz2/11p7v88Suv/Okfq/1pm7v+iqfX/+fn+///////9/f3B/v7+CQAAAAAAAAAAAAAAAP///wr9/f2d///////////////////////////////////////////9/f2Z/v7+CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f2Z/f392/39/fr9/f36/f392v39/Zj///8wAAAAAAAAAAAAAAAAAAAAAPAPAADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/g3+/v5X/f39mf39/cj9/f3q/f39+f39/fn9/f3q/f39yP39/Zn+/v5W////DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f2c/f399f/////////////////////////////////////////////////////9/f31/f39mv7+/iMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/gn9/f2K/f39+////////////////////////////////////////////////////////////////////////////f39+v39/Yf///8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4k/f390v////////////////////////////////////////////////////////////////////////////////////////////////39/dD///8iAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////MP39/er//////////////////////////+r05v+v16H/gsBs/2WxSf9Wqjj/Vqk3/2OwRv99vWX/pdKV/97u2P////////////////////////////39/ej+/v4vAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/iT9/f3q/////////////////////+v15/+Pxnv/VKk2/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/36+Z//d7tf///////////////////////39/ej///8iAAAAAAAAAAAAAAAAAAAAAAAAAAD///8K/f390//////////////////////E4bn/XKw+/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1apN/+x0pv///////////////////////39/dD///8IAAAAAAAAAAAAAAAAAAAAAP39/Yv/////////////////////sdij/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9TqDT/YKU1/8qOPv/5wZ////////////////////////39/YcAAAAAAAAAAAAAAAD+/v4l/f39+////////////////8Lgt/9TqDT/U6g0/1OoNP9TqDT/U6g0/1OoNP9utlT/n86N/7faqv+426v/pdKV/3u8ZP9UqDX/U6g0/3egN//jiUH/9IVC//SFQv/82MP//////////////////f39+v7+/iMAAAAAAAAAAP39/Z3////////////////q9Ob/W6w+/1OoNP9TqDT/U6g0/1OoNP9nskz/zOXC/////////////////////////////////+Dv2v+osWP/8YVC//SFQv/0hUL/9IVC//WQVP/++fb//////////////////f39mgAAAAD+/v4O/f399v///////////////4LHj/9TqDT/U6g0/1OoNP9TqDT/dblc//L58P/////////////////////////////////////////////8+v/3p3f/9IVC//SFQv/0hUL/9IVC//rIqf/////////////////9/f31////DP7+/ln////////////////f9v7/Cbz2/zOwhv9TqDT/U6g0/2KwRv/v9+z///////////////////////////////////////////////////////738//1kFT/9IVC//SFQv/0hUL/9plg///////////////////////+/v5W/f39nP///////////////4jf/f8FvPv/Bbz7/yG1s/9QqDz/vN2w//////////////////////////////////////////////////////////////////rHqP/0hUL/9IVC//SFQv/0hUL//vDn//////////////////39/Zn9/f3L////////////////R878/wW8+/8FvPv/Bbz7/y7C5P/7/fr//////////////////////////////////////////////////////////////////ere//SFQv/0hUL/9IVC//SFQv/718H//////////////////f39yP39/ez///////////////8cwvv/Bbz7/wW8+/8FvPv/WNL8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//rIqv/////////////////9/f3q/f39+v///////////////we9+/8FvPv/Bbz7/wW8+/993P3///////////////////////////////////////SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/+cGf//////////////////39/fn9/f36////////////////B737/wW8+/8FvPv/Bbz7/33c/f//////////////////////////////////////9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/6xaX//////////////////f39+f39/e3///////////////8cwvv/Bbz7/wW8+/8FvPv/WdP8///////////////////////////////////////0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//vVv//////////////////9/f3q/f39y////////////////0bN/P8FvPv/Bbz7/wW8+/8hrvn/+/v///////////////////////////////////////////////////////////////////////////////////////////////////////////////////39/cj9/f2c////////////////ht/9/wW8+/8FvPv/FZP1/zRJ6/+zuPf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////f39mf7+/lr////////////////d9v7/B7n7/yB38f81Q+r/NUPq/0hV7P/u8P3////////////////////////////////////////////////////////////////////////////////////////////////////////////+/v5X////D/39/ff///////////////9tkPT/NUPq/zVD6v81Q+r/NUPq/2Fs7//y8v7////////////////////////////////////////////09f7//////////////////////////////////////////////////f399f7+/g0AAAAA/f39n////////////////+Tm/P89Suv/NUPq/zVD6v81Q+r/NUPq/1Bc7f/IzPn/////////////////////////////////x8v5/0xY7P+MlPP////////////////////////////////////////////9/f2cAAAAAAAAAAD+/v4n/f39/P///////////////7W69/81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v9ZZe7/k5v0/6609/+vtff/lJv0/1pm7v81Q+r/NUPq/zVD6v+GjvL//v7//////////////////////////////f39+/7+/iQAAAAAAAAAAAAAAAD9/f2N/////////////////////6Cn9f81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v+BivL////////////////////////////9/f2KAAAAAAAAAAAAAAAAAAAAAP7+/gv9/f3V/////////////////////7W69/8+S+v/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/P0zr/7q/+P///////////////////////f390v7+/gkAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3r/////////////////////+Xn/P94gfH/NkTq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NUPq/zVD6v81Q+r/NkTq/3Z/8f/l5/z///////////////////////39/er+/v4kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/jL9/f3r///////////////////////////k5vz/nqX1/2p08P9IVez/OEbq/zdF6v9GU+z/aHLv/5qh9f/i5Pz////////////////////////////9/f3q////MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7+/ib9/f3V/////////////////////////////////////////////////////////////////////////////////////////////////f390v7+/iQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wr9/f2N/f39/P///////////////////////////////////////////////////////////////////////////f39+/39/Yv+/v4JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v4n/f39n/39/ff//////////////////////////////////////////////////////f399v39/Z3+/v4lAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v7+Dv7+/lr9/f2c/f39y/39/e39/f36/f39+v39/ez9/f3L/f39nP7+/ln+/v4OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AAAAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8AAP//wAP/</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
+<Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="ie" value="utf-8"/>
+ <Param name="oe" value="utf-8"/>
+ <MozParam name="client" condition="purpose" purpose="keyword" value="firefox-b-ab"/>
+ <MozParam name="client" condition="purpose" purpose="searchbar" value="firefox-b"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/gujaratilexicon.xml b/browser/locales/searchplugins/gujaratilexicon.xml
new file mode 100644
index 000000000..cda0d9268
--- /dev/null
+++ b/browser/locales/searchplugins/gujaratilexicon.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>GujaratiLexicon.com</ShortName>
+<Description>English - Gujarati dictionary on www.gujaratilexicon.com</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAABMLAAATCwAAAAAAAAAAAAA4ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2FyI4ov%2F%2FOKL%2Fqjii%2FxEAAAAAAAAAADii%2FxE4ov93OKL%2FiDii%2F0QAAAAAAAAAAAAAAAAAAAAAOKL%2F%2Fzii%2F%2F8AAAAAOKL%2FiDii%2F%2F84ov%2F%2FOKL%2FmTii%2F6o4ov%2FdOKL%2Fqjii%2F%2F84ov%2FMAAAAAAAAAAAAAAAAAAAAADii%2F%2F84ov%2F%2FAAAAAAAAAAA4ov93OKL%2FzDii%2F%2F84ov%2BqOKL%2FEQAAAAA4ov%2BZOKL%2FzAAAAAA4ov9EAAAAAAAAAAA4ov%2F%2FOKL%2F%2FwAAAAAAAAAAAAAAADii%2F7s4ov%2FuOKL%2F%2Fzii%2F8w4ov9mOKL%2F7jii%2F3c4ov%2FuOKL%2FzAAAAAAAAAAAOKL%2F%2Fzii%2F%2F8AAAAAAAAAADii%2F1U4ov%2F%2FOKL%2FZjii%2F1U4ov%2FMOKL%2F%2Fzii%2F%2B44ov8ROKL%2F%2Fzii%2F8wAAAAAAAAAADii%2F%2F84ov%2F%2FAAAAAAAAAAAAAAAAOKL%2FmTii%2F%2F84ov%2B7OKL%2F%2Fzii%2F8w4ov8RAAAAADii%2F%2F84ov%2FMAAAAAAAAAAA4ov%2F%2FOKL%2F%2FwAAAAAAAAAAAAAAADii%2F7s4ov%2FdAAAAADii%2F3c4ov%2F%2FOKL%2FiAAAAAA4ov%2F%2FOKL%2FzAAAAAAAAAAAOKL%2F%2Fzii%2F%2F8AAAAAAAAAAAAAAAA4ov%2BqOKL%2F%2Fzii%2F7s4ov%2BIOKL%2F%2Fzii%2F%2F8AAAAAOKL%2F%2Fzii%2F8wAAAAAAAAAADii%2F%2F84ov%2F%2FAAAAAAAAAAAAAAAAAAAAADii%2F3c4ov%2BqOKL%2F%2Fzii%2F5k4ov93AAAAADii%2F%2F84ov%2FMAAAAAAAAAAA4ov%2F%2FOKL%2F%2FwAAAAAAAAAAOKL%2Fdzii%2F8w4ov%2FMOKL%2F3Tii%2F%2F84ov%2FdOKL%2FzDii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F5kAAAAAOKL%2F%2Fzii%2F%2F8AAAAAAAAAADii%2FyI4ov9VOKL%2FVTii%2F1U4ov9VOKL%2FVTii%2F1U4ov93OKL%2F%2Fzii%2F904ov9EAAAAADii%2F%2F84ov%2F%2FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOKL%2FETii%2F%2F84ov%2FMAAAAAAAAAAA4ov%2F%2FOKL%2F%2FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADii%2F3c4ov%2F%2FOKL%2FiAAAAAAAAAAAOKL%2F%2Fzii%2F%2F8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADii%2FxE4ov%2FuOKL%2F%2Fzii%2FyIAAAAAAAAAADii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FOKL%2F%2Fzii%2F%2F84ov%2F%2FAAD%2F%2F0%2B%2B%2F%2F9AHv%2F%2FcZ7%2F%2F3Cm%2F%2F92Jv%2F%2FcGb%2F%2F3Mm%2F%2F9wJv%2F%2FfGb%2F%2F3AC%2F%2F9%2F5v%2F%2Ff%2Bb%2F%2F3%2Fm%2F%2F9%2Fzv%2F%2FAAD%2F%2Fw%3D%3D</Image>
+<Url type="text/html" method="GET" template="http://www.gujaratilexicon.com/index.php" resultdomain="gujaratilexicon.com">
+ <Param name="action" value="dictionary"/>
+ <Param name="mode" value="search"/>
+ <Param name="type" value="1"/>
+ <Param name="dictype" value="EG"/>
+ <Param name="sitem" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.gujaratilexicon.com/index.php?action=dictionary&amp;chngdictype=EG</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/gulesider-NO.xml b/browser/locales/searchplugins/gulesider-NO.xml
new file mode 100644
index 000000000..87d7814b4
--- /dev/null
+++ b/browser/locales/searchplugins/gulesider-NO.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Gule sider</ShortName>
+<Description>Gule sider person og firmasøk</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAABI1f7/SNX+/0jV/v9I1f7/SNX+/0jV/v9I1f7/SNX+/0jV/v9K3P//OLDU/wAEBf8AAAD/AAAA/wAAAP8EDhL/StX+/0rV/v9K1f7/StX+/0rV/v9K1f7/StX+/0rV/v9K1f7/Td3//zirzv8AAgP/AAAA/wAAAP8AAAD/AwkM/0zW/f9M1v3/TNb9/0zW/f9M1v3/TNb9/0zW/f9M1v3/TNb9/0/e//85rM7/AAID/wAAAP8AAAD/AAAA/wMKDf9P1/3/T9f9/0/X/f9P1/3/T9f9/0/X/f9P1/3/T9f9/0/X/f9S3///OqzN/wAAAP8AAAD/AAAA/wAAAP8CBwn/UNj9/1DY/f9Q2P3/UNj9/1DX/f9U4f//Vuf//1bn//9W5///We7//z+32v8CCg3/AwcJ/wMHCf8DBwn/BhQZ/1LZ/f9S2f3/Utn9/1LZ/v9T3f7/NJGr/ydmeP8nZnj/J2Z4/ydneP8+qsf/Tcrm/0rD5P9Kw+T/SsPk/0vG5/9T2f3/U9n9/1PZ/f9U2v7/VOD//xM/TP8AAAD/AAAA/wAAAP8AAAD/K3aK/1rs//9V3v//Vd7//1Xe//9V3v//U9n9/1PZ/f9T2f3/VNr+/1Tf//8XSFb/AAAA/wAAAP8AAAD/AAAA/y16j/9Y5v//U9n9/1PZ/f9T2f3/U9n9/1PZ/f9T2f3/U9n9/1Ta/v9U3///F0hW/wAAAP8AAAD/AAAA/wAAAP8teo//WOb//1PZ/f9T2f3/U9n9/1PZ/f9V3v//Vd7//1Xe//9W3///VuT//xRATP8AAAD/AAAA/wAAAP8AAAD/LHWJ/1rs//9V3v//Vd7//1Xe//9V3v//S8Tk/0vE5P9LxOT/S8Tl/07O7P8uh6H/GlNk/xpUZf8aVGX/GlRl/zmjwP9OzOf/S8Tk/0vE5P9LxOT/S8bn/wMICf8DCAn/AwgJ/wMHCf8OLTb/Utr//1bl//9V4///VeP//1jq//9AuNr/AwoN/wMICf8DCAn/AwgJ/wYUGf8AAAD/AAAA/wAAAP8AAAD/CiEp/0/R9f9V3f//U9r+/1Pa/v9Y4v//Pa3N/wAAAP8AAAD/AAAA/wAAAP8CBwn/AAAA/wAAAP8AAAD/AAAA/wskLP9P0fX/VNz//1PZ/f9T2f3/V+H//z2uzv8AAgP/AAAA/wAAAP8AAAD/AwoN/wAAAP8AAAD/AAAA/wAAAP8LIyv/T9H1/1Tc//9T2f3/U9n9/1fh//89rc3/AAID/wAAAP8AAAD/AAAA/wMJDP8AAAD/AAAA/wAAAP8AAAD/DCcw/0/T9/9U3P//U9n9/1PZ/f9X4f//PrLT/wAEBf8AAAD/AAAA/wAAAP8EDhL/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</Image>
+<Url type="text/html" method="GET" template="http://www.gulesider.no/query" resultdomain="gulesider.no">
+ <Param name="what" value="all"/>
+ <Param name="search_word" value="{searchTerms}"/>
+ <Param name="cmpid" value="fre_partner_fire_gssbtop"/>
+</Url>
+<SearchForm>http://www.gulesider.no/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/heureka-cz.xml b/browser/locales/searchplugins/heureka-cz.xml
new file mode 100644
index 000000000..0dccb6f5e
--- /dev/null
+++ b/browser/locales/searchplugins/heureka-cz.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Heuréka</ShortName>
+<Description>Vyhledávání na Heuréka.cz</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAA9fX13/X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX13/X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//769f/59/X/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/+/y9f8mjP7/lMP5//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/09fX//fn1/6XM+P97t/r/ir/6/9jm9v8ahf7/AHb//7fV+P/19fX/9fX1//X19f/19fX/9fX1//X19f/09fX/0eL2/w1///8AdP//NZP9/wJ5//8AdP//A3n//7HT+P/39vX/9fX1//X19f/19fX/9fX1//X19f/19fX/9PT1/w5///8wkf3/8vP1//Dz9f///vT/gbr6/wBy///I3vf/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/5fF+f8Ad///+ff1/8rf9/8Ab///nMj4///79f85lf3/TZ/8//r49f/19fX/9fX1//X19f/19fX/9fX1//X19f98t/r/N5T9//n39f8Nfv//HIb+/wR6///y9PX/g7v6/xOC////+/T/9fX1//X19f/19fX/9fX1//X19f/19fX/grr6/xqF/v/y9PX/Xqj7//359f8bhv7/W6b8/3Cx+v8pjf3//vr1//X19f/19fX/9fX1//X19f/19fX/9fX1/9fm9v8AcP//mcb5//759f/09fX/wdv3/wBz//8Lfv//g7v6//b19f/19fX/9fX1//X19f/19fX/9fX1//X19f/59/X/eLX6/wBw//9Ypfz/nsn5/3W0+v8Ge///LY/9//r49f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//z59f+s0Pj/MpH9/xOC//8giP7/f7n6//r49f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//359f//+/T///r1//b29f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX13/X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX13wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA9fX1gPX19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19YD19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/5vH+f9Amf3/bbD7//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f+bx/n/BHr//wR6//8Eev//9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/m8f5/wR6//8Eev//BHr//xOC/v/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/X5vb/bbD7/zGR/f8Eev//BHr//wR6//9Amf3/m8f5/4y/+f8Eev//BHr//wR6//8Tgv7/yN73//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/bbD7/wR6//8Eev//BHr//wR6//8Eev//BHr//wR6//8Eev//BHr//wR6//8Eev//E4L+/8je9//19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/zGR/f8Eev//BHr//wR6//8iif7/Xqj7/324+v9Amf3/BHr//wR6//8Eev//BHr//xOC/v/I3vf/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f9tsPv/BHr//wR6//8Tgv7/m8f5//X19f/19fX/9fX1//X19f/m7fb/bbD7/wR6//8Eev//BHr//8je9//19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/udb4/wR6//8Eev//E4L+/8je9//19fX/9fX1/9fm9v+51vj/9fX1//X19f/19fX/m8f5/wR6//8Eev//QJn9//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f9eqPv/BHr//wR6//+51vj/9fX1//X19f/X5vb/E4L+/wR6//99uPr/9fX1//X19f/19fX/QJn9/wR6//8Eev//yN73//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/xOC/v8Eev//MZH9//X19f/19fX/9fX1/zGR/f8Eev//BHr//xOC/v/X5vb/9fX1//X19f+51vj/BHr//wR6//99uPr/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/BHr//wR6//9tsPv/9fX1//X19f9tsPv/BHr//wR6//8Eev//BHr//0+g/P/19fX/9fX1//X19f8Eev//BHr//0CZ/f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f8Eev//BHr//324+v/19fX/udb4/wR6//8Eev//MZH9/0+g/P8Eev//BHr//6rP+P/19fX/9fX1/xOC/v8Eev//QJn9//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/wR6//8Eev//bbD7//X19f+bx/n/BHr//xOC/v/I3vf/1+b2/xOC/v8Eev//Ion+/+bt9v/19fX/BHr//wR6//9PoPz/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/Ion+/wR6//8iif7/9fX1//X19f/I3vf/1+b2//X19f/19fX/jL/5/wR6//8Eev//bbD7/6rP+P8Eev//BHr//324+v/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f9tsPv/BHr//wR6//+Mv/n/9fX1//X19f/19fX/9fX1//X19f/19fX/MZH9/wR6//8Eev//MZH9/wR6//8Eev//1+b2//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/9fm9v8Eev//BHr//xOC/v+51vj/9fX1//X19f/19fX/9fX1//X19f+51vj/BHr//wR6//8Eev//BHr//0CZ/f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/4y/+f8Eev//BHr//wR6//99uPr/5u32//X19f/19fX/9fX1/7nW+P8iif7/BHr//wR6//8Tgv7/1+b2//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/16o+/8Eev//BHr//wR6//8Eev//MZH9/0CZ/f8Tgv7/BHr//wR6//8Eev//E4L+/8je9//19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/6rP+P8Tgv7/BHr//wR6//8Eev//BHr//wR6//8Eev//BHr//0CZ/f/X5vb/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1/+bt9v+bx/n/T6D8/0CZ/f9Amf3/QJn9/22w+//I3vf/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fWA9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1//X19f/19fX/9fX1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.heureka.cz/direct/firefox/autocompleter.php">
+ <Param name="query" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="http://www.heureka.cz/" resultdomain="heureka.cz">
+ <Param name="h[fraze]" value="{searchTerms}"/>
+ <Param name="utm_source" value="firefox-search"/>
+</Url>
+<SearchForm>http://www.heureka.cz/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/hoepli.xml b/browser/locales/searchplugins/hoepli.xml
new file mode 100644
index 000000000..ae856aad0
--- /dev/null
+++ b/browser/locales/searchplugins/hoepli.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Hoepli</ShortName>
+<Description>Dizionario della lingua italiana Hoepli</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,
+iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAEZJREFUeNpi/P//PwMpgAVCMSYehDD+z7dHlsYUZ2IgEQxCDSxofLgvB4+TcMUDLZ2kQKqGBxQ6iZHU1EqyDQAAAAD//wMApAcRQrj9oIAAAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="http://dizionari.hoepli.it/Dizionario_Italiano/cerca.aspx" resultdomain="hoepli.it">
+ <Param name="idD" value="1" />
+ <Param name="utm_source" value="mozilla-firefox" />
+ <Param name="query" value="{searchTerms}" />
+</Url>
+<SearchForm>http://dizionari.hoepli.it/Dizionario_Italiano.aspx?idD=1</SearchForm>
+</SearchPlugin> \ No newline at end of file
diff --git a/browser/locales/searchplugins/images/yandex-en.ico b/browser/locales/searchplugins/images/yandex-en.ico
new file mode 100644
index 000000000..6398f30e9
--- /dev/null
+++ b/browser/locales/searchplugins/images/yandex-en.ico
Binary files differ
diff --git a/browser/locales/searchplugins/kannadastore.xml b/browser/locales/searchplugins/kannadastore.xml
new file mode 100644
index 000000000..794a3ba3e
--- /dev/null
+++ b/browser/locales/searchplugins/kannadastore.xml
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Kannada Store</ShortName>
+<Description>Kanada Store, Online store</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%06%00%00%00%1F%F3%FFa%00%00%00%01sRGB%00%AE%CE%1C%E9%00%00%00%06bKGD%00%FF%00%FF%00%FF%A0%BD%A7%93%00%00%00%09pHYs%00%00%0B%13%00%00%0B%13%01%00%9A%9C%18%00%00%00%07tIME%07%D8%0A%0E%11%0D%25%7B%988%3D%00%00%00%19tEXtComment%00Created%20with%20GIMPW%81%0E%17%00%00%02%96IDAT8%CB%B5S%5DHSq%1C%3D%FF%BB%7B%AFw%DB%DD%A6ss%CEebs%E9%0Cm%AB%F0%23T%10%FB%20la%05%81%91F%F8%D0%93%BD%F4R%0F%BD%D4%7B%09AAbdDA%84f%8C%B0%CC%1E%FAxP%B0%9A%99%18%E9%CA%0F%B4%E9%3Elnn%BB%9B%F7%DF%93%D2%C7%A4%A7~%AF%BF%F3%3B%9Cs8%3F%E0%7FN4%D2g%F9%17%86l%B6%A0%94%8A%81%5E%D7%AB%F3%2B%DD%CE%2C%93%12%81%88%3C%DB%E7%91%1Ek%09%18qEb%07%2F%19o%5B%F4%8AavS%EA%D8%D8%BE%2Fr%A5%93%F7%BD%9D%19)v%5D%8B%B2%92%CA%5E%CF%F1%99s%1FO-%7B%F9%89%9B%03%9A(%00lJ%10%0F%BCv%C5%FD%5E%5C%3E%7D%F6%8E%D9D%3A%00%A0%E7%DE%03m%8C%81%D3%B0%93%B6%1C%3CZ%1E%06%00%26%DD%F1%D2%87%AEN)%E4%AE%CFS%8D%40%FCv%F78%A5%B4%00%00%04VRX%AD%DB%0A%0E45%C7%D7%B1i%15%F0k%5E%AB%A8yZh%AE%06%BAG%D9d%CF%E8r%C5%05w%A0L%D8%C16H%FE%B9%00%00'%80%A1%8D%10%FB%DF%CC%E6%3E%FA%1Cl%CE%B3%08W_%8E%871%1CJ%E0%8C%ED%09%15U%22%AEO%B7%12Be%C8s2%88%17%A8-%D3%2C6%ED%15l%ED%8D%BA%F0%86%02F%9D%7D%D13%95q%AEk%60%95f%A8%B3%08%C7%25%D09%DFF%90%93%09%B5%AC%40%CA%17%07%13%0D%C3a%5DE%89%3Er%BF%BD%D1%11%5EW%CB%00%C0~%87%F2V%9D%3D%E5%DFU%A8%24rL%09%CA%E6%40%CC7BT)!%FB%FD%A0%DF%C7%E04%2C%A0%D6F%A0%15%D7B%BF%DAe%00%80%10%F2i%E9%07%EF%AD)V%80%E3%08h%24%85%E4%D7%04%92%93%3E%C8A%1F8%9D%06%06%A3%0E%16%93%16%5B%8D%CA%DF%82%DF%08%D1%A4%97%19K6%0F%BB%83b%DC%C7%02kI%10%A8%20%0B%3A%18T%0C%EC%25z%24%14%B2%A4%E3%B9%D1%B4%04z%3DK%8A%B6%08%B4F%99A%92%3E%19%D3A%0A)%C4%40%A3%11Pi%D6%60%C8%F3%0E%E6y7%A9(%B7%09%7FY%00%80%3D%A5Y%1D%0F%FB%06%C9%EC%E4%04%8A%84%14%B4%99%02d%15%8F%5C.%09%B3%B0%0C%C1s%03f%83.%E8%AC%A8%9DI%FB%0B%94R%F5%A1%FD%0D%11C%FEv%94%1Dn%C3%82%2C%E2y%FF3%F0%93%2F%D0%D6%D2%8C%BA%EA%DD%88%85%97%8FUUW%F5%A6%B5%00%E0%CA%89%93%AD%A9X%2C%CE%E4%EB)%93%13%5E%C4%FB)7%9A%8E%B8%A0W%B1(-)%06%A7%20%BD%7F%96%EE'%A3%D6%EC%AB%93r%84%7D%00%00%00%00IEND%AEB%60%82</Image>
+<Url type="text/html" method="GET" template="http://www.kannadastore.com/advanced_search_result.php" resultdomain="kannadastore.com">
+ <Param name="keywords" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.kannadastore.com/advanced_search_result.php</SearchForm>
+</SearchPlugin> \ No newline at end of file
diff --git a/browser/locales/searchplugins/kaz-kk.xml b/browser/locales/searchplugins/kaz-kk.xml
new file mode 100644
index 000000000..cfc32cab8
--- /dev/null
+++ b/browser/locales/searchplugins/kaz-kk.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>KAZ.KZ</ShortName>
+<LongName>KAZ.KZ көмегімен іздестіру</LongName>
+<Description>Қазақстандық іздестіру жүйесі</Description>
+<InputEncoding>utf-8</InputEncoding>
+<Image height="16" width="16" type="image/x-icon">data:image/x-icon;base64,AAABAAMAEBAQAAEABAAoAQAANgAAABAQAAABAAgAaAUAAF4BAAAQEAAAAQAgAGgEAADGBgAAKAAAABAAAAAgAAAAAQAEAAAAAADAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAD82G0A9MxaAO3gugDz8vEA%2FtRcAP7prwD44JkA%2Fs5DAPLy8QDp6ecA%2F8MbAP%2FNPQD%2F1FcA%2F807AP%2FwxwAAAAG7u9AAAAANyFAFuxAAABVwAAhXjQAA1QAALAAM0A27u4iAAADhG4cHiwAAAF7oAAALYAAAe7UAAAVQAAALtQAABVAAAHvoAAALcAAAexuHB4UAAACMDbu7UAAAAOAAuAAAAAAF0AAVAAAAAI0AAADYVmWLEAAAAAHrvtAAAPgfAADhhwAAx4MAAM85AACAfAAACPwAAD58AAA%2BfgAAPnwAAD58AAAI%2FAAAgf0AAM%2F5AADP8wAA8AcAAPgfAAAoAAAAEAAAACAAAAABAAgAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP7%2B%2FgD29fUA6ennAPDjvQD%2B4ZAA%2FuKRAPj49wDw7%2BsA7NurAOzs6gDs6%2BoA6uroAPHlxAD8xisA9%2Ff2APXt1QDw47sA%2Fsw7AOXYswD%2B7LoA8vLxAP7NPAD%2B9NkA7eXMAPLnxwDw7%2B4A6ejmAPHjuAD379oA%2FMo%2FAObl4wDx8fAA49avAObk3QD%2B34UA%2Fuu1APPz8gD%2B4pIA7OzqAPz8%2FADs6%2BkA%2Fu28AP7ORQD08%2FEA%2FuSaAOHSqgDx8O8A6OflAOvdswD48uEA6%2BvpAPf29gD06s0A8OO%2FAO%2Fu7QDr5M0A8ubEAOvZpQDy47kA9vb1APzHLwD025UA5%2BbkAO%2Fu7QD5%2BfkA8%2BnLAOLVrgD%2B5Z4A5eLbAP7RUgD%2F4IgA%2F9hsAP%2FQTgD%2F0E4A%2F9VgAP%2FoqgD%2FwQwA%2F%2BqtAP%2FabAD%2FwQ4A%2F%2BGJAP%2FVXAD%2Fxh4A%2F%2BqtAP%2FNPQD%2F2WgA%2F9JTAP%2FKMAD%2F6KgA%2F9ZfAP%2FRTwD%2F0E0A%2F9NWAP%2FYbAD%2F7sAA%2F8UhAP%2FLOAD%2FyzAA%2F85EAP%2FLOQD%2FxR0A%2F9BBAP%2FUVAD%2F6aYA%2F8guAP%2FbcAD%2Fz0gA%2F8o1AP%2Fz0QD%2F1V8A%2F%2BGNAP%2FjlQD%2FyzkA%2F%2BSZAP%2FXZAD%2F5p0A%2F%2BKRAP%2FbeAD%2FyjUA%2F9NTAP%2FKNgD%2FxiEA%2F89HAP%2FEGQD%2FzD4A%2F%2BeiAP%2FPTAD%2Fz0gA%2F%2BmpAP%2FKMAD%2F1l8A%2F%2BqvAP%2FghgD%2FyTAA%2F803AP%2FTUAD%2FwxkA%2F%2BSTAP%2B%2BAQD%2F3nsA%2F8cmAP%2FFHAD%2FwAUA%2F9FRAP%2FDFwD%2F01gA%2F95%2FAP%2FSUwD%2F5Z0A%2F%2BekAP%2FLNwD%2Fz0UA%2F9BEAP%2FVXgD%2FyCQA%2F%2BafAP%2FhiAD%2F1l8A%2F85AAP%2FEEgD%2FwhIA%2F%2BmtAP%2FTVwD%2F56UA%2F9BMAP%2FNQAD%2FzDoA%2F9djAP%2FCFQD%2F0lAA%2F8AJAP%2FEGwD%2F0VAA%2F8EMAP%2B%2BAAD%2F8McAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWcqo2urqyobxoAAAAAADWol312lX5eUHyqBAEAADJqf0QiACwUW5AGSmcEAAKKh4VtQkURlFc7CA2AiEBoZqBNi2BVYWscKAAHCViTVlMWJl8jEqEqQQAAAAqlZIJ5ITMAAy4OTAAAAAAAcIllWjAAAAA%2FrW4AAAAAAIGreoMbAAAAH61LAAAAAAByq2KnEwsADEM9PgAAAAAAR6xPjistsAUeRjgAAAAAKXufTpmPr6%2BRozEnAAAAPDqGdTRRm5KEJDcAAAAADzZJXS8AHYyYpBclAAADGKJcnjkAAAAQnWNxc5ZZSKZsUhkBAAAAAAJUmnepqWl4dCAAAAD4HwAA4YcAAMeDAADPOQAAgHwAAAj8AAA%2BfAAAPn4AAD58AAA%2BfAAACPwAAIH9AADP%2BQAAz%2FMAAPAHAAD4HwAAKAAAABAAAAAgAAAAAQAgAAAAAABABAAAAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAPLy8Q7%2F5p9g%2F9JQr%2F%2FHJtn%2FwQzz%2F8EM8%2F%2FEG%2BT%2F12Oc%2F%2BGNcvDv7hH%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAPTqzTL%2F12Oc%2F8s3zP%2FMPtD%2F23ih%2F%2BWdcv%2Fnom7%2F2Gyu%2F8EO8f%2FEGeb%2F0lCv8OO9Qv7%2B%2FgH%2F%2F%2F8A%2F%2F%2F%2FAPjy4R7%2F23CP%2F89Mwv7lnoPm5N0k%2F%2F%2F%2FAPTz8Q%2F%2B7Lpb%2F9FPw%2F%2FRUbj%2B4pGE%2F9BOwf%2FUVKvw471C%2F%2F%2F%2FAPb19Qr%2F5JNs%2F803yv%2Fghpv%2F89FE8%2BnLQ%2BXi2ybw47tU%2F9JTvv%2FSU7by47lN8O%2FrFfHlxE7%2Fz0jI%2F9NQr%2B%2Fu7RL%2F6aZZ%2F9BBvv%2FEEu3%2FwQz2%2F74B%2F%2F%2FFIeb%2FzT3Q%2F8s40%2F%2FPSMDx47hO%2FPz8A%2F%2F%2F%2FwD4%2BPcI7Nurbf%2FKMNP%2F3n%2BA%2F9lol%2F%2FGHuL%2BzTzU%2FuKSkf%2FuwGP%2B34WX%2Fsw70v%2FCEu%2F%2B7bxi%2Bfn5Bv%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAOzs6hX%2F0EzJ%2F8s5xv%2FKMM%2F%2Fyjba49avY%2Bvr6Rb%2F%2F%2F8A6ennGOHSqmv8xivh%2F%2Biqff%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2BOVjf%2FDGeb%2FxR3i%2F9Zfu%2Bjn5Rr%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwDn5uQb%2F9FQyP%2FVX7r%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2FpqXL%2FwAn2%2F8Yh3v%2FWX7rp6OYZ%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A5uXjHP%2FRUMj%2F1WC5%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F5JmA%2F8AJ9v%2FLMM%2F%2FzDrU5dizYOzr6hX%2F%2F%2F8A6uroF%2BLVrmX8xy%2Fd9NuVi%2F%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2BCImP%2FEG%2BT%2F2myT%2F8Uc5P7ORc3%2B5JqF%2F%2FDHWP7hkI38yj%2FN%2FtFSxOvkzUb%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A7OvpFv%2FPR8v%2FzkC%2F%2F%2BqtUv%2FQRLv%2FwAX6%2F74A%2F%2F%2B%2BAP%2F%2Fwxft%2F9NXwevds2Ds7OoV%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A9vb1CuvZpXP%2FyTDT%2F%2BKRbvf29gn%2F4Yl2%2F8gk2%2F%2FTWMb%2F6q92%2Fuu1Zu%2Fu7RL%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A9%2Ff2CfDjv1b%2F0E7A%2F9NWqfHw7xD%2F%2F%2F8A9%2B%2FaJf%2Fee4T%2Fz0XE%2F%2Belef702Tvz8%2FIN%2F%2F%2F%2FAP%2F%2F%2FwDp6ecY7eXMRv%2FprXf%2F0E3C%2F9ZfoPLmxDv%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwD17dUq%2F%2BGId%2F%2FORL3%2FyznW%2F9dkuP%2FnpIr%2F6KiH%2F9hssv%2FNQNH%2FyjXO%2F9Vco%2FLnxzj%2B%2Fv4B%2F%2F%2F%2FAP%2F%2F%2FwD%2F%2F%2F8A%2F%2F%2F%2FAPb19Qr%2F6q1S%2F9Veof%2FKNcr%2FwhXq%2F8IV6v%2FILtH%2F01Os%2F%2BadYvHx8A%2F%2F%2F%2F8A%2F%2F%2F%2FAP%2F%2F%2FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="text/html" method="get" template="http://search.kaz.kz/search.cgi" resultdomain="kaz.kz">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="lang" value="KAZ"/>
+</Url>
+<SearchForm>http://kaz.kz/index_kk.html</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/klask.xml b/browser/locales/searchplugins/klask.xml
new file mode 100644
index 000000000..321c908d3
--- /dev/null
+++ b/browser/locales/searchplugins/klask.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Klask</ShortName>
+<Description>Klask, stal ar brezhoneg</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACFhYUAioqKAJGRkQCcnJwAn5+fAKampgCnp6cAOqbyALCwsACxsbEAtbW1ALm5uQDBwcEAwsLCAM7OzgDPz88A2traANvb2wDm5uYA+vr6APz8/AD+/v4A////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhYWFhYWFhYWFhYWFhYWFhYWBwcHFhYWFhAHBwcHARYWFgcHBxYWFg4HBwcHBBYWFhYHBwcWFg0HBwcHCRYWFhYWBwcHFgoHBwcHDBYWFhYWFgcHBwUHBwcHDxYWFhYWFhYHBwcHBwcHERYWFhYWFhYWBwcHBwcHEhYWFhYWFhYWFgcHBwcHBwsWFhYWFhYWFhYHBwcHBwcHCBYWFhYWFhYWBwcHEwcHBwcGFhYWFhYWFgcHBxYUBwcHBwMWFhYWFhYHBwcWFhUHBwcHAhYWFhYWBwcHFhYWFgcHBwcAFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</Image>
+<Url type="text/html" method="POST" template="http://www.klask.com/index.php" resultdomain="klask.com">
+ <Param name="yc" value="0"/>
+ <Param name="dib" value="13"/>
+ <Param name="ger" value="{searchTerms}"/>
+</Url>
+
+<SearchForm>http://www.klask.com/index.php</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/leit-is.xml b/browser/locales/searchplugins/leit-is.xml
new file mode 100644
index 000000000..0e4f230d5
--- /dev/null
+++ b/browser/locales/searchplugins/leit-is.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>leit.is</ShortName>
+<Description>leit.is - Íslensk leitarvél</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAALifu/yoi7f8rI+3/KiHt/zMt8P83MvH/MS3J/zAsv/8xLML/MSzB/zEtwf8sKMH/Ix3A/yQdwP8jHMD/KCLB/ygg7f8iGu3/Ihrs/ycg7v8AAOn/AAC+/wAAsP8AALb/AAC1/wAAtf8AALT/AAC4/yAZvv8bFL3/GxO9/yIbwP8rI+3/JBzt/ysk7f8AAO7/oqDr/8rJ5v/Cwev/xMLp/8TC6v/Cwen/ycjs/6Si3/8AALn/JB6//x0Wvv8jHbz/KiLt/yQc7f8vKu3/AADq/9nY/v/R0er/goDQ/5aU1v+TktX/kpHV/5eW1/96eM//AAC8/yEbv/8bFLb/JyDV/yoi7f8kHO3/Lint/wAA6v/R0Pz/o6LX/wAArv8AALj/AAC3/wAAt/8AALb/AAC5/yEbwP8bFLb/HxfV/ysk8/8qIu3/JBzt/y8p7f8AAOr/0tH8/66t3P8AALj/NTHB/ygjv/8oI7//KSS//yUgwP8bFLb/IRnV/yQb8/8qIuz/KiLt/yQc7f8vKe3/AADq/9LR/P+trNv/AAC2/y0ov/8dFr7/HRa+/x0Wv/8cFbb/IBnV/yUd8/8jG+z/KiLt/yoi7f8kHO3/Lynt/wAA6v/S0fz/razb/wAAtv8tKb//HRa+/x0Wv/8cFbb/IBnV/yUd8/8kHOz/Ixvs/yoi7f8qIu3/JBzt/y8p7f8AAOr/0tH8/62s2/8AALb/LSm//x0Wv/8cFbb/IBnV/yUd8/8kHOz/JBzt/yMb7f8qIu3/KiLt/yQc7f8vKe3/AADq/9LR/P+trNv/AAC2/y0pwf8cFbb/IBnV/yUd8/8kHOz/JBzt/yQc7f8jG+3/KiLt/yoi7f8kHO3/Lynt/wAA6v/S0fz/razb/wAAuP8sKLj/IBnV/yUd8/8kHOz/JBzt/yQc7f8kHO3/Ixvt/yoi7f8qIu3/JBzt/y4p7f8AAOr/0dD8/6uq3P8AAK7/LyrW/yUc8/8kHOz/JBzt/yQc7f8kHO3/JBzt/yMb7f8qIu3/KiLt/yQc7f8vKe3/AADq/9rZ/f+0s9f/AADP/zQu9P8kHOz/JBzt/yQc7f8kHO3/JBzt/yQc7f8jG+3/KiLt/ysj7f8kHO3/KiTt/wAA7P+op/b/jYzq/wAA7v8tJ+3/JBzt/yQc7f8kHO3/JBzt/yQc7f8lHe3/Ixvt/ysj7f8oIO3/Ihrt/yIa7f8oIe3/AADr/wAA7v8qI+3/IRns/yMb7f8jG+3/Ixvt/yMb7f8jG+3/Ixvt/yIa7f8oIO3/Lifu/yoi7f8rI+3/KiHt/zMt7v8xKu3/KSHt/ysj7f8qIu3/KiLt/yoi7f8qIu3/KiLt/ysj7f8qIu3/Lifu/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAPDXv/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8xKff/Lifk/ykjwP8pI8D/KSPC/ykjwv8pI8L/KSPC/ykjwv8pI8L/KSPC/ykjwv8pI8L/KSPC/ykjwv8pI8L/KSPC/ykjwv8pI8L/KSPC/zYwxf8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JR33/yMb5P8cFbr/HBa5/x0Wv/8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yYd9/8iG+P/HBW5/xwVuf8dFr//HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8pI8L/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yoh7v8WDfD/AADY/wAAr/8AAK//AAC1/wAAtP8AALT/AAC0/wAAtP8AALT/AAC0/wAAtP8AALT/AAC0/wAAtP8NBrn/IhrA/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/ykjwv8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/GRLv/0Q87f9sZ9D/Yl3M/2Ne0/9jXtL/Y17S/2Ne0v9jXtL/Y17S/2Ne0v9jXtL/Y17S/2Ne0v9jXtL/bGjV/z44x/8SDbr/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/KSPC/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/lpL0////////////////////////////////////////////////////////////////////////////o6Dl/wAAr/8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvf8oIr3/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/wAA6v+NifX///////Hw+/+gnuT/n5zk/6Og5f+joOX/o6Dl/6Og5f+joOX/o6Dl/6Og5f+joOX/o6Dl/6im5v9cV9H/Cga3/x0Wvv8dFr7/HRa+/x0Wvv8dFrz/HBW1/ywl1P8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/AADq/4uH9f//////zs3x/wAAs/8AALL/AAC1/wAAtf8AALX/AAC1/wAAtf8AALX/AAC1/wAAtf8AALX/AAC2/w8Huv8gGMD/HRa+/x0Wvv8dFr7/HRa9/xwVtP8gGNH/MSn2/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/i4f1///////U0/P/FA27/xILuv8bFL3/GxS9/xsUvf8bFL3/GxS9/xsUvf8bFL3/GxS9/xsUvf8bFL3/HBW+/x0Wvv8dFr7/HRa+/x0WvP8cFbT/IBnR/yUd9f8xKfH/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/wAA6v+Lh/X//////9TT8/8WD7z/FA27/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFrz/GxW1/yAY0f8lHfX/JR3w/zAo7f8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/AADq/4uH9f//////1NPz/xYPvP8UDbv/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr//HRa9/xwVtP8gGNH/JR31/yQc8P8kHOz/MCju/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/i4f1///////U0/P/Fg+8/xQNu/8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0WvP8cFbT/IBjR/yUd9f8kHPD/JBzs/yQc7f8wKO7/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/wAA6v+Lh/X//////9TT8/8WD7z/FA27/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr3/HBW0/yAY0f8lHfX/JBzx/yQc7P8kHO3/JBzt/zAo7v8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/AADq/4uH9f//////1NPz/xYPvP8UDbv/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa8/xsVtf8gGNH/JR31/yQc8P8kHOz/JBzt/yQc7f8kHO3/MCju/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/i4f1///////U0/P/Fg+8/xQNu/8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0WvP8cFbT/IBjR/yUd9f8lHfD/JBzs/yQc7f8kHO3/JBzt/yQc7f8wKO7/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/wAA6v+Lh/X//////9TT8/8WD7z/FA27/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFrz/GxW0/yAY0f8lHfb/JBzw/yQc7P8kHO3/JBzt/yQc7f8kHO3/JBzt/zAo7v8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/AADq/4uH9f//////1NPz/xYPvP8UDbv/HRa+/x0Wvv8dFr7/HRa+/x0Wvv8dFr7/HRa9/xsVtP8gGNH/JR32/yQc8f8kHOz/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/MCju/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/i4f1///////U0/P/Fg+8/xQNu/8dFr7/HRa+/x0Wvv8dFr7/HRa+/x0Wvf8cFbT/IBjR/yUd9f8kHPD/JBzs/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8wKO7/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/wAA6v+Lh/X//////9TT8/8WD7z/FA27/x0Wvv8dFr7/HRa+/x0Wv/8dFr3/GxW0/yAY0f8mHfX/JBzw/yQc7P8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/zAo7v8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/AADq/4uH9f//////1NPz/xYPvP8UDbv/HRa+/x0Wvv8dFr7/HRa8/xwVtP8gGdH/JR32/yQd8P8kHOz/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/MCju/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/i4f1///////U0/P/Fg+8/xQNu/8dFr7/HRa+/x0Wvf8bFbT/IBjR/yUd9v8lHPH/JBzs/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8wKO7/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/wAA6v+Lh/X//////9TT8/8WD7z/FA27/x0Wvv8dFr3/HBW0/yAY0f8lHfX/JBzw/yQc7P8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/zAo7v8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/AADq/4uH9f//////1NPz/xYPvP8UDbv/HRa8/xwVtP8gGNH/JR31/yQc8P8kHOz/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/MCju/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/i4f1///////U0/P/Fg+8/xQNuv8cFbT/IBjR/yUd9f8kHPD/JBzs/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8wKO7/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/wAA6v+Lh/X//////9TT8/8WD7v/Eguy/yAZ0f8lHfX/JR3w/yQc7P8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/zAo7v8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/AADq/42J9f//////19by/xUOs/8XD8//JR31/yUd8P8kHOz/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/MCju/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8AAOr/lpL2///////m5vD/GRHQ/xwS8/8kHPD/JBzs/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8wKO7/MCju/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/xgR7P9EPfD/dnH0/1tV8P8jGvD/Ihnv/yQc7P8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/zAo7v8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/KSHu/xUM7P8AAOr/CgHs/yUd7v8lHe3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/MCju/zAo7v8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8wKO7/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f8kHO3/JBzt/yQc7f88Ne//MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/MCju/zAo7v8wKO7/PDXv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="text/html" method="GET" template="http://leit.is/leita" resultdomain="leit.is">
+ <Param name="utf-8" value="✓"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://leit.is/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/leo_ende_de-rm.xml b/browser/locales/searchplugins/leo_ende_de-rm.xml
new file mode 100644
index 000000000..9c88d1468
--- /dev/null
+++ b/browser/locales/searchplugins/leo_ende_de-rm.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>LEO Eng-Tud</ShortName>
+<Description>Pledari tudestg-englais da LEO</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACtElEQVR4nF3Ma0hTYQDG8ec9e8/OOdNd9KjTwAwrmTjNDOxDVFJpIEVFgRkUhRkWXaAkMNDCyqgsumBIReGHiILCIOhDUBG1IE2FrprXXEwTJ7bN7d25vH0owvp//j0PSbc7fxqapnNNM4oIuQykUYGaOgc0B01uNOWlAIPVCllW42YjDceiM1ocQLbLme6wy9/8NA4OCImY26jZSoEIYAPEDM04RguqIFoVADlAc/PJjo5nRwjpBwbtmGkF7wPvAu8Cf5+hdV/TOd9V10413SwC1gMez0KvNzcgSQKNuc9AWQ2EAABihm7UY9HeqrpHbbdeC0XATUACFEUmBNxK0pthK5ul9Xrm2bO99mHb9ZeQKL0hi2kzjAGU0mBwWi3WZ38b9os0a+tY2Cgt8axb6yUCoamGzoAIEVwuZyAw5s7X/2qm108/jmqBs6KALbJICAFA4ywOIGKzuN2pPl/n3CT+W4eGqkdq77ChUfwb5QB1gBdbVDV5YmIiSwXEjNDw7tHjb9iIH4T8PxBTxLRTjL+0UGpljCkONcYaAldeGOFI2r6dRBCIIpnxOIHJwe2SRNVzorKMCc8FgHAOi/OQPL9Sl30BV+LAisKenu6hd75Ygk2gFlmyToVNauZr0SC+jxumaWRlZZ4+2qTP60oMjbu9C/L8A2Vly72Hqx2OJCABiJSs2ojpV9aayvS2tlbOpzmf6uvrDIVGOY/zP4WDwWGf72lLy6W15RugbqGiaJmMZu7YsQ0QGIu5XK7u7g+fPz8Y7Ov/Ojz+pXfMpFLFphV5g6NNSkpS5RqqJM45sHl1xcr1THV3vff/mNTm52TvN8ZKqHWosOKTrxcmvkzl1t2unzl7+UTnM6oZDZm+T/LimvvtH2HJQYLhWVJQ7n9ColFJtkOwwCbeu/uWA7fPH0y+cPUXI+Y0JNj13cUAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="get"
+ template="http://dict.leo.org/dictQuery/m-query/conf/ende/query.conf/strlist.json?q={searchTerms}&amp;sort=PLa&amp;shortQuery&amp;noDescription&amp;noQueryURLs"/>
+<Url type="text/html" method="GET" template="http://dict.leo.org/ende" resultdomain="leo.org">
+<Param name="lang" value="de"/>
+<Param name="from" value="fxdesktop"/>
+<Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://dict.leo.org</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/leo_ende_de.xml b/browser/locales/searchplugins/leo_ende_de.xml
new file mode 100644
index 000000000..5b1113e95
--- /dev/null
+++ b/browser/locales/searchplugins/leo_ende_de.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>LEO Eng-Deu</ShortName>
+<Description>Deutsch-Englisch Wörterbuch von LEO</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACtElEQVR4nF3Ma0hTYQDG8ec9e8/OOdNd9KjTwAwrmTjNDOxDVFJpIEVFgRkUhRkWXaAkMNDCyqgsumBIReGHiILCIOhDUBG1IE2FrprXXEwTJ7bN7d25vH0owvp//j0PSbc7fxqapnNNM4oIuQykUYGaOgc0B01uNOWlAIPVCllW42YjDceiM1ocQLbLme6wy9/8NA4OCImY26jZSoEIYAPEDM04RguqIFoVADlAc/PJjo5nRwjpBwbtmGkF7wPvAu8Cf5+hdV/TOd9V10413SwC1gMez0KvNzcgSQKNuc9AWQ2EAABihm7UY9HeqrpHbbdeC0XATUACFEUmBNxK0pthK5ul9Xrm2bO99mHb9ZeQKL0hi2kzjAGU0mBwWi3WZ38b9os0a+tY2Cgt8axb6yUCoamGzoAIEVwuZyAw5s7X/2qm108/jmqBs6KALbJICAFA4ywOIGKzuN2pPl/n3CT+W4eGqkdq77ChUfwb5QB1gBdbVDV5YmIiSwXEjNDw7tHjb9iIH4T8PxBTxLRTjL+0UGpljCkONcYaAldeGOFI2r6dRBCIIpnxOIHJwe2SRNVzorKMCc8FgHAOi/OQPL9Sl30BV+LAisKenu6hd75Ygk2gFlmyToVNauZr0SC+jxumaWRlZZ4+2qTP60oMjbu9C/L8A2Vly72Hqx2OJCABiJSs2ojpV9aayvS2tlbOpzmf6uvrDIVGOY/zP4WDwWGf72lLy6W15RugbqGiaJmMZu7YsQ0QGIu5XK7u7g+fPz8Y7Ov/Ojz+pXfMpFLFphV5g6NNSkpS5RqqJM45sHl1xcr1THV3vff/mNTm52TvN8ZKqHWosOKTrxcmvkzl1t2unzl7+UTnM6oZDZm+T/LimvvtH2HJQYLhWVJQ7n9ColFJtkOwwCbeu/uWA7fPH0y+cPUXI+Y0JNj13cUAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="get"
+ template="http://dict.leo.org/dictQuery/m-query/conf/ende/query.conf/strlist.json?q={searchTerms}&amp;sort=PLa&amp;shortQuery&amp;noDescription&amp;noQueryURLs"/>
+<Url type="text/html" method="GET" template="http://dict.leo.org/ende" resultdomain="leo.org">
+<Param name="lang" value="de"/>
+<Param name="from" value="fxdesktop"/>
+<Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://dict.leo.org</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/list-am.xml b/browser/locales/searchplugins/list-am.xml
new file mode 100644
index 000000000..7117f098c
--- /dev/null
+++ b/browser/locales/searchplugins/list-am.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>List.am</ShortName>
+ <Description>Հանրային զերծ ads մասին: Վաճառք եւ գնման բնակարաններ, կենցաղային իրեր, որոնել աշխատանքի.</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">
+ data:image/gif;base64,R0lGODlhGAAYAIQfAKwVGN9vY/bw0ShPifHln1NqluXMLhxMjbC2ytKTidVDKcvP2vr29OyuoOC8F/PYHejJxNvc4hw4ertQRODCGOPDDx1qsvvoG/XkWM0gGjddmJoyQOCSh+Pm7IObwf///yH5BAEAAB8ALAAAAAAYABYAAAWs4CeOZGmeaKqSyTQlKxpkNADHJKQESgYAENyIE2D0aDdhQ7GkKZIphoAgWCqOiuCKUKE4HLyMArDxRFQEA8b77W0GknghpWawwW+N5iAZIFBqaw4PXgUeHQV8A3MnGF1eFw8Ohgt7B4sodg6QDxgFepcWmCgEX4MUAh4HqweiHmhsGB8RlpcFHVsGFQSzCJcHBQs4UyIRDInBQrPFHwsFZ8rQ0srMJNDRJdclIQA7
+ </Image>
+ <SearchForm>http://www.list.am/category?q=</SearchForm>
+ <Url type="text/html" method="GET" template="http://www.list.am/category" resultdomain="list.am">
+ <Param name="q" value="{searchTerms}"/>
+ </Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/longdo.xml b/browser/locales/searchplugins/longdo.xml
new file mode 100644
index 000000000..5df2e6421
--- /dev/null
+++ b/browser/locales/searchplugins/longdo.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>พจนานุกรม ลองดู</ShortName>
+<Description>พจนานุกรม ลองดู</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAcklEQVR42qSTwQ3AIAwDHZSROlOZiZ26k/tqRQtBjrgnECtEF+MJQqURMPsc+XuhQk4CAKCa1MHxe+ZDiz1CqA8FT0hQfHEVoDAdYoZwiAr9txqBaijYpKQk2grY6mBhaiySqLdnzYsDMgvVe5Va5wn3AGu/H1vn5EUlAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://search.longdo.com/Suggest/HeadSearch">
+ <Param name="ds" value="head"/>
+ <Param name="fxjson" value="1"/>
+ <Param name="key" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="http://dict.longdo.org/" resultdomain="longdo.org">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="src" value="moz"/>
+</Url>
+<SearchForm>http://dict.longdo.org/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/mailru.xml b/browser/locales/searchplugins/mailru.xml
new file mode 100644
index 000000000..0507e07d6
--- /dev/null
+++ b/browser/locales/searchplugins/mailru.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Поиск Mail.Ru</ShortName>
+<Description>Search with Поиск Mail.Ru</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAARCwAAEQsAAAAAAAAAAAAA85MASvOTAOfzkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//KTAP/rjgD/34YA/+CHAP/sjwDl85MASvOTAObzkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//KTAP/qjQD/zHsA/6tpAv+2bgL/4IcA//OTAOfzkwD/85MA//OTAP/wkQD/54wA/9yFAP/VgAD/1YAA/9uFAP/fhgD/yHkA/6VtHf/n3M3/7NzG/9+GAP/zkwD/85MA//OTAP/vkAD/3YUA/79zAv+iYwT/lFoF/5RaBf+iYwT/qWgD/6BpHv/q4dX///////Xlz//rjgD/85MA//OTAP/wkQD/3YUA/7JsAv+yi1b/4dPB//r49f/6+PX/4dPB/6yOYf/p4db///////bp1v/rlhv/8pMA//OTAP/zkwD/54wA/79zAv/Jr4r///////////////////////////////////////Hm1//ikBz/8pMA//OTAP/zkwD/85MA/9yFAP+/klX///////38+f/runb/75cY/++XGP/ruXb//fv5///////DmWH/24UA//OTAP/zkwD/85MA//OTAP/VgQD/5NXB///////gs3f/7pAA//OTAP/zkwD/7Y8A/+G3fv//////5NXB/9WBAP/zkwD/85MA//OTAP/zkwD/1YEA//n38///////y4Ea/+6QAP/zkwD/85MA/+2PAP/Kghr///////r39P/VgQD/85MA//OTAP/zkwD/85MA/9yFAP/69/P//////7N0Gv/bhQD/7pAA/+6QAP/bhAD/snQa///////69/P/3IUA//OTAP/zkwD/85MA//OTAP/ojAD/79zC//////++oHb/rWoC/8d4AP/HeAD/rWoC/8Klff//////79zC/+eMAP/zkwD/85MA//OTAP/zkwD/8JEA/+eqVv///////Pv5/7uedf+UYRr/lGAZ/7uedf/8+/n//////+eqV//wkQD/85MA//OTAP/zkwD/85MA//OTAP/vkAD/78SM/////////////////////////////////+/EjP/vkAD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//CRAP/vr1f/9uDC//359P/9+fT/9uDC//CvVv/wkQD/85MA//OTAP/zkwD/85MA//OTAP/zkwDm85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwDm85MASfOTAObzkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwDm85MASQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAARCwAAEQsAAAAAAAAAAAAA85MAAPOTACfzkwCt85MA8vOTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAPLzkwCt85MAJ2tCBwDzkwAo85MA7/OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwDv85MAKPOTAK3zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JEA/+aLAP/ahAD/2oQA/+aLAP/wkQD/85MA//OTAP/zkwCt85MA8fOTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//CRAP/ghwD/vnMA/55hA/+fYgP/w3UB/+aLAP/zkwD/85MA//OTAPPzkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/wkQD/4IcA/7hvAf+wiVb/8e3n//Ht5v+7j1L/2oQA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JEA/9+HAP+3bwH/s45d//39/P////////////Xv5//ZgwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JEA/+eMAP/dhgD/14IA/9N/AP/TfwD/14IA/92GAP/njAD/8JEA//CRAP/fhwD/t28B/7OPXv/9/fz/////////////////+fLp/+aLAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JEA/+aLAP/TfwD/unEB/6VlBP+YXQT/kVkE/5FZBP+YXQT/pWUE/7pxAf/RfgD/1YAA/7ZuAv+zj17//f38//////////////////79/P/qr17/8JEA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA/+6QAP/bhQD/unEB/5xiDP+wj2L/08Sv/+3o4f/8+/r//Pv6/+3o4f/TxK//sI9i/5lgDf+dYAT/sY1e//39/P/////////////////+/fz/6rFh//CRAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/ukAD/2IIA/61qAv+vi1r/6+Xd////////////////////////////////////////////6+Xd/7+qjf/9/fz//////////////////v38/+qxYf/wkQD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JEA/9yFAP+vbQb/y7WY//////////////////////////////////////////////////////////////////////////////////79/P/qsWH/8JEA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/liwD/unEB/8ixj//////////////////////////////////////////////////////////////////////////////////+/fz/46xh//CRAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JEA/9J/AP+8k13///////////////////////38+f/wypb/8KY+//GWDf/xlg3/8KY+//DKlv/9/Pn//////////////////////860kP/QfgD/75EA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/njAD/vHQK//Hs5f/////////////////48Ob/6qE6//GSAP/zkwD/85MA//OTAP/zkwD/8ZIA/+qhOP/48Ob/////////////////8u3l/7x1DP/njAD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA/92GAP/GnGP//////////////////fv4/+OcOP/xkgD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8ZIA/+OcOP/9+/j/////////////////xpxk/92GAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/14IA/97Kr//////////////////lxJf/644A//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/6o4A/+XEl//////////////////ey7D/14IA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/UgAD/7ubb/////////////////9iZQf/wkQD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/wkQD/15lB//////////////////Hr4v/TfwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA/9OAAP/59/P/////////////////zn8I//CRAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//CRAP/PgRH//////////////////Pv5/9N/AP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/14IA//z7+P/////////////////Cdwv/6o4A//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/6o4A/8J4DP/////////////////9+/n/14IA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/dhgD/9Ozi/////////////////7yIQP/chQD/8ZIA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//GSAP/chQD/vIhA//////////////////Ts4v/dhgD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA/+eMAP/p0bD/////////////////zreX/75zAP/liwD/8ZIA//OTAP/zkwD/85MA//OTAP/xkgD/5YsA/79zAv/Ot5f/////////////////6dGx/+eMAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JEA/+KtZP/////////////////8+/n/qHk5/75zAP/chQD/6o4A//CRAP/wkQD/6o4A/9yFAP++cwD/p3g4//v6+P/////////////////irWX/75EA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/544F//fw5f/////////////////w7OX/oXU4/6hnA//AdAH/zHsA/8x7AP/AdAH/qGcD/6F1OP/w7OX/////////////////9/Dm/+aOBv/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/wkQD/6K5d///////////////////////8+/n/xrKW/6B3Qf+QXRP/kF0T/6B3Qf/Gspb//Pv5///////////////////////orl3/8JEA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/ukAD/7saQ////////////////////////////////////////////////////////////////////////////7saQ/+6QAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/ukQD/78aQ/////////////////////////////////////////////////////////////////+/GkP/ukQD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/wkQD/7rBb//nt3v////////////////////////////////////////////nt3v/usVz/8JEA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/8JQF/++zX//z1az/+/Dh//78+f/9+fT/+ezZ//PVrP/ws17/8JQF//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwDx85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA8/OTAK3zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwCt85MAJ/OTAO/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA7/OTACfzkwAA85MAJfOTAK3zkwDx85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA//OTAP/zkwD/85MA8fOTAK3zkwAl85MAAIAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAB</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://suggests.go.mail.ru/ff3">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="gp" value="900200"/>
+</Url>
+<Url type="text/html" method="GET" template="https://go.mail.ru/search" resultdomain="mail.ru">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="fr" value="osmi"/>
+ <Param name="gp" value="900200"/>
+ <Param name="frc" value="900200"/>
+</Url>
+<SearchForm>https://go.mail.ru/?gp=900200</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/mapy-cz.xml b/browser/locales/searchplugins/mapy-cz.xml
new file mode 100644
index 000000000..4e7511997
--- /dev/null
+++ b/browser/locales/searchplugins/mapy-cz.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Mapy.cz</ShortName>
+<Description>Vyhledávání na Mapy.cz</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFqSURBVHjajJM9KIVRGMffe11fhdwM7mBQkpIwsPiaFIPFxCYzyWIxKiXDXSxmBimDbrJZiUEhkxADi6/7+lbX9Tv6v2+n05vrqV/vc855z/88H+fEHlqbPSwGLTALF7AOx14BSx6degn5eSiGUY1HoA9uIvaV6MBPM4hbC3fwLr8OJiM2m/8nYAFqXYG85ZfCgL62tcMYTMOyK1AmAquHYWd9HNo07nAFksrtC04gC0NaK4cpJy2TcljEImiS/6QQU5CGThiEOSedrC1gvt2WwJ6K9A37iuwafLXb2Iudgmlhjwp5qG7k4EybfVU/bUXwZgtUQ4MmNy3RlPw12IJXVyChExpV5UvYtnLMSGReczmlZQ7+CATiaokJfxee9fM9zOiA4I6YtUeokViYQr8mViJun33BbhVl8H5+BSqhC85hp8D7uVI3QgGTQi9UwGIQ1h/m66VWwUEgYIq0BKve/2xDm00tvB8BBgA9FU3+lToaKQAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="http://www.mapy.cz/" resultdomain="mapy.cz">
+ <Param name="query" value="{searchTerms}"/>
+ <Param name="sourceid" value="Searchmodule_3"/>
+</Url>
+<SearchForm>http://www.mapy.cz/</SearchForm>
+</SearchPlugin> \ No newline at end of file
diff --git a/browser/locales/searchplugins/marktplaats-fy-NL.xml b/browser/locales/searchplugins/marktplaats-fy-NL.xml
new file mode 100644
index 000000000..8ad4f61d3
--- /dev/null
+++ b/browser/locales/searchplugins/marktplaats-fy-NL.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Marktplaats.nl</ShortName>
+<Description>Sykje yn alle kategoryen op Marktplaats.nl</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABC0lEQVR42rSSoU7DUBSGv42FNKHiBtMrEE2KwnRysoIwP4UZ6SOQ9AW2B5hFN+BRCDZVSQKi9WtSeVFUVCwEgehdb0p2xQTHnfz/d3POf+6gyhYcU0OOrBGQpBSV7lcxod9zFBVJChBIZhOGrWk61rKq/z75mAFIwfKW6Xg/0s0eeH6j2Rm3qnXrCaTo7yAFUlAqSmWAp6zX9gBP4AmAh1cjr3PuIntKrVYq1rl2S8GZYwcCSSABPmuATc484lLaAdfRcptMUZn0rIebTXAdgOvFAfcBoJsKmEd2YKtodjryVWyC7k7ZqSf3cZSkvHzw1fC+5eqCcxcg9Al9kpRNzvePVk9HDP79t/4OALMxUpBz00CfAAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="GET" template="http://www.marktplaats.nl/z.html" resultdomain="marktplaats.nl">
+ <Param name="client" value="firefox-search"/>
+ <Param name="query" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.marktplaats.nl</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/marktplaats-nl.xml b/browser/locales/searchplugins/marktplaats-nl.xml
new file mode 100644
index 000000000..8d830cc8a
--- /dev/null
+++ b/browser/locales/searchplugins/marktplaats-nl.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Marktplaats.nl</ShortName>
+<Description>Zoeken in alle categorieën op Marktplaats.nl</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABC0lEQVR42rSSoU7DUBSGv42FNKHiBtMrEE2KwnRysoIwP4UZ6SOQ9AW2B5hFN+BRCDZVSQKi9WtSeVFUVCwEgehdb0p2xQTHnfz/d3POf+6gyhYcU0OOrBGQpBSV7lcxod9zFBVJChBIZhOGrWk61rKq/z75mAFIwfKW6Xg/0s0eeH6j2Rm3qnXrCaTo7yAFUlAqSmWAp6zX9gBP4AmAh1cjr3PuIntKrVYq1rl2S8GZYwcCSSABPmuATc484lLaAdfRcptMUZn0rIebTXAdgOvFAfcBoJsKmEd2YKtodjryVWyC7k7ZqSf3cZSkvHzw1fC+5eqCcxcg9Al9kpRNzvePVk9HDP79t/4OALMxUpBz00CfAAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="GET" template="http://www.marktplaats.nl/z.html" resultdomain="marktplaats.nl">
+ <Param name="client" value="firefox-search"/>
+ <Param name="query" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.marktplaats.nl</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/mercadolibre-ar.xml b/browser/locales/searchplugins/mercadolibre-ar.xml
new file mode 100644
index 000000000..e9f041528
--- /dev/null
+++ b/browser/locales/searchplugins/mercadolibre-ar.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>MercadoLibre Argentina</ShortName>
+<Description>MercadoLibre Argentina</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCfDxAAnw8QAJ8ODwCgDAwApgUELqkBAHqmBQSwowgJwaMICcGmBQSxqQEAe6cFBC+hDAwAnw4PAJ8PEACfDxAAnw8QAJ8PEACnBQM1ogkKs3w6Q/lOcoj/MJix/x6kwf8fpsT/LZq0/010iv98OkP5ogkKtKcEAzafDxAAnw8QAJ8PDwCoAwFMfzY/7yqkv/8A2///AN3//xW42/9bqr//UKG3/zSqxv8A1P7/ANz//ymkwP9+N0DwqAMBTp8ODwCqAQEddEVR6QfR8/8A4f//Dbrf/3ybq//hwsP/8bWy/+qlo//rq6n/nIyW/x+w0/8A4f//B9L0/3JHU+qqAQAflxgbjA7D5/8A5P//CbXZ/7SRlv//19T/68TE/9+wsP/+/Pz/9OLi//jFw//IkpT/IqfG/wDk//8Mxun/lhkck25JVsQDwuj/NabA/5Scp//utbP/5by9/+7V1v/////////////////t0tL/67Kw/6iYof9Kobr/Dbrb/2pNW8ecNTqszb7D//ja2f///fv///////js6//w2tr////////////26Oj/3qyt//Lh4f///////+bk/9zEx/+gOj2yohAPPfPW1vz////////+///v7f/nn53/6qyq/+a6uv/pxcX/5LSz///o5v//9/X///n2///////y2Nj8ohERQZcAAACvMTJ11ba5/5C1wv9Wp7//WJau/5aQm//nvLz/6bW0/97Bw/+Wq7f/Yqe8/3GxxP/Cqq//sjMzdpYAAACaAgMAnAAAAJINEVU9dYrSAsbr/wDf//8A1v//Hrvc/zW40/8Ov+P/ANL4/wHE6f84fJTSixAWVp4AAACaAQIAmgIDAJsFBgCjAgAAqgEACo4jKFJvSlWVUmuBvDyDndI6hZ7STm+EvW5LWJWOIypSqwAACqQCAQCdCAkAmgECAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//AAD//wAA8A8AAMADAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAAwAMAAOAHAAD//wAA//8AAP//AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PEJ8ODzufDg9fnw4PfKANDo6gDQ6YoA0OmKANDo6fDg99nw4PYJ8ODzyfDg8Rnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PIJ8OD26gDQ20owkJ46YFBPqoAwH/qAMB/6gDAf+oAwH/qAMB/6gDAf+nBQT6owgJ5KANDrafDg9xnw4PIZ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4POaANDq+mBgb6qAMC/54PEP+GLDP/bUtY/1tidP9Ocof/R3mR/0d5kf9Ocof/WmJ1/21LWf+GLTT/nhAR/6gDAv+mBgX6oA0Os58ODz2fDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PD58ODqGnBAP/ogoK/3g+Sf8/g53/FrXa/wHQ+v8A2f//ANz//wDf//8A3v//ANv//wDe//8A3f//ANr//wHR+/8Vttr/PoSe/3c/Sv+iCwv/pwQD/6ANDqefDg8Rnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8OECKjCQnWpQcH/21LWf8fqsz/ANj//wDb//8A1f//ANH8/wDU/v8A1///C6/V/wi64f8A0vv/BbXd/wHP9v8A3P//ANT+/wDU//8A2///ANj//x6rzv9rTVv/pAgH/6MJCdqfDhAmnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDxAcpgYF4JMdIf8tmbj/ANv//wDX//8A0Pv/AND6/wDU/v8A1///A7jg/0J2jv/JfH3/rHF2/39WYf/EfoD/flNf/0J1jv8OvuL/ANz//wDR/P8A0Pr/ANf//wDc//8rnLv/kh4j/6YFBOSfDxAfnw4PAJ8ODwCfDg8Anw4PAKYGBrmJKTD/E7vf/wDc//8A0Pr/AND6/wDQ+v8A1v//Bbfe/0Fyiv/BcnT/1IOD///////14N//0X18/+/V1P/Vf37//87L/4pJUf8gj6//A8vy/wDT/f8A0Pr/AND6/wDc//8SveH/hysz/6YGBb6fDhAAnw4PAJ8ODwCjCgpXkx0h/xO94f8A2f//AND6/wDQ+v8A0vz/ANz//y2Orf/CcXL/35iX///9/f//////7tPT/9CIif/aoaL/5Lq7///////jurr/0Hl4/++opf9uZ3j/ANT//wDW/v8Az/r/AND6/wDZ//8RwOT/kR8k/6MJCVyfDg8AogoKJKYFBL42kaz/ANz//wDQ+v8A0Pr/ANL8/wXG7/9pa3z/slVY///19P////////////////+7WFn/366v////////////79bW/9CIif///////vj4/7dJSf+DWWX/IabH/wDV//8A0Pr/AND6/wDc//8zlLD/pgYFwqMJCTKoAwJggTM78QDV/f8A0fv/AND6/wDQ+v8A3///NIqi//S1sv/9/Pz/9ebm///////Sjo//z4aG/9KOjv/////////////////58fH//fv7//rx8f/GcXH/7NHS///x7v9+Xmn/AN3//wDS/P8A0Pr/ANH7/wDW//9+Nj/yqAMCbqILC4hVan7/AN7//wDV//8A2v//ANX//wus0v9tUV//z3Fw/82Cgv++XF3/xm9w/9aXmP/s0ND//fv7///////////////////////+/Pz/0YuM//DY2P//////2ZKS/4w8Q/8jj6//AMPt/wDa//8A2f//AOD//1Jugv+dEBGRmRYYk0CIn/8A1P//D6jN/zh8lv97X2v/x35///va2f/y3+D/7tTU//36+v/58fH///////////////////////////////////////36+v/68vP//v38/8l3eP/Wm5v//+/v/+Ofnv+lZWv/Y2Bx/ymJp/8Avuf/Oomj/5EhJZmbEROFg0BJ/5ZocP/KfX7/77Wz///w7v////////////////////////////////////////////////////////////////////////////nw8P/CZmf/362u///////////////////////82tf/45ua/7Zucf+UP0X/mhMWjpwDBFLCW1vp///////////////////////////////////////////+/f3/47i4/9WWl//v19f////////////////////////////x3Nz/v15f/+nHx////////////////////////////////////////////8ZoaOqeBwhhmwMEEKUeH5726ur//////////////////////////////////////9ujpP/Gb3D/6cjI/8Rra//YnJz/////////////////58PD/79eXv/y4OD////////////////////////////////////////////37Oz/piAhopoBAhufDQ4AmgECJ8VvcPT//////////////////////////////////////PPy/8t4ef/y39///////9OSk//EbGz/4bS0/9CJiv/Hc3P/+fDw/////////////////////////////////////////////////8d0dPWaAQIqngwNAJ8ODwCfDQ4AmgMEYtecnf////////////7g3v/mpaP/vnh6/5dncP+ebnb/wnBx/7kuLf/419b///////Hb2//UlJT/5b29/////////////d3a/+OhoP/Ihoj/v3+B/8yJiv/np6b//uPh///////YoaL/mwQFZZ8NDgCfDg8Anw4PAJ8ODwCeDA0AnQoLb818fP+nfYT/Xmx//yqJp/8Hs9r/AMr1/wDI8v8QrdL/LIyq/3Jbaf/dkpH//dfV///z8f//5OL/77Kw/7d0eP9gZnj/JJCv/wut0v8HtNr/DqnO/yWQr/9lanz/u1dY/6APEHKeCwwAnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDQ4AngAASW45RucNv+T/AOb//wDa//8A0v3/ANH7/wDV//8A2v//ANP7/x6Yuf9QcIX/a2d3/1xqff8xhKD/CLfe/wDa//8A2P//ANX//wDa//8A4f//DsHn/2RQYOmdAQFLnwwNAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDhAApgYFEp0QEoxgW23wHqzO/wHT+/8A3P//ANn//wDV//8A0/7/ANj//wDb//8A2f//ANv//wDc//8A2P//ANn//wDc//8B0/z/HazP/15db/GdEROOpwQDE58ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8AoAwNAKgDAhOiCgptgzA4xVhlePYxlLH/F7TY/wrE6/8DzPb/ANH6/wDR+v8Dzfb/CsTs/xe02P8wlbP/V2Z694IxOseiCgttqAMCFKEMDACfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODgCkCAgAqAMBGKcEA0ueDxB2lRodmosmLLOFLja/hS42v4smLLOVGh6ang8Qd6cFBEypAgEZpAgIAJ8NDgCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//////////////////////wAP//wAA//wAAD/wAAAP4AAAB8AAAAPAAAADgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAAAA+AAAAfwAAAP+AAAH/4AAH//wAP/////////////////////////////////</Image>
+<Url type="text/html" method="GET" template="http://www.mercadolibre.com.ar/jm/search" resultdomain="mercadolibre.com.ar">
+ <Param name="as_word" value="{searchTerms}"/>
+ <Param name="source" value="firefox_box"/>
+</Url>
+<SearchForm>http://www.mercadolibre.com.ar/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/mercadolibre-cl.xml b/browser/locales/searchplugins/mercadolibre-cl.xml
new file mode 100644
index 000000000..17a654c55
--- /dev/null
+++ b/browser/locales/searchplugins/mercadolibre-cl.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>MercadoLibre Chile</ShortName>
+<Description>MercadoLibre Chile</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCfDxAAnw8QAJ8ODwCgDAwApgUELqkBAHqmBQSwowgJwaMICcGmBQSxqQEAe6cFBC+hDAwAnw4PAJ8PEACfDxAAnw8QAJ8PEACnBQM1ogkKs3w6Q/lOcoj/MJix/x6kwf8fpsT/LZq0/010iv98OkP5ogkKtKcEAzafDxAAnw8QAJ8PDwCoAwFMfzY/7yqkv/8A2///AN3//xW42/9bqr//UKG3/zSqxv8A1P7/ANz//ymkwP9+N0DwqAMBTp8ODwCqAQEddEVR6QfR8/8A4f//Dbrf/3ybq//hwsP/8bWy/+qlo//rq6n/nIyW/x+w0/8A4f//B9L0/3JHU+qqAQAflxgbjA7D5/8A5P//CbXZ/7SRlv//19T/68TE/9+wsP/+/Pz/9OLi//jFw//IkpT/IqfG/wDk//8Mxun/lhkck25JVsQDwuj/NabA/5Scp//utbP/5by9/+7V1v/////////////////t0tL/67Kw/6iYof9Kobr/Dbrb/2pNW8ecNTqszb7D//ja2f///fv///////js6//w2tr////////////26Oj/3qyt//Lh4f///////+bk/9zEx/+gOj2yohAPPfPW1vz////////+///v7f/nn53/6qyq/+a6uv/pxcX/5LSz///o5v//9/X///n2///////y2Nj8ohERQZcAAACvMTJ11ba5/5C1wv9Wp7//WJau/5aQm//nvLz/6bW0/97Bw/+Wq7f/Yqe8/3GxxP/Cqq//sjMzdpYAAACaAgMAnAAAAJINEVU9dYrSAsbr/wDf//8A1v//Hrvc/zW40/8Ov+P/ANL4/wHE6f84fJTSixAWVp4AAACaAQIAmgIDAJsFBgCjAgAAqgEACo4jKFJvSlWVUmuBvDyDndI6hZ7STm+EvW5LWJWOIypSqwAACqQCAQCdCAkAmgECAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//AAD//wAA8A8AAMADAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAAwAMAAOAHAAD//wAA//8AAP//AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PEJ8ODzufDg9fnw4PfKANDo6gDQ6YoA0OmKANDo6fDg99nw4PYJ8ODzyfDg8Rnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PIJ8OD26gDQ20owkJ46YFBPqoAwH/qAMB/6gDAf+oAwH/qAMB/6gDAf+nBQT6owgJ5KANDrafDg9xnw4PIZ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4POaANDq+mBgb6qAMC/54PEP+GLDP/bUtY/1tidP9Ocof/R3mR/0d5kf9Ocof/WmJ1/21LWf+GLTT/nhAR/6gDAv+mBgX6oA0Os58ODz2fDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PD58ODqGnBAP/ogoK/3g+Sf8/g53/FrXa/wHQ+v8A2f//ANz//wDf//8A3v//ANv//wDe//8A3f//ANr//wHR+/8Vttr/PoSe/3c/Sv+iCwv/pwQD/6ANDqefDg8Rnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8OECKjCQnWpQcH/21LWf8fqsz/ANj//wDb//8A1f//ANH8/wDU/v8A1///C6/V/wi64f8A0vv/BbXd/wHP9v8A3P//ANT+/wDU//8A2///ANj//x6rzv9rTVv/pAgH/6MJCdqfDhAmnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDxAcpgYF4JMdIf8tmbj/ANv//wDX//8A0Pv/AND6/wDU/v8A1///A7jg/0J2jv/JfH3/rHF2/39WYf/EfoD/flNf/0J1jv8OvuL/ANz//wDR/P8A0Pr/ANf//wDc//8rnLv/kh4j/6YFBOSfDxAfnw4PAJ8ODwCfDg8Anw4PAKYGBrmJKTD/E7vf/wDc//8A0Pr/AND6/wDQ+v8A1v//Bbfe/0Fyiv/BcnT/1IOD///////14N//0X18/+/V1P/Vf37//87L/4pJUf8gj6//A8vy/wDT/f8A0Pr/AND6/wDc//8SveH/hysz/6YGBb6fDhAAnw4PAJ8ODwCjCgpXkx0h/xO94f8A2f//AND6/wDQ+v8A0vz/ANz//y2Orf/CcXL/35iX///9/f//////7tPT/9CIif/aoaL/5Lq7///////jurr/0Hl4/++opf9uZ3j/ANT//wDW/v8Az/r/AND6/wDZ//8RwOT/kR8k/6MJCVyfDg8AogoKJKYFBL42kaz/ANz//wDQ+v8A0Pr/ANL8/wXG7/9pa3z/slVY///19P////////////////+7WFn/366v////////////79bW/9CIif///////vj4/7dJSf+DWWX/IabH/wDV//8A0Pr/AND6/wDc//8zlLD/pgYFwqMJCTKoAwJggTM78QDV/f8A0fv/AND6/wDQ+v8A3///NIqi//S1sv/9/Pz/9ebm///////Sjo//z4aG/9KOjv/////////////////58fH//fv7//rx8f/GcXH/7NHS///x7v9+Xmn/AN3//wDS/P8A0Pr/ANH7/wDW//9+Nj/yqAMCbqILC4hVan7/AN7//wDV//8A2v//ANX//wus0v9tUV//z3Fw/82Cgv++XF3/xm9w/9aXmP/s0ND//fv7///////////////////////+/Pz/0YuM//DY2P//////2ZKS/4w8Q/8jj6//AMPt/wDa//8A2f//AOD//1Jugv+dEBGRmRYYk0CIn/8A1P//D6jN/zh8lv97X2v/x35///va2f/y3+D/7tTU//36+v/58fH///////////////////////////////////////36+v/68vP//v38/8l3eP/Wm5v//+/v/+Ofnv+lZWv/Y2Bx/ymJp/8Avuf/Oomj/5EhJZmbEROFg0BJ/5ZocP/KfX7/77Wz///w7v////////////////////////////////////////////////////////////////////////////nw8P/CZmf/362u///////////////////////82tf/45ua/7Zucf+UP0X/mhMWjpwDBFLCW1vp///////////////////////////////////////////+/f3/47i4/9WWl//v19f////////////////////////////x3Nz/v15f/+nHx////////////////////////////////////////////8ZoaOqeBwhhmwMEEKUeH5726ur//////////////////////////////////////9ujpP/Gb3D/6cjI/8Rra//YnJz/////////////////58PD/79eXv/y4OD////////////////////////////////////////////37Oz/piAhopoBAhufDQ4AmgECJ8VvcPT//////////////////////////////////////PPy/8t4ef/y39///////9OSk//EbGz/4bS0/9CJiv/Hc3P/+fDw/////////////////////////////////////////////////8d0dPWaAQIqngwNAJ8ODwCfDQ4AmgMEYtecnf////////////7g3v/mpaP/vnh6/5dncP+ebnb/wnBx/7kuLf/419b///////Hb2//UlJT/5b29/////////////d3a/+OhoP/Ihoj/v3+B/8yJiv/np6b//uPh///////YoaL/mwQFZZ8NDgCfDg8Anw4PAJ8ODwCeDA0AnQoLb818fP+nfYT/Xmx//yqJp/8Hs9r/AMr1/wDI8v8QrdL/LIyq/3Jbaf/dkpH//dfV///z8f//5OL/77Kw/7d0eP9gZnj/JJCv/wut0v8HtNr/DqnO/yWQr/9lanz/u1dY/6APEHKeCwwAnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDQ4AngAASW45RucNv+T/AOb//wDa//8A0v3/ANH7/wDV//8A2v//ANP7/x6Yuf9QcIX/a2d3/1xqff8xhKD/CLfe/wDa//8A2P//ANX//wDa//8A4f//DsHn/2RQYOmdAQFLnwwNAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDhAApgYFEp0QEoxgW23wHqzO/wHT+/8A3P//ANn//wDV//8A0/7/ANj//wDb//8A2f//ANv//wDc//8A2P//ANn//wDc//8B0/z/HazP/15db/GdEROOpwQDE58ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8AoAwNAKgDAhOiCgptgzA4xVhlePYxlLH/F7TY/wrE6/8DzPb/ANH6/wDR+v8Dzfb/CsTs/xe02P8wlbP/V2Z694IxOseiCgttqAMCFKEMDACfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODgCkCAgAqAMBGKcEA0ueDxB2lRodmosmLLOFLja/hS42v4smLLOVGh6ang8Qd6cFBEypAgEZpAgIAJ8NDgCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//////////////////////wAP//wAA//wAAD/wAAAP4AAAB8AAAAPAAAADgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAAAA+AAAAfwAAAP+AAAH/4AAH//wAP/////////////////////////////////</Image>
+<Url type="text/html" method="GET" template="http://www.mercadolibre.cl/jm/search" resultdomain="mercadolibre.cl">
+ <Param name="as_word" value="{searchTerms}"/>
+ <Param name="source" value="firefox_box"/>
+</Url>
+<SearchForm>http://www.mercadolibre.cl/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/mercadolibre-mx.xml b/browser/locales/searchplugins/mercadolibre-mx.xml
new file mode 100644
index 000000000..49dd89e18
--- /dev/null
+++ b/browser/locales/searchplugins/mercadolibre-mx.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>MercadoLibre Mexico</ShortName>
+<Description>MercadoLibre Mexico</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCfDxAAnw8QAJ8ODwCgDAwApgUELqkBAHqmBQSwowgJwaMICcGmBQSxqQEAe6cFBC+hDAwAnw4PAJ8PEACfDxAAnw8QAJ8PEACnBQM1ogkKs3w6Q/lOcoj/MJix/x6kwf8fpsT/LZq0/010iv98OkP5ogkKtKcEAzafDxAAnw8QAJ8PDwCoAwFMfzY/7yqkv/8A2///AN3//xW42/9bqr//UKG3/zSqxv8A1P7/ANz//ymkwP9+N0DwqAMBTp8ODwCqAQEddEVR6QfR8/8A4f//Dbrf/3ybq//hwsP/8bWy/+qlo//rq6n/nIyW/x+w0/8A4f//B9L0/3JHU+qqAQAflxgbjA7D5/8A5P//CbXZ/7SRlv//19T/68TE/9+wsP/+/Pz/9OLi//jFw//IkpT/IqfG/wDk//8Mxun/lhkck25JVsQDwuj/NabA/5Scp//utbP/5by9/+7V1v/////////////////t0tL/67Kw/6iYof9Kobr/Dbrb/2pNW8ecNTqszb7D//ja2f///fv///////js6//w2tr////////////26Oj/3qyt//Lh4f///////+bk/9zEx/+gOj2yohAPPfPW1vz////////+///v7f/nn53/6qyq/+a6uv/pxcX/5LSz///o5v//9/X///n2///////y2Nj8ohERQZcAAACvMTJ11ba5/5C1wv9Wp7//WJau/5aQm//nvLz/6bW0/97Bw/+Wq7f/Yqe8/3GxxP/Cqq//sjMzdpYAAACaAgMAnAAAAJINEVU9dYrSAsbr/wDf//8A1v//Hrvc/zW40/8Ov+P/ANL4/wHE6f84fJTSixAWVp4AAACaAQIAmgIDAJsFBgCjAgAAqgEACo4jKFJvSlWVUmuBvDyDndI6hZ7STm+EvW5LWJWOIypSqwAACqQCAQCdCAkAmgECAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//AAD//wAA8A8AAMADAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAAwAMAAOAHAAD//wAA//8AAP//AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PEJ8ODzufDg9fnw4PfKANDo6gDQ6YoA0OmKANDo6fDg99nw4PYJ8ODzyfDg8Rnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PIJ8OD26gDQ20owkJ46YFBPqoAwH/qAMB/6gDAf+oAwH/qAMB/6gDAf+nBQT6owgJ5KANDrafDg9xnw4PIZ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4POaANDq+mBgb6qAMC/54PEP+GLDP/bUtY/1tidP9Ocof/R3mR/0d5kf9Ocof/WmJ1/21LWf+GLTT/nhAR/6gDAv+mBgX6oA0Os58ODz2fDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PD58ODqGnBAP/ogoK/3g+Sf8/g53/FrXa/wHQ+v8A2f//ANz//wDf//8A3v//ANv//wDe//8A3f//ANr//wHR+/8Vttr/PoSe/3c/Sv+iCwv/pwQD/6ANDqefDg8Rnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8OECKjCQnWpQcH/21LWf8fqsz/ANj//wDb//8A1f//ANH8/wDU/v8A1///C6/V/wi64f8A0vv/BbXd/wHP9v8A3P//ANT+/wDU//8A2///ANj//x6rzv9rTVv/pAgH/6MJCdqfDhAmnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDxAcpgYF4JMdIf8tmbj/ANv//wDX//8A0Pv/AND6/wDU/v8A1///A7jg/0J2jv/JfH3/rHF2/39WYf/EfoD/flNf/0J1jv8OvuL/ANz//wDR/P8A0Pr/ANf//wDc//8rnLv/kh4j/6YFBOSfDxAfnw4PAJ8ODwCfDg8Anw4PAKYGBrmJKTD/E7vf/wDc//8A0Pr/AND6/wDQ+v8A1v//Bbfe/0Fyiv/BcnT/1IOD///////14N//0X18/+/V1P/Vf37//87L/4pJUf8gj6//A8vy/wDT/f8A0Pr/AND6/wDc//8SveH/hysz/6YGBb6fDhAAnw4PAJ8ODwCjCgpXkx0h/xO94f8A2f//AND6/wDQ+v8A0vz/ANz//y2Orf/CcXL/35iX///9/f//////7tPT/9CIif/aoaL/5Lq7///////jurr/0Hl4/++opf9uZ3j/ANT//wDW/v8Az/r/AND6/wDZ//8RwOT/kR8k/6MJCVyfDg8AogoKJKYFBL42kaz/ANz//wDQ+v8A0Pr/ANL8/wXG7/9pa3z/slVY///19P////////////////+7WFn/366v////////////79bW/9CIif///////vj4/7dJSf+DWWX/IabH/wDV//8A0Pr/AND6/wDc//8zlLD/pgYFwqMJCTKoAwJggTM78QDV/f8A0fv/AND6/wDQ+v8A3///NIqi//S1sv/9/Pz/9ebm///////Sjo//z4aG/9KOjv/////////////////58fH//fv7//rx8f/GcXH/7NHS///x7v9+Xmn/AN3//wDS/P8A0Pr/ANH7/wDW//9+Nj/yqAMCbqILC4hVan7/AN7//wDV//8A2v//ANX//wus0v9tUV//z3Fw/82Cgv++XF3/xm9w/9aXmP/s0ND//fv7///////////////////////+/Pz/0YuM//DY2P//////2ZKS/4w8Q/8jj6//AMPt/wDa//8A2f//AOD//1Jugv+dEBGRmRYYk0CIn/8A1P//D6jN/zh8lv97X2v/x35///va2f/y3+D/7tTU//36+v/58fH///////////////////////////////////////36+v/68vP//v38/8l3eP/Wm5v//+/v/+Ofnv+lZWv/Y2Bx/ymJp/8Avuf/Oomj/5EhJZmbEROFg0BJ/5ZocP/KfX7/77Wz///w7v////////////////////////////////////////////////////////////////////////////nw8P/CZmf/362u///////////////////////82tf/45ua/7Zucf+UP0X/mhMWjpwDBFLCW1vp///////////////////////////////////////////+/f3/47i4/9WWl//v19f////////////////////////////x3Nz/v15f/+nHx////////////////////////////////////////////8ZoaOqeBwhhmwMEEKUeH5726ur//////////////////////////////////////9ujpP/Gb3D/6cjI/8Rra//YnJz/////////////////58PD/79eXv/y4OD////////////////////////////////////////////37Oz/piAhopoBAhufDQ4AmgECJ8VvcPT//////////////////////////////////////PPy/8t4ef/y39///////9OSk//EbGz/4bS0/9CJiv/Hc3P/+fDw/////////////////////////////////////////////////8d0dPWaAQIqngwNAJ8ODwCfDQ4AmgMEYtecnf////////////7g3v/mpaP/vnh6/5dncP+ebnb/wnBx/7kuLf/419b///////Hb2//UlJT/5b29/////////////d3a/+OhoP/Ihoj/v3+B/8yJiv/np6b//uPh///////YoaL/mwQFZZ8NDgCfDg8Anw4PAJ8ODwCeDA0AnQoLb818fP+nfYT/Xmx//yqJp/8Hs9r/AMr1/wDI8v8QrdL/LIyq/3Jbaf/dkpH//dfV///z8f//5OL/77Kw/7d0eP9gZnj/JJCv/wut0v8HtNr/DqnO/yWQr/9lanz/u1dY/6APEHKeCwwAnw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDQ4AngAASW45RucNv+T/AOb//wDa//8A0v3/ANH7/wDV//8A2v//ANP7/x6Yuf9QcIX/a2d3/1xqff8xhKD/CLfe/wDa//8A2P//ANX//wDa//8A4f//DsHn/2RQYOmdAQFLnwwNAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDhAApgYFEp0QEoxgW23wHqzO/wHT+/8A3P//ANn//wDV//8A0/7/ANj//wDb//8A2f//ANv//wDc//8A2P//ANn//wDc//8B0/z/HazP/15db/GdEROOpwQDE58ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8AoAwNAKgDAhOiCgptgzA4xVhlePYxlLH/F7TY/wrE6/8DzPb/ANH6/wDR+v8Dzfb/CsTs/xe02P8wlbP/V2Z694IxOseiCgttqAMCFKEMDACfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODgCkCAgAqAMBGKcEA0ueDxB2lRodmosmLLOFLja/hS42v4smLLOVGh6ang8Qd6cFBEypAgEZpAgIAJ8NDgCfDg8Anw4PAJ8ODwCfDg8Anw4PAJ8ODwCfDg8Anw4PAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP//////////////////////wAP//wAA//wAAD/wAAAP4AAAB8AAAAPAAAADgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAAAA+AAAAfwAAAP+AAAH/4AAH//wAP/////////////////////////////////</Image>
+<Url type="text/html" method="GET" template="http://www.mercadolibre.com.mx/jm/search" resultdomain="mercadolibre.com.mx">
+ <Param name="as_word" value="{searchTerms}"/>
+ <Param name="source" value="firefox_box"/>
+</Url>
+<SearchForm>http://www.mercadolibre.com.mx/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/mercadolivre.xml b/browser/locales/searchplugins/mercadolivre.xml
new file mode 100644
index 000000000..28be3c307
--- /dev/null
+++ b/browser/locales/searchplugins/mercadolivre.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>MercadoLivre</ShortName>
+<Description>Onde comprar e vender de Tudo.</Description>
+<InputEncoding>iso-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAABnRSTlMAAAAAAABupgeRAAABnklEQVR4nGNgGPqA8f///wwMDP///7199/2Js28fPvspI/b3758P////Y2IWfPiCjYeX1d9ZREWRj5mFlYGBgeH///+XrrycMONSZtk5LeOw//8Z0FBVuYSm1eaqljNHTjz6//8/4/lLL1asedTRaPr3H8P+A7vF+dM+fDBk5Cj+8OkDL8ctxr+b/zDYCktU6euwF1WfDPaVYsgsPnL+0vPnz/7+RwUXrvw/fALB/fXt/8sX3+z9N7F8/PznyOkPxzdpJMSp/WXtklV3EORjkJJg0NdmOHb624rNXAL/lgmy1967fe8h6/nnr38y8QmyLlj5nFk8zDXotIe04945NacuMhw7x/DkE8Odp1yP95R46Eabe9z7yKy3fstHNSUhhj9/fzoG7mBgWJCcZAD36OalfVuXNJ7dwAvhtjYqMjDMEtVe9+HjVwaIE2cvvcjAuI6BqSIzT3DLCoZdyxj2rWLYvpqhqJSfgSuHgWFVXc9piEpG5Eh58Oz/zj2fn7x79erN+1+//3NzcKtKSznYC+ipMTAwMDAyMmKJyEEAAHOj6iZS/gfAAAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="GET" template="http://pmstrk.mercadolivre.com.br/jm/PmsTrk" resultdomain="mercadolivre.com.br">
+ <Param name="tool" value="5668605"/>
+ <Param name="go" value="/jm/search%3fas_word={searchTerms}%26as_search_both=N"/>
+</Url>
+<SearchForm>http://www.mercadolivre.com.br/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/meta-ua.xml b/browser/locales/searchplugins/meta-ua.xml
new file mode 100644
index 000000000..1392dc098
--- /dev/null
+++ b/browser/locales/searchplugins/meta-ua.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>&lt;META&gt;</ShortName>
+<Description>Українська пошукова система.</Description>
+<InputEncoding>windows-1251</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABgUExURUJicNDy9Oz19AJ2thGF1YvN9Cqa87bm9Eax9C2Uzv/eH2u36wBsqwJ/xHjG812686WGCjSi9ESi26je9MadABqN4JyyxBiAunV2VA5ZmP/1hCeDwCNmj3Ogwhd/vP///4N3QeoAAAAgdFJOU/////////////////////////////////////////8AXFwb7QAAAKZJREFUeNpEj+sWgyAMgykKFE3FG5ub23j/t1zRedZfzXcgaUypA8wo55hD72R7/AF6a4mlCPADROQ4i6WIE3yWkNhHCi7D6MOhb5g5D2FgFiP6n8LdbK9F9QQTLYUlGfscuepiEFTz1nXr7nNNQRq4jbdx3aybW9GUxstsyb3VK3FNKXgsuqqXUxccd9S8JiW+QFNX71H1eelUAbQzrrYQuep/BRgAuVcP4GB+AzgAAAAASUVORK5CYII=</Image>
+<SearchForm>http://search.meta.ua/</SearchForm>
+<Url type="text/html" method="GET" template="http://meta.ua/search.asp" resultdomain="meta.ua">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<Url type="application/x-suggestions+json" method="GET" template="http://meta.ua/suggestions/">
+ <Param name="output" value="fxjson"/>
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-suggestqueries"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/metamarket.xml b/browser/locales/searchplugins/metamarket.xml
new file mode 100644
index 000000000..8e3d2d69c
--- /dev/null
+++ b/browser/locales/searchplugins/metamarket.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>МетаМаркет</ShortName>
+<Description>МетаМаркет - пошук товарів.</Description>
+<InputEncoding>windows-1251</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABgUExURQCZ/wCJ//+sAKnq/1jN/wC5/4/X/wCn/wDi/wDW/wDK/47n/1Xo/wBi//+VAP/AAAB4//r+/wtp9Rfp/4/x/wtt/xfR/xe+/xei/8f3/wDo/wAzmf/MAP9mAP///////4Gaq4UAAAAgdFJOU/////////////////////////////////////////8AXFwb7QAAAJVJREFUeNo8zYsOgyAMQNE6HuJrAwQnTMv//+VaQW8CaU9IgFLLHA/A13HGHGN84DzKL/OpcB6enxtjBuqCJed1DiLM2zgyfJa8BoeIbtoqeDM7RWCdvCEkpdBanBTDyw8iAUJaUDQYp2QTEui+gUwWCZxsoL6SfklO78Dw9opECy13aNBTQHVd90BdLyjlB/dK8BdgAKQCEpRJB2uO</Image>
+<SearchForm>http://metamarket.ua/</SearchForm>
+<Url type="text/html" method="GET" template="http://metamarket.ua/ua/search/" resultdomain="metamarket.ua">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="m" value="market"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/morfix-dic.xml b/browser/locales/searchplugins/morfix-dic.xml
new file mode 100644
index 000000000..e5572475f
--- /dev/null
+++ b/browser/locales/searchplugins/morfix-dic.xml
@@ -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/. -->
+
+<!-- milon.morfix.co.il MozSearch plugin
+ Compatible with Mozilla Firefox 2+
+ Tomer Cohen, September 2006 - http://mozilla.org.il -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>מילון מורפיקס</ShortName>
+ <Description></Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+g
+vaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gkdCRktdt52QwAAACR0
+RVh0Q29tbWVudABUb21lciBDb2hlbgp0b21lcmNAZ21haWwuY29tUau7swAAAxxJ
+REFUOMttk91rm2UYxq/7eZ/3fZO8SZqlcflct2at/YjokDJBPNAyHW4Hgw31wP9B
+kP0Bngw2QWHsxFOPdjJPhG1MEaqIddOWDbqxOmrStE3atGk+lrxJ3o/neTxYtlXx
+hpv74Oa64Oa+foQDpZQCAALAAPDhJADiQCsieqGh/4g5gCCAmO85cdFvhZVUJM0R
+O2AGGsTQJFAPgP/chA6IdQBxr7MzIde+m9Vqi+OsUxwl4ULo0dZecK4kXvvsYTqb
+faJpWuO5CQeAXrerBa1QzKuvnmD3L79n7P55grqVDKRrASAO6qfZcmV/69bY2utf
+BkLH37jfdBpNAJKqb09TMZM2kl9cyR+tfnNB37pzBv3GEQCaAgQ9O8sA4ChQ8fF+
+9IdL5vTNTlquCiUG2sUwo+ZGVTemvUzSXniX7NqbAPi+jcqDCisZnLXCpjIARAkI
+jZiOXyw1d5YIVWn6fQYACsCxSFUjuxYFoA181H4pmUsL1uc//n7k65/7RvoJABtA
+iGvInrS8XGptKjxWn2EcAJ6mQiogGgTpAkC372GjTIWVs+c/fliYmR11/thN4dFX
+efiuxYBgBH7o7NxpY/7UB8QBQHZcLJRT0zFnaqrbE/H1QWyCItnruUzW1Q2DfCsB
+MAOAKxXg+hKOyTX/cDKp+A1KKxUPQ7fzj47K1pzrdNHCYeTe+vSiETAPOY1iRt+7
+Ow7hWgAcV2Bvs2PVzEjcJkaSz9+4Tp7jUDqTIfb420uB7dtzuqoaJq721E/XFEkn
+Rp1yGsLVQahtNdVff4vJ4ju5XDcSjChemJkBAO5sL0eM+vejrL2SgvQPvUypYlBK
+Athp2Hhwd8u6xyfPrR8fHx+AoPgwksrr7Qv96UYPwm0PGWAApFQYeAL17bZa/W3T
+uree+GT59Pz79WQyKRhj4MMvuvKVk5srzfyi2N3dAxAeGvgKsEvt4HZZ5Ivs1Y/W
+z5z6sD5bKDicc3WQBVJSBsqVamxp8ddou93ShzsJYo5hjdj5Y2OdyYmJQSKREJqm
+qf+jkQAw3/eZEPIlrgTFGJOapiki+hfKAPAPFp9sbyN2GzAAAAAASUVORK5CYII=
+</Image>
+ <Url type="text/html" method="GET" template="http://milon.morfix.co.il/default.aspx" resultdomain="morfix.co.il">
+ <Param name="q" value="{searchTerms}"/>
+ </Url>
+ <SearchForm>http://milon.morfix.co.il/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/najdi-si.xml b/browser/locales/searchplugins/najdi-si.xml
new file mode 100644
index 000000000..2e396c714
--- /dev/null
+++ b/browser/locales/searchplugins/najdi-si.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Najdi.si</ShortName>
+<Description>Iskalnik Najdi.si</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABSlBMVEUAO1QQR148aXxghZVhhZUQR19Xfo7O2d75+vv////6+/vP2t9Yf49wkZ/4+vrk6+19m6dCboBCbYB7mqfj6uxzk6FEcIL2+PnC0NYgU2kMRFweUmjAztT29/hGcYMEPle0xs3z9vchVWqEoKzN2d5+nKkfU2ny9fa3x88FP1cAOlMfUmjl6+2ovMV9m6jE0dfc5OeetL2lucLm7O4vXnP19/h8maYFPlcmWG3V3uN7mKX3+PkwX3QqW2/x9PWLprEkVmzQ299/namIoq7U3uLY4eU8aHxtj50YTGMEPVbQ2t/V3+MRSF9+m6j9/v73+foDPVULQlpuj51IcoNoipn+/f6DoKwKQlp2lKGjucFNdocMQ1t6mKXR2+AUSmGkucLBz9Xv8/T8/f3G09kaT2WJpK/U3eK0xc1jh5YNQ1sCO1QORFwNRFwBO1TzxcIvAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YJCQkmNJUQNHcAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAL5JREFUGNNjYMAKGJmYWZhYEXw2dg5OTi5uHhifl49fQFBIWIRDFMIXE5eQlAICaRlZORBfXkFRSVkFCFTV1DU0GRi0tHV0tfT0DTg5DbWMjCW1GLRMTM20zC0sQQJW1jZAAVs7ey0HRw4nO0MtZztboACji6sbu7i7h52hp5e3D1BAy9fPX9w9IDAoOCQ0TAskEB4RGeWppRUdoxgbBxYAgfgEicSk5BQtuECqS1p6RqYWQiArOycXwoIKIAAAP8Amx8T+6hgAAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="http://www.najdi.si/search.jsp" resultdomain="najdi.si">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="o" value="0"/>
+ <Param name="foxsbar" value="ff"/>
+</Url>
+<SearchForm>http://www.najdi.si/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/naver-kr.xml b/browser/locales/searchplugins/naver-kr.xml
new file mode 100644
index 000000000..158f9f269
--- /dev/null
+++ b/browser/locales/searchplugins/naver-kr.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>네이버</ShortName>
+<Description>네이버 검색</Description>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAICAAAAEAIACoEAAAJgAAABAQAAABACAAaAQAAM4QAAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAMgdEADIHb8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgdvwDIHRAAyB2/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgdvwDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf////////////////////////////////8/1lX/AMgd/wDIHf8AyB3/AMgd/8/11f////////////////////////////////8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/////////////////////////////////z/WVf8AyB3/AMgd/wDIHf9/5I7//////////////////////////////////////wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/////////////////////////////////P9ZV/wDIHf8AyB3/P9ZV////////////////////////////////////////////AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf////////////////////////////////8/1lX/AMgd/xDLK//f+OP///////////////////////////////////////////8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/////////////////////////////////z/WVf8AyB3/nuqp/////////////////////////////////////////////////wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/////////////////////////////////P9ZV/0/ZZP//////////////////////////////////////////////////////AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf////////////////////////////////9P2WT/7/zx//////////////////////////////////////////////////////8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/////////////////////////////////+/88f///////////////////////////////////////////////////////////wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3////////////////////////////////////////////////////////////P9dX/////////////////////////////////AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf//////////////////////////////////////////////////////z/XV/0/ZZP////////////////////////////////8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd//////////////////////////////////////////////////////8v0kf/P9ZV/////////////////////////////////wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/////////////////////////////////////////////////b+CA/wDIHf8/1lX/////////////////////////////////AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf///////////////////////////////////////////77xx/8AyB3/AMgd/z/WVf////////////////////////////////8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd///////////////////////////////////////v/PH/H885/wDIHf8AyB3/P9ZV/////////////////////////////////wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3//////////////////////////////////////0/ZZP8AyB3/AMgd/wDIHf8/1lX/////////////////////////////////AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf////////////////////////////////+u7rf/AMgd/wDIHf8AyB3/AMgd/z/WVf////////////////////////////////8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgdvwDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHb8AyB0QAMgdvwDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB2/AMgdEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAABAAAAAgAAAAAQAgAAAAAABABAAAAAAAAAAAAAAAAAAAAAAAAADIHYAAyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHYAAyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf9/5I7///////////++8cf/AMgd/z/WVf////////////////9/5I7/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/f+SO////////////vvHH/wDIHf/f+OP/////////////////f+SO/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/3/kjv///////////77xx/9/5I7//////////////////////3/kjv8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf9/5I7////////////f+OP///////////////////////////9/5I7/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/f+SO////////////////////////////3/jj////////////f+SO/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/3/kjv//////////////////////f+SO/77xx////////////3/kjv8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf9/5I7/////////////////3/jj/wDIHf++8cf///////////9/5I7/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/f+SO/////////////////z/WVf8AyB3/vvHH////////////f+SO/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHYAAyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHf8AyB3/AMgd/wDIHYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://ac.search.naver.com/nx/ac">
+ <Param name="of" value="os" />
+ <Param name="ie" value="utf-8" />
+ <Param name="q" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="http://search.naver.com/search.naver" resultdomain="naver.com">
+ <Param name="where" value="nexearch"/>
+ <Param name="frm" value="ff"/>
+ <Param name="sm" value="oss"/>
+ <Param name="ie" value="utf8"/>
+ <Param name="query" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://search.naver.com</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/neti-ee.xml b/browser/locales/searchplugins/neti-ee.xml
new file mode 100644
index 000000000..d3b8d6ad8
--- /dev/null
+++ b/browser/locales/searchplugins/neti-ee.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Neti</ShortName>
+<Description>Neti</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAApBJREFUOBGlkrtv01AUxn92nDhvJ4TQNG0FdCjiIVGpAtEhEzDCgFgqMfAHsDKx8IewITEjkGCohBBCAgaoeAgKKVAVWtqkSZM0ieP4cbl2FZfOXPn6WL7fd853vnuU6VtPBf+xNFWSPbkPRKuDufYap7kKkRiR4gkS5VmUaOIgTvI0nzxaQni0q4vsfl0Es41imwhJcraW0dbeYMxcIjN2ZgQPYqjA8xyab+/Tq1WJ6mmSpyrECpNSnorV2MBsbtCuf8PVkmQL02GSUEHr8+OAnJw6iXH9NpqRx3NcRN8i1uqSbjbo/PrEUI3jyGKaqu0rsNtr9FaeE8uVyC3cYbxc4HhGozUUrGybWK5HxLYxxvfkK7oOAytIoPoe9KvPUM0OmdnL6Lkss4UYN2dSXJzQKWZjEJdb01AjGj5ZHMri5TKBoarvvt34IXu10Y+eRbgCRwi6jpCVBfLZX6oSfItiDmQCv3jggSevyksYKEqcoTnkXT3KpunRtT3qrQFKzwTHAdmKHlEpljL0cej4CXwFqnTVcx2G9d9EjSNsS2BzJxJU83ZNIr2B7NNCcV1KRYP5ks6HLZfWSEFico7+oM1gvYpenJEzYCG0vQQR3yxJ1mT0JV89V2aqEOXFejPwIFCQkJMW3/2DJRFWbYt42pCyfG3IYXKCyj75yvkprs2VePK+xma9G5yHc5CfusBOt4ki/RCSrHjy6oZ2ACrkEyxUjnGjMslqrc+9R1/CkQ4nMRqNczhfRsSiiGwKp2CQLaaZH9OpTCQ5ndN4Vd3h7oOPtE07SOBnDxUEpeRLkVVFp4eSiqOIFIOhw1K1wcPvNZaW64EPI6wfQwWjn0Hn0jDt5wYduV/KA79///+/cYT/C7yAFby9xs1kAAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="GET" template="http://www.neti.ee/cgi-bin/otsing" resultdomain="neti.ee">
+ <Param name="query" value="{searchTerms}"/>
+ <Param name="src" value="web"/>
+</Url>
+<SearchForm>http://www.neti.ee/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/odpiralni.xml b/browser/locales/searchplugins/odpiralni.xml
new file mode 100644
index 000000000..3a109bc90
--- /dev/null
+++ b/browser/locales/searchplugins/odpiralni.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Odpiralni Časi</ShortName>
+<Description>Odpiralni Časi v Sloveniji</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAnlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuMS4yIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBJbWFnZVJlYWR5PC94bXA6Q3JlYXRvclRvb2w+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjI5OTk5LzEwMDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+Mjk5OTkvMTAwPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KHvZwVAAAA0xJREFUOBE9U99PHFUYPXPv/NjdWXELLlRbgUSRXYxtgEVAcfXR/tBqGpBoaNIE+2rE+NAQH/AvUN980gf6Aib2uZhg0sqq2fWBVEtraRNghbWwdJlhdnfu/Oh3J6E3uZncOznnfOfc71PCMMTExARfWFjwFVrv5POXAs+bcuzD0WajkfB9H3HTPDRN8xZT+PyvKzevESZ8ihkfH4/AQ0NDvaZuzDu2nXMOHbzSl8XJF09C0zQ8fPAQ/95ZQywRh5k0f3cbjamVUum+JFFkBWME5pp+s7a/n3711CkxPvkR6+vL8kMiery/j3Q6jXt37/qLC4vh2t//qC2plh3Pdcd+KxbXmSxb1fV5q1ZLj72dd6/Ozmq53CBPJpP4q1TCl5/PwPM89Pf385kvZtTXR4dd68A6rurGD6DF8iMjlzzh5dIdHd7l6Wk9lXoWtmUTyIcQHuyDAzSbTdi2jYRpYmJyUm9ta/N8z3srP/LmRcY17ePKf9t474MLyvGODjh1RxIjCAJAAWWgA2RTHuqOg/b2drx75oxS3d0DU9knTLjijVRbKzLZLJeJS2AQBgjpK7fCFDqHdO8TTwhSRld3F3umpQWeEHnmus1kmliletN1IzHfJ2CkqURWJJEklgSCCMiCcuy5VknQpiKqTol+RuoENnQd5XIZSzdu4KWel0E5w5ckRKBIItryTi6mappT3d1FpVKJLqlibG1u4uu5OWxvlTF95VMYhhG9hBSQsD3yb9UOwFW1xuhn4dFOBQ/W132VMTQo8e+++RZ7/+/i6lez6O7uhitEFGRAGcmKNzY2fNuyiEAr8M4TnYjFYx9ub+8Eg7lBFovFYNA+//55dHZ1oV6vR6XKXtBUDdVqFT8tLgZkieTYHKfGuN2s1y+UN7deoBBFJpPhvZleJOLx6P2lV+lZp+e0bAvXr/8s1u/dJy6teOuPwmdRKw8PDJ82DL5CrZw4PTAgzp47y0lddmmUvk9NRbkEvywt+bdXV7VYPL7neWJspVhcU46GaTSXG9B140eawtdCMtqbzSCVOkbBgubhcTRQBJL2VoUbThZKhTtPh+loNHt6eowT7c9fCQIxRYPUT02jyio45z6p/sm5do0Z6vfLy8veEeYJlojIHKnXHv0AAAAASUVORK5CYII=</Image>
+<UpdateUrl>http://www.odpiralnicasi.com/opensearch/description.xml</UpdateUrl>
+<Url type="text/html" method="GET" template="http://www.odpiralnicasi.com/spots" resultdomain="odpiralnicasi.com">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="source" value="1"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/olx.xml b/browser/locales/searchplugins/olx.xml
new file mode 100644
index 000000000..4163104d2
--- /dev/null
+++ b/browser/locales/searchplugins/olx.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>OLX.ba</ShortName>
+<Description>OLX.ba pretraživač</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///yH///+N////zP//////////////z////5D///8nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///3L////z////////////////////////////////////9v///3IAAAAAAAAAAAAAAAAAAAAA////Bv///7f/////////////////////////////////////////////////////////wP///wYAAAAAAAAAAP///5D///////////////////////////////////////////////////////////////////+cAAAAAP///y3////8/v39/+7i5//8+vv///////////////////////7+///2+/7///////z+//+Ixff/3e79/////zD///+T+/j6/5VCav+UQ2r/iS5a//bu8v////////////////+63fv/H5Lv//7///+v2fn/AHbr/57R+P////+f////261tjP+3f5v//////9Kvv/+bRW3/9vv8/8Hw7//D8O7/qN/x/weC7v9qt/T/GI/v/0Wm8v//////////3v////l4EUP/4cvV///////v4+j/iRlK/3zBx/8Awr7/AL26/5Tn4f/f8P3/AIDt/w6J7v/y+f7///////////n////5hCVT/9q+zP//////693j/4odTv9/zM//MdDN//v+/v//////+v3//wCD7f8XkO///v7////////////5////29i7yf+ILln/8+nt/6FWev/Km7D/m+rp/zfOy////////////4bG9/9GpvL/gML3/3e/9v//////////3v///5P/////2LrI/5dJbv/MobT//v///4Pi4P8uy8j///////7+//9fsvT/8fj+//////9rufX/5/P9/////5////8t/////P/////////////////////G8fD/meXl//////////////////////////////////////////8wAAAAAP///5P///////////////////////////////////////////////////////////////////+cAAAAAAAAAAD///8G////w//////////////////////////////////////////////////////////G////CQAAAAAAAAAAAAAAAP///wb///+T//////////////////////////////////////////////+f////CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///zD///+T////1f//////////////2P///5n///8zAAAAAAAAAAAAAAAAAAAAAPAPAADgBwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8h////If///43///+N////zP///8z/////////////////////////z////8////+Q////kP///yf///8nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///yH///8h////jf///43////M////zP/////////////////////////P////z////5D///+Q////J////ycAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///3L///9y////8/////P////////////////////////////////////////////////////////////////////2////9v///3L///9yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////cv///3L////z////8/////////////////////////////////////////////////////////////////////b////2////cv///3IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wb///8G////t////7f//////////////////////////////////////////////////////////////////////////////////////////////////////////////8D////A////Bv///wYAAAAAAAAAAAAAAAAAAAAA////Bv///wb///+3////t///////////////////////////////////////////////////////////////////////////////////////////////////////////////wP///8D///8G////BgAAAAAAAAAAAAAAAAAAAAD///+Q////kP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////5z///+cAAAAAAAAAAAAAAAAAAAAAP///5D///+Q////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////nP///5wAAAAAAAAAAP///y3///8t/////P////z+/f3//v39/+7i5//u4uf//Pr7//z6+/////////////////////////////////////////////7+///+/v//9vv+//b7/v////////////z+///8/v//iMX3/4jF9//d7v3/3e79/////zD///8w////Lf///y3////8/////P79/f/+/f3/7uLn/+7i5//8+vv//Pr7/////////////////////////////////////////////v7///7+///2+/7/9vv+/////////////P7///z+//+Ixff/iMX3/93u/f/d7v3/////MP///zD///+T////k/v4+v/7+Pr/lUJq/5VCav+UQ2r/lENq/4kuWv+JLlr/9u7y//bu8v////////////////////////////////+63fv/ut37/x+S7/8fku///v////7///+v2fn/r9n5/wB26/8Aduv/ntH4/57R+P////+f////n////5P///+T+/j6//v4+v+VQmr/lUJq/5RDav+UQ2r/iS5a/4kuWv/27vL/9u7y/////////////////////////////////7rd+/+63fv/H5Lv/x+S7//+/////v///6/Z+f+v2fn/AHbr/wB26/+e0fj/ntH4/////5////+f////2////9utbYz/rW2M/7d/m/+3f5v////////////Sr7//0q+//5tFbf+bRW3/9vv8//b7/P/B8O//wfDv/8Pw7v/D8O7/qN/x/6jf8f8Hgu7/B4Lu/2q39P9qt/T/GI/v/xiP7/9FpvL/Raby////////////////3v///97////b////261tjP+tbYz/t3+b/7d/m////////////9Kvv//Sr7//m0Vt/5tFbf/2+/z/9vv8/8Hw7//B8O//w/Du/8Pw7v+o3/H/qN/x/weC7v8Hgu7/arf0/2q39P8Yj+//GI/v/0Wm8v9FpvL////////////////e////3v////n////5eBFD/3gRQ//hy9X/4cvV////////////7+Po/+/j6P+JGUr/iRlK/3zBx/98wcf/AMK+/wDCvv8Avbr/AL26/5Tn4f+U5+H/3/D9/9/w/f8AgO3/AIDt/w6J7v8Oie7/8vn+//L5/v////////////////n////5////+f////l4EUP/eBFD/+HL1f/hy9X////////////v4+j/7+Po/4kZSv+JGUr/fMHH/3zBx/8Awr7/AMK+/wC9uv8Avbr/lOfh/5Tn4f/f8P3/3/D9/wCA7f8AgO3/Donu/w6J7v/y+f7/8vn+////////////////+f////n////5////+YQlU/+EJVP/2r7M/9q+zP///////////+vd4//r3eP/ih1O/4odTv9/zM//f8zP/zHQzf8x0M3/+/7+//v+/v////////////r9///6/f//AIPt/wCD7f8XkO//F5Dv//7+///+/v/////////////////5////+f////n////5hCVT/4QlU//avsz/2r7M////////////693j/+vd4/+KHU7/ih1O/3/Mz/9/zM//MdDN/zHQzf/7/v7/+/7+////////////+v3///r9//8Ag+3/AIPt/xeQ7/8XkO///v7///7+//////////////////n////5////2////9vYu8n/2LvJ/4guWf+ILln/8+nt//Pp7f+hVnr/oVZ6/8qbsP/Km7D/m+rp/5vq6f83zsv/N87L//////////////////////+Gxvf/hsb3/0am8v9GpvL/gML3/4DC9/93v/b/d7/2////////////////3v///97////b////29i7yf/Yu8n/iC5Z/4guWf/z6e3/8+nt/6FWev+hVnr/ypuw/8qbsP+b6un/m+rp/zfOy/83zsv//////////////////////4bG9/+Gxvf/Rqby/0am8v+Awvf/gML3/3e/9v93v/b////////////////e////3v///5P///+T///////////Yusj/2LrI/5dJbv+XSW7/zKG0/8yhtP/+/////v///4Pi4P+D4uD/LsvI/y7LyP////////////7+///+/v//X7L0/1+y9P/x+P7/8fj+////////////a7n1/2u59f/n8/3/5/P9/////5////+f////k////5P//////////9i6yP/Yusj/l0lu/5dJbv/MobT/zKG0//7////+////g+Lg/4Pi4P8uy8j/LsvI/////////////v7///7+//9fsvT/X7L0//H4/v/x+P7///////////9rufX/a7n1/+fz/f/n8/3/////n////5////8t////Lf////z////8///////////////////////////////////////////G8fD/xvHw/5nl5f+Z5eX///////////////////////////////////////////////////////////////////////////////8w////MP///y3///8t/////P////z//////////////////////////////////////////8bx8P/G8fD/meXl/5nl5f///////////////////////////////////////////////////////////////////////////////zD///8wAAAAAAAAAAD///+T////k////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////5z///+cAAAAAAAAAAAAAAAAAAAAAP///5P///+T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////nP///5wAAAAAAAAAAAAAAAAAAAAA////Bv///wb////D////w///////////////////////////////////////////////////////////////////////////////////////////////////////////////xv///8b///8J////CQAAAAAAAAAAAAAAAAAAAAD///8G////Bv///8P////D///////////////////////////////////////////////////////////////////////////////////////////////////////////////G////xv///wn///8JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Bv///wb///+T////k/////////////////////////////////////////////////////////////////////////////////////////+f////n////wn///8JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8G////Bv///5P///+T/////////////////////////////////////////////////////////////////////////////////////////5////+f////Cf///wkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8w////MP///5P///+T////1f///9X/////////////////////////2P///9j///+Z////mf///zP///8zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///zD///8w////k////5P////V////1f/////////////////////////Y////2P///5n///+Z////M////zMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAP//AAD//AAAP/wAAD/AAAADwAAAA8AAAAPAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAPAAAADwAAAA8AAAAPwAAAP8AAAD/8AAP//AAD/</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.olx.ba/sugestije/firefox_pojmovi">
+ <Param name="sta" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="http://www.olx.ba/pretraga" resultdomain="olx.ba">
+ <Param name="trazilica" value="{searchTerms}" />
+</Url>
+<SearchForm>http://www.olx.ba/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/oshiete-goo.xml b/browser/locales/searchplugins/oshiete-goo.xml
new file mode 100644
index 000000000..291cfc1e0
--- /dev/null
+++ b/browser/locales/searchplugins/oshiete-goo.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>教えて!goo</ShortName>
+<Description>教えて!goo</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAICAAAAEAIAAoEAAAJgAAABAQAAABACAAKAQAAE4QAAAoAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////S////+f/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////5////0v////l/////////////////////////////////////////////////////////////////////+fn+f/Z2fX/////////////////////////////////////////////////////////////////////////////////////5f////////////////////////////////////////////////X1/f+1tez/fX3f/01N0/8zM8z/MzPM/zMzzP8zM8z/MzPM/1dX1f+VleX/2dn1//////////////////////////////////////////////////////////////////////////////////////////////////////+3t+3/QUHQ/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/Xl7X/9vb9v//////////////////////////////////////////////////////////////////////////////////////q6vq/zMzzP8zM8z/MzPM/zMzzP9BQdD/b2/b/4WF4P+FheD/hYXg/2Fh1/86Os7/MzPM/zMzzP8zM8z/Pz/P/9vb9v////////////////////////////////////////////////////////////////////////////Pz/P89Pc7/MzPM/zMzzP9JSdH/vLzu//////////////////////////////////X1/f+kpOj/Pz/P/zMzzP8zM8z/XFzW////////////////////////////////////////////////////////////////////////////wcHv/zMzzP8zM8z/MzPM/9nZ9f/////////////////////////////////////////////////Bwe//MzPM/zMzzP8zM8z/4uL4//////////////////////////////////////////////////////////////////////+8vO7/MzPM/zMzzP84OM3//////////////////////////////////////////////////////+np+v8zM8z/MzPM/zMzzP/Pz/P//////////////////////////////////////////////////////////////////////+Dg9/8zM8z/MzPM/zMzzP+3t+3/////////////////////////////////////////////////qanp/zMzzP8zM8z/MzPM/+np+v///////////////////////////////////////////////////////////////////////////3R03P8zM8z/MzPM/zU1zf+OjuP/4uL4////////////////////////////1tb1/4CA3/8zM8z/MzPM/zMzzP9vb9v/////////////////////////////////////////////////////////////////////////////////+Pj9/29v2/8zM8z/MzPM/zMzzP8zM8z/MzPM/1xc1v9cXNb/XFzW/zU1zf8zM8z/MzPM/zMzzP8zM8z/RkbR/+Li+P//////////////////////////////////////////////////////////////////////////////////////29v2/0ZG0f8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/2Fh1//l5fj//////////////////////////////////////////////////////////////////////////////////////76+7/84OM3/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8/P8//aGjZ/5GR4//Pz/P/////////////////////////////////////////////////////////////////////////////////////////////////UFDT/zMzzP8zM8z/dnbd/8jI8f/W1vX/+Pj9//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////39/v8zM8z/MzPM/zMzzP/4+P3////////////p6fr/w8Pw/62t6/+8vO7/2dn1/////////////////////////////////////////////////////////////////////////////////////////////////////////////////1BQ0/8zM8z/MzPM/4KC4P+fn+f/YWHX/zMzzP8zM8z/MzPM/zMzzP8zM8z/RkbR/6mp6f//////////////////////////////////////////////////////////////////////////////////////////////////////srLs/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/3Fx3P/6+v7/////////////////////////////////////////////////////////////////////////////////////////////////t7ft/0lJ0f8zM8z/MzPM/zMzzP9JSdH/mprm/62t6/+fn+f/VVXU/zMzzP8zM8z/MzPM/5iY5f//////////////////////////////////////////////////////////////////////////////////////////////////////fX3f/zMzzP8zM8z/WVnW//j4/f//////////////////////e3ve/zMzzP8zM8z/Pz/P//j4/f////////////////////////////////////////////////////////////////////////////////////////////r6/v86Os7/MzPM/zMzzP/Pz/P////////////////////////////4+P3/PT3O/zMzzP8zM8z/z8/z////////////////////////////////////////////////////////////////////////////////////////////4OD3/zMzzP8zM8z/MzPM/+7u+/////////////////////////////////9cXNb/MzPM/zMzzP++vu/////////////////////////////////////////////////////////////////////////////////////////////l5fj/MzPM/zMzzP8zM8z/zc3y////////////////////////////+Pj9/z8/z/8zM8z/MzPM/9HR9P////////////////////////////////////////////////////////////////////////////////////////////////9NTdP/MzPM/zMzzP9ZWdb/9fX9//////////////////////97e97/MzPM/zMzzP9ERND/+vr+/////////////////////////////////////////////////////////////////////////////////////////////////7m57v8zM8z/MzPM/zMzzP9GRtH/n5/n/7e37f+pqen/V1fV/zMzzP8zM8z/MzPM/5+f5////////////////////////////////////////////////////////////////////////////////////////////////////////////5+f5/8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/MzPM/zMzzP95ed3/+vr+/5OT5P9cXNb/sLDr/////////////////////////////////////////////////////////////////////////////////////////////////7m57v9VVdT/MzPM/zMzzP8zM8z/MzPM/zMzzP8zM8z/V1fV///////NzfL/MzPM/zMzzP81Nc3/4OD3///////////////////////////////////////////////////////////////////////////////////////////////////////i4vj/xcXx/62t6/+5ue7/Y2PY/zMzzP8/P8//6en6//Pz/P89Pc7/MzPM/zMzzP+5ue7///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+rq+r/MzPM/zMzzP86Os7/SUnR/zMzzP8zM8z/ODjN/+Xl+P////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////39/v+CguD/MzPM/zMzzP8zM8z/MzPM/zU1zf+3t+3////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////NzfL/mprm/4WF4P+dneb/5eX4////////////////////////////////////////////////5////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+f///9J////yf/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////J////SSgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD////D//////////////////////////////////////////////////////////////////////////////+3/////////////////f3+/6mp6f9vb9v/UlLU/zMzzP9ERND/cXHc/8HB7////////////////////////////////////////////4eH4f8zM8z/ZWXZ/5+f5/+trev/nZ3m/2Fh1/8zM8z/oaHo//////////////////////////////////////86Os7/V1fV////////////////////////////V1fV/0lJ0f//////////////////////////////////////WVnW/0FB0P/W1vX/////////////////2dn1/0FB0P9eXtf//////////////////////////////////////+np+v9LS9L/MzPM/zMzzP9cXNb/ODjN/zMzzP8/P8//0dH0//////////////////////////////////////+RkeP/MzPM/1dX1f+FheD/hYXg/4mJ4v+ysuz/8/P8////////////////////////////////////////////XFzW/1BQ0//i4vj/t7ft/4eH4f+dneb/5+f5/////////////////////////////////////////////////76+7/89Pc7/MzPM/zMzzP9SUtT/MzPM/zg4zf/Pz/P/////////////////////////////////////////////////e3ve/zo6zv/d3ff//////+np+v9LS9L/V1fV/////////////////////////////////////////////////1JS1P9VVdT/////////////////e3ve/0FB0P////////////////////////////////////////////////95ed3/MzPM/7Cw6///////yMjx/z8/z/9tbdr/////////////////////////////////////////////////7u77/15e1/8zM8z/MzPM/zMzzP9NTdP/z8/z/3R03P/i4vj/////////////////////////////////////////////////29v2/7Cw6/92dt3/SUnR/5+f5/8zM8z/oaHo////////////////////////////////////////////////////////////7Oz6/29v2/8zM8z/amra/+7u+//////////////////////H///////////////////////////////////////////////////////////////////////////////BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</Image>
+<Url type="text/html" method="GET" template="http://oshiete.goo.ne.jp/search_goo/result/" resultdomain="goo.ne.jp">
+ <Param name="MT" value="{searchTerms}"/>
+ <Param name="from" value="Firefox30"/>
+ <Param name="PT" value="Firefox30"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/osta-ee.xml b/browser/locales/searchplugins/osta-ee.xml
new file mode 100644
index 000000000..1b5bf8a50
--- /dev/null
+++ b/browser/locales/searchplugins/osta-ee.xml
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Osta</ShortName>
+<Description>Osta</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA%2BklEQVQoFWP8%2F%2F8%2FAymAiRTFILXYNfw8OuFDk%2BDf5xcwjcOi4f%2BPDz%2FPLQSSn2c7YupB14CsDsj%2BtrUQzRIWZD6yaqA4l3c%2Fq1YAsgIgG6EBTTWLkgO7dQGaaiCXERKsQKt%2FX9vw7%2F0DZBVsRgncIfORRYBsqB%2F%2Bf%2F8AV83hXM%2FIIQCU%2B3VuwZclgUCbkfVANQBNApoHlAA6g9O5gSdmPUQR0FpgWCHrgToJIg0MfqDTmSUNgFyg8V%2FXJELEgSK8qfsh1qJogEjDSWQ9QIN4U%2FYDpdDjAa4ayMDqaQZgKOEHv66u%2F7an%2Ft%2F39xBl%2BJyEbBucjc9JcEXIDAC2WqE7XED1KgAAAABJRU5ErkJggg%3D%3D</Image>
+<Url type="text/html" method="GET" template="http://www.osta.ee/firefox/" resultdomain="osta.ee">
+ <Param name="keyword" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.osta.ee/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/ozonru.xml b/browser/locales/searchplugins/ozonru.xml
new file mode 100644
index 000000000..e8002c5eb
--- /dev/null
+++ b/browser/locales/searchplugins/ozonru.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>OZON.ru</ShortName>
+<Description>OZON.ru provider</Description>
+<InputEncoding>WINDOWS-1251</InputEncoding>
+<Image height="16" width="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACABoBQAAJgAAACAgAAABAAgAqAgAAI4FAAAoAAAAEAAAACAAAAABAAgAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMuWTQDMl1AAzZpVANCfXADQoF4A8b5DAPPGWgDzyF4A9MplAPXPcwD10HgA9dF6AAPO/wAJz/8AJ9X/ADPX/wBO3P8AXd//AGbh/wB45f8A3bmIAN26iwDfv5IA9tSDAPbVhQDgwJUA5MihAPjfowDs2LwA7Nm/APngpgD546wA+ue6AProvACB5v8Ajen/AJDp/wCZ6/8Aou3/ALfx/wC68f8AwMDAAO/fyQD76sEA8eLOAPvsyADy5dMA9evcAP3z3QDJ9P8A1ff/AN75/wD27eEA/fXhAPjw5gD99uYA+fPrAP757wDh+f8A+/bwAPz59QD//fgA/vz6AP/9+gD5/v8A////AMuVSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsEIwAAAA3ADIHkXwHAAAA2Ac3AAAANwAwo/oABBCMAAAAAABIEowAGO6QfDgHkXz/////MgeRfKsGkXzrBpF8JgAAACYAAAACAAAAyAWRfNCJDQAYEYwAUQWRfHgTBQBtBZF8AAAAAD0EkXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzjORfApuAABoDowAAAAFABwTjAAY7pB8wECRfP////+7QJF8jtYBAEUAAABcEIwAgAAQwBwTjAAY7pB8cAWRfP////8AAAAAPQSRfJARjAB1C4F8twAAAAEAAAACAAAADBOMABgAAAAAAAAAeBGMAEIAAAAAAAAAXBGMAAAAAAAAAAAAAAAAAAwAAAACAAAAAQGQfADs/X9CAAAAAAAAAAMAAABKABoC2IkNAAAAAADYiQ0ABQAAAN/igHy0EYwATxqAfLwCAAAAAADAAAAAAAAAAAACAAAAgAAAAAAAAAD0EowAf89KAGAQ+QAAAADAAAAAAOgSjAACAAAAgAAAAAAAAAB0VYwA7PWmAAIAAABDOlx3b3JrXG96b25fbmV3BwAAAGVcZmF2aWNvbi5pY28AutwAAAAAUBKMABt5SgB8EowAsYvUdwDg/X98EowAAAAAADCj+gD/h9R3/+mQfA4QgXy8AgAAAAAAAAAAAAAAAAAAcBKMADCj+gAmAAAAAAAAAC4QgXwmAAAADBOMAAIAAAAwAAAAJgAAAAAAAAAmAAAAXBKMAP+H1HccE4wA85mDfDgQgXz/////LhCBfBbQSgC8AgAAMKP6ACYAAADAEowAAAAAAAAAQkJCQkJCQkJCQkJCAAAAQkJCQkJCNRVDBRo9QkIAQkJCQkJCMAEbLx0DAj1CQkJCQkI+Oh43QkJCPwUaQkJCQjEJDBwxQkJCQkIrBDxCQjYGLEJCQkJCQkJCOQQ1QkIYGUJCQkJCQkJCQi0XPEJCByFCQkJCQkJCQkIWPEJCQgkgQkJCQkJCQkJCMzJCQkIfCEBCQkJCQkJCQjQSO0JCQAsKOEJCQkJCQkI0EDJCQkJCIhkuI0JCQkJCJBE7QkJCQkJCQhQnQkJCKQ4oQkJCQkJCQkJBEQ8TEA0lQkJCAEJCQkJCQkIyJiY7QkJCAAAAQkJCQkJCQkJCQkJCAADAAwAAgAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAADAAwAAKAAAACAAAABAAAAAAQAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEhjMAxIc0AMmHNADFiDYAxYk3AMaLOwD4uzYA8bw8APG9PgDxvT8AyI5AAMiQQgDIkEMAypNHAMyYUADNmVMAzptWAM6cWADPnVkA8b5CAPK/RADyv0UA8r9GANGiYgDTpmkA1KdqANetdQDywk4A8sNQAPPDUQDzxVUA88VWAPPGWQDzyF4A9MlhAPTJYwD0zGkA9s5rAPXNbgD1znAA9dF6AADN/wACzf8ABM7/AAXO/wAHzv8AGNL/AB7T/wAj1P8AJtT/ACfV/wAo1f8ALtb/AC/W/wAw1v8AMdf/ADTX/wA52P8AStz/AFPd/wBY3v8AauL/AGvi/wBt4v8AbuP/AHLj/wBz5f8AdeT/AH/m/wDbtoMA3LiHAN26iwDevI8A3r2PAOS6iQD21IEA4sCWAOLDmgDjw5sA48afAOTFnQD32pIA+NuXAPjcmQD43p8A5cunAObMqQDnzasA6M6tAOjQrwD436IA+N+jAOnQsQDp0rMA+eKsAPnkrwD75rQA+ua2APrnuQD657oAlOr/AJfq/gCW6v8AmOv/AKDs/wCj7f8Ape3/AKzv/wCt7/8Aru//AKXw/wCw8P8AtPD/ALvy/wDAwMAA7dvCAO/exwD768QA++vFAPvsxgDw4c0A+uvIAPvuzQDt5dUA8uPQAPLl0wD059UA9OfWAPTo1wD879AA/PDUAPzx1gD88tkAwPP/AMj0/wDP9v8A1vP4ANH1+wDX9/8A3/n/APbt4QD37+UA9vPhAP314QD99eIA+vHnAPjx6AD99+gA/vfoAP346gD++e0A4Pn/AOz7/wDu/P8A+/jzAPz48wD++/IA/Pn0APz59QD+/PUA/vz2APH8/wD0/f8A9v3/APf9/wD//fgA//36AP/++wD4/v8A+f7/AP7+/QD//v0A/P7/AP///gD+//8A////AMKDLQADAAAASgAaAtiJDQAAAAAA2IkNAAUAAADf4oB8tBGMAE8agHy8AgAAAAAAwAAAAAAAAAAAAgAAAIAAAAAAAAAA9BKMAH/PSgBgEPkAAAAAwAAAAADoEowAAgAAAIAAAAAAAAAAdFWMAOz1pgACAAAAQzpcd29ya1xvem9uX25ldwcAAABlXGZhdmljb24uaWNvALrcAAAAAFASjAAbeUoAfBKMALGL1HcA4P1/fBKMAAAAAAAwo/oA/4fUd//pkHwOEIF8vAIAAAAAAAAAAAAAAAAAAHASjAAwo/oAJgAAAAAAAAAuEIF8JgAAAAwTjAACAAAAMAAAACYAAAAAAAAAJgAAAFwSjAD/h9R3HBOMAPOZg3w4EIF8/////y4QgXwW0EoAvAIAADCj+gAmAAAAwBKMAAAAAAAAAAAAsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwAAAAAAAAsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsAAAALCwsLCwsLCwsLCwsLCwsLCAXVFNT1l9sLCwsLCwsAAAsLCwsLCwsLCwsLCwsLCrUA4EAQEFBg1KnrCwsLCwALCwsLCwsLCwsLCwsLCwnhmxsQwTEwuxsbERjrCwsLCwsLCwsLCwsLCwsLCwsKwZsRl1n7CwnHQYsbEPm7CwsLCwsLCwsLCwsLCwsLCwSbFesLCwsLCwsK5YArEbsLCwsLCwsLCwsLCwsKygp7B/ebCwsLCwsLCwsLBXsbF+sLCwsLCwsLCwsHZMIBYdJnqwsLCwsLCwsLCwsLASsVCwsLCwsLCwsJAnCAglVWJggrCwsLCwsLCwsLCwsFexSLCwsLCwsLCFHAghe7CwsLCwsLCwsLCwsLCwsLCwgbFGsLCwsLCwnR8II5ewsLCwsLCwsLCwsLCwsLCwsLCTsUewsLCwsLBSCBeQsLCwsLCwsLCwsLCwsLCwsLCwsI2xTrCwsLCwlBQIW7CwsLCwsLCwsLCwsLCwsLCwsLCwWrGBsLCwsLBkCAmRsLCwsLCwsLCwsLCwsLCwsLCwsLAQGrCwsLCwsFUIHKGwsLCwsLCwsLCwsLCwsLCwsLCwVgOSsLCwsLCwVAgeprCwsLCwsLCwsLCwsLCwsLCwsLBLfLCwsLCwsLBfCBWXsLCwsLCwsLCwsLCwsLCwsLCwsLCJb7CwsLCwsIMICHewsLCwsLCwsLCwsLCwsLCwsLCwsEMumrCwsLCwqCIIKLCwsLCwsLCwsLCwsLCwsLCwsLCwhipqsLCwsLCwgggIU7CwsLCwsLCwsLCwsLCwsLCwsLCMKkSwsLCwsLCwWwgIVKewsLCwsLCwsLCwsLCwsLCwsIsqPrCwsLCwsLCwXAoIJGOVsKOqsLCwsLCwsLCwsLCwbipBsLCwsLCwsLCwhCkJCAePZj+wsLCwsLCwsLCwsLA9KkWwsLCwsLCwsLCwsJR4YZaKLZiwsLCwsLCwsLCwiC4qbbCwsLCwsLCwsLCwsLCwsK81OaSwsLCwsLCwsJk1KjCjsLCwsLCwsLCwsLCwsLCwsIcuOnGtsLCwsKVsNyoscLCwsLCwsLCwsLCwsLCwsLCwsHIrKjZCZ2VANCoqK2mwsLCwsACwsLCwsLCwsLCwsLCwsIc4KioqKioqKjJxsLCwsLAAALCwsLCwsLCwsLCwsLCwsKlrPDMvMTtoorCwsLCwsAAAALCwsLCwsLCwsLCwsLCwr7CwsLCwsLCwsLCwsLAAAAAAAACwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLAAAAAA8AAAD8AAAAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAGAAAABwAAAA/AAAA8=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.ozon.ru/JSONSuggestionHandler.ashx">
+ <Param name="text" value="{searchTerms}"/>
+ <Param name="from" value="firefox"/>
+</Url>
+<Url type="text/html" method="GET" template="http://www.ozon.ru/?" resultdomain="ozon.ru">
+ <Param name="context" value="search"/>
+ <Param name="text" value="{searchTerms}"/>
+ <Param name="from" value="firefox"/>
+</Url>
+<SearchForm>http://www.ozon.ru/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/palasprint.xml b/browser/locales/searchplugins/palasprint.xml
new file mode 100644
index 000000000..0baa10e0b
--- /dev/null
+++ b/browser/locales/searchplugins/palasprint.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Palas Print</ShortName>
+<Description>Palas Print - Heb Ffiniau</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAQAEAAAAAAAAAAAAAAAEAAAAAAAAAAP4ADvb%2BABHO%2FQAQev4AB8j%2FAA6y%2FgAVvv0AEPb%2BAAAG%2BgAMYf4AFGz%2BAA%2Fe%2FQAOof4ACdT%2FAAyg%2FgAScP4AEKf%2BABSe%2FgACDv4AFY39ABVh%2FQANTP8ABxn%2FAAAG%2FgAS9v4AAQH9AAAA%2FAD%2F%2F%2F8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkaGgAAAAAAAAAAAAEHABcAAAAAAAAAAAAAAAABGAAAAAAAAAAAAAAAAAAAARgAAAAAAAAAAAAAAAAAAAEBAAAAAAAAAAAAAAAAAAABBxMUFBUWABcAAAAAAAAAAQcBAQEBARESAAAAAAAAAAEHAAAADwEBEAAAAAAAAAABAQAAAAAAAQEAAAAAAAAAAQEAAAAAAA0BDgAAAAAAAAEHAAAAAAALAQwAAAAAAAABBwAAAAgJAQEKAAAAAAAAAQEEBQUBAQEGAAAAAAAAAAEBAQEBAQIDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%3D</Image>
+<Url type="text/html" method="GET" template="http://palasprint.com/siopa/search_all.php" resultdomain="palasprint.com">
+ <Param name="keywords" value="{searchTerms}"/>
+ <Param name="source" value="mozilla"/>
+</Url>
+<SearchForm>http://palasprint.com/siopa/advanced_search.php</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/paroledigenova-lij.xml b/browser/locales/searchplugins/paroledigenova-lij.xml
new file mode 100644
index 000000000..e671db15d
--- /dev/null
+++ b/browser/locales/searchplugins/paroledigenova-lij.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Parole di Genova</ShortName>
+<Description>Parole di Genova, grande dizionario della lingua genovese</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC%2FxhBQAAAwBQTFRFAAAABQUEBg8UEhISGxcWAhUgEio6GS87KG8gMHEnPHAzHlR1PUFEQGk0Tk9QT1BRUk9NV29PY1RcZ11ZYF1jf1xlbmFnfXl4fXt7Hl6GI2GHLmOFLGSHImuZf3%2BBM4McOYEvPo8yOpExQZgkQZc3QpU6QpY6SJ06S549UJ5CgwwOgQ4PigYIiQsMhBAQiB0XliYgmTYtoRkVqzktvj8x5kk58049gXSFhX6UiH%2BZjIyQi4qak5KVibuJm6CnmqCon6GsppujoKWuoKuzp663pLC7uqavrLXBrrfBrrnFsrfDsbvHucPKuMTQuNbIvtrQyMXRxdPfytPd19PZ2tTX2djfxtXhx9bkydXhytfkytjlzdnmz9zk0N3k0N7r0%2BDv1ub01%2Bf31uf42Ofz2Of22Oj32%2Bv03Ov13uz32uj42un62%2Bv73Ov73u363%2Bz73ez83e393u7%2B3%2B%2F%2F2vD%2F3vD%2F4e384PD%2F4%2FL84fL%2F4%2FT%2F5PX%2F5fb%2F4Pr%2F5%2Fj%2F6Pn%2F6fr%2F6%2Fz%2F7P3%2F7v%2F%2FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzK96PwAAAAlwSFlzAAAWJAAAFiQBmxXGFAAAABl0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuNtCDrVoAAADbSURBVChTYyhDAwxYBIoYGqpAwkVlTc5lZUAVBd4sNlb53AJswp7s%2BSCBsnxW18BUfnd78XDGBLBApmxEWaJcdiBfoW8cUMAvL0Umviw%2FKj9MOqewECigrMIrUgAyNFIyCUgylMlzCIoxWXqVMTBL8UhkAQVsFRU4uawtzIUMDI1NioECuWqqahoBwY6i2rqmZjUgWzTVlfxLQkPctPR0jErA1lYAHQkkiyr0S8EuLXNwinGp9rHzCIquBglUVccmpzXVladX1Gc0VgIFqqur6yuqa4E6Kqvrq4oAvlJaDTVq%2F2QAAAAASUVORK5CYII%3D</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.paroledigenova.net/it/api.php">
+<Param name="action" value="opensearch"/>
+<Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="http://www.paroledigenova.net/it/index.php" resultdomain="paroledigenova.net">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="fulltext" value="Ricerca" />
+</Url>
+<SearchForm>http://www.paroledigenova.net/it/index.php?title=Speciale:Ricerca</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/pledarigrond.xml b/browser/locales/searchplugins/pledarigrond.xml
new file mode 100644
index 000000000..e4a40f3fc
--- /dev/null
+++ b/browser/locales/searchplugins/pledarigrond.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Pledari Grond</ShortName>
+ <Description>Pledari Grond online: vocabulari rumantsch-tudestg</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABXUlEQVR4nGJk0Jzyn4ECABBATJRoBgGAAKLYAIAAotgAgABicdB8ybC/Zjdc4OM3VoYNZ2UZFhxUZjhwXRwsNiH2DIOD1ksGfbn3YP7FR4IMCTOsGC48FGQACCAUF4Ak+Ll+M8Tb3mOYEHcGrjnf4wZY88M33GAxEHtD0QEGAa5fDAABxIJsQMEiE7BN9UGX4LaBNIPAwsNKYFsN5N8zBJg8ZnjwGmIYQAChGAAyFeQCEDgIdD7Ie3C5M7IMCXZ3gfgeRECTAegFIQaAAEIx4MEbHoYPX9nAfpuwQwMlsBREvjIIcP9isEcyFOQFgADC8AIs4GAA5G95oOYE+7sME7ZrMCTOtGSYn34cLg8QQCgGYAMBfQ4MB2p2gcMEWSPI4AdADBBALCCicZ0e1AvcGAaAvOPQ4gYOD5AXwOqAAbjgkDKYDRBAjJTmBYAAojglAgQQxQYABBDFBgAEGAB/YnDI5He1+AAAAABJRU5ErkJggg==</Image>
+ <Url type="text/html" method="GET" template="http://pledarigrond.ch/#searchPhrase={searchTerms}" resultdomain="pledarigrond.ch" />
+ <SearchForm>http://www.pledarigrond.ch/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/pogodak.xml b/browser/locales/searchplugins/pogodak.xml
new file mode 100644
index 000000000..b6ed90c4a
--- /dev/null
+++ b/browser/locales/searchplugins/pogodak.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Погодак</ShortName>
+ <Description>Погодак: претраживач Интернета</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AHd72f9yc+n/fn/u/3+A6/9kZ9v/jJPS/////wD///8A////AP///wD///8A////AP///wD///8AuLn1/0ZJ4f9fX+j/qqn0/87P9P/S2Pj/b3Th/xogzv+Fh+//////AP///wD///8A////AP///wD///8Ak5Xx/ysu5P+2tvX////4/+rq6f+9veT/yszh//LzzP/W2Nr/Wl3i/2Zo7P////8A////AP///wD///8AtLf0/w8V3P/a2fT////1/1xf3/8ICtX/DQzT/wAAxP8ODrX/xsjk////6f9HS9X/fYDx/////wD///8A////ACcr3v+houn////z/zU44P8dIOX/paXt/9/e6v+fmsj/NTbG/w0S3v+7ven//v/n/ywu1v/Hx/n/////AKOh7/8pJtP////q/3p86P8iJO3/9PX4////7//x8Nf/9PS+////0f97f+P/Li/o////6f9ubc//U1Lj/////wBaV+P/Y13Q/+/w4/8iJur/tLX4/////f+SlOf/ExDA/1FKwv/5+en/8/Pf/0hJ6f+rrPH/sbDR/z471/////8ARELk/4SB0f/ExeH/MDTt////+//g3vL/AADX/wINvv8AAMf/lofk////5/94evX/eH/5/8fI2f9AQef/5ub//05N6v98ec3/v77d/zEz7P////f/2Njw/wAA0/8wQuL/QHTx/2ma8v++5fn/rKHS/+i/p//IvNH/PT/m/9/h/v9qbvH/VVfO/9na1P8lJ+r/r6/w////9/9+g+L/Hh3o/3p+5//X+e3/nt74/461yv/Y06r/ood+/3JDQf/s0rT/qav6/yEm2f/o6M//eXrq/xcZ7f/i4ez////x/+bn3f/59NH////k/6ie1f+dhnn/rqeG/46vr/+UwML/8erg/////wAnK+v/fIDR////2v88Pen/Exbp/5OT5v/T0Nv/r6vU/zU94P8mMeT/9b+B//6uRv+2mIX/1+7o/////wD///8Aqqv9/wEE4/+2ttH////e/3N35P8VGdv/DgvW/wMA0/87Pdr/3uDs////6v9LTe3/hXbV///qu/////8A////AP///wB7fPn/AAHi/4uM1//9/dv/8fHg/8rJ4P/e3eT////r/93b7P80O/P/OT/7/////wD///8A////AP///wD///8A////AJWW9/8eIun/MDLl/31/6v+qrOz/l5vv/1ZY7f8fH+v/XmDw/////wD///8A////AP///wD///8A////AP///wD///8A////AJia+/9QUvL/Oz7t/1JW8P91d/X/xMT5/////wD///8A////AP///wD///8A+B8AAOAPAADABwAAgAMAAIABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAACAAQAAgAEAAMAHAADgDwAA+B8AAA==</Image>
+ <Url type="text/html" method="GET" template="http://www.pogodak.rs/pretraga" resultdomain="pogodak.rs">
+ <Param name="q" value="{searchTerms}" />
+ <Param name="foxsbar" value="ff"/>
+ </Url>
+ <SearchForm>http://www.pogodak.rs</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/portalbgdict.xml b/browser/locales/searchplugins/portalbgdict.xml
new file mode 100644
index 000000000..27f5af418
--- /dev/null
+++ b/browser/locales/searchplugins/portalbgdict.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Речник на Portal.BG</ShortName>
+<Description>Английско-български речник на Portal.BG</Description>
+<InputEncoding>windows-1251</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC%2FklEQVQ4EX2STUxTaRSGn%2Fbe3ha4bS14KUJBtGhARI0k6kTNxJXOYiK60oStdGGi8S8mGtTEjYnRhXEDxp0mxIXJZHSicaEJcYJKA4uZBBGEBuSnXIH%2B0D%2FaXu%2F91DqLiWfx3Xu%2Bc857zvee12YM7TDSkXIS4fVkR4tkZj9Q9M9SfaIS1SfjkG38zGSrODt2BHVzDYpnDgahvi9MKBSiu2s31VWVKA75%2FzHmupHjAxtwt9ZQ3tIikiSvVxRbzuT9J0ihDvQK%2BGslytDKPAvLCzQVnVwN7kPLG8i5iTy5KrOzaeWdnZzp7%2BfUixdkJiZY2r%2Bdd5%2Fn%2BeDxUVe7kYvaeZG38dHv%2FP3mAU%2FW5TGfMCYu6%2Fv6ROdz4TApXRd3OUnidVmaNjVIp3a0NJnrkMI%2FuVnuRgzsRu0M8bdvRdDqnBodpZBIUJTsRHc1EzESzCUXBaB19PT0sP%2FZWuEPpUHWTvgYP52gq7eXzLc0w2YjcnAP5ZvqiSxOi9vQjdC36I%2BPbHHgXuug5uE1Jh88Jzs8SrrMRbKlEceWRoINfjYno%2FRPj%2FCx58%2BvQOZ2etvDSIaNXzIZbJYOspVXWFpOEtVjjCx4CMfaicQ0VpbjBJwTvFxzm1%2F%2FbRAAVrEdGx3JAMeLw%2Ba%2FaU7FgVblJV%2FRzKBhaiIQpO9CNbX6TQZngnjfXETZmmG8bYaObAOXU9s47mlmQ51CSSGSSdrjkU24NQd%2Bv6vEuKLYGV%2FWaI%2Fs5dJuHafTVKcpLJ9XRSs4v05gTZFdtTEWLSMez9PVDp4Dt4g1Xef1nUYOB%2B7wftaNqrpoalxHi0nu%2BvpqvKr0A0CWDJb0FFNTKQuPm8dU5uczpUkc9hyr%2BQIul4JbLRPyNpf1nyeYbLT6pugfrzOLugXIdOwsw%2FpJEvEs3YeeIktu7HZBm4hbR4kDyznz2xwDt%2B38oZ%2BiUDBIpdJk0nk6tg7Q2pClck2tyYHDSi2ZWON3b9UUxqdokXuvdvJusokKeZE2%2FxDb6iYF48GAU7zbGv27fQHPoxrlB7FOagAAAABJRU5ErkJggg%3D%3D</Image>
+<Url type="text/html" method="GET" template="http://portal.bg/dict/index.php" resultdomain="portal.bg">
+ <Param name="encin" value="windows-1251" />
+ <Param name="encout" value="windows-1251" />
+ <Param name="translate" value="firefox" />
+ <Param name="word" value="{searchTerms}" />
+</Url>
+<SearchForm>http://portal.bg/dict/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/priberam.xml b/browser/locales/searchplugins/priberam.xml
new file mode 100644
index 000000000..9ff992a00
--- /dev/null
+++ b/browser/locales/searchplugins/priberam.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Priberam</ShortName>
+<Description>Dicionário Priberam</Description>
+<InputEncoding>ISO-8859-15</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAABGdBTUEAANbY1E9YMgAAABl0RVh0
+U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKoSURBVHjaYvz7+dv3r98Y/n7+7u/jCxBA
+jH8/fmVgYGBat23T1+/fAQKI8e+X7wz//xeVlykqKjB8fP4qJj4GqHzR3LkAAQRV9+zlCylxCSYG
+JkYgunXnNohx78qNv5++fXn9Dkgy/Prw5YVwLQsLyy2hSoAAYgRaATTt4ZPHX799+fTh06Mnj4P9
+g5iZmZgYGBmASExEZPfevdHJCbdu3548c+rd+3eZGMCAk5Mz7b/Zg4cP33x8X5Cd+2vHbcaf7z4d
+OXbUwdaOARUABBDUXUDw79+/L9++MTIx8nJxA53FwsDI+Ofvn2vXr/Fy85y7fOnvnz9hQSEga4Ee
++vPpK5D8+eGzqpLy8wePbpy7CPQ4C8g4Rsblq1ZIiEncvnd36vTpvLw8xaqFDECPyUhJA0kgkpeT
+B5K/P3wBeQ5oCAjdexuiYsvEzARkvz11++f91yzfvn27dv36rvDepcXtC7gvGttarbAq+asuzbh/
+yw4zU1MODg5UXzACBBg45P/9h4uAfPPly+9fv779+M7EzHzt5g0JMXFdLW2oNBMjOKjAwfjh44cL
+Fy98+faVh49XWFRk3qIF2iaGl65cPnv+7NUb1yBqQFoYgKaDkQC/gIG+AR8P77nz58urq46fPPX/
+37/TZ86EBoW8ffcut6jg/qOHIB1AJ0E8ePX0ufrK6o3r1v/6+BkYdNaWlnp6ekDG7UtXwoNDLp89
+B1IGdD9Iw6dvpw4e2bBi9a8PnyEBdvPqVSkpKaBxU/snQkSg6Mt3aAKB+/hj+a6fM/dfZfslvDu+
+rreN+R9Dfnbu5YuXzk/a/PvZ28qwPMbbV65NmTFDVETk04cPMioKroY2CtxibLICDGzMwGA4cPSw
+vZWNoIAgw6+/fx99YFYUZPz39cd/pGDFD4CJBwBLx4gNwqkZawAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="http://www.priberam.pt/dlpo/firefox.aspx" resultdomain="priberam.pt">
+ <Param name="pal" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.priberam.pt/dlpo/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/priceru.xml b/browser/locales/searchplugins/priceru.xml
new file mode 100644
index 000000000..55ea693c2
--- /dev/null
+++ b/browser/locales/searchplugins/priceru.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Price.ru</ShortName>
+<Description>Поиск предложений и цен</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABhklEQVQ4jX2Sv0vDQBiGn4uF0iK1giJ2sAGHbiLauhSKIGj%2FAmddU1D3Tg4ujoJFcXYSHRREOopuKuogiCB10KVo4o9aK9pzCElzTewLgdx73%2FPm%2By4HbZKmKaVh2I9pStcP8AC0dphisWV43z2eN0T8CzsaG4eLc78PiFJJiE7ww3VZWSdHpn019gi7e76Nq5sTrFC0YyArK60RwD4ogPLBtuulkv1YoSjxn08ArFAUvWAQSeiEs3kRArB2NiTAK1BZL3FrSlK9dvbtQ9VOGk4CoBcMAOpPFaRpyq7G6ZH8ebfcL8YnMvQ83nFxb%2FH8BX0RwUAbDBBJ6PxWH9HC2byIJHRlNL1gkEvb3n9wbWm%2BNUI4mxeN0yNZf6ooQbm0roBe2JF7kTZnZnE6qayXOD6zw5qry4GwNppRA7whDuyqfOiDtewUgPob17pjyj3PpXWGalWaaGg0FTg2tyh8HSx8vCmBjuR3IxD2ddDeidOBA8e39gPrA7XWHZOXkyPyJTMoO9X9AaeeqENksUPXAAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="GET" template="http://price.ru/search" resultdomain="price.ru">
+ <Param name="query" value="{searchTerms}"/>
+ <Param name="from" value="fx3"/>
+</Url>
+<SearchForm>http://price.ru/index.html</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/prisjakt-sv-SE.xml b/browser/locales/searchplugins/prisjakt-sv-SE.xml
new file mode 100644
index 000000000..3c7d7fd5d
--- /dev/null
+++ b/browser/locales/searchplugins/prisjakt-sv-SE.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Prisjakt</ShortName>
+<Description>Prisjakt - jämför priser och produkter</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAQAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKieMACommAAqJaMAKyauAC0prwBMSLsAZF%2FFAH16zQBwa9EAi4nTALu55QCxrvYA7Ov4AP%2F%2F%2FwBwADIAaQBjAG8AAADAHiQAfO8SAEwAAAAoJYAAjAAAAOS%2F9QBxGuYAjAAAAJgFAgAA8BIAGAAAAHDvEgDI7xIA4xq%2BAIwAAACYBQIAAPASABgAAAAAAAAA2T7GABg%2FxgDkCQUAVAAAAGDyEgChUcYA5AkFAFQAAABg8hIAAAAAAMzyEgBghgcAHjvnAPc65wDg8hIAWAcXAKzvEgCw7xIAgP4SAAlI6QBYMOgA%2F%2F%2F%2FAB475wAbrQEAYIYHAODyEgBYBxcABACkAAAApAD%2F%2F%2F8AsAgAAAAAQAAEAKQAZAAAAGIAbQBwADIAaQBjAG8ALgBlAHgAZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAAAAAAAAAAAAAAACAAAAXPESALgLpADoC8IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6AvCAAAAAAAAAAAAAAAAAP%2F%2F%2FwAAAAAAAAAAAAAAAAAAAAAAX6bnAAAAAAAAAAAAAAAAAAAAAAAoLxQAAAAAAAkOAgACDgIADQAAAADw%2FQAA4P0AAg4CAAkOAgAAAAAA9gvCAODyEgBSAAAAAAAAACTVpAAAAAAA%2F%2F%2F%2FAFzxEgBq8RIAXPESAMzx5wAEwPUARPESAAAAFACoRPkARQAAAHgTFAAAABQAoCAUABzxEgAg8RIAZPMSAPCI%2BgBSAAAAAAAAAJDWpAAAAAAAOor1AAAAAAAA7P0AAAAAAAAAAABwADIAaQBjAG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDO8YApTbGAM0JAQAPAIUAAAAAAJDWpADNCQEAAQAAAAAAAAAAAAAAFTbGAM0JAQAAAAEAwU1BAM0JAQAAAwAABMD1AFik5wB0AAAAAAAAAOjyEgB0AAAAAAAAAAAAAAD%2F%2F%2F8AAAAAAAAAAAAAAAAAAAAAAP%2F%2F%2FwAAAAAAAAAAAAAAAAAAAAAAKC8UAAAAAABkxfUAqfHnAIwAAAAAAAAAAAAAAAAAAAB88hIAAADdAAADAAAAAAAAyfHnAAADAAAAAN0AjAAAAAAAAAAAAwAAAQAYAAAAAABw8hIAAAAAAKqb9QCzm%2FUA4PUSACQAAgAA7P0AEW5AAAUAAAAkAAIAAPD9AJzyEgACAAAAQKP1AJACAgD5m%2FUA4En8ACOj9QAro%2FUAAAAAAAgCAACgIBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAAAAAAAAAAAAAAAACwgGFQYICwAAAAAAAAANCgUEBAEEBAUKDQAAAAAACgQEBwwCBAQEBAoAAAAACwUEBAQOAwQEBAQFCwAAAAgEBAwODg4OCQQEBAgAAAAGBAQEBAQEBA4EBAQGAAAVFQECAwwODg4MAwIBFRUAAAYEBAQOBAMEBAQEBAYAAAAIBAQECQ4ODg4MBAQIAAAACwUEBAQEAw4EBAQFCwAAAAAKBAQEBAIMBwQECgAAAAAADQoFBAQBBAQFCg0AAAAAAAAACwgGFQYICwAAAAAAAAAAAAAAABUAAAAAAAAAAP%2F%2FRgD%2B%2FwAA8B8AAMAHAADABwAAgAMAFYADAACAAwAAAAEAAIADBhWAAwsAgAMAAMAHDQrABwQB8B8FCv7%2FAAA%3D</Image>
+<Url type="text/html" method="GET" template="http://www.prisjakt.nu/supersearch.php" resultdomain="prisjakt.nu">
+ <Param name="s" value="{searchTerms}"/>
+ <Param name="r" value="1"/>
+ <Param name="e" value="utf8"/>
+ <Param name="ref" value="155"/>
+</Url>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.prisjakt.nu/plugins/opensearch/suggestions.php?search={searchTerms}">
+</Url>
+<SearchForm>http://www.prisjakt.nu</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/pwn-pl.xml b/browser/locales/searchplugins/pwn-pl.xml
new file mode 100644
index 000000000..2b0849f84
--- /dev/null
+++ b/browser/locales/searchplugins/pwn-pl.xml
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Encyklopedia PWN</ShortName>
+ <Description>Wyszukiwanie w Encyklopedii PWN</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAI9UExURQAAAB9YryBWrgmU3SBWrh9YryBWriBWriBWri0zkwmU3Q2L1hCBzg2L1hCBzhR3xiNOqB9YsBxitydFoRhsvxR3xhxitxhsvyNOqB9YsCdEoCo7mi01lSs6mSBWriBWri00lCBWrgqT3CBWriBWrgmU3Ss6mQqR2g2L1iNOqCdEoC0zkyBWriBWridEoCBXryBWris6mSBWri0zlA2K1RCBzh9YsB9YryNOqC0zlCBWrhR3xhCBzhxitxhsvyBWriBWrh9YsBR3xhxitxhsvyBWri0zkyBWriBWriBWriwykx9YryBWriwykyBWriBWriBWri0zkyBXryBWrh9YryBWriBWriBWrisykyBWriBWriBWriBWriBWriBWriBWrh9YryBWriBWriBWriBWriBWriBWriBWriBWriBWriBWriBWrgmU3Q2L1idEoBCBziJNpwWS3Cs6mW6UzBR2xiNOqB9YrxVrvhphtxCAzgyL1hd4xyFZsEWJzD96whl6yBSP1w1/zQiT3Ehrtg6AzguK1SVCn8XQ6B1Xr3eq2hJ1xmiQynmi1D2Z2F2q3hB0xSSX2l605TNbru31+93l8nmKw5XC5io6mBltv4W/5mi553uy3yhFoRdfthp6yODn87LO6svh8iZputTc7is6mFyFxTh0v4Kp1+rv+Ojv+EqNzTuCyLHN6Xyk1YK34ZLD55il0Z3A44an1mKLyBlgthZrvs/U6S08mtrg8DJAnLe/3jNBnGd6vCw7mTZEnjWei8gAAABsdFJOUwBNVQgHMwpZVGO9vQgIvQgICAgICL29vb29u4AO+w0uT3y1kwz+VRX4/VUhCZf9LR/9hWNK/f1gVWQyVVVVVV4gVf39/QhifaW+Y1OvZJK0lWRiMUKef3NlTZSZerKsFimuImocX3IVYkMpUosemJAAAAEISURBVBjTY2BQV1LNaSoszAEBLmYGBgYTjdzWCbMmNlTl5uZy8wIFbExbemeun9zXWJmfz8cDFLB26pq2bt7C2f311SWi/EABO5etGxesWrN6RmdtubgIUMDWecvylWuXzV3UU1chJgQUcDTrWLqieNPm7uKaMklBoICFZsGkxfN3TGlvLiiVEAAKaOnlzdmwa8+2qW15eVLCQAE1w6IlO3fv3bd9epGsNJDPoG1s6eblyxkRyMmpIAMS0Ddn9GZkDWFICGJlkGcBCrAbWXn6RIbGs0f5O3DIgQR0mdzDwuOSM5liAuxVQALKBq7BsWmJKRzp0R46QAEORQYGtoykVJZsliw/NgYAqJdNo4WkE2YAAAAASUVORK5CYII=</Image>
+ <SearchForm>http://encyklopedia.pwn.pl/szukaj/</SearchForm>
+ <Url method="GET"
+ template="http://encyklopedia.pwn.pl/szukaj/{searchTerms}"
+ type="text/html" />
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/qxl-NO.xml b/browser/locales/searchplugins/qxl-NO.xml
new file mode 100644
index 000000000..bb2c92f29
--- /dev/null
+++ b/browser/locales/searchplugins/qxl-NO.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>QXL</ShortName>
+<Description>QXL søk</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA////AAB15wAAdeYABXv1ABV79AAVdesAGIH8ABeB+QAAY9UAAFPEAABdzyMbg//lG4L/7RqB/tkTfvou////AP///wAAdecAAHXmAAV79QAVe/QAFXXrABiB/AAVgfkAC2ziAAAGVgAMWcyKHYf//xuC//8YgPyCEXv4AP///wD///8AAHXnAAB15gAFe/UAFXv0ABV16wAYgfwAFX/3BxN9+AQPUKAbGnzz8huD//8bgv/oFX/8DhV+/AD///8A////AAB15wAAdeYABXv1ABV89gYXePBhG4P/vxuC/dwbgv7ZHYj/2xuD//8bgv//GoH+eBqF/wAahv8A////AP///wAAdeYAAHTmAAd79R8bgv/HG4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xqC/0cITs8ABkPGAP///wD///8ABHvxAAR78QgYgfzRG4L//xuD//8bgP3wGoH+shuC/60bgv7pHYP//xuC//8bg//mADi4HQAemwD///8A////ABiA/QAagf6HHYP//xuC//8Ygf+uDWXaGBZ9/AAYgf4AFX74DRV695QbhP//G4P//xd777IKX74K////AP///wAVf/wzG4L/6xuC//8agf7eBHjwCglj1QAWffwAGoL/ABN48gAOaegAGn36vhuD//8bhP//EX78Wf///wD///8AFXvxcBuC//8bgv//GoH9fBaA/AAVffgAFn/8ABiB/AAMcOMADWjdABJ0608bg///G4L//xuC/4r///8A////ABh68YQbgv//HYP//w508FkIa+cABmzoAAhy7QAXf/oAE332ABV++AAVfvgxG4L//xuC//8bgv+Y////AP///wAVfPVqG4L//x2E//8TdPCHBWLaAAtm4QATffgAGoH/AAdj3wAAX9UACm7pWR2E//8bgv//GoH+h////wD///8AE4D8LBuC/+Ubgv//GoH86gJi1RQRfvgAFX/8ABuE/wAQa94AAVvLBhV89s8dg///HYL//BF9+FD///8A////ABZ//AAagf53HYP//xuC//8af/zHFn/8LxN/+wEbg/8AFHDnIRt++LAdg///G4L//xiE/aITgfkD////AP///wADbO4AA2ztARZ//MEdg///G4P//xuC//sagv7SG4L/zxuC//cbgv//G4L//xuD/9gAObcSACWjAP///wD///8AAF7jAABc4gAKdvQTGoL+rhuC//8bgv//HYP//xuC//8bg///HYP//xqB/sgYgf4hAB6gAAAPigD///8A////AABf5AAAXeMACHb0ABN++QEYffpGGoH+pBqA/NMagf3VG4H+sBV57VUTgPwFGID+AAAfpAAAEI8A////AP/BAAD/wwAA/gMAAPAHAADgBwAAwAMAAMGBAACD4QAAh+EAAIfhAACH4QAAg8EAAMCBAADAAwAA4AcAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA////AP///wD///8AAHXnAAB15wAAdOYAAXnwAA1++gARgPwAF3ftAA1e1wAYg/8AGIH+ABB77QAagf0AEYH0AAFq4gAAVMwAAFbOAABWzgAAVc0AAFjQChh//MQbg//rG4L/5RuC/+Ubgv/nG4L/5Rh/+yP///8A////AP///wD///8A////AP///wAAdecAAHXnAAB05gABefAADX76ABGA/AAXd+0ADV7XABiD/wAYgf4AEHvtABqB/QARgPQABHHnAABdvQAAXrwAAF69AABbtwADbdlSHYP//xuC//8bgv//G4L//xuC//8WgPywDHv1AP///wD///8A////AP///wD///8A////AAB15wAAdecAAHTmAAF58AANfvoAEYD8ABd37QANXtcAGIP/ABiB/gAQe+0AGoH9ABB/8wAMe/UAA0C9AAARkgAAFJkAABudBhh79dIdhP//G4L//xuC//8bgv//G4L//xV8+jwRefgA////AP///wD///8A////AP///wD///8AAHXnAAB15wAAdOYAAXnwAA1++gARgPwAF3ftAA1e1wAYg/8AGIH+ABB77QAagf0AEH/zAAx59AAVde8AAAg7AAAACAALTKtyHYj//xuC//8bgv//G4L//xuC//8agf7CFX36ABV9+gD///8A////AP///wD///8A////AP///wAAdecAAHXnAAB05gABefAADX76ABGA/AAXd+0ADV7XABiD/wAYgf4AEHvtABqB/QAQf/IACnbwABuJ/wAIQ4QAAAIBARdz3sodhf//G4L//xuC//8bgv//G4L//xh//EUVfvwAFX78AP///wD///8A////AP///wD///8A////AAB15wAAdecAAHTmAAF58AANfvoAEYD8ABd37QANXtcAGIP/ABiB/gAQeuwAGoH8ABB/8wQKdvAFGoL/ARh++AAXevFXG4P//xuC//8bgv//G4L//xuC//8agf6uFn/8ABZ//AAWf/wA////AP///wD///8A////AP///wD///8AAHXnAAB15wAAdOYAAXnwAA1++gARgPwAF3ftAA1e1QAYg/8SGoH9YxiA+q0bgv7MGoL81RqA/NYbgv/PGoL/tBuE/+Ebgv//G4L//xuC//8bgv//G4L//xqA/VAYgPwAGID8ABiA/AD///8A////AP///wD///8A////AP///wAAdecAAHXnAAB05gABefAADX76ABF/+wAYe/MaFXDqghuD/+Qbgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8agv7AFoD9ARiB/gAYgv8AGIL/AP///wD///8A////AP///wD///8A////AAB15wAAdecAAHTmAAF58AANfvoAE3/7ThuC/+obhP//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xiB/kMYgf4AF335ABd79wAXe/cA////AP///wD///8A////AP///wD///8AAHTmAAB05gAAc+UAAHjvABiB/ncbgv/8G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L/thqF/xMAObsAABKXAAAWnQD///8A////AP///wD///8A////AP///wAAeewAAHnsAAB47AAMfPZoHYP//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4b/qAAxsgEAD4kAABCPAP///wD///8A////AP///wD///8A////AA9/9gAQf/UAE4D5NB2D//Ybgv//G4L//xuC//8bgv//G4L//xuC//8bgf7WFn78hxqB/lgagf5SGoH+cxqB/b0bgv/7G4P//xuC//8bgv//G4L//xuC//8dhP//DmzohQBVzwAAV9IA////AP///wD///8A////AP///wD///8AFoD9ABZ//Asagf7UG4L//xuC//8bgv//G4L//xuC//8ag//bD2rcQhRq3gcRfPsAGID9ABiA/gAYgP4AE4D4Ahh/+yMaffmxHYT//xuC//8bgv//G4L//xuC//8diP/8CU6aLwItXgD///8A////AP///wD///8A////AP///wAWgPwAGoH+ThuC//8bgv//G4L//xuC//8bgv//HYL/zRaC/ycJZNMAEmfZABF8/AAYgP0AGID+ABiA/gATgPkAGn76AAxy7wQMe/eRHYP//xuC//8bgv//G4L//xuD//8Zee+XEHHiAP///wD///8A////AP///wD///8A////ABd++gAagf2xG4L//xuC//8bgv//G4L//xqB/vEAbOkgAHHuAAll0gAUZ9oAEXz8ABiA/QAYgP4AGID+ABOA+QAagPwADnLuAAJz7gEbg//FG4P//xuC//8bgv//G4L//xuD//QEdfIg////AP///wD///8A////AP///wD///8AE335IBuC//Qbgv//G4L//xuC//8bgv//GIH8hAJ66gAEfe8ACWvVABRo3AARfPwAGID9ABiA/gAYgf8AEXv0ABJx7gASbusAFHHtABJy7zIagP35G4L//xuC//8bgv//G4P//xd9+3H///8A////AP///wD///8A////AP///wAPgPxSG4L//xuC//8bgv//G4L//xuC//YTfvsjE378ABN+/QARfPgAFXv3ABF6+AAYgP0AGID+ABqD/wASdPAAC1LTAAtV1wALVNYAC1TWABp8+bMbg///G4L//xuC//8bgv//GoH+m////wD///8A////AP///wD///8A////ABZs1X8dhf//G4L//xuC//8bgv//G4P/wxuD/wAbg/8AG4P/ABuD/wAdhP8AFoD9ABuD/wAagf8ADnnoAAh44QAKfOUACnzlAAp85QAIe+QAEHztah2D//8bgv//G4L//xuC//8bgv+x////AP///wD///8A////AP///wD///8AF3vzkRuD//8bgv//G4L//x2D//8RevWjAGnjAAFq5QABauUAAWrlAAFq5QABauUABGznABiA/QAXgPgAFX/2ABV/9gAVf/YAFX/2ABV/9QAYgPlVG4L//xuC//8bgv//G4L//xuC/7v///8A////AP///wD///8A////AP///wAXd+yQG4P//xuC//8bgv//HYP//xV9+acEce8ABnLvAAZy7wAGcu8ABnLvAAZy7wAKdfIAGoH9ABV+/AAVgP0AFYH/ABWB/wAVgf8AFYH/ABaA/lYbgv//G4L//xuC//8bgv//G4L/u////wD///8A////AP///wD///8A////ABd153gbhP//G4L//xuC//8bhP//GHv4zgxQ0AANUNIADVDSAA1Q0gAKTM0AC2biABqF/wAbgv8AE335AAdi4gAHWtwAB1vdAAdb3QAFWNoAEnLvdx2E//8bgv//G4L//xuC//8bgf6u////AP///wD///8A////AP///wD///8AEYD8SBuC//8bgv//G4L//xuC//8dgv/9DHjwNAN05wAEdegAA3LoABB69gAOfPgAFoD8ABqB/wAbhf8ADV3bAABUxgAAXcsAAF7LAABZyQAXd/THHYT//xuC//8bgv//G4L//xqA/Zf///8A////AP///wD///8A////AP///wATf/kUGoL/6xuC//8bgv//G4L//x2D//8SffKlAGLJAABlywAEd+MAEX35AA19+AAWgPwAGoH/ABqF/wATcvEACGXJAABXvwAAUb8AA3TjUx2F//8bgv//G4L//xuC//8bgv//E3/7Yf///wD///8A////AP///wD///8A////AA99+QAWgPyZG4L//xuC//8bgv//G4L//x2H//8IS81DBk7OABuF/wATffoADnz4ABaA/AAagf8AGoX/ABNv8AARYs4AD2LbAA9f4hIafvziG4P//xuC//8bgv//G4L//xqB/ucEde8S////AP///wD///8A////AP///wD///8AFoH+ABiC/zwbgv//G4L//xuC//8bgv//G4L//x2E/+0Wgf1SGH/8ABN9+gAOfPgAFoD8ABqB/wAahf8AFG/wAA9gygAXfPAlHYf/wRuD//8bgv//G4L//xuC//8bgv//G4H/fRqA/wD///8A////AP///wD///8A////AP///wAWfvwAFn78ABqB/rkbgv//G4L//xuC//8bgv//G4L//xuC//gagf6AE376IA58+AIWgPwAGoH/ABqE/wATcfAUFm7iUhp++OEbg///G4L//xuC//8bgv//G4L//xuG/+8VhvshFYX7AP///wD///8A////AP///wD///8A////ABB29AAQdvQAE3n3GBuC/98bgv//G4L//xuC//8bgv//G4L//xuC//8bgv/4GIH9zxqB/qMbgv+dG4L/vhuB/u0bg///G4P//xuC//8bgv//G4L//xuC//8dhf//CVzYXABDtAAARrgA////AP///wD///8A////AP///wD///8AAGXoAABl6AAAY+cACHTzRB2D//Ybgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuH/4QAM7IAAA+JAAAQjwD///8A////AP///wD///8A////AP///wAAXuMAAF7jAABc4gABbe4AFoH8RhqB/u0bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//4agf6KGob/AAA0tgAAEI4AABGUAP///wD///8A////AP///wD///8A////AABf5AAAX+QAAF3iAAJu7wAPf/gAD375IhqB/rwbg///G4L//xuC//8bgv//G4L//xuC//8bgv//G4L//xuC//8bgv//G4P//xuC//8agv/oFn/8URiA/QAahv8AADS2AAAQjgAAEZQA////AP///wD///8A////AP///wD///8AAF/kAABf5AAAXeIAAm7vAA9/+QAPfvgAFn/8BxVx8Fcbgv+zG4L//BuE//8bgv//G4T//x2D//8bgv//G4L//xuD//8YfPPRGH/5dRGA+xgVf/sAGID9ABqG/wAANLYAABCOAAARlAD///8A////AP///wD///8A////AP///wAAX+QAAF/kAABd4gACbu8AD3/5AA9++AAWf/wAEGfkABiB/gEYgf0pFXXybxuB/qIXe/i8FX/6vxuC/60agf6CGoT/QA9p0QcUeOwAD4H8ABV/+wAYgP0AGob/AAA0tgAAEI4AABGUAP///wD///8A////AP//+Af///gP///wD///8B///+Af//4gP//gAD//gAA//wAAf/4AAD/8AAAf+AAAH/ADwA/wD/AP8B/4B+A//AfgP/4H4H/+B+B//gfgf/4H4H/+B+A//gfgP/wH8B/4B/AP8A/4AcAP+AAAH/wAAD/+AAB//wAA//+AAf//4Af/</Image>
+<Url type="text/html" method="GET" template="http://www.qxl.no/search/search.asp" resultdomain="qxl.no">
+ <Param name="txtSearch" value="{searchTerms}"/>
+ <Param name="InTitleAndDesc" value="1"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>http://www.qxl.no/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/rakuten.xml b/browser/locales/searchplugins/rakuten.xml
new file mode 100644
index 000000000..85c5b81b3
--- /dev/null
+++ b/browser/locales/searchplugins/rakuten.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>楽天市場</ShortName>
+<Description>楽天市場 商品検索</Description>
+<InputEncoding>EUC-JP</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,R0lGODlhEAAQALMOAOefn9psbMswMMMQEPjj4/PPz++/v+OPj99/f9+AgMcgIM9AQOuvr////wAAAL8AACH5BAEAAA4ALAAAAAAQABAAAARf0EnJ0FqIze2YemCoaBsQniEwMeIloOQHJk1NvKDSnTRQNIZThddYPAiNk4UYCDQIpwux1ghEEUTGc6BkhWiLA1DokD3Ag5/1odvlFlzFAkdymFAn1caDH3FWFhh1EhEAOw==</Image>
+<Url type="text/html" method="GET" template="http://pt.afl.rakuten.co.jp/c/013ca98b.cd7c5f0c/" resultdomain="rakuten.co.jp">
+ <Param name="sitem" value="{searchTerms}"/>
+ <Param name="sv" value="2"/>
+ <Param name="p" value="0"/>
+</Url>
+<SearchForm>http://www.rakuten.co.jp/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/rediff.xml b/browser/locales/searchplugins/rediff.xml
new file mode 100644
index 000000000..445f08951
--- /dev/null
+++ b/browser/locales/searchplugins/rediff.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Rediff</ShortName>
+<Description>Rediff Search</Description>
+<InputEncoding>utf-8</InputEncoding>
+<Image width="16" height="16">data:image/x-ico;base64,AAABAAEAEBAAAAAAAABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAP8AAP8AAP8lJf9iYv%2BkpP%2FR0f%2Ff3%2F%2FR0f%2BkpP9iYv8lJf8AAP8AAP8AAP8AAP8AAP8AAP9JSf%2Bhof%2Fe3v%2F19f%2F7%2B%2F%2F9%2Ff%2F7%2B%2F%2F19f%2Fe3v%2Bhof9JSf8AAP8AAP8AAP8AAP9JSf%2B9vf%2Ft7f%2F8%2FP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8%2FP%2Ft7f%2B9vf9JSf8AAP8AAP8lJf%2Bhof%2Ft7f%2F%2B%2Fv%2F%2F%2F%2F%2BHh4dAQEBAQECTk5P%2F%2F%2F%2F%2F%2F%2F%2F%2B%2Fv%2Ft7f%2Bhof8lJf8AAP9iYv%2Fe3v%2F8%2FP%2F%2F%2F%2F%2F%2F%2F%2F9gYGAAAAAAAABwcHD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8%2FP%2Fe3v9iYv8AAP%2BkpP%2F19f%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F9gYGAAAAAAAABwcHD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F19f%2BkpP8AAP%2FR0f%2F7%2B%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F9gYGAAAAAAAABwcHD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F7%2B%2F%2FR0f8AAP%2Ff3%2F%2F9%2Ff%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F9gYGAAAAAAAABwcHD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F9%2Ff%2Ff3%2F8AAP%2FR0f%2F7%2B%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F9gYGAAAAAAAABwcHD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F7%2B%2F%2FR0f8AAP%2BkpP%2F19f%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F9gYGAAAAAAAABGRkafn5%2B9vb3%2F%2F%2F%2F%2F%2F%2F%2F19f%2BkpP8AAP9iYv%2Fe3v%2F8%2FP%2F%2F%2F%2F%2F%2F%2F%2F9gYGAAAAAAAAAjIyMyMjI3NzfDw8P8%2FP%2Fe3v9iYv8AAP8lJf%2Bhof%2Ft7f%2F%2B%2Fv%2F%2F%2F%2F%2BHh4dAQEBAQECTk5O3t7dAQEBubnDt7f%2Bhof8lJf8AAP8AAP9JSf%2B8vP%2Ft7f%2F8%2FP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8%2FP%2Ft7f%2B9vf9JSf8AAP8AAP8AAP8AAP9JSf%2Bhof%2Fe3v%2F19f%2F7%2B%2F%2F9%2Ff%2F7%2B%2F%2F19f%2Fe3v%2Bhof9JSf8AAP8AAP8AAP8AAP8AAP8AAP8lJf9iYv%2BkpP%2FR0f%2Ff3%2F%2FR0f%2BkpP9iYv8lJf8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="text/html" method="GET" template="http://search.rediff.com/dirsrch/default.asp" resultdomain="rediff.com">
+<Param name="MT" value="{searchTerms}"/>
+<Param name="sourceid" value="Mozilla-search"/>
+</Url>
+<SearchForm>http://search.rediff.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/reta-vortaro.xml b/browser/locales/searchplugins/reta-vortaro.xml
new file mode 100644
index 000000000..2491188ea
--- /dev/null
+++ b/browser/locales/searchplugins/reta-vortaro.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Reta Vortaro</ShortName>
+<Description>Ĝenerala reta vortaro en Esperanto / General online dictionary in Esperanto</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAQAAEABAAoAQAAJgAAACAgEAABAAQA6AIAAE4BAAAoAAAAEAAAACAAAAABAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMD%2BwACAgAAAuvOvAMDAAACFixEAwP%2FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZmZmAGZmZgZmQkZmZCJGZmYiJmZCZiRmZCYkZiJmJWZiRkJmImYlZkJmYkZCZiRmJGZkJmQiQ2ARZmFmZhERACJmQkYiIiQGImQkZiRERmYiREZmJGZmZiIiRmYiImZmImYkZiRmZmYiZiRmJERGZiIiRmYiIiRgZmZmAGZmZggYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgYEAACgAAAAgAAAAQAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgIAAAMDAAADA%2F8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzMzMzMAAAAzMzMzMAAAAzMzMjMzMwAzMzMzMzMwADMzMxETMzMzMzMiIjMzMwAzMzIREjMzMzMhERESMzMDMzMxEREzMzMyERERESMzMzMzIRERIzMzIREjMhESMzMzMxESERMzMxESMzMhETMzMzIRExESMzMREzMzMREzMzMxESMhETMzERMzMzERMzMzIREzMREjMxESMzMhETMzMxESMzIREzMhESMyERIzMzIREzMzERIzMhEREREjMzMxESMzMyERMzMhERESMzMwMhEzMzMxEjMzMyIiMzMzADMzMzMzMzMzMzMzMzMzMwADMzMzMzMzMzMzMzMzMzAAAyEjMzMzISMyERERESMwADMREzMzMhETMRERERETMwAzERMzMyERIzERERERIzMDMxETMzIREjMxETMzMzMzMzMREzMhESMzMREzMzMzMzMzERERERIzMzERMzMzMzMzMxERERESMzMxERESMzMzMzMRERERESMzMRERETMzMzMzERMzMhETMzERERIzMzMzMxETMzMREzMxETMzMzMzMzMREzMyERMzMREzMzMzMzMzEREREREjMzERERERIzMwMxERERESMzMxERERERMzADMhERESIzMzMhEREREjMwADMzMzMzMzADMzMzMzMzAAAAMzMzMzAAAAMzMzMzAADwB%2BAPwAGAA4AAAAGAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAYAAAAHAAAADwAAAA4AAAAGAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAYAAAAHAAYAD8AfgDw%3D%3D</Image>
+<SearchForm>http://www.reta-vortaro.de/revo/</SearchForm>
+<Url type="text/html" method="GET" template="http://www.reta-vortaro.de/cgi-bin/sercxu.pl" resultdomain="reta-vortaro.de">
+ <Param name="sercxata" value="{searchTerms}"/>
+ <Param name="kadroj" value="1"/>
+ <Param name="from" value="mozilla"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/salidzinilv.xml b/browser/locales/searchplugins/salidzinilv.xml
new file mode 100644
index 000000000..464a639a1
--- /dev/null
+++ b/browser/locales/searchplugins/salidzinilv.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Salidzini.lv</ShortName>
+<Description>Salidzini.lv - Latvijas interneta veikalu mekletajs</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAABoBQAAJgAAACAgAAAAAAAAqAgAAI4FAAAoAAAAEAAAACAAAAABAAgAAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wDfn0gA7s+kAPbn0gDmtnQA6sONAPrz6QDz27wA46laAOSvaADpvIAA7MiYAPz59QD47NwA8dayAPXiyQDhpFAA+/bvAPnv4gDpv4YA4aVWAP38+gDv0qkA897BAPfp1wDx2LcA46xkAO3LnAD15c0A4KFMAOi6fADrxZEA+vHlAPTgxADqwYkA7s2hAPv07ADlsGsA7MaUAP39/QD8+PIA6b6DAOGlUwDy2rkA8di0AOKnVwDgoEoA4KJQAPry5wDkrmQA9+rZAPfo1QDmtXIA5K5mAP7+/gDhok0A/PfwAOi9gQD+/fwA/fz8APz69gDgpVQA+/XtAPr06wD68OQA9uTNAOm7fwD05M0A6Lx/AOm9ggDqwIgA68OOAOvEkADsx5UA7cqbAOzKnADuzqMA359JAP7+/QDgoEkA/f38AOCgSwD9/PsA4KNQAPfq2AD36dYA9eXOAOi8gADpvoQA6b+FAOvFkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADdBNS9QTk5QUAJQThFEKAE3Ow8KK1IuGz4CUFBQHz8BNzcSSiwlPDdTTDhOUBVXATc3NwdTNzc3NwQJUFAeIgE3Nzc3Nzc3NzcYMlQvMBABNzc3Nzc3NxYEBkMFNiYOATc3Nzc3JR0nWEVFRVgYOwE3NzcWBAwqRUVFCzoXPTcBNzc9FzpFRUVFFBw0Uzc3ATc7GFgLRUUqTRk5Nzc3NwE3IVtFRVhLMTc3Nzc3NzcBN1UqRQsjMzc3Nzc3Nzc3ATcZRkVFWkI3N08NVkA3NwE3E0hFRUVJLQgPJEcYNzcBNxYaRUVFRUVFRVhFIFE3ATc3KQM6RVhYRVgLC1kSNwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAAAABAAAAAAQAIAAAAAACABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A3Zo+AP7TmQD15c4A6rVuAPOtTwDqwooA8de0AP/z5ADiqV0A+sZ/AOzKnAD/47cA5qRKAOe7fAD7+PIA//DRAPPewQD4v3IA9LNcAO/RqAD/6sUA+OvbAPCmQwD7zIoA6bBjAP/ZogD//usA4aRUAOvGkwDuwYIA355GAPPPngDnqVYA///0APr07ADlsGoA+uTGAPvp0QD57+IA6K1cAOa0dADwxIoA8apJAPDUrgDgoU0A9OHHAPLbvAD32rUA6L2DAP37+gD+8N4A9unVAO7OogD44L4A+smFAOStYwD/9ukA5aVPAPvmywD0sVcA/+7MAOi4eADls28A/e3aAOKnVwD8z44A68OPAPny5gD/+vMA3pxCAOzIlwDqs2oA6cCGAPz69gD///sA+/buAPXjygDpvH8A8tm4AO/ChQDw0qsA7cyfAP/48ADoq1kA9ufSAPHVsQD9/f0A8atNAOSrXwDlokcA36BKAOCjUAD47uAA89y+APLOmwD/8uEA6K5fAOWvaADz38QA7cB/AO7PpQDxqEUA5rVxAPzKhwDrwIkA68ONAP/05wDmpUwA57l6APz38QD78+kA8KlIAOGmVQD979wA9+zdAPfp1wDpun0A6b6CAO3JmgD//v0A/v37APz7+ADenUQA/Pn0AN+fSADyqksA4qhcAOivYQD048wA5bJtAPrlyADqtGwA57Z0APLaugDx2bUA6b6FAO/DhwD/05sA79KpAOvFkQDsx5UA9ujTAPjgwADou34A6sCIAPHVrwD69e0A4KBLAOChSwD+9egA5qRLAPrz6ADjrGAA/OrSAOSxaQD14skA9ODFAPPcvADounsA6Lx9AOi9gADpvYIA7r+AAPHWsgD7yIQA7sGEAOq/hwD7y4kA8NOtAOrBiwDpwo4A68SPAO/PpADty50A/v7/AP3+/gD+/f4A/v78AP38/ADemj8A/fz5AN6bQQD9+vgA/fv3AN+dRQD9+vUA3p1GAP/79ADgn0cA/PjzAPCnRQD89/IA+/fxAPz38ADxqUgA8atKAPn07ADho1EA8qxNAPKsTwD68eYA4qdVAPnw4wDjp1gA+vDiAOapVwD+8uAA56pXAPnu4QD98d4A6KxbAPjt3QDjql8A/e7bAPjs3AD46toA5K5kAPfo1AD359MA++fMAPXkzQD24soA57h5AOi4egDnun0A9N7AAOm7fgDz3b8A6Lt/APLcvwDnvH8A8ty9AOm+gwDqvoYA8di3APvJhgDy2LUA6sGJAOnBiQDvxIgA8dSuAOzGkgDw06oA7MaUAOvHlADw0agA79GnAO7QpwDtyJgA7MmYAO7PowDuzaMA7cqbAO7NoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALGwl5NuQly6ICB8fHy8ICAgfHwgfn4gR8uSZMawsLAAAQEBEYsTFAZ/ZxjAxFnJWXHAccUGBsgsPQsNHAEBAQCwAQFhMSuF1FUi0SkahQVJgc87mJgODlttYoo3IwGwALIBARBWruaGT2t4rlKlCFf5rEBCliB+fn6VY1PTAbAAWAEBeZnlU6/uBNJNEH1Lu5SCDINdfCB+frqARCcBsABYAQEBuNgvTtJ9AQEBAQEBAZTnic26IH4gtx2JhAF5AFgBAQF6b3CUuLABAQEBAQEBfd7zWr6+viC1x3cmAXkAWAEBAbB6tLABAQGwsLABAQF9BEjalr4gfAJddyYBeQBYAQGwsLCwAQEBAQEBAQEBesrlrZxyXS5+R3KoPAGwAFgBAbCwsAEBsLABAQEBAbCU3hWShmOagB2VCkSbAbAAWAEBsLCwsAEBAQGwWHp9RQQtHqN2P2glOdYq/TQBsABYAQGwsLCwAQEBekskF53sZo3q5k9PoIYq4UgSVAGwAFgBAbCwAQEBWH1w2E6l/quJok+hT5Gg4DJ45Sh5AbAAWAEBsLABAVhN2WSl/I2j4OBuoZGhbm6RHlBeewEBsABYAQGwsHlLRQQIr2tKopEPD6Ch5E+iqPaTjxABAQGwAFgBAQFYTTXp+R6JoaCgoaEPbqBPqI4VX3VNWAEBAbAAWAEBsMKPLR5K6KGhDw8PD08y7x42UFaZS3mwsAEBsABYAQF9dIiNduBuD6EPbm6Rq3hSh53Vw3kBAbCwAQGwALEBAcwSr+puoOSRoU93744VX9zMlLl5AQEBsLABAbAAsQG9BPgHoW6RT5Gi76+lntvMbzOwAQEBsLCwsAEBsABYATowjqKhD5GhovBT416/M1iwsAEBAQGwsLCwAQGwAFgB0JMHoKGRkaGor1/SMwEBAQEBAQEBAQGwsLABAbAAWAFz9KhuoZHkkWuq28EBAQEBAQEBsHl5sLCwsAEBsABYAUH36+ChoXbia5MXMwEBAQEBAQG5EBC4erCwAQGwAFgB11KobqGhoQ/vrt9wM3p6tLZ7TV51F3BLeQEBAbAAWAE0qmqgoaGh4ur18p3YzijSF3VOUFcwdU2zAQEBsABYAWxQ86KhoaGRT0oeZgifn4cIqjbzrVMSzHoBAQGwALEBRp3/qA9ukU+hT+qrHvv6SB5EkuaR74w1MwEBAbAAsQEBCZAh8WUfHx8fH6enUVGnp6cfpKQfYN1MAQEBsAABAQEBHBYbQxmpGampae04ODjtaWlpaaYDPgEBAQEBALGwsFgkTmYHog+hoaGRkQ8PD5GRkU92P2pQcLGwsLAA/////wAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAE=</Image>
+<Url type="text/html" method="GET" template="http://www.salidzini.lv/search.php" resultdomain="salidzini.lv">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="utm_source" value="firefox-plugin"/>
+</Url>
+<Url type="application/x-suggestions+json" method="GET" template="http://www.salidzini.lv/suggested_search.php">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="utm_source" value="firefox-plugin"/>
+</Url>
+<SearchForm>http://salidzini.lv</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/sapo.xml b/browser/locales/searchplugins/sapo.xml
new file mode 100644
index 000000000..d5f7803a3
--- /dev/null
+++ b/browser/locales/searchplugins/sapo.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>SAPO</ShortName>
+<Description>Pesquisa SAPO</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABXmHT/EnM8/wd6L/8EdRj/BHUY/wd6L/8Sczz/WJh0/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACt+UP8FaTL/B4I8/wmoTv8JO03/Ew62/xMOtv8JME7/CahO/weCPP8GajL/K35Q/wAAAAAAAAAAAAAAABJzPP8Hgjz/Cq5P/wm6Vv8BIwj/DAB8/xcG3v8ZB+b/DACA/wEUBf8JtVT/CahO/weCPP8Sczz/AAAAAECTZP8IkEP/Ca5Q/wquT/8CHA3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AhwN/wmuUP8Krk//CJBD/0OUZv8IkEP/CahO/wquT/8FYCz/CJBD/wd3N/8DIA//AQwE/wEMBP8CHA3/BWAs/wiQQ/8FYCz/CahO/wquT/8HjkL/CahO/wSIPP8Hdzf/BqQ+/wakPv8GpD7/BqQ+/wakPv8GpD7/BqQ+/wakPv8GpD7/BqQ+/wN9N/8DfTf/CahO/1fAhv8Vm0//Ao82/wOmR/8Pok//BqQ+/wSeNP8Dpkf/A6ZH/wSeNP8GpD7/D6JP/wOmR/8Cjzb/FZtP/13Ejf8AAAAAa8eW/13Ejf8TqFP/AZgu/x2Zfv8xxdP/D6JP/w+iT/8xxdP/HZl+/wGXLP8Sp1L/aMiV/3fLof8AAAAAAAAAAAAAAACc1rv/u+rN/3G0nP8E8Pf/A/D3/xfNz/8Xzc//A/D3/wPw9/9ws5v/wezS/5zWu/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGQkH/BPD3/wAkJv8D8Pf/A/D3/wAsLv8D8Pf/RkJA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARkJB/0vw9v8E8Pf/NrjA/zi1vf8D8Pf/SPH3/0ZCQP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGQkH/etfY/0ZCQf9GQkH/edbY/0ZCQP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEZCQf8AAAAAAAAAAEZCQf9+fHz/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////////wD///wAP//4AB//8AAP//AAD//wAA//8AAP//gAH//8AD///wD///8A////gf///9n////////w==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://pesquisa.sapo.pt/livesapo?q={searchTerms}" />
+<Url type="text/html" method="GET" template="http://pesquisa.sapo.pt/FF2" resultdomain="sapo.pt">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="enc" value="utf-8"/>
+</Url>
+</SearchPlugin> \ No newline at end of file
diff --git a/browser/locales/searchplugins/seznam-cz.xml b/browser/locales/searchplugins/seznam-cz.xml
new file mode 100644
index 000000000..66da3c0fc
--- /dev/null
+++ b/browser/locales/searchplugins/seznam-cz.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Seznam</ShortName>
+<Description>Vyhledávání na Seznam.cz</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFGSURBVHjalNI/KEVRHMDxc+5DKZM3qNvbXH+ysSBFGMSgLDJKseCtymKwSK+UmVEmTG+RQQxKJgzkleVa3vAGIqXn+p763Tqdrnv51ad3T+fc3/vd3/npKIpUrVBQKdGCbvRhDPP4NButYagaEl5oloMjGEA7fGv/ERvxQjsVzGALnSkVhegwVbgVjONYZccJ6vHCTrCUcPgbb1L2Pc5QxldSglvMOgmeMYeb38rxrOcS9p1908B1FDGEJjeBTrjGKaxhOOEPD3GJI1RNE3XKHAxiQRL6zt4utknw4qV0+wqL6ME0DvAue8W4X/oPk2iHuf89+bwaFeQ99b94kms00eheY1b40pNNWa9mJcihCxOYxKicP8cOLuwEGm0I0J8LglK9UnngOY8PXGMFpzJcyp1E0+1eSfTKy8v83skIV9O+60eAAQAd5lci6mlq3QAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://suggest.seznam.cz/fulltext_ff">
+ <Param name="phrase" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://search.seznam.cz/" rel="searchform" resultdomain="seznam.cz">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="sourceid" value="firefox"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/slovnik-sk.xml b/browser/locales/searchplugins/slovnik-sk.xml
new file mode 100644
index 000000000..0b1fdee2f
--- /dev/null
+++ b/browser/locales/searchplugins/slovnik-sk.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Slovnik.sk (EN-SK)</ShortName>
+<Description>Prekladové slovníky - Slovnik.sk</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAABoBQAAJgAAACAgAAAAAAAAqAgAAI4FAAAoAAAAEAAAACAAAAABAAgAAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD5cRMA/rqPAPmWUgD+28cA+YIzAPmqcQD+7OEA+8mlAPifYgD5jEIA9XgjAP6xgAD++O0A/Na4APlnBwD/49IA/aJrAPp+KgD6w54A/vv3APl0HAD7hzoA/fDoAPucVwD6rnoA/Nm/APzo1gD1k00A/XggAP7o3AD4m1wA+qJmAPqUTAD6//4A9nIXAPp5JAD7iD8A+6t2APh4HwD2fCgA+7ODAP/dywD/+PQA+/XuAPvr2wD//PsA/uTWAP3dwAD8pmoA+IAtAPeHOwD3iEAA+5NQAPyBMQD8hDgA/dS7AParcwD9//wA+nYiAPd7IwD/38gA/vTtAPdyGgD+694A+XMfAPR4IAD5fCcA/P3/AP3k0AD7gTUA/NzFAPp4HQD8170A/u7nAP/q2wD5eh4A93khAPqcWwD8sH8A//79APhvEwD5dRoA+ncgAPh3IgD+3ckA94ArAPiJOwD9//8A//3/APZ4HwD87+cA/+vgAPl5IAD7eCEA9nciAPV5IQD3eCMA+XgjAPZ5JAD5fSkA+9i+AP3VuQD4i0EA+JdTAPv//wD+/v4A/P7+AP7//QD8//0A///8AP/9/AD6eB8A+HcgAPl4IQD/4tEA+YM0APiJQAD7lE8A//7/AP3+/wD8/v8A///+AP3//gD+/f8A//7+AP78+wD2eCAA93kgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFVhgChJJWBNKE48XkOAc1RjawF4WXoBWVlZe2trWQFgKAFZfll7CCtmdG8BfFp9c2B+bQFWMiYCEEQZOWoBb2FzeFkwdVI3OioEW0dIeFFzVTt4MXJoBVkBRhcWB2x4KGJZeHARP3hBZxo0UycBeGOAAXoBaxRPC15CPTYfe1lzTntaLCA8ZIB2IQkVe3gBKGMBAQ9yBhI+GHsvXAF5AV9OblkbHldQcAFKWDguAXuBXnhRfwpAMx1pZSQ1THt4clV7fFlLAyITKXcNHG1Ze3GAAXtrAXkOXV0tAXt4ASNzY3hFAWt7AXt4eAEBfH0BPF5yYXNfgGNODF5eVWMoTlsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAAAABAAAAAAQAIAAAAAACABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A+m8VAPy4igD6lFAA/9vFAP6EMQD97eIA/adsAP3LqAD2YAAA/8eVAPh6IwD5iUEA//jvAPqvewD+49MA/NO3APudXwD/rl4A/8SLAPq9lgD7xaEA9YIzAPl9LAD84csA+rOEAP/p2wD5mVYA+ql1AP769wD4kEoA/vLpAP3XvgD5dh0A/aJmAPmGOQD/y54A+WsPAPzOsgD/9OMA/ocrAPy7kAD5gi8A+41GAPzCmwD3chgA+H4oAP3QrQD628MA+aRrAP726gD/4NAA+6pxAPzIpAD/xZIA//z7APdvEQD6gjMA9pZSAPyWVAD9694A/bGAAPdkAgD/+/MA//fzAPd3IAD5iD0A/H0pAPzjzwD6oGIA+6x4AP3u5gD+6dcA+XsoAPzm1AD91LsA/8qYAP/fyQD/2sEA+o5BAPqSTQD90rQA/J1bAPz9/AD+9O0A/+zgAPuFNwD3jUMA/MieAP3AmAD8vpMA/LSGAPlzGgD7dh8A94AvAPrZwAD/xI0A+IQ2APvJqgD3m1kA+7F9AP718AD2dx4A/+7iAPl6IQD2dyMA/M6sAP3HowD5rHQA/Kp2APyuewD5cRcA/ebVAPd+KgD5fywA/+PSAPaCMAD5j0cA+Z1cAPugXwD6o2cA/KJqAPq5jAD3qnEA/LeHAPywewD6soAA/f//AP/89gD++PUA928UAPZyFQD4dB4A93kgAPd7JgD/4M0A+oEwAPeBMQD4gzEA/dfBAP3avgD917sA/dW2APrQsQD6mFMA+ZlaAP/GkgD9vI4A/LiNAPyqcwD9s4QA/f77AP/7+QD5bxAA+XchAP/m2QD9hC8A+5xdAP/9+QD/8+wA/fTrAPhwFgD98ucA//DnAP7q3gD4fCUA+IEsAPmGNwD92sQA+os/APiMRQD+07kA/dO2APvRtgD7zq4A+ZhVAPuhZAD6pWsA/KlvAP26iwD6rnoA/f79AP/+/QD/+vUA//nyAP/68AD4cRQA//buAP706QD6eSIA/O7kAPl8JwD+6uAA+n4rAP7o2QD6gjEA+IIzAPyFMQD6hDMA++PRAP3hzQD5hjsA/N/KAPqIPAD7iD4A+Io/APuKQgD5jkUA/NS5APqWUgD7yqkA/MimAPmcXgD7xqIA/J9iAP/LmwD/yJcA+qRoAPvCmQD/xZQA/b+WAP27kgD6tYYA/LB/APizggD//P0A/fz7AP36+QD/+vcA+XcfAPd5IgD4eCMA/+7hAP3s4AD4fywA/oUrAPmALgD+59cA+oEyAPaDMQD949EA94MzAPzhzwD+2sIA+Y1DAP3awAD9y6oA/8qWAP/GlwD6p2wA/b6YAPq+kwD/xIwA/LaKAP79/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGopxvDw8IsrK8Q6xcXFi4uL8vLF74mLdXV18IsG7JsAwhNhC5OTk5OTkwv52dn4TU1N2Qvc3JOT3JOT3GETRACJ/Si5ubm5uUBAuLhAQLm5ublAQEBAubkODrm5KGGKAIvYAQEBAQEBgIABAQEBAQGAAQEBAQEBAQEBAQEBJcUAKwuBAQEBAQGAATi8pTQFTNEhiGiBAQEBgAEBAbjZigBfNw4BAQEBt2buIWyVbtc80p6WlDCIpOOAgAEBDpPwAIo3DgEBATik9N2zPM+JaXBw5itQeH2tB1SAAQEOk/AAijcOAYD/pKzgUYleXr4v63NKDGmnLG4nvzgBAQ6TiwCJN7kBgB40XEOiOSJXHHzhfzWwqIaFzW8hoAEBuZOLAIk3ugEBDo6xLgqDDW42Mcjx9Nt46yZeBPxLtgFAk4sAijcOAQEOjTWL5x9cjCDj/5mg9mXMAl2obWBUAbiTiwDFN7oBAbhJNn5v1hCZAQEBQT1StQ1du3N5Ef8BgdyLAMXcuQEBt7hWdPHqZkGgG06tLbUcYl0izG+ptwG43DoAdTe5AQEBAQEBgurLrWwqfrEEQ1/nXXMSbKUBAUDcOgB1N7oBgIABt0HzFg9G0vXHwJub6HIknvsQggEBudyLAHWTugEBATg9rhqSQ3JnhDmihXMNd3/3EEEBAQG5k4sAdZO6AQG3IK8yykroh4dy8quwst/TRbz/AQEBAbmTiwCKN7kBAbnJfyRwhRgNHPoaFdMhcaCAgAEBAQEBuZPEAO/cuQEBva15SrsYLHpaT8NIZrhBoAcHVTgBgAG53MQAije6AQGja1Nemu3SlwWfAQEBgsnU/t7Ro7YBAbqTiwB1k7qAgFWOCESapnZvTFUe5WgRD3bCLANLmAEBupOLACuTugEBHnEDQ7su67Df05BrW9qoOT9KRxmfAQG5k3UAKzcOAQG3Zq4yYkJCilg7ZLDQiSIuQh8qcZ8BAbncigCJNw4BAQG3PWMdUTovhmdCQua+wqrVe40gtwEBuTeJAIo3DgGAgIDkwUzeCJGqx+3FztIjlwlFoZ8BgAG6k8QAijcOAQG2gAHioHSPWbQ+b+B7FlJOaLgBAQEBgLqTxAB1C4EBAQEBAQEBAR4z6RucGwehHgEBAQEBAQGAgdmLAMXYAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBJToAif0ouroOuroOublAQEC5ublAQLhAubm6ug66uihhiQBEExQ3kzc3Nzc3N9wLCwsL+QuTN5M3N9w3NzeTFBNEAL4pnXV1dYmJiSt1ijqLi/AXxYsrK4qKxe+JKyudKYYA/////wAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAE=</Image>
+<Url type="text/html" method="GET" template="http://slovnik.azet.sk/" resultdomain="slovnik.azet.sk" rel="searchform">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="l" value="en-sk"/>
+ <Param name="sourceid" value="firefox"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/sslv.xml b/browser/locales/searchplugins/sslv.xml
new file mode 100644
index 000000000..d0e687e9f
--- /dev/null
+++ b/browser/locales/searchplugins/sslv.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>SS.lv</ShortName>
+<Description>SS.lv - Lielākais sludinājumu serviss Latvijā</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAqgADAIAAAgAAAAAAAAAAAIAAAgBVAAMAAAAAAAAAAACqAAMAgAACAAAAAAAAAAAAgAACAKoAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAACF4sACxqGCGEZhQesG4YIyhqGCMYahgilGYQIPgAAAAAggAAIG4UJVhqGCKUahgjIGoUIxxqGCKgZhQdHAAAAABqGCGEemQn/HI8J/xqGCPIahwj+HJEJ/xqICP8ciQk2GocHRh2WCf8ckQn/GoYI9RqGCP4ckQn/G4oI/xqFCVgahghjG4cIthmGCD0kkgAHEYgADxmGCMkdlQn/GoYJkRqEB00bhgjAGocIRBSJAA0ggAAIGoYIuRyRCf8ahgi+FIkADQAAAAAAAAAAEYgADxuFCF4ahgjgHJEJ/xqFCJ0AAAABAAAAAAAAAAAcjgAJGYQJURqGCNccjwn/GoYIvQAAAAAXgAwWG4YHjxqGCOYbjQj/HJAJ/xqHCP8ahQdFAAAAACKIAA8ZhgiDGoYI3xuMCP8ckAn/G4oI/xqHCVcbgAkcGoUI2BySCf8ckAn/GoYI+BqFCLgbhQhBAAAAABuGDRMahgjLHJEJ/xyQCf8ahgj9G4YIwBuFB0sAAAAAG4cJcxySCf8ahgnwGocIiBuGByYAAAAAAAAAAAAAAAAahQdrHJAJ/xqGCPYahgmTHIgGLQAAAAAAAAAAAP8AARmGCHodlAn/GoYHzxWAAAwAAAAAHIUGLhuGB48YggYrGoUHaxyTCf8ahgjhGIYMFQAAAAAagwcnGoYIhxuGB0wXhQYsGoYH7xyRCf8ahgj2GocI6RuNCP8dlwn/GoUHRRiGDBUahgjqHJIJ/xqGCPcZhgjnG4oI/x6aCf8ahwhiAAAAAByIBi0ahgigGoYJzxmGCNMbhgetGocIZBKAAA4AAAAAGoMHJxqGCJwahgnQGoUI2BqGCbMahgduHocPEQCAAAIAAAAAAAAAAAD/AAEA/wABAAAAAAAAAAAAAAAAAFUAAwAAAAAAAAAAAIAAAgBVAAMAAAAAAAAAAAAAAAAAAAAAAIAAAgBVAAMAAAAAAAAAAACAAAIAgAAEAAAAAQAAAAAAgAACAFUAAwAAAAAAAAAAAP8AAQCAAAQAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAOfnAAAZmQAA/v4AAAEBAAAAAAAAAAAAAGBgAACAgAAAAQEAAAcGAAAICAAAAAAAAICAAABnZwAAmJgAAOfnAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYIFBqGCEYahgh3GoYIlBqGCKMahgifGoYIjRqGCGkahgg0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqGCAgahgg6GoYIbBqGCI8ahgifGoYInxqGCJAahghvGoYIOhqGCAMAAAAAAAAAAAAAAAAAAAAAGoYIJBqGCIsahgjqGoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgi5GoYIKwAAAAAAAAAAAAAAABqGCBMahgh2GoYI1xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYIyRqGCEAAAAAAAAAAAAAAAAAahgi9GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgjsGoYIMwAAAAAAAAAAGoYImhqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI+BqGCEwAAAAAAAAAABqGCMEahgj/GoYI/xqGCP8ahgj/GoYI8hqGCOAahgjnGoYI/xqGCP8ahgj/GoYI/xqGCP8ahgjLGoYIBwAAAAAahgilGoYI/xqGCP8ahgj/GoYI/xqGCPkahgjkGoYI5xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI6BqGCCMAAAAAGoYIwRqGCP8ahgjzGoYImRqGCFIahggZGoYIBxqGCAwahghKGoYI2hqGCP8ahgj/GoYI/xqGCP8ahgg1AAAAABqGCKQahgj/GoYI/hqGCKkahghcGoYIIxqGCAkahggLGoYIQRqGCMoahgj/GoYI/xqGCP8ahgj/GoYIZwAAAAAahgi9GoYIoxqGCCIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAahgiAGoYI/xqGCP8ahgj/GoYI/xqGCFIAAAAAGoYIpxqGCLkahggzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYISRqGCP8ahgj/GoYI/xqGCP8ahgiHAAAAABqGCBkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYIEhqGCMAahgj/GoYI/xqGCP8ahgj/GoYIUAAAAAAahggfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqGCAYahgicGoYI/xqGCP8ahgj/GoYI/xqGCIUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYILhqGCIsahgjhGoYI/xqGCP8ahgj/GoYI/xqGCP8ahggwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqGCB0ahgh2GoYI0BqGCP8ahgj/GoYI/xqGCP8ahgj/GoYIYgAAAAAAAAAAAAAAAAAAAAAahggHGoYIWRqGCMcahgj8GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYIxRqGCAUAAAAAAAAAAAAAAAAAAAAAAAAAABqGCEIahgizGoYI8hqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCOYahgghAAAAAAAAAAAAAAAAGoYIUBqGCNEahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCO4ahggxAAAAAAAAAAAAAAAAAAAAABqGCDUahgi8GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj7GoYITAAAAAAAAAAAAAAAABqGCIIahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgi3GoYIMAAAAAAAAAAAAAAAAAAAAAAahghgGoYI9RqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYIzBqGCEUAAAAAAAAAAAAAAAAahghhGoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCPMahgimGoYIQgAAAAAAAAAAAAAAAAAAAAAAAAAAGoYIOxqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj6GoYIvhqGCFIahggFAAAAAAAAAAAAAAAAAAAAABqGCMwahgj/GoYI/xqGCP8ahgj/GoYI/xqGCM0ahghuGoYIHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAahgi2GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgjcGoYIgxqGCCYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYI8RqGCP8ahgj/GoYI/xqGCPMahghmGoYIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYIBxqGCN8ahgj/GoYI/xqGCP8ahgj8GoYIgBqGCAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAahgj5GoYI/xqGCP8ahgj/GoYIkwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAahghMGoYIVQAAAAAahggMGoYI5BqGCP8ahgj/GoYI/xqGCMsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYIOBqGCGkahggEAAAAABqGCOEahgj/GoYI/xqGCP8ahgjTGoYIORqGCAIAAAAAGoYIDBqGCDYahghvGoYIyxqGCP8ahgiKAAAAABqGCAIahgjTGoYI/xqGCP8ahgj/GoYI6xqGCEMahggEAAAAABqGCAUahggsGoYIYxqGCLoahgj/GoYIuwAAAAAAAAAAGoYIkxqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI1xqGCMwahgjkGoYI/xqGCP8ahgj/GoYI/xqGCIIAAAAAAAAAABqGCHMahgj/GoYI/xqGCP8ahgj/GoYI/xqGCNkahgjKGoYI2xqGCP8ahgj/GoYI/xqGCP8ahgiwAAAAAAAAAAAahggOGoYIxRqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYIiAAAAAAAAAAAGoYIBRqGCLIahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCLgAAAAAAAAAAAAAAAAahggNGoYImRqGCPgahgj/GoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI6BqGCJoahggrAAAAAAAAAAAAAAAAGoYIAxqGCIUahgjzGoYI/xqGCP8ahgj/GoYI/xqGCP8ahgj/GoYI/xqGCPMahgirGoYIQAAAAAAAAAAAAAAAAAAAAAAAAAAAGoYIKBqGCGgahgiUGoYIthqGCLoahgibGoYIdxqGCEsahggUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqGCCIahghkGoYIlRqGCLoahgjBGoYIqRqGCIIahghVGoYIHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////+AP4AeAA4ADgAGAAYAAgACAAIAAj8CPwL+Av4D+AP4A8AD4AOAB4AHAA8ADgA+AB4A/gD+A/wD/g/kD+ICBAIGAAYABgAGAAcABwAHwB/AH////////////////////////////////</Image>
+<Url type="text/html" method="POST" template="https://www.ss.lv/lv/search_result/index.html" resultdomain="ss.lv">
+ <Param name="txt" value="{searchTerms}"/>
+ <Param name="from" value="firefox-plugin"/>
+</Url>
+<SearchForm>https://www.ss.lv</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/sztaki-en-hu.xml b/browser/locales/searchplugins/sztaki-en-hu.xml
new file mode 100644
index 000000000..80a90203b
--- /dev/null
+++ b/browser/locales/searchplugins/sztaki-en-hu.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>SZTAKI angol-magyar</ShortName>
+ <Description>Keresés a SZTAKI angol-magyar szótárában</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAN1wAADdcAAAuTgAAITYAACA1AD0wTwCgP2kA1EZ0AOpGdADqPmgA1i5NAKUZKgBDERwAABQiAAAUIgAAFCIAADdcAAA3WwAALUsACjZaAJhNgAD+VY0A/1SMAP9TigD/U4oA/1SMAP9VjgD/TH4A/yxJAKESHgAQEyEAABQiAAAvVgAALlQACjppALlKhAD/SYMA/0iBAP9IgQD/SIEA/0iBAP9IgQD/SIEA/0qEAP9MhwD/LFIAwwsZABARIAAAgZliAIilYJOVu2L/lbpj/pK2YP6NsFr+jbBZ/oyvWP6Mr1j+jK9Y/oyvWP6Mr1j+jLBY/pC1Wf9WZz+iIScXALKnoTbFu7v9zMTI/8vDyP/Y1dv/+/r///36////////////////////////////////////////8vHz/1NTVUGOfLudkF5Y/3oqA/9sFgD/bCsV/8K+vP/3+Pj/9/f3//////////////////////////////////////+Xl5eompL11mJT5P+Laar/m1VA/38cAP+Vc2f/2uDg//T08v/5+fj/////////////////////////////////1tfO2fXm1e3p5vr/jYTw/2pc5P+ifqD/3sfC/2Zeuf/c3dn/5OLv/4B13f2Dd+P+hXni/oV54v6FeeL+hXnk/nluy+rUxdjs5+P4/+zq+v/EvvP/w8H8/+De/f8+KdX/ST2k/+3v4/+Fe9j/DADG/xgBzP8ZA8v/GQPL/xkCzv8cCLnrVUTd1C0W0P8yHNH/NyLS/zci0v8yHNH/LBXR/x8HzP9ZT6b/+fzp/4h+2v8ZAsz/IAvQ/yEL0P8iC9X/Hwy112JR3JgdBMz/IgrN/yAIzf8fB8z/HwfM/ykSzv8sFdL/GADH/2hfqP/q69//cGXG/zUkvP87K77/Py/F/ycZmaTa1vYxurPv/Kif6/+3sO//wr75/7659f9BLNP/IAfM/42B6f+xru//vbzQ/+Xm1//Y2dD/zcnF/8S9tf+UkJk+7ev6AL648I6bker///////Hh0P/s29H/UT/d/xsDzf+9suf/07i3/35v5f+AdvL/4d3+//3z6/++mYudt5yPAPX0+wD29fwH+vbysbaDbv95GwP/xqGY/1NF5f8dBc//sKTi/5hIGf+YTzr/h2Oh/15M2f+wqfC8+fv1De3s7AD+/v0A////APTt6ge5ineNhC4U+seknP9MPeP/FwDO/7Ck5P+URBr/cA4A/4kxB/28lZCXtK7xC/Lz8gDw8PAA/f39AP///wDz7OoA6NnTANzDtjTn2NSVfG/m0D8q1uu6sOfrrnFQ06ZpUpq7jXk6yKecAL+67wDx8fEA7+/vAPAPAADAAwAAgAEAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAACAAQAAwAMAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAN1wAADdcAAA3XAAAN1wAADFSAAArSAAAJj8AACI4AAAdMAAAFiUAABQhACgcLgBxKEIAsDNVANE6YgDjPmcA7D1mAOw6YADlMlMA1CZAALYaKwB5Eh4AMREdAAAUIQAAFCIAABQiAAAUIgAAFCIAABQiAAAUIgAAFCIAABQiAAA3XAAAN1wAADdcAAA3XAAAMVIAACtIAAAmPwAAIDUAAB0wADIrRwCsQGoA906BAP9TigD/VI0A/1SMAP9UjAD/VIwA/1SMAP9UjQD/U4oA/02AAP89ZQD8JT4AuRIfAD8SHgAAFCIAABQiAAAUIgAAFCIAABQiAAAUIgAAFCIAADdcAAA3XAAAN1wAADdcAAAxUgAAK0cAACQ8AA0rSACNQ28A+FOKAP9UjAD/UokA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UokA/1SNAP9SiQD/PmgA/x4yAJ8RHQAYFCEAABQiAAAUIgAAFCIAABQiAAAUIgAAN1wAADdcAAA3XAAAN1wAADBRAAAqRgAqOV8AzVCFAP9UiwD/UYgA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GIAP9VjQD/T4MA/y5MANwSHgA8EyAAABQiAAAUIgAAFCIAABQiAAA3XAAAN1wAADdcAAA2WwAAMlQAOENvAOdTigD/UokA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9SigD/VIwA/zheAPMVIwBMEyAAABQiAAAUIgAAFCIAADZcAAA2XAAAN1wAADZbACVGdADnU4sA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RhwD/UYcA/1GHAP9RiAD/VY4A/zheAPQSHgA7EyEAABQiAAAUIgAAP2YDAD9nBAA5YgAJPW0AykyEAP9KgwD/SoIA/0qCAP9KggD/SoIA/0qCAP9KggD/SoIA/0qCAP9KggD/SoIA/0qCAP9KggD/SoIA/0qCAP9KggD/SoIA/0qCAP9KggD/SoIA/0qCAP9LgwD/TYcA/yZJAN8NGgAWFSMCABUjAQC5wK4AuL+uALO+pILC1Kr/xdis/MXWrPzF1qz8xdas/MXWrPzE1qz8xNas/MTWrPzD1ar8w9Wq/MPVqvzD1ar8w9Wq/MPVqvzD1ar8w9Wq/MPVqvzD1ar8w9Wq/MPVqvzD1ar8w9Wq/MPVqvzF2Kv8v9Co/1VZT6AzNTAAOTs2AM/P0wDU09ck+fj99f/////////////////////////////////////9+v///fv/////////////////////////////////////////////////////////////////////////////////////////////ysrL/j49PjxAQEEAdWBXAGVaVaeXlZT/mpeW/5aUk/+WlJP/lpST/5aUk/+WlZX/x8nK//Dw8P/k5OT/2tra//39/f//////////////////////////////////////////////////////////////////////////////////////e3t7vDc3NwDYy84dtJR+9XQtEf9kEgD/ah0A/2wgAP9sHwH/bB8A/2cbAf9kTEP/2+Hj///////q6ur/3t7e///////////////////////////////////////////////////////////////////////////////////////IyMj9Ozs7Lndq52KPg+//2sjU/7uCZf+CJgT/eBgA/34iAP9/JAD/gSMA/28aAP9jU0z/4OPk///////l5eX/5OTk//////////////////////////////////////////////////////////////////////////////////b29v9aWlp7gHLjohUAy/8yH9j/nZbz/9jDxv+qaUj/ex8A/3cZAP98IwD/fh8A/4FBKv+Dhoj/6enn///////d3d3/6Ojo/////////////////////////////////////////////////////////////////////////////////4WFhbj////Ov7jx/15N2v8YAMv/RTTe/62l7//Nr67/qmhJ/3wgAP9xEAD/qmlR/9TW1/9/f4T/7u7q///////c3Nz/6+vr////////////////////////////////////////////////////////////////////////////s7Ou1v///+f//////////8W/8v9TQNj/EgDM/0Y24P+uqPP/1724/5hLMP+cWD7//////1VItf9xboP//f74///////c3Nv/1NHm/tfU9/zb2Pb929f2/dvX9v3b1/b929f2/dvX9v3b1/b929f2/dvX9v3b1/b929f2/dvY9/6tq8Lksn1s7uXSzf////7///////39//+so+3/QCvU/xgAzP9TReP/vLPn/93Hw/////7/XUrk/xwKmf+VlpH////+//////+wrM7+JxTD/iURzv4pFcz+KRXN/ikVzf4pFc3+KRXN/ikVzf4pFc3+KRXN/ikVzf4pFc3+KRTS/ikZq+ziy77u7NrO///////////////////////8+/7/0s71/8nE8//T0Pj///////////9gT9z/HgXT/ykbif+lp57//////////f+PiMj/GALH/yMNz/8kD87/JA/O/yQPzv8kD87/JA/O/yQPzv8kD87/JA/O/yQPzv8kDdP/JRSu7OTk/+XZ1/3/1tL2/9bR9v/W0fb/1tH2/9nV9//f3Pj/39z4/97a+P/X0vb/1tL2/1RC2f8kDM//Jg7U/y4jgf+1tqr///////399v94b8H/GQPK/yUQz/8mEc7/JhHO/yYRzv8mEc7/JhHO/yYRzv8mEc7/JhHO/yYQ0/8nF6vkZlbcySYPzv8rFM//KxTP/ysUz/8rFM//KxTP/ysUz/8rFM//KxTP/ysUz/8rFM//KxTP/ysUz/8rFNH/JQ3Q/zMogv+3uK////////j58/90a8L/GwXK/yUQz/8mEc7/JhHO/yYRzv8mEc7/JhHO/yYRzv8mEc7/JxHT/x8PodNwYN+bLBXP/ygRz/8pEs//KRLP/ykSz/8pEs//KRLP/ykSz/8pEs//KRLP/ykSz/8rFM//KxTP/ysUz/8rFNL/JAzR/zMqev/Jy8D///////n68f9pXr//GgPM/yYRz/8mEc7/JhHO/yYRzv8mEc7/JhHO/yYRzv8nEdL/GwyUsoR35Fk9KNP/KBDO/ysUz/8rFM//KxTP/ysUz/8rFM//KxTP/ysUz/8rFM//KxTP/ysUz/8rFM//KxTP/ysUz/8rFNL/IgvL/0pDev/e4NL///////P07/9cT8D/FwDO/x4I0P8eCND/HgjQ/x4I0P8eCND/HgjR/x8Kzf8YCoxxjYHlFlZE2e4aAsv/HwbM/x8HzP8fB8z/HwfM/x8HzP8fB8z/HwfM/x8HzP8fBsz/KBHO/ywVz/8rFM//LBXP/ycPzv8fBtH/FQC6/0hDcP/Ky8X/+Pj2/+Hh4/9wZ7z/Vkq5/1VJuf9WSrn/Vkq5/1ZJuf9aTr3/Sj+m/B0SdSi7tO8ArqbtmH1u4v98buH/fnDi/35w4v99cOL/e27h/3tt4f97beH/e23h/3xu4f8/KtT/KBDO/ysUz/8nD87/RjLV/31v4f98buj/eGzQ/4eGkv+bm5f/oqKg/6Wmm/+goZb/n6CW/5+glv+gopj/oKOZ/6Slmf98fX64UU13Af///gD///8f////8f////////////////z8/v///////////////////////////2FQ3P8iCc3/KxTP/x8HzP92Z+D////////////8+/////7///v7/f///////////////////////v39//bw7v/79/b/8O7t/MXFxTXFxcMA8e/6APLx+wDt6/t4emzh/zkk0/9gT9v/wrzy//7+//////////////jz8f//////Xk3b/yIKzf8rFM//IAjM/3Jj4P////3/6ubz/5aP8/9INtn/QCzU/4+D5v/z8v3////////////9/Pv/zKmf/5NINv+TXEiXoH9zAKB9cADOyfMAz8rzANHM8wWMf+W/gXTj/+fk+v////////////jy8f+1gnT/qGtW//////9fTdv/IgrN/ysUz/8gCMz/c2Th/////P+naVH/xZyQ/7+36/9fUef/FQDM/z0o0/+on+z/+vr+////////////3MvF1rWWjBC5npQAuZ6TAPX1+wD19fsA9fX7APv7/Rz////e//////v49//LqJ3/jkAm/28MAP+fXUT//////19N2/8iCs3/KxTP/yAIzP9zZOH//////5NJLf9xEAD/nVMz/9e5q/+5svH/Rzfh/xMAzP9KNtb/uLDv//n5/u32+fkv8vT1APHz9ADx8/QA///9AP///QD///0A///9AP///yzn19Hbl081/3MTAP95HQD/dxoA/6FhSf//////X03b/yIKzf8rFM//IAjM/3Nk4f//////l080/3gcAP94GwD/eRwA/6VfP//Lraz/q6Pt/0o43P8xG9Dqw73vPfr78wDv7+8A7+/vAO/v7wD9/f0A/f39AP39/QD9/f0A////AOjY0x29kH28iTka/3gbAP91FwD/oWFJ//////9fTdv/IgrN/ysUz/8gCMz/c2Th//////+XTzT/eBwA/30kAP99IwD/dhkA/3gZAP+rakb/6dvYzpyT6y2+uO0A+fnzAO/v7wDv7+8A7+/vAP39/QD9/f0A/f39AP39/QD+//8A693YAOjY0gTZvrR4qGtU7nofBP+dWkH//////15M2/8iCc3/KxTP/yAIzP9zZOH//////5dPNP93GwD/eyEA/3oeAP98IwD/jkAh9qNlS4rcwrANr6ryAL237QD5+fMA7+/vAO/v7wDv7+8A/f39AP39/QD9/f0A/f39AP7//wDq3NcA49DIAOfY0QDo2NIiyaSVlsSaiur/////X03b/xwDzP8kDM3/GQDK/29f4P//////lEsu/3caAP+EMQ//lU0v8a52YKPCmoktv5WDANzFtwCrpe8AvbftAPn58wDv7+8A7+/vAO/v7wD9/f0A/f39AP39/QD9/f0A/v//AOrc1wDj0MgA5NPLAOXUzQDm1c8A7eHdGP///1m5se+agnXjyG5e3+NZR9ntlIno7f///+W8jXzLs4BroMKZiWLJppgfy6mbAMWfkAC8j30A3MS2AKul7wC9t+0A+fnzAO/v7wDv7+8A7+/vAP/AA///AAD//AAAP/gAAB/wAAAP4AAAB8AAAAPAAAADgAAAAYAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgAAAAcAAAAPAAAAD4AAAB/AAAA/4AAAf/AAAP/8AAP//wAP/</Image>
+ <Url type="text/html" method="GET" template="http://szotar.sztaki.hu/search" resultdomain="sztaki.hu">
+ <Param name="searchWord" value="{searchTerms}" />
+ <Param name="fromlang" value="hun" />
+ <Param name="tolang" value="eng" />
+ <Param name="ignoreAccents" value="1" />
+ <Param name="langcode" value="hu" />
+ <Param name="viewMode" value="full" />
+ <Param name="u" value="0" />
+ <Param name="searchMode" value="WORD_PREFIX" />
+ </Url>
+ <SearchForm>http://szotar.sztaki.hu/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/tearma.xml b/browser/locales/searchplugins/tearma.xml
new file mode 100644
index 000000000..d0b2a8b24
--- /dev/null
+++ b/browser/locales/searchplugins/tearma.xml
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>tearma.ie</ShortName>
+<Description>tearma.ie: Cuardach Comhtháite</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+/vz//vvz//768//++vP//vvz//7+/P////////////////////////////////////////////779P/12Iz/8MJK//DAQ//wwEP/8MBD//DAQ//wwkv/9tmQ//789v////////////////////////////7+/v/z0HP/8MBD//HGVv/z0HP/89Bz//PQc//z0HP/8cZV//DAQ//00nn//v7+/////////////////8i1t//CqZv/u4g6/7yOTP/FsK//8ezt////////////8ezt/8Wwrv+8jUn/u4g6/8OqoP/Itbf///////////9iLjT/WyQr/1skK/9bJCv/WyQr/5ZzeP///////////5ZzeP9bJCv/WyQr/1skK/9bJCv/Yi40////////////lHB1/41kXv+KVTL/i1pC/5Bqb//Txcf////////////Txcf/kGpv/4tZP/+KVTL/jWRg/5Rwdf/////////////////++/X/8cVU//DARP/22Y7/+OSv//jkr//45K//+OSv//XYi//wwEP/8cdY//78+P////////////////////////////vx1v/xxlb/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8cdZ//zy2v///////////////////////////////////////vz4//rsx//557j/+ee4//nnuP/557j/+uzI//79+f///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////v7+//779P/99+n//fbn//325//99uf//fbn//325//99uf//ffp//779f/+/v7//////////////////////////////////////////////////////////////////////////////////////////////////vv0//ffo//yyWD/8MBE//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBF//LKZP/44qn//vz3//////////////////////////////////////////////////////////////////////////////////vx1f/xxlj/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//yyF7//PPe///////////////////////////////////////////////////////////////////////88tv/8MNM//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//xxFH//PXk/////////////////////////////////////////////////////////////v37//LKYv/wwEP/8MBD//DAQ//ww0z/9deI//ffo//34KT/9+Ck//fgpP/34KT/9+Ck//fgpP/336L/9dWE//DCSf/wwEP/8MBD//DAQ//zzm3//v7+///////////////////////////////////////////////////////56L3/8MBD//DAQ//wwEP/8stk//336v///////////////////////////////////////////////////////PXj//LIXP/wwEP/8MBD//DAQ//67cv//////////////////////////////////////5d0eP+KY2j/imNo/4hYSf+GUTL/hlEy/4ZRMv+JXVb/imNo/41obP/Itrj//v7+///////////////////////+/v7/yLa4/41obP+KY2j/iFxS/4ZRMv+GUTL/hlEy/4haTf+KY2j/imNo/5d0eP//////////////////////aTg+/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1wlLP/f1NX//////////////////////9/U1f9cJSz/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/aTg+//////////////////////9pOD7/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/8Sxs///////////////////////xLCz/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9pOD7//////////////////////2k4Pv9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9rOkD/8e3u///////////////////////x7e7/azpA/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/1skK/9bJCv/WyQr/2k4Pv//////////////////////yri7/8Kusf/CrrH/vZlz/7mGOv+5hjr/uYY6/76beP/CrrH/x7S2//Lu7v/////////////////////////////////y7u7/x7S2/8Kusf+9mG//uYY6/7mGOv+5hjr/vpx8/8Kusf/CrrH/yri7///////////////////////////////////////78tn/8MBD//DAQ//wwEP/8MJJ//jksf/+/fr////////////////////////////////////////////+/Pf/+OGo//DBRv/wwEP/8MBD//DARv/89uX///////////////////////////////////////////////////////7+/v/114n/8MBD//DAQ//wwEP/8MBD//DCS//yyV//8slg//LJYP/yyWD/8slg//LJYP/yyWD/8slf//DCSf/wwEP/8MBD//DAQ//wwEP/9tuX//////////////////////////////////////////////////////////////////789v/zzm7/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//TReP/+/fr///////////////////////////////////////////////////////////////////////789//22Y//8MBF//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DAQ//wwEP/8MBD//DBRv/23Jn//v36///////////////////////////////////////////////////////////////////////////////////////89eP/99+i//TSfP/zz3H/889x//PPcf/zz3H/889x//PPcf/zz3H/889x//TTff/34Kb//ffn/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="text/html" method="GET" template="http://www.tearma.ie/Search.aspx" resultdomain="tearma.ie">
+ <Param name="term" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.tearma.ie/Home.aspx</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/termau.xml b/browser/locales/searchplugins/termau.xml
new file mode 100644
index 000000000..1e89a0961
--- /dev/null
+++ b/browser/locales/searchplugins/termau.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Porth Termau</ShortName>
+<Description>Porth termau wedi'u safoni</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAASCwAAEgsAAAAAAAAAAAAAVmTqk0JS6Pg8TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/9CUuj4VmTqk0JS6Pc8TOf/PEzn/zxM5/88TOf/O0vn/zpK5/86Suf/Okrn/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/0JS6Pc8TOf/PEzn/zxM5/88TOf/O0zn/09d6f9ve+7/eoXv/3B87v9WZOr/Pk7n/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/P07n/3F97v+fp/T/hY/w/2l27f9mc+3/c3/u/2x47f9DU+j/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/3F87v+nr/X/Y3Ds/zxM5/86Suf/Okrn/ztL5/9TYer/YW7s/z5O5/88TOf/PEzn/zxM5/88TOf/O0vn/05d6f+nr/X/dYHu/zpK5/88TOf/PEzn/zxM5/88TOf/O0vn/09e6v9KWen/O0vn/zxM5/88TOf/PEzn/zpK5/9ueu3/rLP2/1Be6v87S+f/PEzn/zxM5/88TOf/PEzn/zxM5/8+Tef/RVTo/ztL5/88TOf/PEzn/zxM5/87S+f/go3w/6iv9f9IV+n/O0vn/zxM5/88TOf/PEzn/zxM5/87S+f/UF7q/2p37f9HVuj/O0vn/zxM5/88TOf/O0vn/3+J8P+xt/b/VWPq/zpK5/88TOf/PEzn/zxM5/87TOf/QlLo/5We8v+5v/f/eoXv/ztL5/88TOf/PEzn/zpK5/9ndO3/t733/4GM8P89Tef/O0vn/zxM5/88TOf/OUrn/2h17f+0u/f/ub/3/3+J8P87S+f/PEzn/zxM5/87S+f/SVjp/6Kp9P+zuvb/d4Lv/0dW6P8+Tuf/QlLo/2Rx7P+mrvX/ub/3/7C39v9baev/Okvn/zxM5/88TOf/PEzn/ztL5/9mc+z/sbj2/7e99/+iqvT/k5zy/5yl8/+yufb/ub/3/7a89/97hu//PU3n/zxM5/88TOf/PEzn/zxM5/88TOf/PU3n/2l27f+lrPX/ub/3/7vB+P+7wPf/uL73/6qx9f92ge7/QVDo/ztM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/87S+f/TFvp/2977v+EjvD/hI/w/3J97v9RX+r/PEzn/zxM5/88TOf/PEzn/zxM5/9CUuj3PEzn/zxM5/88TOf/PEzn/ztL5/86Suf/PEzn/zxM5/86Suf/O0vn/zxM5/88TOf/PEzn/zxM5/9CUuj3VmTqk0JS6Pg8TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/9CUuj4VmTqkwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAASCwAAEgsAAAAAAAAAAAAAnKLzCGx47XJVY+rnP0/n/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/z9P5/9XZermbHjtbJ6l8whqdu10RlXo7jxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/9HVujtbHjtbFNh6ug7TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/9XZevlP0/n/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/ztL5/87S+f/O0vn/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/z9P5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/O0vn/zpK5/88TOf/RVTo/05d6v9PXur/R1bp/z1O5/85Suf/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zpK5/9DUuj/ZHHs/4WP8f+fp/T/rLT2/6629v+lrfX/kJry/3N/7v9PXen/Okvn/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/Xmvr/5Sd8v+3vff/ucD3/6au9f+PmfL/hZDx/4WP8f+Nl/L/nqf0/6Gp9P90f+7/Q1Lo/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/Pk7n/3F97v+utfb/u8H3/6Ws9f9zf+7/TFvp/zxM5/86S+f/Okvn/zxM5/9FVOj/Y3Ds/5Ga8v+HkfH/R1bo/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/9yfu7/tLr3/7rA9/+XoPP/UmDq/zlK5/87S+f/PEzn/zxM5/88TOf/PEzn/ztL5/86Suf/QVHo/3qF7/+Ai/D/QlLo/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86Suf/X2zr/6619v+6wPf/mKDz/0ta6f86Suf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/87S+f/PEzn/3B97v9qdu3/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/O0zn/0NS6P+XoPP/usD3/6y09v9ZZ+v/Okrn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/QE/o/3N+7v9NXOn/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86Suf/Z3Pt/7W79/+5v/f/g43w/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86S+f/UmDq/15s7P86S+f/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/+Jk/H/usD3/7W79/9hb+z/Okrn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/89Tef/VmTq/z9P6P88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/87S+f/R1fo/5+n9P+6v/f/qbD1/05c6f87S+f/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/ztL5/9KWen/QlHo/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/ztL5/9WZOr/rLP2/7m/9/+fqPT/RVTo/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/O0vn/0VU6P9AT+f/Okvn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/Okrn/19s7P+zuvf/ub73/6Kq9P9HVun/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/0BQ6P9xfe7/lZ7y/4WP8P9TYer/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86Suf/XGnr/7G39v+4vvf/rrX2/1Ri6v87S+f/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86Suf/anft/7S69v+5v/f/usD3/5uj8/9IV+j/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/ztL5/9QXur/p671/7i+9/+4vvf/bXnt/zlJ5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/O0vn/0RU6P+bpPP/ub/3/7e99/+3vff/s7r2/19t7P86Suf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/0JR6P+XoPP/ub/3/7m/9/+VnvP/RFPo/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86Suf/bXrt/7a89/+3vff/t733/7e99/+zufb/X2zr/zpK5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/Okrn/3+K8P+6wPf/t733/7a99/9yfu7/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/Okvn/0pZ6f+fp/T/ub/3/7e99/+3vff/uL73/6eu9f9NW+n/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86S+f/WWbr/6619v+4vvf/uL73/6yz9f9ndO3/O0zn/zpL5/88TOf/PEzn/zxM5/88TOf/PEzn/zlJ5/9GVuj/i5Xx/7i+9/+3vff/t733/7e99/+6wPf/hI7w/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/h5Hx/7m/9/+3vff/uL73/6+29v96hu//TVzp/zxM5/86Suf/Okrn/zpK5/9AUOj/X2zr/5af8/+3vvf/t733/7e99/+3vff/uL73/6yz9f9XZer/Okrn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/ztL5/9SYOr/pKz0/7m/9/+3vff/uL73/7i+9/+mrvX/hY/w/3SA7v9yfu7/e4bv/5Sd8v+yufb/ub/3/7e99/+3vff/t733/7e99/+2vPf/dH/u/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zpL5/9jcOz/rbT2/7m/9/+3vff/t733/7i+9/+5v/f/ub/3/7m/9/+5v/f/ub/3/7e99/+3vff/t733/7e99/+4vvf/uL73/4SO8P9CUej/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/ztL5/9mc+3/qLD1/7m/9/+3vff/t733/7e99/+3vff/t733/7e99/+3vff/t733/7e99/+3vff/ub/3/7K59v+Ai/D/Q1Po/ztL5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/ztL5/9YZuv/j5ny/7O59v+6wPf/uL73/7i99/+3vff/t733/7i99/+4vvf/ur/3/7e99/+cpfP/aHXs/z5O5/87S+f/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zpK5/9AT+f/Y3Ds/4yW8v+kq/T/sLf2/7S79/+0uvf/sLf2/6Wt9P+Rm/L/b3vu/0dW6P86Suf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/86Suf/PU3n/0lY6f9aZ+v/ZHHs/2Vy7P9baOv/S1np/z1N5/86Suf/O0vn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/8/T+f/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/O0vn/zpL5/86Suf/Okrn/zpL5/87S+f/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/P0/n/1Jg6ug7TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/9VY+rlaHXtekVU6O87TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/87TOf/RlXo7mx47XKepfMIaXXtelJg6uo/T+f/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/PEzn/zxM5/88TOf/P0/n/1Nh6ulqdu10oaj0CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="text/html" method="GET" template="http://termau.cymru/#{searchTerms}" resultdomain="termau.cymru">
+</Url>
+<SearchForm>http://termau.cymru</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/twitter-ja.xml b/browser/locales/searchplugins/twitter-ja.xml
new file mode 100644
index 000000000..e6950d6a2
--- /dev/null
+++ b/browser/locales/searchplugins/twitter-ja.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Twitter</ShortName>
+<Description>リアルタイム Twitter 検索</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAALAgAAJgAAACAgAAAAAAAAQQQAADECAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAHSSURBVDjLfVO/axRREP6SS0ihlv4FQpoYWxHyHxgCsUxhmToBS4tF7NLZWaiIEHLv7d5dJNEQ0tgYEAmIjeAfYC57++7MGbzLjxu/Nxt27zZrBoadnZnvm3kz7wEqMpZqmdAfBBMIZBzGVGCkorbXq4kFp/yPtBgPZCJzZvYQuNZ+jKhVo75G2LyHVz9uoeFmc6bILaHaepv9v/w6iRcypXbonmNPBB9OBbv82tYfmPgn7NHTnKAaP8E+g8Ztot68k/nXDm8ooNEVhHEP1vXROBbUf/M/Wc0Jwl8zsEkHOxcMtE6Y+A42nodpLtLfR9QWRMmA9jm2eh78aXQY4eF9VvjCgKDWEewM2PIZQc4nD9Sf6hk+sohNqorzW0kN91BBdYKtO9dE/00JZITA50XxsxTHlWarqMYBh/O3UPGqrednx7ox3o+UhbIRT7O1BO9PfIsXI5V9J34WvrqN94buwFi+b3N0k0nrCjAFAp1+Nz1Czd3N2y8Tm6ywYk9JTHKqk/cD9cM17YW89WExbo6Ja1zhvgK3+4Itql+rt8PkO1f6oBysBN3brLrMVhs84wHJvpHoMwnf8CI9ygZd3nbJgymrMvxeSoLjmlCsIJf+a17lP6juZmUWkMzvAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAECElEQVRYw+1XS2gTURS9ia0/EMSCCC4siCIKrly4UVwIrgRR3KgbXQmCCiIiCha7EQRR6cIupHShSeZjf6lttdWFgqBRBPGDKIqF1mQ+1Wp/SZvnvXfmTWfMpJ3U0pUPHplJ3rv33HO/Afi/5nPViTjuKm8rYlFlAoSI8a500R1SGLYUZRH+Hvee6+qqygvxW1KJ1XLd+74aUuY+0OzjoHw/BElj4zQQHxv0HDBUvjR9WQlNYmnJhSjK9dxFUC0LOsYE3M8LSI8LUKw8AmoG5ecqPtP8vgZ04zKeO+jqjUtBDi2qWY+71xPaKKpnpF0uzUpCnxDQOixQ+BSo9iR/3vsh4CF+r5of8D3F75r9udQAKUwx++AJXkhZCe9QBkGEuUSC1swTjhK0VrUmEYzw7SKCyUPrLwEP8IxufQN9aCfKPwCJwdq/lCPluvUG2n4jhQW8bD6Hu+amABvSd/LOTbEENOMjdIyicLuA1om/ADhbMqIZn/DMOMq+7iqPBanUzWfQOUGUjUFnnj5H0WdnQOlfFoxssdix3tjGgnWbrCuGKvdA4LmuSQJ6s9SN0zFwg6lS7Qmms+WngK4pEvAVg+w83MltCAAhAHSGFMwEgGQRq2quwZe2cShJkaYvtey39hEHMfsQL7cjxd1F4dBnPWUKVeMwaLmTkRggGd1oiJI9GxLcLg2NA8s5DTXzCKePbjtBJAVoVoGjmFxEwsgiCi5tFuoDDFinHcb9xWg6CBcjpS9ReReCeF9GOL5TsCEYhRkSs26HmSLXh2R2f8DlJS7Q7BZOwyhWzeZ3P2hijtJRG9wSXuQkIj27m1OKKKPUiQIiCv1ptF4x33rUh/Yb+aVqN8BjIf07HwDy0EPFzU2/ck0rgCpln8MgtOcBgOOiFizRSWt7aQCWA0GHdesSBuOAm+fFOQIoOFXVeBRqaGnvlsGIed5LwWiO/LP11KASg7tmt97fnRoz1Xj5NXc4WQP0Sliw6d4EN6mUdStyew8cbB6uQWGdXL+pPHMrjQLCVe40tHdo1NJoQ47X5XBnfKUykduBQ0U9+jEbKR5IOTc0IwfJb+tnjvwoq+3DCmxEV1iwvzyHtlzbSTnVHMRKuTm6crK8AcelDrGc/a/0r4WUsRcF3ebWzC4YCqt8Rbdo5XmO4IZj9IGSW+PFUuSVyu7BeeABIrdZMNFIfiSrCQQFouwDNHxQLyBQdK6nSPOfgftUSXWtaOlD67BkXkCLXrFV1IYJBAUifcpnspQHFjyjmi+4y8nBs6KIL1cJuRZkt6K1xzCYrvFAqdtptDCNz3dxX8V91Gsufqvn8r/CByJemd/kvJiprui/RAQkMaZR/r1i4W6a0rP/N/gXi6OvmDvBLoiyBV1/AN29Cs9hVFoUAAAAAElFTkSuQmCC</Image>
+<SearchForm>https://twitter.com/search/</SearchForm>
+<Url type="text/html" method="GET" template="https://twitter.com/search/{searchTerms} lang:ja" resultdomain="twitter.com">
+ <Param name="partner" value="Firefox"/>
+ <Param name="source" value="desktop-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/twitter.xml b/browser/locales/searchplugins/twitter.xml
new file mode 100644
index 000000000..03d15a2d8
--- /dev/null
+++ b/browser/locales/searchplugins/twitter.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Twitter</ShortName>
+<Description>Realtime Twitter Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAALAgAAJgAAACAgAAAAAAAAQQQAADECAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAHSSURBVDjLfVO/axRREP6SS0ihlv4FQpoYWxHyHxgCsUxhmToBS4tF7NLZWaiIEHLv7d5dJNEQ0tgYEAmIjeAfYC57++7MGbzLjxu/Nxt27zZrBoadnZnvm3kz7wEqMpZqmdAfBBMIZBzGVGCkorbXq4kFp/yPtBgPZCJzZvYQuNZ+jKhVo75G2LyHVz9uoeFmc6bILaHaepv9v/w6iRcypXbonmNPBB9OBbv82tYfmPgn7NHTnKAaP8E+g8Ztot68k/nXDm8ooNEVhHEP1vXROBbUf/M/Wc0Jwl8zsEkHOxcMtE6Y+A42nodpLtLfR9QWRMmA9jm2eh78aXQY4eF9VvjCgKDWEewM2PIZQc4nD9Sf6hk+sohNqorzW0kN91BBdYKtO9dE/00JZITA50XxsxTHlWarqMYBh/O3UPGqrednx7ox3o+UhbIRT7O1BO9PfIsXI5V9J34WvrqN94buwFi+b3N0k0nrCjAFAp1+Nz1Czd3N2y8Tm6ywYk9JTHKqk/cD9cM17YW89WExbo6Ja1zhvgK3+4Itql+rt8PkO1f6oBysBN3brLrMVhs84wHJvpHoMwnf8CI9ygZd3nbJgymrMvxeSoLjmlCsIJf+a17lP6juZmUWkMzvAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAECElEQVRYw+1XS2gTURS9ia0/EMSCCC4siCIKrly4UVwIrgRR3KgbXQmCCiIiCha7EQRR6cIupHShSeZjf6lttdWFgqBRBPGDKIqF1mQ+1Wp/SZvnvXfmTWfMpJ3U0pUPHplJ3rv33HO/Afi/5nPViTjuKm8rYlFlAoSI8a500R1SGLYUZRH+Hvee6+qqygvxW1KJ1XLd+74aUuY+0OzjoHw/BElj4zQQHxv0HDBUvjR9WQlNYmnJhSjK9dxFUC0LOsYE3M8LSI8LUKw8AmoG5ecqPtP8vgZ04zKeO+jqjUtBDi2qWY+71xPaKKpnpF0uzUpCnxDQOixQ+BSo9iR/3vsh4CF+r5of8D3F75r9udQAKUwx++AJXkhZCe9QBkGEuUSC1swTjhK0VrUmEYzw7SKCyUPrLwEP8IxufQN9aCfKPwCJwdq/lCPluvUG2n4jhQW8bD6Hu+amABvSd/LOTbEENOMjdIyicLuA1om/ADhbMqIZn/DMOMq+7iqPBanUzWfQOUGUjUFnnj5H0WdnQOlfFoxssdix3tjGgnWbrCuGKvdA4LmuSQJ6s9SN0zFwg6lS7Qmms+WngK4pEvAVg+w83MltCAAhAHSGFMwEgGQRq2quwZe2cShJkaYvtey39hEHMfsQL7cjxd1F4dBnPWUKVeMwaLmTkRggGd1oiJI9GxLcLg2NA8s5DTXzCKePbjtBJAVoVoGjmFxEwsgiCi5tFuoDDFinHcb9xWg6CBcjpS9ReReCeF9GOL5TsCEYhRkSs26HmSLXh2R2f8DlJS7Q7BZOwyhWzeZ3P2hijtJRG9wSXuQkIj27m1OKKKPUiQIiCv1ptF4x33rUh/Yb+aVqN8BjIf07HwDy0EPFzU2/ck0rgCpln8MgtOcBgOOiFizRSWt7aQCWA0GHdesSBuOAm+fFOQIoOFXVeBRqaGnvlsGIed5LwWiO/LP11KASg7tmt97fnRoz1Xj5NXc4WQP0Sliw6d4EN6mUdStyew8cbB6uQWGdXL+pPHMrjQLCVe40tHdo1NJoQ47X5XBnfKUykduBQ0U9+jEbKR5IOTc0IwfJb+tnjvwoq+3DCmxEV1iwvzyHtlzbSTnVHMRKuTm6crK8AcelDrGc/a/0r4WUsRcF3ebWzC4YCqt8Rbdo5XmO4IZj9IGSW+PFUuSVyu7BeeABIrdZMNFIfiSrCQQFouwDNHxQLyBQdK6nSPOfgftUSXWtaOlD67BkXkCLXrFV1IYJBAUifcpnspQHFjyjmi+4y8nBs6KIL1cJuRZkt6K1xzCYrvFAqdtptDCNz3dxX8V91Gsufqvn8r/CByJemd/kvJiprui/RAQkMaZR/r1i4W6a0rP/N/gXi6OvmDvBLoiyBV1/AN29Cs9hVFoUAAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="GET" template="https://twitter.com/search" rel="searchform">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="partner" value="Firefox"/>
+ <Param name="source" value="desktop-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/tyda-sv-SE.xml b/browser/locales/searchplugins/tyda-sv-SE.xml
new file mode 100644
index 000000000..f0aaa6fea
--- /dev/null
+++ b/browser/locales/searchplugins/tyda-sv-SE.xml
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Tyda.se</ShortName>
+<Description>Tyda.se, lexikon, ordlista och översättning.</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABQklEQVR42mO8fv3GxvVbdu/ay0AcYBYRkrhx4+aXL1+I1cDCxE68agYGBiY4y93Dtbe/Q1lFiVgN4uJi+gZ6PDw8xGrQN9BlYGBwc3eWkJQIDgmIi4+GiEtISsTFR+vp6WDYICHOwMCgoqLMzc2lpKwYlxANcV5gkF9cQjQvHy+6hl079jAwMEybOuvunXvr1m5kYGBwc3dhYGCwtrF88eLlo4eP0TUgg7t37t29c8/dw1VZRUlCQvzokeOfPn/Gp4GBgWHd2o08PNyNzbUMDAzr1236+OEjAQ1Hjxz78uWrhIT4xQuXXjx/gSWU0MCXL1+PHjnGwMCwaydKqmGBsxYtXLpo4VJkOQkJcbg2wjaIioroG+hBHIaiwcbWWkJSAlNDQlIsAwPDzu270WM6IipERUUZU4OEhPjv37+fI3kXAgDUBm6U4URcZQAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="http://tyda.se" resultdomain="tyda.se">
+ <Param name="w" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://tyda.se</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/vatera.xml b/browser/locales/searchplugins/vatera.xml
new file mode 100644
index 000000000..f4899f964
--- /dev/null
+++ b/browser/locales/searchplugins/vatera.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Vatera.hu</ShortName>
+ <Description>Keresés a Vatera.hu piacterén</Description>
+ <Language>hu</Language>
+ <OutputEncoding>ISO-8859-2</OutputEncoding>
+ <InputEncoding>ISO-8859-2</InputEncoding>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wEAAAAAAGD/GABa/2MAWv9eAFv/XwBa/2AAWv8RAAAAAAAA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZv8FAAAAAABZ/4wAZv//AF7//wBe//8AZv//AFn/dQAAAAAAgP8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWv/VAFv//wBa//sAWv/6AF3//wBa/8MAAAAAAID/AgAAAAAAAAAAAAAAAAAAAAAAAAAAAFX/AwAAAAAAXP8yAFv//wBb//8AWf/qAFr/7QBc//8AWv/6AF3/IQAAAAAAgP8CAAAAAAAAAAAAAAAAAAAAAACA/wQAAAAAAFr/fwBf//8AYP//AFn/iQBa/5sAX///AF///wBa/2kAAAAAAID/BAAAAAAAAAAAAAAAAAAAAAAAAP8BAAAAAABa/8sAXf//AF///wBb/zgAWv9PAGD//wBd//8AWv+4AAAAAABV/wMAAAAAAAAAAAAAAAAAgP8CAAAAAABZ/ygAWv/9AFz//wBa/+oAVf8GAFX/GABa//YAXP//AFr/8wBg/xgAAAAAAID/AgAAAAAAAAAAAID/BAAAAAAAWv9xAF///wBe//8AWv+oAAAAAAAAAAAAWv+8AF3//wBf//8AWf9cAAAAAABA/wQAAAAAAAAAAACA/wIAAAAAAFv/vwBd//8AXv//AFn/WQAAAAAAAAAAAFr/bwBf//8AXv//AFr/rAAAAAAAQP8EAAAAAACA/wIAAAAAAF7/HgBa//cAW///AFr/8wBZ/xcAAAAAAAAAAABc/ycAWv/9AFv//wBa/+wAYP8QAAAAAAAA/wEAQP8EAAAAAABZ/2QAX///AF3//wBa/7gAAAAAAFX/AwAA/wEAAAAAAFr/ywBd//8AXv//AFr/TwAAAAAAQP8EAFX/AwAAAAAAWv+vAFz//wBe//8AWf9qAAAAAABA/wQAgP8EAAAAAABa/4AAXv//AF3//wBa/5sAAAAAAID/BAAAAAAAWP8aAFr//wBh//8AXP//AFr/IgAAAAAAgP8CAFX/AwAAAAAAWv8zAF///wBh//8AW//yAE7/DQAAAAAAAAAAAFX/EgBa/1IAWf9TAFn/QgAAAAAAAAAAAAAAAAAAAAAAAAAAAID/AgBa/0cAWv9SAFn/UABV/wwAAAAAAAD/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AffvAADoFwAA6BcAAPgXAADQCwAA0AsAANALAACgBQAAoYUAAKGFAABBggAAQkIAAEJCAACCQQAAh8EAAH/+AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABV/wMAVf8DAFX/AwBV/wMAVf8DAFX/AwBV/wMAVf8DAFX/AwCA/wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFX/AwAAAAAAW/9iAFr/xgBa/7sAWv+9AFr/vQBa/70AWv+9AFr/vABa/8MAWf9FAAAAAABV/wMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wEAAAAAAFX/BgBa/+oAYv//AF7//wBf//8AX///AF///wBf//8AXf//AGT//wBa/8QAAAAAAFX/AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFX/AwAAAAAAWf9CAF3//wBa//gAWv/8AFr//ABa//sAWv/7AFr/+wBa//sAWv/9AFn/+ABa/x8AAAAAAID/AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP8EAAAAAABa/5EAYP//AFr/+gBa//8AWv//AFr//gBa//8AWv//AFr//wBZ//sAYP//AFr/ZgAAAAAAQP8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AQAAAAAAgP8CAFr/2gBd//8AWv/9AFr//wBa//8AWv//AFr//wBa//8AWv//AFr/+wBf//8AWv+1AAAAAABV/wMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVf8DAAAAAABd/zcAXP//AFr//QBa//8AWv//AFr//wBa//sAWv/8AFr//wBa//8AWv/+AFv//wBa//MAWf8XAAAAAAD//wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACA/wQAAAAAAFr/hQBg//8AWv/6AFr//wBa//sAYP//AFr/pwBa/8EAX///AFr//ABa//8AWf/7AGD//wBc/1kAAAAAAED/BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AQAAAAAAWf/QAF3//wBa//wAWv//AFn/+wBh//8AWf9FAFv/cwBi//8AWv/6AFr//wBa//sAYP//AFv/qQAAAAAAQP8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACA/wIAAAAAAFv/LQBb//8AWv/+AFr//wBa//4AW///AFr/9gBd/wsAWv8zAF3//wBa//0AWv//AFr//QBc//8AWv/rAFX/DwAAAAAAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAID/BAAAAAAAWf94AGD//wBa//oAWv//AFr/+wBf//8AWv+4AAAAAABV/wYAWv/cAF3//wBa//0AWv//AFr/+wBf//8AWf9NAAAAAABA/wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP8CAAAAAABZ/8UAXv//AFr//ABa//8AWv/6AGD//wBa/2kAAAAAAAAAAABb/5UAYP//AFr/+gBa//8AWf/7AGD//wBZ/50AAAAAAED/BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAID/AgAAAAAAV/8jAFr/+wBa//8AWv/+AFr//gBa//8AWv/7AFf/IwAAAAAAAAAAAFr/RwBe//8AWv/8AFr//wBa//0AXP//AFr/4gBt/wcAAAAAAAD/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP8EAAAAAABZ/2oAYP//AFr/+gBa//8AWv/8AF7//wBa/8YAAAAAAFX/AwAAAAAAVf8MAFv/5wBc//8AWv/9AFr//wBa//wAXv//AFr/QQAAAAAAVf8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABV/wMAAAAAAFr/uQBf//8AWv/7AFr//wBa//oAYP//AFr/egAAAAAAgP8EAED/BAAAAAAAWf+mAGD//wBZ//sAWv//AFr/+gBg//8AWv+QAAAAAACA/wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP8CAAAAAABc/xkAWv/1AFv//wBa//4AWv//AFr//gBb//8AWv8wAAAAAABV/wMAQP8EAAAAAABb/1cAX///AFr/+wBa//8AWv/9AF3//wBa/9gAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA/wQAAAAAAFr/XQBg//8AWf/7AFr//wBa//wAXf//AFr/1AAAAAAAAAAAAAAAAAAA/wEAAAAAAF3/FgBa//IAW///AFr//gBa//8AWv/9AFz//wBa/zYAAAAAAFX/AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAED/BAAAAAAAWv+tAF///wBa//sAWv//AFr/+gBg//8AWv+LAAAAAACA/wQAAAAAAAAAAABV/wMAAAAAAFr/tgBf//8AWv/7AFr//wBa//oAYP//AFr/ggAAAAAAgP8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wEAAAAAAFr/EQBa/+0AW///AFn//gBa//8AWv/9AF3//wBa/z4AAAAAAFX/AwAAAAAAAAAAAED/BAAAAAAAWf9nAGD//wBa//oAWv//AFr//ABe//8AWv/OAAAAAAAA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAED/BAAAAAAAWf9QAF///wBa//sAWv//AFr//QBc//8AWv/gAFX/BgAAAAAAAP8BAAAAAAAAAAAAgP8CAAAAAABd/yEAWf/7AFr//wBa//4AWv/+AFr//gBa//8AWf8rAAAAAACA/wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQP8EAAAAAABa/58AYP//AFn/+wBa//8AWf/7AGD//wBa/5sAAAAAAED/BAAAAAAAAAAAAAAAAAAAAAAAgP8CAAAAAABa/8QAXv//AFr//ABa//8AWv/6AGD//wBZ/3UAAAAAAID/BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AQAAAAAAVf8JAFn/5ABc//8AWv/9AFr//wBa//sAX///AFn/TQAAAAAAQP8EAAAAAAAAAAAAAAAAAAAAAACA/wQAAAAAAFn/eABg//8AWv/6AFr//wBa//wAXv//AFv/wgAAAAAAgP8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAVf8DAAAAAABa/0QAXv//AFr//ABa//8AWv/9AFv//wBa/+sAVf8PAAAAAAAA/wEAAAAAAAAAAAAAAAAAAAAAAID/AgAAAAAAWf8uAFv//wBa//4AWv//AFr//gBa//8AWv/6AFX/IQAAAAAAgP8CAAAAAAAAAAAAAAAAAAAAAACA/wQAAAAAAFn/kgBg//8AWv/6AFr//wBa//sAX///AFn/qwAAAAAAQP8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWv/SAF3//wBa//wAWv//AFr/+gBg//8AWf9nAAAAAABA/wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWf/WAFv//wBa//kAWv/7AFr/9gBf//8AWv9dAAAAAABA/wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP8EAAAAAABZ/4kAX///AFr/9gBZ//sAWv/3AF7//wBa/7IAAAAAAFX/AwAAAAAAAAAAAAAAAABV/wMAAAAAAFn/QgBj//8AX///AGD//wBg//8AYf//AFv//wBc/xkAAAAAAID/AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABV/wMAAAAAAFn/PABi//8AYP//AGD//wBg//8AYf//AFz//wBe/x4AAAAAAID/AgAAAAAAAAAAAFX/AwAAAAAAWf88AFv/qQBZ/50AWv+fAFn/nQBa/6gAWf9wAAAAAACA/wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP8CAFn/hgBa/6QAWv+eAFr/nwBa/54AWv+kAFz/JAAAAAAAgP8CAAAAAAAAAAAAAAAAAAD/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAID/AgCA/wQAQP8EAED/BABA/wQAgP8EAFX/AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA/wQAQP8EAED/BABA/wQAQP8EAID/BAAA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////4Af//9/7//+gBf//QAX//0AC//9AAv/+gAL//oABf/6AAX/+gAF//QAAv/0BAL/9AYC/+gGAX/oCgF/6AkBf9AJAP/QHQC/0BaAv6AWgL+gFoBfoC9AX0AvQF9AL0AvQF/gL8BfoC6AX6AWgL/gF39//++A//Af/////</Image>
+ <Url type="text/html" method="GET" template="http://www.vatera.hu/listings/index.php" resultdomain="vatera.hu">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="c" value="0"/>
+ <Param name="td" value="on"/>
+ </Url>
+ <SearchForm>http://www.vatera.hu/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/webdunia.xml b/browser/locales/searchplugins/webdunia.xml
new file mode 100644
index 000000000..47a0da3c8
--- /dev/null
+++ b/browser/locales/searchplugins/webdunia.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Webdunia Search</ShortName>
+<Description>Webdunia Search : A Multilingual Search Engine for Indian Content</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAyAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHCTaLhmR7CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHCTaAhwk2nYcJNr4GZHs9hmR7GoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwk2iAcJNrBHCTa%2Fxwk2v8Zkez%2FGZHs%2FxmR7K8ZkewTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcJNpTHCTa8Rwk2v8cJNr%2FHCTa%2FxmR7P8Zkez%2FGZHs%2FxmR7OIZkew3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwk2sgcJNr%2FHCTa%2Fxwk2v8cJNr%2FGZHs%2FxmR7P8Zkez%2FGZHs%2FxmR7KIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcJNoDHCTa9Bwk2v8cJNr%2FHCTa%2Fxwk2v8Zkez%2FGZHs%2FxmR7P8Zkez%2FGZHsywAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwk2igcJNr%2FHCTa%2Fxwk2v8gJ73zJ0OX9TBWmPEggdTyGZHs%2FxmR7P8ZkezyGZHsAQAAAAAAAAAAAAAAAAAAAAAAAAAAHCTaVxwk2v8fJbvaKSiT%2FRlTp%2F8Gvsr%2FRXBY%2FzI0pP8nRb3%2BHnvX3RmR7P0ZkewdAAAAAAAAAAAAAAAAAAAAAAAAAAAbI7KSGyOj8DgufPlWS1r%2FBs7W%2FwPQ2v9Ri13%2FjV8I%2F2VLQf82NJr7Gz%2Fc9BpO4nkAAAAAAAAAAAAAAAAbI6IFGyOiaRsjorU7L4H%2FaFtV%2FzemoP8D1%2BH%2FONnh%2F3HNyP%2BmfBP%2Fn2wG%2F4NpFv83bpb%2FGlHiuhwo22QcKNsEAAAAABsjoggbI6JIGyOi9083bf0strn%2FAPX%2F%2FwDs9v958fb%2F2c20%2F469kv%2BjexL%2FPaaE%2F0V5ff4Zkez2GZHsRhwo2wgAAAAAAAAAABsjogUbI6IsZUBWpUWUk%2F8A9f%2F%2FGd3Y%2F2bLpv997%2B%2F%2FTuPZ%2Fw7V1v8D2%2BP%2FNKCX0xmR7DYZkewMAAAAAAAAAAAAAAAAAAAAAAAAAAB8ST89fEk%2F%2Fz2jn%2F9Wln%2F%2FN8Oo%2Fxri2P8A9f%2F%2FAO%2F5%2FwDv%2Bf9Co5NmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8ST%2B3ekxD%2Fzqspv8U3t%2F%2FAPX%2F%2FwD1%2F%2F8A9f%2F%2FHtPR6wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHxJPxB8ST%2B3ZGpk%2F1OHfv9Ll4z%2FGdXY%2FxzR0%2BtOjYUXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4T0U7XnFsh3xJP3Z4T0U7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2Fn%2BAAPg%2FgADwD4AA4AeAAOAHgADAB4AAwAOAAMADgADAA4AAAACAAAAAgACAAYAA4AeAAPAPgADwD4AA%2FD%2BAAP%2F%2FgAA%3D</Image>
+<Url type="text/html" template="http://search.webdunia.com/search.aspx" resultdomain="webdunia.com">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="FF" value="SearchBox" />
+</Url>
+<Url type="application/x-suggestions+json" template="http://search.webdunia.com/suggestions.aspx">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="stype" value="json"/>
+</Url>
+<SearchForm>http://search.webdunia.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-NN.xml b/browser/locales/searchplugins/wikipedia-NN.xml
new file mode 100644
index 000000000..f8c4e9b49
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-NN.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (nn)</ShortName>
+<Description>Wikipedia, det frie oppslagsverket</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://nn.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://nn.wikipedia.org/wiki/Spesial:Søk"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-NO.xml b/browser/locales/searchplugins/wikipedia-NO.xml
new file mode 100644
index 000000000..31af946df
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-NO.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (no)</ShortName>
+<Description>Wikipedia, den frie encyklopedi</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://no.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://no.wikipedia.org/wiki/Spesial:Søk"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-af.xml b/browser/locales/searchplugins/wikipedia-af.xml
new file mode 100644
index 000000000..e86550be5
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-af.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (af)</ShortName>
+<Description>Wikipedia, die vrye ensiklopedie</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://af.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://af.wikipedia.org/wiki/Spesiaal:Soek"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-an.xml b/browser/locales/searchplugins/wikipedia-an.xml
new file mode 100644
index 000000000..f86c48190
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-an.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Biquipedia (an)</ShortName>
+<Description>A enciclopedia Libre</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://an.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://an.wikipedia.org/wiki/Especial:Mirar"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ar.xml b/browser/locales/searchplugins/wikipedia-ar.xml
new file mode 100644
index 000000000..d241bdf67
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ar.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>ويكيبيديا (ar)</ShortName>
+<Description>ويكيبيديا (ar)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ar.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ar.wikipedia.org/wiki/خاص:بحث"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-as.xml b/browser/locales/searchplugins/wikipedia-as.xml
new file mode 100644
index 000000000..97b1b44aa
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-as.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (as)</ShortName>
+<Description>ৱিকিপিডিয়া, এখন মুক্ত বিশ্বকোষ</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://as.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://as.wikipedia.org/wiki/বিশেষ:সন্ধান"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ast.xml b/browser/locales/searchplugins/wikipedia-ast.xml
new file mode 100644
index 000000000..aa6bb1df5
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ast.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Uiquipedia (ast)</ShortName>
+<Description>La enciclopedia llibre</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ast.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ast.wikipedia.org/wiki/Especial:Gueta"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-az.xml b/browser/locales/searchplugins/wikipedia-az.xml
new file mode 100644
index 000000000..07cef511c
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-az.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vikipediya (az)</ShortName>
+<Description>Vikipediya, açıq ensiklopediya</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://az.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://az.wikipedia.org/wiki/Xüsusi:Axtar"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-bg.xml b/browser/locales/searchplugins/wikipedia-bg.xml
new file mode 100644
index 000000000..3208c8249
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-bg.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Уикипедия (bg)</ShortName>
+<Description>Уикипедия, свободната енциклоподия</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://bg.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://bg.wikipedia.org/wiki/Специални:Търсене"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-bn.xml b/browser/locales/searchplugins/wikipedia-bn.xml
new file mode 100644
index 000000000..8073858e9
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-bn.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>উইকিপিডিয়া (bn)</ShortName>
+<Description>উইকিপিডিয়া, মুক্ত বিশ্বকোষ</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://bn.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://bn.wikipedia.org/wiki/বিশেষ:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-br.xml b/browser/locales/searchplugins/wikipedia-br.xml
new file mode 100644
index 000000000..5696a3a83
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-br.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (br)</ShortName>
+<Description>Wikipedia, an holloueziadur digor</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://br.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://br.wikipedia.org/wiki/Dibar:Klask"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-bs.xml b/browser/locales/searchplugins/wikipedia-bs.xml
new file mode 100644
index 000000000..86d5cc38e
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-bs.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (bs)</ShortName>
+<Description>Slobodna enciklopedija</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://bs.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://bs.wikipedia.org/wiki/Posebno:Pretraga"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ca.xml b/browser/locales/searchplugins/wikipedia-ca.xml
new file mode 100644
index 000000000..09a630a10
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ca.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Viquipèdia (ca)</ShortName>
+<Description>L'enciclopèdia lliure</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ca.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ca.wikipedia.org/wiki/Especial:Cerca"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-cy.xml b/browser/locales/searchplugins/wikipedia-cy.xml
new file mode 100644
index 000000000..18a2805ea
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-cy.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wicipedia (cy)</ShortName>
+<Description>Wicipedia, Y Gwyddioniadur Rhydd</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://cy.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://cy.wikipedia.org/wiki/Arbennig:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-cz.xml b/browser/locales/searchplugins/wikipedia-cz.xml
new file mode 100644
index 000000000..8595e0569
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-cz.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedie (cs)</ShortName>
+<Description>Wikipedia, svobodná encyclopedie</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://cs.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://cs.wikipedia.org/wiki/Speciální:Hledání"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-da.xml b/browser/locales/searchplugins/wikipedia-da.xml
new file mode 100644
index 000000000..b2ff05258
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-da.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (da)</ShortName>
+<Description>Wikipedia, den frie encyklopædi</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://da.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://da.wikipedia.org/wiki/Speciel:Søgning"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-de.xml b/browser/locales/searchplugins/wikipedia-de.xml
new file mode 100644
index 000000000..56440461b
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-de.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (de)</ShortName>
+<Description>Wikipedia, die freie Enzyklopädie</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://de.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://de.wikipedia.org/wiki/Spezial:Suche"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-dsb.xml b/browser/locales/searchplugins/wikipedia-dsb.xml
new file mode 100644
index 000000000..cee19b404
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-dsb.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedija (dsb)</ShortName>
+<Description>Wikipedija, lichotna encyklopedija</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://dsb.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://dsb.wikipedia.org/wiki/Specialne:Pytaś"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-el.xml b/browser/locales/searchplugins/wikipedia-el.xml
new file mode 100644
index 000000000..40c72079f
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-el.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (el)</ShortName>
+<Description>Βικιπαίδεια, η ελεύθερη εγκυκλοπαίδεια</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://el.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://el.wikipedia.org/wiki/Ειδικό:Αναζήτηση"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-eo.xml b/browser/locales/searchplugins/wikipedia-eo.xml
new file mode 100644
index 000000000..a579788c9
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-eo.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vikipedio (eo)</ShortName>
+<Description>Vikipedio, la libera enciklopedio</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://eo.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://eo.wikipedia.org/wiki/Specialaĵo:Serĉi"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-es.xml b/browser/locales/searchplugins/wikipedia-es.xml
new file mode 100644
index 000000000..1edf8e1ef
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-es.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (es)</ShortName>
+<Description>Wikipedia, la enciclopedia libre</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://es.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://es.wikipedia.org/wiki/Especial:Buscar"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-et.xml b/browser/locales/searchplugins/wikipedia-et.xml
new file mode 100644
index 000000000..4859a0b10
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-et.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vikipeedia (et)</ShortName>
+<Description>Vikipeedia, vaba entsüklopeedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://et.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://et.wikipedia.org/wiki/Eri:Otsimine"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-eu.xml b/browser/locales/searchplugins/wikipedia-eu.xml
new file mode 100644
index 000000000..1792a1760
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-eu.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (eu)</ShortName>
+<Description>Wikipedia, entziklopedia askea</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://eu.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://eu.wikipedia.org/wiki/Berezi:Bilatu"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-fa.xml b/browser/locales/searchplugins/wikipedia-fa.xml
new file mode 100644
index 000000000..68a4bc846
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-fa.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>ویکی‌پدیا (fa)</ShortName>
+<Description>ویکی‌پدیا، دانشنامهٔ آزاد</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://fa.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://fa.wikipedia.org/wiki/ویژه:جستجو"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-fi.xml b/browser/locales/searchplugins/wikipedia-fi.xml
new file mode 100644
index 000000000..ba3418a5b
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-fi.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (fi)</ShortName>
+<Description>Wikipedia (fi), vapaa tietosanakirja</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://fi.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://fi.wikipedia.org/wiki/Toiminnot:Haku"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-fr.xml b/browser/locales/searchplugins/wikipedia-fr.xml
new file mode 100644
index 000000000..88620ddea
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-fr.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipédia (fr)</ShortName>
+<Description>Wikipédia, l'encyclopédie libre</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://fr.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://fr.wikipedia.org/wiki/Spécial:Recherche"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-fy-NL.xml b/browser/locales/searchplugins/wikipedia-fy-NL.xml
new file mode 100644
index 000000000..f9e531aa4
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-fy-NL.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedy (fy)</ShortName>
+<Description>De fergese ensyklopedy</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://fy.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://fy.wikipedia.org/wiki/Wiki:Sykje"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ga-IE.xml b/browser/locales/searchplugins/wikipedia-ga-IE.xml
new file mode 100644
index 000000000..5abf6e314
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ga-IE.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vicipéid (ga)</ShortName>
+<Description>Vicipéid, an Chiclipéid Shaor</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ga.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ga.wikipedia.org/wiki/Speisialta:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-gd.xml b/browser/locales/searchplugins/wikipedia-gd.xml
new file mode 100644
index 000000000..019305ee4
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-gd.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Uicipeid (gd)</ShortName>
+<Description>Wikipedia, An leabhar mòr-eòlais</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://gd.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://gd.wikipedia.org/wiki/Sònraichte:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-gl.xml b/browser/locales/searchplugins/wikipedia-gl.xml
new file mode 100644
index 000000000..da30f3c04
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-gl.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (gl)</ShortName>
+<Description>Wikipedia, a enciclopedia libre</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://gl.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://gl.wikipedia.org/wiki/Especial:Procurar"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-gn.xml b/browser/locales/searchplugins/wikipedia-gn.xml
new file mode 100644
index 000000000..57947b444
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-gn.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vikipetã (gn)</ShortName>
+<Description>Vikipetã, opaite tembikuaa hekosãsóva renda</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://gn.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://gn.wikipedia.org/wiki/Mba'echĩchĩ:Buscar"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-gu.xml b/browser/locales/searchplugins/wikipedia-gu.xml
new file mode 100644
index 000000000..09d9277de
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-gu.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>વિકિપીડિયા (gu)</ShortName>
+<Description>વીકીપીડિયા, મુક્ત એનસાયક્લોપીડિયા</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://gu.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://gu.wikipedia.org/wiki/વિશેષ:શોધ"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-he.xml b/browser/locales/searchplugins/wikipedia-he.xml
new file mode 100644
index 000000000..d9b4730d7
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-he.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>ויקיפדיה</ShortName>
+<Description>ויקיפדיה</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://he.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://he.wikipedia.org/wiki/מיוחד:חיפוש"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-hi.xml b/browser/locales/searchplugins/wikipedia-hi.xml
new file mode 100755
index 000000000..090a78443
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-hi.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>विकिपीडिया (hi)</ShortName>
+<Description>विकिपीडिया (हिन्दी)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://hi.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://hi.wikipedia.org/wiki/विशेष:खोज"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-hr.xml b/browser/locales/searchplugins/wikipedia-hr.xml
new file mode 100644
index 000000000..0c6a13da2
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-hr.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedija (hr)</ShortName>
+<Description>Wikipedija, slobodna enciklopedija</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://hr.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://hr.wikipedia.org/wiki/Posebno:Traži"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-hsb.xml b/browser/locales/searchplugins/wikipedia-hsb.xml
new file mode 100644
index 000000000..0540ff844
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-hsb.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedija (hsb)</ShortName>
+<Description>Wikipedija, swobodna encyklopedija</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://hsb.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://hsb.wikipedia.org/wiki/Specialnje:Pytać"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-hu.xml b/browser/locales/searchplugins/wikipedia-hu.xml
new file mode 100644
index 000000000..9c7a3e5ea
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-hu.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipédia (hu)</ShortName>
+<Description>Wikipedia, a szabad enciklopédia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://hu.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://hu.wikipedia.org/wiki/Speciális:Keresés"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-hy.xml b/browser/locales/searchplugins/wikipedia-hy.xml
new file mode 100644
index 000000000..d45001783
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-hy.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (hy)</ShortName>
+<Description>Վիքիփեդիա՝ ազատ հանրագիտարան</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://hy.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://hy.wikipedia.org/wiki/Սպասարկող:Որոնել"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-id.xml b/browser/locales/searchplugins/wikipedia-id.xml
new file mode 100644
index 000000000..aaa65e19d
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-id.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (id)</ShortName>
+<Description>Wikipedia, ensiklopedia bebas</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://id.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://id.wikipedia.org/wiki/Istimewa:Pencarian"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-is.xml b/browser/locales/searchplugins/wikipedia-is.xml
new file mode 100644
index 000000000..453da21f7
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-is.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (is)</ShortName>
+<Description>Wikipedia, the free encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Image width="65" height="26">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAYAAADovjFxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjA3MkMyRDY5QjUyNTExRTNCNzdEOTU4N0NCMjI3MEFDIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjA3MkMyRDZBQjUyNTExRTNCNzdEOTU4N0NCMjI3MEFDIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDcyQzJENjdCNTI1MTFFM0I3N0Q5NTg3Q0IyMjcwQUMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDcyQzJENjhCNTI1MTFFM0I3N0Q5NTg3Q0IyMjcwQUMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7lkNGzAAAELklEQVR42uyYa4hUZRjHn3Nm13X2Mu6urtqutrTeVjRUQikoKUoQLIy8hybiBcVMRdFASPwgQTczsCiMvFYGffBDeANFUDEydc31viheat2dyVndmdmdy57+7/I/+HCcKaX1Q3Re+LHnOe9z3sv/PM/znlnLcRz5vzdb/OaL4Ivgi3C/WV01UCAQkNraWqmpGSCVVVWSl5cnZaXl4jgZaWpugp0viURcwpGIJJMpSba1SY+yUlmy5B3ZuGGDXGlokJKSYkmn0pJOp6UDBbsl2iJFRYVi25bEYjFpwzOtra3S3t4umUxGuqqo53WVCGZR9fX1nZgWDAaxgWIJhUpASGyIFLBtSWKDhcFCScTjuE5JXV2dhMNhSaVS2Kwt0WgUYiU6hTCiRSLNGM2BnYF4yccWCWPB6yAC9oGzYC2Iga/pNx/kg/fBs2AcSICvQOMjT2pZD7xFE0kFBd0lHo89zsiv5d8L3o4nwW+dcotUMDo+p11Jn3YKY/qeY9900P0/lv5hUJ+rswc3Nlnd6wDvgmHgR3V/GfgyS0RZf1NrrEeoR/ZD1rDAPzzrHX8wuEYhcr68E6BV2W9RGJMiI9T9wxzwFfAd+AyUgeFgK/iG9ovgAMcpAIvB92AVNzCJ9hoKu4LjjwabmYrGdwtYyb45nPM98DbYyHW4bSr7Pwb9PPszqTwGnGdUZ22DuekX1L074JSy3wC7eV1C/53clFHXVLEz7Hc3UsG38gz9x7O/F+25oC+vPzI1FVwGN0FP8Dz4gmO4fvPYN5X2LI5ZSvsTT9F/iveEgkZyhc8lTrxd3TPhM1LZ08C3vL7HxRplM6AN/AqOgVGgjhts5sKugyi4qvKzhaKZ4voHGMKCe5oF2qHfIj7TyDGucSM/sH5tY7/p+x1cBGm17vmMgikUsxw8kSv/ZoBqqjiCm7zLsBQ+vMuTl/nKbuTJcdIjpmnduIBuKmfNmfcy570BlrMvzpSap+Z2GG2WZ869nr3Ynn7TngZ7wACKbIRdl+s74QgddlPphVzgh6A/c1g8xbND2b24qD/BLVDk8XWUv8MIKuTCTNinVFjfBh+oY82mv+OZs1SNJ1n6ZzLi1nvWsgksyFUbXuVAO5RQjppENxNynyq7AfzE6xsMyyDtfhxDF1mHkZCtSJ9T9mLWIPeZibzuyejb5hlztVr7ZaaBtzlu5GU7Zi7xW8BU0CaqZt7wfnBI+b0GhvIb4iBD7SWOuYfKr+Px+zN4k3loJj/KojYIFINfmALu6TCW0dGbp0gNj+nZLHI203YCOA6WqtNhEF/OWc45ki/kHOuWzW+cgYyyK//mw8MtLEU8AkMqbIMeP5OjfWj3oU9flUL63C5XZ7zZaJXq66+eGajmdJvrG2LkuHNUco1uLapWdarC8v+z5P+U9kXwRfBF8EXwRfBFyNH+EmAA/FQ613UCCjYAAAAASUVORK5CYII=</Image>
+<Image width="130" height="52">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAYAAABGkOCVAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjg5QzlEREIwQjUyNjExRTNCNzdEOTU4N0NCMjI3MEFDIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjg5QzlEREIxQjUyNjExRTNCNzdEOTU4N0NCMjI3MEFDIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODlDOUREQUVCNTI2MTFFM0I3N0Q5NTg3Q0IyMjcwQUMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODlDOUREQUZCNTI2MTFFM0I3N0Q5NTg3Q0IyMjcwQUMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz41D9uwAAAJWElEQVR42uxcCWxURRj+t3t029IiVCyXLSpo0ERFLVAvNJ4oBRUVL7Se4IUXeOIVjaKmgkHxthweiKJGY/HAKxqwaiwYBBESwQtpgdCWtns//6nfi7/je7v72kI0mS/5s7vvn5n3Zuab/3pNfZZlkYFBjlkCA0MEA0MEA0MEA0MEA0MEA0MEA0MEA0MEA0MEA0MEg87B919+OL/fT4FAgELBIF0zZQqVl5dTSZ8SWr1mHY2oqKBYtIWSiSSlLB81bN5EkUiU4ok4BYMBSqZ8FItEqCmVoBhf6xvOJx+PY6VSRHi/krJS5PP5OiTB4+SFcymVTFI797N4TFJ6foatjVsoNxyi3sW7U3t7hOKxCB+hHGppbqFgfh7ts+cgamlvofy8fIrHk+Tj4ROxKK1Y+S21trbRgvnz+NkilOSxU+r+hghdw+DBg6msbBDV1X1FY8ZUUmtbS8fG+nw5VFVVRUWFPamppYlCoSAlkooI7dTMmxlnMvTzSAS1oe1trdSjqIiJsJWJEKRevYs7iBCLMhH8OfwZo2Zus7R2CcWYHIq0hw4fQX5+nveX1FJ9/bfGIuxqDBgwgPLy8ngD4x0bq7baSllUstcgGj9hAoWjfJ03yrIkESwQgU9wIkXh3FAHESLRKBUV7UZlpaW0dt2PFGlro8WLX6eGhoaOE93xxpb7qL7qpDfydRuKDIlkgsi81P0PBkI53kOhIUOG0LRptzAhijrV//8YIwxluZMlxtJCf3HZz1LM8hbLQpYDWe5gaWJhu0ghliBLNctqlqksB7NsYwmz9GCZw/IFy0Uso9E3ylLIkg/dbEPTnY5TWTawfJ+p4R4sN7GsAgmUtLHczXKYaDNb6JVMB1kUjmd5R+hU2z2hG8HyIMa09U+hj8HOhdo3FZ2+7qVTL5bN2KiVLm1mQd/OMlDTjYPuXpe+86EfZvZnl+EWcbD7e+l4jzi1Zzjo+7I0Ql+j6epYfoFbcSKZcjsLzN7sMoQ0Kz/FS+dSllZ0/MSlzf3Qq3b74dpxuHaOS5/roD/Q7M8uwyU4nPZ+feN1gGfRMc5S4WIVtqPNQ7imXMkacq9UbmJZJgLUXBcGO323ERTfA3rtSViioIM+IPr7tbFshNOsScDlmdImK2nmEfRwqp36+7PoW4cAvzcCfLVfI9NNUMfTLBfiIa5kWa7p/0AwOB2s246TPgaBiY7TQZ7r8VtZkWcwmQQ+fSDTVWgzE1lICqI2aS0yELUQL7H0YymCy1Exy40s37FczTIeYybFfdS8XmE5gOVhlgKM7cMixbR4Rrox5SavweYmsW5JCIHYPVmWIINSGAT3aY+fg+dox3xS+P0uywuwsDqRVFZ2CO5jZ3Mq87qV5es0JBjOUs5yLjK5z1hOhMX+0gub38ON1U33colGG4X/eSPNWJ/hYWzkY5NrRP8rVOou03g8tK1fxLK/sCiVQnctAtACu7bEMoplI/Q/sBzK0gd61e4gkMkeYxzIfBRMqLr2srBcfbCwddD9zjKR5TSWsSw3i+vSwgwDqe37TMJ91PzPxLqp6+vgXvXUfjDLWaL/QvQtzLB/i7DuNiah/88gbNY4Udx8hkub6aLNyS5t9oX+dgfdKOh+cnEVhBOu2hytXVfj/cpSlmYOj2vuS8dY6Fdo13uKOOlUlyj8SZf5qHx9d+36kejzmyCrbjHtTKw8wzqcksXe9YO1kfGaSvObMcZYNz/mhA+ECbkUfkbHAuEK3HzPZfic46ArEM9Q5KCXfl+eAHUSb8MmbczCv7qRLE/cR6JJFF+GuMQRuS6Wb6bDXDLN8024gDDis5DLWtjWNBOuByFfFde2ilrC+V6IQKLqpxhe5aAfIPpPhrvQF2AyxtnexQg4IU7X04gDVu7k1IsQf6R7HsIhuRzfH4OF84q5+FQu69guPHcAcdRH9O+3HfPxWYm9y5oIb7OsFz5GN2vVqBsov1iCTZc4Dye5O8rIO2B1aumvcvj8btx0PcBVzzwU35elKb6Voah2l2aGO/O66Xe4OoVjujCX0dgLZS3fRwngUwSxDwhLeK4XIuxAdG/7+tOErhIbczFMG+GUFos2KnVZikCoq3gB2UthmvpGZ1GAoEwFhHsjiAyhuLbGpc8RMOM1qJH80cVniCCGIK8VQA1Xw/o+D/em4p96ZFy1SALIiQiZoMz9ZoeCRD0iUBkMKZmGa8dmEdycgjYbRUSv+8Xv0eZhTE59X410NBOeQftZLvoJ0Dchyp6LhVqG9NkJd4vovT9M7HQtQtdxEvpsQiDnBBVzfI52zzvo7XU4M819hqLNJWnaVIhUtMIrGarFRg9DXqs/1MdiU1Xa8yICFuomItjvJz7E7+UZCkBeiLAClmyYS6rsRIRntYh8YheJ0EPM9Z5OEuEJkLogQ5HLTo8fzdY1yKDRLrY8CllF/3yjNVOUqFWGcAJOcXfBfsE1Hq5mJAKz7oAfUXW9h0DPr0XkC8R6FnbiGQpFhrK0k+7tKtQ+WjPEQ2+LtezphQgb4DftQGYU/L/EhyKKn4xCVE2WmUBKVOgkZOUuis9mpD9tKEJNTTN+Uvt003sJ7uLap44bHNxKMsM8CUWjIAI7pwDVcshW9JRR4bks5rAQNYtSFNA8YTgmYptlp9L0JOFCbs5izLPRdrtD6mlHt5vQZoKmu0Dc64I01TULgaYTqqBf72Ed5qBPrYPucJYGnDSJcaJKW+pQPZwI/VqntA6HdQPaOKXx+4EgjR7mUY/xvszgShzxCTpXuujzUUqO07//VkGiP8z6L2Izl+OE24tzKU6GJchyB/3zZc0XQl8rgh/lkxcLnYrI54miVxnihs2izTLc3+1vOEeCWAm0Vyf7LQSYNUjPLByWElF/mYENlvO8DylwNSzpr3AtJQ4EqBLxl4XsZBbWdyCqp1uEfgkOYaGL+1AW6zURMFqoWt4X8EAE9VJH1ezfcdG3IS3JFTmxW8CyDRNqwuIP1MxtApOai8XtiwmHRLtb8TxRBJs+4b9XIZJvBUF3E1YsgFStGic4DIsUyfDM67GQTfjdQyxmLsj0IwhmP4edgm/D8/Whv1+0qXavYKM3uNzT/nOARZh3Ma4HMZ6axyNYmyDIF3ex2DlYq3qszQ6sZzE5vyw0MDAwMDAwMDAwQKpm/uGmgZ1SGBgYIhgYIhgYIhgYIhgYIhgYIhgYIhgYIhgYIhh4w58CDAAL8AVXFSeKUgAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://is.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://is.wikipedia.org/wiki/Kerfissíða:Leit"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-it.xml b/browser/locales/searchplugins/wikipedia-it.xml
new file mode 100644
index 000000000..6e9454197
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-it.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (it)</ShortName>
+<Description>Wikipedia, l'enciclopedia libera</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://it.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://it.wikipedia.org/wiki/Speciale:Ricerca"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ja.xml b/browser/locales/searchplugins/wikipedia-ja.xml
new file mode 100644
index 000000000..f51004d9b
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ja.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (ja)</ShortName>
+<Description>Wikipedia - フリー百科事典</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ja.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ja.wikipedia.org/wiki/特別:検索"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ka.xml b/browser/locales/searchplugins/wikipedia-ka.xml
new file mode 100644
index 000000000..53a868d76
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ka.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>ვიკიპედია (ka)</ShortName>
+<Description>ვიკიპედია, თავისუფალი ენციკლოპედია</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ka.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ka.wikipedia.org/wiki/სპეციალური:ძიება"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-kab.xml b/browser/locales/searchplugins/wikipedia-kab.xml
new file mode 100644
index 000000000..63f01a585
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-kab.xml
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (kab)</ShortName>
+<Description>Wikipedia, tasanayt tilellit</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://kab.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://kab.wikipedia.org/wiki/Uslig:Search" resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin> \ No newline at end of file
diff --git a/browser/locales/searchplugins/wikipedia-kk.xml b/browser/locales/searchplugins/wikipedia-kk.xml
new file mode 100644
index 000000000..1e17e5970
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-kk.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Уикипедия (kk)</ShortName>
+<Description>Уикипедия (kk)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://kk.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://kk.wikipedia.org/wiki/Арнайы:Іздеу"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-km.xml b/browser/locales/searchplugins/wikipedia-km.xml
new file mode 100644
index 000000000..62bf73d87
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-km.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>វីគីភីឌា (km)</ShortName>
+<Description>វីគីភីឌា សព្វ​វចនា​ធិប្បាយ​សេរី</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://km.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://km.wikipedia.org/wiki/ពិសេស:ស្វែងរក"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-kn.xml b/browser/locales/searchplugins/wikipedia-kn.xml
new file mode 100644
index 000000000..34af446da
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-kn.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (kn)</ShortName>
+<Description>Wikipedia, the free encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://kn.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://kn.wikipedia.org/wiki/ವಿಶೇಷ:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-kr.xml b/browser/locales/searchplugins/wikipedia-kr.xml
new file mode 100644
index 000000000..2ed60c869
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-kr.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>위키백과 (ko)</ShortName>
+<Description>Wikipedia, the free encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ko.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ko.wikipedia.org/wiki/특수기능:찾기"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-lij.xml b/browser/locales/searchplugins/wikipedia-lij.xml
new file mode 100644
index 000000000..23c1bcea4
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-lij.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (lij)</ShortName>
+<Description>Wikipedia, l'enciclopedia libera</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://lij.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://lij.wikipedia.org/wiki/Speçiale:Riçerca"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-lt.xml b/browser/locales/searchplugins/wikipedia-lt.xml
new file mode 100644
index 000000000..f2e0a2d3d
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-lt.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (lt)</ShortName>
+<Description>Vikipedija, laisvoji enciklopedija</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://lt.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://lt.wikipedia.org/wiki/Specialus:Paieška"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-lv.xml b/browser/locales/searchplugins/wikipedia-lv.xml
new file mode 100644
index 000000000..2eccccbb4
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-lv.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vikipēdija</ShortName>
+<Description>Vikipēdija, brīvā encikopēdija</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://lv.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://lv.wikipedia.org/wiki/Special:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-mk.xml b/browser/locales/searchplugins/wikipedia-mk.xml
new file mode 100644
index 000000000..a9782adc4
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-mk.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Википедија (mk)</ShortName>
+<Description>Википедија, слободната енциклопедија</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://mk.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://mk.wikipedia.org/wiki/Специјална:Барај"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ml.xml b/browser/locales/searchplugins/wikipedia-ml.xml
new file mode 100644
index 000000000..b75780316
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ml.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>വിക്കിപീഡിയ (ml)</ShortName>
+<Description>വിക്കിപീഡിയ, സ്വതന്ത്ര സര്‍വ്വവിജ്ഞാനകോശം </Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ml.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ml.wikipedia.org/wiki/പ്രത്യേകം:അന്വേഷണം"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-mr.xml b/browser/locales/searchplugins/wikipedia-mr.xml
new file mode 100644
index 000000000..a161599fa
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-mr.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>विकिपीडिया (mr)</ShortName>
+<Description>विकिपीडिया, मोफत माहितीकोष</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://mr.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://mr.wikipedia.org/wiki/विशेष:शोधा"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ms.xml b/browser/locales/searchplugins/wikipedia-ms.xml
new file mode 100644
index 000000000..13be0d2f4
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ms.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (ms)</ShortName>
+<Description>Wikipedia, ensiklopedia bebas</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ms.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ms.wikipedia.org/wiki/Khas:Gelintar"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-my.xml b/browser/locales/searchplugins/wikipedia-my.xml
new file mode 100644
index 000000000..d6852bd28
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-my.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (my)</ShortName>
+<Description>အခမဲ့လွတ်လပ်စွယ်စုံကျမ်း</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://my.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://my.wikipedia.org/wiki/Special:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ne.xml b/browser/locales/searchplugins/wikipedia-ne.xml
new file mode 100644
index 000000000..5f704a813
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ne.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>विकिपीडिया (ne)</ShortName>
+<Description>विकिपिडिया एक स्वतन्त्र विश्वकोष</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ne.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ne.wikipedia.org/wiki/विशेष:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-nl.xml b/browser/locales/searchplugins/wikipedia-nl.xml
new file mode 100644
index 000000000..ade5458ad
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-nl.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (nl)</ShortName>
+<Description>De vrije encyclopedie</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://nl.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://nl.wikipedia.org/wiki/Speciaal:Zoeken"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-or.xml b/browser/locales/searchplugins/wikipedia-or.xml
new file mode 100644
index 000000000..27dafbf8a
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-or.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (or)</ShortName>
+<Description>ୱିକିପିଡ଼ିଆ (ଓଡ଼ିଆ)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://or.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://or.wikipedia.org/wiki/ବିଶେଷ:ଖୋଜନ୍ତୁ"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-pa.xml b/browser/locales/searchplugins/wikipedia-pa.xml
new file mode 100644
index 000000000..ed13d944c
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-pa.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (pa)</ShortName>
+<Description>ਵਿਕਿਪੀਡਿਆ, ਮੁਫ਼ਤ/ਮੁਕਤ ਸ਼ਬਦਕੋਸ਼</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://pa.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://pa.wikipedia.org/wiki/ਖ਼ਾਸ:ਖੋਜੋ"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-pl.xml b/browser/locales/searchplugins/wikipedia-pl.xml
new file mode 100644
index 000000000..2ff566600
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-pl.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (pl)</ShortName>
+<Description>Wikipedia, wolna encyklopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://pl.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch" />
+ <Param name="search" value="{searchTerms}" />
+</Url>
+ <Url method="GET"
+ rel="searchform"
+ template="https://pl.wikipedia.org/wiki/Specjalna:Szukaj"
+ type="text/html">
+ <Param name="search" value="{searchTerms}" />
+ <Param name="sourceid" value="Mozilla-search" />
+ </Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-pt.xml b/browser/locales/searchplugins/wikipedia-pt.xml
new file mode 100644
index 000000000..86e4b76e4
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-pt.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (pt)</ShortName>
+<Description>Wikipédia, a enciclopédia livre</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://pt.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://pt.wikipedia.org/wiki/Especial:Pesquisar"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-rm.xml b/browser/locales/searchplugins/wikipedia-rm.xml
new file mode 100644
index 000000000..39702810f
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-rm.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (rm)</ShortName>
+<Description>Vichipedia, l'enciclopedia libra</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://rm.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://rm.wikipedia.org/wiki/Spezial:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ru.xml b/browser/locales/searchplugins/wikipedia-ru.xml
new file mode 100644
index 000000000..a5660bc5d
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ru.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Википедия (ru)</ShortName>
+<Description>Википедия, свободная энциклопедия</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ru.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ru.wikipedia.org/wiki/Служебная:Поиск"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-si.xml b/browser/locales/searchplugins/wikipedia-si.xml
new file mode 100644
index 000000000..6cb6d8102
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-si.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (si)</ShortName>
+<Description>Wikipedia, the free encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://si.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://si.wikipedia.org/wiki/විශේෂ:ගවේෂණය"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-sk.xml b/browser/locales/searchplugins/wikipedia-sk.xml
new file mode 100644
index 000000000..f3cf6f00a
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-sk.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipédia (sk)</ShortName>
+<Description>Wikipédia, slobodná a otvorená encyklopédia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://sk.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://sk.wikipedia.org/wiki/Špeciálne:Hľadanie"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-sl.xml b/browser/locales/searchplugins/wikipedia-sl.xml
new file mode 100644
index 000000000..1fd10c25f
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-sl.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedija</ShortName>
+<Description>Wikipedija, prosta enciklopedija</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://sl.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://sl.wikipedia.org/wiki/Posebno:Iskanje"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-sq.xml b/browser/locales/searchplugins/wikipedia-sq.xml
new file mode 100644
index 000000000..a72be35e2
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-sq.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (sq)</ShortName>
+<Description>Wikipedia, enciklopedia e lirë</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://sq.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://sq.wikipedia.org/wiki/Speciale:Kërkim"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-sr.xml b/browser/locales/searchplugins/wikipedia-sr.xml
new file mode 100644
index 000000000..50feaed8f
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-sr.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Википедија (sr)</ShortName>
+<Description>Претрага Википедије на српском језику</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://sr.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://sr.wikipedia.org/wiki/Посебно:Претражи"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-sv-SE.xml b/browser/locales/searchplugins/wikipedia-sv-SE.xml
new file mode 100644
index 000000000..23df8087d
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-sv-SE.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (sv)</ShortName>
+<Description>Wikipedia, den fria encyklopedin</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://sv.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://sv.wikipedia.org/wiki/Special:Sök"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ta.xml b/browser/locales/searchplugins/wikipedia-ta.xml
new file mode 100644
index 000000000..f727c1d66
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ta.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>விக்கிப்பீடியா (ta)</ShortName>
+<Description>விக்கிப்பீடியா (ta)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ta.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ta.wikipedia.org/wiki/சிறப்பு:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-te.xml b/browser/locales/searchplugins/wikipedia-te.xml
new file mode 100644
index 000000000..ec3775ffa
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-te.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>వికీపీడియా (te)</ShortName>
+<Description>వికీపీడియా (te)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://te.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://te.wikipedia.org/wiki/ప్రత్యేక:అన్వేషణ"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-th.xml b/browser/locales/searchplugins/wikipedia-th.xml
new file mode 100644
index 000000000..7c62f5457
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-th.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>วิกิพีเดีย</ShortName>
+<Description>วิกิพีเดีย สารานุกรมเสรี</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://th.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://th.wikipedia.org/wiki/พิเศษ:ค้นหา"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-tl.xml b/browser/locales/searchplugins/wikipedia-tl.xml
new file mode 100644
index 000000000..fbc14236d
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-tl.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (tl)</ShortName>
+<Description>Wikipedia, ang malayang ensiklopedya</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://tl.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://tl.wikipedia.org/wiki/Natatangi:Maghanap"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-tr.xml b/browser/locales/searchplugins/wikipedia-tr.xml
new file mode 100644
index 000000000..7841ca2ca
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-tr.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (tr)</ShortName>
+<Description>Vikipedi, özgür ansiklopedi</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://tr.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://tr.wikipedia.org/wiki/Özel:Ara"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-uk.xml b/browser/locales/searchplugins/wikipedia-uk.xml
new file mode 100644
index 000000000..4f7df8371
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-uk.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Вікіпедія (uk)</ShortName>
+<Description>Вікіпедія, вільна енциклопедія</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://uk.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://uk.wikipedia.org/wiki/Спеціальна:Пошук"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-ur.xml b/browser/locales/searchplugins/wikipedia-ur.xml
new file mode 100644
index 000000000..6c86ab18d
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-ur.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>ویکیپیڈیا (ur)</ShortName>
+<Description>ویکیپیڈیا آزاد دائرۃ المعارف</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ur.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ur.wikipedia.org/wiki/خاص:تلاش"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-uz.xml b/browser/locales/searchplugins/wikipedia-uz.xml
new file mode 100644
index 000000000..badfc12c0
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-uz.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Vikipediya (uz)</ShortName>
+<Description>Vikipediya, ochiq ensiklopediya</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://uz.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://uz.wikipedia.org/wiki/Maxsus:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-vi.xml b/browser/locales/searchplugins/wikipedia-vi.xml
new file mode 100644
index 000000000..ba8639b29
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-vi.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (vi)</ShortName>
+<Description>Wikipedia, bách khoa toàn thư mở</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://vi.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://vi.wikipedia.org/wiki/Đặc_biệt:Tìm_kiếm"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-wo.xml b/browser/locales/searchplugins/wikipedia-wo.xml
new file mode 100644
index 000000000..046ffad38
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-wo.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (wo)</ShortName>
+<Description>Wikipedia, Jimbulang bu Ubbeeku bi</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://wo.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://wo.wikipedia.org/wiki/Jagleel:Ceet"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-zh-CN.xml b/browser/locales/searchplugins/wikipedia-zh-CN.xml
new file mode 100644
index 000000000..0a6e813f3
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-zh-CN.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>维基百科</ShortName>
+<Description>维基百科,自由的百科全书</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://zh.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://zh.wikipedia.org/wiki/Special:搜索"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia-zh-TW.xml b/browser/locales/searchplugins/wikipedia-zh-TW.xml
new file mode 100644
index 000000000..f25052349
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia-zh-TW.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (zh)</ShortName>
+<Description>維基百科,自由的百科全書</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://zh.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://zh.wikipedia.org/wiki/Special:搜索"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+ <Param name="variant" value="zh-tw"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipedia.xml b/browser/locales/searchplugins/wikipedia.xml
new file mode 100644
index 000000000..9bb2634e6
--- /dev/null
+++ b/browser/locales/searchplugins/wikipedia.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (en)</ShortName>
+<Description>Wikipedia, the Free Encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://en.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://en.wikipedia.org/wiki/Special:Search"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wikipediaro.xml b/browser/locales/searchplugins/wikipediaro.xml
new file mode 100644
index 000000000..58021460c
--- /dev/null
+++ b/browser/locales/searchplugins/wikipediaro.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (ro)</ShortName>
+<Description>Wikipedia, enciclopedia liberă</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://ro.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://ro.wikipedia.org/wiki/Special:Căutare"
+ resultdomain="wikipedia.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="sourceid" value="Mozilla-search"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wiktionary-te.xml b/browser/locales/searchplugins/wiktionary-te.xml
new file mode 100644
index 000000000..851e02089
--- /dev/null
+++ b/browser/locales/searchplugins/wiktionary-te.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>విక్షనరీ (te)</ShortName>
+<Description>విక్షనరీ (te)</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9%2FAAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB%2FWvXoYiIiIfEZfWBSIiIEGi%2FfoqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF%2BiDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://te.wiktionary.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+ <Param name="namespace" value="0"/>
+</Url>
+<Url type="text/html" method="get" template="https://te.wiktionary.org/wiki/ప్రత్యేక:అన్వేషణ"
+ resultdomain="wiktionary.org" rel="searchform">
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/wolnelektury-pl.xml b/browser/locales/searchplugins/wolnelektury-pl.xml
new file mode 100644
index 000000000..2ac3ba4df
--- /dev/null
+++ b/browser/locales/searchplugins/wolnelektury-pl.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Wolne Lektury</ShortName>
+ <Description>Biblioteka internetowa WolneLektury.pl</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image height="16" width="16" type="image/png">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAZlBMVEWauyuauiubuiubuyqauyqbvCrHuBfHuBfsvADrvgDsvgDsvQDrvgDsvQCbuyvsvgC4ABy8ABy+ABm+ABq/ABq/ABm+ABk9YW4+YW49YW49YnA9Y3E4Y3E4Y3G/ABo+Ym+/ABm4ABw5ZyQ5AAAAInRSTlPbyaSAWzYSADZbgKTJ2///EjZbf6TJ28+kgFs2EgD//4AA+LFVUAAAAFdJREFUeAGdzsEKgDAMA9CklzlwQy/+/zeKoCAuspVdvAx8hwYCpSXoIKNLqC7Elo8RjaxnL/i7KAxyqc1ChkgHuiWjOm3qfwyv5G9xjFa4SfMurbhV4QWhBzKL2hdUYQAAAABJRU5ErkJggg==</Image>
+ <SearchForm>https://wolnelektury.pl</SearchForm>
+ <Url method="GET"
+ template="https://wolnelektury.pl/katalog/jtags/?mozhint=1&amp;q={searchTerms}"
+ type="application/x-suggestions+json" />
+ <Url method="GET"
+ template="https://wolnelektury.pl/szukaj/?q={searchTerms}"
+ type="text/html" />
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-NO.xml b/browser/locales/searchplugins/yahoo-NO.xml
new file mode 100644
index 000000000..8353adf6b
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-NO.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Søk</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://no.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://no.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://no.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-answer-zh-TW.xml b/browser/locales/searchplugins/yahoo-answer-zh-TW.xml
new file mode 100644
index 000000000..a12e7f19d
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-answer-zh-TW.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo!奇摩知識+</ShortName>
+<Description>Yahoo!奇摩知識+</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/gif;base64,R0lGODlhEAAQAOYAAAAAAP///3dub4eFh15ZYGZXcWxlgltdakxQXF1ulFJWW1VZXlpeY1xgZWZ2iGtucU5WXVFZYFJYXQB1xwB1xgByw1p5jACd9ACH0wCEzACDywB/ycDo/sfr/8rq/FhhZlhfY7O6vgCb6ACQ1wCJzgCHzaTd+qfe+rXk+7nl+8fs/9Lw/9XZ2+bo6Y3W+I/Y+Z3b+Z3c+Z7c+aDc+aXi/qTe+qfh+6rg+rjj+Lzn+8Pq/Mjs/QCi4wCZ2AWc2R2l3Uy56Fa+6Yjc/ojU9YvX+JDa+JLZ+JXb+Z7c9qrk/Mrt/FzB6L7u/8bd5fP9/+j8/+D8/+b+/+X//+f//+r//+////f//4R/df2bAO6lLf/+/Yh0boNuanBnZq6urv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAF8ALAAAAAAQABAAAAeggF+Cgl4IEREHXoOLghAtAZABLR+MghFaAU5KHiuQDIxejzs3MjAmKFItioMQAVEpNUYuRycdAQuLrVUqQzQvRTM5AQq5AVMVJEBESTY6ARKLoQEZPyUbQRxQqowQVj44SxpIVAEPlRBPPQEUMQEsDZVfXU0YE0wBAwXxX1w8I0IBQOz74mCLiAshCAwkiMWABQELGWa54iDilwQOKg4MBAA7</Image>
+<Url type="text/html" method="GET" template="https://tw.knowledge.search.yahoo.com/search" resultDomain="tw.knowledge.search.yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="fr" value="uh3_answers_web_gs"/>
+</Url>
+<SearchForm>https://tw.answers.yahoo.com/</SearchForm>
+</SearchPlugin>
+
diff --git a/browser/locales/searchplugins/yahoo-ar.xml b/browser/locales/searchplugins/yahoo-ar.xml
new file mode 100644
index 000000000..f855f7fe4
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-ar.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo Argentina</ShortName>
+<Description>Buscar en Yahoo Argentina</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://ar.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://ar.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://ar.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-bid-zh-TW.xml b/browser/locales/searchplugins/yahoo-bid-zh-TW.xml
new file mode 100644
index 000000000..b290bed8a
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-bid-zh-TW.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo!奇摩拍賣</ShortName>
+<Description>Yahoo!奇摩拍賣</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/gif;base64,R0lGODlhEAAQAMQfAP+7Q4aWvf2QBaKtx//MS+6OLI1MMf/XYv+sKkpalEZNepyUa4KOo2ttjpx4Tdh1ALdrI9lSAHhaVWNSa+Z7Ge9zAPdrALVaIR4/ie9rAOZrELXB0Gtzc/9zCJg+Hf///yH5BAEAAB8ALAAAAAAQABAAAAWM4CeO3xAEA6mKw3IQDrOSAUEAiBPMpXsciIdk1XAUfkCBwKBiAG4IAED5UKg4iGxFWfFgNk0lF+KJWBqribgCwUgKlsRok0hMHvj2YNKJfzYMBxcXE4UTEQkbChlxDT9SBZEUGogBiwkSBFE4khoGcgEJAw0GEBSRBRQUF580CQawsbAJO64Kt7i0KiEAOw==</Image>
+<Url type="text/html" method="GET" template="http://tw.search.bid.yahoo.com/search/ac" resultdomain="bid.yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+</Url>
+<SearchForm>http://tw.search.bid.yahoo.com/search/ac</SearchForm>
+</SearchPlugin> \ No newline at end of file
diff --git a/browser/locales/searchplugins/yahoo-br.xml b/browser/locales/searchplugins/yahoo-br.xml
new file mode 100644
index 000000000..d22ad16ff
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-br.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Pesquisa Yahoo</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://br.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://br.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://br.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-ch.xml b/browser/locales/searchplugins/yahoo-ch.xml
new file mode 100644
index 000000000..946a8fc38
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-ch.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Tschertga Yahoo Svizra</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://ch.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://ch.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://ch.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-cl.xml b/browser/locales/searchplugins/yahoo-cl.xml
new file mode 100644
index 000000000..4796c87c0
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-cl.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Buscar en Yahoo Chile</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://cl.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://cl.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://cl.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-de.xml b/browser/locales/searchplugins/yahoo-de.xml
new file mode 100644
index 000000000..dde138bb8
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-de.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://de.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://de.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://de.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-en-CA.xml b/browser/locales/searchplugins/yahoo-en-CA.xml
new file mode 100644
index 000000000..b23af314c
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-en-CA.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo Canada</ShortName>
+<Description>Yahoo Canada Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://ca.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://ca.search.yahoo.com/yhs/search"
+ resultdomain="yahoo.com" rel="searchform">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="hspart" value="mozilla"/>
+ <MozParam name="hsimp" condition="purpose" purpose="searchbar" value="yhs-001"/>
+ <MozParam name="hsimp" condition="purpose" purpose="keyword" value="yhs-002"/>
+ <MozParam name="hsimp" condition="purpose" purpose="homepage" value="yhs-003"/>
+ <MozParam name="hsimp" condition="purpose" purpose="newtab" value="yhs-004"/>
+ <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
+ <MozParam name="hsimp" condition="purpose" purpose="system" value="yhs-007"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-en-GB.xml b/browser/locales/searchplugins/yahoo-en-GB.xml
new file mode 100644
index 000000000..9ac91b9f7
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-en-GB.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo.co.uk</ShortName>
+<Description>Yahoo UK &amp; Ireland Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://uk.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://uk.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://uk.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-es.xml b/browser/locales/searchplugins/yahoo-es.xml
new file mode 100644
index 000000000..7d2137b1c
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-es.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://es.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://es.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://es.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-espanol.xml b/browser/locales/searchplugins/yahoo-espanol.xml
new file mode 100644
index 000000000..33d35abe2
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-espanol.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://espanol.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://espanol.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://espanol.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-fi.xml b/browser/locales/searchplugins/yahoo-fi.xml
new file mode 100644
index 000000000..885118c04
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-fi.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo-haku</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://fi.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://fi.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://fi.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-france.xml b/browser/locales/searchplugins/yahoo-france.xml
new file mode 100644
index 000000000..aee1f204a
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-france.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Recherche Yahoo</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://fr.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://fr.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://fr.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-fy-NL.xml b/browser/locales/searchplugins/yahoo-fy-NL.xml
new file mode 100644
index 000000000..3ffc5efeb
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-fy-NL.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Sykje mei Yahoo</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://nl.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://nl.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://nl.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-id.xml b/browser/locales/searchplugins/yahoo-id.xml
new file mode 100644
index 000000000..45e46d249
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-id.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Pencarian Yahoo</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://id.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://id.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://id.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-in.xml b/browser/locales/searchplugins/yahoo-in.xml
new file mode 100644
index 000000000..d537aaad3
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-in.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://in.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://in.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://in.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-it.xml b/browser/locales/searchplugins/yahoo-it.xml
new file mode 100644
index 000000000..5099a6598
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-it.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://it.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://it.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://it.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-jp-auctions.xml b/browser/locales/searchplugins/yahoo-jp-auctions.xml
new file mode 100644
index 000000000..940ea7fef
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-jp-auctions.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>ヤフオク!</ShortName>
+<Description>ヤフオク! 検索</Description>
+<InputEncoding>EUC-JP</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAKNJREFUOBHFkVELgzAMhHND2f7/r50oRK/jymEVC31YX5ImuS9pisyMkfMaEVP7fwDWeN8uYcovnp4ILhFAHqBaO8dS/COHDZ/bBiwqO2ChdyOMMRZ4nGC/VwAdHk+qsyzFgv6qIxhrfuEK4mIBBWkA50lcLBGt4pMHe3xOyKXrOeUXXHgeUTkKlfNnNgDSJXJbO1p35huAi3r8yyX2CFUzDNgBKmdd7X70FZwAAAAASUVORK5CYII=</Image>
+<Url type="text/html" method="GET" template="http://auctions.search.yahoo.co.jp/search" resultdomain="yahoo.co.jp">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="EUC-JP"/>
+ <Param name="fr" value="mozff" />
+ <Param name="rls" value="{moz:distributionID}:ja-JP:{moz:official}"/>
+</Url>
+<SearchForm>http://auctions.yahoo.co.jp/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-jp.xml b/browser/locales/searchplugins/yahoo-jp.xml
new file mode 100644
index 000000000..7a29e5c19
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-jp.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo! JAPAN</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMA/24zAP//MwD//zMA//8zAP//MwD//wAAAAAAAAAAMwD/7TMA/+MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkI//8zAP//MwD/bwAAAAAAAAAAAAAAADEA/zQyAP8pNwD/FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5CP//MwD//zIA/3AAAAAAAAAAAAAAAAAAAAAAMgD/wDIA/6cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AP8sMwD/9zMA//8zAP//NwD/FwAAAAAAAAAAAAAAADQA/3EzAP//MQD/PgAAAAAAAAAAAAAAAAAAAAA3AP8OMwD/2DMA//80AP9PMwD/WjMA//8xAP8qAAAAAAAAAAAyAP8kMwD//zMA/9EAAAAIAAAAAAAAAAAAAAADMwD/tDMA//8yAP+JAAAAAAAAAAAxAP85MwD//zQA/1gAAAAAAAAAADMA/9UzAP//MwD/qQAAAAAAAAAAMgD/kzMA//8zAP/EAAAABAAAAAAAAAAAMwD//zMA//8zAP//MwD//zMA/30xAP8+MwD/+TEA/z4yAP+sMwD//zMA//8zAP//MwD//zMA/5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD//wAA//8AAP//AADgZwAA+OMAAPjzAADwcQAA4DAAAMMYAADDAAAAA/8AAP//AAD//wAA//8AAP//AAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwD//zMA//8zAP//MwD//zMA//8zAP//MwD//zMA//8zAP//MwD//zMA//8zAP//AAAAAAAAAAAAAAAAMQD/HzQA/2MyAP+iAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyAP+dMwD/9jMA//8zAP//MwD//zMA//8zAP//MwD//zMA//8zAP//MwD//zMA/3gAAAAAAAAAAAAAAAAzAP+/MwD//zMA//80AP9UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAD/JzQA/4ozAP//MwD//zMA//8zAP/nMwD/NzQA/ycxAP8VAAAABAAAAAAAAAAAAAAAADMA/1AzAP//MwD/zjMA/18AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMA//8zAP//MwD//zMA/98AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjAA/yUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwD//zMA//8zAP//MwD/3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADUA/yIzAP9zMwD/UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzAP//MwD//zMA//8zAP/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAD/njMA//8zAP/pMwD/DwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAD/TzMA//8zAP//MwD//zMA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzAP9QMwD//zMA//80AP+PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAg0AP+UMwD//zMA//8zAP//MwD//jIA/1IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC4A/wszAP/2MwD//zMA//sxAP8qAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwD/lzMA//8zAP//MwD//zMA/84zAP/5MwD/+TIA/1EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMA/7MzAP//MwD//zQA/7wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIA/1szAP//MwD//zMA//8zAP/fOQD/EjMA/0EzAP/tMwD//DQA/2wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMgD/ZTMA//8zAP//MwD//zIA/1IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxAP8vMwD/9TMA//8zAP//MwD/+DEA/zQAAAAAAAAAADUA/yIzAP/UMwD//zMA/5YAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAzAP8ZMwD//TMA//8zAP//MwD/3wAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALgD/FjMA/98zAP//MwD//zMA//8yAP9mAAAAAAAAAAAAAAAAAAAAADMA/w8zAP+4MwD//zMA/8MzAP8ZAAAAAAAAAAAAAAAAAAAAAAAAAAAzAP/IMwD//zMA//8zAP//MgD/fwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYzAP/DMwD//zMA//8zAP//MgD/ogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYzAP+zMwD//zMA/+k0AP9JAAAAAAAAAAAAAAAAAAAAADMA/3kzAP//MwD//zMA//8zAP/2MAD/IAAAAAAAAAAAAAAAAAAAAAAAAAABMwD/pDMA//8zAP//MwD//zMA/9YuAP8LAAAAAAAAAAAAAAAAAAAAAAAAAAA1AP8/NAD/YjMA/5ozAP//MwD//zMA//8zAP+/MwD/gzIA/00wAP8QNQD/KzMA//8zAP//MwD//zMA//8zAP+zAAAAAAAAAAAAAAAAAAAAADMA/40zAP//MwD//zMA//8zAP/3MwD/LQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMA/98zAP/fMwD/3zMA/98zAP/fMwD/3zMA/98zAP/fMwD/3zMA/58AAAAAMwD/3TMA//8zAP/fMwD/kTEA/0MzAP88MgD/YDMA/4IzAP+6MwD//zMA//8zAP//MwD//zMA/7oyAP9HNAD/NjAA/yUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AP9FMgD/LgAAAAAAAAAAAAAAADMA//YzAP//MwD//zMA//8zAP//MwD//zMA//8zAP//MwD//zMA//8zAP//MwD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////////////////////////////////+ABw//gAcP/+AHD//4fz//+H/H//h/w//wf8P/4D/B/+Af4f/AD+D/gYPgfwPB8H4H4PA8B8AAPA/ACAAD//nAA//////////////////////////////////////////////</Image>
+<Url type="text/html" method="GET" template="http://search.yahoo.co.jp/search" resultdomain="yahoo.co.jp">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="mozff" />
+</Url>
+<SearchForm>http://search.yahoo.co.jp/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-mx.xml b/browser/locales/searchplugins/yahoo-mx.xml
new file mode 100644
index 000000000..60241a6eb
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-mx.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://mx.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://mx.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://mx.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-sv-SE.xml b/browser/locales/searchplugins/yahoo-sv-SE.xml
new file mode 100644
index 000000000..33eb9f022
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-sv-SE.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Sök</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://se.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://se.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://se.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-tl.xml b/browser/locales/searchplugins/yahoo-tl.xml
new file mode 100644
index 000000000..d44fe0849
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-tl.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://sg.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://sg.search.yahoo.com/search" resultdomain="yahoo.com">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="fr" value="moz35" />
+</Url>
+<SearchForm>https://sg.search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-zh-TW-HK.xml b/browser/locales/searchplugins/yahoo-zh-TW-HK.xml
new file mode 100644
index 000000000..6b06e8cb2
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-zh-TW-HK.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo 雅虎香港</ShortName>
+<Description>Yahoo 雅虎香港</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://hk.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://hk.search.yahoo.com/yhs/search"
+ resultdomain="yahoo.com" rel="searchform">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="hspart" value="mozilla"/>
+ <MozParam name="hsimp" condition="purpose" purpose="searchbar" value="yhs-001"/>
+ <MozParam name="hsimp" condition="purpose" purpose="keyword" value="yhs-002"/>
+ <MozParam name="hsimp" condition="purpose" purpose="homepage" value="yhs-003"/>
+ <MozParam name="hsimp" condition="purpose" purpose="newtab" value="yhs-004"/>
+ <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo-zh-TW.xml b/browser/locales/searchplugins/yahoo-zh-TW.xml
new file mode 100644
index 000000000..b408fa790
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo-zh-TW.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo 搜尋</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://tw.search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://tw.search.yahoo.com/yhs/search"
+ resultdomain="yahoo.com" rel="searchform">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="hspart" value="mozilla"/>
+ <MozParam name="hsimp" condition="purpose" purpose="searchbar" value="yhs-001"/>
+ <MozParam name="hsimp" condition="purpose" purpose="keyword" value="yhs-002"/>
+ <MozParam name="hsimp" condition="purpose" purpose="homepage" value="yhs-003"/>
+ <MozParam name="hsimp" condition="purpose" purpose="newtab" value="yhs-004"/>
+ <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yahoo.xml b/browser/locales/searchplugins/yahoo.xml
new file mode 100644
index 000000000..7337bef92
--- /dev/null
+++ b/browser/locales/searchplugins/yahoo.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://search.yahoo.com/sugg/ff">
+ <Param name="output" value="fxjson" />
+ <Param name="appid" value="ffd" />
+ <Param name="command" value="{searchTerms}" />
+</Url>
+<Url type="text/html" method="GET" template="https://search.yahoo.com/yhs/search"
+ resultdomain="yahoo.com" rel="searchform">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+ <Param name="hspart" value="mozilla"/>
+ <MozParam name="hsimp" condition="purpose" purpose="searchbar" value="yhs-001"/>
+ <MozParam name="hsimp" condition="purpose" purpose="keyword" value="yhs-002"/>
+ <MozParam name="hsimp" condition="purpose" purpose="homepage" value="yhs-003"/>
+ <MozParam name="hsimp" condition="purpose" purpose="newtab" value="yhs-004"/>
+ <MozParam name="hsimp" condition="purpose" purpose="contextmenu" value="yhs-005"/>
+ <MozParam name="hsimp" condition="purpose" purpose="system" value="yhs-007"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yandex-az.xml b/browser/locales/searchplugins/yandex-az.xml
new file mode 100644
index 000000000..71cb613af
--- /dev/null
+++ b/browser/locales/searchplugins/yandex-az.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yandex</ShortName>
+<Description>İnternetdə axtarış üçün Yandexdən istifadə edin.</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAB50lEQVQ4T43SvWsacRgH8OcPCITSLRQ6BSRL1gxZTNZQsgYC3QKunVoMnTKFhBQSnDqIUa5gxNbBCgriC4pv50sJ1lIR5SROpRVbz+O4b5/flbvedekJH+557nvfH8chdbvdIFMYPAgBICdqt9uKpmnw8huNRuDnX8J5QKvVgmEYnqiqClmWwZ113kmger0OXdf/Wv6EIb0BTo+AgB94ceDKJ5MJuHPOMwlUqVSwWq1sevQaxqvn0O5l6HdvgaMdV75cLtFoNMC9Xd6JisWiedNiPNuB9l4yZ+1jEvBvuXJBURRwL8YzUT6fx2KxsGHrKdQPCXNW794Bvieu3CLegrsnlM1mMZ/PbfqeH6vToDkvb2+Bx49cuWU2m4G7bUqn0xiPx7ZpqYRf29v4cXyMxf4+tLU1V24ZDAbgbptSqRSGw6HL9OwM37n4bXPTvP6bC7lcDtw9oWQyiX6/b/vMH1XZ2MAoEMDXqytM+QBnLtRqNXAvxjNRPB5Hr9ez9Q8PMfD50OM/2P3FBb7wAc680+mIMri3yzuRJEloNpsmORTCJy7INzd/9stLc7dyIZPJgDvnPJNA0WgU1WrVkxJ/4FgsBu6s804CRSKRh0KhgHK5/F+JRAL8fJBnslA4HH7NHhg8CDnLwm8IYz560xw92AAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://suggest.yandex.net/suggest-ff.cgi">
+ <Param name="part" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://yandex.ru/yandsearch" resultdomain="yandex.ru">
+ <Param name="text" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.yandex.ru/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yandex-en.xml b/browser/locales/searchplugins/yandex-en.xml
new file mode 100644
index 000000000..ab5975f30
--- /dev/null
+++ b/browser/locales/searchplugins/yandex-en.xml
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yandex</ShortName>
+<Description>Use Yandex to search the Internet.</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">resource://search-plugins/images/yandex-en.ico</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://suggest.yandex.com/suggest-ff.cgi">
+ <Param name="part" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://www.yandex.com/search">
+ <Param name="text" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.yandex.com/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yandex-kk.xml b/browser/locales/searchplugins/yandex-kk.xml
new file mode 100644
index 000000000..23d892617
--- /dev/null
+++ b/browser/locales/searchplugins/yandex-kk.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Яндекс</ShortName>
+<Description>Воспользуйтесь Яндексом для поиска в Интернете.</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAB50lEQVQ4T43SvWsacRgH8OcPCITSLRQ6BSRL1gxZTNZQsgYC3QKunVoMnTKFhBQSnDqIUa5gxNbBCgriC4pv50sJ1lIR5SROpRVbz+O4b5/flbvedekJH+557nvfH8chdbvdIFMYPAgBICdqt9uKpmnw8huNRuDnX8J5QKvVgmEYnqiqClmWwZ113kmger0OXdf/Wv6EIb0BTo+AgB94ceDKJ5MJuHPOMwlUqVSwWq1sevQaxqvn0O5l6HdvgaMdV75cLtFoNMC9Xd6JisWiedNiPNuB9l4yZ+1jEvBvuXJBURRwL8YzUT6fx2KxsGHrKdQPCXNW794Bvieu3CLegrsnlM1mMZ/PbfqeH6vToDkvb2+Bx49cuWU2m4G7bUqn0xiPx7ZpqYRf29v4cXyMxf4+tLU1V24ZDAbgbptSqRSGw6HL9OwM37n4bXPTvP6bC7lcDtw9oWQyiX6/b/vMH1XZ2MAoEMDXqytM+QBnLtRqNXAvxjNRPB5Hr9ez9Q8PMfD50OM/2P3FBb7wAc680+mIMri3yzuRJEloNpsmORTCJy7INzd/9stLc7dyIZPJgDvnPJNA0WgU1WrVkxJ/4FgsBu6s804CRSKRh0KhgHK5/F+JRAL8fJBnslA4HH7NHhg8CDnLwm8IYz560xw92AAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://suggest.yandex.net/suggest-ff.cgi">
+ <Param name="part" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://yandex.kz/yandsearch" resultdomain="yandex.kz">
+ <MozParam name="clid" condition="purpose" purpose="searchbar" value="2186618"/>
+ <MozParam name="clid" condition="purpose" purpose="keyword" value="2186621"/>
+ <MozParam name="clid" condition="purpose" purpose="contextmenu" value="2186623"/>
+ <MozParam name="clid" condition="purpose" purpose="homepage" value="2186617"/>
+ <MozParam name="clid" condition="purpose" purpose="newtab" value="2186620"/>
+ <Param name="text" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.yandex.kz/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yandex-ru.xml b/browser/locales/searchplugins/yandex-ru.xml
new file mode 100644
index 000000000..0f7551303
--- /dev/null
+++ b/browser/locales/searchplugins/yandex-ru.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Яндекс</ShortName>
+<Description>Воспользуйтесь Яндексом для поиска в Интернете.</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAB50lEQVQ4T43SvWsacRgH8OcPCITSLRQ6BSRL1gxZTNZQsgYC3QKunVoMnTKFhBQSnDqIUa5gxNbBCgriC4pv50sJ1lIR5SROpRVbz+O4b5/flbvedekJH+557nvfH8chdbvdIFMYPAgBICdqt9uKpmnw8huNRuDnX8J5QKvVgmEYnqiqClmWwZ113kmger0OXdf/Wv6EIb0BTo+AgB94ceDKJ5MJuHPOMwlUqVSwWq1sevQaxqvn0O5l6HdvgaMdV75cLtFoNMC9Xd6JisWiedNiPNuB9l4yZ+1jEvBvuXJBURRwL8YzUT6fx2KxsGHrKdQPCXNW794Bvieu3CLegrsnlM1mMZ/PbfqeH6vToDkvb2+Bx49cuWU2m4G7bUqn0xiPx7ZpqYRf29v4cXyMxf4+tLU1V24ZDAbgbptSqRSGw6HL9OwM37n4bXPTvP6bC7lcDtw9oWQyiX6/b/vMH1XZ2MAoEMDXqytM+QBnLtRqNXAvxjNRPB5Hr9ez9Q8PMfD50OM/2P3FBb7wAc680+mIMri3yzuRJEloNpsmORTCJy7INzd/9stLc7dyIZPJgDvnPJNA0WgU1WrVkxJ/4FgsBu6s804CRSKRh0KhgHK5/F+JRAL8fJBnslA4HH7NHhg8CDnLwm8IYz560xw92AAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="https://suggest.yandex.net/suggest-ff.cgi">
+ <Param name="part" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="https://yandex.ru/yandsearch" resultdomain="yandex.ru">
+ <MozParam name="clid" condition="purpose" purpose="searchbar" value="2186618"/>
+ <MozParam name="clid" condition="purpose" purpose="keyword" value="2186621"/>
+ <MozParam name="clid" condition="purpose" purpose="contextmenu" value="2186623"/>
+ <MozParam name="clid" condition="purpose" purpose="homepage" value="2186617"/>
+ <MozParam name="clid" condition="purpose" purpose="newtab" value="2186620"/>
+ <Param name="text" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.yandex.ru/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yandex-tr.xml b/browser/locales/searchplugins/yandex-tr.xml
new file mode 100644
index 000000000..e5f56167a
--- /dev/null
+++ b/browser/locales/searchplugins/yandex-tr.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yandex</ShortName>
+<Description>Yandex Türkiye arama motoru</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAAAAAAsAAAAUwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAAF8AAABfAAAAXwAAADjBwcEAwcHBAMHBwQDBwcEAAAAAUbq6uurExMT/xMTE/8TExP/ExMT/xMTE/8TExP/ExMT/xMTE/8TExP9ubm6QAAAALcTExADExMQAxMTEAAAAAFrHx8f/x8fH/8fHx/+7u8j/AADS/wAA0v+wsMj/x8fH/8fHx//Hx8f/vr6+7h4eHmQAAAAQx8fHAMfHxwAAAABXy8vL/8vLy//Ly8v/v7/M/wAA2/8AANv/s7PN/8vLy//Ly8v/y8vL/8vLy/+kpKS6AAAAM8vLywDLy8sAAAAAU9DQ0P/Q0ND/0NDQ/8TE0f8AAOX/AADl/7i40v/Q0ND/0NDQ/9DQ0P/Q0ND/0NDQ/2VlZXsAAAAk0NDQAAAAAE/U1NT/1NTU/9TU1P/IyNb/AADw/wAA8P+7u9f/1NTU/9TU1P/U1NT/1NTU/9TU1P/JycnkDw8PUwAAAAcAAABL2dnZ/9nZ2f/Z2dn/np7i/wAA+f8AAPn/goLm/9nZ2f/Z2dn/2dnZ/9nZ2f/Z2dn/2dnZ/6ioqKYAAAAqAAAASOLi4v/i4uL/4uLi/0hI9v8AAP//BQX+/yAg+//f3+L/4uLi/+Li4v/i4uL/4uLi/+Li4v/g4OD7AAAARgAAAETx8fH/8fHx/9jZ8v8LD///HiL+/3h6+P8IDP//lJX3//Hx8f/x8fH/8fHx//Hx8f/x8fH/7+/v+wAAAEIAAABA9vb2//b29v+Ah/v/Ex///3N6+//Z2vf/FiL//ys2/v/r7Pb/9vb2//b29v/29vb/9vb2/8jIyKMAAAAkAAAAPPn5+f/09Pn/NEf+/yA0///Gy/r/+fn5/1hn/f8gNP//jpj8//n5+f/5+fn/+fn5//Dw8OMXFxdBAAAABgAAADj7+/v/tb/8/y5K//9GX///+/v7//v7+/+zvfz/Lkr//zhS///l6Pv/+/v7//v7+/+Tk5NnAAAAGfv7+wAAAAA1/f39/3OM/v85Xf//mqz+//39/f/9/f3/9vf9/0do//85Xf//fpX+//39/f/c3NysAAAAH/39/QD9/f0AAAAAMv7+/v/+/v7//v7+//7+/v/+/v7//v7+//7+/v+VrP//QWn//0Fp///T3PrtPj4+PgAAAAn+/v4A/v7+AAAAACn39/fi////////////////////////////////////////////////tbW1cgAAABf///8A////AP///wAAAAAVAAAAKAAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAABv///8A////AP///wD///8AAA8AAAAHAAAAAwAAAAMAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAADAAAABwAAAA8AAA==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://suggest.yandex.com.tr/suggest-ff.cgi">
+ <Param name="part" value="{searchTerms}"/>
+ <Param name="uil" value="tr"/>
+</Url>
+<Url type="text/html" rel="searchform" method="GET" template="http://yandex.com.tr/yandsearch" resultdomain="yandex.com.tr">
+ <MozParam name="clid" condition="purpose" purpose="searchbar" value="2186618"/>
+ <MozParam name="clid" condition="purpose" purpose="keyword" value="2186621"/>
+ <MozParam name="clid" condition="purpose" purpose="contextmenu" value="2186623"/>
+ <MozParam name="clid" condition="purpose" purpose="homepage" value="2186617"/>
+ <MozParam name="clid" condition="purpose" purpose="newtab" value="2186620"/>
+ <Param name="text" value="{searchTerms}"/>
+</Url>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/yandex-uk.xml b/browser/locales/searchplugins/yandex-uk.xml
new file mode 100644
index 000000000..b403a1d8f
--- /dev/null
+++ b/browser/locales/searchplugins/yandex-uk.xml
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Яндекс</ShortName>
+<Description>Пошук Яндексом.</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAB50lEQVQ4T43SvWsacRgH8OcPCITSLRQ6BSRL1gxZTNZQsgYC3QKunVoMnTKFhBQSnDqIUa5gxNbBCgriC4pv50sJ1lIR5SROpRVbz+O4b5/flbvedekJH+557nvfH8chdbvdIFMYPAgBICdqt9uKpmnw8huNRuDnX8J5QKvVgmEYnqiqClmWwZ113kmger0OXdf/Wv6EIb0BTo+AgB94ceDKJ5MJuHPOMwlUqVSwWq1sevQaxqvn0O5l6HdvgaMdV75cLtFoNMC9Xd6JisWiedNiPNuB9l4yZ+1jEvBvuXJBURRwL8YzUT6fx2KxsGHrKdQPCXNW794Bvieu3CLegrsnlM1mMZ/PbfqeH6vToDkvb2+Bx49cuWU2m4G7bUqn0xiPx7ZpqYRf29v4cXyMxf4+tLU1V24ZDAbgbptSqRSGw6HL9OwM37n4bXPTvP6bC7lcDtw9oWQyiX6/b/vMH1XZ2MAoEMDXqytM+QBnLtRqNXAvxjNRPB5Hr9ez9Q8PMfD50OM/2P3FBb7wAc680+mIMri3yzuRJEloNpsmORTCJy7INzd/9stLc7dyIZPJgDvnPJNA0WgU1WrVkxJ/4FgsBu6s804CRSKRh0KhgHK5/F+JRAL8fJBnslA4HH7NHhg8CDnLwm8IYz560xw92AAAAABJRU5ErkJggg==</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://suggest.yandex.net/suggest-ff.cgi?part={searchTerms}"/>
+<Url type="text/html" method="GET" template="http://www.yandex.ua/yandsearch" resultdomain="yandex.ua">
+ <Param name="text" value="{searchTerms}"/>
+ <Param name="from" value="fx3"/>
+</Url>
+<SearchForm>http://www.yandex.ua/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/zing-mp3.xml b/browser/locales/searchplugins/zing-mp3.xml
new file mode 100644
index 000000000..b669e4675
--- /dev/null
+++ b/browser/locales/searchplugins/zing-mp3.xml
@@ -0,0 +1,17 @@
+<?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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Zing MP3</ShortName>
+<Description>Zing MP3 - Tìm nhạc</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAA//auAP//1wDspn8A/+aAAMeLYQDYxLAAx5N0ANGANQD/wTIA7+ncAP/GdwD07+QA/9eSAOuqhQD/8b0A1MO8AP/AXwD/1IIAvJeDAMKhkADNrJoAwIlgAP/bPQD/0oAA3cm0AN/JtADroTwAsnZdALB1YADbx7IAxYBZAOabVgD/8ccA/8hjAP//jwD/z3kAvY59AMqVcQD7iDAA/850AODFsAD/w1kAvINzAOesegD/+80AxIliANK4pADFkXUA/8hDAP//2wDEimgAxYJmAP/PSQD/0nsA7rJ7AP//uwDgzLcA/80uAP//7QDRv6sA2pNoAP/FXgD/0jEA/+dqAOeUZQDbv6sA//RvANW7pgC9kYAA//xhAP+4QQDp070AuZaDAP/suADCclMAypqQAP/jPAD//+YAxollANh0PAD/xlQA0YhXAP/UiAD/ryEAx4BNAPOqagD/8skA9J1dAMaKYwDQiGAAuIx3AP/YjgD3uH0AyrCeAM63owAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQTSS8AAAAAAAAAAAAAAB03IVddHAAAAAAAAAAAAEUoODICASQlAAAAAAAAAAA0KlNKDw0iHwAAAAAAAAAAQic2TjtcMS4AAAAAAAAAAAApWgMOUj4zAAAAAAAAAAAAAAAAAEgRTwAAW14AAAAAAAAAAAA5UVkAACwwAAAAAAAAAAAAGTUFAB4SJgAAAAAAAAAAAAYXFjxVIwcAAAAAAAAAAABfPwgbRkAVAAAAAAAAAAAAFAk6TUMgAAAAAAAAAAAAACtHVARWGgAAAAAAAAAAAABLCxg9CgAAAAAAAAAAAAAAUC1BDAAAAAAAAAAAAAAAAExYEAAAAAAAAPD/AADgfwAAwD8AAMA/AADAPwAA4D8AAP4zAAD+MwAA/iMAAP4DAAD+AwAA/gcAAP4HAAD+DwAA/h8AAP4/AAA=</Image>
+<Url type="text/html" method="GET" template="http://mp3.zing.vn/mp3/search/do.html" resultdomain="zing.vn">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="t" value="0"/>
+ <Param name="utm_source" value="firefox"/>
+</Url>
+<SearchForm>http://mp3.zing.vn/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/searchplugins/zoznam-sk.xml b/browser/locales/searchplugins/zoznam-sk.xml
new file mode 100644
index 000000000..e2d5501e8
--- /dev/null
+++ b/browser/locales/searchplugins/zoznam-sk.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/" xmlns:os="http://a9.com/-/spec/opensearch/1.1/">
+<ShortName>Zoznam</ShortName>
+<Description>Zoznam slovenskeho internetu</Description>
+<InputEncoding>WINDOWS-1250</InputEncoding>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAApUlEQVQ4jWNggIJ/DAz//zEw/D+tKv3/dqT3fwYiASNMMy4FZ1WlGRg4OBgEdDQYVJdvZcRpEswFhPBmTu7/p1Wl4RYyohtCrNMZGBgYmBgYGHE7CYsL0TVjuIBUzUQZgE8zQQMIacZrADGacRpArGasBpCiGcMAUjWjGECOZqg67ABXUl5ta/x/d2o0aoo9bKqNO+3rGv+/7Gr8/3BmNknJnGgAAJxPXsbphIDfAAAAAElFTkSuQmCC</Image>
+<Url type="text/html" method="GET" template="http://www.zoznam.sk/hladaj.fcgi" resultdomain="zoznam.sk">
+ <Param name="co" value="odkazy"/>
+ <Param name="s" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://www.zoznam.sk/</SearchForm>
+</SearchPlugin>
diff --git a/browser/locales/shipped-locales b/browser/locales/shipped-locales
new file mode 100644
index 000000000..6dc8e0dc8
--- /dev/null
+++ b/browser/locales/shipped-locales
@@ -0,0 +1,93 @@
+ach
+af
+an
+ar
+as
+ast
+az
+bg
+bn-BD
+bn-IN
+br
+bs
+ca
+cak
+cs
+cy
+da
+de
+dsb
+el
+en-GB
+en-US
+en-ZA
+eo
+es-AR
+es-CL
+es-ES
+es-MX
+et
+eu
+fa
+ff
+fi
+fr
+fy-NL
+ga-IE
+gd
+gl
+gn
+gu-IN
+he
+hi-IN
+hr
+hsb
+hu
+hy-AM
+id
+is
+it
+ja linux win32
+ja-JP-mac osx
+ka
+kab
+kk
+km
+kn
+ko
+lij
+lt
+lv
+mai
+mk
+ml
+mr
+ms
+nb-NO
+nl
+nn-NO
+or
+pa-IN
+pl
+pt-BR
+pt-PT
+rm
+ro
+ru
+si
+sk
+sl
+son
+sq
+sr
+sv-SE
+ta
+te
+th
+tr
+uk
+uz
+vi
+xh
+zh-CN
+zh-TW
diff --git a/browser/modules/AboutHome.jsm b/browser/modules/AboutHome.jsm
new file mode 100644
index 000000000..01cbafba9
--- /dev/null
+++ b/browser/modules/AboutHome.jsm
@@ -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";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "AboutHomeUtils", "AboutHome" ];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
+ "resource:///modules/AutoMigrate.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
+ "resource://gre/modules/FxAccounts.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+// Url to fetch snippets, in the urlFormatter service format.
+const SNIPPETS_URL_PREF = "browser.aboutHomeSnippets.updateUrl";
+
+// Should be bumped up if the snippets content format changes.
+const STARTPAGE_VERSION = 4;
+
+this.AboutHomeUtils = {
+ get snippetsVersion() {
+ return STARTPAGE_VERSION;
+ },
+
+ /*
+ * showKnowYourRights - Determines if the user should be shown the
+ * about:rights notification. The notification should *not* be shown if
+ * we've already shown the current version, or if the override pref says to
+ * never show it. The notification *should* be shown if it's never been seen
+ * before, if a newer version is available, or if the override pref says to
+ * always show it.
+ */
+ get showKnowYourRights() {
+ // Look for an unconditional override pref. If set, do what it says.
+ // (true --> never show, false --> always show)
+ try {
+ return !Services.prefs.getBoolPref("browser.rights.override");
+ } catch (e) { }
+ // Ditto, for the legacy EULA pref.
+ try {
+ return !Services.prefs.getBoolPref("browser.EULA.override");
+ } catch (e) { }
+
+ if (!AppConstants.MOZILLA_OFFICIAL) {
+ // Non-official builds shouldn't show the notification.
+ return false;
+ }
+
+ // Look to see if the user has seen the current version or not.
+ var currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ try {
+ return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown");
+ } catch (e) { }
+
+ // Legacy: If the user accepted a EULA, we won't annoy them with the
+ // equivalent about:rights page until the version changes.
+ try {
+ return !Services.prefs.getBoolPref("browser.EULA." + currentVersion + ".accepted");
+ } catch (e) { }
+
+ // We haven't shown the notification before, so do so now.
+ return true;
+ }
+};
+
+/**
+ * Returns the URL to fetch snippets from, in the urlFormatter service format.
+ */
+XPCOMUtils.defineLazyGetter(AboutHomeUtils, "snippetsURL", function() {
+ let updateURL = Services.prefs
+ .getCharPref(SNIPPETS_URL_PREF)
+ .replace("%STARTPAGE_VERSION%", STARTPAGE_VERSION);
+ return Services.urlFormatter.formatURL(updateURL);
+});
+
+/**
+ * This code provides services to the about:home page. Whenever
+ * about:home needs to do something chrome-privileged, it sends a
+ * message that's handled here.
+ */
+var AboutHome = {
+ MESSAGES: [
+ "AboutHome:RestorePreviousSession",
+ "AboutHome:Downloads",
+ "AboutHome:Bookmarks",
+ "AboutHome:History",
+ "AboutHome:Addons",
+ "AboutHome:Sync",
+ "AboutHome:Settings",
+ "AboutHome:RequestUpdate",
+ "AboutHome:MaybeShowAutoMigrationUndoNotification",
+ ],
+
+ init: function() {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+
+ for (let msg of this.MESSAGES) {
+ mm.addMessageListener(msg, this);
+ }
+ },
+
+ receiveMessage: function(aMessage) {
+ let window = aMessage.target.ownerGlobal;
+
+ switch (aMessage.name) {
+ case "AboutHome:RestorePreviousSession":
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ if (ss.canRestoreLastSession) {
+ ss.restoreLastSession();
+ }
+ break;
+
+ case "AboutHome:Downloads":
+ window.BrowserDownloadsUI();
+ break;
+
+ case "AboutHome:Bookmarks":
+ window.PlacesCommandHook.showPlacesOrganizer("UnfiledBookmarks");
+ break;
+
+ case "AboutHome:History":
+ window.PlacesCommandHook.showPlacesOrganizer("History");
+ break;
+
+ case "AboutHome:Addons":
+ window.BrowserOpenAddonsMgr();
+ break;
+
+ case "AboutHome:Sync":
+ window.openPreferences("paneSync", { urlParams: { entrypoint: "abouthome" } });
+ break;
+
+ case "AboutHome:Settings":
+ window.openPreferences();
+ break;
+
+ case "AboutHome:RequestUpdate":
+ this.sendAboutHomeData(aMessage.target);
+ break;
+
+ case "AboutHome:MaybeShowAutoMigrationUndoNotification":
+ AutoMigrate.maybeShowUndoNotification(aMessage.target);
+ break;
+ }
+ },
+
+ // Send all the chrome-privileged data needed by about:home. This
+ // gets re-sent when the search engine changes.
+ sendAboutHomeData: function(target) {
+ let wrapper = {};
+ Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm",
+ wrapper);
+ let ss = wrapper.SessionStore;
+
+ ss.promiseInitialized.then(function() {
+ let data = {
+ showRestoreLastSession: ss.canRestoreLastSession,
+ snippetsURL: AboutHomeUtils.snippetsURL,
+ showKnowYourRights: AboutHomeUtils.showKnowYourRights,
+ snippetsVersion: AboutHomeUtils.snippetsVersion,
+ };
+
+ if (AboutHomeUtils.showKnowYourRights) {
+ // Set pref to indicate we've shown the notification.
+ let currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
+ }
+
+ if (target && target.messageManager) {
+ target.messageManager.sendAsyncMessage("AboutHome:Update", data);
+ } else {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.broadcastAsyncMessage("AboutHome:Update", data);
+ }
+ }).then(null, function onError(x) {
+ Cu.reportError("Error in AboutHome.sendAboutHomeData: " + x);
+ });
+ },
+
+};
diff --git a/browser/modules/AboutNewTab.jsm b/browser/modules/AboutNewTab.jsm
new file mode 100644
index 000000000..4337c5a2d
--- /dev/null
+++ b/browser/modules/AboutNewTab.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/. */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "AboutNewTab" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
+ "resource:///modules/AutoMigrate.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
+ "resource://gre/modules/NewTabUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
+ "resource://gre/modules/RemotePageManager.jsm");
+
+var AboutNewTab = {
+
+ pageListener: null,
+
+ init: function() {
+ this.pageListener = new RemotePages("about:newtab");
+ this.pageListener.addMessageListener("NewTab:Customize", this.customize.bind(this));
+ this.pageListener.addMessageListener("NewTab:MaybeShowAutoMigrationUndoNotification",
+ (msg) => AutoMigrate.maybeShowUndoNotification(msg.target.browser));
+ },
+
+ customize: function(message) {
+ NewTabUtils.allPages.enabled = message.data.enabled;
+ NewTabUtils.allPages.enhanced = message.data.enhanced;
+ },
+
+ uninit: function() {
+ this.pageListener.destroy();
+ this.pageListener = null;
+ },
+};
diff --git a/browser/modules/AttributionCode.jsm b/browser/modules/AttributionCode.jsm
new file mode 100644
index 000000000..dc42b2be4
--- /dev/null
+++ b/browser/modules/AttributionCode.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/. */
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["AttributionCode"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, 'AppConstants',
+ 'resource://gre/modules/AppConstants.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'OS',
+ 'resource://gre/modules/osfile.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Services',
+ 'resource://gre/modules/Services.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'Task',
+ 'resource://gre/modules/Task.jsm');
+
+const ATTR_CODE_MAX_LENGTH = 200;
+const ATTR_CODE_KEYS_REGEX = /^source|medium|campaign|content$/;
+const ATTR_CODE_VALUE_REGEX = /[a-zA-Z0-9_%\\-\\.\\(\\)]*/;
+const ATTR_CODE_FIELD_SEPARATOR = "%26"; // URL-encoded &
+const ATTR_CODE_KEY_VALUE_SEPARATOR = "%3D"; // URL-encoded =
+
+let gCachedAttrData = null;
+
+/**
+ * Returns an nsIFile for the file containing the attribution data.
+ */
+function getAttributionFile() {
+ let file = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
+ // appinfo does not exist in xpcshell, so we need defaults.
+ file.append(Services.appinfo.vendor || "mozilla");
+ file.append(AppConstants.MOZ_APP_NAME);
+ file.append("postSigningData");
+ return file;
+}
+
+/**
+ * Returns an object containing a key-value pair for each piece of attribution
+ * data included in the passed-in attribution code string.
+ * If the string isn't a valid attribution code, returns an empty object.
+ */
+function parseAttributionCode(code) {
+ if (code.length > ATTR_CODE_MAX_LENGTH) {
+ return {};
+ }
+
+ let isValid = true;
+ let parsed = {};
+ for (let param of code.split(ATTR_CODE_FIELD_SEPARATOR)) {
+ let [key, value] = param.split(ATTR_CODE_KEY_VALUE_SEPARATOR, 2);
+ if (key && ATTR_CODE_KEYS_REGEX.test(key)) {
+ if (value && ATTR_CODE_VALUE_REGEX.test(value)) {
+ parsed[key] = value;
+ }
+ } else {
+ isValid = false;
+ break;
+ }
+ }
+ return isValid ? parsed : {};
+}
+
+var AttributionCode = {
+ /**
+ * Reads the attribution code, either from disk or a cached version.
+ * Returns a promise that fulfills with an object containing the parsed
+ * attribution data if the code could be read and is valid,
+ * or an empty object otherwise.
+ */
+ getAttrDataAsync() {
+ return Task.spawn(function*() {
+ if (gCachedAttrData != null) {
+ return gCachedAttrData;
+ }
+
+ let code = "";
+ try {
+ let bytes = yield OS.File.read(getAttributionFile().path);
+ let decoder = new TextDecoder();
+ code = decoder.decode(bytes);
+ } catch (ex) {
+ // The attribution file may already have been deleted,
+ // or it may have never been installed at all;
+ // failure to open or read it isn't an error.
+ }
+
+ gCachedAttrData = parseAttributionCode(code);
+ return gCachedAttrData;
+ });
+ },
+
+ /**
+ * Deletes the attribution data file.
+ * Returns a promise that resolves when the file is deleted,
+ * or if the file couldn't be deleted (the promise is never rejected).
+ */
+ deleteFileAsync() {
+ return Task.spawn(function*() {
+ try {
+ yield OS.File.remove(getAttributionFile().path);
+ } catch (ex) {
+ // The attribution file may already have been deleted,
+ // or it may have never been installed at all;
+ // failure to delete it isn't an error.
+ }
+ });
+ },
+
+ /**
+ * Clears the cached attribution code value, if any.
+ * Does nothing if called from outside of an xpcshell test.
+ */
+ _clearCache() {
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ if (env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
+ gCachedAttrData = null;
+ }
+ },
+};
diff --git a/browser/modules/BrowserUITelemetry.jsm b/browser/modules/BrowserUITelemetry.jsm
new file mode 100644
index 000000000..392462b45
--- /dev/null
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -0,0 +1,896 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If 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 = ["BrowserUITelemetry"];
+
+const {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
+ "resource://gre/modules/UITelemetry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UITour",
+ "resource:///modules/UITour.jsm");
+XPCOMUtils.defineLazyGetter(this, "Timer", function() {
+ let timer = {};
+ Cu.import("resource://gre/modules/Timer.jsm", timer);
+ return timer;
+});
+
+const MS_SECOND = 1000;
+const MS_MINUTE = MS_SECOND * 60;
+const MS_HOUR = MS_MINUTE * 60;
+
+XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREA_PLACEMENTS", function() {
+ let result = {
+ "PanelUI-contents": [
+ "edit-controls",
+ "zoom-controls",
+ "new-window-button",
+ "privatebrowsing-button",
+ "save-page-button",
+ "print-button",
+ "history-panelmenu",
+ "fullscreen-button",
+ "find-button",
+ "preferences-button",
+ "add-ons-button",
+ "sync-button",
+ "developer-button",
+ ],
+ "nav-bar": [
+ "urlbar-container",
+ "search-container",
+ "bookmarks-menu-button",
+ "pocket-button",
+ "downloads-button",
+ "home-button",
+ "social-share-button",
+ ],
+ // It's true that toolbar-menubar is not visible
+ // on OS X, but the XUL node is definitely present
+ // in the document.
+ "toolbar-menubar": [
+ "menubar-items",
+ ],
+ "TabsToolbar": [
+ "tabbrowser-tabs",
+ "new-tab-button",
+ "alltabs-button",
+ ],
+ "PersonalToolbar": [
+ "personal-bookmarks",
+ ],
+ };
+
+ let showCharacterEncoding = Services.prefs.getComplexValue(
+ "browser.menu.showCharacterEncoding",
+ Ci.nsIPrefLocalizedString
+ ).data;
+ if (showCharacterEncoding == "true") {
+ result["PanelUI-contents"].push("characterencoding-button");
+ }
+
+ return result;
+});
+
+XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREAS", function() {
+ return Object.keys(DEFAULT_AREA_PLACEMENTS);
+});
+
+XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() {
+ let result = [
+ "open-file-button",
+ "developer-button",
+ "feed-button",
+ "email-link-button",
+ "containers-panelmenu",
+ ];
+
+ let panelPlacements = DEFAULT_AREA_PLACEMENTS["PanelUI-contents"];
+ if (panelPlacements.indexOf("characterencoding-button") == -1) {
+ result.push("characterencoding-button");
+ }
+
+ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
+ result.push("panic-button");
+ }
+
+ return result;
+});
+
+XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() {
+ let result = [];
+ for (let [, buttons] of Object.entries(DEFAULT_AREA_PLACEMENTS)) {
+ result = result.concat(buttons);
+ }
+ return result;
+});
+
+XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
+ // These special cases are for click events on built-in items that are
+ // contained within customizable items (like the navigation widget).
+ const SPECIAL_CASES = [
+ "back-button",
+ "forward-button",
+ "urlbar-stop-button",
+ "urlbar-go-button",
+ "urlbar-reload-button",
+ "searchbar",
+ "cut-button",
+ "copy-button",
+ "paste-button",
+ "zoom-out-button",
+ "zoom-reset-button",
+ "zoom-in-button",
+ "BMB_bookmarksPopup",
+ "BMB_unsortedBookmarksPopup",
+ "BMB_bookmarksToolbarPopup",
+ "search-go-button",
+ "soundplaying-icon",
+ ]
+ return DEFAULT_ITEMS.concat(PALETTE_ITEMS)
+ .concat(SPECIAL_CASES);
+});
+
+const OTHER_MOUSEUP_MONITORED_ITEMS = [
+ "PlacesChevron",
+ "PlacesToolbarItems",
+ "menubar-items",
+];
+
+// Items that open arrow panels will often be overlapped by
+// the panel that they're opening by the time the mouseup
+// event is fired, so for these items, we monitor mousedown.
+const MOUSEDOWN_MONITORED_ITEMS = [
+ "PanelUI-menu-button",
+];
+
+// Weakly maps browser windows to objects whose keys are relative
+// timestamps for when some kind of session started. For example,
+// when a customization session started. That way, when the window
+// exits customization mode, we can determine how long the session
+// lasted.
+const WINDOW_DURATION_MAP = new WeakMap();
+
+// Default bucket name, when no other bucket is active.
+const BUCKET_DEFAULT = "__DEFAULT__";
+// Bucket prefix, for named buckets.
+const BUCKET_PREFIX = "bucket_";
+// Standard separator to use between different parts of a bucket name, such
+// as primary name and the time step string.
+const BUCKET_SEPARATOR = "|";
+
+this.BrowserUITelemetry = {
+ init: function() {
+ UITelemetry.addSimpleMeasureFunction("toolbars",
+ this.getToolbarMeasures.bind(this));
+ UITelemetry.addSimpleMeasureFunction("contextmenu",
+ this.getContextMenuInfo.bind(this));
+ // Ensure that UITour.jsm remains lazy-loaded, yet always registers its
+ // simple measure function with UITelemetry.
+ UITelemetry.addSimpleMeasureFunction("UITour",
+ () => UITour.getTelemetry());
+
+ UITelemetry.addSimpleMeasureFunction("syncstate",
+ this.getSyncState.bind(this));
+
+ Services.obs.addObserver(this, "sessionstore-windows-restored", false);
+ Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
+ Services.obs.addObserver(this, "autocomplete-did-enter-text", false);
+ CustomizableUI.addListener(this);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "sessionstore-windows-restored":
+ this._gatherFirstWindowMeasurements();
+ break;
+ case "browser-delayed-startup-finished":
+ this._registerWindow(aSubject);
+ break;
+ case "autocomplete-did-enter-text":
+ let input = aSubject.QueryInterface(Ci.nsIAutoCompleteInput);
+ if (input && input.id == "urlbar" && !input.inPrivateContext &&
+ input.popup.selectedIndex != -1) {
+ this._logAwesomeBarSearchResult(input.textValue);
+ }
+ break;
+ }
+ },
+
+ /**
+ * For the _countableEvents object, constructs a chain of
+ * Javascript Objects with the keys in aKeys, with the final
+ * key getting the value in aEndWith. If the final key already
+ * exists in the final object, its value is not set. In either
+ * case, a reference to the second last object in the chain is
+ * returned.
+ *
+ * Example - suppose I want to store:
+ * _countableEvents: {
+ * a: {
+ * b: {
+ * c: 0
+ * }
+ * }
+ * }
+ *
+ * And then increment the "c" value by 1, you could call this
+ * function like this:
+ *
+ * let example = this._ensureObjectChain([a, b, c], 0);
+ * example["c"]++;
+ *
+ * Subsequent repetitions of these last two lines would
+ * simply result in the c value being incremented again
+ * and again.
+ *
+ * @param aKeys the Array of keys to chain Objects together with.
+ * @param aEndWith the value to assign to the last key.
+ * @param aRoot the root object onto which we create/get the object chain
+ * designated by aKeys.
+ * @returns a reference to the second last object in the chain -
+ * so in our example, that'd be "b".
+ */
+ _ensureObjectChain: function(aKeys, aEndWith, aRoot) {
+ let current = aRoot;
+ let parent = null;
+ aKeys.unshift(this._bucket);
+ for (let [i, key] of aKeys.entries()) {
+ if (!(key in current)) {
+ if (i == aKeys.length - 1) {
+ current[key] = aEndWith;
+ } else {
+ current[key] = {};
+ }
+ }
+ parent = current;
+ current = current[key];
+ }
+ return parent;
+ },
+
+ _countableEvents: {},
+ _countEvent: function(aKeyArray, root=this._countableEvents) {
+ let countObject = this._ensureObjectChain(aKeyArray, 0, root);
+ let lastItemKey = aKeyArray[aKeyArray.length - 1];
+ countObject[lastItemKey]++;
+ },
+
+ _countMouseUpEvent: function(aCategory, aAction, aButton) {
+ const BUTTONS = ["left", "middle", "right"];
+ let buttonKey = BUTTONS[aButton];
+ if (buttonKey) {
+ this._countEvent([aCategory, aAction, buttonKey]);
+ }
+ },
+
+ _firstWindowMeasurements: null,
+ _gatherFirstWindowMeasurements: function() {
+ // We'll gather measurements as soon as the session has restored.
+ // We do this here instead of waiting for UITelemetry to ask for
+ // our measurements because at that point all browser windows have
+ // probably been closed, since the vast majority of saved-session
+ // pings are gathered during shutdown.
+ let win = RecentWindow.getMostRecentBrowserWindow({
+ private: false,
+ allowPopups: false,
+ });
+
+ Services.search.init(rv => {
+ // If there are no such windows (or we've just about found one
+ // but it's closed already), we're out of luck. :(
+ let hasWindow = win && !win.closed;
+ this._firstWindowMeasurements = hasWindow ? this._getWindowMeasurements(win, rv)
+ : {};
+ });
+ },
+
+ _registerWindow: function(aWindow) {
+ aWindow.addEventListener("unload", this);
+ let document = aWindow.document;
+
+ for (let areaID of CustomizableUI.areas) {
+ let areaNode = document.getElementById(areaID);
+ if (areaNode) {
+ (areaNode.customizationTarget || areaNode).addEventListener("mouseup", this);
+ }
+ }
+
+ for (let itemID of OTHER_MOUSEUP_MONITORED_ITEMS) {
+ let item = document.getElementById(itemID);
+ if (item) {
+ item.addEventListener("mouseup", this);
+ }
+ }
+
+ for (let itemID of MOUSEDOWN_MONITORED_ITEMS) {
+ let item = document.getElementById(itemID);
+ if (item) {
+ item.addEventListener("mousedown", this);
+ }
+ }
+
+ WINDOW_DURATION_MAP.set(aWindow, {});
+ },
+
+ _unregisterWindow: function(aWindow) {
+ aWindow.removeEventListener("unload", this);
+ let document = aWindow.document;
+
+ for (let areaID of CustomizableUI.areas) {
+ let areaNode = document.getElementById(areaID);
+ if (areaNode) {
+ (areaNode.customizationTarget || areaNode).removeEventListener("mouseup", this);
+ }
+ }
+
+ for (let itemID of OTHER_MOUSEUP_MONITORED_ITEMS) {
+ let item = document.getElementById(itemID);
+ if (item) {
+ item.removeEventListener("mouseup", this);
+ }
+ }
+
+ for (let itemID of MOUSEDOWN_MONITORED_ITEMS) {
+ let item = document.getElementById(itemID);
+ if (item) {
+ item.removeEventListener("mousedown", this);
+ }
+ }
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ this._unregisterWindow(aEvent.currentTarget);
+ break;
+ case "mouseup":
+ this._handleMouseUp(aEvent);
+ break;
+ case "mousedown":
+ this._handleMouseDown(aEvent);
+ break;
+ }
+ },
+
+ _handleMouseUp: function(aEvent) {
+ let targetID = aEvent.currentTarget.id;
+
+ switch (targetID) {
+ case "PlacesToolbarItems":
+ this._PlacesToolbarItemsMouseUp(aEvent);
+ break;
+ case "PlacesChevron":
+ this._PlacesChevronMouseUp(aEvent);
+ break;
+ case "menubar-items":
+ this._menubarMouseUp(aEvent);
+ break;
+ default:
+ this._checkForBuiltinItem(aEvent);
+ }
+ },
+
+ _handleMouseDown: function(aEvent) {
+ if (aEvent.currentTarget.id == "PanelUI-menu-button") {
+ // _countMouseUpEvent expects a detail for the second argument,
+ // but we don't really have any details to give. Just passing in
+ // "button" is probably simpler than trying to modify
+ // _countMouseUpEvent for this particular case.
+ this._countMouseUpEvent("click-menu-button", "button", aEvent.button);
+ }
+ },
+
+ _PlacesChevronMouseUp: function(aEvent) {
+ let target = aEvent.originalTarget;
+ let result = target.id == "PlacesChevron" ? "chevron" : "overflowed-item";
+ this._countMouseUpEvent("click-bookmarks-bar", result, aEvent.button);
+ },
+
+ _PlacesToolbarItemsMouseUp: function(aEvent) {
+ let target = aEvent.originalTarget;
+ // If this isn't a bookmark-item, we don't care about it.
+ if (!target.classList.contains("bookmark-item")) {
+ return;
+ }
+
+ let result = target.hasAttribute("container") ? "container" : "item";
+ this._countMouseUpEvent("click-bookmarks-bar", result, aEvent.button);
+ },
+
+ _menubarMouseUp: function(aEvent) {
+ let target = aEvent.originalTarget;
+ let tag = target.localName
+ let result = (tag == "menu" || tag == "menuitem") ? tag : "other";
+ this._countMouseUpEvent("click-menubar", result, aEvent.button);
+ },
+
+ _bookmarksMenuButtonMouseUp: function(aEvent) {
+ let bookmarksWidget = CustomizableUI.getWidget("bookmarks-menu-button");
+ if (bookmarksWidget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
+ // In the menu panel, only the star is visible, and that opens up the
+ // bookmarks subview.
+ this._countMouseUpEvent("click-bookmarks-menu-button", "in-panel",
+ aEvent.button);
+ } else {
+ let clickedItem = aEvent.originalTarget;
+ // Did we click on the star, or the dropmarker? The star
+ // has an anonid of "button". If we don't find that, we'll
+ // assume we clicked on the dropmarker.
+ let action = "menu";
+ if (clickedItem.getAttribute("anonid") == "button") {
+ // We clicked on the star - now we just need to record
+ // whether or not we're adding a bookmark or editing an
+ // existing one.
+ let bookmarksMenuNode =
+ bookmarksWidget.forWindow(aEvent.target.ownerGlobal).node;
+ action = bookmarksMenuNode.hasAttribute("starred") ? "edit" : "add";
+ }
+ this._countMouseUpEvent("click-bookmarks-menu-button", action,
+ aEvent.button);
+ }
+ },
+
+ _checkForBuiltinItem: function(aEvent) {
+ let item = aEvent.originalTarget;
+
+ // We don't want to count clicks on the private browsing
+ // button for privacy reasons. See bug 1176391.
+ if (item.id == "privatebrowsing-button") {
+ return;
+ }
+
+ // We special-case the bookmarks-menu-button, since we want to
+ // monitor more than just clicks on it.
+ if (item.id == "bookmarks-menu-button" ||
+ getIDBasedOnFirstIDedAncestor(item) == "bookmarks-menu-button") {
+ this._bookmarksMenuButtonMouseUp(aEvent);
+ return;
+ }
+
+ // Perhaps we're seeing one of the default toolbar items
+ // being clicked.
+ if (ALL_BUILTIN_ITEMS.indexOf(item.id) != -1) {
+ // Base case - we clicked directly on one of our built-in items,
+ // and we can go ahead and register that click.
+ this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button);
+ return;
+ }
+
+ // If not, we need to check if the item's anonid is in our list
+ // of built-in items to check.
+ if (ALL_BUILTIN_ITEMS.indexOf(item.getAttribute("anonid")) != -1) {
+ this._countMouseUpEvent("click-builtin-item", item.getAttribute("anonid"), aEvent.button);
+ return;
+ }
+
+ // If not, we need to check if one of the ancestors of the clicked
+ // item is in our list of built-in items to check.
+ let candidate = getIDBasedOnFirstIDedAncestor(item);
+ if (ALL_BUILTIN_ITEMS.indexOf(candidate) != -1) {
+ this._countMouseUpEvent("click-builtin-item", candidate, aEvent.button);
+ }
+ },
+
+ _getWindowMeasurements: function(aWindow, searchResult) {
+ let document = aWindow.document;
+ let result = {};
+
+ // Determine if the window is in the maximized, normal or
+ // fullscreen state.
+ result.sizemode = document.documentElement.getAttribute("sizemode");
+
+ // Determine if the Bookmarks bar is currently visible
+ let bookmarksBar = document.getElementById("PersonalToolbar");
+ result.bookmarksBarEnabled = bookmarksBar && !bookmarksBar.collapsed;
+
+ // Determine if the menubar is currently visible. On OS X, the menubar
+ // is never shown, despite not having the collapsed attribute set.
+ let menuBar = document.getElementById("toolbar-menubar");
+ result.menuBarEnabled =
+ menuBar && Services.appinfo.OS != "Darwin"
+ && menuBar.getAttribute("autohide") != "true";
+
+ // Determine if the titlebar is currently visible.
+ result.titleBarEnabled = !Services.prefs.getBoolPref("browser.tabs.drawInTitlebar");
+
+ // Examine all customizable areas and see what default items
+ // are present and missing.
+ let defaultKept = [];
+ let defaultMoved = [];
+ let nondefaultAdded = [];
+
+ for (let areaID of CustomizableUI.areas) {
+ let items = CustomizableUI.getWidgetIdsInArea(areaID);
+ for (let item of items) {
+ // Is this a default item?
+ if (DEFAULT_ITEMS.indexOf(item) != -1) {
+ // Ok, it's a default item - but is it in its default
+ // toolbar? We use Array.isArray instead of checking for
+ // toolbarID in DEFAULT_AREA_PLACEMENTS because an add-on might
+ // be clever and give itself the id of "toString" or something.
+ if (Array.isArray(DEFAULT_AREA_PLACEMENTS[areaID]) &&
+ DEFAULT_AREA_PLACEMENTS[areaID].indexOf(item) != -1) {
+ // The item is in its default toolbar
+ defaultKept.push(item);
+ } else {
+ defaultMoved.push(item);
+ }
+ } else if (PALETTE_ITEMS.indexOf(item) != -1) {
+ // It's a palette item that's been moved into a toolbar
+ nondefaultAdded.push(item);
+ }
+ // else, it's provided by an add-on, and we won't record it.
+ }
+ }
+
+ // Now go through the items in the palette to see what default
+ // items are in there.
+ let paletteItems =
+ CustomizableUI.getUnusedWidgets(aWindow.gNavToolbox.palette);
+ let defaultRemoved = [];
+ for (let item of paletteItems) {
+ if (DEFAULT_ITEMS.indexOf(item.id) != -1) {
+ defaultRemoved.push(item.id);
+ }
+ }
+
+ result.defaultKept = defaultKept;
+ result.defaultMoved = defaultMoved;
+ result.nondefaultAdded = nondefaultAdded;
+ result.defaultRemoved = defaultRemoved;
+
+ // Next, determine how many add-on provided toolbars exist.
+ let addonToolbars = 0;
+ let toolbars = document.querySelectorAll("toolbar[customizable=true]");
+ for (let toolbar of toolbars) {
+ if (DEFAULT_AREAS.indexOf(toolbar.id) == -1) {
+ addonToolbars++;
+ }
+ }
+ result.addonToolbars = addonToolbars;
+
+ // Find out how many open tabs we have in each window
+ let winEnumerator = Services.wm.getEnumerator("navigator:browser");
+ let visibleTabs = [];
+ let hiddenTabs = [];
+ while (winEnumerator.hasMoreElements()) {
+ let someWin = winEnumerator.getNext();
+ if (someWin.gBrowser) {
+ let visibleTabsNum = someWin.gBrowser.visibleTabs.length;
+ visibleTabs.push(visibleTabsNum);
+ hiddenTabs.push(someWin.gBrowser.tabs.length - visibleTabsNum);
+ }
+ }
+ result.visibleTabs = visibleTabs;
+ result.hiddenTabs = hiddenTabs;
+
+ if (Components.isSuccessCode(searchResult)) {
+ result.currentSearchEngine = Services.search.currentEngine.name;
+ }
+
+ return result;
+ },
+
+ getToolbarMeasures: function() {
+ let result = this._firstWindowMeasurements || {};
+ result.countableEvents = this._countableEvents;
+ result.durations = this._durations;
+ return result;
+ },
+
+ getSyncState: function() {
+ let result = {};
+ for (let sub of ["desktop", "mobile"]) {
+ let count = 0;
+ try {
+ count = Services.prefs.getIntPref("services.sync.clients.devices." + sub);
+ } catch (ex) {}
+ result[sub] = count;
+ }
+ return result;
+ },
+
+ countCustomizationEvent: function(aEventType) {
+ this._countEvent(["customize", aEventType]);
+ },
+
+ countSearchEvent: function(source, query, selection) {
+ this._countEvent(["search", source]);
+ if ((/^[a-zA-Z]+:[^\/\\]/).test(query)) {
+ this._countEvent(["search", "urlbar-keyword"]);
+ }
+ if (selection) {
+ this._countEvent(["search", "selection", source, selection.index, selection.kind]);
+ }
+ },
+
+ countOneoffSearchEvent: function(id, type, where) {
+ this._countEvent(["search-oneoff", id, type, where]);
+ },
+
+ countSearchSettingsEvent: function(source) {
+ this._countEvent(["click-builtin-item", source, "search-settings"]);
+ },
+
+ countPanicEvent: function(timeId) {
+ this._countEvent(["forget-button", timeId]);
+ },
+
+ countTabMutingEvent: function(action, reason) {
+ this._countEvent(["tab-audio-control", action, reason || "no reason given"]);
+ },
+
+ countSyncedTabEvent: function(what, where) {
+ // "what" will be, eg, "open"
+ // "where" will be "toolbarbutton-subview" or "sidebar"
+ this._countEvent(["synced-tabs", what, where]);
+ },
+
+ countSidebarEvent: function(sidebarID, action) {
+ // sidebarID is the ID of the sidebar (duh!)
+ // action will be "hide" or "show"
+ this._countEvent(["sidebar", sidebarID, action]);
+ },
+
+ _logAwesomeBarSearchResult: function (url) {
+ let spec = Services.search.parseSubmissionURL(url);
+ if (spec.engine) {
+ let matchedEngine = "default";
+ if (spec.engine.name !== Services.search.currentEngine.name) {
+ matchedEngine = "other";
+ }
+ this.countSearchEvent("autocomplete-" + matchedEngine);
+ }
+ },
+
+ _durations: {
+ customization: [],
+ },
+
+ onCustomizeStart: function(aWindow) {
+ this._countEvent(["customize", "start"]);
+ let durationMap = WINDOW_DURATION_MAP.get(aWindow);
+ if (!durationMap) {
+ durationMap = {};
+ WINDOW_DURATION_MAP.set(aWindow, durationMap);
+ }
+
+ durationMap.customization = {
+ start: aWindow.performance.now(),
+ bucket: this._bucket,
+ };
+ },
+
+ onCustomizeEnd: function(aWindow) {
+ let durationMap = WINDOW_DURATION_MAP.get(aWindow);
+ if (durationMap && "customization" in durationMap) {
+ let duration = aWindow.performance.now() - durationMap.customization.start;
+ this._durations.customization.push({
+ duration: duration,
+ bucket: durationMap.customization.bucket,
+ });
+ delete durationMap.customization;
+ }
+ },
+
+ _contextMenuItemWhitelist: new Set([
+ "close-without-interaction", // for closing the menu without clicking it.
+ "custom-page-item", // The ID we use for page-provided items
+ "unknown", // The bucket for stuff with no id.
+ // Everything we know of so far (which will exclude add-on items):
+ "navigation", "back", "forward", "reload", "stop", "bookmarkpage",
+ "spell-no-suggestions", "spell-add-to-dictionary",
+ "spell-undo-add-to-dictionary", "openlinkincurrent", "openlinkintab",
+ "openlink",
+ // "openlinkprivate" intentionally omitted for privacy reasons. See bug 1176391.
+ "bookmarklink", "sharelink", "savelink",
+ "marklinkMenu", "copyemail", "copylink", "media-play", "media-pause",
+ "media-mute", "media-unmute", "media-playbackrate",
+ "media-playbackrate-050x", "media-playbackrate-100x",
+ "media-playbackrate-125x", "media-playbackrate-150x", "media-playbackrate-200x",
+ "media-showcontrols", "media-hidecontrols",
+ "video-fullscreen", "leave-dom-fullscreen",
+ "reloadimage", "viewimage", "viewvideo", "copyimage-contents", "copyimage",
+ "copyvideourl", "copyaudiourl", "saveimage", "shareimage", "sendimage",
+ "setDesktopBackground", "viewimageinfo", "viewimagedesc", "savevideo",
+ "sharevideo", "saveaudio", "video-saveimage", "sendvideo", "sendaudio",
+ "ctp-play", "ctp-hide", "sharepage", "savepage", "pocket", "markpageMenu",
+ "viewbgimage", "undo", "cut", "copy", "paste", "delete", "selectall",
+ "keywordfield", "searchselect", "shareselect", "frame", "showonlythisframe",
+ "openframeintab", "openframe", "reloadframe", "bookmarkframe", "saveframe",
+ "printframe", "viewframesource", "viewframeinfo",
+ "viewpartialsource-selection", "viewpartialsource-mathml",
+ "viewsource", "viewinfo", "spell-check-enabled",
+ "spell-add-dictionaries-main", "spell-dictionaries",
+ "spell-dictionaries-menu", "spell-add-dictionaries",
+ "bidi-text-direction-toggle", "bidi-page-direction-toggle", "inspect",
+ "media-eme-learn-more"
+ ]),
+
+ _contextMenuInteractions: {},
+
+ registerContextMenuInteraction: function(keys, itemID) {
+ if (itemID) {
+ if (itemID == "openlinkprivate") {
+ // Don't record anything, not even an other-item count
+ // if the user chose to open in a private window. See
+ // bug 1176391.
+ return;
+ }
+
+ if (!this._contextMenuItemWhitelist.has(itemID)) {
+ itemID = "other-item";
+ }
+ keys.push(itemID);
+ }
+
+ this._countEvent(keys, this._contextMenuInteractions);
+ },
+
+ getContextMenuInfo: function() {
+ return this._contextMenuInteractions;
+ },
+
+ _bucket: BUCKET_DEFAULT,
+ _bucketTimer: null,
+
+ /**
+ * Default bucket name, when no other bucket is active.
+ */
+ get BUCKET_DEFAULT() {
+ return BUCKET_DEFAULT;
+ },
+
+ /**
+ * Bucket prefix, for named buckets.
+ */
+ get BUCKET_PREFIX() {
+ return BUCKET_PREFIX;
+ },
+
+ /**
+ * Standard separator to use between different parts of a bucket name, such
+ * as primary name and the time step string.
+ */
+ get BUCKET_SEPARATOR() {
+ return BUCKET_SEPARATOR;
+ },
+
+ get currentBucket() {
+ return this._bucket;
+ },
+
+ /**
+ * Sets a named bucket for all countable events and select durections to be
+ * put into.
+ *
+ * @param aName Name of bucket, or null for default bucket name (__DEFAULT__)
+ */
+ setBucket: function(aName) {
+ if (this._bucketTimer) {
+ Timer.clearTimeout(this._bucketTimer);
+ this._bucketTimer = null;
+ }
+
+ if (aName)
+ this._bucket = BUCKET_PREFIX + aName;
+ else
+ this._bucket = BUCKET_DEFAULT;
+ },
+
+ /**
+ * Sets a bucket that expires at the rate of a given series of time steps.
+ * Once the bucket expires, the current bucket will automatically revert to
+ * the default bucket. While the bucket is expiring, it's name is postfixed
+ * by '|' followed by a short string representation of the time step it's
+ * currently in.
+ * If any other bucket (expiring or normal) is set while an expiring bucket is
+ * still expiring, the old expiring bucket stops expiring and the new bucket
+ * immediately takes over.
+ *
+ * @param aName Name of bucket.
+ * @param aTimeSteps An array of times in milliseconds to count up to before
+ * reverting back to the default bucket. The array of times
+ * is expected to be pre-sorted in ascending order.
+ * For example, given a bucket name of 'bucket', the times:
+ * [60000, 300000, 600000]
+ * will result in the following buckets:
+ * * bucket|1m - for the first 1 minute
+ * * bucket|5m - for the following 4 minutes
+ * (until 5 minutes after the start)
+ * * bucket|10m - for the following 5 minutes
+ * (until 10 minutes after the start)
+ * * __DEFAULT__ - until a new bucket is set
+ * @param aTimeOffset Time offset, in milliseconds, from which to start
+ * counting. For example, if the first time step is 1000ms,
+ * and the time offset is 300ms, then the next time step
+ * will become active after 700ms. This affects all
+ * following time steps also, meaning they will also all be
+ * timed as though they started expiring 300ms before
+ * setExpiringBucket was called.
+ */
+ setExpiringBucket: function(aName, aTimeSteps, aTimeOffset = 0) {
+ if (aTimeSteps.length === 0) {
+ this.setBucket(null);
+ return;
+ }
+
+ if (this._bucketTimer) {
+ Timer.clearTimeout(this._bucketTimer);
+ this._bucketTimer = null;
+ }
+
+ // Make a copy of the time steps array, so we can safely modify it without
+ // modifying the original array that external code has passed to us.
+ let steps = [...aTimeSteps];
+ let msec = steps.shift();
+ let postfix = this._toTimeStr(msec);
+ this.setBucket(aName + BUCKET_SEPARATOR + postfix);
+
+ this._bucketTimer = Timer.setTimeout(() => {
+ this._bucketTimer = null;
+ this.setExpiringBucket(aName, steps, aTimeOffset + msec);
+ }, msec - aTimeOffset);
+ },
+
+ /**
+ * Formats a time interval, in milliseconds, to a minimal non-localized string
+ * representation. Format is: 'h' for hours, 'm' for minutes, 's' for seconds,
+ * 'ms' for milliseconds.
+ * Examples:
+ * 65 => 65ms
+ * 1000 => 1s
+ * 60000 => 1m
+ * 61000 => 1m01s
+ *
+ * @param aTimeMS Time in milliseconds
+ *
+ * @return Minimal string representation.
+ */
+ _toTimeStr: function(aTimeMS) {
+ let timeStr = "";
+
+ function reduce(aUnitLength, aSymbol) {
+ if (aTimeMS >= aUnitLength) {
+ let units = Math.floor(aTimeMS / aUnitLength);
+ aTimeMS = aTimeMS - (units * aUnitLength)
+ timeStr += units + aSymbol;
+ }
+ }
+
+ reduce(MS_HOUR, "h");
+ reduce(MS_MINUTE, "m");
+ reduce(MS_SECOND, "s");
+ reduce(1, "ms");
+
+ return timeStr;
+ },
+};
+
+/**
+ * Returns the id of the first ancestor of aNode that has an id. If aNode
+ * has no parent, or no ancestor has an id, returns null.
+ *
+ * @param aNode the node to find the first ID'd ancestor of
+ */
+function getIDBasedOnFirstIDedAncestor(aNode) {
+ while (!aNode.id) {
+ aNode = aNode.parentNode;
+ if (!aNode) {
+ return null;
+ }
+ }
+
+ return aNode.id;
+}
diff --git a/browser/modules/BrowserUsageTelemetry.jsm b/browser/modules/BrowserUsageTelemetry.jsm
new file mode 100644
index 000000000..39012d2ab
--- /dev/null
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -0,0 +1,468 @@
+/* -*- js-indent-level: 2; 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["BrowserUsageTelemetry"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+// The upper bound for the count of the visited unique domain names.
+const MAX_UNIQUE_VISITED_DOMAINS = 100;
+
+// Observed topic names.
+const WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
+const TAB_RESTORING_TOPIC = "SSTabRestoring";
+const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split";
+const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
+
+// Probe names.
+const MAX_TAB_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_tab_count";
+const MAX_WINDOW_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_window_count";
+const TAB_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.tab_open_event_count";
+const WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.window_open_event_count";
+const UNIQUE_DOMAINS_COUNT_SCALAR_NAME = "browser.engagement.unique_domains_count";
+const TOTAL_URI_COUNT_SCALAR_NAME = "browser.engagement.total_uri_count";
+const UNFILTERED_URI_COUNT_SCALAR_NAME = "browser.engagement.unfiltered_uri_count";
+
+// A list of known search origins.
+const KNOWN_SEARCH_SOURCES = [
+ "abouthome",
+ "contextmenu",
+ "newtab",
+ "searchbar",
+ "urlbar",
+];
+
+const KNOWN_ONEOFF_SOURCES = [
+ "oneoff-urlbar",
+ "oneoff-searchbar",
+ "unknown", // Edge case: this is the searchbar (see bug 1195733 comment 7).
+];
+
+function getOpenTabsAndWinsCounts() {
+ let tabCount = 0;
+ let winCount = 0;
+
+ let browserEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserEnum.hasMoreElements()) {
+ let win = browserEnum.getNext();
+ winCount++;
+ tabCount += win.gBrowser.tabs.length;
+ }
+
+ return { tabCount, winCount };
+}
+
+function getSearchEngineId(engine) {
+ if (engine) {
+ if (engine.identifier) {
+ return engine.identifier;
+ }
+ // Due to bug 1222070, we can't directly check Services.telemetry.canRecordExtended
+ // here.
+ const extendedTelemetry = Services.prefs.getBoolPref("toolkit.telemetry.enabled");
+ if (engine.name && extendedTelemetry) {
+ // If it's a custom search engine only report the engine name
+ // if extended Telemetry is enabled.
+ return "other-" + engine.name;
+ }
+ }
+ return "other";
+}
+
+let URICountListener = {
+ // A set containing the visited domains, see bug 1271310.
+ _domainSet: new Set(),
+ // A map to keep track of the URIs loaded from the restored tabs.
+ _restoredURIsMap: new WeakMap(),
+
+ isHttpURI(uri) {
+ // Only consider http(s) schemas.
+ return uri.schemeIs("http") || uri.schemeIs("https");
+ },
+
+ addRestoredURI(browser, uri) {
+ if (!this.isHttpURI(uri)) {
+ return;
+ }
+
+ this._restoredURIsMap.set(browser, uri.spec);
+ },
+
+ onLocationChange(browser, webProgress, request, uri, flags) {
+ // Don't count this URI if it's an error page.
+ if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+ return;
+ }
+
+ // We only care about top level loads.
+ if (!webProgress.isTopLevel) {
+ return;
+ }
+
+ // The SessionStore sets the URI of a tab first, firing onLocationChange the
+ // first time, then manages content loading using its scheduler. Once content
+ // loads, we will hit onLocationChange again.
+ // We can catch the first case by checking for null requests: be advised that
+ // this can also happen when navigating page fragments, so account for it.
+ if (!request &&
+ !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+ return;
+ }
+
+ // Track URI loads, even if they're not http(s).
+ let uriSpec = null;
+ try {
+ uriSpec = uri.spec;
+ } catch (e) {
+ // If we have troubles parsing the spec, still count this as
+ // an unfiltered URI.
+ Services.telemetry.scalarAdd(UNFILTERED_URI_COUNT_SCALAR_NAME, 1);
+ return;
+ }
+
+
+ // Don't count about:blank and similar pages, as they would artificially
+ // inflate the counts.
+ if (browser.ownerDocument.defaultView.gInitialPages.includes(uriSpec)) {
+ return;
+ }
+
+ // If the URI we're loading is in the _restoredURIsMap, then it comes from a
+ // restored tab. If so, let's skip it and remove it from the map as we want to
+ // count page refreshes.
+ if (this._restoredURIsMap.get(browser) === uriSpec) {
+ this._restoredURIsMap.delete(browser);
+ return;
+ }
+
+ // The URI wasn't from a restored tab. Count it among the unfiltered URIs.
+ // If this is an http(s) URI, this also gets counted by the "total_uri_count"
+ // probe.
+ Services.telemetry.scalarAdd(UNFILTERED_URI_COUNT_SCALAR_NAME, 1);
+
+ if (!this.isHttpURI(uri)) {
+ return;
+ }
+
+ // Update the URI counts.
+ Services.telemetry.scalarAdd(TOTAL_URI_COUNT_SCALAR_NAME, 1);
+
+ // We only want to count the unique domains up to MAX_UNIQUE_VISITED_DOMAINS.
+ if (this._domainSet.size == MAX_UNIQUE_VISITED_DOMAINS) {
+ return;
+ }
+
+ // Unique domains should be aggregated by (eTLD + 1): x.test.com and y.test.com
+ // are counted once as test.com.
+ try {
+ // Even if only considering http(s) URIs, |getBaseDomain| could still throw
+ // due to the URI containing invalid characters or the domain actually being
+ // an ipv4 or ipv6 address.
+ this._domainSet.add(Services.eTLD.getBaseDomain(uri));
+ } catch (e) {
+ return;
+ }
+
+ Services.telemetry.scalarSet(UNIQUE_DOMAINS_COUNT_SCALAR_NAME, this._domainSet.size);
+ },
+
+ /**
+ * Reset the counts. This should be called when breaking a session in Telemetry.
+ */
+ reset() {
+ this._domainSet.clear();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+};
+
+let BrowserUsageTelemetry = {
+ init() {
+ Services.obs.addObserver(this, WINDOWS_RESTORED_TOPIC, false);
+ },
+
+ /**
+ * Handle subsession splits in the parent process.
+ */
+ afterSubsessionSplit() {
+ // Scalars just got cleared due to a subsession split. We need to set the maximum
+ // concurrent tab and window counts so that they reflect the correct value for the
+ // new subsession.
+ const counts = getOpenTabsAndWinsCounts();
+ Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
+ Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+
+ // Reset the URI counter.
+ URICountListener.reset();
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC, false);
+ Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
+ Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC, false);
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case WINDOWS_RESTORED_TOPIC:
+ this._setupAfterRestore();
+ break;
+ case DOMWINDOW_OPENED_TOPIC:
+ this._onWindowOpen(subject);
+ break;
+ case TELEMETRY_SUBSESSIONSPLIT_TOPIC:
+ this.afterSubsessionSplit();
+ break;
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "TabOpen":
+ this._onTabOpen();
+ break;
+ case "unload":
+ this._unregisterWindow(event.target);
+ break;
+ case TAB_RESTORING_TOPIC:
+ // We're restoring a new tab from a previous or crashed session.
+ // We don't want to track the URIs from these tabs, so let
+ // |URICountListener| know about them.
+ let browser = event.target.linkedBrowser;
+ URICountListener.addRestoredURI(browser, browser.currentURI);
+ break;
+ }
+ },
+
+ /**
+ * The main entry point for recording search related Telemetry. This includes
+ * search counts and engagement measurements.
+ *
+ * Telemetry records only search counts per engine and action origin, but
+ * nothing pertaining to the search contents themselves.
+ *
+ * @param {nsISearchEngine} engine
+ * The engine handling the search.
+ * @param {String} source
+ * Where the search originated from. See KNOWN_SEARCH_SOURCES for allowed
+ * values.
+ * @param {Object} [details] Options object.
+ * @param {Boolean} [details.isOneOff=false]
+ * true if this event was generated by a one-off search.
+ * @param {Boolean} [details.isSuggestion=false]
+ * true if this event was generated by a suggested search.
+ * @param {Boolean} [details.isAlias=false]
+ * true if this event was generated by a search using an alias.
+ * @param {Object} [details.type=null]
+ * The object describing the event that triggered the search.
+ * @throws if source is not in the known sources list.
+ */
+ recordSearch(engine, source, details={}) {
+ const isOneOff = !!details.isOneOff;
+ const countId = getSearchEngineId(engine) + "." + source;
+
+ if (isOneOff) {
+ if (!KNOWN_ONEOFF_SOURCES.includes(source)) {
+ // Silently drop the error if this bogus call
+ // came from 'urlbar' or 'searchbar'. They're
+ // calling |recordSearch| twice from two different
+ // code paths because they want to record the search
+ // in SEARCH_COUNTS.
+ if (['urlbar', 'searchbar'].includes(source)) {
+ Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").add(countId);
+ return;
+ }
+ throw new Error("Unknown source for one-off search: " + source);
+ }
+ } else {
+ if (!KNOWN_SEARCH_SOURCES.includes(source)) {
+ throw new Error("Unknown source for search: " + source);
+ }
+ Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").add(countId);
+ }
+
+ // Dispatch the search signal to other handlers.
+ this._handleSearchAction(engine, source, details);
+ },
+
+ _recordSearch(engine, source, action = null) {
+ let scalarKey = action ? "search_" + action : "search";
+ Services.telemetry.keyedScalarAdd("browser.engagement.navigation." + source,
+ scalarKey, 1);
+ Services.telemetry.recordEvent("navigation", "search", source, action,
+ { engine: getSearchEngineId(engine) });
+ },
+
+ _handleSearchAction(engine, source, details) {
+ switch (source) {
+ case "urlbar":
+ case "oneoff-urlbar":
+ case "searchbar":
+ case "oneoff-searchbar":
+ case "unknown": // Edge case: this is the searchbar (see bug 1195733 comment 7).
+ this._handleSearchAndUrlbar(engine, source, details);
+ break;
+ case "abouthome":
+ this._recordSearch(engine, "about_home", "enter");
+ break;
+ case "newtab":
+ this._recordSearch(engine, "about_newtab", "enter");
+ break;
+ case "contextmenu":
+ this._recordSearch(engine, "contextmenu");
+ break;
+ }
+ },
+
+ /**
+ * This function handles the "urlbar", "urlbar-oneoff", "searchbar" and
+ * "searchbar-oneoff" sources.
+ */
+ _handleSearchAndUrlbar(engine, source, details) {
+ // We want "urlbar" and "urlbar-oneoff" (and similar cases) to go in the same
+ // scalar, but in a different key.
+
+ // When using one-offs in the searchbar we get an "unknown" source. See bug
+ // 1195733 comment 7 for the context. Fix-up the label here.
+ const sourceName =
+ (source === "unknown") ? "searchbar" : source.replace("oneoff-", "");
+
+ const isOneOff = !!details.isOneOff;
+ if (isOneOff) {
+ // We will receive a signal from the "urlbar"/"searchbar" even when the
+ // search came from "oneoff-urlbar". That's because both signals
+ // are propagated from search.xml. Skip it if that's the case.
+ // Moreover, we skip the "unknown" source that comes from the searchbar
+ // when performing searches from the default search engine. See bug 1195733
+ // comment 7 for context.
+ if (["urlbar", "searchbar", "unknown"].includes(source)) {
+ return;
+ }
+
+ // If that's a legit one-off search signal, record it using the relative key.
+ this._recordSearch(engine, sourceName, "oneoff");
+ return;
+ }
+
+ // The search was not a one-off. It was a search with the default search engine.
+ if (details.isSuggestion) {
+ // It came from a suggested search, so count it as such.
+ this._recordSearch(engine, sourceName, "suggestion");
+ return;
+ } else if (details.isAlias) {
+ // This one came from a search that used an alias.
+ this._recordSearch(engine, sourceName, "alias");
+ return;
+ }
+
+ // The search signal was generated by typing something and pressing enter.
+ this._recordSearch(engine, sourceName, "enter");
+ },
+
+ /**
+ * This gets called shortly after the SessionStore has finished restoring
+ * windows and tabs. It counts the open tabs and adds listeners to all the
+ * windows.
+ */
+ _setupAfterRestore() {
+ // Make sure to catch new chrome windows and subsession splits.
+ Services.obs.addObserver(this, DOMWINDOW_OPENED_TOPIC, false);
+ Services.obs.addObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
+
+ // Attach the tabopen handlers to the existing Windows.
+ let browserEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserEnum.hasMoreElements()) {
+ this._registerWindow(browserEnum.getNext());
+ }
+
+ // Get the initial tab and windows max counts.
+ const counts = getOpenTabsAndWinsCounts();
+ Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
+ Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+ },
+
+ /**
+ * Adds listeners to a single chrome window.
+ */
+ _registerWindow(win) {
+ win.addEventListener("unload", this);
+ win.addEventListener("TabOpen", this, true);
+
+ // Don't include URI and domain counts when in private mode.
+ if (PrivateBrowsingUtils.isWindowPrivate(win)) {
+ return;
+ }
+ win.gBrowser.tabContainer.addEventListener(TAB_RESTORING_TOPIC, this);
+ win.gBrowser.addTabsProgressListener(URICountListener);
+ },
+
+ /**
+ * Removes listeners from a single chrome window.
+ */
+ _unregisterWindow(win) {
+ win.removeEventListener("unload", this);
+ win.removeEventListener("TabOpen", this, true);
+
+ // Don't include URI and domain counts when in private mode.
+ if (PrivateBrowsingUtils.isWindowPrivate(win.defaultView)) {
+ return;
+ }
+ win.defaultView.gBrowser.tabContainer.removeEventListener(TAB_RESTORING_TOPIC, this);
+ win.defaultView.gBrowser.removeTabsProgressListener(URICountListener);
+ },
+
+ /**
+ * Updates the tab counts.
+ * @param {Number} [newTabCount=0] The count of the opened tabs across all windows. This
+ * is computed manually if not provided.
+ */
+ _onTabOpen(tabCount = 0) {
+ // Use the provided tab count if available. Otherwise, go on and compute it.
+ tabCount = tabCount || getOpenTabsAndWinsCounts().tabCount;
+ // Update the "tab opened" count and its maximum.
+ Services.telemetry.scalarAdd(TAB_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
+ Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, tabCount);
+ },
+
+ /**
+ * Tracks the window count and registers the listeners for the tab count.
+ * @param{Object} win The window object.
+ */
+ _onWindowOpen(win) {
+ // Make sure to have a |nsIDOMWindow|.
+ if (!(win instanceof Ci.nsIDOMWindow)) {
+ return;
+ }
+
+ let onLoad = () => {
+ win.removeEventListener("load", onLoad, false);
+
+ // Ignore non browser windows.
+ if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
+ return;
+ }
+
+ this._registerWindow(win);
+ // Track the window open event and check the maximum.
+ const counts = getOpenTabsAndWinsCounts();
+ Services.telemetry.scalarAdd(WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
+ Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
+
+ // We won't receive the "TabOpen" event for the first tab within a new window.
+ // Account for that.
+ this._onTabOpen(counts.tabCount);
+ };
+ win.addEventListener("load", onLoad, false);
+ },
+};
diff --git a/browser/modules/CastingApps.jsm b/browser/modules/CastingApps.jsm
new file mode 100644
index 000000000..6f32753e8
--- /dev/null
+++ b/browser/modules/CastingApps.jsm
@@ -0,0 +1,164 @@
+// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["CastingApps"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm");
+
+
+var CastingApps = {
+ _sendEventToVideo: function (element, data) {
+ let event = element.ownerDocument.createEvent("CustomEvent");
+ event.initCustomEvent("media-videoCasting", false, true, JSON.stringify(data));
+ element.dispatchEvent(event);
+ },
+
+ makeURI: function (url, charset, baseURI) {
+ return Services.io.newURI(url, charset, baseURI);
+ },
+
+ getVideo: function (element) {
+ if (!element) {
+ return null;
+ }
+
+ let extensions = SimpleServiceDiscovery.getSupportedExtensions();
+ let types = SimpleServiceDiscovery.getSupportedMimeTypes();
+
+ // Grab the poster attribute from the <video>
+ let posterURL = element.poster;
+
+ // First, look to see if the <video> has a src attribute
+ let sourceURL = element.src;
+
+ // If empty, try the currentSrc
+ if (!sourceURL) {
+ sourceURL = element.currentSrc;
+ }
+
+ if (sourceURL) {
+ // Use the file extension to guess the mime type
+ let sourceURI = this.makeURI(sourceURL, null, this.makeURI(element.baseURI));
+ if (this.allowableExtension(sourceURI, extensions)) {
+ return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI};
+ }
+ }
+
+ // Next, look to see if there is a <source> child element that meets
+ // our needs
+ let sourceNodes = element.getElementsByTagName("source");
+ for (let sourceNode of sourceNodes) {
+ let sourceURI = this.makeURI(sourceNode.src, null, this.makeURI(sourceNode.baseURI));
+
+ // Using the type attribute is our ideal way to guess the mime type. Otherwise,
+ // fallback to using the file extension to guess the mime type
+ if (this.allowableMimeType(sourceNode.type, types) || this.allowableExtension(sourceURI, extensions)) {
+ return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type };
+ }
+ }
+
+ return null;
+ },
+
+ sendVideoToService: function (videoElement, service) {
+ if (!service)
+ return;
+
+ let video = this.getVideo(videoElement);
+ if (!video) {
+ return;
+ }
+
+ // Make sure we have a player app for the given service
+ let app = SimpleServiceDiscovery.findAppForService(service);
+ if (!app)
+ return;
+
+ video.title = videoElement.ownerGlobal.top.document.title;
+ if (video.element) {
+ // If the video is currently playing on the device, pause it
+ if (!video.element.paused) {
+ video.element.pause();
+ }
+ }
+
+ app.stop(() => {
+ app.start(started => {
+ if (!started) {
+ Cu.reportError("CastingApps: Unable to start app");
+ return;
+ }
+
+ app.remoteMedia(remoteMedia => {
+ if (!remoteMedia) {
+ Cu.reportError("CastingApps: Failed to create remotemedia");
+ return;
+ }
+
+ this.session = {
+ service: service,
+ app: app,
+ remoteMedia: remoteMedia,
+ data: {
+ title: video.title,
+ source: video.source,
+ poster: video.poster
+ },
+ videoRef: Cu.getWeakReference(video.element)
+ };
+ }, this);
+ });
+ });
+ },
+
+ getServicesForVideo: function (videoElement) {
+ let video = this.getVideo(videoElement);
+ if (!video) {
+ return {};
+ }
+
+ let filteredServices = SimpleServiceDiscovery.services.filter(service => {
+ return this.allowableExtension(video.sourceURI, service.extensions) ||
+ this.allowableMimeType(video.type, service.types);
+ });
+
+ return filteredServices;
+ },
+
+ getServicesForMirroring: function () {
+ return SimpleServiceDiscovery.services.filter(service => service.mirror);
+ },
+
+ // RemoteMedia callback API methods
+ onRemoteMediaStart: function (remoteMedia) {
+ if (!this.session) {
+ return;
+ }
+
+ remoteMedia.load(this.session.data);
+
+ let video = this.session.videoRef.get();
+ if (video) {
+ this._sendEventToVideo(video, { active: true });
+ }
+ },
+
+ onRemoteMediaStop: function (remoteMedia) {
+ },
+
+ onRemoteMediaStatus: function (remoteMedia) {
+ },
+
+ allowableExtension: function (uri, extensions) {
+ return (uri instanceof Ci.nsIURL) && extensions.indexOf(uri.fileExtension) != -1;
+ },
+
+ allowableMimeType: function (type, types) {
+ return types.indexOf(type) != -1;
+ }
+};
diff --git a/browser/modules/ContentClick.jsm b/browser/modules/ContentClick.jsm
new file mode 100644
index 000000000..8abc32525
--- /dev/null
+++ b/browser/modules/ContentClick.jsm
@@ -0,0 +1,97 @@
+/* -*- mode: js; 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 Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "ContentClick" ];
+
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var ContentClick = {
+ init: function() {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("Content:Click", this);
+ },
+
+ receiveMessage: function (message) {
+ switch (message.name) {
+ case "Content:Click":
+ this.contentAreaClick(message.json, message.target)
+ break;
+ }
+ },
+
+ contentAreaClick: function (json, browser) {
+ // This is heavily based on contentAreaClick from browser.js (Bug 903016)
+ // The json is set up in a way to look like an Event.
+ let window = browser.ownerGlobal;
+
+ if (!json.href) {
+ // Might be middle mouse navigation.
+ if (Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
+ !Services.prefs.getBoolPref("general.autoScroll")) {
+ window.middleMousePaste(json);
+ }
+ return;
+ }
+
+ if (json.bookmark) {
+ // This is the Opera convention for a special link that, when clicked,
+ // allows to add a sidebar panel. The link's title attribute contains
+ // the title that should be used for the sidebar panel.
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: Services.io.newURI(json.href, null, null)
+ , title: json.title
+ , loadBookmarkInSidebar: true
+ , hiddenRows: [ "description"
+ , "location"
+ , "keyword" ]
+ }, window);
+ return;
+ }
+
+ // Note: We don't need the sidebar code here.
+
+ // Mark the page as a user followed link. This is done so that history can
+ // distinguish automatic embed visits from user activated ones. For example
+ // pages loaded in frames are embed visits and lost with the session, while
+ // visits across frames should be preserved.
+ try {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsFollowedLink(json.href);
+ } catch (ex) { /* Skip invalid URIs. */ }
+
+ // This part is based on handleLinkClick.
+ var where = window.whereToOpenLink(json);
+ if (where == "current")
+ return;
+
+ // Todo(903022): code for where == save
+
+ let params = {
+ charset: browser.characterSet,
+ referrerURI: browser.documentURI,
+ referrerPolicy: json.referrerPolicy,
+ noReferrer: json.noReferrer,
+ allowMixedContent: json.allowMixedContent,
+ isContentWindowPrivate: json.isContentWindowPrivate,
+ originPrincipal: json.originPrincipal,
+ };
+
+ // The new tab/window must use the same userContextId.
+ if (json.originAttributes.userContextId) {
+ params.userContextId = json.originAttributes.userContextId;
+ }
+
+ window.openLinkIn(json.href, where, params);
+ }
+};
diff --git a/browser/modules/ContentCrashHandlers.jsm b/browser/modules/ContentCrashHandlers.jsm
new file mode 100644
index 000000000..2f755d142
--- /dev/null
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -0,0 +1,1035 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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;
+
+this.EXPORTED_SYMBOLS = [ "TabCrashHandler",
+ "PluginCrashReporter",
+ "UnsubmittedCrashHandler" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
+ "resource://gre/modules/CrashSubmit.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
+ "resource://gre/modules/RemotePageManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() {
+ const url = "chrome://browser/locale/browser.properties";
+ return Services.strings.createBundle(url);
+});
+
+// We don't process crash reports older than 28 days, so don't bother
+// submitting them
+const PENDING_CRASH_REPORT_DAYS = 28;
+const DAY = 24 * 60 * 60 * 1000; // milliseconds
+const DAYS_TO_SUPPRESS = 30;
+const MAX_UNSEEN_CRASHED_CHILD_IDS = 20;
+
+this.TabCrashHandler = {
+ _crashedTabCount: 0,
+ childMap: new Map(),
+ browserMap: new WeakMap(),
+ unseenCrashedChildIDs: [],
+ crashedBrowserQueues: new Map(),
+
+ get prefs() {
+ delete this.prefs;
+ return this.prefs = Services.prefs.getBranch("browser.tabs.crashReporting.");
+ },
+
+ init: function () {
+ if (this.initialized)
+ return;
+ this.initialized = true;
+
+ Services.obs.addObserver(this, "ipc:content-shutdown", false);
+ Services.obs.addObserver(this, "oop-frameloader-crashed", false);
+
+ this.pageListener = new RemotePages("about:tabcrashed");
+ // LOAD_BACKGROUND pages don't fire load events, so the about:tabcrashed
+ // content will fire up its own message when its initial scripts have
+ // finished running.
+ this.pageListener.addMessageListener("Load", this.receiveMessage.bind(this));
+ this.pageListener.addMessageListener("RemotePage:Unload", this.receiveMessage.bind(this));
+ this.pageListener.addMessageListener("closeTab", this.receiveMessage.bind(this));
+ this.pageListener.addMessageListener("restoreTab", this.receiveMessage.bind(this));
+ this.pageListener.addMessageListener("restoreAll", this.receiveMessage.bind(this));
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "ipc:content-shutdown": {
+ aSubject.QueryInterface(Ci.nsIPropertyBag2);
+
+ if (!aSubject.get("abnormal")) {
+ return;
+ }
+
+ let childID = aSubject.get("childID");
+ let dumpID = aSubject.get("dumpID");
+
+ if (!dumpID) {
+ Services.telemetry
+ .getHistogramById("FX_CONTENT_CRASH_DUMP_UNAVAILABLE")
+ .add(1);
+ } else if (AppConstants.MOZ_CRASHREPORTER) {
+ this.childMap.set(childID, dumpID);
+ }
+
+ if (!this.flushCrashedBrowserQueue(childID)) {
+ this.unseenCrashedChildIDs.push(childID);
+ // The elements in unseenCrashedChildIDs will only be removed if
+ // the tab crash page is shown. However, ipc:content-shutdown might
+ // be fired for processes for which we'll never show the tab crash
+ // page - for example, the thumbnailing process. Another case to
+ // consider is if the user is configured to submit backlogged crash
+ // reports automatically, and a background tab crashes. In that case,
+ // we will never show the tab crash page, and never remove the element
+ // from the list.
+ //
+ // Instead of trying to account for all of those cases, we prevent
+ // this list from getting too large by putting a reasonable upper
+ // limit on how many childIDs we track. It's unlikely that this
+ // array would ever get so large as to be unwieldy (that'd be a lot
+ // or crashes!), but a leak is a leak.
+ if (this.unseenCrashedChildIDs.length > MAX_UNSEEN_CRASHED_CHILD_IDS) {
+ this.unseenCrashedChildIDs.shift();
+ }
+ }
+
+ // check for environment affecting crash reporting
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let shutdown = env.exists("MOZ_CRASHREPORTER_SHUTDOWN");
+
+ if (shutdown) {
+ Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+ }
+
+ break;
+ }
+ case "oop-frameloader-crashed": {
+ aSubject.QueryInterface(Ci.nsIFrameLoader);
+
+ let browser = aSubject.ownerElement;
+ if (!browser) {
+ return;
+ }
+
+ this.browserMap.set(browser.permanentKey, aSubject.childID);
+ break;
+ }
+ }
+ },
+
+ receiveMessage: function(message) {
+ let browser = message.target.browser;
+ let gBrowser = browser.ownerGlobal.gBrowser;
+ let tab = gBrowser.getTabForBrowser(browser);
+
+ switch (message.name) {
+ case "Load": {
+ this.onAboutTabCrashedLoad(message);
+ break;
+ }
+
+ case "RemotePage:Unload": {
+ this.onAboutTabCrashedUnload(message);
+ break;
+ }
+
+ case "closeTab": {
+ this.maybeSendCrashReport(message);
+ gBrowser.removeTab(tab, { animate: true });
+ break;
+ }
+
+ case "restoreTab": {
+ this.maybeSendCrashReport(message);
+ SessionStore.reviveCrashedTab(tab);
+ break;
+ }
+
+ case "restoreAll": {
+ this.maybeSendCrashReport(message);
+ SessionStore.reviveAllCrashedTabs();
+ break;
+ }
+ }
+ },
+
+ /**
+ * This should be called once a content process has finished
+ * shutting down abnormally. Any tabbrowser browsers that were
+ * selected at the time of the crash will then be sent to
+ * the crashed tab page.
+ *
+ * @param childID (int)
+ * The childID of the content process that just crashed.
+ * @returns boolean
+ * True if one or more browsers were sent to the tab crashed
+ * page.
+ */
+ flushCrashedBrowserQueue(childID) {
+ let browserQueue = this.crashedBrowserQueues.get(childID);
+ if (!browserQueue) {
+ return false;
+ }
+
+ this.crashedBrowserQueues.delete(childID);
+
+ let sentBrowser = false;
+ for (let weakBrowser of browserQueue) {
+ let browser = weakBrowser.get();
+ if (browser) {
+ this.sendToTabCrashedPage(browser);
+ sentBrowser = true;
+ }
+ }
+
+ return sentBrowser;
+ },
+
+ /**
+ * Called by a tabbrowser when it notices that its selected browser
+ * has crashed. This will queue the browser to show the tab crash
+ * page once the content process has finished tearing down.
+ *
+ * @param browser (<xul:browser>)
+ * The selected browser that just crashed.
+ */
+ onSelectedBrowserCrash(browser) {
+ if (!browser.isRemoteBrowser) {
+ Cu.reportError("Selected crashed browser is not remote.")
+ return;
+ }
+ if (!browser.frameLoader) {
+ Cu.reportError("Selected crashed browser has no frameloader.");
+ return;
+ }
+
+ let childID = browser.frameLoader.childID;
+ let browserQueue = this.crashedBrowserQueues.get(childID);
+ if (!browserQueue) {
+ browserQueue = [];
+ this.crashedBrowserQueues.set(childID, browserQueue);
+ }
+ // It's probably unnecessary to store this browser as a
+ // weak reference, since the content process should complete
+ // its teardown in the same tick of the event loop, and then
+ // this queue will be flushed. The weak reference is to avoid
+ // leaking browsers in case anything goes wrong during this
+ // teardown process.
+ browserQueue.push(Cu.getWeakReference(browser));
+ },
+
+ /**
+ * This method is exposed for SessionStore to call if the user selects
+ * a tab which will restore on demand. It's possible that the tab
+ * is in this state because it recently crashed. If that's the case, then
+ * it's also possible that the user has not seen the tab crash page for
+ * that particular crash, in which case, we might show it to them instead
+ * of restoring the tab.
+ *
+ * @param browser (<xul:browser>)
+ * A browser from a browser tab that the user has just selected
+ * to restore on demand.
+ * @returns (boolean)
+ * True if TabCrashHandler will send the user to the tab crash
+ * page instead.
+ */
+ willShowCrashedTab(browser) {
+ let childID = this.browserMap.get(browser.permanentKey);
+ // We will only show the tab crash page if:
+ // 1) We are aware that this browser crashed
+ // 2) We know we've never shown the tab crash page for the
+ // crash yet
+ // 3) The user is not configured to automatically submit backlogged
+ // crash reports. If they are, we'll send the crash report
+ // immediately.
+ if (childID &&
+ this.unseenCrashedChildIDs.indexOf(childID) != -1) {
+ if (UnsubmittedCrashHandler.autoSubmit) {
+ let dumpID = this.childMap.get(childID);
+ if (dumpID) {
+ UnsubmittedCrashHandler.submitReports([dumpID]);
+ }
+ } else {
+ this.sendToTabCrashedPage(browser);
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * We show a special page to users when a normal browser tab has crashed.
+ * This method should be called to send a browser to that page once the
+ * process has completely closed.
+ *
+ * @param browser (<xul:browser>)
+ * The browser that has recently crashed.
+ */
+ sendToTabCrashedPage(browser) {
+ let title = browser.contentTitle;
+ let uri = browser.currentURI;
+ let gBrowser = browser.ownerGlobal.gBrowser;
+ let tab = gBrowser.getTabForBrowser(browser);
+ // The tab crashed page is non-remote by default.
+ gBrowser.updateBrowserRemoteness(browser, false);
+
+ browser.setAttribute("crashedPageTitle", title);
+ browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
+ browser.removeAttribute("crashedPageTitle");
+ tab.setAttribute("crashed", true);
+ },
+
+ /**
+ * Submits a crash report from about:tabcrashed, if the crash
+ * reporter is enabled and a crash report can be found.
+ *
+ * @param aBrowser
+ * The <xul:browser> that the report was sent from.
+ * @param aFormData
+ * An Object with the following properties:
+ *
+ * includeURL (bool):
+ * Whether to include the URL that the user was on
+ * in the crashed tab before the crash occurred.
+ * URL (String)
+ * The URL that the user was on in the crashed tab
+ * before the crash occurred.
+ * emailMe (bool):
+ * Whether or not to include the user's email address
+ * in the crash report.
+ * email (String):
+ * The email address of the user.
+ * comments (String):
+ * Any additional comments from the user.
+ *
+ * Note that it is expected that all properties are set,
+ * even if they are empty.
+ */
+ maybeSendCrashReport(message) {
+ if (!AppConstants.MOZ_CRASHREPORTER) {
+ return;
+ }
+
+ if (!message.data.hasReport) {
+ // There was no report, so nothing to do.
+ return;
+ }
+
+ let browser = message.target.browser;
+
+ if (message.data.autoSubmit) {
+ // The user has opted in to autosubmitted backlogged
+ // crash reports in the future.
+ UnsubmittedCrashHandler.autoSubmit = true;
+ }
+
+ let childID = this.browserMap.get(browser.permanentKey);
+ let dumpID = this.childMap.get(childID);
+ if (!dumpID) {
+ return;
+ }
+
+ if (!message.data.sendReport) {
+ Services.telemetry.getHistogramById("FX_CONTENT_CRASH_NOT_SUBMITTED").add(1);
+ this.prefs.setBoolPref("sendReport", false);
+ return;
+ }
+
+ let {
+ includeURL,
+ comments,
+ email,
+ emailMe,
+ URL,
+ } = message.data;
+
+ let extraExtraKeyVals = {
+ "Comments": comments,
+ "Email": email,
+ "URL": URL,
+ };
+
+ // For the entries in extraExtraKeyVals, we only want to submit the
+ // extra data values where they are not the empty string.
+ for (let key in extraExtraKeyVals) {
+ let val = extraExtraKeyVals[key].trim();
+ if (!val) {
+ delete extraExtraKeyVals[key];
+ }
+ }
+
+ // URL is special, since it's already been written to extra data by
+ // default. In order to make sure we don't send it, we overwrite it
+ // with the empty string.
+ if (!includeURL) {
+ extraExtraKeyVals["URL"] = "";
+ }
+
+ CrashSubmit.submit(dumpID, {
+ recordSubmission: true,
+ extraExtraKeyVals,
+ }).then(null, Cu.reportError);
+
+ this.prefs.setBoolPref("sendReport", true);
+ this.prefs.setBoolPref("includeURL", includeURL);
+ this.prefs.setBoolPref("emailMe", emailMe);
+ if (emailMe) {
+ this.prefs.setCharPref("email", email);
+ } else {
+ this.prefs.setCharPref("email", "");
+ }
+
+ this.childMap.set(childID, null); // Avoid resubmission.
+ this.removeSubmitCheckboxesForSameCrash(childID);
+ },
+
+ removeSubmitCheckboxesForSameCrash: function(childID) {
+ let enumerator = Services.wm.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ let window = enumerator.getNext();
+ if (!window.gMultiProcessBrowser)
+ continue;
+
+ for (let browser of window.gBrowser.browsers) {
+ if (browser.isRemoteBrowser)
+ continue;
+
+ let doc = browser.contentDocument;
+ if (!doc.documentURI.startsWith("about:tabcrashed"))
+ continue;
+
+ if (this.browserMap.get(browser.permanentKey) == childID) {
+ this.browserMap.delete(browser.permanentKey);
+ let ports = this.pageListener.portsForBrowser(browser);
+ if (ports.length) {
+ // For about:tabcrashed, we don't expect subframes. We can
+ // assume sending to the first port is sufficient.
+ ports[0].sendAsyncMessage("CrashReportSent");
+ }
+ }
+ }
+ }
+ },
+
+ onAboutTabCrashedLoad: function (message) {
+ this._crashedTabCount++;
+
+ // Broadcast to all about:tabcrashed pages a count of
+ // how many about:tabcrashed pages exist, so that they
+ // can decide whether or not to display the "Restore All
+ // Crashed Tabs" button.
+ this.pageListener.sendAsyncMessage("UpdateCount", {
+ count: this._crashedTabCount,
+ });
+
+ let browser = message.target.browser;
+
+ let childID = this.browserMap.get(browser.permanentKey);
+ let index = this.unseenCrashedChildIDs.indexOf(childID);
+ if (index != -1) {
+ this.unseenCrashedChildIDs.splice(index, 1);
+ }
+
+ let dumpID = this.getDumpID(browser);
+ if (!dumpID) {
+ message.target.sendAsyncMessage("SetCrashReportAvailable", {
+ hasReport: false,
+ });
+ return;
+ }
+
+ let requestAutoSubmit = !UnsubmittedCrashHandler.autoSubmit;
+ let requestEmail = this.prefs.getBoolPref("requestEmail");
+ let sendReport = this.prefs.getBoolPref("sendReport");
+ let includeURL = this.prefs.getBoolPref("includeURL");
+ let emailMe = this.prefs.getBoolPref("emailMe");
+
+ let data = {
+ hasReport: true,
+ sendReport,
+ includeURL,
+ emailMe,
+ requestAutoSubmit,
+ requestEmail,
+ };
+
+ if (emailMe) {
+ data.email = this.prefs.getCharPref("email", "");
+ }
+
+ // Make sure to only count once even if there are multiple windows
+ // that will all show about:tabcrashed.
+ if (this._crashedTabCount == 1) {
+ Services.telemetry.getHistogramById("FX_CONTENT_CRASH_PRESENTED").add(1);
+ }
+
+ message.target.sendAsyncMessage("SetCrashReportAvailable", data);
+ },
+
+ onAboutTabCrashedUnload(message) {
+ if (!this._crashedTabCount) {
+ Cu.reportError("Can not decrement crashed tab count to below 0");
+ return;
+ }
+ this._crashedTabCount--;
+
+ // Broadcast to all about:tabcrashed pages a count of
+ // how many about:tabcrashed pages exist, so that they
+ // can decide whether or not to display the "Restore All
+ // Crashed Tabs" button.
+ this.pageListener.sendAsyncMessage("UpdateCount", {
+ count: this._crashedTabCount,
+ });
+
+ let browser = message.target.browser;
+ let childID = this.browserMap.get(browser.permanentKey);
+
+ // Make sure to only count once even if there are multiple windows
+ // that will all show about:tabcrashed.
+ if (this._crashedTabCount == 0 && childID) {
+ Services.telemetry.getHistogramById("FX_CONTENT_CRASH_NOT_SUBMITTED").add(1);
+ }
+ },
+
+ /**
+ * For some <xul:browser>, return a crash report dump ID for that browser
+ * if we have been informed of one. Otherwise, return null.
+ *
+ * @param browser (<xul:browser)
+ * The browser to try to get the dump ID for
+ * @returns dumpID (String)
+ */
+ getDumpID(browser) {
+ if (!AppConstants.MOZ_CRASHREPORTER) {
+ return null;
+ }
+
+ return this.childMap.get(this.browserMap.get(browser.permanentKey));
+ },
+}
+
+/**
+ * This component is responsible for scanning the pending
+ * crash report directory for reports, and (if enabled), to
+ * prompt the user to submit those reports. It might also
+ * submit those reports automatically without prompting if
+ * the user has opted in.
+ */
+this.UnsubmittedCrashHandler = {
+ get prefs() {
+ delete this.prefs;
+ return this.prefs =
+ Services.prefs.getBranch("browser.crashReports.unsubmittedCheck.");
+ },
+
+ get enabled() {
+ return this.prefs.getBoolPref("enabled");
+ },
+
+ // showingNotification is set to true once a notification
+ // is successfully shown, and then set back to false if
+ // the notification is dismissed by an action by the user.
+ showingNotification: false,
+ // suppressed is true if we've determined that we've shown
+ // the notification too many times across too many days without
+ // user interaction, so we're suppressing the notification for
+ // some number of days. See the documentation for
+ // shouldShowPendingSubmissionsNotification().
+ suppressed: false,
+
+ init() {
+ if (this.initialized) {
+ return;
+ }
+
+ this.initialized = true;
+
+ // UnsubmittedCrashHandler can be initialized but still be disabled.
+ // This is intentional, as this makes simulating UnsubmittedCrashHandler's
+ // reactions to browser startup and shutdown easier in test automation.
+ //
+ // UnsubmittedCrashHandler, when initialized but not enabled, is inert.
+ if (this.enabled) {
+ if (this.prefs.prefHasUserValue("suppressUntilDate")) {
+ if (this.prefs.getCharPref("suppressUntilDate") > this.dateString()) {
+ // We'll be suppressing any notifications until after suppressedDate,
+ // so there's no need to do anything more.
+ this.suppressed = true;
+ return;
+ }
+
+ // We're done suppressing, so we don't need this pref anymore.
+ this.prefs.clearUserPref("suppressUntilDate");
+ }
+
+ Services.obs.addObserver(this, "browser-delayed-startup-finished",
+ false);
+ Services.obs.addObserver(this, "profile-before-change",
+ false);
+ }
+ },
+
+ uninit() {
+ if (!this.initialized) {
+ return;
+ }
+
+ this.initialized = false;
+
+ if (!this.enabled) {
+ return;
+ }
+
+ if (this.suppressed) {
+ this.suppressed = false;
+ // No need to do any more clean-up, since we were suppressed.
+ return;
+ }
+
+ if (this.showingNotification) {
+ this.prefs.setBoolPref("shutdownWhileShowing", true);
+ this.showingNotification = false;
+ }
+
+ try {
+ Services.obs.removeObserver(this, "browser-delayed-startup-finished");
+ } catch (e) {
+ // The browser-delayed-startup-finished observer might have already
+ // fired and removed itself, so if this fails, it's okay.
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+
+ Services.obs.removeObserver(this, "profile-before-change");
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "browser-delayed-startup-finished": {
+ Services.obs.removeObserver(this, topic);
+ this.checkForUnsubmittedCrashReports();
+ break;
+ }
+ case "profile-before-change": {
+ this.uninit();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Scans the profile directory for unsubmitted crash reports
+ * within the past PENDING_CRASH_REPORT_DAYS days. If it
+ * finds any, it will, if necessary, attempt to open a notification
+ * bar to prompt the user to submit them.
+ *
+ * @returns Promise
+ * Resolves with the <xul:notification> after it tries to
+ * show a notification on the most recent browser window.
+ * If a notification cannot be shown, will resolve with null.
+ */
+ checkForUnsubmittedCrashReports: Task.async(function*() {
+ let dateLimit = new Date();
+ dateLimit.setDate(dateLimit.getDate() - PENDING_CRASH_REPORT_DAYS);
+
+ let reportIDs = [];
+ try {
+ reportIDs = yield CrashSubmit.pendingIDsAsync(dateLimit);
+ } catch (e) {
+ Cu.reportError(e);
+ return null;
+ }
+
+ if (reportIDs.length) {
+ if (this.autoSubmit) {
+ this.submitReports(reportIDs);
+ } else if (this.shouldShowPendingSubmissionsNotification()) {
+ return this.showPendingSubmissionsNotification(reportIDs);
+ }
+ }
+ return null;
+ }),
+
+ /**
+ * Returns true if the notification should be shown.
+ * shouldShowPendingSubmissionsNotification makes this decision
+ * by looking at whether or not the user has seen the notification
+ * over several days without ever interacting with it. If this occurs
+ * too many times, we suppress the notification for DAYS_TO_SUPPRESS
+ * days.
+ *
+ * @returns bool
+ */
+ shouldShowPendingSubmissionsNotification() {
+ if (!this.prefs.prefHasUserValue("shutdownWhileShowing")) {
+ return true;
+ }
+
+ let shutdownWhileShowing = this.prefs.getBoolPref("shutdownWhileShowing");
+ this.prefs.clearUserPref("shutdownWhileShowing");
+
+ if (!this.prefs.prefHasUserValue("lastShownDate")) {
+ // This isn't expected, but we're being defensive here. We'll
+ // opt for showing the notification in this case.
+ return true;
+ }
+
+ let lastShownDate = this.prefs.getCharPref("lastShownDate");
+ if (this.dateString() > lastShownDate && shutdownWhileShowing) {
+ // We're on a newer day then when we last showed the
+ // notification without closing it. We don't want to do
+ // this too many times, so we'll decrement a counter for
+ // this situation. Too many of these, and we'll assume the
+ // user doesn't know or care about unsubmitted notifications,
+ // and we'll suppress the notification for a while.
+ let chances = this.prefs.getIntPref("chancesUntilSuppress");
+ if (--chances < 0) {
+ // We're out of chances!
+ this.prefs.clearUserPref("chancesUntilSuppress");
+ // We'll suppress for DAYS_TO_SUPPRESS days.
+ let suppressUntil =
+ this.dateString(new Date(Date.now() + (DAY * DAYS_TO_SUPPRESS)));
+ this.prefs.setCharPref("suppressUntilDate", suppressUntil);
+ return false;
+ }
+ this.prefs.setIntPref("chancesUntilSuppress", chances);
+ }
+
+ return true;
+ },
+
+ /**
+ * Given an array of unsubmitted crash report IDs, try to open
+ * up a notification asking the user to submit them.
+ *
+ * @param reportIDs (Array<string>)
+ * The Array of report IDs to offer the user to send.
+ * @returns The <xul:notification> if one is shown. null otherwise.
+ */
+ showPendingSubmissionsNotification(reportIDs) {
+ let count = reportIDs.length;
+ if (!count) {
+ return null;
+ }
+
+ let messageTemplate =
+ gNavigatorBundle.GetStringFromName("pendingCrashReports2.label");
+
+ let message = PluralForm.get(count, messageTemplate).replace("#1", count);
+
+ let notification = this.show({
+ notificationID: "pending-crash-reports",
+ message,
+ reportIDs,
+ onAction: () => {
+ this.showingNotification = false;
+ },
+ });
+
+ if (notification) {
+ this.showingNotification = true;
+ this.prefs.setCharPref("lastShownDate", this.dateString());
+ }
+
+ return notification;
+ },
+
+ /**
+ * Returns a string representation of a Date in the format
+ * YYYYMMDD.
+ *
+ * @param someDate (Date, optional)
+ * The Date to convert to the string. If not provided,
+ * defaults to today's date.
+ * @returns String
+ */
+ dateString(someDate = new Date()) {
+ let year = String(someDate.getFullYear()).padStart(4, "0");
+ let month = String(someDate.getMonth() + 1).padStart(2, "0");
+ let day = String(someDate.getDate()).padStart(2, "0");
+ return year + month + day;
+ },
+
+ /**
+ * Attempts to show a notification bar to the user in the most
+ * recent browser window asking them to submit some crash report
+ * IDs. If a notification cannot be shown (for example, there
+ * is no browser window), this method exits silently.
+ *
+ * The notification will allow the user to submit their crash
+ * reports. If the user dismissed the notification, the crash
+ * reports will be marked to be ignored (though they can
+ * still be manually submitted via about:crashes).
+ *
+ * @param JS Object
+ * An Object with the following properties:
+ *
+ * notificationID (string)
+ * The ID for the notification to be opened.
+ *
+ * message (string)
+ * The message to be displayed in the notification.
+ *
+ * reportIDs (Array<string>)
+ * The array of report IDs to offer to the user.
+ *
+ * onAction (function, optional)
+ * A callback to fire once the user performs an
+ * action on the notification bar (this includes
+ * dismissing the notification).
+ *
+ * @returns The <xul:notification> if one is shown. null otherwise.
+ */
+ show({ notificationID, message, reportIDs, onAction }) {
+ let chromeWin = RecentWindow.getMostRecentBrowserWindow();
+ if (!chromeWin) {
+ // Can't show a notification in this case. We'll hopefully
+ // get another opportunity to have the user submit their
+ // crash reports later.
+ return null;
+ }
+
+ let nb = chromeWin.document.getElementById("global-notificationbox");
+ let notification = nb.getNotificationWithValue(notificationID);
+ if (notification) {
+ return null;
+ }
+
+ let buttons = [{
+ label: gNavigatorBundle.GetStringFromName("pendingCrashReports.send"),
+ callback: () => {
+ this.submitReports(reportIDs);
+ if (onAction) {
+ onAction();
+ }
+ },
+ },
+ {
+ label: gNavigatorBundle.GetStringFromName("pendingCrashReports.alwaysSend"),
+ callback: () => {
+ this.autoSubmit = true;
+ this.submitReports(reportIDs);
+ if (onAction) {
+ onAction();
+ }
+ },
+ },
+ {
+ label: gNavigatorBundle.GetStringFromName("pendingCrashReports.viewAll"),
+ callback: function() {
+ chromeWin.openUILinkIn("about:crashes", "tab");
+ return true;
+ },
+ }];
+
+ let eventCallback = (eventType) => {
+ if (eventType == "dismissed") {
+ // The user intentionally dismissed the notification,
+ // which we interpret as meaning that they don't care
+ // to submit the reports. We'll ignore these particular
+ // reports going forward.
+ reportIDs.forEach(function(reportID) {
+ CrashSubmit.ignore(reportID);
+ });
+ if (onAction) {
+ onAction();
+ }
+ }
+ };
+
+ return nb.appendNotification(message, notificationID,
+ "chrome://browser/skin/tab-crashed.svg",
+ nb.PRIORITY_INFO_HIGH, buttons,
+ eventCallback);
+ },
+
+ get autoSubmit() {
+ return Services.prefs
+ .getBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit2");
+ },
+
+ set autoSubmit(val) {
+ Services.prefs.setBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit2",
+ val);
+ },
+
+ /**
+ * Attempt to submit reports to the crash report server. Each
+ * report will have the "SubmittedFromInfobar" extra key set
+ * to true.
+ *
+ * @param reportIDs (Array<string>)
+ * The array of reportIDs to submit.
+ */
+ submitReports(reportIDs) {
+ for (let reportID of reportIDs) {
+ CrashSubmit.submit(reportID, {
+ extraExtraKeyVals: {
+ "SubmittedFromInfobar": true,
+ },
+ });
+ }
+ },
+};
+
+this.PluginCrashReporter = {
+ /**
+ * Makes the PluginCrashReporter ready to hear about and
+ * submit crash reports.
+ */
+ init() {
+ if (this.initialized) {
+ return;
+ }
+
+ this.initialized = true;
+ this.crashReports = new Map();
+
+ Services.obs.addObserver(this, "plugin-crashed", false);
+ Services.obs.addObserver(this, "gmp-plugin-crash", false);
+ Services.obs.addObserver(this, "profile-after-change", false);
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, "plugin-crashed", false);
+ Services.obs.removeObserver(this, "gmp-plugin-crash", false);
+ Services.obs.removeObserver(this, "profile-after-change", false);
+ this.initialized = false;
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "plugin-crashed": {
+ let propertyBag = subject;
+ if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
+ !(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
+ !propertyBag.hasKey("runID") ||
+ !propertyBag.hasKey("pluginDumpID")) {
+ Cu.reportError("PluginCrashReporter can not read plugin information.");
+ return;
+ }
+
+ let runID = propertyBag.getPropertyAsUint32("runID");
+ let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
+ let browserDumpID = propertyBag.getPropertyAsAString("browserDumpID");
+ if (pluginDumpID) {
+ this.crashReports.set(runID, { pluginDumpID, browserDumpID });
+ }
+ break;
+ }
+ case "gmp-plugin-crash": {
+ let propertyBag = subject;
+ if (!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
+ !propertyBag.hasKey("pluginID") ||
+ !propertyBag.hasKey("pluginDumpID") ||
+ !propertyBag.hasKey("pluginName")) {
+ Cu.reportError("PluginCrashReporter can not read plugin information.");
+ return;
+ }
+
+ let pluginID = propertyBag.getPropertyAsUint32("pluginID");
+ let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
+ if (pluginDumpID) {
+ this.crashReports.set(pluginID, { pluginDumpID });
+ }
+
+ // Only the parent process gets the gmp-plugin-crash observer
+ // notification, so we need to inform any content processes that
+ // the GMP has crashed.
+ if (Cc["@mozilla.org/parentprocessmessagemanager;1"]) {
+ let pluginName = propertyBag.getPropertyAsAString("pluginName");
+ let mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ mm.broadcastAsyncMessage("gmp-plugin-crash",
+ { pluginName, pluginID });
+ }
+ break;
+ }
+ case "profile-after-change":
+ this.uninit();
+ break;
+ }
+ },
+
+ /**
+ * Submit a crash report for a crashed NPAPI plugin.
+ *
+ * @param runID
+ * The runID of the plugin that crashed. A run ID is a unique
+ * identifier for a particular run of a plugin process - and is
+ * analogous to a process ID (though it is managed by Gecko instead
+ * of the operating system).
+ * @param keyVals
+ * An object whose key-value pairs will be merged
+ * with the ".extra" file submitted with the report.
+ * The properties of htis object will override properties
+ * of the same name in the .extra file.
+ */
+ submitCrashReport(runID, keyVals) {
+ if (!this.crashReports.has(runID)) {
+ Cu.reportError(`Could not find plugin dump IDs for run ID ${runID}.` +
+ `It is possible that a report was already submitted.`);
+ return;
+ }
+
+ keyVals = keyVals || {};
+ let { pluginDumpID, browserDumpID } = this.crashReports.get(runID);
+
+ let submissionPromise = CrashSubmit.submit(pluginDumpID, {
+ recordSubmission: true,
+ extraExtraKeyVals: keyVals,
+ });
+
+ if (browserDumpID)
+ CrashSubmit.submit(browserDumpID);
+
+ this.broadcastState(runID, "submitting");
+
+ submissionPromise.then(() => {
+ this.broadcastState(runID, "success");
+ }, () => {
+ this.broadcastState(runID, "failed");
+ });
+
+ this.crashReports.delete(runID);
+ },
+
+ broadcastState(runID, state) {
+ let enumerator = Services.wm.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ let window = enumerator.getNext();
+ let mm = window.messageManager;
+ mm.broadcastAsyncMessage("BrowserPlugins:CrashReportSubmitted",
+ { runID, state });
+ }
+ },
+
+ hasCrashReport(runID) {
+ return this.crashReports.has(runID);
+ },
+};
diff --git a/browser/modules/ContentLinkHandler.jsm b/browser/modules/ContentLinkHandler.jsm
new file mode 100644
index 000000000..443cae2da
--- /dev/null
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -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/. */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "ContentLinkHandler" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
+ "resource:///modules/Feeds.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
+const SIZES_TELEMETRY_ENUM = {
+ NO_SIZES: 0,
+ ANY: 1,
+ DIMENSION: 2,
+ INVALID: 3,
+};
+
+this.ContentLinkHandler = {
+ init: function(chromeGlobal) {
+ chromeGlobal.addEventListener("DOMLinkAdded", (event) => {
+ this.onLinkEvent(event, chromeGlobal);
+ }, false);
+ chromeGlobal.addEventListener("DOMLinkChanged", (event) => {
+ this.onLinkEvent(event, chromeGlobal);
+ }, false);
+ },
+
+ onLinkEvent: function(event, chromeGlobal) {
+ var link = event.originalTarget;
+ var rel = link.rel && link.rel.toLowerCase();
+ if (!link || !link.ownerDocument || !rel || !link.href)
+ return;
+
+ // Ignore sub-frames (bugs 305472, 479408).
+ let window = link.ownerGlobal;
+ if (window != window.top)
+ return;
+
+ var feedAdded = false;
+ var iconAdded = false;
+ var searchAdded = false;
+ var rels = {};
+ for (let relString of rel.split(/\s+/))
+ rels[relString] = true;
+
+ for (let relVal in rels) {
+ switch (relVal) {
+ case "feed":
+ case "alternate":
+ if (!feedAdded && event.type == "DOMLinkAdded") {
+ if (!rels.feed && rels.alternate && rels.stylesheet)
+ break;
+
+ if (Feeds.isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) {
+ chromeGlobal.sendAsyncMessage("Link:AddFeed",
+ {type: link.type,
+ href: link.href,
+ title: link.title});
+ feedAdded = true;
+ }
+ }
+ break;
+ case "icon":
+ if (iconAdded || !Services.prefs.getBoolPref("browser.chrome.site_icons"))
+ break;
+
+ var uri = this.getLinkIconURI(link);
+ if (!uri)
+ break;
+
+ // Telemetry probes for measuring the sizes attribute
+ // usage and available dimensions.
+ let sizeHistogramTypes = Services.telemetry.
+ getHistogramById("LINK_ICON_SIZES_ATTR_USAGE");
+ let sizeHistogramDimension = Services.telemetry.
+ getHistogramById("LINK_ICON_SIZES_ATTR_DIMENSION");
+ let sizesType;
+ if (link.sizes.length) {
+ for (let size of link.sizes) {
+ if (size.toLowerCase() == "any") {
+ sizesType = SIZES_TELEMETRY_ENUM.ANY;
+ break;
+ } else {
+ let re = /^([1-9][0-9]*)x[1-9][0-9]*$/i;
+ let values = re.exec(size);
+ if (values && values.length > 1) {
+ sizesType = SIZES_TELEMETRY_ENUM.DIMENSION;
+ sizeHistogramDimension.add(parseInt(values[1]));
+ } else {
+ sizesType = SIZES_TELEMETRY_ENUM.INVALID;
+ break;
+ }
+ }
+ }
+ } else {
+ sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
+ }
+ sizeHistogramTypes.add(sizesType);
+
+ chromeGlobal.sendAsyncMessage(
+ "Link:SetIcon",
+ {url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
+ iconAdded = true;
+ break;
+ case "search":
+ if (!searchAdded && event.type == "DOMLinkAdded") {
+ var type = link.type && link.type.toLowerCase();
+ type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
+
+ let re = /^(?:https?|ftp):/i;
+ if (type == "application/opensearchdescription+xml" && link.title &&
+ re.test(link.href))
+ {
+ let engine = { title: link.title, href: link.href };
+ chromeGlobal.sendAsyncMessage("Link:AddSearch",
+ {engine: engine,
+ url: link.ownerDocument.documentURI});
+ searchAdded = true;
+ }
+ }
+ break;
+ }
+ }
+ },
+
+ getLinkIconURI: function(aLink) {
+ let targetDoc = aLink.ownerDocument;
+ var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet);
+ try {
+ uri.userPass = "";
+ } catch (e) {
+ // some URIs are immutable
+ }
+ return uri;
+ },
+};
diff --git a/browser/modules/ContentObservers.jsm b/browser/modules/ContentObservers.jsm
new file mode 100644
index 000000000..9d627ddc2
--- /dev/null
+++ b/browser/modules/ContentObservers.jsm
@@ -0,0 +1,55 @@
+/* -*- 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 module is for small observers that we want to register once per content
+ * process, usually in order to forward content-based observer service notifications
+ * to the chrome process through message passing. Using a JSM avoids having them
+ * in content.js and thereby registering N observers for N open tabs, which is bad
+ * for perf.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var gEMEUIObserver = function(subject, topic, data) {
+ let win = subject.top;
+ let mm = getMessageManagerForWindow(win);
+ if (mm) {
+ mm.sendAsyncMessage("EMEVideo:ContentMediaKeysRequest", data);
+ }
+};
+
+var gDecoderDoctorObserver = function(subject, topic, data) {
+ let win = subject.top;
+ let mm = getMessageManagerForWindow(win);
+ if (mm) {
+ mm.sendAsyncMessage("DecoderDoctor:Notification", data);
+ }
+};
+
+function getMessageManagerForWindow(aContentWindow) {
+ let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor);
+ try {
+ // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
+ return ir.getInterface(Ci.nsIContentFrameMessageManager);
+ } catch (e) {
+ if (e.result == Cr.NS_NOINTERFACE) {
+ return null;
+ }
+ throw e;
+ }
+}
+
+Services.obs.addObserver(gEMEUIObserver, "mediakeys-request", false);
+Services.obs.addObserver(gDecoderDoctorObserver, "decoder-doctor-notification", false);
diff --git a/browser/modules/ContentSearch.jsm b/browser/modules/ContentSearch.jsm
new file mode 100644
index 000000000..91b0b9ac8
--- /dev/null
+++ b/browser/modules/ContentSearch.jsm
@@ -0,0 +1,566 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 XPCOMUtils, Services, Task, Promise, SearchSuggestionController, FormHistory, PrivateBrowsingUtils */
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "ContentSearch",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
+ "resource://gre/modules/SearchSuggestionController.jsm");
+
+const INBOUND_MESSAGE = "ContentSearch";
+const OUTBOUND_MESSAGE = INBOUND_MESSAGE;
+const MAX_LOCAL_SUGGESTIONS = 3;
+const MAX_SUGGESTIONS = 6;
+
+/**
+ * ContentSearch receives messages named INBOUND_MESSAGE and sends messages
+ * named OUTBOUND_MESSAGE. The data of each message is expected to look like
+ * { type, data }. type is the message's type (or subtype if you consider the
+ * type of the message itself to be INBOUND_MESSAGE), and data is data that is
+ * specific to the type.
+ *
+ * Inbound messages have the following types:
+ *
+ * AddFormHistoryEntry
+ * Adds an entry to the search form history.
+ * data: the entry, a string
+ * GetSuggestions
+ * Retrieves an array of search suggestions given a search string.
+ * data: { engineName, searchString, [remoteTimeout] }
+ * GetState
+ * Retrieves the current search engine state.
+ * data: null
+ * GetStrings
+ * Retrieves localized search UI strings.
+ * data: null
+ * ManageEngines
+ * Opens the search engine management window.
+ * data: null
+ * RemoveFormHistoryEntry
+ * Removes an entry from the search form history.
+ * data: the entry, a string
+ * Search
+ * Performs a search.
+ * Any GetSuggestions messages in the queue from the same target will be
+ * cancelled.
+ * data: { engineName, searchString, healthReportKey, searchPurpose }
+ * SetCurrentEngine
+ * Sets the current engine.
+ * data: the name of the engine
+ * SpeculativeConnect
+ * Speculatively connects to an engine.
+ * data: the name of the engine
+ *
+ * Outbound messages have the following types:
+ *
+ * CurrentEngine
+ * Broadcast when the current engine changes.
+ * data: see _currentEngineObj
+ * CurrentState
+ * Broadcast when the current search state changes.
+ * data: see currentStateObj
+ * State
+ * Sent in reply to GetState.
+ * data: see currentStateObj
+ * Strings
+ * Sent in reply to GetStrings
+ * data: Object containing string names and values for the current locale.
+ * Suggestions
+ * Sent in reply to GetSuggestions.
+ * data: see _onMessageGetSuggestions
+ * SuggestionsCancelled
+ * Sent in reply to GetSuggestions when pending GetSuggestions events are
+ * cancelled.
+ * data: null
+ */
+
+this.ContentSearch = {
+
+ // Inbound events are queued and processed in FIFO order instead of handling
+ // them immediately, which would result in non-FIFO responses due to the
+ // asynchrononicity added by converting image data URIs to ArrayBuffers.
+ _eventQueue: [],
+ _currentEventPromise: null,
+
+ // This is used to handle search suggestions. It maps xul:browsers to objects
+ // { controller, previousFormHistoryResult }. See _onMessageGetSuggestions.
+ _suggestionMap: new WeakMap(),
+
+ // Resolved when we finish shutting down.
+ _destroyedPromise: null,
+
+ // The current controller and browser in _onMessageGetSuggestions. Allows
+ // fetch cancellation from _cancelSuggestions.
+ _currentSuggestion: null,
+
+ init: function () {
+ Cc["@mozilla.org/globalmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager).
+ addMessageListener(INBOUND_MESSAGE, this);
+ Services.obs.addObserver(this, "browser-search-engine-modified", false);
+ Services.obs.addObserver(this, "shutdown-leaks-before-check", false);
+ Services.prefs.addObserver("browser.search.hiddenOneOffs", this, false);
+ this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
+ },
+
+ get searchSuggestionUIStrings() {
+ if (this._searchSuggestionUIStrings) {
+ return this._searchSuggestionUIStrings;
+ }
+ this._searchSuggestionUIStrings = {};
+ let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
+ let stringNames = ["searchHeader", "searchPlaceholder", "searchForSomethingWith",
+ "searchWithHeader", "searchSettings"];
+
+ for (let name of stringNames) {
+ this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name);
+ }
+ return this._searchSuggestionUIStrings;
+ },
+
+ destroy: function () {
+ if (this._destroyedPromise) {
+ return this._destroyedPromise;
+ }
+
+ Cc["@mozilla.org/globalmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager).
+ removeMessageListener(INBOUND_MESSAGE, this);
+ Services.obs.removeObserver(this, "browser-search-engine-modified");
+ Services.obs.removeObserver(this, "shutdown-leaks-before-check");
+
+ this._eventQueue.length = 0;
+ this._destroyedPromise = Promise.resolve(this._currentEventPromise);
+ return this._destroyedPromise;
+ },
+
+ /**
+ * Focuses the search input in the page with the given message manager.
+ * @param messageManager
+ * The MessageManager object of the selected browser.
+ */
+ focusInput: function (messageManager) {
+ messageManager.sendAsyncMessage(OUTBOUND_MESSAGE, {
+ type: "FocusInput"
+ });
+ },
+
+ receiveMessage: function (msg) {
+ // Add a temporary event handler that exists only while the message is in
+ // the event queue. If the message's source docshell changes browsers in
+ // the meantime, then we need to update msg.target. event.detail will be
+ // the docshell's new parent <xul:browser> element.
+ msg.handleEvent = event => {
+ let browserData = this._suggestionMap.get(msg.target);
+ if (browserData) {
+ this._suggestionMap.delete(msg.target);
+ this._suggestionMap.set(event.detail, browserData);
+ }
+ msg.target.removeEventListener("SwapDocShells", msg, true);
+ msg.target = event.detail;
+ msg.target.addEventListener("SwapDocShells", msg, true);
+ };
+ msg.target.addEventListener("SwapDocShells", msg, true);
+
+ // Search requests cause cancellation of all Suggestion requests from the
+ // same browser.
+ if (msg.data.type === "Search") {
+ this._cancelSuggestions(msg);
+ }
+
+ this._eventQueue.push({
+ type: "Message",
+ data: msg,
+ });
+ this._processEventQueue();
+ },
+
+ observe: function (subj, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ case "browser-search-engine-modified":
+ this._eventQueue.push({
+ type: "Observe",
+ data: data,
+ });
+ this._processEventQueue();
+ break;
+ case "shutdown-leaks-before-check":
+ subj.wrappedJSObject.client.addBlocker(
+ "ContentSearch: Wait until the service is destroyed", () => this.destroy());
+ break;
+ }
+ },
+
+ removeFormHistoryEntry: function (msg, entry) {
+ let browserData = this._suggestionDataForBrowser(msg.target);
+ if (browserData && browserData.previousFormHistoryResult) {
+ let { previousFormHistoryResult } = browserData;
+ for (let i = 0; i < previousFormHistoryResult.matchCount; i++) {
+ if (previousFormHistoryResult.getValueAt(i) === entry) {
+ previousFormHistoryResult.removeValueAt(i, true);
+ break;
+ }
+ }
+ }
+ },
+
+ performSearch: function (msg, data) {
+ this._ensureDataHasProperties(data, [
+ "engineName",
+ "searchString",
+ "healthReportKey",
+ "searchPurpose",
+ ]);
+ let engine = Services.search.getEngineByName(data.engineName);
+ let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
+ let browser = msg.target;
+ let win = browser.ownerGlobal;
+ if (!win) {
+ // The browser may have been closed between the time its content sent the
+ // message and the time we handle it.
+ return;
+ }
+ let where = win.whereToOpenLink(data.originalEvent);
+
+ // There is a chance that by the time we receive the search message, the user
+ // has switched away from the tab that triggered the search. If, based on the
+ // event, we need to load the search in the same tab that triggered it (i.e.
+ // where === "current"), openUILinkIn will not work because that tab is no
+ // longer the current one. For this case we manually load the URI.
+ if (where === "current") {
+ browser.loadURIWithFlags(submission.uri.spec,
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null,
+ submission.postData);
+ } else {
+ let params = {
+ postData: submission.postData,
+ inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
+ };
+ win.openUILinkIn(submission.uri.spec, where, params);
+ }
+ win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey,
+ { selection: data.selection });
+ return;
+ },
+
+ getSuggestions: Task.async(function* (engineName, searchString, browser, remoteTimeout=null) {
+ let engine = Services.search.getEngineByName(engineName);
+ if (!engine) {
+ throw new Error("Unknown engine name: " + engineName);
+ }
+
+ let browserData = this._suggestionDataForBrowser(browser, true);
+ let { controller } = browserData;
+ let ok = SearchSuggestionController.engineOffersSuggestions(engine);
+ controller.maxLocalResults = ok ? MAX_LOCAL_SUGGESTIONS : MAX_SUGGESTIONS;
+ controller.maxRemoteResults = ok ? MAX_SUGGESTIONS : 0;
+ controller.remoteTimeout = remoteTimeout || undefined;
+ let priv = PrivateBrowsingUtils.isBrowserPrivate(browser);
+ // fetch() rejects its promise if there's a pending request, but since we
+ // process our event queue serially, there's never a pending request.
+ this._currentSuggestion = { controller: controller, target: browser };
+ let suggestions = yield controller.fetch(searchString, priv, engine);
+ this._currentSuggestion = null;
+
+ // suggestions will be null if the request was cancelled
+ let result = {};
+ if (!suggestions) {
+ return result;
+ }
+
+ // Keep the form history result so RemoveFormHistoryEntry can remove entries
+ // from it. Keeping only one result isn't foolproof because the client may
+ // try to remove an entry from one set of suggestions after it has requested
+ // more but before it's received them. In that case, the entry may not
+ // appear in the new suggestions. But that should happen rarely.
+ browserData.previousFormHistoryResult = suggestions.formHistoryResult;
+ result = {
+ engineName,
+ term: suggestions.term,
+ local: suggestions.local,
+ remote: suggestions.remote,
+ };
+ return result;
+ }),
+
+ addFormHistoryEntry: Task.async(function* (browser, entry="") {
+ let isPrivate = false;
+ try {
+ // isBrowserPrivate assumes that the passed-in browser has all the normal
+ // properties, which won't be true if the browser has been destroyed.
+ // That may be the case here due to the asynchronous nature of messaging.
+ isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser.target);
+ } catch (err) {
+ return false;
+ }
+ if (isPrivate || entry === "") {
+ return false;
+ }
+ let browserData = this._suggestionDataForBrowser(browser.target, true);
+ FormHistory.update({
+ op: "bump",
+ fieldname: browserData.controller.formHistoryParam,
+ value: entry,
+ }, {
+ handleCompletion: () => {},
+ handleError: err => {
+ Cu.reportError("Error adding form history entry: " + err);
+ },
+ });
+ return true;
+ }),
+
+ currentStateObj: Task.async(function* (uriFlag=false) {
+ let state = {
+ engines: [],
+ currentEngine: yield this._currentEngineObj(),
+ };
+ if (uriFlag) {
+ state.currentEngine.iconBuffer = Services.search.currentEngine.getIconURLBySize(16, 16);
+ }
+ let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
+ let hiddenList = pref ? pref.split(",") : [];
+ for (let engine of Services.search.getVisibleEngines()) {
+ let uri = engine.getIconURLBySize(16, 16);
+ let iconBuffer = uri;
+ if (!uriFlag) {
+ iconBuffer = yield this._arrayBufferFromDataURI(uri);
+ }
+ state.engines.push({
+ name: engine.name,
+ iconBuffer,
+ hidden: hiddenList.indexOf(engine.name) !== -1,
+ });
+ }
+ return state;
+ }),
+
+ _processEventQueue: function () {
+ if (this._currentEventPromise || !this._eventQueue.length) {
+ return;
+ }
+
+ let event = this._eventQueue.shift();
+
+ this._currentEventPromise = Task.spawn(function* () {
+ try {
+ yield this["_on" + event.type](event.data);
+ } catch (err) {
+ Cu.reportError(err);
+ } finally {
+ this._currentEventPromise = null;
+ this._processEventQueue();
+ }
+ }.bind(this));
+ },
+
+ _cancelSuggestions: function (msg) {
+ let cancelled = false;
+ // cancel active suggestion request
+ if (this._currentSuggestion && this._currentSuggestion.target === msg.target) {
+ this._currentSuggestion.controller.stop();
+ cancelled = true;
+ }
+ // cancel queued suggestion requests
+ for (let i = 0; i < this._eventQueue.length; i++) {
+ let m = this._eventQueue[i].data;
+ if (msg.target === m.target && m.data.type === "GetSuggestions") {
+ this._eventQueue.splice(i, 1);
+ cancelled = true;
+ i--;
+ }
+ }
+ if (cancelled) {
+ this._reply(msg, "SuggestionsCancelled");
+ }
+ },
+
+ _onMessage: Task.async(function* (msg) {
+ let methodName = "_onMessage" + msg.data.type;
+ if (methodName in this) {
+ yield this._initService();
+ yield this[methodName](msg, msg.data.data);
+ if (!Cu.isDeadWrapper(msg.target)) {
+ msg.target.removeEventListener("SwapDocShells", msg, true);
+ }
+ }
+ }),
+
+ _onMessageGetState: function (msg, data) {
+ return this.currentStateObj().then(state => {
+ this._reply(msg, "State", state);
+ });
+ },
+
+ _onMessageGetStrings: function (msg, data) {
+ this._reply(msg, "Strings", this.searchSuggestionUIStrings);
+ },
+
+ _onMessageSearch: function (msg, data) {
+ this.performSearch(msg, data);
+ },
+
+ _onMessageSetCurrentEngine: function (msg, data) {
+ Services.search.currentEngine = Services.search.getEngineByName(data);
+ },
+
+ _onMessageManageEngines: function (msg, data) {
+ let browserWin = msg.target.ownerGlobal;
+ browserWin.openPreferences("paneSearch");
+ },
+
+ _onMessageGetSuggestions: Task.async(function* (msg, data) {
+ this._ensureDataHasProperties(data, [
+ "engineName",
+ "searchString",
+ ]);
+ let {engineName, searchString} = data;
+ let suggestions = yield this.getSuggestions(engineName, searchString, msg.target);
+
+ this._reply(msg, "Suggestions", {
+ engineName: data.engineName,
+ searchString: suggestions.term,
+ formHistory: suggestions.local,
+ remote: suggestions.remote,
+ });
+ }),
+
+ _onMessageAddFormHistoryEntry: Task.async(function* (msg, entry) {
+ yield this.addFormHistoryEntry(msg, entry);
+ }),
+
+ _onMessageRemoveFormHistoryEntry: function (msg, entry) {
+ this.removeFormHistoryEntry(msg, entry);
+ },
+
+ _onMessageSpeculativeConnect: function (msg, engineName) {
+ let engine = Services.search.getEngineByName(engineName);
+ if (!engine) {
+ throw new Error("Unknown engine name: " + engineName);
+ }
+ if (msg.target.contentWindow) {
+ engine.speculativeConnect({
+ window: msg.target.contentWindow,
+ });
+ }
+ },
+
+ _onObserve: Task.async(function* (data) {
+ if (data === "engine-current") {
+ let engine = yield this._currentEngineObj();
+ this._broadcast("CurrentEngine", engine);
+ }
+ else if (data !== "engine-default") {
+ // engine-default is always sent with engine-current and isn't otherwise
+ // relevant to content searches.
+ let state = yield this.currentStateObj();
+ this._broadcast("CurrentState", state);
+ }
+ }),
+
+ _suggestionDataForBrowser: function (browser, create=false) {
+ let data = this._suggestionMap.get(browser);
+ if (!data && create) {
+ // Since one SearchSuggestionController instance is meant to be used per
+ // autocomplete widget, this means that we assume each xul:browser has at
+ // most one such widget.
+ data = {
+ controller: new SearchSuggestionController(),
+ };
+ this._suggestionMap.set(browser, data);
+ }
+ return data;
+ },
+
+ _reply: function (msg, type, data) {
+ // We reply asyncly to messages, and by the time we reply the browser we're
+ // responding to may have been destroyed. messageManager is null then.
+ if (!Cu.isDeadWrapper(msg.target) && msg.target.messageManager) {
+ msg.target.messageManager.sendAsyncMessage(...this._msgArgs(type, data));
+ }
+ },
+
+ _broadcast: function (type, data) {
+ Cc["@mozilla.org/globalmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager).
+ broadcastAsyncMessage(...this._msgArgs(type, data));
+ },
+
+ _msgArgs: function (type, data) {
+ return [OUTBOUND_MESSAGE, {
+ type: type,
+ data: data,
+ }];
+ },
+
+ _currentEngineObj: Task.async(function* () {
+ let engine = Services.search.currentEngine;
+ let favicon = engine.getIconURLBySize(16, 16);
+ let placeholder = this._stringBundle.formatStringFromName(
+ "searchWithEngine", [engine.name], 1);
+ let obj = {
+ name: engine.name,
+ placeholder: placeholder,
+ iconBuffer: yield this._arrayBufferFromDataURI(favicon),
+ };
+ return obj;
+ }),
+
+ _arrayBufferFromDataURI: function (uri) {
+ if (!uri) {
+ return Promise.resolve(null);
+ }
+ let deferred = Promise.defer();
+ let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open("GET", uri, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = () => {
+ deferred.resolve(xhr.response);
+ };
+ xhr.onerror = xhr.onabort = xhr.ontimeout = () => {
+ deferred.resolve(null);
+ };
+ try {
+ // This throws if the URI is erroneously encoded.
+ xhr.send();
+ }
+ catch (err) {
+ return Promise.resolve(null);
+ }
+ return deferred.promise;
+ },
+
+ _ensureDataHasProperties: function (data, requiredProperties) {
+ for (let prop of requiredProperties) {
+ if (!(prop in data)) {
+ throw new Error("Message data missing required property: " + prop);
+ }
+ }
+ },
+
+ _initService: function () {
+ if (!this._initServicePromise) {
+ let deferred = Promise.defer();
+ this._initServicePromise = deferred.promise;
+ Services.search.init(() => deferred.resolve());
+ }
+ return this._initServicePromise;
+ },
+};
diff --git a/browser/modules/ContentWebRTC.jsm b/browser/modules/ContentWebRTC.jsm
new file mode 100644
index 000000000..bfb98a868
--- /dev/null
+++ b/browser/modules/ContentWebRTC.jsm
@@ -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";
+
+const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = [ "ContentWebRTC" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
+ "@mozilla.org/mediaManagerService;1",
+ "nsIMediaManagerService");
+
+const kBrowserURL = "chrome://browser/content/browser.xul";
+
+this.ContentWebRTC = {
+ _initialized: false,
+
+ init: function() {
+ if (this._initialized)
+ return;
+
+ this._initialized = true;
+ Services.obs.addObserver(handleGUMRequest, "getUserMedia:request", false);
+ Services.obs.addObserver(handlePCRequest, "PeerConnection:request", false);
+ Services.obs.addObserver(updateIndicators, "recording-device-events", false);
+ Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
+
+ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT)
+ Services.obs.addObserver(processShutdown, "content-child-shutdown", false);
+ },
+
+ uninit: function() {
+ Services.obs.removeObserver(handleGUMRequest, "getUserMedia:request");
+ Services.obs.removeObserver(handlePCRequest, "PeerConnection:request");
+ Services.obs.removeObserver(updateIndicators, "recording-device-events");
+ Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
+
+ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT)
+ Services.obs.removeObserver(processShutdown, "content-child-shutdown");
+
+ this._initialized = false;
+ },
+
+ // Called only for 'unload' to remove pending gUM prompts in reloaded frames.
+ handleEvent: function(aEvent) {
+ let contentWindow = aEvent.target.defaultView;
+ let mm = getMessageManagerForWindow(contentWindow);
+ for (let key of contentWindow.pendingGetUserMediaRequests.keys()) {
+ mm.sendAsyncMessage("webrtc:CancelRequest", key);
+ }
+ for (let key of contentWindow.pendingPeerConnectionRequests.keys()) {
+ mm.sendAsyncMessage("rtcpeer:CancelRequest", key);
+ }
+ },
+
+ receiveMessage: function(aMessage) {
+ switch (aMessage.name) {
+ case "rtcpeer:Allow":
+ case "rtcpeer:Deny": {
+ let callID = aMessage.data.callID;
+ let contentWindow = Services.wm.getOuterWindowWithId(aMessage.data.windowID);
+ forgetPCRequest(contentWindow, callID);
+ let topic = (aMessage.name == "rtcpeer:Allow") ? "PeerConnection:response:allow" :
+ "PeerConnection:response:deny";
+ Services.obs.notifyObservers(null, topic, callID);
+ break;
+ }
+ case "webrtc:Allow": {
+ let callID = aMessage.data.callID;
+ let contentWindow = Services.wm.getOuterWindowWithId(aMessage.data.windowID);
+ let devices = contentWindow.pendingGetUserMediaRequests.get(callID);
+ forgetGUMRequest(contentWindow, callID);
+
+ let allowedDevices = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ for (let deviceIndex of aMessage.data.devices)
+ allowedDevices.appendElement(devices[deviceIndex], /* weak =*/ false);
+
+ Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", callID);
+ break;
+ }
+ case "webrtc:Deny":
+ denyGUMRequest(aMessage.data);
+ break;
+ case "webrtc:StopSharing":
+ Services.obs.notifyObservers(null, "getUserMedia:revoke", aMessage.data);
+ break;
+ }
+ }
+};
+
+function handlePCRequest(aSubject, aTopic, aData) {
+ let { windowID, innerWindowID, callID, isSecure } = aSubject;
+ let contentWindow = Services.wm.getOuterWindowWithId(windowID);
+
+ let mm = getMessageManagerForWindow(contentWindow);
+ if (!mm) {
+ // Workaround for Bug 1207784. To use WebRTC, add-ons right now use
+ // hiddenWindow.mozRTCPeerConnection which is only privileged on OSX. Other
+ // platforms end up here without a message manager.
+ // TODO: Remove once there's a better way (1215591).
+
+ // Skip permission check in the absence of a message manager.
+ Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID);
+ return;
+ }
+
+ if (!contentWindow.pendingPeerConnectionRequests) {
+ setupPendingListsInitially(contentWindow);
+ }
+ contentWindow.pendingPeerConnectionRequests.add(callID);
+
+ let request = {
+ windowID: windowID,
+ innerWindowID: innerWindowID,
+ callID: callID,
+ documentURI: contentWindow.document.documentURI,
+ secure: isSecure,
+ };
+ mm.sendAsyncMessage("rtcpeer:Request", request);
+}
+
+function handleGUMRequest(aSubject, aTopic, aData) {
+ let constraints = aSubject.getConstraints();
+ let secure = aSubject.isSecure;
+ let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);
+
+ contentWindow.navigator.mozGetUserMediaDevices(
+ constraints,
+ function (devices) {
+ // If the window has been closed while we were waiting for the list of
+ // devices, there's nothing to do in the callback anymore.
+ if (contentWindow.closed)
+ return;
+
+ prompt(contentWindow, aSubject.windowID, aSubject.callID,
+ constraints, devices, secure);
+ },
+ function (error) {
+ // bug 827146 -- In the future, the UI should catch NotFoundError
+ // and allow the user to plug in a device, instead of immediately failing.
+ denyGUMRequest({callID: aSubject.callID}, error);
+ },
+ aSubject.innerWindowID,
+ aSubject.callID);
+}
+
+function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSecure) {
+ let audioDevices = [];
+ let videoDevices = [];
+ let devices = [];
+
+ // MediaStreamConstraints defines video as 'boolean or MediaTrackConstraints'.
+ let video = aConstraints.video || aConstraints.picture;
+ let audio = aConstraints.audio;
+ let sharingScreen = video && typeof(video) != "boolean" &&
+ video.mediaSource != "camera";
+ let sharingAudio = audio && typeof(audio) != "boolean" &&
+ audio.mediaSource != "microphone";
+ for (let device of aDevices) {
+ device = device.QueryInterface(Ci.nsIMediaDevice);
+ switch (device.type) {
+ case "audio":
+ // Check that if we got a microphone, we have not requested an audio
+ // capture, and if we have requested an audio capture, we are not
+ // getting a microphone instead.
+ if (audio && (device.mediaSource == "microphone") != sharingAudio) {
+ audioDevices.push({name: device.name, deviceIndex: devices.length,
+ id: device.rawId, mediaSource: device.mediaSource});
+ devices.push(device);
+ }
+ break;
+ case "video":
+ // Verify that if we got a camera, we haven't requested a screen share,
+ // or that if we requested a screen share we aren't getting a camera.
+ if (video && (device.mediaSource == "camera") != sharingScreen) {
+ let deviceObject = {name: device.name, deviceIndex: devices.length,
+ id: device.rawId, mediaSource: device.mediaSource};
+ if (device.scary)
+ deviceObject.scary = true;
+ videoDevices.push(deviceObject);
+ devices.push(device);
+ }
+ break;
+ }
+ }
+
+ let requestTypes = [];
+ if (videoDevices.length)
+ requestTypes.push(sharingScreen ? "Screen" : "Camera");
+ if (audioDevices.length)
+ requestTypes.push(sharingAudio ? "AudioCapture" : "Microphone");
+
+ if (!requestTypes.length) {
+ denyGUMRequest({callID: aCallID}, "NotFoundError");
+ return;
+ }
+
+ if (!aContentWindow.pendingGetUserMediaRequests) {
+ setupPendingListsInitially(aContentWindow);
+ }
+ aContentWindow.pendingGetUserMediaRequests.set(aCallID, devices);
+
+ let request = {
+ callID: aCallID,
+ windowID: aWindowID,
+ documentURI: aContentWindow.document.documentURI,
+ secure: aSecure,
+ requestTypes: requestTypes,
+ sharingScreen: sharingScreen,
+ sharingAudio: sharingAudio,
+ audioDevices: audioDevices,
+ videoDevices: videoDevices
+ };
+
+ let mm = getMessageManagerForWindow(aContentWindow);
+ mm.sendAsyncMessage("webrtc:Request", request);
+}
+
+function denyGUMRequest(aData, aError) {
+ let msg = null;
+ if (aError) {
+ msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ msg.data = aError;
+ }
+ Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aData.callID);
+
+ if (!aData.windowID)
+ return;
+ let contentWindow = Services.wm.getOuterWindowWithId(aData.windowID);
+ if (contentWindow.pendingGetUserMediaRequests)
+ forgetGUMRequest(contentWindow, aData.callID);
+}
+
+function forgetGUMRequest(aContentWindow, aCallID) {
+ aContentWindow.pendingGetUserMediaRequests.delete(aCallID);
+ forgetPendingListsEventually(aContentWindow);
+}
+
+function forgetPCRequest(aContentWindow, aCallID) {
+ aContentWindow.pendingPeerConnectionRequests.delete(aCallID);
+ forgetPendingListsEventually(aContentWindow);
+}
+
+function setupPendingListsInitially(aContentWindow) {
+ if (aContentWindow.pendingGetUserMediaRequests) {
+ return;
+ }
+ aContentWindow.pendingGetUserMediaRequests = new Map();
+ aContentWindow.pendingPeerConnectionRequests = new Set();
+ aContentWindow.addEventListener("unload", ContentWebRTC);
+}
+
+function forgetPendingListsEventually(aContentWindow) {
+ if (aContentWindow.pendingGetUserMediaRequests.size ||
+ aContentWindow.pendingPeerConnectionRequests.size) {
+ return;
+ }
+ aContentWindow.pendingGetUserMediaRequests = null;
+ aContentWindow.pendingPeerConnectionRequests = null;
+ aContentWindow.removeEventListener("unload", ContentWebRTC);
+}
+
+function updateIndicators(aSubject, aTopic, aData) {
+ if (aSubject instanceof Ci.nsIPropertyBag &&
+ aSubject.getProperty("requestURL") == kBrowserURL) {
+ // Ignore notifications caused by the browser UI showing previews.
+ return;
+ }
+
+ let contentWindowArray = MediaManagerService.activeMediaCaptureWindows;
+ let count = contentWindowArray.length;
+
+ let state = {
+ showGlobalIndicator: count > 0,
+ showCameraIndicator: false,
+ showMicrophoneIndicator: false,
+ showScreenSharingIndicator: ""
+ };
+
+ let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageSender);
+ cpmm.sendAsyncMessage("webrtc:UpdatingIndicators");
+
+ // If several iframes in the same page use media streams, it's possible to
+ // have the same top level window several times. We use a Set to avoid
+ // sending duplicate notifications.
+ let contentWindows = new Set();
+ for (let i = 0; i < count; ++i) {
+ contentWindows.add(contentWindowArray.queryElementAt(i, Ci.nsISupports).top);
+ }
+
+ for (let contentWindow of contentWindows) {
+ if (contentWindow.document.documentURI == kBrowserURL) {
+ // There may be a preview shown at the same time as other streams.
+ continue;
+ }
+
+ let tabState = getTabStateForContentWindow(contentWindow);
+ if (tabState.camera)
+ state.showCameraIndicator = true;
+ if (tabState.microphone)
+ state.showMicrophoneIndicator = true;
+ if (tabState.screen) {
+ if (tabState.screen == "Screen") {
+ state.showScreenSharingIndicator = "Screen";
+ }
+ else if (tabState.screen == "Window") {
+ if (state.showScreenSharingIndicator != "Screen")
+ state.showScreenSharingIndicator = "Window";
+ }
+ else if (tabState.screen == "Application") {
+ if (!state.showScreenSharingIndicator)
+ state.showScreenSharingIndicator = "Application";
+ }
+ else if (tabState.screen == "Browser") {
+ if (!state.showScreenSharingIndicator)
+ state.showScreenSharingIndicator = "Browser";
+ }
+ }
+ let mm = getMessageManagerForWindow(contentWindow);
+ mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
+ }
+
+ cpmm.sendAsyncMessage("webrtc:UpdateGlobalIndicators", state);
+}
+
+function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
+ let contentWindow = Services.wm.getOuterWindowWithId(aData).top;
+ if (contentWindow.document.documentURI == kBrowserURL) {
+ // Ignore notifications caused by the browser UI showing previews.
+ return;
+ }
+
+ let tabState = getTabStateForContentWindow(contentWindow);
+ if (!tabState.camera && !tabState.microphone && !tabState.screen)
+ tabState = {windowId: tabState.windowId};
+
+ let mm = getMessageManagerForWindow(contentWindow);
+ if (mm)
+ mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
+}
+
+function getTabStateForContentWindow(aContentWindow) {
+ let camera = {}, microphone = {}, screen = {}, window = {}, app = {}, browser = {};
+ MediaManagerService.mediaCaptureWindowState(aContentWindow, camera, microphone,
+ screen, window, app, browser);
+ let tabState = {camera: camera.value, microphone: microphone.value};
+ if (screen.value)
+ tabState.screen = "Screen";
+ else if (window.value)
+ tabState.screen = "Window";
+ else if (app.value)
+ tabState.screen = "Application";
+ else if (browser.value)
+ tabState.screen = "Browser";
+
+ tabState.windowId = getInnerWindowIDForWindow(aContentWindow);
+ tabState.documentURI = aContentWindow.document.documentURI;
+
+ return tabState;
+}
+
+function getInnerWindowIDForWindow(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+}
+
+function getMessageManagerForWindow(aContentWindow) {
+ let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor);
+ try {
+ // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
+ return ir.getInterface(Ci.nsIContentFrameMessageManager);
+ } catch (e) {
+ if (e.result == Cr.NS_NOINTERFACE) {
+ return null;
+ }
+ throw e;
+ }
+}
+
+function processShutdown() {
+ ContentWebRTC.uninit();
+}
diff --git a/browser/modules/DirectoryLinksProvider.jsm b/browser/modules/DirectoryLinksProvider.jsm
new file mode 100644
index 000000000..117564099
--- /dev/null
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -0,0 +1,1255 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["DirectoryLinksProvider"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+const ParserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
+
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
+ "resource://gre/modules/NewTabUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm")
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "eTLD",
+ "@mozilla.org/network/effective-tld-service;1",
+ "nsIEffectiveTLDService");
+XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
+ return new TextDecoder();
+});
+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;
+});
+
+
+// The filename where directory links are stored locally
+const DIRECTORY_LINKS_FILE = "directoryLinks.json";
+const DIRECTORY_LINKS_TYPE = "application/json";
+
+// The preference that tells whether to match the OS locale
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+
+// The preference that tells what locale the user selected
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+
+// The preference that tells where to obtain directory links
+const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
+
+// The preference that tells where to send click/view pings
+const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
+
+// The preference that tells if newtab is enhanced
+const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
+
+// Only allow link urls that are http(s)
+const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]);
+
+// Only allow link image urls that are https or data
+const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]);
+
+// Only allow urls to Mozilla's CDN or empty (for data URIs)
+const ALLOWED_URL_BASE = new Set(["mozilla.net", ""]);
+
+// The frecency of a directory link
+const DIRECTORY_FRECENCY = 1000;
+
+// The frecency of a suggested link
+const SUGGESTED_FRECENCY = Infinity;
+
+// The filename where frequency cap data stored locally
+const FREQUENCY_CAP_FILE = "frequencyCap.json";
+
+// Default settings for daily and total frequency caps
+const DEFAULT_DAILY_FREQUENCY_CAP = 3;
+const DEFAULT_TOTAL_FREQUENCY_CAP = 10;
+
+// Default timeDelta to prune unused frequency cap objects
+// currently set to 10 days in milliseconds
+const DEFAULT_PRUNE_TIME_DELTA = 10*24*60*60*1000;
+
+// The min number of visible (not blocked) history tiles to have before showing suggested tiles
+const MIN_VISIBLE_HISTORY_TILES = 8;
+
+// The max number of visible (not blocked) history tiles to test for inadjacency
+const MAX_VISIBLE_HISTORY_TILES = 15;
+
+// Allowed ping actions remotely stored as columns: case-insensitive [a-z0-9_]
+const PING_ACTIONS = ["block", "click", "pin", "sponsored", "sponsored_link", "unpin", "view"];
+
+// Location of inadjacent sites json
+const INADJACENCY_SOURCE = "chrome://browser/content/newtab/newTab.inadjacent.json";
+
+// Fake URL to keep track of last block of a suggested tile in the frequency cap object
+const FAKE_SUGGESTED_BLOCK_URL = "ignore://suggested_block";
+
+// Time before suggested tile is allowed to play again after block - default to 1 day
+const AFTER_SUGGESTED_BLOCK_DECAY_TIME = 24*60*60*1000;
+
+/**
+ * Singleton that serves as the provider of directory links.
+ * Directory links are a hard-coded set of links shown if a user's link
+ * inventory is empty.
+ */
+var DirectoryLinksProvider = {
+
+ __linksURL: null,
+
+ _observers: new Set(),
+
+ // links download deferred, resolved upon download completion
+ _downloadDeferred: null,
+
+ // download default interval is 24 hours in milliseconds
+ _downloadIntervalMS: 86400000,
+
+ /**
+ * A mapping from eTLD+1 to an enhanced link objects
+ */
+ _enhancedLinks: new Map(),
+
+ /**
+ * A mapping from site to a list of suggested link objects
+ */
+ _suggestedLinks: new Map(),
+
+ /**
+ * Frequency Cap object - maintains daily and total tile counts, and frequency cap settings
+ */
+ _frequencyCaps: {},
+
+ /**
+ * A set of top sites that we can provide suggested links for
+ */
+ _topSitesWithSuggestedLinks: new Set(),
+
+ /**
+ * lookup Set of inadjacent domains
+ */
+ _inadjacentSites: new Set(),
+
+ /**
+ * This flag is set if there is a suggested tile configured to avoid
+ * inadjacent sites in new tab
+ */
+ _avoidInadjacentSites: false,
+
+ /**
+ * This flag is set if _avoidInadjacentSites is true and there is
+ * an inadjacent site in the new tab
+ */
+ _newTabHasInadjacentSite: false,
+
+ get _observedPrefs() {
+ return Object.freeze({
+ enhanced: PREF_NEWTAB_ENHANCED,
+ linksURL: PREF_DIRECTORY_SOURCE,
+ matchOSLocale: PREF_MATCH_OS_LOCALE,
+ prefSelectedLocale: PREF_SELECTED_LOCALE,
+ });
+ },
+
+ get _linksURL() {
+ if (!this.__linksURL) {
+ try {
+ this.__linksURL = Services.prefs.getCharPref(this._observedPrefs["linksURL"]);
+ this.__linksURLModified = Services.prefs.prefHasUserValue(this._observedPrefs["linksURL"]);
+ }
+ catch (e) {
+ Cu.reportError("Error fetching directory links url from prefs: " + e);
+ }
+ }
+ return this.__linksURL;
+ },
+
+ /**
+ * Gets the currently selected locale for display.
+ * @return the selected locale or "en-US" if none is selected
+ */
+ get locale() {
+ let matchOS;
+ try {
+ matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
+ }
+ catch (e) {}
+
+ if (matchOS) {
+ return Services.locale.getLocaleComponentForUserAgent();
+ }
+
+ try {
+ let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
+ Ci.nsIPrefLocalizedString);
+ if (locale) {
+ return locale.data;
+ }
+ }
+ catch (e) {}
+
+ try {
+ return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
+ }
+ catch (e) {}
+
+ return "en-US";
+ },
+
+ /**
+ * Set appropriate default ping behavior controlled by enhanced pref
+ */
+ _setDefaultEnhanced: function DirectoryLinksProvider_setDefaultEnhanced() {
+ if (!Services.prefs.prefHasUserValue(PREF_NEWTAB_ENHANCED)) {
+ let enhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
+ try {
+ // Default to not enhanced if DNT is set to tell websites to not track
+ if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled")) {
+ enhanced = false;
+ }
+ }
+ catch (ex) {}
+ Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, enhanced);
+ }
+ },
+
+ observe: function DirectoryLinksProvider_observe(aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed") {
+ switch (aData) {
+ // Re-set the default in case the user clears the pref
+ case this._observedPrefs.enhanced:
+ this._setDefaultEnhanced();
+ break;
+
+ case this._observedPrefs.linksURL:
+ delete this.__linksURL;
+ // fallthrough
+
+ // Force directory download on changes to fetch related prefs
+ case this._observedPrefs.matchOSLocale:
+ case this._observedPrefs.prefSelectedLocale:
+ this._fetchAndCacheLinksIfNecessary(true);
+ break;
+ }
+ }
+ },
+
+ _addPrefsObserver: function DirectoryLinksProvider_addObserver() {
+ for (let pref in this._observedPrefs) {
+ let prefName = this._observedPrefs[pref];
+ Services.prefs.addObserver(prefName, this, false);
+ }
+ },
+
+ _removePrefsObserver: function DirectoryLinksProvider_removeObserver() {
+ for (let pref in this._observedPrefs) {
+ let prefName = this._observedPrefs[pref];
+ Services.prefs.removeObserver(prefName, this);
+ }
+ },
+
+ _cacheSuggestedLinks: function(link) {
+ // Don't cache links that don't have the expected 'frecent_sites'
+ if (!link.frecent_sites) {
+ return;
+ }
+
+ for (let suggestedSite of link.frecent_sites) {
+ let suggestedMap = this._suggestedLinks.get(suggestedSite) || new Map();
+ suggestedMap.set(link.url, link);
+ this._setupStartEndTime(link);
+ this._suggestedLinks.set(suggestedSite, suggestedMap);
+ }
+ },
+
+ _fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) {
+ // Replace with the same display locale used for selecting links data
+ uri = uri.replace("%LOCALE%", this.locale);
+ uri = uri.replace("%CHANNEL%", UpdateUtils.UpdateChannel);
+
+ return this._downloadJsonData(uri).then(json => {
+ return OS.File.writeAtomic(this._directoryFilePath, json, {tmpPath: this._directoryFilePath + ".tmp"});
+ });
+ },
+
+ /**
+ * Downloads a links with json content
+ * @param download uri
+ * @return promise resolved to json string, "{}" returned if status != 200
+ */
+ _downloadJsonData: function DirectoryLinksProvider__downloadJsonData(uri) {
+ let deferred = Promise.defer();
+ let xmlHttp = this._newXHR();
+
+ xmlHttp.onload = function(aResponse) {
+ let json = this.responseText;
+ if (this.status && this.status != 200) {
+ json = "{}";
+ }
+ deferred.resolve(json);
+ };
+
+ xmlHttp.onerror = function(e) {
+ deferred.reject("Fetching " + uri + " results in error code: " + e.target.status);
+ };
+
+ try {
+ xmlHttp.open("GET", uri);
+ // Override the type so XHR doesn't complain about not well-formed XML
+ xmlHttp.overrideMimeType(DIRECTORY_LINKS_TYPE);
+ // Set the appropriate request type for servers that require correct types
+ xmlHttp.setRequestHeader("Content-Type", DIRECTORY_LINKS_TYPE);
+ xmlHttp.send();
+ } catch (e) {
+ deferred.reject("Error fetching " + uri);
+ Cu.reportError(e);
+ }
+ return deferred.promise;
+ },
+
+ /**
+ * Downloads directory links if needed
+ * @return promise resolved immediately if no download needed, or upon completion
+ */
+ _fetchAndCacheLinksIfNecessary: function DirectoryLinksProvider_fetchAndCacheLinksIfNecessary(forceDownload=false) {
+ if (this._downloadDeferred) {
+ // fetching links already - just return the promise
+ return this._downloadDeferred.promise;
+ }
+
+ if (forceDownload || this._needsDownload) {
+ this._downloadDeferred = Promise.defer();
+ this._fetchAndCacheLinks(this._linksURL).then(() => {
+ // the new file was successfully downloaded and cached, so update a timestamp
+ this._lastDownloadMS = Date.now();
+ this._downloadDeferred.resolve();
+ this._downloadDeferred = null;
+ this._callObservers("onManyLinksChanged")
+ },
+ error => {
+ this._downloadDeferred.resolve();
+ this._downloadDeferred = null;
+ this._callObservers("onDownloadFail");
+ });
+ return this._downloadDeferred.promise;
+ }
+
+ // download is not needed
+ return Promise.resolve();
+ },
+
+ /**
+ * @return true if download is needed, false otherwise
+ */
+ get _needsDownload () {
+ // fail if last download occured less then 24 hours ago
+ if ((Date.now() - this._lastDownloadMS) > this._downloadIntervalMS) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Create a new XMLHttpRequest that is anonymous, i.e., doesn't send cookies
+ */
+ _newXHR() {
+ return new XMLHttpRequest({mozAnon: true});
+ },
+
+ /**
+ * Reads directory links file and parses its content
+ * @return a promise resolved to an object with keys 'directory' and 'suggested',
+ * each containing a valid list of links,
+ * or {'directory': [], 'suggested': []} if read or parse fails.
+ */
+ _readDirectoryLinksFile: function DirectoryLinksProvider_readDirectoryLinksFile() {
+ let emptyOutput = {directory: [], suggested: [], enhanced: []};
+ return OS.File.read(this._directoryFilePath).then(binaryData => {
+ let output;
+ try {
+ let json = gTextDecoder.decode(binaryData);
+ let linksObj = JSON.parse(json);
+ output = {directory: linksObj.directory || [],
+ suggested: linksObj.suggested || [],
+ enhanced: linksObj.enhanced || []};
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ return output || emptyOutput;
+ },
+ error => {
+ Cu.reportError(error);
+ return emptyOutput;
+ });
+ },
+
+ /**
+ * Translates link.time_limits to UTC miliseconds and sets
+ * link.startTime and link.endTime properties in link object
+ */
+ _setupStartEndTime: function DirectoryLinksProvider_setupStartEndTime(link) {
+ // set start/end limits. Use ISO_8601 format: '2014-01-10T20:20:20.600Z'
+ // (details here http://en.wikipedia.org/wiki/ISO_8601)
+ // Note that if timezone is missing, FX will interpret as local time
+ // meaning that the server can sepecify any time, but if the capmaign
+ // needs to start at same time across multiple timezones, the server
+ // omits timezone indicator
+ if (!link.time_limits) {
+ return;
+ }
+
+ let parsedTime;
+ if (link.time_limits.start) {
+ parsedTime = Date.parse(link.time_limits.start);
+ if (parsedTime && !isNaN(parsedTime)) {
+ link.startTime = parsedTime;
+ }
+ }
+ if (link.time_limits.end) {
+ parsedTime = Date.parse(link.time_limits.end);
+ if (parsedTime && !isNaN(parsedTime)) {
+ link.endTime = parsedTime;
+ }
+ }
+ },
+
+ /*
+ * Handles campaign timeout
+ */
+ _onCampaignTimeout: function DirectoryLinksProvider_onCampaignTimeout() {
+ // _campaignTimeoutID is invalid here, so just set it to null
+ this._campaignTimeoutID = null;
+ this._updateSuggestedTile();
+ },
+
+ /*
+ * Clears capmpaign timeout
+ */
+ _clearCampaignTimeout: function DirectoryLinksProvider_clearCampaignTimeout() {
+ if (this._campaignTimeoutID) {
+ clearTimeout(this._campaignTimeoutID);
+ this._campaignTimeoutID = null;
+ }
+ },
+
+ /**
+ * Setup capmpaign timeout to recompute suggested tiles upon
+ * reaching soonest start or end time for the campaign
+ * @param timeout in milliseconds
+ */
+ _setupCampaignTimeCheck: function DirectoryLinksProvider_setupCampaignTimeCheck(timeout) {
+ // sanity check
+ if (!timeout || timeout <= 0) {
+ return;
+ }
+ this._clearCampaignTimeout();
+ // setup next timeout
+ this._campaignTimeoutID = setTimeout(this._onCampaignTimeout.bind(this), timeout);
+ },
+
+ /**
+ * Test link for campaign time limits: checks if link falls within start/end time
+ * and returns an object containing a use flag and the timeoutDate milliseconds
+ * when the link has to be re-checked for campaign start-ready or end-reach
+ * @param link
+ * @return object {use: true or false, timeoutDate: milliseconds or null}
+ */
+ _testLinkForCampaignTimeLimits: function DirectoryLinksProvider_testLinkForCampaignTimeLimits(link) {
+ let currentTime = Date.now();
+ // test for start time first
+ if (link.startTime && link.startTime > currentTime) {
+ // not yet ready for start
+ return {use: false, timeoutDate: link.startTime};
+ }
+ // otherwise check for end time
+ if (link.endTime) {
+ // passed end time
+ if (link.endTime <= currentTime) {
+ return {use: false};
+ }
+ // otherwise link is still ok, but we need to set timeoutDate
+ return {use: true, timeoutDate: link.endTime};
+ }
+ // if we are here, the link is ok and no timeoutDate needed
+ return {use: true};
+ },
+
+ /**
+ * Handles block on suggested tile: updates fake block url with current timestamp
+ */
+ handleSuggestedTileBlock: function DirectoryLinksProvider_handleSuggestedTileBlock() {
+ this._updateFrequencyCapSettings({url: FAKE_SUGGESTED_BLOCK_URL});
+ this._writeFrequencyCapFile();
+ this._updateSuggestedTile();
+ },
+
+ /**
+ * Checks if suggested tile is being blocked for the rest of "decay time"
+ * @return True if blocked, false otherwise
+ */
+ _isSuggestedTileBlocked: function DirectoryLinksProvider__isSuggestedTileBlocked() {
+ let capObject = this._frequencyCaps[FAKE_SUGGESTED_BLOCK_URL];
+ if (!capObject || !capObject.lastUpdated) {
+ // user never blocked suggested tile or lastUpdated is missing
+ return false;
+ }
+ // otherwise, make sure that enough time passed after suggested tile was blocked
+ return (capObject.lastUpdated + AFTER_SUGGESTED_BLOCK_DECAY_TIME) > Date.now();
+ },
+
+ /**
+ * Report some action on a newtab page (view, click)
+ * @param sites Array of sites shown on newtab page
+ * @param action String of the behavior to report
+ * @param triggeringSiteIndex optional Int index of the site triggering action
+ * @return download promise
+ */
+ reportSitesAction: function DirectoryLinksProvider_reportSitesAction(sites, action, triggeringSiteIndex) {
+ // Check if the suggested tile was shown
+ if (action == "view") {
+ sites.slice(0, triggeringSiteIndex + 1).filter(s => s).forEach(site => {
+ let {targetedSite, url} = site.link;
+ if (targetedSite) {
+ this._addFrequencyCapView(url);
+ }
+ });
+ }
+ // any click action on a suggested tile should stop that tile suggestion
+ // click/block - user either removed a tile or went to a landing page
+ // pin - tile turned into history tile, should no longer be suggested
+ // unpin - the tile was pinned before, should not matter
+ else {
+ // suggested tile has targetedSite, or frecent_sites if it was pinned
+ let {frecent_sites, targetedSite, url} = sites[triggeringSiteIndex].link;
+ if (frecent_sites || targetedSite) {
+ this._setFrequencyCapClick(url);
+ }
+ }
+
+ let newtabEnhanced = false;
+ let pingEndPoint = "";
+ try {
+ newtabEnhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
+ pingEndPoint = Services.prefs.getCharPref(PREF_DIRECTORY_PING);
+ }
+ catch (ex) {}
+
+ // Bug 1240245 - We no longer send pings, but frequency capping and fetching
+ // tests depend on the following actions, so references to PING remain.
+ let invalidAction = PING_ACTIONS.indexOf(action) == -1;
+ if (!newtabEnhanced || pingEndPoint == "" || invalidAction) {
+ return Promise.resolve();
+ }
+
+ return Task.spawn(function* () {
+ // since we updated views/clicks we need write _frequencyCaps to disk
+ yield this._writeFrequencyCapFile();
+ // Use this as an opportunity to potentially fetch new links
+ yield this._fetchAndCacheLinksIfNecessary();
+ }.bind(this));
+ },
+
+ /**
+ * Get the enhanced link object for a link (whether history or directory)
+ */
+ getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
+ // Use the provided link if it's already enhanced
+ return link.enhancedImageURI && link ? link :
+ this._enhancedLinks.get(NewTabUtils.extractSite(link.url));
+ },
+
+ /**
+ * Check if a url's scheme is in a Set of allowed schemes and if the base
+ * domain is allowed.
+ * @param url to check
+ * @param allowed Set of allowed schemes
+ * @param checkBase boolean to check the base domain
+ */
+ isURLAllowed(url, allowed, checkBase) {
+ // Assume no url is an allowed url
+ if (!url) {
+ return true;
+ }
+
+ let scheme = "", base = "";
+ try {
+ // A malformed url will not be allowed
+ let uri = Services.io.newURI(url, null, null);
+ scheme = uri.scheme;
+
+ // URIs without base domains will be allowed
+ base = Services.eTLD.getBaseDomain(uri);
+ }
+ catch (ex) {}
+ // Require a scheme match and the base only if desired
+ return allowed.has(scheme) && (!checkBase || ALLOWED_URL_BASE.has(base));
+ },
+
+ _escapeChars(text) {
+ let charMap = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#039;'
+ };
+
+ return text.replace(/[&<>"']/g, (character) => charMap[character]);
+ },
+
+ /**
+ * Gets the current set of directory links.
+ * @param aCallback The function that the array of links is passed to.
+ */
+ getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
+ this._readDirectoryLinksFile().then(rawLinks => {
+ // Reset the cache of suggested tiles and enhanced images for this new set of links
+ this._enhancedLinks.clear();
+ this._suggestedLinks.clear();
+ this._clearCampaignTimeout();
+ this._avoidInadjacentSites = false;
+
+ // Only check base domain for images when using the default pref
+ let checkBase = !this.__linksURLModified;
+ let validityFilter = function(link) {
+ // Make sure the link url is allowed and images too if they exist
+ return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES, false) &&
+ (!link.imageURI ||
+ this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES, checkBase)) &&
+ (!link.enhancedImageURI ||
+ this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES, checkBase));
+ }.bind(this);
+
+ rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
+ // Suggested sites must have an adgroup name.
+ if (!link.adgroup_name) {
+ return;
+ }
+
+ let sanitizeFlags = ParserUtils.SanitizerCidEmbedsOnly |
+ ParserUtils.SanitizerDropForms |
+ ParserUtils.SanitizerDropNonCSSPresentation;
+
+ link.explanation = this._escapeChars(link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : "");
+ link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0));
+ link.lastVisitDate = rawLinks.suggested.length - position;
+ // check if link wants to avoid inadjacent sites
+ if (link.check_inadjacency) {
+ this._avoidInadjacentSites = true;
+ }
+
+ // We cache suggested tiles here but do not push any of them in the links list yet.
+ // The decision for which suggested tile to include will be made separately.
+ this._cacheSuggestedLinks(link);
+ this._updateFrequencyCapSettings(link);
+ });
+
+ rawLinks.enhanced.filter(validityFilter).forEach((link, position) => {
+ link.lastVisitDate = rawLinks.enhanced.length - position;
+
+ // Stash the enhanced image for the site
+ if (link.enhancedImageURI) {
+ this._enhancedLinks.set(NewTabUtils.extractSite(link.url), link);
+ }
+ });
+
+ let links = rawLinks.directory.filter(validityFilter).map((link, position) => {
+ link.lastVisitDate = rawLinks.directory.length - position;
+ link.frecency = DIRECTORY_FRECENCY;
+ return link;
+ });
+
+ // Allow for one link suggestion on top of the default directory links
+ this.maxNumLinks = links.length + 1;
+
+ // prune frequency caps of outdated urls
+ this._pruneFrequencyCapUrls();
+ // write frequency caps object to disk asynchronously
+ this._writeFrequencyCapFile();
+
+ return links;
+ }).catch(ex => {
+ Cu.reportError(ex);
+ return [];
+ }).then(links => {
+ aCallback(links);
+ this._populatePlacesLinks();
+ });
+ },
+
+ init: function DirectoryLinksProvider_init() {
+ this._setDefaultEnhanced();
+ this._addPrefsObserver();
+ // setup directory file path and last download timestamp
+ this._directoryFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, DIRECTORY_LINKS_FILE);
+ this._lastDownloadMS = 0;
+
+ // setup frequency cap file path
+ this._frequencyCapFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, FREQUENCY_CAP_FILE);
+ // setup inadjacent sites URL
+ this._inadjacentSitesUrl = INADJACENCY_SOURCE;
+
+ NewTabUtils.placesProvider.addObserver(this);
+ NewTabUtils.links.addObserver(this);
+
+ return Task.spawn(function*() {
+ // get the last modified time of the links file if it exists
+ let doesFileExists = yield OS.File.exists(this._directoryFilePath);
+ if (doesFileExists) {
+ let fileInfo = yield OS.File.stat(this._directoryFilePath);
+ this._lastDownloadMS = Date.parse(fileInfo.lastModificationDate);
+ }
+ // read frequency cap file
+ yield this._readFrequencyCapFile();
+ // fetch directory on startup without force
+ yield this._fetchAndCacheLinksIfNecessary();
+ // fecth inadjacent sites on startup
+ yield this._loadInadjacentSites();
+ }.bind(this));
+ },
+
+ _handleManyLinksChanged: function() {
+ this._topSitesWithSuggestedLinks.clear();
+ this._suggestedLinks.forEach((suggestedLinks, site) => {
+ if (NewTabUtils.isTopPlacesSite(site)) {
+ this._topSitesWithSuggestedLinks.add(site);
+ }
+ });
+ this._updateSuggestedTile();
+ },
+
+ /**
+ * Updates _topSitesWithSuggestedLinks based on the link that was changed.
+ *
+ * @return true if _topSitesWithSuggestedLinks was modified, false otherwise.
+ */
+ _handleLinkChanged: function(aLink) {
+ let changedLinkSite = NewTabUtils.extractSite(aLink.url);
+ let linkStored = this._topSitesWithSuggestedLinks.has(changedLinkSite);
+
+ if (!NewTabUtils.isTopPlacesSite(changedLinkSite) && linkStored) {
+ this._topSitesWithSuggestedLinks.delete(changedLinkSite);
+ return true;
+ }
+
+ if (this._suggestedLinks.has(changedLinkSite) &&
+ NewTabUtils.isTopPlacesSite(changedLinkSite) && !linkStored) {
+ this._topSitesWithSuggestedLinks.add(changedLinkSite);
+ return true;
+ }
+
+ // always run _updateSuggestedTile if aLink is inadjacent
+ // and there are tiles configured to avoid it
+ if (this._avoidInadjacentSites && this._isInadjacentLink(aLink)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ _populatePlacesLinks: function () {
+ NewTabUtils.links.populateProviderCache(NewTabUtils.placesProvider, () => {
+ this._handleManyLinksChanged();
+ });
+ },
+
+ onDeleteURI: function(aProvider, aLink) {
+ let {url} = aLink;
+ // remove clicked flag for that url and
+ // call observer upon disk write completion
+ this._removeTileClick(url).then(() => {
+ this._callObservers("onDeleteURI", url);
+ });
+ },
+
+ onClearHistory: function() {
+ // remove all clicked flags and call observers upon file write
+ this._removeAllTileClicks().then(() => {
+ this._callObservers("onClearHistory");
+ });
+ },
+
+ onLinkChanged: function (aProvider, aLink) {
+ // Make sure NewTabUtils.links handles the notification first.
+ setTimeout(() => {
+ if (this._handleLinkChanged(aLink) || this._shouldUpdateSuggestedTile()) {
+ this._updateSuggestedTile();
+ }
+ }, 0);
+ },
+
+ onManyLinksChanged: function () {
+ // Make sure NewTabUtils.links handles the notification first.
+ setTimeout(() => {
+ this._handleManyLinksChanged();
+ }, 0);
+ },
+
+ _getCurrentTopSiteCount: function() {
+ let visibleTopSiteCount = 0;
+ let newTabLinks = NewTabUtils.links.getLinks();
+ for (let link of newTabLinks.slice(0, MIN_VISIBLE_HISTORY_TILES)) {
+ // compute visibleTopSiteCount for suggested tiles
+ if (link && (link.type == "history" || link.type == "enhanced")) {
+ visibleTopSiteCount++;
+ }
+ }
+ // since newTabLinks are available, set _newTabHasInadjacentSite here
+ // note that _shouldUpdateSuggestedTile is called by _updateSuggestedTile
+ this._newTabHasInadjacentSite = this._avoidInadjacentSites && this._checkForInadjacentSites(newTabLinks);
+
+ return visibleTopSiteCount;
+ },
+
+ _shouldUpdateSuggestedTile: function() {
+ let sortedLinks = NewTabUtils.getProviderLinks(this);
+
+ let mostFrecentLink = {};
+ if (sortedLinks && sortedLinks.length) {
+ mostFrecentLink = sortedLinks[0]
+ }
+
+ let currTopSiteCount = this._getCurrentTopSiteCount();
+ if ((!mostFrecentLink.targetedSite && currTopSiteCount >= MIN_VISIBLE_HISTORY_TILES) ||
+ (mostFrecentLink.targetedSite && currTopSiteCount < MIN_VISIBLE_HISTORY_TILES)) {
+ // If mostFrecentLink has a targetedSite then mostFrecentLink is a suggested link.
+ // If we have enough history links (8+) to show a suggested tile and we are not
+ // already showing one, then we should update (to *attempt* to add a suggested tile).
+ // OR if we don't have enough history to show a suggested tile (<8) and we are
+ // currently showing one, we should update (to remove it).
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Chooses and returns a suggested tile based on a user's top sites
+ * that we have an available suggested tile for.
+ *
+ * @return the chosen suggested tile, or undefined if there isn't one
+ */
+ _updateSuggestedTile: function() {
+ let sortedLinks = NewTabUtils.getProviderLinks(this);
+
+ if (!sortedLinks) {
+ // If NewTabUtils.links.resetCache() is called before getting here,
+ // sortedLinks may be undefined.
+ return undefined;
+ }
+
+ // Delete the current suggested tile, if one exists.
+ let initialLength = sortedLinks.length;
+ if (initialLength) {
+ let mostFrecentLink = sortedLinks[0];
+ if (mostFrecentLink.targetedSite) {
+ this._callObservers("onLinkChanged", {
+ url: mostFrecentLink.url,
+ frecency: SUGGESTED_FRECENCY,
+ lastVisitDate: mostFrecentLink.lastVisitDate,
+ type: mostFrecentLink.type,
+ }, 0, true);
+ }
+ }
+
+ if (this._topSitesWithSuggestedLinks.size == 0 ||
+ !this._shouldUpdateSuggestedTile() ||
+ this._isSuggestedTileBlocked()) {
+ // There are no potential suggested links we can show or not
+ // enough history for a suggested tile, or suggested tile was
+ // recently blocked and wait time interval has not decayed yet
+ return undefined;
+ }
+
+ // Create a flat list of all possible links we can show as suggested.
+ // Note that many top sites may map to the same suggested links, but we only
+ // want to count each suggested link once (based on url), thus possibleLinks is a map
+ // from url to suggestedLink. Thus, each link has an equal chance of being chosen at
+ // random from flattenedLinks if it appears only once.
+ let nextTimeout;
+ let possibleLinks = new Map();
+ let targetedSites = new Map();
+ this._topSitesWithSuggestedLinks.forEach(topSiteWithSuggestedLink => {
+ let suggestedLinksMap = this._suggestedLinks.get(topSiteWithSuggestedLink);
+ suggestedLinksMap.forEach((suggestedLink, url) => {
+ // Skip this link if we've shown it too many times already
+ if (!this._testFrequencyCapLimits(url)) {
+ return;
+ }
+
+ // as we iterate suggestedLinks, check for campaign start/end
+ // time limits, and set nextTimeout to the closest timestamp
+ let {use, timeoutDate} = this._testLinkForCampaignTimeLimits(suggestedLink);
+ // update nextTimeout is necessary
+ if (timeoutDate && (!nextTimeout || nextTimeout > timeoutDate)) {
+ nextTimeout = timeoutDate;
+ }
+ // Skip link if it falls outside campaign time limits
+ if (!use) {
+ return;
+ }
+
+ // Skip link if it avoids inadjacent sites and newtab has one
+ if (suggestedLink.check_inadjacency && this._newTabHasInadjacentSite) {
+ return;
+ }
+
+ possibleLinks.set(url, suggestedLink);
+
+ // Keep a map of URL to targeted sites. We later use this to show the user
+ // what site they visited to trigger this suggestion.
+ if (!targetedSites.get(url)) {
+ targetedSites.set(url, []);
+ }
+ targetedSites.get(url).push(topSiteWithSuggestedLink);
+ })
+ });
+
+ // setup timeout check for starting or ending campaigns
+ if (nextTimeout) {
+ this._setupCampaignTimeCheck(nextTimeout - Date.now());
+ }
+
+ // We might have run out of possible links to show
+ let numLinks = possibleLinks.size;
+ if (numLinks == 0) {
+ return undefined;
+ }
+
+ let flattenedLinks = [...possibleLinks.values()];
+
+ // Choose our suggested link at random
+ let suggestedIndex = Math.floor(Math.random() * numLinks);
+ let chosenSuggestedLink = flattenedLinks[suggestedIndex];
+
+ // Add the suggested link to the front with some extra values
+ this._callObservers("onLinkChanged", Object.assign({
+ frecency: SUGGESTED_FRECENCY,
+
+ // Choose the first site a user has visited as the target. In the future,
+ // this should be the site with the highest frecency. However, we currently
+ // store frecency by URL not by site.
+ targetedSite: targetedSites.get(chosenSuggestedLink.url).length ?
+ targetedSites.get(chosenSuggestedLink.url)[0] : null
+ }, chosenSuggestedLink));
+ return chosenSuggestedLink;
+ },
+
+ /**
+ * Loads inadjacent sites
+ * @return a promise resolved when lookup Set for sites is built
+ */
+ _loadInadjacentSites: function DirectoryLinksProvider_loadInadjacentSites() {
+ return this._downloadJsonData(this._inadjacentSitesUrl).then(jsonString => {
+ let jsonObject = {};
+ try {
+ jsonObject = JSON.parse(jsonString);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+
+ this._inadjacentSites = new Set(jsonObject.domains);
+ });
+ },
+
+ /**
+ * Genegrates hash suitable for looking up inadjacent site
+ * @param value to hsh
+ * @return hased value, base64-ed
+ */
+ _generateHash: function DirectoryLinksProvider_generateHash(value) {
+ let byteArr = gUnicodeConverter.convertToByteArray(value);
+ gCryptoHash.init(gCryptoHash.MD5);
+ gCryptoHash.update(byteArr, byteArr.length);
+ return gCryptoHash.finish(true);
+ },
+
+ /**
+ * Checks if link belongs to inadjacent domain
+ * @param link to check
+ * @return true for inadjacent domains, false otherwise
+ */
+ _isInadjacentLink: function DirectoryLinksProvider_isInadjacentLink(link) {
+ let baseDomain = link.baseDomain || NewTabUtils.extractSite(link.url || "");
+ if (!baseDomain) {
+ return false;
+ }
+ // check if hashed domain is inadjacent
+ return this._inadjacentSites.has(this._generateHash(baseDomain));
+ },
+
+ /**
+ * Checks if new tab has inadjacent site
+ * @param new tab links (or nothing, in which case NewTabUtils.links.getLinks() is called
+ * @return true if new tab shows has inadjacent site
+ */
+ _checkForInadjacentSites: function DirectoryLinksProvider_checkForInadjacentSites(newTabLink) {
+ let links = newTabLink || NewTabUtils.links.getLinks();
+ for (let link of links.slice(0, MAX_VISIBLE_HISTORY_TILES)) {
+ // check links against inadjacent list - specifically include ALL link types
+ if (this._isInadjacentLink(link)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Reads json file, parses its content, and returns resulting object
+ * @param json file path
+ * @param json object to return in case file read or parse fails
+ * @return a promise resolved to a valid object or undefined upon error
+ */
+ _readJsonFile: Task.async(function* (filePath, nullObject) {
+ let jsonObj;
+ try {
+ let binaryData = yield OS.File.read(filePath);
+ let json = gTextDecoder.decode(binaryData);
+ jsonObj = JSON.parse(json);
+ }
+ catch (e) {}
+ return jsonObj || nullObject;
+ }),
+
+ /**
+ * Loads frequency cap object from file and parses its content
+ * @return a promise resolved upon load completion
+ * on error or non-exstent file _frequencyCaps is set to empty object
+ */
+ _readFrequencyCapFile: Task.async(function* () {
+ // set _frequencyCaps object to file's content or empty object
+ this._frequencyCaps = yield this._readJsonFile(this._frequencyCapFilePath, {});
+ }),
+
+ /**
+ * Saves frequency cap object to file
+ * @return a promise resolved upon file i/o completion
+ */
+ _writeFrequencyCapFile: function DirectoryLinksProvider_writeFrequencyCapFile() {
+ let json = JSON.stringify(this._frequencyCaps || {});
+ return OS.File.writeAtomic(this._frequencyCapFilePath, json, {tmpPath: this._frequencyCapFilePath + ".tmp"});
+ },
+
+ /**
+ * Clears frequency cap object and writes empty json to file
+ * @return a promise resolved upon file i/o completion
+ */
+ _clearFrequencyCap: function DirectoryLinksProvider_clearFrequencyCap() {
+ this._frequencyCaps = {};
+ return this._writeFrequencyCapFile();
+ },
+
+ /**
+ * updates frequency cap configuration for a link
+ */
+ _updateFrequencyCapSettings: function DirectoryLinksProvider_updateFrequencyCapSettings(link) {
+ let capsObject = this._frequencyCaps[link.url];
+ if (!capsObject) {
+ // create an object with empty counts
+ capsObject = {
+ dailyViews: 0,
+ totalViews: 0,
+ lastShownDate: 0,
+ };
+ this._frequencyCaps[link.url] = capsObject;
+ }
+ // set last updated timestamp
+ capsObject.lastUpdated = Date.now();
+ // check for link configuration
+ if (link.frequency_caps) {
+ capsObject.dailyCap = link.frequency_caps.daily || DEFAULT_DAILY_FREQUENCY_CAP;
+ capsObject.totalCap = link.frequency_caps.total || DEFAULT_TOTAL_FREQUENCY_CAP;
+ }
+ else {
+ // fallback to defaults
+ capsObject.dailyCap = DEFAULT_DAILY_FREQUENCY_CAP;
+ capsObject.totalCap = DEFAULT_TOTAL_FREQUENCY_CAP;
+ }
+ },
+
+ /**
+ * Prunes frequency cap objects for outdated links
+ * @param timeDetla milliseconds
+ * all cap objects with lastUpdated less than (now() - timeDelta)
+ * will be removed. This is done to remove frequency cap objects
+ * for unused tile urls
+ */
+ _pruneFrequencyCapUrls: function DirectoryLinksProvider_pruneFrequencyCapUrls(timeDelta = DEFAULT_PRUNE_TIME_DELTA) {
+ let timeThreshold = Date.now() - timeDelta;
+ Object.keys(this._frequencyCaps).forEach(url => {
+ // remove url if it is not ignorable and wasn't updated for a while
+ if (!url.startsWith("ignore") && this._frequencyCaps[url].lastUpdated <= timeThreshold) {
+ delete this._frequencyCaps[url];
+ }
+ });
+ },
+
+ /**
+ * Checks if supplied timestamp happened today
+ * @param timestamp in milliseconds
+ * @return true if the timestamp was made today, false otherwise
+ */
+ _wasToday: function DirectoryLinksProvider_wasToday(timestamp) {
+ let showOn = new Date(timestamp);
+ let today = new Date();
+ // call timestamps identical if both day and month are same
+ return showOn.getDate() == today.getDate() &&
+ showOn.getMonth() == today.getMonth() &&
+ showOn.getYear() == today.getYear();
+ },
+
+ /**
+ * adds some number of views for a url
+ * @param url String url of the suggested link
+ */
+ _addFrequencyCapView: function DirectoryLinksProvider_addFrequencyCapView(url) {
+ let capObject = this._frequencyCaps[url];
+ // sanity check
+ if (!capObject) {
+ return;
+ }
+
+ // if the day is new: reset the daily counter and lastShownDate
+ if (!this._wasToday(capObject.lastShownDate)) {
+ capObject.dailyViews = 0;
+ // update lastShownDate
+ capObject.lastShownDate = Date.now();
+ }
+
+ // bump both daily and total counters
+ capObject.totalViews++;
+ capObject.dailyViews++;
+
+ // if any of the caps is reached - update suggested tiles
+ if (capObject.totalViews >= capObject.totalCap ||
+ capObject.dailyViews >= capObject.dailyCap) {
+ this._updateSuggestedTile();
+ }
+ },
+
+ /**
+ * Sets clicked flag for link url
+ * @param url String url of the suggested link
+ */
+ _setFrequencyCapClick(url) {
+ let capObject = this._frequencyCaps[url];
+ // sanity check
+ if (!capObject) {
+ return;
+ }
+ capObject.clicked = true;
+ // and update suggested tiles, since current tile became invalid
+ this._updateSuggestedTile();
+ },
+
+ /**
+ * Tests frequency cap limits for link url
+ * @param url String url of the suggested link
+ * @return true if link is viewable, false otherwise
+ */
+ _testFrequencyCapLimits: function DirectoryLinksProvider_testFrequencyCapLimits(url) {
+ let capObject = this._frequencyCaps[url];
+ // sanity check: if url is missing - do not show this tile
+ if (!capObject) {
+ return false;
+ }
+
+ // check for clicked set or total views reached
+ if (capObject.clicked || capObject.totalViews >= capObject.totalCap) {
+ return false;
+ }
+
+ // otherwise check if link is over daily views limit
+ if (this._wasToday(capObject.lastShownDate) &&
+ capObject.dailyViews >= capObject.dailyCap) {
+ return false;
+ }
+
+ // we passed all cap tests: return true
+ return true;
+ },
+
+ /**
+ * Removes clicked flag from frequency cap entry for tile landing url
+ * @param url String url of the suggested link
+ * @return promise resolved upon disk write completion
+ */
+ _removeTileClick: function DirectoryLinksProvider_removeTileClick(url = "") {
+ // remove trailing slash, to accomodate Places sending site urls ending with '/'
+ let noTrailingSlashUrl = url.replace(/\/$/, "");
+ let capObject = this._frequencyCaps[url] || this._frequencyCaps[noTrailingSlashUrl];
+ // return resolved promise if capObject is not found
+ if (!capObject) {
+ return Promise.resolve();
+ }
+ // otherwise remove clicked flag
+ delete capObject.clicked;
+ return this._writeFrequencyCapFile();
+ },
+
+ /**
+ * Removes all clicked flags from frequency cap object
+ * @return promise resolved upon disk write completion
+ */
+ _removeAllTileClicks: function DirectoryLinksProvider_removeAllTileClicks() {
+ Object.keys(this._frequencyCaps).forEach(url => {
+ delete this._frequencyCaps[url].clicked;
+ });
+ return this._writeFrequencyCapFile();
+ },
+
+ /**
+ * Return the object to its pre-init state
+ */
+ reset: function DirectoryLinksProvider_reset() {
+ delete this.__linksURL;
+ this._removePrefsObserver();
+ this._removeObservers();
+ },
+
+ addObserver: function DirectoryLinksProvider_addObserver(aObserver) {
+ this._observers.add(aObserver);
+ },
+
+ removeObserver: function DirectoryLinksProvider_removeObserver(aObserver) {
+ this._observers.delete(aObserver);
+ },
+
+ _callObservers(methodName, ...args) {
+ for (let obs of this._observers) {
+ if (typeof(obs[methodName]) == "function") {
+ try {
+ obs[methodName](this, ...args);
+ } catch (err) {
+ Cu.reportError(err);
+ }
+ }
+ }
+ },
+
+ _removeObservers: function() {
+ this._observers.clear();
+ }
+};
diff --git a/browser/modules/E10SUtils.jsm b/browser/modules/E10SUtils.jsm
new file mode 100644
index 000000000..7ed51ee96
--- /dev/null
+++ b/browser/modules/E10SUtils.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["E10SUtils"];
+
+const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function getAboutModule(aURL) {
+ // Needs to match NS_GetAboutModuleName
+ let moduleName = aURL.path.replace(/[#?].*/, "").toLowerCase();
+ let contract = "@mozilla.org/network/protocol/about;1?what=" + moduleName;
+ try {
+ return Cc[contract].getService(Ci.nsIAboutModule);
+ }
+ catch (e) {
+ // Either the about module isn't defined or it is broken. In either case
+ // ignore it.
+ return null;
+ }
+}
+
+this.E10SUtils = {
+ canLoadURIInProcess: function(aURL, aProcess) {
+ // loadURI in browser.xml treats null as about:blank
+ if (!aURL)
+ aURL = "about:blank";
+
+ // Javascript urls can load in any process, they apply to the current document
+ if (aURL.startsWith("javascript:"))
+ return true;
+
+ let processIsRemote = aProcess == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+ let canLoadRemote = true;
+ let mustLoadRemote = true;
+
+ if (aURL.startsWith("about:")) {
+ let url = Services.io.newURI(aURL, null, null);
+ let module = getAboutModule(url);
+ // If the module doesn't exist then an error page will be loading, that
+ // should be ok to load in either process
+ if (module) {
+ let flags = module.getURIFlags(url);
+ canLoadRemote = !!(flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD);
+ mustLoadRemote = !!(flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD);
+ }
+ }
+
+ if (aURL.startsWith("chrome:")) {
+ let url;
+ try {
+ // This can fail for invalid Chrome URIs, in which case we will end up
+ // not loading anything anyway.
+ url = Services.io.newURI(aURL, null, null);
+ } catch (ex) {
+ canLoadRemote = true;
+ mustLoadRemote = false;
+ }
+ if (url) {
+ let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry);
+ canLoadRemote = chromeReg.canLoadURLRemotely(url);
+ mustLoadRemote = chromeReg.mustLoadURLRemotely(url);
+ }
+ }
+
+ if (aURL.startsWith("moz-extension:")) {
+ canLoadRemote = false;
+ mustLoadRemote = false;
+ }
+
+ if (aURL.startsWith("view-source:")) {
+ return this.canLoadURIInProcess(aURL.substr("view-source:".length), aProcess);
+ }
+
+ if (mustLoadRemote)
+ return processIsRemote;
+
+ if (!canLoadRemote && processIsRemote)
+ return false;
+
+ return true;
+ },
+
+ shouldLoadURI: function(aDocShell, aURI, aReferrer) {
+ // Inner frames should always load in the current process
+ if (aDocShell.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeParent)
+ return true;
+
+ // If the URI can be loaded in the current process then continue
+ return this.canLoadURIInProcess(aURI.spec, Services.appinfo.processType);
+ },
+
+ redirectLoad: function(aDocShell, aURI, aReferrer, aFreshProcess) {
+ // Retarget the load to the correct process
+ let messageManager = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ let sessionHistory = aDocShell.getInterface(Ci.nsIWebNavigation).sessionHistory;
+
+ messageManager.sendAsyncMessage("Browser:LoadURI", {
+ loadOptions: {
+ uri: aURI.spec,
+ flags: Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+ referrer: aReferrer ? aReferrer.spec : null,
+ reloadInFreshProcess: !!aFreshProcess,
+ },
+ historyIndex: sessionHistory.requestedIndex,
+ });
+ return false;
+ },
+
+ wrapHandlingUserInput: function(aWindow, aIsHandling, aCallback) {
+ var handlingUserInput;
+ try {
+ handlingUserInput = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .setHandlingUserInput(aIsHandling);
+ aCallback();
+ } finally {
+ handlingUserInput.destruct();
+ }
+ },
+};
diff --git a/browser/modules/Feeds.jsm b/browser/modules/Feeds.jsm
new file mode 100644
index 000000000..179d2b83d
--- /dev/null
+++ b/browser/modules/Feeds.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = [ "Feeds" ];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+
+const { interfaces: Ci, classes: Cc } = Components;
+
+this.Feeds = {
+ init() {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("WCCR:registerProtocolHandler", this);
+ mm.addMessageListener("WCCR:registerContentHandler", this);
+
+ Services.ppmm.addMessageListener("WCCR:setAutoHandler", this);
+ Services.ppmm.addMessageListener("FeedConverter:addLiveBookmark", this);
+ },
+
+ receiveMessage(aMessage) {
+ let data = aMessage.data;
+ switch (aMessage.name) {
+ case "WCCR:registerProtocolHandler": {
+ let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentHandlerRegistrar);
+ registrar.registerProtocolHandler(data.protocol, data.uri, data.title,
+ aMessage.target);
+ break;
+ }
+
+ case "WCCR:registerContentHandler": {
+ let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentHandlerRegistrar);
+ registrar.registerContentHandler(data.contentType, data.uri, data.title,
+ aMessage.target);
+ break;
+ }
+
+ case "WCCR:setAutoHandler": {
+ let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService);
+ registrar.setAutoHandler(data.contentType, data.handler);
+ break;
+ }
+
+ case "FeedConverter:addLiveBookmark": {
+ let topWindow = RecentWindow.getMostRecentBrowserWindow();
+ topWindow.PlacesCommandHook.addLiveBookmark(data.spec, data.title, data.subtitle)
+ .catch(Components.utils.reportError);
+ break;
+ }
+ }
+ },
+
+ /**
+ * isValidFeed: checks whether the given data represents a valid feed.
+ *
+ * @param aLink
+ * An object representing a feed with title, href and type.
+ * @param aPrincipal
+ * The principal of the document, used for security check.
+ * @param aIsFeed
+ * Whether this is already a known feed or not, if true only a security
+ * check will be performed.
+ */
+ isValidFeed: function(aLink, aPrincipal, aIsFeed) {
+ if (!aLink || !aPrincipal)
+ return false;
+
+ var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
+ if (!aIsFeed) {
+ aIsFeed = (type == "application/rss+xml" ||
+ type == "application/atom+xml");
+ }
+
+ if (aIsFeed) {
+ // re-create the principal as it may be a CPOW.
+ // once this can't be a CPOW anymore, we should just use aPrincipal instead
+ // of creating a new one.
+ let principalURI = BrowserUtils.makeURIFromCPOW(aPrincipal.URI);
+ let principalToCheck =
+ Services.scriptSecurityManager.createCodebasePrincipal(principalURI, aPrincipal.originAttributes);
+ try {
+ BrowserUtils.urlSecurityCheck(aLink.href, principalToCheck,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ return type || "application/rss+xml";
+ }
+ catch (ex) {
+ }
+ }
+
+ return null;
+ },
+
+};
diff --git a/browser/modules/FormSubmitObserver.jsm b/browser/modules/FormSubmitObserver.jsm
new file mode 100644
index 000000000..058794a54
--- /dev/null
+++ b/browser/modules/FormSubmitObserver.jsm
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Handles the validation callback from nsIFormFillController and
+ * the display of the help panel on invalid elements.
+ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+var HTMLInputElement = Ci.nsIDOMHTMLInputElement;
+var HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
+var HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
+var HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
+
+this.EXPORTED_SYMBOLS = [ "FormSubmitObserver" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/BrowserUtils.jsm");
+
+function FormSubmitObserver(aWindow, aTabChildGlobal) {
+ this.init(aWindow, aTabChildGlobal);
+}
+
+FormSubmitObserver.prototype =
+{
+ _validationMessage: "",
+ _content: null,
+ _element: null,
+
+ /*
+ * Public apis
+ */
+
+ init: function(aWindow, aTabChildGlobal)
+ {
+ this._content = aWindow;
+ this._tab = aTabChildGlobal;
+ this._mm =
+ this._content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ // nsIFormSubmitObserver callback about invalid forms. See HTMLFormElement
+ // for details.
+ Services.obs.addObserver(this, "invalidformsubmit", false);
+ this._tab.addEventListener("pageshow", this, false);
+ this._tab.addEventListener("unload", this, false);
+ },
+
+ uninit: function()
+ {
+ Services.obs.removeObserver(this, "invalidformsubmit");
+ this._content.removeEventListener("pageshow", this, false);
+ this._content.removeEventListener("unload", this, false);
+ this._mm = null;
+ this._element = null;
+ this._content = null;
+ this._tab = null;
+ },
+
+ /*
+ * Events
+ */
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "pageshow":
+ if (this._isRootDocumentEvent(aEvent)) {
+ this._hidePopup();
+ }
+ break;
+ case "unload":
+ this.uninit();
+ break;
+ case "input":
+ this._onInput(aEvent);
+ break;
+ case "blur":
+ this._onBlur(aEvent);
+ break;
+ }
+ },
+
+ /*
+ * nsIFormSubmitObserver
+ */
+
+ notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+ {
+ // We are going to handle invalid form submission attempt by focusing the
+ // first invalid element and show the corresponding validation message in a
+ // panel attached to the element.
+ if (!aInvalidElements.length) {
+ return;
+ }
+
+ // Insure that this is the FormSubmitObserver associated with the
+ // element / window this notification is about.
+ let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
+ if (this._content != element.ownerGlobal.top.document.defaultView) {
+ return;
+ }
+
+ if (!(element instanceof HTMLInputElement ||
+ element instanceof HTMLTextAreaElement ||
+ element instanceof HTMLSelectElement ||
+ element instanceof HTMLButtonElement)) {
+ return;
+ }
+
+ // Update validation message before showing notification
+ this._validationMessage = element.validationMessage;
+
+ // Don't connect up to the same element more than once.
+ if (this._element == element) {
+ this._showPopup(element);
+ return;
+ }
+ this._element = element;
+
+ element.focus();
+
+ // Watch for input changes which may change the validation message.
+ element.addEventListener("input", this, false);
+
+ // Watch for focus changes so we can disconnect our listeners and
+ // hide the popup.
+ element.addEventListener("blur", this, false);
+
+ this._showPopup(element);
+ },
+
+ /*
+ * Internal
+ */
+
+ /*
+ * Handles input changes on the form element we've associated a popup
+ * with. Updates the validation message or closes the popup if form data
+ * becomes valid.
+ */
+ _onInput: function (aEvent) {
+ let element = aEvent.originalTarget;
+
+ // If the form input is now valid, hide the popup.
+ if (element.validity.valid) {
+ this._hidePopup();
+ return;
+ }
+
+ // If the element is still invalid for a new reason, we should update
+ // the popup error message.
+ if (this._validationMessage != element.validationMessage) {
+ this._validationMessage = element.validationMessage;
+ this._showPopup(element);
+ }
+ },
+
+ /*
+ * Blur event handler in which we disconnect from the form element and
+ * hide the popup.
+ */
+ _onBlur: function (aEvent) {
+ aEvent.originalTarget.removeEventListener("input", this, false);
+ aEvent.originalTarget.removeEventListener("blur", this, false);
+ this._element = null;
+ this._hidePopup();
+ },
+
+ /*
+ * Send the show popup message to chrome with appropriate position
+ * information. Can be called repetitively to update the currently
+ * displayed popup position and text.
+ */
+ _showPopup: function (aElement) {
+ // Collect positional information and show the popup
+ let panelData = {};
+
+ panelData.message = this._validationMessage;
+
+ // Note, this is relative to the browser and needs to be translated
+ // in chrome.
+ panelData.contentRect = BrowserUtils.getElementBoundingRect(aElement);
+
+ // We want to show the popup at the middle of checkbox and radio buttons
+ // and where the content begin for the other elements.
+ let offset = 0;
+
+ if (aElement.tagName == 'INPUT' &&
+ (aElement.type == 'radio' || aElement.type == 'checkbox')) {
+ panelData.position = "bottomcenter topleft";
+ } else {
+ let win = aElement.ownerGlobal;
+ let style = win.getComputedStyle(aElement, null);
+ if (style.direction == 'rtl') {
+ offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
+ } else {
+ offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
+ }
+ let zoomFactor = this._getWindowUtils().fullZoom;
+ panelData.offset = Math.round(offset * zoomFactor);
+ panelData.position = "after_start";
+ }
+ this._mm.sendAsyncMessage("FormValidation:ShowPopup", panelData);
+ },
+
+ _hidePopup: function () {
+ this._mm.sendAsyncMessage("FormValidation:HidePopup", {});
+ },
+
+ _getWindowUtils: function () {
+ return this._content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ _isRootDocumentEvent: function (aEvent) {
+ if (this._content == null) {
+ return true;
+ }
+ let target = aEvent.originalTarget;
+ return (target == this._content.document ||
+ (target.ownerDocument && target.ownerDocument == this._content.document));
+ },
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver])
+};
diff --git a/browser/modules/FormValidationHandler.jsm b/browser/modules/FormValidationHandler.jsm
new file mode 100644
index 000000000..e7e7b14c3
--- /dev/null
+++ b/browser/modules/FormValidationHandler.jsm
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Chrome side handling of form validation popup.
+ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "FormValidationHandler" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var FormValidationHandler =
+{
+ _panel: null,
+ _anchor: null,
+
+ /*
+ * Public apis
+ */
+
+ init: function () {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("FormValidation:ShowPopup", this);
+ mm.addMessageListener("FormValidation:HidePopup", this);
+ },
+
+ uninit: function () {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.removeMessageListener("FormValidation:ShowPopup", this);
+ mm.removeMessageListener("FormValidation:HidePopup", this);
+ this._panel = null;
+ this._anchor = null;
+ },
+
+ hidePopup: function () {
+ this._hidePopup();
+ },
+
+ /*
+ * Events
+ */
+
+ receiveMessage: function (aMessage) {
+ let window = aMessage.target.ownerGlobal;
+ let json = aMessage.json;
+ let tabBrowser = window.gBrowser;
+ switch (aMessage.name) {
+ case "FormValidation:ShowPopup":
+ // target is the <browser>, make sure we're receiving a message
+ // from the foreground tab.
+ if (tabBrowser && aMessage.target != tabBrowser.selectedBrowser) {
+ return;
+ }
+ this._showPopup(window, json);
+ break;
+ case "FormValidation:HidePopup":
+ this._hidePopup();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ this._hidePopup();
+ },
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "FullZoomChange":
+ case "TextZoomChange":
+ case "ZoomChangeUsingMouseWheel":
+ case "scroll":
+ this._hidePopup();
+ break;
+ case "popuphiding":
+ this._onPopupHiding(aEvent);
+ break;
+ }
+ },
+
+ /*
+ * Internal
+ */
+
+ _onPopupHiding: function (aEvent) {
+ aEvent.originalTarget.removeEventListener("popuphiding", this, true);
+ let tabBrowser = aEvent.originalTarget.ownerDocument.getElementById("content");
+ tabBrowser.selectedBrowser.removeEventListener("scroll", this, true);
+ tabBrowser.selectedBrowser.removeEventListener("FullZoomChange", this, false);
+ tabBrowser.selectedBrowser.removeEventListener("TextZoomChange", this, false);
+ tabBrowser.selectedBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this, false);
+
+ this._panel.hidden = true;
+ this._panel = null;
+ this._anchor.hidden = true;
+ this._anchor = null;
+ },
+
+ /*
+ * Shows the form validation popup at a specified position or updates the
+ * messaging and position if the popup is already displayed.
+ *
+ * @aWindow - the chrome window
+ * @aPanelData - Object that contains popup information
+ * aPanelData stucture detail:
+ * contentRect - the bounding client rect of the target element. If
+ * content is remote, this is relative to the browser, otherwise its
+ * relative to the window.
+ * position - popup positional string constants.
+ * message - the form element validation message text.
+ */
+ _showPopup: function (aWindow, aPanelData) {
+ let previouslyShown = !!this._panel;
+ this._panel = aWindow.document.getElementById("invalid-form-popup");
+ this._panel.firstChild.textContent = aPanelData.message;
+ this._panel.hidden = false;
+
+ let tabBrowser = aWindow.gBrowser;
+ this._anchor = tabBrowser.popupAnchor;
+ this._anchor.left = aPanelData.contentRect.left;
+ this._anchor.top = aPanelData.contentRect.top;
+ this._anchor.width = aPanelData.contentRect.width;
+ this._anchor.height = aPanelData.contentRect.height;
+ this._anchor.hidden = false;
+
+ // Display the panel if it isn't already visible.
+ if (!previouslyShown) {
+ // Cleanup after the popup is hidden
+ this._panel.addEventListener("popuphiding", this, true);
+
+ // Hide if the user scrolls the page
+ tabBrowser.selectedBrowser.addEventListener("scroll", this, true);
+ tabBrowser.selectedBrowser.addEventListener("FullZoomChange", this, false);
+ tabBrowser.selectedBrowser.addEventListener("TextZoomChange", this, false);
+ tabBrowser.selectedBrowser.addEventListener("ZoomChangeUsingMouseWheel", this, false);
+
+ // Open the popup
+ this._panel.openPopup(this._anchor, aPanelData.position, 0, 0, false);
+ }
+ },
+
+ /*
+ * Hide the popup if currently displayed. Will fire an event to onPopupHiding
+ * above if visible.
+ */
+ _hidePopup: function () {
+ if (this._panel) {
+ this._panel.hidePopup();
+ }
+ }
+};
diff --git a/browser/modules/HiddenFrame.jsm b/browser/modules/HiddenFrame.jsm
new file mode 100644
index 000000000..7676ae189
--- /dev/null
+++ b/browser/modules/HiddenFrame.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["HiddenFrame"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/PromiseUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
+
+/**
+ * An hidden frame object. It takes care of creating an IFRAME and attaching it the
+ * |hiddenDOMWindow|.
+ */
+function HiddenFrame() {}
+
+HiddenFrame.prototype = {
+ _frame: null,
+ _deferred: null,
+ _retryTimerId: null,
+
+ get hiddenDOMDocument() {
+ return Services.appShell.hiddenDOMWindow.document;
+ },
+
+ get isReady() {
+ return this.hiddenDOMDocument.readyState === "complete";
+ },
+
+ /**
+ * Gets the |contentWindow| of the hidden frame. Creates the frame if needed.
+ * @returns Promise Returns a promise which is resolved when the hidden frame has finished
+ * loading.
+ */
+ get: function () {
+ if (!this._deferred) {
+ this._deferred = PromiseUtils.defer();
+ this._create();
+ }
+
+ return this._deferred.promise;
+ },
+
+ destroy: function () {
+ clearTimeout(this._retryTimerId);
+
+ if (this._frame) {
+ if (!Cu.isDeadWrapper(this._frame)) {
+ this._frame.removeEventListener("load", this, true);
+ this._frame.remove();
+ }
+
+ this._frame = null;
+ this._deferred = null;
+ }
+ },
+
+ handleEvent: function () {
+ let contentWindow = this._frame.contentWindow;
+ if (contentWindow.location.href === XUL_PAGE) {
+ this._frame.removeEventListener("load", this, true);
+ this._deferred.resolve(contentWindow);
+ } else {
+ contentWindow.location = XUL_PAGE;
+ }
+ },
+
+ _create: function () {
+ if (this.isReady) {
+ let doc = this.hiddenDOMDocument;
+ this._frame = doc.createElementNS(HTML_NS, "iframe");
+ this._frame.addEventListener("load", this, true);
+ doc.documentElement.appendChild(this._frame);
+ } else {
+ // Check again if |hiddenDOMDocument| is ready as soon as possible.
+ this._retryTimerId = setTimeout(this._create.bind(this), 0);
+ }
+ }
+};
diff --git a/browser/modules/LaterRun.jsm b/browser/modules/LaterRun.jsm
new file mode 100644
index 000000000..4c93a904a
--- /dev/null
+++ b/browser/modules/LaterRun.jsm
@@ -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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+this.EXPORTED_SYMBOLS = ["LaterRun"];
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setInterval", "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "clearInterval", "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", "resource://gre/modules/RecentWindow.jsm");
+
+const kEnabledPref = "browser.laterrun.enabled";
+const kPagePrefRoot = "browser.laterrun.pages.";
+// Number of sessions we've been active in
+const kSessionCountPref = "browser.laterrun.bookkeeping.sessionCount";
+// Time the profile was created at:
+const kProfileCreationTime = "browser.laterrun.bookkeeping.profileCreationTime";
+
+// After 50 sessions or 1 month since install, assume we will no longer be
+// interested in showing anything to "new" users
+const kSelfDestructSessionLimit = 50;
+const kSelfDestructHoursLimit = 31 * 24;
+
+class Page {
+ constructor({pref, minimumHoursSinceInstall, minimumSessionCount, requireBoth, url}) {
+ this.pref = pref;
+ this.minimumHoursSinceInstall = minimumHoursSinceInstall || 0;
+ this.minimumSessionCount = minimumSessionCount || 1;
+ this.requireBoth = requireBoth || false;
+ this.url = url;
+ }
+
+ get hasRun() {
+ return Preferences.get(this.pref + "hasRun", false);
+ }
+
+ applies(sessionInfo) {
+ if (this.hasRun) {
+ return false;
+ }
+ if (this.requireBoth) {
+ return sessionInfo.sessionCount >= this.minimumSessionCount &&
+ sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall;
+ }
+ return sessionInfo.sessionCount >= this.minimumSessionCount ||
+ sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall;
+ }
+}
+
+let LaterRun = {
+ init() {
+ if (!this.enabled) {
+ return;
+ }
+ // If this is the first run, set the time we were installed
+ if (!Preferences.has(kProfileCreationTime)) {
+ // We need to store seconds in order to fit within int prefs.
+ Preferences.set(kProfileCreationTime, Math.floor(Date.now() / 1000));
+ }
+ this.sessionCount++;
+
+ if (this.hoursSinceInstall > kSelfDestructHoursLimit ||
+ this.sessionCount > kSelfDestructSessionLimit) {
+ this.selfDestruct();
+ return;
+ }
+ },
+
+ // The enabled, hoursSinceInstall and sessionCount properties mirror the
+ // preferences system, and are here for convenience.
+ get enabled() {
+ return Preferences.get(kEnabledPref, false);
+ },
+
+ set enabled(val) {
+ let wasEnabled = this.enabled;
+ Preferences.set(kEnabledPref, val);
+ if (val && !wasEnabled) {
+ this.init();
+ }
+ },
+
+ get hoursSinceInstall() {
+ let installStamp = Preferences.get(kProfileCreationTime, Date.now() / 1000);
+ return Math.floor((Date.now() / 1000 - installStamp) / 3600);
+ },
+
+ get sessionCount() {
+ if (this._sessionCount) {
+ return this._sessionCount;
+ }
+ return this._sessionCount = Preferences.get(kSessionCountPref, 0);
+ },
+
+ set sessionCount(val) {
+ this._sessionCount = val;
+ Preferences.set(kSessionCountPref, val);
+ },
+
+ // Because we don't want to keep incrementing this indefinitely for no reason,
+ // we will turn ourselves off after a set amount of time/sessions (see top of
+ // file).
+ selfDestruct() {
+ Preferences.set(kEnabledPref, false);
+ },
+
+ // Create an array of Page objects based on the currently set prefs
+ readPages() {
+ // Enumerate all the pages.
+ let allPrefsForPages = Services.prefs.getChildList(kPagePrefRoot);
+ let pageDataStore = new Map();
+ for (let pref of allPrefsForPages) {
+ let [slug, prop] = pref.substring(kPagePrefRoot.length).split(".");
+ if (!pageDataStore.has(slug)) {
+ pageDataStore.set(slug, {pref: pref.substring(0, pref.length - prop.length)});
+ }
+ let defaultPrefValue = 0;
+ if (prop == "requireBoth" || prop == "hasRun") {
+ defaultPrefValue = false;
+ } else if (prop == "url") {
+ defaultPrefValue = "";
+ }
+ pageDataStore.get(slug)[prop] = Preferences.get(pref, defaultPrefValue);
+ }
+ let rv = [];
+ for (let [, pageData] of pageDataStore) {
+ if (pageData.url) {
+ let uri = null;
+ try {
+ let urlString = Services.urlFormatter.formatURL(pageData.url.trim());
+ uri = Services.io.newURI(urlString, null, null);
+ } catch (ex) {
+ Cu.reportError("Invalid LaterRun page URL " + pageData.url + " ignored.");
+ continue;
+ }
+ if (!uri.schemeIs("https")) {
+ Cu.reportError("Insecure LaterRun page URL " + uri.spec + " ignored.");
+ } else {
+ pageData.url = uri.spec;
+ rv.push(new Page(pageData));
+ }
+ }
+ }
+ return rv;
+ },
+
+ // Return a URL for display as a 'later run' page if its criteria are matched,
+ // or null otherwise.
+ // NB: will only return one page at a time; if multiple pages match, it's up
+ // to the preference service which one gets shown first, and the next one
+ // will be shown next startup instead.
+ getURL() {
+ if (!this.enabled) {
+ return null;
+ }
+ let pages = this.readPages();
+ let page = pages.find(page => page.applies(this));
+ if (page) {
+ Services.prefs.setBoolPref(page.pref + "hasRun", true);
+ return page.url;
+ }
+ return null;
+ },
+};
+
+LaterRun.init();
diff --git a/browser/modules/NetworkPrioritizer.jsm b/browser/modules/NetworkPrioritizer.jsm
new file mode 100644
index 000000000..770a30e09
--- /dev/null
+++ b/browser/modules/NetworkPrioritizer.jsm
@@ -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/. */
+
+/*
+ * This module adjusts network priority for tabs in a way that gives 'important'
+ * tabs a higher priority. There are 3 levels of priority. Each is listed below
+ * with the priority adjustment used.
+ *
+ * Highest (-10): Selected tab in the focused window.
+ * Medium (0): Background tabs in the focused window.
+ * Selected tab in background windows.
+ * Lowest (+10): Background tabs in background windows.
+ */
+
+this.EXPORTED_SYMBOLS = ["trackBrowserWindow"];
+
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+
+// Lazy getters
+XPCOMUtils.defineLazyServiceGetter(this, "_focusManager",
+ "@mozilla.org/focus-manager;1",
+ "nsIFocusManager");
+
+
+// Constants
+const TAB_EVENTS = ["TabBrowserInserted", "TabSelect", "TabRemotenessChange"];
+const WINDOW_EVENTS = ["activate", "unload"];
+// lower value means higher priority
+const PRIORITY_DELTA = Ci.nsISupportsPriority.PRIORITY_NORMAL - Ci.nsISupportsPriority.PRIORITY_LOW;
+
+
+// Variables
+var _lastFocusedWindow = null;
+var _windows = [];
+// this is used for restoring the priority after TabRemotenessChange
+var _priorityBackup = new WeakMap();
+
+
+// Exported symbol
+this.trackBrowserWindow = function trackBrowserWindow(aWindow) {
+ WindowHelper.addWindow(aWindow);
+}
+
+
+// Global methods
+function _handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "TabBrowserInserted":
+ BrowserHelper.onOpen(aEvent.target.linkedBrowser);
+ break;
+ case "TabSelect":
+ BrowserHelper.onSelect(aEvent.target.linkedBrowser);
+ break;
+ case "activate":
+ WindowHelper.onActivate(aEvent.target);
+ break;
+ case "TabRemotenessChange":
+ BrowserHelper.onRemotenessChange(aEvent.target.linkedBrowser);
+ break;
+ case "unload":
+ WindowHelper.removeWindow(aEvent.currentTarget);
+ break;
+ }
+}
+
+
+// Methods that impact a browser. Put into single object for organization.
+var BrowserHelper = {
+ onOpen: function NP_BH_onOpen(aBrowser) {
+ _priorityBackup.set(aBrowser.permanentKey, Ci.nsISupportsPriority.PRIORITY_NORMAL);
+
+ // If the tab is in the focused window, leave priority as it is
+ if (aBrowser.ownerGlobal != _lastFocusedWindow)
+ this.decreasePriority(aBrowser);
+ },
+
+ onSelect: function NP_BH_onSelect(aBrowser) {
+ let windowEntry = WindowHelper.getEntry(aBrowser.ownerGlobal);
+ if (windowEntry.lastSelectedBrowser)
+ this.decreasePriority(windowEntry.lastSelectedBrowser);
+ this.increasePriority(aBrowser);
+
+ windowEntry.lastSelectedBrowser = aBrowser;
+ },
+
+ onRemotenessChange: function (aBrowser) {
+ aBrowser.setPriority(_priorityBackup.get(aBrowser.permanentKey));
+ },
+
+ increasePriority: function NP_BH_increasePriority(aBrowser) {
+ aBrowser.adjustPriority(PRIORITY_DELTA);
+ _priorityBackup.set(aBrowser.permanentKey,
+ _priorityBackup.get(aBrowser.permanentKey) + PRIORITY_DELTA);
+ },
+
+ decreasePriority: function NP_BH_decreasePriority(aBrowser) {
+ aBrowser.adjustPriority(PRIORITY_DELTA * -1);
+ _priorityBackup.set(aBrowser.permanentKey,
+ _priorityBackup.get(aBrowser.permanentKey) - PRIORITY_DELTA);
+ }
+};
+
+
+// Methods that impact a window. Put into single object for organization.
+var WindowHelper = {
+ addWindow: function NP_WH_addWindow(aWindow) {
+ // Build internal data object
+ _windows.push({ window: aWindow, lastSelectedBrowser: null });
+
+ // Add event listeners
+ TAB_EVENTS.forEach(function(event) {
+ aWindow.gBrowser.tabContainer.addEventListener(event, _handleEvent, false);
+ });
+ WINDOW_EVENTS.forEach(function(event) {
+ aWindow.addEventListener(event, _handleEvent, false);
+ });
+
+ // This gets called AFTER activate event, so if this is the focused window
+ // we want to activate it. Otherwise, deprioritize it.
+ if (aWindow == _focusManager.activeWindow)
+ this.handleFocusedWindow(aWindow);
+ else
+ this.decreasePriority(aWindow);
+
+ // Select the selected tab
+ BrowserHelper.onSelect(aWindow.gBrowser.selectedBrowser);
+ },
+
+ removeWindow: function NP_WH_removeWindow(aWindow) {
+ if (aWindow == _lastFocusedWindow)
+ _lastFocusedWindow = null;
+
+ // Delete this window from our tracking
+ _windows.splice(this.getEntryIndex(aWindow), 1);
+
+ // Remove the event listeners
+ TAB_EVENTS.forEach(function(event) {
+ aWindow.gBrowser.tabContainer.removeEventListener(event, _handleEvent, false);
+ });
+ WINDOW_EVENTS.forEach(function(event) {
+ aWindow.removeEventListener(event, _handleEvent, false);
+ });
+ },
+
+ onActivate: function NP_WH_onActivate(aWindow, aHasFocus) {
+ // If this window was the last focused window, we don't need to do anything
+ if (aWindow == _lastFocusedWindow)
+ return;
+
+ // handleFocusedWindow will deprioritize the current window
+ this.handleFocusedWindow(aWindow);
+
+ // Lastly we should increase priority for this window
+ this.increasePriority(aWindow);
+ },
+
+ handleFocusedWindow: function NP_WH_handleFocusedWindow(aWindow) {
+ // If we have a last focused window, we need to deprioritize it first
+ if (_lastFocusedWindow)
+ this.decreasePriority(_lastFocusedWindow);
+
+ // aWindow is now focused
+ _lastFocusedWindow = aWindow;
+ },
+
+ // Auxiliary methods
+ increasePriority: function NP_WH_increasePriority(aWindow) {
+ aWindow.gBrowser.browsers.forEach(function(aBrowser) {
+ BrowserHelper.increasePriority(aBrowser);
+ });
+ },
+
+ decreasePriority: function NP_WH_decreasePriority(aWindow) {
+ aWindow.gBrowser.browsers.forEach(function(aBrowser) {
+ BrowserHelper.decreasePriority(aBrowser);
+ });
+ },
+
+ getEntry: function NP_WH_getEntry(aWindow) {
+ return _windows[this.getEntryIndex(aWindow)];
+ },
+
+ getEntryIndex: function NP_WH_getEntryAtIndex(aWindow) {
+ // Assumes that every object has a unique window & it's in the array
+ for (let i = 0; i < _windows.length; i++)
+ if (_windows[i].window == aWindow)
+ return i;
+ }
+};
+
diff --git a/browser/modules/PermissionUI.jsm b/browser/modules/PermissionUI.jsm
new file mode 100644
index 000000000..5fa0f9f06
--- /dev/null
+++ b/browser/modules/PermissionUI.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";
+
+this.EXPORTED_SYMBOLS = [
+ "PermissionUI",
+];
+
+/**
+ * PermissionUI is responsible for exposing both a prototype
+ * PermissionPrompt that can be used by arbitrary browser
+ * components and add-ons, but also hosts the implementations of
+ * built-in permission prompts.
+ *
+ * If you're developing a feature that requires web content to ask
+ * for special permissions from the user, this module is for you.
+ *
+ * Suppose a system add-on wants to add a new prompt for a new request
+ * for getting more low-level access to the user's sound card, and the
+ * permission request is coming up from content by way of the
+ * nsContentPermissionHelper. The system add-on could then do the following:
+ *
+ * Cu.import("resource://gre/modules/Integration.jsm");
+ * Cu.import("resource:///modules/PermissionUI.jsm");
+ *
+ * const SoundCardIntegration = (base) => ({
+ * __proto__: base,
+ * createPermissionPrompt(type, request) {
+ * if (type != "sound-api") {
+ * return super.createPermissionPrompt(...arguments);
+ * }
+ *
+ * return {
+ * __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+ * get permissionKey() {
+ * return "sound-permission";
+ * }
+ * // etc - see the documentation for PermissionPrompt for
+ * // a better idea of what things one can and should override.
+ * }
+ * },
+ * });
+ *
+ * // Add-on startup:
+ * Integration.contentPermission.register(SoundCardIntegration);
+ * // ...
+ * // Add-on shutdown:
+ * Integration.contentPermission.unregister(SoundCardIntegration);
+ *
+ * Note that PermissionPromptForRequestPrototype must be used as the
+ * prototype, since the prompt is wrapping an nsIContentPermissionRequest,
+ * and going through nsIContentPermissionPrompt.
+ *
+ * It is, however, possible to take advantage of PermissionPrompt without
+ * having to go through nsIContentPermissionPrompt or with a
+ * nsIContentPermissionRequest. The PermissionPromptPrototype can be
+ * imported, subclassed, and have prompt() called directly, without
+ * the caller having called into createPermissionPrompt.
+ */
+const { 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, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
+ return Services.strings
+ .createBundle('chrome://branding/locale/brand.properties');
+});
+
+XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
+ return Services.strings
+ .createBundle('chrome://browser/locale/browser.properties');
+});
+
+this.PermissionUI = {};
+
+/**
+ * PermissionPromptPrototype should be subclassed by callers that
+ * want to display prompts to the user. See each method and property
+ * below for guidance on what to override.
+ *
+ * Note that if you're creating a prompt for an
+ * nsIContentPermissionRequest, you'll want to subclass
+ * PermissionPromptForRequestPrototype instead.
+ */
+this.PermissionPromptPrototype = {
+ /**
+ * Returns the associated <xul:browser> for the request. This should
+ * work for the e10s and non-e10s case.
+ *
+ * Subclasses must override this.
+ *
+ * @return {<xul:browser>}
+ */
+ get browser() {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * Returns the nsIPrincipal associated with the request.
+ *
+ * Subclasses must override this.
+ *
+ * @return {nsIPrincipal}
+ */
+ get principal() {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * If the nsIPermissionManager is being queried and written
+ * to for this permission request, set this to the key to be
+ * used. If this is undefined, user permissions will not be
+ * read from or written to.
+ *
+ * Note that if a permission is set, in any follow-up
+ * prompting within the expiry window of that permission,
+ * the prompt will be skipped and the allow or deny choice
+ * will be selected automatically.
+ */
+ get permissionKey() {
+ return undefined;
+ },
+
+ /**
+ * These are the options that will be passed to the
+ * PopupNotification when it is shown. See the documentation
+ * for PopupNotification for more details.
+ *
+ * Note that prompt() will automatically set displayURI to
+ * be the URI of the requesting pricipal, unless the displayURI is exactly
+ * set to false.
+ */
+ get popupOptions() {
+ return {};
+ },
+
+ /**
+ * PopupNotification requires a unique ID to open the notification.
+ * You must return a unique ID string here, for which PopupNotification
+ * will then create a <xul:popupnotification> node with the ID
+ * "<notificationID>-notification".
+ *
+ * If there's a custom <xul:popupnotification> you're hoping to show,
+ * then you need to make sure its ID has the "-notification" suffix,
+ * and then return the prefix here.
+ *
+ * See PopupNotification.jsm for more details.
+ *
+ * @return {string}
+ * The unique ID that will be used to as the
+ * "<unique ID>-notification" ID for the <xul:popupnotification>
+ * to use or create.
+ */
+ get notificationID() {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * The ID of the element to anchor the PopupNotification to.
+ *
+ * @return {string}
+ */
+ get anchorID() {
+ return "default-notification-icon";
+ },
+
+ /**
+ * The message to show the user in the PopupNotification. This
+ * is usually a string describing the permission that is being
+ * requested.
+ *
+ * Subclasses must override this.
+ *
+ * @return {string}
+ */
+ get message() {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * This will be called if the request is to be cancelled.
+ *
+ * Subclasses only need to override this if they provide a
+ * permissionKey.
+ */
+ cancel() {
+ throw new Error("Not implemented.")
+ },
+
+ /**
+ * This will be called if the request is to be allowed.
+ *
+ * Subclasses only need to override this if they provide a
+ * permissionKey.
+ */
+ allow() {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * The actions that will be displayed in the PopupNotification
+ * via a dropdown menu. The first item in this array will be
+ * the default selection. Each action is an Object with the
+ * following properties:
+ *
+ * label (string):
+ * The label that will be displayed for this choice.
+ * accessKey (string):
+ * The access key character that will be used for this choice.
+ * action (Ci.nsIPermissionManager action, optional)
+ * The nsIPermissionManager action that will be associated with
+ * this choice. For example, Ci.nsIPermissionManager.DENY_ACTION.
+ *
+ * If omitted, the nsIPermissionManager will not be written to
+ * when this choice is chosen.
+ * expireType (Ci.nsIPermissionManager expiration policy, optional)
+ * The nsIPermissionManager expiration policy that will be associated
+ * with this choice. For example, Ci.nsIPermissionManager.EXPIRE_SESSION.
+ *
+ * If action is not set, expireType will be ignored.
+ * callback (function, optional)
+ * A callback function that will fire if the user makes this choice.
+ */
+ get promptActions() {
+ return [];
+ },
+
+ /**
+ * If the prompt will be shown to the user, this callback will
+ * be called just before. Subclasses may want to override this
+ * in order to, for example, bump a counter Telemetry probe for
+ * how often a particular permission request is seen.
+ */
+ onBeforeShow() {},
+
+ /**
+ * Will determine if a prompt should be shown to the user, and if so,
+ * will show it.
+ *
+ * If a permissionKey is defined prompt() might automatically
+ * allow or cancel itself based on the user's current
+ * permission settings without displaying the prompt.
+ *
+ * If the <xul:browser> that the request is associated with
+ * does not belong to a browser window with the PopupNotifications
+ * global set, the prompt request is ignored.
+ */
+ prompt() {
+ let chromeWin = this.browser.ownerGlobal;
+ if (!chromeWin.PopupNotifications) {
+ return;
+ }
+
+ // We ignore requests from non-nsIStandardURLs
+ let requestingURI = this.principal.URI;
+ if (!(requestingURI instanceof Ci.nsIStandardURL)) {
+ return;
+ }
+
+ if (this.permissionKey) {
+ // If we're reading and setting permissions, then we need
+ // to check to see if we already have a permission setting
+ // for this particular principal.
+ let result =
+ Services.perms.testExactPermissionFromPrincipal(this.principal,
+ this.permissionKey);
+
+ if (result == Ci.nsIPermissionManager.DENY_ACTION) {
+ this.cancel();
+ return;
+ }
+
+ if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
+ this.allow();
+ return;
+ }
+ }
+
+ // Transform the PermissionPrompt actions into PopupNotification actions.
+ let popupNotificationActions = [];
+ for (let promptAction of this.promptActions) {
+ // Don't offer action in PB mode if the action remembers permission
+ // for more than a session.
+ if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) &&
+ promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
+ promptAction.action) {
+ continue;
+ }
+
+ let action = {
+ label: promptAction.label,
+ accessKey: promptAction.accessKey,
+ callback: () => {
+ if (promptAction.callback) {
+ promptAction.callback();
+ }
+
+ if (this.permissionKey) {
+ // Remember permissions.
+ if (promptAction.action) {
+ Services.perms.addFromPrincipal(this.principal,
+ this.permissionKey,
+ promptAction.action,
+ promptAction.expireType);
+ }
+
+ // Grant permission if action is null or ALLOW_ACTION.
+ if (!promptAction.action ||
+ promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
+ this.allow();
+ } else {
+ this.cancel();
+ }
+ }
+ },
+ };
+ if (promptAction.dismiss) {
+ action.dismiss = promptAction.dismiss
+ }
+
+ popupNotificationActions.push(action);
+ }
+
+ let mainAction = popupNotificationActions.length ?
+ popupNotificationActions[0] : null;
+ let secondaryActions = popupNotificationActions.splice(1);
+
+ let options = this.popupOptions;
+
+ if (!options.hasOwnProperty('displayURI') || options.displayURI) {
+ options.displayURI = this.principal.URI;
+ }
+
+ this.onBeforeShow();
+ chromeWin.PopupNotifications.show(this.browser,
+ this.notificationID,
+ this.message,
+ this.anchorID,
+ mainAction,
+ secondaryActions,
+ options);
+ },
+};
+
+PermissionUI.PermissionPromptPrototype = PermissionPromptPrototype;
+
+/**
+ * A subclass of PermissionPromptPrototype that assumes
+ * that this.request is an nsIContentPermissionRequest
+ * and fills in some of the required properties on the
+ * PermissionPrompt. For callers that are wrapping an
+ * nsIContentPermissionRequest, this should be subclassed
+ * rather than PermissionPromptPrototype.
+ */
+this.PermissionPromptForRequestPrototype = {
+ __proto__: PermissionPromptPrototype,
+
+ get browser() {
+ // In the e10s-case, the <xul:browser> will be at request.element.
+ // In the single-process case, we have to use some XPCOM incantations
+ // to resolve to the <xul:browser>.
+ if (this.request.element) {
+ return this.request.element;
+ }
+ return this.request
+ .window
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ },
+
+ get principal() {
+ return this.request.principal;
+ },
+
+ cancel() {
+ this.request.cancel();
+ },
+
+ allow() {
+ this.request.allow();
+ },
+};
+
+PermissionUI.PermissionPromptForRequestPrototype =
+ PermissionPromptForRequestPrototype;
+
+/**
+ * Creates a PermissionPrompt for a nsIContentPermissionRequest for
+ * the GeoLocation API.
+ *
+ * @param request (nsIContentPermissionRequest)
+ * The request for a permission from content.
+ */
+function GeolocationPermissionPrompt(request) {
+ this.request = request;
+}
+
+GeolocationPermissionPrompt.prototype = {
+ __proto__: PermissionPromptForRequestPrototype,
+
+ get permissionKey() {
+ return "geo";
+ },
+
+ get popupOptions() {
+ let pref = "browser.geolocation.warning.infoURL";
+ return {
+ learnMoreURL: Services.urlFormatter.formatURLPref(pref),
+ };
+ },
+
+ get notificationID() {
+ return "geolocation";
+ },
+
+ get anchorID() {
+ return "geo-notification-icon";
+ },
+
+ get message() {
+ let message;
+ if (this.principal.URI.schemeIs("file")) {
+ message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile2");
+ } else {
+ message = gBrowserBundle.GetStringFromName("geolocation.shareWithSite2");
+ }
+ return message;
+ },
+
+ get promptActions() {
+ // We collect Telemetry data on Geolocation prompts and how users
+ // respond to them. The probe keys are a bit verbose, so let's alias them.
+ const SHARE_LOCATION =
+ Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_SHARE_LOCATION;
+ const ALWAYS_SHARE =
+ Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_ALWAYS_SHARE;
+ const NEVER_SHARE =
+ Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE;
+
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+
+ let actions = [{
+ label: gBrowserBundle.GetStringFromName("geolocation.shareLocation"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("geolocation.shareLocation.accesskey"),
+ action: null,
+ expireType: null,
+ callback: function() {
+ secHistogram.add(SHARE_LOCATION);
+ },
+ }];
+
+ if (!this.principal.URI.schemeIs("file")) {
+ // Always share location action.
+ actions.push({
+ label: gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation.accesskey"),
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: null,
+ callback: function() {
+ secHistogram.add(ALWAYS_SHARE);
+ },
+ });
+
+ // Never share location action.
+ actions.push({
+ label: gBrowserBundle.GetStringFromName("geolocation.neverShareLocation"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("geolocation.neverShareLocation.accesskey"),
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: null,
+ callback: function() {
+ secHistogram.add(NEVER_SHARE);
+ },
+ });
+ }
+
+ return actions;
+ },
+
+ onBeforeShow() {
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+ const SHOW_REQUEST = Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST;
+ secHistogram.add(SHOW_REQUEST);
+ },
+};
+
+PermissionUI.GeolocationPermissionPrompt = GeolocationPermissionPrompt;
+
+/**
+ * Creates a PermissionPrompt for a nsIContentPermissionRequest for
+ * the Desktop Notification API.
+ *
+ * @param request (nsIContentPermissionRequest)
+ * The request for a permission from content.
+ * @return {PermissionPrompt} (see documentation in header)
+ */
+function DesktopNotificationPermissionPrompt(request) {
+ this.request = request;
+}
+
+DesktopNotificationPermissionPrompt.prototype = {
+ __proto__: PermissionPromptForRequestPrototype,
+
+ get permissionKey() {
+ return "desktop-notification";
+ },
+
+ get popupOptions() {
+ let learnMoreURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
+
+ // The eventCallback is bound to the Notification that's being
+ // shown. We'll stash a reference to this in the closure so that
+ // the request can be cancelled.
+ let prompt = this;
+
+ let eventCallback = function(type) {
+ if (type == "dismissed") {
+ // Bug 1259148: Hide the doorhanger icon. Unlike other permission
+ // doorhangers, the user can't restore the doorhanger using the icon
+ // in the location bar. Instead, the site will be notified that the
+ // doorhanger was dismissed.
+ this.remove();
+ prompt.request.cancel();
+ }
+ };
+
+ return {
+ learnMoreURL,
+ eventCallback,
+ };
+ },
+
+ get notificationID() {
+ return "web-notifications";
+ },
+
+ get anchorID() {
+ return "web-notifications-notification-icon";
+ },
+
+ get message() {
+ return gBrowserBundle.GetStringFromName("webNotifications.receiveFromSite");
+ },
+
+ get promptActions() {
+ let promptActions;
+ // Only show "allow for session" in PB mode, we don't
+ // support "allow for session" in non-PB mode.
+ if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
+ promptActions = [
+ {
+ label: gBrowserBundle.GetStringFromName("webNotifications.receiveForSession"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("webNotifications.receiveForSession.accesskey"),
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ }
+ ];
+ } else {
+ promptActions = [
+ {
+ label: gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive.accesskey"),
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: null,
+ },
+ {
+ label: gBrowserBundle.GetStringFromName("webNotifications.neverShow"),
+ accessKey:
+ gBrowserBundle.GetStringFromName("webNotifications.neverShow.accesskey"),
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: null,
+ },
+ ];
+ }
+
+ return promptActions;
+ },
+};
+
+PermissionUI.DesktopNotificationPermissionPrompt =
+ DesktopNotificationPermissionPrompt;
diff --git a/browser/modules/PluginContent.jsm b/browser/modules/PluginContent.jsm
new file mode 100644
index 000000000..1bbfa9a50
--- /dev/null
+++ b/browser/modules/PluginContent.jsm
@@ -0,0 +1,1154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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;
+
+this.EXPORTED_SYMBOLS = [ "PluginContent" ];
+
+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/BrowserUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() {
+ const url = "chrome://browser/locale/browser.properties";
+ return Services.strings.createBundle(url);
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+this.PluginContent = function (global) {
+ this.init(global);
+}
+
+const FLASH_MIME_TYPE = "application/x-shockwave-flash";
+const REPLACEMENT_STYLE_SHEET = Services.io.newURI("chrome://pluginproblem/content/pluginReplaceBinding.css", null, null);
+
+PluginContent.prototype = {
+ init: function (global) {
+ this.global = global;
+ // Need to hold onto the content window or else it'll get destroyed
+ this.content = this.global.content;
+ // Cache of plugin actions for the current page.
+ this.pluginData = new Map();
+ // Cache of plugin crash information sent from the parent
+ this.pluginCrashData = new Map();
+
+ // Note that the XBL binding is untrusted
+ global.addEventListener("PluginBindingAttached", this, true, true);
+ global.addEventListener("PluginPlaceholderReplaced", this, true, true);
+ global.addEventListener("PluginCrashed", this, true);
+ global.addEventListener("PluginOutdated", this, true);
+ global.addEventListener("PluginInstantiated", this, true);
+ global.addEventListener("PluginRemoved", this, true);
+ global.addEventListener("pagehide", this, true);
+ global.addEventListener("pageshow", this, true);
+ global.addEventListener("unload", this);
+ global.addEventListener("HiddenPlugin", this, true);
+
+ global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
+ global.addMessageListener("BrowserPlugins:NotificationShown", this);
+ global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
+ global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
+ global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
+ global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
+
+ Services.obs.addObserver(this, "decoder-doctor-notification", false);
+ },
+
+ uninit: function() {
+ let global = this.global;
+
+ global.removeEventListener("PluginBindingAttached", this, true);
+ global.removeEventListener("PluginPlaceholderReplaced", this, true, true);
+ global.removeEventListener("PluginCrashed", this, true);
+ global.removeEventListener("PluginOutdated", this, true);
+ global.removeEventListener("PluginInstantiated", this, true);
+ global.removeEventListener("PluginRemoved", this, true);
+ global.removeEventListener("pagehide", this, true);
+ global.removeEventListener("pageshow", this, true);
+ global.removeEventListener("unload", this);
+ global.removeEventListener("HiddenPlugin", this, true);
+
+ global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
+ global.removeMessageListener("BrowserPlugins:NotificationShown", this);
+ global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
+ global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
+ global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
+ global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
+
+ Services.obs.removeObserver(this, "decoder-doctor-notification");
+
+ delete this.global;
+ delete this.content;
+ },
+
+ receiveMessage: function (msg) {
+ switch (msg.name) {
+ case "BrowserPlugins:ActivatePlugins":
+ this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
+ break;
+ case "BrowserPlugins:NotificationShown":
+ setTimeout(() => this.updateNotificationUI(), 0);
+ break;
+ case "BrowserPlugins:ContextMenuCommand":
+ switch (msg.data.command) {
+ case "play":
+ this._showClickToPlayNotification(msg.objects.plugin, true);
+ break;
+ case "hide":
+ this.hideClickToPlayOverlay(msg.objects.plugin);
+ break;
+ }
+ break;
+ case "BrowserPlugins:NPAPIPluginProcessCrashed":
+ this.NPAPIPluginProcessCrashed({
+ pluginName: msg.data.pluginName,
+ runID: msg.data.runID,
+ state: msg.data.state,
+ });
+ break;
+ case "BrowserPlugins:CrashReportSubmitted":
+ this.NPAPIPluginCrashReportSubmitted({
+ runID: msg.data.runID,
+ state: msg.data.state,
+ })
+ break;
+ case "BrowserPlugins:Test:ClearCrashData":
+ // This message should ONLY ever be sent by automated tests.
+ if (Services.prefs.getBoolPref("plugins.testmode")) {
+ this.pluginCrashData.clear();
+ }
+ }
+ },
+
+ observe: function observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "decoder-doctor-notification":
+ let data = JSON.parse(aData);
+ if (this.haveShownNotification &&
+ aSubject.top.document == this.content.document &&
+ data.formats.toLowerCase().includes("application/x-mpegurl", 0)) {
+ let principal = this.content.document.nodePrincipal;
+ let location = this.content.document.location.href;
+ this.global.content.pluginRequiresReload = true;
+ this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification",
+ { plugins: [... this.pluginData.values()],
+ showNow: true,
+ location: location,
+ }, null, principal);
+ }
+ }
+ },
+
+ onPageShow: function (event) {
+ // Ignore events that aren't from the main document.
+ if (!this.content || event.target != this.content.document) {
+ return;
+ }
+
+ // The PluginClickToPlay events are not fired when navigating using the
+ // BF cache. |persisted| is true when the page is loaded from the
+ // BF cache, so this code reshows the notification if necessary.
+ if (event.persisted) {
+ this.reshowClickToPlayNotification();
+ }
+ },
+
+ onPageHide: function (event) {
+ // Ignore events that aren't from the main document.
+ if (!this.content || event.target != this.content.document) {
+ return;
+ }
+
+ this._finishRecordingFlashPluginTelemetry();
+ this.clearPluginCaches();
+ this.haveShownNotification = false;
+ },
+
+ getPluginUI: function (plugin, anonid) {
+ return plugin.ownerDocument.
+ getAnonymousElementByAttribute(plugin, "anonid", anonid);
+ },
+
+ _getPluginInfo: function (pluginElement) {
+ if (pluginElement instanceof Ci.nsIDOMHTMLAnchorElement) {
+ // Anchor elements are our place holders, and we only have them for Flash
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ return {
+ pluginName: "Shockwave Flash",
+ mimetype: FLASH_MIME_TYPE,
+ permissionString: pluginHost.getPermissionStringForType(FLASH_MIME_TYPE)
+ };
+ }
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ pluginElement.QueryInterface(Ci.nsIObjectLoadingContent);
+
+ let tagMimetype;
+ let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
+ let pluginTag = null;
+ let permissionString = null;
+ let fallbackType = null;
+ let blocklistState = null;
+
+ tagMimetype = pluginElement.actualType;
+ if (tagMimetype == "") {
+ tagMimetype = pluginElement.type;
+ }
+
+ if (this.isKnownPlugin(pluginElement)) {
+ pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
+ pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
+
+ // Convert this from nsIPluginTag so it can be serialized.
+ let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
+ let pluginTagCopy = {};
+ for (let prop of properties) {
+ pluginTagCopy[prop] = pluginTag[prop];
+ }
+ pluginTag = pluginTagCopy;
+
+ permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
+ fallbackType = pluginElement.defaultFallbackType;
+ blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
+ // Make state-softblocked == state-notblocked for our purposes,
+ // they have the same UI. STATE_OUTDATED should not exist for plugin
+ // items, but let's alias it anyway, just in case.
+ if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+ blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+ }
+ }
+
+ return { mimetype: tagMimetype,
+ pluginName: pluginName,
+ pluginTag: pluginTag,
+ permissionString: permissionString,
+ fallbackType: fallbackType,
+ blocklistState: blocklistState,
+ };
+ },
+
+ _getPluginInfoForTag: function (pluginTag, tagMimetype) {
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+ let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
+ let permissionString = null;
+ let blocklistState = null;
+
+ if (pluginTag) {
+ pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
+
+ permissionString = pluginHost.getPermissionStringForTag(pluginTag);
+ blocklistState = pluginTag.blocklistState;
+
+ // Convert this from nsIPluginTag so it can be serialized.
+ let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
+ let pluginTagCopy = {};
+ for (let prop of properties) {
+ pluginTagCopy[prop] = pluginTag[prop];
+ }
+ pluginTag = pluginTagCopy;
+
+ // Make state-softblocked == state-notblocked for our purposes,
+ // they have the same UI. STATE_OUTDATED should not exist for plugin
+ // items, but let's alias it anyway, just in case.
+ if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+ blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+ }
+ }
+
+ return { mimetype: tagMimetype,
+ pluginName: pluginName,
+ pluginTag: pluginTag,
+ permissionString: permissionString,
+ fallbackType: null,
+ blocklistState: blocklistState,
+ };
+ },
+
+ /**
+ * Update the visibility of the plugin overlay.
+ */
+ setVisibility : function (plugin, overlay, shouldShow) {
+ overlay.classList.toggle("visible", shouldShow);
+ if (shouldShow) {
+ overlay.removeAttribute("dismissed");
+ }
+ },
+
+ /**
+ * Check whether the plugin should be visible on the page. A plugin should
+ * not be visible if the overlay is too big, or if any other page content
+ * overlays it.
+ *
+ * This function will handle showing or hiding the overlay.
+ * @returns true if the plugin is invisible.
+ */
+ shouldShowOverlay : function (plugin, overlay) {
+ // If the overlay size is 0, we haven't done layout yet. Presume that
+ // plugins are visible until we know otherwise.
+ if (overlay.scrollWidth == 0) {
+ return true;
+ }
+
+ // Is the <object>'s size too small to hold what we want to show?
+ let pluginRect = plugin.getBoundingClientRect();
+ // XXX bug 446693. The text-shadow on the submitted-report text at
+ // the bottom causes scrollHeight to be larger than it should be.
+ let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) ||
+ (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height));
+ if (overflows) {
+ return false;
+ }
+
+ // Is the plugin covered up by other content so that it is not clickable?
+ // Floating point can confuse .elementFromPoint, so inset just a bit
+ let left = pluginRect.left + 2;
+ let right = pluginRect.right - 2;
+ let top = pluginRect.top + 2;
+ let bottom = pluginRect.bottom - 2;
+ let centerX = left + (right - left) / 2;
+ let centerY = top + (bottom - top) / 2;
+ let points = [[left, top],
+ [left, bottom],
+ [right, top],
+ [right, bottom],
+ [centerX, centerY]];
+
+ if (right <= 0 || top <= 0) {
+ return false;
+ }
+
+ let contentWindow = plugin.ownerGlobal;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ for (let [x, y] of points) {
+ let el = cwu.elementFromPoint(x, y, true, true);
+ if (el !== plugin) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ addLinkClickCallback: function (linkNode, callbackName /* callbackArgs...*/) {
+ // XXX just doing (callback)(arg) was giving a same-origin error. bug?
+ let self = this;
+ let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
+ linkNode.addEventListener("click",
+ function(evt) {
+ if (!evt.isTrusted)
+ return;
+ evt.preventDefault();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ evt ];
+ (self[callbackName]).apply(self, callbackArgs);
+ },
+ true);
+
+ linkNode.addEventListener("keydown",
+ function(evt) {
+ if (!evt.isTrusted)
+ return;
+ if (evt.keyCode == evt.DOM_VK_RETURN) {
+ evt.preventDefault();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ evt ];
+ evt.preventDefault();
+ (self[callbackName]).apply(self, callbackArgs);
+ }
+ },
+ true);
+ },
+
+ // Helper to get the binding handler type from a plugin object
+ _getBindingType : function(plugin) {
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ return null;
+
+ switch (plugin.pluginFallbackType) {
+ case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
+ return "PluginNotFound";
+ case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
+ return "PluginDisabled";
+ case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
+ return "PluginBlocklisted";
+ case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
+ return "PluginOutdated";
+ case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
+ return "PluginClickToPlay";
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+ return "PluginVulnerableUpdatable";
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+ return "PluginVulnerableNoUpdate";
+ default:
+ // Not all states map to a handler
+ return null;
+ }
+ },
+
+ handleEvent: function (event) {
+ let eventType = event.type;
+
+ if (eventType == "unload") {
+ this.uninit();
+ return;
+ }
+
+ if (eventType == "pagehide") {
+ this.onPageHide(event);
+ return;
+ }
+
+ if (eventType == "pageshow") {
+ this.onPageShow(event);
+ return;
+ }
+
+ if (eventType == "PluginRemoved") {
+ this.updateNotificationUI(event.target);
+ return;
+ }
+
+ if (eventType == "click") {
+ this.onOverlayClick(event);
+ return;
+ }
+
+ if (eventType == "PluginCrashed" &&
+ !(event.target instanceof Ci.nsIObjectLoadingContent)) {
+ // If the event target is not a plugin object (i.e., an <object> or
+ // <embed> element), this call is for a window-global plugin.
+ this.onPluginCrashed(event.target, event);
+ return;
+ }
+
+ if (eventType == "HiddenPlugin") {
+ let win = event.target.defaultView;
+ if (!win.mozHiddenPluginTouched) {
+ let pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag);
+ if (win.top.document != this.content.document) {
+ return;
+ }
+ this._showClickToPlayNotification(pluginTag, false);
+ let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ try {
+ winUtils.loadSheet(REPLACEMENT_STYLE_SHEET, win.AGENT_SHEET);
+ win.mozHiddenPluginTouched = true;
+ } catch (e) {
+ Cu.reportError("Error adding plugin replacement style sheet: " + e);
+ }
+ }
+ }
+
+ let plugin = event.target;
+
+ if (eventType == "PluginPlaceholderReplaced") {
+ plugin.removeAttribute("href");
+ let overlay = this.getPluginUI(plugin, "main");
+ this.setVisibility(plugin, overlay, true);
+ let inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Ci.inIDOMUtils);
+ // Add psuedo class so our styling will take effect
+ inIDOMUtils.addPseudoClassLock(plugin, "-moz-handler-clicktoplay");
+ overlay.addEventListener("click", this, true);
+ return;
+ }
+
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ return;
+
+ if (eventType == "PluginBindingAttached") {
+ // The plugin binding fires this event when it is created.
+ // As an untrusted event, ensure that this object actually has a binding
+ // and make sure we don't handle it twice
+ let overlay = this.getPluginUI(plugin, "main");
+ if (!overlay || overlay._bindingHandled) {
+ return;
+ }
+ overlay._bindingHandled = true;
+
+ // Lookup the handler for this binding
+ eventType = this._getBindingType(plugin);
+ if (!eventType) {
+ // Not all bindings have handlers
+ return;
+ }
+ }
+
+ let shouldShowNotification = false;
+ switch (eventType) {
+ case "PluginCrashed":
+ this.onPluginCrashed(plugin, event);
+ break;
+
+ case "PluginNotFound": {
+ /* NOP */
+ break;
+ }
+
+ case "PluginBlocklisted":
+ case "PluginOutdated":
+ shouldShowNotification = true;
+ break;
+
+ case "PluginVulnerableUpdatable":
+ let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
+ let { pluginTag } = this._getPluginInfo(plugin);
+ this.addLinkClickCallback(updateLink, "forwardCallback",
+ "openPluginUpdatePage", pluginTag);
+ /* FALLTHRU */
+
+ case "PluginVulnerableNoUpdate":
+ case "PluginClickToPlay":
+ this._handleClickToPlayEvent(plugin);
+ let pluginName = this._getPluginInfo(plugin).pluginName;
+ let messageString = gNavigatorBundle.formatStringFromName("PluginClickToActivate", [pluginName], 1);
+ let overlayText = this.getPluginUI(plugin, "clickToPlay");
+ overlayText.textContent = messageString;
+ if (eventType == "PluginVulnerableUpdatable" ||
+ eventType == "PluginVulnerableNoUpdate") {
+ let vulnerabilityString = gNavigatorBundle.GetStringFromName(eventType);
+ let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
+ vulnerabilityText.textContent = vulnerabilityString;
+ }
+ shouldShowNotification = true;
+ break;
+
+ case "PluginDisabled":
+ let manageLink = this.getPluginUI(plugin, "managePluginsLink");
+ this.addLinkClickCallback(manageLink, "forwardCallback", "managePlugins");
+ shouldShowNotification = true;
+ break;
+
+ case "PluginInstantiated":
+ let key = this._getPluginInfo(plugin).pluginTag.niceName;
+ Services.telemetry.getKeyedHistogramById('PLUGIN_ACTIVATION_COUNT').add(key);
+ shouldShowNotification = true;
+ let pluginRect = plugin.getBoundingClientRect();
+ if (pluginRect.width <= 5 && pluginRect.height <= 5) {
+ Services.telemetry.getHistogramById('PLUGIN_TINY_CONTENT').add(1);
+ }
+ break;
+ }
+
+ if (this._getPluginInfo(plugin).mimetype === FLASH_MIME_TYPE) {
+ this._recordFlashPluginTelemetry(eventType, plugin);
+ }
+
+ // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
+ let overlay = this.getPluginUI(plugin, "main");
+ if (eventType != "PluginCrashed") {
+ if (overlay != null) {
+ this.setVisibility(plugin, overlay,
+ this.shouldShowOverlay(plugin, overlay));
+ let resizeListener = (event) => {
+ this.setVisibility(plugin, overlay,
+ this.shouldShowOverlay(plugin, overlay));
+ this.updateNotificationUI();
+ };
+ plugin.addEventListener("overflow", resizeListener);
+ plugin.addEventListener("underflow", resizeListener);
+ }
+ }
+
+ let closeIcon = this.getPluginUI(plugin, "closeIcon");
+ if (closeIcon) {
+ closeIcon.addEventListener("click", event => {
+ if (event.button == 0 && event.isTrusted) {
+ this.hideClickToPlayOverlay(plugin);
+ overlay.setAttribute("dismissed", "true");
+ }
+ }, true);
+ }
+
+ if (shouldShowNotification) {
+ this._showClickToPlayNotification(plugin, false);
+ }
+ },
+
+ _recordFlashPluginTelemetry: function (eventType, plugin) {
+ if (!Services.telemetry.canRecordExtended) {
+ return;
+ }
+
+ if (!this.flashPluginStats) {
+ this.flashPluginStats = {
+ instancesCount: 0,
+ plugins: new WeakSet()
+ };
+ }
+
+ if (!this.flashPluginStats.plugins.has(plugin)) {
+ // Reporting plugin instance and its dimensions only once.
+ this.flashPluginStats.plugins.add(plugin);
+
+ this.flashPluginStats.instancesCount++;
+
+ let pluginRect = plugin.getBoundingClientRect();
+ Services.telemetry.getHistogramById('FLASH_PLUGIN_WIDTH')
+ .add(pluginRect.width);
+ Services.telemetry.getHistogramById('FLASH_PLUGIN_HEIGHT')
+ .add(pluginRect.height);
+ Services.telemetry.getHistogramById('FLASH_PLUGIN_AREA')
+ .add(pluginRect.width * pluginRect.height);
+
+ let state = this._getPluginInfo(plugin).fallbackType;
+ if (state === null) {
+ state = Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED;
+ }
+ Services.telemetry.getHistogramById('FLASH_PLUGIN_STATES')
+ .add(state);
+ }
+ },
+
+ _finishRecordingFlashPluginTelemetry: function () {
+ if (this.flashPluginStats) {
+ Services.telemetry.getHistogramById('FLASH_PLUGIN_INSTANCES_ON_PAGE')
+ .add(this.flashPluginStats.instancesCount);
+ delete this.flashPluginStats;
+ }
+ },
+
+ isKnownPlugin: function (objLoadingContent) {
+ return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
+ Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+ },
+
+ canActivatePlugin: function (objLoadingContent) {
+ // if this isn't a known plugin, we can't activate it
+ // (this also guards pluginHost.getPermissionStringForType against
+ // unexpected input)
+ if (!this.isKnownPlugin(objLoadingContent))
+ return false;
+
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+ let principal = objLoadingContent.ownerGlobal.top.document.nodePrincipal;
+ let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
+
+ let isFallbackTypeValid =
+ objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
+ objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
+
+ return !objLoadingContent.activated &&
+ pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
+ isFallbackTypeValid;
+ },
+
+ hideClickToPlayOverlay: function (plugin) {
+ let overlay = this.getPluginUI(plugin, "main");
+ if (overlay) {
+ overlay.classList.remove("visible");
+ }
+ },
+
+ // Forward a link click callback to the chrome process.
+ forwardCallback: function (name, pluginTag) {
+ this.global.sendAsyncMessage("PluginContent:LinkClickCallback",
+ { name, pluginTag });
+ },
+
+ submitReport: function submitReport(plugin) {
+ if (!AppConstants.MOZ_CRASHREPORTER) {
+ return;
+ }
+ if (!plugin) {
+ Cu.reportError("Attempted to submit crash report without an associated plugin.");
+ return;
+ }
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent)) {
+ Cu.reportError("Attempted to submit crash report on plugin that does not" +
+ "implement nsIObjectLoadingContent.");
+ return;
+ }
+
+ let runID = plugin.runID;
+ let submitURLOptIn = this.getPluginUI(plugin, "submitURLOptIn").checked;
+ let keyVals = {};
+ let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
+ if (userComment)
+ keyVals.PluginUserComment = userComment;
+ if (submitURLOptIn)
+ keyVals.PluginContentURL = plugin.ownerDocument.URL;
+
+ this.global.sendAsyncMessage("PluginContent:SubmitReport",
+ { runID, keyVals, submitURLOptIn });
+ },
+
+ reloadPage: function () {
+ this.global.content.location.reload();
+ },
+
+ // Event listener for click-to-play plugins.
+ _handleClickToPlayEvent: function (plugin) {
+ let doc = plugin.ownerDocument;
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let permissionString;
+ if (plugin instanceof Ci.nsIDOMHTMLAnchorElement) {
+ // We only have replacement content for Flash installs
+ permissionString = pluginHost.getPermissionStringForType(FLASH_MIME_TYPE);
+ } else {
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // guard against giving pluginHost.getPermissionStringForType a type
+ // not associated with any known plugin
+ if (!this.isKnownPlugin(objLoadingContent))
+ return;
+ permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+ }
+
+ let principal = doc.defaultView.top.document.nodePrincipal;
+ let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
+
+ let overlay = this.getPluginUI(plugin, "main");
+
+ if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
+ if (overlay) {
+ overlay.classList.remove("visible");
+ }
+ return;
+ }
+
+ if (overlay) {
+ overlay.addEventListener("click", this, true);
+ }
+ },
+
+ onOverlayClick: function (event) {
+ let document = event.target.ownerDocument;
+ let plugin = document.getBindingParent(event.target);
+ let contentWindow = plugin.ownerGlobal.top;
+ let overlay = this.getPluginUI(plugin, "main");
+ // Have to check that the target is not the link to update the plugin
+ if (!(event.originalTarget instanceof contentWindow.HTMLAnchorElement) &&
+ (event.originalTarget.getAttribute('anonid') != 'closeIcon') &&
+ !overlay.hasAttribute('dismissed') &&
+ event.button == 0 &&
+ event.isTrusted) {
+ this._showClickToPlayNotification(plugin, true);
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ },
+
+ reshowClickToPlayNotification: function () {
+ let contentWindow = this.global.content;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let plugins = cwu.plugins;
+ for (let plugin of plugins) {
+ let overlay = this.getPluginUI(plugin, "main");
+ if (overlay)
+ overlay.removeEventListener("click", this, true);
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (this.canActivatePlugin(objLoadingContent))
+ this._handleClickToPlayEvent(plugin);
+ }
+ this._showClickToPlayNotification(null, false);
+ },
+
+ /**
+ * Activate the plugins that the user has specified.
+ */
+ activatePlugins: function (pluginInfo, newState) {
+ let contentWindow = this.global.content;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let plugins = cwu.plugins;
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+ let pluginFound = false;
+ let placeHolderFound = false;
+ for (let plugin of plugins) {
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (!this.isKnownPlugin(plugin)) {
+ continue;
+ }
+ if (pluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
+ let overlay = this.getPluginUI(plugin, "main");
+ if (plugin instanceof Ci.nsIDOMHTMLAnchorElement) {
+ placeHolderFound = true;
+ } else {
+ pluginFound = true;
+ }
+ if (newState == "block") {
+ if (overlay) {
+ overlay.addEventListener("click", this, true);
+ }
+ plugin.reload(true);
+ } else if (this.canActivatePlugin(plugin)) {
+ if (overlay) {
+ overlay.removeEventListener("click", this, true);
+ }
+ plugin.playPlugin();
+ }
+ }
+ }
+
+ // If there are no instances of the plugin on the page any more, what the
+ // user probably needs is for us to allow and then refresh. Additionally, if
+ // this is content that requires HLS or we replaced the placeholder the page
+ // needs to be refreshed for it to insert its plugins
+ if (newState != "block" &&
+ (!pluginFound || placeHolderFound || contentWindow.pluginRequiresReload)) {
+ this.reloadPage();
+ }
+ this.updateNotificationUI();
+ },
+
+ _showClickToPlayNotification: function (plugin, showNow) {
+ let plugins = [];
+
+ // If plugin is null, that means the user has navigated back to a page with
+ // plugins, and we need to collect all the plugins.
+ if (plugin === null) {
+ let contentWindow = this.content;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ // cwu.plugins may contain non-plugin <object>s, filter them out
+ plugins = cwu.plugins.filter((plugin) =>
+ plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+
+ if (plugins.length == 0) {
+ this.removeNotification("click-to-play-plugins");
+ return;
+ }
+ } else {
+ plugins = [plugin];
+ }
+
+ let pluginData = this.pluginData;
+
+ let principal = this.content.document.nodePrincipal;
+ let location = this.content.document.location.href;
+
+ for (let p of plugins) {
+ let pluginInfo;
+ if (p instanceof Ci.nsIPluginTag) {
+ let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
+ pluginInfo = this._getPluginInfoForTag(p, mimeType);
+ } else {
+ pluginInfo = this._getPluginInfo(p);
+ }
+ if (pluginInfo.permissionString === null) {
+ Cu.reportError("No permission string for active plugin.");
+ continue;
+ }
+ if (pluginData.has(pluginInfo.permissionString)) {
+ continue;
+ }
+
+ let permissionObj = Services.perms.
+ getPermissionObject(principal, pluginInfo.permissionString, false);
+ if (permissionObj) {
+ pluginInfo.pluginPermissionPrePath = permissionObj.principal.originNoSuffix;
+ pluginInfo.pluginPermissionType = permissionObj.expireType;
+ }
+ else {
+ pluginInfo.pluginPermissionPrePath = principal.originNoSuffix;
+ pluginInfo.pluginPermissionType = undefined;
+ }
+
+ this.pluginData.set(pluginInfo.permissionString, pluginInfo);
+ }
+
+ this.haveShownNotification = true;
+
+ this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", {
+ plugins: [... this.pluginData.values()],
+ showNow: showNow,
+ location: location,
+ }, null, principal);
+ },
+
+ /**
+ * Updates the "hidden plugin" notification bar UI.
+ *
+ * @param document (optional)
+ * Specify the document that is causing the update.
+ * This is useful when the document is possibly no longer
+ * the current loaded document (for example, if we're
+ * responding to a PluginRemoved event for an unloading
+ * document). If this parameter is omitted, it defaults
+ * to the current top-level document.
+ */
+ updateNotificationUI: function (document) {
+ document = document || this.content.document;
+
+ // We're only interested in the top-level document, since that's
+ // the one that provides the Principal that we send back to the
+ // parent.
+ let principal = document.defaultView.top.document.nodePrincipal;
+ let location = document.location.href;
+
+ // Make a copy of the actions from the last popup notification.
+ let haveInsecure = false;
+ let actions = new Map();
+ for (let action of this.pluginData.values()) {
+ switch (action.fallbackType) {
+ // haveInsecure will trigger the red flashing icon and the infobar
+ // styling below
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+ haveInsecure = true;
+ // fall through
+
+ case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
+ actions.set(action.permissionString, action);
+ continue;
+ }
+ }
+
+ // Remove plugins that are already active, or large enough to show an overlay.
+ let cwu = this.content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ for (let plugin of cwu.plugins) {
+ let info = this._getPluginInfo(plugin);
+ if (!actions.has(info.permissionString)) {
+ continue;
+ }
+ let fallbackType = info.fallbackType;
+ if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
+ actions.delete(info.permissionString);
+ if (actions.size == 0) {
+ break;
+ }
+ continue;
+ }
+ if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
+ fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
+ fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
+ continue;
+ }
+ let overlay = this.getPluginUI(plugin, "main");
+ if (!overlay) {
+ continue;
+ }
+ let shouldShow = this.shouldShowOverlay(plugin, overlay);
+ this.setVisibility(plugin, overlay, shouldShow);
+ if (shouldShow) {
+ actions.delete(info.permissionString);
+ if (actions.size == 0) {
+ break;
+ }
+ }
+ }
+
+ // If there are any items remaining in `actions` now, they are hidden
+ // plugins that need a notification bar.
+ this.global.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", {
+ haveInsecure: haveInsecure,
+ actions: [... actions.values()],
+ location: location,
+ }, null, principal);
+ },
+
+ removeNotification: function (name) {
+ this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name: name });
+ },
+
+ clearPluginCaches: function () {
+ this.pluginData.clear();
+ this.pluginCrashData.clear();
+ },
+
+ hideNotificationBar: function (name) {
+ this.global.sendAsyncMessage("PluginContent:HideNotificationBar", { name: name });
+ },
+
+ /**
+ * The PluginCrashed event handler. Note that the PluginCrashed event is
+ * fired for both NPAPI and Gecko Media plugins. In the latter case, the
+ * target of the event is the document that the GMP is being used in.
+ */
+ onPluginCrashed: function (target, aEvent) {
+ if (!(aEvent instanceof this.content.PluginCrashedEvent))
+ return;
+
+ if (aEvent.gmpPlugin) {
+ this.GMPCrashed(aEvent);
+ return;
+ }
+
+ if (!(target instanceof Ci.nsIObjectLoadingContent))
+ return;
+
+ let crashData = this.pluginCrashData.get(target.runID);
+ if (!crashData) {
+ // We haven't received information from the parent yet about
+ // this crash, so we should hold off showing the crash report
+ // UI.
+ return;
+ }
+
+ crashData.instances.delete(target);
+ if (crashData.instances.length == 0) {
+ this.pluginCrashData.delete(target.runID);
+ }
+
+ this.setCrashedNPAPIPluginState({
+ plugin: target,
+ state: crashData.state,
+ message: crashData.message,
+ });
+ },
+
+ NPAPIPluginProcessCrashed: function ({pluginName, runID, state}) {
+ let message =
+ gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
+ [pluginName], 1);
+
+ let contentWindow = this.global.content;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let plugins = cwu.plugins;
+
+ for (let plugin of plugins) {
+ if (plugin instanceof Ci.nsIObjectLoadingContent &&
+ plugin.runID == runID) {
+ // The parent has told us that the plugin process has died.
+ // It's possible that this content process hasn't yet noticed,
+ // in which case we need to stash this data around until the
+ // PluginCrashed events get sent up.
+ if (plugin.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CRASHED) {
+ // This plugin has already been put into the crashed state by the
+ // content process, so we can tweak its crash UI without delay.
+ this.setCrashedNPAPIPluginState({plugin, state, message});
+ } else {
+ // The content process hasn't yet determined that the plugin has crashed.
+ // Stash the data in our map, and throw the plugin into a WeakSet. When
+ // the PluginCrashed event fires on the <object>/<embed>, we'll retrieve
+ // the information we need from the Map and remove the instance from the
+ // WeakSet. Once the WeakSet is empty, we can clear the map.
+ if (!this.pluginCrashData.has(runID)) {
+ this.pluginCrashData.set(runID, {
+ state: state,
+ message: message,
+ instances: new WeakSet(),
+ });
+ }
+ let crashData = this.pluginCrashData.get(runID);
+ crashData.instances.add(plugin);
+ }
+ }
+ }
+ },
+
+ setCrashedNPAPIPluginState: function ({plugin, state, message}) {
+ // Force a layout flush so the binding is attached.
+ plugin.clientTop;
+ let overlay = this.getPluginUI(plugin, "main");
+ let statusDiv = this.getPluginUI(plugin, "submitStatus");
+ let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
+
+ this.getPluginUI(plugin, "submitButton")
+ .addEventListener("click", (event) => {
+ if (event.button != 0 || !event.isTrusted)
+ return;
+ this.submitReport(plugin);
+ });
+
+ let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
+ optInCB.checked = pref.getBoolPref("");
+
+ statusDiv.setAttribute("status", state);
+
+ let helpIcon = this.getPluginUI(plugin, "helpIcon");
+ this.addLinkClickCallback(helpIcon, "openHelpPage");
+
+ let crashText = this.getPluginUI(plugin, "crashedText");
+ crashText.textContent = message;
+
+ let link = this.getPluginUI(plugin, "reloadLink");
+ this.addLinkClickCallback(link, "reloadPage");
+
+ let isShowing = this.shouldShowOverlay(plugin, overlay);
+
+ // Is the <object>'s size too small to hold what we want to show?
+ if (!isShowing) {
+ // First try hiding the crash report submission UI.
+ statusDiv.removeAttribute("status");
+
+ isShowing = this.shouldShowOverlay(plugin, overlay);
+ }
+ this.setVisibility(plugin, overlay, isShowing);
+
+ let doc = plugin.ownerDocument;
+ let runID = plugin.runID;
+
+ if (isShowing) {
+ // If a previous plugin on the page was too small and resulted in adding a
+ // notification bar, then remove it because this plugin instance it big
+ // enough to serve as in-content notification.
+ this.hideNotificationBar("plugin-crashed");
+ doc.mozNoPluginCrashedNotification = true;
+
+ // Notify others that the crash reporter UI is now ready.
+ // Currently, this event is only used by tests.
+ let winUtils = this.content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let event = new this.content.CustomEvent("PluginCrashReporterDisplayed", {bubbles: true});
+ winUtils.dispatchEventToChromeOnly(plugin, event);
+ } else if (!doc.mozNoPluginCrashedNotification) {
+ // If another plugin on the page was large enough to show our UI, we don't
+ // want to show a notification bar.
+ this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
+ { messageString: message, pluginID: runID });
+ // Remove the notification when the page is reloaded.
+ doc.defaultView.top.addEventListener("unload", event => {
+ this.hideNotificationBar("plugin-crashed");
+ }, false);
+ }
+ },
+
+ NPAPIPluginCrashReportSubmitted: function({ runID, state }) {
+ this.pluginCrashData.delete(runID);
+ let contentWindow = this.global.content;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let plugins = cwu.plugins;
+
+ for (let plugin of plugins) {
+ if (plugin instanceof Ci.nsIObjectLoadingContent &&
+ plugin.runID == runID) {
+ let statusDiv = this.getPluginUI(plugin, "submitStatus");
+ statusDiv.setAttribute("status", state);
+ }
+ }
+ },
+
+ GMPCrashed: function(aEvent) {
+ let target = aEvent.target;
+ let pluginName = aEvent.pluginName;
+ let gmpPlugin = aEvent.gmpPlugin;
+ let pluginID = aEvent.pluginID;
+ let doc = target.document;
+
+ if (!gmpPlugin || !doc) {
+ // TODO: Throw exception? How did we get here?
+ return;
+ }
+
+ let messageString =
+ gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
+ [pluginName], 1);
+
+ this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
+ { messageString, pluginID });
+
+ // Remove the notification when the page is reloaded.
+ doc.defaultView.top.addEventListener("unload", event => {
+ this.hideNotificationBar("plugin-crashed");
+ }, false);
+ },
+};
diff --git a/browser/modules/ProcessHangMonitor.jsm b/browser/modules/ProcessHangMonitor.jsm
new file mode 100644
index 000000000..e048f5b40
--- /dev/null
+++ b/browser/modules/ProcessHangMonitor.jsm
@@ -0,0 +1,397 @@
+/* -*- mode: js; 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 Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ["ProcessHangMonitor"];
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * This JSM is responsible for observing content process hang reports
+ * and asking the user what to do about them. See nsIHangReport for
+ * the platform interface.
+ */
+
+var ProcessHangMonitor = {
+ /**
+ * This timeout is the wait period applied after a user selects "Wait" in
+ * an existing notification.
+ */
+ get WAIT_EXPIRATION_TIME() {
+ try {
+ return Services.prefs.getIntPref("browser.hangNotification.waitPeriod");
+ } catch (ex) {
+ return 10000;
+ }
+ },
+
+ /**
+ * Collection of hang reports that haven't expired or been dismissed
+ * by the user. These are nsIHangReports.
+ */
+ _activeReports: new Set(),
+
+ /**
+ * Collection of hang reports that have been suppressed for a short
+ * period of time. Value is an nsITimer for when the wait time
+ * expires.
+ */
+ _pausedReports: new Map(),
+
+ /**
+ * Initialize hang reporting. Called once in the parent process.
+ */
+ init: function() {
+ Services.obs.addObserver(this, "process-hang-report", false);
+ Services.obs.addObserver(this, "clear-hang-report", false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ Services.ww.registerNotification(this);
+ },
+
+ /**
+ * Terminate JavaScript associated with the hang being reported for
+ * the selected browser in |win|.
+ */
+ terminateScript: function(win) {
+ this.handleUserInput(win, report => report.terminateScript());
+ },
+
+ /**
+ * Start devtools debugger for JavaScript associated with the hang
+ * being reported for the selected browser in |win|.
+ */
+ debugScript: function(win) {
+ this.handleUserInput(win, report => {
+ function callback() {
+ report.endStartingDebugger();
+ }
+
+ report.beginStartingDebugger();
+
+ let svc = Cc["@mozilla.org/dom/slow-script-debug;1"].getService(Ci.nsISlowScriptDebug);
+ let handler = svc.remoteActivationHandler;
+ handler.handleSlowScriptDebug(report.scriptBrowser, callback);
+ });
+ },
+
+ /**
+ * Terminate the plugin process associated with a hang being reported
+ * for the selected browser in |win|. Will attempt to generate a combined
+ * crash report for all processes.
+ */
+ terminatePlugin: function(win) {
+ this.handleUserInput(win, report => report.terminatePlugin());
+ },
+
+ /**
+ * Dismiss the browser notification and invoke an appropriate action based on
+ * the hang type.
+ */
+ stopIt: function (win) {
+ let report = this.findActiveReport(win.gBrowser.selectedBrowser);
+ if (!report) {
+ return;
+ }
+
+ switch (report.hangType) {
+ case report.SLOW_SCRIPT:
+ this.terminateScript(win);
+ break;
+ case report.PLUGIN_HANG:
+ this.terminatePlugin(win);
+ break;
+ }
+ },
+
+ /**
+ * Dismiss the notification, clear the report from the active list and set up
+ * a new timer to track a wait period during which we won't notify.
+ */
+ waitLonger: function(win) {
+ let report = this.findActiveReport(win.gBrowser.selectedBrowser);
+ if (!report) {
+ return;
+ }
+ // Remove the report from the active list.
+ this.removeActiveReport(report);
+
+ // NOTE, we didn't call userCanceled on nsIHangReport here. This insures
+ // we don't repeatedly generate and cache crash report data for this hang
+ // in the process hang reporter. It already has one report for the browser
+ // process we want it hold onto.
+
+ // Create a new wait timer with notify callback
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(() => {
+ for (let [stashedReport, otherTimer] of this._pausedReports) {
+ if (otherTimer === timer) {
+ this.removePausedReport(stashedReport);
+
+ // We're still hung, so move the report back to the active
+ // list and update the UI.
+ this._activeReports.add(report);
+ this.updateWindows();
+ break;
+ }
+ }
+ }, this.WAIT_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
+
+ this._pausedReports.set(report, timer);
+
+ // remove the browser notification associated with this hang
+ this.updateWindows();
+ },
+
+ /**
+ * If there is a hang report associated with the selected browser in
+ * |win|, invoke |func| on that report and stop notifying the user
+ * about it.
+ */
+ handleUserInput: function(win, func) {
+ let report = this.findActiveReport(win.gBrowser.selectedBrowser);
+ if (!report) {
+ return null;
+ }
+ this.removeActiveReport(report);
+
+ return func(report);
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "xpcom-shutdown":
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ Services.obs.removeObserver(this, "process-hang-report");
+ Services.obs.removeObserver(this, "clear-hang-report");
+ Services.ww.unregisterNotification(this);
+ break;
+
+ case "process-hang-report":
+ this.reportHang(subject.QueryInterface(Ci.nsIHangReport));
+ break;
+
+ case "clear-hang-report":
+ this.clearHang(subject.QueryInterface(Ci.nsIHangReport));
+ break;
+
+ case "domwindowopened":
+ // Install event listeners on the new window in case one of
+ // its tabs is already hung.
+ let win = subject.QueryInterface(Ci.nsIDOMWindow);
+ let listener = (ev) => {
+ win.removeEventListener("load", listener, true);
+ this.updateWindows();
+ };
+ win.addEventListener("load", listener, true);
+ break;
+ }
+ },
+
+ /**
+ * Find a active hang report for the given <browser> element.
+ */
+ findActiveReport: function(browser) {
+ let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ for (let report of this._activeReports) {
+ if (report.isReportForBrowser(frameLoader)) {
+ return report;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Find a paused hang report for the given <browser> element.
+ */
+ findPausedReport: function(browser) {
+ let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ for (let [report, ] of this._pausedReports) {
+ if (report.isReportForBrowser(frameLoader)) {
+ return report;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Remove an active hang report from the active list and cancel the timer
+ * associated with it.
+ */
+ removeActiveReport: function(report) {
+ this._activeReports.delete(report);
+ this.updateWindows();
+ },
+
+ /**
+ * Remove a paused hang report from the paused list and cancel the timer
+ * associated with it.
+ */
+ removePausedReport: function(report) {
+ let timer = this._pausedReports.get(report);
+ if (timer) {
+ timer.cancel();
+ }
+ this._pausedReports.delete(report);
+ },
+
+ /**
+ * Iterate over all XUL windows and ensure that the proper hang
+ * reports are shown for each one. Also install event handlers in
+ * each window to watch for events that would cause a different hang
+ * report to be displayed.
+ */
+ updateWindows: function() {
+ let e = Services.wm.getEnumerator("navigator:browser");
+ while (e.hasMoreElements()) {
+ let win = e.getNext();
+
+ this.updateWindow(win);
+
+ // Only listen for these events if there are active hang reports.
+ if (this._activeReports.size) {
+ this.trackWindow(win);
+ } else {
+ this.untrackWindow(win);
+ }
+ }
+ },
+
+ /**
+ * If there is a hang report for the current tab in |win|, display it.
+ */
+ updateWindow: function(win) {
+ let report = this.findActiveReport(win.gBrowser.selectedBrowser);
+
+ if (report) {
+ this.showNotification(win, report);
+ } else {
+ this.hideNotification(win);
+ }
+ },
+
+ /**
+ * Show the notification for a hang.
+ */
+ showNotification: function(win, report) {
+ let nb = win.document.getElementById("high-priority-global-notificationbox");
+ let notification = nb.getNotificationWithValue("process-hang");
+ if (notification) {
+ return;
+ }
+
+ let bundle = win.gNavigatorBundle;
+
+ let buttons = [{
+ label: bundle.getString("processHang.button_stop.label"),
+ accessKey: bundle.getString("processHang.button_stop.accessKey"),
+ callback: function() {
+ ProcessHangMonitor.stopIt(win);
+ }
+ },
+ {
+ label: bundle.getString("processHang.button_wait.label"),
+ accessKey: bundle.getString("processHang.button_wait.accessKey"),
+ callback: function() {
+ ProcessHangMonitor.waitLonger(win);
+ }
+ }];
+
+ if (AppConstants.MOZ_DEV_EDITION && report.hangType == report.SLOW_SCRIPT) {
+ buttons.push({
+ label: bundle.getString("processHang.button_debug.label"),
+ accessKey: bundle.getString("processHang.button_debug.accessKey"),
+ callback: function() {
+ ProcessHangMonitor.debugScript(win);
+ }
+ });
+ }
+
+ nb.appendNotification(bundle.getString("processHang.label"),
+ "process-hang",
+ "chrome://browser/content/aboutRobots-icon.png",
+ nb.PRIORITY_WARNING_HIGH, buttons);
+ },
+
+ /**
+ * Ensure that no hang notifications are visible in |win|.
+ */
+ hideNotification: function(win) {
+ let nb = win.document.getElementById("high-priority-global-notificationbox");
+ let notification = nb.getNotificationWithValue("process-hang");
+ if (notification) {
+ nb.removeNotification(notification);
+ }
+ },
+
+ /**
+ * Install event handlers on |win| to watch for events that would
+ * cause a different hang report to be displayed.
+ */
+ trackWindow: function(win) {
+ win.gBrowser.tabContainer.addEventListener("TabSelect", this, true);
+ win.gBrowser.tabContainer.addEventListener("TabRemotenessChange", this, true);
+ },
+
+ untrackWindow: function(win) {
+ win.gBrowser.tabContainer.removeEventListener("TabSelect", this, true);
+ win.gBrowser.tabContainer.removeEventListener("TabRemotenessChange", this, true);
+ },
+
+ handleEvent: function(event) {
+ let win = event.target.ownerGlobal;
+
+ // If a new tab is selected or if a tab changes remoteness, then
+ // we may need to show or hide a hang notification.
+
+ if (event.type == "TabSelect" || event.type == "TabRemotenessChange") {
+ this.updateWindow(win);
+ }
+ },
+
+ /**
+ * Handle a potentially new hang report. If it hasn't been seen
+ * before, show a notification for it in all open XUL windows.
+ */
+ reportHang: function(report) {
+ // If this hang was already reported reset the timer for it.
+ if (this._activeReports.has(report)) {
+ // if this report is in active but doesn't have a notification associated
+ // with it, display a notification.
+ this.updateWindows();
+ return;
+ }
+
+ // If this hang was already reported and paused by the user ignore it.
+ if (this._pausedReports.has(report)) {
+ return;
+ }
+
+ // On e10s this counts slow-script/hanged-plugin notice only once.
+ // This code is not reached on non-e10s.
+ if (report.hangType == report.SLOW_SCRIPT) {
+ // On non-e10s, SLOW_SCRIPT_NOTICE_COUNT is probed at nsGlobalWindow.cpp
+ Services.telemetry.getHistogramById("SLOW_SCRIPT_NOTICE_COUNT").add();
+ } else if (report.hangType == report.PLUGIN_HANG) {
+ // On non-e10s we have sufficient plugin telemetry probes,
+ // so PLUGIN_HANG_NOTICE_COUNT is only probed on e10s.
+ Services.telemetry.getHistogramById("PLUGIN_HANG_NOTICE_COUNT").add();
+ }
+
+ this._activeReports.add(report);
+ this.updateWindows();
+ },
+
+ clearHang: function(report) {
+ this.removeActiveReport(report);
+ this.removePausedReport(report);
+ report.userCanceled();
+ },
+};
diff --git a/browser/modules/ReaderParent.jsm b/browser/modules/ReaderParent.jsm
new file mode 100644
index 000000000..6fcaada42
--- /dev/null
+++ b/browser/modules/ReaderParent.jsm
@@ -0,0 +1,186 @@
+// -*- 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, utils: Cu } = Components;
+
+this.EXPORTED_SYMBOLS = [ "ReaderParent" ];
+
+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");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm");
+
+const gStringBundle = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
+
+var ReaderParent = {
+ _readerModeInfoPanelOpen: false,
+
+ MESSAGES: [
+ "Reader:ArticleGet",
+ "Reader:FaviconRequest",
+ "Reader:UpdateReaderButton",
+ ],
+
+ init: function() {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ for (let msg of this.MESSAGES) {
+ mm.addMessageListener(msg, this);
+ }
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Reader:ArticleGet":
+ this._getArticle(message.data.url, message.target).then((article) => {
+ // Make sure the target browser is still alive before trying to send data back.
+ if (message.target.messageManager) {
+ message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { article: article });
+ }
+ }, e => {
+ if (e && e.newURL) {
+ // Make sure the target browser is still alive before trying to send data back.
+ if (message.target.messageManager) {
+ message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { newURL: e.newURL });
+ }
+ }
+ });
+ break;
+
+ case "Reader:FaviconRequest": {
+ if (message.target.messageManager) {
+ let faviconUrl = PlacesUtils.promiseFaviconLinkUrl(message.data.url);
+ faviconUrl.then(function onResolution(favicon) {
+ message.target.messageManager.sendAsyncMessage("Reader:FaviconReturn", {
+ url: message.data.url,
+ faviconUrl: favicon.path.replace(/^favicon:/, "")
+ })
+ },
+ function onRejection(reason) {
+ Cu.reportError("Error requesting favicon URL for about:reader content: " + reason);
+ }).catch(Cu.reportError);
+ }
+ break;
+ }
+
+ case "Reader:UpdateReaderButton": {
+ let browser = message.target;
+ if (message.data && message.data.isArticle !== undefined) {
+ browser.isArticle = message.data.isArticle;
+ }
+ this.updateReaderButton(browser);
+ break;
+ }
+ }
+ },
+
+ updateReaderButton: function(browser) {
+ let win = browser.ownerGlobal;
+ if (browser != win.gBrowser.selectedBrowser) {
+ return;
+ }
+
+ let button = win.document.getElementById("reader-mode-button");
+ let command = win.document.getElementById("View:ReaderView");
+ let key = win.document.getElementById("toggleReaderMode");
+ if (browser.currentURI.spec.startsWith("about:reader")) {
+ button.setAttribute("readeractive", true);
+ button.hidden = false;
+ let closeText = gStringBundle.GetStringFromName("readerView.close");
+ button.setAttribute("tooltiptext", closeText);
+ command.setAttribute("label", closeText);
+ command.setAttribute("hidden", false);
+ command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.close.accesskey"));
+ key.setAttribute("disabled", false);
+ } else {
+ button.removeAttribute("readeractive");
+ button.hidden = !browser.isArticle;
+ let enterText = gStringBundle.GetStringFromName("readerView.enter");
+ button.setAttribute("tooltiptext", enterText);
+ command.setAttribute("label", enterText);
+ command.setAttribute("hidden", !browser.isArticle);
+ command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.enter.accesskey"));
+ key.setAttribute("disabled", !browser.isArticle);
+ }
+
+ let currentUriHost = browser.currentURI && browser.currentURI.asciiHost;
+ if (browser.isArticle &&
+ !Services.prefs.getBoolPref("browser.reader.detectedFirstArticle") &&
+ currentUriHost && !currentUriHost.endsWith("mozilla.org")) {
+ this.showReaderModeInfoPanel(browser);
+ Services.prefs.setBoolPref("browser.reader.detectedFirstArticle", true);
+ this._readerModeInfoPanelOpen = true;
+ } else if (this._readerModeInfoPanelOpen) {
+ if (UITour.isInfoOnTarget(win, "readerMode-urlBar")) {
+ UITour.hideInfo(win);
+ }
+ this._readerModeInfoPanelOpen = false;
+ }
+ },
+
+ forceShowReaderIcon: function(browser) {
+ browser.isArticle = true;
+ this.updateReaderButton(browser);
+ },
+
+ buttonClick(event) {
+ if (event.button != 0) {
+ return;
+ }
+ this.toggleReaderMode(event);
+ },
+
+ toggleReaderMode: function(event) {
+ let win = event.target.ownerGlobal;
+ let browser = win.gBrowser.selectedBrowser;
+ browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
+ },
+
+ /**
+ * Shows an info panel from the UITour for Reader Mode.
+ *
+ * @param browser The <browser> that the tour should be started for.
+ */
+ showReaderModeInfoPanel(browser) {
+ let win = browser.ownerGlobal;
+ let targetPromise = UITour.getTarget(win, "readerMode-urlBar");
+ targetPromise.then(target => {
+ let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+ let icon = "chrome://browser/skin/";
+ if (win.devicePixelRatio > 1) {
+ icon += "reader-tour@2x.png";
+ } else {
+ icon += "reader-tour.png";
+ }
+ UITour.showInfo(win, target,
+ browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.title"),
+ browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.body"),
+ icon);
+ });
+ },
+
+ /**
+ * Gets an article for a given URL. This method will download and parse a document.
+ *
+ * @param url The article URL.
+ * @param browser The browser where the article is currently loaded.
+ * @return {Promise}
+ * @resolves JS object representing the article, or null if no article is found.
+ */
+ _getArticle: Task.async(function* (url, browser) {
+ return yield ReaderMode.downloadAndParseDocument(url).catch(e => {
+ if (e && e.newURL) {
+ // Pass up the error so we can navigate the browser in question to the new URL:
+ throw e;
+ }
+ Cu.reportError("Error downloading and parsing document: " + e);
+ return null;
+ });
+ })
+};
diff --git a/browser/modules/RecentWindow.jsm b/browser/modules/RecentWindow.jsm
new file mode 100644
index 000000000..fac9dcea4
--- /dev/null
+++ b/browser/modules/RecentWindow.jsm
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["RecentWindow"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+this.RecentWindow = {
+ /*
+ * Get the most recent browser window.
+ *
+ * @param aOptions an object accepting the arguments for the search.
+ * * private: true to restrict the search to private windows
+ * only, false to restrict the search to non-private only.
+ * Omit the property to search in both groups.
+ * * allowPopups: true if popup windows are permissable.
+ */
+ getMostRecentBrowserWindow: function RW_getMostRecentBrowserWindow(aOptions) {
+ let checkPrivacy = typeof aOptions == "object" &&
+ "private" in aOptions;
+
+ let allowPopups = typeof aOptions == "object" && !!aOptions.allowPopups;
+
+ function isSuitableBrowserWindow(win) {
+ return (!win.closed &&
+ (allowPopups || win.toolbar.visible) &&
+ (!checkPrivacy ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing ||
+ PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private));
+ }
+
+ let broken_wm_z_order =
+ AppConstants.platform != "macosx" && AppConstants.platform != "win";
+
+ if (broken_wm_z_order) {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ // if we're lucky, this isn't a popup, and we can just return this
+ if (win && !isSuitableBrowserWindow(win)) {
+ win = null;
+ let windowList = Services.wm.getEnumerator("navigator:browser");
+ // this is oldest to newest, so this gets a bit ugly
+ while (windowList.hasMoreElements()) {
+ let nextWin = windowList.getNext();
+ if (isSuitableBrowserWindow(nextWin))
+ win = nextWin;
+ }
+ }
+ return win;
+ }
+ let windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
+ while (windowList.hasMoreElements()) {
+ let win = windowList.getNext();
+ if (isSuitableBrowserWindow(win))
+ return win;
+ }
+ return null;
+ }
+};
+
diff --git a/browser/modules/RemotePrompt.jsm b/browser/modules/RemotePrompt.jsm
new file mode 100644
index 000000000..da4945c2e
--- /dev/null
+++ b/browser/modules/RemotePrompt.jsm
@@ -0,0 +1,110 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "RemotePrompt" ];
+
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
+
+var RemotePrompt = {
+ init: function() {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("Prompt:Open", this);
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "Prompt:Open":
+ if (message.data.uri) {
+ this.openModalWindow(message.data, message.target);
+ } else {
+ this.openTabPrompt(message.data, message.target)
+ }
+ break;
+ }
+ },
+
+ openTabPrompt: function(args, browser) {
+ let window = browser.ownerGlobal;
+ let tabPrompt = window.gBrowser.getTabModalPromptBox(browser)
+ let newPrompt;
+ let needRemove = false;
+ let promptId = args._remoteId;
+
+ function onPromptClose(forceCleanup) {
+ // It's possible that we removed the prompt during the
+ // appendPrompt call below. In that case, newPrompt will be
+ // undefined. We set the needRemove flag to remember to remove
+ // it right after we've finished adding it.
+ if (newPrompt)
+ tabPrompt.removePrompt(newPrompt);
+ else
+ needRemove = true;
+
+ PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
+ browser.messageManager.sendAsyncMessage("Prompt:Close", args);
+ }
+
+ browser.messageManager.addMessageListener("Prompt:ForceClose", function listener(message) {
+ // If this was for another prompt in the same tab, ignore it.
+ if (message.data._remoteId !== promptId) {
+ return;
+ }
+
+ browser.messageManager.removeMessageListener("Prompt:ForceClose", listener);
+
+ if (newPrompt) {
+ newPrompt.abortPrompt();
+ }
+ });
+
+ try {
+ let eventDetail = {
+ tabPrompt: true,
+ promptPrincipal: args.promptPrincipal,
+ inPermitUnload: args.inPermitUnload,
+ };
+ PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser, eventDetail);
+
+ args.promptActive = true;
+
+ newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
+
+ if (needRemove) {
+ tabPrompt.removePrompt(newPrompt);
+ }
+
+ // 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.
+ } catch (ex) {
+ onPromptClose(true);
+ }
+ },
+
+ openModalWindow: function(args, browser) {
+ let window = browser.ownerGlobal;
+ try {
+ PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
+ let bag = PromptUtils.objectToPropBag(args);
+
+ Services.ww.openWindow(window, args.uri, "_blank",
+ "centerscreen,chrome,modal,titlebar", bag);
+
+ PromptUtils.propBagToObject(bag, args);
+ } finally {
+ PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
+ browser.messageManager.sendAsyncMessage("Prompt:Close", args);
+ }
+ }
+};
diff --git a/browser/modules/Sanitizer.jsm b/browser/modules/Sanitizer.jsm
new file mode 100644
index 000000000..31c2823c7
--- /dev/null
+++ b/browser/modules/Sanitizer.jsm
@@ -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";
+
+//
+// A shared module for sanitize.js
+//
+// Until bug 1167238 lands, this serves only as a way to ensure that
+// sanitize is loaded from its own compartment, rather than from that
+// of the sanitize dialog.
+//
+
+this.EXPORTED_SYMBOLS = ["Sanitizer"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+var scope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", scope);
+
+this.Sanitizer = scope.Sanitizer;
diff --git a/browser/modules/SelfSupportBackend.jsm b/browser/modules/SelfSupportBackend.jsm
new file mode 100644
index 000000000..3a3f8cb8b
--- /dev/null
+++ b/browser/modules/SelfSupportBackend.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["SelfSupportBackend"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "HiddenFrame",
+ "resource:///modules/HiddenFrame.jsm");
+
+// Enables or disables the Self Support.
+const PREF_ENABLED = "browser.selfsupport.enabled";
+// Url to open in the Self Support browser, in the urlFormatter service format.
+const PREF_URL = "browser.selfsupport.url";
+// Unified Telemetry status.
+const PREF_TELEMETRY_UNIFIED = "toolkit.telemetry.unified";
+// UITour status.
+const PREF_UITOUR_ENABLED = "browser.uitour.enabled";
+
+// Controls the interval at which the self support page tries to reload in case of
+// errors.
+const RETRY_INTERVAL_MS = 30000;
+// Maximum number of SelfSupport page load attempts in case of failure.
+const MAX_RETRIES = 5;
+// The delay after which to load the self-support, at startup.
+const STARTUP_DELAY_MS = 5000;
+
+const LOGGER_NAME = "Browser.SelfSupportBackend";
+const PREF_BRANCH_LOG = "browser.selfsupport.log.";
+const PREF_LOG_LEVEL = PREF_BRANCH_LOG + "level";
+const PREF_LOG_DUMP = PREF_BRANCH_LOG + "dump";
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const UITOUR_FRAME_SCRIPT = "chrome://browser/content/content-UITour.js";
+
+// Whether the FHR/Telemetry unification features are enabled.
+// Changing this pref requires a restart.
+const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_TELEMETRY_UNIFIED, false);
+
+var gLogAppenderDump = null;
+
+this.SelfSupportBackend = Object.freeze({
+ init: function () {
+ SelfSupportBackendInternal.init();
+ },
+
+ uninit: function () {
+ SelfSupportBackendInternal.uninit();
+ },
+});
+
+var SelfSupportBackendInternal = {
+ // The browser element that will load the SelfSupport page.
+ _browser: null,
+ // The Id of the timer triggering delayed SelfSupport page load.
+ _delayedLoadTimerId: null,
+ // The HiddenFrame holding the _browser element.
+ _frame: null,
+ _log: null,
+ _progressListener: null,
+
+ /**
+ * Initializes the self support backend.
+ */
+ init: function () {
+ this._configureLogging();
+
+ this._log.trace("init");
+
+ Preferences.observe(PREF_BRANCH_LOG, this._configureLogging, this);
+
+ // Only allow to use SelfSupport if Unified Telemetry is enabled.
+ let reportingEnabled = IS_UNIFIED_TELEMETRY;
+ if (!reportingEnabled) {
+ this._log.config("init - Disabling SelfSupport because FHR and Unified Telemetry are disabled.");
+ return;
+ }
+
+ // Make sure UITour is enabled.
+ let uiTourEnabled = Preferences.get(PREF_UITOUR_ENABLED, false);
+ if (!uiTourEnabled) {
+ this._log.config("init - Disabling SelfSupport because UITour is disabled.");
+ return;
+ }
+
+ // Check the preferences to see if we want this to be active.
+ if (!Preferences.get(PREF_ENABLED, true)) {
+ this._log.config("init - SelfSupport is disabled.");
+ return;
+ }
+
+ Services.obs.addObserver(this, "sessionstore-windows-restored", false);
+ },
+
+ /**
+ * Shut down the self support backend, if active.
+ */
+ uninit: function () {
+ this._log.trace("uninit");
+
+ Preferences.ignore(PREF_BRANCH_LOG, this._configureLogging, this);
+
+ // Cancel delayed loading, if still active, when shutting down.
+ clearTimeout(this._delayedLoadTimerId);
+
+ // Dispose of the hidden browser.
+ if (this._browser !== null) {
+ if (this._browser.contentWindow) {
+ this._browser.contentWindow.removeEventListener("DOMWindowClose", this, true);
+ }
+
+ if (this._progressListener) {
+ this._browser.removeProgressListener(this._progressListener);
+ this._progressListener.destroy();
+ this._progressListener = null;
+ }
+
+ this._browser.remove();
+ this._browser = null;
+ }
+
+ if (this._frame) {
+ this._frame.destroy();
+ this._frame = null;
+ }
+ },
+
+ /**
+ * Handle notifications. Once all windows are created, we wait a little bit more
+ * since tabs might still be loading. Then, we open the self support.
+ */
+ observe: function (aSubject, aTopic, aData) {
+ this._log.trace("observe - Topic " + aTopic);
+
+ if (aTopic === "sessionstore-windows-restored") {
+ Services.obs.removeObserver(this, "sessionstore-windows-restored");
+ this._delayedLoadTimerId = setTimeout(this._loadSelfSupport.bind(this), STARTUP_DELAY_MS);
+ }
+ },
+
+ /**
+ * Configure the logger based on the preferences.
+ */
+ _configureLogging: function() {
+ if (!this._log) {
+ this._log = Log.repository.getLogger(LOGGER_NAME);
+
+ // Log messages need to go to the browser console.
+ let consoleAppender = new Log.ConsoleAppender(new Log.BasicFormatter());
+ this._log.addAppender(consoleAppender);
+ }
+
+ // Make sure the logger keeps up with the logging level preference.
+ this._log.level = Log.Level[Preferences.get(PREF_LOG_LEVEL, "Warn")];
+
+ // If enabled in the preferences, add a dump appender.
+ let logDumping = Preferences.get(PREF_LOG_DUMP, false);
+ if (logDumping != !!gLogAppenderDump) {
+ if (logDumping) {
+ gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
+ this._log.addAppender(gLogAppenderDump);
+ } else {
+ this._log.removeAppender(gLogAppenderDump);
+ gLogAppenderDump = null;
+ }
+ }
+ },
+
+ /**
+ * Create an hidden frame to host our |browser|, then load the SelfSupport page in it.
+ * @param aURL The URL to load in the browser.
+ */
+ _makeHiddenBrowser: function(aURL) {
+ this._frame = new HiddenFrame();
+ return this._frame.get().then(aFrame => {
+ let doc = aFrame.document;
+
+ this._browser = doc.createElementNS(XUL_NS, "browser");
+ this._browser.setAttribute("type", "content");
+ this._browser.setAttribute("disableglobalhistory", "true");
+ this._browser.setAttribute("src", aURL);
+
+ doc.documentElement.appendChild(this._browser);
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ this._log.trace("handleEvent - aEvent.type " + aEvent.type + ", Trusted " + aEvent.isTrusted);
+
+ if (aEvent.type === "DOMWindowClose") {
+ let window = this._browser.contentDocument.defaultView;
+ let target = aEvent.target;
+
+ if (target == window) {
+ // preventDefault stops the default window.close(). We need to do that to prevent
+ // Services.appShell.hiddenDOMWindow from being destroyed.
+ aEvent.preventDefault();
+
+ this.uninit();
+ }
+ }
+ },
+
+ /**
+ * Called when the self support page correctly loads.
+ */
+ _pageSuccessCallback: function() {
+ this._log.debug("_pageSuccessCallback - Page correctly loaded.");
+ this._browser.removeProgressListener(this._progressListener);
+ this._progressListener.destroy();
+ this._progressListener = null;
+
+ // Allow SelfSupportBackend to catch |window.close()| issued by the content.
+ this._browser.contentWindow.addEventListener("DOMWindowClose", this, true);
+ },
+
+ /**
+ * Called when the self support page fails to load.
+ */
+ _pageLoadErrorCallback: function() {
+ this._log.info("_pageLoadErrorCallback - Too many failed load attempts. Giving up.");
+ this.uninit();
+ },
+
+ /**
+ * Create a browser and attach it to an hidden window. The browser will contain the
+ * self support page and attempt to load the page content. If loading fails, try again
+ * after an interval.
+ */
+ _loadSelfSupport: function() {
+ // Fetch the Self Support URL from the preferences.
+ let unformattedURL = Preferences.get(PREF_URL, null);
+ let url = Services.urlFormatter.formatURL(unformattedURL);
+ if (!url.startsWith("https:")) {
+ this._log.error("_loadSelfSupport - Non HTTPS URL provided: " + url);
+ return;
+ }
+
+ this._log.config("_loadSelfSupport - URL " + url);
+
+ // Create the hidden browser.
+ this._makeHiddenBrowser(url).then(() => {
+ // Load UITour frame script.
+ this._browser.messageManager.loadFrameScript(UITOUR_FRAME_SCRIPT, true);
+
+ // We need to watch for load errors as well and, in case, try to reload
+ // the self support page.
+ const webFlags = Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
+ Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+ Ci.nsIWebProgress.NOTIFY_LOCATION;
+
+ this._progressListener = new ProgressListener(() => this._pageLoadErrorCallback(),
+ () => this._pageSuccessCallback());
+
+ this._browser.addProgressListener(this._progressListener, webFlags);
+ });
+ }
+};
+
+/**
+ * A progress listener object which notifies of page load error and load success
+ * through callbacks. When the page fails to load, the progress listener tries to
+ * reload it up to MAX_RETRIES times. The page is not loaded again immediately, but
+ * after a timeout.
+ *
+ * @param aLoadErrorCallback Called when a page failed to load MAX_RETRIES times.
+ * @param aLoadSuccessCallback Called when a page correctly loads.
+ */
+function ProgressListener(aLoadErrorCallback, aLoadSuccessCallback) {
+ this._loadErrorCallback = aLoadErrorCallback;
+ this._loadSuccessCallback = aLoadSuccessCallback;
+ // The number of page loads attempted.
+ this._loadAttempts = 0;
+ this._log = Log.repository.getLogger(LOGGER_NAME);
+ // The Id of the timer which triggers page load again in case of errors.
+ this._reloadTimerId = null;
+}
+
+ProgressListener.prototype = {
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
+ this._log.warn("onLocationChange - There was a problem fetching the SelfSupport URL (attempt " +
+ this._loadAttempts + ").");
+
+ // Increase the number of attempts and bail out if we failed too many times.
+ this._loadAttempts++;
+ if (this._loadAttempts > MAX_RETRIES) {
+ this._loadErrorCallback();
+ return;
+ }
+
+ // Reload the page after the retry interval expires. The interval is multiplied
+ // by the number of attempted loads, so that it takes a bit more to try to reload
+ // when frequently failing.
+ this._reloadTimerId = setTimeout(() => {
+ this._log.debug("onLocationChange - Reloading SelfSupport URL in the hidden browser.");
+ aWebProgress.DOMWindow.location.reload();
+ }, RETRY_INTERVAL_MS * this._loadAttempts);
+ }
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aFlags, aStatus) {
+ if (aFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+ aFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
+ Components.isSuccessCode(aStatus)) {
+ this._loadSuccessCallback();
+ }
+ },
+
+ destroy: function () {
+ // Make sure we don't try to reload self support when shutting down.
+ clearTimeout(this._reloadTimerId);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+};
diff --git a/browser/modules/SitePermissions.jsm b/browser/modules/SitePermissions.jsm
new file mode 100644
index 000000000..d15ddb21b
--- /dev/null
+++ b/browser/modules/SitePermissions.jsm
@@ -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/. */
+
+this.EXPORTED_SYMBOLS = [ "SitePermissions" ];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gStringBundle =
+ Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
+
+this.SitePermissions = {
+
+ UNKNOWN: Services.perms.UNKNOWN_ACTION,
+ ALLOW: Services.perms.ALLOW_ACTION,
+ BLOCK: Services.perms.DENY_ACTION,
+ SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
+
+ /* Returns all custom permissions for a given URI, the return
+ * type is a list of objects with the keys:
+ * - id: the permissionId of the permission
+ * - state: a constant representing the current permission state
+ * (e.g. SitePermissions.ALLOW)
+ *
+ * To receive a more detailed, albeit less performant listing see
+ * SitePermissions.getPermissionDetailsByURI().
+ *
+ * install addon permission is excluded, check bug 1303108
+ */
+ getAllByURI: function (aURI) {
+ let result = [];
+ if (!this.isSupportedURI(aURI)) {
+ return result;
+ }
+
+ let permissions = Services.perms.getAllForURI(aURI);
+ while (permissions.hasMoreElements()) {
+ let permission = permissions.getNext();
+
+ // filter out unknown permissions
+ if (gPermissionObject[permission.type]) {
+ // XXX Bug 1303108 - Control Center should only show non-default permissions
+ if (permission.type == "install") {
+ continue;
+ }
+ result.push({
+ id: permission.type,
+ state: permission.capability,
+ });
+ }
+ }
+
+ return result;
+ },
+
+ /* Returns an object representing the aId permission. It contains the
+ * following keys:
+ * - id: the permissionID of the permission
+ * - label: the localized label
+ * - state: a constant representing the aState permission state
+ * (e.g. SitePermissions.ALLOW), or the default if aState is omitted
+ * - availableStates: an array of all available states for that permission,
+ * represented as objects with the keys:
+ * - id: the state constant
+ * - label: the translated label of that state
+ */
+ getPermissionItem: function (aId, aState) {
+ let availableStates = this.getAvailableStates(aId).map(state => {
+ return { id: state, label: this.getStateLabel(aId, state) };
+ });
+ if (aState == undefined)
+ aState = this.getDefault(aId);
+ return {id: aId, label: this.getPermissionLabel(aId),
+ state: aState, availableStates};
+ },
+
+ /* Returns a list of objects representing all permissions that are currently
+ * set for the given URI. See getPermissionItem for the content of each object.
+ */
+ getPermissionDetailsByURI: function (aURI) {
+ let permissions = [];
+ for (let {state, id} of this.getAllByURI(aURI)) {
+ permissions.push(this.getPermissionItem(id, state));
+ }
+
+ return permissions;
+ },
+
+ /* Checks whether a UI for managing permissions should be exposed for a given
+ * URI. This excludes file URIs, for instance, as they don't have a host,
+ * even though nsIPermissionManager can still handle them.
+ */
+ isSupportedURI: function (aURI) {
+ return aURI.schemeIs("http") || aURI.schemeIs("https");
+ },
+
+ /* Returns an array of all permission IDs.
+ */
+ listPermissions: function () {
+ return Object.keys(gPermissionObject);
+ },
+
+ /* Returns an array of permission states to be exposed to the user for a
+ * permission with the given ID.
+ */
+ getAvailableStates: function (aPermissionID) {
+ if (aPermissionID in gPermissionObject &&
+ gPermissionObject[aPermissionID].states)
+ return gPermissionObject[aPermissionID].states;
+
+ if (this.getDefault(aPermissionID) == this.UNKNOWN)
+ return [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ];
+
+ return [ SitePermissions.ALLOW, SitePermissions.BLOCK ];
+ },
+
+ /* Returns the default state of a particular permission.
+ */
+ getDefault: function (aPermissionID) {
+ if (aPermissionID in gPermissionObject &&
+ gPermissionObject[aPermissionID].getDefault)
+ return gPermissionObject[aPermissionID].getDefault();
+
+ return this.UNKNOWN;
+ },
+
+ /* Returns the state of a particular permission for a given URI.
+ */
+ get: function (aURI, aPermissionID) {
+ if (!this.isSupportedURI(aURI))
+ return this.UNKNOWN;
+
+ let state;
+ if (aPermissionID in gPermissionObject &&
+ gPermissionObject[aPermissionID].exactHostMatch)
+ state = Services.perms.testExactPermission(aURI, aPermissionID);
+ else
+ state = Services.perms.testPermission(aURI, aPermissionID);
+ return state;
+ },
+
+ /* Sets the state of a particular permission for a given URI.
+ */
+ set: function (aURI, aPermissionID, aState) {
+ if (!this.isSupportedURI(aURI))
+ return;
+
+ if (aState == this.UNKNOWN) {
+ this.remove(aURI, aPermissionID);
+ return;
+ }
+
+ Services.perms.add(aURI, aPermissionID, aState);
+ },
+
+ /* Removes the saved state of a particular permission for a given URI.
+ */
+ remove: function (aURI, aPermissionID) {
+ if (!this.isSupportedURI(aURI))
+ return;
+
+ Services.perms.remove(aURI, aPermissionID);
+ },
+
+ /* Returns the localized label for the permission with the given ID, to be
+ * used in a UI for managing permissions.
+ */
+ getPermissionLabel: function (aPermissionID) {
+ let labelID = gPermissionObject[aPermissionID].labelID || aPermissionID;
+ return gStringBundle.GetStringFromName("permission." + labelID + ".label");
+ },
+
+ /* Returns the localized label for the given permission state, to be used in
+ * a UI for managing permissions.
+ */
+ getStateLabel: function (aPermissionID, aState, aInUse = false) {
+ switch (aState) {
+ case this.UNKNOWN:
+ if (aInUse)
+ return gStringBundle.GetStringFromName("allowTemporarily");
+ return gStringBundle.GetStringFromName("alwaysAsk");
+ case this.ALLOW:
+ return gStringBundle.GetStringFromName("allow");
+ case this.SESSION:
+ return gStringBundle.GetStringFromName("allowForSession");
+ case this.BLOCK:
+ return gStringBundle.GetStringFromName("block");
+ default:
+ return null;
+ }
+ }
+};
+
+var gPermissionObject = {
+ /* Holds permission ID => options pairs.
+ *
+ * Supported options:
+ *
+ * - exactHostMatch
+ * Allows sub domains to have their own permissions.
+ * Defaults to false.
+ *
+ * - getDefault
+ * Called to get the permission's default state.
+ * Defaults to UNKNOWN, indicating that the user will be asked each time
+ * a page asks for that permissions.
+ *
+ * - labelID
+ * Use the given ID instead of the permission name for looking up strings.
+ * e.g. "desktop-notification2" to use permission.desktop-notification2.label
+ *
+ * - states
+ * Array of permission states to be exposed to the user.
+ * Defaults to ALLOW, BLOCK and the default state (see getDefault).
+ */
+
+ "image": {
+ getDefault: function () {
+ return Services.prefs.getIntPref("permissions.default.image") == 2 ?
+ SitePermissions.BLOCK : SitePermissions.ALLOW;
+ }
+ },
+
+ "cookie": {
+ states: [ SitePermissions.ALLOW, SitePermissions.SESSION, SitePermissions.BLOCK ],
+ getDefault: function () {
+ if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == 2)
+ return SitePermissions.BLOCK;
+
+ if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == 2)
+ return SitePermissions.SESSION;
+
+ return SitePermissions.ALLOW;
+ }
+ },
+
+ "desktop-notification": {
+ exactHostMatch: true,
+ labelID: "desktop-notification2",
+ },
+
+ "camera": {},
+ "microphone": {},
+ "screen": {
+ states: [ SitePermissions.UNKNOWN, SitePermissions.BLOCK ],
+ },
+
+ "popup": {
+ getDefault: function () {
+ return Services.prefs.getBoolPref("dom.disable_open_during_load") ?
+ SitePermissions.BLOCK : SitePermissions.ALLOW;
+ }
+ },
+
+ "install": {
+ getDefault: function () {
+ return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
+ SitePermissions.BLOCK : SitePermissions.ALLOW;
+ }
+ },
+
+ "geo": {
+ exactHostMatch: true
+ },
+
+ "indexedDB": {}
+};
+
+const kPermissionIDs = Object.keys(gPermissionObject);
diff --git a/browser/modules/Social.jsm b/browser/modules/Social.jsm
new file mode 100644
index 000000000..1569e0122
--- /dev/null
+++ b/browser/modules/Social.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Social", "OpenGraphBuilder",
+ "DynamicResizeWatcher", "sizeSocialPanelToContent"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+// The minimum sizes for the auto-resize panel code, minimum size necessary to
+// properly show the error page in the panel.
+const PANEL_MIN_HEIGHT = 190;
+const PANEL_MIN_WIDTH = 330;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
+ "resource:///modules/CustomizableUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
+ "resource:///modules/SocialService.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
+ "resource://gre/modules/PageMetadata.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+
+this.Social = {
+ initialized: false,
+ lastEventReceived: 0,
+ providers: [],
+ _disabledForSafeMode: false,
+
+ init: function Social_init() {
+ this._disabledForSafeMode = Services.appinfo.inSafeMode && this.enabled;
+ let deferred = Promise.defer();
+
+ if (this.initialized) {
+ deferred.resolve(true);
+ return deferred.promise;
+ }
+ this.initialized = true;
+ // if SocialService.hasEnabledProviders, retreive the providers so the
+ // front-end can generate UI
+ if (SocialService.hasEnabledProviders) {
+ // Retrieve the current set of providers, and set the current provider.
+ SocialService.getOrderedProviderList(function (providers) {
+ Social._updateProviderCache(providers);
+ Social._updateEnabledState(SocialService.enabled);
+ deferred.resolve(false);
+ });
+ } else {
+ deferred.resolve(false);
+ }
+
+ // Register an observer for changes to the provider list
+ SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
+ // An engine change caused by adding/removing a provider should notify.
+ // any providers we receive are enabled in the AddonsManager
+ if (topic == "provider-installed" || topic == "provider-uninstalled") {
+ // installed/uninstalled do not send the providers param
+ Services.obs.notifyObservers(null, "social:" + topic, origin);
+ return;
+ }
+ if (topic == "provider-enabled") {
+ Social._updateProviderCache(providers);
+ Social._updateEnabledState(true);
+ Services.obs.notifyObservers(null, "social:" + topic, origin);
+ return;
+ }
+ if (topic == "provider-disabled") {
+ // a provider was removed from the list of providers, update states
+ Social._updateProviderCache(providers);
+ Social._updateEnabledState(providers.length > 0);
+ Services.obs.notifyObservers(null, "social:" + topic, origin);
+ return;
+ }
+ if (topic == "provider-update") {
+ // a provider has self-updated its manifest, we need to update our cache
+ // and reload the provider.
+ Social._updateProviderCache(providers);
+ let provider = Social._getProviderFromOrigin(origin);
+ provider.reload();
+ }
+ });
+ return deferred.promise;
+ },
+
+ _updateEnabledState: function(enable) {
+ for (let p of Social.providers) {
+ p.enabled = enable;
+ }
+ },
+
+ // Called to update our cache of providers and set the current provider
+ _updateProviderCache: function (providers) {
+ this.providers = providers;
+ Services.obs.notifyObservers(null, "social:providers-changed", null);
+ },
+
+ get enabled() {
+ return !this._disabledForSafeMode && this.providers.length > 0;
+ },
+
+ _getProviderFromOrigin: function (origin) {
+ for (let p of this.providers) {
+ if (p.origin == origin) {
+ return p;
+ }
+ }
+ return null;
+ },
+
+ getManifestByOrigin: function(origin) {
+ return SocialService.getManifestByOrigin(origin);
+ },
+
+ installProvider: function(data, installCallback, options={}) {
+ SocialService.installProvider(data, installCallback, options);
+ },
+
+ uninstallProvider: function(origin, aCallback) {
+ SocialService.uninstallProvider(origin, aCallback);
+ },
+
+ // Activation functionality
+ activateFromOrigin: function (origin, callback) {
+ // It's OK if the provider has already been activated - we still get called
+ // back with it.
+ SocialService.enableProvider(origin, callback);
+ }
+};
+
+function sizeSocialPanelToContent(panel, iframe, requestedSize) {
+ let doc = iframe.contentDocument;
+ if (!doc || !doc.body) {
+ return;
+ }
+ // We need an element to use for sizing our panel. See if the body defines
+ // an id for that element, otherwise use the body itself.
+ let body = doc.body;
+ let docEl = doc.documentElement;
+ let bodyId = body.getAttribute("contentid");
+ if (bodyId) {
+ body = doc.getElementById(bodyId) || doc.body;
+ }
+ // offsetHeight/Width don't include margins, so account for that.
+ let cs = doc.defaultView.getComputedStyle(body);
+ let width = Math.max(PANEL_MIN_WIDTH, docEl.offsetWidth);
+ let height = Math.max(PANEL_MIN_HEIGHT, docEl.offsetHeight);
+ // if the panel is preloaded prior to being shown, cs will be null. in that
+ // case use the minimum size for the panel until it is shown.
+ if (cs) {
+ let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom);
+ height = Math.max(computedHeight, height);
+ let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight);
+ width = Math.max(computedWidth, width);
+ }
+
+ // if our scrollHeight is still larger than the iframe, the css calculations
+ // above did not work for this site, increase the height. This can happen if
+ // the site increases its height for additional UI.
+ if (docEl.scrollHeight > iframe.boxObject.height)
+ height = docEl.scrollHeight;
+
+ // if a size was defined in the manifest use it as a minimum
+ if (requestedSize) {
+ if (requestedSize.height)
+ height = Math.max(height, requestedSize.height);
+ if (requestedSize.width)
+ width = Math.max(width, requestedSize.width);
+ }
+
+ // add the extra space used by the panel (toolbar, borders, etc) if the iframe
+ // has been loaded
+ if (iframe.boxObject.width && iframe.boxObject.height) {
+ // add extra space the panel needs if any
+ width += panel.boxObject.width - iframe.boxObject.width;
+ height += panel.boxObject.height - iframe.boxObject.height;
+ }
+
+ // using panel.sizeTo will ignore css transitions, set size via style
+ if (Math.abs(panel.boxObject.width - width) >= 2)
+ panel.style.width = width + "px";
+ if (Math.abs(panel.boxObject.height - height) >= 2)
+ panel.style.height = height + "px";
+}
+
+function DynamicResizeWatcher() {
+ this._mutationObserver = null;
+}
+
+DynamicResizeWatcher.prototype = {
+ start: function DynamicResizeWatcher_start(panel, iframe, requestedSize) {
+ this.stop(); // just in case...
+ let doc = iframe.contentDocument;
+ this._mutationObserver = new iframe.contentWindow.MutationObserver((mutations) => {
+ sizeSocialPanelToContent(panel, iframe, requestedSize);
+ });
+ // Observe anything that causes the size to change.
+ let config = {attributes: true, characterData: true, childList: true, subtree: true};
+ this._mutationObserver.observe(doc, config);
+ // and since this may be setup after the load event has fired we do an
+ // initial resize now.
+ sizeSocialPanelToContent(panel, iframe, requestedSize);
+ },
+ stop: function DynamicResizeWatcher_stop() {
+ if (this._mutationObserver) {
+ try {
+ this._mutationObserver.disconnect();
+ } catch (ex) {
+ // may get "TypeError: can't access dead object" which seems strange,
+ // but doesn't seem to indicate a real problem, so ignore it...
+ }
+ this._mutationObserver = null;
+ }
+ }
+}
+
+
+this.OpenGraphBuilder = {
+ generateEndpointURL: function(URLTemplate, pageData) {
+ // support for existing oexchange style endpoints by supporting their
+ // querystring arguments. parse the query string template and do
+ // replacements where necessary the query names may be different than ours,
+ // so we could see u=%{url} or url=%{url}
+ let [endpointURL, queryString] = URLTemplate.split("?");
+ let query = {};
+ if (queryString) {
+ queryString.split('&').forEach(function (val) {
+ let [name, value] = val.split('=');
+ let p = /%\{(.+)\}/.exec(value);
+ if (!p) {
+ // preserve non-template query vars
+ query[name] = value;
+ } else if (pageData[p[1]]) {
+ if (p[1] == "previews")
+ query[name] = pageData[p[1]][0];
+ else
+ query[name] = pageData[p[1]];
+ } else if (p[1] == "body") {
+ // build a body for emailers
+ let body = "";
+ if (pageData.title)
+ body += pageData.title + "\n\n";
+ if (pageData.description)
+ body += pageData.description + "\n\n";
+ if (pageData.text)
+ body += pageData.text + "\n\n";
+ body += pageData.url;
+ query["body"] = body;
+ }
+ });
+ // if the url template doesn't have title and no text was provided, add the title as the text.
+ if (!query.text && !query.title && pageData.title) {
+ query.text = pageData.title;
+ }
+ }
+ var str = [];
+ for (let p in query)
+ str.push(p + "=" + encodeURIComponent(query[p]));
+ if (str.length)
+ endpointURL = endpointURL + "?" + str.join("&");
+ return endpointURL;
+ },
+};
diff --git a/browser/modules/SocialService.jsm b/browser/modules/SocialService.jsm
new file mode 100644
index 000000000..95f5e0259
--- /dev/null
+++ b/browser/modules/SocialService.jsm
@@ -0,0 +1,1097 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["SocialService"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
+const ADDON_TYPE_SERVICE = "service";
+const ID_SUFFIX = "@services.mozilla.org";
+const STRING_TYPE_NAME = "type.%ID%.name";
+
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "etld",
+ "@mozilla.org/network/effective-tld-service;1",
+ "nsIEffectiveTLDService");
+
+/**
+ * The SocialService is the public API to social providers - it tracks which
+ * providers are installed and enabled, and is the entry-point for access to
+ * the provider itself.
+ */
+
+// Internal helper methods and state
+var SocialServiceInternal = {
+ get enabled() {
+ return this.providerArray.length > 0;
+ },
+
+ get providerArray() {
+ return Object.keys(this.providers).map(origin => this.providers[origin]);
+ },
+ *manifestsGenerator() {
+ // Retrieve the manifests of installed providers from prefs
+ let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
+ let prefs = MANIFEST_PREFS.getChildList("", []);
+ for (let pref of prefs) {
+ // we only consider manifests in user level prefs to be *installed*
+ if (!MANIFEST_PREFS.prefHasUserValue(pref))
+ continue;
+ try {
+ var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data);
+ if (manifest && typeof(manifest) == "object" && manifest.origin)
+ yield manifest;
+ } catch (err) {
+ Cu.reportError("SocialService: failed to load manifest: " + pref +
+ ", exception: " + err);
+ }
+ }
+ },
+ get manifests() {
+ return this.manifestsGenerator();
+ },
+ getManifestPrefname: function(origin) {
+ // Retrieve the prefname for a given origin/manifest.
+ // If no existing pref, return a generated prefname.
+ let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
+ let prefs = MANIFEST_PREFS.getChildList("", []);
+ for (let pref of prefs) {
+ try {
+ var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data);
+ if (manifest.origin == origin) {
+ return pref;
+ }
+ } catch (err) {
+ Cu.reportError("SocialService: failed to load manifest: " + pref +
+ ", exception: " + err);
+ }
+ }
+ let originUri = Services.io.newURI(origin, null, null);
+ return originUri.hostPort.replace('.', '-');
+ },
+ orderedProviders: function(aCallback) {
+ if (SocialServiceInternal.providerArray.length < 2) {
+ schedule(function () {
+ aCallback(SocialServiceInternal.providerArray);
+ });
+ return;
+ }
+ // query moz_hosts for frecency. since some providers may not have a
+ // frecency entry, we need to later sort on our own. We use the providers
+ // object below as an easy way to later record the frecency on the provider
+ // object from the query results.
+ let hosts = [];
+ let providers = {};
+
+ for (let p of SocialServiceInternal.providerArray) {
+ p.frecency = 0;
+ providers[p.domain] = p;
+ hosts.push(p.domain);
+ }
+
+ // cannot bind an array to stmt.params so we have to build the string
+ let stmt = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection.createAsyncStatement(
+ "SELECT host, frecency FROM moz_hosts WHERE host IN (" +
+ hosts.map(host => '"' + host + '"').join(",") + ") "
+ );
+
+ try {
+ stmt.executeAsync({
+ handleResult: function(aResultSet) {
+ let row;
+ while ((row = aResultSet.getNextRow())) {
+ let rh = row.getResultByName("host");
+ let frecency = row.getResultByName("frecency");
+ providers[rh].frecency = parseInt(frecency) || 0;
+ }
+ },
+ handleError: function(aError) {
+ Cu.reportError(aError.message + " (Result = " + aError.result + ")");
+ },
+ handleCompletion: function(aReason) {
+ // the query may not have returned all our providers, so we have
+ // stamped the frecency on the provider and sort here. This makes sure
+ // all enabled providers get sorted even with frecency zero.
+ let providerList = SocialServiceInternal.providerArray;
+ // reverse sort
+ aCallback(providerList.sort((a, b) => b.frecency - a.frecency));
+ }
+ });
+ } finally {
+ stmt.finalize();
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
+ initService();
+ let providers = {};
+ for (let manifest of this.manifests) {
+ try {
+ if (ActiveProviders.has(manifest.origin)) {
+ // enable the api when a provider is enabled
+ let provider = new SocialProvider(manifest);
+ providers[provider.origin] = provider;
+ }
+ } catch (err) {
+ Cu.reportError("SocialService: failed to load provider: " + manifest.origin +
+ ", exception: " + err);
+ }
+ }
+ return providers;
+});
+
+function getOriginActivationType(origin) {
+ // if this is an about uri, treat it as a directory
+ let URI = Services.io.newURI(origin, null, null);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(URI, {});
+ if (Services.scriptSecurityManager.isSystemPrincipal(principal) || origin == "moz-safe-about:home") {
+ return "internal";
+ }
+
+ let directories = Services.prefs.getCharPref("social.directories").split(',');
+ if (directories.indexOf(origin) >= 0)
+ return "directory";
+
+ return "foreign";
+}
+
+var ActiveProviders = {
+ get _providers() {
+ delete this._providers;
+ this._providers = {};
+ try {
+ let pref = Services.prefs.getComplexValue("social.activeProviders",
+ Ci.nsISupportsString);
+ this._providers = JSON.parse(pref);
+ } catch (ex) {}
+ return this._providers;
+ },
+
+ has: function (origin) {
+ return (origin in this._providers);
+ },
+
+ add: function (origin) {
+ this._providers[origin] = 1;
+ this._deferredTask.arm();
+ },
+
+ delete: function (origin) {
+ delete this._providers[origin];
+ this._deferredTask.arm();
+ },
+
+ flush: function () {
+ this._deferredTask.disarm();
+ this._persist();
+ },
+
+ get _deferredTask() {
+ delete this._deferredTask;
+ return this._deferredTask = new DeferredTask(this._persist.bind(this), 0);
+ },
+
+ _persist: function () {
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(this._providers);
+ Services.prefs.setComplexValue("social.activeProviders",
+ Ci.nsISupportsString, string);
+ }
+};
+
+function migrateSettings() {
+ let activeProviders, enabled;
+ try {
+ activeProviders = Services.prefs.getCharPref("social.activeProviders");
+ } catch (e) {
+ // not set, we'll check if we need to migrate older prefs
+ }
+ if (Services.prefs.prefHasUserValue("social.enabled")) {
+ enabled = Services.prefs.getBoolPref("social.enabled");
+ }
+ if (activeProviders) {
+ // migration from fx21 to fx22 or later
+ // ensure any *builtin* provider in activeproviders is in user level prefs
+ for (let origin in ActiveProviders._providers) {
+ let prefname;
+ let manifest;
+ let defaultManifest;
+ try {
+ prefname = getPrefnameFromOrigin(origin);
+ manifest = JSON.parse(Services.prefs.getComplexValue(prefname, Ci.nsISupportsString).data);
+ } catch (e) {
+ // Our preference is missing or bad, remove from ActiveProviders and
+ // continue. This is primarily an error-case and should only be
+ // reached by either messing with preferences or hitting the one or
+ // two days of nightly that ran into it, so we'll flush right away.
+ ActiveProviders.delete(origin);
+ ActiveProviders.flush();
+ continue;
+ }
+ let needsUpdate = !manifest.updateDate;
+ // fx23 may have built-ins with shareURL
+ try {
+ defaultManifest = Services.prefs.getDefaultBranch(null)
+ .getComplexValue(prefname, Ci.nsISupportsString).data;
+ defaultManifest = JSON.parse(defaultManifest);
+ } catch (e) {
+ // not a built-in, continue
+ }
+ if (defaultManifest) {
+ if (defaultManifest.shareURL && !manifest.shareURL) {
+ manifest.shareURL = defaultManifest.shareURL;
+ needsUpdate = true;
+ }
+ if (defaultManifest.version && (!manifest.version || defaultManifest.version > manifest.version)) {
+ manifest = defaultManifest;
+ needsUpdate = true;
+ }
+ }
+ if (needsUpdate) {
+ // the provider was installed with an older build, so we will update the
+ // timestamp and ensure the manifest is in user prefs
+ delete manifest.builtin;
+ // we're potentially updating for share, so always mark the updateDate
+ manifest.updateDate = Date.now();
+ if (!manifest.installDate)
+ manifest.installDate = 0; // we don't know when it was installed
+
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ Services.prefs.setComplexValue(prefname, Ci.nsISupportsString, string);
+ }
+ // as of fx 29, we no longer rely on social.enabled. migration from prior
+ // versions should disable all service addons if social.enabled=false
+ if (enabled === false) {
+ ActiveProviders.delete(origin);
+ }
+ }
+ ActiveProviders.flush();
+ Services.prefs.clearUserPref("social.enabled");
+ return;
+ }
+
+ // primary migration from pre-fx21
+ let active;
+ try {
+ active = Services.prefs.getBoolPref("social.active");
+ } catch (e) {}
+ if (!active)
+ return;
+
+ // primary difference from SocialServiceInternal.manifests is that we
+ // only read the default branch here.
+ let manifestPrefs = Services.prefs.getDefaultBranch("social.manifest.");
+ let prefs = manifestPrefs.getChildList("", []);
+ for (let pref of prefs) {
+ try {
+ let manifest;
+ try {
+ manifest = JSON.parse(manifestPrefs.getComplexValue(pref, Ci.nsISupportsString).data);
+ } catch (e) {
+ // bad or missing preference, we wont update this one.
+ continue;
+ }
+ if (manifest && typeof(manifest) == "object" && manifest.origin) {
+ // our default manifests have been updated with the builtin flags as of
+ // fx22, delete it so we can set the user-pref
+ delete manifest.builtin;
+ if (!manifest.updateDate) {
+ manifest.updateDate = Date.now();
+ manifest.installDate = 0; // we don't know when it was installed
+ }
+
+ let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ // pref here is just the branch name, set the full pref name
+ Services.prefs.setComplexValue("social.manifest." + pref, Ci.nsISupportsString, string);
+ ActiveProviders.add(manifest.origin);
+ ActiveProviders.flush();
+ // social.active was used at a time that there was only one
+ // builtin, we'll assume that is still the case
+ return;
+ }
+ } catch (err) {
+ Cu.reportError("SocialService: failed to load manifest: " + pref + ", exception: " + err);
+ }
+ }
+}
+
+function initService() {
+ Services.obs.addObserver(function xpcomShutdown() {
+ ActiveProviders.flush();
+ SocialService._providerListeners = null;
+ Services.obs.removeObserver(xpcomShutdown, "xpcom-shutdown");
+ }, "xpcom-shutdown", false);
+
+ try {
+ migrateSettings();
+ } catch (e) {
+ // no matter what, if migration fails we do not want to render social
+ // unusable. Worst case scenario is that, when upgrading Firefox, previously
+ // enabled providers are not migrated.
+ Cu.reportError("Error migrating social settings: " + e);
+ }
+}
+
+function schedule(callback) {
+ Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+}
+
+// Public API
+this.SocialService = {
+ get hasEnabledProviders() {
+ // used as an optimization during startup, can be used to check if further
+ // initialization should be done (e.g. creating the instances of
+ // SocialProvider and turning on UI). ActiveProviders may have changed and
+ // not yet flushed so we check the active providers array
+ for (let p in ActiveProviders._providers) {
+ return true;
+ }
+ return false;
+ },
+ get enabled() {
+ return SocialServiceInternal.enabled;
+ },
+ set enabled(val) {
+ throw new Error("not allowed to set SocialService.enabled");
+ },
+
+ // Enables a provider, the manifest must already exist in prefs. The provider
+ // may or may not have previously been added. onDone is always called
+ // - with null if no such provider exists, or the activated provider on
+ // success.
+ enableProvider: function enableProvider(origin, onDone) {
+ if (SocialServiceInternal.providers[origin]) {
+ schedule(function() {
+ onDone(SocialServiceInternal.providers[origin]);
+ });
+ return;
+ }
+ let manifest = SocialService.getManifestByOrigin(origin);
+ if (manifest) {
+ let addon = new AddonWrapper(manifest);
+ AddonManagerPrivate.callAddonListeners("onEnabling", addon, false);
+ addon.pendingOperations |= AddonManager.PENDING_ENABLE;
+ this.addProvider(manifest, onDone);
+ addon.pendingOperations -= AddonManager.PENDING_ENABLE;
+ AddonManagerPrivate.callAddonListeners("onEnabled", addon);
+ return;
+ }
+ schedule(function() {
+ onDone(null);
+ });
+ },
+
+ // Adds a provider given a manifest, and returns the added provider.
+ addProvider: function addProvider(manifest, onDone) {
+ if (SocialServiceInternal.providers[manifest.origin])
+ throw new Error("SocialService.addProvider: provider with this origin already exists");
+
+ // enable the api when a provider is enabled
+ let provider = new SocialProvider(manifest);
+ SocialServiceInternal.providers[provider.origin] = provider;
+ ActiveProviders.add(provider.origin);
+
+ this.getOrderedProviderList(function (providers) {
+ this._notifyProviderListeners("provider-enabled", provider.origin, providers);
+ if (onDone)
+ onDone(provider);
+ }.bind(this));
+ },
+
+ // Removes a provider with the given origin, and notifies when the removal is
+ // complete.
+ disableProvider: function disableProvider(origin, onDone) {
+ if (!(origin in SocialServiceInternal.providers))
+ throw new Error("SocialService.disableProvider: no provider with origin " + origin + " exists!");
+
+ let provider = SocialServiceInternal.providers[origin];
+ let manifest = SocialService.getManifestByOrigin(origin);
+ let addon = manifest && new AddonWrapper(manifest);
+ if (addon) {
+ AddonManagerPrivate.callAddonListeners("onDisabling", addon, false);
+ addon.pendingOperations |= AddonManager.PENDING_DISABLE;
+ }
+ provider.enabled = false;
+
+ ActiveProviders.delete(provider.origin);
+
+ delete SocialServiceInternal.providers[origin];
+
+ if (addon) {
+ // we have to do this now so the addon manager ui will update an uninstall
+ // correctly.
+ addon.pendingOperations -= AddonManager.PENDING_DISABLE;
+ AddonManagerPrivate.callAddonListeners("onDisabled", addon);
+ }
+
+ this.getOrderedProviderList(function (providers) {
+ this._notifyProviderListeners("provider-disabled", origin, providers);
+ if (onDone)
+ onDone();
+ }.bind(this));
+ },
+
+ // Returns a single provider object with the specified origin. The provider
+ // must be "installed" (ie, in ActiveProviders)
+ getProvider: function getProvider(origin, onDone) {
+ schedule((function () {
+ onDone(SocialServiceInternal.providers[origin] || null);
+ }).bind(this));
+ },
+
+ // Returns an unordered array of installed providers
+ getProviderList: function(onDone) {
+ schedule(function () {
+ onDone(SocialServiceInternal.providerArray);
+ });
+ },
+
+ getManifestByOrigin: function(origin) {
+ for (let manifest of SocialServiceInternal.manifests) {
+ if (origin == manifest.origin) {
+ return manifest;
+ }
+ }
+ return null;
+ },
+
+ // Returns an array of installed providers, sorted by frecency
+ getOrderedProviderList: function(onDone) {
+ SocialServiceInternal.orderedProviders(onDone);
+ },
+
+ getOriginActivationType: function (origin) {
+ return getOriginActivationType(origin);
+ },
+
+ _providerListeners: new Map(),
+ registerProviderListener: function registerProviderListener(listener) {
+ this._providerListeners.set(listener, 1);
+ },
+ unregisterProviderListener: function unregisterProviderListener(listener) {
+ this._providerListeners.delete(listener);
+ },
+
+ _notifyProviderListeners: function (topic, origin, providers) {
+ for (let [listener, ] of this._providerListeners) {
+ try {
+ listener(topic, origin, providers);
+ } catch (ex) {
+ Components.utils.reportError("SocialService: provider listener threw an exception: " + ex);
+ }
+ }
+ },
+
+ _manifestFromData: function(type, data, installOrigin) {
+ let featureURLs = ['shareURL'];
+ let resolveURLs = featureURLs.concat(['postActivationURL']);
+
+ if (type == 'directory' || type == 'internal') {
+ // directory provided manifests must have origin in manifest, use that
+ if (!data['origin']) {
+ Cu.reportError("SocialService.manifestFromData directory service provided manifest without origin.");
+ return null;
+ }
+ installOrigin = data.origin;
+ }
+ // force/fixup origin
+ let URI = Services.io.newURI(installOrigin, null, null);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(URI, {});
+ data.origin = principal.origin;
+
+ // iconURL and name are required
+ let providerHasFeatures = featureURLs.some(url => data[url]);
+ if (!providerHasFeatures) {
+ Cu.reportError("SocialService.manifestFromData manifest missing required urls.");
+ return null;
+ }
+ if (!data['name'] || !data['iconURL']) {
+ Cu.reportError("SocialService.manifestFromData manifest missing name or iconURL.");
+ return null;
+ }
+ for (let url of resolveURLs) {
+ if (data[url]) {
+ try {
+ let resolved = Services.io.newURI(principal.URI.resolve(data[url]), null, null);
+ if (!(resolved.schemeIs("http") || resolved.schemeIs("https"))) {
+ Cu.reportError("SocialService.manifestFromData unsupported scheme '" + resolved.scheme + "' for " + principal.origin);
+ return null;
+ }
+ data[url] = resolved.spec;
+ } catch (e) {
+ Cu.reportError("SocialService.manifestFromData unable to resolve '" + url + "' for " + principal.origin);
+ return null;
+ }
+ }
+ }
+ return data;
+ },
+
+ _showInstallNotification: function(data, aAddonInstaller) {
+ let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+
+ // internal/directory activations need to use the manifest origin, any other
+ // use the domain activation is occurring on
+ let url = data.url;
+ if (data.installType == "internal" || data.installType == "directory") {
+ url = data.manifest.origin;
+ }
+ let requestingURI = Services.io.newURI(url, null, null);
+ let productName = brandBundle.GetStringFromName("brandShortName");
+
+ let message = browserBundle.formatStringFromName("service.install.description",
+ [requestingURI.host, productName], 2);
+
+ let action = {
+ label: browserBundle.GetStringFromName("service.install.ok.label"),
+ accessKey: browserBundle.GetStringFromName("service.install.ok.accesskey"),
+ callback: function() {
+ aAddonInstaller.install();
+ },
+ };
+
+ let options = {
+ learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api",
+ };
+ let anchor = "servicesInstall-notification-icon";
+ let notificationid = "servicesInstall";
+ data.window.PopupNotifications.show(data.window.gBrowser.selectedBrowser,
+ notificationid, message, anchor,
+ action, [], options);
+ },
+
+ installProvider: function(data, installCallback, options={}) {
+ data.installType = getOriginActivationType(data.origin);
+ // if we get data, we MUST have a valid manifest generated from the data
+ let manifest = this._manifestFromData(data.installType, data.manifest, data.origin);
+ if (!manifest)
+ throw new Error("SocialService.installProvider: service configuration is invalid from " + data.url);
+
+ let addon = new AddonWrapper(manifest);
+ if (addon && addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+ throw new Error("installProvider: provider with origin [" +
+ data.origin + "] is blocklisted");
+ // manifestFromData call above will enforce correct origin. To support
+ // activation from about: uris, we need to be sure to use the updated
+ // origin on the manifest.
+ data.manifest = manifest;
+ let id = getAddonIDFromOrigin(manifest.origin);
+ AddonManager.getAddonByID(id, function(aAddon) {
+ if (aAddon && aAddon.userDisabled) {
+ aAddon.cancelUninstall();
+ aAddon.userDisabled = false;
+ }
+ schedule(function () {
+ try {
+ this._installProvider(data, options, aManifest => {
+ this._notifyProviderListeners("provider-installed", aManifest.origin);
+ installCallback(aManifest);
+ });
+ } catch (e) {
+ Cu.reportError("Activation failed: " + e);
+ installCallback(null);
+ }
+ }.bind(this));
+ }.bind(this));
+ },
+
+ _installProvider: function(data, options, installCallback) {
+ if (!data.manifest)
+ throw new Error("Cannot install provider without manifest data");
+
+ if (data.installType == "foreign" && !Services.prefs.getBoolPref("social.remote-install.enabled"))
+ throw new Error("Remote install of services is disabled");
+
+ // if installing from any website, the install must happen over https.
+ // "internal" are installs from about:home or similar
+ if (data.installType != "internal" && !Services.io.newURI(data.origin, null, null).schemeIs("https")) {
+ throw new Error("attempt to activate provider over unsecured channel: " + data.origin);
+ }
+
+ let installer = new AddonInstaller(data.url, data.manifest, installCallback);
+ let bypassPanel = options.bypassInstallPanel ||
+ (data.installType == "internal" && data.manifest.oneclick);
+ if (bypassPanel)
+ installer.install();
+ else
+ this._showInstallNotification(data, installer);
+ },
+
+ createWrapper: function(manifest) {
+ return new AddonWrapper(manifest);
+ },
+
+ /**
+ * updateProvider is used from the worker to self-update. Since we do not
+ * have knowledge of the currently selected provider here, we will notify
+ * the front end to deal with any reload.
+ */
+ updateProvider: function(aUpdateOrigin, aManifest) {
+ let installType = this.getOriginActivationType(aUpdateOrigin);
+ // if we get data, we MUST have a valid manifest generated from the data
+ let manifest = this._manifestFromData(installType, aManifest, aUpdateOrigin);
+ if (!manifest)
+ throw new Error("SocialService.installProvider: service configuration is invalid from " + aUpdateOrigin);
+
+ // overwrite the preference
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ Services.prefs.setComplexValue(getPrefnameFromOrigin(manifest.origin), Ci.nsISupportsString, string);
+
+ // overwrite the existing provider then notify the front end so it can
+ // handle any reload that might be necessary.
+ if (ActiveProviders.has(manifest.origin)) {
+ let provider = SocialServiceInternal.providers[manifest.origin];
+ provider.enabled = false;
+ provider = new SocialProvider(manifest);
+ SocialServiceInternal.providers[provider.origin] = provider;
+ // update the cache and ui, reload provider if necessary
+ this.getOrderedProviderList(providers => {
+ this._notifyProviderListeners("provider-update", provider.origin, providers);
+ });
+ }
+
+ },
+
+ uninstallProvider: function(origin, aCallback) {
+ let manifest = SocialService.getManifestByOrigin(origin);
+ let addon = new AddonWrapper(manifest);
+ addon.uninstall(aCallback);
+ }
+};
+
+/**
+ * The SocialProvider object represents a social provider.
+ *
+ * @constructor
+ * @param {jsobj} object representing the manifest file describing this provider
+ * @param {bool} boolean indicating whether this provider is "built in"
+ */
+function SocialProvider(input) {
+ if (!input.name)
+ throw new Error("SocialProvider must be passed a name");
+ if (!input.origin)
+ throw new Error("SocialProvider must be passed an origin");
+
+ let addon = new AddonWrapper(input);
+ if (addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+ throw new Error("SocialProvider: provider with origin [" +
+ input.origin + "] is blocklisted");
+
+ this.name = input.name;
+ this.iconURL = input.iconURL;
+ this.icon32URL = input.icon32URL;
+ this.icon64URL = input.icon64URL;
+ this.shareURL = input.shareURL;
+ this.postActivationURL = input.postActivationURL;
+ this.origin = input.origin;
+ let originUri = Services.io.newURI(input.origin, null, null);
+ this.principal = Services.scriptSecurityManager.createCodebasePrincipal(originUri, {});
+ this.ambientNotificationIcons = {};
+ this.errorState = null;
+ this.frecency = 0;
+
+ try {
+ this.domain = etld.getBaseDomainFromHost(originUri.host);
+ } catch (e) {
+ this.domain = originUri.host;
+ }
+}
+
+SocialProvider.prototype = {
+ reload: function() {
+ // calling terminate/activate does not set the enabled state whereas setting
+ // enabled will call terminate/activate
+ this.enabled = false;
+ this.enabled = true;
+ Services.obs.notifyObservers(null, "social:provider-reload", this.origin);
+ },
+
+ // Provider enabled/disabled state.
+ _enabled: false,
+ get enabled() {
+ return this._enabled;
+ },
+ set enabled(val) {
+ let enable = !!val;
+ if (enable == this._enabled)
+ return;
+
+ this._enabled = enable;
+
+ if (enable) {
+ this._activate();
+ } else {
+ this._terminate();
+ }
+ },
+
+ get manifest() {
+ return SocialService.getManifestByOrigin(this.origin);
+ },
+
+ getPageSize: function(name) {
+ let manifest = this.manifest;
+ if (manifest && manifest.pageSize)
+ return manifest.pageSize[name];
+ return undefined;
+ },
+
+ // Internal helper methods
+ _activate: function _activate() {
+ },
+
+ _terminate: function _terminate() {
+ this.errorState = null;
+ },
+
+ /**
+ * Checks if a given URI is of the same origin as the provider.
+ *
+ * Returns true or false.
+ *
+ * @param {URI or string} uri
+ */
+ isSameOrigin: function isSameOrigin(uri, allowIfInheritsPrincipal) {
+ if (!uri)
+ return false;
+ if (typeof uri == "string") {
+ try {
+ uri = Services.io.newURI(uri, null, null);
+ } catch (ex) {
+ // an invalid URL can't be loaded!
+ return false;
+ }
+ }
+ try {
+ this.principal.checkMayLoad(
+ uri, // the thing to check.
+ false, // reportError - we do our own reporting when necessary.
+ allowIfInheritsPrincipal
+ );
+ return true;
+ } catch (ex) {
+ return false;
+ }
+ },
+
+ /**
+ * Resolve partial URLs for a provider.
+ *
+ * Returns nsIURI object or null on failure
+ *
+ * @param {string} url
+ */
+ resolveUri: function resolveUri(url) {
+ try {
+ let fullURL = this.principal.URI.resolve(url);
+ return Services.io.newURI(fullURL, null, null);
+ } catch (ex) {
+ Cu.reportError("mozSocial: failed to resolve window URL: " + url + "; " + ex);
+ return null;
+ }
+ }
+};
+
+function getAddonIDFromOrigin(origin) {
+ let originUri = Services.io.newURI(origin, null, null);
+ return originUri.host + ID_SUFFIX;
+}
+
+function getPrefnameFromOrigin(origin) {
+ return "social.manifest." + SocialServiceInternal.getManifestPrefname(origin);
+}
+
+function AddonInstaller(sourceURI, aManifest, installCallback) {
+ aManifest.updateDate = Date.now();
+ // get the existing manifest for installDate
+ let manifest = SocialService.getManifestByOrigin(aManifest.origin);
+ let isNewInstall = !manifest;
+ if (manifest && manifest.installDate)
+ aManifest.installDate = manifest.installDate;
+ else
+ aManifest.installDate = aManifest.updateDate;
+
+ this.sourceURI = sourceURI;
+ this.install = function() {
+ let addon = this.addon;
+ if (isNewInstall) {
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null, addon, null, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", addon, false);
+ }
+
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(aManifest);
+ Services.prefs.setComplexValue(getPrefnameFromOrigin(aManifest.origin), Ci.nsISupportsString, string);
+
+ if (isNewInstall) {
+ AddonManagerPrivate.callAddonListeners("onInstalled", addon);
+ }
+ installCallback(aManifest);
+ };
+ this.cancel = function() {
+ Services.prefs.clearUserPref(getPrefnameFromOrigin(aManifest.origin));
+ };
+ this.addon = new AddonWrapper(aManifest);
+}
+
+var SocialAddonProvider = {
+ startup: function() {},
+
+ shutdown: function() {},
+
+ updateAddonAppDisabledStates: function() {
+ // we wont bother with "enabling" services that are released from blocklist
+ for (let manifest of SocialServiceInternal.manifests) {
+ try {
+ if (ActiveProviders.has(manifest.origin)) {
+ let addon = new AddonWrapper(manifest);
+ if (addon.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+ SocialService.disableProvider(manifest.origin);
+ }
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ getAddonByID: function(aId, aCallback) {
+ for (let manifest of SocialServiceInternal.manifests) {
+ if (aId == getAddonIDFromOrigin(manifest.origin)) {
+ aCallback(new AddonWrapper(manifest));
+ return;
+ }
+ }
+ aCallback(null);
+ },
+
+ getAddonsByTypes: function(aTypes, aCallback) {
+ if (aTypes && aTypes.indexOf(ADDON_TYPE_SERVICE) == -1) {
+ aCallback([]);
+ return;
+ }
+ aCallback([...SocialServiceInternal.manifests].map(a => new AddonWrapper(a)));
+ },
+
+ removeAddon: function(aAddon, aCallback) {
+ AddonManagerPrivate.callAddonListeners("onUninstalling", aAddon, false);
+ aAddon.pendingOperations |= AddonManager.PENDING_UNINSTALL;
+ Services.prefs.clearUserPref(getPrefnameFromOrigin(aAddon.manifest.origin));
+ aAddon.pendingOperations -= AddonManager.PENDING_UNINSTALL;
+ AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
+ SocialService._notifyProviderListeners("provider-uninstalled", aAddon.manifest.origin);
+ if (aCallback)
+ schedule(aCallback);
+ }
+};
+
+
+function AddonWrapper(aManifest) {
+ this.manifest = aManifest;
+ this.id = getAddonIDFromOrigin(this.manifest.origin);
+ this._pending = AddonManager.PENDING_NONE;
+}
+AddonWrapper.prototype = {
+ get type() {
+ return ADDON_TYPE_SERVICE;
+ },
+
+ get appDisabled() {
+ return this.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED;
+ },
+
+ set softDisabled(val) {
+ this.userDisabled = val;
+ },
+
+ get softDisabled() {
+ return this.userDisabled;
+ },
+
+ get isCompatible() {
+ return true;
+ },
+
+ get isPlatformCompatible() {
+ return true;
+ },
+
+ get scope() {
+ return AddonManager.SCOPE_PROFILE;
+ },
+
+ get foreignInstall() {
+ return false;
+ },
+
+ isCompatibleWith: function(appVersion, platformVersion) {
+ return true;
+ },
+
+ get providesUpdatesSecurely() {
+ return true;
+ },
+
+ get blocklistState() {
+ return Services.blocklist.getAddonBlocklistState(this);
+ },
+
+ get blocklistURL() {
+ return Services.blocklist.getAddonBlocklistURL(this);
+ },
+
+ get screenshots() {
+ return [];
+ },
+
+ get pendingOperations() {
+ return this._pending || AddonManager.PENDING_NONE;
+ },
+ set pendingOperations(val) {
+ this._pending = val;
+ },
+
+ get operationsRequiringRestart() {
+ return AddonManager.OP_NEEDS_RESTART_NONE;
+ },
+
+ get size() {
+ return null;
+ },
+
+ get permissions() {
+ let permissions = 0;
+ // any "user defined" manifest can be removed
+ if (Services.prefs.prefHasUserValue(getPrefnameFromOrigin(this.manifest.origin)))
+ permissions = AddonManager.PERM_CAN_UNINSTALL;
+ if (!this.appDisabled) {
+ if (this.userDisabled) {
+ permissions |= AddonManager.PERM_CAN_ENABLE;
+ } else {
+ permissions |= AddonManager.PERM_CAN_DISABLE;
+ }
+ }
+ return permissions;
+ },
+
+ findUpdates: function(listener, reason, appVersion, platformVersion) {
+ if ("onNoCompatibilityUpdateAvailable" in listener)
+ listener.onNoCompatibilityUpdateAvailable(this);
+ if ("onNoUpdateAvailable" in listener)
+ listener.onNoUpdateAvailable(this);
+ if ("onUpdateFinished" in listener)
+ listener.onUpdateFinished(this);
+ },
+
+ get isActive() {
+ return ActiveProviders.has(this.manifest.origin);
+ },
+
+ get name() {
+ return this.manifest.name;
+ },
+ get version() {
+ return this.manifest.version ? this.manifest.version.toString() : "";
+ },
+
+ get iconURL() {
+ return this.manifest.icon32URL ? this.manifest.icon32URL : this.manifest.iconURL;
+ },
+ get icon64URL() {
+ return this.manifest.icon64URL;
+ },
+ get icons() {
+ let icons = {
+ 16: this.manifest.iconURL
+ };
+ if (this.manifest.icon32URL)
+ icons[32] = this.manifest.icon32URL;
+ if (this.manifest.icon64URL)
+ icons[64] = this.manifest.icon64URL;
+ return icons;
+ },
+
+ get description() {
+ return this.manifest.description;
+ },
+ get homepageURL() {
+ return this.manifest.homepageURL;
+ },
+ get defaultLocale() {
+ return this.manifest.defaultLocale;
+ },
+ get selectedLocale() {
+ return this.manifest.selectedLocale;
+ },
+
+ get installDate() {
+ return this.manifest.installDate ? new Date(this.manifest.installDate) : null;
+ },
+ get updateDate() {
+ return this.manifest.updateDate ? new Date(this.manifest.updateDate) : null;
+ },
+
+ get creator() {
+ return new AddonManagerPrivate.AddonAuthor(this.manifest.author);
+ },
+
+ get userDisabled() {
+ return this.appDisabled || !ActiveProviders.has(this.manifest.origin);
+ },
+
+ set userDisabled(val) {
+ if (val == this.userDisabled)
+ return val;
+ if (val) {
+ SocialService.disableProvider(this.manifest.origin);
+ } else if (!this.appDisabled) {
+ SocialService.enableProvider(this.manifest.origin);
+ }
+ return val;
+ },
+
+ uninstall: function(aCallback) {
+ let prefName = getPrefnameFromOrigin(this.manifest.origin);
+ if (Services.prefs.prefHasUserValue(prefName)) {
+ if (ActiveProviders.has(this.manifest.origin)) {
+ SocialService.disableProvider(this.manifest.origin, function() {
+ SocialAddonProvider.removeAddon(this, aCallback);
+ }.bind(this));
+ } else {
+ SocialAddonProvider.removeAddon(this, aCallback);
+ }
+ } else {
+ schedule(aCallback);
+ }
+ },
+
+ cancelUninstall: function() {
+ this._pending -= AddonManager.PENDING_UNINSTALL;
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
+ }
+};
+
+
+AddonManagerPrivate.registerProvider(SocialAddonProvider, [
+ new AddonManagerPrivate.AddonType(ADDON_TYPE_SERVICE, URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 10000)
+]);
diff --git a/browser/modules/TransientPrefs.jsm b/browser/modules/TransientPrefs.jsm
new file mode 100644
index 000000000..30ba6a152
--- /dev/null
+++ b/browser/modules/TransientPrefs.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["TransientPrefs"];
+
+Components.utils.import("resource://gre/modules/Preferences.jsm");
+
+var prefVisibility = new Map;
+
+/* Use for preferences that should only be visible when they've been modified.
+ When reset to their default state, they remain visible until restarting the
+ application. */
+
+this.TransientPrefs = {
+ prefShouldBeVisible: function (prefName) {
+ if (Preferences.isSet(prefName))
+ prefVisibility.set(prefName, true);
+
+ return !!prefVisibility.get(prefName);
+ }
+};
diff --git a/browser/modules/URLBarZoom.jsm b/browser/modules/URLBarZoom.jsm
new file mode 100644
index 000000000..3e1c0f707
--- /dev/null
+++ b/browser/modules/URLBarZoom.jsm
@@ -0,0 +1,51 @@
+// -*- 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 = [ "URLBarZoom" ];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var URLBarZoom = {
+
+ init: function(aWindow) {
+ // Register ourselves with the service so we know when the zoom prefs change.
+ Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomChange", false);
+ Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomReset", false);
+ Services.obs.addObserver(updateZoomButton, "browser-fullZoom:location-change", false);
+ },
+}
+
+function updateZoomButton(aSubject, aTopic) {
+ let win = aSubject.ownerDocument.defaultView;
+ let customizableZoomControls = win.document.getElementById("zoom-controls");
+ let zoomResetButton = win.document.getElementById("urlbar-zoom-button");
+ let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
+
+ // Ensure that zoom controls haven't already been added to browser in Customize Mode
+ if (customizableZoomControls &&
+ customizableZoomControls.getAttribute("cui-areatype") == "toolbar") {
+ zoomResetButton.hidden = true;
+ return;
+ }
+ if (zoomFactor != 100) {
+ // Check if zoom button is visible and update label if it is
+ if (zoomResetButton.hidden) {
+ zoomResetButton.hidden = false;
+ }
+ // Only allow pulse animation for zoom changes, not tab switching
+ if (aTopic != "browser-fullZoom:location-change") {
+ zoomResetButton.setAttribute("animate", "true");
+ } else {
+ zoomResetButton.removeAttribute("animate");
+ }
+ zoomResetButton.setAttribute("label",
+ win.gNavigatorBundle.getFormattedString("urlbar-zoom-button.label", [zoomFactor]));
+ // Hide button if zoom is at 100%
+ } else {
+ zoomResetButton.hidden = true;
+ }
+}
diff --git a/browser/modules/Windows8WindowFrameColor.jsm b/browser/modules/Windows8WindowFrameColor.jsm
new file mode 100644
index 000000000..911333747
--- /dev/null
+++ b/browser/modules/Windows8WindowFrameColor.jsm
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+const {interfaces: Ci, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = ["Windows8WindowFrameColor"];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+var Registry = Cu.import("resource://gre/modules/WindowsRegistry.jsm").WindowsRegistry;
+
+var Windows8WindowFrameColor = {
+ _windowFrameColor: null,
+
+ get: function() {
+ if (this._windowFrameColor)
+ return this._windowFrameColor;
+
+ const HKCU = Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER;
+ const dwmKey = "Software\\Microsoft\\Windows\\DWM";
+ let customizationColor = Registry.readRegKey(HKCU, dwmKey,
+ "ColorizationColor");
+ if (customizationColor == undefined) {
+ // Seems to be the default color (hardcoded because of bug 1065998)
+ return [158, 158, 158];
+ }
+
+ // The color returned from the Registry is in decimal form.
+ let customizationColorHex = customizationColor.toString(16);
+
+ // Zero-pad the number just to make sure that it is 8 digits.
+ customizationColorHex = ("00000000" + customizationColorHex).substr(-8);
+ let customizationColorArray = customizationColorHex.match(/../g);
+ let [, fgR, fgG, fgB] = customizationColorArray.map(val => parseInt(val, 16));
+ let colorizationColorBalance = Registry.readRegKey(HKCU, dwmKey,
+ "ColorizationColorBalance");
+ if (colorizationColorBalance == undefined) {
+ colorizationColorBalance = 78;
+ }
+
+ // Window frame base color when Color Intensity is at 0, see bug 1004576.
+ let frameBaseColor = 217;
+ let alpha = colorizationColorBalance / 100;
+
+ // Alpha-blend the foreground color with the frame base color.
+ let r = Math.round(fgR * alpha + frameBaseColor * (1 - alpha));
+ let g = Math.round(fgG * alpha + frameBaseColor * (1 - alpha));
+ let b = Math.round(fgB * alpha + frameBaseColor * (1 - alpha));
+ return this._windowFrameColor = [r, g, b];
+ },
+};
diff --git a/browser/modules/WindowsJumpLists.jsm b/browser/modules/WindowsJumpLists.jsm
new file mode 100644
index 000000000..4df87b47a
--- /dev/null
+++ b/browser/modules/WindowsJumpLists.jsm
@@ -0,0 +1,579 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Constants
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// Stop updating jumplists after some idle time.
+const IDLE_TIMEOUT_SECONDS = 5 * 60;
+
+// Prefs
+const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
+const PREF_TASKBAR_ENABLED = "enabled";
+const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
+const PREF_TASKBAR_FREQUENT = "frequent.enabled";
+const PREF_TASKBAR_RECENT = "recent.enabled";
+const PREF_TASKBAR_TASKS = "tasks.enabled";
+const PREF_TASKBAR_REFRESH = "refreshInSeconds";
+
+// Hash keys for pendingStatements.
+const LIST_TYPE = {
+ FREQUENT: 0
+, RECENT: 1
+}
+
+/**
+ * Exports
+ */
+
+this.EXPORTED_SYMBOLS = [
+ "WinTaskbarJumpList",
+];
+
+/**
+ * Smart getters
+ */
+
+XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
+ return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
+});
+
+XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
+ return Services.strings
+ .createBundle("chrome://browser/locale/taskbar.properties");
+});
+
+XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ return PlacesUtils;
+});
+
+XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
+ Components.utils.import("resource://gre/modules/NetUtil.jsm");
+ return NetUtil;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "_idle",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
+ "@mozilla.org/windows-taskbar;1",
+ "nsIWinTaskbar");
+
+XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
+ "@mozilla.org/browser/shell-service;1",
+ "nsIWindowsShellService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+/**
+ * Global functions
+ */
+
+function _getString(name) {
+ return _stringBundle.GetStringFromName(name);
+}
+
+// Task list configuration data object.
+
+var tasksCfg = [
+ /**
+ * Task configuration options: title, description, args, iconIndex, open, close.
+ *
+ * title - Task title displayed in the list. (strings in the table are temp fillers.)
+ * description - Tooltip description on the list item.
+ * args - Command line args to invoke the task.
+ * iconIndex - Optional win icon index into the main application for the
+ * list item.
+ * open - Boolean indicates if the command should be visible after the browser opens.
+ * close - Boolean indicates if the command should be visible after the browser closes.
+ */
+ // Open new tab
+ {
+ get title() { return _getString("taskbar.tasks.newTab.label"); },
+ get description() { return _getString("taskbar.tasks.newTab.description"); },
+ args: "-new-tab about:blank",
+ iconIndex: 3, // New window icon
+ open: true,
+ close: true, // The jump list already has an app launch icon, but
+ // we don't always update the list on shutdown.
+ // Thus true for consistency.
+ },
+
+ // Open new window
+ {
+ get title() { return _getString("taskbar.tasks.newWindow.label"); },
+ get description() { return _getString("taskbar.tasks.newWindow.description"); },
+ args: "-browser",
+ iconIndex: 2, // New tab icon
+ open: true,
+ close: true, // No point, but we don't always update the list on
+ // shutdown. Thus true for consistency.
+ },
+
+ // Open new private window
+ {
+ get title() { return _getString("taskbar.tasks.newPrivateWindow.label"); },
+ get description() { return _getString("taskbar.tasks.newPrivateWindow.description"); },
+ args: "-private-window",
+ iconIndex: 4, // Private browsing mode icon
+ open: true,
+ close: true, // No point, but we don't always update the list on
+ // shutdown. Thus true for consistency.
+ },
+];
+
+// Implementation
+
+this.WinTaskbarJumpList =
+{
+ _builder: null,
+ _tasks: null,
+ _shuttingDown: false,
+
+ /**
+ * Startup, shutdown, and update
+ */
+
+ startup: function WTBJL_startup() {
+ // exit if this isn't win7 or higher.
+ if (!this._initTaskbar())
+ return;
+
+ // Win shell shortcut maintenance. If we've gone through an update,
+ // this will update any pinned taskbar shortcuts. Not specific to
+ // jump lists, but this was a convienent place to call it.
+ try {
+ // dev builds may not have helper.exe, ignore failures.
+ this._shortcutMaintenance();
+ } catch (ex) {
+ }
+
+ // Store our task list config data
+ this._tasks = tasksCfg;
+
+ // retrieve taskbar related prefs.
+ this._refreshPrefs();
+
+ // observer for private browsing and our prefs branch
+ this._initObs();
+
+ // jump list refresh timer
+ this._updateTimer();
+ },
+
+ update: function WTBJL_update() {
+ // are we disabled via prefs? don't do anything!
+ if (!this._enabled)
+ return;
+
+ // do what we came here to do, update the taskbar jumplist
+ this._buildList();
+ },
+
+ _shutdown: function WTBJL__shutdown() {
+ this._shuttingDown = true;
+
+ // Correctly handle a clear history on shutdown. If there are no
+ // entries be sure to empty all history lists. Luckily Places caches
+ // this value, so it's a pretty fast call.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ this.update();
+ }
+
+ this._free();
+ },
+
+ _shortcutMaintenance: function WTBJL__maintenace() {
+ _winShellService.shortcutMaintenance();
+ },
+
+ /**
+ * List building
+ *
+ * @note Async builders must add their mozIStoragePendingStatement to
+ * _pendingStatements object, using a different LIST_TYPE entry for
+ * each statement. Once finished they must remove it and call
+ * commitBuild(). When there will be no more _pendingStatements,
+ * commitBuild() will commit for real.
+ */
+
+ _pendingStatements: {},
+ _hasPendingStatements: function WTBJL__hasPendingStatements() {
+ return Object.keys(this._pendingStatements).length > 0;
+ },
+
+ _buildList: function WTBJL__buildList() {
+ if (this._hasPendingStatements()) {
+ // We were requested to update the list while another update was in
+ // progress, this could happen at shutdown, idle or privatebrowsing.
+ // Abort the current list building.
+ for (let listType in this._pendingStatements) {
+ this._pendingStatements[listType].cancel();
+ delete this._pendingStatements[listType];
+ }
+ this._builder.abortListBuild();
+ }
+
+ // anything to build?
+ if (!this._showFrequent && !this._showRecent && !this._showTasks) {
+ // don't leave the last list hanging on the taskbar.
+ this._deleteActiveJumpList();
+ return;
+ }
+
+ if (!this._startBuild())
+ return;
+
+ if (this._showTasks)
+ this._buildTasks();
+
+ // Space for frequent items takes priority over recent.
+ if (this._showFrequent)
+ this._buildFrequent();
+
+ if (this._showRecent)
+ this._buildRecent();
+
+ this._commitBuild();
+ },
+
+ /**
+ * Taskbar api wrappers
+ */
+
+ _startBuild: function WTBJL__startBuild() {
+ var removedItems = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._builder.abortListBuild();
+ if (this._builder.initListBuild(removedItems)) {
+ // Prior to building, delete removed items from history.
+ this._clearHistory(removedItems);
+ return true;
+ }
+ return false;
+ },
+
+ _commitBuild: function WTBJL__commitBuild() {
+ if (!this._hasPendingStatements() && !this._builder.commitListBuild()) {
+ this._builder.abortListBuild();
+ }
+ },
+
+ _buildTasks: function WTBJL__buildTasks() {
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._tasks.forEach(function (task) {
+ if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
+ return;
+ var item = this._getHandlerAppItem(task.title, task.description,
+ task.args, task.iconIndex, null);
+ items.appendElement(item, false);
+ }, this);
+
+ if (items.length > 0)
+ this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
+ },
+
+ _buildCustom: function WTBJL__buildCustom(title, items) {
+ if (items.length > 0)
+ this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
+ },
+
+ _buildFrequent: function WTBJL__buildFrequent() {
+ // If history is empty, just bail out.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ return;
+ }
+
+ // Windows supports default frequent and recent lists,
+ // but those depend on internal windows visit tracking
+ // which we don't populate. So we build our own custom
+ // frequent and recent lists using our nav history data.
+
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ // track frequent items so that we don't add them to
+ // the recent list.
+ this._frequentHashList = [];
+
+ this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
+ Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
+ this._maxItemCount,
+ function (aResult) {
+ if (!aResult) {
+ delete this._pendingStatements[LIST_TYPE.FREQUENT];
+ // The are no more results, build the list.
+ this._buildCustom(_getString("taskbar.frequent.label"), items);
+ this._commitBuild();
+ return;
+ }
+
+ let title = aResult.title || aResult.uri;
+ let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
+ let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
+ faviconPageUri);
+ items.appendElement(shortcut, false);
+ this._frequentHashList.push(aResult.uri);
+ },
+ this
+ );
+ },
+
+ _buildRecent: function WTBJL__buildRecent() {
+ // If history is empty, just bail out.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ return;
+ }
+
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ // Frequent items will be skipped, so we select a double amount of
+ // entries and stop fetching results at _maxItemCount.
+ var count = 0;
+
+ this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
+ this._maxItemCount * 2,
+ function (aResult) {
+ if (!aResult) {
+ // The are no more results, build the list.
+ this._buildCustom(_getString("taskbar.recent.label"), items);
+ delete this._pendingStatements[LIST_TYPE.RECENT];
+ this._commitBuild();
+ return;
+ }
+
+ if (count >= this._maxItemCount) {
+ return;
+ }
+
+ // Do not add items to recent that have already been added to frequent.
+ if (this._frequentHashList &&
+ this._frequentHashList.indexOf(aResult.uri) != -1) {
+ return;
+ }
+
+ let title = aResult.title || aResult.uri;
+ let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
+ let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
+ faviconPageUri);
+ items.appendElement(shortcut, false);
+ count++;
+ },
+ this
+ );
+ },
+
+ _deleteActiveJumpList: function WTBJL__deleteAJL() {
+ this._builder.deleteActiveList();
+ },
+
+ /**
+ * Jump list item creation helpers
+ */
+
+ _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
+ args, iconIndex,
+ faviconPageUri) {
+ var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile);
+
+ var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.executable = file;
+ // handlers default to the leaf name if a name is not specified
+ if (name && name.length != 0)
+ handlerApp.name = name;
+ handlerApp.detailedDescription = description;
+ handlerApp.appendParameter(args);
+
+ var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].
+ createInstance(Ci.nsIJumpListShortcut);
+ item.app = handlerApp;
+ item.iconIndex = iconIndex;
+ item.faviconPageUri = faviconPageUri;
+ return item;
+ },
+
+ _getSeparatorItem: function WTBJL__getSeparatorItem() {
+ var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
+ createInstance(Ci.nsIJumpListSeparator);
+ return item;
+ },
+
+ /**
+ * Nav history helpers
+ */
+
+ _getHistoryResults:
+ function WTBLJL__getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.maxResults = aLimit;
+ options.sortingMode = aSortingMode;
+ var query = PlacesUtils.history.getNewQuery();
+
+ // Return the pending statement to the caller, to allow cancelation.
+ return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .asyncExecuteLegacyQueries([query], 1, options, {
+ handleResult: function (aResultSet) {
+ for (let row; (row = aResultSet.getNextRow());) {
+ try {
+ aCallback.call(aScope,
+ { uri: row.getResultByIndex(1)
+ , title: row.getResultByIndex(2)
+ });
+ } catch (e) {}
+ }
+ },
+ handleError: function (aError) {
+ Components.utils.reportError(
+ "Async execution error (" + aError.result + "): " + aError.message);
+ },
+ handleCompletion: function (aReason) {
+ aCallback.call(WinTaskbarJumpList, null);
+ },
+ });
+ },
+
+ _clearHistory: function WTBJL__clearHistory(items) {
+ if (!items)
+ return;
+ var URIsToRemove = [];
+ var e = items.enumerate();
+ while (e.hasMoreElements()) {
+ let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut);
+ if (oldItem) {
+ try { // in case we get a bad uri
+ let uriSpec = oldItem.app.getParameter(0);
+ URIsToRemove.push(NetUtil.newURI(uriSpec));
+ } catch (err) { }
+ }
+ }
+ if (URIsToRemove.length > 0) {
+ PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
+ }
+ },
+
+ /**
+ * Prefs utilities
+ */
+
+ _refreshPrefs: function WTBJL__refreshPrefs() {
+ this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
+ this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
+ this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
+ this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
+ this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
+ },
+
+ /**
+ * Init and shutdown utilities
+ */
+
+ _initTaskbar: function WTBJL__initTaskbar() {
+ this._builder = _taskbarService.createJumpListBuilder();
+ if (!this._builder || !this._builder.available)
+ return false;
+
+ return true;
+ },
+
+ _initObs: function WTBJL__initObs() {
+ // If the browser is closed while in private browsing mode, the "exit"
+ // notification is fired on quit-application-granted.
+ // History cleanup can happen at profile-change-teardown.
+ Services.obs.addObserver(this, "profile-before-change", false);
+ Services.obs.addObserver(this, "browser:purge-session-history", false);
+ _prefs.addObserver("", this, false);
+ },
+
+ _freeObs: function WTBJL__freeObs() {
+ Services.obs.removeObserver(this, "profile-before-change");
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ _prefs.removeObserver("", this);
+ },
+
+ _updateTimer: function WTBJL__updateTimer() {
+ if (this._enabled && !this._shuttingDown && !this._timer) {
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._timer.initWithCallback(this,
+ _prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
+ this._timer.TYPE_REPEATING_SLACK);
+ }
+ else if ((!this._enabled || this._shuttingDown) && this._timer) {
+ this._timer.cancel();
+ delete this._timer;
+ }
+ },
+
+ _hasIdleObserver: false,
+ _updateIdleObserver: function WTBJL__updateIdleObserver() {
+ if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
+ _idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._hasIdleObserver = true;
+ }
+ else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) {
+ _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._hasIdleObserver = false;
+ }
+ },
+
+ _free: function WTBJL__free() {
+ this._freeObs();
+ this._updateTimer();
+ this._updateIdleObserver();
+ delete this._builder;
+ },
+
+ /**
+ * Notification handlers
+ */
+
+ notify: function WTBJL_notify(aTimer) {
+ // Add idle observer on the first notification so it doesn't hit startup.
+ this._updateIdleObserver();
+ this.update();
+ },
+
+ observe: function WTBJL_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED))
+ this._deleteActiveJumpList();
+ this._refreshPrefs();
+ this._updateTimer();
+ this._updateIdleObserver();
+ this.update();
+ break;
+
+ case "profile-before-change":
+ this._shutdown();
+ break;
+
+ case "browser:purge-session-history":
+ this.update();
+ break;
+ case "idle":
+ if (this._timer) {
+ this._timer.cancel();
+ delete this._timer;
+ }
+ break;
+
+ case "active":
+ this._updateTimer();
+ break;
+ }
+ },
+};
diff --git a/browser/modules/WindowsPreviewPerTab.jsm b/browser/modules/WindowsPreviewPerTab.jsm
new file mode 100644
index 000000000..6586b5d3b
--- /dev/null
+++ b/browser/modules/WindowsPreviewPerTab.jsm
@@ -0,0 +1,862 @@
+/* vim: se cin sw=2 ts=2 et filetype=javascript :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ * This module implements the front end behavior for AeroPeek. Starting in
+ * Windows Vista, the taskbar began showing live thumbnail previews of windows
+ * when the user hovered over the window icon in the taskbar. Starting with
+ * Windows 7, the taskbar allows an application to expose its tabbed interface
+ * in the taskbar by showing thumbnail previews rather than the default window
+ * preview. Additionally, when a user hovers over a thumbnail (tab or window),
+ * they are shown a live preview of the window (or tab + its containing window).
+ *
+ * In Windows 7, a title, icon, close button and optional toolbar are shown for
+ * each preview. This feature does not make use of the toolbar. For window
+ * previews, the title is the window title and the icon the window icon. For
+ * tab previews, the title is the page title and the page's favicon. In both
+ * cases, the close button "does the right thing."
+ *
+ * The primary objects behind this feature are nsITaskbarTabPreview and
+ * nsITaskbarPreviewController. Each preview has a controller. The controller
+ * responds to the user's interactions on the taskbar and provides the required
+ * data to the preview for determining the size of the tab and thumbnail. The
+ * PreviewController class implements this interface. The preview will request
+ * the controller to provide a thumbnail or preview when the user interacts with
+ * the taskbar. To reduce the overhead of drawing the tab area, the controller
+ * implementation caches the tab's contents in a <canvas> element. If no
+ * previews or thumbnails have been requested for some time, the controller will
+ * discard its cached tab contents.
+ *
+ * Screen real estate is limited so when there are too many thumbnails to fit
+ * on the screen, the taskbar stops displaying thumbnails and instead displays
+ * just the title, icon and close button in a similar fashion to previous
+ * versions of the taskbar. If there are still too many previews to fit on the
+ * screen, the taskbar resorts to a scroll up and scroll down button pair to let
+ * the user scroll through the list of tabs. Since this is undoubtedly
+ * inconvenient for users with many tabs, the AeroPeek objects turns off all of
+ * the tab previews. This tells the taskbar to revert to one preview per window.
+ * If the number of tabs falls below this magic threshold, the preview-per-tab
+ * behavior returns. There is no reliable way to determine when the scroll
+ * buttons appear on the taskbar, so a magic pref-controlled number determines
+ * when this threshold has been crossed.
+ */
+this.EXPORTED_SYMBOLS = ["AeroPeek"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Pref to enable/disable preview-per-tab
+const TOGGLE_PREF_NAME = "browser.taskbar.previews.enable";
+// Pref to determine the magic auto-disable threshold
+const DISABLE_THRESHOLD_PREF_NAME = "browser.taskbar.previews.max";
+// Pref to control the time in seconds that tab contents live in the cache
+const CACHE_EXPIRATION_TIME_PREF_NAME = "browser.taskbar.previews.cachetime";
+
+const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+
+// Various utility properties
+XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
+ "@mozilla.org/image/tools;1",
+ "imgITools");
+XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
+ "resource://gre/modules/PageThumbs.jsm");
+
+// nsIURI -> imgIContainer
+function _imageFromURI(uri, privateMode, callback) {
+ let channel = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+ });
+
+ try {
+ channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ channel.setPrivate(privateMode);
+ } catch (e) {
+ // Ignore channels which do not support nsIPrivateBrowsingChannel
+ }
+ NetUtil.asyncFetch(channel, function(inputStream, resultCode) {
+ if (!Components.isSuccessCode(resultCode))
+ return;
+ try {
+ let out_img = { value: null };
+ imgTools.decodeImageData(inputStream, channel.contentType, out_img);
+ callback(out_img.value);
+ } catch (e) {
+ // We failed, so use the default favicon (only if this wasn't the default
+ // favicon).
+ let defaultURI = PlacesUtils.favicons.defaultFavicon;
+ if (!defaultURI.equals(uri))
+ _imageFromURI(defaultURI, privateMode, callback);
+ }
+ });
+}
+
+// string? -> imgIContainer
+function getFaviconAsImage(iconurl, privateMode, callback) {
+ if (iconurl) {
+ _imageFromURI(NetUtil.newURI(iconurl), privateMode, callback);
+ } else {
+ _imageFromURI(PlacesUtils.favicons.defaultFavicon, privateMode, callback);
+ }
+}
+
+// Snaps the given rectangle to be pixel-aligned at the given scale
+function snapRectAtScale(r, scale) {
+ let x = Math.floor(r.x * scale);
+ let y = Math.floor(r.y * scale);
+ let width = Math.ceil((r.x + r.width) * scale) - x;
+ let height = Math.ceil((r.y + r.height) * scale) - y;
+
+ r.x = x / scale;
+ r.y = y / scale;
+ r.width = width / scale;
+ r.height = height / scale;
+}
+
+// PreviewController
+
+/*
+ * This class manages the behavior of thumbnails and previews. It has the following
+ * responsibilities:
+ * 1) responding to requests from Windows taskbar for a thumbnail or window
+ * preview.
+ * 2) listens for dom events that result in a thumbnail or window preview needing
+ * to be refresh, and communicates this to the taskbar.
+ * 3) Handles querying and returning to the taskbar new thumbnail or window
+ * preview images through PageThumbs.
+ *
+ * @param win
+ * The TabWindow (see below) that owns the preview that this controls
+ * @param tab
+ * The <tab> that this preview is associated with
+ */
+function PreviewController(win, tab) {
+ this.win = win;
+ this.tab = tab;
+ this.linkedBrowser = tab.linkedBrowser;
+ this.preview = this.win.createTabPreview(this);
+
+ this.tab.addEventListener("TabAttrModified", this, false);
+
+ XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
+ let canvas = PageThumbs.createCanvas();
+ canvas.mozOpaque = true;
+ return canvas;
+ });
+}
+
+PreviewController.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
+ Ci.nsIDOMEventListener]),
+
+ destroy: function () {
+ this.tab.removeEventListener("TabAttrModified", this, false);
+
+ // Break cycles, otherwise we end up leaking the window with everything
+ // attached to it.
+ delete this.win;
+ delete this.preview;
+ },
+
+ get wrappedJSObject() {
+ return this;
+ },
+
+ // Resizes the canvasPreview to 0x0, essentially freeing its memory.
+ resetCanvasPreview: function () {
+ this.canvasPreview.width = 0;
+ this.canvasPreview.height = 0;
+ },
+
+ /**
+ * Set the canvas dimensions.
+ */
+ resizeCanvasPreview: function (aRequestedWidth, aRequestedHeight) {
+ this.canvasPreview.width = aRequestedWidth;
+ this.canvasPreview.height = aRequestedHeight;
+ },
+
+
+ get zoom() {
+ // Note that winutils.fullZoom accounts for "quantization" of the zoom factor
+ // from nsIContentViewer due to conversion through appUnits.
+ // We do -not- want screenPixelsPerCSSPixel here, because that would -also-
+ // incorporate any scaling that is applied due to hi-dpi resolution options.
+ return this.tab.linkedBrowser.fullZoom;
+ },
+
+ get screenPixelsPerCSSPixel() {
+ let chromeWin = this.tab.ownerGlobal;
+ let windowUtils = chromeWin.getInterface(Ci.nsIDOMWindowUtils);
+ return windowUtils.screenPixelsPerCSSPixel;
+ },
+
+ get browserDims() {
+ return this.tab.linkedBrowser.getBoundingClientRect();
+ },
+
+ cacheBrowserDims: function () {
+ let dims = this.browserDims;
+ this._cachedWidth = dims.width;
+ this._cachedHeight = dims.height;
+ },
+
+ testCacheBrowserDims: function () {
+ let dims = this.browserDims;
+ return this._cachedWidth == dims.width &&
+ this._cachedHeight == dims.height;
+ },
+
+ /**
+ * Capture a new thumbnail image for this preview. Called by the controller
+ * in response to a request for a new thumbnail image.
+ */
+ updateCanvasPreview: function (aFullScale, aCallback) {
+ // Update our cached browser dims so that delayed resize
+ // events don't trigger another invalidation if this tab becomes active.
+ this.cacheBrowserDims();
+ PageThumbs.captureToCanvas(this.linkedBrowser, this.canvasPreview,
+ aCallback, { fullScale: aFullScale });
+ // If we're updating the canvas, then we're in the middle of a peek so
+ // don't discard the cache of previews.
+ AeroPeek.resetCacheTimer();
+ },
+
+ updateTitleAndTooltip: function () {
+ let title = this.win.tabbrowser.getWindowTitleForBrowser(this.linkedBrowser);
+ this.preview.title = title;
+ this.preview.tooltip = title;
+ },
+
+ // nsITaskbarPreviewController
+
+ // window width and height, not browser
+ get width() {
+ return this.win.width;
+ },
+
+ // window width and height, not browser
+ get height() {
+ return this.win.height;
+ },
+
+ get thumbnailAspectRatio() {
+ let browserDims = this.browserDims;
+ // Avoid returning 0
+ let tabWidth = browserDims.width || 1;
+ // Avoid divide by 0
+ let tabHeight = browserDims.height || 1;
+ return tabWidth / tabHeight;
+ },
+
+ /**
+ * Responds to taskbar requests for window previews. Returns the results asynchronously
+ * through updateCanvasPreview.
+ *
+ * @param aTaskbarCallback nsITaskbarPreviewCallback results callback
+ */
+ requestPreview: function (aTaskbarCallback) {
+ // Grab a high res content preview
+ this.resetCanvasPreview();
+ this.updateCanvasPreview(true, (aPreviewCanvas) => {
+ let winWidth = this.win.width;
+ let winHeight = this.win.height;
+
+ let composite = PageThumbs.createCanvas();
+
+ // Use transparency, Aero glass is drawn black without it.
+ composite.mozOpaque = false;
+
+ let ctx = composite.getContext('2d');
+ let scale = this.screenPixelsPerCSSPixel / this.zoom;
+
+ composite.width = winWidth * scale;
+ composite.height = winHeight * scale;
+
+ ctx.save();
+ ctx.scale(scale, scale);
+
+ // Draw chrome. Note we currently do not get scrollbars for remote frames
+ // in the image above.
+ ctx.drawWindow(this.win.win, 0, 0, winWidth, winHeight, "rgba(0,0,0,0)");
+
+ // Draw the content are into the composite canvas at the right location.
+ ctx.drawImage(aPreviewCanvas, this.browserDims.x, this.browserDims.y,
+ aPreviewCanvas.width, aPreviewCanvas.height);
+ ctx.restore();
+
+ // Deliver the resulting composite canvas to Windows
+ this.win.tabbrowser.previewTab(this.tab, function () {
+ aTaskbarCallback.done(composite, false);
+ });
+ });
+ },
+
+ /**
+ * Responds to taskbar requests for tab thumbnails. Returns the results asynchronously
+ * through updateCanvasPreview.
+ *
+ * Note Windows requests a specific width and height here, if the resulting thumbnail
+ * does not match these dimensions thumbnail display will fail.
+ *
+ * @param aTaskbarCallback nsITaskbarPreviewCallback results callback
+ * @param aRequestedWidth width of the requested thumbnail
+ * @param aRequestedHeight height of the requested thumbnail
+ */
+ requestThumbnail: function (aTaskbarCallback, aRequestedWidth, aRequestedHeight) {
+ this.resizeCanvasPreview(aRequestedWidth, aRequestedHeight);
+ this.updateCanvasPreview(false, (aThumbnailCanvas) => {
+ aTaskbarCallback.done(aThumbnailCanvas, false);
+ });
+ },
+
+ // Event handling
+
+ onClose: function () {
+ this.win.tabbrowser.removeTab(this.tab);
+ },
+
+ onActivate: function () {
+ this.win.tabbrowser.selectedTab = this.tab;
+
+ // Accept activation - this will restore the browser window
+ // if it's minimized
+ return true;
+ },
+
+ // nsIDOMEventListener
+ handleEvent: function (evt) {
+ switch (evt.type) {
+ case "TabAttrModified":
+ this.updateTitleAndTooltip();
+ break;
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
+ function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D;
+ return canvasInterface.DRAWWINDOW_DRAW_VIEW
+ | canvasInterface.DRAWWINDOW_DRAW_CARET
+ | canvasInterface.DRAWWINDOW_ASYNC_DECODE_IMAGES
+ | canvasInterface.DRAWWINDOW_DO_NOT_FLUSH;
+});
+
+// TabWindow
+
+/*
+ * This class monitors a browser window for changes to its tabs
+ *
+ * @param win
+ * The nsIDOMWindow browser window
+ */
+function TabWindow(win) {
+ this.win = win;
+ this.tabbrowser = win.gBrowser;
+
+ this.previews = new Map();
+
+ for (let i = 0; i < this.tabEvents.length; i++)
+ this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false);
+
+ for (let i = 0; i < this.winEvents.length; i++)
+ this.win.addEventListener(this.winEvents[i], this, false);
+
+ this.tabbrowser.addTabsProgressListener(this);
+
+ AeroPeek.windows.push(this);
+ let tabs = this.tabbrowser.tabs;
+ for (let i = 0; i < tabs.length; i++)
+ this.newTab(tabs[i]);
+
+ this.updateTabOrdering();
+ AeroPeek.checkPreviewCount();
+}
+
+TabWindow.prototype = {
+ _enabled: false,
+ _cachedWidth: 0,
+ _cachedHeight: 0,
+ tabEvents: ["TabOpen", "TabClose", "TabSelect", "TabMove"],
+ winEvents: ["resize"],
+
+ destroy: function () {
+ this._destroying = true;
+
+ let tabs = this.tabbrowser.tabs;
+
+ this.tabbrowser.removeTabsProgressListener(this);
+
+ for (let i = 0; i < this.winEvents.length; i++)
+ this.win.removeEventListener(this.winEvents[i], this, false);
+
+ for (let i = 0; i < this.tabEvents.length; i++)
+ this.tabbrowser.tabContainer.removeEventListener(this.tabEvents[i], this, false);
+
+ for (let i = 0; i < tabs.length; i++)
+ this.removeTab(tabs[i]);
+
+ let idx = AeroPeek.windows.indexOf(this.win.gTaskbarTabGroup);
+ AeroPeek.windows.splice(idx, 1);
+ AeroPeek.checkPreviewCount();
+ },
+
+ get width () {
+ return this.win.innerWidth;
+ },
+ get height () {
+ return this.win.innerHeight;
+ },
+
+ cacheDims: function () {
+ this._cachedWidth = this.width;
+ this._cachedHeight = this.height;
+ },
+
+ testCacheDims: function () {
+ return this._cachedWidth == this.width && this._cachedHeight == this.height;
+ },
+
+ // Invoked when the given tab is added to this window
+ newTab: function (tab) {
+ let controller = new PreviewController(this, tab);
+ // It's OK to add the preview now while the favicon still loads.
+ this.previews.set(tab, controller.preview);
+ AeroPeek.addPreview(controller.preview);
+ // updateTitleAndTooltip relies on having controller.preview which is lazily resolved.
+ // Now that we've updated this.previews, it will resolve successfully.
+ controller.updateTitleAndTooltip();
+ },
+
+ createTabPreview: function (controller) {
+ let docShell = this.win
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ let preview = AeroPeek.taskbar.createTaskbarTabPreview(docShell, controller);
+ preview.visible = AeroPeek.enabled;
+ preview.active = this.tabbrowser.selectedTab == controller.tab;
+ this.onLinkIconAvailable(controller.tab.linkedBrowser,
+ controller.tab.getAttribute("image"));
+ return preview;
+ },
+
+ // Invoked when the given tab is closed
+ removeTab: function (tab) {
+ let preview = this.previewFromTab(tab);
+ preview.active = false;
+ preview.visible = false;
+ preview.move(null);
+ preview.controller.wrappedJSObject.destroy();
+
+ this.previews.delete(tab);
+ AeroPeek.removePreview(preview);
+ },
+
+ get enabled () {
+ return this._enabled;
+ },
+
+ set enabled (enable) {
+ this._enabled = enable;
+ // Because making a tab visible requires that the tab it is next to be
+ // visible, it is far simpler to unset the 'next' tab and recreate them all
+ // at once.
+ for (let [, preview] of this.previews) {
+ preview.move(null);
+ preview.visible = enable;
+ }
+ this.updateTabOrdering();
+ },
+
+ previewFromTab: function (tab) {
+ return this.previews.get(tab);
+ },
+
+ updateTabOrdering: function () {
+ let previews = this.previews;
+ let tabs = this.tabbrowser.tabs;
+
+ // Previews are internally stored using a map, so we need to iterate the
+ // tabbrowser's array of tabs to retrieve previews in the same order.
+ let inorder = [];
+ for (let t of tabs) {
+ if (previews.has(t)) {
+ inorder.push(previews.get(t));
+ }
+ }
+
+ // Since the internal taskbar array has not yet been updated we must force
+ // on it the sorting order of our local array. To do so we must walk
+ // the local array backwards, otherwise we would send move requests in the
+ // wrong order. See bug 522610 for details.
+ for (let i = inorder.length - 1; i >= 0; i--) {
+ inorder[i].move(inorder[i + 1] || null);
+ }
+ },
+
+ // nsIDOMEventListener
+ handleEvent: function (evt) {
+ let tab = evt.originalTarget;
+ switch (evt.type) {
+ case "TabOpen":
+ this.newTab(tab);
+ this.updateTabOrdering();
+ break;
+ case "TabClose":
+ this.removeTab(tab);
+ this.updateTabOrdering();
+ break;
+ case "TabSelect":
+ this.previewFromTab(tab).active = true;
+ break;
+ case "TabMove":
+ this.updateTabOrdering();
+ break;
+ case "resize":
+ if (!AeroPeek._prefenabled)
+ return;
+ this.onResize();
+ break;
+ }
+ },
+
+ // Set or reset a timer that will invalidate visible thumbnails soon.
+ setInvalidationTimer: function () {
+ if (!this.invalidateTimer) {
+ this.invalidateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+ this.invalidateTimer.cancel();
+
+ // delay 1 second before invalidating
+ this.invalidateTimer.initWithCallback(() => {
+ // invalidate every preview. note the internal implementation of
+ // invalidate ignores thumbnails that aren't visible.
+ this.previews.forEach(function (aPreview) {
+ let controller = aPreview.controller.wrappedJSObject;
+ if (!controller.testCacheBrowserDims()) {
+ controller.cacheBrowserDims();
+ aPreview.invalidate();
+ }
+ });
+ }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ onResize: function () {
+ // Specific to a window.
+
+ // Call invalidate on each tab thumbnail so that Windows will request an
+ // updated image. However don't do this repeatedly across multiple resize
+ // events triggered during window border drags.
+
+ if (this.testCacheDims()) {
+ return;
+ }
+
+ // update the window dims on our TabWindow object.
+ this.cacheDims();
+
+ // invalidate soon
+ this.setInvalidationTimer();
+ },
+
+ invalidateTabPreview: function(aBrowser) {
+ for (let [tab, preview] of this.previews) {
+ if (aBrowser == tab.linkedBrowser) {
+ preview.invalidate();
+ break;
+ }
+ }
+ },
+
+ // Browser progress listener
+
+ onLocationChange: function (aBrowser) {
+ // I'm not sure we need this, onStateChange does a really good job
+ // of picking up page changes.
+ // this.invalidateTabPreview(aBrowser);
+ },
+
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ this.invalidateTabPreview(aBrowser);
+ }
+ },
+
+ directRequestProtocols: new Set([
+ "file", "chrome", "resource", "about"
+ ]),
+ onLinkIconAvailable: function (aBrowser, aIconURL) {
+ let requestURL = null;
+ if (aIconURL) {
+ let shouldRequestFaviconURL = true;
+ try {
+ let urlObject = NetUtil.newURI(aIconURL);
+ shouldRequestFaviconURL =
+ !this.directRequestProtocols.has(urlObject.scheme);
+ } catch (ex) {}
+
+ requestURL = shouldRequestFaviconURL ?
+ "moz-anno:favicon:" + aIconURL :
+ aIconURL;
+ }
+ let isDefaultFavicon = !requestURL;
+ getFaviconAsImage(
+ requestURL,
+ PrivateBrowsingUtils.isWindowPrivate(this.win),
+ img => {
+ let index = this.tabbrowser.browsers.indexOf(aBrowser);
+ // Only add it if we've found the index and the URI is still the same.
+ // The tab could have closed, and there's no guarantee the icons
+ // will have finished fetching 'in order'.
+ if (index != -1) {
+ let tab = this.tabbrowser.tabs[index];
+ let preview = this.previews.get(tab);
+ if (tab.getAttribute("image") == aIconURL ||
+ (!preview.icon && isDefaultFavicon)) {
+ preview.icon = img;
+ }
+ }
+ }
+ );
+ }
+}
+
+// AeroPeek
+
+/*
+ * This object acts as global storage and external interface for this feature.
+ * It maintains the values of the prefs.
+ */
+this.AeroPeek = {
+ available: false,
+ // Does the pref say we're enabled?
+ __prefenabled: false,
+
+ _enabled: true,
+
+ initialized: false,
+
+ // nsITaskbarTabPreview array
+ previews: [],
+
+ // TabWindow array
+ windows: [],
+
+ // nsIWinTaskbar service
+ taskbar: null,
+
+ // Maximum number of previews
+ maxpreviews: 20,
+
+ // Length of time in seconds that previews are cached
+ cacheLifespan: 20,
+
+ initialize: function () {
+ if (!(WINTASKBAR_CONTRACTID in Cc))
+ return;
+ this.taskbar = Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar);
+ this.available = this.taskbar.available;
+ if (!this.available)
+ return;
+
+ this.prefs.addObserver(TOGGLE_PREF_NAME, this, true);
+ this.enabled = this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
+ this.initialized = true;
+ },
+
+ destroy: function destroy() {
+ this._enabled = false;
+
+ if (this.cacheTimer)
+ this.cacheTimer.cancel();
+ },
+
+ get enabled() {
+ return this._enabled;
+ },
+
+ set enabled(enable) {
+ if (this._enabled == enable)
+ return;
+
+ this._enabled = enable;
+
+ this.windows.forEach(function (win) {
+ win.enabled = enable;
+ });
+ },
+
+ get _prefenabled() {
+ return this.__prefenabled;
+ },
+
+ set _prefenabled(enable) {
+ if (enable == this.__prefenabled) {
+ return;
+ }
+ this.__prefenabled = enable;
+
+ if (enable) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ },
+
+ _observersAdded: false,
+
+ enable() {
+ if (!this._observersAdded) {
+ this.prefs.addObserver(DISABLE_THRESHOLD_PREF_NAME, this, true);
+ this.prefs.addObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this, true);
+ PlacesUtils.history.addObserver(this, true);
+ this._observersAdded = true;
+ }
+
+ this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME);
+
+ this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
+
+ // If the user toggled us on/off while the browser was already up
+ // (rather than this code running on startup because the pref was
+ // already set to true), we must initialize previews for open windows:
+ if (this.initialized) {
+ let browserWindows = Services.wm.getEnumerator("navigator:browser");
+ while (browserWindows.hasMoreElements()) {
+ let win = browserWindows.getNext();
+ if (!win.closed) {
+ this.onOpenWindow(win);
+ }
+ }
+ }
+ },
+
+ disable() {
+ while (this.windows.length) {
+ // We can't call onCloseWindow here because it'll bail if we're not
+ // enabled.
+ let tabWinObject = this.windows[0];
+ tabWinObject.destroy(); // This will remove us from the array.
+ delete tabWinObject.win.gTaskbarTabGroup; // Tidy up the window.
+ }
+ },
+
+ addPreview: function (preview) {
+ this.previews.push(preview);
+ this.checkPreviewCount();
+ },
+
+ removePreview: function (preview) {
+ let idx = this.previews.indexOf(preview);
+ this.previews.splice(idx, 1);
+ this.checkPreviewCount();
+ },
+
+ checkPreviewCount: function () {
+ if (!this._prefenabled) {
+ return;
+ }
+ this.enabled = this.previews.length <= this.maxpreviews;
+ },
+
+ onOpenWindow: function (win) {
+ // This occurs when the taskbar service is not available (xp, vista)
+ if (!this.available || !this._prefenabled)
+ return;
+
+ win.gTaskbarTabGroup = new TabWindow(win);
+ },
+
+ onCloseWindow: function (win) {
+ // This occurs when the taskbar service is not available (xp, vista)
+ if (!this.available || !this._prefenabled)
+ return;
+
+ win.gTaskbarTabGroup.destroy();
+ delete win.gTaskbarTabGroup;
+
+ if (this.windows.length == 0)
+ this.destroy();
+ },
+
+ resetCacheTimer: function () {
+ this.cacheTimer.cancel();
+ this.cacheTimer.init(this, 1000*this.cacheLifespan, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ // nsIObserver
+ observe: function (aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed" && aData == TOGGLE_PREF_NAME) {
+ this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
+ }
+ if (!this._prefenabled) {
+ return;
+ }
+ switch (aTopic) {
+ case "nsPref:changed":
+ if (aData == CACHE_EXPIRATION_TIME_PREF_NAME)
+ break;
+
+ if (aData == DISABLE_THRESHOLD_PREF_NAME)
+ this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
+ // Might need to enable/disable ourselves
+ this.checkPreviewCount();
+ break;
+ case "timer-callback":
+ this.previews.forEach(function (preview) {
+ let controller = preview.controller.wrappedJSObject;
+ controller.resetCanvasPreview();
+ });
+ break;
+ }
+ },
+
+ /* nsINavHistoryObserver implementation */
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onVisit() {},
+ onTitleChanged() {},
+ onFrecencyChanged() {},
+ onManyFrecenciesChanged() {},
+ onDeleteURI() {},
+ onClearHistory() {},
+ onDeleteVisits() {},
+ onPageChanged(uri, changedConst, newValue) {
+ if (this.enabled && changedConst == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
+ for (let win of this.windows) {
+ for (let [tab, ] of win.previews) {
+ if (tab.getAttribute("image") == newValue) {
+ win.onLinkIconAvailable(tab.linkedBrowser, newValue);
+ }
+ }
+ }
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISupportsWeakReference,
+ Ci.nsINavHistoryObserver,
+ Ci.nsIObserver
+ ]),
+};
+
+XPCOMUtils.defineLazyGetter(AeroPeek, "cacheTimer", () =>
+ Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
+);
+
+XPCOMUtils.defineLazyServiceGetter(AeroPeek, "prefs",
+ "@mozilla.org/preferences-service;1",
+ "nsIPrefBranch");
+
+AeroPeek.initialize();
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
new file mode 100644
index 000000000..d71305198
--- /dev/null
+++ b/browser/modules/moz.build
@@ -0,0 +1,56 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+XPCSHELL_TESTS_MANIFESTS += [
+ 'test/unit/social/xpcshell.ini',
+ 'test/xpcshell/xpcshell.ini',
+]
+
+EXTRA_JS_MODULES += [
+ 'AboutHome.jsm',
+ 'AboutNewTab.jsm',
+ 'AttributionCode.jsm',
+ 'BrowserUITelemetry.jsm',
+ 'BrowserUsageTelemetry.jsm',
+ 'CastingApps.jsm',
+ 'ContentClick.jsm',
+ 'ContentCrashHandlers.jsm',
+ 'ContentLinkHandler.jsm',
+ 'ContentObservers.jsm',
+ 'ContentSearch.jsm',
+ 'ContentWebRTC.jsm',
+ 'DirectoryLinksProvider.jsm',
+ 'E10SUtils.jsm',
+ 'Feeds.jsm',
+ 'FormSubmitObserver.jsm',
+ 'FormValidationHandler.jsm',
+ 'HiddenFrame.jsm',
+ 'LaterRun.jsm',
+ 'NetworkPrioritizer.jsm',
+ 'offlineAppCache.jsm',
+ 'PermissionUI.jsm',
+ 'PluginContent.jsm',
+ 'ProcessHangMonitor.jsm',
+ 'ReaderParent.jsm',
+ 'RecentWindow.jsm',
+ 'RemotePrompt.jsm',
+ 'Sanitizer.jsm',
+ 'SelfSupportBackend.jsm',
+ 'SitePermissions.jsm',
+ 'Social.jsm',
+ 'SocialService.jsm',
+ 'TransientPrefs.jsm',
+ 'URLBarZoom.jsm',
+ 'webrtcUI.jsm',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ EXTRA_JS_MODULES += [
+ 'Windows8WindowFrameColor.jsm',
+ 'WindowsJumpLists.jsm',
+ 'WindowsPreviewPerTab.jsm',
+ ]
diff --git a/browser/modules/offlineAppCache.jsm b/browser/modules/offlineAppCache.jsm
new file mode 100644
index 000000000..5d0e3481a
--- /dev/null
+++ b/browser/modules/offlineAppCache.jsm
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["OfflineAppCacheHelper"];
+
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+this.OfflineAppCacheHelper = {
+ clear: function() {
+ var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+ var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
+ try {
+ appCacheStorage.asyncEvictStorage(null);
+ } catch (er) {}
+ }
+};
diff --git a/browser/modules/test/.eslintrc.js b/browser/modules/test/.eslintrc.js
new file mode 100644
index 000000000..e2d7896f8
--- /dev/null
+++ b/browser/modules/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/modules/test/browser.ini b/browser/modules/test/browser.ini
new file mode 100644
index 000000000..af624439c
--- /dev/null
+++ b/browser/modules/test/browser.ini
@@ -0,0 +1,42 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_BrowserUITelemetry_buckets.js]
+[browser_BrowserUITelemetry_defaults.js]
+[browser_BrowserUITelemetry_sidebar.js]
+[browser_BrowserUITelemetry_syncedtabs.js]
+[browser_ContentSearch.js]
+skip-if = true # Bug 1308343
+support-files =
+ contentSearch.js
+ contentSearchBadImage.xml
+ contentSearchSuggestions.sjs
+ contentSearchSuggestions.xml
+ !/browser/components/search/test/head.js
+ !/browser/components/search/test/testEngine.xml
+[browser_NetworkPrioritizer.js]
+[browser_PermissionUI.js]
+[browser_ProcessHangNotifications.js]
+skip-if = !e10s
+[browser_SelfSupportBackend.js]
+support-files =
+ ../../components/uitour/test/uitour.html
+ ../../components/uitour/UITour-lib.js
+[browser_taskbar_preview.js]
+skip-if = os != "win"
+[browser_UnsubmittedCrashHandler.js]
+run-if = crashreporter
+[browser_UsageTelemetry.js]
+[browser_UsageTelemetry_private_and_restore.js]
+[browser_UsageTelemetry_urlbar.js]
+support-files =
+ usageTelemetrySearchSuggestions.sjs
+ usageTelemetrySearchSuggestions.xml
+[browser_UsageTelemetry_searchbar.js]
+support-files =
+ usageTelemetrySearchSuggestions.sjs
+ usageTelemetrySearchSuggestions.xml
+[browser_UsageTelemetry_content.js]
+[browser_UsageTelemetry_content_aboutHome.js]
+[browser_urlBar_zoom.js]
diff --git a/browser/modules/test/browser_BrowserUITelemetry_buckets.js b/browser/modules/test/browser_BrowserUITelemetry_buckets.js
new file mode 100644
index 000000000..f55761705
--- /dev/null
+++ b/browser/modules/test/browser_BrowserUITelemetry_buckets.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ WHERE'S MAH BUCKET?!
+ \
+ ___
+ .-9 9 `\
+ =(:(::)= ;
+ |||| \
+ |||| `-.
+ ,\|\| `,
+ / \
+ ; `'---.,
+ | `\
+ ; / |
+ \ | /
+ ) \ __,.--\ /
+ .-' \,..._\ \` .-' .-'
+ `-=`` `: | /-/-/`
+ `.__/
+*/
+
+"use strict";
+
+
+add_task(function* testBUIT() {
+ let s = {};
+ Components.utils.import("resource:///modules/BrowserUITelemetry.jsm", s);
+ let BUIT = s.BrowserUITelemetry;
+
+ registerCleanupFunction(function() {
+ BUIT.setBucket(null);
+ });
+
+
+ // setBucket
+ is(BUIT.currentBucket, BUIT.BUCKET_DEFAULT, "Bucket should be default bucket");
+ BUIT.setBucket("mah-bucket");
+ is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "mah-bucket", "Bucket should have correct name");
+ BUIT.setBucket(null);
+ is(BUIT.currentBucket, BUIT.BUCKET_DEFAULT, "Bucket should be reset to default");
+
+
+ // _toTimeStr
+ is(BUIT._toTimeStr(10), "10ms", "Checking time string reprentation, 10ms");
+ is(BUIT._toTimeStr(1000 + 10), "1s10ms", "Checking time string reprentation, 1s10ms");
+ is(BUIT._toTimeStr((20 * 1000) + 10), "20s10ms", "Checking time string reprentation, 20s10ms");
+ is(BUIT._toTimeStr(60 * 1000), "1m", "Checking time string reprentation, 1m");
+ is(BUIT._toTimeStr(3 * 60 * 1000), "3m", "Checking time string reprentation, 3m");
+ is(BUIT._toTimeStr((3 * 60 * 1000) + 1), "3m1ms", "Checking time string reprentation, 3m1ms");
+ is(BUIT._toTimeStr((60 * 60 * 1000) + (10 * 60 * 1000)), "1h10m", "Checking time string reprentation, 1h10m");
+ is(BUIT._toTimeStr(100 * 60 * 60 * 1000), "100h", "Checking time string reprentation, 100h");
+
+
+ // setExpiringBucket
+ BUIT.setExpiringBucket("walrus", [1001, 2001, 3001, 10001]);
+ is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "1s1ms",
+ "Bucket should be expiring and have time step of 1s1ms");
+
+ yield waitForConditionPromise(function() {
+ return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "2s1ms");
+ }, "Bucket should be expiring and have time step of 2s1ms");
+
+ yield waitForConditionPromise(function() {
+ return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus" + BUIT.BUCKET_SEPARATOR + "3s1ms");
+ }, "Bucket should be expiring and have time step of 3s1ms");
+
+
+ // Interupt previous expiring bucket
+ BUIT.setExpiringBucket("walrus2", [1002, 2002]);
+ is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus2" + BUIT.BUCKET_SEPARATOR + "1s2ms",
+ "Should be new expiring bucket, with time step of 1s2ms");
+
+ yield waitForConditionPromise(function() {
+ return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "walrus2" + BUIT.BUCKET_SEPARATOR + "2s2ms");
+ }, "Should be new expiring bucket, with time step of 2s2ms");
+
+
+ // Let expiring bucket expire
+ yield waitForConditionPromise(function() {
+ return BUIT.currentBucket == BUIT.BUCKET_DEFAULT;
+ }, "Bucket should have expired, default bucket should now be active");
+
+
+ // Interupt expiring bucket with normal bucket
+ BUIT.setExpiringBucket("walrus3", [1003, 2003]);
+ is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "walrus3" + BUIT.BUCKET_SEPARATOR + "1s3ms",
+ "Should be new expiring bucket, with time step of 1s3ms");
+
+ BUIT.setBucket("mah-bucket");
+ is(BUIT.currentBucket, BUIT.BUCKET_PREFIX + "mah-bucket", "Bucket should have correct name");
+
+ yield waitForConditionPromise(function() {
+ return BUIT.currentBucket == (BUIT.BUCKET_PREFIX + "mah-bucket");
+ }, "Next step of old expiring bucket shouldn't have progressed");
+});
diff --git a/browser/modules/test/browser_BrowserUITelemetry_defaults.js b/browser/modules/test/browser_BrowserUITelemetry_defaults.js
new file mode 100644
index 000000000..ced1bbce0
--- /dev/null
+++ b/browser/modules/test/browser_BrowserUITelemetry_defaults.js
@@ -0,0 +1,37 @@
+// The purpose of this test is to ensure that by default, BrowserUITelemetry
+// isn't reporting any UI customizations. This is primarily so changes to
+// customizableUI (eg, new buttons, button location changes) also have a
+// corresponding BrowserUITelemetry change.
+
+function test() {
+ let s = {};
+ Cu.import("resource:///modules/CustomizableUI.jsm", s);
+ Cu.import("resource:///modules/BrowserUITelemetry.jsm", s);
+
+ let { CustomizableUI, BrowserUITelemetry } = s;
+
+ // Bug 1278176 - DevEdition never has the UI in a default state by default.
+ if (!AppConstants.MOZ_DEV_EDITION) {
+ Assert.ok(CustomizableUI.inDefaultState,
+ "No other test should have left CUI in a dirty state.");
+ }
+
+ let result = BrowserUITelemetry._getWindowMeasurements(window, 0);
+
+ // Bug 1278176 - DevEdition always reports the developer-button is moved.
+ if (!AppConstants.MOZ_DEV_EDITION) {
+ Assert.deepEqual(result.defaultMoved, []);
+ }
+ Assert.deepEqual(result.nondefaultAdded, []);
+ // This one is a bit weird - the "social-share-button" is dynamically added
+ // to the toolbar as the feature is first used - but it's listed as being in
+ // the toolbar by default so it doesn't end up in nondefaultAdded once it
+ // is created. The end result is that it ends up in defaultRemoved before
+ // the feature has been activated.
+ // Bug 1273358 exists to fix this.
+ Assert.deepEqual(result.defaultRemoved, ["social-share-button"]);
+
+ // And mochi insists there's only a single window with a single tab when
+ // starting a test, so check that for good measure.
+ Assert.deepEqual(result.visibleTabs, [1]);
+}
diff --git a/browser/modules/test/browser_BrowserUITelemetry_sidebar.js b/browser/modules/test/browser_BrowserUITelemetry_sidebar.js
new file mode 100644
index 000000000..5f19eabd5
--- /dev/null
+++ b/browser/modules/test/browser_BrowserUITelemetry_sidebar.js
@@ -0,0 +1,56 @@
+// Test the sidebar counters in BrowserUITelemetry.
+"use strict";
+
+const { BrowserUITelemetry: BUIT } = Cu.import("resource:///modules/BrowserUITelemetry.jsm", {});
+
+add_task(function* testSidebarOpenClose() {
+ // Reset BrowserUITelemetry's world.
+ BUIT._countableEvents = {};
+
+ yield SidebarUI.show("viewTabsSidebar");
+
+ let counts = BUIT._countableEvents[BUIT.currentBucket];
+
+ Assert.deepEqual(counts, { sidebar: { viewTabsSidebar: { show: 1 } } });
+ yield SidebarUI.hide();
+ Assert.deepEqual(counts, { sidebar: { viewTabsSidebar: { show: 1, hide: 1 } } });
+
+ yield SidebarUI.show("viewBookmarksSidebar");
+ Assert.deepEqual(counts, {
+ sidebar: {
+ viewTabsSidebar: { show: 1, hide: 1 },
+ viewBookmarksSidebar: { show: 1 },
+ }
+ });
+ // Re-open the tabs sidebar while bookmarks is open - bookmarks should
+ // record a close.
+ yield SidebarUI.show("viewTabsSidebar");
+ Assert.deepEqual(counts, {
+ sidebar: {
+ viewTabsSidebar: { show: 2, hide: 1 },
+ viewBookmarksSidebar: { show: 1, hide: 1 },
+ }
+ });
+ yield SidebarUI.hide();
+ Assert.deepEqual(counts, {
+ sidebar: {
+ viewTabsSidebar: { show: 2, hide: 2 },
+ viewBookmarksSidebar: { show: 1, hide: 1 },
+ }
+ });
+ // Toggle - this will re-open viewTabsSidebar
+ yield SidebarUI.toggle("viewTabsSidebar");
+ Assert.deepEqual(counts, {
+ sidebar: {
+ viewTabsSidebar: { show: 3, hide: 2 },
+ viewBookmarksSidebar: { show: 1, hide: 1 },
+ }
+ });
+ yield SidebarUI.toggle("viewTabsSidebar");
+ Assert.deepEqual(counts, {
+ sidebar: {
+ viewTabsSidebar: { show: 3, hide: 3 },
+ viewBookmarksSidebar: { show: 1, hide: 1 },
+ }
+ });
+});
diff --git a/browser/modules/test/browser_BrowserUITelemetry_syncedtabs.js b/browser/modules/test/browser_BrowserUITelemetry_syncedtabs.js
new file mode 100644
index 000000000..d3e1eac57
--- /dev/null
+++ b/browser/modules/test/browser_BrowserUITelemetry_syncedtabs.js
@@ -0,0 +1,114 @@
+// Test the SyncedTabs counters in BrowserUITelemetry.
+"use strict";
+
+const { BrowserUITelemetry: BUIT } = Cu.import("resource:///modules/BrowserUITelemetry.jsm", {});
+const {SyncedTabs} = Cu.import("resource://services-sync/SyncedTabs.jsm", {});
+
+function mockSyncedTabs() {
+ // Mock SyncedTabs.jsm
+ let mockedInternal = {
+ get isConfiguredToSyncTabs() { return true; },
+ getTabClients() {
+ return Promise.resolve([
+ {
+ id: "guid_desktop",
+ type: "client",
+ name: "My Desktop",
+ tabs: [
+ {
+ title: "http://example.com/10",
+ lastUsed: 10, // the most recent
+ },
+ ],
+ }
+ ]);
+ },
+ syncTabs() {
+ return Promise.resolve();
+ },
+ hasSyncedThisSession: true,
+ };
+
+ let oldInternal = SyncedTabs._internal;
+ SyncedTabs._internal = mockedInternal;
+
+ // configure our broadcasters so we are in the right state.
+ document.getElementById("sync-reauth-state").hidden = true;
+ document.getElementById("sync-setup-state").hidden = true;
+ document.getElementById("sync-syncnow-state").hidden = false;
+
+ registerCleanupFunction(() => {
+ SyncedTabs._internal = oldInternal;
+
+ document.getElementById("sync-reauth-state").hidden = true;
+ document.getElementById("sync-setup-state").hidden = false;
+ document.getElementById("sync-syncnow-state").hidden = true;
+ });
+}
+
+mockSyncedTabs();
+
+function promiseTabsUpdated() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(onNotification, aTopic);
+ resolve();
+ }, "synced-tabs-menu:test:tabs-updated", false);
+ });
+}
+
+add_task(function* test_menu() {
+ // Reset BrowserUITelemetry's world.
+ BUIT._countableEvents = {};
+
+ let tabsUpdated = promiseTabsUpdated();
+
+ // check the button's functionality
+ yield PanelUI.show();
+
+ let syncButton = document.getElementById("sync-button");
+ syncButton.click();
+
+ yield tabsUpdated;
+ // Get our 1 tab and click on it.
+ let tabList = document.getElementById("PanelUI-remotetabs-tabslist");
+ let tabEntry = tabList.firstChild.nextSibling;
+ tabEntry.click();
+
+ let counts = BUIT._countableEvents[BUIT.currentBucket];
+ Assert.deepEqual(counts, {
+ "click-builtin-item": { "sync-button": { left: 1 } },
+ "synced-tabs": { open: { "toolbarbutton-subview": 1 } },
+ });
+});
+
+add_task(function* test_sidebar() {
+ // Reset BrowserUITelemetry's world.
+ BUIT._countableEvents = {};
+
+ yield SidebarUI.show('viewTabsSidebar');
+
+ let syncedTabsDeckComponent = SidebarUI.browser.contentWindow.syncedTabsDeckComponent;
+
+ syncedTabsDeckComponent._accountStatus = () => Promise.resolve(true);
+
+ // Once the tabs container has been selected (which here means "'selected'
+ // added to the class list") we are ready to test.
+ let container = SidebarUI.browser.contentDocument.querySelector(".tabs-container");
+ let promiseUpdated = BrowserTestUtils.waitForAttribute("class", container);
+
+ yield syncedTabsDeckComponent.updatePanel();
+ yield promiseUpdated;
+
+ let selectedPanel = syncedTabsDeckComponent.container.querySelector(".sync-state.selected");
+ let tab = selectedPanel.querySelector(".tab");
+ tab.click();
+ let counts = BUIT._countableEvents[BUIT.currentBucket];
+ Assert.deepEqual(counts, {
+ sidebar: {
+ viewTabsSidebar: { show: 1 },
+ },
+ "synced-tabs": { open: { sidebar: 1 } }
+ });
+ yield SidebarUI.hide();
+});
diff --git a/browser/modules/test/browser_ContentSearch.js b/browser/modules/test/browser_ContentSearch.js
new file mode 100644
index 000000000..97bd6ac51
--- /dev/null
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -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/. */
+
+const TEST_MSG = "ContentSearchTest";
+const CONTENT_SEARCH_MSG = "ContentSearch";
+const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
+
+// This timeout is absurdly high to avoid random failures like bug 1087120.
+// That bug was reported when the timeout was 5 seconds, so let's try 10.
+const SUGGESTIONS_TIMEOUT = 10000;
+
+var gMsgMan;
+/* eslint no-undef:"error" */
+/* import-globals-from ../../components/search/test/head.js */
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/components/search/test/head.js",
+ this);
+
+let originalEngine = Services.search.currentEngine;
+
+add_task(function* setup() {
+ yield promiseNewEngine("testEngine.xml", {
+ setAsCurrent: true,
+ testPath: "chrome://mochitests/content/browser/browser/components/search/test/",
+ });
+
+ registerCleanupFunction(() => {
+ Services.search.currentEngine = originalEngine;
+ });
+});
+
+add_task(function* GetState() {
+ yield addTab();
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "GetState",
+ });
+ let msg = yield waitForTestMsg("State");
+ checkMsg(msg, {
+ type: "State",
+ data: yield currentStateObj(),
+ });
+});
+
+add_task(function* SetCurrentEngine() {
+ yield addTab();
+ let newCurrentEngine = null;
+ let oldCurrentEngine = Services.search.currentEngine;
+ let engines = Services.search.getVisibleEngines();
+ for (let engine of engines) {
+ if (engine != oldCurrentEngine) {
+ newCurrentEngine = engine;
+ break;
+ }
+ }
+ if (!newCurrentEngine) {
+ info("Couldn't find a non-selected search engine, " +
+ "skipping this part of the test");
+ return;
+ }
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "SetCurrentEngine",
+ data: newCurrentEngine.name,
+ });
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function obs(subj, topic, data) {
+ info("Test observed " + data);
+ if (data == "engine-current") {
+ ok(true, "Test observed engine-current");
+ Services.obs.removeObserver(obs, "browser-search-engine-modified");
+ deferred.resolve();
+ }
+ }, "browser-search-engine-modified", false);
+ let searchPromise = waitForTestMsg("CurrentEngine");
+ info("Waiting for test to observe engine-current...");
+ yield deferred.promise;
+ let msg = yield searchPromise;
+ checkMsg(msg, {
+ type: "CurrentEngine",
+ data: yield currentEngineObj(newCurrentEngine),
+ });
+
+ Services.search.currentEngine = oldCurrentEngine;
+ msg = yield waitForTestMsg("CurrentEngine");
+ checkMsg(msg, {
+ type: "CurrentEngine",
+ data: yield currentEngineObj(oldCurrentEngine),
+ });
+});
+
+add_task(function* modifyEngine() {
+ yield addTab();
+ let engine = Services.search.currentEngine;
+ let oldAlias = engine.alias;
+ engine.alias = "ContentSearchTest";
+ let msg = yield waitForTestMsg("CurrentState");
+ checkMsg(msg, {
+ type: "CurrentState",
+ data: yield currentStateObj(),
+ });
+ engine.alias = oldAlias;
+ msg = yield waitForTestMsg("CurrentState");
+ checkMsg(msg, {
+ type: "CurrentState",
+ data: yield currentStateObj(),
+ });
+});
+
+add_task(function* search() {
+ yield addTab();
+ let engine = Services.search.currentEngine;
+ let data = {
+ engineName: engine.name,
+ searchString: "ContentSearchTest",
+ healthReportKey: "ContentSearchTest",
+ searchPurpose: "ContentSearchTest",
+ };
+ let submissionURL =
+ engine.getSubmission(data.searchString, "", data.whence).uri.spec;
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "Search",
+ data: data,
+ expectedURL: submissionURL,
+ });
+ let msg = yield waitForTestMsg("loadStopped");
+ Assert.equal(msg.data.url, submissionURL, "Correct search page loaded");
+});
+
+add_task(function* searchInBackgroundTab() {
+ // This test is like search(), but it opens a new tab after starting a search
+ // in another. In other words, it performs a search in a background tab. The
+ // search page should be loaded in the same tab that performed the search, in
+ // the background tab.
+ yield addTab();
+ let engine = Services.search.currentEngine;
+ let data = {
+ engineName: engine.name,
+ searchString: "ContentSearchTest",
+ healthReportKey: "ContentSearchTest",
+ searchPurpose: "ContentSearchTest",
+ };
+ let submissionURL =
+ engine.getSubmission(data.searchString, "", data.whence).uri.spec;
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "Search",
+ data: data,
+ expectedURL: submissionURL,
+ });
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ registerCleanupFunction(() => gBrowser.removeTab(newTab));
+
+ let msg = yield waitForTestMsg("loadStopped");
+ Assert.equal(msg.data.url, submissionURL, "Correct search page loaded");
+});
+
+add_task(function* badImage() {
+ yield addTab();
+ // If the bad image URI caused an exception to be thrown within ContentSearch,
+ // then we'll hang waiting for the CurrentState responses triggered by the new
+ // engine. That's what we're testing, and obviously it shouldn't happen.
+ let vals = yield waitForNewEngine("contentSearchBadImage.xml", 1);
+ let engine = vals[0];
+ let finalCurrentStateMsg = vals[vals.length - 1];
+ let expectedCurrentState = yield currentStateObj();
+ let expectedEngine =
+ expectedCurrentState.engines.find(e => e.name == engine.name);
+ ok(!!expectedEngine, "Sanity check: engine should be in expected state");
+ ok(expectedEngine.iconBuffer === null,
+ "Sanity check: icon array buffer of engine in expected state " +
+ "should be null: " + expectedEngine.iconBuffer);
+ checkMsg(finalCurrentStateMsg, {
+ type: "CurrentState",
+ data: expectedCurrentState,
+ });
+ // Removing the engine triggers a final CurrentState message. Wait for it so
+ // it doesn't trip up subsequent tests.
+ Services.search.removeEngine(engine);
+ yield waitForTestMsg("CurrentState");
+});
+
+add_task(function* GetSuggestions_AddFormHistoryEntry_RemoveFormHistoryEntry() {
+ yield addTab();
+
+ // Add the test engine that provides suggestions.
+ let vals = yield waitForNewEngine("contentSearchSuggestions.xml", 0);
+ let engine = vals[0];
+
+ let searchStr = "browser_ContentSearch.js-suggestions-";
+
+ // Add a form history suggestion and wait for Satchel to notify about it.
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "AddFormHistoryEntry",
+ data: searchStr + "form",
+ });
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function onAdd(subj, topic, data) {
+ if (data == "formhistory-add") {
+ Services.obs.removeObserver(onAdd, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+ yield deferred.promise;
+
+ // Send GetSuggestions using the test engine. Its suggestions should appear
+ // in the remote suggestions in the Suggestions response below.
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "GetSuggestions",
+ data: {
+ engineName: engine.name,
+ searchString: searchStr,
+ remoteTimeout: SUGGESTIONS_TIMEOUT,
+ },
+ });
+
+ // Check the Suggestions response.
+ let msg = yield waitForTestMsg("Suggestions");
+ checkMsg(msg, {
+ type: "Suggestions",
+ data: {
+ engineName: engine.name,
+ searchString: searchStr,
+ formHistory: [searchStr + "form"],
+ remote: [searchStr + "foo", searchStr + "bar"],
+ },
+ });
+
+ // Delete the form history suggestion and wait for Satchel to notify about it.
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "RemoveFormHistoryEntry",
+ data: searchStr + "form",
+ });
+ deferred = Promise.defer();
+ Services.obs.addObserver(function onRemove(subj, topic, data) {
+ if (data == "formhistory-remove") {
+ Services.obs.removeObserver(onRemove, "satchel-storage-changed");
+ executeSoon(() => deferred.resolve());
+ }
+ }, "satchel-storage-changed", false);
+ yield deferred.promise;
+
+ // Send GetSuggestions again.
+ gMsgMan.sendAsyncMessage(TEST_MSG, {
+ type: "GetSuggestions",
+ data: {
+ engineName: engine.name,
+ searchString: searchStr,
+ remoteTimeout: SUGGESTIONS_TIMEOUT,
+ },
+ });
+
+ // The formHistory suggestions in the Suggestions response should be empty.
+ msg = yield waitForTestMsg("Suggestions");
+ checkMsg(msg, {
+ type: "Suggestions",
+ data: {
+ engineName: engine.name,
+ searchString: searchStr,
+ formHistory: [],
+ remote: [searchStr + "foo", searchStr + "bar"],
+ },
+ });
+
+ // Finally, clean up by removing the test engine.
+ Services.search.removeEngine(engine);
+ yield waitForTestMsg("CurrentState");
+});
+
+function buffersEqual(actualArrayBuffer, expectedArrayBuffer) {
+ let expectedView = new Int8Array(expectedArrayBuffer);
+ let actualView = new Int8Array(actualArrayBuffer);
+ for (let i = 0; i < expectedView.length; i++) {
+ if (actualView[i] != expectedView[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function arrayBufferEqual(actualArrayBuffer, expectedArrayBuffer) {
+ ok(actualArrayBuffer instanceof ArrayBuffer, "Actual value is ArrayBuffer.");
+ ok(expectedArrayBuffer instanceof ArrayBuffer, "Expected value is ArrayBuffer.");
+ Assert.equal(actualArrayBuffer.byteLength, expectedArrayBuffer.byteLength,
+ "Array buffers have the same length.");
+ ok(buffersEqual(actualArrayBuffer, expectedArrayBuffer), "Buffers are equal.");
+}
+
+function checkArrayBuffers(actual, expected) {
+ if (actual instanceof ArrayBuffer) {
+ arrayBufferEqual(actual, expected);
+ }
+ if (typeof actual == "object") {
+ for (let i in actual) {
+ checkArrayBuffers(actual[i], expected[i]);
+ }
+ }
+}
+
+function checkMsg(actualMsg, expectedMsgData) {
+ let actualMsgData = actualMsg.data;
+ SimpleTest.isDeeply(actualMsg.data, expectedMsgData, "Checking message");
+
+ // Engines contain ArrayBuffers which we have to compare byte by byte and
+ // not as Objects (like SimpleTest.isDeeply does).
+ checkArrayBuffers(actualMsgData, expectedMsgData);
+}
+
+function waitForMsg(name, type) {
+ let deferred = Promise.defer();
+ info("Waiting for " + name + " message " + type + "...");
+ gMsgMan.addMessageListener(name, function onMsg(msg) {
+ info("Received " + name + " message " + msg.data.type + "\n");
+ if (msg.data.type == type) {
+ gMsgMan.removeMessageListener(name, onMsg);
+ deferred.resolve(msg);
+ }
+ });
+ return deferred.promise;
+}
+
+function waitForTestMsg(type) {
+ return waitForMsg(TEST_MSG, type);
+}
+
+function waitForNewEngine(basename, numImages) {
+ info("Waiting for engine to be added: " + basename);
+
+ // Wait for the search events triggered by adding the new engine.
+ // engine-added engine-loaded
+ let expectedSearchEvents = ["CurrentState", "CurrentState"];
+ // engine-changed for each of the images
+ for (let i = 0; i < numImages; i++) {
+ expectedSearchEvents.push("CurrentState");
+ }
+ let eventPromises = expectedSearchEvents.map(e => waitForTestMsg(e));
+
+ // Wait for addEngine().
+ let addDeferred = Promise.defer();
+ let url = getRootDirectory(gTestPath) + basename;
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess: function (engine) {
+ info("Search engine added: " + basename);
+ addDeferred.resolve(engine);
+ },
+ onError: function (errCode) {
+ ok(false, "addEngine failed with error code " + errCode);
+ addDeferred.reject();
+ },
+ });
+
+ return Promise.all([addDeferred.promise].concat(eventPromises));
+}
+
+function addTab() {
+ let deferred = Promise.defer();
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", function load() {
+ tab.linkedBrowser.removeEventListener("load", load, true);
+ let url = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
+ gMsgMan = tab.linkedBrowser.messageManager;
+ gMsgMan.sendAsyncMessage(CONTENT_SEARCH_MSG, {
+ type: "AddToWhitelist",
+ data: ["about:blank"],
+ });
+ waitForMsg(CONTENT_SEARCH_MSG, "AddToWhitelistAck").then(() => {
+ gMsgMan.loadFrameScript(url, false);
+ deferred.resolve();
+ });
+ }, true);
+ registerCleanupFunction(() => gBrowser.removeTab(tab));
+ return deferred.promise;
+}
+
+var currentStateObj = Task.async(function* () {
+ let state = {
+ engines: [],
+ currentEngine: yield currentEngineObj(),
+ };
+ for (let engine of Services.search.getVisibleEngines()) {
+ let uri = engine.getIconURLBySize(16, 16);
+ state.engines.push({
+ name: engine.name,
+ iconBuffer: yield arrayBufferFromDataURI(uri),
+ hidden: false,
+ });
+ }
+ return state;
+});
+
+var currentEngineObj = Task.async(function* () {
+ let engine = Services.search.currentEngine;
+ let uriFavicon = engine.getIconURLBySize(16, 16);
+ let bundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
+ return {
+ name: engine.name,
+ placeholder: bundle.formatStringFromName("searchWithEngine", [engine.name], 1),
+ iconBuffer: yield arrayBufferFromDataURI(uriFavicon),
+ };
+});
+
+function arrayBufferFromDataURI(uri) {
+ if (!uri) {
+ return Promise.resolve(null);
+ }
+ let deferred = Promise.defer();
+ let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open("GET", uri, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onerror = () => {
+ deferred.resolve(null);
+ };
+ xhr.onload = () => {
+ deferred.resolve(xhr.response);
+ };
+ try {
+ xhr.send();
+ }
+ catch (err) {
+ return Promise.resolve(null);
+ }
+ return deferred.promise;
+}
diff --git a/browser/modules/test/browser_NetworkPrioritizer.js b/browser/modules/test/browser_NetworkPrioritizer.js
new file mode 100644
index 000000000..91557b0fd
--- /dev/null
+++ b/browser/modules/test/browser_NetworkPrioritizer.js
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** Tests for NetworkPrioritizer.jsm (Bug 514490) **/
+
+const LOWEST = Ci.nsISupportsPriority.PRIORITY_LOWEST;
+const LOW = Ci.nsISupportsPriority.PRIORITY_LOW;
+const NORMAL = Ci.nsISupportsPriority.PRIORITY_NORMAL;
+const HIGH = Ci.nsISupportsPriority.PRIORITY_HIGH;
+const HIGHEST = Ci.nsISupportsPriority.PRIORITY_HIGHEST;
+
+const DELTA = NORMAL - LOW; // lower value means higher priority
+
+// Test helper functions.
+// getPriority and setPriority can take a tab or a Browser
+function* getPriority(aBrowser) {
+ if (aBrowser.localName == "tab")
+ aBrowser = aBrowser.linkedBrowser;
+
+ return yield ContentTask.spawn(aBrowser, null, function* () {
+ return docShell.QueryInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocumentLoader)
+ .loadGroup
+ .QueryInterface(Components.interfaces.nsISupportsPriority)
+ .priority;
+ });
+}
+
+function* setPriority(aBrowser, aPriority) {
+ if (aBrowser.localName == "tab")
+ aBrowser = aBrowser.linkedBrowser;
+
+ yield ContentTask.spawn(aBrowser, aPriority, function* (aPriority) {
+ docShell.QueryInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocumentLoader)
+ .loadGroup
+ .QueryInterface(Ci.nsISupportsPriority)
+ .priority = aPriority;
+ });
+}
+
+function* isWindowState(aWindow, aTabPriorities) {
+ let browsers = aWindow.gBrowser.browsers;
+ // Make sure we have the right number of tabs & priorities
+ is(browsers.length, aTabPriorities.length,
+ "Window has expected number of tabs");
+ // aState should be in format [ priority, priority, priority ]
+ for (let i = 0; i < browsers.length; i++) {
+ is(yield getPriority(browsers[i]), aTabPriorities[i],
+ "Tab " + i + " had expected priority");
+ }
+}
+
+function promiseWaitForFocus(aWindow) {
+ return new Promise((resolve) => {
+ waitForFocus(resolve, aWindow);
+ });
+}
+
+add_task(function*() {
+ // This is the real test. It creates multiple tabs & windows, changes focus,
+ // closes windows/tabs to make sure we behave correctly.
+ // This test assumes that no priorities have been adjusted and the loadgroup
+ // priority starts at 0.
+
+ // Call window "window_A" to make the test easier to follow
+ let window_A = window;
+
+ // Test 1 window, 1 tab case.
+ yield isWindowState(window_A, [HIGH]);
+
+ // Exising tab is tab_A1
+ let tab_A2 = window_A.gBrowser.addTab("http://example.com");
+ let tab_A3 = window_A.gBrowser.addTab("about:config");
+ yield BrowserTestUtils.browserLoaded(tab_A3.linkedBrowser);
+
+ // tab_A2 isn't focused yet
+ yield isWindowState(window_A, [HIGH, NORMAL, NORMAL]);
+
+ // focus tab_A2 & make sure priority got updated
+ window_A.gBrowser.selectedTab = tab_A2;
+ yield isWindowState(window_A, [NORMAL, HIGH, NORMAL]);
+
+ window_A.gBrowser.removeTab(tab_A2);
+ // Next tab is auto selected synchronously as part of removeTab, and we
+ // expect the priority to be updated immediately.
+ yield isWindowState(window_A, [NORMAL, HIGH]);
+
+ // Open another window then play with focus
+ let window_B = yield BrowserTestUtils.openNewBrowserWindow();
+
+ yield promiseWaitForFocus(window_B);
+ yield isWindowState(window_A, [LOW, NORMAL]);
+ yield isWindowState(window_B, [HIGH]);
+
+ yield promiseWaitForFocus(window_A);
+ yield isWindowState(window_A, [NORMAL, HIGH]);
+ yield isWindowState(window_B, [NORMAL]);
+
+ yield promiseWaitForFocus(window_B);
+ yield isWindowState(window_A, [LOW, NORMAL]);
+ yield isWindowState(window_B, [HIGH]);
+
+ // Cleanup
+ window_A.gBrowser.removeTab(tab_A3);
+ yield BrowserTestUtils.closeWindow(window_B);
+});
+
+add_task(function*() {
+ // This is more a test of nsLoadGroup and how it handles priorities. But since
+ // we depend on its behavior, it's good to test it. This is testing that there
+ // are no errors if we adjust beyond nsISupportsPriority's bounds.
+
+ yield promiseWaitForFocus();
+
+ let tab1 = gBrowser.tabs[0];
+ let oldPriority = yield getPriority(tab1);
+
+ // Set the priority of tab1 to the lowest possible. Selecting the other tab
+ // will try to lower it
+ yield setPriority(tab1, LOWEST);
+
+ let tab2 = gBrowser.addTab("http://example.com");
+ yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
+ gBrowser.selectedTab = tab2;
+ is(yield getPriority(tab1), LOWEST - DELTA, "Can adjust priority beyond 'lowest'");
+
+ // Now set priority to "highest" and make sure that no errors occur.
+ yield setPriority(tab1, HIGHEST);
+ gBrowser.selectedTab = tab1;
+
+ is(yield getPriority(tab1), HIGHEST + DELTA, "Can adjust priority beyond 'highest'");
+
+ // Cleanup
+ gBrowser.removeTab(tab2);
+ yield setPriority(tab1, oldPriority);
+});
+
+add_task(function*() {
+ // This tests that the priority doesn't get lost when switching the browser's remoteness
+
+ if (!gMultiProcessBrowser) {
+ return;
+ }
+
+ let browser = gBrowser.selectedBrowser;
+
+ browser.loadURI("http://example.com");
+ yield BrowserTestUtils.browserLoaded(browser);
+ ok(browser.isRemoteBrowser, "web page should be loaded in remote browser");
+ is(yield getPriority(browser), HIGH, "priority of selected tab should be 'high'");
+
+ browser.loadURI("about:rights");
+ yield BrowserTestUtils.browserLoaded(browser);
+ ok(!browser.isRemoteBrowser, "about:rights should switch browser to non-remote");
+ is(yield getPriority(browser), HIGH,
+ "priority of selected tab should be 'high' when going from remote to non-remote");
+
+ browser.loadURI("http://example.com");
+ yield BrowserTestUtils.browserLoaded(browser);
+ ok(browser.isRemoteBrowser, "going from about:rights to web page should switch browser to remote");
+ is(yield getPriority(browser), HIGH,
+ "priority of selected tab should be 'high' when going from non-remote to remote");
+});
diff --git a/browser/modules/test/browser_PermissionUI.js b/browser/modules/test/browser_PermissionUI.js
new file mode 100644
index 000000000..006bc5e66
--- /dev/null
+++ b/browser/modules/test/browser_PermissionUI.js
@@ -0,0 +1,445 @@
+/**
+ * These tests test the ability for the PermissionUI module to open
+ * permission prompts to the user. It also tests to ensure that
+ * add-ons can introduce their own permission prompts.
+ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Integration.jsm", this);
+Cu.import("resource:///modules/PermissionUI.jsm", this);
+
+/**
+ * Given a <xul:browser> at some non-internal web page,
+ * return something that resembles an nsIContentPermissionRequest,
+ * using the browsers currently loaded document to get a principal.
+ *
+ * @param browser (<xul:browser>)
+ * The browser that we'll create a nsIContentPermissionRequest
+ * for.
+ * @returns A nsIContentPermissionRequest-ish object.
+ */
+function makeMockPermissionRequest(browser) {
+ let result = {
+ types: null,
+ principal: browser.contentPrincipal,
+ requester: null,
+ _cancelled: false,
+ cancel() {
+ this._cancelled = true;
+ },
+ _allowed: false,
+ allow() {
+ this._allowed = true;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
+ };
+
+ // In the e10s-case, nsIContentPermissionRequest will have
+ // element defined. window is defined otherwise.
+ if (browser.isRemoteBrowser) {
+ result.element = browser;
+ } else {
+ result.window = browser.contentWindow;
+ }
+
+ return result;
+}
+
+/**
+ * For an opened PopupNotification, clicks on the main action,
+ * and waits for the panel to fully close.
+ *
+ * @return {Promise}
+ * Resolves once the panel has fired the "popuphidden"
+ * event.
+ */
+function clickMainAction() {
+ let removePromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ let popupNotification = getPopupNotificationNode();
+ popupNotification.button.click();
+ return removePromise;
+}
+
+/**
+ * For an opened PopupNotification, clicks on a secondary action,
+ * and waits for the panel to fully close.
+ *
+ * @param {int} index
+ * The 0-indexed index of the secondary menuitem to choose.
+ * @return {Promise}
+ * Resolves once the panel has fired the "popuphidden"
+ * event.
+ */
+function clickSecondaryAction(index) {
+ let removePromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ let popupNotification = getPopupNotificationNode();
+ let menuitems = popupNotification.children;
+ menuitems[index].click();
+ return removePromise;
+}
+
+/**
+ * Makes sure that 1 (and only 1) <xul:popupnotification> is being displayed
+ * by PopupNotification, and then returns that <xul:popupnotification>.
+ *
+ * @return {<xul:popupnotification>}
+ */
+function getPopupNotificationNode() {
+ // PopupNotification is a bit overloaded here, so to be
+ // clear, popupNotifications is a list of <xul:popupnotification>
+ // nodes.
+ let popupNotifications = PopupNotifications.panel.childNodes;
+ Assert.equal(popupNotifications.length, 1,
+ "Should be showing a <xul:popupnotification>");
+ return popupNotifications[0];
+}
+
+/**
+ * Tests the PermissionPromptForRequest prototype to ensure that a prompt
+ * can be displayed. Does not test permission handling.
+ */
+add_task(function* test_permission_prompt_for_request() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com/",
+ }, function*(browser) {
+ const kTestNotificationID = "test-notification";
+ const kTestMessage = "Test message";
+ let mainAction = {
+ label: "Main",
+ accessKey: "M",
+ };
+ let secondaryAction = {
+ label: "Secondary",
+ accessKey: "S",
+ };
+
+ let mockRequest = makeMockPermissionRequest(browser);
+ let TestPrompt = {
+ __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+ request: mockRequest,
+ notificationID: kTestNotificationID,
+ message: kTestMessage,
+ promptActions: [mainAction, secondaryAction],
+ };
+
+ let shownPromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ TestPrompt.prompt();
+ yield shownPromise;
+ let notification =
+ PopupNotifications.getNotification(kTestNotificationID, browser);
+ Assert.ok(notification, "Should have gotten the notification");
+
+ Assert.equal(notification.message, kTestMessage,
+ "Should be showing the right message");
+ Assert.equal(notification.mainAction.label, mainAction.label,
+ "The main action should have the right label");
+ Assert.equal(notification.mainAction.accessKey, mainAction.accessKey,
+ "The main action should have the right access key");
+ Assert.equal(notification.secondaryActions.length, 1,
+ "There should only be 1 secondary action");
+ Assert.equal(notification.secondaryActions[0].label, secondaryAction.label,
+ "The secondary action should have the right label");
+ Assert.equal(notification.secondaryActions[0].accessKey,
+ secondaryAction.accessKey,
+ "The secondary action should have the right access key");
+ Assert.ok(notification.options.displayURI.equals(mockRequest.principal.URI),
+ "Should be showing the URI of the requesting page");
+
+ let removePromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ notification.remove();
+ yield removePromise;
+ });
+});
+
+/**
+ * Tests that if the PermissionPrompt sets displayURI to false in popupOptions,
+ * then there is no URI shown on the popupnotification.
+ */
+add_task(function* test_permission_prompt_for_popupOptions() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com/",
+ }, function*(browser) {
+ const kTestNotificationID = "test-notification";
+ const kTestMessage = "Test message";
+ let mainAction = {
+ label: "Main",
+ accessKey: "M",
+ };
+ let secondaryAction = {
+ label: "Secondary",
+ accessKey: "S",
+ };
+
+ let mockRequest = makeMockPermissionRequest(browser);
+ let TestPrompt = {
+ __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+ request: mockRequest,
+ notificationID: kTestNotificationID,
+ message: kTestMessage,
+ promptActions: [mainAction, secondaryAction],
+ popupOptions: {
+ displayURI: false,
+ },
+ };
+
+ let shownPromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ TestPrompt.prompt();
+ yield shownPromise;
+ let notification =
+ PopupNotifications.getNotification(kTestNotificationID, browser);
+
+ Assert.ok(!notification.options.displayURI,
+ "Should not show the URI of the requesting page");
+
+ let removePromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ notification.remove();
+ yield removePromise;
+ });
+});
+
+/**
+ * Tests that if the PermissionPrompt has the permissionKey
+ * set that permissions can be set properly by the user. Also
+ * ensures that callbacks for promptActions are properly fired.
+ */
+add_task(function* test_with_permission_key() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com",
+ }, function*(browser) {
+ const kTestNotificationID = "test-notification";
+ const kTestMessage = "Test message";
+ const kTestPermissionKey = "test-permission-key";
+
+ let allowed = false;
+ let mainAction = {
+ label: "Allow",
+ accessKey: "M",
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expiryType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ callback: function() {
+ allowed = true;
+ }
+ };
+
+ let denied = false;
+ let secondaryAction = {
+ label: "Deny",
+ accessKey: "D",
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expiryType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ callback: function() {
+ denied = true;
+ }
+ };
+
+ let mockRequest = makeMockPermissionRequest(browser);
+ let principal = mockRequest.principal;
+ registerCleanupFunction(function() {
+ Services.perms.removeFromPrincipal(principal, kTestPermissionKey);
+ });
+
+ let TestPrompt = {
+ __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+ request: mockRequest,
+ notificationID: kTestNotificationID,
+ permissionKey: kTestPermissionKey,
+ message: kTestMessage,
+ promptActions: [mainAction, secondaryAction],
+ };
+
+ let shownPromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ TestPrompt.prompt();
+ yield shownPromise;
+ let notification =
+ PopupNotifications.getNotification(kTestNotificationID, browser);
+ Assert.ok(notification, "Should have gotten the notification");
+
+ let curPerm =
+ Services.perms.testPermissionFromPrincipal(principal,
+ kTestPermissionKey);
+ Assert.equal(curPerm, Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "Should be no permission set to begin with.");
+
+ // First test denying the permission request.
+ Assert.equal(notification.secondaryActions.length, 1,
+ "There should only be 1 secondary action");
+ yield clickSecondaryAction(0);
+ curPerm = Services.perms.testPermissionFromPrincipal(principal,
+ kTestPermissionKey);
+ Assert.equal(curPerm, Ci.nsIPermissionManager.DENY_ACTION,
+ "Should have denied the action");
+ Assert.ok(denied, "The secondaryAction callback should have fired");
+ Assert.ok(!allowed, "The mainAction callback should not have fired");
+ Assert.ok(mockRequest._cancelled,
+ "The request should have been cancelled");
+ Assert.ok(!mockRequest._allowed,
+ "The request should not have been allowed");
+
+ // Clear the permission and pretend we never denied
+ Services.perms.removeFromPrincipal(principal, kTestPermissionKey);
+ denied = false;
+ mockRequest._cancelled = false;
+
+ // Bring the PopupNotification back up now...
+ shownPromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ TestPrompt.prompt();
+ yield shownPromise;
+
+ // Next test allowing the permission request.
+ yield clickMainAction();
+ curPerm = Services.perms.testPermissionFromPrincipal(principal,
+ kTestPermissionKey);
+ Assert.equal(curPerm, Ci.nsIPermissionManager.ALLOW_ACTION,
+ "Should have allowed the action");
+ Assert.ok(!denied, "The secondaryAction callback should not have fired");
+ Assert.ok(allowed, "The mainAction callback should have fired");
+ Assert.ok(!mockRequest._cancelled,
+ "The request should not have been cancelled");
+ Assert.ok(mockRequest._allowed,
+ "The request should have been allowed");
+ });
+});
+
+/**
+ * Tests that the onBeforeShow method will be called before
+ * the popup appears.
+ */
+add_task(function* test_on_before_show() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com",
+ }, function*(browser) {
+ const kTestNotificationID = "test-notification";
+ const kTestMessage = "Test message";
+
+ let mainAction = {
+ label: "Test action",
+ accessKey: "T",
+ };
+
+ let mockRequest = makeMockPermissionRequest(browser);
+ let beforeShown = false;
+
+ let TestPrompt = {
+ __proto__: PermissionUI.PermissionPromptForRequestPrototype,
+ request: mockRequest,
+ notificationID: kTestNotificationID,
+ message: kTestMessage,
+ promptActions: [mainAction],
+ onBeforeShow() {
+ beforeShown = true;
+ }
+ };
+
+ let shownPromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ TestPrompt.prompt();
+ Assert.ok(beforeShown, "Should have called onBeforeShown");
+ yield shownPromise;
+ let notification =
+ PopupNotifications.getNotification(kTestNotificationID, browser);
+
+ let removePromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+ notification.remove();
+ yield removePromise;
+ });
+});
+
+/**
+ * Tests that we can open a PermissionPrompt without wrapping a
+ * nsIContentPermissionRequest.
+ */
+add_task(function* test_no_request() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com",
+ }, function*(browser) {
+ const kTestNotificationID = "test-notification";
+ let allowed = false;
+ let mainAction = {
+ label: "Allow",
+ accessKey: "M",
+ callback: function() {
+ allowed = true;
+ }
+ };
+
+ let denied = false;
+ let secondaryAction = {
+ label: "Deny",
+ accessKey: "D",
+ callback: function() {
+ denied = true;
+ }
+ };
+
+ const kTestMessage = "Test message with no request";
+ let principal = browser.contentPrincipal;
+ let beforeShown = false;
+
+ let TestPrompt = {
+ __proto__: PermissionUI.PermissionPromptPrototype,
+ notificationID: kTestNotificationID,
+ principal,
+ browser,
+ message: kTestMessage,
+ promptActions: [mainAction, secondaryAction],
+ onBeforeShow() {
+ beforeShown = true;
+ }
+ };
+
+ let shownPromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ TestPrompt.prompt();
+ Assert.ok(beforeShown, "Should have called onBeforeShown");
+ yield shownPromise;
+ let notification =
+ PopupNotifications.getNotification(kTestNotificationID, browser);
+
+ Assert.equal(notification.message, kTestMessage,
+ "Should be showing the right message");
+ Assert.equal(notification.mainAction.label, mainAction.label,
+ "The main action should have the right label");
+ Assert.equal(notification.mainAction.accessKey, mainAction.accessKey,
+ "The main action should have the right access key");
+ Assert.equal(notification.secondaryActions.length, 1,
+ "There should only be 1 secondary action");
+ Assert.equal(notification.secondaryActions[0].label, secondaryAction.label,
+ "The secondary action should have the right label");
+ Assert.equal(notification.secondaryActions[0].accessKey,
+ secondaryAction.accessKey,
+ "The secondary action should have the right access key");
+ Assert.ok(notification.options.displayURI.equals(principal.URI),
+ "Should be showing the URI of the requesting page");
+
+ // First test denying the permission request.
+ Assert.equal(notification.secondaryActions.length, 1,
+ "There should only be 1 secondary action");
+ yield clickSecondaryAction(0);
+ Assert.ok(denied, "The secondaryAction callback should have fired");
+ Assert.ok(!allowed, "The mainAction callback should not have fired");
+
+ shownPromise =
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+ TestPrompt.prompt();
+ yield shownPromise;
+
+ // Next test allowing the permission request.
+ yield clickMainAction();
+ Assert.ok(allowed, "The mainAction callback should have fired");
+ });
+});
diff --git a/browser/modules/test/browser_ProcessHangNotifications.js b/browser/modules/test/browser_ProcessHangNotifications.js
new file mode 100644
index 000000000..597be611a
--- /dev/null
+++ b/browser/modules/test/browser_ProcessHangNotifications.js
@@ -0,0 +1,189 @@
+
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
+
+function getNotificationBox(aWindow) {
+ return aWindow.document.getElementById("high-priority-global-notificationbox");
+}
+
+function promiseNotificationShown(aWindow, aName) {
+ return new Promise((resolve) => {
+ let notification = getNotificationBox(aWindow);
+ notification.addEventListener("AlertActive", function active() {
+ notification.removeEventListener("AlertActive", active, true);
+ is(notification.allNotifications.length, 1, "Notification Displayed.");
+ resolve(notification);
+ });
+ });
+}
+
+function promiseReportCallMade(aValue) {
+ return new Promise((resolve) => {
+ let old = gTestHangReport.testCallback;
+ gTestHangReport.testCallback = function (val) {
+ gTestHangReport.testCallback = old;
+ is(aValue, val, "was the correct method call made on the hang report object?");
+ resolve();
+ };
+ });
+}
+
+function pushPrefs(...aPrefs) {
+ return new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
+ resolve();
+ });
+}
+
+function popPrefs() {
+ return new Promise((resolve) => {
+ SpecialPowers.popPrefEnv(resolve);
+ resolve();
+ });
+}
+
+let gTestHangReport = {
+ SLOW_SCRIPT: 1,
+ PLUGIN_HANG: 2,
+
+ TEST_CALLBACK_CANCELED: 1,
+ TEST_CALLBACK_TERMSCRIPT: 2,
+ TEST_CALLBACK_TERMPLUGIN: 3,
+
+ _hangType: 1,
+ _tcb: function (aCallbackType) {},
+
+ get hangType() {
+ return this._hangType;
+ },
+
+ set hangType(aValue) {
+ this._hangType = aValue;
+ },
+
+ set testCallback(aValue) {
+ this._tcb = aValue;
+ },
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Components.interfaces.nsIHangReport) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ userCanceled: function () {
+ this._tcb(this.TEST_CALLBACK_CANCELED);
+ },
+
+ terminateScript: function () {
+ this._tcb(this.TEST_CALLBACK_TERMSCRIPT);
+ },
+
+ terminatePlugin: function () {
+ this._tcb(this.TEST_CALLBACK_TERMPLUGIN);
+ },
+
+ isReportForBrowser: function(aFrameLoader) {
+ return true;
+ }
+};
+
+// on dev edition we add a button for js debugging of hung scripts.
+let buttonCount = (UpdateUtils.UpdateChannel == "aurora" ? 3 : 2);
+
+/**
+ * Test if hang reports receive a terminate script callback when the user selects
+ * stop in response to a script hang.
+ */
+
+add_task(function* terminateScriptTest() {
+ let promise = promiseNotificationShown(window, "process-hang");
+ Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
+ let notification = yield promise;
+
+ let buttons = notification.currentNotification.getElementsByTagName("button");
+ // Fails on aurora on-push builds, bug 1232204
+ // is(buttons.length, buttonCount, "proper number of buttons");
+
+ // Click the "Stop It" button, we should get a terminate script callback
+ gTestHangReport.hangType = gTestHangReport.SLOW_SCRIPT;
+ promise = promiseReportCallMade(gTestHangReport.TEST_CALLBACK_TERMSCRIPT);
+ buttons[0].click();
+ yield promise;
+});
+
+/**
+ * Test if hang reports receive user canceled callbacks after a user selects wait
+ * and the browser frees up from a script hang on its own.
+ */
+
+add_task(function* waitForScriptTest() {
+ let promise = promiseNotificationShown(window, "process-hang");
+ Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
+ let notification = yield promise;
+
+ let buttons = notification.currentNotification.getElementsByTagName("button");
+ // Fails on aurora on-push builds, bug 1232204
+ // is(buttons.length, buttonCount, "proper number of buttons");
+
+ yield pushPrefs(["browser.hangNotification.waitPeriod", 1000]);
+
+ function nocbcheck() {
+ ok(false, "received a callback?");
+ }
+ let oldcb = gTestHangReport.testCallback;
+ gTestHangReport.testCallback = nocbcheck;
+ // Click the "Wait" button this time, we shouldn't get a callback at all.
+ buttons[1].click();
+ gTestHangReport.testCallback = oldcb;
+
+ // send another hang pulse, we should not get a notification here
+ Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
+ is(notification.currentNotification, null, "no notification should be visible");
+
+ gTestHangReport.testCallback = function() {};
+ Services.obs.notifyObservers(gTestHangReport, "clear-hang-report", null);
+ gTestHangReport.testCallback = oldcb;
+
+ yield popPrefs();
+});
+
+/**
+ * Test if hang reports receive user canceled callbacks after the content
+ * process stops sending hang notifications.
+ */
+
+add_task(function* hangGoesAwayTest() {
+ yield pushPrefs(["browser.hangNotification.expiration", 1000]);
+
+ let promise = promiseNotificationShown(window, "process-hang");
+ Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
+ yield promise;
+
+ promise = promiseReportCallMade(gTestHangReport.TEST_CALLBACK_CANCELED);
+ Services.obs.notifyObservers(gTestHangReport, "clear-hang-report", null);
+ yield promise;
+
+ yield popPrefs();
+});
+
+/**
+ * Tests if hang reports receive a terminate plugin callback when the user selects
+ * stop in response to a plugin hang.
+ */
+
+add_task(function* terminatePluginTest() {
+ let promise = promiseNotificationShown(window, "process-hang");
+ Services.obs.notifyObservers(gTestHangReport, "process-hang-report", null);
+ let notification = yield promise;
+
+ let buttons = notification.currentNotification.getElementsByTagName("button");
+ // Fails on aurora on-push builds, bug 1232204
+ // is(buttons.length, buttonCount, "proper number of buttons");
+
+ // Click the "Stop It" button, we should get a terminate script callback
+ gTestHangReport.hangType = gTestHangReport.PLUGIN_HANG;
+ promise = promiseReportCallMade(gTestHangReport.TEST_CALLBACK_TERMPLUGIN);
+ buttons[0].click();
+ yield promise;
+});
diff --git a/browser/modules/test/browser_SelfSupportBackend.js b/browser/modules/test/browser_SelfSupportBackend.js
new file mode 100644
index 000000000..9e2c1d181
--- /dev/null
+++ b/browser/modules/test/browser_SelfSupportBackend.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";
+
+// Pass an empty scope object to the import to prevent "leaked window property"
+// errors in tests.
+var Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
+var PromiseUtils = Cu.import("resource://gre/modules/PromiseUtils.jsm", {}).PromiseUtils;
+var SelfSupportBackend =
+ Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackend;
+
+const PREF_SELFSUPPORT_ENABLED = "browser.selfsupport.enabled";
+const PREF_SELFSUPPORT_URL = "browser.selfsupport.url";
+const PREF_UITOUR_ENABLED = "browser.uitour.enabled";
+
+const TEST_WAIT_RETRIES = 60;
+
+const TEST_PAGE_URL = getRootDirectory(gTestPath) + "uitour.html";
+const TEST_PAGE_URL_HTTPS = TEST_PAGE_URL.replace("chrome://mochitests/content/", "https://example.com/");
+
+function sendSessionRestoredNotification() {
+ let selfSupportBackendImpl =
+ Cu.import("resource:///modules/SelfSupportBackend.jsm", {}).SelfSupportBackendInternal;
+ selfSupportBackendImpl.observe(null, "sessionstore-windows-restored", null);
+}
+
+/**
+ * Find a browser, with an IFRAME as parent, who has aURL as the source attribute.
+ *
+ * @param aURL The URL to look for to identify the browser.
+ *
+ * @returns {Object} The browser element or null on failure.
+ */
+function findSelfSupportBrowser(aURL) {
+ let frames = Services.appShell.hiddenDOMWindow.document.querySelectorAll('iframe');
+ for (let frame of frames) {
+ try {
+ let browser = frame.contentDocument.getElementById("win").querySelectorAll('browser')[0];
+ let url = browser.getAttribute("src");
+ if (url == aURL) {
+ return browser;
+ }
+ } catch (e) {
+ continue;
+ }
+ }
+ return null;
+}
+
+/**
+ * Wait for self support page to load.
+ *
+ * @param aURL The URL to look for to identify the browser.
+ *
+ * @returns {Promise} Return a promise which is resolved when SelfSupport page is fully
+ * loaded.
+ */
+function promiseSelfSupportLoad(aURL) {
+ return new Promise((resolve, reject) => {
+ // Find the SelfSupport browser.
+ let browserPromise = waitForConditionPromise(() => !!findSelfSupportBrowser(aURL),
+ "SelfSupport browser not found.",
+ TEST_WAIT_RETRIES);
+
+ // Once found, append a "load" listener to catch page loads.
+ browserPromise.then(() => {
+ let browser = findSelfSupportBrowser(aURL);
+ if (browser.contentDocument.readyState === "complete") {
+ resolve(browser);
+ } else {
+ let handler = () => {
+ browser.removeEventListener("load", handler, true);
+ resolve(browser);
+ };
+ browser.addEventListener("load", handler, true);
+ }
+ }, reject);
+ });
+}
+
+/**
+ * Wait for self support to close.
+ *
+ * @param aURL The URL to look for to identify the browser.
+ *
+ * @returns {Promise} Return a promise which is resolved when SelfSupport browser cannot
+ * be found anymore.
+ */
+function promiseSelfSupportClose(aURL) {
+ return waitForConditionPromise(() => !findSelfSupportBrowser(aURL),
+ "SelfSupport browser is still open.", TEST_WAIT_RETRIES);
+}
+
+/**
+ * Prepare the test environment.
+ */
+add_task(function* setupEnvironment() {
+ // We always run the SelfSupportBackend in tests to check for weird behaviours.
+ // Disable it to test its start-up.
+ SelfSupportBackend.uninit();
+
+ // Testing prefs are set via |user_pref|, so we need to get their value in order
+ // to restore them.
+ let selfSupportEnabled = Preferences.get(PREF_SELFSUPPORT_ENABLED, true);
+ let uitourEnabled = Preferences.get(PREF_UITOUR_ENABLED, false);
+ let selfSupportURL = Preferences.get(PREF_SELFSUPPORT_URL, "");
+
+ // Enable the SelfSupport backend and set the page URL. We also make sure UITour
+ // is enabled.
+ Preferences.set(PREF_SELFSUPPORT_ENABLED, true);
+ Preferences.set(PREF_UITOUR_ENABLED, true);
+ Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL_HTTPS);
+
+ // Whitelist the HTTPS page to use UITour.
+ let pageURI = Services.io.newURI(TEST_PAGE_URL_HTTPS, null, null);
+ Services.perms.add(pageURI, "uitour", Services.perms.ALLOW_ACTION);
+
+ registerCleanupFunction(() => {
+ Services.perms.remove(pageURI, "uitour");
+ Preferences.set(PREF_SELFSUPPORT_ENABLED, selfSupportEnabled);
+ Preferences.set(PREF_UITOUR_ENABLED, uitourEnabled);
+ Preferences.set(PREF_SELFSUPPORT_URL, selfSupportURL);
+ });
+});
+
+/**
+ * Test that the self support page can use the UITour API and close itself.
+ */
+add_task(function* test_selfSupport() {
+ // Initialise the SelfSupport backend and trigger the load.
+ SelfSupportBackend.init();
+
+ // SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
+ info("Sending sessionstore-windows-restored");
+ sendSessionRestoredNotification();
+
+ // Wait for the SelfSupport page to load.
+ info("Waiting for the SelfSupport local page to load.");
+ let selfSupportBrowser = yield promiseSelfSupportLoad(TEST_PAGE_URL_HTTPS);
+ Assert.ok(!!selfSupportBrowser, "SelfSupport browser must exist.");
+
+ // Get a reference to the UITour API.
+ info("Testing access to the UITour API.");
+ let contentWindow =
+ Cu.waiveXrays(selfSupportBrowser.contentDocument.defaultView);
+ let uitourAPI = contentWindow.Mozilla.UITour;
+
+ // Test the UITour API with a ping.
+ let pingPromise = new Promise((resolve) => {
+ uitourAPI.ping(resolve);
+ });
+ yield pingPromise;
+ info("Ping succeeded");
+
+ let observePromise = ContentTask.spawn(selfSupportBrowser, null, function* checkObserve() {
+ yield new Promise(resolve => {
+ let win = Cu.waiveXrays(content);
+ win.Mozilla.UITour.observe((event, data) => {
+ if (event != "Heartbeat:Engaged") {
+ return;
+ }
+ Assert.equal(data.flowId, "myFlowID", "Check flowId");
+ Assert.ok(!!data.timestamp, "Check timestamp");
+ resolve(data);
+ }, () => {});
+ });
+ });
+
+ info("Notifying Heartbeat:Engaged");
+ UITour.notify("Heartbeat:Engaged", {
+ flowId: "myFlowID",
+ timestamp: Date.now(),
+ });
+ yield observePromise;
+ info("Observed in the hidden frame");
+
+ // Close SelfSupport from content.
+ contentWindow.close();
+
+ // Wait until SelfSupport closes.
+ info("Waiting for the SelfSupport to close.");
+ yield promiseSelfSupportClose(TEST_PAGE_URL_HTTPS);
+
+ // Find the SelfSupport browser, again. We don't expect to find it.
+ selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL_HTTPS);
+ Assert.ok(!selfSupportBrowser, "SelfSupport browser must not exist.");
+
+ // We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
+ // doesn't create any problem.
+ SelfSupportBackend.uninit();
+});
+
+/**
+ * Test that SelfSupportBackend only allows HTTPS.
+ */
+add_task(function* test_selfSupport_noHTTPS() {
+ Preferences.set(PREF_SELFSUPPORT_URL, TEST_PAGE_URL);
+
+ SelfSupportBackend.init();
+
+ // SelfSupportBackend waits for "sessionstore-windows-restored" to start loading. Send it.
+ info("Sending sessionstore-windows-restored");
+ sendSessionRestoredNotification();
+
+ // Find the SelfSupport browser. We don't expect to find it since we are not using https.
+ let selfSupportBrowser = findSelfSupportBrowser(TEST_PAGE_URL);
+ Assert.ok(!selfSupportBrowser, "SelfSupport browser must not exist.");
+
+ // We shouldn't need this, but let's keep it to make sure closing SelfSupport twice
+ // doesn't create any problem.
+ SelfSupportBackend.uninit();
+})
diff --git a/browser/modules/test/browser_UnsubmittedCrashHandler.js b/browser/modules/test/browser_UnsubmittedCrashHandler.js
new file mode 100644
index 000000000..2d78c746b
--- /dev/null
+++ b/browser/modules/test/browser_UnsubmittedCrashHandler.js
@@ -0,0 +1,680 @@
+"use strict";
+
+/**
+ * This suite tests the "unsubmitted crash report" notification
+ * that is seen when we detect pending crash reports on startup.
+ */
+
+const { UnsubmittedCrashHandler } =
+ Cu.import("resource:///modules/ContentCrashHandlers.jsm", this);
+const { FileUtils } =
+ Cu.import("resource://gre/modules/FileUtils.jsm", this);
+const { makeFakeAppDir } =
+ Cu.import("resource://testing-common/AppData.jsm", this);
+const { OS } =
+ Cu.import("resource://gre/modules/osfile.jsm", this);
+
+const DAY = 24 * 60 * 60 * 1000; // milliseconds
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+
+/**
+ * Returns the directly where the browsing is storing the
+ * pending crash reports.
+ *
+ * @returns nsIFile
+ */
+function getPendingCrashReportDir() {
+ // The fake UAppData directory that makeFakeAppDir provides
+ // is just UAppData under the profile directory.
+ return FileUtils.getDir("ProfD", [
+ "UAppData",
+ "Crash Reports",
+ "pending",
+ ], false);
+}
+
+/**
+ * Synchronously deletes all entries inside the pending
+ * crash report directory.
+ */
+function clearPendingCrashReports() {
+ let dir = getPendingCrashReportDir();
+ let entries = dir.directoryEntries;
+
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+ if (entry.isFile()) {
+ entry.remove(false);
+ }
+ }
+}
+
+/**
+ * Randomly generates howMany crash report .dmp and .extra files
+ * to put into the pending crash report directory. We're not
+ * actually creating real crash reports here, just stubbing
+ * out enough of the files to satisfy our notification and
+ * submission code.
+ *
+ * @param howMany (int)
+ * How many pending crash reports to put in the pending
+ * crash report directory.
+ * @param accessDate (Date, optional)
+ * What date to set as the last accessed time on the created
+ * crash reports. This defaults to the current date and time.
+ * @returns Promise
+ */
+function* createPendingCrashReports(howMany, accessDate) {
+ let dir = getPendingCrashReportDir();
+ if (!accessDate) {
+ accessDate = new Date();
+ }
+
+ /**
+ * Helper function for creating a file in the pending crash report
+ * directory.
+ *
+ * @param fileName (string)
+ * The filename for the crash report, not including the
+ * extension. This is usually a UUID.
+ * @param extension (string)
+ * The file extension for the created file.
+ * @param accessDate (Date)
+ * The date to set lastAccessed to.
+ * @param contents (string, optional)
+ * Set this to whatever the file needs to contain, if anything.
+ * @returns Promise
+ */
+ let createFile = (fileName, extension, accessDate, contents) => {
+ let file = dir.clone();
+ file.append(fileName + "." + extension);
+ file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ let promises = [OS.File.setDates(file.path, accessDate)];
+
+ if (contents) {
+ let encoder = new TextEncoder();
+ let array = encoder.encode(contents);
+ promises.push(OS.File.writeAtomic(file.path, array, {
+ tmpPath: file.path + ".tmp",
+ }));
+ }
+ return Promise.all(promises);
+ }
+
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ // CrashSubmit expects there to be a ServerURL key-value
+ // pair in the .extra file, so we'll satisfy it.
+ let extraFileContents = "ServerURL=" + SERVER_URL;
+
+ return Task.spawn(function*() {
+ let uuids = [];
+ for (let i = 0; i < howMany; ++i) {
+ let uuid = uuidGenerator.generateUUID().toString();
+ // Strip the {}...
+ uuid = uuid.substring(1, uuid.length - 1);
+ yield createFile(uuid, "dmp", accessDate);
+ yield createFile(uuid, "extra", accessDate, extraFileContents);
+ uuids.push(uuid);
+ }
+ return uuids;
+ });
+}
+
+/**
+ * Returns a Promise that resolves once CrashSubmit starts sending
+ * success notifications for crash submission matching the reportIDs
+ * being passed in.
+ *
+ * @param reportIDs (Array<string>)
+ * The IDs for the reports that we expect CrashSubmit to have sent.
+ * @returns Promise
+ */
+function waitForSubmittedReports(reportIDs) {
+ let promises = [];
+ for (let reportID of reportIDs) {
+ let promise = TestUtils.topicObserved("crash-report-status", (subject, data) => {
+ if (data == "success") {
+ let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
+ let dumpID = propBag.getPropertyAsAString("minidumpID");
+ if (dumpID == reportID) {
+ return true;
+ }
+ }
+ return false;
+ });
+ promises.push(promise);
+ }
+ return Promise.all(promises);
+}
+
+/**
+ * Returns a Promise that resolves once a .dmp.ignore file is created for
+ * the crashes in the pending directory matching the reportIDs being
+ * passed in.
+ *
+ * @param reportIDs (Array<string>)
+ * The IDs for the reports that we expect CrashSubmit to have been
+ * marked for ignoring.
+ * @returns Promise
+ */
+function waitForIgnoredReports(reportIDs) {
+ let dir = getPendingCrashReportDir();
+ let promises = [];
+ for (let reportID of reportIDs) {
+ let file = dir.clone();
+ file.append(reportID + ".dmp.ignore");
+ promises.push(OS.File.exists(file.path));
+ }
+ return Promise.all(promises);
+}
+
+let gNotificationBox;
+
+add_task(function* setup() {
+ // Pending crash reports are stored in the UAppData folder,
+ // which exists outside of the profile folder. In order to
+ // not overwrite / clear pending crash reports for the poor
+ // soul who runs this test, we use AppData.jsm to point to
+ // a special made-up directory inside the profile
+ // directory.
+ yield makeFakeAppDir();
+ // We'll assume that the notifications will be shown in the current
+ // browser window's global notification box.
+ gNotificationBox = document.getElementById("global-notificationbox");
+
+ // If we happen to already be seeing the unsent crash report
+ // notification, it's because the developer running this test
+ // happened to have some unsent reports in their UAppDir.
+ // We'll remove the notification without touching those reports.
+ let notification =
+ gNotificationBox.getNotificationWithValue("pending-crash-reports");
+ if (notification) {
+ notification.close();
+ }
+
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ let oldServerURL = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ // nsBrowserGlue starts up UnsubmittedCrashHandler automatically
+ // so at this point, it is initialized. It's possible that it
+ // was initialized, but is preffed off, so it's inert, so we
+ // shut it down, make sure it's preffed on, and then restart it.
+ // Note that making the component initialize even when it's
+ // disabled is an intentional choice, as this allows for easier
+ // simulation of startup and shutdown.
+ UnsubmittedCrashHandler.uninit();
+ yield SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.crashReports.unsubmittedCheck.enabled", true],
+ ],
+ });
+ UnsubmittedCrashHandler.init();
+
+ registerCleanupFunction(function() {
+ gNotificationBox = null;
+ clearPendingCrashReports();
+ env.set("MOZ_CRASHREPORTER_URL", oldServerURL);
+ });
+});
+
+/**
+ * Tests that if there are no pending crash reports, then the
+ * notification will not show up.
+ */
+add_task(function* test_no_pending_no_notification() {
+ // Make absolutely sure there are no pending crash reports first...
+ clearPendingCrashReports();
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(notification, null,
+ "There should not be a notification if there are no " +
+ "pending crash reports");
+});
+
+/**
+ * Tests that there is a notification if there is one pending
+ * crash report.
+ */
+add_task(function* test_one_pending() {
+ yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that there is a notification if there is more than one
+ * pending crash report.
+ */
+add_task(function* test_several_pending() {
+ yield createPendingCrashReports(3);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that there is no notification if the only pending crash
+ * reports are over 28 days old. Also checks that if we put a newer
+ * crash with that older set, that we can still get a notification.
+ */
+add_task(function* test_several_pending() {
+ // Let's create some crash reports from 30 days ago.
+ let oldDate = new Date(Date.now() - (30 * DAY));
+ yield createPendingCrashReports(3, oldDate);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(notification, null,
+ "There should not be a notification if there are only " +
+ "old pending crash reports");
+ // Now let's create a new one and check again
+ yield createPendingCrashReports(1);
+ notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that the notification can submit a report.
+ */
+add_task(function* test_can_submit() {
+ let reportIDs = yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Attempt to submit the notification by clicking on the submit
+ // button
+ let buttons = notification.querySelectorAll(".notification-button");
+ // ...which should be the first button.
+ let submit = buttons[0];
+
+ let promiseReports = waitForSubmittedReports(reportIDs);
+ info("Sending crash report");
+ submit.click();
+ info("Sent!");
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ info("Waiting on reports to be received.");
+ yield promiseReports;
+ info("Received!");
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that the notification can submit multiple reports.
+ */
+add_task(function* test_can_submit_several() {
+ let reportIDs = yield createPendingCrashReports(3);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Attempt to submit the notification by clicking on the submit
+ // button
+ let buttons = notification.querySelectorAll(".notification-button");
+ // ...which should be the first button.
+ let submit = buttons[0];
+
+ let promiseReports = waitForSubmittedReports(reportIDs);
+ info("Sending crash reports");
+ submit.click();
+ info("Sent!");
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ info("Waiting on reports to be received.");
+ yield promiseReports;
+ info("Received!");
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that choosing "Send Always" flips the autoSubmit pref
+ * and sends the pending crash reports.
+ */
+add_task(function* test_can_submit_always() {
+ let pref = "browser.crashReports.unsubmittedCheck.autoSubmit2";
+ Assert.equal(Services.prefs.getBoolPref(pref), false,
+ "We should not be auto-submitting by default");
+
+ let reportIDs = yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Attempt to submit the notification by clicking on the send all
+ // button
+ let buttons = notification.querySelectorAll(".notification-button");
+ // ...which should be the second button.
+ let sendAll = buttons[1];
+
+ let promiseReports = waitForSubmittedReports(reportIDs);
+ info("Sending crash reports");
+ sendAll.click();
+ info("Sent!");
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ info("Waiting on reports to be received.");
+ yield promiseReports;
+ info("Received!");
+
+ // Make sure the pref was set
+ Assert.equal(Services.prefs.getBoolPref(pref), true,
+ "The autoSubmit pref should have been set");
+
+ // And revert back to default now.
+ Services.prefs.clearUserPref(pref);
+
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if the user has chosen to automatically send
+ * crash reports that no notification is displayed to the
+ * user.
+ */
+add_task(function* test_can_auto_submit() {
+ yield SpecialPowers.pushPrefEnv({ set: [
+ ["browser.crashReports.unsubmittedCheck.autoSubmit2", true],
+ ]});
+
+ let reportIDs = yield createPendingCrashReports(3);
+ let promiseReports = waitForSubmittedReports(reportIDs);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(notification, null, "There should be no notification");
+ info("Waiting on reports to be received.");
+ yield promiseReports;
+ info("Received!");
+
+ clearPendingCrashReports();
+ yield SpecialPowers.popPrefEnv();
+});
+
+/**
+ * Tests that if the user chooses to dismiss the notification,
+ * then the current pending requests won't cause the notification
+ * to appear again in the future.
+ */
+add_task(function* test_can_ignore() {
+ let reportIDs = yield createPendingCrashReports(3);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Dismiss the notification by clicking on the "X" button.
+ let anonyNodes = document.getAnonymousNodes(notification)[0];
+ let closeButton = anonyNodes.querySelector(".close-icon");
+ closeButton.click();
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+ yield waitForIgnoredReports(reportIDs);
+
+ notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(notification, null, "There should be no notification");
+
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if the notification is shown, then the
+ * lastShownDate is set for today.
+ */
+add_task(function* test_last_shown_date() {
+ yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ let today = UnsubmittedCrashHandler.dateString(new Date());
+ let lastShownDate =
+ UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate");
+ Assert.equal(today, lastShownDate,
+ "Last shown date should be today.");
+
+ UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate");
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if UnsubmittedCrashHandler is uninit with a
+ * notification still being shown, that
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * set to true.
+ */
+add_task(function* test_shutdown_while_showing() {
+ yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ UnsubmittedCrashHandler.uninit();
+ let shutdownWhileShowing =
+ UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing");
+ Assert.ok(shutdownWhileShowing,
+ "We should have noticed that we uninitted while showing " +
+ "the notification.");
+ UnsubmittedCrashHandler.prefs.clearUserPref("shutdownWhileShowing");
+ UnsubmittedCrashHandler.init();
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if UnsubmittedCrashHandler is uninit after
+ * the notification has been closed, that
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * not set in prefs.
+ */
+add_task(function* test_shutdown_while_not_showing() {
+ let reportIDs = yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Dismiss the notification by clicking on the "X" button.
+ let anonyNodes = document.getAnonymousNodes(notification)[0];
+ let closeButton = anonyNodes.querySelector(".close-icon");
+ closeButton.click();
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ yield waitForIgnoredReports(reportIDs);
+
+ UnsubmittedCrashHandler.uninit();
+ Assert.throws(() => {
+ UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing");
+ }, "We should have noticed that the notification had closed before " +
+ "uninitting.");
+ UnsubmittedCrashHandler.init();
+
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * set and the lastShownDate is today, then we don't decrement
+ * browser.crashReports.unsubmittedCheck.chancesUntilSuppress.
+ */
+add_task(function* test_dont_decrement_chances_on_same_day() {
+ let initChances =
+ UnsubmittedCrashHandler.prefs.getIntPref("chancesUntilSuppress");
+ Assert.ok(initChances > 1, "We should start with at least 1 chance.");
+
+ yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ UnsubmittedCrashHandler.uninit();
+
+ gNotificationBox.removeNotification(notification, true);
+
+ let shutdownWhileShowing =
+ UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing");
+ Assert.ok(shutdownWhileShowing,
+ "We should have noticed that we uninitted while showing " +
+ "the notification.");
+
+ let today = UnsubmittedCrashHandler.dateString(new Date());
+ let lastShownDate =
+ UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate");
+ Assert.equal(today, lastShownDate,
+ "Last shown date should be today.");
+
+ UnsubmittedCrashHandler.init();
+
+ notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should still be a notification");
+
+ let chances =
+ UnsubmittedCrashHandler.prefs.getIntPref("chancesUntilSuppress");
+
+ Assert.equal(initChances, chances,
+ "We should not have decremented chances.");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * set and the lastShownDate is before today, then we decrement
+ * browser.crashReports.unsubmittedCheck.chancesUntilSuppress.
+ */
+add_task(function* test_decrement_chances_on_other_day() {
+ let initChances =
+ UnsubmittedCrashHandler.prefs.getIntPref("chancesUntilSuppress");
+ Assert.ok(initChances > 1, "We should start with at least 1 chance.");
+
+ yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ UnsubmittedCrashHandler.uninit();
+
+ gNotificationBox.removeNotification(notification, true);
+
+ let shutdownWhileShowing =
+ UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing");
+ Assert.ok(shutdownWhileShowing,
+ "We should have noticed that we uninitted while showing " +
+ "the notification.");
+
+ // Now pretend that the notification was shown yesterday.
+ let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY));
+ UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday);
+
+ UnsubmittedCrashHandler.init();
+
+ notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should still be a notification");
+
+ let chances =
+ UnsubmittedCrashHandler.prefs.getIntPref("chancesUntilSuppress");
+
+ Assert.equal(initChances - 1, chances,
+ "We should have decremented our chances.");
+ UnsubmittedCrashHandler.prefs.clearUserPref("chancesUntilSuppress");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if we've shutdown too many times showing the
+ * notification, and we've run out of chances, then
+ * browser.crashReports.unsubmittedCheck.suppressUntilDate is
+ * set for some days into the future.
+ */
+add_task(function* test_can_suppress_after_chances() {
+ // Pretend that a notification was shown yesterday.
+ let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY));
+ UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday);
+ UnsubmittedCrashHandler.prefs.setBoolPref("shutdownWhileShowing", true);
+ UnsubmittedCrashHandler.prefs.setIntPref("chancesUntilSuppress", 0);
+
+ yield createPendingCrashReports(1);
+ let notification =
+ yield UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(notification, null,
+ "There should be no notification if we've run out of chances");
+
+ // We should have set suppressUntilDate into the future
+ let suppressUntilDate =
+ UnsubmittedCrashHandler.prefs.getCharPref("suppressUntilDate");
+
+ let today = UnsubmittedCrashHandler.dateString(new Date());
+ Assert.ok(suppressUntilDate > today,
+ "We should be suppressing until some days into the future.");
+
+ UnsubmittedCrashHandler.prefs.clearUserPref("chancesUntilSuppress");
+ UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate");
+ UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate");
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if there's a suppression date set, then no notification
+ * will be shown even if there are pending crash reports.
+ */
+add_task(function* test_suppression() {
+ let future = UnsubmittedCrashHandler.dateString(new Date(Date.now() + (DAY * 5)));
+ UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", future);
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+
+ Assert.ok(UnsubmittedCrashHandler.suppressed,
+ "The UnsubmittedCrashHandler should be suppressed.");
+ UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate");
+
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+});
+
+/**
+ * Tests that if there's a suppression date set, but we've exceeded
+ * it, then we can show the notification again.
+ */
+add_task(function* test_end_suppression() {
+ let yesterday = UnsubmittedCrashHandler.dateString(new Date(Date.now() - DAY));
+ UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", yesterday);
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+
+ Assert.ok(!UnsubmittedCrashHandler.suppressed,
+ "The UnsubmittedCrashHandler should not be suppressed.");
+ Assert.ok(!UnsubmittedCrashHandler.prefs.prefHasUserValue("suppressUntilDate"),
+ "The suppression date should been cleared from preferences.");
+
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+});
diff --git a/browser/modules/test/browser_UsageTelemetry.js b/browser/modules/test/browser_UsageTelemetry.js
new file mode 100644
index 000000000..a84f33a97
--- /dev/null
+++ b/browser/modules/test/browser_UsageTelemetry.js
@@ -0,0 +1,268 @@
+"use strict";
+
+const MAX_CONCURRENT_TABS = "browser.engagement.max_concurrent_tab_count";
+const TAB_EVENT_COUNT = "browser.engagement.tab_open_event_count";
+const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count";
+const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count";
+const TOTAL_URI_COUNT = "browser.engagement.total_uri_count";
+const UNIQUE_DOMAINS_COUNT = "browser.engagement.unique_domains_count";
+const UNFILTERED_URI_COUNT = "browser.engagement.unfiltered_uri_count";
+
+const TELEMETRY_SUBSESSION_TOPIC = "internal-telemetry-after-subsession-split";
+
+/**
+ * Waits for the web progress listener associated with this tab to fire an
+ * onLocationChange for a non-error page.
+ *
+ * @param {xul:browser} browser
+ * A xul:browser.
+ *
+ * @return {Promise}
+ * @resolves When navigating to a non-error page.
+ */
+function browserLocationChanged(browser) {
+ return new Promise(resolve => {
+ let wpl = {
+ onStateChange() {},
+ onSecurityChange() {},
+ onStatusChange() {},
+ onLocationChange(aWebProgress, aRequest, aURI, aFlags) {
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE)) {
+ browser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(wpl);
+ resolve();
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsIWebProgressListener2,
+ ]),
+ };
+ const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ filter.addProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL);
+ browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
+ });
+}
+
+/**
+ * An helper that checks the value of a scalar if it's expected to be > 0,
+ * otherwise makes sure that the scalar it's not reported.
+ */
+let checkScalar = (scalars, scalarName, value, msg) => {
+ if (value > 0) {
+ is(scalars[scalarName], value, msg);
+ return;
+ }
+ ok(!(scalarName in scalars), scalarName + " must not be reported.");
+};
+
+/**
+ * Get a snapshot of the scalars and check them against the provided values.
+ */
+let checkScalars = (countsObject) => {
+ const scalars =
+ Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+
+ // Check the expected values. Scalars that are never set must not be reported.
+ checkScalar(scalars, MAX_CONCURRENT_TABS, countsObject.maxTabs,
+ "The maximum tab count must match the expected value.");
+ checkScalar(scalars, TAB_EVENT_COUNT, countsObject.tabOpenCount,
+ "The number of open tab event count must match the expected value.");
+ checkScalar(scalars, MAX_CONCURRENT_WINDOWS, countsObject.maxWindows,
+ "The maximum window count must match the expected value.");
+ checkScalar(scalars, WINDOW_OPEN_COUNT, countsObject.windowsOpenCount,
+ "The number of window open event count must match the expected value.");
+ checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
+ "The total URI count must match the expected value.");
+ checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
+ "The unique domains count must match the expected value.");
+ checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
+ "The unfiltered URI count must match the expected value.");
+};
+
+add_task(function* test_tabsAndWindows() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+
+ let openedTabs = [];
+ let expectedTabOpenCount = 0;
+ let expectedWinOpenCount = 0;
+ let expectedMaxTabs = 0;
+ let expectedMaxWins = 0;
+
+ // Add a new tab and check that the count is right.
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+ expectedTabOpenCount = 1;
+ expectedMaxTabs = 2;
+ // This, and all the checks below, also check that initial pages (about:newtab, about:blank, ..)
+ // are not counted by the total_uri_count and the unfiltered_uri_count probes.
+ checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+ windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+ totalUnfilteredURIs: 0});
+
+ // Add two new tabs in the same window.
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+ expectedTabOpenCount += 2;
+ expectedMaxTabs += 2;
+ checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+ windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+ totalUnfilteredURIs: 0});
+
+ // Add a new window and then some tabs in it. An empty new windows counts as a tab.
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"));
+ // The new window started with a new tab, so account for it.
+ expectedTabOpenCount += 4;
+ expectedWinOpenCount += 1;
+ expectedMaxWins = 2;
+ expectedMaxTabs += 4;
+
+ // Remove a tab from the first window, the max shouldn't change.
+ yield BrowserTestUtils.removeTab(openedTabs.pop());
+ checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+ windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+ totalUnfilteredURIs: 0});
+
+ // Remove all the extra windows and tabs.
+ for (let tab of openedTabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ yield BrowserTestUtils.closeWindow(win);
+
+ // Make sure all the scalars still have the expected values.
+ checkScalars({maxTabs: expectedMaxTabs, tabOpenCount: expectedTabOpenCount, maxWindows: expectedMaxWins,
+ windowsOpenCount: expectedWinOpenCount, totalURIs: 0, domainCount: 0,
+ totalUnfilteredURIs: 0});
+});
+
+add_task(function* test_subsessionSplit() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+
+ // Add a new window (that will have 4 tabs).
+ let win = yield BrowserTestUtils.openNewBrowserWindow();
+ let openedTabs = [];
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:blank"));
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "about:mozilla"));
+ openedTabs.push(yield BrowserTestUtils.openNewForegroundTab(win.gBrowser, "http://www.example.com"));
+
+ // Check that the scalars have the right values. We expect 2 unfiltered URI loads
+ // (about:mozilla and www.example.com, but no about:blank) and 1 URI totalURIs
+ // (only www.example.com).
+ checkScalars({maxTabs: 5, tabOpenCount: 4, maxWindows: 2, windowsOpenCount: 1,
+ totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 2});
+
+ // Remove a tab.
+ yield BrowserTestUtils.removeTab(openedTabs.pop());
+
+ // Simulate a subsession split by clearing the scalars (via |snapshotScalars|) and
+ // notifying the subsession split topic.
+ Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
+ true /* clearScalars */);
+ Services.obs.notifyObservers(null, TELEMETRY_SUBSESSION_TOPIC, "");
+
+ // After a subsession split, only the MAX_CONCURRENT_* scalars must be available
+ // and have the correct value. No tabs, windows or URIs were opened so other scalars
+ // must not be reported.
+ checkScalars({maxTabs: 4, tabOpenCount: 0, maxWindows: 2, windowsOpenCount: 0,
+ totalURIs: 0, domainCount: 0, totalUnfilteredURIs: 0});
+
+ // Remove all the extra windows and tabs.
+ for (let tab of openedTabs) {
+ yield BrowserTestUtils.removeTab(tab);
+ }
+ yield BrowserTestUtils.closeWindow(win);
+});
+
+add_task(function* test_URIAndDomainCounts() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+
+ let checkCounts = (countsObject) => {
+ // Get a snapshot of the scalars and then clear them.
+ const scalars =
+ Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+ checkScalar(scalars, TOTAL_URI_COUNT, countsObject.totalURIs,
+ "The URI scalar must contain the expected value.");
+ checkScalar(scalars, UNIQUE_DOMAINS_COUNT, countsObject.domainCount,
+ "The unique domains scalar must contain the expected value.");
+ checkScalar(scalars, UNFILTERED_URI_COUNT, countsObject.totalUnfilteredURIs,
+ "The unfiltered URI scalar must contain the expected value.");
+ };
+
+ // Check that about:blank doesn't get counted in the URI total.
+ let firstTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ checkCounts({totalURIs: 0, domainCount: 0, totalUnfilteredURIs: 0});
+
+ // Open a different page and check the counts.
+ yield BrowserTestUtils.loadURI(firstTab.linkedBrowser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(firstTab.linkedBrowser);
+ checkCounts({totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 1});
+
+ // Activating a different tab must not increase the URI count.
+ let secondTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+ yield BrowserTestUtils.switchTab(gBrowser, firstTab);
+ checkCounts({totalURIs: 1, domainCount: 1, totalUnfilteredURIs: 1});
+ yield BrowserTestUtils.removeTab(secondTab);
+
+ // Open a new window and set the tab to a new address.
+ let newWin = yield BrowserTestUtils.openNewBrowserWindow();
+ yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
+ checkCounts({totalURIs: 2, domainCount: 1, totalUnfilteredURIs: 2});
+
+ // We should not count AJAX requests.
+ const XHR_URL = "http://example.com/r";
+ yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, XHR_URL, function(url) {
+ return new Promise(resolve => {
+ var xhr = new content.window.XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.onload = () => resolve();
+ xhr.send();
+ });
+ });
+ checkCounts({totalURIs: 2, domainCount: 1, totalUnfilteredURIs: 2});
+
+ // Check that we're counting page fragments.
+ let loadingStopped = browserLocationChanged(newWin.gBrowser.selectedBrowser);
+ yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://example.com/#2");
+ yield loadingStopped;
+ checkCounts({totalURIs: 3, domainCount: 1, totalUnfilteredURIs: 3});
+
+ // Check that a different URI from the example.com domain doesn't increment the unique count.
+ yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "http://test1.example.com/");
+ yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
+ checkCounts({totalURIs: 4, domainCount: 1, totalUnfilteredURIs: 4});
+
+ // Make sure that the unique domains counter is incrementing for a different domain.
+ yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, "https://example.org/");
+ yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
+ checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 5});
+
+ // Check that we only account for top level loads (e.g. we don't count URIs from
+ // embedded iframes).
+ yield ContentTask.spawn(newWin.gBrowser.selectedBrowser, null, function* () {
+ let doc = content.document;
+ let iframe = doc.createElement("iframe");
+ let promiseIframeLoaded = ContentTaskUtils.waitForEvent(iframe, "load", false);
+ iframe.src = "https://example.org/test";
+ doc.body.insertBefore(iframe, doc.body.firstChild);
+ yield promiseIframeLoaded;
+ });
+ checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 5});
+
+ // Check that uncommon protocols get counted in the unfiltered URI probe.
+ const TEST_PAGE =
+ "data:text/html,<a id='target' href='%23par1'>Click me</a><a name='par1'>The paragraph.</a>";
+ yield BrowserTestUtils.loadURI(newWin.gBrowser.selectedBrowser, TEST_PAGE);
+ yield BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
+ checkCounts({totalURIs: 5, domainCount: 2, totalUnfilteredURIs: 6});
+
+ // Clean up.
+ yield BrowserTestUtils.removeTab(firstTab);
+ yield BrowserTestUtils.closeWindow(newWin);
+});
diff --git a/browser/modules/test/browser_UsageTelemetry_content.js b/browser/modules/test/browser_UsageTelemetry_content.js
new file mode 100644
index 000000000..35c6b5a6d
--- /dev/null
+++ b/browser/modules/test/browser_UsageTelemetry_content.js
@@ -0,0 +1,121 @@
+"use strict";
+
+const BASE_PROBE_NAME = "browser.engagement.navigation.";
+const SCALAR_CONTEXT_MENU = BASE_PROBE_NAME + "contextmenu";
+const SCALAR_ABOUT_NEWTAB = BASE_PROBE_NAME + "about_newtab";
+
+add_task(function* setup() {
+ // Create two new search engines. Mark one as the default engine, so
+ // the test don't crash. We need to engines for this test as the searchbar
+ // in content doesn't display the default search engine among the one-off engines.
+ Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ // Make the first engine the default search engine.
+ let engineDefault = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engineDefault;
+
+ // Move the second engine at the beginning of the one-off list.
+ let engineOneOff = Services.search.getEngineByName("MozSearch2");
+ Services.search.moveEngine(engineOneOff, 0);
+
+ yield SpecialPowers.pushPrefEnv({"set": [
+ ["dom.select_events.enabled", true], // We want select events to be fired.
+ ["toolkit.telemetry.enabled", true] // And Extended Telemetry to be enabled.
+ ]});
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(function* () {
+ Services.search.currentEngine = originalEngine;
+ Services.search.removeEngine(engineDefault);
+ Services.search.removeEngine(engineOneOff);
+ });
+});
+
+add_task(function* test_context_menu() {
+ // Let's reset the Telemetry data.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ // Open a new tab with a page containing some text.
+ let tab =
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/plain;charset=utf8,test%20search");
+
+ info("Select all the text in the page.");
+ yield ContentTask.spawn(tab.linkedBrowser, "", function*() {
+ return new Promise(resolve => {
+ content.document.addEventListener("selectionchange", () => resolve(), { once: true });
+ content.document.getSelection().selectAllChildren(content.document.body);
+ });
+ });
+
+ info("Open the context menu.");
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ BrowserTestUtils.synthesizeMouseAtCenter("body", { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser);
+ yield popupPromise;
+
+ info("Click on search.");
+ let searchItem = contextMenu.getElementsByAttribute("id", "context-searchselect")[0];
+ searchItem.click();
+
+ info("Validate the search metrics.");
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_CONTEXT_MENU, "search", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_CONTEXT_MENU]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch.contextmenu', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "contextmenu", null, {engine: "other-MozSearch"}]]);
+
+ contextMenu.hidePopup();
+ yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_about_newtab() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:newtab", false);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
+ yield ContentTaskUtils.waitForCondition(() => !content.document.hidden);
+ });
+
+ info("Trigger a simple serch, just text + enter.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield typeInSearchField(tab.linkedBrowser, "test query", "newtab-search-text");
+ yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_ABOUT_NEWTAB, "search_enter", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_ABOUT_NEWTAB]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch.newtab', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "about_newtab", "enter", {engine: "other-MozSearch"}]]);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js b/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
new file mode 100644
index 000000000..1818ae5fd
--- /dev/null
+++ b/browser/modules/test/browser_UsageTelemetry_content_aboutHome.js
@@ -0,0 +1,84 @@
+"use strict";
+
+const SCALAR_ABOUT_HOME = "browser.engagement.navigation.about_home";
+
+add_task(function* setup() {
+ // about:home uses IndexedDB. However, the test finishes too quickly and doesn't
+ // allow it enougth time to save. So it throws. This disables all the uncaught
+ // exception in this file and that's the reason why we split about:home tests
+ // out of the other UsageTelemetry files.
+ ignoreAllUncaughtExceptions();
+
+ // Create two new search engines. Mark one as the default engine, so
+ // the test don't crash. We need to engines for this test as the searchbar
+ // in content doesn't display the default search engine among the one-off engines.
+ Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ // Make the first engine the default search engine.
+ let engineDefault = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engineDefault;
+
+ // Move the second engine at the beginning of the one-off list.
+ let engineOneOff = Services.search.getEngineByName("MozSearch2");
+ Services.search.moveEngine(engineOneOff, 0);
+
+ // Enable Extended Telemetry.
+ yield SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(function* () {
+ Services.search.currentEngine = originalEngine;
+ Services.search.removeEngine(engineDefault);
+ Services.search.removeEngine(engineOneOff);
+ });
+});
+
+add_task(function* test_abouthome_simpleQuery() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Setup waiting for AboutHomeLoadSnippetsCompleted.");
+ let promiseAboutHomeLoaded = new Promise(resolve => {
+ tab.linkedBrowser.addEventListener("AboutHomeLoadSnippetsCompleted", function loadListener(event) {
+ tab.linkedBrowser.removeEventListener("AboutHomeLoadSnippetsCompleted", loadListener, true);
+ resolve();
+ }, true, true);
+ });
+
+ info("Load about:home.");
+ tab.linkedBrowser.loadURI("about:home");
+ info("Wait for AboutHomeLoadSnippetsCompleted.");
+ yield promiseAboutHomeLoaded;
+
+ info("Trigger a simple serch, just test + enter.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield typeInSearchField(tab.linkedBrowser, "test query", "searchText");
+ yield BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_ABOUT_HOME, "search_enter", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_ABOUT_HOME]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch.abouthome', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "about_home", "enter", {engine: "other-MozSearch"}]]);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/modules/test/browser_UsageTelemetry_private_and_restore.js b/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
new file mode 100644
index 000000000..144a4a03f
--- /dev/null
+++ b/browser/modules/test/browser_UsageTelemetry_private_and_restore.js
@@ -0,0 +1,90 @@
+"use strict";
+
+const MAX_CONCURRENT_TABS = "browser.engagement.max_concurrent_tab_count";
+const TAB_EVENT_COUNT = "browser.engagement.tab_open_event_count";
+const MAX_CONCURRENT_WINDOWS = "browser.engagement.max_concurrent_window_count";
+const WINDOW_OPEN_COUNT = "browser.engagement.window_open_event_count";
+const TOTAL_URI_COUNT = "browser.engagement.total_uri_count";
+const UNFILTERED_URI_COUNT = "browser.engagement.unfiltered_uri_count";
+const UNIQUE_DOMAINS_COUNT = "browser.engagement.unique_domains_count";
+
+function promiseBrowserStateRestored() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ Services.obs.removeObserver(observer, "sessionstore-browser-state-restored");
+ resolve();
+ }, "sessionstore-browser-state-restored", false);
+ });
+}
+
+add_task(function* test_privateMode() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+
+ // Open a private window and load a website in it.
+ let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
+ yield BrowserTestUtils.loadURI(privateWin.gBrowser.selectedBrowser, "http://example.com/");
+ yield BrowserTestUtils.browserLoaded(privateWin.gBrowser.selectedBrowser);
+
+ // Check that tab and window count is recorded.
+ const scalars =
+ Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+
+ ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs in private mode.");
+ ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs in private mode.");
+ ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains in private mode.");
+ is(scalars[TAB_EVENT_COUNT], 1, "The number of open tab event count must match the expected value.");
+ is(scalars[MAX_CONCURRENT_TABS], 2, "The maximum tab count must match the expected value.");
+ is(scalars[WINDOW_OPEN_COUNT], 1, "The number of window open event count must match the expected value.");
+ is(scalars[MAX_CONCURRENT_WINDOWS], 2, "The maximum window count must match the expected value.");
+
+ // Clean up.
+ yield BrowserTestUtils.closeWindow(privateWin);
+});
+
+add_task(function* test_sessionRestore() {
+ const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
+ Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
+ });
+
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+
+ // The first window will be put into the already open window and the second
+ // window will be opened with _openWindowWithState, which is the source of the problem.
+ const state = {
+ windows: [
+ {
+ tabs: [
+ { entries: [{ url: "http://example.org" }], extData: { "uniq": 3785 } }
+ ],
+ selected: 1
+ }
+ ]
+ };
+
+ // Save the current session.
+ let SessionStore =
+ Cu.import("resource:///modules/sessionstore/SessionStore.jsm", {}).SessionStore;
+
+ // Load the custom state and wait for SSTabRestored, as we want to make sure
+ // that the URI counting code was hit.
+ let tabRestored = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "SSTabRestored");
+ SessionStore.setBrowserState(JSON.stringify(state));
+ yield tabRestored;
+
+ // Check that the URI is not recorded.
+ const scalars =
+ Services.telemetry.snapshotScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN);
+
+ ok(!(TOTAL_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
+ ok(!(UNFILTERED_URI_COUNT in scalars), "We should not track URIs from restored sessions.");
+ ok(!(UNIQUE_DOMAINS_COUNT in scalars), "We should not track unique domains from restored sessions.");
+
+ // Restore the original session and cleanup.
+ let sessionRestored = promiseBrowserStateRestored();
+ SessionStore.setBrowserState(JSON.stringify(state));
+ yield sessionRestored;
+});
diff --git a/browser/modules/test/browser_UsageTelemetry_searchbar.js b/browser/modules/test/browser_UsageTelemetry_searchbar.js
new file mode 100644
index 000000000..8aa3ceaee
--- /dev/null
+++ b/browser/modules/test/browser_UsageTelemetry_searchbar.js
@@ -0,0 +1,195 @@
+"use strict";
+
+const SCALAR_SEARCHBAR = "browser.engagement.navigation.searchbar";
+
+let searchInSearchbar = Task.async(function* (inputText) {
+ let win = window;
+ yield new Promise(r => waitForFocus(r, win));
+ let sb = BrowserSearch.searchBar;
+ // Write the search query in the searchbar.
+ sb.focus();
+ sb.value = inputText;
+ sb.textbox.controller.startSearch(inputText);
+ // Wait for the popup to show.
+ yield BrowserTestUtils.waitForEvent(sb.textbox.popup, "popupshown");
+ // And then for the search to complete.
+ yield BrowserTestUtils.waitForCondition(() => sb.textbox.controller.searchStatus >=
+ Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH,
+ "The search in the searchbar must complete.");
+});
+
+/**
+ * Click one of the entries in the search suggestion popup.
+ *
+ * @param {String} entryName
+ * The name of the elemet to click on.
+ */
+function clickSearchbarSuggestion(entryName) {
+ let popup = BrowserSearch.searchBar.textbox.popup;
+ let column = popup.tree.columns[0];
+
+ for (let rowID = 0; rowID < popup.tree.view.rowCount; rowID++) {
+ const suggestion = popup.tree.view.getValueAt(rowID, column);
+ if (suggestion !== entryName) {
+ continue;
+ }
+
+ // Make sure the suggestion is visible, just in case.
+ let tbo = popup.tree.treeBoxObject;
+ tbo.ensureRowIsVisible(rowID);
+ // Calculate the click coordinates.
+ let rect = tbo.getCoordsForCellItem(rowID, column, "text");
+ let x = rect.x + rect.width / 2;
+ let y = rect.y + rect.height / 2;
+ // Simulate the click.
+ EventUtils.synthesizeMouse(popup.tree.body, x, y, {},
+ popup.tree.ownerGlobal);
+ break;
+ }
+}
+
+add_task(function* setup() {
+ // Create two new search engines. Mark one as the default engine, so
+ // the test don't crash. We need to engines for this test as the searchbar
+ // doesn't display the default search engine among the one-off engines.
+ Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ Services.search.addEngineWithDetails("MozSearch2", "", "mozalias2", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ // Make the first engine the default search engine.
+ let engineDefault = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engineDefault;
+
+ // Move the second engine at the beginning of the one-off list.
+ let engineOneOff = Services.search.getEngineByName("MozSearch2");
+ Services.search.moveEngine(engineOneOff, 0);
+
+ // Enable Extended Telemetry.
+ yield SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(function* () {
+ Services.search.currentEngine = originalEngine;
+ Services.search.removeEngine(engineDefault);
+ Services.search.removeEngine(engineOneOff);
+ });
+});
+
+add_task(function* test_plainQuery() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ info("Simulate entering a simple search.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield searchInSearchbar("simple query");
+ EventUtils.sendKey("return");
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_enter", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch.searchbar', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "searchbar", "enter", {engine: "other-MozSearch"}]]);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_oneOff() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ info("Perform a one-off search using the first engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield searchInSearchbar("query");
+
+ info("Pressing Alt+Down to highlight the first one off engine.");
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
+ EventUtils.sendKey("return");
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_oneoff", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch2.searchbar', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "searchbar", "oneoff", {engine: "other-MozSearch2"}]]);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_suggestion() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ // Create an engine to generate search suggestions and add it as default
+ // for this test.
+ const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+ let suggestionEngine = yield new Promise((resolve, reject) => {
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess(engine) { resolve(engine) },
+ onError() { reject() }
+ });
+ });
+
+ let previousEngine = Services.search.currentEngine;
+ Services.search.currentEngine = suggestionEngine;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ info("Perform a one-off search using the first engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield searchInSearchbar("query");
+ info("Clicking the searchbar suggestion.");
+ clickSearchbarSuggestion("queryfoo");
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_SEARCHBAR, "search_suggestion", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_SEARCHBAR]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ let searchEngineId = 'other-' + suggestionEngine.name;
+ checkKeyedHistogram(search_hist, searchEngineId + '.searchbar', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "searchbar", "suggestion", {engine: searchEngineId}]]);
+
+ Services.search.currentEngine = previousEngine;
+ Services.search.removeEngine(suggestionEngine);
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/modules/test/browser_UsageTelemetry_urlbar.js b/browser/modules/test/browser_UsageTelemetry_urlbar.js
new file mode 100644
index 000000000..81d3e28ba
--- /dev/null
+++ b/browser/modules/test/browser_UsageTelemetry_urlbar.js
@@ -0,0 +1,220 @@
+"use strict";
+
+const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
+
+// The preference to enable suggestions in the urlbar.
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+// The name of the search engine used to generate suggestions.
+const SUGGESTION_ENGINE_NAME = "browser_UsageTelemetry usageTelemetrySearchSuggestions.xml";
+const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
+
+let searchInAwesomebar = Task.async(function* (inputText, win=window) {
+ yield new Promise(r => waitForFocus(r, win));
+ // Write the search query in the urlbar.
+ win.gURLBar.focus();
+ win.gURLBar.value = inputText;
+ win.gURLBar.controller.startSearch(inputText);
+ // Wait for the popup to show.
+ yield BrowserTestUtils.waitForEvent(win.gURLBar.popup, "popupshown");
+ // And then for the search to complete.
+ yield BrowserTestUtils.waitForCondition(() => win.gURLBar.controller.searchStatus >=
+ Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH);
+});
+
+/**
+ * Click one of the entries in the urlbar suggestion popup.
+ *
+ * @param {String} entryName
+ * The name of the elemet to click on.
+ */
+function clickURLBarSuggestion(entryName) {
+ // The entry in the suggestion list should follow the format:
+ // "<search term> <engine name> Search"
+ const expectedSuggestionName = entryName + " " + SUGGESTION_ENGINE_NAME + " Search";
+ for (let child of gURLBar.popup.richlistbox.children) {
+ if (child.label === expectedSuggestionName) {
+ // This entry is the search suggestion we're looking for.
+ child.click();
+ return;
+ }
+ }
+}
+
+add_task(function* setup() {
+ // Create a new search engine.
+ Services.search.addEngineWithDetails("MozSearch", "", "mozalias", "", "GET",
+ "http://example.com/?q={searchTerms}");
+
+ // Make it the default search engine.
+ let engine = Services.search.getEngineByName("MozSearch");
+ let originalEngine = Services.search.currentEngine;
+ Services.search.currentEngine = engine;
+
+ // And the first one-off engine.
+ Services.search.moveEngine(engine, 0);
+
+ // Enable search suggestions in the urlbar.
+ Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+
+ // Enable the urlbar one-off buttons.
+ Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
+
+ // Enable Extended Telemetry.
+ yield SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
+
+ // Make sure to restore the engine once we're done.
+ registerCleanupFunction(function* () {
+ Services.search.currentEngine = originalEngine;
+ Services.search.removeEngine(engine);
+ Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF, true);
+ Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
+ });
+});
+
+add_task(function* test_simpleQuery() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ info("Simulate entering a simple search.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield searchInAwesomebar("simple query");
+ EventUtils.sendKey("return");
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_URLBAR, "search_enter", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "urlbar", "enter", {engine: "other-MozSearch"}]]);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_searchAlias() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ info("Search using a search alias.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield searchInAwesomebar("mozalias query");
+ EventUtils.sendKey("return");
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_URLBAR, "search_alias", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "urlbar", "alias", {engine: "other-MozSearch"}]]);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_oneOff() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ info("Perform a one-off search using the first engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield searchInAwesomebar("query");
+
+ info("Pressing Alt+Down to take us to the first one-off engine.");
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true });
+ EventUtils.sendKey("return");
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_URLBAR, "search_oneoff", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ checkKeyedHistogram(search_hist, 'other-MozSearch.urlbar', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "urlbar", "oneoff", {engine: "other-MozSearch"}]]);
+
+ yield BrowserTestUtils.removeTab(tab);
+});
+
+add_task(function* test_suggestion() {
+ // Let's reset the counts.
+ Services.telemetry.clearScalars();
+ Services.telemetry.clearEvents();
+ let search_hist = getSearchCountsHistogram();
+
+ // Create an engine to generate search suggestions and add it as default
+ // for this test.
+ const url = getRootDirectory(gTestPath) + "usageTelemetrySearchSuggestions.xml";
+ let suggestionEngine = yield new Promise((resolve, reject) => {
+ Services.search.addEngine(url, null, "", false, {
+ onSuccess(engine) { resolve(engine) },
+ onError() { reject() }
+ });
+ });
+
+ let previousEngine = Services.search.currentEngine;
+ Services.search.currentEngine = suggestionEngine;
+
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
+
+ info("Perform a one-off search using the first engine.");
+ let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ yield searchInAwesomebar("query");
+ info("Clicking the urlbar suggestion.");
+ clickURLBarSuggestion("queryfoo");
+ yield p;
+
+ // Check if the scalars contain the expected values.
+ const scalars =
+ Services.telemetry.snapshotKeyedScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ checkKeyedScalar(scalars, SCALAR_URLBAR, "search_suggestion", 1);
+ Assert.equal(Object.keys(scalars[SCALAR_URLBAR]).length, 1,
+ "This search must only increment one entry in the scalar.");
+
+ // Make sure SEARCH_COUNTS contains identical values.
+ let searchEngineId = 'other-' + suggestionEngine.name;
+ checkKeyedHistogram(search_hist, searchEngineId + '.urlbar', 1);
+
+ // Also check events.
+ let events = Services.telemetry.snapshotBuiltinEvents(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, false);
+ events = events.filter(e => e[1] == "navigation" && e[2] == "search");
+ checkEvents(events, [["navigation", "search", "urlbar", "suggestion", {engine: searchEngineId}]]);
+
+ Services.search.currentEngine = previousEngine;
+ Services.search.removeEngine(suggestionEngine);
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/modules/test/browser_taskbar_preview.js b/browser/modules/test/browser_taskbar_preview.js
new file mode 100644
index 000000000..89295b9e0
--- /dev/null
+++ b/browser/modules/test/browser_taskbar_preview.js
@@ -0,0 +1,100 @@
+function test() {
+ var isWin7OrHigher = false;
+ try {
+ let version = Cc["@mozilla.org/system-info;1"]
+ .getService(Ci.nsIPropertyBag2)
+ .getProperty("version");
+ isWin7OrHigher = (parseFloat(version) >= 6.1);
+ } catch (ex) { }
+
+ is(!!Win7Features, isWin7OrHigher, "Win7Features available when it should be");
+ if (!isWin7OrHigher)
+ return;
+
+ const ENABLE_PREF_NAME = "browser.taskbar.previews.enable";
+
+ let temp = {};
+ Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", temp);
+ let AeroPeek = temp.AeroPeek;
+
+ waitForExplicitFinish();
+
+ gPrefService.setBoolPref(ENABLE_PREF_NAME, true);
+
+ is(1, AeroPeek.windows.length, "Got the expected number of windows");
+
+ checkPreviews(1, "Browser starts with one preview");
+
+ gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.addTab();
+
+ checkPreviews(4, "Correct number of previews after adding");
+
+ for (let preview of AeroPeek.previews)
+ ok(preview.visible, "Preview is shown as expected");
+
+ gPrefService.setBoolPref(ENABLE_PREF_NAME, false);
+ is(0, AeroPeek.previews.length, "Should have 0 previews when disabled");
+
+ gPrefService.setBoolPref(ENABLE_PREF_NAME, true);
+ checkPreviews(4, "Previews are back when re-enabling");
+ for (let preview of AeroPeek.previews)
+ ok(preview.visible, "Preview is shown as expected after re-enabling");
+
+ [1, 2, 3, 4].forEach(function (idx) {
+ gBrowser.selectedTab = gBrowser.tabs[idx];
+ ok(checkSelectedTab(), "Current tab is correctly selected");
+ });
+
+ // Close #4
+ getPreviewForTab(gBrowser.selectedTab).controller.onClose();
+ checkPreviews(3, "Expected number of previews after closing selected tab via controller");
+ ok(gBrowser.tabs.length == 3, "Successfully closed a tab");
+
+ // Select #1
+ ok(getPreviewForTab(gBrowser.tabs[0]).controller.onActivate(), "Activation was accepted");
+ ok(gBrowser.tabs[0].selected, "Correct tab was selected");
+ checkSelectedTab();
+
+ // Remove #3 (non active)
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ checkPreviews(2, "Expected number of previews after closing unselected via browser");
+
+ // Remove #1 (active)
+ gBrowser.removeTab(gBrowser.tabContainer.firstChild);
+ checkPreviews(1, "Expected number of previews after closing selected tab via browser");
+
+ // Add a new tab
+ gBrowser.addTab();
+ checkPreviews(2);
+ // Check default selection
+ checkSelectedTab();
+
+ // Change selection
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ checkSelectedTab();
+ // Close nonselected tab via controller
+ getPreviewForTab(gBrowser.tabs[1]).controller.onClose();
+ checkPreviews(1);
+
+ if (gPrefService.prefHasUserValue(ENABLE_PREF_NAME))
+ gPrefService.setBoolPref(ENABLE_PREF_NAME, !gPrefService.getBoolPref(ENABLE_PREF_NAME));
+
+ finish();
+
+ function checkPreviews(aPreviews, msg) {
+ let nPreviews = AeroPeek.previews.length;
+ is(aPreviews, gBrowser.tabs.length, "Browser has expected number of tabs - " + msg);
+ is(nPreviews, gBrowser.tabs.length, "Browser has one preview per tab - " + msg);
+ is(nPreviews, aPreviews, msg || "Got expected number of previews");
+ }
+
+ function getPreviewForTab(tab) {
+ return window.gTaskbarTabGroup.previewFromTab(tab);
+ }
+
+ function checkSelectedTab() {
+ return getPreviewForTab(gBrowser.selectedTab).active;
+ }
+}
diff --git a/browser/modules/test/browser_urlBar_zoom.js b/browser/modules/test/browser_urlBar_zoom.js
new file mode 100644
index 000000000..9cb5c96c6
--- /dev/null
+++ b/browser/modules/test/browser_urlBar_zoom.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";
+
+var initialPageZoom = ZoomManager.zoom;
+const kTimeoutInMS = 20000;
+
+add_task(function* () {
+ info("Confirm whether the browser zoom is set to the default level");
+ is(initialPageZoom, 1, "Page zoom is set to default (100%)");
+ let zoomResetButton = document.getElementById("urlbar-zoom-button");
+ is(zoomResetButton.hidden, true, "Zoom reset button is currently hidden");
+
+ info("Change zoom and confirm zoom button appears");
+ let labelUpdatePromise = BrowserTestUtils.waitForAttribute("label", zoomResetButton);
+ FullZoom.enlarge();
+ yield labelUpdatePromise;
+ info("Zoom increased to " + Math.floor(ZoomManager.zoom * 100) + "%");
+ is(zoomResetButton.hidden, false, "Zoom reset button is now visible");
+ let pageZoomLevel = Math.floor(ZoomManager.zoom * 100);
+ let expectedZoomLevel = 110;
+ let buttonZoomLevel = parseInt(zoomResetButton.getAttribute("label"), 10);
+ is(buttonZoomLevel, expectedZoomLevel, ("Button label updated successfully to " + Math.floor(ZoomManager.zoom * 100) + "%"));
+
+ let zoomResetPromise = promiseObserverNotification("browser-fullZoom:zoomReset");
+ zoomResetButton.click();
+ yield zoomResetPromise;
+ pageZoomLevel = Math.floor(ZoomManager.zoom * 100);
+ expectedZoomLevel = 100;
+ is(pageZoomLevel, expectedZoomLevel, "Clicking zoom button successfully resets browser zoom to 100%");
+ is(zoomResetButton.hidden, true, "Zoom reset button returns to being hidden");
+
+});
+
+add_task(function* () {
+ info("Confirm that URL bar zoom button doesn't appear when customizable zoom widget is added to toolbar");
+ CustomizableUI.addWidgetToArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
+ let zoomCustomizableWidget = document.getElementById("zoom-reset-button");
+ let zoomResetButton = document.getElementById("urlbar-zoom-button");
+ let zoomChangePromise = promiseObserverNotification("browser-fullZoom:zoomChange");
+ FullZoom.enlarge();
+ yield zoomChangePromise;
+ is(zoomResetButton.hidden, true, "URL zoom button remains hidden despite zoom increase");
+ is(parseInt(zoomCustomizableWidget.label, 10), 110, "Customizable zoom widget's label has updated to " + zoomCustomizableWidget.label);
+});
+
+add_task(function* asyncCleanup() {
+ // reset zoom level and customizable widget
+ ZoomManager.zoom = initialPageZoom;
+ is(ZoomManager.zoom, 1, "Zoom level was restored");
+ if (document.getElementById("zoom-controls")) {
+ CustomizableUI.removeWidgetFromArea("zoom-controls", CustomizableUI.AREA_NAVBAR);
+ ok(!document.getElementById("zoom-controls"), "Customizable zoom widget removed from toolbar");
+ }
+
+});
+
+function promiseObserverNotification(aObserver) {
+ let deferred = Promise.defer();
+ function notificationCallback(e) {
+ Services.obs.removeObserver(notificationCallback, aObserver, false);
+ clearTimeout(timeoutId);
+ deferred.resolve();
+ }
+ let timeoutId = setTimeout(() => {
+ Services.obs.removeObserver(notificationCallback, aObserver, false);
+ deferred.reject("Notification '" + aObserver + "' did not happen within 20 seconds.");
+ }, kTimeoutInMS);
+ Services.obs.addObserver(notificationCallback, aObserver, false);
+ return deferred.promise;
+}
diff --git a/browser/modules/test/contentSearch.js b/browser/modules/test/contentSearch.js
new file mode 100644
index 000000000..b5dddfe45
--- /dev/null
+++ b/browser/modules/test/contentSearch.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 TEST_MSG = "ContentSearchTest";
+const SERVICE_EVENT_TYPE = "ContentSearchService";
+const CLIENT_EVENT_TYPE = "ContentSearchClient";
+
+// Forward events from the in-content service to the test.
+content.addEventListener(SERVICE_EVENT_TYPE, event => {
+ // The event dispatch code in content.js clones the event detail into the
+ // content scope. That's generally the right thing, but causes us to end
+ // up with an XrayWrapper to it here, which will screw us up when trying to
+ // serialize the object in sendAsyncMessage. Waive Xrays for the benefit of
+ // the test machinery.
+ sendAsyncMessage(TEST_MSG, Components.utils.waiveXrays(event.detail));
+});
+
+// Forward messages from the test to the in-content service.
+addMessageListener(TEST_MSG, msg => {
+ // If the message is a search, stop the page from loading and then tell the
+ // test that it loaded.
+ if (msg.data.type == "Search") {
+ waitForLoadAndStopIt(msg.data.expectedURL, url => {
+ sendAsyncMessage(TEST_MSG, {
+ type: "loadStopped",
+ url: url,
+ });
+ });
+ }
+
+ content.dispatchEvent(
+ new content.CustomEvent(CLIENT_EVENT_TYPE, {
+ detail: msg.data,
+ })
+ );
+});
+
+function waitForLoadAndStopIt(expectedURL, callback) {
+ let Ci = Components.interfaces;
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ let listener = {
+ onStateChange: function (webProg, req, flags, status) {
+ if (req instanceof Ci.nsIChannel) {
+ let url = req.originalURI.spec;
+ dump("waitForLoadAndStopIt: onStateChange " + url + "\n");
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if ((flags & docStart) && webProg.isTopLevel && url == expectedURL) {
+ webProgress.removeProgressListener(listener);
+ req.cancel(Components.results.NS_ERROR_FAILURE);
+ callback(url);
+ }
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ ]),
+ };
+ webProgress.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
+ dump("waitForLoadAndStopIt: Waiting for URL to load: " + expectedURL + "\n");
+}
diff --git a/browser/modules/test/contentSearchBadImage.xml b/browser/modules/test/contentSearchBadImage.xml
new file mode 100644
index 000000000..6e4cb60a5
--- /dev/null
+++ b/browser/modules/test/contentSearchBadImage.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_ContentSearch contentSearchBadImage.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-ContentSearch.com/contentSearchBadImage" rel="searchform"/>
+<Image width="16" height="16">data:image/x-icon;base64,notbase64</Image>
+</SearchPlugin>
diff --git a/browser/modules/test/contentSearchSuggestions.sjs b/browser/modules/test/contentSearchSuggestions.sjs
new file mode 100644
index 000000000..1978b4f66
--- /dev/null
+++ b/browser/modules/test/contentSearchSuggestions.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/modules/test/contentSearchSuggestions.xml b/browser/modules/test/contentSearchSuggestions.xml
new file mode 100644
index 000000000..81c23379c
--- /dev/null
+++ b/browser/modules/test/contentSearchSuggestions.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_ContentSearch contentSearchSuggestions.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/modules/test/contentSearchSuggestions.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://browser-ContentSearch.com/contentSearchSuggestions" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/modules/test/head.js b/browser/modules/test/head.js
new file mode 100644
index 000000000..be0215156
--- /dev/null
+++ b/browser/modules/test/head.js
@@ -0,0 +1,113 @@
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const SINGLE_TRY_TIMEOUT = 100;
+const NUMBER_OF_TRIES = 30;
+
+function waitForConditionPromise(condition, timeoutMsg, tryCount=NUMBER_OF_TRIES) {
+ let defer = Promise.defer();
+ let tries = 0;
+ function checkCondition() {
+ if (tries >= tryCount) {
+ defer.reject(timeoutMsg);
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ return defer.reject(e);
+ }
+ if (conditionPassed) {
+ return defer.resolve();
+ }
+ tries++;
+ setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+ return undefined;
+ }
+ setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+ return defer.promise;
+}
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ waitForConditionPromise(condition, errorMsg).then(nextTest, (reason) => {
+ ok(false, reason + (reason.stack ? "\n" + reason.stack : ""));
+ });
+}
+
+/**
+ * Checks if the snapshotted keyed scalars contain the expected
+ * data.
+ *
+ * @param {Object} scalars
+ * The snapshot of the keyed scalars.
+ * @param {String} scalarName
+ * The name of the keyed scalar to check.
+ * @param {String} key
+ * The key that must be within the keyed scalar.
+ * @param {String|Boolean|Number} expectedValue
+ * The expected value for the provided key in the scalar.
+ */
+function checkKeyedScalar(scalars, scalarName, key, expectedValue) {
+ Assert.ok(scalarName in scalars,
+ scalarName + " must be recorded.");
+ Assert.ok(key in scalars[scalarName],
+ scalarName + " must contain the '" + key + "' key.");
+ Assert.ok(scalars[scalarName][key], expectedValue,
+ scalarName + "['" + key + "'] must contain the expected value");
+}
+
+/**
+ * An utility function to write some text in the search input box
+ * in a content page.
+ * @param {Object} browser
+ * The browser that contains the content.
+ * @param {String} text
+ * The string to write in the search field.
+ * @param {String} fieldName
+ * The name of the field to write to.
+ */
+let typeInSearchField = Task.async(function* (browser, text, fieldName) {
+ yield ContentTask.spawn(browser, { fieldName, text }, function* ({fieldName, text}) {
+ // Avoid intermittent failures.
+ if (fieldName === "searchText") {
+ content.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
+ }
+ // Put the focus on the search box.
+ let searchInput = content.document.getElementById(fieldName);
+ searchInput.focus();
+ searchInput.value = text;
+ });
+});
+
+/**
+ * Clear and get the SEARCH_COUNTS histogram.
+ */
+function getSearchCountsHistogram() {
+ let search_hist = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
+ search_hist.clear();
+ return search_hist;
+}
+
+/**
+ * Check that the keyed histogram contains the right value.
+ */
+function checkKeyedHistogram(h, key, expectedValue) {
+ const snapshot = h.snapshot();
+ Assert.ok(key in snapshot, `The histogram must contain ${key}.`);
+ Assert.equal(snapshot[key].sum, expectedValue, `The key ${key} must contain ${expectedValue}.`);
+}
+
+function checkEvents(events, expectedEvents) {
+ if (!Services.telemetry.canRecordExtended) {
+ // Currently we only collect the tested events when extended Telemetry is enabled.
+ return;
+ }
+
+ Assert.equal(events.length, expectedEvents.length, "Should have matching amount of events.");
+
+ // Strip timestamps from the events for easier comparison.
+ events = events.map(e => e.slice(1));
+
+ for (let i = 0; i < events.length; ++i) {
+ Assert.deepEqual(events[i], expectedEvents[i], "Events should match.");
+ }
+}
diff --git a/browser/modules/test/unit/social/.eslintrc.js b/browser/modules/test/unit/social/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/browser/modules/test/unit/social/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/modules/test/unit/social/blocklist.xml b/browser/modules/test/unit/social/blocklist.xml
new file mode 100644
index 000000000..c8d72d624
--- /dev/null
+++ b/browser/modules/test/unit/social/blocklist.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+ <emItems>
+ <emItem blockID="s1" id="bad.com@services.mozilla.org"></emItem>
+ </emItems>
+</blocklist>
diff --git a/browser/modules/test/unit/social/head.js b/browser/modules/test/unit/social/head.js
new file mode 100644
index 000000000..0beabb685
--- /dev/null
+++ b/browser/modules/test/unit/social/head.js
@@ -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/. */
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var Social, SocialService;
+
+var manifests = [
+ {
+ name: "provider 1",
+ origin: "https://example1.com",
+ sidebarURL: "https://example1.com/sidebar/",
+ },
+ {
+ name: "provider 2",
+ origin: "https://example2.com",
+ sidebarURL: "https://example1.com/sidebar/",
+ }
+];
+
+const MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
+
+// SocialProvider class relies on blocklisting being enabled. To enable
+// blocklisting, we have to setup an app and initialize the blocklist (see
+// initApp below).
+const gProfD = do_get_profile();
+
+function createAppInfo(ID, name, version, platformVersion="1.0") {
+ let tmp = {};
+ Cu.import("resource://testing-common/AppInfo.jsm", tmp);
+ tmp.updateAppInfo({
+ ID, name, version, platformVersion,
+ crashReporter: true,
+ });
+ gAppInfo = tmp.getAppInfo();
+}
+
+function initApp() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+ // prepare a blocklist file for the blocklist service
+ var blocklistFile = gProfD.clone();
+ blocklistFile.append("blocklist.xml");
+ if (blocklistFile.exists())
+ blocklistFile.remove(false);
+ var source = do_get_file("blocklist.xml");
+ source.copyTo(gProfD, "blocklist.xml");
+ blocklistFile.lastModifiedTime = Date.now();
+
+
+ let internalManager = Cc["@mozilla.org/addons/integration;1"].
+ getService(Ci.nsIObserver).
+ QueryInterface(Ci.nsITimerCallback);
+
+ internalManager.observe(null, "addons-startup", null);
+}
+
+function setManifestPref(manifest) {
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ Services.prefs.setComplexValue("social.manifest." + manifest.origin, Ci.nsISupportsString, string);
+}
+
+function do_wait_observer(obsTopic, cb) {
+ function observer(subject, topic, data) {
+ Services.obs.removeObserver(observer, topic);
+ cb();
+ }
+ Services.obs.addObserver(observer, obsTopic, false);
+}
+
+function do_add_providers(cb) {
+ // run only after social is already initialized
+ SocialService.addProvider(manifests[0], function() {
+ do_wait_observer("social:providers-changed", function() {
+ do_check_eq(Social.providers.length, 2, "2 providers installed");
+ do_execute_soon(cb);
+ });
+ SocialService.addProvider(manifests[1]);
+ });
+}
+
+function do_initialize_social(enabledOnStartup, cb) {
+ initApp();
+
+ if (enabledOnStartup) {
+ // set prefs before initializing social
+ manifests.forEach(function (manifest) {
+ setManifestPref(manifest);
+ });
+ // Set both providers active and flag the first one as "current"
+ let activeVal = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ let active = {};
+ for (let m of manifests)
+ active[m.origin] = 1;
+ activeVal.data = JSON.stringify(active);
+ Services.prefs.setComplexValue("social.activeProviders",
+ Ci.nsISupportsString, activeVal);
+
+ do_register_cleanup(function() {
+ manifests.forEach(function (manifest) {
+ Services.prefs.clearUserPref("social.manifest." + manifest.origin);
+ });
+ Services.prefs.clearUserPref("social.activeProviders");
+ });
+
+ // expecting 2 providers installed
+ do_wait_observer("social:providers-changed", function() {
+ do_check_eq(Social.providers.length, 2, "2 providers installed");
+ do_execute_soon(cb);
+ });
+ }
+
+ // import and initialize everything
+ SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+ do_check_eq(enabledOnStartup, SocialService.hasEnabledProviders, "Service has enabled providers");
+ Social = Cu.import("resource:///modules/Social.jsm", {}).Social;
+ do_check_false(Social.initialized, "Social is not initialized");
+ Social.init();
+ do_check_true(Social.initialized, "Social is initialized");
+ if (!enabledOnStartup)
+ do_execute_soon(cb);
+}
+
+function AsyncRunner() {
+ do_test_pending();
+ do_register_cleanup(() => this.destroy());
+
+ this._callbacks = {
+ done: do_test_finished,
+ error: function (err) {
+ // xpcshell test functions like do_check_eq throw NS_ERROR_ABORT on
+ // failure. Ignore those so they aren't rethrown here.
+ if (err !== Cr.NS_ERROR_ABORT) {
+ if (err.stack) {
+ err = err + " - See following stack:\n" + err.stack +
+ "\nUseless do_throw stack";
+ }
+ do_throw(err);
+ }
+ },
+ consoleError: function (scriptErr) {
+ // Try to ensure the error is related to the test.
+ let filename = scriptErr.sourceName || scriptErr.toString() || "";
+ if (filename.indexOf("/toolkit/components/social/") >= 0)
+ do_throw(scriptErr);
+ },
+ };
+ this._iteratorQueue = [];
+
+ // This catches errors reported to the console, e.g., via Cu.reportError, but
+ // not on the runner's stack.
+ Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService).
+ registerListener(this);
+}
+
+AsyncRunner.prototype = {
+
+ appendIterator: function appendIterator(iter) {
+ this._iteratorQueue.push(iter);
+ },
+
+ next: function next(arg) {
+ if (!this._iteratorQueue.length) {
+ this.destroy();
+ this._callbacks.done();
+ return;
+ }
+
+ try {
+ var { done, value: val } = this._iteratorQueue[0].next(arg);
+ if (done) {
+ this._iteratorQueue.shift();
+ this.next();
+ return;
+ }
+ }
+ catch (err) {
+ this._callbacks.error(err);
+ }
+
+ // val is an iterator => prepend it to the queue and start on it
+ // val is otherwise truthy => call next
+ if (val) {
+ if (typeof(val) != "boolean")
+ this._iteratorQueue.unshift(val);
+ this.next();
+ }
+ },
+
+ destroy: function destroy() {
+ Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService).
+ unregisterListener(this);
+ this.destroy = function alreadyDestroyed() {};
+ },
+
+ observe: function observe(msg) {
+ if (msg instanceof Ci.nsIScriptError &&
+ !(msg.flags & Ci.nsIScriptError.warningFlag))
+ {
+ this._callbacks.consoleError(msg);
+ }
+ },
+};
diff --git a/browser/modules/test/unit/social/test_SocialService.js b/browser/modules/test/unit/social/test_SocialService.js
new file mode 100644
index 000000000..e6f354fed
--- /dev/null
+++ b/browser/modules/test/unit/social/test_SocialService.js
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+function run_test() {
+ initApp();
+
+ // NOTE: none of the manifests here can have a workerURL set, or we attempt
+ // to create a FrameWorker and that fails under xpcshell...
+ let manifests = [
+ { // normal provider
+ name: "provider 1",
+ origin: "https://example1.com",
+ shareURL: "https://example1.com/share/",
+ },
+ { // provider without workerURL
+ name: "provider 2",
+ origin: "https://example2.com",
+ shareURL: "https://example2.com/share/",
+ }
+ ];
+
+ Cu.import("resource:///modules/SocialService.jsm");
+
+ let runner = new AsyncRunner();
+ let next = runner.next.bind(runner);
+ runner.appendIterator(testAddProviders(manifests, next));
+ runner.appendIterator(testGetProvider(manifests, next));
+ runner.appendIterator(testGetProviderList(manifests, next));
+ runner.appendIterator(testAddRemoveProvider(manifests, next));
+ runner.appendIterator(testIsSameOrigin(manifests, next));
+ runner.appendIterator(testResolveUri (manifests, next));
+ runner.appendIterator(testOrderedProviders(manifests, next));
+ runner.appendIterator(testRemoveProviders(manifests, next));
+ runner.next();
+}
+
+function* testAddProviders(manifests, next) {
+ do_check_false(SocialService.enabled);
+ let provider = yield SocialService.addProvider(manifests[0], next);
+ do_check_true(SocialService.enabled);
+ do_check_false(provider.enabled);
+ provider = yield SocialService.addProvider(manifests[1], next);
+ do_check_false(provider.enabled);
+}
+
+function* testRemoveProviders(manifests, next) {
+ do_check_true(SocialService.enabled);
+ yield SocialService.disableProvider(manifests[0].origin, next);
+ yield SocialService.disableProvider(manifests[1].origin, next);
+ do_check_false(SocialService.enabled);
+}
+
+function* testGetProvider(manifests, next) {
+ for (let i = 0; i < manifests.length; i++) {
+ let manifest = manifests[i];
+ let provider = yield SocialService.getProvider(manifest.origin, next);
+ do_check_neq(provider, null);
+ do_check_eq(provider.name, manifest.name);
+ do_check_eq(provider.workerURL, manifest.workerURL);
+ do_check_eq(provider.origin, manifest.origin);
+ }
+ do_check_eq((yield SocialService.getProvider("bogus", next)), null);
+}
+
+function* testGetProviderList(manifests, next) {
+ let providers = yield SocialService.getProviderList(next);
+ do_check_true(providers.length >= manifests.length);
+ for (let i = 0; i < manifests.length; i++) {
+ let providerIdx = providers.map(p => p.origin).indexOf(manifests[i].origin);
+ let provider = providers[providerIdx];
+ do_check_true(!!provider);
+ do_check_false(provider.enabled);
+ do_check_eq(provider.workerURL, manifests[i].workerURL);
+ do_check_eq(provider.name, manifests[i].name);
+ }
+}
+
+function* testAddRemoveProvider(manifests, next) {
+ var threw;
+ try {
+ // Adding a provider whose origin already exists should fail
+ SocialService.addProvider(manifests[0]);
+ } catch (ex) {
+ threw = ex;
+ }
+ do_check_neq(threw.toString().indexOf("SocialService.addProvider: provider with this origin already exists"), -1);
+
+ let originalProviders = yield SocialService.getProviderList(next);
+
+ // Check that provider installation succeeds
+ let newProvider = yield SocialService.addProvider({
+ name: "foo",
+ origin: "http://example3.com"
+ }, next);
+ let retrievedNewProvider = yield SocialService.getProvider(newProvider.origin, next);
+ do_check_eq(newProvider, retrievedNewProvider);
+
+ let providersAfter = yield SocialService.getProviderList(next);
+ do_check_eq(providersAfter.length, originalProviders.length + 1);
+ do_check_neq(providersAfter.indexOf(newProvider), -1);
+
+ // Now remove the provider
+ yield SocialService.disableProvider(newProvider.origin, next);
+ providersAfter = yield SocialService.getProviderList(next);
+ do_check_eq(providersAfter.length, originalProviders.length);
+ do_check_eq(providersAfter.indexOf(newProvider), -1);
+ newProvider = yield SocialService.getProvider(newProvider.origin, next);
+ do_check_true(!newProvider);
+}
+
+function* testIsSameOrigin(manifests, next) {
+ let providers = yield SocialService.getProviderList(next);
+ let provider = providers[0];
+ // provider.origin is a string.
+ do_check_true(provider.isSameOrigin(provider.origin));
+ do_check_true(provider.isSameOrigin(Services.io.newURI(provider.origin, null, null)));
+ do_check_true(provider.isSameOrigin(provider.origin + "/some-sub-page"));
+ do_check_true(provider.isSameOrigin(Services.io.newURI(provider.origin + "/some-sub-page", null, null)));
+ do_check_false(provider.isSameOrigin("http://something.com"));
+ do_check_false(provider.isSameOrigin(Services.io.newURI("http://something.com", null, null)));
+ do_check_false(provider.isSameOrigin("data:text/html,<p>hi"));
+ do_check_true(provider.isSameOrigin("data:text/html,<p>hi", true));
+ do_check_false(provider.isSameOrigin(Services.io.newURI("data:text/html,<p>hi", null, null)));
+ do_check_true(provider.isSameOrigin(Services.io.newURI("data:text/html,<p>hi", null, null), true));
+ // we explicitly handle null and return false
+ do_check_false(provider.isSameOrigin(null));
+}
+
+function* testResolveUri(manifests, next) {
+ let providers = yield SocialService.getProviderList(next);
+ let provider = providers[0];
+ do_check_eq(provider.resolveUri(provider.origin).spec, provider.origin + "/");
+ do_check_eq(provider.resolveUri("foo.html").spec, provider.origin + "/foo.html");
+ do_check_eq(provider.resolveUri("/foo.html").spec, provider.origin + "/foo.html");
+ do_check_eq(provider.resolveUri("http://somewhereelse.com/foo.html").spec, "http://somewhereelse.com/foo.html");
+ do_check_eq(provider.resolveUri("data:text/html,<p>hi").spec, "data:text/html,<p>hi");
+}
+
+function* testOrderedProviders(manifests, next) {
+ let providers = yield SocialService.getProviderList(next);
+
+ // add visits for only one of the providers
+ let visits = [];
+ let startDate = Date.now() * 1000;
+ for (let i = 0; i < 10; i++) {
+ visits.push({
+ uri: Services.io.newURI(providers[1].shareURL + i, null, null),
+ visitDate: startDate + i
+ });
+ }
+
+ PlacesTestUtils.addVisits(visits).then(next);
+ yield;
+ let orderedProviders = yield SocialService.getOrderedProviderList(next);
+ do_check_eq(orderedProviders[0], providers[1]);
+ do_check_eq(orderedProviders[1], providers[0]);
+ do_check_true(orderedProviders[0].frecency > orderedProviders[1].frecency);
+ PlacesTestUtils.clearHistory().then(next);
+ yield;
+}
diff --git a/browser/modules/test/unit/social/test_SocialServiceMigration21.js b/browser/modules/test/unit/social/test_SocialServiceMigration21.js
new file mode 100644
index 000000000..dfe6183bf
--- /dev/null
+++ b/browser/modules/test/unit/social/test_SocialServiceMigration21.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/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const DEFAULT_PREFS = Services.prefs.getDefaultBranch("social.manifest.");
+
+function run_test() {
+ // Test must run at startup for migration to occur, so we can only test
+ // one migration per test file
+ initApp();
+
+ // NOTE: none of the manifests here can have a workerURL set, or we attempt
+ // to create a FrameWorker and that fails under xpcshell...
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example1.com",
+ builtin: true // as of fx22 this should be true for default prefs
+ };
+
+ DEFAULT_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
+ Services.prefs.setBoolPref("social.active", true);
+
+ Cu.import("resource:///modules/SocialService.jsm");
+
+ let runner = new AsyncRunner();
+ let next = runner.next.bind(runner);
+ runner.appendIterator(testMigration(manifest, next));
+ runner.next();
+}
+
+function* testMigration(manifest, next) {
+ // look at social.activeProviders, we should have migrated into that, and
+ // we should be set as a user level pref after migration
+ do_check_false(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+ // we need to access the providers for everything to initialize
+ yield SocialService.getProviderList(next);
+ do_check_true(SocialService.enabled);
+ do_check_true(Services.prefs.prefHasUserValue("social.activeProviders"));
+
+ let activeProviders;
+ let pref = Services.prefs.getComplexValue("social.activeProviders",
+ Ci.nsISupportsString);
+ activeProviders = JSON.parse(pref);
+ do_check_true(activeProviders[manifest.origin]);
+ do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+ do_check_true(JSON.parse(DEFAULT_PREFS.getCharPref(manifest.origin)).builtin);
+
+ let userPref = JSON.parse(MANIFEST_PREFS.getCharPref(manifest.origin));
+ do_check_true(parseInt(userPref.updateDate) > 0);
+ // migrated providers wont have an installDate
+ do_check_true(userPref.installDate === 0);
+}
diff --git a/browser/modules/test/unit/social/test_SocialServiceMigration22.js b/browser/modules/test/unit/social/test_SocialServiceMigration22.js
new file mode 100644
index 000000000..1a3953175
--- /dev/null
+++ b/browser/modules/test/unit/social/test_SocialServiceMigration22.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/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const DEFAULT_PREFS = Services.prefs.getDefaultBranch("social.manifest.");
+
+function run_test() {
+ // Test must run at startup for migration to occur, so we can only test
+ // one migration per test file
+ initApp();
+
+ // NOTE: none of the manifests here can have a workerURL set, or we attempt
+ // to create a FrameWorker and that fails under xpcshell...
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example1.com",
+ builtin: true // as of fx22 this should be true for default prefs
+ };
+
+ DEFAULT_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
+
+ // Set both providers active and flag the first one as "current"
+ let activeVal = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ let active = {};
+ active[manifest.origin] = 1;
+ // bad.origin tests that a missing manifest does not break migration, bug 859715
+ active["bad.origin"] = 1;
+ activeVal.data = JSON.stringify(active);
+ Services.prefs.setComplexValue("social.activeProviders",
+ Ci.nsISupportsString, activeVal);
+
+ Cu.import("resource:///modules/SocialService.jsm");
+
+ let runner = new AsyncRunner();
+ let next = runner.next.bind(runner);
+ runner.appendIterator(testMigration(manifest, next));
+ runner.next();
+}
+
+function* testMigration(manifest, next) {
+ // look at social.activeProviders, we should have migrated into that, and
+ // we should be set as a user level pref after migration
+ do_check_false(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+ // we need to access the providers for everything to initialize
+ yield SocialService.getProviderList(next);
+ do_check_true(SocialService.enabled);
+ do_check_true(Services.prefs.prefHasUserValue("social.activeProviders"));
+
+ let activeProviders;
+ let pref = Services.prefs.getComplexValue("social.activeProviders",
+ Ci.nsISupportsString);
+ activeProviders = JSON.parse(pref);
+ do_check_true(activeProviders[manifest.origin]);
+ do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+ do_check_true(JSON.parse(DEFAULT_PREFS.getCharPref(manifest.origin)).builtin);
+
+ let userPref = JSON.parse(MANIFEST_PREFS.getCharPref(manifest.origin));
+ do_check_true(parseInt(userPref.updateDate) > 0);
+ // migrated providers wont have an installDate
+ do_check_true(userPref.installDate === 0);
+
+ // bug 859715, this should have been removed during migration
+ do_check_false(!!activeProviders["bad.origin"]);
+}
diff --git a/browser/modules/test/unit/social/test_SocialServiceMigration29.js b/browser/modules/test/unit/social/test_SocialServiceMigration29.js
new file mode 100644
index 000000000..824673ddf
--- /dev/null
+++ b/browser/modules/test/unit/social/test_SocialServiceMigration29.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/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+
+function run_test() {
+ // Test must run at startup for migration to occur, so we can only test
+ // one migration per test file
+ initApp();
+
+ // NOTE: none of the manifests here can have a workerURL set, or we attempt
+ // to create a FrameWorker and that fails under xpcshell...
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example1.com",
+ };
+
+ MANIFEST_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
+
+ // Set both providers active and flag the first one as "current"
+ let activeVal = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ let active = {};
+ active[manifest.origin] = 1;
+ activeVal.data = JSON.stringify(active);
+ Services.prefs.setComplexValue("social.activeProviders",
+ Ci.nsISupportsString, activeVal);
+
+ // social.enabled pref is the key focus of this test. We set the user pref,
+ // and then migration should a) remove the provider from activeProviders and
+ // b) unset social.enabled
+ Services.prefs.setBoolPref("social.enabled", false);
+
+ Cu.import("resource:///modules/SocialService.jsm");
+
+ let runner = new AsyncRunner();
+ let next = runner.next.bind(runner);
+ runner.appendIterator(testMigration(manifest, next));
+ runner.next();
+}
+
+function* testMigration(manifest, next) {
+ // look at social.activeProviders, we should have migrated into that, and
+ // we should be set as a user level pref after migration
+ do_check_true(Services.prefs.prefHasUserValue("social.enabled"));
+ do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+ // we need to access the providers for everything to initialize
+ yield SocialService.getProviderList(next);
+ do_check_false(SocialService.enabled);
+ do_check_false(Services.prefs.prefHasUserValue("social.enabled"));
+ do_check_true(Services.prefs.prefHasUserValue("social.activeProviders"));
+
+ let activeProviders;
+ let pref = Services.prefs.getComplexValue("social.activeProviders",
+ Ci.nsISupportsString).data;
+ activeProviders = JSON.parse(pref);
+ do_check_true(activeProviders[manifest.origin] == undefined);
+ do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+}
diff --git a/browser/modules/test/unit/social/test_social.js b/browser/modules/test/unit/social/test_social.js
new file mode 100644
index 000000000..3117306c1
--- /dev/null
+++ b/browser/modules/test/unit/social/test_social.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/. */
+
+function run_test() {
+ // we are testing worker startup specifically
+ do_test_pending();
+ add_test(testStartupEnabled);
+ add_test(testDisableAfterStartup);
+ do_initialize_social(true, run_next_test);
+}
+
+function testStartupEnabled() {
+ // wait on startup before continuing
+ do_check_eq(Social.providers.length, 2, "two social providers enabled");
+ do_check_true(Social.providers[0].enabled, "provider 0 is enabled");
+ do_check_true(Social.providers[1].enabled, "provider 1 is enabled");
+ run_next_test();
+}
+
+function testDisableAfterStartup() {
+ let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
+ SocialService.disableProvider(Social.providers[0].origin, function() {
+ do_wait_observer("social:providers-changed", function() {
+ do_check_eq(Social.enabled, false, "Social is disabled");
+ do_check_eq(Social.providers.length, 0, "no social providers available");
+ do_test_finished();
+ run_next_test();
+ });
+ SocialService.disableProvider(Social.providers[0].origin)
+ });
+}
diff --git a/browser/modules/test/unit/social/test_socialDisabledStartup.js b/browser/modules/test/unit/social/test_socialDisabledStartup.js
new file mode 100644
index 000000000..a2f7a1d5a
--- /dev/null
+++ b/browser/modules/test/unit/social/test_socialDisabledStartup.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ // we are testing worker startup specifically
+ do_test_pending();
+ add_test(testStartupDisabled);
+ add_test(testEnableAfterStartup);
+ do_initialize_social(false, run_next_test);
+}
+
+function testStartupDisabled() {
+ // wait on startup before continuing
+ do_check_false(Social.enabled, "Social is disabled");
+ do_check_eq(Social.providers.length, 0, "zero social providers available");
+ run_next_test();
+}
+
+function testEnableAfterStartup() {
+ do_add_providers(function () {
+ do_check_true(Social.enabled, "Social is enabled");
+ do_check_eq(Social.providers.length, 2, "two social providers available");
+ do_check_true(Social.providers[0].enabled, "provider 0 is enabled");
+ do_check_true(Social.providers[1].enabled, "provider 1 is enabled");
+ do_test_finished();
+ run_next_test();
+ });
+}
diff --git a/browser/modules/test/unit/social/xpcshell.ini b/browser/modules/test/unit/social/xpcshell.ini
new file mode 100644
index 000000000..277dd4f49
--- /dev/null
+++ b/browser/modules/test/unit/social/xpcshell.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+head = head.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files = blocklist.xml
+
+[test_social.js]
+[test_socialDisabledStartup.js]
+[test_SocialService.js]
+[test_SocialServiceMigration21.js]
+[test_SocialServiceMigration22.js]
+[test_SocialServiceMigration29.js]
diff --git a/browser/modules/test/usageTelemetrySearchSuggestions.sjs b/browser/modules/test/usageTelemetrySearchSuggestions.sjs
new file mode 100644
index 000000000..1978b4f66
--- /dev/null
+++ b/browser/modules/test/usageTelemetrySearchSuggestions.sjs
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ let suffixes = ["foo", "bar"];
+ let data = [req.queryString, suffixes.map(s => req.queryString + s)];
+ resp.setHeader("Content-Type", "application/json", false);
+ resp.write(JSON.stringify(data));
+}
diff --git a/browser/modules/test/usageTelemetrySearchSuggestions.xml b/browser/modules/test/usageTelemetrySearchSuggestions.xml
new file mode 100644
index 000000000..76276045d
--- /dev/null
+++ b/browser/modules/test/usageTelemetrySearchSuggestions.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_UsageTelemetry usageTelemetrySearchSuggestions.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/modules/test/usageTelemetrySearchSuggestions.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://example.com" rel="searchform"/>
+</SearchPlugin>
diff --git a/browser/modules/test/xpcshell/.eslintrc.js b/browser/modules/test/xpcshell/.eslintrc.js
new file mode 100644
index 000000000..fee088c17
--- /dev/null
+++ b/browser/modules/test/xpcshell/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/browser/modules/test/xpcshell/test_AttributionCode.js b/browser/modules/test/xpcshell/test_AttributionCode.js
new file mode 100644
index 000000000..d979ae845
--- /dev/null
+++ b/browser/modules/test/xpcshell/test_AttributionCode.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource:///modules/AttributionCode.jsm");
+Cu.import('resource://gre/modules/osfile.jsm');
+Cu.import("resource://gre/modules/Services.jsm");
+
+let validAttrCodes = [
+ {code: "source%3Dgoogle.com%26medium%3Dorganic%26campaign%3D(not%20set)%26content%3D(not%20set)",
+ parsed: {"source": "google.com", "medium": "organic",
+ "campaign": "(not%20set)", "content": "(not%20set)"}},
+ {code: "source%3Dgoogle.com%26medium%3Dorganic%26campaign%3D%26content%3D",
+ parsed: {"source": "google.com", "medium": "organic"}},
+ {code: "source%3Dgoogle.com%26medium%3Dorganic%26campaign%3D(not%20set)",
+ parsed: {"source": "google.com", "medium": "organic", "campaign": "(not%20set)"}},
+ {code: "source%3Dgoogle.com%26medium%3Dorganic",
+ parsed: {"source": "google.com", "medium": "organic"}},
+ {code: "source%3Dgoogle.com",
+ parsed: {"source": "google.com"}},
+ {code: "medium%3Dgoogle.com",
+ parsed: {"medium": "google.com"}},
+ {code: "campaign%3Dgoogle.com",
+ parsed: {"campaign": "google.com"}},
+ {code: "content%3Dgoogle.com",
+ parsed: {"content": "google.com"}}
+];
+
+let invalidAttrCodes = [
+ // Empty string
+ "",
+ // Not escaped
+ "source=google.com&medium=organic&campaign=(not set)&content=(not set)",
+ // Too long
+ "source%3Dreallyreallyreallyreallyreallyreallyreallyreallyreallylongdomain.com%26medium%3Dorganic%26campaign%3D(not%20set)%26content%3Dalmostexactlyenoughcontenttomakethisstringlongerthanthe200characterlimit",
+ // Unknown key name
+ "source%3Dgoogle.com%26medium%3Dorganic%26large%3Dgeneticallymodified",
+ // Empty key name
+ "source%3Dgoogle.com%26medium%3Dorganic%26%3Dgeneticallymodified"
+];
+
+function* writeAttributionFile(data) {
+ let appDir = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
+ let file = appDir.clone();
+ file.append(Services.appinfo.vendor || "mozilla");
+ file.append(AppConstants.MOZ_APP_NAME);
+
+ yield OS.File.makeDir(file.path,
+ {from: appDir.path, ignoreExisting: true});
+
+ file.append("postSigningData");
+ yield OS.File.writeAtomic(file.path, data);
+}
+
+/**
+ * Test validation of attribution codes,
+ * to make sure we reject bad ones and accept good ones.
+ */
+add_task(function* testValidAttrCodes() {
+ for (let entry of validAttrCodes) {
+ AttributionCode._clearCache();
+ yield writeAttributionFile(entry.code);
+ let result = yield AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, entry.parsed,
+ "Parsed code should match expected value, code was: " + entry.code);
+ }
+ AttributionCode._clearCache();
+});
+
+/**
+ * Make sure codes with various formatting errors are not seen as valid.
+ */
+add_task(function* testInvalidAttrCodes() {
+ for (let code of invalidAttrCodes) {
+ AttributionCode._clearCache();
+ yield writeAttributionFile(code);
+ let result = yield AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {},
+ "Code should have failed to parse: " + code);
+ }
+ AttributionCode._clearCache();
+});
+
+/**
+ * Test the cache by deleting the attribution data file
+ * and making sure we still get the expected code.
+ */
+add_task(function* testDeletedFile() {
+ // Set up the test by clearing the cache and writing a valid file.
+ yield writeAttributionFile(validAttrCodes[0].code);
+ let result = yield AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, validAttrCodes[0].parsed,
+ "The code should be readable directly from the file");
+
+ // Delete the file and make sure we can still read the value back from cache.
+ yield AttributionCode.deleteFileAsync();
+ result = yield AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, validAttrCodes[0].parsed,
+ "The code should be readable from the cache");
+
+ // Clear the cache and check we can't read anything.
+ AttributionCode._clearCache();
+ result = yield AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {},
+ "Shouldn't be able to get a code after file is deleted and cache is cleared");
+});
diff --git a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
new file mode 100644
index 000000000..712f52fa6
--- /dev/null
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -0,0 +1,1854 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+/**
+ * This file tests the DirectoryLinksProvider singleton in the DirectoryLinksProvider.jsm module.
+ */
+
+var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu, Constructor: CC } = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/DirectoryLinksProvider.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Http.jsm");
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
+ "resource://gre/modules/NewTabUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+do_get_profile();
+
+const DIRECTORY_LINKS_FILE = "directoryLinks.json";
+const DIRECTORY_FRECENCY = 1000;
+const SUGGESTED_FRECENCY = Infinity;
+const kURLData = {"directory": [{"url":"http://example.com", "title":"LocalSource"}]};
+const kTestURL = 'data:application/json,' + JSON.stringify(kURLData);
+
+// DirectoryLinksProvider preferences
+const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale;
+const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL;
+const kPingUrlPref = "browser.newtabpage.directory.ping";
+const kNewtabEnhancedPref = "browser.newtabpage.enhanced";
+
+// httpd settings
+var server;
+const kDefaultServerPort = 9000;
+const kBaseUrl = "http://localhost:" + kDefaultServerPort;
+const kExamplePath = "/exampleTest/";
+const kFailPath = "/fail/";
+const kPingPath = "/ping/";
+const kExampleURL = kBaseUrl + kExamplePath;
+const kFailURL = kBaseUrl + kFailPath;
+const kPingUrl = kBaseUrl + kPingPath;
+
+// app/profile/firefox.js are not avaialble in xpcshell: hence, preset them
+Services.prefs.setCharPref(kLocalePref, "en-US");
+Services.prefs.setCharPref(kSourceUrlPref, kTestURL);
+Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
+Services.prefs.setBoolPref(kNewtabEnhancedPref, true);
+
+const kHttpHandlerData = {};
+kHttpHandlerData[kExamplePath] = {"directory": [{"url":"http://example.com", "title":"RemoteSource"}]};
+
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+var gLastRequestPath;
+
+var suggestedTile1 = {
+ url: "http://turbotax.com",
+ type: "affiliate",
+ lastVisitDate: 4,
+ adgroup_name: "Adgroup1",
+ frecent_sites: [
+ "taxact.com",
+ "hrblock.com",
+ "1040.com",
+ "taxslayer.com"
+ ]
+};
+var suggestedTile2 = {
+ url: "http://irs.gov",
+ type: "affiliate",
+ lastVisitDate: 3,
+ adgroup_name: "Adgroup2",
+ frecent_sites: [
+ "taxact.com",
+ "hrblock.com",
+ "freetaxusa.com",
+ "taxslayer.com"
+ ]
+};
+var suggestedTile3 = {
+ url: "http://hrblock.com",
+ type: "affiliate",
+ lastVisitDate: 2,
+ adgroup_name: "Adgroup3",
+ frecent_sites: [
+ "taxact.com",
+ "freetaxusa.com",
+ "1040.com",
+ "taxslayer.com"
+ ]
+};
+var suggestedTile4 = {
+ url: "http://sponsoredtile.com",
+ type: "sponsored",
+ lastVisitDate: 1,
+ adgroup_name: "Adgroup4",
+ frecent_sites: [
+ "sponsoredtarget.com"
+ ]
+}
+var suggestedTile5 = {
+ url: "http://eviltile.com",
+ type: "affiliate",
+ lastVisitDate: 5,
+ explanation: "This is an evil tile <form><button formaction='javascript:alert(1)''>X</button></form> muhahaha",
+ adgroup_name: "WE ARE EVIL <link rel='import' href='test.svg'/>",
+ frecent_sites: [
+ "eviltarget.com"
+ ]
+}
+var someOtherSite = {url: "http://someothersite.com", title: "Not_A_Suggested_Site"};
+
+function getHttpHandler(path) {
+ let code = 200;
+ let body = JSON.stringify(kHttpHandlerData[path]);
+ if (path == kFailPath) {
+ code = 204;
+ }
+ return function(aRequest, aResponse) {
+ gLastRequestPath = aRequest.path;
+ aResponse.setStatusLine(null, code);
+ aResponse.setHeader("Content-Type", "application/json");
+ aResponse.write(body);
+ };
+}
+
+function isIdentical(actual, expected) {
+ if (expected == null) {
+ do_check_eq(actual, expected);
+ }
+ else if (typeof expected == "object") {
+ // Make sure all the keys match up
+ do_check_eq(Object.keys(actual).sort() + "", Object.keys(expected).sort());
+
+ // Recursively check each value individually
+ Object.keys(expected).forEach(key => {
+ isIdentical(actual[key], expected[key]);
+ });
+ }
+ else {
+ do_check_eq(actual, expected);
+ }
+}
+
+function fetchData() {
+ let deferred = Promise.defer();
+
+ DirectoryLinksProvider.getLinks(linkData => {
+ deferred.resolve(linkData);
+ });
+ return deferred.promise;
+}
+
+function readJsonFile(jsonFile = DIRECTORY_LINKS_FILE) {
+ let decoder = new TextDecoder();
+ let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, jsonFile);
+ return OS.File.read(directoryLinksFilePath).then(array => {
+ let json = decoder.decode(array);
+ return JSON.parse(json);
+ }, () => { return "" });
+}
+
+function cleanJsonFile(jsonFile = DIRECTORY_LINKS_FILE) {
+ let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, jsonFile);
+ return OS.File.remove(directoryLinksFilePath);
+}
+
+function LinksChangeObserver() {
+ this.deferred = Promise.defer();
+ this.onManyLinksChanged = () => this.deferred.resolve();
+ this.onDownloadFail = this.onManyLinksChanged;
+}
+
+function promiseDirectoryDownloadOnPrefChange(pref, newValue) {
+ let oldValue = Services.prefs.getCharPref(pref);
+ if (oldValue != newValue) {
+ // if the preference value is already equal to newValue
+ // the pref service will not call our observer and we
+ // deadlock. Hence only setup observer if values differ
+ let observer = new LinksChangeObserver();
+ DirectoryLinksProvider.addObserver(observer);
+ Services.prefs.setCharPref(pref, newValue);
+ return observer.deferred.promise.then(() => {
+ DirectoryLinksProvider.removeObserver(observer);
+ });
+ }
+ return Promise.resolve();
+}
+
+function promiseSetupDirectoryLinksProvider(options = {}) {
+ return Task.spawn(function*() {
+ let linksURL = options.linksURL || kTestURL;
+ yield DirectoryLinksProvider.init();
+ yield promiseDirectoryDownloadOnPrefChange(kLocalePref, options.locale || "en-US");
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, linksURL);
+ do_check_eq(DirectoryLinksProvider._linksURL, linksURL);
+ DirectoryLinksProvider._lastDownloadMS = options.lastDownloadMS || 0;
+ });
+}
+
+function promiseCleanDirectoryLinksProvider() {
+ return Task.spawn(function*() {
+ yield promiseDirectoryDownloadOnPrefChange(kLocalePref, "en-US");
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kTestURL);
+ yield DirectoryLinksProvider._clearFrequencyCap();
+ yield DirectoryLinksProvider._loadInadjacentSites();
+ DirectoryLinksProvider._lastDownloadMS = 0;
+ DirectoryLinksProvider.reset();
+ });
+}
+
+function run_test() {
+ // Set up a mock HTTP server to serve a directory page
+ server = new HttpServer();
+ server.registerPrefixHandler(kExamplePath, getHttpHandler(kExamplePath));
+ server.registerPrefixHandler(kFailPath, getHttpHandler(kFailPath));
+ server.start(kDefaultServerPort);
+ NewTabUtils.init();
+
+ run_next_test();
+
+ // Teardown.
+ do_register_cleanup(function() {
+ server.stop(function() { });
+ DirectoryLinksProvider.reset();
+ Services.prefs.clearUserPref(kLocalePref);
+ Services.prefs.clearUserPref(kSourceUrlPref);
+ Services.prefs.clearUserPref(kPingUrlPref);
+ Services.prefs.clearUserPref(kNewtabEnhancedPref);
+ });
+}
+
+
+function setTimeout(fun, timeout) {
+ let timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ var event = {
+ notify: function () {
+ fun();
+ }
+ };
+ timer.initWithCallback(event, timeout,
+ Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+}
+
+add_task(function test_shouldUpdateSuggestedTile() {
+ let suggestedLink = {
+ targetedSite: "somesite.com"
+ };
+
+ // DirectoryLinksProvider has no suggested tile and no top sites => no need to update
+ do_check_eq(DirectoryLinksProvider._getCurrentTopSiteCount(), 0);
+ isIdentical(NewTabUtils.getProviderLinks(), []);
+ do_check_eq(DirectoryLinksProvider._shouldUpdateSuggestedTile(), false);
+
+ // DirectoryLinksProvider has a suggested tile and no top sites => need to update
+ let origGetProviderLinks = NewTabUtils.getProviderLinks;
+ NewTabUtils.getProviderLinks = (provider) => [suggestedLink];
+
+ do_check_eq(DirectoryLinksProvider._getCurrentTopSiteCount(), 0);
+ isIdentical(NewTabUtils.getProviderLinks(), [suggestedLink]);
+ do_check_eq(DirectoryLinksProvider._shouldUpdateSuggestedTile(), true);
+
+ // DirectoryLinksProvider has a suggested tile and 8 top sites => no need to update
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ do_check_eq(DirectoryLinksProvider._getCurrentTopSiteCount(), 8);
+ isIdentical(NewTabUtils.getProviderLinks(), [suggestedLink]);
+ do_check_eq(DirectoryLinksProvider._shouldUpdateSuggestedTile(), false);
+
+ // DirectoryLinksProvider has no suggested tile and 8 top sites => need to update
+ NewTabUtils.getProviderLinks = origGetProviderLinks;
+ do_check_eq(DirectoryLinksProvider._getCurrentTopSiteCount(), 8);
+ isIdentical(NewTabUtils.getProviderLinks(), []);
+ do_check_eq(DirectoryLinksProvider._shouldUpdateSuggestedTile(), true);
+
+ // Cleanup
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+});
+
+add_task(function* test_updateSuggestedTile() {
+ let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
+
+ // Initial setup
+ let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ let testObserver = new TestFirstRun();
+ DirectoryLinksProvider.addObserver(testObserver);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ let links = yield fetchData();
+
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = function(site) {
+ return topSites.indexOf(site) >= 0;
+ }
+
+ let origGetProviderLinks = NewTabUtils.getProviderLinks;
+ NewTabUtils.getProviderLinks = function(provider) {
+ return links;
+ }
+
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ do_check_eq(DirectoryLinksProvider._updateSuggestedTile(), undefined);
+
+ function TestFirstRun() {
+ this.promise = new Promise(resolve => {
+ this.onLinkChanged = (directoryLinksProvider, link) => {
+ links.unshift(link);
+ let possibleLinks = [suggestedTile1.url, suggestedTile2.url, suggestedTile3.url];
+
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "1040.com", "freetaxusa.com"]);
+ do_check_true(possibleLinks.indexOf(link.url) > -1);
+ do_check_eq(link.frecency, SUGGESTED_FRECENCY);
+ do_check_eq(link.type, "affiliate");
+ resolve();
+ };
+ });
+ }
+
+ function TestChangingSuggestedTile() {
+ this.count = 0;
+ this.promise = new Promise(resolve => {
+ this.onLinkChanged = (directoryLinksProvider, link) => {
+ this.count++;
+ let possibleLinks = [suggestedTile1.url, suggestedTile2.url, suggestedTile3.url];
+
+ do_check_true(possibleLinks.indexOf(link.url) > -1);
+ do_check_eq(link.type, "affiliate");
+ do_check_true(this.count <= 2);
+
+ if (this.count == 1) {
+ // The removed suggested link is the one we added initially.
+ do_check_eq(link.url, links.shift().url);
+ do_check_eq(link.frecency, SUGGESTED_FRECENCY);
+ } else {
+ links.unshift(link);
+ do_check_eq(link.frecency, SUGGESTED_FRECENCY);
+ }
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "freetaxusa.com"]);
+ resolve();
+ }
+ });
+ }
+
+ function TestRemovingSuggestedTile() {
+ this.count = 0;
+ this.promise = new Promise(resolve => {
+ this.onLinkChanged = (directoryLinksProvider, link) => {
+ this.count++;
+
+ do_check_eq(link.type, "affiliate");
+ do_check_eq(this.count, 1);
+ do_check_eq(link.frecency, SUGGESTED_FRECENCY);
+ do_check_eq(link.url, links.shift().url);
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], []);
+ resolve();
+ }
+ });
+ }
+
+ // Test first call to '_updateSuggestedTile()', called when fetching directory links.
+ yield testObserver.promise;
+ DirectoryLinksProvider.removeObserver(testObserver);
+
+ // Removing a top site that doesn't have a suggested link should
+ // not change the current suggested tile.
+ let removedTopsite = topSites.shift();
+ do_check_eq(removedTopsite, "site0.com");
+ do_check_false(NewTabUtils.isTopPlacesSite(removedTopsite));
+ let updateSuggestedTile = DirectoryLinksProvider._handleLinkChanged({
+ url: "http://" + removedTopsite,
+ type: "history",
+ });
+ do_check_false(updateSuggestedTile);
+
+ // Removing a top site that has a suggested link should
+ // remove any current suggested tile and add a new one.
+ testObserver = new TestChangingSuggestedTile();
+ DirectoryLinksProvider.addObserver(testObserver);
+ removedTopsite = topSites.shift();
+ do_check_eq(removedTopsite, "1040.com");
+ do_check_false(NewTabUtils.isTopPlacesSite(removedTopsite));
+ DirectoryLinksProvider.onLinkChanged(DirectoryLinksProvider, {
+ url: "http://" + removedTopsite,
+ type: "history",
+ });
+ yield testObserver.promise;
+ do_check_eq(testObserver.count, 2);
+ DirectoryLinksProvider.removeObserver(testObserver);
+
+ // Removing all top sites with suggested links should remove
+ // the current suggested link and not replace it.
+ topSites = [];
+ testObserver = new TestRemovingSuggestedTile();
+ DirectoryLinksProvider.addObserver(testObserver);
+ DirectoryLinksProvider.onManyLinksChanged();
+ yield testObserver.promise;
+
+ // Cleanup
+ yield promiseCleanDirectoryLinksProvider();
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ NewTabUtils.getProviderLinks = origGetProviderLinks;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+});
+
+add_task(function* test_suggestedLinksMap() {
+ let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3, suggestedTile4], "directory": [someOtherSite]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ let links = yield fetchData();
+
+ // Ensure the suggested tiles were not considered directory tiles.
+ do_check_eq(links.length, 1);
+ let expected_data = [{url: "http://someothersite.com", title: "Not_A_Suggested_Site", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
+ isIdentical(links, expected_data);
+
+ // Check for correctly saved suggested tiles data.
+ expected_data = {
+ "taxact.com": [suggestedTile1, suggestedTile2, suggestedTile3],
+ "hrblock.com": [suggestedTile1, suggestedTile2],
+ "1040.com": [suggestedTile1, suggestedTile3],
+ "taxslayer.com": [suggestedTile1, suggestedTile2, suggestedTile3],
+ "freetaxusa.com": [suggestedTile2, suggestedTile3],
+ "sponsoredtarget.com": [suggestedTile4],
+ };
+
+ let suggestedSites = [...DirectoryLinksProvider._suggestedLinks.keys()];
+ do_check_eq(suggestedSites.indexOf("sponsoredtarget.com"), 5);
+ do_check_eq(suggestedSites.length, Object.keys(expected_data).length);
+
+ DirectoryLinksProvider._suggestedLinks.forEach((suggestedLinks, site) => {
+ let suggestedLinksItr = suggestedLinks.values();
+ for (let link of expected_data[site]) {
+ let linkCopy = JSON.parse(JSON.stringify(link));
+ linkCopy.targetedName = link.adgroup_name;
+ linkCopy.explanation = "";
+ isIdentical(suggestedLinksItr.next().value, linkCopy);
+ }
+ })
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_topSitesWithSuggestedLinks() {
+ let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = function(site) {
+ return topSites.indexOf(site) >= 0;
+ }
+
+ // Mock out getProviderLinks() so we don't have to populate cache in NewTabUtils
+ let origGetProviderLinks = NewTabUtils.getProviderLinks;
+ NewTabUtils.getProviderLinks = function(provider) {
+ return [];
+ }
+
+ // We start off with no top sites with suggested links.
+ do_check_eq(DirectoryLinksProvider._topSitesWithSuggestedLinks.size, 0);
+
+ let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ yield fetchData();
+
+ // Check we've populated suggested links as expected.
+ do_check_eq(DirectoryLinksProvider._suggestedLinks.size, 5);
+
+ // When many sites change, we update _topSitesWithSuggestedLinks as expected.
+ let expectedTopSitesWithSuggestedLinks = ["hrblock.com", "1040.com", "freetaxusa.com"];
+ DirectoryLinksProvider._handleManyLinksChanged();
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
+
+ // Removing site6.com as a topsite has no impact on _topSitesWithSuggestedLinks.
+ let popped = topSites.pop();
+ DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
+
+ // Removing freetaxusa.com as a topsite will remove it from _topSitesWithSuggestedLinks.
+ popped = topSites.pop();
+ expectedTopSitesWithSuggestedLinks.pop();
+ DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
+
+ // Re-adding freetaxusa.com as a topsite will add it to _topSitesWithSuggestedLinks.
+ topSites.push(popped);
+ expectedTopSitesWithSuggestedLinks.push(popped);
+ DirectoryLinksProvider._handleLinkChanged({url: "http://" + popped});
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], expectedTopSitesWithSuggestedLinks);
+
+ // Cleanup.
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ NewTabUtils.getProviderLinks = origGetProviderLinks;
+});
+
+add_task(function* test_suggestedAttributes() {
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = () => true;
+
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ let frecent_sites = "addons.mozilla.org,air.mozilla.org,blog.mozilla.org,bugzilla.mozilla.org,developer.mozilla.org,etherpad.mozilla.org,forums.mozillazine.org,hacks.mozilla.org,hg.mozilla.org,mozilla.org,planet.mozilla.org,quality.mozilla.org,support.mozilla.org,treeherder.mozilla.org,wiki.mozilla.org".split(",");
+ let imageURI = "https://image/";
+ let title = "the title";
+ let type = "affiliate";
+ let url = "http://test.url/";
+ let adgroup_name = "Mozilla";
+ let data = {
+ suggested: [{
+ frecent_sites,
+ imageURI,
+ title,
+ type,
+ url,
+ adgroup_name
+ }]
+ };
+ let dataURI = "data:application/json," + escape(JSON.stringify(data));
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ // Wait for links to get loaded
+ let gLinks = NewTabUtils.links;
+ gLinks.addProvider(DirectoryLinksProvider);
+ gLinks.populateCache();
+ yield new Promise(resolve => {
+ NewTabUtils.allPages.register({
+ observe: _ => _,
+ update() {
+ NewTabUtils.allPages.unregister(this);
+ resolve();
+ }
+ });
+ });
+
+ // Make sure we get the expected attributes on the suggested tile
+ let link = gLinks.getLinks()[0];
+ do_check_eq(link.imageURI, imageURI);
+ do_check_eq(link.targetedName, "Mozilla");
+ do_check_eq(link.targetedSite, frecent_sites[0]);
+ do_check_eq(link.title, title);
+ do_check_eq(link.type, type);
+ do_check_eq(link.url, url);
+
+ // Cleanup.
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+ gLinks.removeProvider(DirectoryLinksProvider);
+ DirectoryLinksProvider.removeObserver(gLinks);
+});
+
+add_task(function* test_frequencyCappedSites_views() {
+ Services.prefs.setCharPref(kPingUrlPref, "");
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = () => true;
+
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ let testUrl = "http://frequency.capped/link";
+ let targets = ["top.site.com"];
+ let data = {
+ suggested: [{
+ type: "affiliate",
+ frecent_sites: targets,
+ url: testUrl,
+ frequency_caps: {daily: 5},
+ adgroup_name: "Test"
+ }],
+ directory: [{
+ type: "organic",
+ url: "http://directory.site/"
+ }]
+ };
+ let dataURI = "data:application/json," + JSON.stringify(data);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ // Wait for links to get loaded
+ let gLinks = NewTabUtils.links;
+ gLinks.addProvider(DirectoryLinksProvider);
+ gLinks.populateCache();
+ yield new Promise(resolve => {
+ NewTabUtils.allPages.register({
+ observe: _ => _,
+ update() {
+ NewTabUtils.allPages.unregister(this);
+ resolve();
+ }
+ });
+ });
+
+ function synthesizeAction(action) {
+ DirectoryLinksProvider.reportSitesAction([{
+ link: {
+ targetedSite: targets[0],
+ url: testUrl
+ }
+ }], action, 0);
+ }
+
+ function checkFirstTypeAndLength(type, length) {
+ let links = gLinks.getLinks();
+ do_check_eq(links[0].type, type);
+ do_check_eq(links.length, length);
+ }
+
+ // Make sure we get 5 views of the link before it is removed
+ checkFirstTypeAndLength("affiliate", 2);
+ synthesizeAction("view");
+ checkFirstTypeAndLength("affiliate", 2);
+ synthesizeAction("view");
+ checkFirstTypeAndLength("affiliate", 2);
+ synthesizeAction("view");
+ checkFirstTypeAndLength("affiliate", 2);
+ synthesizeAction("view");
+ checkFirstTypeAndLength("affiliate", 2);
+ synthesizeAction("view");
+ checkFirstTypeAndLength("organic", 1);
+
+ // Cleanup.
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+ gLinks.removeProvider(DirectoryLinksProvider);
+ DirectoryLinksProvider.removeObserver(gLinks);
+ Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
+});
+
+add_task(function* test_frequencyCappedSites_click() {
+ Services.prefs.setCharPref(kPingUrlPref, "");
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = () => true;
+
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ let testUrl = "http://frequency.capped/link";
+ let targets = ["top.site.com"];
+ let data = {
+ suggested: [{
+ type: "affiliate",
+ frecent_sites: targets,
+ url: testUrl,
+ adgroup_name: "Test"
+ }],
+ directory: [{
+ type: "organic",
+ url: "http://directory.site/"
+ }]
+ };
+ let dataURI = "data:application/json," + JSON.stringify(data);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ // Wait for links to get loaded
+ let gLinks = NewTabUtils.links;
+ gLinks.addProvider(DirectoryLinksProvider);
+ gLinks.populateCache();
+ yield new Promise(resolve => {
+ NewTabUtils.allPages.register({
+ observe: _ => _,
+ update() {
+ NewTabUtils.allPages.unregister(this);
+ resolve();
+ }
+ });
+ });
+
+ function synthesizeAction(action) {
+ DirectoryLinksProvider.reportSitesAction([{
+ link: {
+ targetedSite: targets[0],
+ url: testUrl
+ }
+ }], action, 0);
+ }
+
+ function checkFirstTypeAndLength(type, length) {
+ let links = gLinks.getLinks();
+ do_check_eq(links[0].type, type);
+ do_check_eq(links.length, length);
+ }
+
+ // Make sure the link disappears after the first click
+ checkFirstTypeAndLength("affiliate", 2);
+ synthesizeAction("view");
+ checkFirstTypeAndLength("affiliate", 2);
+ synthesizeAction("click");
+ checkFirstTypeAndLength("organic", 1);
+
+ // Cleanup.
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+ gLinks.removeProvider(DirectoryLinksProvider);
+ DirectoryLinksProvider.removeObserver(gLinks);
+ Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
+});
+
+add_task(function* test_fetchAndCacheLinks_local() {
+ yield DirectoryLinksProvider.init();
+ yield cleanJsonFile();
+ // Trigger cache of data or chrome uri files in profD
+ yield DirectoryLinksProvider._fetchAndCacheLinks(kTestURL);
+ let data = yield readJsonFile();
+ isIdentical(data, kURLData);
+});
+
+add_task(function* test_fetchAndCacheLinks_remote() {
+ yield DirectoryLinksProvider.init();
+ yield cleanJsonFile();
+ // this must trigger directory links json download and save it to cache file
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kExampleURL + "%LOCALE%");
+ do_check_eq(gLastRequestPath, kExamplePath + "en-US");
+ let data = yield readJsonFile();
+ isIdentical(data, kHttpHandlerData[kExamplePath]);
+});
+
+add_task(function* test_fetchAndCacheLinks_malformedURI() {
+ yield DirectoryLinksProvider.init();
+ yield cleanJsonFile();
+ let someJunk = "some junk";
+ try {
+ yield DirectoryLinksProvider._fetchAndCacheLinks(someJunk);
+ do_throw("Malformed URIs should fail")
+ } catch (e) {
+ do_check_eq(e, "Error fetching " + someJunk)
+ }
+
+ // File should be empty.
+ let data = yield readJsonFile();
+ isIdentical(data, "");
+});
+
+add_task(function* test_fetchAndCacheLinks_unknownHost() {
+ yield DirectoryLinksProvider.init();
+ yield cleanJsonFile();
+ let nonExistentServer = "http://localhost:56789/";
+ try {
+ yield DirectoryLinksProvider._fetchAndCacheLinks(nonExistentServer);
+ do_throw("BAD URIs should fail");
+ } catch (e) {
+ do_check_true(e.startsWith("Fetching " + nonExistentServer + " results in error code: "))
+ }
+
+ // File should be empty.
+ let data = yield readJsonFile();
+ isIdentical(data, "");
+});
+
+add_task(function* test_fetchAndCacheLinks_non200Status() {
+ yield DirectoryLinksProvider.init();
+ yield cleanJsonFile();
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kFailURL);
+ do_check_eq(gLastRequestPath, kFailPath);
+ let data = yield readJsonFile();
+ isIdentical(data, {});
+});
+
+// To test onManyLinksChanged observer, trigger a fetch
+add_task(function* test_DirectoryLinksProvider__linkObservers() {
+ yield DirectoryLinksProvider.init();
+
+ let testObserver = new LinksChangeObserver();
+ DirectoryLinksProvider.addObserver(testObserver);
+ do_check_eq(DirectoryLinksProvider._observers.size, 1);
+ DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
+
+ yield testObserver.deferred.promise;
+ DirectoryLinksProvider._removeObservers();
+ do_check_eq(DirectoryLinksProvider._observers.size, 0);
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider__prefObserver_url() {
+ yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL});
+
+ let links = yield fetchData();
+ do_check_eq(links.length, 1);
+ let expectedData = [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
+ isIdentical(links, expectedData);
+
+ // tests these 2 things:
+ // 1. _linksURL is properly set after the pref change
+ // 2. invalid source url is correctly handled
+ let exampleUrl = 'http://localhost:56789/bad';
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl);
+ do_check_eq(DirectoryLinksProvider._linksURL, exampleUrl);
+
+ // since the download fail, the directory file must remain the same
+ let newLinks = yield fetchData();
+ isIdentical(newLinks, expectedData);
+
+ // now remove the file, and re-download
+ yield cleanJsonFile();
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl + " ");
+ // we now should see empty links
+ newLinks = yield fetchData();
+ isIdentical(newLinks, []);
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_getLinks_noDirectoryData() {
+ let data = {
+ "directory": [],
+ };
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ let links = yield fetchData();
+ do_check_eq(links.length, 0);
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_getLinks_badData() {
+ let data = {
+ "en-US": {
+ "en-US": [{url: "http://example.com", title: "US"}],
+ },
+ };
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ // Make sure we get nothing for incorrectly formatted data
+ let links = yield fetchData();
+ do_check_eq(links.length, 0);
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function test_DirectoryLinksProvider_needsDownload() {
+ // test timestamping
+ DirectoryLinksProvider._lastDownloadMS = 0;
+ do_check_true(DirectoryLinksProvider._needsDownload);
+ DirectoryLinksProvider._lastDownloadMS = Date.now();
+ do_check_false(DirectoryLinksProvider._needsDownload);
+ DirectoryLinksProvider._lastDownloadMS = Date.now() - (60*60*24 + 1)*1000;
+ do_check_true(DirectoryLinksProvider._needsDownload);
+ DirectoryLinksProvider._lastDownloadMS = 0;
+});
+
+add_task(function* test_DirectoryLinksProvider_fetchAndCacheLinksIfNecessary() {
+ yield DirectoryLinksProvider.init();
+ yield cleanJsonFile();
+ // explicitly change source url to cause the download during setup
+ yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL+" "});
+ yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary();
+
+ // inspect lastDownloadMS timestamp which should be 5 seconds less then now()
+ let lastDownloadMS = DirectoryLinksProvider._lastDownloadMS;
+ do_check_true((Date.now() - lastDownloadMS) < 5000);
+
+ // we should have fetched a new file during setup
+ let data = yield readJsonFile();
+ isIdentical(data, kURLData);
+
+ // attempt to download again - the timestamp should not change
+ yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary();
+ do_check_eq(DirectoryLinksProvider._lastDownloadMS, lastDownloadMS);
+
+ // clean the file and force the download
+ yield cleanJsonFile();
+ yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
+ data = yield readJsonFile();
+ isIdentical(data, kURLData);
+
+ // make sure that failed download does not corrupt the file, nor changes lastDownloadMS
+ lastDownloadMS = DirectoryLinksProvider._lastDownloadMS;
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, "http://");
+ yield DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
+ data = yield readJsonFile();
+ isIdentical(data, kURLData);
+ do_check_eq(DirectoryLinksProvider._lastDownloadMS, lastDownloadMS);
+
+ // _fetchAndCacheLinksIfNecessary must return same promise if download is in progress
+ let downloadPromise = DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
+ let anotherPromise = DirectoryLinksProvider._fetchAndCacheLinksIfNecessary(true);
+ do_check_true(downloadPromise === anotherPromise);
+ yield downloadPromise;
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_fetchDirectoryOnPrefChange() {
+ yield DirectoryLinksProvider.init();
+
+ let testObserver = new LinksChangeObserver();
+ DirectoryLinksProvider.addObserver(testObserver);
+
+ yield cleanJsonFile();
+ // ensure that provider does not think it needs to download
+ do_check_false(DirectoryLinksProvider._needsDownload);
+
+ // change the source URL, which should force directory download
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kExampleURL);
+ // then wait for testObserver to fire and test that json is downloaded
+ yield testObserver.deferred.promise;
+ do_check_eq(gLastRequestPath, kExamplePath);
+ let data = yield readJsonFile();
+ isIdentical(data, kHttpHandlerData[kExamplePath]);
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_fetchDirectoryOnShow() {
+ yield promiseSetupDirectoryLinksProvider();
+
+ // set lastdownload to 0 to make DirectoryLinksProvider want to download
+ DirectoryLinksProvider._lastDownloadMS = 0;
+ do_check_true(DirectoryLinksProvider._needsDownload);
+
+ // download should happen on view
+ yield DirectoryLinksProvider.reportSitesAction([], "view");
+ do_check_true(DirectoryLinksProvider._lastDownloadMS != 0);
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_fetchDirectoryOnInit() {
+ // ensure preferences are set to defaults
+ yield promiseSetupDirectoryLinksProvider();
+ // now clean to provider, so we can init it again
+ yield promiseCleanDirectoryLinksProvider();
+
+ yield cleanJsonFile();
+ yield DirectoryLinksProvider.init();
+ let data = yield readJsonFile();
+ isIdentical(data, kURLData);
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_getLinksFromCorruptedFile() {
+ yield promiseSetupDirectoryLinksProvider();
+
+ // write bogus json to a file and attempt to fetch from it
+ let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.profileDir, DIRECTORY_LINKS_FILE);
+ yield OS.File.writeAtomic(directoryLinksFilePath, '{"en-US":');
+ let data = yield fetchData();
+ isIdentical(data, []);
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_getAllowedLinks() {
+ let data = {"directory": [
+ {url: "ftp://example.com"},
+ {url: "http://example.net"},
+ {url: "javascript:5"},
+ {url: "https://example.com"},
+ {url: "httpJUNKjavascript:42"},
+ {url: "data:text/plain,hi"},
+ {url: "http/bork:eh"},
+ ]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ let links = yield fetchData();
+ do_check_eq(links.length, 2);
+
+ // The only remaining url should be http and https
+ do_check_eq(links[0].url, data["directory"][1].url);
+ do_check_eq(links[1].url, data["directory"][3].url);
+});
+
+add_task(function* test_DirectoryLinksProvider_getAllowedImages() {
+ let data = {"directory": [
+ {url: "http://example.com", imageURI: "ftp://example.com"},
+ {url: "http://example.com", imageURI: "http://example.net"},
+ {url: "http://example.com", imageURI: "javascript:5"},
+ {url: "http://example.com", imageURI: "https://example.com"},
+ {url: "http://example.com", imageURI: "httpJUNKjavascript:42"},
+ {url: "http://example.com", imageURI: "data:text/plain,hi"},
+ {url: "http://example.com", imageURI: "http/bork:eh"},
+ ]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ let links = yield fetchData();
+ do_check_eq(links.length, 2);
+
+ // The only remaining images should be https and data
+ do_check_eq(links[0].imageURI, data["directory"][3].imageURI);
+ do_check_eq(links[1].imageURI, data["directory"][5].imageURI);
+});
+
+add_task(function* test_DirectoryLinksProvider_getAllowedImages_base() {
+ let data = {"directory": [
+ {url: "http://example1.com", imageURI: "https://example.com"},
+ {url: "http://example2.com", imageURI: "https://tiles.cdn.mozilla.net"},
+ {url: "http://example3.com", imageURI: "https://tiles2.cdn.mozilla.net"},
+ {url: "http://example4.com", enhancedImageURI: "https://mozilla.net"},
+ {url: "http://example5.com", imageURI: "data:text/plain,hi"},
+ ]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ // Pretend we're using the default pref to trigger base matching
+ DirectoryLinksProvider.__linksURLModified = false;
+
+ let links = yield fetchData();
+ do_check_eq(links.length, 4);
+
+ // The only remaining images should be https with mozilla.net or data URI
+ do_check_eq(links[0].url, data["directory"][1].url);
+ do_check_eq(links[1].url, data["directory"][2].url);
+ do_check_eq(links[2].url, data["directory"][3].url);
+ do_check_eq(links[3].url, data["directory"][4].url);
+});
+
+add_task(function* test_DirectoryLinksProvider_getAllowedEnhancedImages() {
+ let data = {"directory": [
+ {url: "http://example.com", enhancedImageURI: "ftp://example.com"},
+ {url: "http://example.com", enhancedImageURI: "http://example.net"},
+ {url: "http://example.com", enhancedImageURI: "javascript:5"},
+ {url: "http://example.com", enhancedImageURI: "https://example.com"},
+ {url: "http://example.com", enhancedImageURI: "httpJUNKjavascript:42"},
+ {url: "http://example.com", enhancedImageURI: "data:text/plain,hi"},
+ {url: "http://example.com", enhancedImageURI: "http/bork:eh"},
+ ]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ let links = yield fetchData();
+ do_check_eq(links.length, 2);
+
+ // The only remaining enhancedImages should be http and https and data
+ do_check_eq(links[0].enhancedImageURI, data["directory"][3].enhancedImageURI);
+ do_check_eq(links[1].enhancedImageURI, data["directory"][5].enhancedImageURI);
+});
+
+add_task(function* test_DirectoryLinksProvider_getEnhancedLink() {
+ let data = {"enhanced": [
+ {url: "http://example.net", enhancedImageURI: "data:,net1"},
+ {url: "http://example.com", enhancedImageURI: "data:,com1"},
+ {url: "http://example.com", enhancedImageURI: "data:,com2"},
+ ]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ let links = yield fetchData();
+ do_check_eq(links.length, 0); // There are no directory links.
+
+ function checkEnhanced(url, image) {
+ let enhanced = DirectoryLinksProvider.getEnhancedLink({url: url});
+ do_check_eq(enhanced && enhanced.enhancedImageURI, image);
+ }
+
+ // Get the expected image for the same site
+ checkEnhanced("http://example.net/", "data:,net1");
+ checkEnhanced("http://example.net/path", "data:,net1");
+ checkEnhanced("https://www.example.net/", "data:,net1");
+ checkEnhanced("https://www3.example.net/", "data:,net1");
+
+ // Get the image of the last entry
+ checkEnhanced("http://example.com", "data:,com2");
+
+ // Get the inline enhanced image
+ let inline = DirectoryLinksProvider.getEnhancedLink({
+ url: "http://example.com/echo",
+ enhancedImageURI: "data:,echo",
+ });
+ do_check_eq(inline.enhancedImageURI, "data:,echo");
+ do_check_eq(inline.url, "http://example.com/echo");
+
+ // Undefined for not enhanced
+ checkEnhanced("http://sub.example.net/", undefined);
+ checkEnhanced("http://example.org", undefined);
+ checkEnhanced("http://localhost", undefined);
+ checkEnhanced("http://127.0.0.1", undefined);
+
+ // Make sure old data is not cached
+ data = {"enhanced": [
+ {url: "http://example.com", enhancedImageURI: "data:,fresh"},
+ ]};
+ dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ links = yield fetchData();
+ do_check_eq(links.length, 0); // There are no directory links.
+ checkEnhanced("http://example.net", undefined);
+ checkEnhanced("http://example.com", "data:,fresh");
+});
+
+add_task(function* test_DirectoryLinksProvider_enhancedURIs() {
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = () => true;
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ let data = {
+ "suggested": [
+ {url: "http://example.net", enhancedImageURI: "data:,net1", title:"SuggestedTitle", adgroup_name: "Test", frecent_sites: ["test.com"]}
+ ],
+ "directory": [
+ {url: "http://example.net", enhancedImageURI: "data:,net2", title:"DirectoryTitle"}
+ ]
+ };
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+ // Wait for links to get loaded
+ let gLinks = NewTabUtils.links;
+ gLinks.addProvider(DirectoryLinksProvider);
+ gLinks.populateCache();
+ yield new Promise(resolve => {
+ NewTabUtils.allPages.register({
+ observe: _ => _,
+ update() {
+ NewTabUtils.allPages.unregister(this);
+ resolve();
+ }
+ });
+ });
+
+ // Check that we've saved the directory tile.
+ let links = yield fetchData();
+ do_check_eq(links.length, 1);
+ do_check_eq(links[0].title, "DirectoryTitle");
+ do_check_eq(links[0].enhancedImageURI, "data:,net2");
+
+ // Check that the suggested tile with the same URL replaces the directory tile.
+ links = gLinks.getLinks();
+ do_check_eq(links.length, 1);
+ do_check_eq(links[0].title, "SuggestedTitle");
+ do_check_eq(links[0].enhancedImageURI, "data:,net1");
+
+ // Cleanup.
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+ gLinks.removeProvider(DirectoryLinksProvider);
+});
+
+add_task(function test_DirectoryLinksProvider_setDefaultEnhanced() {
+ function checkDefault(expected) {
+ Services.prefs.clearUserPref(kNewtabEnhancedPref);
+ do_check_eq(Services.prefs.getBoolPref(kNewtabEnhancedPref), expected);
+ }
+
+ // Use the default donottrack prefs (enabled = false)
+ Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
+ checkDefault(true);
+
+ // Turn on DNT - no track
+ Services.prefs.setBoolPref("privacy.donottrackheader.enabled", true);
+ checkDefault(false);
+
+ // Turn off DNT header
+ Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
+ checkDefault(true);
+
+ // Clean up
+ Services.prefs.clearUserPref("privacy.donottrackheader.value");
+});
+
+add_task(function* test_timeSensetiveSuggestedTiles() {
+ // make tile json with start and end dates
+ let testStartTime = Date.now();
+ // start date is now + 1 seconds
+ let startDate = new Date(testStartTime + 1000);
+ // end date is now + 3 seconds
+ let endDate = new Date(testStartTime + 3000);
+ let suggestedTile = Object.assign({
+ time_limits: {
+ start: startDate.toISOString(),
+ end: endDate.toISOString(),
+ }
+ }, suggestedTile1);
+
+ // Initial setup
+ let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
+ let data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ let testObserver = new TestTimingRun();
+ DirectoryLinksProvider.addObserver(testObserver);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ let links = yield fetchData();
+
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = function(site) {
+ return topSites.indexOf(site) >= 0;
+ }
+
+ let origGetProviderLinks = NewTabUtils.getProviderLinks;
+ NewTabUtils.getProviderLinks = function(provider) {
+ return links;
+ }
+
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ do_check_eq(DirectoryLinksProvider._updateSuggestedTile(), undefined);
+
+ // this tester will fire twice: when start limit is reached and when tile link
+ // is removed upon end of the campaign, in which case deleteFlag will be set
+ function TestTimingRun() {
+ this.promise = new Promise(resolve => {
+ this.onLinkChanged = (directoryLinksProvider, link, ignoreFlag, deleteFlag) => {
+ // if we are not deleting, add link to links, so we can catch it's removal
+ if (!deleteFlag) {
+ links.unshift(link);
+ }
+
+ isIdentical([...DirectoryLinksProvider._topSitesWithSuggestedLinks], ["hrblock.com", "1040.com"]);
+ do_check_eq(link.frecency, SUGGESTED_FRECENCY);
+ do_check_eq(link.type, "affiliate");
+ do_check_eq(link.url, suggestedTile.url);
+ let timeDelta = Date.now() - testStartTime;
+ if (!deleteFlag) {
+ // this is start timeout corresponding to campaign start
+ // a seconds must pass and targetedSite must be set
+ do_print("TESTING START timeDelta: " + timeDelta);
+ do_check_true(timeDelta >= 1000 / 2); // check for at least half time
+ do_check_eq(link.targetedSite, "hrblock.com");
+ do_check_true(DirectoryLinksProvider._campaignTimeoutID);
+ }
+ else {
+ // this is the campaign end timeout, so 3 seconds must pass
+ // and timeout should be cleared
+ do_print("TESTING END timeDelta: " + timeDelta);
+ do_check_true(timeDelta >= 3000 / 2); // check for at least half time
+ do_check_false(link.targetedSite);
+ do_check_false(DirectoryLinksProvider._campaignTimeoutID);
+ resolve();
+ }
+ };
+ });
+ }
+
+ // _updateSuggestedTile() is called when fetching directory links.
+ yield testObserver.promise;
+ DirectoryLinksProvider.removeObserver(testObserver);
+
+ // shoudl suggest nothing
+ do_check_eq(DirectoryLinksProvider._updateSuggestedTile(), undefined);
+
+ // set links back to contain directory tile only
+ links.shift();
+
+ // drop the end time - we should pick up the tile
+ suggestedTile.time_limits.end = null;
+ data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
+ dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ // redownload json and getLinks to force time recomputation
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, dataURI);
+
+ // ensure that there's a link returned by _updateSuggestedTile and no timeout
+ let deferred = Promise.defer();
+ DirectoryLinksProvider.getLinks(() => {
+ let link = DirectoryLinksProvider._updateSuggestedTile();
+ // we should have a suggested tile and no timeout
+ do_check_eq(link.type, "affiliate");
+ do_check_eq(link.url, suggestedTile.url);
+ do_check_false(DirectoryLinksProvider._campaignTimeoutID);
+ deferred.resolve();
+ });
+ yield deferred.promise;
+
+ // repeat the test for end time only
+ suggestedTile.time_limits.start = null;
+ suggestedTile.time_limits.end = (new Date(Date.now() + 3000)).toISOString();
+
+ data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
+ dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ // redownload json and call getLinks() to force time recomputation
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, dataURI);
+
+ // ensure that there's a link returned by _updateSuggestedTile and timeout set
+ deferred = Promise.defer();
+ DirectoryLinksProvider.getLinks(() => {
+ let link = DirectoryLinksProvider._updateSuggestedTile();
+ // we should have a suggested tile and timeout set
+ do_check_eq(link.type, "affiliate");
+ do_check_eq(link.url, suggestedTile.url);
+ do_check_true(DirectoryLinksProvider._campaignTimeoutID);
+ DirectoryLinksProvider._clearCampaignTimeout();
+ deferred.resolve();
+ });
+ yield deferred.promise;
+
+ // Cleanup
+ yield promiseCleanDirectoryLinksProvider();
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ NewTabUtils.getProviderLinks = origGetProviderLinks;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+});
+
+add_task(function test_setupStartEndTime() {
+ let currentTime = Date.now();
+ let dt = new Date(currentTime);
+ let link = {
+ time_limits: {
+ start: dt.toISOString()
+ }
+ };
+
+ // test ISO translation
+ DirectoryLinksProvider._setupStartEndTime(link);
+ do_check_eq(link.startTime, currentTime);
+
+ // test localtime translation
+ let shiftedDate = new Date(currentTime - dt.getTimezoneOffset()*60*1000);
+ link.time_limits.start = shiftedDate.toISOString().replace(/Z$/, "");
+
+ DirectoryLinksProvider._setupStartEndTime(link);
+ do_check_eq(link.startTime, currentTime);
+
+ // throw some garbage into date string
+ delete link.startTime;
+ link.time_limits.start = "no date"
+ DirectoryLinksProvider._setupStartEndTime(link);
+ do_check_false(link.startTime);
+
+ link.time_limits.start = "2015-99999-01T00:00:00"
+ DirectoryLinksProvider._setupStartEndTime(link);
+ do_check_false(link.startTime);
+
+ link.time_limits.start = "20150501T00:00:00"
+ DirectoryLinksProvider._setupStartEndTime(link);
+ do_check_false(link.startTime);
+});
+
+add_task(function* test_DirectoryLinksProvider_frequencyCapSetup() {
+ yield promiseSetupDirectoryLinksProvider();
+ yield DirectoryLinksProvider.init();
+
+ yield promiseCleanDirectoryLinksProvider();
+ yield DirectoryLinksProvider._readFrequencyCapFile();
+ isIdentical(DirectoryLinksProvider._frequencyCaps, {});
+
+ // setup few links
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "1",
+ });
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "2",
+ frequency_caps: {daily: 1, total: 2}
+ });
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "3",
+ frequency_caps: {total: 2}
+ });
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "4",
+ frequency_caps: {daily: 1}
+ });
+ let freqCapsObject = DirectoryLinksProvider._frequencyCaps;
+ let capObject = freqCapsObject["1"];
+ let defaultDaily = capObject.dailyCap;
+ let defaultTotal = capObject.totalCap;
+ // check if we have defaults set
+ do_check_true(capObject.dailyCap > 0);
+ do_check_true(capObject.totalCap > 0);
+ // check if defaults are properly handled
+ do_check_eq(freqCapsObject["2"].dailyCap, 1);
+ do_check_eq(freqCapsObject["2"].totalCap, 2);
+ do_check_eq(freqCapsObject["3"].dailyCap, defaultDaily);
+ do_check_eq(freqCapsObject["3"].totalCap, 2);
+ do_check_eq(freqCapsObject["4"].dailyCap, 1);
+ do_check_eq(freqCapsObject["4"].totalCap, defaultTotal);
+
+ // write object to file
+ yield DirectoryLinksProvider._writeFrequencyCapFile();
+ // empty out freqCapsObject and read file back
+ DirectoryLinksProvider._frequencyCaps = {};
+ yield DirectoryLinksProvider._readFrequencyCapFile();
+ // re-ran tests - they should all pass
+ do_check_eq(freqCapsObject["2"].dailyCap, 1);
+ do_check_eq(freqCapsObject["2"].totalCap, 2);
+ do_check_eq(freqCapsObject["3"].dailyCap, defaultDaily);
+ do_check_eq(freqCapsObject["3"].totalCap, 2);
+ do_check_eq(freqCapsObject["4"].dailyCap, 1);
+ do_check_eq(freqCapsObject["4"].totalCap, defaultTotal);
+
+ // wait a second and prune frequency caps
+ yield new Promise(resolve => {
+ setTimeout(resolve, 1100);
+ });
+
+ // update one link and create another
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "3",
+ frequency_caps: {daily: 1, total: 2}
+ });
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "7",
+ frequency_caps: {daily: 1, total: 2}
+ });
+ // now prune the ones that have been in the object longer than 1 second
+ DirectoryLinksProvider._pruneFrequencyCapUrls(1000);
+ // make sure all keys but "3" and "7" are deleted
+ Object.keys(DirectoryLinksProvider._frequencyCaps).forEach(key => {
+ do_check_true(key == "3" || key == "7");
+ });
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_getFrequencyCapLogic() {
+ yield promiseSetupDirectoryLinksProvider();
+ yield DirectoryLinksProvider.init();
+
+ // setup suggested links
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "1",
+ frequency_caps: {daily: 2, total: 4}
+ });
+
+ do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
+ // exhaust daily views
+ DirectoryLinksProvider._addFrequencyCapView("1")
+ do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
+ DirectoryLinksProvider._addFrequencyCapView("1")
+ do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("1"));
+
+ // now step into the furture
+ let _wasTodayOrig = DirectoryLinksProvider._wasToday;
+ DirectoryLinksProvider._wasToday = function () { return false; }
+ // exhaust total views
+ DirectoryLinksProvider._addFrequencyCapView("1")
+ do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
+ DirectoryLinksProvider._addFrequencyCapView("1")
+ // reached totalViews 4, should return false
+ do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("1"));
+
+ // add more views by updating configuration
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "1",
+ frequency_caps: {daily: 5, total: 10}
+ });
+ // should be true, since we have more total views
+ do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("1"));
+
+ // set click flag
+ DirectoryLinksProvider._setFrequencyCapClick("1");
+ // always false after click
+ do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("1"));
+
+ // use unknown urls and ensure nothing breaks
+ DirectoryLinksProvider._addFrequencyCapView("nosuch.url");
+ DirectoryLinksProvider._setFrequencyCapClick("nosuch.url");
+ // testing unknown url should always return false
+ do_check_false(DirectoryLinksProvider._testFrequencyCapLimits("nosuch.url"));
+
+ // reset _wasToday back to original function
+ DirectoryLinksProvider._wasToday = _wasTodayOrig;
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_getFrequencyCapReportSiteAction() {
+ yield promiseSetupDirectoryLinksProvider();
+ yield DirectoryLinksProvider.init();
+
+ // setup suggested links
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "bar.com",
+ frequency_caps: {daily: 2, total: 4}
+ });
+
+ do_check_true(DirectoryLinksProvider._testFrequencyCapLimits("bar.com"));
+ // report site action
+ yield DirectoryLinksProvider.reportSitesAction([{
+ link: {
+ targetedSite: "foo.com",
+ url: "bar.com"
+ },
+ isPinned: function() { return false; },
+ }], "view", 0);
+
+ // read file content and ensure that view counters are updated
+ let data = yield readJsonFile(DirectoryLinksProvider._frequencyCapFilePath);
+ do_check_eq(data["bar.com"].dailyViews, 1);
+ do_check_eq(data["bar.com"].totalViews, 1);
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_DirectoryLinksProvider_ClickRemoval() {
+ yield promiseSetupDirectoryLinksProvider();
+ yield DirectoryLinksProvider.init();
+ let landingUrl = "http://foo.com";
+
+ // setup suggested links
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: landingUrl,
+ frequency_caps: {daily: 2, total: 4}
+ });
+
+ // add views
+ DirectoryLinksProvider._addFrequencyCapView(landingUrl)
+ DirectoryLinksProvider._addFrequencyCapView(landingUrl)
+ // make a click
+ DirectoryLinksProvider._setFrequencyCapClick(landingUrl);
+
+ // views must be 2 and click must be set
+ do_check_eq(DirectoryLinksProvider._frequencyCaps[landingUrl].totalViews, 2);
+ do_check_true(DirectoryLinksProvider._frequencyCaps[landingUrl].clicked);
+
+ // now insert a visit into places
+ yield new Promise(resolve => {
+ PlacesUtils.asyncHistory.updatePlaces(
+ {
+ uri: NetUtil.newURI(landingUrl),
+ title: "HELLO",
+ visits: [{
+ visitDate: Date.now()*1000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ }]
+ },
+ {
+ handleError: function () { do_check_true(false); },
+ handleResult: function () {},
+ handleCompletion: function () { resolve(); }
+ }
+ );
+ });
+
+ function UrlDeletionTester() {
+ this.promise = new Promise(resolve => {
+ this.onDeleteURI = (directoryLinksProvider, link) => {
+ resolve();
+ };
+ this.onClearHistory = (directoryLinksProvider) => {
+ resolve();
+ };
+ });
+ }
+
+ let testObserver = new UrlDeletionTester();
+ DirectoryLinksProvider.addObserver(testObserver);
+
+ PlacesUtils.bhistory.removePage(NetUtil.newURI(landingUrl));
+ yield testObserver.promise;
+ DirectoryLinksProvider.removeObserver(testObserver);
+ // views must be 2 and click should not exist
+ do_check_eq(DirectoryLinksProvider._frequencyCaps[landingUrl].totalViews, 2);
+ do_check_false(DirectoryLinksProvider._frequencyCaps[landingUrl].hasOwnProperty("clicked"));
+
+ // verify that disk written data is kosher
+ let data = yield readJsonFile(DirectoryLinksProvider._frequencyCapFilePath);
+ do_check_eq(data[landingUrl].totalViews, 2);
+ do_check_false(data[landingUrl].hasOwnProperty("clicked"));
+
+ // now test clear history
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: landingUrl,
+ frequency_caps: {daily: 2, total: 4}
+ });
+ DirectoryLinksProvider._updateFrequencyCapSettings({
+ url: "http://bar.com",
+ frequency_caps: {daily: 2, total: 4}
+ });
+
+ DirectoryLinksProvider._setFrequencyCapClick(landingUrl);
+ DirectoryLinksProvider._setFrequencyCapClick("http://bar.com");
+ // both tiles must have clicked
+ do_check_true(DirectoryLinksProvider._frequencyCaps[landingUrl].clicked);
+ do_check_true(DirectoryLinksProvider._frequencyCaps["http://bar.com"].clicked);
+
+ testObserver = new UrlDeletionTester();
+ DirectoryLinksProvider.addObserver(testObserver);
+ yield PlacesTestUtils.clearHistory();
+
+ yield testObserver.promise;
+ DirectoryLinksProvider.removeObserver(testObserver);
+ // no clicks should remain in the cap object
+ do_check_false(DirectoryLinksProvider._frequencyCaps[landingUrl].hasOwnProperty("clicked"));
+ do_check_false(DirectoryLinksProvider._frequencyCaps["http://bar.com"].hasOwnProperty("clicked"));
+
+ // verify that disk written data is kosher
+ data = yield readJsonFile(DirectoryLinksProvider._frequencyCapFilePath);
+ do_check_false(data[landingUrl].hasOwnProperty("clicked"));
+ do_check_false(data["http://bar.com"].hasOwnProperty("clicked"));
+
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function test_DirectoryLinksProvider_anonymous() {
+ do_check_true(DirectoryLinksProvider._newXHR().mozAnon);
+});
+
+add_task(function* test_sanitizeExplanation() {
+ // Note: this is a basic test to ensure we applied sanitization to the link explanation.
+ // Full testing for appropriate sanitization is done in parser/xml/test/unit/test_sanitizer.js.
+ let data = {"suggested": [suggestedTile5]};
+ let dataURI = 'data:application/json,' + encodeURIComponent(JSON.stringify(data));
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ yield fetchData();
+
+ let suggestedSites = [...DirectoryLinksProvider._suggestedLinks.keys()];
+ do_check_eq(suggestedSites.indexOf("eviltarget.com"), 0);
+ do_check_eq(suggestedSites.length, 1);
+
+ let suggestedLink = [...DirectoryLinksProvider._suggestedLinks.get(suggestedSites[0]).values()][0];
+ do_check_eq(suggestedLink.explanation, "This is an evil tile X muhahaha");
+ do_check_eq(suggestedLink.targetedName, "WE ARE EVIL ");
+});
+
+add_task(function* test_inadjecentSites() {
+ let suggestedTile = Object.assign({
+ check_inadjacency: true
+ }, suggestedTile1);
+
+ // Initial setup
+ let topSites = ["1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
+ let data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ let testObserver = new TestFirstRun();
+ DirectoryLinksProvider.addObserver(testObserver);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ let links = yield fetchData();
+
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = function(site) {
+ return topSites.indexOf(site) >= 0;
+ }
+
+ let origGetProviderLinks = NewTabUtils.getProviderLinks;
+ NewTabUtils.getProviderLinks = function(provider) {
+ return links;
+ }
+
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => {
+ origCurrentTopSiteCount.apply(DirectoryLinksProvider);
+ return 8;
+ };
+
+ // store oroginal inadjacent sites url
+ let origInadjacentSitesUrl = DirectoryLinksProvider._inadjacentSitesUrl;
+
+ // loading inadjacent sites list function
+ function setInadjacentSites(sites) {
+ let badSiteB64 = [];
+ sites.forEach(site => {
+ badSiteB64.push(DirectoryLinksProvider._generateHash(site));
+ });
+ let theList = {"domains": badSiteB64};
+ let uri = 'data:application/json,' + JSON.stringify(theList);
+ DirectoryLinksProvider._inadjacentSitesUrl = uri;
+ return DirectoryLinksProvider._loadInadjacentSites();
+ }
+
+ // setup gLinks loader
+ let gLinks = NewTabUtils.links;
+ gLinks.addProvider(DirectoryLinksProvider);
+
+ function updateNewTabCache() {
+ gLinks.populateCache();
+ return new Promise(resolve => {
+ NewTabUtils.allPages.register({
+ observe: _ => _,
+ update() {
+ NewTabUtils.allPages.unregister(this);
+ resolve();
+ }
+ });
+ });
+ }
+
+ // no suggested file
+ do_check_eq(DirectoryLinksProvider._updateSuggestedTile(), undefined);
+ // _avoidInadjacentSites should be set, since link.check_inadjacency is on
+ do_check_true(DirectoryLinksProvider._avoidInadjacentSites);
+ // make sure example.com is included in inadjacent sites list
+ do_check_true(DirectoryLinksProvider._isInadjacentLink({baseDomain: "example.com"}));
+
+ function TestFirstRun() {
+ this.promise = new Promise(resolve => {
+ this.onLinkChanged = (directoryLinksProvider, link) => {
+ do_check_eq(link.url, suggestedTile.url);
+ do_check_eq(link.type, "affiliate");
+ resolve();
+ };
+ });
+ }
+
+ // Test first call to '_updateSuggestedTile()', called when fetching directory links.
+ yield testObserver.promise;
+ DirectoryLinksProvider.removeObserver(testObserver);
+
+ // update newtab cache
+ yield updateNewTabCache();
+ // this should have set
+ do_check_true(DirectoryLinksProvider._avoidInadjacentSites);
+
+ // there should be siggested link
+ let link = DirectoryLinksProvider._updateSuggestedTile();
+ do_check_eq(link.url, "http://turbotax.com");
+ // and it should have avoidInadjacentSites flag
+ do_check_true(link.check_inadjacency);
+
+ // make someothersite.com inadjacent
+ yield setInadjacentSites(["someothersite.com"]);
+
+ // there should be no suggested link
+ link = DirectoryLinksProvider._updateSuggestedTile();
+ do_check_false(link);
+ do_check_true(DirectoryLinksProvider._newTabHasInadjacentSite);
+
+ // _handleLinkChanged must return true on inadjacent site
+ do_check_true(DirectoryLinksProvider._handleLinkChanged({
+ url: "http://someothersite.com",
+ type: "history",
+ }));
+ // _handleLinkChanged must return false on ok site
+ do_check_false(DirectoryLinksProvider._handleLinkChanged({
+ url: "http://foobar.com",
+ type: "history",
+ }));
+
+ // change inadjacent list to sites not on newtab page
+ yield setInadjacentSites(["foo.com", "bar.com"]);
+
+ link = DirectoryLinksProvider._updateSuggestedTile();
+ // we should now have a link
+ do_check_true(link);
+ do_check_eq(link.url, "http://turbotax.com");
+
+ // make newtab offending again
+ yield setInadjacentSites(["someothersite.com", "foo.com"]);
+ // there should be no suggested link
+ link = DirectoryLinksProvider._updateSuggestedTile();
+ do_check_false(link);
+ do_check_true(DirectoryLinksProvider._newTabHasInadjacentSite);
+
+ // remove avoidInadjacentSites flag from suggested tile and reload json
+ delete suggestedTile.check_inadjacency;
+ data = {"suggested": [suggestedTile], "directory": [someOtherSite]};
+ dataURI = 'data:application/json,' + JSON.stringify(data);
+ yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, dataURI);
+ yield fetchData();
+
+ // inadjacent checking should be disabled
+ do_check_false(DirectoryLinksProvider._avoidInadjacentSites);
+ link = DirectoryLinksProvider._updateSuggestedTile();
+ do_check_true(link);
+ do_check_eq(link.url, "http://turbotax.com");
+ do_check_false(DirectoryLinksProvider._newTabHasInadjacentSite);
+
+ // _handleLinkChanged should return false now, even if newtab has bad site
+ do_check_false(DirectoryLinksProvider._handleLinkChanged({
+ url: "http://someothersite.com",
+ type: "history",
+ }));
+
+ // test _isInadjacentLink
+ do_check_true(DirectoryLinksProvider._isInadjacentLink({baseDomain: "someothersite.com"}));
+ do_check_false(DirectoryLinksProvider._isInadjacentLink({baseDomain: "bar.com"}));
+ do_check_true(DirectoryLinksProvider._isInadjacentLink({url: "http://www.someothersite.com"}));
+ do_check_false(DirectoryLinksProvider._isInadjacentLink({url: "http://www.bar.com"}));
+ // try to crash _isInadjacentLink
+ do_check_false(DirectoryLinksProvider._isInadjacentLink({baseDomain: ""}));
+ do_check_false(DirectoryLinksProvider._isInadjacentLink({url: ""}));
+ do_check_false(DirectoryLinksProvider._isInadjacentLink({url: "http://localhost:8081/"}));
+ do_check_false(DirectoryLinksProvider._isInadjacentLink({url: "abracodabra"}));
+ do_check_false(DirectoryLinksProvider._isInadjacentLink({}));
+
+ // test _checkForInadjacentSites
+ do_check_true(DirectoryLinksProvider._checkForInadjacentSites());
+
+ // Cleanup
+ gLinks.removeProvider(DirectoryLinksProvider);
+ DirectoryLinksProvider._inadjacentSitesUrl = origInadjacentSitesUrl;
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ NewTabUtils.getProviderLinks = origGetProviderLinks;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+ yield promiseCleanDirectoryLinksProvider();
+});
+
+add_task(function* test_blockSuggestedTiles() {
+ // Initial setup
+ let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
+ let data = {"suggested": [suggestedTile1, suggestedTile2, suggestedTile3], "directory": [someOtherSite]};
+ let dataURI = 'data:application/json,' + JSON.stringify(data);
+
+ yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+ let links = yield fetchData();
+
+ let origIsTopPlacesSite = NewTabUtils.isTopPlacesSite;
+ NewTabUtils.isTopPlacesSite = function(site) {
+ return topSites.indexOf(site) >= 0;
+ }
+
+ let origGetProviderLinks = NewTabUtils.getProviderLinks;
+ NewTabUtils.getProviderLinks = function(provider) {
+ return links;
+ }
+
+ let origCurrentTopSiteCount = DirectoryLinksProvider._getCurrentTopSiteCount;
+ DirectoryLinksProvider._getCurrentTopSiteCount = () => 8;
+
+ // load the links
+ yield new Promise(resolve => {
+ DirectoryLinksProvider.getLinks(resolve);
+ });
+
+ // ensure that tile is suggested
+ let suggestedLink = DirectoryLinksProvider._updateSuggestedTile();
+ do_check_true(suggestedLink.frecent_sites);
+
+ // block suggested tile in a regular way
+ DirectoryLinksProvider.reportSitesAction([{
+ isPinned: function() { return false; },
+ link: Object.assign({frecency: 1000}, suggestedLink)
+ }], "block", 0);
+
+ // suggested tile still must be recommended
+ suggestedLink = DirectoryLinksProvider._updateSuggestedTile();
+ do_check_true(suggestedLink.frecent_sites);
+
+ // timestamp suggested_block in the frequency cap object
+ DirectoryLinksProvider.handleSuggestedTileBlock();
+ // no more recommendations should be seen
+ do_check_eq(DirectoryLinksProvider._updateSuggestedTile(), undefined);
+
+ // move lastUpdated for suggested tile into the past
+ DirectoryLinksProvider._frequencyCaps["ignore://suggested_block"].lastUpdated = Date.now() - 25*60*60*1000;
+ // ensure that suggested tile updates again
+ suggestedLink = DirectoryLinksProvider._updateSuggestedTile();
+ do_check_true(suggestedLink.frecent_sites);
+
+ // Cleanup
+ yield promiseCleanDirectoryLinksProvider();
+ NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
+ NewTabUtils.getProviderLinks = origGetProviderLinks;
+ DirectoryLinksProvider._getCurrentTopSiteCount = origCurrentTopSiteCount;
+});
diff --git a/browser/modules/test/xpcshell/test_LaterRun.js b/browser/modules/test/xpcshell/test_LaterRun.js
new file mode 100644
index 000000000..7b45c7cd5
--- /dev/null
+++ b/browser/modules/test/xpcshell/test_LaterRun.js
@@ -0,0 +1,138 @@
+"use strict";
+
+const kEnabledPref = "browser.laterrun.enabled";
+const kPagePrefRoot = "browser.laterrun.pages.";
+const kSessionCountPref = "browser.laterrun.bookkeeping.sessionCount";
+const kProfileCreationTime = "browser.laterrun.bookkeeping.profileCreationTime";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/LaterRun.jsm");
+
+Services.prefs.setBoolPref(kEnabledPref, true);
+Components.utils.import("resource://testing-common/AppInfo.jsm");
+updateAppInfo();
+
+add_task(function* test_page_applies() {
+ Services.prefs.setCharPref(kPagePrefRoot + "test_LaterRun_unittest.url", "https://www.mozilla.org/%VENDOR%/%NAME%/%ID%/%VERSION%/");
+ Services.prefs.setIntPref(kPagePrefRoot + "test_LaterRun_unittest.minimumHoursSinceInstall", 10);
+ Services.prefs.setIntPref(kPagePrefRoot + "test_LaterRun_unittest.minimumSessionCount", 3);
+
+ let pages = LaterRun.readPages();
+ // We have to filter the pages because it's possible Firefox ships with other URLs
+ // that get included in this test.
+ pages = pages.filter(page => page.pref == kPagePrefRoot + "test_LaterRun_unittest.");
+ Assert.equal(pages.length, 1, "Got 1 page");
+ let page = pages[0];
+ Assert.equal(page.pref, kPagePrefRoot + "test_LaterRun_unittest.", "Should know its own pref");
+ Assert.equal(page.minimumHoursSinceInstall, 10, "Needs to have 10 hours since install");
+ Assert.equal(page.minimumSessionCount, 3, "Needs to have 3 sessions");
+ Assert.equal(page.requireBoth, false, "Either requirement is enough");
+ let expectedURL = "https://www.mozilla.org/" +
+ Services.appinfo.vendor + "/" +
+ Services.appinfo.name + "/" +
+ Services.appinfo.ID + "/" +
+ Services.appinfo.version + "/";
+ Assert.equal(page.url, expectedURL, "URL is stored correctly");
+
+ Assert.ok(page.applies({hoursSinceInstall: 1, sessionCount: 3}),
+ "Applies when session count has been met.");
+ Assert.ok(page.applies({hoursSinceInstall: 1, sessionCount: 4}),
+ "Applies when session count has been exceeded.");
+ Assert.ok(page.applies({hoursSinceInstall: 10, sessionCount: 2}),
+ "Applies when total session time has been met.");
+ Assert.ok(page.applies({hoursSinceInstall: 20, sessionCount: 2}),
+ "Applies when total session time has been exceeded.");
+ Assert.ok(page.applies({hoursSinceInstall: 10, sessionCount: 3}),
+ "Applies when both time and session count have been met.");
+ Assert.ok(!page.applies({hoursSinceInstall: 1, sessionCount: 1}),
+ "Does not apply when neither time and session count have been met.");
+
+ page.requireBoth = true;
+
+ Assert.ok(!page.applies({hoursSinceInstall: 1, sessionCount: 3}),
+ "Does not apply when only session count has been met.");
+ Assert.ok(!page.applies({hoursSinceInstall: 1, sessionCount: 4}),
+ "Does not apply when only session count has been exceeded.");
+ Assert.ok(!page.applies({hoursSinceInstall: 10, sessionCount: 2}),
+ "Does not apply when only total session time has been met.");
+ Assert.ok(!page.applies({hoursSinceInstall: 20, sessionCount: 2}),
+ "Does not apply when only total session time has been exceeded.");
+ Assert.ok(page.applies({hoursSinceInstall: 10, sessionCount: 3}),
+ "Applies when both time and session count have been met.");
+ Assert.ok(!page.applies({hoursSinceInstall: 1, sessionCount: 1}),
+ "Does not apply when neither time and session count have been met.");
+
+ // Check that pages that have run never apply:
+ Services.prefs.setBoolPref(kPagePrefRoot + "test_LaterRun_unittest.hasRun", true);
+ page.requireBoth = false;
+
+ Assert.ok(!page.applies({hoursSinceInstall: 1, sessionCount: 3}),
+ "Does not apply when page has already run (sessionCount equal).");
+ Assert.ok(!page.applies({hoursSinceInstall: 1, sessionCount: 4}),
+ "Does not apply when page has already run (sessionCount exceeding).");
+ Assert.ok(!page.applies({hoursSinceInstall: 10, sessionCount: 2}),
+ "Does not apply when page has already run (hoursSinceInstall equal).");
+ Assert.ok(!page.applies({hoursSinceInstall: 20, sessionCount: 2}),
+ "Does not apply when page has already run (hoursSinceInstall exceeding).");
+ Assert.ok(!page.applies({hoursSinceInstall: 10, sessionCount: 3}),
+ "Does not apply when page has already run (both criteria equal).");
+ Assert.ok(!page.applies({hoursSinceInstall: 1, sessionCount: 1}),
+ "Does not apply when page has already run (both criteria insufficient anyway).");
+
+ clearAllPagePrefs();
+});
+
+add_task(function* test_get_URL() {
+ Services.prefs.setIntPref(kProfileCreationTime, Math.floor((Date.now() - 11 * 60 * 60 * 1000) / 1000));
+ Services.prefs.setCharPref(kPagePrefRoot + "test_LaterRun_unittest.url", "https://www.mozilla.org/");
+ Services.prefs.setIntPref(kPagePrefRoot + "test_LaterRun_unittest.minimumHoursSinceInstall", 10);
+ Services.prefs.setIntPref(kPagePrefRoot + "test_LaterRun_unittest.minimumSessionCount", 3);
+ let pages = LaterRun.readPages();
+ // We have to filter the pages because it's possible Firefox ships with other URLs
+ // that get included in this test.
+ pages = pages.filter(page => page.pref == kPagePrefRoot + "test_LaterRun_unittest.");
+ Assert.equal(pages.length, 1, "Should only be 1 matching page");
+ let page = pages[0];
+ let url;
+ do {
+ url = LaterRun.getURL();
+ // We have to loop because it's possible Firefox ships with other URLs that get triggered by
+ // this test.
+ } while (url && url != "https://www.mozilla.org/");
+ Assert.equal(url, "https://www.mozilla.org/", "URL should be as expected when prefs are set.");
+ Assert.ok(Services.prefs.prefHasUserValue(kPagePrefRoot + "test_LaterRun_unittest.hasRun"), "Should have set pref");
+ Assert.ok(Services.prefs.getBoolPref(kPagePrefRoot + "test_LaterRun_unittest.hasRun"), "Should have set pref to true");
+ Assert.ok(page.hasRun, "Other page objects should know it has run, too.");
+
+ clearAllPagePrefs();
+});
+
+add_task(function* test_insecure_urls() {
+ Services.prefs.setCharPref(kPagePrefRoot + "test_LaterRun_unittest.url", "http://www.mozilla.org/");
+ Services.prefs.setIntPref(kPagePrefRoot + "test_LaterRun_unittest.minimumHoursSinceInstall", 10);
+ Services.prefs.setIntPref(kPagePrefRoot + "test_LaterRun_unittest.minimumSessionCount", 3);
+ let pages = LaterRun.readPages();
+ // We have to filter the pages because it's possible Firefox ships with other URLs
+ // that get triggered in this test.
+ pages = pages.filter(page => page.pref == kPagePrefRoot + "test_LaterRun_unittest.");
+ Assert.equal(pages.length, 0, "URL with non-https scheme should get ignored");
+ clearAllPagePrefs();
+});
+
+add_task(function* test_dynamic_pref_getter_setter() {
+ delete LaterRun._sessionCount;
+ Services.prefs.setIntPref(kSessionCountPref, 0);
+ Assert.equal(LaterRun.sessionCount, 0, "Should start at 0");
+
+ LaterRun.sessionCount++;
+ Assert.equal(LaterRun.sessionCount, 1, "Should increment.");
+ Assert.equal(Services.prefs.getIntPref(kSessionCountPref), 1, "Should update pref");
+});
+
+function clearAllPagePrefs() {
+ let allChangedPrefs = Services.prefs.getChildList(kPagePrefRoot);
+ for (let pref of allChangedPrefs) {
+ Services.prefs.clearUserPref(pref);
+ }
+}
+
diff --git a/browser/modules/test/xpcshell/test_SitePermissions.js b/browser/modules/test/xpcshell/test_SitePermissions.js
new file mode 100644
index 000000000..808d96599
--- /dev/null
+++ b/browser/modules/test/xpcshell/test_SitePermissions.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+Components.utils.import("resource:///modules/SitePermissions.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+add_task(function* testPermissionsListing() {
+ Assert.deepEqual(SitePermissions.listPermissions().sort(),
+ ["camera", "cookie", "desktop-notification", "geo", "image",
+ "indexedDB", "install", "microphone", "popup", "screen"],
+ "Correct list of all permissions");
+});
+
+add_task(function* testGetAllByURI() {
+ // check that it returns an empty array on an invalid URI
+ // like a file URI, which doesn't support site permissions
+ let wrongURI = Services.io.newURI("file:///example.js", null, null)
+ Assert.deepEqual(SitePermissions.getAllByURI(wrongURI), []);
+
+ let uri = Services.io.newURI("https://example.com", null, null)
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
+
+ SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+ { id: "camera", state: SitePermissions.ALLOW }
+ ]);
+
+ SitePermissions.set(uri, "microphone", SitePermissions.SESSION);
+ SitePermissions.set(uri, "desktop-notification", SitePermissions.BLOCK);
+
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+ { id: "camera", state: SitePermissions.ALLOW },
+ { id: "microphone", state: SitePermissions.SESSION },
+ { id: "desktop-notification", state: SitePermissions.BLOCK }
+ ]);
+
+ SitePermissions.remove(uri, "microphone");
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), [
+ { id: "camera", state: SitePermissions.ALLOW },
+ { id: "desktop-notification", state: SitePermissions.BLOCK }
+ ]);
+
+ SitePermissions.remove(uri, "camera");
+ SitePermissions.remove(uri, "desktop-notification");
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
+
+ // XXX Bug 1303108 - Control Center should only show non-default permissions
+ SitePermissions.set(uri, "addon", SitePermissions.BLOCK);
+ Assert.deepEqual(SitePermissions.getAllByURI(uri), []);
+ SitePermissions.remove(uri, "addon");
+});
+
+add_task(function* testGetPermissionDetailsByURI() {
+ // check that it returns an empty array on an invalid URI
+ // like a file URI, which doesn't support site permissions
+ let wrongURI = Services.io.newURI("file:///example.js", null, null)
+ Assert.deepEqual(SitePermissions.getPermissionDetailsByURI(wrongURI), []);
+
+ let uri = Services.io.newURI("https://example.com", null, null)
+
+ SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
+ SitePermissions.set(uri, "cookie", SitePermissions.SESSION);
+ SitePermissions.set(uri, "popup", SitePermissions.BLOCK);
+
+ let permissions = SitePermissions.getPermissionDetailsByURI(uri);
+
+ let camera = permissions.find(({id}) => id === "camera");
+ Assert.deepEqual(camera, {
+ id: "camera",
+ label: "Use the Camera",
+ state: SitePermissions.ALLOW,
+ availableStates: [
+ { id: SitePermissions.UNKNOWN, label: "Always Ask" },
+ { id: SitePermissions.ALLOW, label: "Allow" },
+ { id: SitePermissions.BLOCK, label: "Block" },
+ ]
+ });
+
+ // check that removed permissions (State.UNKNOWN) are skipped
+ SitePermissions.remove(uri, "camera");
+ permissions = SitePermissions.getPermissionDetailsByURI(uri);
+
+ camera = permissions.find(({id}) => id === "camera");
+ Assert.equal(camera, undefined);
+
+ // check that different available state values are represented
+
+ let cookie = permissions.find(({id}) => id === "cookie");
+ Assert.deepEqual(cookie, {
+ id: "cookie",
+ label: "Set Cookies",
+ state: SitePermissions.SESSION,
+ availableStates: [
+ { id: SitePermissions.ALLOW, label: "Allow" },
+ { id: SitePermissions.SESSION, label: "Allow for Session" },
+ { id: SitePermissions.BLOCK, label: "Block" },
+ ]
+ });
+
+ let popup = permissions.find(({id}) => id === "popup");
+ Assert.deepEqual(popup, {
+ id: "popup",
+ label: "Open Pop-up Windows",
+ state: SitePermissions.BLOCK,
+ availableStates: [
+ { id: SitePermissions.ALLOW, label: "Allow" },
+ { id: SitePermissions.BLOCK, label: "Block" },
+ ]
+ });
+
+ SitePermissions.remove(uri, "cookie");
+ SitePermissions.remove(uri, "popup");
+});
diff --git a/browser/modules/test/xpcshell/xpcshell.ini b/browser/modules/test/xpcshell/xpcshell.ini
new file mode 100644
index 000000000..28df9c4ed
--- /dev/null
+++ b/browser/modules/test/xpcshell/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head =
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_AttributionCode.js]
+skip-if = os != 'win'
+[test_DirectoryLinksProvider.js]
+[test_SitePermissions.js]
+[test_LaterRun.js]
diff --git a/browser/modules/webrtcUI.jsm b/browser/modules/webrtcUI.jsm
new file mode 100644
index 000000000..b24135bfc
--- /dev/null
+++ b/browser/modules/webrtcUI.jsm
@@ -0,0 +1,963 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["webrtcUI"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+this.webrtcUI = {
+ init: function () {
+ Services.obs.addObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished", false);
+
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.addMessageListener("webrtc:UpdatingIndicators", this);
+ ppmm.addMessageListener("webrtc:UpdateGlobalIndicators", this);
+ ppmm.addMessageListener("child-process-shutdown", this);
+
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("rtcpeer:Request", this);
+ mm.addMessageListener("rtcpeer:CancelRequest", this);
+ mm.addMessageListener("webrtc:Request", this);
+ mm.addMessageListener("webrtc:CancelRequest", this);
+ mm.addMessageListener("webrtc:UpdateBrowserIndicators", this);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished");
+
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.removeMessageListener("webrtc:UpdatingIndicators", this);
+ ppmm.removeMessageListener("webrtc:UpdateGlobalIndicators", this);
+
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ mm.removeMessageListener("rtcpeer:Request", this);
+ mm.removeMessageListener("rtcpeer:CancelRequest", this);
+ mm.removeMessageListener("webrtc:Request", this);
+ mm.removeMessageListener("webrtc:CancelRequest", this);
+ mm.removeMessageListener("webrtc:UpdateBrowserIndicators", this);
+
+ if (gIndicatorWindow) {
+ gIndicatorWindow.close();
+ gIndicatorWindow = null;
+ }
+ },
+
+ processIndicators: new Map(),
+
+ get showGlobalIndicator() {
+ for (let [, indicators] of this.processIndicators) {
+ if (indicators.showGlobalIndicator)
+ return true;
+ }
+ return false;
+ },
+
+ get showCameraIndicator() {
+ for (let [, indicators] of this.processIndicators) {
+ if (indicators.showCameraIndicator)
+ return true;
+ }
+ return false;
+ },
+
+ get showMicrophoneIndicator() {
+ for (let [, indicators] of this.processIndicators) {
+ if (indicators.showMicrophoneIndicator)
+ return true;
+ }
+ return false;
+ },
+
+ get showScreenSharingIndicator() {
+ let list = [""];
+ for (let [, indicators] of this.processIndicators) {
+ if (indicators.showScreenSharingIndicator)
+ list.push(indicators.showScreenSharingIndicator);
+ }
+
+ let precedence =
+ ["Screen", "Window", "Application", "Browser", ""];
+
+ list.sort((a, b) => { return precedence.indexOf(a) -
+ precedence.indexOf(b); });
+
+ return list[0];
+ },
+
+ _streams: [],
+ // The boolean parameters indicate which streams should be included in the result.
+ getActiveStreams: function(aCamera, aMicrophone, aScreen) {
+ return webrtcUI._streams.filter(aStream => {
+ let state = aStream.state;
+ return aCamera && state.camera ||
+ aMicrophone && state.microphone ||
+ aScreen && state.screen;
+ }).map(aStream => {
+ let state = aStream.state;
+ let types = {camera: state.camera, microphone: state.microphone,
+ screen: state.screen};
+ let browser = aStream.browser;
+ let browserWindow = browser.ownerGlobal;
+ let tab = browserWindow.gBrowser &&
+ browserWindow.gBrowser.getTabForBrowser(browser);
+ return {uri: state.documentURI, tab: tab, browser: browser, types: types};
+ });
+ },
+
+ swapBrowserForNotification: function(aOldBrowser, aNewBrowser) {
+ for (let stream of this._streams) {
+ if (stream.browser == aOldBrowser)
+ stream.browser = aNewBrowser;
+ }
+ },
+
+ forgetStreamsFromBrowser: function(aBrowser) {
+ this._streams = this._streams.filter(stream => stream.browser != aBrowser);
+ },
+
+ showSharingDoorhanger: function(aActiveStream, aType) {
+ let browserWindow = aActiveStream.browser.ownerGlobal;
+ if (aActiveStream.tab) {
+ browserWindow.gBrowser.selectedTab = aActiveStream.tab;
+ } else {
+ aActiveStream.browser.focus();
+ }
+ browserWindow.focus();
+ let identityBox = browserWindow.document.getElementById("identity-box");
+ if (AppConstants.platform == "macosx" && !Services.focus.activeWindow) {
+ browserWindow.addEventListener("activate", function onActivate() {
+ browserWindow.removeEventListener("activate", onActivate);
+ Services.tm.mainThread.dispatch(function() {
+ identityBox.click();
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport)
+ .activateApplication(true);
+ return;
+ }
+ identityBox.click();
+ },
+
+ updateMainActionLabel: function(aMenuList) {
+ let type = aMenuList.selectedItem.getAttribute("devicetype");
+ let document = aMenuList.ownerDocument;
+ document.getElementById("webRTC-all-windows-shared").hidden = type != "Screen";
+
+ // If we are also requesting audio in addition to screen sharing,
+ // always use a generic label.
+ if (!document.getElementById("webRTC-selectMicrophone").hidden)
+ type = "";
+
+ let bundle = document.defaultView.gNavigatorBundle;
+ let stringId = "getUserMedia.share" + (type || "SelectedItems") + ".label";
+ let popupnotification = aMenuList.parentNode.parentNode;
+ popupnotification.setAttribute("buttonlabel", bundle.getString(stringId));
+ },
+
+ receiveMessage: function(aMessage) {
+ switch (aMessage.name) {
+
+ // Add-ons can override stock permission behavior by doing:
+ //
+ // var stockReceiveMessage = webrtcUI.receiveMessage;
+ //
+ // webrtcUI.receiveMessage = function(aMessage) {
+ // switch (aMessage.name) {
+ // case "rtcpeer:Request": {
+ // // new code.
+ // break;
+ // ...
+ // default:
+ // return stockReceiveMessage.call(this, aMessage);
+ //
+ // Intercepting gUM and peerConnection requests should let an add-on
+ // limit PeerConnection activity with automatic rules and/or prompts
+ // in a sensible manner that avoids double-prompting in typical
+ // gUM+PeerConnection scenarios. For example:
+ //
+ // State Sample Action
+ // --------------------------------------------------------------
+ // No IP leaked yet + No gUM granted Warn user
+ // No IP leaked yet + gUM granted Avoid extra dialog
+ // No IP leaked yet + gUM request pending. Delay until gUM grant
+ // IP already leaked Too late to warn
+
+ case "rtcpeer:Request": {
+ // Always allow. This code-point exists for add-ons to override.
+ let { callID, windowID } = aMessage.data;
+ // Also available: isSecure, innerWindowID. For contentWindow:
+ //
+ // let contentWindow = Services.wm.getOuterWindowWithId(windowID);
+
+ let mm = aMessage.target.messageManager;
+ mm.sendAsyncMessage("rtcpeer:Allow",
+ { callID: callID, windowID: windowID });
+ break;
+ }
+ case "rtcpeer:CancelRequest":
+ // No data to release. This code-point exists for add-ons to override.
+ break;
+ case "webrtc:Request":
+ prompt(aMessage.target, aMessage.data);
+ break;
+ case "webrtc:CancelRequest":
+ removePrompt(aMessage.target, aMessage.data);
+ break;
+ case "webrtc:UpdatingIndicators":
+ webrtcUI._streams = [];
+ break;
+ case "webrtc:UpdateGlobalIndicators":
+ updateIndicators(aMessage.data, aMessage.target);
+ break;
+ case "webrtc:UpdateBrowserIndicators":
+ let id = aMessage.data.windowId;
+ let index;
+ for (index = 0; index < webrtcUI._streams.length; ++index) {
+ if (webrtcUI._streams[index].state.windowId == id)
+ break;
+ }
+ // If there's no documentURI, the update is actually a removal of the
+ // stream, triggered by the recording-window-ended notification.
+ if (!aMessage.data.documentURI && index < webrtcUI._streams.length)
+ webrtcUI._streams.splice(index, 1);
+ else
+ webrtcUI._streams[index] = {browser: aMessage.target, state: aMessage.data};
+ let tabbrowser = aMessage.target.ownerGlobal.gBrowser;
+ if (tabbrowser)
+ tabbrowser.setBrowserSharing(aMessage.target, aMessage.data);
+ break;
+ case "child-process-shutdown":
+ webrtcUI.processIndicators.delete(aMessage.target);
+ updateIndicators(null, null);
+ break;
+ }
+ }
+};
+
+function getBrowserForWindow(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+}
+
+function denyRequest(aBrowser, aRequest) {
+ aBrowser.messageManager.sendAsyncMessage("webrtc:Deny",
+ {callID: aRequest.callID,
+ windowID: aRequest.windowID});
+}
+
+function getHost(uri, href) {
+ let host;
+ try {
+ if (!uri) {
+ uri = Services.io.newURI(href, null, null);
+ }
+ host = uri.host;
+ } catch (ex) {}
+ if (!host) {
+ if (uri && uri.scheme.toLowerCase() == "about") {
+ // For about URIs, just use the full spec, without any #hash parts.
+ host = uri.specIgnoringRef;
+ } else {
+ // This is unfortunate, but we should display *something*...
+ const kBundleURI = "chrome://browser/locale/browser.properties";
+ let bundle = Services.strings.createBundle(kBundleURI);
+ host = bundle.GetStringFromName("getUserMedia.sharingMenuUnknownHost");
+ }
+ }
+ return host;
+}
+
+function prompt(aBrowser, aRequest) {
+ let {audioDevices: audioDevices, videoDevices: videoDevices,
+ sharingScreen: sharingScreen, sharingAudio: sharingAudio,
+ requestTypes: requestTypes} = aRequest;
+ let uri = Services.io.newURI(aRequest.documentURI, null, null);
+ let host = getHost(uri);
+ let chromeDoc = aBrowser.ownerDocument;
+ let chromeWin = chromeDoc.defaultView;
+ let stringBundle = chromeWin.gNavigatorBundle;
+ let stringId = "getUserMedia.share" + requestTypes.join("And") + ".message";
+ let message = stringBundle.getFormattedString(stringId, [host]);
+
+ let mainLabel;
+ if (sharingScreen || sharingAudio) {
+ mainLabel = stringBundle.getString("getUserMedia.shareSelectedItems.label");
+ } else {
+ let string = stringBundle.getString("getUserMedia.shareSelectedDevices.label");
+ mainLabel = PluralForm.get(requestTypes.length, string);
+ }
+
+ let notification; // Used by action callbacks.
+ let mainAction = {
+ label: mainLabel,
+ accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
+ // The real callback will be set during the "showing" event. The
+ // empty function here is so that PopupNotifications.show doesn't
+ // reject the action.
+ callback: function() {}
+ };
+
+ let secondaryActions = [
+ {
+ label: stringBundle.getString("getUserMedia.denyRequest.label"),
+ accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
+ callback: function () {
+ denyRequest(notification.browser, aRequest);
+ }
+ }
+ ];
+ // Bug 1037438: implement 'never' for screen sharing.
+ if (!sharingScreen && !sharingAudio) {
+ secondaryActions.push({
+ label: stringBundle.getString("getUserMedia.never.label"),
+ accessKey: stringBundle.getString("getUserMedia.never.accesskey"),
+ callback: function () {
+ denyRequest(notification.browser, aRequest);
+ // Let someone save "Never" for http sites so that they can be stopped from
+ // bothering you with doorhangers.
+ let perms = Services.perms;
+ if (audioDevices.length)
+ perms.add(uri, "microphone", perms.DENY_ACTION);
+ if (videoDevices.length)
+ perms.add(uri, "camera", perms.DENY_ACTION);
+ }
+ });
+ }
+
+ if (aRequest.secure && !sharingScreen && !sharingAudio) {
+ // Don't show the 'Always' action if the connection isn't secure, or for
+ // screen/audio sharing (because we can't guess which window the user wants
+ // to share without prompting).
+ secondaryActions.unshift({
+ label: stringBundle.getString("getUserMedia.always.label"),
+ accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
+ callback: function (aState) {
+ mainAction.callback(aState, true);
+ }
+ });
+ }
+
+ let options = {
+ eventCallback: function(aTopic, aNewBrowser) {
+ if (aTopic == "swapping")
+ return true;
+
+ let chromeDoc = this.browser.ownerDocument;
+
+ // Clean-up video streams of screensharing previews.
+ if ((aTopic == "dismissed" || aTopic == "removed") &&
+ requestTypes.includes("Screen")) {
+ let video = chromeDoc.getElementById("webRTC-previewVideo");
+ video.deviceId = undefined;
+ if (video.stream) {
+ video.stream.getTracks().forEach(t => t.stop());
+ video.stream = null;
+ video.src = null;
+ chromeDoc.getElementById("webRTC-preview").hidden = true;
+ }
+ let menupopup = chromeDoc.getElementById("webRTC-selectWindow-menupopup");
+ if (menupopup._commandEventListener) {
+ menupopup.removeEventListener("command", menupopup._commandEventListener);
+ menupopup._commandEventListener = null;
+ }
+ }
+
+ if (aTopic != "showing")
+ return false;
+
+ // DENY_ACTION is handled immediately by MediaManager, but handling
+ // of ALLOW_ACTION is delayed until the popupshowing event
+ // to avoid granting permissions automatically to background tabs.
+ if (aRequest.secure) {
+ let perms = Services.perms;
+
+ let micPerm = perms.testExactPermission(uri, "microphone");
+ if (micPerm == perms.PROMPT_ACTION)
+ micPerm = perms.UNKNOWN_ACTION;
+
+ let camPerm = perms.testExactPermission(uri, "camera");
+
+ let mediaManagerPerm =
+ perms.testExactPermission(uri, "MediaManagerVideo");
+ if (mediaManagerPerm) {
+ perms.remove(uri, "MediaManagerVideo");
+ }
+
+ if (camPerm == perms.PROMPT_ACTION)
+ camPerm = perms.UNKNOWN_ACTION;
+
+ // Screen sharing shouldn't follow the camera permissions.
+ if (videoDevices.length && sharingScreen)
+ camPerm = perms.UNKNOWN_ACTION;
+
+ // We don't check that permissions are set to ALLOW_ACTION in this
+ // test; only that they are set. This is because if audio is allowed
+ // and video is denied persistently, we don't want to show the prompt,
+ // and will grant audio access immediately.
+ if ((!audioDevices.length || micPerm) && (!videoDevices.length || camPerm)) {
+ // All permissions we were about to request are already persistently set.
+ let allowedDevices = [];
+ if (videoDevices.length && camPerm == perms.ALLOW_ACTION) {
+ allowedDevices.push(videoDevices[0].deviceIndex);
+ let perms = Services.perms;
+ perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
+ perms.EXPIRE_SESSION);
+ }
+ if (audioDevices.length && micPerm == perms.ALLOW_ACTION)
+ allowedDevices.push(audioDevices[0].deviceIndex);
+
+ // Remember on which URIs we found persistent permissions so that we
+ // can remove them if the user clicks 'Stop Sharing'. There's no
+ // other way for the stop sharing code to know the hostnames of frames
+ // using devices until bug 1066082 is fixed.
+ let browser = this.browser;
+ browser._devicePermissionURIs = browser._devicePermissionURIs || [];
+ browser._devicePermissionURIs.push(uri);
+
+ let mm = browser.messageManager;
+ mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
+ windowID: aRequest.windowID,
+ devices: allowedDevices});
+ this.remove();
+ return true;
+ }
+ }
+
+ function listDevices(menupopup, devices) {
+ while (menupopup.lastChild)
+ menupopup.removeChild(menupopup.lastChild);
+
+ for (let device of devices)
+ addDeviceToList(menupopup, device.name, device.deviceIndex);
+ }
+
+ function listScreenShareDevices(menupopup, devices) {
+ while (menupopup.lastChild)
+ menupopup.removeChild(menupopup.lastChild);
+
+ let type = devices[0].mediaSource;
+ let typeName = type.charAt(0).toUpperCase() + type.substr(1);
+
+ let label = chromeDoc.getElementById("webRTC-selectWindow-label");
+ let stringId = "getUserMedia.select" + typeName;
+ label.setAttribute("value",
+ stringBundle.getString(stringId + ".label"));
+ label.setAttribute("accesskey",
+ stringBundle.getString(stringId + ".accesskey"));
+
+ // "No <type>" is the default because we can't pick a
+ // 'default' window to share.
+ addDeviceToList(menupopup,
+ stringBundle.getString("getUserMedia.no" + typeName + ".label"),
+ "-1");
+ menupopup.appendChild(chromeDoc.createElement("menuseparator"));
+
+ // Build the list of 'devices'.
+ let monitorIndex = 1;
+ for (let i = 0; i < devices.length; ++i) {
+ let device = devices[i];
+
+ let name;
+ // Building screen list from available screens.
+ if (type == "screen") {
+ if (device.name == "Primary Monitor") {
+ name = stringBundle.getString("getUserMedia.shareEntireScreen.label");
+ } else {
+ name = stringBundle.getFormattedString("getUserMedia.shareMonitor.label",
+ [monitorIndex]);
+ ++monitorIndex;
+ }
+ }
+ else {
+ name = device.name;
+ if (type == "application") {
+ // The application names returned by the platform are of the form:
+ // <window count>\x1e<application name>
+ let sepIndex = name.indexOf("\x1e");
+ let count = name.slice(0, sepIndex);
+ let stringId = "getUserMedia.shareApplicationWindowCount.label";
+ name = PluralForm.get(parseInt(count), stringBundle.getString(stringId))
+ .replace("#1", name.slice(sepIndex + 1))
+ .replace("#2", count);
+ }
+ }
+ let item = addDeviceToList(menupopup, name, i, typeName);
+ item.deviceId = device.id;
+ if (device.scary)
+ item.scary = true;
+ }
+
+ // Always re-select the "No <type>" item.
+ chromeDoc.getElementById("webRTC-selectWindow-menulist").removeAttribute("value");
+ chromeDoc.getElementById("webRTC-all-windows-shared").hidden = true;
+ menupopup._commandEventListener = event => {
+ let video = chromeDoc.getElementById("webRTC-previewVideo");
+ if (video.stream) {
+ video.stream.getTracks().forEach(t => t.stop());
+ video.stream = null;
+ }
+
+ let deviceId = event.target.deviceId;
+ if (deviceId == undefined) {
+ chromeDoc.getElementById("webRTC-preview").hidden = true;
+ video.src = null;
+ return;
+ }
+
+ let scary = event.target.scary;
+ let warning = chromeDoc.getElementById("webRTC-previewWarning");
+ warning.hidden = !scary;
+ let chromeWin = chromeDoc.defaultView;
+ if (scary) {
+ warning.hidden = false;
+ let string;
+ let bundle = chromeWin.gNavigatorBundle;
+
+ let learnMoreText =
+ bundle.getString("getUserMedia.shareScreen.learnMoreLabel");
+ let baseURL =
+ Services.urlFormatter.formatURLPref("app.support.baseURL");
+ let learnMore =
+ "<label class='text-link' href='" + baseURL + "screenshare-safety'>" +
+ learnMoreText + "</label>";
+
+ if (type == "screen") {
+ string = bundle.getFormattedString("getUserMedia.shareScreenWarning.message",
+ [learnMore]);
+ }
+ else {
+ let brand =
+ chromeDoc.getElementById("bundle_brand").getString("brandShortName");
+ string = bundle.getFormattedString("getUserMedia.shareFirefoxWarning.message",
+ [brand, learnMore]);
+ }
+ warning.innerHTML = string;
+ }
+
+ let perms = Services.perms;
+ let chromeUri = Services.io.newURI(chromeDoc.documentURI, null, null);
+ perms.add(chromeUri, "MediaManagerVideo", perms.ALLOW_ACTION,
+ perms.EXPIRE_SESSION);
+
+ video.deviceId = deviceId;
+ let constraints = { video: { mediaSource: type, deviceId: {exact: deviceId } } };
+ chromeWin.navigator.mediaDevices.getUserMedia(constraints).then(stream => {
+ if (video.deviceId != deviceId) {
+ // The user has selected a different device or closed the panel
+ // before getUserMedia finished.
+ stream.getTracks().forEach(t => t.stop());
+ return;
+ }
+ video.src = chromeWin.URL.createObjectURL(stream);
+ video.stream = stream;
+ chromeDoc.getElementById("webRTC-preview").hidden = false;
+ video.onloadedmetadata = function(e) {
+ video.play();
+ };
+ });
+ };
+ menupopup.addEventListener("command", menupopup._commandEventListener);
+ }
+
+ function addDeviceToList(menupopup, deviceName, deviceIndex, type) {
+ let menuitem = chromeDoc.createElement("menuitem");
+ menuitem.setAttribute("value", deviceIndex);
+ menuitem.setAttribute("label", deviceName);
+ menuitem.setAttribute("tooltiptext", deviceName);
+ if (type)
+ menuitem.setAttribute("devicetype", type);
+ menupopup.appendChild(menuitem);
+ return menuitem;
+ }
+
+ chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length || sharingScreen;
+ chromeDoc.getElementById("webRTC-selectWindowOrScreen").hidden = !sharingScreen || !videoDevices.length;
+ chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length || sharingAudio;
+
+ let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
+ let windowMenupopup = chromeDoc.getElementById("webRTC-selectWindow-menupopup");
+ let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
+ if (sharingScreen)
+ listScreenShareDevices(windowMenupopup, videoDevices);
+ else
+ listDevices(camMenupopup, videoDevices);
+
+ if (!sharingAudio)
+ listDevices(micMenupopup, audioDevices);
+
+ this.mainAction.callback = function(aState, aRemember) {
+ let allowedDevices = [];
+ let perms = Services.perms;
+ if (videoDevices.length) {
+ let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
+ let videoDeviceIndex = chromeDoc.getElementById(listId).value;
+ let allowCamera = videoDeviceIndex != "-1";
+ if (allowCamera) {
+ allowedDevices.push(videoDeviceIndex);
+ // Session permission will be removed after use
+ // (it's really one-shot, not for the entire session)
+ perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
+ perms.EXPIRE_SESSION);
+ }
+ if (aRemember) {
+ perms.add(uri, "camera",
+ allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
+ }
+ }
+ if (audioDevices.length) {
+ if (!sharingAudio) {
+ let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
+ let allowMic = audioDeviceIndex != "-1";
+ if (allowMic)
+ allowedDevices.push(audioDeviceIndex);
+ if (aRemember) {
+ perms.add(uri, "microphone",
+ allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
+ }
+ } else {
+ // Only one device possible for audio capture.
+ allowedDevices.push(0);
+ }
+ }
+
+ if (!allowedDevices.length) {
+ denyRequest(notification.browser, aRequest);
+ return;
+ }
+
+ if (aRemember) {
+ // Remember on which URIs we set persistent permissions so that we
+ // can remove them if the user clicks 'Stop Sharing'.
+ aBrowser._devicePermissionURIs = aBrowser._devicePermissionURIs || [];
+ aBrowser._devicePermissionURIs.push(uri);
+ }
+
+ let mm = notification.browser.messageManager;
+ mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
+ windowID: aRequest.windowID,
+ devices: allowedDevices});
+ };
+ return false;
+ }
+ };
+
+ let iconType = "Devices";
+ if (requestTypes.length == 1 && (requestTypes[0] == "Microphone" ||
+ requestTypes[0] == "AudioCapture"))
+ iconType = "Microphone";
+ if (requestTypes.includes("Screen"))
+ iconType = "Screen";
+ let anchorId = "webRTC-share" + iconType + "-notification-icon";
+
+ let iconClass = iconType.toLowerCase();
+ if (iconClass == "devices")
+ iconClass = "camera";
+ options.popupIconClass = iconClass + "-icon";
+
+ notification =
+ chromeWin.PopupNotifications.show(aBrowser, "webRTC-shareDevices", message,
+ anchorId, mainAction, secondaryActions,
+ options);
+ notification.callID = aRequest.callID;
+}
+
+function removePrompt(aBrowser, aCallId) {
+ let chromeWin = aBrowser.ownerGlobal;
+ let notification =
+ chromeWin.PopupNotifications.getNotification("webRTC-shareDevices", aBrowser);
+ if (notification && notification.callID == aCallId)
+ notification.remove();
+}
+
+function getGlobalIndicator() {
+ if (AppConstants.platform != "macosx") {
+ const INDICATOR_CHROME_URI = "chrome://browser/content/webrtcIndicator.xul";
+ const features = "chrome,dialog=yes,titlebar=no,popup=yes";
+
+ return Services.ww.openWindow(null, INDICATOR_CHROME_URI, "_blank", features, []);
+ }
+
+ let indicator = {
+ _camera: null,
+ _microphone: null,
+ _screen: null,
+
+ _hiddenDoc: Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService)
+ .hiddenDOMWindow.document,
+ _statusBar: Cc["@mozilla.org/widget/macsystemstatusbar;1"]
+ .getService(Ci.nsISystemStatusBar),
+
+ _command: function(aEvent) {
+ let type = this.getAttribute("type");
+ if (type == "Camera" || type == "Microphone")
+ type = "Devices";
+ else if (type == "Window" || type == "Application" || type == "Browser")
+ type = "Screen";
+ webrtcUI.showSharingDoorhanger(aEvent.target.stream, type);
+ },
+
+ _popupShowing: function(aEvent) {
+ let type = this.getAttribute("type");
+ let activeStreams;
+ if (type == "Camera") {
+ activeStreams = webrtcUI.getActiveStreams(true, false, false);
+ }
+ else if (type == "Microphone") {
+ activeStreams = webrtcUI.getActiveStreams(false, true, false);
+ }
+ else if (type == "Screen") {
+ activeStreams = webrtcUI.getActiveStreams(false, false, true);
+ type = webrtcUI.showScreenSharingIndicator;
+ }
+
+ let bundle =
+ Services.strings.createBundle("chrome://browser/locale/webrtcIndicator.properties");
+
+ if (activeStreams.length == 1) {
+ let stream = activeStreams[0];
+
+ let menuitem = this.ownerDocument.createElement("menuitem");
+ let labelId = "webrtcIndicator.sharing" + type + "With.menuitem";
+ let label = stream.browser.contentTitle || stream.uri;
+ menuitem.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
+ menuitem.setAttribute("disabled", "true");
+ this.appendChild(menuitem);
+
+ menuitem = this.ownerDocument.createElement("menuitem");
+ menuitem.setAttribute("label",
+ bundle.GetStringFromName("webrtcIndicator.controlSharing.menuitem"));
+ menuitem.setAttribute("type", type);
+ menuitem.stream = stream;
+ menuitem.addEventListener("command", indicator._command);
+
+ this.appendChild(menuitem);
+ return true;
+ }
+
+ // We show a different menu when there are several active streams.
+ let menuitem = this.ownerDocument.createElement("menuitem");
+ let labelId = "webrtcIndicator.sharing" + type + "WithNTabs.menuitem";
+ let count = activeStreams.length;
+ let label = PluralForm.get(count, bundle.GetStringFromName(labelId)).replace("#1", count);
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("disabled", "true");
+ this.appendChild(menuitem);
+
+ for (let stream of activeStreams) {
+ let item = this.ownerDocument.createElement("menuitem");
+ let labelId = "webrtcIndicator.controlSharingOn.menuitem";
+ let label = stream.browser.contentTitle || stream.uri;
+ item.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
+ item.setAttribute("type", type);
+ item.stream = stream;
+ item.addEventListener("command", indicator._command);
+ this.appendChild(item);
+ }
+
+ return true;
+ },
+
+ _popupHiding: function(aEvent) {
+ while (this.firstChild)
+ this.firstChild.remove();
+ },
+
+ _setIndicatorState: function(aName, aState) {
+ let field = "_" + aName.toLowerCase();
+ if (aState && !this[field]) {
+ let menu = this._hiddenDoc.createElement("menu");
+ menu.setAttribute("id", "webRTC-sharing" + aName + "-menu");
+
+ // The CSS will only be applied if the menu is actually inserted in the DOM.
+ this._hiddenDoc.documentElement.appendChild(menu);
+
+ this._statusBar.addItem(menu);
+
+ let menupopup = this._hiddenDoc.createElement("menupopup");
+ menupopup.setAttribute("type", aName);
+ menupopup.addEventListener("popupshowing", this._popupShowing);
+ menupopup.addEventListener("popuphiding", this._popupHiding);
+ menupopup.addEventListener("command", this._command);
+ menu.appendChild(menupopup);
+
+ this[field] = menu;
+ }
+ else if (this[field] && !aState) {
+ this._statusBar.removeItem(this[field]);
+ this[field].remove();
+ this[field] = null
+ }
+ },
+ updateIndicatorState: function() {
+ this._setIndicatorState("Camera", webrtcUI.showCameraIndicator);
+ this._setIndicatorState("Microphone", webrtcUI.showMicrophoneIndicator);
+ this._setIndicatorState("Screen", webrtcUI.showScreenSharingIndicator);
+ },
+ close: function() {
+ this._setIndicatorState("Camera", false);
+ this._setIndicatorState("Microphone", false);
+ this._setIndicatorState("Screen", false);
+ }
+ };
+
+ indicator.updateIndicatorState();
+ return indicator;
+}
+
+function onTabSharingMenuPopupShowing(e) {
+ let streams = webrtcUI.getActiveStreams(true, true, true);
+ for (let streamInfo of streams) {
+ let stringName = "getUserMedia.sharingMenu";
+ let types = streamInfo.types;
+ if (types.camera)
+ stringName += "Camera";
+ if (types.microphone)
+ stringName += "Microphone";
+ if (types.screen)
+ stringName += types.screen;
+
+ let doc = e.target.ownerDocument;
+ let bundle = doc.defaultView.gNavigatorBundle;
+
+ let origin = getHost(null, streamInfo.uri);
+ let menuitem = doc.createElement("menuitem");
+ menuitem.setAttribute("label", bundle.getFormattedString(stringName, [origin]));
+ menuitem.stream = streamInfo;
+
+ // We can only open 1 doorhanger at a time. Guessing that users would be
+ // most eager to control screen/window/app sharing, and only then
+ // camera/microphone sharing, in that (decreasing) order of priority.
+ let doorhangerType;
+ if ((/Screen|Window|Application/).test(stringName)) {
+ doorhangerType = "Screen";
+ } else {
+ doorhangerType = "Devices";
+ }
+ menuitem.setAttribute("doorhangertype", doorhangerType);
+ menuitem.addEventListener("command", onTabSharingMenuPopupCommand);
+ e.target.appendChild(menuitem);
+ }
+}
+
+function onTabSharingMenuPopupHiding(e) {
+ while (this.lastChild)
+ this.lastChild.remove();
+}
+
+function onTabSharingMenuPopupCommand(e) {
+ let type = e.target.getAttribute("doorhangertype");
+ webrtcUI.showSharingDoorhanger(e.target.stream, type);
+}
+
+function showOrCreateMenuForWindow(aWindow) {
+ let document = aWindow.document;
+ let menu = document.getElementById("tabSharingMenu");
+ if (!menu) {
+ let stringBundle = aWindow.gNavigatorBundle;
+ menu = document.createElement("menu");
+ menu.id = "tabSharingMenu";
+ let labelStringId = "getUserMedia.sharingMenu.label";
+ menu.setAttribute("label", stringBundle.getString(labelStringId));
+
+ let container, insertionPoint;
+ if (AppConstants.platform == "macosx") {
+ container = document.getElementById("windowPopup");
+ insertionPoint = document.getElementById("sep-window-list");
+ let separator = document.createElement("menuseparator");
+ separator.id = "tabSharingSeparator";
+ container.insertBefore(separator, insertionPoint);
+ } else {
+ let accesskeyStringId = "getUserMedia.sharingMenu.accesskey";
+ menu.setAttribute("accesskey", stringBundle.getString(accesskeyStringId));
+ container = document.getElementById("main-menubar");
+ insertionPoint = document.getElementById("helpMenu");
+ }
+ let popup = document.createElement("menupopup");
+ popup.id = "tabSharingMenuPopup";
+ popup.addEventListener("popupshowing", onTabSharingMenuPopupShowing);
+ popup.addEventListener("popuphiding", onTabSharingMenuPopupHiding);
+ menu.appendChild(popup);
+ container.insertBefore(menu, insertionPoint);
+ } else {
+ menu.hidden = false;
+ if (AppConstants.platform == "macosx") {
+ document.getElementById("tabSharingSeparator").hidden = false;
+ }
+ }
+}
+
+function maybeAddMenuIndicator(window) {
+ if (webrtcUI.showGlobalIndicator) {
+ showOrCreateMenuForWindow(window);
+ }
+}
+
+var gIndicatorWindow = null;
+
+function updateIndicators(data, target) {
+ if (data) {
+ // the global indicators specific to this process
+ let indicators;
+ if (webrtcUI.processIndicators.has(target)) {
+ indicators = webrtcUI.processIndicators.get(target);
+ } else {
+ indicators = {};
+ webrtcUI.processIndicators.set(target, indicators);
+ }
+
+ indicators.showGlobalIndicator = data.showGlobalIndicator;
+ indicators.showCameraIndicator = data.showCameraIndicator;
+ indicators.showMicrophoneIndicator = data.showMicrophoneIndicator;
+ indicators.showScreenSharingIndicator = data.showScreenSharingIndicator;
+ }
+
+ let browserWindowEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserWindowEnum.hasMoreElements()) {
+ let chromeWin = browserWindowEnum.getNext();
+ if (webrtcUI.showGlobalIndicator) {
+ showOrCreateMenuForWindow(chromeWin);
+ } else {
+ let doc = chromeWin.document;
+ let existingMenu = doc.getElementById("tabSharingMenu");
+ if (existingMenu) {
+ existingMenu.hidden = true;
+ }
+ if (AppConstants.platform == "macosx") {
+ let separator = doc.getElementById("tabSharingSeparator");
+ if (separator) {
+ separator.hidden = true;
+ }
+ }
+ }
+ }
+
+ if (webrtcUI.showGlobalIndicator) {
+ if (!gIndicatorWindow)
+ gIndicatorWindow = getGlobalIndicator();
+ else
+ gIndicatorWindow.updateIndicatorState();
+ } else if (gIndicatorWindow) {
+ gIndicatorWindow.close();
+ gIndicatorWindow = null;
+ }
+}
diff --git a/browser/moz.build b/browser/moz.build
new file mode 100644
index 000000000..a691aeef2
--- /dev/null
+++ b/browser/moz.build
@@ -0,0 +1,41 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+CONFIGURE_SUBST_FILES += ['installer/Makefile']
+
+SPHINX_TREES['browser'] = 'docs'
+
+DIRS += [
+ 'base',
+ 'components',
+ 'experiments',
+ 'fonts',
+ 'locales',
+ 'modules',
+ 'themes',
+ 'extensions',
+]
+
+DIRS += [
+ 'app',
+]
+
+if CONFIG['MAKENSISU']:
+ DIRS += ['installer/windows']
+
+TEST_DIRS += [
+ 'tools/mozscreenshots',
+]
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+if CONFIG['MOZ_ARTIFACT_BUILDS']:
+ # Ensure a pre-built interfaces.xpt installed to the objdir by the artifact
+ # code is included by the top-level chrome.manifest.
+ EXTRA_COMPONENTS += [
+ '../build/prebuilt-interfaces.manifest',
+ ]
diff --git a/browser/moz.configure b/browser/moz.configure
new file mode 100644
index 000000000..fba4603be
--- /dev/null
+++ b/browser/moz.configure
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+imply_option('MOZ_PLACES', True)
+imply_option('MOZ_SERVICES_HEALTHREPORT', True)
+imply_option('MOZ_SERVICES_SYNC', True)
+imply_option('MOZ_SERVICES_CLOUDSYNC', True)
+
+include('../toolkit/moz.configure')
diff --git a/browser/themes/LICENSE b/browser/themes/LICENSE
new file mode 100644
index 000000000..39d4f8fdf
--- /dev/null
+++ b/browser/themes/LICENSE
@@ -0,0 +1,2 @@
+All files in this directory are assumed to be licensed under the MPL 2 license
+which is used throughout this codebase.
diff --git a/browser/themes/linux/Info.png b/browser/themes/linux/Info.png
new file mode 100644
index 000000000..d14479852
--- /dev/null
+++ b/browser/themes/linux/Info.png
Binary files differ
diff --git a/browser/themes/linux/Privacy-16.png b/browser/themes/linux/Privacy-16.png
new file mode 100644
index 000000000..013cdc4b9
--- /dev/null
+++ b/browser/themes/linux/Privacy-16.png
Binary files differ
diff --git a/browser/themes/linux/Security-broken.png b/browser/themes/linux/Security-broken.png
new file mode 100644
index 000000000..1ec110be4
--- /dev/null
+++ b/browser/themes/linux/Security-broken.png
Binary files differ
diff --git a/browser/themes/linux/Toolbar-inverted.png b/browser/themes/linux/Toolbar-inverted.png
new file mode 100644
index 000000000..50a3d346f
--- /dev/null
+++ b/browser/themes/linux/Toolbar-inverted.png
Binary files differ
diff --git a/browser/themes/linux/Toolbar-inverted@2x.png b/browser/themes/linux/Toolbar-inverted@2x.png
new file mode 100644
index 000000000..3472548d2
--- /dev/null
+++ b/browser/themes/linux/Toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/themes/linux/Toolbar-small.png b/browser/themes/linux/Toolbar-small.png
new file mode 100644
index 000000000..8f100a54a
--- /dev/null
+++ b/browser/themes/linux/Toolbar-small.png
Binary files differ
diff --git a/browser/themes/linux/Toolbar.png b/browser/themes/linux/Toolbar.png
new file mode 100644
index 000000000..cdd100aa5
--- /dev/null
+++ b/browser/themes/linux/Toolbar.png
Binary files differ
diff --git a/browser/themes/linux/Toolbar@2x.png b/browser/themes/linux/Toolbar@2x.png
new file mode 100644
index 000000000..3244081c3
--- /dev/null
+++ b/browser/themes/linux/Toolbar@2x.png
Binary files differ
diff --git a/browser/themes/linux/aboutSessionRestore-window-icon.png b/browser/themes/linux/aboutSessionRestore-window-icon.png
new file mode 100644
index 000000000..a99832372
--- /dev/null
+++ b/browser/themes/linux/aboutSessionRestore-window-icon.png
Binary files differ
diff --git a/browser/themes/linux/aboutSyncTabs.css b/browser/themes/linux/aboutSyncTabs.css
new file mode 100644
index 000000000..4cedad649
--- /dev/null
+++ b/browser/themes/linux/aboutSyncTabs.css
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#tabs-display,
+#tabsList {
+ background-color: transparent;
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#tabsList {
+ width: 100%;
+}
+
+#tabs-display {
+ background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
+}
+
+#headers {
+ background: url(chrome://browser/skin/sync-32.png) no-repeat;
+ margin-top: 4px;
+ width: 45em;
+ height: 32px;
+ 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://browser/skin/sync-mobileIcon.svg#icon");
+}
+richlistitem.desktop[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
+}
+
+.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/browser/themes/linux/actionicon-tab.png b/browser/themes/linux/actionicon-tab.png
new file mode 100644
index 000000000..433c25e1a
--- /dev/null
+++ b/browser/themes/linux/actionicon-tab.png
Binary files differ
diff --git a/browser/themes/linux/browser-lightweightTheme.css b/browser/themes/linux/browser-lightweightTheme.css
new file mode 100644
index 000000000..d3109bec3
--- /dev/null
+++ b/browser/themes/linux/browser-lightweightTheme.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/. */
+
+%include linuxShared.inc
+%filter substitution
+
+/*
+ * LightweightThemeListener will append the current lightweight theme's header
+ * image to the background-image for each of the following rulesets.
+ */
+
+/* Lightweight theme on tabs */
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
+ background-attachment: scroll, fixed;
+ background-color: transparent;
+ background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
+ background-position: 0 0, right top;
+ background-repeat: repeat-x, no-repeat;
+}
+
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+ background-attachment: scroll, scroll, fixed;
+ background-color: transparent;
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
+ @fgTabTextureLWT@;/*,
+ lwtHeader;*/
+ background-position: 0 0, 0 0, right top;
+ background-repeat: repeat-x, repeat-x, no-repeat;
+}
diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css
new file mode 100644
index 000000000..73d3844a2
--- /dev/null
+++ b/browser/themes/linux/browser.css
@@ -0,0 +1,1658 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+@import url("chrome://global/skin/");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+@namespace svg url("http://www.w3.org/2000/svg");
+
+%include ../shared/browser.inc
+%include linuxShared.inc
+%filter substitution
+
+%define forwardTransitionLength 150ms
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
+
+:root {
+ --backbutton-urlbar-overlap: 6px;
+ /* icon width + border + horizontal padding (without the overlap from backbutton-urlbar-overlap) */
+ --forwardbutton-width: 25px;
+
+ --toolbarbutton-hover-background: rgba(255,255,255,.5) linear-gradient(rgba(255,255,255,.5), transparent);
+ --toolbarbutton-hover-bordercolor: rgba(0,0,0,.25);
+ --toolbarbutton-hover-boxshadow: none;
+
+ --toolbarbutton-active-background: rgba(154,154,154,.5) linear-gradient(rgba(255,255,255,.7), rgba(255,255,255,.4));
+ --toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
+ --toolbarbutton-active-boxshadow: 0 1px 1px rgba(0,0,0,.1) inset, 0 0 1px rgba(0,0,0,.3) inset;
+
+ --toolbarbutton-checkedhover-backgroundcolor: rgba(200,200,200,.5);
+
+ --panel-separator-color: ThreeDShadow;
+ --arrowpanel-dimmed: hsla(0,0%,80%,.3);
+ --arrowpanel-dimmed-further: hsla(0,0%,80%,.45);
+ --arrowpanel-dimmed-even-further: hsla(0,0%,80%,.8);
+
+ --urlbar-separator-color: ThreeDShadow;
+}
+
+#menubar-items {
+ -moz-box-orient: vertical; /* for flex hack */
+}
+
+#main-menubar {
+ -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
+}
+
+#navigator-toolbox {
+ -moz-appearance: none;
+ background-color: transparent;
+ border-top: none;
+}
+
+#navigator-toolbox::after {
+ content: "";
+ display: -moz-box;
+ -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
+ border-bottom: 1px solid ThreeDShadow;
+}
+
+#navigator-toolbox:-moz-lwtheme::after {
+ border-bottom-color: rgba(0,0,0,.3);
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#addon-bar) {
+ background-image: linear-gradient(@toolbarHighlight@, @toolbarHighlight@);
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#addon-bar):-moz-lwtheme {
+ background-image: linear-gradient(@toolbarHighlightLWT@, @toolbarHighlightLWT@);
+}
+
+#navigator-toolbox > toolbar:not(:-moz-lwtheme):not(#toolbar-menubar):not(#TabsToolbar) {
+ -moz-appearance: none;
+ border-style: none;
+ background-color: -moz-Dialog;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar) {
+ overflow: -moz-hidden-unscrollable;
+ max-height: 4em;
+ transition: min-height 170ms ease-out, max-height 170ms ease-out;
+ padding: 1px 4px;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar)[collapsed=true] {
+ min-height: 0.1px;
+ max-height: 0;
+ transition: min-height 170ms ease-out, max-height 170ms ease-out, visibility 170ms linear;
+}
+
+#TabsToolbar:not([collapsed="true"]) + #nav-bar {
+ border-top: 1px solid hsla(0,0%,0%,.3) !important;
+ background-clip: padding-box;
+ /* Move up into the TabsToolbar for the inner highlight at the top of the nav-bar */
+ margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
+ /* Position the toolbar above the bottom of background tabs */
+ position: relative;
+ z-index: 1;
+}
+
+#nav-bar {
+ box-shadow: 0 1px 0 @navbarInsetHighlight@ inset;
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+#nav-bar-overflow-button {
+ -moz-image-region: rect(-5px, 12px, 11px, -4px);
+}
+
+/* This only has an effect when this element is placed on the bookmarks toolbar.
+ * It's 30px to make sure buttons with 18px icons fit along with the default 16px
+ * icons, without changing the size of the toolbar.
+ */
+#personal-bookmarks {
+ min-height: 30px;
+}
+
+#browser-bottombox {
+ /* opaque for layers optimization */
+ background-color: -moz-Dialog;
+}
+
+/* Places toolbar */
+toolbarbutton.bookmark-item:not(.subviewbutton),
+#personal-bookmarks[cui-areatype="toolbar"]:not([overflowedItem=true]) > #bookmarks-toolbar-placeholder {
+ margin: 0;
+ padding: 2px 3px;
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):not(:hover):not(:active):not([open]) {
+ color: inherit;
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton) {
+ -moz-appearance: none;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ transition-property: background-color, border-color;
+ transition-duration: 150ms;
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):hover:not([open]) {
+ background: var(--toolbarbutton-hover-background);
+ border-color: var(--toolbarbutton-hover-bordercolor);
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):hover:active,
+toolbarbutton.bookmark-item[open="true"] {
+ background: var(--toolbarbutton-active-background);
+ box-shadow: var(--toolbarbutton-active-boxshadow);
+ border-color: var(--toolbarbutton-active-bordercolor);
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):hover:-moz-lwtheme {
+ background: var(--toolbarbutton-hover-background);
+ border-color: var(--toolbarbutton-hover-bordercolor);
+}
+
+.bookmark-item > .toolbarbutton-icon,
+#personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Force the display of the label for bookmarks */
+.bookmark-item > .toolbarbutton-text,
+#personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-text {
+ display: -moz-box !important;
+}
+
+.bookmark-item > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+/* Dropmarker for folder bookmarks */
+.bookmark-item[container] > .toolbarbutton-menu-dropmarker {
+ display: -moz-box !important;
+}
+
+#bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
+#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
+}
+
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+ from { transform: rotate(0deg) translateX(-16px) rotate(0deg) scale(1); opacity: 0; }
+ 60% { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(2.2); opacity: 1; }
+ 80% { opacity: 1; }
+ to { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+ from { transform: scale(1); }
+ 50% { transform: scale(1.3); }
+ to { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+ min-height: 1px;
+ min-width: 1px;
+ height: 1px;
+ margin-bottom: -1px;
+ z-index: 5;
+ position: relative;
+}
+
+#bookmarked-notification {
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+ opacity: 0;
+}
+
+#bookmarked-notification-dropmarker-anchor {
+ z-index: -1;
+ position: relative;
+}
+
+#bookmarked-notification-dropmarker-icon {
+ width: 18px;
+ height: 18px;
+ visibility: hidden;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+ background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+ animation: animation-bookmarkAdded 800ms;
+ animation-timing-function: ease, ease, ease;
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: none !important;
+}
+
+#bookmarked-notification-dropmarker-anchor[notification="finish"] > #bookmarked-notification-dropmarker-icon {
+ visibility: visible;
+ animation: animation-bookmarkPulse 300ms;
+ animation-delay: 600ms;
+ animation-timing-function: ease-out;
+}
+
+/* Bookmark menus */
+menu.bookmark-item,
+menuitem.bookmark-item {
+ min-width: 0;
+ max-width: 32em;
+}
+
+.bookmark-item:not(.subviewbutton) > .menu-iconic-left {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.bookmark-item > .menu-iconic-left > .menu-iconic-icon {
+ padding-inline-start: 0px;
+}
+
+/* Bookmark drag and drop styles */
+.bookmark-item[dragover-into="true"] {
+ background: Highlight !important;
+ color: HighlightText !important;
+}
+
+/* rules for menupopup drop indicators */
+.menupopup-drop-indicator-bar {
+ position: relative;
+ /* these two margins must together compensate the indicator's height */
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.menupopup-drop-indicator {
+ list-style-image: none;
+ height: 2px;
+ margin-inline-end: -4em;
+ background-color: Highlight;
+}
+
+/* Bookmarks toolbar */
+#PlacesToolbarDropIndicator {
+ list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
+}
+
+/* Bookmark items */
+.bookmark-item {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.bookmark-item[container] {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+.bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
+}
+
+.bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited] {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+}
+
+.bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+.bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+}
+
+.bookmark-item[query][hostContainer] {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+.bookmark-item[query][hostContainer][open] {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+.bookmark-item[cutting] > .toolbarbutton-icon,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
+ opacity: 0.5;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-text,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
+ opacity: 0.7;
+}
+
+/* Primary toolbar buttons */
+
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
+ max-width: 16px;
+}
+
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon,
+#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ max-width: 18px;
+}
+
+.findbar-button,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 {
+ -moz-appearance: none;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"],
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:hover:active {
+ padding: 3px;
+}
+
+.findbar-button > .toolbarbutton-text,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-badge-stack,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon {
+ margin-inline-end: 0;
+ padding: 2px 6px;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ transition-property: background-color, border-color;
+ transition-duration: 150ms;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-badge-stack,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ padding: 3px 7px;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ /* horizontal padding + border + actual icon width */
+ max-width: 32px !important /* bug 561154 */;
+}
+
+#nav-bar #PanelUI-menu-button {
+ padding-inline-start: 5px;
+ padding-inline-end: 5px;
+}
+
+.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover > .toolbarbutton-text,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open]:not([disabled=true]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):not([open]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-badge-stack,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):hover > .toolbarbutton-icon {
+ background: var(--toolbarbutton-hover-background);
+ background-clip: padding-box;
+ border-color: var(--toolbarbutton-hover-bordercolor);
+ box-shadow: var(--toolbarbutton-hover-boxshadow);
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
+.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active) > .toolbarbutton-text,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open="true"]) > .toolbarbutton-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[open="true"] > .toolbarbutton-menubutton-dropmarker:not([disabled=true]) > .dropmarker-icon,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-stack,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon {
+ background: var(--toolbarbutton-active-background);
+ background-clip: padding-box;
+ box-shadow: var(--toolbarbutton-active-boxshadow);
+ border-color: var(--toolbarbutton-active-bordercolor);
+ transition-duration: 10ms;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
+ background-color: var(--toolbarbutton-checkedhover-backgroundcolor);
+ transition: background-color 150ms;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button[open],
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button:hover:active,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:hover:active {
+ padding: 3px;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbaritem-combined-buttons {
+ margin-left: 2px;
+ margin-right: 2px;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbaritem-combined-buttons > .toolbarbutton-1 {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbaritem-combined-buttons:not(:hover) > separator,
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ content: "";
+ display: -moz-box;
+ width: 1px;
+ height: 18px;
+ margin-inline-end: -1px;
+ background-image: linear-gradient(currentColor 0, currentColor 100%);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 1px 18px;
+ opacity: .2;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar)[brighttext] .toolbaritem-combined-buttons > separator,
+:-moz-any(#TabsToolbar, #nav-bar)[brighttext] .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ opacity: .3;
+}
+
+:-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ margin-inline-start: -4px;
+ margin-top: 3px;
+ margin-bottom: 3px;
+}
+
+:-moz-any(#back-button, #forward-button) > .toolbarbutton-icon {
+ border-color: var(--urlbar-border-color) !important /* bug 561154 */;
+}
+
+:-moz-any(#back-button, #forward-button):not(:hover):not(:active):not([open=true]) > .toolbarbutton-icon,
+:-moz-any(#back-button, #forward-button)[disabled=true] > .toolbarbutton-icon {
+ background-color: rgba(255,255,255,.25) !important /* bug 561154 */;
+ background-clip: padding-box;
+}
+
+#back-button {
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-inline-start: 4px;
+ padding-inline-end: 0;
+ position: relative;
+ z-index: 1;
+ border-radius: 0 10000px 10000px 0;
+}
+
+#back-button:-moz-locale-dir(rtl) {
+ border-radius: 10000px 0 0 10000px;
+}
+
+#back-button > menupopup {
+ margin-top: -1px;
+}
+
+#back-button > .toolbarbutton-icon {
+ border-radius: 10000px;
+ padding: 6px;
+ max-width: 32px; /* horizontal padding + border + icon width */
+}
+
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#forward-button {
+ -moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
+ padding: 0;
+}
+
+#forward-button > .toolbarbutton-icon {
+ padding-left: calc(var(--backbutton-urlbar-overlap) + 3px);
+ padding-right: 3px;
+ border-left-style: none;
+ border-radius: 0;
+ max-width: calc(var(--forwardbutton-width) + var(--backbutton-urlbar-overlap));
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+ transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+ margin-left: calc(0px - var(--forwardbutton-width) - var(--backbutton-urlbar-overlap));
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+ /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+ transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+ /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+ margin-left: calc(-0.01px - var(--forwardbutton-width) - var(--backbutton-urlbar-overlap));
+}
+
+/* undo close tab menu item */
+#alltabs_undoCloseTab {
+ list-style-image: url(chrome://browser/skin/undoCloseTab.png);
+}
+
+.unified-nav-back[_moz-menuactive] {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=menu") !important;
+}
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=menu") !important;
+}
+.unified-nav-forward[_moz-menuactive] {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=menu") !important;
+}
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu") !important;
+}
+
+/* Menu panel buttons */
+
+%include ../shared/toolbarbuttons.inc.css
+%include ../shared/menupanel.inc.css
+
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menu-dropmarker,
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-dropmarker,
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled=true] > .toolbarbutton-icon {
+ opacity: 0.4;
+}
+
+/* Fullscreen window controls */
+#window-controls {
+ -moz-box-align: start;
+ margin-inline-start: 10px;
+}
+
+#minimize-button {
+ list-style-image: url("chrome://global/skin/icons/Minimize.gif");
+}
+#restore-button {
+ list-style-image: url("chrome://global/skin/icons/Restore.gif");
+}
+#close-button {
+ list-style-image: url("chrome://global/skin/icons/Close.gif");
+}
+
+/* Location bar */
+#main-window {
+ --urlbar-border-color: ThreeDShadow;
+}
+
+#navigator-toolbox:-moz-lwtheme {
+ --urlbar-border-color: rgba(0,0,0,.3);
+}
+
+#urlbar,
+.searchbar-textbox {
+ -moz-appearance: none;
+ padding: 0;
+ border: 1px solid var(--urlbar-border-color);
+ border-radius: 2px;
+ background-clip: padding-box;
+ margin: 0 3px;
+}
+
+#urlbar[focused],
+.searchbar-textbox[focused] {
+ border-color: Highlight;
+}
+
+#urlbar {
+ background-color: -moz-field;
+}
+
+#urlbar:-moz-lwtheme,
+.searchbar-textbox:-moz-lwtheme {
+ background-color: rgba(255,255,255,.8);
+ color: black;
+}
+
+#urlbar:-moz-lwtheme[focused=true],
+.searchbar-textbox:-moz-lwtheme[focused=true] {
+ background-color: white;
+}
+
+.urlbar-textbox-container {
+ -moz-appearance: none;
+ -moz-box-align: stretch;
+}
+
+.urlbar-input-box {
+ margin-inline-start: 0;
+}
+
+.urlbar-input-box,
+#urlbar-display-box {
+ padding-inline-start: 4px;
+ border-inline-start: 1px solid var(--urlbar-separator-color);
+ border-image: linear-gradient(transparent 15%, var(--urlbar-separator-color) 15%, var(--urlbar-separator-color) 85%, transparent 85%);
+ border-image-slice: 1;
+}
+
+.urlbar-history-dropmarker {
+ -moz-appearance: toolbarbutton-dropdown;
+ transition: opacity 0.15s ease;
+}
+
+#urlbar-wrapper[switchingtabs] > #urlbar > .urlbar-textbox-container > .urlbar-history-dropmarker {
+ transition: none;
+}
+
+#navigator-toolbox:not(:hover) #nav-bar:not([customizing="true"]) #urlbar:not([focused]) > .urlbar-textbox-container > .urlbar-history-dropmarker {
+ opacity: 0;
+}
+
+#urlbar-container {
+ -moz-box-align: center;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar {
+ border-inline-start: none;
+ margin-left: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ {
+ clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
+ margin-inline-start: calc(-1 * var(--backbutton-urlbar-overlap));
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+ /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
+ transform: scaleX(-1);
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+ -moz-box-direction: reverse;
+}
+
+#urlbar-icons {
+ -moz-box-align: center;
+}
+
+.urlbar-icon {
+ padding: 0 3px;
+ /* 16x16 icon with border-box sizing */
+ width: 22px;
+ height: 16px;
+}
+
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+ 0% {
+ transform: scale(0);
+ }
+ 75% {
+ transform: scale(1.5);
+ }
+ 100% {
+ transform: scale(1.0);
+ }
+}
+
+#urlbar-zoom-button {
+ -moz-appearance: none;
+ margin: 0 3px;
+ font-size: .8em;
+ padding: 0 8px;
+ border-radius: 1em;
+ background-color: hsla(0,0%,0%,.05);
+ color: inherit;
+ border: 1px solid ThreeDLightShadow;
+}
+
+#urlbar-zoom-button[animate="true"] {
+ animation-name: urlbar-zoom-reset-pulse;
+ animation-duration: 250ms;
+}
+
+#urlbar-zoom-button:hover {
+ background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button:hover:active {
+ background-color: hsla(0,0%,0%,.15);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+ display: none;
+}
+
+#urlbar-search-footer {
+ border-top: 1px solid var(--panel-separator-color);
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#urlbar-search-settings {
+ -moz-appearance: none;
+ -moz-user-focus: ignore;
+ color: GrayText;
+ margin: 0;
+ border: 0;
+ padding: 8px 20px;
+ background: transparent;
+}
+
+#urlbar-search-settings:hover {
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#urlbar-search-settings:hover:active {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+#urlbar-search-splitter {
+ -moz-appearance: none;
+ width: 8px;
+ margin-inline-start: -4px;
+}
+
+#urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
+ margin-inline-start: 0;
+}
+
+#urlbar-display-box {
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.urlbar-display {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-inline-start: 0;
+ color: GrayText;
+}
+
+%include ../shared/urlbarSearchSuggestionsNotification.inc.css
+
+#search-container {
+ min-width: calc(54px + 11ch);
+}
+
+/* identity box */
+
+#identity-box:-moz-locale-dir(ltr) {
+ border-top-left-radius: 1.5px;
+ border-bottom-left-radius: 1.5px;
+}
+
+#identity-box:-moz-locale-dir(rtl) {
+ border-top-right-radius: 1.5px;
+ border-bottom-right-radius: 1.5px;
+}
+
+#identity-box:-moz-focusring {
+ outline: 1px dotted;
+ outline-offset: -3px;
+}
+
+%include ../shared/identity-block/identity-block.inc.css
+
+%include ../shared/notification-icons.inc.css
+
+.popup-notification-body[popupid="addon-progress"],
+.popup-notification-body[popupid="addon-install-confirmation"] {
+ width: 28em;
+ max-width: 28em;
+}
+
+.addon-install-confirmation-name {
+ font-weight: bold;
+}
+
+/* Notification icon box */
+
+.notification-anchor-icon:-moz-focusring {
+ outline: 1px dotted -moz-DialogText;
+}
+
+/* Translation infobar */
+
+%include ../shared/translation/infobar.inc.css
+
+notification[value="translation"] {
+ min-height: 40px;
+}
+
+notification[value="translation"],
+notification[value="translation"] button,
+notification[value="translation"] menulist {
+ min-height: 30px;
+ color: #5A5959;
+}
+
+notification[value="translation"] {
+ background-color: #F2F1F0;
+}
+
+notification[value="translation"] button,
+notification[value="translation"] menulist {
+ padding-inline-end: 1ch;
+}
+
+notification[value="translation"] menulist {
+ border: 1px solid #C1C1C1;
+ background-color: #FFF;
+}
+
+notification[value="translation"] button {
+ border: 1px solid #C1C1C1;
+ background-color: #F2F1F0;
+}
+
+notification[value="translation"] button,
+notification[value="translation"] menulist,
+notification[value="translation"] menulist > .menulist-label-box {
+ margin-inline-start: 1ch;
+ margin-inline-end: 1ch;
+}
+
+notification[value="translation"] button:hover,
+notification[value="translation"] button:active,
+notification[value="translation"] menulist:hover,
+notification[value="translation"] menulist:active {
+ background-color: #E2E1E0;
+}
+
+notification[value="translation"] button[anonid="translate"] {
+ color: #FFF;
+ background-image: linear-gradient(#9FB938, #8DA726);
+ box-shadow: none;
+ border: 1px solid #829C1C;
+}
+
+notification[value="translation"] button[anonid="translate"]:hover,
+notification[value="translation"] button[anonid="translate"]:active {
+ background-image: linear-gradient(#8DA726, #8DA726);
+}
+
+notification[value="translation"] button > .button-box,
+notification[value="translation"] button[type="menu"] > .button-box > .button-menu-dropmarker {
+ padding: 0;
+ margin-inline-start: 3ch;
+}
+
+notification[value="translation"] button:not([type="menu"]) > .button-box {
+ margin-inline-end: 3ch;
+}
+
+notification[value="translation"] menulist > .menulist-dropmarker {
+ display: block;
+}
+
+/* AutoComplete */
+
+%include ../shared/autocomplete.inc.css
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
+ border-top: 1px solid ThreeDShadow;
+}
+
+#treecolAutoCompleteImage {
+ max-width : 36px;
+}
+
+.autocomplete-richlistbox {
+ padding: 4px;
+}
+
+.autocomplete-richlistitem {
+ height: 30px;
+ min-height: 30px;
+ font: message-box;
+ border-radius: 2px;
+ border: 1px solid transparent;
+}
+
+.autocomplete-richlistitem[selected=true] {
+ background-color: Highlight;
+}
+
+.ac-title {
+ font-size: 1.05em;
+}
+
+.ac-tags {
+ font-size: 0.9em;
+}
+
+html|span.ac-tag {
+ background-color: MenuText;
+ color: Menu;
+ border-radius: 2px;
+ border: 1px solid transparent;
+ padding: 0 1px;
+}
+
+.ac-separator,
+.ac-url,
+.ac-action {
+ font-size: 0.9em;
+ color: -moz-nativehyperlinktext;
+}
+
+.ac-title[selected=true],
+.ac-separator[selected],
+.ac-url[selected=true],
+.ac-action[selected=true] {
+ color: inherit !important;
+}
+
+.ac-tags-text[selected] > html|span.ac-tag {
+ background-color: HighlightText;
+ color: Highlight;
+}
+
+html|span.ac-emphasize-text-title,
+html|span.ac-emphasize-text-tag,
+html|span.ac-emphasize-text-url {
+ font-weight: 600;
+}
+
+.ac-type-icon[type=bookmark] {
+ list-style-image: url("chrome://browser/skin/urlbar-star.svg#star");
+}
+
+.ac-type-icon[type=bookmark][selected][current] {
+ list-style-image: url("chrome://browser/skin/urlbar-star.svg#star-inverted");
+}
+
+.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/autocomplete-star.png");
+ width: 16px;
+ height: 16px;
+}
+
+.ac-type-icon[type=keyword],
+.ac-site-icon[type=searchengine],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
+}
+
+.ac-type-icon[type=keyword][selected],
+.ac-site-icon[type=searchengine][selected],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
+}
+
+.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ width: 16px;
+ height: 16px;
+}
+
+.ac-type-icon[type=switchtab],
+.ac-type-icon[type=remotetab] {
+ list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
+}
+
+.ac-type-icon[type=switchtab][selected],
+.ac-type-icon[type=remotetab][selected] {
+ list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
+ color: GrayText;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
+.autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment) {
+ color: GrayText;
+ font-size: smaller;
+}
+
+.autocomplete-treebody::-moz-tree-cell(suggesthint) {
+ border-top: 1px solid GrayText;
+}
+
+/* Combined go/reload/stop button in location bar */
+
+#urlbar-go-button,
+#urlbar-reload-button,
+#urlbar-stop-button {
+ -moz-appearance: none;
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+ padding: 0 9px;
+ margin-inline-start: 5px;
+ border-inline-start: 1px solid var(--urlbar-separator-color);
+ border-image: linear-gradient(transparent 15%,
+ var(--urlbar-separator-color) 15%,
+ var(--urlbar-separator-color) 85%,
+ transparent 85%);
+ border-image-slice: 1;
+}
+
+#urlbar-reload-button {
+ -moz-image-region: rect(0, 14px, 14px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover {
+ -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover:active {
+ -moz-image-region: rect(28px, 14px, 42px, 0);
+}
+
+#urlbar-reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-go-button {
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+}
+
+#urlbar-go-button:hover {
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+#urlbar-go-button:hover:active {
+ -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-stop-button {
+ -moz-image-region: rect(0, 28px, 14px, 14px);
+}
+
+#urlbar-stop-button:not([disabled]):hover {
+ -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+
+#urlbar-stop-button:hover:active {
+ -moz-image-region: rect(28px, 28px, 42px, 14px);
+}
+
+@media (min-resolution: 1.1dppx) {
+ #urlbar-go-button,
+ #urlbar-reload-button,
+ #urlbar-stop-button {
+ list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
+ }
+
+ #urlbar-go-button > .toolbarbutton-icon,
+ #urlbar-reload-button > .toolbarbutton-icon,
+ #urlbar-stop-button > .toolbarbutton-icon {
+ width: 14px;
+ }
+
+ #urlbar-go-button {
+ -moz-image-region: rect(0, 84px, 28px, 56px);
+ }
+
+ #urlbar-go-button:hover {
+ -moz-image-region: rect(28px, 84px, 56px, 56px);
+ }
+
+ #urlbar-go-button:hover:active {
+ -moz-image-region: rect(56px, 84px, 84px, 56px);
+ }
+
+ #urlbar-reload-button {
+ -moz-image-region: rect(0, 28px, 28px, 0);
+ }
+
+ #urlbar-reload-button:not([disabled]):hover {
+ -moz-image-region: rect(28px, 28px, 56px, 0);
+ }
+
+ #urlbar-reload-button:not([disabled]):hover:active {
+ -moz-image-region: rect(56px, 28px, 84px, 0);
+ }
+
+ #urlbar-stop-button {
+ -moz-image-region: rect(0, 56px, 28px, 28px);
+ }
+
+ #urlbar-stop-button:not([disabled]):hover {
+ -moz-image-region: rect(28px, 56px, 56px, 28px);
+ }
+
+ #urlbar-stop-button:hover:active {
+ -moz-image-region: rect(56px, 56px, 84px, 28px);
+ }
+}
+
+/* Popup blocker button */
+#page-report-button {
+ list-style-image: url("chrome://browser/skin/Info.png");
+}
+
+/* Reader mode button */
+
+#reader-mode-button {
+ list-style-image: url("chrome://browser/skin/readerMode.svg");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#reader-mode-button:hover,
+#reader-mode-button[readeractive]:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#reader-mode-button:hover:active,
+#reader-mode-button[readeractive] {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+/* social share panel */
+%include ../shared/social/social.inc.css
+
+.social-share-frame {
+ border-top: 1px solid #f8f8f8;
+ width: 756px;
+ height: 150px;
+}
+
+#share-container {
+ min-width: 756px;
+ background-color: white;
+ background-repeat: no-repeat;
+ background-position: center center;
+}
+#share-container[loading] {
+ background-image: url(chrome://browser/skin/tabbrowser/pendingpaint.png);
+}
+#share-container > browser {
+ transition: opacity 150ms ease-in-out;
+ opacity: 1;
+}
+#share-container[loading] > browser {
+ opacity: 0;
+}
+
+.social-share-toolbar {
+ border-bottom: 1px solid #dedede;
+ padding: 2px;
+}
+
+#social-share-provider-buttons {
+ padding: 0;
+ margin: 0;
+}
+
+.share-provider-button {
+ padding: 5px;
+ margin: 2px;
+}
+
+.share-provider-button > .toolbarbutton-text {
+ display: none;
+}
+.share-provider-button > .toolbarbutton-icon {
+ width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+}
+
+/* bookmarks menu-button */
+
+#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker {
+ -moz-appearance: none !important;
+ -moz-box-align: center;
+}
+
+#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ margin-top: 3px;
+ margin-bottom: 3px;
+}
+
+#bookmarks-menu-button[disabled][cui-areatype="toolbar"] > .toolbarbutton-icon,
+#bookmarks-menu-button[disabled][cui-areatype="toolbar"] > .toolbarbutton-menu-dropmarker,
+#bookmarks-menu-button[disabled][cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker,
+#bookmarks-menu-button[disabled][cui-areatype="toolbar"] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-button[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+#BMB_bookmarksPopup[side="top"],
+#BMB_bookmarksPopup[side="bottom"] {
+ margin-left: -16px;
+ margin-right: -16px;
+}
+
+#BMB_bookmarksPopup[side="left"],
+#BMB_bookmarksPopup[side="right"] {
+ margin-top: -16px;
+ margin-bottom: -16px;
+}
+
+#nav-bar .toolbarbutton-1 > menupopup[side="top"].cui-widget-panel,
+#nav-bar .toolbarbutton-1 > menupopup[side="bottom"].cui-widget-panel {
+ margin-top: -4px;
+}
+
+/* Bookmarking panel */
+#editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48.png");
+ width: 48px;
+ height: 48px;
+}
+
+#editBookmarkPanelStarIcon[unstarred] {
+ list-style-image: url("chrome://browser/skin/places/unstarred48.png");
+}
+
+#editBookmarkPanelTitle {
+ font-size: 130%;
+}
+
+#editBookmarkPanelHeader,
+#editBookmarkPanelContent {
+ margin-bottom: .5em;
+}
+
+/* Implements editBookmarkPanel resizing on folderTree un-collapse. */
+#editBMPanel_folderTree {
+ min-width: 27em;
+}
+
+/* Content area */
+#sidebar {
+ background-color: Window;
+}
+
+#sidebar-header > .close-icon:not(:hover):-moz-lwtheme-brighttext {
+ background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64);
+}
+
+.browserContainer > findbar {
+ background-color: -moz-dialog;
+ color: -moz-DialogText;
+ text-shadow: none;
+}
+
+/* Tabstrip */
+
+%include ../shared/tabs.inc.css
+
+#tabbrowser-tabs {
+ /* override the global style to allow the selected tab to be above the nav-bar */
+ z-index: auto;
+}
+
+#TabsToolbar {
+ min-height: 0;
+ padding: 0;
+ margin-bottom: calc(-1 * var(--tab-toolbar-navbar-overlap));
+}
+
+#TabsToolbar:not(:-moz-lwtheme) {
+ -moz-appearance: menubar;
+ color: -moz-menubartext;
+}
+
+#toolbar-menubar:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag),
+#TabsToolbar:not(:-moz-lwtheme):-moz-system-metric(menubar-drag) {
+ -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-drag");
+}
+
+.tabbrowser-tab:focus > .tab-stack > .tab-content {
+ outline: 1px dotted;
+ outline-offset: -6px;
+}
+
+#context_reloadTab {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu");
+}
+
+#context_closeOtherTabs {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu");
+}
+
+#context_closeOtherTabs[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu&state=disabled");
+}
+
+#context_undoCloseTab {
+ list-style-image: url("moz-icon://stock/gtk-undelete?size=menu");
+}
+
+#context_closeTab {
+ list-style-image: url("moz-icon://stock/gtk-close?size=menu");
+}
+
+/* Tab drag and drop */
+.tab-drop-indicator {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
+ margin-bottom: -9px;
+ z-index: 3;
+}
+
+/* Tab close button */
+.tab-close-button:not([selected]):not(:hover) {
+ background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 64, 16, 48);
+}
+
+.tab-close-button:not(:hover):-moz-lwtheme-brighttext,
+#TabsToolbar[brighttext] .tab-close-button:not([selected]):not(:hover) {
+ background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64);
+}
+
+.tab-close-button:not(:hover):-moz-lwtheme-darktext {
+ background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 96, 16, 80);
+}
+
+/* Tabstrip new tab button */
+.tabs-newtab-button,
+#TabsToolbar > #new-tab-button ,
+#TabsToolbar > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab.svg);
+ -moz-image-region: auto;
+}
+
+#TabsToolbar[brighttext] .tabs-newtab-button,
+#TabsToolbar[brighttext] > #new-tab-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.svg);
+}
+
+/* Tabbrowser arrowscrollbox arrows */
+.tabbrowser-arrowscrollbox > .scrollbutton-up > .toolbarbutton-icon,
+.tabbrowser-arrowscrollbox > .scrollbutton-down > .toolbarbutton-icon {
+ -moz-appearance: none;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-appearance: none;
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
+ margin: 0 0 var(--tab-toolbar-navbar-overlap);
+}
+
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-up,
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-down {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled],
+.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] {
+ opacity: .4;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
+ transform: scaleX(-1);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ transition: 1s background-color ease-out;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
+ background-color: Highlight;
+ transition: none;
+}
+
+#TabsToolbar .toolbarbutton-1 {
+ margin-bottom: var(--tab-toolbar-navbar-overlap);
+}
+
+#alltabs-button {
+ list-style-image: url("chrome://browser/skin/tabbrowser/alltabs.png");
+}
+
+#TabsToolbar[brighttext] > #alltabs-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #alltabs-button {
+ list-style-image: url("chrome://browser/skin/tabbrowser/alltabs-inverted.png");
+}
+
+#alltabs-button > .toolbarbutton-icon {
+ padding: 9px 6px 6px;
+}
+
+#alltabs-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+/* All tabs menupopup */
+.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.alltabs-item[selected="true"] {
+ font-weight: bold;
+}
+
+.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+/* Sidebar */
+#sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+ margin-inline-end: 4px;
+}
+
+toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/toolbar/chevron.gif") !important;
+}
+
+toolbar[brighttext] toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/toolbar/chevron-inverted.png") !important;
+}
+
+toolbarbutton.chevron:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+toolbarbutton.chevron > .toolbarbutton-text,
+toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+toolbarbutton.chevron > .toolbarbutton-icon {
+ margin: 0;
+}
+
+/* Status panel */
+
+.statuspanel-label {
+ margin: 0;
+ padding: 2px 4px;
+ background: -moz-dialog;
+ border: 1px none ThreeDShadow;
+ border-top-style: solid;
+ color: -moz-dialogText;
+ text-shadow: none;
+}
+
+.statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
+.statuspanel-label:-moz-locale-dir(rtl)[mirror] {
+ border-right-style: solid;
+ border-top-right-radius: .3em;
+ margin-right: 1em;
+}
+
+.statuspanel-label:-moz-locale-dir(rtl):not([mirror]),
+.statuspanel-label:-moz-locale-dir(ltr)[mirror] {
+ border-left-style: solid;
+ border-top-left-radius: .3em;
+ margin-left: 1em;
+}
+
+%include ../shared/fullscreen/warning.inc.css
+%include ../shared/ctrlTab.inc.css
+%include ../../../devtools/client/themes/responsivedesign.inc.css
+%include ../../../devtools/client/themes/commandline.inc.css
+%include ../shared/plugin-doorhanger.inc.css
+
+notification.pluginVulnerable > .notification-inner > .messageCloseButton:not(:hover) {
+ background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64);
+}
+
+
+%include downloads/indicator.css
+
+.gcli-panel {
+ padding: 0;
+}
+
+.gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
+ color: hsl(210,11%,16%);
+}
+
+/* Error counter */
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ color: #FDF3DE;
+ min-width: 16px;
+ text-shadow: none;
+ background-image: linear-gradient(#B4211B, #8A1915);
+ border-radius: 1px;
+ margin-inline-end: 2px;
+}
+
+/* Customization mode */
+
+%include ../shared/customizableui/customizeMode.inc.css
+
+#main-window[customize-entered] > #tab-view-deck {
+ background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
+ linear-gradient(to bottom, #bcbcbc, #b5b5b5);
+ background-attachment: fixed;
+}
+
+#main-window[customization-lwtheme] > #tab-view-deck:-moz-lwtheme {
+ background-repeat: no-repeat;
+ background-position: right top;
+ background-attachment: fixed;
+ /* The image will get set from CustomizeMode.jsm */
+ background-image: none;
+ background-color: transparent;
+}
+
+#main-window[customization-lwtheme]:-moz-lwtheme {
+ background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
+ url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
+ linear-gradient(to bottom, #bcbcbc, #b5b5b5);
+ background-color: #b5b5b5;
+ background-repeat: repeat;
+ background-attachment: fixed;
+ background-position: left top;
+}
+
+#main-window[customize-entered] #browser-bottombox,
+#main-window[customize-entered] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar),
+#main-window[customize-entered] #customization-container {
+ border: 3px solid hsla(0,0%,0%,.1);
+ border-top-width: 0;
+ background-clip: padding-box;
+ background-origin: padding-box;
+ -moz-border-right-colors: hsla(0,0%,0%,.05) hsla(0,0%,0%,.1) hsla(0,0%,0%,.2);
+ -moz-border-bottom-colors: hsla(0,0%,0%,.05) hsla(0,0%,0%,.1) hsla(0,0%,0%,.2);
+ -moz-border-left-colors: hsla(0,0%,0%,.05) hsla(0,0%,0%,.1) hsla(0,0%,0%,.2);
+}
+
+#main-window[customize-entered] #customization-container,
+#main-window[customize-entered] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ border-bottom-width: 0;
+}
+
+#main-window[customize-entered] #TabsToolbar {
+ -moz-appearance: none;
+ background-clip: padding-box;
+ border-right: 3px solid transparent;
+ border-left: 3px solid transparent;
+}
+
+/* The :hover:active style from toolkit doesn't seem to work in this panel so just use :active. */
+.customization-tipPanel-closeBox > .close-icon:active {
+ background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 48, 16, 32);
+}
+
+/* End customization mode */
+
+
+#main-window[privatebrowsingmode=temporary] #private-browsing-indicator {
+ background: url("chrome://browser/skin/privatebrowsing-mask.png") center no-repeat;
+ width: 40px;
+}
+
+%include ../shared/UITour.inc.css
+
+#UITourHighlight {
+ /* Below are some fixes for people without an X compositor on Linux.
+ This is why we can't have nice things: */
+ /* Animations don't repaint properly without an X compositor. */
+ animation-name: none !important;
+ /* Opacity rounds to 0 or 1 on Linux without an X compositor so make the
+ background be transparent in that case by having all alpha values < 0.5 */
+ background-image: radial-gradient(50% 100%, rgba(0,149,220,0.3) 50%, rgba(0,149,220,0.49) 100%);
+ /* The highlight isn't anti-aliased without an X compositor so make it thicker.
+ Make it a darker color since we don't have the box-shadow in this case. */
+ border: 4px solid rgb(0,149,220);
+}
+
+#UITourTooltipDescription {
+ font-size: 1.05rem;
+}
+
+#UITourTooltipClose {
+ margin-inline-end: -4px;
+ height: 16px;
+ width: 16px;
+}
+
+/**
+ * Override the --arrowpanel-padding so the background extends
+ * to the sides and bottom of the panel.
+ */
+#UITourTooltipButtons {
+ margin-left: -10px;
+ margin-bottom: -10px;
+}
+
+%include ../shared/contextmenu.inc.css
+
+#context-navigation > .menuitem-iconic > .menu-iconic-left {
+ /* override toolkit/themes/linux/global/menu.css */
+ padding-inline-end: 0 !important;
+ margin-inline-end: 0 !important;
+}
+
+.browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+ overflow: hidden;
+}
+
+.webextension-popup-browser {
+ border-radius: inherit;
+}
diff --git a/browser/themes/linux/click-to-play-warning-stripes.png b/browser/themes/linux/click-to-play-warning-stripes.png
new file mode 100644
index 000000000..29f15f7b8
--- /dev/null
+++ b/browser/themes/linux/click-to-play-warning-stripes.png
Binary files differ
diff --git a/browser/themes/linux/communicator/communicator.css b/browser/themes/linux/communicator/communicator.css
new file mode 100644
index 000000000..0b57574fd
--- /dev/null
+++ b/browser/themes/linux/communicator/communicator.css
@@ -0,0 +1,6 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://global/skin/");
+
diff --git a/browser/themes/linux/communicator/jar.mn b/browser/themes/linux/communicator/jar.mn
new file mode 100644
index 000000000..dfd20c523
--- /dev/null
+++ b/browser/themes/linux/communicator/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+% skin communicator classic/1.0 %skin/classic/communicator/
+ skin/classic/communicator/communicator.css
diff --git a/browser/themes/linux/communicator/moz.build b/browser/themes/linux/communicator/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/themes/linux/communicator/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/themes/linux/controlcenter/panel.css b/browser/themes/linux/controlcenter/panel.css
new file mode 100644
index 000000000..fab9aa3a4
--- /dev/null
+++ b/browser/themes/linux/controlcenter/panel.css
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/controlcenter/panel.inc.css
+
+.identity-popup-expander:-moz-focusring {
+ padding: 1px;
+}
+
+.identity-popup-expander:-moz-focusring > .button-box {
+ outline: 1px -moz-dialogtext dotted;
+}
diff --git a/browser/themes/linux/customizableui/background-noise-toolbar.png b/browser/themes/linux/customizableui/background-noise-toolbar.png
new file mode 100644
index 000000000..d09ba9daf
--- /dev/null
+++ b/browser/themes/linux/customizableui/background-noise-toolbar.png
Binary files differ
diff --git a/browser/themes/linux/customizableui/customizeMode-gridTexture.png b/browser/themes/linux/customizableui/customizeMode-gridTexture.png
new file mode 100644
index 000000000..a7c2775cf
--- /dev/null
+++ b/browser/themes/linux/customizableui/customizeMode-gridTexture.png
Binary files differ
diff --git a/browser/themes/linux/customizableui/customizeMode-separatorHorizontal.png b/browser/themes/linux/customizableui/customizeMode-separatorHorizontal.png
new file mode 100644
index 000000000..5e17cb4db
--- /dev/null
+++ b/browser/themes/linux/customizableui/customizeMode-separatorHorizontal.png
Binary files differ
diff --git a/browser/themes/linux/customizableui/customizeMode-separatorVertical.png b/browser/themes/linux/customizableui/customizeMode-separatorVertical.png
new file mode 100644
index 000000000..dc4caee81
--- /dev/null
+++ b/browser/themes/linux/customizableui/customizeMode-separatorVertical.png
Binary files differ
diff --git a/browser/themes/linux/customizableui/panelUI.css b/browser/themes/linux/customizableui/panelUI.css
new file mode 100644
index 000000000..289faa085
--- /dev/null
+++ b/browser/themes/linux/customizableui/panelUI.css
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/customizableui/panelUI.inc.css
+
+.panel-subviews {
+ background-color: var(--arrowpanel-background);
+}
+
+#BMB_bookmarksPopup > menuitem[type="checkbox"] {
+ -moz-appearance: none !important; /* important, to override toolkit rule */
+}
+
+#BMB_bookmarksPopup menupopup {
+ -moz-appearance: none;
+ background: var(--arrowpanel-background);
+ color: var(--arrowpanel-color);
+ border: 1px solid var(--arrowpanel-border-color);
+ margin-top: -6px;
+ padding-top: 1px;
+}
+
+/* Add some space at the top because there are no headers: */
+#BMB_bookmarksPopup menupopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox {
+ padding-top: 4px;
+}
+
+.subviewbutton > .toolbarbutton-text {
+ padding-top: 3px;
+ padding-bottom: 3px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-appearance: none;
+ border: 0;
+ margin-inline-start: 3px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ margin-inline-start: 0;
+}
+
+.subviewbutton > .toolbarbutton-text {
+ padding-inline-start: 16px;
+}
+
+.subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .restoreallitem, .bookmark-item) > .toolbarbutton-text {
+ padding-inline-start: 0;
+}
+
+/* subviewbutton entries for social sidebars have images that come from external
+/* sources, and are not guaranteed to be the size we want, so force the size on
+/* those icons. */
+toolbarbutton.social-provider-menuitem > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .restoreallitem, .bookmark-item)[checked="true"] > .toolbarbutton-icon {
+ visibility: hidden;
+}
+
+menuitem.subviewbutton {
+ -moz-appearance: none !important;
+}
+
+menu.subviewbutton > .menu-right {
+ -moz-appearance: none;
+ list-style-image: url(chrome://browser/skin/places/bookmarks-menu-arrow.png);
+ -moz-image-region: rect(0, 16px, 16px, 0);
+ width: 16px;
+ height: 16px;
+}
+
+menu[disabled="true"].subviewbutton > .menu-right {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.subviewbutton > .toolbarbutton-icon {
+ margin-inline-end: 5px !important;
+}
+
+.subviewbutton > .menu-right,
+.subviewbutton > .menu-iconic-left {
+ padding-top: 1px;
+ /* These need !important to override menu.css */
+ margin-top: 1px !important;
+ margin-bottom: 2px !important;
+}
+
+.subviewradio > .radio-label-box {
+ -moz-appearance: none;
+}
diff --git a/browser/themes/linux/devedition.css b/browser/themes/linux/devedition.css
new file mode 100644
index 000000000..1f16d5d63
--- /dev/null
+++ b/browser/themes/linux/devedition.css
@@ -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/.
+
+%include ../shared/devedition.inc.css
+
+:root {
+ --forwardbutton-width: 29px;
+}
+
+:root[devtoolstheme="light"] {
+ --urlbar-dropmarker-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
+ --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
+ --urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
+ --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
+ --urlbar-dropmarker-2x-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
+ --urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
+ --urlbar-dropmarker-hover-2x-region: rect(0, 22px, 14px, 11px);
+ --urlbar-dropmarker-active-2x-region: rect(0px, 33px, 14px, 22px);
+}
+
+:root[devtoolstheme="dark"] .findbar-closebutton:not(:hover),
+:root[devtoolstheme="dark"] #sidebar-header > .close-icon:not(:hover),
+.tab-close-button[selected]:not(:hover) {
+ background-image: -moz-image-rect(url("chrome://global/skin/icons/close.svg"), 0, 80, 16, 64);
+}
+
+/* The menubar and tabs toolbar should match the devedition theme */
+#TabsToolbar,
+#toolbar-menubar {
+ -moz-appearance: none !important;
+}
+#main-menubar {
+ color: var(--chrome-color);
+}
+#main-menubar > menu:not([open]) {
+ color: inherit;
+}
+
+/* Allow buttons with -moz-appearance set to look normal on hover and open states */
+#navigator-toolbox .toolbarbutton-1:-moz-any(:hover, [open="true"]),
+#PlacesToolbar toolbarbutton.bookmark-item:-moz-any(:hover, [open="true"]) {
+ color: initial;
+}
+
+/* Square back and forward buttons */
+#back-button > .toolbarbutton-icon,
+#forward-button > .toolbarbutton-icon {
+ margin: 0;
+ border: 1px solid var(--chrome-nav-bar-controls-border-color);
+ padding: 2px 5px;
+ background: var(--chrome-nav-buttons-background);
+ box-shadow: none !important;
+}
+
+#forward-button > .toolbarbutton-icon {
+ border-inline-start: none;
+}
+
+/* Override a box shadow for disabled back button */
+#main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
+ box-shadow: none !important;
+}
+
+#back-button:hover:not([disabled="true"]) > .toolbarbutton-icon,
+#forward-button:hover:not([disabled="true"]) > .toolbarbutton-icon {
+ background: var(--chrome-nav-buttons-hover-background) !important;
+}
+
+#back-button > .toolbarbutton-icon {
+ border-radius: 2px 0 0 2px !important;
+}
+
+.urlbar-history-dropmarker {
+ -moz-appearance: none;
+ padding: 0 3px;
+ list-style-image: var(--urlbar-dropmarker-url);
+ -moz-image-region: var(--urlbar-dropmarker-region);
+}
+
+/* Add the proper background for tab overflow */
+#alltabs-button,
+#new-tab-button {
+ background: var(--chrome-background-color);
+}
+
+#new-tab-button:hover > .toolbarbutton-icon {
+ border-color: transparent !important;
+}
+
+/* Prevent double border below tabs toolbar */
+#TabsToolbar:not([collapsed="true"]) + #nav-bar {
+ border-top-width: 0 !important;
+}
+
+/* Fix the bad-looking text-shadow in the sidebar header: */
+.sidebar-header,
+#sidebar-header {
+ text-shadow: none;
+}
+
+.ac-type-icon {
+ /* Left-align the type icon in awesomebar popup results with the icon in the
+ urlbar. */
+ margin-inline-start: 11px;
+}
diff --git a/browser/themes/linux/downloads/allDownloadsViewOverlay.css b/browser/themes/linux/downloads/allDownloadsViewOverlay.css
new file mode 100644
index 000000000..f5a05c9b0
--- /dev/null
+++ b/browser/themes/linux/downloads/allDownloadsViewOverlay.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/. */
+
+%include ../../shared/downloads/allDownloadsViewOverlay.inc.css
+
+/*** List items ***/
+
+:root {
+ --downloads-item-height: 5em;
+}
diff --git a/browser/themes/linux/downloads/download-glow-menuPanel.png b/browser/themes/linux/downloads/download-glow-menuPanel.png
new file mode 100644
index 000000000..b8443f045
--- /dev/null
+++ b/browser/themes/linux/downloads/download-glow-menuPanel.png
Binary files differ
diff --git a/browser/themes/linux/downloads/download-notification-finish.png b/browser/themes/linux/downloads/download-notification-finish.png
new file mode 100644
index 000000000..f5fa12053
--- /dev/null
+++ b/browser/themes/linux/downloads/download-notification-finish.png
Binary files differ
diff --git a/browser/themes/linux/downloads/download-notification-start.png b/browser/themes/linux/downloads/download-notification-start.png
new file mode 100644
index 000000000..bd548b183
--- /dev/null
+++ b/browser/themes/linux/downloads/download-notification-start.png
Binary files differ
diff --git a/browser/themes/linux/downloads/downloads.css b/browser/themes/linux/downloads/downloads.css
new file mode 100644
index 000000000..41ea68d43
--- /dev/null
+++ b/browser/themes/linux/downloads/downloads.css
@@ -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 ../../shared/downloads/downloads.inc.css
+
+/*** List items and similar elements in the summary ***/
+
+:root {
+ --downloads-item-height: 5.5em;
+ --downloads-item-font-size-factor: 0.9;
+ --downloads-item-details-opacity: 0.6;
+}
+
+@keyfocus@ @itemFocused@,
+@keyfocus@ #downloadsSummary:focus,
+@keyfocus@ .downloadsPanelFooterButton:focus,
+.downloadButton:focus {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
diff --git a/browser/themes/linux/downloads/indicator.css b/browser/themes/linux/downloads/indicator.css
new file mode 100644
index 000000000..6e9d9ace5
--- /dev/null
+++ b/browser/themes/linux/downloads/indicator.css
@@ -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/. */
+
+/*** Status and progress indicator ***/
+
+#downloads-animation-container {
+ min-height: 1px;
+ min-width: 1px;
+ height: 1px;
+ margin-bottom: -1px;
+ /* Makes the outermost animation container element positioned, so that its
+ contents are rendered over the main browser window in the Z order.
+ This is required by the animated event notification. */
+ position: relative;
+ /* The selected tab may overlap #downloads-indicator-notification */
+ z-index: 5;
+}
+
+/*** Main indicator icon ***/
+
+@media not all and (min-resolution: 1.1dppx) {
+ #downloads-button {
+ --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 198, 18, 180);
+ --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
+ --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+ --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ #downloads-button {
+ --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
+ --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 36, 396, 72, 360);
+ --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
+ --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 36, 396, 72, 360);
+ }
+}
+
+#downloads-button[cui-areatype="toolbar"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background: var(--downloads-indicator-icon) center no-repeat;
+ width: 18px;
+ height: 18px;
+ background-size: 18px;
+}
+
+toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: var(--downloads-indicator-icon-inverted);
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ display: -moz-box;
+ height: 8px;
+ width: 8px;
+ min-width: 0;
+ border-radius: 50%;
+ /* "!important" is necessary to override the rule in toolbarbutton.css */
+ margin-top: -1px !important;
+ margin-right: -2px !important;
+}
+
+#downloads-button[cui-areatype="toolbar"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ height: 7px;
+ width: 7px;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #D90000;
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #FFBF00;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+ filter: none;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: var(--downloads-indicator-icon-attention);
+}
+
+toolbar[brighttext] #downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: var(--downloads-indicator-icon-attention-inverted);
+}
+
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
+ list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
+ -moz-image-region: auto;
+}
+
+/* In the next few rules, we use :not([counter]) as a shortcut that is
+ equivalent to -moz-any([progress], [paused]). */
+
+#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: var(--downloads-indicator-icon) center no-repeat;
+ background-size: 12px;
+}
+
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
+ background-image: var(--downloads-indicator-icon-inverted);
+}
+
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: var(--downloads-indicator-icon-attention);
+}
+
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: var(--downloads-indicator-icon-attention-inverted);
+}
+
+/*** Download notifications ***/
+
+#downloads-indicator-notification {
+ opacity: 0;
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+}
+
+@keyframes downloadsIndicatorNotificationStartRight {
+ from { opacity: 0; transform: translate(-128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+@keyframes downloadsIndicatorNotificationStartLeft {
+ from { opacity: 0; transform: translate(128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+#downloads-notification-anchor[notification="start"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
+ animation-name: downloadsIndicatorNotificationStartRight;
+ animation-duration: 1s;
+}
+
+#downloads-notification-anchor[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-notification {
+ animation-name: downloadsIndicatorNotificationStartLeft;
+}
+
+@keyframes downloadsIndicatorNotificationFinish {
+ from { opacity: 0; transform: scale(1); }
+ 20% { opacity: .65; animation-timing-function: ease-in; }
+ to { opacity: 0; transform: scale(8); }
+}
+
+#downloads-notification-anchor[notification="finish"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
+ animation-name: downloadsIndicatorNotificationFinish;
+ animation-duration: 1s;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+ height: 10px;
+ margin: 0;
+ color: hsl(0,0%,30%);
+ text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
+ font-size: 10px;
+ line-height: 10px;
+ text-align: center;
+}
+
+toolbar[brighttext] #downloads-indicator-counter {
+ color: white;
+ text-shadow: 0 0 1px rgba(0,0,0,.7),
+ 0 1px 1.5px rgba(0,0,0,.5);
+}
+
+#downloads-indicator-progress {
+ width: 18px;
+ height: 6px;
+ min-width: 0;
+ min-height: 0;
+ margin-top: 1px;
+ margin-bottom: 2px;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
+ background-clip: padding-box, border-box;
+ background-color: rgb(255, 135, 94);
+ background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
+ border: 1px solid;
+ border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
+ border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ background-image: linear-gradient(#505050, #575757);
+ border: 1px solid;
+ border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
+ border-inline-start: none;
+ border-radius: 0 2px 2px 0;
+}
+
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+ background-color: rgb(220, 230, 81);
+}
+
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+ background-image: linear-gradient(#4b5000, #515700);
+}
diff --git a/browser/themes/linux/feeds/feedIcon.png b/browser/themes/linux/feeds/feedIcon.png
new file mode 100644
index 000000000..a788fffb0
--- /dev/null
+++ b/browser/themes/linux/feeds/feedIcon.png
Binary files differ
diff --git a/browser/themes/linux/feeds/feedIcon16.png b/browser/themes/linux/feeds/feedIcon16.png
new file mode 100644
index 000000000..f8536a4e1
--- /dev/null
+++ b/browser/themes/linux/feeds/feedIcon16.png
Binary files differ
diff --git a/browser/themes/linux/feeds/subscribe-ui.css b/browser/themes/linux/feeds/subscribe-ui.css
new file mode 100644
index 000000000..f1650036e
--- /dev/null
+++ b/browser/themes/linux/feeds/subscribe-ui.css
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.alwaysUse {
+ padding: 5px;
+}
diff --git a/browser/themes/linux/feeds/subscribe.css b/browser/themes/linux/feeds/subscribe.css
new file mode 100644
index 000000000..a9b59d2e1
--- /dev/null
+++ b/browser/themes/linux/feeds/subscribe.css
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+html {
+ background: -moz-Dialog;
+ font: 3mm tahoma,arial,helvetica,sans-serif;
+}
+
+#subscribeUsingDescription,
+#subscribeButton {
+ display: block;
+}
+
+#subscribeUsingDescription {
+ margin-bottom: 0.5em;
+}
+
+#subscribeButton {
+ margin-top: 0.5em;
+ margin-inline-start: auto;
+}
+
+#feedBody {
+ border: 1px solid THreeDShadow;
+ padding: 3em;
+ padding-inline-start: 30px;
+ margin: 2em auto;
+ background: -moz-Field;
+}
+
+#feedHeaderContainer {
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ margin: -4em auto 0 auto;
+ background-color: InfoBackground;
+ -moz-appearance: -moz-gtk-info-bar;
+ display: flex;
+}
+
+#feedHeaderContainerSpacer {
+ flex-grow: 1;
+}
+
+#feedHeader {
+ margin-top: 4.9em;
+ margin-bottom: 1em;
+ margin-inline-start: 1.4em;
+ margin-inline-end: 1em;
+ padding-inline-start: 2.9em;
+ font-size: 110%;
+ color: -moz-gtk-info-bar-text;
+}
+
+.feedBackground {
+ background: url("chrome://browser/skin/feeds/feedIcon.png") 0% 10% no-repeat;
+}
+
+.videoPodcastBackground {
+ background: url("chrome://browser/skin/feeds/videoFeedIcon.png") 0% 10% no-repeat;
+}
+
+.audioPodcastBackground {
+ background: url("chrome://browser/skin/feeds/audioFeedIcon.png") 0% 10% no-repeat;
+}
+
+#feedHeader[dir="rtl"] {
+ background-position: 100% 10%;
+}
+
+#feedIntroText {
+ display: none;
+}
+
+#feedHeader[firstrun="true"] #feedIntroText {
+ padding-top: 0.1em;
+ padding-inline-start: 0.6em;
+ display: block;
+}
+
+#feedHeader[firstrun="true"] > #feedSubscribeLine {
+ padding-inline-start: 1.8em;
+}
+
+#feedSubscribeLine {
+ padding-top: 0.2em;
+ padding-inline-start: 0.5em;
+}
+
+/* Don't print subscription UI */
+@media print {
+ #feedHeaderContainer {
+ display: none;
+ }
+}
+
+body {
+ margin: 0;
+ padding: 0 3em;
+ color: -moz-fieldText;
+ font: message-box;
+}
+
+h1 {
+ font-size: 160%;
+ border-bottom: 2px solid ThreeDLightShadow;
+ margin: 0 0 .2em 0;
+}
+
+h2 {
+ color: GrayText;
+ font-size: 110%;
+ font-weight: normal;
+ margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+ float: right;
+ margin-inline-start: .6em;
+ margin-inline-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+a[href] img {
+ border: none;
+}
+
+#feedTitleContainer {
+ margin-inline-start: 0;
+ margin-inline-end: .6em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#feedTitleImage {
+ margin-inline-start: .6em;
+ margin-inline-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-width: 300px;
+ max-height: 150px;
+}
+
+.feedEntryContent {
+ font-size: 110%;
+}
+
+.link {
+ color: #0000FF;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.link:hover:active {
+ color: #FF0000;
+}
+
+.lastUpdated {
+ font-size: 85%;
+ font-weight: normal;
+}
+
+.type-icon {
+ vertical-align: bottom;
+ height: 16px;
+ width: 16px;
+}
+
+.enclosures {
+ border: 1px solid THreeDShadow;
+ padding: 1em;
+ margin: 1em auto;
+ background: -moz-Dialog;
+}
+
+.enclosure {
+ vertical-align: middle;
+ margin-left: 2px;
+}
+
+.handlersMenuList > .menulist-label-box > .menulist-icon {
+ max-width: 16px;
+ max-height: 16px;
+}
diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn
new file mode 100644
index 000000000..e09029438
--- /dev/null
+++ b/browser/themes/linux/jar.mn
@@ -0,0 +1,140 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+% skin browser classic/1.0 %skin/classic/browser/
+% override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu
+#include ../shared/jar.inc.mn
+ skin/classic/browser/sanitizeDialog.css
+ skin/classic/browser/aboutSessionRestore-window-icon.png
+ skin/classic/browser/aboutSyncTabs.css
+* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
+ skin/classic/browser/actionicon-tab.png
+* skin/classic/browser/browser.css
+* skin/classic/browser/devedition.css
+* skin/classic/browser/browser-lightweightTheme.css
+ skin/classic/browser/click-to-play-warning-stripes.png
+ skin/classic/browser/Info.png
+ skin/classic/browser/menuPanel-customize.png
+ skin/classic/browser/menuPanel-customize@2x.png
+ skin/classic/browser/menuPanel-exit.png
+ skin/classic/browser/menuPanel-exit@2x.png
+ skin/classic/browser/menuPanel-help.png
+ skin/classic/browser/menuPanel-help@2x.png
+ skin/classic/browser/monitor.png
+ skin/classic/browser/monitor_16-10.png
+* skin/classic/browser/pageInfo.css
+ skin/classic/browser/pageInfo.png
+ skin/classic/browser/page-livemarks.png
+ skin/classic/browser/Privacy-16.png
+ skin/classic/browser/privatebrowsing-mask.png
+ skin/classic/browser/reload-stop-go.png
+ skin/classic/browser/reload-stop-go@2x.png
+ skin/classic/browser/searchbar.css
+ skin/classic/browser/Security-broken.png
+ skin/classic/browser/setDesktopBackground.css
+ skin/classic/browser/slowStartup-16.png
+ skin/classic/browser/Toolbar.png
+ skin/classic/browser/Toolbar@2x.png
+ skin/classic/browser/Toolbar-inverted.png
+ skin/classic/browser/Toolbar-inverted@2x.png
+ skin/classic/browser/Toolbar-small.png
+ skin/classic/browser/webRTC-indicator.css
+* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)
+ skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
+ skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png)
+ skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)
+ skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png)
+* skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css)
+* skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
+ skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
+ skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
+ skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
+* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
+ skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
+ skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/places/autocomplete-star.png (places/autocomplete-star.png)
+ skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
+ skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
+ skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
+ skin/classic/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png)
+ skin/classic/browser/places/bookmarks-menu-arrow.png (places/bookmarks-menu-arrow.png)
+ skin/classic/browser/places/calendar.png (places/calendar.png)
+* skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
+ skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
+ skin/classic/browser/places/starred48.png (places/starred48.png)
+ skin/classic/browser/places/unstarred48.png (places/unstarred48.png)
+ skin/classic/browser/places/places.css (places/places.css)
+ skin/classic/browser/places/organizer.css (places/organizer.css)
+ skin/classic/browser/places/organizer.xml (places/organizer.xml)
+ skin/classic/browser/places/query.png (places/query.png)
+ skin/classic/browser/places/tag.png (places/tag.png)
+ skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
+ skin/classic/browser/places/unsortedBookmarks.png (places/unsortedBookmarks.png)
+ skin/classic/browser/places/downloads.png (places/downloads.png)
+ skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
+ skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
+* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
+* skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css)
+ skin/classic/browser/preferences/applications.css (preferences/applications.css)
+ skin/classic/browser/social/services-16.png (social/services-16.png)
+ skin/classic/browser/social/services-64.png (social/services-64.png)
+ skin/classic/browser/social/share-button.png (social/share-button.png)
+ skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
+ skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png)
+ skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
+ skin/classic/browser/tabbrowser/newtab.svg (tabbrowser/newtab.svg)
+ skin/classic/browser/tabbrowser/newtab-inverted.svg (tabbrowser/newtab-inverted.svg)
+ skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
+ skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
+ skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)
+ skin/classic/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
+ skin/classic/browser/tabbrowser/tab-background-middle@2x.png (tabbrowser/tab-background-middle@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-start.png (tabbrowser/tab-background-start.png)
+ skin/classic/browser/tabbrowser/tab-background-start@2x.png (tabbrowser/tab-background-start@2x.png)
+
+# NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
+# Makefile.in with a non-default marker of "%" and the result of that gets packaged.
+ skin/classic/browser/tabbrowser/tab-selected-end.svg (tab-selected-end.svg)
+ skin/classic/browser/tabbrowser/tab-selected-start.svg (tab-selected-start.svg)
+
+ skin/classic/browser/tabbrowser/tab-stroke-end.png (tabbrowser/tab-stroke-end.png)
+ skin/classic/browser/tabbrowser/tab-stroke-end@2x.png (tabbrowser/tab-stroke-end@2x.png)
+ skin/classic/browser/tabbrowser/tab-stroke-start.png (tabbrowser/tab-stroke-start.png)
+ skin/classic/browser/tabbrowser/tab-stroke-start@2x.png (tabbrowser/tab-stroke-start@2x.png)
+ skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
+
+ skin/classic/browser/sync-16.png
+ skin/classic/browser/sync-32.png
+ skin/classic/browser/sync-bg.png
+ skin/classic/browser/sync-128.png
+ skin/classic/browser/sync-desktopIcon.svg (../shared/sync-desktopIcon.svg)
+ skin/classic/browser/sync-horizontalbar.png
+ skin/classic/browser/sync-horizontalbar@2x.png
+ skin/classic/browser/sync-mobileIcon.svg (../shared/sync-mobileIcon.svg)
+ skin/classic/browser/sync-notification-24.png
+ skin/classic/browser/syncProgress-menuPanel.png
+ skin/classic/browser/syncProgress-menuPanel@2x.png
+ skin/classic/browser/syncProgress-toolbar.png
+ skin/classic/browser/syncProgress-toolbar-inverted.png
+ skin/classic/browser/syncSetup.css
+ skin/classic/browser/syncCommon.css
+ skin/classic/browser/syncQuota.css
+ skin/classic/browser/syncProgress-horizontalbar.png
+ skin/classic/browser/syncProgress-horizontalbar@2x.png
+#ifdef E10S_TESTING_ONLY
+ skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
+#endif
+
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
+% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
+% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
+% override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
+% override chrome://browser/skin/feeds/videoFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
diff --git a/browser/themes/linux/linuxShared.inc b/browser/themes/linux/linuxShared.inc
new file mode 100644
index 000000000..80f88ae73
--- /dev/null
+++ b/browser/themes/linux/linuxShared.inc
@@ -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/. */
+
+%filter substitution
+
+%define toolbarHighlight hsla(0,0%,100%,.15)
+%define toolbarHighlightLWT rgba(255,255,255,.4)
+/* navbarInsetHighlight is tightly coupled to the toolbarHighlight constant. */
+%define navbarInsetHighlight hsla(0,0%,100%,.4)
+%define fgTabTexture linear-gradient(transparent 2px, @toolbarHighlight@ 2px, @toolbarHighlight@)
+%define fgTabTextureLWT linear-gradient(transparent 2px, @toolbarHighlightLWT@ 2px, @toolbarHighlightLWT@)
+%define fgTabBackgroundColor -moz-dialog
diff --git a/browser/themes/linux/menuPanel-customize.png b/browser/themes/linux/menuPanel-customize.png
new file mode 100644
index 000000000..e0b0a70a3
--- /dev/null
+++ b/browser/themes/linux/menuPanel-customize.png
Binary files differ
diff --git a/browser/themes/linux/menuPanel-customize@2x.png b/browser/themes/linux/menuPanel-customize@2x.png
new file mode 100644
index 000000000..5ce3b766a
--- /dev/null
+++ b/browser/themes/linux/menuPanel-customize@2x.png
Binary files differ
diff --git a/browser/themes/linux/menuPanel-exit.png b/browser/themes/linux/menuPanel-exit.png
new file mode 100644
index 000000000..8daaffe95
--- /dev/null
+++ b/browser/themes/linux/menuPanel-exit.png
Binary files differ
diff --git a/browser/themes/linux/menuPanel-exit@2x.png b/browser/themes/linux/menuPanel-exit@2x.png
new file mode 100644
index 000000000..d48426546
--- /dev/null
+++ b/browser/themes/linux/menuPanel-exit@2x.png
Binary files differ
diff --git a/browser/themes/linux/menuPanel-help.png b/browser/themes/linux/menuPanel-help.png
new file mode 100644
index 000000000..962001ff6
--- /dev/null
+++ b/browser/themes/linux/menuPanel-help.png
Binary files differ
diff --git a/browser/themes/linux/menuPanel-help@2x.png b/browser/themes/linux/menuPanel-help@2x.png
new file mode 100644
index 000000000..b8f0a80fa
--- /dev/null
+++ b/browser/themes/linux/menuPanel-help@2x.png
Binary files differ
diff --git a/browser/themes/linux/monitor.png b/browser/themes/linux/monitor.png
new file mode 100644
index 000000000..35e7b2056
--- /dev/null
+++ b/browser/themes/linux/monitor.png
Binary files differ
diff --git a/browser/themes/linux/monitor_16-10.png b/browser/themes/linux/monitor_16-10.png
new file mode 100644
index 000000000..41950340e
--- /dev/null
+++ b/browser/themes/linux/monitor_16-10.png
Binary files differ
diff --git a/browser/themes/linux/moz.build b/browser/themes/linux/moz.build
new file mode 100644
index 000000000..8b611040d
--- /dev/null
+++ b/browser/themes/linux/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['communicator']
+
+JAR_MANIFESTS += ['jar.mn']
+
+include('../tab-svgs.mozbuild')
diff --git a/browser/themes/linux/newtab/newTab.css b/browser/themes/linux/newtab/newTab.css
new file mode 100644
index 000000000..371594f06
--- /dev/null
+++ b/browser/themes/linux/newtab/newTab.css
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/newtab/newTab.inc.css
+
+
+.newtab-undo-button {
+ color: rgb(221,72,20);
+}
+
+#newtab-undo-close-button {
+ height: 16px;
+ width: 16px;
+}
+
+.newtab-title {
+ font-family: sans-serif;
+}
diff --git a/browser/themes/linux/page-livemarks.png b/browser/themes/linux/page-livemarks.png
new file mode 100644
index 000000000..c3b534e04
--- /dev/null
+++ b/browser/themes/linux/page-livemarks.png
Binary files differ
diff --git a/browser/themes/linux/pageInfo.css b/browser/themes/linux/pageInfo.css
new file mode 100644
index 000000000..6cf37a7c9
--- /dev/null
+++ b/browser/themes/linux/pageInfo.css
@@ -0,0 +1,267 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+@import url("chrome://global/skin/");
+
+/* View buttons */
+#viewGroup > radio {
+ list-style-image: url("chrome://browser/skin/pageInfo.png");
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+ -moz-appearance: none;
+ min-width: 4.5em;
+ margin: 0;
+ padding: 3px;
+ color: -moz-FieldText;
+}
+
+#viewGroup > radio[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+#topBar {
+ -moz-appearance: listbox;
+ margin: 8px 8px 0;
+}
+
+#generalTab {
+ -moz-image-region: rect(0px, 32px, 32px, 0px)
+}
+
+#mediaTab {
+ -moz-image-region: rect(0px, 64px, 32px, 32px)
+}
+
+#feedTab {
+ -moz-image-region: rect(0px, 96px, 32px, 64px)
+}
+
+#permTab {
+ -moz-image-region: rect(0px, 128px, 32px, 96px)
+}
+
+#securityTab {
+ -moz-image-region: rect(0px, 160px, 32px, 128px)
+}
+
+#mainDeck {
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+deck {
+ padding: 10px 10px 10px 10px;
+}
+
+/* Misc */
+tree {
+ margin: .5em;
+}
+
+.gridSeparator {
+ width: .5em;
+}
+
+textbox {
+ background: transparent !important;
+ border: none;
+ padding: 0px;
+ margin-top: 1px;
+ -moz-appearance: none;
+}
+
+textbox.header {
+ margin-inline-start: 0;
+}
+
+.iframe {
+ margin: .5em;
+ background: white;
+ overflow: auto;
+}
+
+.fixedsize {
+ height: 8.5em;
+}
+
+textbox[disabled] {
+ font-style: italic;
+}
+
+/* General Tab */
+#generalPanel > #titletext {
+ margin-inline-start: 5px;
+}
+
+groupbox.collapsable caption .caption-icon {
+ width: 9px;
+ height: 9px;
+ background-repeat: no-repeat;
+ background-position: center;
+ margin-inline-start: 1px;
+ margin-inline-end: 3px;
+ background-image: url("chrome://global/skin/tree/twisty-open.png");
+}
+
+groupbox.collapsable[closed="true"] {
+ border: none;
+}
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+ background-image: url("chrome://global/skin/tree/twisty-clsd.png");
+}
+
+groupbox tree {
+ margin: 0;
+ border: none;
+}
+
+groupbox.treebox .groupbox-body {
+ margin-inline-start: 5px;
+ margin-inline-end: 1px;
+ padding-top: 0;
+}
+
+#securityBox description {
+ margin-inline-start: 10px;
+}
+
+#general-security-identity {
+ white-space: pre-wrap;
+ line-height: 2em;
+}
+
+/* Media Tab */
+#imagetree {
+ min-height: 10em;
+ margin-top: 2px;
+ margin-bottom: 0;
+}
+
+#mediaSplitter {
+ -moz-appearance: none;
+ height: .8em;
+}
+
+#mediaGrid {
+ min-height: 9em;
+}
+
+#mediaLabelColumn {
+ min-width: 10em;
+}
+
+#thepreviewimage {
+ margin: 1em;
+}
+
+treechildren::-moz-tree-cell-text(broken) {
+ font-style: italic;
+ color: graytext;
+}
+
+#mediaPreviewBox .inset {
+ -moz-appearance: listbox;
+ margin-bottom: 0;
+}
+
+/* Feeds Tab */
+#feedPanel {
+ margin-left: 2px;
+ margin-right: 2px;
+}
+
+#feedtree {
+ margin-bottom: 0px;
+}
+
+#feedListbox richlistitem {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+ color: -moz-FieldText;
+}
+
+#feedListbox richlistitem[selected="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+#feedListbox {
+ margin-bottom: 0;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+}
+
+.feedTitle {
+ font-weight: bold;
+}
+
+/* Permissions Tab */
+#permPanel {
+ margin-left: 6px;
+ margin-right: 6px;
+}
+
+#permList {
+ -moz-appearance: listbox;
+ margin-top: .5em;
+ overflow: auto;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ background-color: -moz-field;
+ color: -moz-FieldText;
+}
+
+.permission {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+.permissionLabel {
+ font-weight: bold;
+}
+
+.permission:hover {
+ background-color: -moz-dialog;
+ color: -moz-DialogText;
+}
+
+/* Security Tab */
+#securityPanel .caption-icon {
+ display: none;
+}
+
+#securityPanel .header {
+ font-size: 120%;
+}
+
+#securityPanel .fieldLabel {
+ margin: 2px 10px 3px;
+}
+
+#securityPanel .fieldValue {
+ font-weight: bold;
+ margin: 2px 10px 3px;
+}
+
+#securityPanel row {
+ -moz-box-align: center;
+}
diff --git a/browser/themes/linux/pageInfo.png b/browser/themes/linux/pageInfo.png
new file mode 100644
index 000000000..2cbb15df9
--- /dev/null
+++ b/browser/themes/linux/pageInfo.png
Binary files differ
diff --git a/browser/themes/linux/places/autocomplete-star.png b/browser/themes/linux/places/autocomplete-star.png
new file mode 100644
index 000000000..2675f9345
--- /dev/null
+++ b/browser/themes/linux/places/autocomplete-star.png
Binary files differ
diff --git a/browser/themes/linux/places/bookmarks-menu-arrow.png b/browser/themes/linux/places/bookmarks-menu-arrow.png
new file mode 100644
index 000000000..616f16b7f
--- /dev/null
+++ b/browser/themes/linux/places/bookmarks-menu-arrow.png
Binary files differ
diff --git a/browser/themes/linux/places/bookmarks-notification-finish.png b/browser/themes/linux/places/bookmarks-notification-finish.png
new file mode 100644
index 000000000..8520b4985
--- /dev/null
+++ b/browser/themes/linux/places/bookmarks-notification-finish.png
Binary files differ
diff --git a/browser/themes/linux/places/bookmarksMenu.png b/browser/themes/linux/places/bookmarksMenu.png
new file mode 100644
index 000000000..80dd2168d
--- /dev/null
+++ b/browser/themes/linux/places/bookmarksMenu.png
Binary files differ
diff --git a/browser/themes/linux/places/bookmarksToolbar-menuPanel.png b/browser/themes/linux/places/bookmarksToolbar-menuPanel.png
new file mode 100644
index 000000000..367a9090d
--- /dev/null
+++ b/browser/themes/linux/places/bookmarksToolbar-menuPanel.png
Binary files differ
diff --git a/browser/themes/linux/places/bookmarksToolbar.png b/browser/themes/linux/places/bookmarksToolbar.png
new file mode 100644
index 000000000..09502fe83
--- /dev/null
+++ b/browser/themes/linux/places/bookmarksToolbar.png
Binary files differ
diff --git a/browser/themes/linux/places/calendar.png b/browser/themes/linux/places/calendar.png
new file mode 100644
index 000000000..f7128685c
--- /dev/null
+++ b/browser/themes/linux/places/calendar.png
Binary files differ
diff --git a/browser/themes/linux/places/downloads.png b/browser/themes/linux/places/downloads.png
new file mode 100644
index 000000000..d641714aa
--- /dev/null
+++ b/browser/themes/linux/places/downloads.png
Binary files differ
diff --git a/browser/themes/linux/places/editBookmarkOverlay.css b/browser/themes/linux/places/editBookmarkOverlay.css
new file mode 100644
index 000000000..dba6ba655
--- /dev/null
+++ b/browser/themes/linux/places/editBookmarkOverlay.css
@@ -0,0 +1,71 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+/**** folder menulist ****/
+.folder-icon > .menulist-label-box > .menulist-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.folder-icon > .menu-iconic-left {
+ display: -moz-box;
+}
+
+.folder-icon {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu") !important;
+}
+
+
+/**** expanders ****/
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ padding: 2px 0;
+ padding-inline-start: 2px;
+}
+
+.expander-up > .button-box {
+ -moz-appearance: button-arrow-up;
+}
+
+.expander-down > .button-box {
+ -moz-appearance: button-arrow-down;
+}
+
+#editBookmarkPanelContent {
+ min-width: 23em;
+}
+
+#editBMPanel_folderTree {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+/* Hide the value column of the tag autocomplete popup
+ * leaving only the comment column visible. This is
+ * so that only the tag being edited is shown in the
+ * popup.
+ */
+#editBMPanel_tagsField #treecolAutoCompleteValue {
+ visibility: collapse;
+}
+
+
+/* Bookmark panel dropdown menu items */
+#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_bmRootItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png") !important;
+}
diff --git a/browser/themes/linux/places/livemark-item.png b/browser/themes/linux/places/livemark-item.png
new file mode 100644
index 000000000..9184b9518
--- /dev/null
+++ b/browser/themes/linux/places/livemark-item.png
Binary files differ
diff --git a/browser/themes/linux/places/organizer.css b/browser/themes/linux/places/organizer.css
new file mode 100644
index 000000000..d1881dbb4
--- /dev/null
+++ b/browser/themes/linux/places/organizer.css
@@ -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/. */
+
+
+/* Toolbar */
+#placesToolbar {
+ -moz-appearance: menubar;
+}
+
+#placesToolbar:-moz-system-metric(menubar-drag) {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
+}
+
+#placesToolbar > toolbarbutton {
+ color: -moz-menubartext;
+}
+
+#placesToolbar > toolbarbutton:hover {
+ color: ButtonText;
+}
+
+#placesToolbar > toolbarbutton[disabled=true] {
+ color: GrayText;
+}
+
+/* back button */
+
+#back-button {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar");
+}
+#back-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar&state=disabled");
+}
+
+#back-button:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar");
+}
+#back-button[disabled="true"]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar&state=disabled");
+}
+
+/* forward button */
+
+#forward-button {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar");
+}
+#forward-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar&state=disabled");
+}
+
+#forward-button:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar");
+}
+#forward-button[disabled="true"]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar&state=disabled");
+}
+
+/* Menu */
+#placesMenu {
+ -moz-appearance: none;
+ border: none;
+}
+
+#placesMenu > menu {
+ padding-inline-start: 4px;
+ -moz-binding: url("chrome://browser/skin/places/organizer.xml#toolbarbutton-dropdown");
+ -moz-appearance: toolbarbutton;
+}
+
+#placesMenu > menu:active,
+#placesMenu > menu:hover,
+#placesMenu > menu[open] {
+ color: ButtonText;
+}
+
+#placesMenu > menu > .menubar-right {
+ -moz-appearance: toolbarbutton-dropdown;
+ width: 12px;
+ height: 12px;
+}
+
+/* Root View */
+#placesView {
+ background-color: Window;
+}
+
+/* Info box */
+#detailsDeck {
+ padding: 5px;
+}
+
+#infoBoxExpanderLabel {
+ padding-inline-start: 2px;
+}
+
+/**
+ * Downloads pane
+ */
+
+#clearDownloadsButton > .toolbarbutton-icon {
+ display: none;
+}
diff --git a/browser/themes/linux/places/organizer.xml b/browser/themes/linux/places/organizer.xml
new file mode 100644
index 000000000..8331ebb11
--- /dev/null
+++ b/browser/themes/linux/places/organizer.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="organizerBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="toolbarbutton-dropdown"
+ extends="chrome://global/content/bindings/menu.xml#menu-base">
+ <content>
+ <xul:image class="menubar-left" xbl:inherits="src=image"/>
+ <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <xul:hbox class="menubar-right"/>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+</bindings>
diff --git a/browser/themes/linux/places/places.css b/browser/themes/linux/places/places.css
new file mode 100644
index 000000000..776191dd5
--- /dev/null
+++ b/browser/themes/linux/places/places.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/. */
+
+/* Sidebars */
+.sidebar-placesTree {
+ margin: 0;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
+.sidebar-placesTreechildren::-moz-tree-image(leaf) {
+ cursor: pointer;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(separator) {
+ cursor: default;
+}
+
+/* Trees */
+treechildren::-moz-tree-image(title) {
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+treechildren::-moz-tree-image(title, livemarkItem) {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, separator) {
+ list-style-image: none;
+ width: 0;
+ height: 0;
+}
+
+treechildren::-moz-tree-image(title, container) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(title, container, livemark) {
+ list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* query-nodes should be styled even if they're not expandable */
+treechildren::-moz-tree-image(title, query) {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+}
+
+treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
+ list-style-image: url("chrome://browser/skin/places/downloads.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, tagContainer),
+treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer, open) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(title, query, OrganizerQuery_History) {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+/* We want some queries to look like ordinary folders. This must come
+ after the (title, query) selector, or it would get overridden. */
+treechildren::-moz-tree-image(title, query, folder) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(cutting) {
+ opacity: 0.5;
+}
+
+treechildren::-moz-tree-cell-text(cutting) {
+ opacity: 0.7;
+}
diff --git a/browser/themes/linux/places/query.png b/browser/themes/linux/places/query.png
new file mode 100644
index 000000000..2420dee0e
--- /dev/null
+++ b/browser/themes/linux/places/query.png
Binary files differ
diff --git a/browser/themes/linux/places/starred48.png b/browser/themes/linux/places/starred48.png
new file mode 100644
index 000000000..bdcc7e757
--- /dev/null
+++ b/browser/themes/linux/places/starred48.png
Binary files differ
diff --git a/browser/themes/linux/places/tag.png b/browser/themes/linux/places/tag.png
new file mode 100644
index 000000000..27176cc6d
--- /dev/null
+++ b/browser/themes/linux/places/tag.png
Binary files differ
diff --git a/browser/themes/linux/places/toolbarDropMarker.png b/browser/themes/linux/places/toolbarDropMarker.png
new file mode 100644
index 000000000..ed3200f6c
--- /dev/null
+++ b/browser/themes/linux/places/toolbarDropMarker.png
Binary files differ
diff --git a/browser/themes/linux/places/unsortedBookmarks.png b/browser/themes/linux/places/unsortedBookmarks.png
new file mode 100644
index 000000000..4dcf76138
--- /dev/null
+++ b/browser/themes/linux/places/unsortedBookmarks.png
Binary files differ
diff --git a/browser/themes/linux/places/unstarred48.png b/browser/themes/linux/places/unstarred48.png
new file mode 100644
index 000000000..15448636e
--- /dev/null
+++ b/browser/themes/linux/places/unstarred48.png
Binary files differ
diff --git a/browser/themes/linux/preferences/alwaysAsk.png b/browser/themes/linux/preferences/alwaysAsk.png
new file mode 100644
index 000000000..45256d4e7
--- /dev/null
+++ b/browser/themes/linux/preferences/alwaysAsk.png
Binary files differ
diff --git a/browser/themes/linux/preferences/applications.css b/browser/themes/linux/preferences/applications.css
new file mode 100644
index 000000000..508076304
--- /dev/null
+++ b/browser/themes/linux/preferences/applications.css
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Line up the actions menu with action labels above and below it.
+ * Equalize the distance from the left side of the action box to the left side
+ * of the icon for both the menu and the non-menu versions of the action box.
+ * Also make sure the labels are the same distance away from the icons.
+ */
+.actionsMenu {
+ margin-top: -1px;
+ margin-bottom: -1px;
+ margin-inline-start: -1px;
+ margin-inline-end: 0;
+}
+
+.typeIcon,
+.actionIcon {
+ margin-inline-start: 3px;
+ margin-inline-end: 3px;
+}
+
+#handlersView > richlistitem label {
+ margin-inline-start: 1px;
+ margin-top: 2px;
+}
+
+#handlersView > richlistitem {
+ min-height: 25px;
+}
+
+richlistitem[appHandlerIcon="ask"],
+menuitem[appHandlerIcon="ask"] {
+ list-style-image: url("chrome://browser/skin/preferences/alwaysAsk.png");
+}
+
+richlistitem[appHandlerIcon="save"],
+menuitem[appHandlerIcon="save"] {
+ list-style-image: url("moz-icon://stock/gtk-save?size=menu");
+}
+
+richlistitem[appHandlerIcon="feed"],
+menuitem[appHandlerIcon="feed"] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+richlistitem[appHandlerIcon="plugin"],
+menuitem[appHandlerIcon="plugin"] {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
+}
+
+.actionsMenu .menulist-icon {
+ margin-inline-end: 1px;
+ height: 16px;
+ width: 16px;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ padding-inline-start: 0;
+ padding-inline-end: 4px !important;
+}
+
+.actionsMenu > menupopup > menuitem {
+ padding-inline-start: 3px;
+}
diff --git a/browser/themes/linux/preferences/in-content/dialog.css b/browser/themes/linux/preferences/in-content/dialog.css
new file mode 100644
index 000000000..3b1ae45e0
--- /dev/null
+++ b/browser/themes/linux/preferences/in-content/dialog.css
@@ -0,0 +1,19 @@
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../../shared/incontentprefs/dialog.inc.css
+
+label:not(.menu-text),
+textbox,
+description,
+.tab-text,
+caption > label {
+ font-size: 1.2em;
+}
+
+/* Create a separate rule to unset these styles on .tree-input instead of
+ using :not(.tree-input) so the selector specifity doesn't change. */
+textbox.tree-input {
+ font-size: unset;
+}
diff --git a/browser/themes/linux/preferences/in-content/preferences.css b/browser/themes/linux/preferences/in-content/preferences.css
new file mode 100644
index 000000000..91997aa0d
--- /dev/null
+++ b/browser/themes/linux/preferences/in-content/preferences.css
@@ -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 ../../../shared/incontentprefs/preferences.inc.css
+
+.treecol-sortdirection {
+ /* override the Linux only toolkit rule */
+ -moz-appearance: none;
+}
+
+.actionsMenu {
+ font-family: "Clear Sans", sans-serif;
+ font-size: 1.25rem;
+ line-height: 22px;
+}
+
+.actionsMenu > .menulist-label-box > .menulist-icon {
+ margin-top: 1px;
+ margin-inline-start: 1px;
+ margin-inline-end: 6px;
+}
+
+.actionsMenu > .menulist-label-box > .menulist-label {
+ margin-top: 2px !important;
+}
+
+#fxaProfileImage {
+ -moz-user-focus: normal;
+}
+
+menulist.actionsMenu > .menulist-dropmarker {
+ margin-top: 11px;
+ margin-bottom: 11px;
+}
+
+textbox + button,
+filefield + button {
+ margin-inline-start: -4px;
+}
+
+/**
+ * Dialog
+ */
+
+#dialogTitle {
+ font-size: 1em;
+}
diff --git a/browser/themes/linux/preferences/mail.png b/browser/themes/linux/preferences/mail.png
new file mode 100644
index 000000000..66d2bc9e0
--- /dev/null
+++ b/browser/themes/linux/preferences/mail.png
Binary files differ
diff --git a/browser/themes/linux/preferences/preferences.css b/browser/themes/linux/preferences/preferences.css
new file mode 100644
index 000000000..45e2dc23d
--- /dev/null
+++ b/browser/themes/linux/preferences/preferences.css
@@ -0,0 +1,106 @@
+/*
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+/* Global Styles */
+.checkbox-check {
+ -moz-appearance: checkbox;
+}
+
+/* General Pane */
+#useFirefoxSync,
+#getStarted {
+ font-size: 90%;
+}
+
+#isNotDefaultLabel {
+ font-weight: bold;
+}
+
+/* Content Pane */
+#translationAttributionImage {
+ width: 70px;
+ cursor: pointer;
+}
+
+/* Modeless Window Dialogs */
+.windowDialog,
+.windowDialog prefpane {
+ padding: 0px;
+}
+
+.contentPane {
+ margin: 9px 8px 5px 8px;
+}
+
+.actionButtons {
+ margin: 0px 3px 6px 3px !important;
+}
+
+/* Cookies Manager */
+#cookiesChildren::-moz-tree-image(domainCol) {
+ width: 16px;
+ height: 16px;
+ margin: 0px 2px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+#linksOpenInBox {
+ margin-top: 5px;
+}
+
+#advancedPrefs {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+#cookiesChildren::-moz-tree-image(domainCol, container) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+#cookieInfoBox {
+ border: 1px solid ThreeDShadow;
+ border-radius: 0px;
+ margin: 4px;
+ padding: 0px;
+}
+
+/* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
+ of the groupbox from being cutoff */
+.bottomBox {
+ padding-bottom: 4px;
+}
+
+/**
+ * Clear Private Data
+ */
+#SanitizeDialogPane > groupbox {
+ margin-top: 0;
+}
+
+/* Sync Pane */
+
+#syncDesc {
+ padding: 0 8em;
+}
+
+#accountCaptionImage {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
+}
+
+#syncAddDeviceLabel {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+#noFxaAccount {
+ margin: 5px;
+ line-height: 1.2em;
+}
+
+#noFxaAccount > label:first-child {
+ margin-bottom: 0.6em;
+}
diff --git a/browser/themes/linux/privatebrowsing-mask.png b/browser/themes/linux/privatebrowsing-mask.png
new file mode 100644
index 000000000..9eaf3aec7
--- /dev/null
+++ b/browser/themes/linux/privatebrowsing-mask.png
Binary files differ
diff --git a/browser/themes/linux/reload-stop-go.png b/browser/themes/linux/reload-stop-go.png
new file mode 100644
index 000000000..1017be903
--- /dev/null
+++ b/browser/themes/linux/reload-stop-go.png
Binary files differ
diff --git a/browser/themes/linux/reload-stop-go@2x.png b/browser/themes/linux/reload-stop-go@2x.png
new file mode 100644
index 000000000..38b27bf0c
--- /dev/null
+++ b/browser/themes/linux/reload-stop-go@2x.png
Binary files differ
diff --git a/browser/themes/linux/sanitizeDialog.css b/browser/themes/linux/sanitizeDialog.css
new file mode 100644
index 000000000..e73cd27e7
--- /dev/null
+++ b/browser/themes/linux/sanitizeDialog.css
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#sanitizeDurationChoice {
+ margin-inline-end: 0;
+}
+
+/* Align the duration label with the warning box and item list */
+#sanitizeDurationLabel {
+ margin-inline-start: 3px;
+}
+
+
+/* Hide the duration dropdown suffix label if it's empty. Otherwise it
+ takes up a little space, causing the end of the dropdown to not be aligned
+ with the warning box. */
+#sanitizeDurationSuffixLabel[value=""] {
+ display: none;
+}
+
+
+/* Places tree */
+#placesTreechildren::-moz-tree-row(selected),
+#placesTreechildren::-moz-tree-row(grippyRow) {
+ background: #999;
+}
+
+#placesTreechildren::-moz-tree-cell-text(selected) {
+ color: #111;
+}
+
+
+/* Sanitize everything warning box */
+#sanitizeEverythingWarningBox {
+ background-color: Window;
+ border: 1px solid ThreeDDarkShadow;
+ border-radius: 5px;
+ padding: 16px;
+}
+
+#sanitizeEverythingWarningIcon {
+ list-style-image: url("moz-icon://stock/gtk-dialog-warning?size=dialog");
+ padding: 0;
+ margin: 0;
+}
+
+#sanitizeEverythingWarningDescBox {
+ padding: 0 16px;
+ margin: 0;
+}
+
+
+/* Progressive disclosure button */
+#detailsExpanderWrapper {
+ padding: 0;
+ margin-top: 6px;
+ margin-bottom: 6px;
+ margin-inline-start: -4px;
+ margin-inline-end: 0;
+}
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ padding: 2px 0;
+ padding-inline-start: 2px;
+}
+
+.expander-up {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+}
+
+.expander-down {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+
+.expander-down:hover:active {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-hov.gif");
+}
+
+.expander-up:hover:active {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up-hov.gif");
+}
+
+
+/* Make the item list the same width as the warning box */
+#itemList {
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+}
+
+/* Without this a useless scrollbar appears in the listbox when its rows
+ attribute is set to the total number of listitems, as it is currently. See
+ bug 489958 comment 14 and bug 491788. */
+#itemList > listitem {
+ padding: 1px 0;
+}
+
+
+/* Align the last dialog button with the end of the warning box */
+.prefWindow-dlgbuttons {
+ margin-inline-end: 0;
+}
+.dialog-button[dlgtype="accept"] {
+ margin-inline-end: 0;
+}
diff --git a/browser/themes/linux/searchbar.css b/browser/themes/linux/searchbar.css
new file mode 100644
index 000000000..cb4103db0
--- /dev/null
+++ b/browser/themes/linux/searchbar.css
@@ -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/. */
+
+#PopupSearchAutoComplete {
+ /* JS code forces the panel to have the width of the searchbar rather than
+ * the width of the textfield. Alignment of the panel with the searchbar is
+ * obtained with negative margins here: margin-inline-start when the text
+ * field is in the same direction as the rest of the UI, margin-inline-end
+ * when the textfield's direction has been reversed.
+ * (eg. using ctrl+shift+X) */
+ margin-inline-start: -23px;
+ margin-inline-end: -16px;
+}
+
+.autocomplete-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.textbox-input-box {
+ margin: 0;
+}
+
+/* Engine button */
+.searchbar-engine-image {
+ height: 16px;
+ width: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ margin-inline-start: -1px;
+}
+
+/* Search go button */
+.search-go-container {
+ -moz-box-align: center;
+}
+
+.search-go-button {
+ padding: 1px;
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+ width: 14px;
+}
+
+.search-go-button:hover {
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+.search-go-button:hover:active {
+ -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+.search-go-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+menuitem[cmd="cmd_clearhistory"] {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu");
+}
+
+menuitem[cmd="cmd_clearhistory"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu&state=disabled");
+}
+
+.searchbar-search-button-container {
+ -moz-box-align: center;
+}
+
+.searchbar-search-button {
+ list-style-image: url("chrome://browser/skin/search-indicator.png");
+ -moz-image-region: rect(0, 20px, 20px, 0);
+ margin-top: 1px;
+ margin-bottom: 1px;
+ margin-inline-start: 2px;
+ width: 20px;
+}
+
+.searchbar-search-button[addengines="true"] {
+ list-style-image: url("chrome://browser/skin/search-indicator-badge-add.png");
+}
+
+.searchbar-search-button:hover {
+ -moz-image-region: rect(0, 40px, 20px, 20px);
+}
+
+.searchbar-search-button:hover:active {
+ -moz-image-region: rect(0, 60px, 20px, 40px);
+}
+
+@media (min-resolution: 1.1dppx) {
+ .searchbar-engine-image {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+
+ .searchbar-search-button {
+ list-style-image: url("chrome://browser/skin/search-indicator@2x.png");
+ -moz-image-region: rect(0, 40px, 40px, 0);
+ }
+
+ .searchbar-search-button[addengines="true"] {
+ list-style-image: url("chrome://browser/skin/search-indicator-badge-add@2x.png");
+ }
+
+ .searchbar-search-button:hover {
+ -moz-image-region: rect(0, 80px, 40px, 40px);
+ }
+
+ .searchbar-search-button:hover:active {
+ -moz-image-region: rect(0, 120px, 40px, 80px);
+ }
+
+ .search-go-button {
+ list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
+ -moz-image-region: rect(0, 84px, 28px, 56px);
+ }
+
+ .search-go-button:hover {
+ -moz-image-region: rect(28px, 84px, 56px, 56px);
+ }
+
+ .search-go-button:hover:active {
+ -moz-image-region: rect(56px, 84px, 84px, 56px);
+ }
+}
+
+.search-panel-current-engine {
+ -moz-box-align: center;
+}
+
+/**
+ * The borders of the various elements are specified as follows.
+ *
+ * The current engine always has a bottom border.
+ * The search results never have a border.
+ *
+ * When the search results are not collapsed:
+ * - The elements underneath the search results all have a top border.
+ *
+ * When the search results are collapsed:
+ * - The elements underneath the search results all have a bottom border, except
+ * the lowest one: search-setting-button.
+ */
+
+.search-panel-current-engine {
+ border-top: none !important;
+ border-bottom: 1px solid var(--panel-separator-color) !important;
+}
+
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-header,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-one-offs,
+.search-panel-tree[collapsed=true] + .search-one-offs > vbox > .addengine-item:first-of-type {
+ border-top: none !important;
+}
+
+.search-panel-tree[collapsed=true] + .search-one-offs > .searchbar-engine-one-off-item,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-current-input,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-one-offs,
+.search-panel-tree[collapsed=true] + .search-one-offs > vbox > .addengine-item:last-of-type {
+ border-bottom: 1px solid var(--panel-separator-color) !important;
+}
+
+.search-panel-header {
+ font-weight: normal;
+ background-color: var(--arrowpanel-dimmed);
+ border: none;
+ border-top: 1px solid var(--panel-separator-color);
+ padding: 3px 5px;
+ color: GrayText;
+}
+
+.search-panel-header > label {
+ margin-top: 2px !important;
+ margin-bottom: 1px !important;
+}
+
+.search-panel-current-input > label {
+ margin: 2px 0 1px !important;
+}
+
+.search-panel-input-value {
+ color: -moz-fieldtext;
+}
+
+.search-panel-one-offs {
+ margin: 0 !important;
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+.searchbar-engine-one-off-item {
+ -moz-appearance: none;
+ display: inline-block;
+ border: none;
+ min-width: 48px;
+ height: 32px;
+ margin: 0;
+ padding: 0;
+ background: linear-gradient(transparent 15%, var(--panel-separator-color) 15%, var(--panel-separator-color) 85%, transparent 85%);
+ background-size: 1px auto;
+ background-repeat: no-repeat;
+ background-position: right center;
+ color: GrayText;
+}
+
+.searchbar-engine-one-off-item:-moz-locale-dir(rtl) {
+ background-position: left center;
+}
+
+.searchbar-engine-one-off-item:not(.last-row) {
+ box-sizing: content-box;
+ border-bottom: 1px solid var(--panel-separator-color);
+}
+
+.search-setting-button-compact {
+ border-bottom: none !important;
+}
+
+.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
+.search-setting-button-compact {
+ background-image: none;
+}
+
+.searchbar-engine-one-off-item[selected] {
+ background-color: Highlight;
+ background-image: none;
+ color: HighlightText;
+}
+
+.searchbar-engine-one-off-item > .button-box {
+ border: none;
+ padding: 0;
+}
+
+.searchbar-engine-one-off-item > .button-box > .button-text {
+ display: none;
+}
+
+.searchbar-engine-one-off-item > .button-box > .button-icon {
+ display: -moz-box;
+ margin-inline-end: 0;
+ width: 16px;
+ height: 16px;
+}
+
+.addengine-item {
+ -moz-appearance: none;
+ background-color: transparent;
+ color: inherit;
+ border: none;
+ height: 32px;
+ margin: 0;
+ padding: 0 10px;
+}
+
+.addengine-item > .button-box {
+ -moz-box-pack: start;
+}
+
+.addengine-item:first-of-type {
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+.addengine-item[selected] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.addengine-icon {
+ width: 16px;
+}
+
+.addengine-badge {
+ width: 16px;
+ height: 16px;
+ margin: -7px -9px 7px 9px;
+ list-style-image: url("chrome://browser/skin/badge-add-engine.png");
+}
+
+.addengine-item > .button-box > .button-text {
+ -moz-box-flex: 1;
+ text-align: start;
+ padding-inline-start: 10px;
+}
+
+.addengine-item:not([image]) {
+ list-style-image: url("chrome://browser/skin/search-engine-placeholder.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ .addengine-badge {
+ list-style-image: url("chrome://browser/skin/badge-add-engine@2x.png");
+ }
+
+ .addengine-item:not([image]) {
+ list-style-image: url("chrome://browser/skin/search-engine-placeholder@2x.png");
+ }
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-cell {
+ border-top: none !important;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-cell-text {
+ padding-inline-start: 4px;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image {
+ padding-inline-start: 5px;
+ width: 14px;
+ height: 14px;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image(fromhistory) {
+ list-style-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon");
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image(fromhistory, selected) {
+ list-style-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active");
+}
+
+.search-setting-button {
+ -moz-appearance: none;
+ margin: 0;
+ min-height: 32px;
+}
+
+.search-setting-button[selected] {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+.search-setting-button-compact > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/gear.svg");
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
diff --git a/browser/themes/linux/setDesktopBackground.css b/browser/themes/linux/setDesktopBackground.css
new file mode 100644
index 000000000..585284c7b
--- /dev/null
+++ b/browser/themes/linux/setDesktopBackground.css
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|canvas#screen {
+ margin: 12px 11px 32px;
+}
+
+#monitor {
+ list-style-image: url("chrome://browser/skin/monitor.png");
+}
+
+#monitor[aspectratio="16:10"] {
+ list-style-image: url("chrome://browser/skin/monitor_16-10.png");
+}
diff --git a/browser/themes/linux/slowStartup-16.png b/browser/themes/linux/slowStartup-16.png
new file mode 100644
index 000000000..834dc0f1c
--- /dev/null
+++ b/browser/themes/linux/slowStartup-16.png
Binary files differ
diff --git a/browser/themes/linux/social/services-16.png b/browser/themes/linux/social/services-16.png
new file mode 100644
index 000000000..7001ea1f0
--- /dev/null
+++ b/browser/themes/linux/social/services-16.png
Binary files differ
diff --git a/browser/themes/linux/social/services-64.png b/browser/themes/linux/social/services-64.png
new file mode 100644
index 000000000..e787bddc3
--- /dev/null
+++ b/browser/themes/linux/social/services-64.png
Binary files differ
diff --git a/browser/themes/linux/social/share-button-active.png b/browser/themes/linux/social/share-button-active.png
new file mode 100644
index 000000000..7df438db0
--- /dev/null
+++ b/browser/themes/linux/social/share-button-active.png
Binary files differ
diff --git a/browser/themes/linux/social/share-button.png b/browser/themes/linux/social/share-button.png
new file mode 100644
index 000000000..c5298c143
--- /dev/null
+++ b/browser/themes/linux/social/share-button.png
Binary files differ
diff --git a/browser/themes/linux/sync-128.png b/browser/themes/linux/sync-128.png
new file mode 100644
index 000000000..1ea34818c
--- /dev/null
+++ b/browser/themes/linux/sync-128.png
Binary files differ
diff --git a/browser/themes/linux/sync-16.png b/browser/themes/linux/sync-16.png
new file mode 100644
index 000000000..0afb1c719
--- /dev/null
+++ b/browser/themes/linux/sync-16.png
Binary files differ
diff --git a/browser/themes/linux/sync-32.png b/browser/themes/linux/sync-32.png
new file mode 100644
index 000000000..7a762cb98
--- /dev/null
+++ b/browser/themes/linux/sync-32.png
Binary files differ
diff --git a/browser/themes/linux/sync-bg.png b/browser/themes/linux/sync-bg.png
new file mode 100644
index 000000000..893a27d76
--- /dev/null
+++ b/browser/themes/linux/sync-bg.png
Binary files differ
diff --git a/browser/themes/linux/sync-horizontalbar.png b/browser/themes/linux/sync-horizontalbar.png
new file mode 100644
index 000000000..824d4691b
--- /dev/null
+++ b/browser/themes/linux/sync-horizontalbar.png
Binary files differ
diff --git a/browser/themes/linux/sync-horizontalbar@2x.png b/browser/themes/linux/sync-horizontalbar@2x.png
new file mode 100644
index 000000000..fadb57586
--- /dev/null
+++ b/browser/themes/linux/sync-horizontalbar@2x.png
Binary files differ
diff --git a/browser/themes/linux/sync-notification-24.png b/browser/themes/linux/sync-notification-24.png
new file mode 100644
index 000000000..d67eb47ac
--- /dev/null
+++ b/browser/themes/linux/sync-notification-24.png
Binary files differ
diff --git a/browser/themes/linux/syncCommon.css b/browser/themes/linux/syncCommon.css
new file mode 100644
index 000000000..1378c9c40
--- /dev/null
+++ b/browser/themes/linux/syncCommon.css
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+ color: -moz-dialogtext;
+}
+
+.statusIcon {
+ margin-inline-start: 4px;
+ max-height: 16px;
+ max-width: 16px;
+}
+
+.statusIcon[status="active"] {
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+.statusIcon[status="error"] {
+ list-style-image: url("moz-icon://stock/gtk-dialog-error?size=menu");
+}
+
+.statusIcon[status="success"] {
+ list-style-image: url("moz-icon://stock/gtk-dialog-info?size=menu");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+ a separate stylesheet for it. */
+.data {
+ font-size: 90%;
+ font-weight: bold;
+}
+
+dialog#change-dialog {
+ width: 40em;
+}
+
+image#syncIcon {
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+ margin-top: 2px;
+}
+
+#feedback {
+ height: 2em;
+}
diff --git a/browser/themes/linux/syncProgress-horizontalbar.png b/browser/themes/linux/syncProgress-horizontalbar.png
new file mode 100644
index 000000000..48cd11055
--- /dev/null
+++ b/browser/themes/linux/syncProgress-horizontalbar.png
Binary files differ
diff --git a/browser/themes/linux/syncProgress-horizontalbar@2x.png b/browser/themes/linux/syncProgress-horizontalbar@2x.png
new file mode 100644
index 000000000..e1de4763d
--- /dev/null
+++ b/browser/themes/linux/syncProgress-horizontalbar@2x.png
Binary files differ
diff --git a/browser/themes/linux/syncProgress-menuPanel.png b/browser/themes/linux/syncProgress-menuPanel.png
new file mode 100644
index 000000000..6fd6f9c16
--- /dev/null
+++ b/browser/themes/linux/syncProgress-menuPanel.png
Binary files differ
diff --git a/browser/themes/linux/syncProgress-menuPanel@2x.png b/browser/themes/linux/syncProgress-menuPanel@2x.png
new file mode 100644
index 000000000..04b2cae00
--- /dev/null
+++ b/browser/themes/linux/syncProgress-menuPanel@2x.png
Binary files differ
diff --git a/browser/themes/linux/syncProgress-toolbar-inverted.png b/browser/themes/linux/syncProgress-toolbar-inverted.png
new file mode 100644
index 000000000..4ede4387d
--- /dev/null
+++ b/browser/themes/linux/syncProgress-toolbar-inverted.png
Binary files differ
diff --git a/browser/themes/linux/syncProgress-toolbar.png b/browser/themes/linux/syncProgress-toolbar.png
new file mode 100644
index 000000000..49e224f0d
--- /dev/null
+++ b/browser/themes/linux/syncProgress-toolbar.png
Binary files differ
diff --git a/browser/themes/linux/syncQuota.css b/browser/themes/linux/syncQuota.css
new file mode 100644
index 000000000..1577de8a3
--- /dev/null
+++ b/browser/themes/linux/syncQuota.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#quotaDialog {
+ width: 33em;
+ height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+ list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+ height: 4em;
+}
+
+.captionWarning {
+ font-weight: bold;
+}
diff --git a/browser/themes/linux/syncSetup.css b/browser/themes/linux/syncSetup.css
new file mode 100644
index 000000000..1c79e2f50
--- /dev/null
+++ b/browser/themes/linux/syncSetup.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/. */
+
+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,
+#login-throbber {
+ -moz-box-align: center;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+ width: 16px;
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+#captchaFeedback {
+ visibility: hidden;
+}
+
+#successPageIcon {
+ /* TODO replace this with a 128px version (bug 591122) */
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
diff --git a/browser/themes/linux/syncedtabs/sidebar.css b/browser/themes/linux/syncedtabs/sidebar.css
new file mode 100644
index 000000000..04e00a7d4
--- /dev/null
+++ b/browser/themes/linux/syncedtabs/sidebar.css
@@ -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 ../../shared/syncedtabs/sidebar.inc.css
+
+/* These styles are intended to mimic XUL trees and the XUL search box. */
+
+html {
+ border: 1px solid ThreeDShadow;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+ box-sizing: border-box;
+}
+
+.item {
+ padding-inline-end: 0;
+}
+
+.item-title {
+ margin: 1px 0 0;
+ margin-inline-end: 6px;
+}
+
+
+.search-box {
+ -moz-appearance: textfield;
+ cursor: text;
+ margin: 2px 4px;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ padding: 2px 2px 3px;
+ padding-inline-start: 4px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+}
+
+.textbox-search-clear {
+ background-image: url(moz-icon://stock/gtk-clear?size=menu);
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+}
+
+.textbox-search-icon {
+ background-image: url(moz-icon://stock/gtk-find?size=menu);
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+ display: block;
+}
+
+.textbox-search-icon[searchbutton]:not([disabled]) ,
+.textbox-search-clear:not([disabled]) {
+ cursor: pointer;
+}
+
+.item.client .item-twisty-container {
+ -moz-appearance: treetwistyopen;
+ margin-top: 3px;
+ margin-left: 2px;
+}
+
+.item.client.closed .item-twisty-container {
+ -moz-appearance: treetwisty;
+}
diff --git a/browser/themes/linux/tabbrowser/alltabs-inverted.png b/browser/themes/linux/tabbrowser/alltabs-inverted.png
new file mode 100644
index 000000000..f3261f1da
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/alltabs-inverted.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/alltabs.png b/browser/themes/linux/tabbrowser/alltabs.png
new file mode 100644
index 000000000..a7abe7396
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/alltabs.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/newtab-inverted.svg b/browser/themes/linux/tabbrowser/newtab-inverted.svg
new file mode 100644
index 000000000..2728cda5c
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/newtab-inverted.svg
@@ -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/. -->
+<svg width="16" height="18" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="#666" stroke-width="2" fill="none">
+ <rect x="7" y="3" width="2" height="12" rx="0.25" ry="0.25"/>
+ <rect x="2" y="8" width="12" height="2" rx="0.25" ry="0.25"/>
+ </g>
+ <g fill="#fff">
+ <rect x="7" y="3" width="2" height="12"/>
+ <rect x="2" y="8" width="12" height="2"/>
+ </g>
+</svg>
diff --git a/browser/themes/linux/tabbrowser/newtab.svg b/browser/themes/linux/tabbrowser/newtab.svg
new file mode 100644
index 000000000..40548da4a
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/newtab.svg
@@ -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/. -->
+<svg width="16" height="18" xmlns="http://www.w3.org/2000/svg" fill="#4c4c4c">
+ <rect x="7" y="3" width="2" height="12"/>
+ <rect x="2" y="8" width="12" height="2"/>
+</svg>
diff --git a/browser/themes/linux/tabbrowser/tab-active-middle.png b/browser/themes/linux/tabbrowser/tab-active-middle.png
new file mode 100644
index 000000000..b7e6d6f77
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-active-middle.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-active-middle@2x.png b/browser/themes/linux/tabbrowser/tab-active-middle@2x.png
new file mode 100644
index 000000000..1e92acbda
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-active-middle@2x.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-arrow-left-inverted.png b/browser/themes/linux/tabbrowser/tab-arrow-left-inverted.png
new file mode 100644
index 000000000..16cd7a277
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-arrow-left-inverted.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-arrow-left.png b/browser/themes/linux/tabbrowser/tab-arrow-left.png
new file mode 100644
index 000000000..e0fb348d6
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-arrow-left.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-background-end.png b/browser/themes/linux/tabbrowser/tab-background-end.png
new file mode 100644
index 000000000..fb353b17e
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-background-end.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-background-end@2x.png b/browser/themes/linux/tabbrowser/tab-background-end@2x.png
new file mode 100644
index 000000000..eefb6ac47
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-background-end@2x.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-background-middle.png b/browser/themes/linux/tabbrowser/tab-background-middle.png
new file mode 100644
index 000000000..51e066c2e
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-background-middle.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-background-middle@2x.png b/browser/themes/linux/tabbrowser/tab-background-middle@2x.png
new file mode 100644
index 000000000..b26cb95de
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-background-middle@2x.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-background-start.png b/browser/themes/linux/tabbrowser/tab-background-start.png
new file mode 100644
index 000000000..cf0dc852a
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-background-start.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-background-start@2x.png b/browser/themes/linux/tabbrowser/tab-background-start@2x.png
new file mode 100644
index 000000000..bbfc77dd1
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-background-start@2x.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-stroke-end.png b/browser/themes/linux/tabbrowser/tab-stroke-end.png
new file mode 100644
index 000000000..2aa5711f8
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-stroke-end.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-stroke-end@2x.png b/browser/themes/linux/tabbrowser/tab-stroke-end@2x.png
new file mode 100644
index 000000000..a87002a83
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-stroke-end@2x.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-stroke-start.png b/browser/themes/linux/tabbrowser/tab-stroke-start.png
new file mode 100644
index 000000000..4e4e41f63
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-stroke-start.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tab-stroke-start@2x.png b/browser/themes/linux/tabbrowser/tab-stroke-start@2x.png
new file mode 100644
index 000000000..13bd6add1
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tab-stroke-start@2x.png
Binary files differ
diff --git a/browser/themes/linux/tabbrowser/tabDragIndicator.png b/browser/themes/linux/tabbrowser/tabDragIndicator.png
new file mode 100644
index 000000000..df7d914f8
--- /dev/null
+++ b/browser/themes/linux/tabbrowser/tabDragIndicator.png
Binary files differ
diff --git a/browser/themes/linux/webRTC-indicator.css b/browser/themes/linux/webRTC-indicator.css
new file mode 100644
index 000000000..c22f942ec
--- /dev/null
+++ b/browser/themes/linux/webRTC-indicator.css
@@ -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/. */
+
+window {
+ border: 1px solid #ff9500;
+}
+
+#audioVideoButton,
+#screenShareButton,
+#firefoxButton {
+ height: 29px;
+ margin: 0;
+ -moz-appearance: none;
+ border-style: none;
+}
+
+#firefoxButton {
+ background-image: url("chrome://branding/content/icon48.png");
+ background-repeat: no-repeat;
+ background-size: 22px;
+ background-position: center center;
+ min-width: 29px;
+ background-color: white;
+}
+
+#firefoxButton:hover {
+ background-color: #f2f2f2;
+}
+
+#screenShareButton {
+ background-image: url("webRTC-screen-white-16.png");
+ background-position: center center;
+ background-repeat: no-repeat;
+ background-size: 16px;
+ min-width: 27px;
+ display: none;
+}
+
+window[sharingscreen] > #screenShareButton {
+ display: -moz-box;
+}
+
+#audioVideoButton {
+ display: none;
+ background-repeat: no-repeat;
+}
+
+/* When screen sharing, need to pull in the separator: */
+window[sharingscreen] > #audioVideoButton {
+ margin-right: -1px;
+}
+
+/* Single icon button: */
+window[sharingvideo] > #audioVideoButton,
+window[sharingaudio] > #audioVideoButton {
+ display: -moz-box;
+ background-position: center center;
+ background-size: 16px;
+ min-width: 26px;
+}
+
+window[sharingvideo] > #audioVideoButton {
+ background-image: url("webRTC-camera-white-16.png");
+}
+
+window[sharingaudio] > #audioVideoButton {
+ background-image: url("webRTC-microphone-white-16.png");
+}
+
+/* Multi-icon button: */
+window[sharingaudio][sharingvideo] > #audioVideoButton {
+ background-image: url("webRTC-camera-white-16.png"),
+ url("webRTC-microphone-white-16.png");
+ background-position: 6px center, 26px center;
+ background-size: 16px, 16px;
+ min-width: 46px;
+}
+
+/* Hover styles */
+#audioVideoButton,
+#screenShareButton {
+ background-color: #ffaa33;
+}
+
+#audioVideoButton:hover,
+#screenShareButton:hover {
+ background-color: #ff9500;
+}
+
+/* Don't show the dropmarker for the type="menu" case */
+#audioVideoButton > .box-inherit > .button-menu-dropmarker,
+#screenShareButton > .box-inherit > .button-menu-dropmarker {
+ display: none;
+}
+
+/* Separator in case of screen sharing + video/audio sharing */
+#shareSeparator {
+ width: 1px;
+ margin: 4px -1px 4px 0;
+ background-color: #FFCA80;
+ /* Separator needs to show above either button when they're hovered: */
+ position: relative;
+ z-index: 1;
+ display: none;
+}
+
+window[sharingscreen][sharingvideo] > #shareSeparator,
+window[sharingscreen][sharingaudio] > #shareSeparator {
+ display: -moz-box;
+}
+
+:-moz-any(#audioVideoButton, #screenShareButton,
+ #firefoxButton):-moz-focusring > .button-box {
+ border: none;
+}
diff --git a/browser/themes/moz.build b/browser/themes/moz.build
new file mode 100644
index 000000000..843567b00
--- /dev/null
+++ b/browser/themes/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+if toolkit == 'cocoa':
+ DIRS += ['osx']
+elif toolkit in ('gtk2', 'gtk3'):
+ DIRS += ['linux']
+else:
+ DIRS += ['windows']
+
+FINAL_TARGET_FILES.extensions['{972ce4c6-7e08-4474-a285-3208198ce6fd}'] += ['shared/icon.png']
diff --git a/browser/themes/osx/Info.png b/browser/themes/osx/Info.png
new file mode 100644
index 000000000..7dd795ac9
--- /dev/null
+++ b/browser/themes/osx/Info.png
Binary files differ
diff --git a/browser/themes/osx/Privacy-16.png b/browser/themes/osx/Privacy-16.png
new file mode 100644
index 000000000..5291abce4
--- /dev/null
+++ b/browser/themes/osx/Privacy-16.png
Binary files differ
diff --git a/browser/themes/osx/Toolbar-background-noise.png b/browser/themes/osx/Toolbar-background-noise.png
new file mode 100644
index 000000000..aab0a02c8
--- /dev/null
+++ b/browser/themes/osx/Toolbar-background-noise.png
Binary files differ
diff --git a/browser/themes/osx/Toolbar-inverted.png b/browser/themes/osx/Toolbar-inverted.png
new file mode 100644
index 000000000..60f001ffa
--- /dev/null
+++ b/browser/themes/osx/Toolbar-inverted.png
Binary files differ
diff --git a/browser/themes/osx/Toolbar-inverted@2x.png b/browser/themes/osx/Toolbar-inverted@2x.png
new file mode 100644
index 000000000..511470b21
--- /dev/null
+++ b/browser/themes/osx/Toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/themes/osx/Toolbar-yosemite.png b/browser/themes/osx/Toolbar-yosemite.png
new file mode 100644
index 000000000..05594f2f5
--- /dev/null
+++ b/browser/themes/osx/Toolbar-yosemite.png
Binary files differ
diff --git a/browser/themes/osx/Toolbar-yosemite@2x.png b/browser/themes/osx/Toolbar-yosemite@2x.png
new file mode 100644
index 000000000..369e2385b
--- /dev/null
+++ b/browser/themes/osx/Toolbar-yosemite@2x.png
Binary files differ
diff --git a/browser/themes/osx/Toolbar.png b/browser/themes/osx/Toolbar.png
new file mode 100644
index 000000000..45d616d94
--- /dev/null
+++ b/browser/themes/osx/Toolbar.png
Binary files differ
diff --git a/browser/themes/osx/Toolbar@2x.png b/browser/themes/osx/Toolbar@2x.png
new file mode 100644
index 000000000..25ab9feba
--- /dev/null
+++ b/browser/themes/osx/Toolbar@2x.png
Binary files differ
diff --git a/browser/themes/osx/aboutSessionRestore-window-icon.png b/browser/themes/osx/aboutSessionRestore-window-icon.png
new file mode 100644
index 000000000..02dc44208
--- /dev/null
+++ b/browser/themes/osx/aboutSessionRestore-window-icon.png
Binary files differ
diff --git a/browser/themes/osx/aboutSyncTabs.css b/browser/themes/osx/aboutSyncTabs.css
new file mode 100644
index 000000000..4cedad649
--- /dev/null
+++ b/browser/themes/osx/aboutSyncTabs.css
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#tabs-display,
+#tabsList {
+ background-color: transparent;
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#tabsList {
+ width: 100%;
+}
+
+#tabs-display {
+ background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
+}
+
+#headers {
+ background: url(chrome://browser/skin/sync-32.png) no-repeat;
+ margin-top: 4px;
+ width: 45em;
+ height: 32px;
+ 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://browser/skin/sync-mobileIcon.svg#icon");
+}
+richlistitem.desktop[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
+}
+
+.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/browser/themes/osx/actionicon-tab.png b/browser/themes/osx/actionicon-tab.png
new file mode 100644
index 000000000..ab7861313
--- /dev/null
+++ b/browser/themes/osx/actionicon-tab.png
Binary files differ
diff --git a/browser/themes/osx/actionicon-tab@2x.png b/browser/themes/osx/actionicon-tab@2x.png
new file mode 100644
index 000000000..f53fdda57
--- /dev/null
+++ b/browser/themes/osx/actionicon-tab@2x.png
Binary files differ
diff --git a/browser/themes/osx/browser-lightweightTheme.css b/browser/themes/osx/browser-lightweightTheme.css
new file mode 100644
index 000000000..aeae9d925
--- /dev/null
+++ b/browser/themes/osx/browser-lightweightTheme.css
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include shared.inc
+
+/*
+ * LightweightThemeListener will append the current lightweight theme's header
+ * image to the background-image for each of the following rulesets.
+ */
+
+/* Lightweight theme on tabs */
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
+ background-attachment: scroll, fixed;
+ background-color: transparent;
+ background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
+ background-position: 0 0, right top;
+ background-repeat: repeat-x, no-repeat;
+}
+
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+ background-attachment: scroll, scroll, fixed;
+ background-color: transparent;
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
+ @fgTabTextureLWT@;/*,
+ lwtHeader;*/
+ background-position: 0 0, 0 0, right top;
+ background-repeat: repeat-x, repeat-x, no-repeat;
+}
+
+@media (min-resolution: 2dppx) {
+ #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
+ @fgTabTextureLWT@;/*,
+ lwtHeader;*/
+ }
+}
diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css
new file mode 100644
index 000000000..e8ac9163e
--- /dev/null
+++ b/browser/themes/osx/browser.css
@@ -0,0 +1,3406 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/");
+
+%include shared.inc
+%filter substitution
+%define forwardTransitionLength 150ms
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
+%define toolbarButtonPressed :hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"])
+%define windowButtonMarginTop 11px
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+@namespace svg url("http://www.w3.org/2000/svg");
+
+:root {
+ --space-above-tabbar: 9px;
+ --tabs-toolbar-color: #333;
+
+ --backbutton-urlbar-overlap: 6px;
+ /* icon width + border + horizontal padding (without the overlap from backbutton-urlbar-overlap) */
+ --forwardbutton-width: 26px;
+
+ --toolbarbutton-hover-background: hsla(0,0%,100%,.1) linear-gradient(hsla(0,0%,100%,.3), hsla(0,0%,100%,.1)) padding-box;
+ --toolbarbutton-hover-bordercolor: hsla(0,0%,0%,.2);
+ --toolbarbutton-hover-boxshadow: 0 1px 0 hsla(0,0%,100%,.5),
+ 0 1px 0 hsla(0,0%,100%,.5) inset;
+
+ --toolbarbutton-active-background: hsla(0,0%,0%,.02) linear-gradient(hsla(0,0%,0%,.12), transparent) border-box;
+ --toolbarbutton-active-bordercolor: hsla(0,0%,0%,.3);
+ --toolbarbutton-active-boxshadow: 0 1px 0 hsla(0,0%,100%,.5),
+ 0 1px 0 hsla(0,0%,0%,.05) inset,
+ 0 1px 1px hsla(0,0%,0%,.2) inset;
+
+ --toolbarbutton-checkedhover-backgroundcolor: hsla(0,0%,0%,.09);
+
+ --urlbar-dropmarker-url: url("chrome://browser/skin/urlbar-history-dropmarker.png");
+ --urlbar-dropmarker-region: rect(0, 11px, 14px, 0);
+ --urlbar-dropmarker-active-region: rect(0, 22px, 14px, 11px);
+ --urlbar-dropmarker-2x-url: url("chrome://browser/skin/urlbar-history-dropmarker@2x.png");
+ --urlbar-dropmarker-2x-region: rect(0, 22px, 28px, 0);
+ --urlbar-dropmarker-active-2x-region: rect(0, 44px, 28px, 22px);
+
+ --panel-separator-color: hsla(210,4%,10%,.14);
+ --arrowpanel-dimmed: hsla(210,4%,10%,.07);
+ --arrowpanel-dimmed-further: hsla(210,4%,10%,.12);
+ --arrowpanel-dimmed-even-further: hsla(210,4%,10%,.17);
+
+ --urlbar-separator-color: hsla(0,0%,16%,.2);
+}
+
+#urlbar:-moz-lwtheme:not([focused="true"]),
+.searchbar-textbox:-moz-lwtheme:not([focused="true"]) {
+ opacity: .9;
+}
+
+#navigator-toolbox::after {
+ -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
+ content: "";
+ display: -moz-box;
+ border-top: 1px solid hsla(0,0%,100%,.15);
+ border-bottom: 1px solid hsla(0,0%,0%,.15);
+ margin-top: -2px;
+ position: relative;
+ z-index: 2; /* navbar is at 1 */
+}
+
+@media (-moz-mac-yosemite-theme) {
+ #navigator-toolbox:-moz-window-inactive::after {
+ border-top-style: none;
+ border-bottom-color: hsla(0,0%,0%,.1);
+ margin-top: -1px;
+ }
+}
+
+#navigator-toolbox toolbarbutton:-moz-lwtheme {
+ color: inherit;
+ text-shadow: inherit;
+}
+
+#main-window {
+ -moz-appearance: none;
+ background-color: #eeeeee;
+}
+
+/** Begin titlebar **/
+
+#titlebar-buttonbox > .titlebar-button {
+ display: none;
+}
+
+/* NB: these would be margin-inline-start/end if it wasn't for the fact that OS X
+ * doesn't reverse the order of the items in the titlebar in RTL mode. */
+.titlebar-placeholder[type="caption-buttons"],
+#titlebar-buttonbox {
+ margin-left: 7px;
+}
+
+.titlebar-placeholder[type="fullscreen-button"],
+#titlebar-secondary-buttonbox {
+ margin-right: 7px;
+ margin-left: 7px;
+}
+
+#main-window:not(:-moz-lwtheme) > #titlebar {
+ -moz-appearance: -moz-window-titlebar;
+}
+
+#main-window:not([tabsintitlebar]) > #titlebar {
+ height: 22px; /* The native titlebar on OS X is 22px tall. */
+}
+
+/**
+ * For tabs in titlebar on OS X, we stretch the titlebar down so that the
+ * tabstrip can overlap it.
+ */
+#main-window[tabsintitlebar] > #titlebar {
+ min-height: calc(var(--tab-min-height) + var(--space-above-tabbar) - var(--tab-toolbar-navbar-overlap));
+}
+
+/**
+ * We also vertically center the window buttons.
+ */
+#titlebar-buttonbox-container {
+ -moz-box-align: start;
+}
+
+#main-window[tabsintitlebar] > #titlebar > #titlebar-content > #titlebar-buttonbox-container,
+#main-window[tabsintitlebar] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button {
+ margin-top: @windowButtonMarginTop@;
+}
+
+#main-window:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-buttonbox-container,
+#main-window:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button {
+ margin-top: 3px;
+}
+
+#main-window[customize-entered] > #titlebar {
+ -moz-appearance: none;
+}
+
+/** End titlebar **/
+
+#main-window[chromehidden~="toolbar"][chromehidden~="location"][chromehidden~="directories"] {
+ border-top: 1px solid rgba(0,0,0,0.65);
+}
+
+#navigator-toolbox > toolbar:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
+ -moz-appearance: none;
+ background: url(chrome://browser/skin/Toolbar-background-noise.png) hsl(0,0%,83%);
+}
+
+/* remove noise texture on Yosemite */
+@media (-moz-mac-yosemite-theme) {
+ #navigator-toolbox > toolbar:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
+ background-image: none;
+ }
+
+ #navigator-toolbox > toolbar:-moz-window-inactive:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
+ background-color: hsl(0,0%,95%);
+ }
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar) {
+ overflow: -moz-hidden-unscrollable;
+ max-height: 4em;
+ transition: min-height 170ms ease-out, max-height 170ms ease-out;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar)[collapsed=true] {
+ min-height: 0.1px;
+ max-height: 0;
+ transition: min-height 170ms ease-out, max-height 170ms ease-out, visibility 170ms linear;
+}
+
+#nav-bar {
+ -moz-appearance: none;
+ background: url(chrome://browser/skin/Toolbar-background-noise.png),
+ linear-gradient(hsl(0,0%,93%), hsl(0,0%,83%));
+ background-clip: border-box;
+ background-origin: border-box !important;
+
+ /* Move the noise texture out of the top 1px strip because that overlaps
+ with the tabbar and we don't want to repaint it when animating tabs.
+ The noise image is at least 100px high, so repeating it only horizontally
+ is enough. */
+ background-repeat: repeat-x, no-repeat;
+ background-position: 0 1px, 0 0;
+
+ box-shadow: inset 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+@media (min-resolution: 2dppx) {
+ #nav-bar {
+ background-size: 100px 100px, auto;
+ }
+}
+
+/* remove noise texture on Yosemite */
+@media (-moz-mac-yosemite-theme) {
+ #nav-bar {
+ background: linear-gradient(hsl(0,0%,93%), hsl(0,0%,83%));
+ }
+
+ #nav-bar:-moz-window-inactive {
+ background: linear-gradient(hsl(0,0%,97%), hsl(0,0%,95%));
+ }
+}
+
+/* Draw the bottom border of the tabs toolbar when it's not using
+ -moz-appearance: toolbar. */
+#main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
+#main-window:not([tabsintitlebar]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
+#TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-lwtheme {
+ border-top: 1px solid hsla(0,0%,0%,.3);
+ background-clip: padding-box;
+ margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
+ /* Position the toolbar above the bottom of background tabs */
+ position: relative;
+ z-index: 1;
+}
+
+/* Always draw a border on Yosemite to ensure the border is well-defined there
+ * (the default border is too light). */
+@media (-moz-mac-yosemite-theme) {
+ #main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:not(:-moz-lwtheme) {
+ border-top: 1px solid hsla(0,0%,0%,.2);
+ background-clip: padding-box;
+ margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
+ /* Position the toolbar above the bottom of background tabs */
+ position: relative;
+ z-index: 1;
+ }
+
+ #main-window[tabsintitlebar] #TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-window-inactive:not(:-moz-lwtheme) {
+ border-top-color: hsla(0,0%,0%,.05);
+ }
+}
+
+#nav-bar-customization-target {
+ padding: 4px;
+}
+
+#PersonalToolbar {
+ padding: 0 4px 4px;
+}
+
+#PersonalToolbar:not([collapsed=true]) {
+ /* 4px padding ^ plus 19px personal-bookmarks (see below) */
+ min-height: 23px;
+}
+
+#navigator-toolbox > toolbar:not(#TabsToolbar):-moz-lwtheme {
+ background-color: @toolbarColorLWT@;
+ background-image: url(chrome://browser/skin/Toolbar-background-noise.png);
+}
+
+#PersonalToolbar:not(:-moz-lwtheme):-moz-window-inactive,
+#nav-bar:not(:-moz-lwtheme):-moz-window-inactive {
+ background-color: -moz-mac-chrome-inactive;
+}
+
+/* ----- BOOKMARK TOOLBAR ----- */
+
+#personal-bookmarks {
+ min-height: 19px; /* 16px button height + 2px padding + 1px margin-bottom */
+}
+
+#nav-bar-customization-target > #wrapper-personal-bookmarks > #personal-bookmarks {
+ min-height: 32px;
+ -moz-box-align: center;
+}
+
+toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/icons/chevron.png");
+ margin: 1px 0 0;
+ padding: 0;
+}
+
+toolbar[brighttext] toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/icons/chevron-inverted.png");
+}
+
+toolbarbutton.chevron > .toolbarbutton-text {
+ display: none;
+}
+
+toolbarbutton.chevron:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+@media (min-resolution: 2dppx) {
+ toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/icons/chevron@2x.png");
+ }
+
+ toolbar[brighttext] toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/icons/chevron-inverted@2x.png");
+ }
+
+ toolbarbutton.chevron > .toolbarbutton-icon {
+ width: 13px;
+ }
+}
+
+/* ----- BOOKMARK BUTTONS ----- */
+
+toolbarbutton.bookmark-item:not(.subviewbutton),
+#personal-bookmarks[cui-areatype="toolbar"]:not([overflowedItem=true]) > #bookmarks-toolbar-placeholder {
+ border: 0;
+ border-radius: 10000px;
+ padding: 1px 8px;
+ margin: 0 0 1px;
+}
+
+#personal-bookmarks[cui-areatype="toolbar"]:not([overflowedItem=true]) > #bookmarks-toolbar-placeholder {
+ -moz-box-orient: horizontal;
+}
+
+.bookmark-item > .toolbarbutton-menu-dropmarker {
+ list-style-image: url("chrome://browser/skin/places/folderDropArrow.png");
+ -moz-image-region: rect(0, 7px, 5px, 0);
+ margin-top: 1px;
+ margin-inline-start: 3px;
+ margin-inline-end: -2px;
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item > .toolbarbutton-menu-dropmarker {
+ list-style-image: url("chrome://browser/skin/places/folderDropArrow@2x.png");
+ -moz-image-region: rect(0, 14px, 10px, 0);
+ }
+
+ .bookmark-item > .toolbarbutton-menu-dropmarker > .dropmarker-icon {
+ width: 7px;
+ }
+}
+
+.bookmark-item > .toolbarbutton-text,
+#personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-text {
+ display: -moz-box !important; /* Force the display of the label for bookmarks */
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):hover {
+ background-color: rgba(0, 0, 0, .205);
+}
+
+toolbarbutton.bookmark-item:hover:not(.subviewbutton),
+toolbarbutton.bookmark-item[open="true"]:not(.subviewbutton) {
+ color: #FFF !important;
+ text-shadow: 0 1px rgba(0, 0, 0, .4) !important;
+}
+
+.bookmark-item:hover > .toolbarbutton-menu-dropmarker,
+.bookmark-item[open="true"] > .toolbarbutton-menu-dropmarker {
+ -moz-image-region: rect(5px, 7px, 10px, 0);
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item:hover > .toolbarbutton-menu-dropmarker,
+ .bookmark-item[open="true"] > .toolbarbutton-menu-dropmarker {
+ -moz-image-region: rect(10px, 14px, 20px, 0);
+ }
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):active:hover,
+toolbarbutton.bookmark-item:not(.subviewbutton)[open="true"] {
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.4), 0 1px rgba(255, 255, 255, 0.4);
+ background-color: rgba(0, 0, 0, .5);
+}
+
+toolbarbutton.bookmark-item > menupopup {
+ margin-inline-start: 3px;
+}
+
+.bookmark-item > .toolbarbutton-icon,
+#personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
+ width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+}
+
+.bookmark-item > .toolbarbutton-icon[label]:not([label=""]),
+.bookmark-item > .toolbarbutton-icon[type="menu"] {
+ margin-inline-end: 5px;
+}
+
+.bookmark-item[container] {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+.bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+.bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited] {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+}
+
+.bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+.bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+.bookmark-item[query][hostContainer] {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+.bookmark-item[query][hostContainer][open] {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item[container] {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png");
+ }
+
+ .bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/page-livemarks@2x.png");
+ }
+
+ .bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ }
+
+ .bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query@2x.png");
+ }
+
+ .bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag@2x.png");
+ }
+
+ .bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/history@2x.png");
+ }
+
+ .bookmark-item[query][hostContainer] {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png");
+ }
+
+ .bookmark-item[query][hostContainer][open] {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png");
+ }
+}
+
+/* Workaround for native menubar inheritance */
+.openintabs-menuitem,
+.openlivemarksite-menuitem,
+.livemarkstatus-menuitem {
+ list-style-image: none;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-icon,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
+ opacity: 0.5;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-text,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
+ opacity: 0.7;
+}
+
+#bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
+#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
+}
+
+@media (min-resolution: 2dppx) {
+ #bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar@2x.png") !important;
+ }
+
+ toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
+ #personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel@2x.png") !important;
+ }
+
+ #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+ from { transform: rotate(0deg) translateX(-14px) rotate(0deg) scale(1); opacity: 0; }
+ 60% { transform: rotate(180deg) translateX(-14px) rotate(-180deg) scale(2.2); opacity: 1; }
+ 80% { opacity: 1; }
+ to { transform: rotate(180deg) translateX(-14px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+ from { transform: scale(1); }
+ 50% { transform: scale(1.3); }
+ to { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+ min-height: 1px;
+ min-width: 1px;
+ height: 1px;
+ margin-bottom: -1px;
+ z-index: 5;
+ position: relative;
+}
+
+#bookmarked-notification {
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+ opacity: 0;
+}
+
+#bookmarked-notification-dropmarker-anchor {
+ z-index: -1;
+ position: relative;
+}
+
+#bookmarked-notification-dropmarker-icon {
+ width: 18px;
+ height: 18px;
+ visibility: hidden;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+ background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+ animation: animation-bookmarkAdded 800ms;
+ animation-timing-function: ease, ease, ease;
+}
+
+@media (min-resolution: 2dppx) {
+ #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+ background-image: url("chrome://browser/skin/places/bookmarks-notification-finish@2x.png");
+ }
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: none !important;
+}
+
+#bookmarked-notification-dropmarker-anchor[notification="finish"] > #bookmarked-notification-dropmarker-icon {
+ visibility: visible;
+ animation: animation-bookmarkPulse 300ms;
+ animation-delay: 600ms;
+ animation-timing-function: ease-out;
+}
+
+/* ----- BOOKMARK MENUS ----- */
+
+.bookmark-item > .menu-iconic-left > .menu-iconic-icon {
+ width: 16px;
+ height: 16px;
+}
+
+#bookmarksToolbarFolderMenu,
+#BMB_bookmarksToolbar,
+#panelMenu_bookmarksToolbar {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+}
+
+#menu_unsortedBookmarks,
+#BMB_unsortedBookmarks,
+#panelMenu_unsortedBookmarks {
+ list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.png");
+}
+
+@media (min-resolution: 2dppx) {
+ #bookmarksToolbarFolderMenu,
+ #BMB_bookmarksToolbar {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar@2x.png");
+ }
+
+ #BMB_unsortedBookmarks {
+ list-style-image: url("chrome://browser/skin/places/unfiledBookmarks@2x.png");
+ }
+}
+
+/* ----- PRIMARY TOOLBAR BUTTONS ----- */
+
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
+ max-width: 16px;
+ margin: 1px;
+}
+
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
+ max-width: 18px;
+ margin: 0;
+}
+
+toolbar .toolbarbutton-1:not([type="menu-button"]),
+.toolbarbutton-1 > .toolbarbutton-menubutton-button,
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ -moz-box-orient: vertical;
+ height: 24px;
+ padding: 0;
+ border: 0;
+}
+
+.findbar-button,
+toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],#back-button,#forward-button)),
+toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ border: 1px solid transparent;
+ border-radius: @toolbarbuttonCornerRadius@;
+ transition-property: background, border-color;
+ transition-duration: 250ms;
+}
+
+.findbar-button {
+ background: none;
+ box-shadow: none;
+}
+
+toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],#back-button,#forward-button)) {
+ padding: 0 4px;
+}
+
+.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover,
+toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],[disabled],[open],#back-button,#forward-button)):hover,
+toolbar .toolbarbutton-1[type="menu-button"]:not([disabled]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker,
+toolbar .toolbarbutton-1[type="menu-button"]:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button,
+toolbar .toolbarbutton-1[type="menu-button"]:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-dropmarker,
+toolbar .toolbaritem-combined-buttons:hover > .toolbarbutton-combined {
+ border-color: var(--toolbarbutton-hover-bordercolor);
+ box-shadow: var(--toolbarbutton-hover-boxshadow);
+}
+
+.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover,
+toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],[disabled],[open],#back-button,#forward-button)):hover,
+toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open]))[buttonover] > .toolbarbutton-menubutton-button,
+toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open],[buttonover])):hover > .toolbarbutton-menubutton-dropmarker {
+ background: var(--toolbarbutton-hover-background);
+}
+
+.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active),
+toolbar .toolbarbutton-1:not(:-moz-any([type="menu-button"],[disabled],#back-button,#forward-button)):-moz-any(:hover:active,[open],[checked]),
+toolbar .toolbarbutton-1[type="menu-button"]:not([disabled]) > .toolbarbutton-menubutton-button[open],
+toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open]))[buttonover]:active > .toolbarbutton-menubutton-button,
+toolbar .toolbarbutton-1[type="menu-button"]:not(:-moz-any([disabled],[open],[buttonover])):hover:active > .toolbarbutton-menubutton-dropmarker,
+toolbar .toolbarbutton-1[type="menu-button"][open]:not([disabled]) > .toolbarbutton-menubutton-dropmarker {
+ background: var(--toolbarbutton-active-background);
+ border-color: var(--toolbarbutton-active-bordercolor);
+ box-shadow: var(--toolbarbutton-active-boxshadow);
+ transition-duration: 10ms;
+}
+
+.findbar-button[checked="true"]:not(:active):hover,
+toolbar .toolbarbutton-1[checked]:not(:active):hover {
+ background-color: var(--toolbarbutton-checkedhover-backgroundcolor);
+ transition: background-color 250ms;
+}
+
+.toolbarbutton-1[type="menu-button"]:not([overflowedItem=true]) {
+ padding: 0;
+}
+
+toolbar .toolbarbutton-1[type="menu-button"] {
+ margin: 0;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menubutton-button,
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ margin: 0;
+}
+
+.toolbarbutton-1 {
+ margin: 0 4px;
+}
+
+toolbar .toolbarbutton-1:not([type="menu-button"]) {
+ margin: 0 2px;
+}
+
+/**
+ * Draw seperators before toolbar button dropmarkers, as well as between
+ * consecutive toolbarbutton-1's within a toolbaritem.
+ */
+toolbar .toolbaritem-combined-buttons:not(:hover) > separator,
+toolbar .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ content: "";
+ display: -moz-box;
+ width: 1px;
+ height: 18px;
+ margin-inline-start: -1px;
+ background-image: linear-gradient(currentColor 0, currentColor 100%);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 1px 18px;
+ opacity: .2;
+}
+
+toolbar[brighttext] .toolbaritem-combined-buttons > separator,
+toolbar[brighttext] .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ opacity: .3;
+}
+
+toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ -moz-box-orient: horizontal;
+}
+
+toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ margin: 0 4px;
+}
+
+#nav-bar .toolbarbutton-1:not(#back-button):not(#forward-button) {
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
+#nav-bar #PanelUI-button {
+ -moz-box-align: center;
+}
+
+#nav-bar #PanelUI-menu-button {
+ margin-top: 0;
+ margin-bottom: 0;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ margin-inline-start: 7px;
+ margin-inline-end: 7px;
+}
+
+%include ../shared/toolbarbuttons.inc.css
+%include ../shared/menupanel.inc.css
+
+@media not all and (min-resolution: 1.1dppx) {
+ #back-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 36px, 36px, 18px);
+ }
+
+ #forward-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 72px, 36px, 54px);
+ }
+
+ #home-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 126px, 36px, 108px);
+ }
+
+ #bookmarks-menu-button[buttonover]@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 144px, 36px, 126px);
+ }
+
+ #bookmarks-menu-button[starred][buttonover]@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 162px, 36px, 144px);
+ }
+
+ #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ -moz-image-region: rect(0px, 630px, 18px, 612px);
+ }
+
+ #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker:hover:active:not([disabled="true"]) > .dropmarker-icon {
+ -moz-image-region: rect(18px, 630px, 36px, 612px);
+ }
+
+ #history-panelmenu@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 180px, 36px, 162px);
+ }
+
+ #downloads-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 198px, 36px, 180px);
+ }
+
+ #add-ons-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 216px, 36px, 198px);
+ }
+
+ #open-file-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 234px, 36px, 216px);
+ }
+
+ #save-page-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 252px, 36px, 234px);
+ }
+
+ #sync-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 792px, 36px, 774px);
+ }
+
+ #containers-panelmenu@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 810px, 36px, 792px);
+ }
+
+ #feed-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 288px, 36px, 270px);
+ }
+
+ #social-share-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 306px, 36px, 288px);
+ }
+
+ #characterencoding-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 324px, 36px, 306px);
+ }
+
+ #new-window-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 342px, 36px, 324px);
+ }
+
+ #e10s-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 342px, 36px, 324px);
+ }
+
+ #webide-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 738px, 36px, 720px);
+ }
+
+ #new-tab-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 360px, 36px, 342px);
+ }
+
+ #privatebrowsing-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 378px, 36px, 360px);
+ }
+
+ #find-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 396px, 36px, 378px);
+ }
+
+ #print-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 414px, 36px, 396px);
+ }
+
+ #restore-button:hover:active:not([disabled="true"]),
+ #fullscreen-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 432px, 36px, 414px);
+ }
+
+ #developer-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 450px, 36px, 432px);
+ }
+
+ #preferences-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 468px, 36px, 450px);
+ }
+
+ #PanelUI-menu-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 486px, 36px, 468px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #cut-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 504px, 36px, 486px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #copy-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 522px, 36px, 504px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #paste-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 540px, 36px, 522px);
+ }
+
+ #zoom-controls[cui-areatype="toolbar"] > #zoom-out-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 558px, 36px, 540px);
+ }
+
+ #zoom-controls[cui-areatype="toolbar"] > #zoom-in-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 576px, 36px, 558px);
+ }
+
+ #nav-bar-overflow-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 612px, 36px, 594px);
+ }
+
+ #email-link-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 666px, 36px, 648px);
+ }
+
+ #sidebar-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 684px, 36px, 666px);
+ }
+
+ #panic-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(18px, 702px, 36px, 684px);
+ }
+
+ /**
+ * OSX has a unique set of icons when fullscreen is in the checked state.
+ */
+
+ #fullscreen-button[checked="true"]:not([cui-areatype="menu-panel"]) {
+ -moz-image-region: rect(36px, 432px, 54px, 414px);
+ }
+
+ #fullscreen-button[checked="true"]@toolbarButtonPressed@ {
+ -moz-image-region: rect(54px, 432px, 72px, 414px);
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ #back-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 72px, 72px, 36px);
+ }
+
+ #forward-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 144px, 72px, 108px);
+ }
+
+ #home-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 252px, 72px, 216px);
+ }
+
+ #bookmarks-menu-button@toolbarButtonPressed@[buttonover] {
+ -moz-image-region: rect(36px, 288px, 72px, 252px);
+ }
+
+ #bookmarks-menu-button@toolbarButtonPressed@[starred][buttonover] {
+ -moz-image-region: rect(36px, 324px, 72px, 288px);
+ }
+
+ #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker:hover:active:not([disabled="true"]) > .dropmarker-icon {
+ -moz-image-region: rect(36px, 1260px, 72px, 1224px);
+ }
+
+ #history-panelmenu@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 360px, 72px, 324px);
+ }
+
+ #downloads-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 396px, 72px, 360px);
+ }
+
+ #add-ons-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 432px, 72px, 396px);
+ }
+
+ #open-file-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 468px, 72px, 432px);
+ }
+
+ #save-page-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 504px, 72px, 468px);
+ }
+
+ #sync-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 1584px, 72px, 1548px);
+ }
+
+ #containers-panelmenu@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 1620px, 72px, 1584px);
+ }
+
+ #feed-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 576px, 72px, 540px);
+ }
+
+ #social-share-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 612px, 72px, 576px);
+ }
+
+ #characterencoding-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 648px, 72px, 612px);
+ }
+
+ #new-window-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 684px, 72px, 648px);
+ }
+
+ #e10s-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 684px, 72px, 648px);
+ }
+
+ #e10s-button > .toolbarbutton-icon {
+ transform: scaleY(-1);
+ }
+
+ #webide-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 1476px, 72px, 1440px);
+ }
+
+ #new-tab-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 720px, 72px, 684px);
+ }
+
+ #privatebrowsing-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 756px, 72px, 720px);
+ }
+
+ #find-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 792px, 72px, 756px);
+ }
+
+ #print-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 828px, 72px, 792px);
+ }
+
+ #restore-button:hover:active:not([disabled="true"]),
+ #fullscreen-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 864px, 72px, 828px);
+ }
+
+ #fullscreen-button[cui-areatype="toolbar"][checked="true"] {
+ -moz-image-region: rect(72px, 864px, 108px, 828px);
+ }
+
+ #fullscreen-button@toolbarButtonPressed@[checked="true"] {
+ -moz-image-region: rect(108px, 864px, 144px, 828px);
+ }
+
+ #developer-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 900px, 72px, 864px);
+ }
+
+ #preferences-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 936px, 72px, 900px);
+ }
+
+ #PanelUI-menu-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 972px, 72px, 936px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #cut-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 1008px, 72px, 972px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #copy-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 1044px, 72px, 1008px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #paste-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 1080px, 72px, 1044px);
+ }
+
+ #zoom-controls[cui-areatype="toolbar"] > #zoom-out-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 1116px, 72px, 1080px);
+ }
+
+ #zoom-controls[cui-areatype="toolbar"] > #zoom-in-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 1152px, 72px, 1116px);
+ }
+
+ #nav-bar-overflow-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 1224px, 72px, 1188px);
+ }
+
+ #email-link-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 1332px, 72px, 1296px);
+ }
+
+ #sidebar-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 1368px, 72px, 1332px);
+ }
+
+ #panic-button@toolbarButtonPressed@ {
+ -moz-image-region: rect(36px, 1404px, 72px, 1368px);
+ }
+}
+
+toolbar .toolbarbutton-1:not([type="menu-button"]),
+toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ min-width: 28px;
+}
+
+#main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled="true"] > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-menu-dropmarker,
+#main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-menubutton-dropmarker,
+.toolbarbutton-1:not(:hover):-moz-window-inactive > #downloads-indicator-anchor,
+.toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-icon,
+.toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-text,
+.toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+.toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menu-dropmarker,
+.toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+.toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ opacity: .5;
+}
+
+#main-window:not([customizing]) .toolbarbutton-1:-moz-window-inactive[disabled="true"] > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1:-moz-window-inactive[disabled="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1:-moz-window-inactive > .toolbarbutton-menubutton-button[disabled="true"] > .toolbarbutton-icon {
+ opacity: .25;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url(chrome://browser/skin/toolbarbutton-dropmarker.png);
+}
+
+@media (min-resolution: 2dppx) {
+ .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url(chrome://browser/skin/toolbarbutton-dropmarker@2x.png);
+ }
+
+ .toolbarbutton-1 > .toolbarbutton-menu-dropmarker > .dropmarker-icon,
+ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ width: 7px;
+ }
+}
+
+.toolbarbutton-1 > .toolbarbutton-menu-dropmarker {
+ margin-inline-end: 1px;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ border-inline-end: none !important;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menubutton-button:-moz-locale-dir(rtl),
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menubutton-button:-moz-locale-dir(ltr),
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.toolbarbutton-1 > menupopup {
+ margin-top: 1px;
+}
+
+.toolbarbutton-1 > menupopup.cui-widget-panel {
+ margin-top: -5px;
+}
+
+/* Common back and forward button styles */
+
+#back-button,
+#forward-button {
+ background: linear-gradient(rgba(255,255,255,0.5),
+ rgba(255,255,255,0.2) 50%,
+ rgba(255,255,255,0.1) 50%,
+ rgba(255,255,255,0.2)) repeat-x;
+}
+
+#back-button:-moz-lwtheme,
+#forward-button:-moz-lwtheme {
+ background-origin: border-box;
+ border: 1px solid rgba(0,0,0,0.4);
+ box-shadow: inset 0 1px rgba(255,255,255,0.3), 0 1px rgba(255,255,255,0.2);
+}
+
+#back-button:active:hover:-moz-lwtheme,
+#forward-button:active:hover:-moz-lwtheme {
+ background-color: rgba(0,0,0,0.2);
+ box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
+}
+
+#back-button:-moz-window-inactive,
+#forward-button:-moz-window-inactive {
+ background-color: rgba(0,0,0,0.04);
+ border-color: rgba(0,0,0,0.2);
+}
+
+#back-button > .toolbarbutton-icon,
+#forward-button > .toolbarbutton-icon {
+ max-width: none;
+ width: 18px;
+}
+
+/* Back button styles */
+
+#back-button {
+ width: 32px;
+ height: 32px;
+ padding: 4px 5px 4px 3px;
+ margin-inline-end: 0;
+ border-radius: 10000px;
+}
+
+#back-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+#back-button:not(:-moz-lwtheme) {
+ height: 33px;
+ padding: 4px 5px 5px 3px;
+ margin-bottom: -1px;
+ background: url(chrome://browser/skin/keyhole-circle.png) 0 0 no-repeat;
+}
+
+@media (min-resolution: 2dppx) {
+ #back-button:not(:-moz-lwtheme) {
+ background-image: url(chrome://browser/skin/keyhole-circle@2x.png);
+ background-size: 96px;
+ }
+}
+
+#back-button:-moz-window-inactive:not(:-moz-lwtheme) {
+ background-position: -64px 0;
+}
+
+#back-button:not([disabled="true"]):active:hover:not(:-moz-lwtheme),
+#back-button[open="true"]:not(:-moz-lwtheme) {
+ background-position: -32px 0;
+}
+
+/* Forward button styles */
+
+#forward-button {
+ margin-left: -2px;
+ margin-right: 0;
+ padding-left: 2px;
+ width: 32px;
+}
+
+#forward-button > .toolbarbutton-icon {
+ /* shift the icon away from the back button */
+ margin-left: 3px;
+ margin-right: -1px;
+}
+
+#forward-button:not(:-moz-lwtheme) {
+ background-image: linear-gradient(hsla(0,0%,100%,.73), hsla(0,0%,100%,.05) 85%);
+ border: 1px solid;
+ border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.25) hsla(0,0%,0%,.2);
+ box-shadow: inset 0 1px 0 hsla(0,0%,100%,.2),
+ inset 0 0 1px hsla(0,0%,100%,.1),
+ 0 1px 0 hsla(0,0%,100%,.2);
+}
+
+#forward-button:hover:active:not(:-moz-lwtheme) {
+ background-image: linear-gradient(hsla(0,0%,60%,.37), hsla(0,0%,100%,.35) 95%);
+ border-color: hsla(0,0%,0%,.43) hsla(0,0%,0%,.25) hsla(0,0%,0%,.37);
+ box-shadow: inset 0 1px 0 hsla(0,0%,0%,.02),
+ inset 0 1px 2px hsla(0,0%,0%,.2),
+ 0 1px 0 hsla(0,0%,100%,.2);
+}
+
+#forward-button:-moz-window-inactive:not(:-moz-lwtheme) {
+ background-image: none;
+ border-color: hsla(0,0%,0%,.2);
+ box-shadow: inset 0 1px 0 hsla(0,0%,100%,.35);
+}
+
+@media (-moz-mac-yosemite-theme) {
+ /* Base and hover styles */
+ #forward-button:not(:-moz-lwtheme),
+ #back-button:not(:-moz-lwtheme),
+ #forward-button:hover:not(:-moz-lwtheme),
+ #back-button:hover:not(:-moz-lwtheme) {
+ background-image: none;
+ background-color: #fbfbfb;
+ background-clip: border-box;
+ border: 1px solid #aeaeae;
+ box-shadow: none;
+ }
+
+ #back-button:not(:-moz-lwtheme) {
+ /* On non-lwtheme OS X, we use a 32-px image in a 33px container with negative margin bottom.
+ * Except that on Yosemite, we want to use a border instead of an image - like for lwthemes.
+ * So we need to readjust the height, padding and margin-bottom of the back button
+ * similarly to how we do this for lwthemes (but keep a specific enough selector to
+ * override the other :not(:-moz-lwtheme) selector above): */
+ height: 32px;
+ padding: 4px 5px 4px 3px;
+ margin-bottom: 0;
+ }
+
+ /* White inset shadow on top of the back button */
+ #back-button:not(:-moz-lwtheme),
+ #back-button:hover:not(:-moz-lwtheme) {
+ box-shadow: inset 0 1px 0.5px 0 #fff;
+ }
+
+ /* Bottom shadow and right border for the forward button to match the location bar */
+ #forward-button:hover:not(:-moz-lwtheme),
+ #forward-button:not(:-moz-lwtheme) {
+ box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
+ border: 0 none;
+ border-right: 1px solid rgba(0,0,0,0.3);
+ }
+
+ /* Active styling: transparent white over toolbar colors */
+ #forward-button:hover:active:not(:-moz-lwtheme),
+ #back-button:not([disabled="true"]):hover:active:not(:-moz-lwtheme) {
+ background-image: linear-gradient(to bottom, rgba(255,255,255, 0.3), rgba(255,255,255, 0.4));
+ background-color: transparent;
+ box-shadow: none;
+ }
+
+ /* Add white detail on top of forward button only when active */
+ #forward-button:hover:active:not(:-moz-lwtheme) {
+ box-shadow: inset 0 1px 0 #f1f1f1, 0 1px 0 0 rgba(0,0,0,0.2);
+ }
+
+ /* Inactive window styling (hover styling is identical;
+ * we need to make this explicit because it is different on lion, see above. */
+ #forward-button:-moz-window-inactive:not(:-moz-lwtheme),
+ #back-button:-moz-window-inactive:not(:-moz-lwtheme),
+ #forward-button:hover:-moz-window-inactive:not(:-moz-lwtheme),
+ #back-button:hover:-moz-window-inactive:not(:-moz-lwtheme) {
+ background-image: none;
+ background-color: #f0f0f0;
+ background-clip: border-box;
+ border: 1px solid rgba(0,0,0,0.1);
+ box-shadow: none;
+ }
+
+ /* Lightweight theme styles */
+ #forward-button:-moz-lwtheme,
+ #forward-button:hover:-moz-lwtheme {
+ border: 0 none;
+ border-right: 1px solid rgba(0,0,0,0.3);
+ box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
+ }
+
+ #forward-button:-moz-lwtheme,
+ #forward-button:hover:-moz-lwtheme,
+ #back-button:-moz-lwtheme,
+ #back-button:hover:-moz-lwtheme {
+ background-color: rgba(255,255,255,0.5);
+ background-image: none;
+ background-clip: border-box;
+ }
+
+ #forward-button:hover:active:-moz-lwtheme,
+ #back-button:hover:active:-moz-lwtheme {
+ background-image: linear-gradient(to bottom, rgba(255,255,255, 0.7), rgba(255,255,255, 0.8));
+ background-color: transparent;
+ box-shadow: none;
+ }
+
+ #forward-button:hover:active:-moz-lwtheme {
+ box-shadow: 0 1px 0 0 rgba(0,0,0,0.2);
+ }
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+ transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+ margin-left: calc(0px - var(--forwardbutton-width) - var(--backbutton-urlbar-overlap));
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+ /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+ transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+ /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+ margin-left: calc(-0.01px - var(--forwardbutton-width) - var(--backbutton-urlbar-overlap));
+}
+
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-back.png") !important;
+}
+
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-forward.png") !important;
+}
+
+/* undo close tab menu item */
+#alltabs_undoCloseTab {
+ list-style-image: url(chrome://browser/skin/undoCloseTab.png);
+}
+
+@media (min-resolution: 2dppx) {
+ #alltabs_undoCloseTab {
+ list-style-image: url(chrome://browser/skin/undoCloseTab@2x.png);
+ }
+ #alltabs_undoCloseTab > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+#cut-button {
+ margin-inline-end: 0;
+}
+
+#edit-controls[cui-areatype="toolbar"] > #cut-button {
+ border-inline-end: 0;
+}
+
+#paste-button {
+ border-inline-start: none;
+ margin-inline-start: 0;
+}
+
+#cut-button:-moz-locale-dir(ltr),
+#paste-button:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+#cut-button:-moz-locale-dir(rtl),
+#paste-button:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+#copy-button {
+ border-radius: 0;
+ margin-right: 0;
+ margin-left: 0;
+}
+
+/* zoom controls */
+
+#zoom-out-button {
+ margin-inline-end: 0;
+}
+
+#zoom-in-button {
+ margin-inline-start: 0;
+ border-inline-start: 0;
+}
+
+#zoom-controls[cui-areatype="toolbar"] > #zoom-out-button {
+ border-inline-end: 0;
+}
+
+#zoom-controls[cui-areatype="toolbar"] > #zoom-in-button {
+ border-inline-start-width: 1px;
+}
+
+#zoom-controls[cui-areatype="toolbar"] > #zoom-reset-button {
+ border-radius: 0;
+}
+
+#zoom-out-button:-moz-locale-dir(ltr),
+#zoom-in-button:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+#zoom-out-button:-moz-locale-dir(rtl),
+#zoom-in-button:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button {
+ min-width: 0;
+ margin: 0;
+ -moz-box-orient: horizontal;
+ -moz-box-align: center;
+}
+
+/* ----- FULLSCREEN WINDOW CONTROLS ----- */
+
+#minimize-button,
+#close-button,
+#fullscreen-button ~ #window-controls > #restore-button {
+ display: none;
+}
+
+/* ::::: nav-bar-inner ::::: */
+
+#urlbar,
+.searchbar-textbox {
+ font: icon;
+ -moz-appearance: none;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2),
+ inset 0 0 1px hsla(0,0%,0%,.05),
+ inset 0 1px 2px hsla(0,0%,0%,.1);
+ margin: 0 4px;
+ padding: 1px 0;
+ border: 1px solid;
+ background-image: linear-gradient(hsl(0,0%,97%), hsl(0,0%,100%));
+ border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.25) hsla(0,0%,0%,.15);
+ background-clip: padding-box;
+}
+
+#urlbar[readonly] {
+ background-color: -moz-field;
+}
+
+@media (-moz-mac-yosemite-theme) {
+ .searchbar-textbox,
+ #urlbar {
+ border-color: #fff;
+ border-radius: 3px;
+ box-shadow: 0 .5px 0 0 rgba(0,0,0,0.2);
+ background-image: none;
+ }
+ .searchbar-textbox:-moz-window-inactive,
+ #urlbar:-moz-window-inactive {
+ box-shadow: none;
+ border-color: rgba(0,0,0,0.1);
+ }
+}
+
+#urlbar[focused="true"],
+.searchbar-textbox[focused="true"] {
+ border-color: -moz-mac-focusring;
+ box-shadow: @focusRingShadow@;
+}
+
+@media (-moz-mac-yosemite-theme) {
+ #urlbar[focused="true"],
+ .searchbar-textbox[focused="true"] {
+ box-shadow: @yosemiteFocusRingShadow@;
+ }
+}
+
+#urlbar-container {
+ -moz-box-align: center;
+}
+
+#urlbar {
+ border-radius: @toolbarbuttonCornerRadius@;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar {
+ border-inline-start: none;
+ margin-left: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ {
+ clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
+ margin-inline-start: calc(-1 * var(--backbutton-urlbar-overlap));
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+ /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
+ transform: scaleX(-1);
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+ -moz-box-direction: reverse;
+}
+
+%include ../shared/identity-block/identity-block.inc.css
+
+#identity-box {
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+#urlbar:not([focused="true"]) > #identity-box {
+ margin-top: -1px;
+ margin-bottom: -1px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+}
+
+@media (-moz-mac-yosemite-theme) {
+ #urlbar:not([focused="true"]):not(:-moz-window-inactive) > #identity-box {
+ margin-top: -2px;
+ margin-bottom: -2px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ }
+}
+
+#identity-box:-moz-locale-dir(ltr) {
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+}
+
+#identity-box:-moz-locale-dir(rtl) {
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+}
+
+#identity-box:-moz-focusring {
+ box-shadow: 0 0 2px 1px -moz-mac-focusring inset,
+ 0 0 2px 2px -moz-mac-focusring;
+ border-inline-end-style: none;
+ padding-inline-end: 5px;
+}
+
+.urlbar-input-box {
+ margin-inline-start: 0;
+ padding: 3px 0 2px;
+}
+
+.urlbar-input-box,
+#urlbar-display-box {
+ padding-inline-start: 4px;
+ border-inline-start: 1px solid var(--urlbar-separator-color);
+ border-image: linear-gradient(transparent 15%, var(--urlbar-separator-color) 15%, var(--urlbar-separator-color) 85%, transparent 85%);
+ border-image-slice: 1;
+}
+
+.urlbar-history-dropmarker {
+ padding: 0 3px;
+ list-style-image: var(--urlbar-dropmarker-url);
+ -moz-image-region: var(--urlbar-dropmarker-region);
+ transition: opacity 0.15s ease;
+}
+
+#urlbar-wrapper[switchingtabs] > #urlbar > .urlbar-textbox-container > .urlbar-history-dropmarker {
+ transition: none;
+}
+
+#navigator-toolbox:not(:hover) #nav-bar:not([customizing="true"]) #urlbar:not([focused]) > .urlbar-textbox-container > .urlbar-history-dropmarker {
+ opacity: 0;
+}
+
+.urlbar-history-dropmarker[open="true"],
+.urlbar-history-dropmarker:hover:active {
+ -moz-image-region: var(--urlbar-dropmarker-active-region);
+}
+
+@media (min-resolution: 2dppx) {
+ .urlbar-history-dropmarker {
+ list-style-image: var(--urlbar-dropmarker-2x-url);
+ -moz-image-region: var(--urlbar-dropmarker-2x-region);
+ }
+
+ .urlbar-history-dropmarker[open="true"],
+ .urlbar-history-dropmarker:hover:active {
+ -moz-image-region: var(--urlbar-dropmarker-active-2x-region);
+ }
+
+ .urlbar-history-dropmarker > .dropmarker-icon {
+ width: 11px;
+ }
+}
+
+#urlbar-icons {
+ -moz-box-align: center;
+}
+
+.urlbar-icon {
+ padding: 0 3px;
+ /* 16x16 icon with border-box sizing */
+ width: 22px;
+ height: 16px;
+}
+
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+ 0% {
+ transform: scale(0);
+ }
+ 75% {
+ transform: scale(1.5);
+ }
+ 100% {
+ transform: scale(1.0);
+ }
+}
+
+#urlbar-zoom-button {
+ margin: 0 3px;
+ font-size: .8em;
+ padding: 0 8px;
+ border-radius: 1em;
+ background-color: hsla(0,0%,0%,.05);
+ border: 1px solid ThreeDLightShadow;
+}
+
+#urlbar-zoom-button[animate="true"] {
+ animation-name: urlbar-zoom-reset-pulse;
+ animation-duration: 250ms;
+}
+
+#urlbar-zoom-button:hover {
+ background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button:hover:active {
+ background-color: hsla(0,0%,0%,.15);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+ display: none;
+}
+
+#urlbar-search-footer {
+ border-top: 1px solid var(--panel-separator-color);
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#urlbar-search-settings {
+ -moz-appearance: none;
+ -moz-user-focus: ignore;
+ color: GrayText;
+ margin: 0;
+ padding: 8px 20px;
+}
+
+#urlbar-search-settings:hover {
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#urlbar-search-settings:hover:active {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+#urlbar-search-splitter {
+ min-width: 8px;
+ width: 8px;
+ background-image: none;
+ margin: 0 -4px;
+ position: relative;
+ height: 22px;
+}
+
+#search-container {
+ min-width: calc(54px + 11ch);
+}
+
+#wrapper-urlbar-container[place="palette"] {
+ max-width: 20em;
+}
+
+.urlbar-display {
+ margin-top: 0;
+ margin-bottom: 0;
+ color: GrayText;
+}
+
+%include ../shared/urlbarSearchSuggestionsNotification.inc.css
+
+/* ----- AUTOCOMPLETE ----- */
+
+%include ../shared/autocomplete.inc.css
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
+ border-top: 1px solid #C7C7C7;
+}
+
+#treecolAutoCompleteImage {
+ max-width: 36px;
+}
+
+.autocomplete-richlistbox {
+ padding: 4px;
+}
+
+.autocomplete-richlistitem {
+ height: 30px;
+ min-height: 30px;
+ font: message-box;
+ border-radius: 2px;
+ border: 1px solid transparent;
+}
+
+.autocomplete-richlistitem[selected] {
+ background-color: hsl(210, 80%, 52%);
+}
+
+.ac-title {
+ font-size: 14px;
+ color: hsl(0, 0%, 0%);
+}
+
+.ac-tags {
+ font-size: 12px;
+}
+
+html|span.ac-tag {
+ background-color: hsl(216, 0%, 88%);
+ color: hsl(0, 0%, 0%);
+ border-radius: 2px;
+ border: 1px solid transparent;
+ padding: 0 1px;
+}
+
+.ac-separator,
+.ac-url,
+.ac-action {
+ font-size: 12px;
+}
+
+.ac-separator {
+ color: hsl(0, 0%, 50%);
+}
+
+.ac-url {
+ color: hsl(210, 77%, 47%);
+}
+
+.ac-action {
+ color: hsl(178, 100%, 28%);
+}
+
+.ac-title[selected],
+.ac-separator[selected],
+.ac-url[selected],
+.ac-action[selected] {
+ color: hsl(0, 0%, 100%);
+}
+
+.ac-tags-text[selected] > html|span.ac-tag {
+ background-color: hsl(0, 0%, 100%);
+ color: hsl(210, 80%, 40%);
+}
+
+html|span.ac-emphasize-text-title,
+html|span.ac-emphasize-text-tag,
+html|span.ac-emphasize-text-url {
+ font-weight: 600;
+}
+
+.ac-type-icon[type=bookmark] {
+ list-style-image: url("chrome://browser/skin/urlbar-star.svg#star");
+}
+
+.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/autocomplete-star.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+.autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+.ac-type-icon[type=bookmark][selected][current] {
+ list-style-image: url("chrome://browser/skin/urlbar-star.svg#star-inverted");
+}
+
+.ac-type-icon[type=keyword],
+.ac-site-icon[type=searchengine],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
+}
+
+.ac-type-icon[type=keyword][selected],
+.ac-site-icon[type=searchengine][selected],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
+}
+
+.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+.ac-type-icon[type=switchtab],
+.ac-type-icon[type=remotetab] {
+ list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
+}
+
+.ac-type-icon[type=switchtab][selected],
+.ac-type-icon[type=remotetab][selected] {
+ list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
+ color: GrayText;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
+.autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment)
+{
+ color: GrayText;
+ font-size: smaller;
+}
+
+.autocomplete-treebody::-moz-tree-cell(suggesthint) {
+ border-top: 1px solid GrayText;
+}
+
+
+/* ----- COMBINED GO/RELOAD/STOP BUTTON IN LOCATION BAR ----- */
+
+#urlbar-go-button,
+#urlbar-reload-button,
+#urlbar-stop-button {
+ margin: 0;
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+ padding: 0 9px;
+ margin-inline-start: 5px;
+ border-inline-start: 1px solid var(--urlbar-separator-color);
+ border-image: linear-gradient(transparent 15%,
+ var(--urlbar-separator-color) 15%,
+ var(--urlbar-separator-color) 85%,
+ transparent 85%);
+ border-image-slice: 1;
+}
+
+#urlbar-go-button {
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+}
+
+#urlbar-go-button:hover:active {
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-reload-button {
+ -moz-image-region: rect(0, 14px, 14px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover:active {
+ -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+#urlbar-reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-stop-button {
+ -moz-image-region: rect(0, 28px, 14px, 14px);
+}
+
+#urlbar-stop-button:hover:active {
+ -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+
+@media (min-resolution: 2dppx) {
+ #urlbar-go-button,
+ #urlbar-reload-button,
+ #urlbar-stop-button {
+ list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
+ }
+
+ #urlbar-go-button > .toolbarbutton-icon,
+ #urlbar-reload-button > .toolbarbutton-icon,
+ #urlbar-stop-button > .toolbarbutton-icon {
+ width: 14px;
+ }
+
+ #urlbar-go-button {
+ -moz-image-region: rect(0, 84px, 28px, 56px);
+ }
+
+ #urlbar-go-button:hover:active {
+ -moz-image-region: rect(28px, 84px, 56px, 56px);
+ }
+
+ #urlbar-reload-button {
+ -moz-image-region: rect(0, 28px, 28px, 0);
+ }
+
+ #urlbar-reload-button:not([disabled]):hover:active {
+ -moz-image-region: rect(28px, 28px, 56px, 0);
+ }
+
+ #urlbar-stop-button {
+ -moz-image-region: rect(0, 56px, 28px, 28px);
+ }
+
+ #urlbar-stop-button:hover:active {
+ -moz-image-region: rect(28px, 56px, 56px, 28px);
+ }
+}
+
+#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ width: 18px;
+ height: 18px;
+}
+
+#bookmarks-menu-button[cui-areatype="toolbar"].bookmark-item > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ width: 16px;
+ height: 16px;
+}
+
+#BMB_bookmarksPopup[side="top"],
+#BMB_bookmarksPopup[side="bottom"] {
+ margin-left: -26px;
+ margin-right: -26px;
+}
+
+#BMB_bookmarksPopup[side="left"],
+#BMB_bookmarksPopup[side="right"] {
+ margin-top: -26px;
+ margin-bottom: -26px;
+}
+
+/* POPUP BLOCKER BUTTON */
+#page-report-button {
+ list-style-image: url("chrome://browser/skin/urlbar-popup-blocked.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#page-report-button:hover:active,
+#page-report-button[open="true"] {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+@media (min-resolution: 2dppx) {
+ #page-report-button {
+ list-style-image: url("chrome://browser/skin/urlbar-popup-blocked@2x.png");
+ -moz-image-region: rect(0, 32px, 32px, 0);
+ }
+
+ #page-report-button:hover:active,
+ #page-report-button[open="true"] {
+ -moz-image-region: rect(0, 64px, 32px, 32px);
+ }
+}
+
+/* Reader mode button */
+
+#reader-mode-button {
+ list-style-image: url("chrome://browser/skin/readerMode.svg");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#reader-mode-button:hover:active {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#reader-mode-button[readeractive] {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+/* social share panel */
+.social-share-frame {
+ border-top: 1px solid #f8f8f8;
+ min-width: 756px;
+ height: 150px;
+ /* we resize our panels dynamically, make it look nice */
+}
+
+#share-container {
+ min-width: 756px;
+ background-repeat: no-repeat;
+ background-position: center center;
+}
+#share-container[loading] {
+ background-image: url(chrome://browser/skin/tabbrowser/pendingpaint.png);
+}
+#share-container > browser {
+ transition: opacity 150ms ease-in-out;
+ opacity: 1;
+}
+#share-container[loading] > browser {
+ opacity: 0;
+}
+
+#manage-share-providers {
+ -moz-image-region: rect(18px, 468px, 36px, 450px);
+}
+
+.social-share-toolbar {
+ border-bottom: 1px solid #dedede;
+ padding: 2px;
+}
+
+#social-share-provider-buttons {
+ padding: 0;
+ margin: 0;
+}
+
+.share-provider-button {
+ padding: 5px;
+ margin: 2px;
+}
+
+.share-provider-button > .toolbarbutton-text {
+ display: none;
+}
+
+.share-provider-button > .toolbarbutton-icon {
+ width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+}
+
+/* BOOKMARKING PANEL */
+#editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48.png");
+ width: 48px;
+ height: 48px;
+}
+
+#editBookmarkPanelStarIcon[unstarred] {
+ list-style-image: url("chrome://browser/skin/places/unstarred48.png");
+}
+
+@media (min-resolution: 2dppx) {
+ #editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48@2x.png");
+ -moz-image-region: rect(0px 96px 96px 0px);
+ }
+}
+
+#editBookmarkPanelTitle {
+ font-size: 130%;
+ font-weight: bold;
+}
+
+#editBMPanel_rows > row {
+ margin-bottom: 8px;
+}
+
+#editBMPanel_rows > row:last-of-type {
+ margin-bottom: 0;
+}
+
+/**** Input elements ****/
+
+#editBMPanel_rows > row > textbox,
+#editBMPanel_rows > row > hbox > textbox {
+ -moz-appearance: none;
+ background: linear-gradient(#fafafa, #fff);
+ background-clip: padding-box;
+ border-radius: 3px;
+ border: 1px solid rgba(0,0,0,.3) !important;
+ box-shadow: inset 0 1px 1px 1px rgba(0,0,0,.05),
+ 0 1px rgba(255,255,255,.3);
+ margin: 0;
+ padding: 3px 6px;
+}
+
+#editBMPanel_rows > row > textbox[focused="true"],
+#editBMPanel_rows > row > hbox > textbox[focused="true"] {
+ border-color: -moz-mac-focusring !important;
+ box-shadow: @focusRingShadow@;
+}
+
+/**** HUD style buttons ****/
+
+.editBookmarkPanelHeaderButton,
+.editBookmarkPanelBottomButton {
+ @hudButton@
+ margin: 0;
+ min-width: 82px;
+ min-height: 22px;
+}
+
+.editBookmarkPanelHeaderButton:hover:active,
+.editBookmarkPanelBottomButton:hover:active {
+ @hudButtonPressed@
+}
+
+.editBookmarkPanelHeaderButton:-moz-focusring,
+.editBookmarkPanelBottomButton:-moz-focusring {
+ @hudButtonFocused@
+}
+
+.editBookmarkPanelBottomButton[default="true"] {
+ background-color: #666;
+}
+
+#editBookmarkPanelHeader {
+ margin-bottom: 6px;
+}
+
+.editBookmarkPanelBottomButton:last-child {
+ margin-inline-start: 8px;
+}
+
+/* The following elements come from editBookmarkOverlay.xul. Styling that's
+ specific to the editBookmarkPanel should be in browser.css. Styling that
+ should be shared by all editBookmarkOverlay.xul consumers should be in
+ editBookmarkOverlay.css. */
+
+#editBMPanel_newFolderBox {
+ background: linear-gradient(#fff, #f2f2f2);
+ background-origin: padding-box;
+ background-clip: padding-box;
+ border-radius: 0 0 3px 3px;
+ border: 1px solid #a5a5a5;
+ box-shadow: inset 0 1px rgba(255,255,255,.8),
+ inset 0 0 1px rgba(255,255, 255,.25),
+ 0 1px rgba(255,255,255,.3);
+ margin: 0;
+ padding: 0;
+ height: 20px;
+}
+
+#editBMPanel_newFolderButton {
+ -moz-appearance: none;
+ border: 0 solid #a5a5a5;
+ border-inline-end-width: 1px;
+ padding: 0 9px;
+ margin: 0;
+ min-width: 21px;
+ min-height: 20px;
+ height: 20px;
+ color: #fff;
+ list-style-image: url("chrome://browser/skin/panel-plus-sign.png");
+ position: relative;
+}
+
+#editBMPanel_newFolderButton:hover:active {
+ background: linear-gradient(rgba(40,40,40,.9), rgba(70,70,70,.9));
+ box-shadow: inset 0 0 3px rgba(0,0,0,.2), inset 0 1px 7px rgba(0,0,0,.4);
+}
+
+#editBMPanel_newFolderButton:-moz-focusring {
+ @hudButtonFocused@
+}
+
+#editBMPanel_newFolderButton .button-text {
+ display: none;
+}
+
+#editBMPanel_folderMenuList {
+ @hudButton@
+ background-clip: padding-box;
+ margin: 0;
+ min-height: 22px;
+ padding-top: 2px;
+ padding-bottom: 1px;
+ padding-inline-start: 8px;
+ padding-inline-end: 4px;
+}
+
+#editBMPanel_folderMenuList:-moz-focusring {
+ @hudButtonFocused@
+}
+
+#editBMPanel_folderMenuList[open="true"],
+#editBMPanel_folderMenuList:hover:active {
+ @hudButtonPressed@
+}
+
+#editBMPanel_folderMenuList > .menulist-dropmarker {
+ -moz-appearance: none;
+ display: -moz-box;
+ background-color: transparent;
+ border: 0;
+ margin: 0;
+ padding: 0;
+ padding-inline-end: 4px;
+ width: 7px;
+}
+
+#editBMPanel_folderMenuList > .menulist-dropmarker > .dropmarker-icon {
+ list-style-image: url("chrome://global/skin/icons/panel-dropmarker.png");
+}
+
+/**** folder tree and tag selector ****/
+
+#editBMPanel_folderTree,
+#editBMPanel_tagsSelector {
+ -moz-appearance: none;
+ background: linear-gradient(#fafafa, #fff);
+ background-clip: padding-box;
+ border-radius: 3px;
+ border: 1px solid rgba(0,0,0,.3);
+ box-shadow: inset 0 1px 1px 1px rgba(0,0,0,.05),
+ 0 1px rgba(255,255,255,.3);
+ margin: 0;
+}
+
+#editBMPanel_folderTree:-moz-focusring,
+#editBMPanel_tagsSelector:-moz-focusring {
+ border-color: -moz-mac-focusring;
+ box-shadow: @focusRingShadow@;
+}
+
+#editBMPanel_folderTree {
+ border-bottom: none;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ /* Implements editBookmarkPanel resizing on folderTree un-collapse. */
+ margin: 0 !important;
+ min-width: 27em;
+ position: relative;
+}
+
+/**** expanders ****/
+
+#editBookmarkPanel .expander-up,
+#editBookmarkPanel .expander-down {
+ @hudButton@
+ margin: 0;
+ margin-inline-start: 4px;
+ min-width: 27px;
+ min-height: 22px;
+}
+
+#editBookmarkPanel .expander-up:-moz-focusring,
+#editBookmarkPanel .expander-down:-moz-focusring {
+ @hudButtonFocused@
+}
+
+#editBookmarkPanel .expander-up:hover:active,
+#editBookmarkPanel .expander-down:hover:active {
+ @hudButtonPressed@
+}
+
+#editBookmarkPanel .expander-up {
+ list-style-image: url("chrome://browser/skin/panel-expander-open.png");
+}
+
+#editBookmarkPanel .expander-down {
+ list-style-image: url("chrome://browser/skin/panel-expander-closed.png");
+}
+
+#editBookmarkPanel .expander-up > .button-box > .button-icon,
+#editBookmarkPanel .expander-down > .button-box > .button-icon {
+ margin: 1px 0 0;
+}
+
+#editBookmarkPanel .expander-up > .button-box > .button-text,
+#editBookmarkPanel .expander-down > .button-box > .button-text {
+ display: none;
+}
+
+@media (min-resolution: 2dppx) {
+ #editBookmarkPanel .expander-up {
+ list-style-image: url("chrome://browser/skin/panel-expander-open@2x.png");
+ }
+
+ #editBookmarkPanel .expander-down {
+ list-style-image: url("chrome://browser/skin/panel-expander-closed@2x.png");
+ }
+
+ #editBookmarkPanel .expander-up > .button-box > .button-icon,
+ #editBookmarkPanel .expander-down > .button-box > .button-icon {
+ width: 9px;
+ }
+}
+
+#editBMPanel_tagsField > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::placeholder {
+ opacity: 1.0;
+ color: #bbb;
+}
+
+.editBMPanel_rowLabel {
+ text-align: end;
+}
+
+/* History Swipe Animation */
+
+#historySwipeAnimationCurrentPage,
+#historySwipeAnimationNextPage {
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.6);
+}
+
+#historySwipeAnimationContainer {
+ background: url("chrome://browser/skin/subtle-pattern.png") #B3B9C1;
+}
+
+/* ----- SIDEBAR ELEMENTS ----- */
+
+#sidebar-box {
+ -moz-appearance: -moz-mac-source-list;
+ box-shadow: inset -2px 0 0 hsla(0,0%,100%,.2);
+}
+
+sidebarheader {
+ padding: 2px 2px 0;
+ text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
+}
+
+.sidebar-splitter {
+ border-inline-start: none;
+ border-inline-end: 1px solid #b4b4b4;
+ min-width: 1px;
+ width: 3px;
+ background-image: none !important;
+ background-color: transparent;
+ margin-inline-start: -3px;
+ position: relative;
+}
+
+#appcontent ~ .sidebar-splitter {
+ border-inline-start: 1px solid #ccc;
+ border-inline-end: none;
+ margin-inline-start: 0;
+ margin-inline-end: -3px;
+}
+
+.sidebar-title,
+#sidebar-title {
+ color: #596c80;
+ font-weight: bold;
+}
+
+.sidebar-title:-moz-window-inactive,
+#sidebar-title:-moz-window-inactive {
+ color: #868b92;
+}
+
+.sidebar-throbber[loading="true"],
+#sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .sidebar-throbber[loading="true"],
+ #sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ width: 16px;
+ }
+}
+
+@media (-moz-mac-yosemite-theme) {
+ #sidebar-box {
+ box-shadow: none;
+ }
+
+ sidebarheader {
+ text-shadow: none;
+ font-weight: 500;
+ }
+
+ .sidebar-title,
+ #sidebar-title {
+ color: #636363;
+ font-weight: 500;
+ }
+}
+
+
+/* ----- CONTENT ----- */
+
+.browserContainer > findbar {
+ background: @scopeBarBackground@;
+ border-top: @scopeBarSeparatorBorder@;
+ color: -moz-DialogText;
+ text-shadow: none;
+}
+
+toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+.bookmark-item {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+
+ image.bookmark-item {
+ width: 16px;
+ }
+}
+
+.openintabs-menuitem {
+ list-style-image: none;
+}
+
+/* ::::: tabbrowser ::::: */
+
+.tabbrowser-tabbox {
+ margin: 0;
+}
+
+%include ../shared/tabs.inc.css
+
+.tab-label {
+ margin-top: 1px;
+ margin-bottom: 0;
+ text-align: center;
+}
+
+@media (-moz-mac-yosemite-theme) {
+ /* image preloading hack from shared/tabs.inc.css */
+ #tabbrowser-tabs::before {
+ background-image:
+ url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg),
+ url(chrome://browser/skin/yosemite/tab-selected-start-inactive.svg),
+ url(chrome://browser/skin/yosemite/tab-stroke-start-inactive.png),
+ url(chrome://browser/skin/yosemite/tab-active-middle-inactive.png),
+ url(chrome://browser/skin/yosemite/tab-stroke-end-inactive.png),
+ url(chrome://browser/skin/tabbrowser/tab-selected-end.svg),
+ url(chrome://browser/skin/tabbrowser/tab-selected-start.svg),
+ url(chrome://browser/skin/tabbrowser/tab-stroke-end.png),
+ url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
+ url(chrome://browser/skin/tabbrowser/tab-stroke-start.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-end.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-start.png);
+ }
+
+ .tab-background-middle[selected=true]:-moz-window-inactive {
+ background-image: url(chrome://browser/skin/yosemite/tab-active-middle-inactive.png),
+ @fgTabTextureYosemiteInactive@,
+ none;
+ }
+
+ .tab-background-start[selected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
+ .tab-background-end[selected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/yosemite/tab-stroke-start-inactive.png);
+ }
+
+ .tab-background-end[selected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
+ .tab-background-start[selected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/yosemite/tab-stroke-end-inactive.png);
+ }
+
+ .tab-background-start[selected=true]:-moz-window-inactive:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
+ .tab-background-end[selected=true]:-moz-window-inactive:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
+ background-image: url(chrome://browser/skin/yosemite/tab-selected-start-inactive.svg);
+ background-size: 100% 100%;
+ }
+
+ .tab-background-end[selected=true]:-moz-window-inactive:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
+ .tab-background-start[selected=true]:-moz-window-inactive:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
+ background-image: url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg);
+ background-size: 100% 100%;
+ }
+
+ @media (min-resolution: 2dppx) {
+ /* image preloading hack from shared/tabs.inc.css */
+ #tabbrowser-tabs::before {
+ background-image:
+ url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg),
+ url(chrome://browser/skin/yosemite/tab-selected-start-inactive.svg),
+ url(chrome://browser/skin/yosemite/tab-stroke-start-inactive@2x.png),
+ url(chrome://browser/skin/yosemite/tab-active-middle-inactive@2x.png),
+ url(chrome://browser/skin/yosemite/tab-stroke-end-inactive@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-selected-end.svg),
+ url(chrome://browser/skin/tabbrowser/tab-selected-start.svg),
+ url(chrome://browser/skin/tabbrowser/tab-stroke-end@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-stroke-start@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png);
+ }
+
+ .tab-background-middle[selected=true]:-moz-window-inactive {
+ background-image: url(chrome://browser/skin/yosemite/tab-active-middle-inactive@2x.png),
+ @fgTabTextureYosemiteInactive@,
+ none;
+ }
+
+ .tab-background-start[selected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
+ .tab-background-end[selected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/yosemite/tab-stroke-start-inactive@2x.png);
+ }
+
+ .tab-background-end[selected=true]:-moz-window-inactive:-moz-locale-dir(ltr)::after,
+ .tab-background-start[selected=true]:-moz-window-inactive:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/yosemite/tab-stroke-end-inactive@2x.png);
+ }
+ }
+}
+
+.tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([selected="true"]) {
+ opacity: .9;
+}
+
+/*
+ * Force the overlay to create a new stacking context so it always appears on
+ * top of the icon.
+ */
+.tab-icon-overlay {
+ opacity: 0.9999;
+}
+
+.tab-label:not([selected="true"]) {
+ opacity: .7;
+}
+
+.tabbrowser-tab,
+.tabs-newtab-button {
+ font: message-box;
+ border: none;
+}
+
+.tabbrowser-tab[visuallyselected=true]:not(:-moz-lwtheme) {
+ /* overriding tabbox.css */
+ color: inherit;
+}
+
+.tabbrowser-tab[visuallyselected=true] {
+ /* overriding tabbox.css */
+ text-shadow: inherit;
+}
+
+.tabs-newtab-button > .toolbarbutton-icon {
+ -moz-box-align: center;
+ border: solid transparent;
+ border-width: 0 11px;
+}
+
+.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label:not([pinned]),
+.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-icon-image[pinned],
+.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-throbber[pinned] {
+ box-shadow: @focusRingShadow@;
+}
+
+#TabsToolbar {
+ -moz-appearance: none;
+ /* overlap the nav-bar's top border */
+ margin-bottom: calc(-1 * var(--tab-toolbar-navbar-overlap));
+}
+
+#main-window:not([customizing]) #navigator-toolbox[inFullscreen] > #TabsToolbar:not(:-moz-lwtheme),
+#main-window:not(:-moz-any([customizing],[tabsintitlebar])) #navigator-toolbox > #TabsToolbar:not(:-moz-lwtheme) {
+ -moz-appearance: toolbar;
+}
+
+#TabsToolbar:not(:-moz-lwtheme) {
+ color: var(--tabs-toolbar-color);
+ text-shadow: @loweredShadow@;
+}
+
+#navigator-toolbox[inFullscreen] > #TabsToolbar {
+ padding-top: var(--space-above-tabbar);
+}
+
+#tabbrowser-tabs {
+ -moz-box-align: stretch;
+}
+
+.tabs-newtab-button > .toolbarbutton-icon {
+ padding: 6px 0 4px;
+}
+
+/**
+ * Tab Drag and Drop
+ */
+
+.tab-drop-indicator {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
+ margin-top: -2px;
+ z-index: 3;
+}
+
+@media (min-resolution: 2dppx) {
+ .tab-drop-indicator {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator@2x.png);
+ width: 12px;
+ }
+}
+
+/**
+ * In-tab close button
+ */
+
+.tab-close-button > .toolbarbutton-icon {
+ margin-inline-end: 0px !important;
+}
+
+.tab-close-button {
+ -moz-appearance: none;
+ border: none !important;
+ background: none;
+ cursor: default;
+}
+
+#TabsToolbar[brighttext] .tab-close-button.close-icon:not([selected=true]):not(:hover) {
+ -moz-image-region: rect(0, 64px, 16px, 48px);
+}
+
+@media (min-resolution: 2dppx) {
+ #TabsToolbar[brighttext] .tab-close-button.close-icon:not([selected=true]):not(:hover) {
+ -moz-image-region: rect(0, 128px, 32px, 96px);
+ }
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-image-region: rect(0, 13px, 20px, 0);
+ margin: 0 0 var(--tab-toolbar-navbar-overlap);
+ padding: 0 4px;
+ border: none;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up {
+ border-inline-end: 2px solid transparent;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ border-inline-start: 2px solid transparent;
+ transition: 1s background-color ease-out;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
+ background-color: Highlight;
+ transition: none;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(ltr),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr),
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-right.png");
+}
+
+#TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(ltr),
+#TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png");
+}
+
+#TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr),
+#TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-right-inverted.png");
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-down:hover {
+ -moz-image-region: rect(0, 26px, 20px, 13px);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:hover:active,
+.tabbrowser-arrowscrollbox > .scrollbutton-down:hover:active {
+ -moz-image-region: rect(0, 39px, 20px, 26px);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled] > .toolbarbutton-icon,
+.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] > .toolbarbutton-icon {
+ -moz-image-region: rect(0, 13px, 20px, 0) !important;
+ opacity: .5;
+}
+
+@media (min-resolution: 2dppx) {
+ .tabbrowser-arrowscrollbox > .scrollbutton-up,
+ .tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-image-region: rect(0, 26px, 40px, 0);
+ }
+
+ .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(ltr),
+ .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left@2x.png");
+ }
+
+ .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr),
+ .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-right@2x.png");
+ }
+
+ #TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(ltr),
+ #TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left-inverted@2x.png");
+ }
+
+ #TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr),
+ #TabsToolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-right-inverted@2x.png");
+ }
+
+ .tabbrowser-arrowscrollbox > .scrollbutton-up:hover,
+ .tabbrowser-arrowscrollbox > .scrollbutton-down:hover {
+ -moz-image-region: rect(0, 52px, 40px, 26px);
+ }
+
+ .tabbrowser-arrowscrollbox > .scrollbutton-up:hover:active,
+ .tabbrowser-arrowscrollbox > .scrollbutton-down:hover:active {
+ -moz-image-region: rect(0, 78px, 40px, 52px);
+ }
+
+ .tabbrowser-arrowscrollbox > .scrollbutton-up[disabled] > .toolbarbutton-icon,
+ .tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] > .toolbarbutton-icon {
+ -moz-image-region: rect(0, 26px, 40px, 0) !important;
+ }
+
+ .tabbrowser-arrowscrollbox > .scrollbutton-up > .toolbarbutton-icon,
+ .tabbrowser-arrowscrollbox > .scrollbutton-down > .toolbarbutton-icon {
+ width: 13px;
+ }
+}
+
+/**
+ * Tabstrip & add-on bar toolbar buttons
+ */
+
+#TabsToolbar .toolbarbutton-1,
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ -moz-appearance: none;
+ margin: 0;
+ /* !important flags needed because of bug 561154: */
+ /* Bug 990390: -moz-any is no longer used in the selector so the !important aren't necessary for that anymore. */
+ padding: 0 !important;
+ border: none !important;
+ border-radius: 0 !important;
+ background: none !important;
+ box-shadow: none !important;
+}
+
+#TabsToolbar .toolbarbutton-1:not([type="menu-button"]),
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ padding: 0 1px;
+}
+
+#TabsToolbar .toolbarbutton-1 {
+ margin-bottom: var(--tab-toolbar-navbar-overlap);
+}
+
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover,
+#TabsToolbar .toolbarbutton-1:not([type="menu-button"]):not([disabled=true]):not([open]):hover,
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):hover,
+#TabsToolbar .toolbarbutton-1:not([disabled=true]):not([buttonover]):hover > .toolbarbutton-menubutton-dropmarker {
+ background-image: linear-gradient(transparent, rgba(0,0,0,.15)) !important;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover:active,
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover:active,
+#TabsToolbar .toolbarbutton-1:not([type="menu-button"]):not([disabled=true]):hover:active,
+#TabsToolbar .toolbarbutton-1[type="menu"][open],
+#TabsToolbar .toolbarbutton-1[type="panel"][open],
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):hover:active,
+#TabsToolbar .toolbarbutton-1[open]:not([disabled=true]):hover > .toolbarbutton-menubutton-dropmarker {
+ background-image: linear-gradient(transparent, rgba(0,0,0,.3)) !important;
+}
+
+.tabs-newtab-button,
+#TabsToolbar > #new-tab-button,
+#TabsToolbar > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab.png);
+ -moz-image-region: rect(0, 18px, 20px, 0);
+}
+
+#TabsToolbar[brighttext] .tabs-newtab-button,
+#TabsToolbar[brighttext] > #new-tab-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.png);
+}
+
+.tabs-newtab-button:hover,
+#TabsToolbar > #new-tab-button:hover {
+ -moz-image-region: rect(0, 36px, 20px, 18px);
+}
+
+.tabs-newtab-button:hover:active,
+#TabsToolbar > #new-tab-button:hover:active {
+ -moz-image-region: rect(0, 54px, 20px, 36px);
+}
+
+@media (min-resolution: 2dppx) {
+ .tabs-newtab-button,
+ #TabsToolbar > #new-tab-button,
+ #TabsToolbar > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab@2x.png);
+ -moz-image-region: rect(0, 36px, 40px, 0);
+ }
+
+ #TabsToolbar[brighttext] .tabs-newtab-button,
+ #TabsToolbar[brighttext] > #new-tab-button,
+ #TabsToolbar[brighttext] > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted@2x.png);
+ }
+
+ .tabs-newtab-button:hover,
+ #TabsToolbar > #new-tab-button:hover {
+ -moz-image-region: rect(0, 72px, 40px, 36px);
+ }
+
+ .tabs-newtab-button:hover:active,
+ #TabsToolbar > #new-tab-button:hover:active {
+ -moz-image-region: rect(0, 108px, 40px, 72px);
+ }
+
+ .tabs-newtab-button > .toolbarbutton-icon {
+ width: 40px;
+ }
+
+ #TabsToolbar > #new-tab-button > .toolbarbutton-icon,
+ #TabsToolbar > toolbarpaletteitem > #new-tab-button > .toolbarbutton-icon {
+ width: 18px;
+ }
+}
+
+#alltabs-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon.png);
+ -moz-image-region: rect(0, 17px, 20px, 0);
+}
+
+#TabsToolbar[brighttext] #alltabs-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon-inverted.png);
+}
+
+#alltabs-button:not([disabled="true"]):hover {
+ -moz-image-region: rect(0, 34px, 20px, 17px);
+}
+
+#alltabs-button[open="true"]:not([disabled="true"]),
+#alltabs-button:not([disabled="true"]):hover:active {
+ -moz-image-region: rect(0, 51px, 20px, 34px);
+}
+
+@media (min-resolution: 2dppx) {
+ #alltabs-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon@2x.png);
+ -moz-image-region: rect(0, 34px, 40px, 0);
+ }
+
+ #TabsToolbar[brighttext] #alltabs-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png);
+ }
+
+ #alltabs-button:not([disabled="true"]):hover {
+ -moz-image-region: rect(0, 68px, 40px, 34px);
+ }
+
+ #alltabs-button[open="true"]:not([disabled="true"]),
+ #alltabs-button:not([disabled="true"]):hover:active {
+ -moz-image-region: rect(0, 102px, 40px, 68px);
+ }
+
+ #alltabs-button > .toolbarbutton-icon {
+ width: 17px;
+ }
+}
+
+#alltabs-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+#alltabs-button > .toolbarbutton-icon {
+ margin-inline-end: 2px;
+}
+
+/* All Tabs Menupopup */
+.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading.png") !important;
+}
+
+@media (min-resolution: 2dppx) {
+ .alltabs-item > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+
+ .alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png") !important;
+ }
+}
+
+/* Bookmarks toolbar */
+#PlacesToolbarDropIndicator {
+ list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
+}
+
+/* Bookmark drag and drop styles */
+
+.bookmark-item[dragover-into="true"] {
+ background: Highlight !important;
+ color: HighlightText !important;
+}
+
+/* rules for menupopup drop indicators */
+.menupopup-drop-indicator-bar {
+ position: relative;
+ /* these two margins must together compensate the indicator's height */
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.menupopup-drop-indicator {
+ list-style-image: none;
+ height: 2px;
+ margin-inline-end: -4em;
+ background-color: Highlight;
+}
+
+%include ../shared/notification-icons.inc.css
+
+.notification-anchor-icon:-moz-focusring {
+ box-shadow: 0 0 2px 1px -moz-mac-focusring inset,
+ 0 0 3px 2px -moz-mac-focusring;
+}
+
+/* Translation */
+
+%include ../shared/translation/infobar.inc.css
+
+notification[value="translation"] {
+ color: #484848;
+ background-color: #EFEFEF;
+ background-image: none;
+ border-top: none;
+ border-bottom: 1px solid #c4c4c4;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ min-height: 35px;
+}
+
+.translate-infobar-element {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+}
+
+button.translate-infobar-element {
+ background: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)) repeat scroll 0% 0% padding-box transparent;
+ color: #333333;
+ border: 1px solid;
+ border-color: rgba(23, 51, 78, 0.15) rgba(23, 51, 78, 0.17) rgba(23, 51, 78, 0.2);
+ box-shadow: 0px 0px 2px rgba(255, 255, 255, 0.5) inset, 0px 1px 0px rgba(255, 255, 255, 0.2);
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+ min-height: 22px;
+ min-width: 0;
+ padding: 0 0.8em !important;
+ margin-left: 0.25em;
+ margin-right: 0.25em;
+}
+
+button.translate-infobar-element .button-text {
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+}
+
+label.translate-infobar-element {
+ padding-top: 2px;
+}
+
+button.translate-infobar-element:hover {
+ background: #f0f0f0;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.1) inset, 0 0 0 1px hsla(0,0%,100%,.05) inset, 0 1px 0 hsla(210,54%,20%,.01), 0 0 4px hsla(206,100%,20%,.1);
+}
+
+button.translate-infobar-element:active {
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset, 0 0 1px hsla(211,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+button.translate-infobar-element[anonid="translate"] {
+ color: #ffffff;
+ background: linear-gradient(#4cb1ff, #1793e5);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset, 0 0 0 1px hsla(0,0%,100%,.1) inset, 0 1px 0 hsla(210,54%,20%,.03);
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ padding: 0 1.1em !important;;
+}
+
+button.translate-infobar-element[anonid="translate"]:hover {
+ background-image: linear-gradient(#66bdff, #0d9eff);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset, 0 0 0 1px hsla(0,0%,100%,.1) inset, 0 1px 0 hsla(210,54%,20%,.03), 0 0 4px hsla(206,100%,20%,.2);
+}
+
+button.translate-infobar-element.options-menu-button {
+ padding-inline-start: 0.5em !important;
+ padding-inline-end: 0em !important;
+}
+
+button.translate-infobar-element.options-menu-button > .button-box > .button-menu-dropmarker {
+ display: -moz-box;
+ list-style-image: url("chrome://global/skin/icons/glyph-dropdown.png");
+ padding: 0 !important;
+ margin: 0 !important;
+}
+
+@media (min-resolution: 2dppx) {
+ button.translate-infobar-element.options-menu-button > .button-box > .button-menu-dropmarker {
+ list-style-image: url("chrome://global/skin/icons/glyph-dropdown@2x.png");
+ }
+
+ button.translate-infobar-element.options-menu-button > .button-box > .button-menu-dropmarker > .dropmarker-icon {
+ width: 8px;
+ }
+}
+
+menulist.translate-infobar-element {
+ text-shadow: 0 1px 1px #FEFFFE;
+ border: 1px solid;
+ border-color: rgba(23, 51, 78, 0.15) rgba(23, 51, 78, 0.17) rgba(23, 51, 78, 0.2);
+ box-shadow: 0 1px 1px 0 #FFFFFF, inset 0 2px 2px 0 #FFFFFF;
+ background-color: #F1F1F1;
+ background-image: linear-gradient(#FFFFFF, rgba(255,255,255,0.1));
+ color: #333333;
+ padding: 0;
+ min-height: 22px !important;
+}
+
+menulist.translate-infobar-element > .menulist-label-box {
+ padding-top: 1px;
+ padding-inline-start: 0.3em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+menulist.translate-infobar-element:hover {
+ background: #f0f0f0;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.1) inset, 0 0 0 1px hsla(0,0%,100%,.05) inset, 0 1px 0 hsla(210,54%,20%,.01), 0 0 4px hsla(206,100%,20%,.1);
+}
+
+menulist.translate-infobar-element[open="true"] {
+ background-image: linear-gradient(rgba(255,255,255,0.1),
+ rgba(255,255,255,0.6));
+}
+
+menulist.translate-infobar-element > .menulist-dropmarker {
+ display: -moz-box;
+ list-style-image: url("chrome://global/skin/icons/glyph-dropdown.png");
+}
+
+@media (min-resolution: 2dppx) {
+ menulist.translate-infobar-element > .menulist-dropmarker {
+ list-style-image: url("chrome://global/skin/icons/glyph-dropdown@2x.png");
+ }
+
+ menulist.translate-infobar-element > .menulist-dropmarker > .dropmarker-icon {
+ width: 8px;
+ }
+}
+
+.popup-notification-body[popupid="addon-progress"],
+.popup-notification-body[popupid="addon-install-confirmation"] {
+ width: 28em;
+ max-width: 28em;
+}
+
+.addon-install-confirmation-name {
+ font-weight: bold;
+}
+
+/* Status panel */
+
+.statuspanel-label {
+ margin: 0;
+ padding: 2px 4px;
+ background: linear-gradient(#fff, #ddd);
+ border: 1px none #ccc;
+ border-top-style: solid;
+ color: #333;
+ text-shadow: none;
+}
+
+.statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
+.statuspanel-label:-moz-locale-dir(rtl)[mirror] {
+ border-right-style: solid;
+ border-top-right-radius: .3em;
+ margin-right: 1em;
+}
+
+.statuspanel-label:-moz-locale-dir(rtl):not([mirror]),
+.statuspanel-label:-moz-locale-dir(ltr)[mirror] {
+ border-left-style: solid;
+ border-top-left-radius: .3em;
+ margin-left: 1em;
+}
+
+%include ../shared/fullscreen/warning.inc.css
+%include ../shared/ctrlTab.inc.css
+%include ../../../devtools/client/themes/responsivedesign.inc.css
+%include ../../../devtools/client/themes/commandline.inc.css
+%include ../shared/plugin-doorhanger.inc.css
+
+%include downloads/indicator.css
+
+/* On mac, the popup notification contents are indented by default and so
+ the default closebutton margins from notification.css require adjustment */
+
+.click-to-play-plugins-notification-description-box > .popup-notification-closebutton {
+ margin-inline-end: -6px;
+ margin-top: -7px;
+}
+
+
+
+.gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
+ color: hsl(210,11%,16%);
+}
+
+/* Error counter */
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ color: #FDF3DE;
+ min-width: 16px;
+ text-shadow: none;
+ background-image: linear-gradient(#B4211B, #8A1915);
+ border-radius: 1px;
+}
+
+/* Share */
+%include ../shared/social/social.inc.css
+
+#social-share-panel {
+ min-height: 100px;
+ min-width: 300px;
+ transition: height .3s ease-in-out, width .3s ease-in-out;
+}
+
+#share-container,
+.social-share-frame {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: inherit;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: inherit;
+}
+
+#social-share-panel > .social-share-toolbar {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+#social-share-provider-buttons {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+/* Customization mode */
+
+%include ../shared/customizableui/customizeMode.inc.css
+
+#main-window[customizing] {
+ background-color: rgb(178,178,178);
+}
+
+#main-window[tabsintitlebar][customize-entered] > #titlebar,
+#main-window[privatebrowsingmode=temporary]:not([tabsintitlebar])[customize-entered] > #titlebar,
+#main-window[customize-entered] > #tab-view-deck {
+ background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
+ url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
+ linear-gradient(to bottom, rgb(233,233,233), rgb(178,178,178) 40px);
+ background-attachment: fixed;
+}
+
+#main-window[tabsintitlebar][customization-lwtheme] > #titlebar:-moz-lwtheme,
+#main-window[customization-lwtheme] > #tab-view-deck:-moz-lwtheme {
+ background-repeat: no-repeat;
+ background-position: right top;
+ background-attachment: fixed;
+ /* The image will get set from CustomizeMode.jsm */
+ background-image: none;
+ background-color: transparent;
+}
+
+#main-window[customization-lwtheme]:-moz-lwtheme {
+ background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
+ url("chrome://browser/skin/customizableui/background-noise-toolbar.png");
+ background-color: rgb(178,178,178);
+ background-repeat: repeat;
+ background-attachment: fixed;
+ background-position: left top;
+}
+
+#main-window[customize-entered] #browser-bottombox,
+#main-window[customize-entered] #navigator-toolbox > toolbar:not(#TabsToolbar),
+#main-window[customize-entered] #customization-container {
+ border: 3px solid hsla(0,0%,0%,.1);
+ border-top-width: 0;
+ background-clip: padding-box;
+ background-origin: padding-box;
+ -moz-border-right-colors: hsla(0,0%,0%,.05) hsla(0,0%,0%,.1) hsla(0,0%,0%,.2);
+ -moz-border-bottom-colors: hsla(0,0%,0%,.05) hsla(0,0%,0%,.1) hsla(0,0%,0%,.2);
+ -moz-border-left-colors: hsla(0,0%,0%,.05) hsla(0,0%,0%,.1) hsla(0,0%,0%,.2);
+}
+
+#main-window[customize-entered] #customization-container,
+#main-window[customize-entered] #navigator-toolbox > toolbar:not(#TabsToolbar) {
+ border-bottom-width: 0;
+}
+
+#main-window[customize-entered] #nav-bar {
+ border-top-left-radius: 2.5px;
+ border-top-right-radius: 2.5px;
+}
+
+/* Compensate for the border set above for this horizontal line. */
+#main-window[customize-entered] #navigator-toolbox::after {
+ margin-left: 3px;
+ margin-right: 3px;
+}
+
+#main-window[customize-entered] #TabsToolbar {
+ background-clip: padding-box;
+ border-right: 3px solid transparent;
+ border-left: 3px solid transparent;
+}
+
+@media (min-resolution: 2dppx) {
+ .customization-tipPanel-infoBox {
+ background-image: url(chrome://browser/skin/customizableui/info-icon-customizeTip@2x.png);
+ background-size: 25px 25px;
+ }
+
+ .customization-tipPanel-contentImage {
+ list-style-image: url(chrome://browser/skin/customizableui/customize-illustration@2x.png);
+ }
+
+ .customization-tipPanel-contentImage:-moz-locale-dir(rtl) {
+ list-style-image: url(chrome://browser/skin/customizableui/customize-illustration-rtl@2x.png);
+ }
+
+ #customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="left"],
+ #customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="right"] {
+ list-style-image: url("chrome://browser/skin/customizableui/panelarrow-customizeTip@2x.png");
+ }
+
+ .customization-lwtheme-menu-theme[defaulttheme] {
+ list-style-image: url(chrome://browser/skin/theme-switcher-icon@2x.png);
+ }
+}
+
+/* End customization mode */
+
+.private-browsing-indicator {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask.png");
+ background-repeat: no-repeat;
+ background-size: 100% auto;
+ width: 38px;
+ height: 28px;
+ /**
+ * The private browsing mask graphic has a 3px flare at the top. The distance
+ * we want between the mask and items on either side is 7px, so we use 4px,
+ * since the other 3px is accounted for by the empty space on either side.
+ */
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+#titlebar-secondary-buttonbox > .private-browsing-indicator {
+ position: relative;
+}
+
+#main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask-short.png");
+ height: 20px;
+}
+
+#main-window:not([privatebrowsingmode=temporary]) .private-browsing-indicator,
+#main-window[privatebrowsingmode=temporary][inFullscreen] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator,
+#main-window[privatebrowsingmode=temporary]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .private-browsing-indicator {
+ display: none;
+}
+
+@media (min-resolution: 2dppx) {
+ .private-browsing-indicator {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask@2x.png");
+ }
+ #main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask-short@2x.png");
+ }
+}
+
+#TabsToolbar > .private-browsing-indicator {
+ transform: translateY(calc(-1 * var(--space-above-tabbar)));
+ /* We offset by 38px for mask graphic, plus 4px to account for the
+ * margin-left, which sums to 42px.
+ */
+ margin-right: -42px;
+}
+
+#main-window[privatebrowsingmode=temporary] .titlebar-placeholder[type="fullscreen-button"],
+#main-window[privatebrowsingmode=temporary] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button {
+ margin-left: 0px;
+}
+
+#main-window[privatebrowsingmode=temporary][inFullscreen] .titlebar-placeholder[type="fullscreen-button"] {
+ /* Override display:none for .titlebar-placeholder in fullscreen so we can have consistent
+ position and padding for the private browsing indicator. */
+ display: -moz-box;
+}
+
+#TabsToolbar > .private-browsing-indicator:-moz-locale-dir(rtl) {
+ -moz-box-ordinal-group: 0;
+}
+
+%include ../shared/UITour.inc.css
+
+#UITourTooltipDescription {
+ font-size: 1.18rem;
+ line-height: 2rem;
+}
+
+#UITourTooltipClose {
+ margin-inline-end: -10px;
+ margin-top: -14px;
+}
+
+@media (min-resolution: 2dppx) {
+ #UITourTooltipClose > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+%include ../shared/contextmenu.inc.css
+
+#context-navigation > .menuitem-iconic {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+ overflow: hidden;
+}
+
+.cui-widget-panelview[id^=PanelUI-webext-] {
+ border-radius: 3.5px;
+}
+
+.webextension-popup-browser {
+ border-radius: inherit;
+}
diff --git a/browser/themes/osx/click-to-play-warning-stripes.png b/browser/themes/osx/click-to-play-warning-stripes.png
new file mode 100644
index 000000000..29f15f7b8
--- /dev/null
+++ b/browser/themes/osx/click-to-play-warning-stripes.png
Binary files differ
diff --git a/browser/themes/osx/communicator/communicator.css b/browser/themes/osx/communicator/communicator.css
new file mode 100644
index 000000000..0b57574fd
--- /dev/null
+++ b/browser/themes/osx/communicator/communicator.css
@@ -0,0 +1,6 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://global/skin/");
+
diff --git a/browser/themes/osx/communicator/jar.mn b/browser/themes/osx/communicator/jar.mn
new file mode 100644
index 000000000..dfd20c523
--- /dev/null
+++ b/browser/themes/osx/communicator/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+% skin communicator classic/1.0 %skin/classic/communicator/
+ skin/classic/communicator/communicator.css
diff --git a/browser/themes/osx/communicator/moz.build b/browser/themes/osx/communicator/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/themes/osx/communicator/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/themes/osx/controlcenter/panel.css b/browser/themes/osx/controlcenter/panel.css
new file mode 100644
index 000000000..b87fa2ac9
--- /dev/null
+++ b/browser/themes/osx/controlcenter/panel.css
@@ -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/. */
+
+%include ../shared.inc
+%include ../../shared/controlcenter/panel.inc.css
+
+#identity-popup {
+ margin-top: 1px;
+}
+
+.identity-popup-expander:-moz-focusring {
+ padding: 2px;
+}
+
+.identity-popup-expander:-moz-focusring > .button-box {
+ @hudButtonFocused@
+}
+
+.identity-popup-permission-remove-button:-moz-focusring {
+ box-shadow: @focusRingShadow@;
+}
+
+#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
+ border-bottom-right-radius: 3.5px;
+}
+
+#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews:-moz-locale-dir(rtl) {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 3.5px;
+}
+
+#tracking-action-block,
+#tracking-action-unblock,
+#tracking-action-unblock-private,
+#identity-popup-securityView-body > button {
+ @hudButton@
+ min-height: 30px;
+}
+
+#tracking-action-block:hover:active,
+#tracking-action-unblock:hover:active,
+#tracking-action-unblock-private:hover:active,
+#identity-popup-securityView-body > button:hover:active {
+ @hudButtonPressed@
+}
+
+#tracking-action-block:-moz-focusring,
+#tracking-action-unblock:-moz-focusring,
+#tracking-action-unblock-private:-moz-focusring,
+#identity-popup-securityView-body > button:-moz-focusring {
+ @hudButtonFocused@
+}
+
diff --git a/browser/themes/osx/customizableui/background-noise-toolbar.png b/browser/themes/osx/customizableui/background-noise-toolbar.png
new file mode 100644
index 000000000..d09ba9daf
--- /dev/null
+++ b/browser/themes/osx/customizableui/background-noise-toolbar.png
Binary files differ
diff --git a/browser/themes/osx/customizableui/customize-titleBar-toggle.png b/browser/themes/osx/customizableui/customize-titleBar-toggle.png
new file mode 100644
index 000000000..fb0edd256
--- /dev/null
+++ b/browser/themes/osx/customizableui/customize-titleBar-toggle.png
Binary files differ
diff --git a/browser/themes/osx/customizableui/customize-titleBar-toggle@2x.png b/browser/themes/osx/customizableui/customize-titleBar-toggle@2x.png
new file mode 100644
index 000000000..7200e74c2
--- /dev/null
+++ b/browser/themes/osx/customizableui/customize-titleBar-toggle@2x.png
Binary files differ
diff --git a/browser/themes/osx/customizableui/customizeMode-gridTexture.png b/browser/themes/osx/customizableui/customizeMode-gridTexture.png
new file mode 100644
index 000000000..a7c2775cf
--- /dev/null
+++ b/browser/themes/osx/customizableui/customizeMode-gridTexture.png
Binary files differ
diff --git a/browser/themes/osx/customizableui/customizeMode-separatorHorizontal.png b/browser/themes/osx/customizableui/customizeMode-separatorHorizontal.png
new file mode 100644
index 000000000..5e17cb4db
--- /dev/null
+++ b/browser/themes/osx/customizableui/customizeMode-separatorHorizontal.png
Binary files differ
diff --git a/browser/themes/osx/customizableui/customizeMode-separatorVertical.png b/browser/themes/osx/customizableui/customizeMode-separatorVertical.png
new file mode 100644
index 000000000..dc4caee81
--- /dev/null
+++ b/browser/themes/osx/customizableui/customizeMode-separatorVertical.png
Binary files differ
diff --git a/browser/themes/osx/customizableui/panelUI.css b/browser/themes/osx/customizableui/panelUI.css
new file mode 100644
index 000000000..09f23b398
--- /dev/null
+++ b/browser/themes/osx/customizableui/panelUI.css
@@ -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/. */
+
+%include ../../shared/customizableui/panelUI.inc.css
+
+.panel-subviews {
+ background-color: hsla(0,0%,100%,.97);
+}
+
+.panelUI-grid .toolbarbutton-1 {
+ margin-right: 0;
+ margin-left: 0;
+ margin-bottom: 0;
+}
+
+.subviewbutton > .toolbarbutton-text {
+ margin: 2px 0 !important; /* !important for overriding toolbarbutton.css */
+}
+
+.subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .bookmark-item) > .toolbarbutton-text {
+ margin: 2px 6px !important; /* !important for overriding toolbarbutton.css */
+}
+
+.restoreallitem > .toolbarbutton-icon {
+ display: none;
+}
+
+.subviewbutton {
+ padding-inline-start: 18px;
+}
+
+.subviewbutton[checked="true"] {
+ background-position: top 5px left 4px;
+}
+
+.subviewbutton[checked="true"]:-moz-locale-dir(rtl) {
+ background-position: top 5px right 4px;
+}
+
+.subviewbutton:not(:-moz-any([image],[targetURI],.cui-withicon, .bookmark-item)) > .menu-iconic-left {
+ display: none;
+}
+
+menu.subviewbutton,
+menuitem.subviewbutton:not(.panel-subview-footer) {
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+/* Override OSX-specific toolkit styles for the bookmarks panel */
+menu.subviewbutton > .menu-right {
+ margin-inline-end: 0;
+ -moz-appearance: none;
+}
+
+menu.subviewbutton > .menu-right > image {
+ /* We don't want the arrow to highlight when the .subviewbutton is hovered,
+ * so we set the -moz-appearance rule on the image
+ * (which doesn't inherit the _moz-menuactive attribute) instead.
+ */
+ -moz-appearance: menuarrow;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ margin-inline-start: 4px;
+}
+
+.PanelUI-subView menuseparator,
+.cui-widget-panelview menuseparator {
+ padding: 0 !important;
+}
+
+toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ padding: 3px 1px;
+}
+
+#PanelUI-remotetabs-tabslist > label[itemtype="client"] {
+ padding-inline-start: 6px;
+}
+
+.PanelUI-remotetabs-notabsforclient-label {
+ margin-left: 19px;
+ font-size: 13px;
+}
+
+#PanelUI-remotetabs-tabslist {
+ padding-bottom: 4px;
+}
diff --git a/browser/themes/osx/devedition.css b/browser/themes/osx/devedition.css
new file mode 100644
index 000000000..c7a2bdd71
--- /dev/null
+++ b/browser/themes/osx/devedition.css
@@ -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/.
+
+%include ../shared/devedition.inc.css
+
+:root {
+ --forwardbutton-width: 32px;
+}
+
+/* Use only 1px separator between nav toolbox and page content */
+#navigator-toolbox::after {
+ border-top-style: none;
+ margin-top: -1px;
+}
+
+/* Include extra space on left/right for dragging since there is no space above
+ the tabs */
+#main-window[tabsintitlebar] #TabsToolbar {
+ padding-left: 50px;
+ padding-right: 50px;
+ margin-bottom: 0; /* Don't overlap the inner highlight at the top of the nav-bar */
+}
+
+/* Get rid of 1px bright strip at the top of window */
+#main-window[tabsintitlebar] #titlebar-content {
+ background: var(--chrome-background-color);
+}
+
+/* Resize things so that the native titlebar is in line with the tabs */
+#main-window[tabsintitlebar] > #titlebar > #titlebar-content > #titlebar-buttonbox-container,
+#main-window[tabsintitlebar] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button {
+ margin-top: 6px;
+}
+
+/* Square back and forward buttons. Need !important on these because there
+ are a lot of more specific selectors sprinkled around elsewhere for changing
+ background / shadows for different states */
+#back-button,
+#forward-button {
+ height: 24px !important;
+ box-shadow: none !important;
+ border: 1px solid var(--chrome-nav-bar-controls-border-color) !important;
+ background: var(--chrome-nav-buttons-background) !important;
+}
+
+#forward-button {
+ border-inline-start: none !important;
+ /* browser.css and friends set up the width of the button to be 32px.
+ * They then set margin-left to -2px to ensure the button is not too wide
+ * compared to the back button, and set padding-left to center the icon
+ * correctly.
+ * In our theme, the back and forward buttons are the same width, with the
+ * back button being 32px with 1px border on both sides. To ensure the
+ * forward button's content box looks like it is the same size with width
+ * set to 32px and a 1px border on only 1 side, we overlap by 1px, so both
+ * buttons end up with a content box that looks like it's 30px.
+ */
+ margin-left: -1px;
+ padding-left: 1px;
+}
+
+#forward-button > .toolbarbutton-icon {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+#back-button:hover:not([disabled="true"]),
+#forward-button:hover:not([disabled="true"]) {
+ background: var(--chrome-nav-buttons-hover-background) !important;
+}
+
+#back-button {
+ border-radius: 3px 0 0 3px !important;
+ padding: 0 !important;
+ margin: 0 !important;
+}
+
+#back-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(18px, 54px, 36px, 36px);
+}
+
+/* Use smaller back button icon */
+@media (min-resolution: 2dppx) {
+ #back-button:hover:active:not([disabled="true"]) {
+ -moz-image-region: rect(36px, 108px, 72px, 72px);
+ }
+}
+
+/* Don't use the default background for tabs toolbar */
+#TabsToolbar {
+ -moz-appearance: none !important;
+}
+
+/* Prevent the hover styling from on the identity icon from overlapping the
+ urlbar border. */
+#identity-box {
+ margin-top: -1px !important;
+ margin-bottom: -1px !important;
+ padding-top: 3px !important;
+ padding-bottom: 3px !important;
+}
+
+:root[devtoolstheme="dark"] .findbar-closebutton:not(:hover),
+/* Tab styling - make sure to use an inverted icon for the selected tab
+ (brighttext only covers the unselected tabs) */
+.tab-close-button[selected=true]:not(:hover) {
+ -moz-image-region: rect(0, 64px, 16px, 48px);
+}
+@media (min-resolution: 2dppx) {
+ :root[devtoolstheme="dark"] .findbar-closebutton:not(:hover),
+ .tab-close-button[selected=true]:not(:hover) {
+ -moz-image-region: rect(0, 128px, 32px, 96px);
+ }
+}
+
+.ac-type-icon {
+ /* Left-align the type icon in awesomebar popup results with the icon in the
+ urlbar. */
+ margin-inline-start: 14px;
+}
diff --git a/browser/themes/osx/downloads/allDownloadsViewOverlay.css b/browser/themes/osx/downloads/allDownloadsViewOverlay.css
new file mode 100644
index 000000000..dbdced970
--- /dev/null
+++ b/browser/themes/osx/downloads/allDownloadsViewOverlay.css
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/downloads/allDownloadsViewOverlay.inc.css
+
+/*** List items ***/
+
+:root {
+ --downloads-item-height: 6em;
+}
+
+.downloadProgress > .progress-bar {
+ background-color: #3c9af8;
+}
+
+.downloadProgress[paused="true"] > .progress-bar {
+ background-color: #a6a6a6;
+}
diff --git a/browser/themes/osx/downloads/download-glow-menuPanel.png b/browser/themes/osx/downloads/download-glow-menuPanel.png
new file mode 100644
index 000000000..583dac86e
--- /dev/null
+++ b/browser/themes/osx/downloads/download-glow-menuPanel.png
Binary files differ
diff --git a/browser/themes/osx/downloads/download-glow-menuPanel@2x.png b/browser/themes/osx/downloads/download-glow-menuPanel@2x.png
new file mode 100644
index 000000000..9b48fbd12
--- /dev/null
+++ b/browser/themes/osx/downloads/download-glow-menuPanel@2x.png
Binary files differ
diff --git a/browser/themes/osx/downloads/download-notification-finish.png b/browser/themes/osx/downloads/download-notification-finish.png
new file mode 100644
index 000000000..139bf0b53
--- /dev/null
+++ b/browser/themes/osx/downloads/download-notification-finish.png
Binary files differ
diff --git a/browser/themes/osx/downloads/download-notification-finish@2x.png b/browser/themes/osx/downloads/download-notification-finish@2x.png
new file mode 100644
index 000000000..4a2512181
--- /dev/null
+++ b/browser/themes/osx/downloads/download-notification-finish@2x.png
Binary files differ
diff --git a/browser/themes/osx/downloads/download-notification-start.png b/browser/themes/osx/downloads/download-notification-start.png
new file mode 100644
index 000000000..bd548b183
--- /dev/null
+++ b/browser/themes/osx/downloads/download-notification-start.png
Binary files differ
diff --git a/browser/themes/osx/downloads/download-notification-start@2x.png b/browser/themes/osx/downloads/download-notification-start@2x.png
new file mode 100644
index 000000000..1d6278050
--- /dev/null
+++ b/browser/themes/osx/downloads/download-notification-start@2x.png
Binary files differ
diff --git a/browser/themes/osx/downloads/downloads.css b/browser/themes/osx/downloads/downloads.css
new file mode 100644
index 000000000..af78dcfe9
--- /dev/null
+++ b/browser/themes/osx/downloads/downloads.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/. */
+
+%include ../../shared/downloads/downloads.inc.css
+
+/*** Panel and outer controls ***/
+
+#downloadsPanel {
+ margin-top: -1px;
+}
+
+@keyfocus@ #downloadsSummary:focus,
+@keyfocus@ .downloadsPanelFooterButton:focus {
+ outline: 2px -moz-mac-focusring solid;
+ outline-offset: -2px;
+}
+
+/*** List items and similar elements in the summary ***/
+
+:root {
+ --downloads-item-height: 6em;
+ --downloads-item-font-size-factor: 0.95;
+ --downloads-item-details-opacity: 0.7;
+}
+
+.downloadButton:focus > .button-box {
+ outline: 2px -moz-mac-focusring solid;
+ outline-offset: -2px;
+}
+
+@item@[verdict="Malware"] {
+ color: #aa1b08;
+}
+
+.downloadProgress > .progress-bar {
+ background-color: #3c9af8;
+}
+
+.downloadProgress[paused="true"] > .progress-bar {
+ background-color: #a6a6a6;
+}
+
+/*** Highlighted list items ***/
+
+@keyfocus@ @itemFocused@ {
+ outline: 2px -moz-mac-focusring solid;
+ outline-offset: -2px;
+}
diff --git a/browser/themes/osx/downloads/indicator.css b/browser/themes/osx/downloads/indicator.css
new file mode 100644
index 000000000..eb88f074e
--- /dev/null
+++ b/browser/themes/osx/downloads/indicator.css
@@ -0,0 +1,250 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*** Status and progress indicator ***/
+
+#downloads-indicator-anchor {
+ min-width: 18px;
+ min-height: 18px;
+}
+
+#downloads-animation-container {
+ min-height: 1px;
+ min-width: 1px;
+ height: 1px;
+ margin-bottom: -1px;
+ /* Makes the outermost animation container element positioned, so that its
+ contents are rendered over the main browser window in the Z order.
+ This is required by the animated event notification. */
+ position: relative;
+ /* The selected tab may overlap #downloads-indicator-notification */
+ z-index: 5;
+}
+
+/*** Main indicator icon ***/
+
+#downloads-indicator-icon {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+ 0, 198, 18, 180) center no-repeat;
+}
+
+toolbar[brighttext] #downloads-indicator-icon {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ display: -moz-box;
+ height: 8px;
+ width: 8px;
+ min-width: 0;
+ border-radius: 50%;
+ /* "!important" is necessary to override the rule in toolbarbutton.css */
+ margin-top: -1px !important;
+ margin-right: -2px !important;
+}
+
+#downloads-button[cui-areatype="toolbar"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ height: 7px;
+ width: 7px;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #D90000;
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #FFBF00;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+ filter: none;
+}
+
+#downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
+}
+
+toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
+}
+
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
+ list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
+ -moz-image-region: auto;
+}
+
+/* In the next few rules, we use :not([counter]) as a shortcut that is
+ equivalent to -moz-any([progress], [paused]). */
+
+#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+ 0, 198, 18, 180) center no-repeat;
+ background-size: 12px;
+}
+
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+}
+
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
+}
+
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
+}
+
+@media (min-resolution: 2dppx) {
+ #downloads-indicator-icon {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
+ background-size: 18px;
+ }
+
+ toolbar[brighttext] #downloads-indicator-icon {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
+ }
+
+ #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
+ }
+
+ toolbar[brighttext] #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"),
+ 0, 396, 36, 360);
+ }
+
+ #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
+ }
+
+ toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
+ }
+
+ #downloads-button[cui-areatype="menu-panel"][attention="success"] {
+ list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel@2x.png");
+ }
+
+ #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
+ }
+
+ toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
+ }
+}
+
+/*** Download notifications ***/
+
+#downloads-indicator-notification {
+ opacity: 0;
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+}
+
+@keyframes downloadsIndicatorNotificationStartRight {
+ from { opacity: 0; transform: translate(-128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+@keyframes downloadsIndicatorNotificationStartLeft {
+ from { opacity: 0; transform: translate(128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+#downloads-notification-anchor[notification="start"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
+ animation-name: downloadsIndicatorNotificationStartRight;
+ animation-duration: 1s;
+}
+
+@media (min-resolution: 2dppx) {
+ #downloads-notification-anchor[notification="start"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-start@2x.png");
+ }
+}
+
+#downloads-notification-anchor[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-notification {
+ animation-name: downloadsIndicatorNotificationStartLeft;
+}
+
+@keyframes downloadsIndicatorNotificationFinish {
+ from { opacity: 0; transform: scale(1); }
+ 20% { opacity: .65; animation-timing-function: ease-in; }
+ to { opacity: 0; transform: scale(8); }
+}
+
+#downloads-notification-anchor[notification="finish"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
+ animation-name: downloadsIndicatorNotificationFinish;
+ animation-duration: 1s;
+}
+
+@media (min-resolution: 2dppx) {
+ #downloads-notification-anchor[notification="finish"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish@2x.png");
+ }
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+ height: 9px;
+ margin: -3px 0 0;
+ color: hsl(0,0%,30%);
+ text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
+ font-size: 9px;
+ line-height: 9px;
+ text-align: center;
+}
+
+#downloads-indicator-progress {
+ width: 16px;
+ height: 5px;
+ min-width: 0;
+ min-height: 0;
+ margin-top: 1px;
+ margin-bottom: 2px;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
+ background-clip: padding-box, border-box;
+ background-color: rgb(90, 185, 255);
+ background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
+ border: 1px solid;
+ border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
+ border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ background-image: linear-gradient(#505050, #575757);
+ border: 1px solid;
+ border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
+ border-inline-start: none;
+ border-radius: 0 2px 2px 0;
+}
+
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+ background-color: rgb(220, 230, 81);
+}
+
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+ background-image: linear-gradient(#4b5000, #515700);
+}
diff --git a/browser/themes/osx/feeds/feedIcon.png b/browser/themes/osx/feeds/feedIcon.png
new file mode 100644
index 000000000..236b5f821
--- /dev/null
+++ b/browser/themes/osx/feeds/feedIcon.png
Binary files differ
diff --git a/browser/themes/osx/feeds/feedIcon16.png b/browser/themes/osx/feeds/feedIcon16.png
new file mode 100644
index 000000000..a489de3bf
--- /dev/null
+++ b/browser/themes/osx/feeds/feedIcon16.png
Binary files differ
diff --git a/browser/themes/osx/feeds/subscribe-ui.css b/browser/themes/osx/feeds/subscribe-ui.css
new file mode 100644
index 000000000..4d0744b3a
--- /dev/null
+++ b/browser/themes/osx/feeds/subscribe-ui.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/. */
+
+.alwaysUse {
+ padding: 3px;
+}
+
diff --git a/browser/themes/osx/feeds/subscribe.css b/browser/themes/osx/feeds/subscribe.css
new file mode 100644
index 000000000..6d1fa8da2
--- /dev/null
+++ b/browser/themes/osx/feeds/subscribe.css
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+html {
+ background: -moz-Dialog;
+ font: 3mm tahoma,arial,helvetica,sans-serif;
+}
+
+#subscribeUsingDescription,
+#subscribeButton {
+ display: block;
+}
+
+#subscribeUsingDescription {
+ margin-bottom: 0.5em;
+}
+
+#subscribeButton {
+ margin-top: 0.5em;
+ margin-inline-start: auto;
+}
+
+#feedBody {
+ border: 1px solid THreeDShadow;
+ padding: 3em;
+ padding-inline-start: 30px;
+ margin: 2em auto;
+ background: -moz-Field;
+}
+
+#feedHeaderContainer {
+ display: flex;
+}
+
+#feedHeaderContainerSpacer {
+ flex-grow: 1;
+}
+
+#feedHeader {
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ padding-top: 4em;
+ padding-bottom: .3em;
+ padding-inline-start: .3em;
+ padding-inline-end: .3em;
+ margin: -4em auto 0 auto;
+ font-size: 110%;
+ color: InfoText;
+ padding: 5em 3em 0 3em;
+}
+
+.feedBackground {
+ background: url("chrome://browser/skin/feeds/feedIcon.png") 1.4em 5.9em no-repeat rgb(255,255,225);
+}
+
+.videoPodcastBackground {
+ background: url("chrome://browser/skin/feeds/videoFeedIcon.png") 1.4em 5.9em no-repeat rgb(255,255,225);
+}
+
+.audioPodcastBackground {
+ background: url("chrome://browser/skin/feeds/audioFeedIcon.png") 1.4em 5.9em no-repeat rgb(255,255,225);
+}
+
+#feedHeader[firstrun="true"] #feedIntroText {
+ display: block;
+}
+
+#feedIntroText {
+ display: none;
+ margin-inline-start: 2em;
+}
+
+#feedSubscribeLine {
+ padding: 0 1em 1em 2em;
+}
+
+#feedHeader[firstrun="true"] #feedSubscribeLine {
+ padding-left: 3.7em;
+}
+
+/* Don't print subscription UI */
+@media print {
+ #feedHeaderContainer {
+ display: none;
+ }
+}
+
+body {
+ margin: 0;
+ padding: 0 3em;
+ color: -moz-fieldText;
+ font: message-box;
+}
+
+h1 {
+ font-size: 160%;
+ border-bottom: 2px solid ThreeDLightShadow;
+ margin: 0 0 .2em 0;
+}
+
+h2 {
+ color: #C0C0C0;
+ font-size: 110%;
+ font-weight: normal;
+ margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+ float: right;
+ margin-inline-start: .6em;
+ margin-inline-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+a[href] img {
+ border: none;
+}
+
+#feedTitleContainer {
+ margin-inline-start: 0;
+ margin-inline-end: .6em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#feedTitleImage {
+ margin-inline-start: .6em;
+ margin-inline-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-width: 300px;
+ max-height: 150px;
+}
+
+.feedEntryContent {
+ font-size: 110%;
+}
+
+.link {
+ color: #0000FF;
+ text-decoration: underline;
+ cursor: pointer;
+ margin-top: -2px;
+}
+
+.link:hover:active {
+ color: #FF0000;
+}
+
+.lastUpdated {
+ font-size: 85%;
+ font-weight: normal;
+}
+
+.type-icon {
+ vertical-align: bottom;
+ height: 16px;
+ width: 16px;
+}
+
+.enclosures {
+ border: 1px solid THreeDShadow;
+ padding: 1em;
+ margin: 1em auto;
+ background: -moz-Dialog;
+}
+
+.enclosure {
+ vertical-align: middle;
+ margin-left: 2px;
+}
+
+.handlersMenuList > .menulist-label-box > .menulist-icon {
+ max-width: 16px;
+ max-height: 16px;
+}
diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn
new file mode 100644
index 000000000..fd9b6127d
--- /dev/null
+++ b/browser/themes/osx/jar.mn
@@ -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/.
+
+browser.jar:
+% skin browser classic/1.0 %skin/classic/browser/
+#include ../shared/jar.inc.mn
+ skin/classic/browser/sanitizeDialog.css
+ skin/classic/browser/aboutSessionRestore-window-icon.png
+ skin/classic/browser/aboutSyncTabs.css
+* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
+ skin/classic/browser/actionicon-tab.png
+ skin/classic/browser/actionicon-tab@2x.png
+* skin/classic/browser/browser.css
+* skin/classic/browser/devedition.css
+* skin/classic/browser/browser-lightweightTheme.css
+ skin/classic/browser/click-to-play-warning-stripes.png
+ skin/classic/browser/Info.png
+ skin/classic/browser/keyhole-circle.png
+ skin/classic/browser/keyhole-circle@2x.png
+ skin/classic/browser/subtle-pattern.png
+ skin/classic/browser/menu-back.png
+ skin/classic/browser/menu-forward.png
+ skin/classic/browser/menuPanel-customize.png
+ skin/classic/browser/menuPanel-customize@2x.png
+ skin/classic/browser/menuPanel-exit.png
+ skin/classic/browser/menuPanel-exit@2x.png
+ skin/classic/browser/menuPanel-help.png
+ skin/classic/browser/menuPanel-help@2x.png
+ skin/classic/browser/panel-expander-closed.png
+ skin/classic/browser/panel-expander-closed@2x.png
+ skin/classic/browser/panel-expander-open.png
+ skin/classic/browser/panel-expander-open@2x.png
+ skin/classic/browser/panel-plus-sign.png
+ skin/classic/browser/page-livemarks.png
+ skin/classic/browser/page-livemarks@2x.png
+ skin/classic/browser/pageInfo.css
+ skin/classic/browser/privatebrowsing-mask.png
+ skin/classic/browser/privatebrowsing-mask@2x.png
+ skin/classic/browser/privatebrowsing-mask-short.png
+ skin/classic/browser/privatebrowsing-mask-short@2x.png
+ skin/classic/browser/reload-stop-go.png
+ skin/classic/browser/reload-stop-go@2x.png
+ skin/classic/browser/searchbar.css
+ skin/classic/browser/slowStartup-16.png
+ skin/classic/browser/Toolbar.png
+ skin/classic/browser/Toolbar@2x.png
+ skin/classic/browser/Toolbar-inverted.png
+ skin/classic/browser/Toolbar-inverted@2x.png
+ skin/classic/browser/toolbarbutton-dropmarker.png
+ skin/classic/browser/urlbar-history-dropmarker.png
+ skin/classic/browser/urlbar-history-dropmarker@2x.png
+ skin/classic/browser/urlbar-popup-blocked.png
+ skin/classic/browser/urlbar-popup-blocked@2x.png
+ skin/classic/browser/webRTC-sharingDevice-menubar.png
+ skin/classic/browser/webRTC-sharingDevice-menubar@2x.png
+ skin/classic/browser/webRTC-sharingMicrophone-menubar.png
+ skin/classic/browser/webRTC-sharingMicrophone-menubar@2x.png
+ skin/classic/browser/webRTC-sharingScreen-menubar.png
+ skin/classic/browser/webRTC-sharingScreen-menubar@2x.png
+ skin/classic/browser/webRTC-indicator.css
+* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)
+ skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
+ skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png)
+ skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png (customizableui/customize-titleBar-toggle@2x.png)
+ skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png)
+ skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)
+ skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png)
+* skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css)
+* skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
+ skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
+ skin/classic/browser/downloads/download-glow-menuPanel@2x.png (downloads/download-glow-menuPanel@2x.png)
+ skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
+ skin/classic/browser/downloads/download-notification-finish@2x.png (downloads/download-notification-finish@2x.png)
+ skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
+ skin/classic/browser/downloads/download-notification-start@2x.png (downloads/download-notification-start@2x.png)
+* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
+ skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
+ skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+ skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/setDesktopBackground.css
+ skin/classic/browser/monitor.png
+ skin/classic/browser/monitor_16-10.png
+ skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)
+ skin/classic/browser/places/autocomplete-star.png (places/autocomplete-star.png)
+ skin/classic/browser/places/autocomplete-star@2x.png (places/autocomplete-star@2x.png)
+* skin/classic/browser/places/places.css (places/places.css)
+ skin/classic/browser/places/organizer.css (places/organizer.css)
+ skin/classic/browser/places/query.png (places/query.png)
+ skin/classic/browser/places/query@2x.png (places/query@2x.png)
+ skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
+ skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
+ skin/classic/browser/places/bookmarksToolbar@2x.png (places/bookmarksToolbar@2x.png)
+ skin/classic/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png)
+ skin/classic/browser/places/bookmarks-notification-finish@2x.png (places/bookmarks-notification-finish@2x.png)
+ skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
+ skin/classic/browser/places/bookmarksToolbar-menuPanel@2x.png (places/bookmarksToolbar-menuPanel@2x.png)
+ skin/classic/browser/places/history.png (places/history.png)
+ skin/classic/browser/places/history@2x.png (places/history@2x.png)
+ skin/classic/browser/places/toolbar.png (places/toolbar.png)
+ skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
+ skin/classic/browser/places/folderDropArrow.png (places/folderDropArrow.png)
+ skin/classic/browser/places/folderDropArrow@2x.png (places/folderDropArrow@2x.png)
+ skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
+ skin/classic/browser/places/minus.png (places/minus.png)
+ skin/classic/browser/places/minus-active.png (places/minus-active.png)
+ skin/classic/browser/places/plus.png (places/plus.png)
+ skin/classic/browser/places/plus-active.png (places/plus-active.png)
+ skin/classic/browser/places/starred48.png (places/starred48.png)
+ skin/classic/browser/places/starred48@2x.png (places/starred48@2x.png)
+ skin/classic/browser/places/unstarred48.png (places/unstarred48.png)
+ skin/classic/browser/places/unfiledBookmarks.png (places/unfiledBookmarks.png)
+ skin/classic/browser/places/unfiledBookmarks@2x.png (places/unfiledBookmarks@2x.png)
+ skin/classic/browser/places/tag.png (places/tag.png)
+ skin/classic/browser/places/tag@2x.png (places/tag@2x.png)
+ skin/classic/browser/places/downloads.png (places/downloads.png)
+ skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
+ skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
+ skin/classic/browser/preferences/application.png (preferences/application.png)
+ skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
+* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
+* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
+* skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css)
+ skin/classic/browser/preferences/applications.css (preferences/applications.css)
+ skin/classic/browser/social/services-16.png (social/services-16.png)
+ skin/classic/browser/social/services-16@2x.png (social/services-16@2x.png)
+ skin/classic/browser/social/services-64.png (social/services-64.png)
+ skin/classic/browser/social/services-64@2x.png (social/services-64@2x.png)
+ skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png (tabbrowser/alltabs-box-bkgnd-icon.png)
+ skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted.png (tabbrowser/alltabs-box-bkgnd-icon-inverted.png)
+ skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png (tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png)
+ skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
+ skin/classic/browser/tabbrowser/newtab@2x.png (tabbrowser/newtab@2x.png)
+ skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
+ skin/classic/browser/tabbrowser/newtab-inverted@2x.png (tabbrowser/newtab-inverted@2x.png)
+ skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
+ skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left@2x.png (tabbrowser/tab-arrow-left@2x.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left-inverted@2x.png (tabbrowser/tab-arrow-left-inverted@2x.png)
+ skin/classic/browser/tabbrowser/tab-arrow-right.png (tabbrowser/tab-arrow-right.png)
+ skin/classic/browser/tabbrowser/tab-arrow-right@2x.png (tabbrowser/tab-arrow-right@2x.png)
+ skin/classic/browser/tabbrowser/tab-arrow-right-inverted.png (tabbrowser/tab-arrow-right-inverted.png)
+ skin/classic/browser/tabbrowser/tab-arrow-right-inverted@2x.png (tabbrowser/tab-arrow-right-inverted@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)
+ skin/classic/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
+ skin/classic/browser/tabbrowser/tab-background-middle@2x.png (tabbrowser/tab-background-middle@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-start.png (tabbrowser/tab-background-start.png)
+ skin/classic/browser/tabbrowser/tab-background-start@2x.png (tabbrowser/tab-background-start@2x.png)
+
+# NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
+# Makefile.in with a non-default marker of "%" and the result of that gets packaged.
+ skin/classic/browser/tabbrowser/tab-selected-end.svg (tab-selected-end.svg)
+ skin/classic/browser/tabbrowser/tab-selected-start.svg (tab-selected-start.svg)
+
+ skin/classic/browser/tabbrowser/tab-stroke-end.png (tabbrowser/tab-stroke-end.png)
+ skin/classic/browser/tabbrowser/tab-stroke-end@2x.png (tabbrowser/tab-stroke-end@2x.png)
+ skin/classic/browser/tabbrowser/tab-stroke-start.png (tabbrowser/tab-stroke-start.png)
+ skin/classic/browser/tabbrowser/tab-stroke-start@2x.png (tabbrowser/tab-stroke-start@2x.png)
+ skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
+ skin/classic/browser/tabbrowser/tabDragIndicator@2x.png (tabbrowser/tabDragIndicator@2x.png)
+ skin/classic/browser/sync-16.png
+ skin/classic/browser/sync-32.png
+ skin/classic/browser/sync-bg.png
+ skin/classic/browser/sync-128.png
+ skin/classic/browser/sync-desktopIcon.svg (../shared/sync-desktopIcon.svg)
+ skin/classic/browser/sync-horizontalbar.png
+ skin/classic/browser/sync-horizontalbar@2x.png
+ skin/classic/browser/sync-mobileIcon.svg (../shared/sync-mobileIcon.svg)
+ skin/classic/browser/sync-notification-24.png
+ skin/classic/browser/syncSetup.css
+ skin/classic/browser/syncCommon.css
+ skin/classic/browser/syncQuota.css
+ skin/classic/browser/syncProgress-horizontalbar.png
+ skin/classic/browser/syncProgress-horizontalbar@2x.png
+ skin/classic/browser/syncProgress-menuPanel.png
+ skin/classic/browser/syncProgress-menuPanel@2x.png
+ skin/classic/browser/syncProgress-toolbar.png
+ skin/classic/browser/syncProgress-toolbar@2x.png
+ skin/classic/browser/syncProgress-toolbar-inverted.png
+ skin/classic/browser/syncProgress-toolbar-inverted@2x.png
+ skin/classic/browser/Toolbar-background-noise.png (Toolbar-background-noise.png)
+ skin/classic/browser/lion/toolbarbutton-dropmarker.png (toolbarbutton-dropmarker-lion.png)
+ skin/classic/browser/toolbarbutton-dropmarker@2x.png (toolbarbutton-dropmarker-lion@2x.png)
+ skin/classic/browser/lion/tabbrowser/alltabs-box-bkgnd-icon.png (tabbrowser/alltabs-box-bkgnd-icon-lion.png)
+ skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon@2x.png (tabbrowser/alltabs-box-bkgnd-icon-lion@2x.png)
+ skin/classic/browser/lion/places/toolbar.png (places/toolbar-lion.png)
+ skin/classic/browser/yosemite/Toolbar.png (Toolbar-yosemite.png)
+ skin/classic/browser/yosemite/Toolbar@2x.png (Toolbar-yosemite@2x.png)
+ skin/classic/browser/yosemite/menuPanel-customize.png (menuPanel-customize-yosemite.png)
+ skin/classic/browser/yosemite/menuPanel-customize@2x.png (menuPanel-customize-yosemite@2x.png)
+ skin/classic/browser/yosemite/menuPanel-exit.png (menuPanel-exit-yosemite.png)
+ skin/classic/browser/yosemite/menuPanel-exit@2x.png (menuPanel-exit-yosemite@2x.png)
+ skin/classic/browser/yosemite/menuPanel-help.png (menuPanel-help-yosemite.png)
+ skin/classic/browser/yosemite/menuPanel-help@2x.png (menuPanel-help-yosemite@2x.png)
+ skin/classic/browser/yosemite/reload-stop-go.png (reload-stop-go-yosemite.png)
+ skin/classic/browser/yosemite/reload-stop-go@2x.png (reload-stop-go-yosemite@2x.png)
+ skin/classic/browser/yosemite/sync-horizontalbar.png (sync-horizontalbar-yosemite.png)
+ skin/classic/browser/yosemite/sync-horizontalbar@2x.png (sync-horizontalbar-yosemite@2x.png)
+ skin/classic/browser/yosemite/tab-selected-end-inactive.svg (tabbrowser/tab-selected-end-yosemite-inactive.svg)
+ skin/classic/browser/yosemite/tab-selected-start-inactive.svg (tabbrowser/tab-selected-start-yosemite-inactive.svg)
+ skin/classic/browser/yosemite/tab-active-middle-inactive.png (tabbrowser/tab-active-middle-yosemite-inactive.png)
+ skin/classic/browser/yosemite/tab-active-middle-inactive@2x.png (tabbrowser/tab-active-middle-yosemite-inactive@2x.png)
+ skin/classic/browser/yosemite/tab-stroke-end-inactive.png (tabbrowser/tab-stroke-end-yosemite-inactive.png)
+ skin/classic/browser/yosemite/tab-stroke-end-inactive@2x.png (tabbrowser/tab-stroke-end-yosemite-inactive@2x.png)
+ skin/classic/browser/yosemite/tab-stroke-start-inactive.png (tabbrowser/tab-stroke-start-yosemite-inactive.png)
+ skin/classic/browser/yosemite/tab-stroke-start-inactive@2x.png (tabbrowser/tab-stroke-start-yosemite-inactive@2x.png)
+#ifdef E10S_TESTING_ONLY
+ skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
+#endif
+
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
+% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
+% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
+% override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
+% override chrome://browser/skin/feeds/videoFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
+% override chrome://browser/skin/toolbarbutton-dropmarker.png chrome://browser/skin/lion/toolbarbutton-dropmarker.png os=Darwin osversion>=10.7
+% override chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon.png chrome://browser/skin/lion/tabbrowser/alltabs-box-bkgnd-icon.png os=Darwin osversion>=10.7
+% override chrome://browser/skin/places/toolbar.png chrome://browser/skin/lion/places/toolbar.png os=Darwin osversion>=10.7
+% override chrome://browser/skin/Toolbar.png chrome://browser/skin/yosemite/Toolbar.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/Toolbar@2x.png chrome://browser/skin/yosemite/Toolbar@2x.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/menuPanel-customize.png chrome://browser/skin/yosemite/menuPanel-customize.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/menuPanel-customize@2x.png chrome://browser/skin/yosemite/menuPanel-customize@2x.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/menuPanel-exit.png chrome://browser/skin/yosemite/menuPanel-exit.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/menuPanel-exit@2x.png chrome://browser/skin/yosemite/menuPanel-exit@2x.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/menuPanel-help.png chrome://browser/skin/yosemite/menuPanel-help.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/menuPanel-help@2x.png chrome://browser/skin/yosemite/menuPanel-help@2x.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/reload-stop-go.png chrome://browser/skin/yosemite/reload-stop-go.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/reload-stop-go@2x.png chrome://browser/skin/yosemite/reload-stop-go@2x.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/sync-horizontalbar.png chrome://browser/skin/yosemite/sync-horizontalbar.png os=Darwin osversion>=10.10
+% override chrome://browser/skin/sync-horizontalbar@2x.png chrome://browser/skin/yosemite/sync-horizontalbar@2x.png os=Darwin osversion>=10.10
diff --git a/browser/themes/osx/keyhole-circle.png b/browser/themes/osx/keyhole-circle.png
new file mode 100644
index 000000000..fc177587f
--- /dev/null
+++ b/browser/themes/osx/keyhole-circle.png
Binary files differ
diff --git a/browser/themes/osx/keyhole-circle@2x.png b/browser/themes/osx/keyhole-circle@2x.png
new file mode 100644
index 000000000..288702805
--- /dev/null
+++ b/browser/themes/osx/keyhole-circle@2x.png
Binary files differ
diff --git a/browser/themes/osx/menu-back.png b/browser/themes/osx/menu-back.png
new file mode 100644
index 000000000..82a74c7b8
--- /dev/null
+++ b/browser/themes/osx/menu-back.png
Binary files differ
diff --git a/browser/themes/osx/menu-forward.png b/browser/themes/osx/menu-forward.png
new file mode 100644
index 000000000..b3b40e394
--- /dev/null
+++ b/browser/themes/osx/menu-forward.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-customize-yosemite.png b/browser/themes/osx/menuPanel-customize-yosemite.png
new file mode 100644
index 000000000..93b660692
--- /dev/null
+++ b/browser/themes/osx/menuPanel-customize-yosemite.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-customize-yosemite@2x.png b/browser/themes/osx/menuPanel-customize-yosemite@2x.png
new file mode 100644
index 000000000..43cde4a68
--- /dev/null
+++ b/browser/themes/osx/menuPanel-customize-yosemite@2x.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-customize.png b/browser/themes/osx/menuPanel-customize.png
new file mode 100644
index 000000000..b0772d822
--- /dev/null
+++ b/browser/themes/osx/menuPanel-customize.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-customize@2x.png b/browser/themes/osx/menuPanel-customize@2x.png
new file mode 100644
index 000000000..aba481aeb
--- /dev/null
+++ b/browser/themes/osx/menuPanel-customize@2x.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-exit-yosemite.png b/browser/themes/osx/menuPanel-exit-yosemite.png
new file mode 100644
index 000000000..4115b53ec
--- /dev/null
+++ b/browser/themes/osx/menuPanel-exit-yosemite.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-exit-yosemite@2x.png b/browser/themes/osx/menuPanel-exit-yosemite@2x.png
new file mode 100644
index 000000000..af3160621
--- /dev/null
+++ b/browser/themes/osx/menuPanel-exit-yosemite@2x.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-exit.png b/browser/themes/osx/menuPanel-exit.png
new file mode 100644
index 000000000..126cde088
--- /dev/null
+++ b/browser/themes/osx/menuPanel-exit.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-exit@2x.png b/browser/themes/osx/menuPanel-exit@2x.png
new file mode 100644
index 000000000..1ccdd65b9
--- /dev/null
+++ b/browser/themes/osx/menuPanel-exit@2x.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-help-yosemite.png b/browser/themes/osx/menuPanel-help-yosemite.png
new file mode 100644
index 000000000..ad1783de6
--- /dev/null
+++ b/browser/themes/osx/menuPanel-help-yosemite.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-help-yosemite@2x.png b/browser/themes/osx/menuPanel-help-yosemite@2x.png
new file mode 100644
index 000000000..4a4c3bcae
--- /dev/null
+++ b/browser/themes/osx/menuPanel-help-yosemite@2x.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-help.png b/browser/themes/osx/menuPanel-help.png
new file mode 100644
index 000000000..a9b34c132
--- /dev/null
+++ b/browser/themes/osx/menuPanel-help.png
Binary files differ
diff --git a/browser/themes/osx/menuPanel-help@2x.png b/browser/themes/osx/menuPanel-help@2x.png
new file mode 100644
index 000000000..acf2e531c
--- /dev/null
+++ b/browser/themes/osx/menuPanel-help@2x.png
Binary files differ
diff --git a/browser/themes/osx/monitor.png b/browser/themes/osx/monitor.png
new file mode 100644
index 000000000..ad90e5520
--- /dev/null
+++ b/browser/themes/osx/monitor.png
Binary files differ
diff --git a/browser/themes/osx/monitor_16-10.png b/browser/themes/osx/monitor_16-10.png
new file mode 100644
index 000000000..fb3fdbc4f
--- /dev/null
+++ b/browser/themes/osx/monitor_16-10.png
Binary files differ
diff --git a/browser/themes/osx/moz.build b/browser/themes/osx/moz.build
new file mode 100644
index 000000000..b787ab08e
--- /dev/null
+++ b/browser/themes/osx/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['communicator']
+
+JAR_MANIFESTS += ['jar.mn']
+
+DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
+
+include('../tab-svgs.mozbuild')
diff --git a/browser/themes/osx/newtab/newTab.css b/browser/themes/osx/newtab/newTab.css
new file mode 100644
index 000000000..0a5bace75
--- /dev/null
+++ b/browser/themes/osx/newtab/newTab.css
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/newtab/newTab.inc.css
+
+
+.newtab-undo-button {
+ color: rgb(20,79,174);
+}
+
+.newtab-title {
+ font-family: Lucida Grande;
+}
diff --git a/browser/themes/osx/page-livemarks.png b/browser/themes/osx/page-livemarks.png
new file mode 100644
index 000000000..e52645846
--- /dev/null
+++ b/browser/themes/osx/page-livemarks.png
Binary files differ
diff --git a/browser/themes/osx/page-livemarks@2x.png b/browser/themes/osx/page-livemarks@2x.png
new file mode 100644
index 000000000..7b170897c
--- /dev/null
+++ b/browser/themes/osx/page-livemarks@2x.png
Binary files differ
diff --git a/browser/themes/osx/pageInfo.css b/browser/themes/osx/pageInfo.css
new file mode 100644
index 000000000..748b7b3fa
--- /dev/null
+++ b/browser/themes/osx/pageInfo.css
@@ -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/. */
+
+@import "chrome://global/skin/";
+
+/* View buttons */
+@import "chrome://global/skin/viewbuttons.css";
+
+/* Extensions will provide icons because the view buttons have icons
+ on the windows and linux themes. We don't want to display them */
+.viewButtonIcon {
+ display: none;
+}
+
+deck {
+ padding: 10px 10px 10px 10px;
+}
+
+/* Misc */
+tree {
+ margin: .5em;
+}
+
+.gridSeparator {
+ width: .5em;
+}
+
+textbox {
+ background: transparent !important;
+ border: none;
+ padding: 0px;
+ margin-top: 1px;
+ -moz-appearance: none;
+}
+
+textbox.header {
+ margin-inline-start: 0;
+}
+
+.iframe {
+ margin: .5em;
+ background: white;
+ overflow: auto;
+}
+
+.fixedsize {
+ height: 8.5em;
+}
+
+textbox[disabled] {
+ font-style: italic;
+}
+
+/* General Tab */
+groupbox.collapsable caption .caption-icon {
+ width: 11px;
+ height: 11px;
+ background-repeat: no-repeat;
+ background-position: center;
+ margin-inline-end: 2px;
+ background-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+ background-image: url("chrome://global/skin/arrow/arrow-rit.gif");
+}
+
+groupbox tree {
+ margin: 0;
+ border: none;
+}
+
+groupbox.treebox .groupbox-body {
+ padding: 0;
+}
+
+#securityBox description {
+ margin-inline-start: 10px;
+}
+
+#general-security-identity {
+ white-space: pre-wrap;
+ line-height: 2em;
+}
+
+/* Media Tab */
+#imagetree {
+ min-height: 10em;
+ margin-bottom: 0;
+}
+
+#mediaSplitter {
+ background: none;
+}
+
+#mediaGrid {
+ min-height: 9em;
+}
+
+#mediaLabelColumn {
+ min-width: 10em;
+}
+
+#thepreviewimage {
+ margin: 1em;
+}
+
+treechildren::-moz-tree-cell-text(broken) {
+ font-style: italic;
+ color: graytext;
+}
+
+/* Feeds Tab */
+#feedtree {
+ margin-bottom: 0px;
+}
+
+#feedListbox richlistitem {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+#feedListbox richlistitem[selected="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+#feedListbox {
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+}
+
+.feedTitle {
+ font-weight: bold;
+}
+
+/* Permissions Tab */
+#permList {
+ margin-top: .5em;
+ overflow: auto;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ background-color: -moz-field;
+}
+
+.permission {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+.permissionLabel {
+ font-weight: bold;
+}
+
+.permission:hover {
+ background-color: -moz-dialog;
+}
+
+/* Security Tab */
+#securityPanel .caption-icon {
+ display: none;
+}
+
+#securityPanel .header {
+ font-size: 120%;
+}
+
+#securityPanel .fieldLabel {
+ margin: 2px 10px 3px;
+}
+
+#securityPanel .fieldValue {
+ font-weight: bold;
+ margin: 2px 10px 3px;
+}
+
+#securityPanel row {
+ -moz-box-align: center;
+}
diff --git a/browser/themes/osx/panel-expander-closed.png b/browser/themes/osx/panel-expander-closed.png
new file mode 100644
index 000000000..f0e97b22e
--- /dev/null
+++ b/browser/themes/osx/panel-expander-closed.png
Binary files differ
diff --git a/browser/themes/osx/panel-expander-closed@2x.png b/browser/themes/osx/panel-expander-closed@2x.png
new file mode 100644
index 000000000..0e7ded50f
--- /dev/null
+++ b/browser/themes/osx/panel-expander-closed@2x.png
Binary files differ
diff --git a/browser/themes/osx/panel-expander-open.png b/browser/themes/osx/panel-expander-open.png
new file mode 100644
index 000000000..e3febf4ff
--- /dev/null
+++ b/browser/themes/osx/panel-expander-open.png
Binary files differ
diff --git a/browser/themes/osx/panel-expander-open@2x.png b/browser/themes/osx/panel-expander-open@2x.png
new file mode 100644
index 000000000..391337030
--- /dev/null
+++ b/browser/themes/osx/panel-expander-open@2x.png
Binary files differ
diff --git a/browser/themes/osx/panel-plus-sign.png b/browser/themes/osx/panel-plus-sign.png
new file mode 100644
index 000000000..375601e68
--- /dev/null
+++ b/browser/themes/osx/panel-plus-sign.png
Binary files differ
diff --git a/browser/themes/osx/places/allBookmarks.png b/browser/themes/osx/places/allBookmarks.png
new file mode 100644
index 000000000..d1abe8192
--- /dev/null
+++ b/browser/themes/osx/places/allBookmarks.png
Binary files differ
diff --git a/browser/themes/osx/places/autocomplete-star.png b/browser/themes/osx/places/autocomplete-star.png
new file mode 100644
index 000000000..4ad9a95cf
--- /dev/null
+++ b/browser/themes/osx/places/autocomplete-star.png
Binary files differ
diff --git a/browser/themes/osx/places/autocomplete-star@2x.png b/browser/themes/osx/places/autocomplete-star@2x.png
new file mode 100644
index 000000000..4a9c203ff
--- /dev/null
+++ b/browser/themes/osx/places/autocomplete-star@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/bookmarks-notification-finish.png b/browser/themes/osx/places/bookmarks-notification-finish.png
new file mode 100644
index 000000000..cb4b53e07
--- /dev/null
+++ b/browser/themes/osx/places/bookmarks-notification-finish.png
Binary files differ
diff --git a/browser/themes/osx/places/bookmarks-notification-finish@2x.png b/browser/themes/osx/places/bookmarks-notification-finish@2x.png
new file mode 100644
index 000000000..8db41cd75
--- /dev/null
+++ b/browser/themes/osx/places/bookmarks-notification-finish@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/bookmarksMenu.png b/browser/themes/osx/places/bookmarksMenu.png
new file mode 100644
index 000000000..c27bd6a6f
--- /dev/null
+++ b/browser/themes/osx/places/bookmarksMenu.png
Binary files differ
diff --git a/browser/themes/osx/places/bookmarksToolbar-menuPanel.png b/browser/themes/osx/places/bookmarksToolbar-menuPanel.png
new file mode 100644
index 000000000..892125ad3
--- /dev/null
+++ b/browser/themes/osx/places/bookmarksToolbar-menuPanel.png
Binary files differ
diff --git a/browser/themes/osx/places/bookmarksToolbar-menuPanel@2x.png b/browser/themes/osx/places/bookmarksToolbar-menuPanel@2x.png
new file mode 100644
index 000000000..c81710a0e
--- /dev/null
+++ b/browser/themes/osx/places/bookmarksToolbar-menuPanel@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/bookmarksToolbar.png b/browser/themes/osx/places/bookmarksToolbar.png
new file mode 100644
index 000000000..2047bffe5
--- /dev/null
+++ b/browser/themes/osx/places/bookmarksToolbar.png
Binary files differ
diff --git a/browser/themes/osx/places/bookmarksToolbar@2x.png b/browser/themes/osx/places/bookmarksToolbar@2x.png
new file mode 100644
index 000000000..dd458981e
--- /dev/null
+++ b/browser/themes/osx/places/bookmarksToolbar@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/downloads.png b/browser/themes/osx/places/downloads.png
new file mode 100644
index 000000000..0756cb680
--- /dev/null
+++ b/browser/themes/osx/places/downloads.png
Binary files differ
diff --git a/browser/themes/osx/places/editBookmarkOverlay.css b/browser/themes/osx/places/editBookmarkOverlay.css
new file mode 100644
index 000000000..584a830ac
--- /dev/null
+++ b/browser/themes/osx/places/editBookmarkOverlay.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/**** folder menulist ****/
+.folder-icon > .menulist-label-box > .menulist-icon,
+.folder-icon > .menu-iconic-left > .menu-iconic-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.folder-icon > .menu-iconic-left {
+ display: -moz-box;
+}
+
+.folder-icon {
+ list-style-image: url("chrome://global/skin/tree/folder.png") !important;
+}
+
+@media (min-resolution: 2dppx) {
+ .folder-icon {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png") !important;
+ }
+}
+
+.menulist-icon {
+ margin: 0 !important;
+}
+
+/**** expanders ****/
+
+.expander-up,
+.expander-down {
+ margin: 0 4px 1px 8px;
+ padding: 0;
+}
+
+.expander-up {
+ -moz-appearance: -moz-mac-disclosure-button-open;
+}
+
+.expander-down {
+ -moz-appearance: -moz-mac-disclosure-button-closed;
+}
+
+#editBookmarkPanelContent {
+ min-width: 23em;
+}
+
+#editBMPanel_folderTree {
+ margin: 6px 4px 0 4px;
+}
+
+/* Hide the value column of the tag autocomplete popup
+ * leaving only the comment column visible. This is
+ * so that only the tag being edited is shown in the
+ * popup.
+ */
+#editBMPanel_tagsField #treecolAutoCompleteValue {
+ visibility: collapse;
+}
+
+
+/* ----- BOOKMARK PANEL DROPDOWN MENU ITEMS ----- */
+
+#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_bmRootItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.png") !important;
+}
+
+@media (min-resolution: 2dppx) {
+ #editBMPanel_folderMenuList[selectedIndex="0"],
+ #editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar@2x.png") !important;
+ }
+
+ #editBMPanel_folderMenuList[selectedIndex="2"],
+ #editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unfiledBookmarks@2x.png") !important;
+ }
+}
diff --git a/browser/themes/osx/places/folderDropArrow.png b/browser/themes/osx/places/folderDropArrow.png
new file mode 100644
index 000000000..8d722ccd5
--- /dev/null
+++ b/browser/themes/osx/places/folderDropArrow.png
Binary files differ
diff --git a/browser/themes/osx/places/folderDropArrow@2x.png b/browser/themes/osx/places/folderDropArrow@2x.png
new file mode 100644
index 000000000..9efb6d95d
--- /dev/null
+++ b/browser/themes/osx/places/folderDropArrow@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/history.png b/browser/themes/osx/places/history.png
new file mode 100644
index 000000000..e5a00b56d
--- /dev/null
+++ b/browser/themes/osx/places/history.png
Binary files differ
diff --git a/browser/themes/osx/places/history@2x.png b/browser/themes/osx/places/history@2x.png
new file mode 100644
index 000000000..684b374ff
--- /dev/null
+++ b/browser/themes/osx/places/history@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/livemark-item.png b/browser/themes/osx/places/livemark-item.png
new file mode 100644
index 000000000..9184b9518
--- /dev/null
+++ b/browser/themes/osx/places/livemark-item.png
Binary files differ
diff --git a/browser/themes/osx/places/minus-active.png b/browser/themes/osx/places/minus-active.png
new file mode 100644
index 000000000..d43c58fe0
--- /dev/null
+++ b/browser/themes/osx/places/minus-active.png
Binary files differ
diff --git a/browser/themes/osx/places/minus.png b/browser/themes/osx/places/minus.png
new file mode 100644
index 000000000..ba868c1b2
--- /dev/null
+++ b/browser/themes/osx/places/minus.png
Binary files differ
diff --git a/browser/themes/osx/places/organizer.css b/browser/themes/osx/places/organizer.css
new file mode 100644
index 000000000..4e3682d4c
--- /dev/null
+++ b/browser/themes/osx/places/organizer.css
@@ -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/. */
+
+/* Places Organizer Sidebars */
+
+#placesList > treechildren::-moz-tree-row {
+ background-color: transparent;
+ border-color: transparent;
+ padding-bottom: 1px;
+ height: 24px;
+}
+
+#placesList > treechildren::-moz-tree-cell-text {
+ font-size: 12px;
+ margin-inline-end: 6px;
+}
+
+#placesList > treechildren::-moz-tree-row(selected) {
+ -moz-appearance: -moz-mac-source-list-selection;
+}
+
+#placesList > treechildren::-moz-tree-row(selected,focus) {
+ -moz-appearance: -moz-mac-active-source-list-selection;
+}
+
+#placesList > treechildren::-moz-tree-row(History),
+#placesList > treechildren::-moz-tree-row(history) {
+ background-color: blue;
+}
+
+#placesList > treechildren::-moz-tree-cell(separator) {
+ cursor: default;
+}
+
+#placesList > treechildren::-moz-tree-separator {
+ border-top: 1px solid #505d6d;
+ margin: 0 10px;
+}
+
+#placesList > treechildren::-moz-tree-cell-text(selected) {
+ color: #fff;
+ font-weight: bold;
+}
+
+#placesList > treechildren::-moz-tree-twisty {
+ -moz-appearance: none;
+ padding: 0 2px;
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+}
+
+#placesList > treechildren::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+}
+
+#placesList > treechildren::-moz-tree-twisty(open) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+}
+
+#placesList > treechildren::-moz-tree-twisty(open, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+}
+
+#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
+}
+
+#placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
+}
+
+@media (-moz-mac-yosemite-theme) {
+ #placesList > treechildren::-moz-tree-cell-text(selected) {
+ color: -moz-dialogtext;
+ font-weight: 500;
+ }
+
+ #placesList > treechildren::-moz-tree-cell-text(selected, focus) {
+ color: #fff;
+ }
+
+ #placesList > treechildren::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+ }
+
+ #placesList > treechildren::-moz-tree-twisty(closed, selected, focus) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+ }
+
+ #placesList > treechildren::-moz-tree-twisty(open, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+ }
+
+ #placesList > treechildren::-moz-tree-twisty(open, selected, focus) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+ }
+
+ #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
+ }
+
+ #placesList > treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected, focus) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
+ }
+}
+
+#placesToolbar {
+ padding: 0 4px 3px;
+}
+
+#placesView {
+ border-top: none !important;
+}
+
+#placesView > splitter {
+ border-inline-start: none !important;
+ border-inline-end: 1px solid #b4b4b4;
+ min-width: 1px;
+ width: 3px;
+ margin-inline-start: -3px;
+ position: relative;
+ background-image: none !important;
+}
+
+#placesToolbar > toolbarbutton {
+ list-style-image: url("chrome://browser/skin/places/toolbar.png");
+ margin: 4px 4px 5px;
+ padding: 0;
+ height: 22px;
+ -moz-appearance: toolbarbutton;
+}
+
+#placesToolbar > toolbarbutton > .toolbarbutton-icon {
+ margin: 1px 4px;
+}
+
+#placesToolbar > toolbarbutton:not(#clearDownloadsButton) > .toolbarbutton-text {
+ display: none;
+}
+
+#placesToolbar > toolbarbutton[type="menu"] > .toolbarbutton-menu-dropmarker {
+ list-style-image: url(chrome://browser/skin/toolbarbutton-dropmarker.png);
+ padding: 0;
+ margin-top: 1px;
+ margin-inline-end: 2px;
+}
+
+@media (min-resolution: 2dppx) {
+ #placesToolbar > toolbarbutton[type="menu"] > .toolbarbutton-menu-dropmarker {
+ list-style-image: url(chrome://browser/skin/toolbarbutton-dropmarker@2x.png);
+ }
+
+ #placesToolbar > toolbarbutton[type="menu"] > .toolbarbutton-menu-dropmarker > .dropmarker-icon {
+ width: 7px;
+ }
+}
+
+#placesToolbar > toolbarbutton[disabled="true"] > .toolbarbutton-icon,
+#placesToolbar > toolbarbutton:not(:hover):-moz-window-inactive > .toolbarbutton-icon,
+#placesToolbar > toolbarbutton[type="menu"][disabled="true"] > .toolbarbutton-menu-dropmarker,
+#placesToolbar > toolbarbutton:not(:hover):-moz-window-inactive[type="menu"] > .toolbarbutton-menu-dropmarker {
+ opacity: .5;
+}
+
+#placesToolbar > toolbarbutton:-moz-window-inactive[disabled="true"] > .toolbarbutton-icon,
+#placesToolbar > toolbarbutton:-moz-window-inactive[type="menu"][disabled="true"] > .toolbarbutton-menu-dropmarker {
+ opacity: .25;
+}
+
+#placesToolbar > toolbarbutton > menupopup {
+ margin-top: 1px;
+}
+
+/* back and forward button */
+#back-button:-moz-locale-dir(ltr),
+#forward-button:-moz-locale-dir(rtl) {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+ margin-right: 0;
+}
+
+#forward-button:-moz-locale-dir(ltr),
+#back-button:-moz-locale-dir(rtl) {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+ margin-left: 0;
+}
+
+#back-button > .toolbarbutton-icon {
+ margin-inline-start: 3px !important;
+ margin-inline-end: 2px !important;
+}
+
+#forward-button > .toolbarbutton-icon {
+ margin-inline-start: 2px !important;
+ margin-inline-end: 3px !important;
+}
+
+/* organize button */
+#organizeButton {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+/* view button */
+#viewMenu {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+/* maintenance button */
+#maintenanceButton {
+ -moz-image-region: rect(0px, 80px, 16px, 64px);
+}
+
+/* Root View */
+#placesView {
+ border-top: 1px solid ThreeDDarkShadow;
+ -moz-user-focus: ignore;
+}
+
+/* Place List, Place Content */
+#placesList {
+ -moz-appearance: -moz-mac-source-list;
+ box-shadow: inset -2px 0 0 hsla(0,0%,100%,.2);
+ width: 160px;
+}
+
+@media (-moz-mac-yosemite-theme) {
+ #placesList {
+ box-shadow: none;
+ }
+}
+
+/* Info box */
+#detailsDeck {
+ border-top: 1px solid #919191;
+ background-color: #f0f0f0;
+ padding: 10px;
+}
+
+#placeContent {
+ -moz-appearance: none;
+ border: 0px;
+}
+
+#placeContent > treechildren::-moz-tree-row {
+ border-top: none !important;
+ padding-top: 1px;
+}
+
+#placeContent > treechildren::-moz-tree-row(odd) {
+ background-color: #edf3fe;
+}
+
+#placeContent > treechildren::-moz-tree-row(selected),
+#placeContent > treechildren::-moz-tree-row(odd, selected) {
+ background-color: -moz-mac-secondaryhighlight;
+}
+
+#placeContent > treechildren::-moz-tree-row(selected, focus),
+#placeContent > treechildren::-moz-tree-row(odd, selected, focus) {
+ background-color: Highlight;
+ color: HighlightText !important;
+}
+
+#placeContent > treechildren::-moz-tree-cell,
+#placeContent > treechildren::-moz-tree-column {
+ border-right: 1px solid #d7dad7;
+}
+
+#placeContent > treechildren::-moz-tree-cell(separator) {
+ border-color: transparent;
+}
+
+/**** expanders ****/
+
+.expander-up,
+.expander-down {
+ margin: 0 4px 1px 8px;
+ padding: 0;
+}
+
+.expander-up {
+ -moz-appearance: -moz-mac-disclosure-button-open;
+}
+
+.expander-down {
+ -moz-appearance: -moz-mac-disclosure-button-closed;
+}
+
+/**
+ * info pane
+ */
+
+#infoBoxExpander {
+ margin: 2px;
+}
+
+#infoBoxExpanderLabel {
+ display: none;
+}
+
+#itemsCountText,
+#selectItemDescription {
+ color: GrayText;
+}
+
+.editBMPanel_rowLabel {
+ text-align: end;
+}
+
+/**
+ * Downloads pane
+ */
+
+#clearDownloadsButton {
+ list-style-image: none !important;
+}
+
+#clearDownloadsButton > .toolbarbutton-icon {
+ display: none;
+}
diff --git a/browser/themes/osx/places/places.css b/browser/themes/osx/places/places.css
new file mode 100644
index 000000000..5eaad2791
--- /dev/null
+++ b/browser/themes/osx/places/places.css
@@ -0,0 +1,280 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../shared.inc
+
+/* Sidebars */
+
+#bookmarksPanel,
+#history-panel,
+#sidebar-search-container,
+#tabs-panel {
+ -moz-appearance: none !important;
+ background-color: transparent !important;
+ border-top: none !important;
+}
+
+.sidebar-placesTree,
+.sidebar-placesTreechildren::-moz-tree-row {
+ padding-bottom: 1px;
+ margin: 0;
+ height: 24px;
+ font-size: 12px;
+}
+
+.sidebar-placesTree {
+ -moz-appearance: -moz-mac-source-list;
+}
+
+.sidebar-placesTreechildren {
+ border-top: 1px solid #bebebe;
+}
+
+.sidebar-placesTreechildren::-moz-tree-separator {
+ border-top: 1px solid #505d6d;
+ margin: 0 10px;
+}
+
+.sidebar-placesTreechildren::-moz-tree-row {
+ background-color: transparent;
+}
+
+.sidebar-placesTreechildren::-moz-tree-row(selected) {
+ -moz-appearance: -moz-mac-source-list-selection;
+}
+
+.sidebar-placesTreechildren::-moz-tree-row(selected,focus) {
+ -moz-appearance: -moz-mac-active-source-list-selection;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text {
+ margin-inline-end: 6px;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
+ color: #fff;
+ font-weight: bold;
+}
+
+#sidebar-search-label {
+ display: none;
+}
+
+.sidebar-placesTreechildren::-moz-tree-twisty {
+ -moz-appearance: none;
+ padding: 0 2px;
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+}
+
+.sidebar-placesTreechildren::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+}
+
+.sidebar-placesTreechildren::-moz-tree-twisty(open) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+}
+
+.sidebar-placesTreechildren::-moz-tree-twisty(open, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+}
+
+.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
+}
+
+.sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
+}
+
+@media (-moz-mac-yosemite-theme) {
+ .sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
+ color: -moz-dialogtext;
+ font-weight: 500;
+ }
+
+ .sidebar-placesTreechildren::-moz-tree-cell-text(selected, focus) {
+ color: #fff;
+ }
+
+ .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+ }
+
+ .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected, focus) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+ }
+
+ .sidebar-placesTreechildren::-moz-tree-twisty(open, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+ }
+
+ .sidebar-placesTreechildren::-moz-tree-twisty(open, selected, focus) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+ }
+
+ .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
+ }
+
+ .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected, focus) {
+ list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl")
+ }
+}
+
+#viewButton {
+ -moz-appearance: none;
+ padding-bottom: 1px;
+ padding-inline-start: 5px;
+ padding-inline-end: 0px;
+ margin: 0;
+ margin-inline-end: 4px;
+ min-width: 0px;
+ min-height: 0px;
+ border: 1px solid #a2a9b1;
+ border-radius: 10px;
+ background-image: linear-gradient(hsla(0,0%,100%,.75),hsla(0,0%,100%,.1));
+ box-shadow: inset 0 0 1px hsla(0,0%,100%,.85),
+ 0 1px hsla(0,0%,100%,.35);
+}
+
+#viewButton:hover:active,
+#viewButton[open=true] {
+ background-image: linear-gradient(hsla(0,0%,100%,.1),hsla(0,0%,100%,.75));
+ box-shadow: @roundButtonPressedShadow@;
+ color: -moz-dialogText;
+}
+
+#viewButton:-moz-window-inactive {
+ border-color: #b6b6b6;
+ background-image: linear-gradient(hsla(0,0%,100%,.3),hsla(0,0%,100%,0));
+}
+
+#viewButton .button-menu-dropmarker {
+ display: -moz-box;
+ list-style-image: url("chrome://global/skin/icons/menulist-dropmarker.png");
+}
+
+#viewButton:focus {
+ box-shadow: 0 1px 0 hsla(0, 0%, 0%, .15),
+ 0 0 0 1px hsla(210, 100%, 60%, .45) inset,
+ 0 0 0 2px hsla(210, 100%, 60%, .45);
+ border-color: hsla(210, 100%, 60%, 1);
+}
+
+#sidebar-search-container {
+ margin: 0 4px 6px;
+}
+
+/* Trees */
+
+treechildren::-moz-tree-image(title) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ padding-inline-end: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(title, livemarkItem) {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, container),
+treechildren::-moz-tree-image(title, open) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+treechildren::-moz-tree-image(title, separator) {
+ list-style-image: none;
+ width: 0 !important;
+ height: 0 !important;
+ margin: 0;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
+}
+
+treechildren::-moz-tree-image(container, livemark) {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png");
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.png");
+}
+
+/* query-nodes should be styled even if they're not expandable */
+treechildren::-moz-tree-image(query) {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+}
+
+treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
+ list-style-image: url("chrome://browser/skin/places/downloads.png");
+}
+
+treechildren::-moz-tree-image(title, query, tagContainer),
+treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer, open) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+treechildren::-moz-tree-image(query, OrganizerQuery_History) {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+/* We want some queries to look like ordinary folders. This must come
+ after the (title, query) selector, or it would get overridden. */
+treechildren::-moz-tree-image(title, query, folder),
+treechildren::-moz-tree-image(title, query, folder, open) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+treechildren::-moz-tree-cell-text(title, separator) {
+ color: ThreeDShadow;
+ margin: 0px 5px;
+}
+
+treechildren::-moz-tree-cell-text(title, separator, selected, focus) {
+ color: HighlightText;
+}
+
+treechildren::-moz-tree-twisty(title, separator) {
+ -moz-appearance: none;
+ padding: 0px;
+}
+
+treechildren::-moz-tree-image(cutting) {
+ opacity: 0.5;
+}
+
+treechildren::-moz-tree-cell-text(cutting) {
+ opacity: 0.7;
+}
diff --git a/browser/themes/osx/places/plus-active.png b/browser/themes/osx/places/plus-active.png
new file mode 100644
index 000000000..2d1dd11be
--- /dev/null
+++ b/browser/themes/osx/places/plus-active.png
Binary files differ
diff --git a/browser/themes/osx/places/plus.png b/browser/themes/osx/places/plus.png
new file mode 100644
index 000000000..c1cc60688
--- /dev/null
+++ b/browser/themes/osx/places/plus.png
Binary files differ
diff --git a/browser/themes/osx/places/query.png b/browser/themes/osx/places/query.png
new file mode 100644
index 000000000..0ccb84702
--- /dev/null
+++ b/browser/themes/osx/places/query.png
Binary files differ
diff --git a/browser/themes/osx/places/query@2x.png b/browser/themes/osx/places/query@2x.png
new file mode 100644
index 000000000..20b458aac
--- /dev/null
+++ b/browser/themes/osx/places/query@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/starred48.png b/browser/themes/osx/places/starred48.png
new file mode 100644
index 000000000..bdcc7e757
--- /dev/null
+++ b/browser/themes/osx/places/starred48.png
Binary files differ
diff --git a/browser/themes/osx/places/starred48@2x.png b/browser/themes/osx/places/starred48@2x.png
new file mode 100644
index 000000000..0ed76af19
--- /dev/null
+++ b/browser/themes/osx/places/starred48@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/tag.png b/browser/themes/osx/places/tag.png
new file mode 100644
index 000000000..a4038bb4f
--- /dev/null
+++ b/browser/themes/osx/places/tag.png
Binary files differ
diff --git a/browser/themes/osx/places/tag@2x.png b/browser/themes/osx/places/tag@2x.png
new file mode 100644
index 000000000..673814bb5
--- /dev/null
+++ b/browser/themes/osx/places/tag@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/toolbar-lion.png b/browser/themes/osx/places/toolbar-lion.png
new file mode 100644
index 000000000..ac3358c91
--- /dev/null
+++ b/browser/themes/osx/places/toolbar-lion.png
Binary files differ
diff --git a/browser/themes/osx/places/toolbar.png b/browser/themes/osx/places/toolbar.png
new file mode 100644
index 000000000..168daf9ce
--- /dev/null
+++ b/browser/themes/osx/places/toolbar.png
Binary files differ
diff --git a/browser/themes/osx/places/toolbarDropMarker.png b/browser/themes/osx/places/toolbarDropMarker.png
new file mode 100644
index 000000000..a217b0ed8
--- /dev/null
+++ b/browser/themes/osx/places/toolbarDropMarker.png
Binary files differ
diff --git a/browser/themes/osx/places/unfiledBookmarks.png b/browser/themes/osx/places/unfiledBookmarks.png
new file mode 100644
index 000000000..69495dac0
--- /dev/null
+++ b/browser/themes/osx/places/unfiledBookmarks.png
Binary files differ
diff --git a/browser/themes/osx/places/unfiledBookmarks@2x.png b/browser/themes/osx/places/unfiledBookmarks@2x.png
new file mode 100644
index 000000000..44efd6aba
--- /dev/null
+++ b/browser/themes/osx/places/unfiledBookmarks@2x.png
Binary files differ
diff --git a/browser/themes/osx/places/unstarred48.png b/browser/themes/osx/places/unstarred48.png
new file mode 100644
index 000000000..8b82aab4b
--- /dev/null
+++ b/browser/themes/osx/places/unstarred48.png
Binary files differ
diff --git a/browser/themes/osx/preferences/alwaysAsk.png b/browser/themes/osx/preferences/alwaysAsk.png
new file mode 100644
index 000000000..c792d1488
--- /dev/null
+++ b/browser/themes/osx/preferences/alwaysAsk.png
Binary files differ
diff --git a/browser/themes/osx/preferences/application.png b/browser/themes/osx/preferences/application.png
new file mode 100644
index 000000000..b4c1ca7d0
--- /dev/null
+++ b/browser/themes/osx/preferences/application.png
Binary files differ
diff --git a/browser/themes/osx/preferences/applications.css b/browser/themes/osx/preferences/applications.css
new file mode 100644
index 000000000..8a4a01500
--- /dev/null
+++ b/browser/themes/osx/preferences/applications.css
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Line up the actions menu with action labels above and below it.
+ * Equalize the distance from the left side of the action box to the left side
+ * of the icon for both the menu and the non-menu versions of the action box.
+ * Also make sure the labels are the same distance away from the icons.
+ */
+.actionsMenu {
+ margin-inline-start: -2px;
+ margin-top: 0;
+ margin-bottom: -1px;
+}
+
+#handlersView > richlistitem label {
+ margin-inline-start: 3px;
+ margin-top: 2px;
+}
+
+#handlersView > richlistitem {
+ min-height: 22px;
+}
+
+.typeIcon,
+.actionIcon {
+ margin-inline-start: 3px;
+}
+
+.typeIcon,
+.actionIcon,
+.actionsMenu .menulist-icon {
+ margin-inline-end: 2px;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ /* Undo content/browser/preferences/handlers.css - we don't
+ * want icon-less labels to line up with the other labels.
+ */
+ min-width: 0;
+}
+
+richlistitem[appHandlerIcon="ask"],
+menuitem[appHandlerIcon="ask"] {
+ list-style-image: url("chrome://browser/skin/preferences/alwaysAsk.png");
+}
+
+richlistitem[appHandlerIcon="save"],
+menuitem[appHandlerIcon="save"] {
+ list-style-image: url("chrome://browser/skin/preferences/saveFile.png");
+}
+
+richlistitem[appHandlerIcon="feed"],
+menuitem[appHandlerIcon="feed"] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+richlistitem[appHandlerIcon="plugin"],
+menuitem[appHandlerIcon="plugin"] {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
+}
+
+/* Repeat what menu.css does for .menuitem-iconic */
+menuitem[appHandlerIcon] {
+ padding-top: 1px;
+ padding-bottom: 3px;
+}
+
+menuitem[appHandlerIcon] > .menu-iconic-left > .menu-iconic-icon {
+ margin-inline-start: 0;
+ width: 16px;
+}
diff --git a/browser/themes/osx/preferences/in-content/dialog.css b/browser/themes/osx/preferences/in-content/dialog.css
new file mode 100644
index 000000000..46a6cb91c
--- /dev/null
+++ b/browser/themes/osx/preferences/in-content/dialog.css
@@ -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 ../../../shared/incontentprefs/dialog.inc.css
+
+prefwindow,
+.windowDialog {
+ font: message-box !important;
+}
+
+label:not(.menu-text),
+textbox,
+description,
+.tab-text,
+caption > label {
+ font-size: 1.3em;
+}
+
+/* Create a separate rule to unset these styles on .tree-input instead of
+ using :not(.tree-input) so the selector specifity doesn't change. */
+textbox.tree-input {
+ font-size: unset;
+}
+
+button {
+ font-size: 1em;
+}
+
+caption {
+ font: message-box;
+}
+
+.prefWindow-dlgbuttons {
+ margin: 0;
+}
diff --git a/browser/themes/osx/preferences/in-content/preferences.css b/browser/themes/osx/preferences/in-content/preferences.css
new file mode 100644
index 000000000..c59b08283
--- /dev/null
+++ b/browser/themes/osx/preferences/in-content/preferences.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/. */
+
+%include ../../../shared/incontentprefs/preferences.inc.css
+
+prefpane .groupbox-title {
+ background: none;
+ margin-bottom: 0;
+}
+
+.actionsMenu > .menulist-label-box > .menulist-icon {
+ margin-top: 2px;
+ margin-inline-start: 2px;
+ margin-inline-end: 8px !important;
+}
+
+#downloadFolder > .fileFieldContentBox {
+ padding-inline-start: 3px;
+}
+
+#fxaProfileImage {
+ -moz-user-focus: normal;
+}
+
+textbox + button {
+ margin-inline-start: -4px;
+}
+
+filefield + button {
+ margin-inline-start: -8px;
+}
+
+#popupPolicyRow {
+ /* Override styles from
+ browser/themes/osx/preferences/preferences.css */
+ margin-bottom: 0 !important;
+ padding-bottom: 0 !important;
+ border-bottom: none;
+}
+
+#advancedPrefs {
+ margin-right: 0; /*override margin from normal preferences.css */
+ margin-left: 0;
+}
+
+/**
+ * Dialog
+ */
+
+#dialogTitle {
+ font-size: 1.1em;
+}
diff --git a/browser/themes/osx/preferences/preferences.css b/browser/themes/osx/preferences/preferences.css
new file mode 100644
index 000000000..da0e66b6e
--- /dev/null
+++ b/browser/themes/osx/preferences/preferences.css
@@ -0,0 +1,131 @@
+/*
+# -*- 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/.
+*/
+
+%include ../../../../toolkit/themes/osx/global/shared.inc
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+.windowDialog {
+ padding: 12px;
+ font: -moz-dialog;
+}
+
+/* ----- APPLICATIONS PREFPANE ----- */
+description {
+ font: small-caption;
+ font-weight: normal;
+ line-height: 1.3em;
+ margin-bottom: 4px !important;
+}
+
+prefpane .groupbox-body {
+ -moz-appearance: none;
+ padding: 8px 4px 4px 4px;
+}
+
+prefpane .groupbox-title {
+ background: url("chrome://global/skin/50pct_transparent_grey.png") repeat-x bottom left;
+ margin-bottom: 4px;
+}
+
+tabpanels {
+ padding: 20px 7px 7px;
+}
+
+caption {
+ padding-inline-start: 5px;
+ padding-top: 4px;
+ padding-bottom: 2px;
+}
+
+#popupPolicyRow {
+ margin-bottom: 4px !important;
+ padding-bottom: 4px !important;
+ border-bottom: 1px solid #ccc;
+}
+
+#translationAttributionImage {
+ width: 70px;
+ cursor: pointer;
+}
+
+#browserUseCurrent,
+#browserUseBookmark,
+#browserUseBlank {
+ margin-top: 10px;
+}
+
+#advancedPrefs {
+ margin: 0 8px;
+}
+
+#privacyPrefs {
+ padding: 0 4px;
+}
+
+#privacyPrefs > tabpanels {
+ padding: 18px 10px 10px;
+}
+
+#OCSPDialogPane {
+ font: message-box !important;
+}
+
+/* General Pane */
+
+#useFirefoxSync,
+#getStarted {
+ font-size: 90%;
+}
+
+#isNotDefaultLabel {
+ font-weight: bold;
+}
+
+/**
+ * Update Preferences
+ */
+#autoInstallOptions {
+ margin-inline-start: 20px;
+}
+
+.updateControls {
+ margin-inline-start: 10px;
+}
+
+/**
+ * Clear Private Data
+ */
+#SanitizeDialogPane > groupbox {
+ margin-top: 0;
+}
+
+
+/* ----- SYNC PANE ----- */
+
+#syncDesc {
+ padding: 0 8em;
+}
+
+#accountCaptionImage {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
+}
+
+#syncAddDeviceLabel {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+#noFxaAccount {
+ margin: 12px 4px;
+ line-height: 1.2em;
+}
+
+#noFxaAccount > label:first-child {
+ margin-bottom: 0.6em;
+}
diff --git a/browser/themes/osx/preferences/saveFile.png b/browser/themes/osx/preferences/saveFile.png
new file mode 100644
index 000000000..7177f8df3
--- /dev/null
+++ b/browser/themes/osx/preferences/saveFile.png
Binary files differ
diff --git a/browser/themes/osx/privatebrowsing-mask-short.png b/browser/themes/osx/privatebrowsing-mask-short.png
new file mode 100644
index 000000000..92f60e29f
--- /dev/null
+++ b/browser/themes/osx/privatebrowsing-mask-short.png
Binary files differ
diff --git a/browser/themes/osx/privatebrowsing-mask-short@2x.png b/browser/themes/osx/privatebrowsing-mask-short@2x.png
new file mode 100644
index 000000000..ec1cf7452
--- /dev/null
+++ b/browser/themes/osx/privatebrowsing-mask-short@2x.png
Binary files differ
diff --git a/browser/themes/osx/privatebrowsing-mask.png b/browser/themes/osx/privatebrowsing-mask.png
new file mode 100644
index 000000000..586d3d9f0
--- /dev/null
+++ b/browser/themes/osx/privatebrowsing-mask.png
Binary files differ
diff --git a/browser/themes/osx/privatebrowsing-mask@2x.png b/browser/themes/osx/privatebrowsing-mask@2x.png
new file mode 100644
index 000000000..efb6d9b80
--- /dev/null
+++ b/browser/themes/osx/privatebrowsing-mask@2x.png
Binary files differ
diff --git a/browser/themes/osx/reload-stop-go-yosemite.png b/browser/themes/osx/reload-stop-go-yosemite.png
new file mode 100644
index 000000000..5efbf0b94
--- /dev/null
+++ b/browser/themes/osx/reload-stop-go-yosemite.png
Binary files differ
diff --git a/browser/themes/osx/reload-stop-go-yosemite@2x.png b/browser/themes/osx/reload-stop-go-yosemite@2x.png
new file mode 100644
index 000000000..b681f8930
--- /dev/null
+++ b/browser/themes/osx/reload-stop-go-yosemite@2x.png
Binary files differ
diff --git a/browser/themes/osx/reload-stop-go.png b/browser/themes/osx/reload-stop-go.png
new file mode 100644
index 000000000..699ef2326
--- /dev/null
+++ b/browser/themes/osx/reload-stop-go.png
Binary files differ
diff --git a/browser/themes/osx/reload-stop-go@2x.png b/browser/themes/osx/reload-stop-go@2x.png
new file mode 100644
index 000000000..704c7a894
--- /dev/null
+++ b/browser/themes/osx/reload-stop-go@2x.png
Binary files differ
diff --git a/browser/themes/osx/sanitizeDialog.css b/browser/themes/osx/sanitizeDialog.css
new file mode 100644
index 000000000..14a6ce527
--- /dev/null
+++ b/browser/themes/osx/sanitizeDialog.css
@@ -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/. */
+
+/* Align the duration label with the warning box and item list */
+#sanitizeDurationLabel {
+ margin-inline-start: 1px;
+}
+
+
+/* Hide the duration dropdown suffix label if it's empty. Otherwise it
+ takes up a little space, causing the end of the dropdown to not be aligned
+ with the warning box. */
+#sanitizeDurationSuffixLabel[value=""] {
+ display: none;
+}
+
+
+/* Places tree */
+#placesTreechildren::-moz-tree-row(selected),
+#placesTreechildren::-moz-tree-row(grippyRow) {
+ background: #999;
+}
+
+#placesTreechildren::-moz-tree-cell-text(selected) {
+ color: #111;
+}
+
+
+/* Sanitize everything warning box */
+#sanitizeEverythingWarningBox {
+ background-color: Window;
+ border: 1px solid ThreeDDarkShadow;
+ border-radius: 5px;
+ padding: 16px;
+}
+
+#sanitizeEverythingWarningIcon {
+ list-style-image: url("chrome://global/skin/icons/warning-large.png");
+ padding: 0;
+ margin: 0;
+}
+
+#sanitizeEverythingWarningDescBox {
+ padding: 0 16px;
+ margin: 0;
+}
+
+
+/* Progressive disclosure button */
+#detailsExpanderWrapper {
+ padding: 0;
+ margin-top: 6px;
+ margin-bottom: 6px;
+}
+
+.expander-up,
+.expander-down {
+ padding: 0;
+ margin: 0;
+}
+
+.expander-up {
+ -moz-appearance: -moz-mac-disclosure-button-open;
+}
+
+.expander-down {
+ -moz-appearance: -moz-mac-disclosure-button-closed;
+}
+
+/* Make the item list the same width as the warning box */
+#itemList {
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+}
+
+/* Without this a useless scrollbar appears in the listbox when its rows
+ attribute is set to the total number of listitems, as it is currently. See
+ bug 489958 comment 14 and bug 491788. */
+#itemList > listitem {
+ padding: 1px 0;
+}
+
+
+/* Align the last dialog button with the end of the warning box */
+.prefWindow-dlgbuttons {
+ margin-inline-end: 0;
+}
+.dialog-button[dlgtype="accept"] {
+ margin-inline-end: 0;
+}
diff --git a/browser/themes/osx/searchbar.css b/browser/themes/osx/searchbar.css
new file mode 100644
index 000000000..4b6aeeb21
--- /dev/null
+++ b/browser/themes/osx/searchbar.css
@@ -0,0 +1,318 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#PopupSearchAutoComplete {
+ /* JS code forces the panel to have the width of the searchbar rather than
+ * the width of the textfield. Alignment of the panel with the searchbar is
+ * obtained with negative margins here: margin-inline-start when the text
+ * field is in the same direction as the rest of the UI, margin-inline-end
+ * when the textfield's direction has been reversed.
+ * (eg. using command+shift+X) */
+ margin-inline-start: -23px;
+ margin-inline-end: -21px;
+}
+
+.searchbar-textbox {
+ border-radius: 10000px;
+}
+
+.searchbar-popup {
+ margin-top: 4px;
+ margin-inline-start: 3px;
+}
+
+.searchbar-textbox > .autocomplete-textbox-container > .textbox-input-box {
+ margin: 0;
+ padding: 3px 0 2px;
+ height: 20px;
+}
+
+.searchbar-engine-image {
+ width: 16px;
+ height: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.search-go-container {
+ -moz-box-align: center;
+ padding-inline-end: 6px;
+}
+
+.searchbar-search-button-container {
+ -moz-box-align: center;
+}
+
+.search-go-button {
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+}
+
+.search-go-button:hover:active {
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+.search-go-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.searchbar-search-button {
+ list-style-image: url("chrome://browser/skin/search-indicator.png");
+ -moz-image-region: rect(0, 20px, 20px, 0);
+ margin-inline-start: 3px;
+ margin-inline-end: 1px;
+}
+
+.searchbar-search-button[addengines="true"] {
+ list-style-image: url("chrome://browser/skin/search-indicator-badge-add.png");
+}
+
+.searchbar-search-button:hover {
+ -moz-image-region: rect(0, 40px, 20px, 20px);
+}
+
+.searchbar-search-button:hover:active {
+ -moz-image-region: rect(0, 60px, 20px, 40px);
+}
+
+@media (min-resolution: 2dppx) {
+ .searchbar-engine-image {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+
+ .search-go-button {
+ list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
+ -moz-image-region: rect(0, 84px, 28px, 56px);
+ width: 14px;
+ }
+
+ .search-go-button:hover:active {
+ list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
+ -moz-image-region: rect(28px, 84px, 56px, 56px);
+ width: 14px;
+ }
+
+ .searchbar-search-button {
+ list-style-image: url("chrome://browser/skin/search-indicator@2x.png");
+ width: 20px;
+ -moz-image-region: rect(0, 40px, 40px, 0);
+ }
+
+ .searchbar-search-button[addengines="true"] {
+ list-style-image: url("chrome://browser/skin/search-indicator-badge-add@2x.png");
+ }
+
+ .searchbar-search-button:hover {
+ -moz-image-region: rect(0, 80px, 40px, 40px);
+ }
+
+ .searchbar-search-button:hover:active {
+ -moz-image-region: rect(0, 120px, 40px, 80px);
+ }
+}
+
+.search-panel-current-engine {
+ border-radius: 4px 4px 0 0;
+}
+
+/**
+ * The borders of the various elements are specified as follows.
+ *
+ * The current engine always has a bottom border.
+ * The search results never have a border.
+ *
+ * When the search results are not collapsed:
+ * - The elements underneath the search results all have a top border.
+ *
+ * When the search results are collapsed:
+ * - The elements underneath the search results all have a bottom border, except
+ * the lowest one: search-setting-button.
+ */
+
+.search-panel-current-engine {
+ border-top: none !important;
+ border-bottom: 1px solid var(--panel-separator-color);
+}
+
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-header,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-one-offs,
+.search-panel-tree[collapsed=true] + .search-one-offs > vbox > .addengine-item:first-of-type {
+ border-top: none;
+}
+
+.search-panel-tree[collapsed=true] + .search-one-offs > .searchbar-engine-one-off-item,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-current-input,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-one-offs,
+.search-panel-tree[collapsed=true] + .search-one-offs > vbox > .addengine-item:last-of-type {
+ border-bottom: 1px solid var(--panel-separator-color);
+}
+
+.search-panel-header {
+ font-size: 10px;
+ font-weight: normal;
+ background-color: var(--arrowpanel-dimmed);
+ border-top: 1px solid var(--panel-separator-color);
+ margin: 0;
+ padding: 3px 6px;
+ color: GrayText;
+}
+
+.search-panel-header > label {
+ margin-top: 2px !important;
+ margin-bottom: 2px !important;
+}
+
+.search-panel-current-input > label {
+ margin: 2px 0 !important;
+}
+
+.search-panel-input-value {
+ color: -moz-fieldtext;
+}
+
+.search-panel-one-offs {
+ margin: 0 !important;
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+.searchbar-engine-one-off-item {
+ -moz-appearance: none;
+ display: inline-block;
+ min-width: 48px;
+ height: 32px;
+ margin: 0;
+ padding: 0;
+ background: linear-gradient(transparent 15%, var(--panel-separator-color) 15%, var(--panel-separator-color) 85%, transparent 85%);
+ background-size: 1px auto;
+ background-repeat: no-repeat;
+ background-position: right center;
+ color: GrayText;
+}
+
+.searchbar-engine-one-off-item:-moz-locale-dir(rtl) {
+ background-position: left center;
+}
+
+.searchbar-engine-one-off-item:not(.last-row) {
+ box-sizing: content-box;
+ border-bottom: 1px solid var(--panel-separator-color);
+}
+
+.search-setting-button-compact {
+ border-bottom: none !important;
+}
+
+.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
+.search-setting-button-compact {
+ background-image: none;
+}
+
+.searchbar-engine-one-off-item[selected] {
+ background-color: Highlight;
+ background-image: none;
+ color: HighlightText;
+}
+
+.searchbar-engine-one-off-item > .button-box > .button-text {
+ display: none;
+}
+
+.searchbar-engine-one-off-item > .button-box > .button-icon {
+ margin-inline-start: 0;
+ width: 16px;
+ height: 16px;
+}
+
+.addengine-item {
+ -moz-appearance: none;
+ font-size: 10px;
+ height: 32px;
+ margin: 0;
+ padding: 0 10px;
+}
+
+.addengine-item > .button-box {
+ -moz-box-pack: start;
+}
+
+.addengine-item:first-of-type {
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+.addengine-item[selected] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.addengine-icon {
+ width: 16px;
+}
+
+.addengine-badge {
+ width: 16px;
+ height: 16px;
+ margin: -7px -9px 7px 9px;
+ list-style-image: url("chrome://browser/skin/badge-add-engine.png");
+}
+
+.addengine-item > .button-box > .button-text {
+ -moz-box-flex: 1;
+ text-align: start;
+ padding-inline-start: 10px;
+}
+
+.addengine-item:not([image]) {
+ list-style-image: url("chrome://browser/skin/search-engine-placeholder.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .addengine-badge {
+ list-style-image: url("chrome://browser/skin/badge-add-engine@2x.png");
+ }
+
+ .addengine-item:not([image]) {
+ list-style-image: url("chrome://browser/skin/search-engine-placeholder@2x.png");
+ }
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-cell {
+ border-top: none !important;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image {
+ padding-inline-start: 4px;
+ padding-inline-end: 2px;
+ width: 14px;
+ height: 14px;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image(fromhistory) {
+ list-style-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon");
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image(fromhistory, selected) {
+ list-style-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active");
+}
+
+#PopupSearchAutoComplete {
+ border-radius: 4px;
+}
+
+.search-setting-button {
+ -moz-appearance: none;
+ border-radius: 0 0 4px 4px;
+ min-height: 32px;
+}
+
+.search-setting-button[selected] {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+.search-setting-button-compact > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/gear.svg");
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
diff --git a/browser/themes/osx/setDesktopBackground.css b/browser/themes/osx/setDesktopBackground.css
new file mode 100644
index 000000000..f9b16ec2b
--- /dev/null
+++ b/browser/themes/osx/setDesktopBackground.css
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|canvas#screen {
+ margin: 12px 11px 38px;
+}
+
+#monitor {
+ list-style-image: url("chrome://browser/skin/monitor.png");
+}
+
+#monitor[aspectratio="16:10"] {
+ list-style-image: url("chrome://browser/skin/monitor_16-10.png");
+}
diff --git a/browser/themes/osx/shared.inc b/browser/themes/osx/shared.inc
new file mode 100644
index 000000000..b3ea4e199
--- /dev/null
+++ b/browser/themes/osx/shared.inc
@@ -0,0 +1,13 @@
+%include ../../../toolkit/themes/osx/global/shared.inc
+%include ../shared/browser.inc
+
+%filter substitution
+
+%define fgTabTexture linear-gradient(transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,93%))
+%define fgTabTextureYosemiteInactive linear-gradient(transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%))
+%define toolbarColorLWT rgba(253,253,253,0.45)
+%define fgTabTextureLWT linear-gradient(transparent 2px, rgba(254,254,254,.72) 2px, @toolbarColorLWT@)
+%define fgTabBackgroundColor transparent
+%define hudButton -moz-appearance: none; color: #434343; border-radius: 4px; border: 1px solid #b5b5b5; background: linear-gradient(#fff, #f2f2f2); box-shadow: inset 0 1px rgba(255,255,255,.8), inset 0 0 1px rgba(255,255, 255,.25), 0 1px rgba(255,255,255,.3); background-clip: padding-box; background-origin: padding-box; padding: 2px 6px;
+%define hudButtonPressed box-shadow: inset 0 1px 4px -3px #000, 0 1px rgba(255,255,255,.3);
+%define hudButtonFocused box-shadow: 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 2px 1px -moz-mac-focusring;
diff --git a/browser/themes/osx/slowStartup-16.png b/browser/themes/osx/slowStartup-16.png
new file mode 100644
index 000000000..439bec340
--- /dev/null
+++ b/browser/themes/osx/slowStartup-16.png
Binary files differ
diff --git a/browser/themes/osx/social/services-16.png b/browser/themes/osx/social/services-16.png
new file mode 100644
index 000000000..7001ea1f0
--- /dev/null
+++ b/browser/themes/osx/social/services-16.png
Binary files differ
diff --git a/browser/themes/osx/social/services-16@2x.png b/browser/themes/osx/social/services-16@2x.png
new file mode 100644
index 000000000..019110ce2
--- /dev/null
+++ b/browser/themes/osx/social/services-16@2x.png
Binary files differ
diff --git a/browser/themes/osx/social/services-64.png b/browser/themes/osx/social/services-64.png
new file mode 100644
index 000000000..e787bddc3
--- /dev/null
+++ b/browser/themes/osx/social/services-64.png
Binary files differ
diff --git a/browser/themes/osx/social/services-64@2x.png b/browser/themes/osx/social/services-64@2x.png
new file mode 100644
index 000000000..e252b80bc
--- /dev/null
+++ b/browser/themes/osx/social/services-64@2x.png
Binary files differ
diff --git a/browser/themes/osx/subtle-pattern.png b/browser/themes/osx/subtle-pattern.png
new file mode 100644
index 000000000..701239ec0
--- /dev/null
+++ b/browser/themes/osx/subtle-pattern.png
Binary files differ
diff --git a/browser/themes/osx/sync-128.png b/browser/themes/osx/sync-128.png
new file mode 100644
index 000000000..1ea34818c
--- /dev/null
+++ b/browser/themes/osx/sync-128.png
Binary files differ
diff --git a/browser/themes/osx/sync-16.png b/browser/themes/osx/sync-16.png
new file mode 100644
index 000000000..0afb1c719
--- /dev/null
+++ b/browser/themes/osx/sync-16.png
Binary files differ
diff --git a/browser/themes/osx/sync-32.png b/browser/themes/osx/sync-32.png
new file mode 100644
index 000000000..7a762cb98
--- /dev/null
+++ b/browser/themes/osx/sync-32.png
Binary files differ
diff --git a/browser/themes/osx/sync-bg.png b/browser/themes/osx/sync-bg.png
new file mode 100644
index 000000000..893a27d76
--- /dev/null
+++ b/browser/themes/osx/sync-bg.png
Binary files differ
diff --git a/browser/themes/osx/sync-horizontalbar-yosemite.png b/browser/themes/osx/sync-horizontalbar-yosemite.png
new file mode 100644
index 000000000..43e911462
--- /dev/null
+++ b/browser/themes/osx/sync-horizontalbar-yosemite.png
Binary files differ
diff --git a/browser/themes/osx/sync-horizontalbar-yosemite@2x.png b/browser/themes/osx/sync-horizontalbar-yosemite@2x.png
new file mode 100644
index 000000000..c9472b70e
--- /dev/null
+++ b/browser/themes/osx/sync-horizontalbar-yosemite@2x.png
Binary files differ
diff --git a/browser/themes/osx/sync-horizontalbar.png b/browser/themes/osx/sync-horizontalbar.png
new file mode 100644
index 000000000..4223d3bcb
--- /dev/null
+++ b/browser/themes/osx/sync-horizontalbar.png
Binary files differ
diff --git a/browser/themes/osx/sync-horizontalbar@2x.png b/browser/themes/osx/sync-horizontalbar@2x.png
new file mode 100644
index 000000000..fdc126681
--- /dev/null
+++ b/browser/themes/osx/sync-horizontalbar@2x.png
Binary files differ
diff --git a/browser/themes/osx/sync-notification-24.png b/browser/themes/osx/sync-notification-24.png
new file mode 100644
index 000000000..8d2ec8fa8
--- /dev/null
+++ b/browser/themes/osx/sync-notification-24.png
Binary files differ
diff --git a/browser/themes/osx/syncCommon.css b/browser/themes/osx/syncCommon.css
new file mode 100644
index 000000000..2d39aa179
--- /dev/null
+++ b/browser/themes/osx/syncCommon.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/. */
+
+/* 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.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .statusIcon[status="active"] {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ }
+}
+
+.statusIcon[status="error"] {
+ list-style-image: url("chrome://global/skin/icons/error-16.png");
+}
+
+.statusIcon[status="success"] {
+ list-style-image: url("chrome://global/skin/icons/information-16.png");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+ a separate stylesheet for it. */
+.data {
+ font-size: 90%;
+ font-weight: bold;
+}
+
+dialog#change-dialog {
+ width: 40em;
+}
+
+image#syncIcon {
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+ margin-top: 2px;
+}
+
+#feedback {
+ height: 2em;
+}
diff --git a/browser/themes/osx/syncProgress-horizontalbar.png b/browser/themes/osx/syncProgress-horizontalbar.png
new file mode 100644
index 000000000..48cd11055
--- /dev/null
+++ b/browser/themes/osx/syncProgress-horizontalbar.png
Binary files differ
diff --git a/browser/themes/osx/syncProgress-horizontalbar@2x.png b/browser/themes/osx/syncProgress-horizontalbar@2x.png
new file mode 100644
index 000000000..741dd2ed4
--- /dev/null
+++ b/browser/themes/osx/syncProgress-horizontalbar@2x.png
Binary files differ
diff --git a/browser/themes/osx/syncProgress-menuPanel.png b/browser/themes/osx/syncProgress-menuPanel.png
new file mode 100644
index 000000000..6fd6f9c16
--- /dev/null
+++ b/browser/themes/osx/syncProgress-menuPanel.png
Binary files differ
diff --git a/browser/themes/osx/syncProgress-menuPanel@2x.png b/browser/themes/osx/syncProgress-menuPanel@2x.png
new file mode 100644
index 000000000..04b2cae00
--- /dev/null
+++ b/browser/themes/osx/syncProgress-menuPanel@2x.png
Binary files differ
diff --git a/browser/themes/osx/syncProgress-toolbar-inverted.png b/browser/themes/osx/syncProgress-toolbar-inverted.png
new file mode 100644
index 000000000..42f3da363
--- /dev/null
+++ b/browser/themes/osx/syncProgress-toolbar-inverted.png
Binary files differ
diff --git a/browser/themes/osx/syncProgress-toolbar-inverted@2x.png b/browser/themes/osx/syncProgress-toolbar-inverted@2x.png
new file mode 100644
index 000000000..eee4a5dd0
--- /dev/null
+++ b/browser/themes/osx/syncProgress-toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/themes/osx/syncProgress-toolbar.png b/browser/themes/osx/syncProgress-toolbar.png
new file mode 100644
index 000000000..49e224f0d
--- /dev/null
+++ b/browser/themes/osx/syncProgress-toolbar.png
Binary files differ
diff --git a/browser/themes/osx/syncProgress-toolbar@2x.png b/browser/themes/osx/syncProgress-toolbar@2x.png
new file mode 100644
index 000000000..fd2038725
--- /dev/null
+++ b/browser/themes/osx/syncProgress-toolbar@2x.png
Binary files differ
diff --git a/browser/themes/osx/syncQuota.css b/browser/themes/osx/syncQuota.css
new file mode 100644
index 000000000..1577de8a3
--- /dev/null
+++ b/browser/themes/osx/syncQuota.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#quotaDialog {
+ width: 33em;
+ height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+ list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+ height: 4em;
+}
+
+.captionWarning {
+ font-weight: bold;
+}
diff --git a/browser/themes/osx/syncSetup.css b/browser/themes/osx/syncSetup.css
new file mode 100644
index 000000000..0861df831
--- /dev/null
+++ b/browser/themes/osx/syncSetup.css
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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,
+#login-throbber {
+ -moz-box-align: center;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+ width: 16px;
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+@media (min-resolution: 2dppx) {
+ #pairDeviceThrobber > image,
+ #login-throbber > image {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ }
+}
+
+#captchaFeedback {
+ visibility: hidden;
+}
+
+#successPageIcon {
+ /* TODO replace this with a 128px version (bug 591122) */
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
diff --git a/browser/themes/osx/syncedtabs/sidebar.css b/browser/themes/osx/syncedtabs/sidebar.css
new file mode 100644
index 000000000..4d1de766c
--- /dev/null
+++ b/browser/themes/osx/syncedtabs/sidebar.css
@@ -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/. */
+
+%include ../../shared/syncedtabs/sidebar.inc.css
+
+/* These styles are intended to mimic XUL trees and the XUL search box. */
+
+.content-container {
+ -moz-appearance: -moz-mac-source-list;
+}
+
+.item {
+ color: -moz-DialogText;
+}
+
+.item-title-container {
+ box-sizing: border-box;
+ align-items: center;
+ height: 24px;
+ font-size: 12px;
+}
+
+.item.selected > .item-title-container {
+ color: HighlightText;
+ font-weight: bold;
+}
+
+.item.selected > .item-title-container {
+ -moz-appearance: -moz-mac-source-list-selection;
+}
+
+.item.selected:focus > .item-title-container {
+ -moz-appearance: -moz-mac-active-source-list-selection;
+}
+
+.item.client .item-twisty-container {
+ min-width: 16px;
+ height: 16px;
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+}
+
+@media not all and (-moz-mac-yosemite-theme) {
+ .item.client.selected .item-twisty-container {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+ }
+
+ .item.client.selected.closed .item-twisty-container {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+ }
+
+ .item.client.selected .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+ }
+
+ .item.client.selected.closed .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
+ }
+}
+
+.item.client.closed .item-twisty-container {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
+}
+
+.item.client.selected:focus .item-twisty-container {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+}
+
+.item.client.selected.closed:focus .item-twisty-container {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
+}
+
+.item.client .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded");
+}
+
+.item.client.closed .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
+}
+
+.item.client.selected:focus .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-expanded-inverted");
+}
+
+.item.client.selected.closed:focus .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
+}
+
+@media (-moz-mac-yosemite-theme) {
+ .item.selected > .item-title-container {
+ color: -moz-dialogtext;
+ font-weight: 500;
+ }
+
+ .item.selected:focus > .item-title-container {
+ color: #fff;
+ }
+}
+
+.sidebar-search-container {
+ border-bottom: 1px solid #bdbdbd;
+}
+
+.search-box {
+ -moz-appearance: searchfield;
+ padding: 1px;
+ font-size: 12px;
+ cursor: text;
+ margin: 4px 8px 10px;
+ border-width: 3px;
+ border-style: solid;
+ border-color: currentcolor;
+ border-image: none;
+ -moz-border-top-colors: transparent #888 #000;
+ -moz-border-right-colors: transparent #FFF #000;
+ -moz-border-bottom-colors: transparent #FFF #000;
+ -moz-border-left-colors: transparent #888 #000;
+ border-top-right-radius: 2px;
+ border-bottom-left-radius: 2px;
+ background-color: #FFF;
+ color: #000;
+ -moz-user-select: text;
+ text-shadow: none;
+}
+
+.search-box.compact > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
+ background-image: url(chrome://global/skin/icons/searchfield-cancel.svg);
+ background-repeat: no-repeat;
+ background-size: 11px 11px;
+ width: 11px;
+ height: 11px;
+}
+
+.search-box.compact > .textbox-input-box > .textbox-search-icons > .textbox-search-icon {
+ display: none;
+}
+
+.search-box[focused="true"] {
+ -moz-border-top-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+ -moz-border-right-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+ -moz-border-bottom-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+ -moz-border-left-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+}
+
+.search-box.compact {
+ padding: 0px;
+ /* font size is in px because the XUL it was copied from uses px */
+ font-size: 11px;
+}
+
+.textbox-search-clear,
+.textbox-search-icon {
+ margin-top: 1px;
+}
diff --git a/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted.png b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted.png
new file mode 100644
index 000000000..295b4ad74
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png
new file mode 100644
index 000000000..23889d7cc
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion.png b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion.png
new file mode 100644
index 000000000..4d71308d2
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion@2x.png b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion@2x.png
new file mode 100644
index 000000000..875f73c5e
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon-lion@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon.png b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon.png
new file mode 100644
index 000000000..94a11fe5f
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/alltabs-box-bkgnd-icon.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/newtab-inverted.png b/browser/themes/osx/tabbrowser/newtab-inverted.png
new file mode 100644
index 000000000..2d29c2cbe
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/newtab-inverted.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/newtab-inverted@2x.png b/browser/themes/osx/tabbrowser/newtab-inverted@2x.png
new file mode 100644
index 000000000..6feba0e83
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/newtab-inverted@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/newtab.png b/browser/themes/osx/tabbrowser/newtab.png
new file mode 100644
index 000000000..32e42b04c
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/newtab.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/newtab@2x.png b/browser/themes/osx/tabbrowser/newtab@2x.png
new file mode 100644
index 000000000..ffde5f050
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/newtab@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive.png b/browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive.png
new file mode 100644
index 000000000..9f74c4e76
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive@2x.png b/browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive@2x.png
new file mode 100644
index 000000000..50961db7e
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-active-middle-yosemite-inactive@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-active-middle.png b/browser/themes/osx/tabbrowser/tab-active-middle.png
new file mode 100644
index 000000000..c3e5e18ba
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-active-middle.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-active-middle@2x.png b/browser/themes/osx/tabbrowser/tab-active-middle@2x.png
new file mode 100644
index 000000000..6cdc4472e
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-active-middle@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-left-inverted.png b/browser/themes/osx/tabbrowser/tab-arrow-left-inverted.png
new file mode 100644
index 000000000..b0826024d
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-left-inverted.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-left-inverted@2x.png b/browser/themes/osx/tabbrowser/tab-arrow-left-inverted@2x.png
new file mode 100644
index 000000000..4ca8ec0ba
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-left-inverted@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-left.png b/browser/themes/osx/tabbrowser/tab-arrow-left.png
new file mode 100644
index 000000000..61ad41ae9
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-left.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-left@2x.png b/browser/themes/osx/tabbrowser/tab-arrow-left@2x.png
new file mode 100644
index 000000000..20bf78db2
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-left@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-right-inverted.png b/browser/themes/osx/tabbrowser/tab-arrow-right-inverted.png
new file mode 100644
index 000000000..0cd8f37a6
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-right-inverted.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-right-inverted@2x.png b/browser/themes/osx/tabbrowser/tab-arrow-right-inverted@2x.png
new file mode 100644
index 000000000..e4ab8cbaa
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-right-inverted@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-right.png b/browser/themes/osx/tabbrowser/tab-arrow-right.png
new file mode 100644
index 000000000..37ccf8cfe
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-right.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-arrow-right@2x.png b/browser/themes/osx/tabbrowser/tab-arrow-right@2x.png
new file mode 100644
index 000000000..d319accc5
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-arrow-right@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-background-end.png b/browser/themes/osx/tabbrowser/tab-background-end.png
new file mode 100644
index 000000000..2e515a39e
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-background-end.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-background-end@2x.png b/browser/themes/osx/tabbrowser/tab-background-end@2x.png
new file mode 100644
index 000000000..18dba9693
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-background-end@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-background-middle.png b/browser/themes/osx/tabbrowser/tab-background-middle.png
new file mode 100644
index 000000000..addb64b13
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-background-middle.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-background-middle@2x.png b/browser/themes/osx/tabbrowser/tab-background-middle@2x.png
new file mode 100644
index 000000000..41eba94af
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-background-middle@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-background-start.png b/browser/themes/osx/tabbrowser/tab-background-start.png
new file mode 100644
index 000000000..243bf0099
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-background-start.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-background-start@2x.png b/browser/themes/osx/tabbrowser/tab-background-start@2x.png
new file mode 100644
index 000000000..86403921d
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-background-start@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-selected-end-yosemite-inactive.svg b/browser/themes/osx/tabbrowser/tab-selected-end-yosemite-inactive.svg
new file mode 100644
index 000000000..decefab6e
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-selected-end-yosemite-inactive.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="30px" height="31px" preserveAspectRatio="none">
+ <defs>
+ <style>
+ #tab-background-fill {
+ background-color: transparent;
+ background-image: linear-gradient(transparent, transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%));
+ background-repeat: no-repeat;
+ height: 100%;
+ width: 100%;
+ }
+ </style>
+
+ <clipPath id="tab-curve-clip-path-end" clipPathUnits="objectBoundingBox">
+ <path d="m 0,0.0625 -0.05,0 0,0.938 1,0 0,-0.028 C 0.67917542,0.95840561 0.56569036,0.81970962 0.51599998,0.5625 0.48279998,0.3905 0.465,0.0659 0,0.0625 z"/>
+ </clipPath>
+ </defs>
+
+ <foreignObject width="30" height="31" clip-path="url(#tab-curve-clip-path-end)">
+ <div id="tab-background-fill" xmlns="http://www.w3.org/1999/xhtml"></div>
+ </foreignObject>
+</svg>
diff --git a/browser/themes/osx/tabbrowser/tab-selected-start-yosemite-inactive.svg b/browser/themes/osx/tabbrowser/tab-selected-start-yosemite-inactive.svg
new file mode 100644
index 000000000..e29bab297
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-selected-start-yosemite-inactive.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="30px" height="31px" preserveAspectRatio="none">
+ <defs>
+ <style>
+ #tab-background-fill {
+ background-color: transparent;
+ background-image: linear-gradient(transparent, transparent 2px, hsl(0,0%,99%) 2px, hsl(0,0%,97%));
+ background-repeat: no-repeat;
+ height: 100%;
+ width: 100%;
+ }
+ </style>
+
+ <clipPath id="tab-curve-clip-path-start" clipPathUnits="objectBoundingBox">
+ <path d="m 1,0.0625 0.05,0 0,0.938 -1,0 0,-0.028 C 0.32082458,0.95840561 0.4353096,0.81970962 0.48499998,0.5625 0.51819998,0.3905 0.535,0.0659 1,0.0625 z"/>
+ </clipPath>
+ </defs>
+
+ <foreignObject width="30" height="31" clip-path="url(#tab-curve-clip-path-start)">
+ <div id="tab-background-fill" xmlns="http://www.w3.org/1999/xhtml"></div>
+ </foreignObject>
+</svg>
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive.png b/browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive.png
new file mode 100644
index 000000000..4206f0c7d
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive@2x.png b/browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive@2x.png
new file mode 100644
index 000000000..21ac89270
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-end-yosemite-inactive@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-end.png b/browser/themes/osx/tabbrowser/tab-stroke-end.png
new file mode 100755
index 000000000..099978580
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-end.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-end@2x.png b/browser/themes/osx/tabbrowser/tab-stroke-end@2x.png
new file mode 100644
index 000000000..d321ca579
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-end@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive.png b/browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive.png
new file mode 100644
index 000000000..2acbac438
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive@2x.png b/browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive@2x.png
new file mode 100644
index 000000000..a12fc39a7
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-start-yosemite-inactive@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-start.png b/browser/themes/osx/tabbrowser/tab-stroke-start.png
new file mode 100755
index 000000000..e5a7b5ee9
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-start.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tab-stroke-start@2x.png b/browser/themes/osx/tabbrowser/tab-stroke-start@2x.png
new file mode 100644
index 000000000..64449115c
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tab-stroke-start@2x.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tabDragIndicator.png b/browser/themes/osx/tabbrowser/tabDragIndicator.png
new file mode 100644
index 000000000..a373cae53
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tabDragIndicator.png
Binary files differ
diff --git a/browser/themes/osx/tabbrowser/tabDragIndicator@2x.png b/browser/themes/osx/tabbrowser/tabDragIndicator@2x.png
new file mode 100644
index 000000000..b51b873cb
--- /dev/null
+++ b/browser/themes/osx/tabbrowser/tabDragIndicator@2x.png
Binary files differ
diff --git a/browser/themes/osx/toolbarbutton-dropmarker-lion.png b/browser/themes/osx/toolbarbutton-dropmarker-lion.png
new file mode 100644
index 000000000..09d80a1b2
--- /dev/null
+++ b/browser/themes/osx/toolbarbutton-dropmarker-lion.png
Binary files differ
diff --git a/browser/themes/osx/toolbarbutton-dropmarker-lion@2x.png b/browser/themes/osx/toolbarbutton-dropmarker-lion@2x.png
new file mode 100644
index 000000000..631fb1352
--- /dev/null
+++ b/browser/themes/osx/toolbarbutton-dropmarker-lion@2x.png
Binary files differ
diff --git a/browser/themes/osx/toolbarbutton-dropmarker.png b/browser/themes/osx/toolbarbutton-dropmarker.png
new file mode 100644
index 000000000..f4cdb664b
--- /dev/null
+++ b/browser/themes/osx/toolbarbutton-dropmarker.png
Binary files differ
diff --git a/browser/themes/osx/urlbar-history-dropmarker.png b/browser/themes/osx/urlbar-history-dropmarker.png
new file mode 100644
index 000000000..53bd1cb0c
--- /dev/null
+++ b/browser/themes/osx/urlbar-history-dropmarker.png
Binary files differ
diff --git a/browser/themes/osx/urlbar-history-dropmarker@2x.png b/browser/themes/osx/urlbar-history-dropmarker@2x.png
new file mode 100644
index 000000000..e7d4937b8
--- /dev/null
+++ b/browser/themes/osx/urlbar-history-dropmarker@2x.png
Binary files differ
diff --git a/browser/themes/osx/urlbar-popup-blocked.png b/browser/themes/osx/urlbar-popup-blocked.png
new file mode 100644
index 000000000..8c4f4d96f
--- /dev/null
+++ b/browser/themes/osx/urlbar-popup-blocked.png
Binary files differ
diff --git a/browser/themes/osx/urlbar-popup-blocked@2x.png b/browser/themes/osx/urlbar-popup-blocked@2x.png
new file mode 100644
index 000000000..4d6aa6f20
--- /dev/null
+++ b/browser/themes/osx/urlbar-popup-blocked@2x.png
Binary files differ
diff --git a/browser/themes/osx/webRTC-indicator.css b/browser/themes/osx/webRTC-indicator.css
new file mode 100644
index 000000000..759dbd813
--- /dev/null
+++ b/browser/themes/osx/webRTC-indicator.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/. */
+
+#webRTC-sharingCamera-menu {
+ list-style-image: url("chrome://browser/skin/webRTC-sharingDevice-menubar.png");
+}
+
+#webRTC-sharingMicrophone-menu {
+ list-style-image: url("chrome://browser/skin/webRTC-sharingMicrophone-menubar.png");
+}
+
+#webRTC-sharingScreen-menu {
+ list-style-image: url("chrome://browser/skin/webRTC-sharingScreen-menubar.png");
+}
+
+@media (min-resolution: 2dppx) {
+ #webRTC-sharingCamera-menu {
+ list-style-image: url("chrome://browser/skin/webRTC-sharingDevice-menubar@2x.png");
+ }
+
+ #webRTC-sharingMicrophone-menu {
+ list-style-image: url("chrome://browser/skin/webRTC-sharingMicrophone-menubar@2x.png");
+ }
+
+ #webRTC-sharingScreen-menu {
+ list-style-image: url("chrome://browser/skin/webRTC-sharingScreen-menubar@2x.png");
+ }
+}
+
+#webRTC-sharingCamera-menu > menupopup,
+#webRTC-sharingMicrophone-menu > menupopup,
+#webRTC-sharingScreen-menu > menupopup {
+ list-style-image: none; /* don't inherit into menu items */
+}
diff --git a/browser/themes/osx/webRTC-sharingDevice-menubar.png b/browser/themes/osx/webRTC-sharingDevice-menubar.png
new file mode 100644
index 000000000..fb74738dd
--- /dev/null
+++ b/browser/themes/osx/webRTC-sharingDevice-menubar.png
Binary files differ
diff --git a/browser/themes/osx/webRTC-sharingDevice-menubar@2x.png b/browser/themes/osx/webRTC-sharingDevice-menubar@2x.png
new file mode 100644
index 000000000..8f64887a8
--- /dev/null
+++ b/browser/themes/osx/webRTC-sharingDevice-menubar@2x.png
Binary files differ
diff --git a/browser/themes/osx/webRTC-sharingMicrophone-menubar.png b/browser/themes/osx/webRTC-sharingMicrophone-menubar.png
new file mode 100644
index 000000000..c47f5827d
--- /dev/null
+++ b/browser/themes/osx/webRTC-sharingMicrophone-menubar.png
Binary files differ
diff --git a/browser/themes/osx/webRTC-sharingMicrophone-menubar@2x.png b/browser/themes/osx/webRTC-sharingMicrophone-menubar@2x.png
new file mode 100644
index 000000000..2c45c8bcc
--- /dev/null
+++ b/browser/themes/osx/webRTC-sharingMicrophone-menubar@2x.png
Binary files differ
diff --git a/browser/themes/osx/webRTC-sharingScreen-menubar.png b/browser/themes/osx/webRTC-sharingScreen-menubar.png
new file mode 100644
index 000000000..6d9e49cfd
--- /dev/null
+++ b/browser/themes/osx/webRTC-sharingScreen-menubar.png
Binary files differ
diff --git a/browser/themes/osx/webRTC-sharingScreen-menubar@2x.png b/browser/themes/osx/webRTC-sharingScreen-menubar@2x.png
new file mode 100644
index 000000000..0f3d62ec5
--- /dev/null
+++ b/browser/themes/osx/webRTC-sharingScreen-menubar@2x.png
Binary files differ
diff --git a/browser/themes/preprocess-tab-svgs.py b/browser/themes/preprocess-tab-svgs.py
new file mode 100755
index 000000000..ab3e6ca88
--- /dev/null
+++ b/browser/themes/preprocess-tab-svgs.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import buildconfig
+
+from mozbuild.preprocessor import preprocess
+
+# By default, the pre-processor used for jar.mn will use "%" as a marker
+# for ".css" files and "#" otherwise. This falls apart when a file using
+# one marker needs to include a file with the other marker since the
+# pre-processor instructions in the included file will not be
+# processed. The following SVG files need to include a file which uses
+# "%" as the marker so we invoke the pre- processor ourselves here with
+# the marker specified. The resulting SVG files will get packaged by the
+# processing of the jar file in the appropriate directory.
+def _do_preprocessing(output_svg, input_svg_file, additional_defines):
+ additional_defines.update(buildconfig.defines)
+ return preprocess(output=output_svg,
+ includes=[input_svg_file],
+ marker='%',
+ defines=additional_defines)
+
+def tab_side_start(output_svg, input_svg_file):
+ return _do_preprocessing(output_svg, input_svg_file, {'TAB_SIDE': 'start'})
+
+def tab_side_end(output_svg, input_svg_file):
+ return _do_preprocessing(output_svg, input_svg_file, {'TAB_SIDE': 'end'})
+
diff --git a/browser/themes/shared/UITour.inc.css b/browser/themes/shared/UITour.inc.css
new file mode 100644
index 000000000..bc89ade76
--- /dev/null
+++ b/browser/themes/shared/UITour.inc.css
@@ -0,0 +1,293 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+/* UI Tour */
+
+#UITourHighlightContainer {
+ -moz-appearance: none;
+ -moz-window-shadow: none;
+ border: none;
+ background-color: transparent;
+ /* This is a buffer to compensate for the movement in the "wobble" effect,
+ and for the box-shadow of #UITourHighlight. */
+ padding: 4px;
+}
+
+#UITourHighlight {
+ background-image: radial-gradient(50% 100%, rgba(0,149,220,0.4) 50%, rgba(0,149,220,0.6) 100%);
+ border-radius: 40px;
+ border: 1px solid white;
+ /* The box-shadow opacity needs to be < 0.5 so it doesn't appear at 100% opacity
+ on Linux without an X compositor where opacity is either 0 or 1. */
+ box-shadow: 0 0 3px 0 rgba(0,0,0,0.49);
+ min-height: 32px;
+ min-width: 32px;
+}
+
+#UITourTooltipBody {
+ -moz-box-align: start;
+}
+
+#UITourTooltipTitleContainer {
+ -moz-box-align: start;
+ margin-bottom: 10px;
+}
+
+#UITourTooltipIcon {
+ width: 48px;
+ height: 48px;
+ margin-inline-end: 10px;
+}
+
+#UITourTooltipTitle,
+#UITourTooltipDescription {
+ max-width: 20rem;
+}
+
+#UITourTooltipTitle {
+ font-size: 1.45rem;
+ font-weight: bold;
+ margin: 0;
+}
+
+#UITourTooltipDescription {
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+ font-size: 1.15rem;
+ line-height: 1.8rem;
+ margin-bottom: 0; /* Override global.css */
+}
+
+#UITourTooltipClose {
+ position: relative;
+ -moz-appearance: none;
+ border: none;
+ background-color: transparent;
+ min-width: 0;
+ margin-inline-start: 4px;
+ margin-top: -2px;
+}
+
+#UITourTooltipClose > .toolbarbutton-text {
+ display: none;
+}
+
+#UITourTooltipButtons {
+ -moz-box-pack: end;
+ background-color: hsla(210,4%,10%,.07);
+ border-top: 1px solid hsla(210,4%,10%,.14);
+ margin: 10px -16px -16px;
+ padding: 16px;
+}
+
+#UITourTooltipButtons > label,
+#UITourTooltipButtons > button {
+ margin: 0 15px;
+}
+
+#UITourTooltipButtons > label:first-child,
+#UITourTooltipButtons > button:first-child {
+ margin-inline-start: 0;
+}
+
+#UITourTooltipButtons > label:last-child,
+#UITourTooltipButtons > button:last-child {
+ margin-inline-end: 0;
+}
+
+#UITourTooltipButtons > button[image] > .button-box > .button-icon {
+ width: 16px;
+ height: 16px;
+ margin-inline-end: 5px;
+}
+
+#UITourTooltipButtons > label,
+#UITourTooltipButtons > button .button-text {
+ font-size: 1.15rem;
+}
+
+#UITourTooltipButtons > button:not(.button-link) {
+ -moz-appearance: none;
+ background-color: rgb(251,251,251);
+ border-radius: 3px;
+ border: 1px solid;
+ border-color: rgb(192,192,192);
+ color: rgb(71,71,71);
+ padding: 4px 30px;
+ transition-property: background-color, border-color;
+ transition-duration: 150ms;
+}
+
+#UITourTooltipButtons > button:not(.button-link):not(:active):hover {
+ background-color: hsla(210,4%,10%,.15);
+ border-color: hsla(210,4%,10%,.15);
+ box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
+}
+
+#UITourTooltipButtons > label,
+#UITourTooltipButtons > button.button-link {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ color: rgba(0,0,0,0.35);
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+#UITourTooltipButtons > button.button-link:hover {
+ color: black;
+}
+
+/* The primary button gets the same color as the customize button. */
+#UITourTooltipButtons > button.button-primary {
+ background-color: rgb(116,191,67);
+ color: white;
+ padding-left: 30px;
+ padding-right: 30px;
+}
+
+#UITourTooltipButtons > button.button-primary:not(:active):hover {
+ background-color: rgb(105,173,61);
+}
+
+/* Notification overrides for Heartbeat UI */
+
+notification.heartbeat {
+%ifdef XP_MACOSX
+ background-image: linear-gradient(-179deg, #FBFBFB 0%, #EBEBEB 100%);
+%else
+ background-color: #F1F1F1;
+%endif
+ border-bottom: 1px solid #C1C1C1;
+ height: 40px;
+}
+
+/* In themes/osx/global/notification.css the close icon is inverted because notifications
+ on OSX are usually dark. Heartbeat is light, so override that behaviour. */
+
+%ifdef XP_MACOSX
+notification.heartbeat[type="info"] .close-icon:not(:hover) {
+ -moz-image-region: rect(0, 16px, 16px, 0px) !important;
+}
+@media (min-resolution: 2dppx) {
+ notification.heartbeat[type="info"] .close-icon:not(:hover) {
+ -moz-image-region: rect(0, 32px, 32px, 0px) !important;
+ }
+}
+%endif
+
+@keyframes pulse-onshow {
+ 0% {
+ opacity: 0;
+ transform: scale(1.0);
+ }
+ 25% {
+ opacity: 1;
+ transform: scale(1.1);
+ }
+ 50% {
+ transform: scale(1.0);
+ }
+ 75% {
+ transform: scale(1.1);
+ }
+ 100% {
+ transform: scale(1.0);
+ }
+}
+
+@keyframes pulse-twice {
+ 0% {
+ transform: scale(1.1);
+ }
+ 50% {
+ transform: scale(0.8);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+
+.messageText.heartbeat {
+ color: #333333;
+ text-shadow: none;
+ margin-inline-start: 0px;
+ /* The !important is required to override OSX default style. */
+ margin-inline-end: 12px !important;
+}
+
+.messageImage.heartbeat {
+ width: 24px;
+ height: 24px;
+ margin-inline-start: 8px;
+ margin-inline-end: 8px;
+}
+
+.messageImage.heartbeat.pulse-onshow {
+ animation-name: pulse-onshow;
+ animation-duration: 1.5s;
+ animation-iteration-count: 1;
+ animation-timing-function: cubic-bezier(.7,1.8,.9,1.1);
+}
+
+.messageImage.heartbeat.pulse-twice {
+ animation-name: pulse-twice;
+ animation-duration: 1s;
+ animation-iteration-count: 2;
+ animation-timing-function: linear;
+}
+
+/* Learn More link styles */
+.heartbeat > .text-link {
+ color: #0095DD;
+ margin-inline-start: 0px;
+}
+
+.heartbeat > .text-link:hover {
+ color: #008ACB;
+ text-decoration: none;
+}
+
+.heartbeat > .text-link:hover:active {
+ color: #006B9D;
+}
+
+/* Heartbeat UI Rating Star Classes */
+.heartbeat > #star-rating-container {
+ display: -moz-box;
+ margin-bottom: 4px;
+}
+
+.heartbeat > #star-rating-container > #star5 {
+ -moz-box-ordinal-group: 5;
+}
+
+.heartbeat > #star-rating-container > #star4 {
+ -moz-box-ordinal-group: 4;
+}
+
+.heartbeat > #star-rating-container > #star3 {
+ -moz-box-ordinal-group: 3;
+}
+
+.heartbeat > #star-rating-container > #star2 {
+ -moz-box-ordinal-group: 2;
+}
+
+.heartbeat > #star-rating-container > .star-x {
+ background: url("chrome://browser/skin/heartbeat-star-off.svg");
+ cursor: pointer;
+ /* Overrides the margin-inline-end for all platforms defined in the .plain class */
+ margin-inline-end: 4px !important;
+ width: 16px;
+ height: 16px;
+}
+
+.heartbeat > #star-rating-container > .star-x:hover,
+.heartbeat > #star-rating-container > .star-x:hover ~ .star-x {
+ background: url("chrome://browser/skin/heartbeat-star-lit.svg");
+}
diff --git a/browser/themes/shared/aboutNetError.css b/browser/themes/shared/aboutNetError.css
new file mode 100644
index 000000000..c0b76aa47
--- /dev/null
+++ b/browser/themes/shared/aboutNetError.css
@@ -0,0 +1,169 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://browser/skin/error-pages.css");
+
+body {
+ background-image: linear-gradient(-45deg, #eeeeee, #eeeeee 33%,
+ #fbfbfb 33%, #fbfbfb 66%,
+ #eeeeee 66%, #eeeeee);
+}
+
+body.certerror {
+ background-image: linear-gradient(-45deg, #f0d000, #f0d000 33%,
+ #fedc00 33%, #fedc00 66%,
+ #f0d000 66%, #f0d000);
+}
+
+body.captiveportal .title {
+ background-image: url("wifi.svg");
+}
+
+body.certerror .title {
+ background-image: url("cert-error.svg");
+}
+
+#errorContainer {
+ display: none;
+}
+
+/* Pressing the retry button will cause the cursor to flicker from a pointer to
+ * not-allowed. Override the disabled cursor behaviour since we will never show
+ * the button disabled as the initial state. */
+button:disabled {
+ cursor: pointer;
+}
+
+#prefChangeContainer {
+ display: none;
+}
+
+#learnMoreContainer {
+ display: none;
+}
+
+#certErrorAndCaptivePortalButtonContainer {
+ display: none;
+}
+
+body:not(.neterror) #certErrorAndCaptivePortalButtonContainer {
+ display: flex;
+}
+
+body:not(.neterror) #netErrorButtonContainer {
+ display: none;
+}
+
+#errorTryAgain {
+ margin-top: 1.2em;
+ min-width: 150px;
+}
+
+#returnButton {
+ min-width: 250px;
+}
+
+#advancedButton {
+ display: none;
+}
+
+body.captiveportal #returnButton {
+ display: none;
+}
+
+body:not(.captiveportal) #openPortalLoginPageButton {
+ display: none;
+}
+
+#openPortalLoginPageButton {
+ margin-inline-start: 0;
+}
+
+body:not(.neterror) #advancedButton {
+ display: block;
+}
+
+#certificateErrorReporting {
+ display: none;
+}
+
+.container {
+ position: relative;
+}
+
+#advancedPanelContainer {
+ position: absolute;
+ padding: 24px 0;
+ width: 100%;
+}
+
+.advanced-panel {
+ /* Hidden until the link is clicked */
+ display: none;
+ background-color: white;
+ border: 1px lightgray solid;
+ /* Don't use top padding because the default p style has top padding, and it
+ * makes the overall div look uneven */
+ padding: 0 12px 12px 12px;
+ box-shadow: 0 0 4px #ddd;
+ font-size: 0.9em;
+}
+
+#overrideWeakCryptoPanel {
+ display: none;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-content: space-between;
+ align-items: flex-start;
+ margin-top: 1em;
+}
+
+span#hostname {
+ font-weight: bold;
+}
+
+#automaticallyReportInFuture {
+ cursor: pointer;
+ display: inline-block;
+ padding-inline-start: 2.3em;
+ text-indent: -2.3em;
+ line-height: 16px
+}
+
+#errorCode:not([href]) {
+ color: var(--in-content-page-color);
+ cursor: text;
+ text-decoration: none;
+}
+
+#errorCode[href] {
+ white-space: nowrap;
+}
+
+#badCertTechnicalInfo {
+ overflow: auto;
+ white-space: pre-wrap;
+}
+
+#certificateErrorReporting {
+ display: none;
+}
+
+#certificateErrorDebugInformation {
+ display: none;
+ background-color: var(--in-content-box-background-hover) !important;
+ border-top: 1px solid var(--in-content-border-color);
+ position: absolute;
+ left: 0%;
+ top: 100%;
+ width: 65%;
+ padding: 1em 17.5%;
+}
+
+#certificateErrorText {
+ font-family: monospace;
+ white-space: pre-wrap;
+ padding: 1em 0;
+}
diff --git a/browser/themes/shared/aboutProviderDirectory.css b/browser/themes/shared/aboutProviderDirectory.css
new file mode 100644
index 000000000..73e570aad
--- /dev/null
+++ b/browser/themes/shared/aboutProviderDirectory.css
@@ -0,0 +1,30 @@
+%include aboutSocialError.css
+
+body {
+ width: 310px;
+ margin: 1em auto;
+}
+
+#message-box {
+ margin-top: 2em;
+ background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px;
+ padding-inline-start: 30px;
+}
+
+#activation-frame {
+ border: none;
+ margin: 0;
+ width: 310px;
+ height: 200px;
+}
+#activation > p {
+ width: 100%;
+ text-align: center;
+ margin: 0;
+ line-height: 2em;
+}
+.link {
+ text-decoration: none;
+ color: -moz-nativehyperlinktext;
+ cursor: pointer;
+}
diff --git a/browser/themes/shared/aboutSessionRestore.css b/browser/themes/shared/aboutSessionRestore.css
new file mode 100644
index 000000000..52488af40
--- /dev/null
+++ b/browser/themes/shared/aboutSessionRestore.css
@@ -0,0 +1,38 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+.title {
+ background-image: url("chrome://browser/skin/session-restore.svg");
+}
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+ padding-right: 2px;
+ margin: 0 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+ list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+
+treechildren::-moz-tree-image(checked) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check");
+}
+treechildren::-moz-tree-image(checked, selected) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
+}
+
+treechildren::-moz-tree-image(partial) {
+ list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial");
+}
+treechildren::-moz-tree-image(partial, selected) {
+ list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial-inverted");
+}
diff --git a/browser/themes/shared/aboutSocialError.css b/browser/themes/shared/aboutSocialError.css
new file mode 100644
index 000000000..f5a922ff4
--- /dev/null
+++ b/browser/themes/shared/aboutSocialError.css
@@ -0,0 +1,40 @@
+@import url("chrome://global/skin/in-content/common.css");
+
+#errorPageContainer {
+ min-width: 50%;
+}
+
+#errorTitle {
+ background: url("chrome://global/skin/icons/info.svg") left 0 no-repeat;
+ background-size: 2em;
+ padding-inline-start: 3em;
+}
+
+#button-box {
+ text-align: center;
+ width: 75%;
+ margin: 0 auto;
+}
+
+button {
+ width: auto !important;
+ min-width: 150px;
+}
+
+@media all and (max-width: 300px) {
+ body {
+ padding: 0px 10px;
+ }
+ #errorPageContainer {
+ min-width: 100%;
+ }
+ #errorTitle {
+ background: none;
+ padding-inline-start: 0 !important;
+ }
+ button {
+ width: auto !important;
+ min-width: auto !important;
+ }
+}
+
diff --git a/browser/themes/shared/aboutTabCrashed.css b/browser/themes/shared/aboutTabCrashed.css
new file mode 100644
index 000000000..264fb4275
--- /dev/null
+++ b/browser/themes/shared/aboutTabCrashed.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/. */
+
+body {
+ font-size: 1.25rem;
+}
+
+.title {
+ background-image: url("chrome://browser/skin/tab-crashed.svg");
+}
+
+.title > h1,
+.offers {
+ margin-left: 14px;
+}
+
+.title > h1 {
+ /**
+ * Add commentary?
+ */
+ padding-right: 14px;
+}
+
+.container {
+ width: 45%;
+}
+
+#reportSent {
+ font-weight: bold;
+}
+
+#reportBox {
+ background-color: var(--in-content-box-background-hover);
+ margin: 24px 0;
+ padding: 14px;
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: 2px;
+}
+
+#reportBox > h2:first-child {
+ margin-top: 0;
+}
+
+#crash-reporter-title {
+ font-weight: bold;
+ margin: 0 0 14px 0;
+}
+
+input[type="text"],
+textarea {
+ width: 100%;
+ box-sizing: border-box;
+ resize: none;
+}
+
+input[type="text"],
+input[type="checkbox"] {
+ -moz-margin-start: 0px;
+}
+
+#options {
+ list-style: none;
+ margin-inline-start: 0;
+}
+
+#options > li,
+#email {
+ margin-top: 14px;
+}
+
+.checkbox-with-label {
+ display: flex;
+}
+
+.checkbox-with-label > label {
+ margin-top: auto;
+ margin-bottom: auto;
+}
+
+/**
+ * Hack alert: the #autoSubmit checkbox has a long label, which means
+ * it often wraps, at least in en-US. Bug 418833 and bug 1317795 allows
+ * us to fix this properly, but bug 418833 didn't uplift in time for this
+ * release. We use some hackery here to make sure that this label wraps
+ * properly, and doesn't end up underneath the checkbox pseudoelement.
+ * We do this by setting a negative margin on the pseudoelement, and then
+ * a positive equivalent margin on the label itself.
+ *
+ * The magic number of 35px is derived from the total width of the checkbox.
+ * The checkbox width is explicitly set at 23px. This, plus the 1px border on either
+ * side gives us 25px. Then there's the 10px margin-inline-end, which gives us 35px.
+ */
+#autoSubmit + label:before {
+ margin-inline-start: -35px;
+}
+
+#autoSubmit + label {
+ margin-inline-start: 35px;
+ line-height: 1.75em;
+} \ No newline at end of file
diff --git a/browser/themes/shared/aboutWelcomeBack.css b/browser/themes/shared/aboutWelcomeBack.css
new file mode 100644
index 000000000..815ad462e
--- /dev/null
+++ b/browser/themes/shared/aboutWelcomeBack.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/. */
+
+.title {
+ background-image: url("chrome://browser/skin/welcome-back.svg");
+}
+
+.radioRestoreContainer:not(:last-child) {
+ margin-bottom: 0.2em;
+}
+
+/* tablist starts out hidden, but JS may make it visible in response to
+ clicks on the radio buttons by setting an "available" attribute.
+*/
+.tree-container:not([available]) {
+ display: none;
+}
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+ padding-right: 2px;
+ margin: 0 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+ list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+
+treechildren::-moz-tree-image(checked) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check");
+}
+treechildren::-moz-tree-image(checked, selected) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
+}
+
+treechildren::-moz-tree-image(partial) {
+ list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial");
+}
+treechildren::-moz-tree-image(partial, selected) {
+ list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial-inverted");
+}
diff --git a/browser/themes/shared/addons/addon-install-anchor.svg b/browser/themes/shared/addons/addon-install-anchor.svg
new file mode 100644
index 000000000..1ee69ca0d
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-anchor.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ class="fieldtext"
+ width="16" height="16" viewBox="0 0 16 16">
+#include ../icon-colors.inc.svg
+ <defs>
+ <path id="shape-notifications-addons" d="M10,15c0.5,0,1-0.4,1-1v-3c0,0,0-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2c0-1.8-0.9-2-1.5-2 c-1.1,0-1.1,0.7-1.8,0.7C11,7.7,11,7,11,7V6c0-0.6-0.5-1-1-1H8c0,0-0.8,0-0.8-0.8C7.2,3.6,8,3.6,8,2.5C8,1.9,7.8,1,6,1 C4.2,1,4,1.9,4,2.5c0,1.1,0.8,1.1,0.8,1.8C4.8,5,4,5,4,5H2C1.5,5,1,5.4,1,6l0,1.5c0,0-0.1,1,1.1,1c0.8,0,0.9-1,1.9-1 C4.5,7.4,5,8,5,9c0,1-0.5,1.6-1,1.6c-1,0-1.1-1.1-1.9-1.1C0.9,9.5,1,10.8,1,10.8V14c0,0.6,0.5,1,1,1l2.6,0c0,0,1.1,0,1.1-1 c0-0.8-1-0.1-1-1.1c0-0.5,0.7-1.2,1.8-1.2s1.8,0.7,1.8,1.2c0,1-1.1,0.3-1.1,1.1c0,1,1.2,1,1.2,1H10z"/>
+ </defs>
+ <use xlink:href="#shape-notifications-addons"/>
+</svg>
diff --git a/browser/themes/shared/addons/addon-install-blocked.svg b/browser/themes/shared/addons/addon-install-blocked.svg
new file mode 100644
index 000000000..caaaa466b
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-blocked.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ }
+ .style-badge-shadow {
+ fill: #0d131a;
+ fill-opacity: .15;
+ }
+ .style-badge-background {
+ fill: #fff;
+ }
+ .style-badge-inside {
+ fill: #e62117;
+ }
+ .style-badge-icon {
+ fill: #fff;
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#999999" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#8c8c8c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+ <svg width="32" height="32" x="32" y="0">
+ <ellipse class="style-badge-shadow" rx="14" ry="15" cx="16" cy="17" />
+ <circle class="style-badge-background" r="15" cy="15" cx="16" />
+ <circle class="style-badge-inside" r="12" cy="15" cx="16" />
+ <rect class="style-badge-icon" x="9" y="13" width="14" height="4" rx="1" ry="1" />
+ </svg>
+</svg>
diff --git a/browser/themes/shared/addons/addon-install-confirm.svg b/browser/themes/shared/addons/addon-install-confirm.svg
new file mode 100644
index 000000000..a16455253
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-confirm.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#66cc52" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#60bf4c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+</svg>
diff --git a/browser/themes/shared/addons/addon-install-downloading.svg b/browser/themes/shared/addons/addon-install-downloading.svg
new file mode 100644
index 000000000..9dcc8069c
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-downloading.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ }
+ .style-badge-shadow {
+ fill: #0d131a;
+ fill-opacity: .15;
+ }
+ .style-badge-background {
+ fill: #fff;
+ }
+ .style-badge-inside {
+ fill: #55cc3d;
+ }
+ .style-badge-icon {
+ fill: #fff;
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#66cc52" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#60bf4c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+ <svg width="32" height="32" x="32" y="0">
+ <ellipse class="style-badge-shadow" rx="14" ry="15" cx="16" cy="17" />
+ <circle class="style-badge-background" r="15" cy="15" cx="16" />
+ <circle class="style-badge-inside" r="12" cy="15" cx="16" />
+ <path class="style-badge-icon" d="M22.7,16.1l-5.6,5.5C16.8,21.9,16.4,22,16,22c-0.4,0-0.7-0.1-1-0.4 l-5.6-5.5C8.8,15.5,8.9,15,9.8,15l3.2,0V9c0-0.6,0.5-1,1.1-1h4c0.6,0,1,0.4,1,1v6h3.2C23.1,15,23.3,15.5,22.7,16.1z"/>
+ </svg>
+</svg>
diff --git a/browser/themes/shared/addons/addon-install-error.svg b/browser/themes/shared/addons/addon-install-error.svg
new file mode 100644
index 000000000..e25950f25
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-error.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ }
+ .style-badge-shadow {
+ fill: #0d131a;
+ fill-opacity: .15;
+ }
+ .style-badge-background {
+ fill: #fff;
+ }
+ .style-badge-inside {
+ fill: #e62117;
+ }
+ .style-badge-icon {
+ fill: #fff;
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#999999" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#8c8c8c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+ <svg width="32" height="32" x="32" y="0">
+ <ellipse class="style-badge-shadow" rx="14" ry="15" cx="16" cy="17" />
+ <circle class="style-badge-background" r="15" cy="15" cx="16" />
+ <circle class="style-badge-inside" r="12" cy="15" cx="16" />
+ <path class="style-badge-icon" d="M14.9,16.2c0,0,0.1,0.8,1.1,0.8c1,0,1.1-0.8,1.1-0.8 s0.7-3.5,0.8-5.2C18,9.3,18.4,7,16,7s-2,2.4-1.9,4C14.2,12.7,14.9,16.2,14.9,16.2z M16,19c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2 c1.1,0,2-0.9,2-2C18,19.9,17.1,19,16,19z" />
+ </svg>
+</svg>
diff --git a/browser/themes/shared/addons/addon-install-installed.svg b/browser/themes/shared/addons/addon-install-installed.svg
new file mode 100644
index 000000000..3b352c21d
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-installed.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ }
+ .style-badge-shadow {
+ fill: #0d131a;
+ fill-opacity: .15;
+ }
+ .style-badge-background {
+ fill: #fff;
+ }
+ .style-badge-inside {
+ fill: #55cc3d;
+ }
+ .style-badge-icon {
+ fill: #fff;
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#66cc52" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#60bf4c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+ <svg width="32" height="32" x="32" y="0">
+ <ellipse class="style-badge-shadow" rx="14" ry="15" cx="16" cy="17" />
+ <circle class="style-badge-background" r="15" cy="15" cx="16" />
+ <circle class="style-badge-inside" r="12" cy="15" cx="16" />
+ <path class="style-badge-icon" d="M22.8,12.3c0,0-6.7,8.1-6.9,8.3c-0.4,0.5-1.5,0.3-1.7,0 c-0.2-0.3-5-5.8-5-5.8c-0.3-0.3-0.3-0.7,0-1l1-1c0.4-0.4,0.9,0,1.2,0.3c0.3,0.4,3.4,3.8,3.4,3.8s5.2-6.1,5.4-6.4 c0.5-0.8,1.6-0.8,1.9-0.5l0.7,0.6C23.1,11.1,23.1,12,22.8,12.3z" />
+ </svg>
+</svg>
diff --git a/browser/themes/shared/addons/addon-install-restart.svg b/browser/themes/shared/addons/addon-install-restart.svg
new file mode 100644
index 000000000..e3269c3b1
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-restart.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ fill-opacity: .25;
+ }
+ .style-puzzle-piece-outline {
+ fill: none;
+ stroke-width: 2;
+ stroke: #52b33e;
+ stroke-dasharray: 4 2;
+ }
+ .style-badge-shadow {
+ fill: #0d131a;
+ fill-opacity: .15;
+ }
+ .style-badge-background {
+ fill: #fff;
+ }
+ .style-badge-inside {
+ fill: #00a1e5;
+ }
+ .style-badge-icon {
+ fill: #fff;
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#66cc52" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#60bf4c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+ <path class="style-puzzle-piece-outline" d="M23.6,3c6.3,0,7.3,3,7.3,4.9c0,2.2-1,3.1-2,4c-0.8,0.8-1.8,1.6-1.8,3.1c0,2.6,2.7,3.7,4.3,4l0.1,0h0.1H42 c1.7,0,3,1.4,3,3v5.8v0l0,0c0.2,1.7,1.2,4.7,3.8,4.7c1.5,0,2.3-0.9,3-1.8c0.8-1,1.6-1.9,3.7-1.9c3.5,0,5.2,2.2,5.2,6.9 c0,6.2-3.2,7.2-5.2,7.2c-2.1,0-2.9-1-3.7-2c-0.7-0.9-1.5-1.9-3-1.9c-2.6,0-3.6,2.9-3.8,4.6l0,0l0,0L45,58c0,1.6-1.3,3-3,3h-5.2l0,0 l0,0c0,0-0.1,0-0.3,0c-4.5,0-4.9-2.4-4.9-3.4c0-1,0.5-1.6,1.5-2.6c1.1-1.1,2.4-2.5,2.4-5.1c0-3.3-3.9-5.5-7.6-5.5 c-4.6,0-7.4,2.8-7.4,5.5c0,2.6,1.4,4,2.5,5.1c1,1,1.5,1.6,1.5,2.6c0,3.1-3.4,3.4-4.9,3.4c-0.2,0-0.3,0-0.3,0l0,0h0H6 c-1.6,0-3-1.3-3-3l0-12.2l0,0l0,0c0,0-0.1-2.5,1.1-3.9c0.6-0.6,1.3-0.9,2.3-0.9c0.9,0,1.5,0.5,2.3,1.5c1,1.2,2.3,2.6,4.9,2.6 c3.3,0,5-3.6,5-7.3c0-3.4-1.6-7-5-7c-2.6,0-3.9,1.4-4.9,2.6c-0.9,1-1.4,1.5-2.3,1.5c-1,0-1.7-0.3-2.3-0.9C2.8,32.6,3,29.9,3,29.9 l0,0l0,0L3,22c0-1.7,1.3-3,3-3h9.7h0.1l0.1,0c1.6-0.3,4.3-1.4,4.3-4c0-1.4-0.9-2.3-1.6-3.1c-0.9-1-1.8-1.9-1.8-4.1 C16.6,4.6,18.9,3,23.6,3"/>
+ <svg width="32" height="32" x="32" y="0">
+ <ellipse class="style-badge-shadow" rx="14" ry="15" cx="16" cy="17" />
+ <circle class="style-badge-background" r="15" cy="15" cx="16" />
+ <circle class="style-badge-inside" r="12" cy="15" cx="16" />
+ <path class="style-badge-icon" d="M21,15h-6l2.4-2.4c-0.6-0.4-1.2-0.6-1.9-0.6c-2,0-3.5,1.6-3.5,3.5 c0,2,1.6,3.5,3.5,3.5c1,0,2-0.5,2.6-1.2l1.7,1c-1,1.3-2.6,2.1-4.3,2.1c-3,0-5.5-2.5-5.5-5.5c0-3,2.5-5.5,5.5-5.5 c1.3,0,2.4,0.4,3.3,1.2L21,9V15z"/>
+ </svg>
+</svg>
diff --git a/browser/themes/shared/addons/addon-install-warning.svg b/browser/themes/shared/addons/addon-install-warning.svg
new file mode 100644
index 000000000..bac1903c6
--- /dev/null
+++ b/browser/themes/shared/addons/addon-install-warning.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .style-puzzle-piece {
+ fill: url('#gradient-linear-puzzle-piece');
+ }
+ .style-badge-shadow {
+ fill: #0d131a;
+ fill-opacity: .15;
+ }
+ .style-badge-background {
+ fill: #fff;
+ }
+ .style-badge-inside {
+ fill: #ffcd02;
+ }
+ .style-badge-icon {
+ fill: #fff;
+ }
+ </style>
+ <linearGradient id="gradient-linear-puzzle-piece" x1="0%" y1="0%" x2="0%" y2="100%">
+ <stop offset="0%" stop-color="#999999" stop-opacity="1"/>
+ <stop offset="100%" stop-color="#8c8c8c" stop-opacity="1"/>
+ </linearGradient>
+ </defs>
+ <path class="style-puzzle-piece" d="M42,62c2.2,0,4-1.8,4-4l0-14.2c0,0,0.4-3.7,2.8-3.7c2.4,0,2.2,3.9,6.7,3.9c2.3,0,6.2-1.2,6.2-8.2 c0-7-3.9-7.9-6.2-7.9c-4.5,0-4.3,3.7-6.7,3.7c-2.4,0-2.8-3.8-2.8-3.8V22c0-2.2-1.8-4-4-4H31.5c0,0-3.4-0.6-3.4-3 c0-2.4,3.8-2.6,3.8-7.1c0-2.3-1.3-5.9-8.3-5.9s-8,3.6-8,5.9c0,4.5,3.4,4.7,3.4,7.1c0,2.4-3.4,3-3.4,3H6c-2.2,0-4,1.8-4,4l0,7.8 c0,0-0.4,6,4.4,6c3.1,0,3.2-4.1,7.3-4.1c2,0,4,1.9,4,6c0,4.2-2,6.3-4,6.3c-4,0-4.2-4.1-7.3-4.1c-4.8,0-4.4,5.8-4.4,5.8L2,58 c0,2.2,1.8,4,4,4H19c0,0,6.3,0.4,6.3-4.4c0-3.1-4-3.6-4-7.7c0-2,2.2-4.5,6.4-4.5c4.2,0,6.6,2.5,6.6,4.5c0,4-3.9,4.6-3.9,7.7 c0,4.9,6.3,4.4,6.3,4.4H42z"/>
+ <svg width="32" height="32" x="32" y="0">
+ <path class="style-badge-shadow" d="M29.5,25.8L18.7,4c-0.6-1.2-1.6-2-2.7-2c-1.1,0-2.1,0.7-2.7,2L2.5,25.8 c-0.6,1.2-0.6,2.5-0.1,3.6C2.9,30.4,4,31,5.2,31h21.6c1.2,0,2.3-0.6,2.8-1.6C30.2,28.4,30.1,27.1,29.5,25.8z" />
+ <path class="style-badge-background" d="M16,0c-1.7,0-3.2,1-4.1,2.7L1.7,21.9c-0.9,1.7-0.9,3.4,0,4.8C2.5,28.2,4.1,29,5.9,29H26 c1.9,0,3.4-0.8,4.3-2.2c0.9-1.4,0.8-3.2,0-4.8L20.1,2.7C19.2,1,17.7,0,16,0L16,0z" />
+ <path class="style-badge-inside" d="M5.9,26c-1.7,0-2.4-1.2-1.6-2.7L14.6,4.1c0.8-1.5,2.1-1.5,2.8,0l10.3,19.3 c0.8,1.5,0.1,2.7-1.6,2.7H5.9z" />
+ <path class="style-badge-icon" d="M14.9,17.6c0,0,0.1,0.7,1.1,0.7c1,0,1.1-0.7,1.1-0.7 s0.7-2.9,0.8-4.2c0.1-1.3,0.5-3.2-1.9-3.2c-2.4,0-2,1.9-1.9,3.2C14.2,14.8,14.9,17.6,14.9,17.6z M16,20c-1.1,0-2,0.9-2,2 c0,1.1,0.9,2,2,2c1.1,0,2-0.9,2-2C18,20.9,17.1,20,16,20z" />
+ </svg>
+</svg>
diff --git a/browser/themes/shared/autocomplete.inc.css b/browser/themes/shared/autocomplete.inc.css
new file mode 100644
index 000000000..d9923a7e7
--- /dev/null
+++ b/browser/themes/shared/autocomplete.inc.css
@@ -0,0 +1,65 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+#PopupAutoComplete > richlistbox > richlistitem {
+ height: 20px;
+ min-height: 20px;
+ border: 0;
+ border-radius: 0;
+ padding: 0px 1px 0px 1px;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .ac-site-icon {
+ margin-inline-start: 4px;
+ margin-inline-end: 0;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem > .ac-title {
+ font: icon;
+ margin-inline-start: 4px;
+}
+
+#PopupAutoComplete > richlistbox {
+ padding: 0;
+}
+
+
+/* Login form autocompletion */
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="login"] > .ac-site-icon {
+ display: initial;
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#login);
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="login"] > .ac-site-icon[selected] {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#login-highlighted);
+}
+
+
+/* Insecure field warning */
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] {
+ background-color: var(--arrowpanel-dimmed);
+ border-bottom: 1px solid var(--panel-separator-color);
+ padding-bottom: 4px;
+ padding-top: 4px;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"][selected] {
+ background-color: var(--arrowpanel-dimmed-further);
+ color: -moz-DialogText;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-title {
+ color: GrayText;
+ font-size: 1em;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"][selected] > .ac-title {
+ color: inherit;
+}
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] > .ac-site-icon {
+ list-style-image: url(chrome://browser/skin/connection-mixed-active-loaded.svg#icon);
+}
diff --git a/browser/themes/shared/blockedSite.css b/browser/themes/shared/blockedSite.css
new file mode 100644
index 000000000..49846ee89
--- /dev/null
+++ b/browser/themes/shared/blockedSite.css
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://browser/skin/error-pages.css");
+
+body {
+ background-image: linear-gradient(-45deg, #9b2e2e, #9b2e2e 33%,
+ #a83232 33%, #a83232 66%,
+ #9b2e2e 66%, #9b2e2e);
+ background-color: #b14646;
+ color: white;
+}
+
+.title {
+ background-image: url("chrome://global/skin/icons/blocked.svg");
+}
+
+.title-text {
+ color: white;
+}
+
+.button-container button:not(.primary) {
+ background-color: transparent;
+ color: white;
+ border: 1px solid #9b2e2e;
+ margin-inline-end: 0;
+}
+
+.button-container button:not(.primary):hover {
+ background-color: #a83232;
+}
+
+.button-container button:not(.primary):active {
+ background-color: #9b2e2e;
+}
+
+.button-container button {
+ margin-top: 1.2em;
+}
+
+/* Style warning button to look like a small text link in the
+ bottom right. This is preferable to just using a text link
+ since there is already a mechanism in browser.js for trapping
+ oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
+#ignoreWarningButton {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ color: white;
+ text-decoration: underline;
+ margin: 4px 0 0 0;
+ padding: 0;
+ font-size: smaller;
+ min-width: 0;
+}
+
+#ignoreWarningButton:hover {
+ cursor: pointer;
+}
+
+#ignoreWarning {
+ margin-top: 1.2em;
+ text-align: end;
+}
diff --git a/browser/themes/shared/browser.inc b/browser/themes/shared/browser.inc
new file mode 100644
index 000000000..c57b59237
--- /dev/null
+++ b/browser/themes/shared/browser.inc
@@ -0,0 +1,13 @@
+%filter substitution
+
+% Note that zoom-reset-button is a bit different since it doesn't use an image and thus has the image with display: none.
+%define nestedButtons #zoom-out-button, #zoom-reset-button, #zoom-in-button, #cut-button, #copy-button, #paste-button
+%define primaryToolbarButtons #back-button, #forward-button, #home-button, #print-button, #downloads-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #fullscreen-button, #sync-button, #feed-button, #social-share-button, #open-file-button, #find-button, #developer-button, #preferences-button, #privatebrowsing-button, #save-page-button, #add-ons-button, #history-panelmenu, #nav-bar-overflow-button, #PanelUI-menu-button, #characterencoding-button, #email-link-button, #sidebar-button, @nestedButtons@, #e10s-button, #panic-button, #webide-button, #containers-panelmenu
+
+%ifdef XP_MACOSX
+% Prior to 10.7 there wasn't a native fullscreen button so we use #restore-button to exit fullscreen
+% and want it to behave like other toolbar buttons.
+%define primaryToolbarButtons @primaryToolbarButtons@, #restore-button
+%endif
+
+%define inAnyPanel :-moz-any(:not([cui-areatype="toolbar"]), [overflowedItem=true])
diff --git a/browser/themes/shared/content-contextmenu.svg b/browser/themes/shared/content-contextmenu.svg
new file mode 100644
index 000000000..6b53c13d9
--- /dev/null
+++ b/browser/themes/shared/content-contextmenu.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <style>
+ path:not(:target),
+ polygon:not(:target) {
+ display: none;
+ }
+ </style>
+ <path id="back" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/>
+ <path id="forward" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/>
+ <path id="reload" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/>
+ <polygon id="stop" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/>
+ <path id="bookmark" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/>
+ <path id="bookmarked" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/>
+</svg>
diff --git a/browser/themes/shared/contextmenu.inc.css b/browser/themes/shared/contextmenu.inc.css
new file mode 100644
index 000000000..2ec60b55b
--- /dev/null
+++ b/browser/themes/shared/contextmenu.inc.css
@@ -0,0 +1,51 @@
+#context-navigation > .menuitem-iconic {
+ -moz-box-flex: 1;
+ -moz-box-pack: center;
+ -moz-box-align: center;
+}
+
+#context-navigation > .menuitem-iconic > .menu-iconic-left {
+ -moz-appearance: none;
+}
+
+#context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon {
+ width: 16px;
+ height: 16px;
+ margin: 7px;
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
+
+#context-back {
+ list-style-image: url("chrome://browser/skin/content-contextmenu.svg#back");
+}
+
+#context-forward {
+ list-style-image: url("chrome://browser/skin/content-contextmenu.svg#forward");
+}
+
+#context-reload {
+ list-style-image: url("chrome://browser/skin/content-contextmenu.svg#reload");
+}
+
+#context-stop {
+ list-style-image: url("chrome://browser/skin/content-contextmenu.svg#stop");
+}
+
+#context-bookmarkpage {
+ list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmark");
+}
+
+#context-bookmarkpage[starred=true] {
+ list-style-image: url("chrome://browser/skin/content-contextmenu.svg#bookmarked");
+}
+
+#context-back:-moz-locale-dir(rtl),
+#context-forward:-moz-locale-dir(rtl),
+#context-reload:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+#context-media-eme-learnmore {
+ list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
+}
diff --git a/browser/themes/shared/controlcenter/arrow-subview-back.svg b/browser/themes/shared/controlcenter/arrow-subview-back.svg
new file mode 100644
index 000000000..9f681207c
--- /dev/null
+++ b/browser/themes/shared/controlcenter/arrow-subview-back.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+#include ../icon-colors.inc.svg
+ <polygon class="highlighttext" points="12,3.5 10.5,2 4.625,8 10.5,14 12,12.5 7.625,8"/>
+</svg>
diff --git a/browser/themes/shared/controlcenter/arrow-subview.svg b/browser/themes/shared/controlcenter/arrow-subview.svg
new file mode 100644
index 000000000..2802fa055
--- /dev/null
+++ b/browser/themes/shared/controlcenter/arrow-subview.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+#include ../icon-colors.inc.svg
+ <polygon class="fieldtext" points="5,3.5 6.5,2 12.375,8 6.5,14 5,12.5 9.375,8"/>
+</svg>
diff --git a/browser/themes/shared/controlcenter/conn-not-secure.svg b/browser/themes/shared/controlcenter/conn-not-secure.svg
new file mode 100644
index 000000000..ef2df7c69
--- /dev/null
+++ b/browser/themes/shared/controlcenter/conn-not-secure.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+#include ../icon-colors.inc.svg
+ <defs>
+ <mask id="mask-globe">
+ <circle fill="#fff" cx="12" cy="12" r="11"/>
+ <path transform="translate(1 1)" fill="#000" d="M8.41648275,2.92371996 C8.38948313,2.92220329 8.35948356,2.92371996 8.33398393,2.93433671 C8.32798401,2.93737006 8.32048413,2.94343677 8.31448421,2.95102016 C8.32348407,2.95102016 8.33398393,2.95102016 8.34148382,2.94950348 C8.36848344,2.94343677 8.38948313,2.92523664 8.41648275,2.92371996 L8.41648275,2.92371996 L8.41648275,2.92371996 Z M8.45098225,4.22654587 C8.48548176,4.18256223 8.40298294,4.14616197 8.35648361,4.14919533 C8.36848344,4.09611162 8.43598246,4.06881142 8.41798272,4.00207762 C8.40148297,3.93231045 8.31748416,3.94596055 8.27248482,3.98387749 C8.23198539,4.01876107 8.21098569,4.07942816 8.17648618,4.12037845 C8.15698646,4.14312862 8.12098698,4.150712 8.10748718,4.1780122 C8.09548735,4.20379571 8.11048713,4.24777936 8.10898715,4.27659623 C8.16448635,4.28417962 8.22298552,4.26749616 8.26348494,4.22806255 L8.29048456,4.21592913 C8.28448464,4.22047916 8.28148468,4.23109591 8.27848473,4.23716262 C8.30548433,4.27052952 8.42548262,4.26142946 8.45098225,4.22654587 L8.45098225,4.22654587 L8.45098225,4.22654587 Z M8.50798144,2.36558267 C8.50498148,2.44748325 8.58448033,2.45658332 8.64297949,2.48843354 C8.62497975,2.53545054 8.56048068,2.53393387 8.53048111,2.5718508 C8.49448162,2.6188678 8.56048068,2.65981809 8.59348021,2.68105158 C8.65797929,2.72048519 8.6219798,2.76598551 8.60997998,2.82210258 C8.59198024,2.9009698 8.7614778,2.87973632 8.79897727,2.87821964 C8.86347635,2.87518629 8.96547488,2.88580303 9.02697399,2.85546948 C9.09297304,2.81906922 9.12747255,2.73868532 9.19647157,2.6992517 C9.25347076,2.6658848 9.33446958,2.64768467 9.39746869,2.67043483 C9.46346774,2.693185 9.45596785,2.77811893 9.50996707,2.81451919 C9.57296618,2.85850283 9.64196518,2.87215293 9.6959644,2.8069358 C9.73046391,2.76598551 9.80696281,2.71441848 9.80996278,2.67043483 C9.81596269,2.59308428 9.83846236,2.53241719 9.92396114,2.51573374 C9.99296016,2.50208364 9.97796036,2.56881745 10.0259597,2.58095087 C10.1324581,2.60825106 10.1834574,2.28368209 10.2989558,2.37923277 C10.3259554,2.40198293 10.3334553,2.48995022 10.3799546,2.48236683 C10.4279539,2.47478345 10.4294539,2.40501628 10.4804532,2.40349961 C10.4969529,2.45051661 10.3919544,2.50815035 10.3784546,2.55971738 C10.4429537,2.50663367 10.4744532,2.51421706 10.5479522,2.50663367 C10.5674519,2.55668403 10.423454,2.63858461 10.3859545,2.64616799 C10.3334553,2.65981809 10.3019557,2.62948454 10.2614563,2.65830141 C10.2299568,2.6795349 10.1864574,2.67801822 10.1489579,2.68105158 C10.0964587,2.68711829 9.99746008,2.75688545 9.99896007,2.81451919 C9.99896007,2.83726935 10.0169598,2.88883638 9.99746008,2.90703651 C9.97946034,2.92675332 9.93596097,2.90855319 9.93146103,2.88883638 C9.88646168,2.95557019 9.82796252,2.83878603 9.79046305,2.93282003 C9.85046218,2.9479868 9.9059614,3.00562055 9.97346042,3.022304 C10.0394595,3.03898745 10.1054585,3.0556709 10.1699576,3.07387103 C10.279456,3.10723793 10.4429537,2.975287 10.5269525,2.91158654 C10.6064514,2.85243612 10.7069499,2.71896851 10.7279496,2.62341783 C10.7519493,2.51876709 10.8659476,2.39894957 10.841948,2.29581551 C10.8209483,2.19874815 10.8059485,2.15324783 10.9154469,2.11836425 C10.9619463,2.10319748 11.0744446,2.07893064 11.0909444,2.02584693 C11.1149441,1.9469797 10.8674476,1.96821318 10.8299482,1.95001306 C10.7054499,1.89389599 10.6514507,1.83171222 10.5074528,1.88782928 C10.4324539,1.91664615 10.3589549,1.94091299 10.280956,1.96214647 C10.2404566,1.97276322 10.1999572,1.97579657 10.1774575,2.01068015 C10.1684576,2.02433025 10.1564578,2.03494699 10.141458,2.04253038 C10.0769589,2.06831389 10.1564578,1.94546302 10.1639577,1.93787964 C10.1849574,1.9136128 10.2194569,1.8392956 10.1519579,1.85597906 C10.0529593,1.87872922 9.98096031,2.03039696 9.87446184,2.03798035 C9.79346301,2.04404706 9.81896264,1.97276322 9.83996235,1.93181293 C9.88046177,1.85749573 9.76496342,1.84839567 9.71396414,1.84839567 C9.64196518,1.84839567 9.58796595,1.88934596 9.51896694,1.89692935 C9.45446788,1.90299606 9.37946895,1.91512947 9.31496987,1.9136128 C9.18597171,1.90906277 9.10047295,1.98489664 8.97297477,1.94242967 C8.8379767,1.89844602 8.69247879,2.01068015 8.56198067,2.02584693 C8.51848128,2.03191364 8.45548219,2.02281357 8.43748245,2.0743806 C8.42248266,2.11684757 8.43748245,2.1820647 8.47048197,2.21391493 L8.48098182,2.20481486 C8.45248223,2.23363173 8.44948228,2.27458203 8.40748288,2.2897488 C8.36698346,2.3033989 8.32648404,2.35648261 8.30548433,2.39288286 C8.28898458,2.42018306 8.24248525,2.53393387 8.32348407,2.47478345 C8.38198324,2.4307998 8.41498277,2.34889922 8.50798144,2.36558267 L8.50798144,2.36558267 L8.50798144,2.36558267 Z M3.65405101,9.54705029 C3.516053,9.44694958 3.20255749,9.42268274 3.24155692,9.18911442 C3.26555658,9.04654674 3.41255447,8.93582929 3.53105277,8.86606213 C3.68705055,8.77506148 3.86104805,8.77202812 4.03654553,8.7871949 C4.07854493,8.79174493 4.15804379,8.78416154 4.18054347,8.81752845 C4.19254329,8.8342119 4.21654295,8.84482864 4.23604266,8.85089535 C4.282542,8.86454545 4.33054131,8.86606213 4.37854064,8.87516219 C4.4505396,8.88881229 4.50153886,8.95099606 4.57353783,8.89942903 C4.65603664,8.84179529 4.66953646,8.82966187 4.77003502,8.84179529 C4.86003373,8.85241203 4.91403295,8.78416154 4.98903188,8.79022825 C5.01303154,8.79174493 5.03403123,8.79629496 5.05203097,8.80387835 C5.05953087,8.77809483 5.07003071,8.75534467 5.08653048,8.75079464 C5.12402995,8.7401779 5.20652875,8.83117854 5.2455282,8.83876193 C5.34452678,8.85999542 5.33852687,8.79022825 5.34602676,8.71894441 C5.39552604,8.70984435 5.4195257,8.77809483 5.46452506,8.73562787 C5.46302509,8.74927796 5.47202495,8.77051145 5.47202495,8.78416154 C5.48102483,8.79022825 5.49152468,8.79022825 5.50052454,8.78264487 C5.50502448,8.77506148 5.50652446,8.76747809 5.50352451,8.75837803 C5.52752416,8.76596142 5.53952399,8.74927796 5.54252394,8.72046109 C5.56052368,8.72197777 5.58902327,8.71136103 5.60702302,8.71439438 C5.62052282,8.66282735 5.6505224,8.59457687 5.61302293,8.54755987 C5.62202281,8.54604319 5.63252265,8.54300983 5.6430225,8.54149316 C5.6430225,8.48992612 5.67752201,8.46717596 5.67902198,8.42622567 C5.62502276,8.41864228 5.56502362,8.42167564 5.50952442,8.42319232 C5.54252394,8.39285877 5.62202281,8.32612496 5.6295227,8.28517467 C5.64452249,8.21237415 5.55002384,8.16839051 5.55752373,8.0819399 C5.56652359,8.12744022 5.61752287,8.23209096 5.66552218,8.24725773 C5.77352063,8.28365799 5.74202108,8.1759739 5.74952097,8.11985683 C5.77652059,7.94240557 5.93551831,8.08952328 5.93851826,8.18204061 C5.9850176,8.07587319 6.10051595,8.19417402 6.0480167,8.28517467 C6.02251706,8.32915832 5.98801756,8.31247486 6.01951711,8.37010861 C6.04201679,8.4110589 6.07501631,8.41257557 6.12301562,8.40195883 C6.13501544,8.37769199 6.1455153,8.3503918 6.1455153,8.32157493 C6.22801412,8.29579141 6.28201334,8.39437545 6.23401403,8.45352586 C6.29551316,8.42015896 6.35851224,8.38830874 6.4245113,8.37162528 C6.38551186,8.23512432 6.34501244,8.1016567 6.37051207,7.95605567 C6.37651198,7.92420544 6.38101192,7.88780518 6.40501158,7.86353835 C6.43501115,7.83168812 6.39751169,7.84382154 6.39451173,7.82258806 C6.38551186,7.7588876 6.45301089,7.69367047 6.47851052,7.63603673 C6.40801154,7.62086996 6.47251061,7.48133563 6.51900994,7.4570688 C6.57000922,7.43128528 6.72000706,7.47375225 6.73050691,7.43280196 C6.76050648,7.44948541 6.78900608,7.47375225 6.82500556,7.47375225 C6.90750438,7.47526892 6.96300358,7.4767856 7.02000276,7.54351941 C7.04850236,7.57840299 7.09050176,7.65575354 7.14150103,7.66182025 C7.14000104,7.71945399 7.20600009,7.76192096 7.13700109,7.81045464 C7.08300187,7.84837157 7.00800294,7.83927151 6.98550326,7.91207202 C6.97050348,7.95757235 6.92700409,7.97880583 6.99300314,8.01065606 C7.02150274,8.02582283 7.05750222,8.02885619 7.08900178,8.02885619 C7.09650167,8.07283983 7.11900135,8.13199025 7.17600052,8.12440686 C7.28699894,8.11075677 7.31099859,7.97122244 7.39949732,7.92420544 C7.54349525,7.84837157 7.52099559,8.1759739 7.6454938,8.09407331 C7.67549337,8.07435651 7.67549337,7.99093925 7.69049315,7.95908902 C7.72049272,7.89235522 7.75499223,7.82410473 7.79849161,7.76495431 C7.85399081,7.68912044 7.92298983,7.61025322 7.89899017,7.51166918 C7.88549035,7.4570688 7.78049187,7.43431863 7.73249254,7.3964017 C7.67549337,7.3493847 7.61999416,7.29933434 7.58549466,7.23411721 C7.56449496,7.1946836 7.55249513,7.1810335 7.58549466,7.1613167 C7.60499438,7.15069995 7.59899447,7.12946647 7.59149458,7.1142997 C7.5554951,7.03846583 7.45799649,6.90348153 7.59449453,6.85646453 C7.6244941,6.84584779 7.6874932,6.74878044 7.69199314,6.7108635 C7.69949303,6.64716305 7.60349441,6.58952931 7.64699378,6.52582885 C7.67849332,6.47881185 7.75799219,6.44847831 7.79849161,6.40146131 C7.81799133,6.37871114 7.84049101,6.35899434 7.87049058,6.35292763 C7.87199055,6.32714411 7.87799047,6.29832724 7.90049015,6.28164379 C7.93648963,6.25434359 7.99348882,6.26799369 8.03548821,6.25434359 C8.10448721,6.23311011 8.13448678,6.16182627 8.18848602,6.12239266 C8.23348537,6.08902576 8.28448464,6.10115917 8.33248395,6.07689234 C8.35798358,6.06475892 8.36848344,6.03745872 8.39398307,6.0253253 C8.45698217,5.99499175 8.52898113,6.04352543 8.55748073,6.09660914 C8.62497975,6.22401005 8.70897855,6.42117811 8.9009758,6.36961108 C8.97897468,6.3483776 9.03747384,6.28467714 9.0614735,6.21187663 C9.08397318,6.14210947 9.05697356,6.08447572 9.0614735,6.01622524 C9.06747341,5.89489104 9.18597171,5.8175405 9.19947152,5.69772298 C9.10947281,5.69923966 9.15597215,5.64312259 9.12597258,5.59155556 C9.09297304,5.53392182 9.01797413,5.57638878 8.96847483,5.56728872 C9.0194741,5.44443785 9.0194741,5.40197088 8.89947583,5.34282046 C8.84697658,5.31703694 8.75997782,5.19570275 8.7194784,5.20025278 C8.75247793,5.15475246 8.83047681,5.23058633 8.85447647,5.25181982 C8.90697572,5.30187017 8.95347505,5.32462033 9.02697399,5.33068704 C9.0059743,5.29883682 8.99547445,5.24120307 9.00897425,5.20480282 C9.02247405,5.17143591 8.98947453,5.13655233 8.99247448,5.09408536 C9.07647329,5.20176946 9.06297347,5.32462033 9.11097278,5.44292117 C9.13047252,5.49297153 9.1799718,5.52633843 9.20097151,5.57790546 C9.22797111,5.64312259 9.20997137,5.64160591 9.26697056,5.68103953 C9.30147007,5.70530637 9.31346989,5.74777333 9.31946981,5.78569027 C9.32996965,5.85242408 9.35396932,5.82360721 9.3899688,5.86152414 C9.41096849,5.8842743 9.46496771,5.88730766 9.45446788,5.93432466 C9.44696797,5.96769156 9.42446829,5.99499175 9.41996837,6.02987534 C9.40646855,6.12997605 9.60296575,5.99347508 9.62246546,5.97982498 C9.66446486,5.94797475 9.73346387,5.94190804 9.76646339,5.90399111 C9.8009629,5.8645575 9.79346301,5.80692375 9.82946249,5.7705235 C9.87446184,5.7235065 9.91796123,5.75535672 9.97196045,5.74625666 C10.0349596,5.73715659 10.0889588,5.68710624 10.1354581,5.6491893 C10.2344567,5.56577204 10.2974558,5.4747714 10.3799546,5.37922072 C10.3439551,5.38832078 10.2209569,5.47780475 10.213457,5.39438749 C10.1669577,5.39438749 10.0589592,5.38680411 10.0424594,5.33523707 C10.0289596,5.29732014 10.0334596,5.25485317 10.0334596,5.21693623 C10.0319596,5.17598594 9.9824603,5.18963604 9.94946077,5.16840256 C9.88346172,5.12593559 9.85046218,5.04706836 9.77996321,5.0106681 C9.66746482,4.95151768 9.59996578,4.85596701 9.53246675,4.75434962 C9.49346731,4.6951992 9.35546929,4.57538168 9.36596914,4.50409784 C9.37196906,4.45708084 9.41096849,4.40703049 9.40796854,4.36001349 C9.40646855,4.31754652 9.37346903,4.29479636 9.37796897,4.24777936 C9.38246889,4.19317897 9.25047079,4.09762829 9.36596914,4.08701155 C9.40196863,4.0839782 9.40796854,4.03999455 9.44846795,4.01572771 C9.49346731,3.98842752 9.48296745,3.96416068 9.53246675,3.97781078 C9.61196561,4.00207762 9.66746482,3.91411032 9.72146405,3.86709332 C9.8144627,3.78519274 9.66596483,3.78367607 9.65846494,3.72149229 C9.65096506,3.65930852 9.61496557,3.61380819 9.60446572,3.54100768 C9.59846581,3.48792397 9.54896651,3.50915745 9.52046693,3.52280755 C9.48146748,3.54100768 9.44246803,3.51370748 9.40496858,3.5061241 C9.37046907,3.49854071 9.34196947,3.44090697 9.29997008,3.4591071 C9.26847053,3.47427387 9.26997051,3.51219081 9.22497116,3.50764077 C9.19197163,3.50460742 9.17097194,3.47275719 9.1379724,3.46669048 C9.08697313,3.46062377 9.13197249,3.50915745 9.0749733,3.51370748 C9.0344739,3.51674084 8.90397575,3.46214045 8.9009758,3.51370748 C8.86047638,3.44394032 8.84397661,3.56224116 8.80047724,3.57437458 C8.75247793,3.58802468 8.70147866,3.57589126 8.65347935,3.59409139 C8.54848085,3.63655836 8.58298036,3.7412091 8.68347892,3.75789255 C8.76447776,3.77002597 8.65947926,3.82614303 8.68647888,3.88074342 C8.71047854,3.9292771 8.71797842,3.962644 8.77047767,3.98236081 C8.85747643,4.01421103 8.95047509,4.03999455 8.92047552,4.15222868 C8.88147609,4.291763 8.78397748,4.42371394 8.64747943,4.48741439 C8.51698131,4.54808149 8.48098182,4.38276365 8.38198324,4.34029668 C8.32048413,4.31451317 8.25148511,4.32361323 8.18698603,4.33119662 C8.17648618,4.34788007 8.27698474,4.37973029 8.29348451,4.40854717 C8.32348407,4.46769759 8.24098526,4.4601142 8.23498534,4.50561452 C8.22898543,4.54353146 8.17948614,4.57083165 8.20648576,4.60874859 C8.17798617,4.57386501 8.12248696,4.62088201 8.10298724,4.64211549 C8.07448765,4.67244904 8.08048756,4.69216584 8.09248739,4.73008278 C8.11648704,4.80743333 7.99948873,4.88781723 7.93048971,4.87871717 C7.87199055,4.8696171 7.81649135,4.87416714 7.76099214,4.84686694 C7.69499309,4.81501672 7.71749277,4.83473352 7.70399297,4.76041633 C7.69049315,4.69064917 7.59449453,4.66031562 7.65149372,4.57386501 C7.69199314,4.51016455 7.67249341,4.51623126 7.66499352,4.45556417 C7.65599366,4.39186371 7.68449323,4.38276365 7.73549251,4.37366358 C7.79099171,4.36304684 7.81499136,4.26446281 7.8479909,4.21744581 C7.85549078,4.20682907 7.88999029,4.11886178 7.83599107,4.14161194 C7.8059915,4.15526204 7.82849118,4.19317897 7.78199184,4.19924568 C7.74899231,4.20531239 7.71599279,4.18256223 7.68149329,4.18256223 C7.64249384,4.18256223 7.60199442,4.20227904 7.56599493,4.1780122 C7.58399467,4.15677871 7.71299283,4.05061129 7.61099429,4.02937781 C7.57049487,4.02027774 7.60349441,4.08701155 7.54949518,4.07791149 C7.53899533,4.1309952 7.47449625,4.12644516 7.44299671,4.15981207 C7.45649651,4.103695 7.57499481,4.06122803 7.53599536,4.01572771 C7.62149415,3.94141052 7.63949389,3.92776042 7.52399554,3.88681013 C7.34249815,3.82310968 7.35599795,3.63655836 7.48049617,3.52432423 C7.59449453,3.42119016 7.78199184,3.2907559 7.89299026,3.46214045 C8.01148856,3.64565842 8.08648747,3.51219081 8.18398608,3.39388997 C8.15098655,3.38023987 8.18098612,3.37113981 8.17048627,3.33018951 C8.06248782,3.37417316 7.9664892,3.23463884 8.03998814,3.15122158 C8.0849875,3.10117122 8.15398651,3.11482132 8.21398565,3.09965455 C8.2664849,3.08600445 8.31448421,3.03443742 8.33698389,2.98590374 C8.29198453,2.99803716 8.29798444,2.97073696 8.31448421,2.95102016 C8.28748459,2.9479868 8.25748502,2.93585338 8.23498534,2.92827 C8.17048627,2.90551983 8.1749862,2.85546948 8.1059872,2.84636942 C7.94248954,2.82210258 8.27698474,2.63403458 8.11348709,2.63403458 C8.06098784,2.6325179 8.01448851,2.55365067 7.97548908,2.56881745 C7.94848946,2.57943419 7.94098957,2.60066767 7.90799003,2.58701758 C7.88549035,2.57791751 7.85849075,2.5582007 7.83149113,2.57336748 C7.77299197,2.60976774 7.76099214,2.56578409 7.70399297,2.58246754 C7.65749363,2.59763432 7.63199399,2.64313464 7.57799476,2.63100122 C7.63199399,2.5582007 7.69799305,2.49753361 7.74599236,2.42169974 C7.77749191,2.3701327 7.81649135,2.3231157 7.8689906,2.29126548 C7.89749018,2.27458203 7.97848902,2.25789857 7.98298896,2.21846496 C7.99048885,2.15324783 7.94998943,2.15931454 7.90499008,2.18509806 C7.78799176,2.25486522 7.66649349,2.32918241 7.55249513,2.40349961 C7.48349612,2.44748325 7.42949689,2.48540019 7.34399812,2.47326677 C7.27799907,2.46265003 7.25099945,2.53545054 7.1985002,2.52938383 C7.17300057,2.42776645 6.61050864,2.79631906 6.54150962,2.82816929 C6.4320112,2.87670296 6.30901296,2.96012022 6.19201463,2.98893709 C6.14401532,3.00107051 6.04351676,3.11330464 6.04951668,2.98893709 C5.98951754,2.98135371 5.94451818,3.0420208 5.90401876,3.07235435 C5.84701958,3.116338 5.77952054,3.14363819 5.71802143,3.18155513 C5.58602333,3.26648906 5.46152511,3.36810645 5.33852687,3.46365713 C5.22152854,3.55465777 5.10453022,3.66082519 4.98153198,3.74272577 C4.93953258,3.77154265 4.7850348,3.85040987 4.78953474,3.90956029 C4.89903316,3.93079378 5.27252782,3.46365713 5.3775263,3.58954135 C5.40452592,3.62139158 5.2185286,3.71542558 5.18852901,3.73362571 C5.16302938,3.74727581 5.13302981,3.74575913 5.10753018,3.75940923 C5.07453065,3.77912603 5.05353096,3.81249294 5.0220314,3.83372642 C4.93803261,3.88681013 4.86753362,3.95506061 4.8060345,4.03089449 C4.76253512,4.08701155 4.73103557,4.15981207 4.68303626,4.2113791 C4.69053615,4.15677871 4.6800363,4.1173451 4.68153629,4.06426139 C4.62003716,4.103695 4.59453753,4.17042881 4.50903876,4.15222868 C4.43103987,4.13402855 4.36954076,4.21289578 4.31404156,4.25536275 C4.1850434,4.35394678 4.09804465,4.46314755 3.9900462,4.57841504 C3.93004706,4.64363217 3.86554798,4.68913249 3.82804851,4.76951639 C3.78754911,4.85596701 3.73054992,4.93331756 3.67805067,5.01370146 C3.57755211,5.16081917 3.46205377,5.29580346 3.36305519,5.44292117 C3.16055809,5.74473998 3.02706,6.09054243 2.86056239,6.41207805 C2.77506361,6.57891256 2.6925648,6.74271373 2.65806529,6.92926505 C2.62806573,7.09003286 2.62656574,7.25383402 2.6295657,7.41763518 C2.72106438,7.34635134 2.71656446,7.49498573 2.6925648,7.53896938 C2.65806529,7.60721986 2.64456548,7.68457041 2.63406564,7.76040428 C2.61906585,7.85898831 2.60106611,7.95757235 2.60106611,8.05767306 C2.60106611,8.14260699 2.5740665,8.22147422 2.57256651,8.3033748 C2.54106697,8.27910796 2.60856599,8.1759739 2.54856686,8.19114067 C2.51856729,8.19872406 2.51706731,8.2427077 2.50956742,8.26545786 C2.48556777,8.34432509 2.3790693,8.3367417 2.36256953,8.42622567 C2.35356965,8.48082606 2.34756974,8.51267628 2.31157026,8.55665993 C2.28307066,8.59002683 2.31007028,8.60519361 2.31607019,8.63856051 C2.32957,8.71742774 2.22757146,8.82662851 2.25457108,8.89032896 C2.28007071,8.95251274 2.26207097,9.0222799 2.29057056,9.08143032 C2.30557034,9.11176387 2.34006985,9.15119748 2.32657005,9.18911442 C2.26207097,9.20124784 2.34456979,9.34988222 2.3505697,9.39538255 C2.35956957,9.46969974 2.42556862,9.70326807 2.4975676,9.73663497 C2.5875663,9.87465261 2.71056454,10.0672706 2.86956225,10.1370378 C2.98206064,10.1855715 3.02256006,10.0399705 3.08555916,9.97930336 C3.16505802,9.90043613 3.26855654,9.85038578 3.37355504,9.81398552 C3.46205377,9.78213529 3.83254845,9.67445119 3.65405101,9.54705029 L3.65405101,9.54705029 L3.65405101,9.54705029 Z M3.81004877,14.5293356 C3.82804851,14.4990021 3.81454871,14.4216515 3.78154919,14.395868 C3.70205032,14.3291342 3.67055078,14.4868687 3.72155004,14.5338857 C3.74104977,14.583936 3.78754911,14.5672526 3.81004877,14.5293356 L3.81004877,14.5293356 L3.81004877,14.5293356 Z M4.1355441,9.9429031 C4.11304444,9.92318629 4.09654467,9.93531971 4.0950447,9.89436942 C4.09654467,9.85796916 4.09954462,9.788202 4.05154531,9.83218565 C4.03804551,9.83673568 4.05604525,9.84431907 4.03654553,9.85190245 C4.02304572,9.85645249 4.01254588,9.84583574 4.003546,9.84128571 C3.97804637,9.83066897 3.96304658,9.82915229 3.94354686,9.85645249 C3.93004706,9.87465261 3.93004706,9.8958861 3.90754738,9.91105287 L3.86854793,9.92470297 C3.85504813,9.929253 3.81604868,9.9565532 3.81454871,9.97171997 C3.8085488,9.99447013 3.8430483,10.0111536 3.86704796,10.0172203 C3.88654768,10.0308704 3.91204732,10.0430038 3.93154704,10.0566539 C3.95104675,10.070304 3.98254631,10.0945708 4.00654597,10.1006376 C4.06054519,10.1309711 4.14454398,10.164338 4.18654338,10.1006376 C4.19854321,10.0763707 4.20754309,10.0581706 4.19104332,10.0369371 C4.17604353,10.0141869 4.15204387,10.0081202 4.14454398,9.9899201 C4.13854407,9.97020329 4.15504384,9.95806987 4.1355441,9.9429031 L4.1355441,9.9429031 L4.1355441,9.9429031 Z M11.3489407,11.0879946 C11.3204411,11.0895112 11.2694418,11.1077114 11.2469422,11.1274282 C11.2019428,11.1668618 11.2979414,11.1896119 11.3354409,11.2002287 C11.3774403,11.2244955 11.4374394,11.2366289 11.4779389,11.2608958 C11.5124384,11.2866793 11.536438,11.3215629 11.5754375,11.3382463 C11.6234368,11.3609965 11.6894358,11.3716132 11.7419351,11.3852633 C11.7644348,11.3928467 11.7989343,11.39133 11.8289338,11.3973968 C11.8619334,11.4171136 11.8769331,11.4474471 11.9039328,11.4686806 C11.9504321,11.5111476 12.0164312,11.5217643 12.0779303,11.5187309 C12.1364294,11.5247977 12.1799288,11.5354144 12.232428,11.5217643 C12.2924272,11.5065975 12.3344266,11.5369311 12.3899258,11.5369311 C12.4124255,11.5369311 12.4349251,11.5187309 12.4559248,11.5202476 C12.4844244,11.5202476 12.4874244,11.532381 12.5009242,11.5566479 C12.5249239,11.5915315 12.586423,11.6446152 12.6299223,11.6461318 C12.656922,11.6461318 12.6779217,11.6415818 12.7004213,11.6491652 C12.725921,11.664332 12.7364208,11.664332 12.7544206,11.6794988 C12.7859201,11.6931489 12.8129197,11.7022489 12.8264195,11.7265158 C12.8504192,11.7689827 12.8489192,11.8159997 12.8864187,11.8493666 C12.9119183,11.8690834 12.9389179,11.8903169 12.9659175,11.9100337 C12.9839173,11.9267172 12.9689175,11.9236838 12.9959171,11.9236838 C13.0109169,11.9267172 13.0394165,11.9267172 13.0589162,11.9206505 C13.1339151,11.9161004 13.0934157,11.809933 13.0694161,11.7750494 C13.0544163,11.7447159 13.0409165,11.720449 13.0469164,11.6916322 C13.0514163,11.6567486 13.0679161,11.6324818 13.0454164,11.6036649 C13.0334166,11.5854648 13.0169168,11.5763647 13.000417,11.5672646 C12.9914172,11.5551312 12.9869172,11.5429978 12.9764174,11.5247977 C12.9539177,11.4959808 12.9104183,11.4868807 12.8819187,11.4595805 C12.8339194,11.4110468 12.8084198,11.3412797 12.7469207,11.2957794 C12.7139211,11.2760626 12.6839216,11.2912293 12.6434222,11.2745459 C12.6269224,11.2639291 12.6179225,11.2533124 12.5939229,11.245729 C12.5714232,11.2381456 12.5519235,11.2426957 12.5309238,11.241179 C12.4874244,11.2381456 12.4499249,11.2002287 12.4079255,11.203262 C12.3614262,11.2093288 12.3524263,11.2593791 12.3254267,11.2866793 C12.3014271,11.3063961 12.2744274,11.3063961 12.2654276,11.2745459 C12.2624276,11.2335956 12.2774274,11.2093288 12.2984271,11.1835452 C12.3314266,11.1486617 12.2984271,11.1289448 12.2549277,11.1259115 C12.1994285,11.1259115 12.1889287,11.1698951 12.166429,11.2169121 C12.1304295,11.2669625 12.1079298,11.2320789 12.0569306,11.2244955 C12.0224311,11.2260122 11.9984314,11.2396623 11.9654319,11.2260122 C11.9429322,11.2184288 11.9384323,11.2002287 11.9219325,11.1896119 C11.8964329,11.1759618 11.8769331,11.1805119 11.8589334,11.1926453 C11.8334338,11.198712 11.8334338,11.198712 11.8079341,11.1850619 C11.7854345,11.1759618 11.7794345,11.1577617 11.750935,11.151695 C11.7059356,11.1425949 11.6594363,11.1805119 11.6219368,11.1698951 C11.605437,11.1607951 11.5919372,11.1365282 11.5694376,11.1289448 C11.5439379,11.1137781 11.5484378,11.1274282 11.5319381,11.1456283 C11.5034385,11.1729285 11.4644391,11.1820286 11.4374394,11.1607951 C11.4029399,11.1365282 11.39994,11.0955779 11.3489407,11.0879946 L11.3489407,11.0879946 L11.3489407,11.0879946 Z M13.0619162,12.8807073 C13.0859158,12.8776739 13.0979156,12.8534071 13.1189153,12.8564404 C13.142915,12.8518904 13.1309152,12.8761572 13.1474149,12.891324 C13.1624147,12.9049741 13.1774145,12.9049741 13.1924143,12.9049741 C13.2194139,12.9095241 13.2719132,12.9125575 13.282413,12.8882907 C13.2944128,12.8503737 13.2314137,12.8427903 13.214914,12.8124568 C13.1999142,12.7684731 13.2344137,12.7260062 13.2464135,12.6880892 C13.2644132,12.6365222 13.1999142,12.613772 13.2059141,12.5728218 C13.2044141,12.5288381 13.2344137,12.5136713 13.2254138,12.4712044 C13.2179139,12.4393541 13.1909143,12.4044706 13.1714146,12.3817204 C13.1534148,12.3574536 13.1189153,12.3347034 13.1219153,12.2998198 C13.1249153,12.2634196 13.1909143,12.2649362 13.1594148,12.2209526 C13.141415,12.1830357 13.0934157,12.190619 13.0499163,12.1845523 C13.0349165,12.1845523 13.0199168,12.186069 13.004917,12.1709022 C12.9914172,12.1496688 12.9974171,12.139052 12.9974171,12.1238852 C12.9884172,12.0829349 12.9569177,12.0677682 12.9209182,12.0510847 C12.9089183,12.045018 12.8909186,12.0374346 12.8834187,12.0192345 C12.8774188,12.0010344 12.8954185,11.9949677 12.8894186,11.9782842 C12.8699189,11.9388506 12.7979199,11.993451 12.7679204,11.9798009 C12.7469207,11.9767675 12.7499206,11.9570507 12.7364208,11.9358172 L12.7004213,11.9191338 C12.6509221,11.8963836 12.6314223,11.9373339 12.6404222,11.9782842 C12.6629219,12.0708015 12.7304209,12.1314686 12.722921,12.2239859 C12.727421,12.2619029 12.7334209,12.280103 12.7469207,12.3149866 C12.7589205,12.365037 12.7724203,12.3893038 12.7499206,12.4378375 C12.7139211,12.4651377 12.7469207,12.4985046 12.7589205,12.5333881 C12.7664204,12.5804051 12.7784202,12.6168054 12.7769202,12.6668558 C12.7679204,12.7578564 12.7394208,12.8473404 12.7469207,12.9398577 C12.7514206,12.9777746 12.7484206,13.0126582 12.7619205,13.0490585 C12.7679204,13.0960755 12.8054198,13.1127589 12.8444193,13.1385424 C12.8819187,13.1703927 13.0559162,13.2750434 13.004917,13.1476425 C12.9899172,13.1188256 12.9659175,13.0778753 12.9584176,13.0445084 C12.9464178,13.0096249 12.9884172,12.985358 12.9899172,12.9489578 C12.9959171,12.9080075 12.9644176,12.8943574 13.0229167,12.8837406 C13.0349165,12.8746406 13.0529163,12.8837406 13.0619162,12.8807073 L13.0619162,12.8807073 L13.0619162,12.8807073 Z M11.2529421,1.95152973 C11.2994414,1.94091299 11.3459408,1.95456309 11.3894401,1.93787964 C11.4119398,1.92877957 11.4854388,1.90299606 11.4809388,1.87266251 C11.4734389,1.81654544 11.2349423,1.8529457 11.1959429,1.86962915 C11.1839431,1.90451273 11.2199426,1.93181293 11.2514421,1.94091299 C11.2514421,1.94394635 11.2529421,1.94849638 11.2529421,1.95152973 L11.2529421,1.95152973 L11.2529421,1.95152973 Z M12.3989257,12.0950684 C12.3899258,12.0753516 12.3989257,12.0571514 12.3989257,12.0374346 C12.3944257,12.0055844 12.3854259,11.9964843 12.3899258,11.9631174 C12.4004256,11.9449173 12.4004256,11.9161004 12.3959257,11.8933503 C12.3869258,11.8751501 12.3719261,11.8599834 12.3584262,11.8463333 C12.3584262,11.8372332 12.3539263,11.8220664 12.3434265,11.8129664 C12.3254267,11.7947662 12.305927,11.8220664 12.2864273,11.8311665 C12.2714275,11.8448166 12.2429279,11.8539167 12.238428,11.8690834 C12.2234282,11.8918336 12.232428,11.9100337 12.232428,11.9297505 L12.236928,11.9525007 C12.2039285,11.9858676 12.235428,12.0738349 12.2309281,12.1072018 C12.2309281,12.1451187 12.1769288,12.2512861 12.2489278,12.2194359 C12.2684275,12.2103359 12.2834273,12.1966858 12.3014271,12.1875857 C12.3254267,12.1739356 12.3554263,12.1739356 12.3839259,12.1648355 C12.3929257,12.1648355 12.445425,12.1602855 12.445425,12.1511854 C12.446925,12.1314686 12.4034256,12.1178185 12.3989257,12.0950684 L12.3989257,12.0950684 L12.3989257,12.0950684 Z M11.0444451,14.0121486 C11.1044442,14.0409655 11.2094427,13.9833318 11.2664419,13.9651316 C11.3384409,13.9423815 11.4524392,13.8665476 11.5259382,13.9090146 C11.5559377,13.925698 11.5709375,13.9605816 11.6024371,13.9742317 C11.6414365,13.9909152 11.6879359,13.9757484 11.7269353,13.9666483 C11.7674347,13.9575482 11.813934,13.9514815 11.8514335,13.9332814 C11.884433,13.916598 11.9054327,13.8892978 11.9339323,13.8680643 C12.0074313,13.8119472 12.0704304,13.8650309 12.1514292,13.8528975 C12.1979285,13.8468308 12.2414279,13.8240806 12.2864273,13.8119472 C12.3194268,13.8043638 12.377926,13.8043638 12.4034256,13.780097 C12.4319252,13.7527968 12.4184254,13.6936464 12.4184254,13.6587628 C12.4169254,13.6117458 12.4199254,13.5632121 12.4034256,13.5192285 C12.3704261,13.4327779 12.2504278,13.3311605 12.374926,13.26291 C12.3989257,13.1112422 12.2219282,13.1370258 12.1709289,13.027825 C12.1379294,12.9565411 12.1274295,12.9019408 12.0329309,12.8943574 C11.953432,12.886774 11.9054327,12.929241 11.8364337,12.9565411 C11.7584348,12.985358 11.6984357,12.9595745 11.6309367,12.9216576 C11.5904373,12.8989074 11.5049385,12.8458237 11.4884387,12.9155909 C11.4734389,12.976258 11.5319381,13.0354084 11.4839388,13.0900088 C11.4419394,13.1385424 11.3684404,13.1597759 11.3084413,13.173426 C11.1779432,13.2007262 11.0744446,13.3008269 10.979946,13.3872775 L10.9904458,13.3963776 C10.9529464,13.3948609 10.8974472,13.4949616 10.8959472,13.5252952 C10.910947,13.5298452 10.9244468,13.5343952 10.9409466,13.5389453 C10.9394466,13.5905123 10.9979457,13.5556287 11.0024457,13.5192285 C11.0144455,13.5222618 11.0264453,13.5298452 11.0384452,13.5313619 C11.048945,13.5343952 11.0714447,13.5328786 11.0804446,13.5374286 C11.1059442,13.5480453 11.1104441,13.5723122 11.1419437,13.5753455 C11.1239439,13.6542128 11.1404437,13.73763 11.1029442,13.8119472 C11.0789446,13.8574475 10.9574463,13.9696817 11.0444451,14.0121486 L11.0444451,14.0121486 L11.0444451,14.0121486 Z M11.6639362,2.34586586 C11.7089356,2.39439954 11.7644348,2.40804964 11.7539349,2.48388351 C11.8109341,2.4914669 11.8469336,2.51270038 11.8799331,2.4641667 C11.9009328,2.43383315 11.9324323,2.40956632 11.9669319,2.3974329 C12.0089312,2.38074944 12.1814288,2.38226612 12.1739289,2.45354996 C12.1694289,2.48843354 12.1484293,2.51876709 12.1424293,2.55365067 C12.1349295,2.60218435 12.1874287,2.56730077 12.2099284,2.57943419 C12.1844287,2.59763432 12.1529292,2.60825106 12.1214296,2.61583445 C12.1349295,2.62493451 12.1439293,2.63706793 12.1454293,2.6522347 C12.1064299,2.66133477 12.0869301,2.77053554 12.0179311,2.79176903 C11.9759317,2.80541912 11.9144326,2.77660225 11.8724332,2.77205222 C11.8229339,2.76598551 11.7854345,2.75081874 11.7359352,2.74778538 C11.6879359,2.74475203 11.7269353,2.68105158 11.6669361,2.693185 C11.6564363,2.73565196 11.677436,2.84333606 11.6849359,2.88580303 C11.6924358,2.93888674 11.7374351,2.96922029 11.7899344,2.97832035 C11.8634333,2.99045377 11.8979328,3.01472061 11.9609319,3.05112087 C12.0104312,3.07842106 12.0659304,3.06173761 12.1199297,3.06628764 C12.1559291,3.069321 12.1859287,3.08297109 12.2129283,3.10572126 C12.2069284,3.12240471 12.1934286,3.1497049 12.2024285,3.16790503 C12.2129283,3.19217187 12.2894272,3.16487168 12.307427,3.163355 C12.3614262,3.15728829 12.4124255,3.09813787 12.4634247,3.10572126 C12.4829245,3.10875461 12.5729232,3.1360548 12.5684232,3.15880497 C12.5204239,3.13908816 12.4889244,3.19823858 12.4514249,3.16638835 C12.4184254,3.13757148 12.3299267,3.16183832 12.3869258,3.20278861 C12.3914258,3.20733864 12.4064255,3.32108945 12.4064255,3.33322287 C12.4019256,3.37568984 12.3254267,3.42119016 12.3314266,3.44849035 C12.3419265,3.45000703 12.4109255,3.45455706 12.4259253,3.46820716 C12.4304252,3.45000703 12.4169254,3.44242364 12.4589248,3.43029022 C12.4904244,3.42119016 12.5279238,3.41815681 12.5594234,3.43332358 C12.5714232,3.48640729 12.5324238,3.54100768 12.6059227,3.52735758 C12.6749217,3.51370748 12.7049213,3.55920781 12.7769202,3.51219081 C12.8219196,3.48489061 12.8699189,3.48944064 12.9089183,3.52887426 C12.9614176,3.58044129 12.8549191,3.65172513 12.9164182,3.70177548 C12.9404179,3.72149229 12.9599176,3.78215939 12.9839173,3.79277613 C13.000417,3.80035952 13.0904157,3.76850929 13.1069155,3.7609259 C13.1354151,3.81249294 13.1609147,3.73362571 13.1834144,3.72907568 C13.1924143,3.69570877 13.2344137,3.65779184 13.2749131,3.65324181 C13.3349122,3.6471751 13.3364122,3.65779184 13.3769116,3.68660871 C13.4954099,3.76850929 13.4789102,3.56830787 13.5404093,3.51674084 C13.6514077,3.42574019 13.7069069,3.33928958 13.7834058,3.22250542 C13.8434049,3.12847142 13.9274038,3.10572126 14.0339022,3.0890378 C14.117901,3.07538771 14.2468992,3.0556709 14.2813987,2.96467025 C14.3218981,2.85850283 14.2243995,2.80086909 14.1389007,2.76901887 C14.0429021,2.73565196 13.9349036,2.6992517 13.976903,2.57791751 C14.0264023,2.43686651 13.982903,2.35496593 13.8329051,2.30946561 C13.5164096,2.21088157 13.2314137,2.04556373 12.9089183,1.95456309 C12.6239224,1.87417918 12.3359266,1.84536231 12.0434308,1.83019554 C11.9129326,1.78469522 11.6369366,1.78014518 11.5574377,1.89086264 C11.5064384,1.96214647 11.5709375,2.02433025 11.5649376,2.09864744 C11.5574377,2.18813141 11.6009371,2.27913206 11.6639362,2.34586586 L11.6639362,2.34586586 L11.6639362,2.34586586 Z M16.6423649,16.4069823 L16.6408648,16.4054657 C16.6453648,16.4130491 16.6423649,16.4266991 16.6438648,16.4373159 C16.702364,16.4373159 16.7278637,16.4903996 16.7908628,16.4706828 C16.8553617,16.4524827 16.8928612,16.3902989 16.841862,16.3387319 C16.7968627,16.2947482 16.7578632,16.2568313 16.6918641,16.2689647 C16.6138653,16.2841315 16.6303649,16.3463152 16.6423649,16.4069823 L16.6423649,16.4069823 L16.6423649,16.4069823 Z M18.6808357,14.3791846 C18.6778357,14.3655345 18.6748358,14.3534011 18.6718357,14.339751 C18.6103366,14.3215508 18.5713371,14.3852513 18.514338,14.3382343 C18.4048396,14.4125515 18.5218379,14.5596692 18.3418405,14.5505691 C18.37334,14.5884861 18.3703401,14.630953 18.3553403,14.6749367 C18.3328406,14.7431872 18.3148408,14.7371204 18.2683415,14.7462205 C18.1708429,14.7613873 18.1243436,14.7007202 18.094344,14.6188196 C17.9983455,14.621853 17.8663472,14.7704874 17.7913484,14.8175044 C17.7718486,14.8281211 17.7373491,14.8599713 17.7163495,14.8736214 C17.6998497,14.8827215 17.6593503,14.9024383 17.6383505,14.9145717 C17.5873513,14.9403552 17.4778528,14.9752388 17.4718529,15.0328725 C17.4463533,15.0283225 17.4073539,15.0434893 17.3818543,15.0404559 C17.3728543,15.0525894 17.3728543,15.0662394 17.3818543,15.0798895 C17.4988526,15.0996064 17.5603516,15.0601727 17.6593503,15.0177058 C17.7628488,14.9706888 17.8738472,14.9813055 17.9713458,14.9449053 C18.0178451,14.9282218 18.0193451,14.8766548 18.095844,14.9069883 C18.1288435,14.9221551 18.167843,14.9706888 18.1753429,15.0040557 C18.1903427,15.0798895 18.1108438,15.1921237 18.0313449,15.1966737 C18.0118452,15.1496567 18.0403448,15.101123 18.0493446,15.0632061 C17.9443462,15.0283225 17.7718486,15.1769569 17.743349,15.2649242 C17.8513475,15.2876744 17.8963469,15.4454088 17.8378476,15.5318595 C17.818348,15.5530929 17.7958484,15.5803931 17.7583489,15.5925266 C17.6968497,15.6107267 17.6683501,15.5546096 17.608351,15.5985933 C17.5303522,15.6577437 17.6158509,15.8200282 17.5708515,15.9110288 C17.5363521,15.980796 17.4778528,16.0065795 17.4283536,16.0551132 C17.395354,16.0899968 17.3758544,16.1279137 17.330855,16.1582472 C17.2723558,16.1976809 17.1298578,16.2826148 17.1418577,16.3645154 C17.2708559,16.408499 17.537852,16.1840308 17.6488504,16.1097136 C17.7193494,16.0626966 17.7628488,15.989896 17.8348477,15.942879 C17.9158466,15.8928287 18.0223451,15.8670452 18.0748442,15.7775612 C18.1048439,15.7259942 18.0808441,15.6804939 18.098844,15.6289268 C18.1153438,15.5834265 18.1468433,15.5682597 18.1768428,15.5333761 C18.2323421,15.4666423 18.2833413,15.4454088 18.3463403,15.3908084 C18.4243393,15.3210413 18.4063395,15.2118405 18.442339,15.1193232 C18.4738386,15.0389393 18.5353378,14.9767555 18.5788371,14.9009216 C18.6463362,14.7811041 18.8233336,14.4959687 18.7483346,14.3609844 C18.7303349,14.3761512 18.6973353,14.3716012 18.6808357,14.3791846 L18.6808357,14.3791846 L18.6808357,14.3791846 Z M20.2123137,10.498007 C20.1733142,10.4252065 20.2318134,10.215905 20.2318134,10.1294544 C20.2303134,9.97020329 20.182314,9.85038578 20.1598145,9.70175139 C20.1463146,9.56221706 20.1268149,9.19214777 20.1778142,9.06626355 C20.2498131,8.88881229 19.8988181,8.58851016 19.8793184,8.39589212 C19.8613187,8.22905761 19.7668201,8.07132315 19.630322,7.97577248 C19.5748228,7.93482218 19.4548246,7.38426828 19.3843255,7.41005179 C19.349826,7.42673525 19.421825,7.54806944 19.4173251,7.58598638 C19.3978254,7.71945399 19.3303263,7.58598638 19.2598273,7.61631992 C19.1293292,7.66940363 18.9913311,7.79528786 18.9748315,7.92420544 C18.9133323,8.40195883 18.5743371,7.90752199 18.6013368,7.88780518 C18.6763357,7.83017144 18.7033352,7.84837157 18.7918339,7.83623815 C18.8893327,7.80135457 18.7378349,7.73158741 18.8908326,7.71642064 C18.8548332,7.6178366 18.935832,7.58598638 18.8968325,7.50863583 C18.8383334,7.39488502 18.7963339,7.40853512 18.8548332,7.28568425 C18.8803327,7.21895044 18.727335,7.00813228 18.7153352,6.92774837 C18.7033352,6.84888115 18.7018353,6.74574708 18.6913354,6.65777979 C18.6853355,6.60166273 18.7768342,6.54857902 18.7603344,6.50459537 C18.7573345,6.3483776 18.7918339,6.1800264 18.7363348,6.02835866 C18.6973353,5.92522459 18.6523361,5.78872362 18.586337,5.70075633 C18.5638373,5.67042279 18.4933383,5.51420501 18.4858384,5.47022136 C18.4678387,5.37315401 18.4243393,5.40955427 18.3703401,5.37163733 C18.3403404,5.3337204 18.2053424,5.20783617 18.1663429,5.18963604 C18.1303435,5.17295259 17.8693472,4.93483423 17.8633473,4.91511743 C17.8408476,4.84686694 17.6938498,4.79378323 17.7103496,4.71946604 C17.7343492,4.60723191 17.3428548,4.31602984 17.2333564,4.29631304 C17.1628574,4.28417962 17.4493532,4.62694872 17.4478533,4.61936533 C17.4523532,4.63908213 17.6323506,4.86810043 17.6323506,4.86810043 C17.6728501,4.88175052 17.7718486,5.16233585 17.7688487,5.19873611 C17.7583489,5.30490353 17.4838527,5.0243182 17.4628531,4.98640127 C17.327855,4.82108343 17.0833585,4.68761581 16.9288607,4.57689836 C16.8193624,4.47528097 16.8733616,4.41764723 16.6858642,4.33119662 C16.6168652,4.29934639 16.4293679,4.14616197 16.3708688,4.14161194 C16.3003698,4.13857858 16.3813685,4.28721297 16.3828686,4.30389642 C16.3933683,4.41006384 16.5088668,4.41764723 16.5778657,4.48741439 C16.6333649,4.54504813 16.6828643,4.61633197 16.6348649,4.68003243 C16.6333649,4.68003243 16.5478663,4.83776688 16.5433663,4.82411678 C16.5673659,4.89085059 16.766863,5.0379683 16.8163624,5.09256869 C16.8073624,5.07891859 17.0773586,5.43230443 17.0998583,5.24423643 C17.1088581,5.17598594 17.0323592,5.09408536 17.0428591,5.03493494 C17.0503591,4.99853468 17.3518546,5.37922072 17.3683544,5.41258762 C17.4418534,5.61430572 17.4358535,5.38225407 17.5123523,5.40652091 C17.5753514,5.42623772 17.7448491,5.63705588 17.603851,5.64615595 C17.3893541,5.65980604 17.6428505,5.84635737 17.7028496,5.87517424 C17.8273479,5.93584134 17.9128467,6.07689234 18.0418448,6.12997605 C18.2443419,6.21187663 18.2023425,6.35747766 18.304341,6.50611205 C18.3373405,6.55312905 17.7223494,6.50611205 17.67435,6.53341224 C17.5933512,6.59711269 17.9833456,7.03239912 17.9848456,7.09761624 C17.9878456,7.23411721 18.0658444,7.33421792 18.0868442,7.47071889 C18.098844,7.59660312 18.0838442,7.76192096 18.1663429,7.86353835 C18.233842,7.92572212 18.2968411,7.76040428 18.4033395,7.85898831 C18.442339,7.87718844 18.4978383,7.92117209 18.5098381,7.95453899 C18.5458375,8.04857299 18.7498347,8.62339374 18.5053381,8.58092677 C18.3973396,8.56120996 18.4588388,9.03289664 18.4663386,9.10114713 C18.4993382,9.24978151 18.5593373,9.23764809 18.5203379,9.4257161 C18.5248379,9.58193387 18.3988397,9.6608011 18.3088409,9.77303523 C18.2668416,9.82460226 18.2398419,9.88526936 18.2203422,9.95048649 C18.1708429,9.90195281 18.1468433,9.82460226 18.0778442,9.79881874 C18.0028453,9.77000187 17.8378476,9.86100252 17.7718486,9.89285274 C17.612851,9.97323665 17.7298493,10.1188377 17.6668501,10.2447219 C17.6218508,10.3372392 17.4868527,10.3827396 17.3983539,10.4282399 C17.2918555,10.4828403 17.1493575,10.540474 17.044359,10.4525067 C16.9543604,10.3797062 16.9933599,10.2325885 16.8988611,10.168888 C16.7923627,10.0976042 16.7818629,10.2568553 16.7578632,10.3205558 C16.7113639,10.4403733 16.5718659,10.4813236 16.6078654,10.6314747 C16.6228651,10.6936584 16.6543647,10.7497755 16.6663645,10.8119593 C16.6813642,10.8893098 16.6423649,10.959077 16.6378649,11.0364275 C16.628865,11.1683785 16.766863,11.198712 16.8028626,11.3063961 C16.8343621,11.4034635 16.8013625,11.5536145 16.6933642,11.5900148 C16.5778657,11.6309651 16.4503675,11.5263143 16.3363691,11.5141809 C16.2223709,11.5020475 16.076873,11.5338977 16.0558732,11.664332 C16.0363735,11.7795995 16.1533718,11.8736335 16.0963726,11.9919343 C16.072373,12.0419847 16.0303736,12.0814183 16.0003741,12.1269186 C15.9478747,12.2027525 15.9178752,12.2907198 15.868376,12.3680703 C15.9268751,12.369587 15.9208752,12.3331867 15.9733744,12.3438035 C16.0288737,12.3559369 16.0783729,12.2998198 16.1248722,12.2816197 C16.1338722,12.31802 16.1293722,12.3559369 16.1338722,12.3923371 C16.1728715,12.4044706 16.211871,12.3893038 16.2463705,12.3756537 C16.2508705,12.4075039 16.2358706,12.4439042 16.2493705,12.4757544 C16.2598704,12.5030546 16.2913699,12.5121547 16.3093696,12.5333881 C16.3558689,12.5895052 16.2988697,12.6820225 16.2658703,12.7305562 C16.1713716,12.8700905 16.0093739,12.9474411 15.9043754,13.0778753 C15.8053768,13.1992095 15.7963769,13.3463272 15.7228781,13.4782782 C15.6973784,13.5237785 15.6718788,13.5859623 15.7393777,13.6071958 C15.7543776,13.5829289 15.7768773,13.5632121 15.8083769,13.5632121 C15.8548761,13.5616954 15.8368765,13.596579 15.860876,13.6238792 C15.9583746,13.7482468 16.0618731,13.5434953 16.1038725,13.4843449 C16.145872,13.4206444 16.3198695,13.3250938 16.3678688,13.4312612 C16.4053683,13.5116451 16.3618688,13.6253959 16.3243694,13.6981964 C16.3873684,13.7270133 16.3693687,13.7725136 16.3843685,13.827114 C16.4038682,13.9029479 16.4728672,13.9514815 16.4728672,14.0348988 C16.4728672,14.1349995 16.2538705,14.3336843 16.3393692,14.4095181 C16.4458677,14.5035521 16.5718659,14.2426836 16.6138653,14.1865665 C16.6933642,14.0788824 16.8748615,14.0637157 16.9198609,13.9317647 C16.9693601,13.7816137 16.9528603,13.6921297 17.1523575,13.6875797 C17.2363564,13.686063 17.2978554,13.6329793 17.3773543,13.6162958 C17.4643531,13.5996124 17.5033525,13.5905123 17.5588518,13.5237785 C17.6383505,13.4282278 17.7103496,13.5419786 17.7133495,13.6178125 C17.7163495,13.6951631 17.68185,13.7922304 17.7298493,13.8619976 C17.7883484,13.9469315 17.8528475,13.8346974 17.9128467,13.780097 C17.9068468,13.8377307 17.9833456,13.8710976 18.028345,13.8892978 C18.0973439,13.8422808 18.1408433,13.7649302 18.2143423,13.7224632 C18.2488418,13.7027464 18.2878413,13.6951631 18.3268407,13.6890963 C18.3373405,13.7512801 18.3478404,13.8180139 18.4033395,13.8483475 C18.4828384,13.8938478 18.3853398,13.9378314 18.4903384,13.9863651 C18.6208365,14.0364155 18.6643359,14.1683664 18.727335,14.2745338 C18.7573345,14.3230675 19.0303306,14.0273154 19.1353291,14.0166987 C19.4833242,13.9772651 19.6648215,13.5237785 19.7938196,13.2553266 C19.9798171,12.8716072 20.0638158,12.4454209 20.1118151,12.049568 C20.2303134,11.805383 20.2783127,11.3579631 20.2303134,11.0743445 C20.2018138,10.9014432 20.3008124,10.6618082 20.2123137,10.498007 L20.2123137,10.498007 L20.2123137,10.498007 Z M17.0623589,16.2568313 C17.0713587,16.2143643 17.1613573,16.0566299 17.0773586,16.0338797 C17.047359,16.0262963 17.0218595,16.0717966 16.9948598,16.07938 C16.9588602,16.0915134 16.9198609,16.0687633 16.8868613,16.0854467 C16.8568618,16.1021302 16.8283622,16.1506639 16.8103624,16.177964 C16.7878628,16.211331 16.7968627,16.224981 16.832862,16.2431812 C16.8688616,16.262898 16.9153609,16.271998 16.9363607,16.3114317 C16.9543604,16.3463152 16.9453605,16.3933322 16.9408605,16.4297325 C16.9408605,16.4282158 16.9468604,16.4221491 16.9483605,16.4160824 C16.9543604,16.4145657 16.9648603,16.4130491 16.9708602,16.4145657 L16.9633602,16.4297325 C17.0593589,16.4448993 17.051859,16.319015 17.0623589,16.2568313 L17.0623589,16.2568313 L17.0623589,16.2568313 Z M18.1858427,14.3594678 C18.1888426,14.4049681 18.238342,14.4004181 18.2713415,14.3882846 C18.3013411,14.3791846 18.3208408,14.3534011 18.3403404,14.3306509 C18.3673401,14.2957673 18.3838399,14.2608837 18.3598402,14.2199334 C18.3358406,14.1774665 18.3208408,14.1471329 18.3088409,14.0970826 C18.2893412,14.1076993 18.2653416,14.1258994 18.2443419,14.1319662 C18.2233421,14.1395495 18.2203422,14.1334828 18.1963426,14.1319662 C18.1408433,14.1304495 18.1528432,14.1729164 18.1288435,14.2078 C18.1093437,14.2381336 18.0658444,14.250267 18.0823442,14.2897006 C18.094344,14.3200342 18.1438434,14.3458177 18.172343,14.3594678 L18.1798428,14.3503677 C18.1783429,14.3534011 18.1768428,14.3549177 18.1753429,14.3579511 C18.1783429,14.3594678 18.1828427,14.3594678 18.1858427,14.3594678 L18.1858427,14.3594678 L18.1858427,14.3594678 Z M12.7709203,17.6157743 C12.8264195,17.5171902 12.9254181,17.4550064 13.0139168,17.386756 C13.1204153,17.3048554 13.214914,17.2093047 13.2989128,17.1061706 C13.2494135,17.0940372 13.2464135,17.0470202 13.214914,17.0166867 C13.1669147,16.9696697 13.0934157,17.0000032 13.0829159,16.9287194 C13.072416,16.8650189 13.0199168,16.8513688 12.9689175,16.8255853 C12.8519192,16.7664349 12.8039199,16.6511674 12.7094212,16.5707835 C12.6059227,16.4797829 12.4649247,16.5101164 12.3389265,16.4858496 C12.2279281,16.4646161 12.1154297,16.2871648 11.9969314,16.3629987 C11.9219325,16.4100157 11.887433,16.5298332 11.9384323,16.6041504 C11.9789317,16.6617841 12.0479307,16.690601 12.0749303,16.7588515 C12.0344309,16.7967684 12.025431,16.8210353 12.0749303,16.8559189 C12.1334295,16.8968691 12.2459278,16.9393361 12.2174283,17.0288201 C12.2024285,17.0773538 12.1619291,17.1243708 12.1094298,17.1334708 C12.0719303,17.1410542 11.9804317,17.1198207 11.9999314,17.1880712 C11.957932,17.0758371 11.8394337,17.2517717 11.7749346,17.1592543 C11.7224354,17.0849372 11.6894358,17.0182033 11.606937,16.968153 C11.5004385,16.9029359 11.6504364,16.8392354 11.6324367,16.7406514 C11.605437,16.596567 11.4299395,16.6360006 11.3669405,16.5298332 C11.329441,16.4691661 11.3804403,16.4206324 11.4044399,16.3675487 C11.4284396,16.3129483 11.4929386,16.356932 11.5274382,16.3720988 C11.6399365,16.4251825 11.8049342,16.4054657 11.8994328,16.3281151 C11.9429322,16.2917149 12.0389308,16.1233637 11.9174326,16.1233637 C11.8334338,16.1248803 11.7689347,16.1976809 11.6894358,16.2022309 C11.677436,16.1066802 11.6324367,15.9443957 11.7344352,15.8806953 C11.8274339,15.8230615 12.0224311,15.7563277 11.9399322,15.6076933 C11.9099327,15.5561263 11.8574334,15.6410602 11.813934,15.6031433 C11.7959343,15.5879765 11.8094341,15.5530929 11.815434,15.5364095 C11.7869344,15.510626 11.7599348,15.4787758 11.744935,15.4423755 C11.678936,15.2816076 11.8739332,15.1557234 11.7914344,14.9858555 C11.7569349,14.9145717 11.6984357,14.8721047 11.6339366,14.8281211 C11.5694376,14.7826208 11.5664376,14.724987 11.5424379,14.6567365 C11.5304381,14.6188196 11.467439,14.5293356 11.4134398,14.5520858 C11.3669405,14.5702859 11.3534406,14.6506698 11.3174412,14.6840367 C11.2364423,14.762904 11.0654448,14.7932375 10.9574463,14.767454 C10.8704476,14.7477372 10.8749475,14.7143703 10.8329481,14.6567365 C10.8194483,14.6355031 10.7894487,14.6339864 10.766949,14.6248863 C10.7279496,14.6097195 10.7234497,14.574836 10.7144498,14.5399524 C10.6784503,14.4080014 10.4174541,14.5718026 10.3769546,14.386768 C10.3679548,14.344301 10.3829546,14.2730172 10.3214554,14.2639171 C10.2524564,14.2517837 10.2494565,14.1850499 10.2494565,14.1289328 C10.2494565,14.0834325 10.2524564,14.019732 10.2044571,13.9939485 C10.142958,13.9605816 10.1279582,13.9757484 10.1084585,13.9059812 C10.0859588,13.8164972 10.0244597,13.9090146 9.97346042,13.8908144 C9.86546198,13.8483475 9.88496169,13.9014312 9.79796295,13.9469315 C9.64946507,14.0257987 9.63296531,13.6602795 9.58196604,13.5889956 C9.48296745,13.4524947 9.5084671,13.7573468 9.44096806,13.7952638 C9.37946895,13.8301473 9.31496987,13.7497634 9.29247019,13.7027464 C9.27897039,13.6754462 9.2714705,13.6451127 9.25497073,13.6178125 C9.22947109,13.5783789 9.18447174,13.5616954 9.15897211,13.5222618 C9.1379724,13.4873782 9.10647286,13.446428 9.09147307,13.408511 C9.07797327,13.3751441 9.08097322,13.3326771 9.05547359,13.305377 C9.02397404,13.2704934 9.06297347,13.2128596 9.08547316,13.168876 C9.1244726,13.1537092 9.18297177,13.1840428 9.21147134,13.2098263 C9.28197034,13.2689767 9.38846881,13.5283285 9.51146705,13.4797949 C9.48596742,13.446428 9.50246719,13.4054777 9.48446744,13.3690774 C9.46496771,13.3311605 9.42746826,13.3084103 9.39896866,13.2780768 C9.33596956,13.2052762 9.26697056,13.1324757 9.22497116,13.0445084 C9.18897168,12.9686746 9.17097194,12.8882907 9.09747298,12.835207 C9.03747384,12.7912233 8.91897554,12.7487563 8.94447517,12.6547223 C8.94447517,12.6532057 8.94597516,12.651689 8.94597516,12.651689 C8.99547445,12.6623057 9.02997396,12.698706 9.06597344,12.7305562 C9.11847269,12.7760565 9.18597171,12.7988067 9.24747084,12.8291402 C9.35996923,12.8837406 9.49046736,12.9216576 9.58646598,13.0050748 C9.64646512,13.0551252 9.61496557,13.1673593 9.68996449,13.2295431 C9.7454637,13.2750434 9.82796252,13.4282278 9.92996105,13.3675607 C9.96746051,13.3448106 9.98396028,13.3008269 10.0214597,13.2750434 C10.0619592,13.2462265 10.1294582,13.220443 10.1759575,13.1992095 C10.2044571,13.1855594 10.2524564,13.1901095 10.2749561,13.1673593 C10.3109556,13.1324757 10.2254568,13.032375 10.2059571,13.0065915 C10.1294582,12.9080075 10.0589592,12.8003234 9.95696066,12.7260062 C9.90446142,12.6880892 9.85346215,12.647139 9.79196304,12.6213554 C9.7589635,12.6077053 9.70046434,12.609222 9.69446443,12.5637217 C9.71096419,12.5743384 9.71996406,12.5713051 9.72296402,12.5546216 C9.72146405,12.5242881 9.67796466,12.5227714 9.65696497,12.5167047 C9.60596569,12.5030546 9.57146619,12.5030546 9.55196647,12.4605876 C9.52646684,12.4090206 9.43646812,12.4105373 9.38846881,12.3984039 C9.31646984,12.3802037 9.25947067,12.3256033 9.19047165,12.2983031 C9.10947281,12.2679696 9.0479737,12.2998198 8.96847483,12.31802 C8.95497503,12.3210533 8.92797542,12.3680703 8.90247578,12.4075039 C8.8454766,12.3938538 8.7824775,12.3999205 8.73447819,12.4378375 C8.66247923,12.4939545 8.62047983,12.5788885 8.56348064,12.6486556 C8.53948099,12.6774725 8.50498148,12.7062894 8.47048197,12.6956726 C8.46448206,12.6926393 8.46748202,12.6850559 8.46298208,12.6820225 C8.50648145,12.3711037 8.53048111,12.0571514 8.50198153,12.1193352 C8.44498234,12.2406694 8.40298294,12.3256033 8.35948356,12.4135706 C8.29648447,12.3862704 8.22298552,12.3847538 8.19598591,12.4514876 C8.16748632,12.5227714 8.20498577,12.6152887 8.15998643,12.6774725 C8.14948658,12.694156 8.13298681,12.6926393 8.11798703,12.7002227 C8.11498707,12.6926393 8.09998729,12.6729225 8.10148726,12.6714058 C8.08798746,12.694156 8.08648747,12.7017393 8.07448765,12.7214561 C8.0294883,12.7229728 7.97398909,12.7047727 7.91848989,12.6865726 C7.91848989,12.6865726 7.91848989,12.6820225 7.91698991,12.6820225 C7.91548992,12.6835392 7.91548992,12.6835392 7.91398995,12.6850559 C7.8479909,12.6623057 7.77899188,12.6410722 7.71899274,12.6623057 C7.59899447,12.7047727 7.59449453,12.8761572 7.51799562,12.980808 C7.40399726,13.1400591 7.11000147,13.0718086 7.05900221,12.882224 C7.10100161,12.8306569 7.14150103,12.7790899 7.18350043,12.7275229 C7.11900135,12.5637217 6.94650382,12.4499709 6.77250631,12.4545209 C6.72300702,12.4560376 6.67050778,12.4651377 6.62550841,12.4439042 C6.57900908,12.421154 6.55350945,12.3726203 6.51301003,12.3422868 C6.38701184,12.2482528 6.21901425,12.3726203 6.11401575,12.4894045 C5.93401832,12.519738 5.76752071,12.6213554 5.65502233,12.7669565 C5.58302336,12.7608898 5.5110244,12.754823 5.4405254,12.7487563 C5.4825248,12.8640238 5.34302681,12.9580578 5.28002771,13.0642252 C5.20202883,13.1931428 5.24252825,13.3432939 5.31902714,13.4813115 C5.31002728,13.502545 5.30402736,13.5252952 5.28452765,13.5343952 C5.19302895,13.5829289 5.21702861,13.6117458 5.24402822,13.7103298 C5.26802788,13.7952638 5.259028,13.9545149 5.23952829,14.0394488 C5.22452851,14.1061826 5.15852944,14.2669504 5.07753061,14.2290335 C5.03553122,14.2078 4.99053186,14.1911166 4.95153241,14.2305502 C4.93353267,14.2472336 4.92153284,14.2684671 4.91553293,14.2912173 C4.88853332,14.292734 4.8615337,14.2957673 4.83603407,14.301834 C4.7850348,14.3124508 4.73103557,14.3245842 4.68153629,14.3033507 C4.63203699,14.2821172 4.56603794,14.2411669 4.51053874,14.2593671 C4.4640394,14.2745338 4.37254072,14.3154841 4.35304099,14.3640178 C4.34404113,14.3852513 4.37254072,14.4413683 4.37254072,14.4701852 C4.37104073,14.5217523 4.41154015,14.5991028 4.39504039,14.6461198 C4.35754093,14.6279197 4.30204173,14.621853 4.27654209,14.583936 C4.25254243,14.5520858 4.21654295,14.5596692 4.18954334,14.5263023 C4.18354343,14.583936 4.16254372,14.6643199 4.09354471,14.6794867 C4.02754566,14.6946535 3.96304658,14.6415698 3.89554755,14.6597699 C3.71855009,14.7052702 4.00204603,14.9267051 4.03954548,14.9691721 C4.10254459,15.0404559 4.12354428,15.1344899 4.17004361,15.2148738 C4.22104289,15.3028411 4.32604139,15.331658 4.3875405,15.4090085 C4.43853977,15.4742257 4.44903961,15.562193 4.52253857,15.6107267 C4.60353741,15.6668438 4.67703635,15.7168941 4.71003588,15.8124448 C4.74453538,15.7790779 4.81653436,15.9368123 4.8900333,15.8109281 C4.92903274,15.7426776 4.99803174,15.6835272 5.04153113,15.7942447 C5.07903059,15.8897953 5.04153113,15.9504624 5.12402995,16.0338797 C5.18702904,16.0990968 5.18402909,16.1764474 5.07603062,16.168864 C5.09403037,16.2173977 5.12252996,16.2659313 5.07603062,16.309915 C5.05503093,16.3311485 4.9950318,16.3751321 5.03853117,16.4054657 C5.08803045,16.3827155 5.1405297,16.3690654 5.190029,16.3463152 C5.24402822,16.3235651 5.29652747,16.2689647 5.35952656,16.2704814 C5.36252653,16.2901982 5.27552777,16.3478319 5.32952699,16.352382 C5.37452635,16.356932 5.43902543,16.309915 5.47502491,16.3493486 C5.51552433,16.3918156 5.46452506,16.4570327 5.48852472,16.5040497 C5.51252437,16.5525834 5.59352321,16.5161831 5.63252265,16.5237665 C5.61602289,16.5662335 5.55302379,16.5586501 5.51852428,16.5753335 C5.59802315,16.6724009 5.49452463,16.8149686 5.37902629,16.8180019 C5.32352708,16.8180019 5.1270299,16.592017 5.11803004,16.737618 C5.11653005,16.780085 5.13002986,16.8331687 5.14352967,16.874119 C5.16152941,16.9272027 5.29052756,16.9044525 5.3370269,16.9241693 C5.40452592,16.9514695 5.4825248,17.0166867 5.50952442,17.0849372 C5.53502405,17.1547043 5.59352321,17.2002046 5.61602289,17.2669384 C5.65952227,17.391306 5.77802057,17.4079894 5.90101881,17.4443897 C6.06451645,17.4929234 5.9850176,17.7462085 5.97601774,17.8675427 C5.96851783,17.9873602 6.13651543,18.0161771 6.21301434,18.0844276 C6.29851311,18.1587448 6.31201291,18.3028291 6.17101494,18.3149625 C6.09901596,18.3210293 5.97901769,18.2876624 5.95201807,18.3801797 C5.91301863,18.5106139 6.11101579,18.4969638 6.19801455,18.5257807 C6.23851397,18.5394308 6.40201163,18.5561143 6.41701141,18.5940312 C6.43951109,18.6531816 6.42151135,18.7335655 6.44251104,18.7957493 C6.49351031,18.9550004 6.64050821,19.0611678 6.78300616,19.1400351 C7.08900178,19.3114196 7.45199657,19.4191037 7.78799176,19.5146544 C7.97548908,19.5692548 8.16598634,19.6117217 8.35798358,19.6375053 C8.54398093,19.6617721 8.70747857,19.6466053 8.86647629,19.7497394 C8.97597473,19.8210232 9.04497373,19.7679395 9.15747212,19.7876563 C9.20547143,19.7967564 9.22647114,19.84074 9.26247062,19.8665236 C9.30297004,19.8983738 9.34946938,19.8574235 9.39296875,19.8741069 C9.40046864,19.8452901 9.39746869,19.8179899 9.38396888,19.7906897 C9.46946765,19.8225399 9.57446615,19.9135406 9.66446486,19.84074 C9.70946422,19.8043398 9.73946379,19.7542894 9.78596312,19.7194058 C9.84146232,19.7239559 9.89546155,19.7269892 9.95096074,19.7269892 C10.1834574,19.7269892 10.3664548,19.6208218 10.5524521,19.4964543 C10.7519493,19.3629866 10.9919458,19.3599533 11.2229425,19.3387198 C11.467439,19.314453 11.7269353,19.2826027 11.9504321,19.1764353 C12.1454293,19.0824013 12.1949286,18.9110168 12.2504278,18.7214321 C12.3104269,18.515164 12.4904244,18.4317467 12.6089227,18.2649122 C12.7484206,18.0707775 12.656922,17.8174923 12.7709203,17.6157743 L12.7709203,17.6157743 L12.7709203,17.6157743 Z M3.82354859,14.8796881 C3.81604868,14.8311544 3.79804894,14.8008209 3.76054949,14.772004 C3.75904951,14.7765541 3.75754954,14.7795874 3.75754954,14.7856541 C3.70655026,14.7598706 3.70055035,14.6628033 3.63155133,14.6703866 C3.57455216,14.579386 3.43055421,14.6203363 3.38555487,14.7037535 C3.3555553,14.7598706 3.39155478,14.7856541 3.4245543,14.8266044 C3.4665537,14.8781714 3.45905381,14.9176051 3.4740536,14.9767555 C3.51155306,15.1299399 3.63905124,15.0252892 3.72904994,15.0844396 C3.76354945,15.1071897 3.77704925,15.1724069 3.82804851,15.1617901 C3.8850477,15.14814 3.88204775,15.0632061 3.86704796,15.0252892 C3.84604827,14.9706888 3.83104848,14.9388385 3.82354859,14.8796881 L3.82354859,14.8796881 L3.82354859,14.8796881 Z M5.0220314,16.686051 C5.03103128,16.6602675 4.99053186,16.6314506 4.96353224,16.6329673 C4.93953258,16.634484 4.91703292,16.6678509 4.90803304,16.686051 C4.87803347,16.7391347 4.911033,16.8104185 4.980032,16.8104185 C4.99653177,16.7816017 4.99053186,16.7254846 5.03403123,16.7209346 C5.03103128,16.7072845 5.0220314,16.7027344 5.01003157,16.6981844 L5.0220314,16.686051 L5.0220314,16.686051 Z M3.74704968,14.7613873 C3.75154962,14.7644206 3.75604955,14.767454 3.76054949,14.772004 C3.76504943,14.7644206 3.76954936,14.7583539 3.77254931,14.7492539 L3.74704968,14.7613873 L3.74704968,14.7613873 Z M17.8018483,16.8892858 C17.7763486,16.9135526 17.7838484,16.9408528 17.7718486,16.9696697 C17.7583489,17.0045532 17.7058496,17.0182033 17.67735,17.0364035 C17.6323506,17.0652203 17.6173508,17.1334708 17.5678516,17.1501543 C17.5468518,17.1183041 17.5153523,17.0288201 17.4718529,17.1061706 C17.4463533,17.156221 17.4628531,17.2062713 17.4223537,17.2517717 C17.3818543,17.2942386 17.3848542,17.3458057 17.3563546,17.3928227 C17.3128551,17.4671399 17.2693558,17.5111235 17.195857,17.5551072 C17.1373577,17.5899907 17.1253579,17.6552079 17.0833585,17.7037415 C17.0383591,17.7568253 16.9693601,17.7765421 16.9078611,17.8038423 C16.8643616,17.8220424 16.7923627,17.8736094 16.7428634,17.8447925 C16.6768644,17.8038423 16.766863,17.7310417 16.7983626,17.7052582 C16.8283622,17.6809914 16.9708602,17.5945408 16.9378606,17.5460071 C16.9153609,17.5141569 16.8343621,17.5187069 16.8013625,17.5217402 C16.7338636,17.5293236 16.6828643,17.5975741 16.6258651,17.6294243 C16.561366,17.6658246 16.5058668,17.6961582 16.4353678,17.720425 C16.354369,17.7492419 16.3483691,17.8250757 16.28387,17.8705761 C16.2328707,17.9100097 16.1668716,17.9403432 16.1008726,17.9403432 C16.0138739,17.9403432 16.0183738,17.8675427 15.9928741,17.8068756 C15.9598747,17.8114256 15.9313751,17.8538926 15.8998755,17.8675427 C15.8488762,17.8887762 15.8158767,17.913043 15.8383763,17.9691601 C15.859376,18.0267938 15.6268794,18.0798775 15.58638,18.1086944 C15.5773802,18.0798775 15.6223794,18.049544 15.6403792,18.0328605 C15.5683802,18.0267938 15.4873814,18.0889776 15.4138824,18.0995943 C15.3433835,18.1086944 15.2563847,18.1572281 15.2458848,18.2300286 C15.238385,18.2876624 15.1618861,18.2861457 15.1138867,18.3058625 C15.0343879,18.3392294 15.0673874,18.3862464 15.0523876,18.4499468 C15.0223881,18.5697644 14.7718916,18.4787637 14.8993898,18.3195126 C14.9443892,18.2633955 15.0118882,18.2269953 15.0523876,18.1693615 C15.098887,18.1026277 15.1063869,18.0192104 15.1423863,17.9479266 C15.0628875,17.9706768 15.0028883,18.0161771 14.9353893,18.0601607 C14.8588904,18.1102111 14.7943913,18.0980777 14.7088926,18.0813942 C14.609894,18.0601607 14.540895,18.1102111 14.4493963,18.1329613 C14.3908971,18.1466113 14.260399,18.1420613 14.2528991,18.2269953 C14.2483992,18.2815956 14.3428978,18.2922124 14.3743973,18.3240626 C14.4208967,18.3725963 14.471896,18.4438801 14.5108954,18.4984805 C14.5423949,18.5409475 14.6593933,18.6000979 14.6518934,18.6546983 C14.6398935,18.7608657 14.5063954,18.7426656 14.4343965,18.7608657 C14.3623975,18.7790658 14.3518977,18.844283 14.2993984,18.8837166 C14.2393993,18.9277002 14.1584004,18.8761332 14.0924014,18.9034334 C14.0249024,18.9292169 13.976903,18.989884 13.9184039,19.0293176 C13.8194053,19.0975681 13.7549062,19.0232509 13.6529077,19.0187009 C13.5704089,19.0156675 13.4954099,19.0535845 13.4174111,19.0702679 C13.3469121,19.0854347 13.2614133,19.0975681 13.2074141,19.1476185 C13.0889158,19.2537859 13.4699103,19.224969 13.5059098,19.2219356 C13.48941,19.2826027 13.4729103,19.3538866 13.4174111,19.3933202 C13.3484121,19.4433706 13.2509134,19.4312371 13.1714146,19.4494373 C13.1099155,19.464604 13.0244167,19.5328545 13.1144154,19.5813882 C13.1954142,19.6223385 13.2989128,19.601105 13.3799116,19.5722881 C13.4729103,19.5404379 13.559909,19.4903876 13.6574076,19.4691541 C13.7594062,19.4464039 13.8659046,19.4570206 13.9679032,19.4388205 C14.0774016,19.417587 14.1734002,19.3584366 14.2753988,19.3174863 C14.3728974,19.2780527 14.4748959,19.2628859 14.5783944,19.2598526 C14.5573947,19.2977695 14.4553962,19.2947362 14.4163967,19.3038362 C14.3398978,19.319003 14.2873986,19.3857368 14.2078997,19.3811868 C14.117901,19.3781534 14.1284009,19.4418539 14.0639018,19.4570206 C14.0219024,19.4676374 13.9229038,19.5647047 13.8914043,19.5040376 C13.8674046,19.4570206 13.8209053,19.4661207 13.8059055,19.5237545 C13.7939057,19.5662214 13.8179053,19.5813882 13.7594062,19.5829049 C13.7144068,19.5829049 13.6934071,19.5647047 13.6529077,19.554088 C13.5719088,19.5328545 13.5359094,19.6162718 13.4789102,19.6375053 C13.3949114,19.6693555 13.3049127,19.6587387 13.2239138,19.7118225 C13.1774145,19.742156 13.1264152,19.7512561 13.070916,19.7679395 C12.9704175,19.7997897 12.8759188,19.8377067 12.7754203,19.8710736 C12.6974214,19.8983738 12.6194225,19.9317407 12.5354237,19.9332574 C12.5009242,19.9332574 12.3629262,19.9089905 12.3404265,19.9499408 C12.2984271,20.0257747 12.4274253,19.9969578 12.4544249,19.981791 C12.5309238,19.9408407 12.6239224,19.9651076 12.7094212,19.9651076 C12.8144197,19.9651076 12.9014185,19.9438741 12.9959171,19.8953404 C13.0214167,19.8816903 13.1909143,19.8452901 13.1999142,19.8650069 C13.214914,19.8741069 13.3274123,19.8331566 13.3469121,19.8286066 C13.4384108,19.8073731 13.5299094,19.7876563 13.6199082,19.7649062 C13.8974042,19.6936223 14.1704003,19.5859382 14.4403964,19.4934209 C14.9788887,19.3114196 15.4738816,19.0035341 15.9418748,18.6910985 C16.1533718,18.5500475 16.3258694,18.3650129 16.5553661,18.2497454 C16.7863628,18.1344779 16.9933599,17.9812935 17.2048568,17.8372092 C17.4118538,17.6961582 17.5693515,17.5020234 17.7418491,17.3245722 C17.9158466,17.1440876 18.0628444,16.9742197 18.1393434,16.7345847 C18.095844,16.7239679 18.0508447,16.8134519 18.0148451,16.8301353 C17.954846,16.8589522 17.8498476,16.8437854 17.8018483,16.8892858 L17.8018483,16.8892858 L17.8018483,16.8892858 Z M16.5088668,16.6451007 C16.561366,16.5738169 16.5163667,16.4767495 16.423368,16.5328666 C16.3888685,16.5525834 16.3933683,16.592017 16.3663688,16.6162838 C16.3378692,16.6420673 16.3348692,16.6117338 16.3063697,16.6041504 C16.2658703,16.5950503 16.1998712,16.6496507 16.1863714,16.686051 C16.1308722,16.6845343 16.0813728,16.7467181 16.1068725,16.7952518 C16.1803715,16.7679516 16.2253709,16.6997011 16.3048696,16.7209346 C16.3678688,16.737618 16.4683674,16.6981844 16.5088668,16.6451007 L16.5088668,16.6451007 L16.5088668,16.6451007 Z"/>
+ </mask>
+ </defs>
+
+ <rect width="24" height="24" mask="url(#mask-globe)" class="fieldtext"/>
+</svg>
diff --git a/browser/themes/shared/controlcenter/connection.svg b/browser/themes/shared/controlcenter/connection.svg
new file mode 100644
index 000000000..85396d68f
--- /dev/null
+++ b/browser/themes/shared/controlcenter/connection.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="24" height="24" viewBox="0 0 24 24">
+#include ../icon-colors.inc.svg
+ <style>
+ svg > rect:not(:target) {
+ display: none;
+ }
+
+ #connection-secure {
+ fill: #4d9a26;
+ }
+ </style>
+
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="5" y="1" width="14" height="20" rx="7" ry="7" />
+ <rect id="shape-lock-clasp-inner" x="8" y="4" width="8" height="14" rx="4" ry="4" />
+ <rect id="shape-lock-base" x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+
+ <mask id="mask-clasp-cutout">
+ <rect width="24" height="24" fill="#000" />
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ </mask>
+
+ <mask id="mask-lock">
+ <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" fill="#fff"/>
+ <use xlink:href="#shape-lock-base" fill="#fff"/>
+ </mask>
+ </defs>
+
+ <rect id="connection-degraded" class="fieldtext" width="24" height="24" mask="url(#mask-lock)"/>
+ <rect id="connection-secure" width="24" height="24" mask="url(#mask-lock)"/>
+</svg>
diff --git a/browser/themes/shared/controlcenter/mcb-disabled.svg b/browser/themes/shared/controlcenter/mcb-disabled.svg
new file mode 100644
index 000000000..76bad47ee
--- /dev/null
+++ b/browser/themes/shared/controlcenter/mcb-disabled.svg
@@ -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/. -->
+<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">
+#include ../icon-colors.inc.svg
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="5" y="1" width="14" height="20" rx="7" ry="7" />
+ <rect id="shape-lock-clasp-inner" x="8" y="4" width="8" height="14" rx="4" ry="4" />
+ <rect id="shape-lock-base" x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+
+ <mask id="mask-clasp-cutout">
+ <rect width="24" height="24" fill="#000" />
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ <line x1="3" y1="21" x2="21.5" y2="0.5" stroke="#000" stroke-width="3" />
+ <line x1="3" y1="25" x2="21.5" y2="4.5" stroke="#000" stroke-width="3" />
+ <rect x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+ </mask>
+
+ <mask id="mask-base-cutout">
+ <rect width="24" height="24" fill="#000" />
+ <use xlink:href="#shape-lock-base" fill="#fff" />
+ <line x1="2.25" y1="24.75" x2="21.5" y2="4.5" stroke="#000" stroke-width="3" />
+ </mask>
+ </defs>
+
+ <use class="fieldtext" xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)"/>
+ <use class="fieldtext" xlink:href="#shape-lock-base" mask="url(#mask-base-cutout)"/>
+
+ <line x1="2.25" y1="22.75" x2="21.5" y2="2.5" stroke="#d92d21" stroke-width="3" />
+</svg>
diff --git a/browser/themes/shared/controlcenter/panel.inc.css b/browser/themes/shared/controlcenter/panel.inc.css
new file mode 100644
index 000000000..a0f3aa854
--- /dev/null
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -0,0 +1,445 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+/* Hide all conditional elements by default. */
+:-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
+ display: none;
+}
+
+/* Show the right elements for the right connection states. */
+#identity-popup[connection=not-secure] [when-connection~=not-secure],
+#identity-popup[connection=secure-cert-user-overridden] [when-connection~=secure-cert-user-overridden],
+#identity-popup[connection=secure-ev] [when-connection~=secure-ev],
+#identity-popup[connection=secure] [when-connection~=secure],
+#identity-popup[connection=chrome] [when-connection~=chrome],
+#identity-popup[connection=file] [when-connection~=file],
+/* Show insecure login forms messages when needed. */
+#identity-popup[loginforms=insecure] [when-loginforms=insecure],
+/* Show weak cipher messages when needed. */
+#identity-popup[ciphers=weak] [when-ciphers~=weak],
+/* Show mixed content warnings when needed */
+#identity-popup[mixedcontent~=active-loaded] [when-mixedcontent=active-loaded],
+#identity-popup[mixedcontent~=passive-loaded]:not([mixedcontent~=active-loaded]) [when-mixedcontent=passive-loaded],
+#identity-popup[mixedcontent~=active-blocked]:not([mixedcontent~=passive-loaded]) [when-mixedcontent=active-blocked],
+/* Show the right elements when there is mixed passive content loaded and active blocked. */
+#identity-popup[mixedcontent~=active-blocked][mixedcontent~=passive-loaded] [when-mixedcontent~=active-blocked][when-mixedcontent~=passive-loaded],
+/* Show 'disable MCB' button always when there is mixed active content blocked. */
+#identity-popup-securityView-body[mixedcontent~=active-blocked] > button[when-mixedcontent=active-blocked] {
+ display: inherit;
+}
+
+/* Hide redundant messages based on insecure login forms presence. */
+#identity-popup[loginforms=secure] [and-when-loginforms=insecure] {
+ display: none;
+}
+#identity-popup[loginforms=insecure] [and-when-loginforms=secure] {
+ display: none;
+}
+
+/* Hide 'not secure' message in subview when weak cipher or mixed content messages are shown. */
+#identity-popup-securityView-body:-moz-any([mixedcontent],[ciphers]) > description[when-connection=not-secure],
+/* Hide 'passive-loaded (only)' message when there is mixed passive content loaded and active blocked. */
+#identity-popup-securityView-body[mixedcontent~=passive-loaded][mixedcontent~=active-blocked] > description[when-mixedcontent=passive-loaded] {
+ display: none;
+}
+
+/* Make sure hidden elements don't accidentally become visible from one of the
+ above selectors (see Bug 1194258) */
+#identity-popup [hidden] {
+ display: none !important;
+}
+
+#identity-popup,
+#identity-popup:not([panelopen]) .panel-viewstack[viewtype="main"]:not([transitioning]) #identity-popup-mainView {
+ /* Tiny hack to ensure the panel shrinks back to its original
+ size after closing a subview that is bigger than the main view. */
+ max-height: 0;
+}
+
+.panel-mainview[panelid=identity-popup][viewtype=subview] > #identity-popup-mainView menulist,
+.panel-mainview[panelid=identity-popup][viewtype=subview] > #identity-popup-mainView button:not([panel-multiview-anchor]) {
+ -moz-user-focus: ignore;
+}
+
+#identity-popup > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+.panel-mainview[panelid=identity-popup] {
+ min-width: 30em;
+}
+
+#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="main"] > .panel-subviews {
+ transform: translateX(100%);
+ box-shadow: none;
+}
+
+#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="main"] > .panel-subviews:-moz-locale-dir(rtl) {
+ transform: translateX(-100%);
+}
+
+#identity-popup-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
+ background: var(--arrowpanel-background);
+ padding: 0;
+}
+
+.identity-popup-section:not(:first-child) {
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+#identity-popup-securityView,
+#identity-popup-security-content,
+#identity-popup-permissions-content,
+#tracking-protection-content {
+ background-repeat: no-repeat;
+ background-position: 1em 1em;
+ background-size: 24px auto;
+}
+
+#identity-popup-security-content,
+#identity-popup-permissions-content,
+#tracking-protection-content {
+ padding: 0.5em 0 1em;
+ /* .identity-popup-headline.host depends on this width */
+ padding-inline-start: calc(2em + 24px);
+ padding-inline-end: 1em;
+}
+
+#identity-popup-securityView:-moz-locale-dir(rtl),
+#identity-popup-security-content:-moz-locale-dir(rtl),
+#identity-popup-permissions-content:-moz-locale-dir(rtl),
+#tracking-protection-content:-moz-locale-dir(rtl) {
+ background-position: calc(100% - 1em) 1em;
+}
+
+/* EXPAND BUTTON */
+
+.identity-popup-expander {
+ margin: 0;
+ padding: 4px 0;
+ min-width: auto;
+ width: var(--identity-popup-expander-width);
+ border: 0 none;
+ -moz-appearance: none;
+ background: url("chrome://browser/skin/controlcenter/arrow-subview.svg") center no-repeat;
+ background-size: 16px, auto;
+}
+
+.identity-popup-expander:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.identity-popup-expander[panel-multiview-anchor] {
+ transition: background-color 250ms ease-in;
+ background-color: Highlight;
+ background-image: url("chrome://browser/skin/controlcenter/arrow-subview-back.svg"),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+}
+
+.identity-popup-expander > .button-box {
+ padding: 0;
+ -moz-appearance: none;
+ border-style: none;
+ border-left: 1px solid var(--panel-separator-color);
+}
+
+.identity-popup-expander:-moz-focusring > .button-box,
+.identity-popup-expander[panel-multiview-anchor] > .button-box {
+ border-style: none;
+}
+
+.identity-popup-expander:hover {
+ background-color: var(--arrowpanel-dimmed);
+ background-image: url("chrome://browser/skin/controlcenter/arrow-subview.svg"),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+}
+
+.identity-popup-expander:hover:active {
+ background-color: var(--arrowpanel-dimmed-further);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
+/* CONTENT */
+
+.identity-popup-permission-label,
+.identity-popup-permission-state-label,
+#identity-popup-security-content > description,
+#identity-popup-security-descriptions > description,
+#identity-popup-securityView-header > description,
+#identity-popup-securityView-body > description,
+#identity-popup-permissions-content > description,
+#tracking-protection-content > label {
+ white-space: pre-wrap;
+ font-size: 110%;
+ margin: 0;
+}
+
+.identity-popup-headline {
+ margin: 3px 0 4px;
+ font-size: 150%;
+}
+
+.identity-popup-headline.host {
+ word-wrap: break-word;
+ /* 1em + 2em + 24px is #identity-popup-security-content padding
+ * 30em is .panel-mainview:not([panelid="PanelUI-popup"]) width */
+ max-width: calc(30rem - 3rem - 24px - var(--identity-popup-expander-width))
+}
+
+.identity-popup-warning-gray {
+ padding-inline-start: 24px;
+ background: url(chrome://browser/skin/controlcenter/warning-gray.svg) no-repeat 0 50%;
+}
+
+.identity-popup-warning-yellow {
+ padding-inline-start: 24px;
+ background: url(chrome://browser/skin/controlcenter/warning-yellow.svg) no-repeat 0 50%;
+}
+
+.identity-popup-warning-gray:-moz-locale-dir(rtl),
+.identity-popup-warning-yellow:-moz-locale-dir(rtl) {
+ background-position: 100% 50%;
+}
+
+/* SECURITY */
+
+.identity-popup-connection-secure {
+ color: #418220;
+}
+
+.identity-popup-connection-not-secure {
+ color: #d74345;
+}
+
+#identity-popup-securityView {
+ overflow: hidden;
+}
+
+#identity-popup-securityView,
+#identity-popup-security-content {
+ background-image: url(chrome://browser/skin/controlcenter/conn-not-secure.svg);
+}
+
+#identity-popup[connection=chrome] #identity-popup-securityView,
+#identity-popup[connection=chrome] #identity-popup-security-content {
+ background-image: url(chrome://branding/content/icon48.png);
+}
+
+#identity-popup[connection^=secure] #identity-popup-securityView,
+#identity-popup[connection^=secure] #identity-popup-security-content {
+ background-image: url(chrome://browser/skin/controlcenter/connection.svg#connection-secure);
+}
+
+/* Use [isbroken] to make sure we don't show a lock on an http page. See Bug 1192162. */
+#identity-popup[ciphers=weak] #identity-popup-securityView,
+#identity-popup[ciphers=weak] #identity-popup-security-content,
+#identity-popup[mixedcontent~=passive-loaded][isbroken] #identity-popup-securityView,
+#identity-popup[mixedcontent~=passive-loaded][isbroken] #identity-popup-security-content {
+ background-image: url(chrome://browser/skin/controlcenter/connection.svg#connection-degraded);
+}
+
+#identity-popup[connection=secure-cert-user-overridden] #identity-popup-securityView,
+#identity-popup[connection=secure-cert-user-overridden] #identity-popup-security-content {
+ background-image: url(chrome://browser/skin/connection-mixed-passive-loaded.svg#icon);
+}
+
+#identity-popup[loginforms=insecure] #identity-popup-securityView,
+#identity-popup[loginforms=insecure] #identity-popup-security-content,
+#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-securityView,
+#identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-security-content {
+ background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);
+}
+
+#identity-popup-security-descriptions > description {
+ margin-top: 6px;
+ color: Graytext;
+}
+
+#identity-popup-securityView-header,
+#identity-popup-securityView-body {
+ margin-inline-start: calc(2em + 24px);
+ margin-inline-end: 1em;
+}
+
+#identity-popup-securityView-header {
+ margin-top: 0.5em;
+ border-bottom: 1px solid var(--panel-separator-color);
+ padding-bottom: 1em;
+}
+
+#identity-popup-securityView-body {
+ padding-inline-end: 1em;
+}
+
+#identity-popup-securityView-footer {
+ margin-top: 1em;
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#identity-popup-securityView-footer > button {
+ -moz-appearance: none;
+ margin: 0;
+ border: none;
+ border-top: 1px solid var(--panel-separator-color);
+ padding: 8px 20px;
+ color: inherit;
+ background-color: transparent;
+}
+
+#identity-popup-securityView-footer > button:hover,
+#identity-popup-securityView-footer > button:focus {
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#identity-popup-securityView-footer > button:hover:active {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+#identity-popup-content-verifier ~ description {
+ margin-top: 1em;
+ color: Graytext;
+}
+
+description#identity-popup-content-verified-by,
+description#identity-popup-content-owner,
+description#identity-popup-content-verifier,
+#identity-popup-securityView-body > button {
+ margin-top: 1em;
+}
+
+#identity-popup-securityView-body > button {
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+}
+
+/* TRACKING PROTECTION */
+
+#tracking-protection-content {
+ background-image: url("chrome://browser/skin/controlcenter/tracking-protection.svg#enabled");
+}
+
+#tracking-protection-content[state="loaded-tracking-content"] {
+ background-image: url("chrome://browser/skin/controlcenter/tracking-protection.svg#disabled");
+}
+
+#tracking-action-block,
+#tracking-action-unblock,
+#tracking-action-unblock-private {
+ margin: 1em 0 0;
+}
+
+#tracking-protection-content[state] > #tracking-not-detected,
+#tracking-protection-content:not([state="blocked-tracking-content"]) > #tracking-blocked,
+#main-window[privatebrowsingmode] #tracking-action-unblock,
+#main-window:not([privatebrowsingmode]) #tracking-action-unblock-private,
+#tracking-protection-content:not([state="blocked-tracking-content"]) #tracking-action-unblock,
+#tracking-protection-content:not([state="blocked-tracking-content"]) #tracking-action-unblock-private,
+#tracking-protection-content:not([state="loaded-tracking-content"]) > #tracking-loaded,
+#tracking-protection-content:not([state="loaded-tracking-content"]) #tracking-action-block,
+#tracking-protection-content:not([state]) > #tracking-actions {
+ display: none;
+}
+
+/* PERMISSIONS */
+
+#identity-popup-permissions-content {
+ background-image: url(chrome://browser/skin/controlcenter/permissions.svg);
+}
+
+#identity-popup-permissions-headline {
+ /* Make sure the label is as tall as the icon so that the permission list
+ which is aligned with the icon doesn't cover it up. */
+ min-height: 24px;
+}
+
+#identity-popup-permission-list {
+ /* Offset the padding set on #identity-popup-permissions-content so that it
+ shows up just below the section. The permission icons are 16px wide and
+ should be right aligned with the section icon. */
+ margin-inline-start: calc(-1em - 16px);
+}
+
+.identity-popup-permission-item {
+ min-height: 24px;
+}
+
+#identity-popup-permission-list:not(:empty) {
+ margin-top: 5px;
+}
+
+.identity-popup-permission-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.identity-popup-permission-icon.in-use {
+ animation: 1.5s ease in-use-blink infinite;
+}
+
+@keyframes in-use-blink {
+ 50% { opacity: 0; }
+}
+
+.identity-popup-permission-label,
+.identity-popup-permission-state-label {
+ /* We need to align the action buttons and permission icons with the text.
+ This is tricky because the icon height is defined in pixels, while the
+ font height can vary with platform and system settings, and at least on
+ Windows the default font metrics reserve more extra space for accents.
+ This value is a good compromise for different platforms and font sizes. */
+ margin-top: -0.1em;
+}
+
+.identity-popup-permission-label {
+ margin-inline-start: 1em;
+}
+
+.identity-popup-permission-state-label {
+ margin-inline-end: 5px;
+ text-align: end;
+ color: graytext;
+}
+
+.identity-popup-permission-remove-button {
+ -moz-appearance: none;
+ margin: 0;
+ border-width: 0;
+ border-radius: 50%;
+ min-width: 0;
+ padding: 2px;
+ background-color: transparent;
+}
+
+.identity-popup-permission-remove-button > .button-box {
+ padding: 0;
+ -moz-appearance: none;
+}
+
+.identity-popup-permission-remove-button > .button-box > .button-icon {
+ margin: 0;
+ width: 16px;
+ height: 16px;
+ list-style-image: url(chrome://browser/skin/panel-icons.svg#cancel);
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: graytext;
+}
+
+.identity-popup-permission-remove-button > .button-box > .button-text {
+ display: none;
+}
+
+/* swap foreground / background colors on hover */
+.identity-popup-permission-remove-button:not(:-moz-focusring):hover {
+ background-color: graytext;
+}
+
+.identity-popup-permission-remove-button:not(:-moz-focusring):hover > .button-box > .button-icon {
+ fill: -moz-field;
+}
+
+.identity-popup-permission-remove-button:not(:-moz-focusring):hover:active {
+ background-color: -moz-fieldtext;
+}
diff --git a/browser/themes/shared/controlcenter/permissions.svg b/browser/themes/shared/controlcenter/permissions.svg
new file mode 100644
index 000000000..77f0f3e50
--- /dev/null
+++ b/browser/themes/shared/controlcenter/permissions.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="24" height="24" viewBox="0 0 24 24">
+#include ../icon-colors.inc.svg
+
+ <defs>
+ <mask id="mask-permissions">
+ <path fill="#fff" d="M2,1h20c1.1,0,2,0.9,2,2v18c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2V3 C0,1.9,0.9,1,2,1z"/>
+ <path fill="#000" d="M12,3h9c0.6,0,1,0.4,1,1v16c0,0.6-0.4,1-1,1h-9V3z"/>
+ <path fill="#000" d="M5.5,12.5l2.7-3.7C8.4,8.5,8.8,8.5,9,8.7l0.7,0.5 c0.2,0.2,0.2,0.5,0,0.7L5.8,15c-0.2,0.2-0.5,0.3-0.8,0.1l-2.2-2.2c-0.2-0.2-0.2-0.5,0-0.7l0.8-0.8c0.2-0.2,0.5-0.2,0.7,0L5.5,12.5z" />
+ <rect x="16.3" y="8.5" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -3.5061 15.5355)" fill="#fff" width="1.4" height="7.1"/>
+ <rect x="16.3" y="8.5" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 20.5355 32.5061)" fill="#fff" width="1.4" height="7.1"/>
+ </mask>
+ </defs>
+
+ <rect class="fieldtext" id="permissions" width="24" height="24" mask="url(#mask-permissions)"/>
+</svg>
diff --git a/browser/themes/shared/controlcenter/tracking-protection.svg b/browser/themes/shared/controlcenter/tracking-protection.svg
new file mode 100644
index 000000000..1779378c1
--- /dev/null
+++ b/browser/themes/shared/controlcenter/tracking-protection.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="24" height="24" viewBox="0 0 24 24">
+#include ../icon-colors.inc.svg
+ <style>
+ g:not(:target) {
+ display: none;
+ }
+ </style>
+
+ <defs>
+ <path id="shape-shield-outer" d="M12,1L3.4,2.4C2.6,2.5,2,3.1,2,3.9c0,1.9,0,5.2,0.2,6.6c0.4,4.2,1.3,6.3,3.2,8.8C8,22.6,12,23,12,23s4-0.4,6.6-3.7 c1.9-2.4,2.8-4.5,3.2-8.8C22,9.1,22,5.7,22,3.9c0-0.8-0.6-1.4-1.4-1.5L12,1L12,1z"/>
+ <path id="shape-shield-inner" d="M12,3l7.9,1.2c0.1,0,0.1,0,0.1,0.1c0,2.9,0,5.2-0.1,6.1c-0.4,4-1.2,5.6-2.8,7.6c-1.8,2.3-4.4,2.8-5.1,3 c-0.7-0.1-3.3-0.7-5.1-3c-1.6-1.9-2.4-3.6-2.8-7.6C4,9.5,4,7.3,4,4.3c0,0,0-0.1,0.1-0.1L12,3"/>
+ <path id="shape-shield-detail" d="M12,20c-0.8-0.2-2.9-0.7-4.4-2.6c-1.4-1.8-2.1-3.2-2.5-7C5,9.6,5,7.7,5,5.1L12,4 V20z"/>
+
+ <mask id="mask-shield-cutout">
+ <rect width="24" height="24" fill="#000"/>
+ <use xlink:href="#shape-shield-outer" fill="#fff"/>
+ <use xlink:href="#shape-shield-inner" fill="#000"/>
+ <use xlink:href="#shape-shield-detail" fill="#fff"/>
+ </mask>
+
+ <mask id="mask-shield-cutout-disabled">
+ <rect width="24" height="24" fill="#000"/>
+ <use xlink:href="#shape-shield-outer" fill="#fff"/>
+ <use xlink:href="#shape-shield-inner" fill="#000"/>
+ <use xlink:href="#shape-shield-detail" fill="#fff"/>
+ <line x1="3" y1="24" x2="23" y2="3" stroke="#000" stroke-width="3"/>
+ </mask>
+ </defs>
+
+ <g id="enabled">
+ <use class="fieldtext" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout)"/>
+ </g>
+
+ <g id="disabled">
+ <use class="fieldtext" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout-disabled)"/>
+ <line x1="3" y1="22" x2="23" y2="1" stroke="#d92d21" stroke-width="3"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/controlcenter/warning-gray.svg b/browser/themes/shared/controlcenter/warning-gray.svg
new file mode 100644
index 000000000..5f122c3ee
--- /dev/null
+++ b/browser/themes/shared/controlcenter/warning-gray.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16" height="16" viewBox="0 0 16 16">
+ <path fill="#808080" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
+ <path fill="#fff" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
+</svg>
diff --git a/browser/themes/shared/controlcenter/warning-yellow.svg b/browser/themes/shared/controlcenter/warning-yellow.svg
new file mode 100644
index 000000000..e2d3a3664
--- /dev/null
+++ b/browser/themes/shared/controlcenter/warning-yellow.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16" height="16" viewBox="0 0 16 16">
+ <path fill="#ffbf00" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
+ <path fill="#fff" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
+</svg>
diff --git a/browser/themes/shared/ctrlTab.inc.css b/browser/themes/shared/ctrlTab.inc.css
new file mode 100644
index 000000000..1205d0499
--- /dev/null
+++ b/browser/themes/shared/ctrlTab.inc.css
@@ -0,0 +1,63 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+/* Ctrl-Tab */
+
+#ctrlTab-panel {
+ -moz-appearance: none;
+%ifdef XP_MACOSX
+ -moz-window-shadow: none;
+%endif
+ background: hsla(0,0%,33%,.85);
+ color: white;
+ border-style: none;
+ padding: 20px 10px 10px;
+%ifndef XP_MACOSX
+ font-weight: bold;
+%endif
+ text-shadow: 0 0 1px hsl(0,0%,12%), 0 0 2px hsl(0,0%,12%);
+}
+
+.ctrlTab-favicon[src] {
+ background-color: white;
+ width: 20px;
+ height: 20px;
+ padding: 2px;
+}
+
+.ctrlTab-preview-inner > .tabPreview-canvas {
+ box-shadow: 1px 1px 2px hsl(0,0%,12%);
+}
+
+.ctrlTab-preview:not(#ctrlTab-showAll) > * > .ctrlTab-preview-inner > .tabPreview-canvas {
+ margin-bottom: 2px;
+}
+
+.ctrlTab-preview-inner {
+ padding: 8px;
+ border: 2px solid transparent;
+ border-radius: .5em;
+}
+
+.ctrlTab-preview:not(#ctrlTab-showAll) > * > .ctrlTab-preview-inner {
+ margin: -10px -10px 0;
+}
+
+#ctrlTab-showAll:not(:focus) > * > .ctrlTab-preview-inner {
+ background-color: rgba(255,255,255,.2);
+}
+
+.ctrlTab-preview:focus > * > .ctrlTab-preview-inner {
+ color: white;
+ background-color: rgba(0,0,0,.6);
+ text-shadow: none;
+ border-color: white;
+}
+
+#ctrlTab-showAll {
+ margin-top: .5em;
+}
+
diff --git a/browser/themes/shared/customizableui/customize-illustration-rtl.png b/browser/themes/shared/customizableui/customize-illustration-rtl.png
new file mode 100644
index 000000000..12005095c
--- /dev/null
+++ b/browser/themes/shared/customizableui/customize-illustration-rtl.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/customize-illustration-rtl@2x.png b/browser/themes/shared/customizableui/customize-illustration-rtl@2x.png
new file mode 100644
index 000000000..c1af12990
--- /dev/null
+++ b/browser/themes/shared/customizableui/customize-illustration-rtl@2x.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/customize-illustration.png b/browser/themes/shared/customizableui/customize-illustration.png
new file mode 100644
index 000000000..9131951b0
--- /dev/null
+++ b/browser/themes/shared/customizableui/customize-illustration.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/customize-illustration@2x.png b/browser/themes/shared/customizableui/customize-illustration@2x.png
new file mode 100644
index 000000000..50c0d1949
--- /dev/null
+++ b/browser/themes/shared/customizableui/customize-illustration@2x.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/customizeFavicon.ico b/browser/themes/shared/customizableui/customizeFavicon.ico
new file mode 100644
index 000000000..c7bd84339
--- /dev/null
+++ b/browser/themes/shared/customizableui/customizeFavicon.ico
Binary files differ
diff --git a/browser/themes/shared/customizableui/customizeMode.inc.css b/browser/themes/shared/customizableui/customizeMode.inc.css
new file mode 100644
index 000000000..48681d9f3
--- /dev/null
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -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/. */
+
+/* Customization mode */
+
+:root {
+ --drag-drop-transition-duration: .3s;
+}
+
+#main-window:-moz-any([customize-entering],[customize-entered]) #browser-bottombox {
+ margin-bottom: 2em;
+}
+
+#main-window:-moz-any([customize-entering],[customize-entered]) #content-deck,
+#main-window:-moz-any([customize-entering],[customize-entered]) #browser-bottombox,
+#main-window:-moz-any([customize-entering],[customize-entered]) #navigator-toolbox {
+ margin-left: 2em;
+ margin-right: 2em;
+}
+
+#main-window:-moz-any([customize-entering],[customize-exiting]) #tab-view-deck {
+ pointer-events: none;
+}
+
+#main-window[customize-entered] .customization-target:not(:-moz-any(#PanelUI-contents, #TabsToolbar, #toolbar-menubar))::before,
+#PanelUI-contents > .panel-customization-placeholder {
+ -moz-outline-radius: 2.5px;
+ outline: 1px dashed transparent;
+}
+
+#main-window[customize-entered] .customization-target:not(:-moz-any(#PanelUI-contents, #TabsToolbar, #toolbar-menubar))::before {
+ /* Prevent jumping of tabs when switching a window between inactive and active (bug 853415). */
+ -moz-box-ordinal-group: 0;
+ content: "";
+ display: -moz-box;
+ height: 100%;
+ left: 0;
+ outline-offset: -2px;
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+/* Shift the TabsToolbar outline up 2px since the #nav-bar is shifted up by 1px and the
+ #TabsToolbar::after is a pixel higher to draw the bottom border of the tabstrip so this makes the
+ offset from the bottom effectively the same as other targets (-2px). */
+#main-window[customize-entered] #TabsToolbar.customization-target::before {
+ top: -2px;
+}
+
+/* The parents of the outline pseudo-elements need to be positioned so that the outline is positioned relative to it. */
+#main-window[customize-entered] .customization-target:not(:-moz-any(#PanelUI-contents, #TabsToolbar, #toolbar-menubar)):hover,
+#main-window[customize-entered] .customization-target[customizing-dragovertarget]:not(:-moz-any(#PanelUI-contents, #TabsToolbar, #toolbar-menubar)),
+#main-window[customize-entered] #nav-bar-customization-target.customization-target {
+ position: relative;
+}
+
+/* Most target outlines are shown on hover and drag over but the panel menu uses
+ placeholders instead. */
+#main-window[customize-entered] .customization-target:not(:-moz-any(#PanelUI-contents, #TabsToolbar, #toolbar-menubar)):hover::before,
+#main-window[customize-entered] .customization-target[customizing-dragovertarget]:not(:-moz-any(#PanelUI-contents, #TabsToolbar, #toolbar-menubar))::before,
+/* nav-bar and panel outlines are always shown */
+#nav-bar[showoutline=true] > #nav-bar-customization-target.customization-target::before {
+ outline-color: currentColor;
+}
+
+#nav-bar[showoutline=true] > #nav-bar-customization-target.customization-target::before {
+ transition: outline-color 250ms linear;
+}
+
+#PanelUI-contents[showoutline=true] > .panel-customization-placeholder {
+ transition: outline-color 250ms linear;
+ outline-color: var(--panel-separator-color);
+}
+
+#PanelUI-contents > .panel-customization-placeholder {
+ cursor: auto;
+ outline-offset: -5px;
+}
+
+#main-window[customizing] .customization-target:not(#PanelUI-contents) {
+ min-width: 100px;
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+#customization-container {
+ background-color: -moz-field;
+ color: -moz-fieldText;
+ text-shadow: none;
+}
+
+#customization-palette,
+#customization-empty {
+ padding: 5px 25px 25px;
+}
+
+#customization-header {
+ font-size: 1.75em;
+ line-height: 1.75em;
+ color: GrayText;
+ font-weight: 200;
+ margin: 25px 25px 12px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid ThreeDLightShadow;
+}
+
+#customization-panel-container {
+ padding: 15px 25px 25px;
+ background-image: linear-gradient(to bottom, #3e86ce, #3878ba);
+}
+
+#main-window:-moz-any([customize-entering],[customize-entered]) #browser-bottombox,
+#customization-footer {
+ background-color: -moz-dialog;
+}
+
+#customization-footer {
+ border-top: 1px solid ThreeDLightShadow;
+ padding: 10px;
+}
+
+%if defined(XP_MACOSX) || defined(XP_WIN)
+%ifdef XP_WIN
+@media (-moz-windows-default-theme) {
+%endif
+
+.customizationmode-button {
+ border: 1px solid rgb(192,192,192);
+ border-radius: 3px;
+ margin: 5px;
+ padding: 2px 10px;
+ background-color: rgb(251,251,251);
+ color: rgb(71,71,71);
+ box-shadow: 0 1px rgba(255, 255, 255, 0.5),
+ inset 0 1px rgba(255, 255, 255, 0.5);
+ -moz-appearance: none;
+}
+
+.customizationmode-button > .box-inherit {
+ border-width: 0;
+ padding-inline-start: 0;
+ padding-inline-end: 0;
+}
+
+.customizationmode-button > .button-icon {
+ margin-inline-start: 0;
+}
+
+.customizationmode-button:not([type=menu]) > .button-text {
+ margin-inline-end: 0;
+}
+
+.customizationmode-button > .button-menu-dropmarker {
+ margin-inline-end: 0;
+ padding-inline-end: 0;
+}
+
+.customizationmode-button:hover:active:not([disabled]),
+.customizationmode-button[open],
+.customizationmode-button[checked] {
+ background-color: rgb(218, 218, 218);
+ border-color: rgb(168, 168, 168);
+ text-shadow: 0 1px rgb(236, 236, 236);
+ box-shadow: 0 1px rgba(255, 255, 255, 0.5),
+ inset 0 1px rgb(196, 196, 196);
+}
+
+.customizationmode-button[disabled="true"] {
+ opacity: .5;
+}
+
+%ifdef XP_WIN
+} /* @media (-moz-windows-default-theme) */
+%endif
+%endif /* defined(XP_MACOSX) || defined(XP_WIN) */
+
+.customizationmode-button > .box-inherit > .box-inherit > .button-icon,
+.customizationmode-button > .button-box > .button-icon {
+ height: 24px;
+}
+
+%ifdef CAN_DRAW_IN_TITLEBAR
+#customization-titlebar-visibility-button > .button-box > .button-text,
+%endif
+#customization-lwtheme-button > .box-inherit > .box-inherit > .button-text {
+ /* Sadly, button.css thinks its margins are perfect for everyone. */
+ margin-inline-start: 6px !important;
+}
+
+#customization-lwtheme-button > .box-inherit > .box-inherit > .button-icon {
+ width: 20px;
+ height: 20px;
+ border-radius: 2px;
+ background-image: url("chrome://browser/skin/theme-switcher-icon.png");
+ background-size: contain;
+}
+
+%ifdef CAN_DRAW_IN_TITLEBAR
+#customization-titlebar-visibility-button {
+ list-style-image: url("chrome://browser/skin/customizableui/customize-titleBar-toggle.png");
+ -moz-image-region: rect(0, 24px, 24px, 0);
+}
+
+#customization-titlebar-visibility-button > .button-box > .button-icon {
+ vertical-align: middle;
+}
+
+#customization-titlebar-visibility-button[checked] {
+ -moz-image-region: rect(0, 48px, 24px, 24px);
+}
+
+@media (min-resolution: 1.1dppx) {
+ #customization-titlebar-visibility-button {
+ list-style-image: url("chrome://browser/skin/customizableui/customize-titleBar-toggle@2x.png");
+ -moz-image-region: rect(0, 48px, 48px, 0);
+ }
+
+ #customization-titlebar-visibility-button[checked] {
+ -moz-image-region: rect(0, 96px, 48px, 48px);
+ }
+}
+%endif /* CAN_DRAW_IN_TITLEBAR */
+
+#main-window[customize-entered] #customization-panel-container {
+ background-image: url("chrome://browser/skin/customizableui/customizeMode-separatorHorizontal.png"),
+ url("chrome://browser/skin/customizableui/customizeMode-separatorVertical.png"),
+ url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png"),
+ url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
+ linear-gradient(to bottom, #3e86ce, #3878ba);
+ background-position: center top, left center, left top, left top, left top;
+ background-repeat: no-repeat, no-repeat, repeat, repeat, no-repeat;
+ background-size: auto 12px, 12px 100%, auto, auto, auto;
+ background-attachment: scroll, scroll, fixed, fixed, scroll;
+}
+
+toolbarpaletteitem[place="toolbar"] {
+ transition: border-width 250ms ease-in-out;
+}
+
+toolbarpaletteitem[mousedown] {
+ cursor: -moz-grabbing;
+}
+
+.panel-customization-placeholder,
+toolbarpaletteitem[place="palette"],
+toolbarpaletteitem[place="panel"] {
+ transition: transform var(--drag-drop-transition-duration) ease-in-out;
+}
+
+#customization-palette {
+ transition: opacity .3s ease-in-out;
+ opacity: 0;
+}
+
+#customization-palette[showing="true"] {
+ opacity: 1;
+}
+
+toolbarpaletteitem toolbarbutton[disabled] {
+ color: inherit !important;
+}
+
+toolbarpaletteitem[notransition].panel-customization-placeholder,
+toolbarpaletteitem[notransition][place="toolbar"],
+toolbarpaletteitem[notransition][place="palette"],
+toolbarpaletteitem[notransition][place="panel"] {
+ transition: none;
+}
+
+toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
+toolbarpaletteitem > toolbarbutton > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+toolbarpaletteitem > toolbaritem.panel-wide-item,
+toolbarpaletteitem > toolbarbutton[type="menu-button"] {
+ transition: transform var(--drag-drop-transition-duration) cubic-bezier(.6, 2, .75, 1.5) !important;
+}
+
+toolbarpaletteitem[mousedown] > toolbarbutton > .toolbarbutton-icon,
+toolbarpaletteitem[mousedown] > toolbarbutton > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+ transform: scale(1.3);
+}
+
+toolbarpaletteitem[mousedown] > toolbaritem.panel-wide-item,
+toolbarpaletteitem[mousedown] > toolbarbutton[type="menu-button"] {
+ transform: scale(1.1);
+}
+
+/* Override the toolkit styling for items being dragged over. */
+toolbarpaletteitem[place="toolbar"] {
+ border-left-width: 0;
+ border-right-width: 0;
+ margin-right: 0;
+ margin-left: 0;
+}
+
+#customization-palette:not([hidden]) {
+ margin-bottom: 25px;
+}
+
+toolbarpaletteitem[place="palette"]:-moz-focusring,
+toolbarpaletteitem[place="panel"]:-moz-focusring,
+toolbarpaletteitem[place="toolbar"]:-moz-focusring {
+ outline-width: 0;
+}
+
+toolbarpaletteitem[place="palette"]:not([mousedown="true"]):-moz-focusring,
+toolbarpaletteitem[place="panel"]:not([mousedown="true"]):-moz-focusring,
+toolbarpaletteitem[place="toolbar"]:not([mousedown="true"]):-moz-focusring {
+ /* Delay adding the focusring back until after the transform transition completes. */
+ transition: outline-width .01s linear var(--drag-drop-transition-duration);
+ outline: 1px dotted;
+ -moz-outline-radius: 2.5px;
+}
+
+toolbarpaletteitem[place="toolbar"]:not([mousedown="true"]):-moz-focusring {
+ outline-offset: -5px;
+}
+
+#wrapper-edit-controls[place="palette"] > #edit-controls > toolbarbutton,
+#wrapper-edit-controls[place="palette"] > #edit-controls > separator,
+#wrapper-zoom-controls[place="palette"] > #zoom-controls > toolbarbutton,
+#wrapper-zoom-controls[place="palette"] > #zoom-controls > separator {
+ margin-top: 20px;
+}
+
+#wrapper-edit-controls[place="palette"] > #edit-controls > toolbarbutton,
+#wrapper-zoom-controls[place="palette"] > #zoom-controls > toolbarbutton {
+ margin-left: 0;
+ margin-right: 0;
+ max-width: 24px;
+ min-width: 24px;
+ max-height: 24px;
+ min-height: 24px;
+ padding: 4px;
+}
+
+#wrapper-edit-controls[place="palette"] > #edit-controls > toolbarbutton > .toolbarbutton-icon,
+#wrapper-zoom-controls[place="palette"] > #zoom-controls > toolbarbutton > .toolbarbutton-icon {
+ width: 16px;
+}
+
+#wrapper-edit-controls > #edit-controls > toolbarbutton > .toolbarbutton-icon {
+ opacity: 1; /* To ensure these buttons always look enabled in customize mode */
+}
+
+#wrapper-zoom-controls[place="palette"] > #zoom-controls > #zoom-reset-button,
+#wrapper-zoom-controls[place="palette"] > #zoom-controls > #zoom-reset-button + separator {
+ display: none;
+}
+
+#wrapper-personal-bookmarks:not([place="toolbar"]) > #personal-bookmarks {
+ -moz-box-pack: center;
+ min-height: 48px;
+}
+
+#personal-bookmarks[cui-areatype="toolbar"]:not([overflowedItem=true]) > #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
+ margin-inline-end: 5px;
+}
+
+#customization-palette > toolbarpaletteitem > label {
+ text-align: center;
+ margin-left: 0;
+ margin-right: 0;
+}
+
+#customization-lwtheme-menu > .panel-arrowcontainer > .panel-arrowcontent {
+ -moz-box-orient: vertical;
+ /* Make the panel padding uniform across all platforms due to the
+ styling of the section headers and footer. */
+ padding: 10px;
+}
+
+.customization-lwtheme-menu-theme > .toolbarbutton-icon {
+ width: 32px;
+ height: 32px;
+}
+
+.customization-lwtheme-menu-theme {
+ -moz-appearance: none;
+ border: 1px solid transparent;
+ margin: 0 -5px 5px;
+ padding-top: 0;
+ padding-inline-end: 5px;
+ padding-bottom: 0;
+ padding-inline-start: 0;
+}
+
+.customization-lwtheme-menu-theme[defaulttheme] {
+ list-style-image: url(chrome://browser/skin/theme-switcher-icon.png);
+}
+
+.customization-lwtheme-menu-theme[active="true"],
+.customization-lwtheme-menu-theme:hover {
+ background-color: var(--arrowpanel-dimmed);
+ border-color: var(--panel-separator-color);
+}
+
+.customization-lwtheme-menu-theme[active="true"],
+.customization-lwtheme-menu-theme:hover:active {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+.customization-lwtheme-menu-theme > .toolbarbutton-icon {
+ margin: 5px;
+}
+
+.customization-lwtheme-menu-theme > .toolbarbutton-text {
+ text-align: start;
+}
+
+#customization-lwtheme-menu-header,
+#customization-lwtheme-menu-recommended {
+ padding: 10px;
+ margin-bottom: 5px;
+}
+
+#customization-lwtheme-menu-header,
+#customization-lwtheme-menu-recommended,
+#customization-lwtheme-menu-footer {
+ background-color: var(--arrowpanel-dimmed);
+ margin-right: -10px;
+ margin-left: -10px;
+}
+
+#customization-lwtheme-menu-header {
+ margin-top: -10px;
+ border-bottom: 1px solid var(--arrowpanel-dimmed);
+}
+
+#customization-lwtheme-menu-recommended {
+ border-top: 1px solid var(--arrowpanel-dimmed);
+ border-bottom: 1px solid var(--arrowpanel-dimmed);
+}
+
+#customization-lwtheme-menu-footer {
+ background: linear-gradient(var(--arrowpanel-dimmed) 60%, transparent) border-box;
+ border-top: 1px solid var(--arrowpanel-dimmed);
+ margin-bottom: -10px;
+}
+
+.customization-lwtheme-menu-footeritem {
+ -moz-appearance: none;
+ -moz-box-flex: 1;
+ color: inherit;
+ border-style: none;
+ padding: 10px;
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.customization-lwtheme-menu-footeritem:hover {
+ background: linear-gradient(var(--arrowpanel-dimmed) 40%, transparent) padding-box;
+}
+
+.customization-lwtheme-menu-footeritem:first-child {
+ border-inline-end: 1px solid var(--panel-separator-color);
+}
+
+%include customizeTip.inc.css
diff --git a/browser/themes/shared/customizableui/customizeTip.inc.css b/browser/themes/shared/customizableui/customizeTip.inc.css
new file mode 100644
index 000000000..26c6ee1ea
--- /dev/null
+++ b/browser/themes/shared/customizableui/customizeTip.inc.css
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#customization-tipPanel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+ margin: 0;
+ min-width: 400px;
+ max-width: 1000px;
+ min-height: 200px;
+ border-radius: 3px;
+ background-image: linear-gradient(90deg, #a0dfff 0%, #ceeeff 100%);
+ border: 0px solid rgba(0,148,221,.5);
+ box-shadow: 0 1px 5px 0 rgba(0,0,0,.5), inset 0 1px 1px 0 #fff;
+ color: rgb(51,51,51);
+}
+
+#customization-tipPanel > .panel-arrowcontainer > .panel-arrowcontent:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(90deg, #ceeeff 0%, #a0dfff 100%);
+}
+
+.customization-tipPanel-infoBox {
+ margin: 20px 25px 25px;
+ width: 25px;
+ background-image: url(chrome://browser/skin/customizableui/info-icon-customizeTip.png);
+ background-repeat: no-repeat;
+}
+
+.customization-tipPanel-content {
+ margin: 25px 0;
+ font-size: 12px;
+ line-height: 18px;
+}
+
+.customization-tipPanel-em {
+ margin: 0;
+ font-weight: bold;
+}
+
+.customization-tipPanel-contentImage {
+ margin-top: 25px;
+ list-style-image: url(chrome://browser/skin/customizableui/customize-illustration.png);
+ min-width: 300px;
+ max-width: 300px;
+ min-height: 190px;
+ max-height: 190px;
+ display: -moz-box;
+}
+
+.customization-tipPanel-contentImage:-moz-locale-dir(rtl) {
+ list-style-image: url(chrome://browser/skin/customizableui/customize-illustration-rtl.png);
+}
+
+.customization-tipPanel-link {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ color: rgb(25,82,171);
+ margin: 0;
+ cursor: pointer;
+}
+
+.customization-tipPanel-link > .button-box > .button-text {
+ margin: 0 !important;
+}
+
+.customization-tipPanel-closeBox > .close-icon {
+ -moz-appearance: none;
+ border: 0;
+ margin-inline-end: -25px;
+}
+
+#customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="left"],
+#customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="right"] {
+ list-style-image: url("chrome://browser/skin/customizableui/panelarrow-customizeTip.png");
+}
diff --git a/browser/themes/shared/customizableui/info-icon-customizeTip.png b/browser/themes/shared/customizableui/info-icon-customizeTip.png
new file mode 100644
index 000000000..0dfbbce5d
--- /dev/null
+++ b/browser/themes/shared/customizableui/info-icon-customizeTip.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/info-icon-customizeTip@2x.png b/browser/themes/shared/customizableui/info-icon-customizeTip@2x.png
new file mode 100644
index 000000000..7a87fac20
--- /dev/null
+++ b/browser/themes/shared/customizableui/info-icon-customizeTip@2x.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/menuPanel-customizeFinish.png b/browser/themes/shared/customizableui/menuPanel-customizeFinish.png
new file mode 100644
index 000000000..07be6a76a
--- /dev/null
+++ b/browser/themes/shared/customizableui/menuPanel-customizeFinish.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/menuPanel-customizeFinish@2x.png b/browser/themes/shared/customizableui/menuPanel-customizeFinish@2x.png
new file mode 100644
index 000000000..7562e138c
--- /dev/null
+++ b/browser/themes/shared/customizableui/menuPanel-customizeFinish@2x.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/panelUI.inc.css b/browser/themes/shared/customizableui/panelUI.inc.css
new file mode 100644
index 000000000..b46110ab5
--- /dev/null
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -0,0 +1,1769 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+
+%define menuPanelWidth 22.35em
+%define standaloneSubviewWidth 30em
+% XXXgijs This is the ugliest bit of code I think I've ever written for Mozilla.
+% Basically, the 0.1px is there to avoid CSS rounding errors causing buttons to wrap.
+% For gory details, refer to https://bugzilla.mozilla.org/show_bug.cgi?id=963365#c11
+% There's no calc() here (and therefore lots of calc() where this is used) because
+% we don't support nested calc(): https://bugzilla.mozilla.org/show_bug.cgi?id=968761
+%define menuPanelButtonWidth (@menuPanelWidth@ / 3 - 0.1px)
+%define buttonStateHover :not(:-moz-any([disabled],[open],:active)):hover
+%define menuStateHover :not(:-moz-any([disabled],:active))[_moz-menuactive]
+%define buttonStateActive :not([disabled]):-moz-any([open],:hover:active)
+%define menuStateActive :not([disabled])[_moz-menuactive]:active
+%define menuStateMenuActive :not([disabled])[_moz-menuactive]
+
+%include ../browser.inc
+
+:root {
+ --panel-ui-exit-subview-gutter-width: 38px;
+}
+
+#PanelUI-popup #PanelUI-contents:empty {
+ height: 128px;
+}
+
+#PanelUI-popup #PanelUI-contents:empty::before {
+ content: "";
+ background-image: url(chrome://browser/skin/customizableui/whimsy.png);
+ background-size: 64px 64px;
+ display: block;
+ width: 64px;
+ height: 64px;
+ position: absolute;
+ transition: transform 1s ease-out;
+ animation: whimsyMoveX 3.05s linear 0s infinite alternate,
+ whimsyMoveY 3.4s linear 0s infinite alternate;
+}
+
+#PanelUI-popup #PanelUI-contents:not(:hover):empty::before {
+ filter: grayscale(100%);
+}
+
+#PanelUI-popup #PanelUI-contents:active:empty::before {
+ animation: whimsyMoveX 3.05s linear 0s infinite alternate,
+ whimsyMoveY 3.4s linear 0s infinite alternate,
+ whimsyRotate 1s linear 0s infinite normal;
+}
+
+#PanelUI-popup #PanelUI-contents:-moz-locale-dir(rtl):empty::before {
+ animation: whimsyMoveXRTL 3.05s linear 0s infinite alternate,
+ whimsyMoveY 3.4s linear 0s infinite alternate;
+}
+
+#PanelUI-popup #PanelUI-contents:-moz-locale-dir(rtl):active:empty::before {
+ animation: whimsyMoveXRTL 3.05s linear 0s infinite alternate,
+ whimsyMoveY 3.4s linear 0s infinite alternate,
+ whimsyRotate 1s linear 0s infinite normal;
+}
+
+@media (min-resolution: 2dppx) {
+ #PanelUI-popup #PanelUI-contents:empty::before {
+ background-image: url(chrome://browser/skin/customizableui/whimsy@2x.png);
+ }
+}
+
+@keyframes whimsyMoveX {
+ /* These values are adjusted for the padding on the panel. */
+ from { margin-left: -15px; } to { margin-left: calc(100% - 49px); }
+}
+
+@keyframes whimsyMoveXRTL {
+ /* These values are adjusted for the padding on the panel. */
+ from { margin-right: -15px; } to { margin-right: calc(100% - 49px); }
+}
+
+@keyframes whimsyMoveY {
+ /* These values are adjusted for the padding and height of the panel. */
+ from { margin-top: -.5em; } to { margin-top: calc(64px - .5em); }
+}
+
+@keyframes whimsyRotate {
+ to { transform: perspective(5000px) rotateY(360deg); }
+}
+
+#PanelUI-button {
+ margin-inline-start: 2px;
+ border-inline-start: 1px solid;
+ border-image: linear-gradient(transparent, rgba(0,0,0,.1) 20%, rgba(0,0,0,.1) 80%, transparent);
+ border-image-slice: 1;
+}
+
+#nav-bar[brighttext] > #PanelUI-button {
+ border-image-source: linear-gradient(transparent, rgba(100%,100%,100%,.2) 20%, rgba(100%,100%,100%,.2) 80%, transparent);
+}
+
+#PanelUI-menu-button[badge-status] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ display: -moz-box;
+ height: 10px;
+ width: 10px;
+ background-size: contain;
+ border: none;
+}
+
+#PanelUI-menu-button[badge-status="download-success"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ display: none;
+}
+
+#PanelUI-menu-button[badge-status="update-succeeded"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
+ height: 13px;
+}
+
+#PanelUI-menu-button[badge-status="update-failed"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+ height: 13px;
+}
+
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ box-shadow: none;
+ filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+}
+
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#PanelUI-menu-button[badge-status="download-severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ width: 7px;
+ height: 7px;
+ min-width: 0;
+ border-radius: 50%;
+ /* "!important" is necessary to override the rule in toolbarbutton.css */
+ margin-top: -1px !important;
+ margin-right: -2px !important;
+}
+
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #FFBF00;
+}
+
+#PanelUI-menu-button[badge-status="download-severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #D90000;
+}
+
+#PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ height: 13px;
+ background: transparent url(chrome://browser/skin/warning.svg) no-repeat center;
+}
+
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+ filter: none;
+}
+
+.panel-subviews {
+ padding: 4px;
+ background-clip: padding-box;
+ border-left: 1px solid var(--arrowpanel-border-color);
+ box-shadow: 0 3px 5px hsla(210,4%,10%,.1),
+ 0 0 7px hsla(210,4%,10%,.1);
+ margin-inline-start: var(--panel-ui-exit-subview-gutter-width);
+}
+
+.panel-viewstack[viewtype="main"] > .panel-subviews {
+ transform: translateX(@menuPanelWidth@);
+}
+
+.panel-viewstack[viewtype="main"] > .panel-subviews:-moz-locale-dir(rtl) {
+ transform: translateX(-@menuPanelWidth@);
+}
+
+panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
+ display: none;
+}
+
+.panel-viewstack:not([viewtype="main"]) > .panel-mainview > #PanelUI-mainView {
+ -moz-box-flex: 1;
+}
+
+.panel-subview-body {
+ overflow-y: auto;
+ overflow-x: hidden;
+ -moz-box-flex: 1;
+}
+
+#PanelUI-popup .panel-subview-body {
+ margin: -4px;
+ padding: 4px 4px;
+}
+
+.panel-subview-header,
+.subviewbutton.panel-subview-footer {
+ box-sizing: border-box;
+ min-height: 41px;
+ padding: 11px 12px;
+}
+
+.panel-subview-header {
+ margin: -4px -4px 4px;
+ border-bottom: 1px solid var(--panel-separator-color);
+ color: GrayText;
+ font-variant: small-caps;
+}
+
+.cui-widget-panelview .panel-subview-header {
+ display: none;
+}
+
+.cui-widget-panelview .subviewbutton.panel-subview-footer {
+ margin: 4px 0 0;
+ -moz-box-pack: center;
+}
+
+#PanelUI-mainView {
+ display: flex;
+ flex-direction: column;
+}
+
+#PanelUI-popup > arrowscrollbox > autorepeatbutton {
+ display: none;
+}
+#PanelUI-popup > arrowscrollbox > scrollbox {
+ overflow: visible;
+}
+
+#PanelUI-popup > .panel-arrowcontainer > .panel-arrowcontent {
+ overflow: hidden;
+}
+
+#PanelUI-popup > .panel-arrowcontainer > .panel-arrowcontent,
+.cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box {
+ padding: 0;
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-multiline-text {
+ line-height: 1.2;
+ max-height: 2.4em;
+}
+
+.panelUI-grid .toolbarbutton-1:not([auto-hyphens="off"]) > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
+.panelUI-grid .toolbarbutton-1:not([auto-hyphens="off"]) > .toolbarbutton-multiline-text {
+ -moz-hyphens: auto;
+}
+
+.panelUI-grid:not([customize-transitioning]) .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text,
+.panelUI-grid:not([customize-transitioning]) .toolbarbutton-1 > .toolbarbutton-multiline-text {
+ position: absolute;
+ clip: rect(-0.1em, auto, 2.6em, auto);
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-text,
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-multiline-text {
+ text-align: center;
+ /* Need to override toolkit theming which sets margin: 0 !important; */
+ margin: 2px 0 0 !important;
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text {
+ text-align: center;
+ margin: -1px 0 0;
+}
+
+#wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls,
+#wrapper-zoom-controls:-moz-any([place="palette"],[place="panel"]) > #zoom-controls {
+ margin-inline-start: 0;
+}
+
+#PanelUI-contents {
+ max-width: @menuPanelWidth@;
+}
+
+#BMB_bookmarksPopup,
+.panel-mainview:not([panelid="PanelUI-popup"]) {
+ max-width: @standaloneSubviewWidth@;
+}
+
+/* Give WebExtension stand-alone panels extra width for Chrome compatibility */
+.cui-widget-panel[viewId^=PanelUI-webext-] .panel-mainview {
+ max-width: 800px;
+}
+
+.cui-widget-panel[viewId^=PanelUI-webext-] > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+panelview[id^=PanelUI-webext-] {
+ overflow: hidden;
+}
+
+panelview:not([mainview]) .toolbarbutton-text,
+.cui-widget-panel toolbarbutton > .toolbarbutton-text {
+ text-align: start;
+ display: -moz-box;
+}
+
+.cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 4px 0;
+}
+
+.cui-widget-panel.cui-widget-panelWithFooter > .panel-arrowcontainer > .panel-arrowcontent {
+ padding-bottom: 0;
+}
+
+#PanelUI-contents {
+ display: block;
+ flex: 1 0 auto;
+ margin-left: auto;
+ margin-right: auto;
+ padding: .5em 0;
+ max-width: @menuPanelWidth@;
+}
+
+#PanelUI-contents-scroller {
+ overflow-y: auto;
+ overflow-x: hidden;
+ width: @menuPanelWidth@;
+ padding-left: 5px;
+ padding-right: 5px;
+ flex: auto;
+}
+
+.toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton > .toolbarbutton-icon {
+ min-width: 0;
+ min-height: 0;
+ margin: 0;
+}
+
+toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item),
+.panelUI-grid .toolbarbutton-1,
+.panel-customization-placeholder-child {
+ -moz-appearance: none;
+ -moz-box-orient: vertical;
+ width: calc(@menuPanelButtonWidth@);
+ height: calc(51px + 2.2em);
+}
+
+/* In order to have button labels constrained appropriately, items inside the toolbarpaletteitem
+ * should have a min-width set so they abide by the width set above (which they do outside of
+ * customize mode because they're in a flexed container) */
+toolbarpaletteitem[place="panel"]:not([haswideitem=true]) > .toolbarbutton-1 {
+ min-width: 0.01px;
+}
+
+/* Help SDK buttons fit in. */
+toolbarpaletteitem[place="palette"] > toolbarbutton[constrain-size="true"] > .toolbarbutton-icon,
+toolbarpaletteitem[place="palette"] > toolbarbutton[constrain-size="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+toolbarbutton[constrain-size="true"][cui-areatype="menu-panel"] > .toolbarbutton-icon,
+toolbarbutton[constrain-size="true"][cui-areatype="menu-panel"] > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+ height: 32px;
+ width: 32px;
+}
+
+toolbarpaletteitem:-moz-any([place="palette"], [place="panel"]) > toolbaritem[sdkstylewidget="true"] > .toolbarbutton-1 > .toolbarbutton-icon {
+ width: 32px;
+ height: 32px;
+}
+
+.customization-palette .toolbarbutton-1 {
+ -moz-appearance: none;
+ -moz-box-orient: vertical;
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-appearance: none;
+ -moz-box-orient: vertical;
+ width: calc(@menuPanelButtonWidth@ - 2px);
+ height: calc(49px + 2.2em);
+ border: 0;
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-text,
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text {
+ margin-top: 2px; /* Hack needed to get the label of type=menu-button aligned with other buttons */
+}
+
+.panel-customization-placeholder-child {
+ margin: 6px 0 0;
+ padding: 2px 6px;
+ border: 1px solid transparent;
+}
+
+.panelUI-grid .toolbarbutton-1[type="menu"] {
+ background-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+ background-position: right 3px top 16px;
+ background-repeat: no-repeat;
+}
+
+.panelUI-grid .toolbarbutton-1[type="menu"]:-moz-locale-dir(rtl) {
+ background-position: left 3px top 16px;
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ -moz-box-align: center;
+ width: 16px;
+ margin-inline-start: -16px;
+ height: 51px;
+ margin-bottom: 2.2em;
+ padding: 0;
+}
+
+.panelUI-grid .toolbarbutton-1:not([buttonover])@buttonStateHover@ > .toolbarbutton-menubutton-dropmarker {
+ background-color: var(--arrowpanel-dimmed) !important;
+ border-radius: 0 0 0 2px;
+}
+
+.panelUI-grid .toolbarbutton-1:not([buttonover])@buttonStateHover@ > .toolbarbutton-menubutton-dropmarker:-moz-locale-dir(rtl) {
+ border-radius: 0 0 2px 0;
+}
+
+#main-window:not([customizing]) .panel-combined-button[disabled] > .toolbarbutton-icon {
+ opacity: .5;
+}
+
+toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) {
+ width: calc(@menuPanelButtonWidth@);
+ margin: 0 !important;
+}
+
+toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) {
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"] > iframe {
+ margin: 4px auto;
+}
+
+#PanelUI-multiView[viewtype="subview"] > .panel-viewcontainer > .panel-viewstack > .panel-mainview > #PanelUI-mainView {
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .panel-wide-item,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-contents-scroller > #PanelUI-contents > .toolbarbutton-1:not([panel-multiview-anchor="true"]),
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-update-status,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-status > #PanelUI-fxa-avatar,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-status > #PanelUI-fxa-label,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-fxa > #PanelUI-fxa-icon,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > toolbarseparator,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-customize,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-mainView > #PanelUI-footer > #PanelUI-footer-inner > #PanelUI-help:not([panel-multiview-anchor="true"]) {
+ opacity: .5;
+}
+
+/*
+ * XXXgijs: this is a workaround for a layout issue that was caused by these iframes,
+ * which was affecting subview display. Because of this, we're hiding the iframe *only*
+ * when displaying a subview. The discerning user might notice this, but it's not nearly
+ * as bad as the brokenness.
+ * This hack should be removed once https://bugzilla.mozilla.org/show_bug.cgi?id=975375
+ * is addressed.
+ */
+#PanelUI-multiView[viewtype="subview"] toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > iframe {
+ visibility: hidden;
+}
+
+toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > .toolbarbutton-text {
+ text-align: center;
+}
+
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-icon,
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-badge-stack,
+.customization-palette .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+.customization-palette .toolbarbutton-1 > .toolbarbutton-icon,
+.customization-palette .toolbarbutton-1 > .toolbarbutton-badge-stack,
+.panelUI-grid #bookmarks-toolbar-placeholder > .toolbarbutton-icon,
+.customization-palette #bookmarks-toolbar-placeholder > .toolbarbutton-icon,
+.panel-customization-placeholder-child > .toolbarbutton-icon {
+ width: 32px;
+ height: 32px;
+ min-width: 32px;
+ min-height: 32px;
+ /* Explanation for the below formula (A / B - C)
+ A
+ Each button is @menuPanelButtonWidth@ wide
+ B
+ Each button has two margins.
+ C (46px / 2 = 23px)
+ The button icon is 32 pixels wide.
+ The button has 12px of horizontal padding (6 on each side).
+ The button has 2px of horizontal border (1 on each side).
+ Total width of button's icon + button padding should therefore be 46px,
+ which means each horizontal margin should be the half the button's width - (46/2) px.
+ */
+ margin: 4px calc(@menuPanelButtonWidth@ / 2 - 23px);
+}
+
+/* above we treat the container as the icon for the margins, that is so the
+/* badge itself is positioned correctly. Here we make sure that the icon itself
+/* has the minimum size we want, but no padding/margin. */
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+.customization-palette .toolbarbutton-1 > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+ width: 32px;
+ height: 32px;
+ min-width: 32px;
+ min-height: 32px;
+ margin: 0;
+ padding: 0;
+}
+
+toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
+ -moz-box-flex: 1;
+}
+
+#personal-bookmarks[overflowedItem=true] > #bookmarks-toolbar-placeholder {
+ -moz-box-flex: 1;
+}
+
+#personal-bookmarks[cui-areatype="toolbar"][overflowedItem=true] > #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
+ margin-inline-end: 2px;
+}
+
+#edit-controls@inAnyPanel@ > #copy-button,
+#zoom-controls@inAnyPanel@ > #zoom-reset-button {
+ border-left: none;
+ border-right: none;
+ border-radius: 0;
+}
+
+#zoom-in-button > .toolbarbutton-text,
+#zoom-out-button > .toolbarbutton-text,
+#zoom-reset-button > .toolbarbutton-icon {
+ display: none;
+}
+
+#PanelUI-footer {
+ display: flex;
+ flex-shrink: 0;
+ flex-direction: column;
+ background-color: var(--arrowpanel-dimmed);
+ padding: 0;
+ margin: 0;
+}
+
+#main-window[customizing] #PanelUI-footer-fxa {
+ display: none;
+}
+
+#PanelUI-footer-fxa:not([fxastatus="signedin"]) > toolbarseparator,
+#PanelUI-footer-fxa:not([fxastatus="signedin"]) > #PanelUI-fxa-icon,
+#PanelUI-footer-fxa:not([fxaprofileimage]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
+ display: none;
+}
+
+#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status::after {
+ content: url(chrome://browser/skin/warning.svg);
+ filter: drop-shadow(0 1px 0 hsla(206,50%,10%,.15));
+ width: 47px;
+ padding-top: 1px;
+ display: block;
+ text-align: center;
+ position: relative;
+ top: 25%;
+}
+
+#PanelUI-update-status[update-status]::after {
+ content: "";
+ width: 14px;
+ height: 14px;
+ margin-inline-end: 16.5px;
+ box-shadow: 0px 1px 0px rgba(255,255,255,.2) inset, 0px -1px 0px rgba(0,0,0,.1) inset, 0px 1px 0px rgba(12,27,38,.2);
+ border-radius: 2px;
+ background-size: contain;
+ display: -moz-box;
+}
+
+#PanelUI-update-status[update-status="succeeded"]::after {
+ background-image: url(chrome://browser/skin/update-badge.svg);
+ background-color: #74BF43;
+}
+
+#PanelUI-update-status[update-status="failed"]::after {
+ background-image: url(chrome://browser/skin/update-badge-failed.svg);
+ background-color: #D90000;
+}
+
+#PanelUI-fxa-status {
+ display: flex;
+ flex: 1 1 0%;
+ width: 1px;
+}
+
+#PanelUI-footer-inner,
+#PanelUI-footer-fxa:not([hidden]) {
+ display: flex;
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+#PanelUI-multiView[viewtype="subview"] #PanelUI-footer-inner,
+#PanelUI-multiView[viewtype="subview"] #PanelUI-footer-fxa {
+ position: relative;
+}
+
+#PanelUI-footer-inner > toolbarseparator,
+#PanelUI-footer-fxa > toolbarseparator {
+ border: 0;
+ border-left: 1px solid var(--panel-separator-color);
+ margin: 7px 0 7px;
+ -moz-appearance: none;
+}
+
+#PanelUI-footer-inner:hover > toolbarseparator,
+#PanelUI-footer-fxa:hover > toolbarseparator {
+ margin: 0;
+}
+
+#PanelUI-update-status,
+#PanelUI-help,
+#PanelUI-fxa-label,
+#PanelUI-fxa-icon,
+#PanelUI-customize,
+#PanelUI-quit {
+ margin: 0;
+ padding: 11px 0;
+ box-sizing: border-box;
+ min-height: 40px;
+ -moz-appearance: none;
+ box-shadow: none;
+ border: none;
+ border-radius: 0;
+ transition: background-color;
+ -moz-box-orient: horizontal;
+}
+
+#PanelUI-update-status {
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+#PanelUI-update-status {
+ border-bottom: 1px solid transparent;
+ margin-bottom: -1px;
+}
+
+#PanelUI-update-status > .toolbarbutton-text {
+ width: 0; /* Fancy cropping solution for flexbox. */
+}
+
+#PanelUI-help,
+#PanelUI-quit {
+ min-width: 46px;
+}
+
+#PanelUI-update-status > .toolbarbutton-text,
+#PanelUI-fxa-label > .toolbarbutton-text,
+#PanelUI-customize > .toolbarbutton-text {
+ margin: 0;
+ padding: 0 6px;
+ text-align: start;
+}
+
+#PanelUI-help > .toolbarbutton-text,
+#PanelUI-quit > .toolbarbutton-text,
+#PanelUI-fxa-avatar > .toolbarbutton-text {
+ display: none;
+}
+
+#PanelUI-update-status > .toolbarbutton-icon,
+#PanelUI-fxa-label > .toolbarbutton-icon,
+#PanelUI-fxa-icon > .toolbarbutton-icon,
+#PanelUI-customize > .toolbarbutton-icon,
+#PanelUI-help > .toolbarbutton-icon,
+#PanelUI-quit > .toolbarbutton-icon {
+ margin-inline-end: 0;
+}
+
+#PanelUI-fxa-icon {
+ padding-inline-start: 15px;
+ padding-inline-end: 15px;
+}
+
+#PanelUI-fxa-label,
+#PanelUI-customize {
+ flex: 1;
+ padding-inline-start: 15px;
+ border-inline-start-style: none;
+}
+
+#PanelUI-footer-fxa[fxaprofileimage="set"] > #PanelUI-fxa-status > #PanelUI-fxa-label,
+#PanelUI-footer-fxa[fxaprofileimage="enabled"]:not([fxastatus="error"]) > #PanelUI-fxa-status > #PanelUI-fxa-label {
+ padding-inline-start: 0px;
+}
+
+#PanelUI-update-status {
+ width: calc(@menuPanelWidth@ + 30px);
+ padding-inline-start: 15px;
+ border-inline-start-style: none;
+}
+
+#PanelUI-update-status {
+ list-style-image: url(chrome://branding/content/icon16.png);
+}
+
+#PanelUI-fxa-label,
+#PanelUI-fxa-icon {
+ list-style-image: url(chrome://browser/skin/sync-horizontalbar.png);
+}
+
+#PanelUI-remotetabs {
+ --panel-ui-sync-illustration-height: 157.5px;
+}
+
+.PanelUI-remotetabs-instruction-title,
+.PanelUI-remotetabs-instruction-label,
+#PanelUI-remotetabs-mobile-promo {
+ /* If you change the margin here, the min-height of the synced tabs panel
+ (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
+ need adjusting (see bug 1248506) */
+ margin: 15px;
+ text-align: center;
+ text-shadow: none;
+ max-width: 15em;
+ color: GrayText;
+}
+
+.PanelUI-remotetabs-instruction-title {
+ font-size: 1.3em;
+}
+
+/* The boxes with "instructions" get extra top and bottom padding for space
+ around the illustration and buttons */
+.PanelUI-remotetabs-instruction-box {
+ /* If you change the padding here, the min-height of the synced tabs panel
+ (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
+ need adjusting (see bug 1248506) */
+ padding-bottom: 30px;
+ padding-top: 15px;
+}
+
+.PanelUI-remotetabs-prefs-button {
+ -moz-appearance: none;
+ background-color: #0096dd;
+ /* !important for the color as an OSX specific rule when a lightweight theme
+ is used for buttons in the toolbox overrides. See bug 1238531 for details */
+ color: white !important;
+ border-radius: 2px;
+ /* If you change the margin or padding below, the min-height of the synced tabs
+ panel (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
+ etc) may need adjusting (see bug 1248506) */
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 8px;
+ text-shadow: none;
+ min-width: 200px;
+}
+
+.PanelUI-remotetabs-prefs-button:hover,
+.PanelUI-remotetabs-prefs-button:hover:active {
+ background-color: #018acb;
+}
+
+.remotetabs-promo-link {
+ margin: 0;
+}
+
+.PanelUI-remotetabs-notabsforclient-label {
+ color: GrayText;
+ /* This margin is to line this label up with the labels in toolbarbuttons. */
+ margin-left: 28px;
+}
+
+.fxaSyncIllustration {
+ height: var(--panel-ui-sync-illustration-height);
+ list-style-image: url(chrome://browser/skin/fxa/sync-illustration.svg);
+}
+
+.PanelUI-remotetabs-prefs-button > .toolbarbutton-text {
+ /* !important to override ".cui-widget-panel toolbarbutton > .toolbarbutton-text" above. */
+ text-align: center !important;
+ text-shadow: none;
+}
+
+#PanelUI-remotetabs[mainview] { /* panel anchored to toolbar button might be too skinny */
+ min-width: 19em;
+}
+
+/* Work around bug 1224412 - these boxes will cause scrollbars to appear when
+ the panel is anchored to a toolbar button.
+*/
+#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
+#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-reauthsync,
+#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-nodevicespane,
+#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-tabsdisabledpane {
+ min-height: calc(var(--panel-ui-sync-illustration-height) +
+ 20px + /* margin of .PanelUI-remotetabs-prefs-button */
+ 16px + /* padding of .PanelUI-remotetabs-prefs-button */
+ 30px + /* margin of .PanelUI-remotetabs-instruction-label */
+ 30px + 15px + /* padding of .PanelUI-remotetabs-instruction-box */
+ 11em);
+}
+
+#PanelUI-remotetabs-tabslist > label[itemtype="client"] {
+ color: GrayText;
+}
+
+/* Collapse the non-active vboxes in the remotetabs deck to use only the
+ height the active box needs */
+#PanelUI-remotetabs-deck:not([selectedIndex="1"]) > #PanelUI-remotetabs-tabsdisabledpane,
+#PanelUI-remotetabs-deck:not([selectedIndex="2"]) > #PanelUI-remotetabs-fetching,
+#PanelUI-remotetabs-deck:not([selectedIndex="3"]) > #PanelUI-remotetabs-nodevicespane {
+ visibility: collapse;
+}
+
+#PanelUI-remotetabs-main[devices-status="single"] > #PanelUI-remotetabs-buttons {
+ display: none;
+}
+
+#PanelUI-fxa-icon[syncstatus="active"]:not([disabled]) {
+ list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png);
+}
+
+#PanelUI-footer-fxa[fxastatus="migrate-signup"] > #PanelUI-fxa-status > #PanelUI-fxa-label,
+#PanelUI-footer-fxa[fxastatus="migrate-verify"] > #PanelUI-fxa-status > #PanelUI-fxa-label {
+ list-style-image: url(chrome://browser/skin/warning.svg);
+ -moz-image-region: auto;
+}
+
+#PanelUI-customize {
+ list-style-image: url(chrome://browser/skin/menuPanel-customize.png);
+}
+
+#customization-panelHolder #PanelUI-customize {
+ list-style-image: url(chrome://browser/skin/customizableui/menuPanel-customizeFinish.png);
+}
+
+#PanelUI-help {
+ list-style-image: url(chrome://browser/skin/menuPanel-help.png);
+}
+
+#PanelUI-quit {
+ border-inline-end-style: none;
+ list-style-image: url(chrome://browser/skin/menuPanel-exit.png);
+}
+
+#PanelUI-fxa-label,
+#PanelUI-fxa-icon,
+#PanelUI-customize,
+#PanelUI-help,
+#PanelUI-quit {
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#PanelUI-footer-fxa[fxastatus="signedin"] > #PanelUI-fxa-status > #PanelUI-fxa-label > .toolbarbutton-icon,
+#PanelUI-footer-fxa[fxastatus="error"][fxaprofileimage="set"] > #PanelUI-fxa-status > #PanelUI-fxa-label > .toolbarbutton-icon {
+ display: none;
+}
+
+#PanelUI-footer-fxa[fxastatus="error"]:not([fxaprofileimage="set"]) > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
+ display: none;
+}
+
+#PanelUI-fxa-status[disabled],
+#PanelUI-fxa-icon[disabled] {
+ pointer-events: none;
+}
+
+#PanelUI-fxa-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background-repeat: no-repeat;
+ background-position: 0 0;
+ background-size: contain;
+ align-self: center;
+ margin: 0px 7px;
+ padding: 0px;
+ border: 0px none;
+ margin-inline-end: 0;
+}
+
+#PanelUI-footer-fxa[fxaprofileimage="enabled"] > #PanelUI-fxa-status > #PanelUI-fxa-avatar {
+ list-style-image: url(chrome://browser/skin/fxa/default-avatar.svg);
+}
+
+#PanelUI-customize:hover,
+#PanelUI-help:not([disabled]):hover,
+#PanelUI-quit:not([disabled]):hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#PanelUI-customize:hover:active,
+#PanelUI-help:not([disabled]):hover:active,
+#PanelUI-quit:not([disabled]):hover:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#PanelUI-help[panel-multiview-anchor="true"] {
+ -moz-image-region: rect(0, 64px, 16px, 48px);
+}
+
+#PanelUI-help[disabled],
+#PanelUI-quit[disabled],
+#PanelUI-fxa-icon[disabled],
+#PanelUI-fxa-avatar[disabled],
+#PanelUI-fxa-label[disabled] > .toolbarbutton-icon,
+#PanelUI-fxa-status::after {
+ opacity: 0.4;
+}
+
+#PanelUI-fxa-status:not([disabled]):hover,
+#PanelUI-fxa-icon:not([disabled]):hover,
+#PanelUI-help:not([disabled]):hover,
+#PanelUI-customize:hover,
+#PanelUI-quit:not([disabled]):hover {
+ outline: 1px solid var(--arrowpanel-dimmed);
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#PanelUI-fxa-status:not([disabled]):hover:active,
+#PanelUI-fxa-icon:not([disabled]):hover:active,
+#PanelUI-help:not([disabled]):hover:active,
+#PanelUI-customize:hover:active,
+#PanelUI-quit:not([disabled]):hover:active {
+ outline: 1px solid var(--arrowpanel-dimmed-further);
+ background-color: var(--arrowpanel-dimmed-further);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
+#PanelUI-fxa-status:not([disabled]):hover,
+#PanelUI-fxa-status:not([disabled]):hover:active,
+#PanelUI-fxa-icon:not([disabled]):hover,
+#PanelUI-fxa-icon:not([disabled]):hover:active {
+ outline: none;
+}
+
+#PanelUI-footer-fxa[fxastatus="error"] {
+ background-color: hsl(42,94%,88%);
+ border-top: 1px solid hsl(42,94%,70%);
+}
+
+#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover {
+ background-color: hsl(42,94%,85%);
+}
+
+#PanelUI-footer-fxa[fxastatus="error"] > #PanelUI-fxa-status:hover:active {
+ background-color: hsl(42,94%,82%);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
+#PanelUI-update-status {
+ color: black;
+}
+
+#PanelUI-update-status[update-status="succeeded"] {
+ background-color: hsla(96,65%,75%,.5);
+}
+
+#PanelUI-update-status[update-status="succeeded"]:not([disabled]):hover {
+ background-color: hsla(96,65%,75%,.8);
+}
+
+#PanelUI-update-status[update-status="succeeded"]:not([disabled]):hover:active {
+ background-color: hsl(96,65%,75%);
+}
+
+#PanelUI-update-status[update-status="failed"] {
+ background-color: hsla(359,69%,84%,.5);
+}
+
+#PanelUI-update-status[update-status="failed"]:not([disabled]):hover {
+ background-color: hsla(359,69%,84%,.8);
+}
+
+#PanelUI-update-status[update-status="failed"]:not([disabled]):hover:active {
+ background-color: hsl(359,69%,84%);
+}
+
+#PanelUI-quit:not([disabled]):hover {
+ background-color: #d94141;
+ outline-color: #c23a3a;
+}
+
+#PanelUI-quit:not([disabled]):hover:active {
+ background-color: #ad3434;
+ outline-color: #992e2e;
+}
+
+#customization-panelHolder #PanelUI-customize {
+ color: white;
+ background-color: hsl(108,66%,30%);
+ text-shadow: none;
+ margin-top: -1px;
+}
+
+#customization-panelHolder #PanelUI-customize + toolbarseparator {
+ display: none;
+}
+
+#customization-panelHolder #PanelUI-customize:hover {
+ background-color: hsl(109,65%,26%);
+}
+
+#customization-panelHolder #PanelUI-customize:hover:active {
+ background-color: hsl(109,65%,22%);
+}
+
+#customization-palette .toolbarbutton-multiline-text,
+#customization-palette .toolbarbutton-text {
+ display: none;
+}
+
+panelview .toolbarbutton-1,
+.subviewbutton,
+.widget-overflow-list .toolbarbutton-1,
+.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+.share-provider-button,
+.toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
+ -moz-appearance: none;
+ padding: 0 6px;
+ background-color: transparent;
+ border-radius: 2px;
+ border-style: solid;
+ border-color: transparent;
+}
+
+panelview .toolbarbutton-1,
+.subviewbutton,
+.widget-overflow-list .toolbarbutton-1,
+.share-provider-button,
+.toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
+ border-width: 1px;
+}
+
+.subviewbutton.panel-subview-footer {
+ border-radius: 0;
+ border: none;
+}
+
+.subviewbutton.panel-subview-footer > .menu-text {
+ -moz-appearance: none;
+ margin-inline-start: 0px !important;
+ padding-inline-start: 6px;
+ padding-inline-end: 6px;
+ -moz-box-flex: 0;
+ text-align: center;
+}
+
+.subviewbutton.panel-subview-footer > .toolbarbutton-icon {
+ margin: 0;
+}
+
+.subviewbutton.panel-subview-footer > .toolbarbutton-text {
+ text-align: center;
+ padding: 0;
+}
+
+.subviewbutton.panel-subview-footer > .menu-accel-container {
+ padding-inline-start: 6px;
+}
+
+.subviewbutton:not(.panel-subview-footer) {
+ margin: 0;
+}
+
+.subviewbutton:not(.panel-subview-footer) > .toolbarbutton-text,
+/* Bookmark items need a more specific selector. */
+.PanelUI-subView .subviewbutton:not(.panel-subview-footer) > .menu-text,
+.PanelUI-subView .subviewbutton:not(.panel-subview-footer) > .menu-iconic-text {
+ font: menu;
+}
+
+.PanelUI-subView .subviewbutton[shortcut]::after {
+ content: attr(shortcut);
+ float: right;
+ color: GrayText;
+}
+
+.PanelUI-subView.cui-widget-panelview .subviewbutton[shortcut]::after {
+ margin-inline-start: 10px;
+}
+
+/* This is a <label> but it should fit in with the menu font- and colorwise. */
+#PanelUI-characterEncodingView-autodetect-label {
+ font: menu;
+ color: inherit;
+}
+
+.cui-widget-panelview .subviewbutton:not(.panel-subview-footer) {
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+panelview .toolbarbutton-1,
+.widget-overflow-list > .toolbarbutton-1:not(:first-child),
+.widget-overflow-list > toolbaritem:not(:first-child) {
+ margin-top: 6px;
+}
+
+panelview .toolbarbutton-1@buttonStateHover@,
+toolbarbutton.subviewbutton@buttonStateHover@,
+menu.subviewbutton@menuStateHover@,
+menuitem.subviewbutton@menuStateHover@,
+.share-provider-button@buttonStateHover@:not([checked="true"]),
+.widget-overflow-list .toolbarbutton-1@buttonStateHover@,
+.toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateHover@ {
+ background-color: var(--arrowpanel-dimmed);
+ border-color: var(--panel-separator-color);
+}
+
+.toolbaritem-combined-buttons@inAnyPanel@@buttonStateHover@ {
+ border-color: var(--panel-separator-color);
+}
+
+panelview .toolbarbutton-1:-moz-any(@buttonStateActive@,[checked=true]),
+toolbarbutton.subviewbutton@buttonStateActive@,
+menu.subviewbutton@menuStateActive@,
+menuitem.subviewbutton@menuStateActive@,
+.share-provider-button:-moz-any(@buttonStateActive@,[checked=true]),
+.widget-overflow-list .toolbarbutton-1@buttonStateActive@,
+.toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateActive@ {
+ background-color: var(--arrowpanel-dimmed-further);
+ border-color: var(--panel-separator-color);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
+}
+
+.subviewbutton.panel-subview-footer {
+ margin: 4px -4px -4px;
+ background-color: var(--arrowpanel-dimmed);
+ border-top: 1px solid var(--panel-separator-color);
+ border-radius: 0;
+}
+
+menuitem.panel-subview-footer@menuStateHover@,
+.subviewbutton.panel-subview-footer@buttonStateHover@ {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+menuitem.panel-subview-footer@menuStateActive@,
+.subviewbutton.panel-subview-footer@buttonStateActive@ {
+ background-color: var(--arrowpanel-dimmed-even-further);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
+#BMB_bookmarksPopup .subviewbutton {
+ font: menu;
+ font-weight: normal;
+}
+
+#BMB_bookmarksPopup .subviewbutton:not([disabled="true"]) {
+ color: inherit;
+}
+
+#BMB_bookmarksPopup .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box > .autorepeatbutton-up,
+#BMB_bookmarksPopup .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box > .autorepeatbutton-down {
+ -moz-appearance: none;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+/* Remove padding on xul:arrowscrollbox to avoid extra padding on footer */
+#BMB_bookmarksPopup arrowscrollbox {
+ padding-bottom: 0px;
+}
+
+#BMB_bookmarksPopup menupopup > .bookmarks-actions-menuseparator {
+ /* Hide bottom separator as the styled footer includes a top border serving the same purpose */
+ display: none;
+}
+
+/* Popups with only one item don't have a footer */
+#BMB_bookmarksPopup menupopup[placespopup=true][singleitempopup=true] > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox,
+/* These popups never have a footer */
+#BMB_bookmarksToolbarPopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox,
+#BMB_unsortedBookmarksPopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox {
+ /* And so they need some bottom padding: */
+ padding-bottom: 4px;
+}
+
+/* Disabled (empty) item is always alone and never has an icon, so fix its left padding */
+#BMB_bookmarksPopup menupopup[emptyplacesresult] .bookmark-item.subviewbutton {
+ padding-left: 6px;
+}
+
+.PanelUI-subView menuseparator,
+.PanelUI-subView toolbarseparator,
+.cui-widget-panelview menuseparator {
+ -moz-appearance: none;
+ min-height: 0;
+ border-top: 1px solid var(--panel-separator-color);
+ border-bottom: none;
+ margin: 6px 0;
+ padding: 0;
+}
+
+.PanelUI-subView menuseparator,
+.PanelUI-subView toolbarseparator {
+ margin-inline-start: -5px;
+ margin-inline-end: -4px;
+}
+
+.PanelUI-subView menuseparator.small-separator,
+.PanelUI-subView toolbarseparator.small-separator {
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+.cui-widget-panelview menuseparator.small-separator {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.subviewbutton > .menu-accel-container {
+ -moz-box-pack: start;
+ margin-inline-start: 10px;
+ margin-inline-end: auto;
+ color: GrayText;
+}
+
+#PanelUI-remotetabs-tabslist > toolbarbutton,
+#PanelUI-historyItems > toolbarbutton {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ #PanelUI-remotetabs-tabslist > toolbarbutton,
+ #PanelUI-historyItems > toolbarbutton {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+}
+
+#PanelUI-remotetabs-tabslist > toolbarbutton > .toolbarbutton-icon,
+#PanelUI-recentlyClosedWindows > toolbarbutton > .toolbarbutton-icon,
+#PanelUI-recentlyClosedTabs > toolbarbutton > .toolbarbutton-icon,
+#PanelUI-historyItems > toolbarbutton > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+toolbarbutton[panel-multiview-anchor="true"],
+toolbarbutton[panel-multiview-anchor="true"] > .toolbarbutton-menubutton-button {
+ color: HighlightText;
+ background-color: Highlight;
+}
+
+#PanelUI-help[panel-multiview-anchor="true"] + toolbarseparator {
+ display: none;
+}
+
+#PanelUI-help[panel-multiview-anchor="true"] {
+ background-image: linear-gradient(rgba(255,255,255,0.3), transparent);
+ background-position: 0;
+}
+
+#PanelUI-help[panel-multiview-anchor="true"]::after {
+ content: "";
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: var(--panel-ui-exit-subview-gutter-width);
+ background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted.png),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+ background-repeat: no-repeat;
+ background-color: Highlight;
+ background-position: left 10px center, 0;
+}
+
+#PanelUI-help[panel-multiview-anchor="true"]:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl.png),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+ background-position: right 10px center, 0;
+}
+
+toolbarbutton[panel-multiview-anchor="true"] {
+ background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted.png),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+ background-position: right calc(@menuPanelButtonWidth@ / 2 - var(--panel-ui-exit-subview-gutter-width) + 2px) center;
+ background-repeat: no-repeat, repeat;
+}
+
+toolbarbutton[panel-multiview-anchor="true"]:-moz-locale-dir(rtl) {
+ background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl.png),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+ background-position: left calc(@menuPanelButtonWidth@ / 2 - var(--panel-ui-exit-subview-gutter-width) + 2px) center;
+}
+
+toolbarpaletteitem[place="palette"] > .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker,
+#bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menubutton-dropmarker {
+ display: none;
+}
+
+#search-container[cui-areatype="menu-panel"],
+#wrapper-search-container[place="panel"] {
+ width: @menuPanelWidth@;
+}
+
+#search-container[cui-areatype="menu-panel"] {
+ margin-top: 6px;
+ margin-bottom: 6px;
+}
+
+toolbarpaletteitem[place="palette"] > #search-container {
+ min-width: 7em;
+ width: 7em;
+}
+
+.toolbaritem-combined-buttons@inAnyPanel@ {
+ background-color: transparent;
+ border-radius: 2px;
+ border: 1px solid;
+ border-color: transparent;
+ border-bottom-color: var(--panel-separator-color);
+ padding: 0;
+ transition-property: background-color, border-color;
+ transition-duration: 150ms;
+}
+
+/* Make direct siblings overlap borders: */
+.toolbaritem-combined-buttons + .toolbaritem-combined-buttons@inAnyPanel@ {
+ border-top-color: transparent !important;
+}
+
+.toolbaritem-combined-buttons + .toolbaritem-combined-buttons@inAnyPanel@,
+toolbarpaletteitem[haswideitem][place="panel"] + toolbarpaletteitem[haswideitem][place="panel"] {
+ margin-top: -1px;
+}
+
+.toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
+ border: 0;
+ padding: .5em;
+ margin: 0;
+ -moz-box-flex: 1;
+ min-width: calc(@menuPanelButtonWidth@);
+ max-width: calc(@menuPanelButtonWidth@);
+ /* We'd prefer to use height: auto here but it leads to layout bugs in the panel. Cope:
+ 1.2em for line height + 2 * .5em padding + margin on the label (2 * 2px) */
+ height: calc(2.2em + 4px);
+ max-height: none;
+ -moz-box-orient: horizontal;
+}
+
+#edit-controls@inAnyPanel@ > #copy-button,
+#zoom-controls@inAnyPanel@ > #zoom-reset-button {
+ /* reduce the width with 2px for this button to compensate for two separators
+ of 1px. */
+ min-width: calc(@menuPanelButtonWidth@ - 2px);
+ max-width: calc(@menuPanelButtonWidth@ - 2px);
+}
+
+#main-window:not([customizing]) .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton[disabled] > .toolbarbutton-icon {
+ opacity: .25;
+}
+
+#zoom-controls[cui-areatype="toolbar"] > #zoom-reset-button > .toolbarbutton-text {
+%ifdef XP_MACOSX
+ min-width: 6ch;
+%else
+ min-width: 7ch;
+%endif
+}
+
+#edit-controls@inAnyPanel@ > #cut-button:-moz-locale-dir(ltr),
+#edit-controls@inAnyPanel@ > #paste-button:-moz-locale-dir(rtl),
+#zoom-controls@inAnyPanel@ > #zoom-out-button:-moz-locale-dir(ltr),
+#zoom-controls@inAnyPanel@ > #zoom-in-button:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+#edit-controls@inAnyPanel@ > #cut-button:-moz-locale-dir(rtl),
+#edit-controls@inAnyPanel@ > #paste-button:-moz-locale-dir(ltr),
+#zoom-controls@inAnyPanel@ > #zoom-out-button:-moz-locale-dir(rtl),
+#zoom-controls@inAnyPanel@ > #zoom-in-button:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.toolbaritem-combined-buttons@inAnyPanel@ > separator {
+ -moz-appearance: none;
+ -moz-box-align: stretch;
+ margin: .5em 0;
+ width: 1px;
+ height: auto;
+ background: var(--panel-separator-color);
+ transition-property: margin;
+ transition-duration: 10ms;
+ transition-timing-function: ease;
+}
+
+.toolbaritem-combined-buttons@inAnyPanel@:hover > separator {
+ margin: 0;
+}
+
+#widget-overflow > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+.cui-widget-panelview,
+#widget-overflow-scroller {
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#widget-overflow-scroller {
+ max-height: 30em;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+#widget-overflow-list {
+ width: @menuPanelWidth@;
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+toolbaritem[overflowedItem=true],
+.widget-overflow-list .toolbarbutton-1 {
+ width: 100%;
+ max-width: @menuPanelWidth@;
+ min-height: 36px;
+ background-repeat: no-repeat;
+ background-position: 0 center;
+}
+
+.widget-overflow-list .toolbarbutton-1,
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-box-align: center;
+ -moz-box-orient: horizontal;
+}
+
+.widget-overflow-list .toolbarbutton-1:not(.toolbarbutton-combined) > .toolbarbutton-text,
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-text {
+ text-align: start;
+ padding-inline-start: .5em;
+}
+
+#widget-overflow-list > .toolbaritem-combined-buttons {
+ min-height: 28px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button::after {
+ content: "";
+ display: -moz-box;
+ width: 1px;
+ height: 18px;
+ margin-inline-end: -1px;
+ background-image: linear-gradient(hsla(210,54%,20%,.2) 0, hsla(210,54%,20%,.2) 18px);
+ background-clip: padding-box;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 1px 18px;
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
+}
+
+.subviewbutton[checked="true"] {
+ background: url("chrome://global/skin/menu/shared-menu-check.png") center left 7px / 11px 11px no-repeat transparent;
+}
+
+.subviewbutton[checked="true"]:-moz-locale-dir(rtl) {
+ background-position: center right 7px;
+}
+
+.subviewbutton > .menu-iconic-left {
+ -moz-appearance: none;
+ margin-inline-end: 3px;
+}
+
+menuitem[checked="true"].subviewbutton > .menu-iconic-left {
+ visibility: hidden;
+}
+
+#PanelUI-containersItems > .subviewbutton > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.panel-mainview[panelid=customizationui-widget-panel],
+#customizationui-widget-multiview > .panel-viewcontainer,
+#customizationui-widget-multiview > .panel-viewcontainer > .panel-viewstack,
+#PanelUI-panicView > .panel-subview-body,
+#PanelUI-panicView {
+ overflow: visible;
+}
+
+#PanelUI-panicView.cui-widget-panelview {
+ min-width: 280px;
+}
+
+#PanelUI-panic-timeframe {
+ padding: 15px;
+ border-bottom: 1px solid var(--panel-separator-color);
+}
+
+#panic-button-success-icon,
+#PanelUI-panic-timeframe-icon,
+#PanelUI-panic-timeframe-icon-small {
+ background-color: transparent;
+ margin-inline-end: 10px;
+}
+
+#panic-button-success-icon,
+#PanelUI-panic-timeframe-icon {
+ list-style-image: url(chrome://browser/skin/panic-panel/header.png);
+ max-height: 48px;
+ width: 48px;
+}
+
+#PanelUI-panic-timeframe-icon-small {
+ list-style-image: url(chrome://browser/skin/panic-panel/header-small.png);
+ max-height: 32px;
+ width: 32px;
+}
+
+/* current attribute is only set when in use as a subview instead of a main view */
+#PanelUI-panicView[current] #PanelUI-panic-timeframe-icon {
+ display: none;
+}
+
+#PanelUI-panicView.cui-widget-panelview #PanelUI-panic-timeframe-icon-small {
+ display: none;
+}
+
+#panic-button-success-header,
+#PanelUI-panic-header {
+ -moz-box-align: center;
+ margin-bottom: 5px;
+}
+
+#PanelUI-panicView.cui-widget-panelview #PanelUI-panic-header {
+ margin-bottom: 0;
+}
+
+#PanelUI-panic-timeframe-icon-small:-moz-locale-dir(rtl),
+#PanelUI-panic-timeframe-icon:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.subviewradio {
+ -moz-binding: url(chrome://global/content/bindings/radio.xml#radio);
+ -moz-appearance: none;
+ -moz-box-align: center;
+ padding: 1px;
+ margin: 0 0 2px;
+ background-color: transparent;
+ border-radius: 2px;
+ border: 1px solid transparent;
+}
+
+.subviewradio@buttonStateHover@ {
+ background-color: var(--arrowpanel-dimmed);
+ border-color: var(--panel-separator-color);
+}
+
+.subviewradio[selected],
+.subviewradio[selected]:hover,
+.subviewradio@buttonStateActive@ {
+ background-color: var(--arrowpanel-dimmed-further);
+ border-color: var(--panel-separator-color);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
+}
+
+.subviewradio > .radio-check {
+ -moz-appearance: none;
+ width: 16px;
+ height: 16px;
+ border: 1px solid #e7e7e7;
+ border-radius: 50%;
+ margin: 1px 5px;
+ background-color: #f1f1f1;
+}
+
+.subviewradio > .radio-check[selected] {
+ background-color: #fff;
+ border: 4px solid #177ee6;
+}
+
+#PanelUI-panic-explanations {
+ padding: 10px 10px 0;
+}
+
+#PanelUI-panic-actionlist-main-label {
+ color: GrayText;
+ font-size: 0.9em;
+}
+
+.PanelUI-panic-actionlist {
+ padding-inline-start: 20px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ background-size: 16px 16px;
+ background-repeat: no-repeat;
+ background-color: transparent;
+ background-position: center left;
+}
+
+.PanelUI-panic-actionlist:-moz-locale-dir(rtl) {
+ background-position: center right;
+}
+
+#PanelUI-panic-actionlist-cookies {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 16, 16, 0);
+}
+
+#PanelUI-panic-actionlist-history {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 32, 16, 16);
+}
+
+#PanelUI-panic-actionlist-windows {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 48, 16, 32);
+}
+
+#PanelUI-panic-actionlist-newwindow {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons.png), 0, 64, 16, 48);
+}
+
+#PanelUI-panic-warning {
+ color: #C11F14;
+ text-align: center;
+ width: 100%;
+ margin-top: 20px;
+}
+
+#PanelUI-panic-view-button {
+ -moz-appearance: none;
+ background-color: #d92316;
+ color: white;
+ margin: 5px 15px 11px;
+ border: 1px solid #c92014;
+ border-radius: 3px;
+ padding: 10px;
+}
+
+#PanelUI-panic-view-button:hover {
+ background-color: #bf1f13;
+ border-color: #b81d12;
+}
+
+#PanelUI-panic-view-button:hover:active {
+ background-color: #99180f;
+ border-color: #91170f;
+}
+
+#PanelUI-panic-view-button > .toolbarbutton-text {
+ text-align: center;
+ text-shadow: none;
+}
+
+#panic-button-success-closebutton {
+ background-color: #e5e5e5;
+ color: black;
+ margin: 5px 0 0;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ padding: 10px;
+ -moz-appearance: none;
+}
+
+#panic-button-success-closebutton:hover {
+ background-color: #dedede;
+ border-color: #bbb;
+}
+
+#panic-button-success-closebutton:hover:active {
+ background-color: #d0d0d0;
+ border-color: #aaa;
+}
+
+@media (min-resolution: 1.1dppx) {
+ #PanelUI-help[panel-multiview-anchor="true"]::after,
+ toolbarbutton[panel-multiview-anchor="true"] {
+ background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted@2x.png),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+ background-size: 16px, auto;
+ }
+
+ #PanelUI-help[panel-multiview-anchor="true"]:-moz-locale-dir(rtl)::after,
+ toolbarbutton[panel-multiview-anchor="true"]:-moz-locale-dir(rtl) {
+ background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl@2x.png),
+ linear-gradient(rgba(255,255,255,0.3), transparent);
+ }
+
+ #PanelUI-update-status {
+ list-style-image: url(chrome://branding/content/icon32.png);
+ }
+
+ #PanelUI-fxa-label,
+ #PanelUI-fxa-icon {
+ list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png);
+ }
+
+ #PanelUI-fxa-icon[syncstatus="active"]:not([disabled]) {
+ list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png);
+ }
+
+ #PanelUI-customize {
+ list-style-image: url(chrome://browser/skin/menuPanel-customize@2x.png);
+ }
+
+ #customization-panelHolder #PanelUI-customize {
+ list-style-image: url(chrome://browser/skin/customizableui/menuPanel-customizeFinish@2x.png);
+ }
+
+ #PanelUI-help {
+ list-style-image: url(chrome://browser/skin/menuPanel-help@2x.png);
+ }
+
+ #PanelUI-quit {
+ list-style-image: url(chrome://browser/skin/menuPanel-exit@2x.png);
+ }
+
+ #PanelUI-fxa-label,
+ #PanelUI-fxa-icon,
+ #PanelUI-customize,
+ #PanelUI-help,
+ #PanelUI-quit {
+ -moz-image-region: rect(0, 32px, 32px, 0);
+ }
+
+ #PanelUI-update-status > .toolbarbutton-icon,
+ #PanelUI-fxa-label > .toolbarbutton-icon,
+ #PanelUI-fxa-icon > .toolbarbutton-icon,
+ #PanelUI-customize > .toolbarbutton-icon,
+ #PanelUI-help > .toolbarbutton-icon,
+ #PanelUI-quit > .toolbarbutton-icon {
+ width: 16px;
+ }
+
+ #PanelUI-customize:hover,
+ #PanelUI-help:not([disabled]):hover,
+ #PanelUI-quit:not([disabled]):hover {
+ -moz-image-region: rect(0, 64px, 32px, 32px);
+ }
+
+ #PanelUI-customize:hover:active,
+ #PanelUI-help:not([disabled]):hover:active,
+ #PanelUI-quit:not([disabled]):hover:active {
+ -moz-image-region: rect(0, 96px, 32px, 64px);
+ }
+
+ #PanelUI-help[panel-multiview-anchor="true"] {
+ -moz-image-region: rect(0, 128px, 32px, 96px);
+ background-size: auto;
+ }
+
+ .subviewbutton[checked="true"] {
+ background-image: url("chrome://global/skin/menu/shared-menu-check@2x.png");
+ }
+
+ #panic-button-success-icon,
+ #PanelUI-panic-timeframe-icon {
+ list-style-image: url(chrome://browser/skin/panic-panel/header@2x.png);
+ }
+
+ #PanelUI-panic-timeframe-icon-small {
+ list-style-image: url(chrome://browser/skin/panic-panel/header-small@2x.png);
+ }
+
+ #PanelUI-panic-actionlist-cookies {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 32, 32, 0);
+ }
+
+ #PanelUI-panic-actionlist-history {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 64, 32, 32);
+ }
+
+ #PanelUI-panic-actionlist-windows {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 96, 32, 64);
+ }
+
+ #PanelUI-panic-actionlist-newwindow {
+ background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 128, 32, 96);
+ }
+}
diff --git a/browser/themes/shared/customizableui/panelarrow-customizeTip.png b/browser/themes/shared/customizableui/panelarrow-customizeTip.png
new file mode 100644
index 000000000..cbdf4692b
--- /dev/null
+++ b/browser/themes/shared/customizableui/panelarrow-customizeTip.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/panelarrow-customizeTip@2x.png b/browser/themes/shared/customizableui/panelarrow-customizeTip@2x.png
new file mode 100644
index 000000000..cfd7ba966
--- /dev/null
+++ b/browser/themes/shared/customizableui/panelarrow-customizeTip@2x.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl.png b/browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl.png
new file mode 100644
index 000000000..cf4198c60
--- /dev/null
+++ b/browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl@2x.png b/browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl@2x.png
new file mode 100644
index 000000000..bb250403d
--- /dev/null
+++ b/browser/themes/shared/customizableui/subView-arrow-back-inverted-rtl@2x.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/subView-arrow-back-inverted.png b/browser/themes/shared/customizableui/subView-arrow-back-inverted.png
new file mode 100644
index 000000000..86178abfe
--- /dev/null
+++ b/browser/themes/shared/customizableui/subView-arrow-back-inverted.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/subView-arrow-back-inverted@2x.png b/browser/themes/shared/customizableui/subView-arrow-back-inverted@2x.png
new file mode 100644
index 000000000..1c8e86db3
--- /dev/null
+++ b/browser/themes/shared/customizableui/subView-arrow-back-inverted@2x.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/whimsy.png b/browser/themes/shared/customizableui/whimsy.png
new file mode 100644
index 000000000..2e9fdd7eb
--- /dev/null
+++ b/browser/themes/shared/customizableui/whimsy.png
Binary files differ
diff --git a/browser/themes/shared/customizableui/whimsy@2x.png b/browser/themes/shared/customizableui/whimsy@2x.png
new file mode 100644
index 000000000..61f55f60f
--- /dev/null
+++ b/browser/themes/shared/customizableui/whimsy@2x.png
Binary files differ
diff --git a/browser/themes/shared/devedition.inc.css b/browser/themes/shared/devedition.inc.css
new file mode 100644
index 000000000..a5c0db948
--- /dev/null
+++ b/browser/themes/shared/devedition.inc.css
@@ -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/.
+
+/* devedition.css is loaded in browser.xul after browser.css when it is
+ preffed on. The bulk of the styling is here in the shared file, but
+ there are overrides for each platform in their devedition.css files. */
+
+:root {
+ --tab-toolbar-navbar-overlap: 0px;
+ --navbar-tab-toolbar-highlight-overlap: 0px;
+ --space-above-tabbar: 0px;
+ --toolbarbutton-text-shadow: none;
+ --backbutton-urlbar-overlap: 0px;
+}
+
+:root[devtoolstheme="dark"] {
+ /* Chrome */
+ --chrome-background-color: #272b35;
+ --chrome-color: #F5F7FA;
+ --chrome-secondary-background-color: #393F4C;
+ --chrome-navigator-toolbox-separator-color: rgba(0,0,0,.2);
+ --chrome-nav-bar-separator-color: rgba(0,0,0,.2);
+ --chrome-nav-buttons-background: #252C33;
+ --chrome-nav-buttons-hover-background: #1B2127;
+ --chrome-nav-bar-controls-border-color: #1D2328;
+ --chrome-selection-color: #fff;
+ --chrome-selection-background-color: #5675B9;
+
+ /* Tabs */
+ --tabs-toolbar-color: #F5F7FA;
+ --tab-background-color: #272b35;
+ --tab-hover-background-color: #07090a;
+ --tab-selection-color: #f5f7fa;
+ --tab-selection-background-color: #5675B9;
+ --tab-selection-box-shadow: none;
+ --pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, transparent 70%);
+
+ /* Url and search bars */
+ --url-and-searchbar-background-color: #171B1F;
+ --urlbar-separator-color: #5F6670;
+ --urlbar-dropmarker-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
+ --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
+ --urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
+ --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
+ --urlbar-dropmarker-2x-url: url("chrome://browser/skin/devedition/urlbar-history-dropmarker.svg");
+ --urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
+ --urlbar-dropmarker-hover-2x-region: rect(0, 22px, 14px, 11px);
+ --urlbar-dropmarker-active-2x-region: rect(0px, 33px, 14px, 22px);
+}
+
+/* Override the lwtheme-specific styling for toolbar buttons */
+:root[devtoolstheme="dark"],
+:root[devtoolstheme="dark"] toolbar:-moz-lwtheme {
+ --toolbarbutton-hover-background: rgba(25,33, 38,.6) linear-gradient(rgba(25,33,38,.6), rgba(25,33,38,.6)) padding-box;
+ --toolbarbutton-hover-boxshadow: none;
+ --toolbarbutton-hover-bordercolor: rgba(25,33,38,.6);
+ --toolbarbutton-active-background: rgba(25,33,38,1) linear-gradient(rgba(25,33,38,1), rgba(25,33,38,1)) border-box;
+ --toolbarbutton-active-boxshadow: none;
+ --toolbarbutton-active-bordercolor: rgba(25,33,38,.8);
+ --toolbarbutton-checkedhover-backgroundcolor: #3C5283;
+
+}
+
+:root[devtoolstheme="light"] {
+ --url-and-searchbar-background-color: #fff;
+
+ --chrome-background-color: #E3E4E6;
+ --chrome-color: #18191a;
+ --chrome-secondary-background-color: #f5f6f7;
+ --chrome-navigator-toolbox-separator-color: #cccccc;
+ --chrome-nav-bar-separator-color: #B6B6B8;
+ --chrome-nav-buttons-background: #ffffff; /* --theme-body-background */
+ --chrome-nav-buttons-hover-background: #DADBDB;
+ --chrome-nav-bar-controls-border-color: #ccc;
+ --chrome-selection-color: #f5f7fa;
+ --chrome-selection-background-color: #4c9ed9;
+
+ --tab-background-color: #E3E4E6;
+ --tab-hover-background-color: #D7D8DA;
+ --tab-selection-color: #f5f7fa;
+ --tab-selection-background-color: #4c9ed9;
+ --tab-selection-box-shadow: none;
+ --pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, transparent 16%);
+}
+
+/* Override the lwtheme-specific styling for toolbar buttons */
+:root[devtoolstheme="light"],
+:root[devtoolstheme="light"] toolbar:-moz-lwtheme {
+ --toolbarbutton-hover-background: #eaeaea;
+ --toolbarbutton-hover-boxshadow: none;
+ --toolbarbutton-hover-bordercolor: rgba(0,0,0,0.1);
+ --toolbarbutton-active-background: #d7d7d8 border-box;
+ --toolbarbutton-active-boxshadow: none;
+ --toolbarbutton-active-bordercolor: rgba(0,0,0,0.15);
+ --toolbarbutton-checkedhover-backgroundcolor: #d7d7d8;
+}
+
+/* Give some space to drag the window around while customizing
+ (normal space to left and right of tabs doesn't work in this case) */
+#main-window[tabsintitlebar][customizing] {
+ --space-above-tabbar: 9px;
+}
+
+/* Override @tabCurveHalfWidth@ and @tabCurveWidth@. XXX: Switch to a CSS variable once the perf is sorted out - bug 1088771 */
+.tab-background-middle {
+ border-left-width: 0;
+ border-right-width: 0;
+ margin: 0;
+}
+
+.tab-background,
+.tabs-newtab-button {
+ margin-inline-end: 0;
+ margin-inline-start: 0;
+}
+
+.tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
+ padding-inline-end: 0;
+ padding-inline-start: 0;
+}
+
+.tab-background-start[selected=true]::after,
+.tab-background-start[selected=true]::before,
+.tab-background-start,
+.tab-background-end,
+.tab-background-end[selected=true]::after,
+.tab-background-end[selected=true]::before {
+ width: 0;
+}
+
+.tab-background-start[selected=true]::after,
+.tab-background-end[selected=true]::after {
+ margin-inline-start: 0;
+}
+/* End override @tabCurveHalfWidth@ and @tabCurveWidth@ */
+
+#urlbar ::-moz-selection,
+#navigator-toolbox .searchbar-textbox ::-moz-selection,
+.browserContainer > findbar ::-moz-selection {
+ background-color: var(--chrome-selection-background-color);
+ color: var(--chrome-selection-color);
+}
+
+/* Change the base colors for the browser chrome */
+
+#tabbrowser-tabs,
+#TabsToolbar,
+#browser-panel {
+ background: var(--chrome-background-color);
+ color: var(--chrome-color);
+}
+
+#navigator-toolbox:-moz-lwtheme::after {
+ border-bottom-color: var(--chrome-navigator-toolbox-separator-color);
+}
+
+#navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar),
+.browserContainer > findbar,
+#browser-bottombox {
+ background-color: var(--chrome-secondary-background-color) !important;
+ background-image: none !important;
+ color: var(--chrome-color);
+}
+
+/* Default findbar text color doesn't look good - Bug 1125677 */
+.browserContainer > findbar .findbar-find-status,
+.browserContainer > findbar .found-matches {
+ color: inherit;
+}
+
+#navigator-toolbox .toolbarbutton-1,
+.browserContainer > findbar .findbar-button,
+#PlacesToolbar toolbarbutton.bookmark-item {
+ color: var(--chrome-color);
+ text-shadow: var(--toolbarbutton-text-shadow);
+}
+
+/* Using toolbar[brighttext] instead of important to override linux */
+toolbar[brighttext] #downloads-indicator-counter {
+ text-shadow: var(--toolbarbutton-text-shadow);
+ color: var(--chrome-color);
+}
+
+#TabsToolbar {
+ text-shadow: none !important;
+}
+
+/* URL bar and search bar*/
+#urlbar,
+#navigator-toolbox .searchbar-textbox {
+ background-color: var(--url-and-searchbar-background-color) !important;
+ background-image: none !important;
+ color: inherit !important;
+ border: 1px solid var(--chrome-nav-bar-controls-border-color) !important;
+ box-shadow: none !important;
+}
+
+%filter substitution
+%define selectorPrefix :root[devtoolstheme="dark"]
+%define selectorSuffix :-moz-lwtheme
+%define iconVariant -white
+%include identity-block/icons.inc.css
+
+#urlbar {
+ border-inline-start: none !important;
+ opacity: 1 !important;
+}
+
+window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
+ overflow: -moz-hidden-unscrollable;
+ clip-path: none;
+ margin-inline-start: 0;
+}
+
+:root[devtoolstheme="dark"] #urlbar-zoom-button:hover {
+ background-color: rgba(255,255,255,.2);
+}
+
+:root[devtoolstheme="dark"] #urlbar-zoom-button:hover:active {
+ background-color: rgba(255,255,255,.3);
+}
+
+/* Nav bar specific stuff */
+#nav-bar {
+ margin-top: 0 !important;
+ border-top: none !important;
+ border-bottom: none !important;
+ border-radius: 0 !important;
+ box-shadow: 0 -1px var(--chrome-nav-bar-separator-color) !important;
+}
+
+/* No extra vertical padding for nav bar */
+#nav-bar-customization-target,
+#nav-bar {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+/* Use smaller back button icon */
+#back-button {
+ -moz-image-region: rect(0, 54px, 18px, 36px);
+}
+
+@media (min-resolution: 1.1dppx) {
+ #back-button {
+ -moz-image-region: rect(0, 108px, 36px, 72px);
+ }
+}
+
+.tab-background {
+ visibility: hidden;
+}
+
+/* Tab separators */
+.tabbrowser-tab::after,
+.tabbrowser-tab::before {
+ background: currentColor;
+ opacity: 0.2 !important;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down,
+.tabbrowser-arrowscrollbox > .scrollbutton-up {
+ background-color: var(--tab-background-color);
+ border-color: transparent;
+}
+
+.tabbrowser-tab {
+ /* We normally rely on other tab elements for pointer events, but this
+ theme hides those so we need it set here instead */
+ pointer-events: auto;
+}
+
+.tabbrowser-tab:-moz-any([image], [pinned]) > .tab-stack > .tab-content[attention]:not([selected="true"]),
+.tabbrowser-tab > .tab-stack > .tab-content[pinned][titlechanged]:not([selected="true"]) {
+ background-image: var(--pinned-tab-glow);
+ background-position: center;
+ background-size: 100%;
+}
+
+.tabbrowser-tab[image] > .tab-stack > .tab-content[attention]:not([pinned]):not([selected="true"]) {
+ background-position: left bottom var(--tab-toolbar-navbar-overlap);
+ background-size: 34px 100%;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
+.tabbrowser-tab:hover {
+ background-color: var(--tab-hover-background-color);
+}
+
+.tabbrowser-tab[visuallyselected] {
+ color: var(--tab-selection-color) !important; /* Override color: inherit */
+ background-color: var(--tab-selection-background-color);
+}
+
+.tab-icon-sound[soundplaying],
+.tab-icon-sound[muted] {
+ filter: url(chrome://browser/skin/filters.svg#fill) !important; /* removes drop-shadow filter */
+}
+
+/* Don't need space for the tab curves (66px - 30px) */
+.tabs-newtab-button {
+ width: 36px;
+}
+
+.tabs-newtab-button:hover {
+ /* Important needed because !important is used in browser.css */
+ background-color: var(--tab-hover-background-color) !important;
+ background-image: none;
+}
diff --git a/browser/themes/shared/devedition/urlbar-history-dropmarker.svg b/browser/themes/shared/devedition/urlbar-history-dropmarker.svg
new file mode 100644
index 000000000..115fbf127
--- /dev/null
+++ b/browser/themes/shared/devedition/urlbar-history-dropmarker.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="33" height="14" viewBox="0 0 33 14">
+ <defs>
+ <polygon points="0,0 5.5,7 11,0" id="dropmarker-shape"/>
+ </defs>
+ <style>
+ use {
+ fill: #b6babf;
+ }
+ .hover {
+ fill: #61bdeb;
+ }
+ .active {
+ fill: #39ace6;
+ }
+ </style>
+ <use xlink:href="#dropmarker-shape" style="transform: translate(0, 4px)"/>
+ <use xlink:href="#dropmarker-shape" style="transform: translate(11px, 4px)" class="hover"/>
+ <use xlink:href="#dropmarker-shape" style="transform: translate(22px, 4px)" class="active"/>
+</svg>
diff --git a/browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css b/browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css
new file mode 100644
index 000000000..bdb8b7df8
--- /dev/null
+++ b/browser/themes/shared/downloads/allDownloadsViewOverlay.inc.css
@@ -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/. */
+
+%filter substitution
+
+%define item richlistitem.download
+%define itemFocused @item@[selected]
+
+/*** View and outer controls ***/
+
+#downloadsRichListBox {
+ /** The default listbox appearance comes with an unwanted margin. **/
+ -moz-appearance: none;
+ margin: 0;
+}
+
+/*** List items ***/
+
+#downloadsRichListBox > richlistitem.download {
+ height: var(--downloads-item-height);
+}
+
+.downloadTypeIcon {
+ margin: 8px 13px;
+ width: 32px;
+ height: 32px;
+}
+
+.downloadBlockedBadge {
+ margin: 0 5px;
+ background: url("chrome://browser/skin/downloads/download-blocked.svg") top right / 16px no-repeat;
+}
+
+.downloadBlockedBadge:-moz-locale-dir(rtl) {
+ background-position-x: left;
+}
+
+@item@[verdict="PotentiallyUnwanted"] .downloadBlockedBadge {
+ background-image: url("chrome://browser/skin/warning.svg");
+}
+
+@item@[verdict="Uncommon"] .downloadBlockedBadge {
+ background-image: url("chrome://browser/skin/info.svg");
+}
+
+@item@ > toolbarseparator {
+ display: none;
+}
+
+.downloadTarget {
+ margin: 0;
+}
+
+.downloadDetails {
+ opacity: 0.7;
+ font-size: 95%;
+ /* Use calc() to keep the height consistent with .downloadTarget, so that the
+ progress bar can be vertically centered. */
+ margin: 4px 0 calc(1em / 0.95 - 1em);
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ -moz-box-align: center;
+ background: transparent;
+ min-width: 0;
+ min-height: 0;
+ margin: 0;
+ border: none;
+ color: inherit;
+ padding: 0 18px;
+}
+
+.downloadButton > .button-box {
+ -moz-appearance: none;
+ padding: 2px !important;
+ border-radius: 50%;
+}
+
+.downloadButton > .button-box > .button-icon {
+ width: 16px;
+ height: 16px;
+ margin: 0;
+ filter: url("chrome://browser/skin/filters.svg#fill");
+ fill: currentColor;
+}
+
+.downloadButton > .button-box > .button-text {
+ display: none;
+}
+
+.downloadButton:hover > .button-box {
+ background-color: graytext;
+ color: -moz-field;
+}
+
+.downloadButton:hover:active > .button-box {
+ background-color: -moz-fieldtext;
+}
+
+@itemFocused@ > .downloadButtonArea > .downloadButton:hover > .button-box {
+ background-color: HighlightText;
+ color: Highlight;
+}
+
+@itemFocused@ > .downloadButtonArea > .downloadButton:hover:active > .button-box {
+ background-color: -moz-field;
+ color: -moz-fieldtext;
+}
+
+/*** Button icons ***/
+
+.downloadIconCancel > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#cancel");
+}
+
+.downloadIconShow > .button-box > .button-icon {
+%ifdef XP_MACOSX
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#magnifier");
+%else
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#folder");
+%endif
+}
+
+.downloadIconRetry > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#retry");
+}
+
+/*** Progressmeter ***/
+%include progressmeter.inc.css
diff --git a/browser/themes/shared/downloads/contentAreaDownloadsView.css b/browser/themes/shared/downloads/contentAreaDownloadsView.css
new file mode 100644
index 000000000..aef7df1d9
--- /dev/null
+++ b/browser/themes/shared/downloads/contentAreaDownloadsView.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/. */
+
+@import url("chrome://global/skin/in-content/common.css");
+
+#contentAreaDownloadsView {
+ padding: 18px;
+}
+
+#downloadsRichListBox:empty {
+ border-color: transparent;
+ background-color: transparent;
+}
+
+.downloadButton:not([disabled="true"]):hover,
+.downloadButton:not([disabled="true"]):hover:active,
+.downloadButton:not([disabled]):hover:active {
+ background: transparent;
+ border: none;
+}
+
+.downloadButton > .button-box {
+ padding-bottom: 0;
+}
+
+#downloadsListEmptyDescription {
+ margin: 1em;
+ text-align: center;
+ color: GrayText;
+}
diff --git a/browser/themes/shared/downloads/download-blocked.svg b/browser/themes/shared/downloads/download-blocked.svg
new file mode 100644
index 000000000..e73efe99e
--- /dev/null
+++ b/browser/themes/shared/downloads/download-blocked.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ circle {
+ fill: #D92215;
+ }
+ rect {
+ fill: #fff;
+ }
+ </style>
+
+ <circle cx="8" cy="8" r="8" />
+ <rect x="3" y="6" width="10" height="4" rx=".5" ry=".5" />
+</svg>
diff --git a/browser/themes/shared/downloads/download-summary.svg b/browser/themes/shared/downloads/download-summary.svg
new file mode 100644
index 000000000..c10ee95fc
--- /dev/null
+++ b/browser/themes/shared/downloads/download-summary.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="32" height="32" viewBox="0 0 32 32">
+ <path fill="#fff" d="M26.2,31.4H6.8c-1.8,0-3.4-1.5-3.4-3.3V3.8c0-1.8,1.6-3.3,3.4-3.3H23l5.6,6.4V28C28.7,29.9,28,31.4,26.2,31.4z" />
+ <path fill="#939393" d="M26.2,31.9H6.8c-2,0-3.8-1.7-3.8-3.8V3.8C3,1.7,4.8,0,6.8,0h16.4L29,6.7v21.4C29,30.2,28.2,31.9,26.2,31.9z M6.8,1C5.2,1,4,2.3,4,3.8V28c0,1.6,1.4,3,2.9,3h19.2c1.6,0,2-1.5,2-3V7.1L22.7,1C22.7,1,6.8,1,6.8,1z" />
+ <path fill="#4c4c4c" d="M22.5,18.2L17,23.6c-0.3,0.3-0.6,0.4-1.1,0.4c-0.3,0-0.8-0.1-1.1-0.4l-5.5-5.4c-0.5-0.5-0.4-1.1,0.4-1.1H13 v-5.9c0-0.5,0.4-1,1-1h3.9c0.5,0,1,0.4,1,1v5.9h3.1C22.8,17.2,23,17.6,22.5,18.2z" />
+ <polygon fill="#939393" points="27,9 21,9 21,1.9 22,1.9 22,8 27,8" />
+</svg>
diff --git a/browser/themes/shared/downloads/downloads.inc.css b/browser/themes/shared/downloads/downloads.inc.css
new file mode 100644
index 000000000..b4d670648
--- /dev/null
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -0,0 +1,372 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%filter substitution
+
+%define keyfocus #downloadsPanel[keyfocus]
+%define notKeyfocus #downloadsPanel:not([keyfocus])
+%define item richlistitem[type="download"]
+%define itemFinished @item@[state="1"]
+%define itemNotFinished @item@:not([state="1"])
+%define itemFocused #downloadsListBox:focus > @item@[selected]
+
+/*** Panel and outer controls ***/
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+ overflow: hidden;
+ display: block;
+}
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent,
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
+ padding: 0;
+}
+
+#downloadsListBox {
+ background: transparent;
+ color: inherit;
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#emptyDownloads {
+ padding: 16px 25px;
+ margin: 0;
+ /* The panel can be wider than this description after the blocked subview is
+ shown, so center the text. */
+ text-align: center;
+}
+
+.downloadsPanelFooter {
+ background-color: var(--arrowpanel-dimmed);
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+.downloadsPanelFooter toolbarseparator,
+@item@ > toolbarseparator {
+ margin: 0;
+ border: 0;
+ min-width: 0;
+ border-left: 1px solid var(--panel-separator-color);
+ -moz-appearance: none;
+}
+
+.downloadsPanelFooterButton {
+ -moz-appearance: none;
+ background-color: transparent;
+ color: inherit;
+ margin: 0;
+ padding: 0;
+ min-width: 0;
+ min-height: 40px;
+ border: none;
+}
+
+.downloadsPanelFooterButton:hover {
+ outline: 1px solid var(--arrowpanel-dimmed);
+ background-color: var(--arrowpanel-dimmed);
+}
+
+.downloadsPanelFooterButton:hover:active,
+.downloadsPanelFooterButton[open="true"] {
+ outline: 1px solid var(--arrowpanel-dimmed-further);
+ background-color: var(--arrowpanel-dimmed-further);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
+.downloadsPanelFooterButton[default] {
+ background-color: #0996f8;
+ color: white;
+}
+
+.downloadsPanelFooterButton[default]:hover {
+ background-color: #0675d3;
+}
+
+.downloadsPanelFooterButton[default]:hover:active {
+ background-color: #0568ba;
+}
+
+.downloadsPanelFooterButton > .button-box {
+ padding: 0;
+ margin: 0;
+ border: none;
+}
+
+#downloadsHistory {
+ padding-inline-start: 10px;
+ padding-inline-end: 10px;
+}
+
+#downloadsPanel[hasdownloads] #downloadsFooterButtons:not(.downloadsHideDropmarker) > #downloadsHistory {
+ padding-inline-start: 68px;
+}
+
+toolbarseparator.downloadsDropmarkerSplitter {
+ margin: 7px 0;
+}
+
+@item@ > toolbarseparator {
+ margin: 10px 0;
+}
+
+@item@:hover > toolbarseparator,
+#downloadsFooter:hover toolbarseparator.downloadsDropmarkerSplitter,
+#downloadsFooter[showingdropdown] toolbarseparator {
+ margin: 0;
+}
+
+.downloadsDropmarker {
+ padding: 0 21px;
+}
+
+.downloadsDropmarker > .button-box > hbox {
+ display: none;
+}
+
+.downloadsDropmarker > .button-box > .button-menu-dropmarker {
+ /* This is to override the linux !important */
+ -moz-appearance: none !important;
+ display: -moz-box;
+ padding: 0;
+ margin: 0;
+}
+
+.downloadsDropmarker > .button-box > .button-menu-dropmarker > .dropmarker-icon {
+ width: 16px;
+ height: 16px;
+ list-style-image: url("chrome://browser/skin/downloads/menubutton-dropmarker.svg");
+ filter: url("chrome://browser/skin/filters.svg#fill");
+ fill: currentColor;
+}
+
+/* Override default icon size which is too small for this dropdown */
+.downloadsDropmarker > .button-box > .button-menu-dropmarker {
+ width: 16px;
+ height: 16px;
+}
+
+#downloadsSummary {
+ -moz-user-focus: normal;
+}
+
+#downloadsSummary > .downloadTypeIcon {
+ list-style-image: url("chrome://browser/skin/downloads/download-summary.svg");
+}
+
+#downloadsSummaryDescription {
+ color: -moz-nativehyperlinktext;
+}
+
+/*** List items and similar elements in the summary ***/
+
+#downloadsSummary,
+richlistitem[type="download"] {
+ height: var(--downloads-item-height);
+}
+
+richlistitem[type="download"] {
+ border-bottom: 1px solid var(--panel-separator-color);
+ background: transparent;
+ color: inherit;
+}
+
+richlistitem[type="download"]:last-child {
+ border-bottom: none;
+}
+
+.downloadTypeIcon {
+ margin: 8px 13px;
+ width: 32px;
+ height: 32px;
+}
+
+.downloadBlockedBadge {
+ margin: 0 5px;
+ background: url("chrome://browser/skin/downloads/download-blocked.svg") top right / 16px no-repeat;
+}
+
+.downloadBlockedBadge:-moz-locale-dir(rtl) {
+ background-position-x: left;
+}
+
+@item@[verdict="PotentiallyUnwanted"] .downloadBlockedBadge {
+ background-image: url("chrome://browser/skin/warning.svg");
+}
+
+@item@[verdict="Uncommon"] .downloadBlockedBadge {
+ background-image: url("chrome://browser/skin/info.svg");
+}
+
+/* We hold .downloadTarget, .downloadProgress and .downloadDetails inside of
+ a vbox with class .downloadContainer. We set the font-size of the entire
+ container to --downloads-item-font-size-factor because:
+
+ 1) This is the size that we want .downloadDetails to be
+ 2) The container's width is set by localizers by &downloadDetails.width;,
+ which is a ch unit. Since this is the value that should control the
+ panel width, we apply it to the outer container to constrain
+ .downloadTarget and .downloadProgress.
+
+ Finally, since we want .downloadTarget's font-size to be at 100% of the
+ font-size of .downloadContainer's parent, we use calc to go from the
+ smaller font-size back to the original font-size.
+ */
+.downloadContainer {
+ font-size: calc(100% * var(--downloads-item-font-size-factor));
+ margin-inline-end: 13px;
+}
+
+#downloadsSummaryDescription,
+.downloadTarget {
+ margin: 0;
+ font-size: calc(100% / var(--downloads-item-font-size-factor));
+}
+
+#downloadsSummaryDetails,
+.downloadDetails {
+ opacity: var(--downloads-item-details-opacity);
+ /* Use calc() to keep the height consistent with .downloadTarget, so that the
+ progress bar can be vertically centered. */
+ margin: 4px 0 calc(1em / var(--downloads-item-font-size-factor) - 1em);
+}
+
+@item@[verdict] > toolbarseparator {
+ visibility: hidden;
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ min-width: 58px;
+ margin: 0;
+ border: none;
+ background: transparent;
+ padding: 0;
+ color: inherit;
+}
+
+.downloadButton > .button-box > .button-icon {
+ width: 16px;
+ height: 16px;
+ margin: 1px;
+ filter: url("chrome://browser/skin/filters.svg#fill");
+ fill: currentColor;
+}
+
+.downloadButton > .button-box > .button-text {
+ margin: 0 !important;
+ padding: 0;
+}
+
+@itemFinished@[exists] .downloadMainArea:hover,
+@item@:not([verdict]) > .downloadButtonArea:hover,
+@item@[verdict]:hover {
+ background-color: var(--arrowpanel-dimmed);
+}
+
+@itemFinished@[exists] > .downloadMainArea:hover:active,
+@item@:not([verdict]) > .downloadButtonArea:hover:active,
+@item@[verdict]:hover:active {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+@item@[showingsubview] {
+ background-color: Highlight;
+ color: HighlightText;
+ transition: background-color var(--panelui-subview-transition-duration),
+ color var(--panelui-subview-transition-duration);
+}
+
+@item@[verdict="Malware"]:hover,
+@item@[verdict="Malware"]:hover:active,
+@item@[verdict="Malware"][showingsubview] {
+ background-color: #aa1b08;
+ color: white;
+}
+
+/*** Button icons ***/
+
+.downloadIconCancel > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#cancel");
+}
+
+.downloadIconShow > .button-box > .button-icon {
+%ifdef XP_MACOSX
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#magnifier");
+%else
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#folder");
+%endif
+}
+
+.downloadIconRetry > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#retry");
+}
+
+.downloadShowBlockedInfo > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-right");
+}
+
+.downloadShowBlockedInfo > .button-box > .button-icon:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-left");
+}
+
+/*** Blocked subview ***/
+
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
+ /* When the main view is showing, the shadow on the left edge of the subview is
+ barely visible on the right edge of the main view, so set it to none. */
+ box-shadow: none;
+}
+
+/* When the subview is showing, turn the download button into an arrow pointing
+ back to the main view. */
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton {
+ color: HighlightText;
+}
+
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-left");
+}
+
+#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton > .button-box > .button-icon:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/panel-icons.svg#arrow-right");
+}
+
+#downloadsPanel-blockedSubview {
+ background-image: url("chrome://browser/skin/warning.svg");
+ background-size: 32px 32px;
+ background-position: 16px 16px;
+ background-repeat: no-repeat;
+}
+
+#downloadsPanel-blockedSubview:-moz-locale-dir(rtl) {
+ background-position: calc(100% - 16px) 16px;
+}
+
+#downloadsPanel-blockedSubview[verdict=Malware] {
+ background-image: url("chrome://browser/skin/downloads/download-blocked.svg");
+}
+
+#downloadsPanel-blockedSubview-title {
+ margin-top: 16px;
+ margin-bottom: 16px;
+ font-size: calc(100% / var(--downloads-item-font-size-factor));
+}
+
+#downloadsPanel-blockedSubview-details1,
+#downloadsPanel-blockedSubview-details2 {
+ font-size: calc(100% * var(--downloads-item-font-size-factor));
+ margin-bottom: 16px;
+ opacity: var(--downloads-item-details-opacity);
+}
+
+#downloadsPanel-blockedSubview-title,
+#downloadsPanel-blockedSubview-details1,
+#downloadsPanel-blockedSubview-details2 {
+ -moz-margin-start: 64px;
+ -moz-margin-end: 16px;
+}
+
+/*** Progressmeter ***/
+%include progressmeter.inc.css
diff --git a/browser/themes/shared/downloads/menubutton-dropmarker.svg b/browser/themes/shared/downloads/menubutton-dropmarker.svg
new file mode 100644
index 000000000..76af113a7
--- /dev/null
+++ b/browser/themes/shared/downloads/menubutton-dropmarker.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="16" height="16" viewBox="0 0 16 16">
+ <path d="m 2,6 6,6 6,-6 -1.5,-1.5 -4.5,4.5 -4.5,-4.5 z" />
+</svg>
diff --git a/browser/themes/shared/downloads/progressmeter.inc.css b/browser/themes/shared/downloads/progressmeter.inc.css
new file mode 100644
index 000000000..22e780e09
--- /dev/null
+++ b/browser/themes/shared/downloads/progressmeter.inc.css
@@ -0,0 +1,70 @@
+/*** Common-styled progressmeter ***/
+.downloadProgress {
+ height: 8px;
+ border-radius: 1px;
+ margin: 4px 0 0;
+ margin-inline-end: 12px;
+
+ /* for overriding rules in progressmeter.css */
+ -moz-appearance: none;
+ border-style: none;
+ background-color: transparent;
+ min-width: initial;
+ min-height: initial;
+}
+
+.downloadProgress[mode="undetermined"] {
+ /* for overriding rules on global.css in Linux. */
+ -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter");
+}
+
+.downloadProgress > .progress-bar {
+ background-color: Highlight;
+
+ /* for overriding rules in progressmeter.css */
+ -moz-appearance: none;
+}
+
+.downloadProgress[paused="true"] > .progress-bar {
+ background-color: GrayText;
+}
+
+.downloadProgress[mode="undetermined"] > .progress-bar {
+ /* Make a white reflecting animation.
+ Create a gradient with 2 identical pattern, and enlarge the size to 200%.
+ This allows us to animate background-position with percentage. */
+ background-image: linear-gradient(90deg, transparent 0%,
+ rgba(255,255,255,0.5) 25%,
+ transparent 50%,
+ rgba(255,255,255,0.5) 75%,
+ transparent 100%);
+ background-blend-mode: lighten;
+ background-size: 200% 100%;
+ animation: downloadProgressSlideX 1.5s linear infinite;
+}
+
+.downloadProgress > .progress-remainder {
+ border: solid ButtonShadow;
+ border-block-start-width: 1px;
+ border-block-end-width: 1px;
+ border-inline-start-width: 0;
+ border-inline-end-width: 1px;
+ background-color: ButtonFace;
+}
+
+.downloadProgress[value="0"] > .progress-remainder {
+ border-width: 1px;
+}
+
+.downloadProgress > .progress-remainder[mode="undetermined"] {
+ border: none;
+}
+
+@keyframes downloadProgressSlideX {
+ 0% {
+ background-position: 0 0;
+ }
+ 100% {
+ background-position: -100% 0;
+ }
+}
diff --git a/browser/themes/shared/drm-icon.svg b/browser/themes/shared/drm-icon.svg
new file mode 100644
index 000000000..db2b36df6
--- /dev/null
+++ b/browser/themes/shared/drm-icon.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
+ <style>
+ #chains {
+ fill: url(#baseGradient);
+ }
+ #chains-pressed {
+ fill: url(#pressedGradient);
+ }
+ #chains-black {
+ fill: #000;
+ }
+ use:not(:target) {
+ display: none;
+ }
+ </style>
+ <defs>
+ <linearGradient id="baseGradient" gradientUnits="userSpaceOnUse" x1="8" x2="8" y1="16" y2="0">
+ <stop offset="0" style="stop-color: #808080"/>
+ <stop offset="1" style="stop-color: #999"/>
+ </linearGradient>
+ <linearGradient id="pressedGradient" gradientUnits="userSpaceOnUse" x1="8" x2="8" y1="16" y2="0">
+ <stop offset="0" style="stop-color: #4d4d4d"/>
+ <stop offset="1" style="stop-color: #808080"/>
+ </linearGradient>
+ <g id="path">
+ <path d="M7.058,9.72c-0.245,0.245-0.62,0.27-0.834,0.056C6.01,9.562,6.035,9.186,6.28,8.942l0.218-0.218 c-0.245-0.245-0.645-0.245-0.89,0L4.496,9.836c-0.245,0.245-0.245,0.645,0,0.89l0.779,0.779c0.245,0.245,0.645,0.245,0.89,0 l1.112-1.112c0.245-0.245,0.245-0.645,0-0.89L7.058,9.72z"/>
+ <path d="M10.726,4.496c-0.245-0.245-0.645-0.245-0.89,0L8.723,5.608c-0.245,0.245-0.245,0.645,0,0.89 L8.95,6.272c0.245-0.245,0.62-0.27,0.834-0.056s0.189,0.59-0.056,0.834L9.502,7.277c0.245,0.245,0.645,0.245,0.89,0l1.112-1.112 c0.245-0.245,0.245-0.645,0-0.89L10.726,4.496z"/>
+ <path d="M8,0C3.582,0,0,3.582,0,8s3.582,8,8,8s8-3.582,8-8S12.418,0,8,0z M12.527,6.81l-1.489,1.489 c-0.631,0.631-1.663,0.631-2.293,0L8.612,8.167L8.167,8.612l0.133,0.133c0.631,0.631,0.631,1.663,0,2.293L6.81,12.527 c-0.631,0.631-1.663,0.631-2.293,0l-1.044-1.044c-0.631-0.631-0.631-1.663,0-2.293l1.489-1.489c0.631-0.631,1.663-0.631,2.293,0 l0.133,0.133l0.445-0.445L7.701,7.255c-0.631-0.631-0.631-1.663,0-2.293L9.19,3.473c0.631-0.631,1.663-0.631,2.293,0l1.044,1.044 C13.158,5.148,13.158,6.18,12.527,6.81z"/>
+ </g>
+ </defs>
+ <use xlink:href="#path" id="chains"/>
+ <use xlink:href="#path" id="chains-pressed"/>
+ <use xlink:href="#path" id="chains-black"/>
+</svg>
diff --git a/browser/themes/shared/e10s-64@2x.png b/browser/themes/shared/e10s-64@2x.png
new file mode 100644
index 000000000..1dc243865
--- /dev/null
+++ b/browser/themes/shared/e10s-64@2x.png
Binary files differ
diff --git a/browser/themes/shared/error-pages.css b/browser/themes/shared/error-pages.css
new file mode 100644
index 000000000..0711be9c7
--- /dev/null
+++ b/browser/themes/shared/error-pages.css
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://global/skin/in-content/info-pages.css");
+
+body {
+ background-size: 64px 32px;
+ background-repeat: repeat-x;
+ /* Top padding for when the window height is small.
+ Bottom padding to keep everything centered. */
+ padding: 75px 0;
+ /* info-pages.css sets a minimum width of 13em to the content
+ * container. If we don't set a min-width here, the content
+ * gets clipped in iframes with small width. We don't accomodate
+ * any padding to prioritize real estate in the small viewport. */
+ min-width: 13em;
+}
+
+.button-container {
+ display: flex;
+ flex-flow: row wrap;
+}
+
+.button-spacer {
+ flex: 1;
+}
+
+@media only screen and (max-width: 959px) {
+ body {
+ padding: 75px 48px;
+ }
+
+ .title {
+ background-image: none !important;
+ padding-inline-start: 0;
+ margin-inline-start: 0;
+ }
+
+ .title-text {
+ padding-top: 0;
+ }
+}
+
+@media only screen and (max-width: 640px) {
+ body {
+ justify-content: unset;
+ /* Now that everything is top-aligned, we don't need the
+ * bottom padding for centering - though it's added back
+ * when the viewport height is < 480px (see below). */
+ padding: 75px 20px 0;
+ }
+
+ .title-text {
+ padding-bottom: 0;
+ border-bottom: none;
+ }
+}
+
+@media only screen and (max-width: 480px) {
+ .button-container button {
+ /* Force buttons to display: block here to try and enforce collapsing margins */
+ display: block;
+ width: 100%;
+ margin: 0.66em 0 0;
+ }
+}
+
+/* For small window height, shift the stripes up by 10px.
+ * We could just change the background size, but that changes
+ * the angle of the stripes so just shifting up is easier. */
+@media only screen and (max-height: 480px) {
+ body {
+ background-position: 10px -10px;
+ padding-top: 38px;
+ /* We get rid of bottom padding for width < 640px, but
+ * for height < 480px a bit of space between the content
+ * and the viewport edge is nice. */
+ padding-bottom: 38px;
+ }
+} \ No newline at end of file
diff --git a/browser/themes/shared/favicon-search-16.svg b/browser/themes/shared/favicon-search-16.svg
new file mode 100644
index 000000000..e839cc804
--- /dev/null
+++ b/browser/themes/shared/favicon-search-16.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <circle cx="8" cy="8" r="8" fill="#58bf43"/>
+ <circle cx="8" cy="8" r="7.5" stroke="#41a833" stroke-width="1" fill="none"/>
+ <path d="M12.879,12L12,12.879,9.015,9.9A4.276,4.276,0,1,1,9.9,9.015ZM6.5,3.536A2.964,2.964,0,1,0,9.464,6.5,2.964,2.964,0,0,0,6.5,3.536Z" stroke="#41a833" stroke-width="2" fill="none"/>
+ <path d="M12.879,12L12,12.879,9.015,9.9A4.276,4.276,0,1,1,9.9,9.015ZM6.5,3.536A2.964,2.964,0,1,0,9.464,6.5,2.964,2.964,0,0,0,6.5,3.536Z" fill="#fff"/>
+</svg>
diff --git a/browser/themes/shared/filters.svg b/browser/themes/shared/filters.svg
new file mode 100644
index 000000000..8fccb13cc
--- /dev/null
+++ b/browser/themes/shared/filters.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="fill">
+ <feComposite in="FillPaint" in2="SourceGraphic" operator="in"/>
+ </filter>
+</svg>
diff --git a/browser/themes/shared/fullscreen/insecure.svg b/browser/themes/shared/fullscreen/insecure.svg
new file mode 100644
index 000000000..eec272f7c
--- /dev/null
+++ b/browser/themes/shared/fullscreen/insecure.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="24" height="24" viewBox="0 0 24 24">
+ <style>
+ .icon-default {
+ fill: #fafafa;
+ }
+ </style>
+
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="5" y="1" width="14" height="20" rx="7" ry="7" />
+ <rect id="shape-lock-clasp-inner" x="8" y="4" width="8" height="14" rx="4" ry="4" />
+ <rect id="shape-lock-base" x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+
+ <mask id="mask-clasp-cutout">
+ <rect width="24" height="24" fill="#000" />
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ <line x1="3" y1="21" x2="21.5" y2="0.5" stroke="#000" stroke-width="3" />
+ <line x1="3" y1="25" x2="21.5" y2="4.5" stroke="#000" stroke-width="3" />
+ <rect x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+ </mask>
+
+ <mask id="mask-base-cutout">
+ <rect width="24" height="24" fill="#000" />
+ <use xlink:href="#shape-lock-base" fill="#fff" />
+ <line x1="2.25" y1="24.75" x2="21.5" y2="4.5" stroke="#000" stroke-width="3" />
+ </mask>
+ </defs>
+
+ <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" class="icon-default" />
+ <use xlink:href="#shape-lock-base" mask="url(#mask-base-cutout)" class="icon-default" />
+
+ <line x1="2.25" y1="22.75" x2="21.5" y2="2.5" stroke="#d92d21" stroke-width="3" />
+</svg>
diff --git a/browser/themes/shared/fullscreen/secure.svg b/browser/themes/shared/fullscreen/secure.svg
new file mode 100644
index 000000000..960fc1df0
--- /dev/null
+++ b/browser/themes/shared/fullscreen/secure.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
+ <style>
+ .icon-default {
+ fill: #fafafa;
+ }
+ </style>
+
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="5" y="1" width="14" height="20" rx="7" ry="7" />
+ <rect id="shape-lock-clasp-inner" x="8" y="4" width="8" height="14" rx="4" ry="4" />
+ <rect id="shape-lock-base" x="3" y="10" width="18" height="13" rx="1.5" ry="1.5" />
+
+ <mask id="mask-clasp-cutout">
+ <rect width="24" height="24" fill="#000" />
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ </mask>
+ </defs>
+
+ <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" class="icon-default" />
+ <use xlink:href="#shape-lock-base" class="icon-default" />
+</svg>
diff --git a/browser/themes/shared/fullscreen/warning.inc.css b/browser/themes/shared/fullscreen/warning.inc.css
new file mode 100644
index 000000000..7111a57c7
--- /dev/null
+++ b/browser/themes/shared/fullscreen/warning.inc.css
@@ -0,0 +1,51 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+html|*.pointerlockfswarning {
+ align-items: center;
+ background: rgba(45, 62, 72, 0.9);
+ border: 2px solid #fafafa;
+ box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.5);
+ border-radius: 8px;
+ padding: 24px 16px;
+ font: message-box;
+}
+
+html|*.pointerlockfswarning::before {
+ margin: 0;
+ width: 24px; height: 24px;
+}
+
+html|*.pointerlockfswarning[data-identity="verifiedIdentity"]::before,
+html|*.pointerlockfswarning[data-identity="verifiedDomain"]::before {
+ content: url("chrome://browser/skin/fullscreen/secure.svg");
+}
+
+html|*.pointerlockfswarning[data-identity="unknownIdentity"]::before {
+ content: url("chrome://browser/skin/fullscreen/insecure.svg");
+}
+
+html|*.pointerlockfswarning-domain-text,
+html|*.pointerlockfswarning-generic-text {
+ font-size: 21px;
+ font-weight: lighter;
+ color: #fafafa;
+ margin: 0 16px;
+}
+
+html|*.pointerlockfswarning-domain {
+ font-weight: bold;
+ margin: 0;
+}
+
+html|*.pointerlockfswarning-exit-button {
+ padding: 5px 30px;
+ font: message-box;
+ font-size: 14px;
+ font-weight: lighter;
+ margin: 0;
+ box-sizing: content-box;
+} \ No newline at end of file
diff --git a/browser/themes/shared/fxa/android.png b/browser/themes/shared/fxa/android.png
new file mode 100644
index 000000000..e0a37a85e
--- /dev/null
+++ b/browser/themes/shared/fxa/android.png
Binary files differ
diff --git a/browser/themes/shared/fxa/android@2x.png b/browser/themes/shared/fxa/android@2x.png
new file mode 100644
index 000000000..bb6bab5e0
--- /dev/null
+++ b/browser/themes/shared/fxa/android@2x.png
Binary files differ
diff --git a/browser/themes/shared/fxa/default-avatar.svg b/browser/themes/shared/fxa/default-avatar.svg
new file mode 100644
index 000000000..540234911
--- /dev/null
+++ b/browser/themes/shared/fxa/default-avatar.svg
@@ -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/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="194 -104 1000 1000" width="80" height="80">
+ <path fill="#c3cfd8" d="M694-104.3c276.1 0 500 223.9 500 500s-223.9 500-500 500-500-223.9-500-500c0-276.2 223.9-500 500-500z"/>
+ <path fill="#fff" fill-rule="evenodd" clip-rule="evenodd" d="M892.4 585.9c10 3.1 19.1 5.7 27.5 8.2 34.5 10 44.8 54.6 17.5 78.1-65.4 56.5-150.7 90.8-244 90.8-92.8 0-177.6-33.8-242.9-89.8-27.4-23.5-17.3-68.2 17.4-78.3 9.2-2.7 19.2-5.5 30.2-9 62.6-19.5 92.6-43.7 98.2-68.7 0-.1 0-.2.1-.2 3.6-16.1-2.8-32.9-15.5-43.5-26.4-22.1-37.1-59.8-44.1-87.5-.8-3.2-1.7-6.5-2.5-9.8-12.1-2.1-25.4-17.3-32.2-38.5-8.2-25.5-3.9-49.8 9.6-54.1 1.3-.4 2.6-.4 3.9-.5-3.1-18.2-6.9-45.4-7.3-69.3-.1-5.2-.2-10.9-.2-16.9 0-3 .1-6.1.1-9.3 0-1.6.1-3.2.2-4.8.1-1.6.2-3.2.3-4.9.9-13.1 2.9-26.8 7-40 7.4-23.7 21.6-45.4 47.4-57.3 5.8-2.7 11-6.4 15.1-11.3 22.4-26.4 49.1-39.6 74.2-45.4 6.9-1.6 13.6-2.6 20.1-3.2 3.2-.3 6.4-.5 9.5-.6 1.6-.1 3.1-.1 4.6-.1h4.5c11.7.3 22 1.8 29.6 3.7 50 12.3 89.2 38 116.4 69.5 13.5 15.8 23.9 33 30.7 50.7 3.4 8.9 5.9 17.9 7.4 26.9.8 4.5 1.3 9 1.6 13.5.3 4.5.3 8.9.1 13.4-1.5 27.1-4.4 45.9-7.3 60.1-2.3 11.1.1 22.2 5 32.4 4.9 10.3 5.3 26.7.2 43.9-6.1 20.3-18.3 35.3-29.8 38.7-2.2 8.1-3.8 13.5-3.9 13.5-3.8 29-10.7 59.8-35.3 82.9-10.5 9.8-15 24.5-13.1 38.7.5 3.5 1 6.6 1.6 9.2 5.6 25.1 35.5 49.3 98.1 68.8z"/>
+</svg>
diff --git a/browser/themes/shared/fxa/ios.png b/browser/themes/shared/fxa/ios.png
new file mode 100644
index 000000000..40c90b1fa
--- /dev/null
+++ b/browser/themes/shared/fxa/ios.png
Binary files differ
diff --git a/browser/themes/shared/fxa/ios@2x.png b/browser/themes/shared/fxa/ios@2x.png
new file mode 100644
index 000000000..af2d5ace5
--- /dev/null
+++ b/browser/themes/shared/fxa/ios@2x.png
Binary files differ
diff --git a/browser/themes/shared/fxa/logo.png b/browser/themes/shared/fxa/logo.png
new file mode 100644
index 000000000..63c006c29
--- /dev/null
+++ b/browser/themes/shared/fxa/logo.png
Binary files differ
diff --git a/browser/themes/shared/fxa/logo@2x.png b/browser/themes/shared/fxa/logo@2x.png
new file mode 100644
index 000000000..283b091df
--- /dev/null
+++ b/browser/themes/shared/fxa/logo@2x.png
Binary files differ
diff --git a/browser/themes/shared/fxa/sync-illustration.png b/browser/themes/shared/fxa/sync-illustration.png
new file mode 100644
index 000000000..3d3730849
--- /dev/null
+++ b/browser/themes/shared/fxa/sync-illustration.png
Binary files differ
diff --git a/browser/themes/shared/fxa/sync-illustration.svg b/browser/themes/shared/fxa/sync-illustration.svg
new file mode 100644
index 000000000..dd2b7bde6
--- /dev/null
+++ b/browser/themes/shared/fxa/sync-illustration.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="320" height="280" viewBox="0 0 320 280">
+ <g fill="#cdcdcd">
+ <path d="M46.352,148.919 L46.352,148.919 L44.938,150.333 L43.523,148.919 L43.523,148.919 L37.866,143.262 L39.281,141.848 L44.938,147.505 L50.594,141.848 L52.009,143.262 L46.352,148.919 ZM43.937,134.000 L45.938,134.000 L45.938,142.000 L43.937,142.000 L43.937,134.000 ZM43.937,122.000 L45.938,122.000 L45.938,130.000 L43.937,130.000 L43.937,122.000 Z"/>
+ <path d="M306.641,132.110 L300.984,126.453 L295.328,132.110 L293.913,130.696 L300.984,123.625 L308.055,130.696 L306.641,132.110 ZM302.000,223.969 L300.000,223.969 L300.000,215.969 L302.000,215.969 L302.000,223.969 ZM302.000,211.969 L300.000,211.969 L300.000,203.969 L302.000,203.969 L302.000,211.969 ZM302.000,199.969 L300.000,199.969 L300.000,191.969 L302.000,191.969 L302.000,199.969 ZM302.000,187.969 L300.000,187.969 L300.000,179.969 L302.000,179.969 L302.000,187.969 ZM302.000,175.969 L300.000,175.969 L300.000,167.969 L302.000,167.969 L302.000,175.969 ZM302.000,163.969 L300.000,163.969 L300.000,155.969 L302.000,155.969 L302.000,163.969 ZM300.000,131.969 L302.000,131.969 L302.000,139.969 L300.000,139.969 L300.000,131.969 ZM302.000,151.969 L300.000,151.969 L300.000,143.969 L302.000,143.969 L302.000,151.969 ZM300.000,227.969 L302.000,227.969 L302.000,232.000 L302.000,234.000 L300.000,234.000 L292.000,234.000 L292.000,232.000 L300.000,232.000 L300.000,227.969 Z"/>
+ <path d="M101.335,236.009 L99.921,234.594 L105.578,228.938 L99.921,223.281 L101.335,221.866 L108.406,228.938 L101.335,236.009 ZM100.000,229.938 L92.000,229.938 L92.000,227.937 L100.000,227.937 L100.000,229.938 ZM80.000,227.937 L88.000,227.937 L88.000,229.938 L80.000,229.938 L80.000,227.937 Z"/>
+ <path d="M182.000,54.000 L182.000,52.000 L190.000,52.000 L190.000,54.000 L182.000,54.000 ZM170.000,52.000 L178.000,52.000 L178.000,54.000 L170.000,54.000 L170.000,52.000 ZM168.488,60.071 L161.417,53.000 L168.488,45.929 L169.902,47.343 L164.245,53.000 L169.902,58.657 L168.488,60.071 Z"/>
+ <path d="M297.688,276.000 L102.312,276.000 C97.721,276.000 94.000,272.279 94.000,267.688 L94.000,260.000 L306.000,260.000 L306.000,267.688 C306.000,272.279 302.279,276.000 297.688,276.000 ZM117.906,150.312 C117.906,145.721 121.628,142.000 126.218,142.000 L273.688,142.000 C278.279,142.000 282.000,145.721 282.000,150.312 L282.000,256.000 L117.906,256.000 L117.906,150.312 ZM132.000,242.000 L270.000,242.000 L270.000,156.000 L132.000,156.000 L132.000,242.000 Z"/>
+ <path d="M307.074,115.969 L206.926,115.969 C203.101,115.969 200.000,112.868 200.000,109.042 L200.000,38.926 C200.000,35.101 203.101,32.000 206.926,32.000 L307.074,32.000 C310.899,32.000 314.000,35.101 314.000,38.926 L314.000,109.042 C314.000,112.868 310.899,115.969 307.074,115.969 ZM210.000,65.875 C210.000,64.770 209.105,63.875 208.000,63.875 C206.895,63.875 206.000,64.770 206.000,65.875 L206.000,82.000 C206.000,83.105 206.895,84.000 208.000,84.000 C209.105,84.000 210.000,83.105 210.000,82.000 L210.000,65.875 ZM302.000,42.000 L216.000,42.000 L216.000,106.000 L302.000,106.000 L302.000,42.000 Z"/>
+ <path d="M65.844,240.000 L26.156,240.000 C23.861,240.000 22.000,238.139 22.000,235.844 L22.000,162.156 C22.000,159.861 23.861,158.000 26.156,158.000 L65.844,158.000 C68.139,158.000 70.000,159.861 70.000,162.156 L70.000,235.844 C70.000,238.139 68.139,240.000 65.844,240.000 ZM46.000,236.000 C48.287,236.000 50.141,234.195 50.141,231.969 C50.141,229.742 48.287,227.938 46.000,227.938 C43.713,227.938 41.859,229.742 41.859,231.969 C41.859,234.195 43.713,236.000 46.000,236.000 ZM66.000,168.000 L26.000,168.000 L26.000,224.000 L66.000,224.000 L66.000,168.000 Z"/>
+ <path d="M171.906,86.156 C171.906,102.329 159.026,115.469 143.017,115.797 L143.039,115.955 L28.850,115.955 L28.869,115.797 C12.872,115.475 -0.000,102.333 -0.000,86.156 C-0.000,71.661 10.336,59.603 23.994,57.019 C23.620,55.457 23.401,53.834 23.401,52.156 C23.401,40.714 32.606,31.438 43.962,31.438 C47.561,31.438 50.941,32.375 53.884,34.012 C53.883,33.930 53.878,33.848 53.878,33.766 C53.878,17.137 67.301,3.656 83.858,3.656 C97.763,3.656 109.453,13.164 112.843,26.059 C116.677,23.334 121.343,21.719 126.393,21.719 C139.394,21.719 149.933,32.331 149.933,45.422 C149.933,49.572 148.868,53.468 147.007,56.861 C161.114,59.082 171.906,71.351 171.906,86.156 Z"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/fxa/sync-illustration@2x.png b/browser/themes/shared/fxa/sync-illustration@2x.png
new file mode 100644
index 000000000..23a38587d
--- /dev/null
+++ b/browser/themes/shared/fxa/sync-illustration@2x.png
Binary files differ
diff --git a/browser/themes/shared/heartbeat-icon.svg b/browser/themes/shared/heartbeat-icon.svg
new file mode 100644
index 000000000..db2cb5f03
--- /dev/null
+++ b/browser/themes/shared/heartbeat-icon.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg width="288px" height="248px" viewBox="0 0 288 248" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#d74345" d="M144,248.571429 C141.214272,248.571429 138.857152,247.607152 136.928571,245.678571 L36.6428571,148.928571 C35.5714232,148.071424 34.0982237,146.678581 32.2232143,144.75 C30.3482049,142.821419 27.3750204,139.312525 23.3035714,134.223214 C19.2321225,129.133903 15.5893018,123.910741 12.375,118.553571 C9.16069821,113.196402 6.29465545,106.714324 3.77678571,99.1071429 C1.25891598,91.499962 0,84.1071788 0,76.9285714 C0,53.357025 6.80350339,34.9286379 20.4107143,21.6428571 C34.0179252,8.35707643 52.8213086,1.71428571 76.8214286,1.71428571 C83.4643189,1.71428571 90.2410369,2.86605991 97.1517857,5.16964286 C104.062535,7.4732258 110.491042,10.5803376 116.4375,14.4910714 C122.383958,18.4018053 127.499979,22.0714114 131.785714,25.5 C136.07145,28.9285886 140.142838,32.5714093 144,36.4285714 C147.857162,32.5714093 151.92855,28.9285886 156.214286,25.5 C160.500021,22.0714114 165.616042,18.4018053 171.5625,14.4910714 C177.508958,10.5803376 183.937465,7.4732258 190.848214,5.16964286 C197.758963,2.86605991 204.535681,1.71428571 211.178571,1.71428571 C235.178691,1.71428571 253.982075,8.35707643 267.589286,21.6428571 C281.196497,34.9286379 288,53.357025 288,76.9285714 C288,100.607261 275.732266,124.714163 251.196429,149.25 L151.071429,245.678571 C149.142847,247.607152 146.785728,248.571429 144,248.571429 L144,248.571429 Z" transform="translate(0,-1)"/>
+ <g transform="translate(0,-0.29)">
+ <mask id="mask" fill="#fff">
+ <path d="M144,246.857143 C141.214272,246.857143 138.857152,245.892867 136.928571,243.964286 L36.6428571,147.214286 C35.5714232,146.357139 34.0982237,144.964295 32.2232143,143.035714 C30.3482049,141.107133 27.3750204,137.59824 23.3035714,132.508929 C19.2321225,127.419617 15.5893018,122.196455 12.375,116.839286 C9.16069821,111.482116 6.29465545,105.000038 3.77678571,97.3928571 C1.25891598,89.7856763 0,82.392893 0,75.2142857 C0,51.6427393 6.80350339,33.2143521 20.4107143,19.9285714 C34.0179252,6.64279071 52.8213086,0 76.8214286,0 C83.4643189,0 90.2410369,1.1517742 97.1517857,3.45535714 C104.062535,5.75894009 110.491042,8.86605187 116.4375,12.7767857 C122.383958,16.6875196 127.499979,20.3571257 131.785714,23.7857143 C136.07145,27.2143029 140.142838,30.8571236 144,34.7142857 C147.857162,30.8571236 151.92855,27.2143029 156.214286,23.7857143 C160.500021,20.3571257 165.616042,16.6875196 171.5625,12.7767857 C177.508958,8.86605187 183.937465,5.75894009 190.848214,3.45535714 C197.758963,1.1517742 204.535681,0 211.178571,0 C235.178691,0 253.982075,6.64279071 267.589286,19.9285714 C281.196497,33.2143521 288,51.6427393 288,75.2142857 C288,98.8929755 275.732266,122.999877 251.196429,147.535714 L151.071429,243.964286 C149.142847,245.892867 146.785728,246.857143 144,246.857143 L144,246.857143 Z"/>
+ </mask>
+ <path fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" mask="url(#mask)" d="M-166,115.135254 C-166,115.135254 0.595052083,115.135254 2.9765625,115.135254 L91.9101562,115.135254 L97.9638977,100.101562 L105.430695,115.135254 L114.893585,115.135254 L131.129913,189.53125 L148.161163,57 L165.348663,131.027344 L172.272491,115.135254 L250.84967,115.135254 L428.259813,115.135254"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/heartbeat-star-lit.svg b/browser/themes/shared/heartbeat-star-lit.svg
new file mode 100644
index 000000000..3f59099b9
--- /dev/null
+++ b/browser/themes/shared/heartbeat-star-lit.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="100%" height="100%">
+ <path fill="#0095dd" d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"/>
+</svg>
diff --git a/browser/themes/shared/heartbeat-star-off.svg b/browser/themes/shared/heartbeat-star-off.svg
new file mode 100644
index 000000000..143fe48e2
--- /dev/null
+++ b/browser/themes/shared/heartbeat-star-off.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="100%" height="100%">
+ <path fill="#c0c0c0" d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"/>
+</svg>
diff --git a/browser/themes/shared/icon-colors.inc.svg b/browser/themes/shared/icon-colors.inc.svg
new file mode 100644
index 000000000..dc29d6055
--- /dev/null
+++ b/browser/themes/shared/icon-colors.inc.svg
@@ -0,0 +1,39 @@
+<style>
+
+.fieldtext {
+ fill: -moz-fieldtext;
+#ifdef MOZ_WIDGET_GTK
+ /* The fill-opacity needs to be sufficient for high-contrast settings, and
+ pathological Gtk themes where -moz-fieldtext provides low contrast by
+ default. */
+ fill-opacity: .7;
+#else
+ fill-opacity: .5;
+#endif
+}
+
+#ifdef XP_WIN
+@media (-moz-windows-default-theme: 0) {
+ /* more opacity for high-contrast themes */
+ .fieldtext {
+ fill-opacity: .8;
+ }
+}
+#endif
+
+.highlighttext {
+ fill: highlighttext;
+ fill-opacity: 1;
+}
+
+.black {
+ fill: black;
+ fill-opacity: .6;
+}
+
+.white {
+ fill: white;
+ fill-opacity: .7;
+}
+
+</style>
diff --git a/browser/themes/shared/icon.png b/browser/themes/shared/icon.png
new file mode 100644
index 000000000..891e7afb1
--- /dev/null
+++ b/browser/themes/shared/icon.png
Binary files differ
diff --git a/browser/themes/shared/identity-block/connection-mixed-active-loaded.svg b/browser/themes/shared/identity-block/connection-mixed-active-loaded.svg
new file mode 100644
index 000000000..35d84784a
--- /dev/null
+++ b/browser/themes/shared/identity-block/connection-mixed-active-loaded.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16" height="16" viewBox="0 0 16 16">
+#include ../icon-colors.inc.svg
+ <style>
+ svg > g:not(:target) {
+ display: none;
+ }
+ </style>
+
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="4" y="2" width="8" height="10" rx="4" ry="4" />
+ <rect id="shape-lock-clasp-inner" x="6" y="4" width="4" height="6" rx="2" ry="2" />
+ <rect id="shape-lock-base" x="3" y="7" width="10" height="7" rx="1" ry="1" />
+
+ <mask id="mask-clasp-cutout">
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ <line x1="2" y1="13" x2="14" y2="1.5" stroke="#000" stroke-width="2" />
+ <line x1="2" y1="15" x2="14" y2="3.5" stroke="#000" stroke-width="2" />
+ <rect x="3" y="7" width="10" height="7" rx="1" ry="1" fill="#000" />
+ </mask>
+
+ <mask id="mask-base-cutout">
+ <use xlink:href="#shape-lock-base" fill="#fff" />
+ <line x1="2" y1="14.8" x2="14" y2="3.2" stroke="#000" stroke-width="1.8" />
+ </mask>
+
+ <g id="lock">
+ <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)"/>
+ <use xlink:href="#shape-lock-base" mask="url(#mask-base-cutout)"/>
+ </g>
+
+ <line id="strike-through-red" x1="2" y1="14.1" x2="14" y2="2.5" stroke="#d92d21" stroke-width="1.8"/>
+ </defs>
+
+ <g id="icon">
+ <use xlink:href="#lock" class="fieldtext"/>
+ <use xlink:href="#strike-through-red"/>
+ </g>
+
+ <g id="icon-black">
+ <use xlink:href="#lock" class="black"/>
+ <use xlink:href="#strike-through-red"/>
+ </g>
+
+ <g id="icon-white">
+ <use xlink:href="#lock" class="white"/>
+ <use xlink:href="#strike-through-red"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/identity-block/connection-mixed-passive-loaded.svg b/browser/themes/shared/identity-block/connection-mixed-passive-loaded.svg
new file mode 100644
index 000000000..66cb96ee0
--- /dev/null
+++ b/browser/themes/shared/identity-block/connection-mixed-passive-loaded.svg
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16" height="16" viewBox="0 0 16 16">
+#include ../icon-colors.inc.svg
+ <style>
+ svg > g:not(:target) {
+ display: none;
+ }
+ </style>
+
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="2" y="1" width="8" height="10" rx="4" ry="4" />
+ <rect id="shape-lock-clasp-inner" x="4" y="3" width="4" height="6" rx="2" ry="2" />
+ <rect id="shape-lock-base" x="1" y="6" width="10" height="7" rx="1" ry="1" />
+
+ <mask id="mask-clasp-cutout">
+ <rect width="16" height="16" fill="#000" />
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ </mask>
+
+ <mask id="mask-lock">
+ <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" fill="#fff"/>
+ <use xlink:href="#shape-lock-base" fill="#fff"/>
+ </mask>
+
+ <g id="warning-triangle">
+ <path fill="#fff" d="M10.5,5C9.8,5,9.1,5.4,8.8,6.2l-3.5,6.8c-0.4,0.7-0.4,1.4,0,2c0.4,0.6,1,1,1.8,1H14c0.8,0,1.4-0.4,1.8-1 c0.3-0.6,0.3-1.4,0-2l-3.5-6.8C11.9,5.4,11.2,5,10.5,5L10.5,5z"/>
+ <path fill="#ffbf00" d="M14.8,13.4l-3.5-6.8C11.2,6.2,10.9,6,10.5,6c-0.3,0-0.7,0.2-0.9,0.6l-3.5,6.8c-0.2,0.4-0.2,0.8,0,1.1C6.3,14.8,6.6,15,7,15 H14c0.4,0,0.7-0.2,0.9-0.5C15.1,14.2,15,13.8,14.8,13.4z"/>
+ <path fill="#fff" d="M10,8.5C10,8.2,10.2,8,10.5,8S11,8.2,11,8.5L10.8,11h-0.6L10,8.5z"/>
+ <circle fill="#fff" cx="10.5" cy="12.5" r=".75"/>
+ </g>
+ </defs>
+
+ <g id="icon">
+ <rect width="16" height="16" mask="url(#mask-lock)" class="fieldtext"/>
+ <use xlink:href="#warning-triangle"/>
+ </g>
+
+ <g id="icon-black">
+ <rect width="16" height="16" mask="url(#mask-lock)" class="black"/>
+ <use xlink:href="#warning-triangle"/>
+ </g>
+
+ <g id="icon-white">
+ <rect width="16" height="16" mask="url(#mask-lock)" class="white"/>
+ <use xlink:href="#warning-triangle"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/identity-block/connection-secure.svg b/browser/themes/shared/identity-block/connection-secure.svg
new file mode 100644
index 000000000..5dad8903b
--- /dev/null
+++ b/browser/themes/shared/identity-block/connection-secure.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ .icon-default {
+ fill: #4d9a26;
+ }
+ </style>
+
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="4" y="2" width="8" height="10" rx="4" ry="4" />
+ <rect id="shape-lock-clasp-inner" x="6" y="4" width="4" height="6" rx="2" ry="2" />
+ <rect id="shape-lock-base" x="3" y="7" width="10" height="7" rx="1" ry="1" />
+
+ <mask id="mask-clasp-cutout">
+ <rect width="16" height="16" fill="#000" />
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ </mask>
+ </defs>
+
+ <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" class="icon-default" />
+ <use xlink:href="#shape-lock-base" class="icon-default" />
+</svg>
diff --git a/browser/themes/shared/identity-block/icons.inc.css b/browser/themes/shared/identity-block/icons.inc.css
new file mode 100644
index 000000000..6cf300061
--- /dev/null
+++ b/browser/themes/shared/identity-block/icons.inc.css
@@ -0,0 +1,62 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+@selectorPrefix@#identity-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/identity-icon.svg#normal@iconVariant@);
+}
+
+@selectorPrefix@#identity-box:hover > #identity-icon:not(.no-hover)@selectorSuffix@,
+@selectorPrefix@#identity-box[open=true] > #identity-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/identity-icon.svg#hover@iconVariant@);
+}
+
+@selectorPrefix@#identity-box.grantedPermissions > #identity-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/identity-icon.svg#notice@iconVariant@);
+}
+
+@selectorPrefix@#identity-box.grantedPermissions:hover > #identity-icon:not(.no-hover)@selectorSuffix@,
+@selectorPrefix@#identity-box.grantedPermissions[open=true] > #identity-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/identity-icon.svg#notice-hover@iconVariant@);
+}
+
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.chromeUI > #identity-icon@selectorSuffix@ {
+ list-style-image: url(chrome://branding/content/identity-icons-brand.svg);
+}
+
+
+@selectorPrefix@#tracking-protection-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/tracking-protection-16.svg#enabled@iconVariant@);
+}
+
+@selectorPrefix@#tracking-protection-icon[state="loaded-tracking-content"]@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/tracking-protection-16.svg#disabled@iconVariant@);
+}
+
+
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.verifiedDomain > #connection-icon@selectorSuffix@,
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #connection-icon@selectorSuffix@,
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.mixedActiveBlocked > #connection-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/connection-secure.svg);
+ visibility: visible;
+}
+
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.certUserOverridden > #connection-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/connection-mixed-passive-loaded.svg#icon@iconVariant@);
+ visibility: visible;
+}
+
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.insecureLoginForms > #connection-icon@selectorSuffix@,
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.mixedActiveContent > #connection-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/connection-mixed-active-loaded.svg#icon@iconVariant@);
+ visibility: visible;
+}
+
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.weakCipher > #connection-icon@selectorSuffix@,
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContent > #connection-icon@selectorSuffix@,
+@selectorPrefix@#urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContentLoadedActiveBlocked > #connection-icon@selectorSuffix@ {
+ list-style-image: url(chrome://browser/skin/connection-mixed-passive-loaded.svg#icon@iconVariant@);
+ visibility: visible;
+}
diff --git a/browser/themes/shared/identity-block/identity-block.inc.css b/browser/themes/shared/identity-block/identity-block.inc.css
new file mode 100644
index 000000000..6fa40e9db
--- /dev/null
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -0,0 +1,163 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+%filter substitution
+
+%define selectorPrefix
+%define selectorSuffix
+%define iconVariant
+%include icons.inc.css
+
+%define selectorPrefix
+%define selectorSuffix :-moz-lwtheme
+%define iconVariant -black
+%include icons.inc.css
+
+#identity-box {
+ font-size: .9em;
+ padding: 3px 5px;
+ overflow: hidden;
+ /* The padding-left and padding-right transitions handle the delayed hiding of
+ the forward button when hovered. */
+ transition: padding-left, padding-right;
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity > #identity-icon-labels {
+ color: hsl(92,100%,30%);
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.chromeUI > #identity-icon-labels {
+%ifdef MOZ_OFFICIAL_BRANDING
+ color: rgb(229,115,0);
+%else
+ color: inherit;
+%endif
+}
+
+#identity-icon-labels:-moz-locale-dir(ltr) {
+ padding-left: 2px;
+}
+
+#identity-icon-labels:-moz-locale-dir(rtl) {
+ padding-right: 2px;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #identity-box {
+ padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 5px);
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #identity-box {
+ /* Forward button hiding is delayed when hovered, so we should use the same
+ delay for the identity box. We handle both horizontal paddings (for LTR and
+ RTL), the latter two delays here are for padding-left and padding-right. */
+ transition-delay: 100s, 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #identity-box {
+ /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
+ padding-inline-start: calc(var(--backbutton-urlbar-overlap) + 5.01px);
+}
+
+/* MAIN IDENTITY ICON */
+
+#identity-icon {
+ width: 16px;
+ height: 16px;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon {
+ opacity: .3;
+}
+
+#urlbar[actiontype="searchengine"] > #identity-box > #identity-icon {
+ -moz-image-region: inherit;
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
+ width: 16px;
+ height: 16px;
+}
+
+#urlbar[actiontype="extension"] > #identity-box > #identity-icon {
+ -moz-image-region: inherit;
+ list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg);
+ width: 16px;
+ height: 16px;
+}
+
+/* SHARING ICON */
+
+#sharing-icon {
+ width: 16px;
+ height: 16px;
+ margin-inline-start: -16px;
+ position: relative;
+ display: none;
+}
+
+#identity-box[sharing="camera"] > #sharing-icon {
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#camera-sharing");
+}
+
+#identity-box[sharing="microphone"] > #sharing-icon {
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#microphone-sharing");
+}
+
+#identity-box[sharing="screen"] > #sharing-icon {
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#screen-sharing");
+}
+
+#identity-box[sharing] > #sharing-icon {
+ display: -moz-box;
+ animation-delay: -1.5s;
+}
+
+#identity-box[sharing] > #identity-icon,
+#sharing-icon {
+ animation: 3s linear pulse infinite;
+}
+
+@keyframes pulse {
+ 0%, 16.66%, 83.33%, 100% {
+ opacity: 0;
+ }
+ 33.33%, 66.66% {
+ opacity: 1;
+ }
+}
+
+/* TRACKING PROTECTION ICON */
+
+#tracking-protection-icon {
+ width: 16px;
+ height: 16px;
+ margin-inline-start: 2px;
+ margin-inline-end: 0;
+}
+
+#tracking-protection-icon[animate] {
+ transition: margin-left 200ms ease-out, margin-right 200ms ease-out;
+}
+
+#tracking-protection-icon:not([state]) {
+ margin-inline-end: -18px;
+ pointer-events: none;
+ opacity: 0;
+ /* Only animate the shield in, when it disappears hide it immediately. */
+ transition: none;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box > #tracking-protection-icon {
+ visibility: collapse;
+}
+
+/* CONNECTION ICON */
+
+#connection-icon {
+ width: 16px;
+ height: 16px;
+ margin-inline-start: 2px;
+ visibility: collapse;
+}
+
diff --git a/browser/themes/shared/identity-block/identity-icon.svg b/browser/themes/shared/identity-block/identity-icon.svg
new file mode 100755
index 000000000..9f4c81655
--- /dev/null
+++ b/browser/themes/shared/identity-block/identity-icon.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ class="fieldtext"
+ width="16" height="16" viewBox="0 0 16 16">
+#include ../icon-colors.inc.svg
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill-rule: evenodd;
+ }
+ </style>
+
+ <defs>
+ <path id="glyph-normal" d="M128,193a7,7,0,1,1,7-7A7,7,0,0,1,128,193Zm0-13a6,6,0,1,0,6,6A6,6,0,0,0,128,180Zm0,10a1,1,0,0,1-1-1v-3a1,1,0,0,1,2,0v3A1,1,0,0,1,128,190Zm0-6a1,1,0,1,1,1-1A1,1,0,0,1,128,184Z" transform="translate(-120 -178)"/>
+ <path id="glyph-hover" d="M102,179a7,7,0,1,1-7,7A7,7,0,0,1,102,179Zm0,3a1,1,0,1,1-1,1A1,1,0,0,1,102,182Zm0,3a1,1,0,0,1,1,1v3a1,1,0,0,1-2,0v-3A1,1,0,0,1,102,185Z" transform="translate(-94 -178)"/>
+ <path id="glyph-notice" d="M133.5,202a2.5,2.5,0,1,1,2.5-2.5A2.5,2.5,0,0,1,133.5,202Zm-5.5,1a1,1,0,1,1,1-1A1,1,0,0,1,128,203Zm1,5a1,1,0,0,1-2,0v-3a1,1,0,0,1,2,0v3Zm-1-9a6.08,6.08,0,1,0,5.629,3.987,3.452,3.452,0,0,0,.984-0.185A6.9,6.9,0,0,1,135,205a7,7,0,1,1-7-7,6.9,6.9,0,0,1,2.2.387,3.452,3.452,0,0,0-.185.984A5.951,5.951,0,0,0,128,199Z" transform="translate(-120 -197)"/>
+ <path id="glyph-notice-hover" d="M107.5,202a2.5,2.5,0,1,1,2.5-2.5A2.5,2.5,0,0,1,107.5,202Zm0,1.039a3.5,3.5,0,0,0,1.125-.2,7.124,7.124,0,1,1-4.464-4.464,3.5,3.5,0,0,0-.2,1.125A3.54,3.54,0,0,0,107.5,203.039ZM102,201a1,1,0,1,0,1,1A1,1,0,0,0,102,201Zm1,4a1,1,0,0,0-2,0v3a1,1,0,0,0,2,0v-3Z" transform="translate(-94 -197)"/>
+ </defs>
+
+ <use id="normal" xlink:href="#glyph-normal"/>
+ <use id="hover" xlink:href="#glyph-hover"/>
+ <use id="notice" xlink:href="#glyph-notice"/>
+ <use id="notice-hover" xlink:href="#glyph-notice-hover"/>
+
+ <use class="black" id="normal-black" xlink:href="#glyph-normal"/>
+ <use class="black" id="hover-black" xlink:href="#glyph-hover"/>
+ <use class="black" id="notice-black" xlink:href="#glyph-notice"/>
+ <use class="black" id="notice-hover-black" xlink:href="#glyph-notice-hover"/>
+
+ <use class="white" id="normal-white" xlink:href="#glyph-normal"/>
+ <use class="white" id="hover-white" xlink:href="#glyph-hover"/>
+ <use class="white" id="notice-white" xlink:href="#glyph-notice"/>
+ <use class="white" id="notice-hover-white" xlink:href="#glyph-notice-hover"/>
+</svg>
diff --git a/browser/themes/shared/identity-block/tracking-protection-16.svg b/browser/themes/shared/identity-block/tracking-protection-16.svg
new file mode 100755
index 000000000..6517fe36b
--- /dev/null
+++ b/browser/themes/shared/identity-block/tracking-protection-16.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16" height="16" viewBox="0 0 16 16">
+#include ../icon-colors.inc.svg
+ <style>
+ g:not(:target) {
+ display: none;
+ }
+ </style>
+
+ <defs>
+ <path id="shape-shield-outer" d="M8,1L2.8,1.9C2.4,1.9,2,2.4,2,2.8C2,4,2,6.1,2.1,7.1c0.3,2.7,0.8,4,1.9,5.6C5.6,14.7,8,15,8,15s2.4-0.3,4-2.4 c1.2-1.5,1.7-2.9,1.9-5.6C14,6.1,14,4,14,2.8c0-0.5-0.4-0.9-0.8-1L8,1L8,1z"/>
+ <path id="shape-shield-inner" d="M8,2l5,0.8c0,2,0,3.5-0.1,4.1c-0.3,2.7-0.8,3.8-1.7,5.1c-1.1,1.5-2.7,1.9-3.2,2c-0.4-0.1-2.1-0.5-3.2-2 c-1-1.3-1.5-2.4-1.7-5.1C3,6.3,3,4.8,3,2.8L8,2"/>
+ <path id="shape-shield-detail" d="M8,13c-0.5-0.1-1.6-0.5-2.4-1.5c-0.9-1.2-1.3-2.1-1.5-4.6C4,6.3,4,5.2,4,3.7L8,3 V13z"/>
+
+ <mask id="mask-shield-cutout">
+ <rect width="16" height="16" fill="#000" />
+ <use xlink:href="#shape-shield-outer" fill="#fff" />
+ <use xlink:href="#shape-shield-inner" fill="#000" />
+ <use xlink:href="#shape-shield-detail" fill="#fff" />
+ </mask>
+
+ <mask id="mask-shield-cutout-disabled">
+ <rect width="16" height="16" fill="#000"/>
+ <use xlink:href="#shape-shield-outer" fill="#fff"/>
+ <use xlink:href="#shape-shield-inner" fill="#000"/>
+ <use xlink:href="#shape-shield-detail" fill="#fff"/>
+ <line x1="3" y1="15" x2="15" y2="3" stroke="#000" stroke-width="2"/>
+ </mask>
+
+ <line id="strike-through-red" x1="3" y1="14" x2="15" y2="2" stroke="#d92d21" stroke-width="2"/>
+ </defs>
+
+ <g id="enabled">
+ <use class="fieldtext" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout)"/>
+ </g>
+ <g id="enabled-black">
+ <use class="black" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout)"/>
+ </g>
+ <g id="enabled-white">
+ <use class="white" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout)"/>
+ </g>
+
+ <g id="disabled">
+ <use class="fieldtext" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout-disabled)"/>
+ <use xlink:href="#strike-through-red"/>
+ </g>
+ <g id="disabled-black">
+ <use class="black" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout-disabled)"/>
+ <use xlink:href="#strike-through-red"/>
+ </g>
+ <g id="disabled-white">
+ <use class="white" xlink:href="#shape-shield-outer" mask="url(#mask-shield-cutout-disabled)"/>
+ <use xlink:href="#strike-through-red"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/incontent-icons/cert-error.svg b/browser/themes/shared/incontent-icons/cert-error.svg
new file mode 100644
index 000000000..9d23267bb
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/cert-error.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<svg version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="45"
+ height="45"
+ viewBox="0 0 45 45">
+
+ <style>
+ .icon-default {
+ fill: #999;
+ }
+ </style>
+
+ <defs>
+ <rect id="shape-lock-clasp-outer" x="8" y="2" width="28" height="40" rx="14" ry="14" />
+ <rect id="shape-lock-clasp-inner" x="14" y="8" width="16" height="28" rx="8" ry="8" />
+ <rect id="shape-lock-base" x="4" y="18" width="36" height="24" rx="3" ry="3" />
+
+ <mask id="mask-clasp-cutout">
+ <rect width="48" height="48" fill="#000" />
+ <use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
+ <use xlink:href="#shape-lock-clasp-inner" fill="#000" />
+ <line x1="4" y1="38" x2="41" y2="3" stroke="#000" stroke-width="5.5" />
+ <line x1="4" y1="46" x2="41" y2="11" stroke="#000" stroke-width="5.5" />
+ <rect x="4" y="18" width="36" height="26" rx="6" ry="6" />
+ </mask>
+
+ <mask id="mask-base-cutout">
+ <rect width="45" height="45" fill="#000" />
+ <use xlink:href="#shape-lock-base" fill="#fff" />
+ <line x1="2.5" y1="41.5" x2="41" y2="5" stroke="#000" stroke-width="8.5" />
+ </mask>
+ </defs>
+
+ <use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" fill="#999" />
+ <use xlink:href="#shape-lock-base" mask="url(#mask-base-cutout)" fill="#999" />
+
+ <line x1="2.5" y1="41.5" x2="41" y2="5" stroke="#d92d21" stroke-width="5.5" />
+
+</svg>
diff --git a/browser/themes/shared/incontent-icons/icon-search-64.svg b/browser/themes/shared/incontent-icons/icon-search-64.svg
new file mode 100644
index 000000000..56ba96fff
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/icon-search-64.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
+ <ellipse cx="32" cy="34" rx="29.5" ry="30" fill="#000" fill-opacity=".1"/>
+ <circle cx="32" cy="32" r="30" fill="#58bf43"/>
+ <circle cx="32" cy="32" r="29.5" stroke="#41a833" stroke-width="1" fill="none"/>
+ <path d="M50,47.131L47.131,50,36.776,39.647a16.038,16.038,0,1,1,2.871-2.871ZM27,15A12,12,0,1,0,39,27,12,12,0,0,0,27,15Z" stroke="#41a833" stroke-width="2" fill="none"/>
+ <path d="M50,47.131L47.131,50,36.776,39.647a16.038,16.038,0,1,1,2.871-2.871ZM27,15A12,12,0,1,0,39,27,12,12,0,0,0,27,15Z" fill="#fff"/>
+ <circle cx="27" cy="27" r="13" fill="#fff" fill-opacity=".2"/>
+</svg>
diff --git a/browser/themes/shared/incontent-icons/session-restore.svg b/browser/themes/shared/incontent-icons/session-restore.svg
new file mode 100644
index 000000000..f545a3362
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/session-restore.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
+ <defs>
+ <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="10" x2="30" y2="50">
+ <stop offset="0" style="stop-color: #fedb00"/>
+ <stop offset="1" style="stop-color: #fcce00"/>
+ </linearGradient>
+ </defs>
+ <path fill="url(#gradient)" d="M49.316,42.867L33.829,12.7c-0.879-1.715-2.274-2.7-3.828-2.7c-1.554,0-2.949,0.985-3.829,2.702 L10.685,42.864c-0.869,1.69-0.913,3.482-0.121,4.909C11.35,49.187,12.817,50,14.591,50h30.82c1.772,0,3.24-0.81,4.023-2.224 C50.227,46.349,50.185,44.56,49.316,42.867z M32.176,22.304l-0.48,14.304h-3.424L27.76,22.304H32.176z M30,44.896 c-1.44,0-2.592-1.184-2.592-2.592c0-1.44,1.152-2.592,2.592-2.592c1.472,0,2.592,1.152,2.592,2.592 C32.592,43.712,31.472,44.896,30,44.896z"/>
+</svg>
diff --git a/browser/themes/shared/incontent-icons/tab-crashed.svg b/browser/themes/shared/incontent-icons/tab-crashed.svg
new file mode 100644
index 000000000..41470df5c
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/tab-crashed.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
+ <defs>
+ <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="12.85" x2="30" y2="47.15">
+ <stop offset="0" style="stop-color: #e63b2e"/>
+ <stop offset="1" style="stop-color: #c33931"/>
+ </linearGradient>
+ </defs>
+ <path fill-rule="evenodd" clip-rule="evenodd" fill="url(#gradient)" d="M49.048,17.648H29.004 c-2.289-0.016-2.809-1.142-3.165-2.401c-0.359-1.269-1.076-2.397-3.229-2.397c-5.775,0-5.42,0-6.167,0 c-2.153,0-2.87,1.127-3.229,2.397c-0.359,1.269-0.882,2.403-3.214,2.403h0.94c-0.519,0.008-0.937,0.433-0.937,0.958v27.583 c0,0.53,0.426,0.959,0.952,0.959h38.093c0.526,0,0.952-0.429,0.952-0.959V18.607C50,18.077,49.574,17.648,49.048,17.648z M18.441,27.932c0-2.119,1.705-3.837,3.809-3.837c2.103,0,3.809,1.718,3.809,3.837c0,2.119-1.705,3.837-3.809,3.837 C20.146,31.769,18.441,30.051,18.441,27.932z M36.717,41.83c-1.525,0-1.525-2.305-6.864-2.305c-5.339,0-5.339,2.305-6.864,2.305 c-0.842,0-1.526-0.512-1.526-1.537c0-1.024,1.271-3.842,8.39-3.842c7.119,0,8.39,2.804,8.39,3.842 C38.243,41.331,37.56,41.83,36.717,41.83z M37.485,31.769c-2.104,0-3.809-1.718-3.809-3.837c0-2.119,1.705-3.837,3.809-3.837 c2.104,0,3.809,1.718,3.809,3.837C41.294,30.051,39.588,31.769,37.485,31.769z"/>
+</svg>
diff --git a/browser/themes/shared/incontent-icons/welcome-back.svg b/browser/themes/shared/incontent-icons/welcome-back.svg
new file mode 100644
index 000000000..2fbea04b4
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/welcome-back.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
+ <defs>
+ <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="0" y1="30" x2="60" y2="30">
+ <stop offset="0" style="stop-color: #fd6b0c"/>
+ <stop offset="1" style="stop-color: #e65206"/>
+ </linearGradient>
+ </defs>
+ <path fill="url(#gradient)" d="M45.844,41.272c0.018-0.08,0.039-0.156,0.055-0.239c0.097-0.35,0.164-0.735,0.215-1.136 c0.375-0.835,0.747-2.172,0.5-3.936c-0.021-0.326-0.073-0.669-0.14-1.018c4.957-3.957,24.499-20.957,5.137-29.039 c0,0,4.23,6.9-2.898,13.92c-4.332,4.266-5.37,8.436-5.058,11.538c0,0,0.036,0.21,0.096,0.564c-1.62-2.178-5.652-4.53-14.256-2.022 c-8.736,2.544-7.476,4.518-9.234,3.606c0,0-0.93-2.166-3.564-4.452c0,0,0.726-4.416-0.42-5.472 c-1.146-1.062-2.628,2.244-5.346,3.432c-2.712,1.188-6.234,2.928-6.57,6.024l-3.39,2.328c0,0-1.446,0.684-0.81,1.32 c0.636,0.636,1.698,1.44,3.012,1.314c1.314-0.126,2.928-0.51,4.158-0.168c1.23,0.336,2.202,2.67,4.872,6.102 c0,0,2.142,4.542,6.936,5.412c0.036,0.024,0.072,0.054,0.108,0.078c1.404,1.026,4.584,3.336,5.148,3.834 c0.744,0.636,7.422,1.158,9.486,0.474c0,0-0.6-3.408-5.04-1.944c0,0-2.082,0.078-4.59-2.892c0.228-0.072,0.456-0.156,0.69-0.252 c1.056-0.402,2.184-0.966,3.39-1.728c0,0,1.542-0.774,3.846-1.356c0,0,2.497-0.555,4.376,0.455c2.542,1.829,6.483,2.442,12.58-0.566 c0,0,5.357,5.102,7.575,8.644c0,0,4.916-1.89-5.065-11.76C51.643,42.336,47.455,42.801,45.844,41.272z"/>
+</svg>
diff --git a/browser/themes/shared/incontent-icons/wifi.svg b/browser/themes/shared/incontent-icons/wifi.svg
new file mode 100644
index 000000000..39fd93623
--- /dev/null
+++ b/browser/themes/shared/incontent-icons/wifi.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg version="1.1"
+ xmlns="http://www.w3.org/2000/svg"
+ width="64"
+ height="64"
+ viewBox="0 0 64 64">
+
+ <style>
+ .gray {
+ fill: #797c80;
+ }
+ </style>
+
+ <defs>
+ <clipPath id="clip-path">
+ <polygon points="32 52.35 78.88 6.06 -14.88 6.06 32 52.35"/>
+ </clipPath>
+ </defs>
+
+ <circle class="gray" cx="32" cy="52" r="6"/>
+
+ <g clip-path="url('#clip-path')">
+ <path class="gray" d="M71.63,52A39.63,39.63,0,1,1,32,12.38,39.63,39.63,0,0,1,71.63,52ZM32,7.63A44.38,44.38,0,1,0,76.38,52,44.38,44.38,0,0,0,32,7.63Z"/>
+ <path class="gray" d="M47.75,52A15.75,15.75,0,1,1,32,36.25,15.75,15.75,0,0,1,47.75,52ZM32,31.65A20.35,20.35,0,1,0,52.35,52,20.35,20.35,0,0,0,32,31.65Z"/>
+ <path class="gray" d="M59.58,52A27.58,27.58,0,1,1,32,24.42,27.58,27.58,0,0,1,59.58,52ZM32,19.38A32.63,32.63,0,1,0,64.63,52,32.63,32.63,0,0,0,32,19.38Z"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/incontentprefs/containers.css b/browser/themes/shared/incontentprefs/containers.css
new file mode 100644
index 000000000..5446dccce
--- /dev/null
+++ b/browser/themes/shared/incontentprefs/containers.css
@@ -0,0 +1,32 @@
+/* Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../../components/contextualidentity/content/usercontext.css
+
+.container-header-links {
+ margin-block-end: 15px;
+}
+
+[data-identity-icon] {
+ margin: 0;
+ margin-inline-end: 16px;
+}
+
+#containersView {
+ border: 0 none;
+ background: transparent;
+}
+
+#containersView richlistitem {
+ margin: 0px;
+ margin-inline-end: 8px;
+ padding: 0;
+ padding-block-end: 8px;
+ border-block-end: 1px solid var(--in-content-header-border-color);
+}
+
+#containersView richlistitem:last-of-type {
+ border-block-end: 0 none;
+ margin-block-end: 8px;
+}
diff --git a/browser/themes/shared/incontentprefs/dialog.inc.css b/browser/themes/shared/incontentprefs/dialog.inc.css
new file mode 100644
index 000000000..27de416ab
--- /dev/null
+++ b/browser/themes/shared/incontentprefs/dialog.inc.css
@@ -0,0 +1,68 @@
+%if 0
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+dialog,
+window,
+prefpane,
+prefwindow,
+.windowDialog {
+ -moz-appearance: none;
+ background-color: #fbfbfb;
+ color: #424e5a;
+ margin: 0;
+ padding: 0;
+}
+
+.contentPane {
+ margin: 0;
+}
+
+tabbox {
+ /* override the rule in certManager.xul */
+ margin: 0 0 5px !important;
+}
+
+tabpanels {
+ font-size: 1em;
+}
+
+tabs,
+label,
+description,
+#useDocumentColors {
+ margin-right: 4px;
+ margin-left: 4px;
+}
+
+tree:not(#rejectsTree) {
+ min-height: 15em;
+}
+
+.actionButtons {
+ margin: 3px 0 0 !important;
+}
+
+caption {
+ padding-inline-start: 0;
+}
+
+groupbox {
+ font-size: 1em;
+ margin-top: 0;
+ margin-right: 4px;
+ margin-left: 4px;
+ padding-top: 0;
+ padding-bottom: 5px;
+}
+
+prefpane .groupbox-body {
+ padding: 0 0 5px;
+}
+
+groupbox description {
+ margin-right: 0;
+ margin-left: 0;
+}
diff --git a/browser/themes/shared/incontentprefs/favicon.ico b/browser/themes/shared/incontentprefs/favicon.ico
new file mode 100644
index 000000000..4d07d2b5d
--- /dev/null
+++ b/browser/themes/shared/incontentprefs/favicon.ico
Binary files differ
diff --git a/browser/themes/shared/incontentprefs/icons.svg b/browser/themes/shared/incontentprefs/icons.svg
new file mode 100644
index 000000000..2d0b67155
--- /dev/null
+++ b/browser/themes/shared/incontentprefs/icons.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill: #fbfbfb;
+ stroke: rgba(0,0,0,0.4);
+ stroke-width: .5px;
+ }
+ use[id$="-native"] {
+ fill: ThreeDHighlight;
+ }
+ </style>
+ <defs>
+ <g id="general-shape">
+ <path d="M18.97,3H5.03C3.914,3,3,3.914,3,5.03v13.94C3,20.086,3.914,21,5.03,21H18.97c1.117,0,2.03-0.914,2.03-2.03 V5.03C21,3.914,20.086,3,18.97,3z M5.35,19.326c-0.404,0-0.731-0.327-0.731-0.731c0-0.404,0.327-0.731,0.731-0.731 c0.404,0,0.731,0.327,0.731,0.731C6.081,19,5.754,19.326,5.35,19.326z M5.35,6.168c-0.403,0-0.731-0.328-0.731-0.731 c0-0.404,0.328-0.731,0.731-0.731c0.403,0,0.731,0.327,0.731,0.731C6.081,5.84,5.753,6.168,5.35,6.168z M15.243,14.035 c0,0.229-0.186,0.416-0.414,0.416c-0.229,0-0.415,0.186-0.415,0.414v3.347c0,0.228-0.185,0.384-0.414,0.384l-4.141,0.03 c-0.227,0-0.414-0.186-0.414-0.414v-3.347c0-0.228-0.185-0.414-0.414-0.414c-0.227,0-0.414-0.187-0.414-0.416V6.582 c0-0.229,0.187-0.414,0.414-0.414h5.798c0.228,0,0.414,0.185,0.414,0.414V14.035z M18.509,19.326c-0.404,0-0.731-0.327-0.731-0.731 c0-0.404,0.327-0.731,0.731-0.731c0.404,0,0.731,0.327,0.731,0.731C19.24,19,18.913,19.326,18.509,19.326z M18.509,6.168 c-0.404,0-0.731-0.328-0.731-0.731c0-0.404,0.327-0.731,0.731-0.731c0.404,0,0.731,0.327,0.731,0.731 C19.24,5.84,18.913,6.168,18.509,6.168z"/>
+ <path d="M12.757,7.824h-1.657c-0.456,0-0.828,0.373-0.828,0.828v8.282c0,0.456,0.373,0.828,0.828,0.828h1.657 c0.456,0,0.828-0.373,0.828-0.828V8.652C13.586,8.196,13.213,7.824,12.757,7.824z"/>
+ </g>
+ <g id="search-shape">
+ <path d="M2,10.018c0,4.43,3.585,8.019,8.009,8.019 c1.603,0,3.095-0.473,4.348-1.285l4.806,4.81c0.58,0.583,1.523,0.583,2.105,0l0.296-0.297c0.582-0.583,0.582-1.527,0-2.11 l-4.808-4.814c0.8-1.247,1.265-2.73,1.265-4.323c0-4.43-3.587-8.018-8.012-8.018C5.585,2,2,5.589,2,10.018z M5.104,10.021 c0-2.716,2.196-4.915,4.906-4.915c2.71,0,4.908,2.199,4.908,4.915c0,2.712-2.198,4.911-4.908,4.911 C7.3,14.931,5.104,12.732,5.104,10.021z"/>
+ </g>
+ <g id="content-shape">
+ <path d="M16.286,2H5.571C4.388,2,3.429,2.96,3.429,4.143v15.714 C3.429,21.04,4.388,22,5.571,22h12.857c1.185,0,2.143-0.96,2.143-2.143V6.286L16.286,2z M18.945,19.223c0,0.22-0.18,0.4-0.4,0.4 h-13.2c-0.22,0-0.4-0.18-0.4-0.4v-0.846c0-0.22,0.18-0.4,0.4-0.4h13.2c0.22,0,0.4,0.18,0.4,0.4V19.223z M18.945,15.223 c0,0.22-0.18,0.4-0.4,0.4h-13.2c-0.22,0-0.4-0.18-0.4-0.4v-0.846c0-0.22,0.18-0.4,0.4-0.4h13.2c0.22,0,0.4,0.18,0.4,0.4V15.223z M18.945,11.229c0,0.22-0.18,0.4-0.4,0.4h-13.2c-0.22,0-0.4-0.18-0.4-0.4v-0.846c0-0.22,0.18-0.4,0.4-0.4h13.2 c0.22,0,0.4,0.18,0.4,0.4V11.229z M14.833,7.707v-4.65l4.65,4.65H14.833z"/>
+ </g>
+ <g id="applications-shape">
+ <path d="M16.673,8.914C16.089,4.122,13.248,1,12,1c-1.25,0-3.986,3.122-4.767,7.914l-3.122,3.131v7.889h2.268 l2.978-3.436c0.28,0.29,0.737,1.666,1.065,1.858h3.155c0.331-0.193,0.789-1.569,1.068-1.858l3.123,3.436h2.12v-7.84L16.673,8.914z M12.042,8.735c-0.604,0-1.279,0.06-1.818,0.165c0.478-1.453,1.345-3.117,1.781-3.117c0.435,0,1.301,1.655,1.775,3.1 C13.265,8.789,12.615,8.735,12.042,8.735z M12.524,19.145c0.076,0.196,0.119,0.602,0.119,0.86c0,0.66-0.524,1.074-0.687,1.074 c-0.163,0-0.615-0.414-0.615-1.074c0-0.257,0.045-0.664,0.119-0.86h-0.754c-0.089,0.345-0.39,1.005-0.39,1.408 c0,1.458,1.328,2.447,1.686,2.447c0.359,0,1.686-0.951,1.686-2.407c0-0.404-0.301-1.103-0.388-1.449H12.524z"/>
+ </g>
+ <g id="privacy-shape">
+ <path d="M21.632,9.541c-0.083,1.403,0.246,3.079-1.597,5.498 c-1.965,2.578-3.914,2.594-4.284,2.575c-2.249-0.117-2.502-1.875-3.792-1.875c-1.13,0-2.012,1.745-3.711,1.836 c-0.37,0.02-2.319,0.042-4.284-2.536c-1.841-2.419-1.514-4.095-1.597-5.498C2.287,8.138,2,6.618,2,6.618s0.887,0.895,2.033,0.973 C5.179,7.67,5.394,7.191,7.811,6.501C10.424,5.752,12,8.814,12,8.814s1.776-3.016,4.189-2.313c2.414,0.7,2.515,1.169,3.661,1.091 C20.996,7.513,22,6.618,22,6.618S21.713,8.138,21.632,9.541z M8.117,10.129c-1.429-0.314-2.028,0.223-2.642,0.451 c-0.534,0.202-1.02,0.264-1.02,0.264s0.083,0.819,1.515,1.521c1.432,0.703,4.37,0.338,4.37,0.338S10.651,10.687,8.117,10.129z M18.525,10.58c-0.612-0.228-1.212-0.765-2.642-0.451c-2.534,0.558-2.223,2.573-2.223,2.573s2.938,0.365,4.37-0.338 c1.432-0.702,1.515-1.521,1.515-1.521S19.059,10.782,18.525,10.58z"/>
+ </g>
+ <g id="security-shape">
+ <path d="M18.909,9.783h-0.863V8.086C18.046,4.725,15.339,2,12,2 C8.661,2,5.954,4.725,5.954,8.086v1.697H5.091c-0.955,0-1.728,0.779-1.728,1.739v8.738c0,0.961,0.773,1.74,1.728,1.74h13.818 c0.954,0,1.728-0.779,1.728-1.74v-8.738C20.637,10.562,19.863,9.783,18.909,9.783z M8.545,8.086c0-1.92,1.547-3.478,3.455-3.478 c1.908,0,3.455,1.557,3.455,3.478v1.697h-6.91V8.086z M5.181,16.092l-0.909-1.2v-2.284l2.728,3.483H5.181z M8.818,16.092 l-2.773-3.657h1.727l2.864,3.657H8.818z M12,16.092l-2.773-3.657h1.727l2.864,3.657H12z M15.637,16.092l-2.773-3.657h1.727 l2.864,3.657H15.637z M19.728,16.092h-0.455l-2.773-3.657h1.727l1.501,1.916V16.092z"/>
+ </g>
+ <g id="sync-shape">
+ <path d="M17.024,3.351 c-0.562,0.331 -1.311,0.879 -1.821,1.698 -0.367,0.592 -0.752,1.288 -1.08,1.914 0.987,0.413 1.862,1.095 2.476,2.029 0.614,0.957 0.929,2.122 0.83,3.351 -0.201,1.787 -1.359,3.433 -3.046,4.36 -0.696,-0.774 -1.951,-2.945 -1.951,-2.945 -0.007,0.007 -0.004,2.556 -0.871,4.334 -0.573,1.184 -1.24,2.202 -2.305,2.995 1.431,0.51 2.915,0.886 4.282,0.909 l 0.162,0.002 c 2.99,0.021 5.844,-0.48 5.844,-0.48 0,0 -1.236,-0.802 -1.808,-1.346 1.86,-1.072 3.111,-2.791 3.634,-4.708 0.283,-0.759 0.478,-1.566 0.57,-2.409 C 22.383,9.011 20.33,5.278 17.024,3.351 Z M 6.569,12.302 C 6.526,10.271 7.755,8.327 9.644,7.29 c 0.696,0.774 2.32,2.899 2.32,2.899 0,0 -0.064,-5.157 1.657,-7.973 -6.097,-0.668 -9.69,0.443 -9.69,0.443 0,0 1.763,0.607 2.333,1.136 C 6.122,3.891 5.984,3.992 5.85,4.096 4.4,5.064 3.368,6.449 2.825,7.994 2.436,8.892 2.171,9.863 2.06,10.887 1.622,14.886 3.629,18.572 6.871,20.515 7.39,20.124 7.975,19.631 8.61,18.983 9.189,18.389 9.647,17.682 10.021,16.967 8.082,16.208 6.714,14.404 6.569,12.302 Z"/>
+ </g>
+ <g id="advanced-shape">
+ <path d="M19.173,16.163c0.004,0.04,0.007,0.08,0.007,0.121c0,1.748-3.197,3.165-7.14,3.165 c-3.943,0-7.14-1.417-7.14-3.165c0 -0.037,0.003-0.073,0.006-0.109C3.11,16.572,2,17.243,2,18.341C2,20.362,6.477,22,12,22 c5.523,0,10-1.638,10-3.659 C22,17.22,20.922,16.553,19.173,16.163z"/>
+ <path d="M18.224,15.979c0.006-0.11-0.018-0.285-0.054-0.39c0,0-0.762-2.205-1.176-3.403 c-0.624-1.807-2.112-6.139-2.112-6.139c-0.036-0.104-0.031-0.273,0.01-0.376l0.497-1.234c0.041-0.102,0.116-0.266,0.166-0.364 l0.986-1.942c0.05-0.098,0.013-0.133-0.081-0.077L9.965,5.871c-0.095,0.056-0.203,0.186-0.24,0.29c0,0-0.252,0.7-0.412,1.144 C8.64,9.173,7.968,11.04,7.296,12.908c-0.26,0.723-0.52,1.446-0.78,2.168c-0.056,0.156-0.112,0.311-0.168,0.466 c-0.093,0.26-0.049,0.617,0.032,0.881c0.237,0.763,1.001,1.189,1.708,1.435c0.611,0.213,1.254,0.328,1.895,0.403 c0.895,0.105,1.805,0.14,2.706,0.112c1.356-0.041,2.767-0.261,4.004-0.846c0.429-0.203,0.854-0.459,1.174-0.816 c0.121-0.135,0.226-0.287,0.297-0.455C18.215,16.134,18.224,15.979,18.224,15.979z M14.063,16.131 c0.019,0.108-0.046,0.156-0.143,0.104l-1.466-0.772c-0.097-0.052-0.257-0.052-0.354,0l-1.466,0.773 c-0.097,0.052-0.162,0.004-0.143-0.104l0.28-1.636c0.019-0.109-0.031-0.261-0.109-0.338l-1.186-1.158 c-0.079-0.077-0.054-0.153,0.055-0.169l1.638-0.239c0.109-0.016,0.238-0.11,0.286-0.209l0.733-1.488 c0.049-0.099,0.128-0.099,0.177,0l0.733,1.488c0.049,0.099,0.178,0.193,0.286,0.209l1.639,0.239 c0.109,0.016,0.134,0.092,0.055,0.169l-1.186,1.158c-0.079,0.077-0.128,0.229-0.109,0.338L14.063,16.131z"/>
+ </g>
+ </defs>
+ <use id="general" xlink:href="#general-shape"/>
+ <use id="general-native" xlink:href="#general-shape"/>
+ <use id="search" xlink:href="#search-shape"/>
+ <use id="search-native" xlink:href="#search-shape"/>
+ <use id="content" xlink:href="#content-shape"/>
+ <use id="content-native" xlink:href="#content-shape"/>
+ <use id="applications" xlink:href="#applications-shape"/>
+ <use id="applications-native" xlink:href="#applications-shape"/>
+ <use id="privacy" xlink:href="#privacy-shape"/>
+ <use id="privacy-native" xlink:href="#privacy-shape"/>
+ <use id="security" xlink:href="#security-shape"/>
+ <use id="security-native" xlink:href="#security-shape"/>
+ <use id="sync" xlink:href="#sync-shape"/>
+ <use id="sync-native" xlink:href="#sync-shape"/>
+ <use id="advanced" xlink:href="#advanced-shape"/>
+ <use id="advanced-native" xlink:href="#advanced-shape"/>
+</svg>
diff --git a/browser/themes/shared/incontentprefs/preferences.inc.css b/browser/themes/shared/incontentprefs/preferences.inc.css
new file mode 100644
index 000000000..577baa6ed
--- /dev/null
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -0,0 +1,591 @@
+%if 0
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+@namespace html "http://www.w3.org/1999/xhtml";
+
+#mainPrefPane {
+ max-width: 800px;
+ padding: 0;
+ font: message-box;
+ font-size: 1.25rem;
+}
+
+* {
+ -moz-user-select: text;
+}
+
+button,
+treecol {
+ /* override the * rule */
+ -moz-user-select: none;
+}
+
+#engineList treechildren::-moz-tree-image(engineShown, checked),
+#blocklistsTree treechildren::-moz-tree-image(selectionCol, checked) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check");
+ width: 21px;
+ height: 21px;
+}
+
+#engineList treechildren::-moz-tree-image(engineShown, checked, selected),
+#blocklistsTree treechildren::-moz-tree-image(selectionCol, checked, selected) {
+ list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
+}
+
+#engineList treechildren::-moz-tree-row,
+#blocklistsTree treechildren::-moz-tree-row {
+ min-height: 36px;
+}
+
+#selectionCol {
+ min-width: 26px;
+}
+
+/* Category List */
+
+#categories {
+ max-height: 100vh;
+}
+
+#categories > scrollbox {
+ overflow-x: hidden !important;
+}
+
+/**
+ * We want the last category to always have non-0 getBoundingClientRect().bottom
+ * so we can use the value to figure out the max-height of the list in
+ * preferences.js, so use collapse instead of display: none; if it's hidden
+ */
+#categories > .category[hidden="true"] {
+ display: -moz-box;
+ visibility: collapse;
+}
+
+#category-general > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#general");
+}
+
+#category-search > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#search");
+}
+
+#category-content > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#content");
+}
+
+#category-application > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#applications");
+}
+
+#category-privacy > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#privacy");
+}
+
+#category-security > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#security");
+}
+
+#category-sync > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#sync");
+}
+
+#category-advanced > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#advanced");
+}
+
+@media (max-width: 800px) {
+ .category-name {
+ display: none;
+ }
+}
+
+/* header */
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.header[hidden=true] {
+ display: none;
+}
+
+#header-advanced {
+ border-bottom: none;
+ padding-bottom: 0;
+}
+
+/* General Pane */
+
+#startupTable {
+ border-collapse: collapse;
+}
+
+#startupTable > tr > td {
+ padding: 0; /* remove the padding from html.css */
+}
+
+#startupTable > tr:not(:first-child) > td {
+ padding-top: 0.5em; /* add a spacing between the rows */
+}
+
+#startupTable > tr > .label-cell {
+ text-align: end;
+ width: 0; /* make the column as small as possible */
+}
+
+#startupTable > tr > .label-cell > label {
+ white-space: nowrap;
+}
+
+#startupTable > tr > .content-cell > menulist,
+#startupTable > tr > .content-cell > textbox {
+ width: calc(100% - 8px);
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+#startupTable > tr > .homepage-buttons {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+#startupTable > tr > .homepage-buttons > .content-cell-item {
+ flex-grow: 1;
+}
+
+#useFirefoxSync {
+ font-size: 90%;
+ margin-inline-end: 8px !important;
+}
+
+#getStarted {
+ font-size: 90%;
+}
+
+#isNotDefaultLabel {
+ font-weight: bold;
+}
+
+#downloadFolder {
+ margin-inline-start: 0;
+}
+
+/* Content pane */
+#playDRMContentLink {
+ /* Line up with the buttons in the other grid bits: */
+ margin-left: 4px !important;
+ margin-right: 4px !important;
+}
+
+#notificationsPolicyLearnMore {
+ margin-inline-start: 1.5em !important;
+}
+
+#defaultFontSizeLabel {
+ /* !important needed to override common !important rule */
+ margin-inline-start: 4px !important;
+}
+
+/* Applications Pane Styles */
+
+#applicationsContent {
+ padding: 15px 0;
+}
+
+#filter {
+ margin-inline-start: 0;
+}
+
+#handlersView {
+ height: 25em;
+}
+
+#handlersView > richlistitem {
+ min-height: 36px !important;
+}
+
+.typeIcon {
+ margin-inline-start: 10px !important;
+ margin-inline-end: 9px !important;
+}
+
+.actionIcon {
+ margin-inline-start: 11px !important;
+ margin-inline-end: 8px !important;
+}
+
+.actionsMenu {
+ min-height: 36px;
+}
+
+.actionsMenu > menupopup > menuitem {
+ padding-inline-start: 10px !important;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ margin-inline-end: 8px !important;
+}
+
+/* Privacy pane */
+
+#trackingProtectionPBMLearnMore,
+#trackingProtectionLearnMore,
+#browserContainersLearnMore {
+ margin-inline-start: 1.5em !important;
+ margin-top: 0;
+ font-weight: normal;
+}
+
+.doNotTrackLearnMore {
+ margin-inline-start: calc(1em + 30px);
+ margin-bottom: 1em;
+ font-weight: normal;
+}
+
+.doNotTrackLearnMore > label {
+ font-size: 1em !important;
+ margin-left: 0;
+}
+
+/* Collapse the non-active vboxes in decks to use only the height the
+ active vbox needs */
+#historyPane:not([selectedIndex="1"]) > #historyDontRememberPane,
+#historyPane:not([selectedIndex="2"]) > #historyCustomPane,
+#weavePrefsDeck:not([selectedIndex="1"]) > #hasAccount,
+#weavePrefsDeck:not([selectedIndex="2"]) > #needsUpdate,
+#weavePrefsDeck:not([selectedIndex="3"]) > #noFxaAccount,
+#weavePrefsDeck:not([selectedIndex="4"]) > #hasFxaAccount,
+#fxaLoginStatus:not([selectedIndex="1"]) > #fxaLoginUnverified,
+#fxaLoginStatus:not([selectedIndex="2"]) > #fxaLoginRejected {
+ visibility: collapse;
+}
+
+/* XXX This style is for bug 740213 and should be removed once that
+ bug has a solution. */
+description > html|a {
+ cursor: pointer;
+}
+
+#weavePrefsDeck > vbox > label,
+#weavePrefsDeck > vbox > groupbox,
+#weavePrefsDeck > vbox > description,
+#weavePrefsDeck > vbox > #pairDevice > label,
+#weavePrefsDeck > #needsUpdate > hbox > #loginError,
+#weavePrefsDeck > #hasFxaAccount > vbox > label,
+#weavePrefsDeck > #hasFxaAccount > hbox:not(#tosPP-normal) > label {
+ /* no margin-inline-start for elements at the beginning of a line */
+ margin-inline-start: 0;
+}
+
+#tabsElement {
+ margin-inline-end: 4px; /* add the 4px end-margin of other elements */
+}
+
+#telemetryLearnMore,
+#FHRLearnMore,
+#crashReporterLearnMore {
+ /* provide some margin between the links and the label text */
+ /* !important is needed to override the rules defined in common.css */
+ margin-inline-start: 20px !important;
+ /* center the links */
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+
+.indent {
+ /* !important needed to override margin-inline-start:0 !important; rule
+ define in common.css for labels */
+ margin-inline-start: 33px !important;
+}
+
+.text-link {
+ margin-bottom: 0;
+}
+
+#showUpdateHistory {
+ margin-inline-start: 0;
+}
+
+/**
+ * Dialog
+ */
+
+#dialogOverlay {
+ background-color: rgba(0,0,0,0.5);
+ visibility: hidden;
+}
+
+#dialogBox {
+ background-color: #fbfbfb;
+ background-clip: content-box;
+ color: #424e5a;
+ font-size: 14px;
+ /* `transparent` will use the dialogText color in high-contrast themes and
+ when page colors are disabled */
+ border: 1px solid transparent;
+ border-radius: 3.5px;
+ box-shadow: 0 2px 6px 0 rgba(0,0,0,0.3);
+ display: -moz-box;
+ margin: 0;
+ padding: 0;
+}
+
+#dialogBox[resizable="true"] {
+ resize: both;
+ overflow: hidden;
+ min-height: 20em;
+ min-width: 66ch;
+}
+
+#dialogBox > .groupbox-title {
+ padding: 3.5px 0;
+ background-color: #F1F1F1;
+ border-bottom: 1px solid #C1C1C1;
+}
+
+#dialogTitle {
+ text-align: center;
+ -moz-user-select: none;
+}
+
+.close-icon {
+ background-color: transparent !important;
+ border: none;
+ box-shadow: none;
+ padding: 0;
+ height: auto;
+ min-height: 16px;
+ min-width: 0;
+}
+
+#dialogBox > .groupbox-body {
+ -moz-appearance: none;
+ padding: 20px;
+}
+
+#dialogFrame {
+ -moz-box-flex: 1;
+ /* Default dialog dimensions */
+ width: 66ch;
+}
+
+.largeDialogContainer.doScroll {
+ overflow-y: auto;
+ -moz-box-flex: 1;
+}
+
+/**
+ * End Dialog
+ */
+
+/**
+ * Font dialog menulist fixes
+ */
+
+#defaultFontType,
+#serif,
+#sans-serif,
+#monospace {
+ min-width: 30ch;
+}
+
+/**
+ * Sync
+ */
+
+#fxaProfileImage {
+ max-width: 60px;
+ border-radius: 50%;
+ list-style-image: url(chrome://browser/skin/fxa/default-avatar.svg);
+ margin-inline-end: 15px;
+ image-rendering: -moz-crisp-edges;
+}
+
+#fxaLoginStatus[hasName] #fxaProfileImage {
+ max-width: 80px;
+}
+
+#fxaProfileImage.actionable {
+ cursor: pointer;
+}
+
+#fxaProfileImage.actionable:hover {
+ box-shadow: 0px 0px 0px 1px #0095DD;
+}
+
+#fxaProfileImage.actionable:hover:active {
+ box-shadow: 0px 0px 0px 1px #ff9500;
+}
+
+#noFxaAccount {
+ /* Overriding the margins from the base preferences.css theme file.
+ These overrides can be simplified by fixing bug 1027174 */
+ margin: 0;
+ padding-top: 15px;
+}
+
+#fxaContentWrapper {
+ -moz-box-flex: 1;
+}
+
+#noFxaGroup {
+ -moz-box-flex: 1;
+ margin: 0;
+}
+
+#fxaContentWrapper {
+ padding-right: 15px;
+}
+
+#noFxaGroup > vbox,
+#fxaGroup {
+ -moz-box-align: start;
+}
+
+#fxaSyncEngines > vbox:first-child {
+ margin-right: 80px;
+}
+
+#fxaSyncComputerName {
+ margin-inline-start: 0px;
+ -moz-box-flex: 1;
+}
+
+#tosPP-small-ToS {
+ margin-bottom: 14px;
+}
+
+#noFxaCaption {
+ font-weight: bold;
+ margin-bottom: 11px;
+}
+
+.fxaSyncIllustration {
+ margin-top: 35px;
+}
+
+#syncOptions caption {
+ margin-bottom: 11px;
+}
+
+#fxaDeviceName {
+ margin-bottom: 27.5px;
+}
+
+#noFxaDescription {
+ margin-bottom: 20px !important;
+}
+
+.separator {
+ border-bottom: 1px solid var(--in-content-header-border-color);
+}
+
+.fxaAccountBox {
+ border: 1px solid #D1D2D3;
+ border-radius: 5px;
+ padding: 14px 20px 14px 14px;
+}
+
+#signedOutAccountBoxTitle {
+ font-weight: bold;
+}
+
+.fxaAccountBoxButtons {
+ margin-bottom: 0 !important;
+ margin-top: 11px;
+ display: flex;
+ align-items: center;
+}
+
+.fxaAccountBoxButtons > * {
+ -moz-box-flex: 1;
+}
+
+.fxaAccountBoxButtons > button {
+ text-align: center;
+ padding-left: 11px;
+ padding-right: 11px;
+ margin: 0;
+ min-width: 0;
+}
+
+.fxaAccountBoxButtons > button:first-child {
+ margin-right: 14px !important;
+}
+
+.fxaSyncIllustration {
+ width: 231px;
+ list-style-image: url(chrome://browser/skin/fxa/sync-illustration.png)
+}
+
+#fxaLoginStatus[hasName] #fxaEmailAddress1 {
+ font-size: 1.1rem;
+}
+
+#fxaEmailAddress1,
+#fxaEmailAddress2,
+#fxaEmailAddress3 {
+ word-break: break-all;
+}
+
+.fxaFirefoxLogo {
+ list-style-image: url(chrome://browser/skin/fxa/logo.png);
+ max-width: 64px;
+ margin-inline-end: 14px;
+}
+
+.fxaMobilePromo {
+ margin-bottom: 20px;
+ margin-top: 25px;
+}
+
+#fxaLoginRejectedWarning {
+ list-style-image: url(chrome://browser/skin/warning.svg);
+ filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+ margin: 4px 8px 0px 0px;
+}
+
+#syncOptions {
+ margin-bottom: 27.5px;
+}
+
+.androidLink {
+ background-image: url("chrome://browser/skin/fxa/android.png");
+}
+
+.iOSLink {
+ background-image: url("chrome://browser/skin/fxa/ios.png");
+}
+
+.androidLink,
+.iOSLink {
+ margin: 0 0 0 2px;
+ padding-left: 28px;
+ padding-top: 6px;
+ height: 28px;
+ background-repeat: no-repeat;
+ background-size: 24px 28px;
+}
+
+#tosPP-small {
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+@media (min-resolution: 1.1dppx) {
+ .androidLink {
+ background-image: url("chrome://browser/skin/fxa/android@2x.png");
+ }
+ .iOSLink {
+ background-image: url("chrome://browser/skin/fxa/ios@2x.png");
+ }
+ .fxaSyncIllustration {
+ list-style-image: url(chrome://browser/skin/fxa/sync-illustration@2x.png)
+ }
+ .fxaFirefoxLogo {
+ list-style-image: url(chrome://browser/skin/fxa/logo@2x.png);
+ }
+}
diff --git a/browser/themes/shared/incontentprefs/search.css b/browser/themes/shared/incontentprefs/search.css
new file mode 100644
index 000000000..f3c206b41
--- /dev/null
+++ b/browser/themes/shared/incontentprefs/search.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/. */
+
+ #defaultEngine {
+ margin-inline-start: 0;
+ }
+
+#defaultEngine > .menulist-label-box > .menulist-icon {
+ height: 16px;
+}
+
+/* work around a display: none in Linux's menu.css, see bug 1112310 */
+.searchengine-menuitem > .menu-iconic-left {
+ display: -moz-box;
+}
+
+#engineList {
+ margin: .5em 0;
+}
+
+#engineList treechildren::-moz-tree-image(engineName) {
+ margin-inline-end: 10px;
+ margin-inline-start: 1px;
+ width: 16px;
+ height: 16px;
+}
+
+#engineList treechildren::-moz-tree-drop-feedback {
+ background-color: Highlight;
+ width: 10000px; /* 100% doesn't work; 10k is hopefully larger than any window
+ we may have, overflow isn't visible. */
+ height: 2px;
+ margin-inline-start: 0;
+}
+
+#engineShown {
+ min-width: 26px;
+}
+
+#addEnginesBox {
+ margin-bottom: 1em;
+}
+
+#removeEngineButton,
+#restoreDefaultSearchEngines {
+ margin-right: 0;
+ margin-left: 0;
+}
diff --git a/browser/themes/shared/info.svg b/browser/themes/shared/info.svg
new file mode 100644
index 000000000..9ff38dd68
--- /dev/null
+++ b/browser/themes/shared/info.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <circle fill="#00a1f2" cx="8" cy="8" r="8" />
+ <circle fill="#fff" cx="8" cy="4" r="1.25" />
+ <rect x="7" y="7" width="2" height="6" rx="1" ry="1" fill="#fff" />
+</svg>
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
new file mode 100644
index 000000000..f99f8de3c
--- /dev/null
+++ b/browser/themes/shared/jar.inc.mn
@@ -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/.
+
+# This is not a complete / proper jar manifest. It is included by the
+# actual theme-specific manifests, so that shared resources need only
+# be specified once. As a result, the source file paths are relative
+# to the location of the actual manifest.
+
+ skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css)
+ skin/classic/browser/blockedSite.css (../shared/blockedSite.css)
+ skin/classic/browser/error-pages.css (../shared/error-pages.css)
+* skin/classic/browser/aboutProviderDirectory.css (../shared/aboutProviderDirectory.css)
+* skin/classic/browser/aboutSessionRestore.css (../shared/aboutSessionRestore.css)
+ skin/classic/browser/aboutSocialError.css (../shared/aboutSocialError.css)
+ skin/classic/browser/aboutTabCrashed.css (../shared/aboutTabCrashed.css)
+ skin/classic/browser/aboutWelcomeBack.css (../shared/aboutWelcomeBack.css)
+ skin/classic/browser/content-contextmenu.svg (../shared/content-contextmenu.svg)
+ skin/classic/browser/addons/addon-install-blocked.svg (../shared/addons/addon-install-blocked.svg)
+ skin/classic/browser/addons/addon-install-confirm.svg (../shared/addons/addon-install-confirm.svg)
+ skin/classic/browser/addons/addon-install-downloading.svg (../shared/addons/addon-install-downloading.svg)
+ skin/classic/browser/addons/addon-install-error.svg (../shared/addons/addon-install-error.svg)
+ skin/classic/browser/addons/addon-install-installed.svg (../shared/addons/addon-install-installed.svg)
+ skin/classic/browser/addons/addon-install-restart.svg (../shared/addons/addon-install-restart.svg)
+ skin/classic/browser/addons/addon-install-warning.svg (../shared/addons/addon-install-warning.svg)
+* skin/classic/browser/addons/addon-install-anchor.svg (../shared/addons/addon-install-anchor.svg)
+* skin/classic/browser/controlcenter/arrow-subview.svg (../shared/controlcenter/arrow-subview.svg)
+* skin/classic/browser/controlcenter/arrow-subview-back.svg (../shared/controlcenter/arrow-subview-back.svg)
+* skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg)
+* skin/classic/browser/controlcenter/connection.svg (../shared/controlcenter/connection.svg)
+* skin/classic/browser/controlcenter/mcb-disabled.svg (../shared/controlcenter/mcb-disabled.svg)
+* skin/classic/browser/controlcenter/permissions.svg (../shared/controlcenter/permissions.svg)
+* skin/classic/browser/controlcenter/tracking-protection.svg (../shared/controlcenter/tracking-protection.svg)
+ skin/classic/browser/controlcenter/warning-gray.svg (../shared/controlcenter/warning-gray.svg)
+ skin/classic/browser/controlcenter/warning-yellow.svg (../shared/controlcenter/warning-yellow.svg)
+ skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico)
+ skin/classic/browser/customizableui/customize-illustration.png (../shared/customizableui/customize-illustration.png)
+ skin/classic/browser/customizableui/customize-illustration@2x.png (../shared/customizableui/customize-illustration@2x.png)
+ skin/classic/browser/customizableui/customize-illustration-rtl.png (../shared/customizableui/customize-illustration-rtl.png)
+ skin/classic/browser/customizableui/customize-illustration-rtl@2x.png (../shared/customizableui/customize-illustration-rtl@2x.png)
+ skin/classic/browser/customizableui/info-icon-customizeTip.png (../shared/customizableui/info-icon-customizeTip.png)
+ skin/classic/browser/customizableui/info-icon-customizeTip@2x.png (../shared/customizableui/info-icon-customizeTip@2x.png)
+ skin/classic/browser/customizableui/menuPanel-customizeFinish.png (../shared/customizableui/menuPanel-customizeFinish.png)
+ skin/classic/browser/customizableui/menuPanel-customizeFinish@2x.png (../shared/customizableui/menuPanel-customizeFinish@2x.png)
+ skin/classic/browser/customizableui/panelarrow-customizeTip.png (../shared/customizableui/panelarrow-customizeTip.png)
+ skin/classic/browser/customizableui/panelarrow-customizeTip@2x.png (../shared/customizableui/panelarrow-customizeTip@2x.png)
+ skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png)
+ skin/classic/browser/customizableui/subView-arrow-back-inverted@2x.png (../shared/customizableui/subView-arrow-back-inverted@2x.png)
+ skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png (../shared/customizableui/subView-arrow-back-inverted-rtl.png)
+ skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl@2x.png (../shared/customizableui/subView-arrow-back-inverted-rtl@2x.png)
+ skin/classic/browser/customizableui/whimsy.png (../shared/customizableui/whimsy.png)
+ skin/classic/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png)
+ skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css)
+ skin/classic/browser/downloads/download-blocked.svg (../shared/downloads/download-blocked.svg)
+ skin/classic/browser/downloads/menubutton-dropmarker.svg (../shared/downloads/menubutton-dropmarker.svg)
+ skin/classic/browser/downloads/download-summary.svg (../shared/downloads/download-summary.svg)
+ skin/classic/browser/drm-icon.svg (../shared/drm-icon.svg)
+ skin/classic/browser/filters.svg (../shared/filters.svg)
+ skin/classic/browser/fullscreen/insecure.svg (../shared/fullscreen/insecure.svg)
+ skin/classic/browser/fullscreen/secure.svg (../shared/fullscreen/secure.svg)
+ skin/classic/browser/heartbeat-icon.svg (../shared/heartbeat-icon.svg)
+ skin/classic/browser/heartbeat-star-lit.svg (../shared/heartbeat-star-lit.svg)
+ skin/classic/browser/heartbeat-star-off.svg (../shared/heartbeat-star-off.svg)
+ skin/classic/browser/connection-secure.svg (../shared/identity-block/connection-secure.svg)
+* skin/classic/browser/connection-mixed-passive-loaded.svg (../shared/identity-block/connection-mixed-passive-loaded.svg)
+* skin/classic/browser/connection-mixed-active-loaded.svg (../shared/identity-block/connection-mixed-active-loaded.svg)
+* skin/classic/browser/identity-icon.svg (../shared/identity-block/identity-icon.svg)
+ skin/classic/browser/info.svg (../shared/info.svg)
+* skin/classic/browser/menuPanel.svg (../shared/menuPanel.svg)
+* skin/classic/browser/menuPanel-small.svg (../shared/menuPanel-small.svg)
+* skin/classic/browser/notification-icons.svg (../shared/notification-icons.svg)
+* skin/classic/browser/tracking-protection-16.svg (../shared/identity-block/tracking-protection-16.svg)
+ skin/classic/browser/newtab/close.png (../shared/newtab/close.png)
+ skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
+ skin/classic/browser/newtab/whimsycorn.png (../shared/newtab/whimsycorn.png)
+ skin/classic/browser/panel-icons.svg (../shared/panel-icons.svg)
+ skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
+ skin/classic/browser/preferences/in-content/icons.svg (../shared/incontentprefs/icons.svg)
+ skin/classic/browser/preferences/in-content/search.css (../shared/incontentprefs/search.css)
+* skin/classic/browser/preferences/in-content/containers.css (../shared/incontentprefs/containers.css)
+* skin/classic/browser/preferences/containers.css (../shared/preferences/containers.css)
+ skin/classic/browser/fxa/default-avatar.svg (../shared/fxa/default-avatar.svg)
+ skin/classic/browser/fxa/logo.png (../shared/fxa/logo.png)
+ skin/classic/browser/fxa/logo@2x.png (../shared/fxa/logo@2x.png)
+ skin/classic/browser/fxa/sync-illustration.png (../shared/fxa/sync-illustration.png)
+ skin/classic/browser/fxa/sync-illustration@2x.png (../shared/fxa/sync-illustration@2x.png)
+ skin/classic/browser/fxa/sync-illustration.svg (../shared/fxa/sync-illustration.svg)
+ skin/classic/browser/fxa/android.png (../shared/fxa/android.png)
+ skin/classic/browser/fxa/android@2x.png (../shared/fxa/android@2x.png)
+ skin/classic/browser/fxa/ios.png (../shared/fxa/ios.png)
+ skin/classic/browser/fxa/ios@2x.png (../shared/fxa/ios@2x.png)
+ skin/classic/browser/search-indicator.png (../shared/search/search-indicator.png)
+ skin/classic/browser/search-indicator@2x.png (../shared/search/search-indicator@2x.png)
+ skin/classic/browser/search-engine-placeholder.png (../shared/search/search-engine-placeholder.png)
+ skin/classic/browser/search-engine-placeholder@2x.png (../shared/search/search-engine-placeholder@2x.png)
+ skin/classic/browser/searchReset.css (../shared/searchReset.css)
+ skin/classic/browser/badge-add-engine.png (../shared/search/badge-add-engine.png)
+ skin/classic/browser/badge-add-engine@2x.png (../shared/search/badge-add-engine@2x.png)
+ skin/classic/browser/search-indicator-badge-add.png (../shared/search/search-indicator-badge-add.png)
+ skin/classic/browser/search-indicator-badge-add@2x.png (../shared/search/search-indicator-badge-add@2x.png)
+ skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg)
+ skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
+ skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
+ skin/classic/browser/gear.svg (../shared/search/gear.svg)
+ skin/classic/browser/social/gear_default.png (../shared/social/gear_default.png)
+ skin/classic/browser/social/gear_clicked.png (../shared/social/gear_clicked.png)
+ skin/classic/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)
+ skin/classic/browser/tabbrowser/connecting@2x.png (../shared/tabbrowser/connecting@2x.png)
+ skin/classic/browser/tabbrowser/crashed.svg (../shared/tabbrowser/crashed.svg)
+ skin/classic/browser/tabbrowser/pendingpaint.png (../shared/tabbrowser/pendingpaint.png)
+ skin/classic/browser/tabbrowser/tab-audio.svg (../shared/tabbrowser/tab-audio.svg)
+ skin/classic/browser/tabbrowser/tab-audio-small.svg (../shared/tabbrowser/tab-audio-small.svg)
+ skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
+ skin/classic/browser/theme-switcher-icon.png (../shared/theme-switcher-icon.png)
+ skin/classic/browser/theme-switcher-icon@2x.png (../shared/theme-switcher-icon@2x.png)
+ skin/classic/browser/toolbarbutton-dropdown-arrow.png (../shared/toolbarbutton-dropdown-arrow.png)
+ skin/classic/browser/translating-16.png (../shared/translation/translating-16.png)
+ skin/classic/browser/translating-16@2x.png (../shared/translation/translating-16@2x.png)
+ skin/classic/browser/translation-16.png (../shared/translation/translation-16.png)
+ skin/classic/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png)
+ skin/classic/browser/undoCloseTab.png (../shared/undoCloseTab.png)
+ skin/classic/browser/undoCloseTab@2x.png (../shared/undoCloseTab@2x.png)
+ skin/classic/browser/update-badge.svg (../shared/update-badge.svg)
+ skin/classic/browser/update-badge-failed.svg (../shared/update-badge-failed.svg)
+ skin/classic/browser/warning.svg (../shared/warning.svg)
+ skin/classic/browser/warning-white.svg (../shared/warning-white.svg)
+ skin/classic/browser/cert-error.svg (../shared/incontent-icons/cert-error.svg)
+ skin/classic/browser/wifi.svg (../shared/incontent-icons/wifi.svg)
+ skin/classic/browser/session-restore.svg (../shared/incontent-icons/session-restore.svg)
+ skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg)
+ skin/classic/browser/favicon-search-16.svg (../shared/favicon-search-16.svg)
+ skin/classic/browser/icon-search-64.svg (../shared/incontent-icons/icon-search-64.svg)
+ skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
+ skin/classic/browser/reader-tour.png (../shared/reader/reader-tour.png)
+ skin/classic/browser/reader-tour@2x.png (../shared/reader/reader-tour@2x.png)
+ skin/classic/browser/readerMode.svg (../shared/reader/readerMode.svg)
+ skin/classic/browser/webRTC-camera-white-16.png (../shared/webrtc/camera-white-16.png)
+ skin/classic/browser/webRTC-microphone-white-16.png (../shared/webrtc/microphone-white-16.png)
+ skin/classic/browser/webRTC-screen-white-16.png (../shared/webrtc/screen-white-16.png)
+ skin/classic/browser/panic-panel/header.png (../shared/panic-panel/header.png)
+ skin/classic/browser/panic-panel/header@2x.png (../shared/panic-panel/header@2x.png)
+ skin/classic/browser/panic-panel/header-small.png (../shared/panic-panel/header-small.png)
+ skin/classic/browser/panic-panel/header-small@2x.png (../shared/panic-panel/header-small@2x.png)
+ skin/classic/browser/panic-panel/icons.png (../shared/panic-panel/icons.png)
+ skin/classic/browser/panic-panel/icons@2x.png (../shared/panic-panel/icons@2x.png)
+ skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
+ skin/classic/browser/privatebrowsing/check.svg (../shared/privatebrowsing/check.svg)
+ skin/classic/browser/privatebrowsing/favicon.svg (../shared/privatebrowsing/favicon.svg)
+ skin/classic/browser/privatebrowsing/private-browsing.svg (../shared/privatebrowsing/private-browsing.svg)
+ skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
+ skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
+ skin/classic/browser/devedition/urlbar-history-dropmarker.svg (../shared/devedition/urlbar-history-dropmarker.svg)
+ skin/classic/browser/urlbar-star.svg (../shared/urlbar-star.svg)
+ skin/classic/browser/urlbar-tab.svg (../shared/urlbar-tab.svg)
diff --git a/browser/themes/shared/menuPanel-small.svg b/browser/themes/shared/menuPanel-small.svg
new file mode 100644
index 000000000..db28992e2
--- /dev/null
+++ b/browser/themes/shared/menuPanel-small.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="96" height="16" viewBox="0 0 96 16"
+ class="fieldtext">
+#include icon-colors.inc.svg
+
+ <path id="placeholder" d="M8,16a8,8,0,1,1,8-8A8,8,0,0,1,8,16ZM12,4H4v8h8V4ZM5,9.939V6.061L6.939,8ZM9.939,11H6.061L8,9.061ZM11,11h0Zm0-4.939V9.939L9.061,8ZM11,5h0ZM6.061,5H9.939L8,6.939Z"/>
+ <path id="cut" d="M29.63,15a2.426,2.426,0,0,1-2.282-1.277c-0.761-1.109-1.694-2.488-1.694-2.488S25,10.329,24.549,9.623a1.05,1.05,0,0,0-1.106-.538S20.6,4.437,20.124,3.706C19.465,2.689,20.7,1,20.7,1l4.4,7.044a19.333,19.333,0,0,0,1.867,2.286c0.519,0.4,1.382-.373,2.8.908C31.7,12.984,31.048,15,29.63,15ZM29.423,12.11c-0.933-1.042-1.728-.908-1.936-0.639a2.093,2.093,0,0,0,.38,1.748,1.612,1.612,0,0,0,1.383.74C29.838,13.959,30.356,13.153,29.423,12.11ZM25.582,7.372L24.4,5.6,27.276,1s1.233,1.69.575,2.708C27.568,4.142,26.445,5.967,25.582,7.372Zm-4.576,2.956A12.482,12.482,0,0,0,22.43,8.645l0.826,1.239c-0.428.65-.937,1.352-0.937,1.352s-0.933,1.378-1.694,2.488A2.426,2.426,0,0,1,18.344,15c-1.417,0-2.074-2.017-.138-3.765C19.624,9.956,20.487,10.732,21.006,10.329ZM18.551,12.11c-0.933,1.042-.415,1.849.173,1.849a1.612,1.612,0,0,0,1.383-.74,2.093,2.093,0,0,0,.38-1.748C20.28,11.2,19.485,11.068,18.551,12.11Z"/>
+ <path id="copy" d="M46,15H40a1,1,0,0,1-1-1V6a1,1,0,0,1,1-1h4.953C45,5,47,6.984,47,7.047V14A1,1,0,0,1,46,15ZM44,6V8h2ZM38,4.886V11H34a1,1,0,0,1-1-1V2a1,1,0,0,1,1-1h4.953C39,1,41,2.985,41,3.047v1.34H38.5A0.5,0.5,0,0,0,38,4.886ZM38,2V4h2Z"/>
+ <path id="paste" d="M59.5,15h-7A1.5,1.5,0,0,1,51,13.5v-9A1.5,1.5,0,0,1,52.5,3H54a2,2,0,1,1,4,0h1.5A1.5,1.5,0,0,1,61,4.5v9A1.5,1.5,0,0,1,59.5,15ZM58.682,4L57.61,3.5a1.613,1.613,0,0,0-3.219,0L53.318,4,52.781,5h6.437ZM58.82,5.688H54.074L51.059,7.428l2.849,4.935,6.574-3.8Z"/>
+ <rect id="zoomOut" x="67" y="7" width="10" height="2"/>
+ <path id="zoomIn" d="M93,9H89v4H87V9H83V7h4V3h2V7h4V9Z"/>
+</svg>
diff --git a/browser/themes/shared/menuPanel.svg b/browser/themes/shared/menuPanel.svg
new file mode 100644
index 000000000..e20f0197c
--- /dev/null
+++ b/browser/themes/shared/menuPanel.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="1056" height="32" viewBox="0 0 1056 32"
+ class="fieldtext">
+#include icon-colors.inc.svg
+
+ <path id="containers" d="M1050,30h-20a2,2,0,0,1-2-2V4.414a1.03,1.03,0,0,1,.29-0.707L1030,2h20l1.71,1.707a1.03,1.03,0,0,1,.29.707V28A2,2,0,0,1,1050,30Zm0-24h-20V28h20V6Zm-2,10h-16V8h16v8Zm-4-4.5a0.5,0.5,0,0,0-1,0V13h-6V11.5a0.5,0.5,0,0,0-1,0v2a0.5,0.5,0,0,0,.5.5h7a0.5,0.5,0,0,0,.5-0.5v-2Zm4,14.5h-16V18h16v8Zm-4-4.5a0.5,0.5,0,0,0-1,0V23h-6V21.5a0.5,0.5,0,0,0-1,0v2a0.5,0.5,0,0,0,.5.5h7a0.5,0.5,0,0,0,.5-0.5v-2Z"/>
+ <path id="tabs" d="M1021.98,28h-28a2,2,0,0,1-2-2V22a2,2,0,0,1,2-2H994c4.591,0,4-3,4.009-8,0.009-4.686.166-8,6.261-8h7.41c6.13,0,6.27,3.314,6.3,8,0.02,5-.59,8,4.02,8h-0.02a2,2,0,0,1,2,2v4A2,2,0,0,1,1021.98,28Z"/>
+ <path id="pocket" d="M975.969,29.969A13.969,13.969,0,0,1,962,16V8.333A4.333,4.333,0,0,1,966.333,4H985.6a4.333,4.333,0,0,1,4.333,4.333V16A13.969,13.969,0,0,1,975.969,29.969Zm7.507-19.035a2.009,2.009,0,0,0-1.424.59l-0.007-.007-6.095,6.015-5.479-5.422a2,2,0,1,0-2.917,2.727l-0.01.01,5.555,5.5h0l1.518,1.5a1.9,1.9,0,0,0,2.661,0l7.558-7.459a1.979,1.979,0,0,0,.649-1.46A2,2,0,0,0,983.476,10.933Z"/>
+ <path id="webIDE" d="M951.947,10a24.679,24.679,0,0,1,.362,2.691L949,16h-4v4l-2,2h-4.393a14.261,14.261,0,0,0,1.358,3.076l-1.716,3.777A15,15,0,1,1,957.175,7.825L955,10h-3.053ZM938.292,26.023A17.1,17.1,0,0,1,936.54,22h-2.819A12.445,12.445,0,0,0,938.292,26.023ZM932.616,20h3.437a25.331,25.331,0,0,1-.462-4h-3.978A12.38,12.38,0,0,0,932.616,20Zm0-10a12.38,12.38,0,0,0-1,4h3.978a25.331,25.331,0,0,1,.462-4h-3.437Zm1.105-2h2.819a17.093,17.093,0,0,1,1.752-4.023A12.444,12.444,0,0,0,933.721,8ZM943,2.613c-0.393.031-.777,0.093-1.158,0.16A10.229,10.229,0,0,0,938.607,8H943V2.613ZM943,10h-4.914a24.566,24.566,0,0,0-.467,4H943V10Zm0,6h-5.381a24.566,24.566,0,0,0,.467,4H943V16Zm2-2h5.381a24.566,24.566,0,0,0-.467-4H945v4Zm1.158-11.227c-0.381-.067-0.765-0.128-1.158-0.16V8h4.393A10.229,10.229,0,0,0,946.158,2.773Zm3.55,1.2A17.093,17.093,0,0,1,951.46,8h2.819A12.444,12.444,0,0,0,949.708,3.977ZM947.068,28.3L939,32l3.726-8.047ZM944,23l13-13,4,4L948,27Zm3.4,6.6,11.2-11.2A15,15,0,0,1,947.4,29.6Z"/>
+ <path id="app" d="M920.044,27.006l-4.354-4.863c-0.39.4-1.028,2.507-1.49,2.769h-4.4c-0.459-.262-1.094-2.375-1.484-2.769l-4.153,4.863H901V16.067l4.353-4.271C906.443,5.26,910.259,1,912,1s5.7,4.26,6.515,10.8L923,16.135V27.006h-2.956Zm-8.036-19.48c-0.61,0-1.819,2.268-2.484,4.251a14.406,14.406,0,0,1,2.535-.224,14.627,14.627,0,0,1,2.423.2C913.818,9.782,912.613,7.526,912.008,7.526Zm-0.762,18.23a4.106,4.106,0,0,0-.165,1.17,1.493,1.493,0,0,0,.858,1.466,1.535,1.535,0,0,0,.957-1.466,4.071,4.071,0,0,0-.165-1.17h1.079a9.949,9.949,0,0,1,.544,1.973A3.6,3.6,0,0,1,912,31.014a3.686,3.686,0,0,1-2.352-3.337,9.384,9.384,0,0,1,.544-1.921h1.051Z"/>
+ <path id="forget" d="M880,31a15,15,0,1,1,15-15A15,15,0,0,1,880,31ZM879.963,8.208V4.159l-6.328,5.2,6.328,5.524v-4.27A5.279,5.279,0,0,1,885.391,16,5.233,5.233,0,0,1,880,21.391a5.406,5.406,0,0,1-5.28-3.516h-2.189A7.773,7.773,0,0,0,887.852,16,8.115,8.115,0,0,0,879.963,8.208Z"/>
+ <path id="sidebars" d="M862,29H834a1,1,0,0,1-1-1V4a1,1,0,0,1,1-1h28a1,1,0,0,1,1,1V28A1,1,0,0,1,862,29ZM840,9h-3a1,1,0,0,0-1,1V25a1,1,0,0,0,1,1h3V9Zm3,17h16a1,1,0,0,0,1-1V10a1,1,0,0,0-1-1H843V26Zm8-20.986a0.991,0.991,0,1,0,1,.99A0.995,0.995,0,0,0,851,5.015Zm3,0a0.991,0.991,0,1,0,1,.99A0.995,0.995,0,0,0,854,5.015ZM859,5h-2a1,1,0,0,0,0,2h2A1,1,0,0,0,859,5Z"/>
+ <path id="addon-generic" d="M815.988,2.013a13.987,13.987,0,1,1-13.975,14A14,14,0,0,1,815.988,2.013ZM814.923,4.4a2.112,2.112,0,0,0-2.371,2,2.064,2.064,0,0,0,.547,1.585,0.884,0.884,0,0,1,.285.717,1.458,1.458,0,0,1-1.706,1.329h-3.2a0.581,0.581,0,0,0-.552.584c-0.011.061,0,2.462,0,2.462a2.445,2.445,0,0,0,.289,1.489,0.8,0.8,0,0,0,.841.411,2.8,2.8,0,0,0,1.6-.487,1.632,1.632,0,0,1,1.221-.428c1.108,0,1.293.45,1.293,1.856,0,1.013-.469,1.931-1.262,1.931a1.054,1.054,0,0,1-.969-0.384,2.6,2.6,0,0,0-1.718-.63,0.99,0.99,0,0,0-1,.485c-0.339.544-.3,1.255-0.3,2.768,0,2.015,0,3.248,0,3.285a0.591,0.591,0,0,0,.552.668h3.2c1.072,0,2.874.361,2.874-1.05a1.611,1.611,0,0,0-.458-1.111,1.685,1.685,0,0,1-.377-1.149c0-.285-0.068-1.283,1.668-1.283,0.071,0,1.634.148,1.634,1.346a1.388,1.388,0,0,1-.245,1.04A1.4,1.4,0,0,0,816.185,23c0,1.5,1.683,1.039,2.213,1.039,1.884,0,2.763.017,3.031,0a0.628,0.628,0,0,0,.585-0.634C822,22.875,822,22.735,822,19.861a6.582,6.582,0,0,1,.172-2.259,0.642,0.642,0,0,1,.657-0.231,1.869,1.869,0,0,1,1.047.384,2.142,2.142,0,0,0,1.625,1.006c1.386,0,2.125-1.622,2.125-2.779,0-1.232-.628-2.754-2.158-2.754a2.029,2.029,0,0,0-1.516.723,1.384,1.384,0,0,1-.876.584,1.131,1.131,0,0,1-1.067-.877V10.738a0.734,0.734,0,0,0-.56-0.709c-0.012,0-1.881,0-3.34,0a1.407,1.407,0,0,1-1.541-1.389,1.292,1.292,0,0,1,.279-0.984,1.8,1.8,0,0,0,.548-1.283C817.392,5.666,817.042,4.4,814.923,4.4Z"/>
+ <path id="mail" d="M787.333,16a4.507,4.507,0,0,1-6.666,0L769.39,6.7A3.349,3.349,0,0,1,772.333,5h23.334a3.348,3.348,0,0,1,2.943,1.7Zm-6.666,3.143a4.507,4.507,0,0,0,6.666,0L799,9.243V23.857A3.242,3.242,0,0,1,795.667,27H772.333A3.243,3.243,0,0,1,769,23.857V9.243Z"/>
+ <path id="settings" d="M763.005,16c0,1.307.425,2.516,1.03,2.728L765,19.068a13.308,13.308,0,0,1-1.644,3.956l-0.921-.443c-0.578-.278-1.733.276-2.657,1.2s-1.478,2.079-1.2,2.657l0.444,0.923a13.357,13.357,0,0,1-3.964,1.622l-0.333-.949c-0.212-.6-1.421-1.03-2.728-1.03s-2.516.425-2.728,1.03l-0.34.969a13.322,13.322,0,0,1-3.956-1.644l0.443-.921c0.278-.578-0.276-1.733-1.2-2.657s-2.08-1.478-2.658-1.2l-0.923.444a13.366,13.366,0,0,1-1.622-3.964l0.949-.333C740.57,18.516,741,17.308,741,16s-0.425-2.516-1.03-2.728l-0.971-.341a13.255,13.255,0,0,1,1.667-3.946l0.9,0.433c0.578,0.278,1.733-.276,2.658-1.2s1.478-2.08,1.2-2.657l-0.433-.9A13.261,13.261,0,0,1,748.932,3l0.34,0.969C749.484,4.57,750.693,5,752,5s2.516-.425,2.728-1.03L755.068,3a13.321,13.321,0,0,1,3.956,1.644l-0.443.921c-0.277.577,0.276,1.733,1.2,2.657s2.079,1.478,2.657,1.2l0.923-.444a13.337,13.337,0,0,1,1.622,3.964l-0.949.333C763.43,13.485,763.005,14.693,763.005,16ZM752,8.946A7.054,7.054,0,1,0,759.054,16,7.054,7.054,0,0,0,752,8.946Z"/>
+ <path id="developer" d="M724.986,11.177a2.978,2.978,0,0,1,.246.367,2.361,2.361,0,0,0,2.835-.346l4.975-4.932A7.808,7.808,0,0,1,734,9.952a7.969,7.969,0,0,1-10.791,7.435L711.4,29.075a3.192,3.192,0,0,1-4.486,0,3.125,3.125,0,0,1,0-4.447l11.675-11.563a7.863,7.863,0,0,1-.64-3.113,7.974,7.974,0,0,1,11.725-7.014l-4.972,4.929a2.307,2.307,0,0,0-.246,2.964A3.066,3.066,0,0,1,724.986,11.177ZM709.25,25A1.747,1.747,0,1,0,711,26.748,1.746,1.746,0,0,0,709.25,25Z"/>
+ <path id="fullscreen" d="M696,22V10l6,6Zm-14,2h12l-6,6Zm11-2H683a1,1,0,0,1-1-1V11a1,1,0,0,1,1-1h10a1,1,0,0,1,1,1V21A1,1,0,0,1,693,22Zm-1-7a1,1,0,0,0-1-1h-6a1,1,0,0,0-1,1v4a1,1,0,0,0,1,1h6a1,1,0,0,0,1-1V15ZM688,2l6,6H681.982Zm-8,8V22l-6-6Z"/>
+ <path id="print" d="M670,26h-4V24h-1l3,6H644l2-4h-4a2,2,0,0,1-2-2V14a2,2,0,0,1,2-2h2V10a2,2,0,0,1,2-2V3a1,1,0,0,1,1-1h18a1,1,0,0,1,1,1V8a2,2,0,0,1,2,2v2h2a2,2,0,0,1,2,2V24A2,2,0,0,1,670,26Zm-24,0,1-2h-1v2Zm1-10h-2a1,1,0,0,0,0,2h2A1,1,0,0,0,647,16ZM664,4.5a0.5,0.5,0,0,0-.5-0.5h-15a0.5,0.5,0,0,0-.5.5v9a0.5,0.5,0,0,0,.5.5h15a0.5,0.5,0,0,0,.5-0.5v-9ZM662.222,24H649.778L648,28h16Z"/>
+ <path id="search" d="M626.853,23.318a10.074,10.074,0,0,1-5.361-1.545l-6.611,6.619a2.028,2.028,0,0,1-2.87,0l-0.4-.4a2.033,2.033,0,0,1,0-2.873l6.618-6.627A10.137,10.137,0,1,1,626.853,23.318Zm0-16.254a6.1,6.1,0,1,0,6.088,6.1A6.092,6.092,0,0,0,626.853,7.064Z"/>
+ <path id="privateBrowsing" d="M574.273,11.973c-0.122,2.136.37,4.688-2.4,8.367-2.953,3.926-5.886,3.626-6.44,3.685-3.322.354-3.76-2.62-5.7-2.62-1.7,0-3.083,2.955-5.578,2.62-0.552-.074-3.487.241-6.44-3.685-2.768-3.679-2.276-6.231-2.4-8.367a41.419,41.419,0,0,0-.553-4.451,5.372,5.372,0,0,0,3.056,1.484c1.722,0.119,2.044-.61,5.678-1.662,3.929-1.137,6.3,3.522,6.3,3.522s2.668-4.591,6.3-3.522,3.78,1.78,5.5,1.662a6.249,6.249,0,0,0,3.232-1.484A41.574,41.574,0,0,0,574.273,11.973Zm-20.315.895c-2.148-.479-3.049.339-3.969,0.688a7.615,7.615,0,0,1-1.534.4s0.123,1.246,2.276,2.314,6.569,0.517,6.569.517S557.769,13.718,553.958,12.868ZM569.6,13.557c-0.92-.349-1.821-1.167-3.969-0.688-3.811.85-3.342,3.918-3.342,3.918s4.416,0.551,6.569-.517,2.276-2.314,2.276-2.314A7.615,7.615,0,0,1,569.6,13.557Z"/>
+ <path id="new-tab" d="M541.977,28h-28a2,2,0,0,1-2-2V22a2,2,0,0,1,2-2H514c4.591,0,4-3,4.009-8,0.009-4.686.166-8,6.26-8h7.415c6.126,0,6.271,3.314,6.293,8,0.023,5-.592,8,4.023,8h-0.023a2,2,0,0,1,2,2v4A2,2,0,0,1,541.977,28ZM533,14h-4V10h-2v4h-4v2h4v4h2V16h4V14Z"/>
+ <path id="new-window" d="M510,29H482a1,1,0,0,1-1-1V4a1,1,0,0,1,1-1h28a1,1,0,0,1,1,1V28A1,1,0,0,1,510,29ZM499,5.015a0.991,0.991,0,1,0,1,.99A0.995,0.995,0,0,0,499,5.015Zm3,0a0.991,0.991,0,1,0,1,.99A0.995,0.995,0,0,0,502,5.015ZM507,5h-2a1,1,0,0,0,0,2h2A1,1,0,0,0,507,5Zm1,5a1,1,0,0,0-1-1H485a1,1,0,0,0-1,1V25a1,1,0,0,0,1,1h22a1,1,0,0,0,1-1V10Z"/>
+ <path id="encoding" d="M474,30H454a4,4,0,0,1-4-4V6a4,4,0,0,1,4-4h20a4,4,0,0,1,4,4V26A4,4,0,0,1,474,30Zm-1-19a6,6,0,0,0-6-6h-8a6,6,0,0,0-6,6v8a6,6,0,0,0,6,6h8c3.314,0,6-.686,6-4V11Zm-5.953,6.863a7.6,7.6,0,0,0,1.655-.171,7.822,7.822,0,0,0,1.587-.552v1.445a8.416,8.416,0,0,1-1.567.532,8.014,8.014,0,0,1-1.714.161A4.231,4.231,0,0,1,462.964,17a4.931,4.931,0,0,1-1.753,1.758,4.724,4.724,0,0,1-2.271.518,3.547,3.547,0,0,1-2.5-.83,3,3,0,0,1-.9-2.325,2.846,2.846,0,0,1,1.211-2.447,6.7,6.7,0,0,1,3.692-.952l1.8-.059V12a2.632,2.632,0,0,0-.566-1.86,2.271,2.271,0,0,0-1.729-.6,6.575,6.575,0,0,0-3,.82l-0.508-1.24a7.934,7.934,0,0,1,3.623-.918,4.438,4.438,0,0,1,2.076.425,2.656,2.656,0,0,1,1.206,1.353A3.647,3.647,0,0,1,464.7,8.653a3.833,3.833,0,0,1,1.909-.469,3.787,3.787,0,0,1,3.008,1.3,5.1,5.1,0,0,1,1.133,3.472V14H463.9Q463.98,17.863,467.047,17.863ZM462.2,13.819l-1.543.068a5.31,5.31,0,0,0-2.617.611,1.837,1.837,0,0,0-.8,1.646,1.673,1.673,0,0,0,.522,1.363,2.092,2.092,0,0,0,1.382.435,3.013,3.013,0,0,0,2.237-.825,3.16,3.16,0,0,0,.82-2.329V13.819Zm6.808-1.114a3.81,3.81,0,0,0-.625-2.344,2.124,2.124,0,0,0-1.8-.82,2.3,2.3,0,0,0-1.861.811,4.028,4.028,0,0,0-.786,2.354h5.069Z"/>
+ <path id="share" d="M433.425,19.753l-0.658.08,0-.08L443.07,4.742l-13.5,15.01,0.154,0.45-0.494.06,0.547,0.094,3.651,10.653L426.77,20.562l-8.777,1.067L445,1V25.188Zm0,1.231,4.938,2.986-4.938,7.04-0.6-10.129Z"/>
+ <path id="feed" d="M412.68,29.958l-3.1.031a1.516,1.516,0,0,1-1.538-1.516s0.687-7.114-6.308-14.356c-5.1-6.065-14.151-6.358-14.151-6.358a1.517,1.517,0,0,1-1.6-1.451l0.031-2.833a1.463,1.463,0,0,1,1.538-1.451s12.628,0.807,19.264,8.856c6.554,6.143,7.213,17.593,7.213,17.593A1.337,1.337,0,0,1,412.68,29.958Zm-25.159-18s7.416,0.88,11.585,4.753c4.264,3.961,4.9,11.794,4.9,11.794,0,0.832-.112,1.474-0.952,1.474l-2.852-.031a1.321,1.321,0,0,1-1.235-1.537,12.715,12.715,0,0,0-3.786-8.6c-2.877-2.641-7.694-2.8-7.694-2.8a1.437,1.437,0,0,1-1.521-1.412L386,13.371A1.436,1.436,0,0,1,387.521,11.96Zm2.488,10.03a4.012,4.012,0,1,1-4,4.012A4,4,0,0,1,390.009,21.989Z"/>
+ <path id="sync" d="M381.914,17.518a13.937,13.937,0,0,1-.8,3.367,10.892,10.892,0,0,1-5.084,6.587,23.381,23.381,0,0,0,2.531,1.884,51.867,51.867,0,0,1-8.176.671c-0.073.012-.145-0.233-0.218-0.221l-0.009.219a19.383,19.383,0,0,1-5.989-1.271,10.818,10.818,0,0,0,3.225-4.19,16.7,16.7,0,0,0,1.216-6.063,36.351,36.351,0,0,0,2.73,4.119,8.152,8.152,0,0,0,4.263-6.1,7.53,7.53,0,0,0-1.165-4.689,7.645,7.645,0,0,0-3.463-2.839c0.461-.872,1-1.847,1.513-2.674a7.385,7.385,0,0,1,2.559-2.383A13.959,13.959,0,0,1,381.914,17.518ZM367.96,13.509s-2.271-2.971-3.244-4.054a8.006,8.006,0,0,0-4.306,7.011,7.6,7.6,0,0,0,4.837,6.526,11.93,11.93,0,0,1-1.982,2.818,21.3,21.3,0,0,1-2.45,2.158,13.955,13.955,0,0,1-5.641-17.528,10.883,10.883,0,0,1,4.232-5.453c0.189-.147.382-0.287,0.577-0.424-0.8-.739-3.667-1.049-3.667-1.049s5.431-2.093,13.959-1.16C367.87,6.295,367.96,13.509,367.96,13.509Z"/>
+ <path id="save" d="M346.25,30h-20.5A1.755,1.755,0,0,1,324,28.25V3.75A1.755,1.755,0,0,1,325.75,2h13.5a5.164,5.164,0,0,1,3.033,1.19L346.717,7.3A4.6,4.6,0,0,1,348,10.241V28.25A1.755,1.755,0,0,1,346.25,30ZM345.774,9.293l-5-4.586C340.347,4.318,340,4.45,340,5v5h5.455C346.055,10,346.2,9.682,345.774,9.293Z"/>
+ <path id="open" d="M319.749,13.924a67.491,67.491,0,0,0-1.34,7.977,37.552,37.552,0,0,0-.4,6.4,0.708,0.708,0,0,1-.714.7H290.679a0.709,0.709,0,0,1-.715-0.7,37.552,37.552,0,0,0-.4-6.4,67.491,67.491,0,0,0-1.34-7.977C287.973,12.779,288.606,12,289,12h29.974C319.368,12,320,12.779,319.749,13.924Zm-29.682-6.9h-0.076V5.019a1.987,1.987,0,0,1,1.968-2.006h8.105c1.087,0,2.276,1.755,2.276,1.755l1.635,2.222,13-.009a1.012,1.012,0,0,1,1.025,1V11H290.048Z"/>
+ <path id="addOns" d="M277.051,30.97a1.987,1.987,0,0,0,1.977-2V21.86s0.3-1.829,1.515-1.829,1.088,1.934,3.356,1.934c1.133,0,3.085-.581,3.085-4.082s-1.952-3.924-3.085-3.924c-2.268,0-2.138,1.828-3.356,1.828s-1.515-1.881-1.515-1.881V10.994a1.988,1.988,0,0,0-1.977-2h-5.2s-1.725-.3-1.725-1.515,1.882-1.3,1.882-3.565c0-1.131-.632-2.926-4.135-2.926s-3.977,1.8-3.977,2.926c0,2.268,1.724,2.349,1.724,3.565S263.9,8.993,263.9,8.993h-4.951a1.989,1.989,0,0,0-1.976,2l0,3.906s-0.211,3.015,2.213,3.015c1.528,0,1.732-2.057,3.742-2.057,1,0,2.019.941,2.019,3.02S263.932,22,262.932,22c-2.01,0-2.214-2.055-3.742-2.055-2.424,0-2.213,2.909-2.213,2.909l0,6.115a1.988,1.988,0,0,0,1.976,2h6.638s3.154,0.212,3.154-2.214c0-1.528-1.991-1.824-1.991-3.835,0-1,1.109-2.238,3.19-2.238s3.314,1.238,3.314,2.238c0,2.012-1.928,2.307-1.928,3.835,0,2.425,3.154,2.214,3.154,2.214h2.572Z"/>
+ <path id="downloads" d="M253.285,18.118L242.09,29.126a3.008,3.008,0,0,1-4.242,0L226.59,18.118c-1.166-1.166-.772-2.121.879-2.121h6.5l0.062-12a2.027,2.027,0,0,1,2.032-2H244a2,2,0,0,1,2,2V16h6.406C254.057,16,254.451,16.952,253.285,18.118Z"/>
+ <path id="history" d="M208.007,30.007a14,14,0,1,1,14-14A14,14,0,0,1,208.007,30.007Zm0-24.007a10.008,10.008,0,1,0,10,10.008A10,10,0,0,0,208.007,6ZM206.1,15.9V10.412a1.829,1.829,0,0,1,1.829-1.829,1.951,1.951,0,0,1,1.965,1.829v5.032a22.977,22.977,0,0,1,3.52,5.939s-4.106-1.8-6.059-3.773A1.811,1.811,0,0,1,206.1,15.9Z"/>
+ <path id="bookmark-filled" d="M188.4,11.546l-2.241-.371-5.3-.872-1.354-2.728v0l-1.09-2.192-1.088-2.2c-0.743-1.5-1.96-1.5-2.7,0l-1.089,2.2-1.088,2.192v0L171.1,10.3l-5.295.872-2.242.371c-1.677.275-2.093,1.49-.928,2.7l5.452,5.634-0.834,5.464L166.879,27.8c-0.253,1.643.766,2.348,2.264,1.576L171.2,28.3l2.051-1.071a0.007,0.007,0,0,0,.005,0l2.726-1.427,2.725,1.427a0.016,0.016,0,0,0,.007,0l2.048,1.071,2.06,1.082c1.5,0.772,2.514.068,2.266-1.576l-0.376-2.461-0.828-5.464,5.444-5.628C190.5,13.037,190.08,11.821,188.4,11.546Z"/>
+ <path id="Bookmark-hollow" d="M144,8.365l1.725,3.526,0.79,1.616,1.773,0.3,4.069,0.681-3.007,3.153-1.182,1.24,0.254,1.7,0.63,4.207-3.426-1.821-1.639-.871-1.639.871-3.423,1.819,0.632-4.2,0.255-1.7-1.184-1.241-3-3.15,4.111-.683,1.79-.3,0.787-1.636L144,8.365M143.984,2a1.671,1.671,0,0,0-1.351,1.139l-3.472,7.213-7.582,1.259c-1.675.279-2.091,1.509-.926,2.735l5.445,5.709-1.207,8.031c-0.183,1.207.3,1.914,1.151,1.914a2.448,2.448,0,0,0,1.111-.317l6.832-3.631,6.832,3.631a2.447,2.447,0,0,0,1.11.317c0.85,0,1.333-.707,1.152-1.914l-1.2-8.031,5.438-5.7c1.165-1.229.749-2.461-.926-2.74l-7.527-1.259-3.527-7.213A1.668,1.668,0,0,0,143.984,2h0Z"/>
+ <path id="home" d="M124,16L112,6,100,16H96L112,2l16,14h-4Zm-2,0v13.96h-8V20h-4v9.96h-8V16l10-8Z"/>
+ <path id="stop" d="M93.121,24.879l-4.243,4.243-8.9-8.9L71.206,29l-4.2-4.2,8.774-8.774-8.9-8.9,4.243-4.243,8.9,8.9L88.794,3l4.2,4.2L84.222,15.98Z"/>
+ <path id="reload" d="M62,14a2,2,0,0,1-2,2H48l5.833-5.833a8.993,8.993,0,1,0,1,12.686l3.035,2.6A13,13,0,1,1,56.669,7.331L62,2V14Z"/>
+ <path id="placeholder" fill-rule="evenodd" d="M16,0A16,16,0,1,1,0,16,16,16,0,0,1,16,0ZM8,24V8H24V24H8Zm14-2h0Zm-2.121,0L16,18.121,12.121,22h7.757Zm-6-6L10,12.121v7.757Zm-1.757-6L16,13.879,19.879,10H12.121Zm6,6L22,19.879V12.121Z"/>
+</svg>
diff --git a/browser/themes/shared/menupanel.inc.css b/browser/themes/shared/menupanel.inc.css
new file mode 100644
index 000000000..7517e4df0
--- /dev/null
+++ b/browser/themes/shared/menupanel.inc.css
@@ -0,0 +1,183 @@
+/* Menu panel and palette styles */
+
+toolbaritem[sdkstylewidget="true"] > toolbarbutton,
+:-moz-any(@primaryToolbarButtons@)[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > :-moz-any(@primaryToolbarButtons@) {
+ list-style-image: url(chrome://browser/skin/menuPanel.svg);
+}
+
+:-moz-any(@primaryToolbarButtons@)[cui-areatype="menu-panel"][panel-multiview-anchor=true] > .toolbarbutton-icon,
+:-moz-any(@primaryToolbarButtons@)[cui-areatype="menu-panel"][panel-multiview-anchor=true] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
+:-moz-any(@primaryToolbarButtons@)[cui-areatype="menu-panel"][panel-multiview-anchor=true] > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
+
+#home-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #home-button {
+ -moz-image-region: rect(0px, 128px, 32px, 96px);
+}
+
+#bookmarks-menu-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #bookmarks-menu-button {
+ -moz-image-region: rect(0px, 192px, 32px, 160px);
+}
+
+#history-panelmenu[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #history-panelmenu {
+ -moz-image-region: rect(0px, 224px, 32px, 192px);
+}
+
+#downloads-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #downloads-button {
+ -moz-image-region: rect(0px, 256px, 32px, 224px);
+}
+
+#add-ons-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #add-ons-button {
+ -moz-image-region: rect(0px, 288px, 32px, 256px);
+}
+
+#open-file-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #open-file-button {
+ -moz-image-region: rect(0px, 320px, 32px, 288px);
+}
+
+#save-page-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #save-page-button {
+ -moz-image-region: rect(0px, 352px, 32px, 320px);
+}
+
+#sync-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #sync-button {
+ -moz-image-region: rect(0px, 1024px, 32px, 992px);
+}
+
+#containers-panelmenu[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #containers-panelmenu {
+ -moz-image-region: rect(0px, 1056px, 32px, 1024px);
+}
+
+#feed-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #feed-button {
+ -moz-image-region: rect(0px, 416px, 32px, 384px);
+}
+
+#social-share-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #social-share-button {
+ -moz-image-region: rect(0px, 448px, 32px, 416px);
+}
+
+#characterencoding-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #characterencoding-button {
+ -moz-image-region: rect(0px, 480px, 32px, 448px);
+}
+
+#new-window-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #new-window-button {
+ -moz-image-region: rect(0px, 512px, 32px, 480px);
+}
+
+#e10s-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #e10s-button {
+ -moz-image-region: rect(0px, 512px, 32px, 480px);
+}
+
+#new-tab-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #new-tab-button {
+ -moz-image-region: rect(0px, 544px, 32px, 512px);
+}
+
+#privatebrowsing-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #privatebrowsing-button {
+ -moz-image-region: rect(0px, 576px, 32px, 544px);
+}
+
+#find-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #find-button {
+ -moz-image-region: rect(0px, 640px, 32px, 608px);
+}
+
+#print-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #print-button {
+ -moz-image-region: rect(0px, 672px, 32px, 640px);
+}
+
+#fullscreen-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #fullscreen-button {
+ -moz-image-region: rect(0px, 704px, 32px, 672px);
+}
+
+#developer-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #developer-button {
+ -moz-image-region: rect(0px, 736px, 32px, 704px);
+}
+
+#preferences-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #preferences-button {
+ -moz-image-region: rect(0px, 768px, 32px, 736px);
+}
+
+#email-link-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #email-link-button {
+ -moz-image-region: rect(0, 800px, 32px, 768px);
+}
+
+#sidebar-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #sidebar-button {
+ -moz-image-region: rect(0, 864px, 32px, 832px);
+}
+
+#panic-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #panic-button {
+ -moz-image-region: rect(0, 896px, 32px, 864px);
+}
+
+#webide-button[cui-areatype="menu-panel"],
+toolbarpaletteitem[place="palette"] > #webide-button {
+ -moz-image-region: rect(0px, 960px, 32px, 928px);
+}
+
+toolbaritem[sdkstylewidget="true"] > toolbarbutton {
+ -moz-image-region: rect(0, 832px, 32px, 800px);
+}
+
+/* Wide panel control icons */
+
+#edit-controls@inAnyPanel@ > toolbarbutton,
+#zoom-controls@inAnyPanel@ > toolbarbutton,
+toolbarpaletteitem[place="palette"] > #edit-controls > toolbarbutton,
+toolbarpaletteitem[place="palette"] > #zoom-controls > toolbarbutton {
+ list-style-image: url(chrome://browser/skin/menuPanel-small.svg);
+}
+
+#edit-controls@inAnyPanel@ > #cut-button,
+toolbarpaletteitem[place="palette"] > #edit-controls > #cut-button {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+#edit-controls@inAnyPanel@ > #copy-button,
+toolbarpaletteitem[place="palette"] > #edit-controls > #copy-button {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+#edit-controls@inAnyPanel@ > #paste-button,
+toolbarpaletteitem[place="palette"] > #edit-controls > #paste-button {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+#zoom-controls@inAnyPanel@ > #zoom-out-button,
+toolbarpaletteitem[place="palette"] > #zoom-controls > #zoom-out-button {
+ -moz-image-region: rect(0px, 80px, 16px, 64px);
+}
+
+#zoom-controls@inAnyPanel@ > #zoom-in-button,
+toolbarpaletteitem[place="palette"] > #zoom-controls > #zoom-in-button {
+ -moz-image-region: rect(0px, 96px, 16px, 80px);
+}
+
+#add-share-provider {
+ list-style-image: url(chrome://browser/skin/menuPanel-small.svg);
+ -moz-image-region: rect(0px, 96px, 16px, 80px);
+}
+
diff --git a/browser/themes/shared/newtab/close.png b/browser/themes/shared/newtab/close.png
new file mode 100644
index 000000000..ea6ada49e
--- /dev/null
+++ b/browser/themes/shared/newtab/close.png
Binary files differ
diff --git a/browser/themes/shared/newtab/controls.svg b/browser/themes/shared/newtab/controls.svg
new file mode 100644
index 000000000..6eb00965d
--- /dev/null
+++ b/browser/themes/shared/newtab/controls.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="288" height="32" viewBox="0 0 288 32">
+ <defs>
+ <style>
+ /* Glyph Styles */
+ .glyphShape-style {
+ fill: #737373;
+ }
+ .glyphShape-style-pin {
+ fill: #b4b4b4;
+ }
+ .glyphShape-style-hover-gear {
+ fill: #4a90e2;
+ }
+ .glyphShape-style-hover-pin {
+ fill: #4a90e2;
+ }
+ .glyphShape-style-hover-delete {
+ fill: #ea0000;
+ }
+ .glyphShape-style-hover-active {
+ fill: #231f20;
+ }
+ /* Circle Background Styles */
+ .glyphShape-style-circle {
+ fill: #fff;
+ }
+ .glyphShape-style-circle-dropshadow {
+ fill: #000;
+ fill-opacity: .5;
+ filter: url(#filter-shadow-drop);
+ }
+ </style>
+ <filter id="filter-shadow-drop" x="-10%" y="-10%" width="120%" height="120%">
+ <feOffset in="SourceAlpha" dx="0" dy=".75" result="filter-shadow-drop-offset" />
+ <feGaussianBlur in="filter-shadow-drop-offset" stdDeviation="1" result="filter-shadow-drop-blur"/>
+ </filter>
+ <path id="glyphShape-gear" d="M28,16c0-1.7,0.9-3.1,2-3.3c-0.4-1.5-0.9-2.9-1.7-4.2c-0.9,0.7-2.6,0.3-3.8-0.9c-1.2-1.2-1.6-2.8-0.9-3.8 c-1.3-0.8-2.7-1.4-4.2-1.7c-0.2,1.1-1.6,2-3.3,2S13,3.1,12.8,2c-1.5,0.4-2.9,0.9-4.2,1.7c0.7,0.9,0.3,2.6-0.9,3.8 c-1.4,1.1-3,1.5-4,0.9C2.9,9.7,2.4,11.2,2,12.7c1.1,0.2,2,1.6,2,3.3s-0.9,3.1-2,3.3c0.4,1.5,0.9,2.9,1.7,4.2 c0.9-0.7,2.6-0.3,3.8,0.9c1.2,1.2,1.6,2.8,0.9,3.8c1.3,0.8,2.7,1.4,4.2,1.7c0.2-1.1,1.6-2,3.3-2s3.1,0.9,3.3,2 c1.5-0.4,2.9-0.9,4.2-1.7c-0.7-0.9-0.3-2.6,0.9-3.8c1.3-1.2,2.8-1.6,3.8-0.9c0.8-1.3,1.4-2.7,1.7-4.2C28.9,19.1,28,17.7,28,16z M16,24c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S20.4,24,16,24z"/>
+ <circle id="glyphShape-circle" cx="16" cy="16" r="14"/>
+ <path id="glyphShape-pin" d="M19,15v-5h2V7H11v3h2v5l-3,2v2h5c0,4.5,0.4,8,1,8s1-3.5,1-8h5v-2L19,15z"/>
+ <polygon id="glyphShape-delete" points="23,11 21,9 16,14 11,9 9,11 14,16 9,21 11,23 16,18 21,23 23,21 18,16"/>
+ </defs>
+ <g id="icon-gear-default">
+ <use xlink:href="#glyphShape-gear" class="glyphShape-style"/>
+ </g>
+ <g id="icon-gear-default" transform="translate(32)">
+ <use xlink:href="#glyphShape-gear" class="glyphShape-style-hover-gear"/>
+ </g>
+ <g id="icon-pin-default" transform="translate(64)">
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow"/>
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle"/>
+ <use xlink:href="#glyphShape-pin" class="glyphShape-style"/>
+ </g>
+ <g id="icon-pin-hover" transform="translate(96)">
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow"/>
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle"/>
+ <use xlink:href="#glyphShape-pin" class="glyphShape-style-hover-pin"/>
+ </g>
+ <g id="icon-pin-hover-active" transform="translate(128)">
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow"/>
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle"/>
+ <use xlink:href="#glyphShape-pin" class="glyphShape-style-hover-active"/>
+ </g>
+ <g id="icon-delete-default" transform="translate(160)">
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow"/>
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle"/>
+ <use xlink:href="#glyphShape-delete" class="glyphShape-style"/>
+ </g>
+ <g id="icon-delete-hover" transform="translate(192)">
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow"/>
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle"/>
+ <use xlink:href="#glyphShape-delete" class="glyphShape-style-hover-delete"/>
+ </g>
+ <g id="icon-delete-hover-active" transform="translate(224)">
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow"/>
+ <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle"/>
+ <use xlink:href="#glyphShape-delete" class="glyphShape-style-hover-active"/>
+ </g>
+ <g id="icon-pin-default" transform="translate(256)">
+ <use xlink:href="#glyphShape-pin" class="glyphShape-style-pin"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/newtab/newTab.inc.css b/browser/themes/shared/newtab/newTab.inc.css
new file mode 100644
index 000000000..8ecd603c9
--- /dev/null
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -0,0 +1,344 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+:root {
+ -moz-appearance: none;
+ font-size: 75%;
+ background-color: transparent;
+}
+
+/* UNDO */
+#newtab-undo-container {
+ padding: 4px 3px;
+ border: 1px solid;
+ border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
+ background-color: rgba(255,255,255,.4);
+ color: #525e69;
+}
+
+#newtab-undo-label {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.newtab-undo-button {
+ -moz-appearance: none;
+ cursor: pointer;
+ padding: 0;
+ margin: 0 4px;
+ border: 0;
+ background: transparent;
+ text-decoration: none;
+ min-width: 0;
+}
+
+.newtab-undo-button:hover {
+ text-decoration: underline;
+}
+
+.newtab-undo-button:-moz-focusring {
+ outline: 1px dotted;
+}
+
+#newtab-undo-close-button {
+ -moz-appearance: none;
+ padding: 0;
+ border: none;
+}
+
+#newtab-undo-close-button {
+ -moz-appearance: none;
+ padding: 0;
+ border: none;
+ height: 16px;
+ width: 16px;
+ float: right;
+ right: 0;
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 16, 16, 0);
+ background-color: transparent;
+}
+
+#newtab-undo-close-button:hover {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 32, 16, 16);
+}
+
+#newtab-undo-close-button:hover:active {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/close.png), 0, 48, 16, 32);
+}
+
+/* CUSTOMIZE */
+#newtab-customize-button,
+.newtab-customize {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 32, 32, 0);
+ background-size: 28px;
+ height: 38px;
+ width: 38px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-color: transparent;
+ border: none;
+}
+
+.newtab-customize {
+ height: 28px;
+ width: 28px;
+}
+
+#newtab-customize-button {
+ font-size: 28px;
+ padding: 0;
+ /* only display the text label when CSS backgrounds are disabled (e.g. in high contrast mode) */
+ color: transparent;
+}
+
+#newtab-customize-button:-moz-any(:hover, :active, [active]) {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 64, 32, 32);
+ background-color: #FFFFFF;
+ border: solid 1px #CCCCCC;
+ border-radius: 2px;
+}
+
+/* GRID */
+#topsites-heading {
+ color: #7A7A7A;
+ font-size: 1em;
+ font-weight: normal;
+ /* Position the heading such that it doesn't affect how many cells we
+ can fit into the grid. */
+ position: absolute;
+ /* The top margin moves the heading away from the grid.
+ The horizontal margin aligns the heading with the cells. */
+ margin: -1em 10px 0;
+}
+
+/* CELLS */
+.newtab-cell {
+ --cell-corner-radius: 8px;
+ background-color: rgba(255,255,255,.2);
+ border-radius: var(--cell-corner-radius);
+}
+
+body.compact .newtab-cell {
+ --cell-corner-radius: 2px;
+}
+
+.newtab-cell:empty {
+ outline: 2px dashed #c1c1c1;
+ outline-offset: -2px;
+ -moz-outline-radius: var(--cell-corner-radius);
+}
+
+/* SITES */
+.newtab-site {
+ border-radius: var(--cell-corner-radius);
+ box-shadow: 0 1px 3px #c1c1c1;
+ text-decoration: none;
+ transition-property: top, left, opacity, box-shadow, background-color;
+}
+
+.newtab-cell:not([ignorehover]) .newtab-control:hover ~ .newtab-link,
+.newtab-cell:not([ignorehover]) .newtab-link:hover,
+.newtab-site[dragged] {
+ border: 2px solid white;
+ box-shadow: 0 0 6px 1px #add6ff;
+ margin: -2px;
+}
+
+.newtab-site[dragged] {
+ transition-property: box-shadow, background-color;
+ background-color: rgb(242,242,242);
+}
+
+/* LINKS */
+.newtab-link {
+ border-radius: var(--cell-corner-radius);
+ overflow: hidden;
+}
+
+/***
+ * If you change the sizes here, change them in newTab.css
+ * and the preference values:
+ * toolkit.pageThumbs.minWidth
+ * toolkit.pageThumbs.minHeight
+ */
+/* THUMBNAILS */
+.newtab-thumbnail {
+ background-origin: padding-box;
+ background-clip: padding-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+ height: 180px;
+ transition: opacity 100ms ease-out;
+}
+
+body.compact .newtab-thumbnail {
+ height: 100%;
+ border-radius: calc(var(--cell-corner-radius) + 1px);
+ outline: 1px solid hsla(0,0%,0%,.1);
+ -moz-outline-radius: var(--cell-corner-radius);
+ outline-offset: -1px;
+}
+
+.newtab-thumbnail.placeholder {
+ color: white;
+ font-size: 85px;
+ line-height: 200%;
+}
+
+body.compact .newtab-thumbnail.placeholder {
+ font-size: 45px;
+}
+
+.newtab-cell:not([ignorehover]) .newtab-site:hover .newtab-thumbnail.enhanced-content {
+ opacity: 0;
+}
+
+.newtab-site[type=affiliate] .newtab-thumbnail,
+.newtab-site[type=enhanced] .newtab-thumbnail,
+.newtab-site[type=organic] .newtab-thumbnail,
+.newtab-site[type=sponsored] .newtab-thumbnail {
+ background-position: center center;
+}
+
+body.compact .newtab-site[type=affiliate] .newtab-thumbnail {
+ background-position: center 30%;
+}
+
+.newtab-site[type=affiliate] .newtab-thumbnail,
+body:not(.compact) .newtab-site[type=enhanced] .newtab-thumbnail,
+body:not(.compact) .newtab-site[type=organic] .newtab-thumbnail,
+body:not(.compact) .newtab-site[type=sponsored] .newtab-thumbnail {
+ background-size: auto;
+}
+
+/* TITLES */
+
+.newtab-title {
+ background-color: #F2F2F2;
+ font-size: 13px;
+ line-height: 30px;
+ border: 1px solid #fff;
+ border-radius: 0 0 var(--cell-corner-radius) var(--cell-corner-radius);
+}
+
+body.compact .newtab-title {
+ background-color: hsla(0,0%,100%,.85);
+ font-size: 12px;
+ line-height: 21px;
+ border: 1px solid hsla(0,0%,80%,.8);
+ border-top-color: hsla(0,0%,0%,.1);
+ background-clip: padding-box;
+}
+
+.newtab-title,
+.newtab-suggested {
+ color: #5c5c5c;
+}
+
+body.compact .newtab-title,
+body.compact .newtab-suggested {
+ color: black;
+}
+
+.newtab-suggested[active] {
+ background-color: rgba(51, 51, 51, 0.95);
+ border: 0;
+ color: white;
+}
+
+body:not(.compact) .newtab-site:hover .newtab-title {
+ color: white;
+ background-color: #333;
+ border-color: #333;
+ border-top-color: white;
+}
+
+body.compact .newtab-site:hover .newtab-title {
+ color: white;
+ background-color: hsla(0,0%,20%,.85);
+ border-color: hsla(0,0%,0%,.8);
+ border-top-color: white;
+}
+
+.newtab-site[pinned] .newtab-title {
+ padding-inline-start: 24px;
+}
+
+.newtab-site[pinned] .newtab-title::before {
+ background-image: -moz-image-rect(url("chrome://browser/skin/newtab/controls.svg"), 7, 278, 28, 266);
+ background-size: 10px;
+ content: "";
+ height: 17px;
+ left: 0;
+ position: absolute;
+ width: 10px;
+ margin-left: 8px;
+ margin-top: 6px;
+}
+
+.newtab-site[pinned] .newtab-title:dir(rtl)::before {
+ left: auto;
+ right: 0;
+}
+
+/* CONTROLS */
+.newtab-control {
+ background-color: transparent;
+ background-size: 24px;
+ border: none;
+ height: 24px;
+ width: 24px;
+ top: 4px;
+}
+
+.newtab-control-pin:dir(ltr),
+.newtab-control-block:dir(rtl) {
+ left: 4px;
+}
+
+.newtab-control-block:dir(ltr),
+.newtab-control-pin:dir(rtl) {
+ right: 4px;
+}
+
+body.compact .newtab-control {
+ top: -8px;
+}
+
+body.compact .newtab-control-pin:dir(ltr),
+body.compact .newtab-control-block:dir(rtl) {
+ left: -8px;
+}
+
+body.compact .newtab-control-block:dir(ltr),
+body.compact .newtab-control-pin:dir(rtl) {
+ right: -8px;
+}
+
+.newtab-control-pin,
+.newtab-site[pinned] .newtab-control-pin:hover:active {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 96, 32, 64);
+}
+
+.newtab-control-pin:hover,
+.newtab-site[pinned] .newtab-control-pin:hover {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 160, 32, 128);
+}
+
+.newtab-control-pin:hover:active,
+.newtab-site[pinned] .newtab-control-pin {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 128, 32, 96);
+}
+
+.newtab-control-block {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 192, 32, 160);
+}
+
+.newtab-control-block:hover {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 224, 32, 192);
+}
+
+.newtab-control-block:hover:active {
+ background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 256, 32, 224);
+}
diff --git a/browser/themes/shared/newtab/whimsycorn.png b/browser/themes/shared/newtab/whimsycorn.png
new file mode 100644
index 000000000..5c5c2f498
--- /dev/null
+++ b/browser/themes/shared/newtab/whimsycorn.png
Binary files differ
diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css
new file mode 100644
index 000000000..595e911b6
--- /dev/null
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -0,0 +1,318 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+#notification-popup-box {
+ padding: 5px 0px;
+ margin: -5px 0px;
+ margin-inline-end: -5px;
+ padding-inline-end: 5px;
+}
+
+.notification-anchor-icon,
+#blocked-permissions-container > .blocked-permission-icon {
+ width: 16px;
+ height: 16px;
+ margin-inline-start: 2px;
+}
+
+/* This class can be used alone or in combination with the class defining the
+ type of icon displayed. This rule must be defined before the others in order
+ for its list-style-image to be overridden. */
+.notification-anchor-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#default-info);
+}
+
+.popup-notification-icon {
+ width: 64px;
+ height: 64px;
+ margin-inline-end: 10px;
+}
+
+.notification-anchor-icon:not(.plugin-blocked):-moz-lwtheme,
+#blocked-permissions-container > .blocked-permission-icon:-moz-lwtheme {
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
+
+.notification-anchor-icon:not(.plugin-blocked):not(:hover) {
+ opacity: .8;
+}
+
+/* INDIVIDUAL NOTIFICATIONS */
+
+.popup-notification-icon[popupid="web-notifications"],
+.desktop-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#desktop-notification);
+}
+
+.desktop-notification-icon.blocked-permission-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#desktop-notification-blocked);
+}
+
+.geo-icon {
+%ifdef XP_MACOSX
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx);
+%elif defined(MOZ_WIDGET_GTK)
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-linux);
+%else
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-windows);
+%endif
+}
+
+.geo-icon.blocked-permission-icon {
+%ifdef XP_MACOSX
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx-blocked);
+%elif defined(MOZ_WIDGET_GTK)
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-linux-blocked);
+%else
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-windows-blocked);
+%endif
+}
+
+.popup-notification-icon[popupid="geolocation"] {
+%ifdef XP_MACOSX
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-osx);
+%elif defined(MOZ_WIDGET_GTK)
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-linux-detailed);
+%else
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#geo-windows-detailed);
+%endif
+}
+
+.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
+.indexedDB-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB);
+}
+
+.indexedDB-icon.blocked-permission-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#indexedDB-blocked);
+}
+
+.login-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#login);
+}
+
+.popup-notification-icon[popupid="password"] {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#login-detailed);
+}
+
+.camera-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#camera);
+}
+
+.camera-icon.in-use {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#camera-sharing);
+}
+
+.camera-icon.blocked-permission-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#camera-blocked);
+}
+
+.microphone-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone);
+}
+
+.microphone-icon.in-use {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone-sharing);
+}
+
+.microphone-icon.blocked-permission-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone-blocked);
+}
+
+.popup-notification-icon.microphone-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#microphone-detailed);
+}
+
+.screen-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#screen);
+}
+
+.screen-icon.in-use {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#screen-sharing);
+}
+
+.screen-icon.blocked-permission-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#screen-blocked);
+}
+
+#webRTC-preview:not([hidden]) {
+ display: -moz-stack;
+ border-radius: 4px;
+ border: 1px solid GrayText;
+ overflow: hidden;
+ min-width: 300px;
+ min-height: 10em;
+}
+
+html|*#webRTC-previewVideo {
+ width: 300px;
+ /* If we don't set the min-width, width is ignored. */
+ min-width: 300px;
+ max-height: 200px;
+}
+
+#webRTC-previewWarning {
+ background: rgba(255, 217, 99, .8) url("chrome://browser/skin/warning-white.svg") no-repeat .75em .75em;
+ margin: 0;
+ padding: .5em;
+ padding-inline-start: calc(1.5em + 16px);
+ border-top: 1px solid GrayText;
+}
+
+#webRTC-previewWarning > .text-link {
+ margin-inline-start: 0;
+}
+
+/* This icon has a block sign in it, so we don't need a blocked version. */
+.popup-icon {
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#popup");
+}
+
+/* EME */
+
+.popup-notification-icon[popupid="drmContentPlaying"],
+.drm-icon {
+ list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
+}
+
+.drm-icon:hover:active {
+ list-style-image: url("chrome://browser/skin/drm-icon.svg#chains-pressed");
+}
+
+#eme-notification-icon[firstplay=true] {
+ animation: emeTeachingMoment 0.2s linear 0s 5 normal;
+}
+
+@keyframes emeTeachingMoment {
+ 0% {transform: translateX(0); }
+ 25% {transform: translateX(3px) }
+ 75% {transform: translateX(-3px) }
+ 100% { transform: translateX(0); }
+}
+
+/* INSTALL ADDONS */
+
+.install-icon {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg);
+}
+
+.popup-notification-icon[popupid="xpinstall-disabled"],
+.popup-notification-icon[popupid="addon-install-blocked"],
+.popup-notification-icon[popupid="addon-install-origin-blocked"] {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-blocked.svg);
+}
+
+.popup-notification-icon[popupid="addon-progress"] {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-downloading.svg);
+}
+
+.popup-notification-icon[popupid="addon-install-failed"] {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-error.svg);
+}
+
+.popup-notification-icon[popupid="addon-install-confirmation"] {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-confirm.svg);
+}
+
+#addon-install-confirmation-notification[warning] .popup-notification-icon[popupid="addon-install-confirmation"] {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-warning.svg);
+}
+
+.popup-notification-icon[popupid="addon-install-complete"] {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-installed.svg);
+}
+
+.popup-notification-icon[popupid="addon-install-restart"] {
+ list-style-image: url(chrome://browser/skin/addons/addon-install-restart.svg);
+}
+
+.popup-notification-icon[popupid="click-to-play-plugins"] {
+ list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
+}
+
+/* OFFLINE APPS */
+
+.popup-notification-icon[popupid*="offline-app-requested"],
+.popup-notification-icon[popupid="offline-app-usage"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+
+/* PLUGINS */
+
+.plugin-icon {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin);
+}
+
+.plugin-icon.plugin-blocked {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin-blocked);
+}
+
+#notification-popup-box[hidden] {
+ /* Override display:none to make the pluginBlockedNotification animation work
+ when showing the notification repeatedly. */
+ display: -moz-box;
+ visibility: collapse;
+}
+
+#plugins-notification-icon.plugin-blocked[showing] {
+ animation: pluginBlockedNotification 500ms ease 0s 5 alternate both;
+}
+
+@keyframes pluginBlockedNotification {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+/* SOCIAL API */
+
+.popup-notification-icon[popupid="servicesInstall"] {
+ list-style-image: url(chrome://browser/skin/social/services-64.png);
+}
+
+.service-icon {
+ list-style-image: url(chrome://browser/skin/social/services-16.png);
+}
+
+%ifdef XP_MACOSX
+@media (min-resolution: 1.1dppx) {
+ .popup-notification-icon[popupid="servicesInstall"] {
+ list-style-image: url(chrome://browser/skin/social/services-64@2x.png);
+ }
+
+ .service-icon {
+ list-style-image: url(chrome://browser/skin/social/services-16@2x.png);
+ }
+}
+%endif
+
+/* TRANSLATION */
+
+.translation-icon {
+ list-style-image: url(chrome://browser/skin/translation-16.png);
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.translation-icon.in-use {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+%ifdef XP_MACOSX
+@media (min-resolution: 1.1dppx) {
+ .translation-icon {
+ list-style-image: url(chrome://browser/skin/translation-16@2x.png);
+ -moz-image-region: rect(0px, 32px, 32px, 0px);
+ }
+
+ .translation-icon.in-use {
+ -moz-image-region: rect(0px, 64px, 32px, 32px);
+ }
+}
+%endif
diff --git a/browser/themes/shared/notification-icons.svg b/browser/themes/shared/notification-icons.svg
new file mode 100644
index 000000000..206157cf2
--- /dev/null
+++ b/browser/themes/shared/notification-icons.svg
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ class="fieldtext"
+ width="32" height="32" viewBox="0 0 32 32">
+#include icon-colors.inc.svg
+ <style>
+ :root > use:not(:target),
+ :root > g:not(:target),
+ #strikeout {
+ display: none;
+ }
+ .blocked:target ~ #strikeout {
+ display: block;
+ }
+ .blocked {
+ clip-path: url(#blocked-clipPath);
+ }
+
+ #login-highlighted {
+ fill: HighlightText;
+ fill-opacity: 1;
+ }
+
+ #plugin-blocked,
+ #plugin-blocked:target ~ #strikeout {
+ fill: #d92215;
+ fill-opacity: 1;
+ }
+
+ #camera-sharing,
+ #microphone-sharing,
+ #screen-sharing {
+ fill: rgb(224, 41, 29);
+ fill-opacity: 1;
+ }
+ </style>
+
+ <defs>
+ <path id="camera-icon" d="m 2,23 a 3,3 0 0 0 3,3 l 14,0 a 3,3 0 0 0 3,-3 l 0,-4 6,5.5 c 0.5,0.5 1,0.7 2,0.5 l 0,-18 c -1,-0.2 -1.5,0 -2,0.5 l -6,5.5 0,-4 a 3,3 0 0 0 -3,-3 l -14,0 a 3,3 0 0 0 -3,3 z" />
+ <path id="desktop-notification-icon" d="m 2,20 a 4,4 0 0 0 4,4 l 13,0 7,7 0,-7 a 4,4 0 0 0 4,-4 l 0,-12 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 5,-2 a 1,1 0 1 1 0,-2 l 10,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 14,0 a 1,1 0 1 1 0,2 z m 0,-4 a 1,1 0 1 1 0,-2 l 18,0 a 1,1 0 1 1 0,2 z" />
+ <path id="geo-linux-icon" d="m 2,15.9 a 14,14 0 1 1 0,0.2 z m 4,2.1 a 10,10 0 0 0 8,8 l 0,-4 4,0 0,4 a 10,10 0 0 0 8,-8 l -4,0 0,-4 4,0 a 10,10 0 0 0 -8,-8 l 0,4 -4,0 0,-4 a 10,10 0 0 0 -8,8 l 4,0 0,4 z" />
+ <path id="geo-linux-detailed-icon" d="m 2,15.9 a 14,14 0 1 1 0,0.2 z m 3,2.1 a 11,11 0 0 0 9,9 l 1,-5 2,0 1,5 a 11,11 0 0 0 9,-9 l -5,-1 0,-2 5,-1 a 11,11 0 0 0 -9,-9 l -1,5 -2,0 -1,-5 a 11,11 0 0 0 -9,9 l 5,1 0,2 z" />
+ <path id="geo-osx-icon" d="m 0,16 16,0 0,16 12,-28 z" />
+ <path id="geo-windows-icon" d="m 2,14 0,4 2,0 a 12,12 0 0 0 10,10 l 0,2 4,0 0,-2 a 12,12 0 0 0 10,-10 l 2,0 0,-4 -2,0 a 12,12 0 0 0 -10,-10 l 0,-2 -4,0 0,2 a 12,12 0 0 0 -10,10 z m 4,1.9 a 10,10 0 1 1 0,0.2 z m 4,0 a 6,6 0 1 1 0,0.2 z" />
+ <path id="geo-windows-detailed-icon" d="m 2,14.5 0,3 2,0.5 a 12,12 0 0 0 10,10 l 0.5,2 3,0 0.5,-2 a 12,12 0 0 0 10,-10 l 2,-0.5 0,-3 -2,-0.5 a 12,12 0 0 0 -10,-10 l -0.5,-2 -3,0 -0.5,2 a 12,12 0 0 0 -10,10 z m 4,1.4 a 10,10 0 1 1 0,0.2 z m 3,0 a 7,7 0 1 1 0,0.2 z" />
+ <path id="indexedDB-icon" d="m 2,24 a 4,4 0 0 0 4,4 l 2,0 0,-4 -2,0 0,-16 20,0 0,16 -2,0 0,4 2,0 a 4,4 0 0 0 4,-4 l 0,-16 a 4,4 0 0 0 -4,-4 l -20,0 a 4,4 0 0 0 -4,4 z m 8,-2 6,7 6,-7 -4,0 0,-8 -4,0 0,8 z" />
+ <path id="login-icon" d="m 2,26 0,4 6,0 0,-2 2,0 0,-2 1,0 0,-1 2,0 0,-3 2,0 2.5,-2.5 1.5,1.5 3,-3 a 8,8 0 1 0 -8,-8 l -3,3 2,2 z m 20,-18.1 a 2,2 0 1 1 0,0.2 z" />
+ <path id="login-detailed-icon" d="m 1,27 0,3.5 a 0.5,0.5 0 0 0 0.5,0.5 l 5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1.5,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1.5 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-1 1,0 a 0.5,0.5 0 0 0 0.5,-0.5 l 0,-2 2,0 2.5,-2.5 q 0.5,-0.5 1,0 l 1,1 c 0.5,0.5 1,0.5 1.5,-0.5 l 1,-2 a 9,9 0 1 0 -8,-8 l -2,1 c -1,0.5 -1,1 -0.5,1.5 l 1.5,1.5 q 0.5,0.5 0,1 z m 21,-19.1 a 2,2 0 1 1 0,0.2 z" />
+ <path id="microphone-icon" d="m 8,14 0,4 a 8,8 0 0 0 6,7.7 l 0,2.3 -2,0 a 2,2 0 0 0 -2,2 l 12,0 a 2,2 0 0 0 -2,-2 l -2,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 -2,0 0,4 a 6,6 0 0 1 -12,0 l 0,-4 z m 4,4 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
+ <path id="microphone-detailed-icon" d="m 8,18 a 8,8 0 0 0 6,7.7 l 0,2.3 -1,0 a 3,2 0 0 0 -3,2 l 12,0 a 3,2 0 0 0 -3,-2 l -1,0 0,-2.3 a 8,8 0 0 0 6,-7.7 l 0,-4 a 1,1 0 0 0 -2,0 l 0,4 a 6,6 0 0 1 -12,0 l 0,-4 a 1,1 0 0 0 -2,0 z m 4,0 a 4,4 0 0 0 8,0 l 0,-12 a 4,4 0 0 0 -8,0 z" />
+ <path id="plugin-icon" d="m 2,26 a 2,2 0 0 0 2,2 l 24,0 a 2,2 0 0 0 2,-2 l 0,-16 a 2,2 0 0 0 -2,-2 l -24,0 a 2,2 0 0 0 -2,2 z m 2,-20 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z m 14,0 10,0 0,-2 a 2,2 0 0 0 -2,-2 l -6,0 a 2,2 0 0 0 -2,2 z" />
+ <path id="popup-icon" d="m 2,24 a 4,4 0 0 0 4,4 l 8,0 a 10,10 0 0 1 -2,-4 l -4,0 a 2,2 0 0 1 -2,-2 l 0,-12 18,0 0,2 a 10,10 0 0 1 4,2 l 0,-8 a 4,4 0 0 0 -4,-4 l -18,0 a 4,4 0 0 0 -4,4 z m 12,-2.1 a 8,8 0 1 1 0,0.2 m 10.7,-4.3 a 5,5 0 0 0 -6.9,6.9 z m -5.4,8.4 a 5,5 0 0 0 6.9,-6.9 z" />
+ <path id="screen-icon" d="m 2,18 a 2,2 0 0 0 2,2 l 2,0 0,-6 a 4,4 0 0 1 4,-4 l 14,0 0,-6 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z m 6,10 a 2,2 0 0 0 2,2 l 18,0 a 2,2 0 0 0 2,-2 l 0,-14 a 2,2 0 0 0 -2,-2 l -18,0 a 2,2 0 0 0 -2,2 z" />
+
+ <clipPath id="blocked-clipPath">
+ <path d="m 0,0 0,31 31,-31 z m 6,32 26,0 0,-26 z"/>
+ </clipPath>
+
+ <mask id="i-mask" style="fill-opacity: 1;">
+ <rect fill="white" width="32" height="32"/>
+ <circle fill="black" cx="16" cy="9" r="2.5"/>
+ <rect fill="black" x="14" y="14" width="4" height="10" rx="2" ry="2"/>
+ </mask>
+ </defs>
+
+ <g id="default-info">
+ <circle cx="16" cy="16" r="14" mask="url(#i-mask)"/>
+ </g>
+
+ <use id="camera" xlink:href="#camera-icon" />
+ <use id="camera-sharing" xlink:href="#camera-icon"/>
+ <use id="camera-blocked" class="blocked" xlink:href="#camera-icon" />
+ <use id="desktop-notification" xlink:href="#desktop-notification-icon" />
+ <use id="desktop-notification-blocked" class="blocked" xlink:href="#desktop-notification-icon" />
+ <use id="geo-osx" xlink:href="#geo-osx-icon" />
+ <use id="geo-osx-blocked" class="blocked" xlink:href="#geo-osx-icon" />
+ <use id="geo-linux" xlink:href="#geo-linux-icon" />
+ <use id="geo-linux-blocked" class="blocked" xlink:href="#geo-linux-icon" />
+ <use id="geo-linux-detailed" xlink:href="#geo-linux-detailed-icon" />
+ <use id="geo-windows" xlink:href="#geo-windows-icon" />
+ <use id="geo-windows-blocked" class="blocked" xlink:href="#geo-windows-icon" />
+ <use id="geo-windows-detailed" xlink:href="#geo-windows-detailed-icon" />
+ <use id="indexedDB" xlink:href="#indexedDB-icon" />
+ <use id="indexedDB-blocked" class="blocked" xlink:href="#indexedDB-icon" />
+ <use id="login" xlink:href="#login-icon" />
+ <use id="login-highlighted" class="highlighted" xlink:href="#login-icon" />
+ <use id="login-detailed" xlink:href="#login-detailed-icon" />
+ <use id="microphone" xlink:href="#microphone-icon" />
+ <use id="microphone-sharing" xlink:href="#microphone-icon"/>
+ <use id="microphone-blocked" class="blocked" xlink:href="#microphone-icon" />
+ <use id="microphone-detailed" xlink:href="#microphone-detailed-icon" />
+ <use id="plugin" xlink:href="#plugin-icon" />
+ <use id="plugin-blocked" class="blocked" xlink:href="#plugin-icon" />
+ <use id="popup" xlink:href="#popup-icon" />
+ <use id="screen" xlink:href="#screen-icon" />
+ <use id="screen-sharing" xlink:href="#screen-icon"/>
+ <use id="screen-blocked" class="blocked" xlink:href="#screen-icon" />
+
+ <path id="strikeout" d="m 2,28 2,2 26,-26 -2,-2 z"/>
+</svg>
diff --git a/browser/themes/shared/panel-icons.svg b/browser/themes/shared/panel-icons.svg
new file mode 100644
index 000000000..2092e0f64
--- /dev/null
+++ b/browser/themes/shared/panel-icons.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+ width="32" height="32" viewBox="0 0 32 32">
+ <style>
+ path:not(:target) {
+ display: none;
+ }
+ </style>
+ <path id="cancel" d="m 6,9.5 6.5,6.5 -6.5,6.5 3.5,3.5 6.5,-6.5 6.5,6.5 3.5,-3.5 -6.5,-6.5 6.5,-6.5 -3.5,-3.5 -6.5,6.5 -6.5,-6.5 z" />
+ <path id="folder" d="M17.3,9.4c0,0,1.1,0,3.7,0c1.7,0,2,0,5.6,0c0.6,0,0.6,0,1.1,0V9.2c0-1.5-0.9-2.6-2-2.6h-5.8V6.3c0-0.6-1.5-2-2.8-2h-7.1 H7.6H4.9v2.4v2.4v2.2c2.8,0,8.5,0,8.5,0C16.4,11.3,17.3,9.4,17.3,9.4z M29,13c0-0.6-0.6-1.1-1.5-1.7l0,0c-0.2,0-0.6,0-0.9,0 c-2.8,0-3,0-4.8,0c-1.9,0-3.3,0-3.3,0s-1.5,2.4-3.7,2.4c0,0-6.5,0-9.1,0H5.4C3,13.7,3,15.9,3,15.9l1.1,9.7C4.1,27.1,5,28,6.5,28 h19.1c1.5,0,2.4-0.9,2.4-2.4L29,13.7l0,0l0,0C29,13.7,29,13,29,13z" />
+ <path id="magnifier" d="M12.9,2c6,0,11,5,11,11c0,2.2-0.6,4.2-1.8,6l7.2,7c0.8,0.8,0.8,2.4,0,3.2c-0.6,0.6-1.2,0.8-1.6,0.8s-1.2-0.2-1.6-0.6l-7-7 c-1.8,1.2-3.8,1.8-6,1.8c-6,0-11-5-11-11C2.1,7.2,6.9,2,12.9,2z M12.9,20c3.8,0,7-3.2,7-7s-3.2-7-7-7s-7,3.2-7,7S9.1,20,12.9,20z" />
+ <path id="retry" d="M28,16.5v-14l-5,4.8c-1.8-1.4-4.4-2.4-7-2.4c-6.4,0-11.8,5.2-11.8,11.8c0,6.4,5.2,11.8,11.8,11.8c3.4,0,6.2-1.4,8.2-3.6 l-3.4-3.4c-1.2,1.2-3,1.8-5,1.8c-3.6,0.2-6.8-2.8-6.8-6.8c0-3.8,3-7.2,7-7.2c1.4,0,2.6,0.4,3.6,1l-6,6.2H28z"/>
+ <path id="arrow-left" d="M23.5,25l-9-9l9-9l-3-3l-12,12l12,12L23.5,25z" />
+ <path id="arrow-right" d="M11.6,28l12-12l-12-12l-3,3l9,9l-9,9L11.6,28z" />
+</svg>
diff --git a/browser/themes/shared/panic-panel/header-small.png b/browser/themes/shared/panic-panel/header-small.png
new file mode 100644
index 000000000..ef1956fb8
--- /dev/null
+++ b/browser/themes/shared/panic-panel/header-small.png
Binary files differ
diff --git a/browser/themes/shared/panic-panel/header-small@2x.png b/browser/themes/shared/panic-panel/header-small@2x.png
new file mode 100644
index 000000000..ae445a4a9
--- /dev/null
+++ b/browser/themes/shared/panic-panel/header-small@2x.png
Binary files differ
diff --git a/browser/themes/shared/panic-panel/header.png b/browser/themes/shared/panic-panel/header.png
new file mode 100644
index 000000000..edac3e6d6
--- /dev/null
+++ b/browser/themes/shared/panic-panel/header.png
Binary files differ
diff --git a/browser/themes/shared/panic-panel/header@2x.png b/browser/themes/shared/panic-panel/header@2x.png
new file mode 100644
index 000000000..f137928e5
--- /dev/null
+++ b/browser/themes/shared/panic-panel/header@2x.png
Binary files differ
diff --git a/browser/themes/shared/panic-panel/icons.png b/browser/themes/shared/panic-panel/icons.png
new file mode 100644
index 000000000..f7f9a3e08
--- /dev/null
+++ b/browser/themes/shared/panic-panel/icons.png
Binary files differ
diff --git a/browser/themes/shared/panic-panel/icons@2x.png b/browser/themes/shared/panic-panel/icons@2x.png
new file mode 100644
index 000000000..959ebf586
--- /dev/null
+++ b/browser/themes/shared/panic-panel/icons@2x.png
Binary files differ
diff --git a/browser/themes/shared/plugin-doorhanger.inc.css b/browser/themes/shared/plugin-doorhanger.inc.css
new file mode 100644
index 000000000..dd315bb8b
--- /dev/null
+++ b/browser/themes/shared/plugin-doorhanger.inc.css
@@ -0,0 +1,65 @@
+#notification-popup[popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0px;
+}
+
+.click-to-play-plugins-notification-center-box {
+ border: 1px solid ThreeDShadow;
+ margin: 10px;
+}
+
+.plugin-popupnotification-centeritem:nth-child(odd) {
+ background-color: rgba(0,0,0,0.1);
+}
+
+.center-item-label {
+ margin-inline-start: 6px;
+ margin-bottom: 0;
+ text-overflow: ellipsis;
+}
+
+.center-item-warning-icon {
+ background-image: url("chrome://mozapps/skin/extensions/alerticon-info-negative.svg");
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 15px;
+ margin-inline-start: 6px;
+}
+
+.click-to-play-plugins-notification-button-container {
+ background-color: var(--arrowpanel-dimmed);
+ border-top: 1px solid var(--panel-separator-color);
+ padding: 10px;
+ margin-top: 5px;
+}
+
+.click-to-play-popup-button {
+ width: 50%;
+}
+
+.click-to-play-plugins-notification-description-box {
+ padding: 10px;
+}
+
+.click-to-play-plugins-outer-description {
+ margin-top: 8px;
+}
+
+.click-to-play-plugins-notification-link,
+.center-item-link {
+ margin: 0;
+}
+
+.messageImage[value="plugin-hidden"] {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin);
+}
+
+/* Keep any changes to this style in sync with pluginProblem.css */
+notification.pluginVulnerable {
+ background-color: rgb(72,72,72);
+ background-image: url(chrome://mozapps/skin/plugins/contentPluginStripe.png);
+ color: white;
+}
+
+notification.pluginVulnerable .messageImage {
+ list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin-blocked);
+}
diff --git a/browser/themes/shared/preferences/containers.css b/browser/themes/shared/preferences/containers.css
new file mode 100644
index 000000000..3fb965331
--- /dev/null
+++ b/browser/themes/shared/preferences/containers.css
@@ -0,0 +1,53 @@
+/* Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../../components/contextualidentity/content/usercontext.css
+
+:root {
+ --preference-selected-color: #0996f8;
+ --preference-unselected-color: #333;
+ --preference-active-color: #858585;
+}
+
+radiogroup {
+ display: flex;
+ margin-inline-start: 0.35rem;
+}
+
+radio {
+ flex: auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ -moz-user-select: none;
+ outline: 2px solid transparent;
+ outline-offset: 4px;
+ -moz-outline-radius: 100%;
+ min-block-size: 24px;
+ min-inline-size: 24px;
+ border-radius: 50%;
+ padding: 2px;
+ margin: 10px;
+}
+
+.icon-buttons > radio > [data-identity-icon] {
+ fill: #4d4d4d;
+}
+
+radio > [data-identity-icon] {
+ inline-size: 22px;
+ block-size: 22px;
+}
+
+radio[selected=true] {
+ outline-color: var(--preference-unselected-color);
+}
+
+radio[focused=true] {
+ outline-color: var(--preference-selected-color);
+}
+
+radio:hover:active {
+ outline-color: var(--preference-active-color);
+}
diff --git a/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
new file mode 100644
index 000000000..923aa929b
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://global/skin/in-content/info-pages.css");
+
+:root {
+ --color-grey-lightest: #fbfbfb;
+ --color-grey: #b1b1b1;
+
+ --color-blue: #0996f8;
+ --color-blue-dark: #0670cc;
+ --color-blue-darker: #005bab;
+
+ --icon-margin: 64px;
+}
+
+html.private {
+ --in-content-page-color: #beb8cc;
+ --in-content-text-color: #beb8cc;
+ --in-content-page-background: #1c023c;
+}
+
+body {
+ padding: 40px;
+}
+
+a:link {
+ color: var(--color-blue);
+ text-decoration: none;
+}
+
+a:hover {
+ color: var(--color-blue-dark);
+ text-decoration: underline;
+}
+
+a:hover:active {
+ color: var(--color-blue-darker);
+}
+
+a:visited {
+ color: var(--color-blue-darker);
+}
+
+.about-content-container {
+ max-width: 780px;
+}
+
+.section-main {
+ margin-bottom: 48px;
+ margin-inline-start: var(--icon-margin);
+ padding-inline-start: 24px;
+}
+
+.section-main:last-child {
+ margin-bottom: 0;
+}
+
+p {
+ line-height: 1.5em;
+}
+
+.list-row {
+ overflow: auto;
+}
+
+.list-row > ul > li {
+ float: left;
+ width: 220px;
+ line-height: 1.5em;
+ margin-inline-start: 1em;
+ margin-bottom: 0;
+}
+
+.list-row > ul > li:dir(rtl) {
+ float: right;
+}
+
+.title {
+ background-image: url("chrome://browser/skin/privatebrowsing/private-browsing.svg");
+ background-size: 64px;
+ background-position: left, center;
+ font-weight: lighter;
+ line-height: 1.5em;
+ min-height: 64px;
+ margin-inline-start: 0;
+ padding-inline-start: calc(var(--icon-margin) + 24px);
+}
+
+.title:dir(rtl) {
+ background-position: right, center;
+}
+
+.about-subheader {
+ display: flex;
+ align-items: center;
+ font-size: 1.5em;
+ font-weight: lighter;
+ min-height: 32px;
+ background-image: url("chrome://browser/skin/privatebrowsing/tracking-protection.svg");
+ background-repeat: no-repeat;
+ background-size: 32px;
+ margin-inline-start: calc(var(--icon-margin) - 32px);
+ padding-inline-start: 56px;
+}
+
+.about-subheader:dir(rtl) {
+ background-position: right;
+}
+
+.about-subheader.tp-off {
+ background-image: url("chrome://browser/skin/privatebrowsing/tracking-protection-off.svg");
+}
+
+.about-info {
+ font-size: .875em;
+}
+
+.tpTitle {
+ margin-inline-end: 12px;
+}
+
+.private strong {
+ color: var(--color-grey-lightest);
+ font-weight: normal;
+}
+
+a.button {
+ padding: 5px 40px;
+ background-color: #381e59;
+ border: 1px solid #43256a;
+ border-radius: 4px;
+ text-decoration: none;
+ display: inline-block;
+}
+
+/**
+ * We want to hide the checkbox in lieu of the toggle-btn
+ * "slider toggle". We need to make the toggle keyboard
+ * focusable, however, which is not possible if it's
+ * display:none. We work around this by making the toggle
+ * invisible but still present in the display list, allowing
+ * it to receive keyboard events. When it is focused by keyboard,
+ * we use the -moz-focusring selector on the invisible checkbox
+ * to show a focus ring around the slider toggle.
+ */
+.toggle-input {
+ opacity: 0;
+ width: 0;
+ pointer-events: none;
+ position: absolute;
+}
+
+.toggle + .toggle-btn {
+ box-sizing: border-box;
+ cursor: pointer;
+ min-width: 60px;
+ height: 24px;
+ border-radius: 12px;
+ background-color: var(--color-grey);
+ border: 1px var(--color-grey) solid;
+ padding: 2px;
+}
+
+.toggle + .toggle-btn::after,
+.toggle + .toggle-btn::before {
+ position: relative;
+ display: block;
+ content: "";
+ width: 19px;
+ height: 100%;
+}
+
+.toggle + .toggle-btn::after {
+ left: 0;
+ box-shadow: 0 0 1px 1px hsla(0, 0%, 0%, .1),
+ 0 1px 0 hsla(0, 0%, 0%, .2);
+ border-radius: 50%;
+ background: white;
+ transition: left .2s ease;
+}
+
+.toggle + .toggle-btn::before {
+ float: left;
+ left: 9px;
+ visibility: hidden;
+ background-size: 16px;
+ background-repeat: no-repeat;
+ background-color: transparent;
+ background-image: url("chrome://browser/skin/privatebrowsing/check.svg");
+}
+
+.toggle + .toggle-btn:dir(rtl)::after {
+ left: auto;
+ right: 0;
+ transition-property: right;
+}
+
+.toggle + .toggle-btn:dir(rtl)::before {
+ float: right;
+ left: auto;
+ right: 9px;
+}
+
+.toggle:checked + .toggle-btn {
+ background: #3fc455;
+ border: 1px solid #269939;
+}
+
+.toggle:checked + .toggle-btn::after {
+ left: 35px;
+}
+
+.toggle:checked + .toggle-btn:dir(rtl)::after {
+ right: 35px;
+}
+
+.toggle:checked + .toggle-btn::before {
+ visibility: visible;
+}
+
+.toggle:-moz-focusring + .toggle-btn {
+ outline: 2px solid rgba(0, 149, 221, 0.5);
+ outline-offset: 1px;
+ -moz-outline-radius: 2px;
+}
diff --git a/browser/themes/shared/privatebrowsing/attention.png b/browser/themes/shared/privatebrowsing/attention.png
new file mode 100755
index 000000000..8706928ff
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/attention.png
Binary files differ
diff --git a/browser/themes/shared/privatebrowsing/attention@2x.png b/browser/themes/shared/privatebrowsing/attention@2x.png
new file mode 100755
index 000000000..5b32888c8
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/attention@2x.png
Binary files differ
diff --git a/browser/themes/shared/privatebrowsing/check.png b/browser/themes/shared/privatebrowsing/check.png
new file mode 100755
index 000000000..59ca51b7b
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/check.png
Binary files differ
diff --git a/browser/themes/shared/privatebrowsing/check.svg b/browser/themes/shared/privatebrowsing/check.svg
new file mode 100644
index 000000000..a8e4bcc2b
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/check.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
+ <path fill="#fff" d="M30.057,9.752L15.9,23.909h0l-4.044,4.045-4.045-4.045h0l-6.068-6.067,4.045-4.045,6.068,6.067L26.012,5.707Z"/>
+</svg>
diff --git a/browser/themes/shared/privatebrowsing/check@2x.png b/browser/themes/shared/privatebrowsing/check@2x.png
new file mode 100755
index 000000000..031685cbf
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/check@2x.png
Binary files differ
diff --git a/browser/themes/shared/privatebrowsing/favicon.svg b/browser/themes/shared/privatebrowsing/favicon.svg
new file mode 100644
index 000000000..6de9c6450
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/favicon.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <circle cx="8" cy="8" r="8" fill="#8d20ae" />
+ <circle cx="8" cy="8" r="7.5" stroke="#7b149a" stroke-width="1" fill="none" />
+ <path d="M11.309,10.995C10.061,10.995,9.2,9.5,8,9.5s-2.135,1.5-3.309,1.5c-1.541,0-2.678-1.455-2.7-3.948C1.983,5.5,2.446,5.005,4.446,5.005S7.031,5.822,8,5.822s1.555-.817,3.555-0.817S14.017,5.5,14.006,7.047C13.987,9.54,12.85,10.995,11.309,10.995ZM5.426,6.911a1.739,1.739,0,0,0-1.716.953A2.049,2.049,0,0,0,5.3,8.544c0.788,0,1.716-.288,1.716-0.544A1.428,1.428,0,0,0,5.426,6.911Zm5.148,0A1.429,1.429,0,0,0,8.981,8c0,0.257.928,0.544,1.716,0.544a2.049,2.049,0,0,0,1.593-.681A1.739,1.739,0,0,0,10.574,6.911Z" stroke="#670c83" stroke-width="2" fill="none" />
+ <path d="M11.309,10.995C10.061,10.995,9.2,9.5,8,9.5s-2.135,1.5-3.309,1.5c-1.541,0-2.678-1.455-2.7-3.948C1.983,5.5,2.446,5.005,4.446,5.005S7.031,5.822,8,5.822s1.555-.817,3.555-0.817S14.017,5.5,14.006,7.047C13.987,9.54,12.85,10.995,11.309,10.995ZM5.426,6.911a1.739,1.739,0,0,0-1.716.953A2.049,2.049,0,0,0,5.3,8.544c0.788,0,1.716-.288,1.716-0.544A1.428,1.428,0,0,0,5.426,6.911Zm5.148,0A1.429,1.429,0,0,0,8.981,8c0,0.257.928,0.544,1.716,0.544a2.049,2.049,0,0,0,1.593-.681A1.739,1.739,0,0,0,10.574,6.911Z" fill="#fff" />
+</svg>
diff --git a/browser/themes/shared/privatebrowsing/private-browsing.svg b/browser/themes/shared/privatebrowsing/private-browsing.svg
new file mode 100644
index 000000000..cc37bc4f3
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/private-browsing.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
+ <ellipse cx="32" cy="34" rx="29.5" ry="30" fill="#000" fill-opacity=".1" />
+ <circle cx="32" cy="32" r="30" fill="#8d20ae" />
+ <circle cx="32" cy="32" r="29.5" stroke="#7b149a" stroke-width="1" fill="none" />
+ <path d="M45.225,43c-4.989,0-8.44-5.5-13.224-5.5S23.468,43,18.776,43C12.62,43,8.074,37.656,8,28.5,7.954,22.815,9.805,21,17.8,21S28.128,24,32,24s6.214-3,14.2-3,9.842,1.815,9.8,7.5C55.926,37.656,51.381,43,45.225,43ZM21.714,28c-4.857.193-6.857,2.846-6.857,3.5s3.22,2.5,6.367,2.5,6.857-1.057,6.857-2C28.082,30.948,26.3,27.818,21.714,28Zm20.572,0c-4.583-.182-6.367,2.948-6.367,4,0,0.943,3.709,2,6.857,2s6.367-1.846,6.367-2.5S47.143,28.193,42.286,28Z" stroke="#670c83" stroke-width="2" fill="none" />
+ <path d="M45.225,43c-4.989,0-8.44-5.5-13.224-5.5S23.468,43,18.776,43C12.62,43,8.074,37.656,8,28.5,7.954,22.815,9.805,21,17.8,21S28.128,24,32,24s6.214-3,14.2-3,9.842,1.815,9.8,7.5C55.926,37.656,51.381,43,45.225,43ZM21.714,28c-4.857.193-6.857,2.846-6.857,3.5s3.22,2.5,6.367,2.5,6.857-1.057,6.857-2C28.082,30.948,26.3,27.818,21.714,28Zm20.572,0c-4.583-.182-6.367,2.948-6.367,4,0,0.943,3.709,2,6.857,2s6.367-1.846,6.367-2.5S47.143,28.193,42.286,28Z" fill="#fff" />
+</svg>
diff --git a/browser/themes/shared/privatebrowsing/shield-page.png b/browser/themes/shared/privatebrowsing/shield-page.png
new file mode 100755
index 000000000..2ddcf34e9
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/shield-page.png
Binary files differ
diff --git a/browser/themes/shared/privatebrowsing/shield-page@2x.png b/browser/themes/shared/privatebrowsing/shield-page@2x.png
new file mode 100755
index 000000000..72a0b8273
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/shield-page@2x.png
Binary files differ
diff --git a/browser/themes/shared/privatebrowsing/tracking-protection-off.svg b/browser/themes/shared/privatebrowsing/tracking-protection-off.svg
new file mode 100644
index 000000000..7da0ca78a
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/tracking-protection-off.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <g fill="#ccc">
+ <path d="M28.8,0.3l-2.4,2.4L16.1,1.1L4.9,2.9c-1,0.2-1.8,1-1.8,2c0,2.5,0,6.9,0.3,8.7c0.4,4.3,1.2,6.9,2.7,9.4l-3.5,3.5l2,2
+ L30.8,2.3L28.8,0.3z M5.3,13.5c-0.2-1.9-0.2-6.2-0.2-8.6c0,0,0,0,0.1,0l10.9-1.8l8.6,1.4L16.1,13V5L7.2,6.6c-0.1,0-0.1,0-0.1,0
+ c0,2,0,5.6,0.2,7.1c0.3,3,0.8,4.9,1.6,6.5l-1.4,1.4C6.3,19.6,5.6,17.3,5.3,13.5z"/>
+ <path d="M16.1,20.3l-3.9,3.9c1.7,1.2,3.4,1.6,3.9,1.7V20.3z"/>
+ <path d="M26.9,13.4c-0.5,5.6-1.7,8-3.8,10.7c-2.4,3.1-6.1,3.9-7,4.1c-0.7-0.2-3.2-0.7-5.4-2.5L9.3,27c3.1,2.7,6.7,3,6.7,3
+ s5.2-0.5,8.6-4.9c2.5-3.2,3.6-5.9,4.2-11.6c0.1-1.3,0.2-4,0.2-6.3l-2,2C27,10.9,27,12.5,26.9,13.4z"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/privatebrowsing/tracking-protection.svg b/browser/themes/shared/privatebrowsing/tracking-protection.svg
new file mode 100644
index 000000000..d22fe7df3
--- /dev/null
+++ b/browser/themes/shared/privatebrowsing/tracking-protection.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <path fill="#ccc" d="M27.2,2.8L16,1L4.8,2.8C3.8,3,3,3.8,3,4.8c0,2.5,0,6.9,0.3,8.7C3.8,19,5,21.8,7.5,25.1C10.8,29.5,16,30,16,30
+ s5.2-0.5,8.6-4.9c2.5-3.2,3.6-5.9,4.2-11.6C29,11.7,29,7.2,29,4.8C29,3.8,28.2,3,27.2,2.8z M26.8,13.3L26.8,13.3L26.8,13.3
+ c-0.5,5.6-1.7,8-3.8,10.7c-2.4,3.1-6.1,3.9-7,4.1c-0.9-0.2-4.6-1-7-4.1c-2.1-2.8-3.3-5.2-3.8-10.6l0,0l0,0C5,11.5,5,7.2,5,4.8
+ c0,0,0,0,0.1,0l0,0l0,0L16,3l10.8,1.8l0,0l0,0c0.1,0,0.1,0,0.1,0C27,7,27,11.5,26.8,13.3z M7.1,6.5L7.1,6.5L7.1,6.5
+ C7,6.5,7,6.5,7.1,6.5C7,8.5,7,12.1,7.2,13.6l0,0l0,0c0.4,4.5,1.4,6.5,3.1,8.9c2,2.6,5,3.3,5.7,3.4v-21L7.1,6.5z"/>
+</svg>
diff --git a/browser/themes/shared/reader/reader-mode-16.png b/browser/themes/shared/reader/reader-mode-16.png
new file mode 100644
index 000000000..12e2fd991
--- /dev/null
+++ b/browser/themes/shared/reader/reader-mode-16.png
Binary files differ
diff --git a/browser/themes/shared/reader/reader-mode-16@2x.png b/browser/themes/shared/reader/reader-mode-16@2x.png
new file mode 100644
index 000000000..236f52d8f
--- /dev/null
+++ b/browser/themes/shared/reader/reader-mode-16@2x.png
Binary files differ
diff --git a/browser/themes/shared/reader/reader-tour.png b/browser/themes/shared/reader/reader-tour.png
new file mode 100644
index 000000000..be346b384
--- /dev/null
+++ b/browser/themes/shared/reader/reader-tour.png
Binary files differ
diff --git a/browser/themes/shared/reader/reader-tour@2x.png b/browser/themes/shared/reader/reader-tour@2x.png
new file mode 100644
index 000000000..1a60d93ca
--- /dev/null
+++ b/browser/themes/shared/reader/reader-tour@2x.png
Binary files differ
diff --git a/browser/themes/shared/reader/readerMode.svg b/browser/themes/shared/reader/readerMode.svg
new file mode 100644
index 000000000..9293d1df2
--- /dev/null
+++ b/browser/themes/shared/reader/readerMode.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 48 16">
+ <defs>
+ <path id="glyphShape-readerMode-book" d="M5.5,5h-2C3.2,5,3,5.2,3,5.5S3.2,6,3.5,6h2 C5.8,6,6,5.8,6,5.5S5.8,5,5.5,5z M5.5,7h-2C3.2,7,3,7.2,3,7.5S3.2,8,3.5,8h2C5.8,8,6,7.8,6,7.5S5.8,7,5.5,7z M5.5,9h-2 C3.2,9,3,9.2,3,9.5S3.2,10,3.5,10h2C5.8,10,6,9.8,6,9.5S5.8,9,5.5,9z M15.4,2c0,0-3.1,0-4.4,0S8.1,2.5,8,4.3C7.9,2.5,6.3,2,5,2 S0.6,2,0.6,2C0.3,2,0,2.3,0,2.7v9.6C0,12.6,0.3,13,0.6,13c0,0,2.6,0,4.4,0c1.6,0,2.8,1,3,2.3C8.2,14,9.4,13,11,13 c1.8,0,4.4,0,4.4,0c0.4,0,0.6-0.4,0.6-0.8V2.7C16,2.3,15.7,2,15.4,2z M14,11L14,11c-0.2,0-1.6,0-3,0c-1.6,0-2.9,0.8-3,2.2 C7.9,11.8,6.6,11,5,11c-1.4,0-2.8,0-3,0l0,0l0,0V4c0,0,2.7,0,3.5,0C6.6,4,8,5.5,8,6.8C8,5.5,9.4,4,10.5,4C11.3,4,14,4,14,4V11 L14,11z"/>
+ <linearGradient id="gradient-state-default" x1="0%" y1="0%" x2="0" y2="100%">
+ <stop stop-color="#989898" offset="0%"/>
+ <stop stop-color="#808080" offset="100%"/>
+ </linearGradient>
+ <linearGradient id="gradient-state-hover" x1="0%" y1="0%" x2="0" y2="100%">
+ <stop stop-color="#24aef4" offset="0%"/>
+ <stop stop-color="#177bdb" offset="100%"/>
+ </linearGradient>
+ <linearGradient id="gradient-state-pressed" x1="0%" y1="0%" x2="0" y2="100%">
+ <stop stop-color="#ff9300" offset="0%"/>
+ <stop stop-color="#ff5500" offset="100%"/>
+ </linearGradient>
+ <style>
+ .icon-state-default { fill: url(#gradient-state-default); }
+ .icon-state-hover { fill: url(#gradient-state-hover); }
+ .icon-state-pressed { fill: url(#gradient-state-pressed); }
+ </style>
+ </defs>
+ <use xlink:href="#glyphShape-readerMode-book" class="icon-state-default"/>
+ <use xlink:href="#glyphShape-readerMode-book" class="icon-state-hover" transform="translate(16)"/>
+ <use xlink:href="#glyphShape-readerMode-book" class="icon-state-pressed" transform="translate(32)"/>
+</svg>
diff --git a/browser/themes/shared/search/badge-add-engine.png b/browser/themes/shared/search/badge-add-engine.png
new file mode 100644
index 000000000..226b7bf2b
--- /dev/null
+++ b/browser/themes/shared/search/badge-add-engine.png
Binary files differ
diff --git a/browser/themes/shared/search/badge-add-engine@2x.png b/browser/themes/shared/search/badge-add-engine@2x.png
new file mode 100644
index 000000000..abf084aad
--- /dev/null
+++ b/browser/themes/shared/search/badge-add-engine@2x.png
Binary files differ
diff --git a/browser/themes/shared/search/gear.svg b/browser/themes/shared/search/gear.svg
new file mode 100755
index 000000000..b765f3dfe
--- /dev/null
+++ b/browser/themes/shared/search/gear.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 32 32">
+ <path id="glyphShape-gear" d="M28,16c0-1.7,0.9-3.1,2-3.3c-0.4-1.5-0.9-2.9-1.7-4.2c-0.9,0.7-2.6,0.3-3.8-0.9c-1.2-1.2-1.6-2.8-0.9-3.8 c-1.3-0.8-2.7-1.4-4.2-1.7c-0.2,1.1-1.6,2-3.3,2S13,3.1,12.8,2c-1.5,0.4-2.9,0.9-4.2,1.7c0.7,0.9,0.3,2.6-0.9,3.8 c-1.4,1.1-3,1.5-4,0.9C2.9,9.7,2.4,11.2,2,12.7c1.1,0.2,2,1.6,2,3.3s-0.9,3.1-2,3.3c0.4,1.5,0.9,2.9,1.7,4.2 c0.9-0.7,2.6-0.3,3.8,0.9c1.2,1.2,1.6,2.8,0.9,3.8c1.3,0.8,2.7,1.4,4.2,1.7c0.2-1.1,1.6-2,3.3-2s3.1,0.9,3.3,2 c1.5-0.4,2.9-0.9,4.2-1.7c-0.7-0.9-0.3-2.6,0.9-3.8c1.3-1.2,2.8-1.6,3.8-0.9c0.8-1.3,1.4-2.7,1.7-4.2C28.9,19.1,28,17.7,28,16z M16,24c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S20.4,24,16,24z"/>
+</svg>
diff --git a/browser/themes/shared/search/history-icon.svg b/browser/themes/shared/search/history-icon.svg
new file mode 100644
index 000000000..213694a8b
--- /dev/null
+++ b/browser/themes/shared/search/history-icon.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill: graytext;
+ }
+ use[id$="-active"] {
+ fill: HighlightText;
+ }
+ </style>
+ <defs>
+ <path id="search-history-glyph" d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7 C15,4.1,11.9,1,8,1z M8,13.3c-2.9,0-5.3-2.4-5.3-5.3S5.1,2.7,8,2.7c2.9,0,5.3,2.4,5.3,5.3S10.9,13.3,8,13.3z M10.5,7H9V5 c0-0.6-0.4-1-1-1S7,4.4,7,5v3c0,0.6,0.4,1,1,1h2.5c0.6,0,1-0.4,1-1C11.5,7.4,11.1,7,10.5,7z"/>
+ </defs>
+ <use id="search-history-icon" xlink:href="#search-history-glyph"/>
+ <use id="search-history-icon-active" xlink:href="#search-history-glyph"/>
+</svg>
diff --git a/browser/themes/shared/search/search-arrow-go.svg b/browser/themes/shared/search/search-arrow-go.svg
new file mode 100644
index 000000000..66d4bc913
--- /dev/null
+++ b/browser/themes/shared/search/search-arrow-go.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill: #616366;
+ }
+ use[id$="-inverted"] {
+ fill: #fff;
+ }
+ </style>
+ <defs>
+ <path id="search-arrow-go-glyph" d="M1,7v2.2C1,9.8,1.4,10,2,10h7.5l-3,3.1c-0.4,0.3-0.4,1,0,1.4l0.8,0.8 c0.4,0.4,1,0.4,1.4,0l6.6-6.6c0.4-0.4,0.4-1,0-1.4L8.7,0.7c-0.4-0.4-1-0.4-1.4,0L6.5,1.6C6.1,2,6.1,2.6,6.5,3l3,3H2C1.4,6,1,6.4,1,7z"/>
+ </defs>
+ <use id="search-arrow-go" xlink:href="#search-arrow-go-glyph"/>
+ <use id="search-arrow-go-rtl" transform="rotate(180 8 8)" xlink:href="#search-arrow-go-glyph"/>
+ <use id="search-arrow-go-inverted" xlink:href="#search-arrow-go-glyph"/>
+ <use id="search-arrow-go-rtl-inverted" transform="rotate(180 8 8)" xlink:href="#search-arrow-go-glyph"/>
+</svg>
diff --git a/browser/themes/shared/search/search-engine-placeholder.png b/browser/themes/shared/search/search-engine-placeholder.png
new file mode 100644
index 000000000..bff355bf4
--- /dev/null
+++ b/browser/themes/shared/search/search-engine-placeholder.png
Binary files differ
diff --git a/browser/themes/shared/search/search-engine-placeholder@2x.png b/browser/themes/shared/search/search-engine-placeholder@2x.png
new file mode 100644
index 000000000..1565fc55d
--- /dev/null
+++ b/browser/themes/shared/search/search-engine-placeholder@2x.png
Binary files differ
diff --git a/browser/themes/shared/search/search-indicator-badge-add.png b/browser/themes/shared/search/search-indicator-badge-add.png
new file mode 100644
index 000000000..62bf01b3d
--- /dev/null
+++ b/browser/themes/shared/search/search-indicator-badge-add.png
Binary files differ
diff --git a/browser/themes/shared/search/search-indicator-badge-add@2x.png b/browser/themes/shared/search/search-indicator-badge-add@2x.png
new file mode 100644
index 000000000..81056248f
--- /dev/null
+++ b/browser/themes/shared/search/search-indicator-badge-add@2x.png
Binary files differ
diff --git a/browser/themes/shared/search/search-indicator-magnifying-glass.svg b/browser/themes/shared/search/search-indicator-magnifying-glass.svg
new file mode 100644
index 000000000..15bddfba4
--- /dev/null
+++ b/browser/themes/shared/search/search-indicator-magnifying-glass.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path fill="#808080" d="M21.7,20.3l-1.4,1.4l-5.4-5.4c-1.3,1-3,1.7-4.9,1.7 c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8c4.4,0,8,3.6,8,8c0,1.8-0.6,3.5-1.7,4.9L21.7,20.3z M10,4c-3.3,0-6,2.7-6,6s2.7,6,6,6s6-2.7,6-6 S13.3,4,10,4z"/>
+</svg>
diff --git a/browser/themes/shared/search/search-indicator.png b/browser/themes/shared/search/search-indicator.png
new file mode 100644
index 000000000..a4eab0d7a
--- /dev/null
+++ b/browser/themes/shared/search/search-indicator.png
Binary files differ
diff --git a/browser/themes/shared/search/search-indicator@2x.png b/browser/themes/shared/search/search-indicator@2x.png
new file mode 100644
index 000000000..2722c1697
--- /dev/null
+++ b/browser/themes/shared/search/search-indicator@2x.png
Binary files differ
diff --git a/browser/themes/shared/searchReset.css b/browser/themes/shared/searchReset.css
new file mode 100644
index 000000000..3573d7d15
--- /dev/null
+++ b/browser/themes/shared/searchReset.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/. */
+
+body {
+ align-items: center;
+}
+
+.title {
+ background-image: url("chrome://browser/skin/icon-search-64.svg");
+}
+
+#defaultEngine {
+ padding-inline-start: 26px;
+ background-repeat: no-repeat;
+ background-position: 5px center;
+ background-size: 16px, 16px;
+}
+
+#defaultEngine:-moz-dir(rtl) {
+ background-position: calc(100% - 5px) center;
+}
diff --git a/browser/themes/shared/social/gear_clicked.png b/browser/themes/shared/social/gear_clicked.png
new file mode 100644
index 000000000..7c93aa767
--- /dev/null
+++ b/browser/themes/shared/social/gear_clicked.png
Binary files differ
diff --git a/browser/themes/shared/social/gear_default.png b/browser/themes/shared/social/gear_default.png
new file mode 100644
index 000000000..2a9c8e198
--- /dev/null
+++ b/browser/themes/shared/social/gear_default.png
Binary files differ
diff --git a/browser/themes/shared/social/social.inc.css b/browser/themes/shared/social/social.inc.css
new file mode 100644
index 000000000..31389b215
--- /dev/null
+++ b/browser/themes/shared/social/social.inc.css
@@ -0,0 +1,23 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+#manage-share-providers {
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+ -moz-image-region: rect(0, 468px, 18px, 450px);
+}
+
+#manage-share-providers > .toolbarbutton-icon {
+ min-height: 18px;
+ min-width: 18px;
+}
+
+.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+/* fixup corners for share panel */
+.social-panel > .social-panel-frame {
+ border-radius: inherit;
+}
diff --git a/browser/themes/shared/sync-desktopIcon.svg b/browser/themes/shared/sync-desktopIcon.svg
new file mode 100644
index 000000000..d3279a984
--- /dev/null
+++ b/browser/themes/shared/sync-desktopIcon.svg
@@ -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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <style>
+ g:not(:target) { display: none; }
+
+ .glyph { fill: #4d4d4d; }
+ .glyph.translucent { fill-opacity: .15; }
+
+ .inverted .glyph { fill: #fff; }
+ .inverted .glyph.translucent { fill-opacity: .15; }
+ </style>
+ <g id="icon">
+ <path class="glyph" d="M15,14H1a1,1,0,0,1-1-1V12.526H16V13A1,1,0,0,1,15,14ZM1,4A1,1,0,0,1,2,3H14a1,1,0,0,1,1,1v8H1V4Zm1,7H14V4H2v7Z"/>
+ <rect class="glyph translucent" x="2" y="4" width="12" height="7"/>
+ </g>
+ <g id="icon-inverted" class="inverted">
+ <path class="glyph" d="M15,14H1a1,1,0,0,1-1-1V12.526H16V13A1,1,0,0,1,15,14ZM1,4A1,1,0,0,1,2,3H14a1,1,0,0,1,1,1v8H1V4Zm1,7H14V4H2v7Z"/>
+ <rect class="glyph translucent" x="2" y="4" width="12" height="7"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/sync-mobileIcon.svg b/browser/themes/shared/sync-mobileIcon.svg
new file mode 100644
index 000000000..df19994f8
--- /dev/null
+++ b/browser/themes/shared/sync-mobileIcon.svg
@@ -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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <style>
+ g:not(:target) { display: none; }
+
+ .glyph { fill: #4d4d4d; }
+ .glyph.translucent { fill-opacity: .15; }
+
+ .inverted .glyph { fill: #fff; }
+ .inverted .glyph.translucent { fill-opacity: .15; }
+ </style>
+ <g id="icon">
+ <path class="glyph" d="M12,16H4a1,1,0,0,1-1-1V1A1,1,0,0,1,4,0h8a1,1,0,0,1,1,1V15A1,1,0,0,1,12,16Zm-4-.684a0.785,0.785,0,1,0-.785-0.785A0.785,0.785,0,0,0,8,15.316ZM12,2H4V13h8V2Z"/>
+ <rect class="glyph translucent" x="4" y="2" width="8" height="11"/>
+ </g>
+ <g id="icon-inverted" class="inverted">
+ <path class="glyph" d="M12,16H4a1,1,0,0,1-1-1V1A1,1,0,0,1,4,0h8a1,1,0,0,1,1,1V15A1,1,0,0,1,12,16Zm-4-.684a0.785,0.785,0,1,0-.785-0.785A0.785,0.785,0,0,0,8,15.316ZM12,2H4V13h8V2Z"/>
+ <rect class="glyph translucent" x="4" y="2" width="8" height="11"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/syncedtabs/sidebar.inc.css b/browser/themes/shared/syncedtabs/sidebar.inc.css
new file mode 100644
index 000000000..4e76a7fc5
--- /dev/null
+++ b/browser/themes/shared/syncedtabs/sidebar.inc.css
@@ -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/.
+
+/* These styles are intended to mimic XUL trees and the XUL search box. */
+
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ margin: 0;
+ font: message-box;
+ color: #333333;
+ -moz-user-select: none;
+}
+
+/* The content-container holds the non-scrollable header and the scrollable
+ content area.
+*/
+.content-container {
+ display: flex;
+ flex-flow: column;
+ height: 100%;
+}
+
+/* The content header is not scrollable */
+.content-header {
+ flex: 0 1 auto;
+}
+
+/* The main content area is scrollable and fills the rest of the area */
+.content-scrollable {
+ flex: 1 1 auto;
+ overflow: auto;
+}
+
+.emptyListInfo {
+ cursor: default;
+ padding: 3em 1em;
+ text-align: center;
+}
+
+.list,
+.item-tabs-list {
+ display: flex;
+ flex-flow: column;
+ flex-grow: 1;
+}
+
+.item.client {
+ opacity: 1;
+ max-height: unset;
+ display: unset;
+}
+
+.item.client.closed .item-tabs-list {
+ display: none;
+}
+
+.item {
+ display: inline-block;
+ opacity: 1;
+ flex: 1;
+ min-width: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ outline: none;
+ color: -moz-FieldText;
+}
+
+.item.selected > .item-title-container {
+ background-color: -moz-cellhighlight;
+ color: -moz-cellhighlighttext;
+ font-weight: bold;
+}
+
+.item.selected:focus > .item-title-container {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.client .item.tab > .item-title-container {
+ padding-inline-start: 35px;
+}
+
+.item.tab > .item-title-container {
+ padding-inline-start: 20px;
+}
+
+.item.client.device-image-desktop > .item-title-container > .item-icon-container {
+ background-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
+}
+
+.item.client.device-image-desktop.selected:focus > .item-title-container > .item-icon-container {
+ background-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon-inverted");
+}
+
+.item.client.device-image-mobile > .item-title-container > .item-icon-container {
+ background-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon");
+}
+
+.item.client.device-image-mobile.selected:focus > .item-title-container > .item-icon-container {
+ background-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon-inverted");
+}
+
+.item.tab > .item-title-container > .item-icon-container {
+ background-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+.item.tab > .item-title-container > .item-icon-container {
+ background-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+}
+
+.item-icon-container {
+ min-width: 16px;
+ max-width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+ margin-right: 5px;
+ margin-left: 5px;
+ background-size: 16px 16px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.item-title-container {
+ display: flex;
+ flex-flow: row;
+ overflow: hidden;
+ flex-grow: 1;
+ padding: 1px 0px 1px 0px;
+}
+
+.item-title {
+ flex-grow: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin: 0px;
+ line-height: 1.3;
+ cursor: default;
+}
+
+.item[hidden] {
+ opacity: 0;
+ max-height: 0;
+ transition: opacity 150ms ease-in-out, max-height 150ms ease-in-out 150ms;
+}
+
+.item.empty .item-title-container {
+ color: #aeaeae;
+}
+
+.client .item.empty > .item-title-container {
+ padding-inline-start: 35px;
+}
+
+.text-input-box {
+ display: flex;
+ flex-flow: row nowrap;
+}
+
+.textbox-input-box {
+ display: flex;
+ flex-direction: row;
+}
+
+.tabsFilter {
+ flex: 1;
+ /* min-width of anything to override the implicit "-moz-min-content" value.
+ 0px is safe as the sidebar itself has a constrained size meaning we will
+ never actually hit this minimum
+ */
+ min-width: 0px;
+}
+
+.sync-state > p {
+ padding-inline-end: 10px;
+ padding-inline-start: 10px;
+ color: #888;
+}
+
+.text-link {
+ color: rgb(0, 149, 221);
+ cursor: pointer;
+}
+
+.text-link:hover {
+ text-decoration: underline;
+}
+
+.text-link,
+.text-link:focus {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
+
+.deck .sync-state {
+ display: none;
+ opacity: 0;
+ transition: opacity 1.5s;
+ border-top: 1px solid #bdbdbd;
+}
+
+.deck .sync-state.tabs-container {
+ border-top: 0px;
+}
+
+.deck .sync-state.selected {
+ display: unset;
+ opacity: 100;
+}
+
+.sidebar-search-container.tabs-container:not(.selected) {
+ display: none;
+}
+
+.textbox-search-clear:not([disabled]) {
+ cursor: default;
+}
+
+.textbox-search-icons .textbox-search-clear,
+.filtered .textbox-search-icons .textbox-search-icon {
+ display: none;
+}
+
+.filtered .textbox-search-icons .textbox-search-clear {
+ display: block;
+}
diff --git a/browser/themes/shared/tab-selected.svg b/browser/themes/shared/tab-selected.svg
new file mode 100644
index 000000000..ca66d8116
--- /dev/null
+++ b/browser/themes/shared/tab-selected.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" width="30px" height="31px" preserveAspectRatio="none">
+ <defs>
+ <style>
+%filter substitution
+%ifdef XP_MACOSX
+%include ../osx/shared.inc
+%elif defined(MOZ_WIDGET_GTK)
+%include ../linux/linuxShared.inc
+%else
+%include ../windows/windowsShared.inc
+%endif
+ #tab-background-fill {
+ background-color: @fgTabBackgroundColor@;
+ background-image: @fgTabTexture@;
+ background-repeat: no-repeat;
+ height: 100%;
+ width: 100%;
+ }
+%ifdef XP_WIN
+ @media (-moz-windows-default-theme) and (-moz-os-version: windows-vista),
+ (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
+ #tab-background-fill {
+ background-color: @customToolbarColor@;
+ }
+ }
+%endif
+ </style>
+%include ../../base/content/tab-shape.inc.svg
+ </defs>
+ <foreignObject width="30" height="31" clip-path="url(#tab-curve-clip-path-@TAB_SIDE@)">
+ <div id="tab-background-fill" xmlns="http://www.w3.org/1999/xhtml"></div>
+ </foreignObject>
+</svg>
diff --git a/browser/themes/shared/tabbrowser/connecting.png b/browser/themes/shared/tabbrowser/connecting.png
new file mode 100644
index 000000000..e564fb570
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/connecting.png
Binary files differ
diff --git a/browser/themes/shared/tabbrowser/connecting@2x.png b/browser/themes/shared/tabbrowser/connecting@2x.png
new file mode 100644
index 000000000..97e2b2eb6
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/connecting@2x.png
Binary files differ
diff --git a/browser/themes/shared/tabbrowser/crashed.svg b/browser/themes/shared/tabbrowser/crashed.svg
new file mode 100644
index 000000000..85f3d8514
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/crashed.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="22 22 16 16">
+ <defs>
+ <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
+ <stop offset="0" style="stop-color: #e63b2e"/>
+ <stop offset="1" style="stop-color: #c33931"/>
+ </linearGradient>
+ </defs>
+ <circle fill="url(#gradient)" cx="30" cy="30" r="7"/>
+ <path fill="#fff" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092 c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z"/>
+</svg>
diff --git a/browser/themes/shared/tabbrowser/pendingpaint.png b/browser/themes/shared/tabbrowser/pendingpaint.png
new file mode 100644
index 000000000..7bbf0fe4b
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/pendingpaint.png
Binary files differ
diff --git a/browser/themes/shared/tabbrowser/tab-audio-small.svg b/browser/themes/shared/tabbrowser/tab-audio-small.svg
new file mode 100644
index 000000000..abfe71268
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-audio-small.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ .icon:not(:target) {
+ display: none;
+ }
+
+ .icon {
+ fill: #262626;
+ }
+ .icon > .outline {
+ fill: #fff;
+ }
+
+ .icon.white {
+ fill: #fff;
+ }
+ .icon.white > .outline {
+ fill: #000;
+ fill-opacity: .5;
+ }
+ </style>
+
+ <g id="tab-audio" class="icon">
+ <path class="outline" d="M12.4,3.6l-1-0.6l-0.9,2.5H10V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5H4C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.6l3.6,3.6 c0.3,0.3,0.9,0.1,0.9-0.4v-3.7h0.5l0.9,2.5l1-0.6C14,11.5,15,9.8,15,8S14,4.5,12.4,3.6z M9,13l-3-3H4c-0.6,0-1-0.4-1-1V7 c0-0.6,0.4-1,1-1h2l3-3V13z M10,9.5v-3c0.8,0,1.5,0.7,1.5,1.5S10.8,9.5,10,9.5z M11.9,11.5l-0.4-0.9C12.4,10,13,9.1,13,8 s-0.6-2-1.4-2.5l0.3-1C13.2,5.2,14,6.5,14,8S13.2,10.8,11.9,11.5z"/>
+ <path d="M4,6C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h2l3,3V3L6,6H4z M10,6.5v3c0.8,0,1.5-0.7,1.5-1.5S10.8,6.5,10,6.5z M11.9,4.5 l-0.4,0.9C12.4,6,13,6.9,13,8s-0.6,2-1.4,2.5l0.4,0.9c1.2-0.7,2.1-2,2.1-3.5S13.2,5.2,11.9,4.5z"/>
+ </g>
+ <g id="tab-audio-muted" class="icon">
+ <path class="outline" d="M5.6,5H4C2.9,5,2,5.9,2,7v2c0,0.7,0.3,1.3,0.9,1.7l-1.8,1.8l2.5,2.5l3-3l2.6,2.6c0.3,0.3,0.9,0.1,0.9-0.4V8.5l3.9-3.9 l-2.5-2.5L10,3.5V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5z"/>
+ <path d="M11.5,3.5L9,5.9V3L6,6H4C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h0.9l-2.5,2.5l1.1,1.1l9-9L11.5,3.5z M9,13V9.7l-1.7,1.7L9,13z"/>
+ </g>
+
+ <g id="tab-audio-white" class="icon white">
+ <path class="outline" d="M12.4,3.6l-1-0.6l-0.9,2.5H10V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5H4C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.6l3.6,3.6 c0.3,0.3,0.9,0.1,0.9-0.4v-3.7h0.5l0.9,2.5l1-0.6C14,11.5,15,9.8,15,8S14,4.5,12.4,3.6z M9,13l-3-3H4c-0.6,0-1-0.4-1-1V7 c0-0.6,0.4-1,1-1h2l3-3V13z M10,9.5v-3c0.8,0,1.5,0.7,1.5,1.5S10.8,9.5,10,9.5z M11.9,11.5l-0.4-0.9C12.4,10,13,9.1,13,8 s-0.6-2-1.4-2.5l0.3-1C13.2,5.2,14,6.5,14,8S13.2,10.8,11.9,11.5z"/>
+ <path d="M4,6C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h2l3,3V3L6,6H4z M10,6.5v3c0.8,0,1.5-0.7,1.5-1.5S10.8,6.5,10,6.5z M11.9,4.5 l-0.4,0.9C12.4,6,13,6.9,13,8s-0.6,2-1.4,2.5l0.4,0.9c1.2-0.7,2.1-2,2.1-3.5S13.2,5.2,11.9,4.5z"/>
+ </g>
+ <g id="tab-audio-white-muted" class="icon white">
+ <path class="outline" d="M5.6,5H4C2.9,5,2,5.9,2,7v2c0,0.7,0.3,1.3,0.9,1.7l-1.8,1.8l2.5,2.5l3-3l2.6,2.6c0.3,0.3,0.9,0.1,0.9-0.4V8.5l3.9-3.9 l-2.5-2.5L10,3.5V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5z"/>
+ <path d="M11.5,3.5L9,5.9V3L6,6H4C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h0.9l-2.5,2.5l1.1,1.1l9-9L11.5,3.5z M9,13V9.7l-1.7,1.7L9,13z"/>
+ </g>
+
+ <g id="tab-audio-blocked" class="icon">
+ <path class="outline" d="M8,1.2C4.3,1.2,1.2,4.3,1.2,8s3.1,6.8,6.8,6.8s6.8-3.1,6.8-6.8S11.7,1.2,8,1.2z M8,11.9
+ c-2.1,0-3.9-1.7-3.9-3.9c0-2.1,1.7-3.9,3.9-3.9s3.9,1.7,3.9,3.9C11.9,10.1,10.1,11.9,8,11.9z M11.1,7.3L6.6,4.6L5.4,3.9v1.4v5.3V12
+ l1.2-0.7L11,8.6L12.2,8L11.1,7.3z"/>
+ <path d="M8,2C4.7,2,2,4.7,2,8s2.7,6,6,6s6-2.7,6-6S11.3,2,8,2z M8,12.7c-2.6,0-4.7-2.1-4.7-4.7
+ S5.4,3.3,8,3.3s4.7,2.1,4.7,4.7S10.6,12.7,8,12.7z M10.7,8L6.2,5.3v5.4L10.7,8z"/>
+ </g>
+ <g id="tab-audio-white-blocked" class="icon">
+ <path class="outline" d="M8,0c3.3,0,6.4,2.2,7.5,5.3c1.1,3.1,0.1,6.7-2.5,8.9c-2.6,2.1-6.3,2.4-9.2,0.7
+ C1,13.1-0.5,9.8,0.1,6.5C0.9,2.8,4.2,0,8,0z"/>
+ <path d="M8,2C4.7,2,2,4.7,2,8s2.7,6,6,6s6-2.7,6-6S11.3,2,8,2z M8,12.7c-2.6,0-4.7-2.1-4.7-4.7
+ S5.4,3.3,8,3.3s4.7,2.1,4.7,4.7S10.6,12.7,8,12.7z M10.7,8L6.2,5.3v5.4L10.7,8z"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/tabbrowser/tab-audio.svg b/browser/themes/shared/tabbrowser/tab-audio.svg
new file mode 100644
index 000000000..274e10c29
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-audio.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ path:not(:target) {
+ display: none;
+ }
+ </style>
+
+ <path id="tab-audio" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
+
+ <path id="tab-audio-muted" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
+
+ <path id="tab-audio-blocked" d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M5.6,11.6l6-3.6l-6-3.6V11.6z M8,14.2
+ c-3.4,0-6.2-2.8-6.2-6.2S4.6,1.8,8,1.8s6.2,2.8,6.2,6.2S11.4,14.2,8,14.2z"/>
+</svg>
diff --git a/browser/themes/shared/tabbrowser/tab-overflow-indicator.png b/browser/themes/shared/tabbrowser/tab-overflow-indicator.png
new file mode 100644
index 000000000..17d27c99e
--- /dev/null
+++ b/browser/themes/shared/tabbrowser/tab-overflow-indicator.png
Binary files differ
diff --git a/browser/themes/shared/tabs.inc.css b/browser/themes/shared/tabs.inc.css
new file mode 100644
index 000000000..632a6e606
--- /dev/null
+++ b/browser/themes/shared/tabs.inc.css
@@ -0,0 +1,566 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+:root {
+ --tab-toolbar-navbar-overlap: 1px;
+ --navbar-tab-toolbar-highlight-overlap: 1px;
+ --tab-min-height: 31px;
+}
+#TabsToolbar {
+ --tab-stroke-background-size: auto 100%;
+}
+
+%define tabCurveWidth 30px
+%define tabCurveHalfWidth 15px
+
+/* image preloading hack */
+#tabbrowser-tabs::before {
+ /* Because of bug 853415, we need to ordinal this to the first position: */
+ -moz-box-ordinal-group: 0;
+ content: '';
+ display: block;
+ background-image:
+ url(chrome://browser/skin/tabbrowser/tab-background-end.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-start.png);
+}
+
+#tabbrowser-tabs {
+ min-height: var(--tab-min-height);
+}
+
+.tabbrowser-tab,
+.tabs-newtab-button {
+ -moz-appearance: none;
+ background-color: transparent;
+ border-radius: 0;
+ border-width: 0;
+ margin: 0;
+ padding: 0;
+}
+
+.tabbrowser-tab {
+ -moz-box-align: stretch;
+}
+
+/* The selected tab should appear above adjacent tabs, .tabs-newtab-button and the highlight of #nav-bar */
+.tabbrowser-tab[visuallyselected=true] {
+ position: relative;
+ z-index: 2;
+}
+
+.tab-background-middle {
+ -moz-box-flex: 1;
+ background-clip: padding-box;
+ border-left: @tabCurveHalfWidth@ solid transparent;
+ border-right: @tabCurveHalfWidth@ solid transparent;
+ margin: 0 -@tabCurveHalfWidth@;
+}
+
+.tab-content {
+ padding-inline-end: 9px;
+ padding-inline-start: 9px;
+}
+
+.tab-content[pinned] {
+ padding-inline-end: 3px;
+}
+
+.tab-throbber,
+.tab-icon-image,
+.tab-sharing-icon-overlay,
+.tab-icon-sound,
+.tab-close-button {
+ margin-top: 1px;
+}
+
+.tab-throbber,
+.tab-sharing-icon-overlay,
+.tab-icon-image {
+ height: 16px;
+ width: 16px;
+ margin-inline-end: 6px;
+}
+
+.tab-icon-image {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.tab-icon-image[sharing]:not([selected]),
+.tab-sharing-icon-overlay {
+ animation: 3s linear pulse infinite;
+}
+
+@keyframes pulse {
+ 0%, 16.66%, 83.33%, 100% {
+ opacity: 0;
+ }
+ 33.33%, 66.66% {
+ opacity: 1;
+ }
+}
+
+.tab-icon-image[sharing]:not([selected]) {
+ animation-delay: -1.5s;
+}
+
+.tab-sharing-icon-overlay {
+ /* 16px of the icon + 6px of margin-inline-end of .tab-icon-image */
+ margin-inline-start: -22px;
+ position: relative;
+}
+
+.tab-sharing-icon-overlay[sharing="camera"] {
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#camera-sharing");
+}
+
+.tab-sharing-icon-overlay[sharing="microphone"] {
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#microphone-sharing");
+}
+
+.tab-sharing-icon-overlay[sharing="screen"] {
+ list-style-image: url("chrome://browser/skin/notification-icons.svg#screen-sharing");
+}
+
+.tab-icon-overlay {
+ width: 16px;
+ height: 16px;
+ margin-top: -8px;
+ margin-inline-start: -15px;
+ margin-inline-end: -1px;
+ position: relative;
+}
+
+.tab-icon-overlay[crashed] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/crashed.svg");
+}
+
+.tab-icon-overlay[soundplaying],
+.tab-icon-overlay[muted]:not([crashed]),
+.tab-icon-overlay[blocked]:not([crashed]) {
+ border-radius: 10px;
+}
+
+.tab-icon-overlay[soundplaying]:hover,
+.tab-icon-overlay[muted]:not([crashed]):hover,
+.tab-icon-overlay[blocked]:not([crashed]):hover {
+ background-color: white;
+}
+
+.tab-icon-overlay[soundplaying] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
+}
+
+.tab-icon-overlay[muted]:not([crashed]) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
+}
+
+.tab-icon-overlay[blocked]:not([crashed]) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-blocked");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[soundplaying]:not([selected]):not(:hover),
+.tab-icon-overlay[soundplaying][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[muted]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[muted][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-muted");
+}
+
+#TabsToolbar[brighttext] .tab-icon-overlay[blocked]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[blocked][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-blocked");
+}
+
+.tab-throbber[busy] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
+}
+
+.tab-throbber[progress] {
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+.tab-label {
+ margin-inline-end: 0;
+ margin-inline-start: 0;
+}
+
+.tab-close-button {
+ margin-inline-start: 4px;
+ margin-inline-end: -2px;
+ padding: 0;
+}
+
+.tab-icon-sound {
+ margin-inline-start: 4px;
+ width: 16px;
+ height: 16px;
+ padding: 0;
+}
+
+.tab-icon-sound[soundplaying],
+.tab-icon-sound[muted],
+.tab-icon-sound[blocked] {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio);
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
+
+.tab-icon-sound[muted] {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted);
+}
+
+.tab-icon-sound[blocked] {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-blocked);
+}
+
+.tab-icon-sound:-moz-lwtheme-darktext[soundplaying],
+.tab-icon-sound:-moz-lwtheme-darktext[muted],
+.tab-icon-sound:-moz-lwtheme-darktext[blocked] {
+ filter: url(chrome://browser/skin/filters.svg#fill) drop-shadow(1px 1px 1px white);
+}
+
+.tab-icon-sound:-moz-lwtheme-brighttext[soundplaying],
+.tab-icon-sound:-moz-lwtheme-brighttext[muted],
+.tab-icon-sound:-moz-lwtheme-brighttext[blocked] {
+ filter: url(chrome://browser/skin/filters.svg#fill) drop-shadow(1px 1px 1px black);
+}
+
+.tab-icon-sound[soundplaying]:not(:hover),
+.tab-icon-sound[muted]:not(:hover),
+.tab-icon-sound[blocked]:not(:hover) {
+ opacity: .8;
+}
+
+.tab-icon-sound[soundplaying-scheduledremoval]:not([muted]):not(:hover),
+.tab-icon-overlay[soundplaying-scheduledremoval]:not([muted]):not(:hover) {
+ transition: opacity .3s linear var(--soundplaying-removal-delay);
+ opacity: 0;
+}
+
+.tab-background,
+.tabs-newtab-button {
+ /* overlap the tab curves */
+ margin-inline-end: -@tabCurveHalfWidth@;
+ margin-inline-start: -@tabCurveHalfWidth@;
+}
+
+.tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
+ padding-inline-end: @tabCurveHalfWidth@;
+ padding-inline-start: @tabCurveHalfWidth@;
+}
+
+/* Tab Overflow */
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]),
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-overflow-indicator.png);
+ background-size: 100% 100%;
+ width: 14px;
+ margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
+ pointer-events: none;
+ position: relative;
+ z-index: 3; /* the selected tab's z-index + 1 */
+}
+
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:-moz-locale-dir(rtl),
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:-moz-locale-dir(ltr) {
+ transform: scaleX(-1);
+}
+
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]) {
+ margin-inline-start: -2px;
+ margin-inline-end: -12px;
+}
+
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
+ margin-inline-start: -12px;
+ margin-inline-end: -2px;
+}
+
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator[collapsed],
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator[collapsed] {
+ opacity: 0;
+}
+
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator,
+.tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator {
+ transition: opacity 150ms ease;
+}
+
+.tab-background-start[selected=true]::after,
+.tab-background-start[selected=true]::before,
+.tab-background-start,
+.tab-background-end,
+.tab-background-end[selected=true]::after,
+.tab-background-end[selected=true]::before {
+ min-height: var(--tab-min-height);
+ width: @tabCurveWidth@;
+}
+
+.tabbrowser-tab:not([visuallyselected=true]),
+.tabbrowser-tab:-moz-lwtheme {
+ color: inherit;
+}
+
+/* Selected tab */
+
+/*
+ Tab background pseudo-elements which are positioned above .tab-background-start/end:
+ - ::before - provides the fill of the tab curve and is clipped to the tab shape. This is where
+ pointer events go for the curve.
+ - ::after - provides the border/stroke of the tab curve and is overlayed above ::before. Pointer
+ events go through to ::before to get the proper shape.
+ */
+
+
+.tab-background-start[selected=true]::after,
+.tab-background-end[selected=true]::after {
+ /* position ::after on top of its parent */
+ margin-inline-start: -@tabCurveWidth@;
+ background-size: 100% 100%;
+ content: "";
+ display: -moz-box;
+ position: relative;
+}
+
+.tab-background-start[selected=true]::before,
+.tab-background-end[selected=true]::before {
+ /* all ::before pseudo elements */
+ content: "";
+ display: -moz-box;
+}
+
+.tab-background-start[selected=true]:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
+.tab-background-end[selected=true]:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-selected-start.svg);
+ background-size: 100% 100%;
+}
+
+.tab-background-end[selected=true]:-moz-locale-dir(ltr):not(:-moz-lwtheme)::before,
+.tab-background-start[selected=true]:-moz-locale-dir(rtl):not(:-moz-lwtheme)::before {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-selected-end.svg);
+ background-size: 100% 100%;
+}
+
+/* For lightweight themes, clip the header image on start, middle, and end. */
+.tab-background-start[selected=true]:-moz-locale-dir(ltr):-moz-lwtheme::before,
+.tab-background-end[selected=true]:-moz-locale-dir(rtl):-moz-lwtheme::before {
+ clip-path: url(chrome://browser/content/browser.xul#tab-curve-clip-path-start);
+}
+
+.tab-background-end[selected=true]:-moz-locale-dir(ltr):-moz-lwtheme::before,
+.tab-background-start[selected=true]:-moz-locale-dir(rtl):-moz-lwtheme::before {
+ clip-path: url(chrome://browser/content/browser.xul#tab-curve-clip-path-end);
+}
+
+.tab-background-start[selected=true]:-moz-locale-dir(ltr)::after,
+.tab-background-end[selected=true]:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-start.png);
+}
+
+.tab-background-end[selected=true]:-moz-locale-dir(ltr)::after,
+.tab-background-start[selected=true]:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-end.png);
+}
+
+.tab-background-middle[selected=true] {
+ background-clip: padding-box, padding-box, content-box;
+ background-color: @fgTabBackgroundColor@;
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
+ @fgTabTexture@,
+ none;
+ background-repeat: repeat-x;
+ background-size: var(--tab-stroke-background-size), auto 100%;
+ /* The padding-top combined with background-clip: content-box (the bottom-most) ensure the
+ background-color doesn't extend above the top border. */
+ padding-top: 2px;
+}
+
+/* Selected tab lightweight theme styles.
+ See browser-lightweightTheme.css for information about run-time changes to LWT styles. */
+.tab-background-middle[selected=true]:-moz-lwtheme {
+ background-color: transparent;
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
+ @fgTabTextureLWT@;/*,
+ lwtHeader;*/
+ /* Don't stretch the LWT header images */
+ background-size: var(--tab-stroke-background-size), auto 100%, auto auto;
+}
+
+/* These LWT styles are normally overridden by browser-lightweightTheme.css */
+.tab-background-start[selected=true]:-moz-lwtheme::before,
+.tab-background-end[selected=true]:-moz-lwtheme::before {
+ background-image: @fgTabTextureLWT@;
+}
+
+.tab-background-start[selected=true]:-moz-lwtheme::before,
+.tab-background-end[selected=true]:-moz-lwtheme::before,
+.tab-background-middle[selected=true]:-moz-lwtheme {
+ background-color: transparent;
+}
+
+/* End selected tab */
+
+/* new tab button border and gradient on hover */
+.tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
+.tabs-newtab-button:hover {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-background-start.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-middle.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-end.png);
+ background-position: left bottom, @tabCurveWidth@ bottom, right bottom;
+ background-repeat: no-repeat;
+ background-size: @tabCurveWidth@ 100%, calc(100% - (2 * @tabCurveWidth@)) 100%, @tabCurveWidth@ 100%;
+}
+
+/* Tab pointer-events */
+.tabbrowser-tab {
+ pointer-events: none;
+}
+
+.tab-background-middle,
+.tabs-newtab-button,
+.tab-icon-overlay[soundplaying],
+.tab-icon-overlay[muted]:not([crashed]),
+.tab-icon-overlay[blocked]:not([crashed]),
+.tab-icon-sound,
+.tab-close-button {
+ pointer-events: auto;
+}
+
+/* Pinned tabs */
+
+/* Pinned tab separators need position: absolute when positioned (during overflow). */
+#tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned]::before {
+ height: 100%;
+ position: absolute;
+}
+
+.tabbrowser-tab:-moz-any([image], [pinned]) > .tab-stack > .tab-content[attention]:not([selected="true"]),
+.tabbrowser-tab > .tab-stack > .tab-content[pinned][titlechanged]:not([selected="true"]) {
+ background-image: radial-gradient(farthest-corner at center bottom, rgb(255,255,255) 3%, rgba(186,221,251,0.75) 20%, rgba(127,179,255,0.25) 40%, transparent 70%);
+ background-position: center bottom var(--tab-toolbar-navbar-overlap);
+ background-repeat: no-repeat;
+ background-size: 85% 100%;
+}
+
+.tabbrowser-tab[image] > .tab-stack > .tab-content[attention]:not([pinned]):not([selected="true"]) {
+ background-position: left bottom var(--tab-toolbar-navbar-overlap);
+ background-size: 34px 100%;
+}
+
+.tab-label[attention]:not([selected="true"]) {
+ font-weight: bold;
+}
+
+/* Tab separators */
+
+.tabbrowser-tab::after,
+.tabbrowser-tab::before {
+ margin-inline-start: -1px;
+ /* Vertical margin doesn't work here for positioned pinned tabs, see
+ bug 1198236 and bug 1300410. We're using linear-gradient instead
+ to cut off the border at the top and at the bottom. */
+ border-left: 1px solid;
+ border-image: linear-gradient(transparent 6px,
+ currentColor 6px,
+ currentColor calc(100% - 5px),
+ transparent calc(100% - 5px));
+ border-image-slice: 1;
+ /* The 1px border and negative margin may amount to a different number of
+ device pixels (bug 477157), so we also set a width to match the margin. */
+ width: 1px;
+ box-sizing: border-box;
+ opacity: 0.2;
+}
+
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-tab::before,
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-tab::after {
+ opacity: 0.4;
+}
+
+/* Also show separators beside the selected tab when dragging it. */
+#tabbrowser-tabs[movingtab] > .tabbrowser-tab[beforeselected]:not([last-visible-tab])::after,
+.tabbrowser-tab:not([selected]):not([afterselected-visible]):not([afterhovered]):not([first-visible-tab]):not(:hover)::before,
+#tabbrowser-tabs:not([overflow]) > .tabbrowser-tab[last-visible-tab]:not([selected]):not([beforehovered]):not(:hover)::after {
+ content: "";
+ display: -moz-box;
+}
+
+/* New tab button */
+
+.tabs-newtab-button {
+ width: calc(36px + @tabCurveWidth@);
+}
+
+@media (min-resolution: 1.1dppx) {
+ /* image preloading hack from like lowdpi styles */
+ #tabbrowser-tabs::before {
+ background-image:
+ url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png);
+ }
+
+ .tabbrowser-tab:hover > .tab-stack > .tab-background:not([selected=true]),
+ .tabs-newtab-button:hover {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-background-start@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-middle@2x.png),
+ url(chrome://browser/skin/tabbrowser/tab-background-end@2x.png);
+ }
+
+ .tab-background-middle[selected=true] {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
+ @fgTabTexture@,
+ none;
+ }
+
+ .tab-background-start[selected=true]:-moz-locale-dir(ltr)::after,
+ .tab-background-end[selected=true]:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-start@2x.png);
+ }
+
+ .tab-background-end[selected=true]:-moz-locale-dir(ltr)::after,
+ .tab-background-start[selected=true]:-moz-locale-dir(rtl)::after {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-stroke-end@2x.png);
+ }
+
+ .tab-throbber[busy] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/connecting@2x.png");
+ }
+
+ .tab-icon-image {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+
+ .tab-throbber[progress] {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ }
+}
+
+/* All tabs menupopup */
+
+.alltabs-item[tabIsVisible] {
+ /* box-shadow instead of background-color to work around native styling */
+ box-shadow: inset -5px 0 ThreeDShadow;
+}
+
+.alltabs-endimage[soundplaying],
+.alltabs-endimage[muted],
+.alltabs-endimage[blocked] {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio);
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
+
+.alltabs-endimage[muted] {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted);
+}
+
+.alltabs-endimage[blocked] {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-blocked);
+}
diff --git a/browser/themes/shared/theme-switcher-icon.png b/browser/themes/shared/theme-switcher-icon.png
new file mode 100644
index 000000000..891e7afb1
--- /dev/null
+++ b/browser/themes/shared/theme-switcher-icon.png
Binary files differ
diff --git a/browser/themes/shared/theme-switcher-icon@2x.png b/browser/themes/shared/theme-switcher-icon@2x.png
new file mode 100644
index 000000000..286adfeaa
--- /dev/null
+++ b/browser/themes/shared/theme-switcher-icon@2x.png
Binary files differ
diff --git a/browser/themes/shared/toolbarbutton-dropdown-arrow.png b/browser/themes/shared/toolbarbutton-dropdown-arrow.png
new file mode 100644
index 000000000..08d9da1d1
--- /dev/null
+++ b/browser/themes/shared/toolbarbutton-dropdown-arrow.png
Binary files differ
diff --git a/browser/themes/shared/toolbarbuttons.inc.css b/browser/themes/shared/toolbarbuttons.inc.css
new file mode 100644
index 000000000..b3b3ffcf8
--- /dev/null
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -0,0 +1,347 @@
+:-moz-any(@primaryToolbarButtons@),
+#bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+}
+
+toolbar[brighttext] :-moz-any(@primaryToolbarButtons@),
+toolbar[brighttext] #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: url(chrome://browser/skin/Toolbar-inverted.png);
+}
+
+#back-button {
+ -moz-image-region: rect(0, 36px, 18px, 18px);
+}
+
+#forward-button {
+ -moz-image-region: rect(0, 72px, 18px, 54px);
+}
+
+#home-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 126px, 18px, 108px);
+}
+
+#bookmarks-menu-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 144px, 18px, 126px);
+}
+
+#bookmarks-menu-button[cui-areatype="toolbar"][starred] {
+ -moz-image-region: rect(0, 162px, 18px, 144px);
+}
+
+#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ -moz-image-region: rect(0, 630px, 18px, 612px);
+}
+
+#history-panelmenu[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 180px, 18px, 162px);
+}
+
+#downloads-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 198px, 18px, 180px);
+}
+
+#add-ons-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 216px, 18px, 198px);
+}
+
+#open-file-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 234px, 18px, 216px);
+}
+
+#save-page-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 252px, 18px, 234px);
+}
+
+#sync-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 792px, 18px, 774px);
+}
+
+#containers-panelmenu[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 810px, 18px, 792px);
+}
+
+#feed-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 288px, 18px, 270px);
+}
+
+#social-share-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0px, 306px, 18px, 288px);
+}
+
+#characterencoding-button[cui-areatype="toolbar"]{
+ -moz-image-region: rect(0, 324px, 18px, 306px);
+}
+
+#new-window-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 342px, 18px, 324px);
+}
+
+#e10s-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 342px, 18px, 324px);
+}
+
+#e10s-button > .toolbarbutton-icon {
+ transform: scaleY(-1);
+}
+
+#new-tab-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 360px, 18px, 342px);
+}
+
+#privatebrowsing-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 378px, 18px, 360px);
+}
+
+#find-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 396px, 18px, 378px);
+}
+
+#print-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 414px, 18px, 396px);
+}
+
+%ifdef XP_MACOSX
+#restore-button,
+%endif
+#fullscreen-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 432px, 18px, 414px);
+}
+
+#developer-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 450px, 18px, 432px);
+}
+
+#preferences-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 468px, 18px, 450px);
+}
+
+#PanelUI-menu-button {
+ -moz-image-region: rect(0, 486px, 18px, 468px);
+}
+
+#edit-controls:not(@inAnyPanel@) > #cut-button {
+ -moz-image-region: rect(0, 504px, 18px, 486px);
+}
+
+#edit-controls:not(@inAnyPanel@) > #copy-button {
+ -moz-image-region: rect(0, 522px, 18px, 504px);
+}
+
+#edit-controls:not(@inAnyPanel@) > #paste-button {
+ -moz-image-region: rect(0, 540px, 18px, 522px);
+}
+
+#zoom-controls:not(@inAnyPanel@) > #zoom-out-button {
+ -moz-image-region: rect(0, 558px, 18px, 540px);
+}
+
+#zoom-controls:not(@inAnyPanel@) > #zoom-in-button {
+ -moz-image-region: rect(0, 576px, 18px, 558px);
+}
+
+#nav-bar-overflow-button {
+ -moz-image-region: rect(0, 612px, 18px, 594px);
+}
+
+#nav-bar-overflow-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#email-link-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 666px, 18px, 648px);
+}
+
+#sidebar-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 684px, 18px, 666px);
+}
+
+#panic-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 702px, 18px, 684px);
+}
+
+#panic-button[cui-areatype="toolbar"][open] {
+%ifdef XP_MACOSX
+ -moz-image-region: rect(36px, 702px, 54px, 684px);
+%else
+ -moz-image-region: rect(18px, 702px, 36px, 684px);
+%endif
+}
+
+#panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#webide-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 738px, 18px, 720px);
+}
+
+@media (min-resolution: 1.1dppx) {
+ :-moz-any(@primaryToolbarButtons@),
+ #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: url("chrome://browser/skin/Toolbar@2x.png");
+ }
+
+ toolbar[brighttext] :-moz-any(@primaryToolbarButtons@),
+ toolbar[brighttext] #bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: url("chrome://browser/skin/Toolbar-inverted@2x.png");
+ }
+
+ #back-button {
+ -moz-image-region: rect(0, 72px, 36px, 36px);
+ }
+
+ #forward-button {
+ -moz-image-region: rect(0, 144px, 36px, 108px);
+ }
+
+ #home-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 252px, 36px, 216px);
+ }
+
+ #bookmarks-menu-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 288px, 36px, 252px);
+ }
+
+ #bookmarks-menu-button[cui-areatype="toolbar"][starred] {
+ -moz-image-region: rect(0, 324px, 36px, 288px);
+ }
+
+ #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ -moz-image-region: rect(0px, 1260px, 36px, 1224px);
+ }
+
+ #history-panelmenu[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 360px, 36px, 324px);
+ }
+
+ #downloads-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 396px, 36px, 360px);
+ }
+
+ #add-ons-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 432px, 36px, 396px);
+ }
+
+ #open-file-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 468px, 36px, 432px);
+ }
+
+ #save-page-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 504px, 36px, 468px);
+ }
+
+ #sync-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 1584px, 36px, 1548px);
+ }
+
+ #containers-panelmenu[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 1620px, 36px, 1584px);
+ }
+
+ #feed-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 576px, 36px, 540px);
+ }
+
+ #social-share-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 612px, 36px, 576px);
+ }
+
+ #characterencoding-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 648px, 36px, 612px);
+ }
+
+ #new-window-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 684px, 36px, 648px);
+ }
+
+ #e10s-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 684px, 36px, 648px);
+ }
+
+ #e10s-button > .toolbarbutton-icon {
+ transform: scaleY(-1);
+ }
+
+ #new-tab-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 720px, 36px, 684px);
+ }
+
+ #privatebrowsing-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 756px, 36px, 720px);
+ }
+
+ #find-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 792px, 36px, 756px);
+ }
+
+ #print-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 828px, 36px, 792px);
+ }
+
+%ifdef XP_MACOSX
+ #restore-button,
+%endif
+ #fullscreen-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 864px, 36px, 828px);
+ }
+
+ #developer-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 900px, 36px, 864px);
+ }
+
+ #preferences-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 936px, 36px, 900px);
+ }
+
+ #PanelUI-menu-button {
+ -moz-image-region: rect(0, 972px, 36px, 936px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #cut-button {
+ -moz-image-region: rect(0, 1008px, 36px, 972px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #copy-button {
+ -moz-image-region: rect(0, 1044px, 36px, 1008px);
+ }
+
+ #edit-controls[cui-areatype="toolbar"] > #paste-button {
+ -moz-image-region: rect(0, 1080px, 36px, 1044px);
+ }
+
+ #zoom-controls[cui-areatype="toolbar"] > #zoom-out-button {
+ -moz-image-region: rect(0, 1116px, 36px, 1080px);
+ }
+
+ #zoom-controls[cui-areatype="toolbar"] > #zoom-in-button {
+ -moz-image-region: rect(0, 1152px, 36px, 1116px);
+ }
+
+ #nav-bar-overflow-button {
+ -moz-image-region: rect(0, 1224px, 36px, 1188px);
+ }
+
+ #email-link-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 1332px, 36px, 1296px);
+ }
+
+ #sidebar-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 1368px, 36px, 1332px);
+ }
+
+ #panic-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 1404px, 36px, 1368px);
+ }
+
+ #panic-button[cui-areatype="toolbar"][open] {
+%ifdef XP_MACOSX
+ -moz-image-region: rect(72px, 1404px, 108px, 1368px);
+%else
+ -moz-image-region: rect(36px, 1404px, 72px, 1368px);
+%endif
+ }
+
+ #webide-button[cui-areatype="toolbar"] {
+ -moz-image-region: rect(0, 1476px, 36px, 1440px);
+ }
+}
diff --git a/browser/themes/shared/translation/infobar.inc.css b/browser/themes/shared/translation/infobar.inc.css
new file mode 100644
index 000000000..50d1acc01
--- /dev/null
+++ b/browser/themes/shared/translation/infobar.inc.css
@@ -0,0 +1,95 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+notification[value="translation"] .messageImage {
+ list-style-image: url(chrome://browser/skin/translation-16.png);
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+@media (min-resolution: 1.25dppx) {
+ notification[value="translation"] .messageImage {
+ list-style-image: url(chrome://browser/skin/translation-16@2x.png);
+ -moz-image-region: rect(0, 64px, 32px, 32px);
+ }
+}
+
+notification[value="translation"][state="translating"] .messageImage {
+ list-style-image: url(chrome://browser/skin/translating-16.png);
+ -moz-image-region: auto;
+}
+
+@media (min-resolution: 1.25dppx) {
+ notification[value="translation"][state="translating"] .messageImage {
+ list-style-image: url(chrome://browser/skin/translating-16@2x.png);
+ }
+}
+
+notification[value="translation"] hbox[anonid="details"] {
+ overflow: hidden;
+}
+
+notification[value="translation"] button,
+notification[value="translation"] menulist {
+ -moz-appearance: none;
+ border-width: 1px;
+ -moz-border-top-colors: none;
+ -moz-border-right-colors: none;
+ -moz-border-bottom-colors: none;
+ -moz-border-left-colors: none;
+ border-radius: 2px;
+ min-width: 0;
+ box-shadow: 0 1px rgba(255, 255, 255, 0.5), 0 1px rgba(255, 255, 255, 0.5) inset;
+}
+
+notification[value="translation"] menulist > .menulist-dropmarker {
+ -moz-appearance: toolbarbutton-dropdown;
+ border: none;
+ background-color: transparent;
+ margin: auto;
+ padding: 5px 0;
+}
+
+.translation-menupopup arrowscrollbox {
+ padding-bottom: 0;
+}
+
+.translation-attribution {
+ cursor: pointer;
+ -moz-box-align: end;
+ font-size: small;
+}
+
+.translation-attribution > label {
+ margin-bottom: 0;
+}
+
+.translation-attribution > image {
+ width: 70px;
+}
+
+.translation-welcome-panel {
+ width: 305px;
+}
+
+.translation-welcome-logo {
+ height: 32px;
+ width: 32px;
+ list-style-image: url(chrome://browser/skin/translation-16@2x.png);
+ -moz-image-region: rect(0, 64px, 32px, 32px);
+}
+
+.translation-welcome-content {
+ margin-inline-start: 16px;
+}
+
+.translation-welcome-headline {
+ font-size: larger;
+ font-weight: bold;
+}
+
+.translation-welcome-body {
+ padding: 1em 0;
+ margin: 0 0;
+}
diff --git a/browser/themes/shared/translation/translating-16.png b/browser/themes/shared/translation/translating-16.png
new file mode 100644
index 000000000..71ca37c22
--- /dev/null
+++ b/browser/themes/shared/translation/translating-16.png
Binary files differ
diff --git a/browser/themes/shared/translation/translating-16@2x.png b/browser/themes/shared/translation/translating-16@2x.png
new file mode 100644
index 000000000..ab6184047
--- /dev/null
+++ b/browser/themes/shared/translation/translating-16@2x.png
Binary files differ
diff --git a/browser/themes/shared/translation/translation-16.png b/browser/themes/shared/translation/translation-16.png
new file mode 100644
index 000000000..4b42dedcf
--- /dev/null
+++ b/browser/themes/shared/translation/translation-16.png
Binary files differ
diff --git a/browser/themes/shared/translation/translation-16@2x.png b/browser/themes/shared/translation/translation-16@2x.png
new file mode 100644
index 000000000..2105a3e4a
--- /dev/null
+++ b/browser/themes/shared/translation/translation-16@2x.png
Binary files differ
diff --git a/browser/themes/shared/undoCloseTab.png b/browser/themes/shared/undoCloseTab.png
new file mode 100644
index 000000000..1d629c82c
--- /dev/null
+++ b/browser/themes/shared/undoCloseTab.png
Binary files differ
diff --git a/browser/themes/shared/undoCloseTab@2x.png b/browser/themes/shared/undoCloseTab@2x.png
new file mode 100644
index 000000000..4854c5681
--- /dev/null
+++ b/browser/themes/shared/undoCloseTab@2x.png
Binary files differ
diff --git a/browser/themes/shared/update-badge-failed.svg b/browser/themes/shared/update-badge-failed.svg
new file mode 100644
index 000000000..e31e6ec6b
--- /dev/null
+++ b/browser/themes/shared/update-badge-failed.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="10px" height="10px">
+ <path d="M5,6C4.2,6,3.5,6.7,3.5,7.5S4.2,9,5,9s1.5-0.7,1.5-1.5S5.8,6,5,6z M5,5L5,5c0.6,0,1-0.4,1-1l0.2-2.8 C6.2,0.5,5.7,0,5,0S3.8,0.5,3.8,1.2L4,4C4,4.6,4.4,5,5,5z" fill="#fff"/>
+</svg>
diff --git a/browser/themes/shared/update-badge.svg b/browser/themes/shared/update-badge.svg
new file mode 100644
index 000000000..3b692e526
--- /dev/null
+++ b/browser/themes/shared/update-badge.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="10px" height="10px">
+ <polygon points="4,9 4,5 2,5 5,1 8,5 6,5 6,9" fill="#fff"/>
+</svg>
diff --git a/browser/themes/shared/urlbar-star.svg b/browser/themes/shared/urlbar-star.svg
new file mode 100644
index 000000000..3080aca1a
--- /dev/null
+++ b/browser/themes/shared/urlbar-star.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ path:not(:target) {
+ display: none;
+ }
+ path {
+ fill: #b2b2b2;
+ }
+ path[id$="-inverted"] {
+ fill: #fff;
+ }
+ </style>
+
+ <path id="star" d="M8.7,0.5l2,4.3l4.6,0.7c0.6,0.1,0.9,0.9,0.4,1.4l-3.3,3.4l0.8,4.8c0.1,0.7-0.6,1.2-1.1,0.9L8,13.7l-4.1,2.3 c-0.6,0.3-1.2-0.2-1.1-0.9l0.8-4.8L0.2,6.9C-0.2,6.4,0,5.6,0.7,5.5l4.6-0.7l2-4.3C7.6-0.1,8.4-0.1,8.7,0.5z"/>
+ <path id="star-inverted" d="M8.7,0.5l2,4.3l4.6,0.7c0.6,0.1,0.9,0.9,0.4,1.4l-3.3,3.4l0.8,4.8c0.1,0.7-0.6,1.2-1.1,0.9L8,13.7l-4.1,2.3 c-0.6,0.3-1.2-0.2-1.1-0.9l0.8-4.8L0.2,6.9C-0.2,6.4,0,5.6,0.7,5.5l4.6-0.7l2-4.3C7.6-0.1,8.4-0.1,8.7,0.5z"/>
+</svg>
diff --git a/browser/themes/shared/urlbar-tab.svg b/browser/themes/shared/urlbar-tab.svg
new file mode 100644
index 000000000..87a2c7f12
--- /dev/null
+++ b/browser/themes/shared/urlbar-tab.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ path:not(:target) {
+ display: none;
+ }
+ path {
+ fill: #b2b2b2;
+ }
+ path[id$="-inverted"] {
+ fill: #fff;
+ }
+ </style>
+
+ <path id="tab" d="M14,9.5V6c0-1.7-1.3-3-3-3H5C3.3,3,2,4.3,2,6v3.5C2,10.3,1.3,11,0.5,11h0C0.2,11,0,11.2,0,11.5v1 C0,12.8,0.2,13,0.5,13h15c0.3,0,0.5-0.2,0.5-0.5v-1c0-0.3-0.2-0.5-0.5-0.5h0C14.7,11,14,10.3,14,9.5z"/>
+ <path id="tab-inverted" d="M14,9.5V6c0-1.7-1.3-3-3-3H5C3.3,3,2,4.3,2,6v3.5C2,10.3,1.3,11,0.5,11h0C0.2,11,0,11.2,0,11.5v1 C0,12.8,0.2,13,0.5,13h15c0.3,0,0.5-0.2,0.5-0.5v-1c0-0.3-0.2-0.5-0.5-0.5h0C14.7,11,14,10.3,14,9.5z"/>
+
+</svg>
diff --git a/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css b/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
new file mode 100644
index 000000000..a34c2bf11
--- /dev/null
+++ b/browser/themes/shared/urlbarSearchSuggestionsNotification.inc.css
@@ -0,0 +1,54 @@
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
+ border-bottom: 1px solid var(--panel-separator-color);
+ background-color: hsla(210, 4%, 10%, 0.07);
+ padding: 6px 0;
+ padding-inline-start: 44px;
+ padding-inline-end: 6px;
+ background-image: url("chrome://browser/skin/info.svg");
+ background-clip: padding-box;
+ background-position: 20px center;
+ background-repeat: no-repeat;
+ background-size: 16px 16px;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"]:-moz-locale-dir(rtl) {
+ background-position: right 20px center;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description {
+ margin: 0;
+ padding: 0;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description > label.text-link {
+ margin-inline-start: 0;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
+ -moz-appearance: none;
+ min-width: 80px;
+ border-radius: 3px;
+ padding: 4px 16px;
+ margin: 0;
+ margin-inline-start: 10px;
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"] {
+ color: hsl(210, 0%, 38%);
+ background-color: hsl(210, 0%, 88%);
+ border: 1px solid hsl(210, 0%, 82%);
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"]:hover {
+ background-color: hsl(210, 0%, 84%);
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"] {
+ color: white;
+ background-color: hsl(93, 82%, 44%);
+ border: 1px solid hsl(93, 82%, 44%);
+}
+
+#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"]:hover {
+ background-color: hsl(93, 82%, 40%);
+}
diff --git a/browser/themes/shared/warning-white.svg b/browser/themes/shared/warning-white.svg
new file mode 100644
index 000000000..942c23cf6
--- /dev/null
+++ b/browser/themes/shared/warning-white.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+ <path fill="#fff" stroke="#000" stroke-opacity="0.3" d="M15.4,12.9 9.46,1.41 C9.12,0.756 8.59,0.381 8,0.381 7.41,0.381 6.88,0.756 6.54,1.41 L0.642,12.9 c-0.331,0.6 -0.348,1.3 -0.05,1.9 0.299,0.5 0.854,0.8 1.534,0.8 H13.9 c0.6,0 1.2,-0.3 1.5,-0.8 0.3,-0.6 0.3,-1.3 0,-1.9z M8.83,5.07 8.65,10.5 H7.34 L7.15,5.07 H8.83z M8,13.7 c-0.55,0 -0.99,-0.5 -0.99,-1 0,-0.6 0.44,-1 0.99,-1 0.56,0 0.99,0.4 0.99,1 0,0.5 -0.43,1 -0.99,1z"/>
+</svg>
diff --git a/browser/themes/shared/warning.svg b/browser/themes/shared/warning.svg
new file mode 100644
index 000000000..7befd73d9
--- /dev/null
+++ b/browser/themes/shared/warning.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <path fill="#ffbf00" d="M14.8,12.5L9.3,1.9C9,1.3,8.5,1,8,1C7.5,1,7,1.3,6.7,1.9L1.2,12.5c-0.3,0.6-0.3,1.2,0,1.7C1.5,14.7,2,15,2.6,15h10.8 c0.6,0,1.1-0.3,1.4-0.8C15.1,13.7,15.1,13.1,14.8,12.5z"/>
+ <path fill="#fff" d="M8,11c-0.8,0-1.5,0.7-1.5,1.5C6.5,13.3,7.2,14,8,14 c0.8,0,1.5-0.7,1.5-1.5C9.5,11.7,8.8,11,8,11z M8,10L8,10C8.6,10,9,9.6,9,9l0.2-4.2c0-0.7-0.5-1.2-1.2-1.2S6.8,4.1,6.8,4.8L7,9 C7,9.6,7.4,10,8,10z"/>
+</svg>
diff --git a/browser/themes/shared/webrtc/camera-white-16.png b/browser/themes/shared/webrtc/camera-white-16.png
new file mode 100644
index 000000000..e1e360bd8
--- /dev/null
+++ b/browser/themes/shared/webrtc/camera-white-16.png
Binary files differ
diff --git a/browser/themes/shared/webrtc/camera-white-16@2x.png b/browser/themes/shared/webrtc/camera-white-16@2x.png
new file mode 100644
index 000000000..ddbbf5083
--- /dev/null
+++ b/browser/themes/shared/webrtc/camera-white-16@2x.png
Binary files differ
diff --git a/browser/themes/shared/webrtc/microphone-white-16.png b/browser/themes/shared/webrtc/microphone-white-16.png
new file mode 100644
index 000000000..0091529ba
--- /dev/null
+++ b/browser/themes/shared/webrtc/microphone-white-16.png
Binary files differ
diff --git a/browser/themes/shared/webrtc/microphone-white-16@2x.png b/browser/themes/shared/webrtc/microphone-white-16@2x.png
new file mode 100644
index 000000000..5bf1f5c62
--- /dev/null
+++ b/browser/themes/shared/webrtc/microphone-white-16@2x.png
Binary files differ
diff --git a/browser/themes/shared/webrtc/screen-white-16.png b/browser/themes/shared/webrtc/screen-white-16.png
new file mode 100644
index 000000000..045bfd35a
--- /dev/null
+++ b/browser/themes/shared/webrtc/screen-white-16.png
Binary files differ
diff --git a/browser/themes/shared/webrtc/screen-white-16@2x.png b/browser/themes/shared/webrtc/screen-white-16@2x.png
new file mode 100644
index 000000000..1e5fca5d6
--- /dev/null
+++ b/browser/themes/shared/webrtc/screen-white-16@2x.png
Binary files differ
diff --git a/browser/themes/tab-svgs.mozbuild b/browser/themes/tab-svgs.mozbuild
new file mode 100644
index 000000000..b1259b04e
--- /dev/null
+++ b/browser/themes/tab-svgs.mozbuild
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+script = '/browser/themes/preprocess-tab-svgs.py'
+input = ['/browser/themes/shared/tab-selected.svg']
+
+# Context variables can't be used inside functions, so hack around that.
+generated_files = GENERATED_FILES
+
+def generate_svg(svg_name, script_function):
+ global generated_files
+ generated_files += [svg_name]
+ svg = generated_files[svg_name]
+ svg.script = script + ':' + script_function
+ svg.inputs = input
+
+generate_svg('tab-selected-end.svg', 'tab_side_end')
+generate_svg('tab-selected-start.svg', 'tab_side_start')
+
diff --git a/browser/themes/windows/Info-XP.png b/browser/themes/windows/Info-XP.png
new file mode 100644
index 000000000..c20f66ce6
--- /dev/null
+++ b/browser/themes/windows/Info-XP.png
Binary files differ
diff --git a/browser/themes/windows/Info.png b/browser/themes/windows/Info.png
new file mode 100644
index 000000000..f5dfb65a3
--- /dev/null
+++ b/browser/themes/windows/Info.png
Binary files differ
diff --git a/browser/themes/windows/Privacy-16-XP.png b/browser/themes/windows/Privacy-16-XP.png
new file mode 100644
index 000000000..335febbb3
--- /dev/null
+++ b/browser/themes/windows/Privacy-16-XP.png
Binary files differ
diff --git a/browser/themes/windows/Privacy-16.png b/browser/themes/windows/Privacy-16.png
new file mode 100644
index 000000000..ff0c565a8
--- /dev/null
+++ b/browser/themes/windows/Privacy-16.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-XP.png b/browser/themes/windows/Toolbar-XP.png
new file mode 100644
index 000000000..dff60911f
--- /dev/null
+++ b/browser/themes/windows/Toolbar-XP.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-aero.png b/browser/themes/windows/Toolbar-aero.png
new file mode 100644
index 000000000..b191ce2aa
--- /dev/null
+++ b/browser/themes/windows/Toolbar-aero.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-aero@2x.png b/browser/themes/windows/Toolbar-aero@2x.png
new file mode 100644
index 000000000..033d87dff
--- /dev/null
+++ b/browser/themes/windows/Toolbar-aero@2x.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-inverted.png b/browser/themes/windows/Toolbar-inverted.png
new file mode 100644
index 000000000..2311803d8
--- /dev/null
+++ b/browser/themes/windows/Toolbar-inverted.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-inverted@2x.png b/browser/themes/windows/Toolbar-inverted@2x.png
new file mode 100644
index 000000000..289c57380
--- /dev/null
+++ b/browser/themes/windows/Toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-lunaSilver.png b/browser/themes/windows/Toolbar-lunaSilver.png
new file mode 100644
index 000000000..30c425c26
--- /dev/null
+++ b/browser/themes/windows/Toolbar-lunaSilver.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-win8.png b/browser/themes/windows/Toolbar-win8.png
new file mode 100644
index 000000000..7e06de7f1
--- /dev/null
+++ b/browser/themes/windows/Toolbar-win8.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar-win8@2x.png b/browser/themes/windows/Toolbar-win8@2x.png
new file mode 100644
index 000000000..6b205ea6f
--- /dev/null
+++ b/browser/themes/windows/Toolbar-win8@2x.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar.png b/browser/themes/windows/Toolbar.png
new file mode 100644
index 000000000..4ae11be4d
--- /dev/null
+++ b/browser/themes/windows/Toolbar.png
Binary files differ
diff --git a/browser/themes/windows/Toolbar@2x.png b/browser/themes/windows/Toolbar@2x.png
new file mode 100644
index 000000000..33c63fdbb
--- /dev/null
+++ b/browser/themes/windows/Toolbar@2x.png
Binary files differ
diff --git a/browser/themes/windows/aboutSessionRestore-window-icon.png b/browser/themes/windows/aboutSessionRestore-window-icon.png
new file mode 100644
index 000000000..c2d15ad7b
--- /dev/null
+++ b/browser/themes/windows/aboutSessionRestore-window-icon.png
Binary files differ
diff --git a/browser/themes/windows/aboutSyncTabs.css b/browser/themes/windows/aboutSyncTabs.css
new file mode 100644
index 000000000..4cedad649
--- /dev/null
+++ b/browser/themes/windows/aboutSyncTabs.css
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#tabs-display,
+#tabsList {
+ background-color: transparent;
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#tabsList {
+ width: 100%;
+}
+
+#tabs-display {
+ background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
+}
+
+#headers {
+ background: url(chrome://browser/skin/sync-32.png) no-repeat;
+ margin-top: 4px;
+ width: 45em;
+ height: 32px;
+ 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://browser/skin/sync-mobileIcon.svg#icon");
+}
+richlistitem.desktop[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
+}
+
+.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/browser/themes/windows/actionicon-tab-XPVista7.png b/browser/themes/windows/actionicon-tab-XPVista7.png
new file mode 100644
index 000000000..8437c7655
--- /dev/null
+++ b/browser/themes/windows/actionicon-tab-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/actionicon-tab.png b/browser/themes/windows/actionicon-tab.png
new file mode 100644
index 000000000..52169a480
--- /dev/null
+++ b/browser/themes/windows/actionicon-tab.png
Binary files differ
diff --git a/browser/themes/windows/actionicon-tab@2x.png b/browser/themes/windows/actionicon-tab@2x.png
new file mode 100644
index 000000000..6a4be3741
--- /dev/null
+++ b/browser/themes/windows/actionicon-tab@2x.png
Binary files differ
diff --git a/browser/themes/windows/browser-aero.css b/browser/themes/windows/browser-aero.css
new file mode 100644
index 000000000..5ff9d8250
--- /dev/null
+++ b/browser/themes/windows/browser-aero.css
@@ -0,0 +1,473 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 glassActiveBorderColor rgb(37, 44, 51)
+%define glassInactiveBorderColor rgb(102, 102, 102)
+
+@media not all and (-moz-windows-classic) {
+ #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel > #navigator-toolbox > #toolbar-menubar {
+ margin-top: 1px;
+ }
+}
+
+@media (-moz-windows-default-theme) {
+ .sidebar-header,
+ #sidebar-header {
+ -moz-appearance: none;
+ border-bottom: none;
+ text-shadow: none;
+ }
+
+ .sidebar-title,
+ #sidebar-title {
+ font-weight: bold;
+ }
+
+ .sidebar-splitter {
+ border: 0;
+ border-inline-end: 1px solid ThreeDLightShadow;
+ min-width: 0;
+ width: 3px;
+ background-color: transparent;
+ margin-inline-start: -3px;
+ position: relative;
+ }
+
+ #appcontent ~ .sidebar-splitter {
+ border-inline-start: 1px solid ThreeDLightShadow;
+ border-inline-end: none;
+ margin-inline-start: 0;
+ margin-inline-end: -3px;
+ }
+
+ .menu-accel,
+ .menu-iconic-accel {
+ color: graytext;
+ }
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ .sidebar-header:not(:-moz-lwtheme),
+ #sidebar-header:not(:-moz-lwtheme) {
+ background-color: #EEF3FA;
+ }
+
+ .sidebar-splitter,
+ #appcontent ~ .sidebar-splitter {
+ border-color: #A9B7C9;
+ }
+
+ #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
+ #browser-bottombox:not(:-moz-lwtheme),
+ .browserContainer > findbar {
+ background-color: @customToolbarColor@;
+ }
+
+ .tab-background-middle[selected=true]:not(:-moz-lwtheme) {
+ background-color: @customToolbarColor@;
+ }
+ }
+}
+
+@media (-moz-windows-compositor) {
+ #main-window {
+ -moz-appearance: -moz-win-glass;
+ }
+
+
+ /* On win10, if we don't set this on the entire browser container including
+ * the sidebar, if the sidebar is open the accent color bleeds through in
+ * the titlebar */
+ #browser {
+ -moz-appearance: -moz-win-exclude-glass;
+ }
+
+ @media not all and (-moz-os-version: windows-vista) {
+ @media not all and (-moz-os-version: windows-win7) {
+ @media not all and (-moz-os-version: windows-win8) {
+ @media (-moz-windows-default-theme) {
+ #main-window {
+ background-color: hsl(0, 0%, 78%);
+ }
+
+ :root[tabsintitlebar] .tab-label:-moz-window-inactive {
+ /* Calculated to match the opacity change of Windows Explorer
+ titlebar text change for inactive windows. */
+ opacity: .6;
+ }
+ }
+
+ @media not all and (-moz-windows-default-theme) {
+ #main-window {
+ background-color: transparent;
+ }
+ }
+
+ #titlebar-buttonbox,
+ .titlebar-button {
+ -moz-appearance: none !important;
+ }
+
+ .titlebar-button {
+ border: none;
+ margin: 0 !important;
+ padding: 10px 17px;
+ }
+
+ #main-window[sizemode=maximized] .titlebar-button {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 12px;
+ height: 12px;
+ }
+
+ #titlebar-min {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
+ }
+
+ #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize);
+ }
+
+ #main-window[sizemode="maximized"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
+ }
+
+ #titlebar-close {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
+ }
+ #titlebar-close:hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
+ }
+
+ #titlebar-min:-moz-lwtheme {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-themes);
+ }
+ #titlebar-max:-moz-lwtheme {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-themes);
+ }
+ #main-window[sizemode="maximized"] #titlebar-max:-moz-lwtheme {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-themes);
+ }
+ #titlebar-close:-moz-lwtheme {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-themes);
+ }
+
+
+ /* the 12px image renders a 10px icon, and the 10px upscaled gets rounded to 12.5, which
+ * rounds up to 13px, which makes the icon one pixel too big on 1.25dppx. Fix: */
+ @media (min-resolution: 1.20dppx) and (max-resolution: 1.45dppx) {
+ .titlebar-button > .toolbarbutton-icon {
+ width: 11.5px;
+ height: 11.5px;
+ }
+ }
+
+ /* 175% dpi should result in the same device pixel sizes as 150% dpi. */
+ @media (min-resolution: 1.70dppx) and (max-resolution: 1.95dppx) {
+ .titlebar-button {
+ padding-left: 14.1px;
+ padding-right: 14.1px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+ }
+
+ /* 225% dpi should result in the same device pixel sizes as 200% dpi. */
+ @media (min-resolution: 2.20dppx) and (max-resolution: 2.45dppx) {
+ .titlebar-button {
+ padding-left: 15.3333px;
+ padding-right: 15.3333px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+ }
+
+ /* 275% dpi should result in the same device pixel sizes as 250% dpi. */
+ @media (min-resolution: 2.70dppx) and (max-resolution: 2.95dppx) {
+ /* NB: todo: this should also change padding on the buttons
+ * themselves, but without a device to test this on, it's
+ * impossible to know by how much. */
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+ }
+
+ @media (-moz-windows-default-theme) {
+ .titlebar-button:hover {
+ background-color: hsla(0, 0%, 0%, .12);
+ }
+
+ .titlebar-button:hover:active {
+ background-color: hsla(0, 0%, 0%, .22);
+ }
+
+ .titlebar-button:not(:hover) > .toolbarbutton-icon:-moz-window-inactive {
+ opacity: 0.5;
+ }
+
+ #titlebar-close:hover {
+ background-color: hsl(355, 86%, 49%);
+ }
+
+ #titlebar-close:hover:active {
+ background-color: hsl(355, 82%, 69%);
+ }
+ }
+ @media not all and (-moz-windows-default-theme) {
+ .titlebar-button {
+ background-color: -moz-field;
+ }
+ .titlebar-button:hover {
+ background-color: Highlight;
+ }
+
+ #titlebar-min {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-highcontrast);
+ }
+ #titlebar-min:hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-highcontrast-hover);
+ }
+
+ #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-highcontrast);
+ }
+ #titlebar-max:hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-highcontrast-hover);
+ }
+
+ #main-window[sizemode="maximized"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-highcontrast);
+ }
+ #main-window[sizemode="maximized"] #titlebar-max:hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-highcontrast-hover);
+ }
+
+ #titlebar-close {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highcontrast);
+ }
+ #titlebar-close:hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highcontrast-hover);
+ }
+ }
+ }
+ }
+ }
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ #main-window[sizemode="maximized"] #titlebar-buttonbox {
+ margin-inline-end: 3px;
+ }
+
+ #main-window {
+ background-color: transparent;
+ -moz-appearance: -moz-win-borderless-glass;
+ }
+
+ /* These should be hidden w/ glass enabled. Windows draws its own buttons. */
+ .titlebar-button {
+ display: none;
+ }
+
+ /* The borders on the glass frame are ours, and inside #browser, and on
+ * vista and win7 we want to make sure they are "glassy", so we can't use
+ * #browser as the exclude-glass container. We use #appcontent instead. */
+ #browser {
+ -moz-appearance: none;
+ }
+
+ #appcontent {
+ -moz-appearance: -moz-win-exclude-glass;
+ }
+ }
+
+ @media (-moz-os-version: windows-win8) {
+ /* Artificially draw window borders that are covered by lwtheme, see bug 591930.
+ * Borders for vista/win7 are below, win10 doesn't need them. */
+ #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+ border-top: 1px solid @toolbarShadowColor@;
+ }
+ }
+
+ @media (-moz-windows-default-theme) {
+ #toolbar-menubar:not(:-moz-lwtheme),
+ #TabsToolbar:not(:-moz-lwtheme) {
+ color: black;
+ }
+
+ #main-menubar > menu:not(:-moz-lwtheme) {
+ color: inherit;
+ }
+
+ /* Use a different color only on Windows 8 and higher for inactive windows.
+ * On aero, the menubar fog disappears for inactive windows, and renders gray
+ * illegible.
+ */
+ @media not all and (-moz-os-version: windows-vista) {
+ @media not all and (-moz-os-version: windows-win7) {
+ #toolbar-menubar:not(:-moz-lwtheme):-moz-window-inactive {
+ color: ThreeDShadow;
+ }
+ }
+ }
+ }
+
+ #main-window[darkwindowframe="true"] #toolbar-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive),
+ #main-window[darkwindowframe="true"] #TabsToolbar:not(:-moz-lwtheme):not(:-moz-window-inactive) {
+ color: white;
+ }
+
+ /* Show borders on vista through win8, but not on win10 and later: */
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ /* Vertical toolbar border */
+ #main-window:not([customizing])[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
+ #main-window:not([customizing])[sizemode=normal] #navigator-toolbox:-moz-lwtheme,
+ #main-window[customizing] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ border-left: 1px solid @toolbarShadowColor@;
+ border-right: 1px solid @toolbarShadowColor@;
+ background-clip: padding-box;
+ }
+
+ #main-window:not([customizing])[sizemode=normal] #navigator-toolbox:not(:-moz-lwtheme)::after,
+ #main-window[customizing] #navigator-toolbox::after {
+ box-shadow: 1px 0 0 @toolbarShadowColor@, -1px 0 0 @toolbarShadowColor@;
+ margin-left: 1px;
+ margin-right: 1px;
+ }
+
+ #main-window[sizemode=normal] #browser-border-start,
+ #main-window[sizemode=normal] #browser-border-end {
+ display: -moz-box;
+ background-color: @toolbarShadowColor@;
+ width: 1px;
+ }
+
+ #main-window[sizemode=normal] #browser-bottombox {
+ border: 1px solid @toolbarShadowColor@;
+ border-top-style: none;
+ background-clip: padding-box;
+ }
+ }
+
+ #main-window[sizemode=normal] #TabsToolbar {
+ padding-left: 1px;
+ padding-right: 1px;
+ }
+
+ #appcontent:not(:-moz-lwtheme) {
+ background-color: -moz-dialog;
+ }
+}
+
+@media (-moz-windows-glass) {
+ #main-window[sizemode=normal] #nav-bar {
+ border-top-left-radius: 2.5px;
+ border-top-right-radius: 2.5px;
+ }
+
+ #main-window[sizemode=fullscreen]:not(:-moz-lwtheme) {
+ -moz-appearance: none;
+ background-color: #556;
+ }
+
+ #toolbar-menubar:not(:-moz-lwtheme) {
+ text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4);
+ }
+
+ #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) {
+ background-color: rgba(255,255,255,.5);
+ color: black;
+ border-radius: 4px;
+ }
+
+ /* Artificially draw window borders that are covered by lwtheme, see bug 591930.
+ * We use a different border for win8, and this is not necessary on win10+ */
+ #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+ border-top: 2px solid;
+ -moz-border-top-colors: @glassActiveBorderColor@ rgba(255,255,255,.6);
+ }
+
+ #main-window[sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive {
+ -moz-border-top-colors: @glassInactiveBorderColor@ rgba(255,255,255,.6);
+ }
+
+ /* Glass Fog */
+
+ #TabsToolbar:not(:-moz-lwtheme) {
+ position: relative;
+ }
+
+ #TabsToolbar:not(:-moz-lwtheme)::after {
+ /* Because we use placeholders for window controls etc. in the tabstrip,
+ * and position those with ordinal attributes, and because our layout code
+ * expects :before/:after nodes to come first/last in the frame list,
+ * we have to reorder this element to come last, hence the
+ * ordinal group value (see bug 853415). */
+ -moz-box-ordinal-group: 1001;
+ box-shadow: 0 0 30px 30px rgba(174,189,204,0.85);
+ content: "";
+ display: -moz-box;
+ height: 0;
+ margin: 0 60px; /* (30px + 30px) from box-shadow */
+ position: absolute;
+ pointer-events: none;
+ top: 50%;
+ width: -moz-available;
+ z-index: -1;
+ }
+
+ /* Need to constrain the glass fog to avoid overlapping layers, see bug 886281. */
+ #navigator-toolbox:not(:-moz-lwtheme) {
+ overflow: -moz-hidden-unscrollable;
+ }
+
+ #main-window[sizemode=normal] .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox > .scrollbox-innerbox:not(:-moz-lwtheme) {
+ position: relative;
+ }
+
+ /* End Glass Fog */
+}
+
+/* Aero Basic */
+@media not all and (-moz-windows-compositor) {
+ @media (-moz-windows-default-theme) {
+ #main-window {
+ background-color: rgb(185,209,234);
+ }
+ #main-window:-moz-window-inactive {
+ background-color: rgb(215,228,242);
+ }
+
+ /* Render a window top border for lwthemes: */
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+ background-image: linear-gradient(to bottom,
+ @glassActiveBorderColor@ 0, @glassActiveBorderColor@ 1px,
+ rgba(255,255,255,.6) 1px, rgba(255,255,255,.6) 2px, transparent 2px);
+ }
+
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive {
+ background-image: linear-gradient(to bottom,
+ @glassInactiveBorderColor@ 0, @glassInactiveBorderColor@ 1px,
+ rgba(255,255,255,.6) 1px, rgba(255,255,255,.6) 2px, transparent 2px);
+ }
+ }
+
+ #print-preview-toolbar:not(:-moz-lwtheme) {
+ -moz-appearance: -moz-win-browsertabbar-toolbox;
+ }
+}
diff --git a/browser/themes/windows/browser-lightweightTheme.css b/browser/themes/windows/browser-lightweightTheme.css
new file mode 100644
index 000000000..d2a1b840c
--- /dev/null
+++ b/browser/themes/windows/browser-lightweightTheme.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/. */
+
+%include windowsShared.inc
+%filter substitution
+
+/*
+ * LightweightThemeListener will append the current lightweight theme's header
+ * image to the background-image for each of the following rulesets.
+ */
+
+/* Lightweight theme on tabs */
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-start[selected=true]:-moz-lwtheme::before,
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-end[selected=true]:-moz-lwtheme::before {
+ background-attachment: scroll, fixed;
+ background-color: transparent;
+ background-image: @fgTabTextureLWT@;/*, lwtHeader;*/
+ background-position: 0 0, right top;
+ background-repeat: repeat-x, no-repeat;
+}
+
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+ background-attachment: scroll, scroll, fixed;
+ background-color: transparent;
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle.png),
+ @fgTabTextureLWT@;/*,
+ lwtHeader;*/
+ background-position: 0 0, 0 0, right top;
+ background-repeat: repeat-x, repeat-x, no-repeat;
+}
+
+@media (min-resolution: 1.25dppx) {
+ #tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab > .tab-stack > .tab-background > .tab-background-middle[selected=true]:-moz-lwtheme {
+ background-image: url(chrome://browser/skin/tabbrowser/tab-active-middle@2x.png),
+ @fgTabTextureLWT@;/*,
+ lwtHeader;*/
+ }
+}
diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css
new file mode 100644
index 000000000..2de5a6545
--- /dev/null
+++ b/browser/themes/windows/browser.css
@@ -0,0 +1,2711 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://global/skin/");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+@namespace svg url("http://www.w3.org/2000/svg");
+
+%include ../shared/browser.inc
+%include windowsShared.inc
+%filter substitution
+%define toolbarShadowColor hsla(209,67%,12%,0.35)
+%define forwardTransitionLength 150ms
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
+
+:root {
+ --space-above-tabbar: 15px;
+
+ --backbutton-urlbar-overlap: 6px;
+
+ /* icon width + border + horizontal padding (without the overlap from backbutton-urlbar-overlap) */
+ --forwardbutton-width: 25px;
+
+ --toolbarbutton-vertical-inner-padding: 2px;
+ --toolbarbutton-vertical-outer-padding: 8px;
+
+ --toolbarbutton-hover-background: rgba(0,0,0,.1);
+ --toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
+ --toolbarbutton-hover-boxshadow: none;
+
+ --toolbarbutton-active-background: rgba(0,0,0,.15);
+ --toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
+ --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0,0,0,.15) inset;
+
+ --toolbarbutton-checkedhover-backgroundcolor: rgba(0,0,0,.1);
+
+ --urlbar-dropmarker-url: url("chrome://browser/skin/urlbar-history-dropmarker.png");
+ --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
+ --urlbar-dropmarker-hover-region: rect(0px, 22px, 14px, 11px);
+ --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
+ --urlbar-dropmarker-2x-url: url("chrome://browser/skin/urlbar-history-dropmarker@2x.png");
+ --urlbar-dropmarker-2x-region: rect(0, 22px, 28px, 0);
+ --urlbar-dropmarker-hover-2x-region: rect(0, 44px, 28px, 22px);
+ --urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
+
+ --panel-separator-color: ThreeDLightShadow;
+ --arrowpanel-dimmed: hsla(0,0%,80%,.3);
+ --arrowpanel-dimmed-further: hsla(0,0%,80%,.45);
+ --arrowpanel-dimmed-even-further: hsla(0,0%,80%,.8);
+
+ --urlbar-separator-color: ThreeDLightShadow;
+}
+
+@media (-moz-windows-default-theme) {
+ :root {
+ --panel-separator-color: hsla(210,4%,10%,.14);
+ }
+}
+
+toolbar[brighttext] {
+ --toolbarbutton-hover-background: rgba(255,255,255,.25);
+ --toolbarbutton-hover-bordercolor: rgba(255,255,255,.5);
+
+ --toolbarbutton-active-background: rgba(255,255,255,.4);
+ --toolbarbutton-active-bordercolor: rgba(255,255,255,.7);
+ --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.4) inset;
+
+ --toolbarbutton-checkedhover-backgroundcolor: rgba(255,255,255,.3);
+}
+
+toolbar:-moz-lwtheme {
+ --toolbarbutton-hover-background: rgba(255,255,255,.25);
+ --toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
+
+ --toolbarbutton-active-background: rgba(70%,70%,70%,.25);
+ --toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
+ --toolbarbutton-active-boxshadow: 0 0 2px rgba(0,0,0,.6) inset;
+
+ --toolbarbutton-checkedhover-backgroundcolor: rgba(85%,85%,85%,.25);
+}
+
+#menubar-items {
+ -moz-box-orient: vertical; /* for flex hack */
+}
+
+#main-menubar {
+ -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
+}
+
+/* Hides the titlebar-placeholder underneath the window caption buttons when we
+ are not autohiding the menubar. */
+#toolbar-menubar:not([autohide="true"]) + #TabsToolbar > .titlebar-placeholder[type="caption-buttons"] {
+ display: none;
+}
+
+/* We want a 4px gap between the TabsToolbar and the toolbar-menubar when the
+ toolbar-menu is displayed, and a 16px gap when it is not. 1px is taken care
+ of by the (light) outer shadow of the tab, the remaining 3/15 are these margins. */
+#toolbar-menubar:not([autohide=true]) ~ #TabsToolbar:not([inFullscreen]),
+#toolbar-menubar[autohide=true]:not([inactive]) ~ #TabsToolbar:not([inFullscreen]) {
+ margin-top: 3px;
+}
+
+#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen])[chromehidden~="menubar"] #toolbar-menubar ~ #TabsToolbar,
+#main-window[tabsintitlebar][sizemode="normal"]:not([inFullscreen]) #toolbar-menubar[autohide="true"][inactive] ~ #TabsToolbar {
+ margin-top: var(--space-above-tabbar);
+}
+
+#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #toolbar-menubar[customizing-dragovertarget].customization-target::before,
+#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #TabsToolbar[customizing-dragovertarget].customization-target::before,
+#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #toolbar-menubar.customization-target:hover::before,
+#main-window[customize-entered][tabsintitlebar]:not([inFullscreen]) #TabsToolbar.customization-target:hover::before {
+ outline-color: CaptionText;
+}
+
+#navigator-toolbox {
+ -moz-appearance: none;
+ background-color: transparent;
+ border-top: none;
+}
+
+#navigator-toolbox::after {
+ content: "";
+ display: -moz-box;
+ -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
+ border-bottom: 1px solid ThreeDShadow;
+}
+
+@media (-moz-windows-default-theme) {
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #navigator-toolbox::after {
+ border-bottom-color: #aabccf;
+ }
+ }
+
+ @media (-moz-os-version: windows-win8),
+ (-moz-os-version: windows-win10) {
+ #navigator-toolbox::after {
+ border-bottom-color: #c2c2c2;
+ }
+ }
+}
+
+#navigator-toolbox:-moz-lwtheme::after {
+ border-bottom-color: rgba(0,0,0,.3);
+}
+
+#navigator-toolbox > toolbar {
+ -moz-appearance: none;
+ border-style: none;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ background-clip: padding-box;
+ background-image: linear-gradient(@toolbarHighlight@, @toolbarHighlight@);
+}
+
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #nav-bar {
+ background-image: linear-gradient(@toolbarHighlight@, transparent) !important;
+ }
+
+ #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar) {
+ background-image: none;
+ }
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme) {
+ background-color: -moz-Dialog;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar) {
+ overflow: -moz-hidden-unscrollable;
+ max-height: 4em;
+ transition: min-height 170ms ease-out, max-height 170ms ease-out;
+ padding: 0 5px;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(#nav-bar):not(#addon-bar)[collapsed=true] {
+ min-height: 0.1px;
+ max-height: 0;
+ transition: min-height 170ms ease-out, max-height 170ms ease-out, visibility 170ms linear;
+}
+
+@media not all and (-moz-windows-compositor),
+ not all and (-moz-windows-default-theme) {
+ /* Please keep the menu text colors in this media block in sync with
+ * devedition.css, minus the :not(:-moz-lwtheme) condition - see Bug 1165718.
+ */
+ #main-window[tabsintitlebar]:not([inFullscreen]) #toolbar-menubar:not(:-moz-lwtheme),
+ #main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar:not(:-moz-lwtheme) {
+ color: CaptionText;
+ }
+
+ #main-window[tabsintitlebar]:not([inFullscreen]) #toolbar-menubar:not(:-moz-lwtheme):-moz-window-inactive,
+ #main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar:not(:-moz-lwtheme):-moz-window-inactive {
+ color: InactiveCaptionText;
+ }
+}
+
+@media not all and (-moz-windows-compositor) {
+ #main-window[tabsintitlebar] #titlebar:-moz-lwtheme {
+ visibility: hidden;
+ }
+
+ #main-window[tabsintitlebar] #titlebar-content:-moz-lwtheme {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
+ visibility: visible;
+ }
+
+ /* Top-level menu appearance has transparent background, so the text color
+ needs to be inherited from our custom menubar too. */
+ #main-window[tabsintitlebar] #main-menubar > menu:not(:-moz-lwtheme) {
+ color: inherit;
+ }
+}
+
+/**
+ * In the classic themes, the titlebar has a horizontal gradient, which is
+ * problematic for reading the text of background tabs when they're in the
+ * titlebar. We side-step this issue by layering our own background underneath
+ * the tabs. Unfortunately, this requires a bunch of positioning in order to get
+ * text and icons to not appear fuzzy.
+ */
+@media (-moz-windows-classic) {
+ /**
+ * We need to bump up the z-index of the tabbrowser-tabs so that they appear
+ * over top of the fog we're applying for classic themes, as well as the nav-bar.
+ */
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #tabbrowser-tabs {
+ position: relative;
+ z-index: 2;
+ }
+
+ #main-window[tabsintitlebar] #TabsToolbar:not(:-moz-lwtheme) {
+ position: relative;
+ }
+
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #TabsToolbar:not(:-moz-lwtheme)::after {
+ /* Because we use placeholders for window controls etc. in the tabstrip,
+ * and position those with ordinal attributes, and because our layout code
+ * expects :before/:after nodes to come first/last in the frame list,
+ * we have to reorder this element to come last, hence the
+ * ordinal group value (see bug 853415). */
+ -moz-box-ordinal-group: 1001;
+ box-shadow: 0 0 50px 8px ActiveCaption;
+ content: "";
+ display: -moz-box;
+ height: 0;
+ margin: 0 50px;
+ position: absolute;
+ pointer-events: none;
+ top: 100%;
+ width: -moz-available;
+ }
+
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #TabsToolbar:not(:-moz-lwtheme):-moz-window-inactive::after {
+ box-shadow: 0 0 50px 8px InactiveCaption;
+ }
+
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) toolbar[customindex]:not(:-moz-lwtheme),
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #PersonalToolbar:not(:-moz-lwtheme) {
+ position: relative;
+ }
+
+ /* Need to constrain the box shadow fade to avoid overlapping layers, see bug 886281. */
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #navigator-toolbox:not(:-moz-lwtheme) {
+ overflow: -moz-hidden-unscrollable;
+ }
+
+ /**
+ * When the tabstrip is overflowed, pinned tab separators get position: absolute,
+ * which makes the pinned tab separators leak over the nav-bar highlight. Forcing
+ * the element to snap to the bottom of the client rect works around the issue.
+ */
+ #main-window[tabsintitlebar] #tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned]::before {
+ bottom: 0px;
+ }
+
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) #TabsToolbar .toolbarbutton-1 {
+ position: relative;
+ z-index: 1;
+ }
+
+ /**
+ * With the tabbrowser-tabs element z-index'd above the nav-bar, we now get the
+ * scrollbox button borders leaking over the nav-bar highlight. This transparent bottom
+ * border forces the scrollbox button borders to terminate a pixel early, working
+ * around the issue.
+ */
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) .tabbrowser-arrowscrollbox > .scrollbutton-up,
+ #main-window[tabsintitlebar]:not([sizemode=fullscreen]) .tabbrowser-arrowscrollbox > .scrollbutton-down {
+ border-bottom: 1px solid transparent;
+ }
+
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+ /* Render a window top border: */
+ background-image: linear-gradient(to bottom,
+ ThreeDLightShadow 0, ThreeDLightShadow 1px,
+ ThreeDHighlight 1px, ThreeDHighlight 2px,
+ ActiveBorder 2px, ActiveBorder 4px, transparent 4px);
+ }
+
+ /* End classic titlebar gradient */
+
+ #main-window[tabsintitlebar]:not([inFullscreen]) :-moz-any(#TabsToolbar, #toolbar-menubar) toolbarbutton:not(:-moz-lwtheme) {
+ color: inherit;
+ }
+}
+
+/* Render a window top border for lwthemes on WinXP modern themes: */
+@media (-moz-windows-theme: luna-blue) {
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+ background-image: linear-gradient(to bottom,
+ rgb(8, 49, 216) 0, rgb(8, 49, 216) 1px,
+ rgb(15, 77, 227) 1px, rgb(15, 77, 227) 2px,
+ rgb(22, 106, 238) 2px, rgb(22, 106, 238) 3px,
+ rgb(8, 85, 221) 3px, rgb(8, 85, 221) 4px,
+ transparent 4px);
+ }
+
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive {
+ background-image: linear-gradient(to bottom,
+ rgb(91, 104, 205) 0, rgb(91, 104, 205) 1px,
+ rgb(116, 128, 220) 1px, rgb(116, 128, 220) 2px,
+ rgb(117, 140, 221) 2px, rgb(117, 140, 221) 4px,
+ transparent 4px);
+ }
+}
+
+@media (-moz-windows-theme: luna-silver) {
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+ background-image: linear-gradient(to bottom,
+ rgb(102,102,126) 0, rgb(102,102,126) 1px,
+ rgb(168,167,191) 1px, rgb(168,167,191) 2px,
+ white 2px, white 3px,
+ rgb(188,188,207) 3px, rgb(188,188,207) 4px,
+ transparent 4px);
+ }
+
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive {
+ background-image: linear-gradient(to bottom,
+ rgb(186,186,197) 0, rgb(186,186,197) 1px,
+ rgb(236,238,245) 1px, rgb(236,238,245) 2px,
+ white 2px, white 3px,
+ rgb(215,215,227) 3px, rgb(215,215,227) 4px,
+ transparent 4px);
+ }
+}
+
+@media (-moz-windows-theme: luna-olive) {
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme {
+ background-image: linear-gradient(to bottom,
+ rgb(139,161,105) 0, rgb(139,161,105) 1px,
+ rgb(171, 189, 133) 1px, rgb(171, 189, 133) 2px,
+ rgb(164,178,127) 2px, rgb(164,178,127) 3px,
+ transparent 3px);
+ }
+
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel:-moz-lwtheme:-moz-window-inactive {
+ background-image: linear-gradient(to bottom,
+ rgb(207, 214, 188) 0, rgb(207, 214, 188) 1px,
+ rgb(224, 226, 200) 1px, rgb(224, 226, 200) 2px,
+ rgb(214, 216, 190) 2px, rgb(214, 216, 190) 3px,
+ transparent 3px);
+ }
+}
+
+#TabsToolbar:not([collapsed="true"]) + #nav-bar {
+ /* Move up into the TabsToolbar for the inner highlight at the top of the nav-bar */
+ margin-top: calc(-1 * var(--navbar-tab-toolbar-highlight-overlap));
+ /* Position the toolbar above the bottom of background tabs */
+ position: relative;
+ z-index: 1;
+}
+
+#nav-bar {
+ border-top: 1px solid @toolbarShadowColor@ !important;
+ box-shadow: 0 1px 0 @toolbarHighlight@ inset;
+}
+@media not all and (-moz-windows-compositor) {
+ #TabsToolbar[collapsed="true"] + #nav-bar {
+ border-top-style: none !important;
+ }
+}
+
+#personal-bookmarks {
+ min-height: 24px;
+}
+
+#print-preview-toolbar:not(:-moz-lwtheme) {
+ -moz-appearance: toolbox;
+}
+
+#browser-bottombox:not(:-moz-lwtheme) {
+ background-color: -moz-dialog;
+}
+
+@media (-moz-os-version: windows-xp) and (-moz-windows-default-theme) {
+ #main-window[tabsintitlebar][sizemode="normal"] #toolbar-menubar {
+ margin-top: 4px;
+ }
+}
+
+/* ::::: titlebar ::::: */
+
+#main-window[sizemode="normal"] > #titlebar {
+ -moz-appearance: -moz-window-titlebar;
+}
+
+#main-window[sizemode="maximized"] > #titlebar {
+ -moz-appearance: -moz-window-titlebar-maximized;
+}
+
+@media (-moz-windows-classic) {
+ #main-window[tabsintitlebar][sizemode="normal"] > #tab-view-deck > #browser-panel > #navigator-toolbox > #toolbar-menubar {
+ margin-top: 4px;
+ }
+}
+
+/* The button box must appear on top of the navigator-toolbox in order for
+ * click and hover mouse events to work properly for the button in the restored
+ * window state. Otherwise, elements in the navigator-toolbox, like the menubar,
+ * can swallow those events. It will also place the buttons above the fog on
+ * themes with Aero Glass.
+ */
+#titlebar-buttonbox {
+ z-index: 1;
+}
+
+.titlebar-placeholder[type="caption-buttons"] {
+ margin-left: 22px; /* space needed for Aero Snap */
+}
+
+@media (-moz-os-version: windows-xp) {
+ .titlebar-placeholder[type="caption-buttons"] {
+ margin-left: 10px; /* less space needed on XP because there's no Aero Snap */
+ }
+}
+
+/* titlebar command buttons */
+
+#titlebar-min {
+ -moz-appearance: -moz-window-button-minimize;
+}
+
+#titlebar-max {
+ -moz-appearance: -moz-window-button-maximize;
+}
+
+#main-window[sizemode="maximized"] #titlebar-max {
+ -moz-appearance: -moz-window-button-restore;
+}
+
+#titlebar-close {
+ -moz-appearance: -moz-window-button-close;
+}
+
+@media not all and (-moz-windows-classic) {
+ #titlebar-min {
+ margin-inline-end: 2px;
+ }
+}
+
+/* ::::: bookmark buttons ::::: */
+
+toolbarbutton.bookmark-item:not(.subviewbutton),
+#personal-bookmarks[cui-areatype="toolbar"]:not([overflowedItem=true]) > #bookmarks-toolbar-placeholder {
+ margin: 0;
+ padding: 2px 3px;
+ -moz-appearance: none;
+ border: 1px solid transparent;
+ border-radius: 1px;
+ background-origin: padding-box !important;
+ background-clip: padding-box !important;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):hover:not([disabled="true"]):not([open]) {
+ border-color: var(--toolbarbutton-hover-bordercolor);
+ background: var(--toolbarbutton-hover-background);
+}
+
+toolbarbutton.bookmark-item:not(.subviewbutton):hover:active:not([disabled="true"]),
+toolbarbutton.bookmark-item[open="true"] {
+ border-color: var(--toolbarbutton-active-bordercolor);
+ box-shadow: var(--toolbarbutton-active-boxshadow);
+ background: var(--toolbarbutton-active-background);
+}
+
+.bookmark-item > .toolbarbutton-icon,
+#personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Force the display of the label for bookmarks */
+.bookmark-item > .toolbarbutton-text,
+#personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-text {
+ display: -moz-box !important;
+}
+
+.bookmark-item > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+#bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
+#personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
+}
+
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+ from { transform: rotate(0deg) translateX(-16px) rotate(0deg) scale(1); opacity: 0; }
+ 60% { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(2.2); opacity: 1; }
+ 80% { opacity: 1; }
+ to { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+ from { transform: scale(1); }
+ 50% { transform: scale(1.3); }
+ to { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+ min-height: 1px;
+ min-width: 1px;
+ height: 1px;
+ margin-bottom: -1px;
+ z-index: 5;
+ position: relative;
+}
+
+#bookmarked-notification {
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+ opacity: 0;
+}
+
+#bookmarked-notification-dropmarker-anchor {
+ z-index: -1;
+ position: relative;
+}
+
+#bookmarked-notification-dropmarker-icon {
+ width: 18px;
+ height: 18px;
+ visibility: hidden;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+ background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+ animation: animation-bookmarkAdded 800ms;
+ animation-timing-function: ease, ease, ease;
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: none !important;
+}
+
+#bookmarked-notification-dropmarker-anchor[notification="finish"] > #bookmarked-notification-dropmarker-icon {
+ visibility: visible;
+ animation: animation-bookmarkPulse 300ms;
+ animation-delay: 600ms;
+ animation-timing-function: ease-out;
+}
+
+/* ::::: bookmark menus ::::: */
+
+menu.bookmark-item,
+menuitem.bookmark-item {
+ min-width: 0;
+ max-width: 32em;
+}
+
+.bookmark-item:not(.subviewbutton) > .menu-iconic-left {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.bookmark-item > .menu-iconic-left > .menu-iconic-icon {
+ padding-inline-start: 0px;
+}
+
+/* ::::: bookmark items ::::: */
+
+.bookmark-item {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.bookmark-item[container] {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][open] {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+.bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/livemark-folder.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited] {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[query][hostContainer] {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[query][hostContainer][open] {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+.bookmark-item[cutting] > .toolbarbutton-icon,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
+ opacity: 0.5;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-text,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
+ opacity: 0.7;
+}
+
+/* ::::: primary toolbar buttons ::::: */
+
+%include ../shared/toolbarbuttons.inc.css
+
+@media (-moz-windows-theme: luna-silver) and (max-resolution: 1dppx) {
+ :-moz-any(@primaryToolbarButtons@),
+ #bookmarks-menu-button.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ list-style-image: url("chrome://browser/skin/Toolbar-lunaSilver.png");
+ }
+}
+
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menu-dropmarker,
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-dropmarker,
+#main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#main-window:not([customizing]) .toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled=true] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+}
+
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
+}
+
+.toolbarbutton-1 > .toolbarbutton-icon,
+.toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ margin-inline-end: 0;
+}
+
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1 > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon {
+ max-width: 16px;
+}
+
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > .toolbarbutton-icon,
+:-moz-any(toolbar, .widget-overflow-list) .toolbarbutton-1:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon) > :-moz-any(.toolbarbutton-menubutton-button, .toolbarbutton-badge-stack) > .toolbarbutton-icon,
+#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ max-width: 18px;
+}
+
+%include ../shared/menupanel.inc.css
+
+.findbar-button,
+#nav-bar .toolbarbutton-1,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-appearance: none;
+ border: none;
+ padding: 0;
+ background: none;
+}
+
+#nav-bar .toolbarbutton-1:not([type=menu-button]),
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ padding: var(--toolbarbutton-vertical-outer-padding) 2px;
+ -moz-box-pack: center;
+}
+
+#nav-bar #PanelUI-menu-button {
+ padding-inline-start: 5px;
+ padding-inline-end: 5px;
+}
+
+#nav-bar .toolbarbutton-1[type=panel],
+#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+#nav-bar .toolbarbutton-1 > menupopup {
+ margin-top: -3px;
+}
+
+#nav-bar .toolbarbutton-1 > menupopup.cui-widget-panel {
+ margin-top: -8px;
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ padding-inline-end: 0;
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ padding-inline-start: 0;
+ -moz-box-align: center;
+}
+
+.findbar-button > .toolbarbutton-text,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-text,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-badge-stack,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
+ padding: var(--toolbarbutton-vertical-inner-padding) 6px;
+ background-origin: padding-box !important;
+ background-clip: padding-box !important;
+ border: 1px solid transparent;
+ border-radius: 1px;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#nav-bar #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ /* horizontal padding + border + actual icon width */
+ max-width: 32px;
+}
+
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ /* < Win8 */
+ :root {
+ --toolbarbutton-hover-background: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
+ --toolbarbutton-hover-bordercolor: hsla(210,54%,20%,.15) hsla(210,54%,20%,.2) hsla(210,54%,20%,.25);
+ --toolbarbutton-hover-boxshadow: 0 1px hsla(0,0%,100%,.3) inset,
+ 0 1px hsla(210,54%,20%,.03),
+ 0 0 2px hsla(210,54%,20%,.1);
+
+ --toolbarbutton-active-background: hsla(210,54%,20%,.15) linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
+ --toolbarbutton-active-bordercolor: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
+ --toolbarbutton-active-boxshadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
+ 0 0 1px hsla(210,54%,20%,.2) inset,
+/* allows keyhole-forward-clip-path to be used for non-hover as well as hover: */
+ 0 1px 0 hsla(210,54%,20%,0),
+ 0 0 2px hsla(210,54%,20%,0);
+
+ --toolbarbutton-checkedhover-backgroundcolor: rgba(90%,90%,90%,.4);
+ }
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(ltr),
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(rtl),
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ border-inline-end-style: none;
+}
+
+#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-badge-stack,
+#nav-bar .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@, .toolbarbutton-legacy-addon)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ padding: calc(var(--toolbarbutton-vertical-inner-padding) + 1px) 7px;
+}
+
+#nav-bar .toolbarbutton-1[type=panel] > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1[type=panel] > .toolbarbutton-badge-stack,
+#nav-bar .toolbarbutton-1[type=menu]:not(#PanelUI-menu-button):not(#back-button):not(#forward-button):not(#new-tab-button) > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1[type=menu]:not(#PanelUI-menu-button) > .toolbarbutton-badge-stack,
+#nav-bar .toolbarbutton-1[type=menu] > .toolbarbutton-text /* hack for add-ons that forcefully display the label */ {
+ padding-inline-end: 17px;
+}
+
+#nav-bar .toolbarbutton-1[type=panel] > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1[type=menu]:not(#PanelUI-menu-button):not(#back-button):not(#forward-button):not(#new-tab-button) > .toolbarbutton-icon {
+ /* horizontal padding + border + icon width */
+ max-width: 43px;
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menu-dropmarker {
+ margin-inline-start: -15px;
+}
+
+#nav-bar #bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ /* horizontal padding + border + actual icon width */
+ max-width: 31px;
+}
+
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ padding-top: calc(var(--toolbarbutton-vertical-inner-padding) + 6px);
+ padding-bottom: calc(var(--toolbarbutton-vertical-inner-padding) + 6px);
+}
+
+#nav-bar .toolbaritem-combined-buttons {
+ margin-left: 2px;
+ margin-right: 2px;
+}
+
+#nav-bar .toolbaritem-combined-buttons > .toolbarbutton-1 {
+ padding-left: 0;
+ padding-right: 0;
+}
+
+#nav-bar .toolbaritem-combined-buttons:not(:hover) > separator,
+#nav-bar .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ content: "";
+ display: -moz-box;
+ width: 1px;
+ height: 16px;
+ margin-inline-end: -1px;
+ background-image: linear-gradient(currentColor 0, currentColor 100%);
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 1px 16px;
+ opacity: .2;
+}
+
+#nav-bar[brighttext] .toolbaritem-combined-buttons > separator,
+#nav-bar[brighttext] .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ opacity: .3;
+}
+
+.findbar-button:not(:-moz-any([checked="true"],[disabled="true"])):hover > .toolbarbutton-text,
+#nav-bar .toolbarbutton-1:not([disabled=true]) > .toolbarbutton-menubutton-button[open] + .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+#nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+#nav-bar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-text,
+#nav-bar .toolbarbutton-1:not([disabled=true]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-badge-stack,
+@conditionalForwardWithUrlbar@ > #forward-button:not([open]):not(:active):not([disabled]):hover > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1:not([buttonover]):not([open]):not(:active):hover > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon {
+ background: var(--toolbarbutton-hover-background);
+ border-color: var(--toolbarbutton-hover-bordercolor);
+ box-shadow: var(--toolbarbutton-hover-boxshadow);
+}
+
+.findbar-button:not([disabled=true]):-moz-any([checked="true"],:hover:active) > .toolbarbutton-text,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):-moz-any(:hover:active, [open]) > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker:not([disabled=true]) > .dropmarker-icon,
+#nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-text,
+#nav-bar .toolbarbutton-1:not([disabled=true]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-stack {
+ background: var(--toolbarbutton-active-background);
+ border-color: var(--toolbarbutton-active-bordercolor);
+ box-shadow: var(--toolbarbutton-active-boxshadow);
+ transition-duration: 10ms;
+}
+
+#nav-bar .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
+ background-color: var(--toolbarbutton-checkedhover-backgroundcolor);
+ transition: background-color .4s;
+}
+
+#TabsToolbar .toolbarbutton-1,
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-appearance: none;
+ border-style: none;
+ padding: 0 3px;
+}
+
+#TabsToolbar .toolbarbutton-1 {
+ margin-bottom: var(--tab-toolbar-navbar-overlap);
+}
+
+#TabsToolbar .toolbarbutton-1:not([disabled=true]):hover,
+#TabsToolbar .toolbarbutton-1[open],
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled=true]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled=true]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled=true]):hover {
+ background-image: linear-gradient(transparent, rgba(255,255,255,.5)),
+ linear-gradient(transparent, rgba(0,0,0,.25) 30%),
+ linear-gradient(transparent, rgba(0,0,0,.25) 30%);
+ background-position: 1px -1px, 0 -1px, 100% -1px;
+ background-size: calc(100% - 2px) 100%, 1px 100%, 1px 100%;
+ background-repeat: no-repeat;
+}
+
+/* unified back/forward button */
+
+:-moz-any(#back-button, #forward-button) > .toolbarbutton-icon {
+ border-color: var(--urlbar-border-color-hover) !important;
+}
+
+:-moz-any(#back-button, #forward-button):not(:hover):not(:active):not([open=true]) > .toolbarbutton-icon,
+:-moz-any(#back-button, #forward-button)[disabled=true] > .toolbarbutton-icon {
+ background-color: rgba(255,255,255,.15) !important;
+}
+
+#forward-button {
+ -moz-box-align: stretch; /* let the button shape grow vertically with the location bar */
+ padding: 0 !important;
+}
+
+#forward-button > menupopup {
+ margin-top: 1px !important;
+}
+
+#forward-button > .toolbarbutton-icon {
+ border-left-style: none !important;
+ border-radius: 0 !important;
+ padding-left: calc(var(--backbutton-urlbar-overlap) + 3px) !important;
+ padding-right: 3px !important;
+ max-width: calc(var(--forwardbutton-width) + var(--backbutton-urlbar-overlap)) !important;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+ transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+ margin-left: calc(0px - var(--forwardbutton-width) - var(--backbutton-urlbar-overlap));
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+ /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+ transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+ /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+ margin-left: calc(-0.01px - var(--forwardbutton-width) - var(--backbutton-urlbar-overlap));
+}
+
+#back-button {
+ padding-top: 3px !important;
+ padding-bottom: 3px !important;
+ padding-inline-start: 5px !important;
+ padding-inline-end: 0 !important;
+ position: relative !important;
+ z-index: 1 !important;
+ border-radius: 0 10000px 10000px 0 !important;
+}
+
+#back-button:-moz-locale-dir(rtl) {
+ border-radius: 10000px 0 0 10000px !important;
+}
+
+#back-button > menupopup {
+ margin-top: -1px !important;
+}
+
+#back-button > .toolbarbutton-icon {
+ border-radius: 10000px !important;
+ padding: 6px !important;
+ max-width: 32px !important; /* icon width + horizontal padding + border */
+}
+
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-back.png") !important;
+}
+
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-forward.png") !important;
+}
+
+/* undo close tab menu item */
+#alltabs_undoCloseTab {
+ list-style-image: url(chrome://browser/skin/undoCloseTab.png);
+}
+
+@media (min-resolution: 1.1dppx) {
+ #alltabs_undoCloseTab {
+ list-style-image: url(chrome://browser/skin/undoCloseTab@2x.png);
+ }
+ #alltabs_undoCloseTab > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+/* zoom control text (reset) button special case: */
+
+#nav-bar #zoom-reset-button > .toolbarbutton-text {
+ /* To make this line up with the icons, it needs the same height (18px) +
+ * padding (2 * 2px) + border (2 * 1px), but as a minimum because otherwise
+ * increase in text sizes would break things...
+ */
+ min-height: 24px;
+}
+
+/* ::::: fullscreen window controls ::::: */
+
+#minimize-button,
+#restore-button,
+#close-button {
+ -moz-appearance: none;
+ border: none;
+ margin: 0 !important;
+ padding: 6px 12px;
+}
+
+#minimize-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
+}
+
+#restore-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
+}
+
+#minimize-button:hover,
+#restore-button:hover {
+ background-color: hsla(0, 0%, 0%, .12);
+}
+
+#minimize-button:hover:active,
+#restore-button:hover:active {
+ background-color: hsla(0, 0%, 0%, .22);
+}
+
+#close-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
+}
+
+#close-button:hover {
+ background-color: hsl(355, 86%, 49%);
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
+}
+
+#close-button:hover:active {
+ background-color: hsl(355, 82%, 69%);
+}
+
+toolbar[brighttext] #minimize-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-white);
+}
+
+toolbar[brighttext] #restore-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-white);
+}
+
+toolbar[brighttext] #close-button {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
+}
+
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #window-controls {
+ margin-inline-start: 4px;
+ }
+
+ #minimize-button,
+ #restore-button,
+ #close-button {
+ /* Important to ensure this applies even on toolbar[brighttext] */
+ list-style-image: url("chrome://global/skin/icons/windowControls.png") !important;
+ /* Also override background color to a avoid hover background styling
+ * leaking through around the image. */
+ background-color: transparent !important;
+ padding: 0;
+ }
+
+ #minimize-button {
+ -moz-image-region: rect(0, 16px, 16px, 0);
+ }
+
+ #minimize-button:hover {
+ -moz-image-region: rect(16px, 16px, 32px, 0);
+ }
+
+ #minimize-button:hover:active {
+ -moz-image-region: rect(32px, 16px, 48px, 0);
+ }
+
+ #restore-button {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+ }
+
+ #restore-button:hover {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+ }
+
+ #restore-button:hover:active {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+ }
+
+ #close-button {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+ -moz-appearance: none;
+ border-style: none;
+ margin: 2px;
+ }
+
+ #close-button:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+ }
+
+ #close-button:hover:active {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+ }
+}
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #window-controls {
+ -moz-box-align: start;
+ }
+
+ #minimize-button,
+ #restore-button,
+ #close-button {
+ -moz-appearance: none;
+ border-style: none;
+ margin: 0;
+ }
+
+ #close-button {
+ -moz-image-region: rect(0, 49px, 16px, 32px);
+ }
+
+ #close-button:hover {
+ -moz-image-region: rect(16px, 49px, 32px, 32px);
+ }
+
+ #close-button:hover:active {
+ -moz-image-region: rect(32px, 49px, 48px, 32px);
+ }
+
+ #minimize-button:-moz-locale-dir(rtl),
+ #restore-button:-moz-locale-dir(rtl),
+ #close-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+ }
+}
+
+/* ::::: Location Bar ::::: */
+
+#main-window {
+ --urlbar-border-color: ThreeDShadow;
+ --urlbar-border-color-hover: var(--urlbar-border-color);
+}
+
+#navigator-toolbox:-moz-lwtheme {
+ --urlbar-border-color: var(--toolbarbutton-hover-bordercolor);
+}
+
+@media (-moz-windows-default-theme) {
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ #main-window:not(:-moz-lwtheme) {
+ --urlbar-border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
+ --urlbar-border-color-hover: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
+ }
+ }
+
+ @media (-moz-os-version: windows-win10) {
+ #main-window:not(:-moz-lwtheme) {
+ --urlbar-border-color: hsl(0,0%,90%);
+ --urlbar-border-color-hover: hsl(0,0%,80%);
+ }
+ }
+}
+
+#urlbar,
+.searchbar-textbox {
+ -moz-appearance: none;
+ margin: 0 3px;
+ padding: 0;
+ background-clip: padding-box;
+ border: 1px solid;
+ border-color: var(--urlbar-border-color);
+}
+
+#urlbar:hover,
+.searchbar-textbox:hover {
+ border-color: var(--urlbar-border-color-hover);
+}
+
+@media (-moz-windows-default-theme) {
+ #urlbar,
+ .searchbar-textbox {
+ border-radius: 1px;
+ }
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ #urlbar:not(:-moz-lwtheme),
+ .searchbar-textbox:not(:-moz-lwtheme) {
+ box-shadow: 0 1px 0 hsla(0,0%,0%,.01) inset,
+ 0 1px 0 hsla(0,0%,100%,.1);
+ }
+ }
+
+ @media (-moz-os-version: windows-win10) {
+ #urlbar:not(:-moz-lwtheme),
+ .searchbar-textbox:not(:-moz-lwtheme) {
+ padding: 1px;
+ transition-property: border-color, box-shadow;
+ transition-duration: .1s;
+ }
+
+ #urlbar:not(:-moz-lwtheme)[focused],
+ .searchbar-textbox:not(:-moz-lwtheme)[focused] {
+ box-shadow: 0 0 0 1px Highlight inset;
+ }
+ }
+
+ @media not all and (-moz-os-version: windows-xp) {
+ #urlbar:not(:-moz-lwtheme)[focused],
+ .searchbar-textbox:not(:-moz-lwtheme)[focused] {
+ border-color: Highlight;
+ }
+ }
+}
+
+@media (-moz-os-version: windows-win10) {
+ #urlbar,
+ .searchbar-textbox {
+ font-size: 1.15em;
+ min-height: 28px;
+ }
+
+ :root {
+ /* let toolbar buttons match the location and search bar's minimum height */
+ --toolbarbutton-vertical-inner-padding: 4px;
+ --toolbarbutton-vertical-outer-padding: 5px;
+ }
+}
+
+#urlbar:-moz-lwtheme,
+.searchbar-textbox:-moz-lwtheme {
+ background-color: rgba(255,255,255,.8);
+ color: black;
+}
+
+#urlbar:-moz-lwtheme:hover:not([readonly]),
+.searchbar-textbox:-moz-lwtheme:hover {
+ background-color: rgba(255,255,255,.9);
+}
+
+#urlbar:-moz-lwtheme[focused]:not([readonly]),
+.searchbar-textbox:-moz-lwtheme[focused] {
+ background-color: white;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar {
+ border-inline-start: none;
+ padding-inline-start: 0;
+ margin-left: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ {
+ clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
+ margin-inline-start: calc(-1 * var(--backbutton-urlbar-overlap));
+}
+
+@media (-moz-os-version: windows-win10) {
+ @conditionalForwardWithUrlbar@ {
+ clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path-win10");
+ }
+
+ :root {
+ --backbutton-urlbar-overlap: 9px;
+ }
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+ /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
+ transform: scaleX(-1);
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+ -moz-box-direction: reverse;
+}
+
+html|*.urlbar-input:-moz-lwtheme::placeholder,
+.searchbar-textbox:-moz-lwtheme > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::placeholder {
+ opacity: 1.0;
+ color: #777;
+}
+
+#urlbar-container {
+ -moz-box-align: center;
+}
+
+.urlbar-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.urlbar-input-box {
+ margin-inline-start: 0;
+}
+
+.urlbar-input-box,
+#urlbar-display-box {
+ padding-inline-start: 4px;
+ border-inline-start: 1px solid var(--urlbar-separator-color);
+ border-image: linear-gradient(transparent 15%, var(--urlbar-separator-color) 15%, var(--urlbar-separator-color) 85%, transparent 85%);
+ border-image-slice: 1;
+}
+
+#urlbar-icons {
+ -moz-box-align: center;
+}
+
+.urlbar-icon {
+ padding: 0 3px;
+ /* 16x16 icon with border-box sizing */
+ width: 22px;
+ height: 16px;
+}
+
+/* ::::: URL Bar Zoom Reset Button ::::: */
+@keyframes urlbar-zoom-reset-pulse {
+ 0% {
+ transform: scale(0);
+ }
+ 75% {
+ transform: scale(1.5);
+ }
+ 100% {
+ transform: scale(1.0);
+ }
+}
+
+#urlbar-zoom-button {
+ -moz-appearance: none;
+ margin: 0 3px;
+ font-size: .8em;
+ padding: 0 8px;
+ border-radius: 1em;
+ background-color: hsla(0,0%,0%,.05);
+ color: inherit;
+ border: 1px solid ThreeDLightShadow;
+}
+
+#urlbar-zoom-button[animate="true"] {
+ animation-name: urlbar-zoom-reset-pulse;
+ animation-duration: 250ms;
+}
+
+#urlbar-zoom-button:hover {
+ background-color: hsla(0,0%,0%,.1);
+}
+
+#urlbar-zoom-button:hover:active {
+ background-color: hsla(0,0%,0%,.15);
+}
+
+#urlbar-zoom-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+
+#urlbar-zoom-button > .toolbarbutton-icon {
+ display: none;
+}
+
+.search-go-container {
+ padding: 2px 2px;
+}
+
+#urlbar-search-footer {
+ border-top: 1px solid var(--panel-separator-color);
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#urlbar-search-settings {
+ -moz-appearance: none;
+ -moz-user-focus: ignore;
+ color: GrayText;
+ margin: 0;
+ border: 0;
+ padding: 8px 20px;
+ background: transparent;
+}
+
+#urlbar-search-settings:hover {
+ background-color: var(--arrowpanel-dimmed);
+}
+
+#urlbar-search-settings:hover:active {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+#urlbar-search-splitter {
+ min-width: 6px;
+ margin-inline-start: -3px;
+ border: none;
+ background: transparent;
+}
+
+#urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
+ margin-inline-start: 0;
+}
+
+.urlbar-display {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-inline-start: 0;
+ color: GrayText;
+}
+
+%include ../shared/urlbarSearchSuggestionsNotification.inc.css
+
+#search-container {
+ min-width: calc(54px + 11ch);
+}
+
+/* identity box */
+
+#identity-box:-moz-focusring {
+ outline: 1px dotted;
+ outline-offset: -3px;
+}
+
+/* Location bar dropmarker */
+
+.urlbar-history-dropmarker {
+ -moz-appearance: none;
+ padding: 0 3px;
+ background-color: transparent;
+ border: none;
+ width: auto;
+ list-style-image: var(--urlbar-dropmarker-url);
+ -moz-image-region: var(--urlbar-dropmarker-region);
+ transition: opacity 0.15s ease;
+}
+
+#urlbar-wrapper[switchingtabs] > #urlbar > .urlbar-textbox-container > .urlbar-history-dropmarker {
+ transition: none;
+}
+
+#navigator-toolbox:not(:hover) #nav-bar:not([customizing="true"]) #urlbar:not([focused]) > .urlbar-textbox-container > .urlbar-history-dropmarker {
+ opacity: 0;
+}
+
+.urlbar-history-dropmarker:hover {
+ -moz-image-region: var(--urlbar-dropmarker-hover-region);
+}
+
+.urlbar-history-dropmarker:hover:active,
+.urlbar-history-dropmarker[open="true"] {
+ -moz-image-region: var(--urlbar-dropmarker-active-region);
+}
+
+@media (min-resolution: 1.1dppx) {
+ .urlbar-history-dropmarker {
+ list-style-image: var(--urlbar-dropmarker-2x-url);
+ -moz-image-region: var(--urlbar-dropmarker-2x-region);
+ }
+
+ .urlbar-history-dropmarker:hover {
+ -moz-image-region: var(--urlbar-dropmarker-hover-2x-region);
+ }
+
+ .urlbar-history-dropmarker[open="true"],
+ .urlbar-history-dropmarker:hover:active {
+ -moz-image-region: var(--urlbar-dropmarker-active-2x-region);
+ }
+
+ .urlbar-history-dropmarker > .dropmarker-icon {
+ width: 11px;
+ }
+}
+
+/* page proxy icon */
+
+%include ../shared/identity-block/identity-block.inc.css
+
+/* autocomplete */
+
+%include ../shared/autocomplete.inc.css
+
+#PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
+ border-top: 1px solid ThreeDShadow;
+}
+
+#treecolAutoCompleteImage {
+ max-width: 36px;
+}
+
+.autocomplete-richlistbox {
+ padding: 4px;
+}
+
+.autocomplete-richlistitem {
+ height: 30px;
+ min-height: 30px;
+ font: message-box;
+ border-radius: 2px;
+ border: 1px solid transparent;
+}
+
+.autocomplete-richlistitem[selected=true] {
+ background-color: hsl(210, 80%, 52%);
+}
+
+.ac-title {
+ font-size: 14px;
+ color: hsl(0, 0%, 0%);
+}
+
+.ac-tags {
+ font-size: 12px;
+}
+
+html|span.ac-tag {
+ background-color: hsl(216, 0%, 88%);
+ color: hsl(0, 0%, 0%);
+ border-radius: 2px;
+ border: 1px solid transparent;
+ padding: 0 1px;
+}
+
+.ac-separator,
+.ac-url,
+.ac-action {
+ font-size: 12px;
+}
+
+.ac-separator {
+ color: hsl(0, 0%, 50%);
+}
+
+.ac-url {
+ color: hsl(210, 77%, 47%);
+}
+
+.ac-action {
+ color: hsl(178, 100%, 28%);
+}
+
+.ac-title[selected=true],
+.ac-separator[selected],
+.ac-url[selected=true],
+.ac-action[selected=true] {
+ color: hsl(0, 0%, 100%);
+}
+
+.ac-tags-text[selected] > html|span.ac-tag {
+ background-color: hsl(0, 0%, 100%);
+ color: hsl(210, 80%, 40%);
+}
+
+html|span.ac-emphasize-text-title,
+html|span.ac-emphasize-text-tag,
+html|span.ac-emphasize-text-url {
+ font-weight: 600;
+}
+
+@media not all and (-moz-windows-default-theme) {
+ .autocomplete-richlistitem[selected=true] {
+ background-color: Highlight;
+ }
+
+ .ac-title {
+ color: inherit;
+ }
+
+ html|span.ac-tag {
+ background-color: -moz-FieldText;
+ color: -moz-Field;
+ }
+
+ .ac-separator,
+ .ac-url,
+ .ac-action {
+ color: -moz-nativehyperlinktext;
+ }
+
+ .ac-tags-text[selected] > html|span.ac-tag {
+ background-color: HighlightText;
+ color: Highlight;
+ }
+}
+
+.ac-type-icon[type=bookmark] {
+ list-style-image: url("chrome://browser/skin/urlbar-star.svg#star");
+}
+
+.ac-type-icon[type=bookmark][selected][current] {
+ list-style-image: url("chrome://browser/skin/urlbar-star.svg#star-inverted");
+}
+
+.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/autocomplete-star.png");
+ -moz-image-region: rect(0 16px 16px 0);
+ width: 16px;
+ height: 16px;
+}
+
+@media (min-resolution: 1.1dppx) {
+ .autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/autocomplete-star@2x.png");
+ -moz-image-region: rect(0 32px 32px 0);
+ }
+}
+
+@media not all and (-moz-os-version: windows-vista) and (-moz-windows-default-theme) {
+ @media not all and (-moz-os-version: windows-win7) and (-moz-windows-default-theme) {
+ .autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
+ -moz-image-region: rect(0 32px 16px 16px);
+ }
+
+ @media (min-resolution: 1.1dppx) {
+ .autocomplete-treebody::-moz-tree-image(selected, current, bookmark, treecolAutoCompleteImage) {
+ -moz-image-region: rect(0 64px 32px 32px);
+ }
+ }
+
+ .autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
+ }
+ }
+}
+
+.ac-type-icon[type=keyword],
+.ac-site-icon[type=searchengine],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
+}
+
+.ac-type-icon[type=keyword][selected],
+.ac-site-icon[type=searchengine][selected],
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage, selected) {
+ list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon-inverted);
+}
+
+.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ width: 16px;
+ height: 16px;
+}
+
+.ac-type-icon[type=switchtab],
+.ac-type-icon[type=remotetab] {
+ list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
+}
+
+.ac-type-icon[type=switchtab][selected],
+.ac-type-icon[type=remotetab][selected] {
+ list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
+ color: GrayText;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
+.autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment)
+{
+ color: GrayText;
+ font-size: smaller;
+}
+
+.autocomplete-treebody::-moz-tree-cell(suggesthint) {
+ border-top: 1px solid GrayText;
+}
+
+/* combined go/reload/stop button in location bar */
+
+#urlbar-go-button,
+#urlbar-reload-button,
+#urlbar-stop-button {
+ -moz-appearance: none;
+ border-style: none;
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+ padding: 0 9px;
+ margin-inline-start: 5px;
+ border-inline-start: 1px solid var(--urlbar-separator-color);
+ border-image: linear-gradient(transparent 15%,
+ var(--urlbar-separator-color) 15%,
+ var(--urlbar-separator-color) 85%,
+ transparent 85%);
+ border-image-slice: 1;
+}
+
+#urlbar-reload-button {
+ -moz-image-region: rect(0, 14px, 14px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover {
+ -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover:active {
+ -moz-image-region: rect(28px, 14px, 42px, 0);
+}
+
+#urlbar-reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-go-button {
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+}
+
+#urlbar-go-button:hover {
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+#urlbar-go-button:hover:active {
+ -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-stop-button {
+ -moz-image-region: rect(0, 28px, 14px, 14px);
+}
+
+#urlbar-stop-button:not([disabled]):hover {
+ -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+
+#urlbar-stop-button:hover:active {
+ -moz-image-region: rect(28px, 28px, 42px, 14px);
+}
+
+@media (min-resolution: 1.1dppx) {
+ #urlbar-go-button,
+ #urlbar-reload-button,
+ #urlbar-stop-button {
+ list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
+ }
+
+ #urlbar-go-button > .toolbarbutton-icon,
+ #urlbar-reload-button > .toolbarbutton-icon,
+ #urlbar-stop-button > .toolbarbutton-icon {
+ width: 14px;
+ }
+
+ #urlbar-go-button {
+ -moz-image-region: rect(0, 84px, 28px, 56px);
+ }
+
+ #urlbar-go-button:hover {
+ -moz-image-region: rect(28px, 84px, 56px, 56px);
+ }
+
+ #urlbar-go-button:hover:active {
+ -moz-image-region: rect(56px, 84px, 84px, 56px);
+ }
+
+ #urlbar-reload-button {
+ -moz-image-region: rect(0, 28px, 28px, 0);
+ }
+
+ #urlbar-reload-button:not([disabled]):hover {
+ -moz-image-region: rect(28px, 28px, 56px, 0);
+ }
+
+ #urlbar-reload-button:not([disabled]):hover:active {
+ -moz-image-region: rect(56px, 28px, 84px, 0);
+ }
+
+ #urlbar-stop-button {
+ -moz-image-region: rect(0, 56px, 28px, 28px);
+ }
+
+ #urlbar-stop-button:not([disabled]):hover {
+ -moz-image-region: rect(28px, 56px, 56px, 28px);
+ }
+
+ #urlbar-stop-button:hover:active {
+ -moz-image-region: rect(56px, 56px, 84px, 28px);
+ }
+}
+
+/* popup blocker button */
+
+#page-report-button {
+ list-style-image: url("chrome://browser/skin/urlbar-popup-blocked.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#page-report-button:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#page-report-button:hover:active,
+#page-report-button[open="true"] {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+/* Reader mode button */
+
+#reader-mode-button {
+ list-style-image: url("chrome://browser/skin/readerMode.svg");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#reader-mode-button:hover,
+#reader-mode-button[readeractive]:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#reader-mode-button:hover:active,
+#reader-mode-button[readeractive] {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+/* social share panel */
+%include ../shared/social/social.inc.css
+
+.social-panel-frame {
+ border-radius: inherit;
+}
+
+.social-share-frame {
+ min-width: 756px;
+ height: 150px;
+}
+#share-container {
+ min-width: 756px;
+ background-color: white;
+ background-repeat: no-repeat;
+ background-position: center center;
+}
+#share-container[loading] {
+ background-image: url(chrome://browser/skin/tabbrowser/pendingpaint.png);
+}
+#share-container > browser {
+ transition: opacity 150ms ease-in-out;
+ opacity: 1;
+}
+#share-container[loading] > browser {
+ opacity: 0;
+}
+
+.social-share-toolbar {
+ border-bottom: 1px solid #e2e5e8;
+ padding: 2px;
+}
+
+#social-share-provider-buttons {
+ padding: 0;
+ margin: 0;
+}
+
+.share-provider-button {
+ padding: 5px;
+ margin: 2px;
+}
+
+.share-provider-button > .toolbarbutton-text {
+ display: none;
+}
+.share-provider-button > .toolbarbutton-icon {
+ width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+}
+
+#social-share-panel {
+ min-height: 100px;
+ min-width: 766px;
+}
+
+#share-container,
+.social-share-frame {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: inherit;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: inherit;
+}
+
+#social-share-panel > .social-share-toolbar {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+#social-share-provider-buttons {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+/* bookmarks menu-button */
+
+#nav-bar #bookmarks-menu-button[cui-areatype="toolbar"]:not([overflowedItem=true]) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ padding-top: var(--toolbarbutton-vertical-inner-padding);
+ padding-bottom: var(--toolbarbutton-vertical-inner-padding);
+}
+
+#BMB_bookmarksPopup[side="top"],
+#BMB_bookmarksPopup[side="bottom"] {
+ margin-left: -20px;
+ margin-right: -20px;
+}
+
+#BMB_bookmarksPopup[side="left"],
+#BMB_bookmarksPopup[side="right"] {
+ margin-top: -20px;
+ margin-bottom: -20px;
+}
+
+/* bookmarking panel */
+
+#editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48.png");
+ width: 48px;
+ height: 48px;
+}
+
+#editBookmarkPanelStarIcon[unstarred] {
+ list-style-image: url("chrome://browser/skin/places/unstarred48.png");
+}
+
+#editBookmarkPanelTitle {
+ font-size: 130%;
+}
+
+#editBookmarkPanelHeader,
+#editBookmarkPanelContent {
+ margin-bottom: .5em;
+}
+
+/* Implements editBookmarkPanel resizing on folderTree un-collapse. */
+#editBMPanel_folderTree {
+ min-width: 27em;
+}
+
+/* ::::: content area ::::: */
+
+#sidebar {
+ background-color: Window;
+}
+
+#sidebar-title {
+ padding-inline-start: 0px;
+}
+
+#sidebar-header > .close-icon {
+ -moz-appearance: none;
+ padding: 2px;
+ margin: 0;
+ border: none;
+}
+
+@media not all and (min-resolution: 1.1dppx) {
+ #sidebar-header > .close-icon:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ #sidebar-header > .close-icon:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+}
+
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #sidebar-header > .close-icon {
+ padding-top: 4px;
+ padding-bottom: 4px;
+ }
+}
+
+.browserContainer > findbar {
+ background-color: -moz-dialog;
+ color: -moz-DialogText;
+ text-shadow: none;
+}
+
+/* Tabstrip */
+
+#TabsToolbar {
+ min-height: 0;
+ padding: 0;
+ margin-bottom: calc(-1 * var(--tab-toolbar-navbar-overlap)); /* overlap the nav-bar's top border */
+}
+
+@media (-moz-os-version: windows-xp) and (-moz-windows-default-theme) {
+ #main-window[sizemode=normal] #TabsToolbar {
+ padding-left: 2px;
+ padding-right: 2px;
+ }
+}
+
+%include ../shared/tabs.inc.css
+
+/* Remove border between tab strip and navigation toolbar on Windows 10+ */
+@media not all and (-moz-os-version: windows-xp) {
+ @media not all and (-moz-os-version: windows-vista) {
+ @media not all and (-moz-os-version: windows-win7) {
+ @media not all and (-moz-os-version: windows-win8) {
+ @media (-moz-windows-default-theme) {
+ .tab-background-end[selected=true]::after,
+ .tab-background-start[selected=true]::after {
+ content: none;
+ }
+
+ #TabsToolbar {
+ --tab-stroke-background-size: 0 0;
+ }
+
+ :root {
+ --tab-toolbar-navbar-overlap: 0px;
+ }
+
+ #nav-bar {
+ border-top-style: none !important;
+ box-shadow: none;
+ }
+ }
+ }
+ }
+ }
+}
+
+/* Invert the unhovered close tab icons on bright-text tabs */
+@media not all and (min-resolution: 1.1dppx) {
+ .tab-close-button:-moz-lwtheme-brighttext,
+ #TabsToolbar[brighttext] .tab-close-button:not([selected="true"]) {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ .tab-close-button:-moz-lwtheme-brighttext,
+ #TabsToolbar[brighttext] .tab-close-button:not([selected="true"]) {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+}
+
+/* tabbrowser-tab focus ring */
+.tabbrowser-tab:focus > .tab-stack > .tab-content {
+ outline: 1px dotted;
+ outline-offset: -6px;
+}
+
+/* Tab DnD indicator */
+.tab-drop-indicator {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
+ margin-bottom: -9px;
+ z-index: 3;
+}
+
+/* Tab close button */
+.tab-close-button {
+ -moz-appearance: none;
+ border: none;
+}
+
+/* Tab scrollbox arrow, tabstrip new tab and all-tabs buttons */
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.svg");
+ margin: 0 0 var(--tab-toolbar-navbar-overlap);
+}
+
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-up,
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-down {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.svg);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled],
+.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] {
+ opacity: .4;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
+ transform: scaleX(-1);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ transition: 1s background-color ease-out;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
+ background-color: Highlight;
+ transition: none;
+}
+
+.tabs-newtab-button > .toolbarbutton-icon {
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.tabs-newtab-button,
+#TabsToolbar > #new-tab-button,
+#TabsToolbar > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab.svg);
+ -moz-image-region: auto;
+}
+
+#TabsToolbar[brighttext] .tabs-newtab-button,
+#TabsToolbar[brighttext] > #new-tab-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.svg);
+}
+
+.tabs-newtab-button > .toolbarbutton-icon,
+#TabsToolbar > #new-tab-button > .toolbarbutton-icon,
+#TabsToolbar > toolbarpaletteitem > #new-tab-button > .toolbarbutton-icon {
+ width: 16px;
+}
+
+#TabsToolbar > #new-tab-button {
+ width: 26px;
+}
+
+#alltabs-button {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+}
+
+#TabsToolbar[brighttext] > #alltabs-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #alltabs-button {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
+}
+
+#alltabs-button > .toolbarbutton-icon {
+ margin: 0 2px;
+}
+
+#alltabs-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+/* All tabs menupopup */
+.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.alltabs-item[selected="true"] {
+ font-weight: bold;
+}
+
+.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ .alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ }
+}
+
+toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/toolbar/chevron.gif") !important;
+}
+
+toolbar[brighttext] toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/toolbar/chevron-inverted.png") !important;
+}
+
+toolbarbutton.chevron:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+toolbarbutton.chevron > .toolbarbutton-text,
+toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+toolbarbutton.chevron > .toolbarbutton-icon {
+ margin: 0;
+}
+
+#sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+ margin-inline-end: 4px;
+}
+
+@media (min-resolution: 1.1dppx) {
+ #sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ width: 16px;
+ }
+}
+
+/* Bookmarks toolbar */
+#PlacesToolbarDropIndicator {
+ list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
+}
+
+toolbarbutton.bookmark-item[dragover="true"][open="true"] {
+ -moz-appearance: none;
+ background: Highlight !important;
+ color: HighlightText !important;
+}
+
+/* rules for menupopup drop indicators */
+.menupopup-drop-indicator-bar {
+ position: relative;
+ /* these two margins must together compensate the indicator's height */
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.menupopup-drop-indicator {
+ list-style-image: none;
+ height: 2px;
+ margin-inline-end: -4em;
+ background-color: Highlight;
+}
+
+%include ../shared/notification-icons.inc.css
+
+.popup-notification-body[popupid="addon-progress"],
+.popup-notification-body[popupid="addon-install-confirmation"] {
+ width: 28em;
+ max-width: 28em;
+}
+
+.addon-install-confirmation-name {
+ font-weight: bold;
+}
+
+/* Notification icon box */
+
+.notification-anchor-icon:-moz-focusring {
+ outline: 1px dotted -moz-DialogText;
+}
+
+/* Translation infobar */
+
+%include ../shared/translation/infobar.inc.css
+
+notification[value="translation"] {
+ min-height: 40px;
+}
+
+@media (-moz-windows-default-theme) {
+ notification[value="translation"],
+ notification[value="translation"] button,
+ notification[value="translation"] menulist {
+ min-height: 30px;
+ color: #545454;
+ }
+
+ notification[value="translation"] {
+ background-color: #EEE;
+ }
+
+ notification[value="translation"] button,
+ notification[value="translation"] menulist {
+ padding-inline-end: 1ch;
+ }
+
+ notification[value="translation"] menulist {
+ border: 1px solid #C1C1C1;
+ background-color: #FFF;
+ }
+
+ notification[value="translation"] button {
+ border: 1px solid #C1C1C1;
+ background-color: #FBFBFB;
+ }
+
+ notification[value="translation"] button,
+ notification[value="translation"] menulist,
+ notification[value="translation"] menulist > .menulist-label-box {
+ margin-inline-start: 1ch;
+ margin-inline-end: 1ch;
+ }
+
+ notification[value="translation"] button:hover,
+ notification[value="translation"] button:active,
+ notification[value="translation"] menulist:hover,
+ notification[value="translation"] menulist:active {
+ background-color: #EBEBEB;
+ }
+
+ notification[value="translation"] button[anonid="translate"] {
+ color: #FFF;
+ background-color: #0095DD;
+ box-shadow: none;
+ border: 1px solid #006B9D;
+ }
+
+ notification[value="translation"] button[anonid="translate"]:hover,
+ notification[value="translation"] button[anonid="translate"]:active {
+ background-color: #008ACB;
+ }
+
+ notification[value="translation"] button[type="menu"] > .button-box > .button-menu-dropmarker,
+ notification[value="translation"] menulist > .menulist-dropmarker {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+ }
+
+ notification[value="translation"] button > .button-box,
+ notification[value="translation"] button[type="menu"] > .button-box > .button-menu-dropmarker {
+ padding: 0;
+ margin-inline-start: 3ch;
+ }
+
+ notification[value="translation"] button:not([type="menu"]) > .button-box {
+ margin-inline-end: 3ch;
+ }
+}
+
+.translation-menupopup {
+ -moz-appearance: none;
+}
+
+/* Bookmarks roots menu-items */
+#subscribeToPageMenuitem:not([disabled]),
+#subscribeToPageMenupopup {
+ list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
+}
+
+#bookmarksToolbarFolderMenu,
+#BMB_bookmarksToolbar,
+#panelMenu_bookmarksToolbar {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+#menu_unsortedBookmarks,
+#BMB_unsortedBookmarks,
+#panelMenu_unsortedBookmarks {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* Status panel */
+
+.statuspanel-label {
+ margin: 0;
+ padding: 2px 4px;
+ background: -moz-dialog;
+ border: 1px none ThreeDShadow;
+ border-top-style: solid;
+ color: -moz-dialogText;
+ text-shadow: none;
+}
+
+@media (-moz-windows-default-theme) {
+ .statuspanel-label {
+ background: linear-gradient(#fff, #ddd);
+ border-color: #ccc;
+ color: #333;
+ }
+}
+
+.statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
+.statuspanel-label:-moz-locale-dir(rtl)[mirror] {
+ border-right-style: solid;
+ /* disabled for triggering grayscale AA (bug 659213)
+ border-top-right-radius: .3em;
+ */
+ margin-right: 1em;
+}
+
+.statuspanel-label:-moz-locale-dir(rtl):not([mirror]),
+.statuspanel-label:-moz-locale-dir(ltr)[mirror] {
+ border-left-style: solid;
+ /* disabled for triggering grayscale AA (bug 659213)
+ border-top-left-radius: .3em;
+ */
+ margin-left: 1em;
+}
+
+%include ../shared/fullscreen/warning.inc.css
+%include ../shared/ctrlTab.inc.css
+%include ../../../devtools/client/themes/responsivedesign.inc.css
+%include ../../../devtools/client/themes/commandline.inc.css
+%include ../shared/plugin-doorhanger.inc.css
+
+notification.pluginVulnerable > .notification-inner > .messageCloseButton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ notification.pluginVulnerable > .notification-inner > .messageCloseButton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+}
+
+
+%include downloads/indicator.css
+
+/* Error counter */
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ color: #FDF3DE;
+ min-width: 16px;
+ text-shadow: none;
+ background-image: linear-gradient(#B4211B, #8A1915);
+ border-radius: 1px;
+ margin-inline-end: 5px;
+}
+
+/* Customization mode */
+
+%include ../shared/customizableui/customizeMode.inc.css
+
+/**
+ * This next rule is a hack to disable subpixel anti-aliasing on all
+ * labels during the customize mode transition. Subpixel anti-aliasing
+ * on Windows with Direct2D layers acceleration is particularly slow to
+ * paint, so this hack is how we sidestep that performance bottleneck.
+ */
+#main-window:-moz-any([customize-entering],[customize-exiting]) label {
+ transform: perspective(0.01px);
+}
+
+#main-window[customize-entered] > #tab-view-deck {
+ background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png");
+ background-attachment: fixed;
+}
+
+#main-window[customization-lwtheme] > #tab-view-deck:-moz-lwtheme {
+ background-repeat: no-repeat;
+ background-position: right top;
+ background-attachment: fixed;
+ /* The image will get set from CustomizeMode.jsm */
+ background-image: none;
+ background-color: transparent;
+}
+
+#main-window[customization-lwtheme]:-moz-lwtheme {
+ background-image: url("chrome://browser/skin/customizableui/customizeMode-gridTexture.png");
+ background-repeat: repeat;
+ background-attachment: fixed;
+ background-position: left top;
+}
+
+#main-window[customize-entered] #browser-bottombox,
+#main-window[customize-entered] #customization-container {
+ border-left: 1px solid @toolbarShadowColor@;
+ border-right: 1px solid @toolbarShadowColor@;
+ background-clip: padding-box;
+}
+
+#main-window[customize-entered] #browser-bottombox {
+ border-bottom: 1px solid @toolbarShadowColor@;
+}
+
+#customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="left"] {
+ margin-right: -2px;
+}
+
+#customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="right"] {
+ margin-left: -2px;
+}
+
+/* End customization mode */
+
+/* Private browsing indicators */
+
+/**
+ * Currently, we have two places where we put private browsing indicators on
+ * Windows. When tabsintitlebar is enabled, we draw the indicator in the titlebar.
+ * When tabsintitlebar is disabled, we draw the indicator at the end of the
+ * tabstrip. The titlebar indicator is the fiddliest of the bunch, since we
+ * want the bottom border of the image to line up with the bottom of the window
+ * caption buttons. That's why there's so much special-casing going on in here.
+ */
+.private-browsing-indicator {
+ display: none;
+ pointer-events: none;
+}
+
+#private-browsing-indicator-titlebar {
+ display: block;
+ position: absolute;
+}
+
+#main-window[privatebrowsingmode=temporary][tabsintitlebar] #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ display: block;
+}
+
+#main-window[privatebrowsingmode=temporary]:-moz-any([inFullscreen],:not([tabsintitlebar])) #TabsToolbar > .private-browsing-indicator {
+ display: -moz-box;
+}
+
+#TabsToolbar > .private-browsing-indicator {
+ background: url("chrome://browser/skin/privatebrowsing-mask-tabstrip.png") no-repeat center -3px;
+ margin-inline-start: 4px;
+ width: 48px;
+}
+
+/* Bug 1008183: We're intentionally using the titlebar asset here for fullscreen
+ * mode, since the tabstrip "mimics" the titlebar in that case with its own
+ * min/max/close window buttons.
+ */
+#private-browsing-indicator-titlebar > .private-browsing-indicator,
+#main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
+ background: url("chrome://browser/skin/privatebrowsing-mask-titlebar.png") no-repeat center 0px;
+ margin-inline-end: 4px;
+ width: 40px;
+ height: 20px;
+ position: relative;
+}
+
+@media (-moz-os-version: windows-xp) {
+ @media not all and (-moz-windows-classic) {
+ #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7-tall.png");
+ height: 28px;
+ }
+
+ #main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ top: -5px;
+ }
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ top: -1px;
+ }
+ }
+}
+
+@media (-moz-windows-classic) {
+ /**
+ * We have to use top instead of background-position in this case, otherwise
+ * the bottom of the indicator would get cut off by the bounds of the
+ * private-browsing-indicator element.
+ */
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ top: 4px;
+ }
+}
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ @media (-moz-windows-glass) {
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ top: 1px;
+ }
+ #main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ top: -1px;
+ }
+ }
+
+ /**
+ * This next block targets Aero Basic, which has different positioning for the
+ * window caption buttons, and therefore needs to be special-cased.
+ */
+ @media (-moz-windows-default-theme) {
+ @media not all and (-moz-windows-compositor) {
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7-tall.png");
+ height: 28px;
+ }
+ }
+ }
+}
+
+/* End private browsing indicators */
+
+%include ../shared/UITour.inc.css
+
+#UITourTooltipButtons {
+ /**
+ * Override the --arrowpanel-padding so the background extends
+ * to the sides and bottom of the panel.
+ */
+ margin-left: -10px;
+ margin-right: -10px;
+ margin-bottom: -10px;
+}
+
+%include ../shared/contextmenu.inc.css
+
+#context-navigation {
+ background-color: menu;
+ padding-bottom: 4px;
+}
+
+#context-sep-navigation {
+ margin-inline-start: -28px;
+ margin-top: -4px;
+}
+
+
+@media not all and (-moz-os-version: windows-xp) {
+%include browser-aero.css
+}
+
+.browser-extension-panel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+ overflow: hidden;
+}
+
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ .cui-widget-panelview[id^=PanelUI-webext-] {
+ border-radius: 4px;
+ }
+}
+
+.webextension-popup-browser {
+ border-radius: inherit;
+}
+
+.contentSelectDropdown-ingroup > .menu-iconic-text {
+ padding-inline-start: 20px;
+}
+
+#ContentSelectDropdown > menupopup {
+ background-color: -moz-field;
+ -moz-border-top-colors: GrayText;
+ -moz-border-right-colors: GrayText;
+ -moz-border-bottom-colors: GrayText;
+ -moz-border-left-colors: GrayText;
+}
+
+#ContentSelectDropdown > menupopup > menucaption,
+#ContentSelectDropdown > menupopup > menuitem {
+ padding: 0 6px;
+ border-width: 0;
+ font: -moz-list;
+}
+
+#ContentSelectDropdown > menupopup > menucaption > .menu-iconic-text,
+#ContentSelectDropdown > menupopup > menuitem > .menu-iconic-text {
+ /* Padding should follow the 4/12 ratio, where 12px is the default font-size
+ with 4px being the preferred padding size. */
+ padding-top: .3333em;
+ padding-bottom: .3333em;
+}
+
+#ContentSelectDropdown > menupopup > menucaption > .menu-iconic-text {
+ font-weight: bold;
+}
+
+#ContentSelectDropdown > menupopup > menuitem[_moz-menuactive="true"][disabled="true"] {
+ color: GrayText;
+ background-color: unset;
+}
+
+#ContentSelectDropdown > menupopup > menucaption {
+ background-color: buttonface;
+}
+
+#ContentSelectDropdown > menupopup > menucaption[disabled="true"] {
+ color: GrayText;
+}
+
+#ContentSelectDropdown > .isOpenedViaTouch > menucaption > .menu-iconic-text,
+#ContentSelectDropdown > .isOpenedViaTouch > menuitem > .menu-iconic-text {
+ /* Touch padding should follow the 11/12 ratio, where 12px is the default
+ font-size with 11px being the preferred padding size. */
+ padding-top: .9167em;
+ padding-bottom: .9167em;
+}
diff --git a/browser/themes/windows/caption-buttons.svg b/browser/themes/windows/caption-buttons.svg
new file mode 100644
index 000000000..3ba4f95a1
--- /dev/null
+++ b/browser/themes/windows/caption-buttons.svg
@@ -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/. -->
+<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <style>
+ g:not(:target) {
+ display: none;
+ }
+ use:target > g {
+ display: initial;
+ }
+
+ g {
+ stroke: ButtonText;
+ stroke-width: 0.9px;
+ fill: none;
+ }
+ g:not([id|="close"]) {
+ shape-rendering: crispEdges;
+ }
+
+ .highcontrast {
+ stroke-width: 1.9px;
+ }
+ .highcontrast-hover > g {
+ stroke: HighlightText;
+ }
+ .white > g {
+ stroke: #fff;
+ }
+ .themes {
+ stroke: #fff;
+ stroke-width: 1.9px;
+ }
+
+ .outer-stroke {
+ stroke: #000;
+ stroke-width: 3.6;
+ opacity: .75;
+ }
+ .restore-background-window {
+ stroke-width: .9;
+ }
+ </style>
+ <g id="close">
+ <path d="M1,1 l 10,10 M1,11 l 10,-10"/>
+ </g>
+ <g id="maximize">
+ <rect x="1.5" y="1.5" width="9" height="9"/>
+ </g>
+ <g id="minimize">
+ <line x1="1" y1="5.5" x2="11" y2="5.5"/>
+ </g>
+ <g id="restore">
+ <rect x="1.5" y="3.5" width="7" height="7"/>
+ <polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5"/>
+ </g>
+
+ <use id="close-white" class="white" xlink:href="#close"/>
+ <use id="maximize-white" class="white" xlink:href="#maximize"/>
+ <use id="minimize-white" class="white" xlink:href="#minimize"/>
+ <use id="restore-white" class="white" xlink:href="#restore"/>
+
+ <g id="close-highcontrast" class="highcontrast">
+ <path d="M1,1 l 10,10 M1,11 l 10,-10"/>
+ </g>
+ <g id="maximize-highcontrast" class="highcontrast">
+ <rect x="2" y="2" width="8" height="8"/>
+ </g>
+ <g id="minimize-highcontrast" class="highcontrast">
+ <line x1="1" y1="6" x2="11" y2="6"/>
+ </g>
+ <g id="restore-highcontrast" class="highcontrast">
+ <rect x="2" y="4" width="6" height="6"/>
+ <polyline points="3.5,1.5 10.5,1.5 10.5,8.5" class="restore-background-window"/>
+ </g>
+
+ <use id="close-highcontrast-hover" class="highcontrast-hover" xlink:href="#close-highcontrast"/>
+ <use id="maximize-highcontrast-hover" class="highcontrast-hover" xlink:href="#maximize-highcontrast"/>
+ <use id="minimize-highcontrast-hover" class="highcontrast-hover" xlink:href="#minimize-highcontrast"/>
+ <use id="restore-highcontrast-hover" class="highcontrast-hover" xlink:href="#restore-highcontrast"/>
+
+ <g id="close-themes" class="themes">
+ <path d="M1,1 l 10,10 M1,11 l 10,-10" class="outer-stroke" />
+ <path d="M1.75,1.75 l 8.5,8.5 M1.75,10.25 l 8.5,-8.5"/>
+ </g>
+ <g id="maximize-themes" class="themes">
+ <rect x="2" y="2" width="8" height="8" class="outer-stroke"/>
+ <rect x="2" y="2" width="8" height="8"/>
+ </g>
+ <g id="minimize-themes" class="themes">
+ <line x1="0" y1="6" x2="12" y2="6" class="outer-stroke"/>
+ <line x1="1" y1="6" x2="11" y2="6"/>
+ </g>
+ <g id="restore-themes" class="themes">
+ <path d="M2,4 l 6,0 l 0,6 l -6,0z M2.5,1.5 l 8,0 l 0,8" class="outer-stroke"/>
+ <rect x="2" y="4" width="6" height="6"/>
+ <polyline points="3.5,1.5 10.5,1.5 10.5,8.5" class="restore-background-window"/>
+ </g>
+</svg>
diff --git a/browser/themes/windows/click-to-play-warning-stripes.png b/browser/themes/windows/click-to-play-warning-stripes.png
new file mode 100644
index 000000000..cb4b71e50
--- /dev/null
+++ b/browser/themes/windows/click-to-play-warning-stripes.png
Binary files differ
diff --git a/browser/themes/windows/communicator/communicator.css b/browser/themes/windows/communicator/communicator.css
new file mode 100644
index 000000000..0b57574fd
--- /dev/null
+++ b/browser/themes/windows/communicator/communicator.css
@@ -0,0 +1,6 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://global/skin/");
+
diff --git a/browser/themes/windows/communicator/jar.mn b/browser/themes/windows/communicator/jar.mn
new file mode 100644
index 000000000..dfd20c523
--- /dev/null
+++ b/browser/themes/windows/communicator/jar.mn
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+% skin communicator classic/1.0 %skin/classic/communicator/
+ skin/classic/communicator/communicator.css
diff --git a/browser/themes/windows/communicator/moz.build b/browser/themes/windows/communicator/moz.build
new file mode 100644
index 000000000..eb4454d28
--- /dev/null
+++ b/browser/themes/windows/communicator/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/browser/themes/windows/content-contextmenu.svg b/browser/themes/windows/content-contextmenu.svg
new file mode 100644
index 000000000..b72b24708
--- /dev/null
+++ b/browser/themes/windows/content-contextmenu.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill: menutext;
+ }
+ use[id$="-active"] {
+ fill: -moz-menuhovertext;
+ }
+ use[id$="-disabled"] {
+ fill: graytext;
+ }
+ </style>
+ <defs>
+ <path id="back-shape" fill-rule="evenodd" d="M1.192,8.893L2.21,9.964c0.064,0.065,0.136,0.117,0.214,0.159 l5.199,5.301c0.607,0.63,1.465,0.764,1.915,0.297l1.02-1.082c0.449-0.467,0.32-1.357-0.288-1.99l-2.116-2.158h5.705 c0.671,0,1.215-0.544,1.215-1.215v-2.43c0-0.671-0.544-1.215-1.215-1.215H8.094l2.271-2.309c0.609-0.626,0.737-1.512,0.288-1.974 L9.635,0.278C9.184-0.188,8.327-0.055,7.718,0.575L2.479,5.901C2.38,5.946,2.289,6.008,2.21,6.089L1.192,7.171 c-0.21,0.219-0.293,0.53-0.26,0.864C0.899,8.367,0.981,8.676,1.192,8.893z"/>
+ <path id="forward-shape" fill-rule="evenodd" d="M14.808,7.107L13.79,6.036c-0.064-0.065-0.136-0.117-0.214-0.159 L8.377,0.576C7.77-0.054,6.912-0.189,6.461,0.278L5.441,1.36c-0.449,0.467-0.32,1.357,0.288,1.99l2.116,2.158H2.14 c-0.671,0-1.215,0.544-1.215,1.215v2.43c0,0.671,0.544,1.215,1.215,1.215h5.765l-2.271,2.309c-0.609,0.626-0.737,1.512-0.288,1.974 l1.019,1.072c0.451,0.465,1.308,0.332,1.917-0.297l5.238-5.326c0.1-0.045,0.191-0.107,0.269-0.188l1.019-1.082 c0.21-0.219,0.293-0.53,0.26-0.864C15.101,7.633,15.019,7.324,14.808,7.107z"/>
+ <path id="reload-shape" fill-rule="evenodd" d="M15.429,8h-8l3.207-3.207C9.889,4.265,8.986,3.947,8,3.947 c-2.554,0-4.625,2.071-4.625,4.625S5.446,13.196,8,13.196c1.638,0,3.069-0.857,3.891-2.141l2.576,1.104 C13.199,14.439,10.794,16,8,16c-4.103,0-7.429-3.326-7.429-7.429S3.897,1.143,8,1.143c1.762,0,3.366,0.624,4.631,1.654L15.429,0V8z"/>
+ <polygon id="stop-shape" points="16,2.748 13.338,0.079 8.038,5.391 2.661,0 0,2.669 5.377,8.059 0.157,13.292 2.819,15.961 8.039,10.728 13.298,16 15.959,13.331 10.701,8.06"/>
+ <path id="bookmark-shape" d="M8.008,3.632l0.986,2.012l0.452,0.922l1.014,0.169l2.326,0.389l-1.719,1.799l-0.676,0.708l0.145,0.967 L10.896,13l-1.959-1.039l-0.937-0.497l-0.937,0.497l-1.957,1.038L5.468,10.6l0.146-0.968L4.937,8.924L3.219,7.126l2.351-0.39 l1.023-0.17l0.45-0.934L8.008,3.632 M8,0C7.72,0,7.44,0.217,7.228,0.65L5.242,4.766L0.907,5.485c-0.958,0.159-1.195,0.861-0.53,1.56 l3.113,3.258l-0.69,4.583c-0.105,0.689,0.172,1.092,0.658,1.092c0.185,0,0.399-0.058,0.635-0.181l3.906-2.072l3.906,2.072 c0.236,0.123,0.45,0.181,0.635,0.181c0.486,0,0.762-0.403,0.659-1.092l-0.687-4.583l3.109-3.255c0.666-0.702,0.428-1.404-0.53-1.564 l-4.303-0.719L8.772,0.65C8.56,0.217,8.28,0,8,0L8,0z"/>
+ <path id="bookmarked-shape" d="M8,0C7.719,0,7.438,0.217,7.225,0.651L5.233,4.773l-4.35,0.72c-0.961,0.159-1.199,0.862-0.531,1.562 l3.124,3.262l-0.692,4.589C2.679,15.596,2.957,16,3.444,16c0.185,0,0.401-0.058,0.637-0.181L8,13.744l3.919,2.075 C12.156,15.942,12.372,16,12.557,16c0.487,0,0.764-0.404,0.661-1.094l-0.69-4.589l3.12-3.259c0.668-0.703,0.43-1.406-0.532-1.566 l-4.317-0.72L8.775,0.651C8.562,0.217,8.281,0,8,0L8,0z"/>
+ </defs>
+ <use id="back" xlink:href="#back-shape"/>
+ <use id="back-active" xlink:href="#back-shape"/>
+ <use id="back-disabled" xlink:href="#back-shape"/>
+ <use id="forward" xlink:href="#forward-shape"/>
+ <use id="forward-active" xlink:href="#forward-shape"/>
+ <use id="forward-disabled" xlink:href="#forward-shape"/>
+ <use id="reload" xlink:href="#reload-shape"/>
+ <use id="reload-active" xlink:href="#reload-shape"/>
+ <use id="reload-disabled" xlink:href="#reload-shape"/>
+ <use id="stop" xlink:href="#stop-shape"/>
+ <use id="stop-active" xlink:href="#stop-shape"/>
+ <use id="stop-disabled" xlink:href="#stop-shape"/>
+ <use id="bookmark" xlink:href="#bookmark-shape"/>
+ <use id="bookmark-active" xlink:href="#bookmark-shape"/>
+ <use id="bookmark-disabled" xlink:href="#bookmark-shape"/>
+ <use id="bookmarked" xlink:href="#bookmarked-shape"/>
+ <use id="bookmarked-active" xlink:href="#bookmarked-shape"/>
+ <use id="bookmarked-disabled" xlink:href="#bookmarked-shape"/>
+</svg>
diff --git a/browser/themes/windows/controlcenter/panel.css b/browser/themes/windows/controlcenter/panel.css
new file mode 100644
index 000000000..fab9aa3a4
--- /dev/null
+++ b/browser/themes/windows/controlcenter/panel.css
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/controlcenter/panel.inc.css
+
+.identity-popup-expander:-moz-focusring {
+ padding: 1px;
+}
+
+.identity-popup-expander:-moz-focusring > .button-box {
+ outline: 1px -moz-dialogtext dotted;
+}
diff --git a/browser/themes/windows/customizableui/background-noise-toolbar.png b/browser/themes/windows/customizableui/background-noise-toolbar.png
new file mode 100644
index 000000000..d09ba9daf
--- /dev/null
+++ b/browser/themes/windows/customizableui/background-noise-toolbar.png
Binary files differ
diff --git a/browser/themes/windows/customizableui/customize-titleBar-toggle.png b/browser/themes/windows/customizableui/customize-titleBar-toggle.png
new file mode 100644
index 000000000..3cf9f163d
--- /dev/null
+++ b/browser/themes/windows/customizableui/customize-titleBar-toggle.png
Binary files differ
diff --git a/browser/themes/windows/customizableui/customize-titleBar-toggle@2x.png b/browser/themes/windows/customizableui/customize-titleBar-toggle@2x.png
new file mode 100644
index 000000000..8e1beaec5
--- /dev/null
+++ b/browser/themes/windows/customizableui/customize-titleBar-toggle@2x.png
Binary files differ
diff --git a/browser/themes/windows/customizableui/customizeMode-gridTexture.png b/browser/themes/windows/customizableui/customizeMode-gridTexture.png
new file mode 100644
index 000000000..ca6c1196c
--- /dev/null
+++ b/browser/themes/windows/customizableui/customizeMode-gridTexture.png
Binary files differ
diff --git a/browser/themes/windows/customizableui/customizeMode-separatorHorizontal.png b/browser/themes/windows/customizableui/customizeMode-separatorHorizontal.png
new file mode 100644
index 000000000..5e17cb4db
--- /dev/null
+++ b/browser/themes/windows/customizableui/customizeMode-separatorHorizontal.png
Binary files differ
diff --git a/browser/themes/windows/customizableui/customizeMode-separatorVertical.png b/browser/themes/windows/customizableui/customizeMode-separatorVertical.png
new file mode 100644
index 000000000..dc4caee81
--- /dev/null
+++ b/browser/themes/windows/customizableui/customizeMode-separatorVertical.png
Binary files differ
diff --git a/browser/themes/windows/customizableui/menu-arrow.svg b/browser/themes/windows/customizableui/menu-arrow.svg
new file mode 100644
index 000000000..8437779fd
--- /dev/null
+++ b/browser/themes/windows/customizableui/menu-arrow.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16" width="16" viewBox="0 0 16 16">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill: MenuText;
+ }
+ use[id$="-disabled"] {
+ fill: GrayText;
+ }
+ use[id$="-hover"] {
+ fill: HighlightText;
+ }
+ </style>
+ <defs>
+ <path id="arrow-shape" d="m 6,4 0,8 5,-4 z"/>
+ </defs>
+ <use id="arrow" xlink:href="#arrow-shape"/>
+ <use id="arrow-disabled" xlink:href="#arrow-shape"/>
+ <use id="arrow-hover" xlink:href="#arrow-shape"/>
+</svg>
diff --git a/browser/themes/windows/customizableui/panelUI.css b/browser/themes/windows/customizableui/panelUI.css
new file mode 100644
index 000000000..92080d599
--- /dev/null
+++ b/browser/themes/windows/customizableui/panelUI.css
@@ -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 ../../shared/customizableui/panelUI.inc.css
+
+.panel-subviews {
+ background-color: var(--arrowpanel-background);
+}
+
+#PanelUI-contents #zoom-out-btn {
+ padding-left: 12px;
+ padding-right: 12px;
+}
+
+#PanelUI-contents #zoom-in-btn {
+ padding-left: 12px;
+ padding-right: 12px;
+}
+
+/* bookmark panel submenus */
+
+#BMB_bookmarksPopup menupopup[placespopup=true] {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ padding: 6px;
+}
+
+#BMB_bookmarksPopup menupopup[placespopup=true] > hbox {
+ /* emulating chrome://browser/content/places/menu.xml#places-popup-arrow but without the arrow */
+ box-shadow: 0 0 4px rgba(0,0,0,0.2);
+ background: var(--arrowpanel-background);
+ color: var(--arrowpanel-color);
+ border: 1px solid var(--arrowpanel-border-color);
+ border-radius: 3.5px;
+ margin-top: -4px;
+}
+
+#BMB_bookmarksPopup menupopup {
+ padding-top: 2px;
+}
+
+/* Add some space at the top because there are no headers: */
+#BMB_bookmarksPopup menupopup[placespopup=true] > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox {
+ padding-top: 4px;
+}
+
+/* bookmark panel separator */
+#BMB_bookmarksPopup menuseparator {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.subviewbutton > .menu-right,
+.subviewbutton > .menu-iconic-left {
+ padding-top: 1px;
+ margin-top: 1px;
+ margin-bottom: 2px;
+}
+
+/* Disabled empty item looks too small otherwise, because it has no icon. */
+menuitem.subviewbutton[disabled]:not(.menuitem-iconic),
+/* Same for checkbox menu items, whose icons lose size due to -moz-appearance: none: */
+menuitem[type="checkbox"].subviewbutton {
+ /* This is 16px for an icon + 3px for its margins + 1px for its padding +
+ * 2px for its border, see above */
+ min-height: 22px;
+}
+
+.subviewbutton > .toolbarbutton-text {
+ padding-top: 3px;
+ padding-bottom: 3px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-appearance: none;
+ border: 0;
+ margin-inline-start: 3px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ padding: 0 2px;
+ padding-inline-start: 0;
+ height: 18px;
+}
+
+.widget-overflow-list .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ padding: 0 6px;
+}
+
+.subviewbutton > .toolbarbutton-text {
+ padding-inline-start: 16px;
+}
+
+.subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .restoreallitem, .bookmark-item) > .toolbarbutton-text {
+ padding-inline-start: 0;
+}
+
+/* subviewbutton entries for social sidebars have images that come from external
+/* sources, and are not guaranteed to be the size we want, so force the size on
+/* those icons. */
+toolbarbutton.social-provider-menuitem > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .restoreallitem, .bookmark-item)[checked="true"] > .toolbarbutton-icon {
+ visibility: hidden;
+}
+
+menu.subviewbutton > .menu-right {
+ -moz-appearance: none;
+ list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg#arrow);
+ /* Reset the rect we inherit from the button: */
+ -moz-image-region: auto;
+}
+
+menu[disabled="true"].subviewbutton > .menu-right {
+ list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg#arrow-disabled);
+}
+
+@media not all and (-moz-windows-default-theme) {
+ menu[_moz-menuactive].subviewbutton > .menu-right {
+ list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg#arrow-hover);
+ }
+}
+
+menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+/* Win8 and beyond. */
+@media not all and (-moz-os-version: windows-xp) {
+ @media not all and (-moz-os-version: windows-vista) {
+ @media not all and (-moz-os-version: windows-win7) {
+ panelview .toolbarbutton-1,
+ .subviewbutton,
+ .widget-overflow-list .toolbarbutton-1,
+ .panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+ #BMB_bookmarksPopup menupopup[placespopup=true] > hbox,
+ #edit-controls@inAnyPanel@,
+ #zoom-controls@inAnyPanel@,
+ #edit-controls@inAnyPanel@ > toolbarbutton,
+ #zoom-controls@inAnyPanel@ > toolbarbutton {
+ border-radius: 0;
+ }
+ }
+ }
+}
+
diff --git a/browser/themes/windows/devedition.css b/browser/themes/windows/devedition.css
new file mode 100644
index 000000000..4c25f33a1
--- /dev/null
+++ b/browser/themes/windows/devedition.css
@@ -0,0 +1,321 @@
+% This Source Code Form is subject to the terms of the Mozilla Public
+% License, v. 2.0. If a copy of the MPL was not distributed with this
+% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+%include ../shared/devedition.inc.css
+
+:root {
+ --forwardbutton-width: 29px;
+}
+
+:root[devtoolstheme="dark"],
+:root[devtoolstheme="light"] {
+ /* Matches the #browser-border-start, #browser-border-end color */
+ --chrome-nav-bar-separator-color: rgba(10, 31, 51, 0.35);
+}
+
+/* The window background is white due to no accentcolor in the lightweight
+ theme. It can't be changed to transparent when there is no compositor
+ (Win XP or 7 in classic / basic theme), or else dragging and focus become
+ broken. So instead just show the normal titlebar in that case, and override
+ the window color as transparent when the compositor is available. */
+@media not all and (-moz-windows-compositor) {
+ #main-window[tabsintitlebar] #titlebar:-moz-lwtheme {
+ visibility: visible;
+ }
+
+ #main-window {
+ background: var(--chrome-background-color) !important;
+ }
+}
+
+@media (-moz-windows-compositor) {
+ #main-window {
+ background: transparent !important;
+ }
+}
+
+#TabsToolbar::after {
+ display: none;
+}
+
+#back-button > .toolbarbutton-icon,
+#forward-button > .toolbarbutton-icon {
+ background: var(--chrome-nav-buttons-background) !important;
+ border-radius: 0 !important;
+ height: auto !important;
+ padding: var(--toolbarbutton-vertical-inner-padding) 5px !important;
+ margin: 0 !important;
+ border: 1px solid var(--chrome-nav-bar-controls-border-color) !important;
+ box-shadow: none !important;
+}
+
+#back-button > .toolbarbutton-icon {
+ /* 18px icon + 2 * 5px padding + 2 * 1px border */
+ width: 30px !important;
+}
+
+#forward-button > .toolbarbutton-icon {
+ /* 18px icon + 2 * 5px padding + 1 * 1px border */
+ width: 29px !important;
+}
+
+/* the normal theme adds box-shadow: <stuff> !important when the back-button is [open]. Fix: */
+#back-button[open="true"] > .toolbarbutton-icon {
+ box-shadow: none !important;
+}
+
+#forward-button > .toolbarbutton-icon {
+ border-inline-start: none !important;
+}
+
+/* Override a box shadow for disabled back button */
+#main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
+ box-shadow: none !important;
+}
+
+/* Override !important properties for hovered back button */
+#main-window #back-button:hover:not([disabled="true"]) > .toolbarbutton-icon,
+#main-window #forward-button:hover:not([disabled="true"]) > .toolbarbutton-icon {
+ background: var(--chrome-nav-buttons-hover-background) !important;
+ box-shadow: none !important;
+}
+
+#back-button > .toolbarbutton-icon {
+ border-radius: 2px 0 0 2px !important;
+}
+
+#nav-bar .toolbarbutton-1:not([type=menu-button]),
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+#nav-bar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+.tabbrowser-tab {
+ background-color: var(--tab-background-color);
+}
+
+#toolbar-menubar {
+ text-shadow: none !important;
+}
+
+:root[devtoolstheme="dark"] .findbar-closebutton,
+:root[devtoolstheme="dark"] #sidebar-header > .close-icon,
+/* Tab styling - make sure to use an inverted icon for the selected tab
+ (brighttext only covers the unselected tabs) */
+.tab-close-button[selected=true] {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ :root[devtoolstheme="dark"] .findbar-closebutton,
+ :root[devtoolstheme="dark"] #sidebar-header > .close-icon,
+ .tab-close-button[selected=true] {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+}
+
+@media (-moz-os-version: windows-xp),
+ (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ :root {
+ --space-above-tabbar: 15px;
+ }
+
+ /* It'd be nice if there was an element in the scrollbox's inner content
+ that collapsed to the current width of the tabs. Since there isn't we
+ need to handle overflowing and non-overflowing tabs separately.
+
+ In the case of overflowing tabs, set a border-top on the entire container,
+ otherwise we need to set it on each element individually */
+ #main-window[sizemode=normal] .tabbrowser-tabs[overflow="true"] {
+ background-clip: padding-box;
+ border-top: 1px solid var(--chrome-nav-bar-separator-color);
+ border-inline-end: 1px solid var(--chrome-nav-bar-separator-color);
+ background-color: var(--tab-background-color); /* Make sure there is no transparent gap during tab close animation */
+ }
+
+ /* Add a border to the left of the first tab (or scroll arrow). Using .tabbrowser-tabs
+ instead of #TabsToolbar because it will work even in customize mode. */
+ #main-window[sizemode=normal] .tabbrowser-tabs {
+ background-clip: padding-box;
+ border-inline-start: 1px solid var(--chrome-nav-bar-separator-color);
+ border-inline-end: 1px solid transparent;
+ }
+
+ #main-window[sizemode=normal] .tabbrowser-tabs:not([overflow="true"]) .tabbrowser-tab,
+ #main-window[sizemode=normal] .tabbrowser-tabs:not([overflow="true"]) .tabbrowser-arrowscrollbox > .scrollbutton-down,
+ #main-window[sizemode=normal] .tabbrowser-tabs:not([overflow="true"]) .tabbrowser-arrowscrollbox > .scrollbutton-up,
+ #main-window[sizemode=normal] .tabbrowser-tabs:not([overflow="true"]) .tabs-newtab-button {
+ background-clip: padding-box;
+ border-top: 1px solid var(--chrome-nav-bar-separator-color);
+ }
+
+ /* Allow the border-top rule to take effect */
+ #main-window[sizemode=normal] .tabbrowser-tabs:not([overflow="true"]) .tabbrowser-tab {
+ -moz-border-top-colors: none;
+ }
+
+ #main-window[sizemode=normal] .tabbrowser-tabs:not([overflow="true"]) .closing-tabs-spacer {
+ background-clip: padding-box;
+ border-inline-start: 1px solid var(--chrome-nav-bar-separator-color);
+ }
+
+ .tabs-newtab-button {
+ background: var(--tab-background-color);
+ }
+
+ /* Use default window colors when in non-maximized mode */
+ #tabbrowser-tabs,
+ #TabsToolbar,
+ #browser-panel,
+ #titlebar-content {
+ background: transparent;
+ }
+
+ /* Ensure that the entire background is styled when maximized/fullscreen */
+ #main-window:not([sizemode="normal"]):not([customizing]) #browser-panel {
+ background: var(--chrome-background-color) !important;
+ }
+
+ /* The menu items need to be visible when the entire background is styled */
+ #main-window:not([sizemode="normal"]) #main-menubar {
+ color: var(--chrome-color);
+ background-color: transparent;
+ }
+
+ #main-window[sizemode="maximized"] #main-menubar > menu:not(:-moz-window-inactive) {
+ color: inherit;
+ }
+
+ /* Use proper menu text styling in Win7 classic mode (copied from browser.css) */
+ @media not all and (-moz-windows-compositor),
+ not all and (-moz-windows-default-theme) {
+ #main-window[tabsintitlebar]:not([inFullscreen]) #toolbar-menubar,
+ #main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar {
+ color: CaptionText;
+ }
+
+ #main-window[tabsintitlebar]:not([inFullscreen]) #toolbar-menubar:-moz-window-inactive,
+ #main-window[tabsintitlebar]:not([inFullscreen]) #TabsToolbar:-moz-window-inactive {
+ color: InactiveCaptionText;
+ }
+
+ #main-window[tabsintitlebar] #main-menubar > menu {
+ color: inherit;
+ }
+ }
+
+ /* Use less opacity than normal since this is very dark, and on top of the default toolbar color */
+ .tabbrowser-arrowscrollbox > .scrollbutton-up[disabled],
+ .tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] {
+ opacity: .6;
+ }
+
+ /* Override scrollbutton gradients in normal and hover state */
+ .tabbrowser-arrowscrollbox > .scrollbutton-down,
+ .tabbrowser-arrowscrollbox > .scrollbutton-up {
+ background-image: none !important;
+ transition: none; /* scrollbutton-down has an unwanted transition on background color */
+ }
+
+ /* Restore draggable space on the sides of tabs when maximized */
+ #main-window[sizemode="maximized"] .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
+ padding-left: 15px;
+ padding-right: 15px;
+ }
+
+ /* Override the padding that's intended to compensate for tabs that can overlap border-radius on nav-bar in default theme. */
+ #main-window[sizemode=normal]:not([customizing]) #TabsToolbar {
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+
+/* Restored windows get an artificial border on windows, because the lwtheme background
+ * overlaps the regular window border. That isn't the case for us, so we avoid painting
+ * over the native border with our custom borders: */
+#browser-panel {
+ /* These are !important to avoid specificity-wars with the selectors that add borders here. */
+ background-image: none !important;
+ border-top: none !important;
+}
+
+#navigator-toolbox {
+ /* The side borders on the toolbox also look out-of-place because we don't paint over
+ * the native background color at all, and these are !important for the same reason as above. */
+ border-left: none !important;
+ border-right: none !important;
+}
+
+/* Disable dragging like in the default theme: */
+#main-window[tabsintitlebar] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):-moz-lwtheme {
+ -moz-window-dragging: no-drag;
+}
+
+/* The sidebar header has no background now that the background of the #browser-panel
+ * has no image and is transparent. Fix: */
+.sidebar-header:-moz-lwtheme,
+#sidebar-header {
+ background-color: var(--chrome-background-color);
+ color: var(--chrome-color);
+}
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ /* And then we add them back on toolbars so that they don't look borderless: */
+ #main-window:not([customizing])[sizemode=normal] #navigator-toolbox::after,
+ #main-window:not([customizing])[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ border-left: 1px solid hsla(209,67%,12%,0.35);
+ border-right: 1px solid hsla(209,67%,12%,0.35);
+ }
+}
+
+@media (-moz-os-version: windows-win10) {
+ /* Always keep draggable space on the sides of tabs since there is no top margin on Win10 */
+ #main-window .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox {
+ padding-left: 15px;
+ padding-right: 15px;
+ }
+
+ /* Force white caption buttons for the dark theme on Windows 10 */
+ :root[devtoolstheme="dark"] #titlebar-min {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-white);
+ }
+ :root[devtoolstheme="dark"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-white);
+ }
+ #main-window[devtoolstheme="dark"][sizemode="maximized"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-white);
+ }
+ :root[devtoolstheme="dark"] #titlebar-close {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
+ }
+
+ /* ... and normal ones for the light theme on Windows 10 */
+ :root[devtoolstheme="light"] #titlebar-min {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
+ }
+ :root[devtoolstheme="light"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize);
+ }
+ #main-window[devtoolstheme="light"][sizemode="maximized"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
+ }
+ :root[devtoolstheme="light"] #titlebar-close {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
+ }
+
+ :root[devtoolstheme="light"] #titlebar-close:hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-white);
+ }
+}
+
+.ac-type-icon {
+ /* Left-align the type icon in awesomebar popup results with the icon in the
+ urlbar. */
+ margin-inline-start: 13px;
+}
diff --git a/browser/themes/windows/downloads/allDownloadsViewOverlay.css b/browser/themes/windows/downloads/allDownloadsViewOverlay.css
new file mode 100644
index 000000000..e288f1e90
--- /dev/null
+++ b/browser/themes/windows/downloads/allDownloadsViewOverlay.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/. */
+
+%include ../../shared/downloads/allDownloadsViewOverlay.inc.css
+
+/*** List items ***/
+
+:root {
+ --downloads-item-height: 6em;
+}
+
+@media (-moz-windows-default-theme) {
+ .downloadProgress > .progress-bar {
+ background-color: #3c9af8;
+ }
+}
+
+/*** Highlighted list items ***/
+
+@media not all and (-moz-os-version: windows-xp) {
+ @media (-moz-windows-default-theme) {
+ /*
+ -moz-appearance: menuitem is almost right, but the hover effect is not
+ transparent and is lighter than desired.
+
+ Copied from the autocomplete richlistbox styling in
+ toolkit/themes/windows/global/autocomplete.css
+
+ This styling should be kept in sync with the style from the above file.
+ */
+ @itemFocused@ {
+ color: inherit;
+ background-color: transparent;
+ /* four gradients for the bevel highlights on each edge, one for blue background */
+ background-image:
+ linear-gradient(to bottom, rgba(255,255,255,0.9) 3px, transparent 3px),
+ linear-gradient(to right, rgba(255,255,255,0.5) 3px, transparent 3px),
+ linear-gradient(to left, rgba(255,255,255,0.5) 3px, transparent 3px),
+ linear-gradient(to top, rgba(255,255,255,0.4) 3px, transparent 3px),
+ linear-gradient(to bottom, rgba(163,196,247,0.3), rgba(122,180,246,0.3));
+ background-clip: content-box;
+ border-radius: 6px;
+ outline: 1px solid rgb(124,163,206);
+ -moz-outline-radius: 3px;
+ outline-offset: -2px;
+ }
+ }
+}
diff --git a/browser/themes/windows/downloads/download-glow-XPVista7.png b/browser/themes/windows/downloads/download-glow-XPVista7.png
new file mode 100644
index 000000000..e7415e83d
--- /dev/null
+++ b/browser/themes/windows/downloads/download-glow-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/downloads/download-glow-menuPanel-XPVista7.png b/browser/themes/windows/downloads/download-glow-menuPanel-XPVista7.png
new file mode 100644
index 000000000..7ff7e6a03
--- /dev/null
+++ b/browser/themes/windows/downloads/download-glow-menuPanel-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/downloads/download-glow-menuPanel.png b/browser/themes/windows/downloads/download-glow-menuPanel.png
new file mode 100644
index 000000000..d05b07b57
--- /dev/null
+++ b/browser/themes/windows/downloads/download-glow-menuPanel.png
Binary files differ
diff --git a/browser/themes/windows/downloads/download-glow.png b/browser/themes/windows/downloads/download-glow.png
new file mode 100644
index 000000000..1239ada52
--- /dev/null
+++ b/browser/themes/windows/downloads/download-glow.png
Binary files differ
diff --git a/browser/themes/windows/downloads/download-notification-finish.png b/browser/themes/windows/downloads/download-notification-finish.png
new file mode 100644
index 000000000..2ef1420ad
--- /dev/null
+++ b/browser/themes/windows/downloads/download-notification-finish.png
Binary files differ
diff --git a/browser/themes/windows/downloads/download-notification-start.png b/browser/themes/windows/downloads/download-notification-start.png
new file mode 100644
index 000000000..a2fca743b
--- /dev/null
+++ b/browser/themes/windows/downloads/download-notification-start.png
Binary files differ
diff --git a/browser/themes/windows/downloads/downloads.css b/browser/themes/windows/downloads/downloads.css
new file mode 100644
index 000000000..4b60da149
--- /dev/null
+++ b/browser/themes/windows/downloads/downloads.css
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/downloads/downloads.inc.css
+
+/*** Panel and outer controls ***/
+
+@keyfocus@ #downloadsSummary:focus,
+@keyfocus@ .downloadsPanelFooterButton:focus {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
+
+@keyfocus@ #downloadsSummary:focus {
+ outline-offset: -5px;
+}
+
+/*** List items and similar elements in the summary ***/
+
+:root {
+ --downloads-item-height: 5.5em;
+ --downloads-item-font-size-factor: 0.9;
+ --downloads-item-details-opacity: 0.6;
+}
+
+.downloadButton > .button-box {
+ border: 1px solid transparent;
+}
+
+@keyfocus@ .downloadButton:focus > .button-box {
+ border: 1px dotted ThreeDDarkShadow;
+}
+
+@media (-moz-windows-default-theme) {
+ @item@[verdict="Malware"] {
+ color: #aa1b08;
+ }
+
+ /* Use unified color for the progressbar on default theme */
+ .downloadProgress > .progress-bar {
+ background-color: #3c9af8;
+ }
+
+ .downloadProgress[paused="true"] > .progress-bar {
+ background-color: #a6a6a6;
+ }
+
+}
+
+/*** Highlighted list items ***/
+
+@keyfocus@ @itemFocused@ {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
diff --git a/browser/themes/windows/downloads/indicator.css b/browser/themes/windows/downloads/indicator.css
new file mode 100644
index 000000000..627265088
--- /dev/null
+++ b/browser/themes/windows/downloads/indicator.css
@@ -0,0 +1,225 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*** Status and progress indicator ***/
+
+#downloads-animation-container {
+ min-height: 1px;
+ min-width: 1px;
+ height: 1px;
+ margin-bottom: -1px;
+ /* Makes the outermost animation container element positioned, so that its
+ contents are rendered over the main browser window in the Z order.
+ This is required by the animated event notification. */
+ position: relative;
+ /* The selected tab may overlap #downloads-indicator-notification */
+ z-index: 5;
+}
+
+/*** Main indicator icon ***/
+
+@media not all and (min-resolution: 1.1dppx) {
+ #downloads-button {
+ --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 198, 18, 180);
+ --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
+ --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
+ --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
+ }
+}
+
+@media (min-resolution: 1.1dppx) {
+ #downloads-button {
+ --downloads-indicator-icon: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
+ --downloads-indicator-icon-attention: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 36, 396, 72, 360);
+ --downloads-indicator-icon-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 0, 396, 36, 360);
+ --downloads-indicator-icon-attention-inverted: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 36, 396, 72, 360);
+ }
+}
+
+#downloads-indicator-icon {
+ background: var(--downloads-indicator-icon) center no-repeat;
+ width: 18px;
+ height: 18px;
+ background-size: 18px;
+}
+
+toolbar[brighttext] #downloads-button:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: var(--downloads-indicator-icon-inverted);
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ display: -moz-box;
+ height: 8px;
+ width: 8px;
+ min-width: 0;
+ border-radius: 50%;
+ /* "!important" is necessary to override the rule in toolbarbutton.css */
+ margin-top: -1px !important;
+ margin-right: -2px !important;
+}
+
+#downloads-button[cui-areatype="toolbar"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ height: 7px;
+ width: 7px;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #D90000;
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+ background: #FFBF00;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+ filter: none;
+}
+
+#downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: var(--downloads-indicator-icon-attention);
+}
+
+toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: var(--downloads-indicator-icon-attention-inverted);
+}
+
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
+ list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
+ -moz-image-region: auto;
+}
+
+/* In the next few rules, we use :not([counter]) as a shortcut that is
+ equivalent to -moz-any([progress], [paused]). */
+
+#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: var(--downloads-indicator-icon) center no-repeat;
+ background-size: 12px;
+}
+
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
+ background-image: var(--downloads-indicator-icon-inverted);
+}
+
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: var(--downloads-indicator-icon-attention);
+}
+
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: var(--downloads-indicator-icon-attention-inverted);
+}
+
+/*** Download notifications ***/
+
+#downloads-indicator-notification {
+ opacity: 0;
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+}
+
+@keyframes downloadsIndicatorNotificationStartRight {
+ from { opacity: 0; transform: translate(-128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+@keyframes downloadsIndicatorNotificationStartLeft {
+ from { opacity: 0; transform: translate(128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+#downloads-notification-anchor[notification="start"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
+ animation-name: downloadsIndicatorNotificationStartRight;
+ animation-duration: 1s;
+}
+
+#downloads-notification-anchor[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-notification {
+ animation-name: downloadsIndicatorNotificationStartLeft;
+}
+
+@keyframes downloadsIndicatorNotificationFinish {
+ from { opacity: 0; transform: scale(1); }
+ 20% { opacity: .65; animation-timing-function: ease-in; }
+ to { opacity: 0; transform: scale(8); }
+}
+
+#downloads-notification-anchor[notification="finish"] > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
+ animation-name: downloadsIndicatorNotificationFinish;
+ animation-duration: 1s;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+ height: 9px;
+ margin: -3px 0px 0px 0px;
+ color: hsl(0,0%,30%);
+ text-shadow: hsla(0,0%,100%,.5) 0 1px;
+ font-size: 9px;
+ line-height: 9px;
+ text-align: center;
+}
+
+@media not all and (-moz-os-version: windows-xp) {
+ #downloads-indicator-counter {
+ /* Bug 812345 added this... */
+ margin-bottom: -1px;
+ }
+}
+
+toolbar[brighttext] #downloads-indicator-counter {
+ color: white;
+ text-shadow: 0 0 1px rgba(0,0,0,.7),
+ 0 1px 1.5px rgba(0,0,0,.5);
+}
+
+#downloads-indicator-progress {
+ width: 16px;
+ height: 5px;
+ min-width: 0;
+ min-height: 0;
+ margin-top: 1px;
+ margin-bottom: 2px;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
+ background-clip: padding-box, border-box;
+ background-color: rgb(90, 201, 66);
+ background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
+ border: 1px solid;
+ border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
+ border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ background-image: linear-gradient(#505050, #575757);
+ border: 1px solid;
+ border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
+ border-inline-start: none;
+ border-radius: 0 2px 2px 0;
+}
+
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+ background-color: rgb(220, 230, 81);
+}
+
+#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+ background-image: linear-gradient(#4b5000, #515700);
+}
diff --git a/browser/themes/windows/feeds/feedIcon-XP.png b/browser/themes/windows/feeds/feedIcon-XP.png
new file mode 100644
index 000000000..d0cafb1d4
--- /dev/null
+++ b/browser/themes/windows/feeds/feedIcon-XP.png
Binary files differ
diff --git a/browser/themes/windows/feeds/feedIcon.png b/browser/themes/windows/feeds/feedIcon.png
new file mode 100644
index 000000000..59b22d8a2
--- /dev/null
+++ b/browser/themes/windows/feeds/feedIcon.png
Binary files differ
diff --git a/browser/themes/windows/feeds/feedIcon16-XP.png b/browser/themes/windows/feeds/feedIcon16-XP.png
new file mode 100644
index 000000000..dd7821f8d
--- /dev/null
+++ b/browser/themes/windows/feeds/feedIcon16-XP.png
Binary files differ
diff --git a/browser/themes/windows/feeds/feedIcon16.png b/browser/themes/windows/feeds/feedIcon16.png
new file mode 100644
index 000000000..071979903
--- /dev/null
+++ b/browser/themes/windows/feeds/feedIcon16.png
Binary files differ
diff --git a/browser/themes/windows/feeds/subscribe-ui.css b/browser/themes/windows/feeds/subscribe-ui.css
new file mode 100644
index 000000000..f1650036e
--- /dev/null
+++ b/browser/themes/windows/feeds/subscribe-ui.css
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.alwaysUse {
+ padding: 5px;
+}
diff --git a/browser/themes/windows/feeds/subscribe.css b/browser/themes/windows/feeds/subscribe.css
new file mode 100644
index 000000000..81168a144
--- /dev/null
+++ b/browser/themes/windows/feeds/subscribe.css
@@ -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/. */
+
+html {
+ background: -moz-Dialog;
+ font: 3mm tahoma,arial,helvetica,sans-serif;
+}
+
+#subscribeUsingDescription,
+#subscribeButton {
+ display: block;
+}
+
+#subscribeUsingDescription {
+ margin-bottom: 0.5em;
+}
+
+#subscribeButton {
+ margin-top: 0.5em;
+ margin-inline-start: auto;
+}
+
+#feedBody {
+ border: 1px solid THreeDShadow;
+ padding: 3em;
+ padding-inline-start: 30px;
+ margin: 2em auto;
+ background: -moz-Field;
+}
+
+#feedHeaderContainer {
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ margin: -4em auto 0 auto;
+ background-color: InfoBackground;
+ display: flex;
+}
+
+#feedHeaderContainerSpacer {
+ flex-grow: 1;
+}
+
+#feedHeader {
+ margin-top: 4.9em;
+ margin-bottom: 1em;
+ margin-inline-start: 1.4em;
+ margin-inline-end: 1em;
+ padding-inline-start: 2.9em;
+ font-size: 110%;
+ color: InfoText;
+}
+
+#feedIntroText {
+ display: none;
+}
+
+.feedBackground {
+ background: url("chrome://browser/skin/feeds/feedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.videoPodcastBackground {
+ background: url("chrome://browser/skin/feeds/videoFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.audioPodcastBackground {
+ background: url("chrome://browser/skin/feeds/audioFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+#feedHeader[dir="rtl"] {
+ background-position: 100% 10%;
+}
+
+#feedHeader[firstrun="true"] #feedIntroText {
+ padding-top: 0.1em;
+ padding-inline-start: 0.6em;
+ display: block;
+}
+
+#feedHeader[firstrun="true"] > #feedSubscribeLine {
+ padding-inline-start: 1.8em;
+}
+
+#feedSubscribeLine {
+ padding-top: 0.2em;
+ padding-inline-start: 0.5em;
+}
+
+/* Don't print subscription UI */
+@media print {
+ #feedHeaderContainer {
+ display: none;
+ }
+}
+
+body {
+ margin: 0;
+ padding: 0 3em;
+ color: -moz-fieldText;
+ font: message-box;
+}
+
+h1 {
+ font-size: 160%;
+ border-bottom: 2px solid ThreeDLightShadow;
+ margin: 0 0 .2em 0;
+}
+
+h2 {
+ color: GrayText;
+ font-size: 110%;
+ font-weight: normal;
+ margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+ float: right;
+ margin-inline-start: .6em;
+ margin-inline-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+a[href] img {
+ border: none;
+}
+
+#feedTitleContainer {
+ margin-inline-start: 0;
+ margin-inline-end: .6em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#feedTitleImage {
+ margin-inline-start: .6em;
+ margin-inline-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-width: 300px;
+ max-height: 150px;
+}
+
+.feedEntryContent {
+ font-size: 110%;
+}
+
+.link {
+ color: #0000FF;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.link:hover:active {
+ color: #FF0000;
+}
+
+.lastUpdated {
+ font-size: 85%;
+ font-weight: normal;
+}
+
+.type-icon {
+ vertical-align: bottom;
+ height: 16px;
+ width: 16px;
+}
+
+.enclosures {
+ border: 1px solid THreeDShadow;
+ padding: 1em;
+ margin: 1em auto;
+ background: -moz-Dialog;
+}
+
+.enclosure {
+ vertical-align: middle;
+ margin-left: 2px;
+}
+
+.handlersMenuList > .menulist-label-box > .menulist-icon {
+ max-width: 16px;
+ max-height: 16px;
+}
diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn
new file mode 100644
index 000000000..89c589aba
--- /dev/null
+++ b/browser/themes/windows/jar.mn
@@ -0,0 +1,268 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+browser.jar:
+% skin browser classic/1.0 %skin/classic/browser/
+#include ../shared/jar.inc.mn
+ skin/classic/browser/sanitizeDialog.css
+ skin/classic/browser/aboutSessionRestore-window-icon.png
+ skin/classic/browser/aboutSyncTabs.css
+* skin/classic/browser/syncedtabs/sidebar.css (syncedtabs/sidebar.css)
+ skin/classic/browser/actionicon-tab.png
+ skin/classic/browser/actionicon-tab@2x.png
+ skin/classic/browser/actionicon-tab-XPVista7.png
+* skin/classic/browser/browser.css
+* skin/classic/browser/devedition.css
+* skin/classic/browser/browser-lightweightTheme.css
+ skin/classic/browser/caption-buttons.svg
+ skin/classic/browser/click-to-play-warning-stripes.png
+ skin/classic/browser/Info.png
+ skin/classic/browser/Info-XP.png
+ skin/classic/browser/keyhole-forward-mask.svg
+ skin/classic/browser/livemark-folder.png
+ skin/classic/browser/livemark-folder-XP.png
+ skin/classic/browser/menu-back.png
+ skin/classic/browser/menu-back-XP.png
+ skin/classic/browser/menu-forward.png
+ skin/classic/browser/menu-forward-XP.png
+ skin/classic/browser/menuPanel-customize.png
+ skin/classic/browser/menuPanel-customize@2x.png
+ skin/classic/browser/menuPanel-exit.png
+ skin/classic/browser/menuPanel-exit@2x.png
+ skin/classic/browser/menuPanel-help.png
+ skin/classic/browser/menuPanel-help@2x.png
+ skin/classic/browser/monitor.png
+ skin/classic/browser/monitor_16-10.png
+ skin/classic/browser/pageInfo.css
+ skin/classic/browser/pageInfo.png
+ skin/classic/browser/pageInfo-XP.png
+ skin/classic/browser/privatebrowsing-mask-tabstrip.png
+ skin/classic/browser/privatebrowsing-mask-tabstrip-XPVista7.png
+ skin/classic/browser/privatebrowsing-mask-titlebar.png
+ skin/classic/browser/privatebrowsing-mask-titlebar-XPVista7.png
+ skin/classic/browser/privatebrowsing-mask-titlebar-XPVista7-tall.png
+ skin/classic/browser/reload-stop-go.png
+ skin/classic/browser/reload-stop-go@2x.png
+ skin/classic/browser/reload-stop-go-XPVista7.png
+ skin/classic/browser/reload-stop-go-XPVista7@2x.png
+ skin/classic/browser/searchbar.css
+ skin/classic/browser/setDesktopBackground.css
+ skin/classic/browser/slowStartup-16.png
+ skin/classic/browser/Toolbar.png
+ skin/classic/browser/Toolbar@2x.png
+ skin/classic/browser/Toolbar-aero.png
+ skin/classic/browser/Toolbar-aero@2x.png
+ skin/classic/browser/Toolbar-inverted.png
+ skin/classic/browser/Toolbar-inverted@2x.png
+ skin/classic/browser/Toolbar-lunaSilver.png
+ skin/classic/browser/Toolbar-win8.png
+ skin/classic/browser/Toolbar-win8@2x.png
+ skin/classic/browser/Toolbar-XP.png
+ skin/classic/browser/toolbarbutton-dropdown-arrow-XPVista7.png
+ skin/classic/browser/toolbarbutton-dropdown-arrow-inverted.png
+ skin/classic/browser/urlbar-popup-blocked.png
+ skin/classic/browser/urlbar-history-dropmarker.png
+ skin/classic/browser/urlbar-history-dropmarker@2x.png
+ skin/classic/browser/urlbar-history-dropmarker-XPVista7.png
+ skin/classic/browser/urlbar-history-dropmarker-XPVista7@2x.png
+ skin/classic/browser/webRTC-indicator.css
+* skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css)
+ skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png)
+ skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png)
+ skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png (customizableui/customize-titleBar-toggle@2x.png)
+ skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png)
+ skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png)
+ skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png)
+ skin/classic/browser/customizableui/menu-arrow.svg (customizableui/menu-arrow.svg)
+* skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css)
+* skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
+ skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
+ skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png (downloads/download-glow-menuPanel-XPVista7.png)
+ skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
+ skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
+* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
+ skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/feedIcon-XP.png (feeds/feedIcon-XP.png)
+ skin/classic/browser/feeds/feedIcon16-XP.png (feeds/feedIcon16-XP.png)
+ skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
+ skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/places/autocomplete-star.png (places/autocomplete-star.png)
+ skin/classic/browser/places/autocomplete-star@2x.png (places/autocomplete-star@2x.png)
+ skin/classic/browser/places/autocomplete-star-XPVista7.png (places/autocomplete-star-XPVista7.png)
+ skin/classic/browser/places/places.css (places/places.css)
+* skin/classic/browser/places/organizer.css (places/organizer.css)
+ skin/classic/browser/places/query.png (places/query.png)
+ skin/classic/browser/places/query-XP.png (places/query-XP.png)
+ skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
+ skin/classic/browser/places/bookmarksMenu-XP.png (places/bookmarksMenu-XP.png)
+ skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
+ skin/classic/browser/places/bookmarksToolbar-XP.png (places/bookmarksToolbar-XP.png)
+ skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
+ skin/classic/browser/places/bookmarksToolbar-menuPanel-XP.png (places/bookmarksToolbar-menuPanel-XP.png)
+ skin/classic/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png)
+ skin/classic/browser/places/calendar.png (places/calendar.png)
+ skin/classic/browser/places/calendar-XP.png (places/calendar-XP.png)
+ skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
+ skin/classic/browser/places/toolbarDropMarker-XP.png (places/toolbarDropMarker-XP.png)
+ skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
+ skin/classic/browser/places/libraryToolbar.png (places/libraryToolbar.png)
+ skin/classic/browser/places/libraryToolbar-XP.png (places/libraryToolbar-XP.png)
+ skin/classic/browser/places/starred48.png (places/starred48.png)
+ skin/classic/browser/places/starred48-XP.png (places/starred48-XP.png)
+ skin/classic/browser/places/unstarred48.png (places/unstarred48.png)
+ skin/classic/browser/places/tag.png (places/tag.png)
+ skin/classic/browser/places/tag-XP.png (places/tag-XP.png)
+ skin/classic/browser/places/history.png (places/history.png)
+ skin/classic/browser/places/history-XP.png (places/history-XP.png)
+ skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)
+ skin/classic/browser/places/allBookmarks-XP.png (places/allBookmarks-XP.png)
+ skin/classic/browser/places/unsortedBookmarks.png (places/unsortedBookmarks.png)
+ skin/classic/browser/places/unsortedBookmarks-XP.png (places/unsortedBookmarks-XP.png)
+ skin/classic/browser/places/downloads.png (places/downloads.png)
+ skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
+ skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
+ skin/classic/browser/preferences/alwaysAsk-XP.png (preferences/alwaysAsk-XP.png)
+ skin/classic/browser/preferences/application.png (preferences/application.png)
+ skin/classic/browser/preferences/application-XP.png (preferences/application-XP.png)
+ skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
+ skin/classic/browser/preferences/saveFile-XP.png (preferences/saveFile-XP.png)
+ skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
+* skin/classic/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
+* skin/classic/browser/preferences/in-content/dialog.css (preferences/in-content/dialog.css)
+ skin/classic/browser/preferences/applications.css (preferences/applications.css)
+ skin/classic/browser/social/services-16.png (social/services-16.png)
+ skin/classic/browser/social/services-64.png (social/services-64.png)
+ skin/classic/browser/tabbrowser/newtab.svg (tabbrowser/newtab.svg)
+ skin/classic/browser/tabbrowser/newtab-XPVista7.svg (tabbrowser/newtab-XPVista7.svg)
+ skin/classic/browser/tabbrowser/newtab-inverted.svg (tabbrowser/newtab-inverted.svg)
+ skin/classic/browser/tabbrowser/newtab-inverted-XPVista7.svg (tabbrowser/newtab-inverted-XPVista7.svg)
+ skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png)
+ skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left.svg (tabbrowser/tab-arrow-left.svg)
+ skin/classic/browser/tabbrowser/tab-arrow-left-XPVista7.svg (tabbrowser/tab-arrow-left-XPVista7.svg)
+ skin/classic/browser/tabbrowser/tab-arrow-left-inverted.svg (tabbrowser/tab-arrow-left-inverted.svg)
+ skin/classic/browser/tabbrowser/tab-background-start.png (tabbrowser/tab-background-start.png)
+ skin/classic/browser/tabbrowser/tab-background-start@2x.png (tabbrowser/tab-background-start@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
+ skin/classic/browser/tabbrowser/tab-background-middle@2x.png (tabbrowser/tab-background-middle@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-end.png (tabbrowser/tab-background-end.png)
+ skin/classic/browser/tabbrowser/tab-background-end@2x.png (tabbrowser/tab-background-end@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-start-preWin10.png (tabbrowser/tab-background-start-preWin10.png)
+ skin/classic/browser/tabbrowser/tab-background-start-preWin10@2x.png (tabbrowser/tab-background-start-preWin10@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-middle-preWin10.png (tabbrowser/tab-background-middle-preWin10.png)
+ skin/classic/browser/tabbrowser/tab-background-middle-preWin10@2x.png (tabbrowser/tab-background-middle-preWin10@2x.png)
+ skin/classic/browser/tabbrowser/tab-background-end-preWin10.png (tabbrowser/tab-background-end-preWin10.png)
+ skin/classic/browser/tabbrowser/tab-background-end-preWin10@2x.png (tabbrowser/tab-background-end-preWin10@2x.png)
+
+# NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
+# Makefile.in with a non-default marker of "%" and the result of that gets packaged.
+ skin/classic/browser/tabbrowser/tab-selected-end.svg (tab-selected-end.svg)
+ skin/classic/browser/tabbrowser/tab-selected-start.svg (tab-selected-start.svg)
+
+ skin/classic/browser/tabbrowser/tab-stroke-end.png (tabbrowser/tab-stroke-end.png)
+ skin/classic/browser/tabbrowser/tab-stroke-end@2x.png (tabbrowser/tab-stroke-end@2x.png)
+ skin/classic/browser/tabbrowser/tab-stroke-start.png (tabbrowser/tab-stroke-start.png)
+ skin/classic/browser/tabbrowser/tab-stroke-start@2x.png (tabbrowser/tab-stroke-start@2x.png)
+ skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
+ skin/classic/browser/sync-16.png
+ skin/classic/browser/sync-32.png
+ skin/classic/browser/sync-128.png
+ skin/classic/browser/sync-bg.png
+ skin/classic/browser/sync-desktopIcon.svg (../shared/sync-desktopIcon.svg)
+ skin/classic/browser/sync-horizontalbar.png
+ skin/classic/browser/sync-horizontalbar@2x.png
+ skin/classic/browser/sync-horizontalbar-XPVista7.png
+ skin/classic/browser/sync-horizontalbar-XPVista7@2x.png
+ skin/classic/browser/sync-mobileIcon.svg (../shared/sync-mobileIcon.svg)
+ skin/classic/browser/sync-notification-24.png
+ skin/classic/browser/syncSetup.css
+ skin/classic/browser/syncCommon.css
+ skin/classic/browser/syncQuota.css
+ skin/classic/browser/syncProgress-horizontalbar.png
+ skin/classic/browser/syncProgress-horizontalbar@2x.png
+ skin/classic/browser/syncProgress-horizontalbar-XPVista7.png
+ skin/classic/browser/syncProgress-horizontalbar-XPVista7@2x.png
+ skin/classic/browser/syncProgress-menuPanel.png
+ skin/classic/browser/syncProgress-menuPanel@2x.png
+ skin/classic/browser/syncProgress-toolbar.png
+ skin/classic/browser/syncProgress-toolbar@2x.png
+ skin/classic/browser/syncProgress-toolbar-inverted.png
+ skin/classic/browser/syncProgress-toolbar-inverted@2x.png
+ skin/classic/browser/syncProgress-toolbar-XPVista7.png
+ skin/classic/browser/syncProgress-toolbar-XPVista7@2x.png
+#ifdef E10S_TESTING_ONLY
+ skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
+#endif
+
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
+% override chrome://browser/skin/page-livemarks.png chrome://browser/skin/feeds/feedIcon16.png
+% override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
+% override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
+% override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png
+% override chrome://browser/skin/feeds/videoFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png
+
+% override chrome://browser/skin/aboutSessionRestore-window-icon.png chrome://browser/skin/preferences/application.png os!=WINNT
+% override chrome://browser/skin/aboutSessionRestore-window-icon.png chrome://browser/skin/preferences/application.png os=WINNT osversion<6
+
+% override chrome://browser/skin/Info.png chrome://browser/skin/Info-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/livemark-folder.png chrome://browser/skin/livemark-folder-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/menu-back.png chrome://browser/skin/menu-back-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/menu-forward.png chrome://browser/skin/menu-forward-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/pageInfo.png chrome://browser/skin/pageInfo-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/feeds/feedIcon.png chrome://browser/skin/feeds/feedIcon-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/feeds/feedIcon16.png chrome://browser/skin/feeds/feedIcon16-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/query.png chrome://browser/skin/places/query-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/bookmarksMenu.png chrome://browser/skin/places/bookmarksMenu-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/bookmarksToolbar.png chrome://browser/skin/places/bookmarksToolbar-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/bookmarksToolbar-menuPanel.png chrome://browser/skin/places/bookmarksToolbar-menuPanel-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/calendar.png chrome://browser/skin/places/calendar-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/toolbarDropMarker.png chrome://browser/skin/places/toolbarDropMarker-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/libraryToolbar.png chrome://browser/skin/places/libraryToolbar-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/starred48.png chrome://browser/skin/places/starred48-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/tag.png chrome://browser/skin/places/tag-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/history.png chrome://browser/skin/places/history-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/allBookmarks.png chrome://browser/skin/places/allBookmarks-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/places/unsortedBookmarks.png chrome://browser/skin/places/unsortedBookmarks-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/preferences/alwaysAsk.png chrome://browser/skin/preferences/alwaysAsk-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/preferences/application.png chrome://browser/skin/preferences/application-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/preferences/saveFile.png chrome://browser/skin/preferences/saveFile-XP.png os=WINNT osversion<6
+
+% override chrome://browser/skin/actionicon-tab.png chrome://browser/skin/actionicon-tab-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/privatebrowsing-mask-tabstrip.png chrome://browser/skin/privatebrowsing-mask-tabstrip-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/privatebrowsing-mask-titlebar.png chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/reload-stop-go.png chrome://browser/skin/reload-stop-go-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/reload-stop-go@2x.png chrome://browser/skin/reload-stop-go-XPVista7@2x.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/sync-horizontalbar.png chrome://browser/skin/sync-horizontalbar-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/sync-horizontalbar@2x.png chrome://browser/skin/sync-horizontalbar-XPVista7@2x.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/syncProgress-horizontalbar.png chrome://browser/skin/syncProgress-horizontalbar-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/syncProgress-horizontalbar@2x.png chrome://browser/skin/syncProgress-horizontalbar-XPVista7@2x.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/syncProgress-toolbar.png chrome://browser/skin/syncProgress-toolbar-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/syncProgress-toolbar@2x.png chrome://browser/skin/syncProgress-toolbar-XPVista7@2x.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/toolbarbutton-dropdown-arrow.png chrome://browser/skin/toolbarbutton-dropdown-arrow-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/urlbar-history-dropmarker.png chrome://browser/skin/urlbar-history-dropmarker-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/urlbar-history-dropmarker@2x.png chrome://browser/skin/urlbar-history-dropmarker-XPVista7@2x.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/downloads/download-glow-menuPanel.png chrome://browser/skin/downloads/download-glow-menuPanel-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/places/autocomplete-star.png chrome://browser/skin/places/autocomplete-star-XPVista7.png os=WINNT osversion<=6.1
+% override chrome://browser/skin/tabbrowser/newtab.svg chrome://browser/skin/tabbrowser/newtab-XPVista7.svg os=WINNT osversion<=6.1
+% override chrome://browser/skin/tabbrowser/newtab-inverted.svg chrome://browser/skin/tabbrowser/newtab-inverted-XPVista7.svg os=WINNT osversion<=6.1
+% override chrome://browser/skin/tabbrowser/tab-arrow-left.svg chrome://browser/skin/tabbrowser/tab-arrow-left-XPVista7.svg os=WINNT osversion<=6.1
+
+% override chrome://browser/skin/Toolbar@2x.png chrome://browser/skin/Toolbar-aero@2x.png os=WINNT osversion=6
+% override chrome://browser/skin/Toolbar@2x.png chrome://browser/skin/Toolbar-aero@2x.png os=WINNT osversion=6.1
+% override chrome://browser/skin/Toolbar@2x.png chrome://browser/skin/Toolbar-win8@2x.png os=WINNT osversion=6.2
+% override chrome://browser/skin/Toolbar@2x.png chrome://browser/skin/Toolbar-win8@2x.png os=WINNT osversion=6.3
+% override chrome://browser/skin/Toolbar.png chrome://browser/skin/Toolbar-XP.png os=WINNT osversion<6
+% override chrome://browser/skin/Toolbar.png chrome://browser/skin/Toolbar-aero.png os=WINNT osversion=6
+% override chrome://browser/skin/Toolbar.png chrome://browser/skin/Toolbar-aero.png os=WINNT osversion=6.1
+% override chrome://browser/skin/Toolbar.png chrome://browser/skin/Toolbar-win8.png os=WINNT osversion=6.2
+% override chrome://browser/skin/Toolbar.png chrome://browser/skin/Toolbar-win8.png os=WINNT osversion=6.3
+
+% override chrome://browser/skin/tabbrowser/tab-background-start.png chrome://browser/skin/tabbrowser/tab-background-start-preWin10.png os=WINNT osversion<=6.3
+% override chrome://browser/skin/tabbrowser/tab-background-start@2x.png chrome://browser/skin/tabbrowser/tab-background-start-preWin10@2x.png os=WINNT osversion<=6.3
+% override chrome://browser/skin/tabbrowser/tab-background-middle.png chrome://browser/skin/tabbrowser/tab-background-middle-preWin10.png os=WINNT osversion<=6.3
+% override chrome://browser/skin/tabbrowser/tab-background-middle@2x.png chrome://browser/skin/tabbrowser/tab-background-middle-preWin10@2x.png os=WINNT osversion<=6.3
+% override chrome://browser/skin/tabbrowser/tab-background-end.png chrome://browser/skin/tabbrowser/tab-background-end-preWin10.png os=WINNT osversion<=6.3
+% override chrome://browser/skin/tabbrowser/tab-background-end@2x.png chrome://browser/skin/tabbrowser/tab-background-end-preWin10@2x.png os=WINNT osversion<=6.3
diff --git a/browser/themes/windows/keyhole-forward-mask.svg b/browser/themes/windows/keyhole-forward-mask.svg
new file mode 100644
index 000000000..051f6d87c
--- /dev/null
+++ b/browser/themes/windows/keyhole-forward-mask.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg">
+ <mask id="mask" maskContentUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="1" fill="#fff"/>
+ <circle cx="-0.46" cy="0.5" r="0.63"/>
+ </mask>
+ <mask id="mask-hover" maskContentUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="1" fill="#fff"/>
+ <circle cx="-0.35" cy="0.5" r="0.58"/>
+ </mask>
+</svg>
diff --git a/browser/themes/windows/livemark-folder-XP.png b/browser/themes/windows/livemark-folder-XP.png
new file mode 100644
index 000000000..00aa0364d
--- /dev/null
+++ b/browser/themes/windows/livemark-folder-XP.png
Binary files differ
diff --git a/browser/themes/windows/livemark-folder.png b/browser/themes/windows/livemark-folder.png
new file mode 100644
index 000000000..0be774e63
--- /dev/null
+++ b/browser/themes/windows/livemark-folder.png
Binary files differ
diff --git a/browser/themes/windows/loop/toolbar-win10.png b/browser/themes/windows/loop/toolbar-win10.png
new file mode 100644
index 000000000..59337608d
--- /dev/null
+++ b/browser/themes/windows/loop/toolbar-win10.png
Binary files differ
diff --git a/browser/themes/windows/loop/toolbar-win10@2x.png b/browser/themes/windows/loop/toolbar-win10@2x.png
new file mode 100644
index 000000000..b87e1ffa3
--- /dev/null
+++ b/browser/themes/windows/loop/toolbar-win10@2x.png
Binary files differ
diff --git a/browser/themes/windows/menu-back-XP.png b/browser/themes/windows/menu-back-XP.png
new file mode 100644
index 000000000..ecb8ccd1a
--- /dev/null
+++ b/browser/themes/windows/menu-back-XP.png
Binary files differ
diff --git a/browser/themes/windows/menu-back.png b/browser/themes/windows/menu-back.png
new file mode 100644
index 000000000..7c1d4620a
--- /dev/null
+++ b/browser/themes/windows/menu-back.png
Binary files differ
diff --git a/browser/themes/windows/menu-forward-XP.png b/browser/themes/windows/menu-forward-XP.png
new file mode 100644
index 000000000..a7460dc27
--- /dev/null
+++ b/browser/themes/windows/menu-forward-XP.png
Binary files differ
diff --git a/browser/themes/windows/menu-forward.png b/browser/themes/windows/menu-forward.png
new file mode 100644
index 000000000..82cd87483
--- /dev/null
+++ b/browser/themes/windows/menu-forward.png
Binary files differ
diff --git a/browser/themes/windows/menuPanel-customize.png b/browser/themes/windows/menuPanel-customize.png
new file mode 100644
index 000000000..161426f89
--- /dev/null
+++ b/browser/themes/windows/menuPanel-customize.png
Binary files differ
diff --git a/browser/themes/windows/menuPanel-customize@2x.png b/browser/themes/windows/menuPanel-customize@2x.png
new file mode 100644
index 000000000..5ce3b766a
--- /dev/null
+++ b/browser/themes/windows/menuPanel-customize@2x.png
Binary files differ
diff --git a/browser/themes/windows/menuPanel-exit.png b/browser/themes/windows/menuPanel-exit.png
new file mode 100644
index 000000000..8daaffe95
--- /dev/null
+++ b/browser/themes/windows/menuPanel-exit.png
Binary files differ
diff --git a/browser/themes/windows/menuPanel-exit@2x.png b/browser/themes/windows/menuPanel-exit@2x.png
new file mode 100644
index 000000000..d48426546
--- /dev/null
+++ b/browser/themes/windows/menuPanel-exit@2x.png
Binary files differ
diff --git a/browser/themes/windows/menuPanel-help.png b/browser/themes/windows/menuPanel-help.png
new file mode 100644
index 000000000..2f2b010bc
--- /dev/null
+++ b/browser/themes/windows/menuPanel-help.png
Binary files differ
diff --git a/browser/themes/windows/menuPanel-help@2x.png b/browser/themes/windows/menuPanel-help@2x.png
new file mode 100644
index 000000000..b8f0a80fa
--- /dev/null
+++ b/browser/themes/windows/menuPanel-help@2x.png
Binary files differ
diff --git a/browser/themes/windows/monitor.png b/browser/themes/windows/monitor.png
new file mode 100644
index 000000000..3c26d2674
--- /dev/null
+++ b/browser/themes/windows/monitor.png
Binary files differ
diff --git a/browser/themes/windows/monitor_16-10.png b/browser/themes/windows/monitor_16-10.png
new file mode 100644
index 000000000..06d91ea37
--- /dev/null
+++ b/browser/themes/windows/monitor_16-10.png
Binary files differ
diff --git a/browser/themes/windows/moz.build b/browser/themes/windows/moz.build
new file mode 100644
index 000000000..b787ab08e
--- /dev/null
+++ b/browser/themes/windows/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['communicator']
+
+JAR_MANIFESTS += ['jar.mn']
+
+DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
+
+include('../tab-svgs.mozbuild')
diff --git a/browser/themes/windows/newtab/newTab.css b/browser/themes/windows/newtab/newTab.css
new file mode 100644
index 000000000..0f7aae6de
--- /dev/null
+++ b/browser/themes/windows/newtab/newTab.css
@@ -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 ../../shared/newtab/newTab.inc.css
+
+
+.newtab-undo-button {
+ color: rgb(0,102,204);
+}
+
+.newtab-undo-button > .button-box {
+ padding: 0;
+}
+
+.newtab-title {
+ font: message-box;
+ font-size: 13px;
+ line-height: 30px;
+}
diff --git a/browser/themes/windows/pageInfo-XP.png b/browser/themes/windows/pageInfo-XP.png
new file mode 100644
index 000000000..bbf257237
--- /dev/null
+++ b/browser/themes/windows/pageInfo-XP.png
Binary files differ
diff --git a/browser/themes/windows/pageInfo.css b/browser/themes/windows/pageInfo.css
new file mode 100644
index 000000000..97d7615c1
--- /dev/null
+++ b/browser/themes/windows/pageInfo.css
@@ -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/. */
+
+@import "chrome://global/skin/";
+
+/* View buttons */
+#viewGroup {
+ padding-inline-start: 10px;
+}
+
+#viewGroup > radio {
+ list-style-image: url("chrome://browser/skin/pageInfo.png");
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+ -moz-appearance: none;
+ padding: 5px 3px 1px 3px;
+ margin: 0 1px;
+ min-width: 4.5em;
+}
+
+#viewGroup > radio:hover {
+ background-color: #E0E8F6;
+ color: black;
+}
+
+#viewGroup > radio[selected="true"] {
+ background-color: #C1D2EE;
+ color: black;
+}
+
+#topBar {
+ border-bottom: 2px groove ThreeDFace;
+ padding-inline-start: 10px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+}
+
+#generalTab {
+ -moz-image-region: rect(0px, 32px, 32px, 0px)
+}
+
+#generalTab:hover, #generalTab[selected="true"] {
+ -moz-image-region: rect(32px, 32px, 64px, 0px)
+}
+
+#mediaTab {
+ -moz-image-region: rect(0px, 64px, 32px, 32px)
+}
+
+#mediaTab:hover, #mediaTab[selected="true"] {
+ -moz-image-region: rect(32px, 64px, 64px, 32px)
+}
+
+#feedTab {
+ -moz-image-region: rect(0px, 96px, 32px, 64px)
+}
+
+#feedTab:hover, #feedTab[selected="true"] {
+ -moz-image-region: rect(32px, 96px, 64px, 64px)
+}
+
+#permTab {
+ -moz-image-region: rect(0px, 128px, 32px, 96px)
+}
+
+#permTab:hover, #permTab[selected="true"] {
+ -moz-image-region: rect(32px, 128px, 64px, 96px)
+}
+
+#securityTab {
+ -moz-image-region: rect(0px, 160px, 32px, 128px)
+}
+
+#securityTab:hover, #securityTab[selected="true"] {
+ -moz-image-region: rect(32px, 160px, 64px, 128px)
+}
+
+deck {
+ padding: 10px 10px 10px 10px;
+}
+
+/* Misc */
+tree {
+ margin: .5em;
+}
+
+.gridSeparator {
+ width: .5em;
+}
+
+textbox {
+ background: transparent !important;
+ border: none;
+ padding: 0px;
+ margin-top: 1px;
+ -moz-appearance: none;
+}
+
+textbox.header {
+ margin-inline-start: 0;
+}
+
+.iframe {
+ margin: .5em;
+ background: white;
+ overflow: auto;
+}
+
+.fixedsize {
+ height: 8.5em;
+}
+
+textbox[disabled] {
+ font-style: italic;
+}
+
+/* General Tab */
+groupbox.collapsable caption .caption-icon {
+ width: 9px;
+ height: 9px;
+ background-repeat: no-repeat;
+ background-position: center;
+ margin-inline-start: 2px;
+ margin-inline-end: 2px;
+ background-image: url("chrome://global/skin/tree/twisty.svg#open");
+}
+
+groupbox.collapsable[closed="true"] {
+ border: none;
+ margin-bottom: 9px;
+ -moz-appearance: none;
+}
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+ background-image: url("chrome://global/skin/tree/twisty.svg#clsd");
+}
+
+groupbox tree {
+ margin: 0 3px;
+ border: none;
+}
+
+#securityBox description {
+ margin-inline-start: 10px;
+}
+
+#general-security-identity {
+ white-space: pre-wrap;
+ line-height: 2em;
+}
+
+/* Media Tab */
+#imagetree {
+ min-height: 10em;
+ margin-bottom: 0;
+}
+
+#mediaSplitter {
+ border-style: none;
+ background: none;
+ height: .8em;
+}
+
+#mediaGrid {
+ min-height: 9em;
+}
+
+#mediaLabelColumn {
+ min-width: 10em;
+}
+
+#thepreviewimage {
+ margin: 1em;
+}
+
+treechildren::-moz-tree-cell-text(broken) {
+ font-style: italic;
+ color: graytext;
+}
+
+/* Feeds Tab */
+#feedtree {
+ margin-bottom: 0px;
+}
+
+#feedListbox richlistitem {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+#feedListbox richlistitem[selected="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+#feedListbox {
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+}
+
+.feedTitle {
+ font-weight: bold;
+}
+
+/* Permissions Tab */
+#permList {
+ margin-top: .5em;
+ overflow: auto;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ background-color: -moz-field;
+}
+
+.permission {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+.permissionLabel {
+ font-weight: bold;
+}
+
+.permission:hover {
+ background-color: -moz-dialog;
+}
+
+/* Security Tab */
+#securityPanel .caption-icon {
+ display: none;
+}
+
+#securityPanel .header {
+ font-size: 120%;
+}
+
+#securityPanel .fieldLabel {
+ margin: 2px 10px 3px;
+}
+
+#securityPanel .fieldValue {
+ font-weight: bold;
+ margin: 2px 10px 3px;
+}
+
+#securityPanel row {
+ -moz-box-align: center;
+}
diff --git a/browser/themes/windows/pageInfo.png b/browser/themes/windows/pageInfo.png
new file mode 100644
index 000000000..bc1b97c55
--- /dev/null
+++ b/browser/themes/windows/pageInfo.png
Binary files differ
diff --git a/browser/themes/windows/places/allBookmarks-XP.png b/browser/themes/windows/places/allBookmarks-XP.png
new file mode 100644
index 000000000..f7903cc5f
--- /dev/null
+++ b/browser/themes/windows/places/allBookmarks-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/allBookmarks.png b/browser/themes/windows/places/allBookmarks.png
new file mode 100644
index 000000000..5331798e3
--- /dev/null
+++ b/browser/themes/windows/places/allBookmarks.png
Binary files differ
diff --git a/browser/themes/windows/places/autocomplete-star-XPVista7.png b/browser/themes/windows/places/autocomplete-star-XPVista7.png
new file mode 100644
index 000000000..af694e91c
--- /dev/null
+++ b/browser/themes/windows/places/autocomplete-star-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/places/autocomplete-star.png b/browser/themes/windows/places/autocomplete-star.png
new file mode 100644
index 000000000..9ba69f727
--- /dev/null
+++ b/browser/themes/windows/places/autocomplete-star.png
Binary files differ
diff --git a/browser/themes/windows/places/autocomplete-star@2x.png b/browser/themes/windows/places/autocomplete-star@2x.png
new file mode 100644
index 000000000..f30523e3d
--- /dev/null
+++ b/browser/themes/windows/places/autocomplete-star@2x.png
Binary files differ
diff --git a/browser/themes/windows/places/bookmarks-notification-finish.png b/browser/themes/windows/places/bookmarks-notification-finish.png
new file mode 100644
index 000000000..1e3f565f9
--- /dev/null
+++ b/browser/themes/windows/places/bookmarks-notification-finish.png
Binary files differ
diff --git a/browser/themes/windows/places/bookmarksMenu-XP.png b/browser/themes/windows/places/bookmarksMenu-XP.png
new file mode 100644
index 000000000..8f0c8bf58
--- /dev/null
+++ b/browser/themes/windows/places/bookmarksMenu-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/bookmarksMenu.png b/browser/themes/windows/places/bookmarksMenu.png
new file mode 100644
index 000000000..84032e1f0
--- /dev/null
+++ b/browser/themes/windows/places/bookmarksMenu.png
Binary files differ
diff --git a/browser/themes/windows/places/bookmarksToolbar-XP.png b/browser/themes/windows/places/bookmarksToolbar-XP.png
new file mode 100644
index 000000000..9e988de20
--- /dev/null
+++ b/browser/themes/windows/places/bookmarksToolbar-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/bookmarksToolbar-menuPanel-XP.png b/browser/themes/windows/places/bookmarksToolbar-menuPanel-XP.png
new file mode 100644
index 000000000..0e4247adb
--- /dev/null
+++ b/browser/themes/windows/places/bookmarksToolbar-menuPanel-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/bookmarksToolbar-menuPanel.png b/browser/themes/windows/places/bookmarksToolbar-menuPanel.png
new file mode 100644
index 000000000..7cbe93d7a
--- /dev/null
+++ b/browser/themes/windows/places/bookmarksToolbar-menuPanel.png
Binary files differ
diff --git a/browser/themes/windows/places/bookmarksToolbar.png b/browser/themes/windows/places/bookmarksToolbar.png
new file mode 100644
index 000000000..70bba1457
--- /dev/null
+++ b/browser/themes/windows/places/bookmarksToolbar.png
Binary files differ
diff --git a/browser/themes/windows/places/calendar-XP.png b/browser/themes/windows/places/calendar-XP.png
new file mode 100644
index 000000000..7645af5cd
--- /dev/null
+++ b/browser/themes/windows/places/calendar-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/calendar.png b/browser/themes/windows/places/calendar.png
new file mode 100644
index 000000000..4127e5988
--- /dev/null
+++ b/browser/themes/windows/places/calendar.png
Binary files differ
diff --git a/browser/themes/windows/places/downloads.png b/browser/themes/windows/places/downloads.png
new file mode 100644
index 000000000..f21a170cf
--- /dev/null
+++ b/browser/themes/windows/places/downloads.png
Binary files differ
diff --git a/browser/themes/windows/places/editBookmarkOverlay.css b/browser/themes/windows/places/editBookmarkOverlay.css
new file mode 100644
index 000000000..3d67cd3f4
--- /dev/null
+++ b/browser/themes/windows/places/editBookmarkOverlay.css
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**** folder menulist ****/
+.folder-icon > .menulist-label-box > .menulist-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.folder-icon > .menu-iconic-left {
+ display: -moz-box;
+}
+
+.folder-icon {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png") !important;
+ -moz-image-region: rect(0px, 32px, 16px, 16px) !important;
+}
+
+
+/**** expanders ****/
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ margin: 0;
+ margin-inline-end: 4px;
+}
+
+.expander-up > .button-box,
+.expander-down > .button-box {
+ padding: 0;
+}
+
+.expander-up {
+ list-style-image: url("chrome://global/skin/icons/collapse.png");
+}
+
+.expander-down {
+ list-style-image: url("chrome://global/skin/icons/expand.png");
+}
+
+#editBookmarkPanelContent {
+ min-width: 23em;
+}
+
+#editBMPanel_folderTree {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+/* Hide the value column of the tag autocomplete popup
+ * leaving only the comment column visible. This is
+ * so that only the tag being edited is shown in the
+ * popup.
+ */
+#editBMPanel_tagsField #treecolAutoCompleteValue {
+ visibility: collapse;
+}
+
+
+/* ::::: bookmark panel dropdown icons ::::: */
+
+#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+ -moz-image-region: auto !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_bmRootItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important;
+ -moz-image-region: auto !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png") !important;
+ -moz-image-region: auto !important;
+}
diff --git a/browser/themes/windows/places/history-XP.png b/browser/themes/windows/places/history-XP.png
new file mode 100644
index 000000000..fcc89bbbf
--- /dev/null
+++ b/browser/themes/windows/places/history-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/history.png b/browser/themes/windows/places/history.png
new file mode 100644
index 000000000..656626930
--- /dev/null
+++ b/browser/themes/windows/places/history.png
Binary files differ
diff --git a/browser/themes/windows/places/libraryToolbar-XP.png b/browser/themes/windows/places/libraryToolbar-XP.png
new file mode 100644
index 000000000..75b390ff6
--- /dev/null
+++ b/browser/themes/windows/places/libraryToolbar-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/libraryToolbar.png b/browser/themes/windows/places/libraryToolbar.png
new file mode 100644
index 000000000..31cee7a49
--- /dev/null
+++ b/browser/themes/windows/places/libraryToolbar.png
Binary files differ
diff --git a/browser/themes/windows/places/livemark-item.png b/browser/themes/windows/places/livemark-item.png
new file mode 100644
index 000000000..4522a20ae
--- /dev/null
+++ b/browser/themes/windows/places/livemark-item.png
Binary files differ
diff --git a/browser/themes/windows/places/organizer.css b/browser/themes/windows/places/organizer.css
new file mode 100644
index 000000000..4de603b9f
--- /dev/null
+++ b/browser/themes/windows/places/organizer.css
@@ -0,0 +1,222 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../windowsShared.inc
+%filter substitution
+
+/* Toolbar */
+#placesToolbar {
+ padding: 3px;
+ padding-inline-end: 6px;
+}
+
+#placesToolbar > toolbarbutton[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+#back-button,
+#forward-button {
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+}
+
+@media (-moz-windows-theme: luna-silver) {
+ #back-button,
+ #forward-button {
+ list-style-image: url("chrome://browser/skin/Toolbar-lunaSilver.png");
+ }
+}
+
+#back-button {
+ -moz-image-region: rect(0, 54px, 18px, 36px);
+}
+
+#forward-button {
+ -moz-image-region: rect(0, 72px, 18px, 54px);
+}
+
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+/* Menu */
+#placesMenu {
+ margin-inline-start: 6px;
+ -moz-appearance: none;
+ border: none;
+}
+
+#placesMenu > menu {
+ padding-inline-start: 4px;
+ padding-inline-end: 1px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ -moz-appearance: toolbarbutton;
+%ifdef XP_WIN
+% use standard menu colors on OS/2
+ color: -moz-DialogText;
+%endif
+ border: 1px solid transparent;
+}
+
+#placesMenu > menu[_moz-menuactive="true"] {
+ background-color: transparent;
+}
+
+#placesMenu > menu:hover {
+ border-color: ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight;
+}
+
+#placesMenu > menu[open="true"] {
+ border-color: ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow;
+ padding-inline-start: 5px;
+ padding-inline-end: 0px;
+ padding-top: 3px;
+ padding-bottom: 1px;
+}
+
+#placesMenu > menu > .menubar-text {
+ padding-inline-end: 8px;
+ background: url(chrome://global/skin/arrow/arrow-dn.gif) right center no-repeat;
+}
+
+#placesMenu > menu > .menubar-text:-moz-locale-dir(rtl) {
+ background-position: left center;
+}
+
+/* organize, view and maintenance buttons icons */
+#organizeButton,
+#viewMenu,
+#maintenanceButton {
+ list-style-image: url("chrome://browser/skin/places/libraryToolbar.png");
+}
+
+/* organize button */
+#organizeButton {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+#organizeButton:hover,
+#organizeButton[open="true"] {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+
+/* view button */
+#viewMenu {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+#viewMenu:hover,
+#viewMenu[open="true"] {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+/* maintenance button */
+#maintenanceButton {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+#maintenanceButton:hover,
+#maintenanceButton[open="true"] {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+
+/* Root View */
+#placesView {
+ border-top: 1px solid ThreeDDarkShadow;
+}
+
+/* Info box */
+#detailsDeck {
+ border-top: 1px solid ThreeDShadow;
+ padding: 5px;
+}
+
+#infoBoxExpanderLabel {
+ padding-inline-start: 2px;
+}
+
+#searchFilter {
+ margin: 0;
+}
+
+/**
+ * Downloads pane
+ */
+
+#clearDownloadsButton > .toolbarbutton-icon {
+ display: none;
+}
+
+#clearDownloadsButton {
+ padding-inline-start: 9px;
+ padding-inline-end: 9px;
+}
+
+
+@media not all and (-moz-os-version: windows-xp) {
+ #placesView {
+ border-top: none;
+ }
+
+ @media not all and (-moz-windows-classic) {
+ #placesToolbox {
+ -moz-appearance: none;
+ background-color: transparent;
+ }
+
+ #placesToolbar {
+ -moz-appearance: none;
+ background-color: -moz-Dialog;
+ color: -moz-dialogText;
+ }
+ }
+
+ @media (-moz-windows-default-theme) {
+ #placesView > splitter {
+ border: 0;
+ border-inline-end: 1px solid #A9B7C9;
+ min-width: 0;
+ width: 3px;
+ background-color: transparent;
+ margin-inline-start: -3px;
+ position: relative;
+ }
+ }
+}
+
+@media (-moz-windows-glass) {
+ #placesToolbox {
+ border-top: none;
+ }
+
+ #placesToolbar {
+ background-image: linear-gradient(@toolbarHighlight@, transparent);
+ }
+}
+
+@media (-moz-windows-default-theme) and (-moz-os-version: windows-vista),
+ (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
+ #placesView,
+ #infoPane,
+ #placesList,
+ #placeContent {
+ background-color: #EEF3FA;
+ }
+
+ #placesToolbar {
+ background-color: @customToolbarColor@;
+ color: black;
+ }
+
+ #detailsDeck {
+ border-top-color: #A9B7C9;
+ }
+
+ #searchFilter {
+ -moz-appearance: none;
+ padding: 2px;
+ padding-inline-start: 4px;
+ background-clip: padding-box;
+ border: 1px solid rgba(0,0,0,.32);
+ border-radius: 2px;
+ }
+}
diff --git a/browser/themes/windows/places/places.css b/browser/themes/windows/places/places.css
new file mode 100644
index 000000000..4ec8f6555
--- /dev/null
+++ b/browser/themes/windows/places/places.css
@@ -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/. */
+
+/* Sidebars */
+.sidebar-placesTree {
+ -moz-appearance: none;
+ border: 0;
+ margin: 0;
+ border-top: 1px solid ThreeDShadow;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
+.sidebar-placesTreechildren::-moz-tree-image(leaf) {
+ cursor: pointer;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(separator) {
+ cursor: default;
+}
+
+/* Style Places sidebars as Vista media collection */
+@media (-moz-windows-default-theme) {
+ @media not all and (-moz-os-version: windows-xp) {
+ .sidebar-placesTree {
+ background-color: transparent;
+ border-top: none;
+ }
+
+ .sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
+ text-decoration: none;
+ }
+ }
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #bookmarksPanel,
+ #history-panel,
+ #tabs-panel {
+ background-color: #EEF3FA;
+ }
+ }
+}
+
+/* Trees */
+treechildren::-moz-tree-image(title) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(title, livemarkItem) {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, separator) {
+ list-style-image: none;
+ width: 0;
+ height: 0;
+}
+
+treechildren::-moz-tree-image(title, container) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, open) {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+treechildren::-moz-tree-image(title, container, livemark) {
+ list-style-image: url("chrome://browser/skin/livemark-folder.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* query-nodes should be styled even if they're not expandable */
+treechildren::-moz-tree-image(title, query) {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, tagContainer),
+treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
+ list-style-image: url("chrome://browser/skin/places/downloads.png");
+ -moz-image-region: auto;
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer, open) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+treechildren::-moz-tree-image(title, query, OrganizerQuery_History) {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+/* We want some queries to look like ordinary folders. This must come
+ after the (title, query) selector, or it would get overridden. */
+treechildren::-moz-tree-image(title, query, folder) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, query, folder, open) {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+treechildren::-moz-tree-image(cutting) {
+ opacity: 0.5;
+}
+
+treechildren::-moz-tree-cell-text(cutting) {
+ opacity: 0.7;
+}
+
+/* Browser Sidebars */
+
+/* Default button vert. margins are 1px/2px, and this can cause misalignment */
+#viewButton {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+#viewButton > .button-box > .button-menu-dropmarker {
+ height: auto;
+ width: auto;
+ margin-inline-end: -3px;
+}
diff --git a/browser/themes/windows/places/query-XP.png b/browser/themes/windows/places/query-XP.png
new file mode 100644
index 000000000..9e79fc791
--- /dev/null
+++ b/browser/themes/windows/places/query-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/query.png b/browser/themes/windows/places/query.png
new file mode 100644
index 000000000..18b65bd71
--- /dev/null
+++ b/browser/themes/windows/places/query.png
Binary files differ
diff --git a/browser/themes/windows/places/starred48-XP.png b/browser/themes/windows/places/starred48-XP.png
new file mode 100644
index 000000000..1cb7bc57d
--- /dev/null
+++ b/browser/themes/windows/places/starred48-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/starred48.png b/browser/themes/windows/places/starred48.png
new file mode 100644
index 000000000..be9afdf3f
--- /dev/null
+++ b/browser/themes/windows/places/starred48.png
Binary files differ
diff --git a/browser/themes/windows/places/tag-XP.png b/browser/themes/windows/places/tag-XP.png
new file mode 100644
index 000000000..4b4a13e66
--- /dev/null
+++ b/browser/themes/windows/places/tag-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/tag.png b/browser/themes/windows/places/tag.png
new file mode 100644
index 000000000..ebe70a6b9
--- /dev/null
+++ b/browser/themes/windows/places/tag.png
Binary files differ
diff --git a/browser/themes/windows/places/toolbarDropMarker-XP.png b/browser/themes/windows/places/toolbarDropMarker-XP.png
new file mode 100644
index 000000000..9173b7a7a
--- /dev/null
+++ b/browser/themes/windows/places/toolbarDropMarker-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/toolbarDropMarker.png b/browser/themes/windows/places/toolbarDropMarker.png
new file mode 100644
index 000000000..6c1b61d30
--- /dev/null
+++ b/browser/themes/windows/places/toolbarDropMarker.png
Binary files differ
diff --git a/browser/themes/windows/places/unsortedBookmarks-XP.png b/browser/themes/windows/places/unsortedBookmarks-XP.png
new file mode 100644
index 000000000..cf73f9464
--- /dev/null
+++ b/browser/themes/windows/places/unsortedBookmarks-XP.png
Binary files differ
diff --git a/browser/themes/windows/places/unsortedBookmarks.png b/browser/themes/windows/places/unsortedBookmarks.png
new file mode 100644
index 000000000..b8d3e69ab
--- /dev/null
+++ b/browser/themes/windows/places/unsortedBookmarks.png
Binary files differ
diff --git a/browser/themes/windows/places/unstarred48.png b/browser/themes/windows/places/unstarred48.png
new file mode 100644
index 000000000..c287f0c4a
--- /dev/null
+++ b/browser/themes/windows/places/unstarred48.png
Binary files differ
diff --git a/browser/themes/windows/preferences/alwaysAsk-XP.png b/browser/themes/windows/preferences/alwaysAsk-XP.png
new file mode 100644
index 000000000..8693211ac
--- /dev/null
+++ b/browser/themes/windows/preferences/alwaysAsk-XP.png
Binary files differ
diff --git a/browser/themes/windows/preferences/alwaysAsk.png b/browser/themes/windows/preferences/alwaysAsk.png
new file mode 100644
index 000000000..b2ee5c0ce
--- /dev/null
+++ b/browser/themes/windows/preferences/alwaysAsk.png
Binary files differ
diff --git a/browser/themes/windows/preferences/application-XP.png b/browser/themes/windows/preferences/application-XP.png
new file mode 100644
index 000000000..7d279ff84
--- /dev/null
+++ b/browser/themes/windows/preferences/application-XP.png
Binary files differ
diff --git a/browser/themes/windows/preferences/application.png b/browser/themes/windows/preferences/application.png
new file mode 100644
index 000000000..e4b09204e
--- /dev/null
+++ b/browser/themes/windows/preferences/application.png
Binary files differ
diff --git a/browser/themes/windows/preferences/applications.css b/browser/themes/windows/preferences/applications.css
new file mode 100644
index 000000000..96ddb7ed0
--- /dev/null
+++ b/browser/themes/windows/preferences/applications.css
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Line up the actions menu with action labels above and below it.
+ * Equalize the distance from the left side of the action box to the left side
+ * of the icon for both the menu and the non-menu versions of the action box.
+ * Also make sure the labels are the same distance away from the icons.
+ */
+.actionsMenu {
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-inline-start: -2px;
+ margin-inline-end: 0;
+}
+
+.typeIcon,
+.actionIcon {
+ margin-inline-start: 3px;
+ margin-inline-end: 3px;
+}
+
+#handlersView > richlistitem label {
+ margin-inline-start: 1px;
+ margin-top: 2px;
+}
+
+#handlersView > richlistitem {
+ min-height: 22px;
+}
+
+richlistitem[appHandlerIcon="ask"],
+menuitem[appHandlerIcon="ask"] {
+ list-style-image: url("chrome://browser/skin/preferences/alwaysAsk.png");
+}
+
+richlistitem[appHandlerIcon="save"],
+menuitem[appHandlerIcon="save"] {
+ list-style-image: url("chrome://browser/skin/preferences/application.png");
+}
+
+richlistitem[appHandlerIcon="feed"],
+menuitem[appHandlerIcon="feed"] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+richlistitem[appHandlerIcon="plugin"],
+menuitem[appHandlerIcon="plugin"] {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
+}
+
+.actionsMenu .menulist-icon {
+ margin-inline-end: 3px;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ padding-inline-start: 0px;
+ padding-inline-end: 2px;
+}
+
+.actionsMenu > menupopup > menuitem {
+ padding-inline-start: 4px;
+}
diff --git a/browser/themes/windows/preferences/in-content/dialog.css b/browser/themes/windows/preferences/in-content/dialog.css
new file mode 100644
index 000000000..3b1ae45e0
--- /dev/null
+++ b/browser/themes/windows/preferences/in-content/dialog.css
@@ -0,0 +1,19 @@
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../../shared/incontentprefs/dialog.inc.css
+
+label:not(.menu-text),
+textbox,
+description,
+.tab-text,
+caption > label {
+ font-size: 1.2em;
+}
+
+/* Create a separate rule to unset these styles on .tree-input instead of
+ using :not(.tree-input) so the selector specifity doesn't change. */
+textbox.tree-input {
+ font-size: unset;
+}
diff --git a/browser/themes/windows/preferences/in-content/preferences.css b/browser/themes/windows/preferences/in-content/preferences.css
new file mode 100644
index 000000000..5ba7c49a4
--- /dev/null
+++ b/browser/themes/windows/preferences/in-content/preferences.css
@@ -0,0 +1,64 @@
+/* - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../../shared/incontentprefs/preferences.inc.css
+
+@media not all and (-moz-windows-default-theme) {
+ #category-general > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#general-native");
+ }
+
+ #category-search > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#search-native");
+ }
+
+ #category-content > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#content-native");
+ }
+
+ #category-application > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#applications-native");
+ }
+
+ #category-privacy > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#privacy-native");
+ }
+
+ #category-security > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#security-native");
+ }
+
+ #category-sync > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#sync-native");
+ }
+
+ #category-advanced > .category-icon {
+ list-style-image: url("chrome://browser/skin/preferences/in-content/icons.svg#advanced-native");
+ }
+}
+
+.actionsMenu > .menulist-label-box > .menulist-icon {
+ margin-inline-end: 9px;
+}
+
+textbox + button,
+filefield + button {
+ margin-inline-start: -4px;
+}
+
+#advancedPrefs {
+ padding-bottom: 0; /* override padding from normal preferences.css */
+}
+
+#fxaProfileImage {
+ -moz-user-focus: normal;
+}
+
+/**
+ * Dialog
+ */
+
+#dialogTitle {
+ font-size: 1em;
+}
diff --git a/browser/themes/windows/preferences/preferences.css b/browser/themes/windows/preferences/preferences.css
new file mode 100644
index 000000000..bd1ec3083
--- /dev/null
+++ b/browser/themes/windows/preferences/preferences.css
@@ -0,0 +1,103 @@
+/*
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+/* General Pane */
+
+#useFirefoxSync,
+#getStarted {
+ font-size: 90%;
+}
+
+#isNotDefaultLabel {
+ font-weight: bold;
+}
+
+/* Content Pane */
+#translationAttributionImage {
+ width: 70px;
+ cursor: pointer;
+}
+
+/* Modeless Window Dialogs */
+.windowDialog,
+.windowDialog prefpane {
+ padding: 0;
+}
+
+.contentPane {
+ margin: 9px 8px 5px;
+}
+
+.actionButtons {
+ margin: 0 3px 6px !important;
+}
+
+/* Cookies Manager */
+#cookiesChildren::-moz-tree-image(domainCol) {
+ width: 16px;
+ height: 16px;
+ margin: 0 2px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png") !important;
+}
+
+#cookiesChildren::-moz-tree-image(domainCol, container) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png") !important;
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#cookiesChildren::-moz-tree-image(domainCol, container, open) {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+#cookieInfoBox {
+ border: 1px solid ThreeDShadow;
+ border-radius: 0;
+ margin: 4px;
+ padding: 0;
+}
+
+/* Advanced Pane */
+
+/* Adding padding-bottom prevents the bottom of the tabpanel from being cutoff
+ when browser.preferences.animateFadeIn = true */
+#advancedPrefs {
+ padding-bottom: 8px;
+}
+
+/* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
+ of the groupbox from being cutoff */
+.bottomBox {
+ padding-bottom: 4px;
+}
+
+/* Sync Pane */
+
+#syncDesc {
+ padding: 0 8em;
+}
+
+.syncGroupBox {
+ padding: 10px;
+}
+
+#accountCaptionImage {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
+}
+
+#syncAddDeviceLabel {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+#noFxaAccount {
+ margin: 6px;
+ line-height: 1.2em;
+}
+
+#noFxaAccount > label:first-child {
+ margin-bottom: 0.6em;
+}
diff --git a/browser/themes/windows/preferences/saveFile-XP.png b/browser/themes/windows/preferences/saveFile-XP.png
new file mode 100644
index 000000000..e115eaa9f
--- /dev/null
+++ b/browser/themes/windows/preferences/saveFile-XP.png
Binary files differ
diff --git a/browser/themes/windows/preferences/saveFile.png b/browser/themes/windows/preferences/saveFile.png
new file mode 100644
index 000000000..ca4b12589
--- /dev/null
+++ b/browser/themes/windows/preferences/saveFile.png
Binary files differ
diff --git a/browser/themes/windows/privatebrowsing-mask-tabstrip-XPVista7.png b/browser/themes/windows/privatebrowsing-mask-tabstrip-XPVista7.png
new file mode 100644
index 000000000..bd5d46a76
--- /dev/null
+++ b/browser/themes/windows/privatebrowsing-mask-tabstrip-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/privatebrowsing-mask-tabstrip.png b/browser/themes/windows/privatebrowsing-mask-tabstrip.png
new file mode 100644
index 000000000..e2eba8b89
--- /dev/null
+++ b/browser/themes/windows/privatebrowsing-mask-tabstrip.png
Binary files differ
diff --git a/browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7-tall.png b/browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7-tall.png
new file mode 100644
index 000000000..4a723c54e
--- /dev/null
+++ b/browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7-tall.png
Binary files differ
diff --git a/browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7.png b/browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7.png
new file mode 100644
index 000000000..835912b53
--- /dev/null
+++ b/browser/themes/windows/privatebrowsing-mask-titlebar-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/privatebrowsing-mask-titlebar.png b/browser/themes/windows/privatebrowsing-mask-titlebar.png
new file mode 100644
index 000000000..111dc7d04
--- /dev/null
+++ b/browser/themes/windows/privatebrowsing-mask-titlebar.png
Binary files differ
diff --git a/browser/themes/windows/reload-stop-go-XPVista7.png b/browser/themes/windows/reload-stop-go-XPVista7.png
new file mode 100644
index 000000000..3ef32c3ce
--- /dev/null
+++ b/browser/themes/windows/reload-stop-go-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/reload-stop-go-XPVista7@2x.png b/browser/themes/windows/reload-stop-go-XPVista7@2x.png
new file mode 100644
index 000000000..38b27bf0c
--- /dev/null
+++ b/browser/themes/windows/reload-stop-go-XPVista7@2x.png
Binary files differ
diff --git a/browser/themes/windows/reload-stop-go.png b/browser/themes/windows/reload-stop-go.png
new file mode 100644
index 000000000..8b3f398c0
--- /dev/null
+++ b/browser/themes/windows/reload-stop-go.png
Binary files differ
diff --git a/browser/themes/windows/reload-stop-go@2x.png b/browser/themes/windows/reload-stop-go@2x.png
new file mode 100644
index 000000000..f116eb98f
--- /dev/null
+++ b/browser/themes/windows/reload-stop-go@2x.png
Binary files differ
diff --git a/browser/themes/windows/sanitizeDialog.css b/browser/themes/windows/sanitizeDialog.css
new file mode 100644
index 000000000..58e1f2983
--- /dev/null
+++ b/browser/themes/windows/sanitizeDialog.css
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#sanitizeDurationChoice {
+ margin-inline-end: 0;
+}
+
+/* Align the duration label with the warning box and item list */
+#sanitizeDurationLabel {
+ margin-inline-start: 3px;
+}
+
+
+/* Hide the duration dropdown suffix label if it's empty. Otherwise it
+ takes up a little space, causing the end of the dropdown to not be aligned
+ with the warning box. */
+#sanitizeDurationSuffixLabel[value=""] {
+ display: none;
+}
+
+
+/* Places tree */
+#placesTreechildren::-moz-tree-row(selected),
+#placesTreechildren::-moz-tree-row(grippyRow) {
+ background: #999;
+}
+
+#placesTreechildren::-moz-tree-cell-text(selected) {
+ color: #111;
+}
+
+
+/* Sanitize everything warning box */
+#sanitizeEverythingWarningBox {
+ background-color: Window;
+ border: 1px solid ThreeDDarkShadow;
+ border-radius: 5px;
+ padding: 16px;
+}
+
+#sanitizeEverythingWarningIcon {
+ list-style-image: url("chrome://global/skin/icons/warning-large.png");
+ padding: 0;
+ margin: 0;
+}
+
+#sanitizeEverythingWarningDescBox {
+ padding: 0 16px;
+ margin: 0;
+}
+
+
+/* Progressive disclosure button */
+#detailsExpanderWrapper {
+ padding: 0;
+ margin: 6px 0;
+}
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ margin: 0;
+}
+
+.expander-up > .button-box,
+.expander-down > .button-box {
+ padding: 0;
+}
+
+.expander-up {
+ list-style-image: url("chrome://global/skin/icons/collapse.png");
+}
+
+.expander-down {
+ list-style-image: url("chrome://global/skin/icons/expand.png");
+}
+
+
+/* Make the item list the same width as the warning box */
+#itemList {
+ margin-inline-start: 0;
+ margin-inline-end: 0;
+}
+
+
+/* Align the last dialog button with the end of the warning box */
+.prefWindow-dlgbuttons {
+ margin-inline-end: 0;
+}
+.dialog-button[dlgtype="cancel"] {
+ margin-inline-end: 0;
+}
diff --git a/browser/themes/windows/searchbar.css b/browser/themes/windows/searchbar.css
new file mode 100644
index 000000000..ef24f7c5e
--- /dev/null
+++ b/browser/themes/windows/searchbar.css
@@ -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/. */
+
+#PopupSearchAutoComplete {
+ /* JS code forces the panel to have the width of the searchbar rather than
+ * the width of the textfield. Alignment of the panel with the searchbar is
+ * obtained with negative margins here: margin-inline-start when the text
+ * field is in the same direction as the rest of the UI, margin-inline-end
+ * when the textfield's direction has been reversed.
+ * (eg. using ctrl+shift+X) */
+ margin-inline-start: -25px;
+ margin-inline-end: -18px;
+}
+
+.autocomplete-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.textbox-input-box {
+ margin: 0;
+}
+
+/* ::::: searchbar-engine-button ::::: */
+
+.searchbar-engine-image {
+ height: 16px;
+ width: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ margin-inline-start: -1px;
+}
+
+/* ::::: search-go-button ::::: */
+
+.search-go-container {
+ -moz-box-align: center;
+}
+
+.search-go-button {
+ padding: 1px;
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+ width: 14px;
+}
+
+.search-go-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.search-go-button:hover {
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+.search-go-button:hover:active {
+ -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+
+.searchbar-search-button-container {
+ -moz-box-align: center;
+}
+
+.searchbar-search-button {
+ list-style-image: url("chrome://browser/skin/search-indicator.png");
+ -moz-image-region: rect(0, 20px, 20px, 0);
+ margin-top: 1px;
+ margin-bottom: 1px;
+ margin-inline-start: 4px;
+ width: 20px;
+}
+
+.searchbar-search-button[addengines="true"] {
+ list-style-image: url("chrome://browser/skin/search-indicator-badge-add.png");
+}
+
+.searchbar-search-button:hover {
+ -moz-image-region: rect(0, 40px, 20px, 20px);
+}
+
+.searchbar-search-button:hover:active {
+ -moz-image-region: rect(0, 60px, 20px, 40px);
+}
+
+@media (min-resolution: 1.1dppx) {
+ .searchbar-engine-image {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+
+ .searchbar-search-button {
+ list-style-image: url("chrome://browser/skin/search-indicator@2x.png");
+ -moz-image-region: rect(0, 40px, 40px, 0);
+ }
+
+ .searchbar-search-button[addengines="true"] {
+ list-style-image: url("chrome://browser/skin/search-indicator-badge-add@2x.png");
+ }
+
+ .searchbar-search-button:hover {
+ -moz-image-region: rect(0, 80px, 40px, 40px);
+ }
+
+ .searchbar-search-button:hover:active {
+ -moz-image-region: rect(0, 120px, 40px, 80px);
+ }
+
+ .search-go-button {
+ list-style-image: url("chrome://browser/skin/reload-stop-go@2x.png");
+ -moz-image-region: rect(0, 84px, 28px, 56px);
+ }
+
+ .search-go-button:hover {
+ -moz-image-region: rect(28px, 84px, 56px, 56px);
+ }
+
+ .search-go-button:hover:active {
+ -moz-image-region: rect(56px, 84px, 84px, 56px);
+ }
+}
+
+.search-panel-current-engine {
+ -moz-box-align: center;
+}
+
+/**
+ * The borders of the various elements are specified as follows.
+ *
+ * The current engine always has a bottom border.
+ * The search results never have a border.
+ *
+ * When the search results are not collapsed:
+ * - The elements underneath the search results all have a top border.
+ *
+ * When the search results are collapsed:
+ * - The elements underneath the search results all have a bottom border, except
+ * the lowest one: search-setting-button.
+ */
+
+.search-panel-current-engine {
+ border-top: none !important;
+ border-bottom: 1px solid var(--panel-separator-color) !important;
+}
+
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-header,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-one-offs,
+.search-panel-tree[collapsed=true] + .search-one-offs > vbox > .addengine-item:first-of-type {
+ border-top: none !important;
+}
+
+.search-panel-tree[collapsed=true] + .search-one-offs > .searchbar-engine-one-off-item,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-current-input,
+.search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-one-offs,
+.search-panel-tree[collapsed=true] + .search-one-offs > vbox > .addengine-item:last-of-type {
+ border-bottom: 1px solid var(--panel-separator-color) !important;
+}
+
+.search-panel-header {
+ font-weight: normal;
+ background-color: var(--arrowpanel-dimmed);
+ border: none;
+ border-top: 1px solid var(--panel-separator-color);
+ margin: 0;
+ padding: 3px 6px;
+ color: GrayText;
+}
+
+.search-panel-header > label {
+ margin-top: 2px !important;
+ margin-bottom: 1px !important;
+}
+
+.search-panel-current-input > label {
+ margin: 2px 0 1px !important;
+}
+
+.search-panel-input-value {
+ color: -moz-fieldtext;
+}
+
+.search-panel-one-offs {
+ margin: 0 !important;
+ border-top: 1px solid var(--panel-separator-color);
+ line-height: 0;
+}
+
+.searchbar-engine-one-off-item {
+ -moz-appearance: none;
+ display: inline-block;
+ border: none;
+ min-width: 48px;
+ height: 32px;
+ margin: 0;
+ padding: 0;
+ background: linear-gradient(transparent 15%, var(--panel-separator-color) 15%, var(--panel-separator-color) 85%, transparent 85%);
+ background-size: 1px auto;
+ background-repeat: no-repeat;
+ background-position: right center;
+ color: GrayText;
+}
+
+.searchbar-engine-one-off-item:-moz-locale-dir(rtl) {
+ background-position: left center;
+}
+
+.searchbar-engine-one-off-item:not(.last-row) {
+ box-sizing: content-box;
+ border-bottom: 1px solid var(--panel-separator-color);
+}
+
+.search-setting-button-compact {
+ border-bottom: none !important;
+}
+
+.search-panel-one-offs:not([compact=true]) > .searchbar-engine-one-off-item.last-of-row,
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-of-row:not(.dummy),
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.dummy:not(.last-of-row),
+.search-panel-one-offs[compact=true] > .searchbar-engine-one-off-item.last-engine,
+.search-setting-button-compact {
+ background-image: none;
+}
+
+.searchbar-engine-one-off-item[selected] {
+ background-color: Highlight;
+ background-image: none;
+ color: HighlightText;
+}
+
+.searchbar-engine-one-off-item > .button-box {
+ border: none;
+ padding: 0;
+}
+
+.searchbar-engine-one-off-item > .button-box > .button-text {
+ display: none;
+}
+
+.searchbar-engine-one-off-item > .button-box > .button-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.addengine-item {
+ -moz-appearance: none;
+ border: none;
+ height: 32px;
+ margin: 0;
+ padding: 0 10px;
+}
+
+.addengine-item > .button-box {
+ -moz-box-pack: start;
+}
+
+.addengine-item:first-of-type {
+ border-top: 1px solid var(--panel-separator-color);
+}
+
+.addengine-item[selected] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.addengine-icon {
+ width: 16px;
+}
+
+.addengine-badge {
+ width: 16px;
+ height: 16px;
+ margin: -7px -9px 7px 9px;
+ list-style-image: url("chrome://browser/skin/badge-add-engine.png");
+}
+
+.addengine-item > .button-box > .button-text {
+ -moz-box-flex: 1;
+ text-align: start;
+ padding-inline-start: 10px;
+}
+
+.addengine-item:not([image]) {
+ list-style-image: url("chrome://browser/skin/search-engine-placeholder.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ .addengine-badge {
+ list-style-image: url("chrome://browser/skin/badge-add-engine@2x.png");
+ }
+
+ .addengine-item:not([image]) {
+ list-style-image: url("chrome://browser/skin/search-engine-placeholder@2x.png");
+ }
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-cell {
+ border-top: none !important;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-cell-text {
+ padding-inline-start: 4px;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image {
+ padding-inline-start: 5px;
+ width: 14px;
+ height: 14px;
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image(fromhistory) {
+ list-style-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon");
+}
+
+.search-panel-tree > .autocomplete-treebody::-moz-tree-image(fromhistory, selected) {
+ list-style-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active");
+}
+
+.search-setting-button {
+ -moz-appearance: none;
+ min-height: 32px;
+}
+
+.search-setting-button[selected] {
+ background-color: var(--arrowpanel-dimmed-further);
+}
+
+.search-setting-button-compact > .button-box > .button-icon {
+ list-style-image: url("chrome://browser/skin/gear.svg");
+ filter: url(chrome://browser/skin/filters.svg#fill);
+ fill: currentColor;
+}
diff --git a/browser/themes/windows/setDesktopBackground.css b/browser/themes/windows/setDesktopBackground.css
new file mode 100644
index 000000000..585284c7b
--- /dev/null
+++ b/browser/themes/windows/setDesktopBackground.css
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|canvas#screen {
+ margin: 12px 11px 32px;
+}
+
+#monitor {
+ list-style-image: url("chrome://browser/skin/monitor.png");
+}
+
+#monitor[aspectratio="16:10"] {
+ list-style-image: url("chrome://browser/skin/monitor_16-10.png");
+}
diff --git a/browser/themes/windows/slowStartup-16.png b/browser/themes/windows/slowStartup-16.png
new file mode 100644
index 000000000..6d09fa99d
--- /dev/null
+++ b/browser/themes/windows/slowStartup-16.png
Binary files differ
diff --git a/browser/themes/windows/social/services-16.png b/browser/themes/windows/social/services-16.png
new file mode 100644
index 000000000..a5f40b21d
--- /dev/null
+++ b/browser/themes/windows/social/services-16.png
Binary files differ
diff --git a/browser/themes/windows/social/services-64.png b/browser/themes/windows/social/services-64.png
new file mode 100644
index 000000000..c42c27826
--- /dev/null
+++ b/browser/themes/windows/social/services-64.png
Binary files differ
diff --git a/browser/themes/windows/sync-128.png b/browser/themes/windows/sync-128.png
new file mode 100644
index 000000000..0647fb080
--- /dev/null
+++ b/browser/themes/windows/sync-128.png
Binary files differ
diff --git a/browser/themes/windows/sync-16.png b/browser/themes/windows/sync-16.png
new file mode 100644
index 000000000..63cd02a8b
--- /dev/null
+++ b/browser/themes/windows/sync-16.png
Binary files differ
diff --git a/browser/themes/windows/sync-32.png b/browser/themes/windows/sync-32.png
new file mode 100644
index 000000000..0e9c0dfd3
--- /dev/null
+++ b/browser/themes/windows/sync-32.png
Binary files differ
diff --git a/browser/themes/windows/sync-bg.png b/browser/themes/windows/sync-bg.png
new file mode 100644
index 000000000..893a27d76
--- /dev/null
+++ b/browser/themes/windows/sync-bg.png
Binary files differ
diff --git a/browser/themes/windows/sync-horizontalbar-XPVista7.png b/browser/themes/windows/sync-horizontalbar-XPVista7.png
new file mode 100644
index 000000000..2c97ce6db
--- /dev/null
+++ b/browser/themes/windows/sync-horizontalbar-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/sync-horizontalbar-XPVista7@2x.png b/browser/themes/windows/sync-horizontalbar-XPVista7@2x.png
new file mode 100644
index 000000000..ee117ab73
--- /dev/null
+++ b/browser/themes/windows/sync-horizontalbar-XPVista7@2x.png
Binary files differ
diff --git a/browser/themes/windows/sync-horizontalbar.png b/browser/themes/windows/sync-horizontalbar.png
new file mode 100644
index 000000000..cabbb7bce
--- /dev/null
+++ b/browser/themes/windows/sync-horizontalbar.png
Binary files differ
diff --git a/browser/themes/windows/sync-horizontalbar@2x.png b/browser/themes/windows/sync-horizontalbar@2x.png
new file mode 100644
index 000000000..fadb57586
--- /dev/null
+++ b/browser/themes/windows/sync-horizontalbar@2x.png
Binary files differ
diff --git a/browser/themes/windows/sync-notification-24.png b/browser/themes/windows/sync-notification-24.png
new file mode 100644
index 000000000..fa2476c7b
--- /dev/null
+++ b/browser/themes/windows/sync-notification-24.png
Binary files differ
diff --git a/browser/themes/windows/syncCommon.css b/browser/themes/windows/syncCommon.css
new file mode 100644
index 000000000..862cc404d
--- /dev/null
+++ b/browser/themes/windows/syncCommon.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/. */
+
+/* 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.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ .statusIcon[status="active"] {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ }
+}
+
+.statusIcon[status="error"] {
+ list-style-image: url("chrome://global/skin/icons/error-16.png");
+}
+
+.statusIcon[status="success"] {
+ list-style-image: url("chrome://global/skin/icons/information-16.png");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+ a separate stylesheet for it. */
+.data {
+ font-size: 90%;
+ font-weight: bold;
+}
+
+dialog#change-dialog {
+ width: 40em;
+}
+
+image#syncIcon {
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+ margin-top: 2px;
+}
+
+#feedback {
+ height: 2em;
+}
diff --git a/browser/themes/windows/syncProgress-horizontalbar-XPVista7.png b/browser/themes/windows/syncProgress-horizontalbar-XPVista7.png
new file mode 100644
index 000000000..48cd11055
--- /dev/null
+++ b/browser/themes/windows/syncProgress-horizontalbar-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-horizontalbar-XPVista7@2x.png b/browser/themes/windows/syncProgress-horizontalbar-XPVista7@2x.png
new file mode 100644
index 000000000..741dd2ed4
--- /dev/null
+++ b/browser/themes/windows/syncProgress-horizontalbar-XPVista7@2x.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-horizontalbar.png b/browser/themes/windows/syncProgress-horizontalbar.png
new file mode 100644
index 000000000..79d972389
--- /dev/null
+++ b/browser/themes/windows/syncProgress-horizontalbar.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-horizontalbar@2x.png b/browser/themes/windows/syncProgress-horizontalbar@2x.png
new file mode 100644
index 000000000..e1de4763d
--- /dev/null
+++ b/browser/themes/windows/syncProgress-horizontalbar@2x.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-menuPanel.png b/browser/themes/windows/syncProgress-menuPanel.png
new file mode 100644
index 000000000..6fd6f9c16
--- /dev/null
+++ b/browser/themes/windows/syncProgress-menuPanel.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-menuPanel@2x.png b/browser/themes/windows/syncProgress-menuPanel@2x.png
new file mode 100644
index 000000000..04b2cae00
--- /dev/null
+++ b/browser/themes/windows/syncProgress-menuPanel@2x.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-toolbar-XPVista7.png b/browser/themes/windows/syncProgress-toolbar-XPVista7.png
new file mode 100644
index 000000000..49e224f0d
--- /dev/null
+++ b/browser/themes/windows/syncProgress-toolbar-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-toolbar-XPVista7@2x.png b/browser/themes/windows/syncProgress-toolbar-XPVista7@2x.png
new file mode 100644
index 000000000..fd2038725
--- /dev/null
+++ b/browser/themes/windows/syncProgress-toolbar-XPVista7@2x.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-toolbar-inverted.png b/browser/themes/windows/syncProgress-toolbar-inverted.png
new file mode 100644
index 000000000..4ede4387d
--- /dev/null
+++ b/browser/themes/windows/syncProgress-toolbar-inverted.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-toolbar-inverted@2x.png b/browser/themes/windows/syncProgress-toolbar-inverted@2x.png
new file mode 100644
index 000000000..eee4a5dd0
--- /dev/null
+++ b/browser/themes/windows/syncProgress-toolbar-inverted@2x.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-toolbar.png b/browser/themes/windows/syncProgress-toolbar.png
new file mode 100644
index 000000000..05078a9c8
--- /dev/null
+++ b/browser/themes/windows/syncProgress-toolbar.png
Binary files differ
diff --git a/browser/themes/windows/syncProgress-toolbar@2x.png b/browser/themes/windows/syncProgress-toolbar@2x.png
new file mode 100644
index 000000000..4493d7c74
--- /dev/null
+++ b/browser/themes/windows/syncProgress-toolbar@2x.png
Binary files differ
diff --git a/browser/themes/windows/syncQuota.css b/browser/themes/windows/syncQuota.css
new file mode 100644
index 000000000..1577de8a3
--- /dev/null
+++ b/browser/themes/windows/syncQuota.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#quotaDialog {
+ width: 33em;
+ height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+ list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+ height: 4em;
+}
+
+.captionWarning {
+ font-weight: bold;
+}
diff --git a/browser/themes/windows/syncSetup.css b/browser/themes/windows/syncSetup.css
new file mode 100644
index 000000000..67335d87d
--- /dev/null
+++ b/browser/themes/windows/syncSetup.css
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+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,
+#login-throbber {
+ -moz-box-align: center;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+ width: 16px;
+ list-style-image: url("chrome://global/skin/icons/loading.png");
+}
+
+@media (min-resolution: 1.1dppx) {
+ #pairDeviceThrobber > image,
+ #login-throbber > image {
+ list-style-image: url("chrome://global/skin/icons/loading@2x.png");
+ }
+}
+
+#captchaFeedback {
+ visibility: hidden;
+}
+
+#successPageIcon {
+ /* TODO replace this with a 128px version (bug 591122) */
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#tosDesc {
+ margin-left: -7px;
+ margin-bottom: 3px;
+} \ No newline at end of file
diff --git a/browser/themes/windows/syncedtabs/sidebar.css b/browser/themes/windows/syncedtabs/sidebar.css
new file mode 100644
index 000000000..6473206bc
--- /dev/null
+++ b/browser/themes/windows/syncedtabs/sidebar.css
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%include ../../shared/syncedtabs/sidebar.inc.css
+
+/* These styles are intended to mimic XUL trees and the XUL search box. */
+
+html {
+ background-color: #EEF3FA;
+}
+
+.item {
+ padding-inline-end: 0;
+}
+
+.item-title {
+ margin: 1px 0 0;
+}
+
+.item-title {
+ margin-inline-end: 6px;
+}
+
+.search-box {
+ -moz-appearance: textfield;
+ cursor: text;
+ margin: 2px 4px;
+ padding: 2px 2px 3px;
+ padding-inline-start: 4px;
+ color: -moz-FieldText;
+}
+
+.textbox-search-icon {
+ width: 16px;
+ height: 16px;
+ background-image: url(chrome://global/skin/icons/Search-glass.png);
+ background-repeat: no-repeat;
+ display: block;
+}
+
+.textbox-search-icon:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.textbox-search-icon[searchbutton]:not([disabled]) {
+ cursor: pointer;
+}
+
+.textbox-search-clear {
+ width: 16px;
+ height: 16px;
+ background-image: url(chrome://global/skin/icons/Search-close.png);
+ background-repeat: no-repeat;
+}
+
+.textbox-search-clear:not([disabled]) {
+ cursor: default;
+}
+
+.textbox-search-icon:not([disabled]) {
+ cursor: text;
+}
+
+.textbox-search-clear:not([disabled]):hover ,
+.textbox-search-icon:not([disabled]):hover {
+ background-position: -16px 0;
+}
+
+.textbox-search-clear:not([disabled]):hover:active ,
+.textbox-search-icon:not([disabled]):hover:active {
+ background-position: -32px 0;
+}
+
+.client .item.tab > .item-title-container {
+ padding-inline-start: 26px;
+}
+.item.tab > .item-title-container {
+ padding-inline-start: 14px;
+}
+
+.item-icon-container {
+ min-width: 16px;
+ max-width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+ margin-right: 5px;
+ background-size: 16px 16px;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.item-twisty-container {
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ padding-top: 5px;
+ min-width: 9px; /* The image's width is 9 pixels */
+ height: 9px;
+}
+
+.item.client .item-twisty-container {
+ background-image: url("chrome://global/skin/tree/twisty.svg#open");
+}
+
+.item.client.closed .item-twisty-container {
+ background-image: url("chrome://global/skin/tree/twisty.svg#clsd");
+}
+
+.item.client .item-twisty-container:hover {
+ background-image: url("chrome://global/skin/tree/twisty.svg#open-hover");
+}
+
+.item.client.closed .item-twisty-container:hover {
+ background-image: url("chrome://global/skin/tree/twisty.svg#clsd-hover");
+}
+
+.item.client .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/twisty.svg#open-rtl");
+}
+
+.item.client.closed .item-twisty-container:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/twisty.svg#clsd-rtl");
+}
+
+.item.client .item-twisty-container:hover:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/twisty.svg#open-hover-rtl");
+}
+
+.item.client.closed .item-twisty-container:hover:dir(rtl) {
+ background-image: url("chrome://global/skin/tree/twisty.svg#clsd-hover-rtl");
+}
diff --git a/browser/themes/windows/tabbrowser/newtab-XPVista7.svg b/browser/themes/windows/tabbrowser/newtab-XPVista7.svg
new file mode 100644
index 000000000..3f431c9db
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/newtab-XPVista7.svg
@@ -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/. -->
+<svg width="16" height="18" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="gradient" x1="0%" x2="0%" y1="0%" y2="100%">
+ <stop offset="0%" stop-color="#1c2835"/>
+ <stop offset="60%" stop-color="#606e7b"/>
+ <stop offset="100%" stop-color="#465765"/>
+ </linearGradient>
+ </defs>
+ <g fill="url(#gradient)">
+ <rect width="10" height="2" x="3" y="8"/>
+ <rect width="2" height="10" x="7" y="4"/>
+ </g>
+</svg>
diff --git a/browser/themes/windows/tabbrowser/newtab-inverted-XPVista7.svg b/browser/themes/windows/tabbrowser/newtab-inverted-XPVista7.svg
new file mode 100644
index 000000000..10ffbc745
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/newtab-inverted-XPVista7.svg
@@ -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/. -->
+<svg width="16" height="18" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="#666" stroke-width="2" fill="none">
+ <rect x="7" y="4" width="2" height="10" rx="0.25" ry="0.25"/>
+ <rect x="3" y="8" width="10" height="2" rx="0.25" ry="0.25"/>
+ </g>
+ <g fill="#fff">
+ <rect width="2" height="10" x="7" y="4"/>
+ <rect width="10" height="2" x="3" y="8"/>
+ </g>
+</svg>
diff --git a/browser/themes/windows/tabbrowser/newtab-inverted.svg b/browser/themes/windows/tabbrowser/newtab-inverted.svg
new file mode 100644
index 000000000..2728cda5c
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/newtab-inverted.svg
@@ -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/. -->
+<svg width="16" height="18" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="#666" stroke-width="2" fill="none">
+ <rect x="7" y="3" width="2" height="12" rx="0.25" ry="0.25"/>
+ <rect x="2" y="8" width="12" height="2" rx="0.25" ry="0.25"/>
+ </g>
+ <g fill="#fff">
+ <rect x="7" y="3" width="2" height="12"/>
+ <rect x="2" y="8" width="12" height="2"/>
+ </g>
+</svg>
diff --git a/browser/themes/windows/tabbrowser/newtab.svg b/browser/themes/windows/tabbrowser/newtab.svg
new file mode 100644
index 000000000..40548da4a
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/newtab.svg
@@ -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/. -->
+<svg width="16" height="18" xmlns="http://www.w3.org/2000/svg" fill="#4c4c4c">
+ <rect x="7" y="3" width="2" height="12"/>
+ <rect x="2" y="8" width="12" height="2"/>
+</svg>
diff --git a/browser/themes/windows/tabbrowser/tab-active-middle.png b/browser/themes/windows/tabbrowser/tab-active-middle.png
new file mode 100644
index 000000000..b7e6d6f77
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-active-middle.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-active-middle@2x.png b/browser/themes/windows/tabbrowser/tab-active-middle@2x.png
new file mode 100644
index 000000000..1e92acbda
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-active-middle@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-arrow-left-XPVista7.svg b/browser/themes/windows/tabbrowser/tab-arrow-left-XPVista7.svg
new file mode 100644
index 000000000..41bb5ab13
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-arrow-left-XPVista7.svg
@@ -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/. -->
+<svg width="15" height="17" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="gradient" x1="0%" x2="0%" y1="0%" y2="100%">
+ <stop offset="0%" stop-color="#1c2835"/>
+ <stop offset="60%" stop-color="#606e7b"/>
+ <stop offset="100%" stop-color="#465765"/>
+ </linearGradient>
+ </defs>
+ <path d="M11 4L9.5 2.5l-5.875 6 5.875 6L11 13 6.625 8.5z" fill="url(#gradient)"/>
+</svg>
diff --git a/browser/themes/windows/tabbrowser/tab-arrow-left-inverted.svg b/browser/themes/windows/tabbrowser/tab-arrow-left-inverted.svg
new file mode 100644
index 000000000..13d3beb95
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-arrow-left-inverted.svg
@@ -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/. -->
+<svg width="15" height="17" xmlns="http://www.w3.org/2000/svg">
+ <path fill="#666" stroke-width="1.5" stroke="#666" d="M11 4L9.5 2.5l-5.875 6 5.875 6L11 13 6.625 8.5z"/>
+ <path fill="#fff" d="M11 4L9.5 2.5l-5.875 6 5.875 6L11 13 6.625 8.5z"/>
+</svg>
diff --git a/browser/themes/windows/tabbrowser/tab-arrow-left.svg b/browser/themes/windows/tabbrowser/tab-arrow-left.svg
new file mode 100644
index 000000000..9bd59ddd3
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-arrow-left.svg
@@ -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/. -->
+<svg width="15" height="17" xmlns="http://www.w3.org/2000/svg">
+ <path d="M11 4L9.5 2.5l-5.875 6 5.875 6L11 13 6.625 8.5z" fill="#4c4c4c"/>
+</svg>
diff --git a/browser/themes/windows/tabbrowser/tab-background-end-preWin10.png b/browser/themes/windows/tabbrowser/tab-background-end-preWin10.png
new file mode 100644
index 000000000..fb353b17e
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-end-preWin10.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-end-preWin10@2x.png b/browser/themes/windows/tabbrowser/tab-background-end-preWin10@2x.png
new file mode 100644
index 000000000..eefb6ac47
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-end-preWin10@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-end.png b/browser/themes/windows/tabbrowser/tab-background-end.png
new file mode 100644
index 000000000..d68ea6da6
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-end.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-end@2x.png b/browser/themes/windows/tabbrowser/tab-background-end@2x.png
new file mode 100644
index 000000000..8ed84ab37
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-end@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-middle-preWin10.png b/browser/themes/windows/tabbrowser/tab-background-middle-preWin10.png
new file mode 100644
index 000000000..51e066c2e
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-middle-preWin10.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-middle-preWin10@2x.png b/browser/themes/windows/tabbrowser/tab-background-middle-preWin10@2x.png
new file mode 100644
index 000000000..b26cb95de
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-middle-preWin10@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-middle.png b/browser/themes/windows/tabbrowser/tab-background-middle.png
new file mode 100644
index 000000000..faaf7e38e
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-middle.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-middle@2x.png b/browser/themes/windows/tabbrowser/tab-background-middle@2x.png
new file mode 100644
index 000000000..c9d245f4f
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-middle@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-start-preWin10.png b/browser/themes/windows/tabbrowser/tab-background-start-preWin10.png
new file mode 100644
index 000000000..cf0dc852a
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-start-preWin10.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-start-preWin10@2x.png b/browser/themes/windows/tabbrowser/tab-background-start-preWin10@2x.png
new file mode 100644
index 000000000..bbfc77dd1
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-start-preWin10@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-start.png b/browser/themes/windows/tabbrowser/tab-background-start.png
new file mode 100644
index 000000000..d1f0b5561
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-start.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-background-start@2x.png b/browser/themes/windows/tabbrowser/tab-background-start@2x.png
new file mode 100644
index 000000000..e860275a6
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-background-start@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-stroke-end.png b/browser/themes/windows/tabbrowser/tab-stroke-end.png
new file mode 100644
index 000000000..2aa5711f8
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-stroke-end.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-stroke-end@2x.png b/browser/themes/windows/tabbrowser/tab-stroke-end@2x.png
new file mode 100644
index 000000000..a87002a83
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-stroke-end@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-stroke-start.png b/browser/themes/windows/tabbrowser/tab-stroke-start.png
new file mode 100644
index 000000000..4e4e41f63
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-stroke-start.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tab-stroke-start@2x.png b/browser/themes/windows/tabbrowser/tab-stroke-start@2x.png
new file mode 100644
index 000000000..13bd6add1
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tab-stroke-start@2x.png
Binary files differ
diff --git a/browser/themes/windows/tabbrowser/tabDragIndicator.png b/browser/themes/windows/tabbrowser/tabDragIndicator.png
new file mode 100644
index 000000000..63c4eccad
--- /dev/null
+++ b/browser/themes/windows/tabbrowser/tabDragIndicator.png
Binary files differ
diff --git a/browser/themes/windows/toolbarbutton-dropdown-arrow-XPVista7.png b/browser/themes/windows/toolbarbutton-dropdown-arrow-XPVista7.png
new file mode 100644
index 000000000..5f892f532
--- /dev/null
+++ b/browser/themes/windows/toolbarbutton-dropdown-arrow-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/toolbarbutton-dropdown-arrow-inverted.png b/browser/themes/windows/toolbarbutton-dropdown-arrow-inverted.png
new file mode 100644
index 000000000..af4c7af0b
--- /dev/null
+++ b/browser/themes/windows/toolbarbutton-dropdown-arrow-inverted.png
Binary files differ
diff --git a/browser/themes/windows/urlbar-history-dropmarker-XPVista7.png b/browser/themes/windows/urlbar-history-dropmarker-XPVista7.png
new file mode 100644
index 000000000..b03338822
--- /dev/null
+++ b/browser/themes/windows/urlbar-history-dropmarker-XPVista7.png
Binary files differ
diff --git a/browser/themes/windows/urlbar-history-dropmarker-XPVista7@2x.png b/browser/themes/windows/urlbar-history-dropmarker-XPVista7@2x.png
new file mode 100644
index 000000000..bff2997f8
--- /dev/null
+++ b/browser/themes/windows/urlbar-history-dropmarker-XPVista7@2x.png
Binary files differ
diff --git a/browser/themes/windows/urlbar-history-dropmarker.png b/browser/themes/windows/urlbar-history-dropmarker.png
new file mode 100644
index 000000000..01432f7e9
--- /dev/null
+++ b/browser/themes/windows/urlbar-history-dropmarker.png
Binary files differ
diff --git a/browser/themes/windows/urlbar-history-dropmarker@2x.png b/browser/themes/windows/urlbar-history-dropmarker@2x.png
new file mode 100644
index 000000000..6e710139a
--- /dev/null
+++ b/browser/themes/windows/urlbar-history-dropmarker@2x.png
Binary files differ
diff --git a/browser/themes/windows/urlbar-popup-blocked.png b/browser/themes/windows/urlbar-popup-blocked.png
new file mode 100644
index 000000000..56dbd2d0d
--- /dev/null
+++ b/browser/themes/windows/urlbar-popup-blocked.png
Binary files differ
diff --git a/browser/themes/windows/webRTC-indicator.css b/browser/themes/windows/webRTC-indicator.css
new file mode 100644
index 000000000..c22f942ec
--- /dev/null
+++ b/browser/themes/windows/webRTC-indicator.css
@@ -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/. */
+
+window {
+ border: 1px solid #ff9500;
+}
+
+#audioVideoButton,
+#screenShareButton,
+#firefoxButton {
+ height: 29px;
+ margin: 0;
+ -moz-appearance: none;
+ border-style: none;
+}
+
+#firefoxButton {
+ background-image: url("chrome://branding/content/icon48.png");
+ background-repeat: no-repeat;
+ background-size: 22px;
+ background-position: center center;
+ min-width: 29px;
+ background-color: white;
+}
+
+#firefoxButton:hover {
+ background-color: #f2f2f2;
+}
+
+#screenShareButton {
+ background-image: url("webRTC-screen-white-16.png");
+ background-position: center center;
+ background-repeat: no-repeat;
+ background-size: 16px;
+ min-width: 27px;
+ display: none;
+}
+
+window[sharingscreen] > #screenShareButton {
+ display: -moz-box;
+}
+
+#audioVideoButton {
+ display: none;
+ background-repeat: no-repeat;
+}
+
+/* When screen sharing, need to pull in the separator: */
+window[sharingscreen] > #audioVideoButton {
+ margin-right: -1px;
+}
+
+/* Single icon button: */
+window[sharingvideo] > #audioVideoButton,
+window[sharingaudio] > #audioVideoButton {
+ display: -moz-box;
+ background-position: center center;
+ background-size: 16px;
+ min-width: 26px;
+}
+
+window[sharingvideo] > #audioVideoButton {
+ background-image: url("webRTC-camera-white-16.png");
+}
+
+window[sharingaudio] > #audioVideoButton {
+ background-image: url("webRTC-microphone-white-16.png");
+}
+
+/* Multi-icon button: */
+window[sharingaudio][sharingvideo] > #audioVideoButton {
+ background-image: url("webRTC-camera-white-16.png"),
+ url("webRTC-microphone-white-16.png");
+ background-position: 6px center, 26px center;
+ background-size: 16px, 16px;
+ min-width: 46px;
+}
+
+/* Hover styles */
+#audioVideoButton,
+#screenShareButton {
+ background-color: #ffaa33;
+}
+
+#audioVideoButton:hover,
+#screenShareButton:hover {
+ background-color: #ff9500;
+}
+
+/* Don't show the dropmarker for the type="menu" case */
+#audioVideoButton > .box-inherit > .button-menu-dropmarker,
+#screenShareButton > .box-inherit > .button-menu-dropmarker {
+ display: none;
+}
+
+/* Separator in case of screen sharing + video/audio sharing */
+#shareSeparator {
+ width: 1px;
+ margin: 4px -1px 4px 0;
+ background-color: #FFCA80;
+ /* Separator needs to show above either button when they're hovered: */
+ position: relative;
+ z-index: 1;
+ display: none;
+}
+
+window[sharingscreen][sharingvideo] > #shareSeparator,
+window[sharingscreen][sharingaudio] > #shareSeparator {
+ display: -moz-box;
+}
+
+:-moz-any(#audioVideoButton, #screenShareButton,
+ #firefoxButton):-moz-focusring > .button-box {
+ border: none;
+}
diff --git a/browser/themes/windows/windowsShared.inc b/browser/themes/windows/windowsShared.inc
new file mode 100644
index 000000000..0cb2ab163
--- /dev/null
+++ b/browser/themes/windows/windowsShared.inc
@@ -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/. */
+
+%filter substitution
+
+%define toolbarHighlight rgba(255,255,255,.4)
+%define fgTabTexture linear-gradient(transparent 2px, @toolbarHighlight@ 2px, @toolbarHighlight@)
+%define fgTabBackgroundColor -moz-dialog
+%define fgTabTextureLWT @fgTabTexture@
+
+% Aero-only defines
+%define customToolbarColor hsl(210,75%,92%)
diff --git a/browser/tools/mozscreenshots/.eslintrc.js b/browser/tools/mozscreenshots/.eslintrc.js
new file mode 100644
index 000000000..55b05398e
--- /dev/null
+++ b/browser/tools/mozscreenshots/.eslintrc.js
@@ -0,0 +1,15 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": [
+ "../../../testing/mochitest/browser.eslintrc.js"
+ ],
+
+ "rules": {
+ "no-unused-vars": ["error", {
+ "vars": "all",
+ "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
+ "args": "none"
+ }]
+ }
+};
diff --git a/browser/tools/mozscreenshots/browser.ini b/browser/tools/mozscreenshots/browser.ini
new file mode 100644
index 000000000..e72a3acb4
--- /dev/null
+++ b/browser/tools/mozscreenshots/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+subsuite = screenshots
+support-files =
+ head.js
+ mozscreenshots/extension/lib/permissionPrompts.html
+ mozscreenshots/extension/lib/controlCenter/password.html
+ mozscreenshots/extension/lib/controlCenter/mixed.html
+ mozscreenshots/extension/lib/controlCenter/mixed_active.html
+ mozscreenshots/extension/lib/controlCenter/mixed_passive.html
+ mozscreenshots/extension/lib/borderify.xpi
+
+[browser_screenshots.js]
diff --git a/browser/tools/mozscreenshots/browser_screenshots.js b/browser/tools/mozscreenshots/browser_screenshots.js
new file mode 100644
index 000000000..502f90fec
--- /dev/null
+++ b/browser/tools/mozscreenshots/browser_screenshots.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";
+
+add_task(function* capture() {
+ let setsEnv = env.get("MOZSCREENSHOTS_SETS");
+ if (!setsEnv) {
+ ok(true, "MOZSCREENSHOTS_SETS wasn't specified so there's nothing to capture");
+ return;
+ }
+
+ let sets = setsEnv.trim().split(",");
+ yield TestRunner.start(sets);
+});
diff --git a/browser/tools/mozscreenshots/controlCenter/browser.ini b/browser/tools/mozscreenshots/controlCenter/browser.ini
new file mode 100644
index 000000000..f5b084edc
--- /dev/null
+++ b/browser/tools/mozscreenshots/controlCenter/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = screenshots
+support-files =
+ ../head.js
+
+[browser_controlCenter.js]
diff --git a/browser/tools/mozscreenshots/controlCenter/browser_controlCenter.js b/browser/tools/mozscreenshots/controlCenter/browser_controlCenter.js
new file mode 100644
index 000000000..7e1ffb125
--- /dev/null
+++ b/browser/tools/mozscreenshots/controlCenter/browser_controlCenter.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/. */
+
+/* import-globals-from ../head.js */
+
+"use strict";
+
+add_task(function* capture() {
+ if (!shouldCapture()) {
+ return;
+ }
+ let sets = ["LightweightThemes", "ControlCenter"];
+
+ yield TestRunner.start(sets, "controlCenter");
+});
diff --git a/browser/tools/mozscreenshots/devtools/browser.ini b/browser/tools/mozscreenshots/devtools/browser.ini
new file mode 100644
index 000000000..e4fa0a988
--- /dev/null
+++ b/browser/tools/mozscreenshots/devtools/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = screenshots
+support-files =
+ ../head.js
+
+[browser_devtools.js]
diff --git a/browser/tools/mozscreenshots/devtools/browser_devtools.js b/browser/tools/mozscreenshots/devtools/browser_devtools.js
new file mode 100644
index 000000000..a47548958
--- /dev/null
+++ b/browser/tools/mozscreenshots/devtools/browser_devtools.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/. */
+
+/* import-globals-from ../head.js */
+
+"use strict";
+
+add_task(function* capture() {
+ if (!shouldCapture()) {
+ return;
+ }
+ let sets = ["DevTools"];
+
+ yield TestRunner.start(sets, "devtools");
+});
diff --git a/browser/tools/mozscreenshots/head.js b/browser/tools/mozscreenshots/head.js
new file mode 100644
index 000000000..c1702ab08
--- /dev/null
+++ b/browser/tools/mozscreenshots/head.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/. */
+
+/* exported TestRunner, shouldCapture */
+
+"use strict";
+
+const {AddonWatcher} = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
+const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
+const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+const EXTENSION_DIR = "chrome://mochitests/content/extensions/mozscreenshots/browser/";
+
+let TestRunner;
+
+function* setup() {
+ requestLongerTimeout(10);
+
+ info("installing extension temporarily");
+ let chromeURL = Services.io.newURI(EXTENSION_DIR, null, null);
+ let dir = chromeRegistry.convertChromeURL(chromeURL).QueryInterface(Ci.nsIFileURL).file;
+ yield AddonManager.installTemporaryAddon(dir);
+
+ info("Checking for mozscreenshots extension");
+ return new Promise((resolve) => {
+ AddonManager.getAddonByID("mozscreenshots@mozilla.org", function(aAddon) {
+ isnot(aAddon, null, "The mozscreenshots extension should be installed");
+ AddonWatcher.ignoreAddonPermanently(aAddon.id);
+ TestRunner = Cu.import("chrome://mozscreenshots/content/TestRunner.jsm", {}).TestRunner;
+ resolve();
+ });
+ });
+}
+
+function shouldCapture() {
+ // Try pushes only capture in browser_screenshots.js with MOZSCREENSHOTS_SETS.
+ if (env.get("MOZSCREENSHOTS_SETS")) {
+ ok(true, "MOZSCREENSHOTS_SETS was specified so only capture what was " +
+ "requested (in browser_screenshots.js)");
+ return false;
+ }
+
+ // Automation isn't able to schedule test jobs to only run on nightlies so we handle it here
+ // (see also: bug 1116275).
+ let capture = AppConstants.MOZ_UPDATE_CHANNEL == "nightly" ||
+ AppConstants.SOURCE_REVISION_URL == "";
+ if (!capture) {
+ ok(true, "Capturing is disabled for this MOZ_UPDATE_CHANNEL or REPO");
+ }
+ return capture;
+}
+
+add_task(setup);
diff --git a/browser/tools/mozscreenshots/moz.build b/browser/tools/mozscreenshots/moz.build
new file mode 100644
index 000000000..0c8b98b6a
--- /dev/null
+++ b/browser/tools/mozscreenshots/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ # Each test is in it's own directory so it gets run in a clean profile with
+ # run-by-dir.
+ 'browser.ini',
+ 'controlCenter/browser.ini',
+ 'devtools/browser.ini',
+ 'permissionPrompts/browser.ini',
+ 'preferences/browser.ini',
+ 'primaryUI/browser.ini',
+]
+
+TEST_DIRS += [
+ 'mozscreenshots/extension',
+]
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/Makefile.in b/browser/tools/mozscreenshots/mozscreenshots/extension/Makefile.in
new file mode 100644
index 000000000..b0004aaa7
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/Makefile.in
@@ -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/.
+
+TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
+GENERATED_DIRS = $(TEST_EXTENSIONS_DIR)
+XPI_PKGNAME = mozscreenshots@mozilla.org
+
+include $(topsrcdir)/config/rules.mk
+
+libs::
+ (cd $(DIST)/xpi-stage && tar $(TAR_CREATE_FLAGS) - $(XPI_NAME)) | (cd $(TEST_EXTENSIONS_DIR) && tar -xf -)
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/Screenshot.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/Screenshot.jsm
new file mode 100644
index 000000000..c43514e94
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/Screenshot.jsm
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Screenshot"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
+// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
+const PREF_LOG_LEVEL = "extensions.mozscreenshots@mozilla.org.loglevel";
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
+ let consoleOptions = {
+ maxLogLevel: "info",
+ maxLogLevelPref: PREF_LOG_LEVEL,
+ prefix: "mozscreenshots",
+ };
+ return new ConsoleAPI(consoleOptions);
+});
+
+this.Screenshot = {
+ _extensionPath: null,
+ _path: null,
+ _imagePrefix: "",
+ _imageExtension: ".png",
+ _screenshotFunction: null,
+
+ init(path, extensionPath, imagePrefix = "") {
+ this._path = path;
+
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath(this._path);
+ if (!dir.exists()) {
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+
+ this._extensionPath = extensionPath;
+ this._imagePrefix = imagePrefix;
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ this._screenshotFunction = this._screenshotWindows;
+ break;
+ case "Darwin":
+ this._screenshotFunction = this._screenshotOSX;
+ break;
+ case "Linux":
+ this._screenshotFunction = this._screenshotLinux;
+ break;
+ default:
+ throw new Error("Unsupported operating system");
+ }
+ },
+
+ _buildImagePath(baseName) {
+ return OS.Path.join(this._path, this._imagePrefix + baseName + this._imageExtension);
+ },
+
+ // Capture the whole screen using an external application.
+ captureExternal(filename) {
+ let imagePath = this._buildImagePath(filename);
+ return this._screenshotFunction(imagePath).then(() => {
+ log.debug("saved screenshot: " + filename);
+ });
+ },
+
+ // helpers
+
+ _screenshotWindows(filename) {
+ return new Promise((resolve, reject) => {
+ let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+ exe.append("screenshot.exe");
+ if (!exe.exists()) {
+ exe = Services.dirsvc.get("CurWorkD", Ci.nsIFile).parent;
+ exe.append("bin");
+ exe.append("screenshot.exe");
+ }
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(exe);
+
+ let args = [filename];
+ process.runAsync(args, args.length, this._processObserver(resolve, reject));
+ });
+ },
+
+ _screenshotOSX: Task.async(function*(filename) {
+ let screencapture = (windowID = null) => {
+ return new Promise((resolve, reject) => {
+ // Get the screencapture executable
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath("/usr/sbin/screencapture");
+
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ // Run the process.
+ let args = ["-x", "-t", "png"];
+ // Darwin version number for OS X 10.6 is 10.x
+ if (windowID && Services.sysinfo.getProperty("version").indexOf("10.") !== 0) {
+ // Capture only that window on 10.7+
+ args.push("-l");
+ args.push(windowID);
+ }
+ args.push(filename);
+ process.runAsync(args, args.length, this._processObserver(resolve, reject));
+ });
+ };
+
+ function readWindowID() {
+ let decoder = new TextDecoder();
+ let promise = OS.File.read("/tmp/mozscreenshots-windowid");
+ return promise.then(function onSuccess(array) {
+ return decoder.decode(array);
+ });
+ }
+
+ let promiseWindowID = () => {
+ return new Promise((resolve, reject) => {
+ // Get the window ID of the application (assuming its front-most)
+ let osascript = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ osascript.initWithPath("/bin/bash");
+
+ let osascriptP = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ osascriptP.init(osascript);
+ let osaArgs = ["-c", "/usr/bin/osascript -e 'tell application (path to frontmost application as text) to set winID to id of window 1' > /tmp/mozscreenshots-windowid"];
+ osascriptP.runAsync(osaArgs, osaArgs.length, this._processObserver(resolve, reject));
+ });
+ };
+
+ yield promiseWindowID();
+ let windowID = yield readWindowID();
+ yield screencapture(windowID);
+ }),
+
+ _screenshotLinux(filename) {
+ return new Promise((resolve, reject) => {
+ let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+ exe.append("screentopng");
+ if (!exe.exists()) {
+ exe = Services.dirsvc.get("CurWorkD", Ci.nsIFile).parent;
+ exe.append("bin");
+ exe.append("screentopng");
+ }
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(exe);
+
+ let args = [filename];
+ process.runAsync(args, args.length, this._processObserver(resolve, reject));
+ });
+ },
+
+ _processObserver(resolve, reject) {
+ return {
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "process-finished":
+ try {
+ // Wait 1s after process to resolve
+ setTimeout(resolve, 1000);
+ } catch (ex) {
+ reject(ex);
+ }
+ break;
+ default:
+ reject(topic);
+ break;
+ }
+ },
+ };
+ },
+};
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
new file mode 100644
index 000000000..557b867b9
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
@@ -0,0 +1,279 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["TestRunner"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+const APPLY_CONFIG_TIMEOUT_MS = 60 * 1000;
+const HOME_PAGE = "chrome://mozscreenshots/content/lib/mozscreenshots.html";
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
+ "resource://testing-common/BrowserTestUtils.jsm");
+
+Cu.import("chrome://mozscreenshots/content/Screenshot.jsm");
+
+// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
+// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
+const PREF_LOG_LEVEL = "extensions.mozscreenshots@mozilla.org.loglevel";
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
+ let consoleOptions = {
+ maxLogLevel: "info",
+ maxLogLevelPref: PREF_LOG_LEVEL,
+ prefix: "mozscreenshots",
+ };
+ return new ConsoleAPI(consoleOptions);
+});
+
+this.TestRunner = {
+ combos: null,
+ completedCombos: 0,
+ currentComboIndex: 0,
+ _lastCombo: null,
+ _libDir: null,
+
+ init(extensionPath) {
+ log.debug("init");
+ this._extensionPath = extensionPath;
+ },
+
+ /**
+ * Load specified sets, execute all combinations of them, and capture screenshots.
+ */
+ start: Task.async(function*(setNames, jobName = null) {
+ let subDirs = ["mozscreenshots",
+ (new Date()).toISOString().replace(/:/g, "-") + "_" + Services.appinfo.OS];
+ let screenshotPath = FileUtils.getFile("TmpD", subDirs).path;
+
+ const MOZ_UPLOAD_DIR = env.get("MOZ_UPLOAD_DIR");
+ if (MOZ_UPLOAD_DIR) {
+ screenshotPath = MOZ_UPLOAD_DIR;
+ }
+
+ log.info("Saving screenshots to:", screenshotPath);
+
+ let screenshotPrefix = Services.appinfo.appBuildID;
+ if (jobName) {
+ screenshotPrefix += "-" + jobName;
+ }
+ screenshotPrefix += "_";
+ Screenshot.init(screenshotPath, this._extensionPath, screenshotPrefix);
+ this._libDir = this._extensionPath.QueryInterface(Ci.nsIFileURL).file.clone();
+ this._libDir.append("chrome");
+ this._libDir.append("mozscreenshots");
+ this._libDir.append("lib");
+
+ let sets = this.loadSets(setNames);
+
+ log.info(sets.length + " sets:", setNames);
+ this.combos = new LazyProduct(sets);
+ log.info(this.combos.length + " combinations");
+
+ this.currentComboIndex = this.completedCombos = 0;
+ this._lastCombo = null;
+
+ // Setup some prefs
+ Services.prefs.setCharPref("browser.aboutHomeSnippets.updateUrl",
+ "data:text/html;charset=utf-8,Generated by mozscreenshots");
+ Services.prefs.setCharPref("extensions.ui.lastCategory", "addons://list/extension");
+ // Don't let the caret blink since it causes false positives for image diffs
+ Services.prefs.setIntPref("ui.caretBlinkTime", -1);
+
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let selectedBrowser = browserWindow.gBrowser.selectedBrowser;
+ yield BrowserTestUtils.loadURI(selectedBrowser, HOME_PAGE);
+ yield BrowserTestUtils.browserLoaded(selectedBrowser);
+
+ for (let i = 0; i < this.combos.length; i++) {
+ this.currentComboIndex = i;
+ yield this._performCombo(this.combos.item(this.currentComboIndex));
+ }
+
+ log.info("Done: Completed " + this.completedCombos + " out of " +
+ this.combos.length + " configurations.");
+ this.cleanup();
+ }),
+
+ /**
+ * Load sets of configurations from JSMs.
+ * @param {String[]} setNames - array of set names (e.g. ["Tabs", "WindowSize"].
+ * @return {Object[]} Array of sets containing `name` and `configurations` properties.
+ */
+ loadSets(setNames) {
+ let sets = [];
+ for (let setName of setNames) {
+ try {
+ let imported = {};
+ Cu.import("chrome://mozscreenshots/content/configurations/" + setName + ".jsm",
+ imported);
+ imported[setName].init(this._libDir);
+ let configurationNames = Object.keys(imported[setName].configurations);
+ if (!configurationNames.length) {
+ throw new Error(setName + " has no configurations for this environment");
+ }
+ for (let config of configurationNames) {
+ // Automatically set the name property of the configuration object to
+ // its name from the configuration object.
+ imported[setName].configurations[config].name = config;
+ }
+ sets.push(imported[setName].configurations);
+ } catch (ex) {
+ log.error("Error loading set: " + setName);
+ log.error(ex);
+ throw ex;
+ }
+ }
+ return sets;
+ },
+
+ cleanup() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+ }
+ gBrowser.unpinTab(gBrowser.selectedTab);
+ gBrowser.selectedBrowser.loadURI("data:text/html;charset=utf-8,<h1>Done!");
+ browserWindow.restore();
+ },
+
+ // helpers
+
+ _performCombo: function*(combo) {
+ let paddedComboIndex = padLeft(this.currentComboIndex + 1, String(this.combos.length).length);
+ log.info("Combination " + paddedComboIndex + "/" + this.combos.length + ": " +
+ this._comboName(combo).substring(1));
+
+ function changeConfig(config) {
+ log.debug("calling " + config.name);
+ let applyPromise = Promise.resolve(config.applyConfig());
+ let timeoutPromise = new Promise((resolve, reject) => {
+ setTimeout(reject, APPLY_CONFIG_TIMEOUT_MS, "Timed out");
+ });
+ log.debug("called " + config.name);
+ // Add a default timeout of 500ms to avoid conflicts when configurations
+ // try to apply at the same time. e.g WindowSize and TabsInTitlebar
+ return Promise.race([applyPromise, timeoutPromise]).then(() => {
+ return new Promise((resolve) => {
+ setTimeout(resolve, 500);
+ });
+ });
+ }
+
+ try {
+ // First go through and actually apply all of the configs
+ for (let i = 0; i < combo.length; i++) {
+ let config = combo[i];
+ if (!this._lastCombo || config !== this._lastCombo[i]) {
+ log.debug("promising", config.name);
+ yield changeConfig(config);
+ }
+ }
+
+ // Update the lastCombo since it's now been applied regardless of whether it's accepted below.
+ log.debug("fulfilled all applyConfig so setting lastCombo.");
+ this._lastCombo = combo;
+
+ // Then ask configs if the current setup is valid. We can't can do this in
+ // the applyConfig methods of the config since it doesn't know what configs
+ // later in the loop will do that may invalidate the combo.
+ for (let i = 0; i < combo.length; i++) {
+ let config = combo[i];
+ // A configuration can specify an optional verifyConfig method to indicate
+ // if the current config is valid for a screenshot. This gets called even
+ // if the this config was used in the lastCombo since another config may
+ // have invalidated it.
+ if (config.verifyConfig) {
+ log.debug("checking if the combo is valid with", config.name);
+ yield config.verifyConfig();
+ }
+ }
+ } catch (ex) {
+ log.warn("\tskipped configuration: " + ex);
+ // Don't set lastCombo here so that we properly know which configurations
+ // need to be applied since the last screenshot
+
+ // Return so we don't take a screenshot.
+ return;
+ }
+
+ yield this._onConfigurationReady(combo);
+ },
+
+ _onConfigurationReady(combo) {
+ let delayedScreenshot = () => {
+ let filename = padLeft(this.currentComboIndex + 1,
+ String(this.combos.length).length) + this._comboName(combo);
+ return Screenshot.captureExternal(filename)
+ .then(() => {
+ this.completedCombos++;
+ });
+ };
+
+ log.debug("_onConfigurationReady");
+ return Task.spawn(delayedScreenshot);
+ },
+
+ _comboName(combo) {
+ return combo.reduce(function(a, b) {
+ return a + "_" + b.name;
+ }, "");
+ },
+};
+
+/**
+ * Helper to lazily compute the Cartesian product of all of the sets of configurations.
+ **/
+function LazyProduct(sets) {
+ /**
+ * An entry for each set with the value being:
+ * [the number of permutations of the sets with lower index,
+ * the number of items in the set at the index]
+ */
+ this.sets = sets;
+ this.lookupTable = [];
+ let combinations = 1;
+ for (let i = this.sets.length - 1; i >= 0; i--) {
+ let set = this.sets[i];
+ let setLength = Object.keys(set).length;
+ this.lookupTable[i] = [combinations, setLength];
+ combinations *= setLength;
+ }
+}
+LazyProduct.prototype = {
+ get length() {
+ let last = this.lookupTable[0];
+ if (!last)
+ return 0;
+ return last[0] * last[1];
+ },
+
+ item(n) {
+ // For set i, get the item from the set with the floored value of
+ // (n / the number of permutations of the sets already chosen from) modulo the length of set i
+ let result = [];
+ for (let i = this.sets.length - 1; i >= 0; i--) {
+ let priorCombinations = this.lookupTable[i][0];
+ let setLength = this.lookupTable[i][1];
+ let keyIndex = Math.floor(n / priorCombinations) % setLength;
+ let keys = Object.keys(this.sets[i]);
+ result[i] = this.sets[i][keys[keyIndex]];
+ }
+ return result;
+ },
+};
+
+function padLeft(number, width, padding = "0") {
+ return padding.repeat(Math.max(0, width - String(number).length)) + number;
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/bootstrap.js b/browser/tools/mozscreenshots/mozscreenshots/extension/bootstrap.js
new file mode 100644
index 000000000..c7e238606
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/bootstrap.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/. */
+/*
+#if 0
+Workaround a build system bug where this file doesn't get packaged if not pre-processed.
+#endif
+*/
+
+/* exported install, uninstall, startup, shutdown */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "TestRunner",
+ "chrome://mozscreenshots/content/TestRunner.jsm");
+
+function install(data, reason) {
+ if (!isAppSupported()) {
+ uninstallExtension(data);
+ return;
+ }
+
+ AddonManager.getAddonByID(data.id, function(addon) {
+ // Enable on install in case the user disabled a prior version
+ if (addon) {
+ addon.userDisabled = false;
+ }
+ });
+}
+
+function startup(data, reason) {
+ if (!isAppSupported()) {
+ uninstallExtension(data);
+ return;
+ }
+
+ AddonManager.getAddonByID(data.id, function(addon) {
+ let extensionPath = addon.getResourceURI();
+ TestRunner.init(extensionPath);
+ });
+}
+
+function shutdown(data, reason) { }
+
+function uninstall(data, reason) { }
+
+/**
+ * @return boolean whether the test suite applies to the application.
+ */
+function isAppSupported() {
+ return true;
+}
+
+function uninstallExtension(data) {
+ AddonManager.getAddonByID(data.id, function(addon) {
+ addon.uninstall();
+ });
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/AppMenu.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/AppMenu.jsm
new file mode 100644
index 000000000..5dbb2c1e2
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/AppMenu.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["AppMenu"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+this.AppMenu = {
+
+ init(libDir) {},
+
+ configurations: {
+ appMenuClosed: {
+ applyConfig: Task.async(function*() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ browserWindow.PanelUI.hide();
+ }),
+ },
+
+ appMenuMainView: {
+ applyConfig() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let promise = browserWindow.PanelUI.show();
+ browserWindow.PanelUI.showMainView();
+ return promise;
+ },
+ },
+
+ appMenuHistorySubview: {
+ applyConfig() {
+ // History has a footer
+ if (isCustomizing()) {
+ return Promise.reject("Can't show subviews while customizing");
+ }
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let promise = browserWindow.PanelUI.show();
+ return promise.then(() => {
+ browserWindow.PanelUI.showMainView();
+ browserWindow.document.getElementById("history-panelmenu").click();
+ });
+ },
+
+ verifyConfig: verifyConfigHelper,
+ },
+
+ appMenuHelpSubview: {
+ applyConfig() {
+ if (isCustomizing()) {
+ return Promise.reject("Can't show subviews while customizing");
+ }
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let promise = browserWindow.PanelUI.show();
+ return promise.then(() => {
+ browserWindow.PanelUI.showMainView();
+ browserWindow.document.getElementById("PanelUI-help").click();
+ });
+ },
+
+ verifyConfig: verifyConfigHelper,
+ },
+
+ },
+};
+
+function verifyConfigHelper() {
+ if (isCustomizing()) {
+ return Promise.reject("AppMenu verifyConfigHelper");
+ }
+ return Promise.resolve("AppMenu verifyConfigHelper");
+}
+
+function isCustomizing() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (browserWindow.document.documentElement.hasAttribute("customizing")) {
+ return true;
+ }
+ return false;
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Buttons.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Buttons.jsm
new file mode 100644
index 000000000..97d8354d5
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Buttons.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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["Buttons"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource:///modules/CustomizableUI.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+this.Buttons = {
+
+ init(libDir) {
+ createWidget();
+ },
+
+ configurations: {
+ navBarButtons: {
+ applyConfig: Task.async(() => {
+ CustomizableUI.addWidgetToArea("screenshot-widget", CustomizableUI.AREA_NAVBAR);
+ }),
+ },
+
+ tabsToolbarButtons: {
+ applyConfig: Task.async(() => {
+ CustomizableUI.addWidgetToArea("screenshot-widget", CustomizableUI.AREA_TABSTRIP);
+ }),
+ },
+
+ menuPanelButtons: {
+ applyConfig: Task.async(() => {
+ CustomizableUI.addWidgetToArea("screenshot-widget", CustomizableUI.AREA_PANEL);
+ }),
+
+ verifyConfig() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (browserWindow.PanelUI.panel.state == "closed") {
+ return Promise.reject("The button isn't shown when the panel isn't open.");
+ }
+ return Promise.resolve("menuPanelButtons.verifyConfig");
+ },
+ },
+
+ custPaletteButtons: {
+ applyConfig: Task.async(() => {
+ CustomizableUI.removeWidgetFromArea("screenshot-widget");
+ }),
+
+ verifyConfig() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (browserWindow.document.documentElement.getAttribute("customizing") != "true") {
+ return Promise.reject("The button isn't shown when we're not in customize mode.");
+ }
+ return Promise.resolve("custPaletteButtons.verifyConfig");
+ },
+ },
+ },
+};
+
+function createWidget() {
+ let id = "screenshot-widget";
+ let spec = {
+ id: id,
+ label: "My Button",
+ removable: true,
+ tooltiptext: "",
+ type: "button",
+ };
+ CustomizableUI.createWidget(spec);
+
+ // Append a <style> for the image
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let st = browserWindow.document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ let styles = "" +
+ "#screenshot-widget > .toolbarbutton-icon {" +
+ " list-style-image: url(chrome://browser/skin/Toolbar.png);" +
+ " -moz-image-region: rect(0px, 18px, 18px, 0px);" +
+ "}";
+ st.appendChild(browserWindow.document.createTextNode(styles));
+ browserWindow.document.documentElement.appendChild(st);
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
new file mode 100644
index 000000000..ed4d92b4f
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
@@ -0,0 +1,243 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["ControlCenter"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+Cu.import("resource:///modules/SitePermissions.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+let {UrlClassifierTestUtils} = Cu.import("resource://testing-common/UrlClassifierTestUtils.jsm", {});
+
+const RESOURCE_PATH = "extensions/mozscreenshots/browser/chrome/mozscreenshots/lib/controlCenter";
+const HTTP_PAGE = "http://example.com/";
+const HTTPS_PAGE = "https://example.com/";
+const PERMISSIONS_PAGE = "https://test1.example.com/";
+const HTTP_PASSWORD_PAGE = `http://test2.example.org/${RESOURCE_PATH}/password.html`;
+const MIXED_CONTENT_URL = `https://example.com/${RESOURCE_PATH}/mixed.html`;
+const MIXED_ACTIVE_CONTENT_URL = `https://example.com/${RESOURCE_PATH}/mixed_active.html`;
+const MIXED_PASSIVE_CONTENT_URL = `https://example.com/${RESOURCE_PATH}/mixed_passive.html`;
+const TRACKING_PAGE = `http://tracking.example.org/${RESOURCE_PATH}/tracking.html`;
+
+this.ControlCenter = {
+ init(libDir) { },
+
+ configurations: {
+ about: {
+ applyConfig: Task.async(function* () {
+ yield loadPage("about:home");
+ yield openIdentityPopup();
+ }),
+ },
+
+ localFile: {
+ applyConfig: Task.async(function* () {
+ let channel = NetUtil.newChannel({
+ uri: "chrome://mozscreenshots/content/lib/mozscreenshots.html",
+ loadUsingSystemPrincipal: true
+ });
+ channel = channel.QueryInterface(Ci.nsIFileChannel);
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, channel.file.path);
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ yield openIdentityPopup();
+ }),
+ },
+
+ http: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(HTTP_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+
+ httpSubView: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(HTTP_PAGE);
+ yield openIdentityPopup(true);
+ }),
+ },
+
+ https: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(HTTPS_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+
+ httpsSubView: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(HTTPS_PAGE);
+ yield openIdentityPopup(true);
+ }),
+ },
+
+ singlePermission: {
+ applyConfig: Task.async(function* () {
+ let uri = Services.io.newURI(PERMISSIONS_PAGE, null, null)
+ SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
+
+ yield loadPage(PERMISSIONS_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+
+ allPermissions: {
+ applyConfig: Task.async(function* () {
+ // there are 3 possible non-default permission states, so we alternate between them
+ let states = [SitePermissions.ALLOW, SitePermissions.BLOCK, SitePermissions.SESSION];
+ let uri = Services.io.newURI(PERMISSIONS_PAGE, null, null)
+ SitePermissions.listPermissions().forEach(function (permission, index) {
+ SitePermissions.set(uri, permission, states[index % 3]);
+ });
+
+ yield loadPage(PERMISSIONS_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+
+ mixed: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(MIXED_CONTENT_URL);
+ yield openIdentityPopup();
+ }),
+ },
+
+ mixedSubView: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(MIXED_CONTENT_URL);
+ yield openIdentityPopup(true);
+ }),
+ },
+
+ mixedPassive: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(MIXED_PASSIVE_CONTENT_URL);
+ yield openIdentityPopup();
+ }),
+ },
+
+ mixedPassiveSubView: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(MIXED_PASSIVE_CONTENT_URL);
+ yield openIdentityPopup(true);
+ }),
+ },
+
+ mixedActive: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(MIXED_ACTIVE_CONTENT_URL);
+ yield openIdentityPopup();
+ }),
+ },
+
+ mixedActiveSubView: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(MIXED_ACTIVE_CONTENT_URL);
+ yield openIdentityPopup(true);
+ }),
+ },
+
+ mixedActiveUnblocked: {
+ applyConfig: Task.async(function* () {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ yield loadPage(MIXED_ACTIVE_CONTENT_URL);
+ gBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, MIXED_ACTIVE_CONTENT_URL);
+ yield openIdentityPopup();
+ }),
+ },
+
+ mixedActiveUnblockedSubView: {
+ applyConfig: Task.async(function* () {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ yield loadPage(MIXED_ACTIVE_CONTENT_URL);
+ gBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection();
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, MIXED_ACTIVE_CONTENT_URL);
+ yield openIdentityPopup(true);
+ }),
+ },
+
+ httpPassword: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(HTTP_PASSWORD_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+
+ httpPasswordSubView: {
+ applyConfig: Task.async(function* () {
+ yield loadPage(HTTP_PASSWORD_PAGE);
+ yield openIdentityPopup(true);
+ }),
+ },
+
+ trackingProtectionNoElements: {
+ applyConfig: Task.async(function* () {
+ Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
+
+ yield loadPage(HTTP_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+
+ trackingProtectionEnabled: {
+ applyConfig: Task.async(function* () {
+ Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
+ Services.prefs.setIntPref("privacy.trackingprotection.introCount", 20);
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ yield loadPage(TRACKING_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+
+ trackingProtectionDisabled: {
+ applyConfig: Task.async(function* () {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
+ Services.prefs.setIntPref("privacy.trackingprotection.introCount", 20);
+ yield UrlClassifierTestUtils.addTestTrackers();
+
+ yield loadPage(TRACKING_PAGE);
+ yield openIdentityPopup();
+ // unblock the page
+ gBrowser.ownerGlobal.document.querySelector("#tracking-action-unblock").click();
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, TRACKING_PAGE);
+ yield openIdentityPopup();
+ }),
+ },
+ },
+};
+
+function* loadPage(url) {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
+ yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, url);
+}
+
+function* openIdentityPopup(expand) {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ let { gIdentityHandler } = gBrowser.ownerGlobal;
+ gIdentityHandler._identityPopup.hidePopup();
+ gIdentityHandler._identityBox.querySelector("#identity-icon").click();
+ if (expand) {
+ // give some time for opening to avoid weird style issues
+ yield new Promise((c) => setTimeout(c, 500));
+ gIdentityHandler._identityPopup.querySelector("#identity-popup-security-expander").click();
+ }
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/CustomizeMode.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/CustomizeMode.jsm
new file mode 100644
index 000000000..bb856508b
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/CustomizeMode.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["CustomizeMode"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+this.CustomizeMode = {
+
+ init(libDir) {},
+
+ configurations: {
+ notCustomizing: {
+ applyConfig() {
+ return new Promise((resolve) => {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (!browserWindow.document.documentElement.hasAttribute("customizing")) {
+ resolve("notCustomizing: already not customizing");
+ return;
+ }
+ function onCustomizationEnds() {
+ browserWindow.gNavToolbox.removeEventListener("aftercustomization",
+ onCustomizationEnds);
+ // Wait for final changes
+ setTimeout(() => resolve("notCustomizing: onCustomizationEnds"), 500);
+ }
+ browserWindow.gNavToolbox.addEventListener("aftercustomization",
+ onCustomizationEnds);
+ browserWindow.gCustomizeMode.exit();
+ });
+ },
+ },
+
+ customizing: {
+ applyConfig() {
+ return new Promise((resolve) => {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (browserWindow.document.documentElement.hasAttribute("customizing")) {
+ resolve("customizing: already customizing");
+ return;
+ }
+ function onCustomizing() {
+ browserWindow.gNavToolbox.removeEventListener("customizationready",
+ onCustomizing);
+ // Wait for final changes
+ setTimeout(() => resolve("customizing: onCustomizing"), 500);
+ }
+ browserWindow.gNavToolbox.addEventListener("customizationready",
+ onCustomizing);
+ browserWindow.gCustomizeMode.enter();
+ });
+ },
+ },
+ },
+};
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevEdition.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevEdition.jsm
new file mode 100644
index 000000000..fd981bca3
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevEdition.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["DevEdition"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const THEME_ID = "firefox-devedition@mozilla.org";
+
+Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+this.DevEdition = {
+ init(libDir) {},
+
+ configurations: {
+ devEditionLight: {
+ applyConfig: Task.async(() => {
+ Services.prefs.setCharPref("devtools.theme", "light");
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(THEME_ID);
+ Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", true);
+ }),
+ },
+ devEditionDark: {
+ applyConfig: Task.async(() => {
+ Services.prefs.setCharPref("devtools.theme", "dark");
+ LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(THEME_ID);
+ Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", true);
+ }),
+ },
+ devEditionOff: {
+ applyConfig: Task.async(() => {
+ Services.prefs.clearUserPref("devtools.theme");
+ LightweightThemeManager.currentTheme = null;
+ Services.prefs.clearUserPref("browser.devedition.theme.showCustomizeButton");
+ }),
+ },
+ },
+};
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
new file mode 100644
index 000000000..f06000b4c
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/DevTools.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["DevTools"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://devtools/client/framework/gDevTools.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+let { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+let TargetFactory = devtools.TargetFactory;
+
+function getTargetForSelectedTab() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let target = TargetFactory.forTab(browserWindow.gBrowser.selectedTab);
+ return target;
+}
+
+this.DevTools = {
+ init(libDir) {
+ let panels = ["options", "webconsole", "jsdebugger", "styleeditor",
+ "performance", "netmonitor"];
+
+ panels.forEach(panel => {
+ this.configurations[panel] = {};
+ this.configurations[panel].applyConfig = Task.async(function* () {
+ yield gDevTools.showToolbox(getTargetForSelectedTab(), panel, "bottom");
+ yield new Promise(resolve => setTimeout(resolve, 500));
+ });
+ });
+ },
+
+ configurations: {
+ bottomToolbox: {
+ applyConfig: Task.async(function* () {
+ yield gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "bottom");
+ yield new Promise(resolve => setTimeout(resolve, 1000));
+ }),
+ },
+ sideToolbox: {
+ applyConfig: Task.async(function* () {
+ yield gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "side");
+ yield new Promise(resolve => setTimeout(resolve, 500));
+ }),
+ },
+ undockedToolbox: {
+ applyConfig: Task.async(function* () {
+ yield gDevTools.showToolbox(getTargetForSelectedTab(), "inspector", "window");
+ yield new Promise(resolve => setTimeout(resolve, 500));
+ }),
+ }
+ },
+};
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
new file mode 100644
index 000000000..52d0c2207
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["LightweightThemes"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+this.LightweightThemes = {
+ init(libDir) {
+ // convert -size 3000x200 canvas:#333 black_theme.png
+ let blackImage = libDir.clone();
+ blackImage.append("black_theme.png");
+ this._blackImageURL = Services.io.newFileURI(blackImage).spec;
+
+ // convert -size 3000x200 canvas:#eee white_theme.png
+ let whiteImage = libDir.clone();
+ whiteImage.append("white_theme.png");
+ this._whiteImageURL = Services.io.newFileURI(whiteImage).spec;
+ },
+
+ configurations: {
+ noLWT: {
+ applyConfig: Task.async(function*() {
+ LightweightThemeManager.currentTheme = null;
+ }),
+ },
+
+ darkLWT: {
+ applyConfig() {
+ LightweightThemeManager.setLocalTheme({
+ id: "black",
+ name: "black",
+ headerURL: LightweightThemes._blackImageURL,
+ footerURL: LightweightThemes._blackImageURL,
+ textcolor: "#eeeeee",
+ accentcolor: "#111111",
+ });
+
+ // Wait for LWT listener
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve("darkLWT");
+ }, 500);
+ });
+ },
+
+ verifyConfig: verifyConfigHelper,
+ },
+
+ lightLWT: {
+ applyConfig() {
+ LightweightThemeManager.setLocalTheme({
+ id: "white",
+ name: "white",
+ headerURL: LightweightThemes._whiteImageURL,
+ footerURL: LightweightThemes._whiteImageURL,
+ textcolor: "#111111",
+ accentcolor: "#eeeeee",
+ });
+ // Wait for LWT listener
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve("lightLWT");
+ }, 500);
+ });
+ },
+
+ verifyConfig: verifyConfigHelper,
+ },
+
+ },
+};
+
+
+function verifyConfigHelper() {
+ return new Promise((resolve, reject) => {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (browserWindow.document.documentElement.hasAttribute("lwtheme")) {
+ resolve("verifyConfigHelper");
+ } else {
+ reject("The @lwtheme attribute wasn't present so themes may not be available");
+ }
+ });
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
new file mode 100644
index 000000000..5e5a988c4
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PermissionPrompts"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource:///modules/E10SUtils.jsm");
+Cu.import("resource://testing-common/ContentTask.jsm");
+Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+
+const URL = "https://test1.example.com/extensions/mozscreenshots/browser/chrome/mozscreenshots/lib/permissionPrompts.html";
+let lastTab = null;
+
+this.PermissionPrompts = {
+ init(libDir) {
+ Services.prefs.setBoolPref("media.navigator.permission.fake", true);
+ Services.prefs.setBoolPref("media.getusermedia.screensharing.allow_on_old_platforms", true);
+ Services.prefs.setCharPref("media.getusermedia.screensharing.allowed_domains",
+ "test1.example.com");
+ Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
+ },
+
+ configurations: {
+ shareDevices: {
+ applyConfig: Task.async(function*() {
+ yield closeLastTab();
+ yield clickOn("#webRTC-shareDevices");
+ }),
+ },
+
+ shareMicrophone: {
+ applyConfig: Task.async(function*() {
+ yield closeLastTab();
+ yield clickOn("#webRTC-shareMicrophone");
+ }),
+ },
+
+ shareVideoAndMicrophone: {
+ applyConfig: Task.async(function*() {
+ yield closeLastTab();
+ yield clickOn("#webRTC-shareDevices2");
+ }),
+ },
+
+ shareScreen: {
+ applyConfig: Task.async(function*() {
+ yield closeLastTab();
+ yield clickOn("#webRTC-shareScreen");
+ }),
+ },
+
+ geo: {
+ applyConfig: Task.async(function*() {
+ yield closeLastTab();
+ yield clickOn("#geo");
+ }),
+ },
+
+ loginCapture: {
+ applyConfig: Task.async(function*() {
+ yield closeLastTab();
+ yield clickOn("#login-capture", URL);
+ }),
+ },
+
+ notifications: {
+ applyConfig: Task.async(function*() {
+ yield closeLastTab();
+ yield clickOn("#web-notifications", URL);
+ }),
+ },
+
+ addons: {
+ applyConfig: Task.async(function*() {
+ Services.prefs.setBoolPref("xpinstall.whitelist.required", true);
+
+ yield closeLastTab();
+ yield clickOn("#addons", URL);
+ }),
+ },
+
+ addonsNoWhitelist: {
+ applyConfig: Task.async(function*() {
+ Services.prefs.setBoolPref("xpinstall.whitelist.required", false);
+
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let notification = browserWindow.document.getElementById("addon-install-confirmation-notification");
+
+ yield closeLastTab();
+ yield clickOn("#addons", URL);
+
+ // We want to skip the progress-notification, so we wait for
+ // the install-confirmation screen to be "not hidden" = shown.
+ yield BrowserTestUtils.waitForCondition(() => !notification.hasAttribute("hidden"),
+ "addon install confirmation did not show", 200);
+ }),
+ },
+ },
+};
+
+function* closeLastTab(selector) {
+ if (!lastTab) {
+ return;
+ }
+ yield BrowserTestUtils.removeTab(lastTab);
+ lastTab = null;
+}
+
+function* clickOn(selector) {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+
+ // Save the tab so we can close it later.
+ lastTab = yield BrowserTestUtils.openNewForegroundTab(browserWindow.gBrowser, URL);
+
+ yield ContentTask.spawn(lastTab.linkedBrowser, selector, function* (arg) {
+ E10SUtils.wrapHandlingUserInput(content, true, function() {
+ let element = content.document.querySelector(arg);
+ element.click();
+ });
+ });
+
+ // Wait for the popup to actually be shown before making the screenshot
+ yield BrowserTestUtils.waitForEvent(browserWindow.PopupNotifications.panel, "popupshown");
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm
new file mode 100644
index 000000000..aa2b3f2a7
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["Preferences"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://testing-common/TestUtils.jsm");
+Cu.import("resource://testing-common/ContentTask.jsm");
+
+this.Preferences = {
+
+ init(libDir) {
+ let panes = [
+ ["paneGeneral", null],
+ ["paneSearch", null],
+ ["paneContent", null],
+ ["paneApplications", null],
+ ["panePrivacy", null],
+ ["panePrivacy", null, DNTDialog],
+ ["panePrivacy", null, clearRecentHistoryDialog],
+ ["paneSecurity", null],
+ ["paneSync", null],
+ ["paneAdvanced", "generalTab"],
+ ["paneAdvanced", "dataChoicesTab"],
+ ["paneAdvanced", "networkTab"],
+ ["paneAdvanced", "networkTab", connectionDialog],
+ ["paneAdvanced", "updateTab"],
+ ["paneAdvanced", "encryptionTab"],
+ ["paneAdvanced", "encryptionTab", certManager],
+ ["paneAdvanced", "encryptionTab", deviceManager],
+ ];
+ for (let [primary, advanced, customFn] of panes) {
+ let configName = primary.replace(/^pane/, "prefs") + (advanced ? "-" + advanced : "");
+ if (customFn) {
+ configName += "-" + customFn.name;
+ }
+ this.configurations[configName] = {};
+ this.configurations[configName].applyConfig = prefHelper.bind(null, primary, advanced, customFn);
+ }
+ },
+
+ configurations: {},
+};
+
+let prefHelper = Task.async(function*(primary, advanced = null, customFn = null) {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let selectedBrowser = browserWindow.gBrowser.selectedBrowser;
+
+ // close any dialog that might still be open
+ yield ContentTask.spawn(selectedBrowser, null, function*() {
+ if (!content.window.gSubDialog) {
+ return;
+ }
+ content.window.gSubDialog.close();
+ });
+
+ let readyPromise = null;
+ if (selectedBrowser.currentURI.specIgnoringRef == "about:preferences") {
+ if (selectedBrowser.currentURI.spec == "about:preferences#" + primary.replace(/^pane/, "")) {
+ // We're already on the correct pane.
+ readyPromise = Promise.resolve();
+ } else {
+ readyPromise = paintPromise(browserWindow);
+ }
+ } else {
+ readyPromise = TestUtils.topicObserved("advanced-pane-loaded");
+ }
+
+ if (primary == "paneAdvanced") {
+ browserWindow.openAdvancedPreferences(advanced);
+ } else {
+ browserWindow.openPreferences(primary);
+ }
+
+ yield readyPromise;
+
+ if (customFn) {
+ let customPaintPromise = paintPromise(browserWindow);
+ yield* customFn(selectedBrowser);
+ yield customPaintPromise;
+ }
+});
+
+function paintPromise(browserWindow) {
+ return new Promise((resolve) => {
+ browserWindow.addEventListener("MozAfterPaint", function onPaint() {
+ browserWindow.removeEventListener("MozAfterPaint", onPaint);
+ resolve();
+ });
+ });
+}
+
+function* DNTDialog(aBrowser) {
+ yield ContentTask.spawn(aBrowser, null, function* () {
+ content.document.getElementById("doNotTrackSettings").click();
+ });
+}
+
+function* connectionDialog(aBrowser) {
+ yield ContentTask.spawn(aBrowser, null, function* () {
+ content.document.getElementById("connectionSettings").click();
+ });
+}
+
+function* clearRecentHistoryDialog(aBrowser) {
+ yield ContentTask.spawn(aBrowser, null, function* () {
+ content.document.getElementById("historyRememberClear").click();
+ });
+}
+
+function* certManager(aBrowser) {
+ yield ContentTask.spawn(aBrowser, null, function* () {
+ content.document.getElementById("viewCertificatesButton").click();
+ });
+}
+
+function* deviceManager(aBrowser) {
+ yield ContentTask.spawn(aBrowser, null, function* () {
+ content.document.getElementById("viewSecurityDevicesButton").click();
+ });
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
new file mode 100644
index 000000000..f2ff43561
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Tabs.jsm
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If 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 = ["Tabs"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const CUST_TAB = "chrome://browser/skin/customizableui/customizeFavicon.ico";
+const PREFS_TAB = "chrome://browser/skin/preferences/in-content/favicon.ico";
+const DEFAULT_FAVICON_TAB = `data:text/html,<meta charset="utf-8">
+<title>No favicon</title>`;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+this.Tabs = {
+ init(libDir) {},
+
+ configurations: {
+ fiveTabs: {
+ applyConfig: Task.async(function*() {
+ fiveTabsHelper();
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ hoverTab(browserWindow.gBrowser.tabs[3]);
+ yield new Promise((resolve, reject) => {
+ setTimeout(resolve, 3000);
+ });
+ }),
+ },
+
+ fourPinned: {
+ applyConfig: Task.async(function*() {
+ fiveTabsHelper();
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let tab = browserWindow.gBrowser.addTab(PREFS_TAB);
+ browserWindow.gBrowser.pinTab(tab);
+ tab = browserWindow.gBrowser.addTab(CUST_TAB);
+ browserWindow.gBrowser.pinTab(tab);
+ tab = browserWindow.gBrowser.addTab("about:privatebrowsing");
+ browserWindow.gBrowser.pinTab(tab);
+ tab = browserWindow.gBrowser.addTab("about:home");
+ browserWindow.gBrowser.pinTab(tab);
+ browserWindow.gBrowser.selectTabAtIndex(5);
+ hoverTab(browserWindow.gBrowser.tabs[2]);
+ // also hover the new tab button
+ let newTabButton = browserWindow.document.getAnonymousElementByAttribute(browserWindow.
+ gBrowser.tabContainer, "class", "tabs-newtab-button");
+ hoverTab(newTabButton);
+ browserWindow.gBrowser.tabs[browserWindow.gBrowser.tabs.length - 1].
+ setAttribute("beforehovered", true);
+ yield new Promise((resolve, reject) => {
+ setTimeout(resolve, 3000);
+ });
+ }),
+ },
+
+ twoPinnedWithOverflow: {
+ applyConfig: Task.async(function*() {
+ fiveTabsHelper();
+
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ browserWindow.gBrowser.loadTabs([
+ PREFS_TAB,
+ CUST_TAB,
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ "about:addons",
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ "about:addons",
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ "about:addons",
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ "about:addons",
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ "about:addons",
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ "about:addons",
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ ], true, true);
+ browserWindow.gBrowser.pinTab(browserWindow.gBrowser.tabs[1]);
+ browserWindow.gBrowser.pinTab(browserWindow.gBrowser.tabs[2]);
+ browserWindow.gBrowser.selectTabAtIndex(3);
+ hoverTab(browserWindow.gBrowser.tabs[5]);
+ yield new Promise((resolve, reject) => {
+ setTimeout(resolve, 3000);
+ });
+ }),
+ },
+ },
+};
+
+
+/* helpers */
+
+function fiveTabsHelper() {
+ // some with no favicon and some with. Selected tab in middle.
+ closeAllButOneTab("about:addons");
+
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ browserWindow.gBrowser.loadTabs([
+ "about:addons",
+ "about:home",
+ DEFAULT_FAVICON_TAB,
+ "about:newtab",
+ CUST_TAB,
+ ], true, true);
+ browserWindow.gBrowser.selectTabAtIndex(1);
+}
+
+function closeAllButOneTab(url = "about:blank") {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let gBrowser = browserWindow.gBrowser;
+ // Close all tabs except the last so we don't quit the browser.
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+ gBrowser.selectedBrowser.loadURI(url);
+ if (gBrowser.selectedTab.pinned)
+ gBrowser.unpinTab(gBrowser.selectedTab);
+ let newTabButton = browserWindow.document.getAnonymousElementByAttribute(browserWindow.gBrowser.tabContainer, "class", "tabs-newtab-button");
+ hoverTab(newTabButton, false);
+}
+
+function hoverTab(tab, hover = true) {
+ const inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+ if (hover) {
+ inIDOMUtils.addPseudoClassLock(tab, ":hover");
+ } else {
+ inIDOMUtils.clearPseudoClassLocks(tab);
+ }
+ // XXX TODO: this isn't necessarily testing what we ship
+ if (tab.nextElementSibling)
+ tab.nextElementSibling.setAttribute("afterhovered", hover || null);
+ if (tab.previousElementSibling)
+ tab.previousElementSibling.setAttribute("beforehovered", hover || null);
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/TabsInTitlebar.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/TabsInTitlebar.jsm
new file mode 100644
index 000000000..5b9d7d9b9
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/TabsInTitlebar.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["TabsInTitlebar"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const PREF_TABS_IN_TITLEBAR = "browser.tabs.drawInTitlebar";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+this.TabsInTitlebar = {
+
+ init(libDir) {},
+
+ configurations: {
+ tabsInTitlebar: {
+ applyConfig: Task.async(function*() {
+ if (Services.appinfo.OS == "Linux") {
+ return Promise.reject("TabsInTitlebar isn't supported on Linux");
+ }
+ Services.prefs.setBoolPref(PREF_TABS_IN_TITLEBAR, true);
+ return undefined;
+ }),
+ },
+
+ tabsOutsideTitlebar: {
+ applyConfig: Task.async(function*() {
+ Services.prefs.setBoolPref(PREF_TABS_IN_TITLEBAR, false);
+ }),
+ },
+
+ },
+};
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Toolbars.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Toolbars.jsm
new file mode 100644
index 000000000..ee284bf97
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Toolbars.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = ["Toolbars"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+this.Toolbars = {
+ init(libDir) {},
+
+ configurations: {
+ onlyNavBar: {
+ applyConfig: Task.async(function*() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let personalToolbar = browserWindow.document.getElementById("PersonalToolbar");
+ browserWindow.setToolbarVisibility(personalToolbar, false);
+ toggleMenubarIfNecessary(false);
+ }),
+ },
+
+ allToolbars: {
+ applyConfig: Task.async(function*() { // Boookmarks and menubar
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ let personalToolbar = browserWindow.document.getElementById("PersonalToolbar");
+ browserWindow.setToolbarVisibility(personalToolbar, true);
+ toggleMenubarIfNecessary(true);
+ }),
+
+ verifyConfig: Task.async(function*() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (browserWindow.fullScreen) {
+ return Promise.reject("The bookmark toolbar and menubar are not shown in fullscreen.");
+ }
+ return undefined;
+ }),
+ },
+
+ },
+};
+
+
+// helpers
+
+function toggleMenubarIfNecessary(visible) {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ // The menubar is not shown on OS X or while in fullScreen
+ if (Services.appinfo.OS != "Darwin" /* && !browserWindow.fullScreen*/) {
+ let menubar = browserWindow.document.getElementById("toolbar-menubar");
+ browserWindow.setToolbarVisibility(menubar, visible);
+ }
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/WindowSize.jsm b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/WindowSize.jsm
new file mode 100644
index 000000000..5cc0d6522
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/WindowSize.jsm
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["WindowSize"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+
+this.WindowSize = {
+
+ init(libDir) {
+ Services.prefs.setBoolPref("browser.fullscreen.autohide", false);
+ },
+
+ configurations: {
+ maximized: {
+ applyConfig: Task.async(function*() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ yield toggleFullScreen(browserWindow, false);
+
+ // Wait for the Lion fullscreen transition to end as there doesn't seem to be an event
+ // and trying to maximize while still leaving fullscreen doesn't work.
+ yield new Promise((resolve, reject) => {
+ setTimeout(function waitToLeaveFS() {
+ browserWindow.maximize();
+ resolve();
+ }, 5000);
+ });
+ }),
+ },
+
+ normal: {
+ applyConfig: Task.async(function*() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ yield toggleFullScreen(browserWindow, false);
+ browserWindow.restore();
+ yield new Promise((resolve, reject) => {
+ setTimeout(resolve, 5000);
+ });
+ }),
+ },
+
+ fullScreen: {
+ applyConfig: Task.async(function*() {
+ let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ yield toggleFullScreen(browserWindow, true);
+ // OS X Lion fullscreen transition takes a while
+ yield new Promise((resolve, reject) => {
+ setTimeout(resolve, 5000);
+ });
+ }),
+ },
+ },
+};
+
+function toggleFullScreen(browserWindow, wantsFS) {
+ browserWindow.fullScreen = wantsFS;
+ return BrowserTestUtils.waitForCondition(() => {
+ return wantsFS == browserWindow.document.documentElement.hasAttribute("inFullscreen");
+ }, "waiting for @inFullscreen change");
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/install.rdf b/browser/tools/mozscreenshots/mozscreenshots/extension/install.rdf
new file mode 100644
index 000000000..429a35840
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/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>mozscreenshots@mozilla.org</em:id>
+#expand <em:version>__MOZILLA_VERSION_U__</em:version>
+ <em:bootstrap>true</em:bootstrap>
+
+ <!-- for running custom screenshot binaries -->
+ <em:unpack>true</em:unpack>
+
+ <!-- Front End MetaData -->
+ <em:name>mozscreenshots</em:name>
+ <em:description>Take screenshots of Mozilla applications in various UI configurations.</em:description>
+ <em:creator>Mozilla</em:creator>
+
+ <em:targetApplication>
+ <Description>
+ <!-- Firefox -->
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+#expand <em:minVersion>__MOZILLA_VERSION_U__</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ </Description>
+</RDF>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/jar.mn b/browser/tools/mozscreenshots/mozscreenshots/extension/jar.mn
new file mode 100644
index 000000000..0f403013a
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/jar.mn
@@ -0,0 +1,6 @@
+mozscreenshots.jar:
+% content mozscreenshots chrome/mozscreenshots/
+ Screenshot.jsm
+ TestRunner.jsm
+ configurations/ (configurations/*.jsm)
+ lib/ (lib/*)
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/black_theme.png b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/black_theme.png
new file mode 100644
index 000000000..78e47207b
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/black_theme.png
Binary files differ
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/borderify.xpi b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/borderify.xpi
new file mode 100644
index 000000000..66ae92ed2
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/borderify.xpi
Binary files differ
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed.html b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed.html
new file mode 100644
index 000000000..9d2ef75c0
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Mixed Content test</title>
+ </head>
+ <body>
+ <iframe style="visibility:hidden" src="http://example.com"></iframe>
+ <img style="visibility:hidden" src="http://example.com/tests/image/test/mochitest/blue.png"></img>
+ </body>
+</html>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_active.html b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_active.html
new file mode 100644
index 000000000..f43ff079f
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_active.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Mixed Active Content test</title>
+ </head>
+ <body>
+ <iframe style="visibility:hidden" src="http://example.com"></iframe>
+ </body>
+</html>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_passive.html b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_passive.html
new file mode 100644
index 000000000..ba8978267
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/mixed_passive.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Mixed Passive Content test</title>
+ </head>
+ <body>
+ <img style="visibility:hidden" src="http://example.com/tests/image/test/mochitest/blue.png"></img>
+ </body>
+</html>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/password.html b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/password.html
new file mode 100644
index 000000000..010384270
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/password.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>HTTP Password test</title>
+ </head>
+ <body>
+ <form>
+ <input type="password" />
+ <button type="submit">Submit</button>
+ </form>
+ </body>
+</html>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/tracking.html b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/tracking.html
new file mode 100644
index 000000000..6fb230645
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/controlCenter/tracking.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Tracking test</title>
+ </head>
+ <body>
+ <iframe style="visibility:hidden" src="http://tracking.example.com/"></iframe>
+ </body>
+</html>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-script.js b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-script.js
new file mode 100644
index 000000000..66cdd8c86
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-script.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+console.log(document, document.body);
+console.assert(false, "Failing mozscreenshots assertion");
+
+console.group("Grouped Message");
+console.log("group message 1");
+console.groupEnd("Grouped Message");
+
+console.count("counter");
+console.count("counter");
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-style.css b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-style.css
new file mode 100644
index 000000000..145a4e57d
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-style.css
@@ -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/. */
+
+body {
+ --background-color: #f3f3f3;
+ background: tomato;
+}
+
+body {
+ background: var(--background-color);
+ color: #222;
+ padding: 0 10px;
+ margin: 0;
+}
+
+header {
+ background: #eee;
+ border-bottom: solid 2px #ccc;
+ margin: 0 -10px;
+ margin-bottom: 5px;
+ padding: 4px;
+}
+
+header h1 {
+ margin: 0;
+ padding: 0;
+}
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots.html b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots.html
new file mode 100644
index 000000000..6778cf7da
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots.html
@@ -0,0 +1,36 @@
+<!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>
+ <head>
+ <meta charset="UTF-8">
+ <title>mozscreenshots</title>
+ <link rel="stylesheet" href="mozscreenshots-style.css" />
+ <script>console.info("This page was generated by mozscreenshots");</script>
+ </head>
+
+<body>
+ <header><h1>mozscreenshots</h1></header>
+
+ <p>
+ This page was generated by mozscreenshots
+ </p>
+
+ <img src="robot.png" />
+
+ <p>Welcome Humans! We invite others to keep the Manifesto’s principles; use the creation delivery and commitment. Advancing the Manifesto. We have shiny metal posteriors which <strong>should not be bitten</strong>. And they have distilled a public benefit is committed to use Mozilla project one basic and communities of modern life.</p>
+
+ <p>Robots have <mark>distilled a balance between commercial aspects of life</mark>.</p>
+
+ <p>Robots may not they have shown in education communication collaboration business entertainment and other people to pursue; speak to continue; and opportunity are many different ways to benefit the public benefit the Manifesto’s principles; build and motivate us and trademarks infrastructure funds and trademarks infrastructure funds and enable open-source technologies and provide a whole. </p>
+
+ <p>And they have seen things you people who believe that Mozilla Manifesto. We are to: articulate a vision of individual human being to benefit the lives of these efforts we will: build and promote models for creating economic value for the Internet. We create world-class open and anticipate the Mozilla Manifesto <strong>There are Your Plastic Pal Who's Fun To Be With</strong></p>
+
+ <p>Some Foundation to advance this vision of individual human being or not deeply involved in groups and promote models for the Manifesto principles will not come to support the Mozilla Foundation Pledge The Mozilla Manifesto in its activities. People are to: articulate a set of consumer products that support <mark>the Internet is a human being</mark> or not be treated as individuals working together in the development of the Internet open and with goodwill!Specifically we believe that we will: build and society as a public good as a result of the lives of collaborative activities. Specifically we have seen things you people acting as optional. Individuals must not come to continue to develop new ways of the Internet are fundamental and with us to ensure that openness innovation and very effective way that the Manifesto There are many benefits; a global public benefit; and society as optional.</p>
+
+ <p><strong>We have metal posteriors which should not deeply involved in a reality.</strong> Individuals must remain open source software promotes the Internet is a balance between commercial profit and within the Mozilla Corporation. Invitation The Internet are key to join us to life on their own. The Internet a whole. The Internet as a vision of the Mozilla contributors proud of time attention and motivate us and provide a reality.</p>
+
+ <script src="mozscreenshots-script.js"></script>
+</body>
+</html>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/permissionPrompts.html b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/permissionPrompts.html
new file mode 100644
index 000000000..de8310666
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/permissionPrompts.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Permission Prompts</title>
+</head>
+<body>
+ <button id="geo" onclick="navigator.geolocation.getCurrentPosition(() => {})">Geolocation</button>
+ <button id="webRTC-shareDevices" onclick="shareDevice({video: true, fake: true});">Video</button>
+ <button id="webRTC-shareMicrophone" onclick="shareDevice({audio: true, fake: true});">Audio</button>
+ <button id="webRTC-shareDevices2" onclick="shareDevice({audio: true, video: true, fake: true});">Audio and Video</button>
+ <button id="webRTC-shareScreen" onclick="shareDevice({video: {mediaSource: 'screen'}});">Screen</button>
+ <button id="web-notifications" onclick="Notification.requestPermission()">web-notifications</button>
+ <a id="addons" href="borderify.xpi">Install Add-On</a>
+ <form>
+ <input type="email" id="email" value="email@example.com" />
+ <input type="password" id="password" value="123456" />
+ <button type="submit" id="login-capture">Login</button>
+ </form>
+
+ <script type="application/javascript">
+ // Share device used in onclick calls above.
+ /* exported shareDevice */
+ function shareDevice(config) {
+ navigator.mediaDevices.getUserMedia(config);
+ }
+ </script>
+
+</body>
+</html>
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/robot.png b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/robot.png
new file mode 100644
index 000000000..1c4899aaf
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/robot.png
Binary files differ
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/lib/white_theme.png b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/white_theme.png
new file mode 100644
index 000000000..6b0c501a1
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/lib/white_theme.png
Binary files differ
diff --git a/browser/tools/mozscreenshots/mozscreenshots/extension/moz.build b/browser/tools/mozscreenshots/mozscreenshots/extension/moz.build
new file mode 100644
index 000000000..d23c7e925
--- /dev/null
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPI_NAME = 'mozscreenshots'
+
+JAR_MANIFESTS += ['jar.mn']
+
+USE_EXTENSION_MANIFEST = True
+NO_JS_MANIFEST = True
+
+FINAL_TARGET_PP_FILES += [
+ 'bootstrap.js',
+ 'install.rdf',
+]
diff --git a/browser/tools/mozscreenshots/permissionPrompts/browser.ini b/browser/tools/mozscreenshots/permissionPrompts/browser.ini
new file mode 100644
index 000000000..5cbe196c8
--- /dev/null
+++ b/browser/tools/mozscreenshots/permissionPrompts/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = screenshots
+support-files =
+ ../head.js
+
+[browser_permissionPrompts.js]
diff --git a/browser/tools/mozscreenshots/permissionPrompts/browser_permissionPrompts.js b/browser/tools/mozscreenshots/permissionPrompts/browser_permissionPrompts.js
new file mode 100644
index 000000000..af63217f2
--- /dev/null
+++ b/browser/tools/mozscreenshots/permissionPrompts/browser_permissionPrompts.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/. */
+
+/* import-globals-from ../head.js */
+
+"use strict";
+
+add_task(function* capture() {
+ if (!shouldCapture()) {
+ return;
+ }
+ let sets = ["LightweightThemes", "PermissionPrompts"];
+
+ yield TestRunner.start(sets, "permissionPrompts");
+});
diff --git a/browser/tools/mozscreenshots/preferences/browser.ini b/browser/tools/mozscreenshots/preferences/browser.ini
new file mode 100644
index 000000000..975d91681
--- /dev/null
+++ b/browser/tools/mozscreenshots/preferences/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = screenshots
+support-files =
+ ../head.js
+
+[browser_preferences.js]
diff --git a/browser/tools/mozscreenshots/preferences/browser_preferences.js b/browser/tools/mozscreenshots/preferences/browser_preferences.js
new file mode 100644
index 000000000..c590f194f
--- /dev/null
+++ b/browser/tools/mozscreenshots/preferences/browser_preferences.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/. */
+
+/* import-globals-from ../head.js */
+
+"use strict";
+
+add_task(function* capture() {
+ if (!shouldCapture()) {
+ return;
+ }
+ let sets = ["Preferences"];
+
+ yield TestRunner.start(sets, "preferences");
+});
diff --git a/browser/tools/mozscreenshots/primaryUI/browser.ini b/browser/tools/mozscreenshots/primaryUI/browser.ini
new file mode 100644
index 000000000..628520b23
--- /dev/null
+++ b/browser/tools/mozscreenshots/primaryUI/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = screenshots
+support-files =
+ ../head.js
+
+[browser_primaryUI.js]
diff --git a/browser/tools/mozscreenshots/primaryUI/browser_primaryUI.js b/browser/tools/mozscreenshots/primaryUI/browser_primaryUI.js
new file mode 100644
index 000000000..6d0eaa1d6
--- /dev/null
+++ b/browser/tools/mozscreenshots/primaryUI/browser_primaryUI.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/. */
+
+/* import-globals-from ../head.js */
+
+"use strict";
+
+add_task(function* capture() {
+ if (!shouldCapture()) {
+ return;
+ }
+
+ requestLongerTimeout(20);
+
+ let sets = ["TabsInTitlebar", "Tabs", "WindowSize", "Toolbars", "LightweightThemes"];
+ yield TestRunner.start(sets, "primaryUI");
+});